diff --git a/.gitignore b/.gitignore index faeda6aac4..bb5e459fd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ -fastlane/Appfile -fastlane/Fastfile fastlane/README.md fastlane/report.xml -fastlane/actions/hockey_sparkle.rb -fastlane/sparkle/dsa_priv.pem -fastlane/sparkle/dsa_pub.pem -fastlane/sparkle/sign_update -Telegram-Mac/Config.swift +xcuserdata +xcuserstate +.DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..45cdbc04b4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,64 @@ +before_script: + - bash ./buildbox/sync-toolbox.sh + +stages: + - deploy + +variables: + LANG: "en_US.UTF-8" + LC_ALL: "en_US.UTF-8" + GIT_SUBMODULE_STRATEGY: recursive + GIT_STRATEGY: fetch + + +beta: + tags: + - beta + stage: deploy + only: + - beta + except: + - tags + script: + - bash ./buildbox/internal.sh beta + environment: + name: beta + +alpha: + tags: + - alpha + stage: deploy + only: + - alpha + except: + - tags + script: + - bash ./buildbox/internal.sh alpha + environment: + name: alpha + +appstore: + tags: + - appstore + stage: deploy + only: + - appstore + except: + - tags + script: + - bash ./buildbox/internal.sh appstore + environment: + name: appstore + +release: + tags: + - release + stage: deploy + only: + - release + except: + - tags + script: + - bash ./buildbox/internal.sh release + environment: + name: release diff --git a/.gitmodules b/.gitmodules index affda3b991..c07bf6978c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,15 @@ -[submodule "submodules/Postbox"] - path = submodules/Postbox - url = https://github.com/peter-iakovlev/Postbox.git -[submodule "submodules/TelegramCore"] - path = submodules/TelegramCore - url = https://github.com/peter-iakovlev/TelegramCore.git -[submodule "Signals"] - path = Signals - url = https://github.com/peter-iakovlev/Signals.git -[submodule "submodules/Signals"] - path = submodules/Signals - url = https://github.com/peter-iakovlev/Signals.git -[submodule "submodules/MtProtoKit"] - path = submodules/MtProtoKit - url = https://github.com/peter-iakovlev/MtProtoKit.git [submodule "submodules/libtgvoip"] path = submodules/libtgvoip - url = https://github.com/grishka/libtgvoip +url=git@github.com:telegramdesktop/libtgvoip +[submodule "submodules/Sparkle"] + path = submodules/Sparkle +url=https://github.com/overtake/Sparkle +[submodule "submodules/Zip"] + path = submodules/Zip + url = https://github.com/overtake/Zip +[submodule "submodules/telegram-ios"] + path = submodules/telegram-ios +url=git@github.com:TelegramMessenger/Telegram-iOS.git +[submodule "submodules/rlottie"] + path = submodules/rlottie +url=git@github.com:overtake/rlottie.git diff --git a/HockeySDK.framework/Headers b/HockeySDK.framework/Headers deleted file mode 120000 index a177d2a6b9..0000000000 --- a/HockeySDK.framework/Headers +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Headers \ No newline at end of file diff --git a/HockeySDK.framework/HockeySDK b/HockeySDK.framework/HockeySDK deleted file mode 120000 index 8b45692e61..0000000000 --- a/HockeySDK.framework/HockeySDK +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/HockeySDK \ No newline at end of file diff --git a/HockeySDK.framework/Modules b/HockeySDK.framework/Modules deleted file mode 120000 index 5736f3186e..0000000000 --- a/HockeySDK.framework/Modules +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Modules \ No newline at end of file diff --git a/HockeySDK.framework/PrivateHeaders b/HockeySDK.framework/PrivateHeaders deleted file mode 120000 index d8e5645269..0000000000 --- a/HockeySDK.framework/PrivateHeaders +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/PrivateHeaders \ No newline at end of file diff --git a/HockeySDK.framework/Resources b/HockeySDK.framework/Resources deleted file mode 120000 index 953ee36f3b..0000000000 --- a/HockeySDK.framework/Resources +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Resources \ No newline at end of file diff --git a/HockeySDK.framework/Versions/A/Headers/BITCrashDetails.h b/HockeySDK.framework/Versions/A/Headers/BITCrashDetails.h deleted file mode 100644 index a6ceecd83d..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITCrashDetails.h +++ /dev/null @@ -1,73 +0,0 @@ -#import - -/** - * Provides details about the crash that occured in the previous app session - */ -@interface BITCrashDetails : NSObject - -/** - * UUID for the crash report - */ -@property (nonatomic, readonly, copy) NSString *incidentIdentifier; - -/** - * UUID for the app installation on the device - */ -@property (nonatomic, readonly, copy) NSString *reporterKey; - -/** - * Signal that caused the crash - */ -@property (nonatomic, readonly, copy) NSString *signal; - -/** - * Exception name that triggered the crash, nil if the crash was not caused by an exception - */ -@property (nonatomic, readonly, copy) NSString *exceptionName; - -/** - * Exception reason, nil if the crash was not caused by an exception - */ -@property (nonatomic, readonly, copy) NSString *exceptionReason; - -/** - * Date and time the app started, nil if unknown - */ -@property (nonatomic, readonly, copy) NSDate *appStartTime; - -/** - * Date and time the crash occured, nil if unknown - */ -@property (nonatomic, readonly, copy) NSDate *crashTime; - -/** - * Operation System version string the app was running on when it crashed. - */ -@property (nonatomic, readonly, copy) NSString *osVersion; - -/** - * Operation System build string the app was running on when it crashed - * - * This may be unavailable. - */ -@property (nonatomic, readonly, copy) NSString *osBuild; - -/** - * CFBundleShortVersionString value of the app that crashed - * - * Can be `nil` if the crash was captured with an older version of the SDK - * or if the app doesn't set the value. - */ -@property (nonatomic, readonly, copy) NSString *appVersion; - -/** - * CFBundleVersion value of the app that crashed - */ -@property (nonatomic, readonly, copy) NSString *appBuild; - -/** - * Identifier of the app process that crashed - */ -@property (nonatomic, readonly, assign) NSUInteger appProcessIdentifier; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITCrashExceptionApplication.h b/HockeySDK.framework/Versions/A/Headers/BITCrashExceptionApplication.h deleted file mode 100644 index 6c55e9a0dc..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITCrashExceptionApplication.h +++ /dev/null @@ -1,127 +0,0 @@ -#import - -/** - * `NSApplication` subclass to catch additional exceptions - * - * On OS X runtime not all uncaught exceptions do end in an custom `NSUncaughtExceptionHandler`. - * In addition "sometimes" exceptions don't even cause the app to crash, depending on where and - * when they happen. - * - * Here are the known scenarios: - * - * 1. Custom `NSUncaughtExceptionHandler` don't start working until after `NSApplication` has finished - * calling all of its delegate methods! - * - * Example: - * - (void)applicationDidFinishLaunching:(NSNotification *)note { - * ... - * [NSException raise:@"ExceptionAtStartup" format:@"This will not be recognized!"]; - * ... - * } - * - * - * 2. The default `NSUncaughtExceptionHandler` in `NSApplication` only logs exceptions to the console and - * ends their processing. Resulting in exceptions that occur in the `NSApplication` "scope" not - * occurring in a registered custom `NSUncaughtExceptionHandler`. - * - * Example: - * - (void)applicationDidFinishLaunching:(NSNotification *)note { - * ... - * [self performSelector:@selector(delayedException) withObject:nil afterDelay:5]; - * ... - * } - * - * - (void)delayedException { - * NSArray *array = [NSArray array]; - * [array objectAtIndex:23]; - * } - * - * 3. Any exceptions occurring in IBAction or other GUI does not even reach the NSApplication default - * UncaughtExceptionHandler. - * - * Example: - * - (IBAction)doExceptionCrash:(id)sender { - * NSArray *array = [NSArray array]; - * [array objectAtIndex:23]; - * } - * - * - * Solution A: - * - * Implement `NSExceptionHandler` and set the `ExceptionHandlingMask` to `NSLogAndHandleEveryExceptionMask` - * - * Benefits: - * - * 1. Solves all of the above scenarios - * - * 2. Clean solution using a standard Cocoa System specifically meant for this purpose. - * - * 3. Safe. Doesn't use private API. - * - * Problems: - * - * 1. To catch all exceptions the `NSExceptionHandlers` mask has to include `NSLogOtherExceptionMask` and - * `NSHandleOtherExceptionMask`. But this will result in @catch blocks to be called after the exception - * handler processed the exception and likely lets the app crash and create a crash report. - * This makes the @catch block basically not working at all. - * - * 2. If anywhere in the app a custom `NSUncaughtExceptionHandler` will be registered, e.g. in a closed source - * library the develop has to use, the complete mechanism will stop working - * - * 3. Not clear if this solves all scenarios there can be. - * - * 4. Requires to adjust PLCrashReporter not to register its `NSUncaughtExceptionHandler` which is not a good idea, - * since it would require the `NSExceptionHandler` would catch *all* exceptions and that would cause - * PLCrashReporter to stop all running threads every time an exception occurs even if will be handled right - * away, e.g. by a system framework. - * - * - * Solution B: - * - * Overwrite and extend specific methods of `NSApplication`. Can be implemented via subclassing NSApplication or - * by using a category. - * - * Benefits: - * - * 1. Solves scenarios 2 (by overwriting `reportException:`) and 3 (by overwriting `sendEvent:`) - * - * 2. Subclassing approach isn't enforcing the mechanism onto apps and let developers opt-in. - * (Category approach would enforce it and rather be a problem of this soltuion.) - * - * 3. Safe. Doesn't use private API. - * - * Problems: - * - * 1. Does not automatically solve scenario 1. Developer would have to put all that code into @try @catch blocks - * - * 2. Not a clean implementation, rather feels like a workaround. - * - * 3. Not clear if this solves all scenarios there can be. - * - * - * Chosen Solution: B via subclassing - * - * Reasons: - * - * 1. The Problems 1. and 2. of Solution A are too drastic and aren't acceptable for every developer using this SDK - * Especially Problem 1 is a big No Go for lots of developers. - * - * 2. Solution B can be used optionally, can be adopted easily into developers own `NSApplication` subclasses and - * by implementing it in a subclass instead of a category isn't enforced even though it requires additional - * steps for setup. - * - * 3. The not covered Scenario 1. can be achieved by the developer by enclosing most of the code within - * NSApplication startup delegates in @try @catch blocks or moving as much code as possible out of these - * methods and deferring their execution, e.g. using background threads. Not ideal though. - * - * - * References: - * https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/Exceptions/Tasks/ControllingAppResponse.html#//apple_ref/doc/uid/20000473-BBCHGJIJ - * http://stackoverflow.com/a/4199717/474794 - * http://stackoverflow.com/a/3419073/474794 - * http://macdevcenter.com/pub/a/mac/2007/07/31/understanding-exceptions-and-handlers-in-cocoa.html - * - */ -@interface BITCrashExceptionApplication : NSApplication - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITCrashManager.h b/HockeySDK.framework/Versions/A/Headers/BITCrashManager.h deleted file mode 100644 index 88cc0f004e..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITCrashManager.h +++ /dev/null @@ -1,279 +0,0 @@ -#import - -#import "BITHockeyBaseManager.h" - -// flags if the crashreporter is activated at all -// set this as bool in user defaults e.g. in the settings, if you want to let the user be able to deactivate it -#define kHockeySDKCrashReportActivated @"HockeySDKCrashReportActivated" - -// flags if the crashreporter should automatically send crashes without asking the user again -// set this as bool in user defaults e.g. in the settings, if you want to let the user be able to set this on or off -// or set it on runtime using the `autoSubmitCrashReport property` -#define kHockeySDKAutomaticallySendCrashReports @"HockeySDKAutomaticallySendCrashReports" - -@protocol BITCrashManagerDelegate; - -@class BITCrashDetails; -@class BITCrashMetaData; -@class BITCrashReportUI; - - -/** - * Custom block that handles the alert that prompts the user whether he wants to send crash reports - * - * @param crashReportText The textual representation of the crash report - * @param applicationLog The application log that will be attached to the crash report - */ -typedef void(^BITCustomCrashReportUIHandler)(NSString *crashReportText, NSString *applicationLog); - - -/** - * Prototype of a callback function used to execute additional user code. Called upon completion of crash - * handling, after the crash report has been written to disk. - * - * @param context The API client's supplied context value. - * - * @see `BITCrashManagerCallbacks` - * @see `[BITCrashManager setCrashCallbacks:]` - */ -typedef void (*BITCrashManagerPostCrashSignalCallback)(void *context); - -/** - * This structure contains callbacks supported by `BITCrashManager` to allow the host application to perform - * additional tasks prior to program termination after a crash has occured. - * - * @see `BITCrashManagerPostCrashSignalCallback` - * @see `[BITCrashManager setCrashCallbacks:]` - */ -typedef struct BITCrashManagerCallbacks { - /** An arbitrary user-supplied context value. This value may be NULL. */ - void *context; - - /** - * The callback used to report caught signal information. - */ - BITCrashManagerPostCrashSignalCallback handleSignal; -} BITCrashManagerCallbacks; - -/** - * Crash Manager alert user input - */ -typedef NS_ENUM(NSUInteger, BITCrashManagerUserInput) { - /** - * User chose not to send the crash report - */ - BITCrashManagerUserInputDontSend = 0, - /** - * User wants the crash report to be sent - */ - BITCrashManagerUserInputSend = 1, - /** - * User chose to always send crash reports - */ - BITCrashManagerUserInputAlwaysSend = 2 - -}; - - -/** - * The crash reporting module. - * - * This is the HockeySDK module for handling crash reports, including when distributed via the App Store. - * As a foundation it is using the open source, reliable and async-safe crash reporting framework - * [PLCrashReporter](https://www.plcrashreporter.org). - * - * This module works as a wrapper around the underlying crash reporting framework and provides functionality to - * detect new crashes, queues them if networking is not available, present a user interface to approve sending - * the reports to the HockeyApp servers and more. - * - * It also provides options to add additional meta information to each crash report, like `userName`, `userEmail`, - * additional textual log information via `BITCrashanagerDelegate` protocol and a way to detect startup - * crashes so you can adjust your startup process to get these crash reports too and delay your app initialization. - * - * Crashes are send the next time the app starts. If `autoSubmitCrashReport` is enabled, crashes will be send - * without any user interaction, otherwise an alert will appear allowing the users to decide whether they want - * to send the report or not. This module is not sending the reports right when the crash happens - * deliberately, because if is not safe to implement such a mechanism while being async-safe (any Objective-C code - * is _NOT_ async-safe!) and not causing more danger like a deadlock of the device, than helping. We found that users - * do start the app again because most don't know what happened, and you will get by far most of the reports. - * - * Sending the reports on startup is done asynchronously (non-blocking) if the crash happened outside of the - * time defined in `maxTimeIntervalOfCrashForReturnMainApplicationDelay`. - * - * More background information on this topic can be found in the following blog post by Landon Fuller, the - * developer of [PLCrashReporter](https://www.plcrashreporter.org), about writing reliable and - * safe crash reporting: [Reliable Crash Reporting](http://goo.gl/WvTBR) - * - * @warning If you start the app with the Xcode debugger attached, detecting crashes will _NOT_ be enabled! - */ -@interface BITCrashManager : BITHockeyBaseManager - - -///----------------------------------------------------------------------------- -/// @name Configuration -///----------------------------------------------------------------------------- - -/** - * Defines if the build in crash report UI should ask for name and email - * - * Default: _YES_ - */ -@property (nonatomic, assign) BOOL askUserDetails; - - -/** - * Trap fatal signals via a Mach exception server. This is now used by default! - * - * Default: _YES_ - * - * @deprecated Mach Exception Handler is now enabled by default! - */ -@property (nonatomic, assign, getter=isMachExceptionHandlerEnabled) BOOL enableMachExceptionHandler __attribute__((deprecated("Mach Exceptions are now enabled by default. If you want to disable them, please use the new property disableMachExceptionHandler"))); - - -/** - * Disable trap fatal signals via a Mach exception server. - * - * By default the SDK is catching fatal signals via a Mach exception server. - * This option allows you to use in-process BSD Signals for catching crashes instead. - * - * Default: _NO_ - * - * @warning The Mach exception handler executes in-process, and will interfere with debuggers when - * they attempt to suspend all active threads (which will include the Mach exception handler). - * Mach-based handling should _NOT_ be used when a debugger is attached. The SDK will not - * enable catching exceptions if the app is started with the debugger running. If you attach - * the debugger during runtime, this may cause issues if it is not disabled! - */ -@property (nonatomic, assign, getter=isMachExceptionHandlerDisabled) BOOL disableMachExceptionHandler; - - -/** - * Submit crash reports without asking the user - * - * _YES_: The crash report will be submitted without asking the user - * _NO_: The user will be asked if the crash report can be submitted (default) - * - * Default: _NO_ - */ -@property (nonatomic, assign, getter=isAutoSubmitCrashReport) BOOL autoSubmitCrashReport; - -/** - * Set the callbacks that will be executed prior to program termination after a crash has occurred - * - * PLCrashReporter provides support for executing an application specified function in the context - * of the crash reporter's signal handler, after the crash report has been written to disk. - * - * Writing code intended for execution inside of a signal handler is exceptionally difficult, and is _NOT_ recommended! - * - * _Program Flow and Signal Handlers_ - * - * When the signal handler is called the normal flow of the program is interrupted, and your program is an unknown state. Locks may be held, the heap may be corrupt (or in the process of being updated), and your signal handler may invoke a function that was being executed at the time of the signal. This may result in deadlocks, data corruption, and program termination. - * - * _Async-Safe Functions_ - * - * A subset of functions are defined to be async-safe by the OS, and are safely callable from within a signal handler. If you do implement a custom post-crash handler, it must be async-safe. A table of POSIX-defined async-safe functions and additional information is available from the [CERT programming guide - SIG30-C](https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers). - * - * Most notably, the Objective-C runtime itself is not async-safe, and Objective-C may not be used within a signal handler. - * - * Documentation taken from PLCrashReporter: https://www.plcrashreporter.org/documentation/api/v1.2-rc2/async_safety.html - * - * @see BITCrashManagerPostCrashSignalCallback - * @see BITCrashManagerCallbacks - * - * @param callbacks A pointer to an initialized PLCrashReporterCallback structure, see https://www.plcrashreporter.org/documentation/api/v1.2-rc2/struct_p_l_crash_reporter_callbacks.html - */ -- (void)setCrashCallbacks: (BITCrashManagerCallbacks *) callbacks; - - -///----------------------------------------------------------------------------- -/// @name Crash Meta Information -///----------------------------------------------------------------------------- - -/** - * Indicates if the app crash in the previous session - * - * Use this on startup, to check if the app starts the first time after it crashed - * previously. You can use this also to disable specific events, like asking - * the user to rate your app. - * - * @warning This property only has a correct value, once `[BITHockeyManager startManager]` was - * invoked! - */ -@property (nonatomic, readonly) BOOL didCrashInLastSession; - -/** - Provides an interface to pass user input from a custom alert to a crash report - - @param userInput Defines the users action wether to send, always send, or not to send the crash report. - @param userProvidedMetaData The content of this optional BITCrashMetaData instance will be attached to the crash report and allows to ask the user for e.g. additional comments or info. - - @return Returns YES if the input is a valid option and successfully triggered further processing of the crash report - - @see BITCrashManagerUserInput - @see BITCrashMetaData - */ -- (BOOL)handleUserInput:(BITCrashManagerUserInput)userInput withUserProvidedMetaData:(BITCrashMetaData *)userProvidedMetaData; - -/** - Lets you set a custom block which handles showing a custom UI and asking the user - whether he wants to send the crash report. - - This replaces the default alert the SDK would show! - - You can use this to present any kind of user interface which asks the user for additional information, - e.g. what they did in the app before the app crashed. - - In addition to this you should always ask your users if they agree to send crash reports, send them - always or not and return the result when calling `handleUserInput:withUserProvidedCrashDescription`. - - @param crashReportUIHandler A block that is responsible for loading, presenting and and dismissing your custom user interface which prompts the user if he wants to send crash reports. The block is also responsible for triggering further processing of the crash reports. - - @warning Block needs to call the `[BITCrashManager handleUserInput:withUserProvidedMetaData:]` method! - - @warning This needs to be set before calling `[BITHockeyManager startManager]`! - */ -- (void)setCrashReportUIHandler:(BITCustomCrashReportUIHandler)crashReportUIHandler; - -/** - * Provides details about the crash that occured in the last app session - */ -@property (nonatomic, readonly) BITCrashDetails *lastSessionCrashDetails; - -/** - * Provides the time between startup and crash in seconds - * - * Use this in together with `didCrashInLastSession` to detect if the app crashed very - * early after startup. This can be used to delay app initialization until the crash - * report has been sent to the server or if you want to do any other actions like - * cleaning up some cache data etc. - * - * The `BITCrashManagerDelegate` protocol provides some delegates to inform if sending - * a crash report was finished successfully, ended in error or was cancelled by the user. - * - * *Default*: _-1_ - * @see didCrashInLastSession - * @see BITCrashManagerDelegate - */ -@property (nonatomic, readonly) NSTimeInterval timeintervalCrashInLastSessionOccured; - - -///----------------------------------------------------------------------------- -/// @name Helper -///----------------------------------------------------------------------------- - -/** - * Lets the app crash for easy testing of the SDK - * - * The best way to use this is to trigger the crash with a button action. - * - * Make sure not to let the app crash in `applicationDidFinishLaunching` or any other - * startup method! Since otherwise the app would crash before the SDK could process it. - * - * Note that our SDK provides support for handling crashes that happen early on startup. - * Check the documentation for more information on how to use this. - */ -- (void)generateTestCrash; - - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITCrashManagerDelegate.h b/HockeySDK.framework/Versions/A/Headers/BITCrashManagerDelegate.h deleted file mode 100644 index 9c16d751a8..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITCrashManagerDelegate.h +++ /dev/null @@ -1,103 +0,0 @@ -#import - -@class BITHockeyAttachment; - -/** - * The `BITCrashManagerDelegate` formal protocol defines methods further configuring - * the behaviour of `BITCrashManager`. - */ -@protocol BITCrashManagerDelegate - -@optional - -/** - * Not used any longer! - * - * In previous SDK versions this invoked once the user interface asking for crash details and if the data should be send is dismissed - * - * @param crashManager The `BITCrashManager` instance invoking the method - * @deprecated The default crash report UI is not shown modal any longer, so this delegate is not being used any more! - */ -- (void) showMainApplicationWindowForCrashManager:(BITCrashManager *)crashManager __attribute__((deprecated("The default crash report UI is not shown modal any longer, so this delegate is now called right away. We recommend to remove the implementation of this method."))); - -///----------------------------------------------------------------------------- -/// @name Additional meta data -///----------------------------------------------------------------------------- - -/** Return any log string based data the crash report being processed should contain - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager; - -/** Return a BITHockeyAttachment object providing an NSData object the crash report - being processed should contain - - Please limit your attachments to reasonable files to avoid high traffic costs for your users. - - Example implementation: - - - (BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager { - NSData *data = [NSData dataWithContentsOfURL:@"mydatafile"]; - - BITHockeyAttachment *attachment = [[BITHockeyAttachment alloc] initWithFilename:@"myfile.data" - hockeyAttachmentData:data - contentType:@"'application/octet-stream"]; - return attachment; - } - - @param crashManager The `BITCrashManager` instance invoking this delegate - @see applicationLogForCrashManager: - */ --(BITHockeyAttachment *)attachmentForCrashManager:(BITCrashManager *)crashManager; - -///----------------------------------------------------------------------------- -/// @name Alert -///----------------------------------------------------------------------------- - -/** - * Invoked before the user is asked to send a crash report, so you can do additional actions. - * - * E.g. to make sure not to ask the user for an app rating :) - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(void)crashManagerWillShowSubmitCrashReportAlert:(BITCrashManager *)crashManager; - - -/** - * Invoked after the user did choose _NOT_ to send a crash in the alert - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - */ --(void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager; - - -///----------------------------------------------------------------------------- -/// @name Networking -///----------------------------------------------------------------------------- - -/** - * Invoked right before sending crash reports will start - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - */ -- (void)crashManagerWillSendCrashReport:(BITCrashManager *)crashManager; - -/** - * Invoked after sending crash reports failed - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - * @param error The error returned from the NSURLSessionDataTask call or `kBITCrashErrorDomain` - * with reason of type `BITCrashErrorReason`. - */ -- (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error; - -/** - * Invoked after sending crash reports succeeded - * - * @param crashManager The `BITCrashManager` instance invoking this delegate - */ -- (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITCrashMetaData.h b/HockeySDK.framework/Versions/A/Headers/BITCrashMetaData.h deleted file mode 100644 index 3ea226335a..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITCrashMetaData.h +++ /dev/null @@ -1,29 +0,0 @@ -#import - - -/** - * This class provides properties that can be attached to a crash report via a custom alert view flow - */ -@interface BITCrashMetaData : NSObject - -/** - * User provided description that should be attached to the crash report as plain text - */ -@property (nonatomic, copy) NSString *userDescription; - -/** - * User name that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userName; - -/** - * User email that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userEmail; - -/** - * User ID that should be attached to the crash report - */ -@property (nonatomic, copy) NSString *userID; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITFeedbackManager.h b/HockeySDK.framework/Versions/A/Headers/BITFeedbackManager.h deleted file mode 100644 index c59f4547f8..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITFeedbackManager.h +++ /dev/null @@ -1,140 +0,0 @@ -#import - -#import "BITHockeyBaseManager.h" - - -// Notification message which tells that loading messages finished -#define BITHockeyFeedbackMessagesLoadingStarted @"BITHockeyFeedbackMessagesLoadingStarted" - -// Notification message which tells that loading messages finished -#define BITHockeyFeedbackMessagesLoadingFinished @"BITHockeyFeedbackMessagesLoadingFinished" - - -/** - * Defines behavior of the user data field - */ -typedef NS_ENUM(NSInteger, BITFeedbackUserDataElement) { - /** - * don't ask for this user data element at all - */ - BITFeedbackUserDataElementDontShow = 0, - /** - * the user may provide it, but does not have to - */ - BITFeedbackUserDataElementOptional = 1, - /** - * the user has to provide this to continue - */ - BITFeedbackUserDataElementRequired = 2 -}; - - -@class BITFeedbackMessage; -@class BITFeedbackWindowController; - - -/** - The feedback module. - - This is the HockeySDK module for letting your users to communicate directly with you via - the app and an integrated user interface. It provides to have a single threaded - discussion with a user running your app. - - The user interface provides a window than can be presented using - `[BITFeedbackManager showFeedbackWindow]`. - This window integrates all features to load new messages, write new messages, view message - and ask the user for additional (optional) data like name and email. - - If the user provides the email address, all responses from the server will also be send - to the user via email and the user is also able to respond directly via email too. - - It is also integrates actions to invoke the user interface to compose a new messages, - reload the list content from the server and changing the users name or email if these - are allowed to be set. - - If new messages are written while the device is offline, the SDK automatically retries to - send them once the app starts again or gets active again, or if the notification - `BITHockeyNetworkDidBecomeReachableNotification` is fired. - - New message are automatically loaded on startup, when the app becomes active again - or when the notification `BITHockeyNetworkDidBecomeReachableNotification` is fired and - the last server communication task was more than 5 minutes ago. This - only happens if the user ever did initiate a conversation by writing the first - feedback message. - */ - -@interface BITFeedbackManager : BITHockeyBaseManager - -///----------------------------------------------------------------------------- -/// @name General settings -///----------------------------------------------------------------------------- - - -/** - Define if a name has to be provided by the user when providing feedback - - - `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all - - `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to - - `BITFeedbackUserDataElementRequired`: The user has to provide this to continue - - The default value is `BITFeedbackUserDataElementOptional`. - - @warning If you provide a non nil value for the `BITFeedbackManager` class via - `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` then this - property will automatically be set to `BITFeedbackUserDataElementDontShow` - - @see requireUserEmail - @see `[BITHockeyManagerDelegate userNameForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserName; - - -/** - Define if an email address has to be provided by the user when providing feedback - - If the user provides the email address, all responses from the server will also be send - to the user via email and the user is also able to respond directly via email too. - - - `BITFeedbackUserDataElementDontShow`: Don't ask for this user data element at all - - `BITFeedbackUserDataElementOptional`: The user may provide it, but does not have to - - `BITFeedbackUserDataElementRequired`: The user has to provide this to continue - - The default value is `BITFeedbackUserDataElementOptional`. - - @warning If you provide a non nil value for the `BITFeedbackManager` class via - `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` then this - property will automatically be set to `BITFeedbackUserDataElementDontShow` - - @see requireUserName - @see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BITFeedbackUserDataElement requireUserEmail; - - -/** - Indicates if an Notification Center alert should be shown when new messages arrived - - The alert is only shown, if the newest message is not originated from the current user. - This requires the users email address to be present! The optional userid property - cannot be used, because users could also answer via email and then this information - is not available. - - Default is `YES` - @see requireUserEmail - @see `[BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:]` - */ -@property (nonatomic, readwrite) BOOL showAlertOnIncomingMessages; - - -///----------------------------------------------------------------------------- -/// @name User Interface -///----------------------------------------------------------------------------- - - -/** - Present the modal feedback list user interface. - */ -- (void)showFeedbackWindow; - - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITFeedbackWindowController.h b/HockeySDK.framework/Versions/A/Headers/BITFeedbackWindowController.h deleted file mode 100644 index 94ffa5f275..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITFeedbackWindowController.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@class BITFeedbackManager; - -@interface BITFeedbackWindowController : NSWindowController - -- (id)initWithManager:(BITFeedbackManager *)feedbackManager; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITHockeyAttachment.h b/HockeySDK.framework/Versions/A/Headers/BITHockeyAttachment.h deleted file mode 100644 index 65fee8a5b6..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITHockeyAttachment.h +++ /dev/null @@ -1,38 +0,0 @@ -#import - -/** - Provides support to add binary attachments to crash reports - - This is used by `[BITCrashManagerDelegate attachmentForCrashManager:]` - */ -@interface BITHockeyAttachment : NSObject - -/** - The filename the attachment should get - */ -@property (nonatomic, readonly, copy) NSString *filename; - -/** - The attachment data as NSData object - */ -@property (nonatomic, readonly, strong) NSData *hockeyAttachmentData; - -/** - The content type of your data as MIME type - */ -@property (nonatomic, readonly, copy) NSString *contentType; - -/** - Create an BITHockeyAttachment instance with a given filename and NSData object - - @param filename The filename the attachment should get. If nil will get a automatically generated filename - @param hockeyAttachmentData The attachment data as NSData. The instance will be ignore if this is set to nil! - @param contentType The content type of your data as MIME type. If nil will be set to "application/octet-stream" - - @return An instsance of BITHockeyAttachment - */ -- (instancetype)initWithFilename:(NSString *)filename - hockeyAttachmentData:(NSData *)hockeyAttachmentData - contentType:(NSString *)contentType; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITHockeyBaseManager.h b/HockeySDK.framework/Versions/A/Headers/BITHockeyBaseManager.h deleted file mode 100644 index 53d2a46c83..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITHockeyBaseManager.h +++ /dev/null @@ -1,24 +0,0 @@ -#import - -/** - The internal superclass for all component managers - - */ - -@interface BITHockeyBaseManager : NSObject - -///----------------------------------------------------------------------------- -/// @name Modules -///----------------------------------------------------------------------------- - - -/** - Defines the server URL to send data to or request data from - - By default this is set to the HockeyApp servers and there rarely should be a - need to modify that. - */ -@property (nonatomic, copy) NSString *serverURL; - - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITHockeyManager.h b/HockeySDK.framework/Versions/A/Headers/BITHockeyManager.h deleted file mode 100644 index 3783399bd2..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITHockeyManager.h +++ /dev/null @@ -1,361 +0,0 @@ -@class BITCrashManager; -@class BITFeedbackManager; -@class BITMetricsManager; -@protocol BITHockeyManagerDelegate; - -#import "HockeySDK.h" - -/** - The HockeySDK manager. Responsible for setup and management of all components - - This is the principal SDK class. It represents the entry point for the HockeySDK. The main promises of the class are initializing the SDK - modules, providing access to global properties and to all modules. Initialization is divided into several distinct phases: - - 1. Setup the [HockeyApp](http://hockeyapp.net/) app identifier and the optional delegate: This is the least required information on setting up the SDK and using it. It does some simple validation of the app identifier. - 2. Provides access to the SDK module `BITCrashManager`. This way all modules can be further configured to personal needs, if the defaults don't fit the requirements. - 3. Configure each module. - 4. Start up all modules. - - The SDK is optimized to defer everything possible to a later time while making sure e.g. crashes on startup can also be caught and each module executes other code with a delay some seconds. This ensures that applicationDidFinishLaunching will process as fast as possible and the SDK will not block the startup sequence resulting in a possible kill by the watchdog process. - - All modules do **NOT** show any user interface if the module is not activated or not integrated. - `BITCrashManager`: Shows an alert on startup asking the user if he/she agrees on sending the crash report, if `[BITCrashManager autoSubmitCrashReport]` is enabled (default) - - Example: - - [[BITHockeyManager sharedHockeyManager] - configureWithIdentifier:@""]; - [[BITHockeyManager sharedHockeyManager] startManager]; - - @warning The SDK is **NOT** thread safe and has to be set up on the main thread! - - @warning You should **NOT** change any module configuration after calling `startManager`! - - */ -@interface BITHockeyManager : NSObject - -#pragma mark - Public Methods - -///----------------------------------------------------------------------------- -/// @name Initialization -///----------------------------------------------------------------------------- - -/** - * Returns the shared manager object - * - * @return A singleton BITHockeyManager instance ready use - */ -+ (BITHockeyManager *)sharedHockeyManager; - -/** - * Initializes the manager with a particular app identifier, company name and delegate - * - * Initialize the manager with a HockeyApp app identifier. - * - * @see BITCrashManagerDelegate - * @see startManager - * @see configureWithIdentifier:delegate: - * @param appIdentifier The app identifier that should be used. - */ -- (void)configureWithIdentifier:(NSString *)appIdentifier; - -/** - * Initializes the manager with a particular app identifier, company name and delegate - * - * Initialize the manager with a HockeyApp app identifier and assign the class that - * implements the optional protocol `BITCrashManagerDelegate`. - * - * @see BITCrashManagerDelegate - * @see startManager - * @see configureWithIdentifier: - * @param appIdentifier The app identifier that should be used. - * @param delegate `nil` or the class implementing the optional protocols - */ -- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id ) delegate; - -/** - * Initializes the manager with a particular app identifier, company name and delegate - * - * Initialize the manager with a HockeyApp app identifier and assign the class that - * implements the required protocol `BITCrashManagerDelegate`. - * - * @see BITCrashManagerDelegate - * @see startManager - * @see configureWithIdentifier: - * @see configureWithIdentifier:delegate: - * @param appIdentifier The app identifier that should be used. - * @param companyName `nil` or the company name, this is not used anywhere any longer. - * @param delegate `nil` or the class implementing the required protocols - */ -- (void)configureWithIdentifier:(NSString *)appIdentifier companyName:(NSString *)companyName delegate:(id ) delegate __attribute__((deprecated("Use configureWithIdentifier:delegate: instead"))); - -/** - * Starts the manager and runs all modules - * - * Call this after configuring the manager and setting up all modules. - * - * @see configureWithIdentifier: - * @see configureWithIdentifier:delegate: - */ -- (void)startManager; - - -#pragma mark - Public Properties - -///----------------------------------------------------------------------------- -/// @name General -///----------------------------------------------------------------------------- - - -/** - * Set the delegate - * - * Defines the class that implements the optional protocol `BITHockeyManagerDelegate`. - * - * @see BITHockeyManagerDelegate - * @see BITCrashManagerDelegate - */ -@property (nonatomic, weak) id delegate; - - -///----------------------------------------------------------------------------- -/// @name Modules -///----------------------------------------------------------------------------- - - -/** - * Defines the server URL to send data to or request data from - * - * By default this is set to the HockeyApp servers and there rarely should be a - * need to modify that. - * Please be aware that the URL for `BITMetricsManager` needs to be set separately - * as this class uses a different endpoint! - */ -@property (nonatomic, copy) NSString *serverURL; - -/** - * Reference to the initialized BITCrashManager module - * - * Returns the BITCrashManager instance initialized by BITHockeyManager - * - * @see configureWithIdentifier: - * @see configureWithIdentifier:delegate: - * @see startManager - * @see disableCrashManager - */ -@property (nonatomic, strong, readonly) BITCrashManager *crashManager; - - -/** - * Flag the determines whether the Crash Manager should be disabled - * - * If this flag is enabled, then crash reporting is disabled and no crashes will - * be send. - * - * Please note that the Crash Manager will be initialized anyway! - * - * *Default*: _NO_ - * @see crashManager - */ -@property (nonatomic, getter = isCrashManagerDisabled) BOOL disableCrashManager; - - -/** - Reference to the initialized BITFeedbackManager module - - Returns the BITFeedbackManager instance initialized by BITHockeyManager - - @see configureWithIdentifier:delegate: - @see startManager - @see disableFeedbackManager - */ -@property (nonatomic, strong, readonly) BITFeedbackManager *feedbackManager; - - -/** - Flag the determines whether the Feedback Manager should be disabled - - If this flag is enabled, then letting the user give feedback and - get responses will be turned off! - - Please note that the Feedback Manager will be initialized anyway! - - *Default*: _NO_ - @see feedbackManager - */ -@property (nonatomic, getter = isFeedbackManagerDisabled) BOOL disableFeedbackManager; - - -/** - Reference to the initialized BITMetricsManager module - - Returns the BITMetricsManager instance initialized by BITHockeyManager - */ -@property (nonatomic, strong, readonly) BITMetricsManager *metricsManager; - -/** - Flag the determines whether the BITMetricsManager should be disabled - - If this flag is enabled, then sending metrics data such as sessions and users - will be turned off! - - Please note that the BITMetricsManager instance will be initialized anyway! - - *Default*: _NO_ - @see metricsManager - */ -@property (nonatomic, getter = isMetricsManagerDisabled) BOOL disableMetricsManager; - - -///----------------------------------------------------------------------------- -/// @name Configuration -///----------------------------------------------------------------------------- - - -/** Set the userid that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see [BITHockeyManagerDelegate userIDForHockeyManager:componentManager:] - @see setUserName: - @see setUserEmail: - - @param userID NSString value for the userID - */ -- (void)setUserID:(NSString *)userID; - - -/** Set the user name that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see [BITHockeyManagerDelegate userNameForHockeyManager:componentManager:] - @see setUserID: - @see setUserEmail: - - @param userName NSString value for the userName - */ -- (void)setUserName:(NSString *)userName; - - -/** Set the users email address that should used in the SDK components - - Right now this is used by the `BITCrashManager` to attach to a crash report. - `BITFeedbackManager` uses it too for assigning the user to a discussion thread. - - The value can be set at any time and will be stored in the keychain on the current - device only! To delete the value from the keychain set the value to `nil`. - - This property is optional and can be used as an alternative to the delegate. If you - want to define specific data for each component, use the delegate instead which does - overwrite the values set by this property. - - @warning When returning a non nil value, crash reports are not anonymous any more - and the crash alerts will not show the word "anonymous"! - - @warning This property needs to be set before calling `startManager` to be considered - for being added to crash reports as meta data. - - @see [BITHockeyManagerDelegate userEmailForHockeyManager:componentManager:] - @see setUserID: - @see setUserName: - - @param userEmail NSString value for the userEmail - */ -- (void)setUserEmail:(NSString *)userEmail; - - -///----------------------------------------------------------------------------- -/// @name Debug Logging -///----------------------------------------------------------------------------- - -/** - This property is used indicate the amount of verboseness and severity for which - you want to see log messages in the console. - */ -@property (nonatomic, assign) BITLogLevel logLevel; - -/** - Flag that determines whether additional logging output should be generated - by the manager and all modules. - - This is ignored if the app is running in the App Store and reverts to the - default value in that case. - - @warning This property needs to be set before calling `startManager` - - *Default*: _NO_ - */ -@property (nonatomic, assign, getter=isDebugLogEnabled) BOOL debugLogEnabled DEPRECATED_MSG_ATTRIBUTE("Use logLevel instead!"); - -/** - Set a custom block that handles all the log messages that are emitted from the SDK. - - You can use this to reroute the messages that would normally be logged by `NSLog();` - to your own custom logging framework. - - An example of how to do this with NSLogger: - - ``` - [[BITHockeyManager sharedHockeyManager] setLogHandler:^(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line) { - LogMessageRawF(file, (int)line, function, @"HockeySDK", (int)logLevel-1, messageProvider()); - }]; - ``` - - or with CocoaLumberjack: - - ``` - [[BITHockeyManager sharedHockeyManager] setLogHandler:^(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line) { - [DDLog log:YES message:messageProvider() level:ddLogLevel flag:(DDLogFlag)(1 << (logLevel-1)) context:<#CocoaLumberjackContext#> file:file function:function line:line tag:nil]; - }]; - ``` - - @param logHandler The block of type BITLogHandler that will process all logged messages. - */ -- (void)setLogHandler:(BITLogHandler)logHandler; - - -///----------------------------------------------------------------------------- -/// @name Integration test -///----------------------------------------------------------------------------- - -/** - Pings the server with the HockeyApp app identifiers used for initialization - - Call this method once for debugging purposes to test if your SDK setup code - reaches the server successfully. - - Once invoked, check the apps page on HockeyApp for a verification. - */ -- (void)testIdentifier; - - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITHockeyManagerDelegate.h b/HockeySDK.framework/Versions/A/Headers/BITHockeyManagerDelegate.h deleted file mode 100644 index 784cba2a12..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITHockeyManagerDelegate.h +++ /dev/null @@ -1,91 +0,0 @@ -#import -#import "BITCrashManagerDelegate.h" - -@class BITHockeyManager; -@class BITHockeyBaseManager; - -/** - The `BITHockeyManagerDelegate` formal protocol defines methods further configuring - the behaviour of `BITHockeyManager`, as well as the delegate of the modules it manages. - */ - -@protocol BITHockeyManagerDelegate - -@optional - - -///----------------------------------------------------------------------------- -/// @name Additional meta data -///----------------------------------------------------------------------------- - - -/** Return the userid that should used in the SDK components - - Right now this is used by the `BITCrashMananger` to attach to a crash report and `BITFeedbackManager`. - - You can find out the component requesting the user name like this: - - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITCrashManager *)componentManager { - if (componentManager == crashManager) { - return UserNameForFeedback; - } else { - return nil; - } - } - - - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITCrashManager` component instance invoking this delegate - @see [BITHockeyManager setUserID:] - @see userNameForHockeyManager:componentManager: - @see userEmailForHockeyManager:componentManager: - */ -- (NSString *)userIDForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - - -/** Return the user name that should used in the SDK components - - Right now this is used by the `BITCrashMananger` to attach to a crash report and `BITFeedbackManager`. - - You can find out the component requesting the user name like this: - - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITCrashManager *)componentManager { - if (componentManager == crashManager) { - return UserNameForFeedback; - } else { - return nil; - } - } - - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITCrashManager` component instance invoking this delegate - @see [BITHockeyManager setUserName:] - @see userIDForHockeyManager:componentManager: - @see userEmailForHockeyManager:componentManager: - */ -- (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - - -/** Return the users email address that should used in the SDK components - - Right now this is used by the `BITCrashMananger` to attach to a crash report and `BITFeedbackManager`. - - You can find out the component requesting the user name like this: - - (NSString *)userNameForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITCrashManager *)componentManager { - if (componentManager == hockeyManager.crashManager) { - return UserNameForCrashReports; - } else { - return nil; - } - } - - - @param hockeyManager The `BITHockeyManager` HockeyManager instance invoking this delegate - @param componentManager The `BITCrashManager` component instance invoking this delegate - @see [BITHockeyManager setUserEmail:] - @see userIDForHockeyManager:componentManager: - @see userNameForHockeyManager:componentManager: - */ -- (NSString *)userEmailForHockeyManager:(BITHockeyManager *)hockeyManager componentManager:(BITHockeyBaseManager *)componentManager; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/BITMetricsManager.h b/HockeySDK.framework/Versions/A/Headers/BITMetricsManager.h deleted file mode 100644 index a3ef7a3f66..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITMetricsManager.h +++ /dev/null @@ -1,54 +0,0 @@ -#import -#import "BITHockeyBaseManager.h" -#import "HockeySDKNullability.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - The metrics module. - - This is the HockeySDK module that handles users, sessions and events tracking. - - Unless disabled, this module automatically tracks users and session of your app to give you - better insights about how your app is being used. - Users are tracked in a completely anonymous way without collecting any personally identifiable - information. - - Before starting to track events, ask yourself the questions that you want to get answers to. - For instance, you might be interested in business, performance/quality or user experience aspects. - Name your events in a meaningful way and keep in mind that you will use these names - when searching for events in the HockeyApp web portal. - - It is your reponsibility to not collect personal information as part of the events tracking or get - prior consent from your users as necessary. - */ -@interface BITMetricsManager : BITHockeyBaseManager - -/** - * A property indicating whether the BITMetricsManager instance is disabled. - */ -@property (nonatomic, assign) BOOL disabled; - -/** - * This method allows to track an event that happened in your app. - * Remember to choose meaningful event names to have the best experience when diagnosing your app - * in the HockeyApp web portal. - * - * @param eventName The event's name as a string. - */ -- (void)trackEventWithName:(nonnull NSString *)eventName; - -/** - * This method allows to track an event that happened in your app. - * Remember to choose meaningful event names to have the best experience when diagnosing your app - * in the web portal. - * - * @param eventName the name of the event, which should be tracked. - * @param properties key value pairs with additional info about the event. - * @param measurements key value pairs, which contain custom metrics. - */ -- (void)trackEventWithName:(nonnull NSString *)eventName properties:(nullable NSDictionary *)properties measurements:(nullable NSDictionary *)measurements; - -@end - -NS_ASSUME_NONNULL_END diff --git a/HockeySDK.framework/Versions/A/Headers/BITSystemProfile.h b/HockeySDK.framework/Versions/A/Headers/BITSystemProfile.h deleted file mode 100644 index ad8a76d261..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/BITSystemProfile.h +++ /dev/null @@ -1,102 +0,0 @@ -#import - -/** - * Helper class for accessing system information and measuring usage time - */ -@interface BITSystemProfile : NSObject - -///----------------------------------------------------------------------------- -/// @name Initialization -///----------------------------------------------------------------------------- - -/** - * Returns a shared BITSystemProfile object - * - * @return A singleton BITSystemProfile instance ready use - */ -+ (BITSystemProfile *)sharedSystemProfile; - - -///----------------------------------------------------------------------------- -/// @name Generic -///----------------------------------------------------------------------------- - -/** - * Return the current devices identifier - * - * @return NSString with the device identifier - */ -+ (NSString *)deviceIdentifier; - -/** - * Return the current device model - * - * @return NSString with the repesentation of the device model - */ -+ (NSString *)deviceModel; - -/** - * Return the system version of the current device - * - * @return NSString with the system version - */ -+ (NSString *)systemVersionString; - -/** - * Return an array with system data for a specific bundle - * - * @param bundle The app or framework bundle to get the system data from - * - * @return NSMutableArrray with system data - */ -- (NSMutableArray *)systemDataForBundle:(NSBundle *)bundle; - -/** - * Return an array with system data - * - * @return NSMutableArray with system data - */ -- (NSMutableArray *)systemData; - -/** - * Return an array with system usage data for a specific bundle - * - * @param bundle The app or framework bundle to get the usage data from - * - * @return NSMutableArray with system and bundle usage data - */ -- (NSMutableArray *)systemUsageDataForBundle:(NSBundle *)bundle; - -/** - * Return an array with system usage data that can be used with Sparkle - * - * Call this method in the Sparkle delegate `feedParametersForUpdater:sendingSystemProfile:` - * to attach system and app data to each Sparkle request - * - * @return NSMutableArray with system and app usage data - */ -- (NSMutableArray *)systemUsageData; - - -///----------------------------------------------------------------------------- -/// @name Usage time -///----------------------------------------------------------------------------- - -/** - * Start recording usage time for a specific app or framework bundle - * - * @param bundle The app or framework bundle to measure the usage time for - */ -- (void)startUsageForBundle:(NSBundle *)bundle; - -/** - * Start recording usage time for the current app - */ -- (void)startUsage; - -/** - * stop recording usage time - */ -- (void)stopUsage; - -@end diff --git a/HockeySDK.framework/Versions/A/Headers/HockeySDK.h b/HockeySDK.framework/Versions/A/Headers/HockeySDK.h deleted file mode 100644 index 8d960c9b3e..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/HockeySDK.h +++ /dev/null @@ -1,96 +0,0 @@ -#import - -#import "HockeySDKEnums.h" - -#import "BITHockeyManager.h" -#import "BITHockeyManagerDelegate.h" - -#import "BITHockeyAttachment.h" - -#import "BITCrashManager.h" -#import "BITCrashManagerDelegate.h" -#import "BITCrashDetails.h" -#import "BITCrashMetaData.h" -#import "BITCrashExceptionApplication.h" - -#import "BITSystemProfile.h" - -#import "BITFeedbackManager.h" -#import "BITFeedbackWindowController.h" - -#import "BITMetricsManager.h" - -// Notification message which HockeyManager is listening to, to retry requesting updated from the server -#define BITHockeyNetworkDidBecomeReachableNotification @"BITHockeyNetworkDidBecomeReachable" - -extern NSString *const kBITDefaultUserID; -extern NSString *const kBITDefaultUserName; -extern NSString *const kBITDefaultUserEmail; - -/** - * HockeySDK Crash Reporter error domain - */ -typedef NS_ENUM (NSInteger, BITCrashErrorReason) { - /** - * Unknown error - */ - BITCrashErrorUnknown, - /** - * API Server rejected app version - */ - BITCrashAPIAppVersionRejected, - /** - * API Server returned empty response - */ - BITCrashAPIReceivedEmptyResponse, - /** - * Connection error with status code - */ - BITCrashAPIErrorWithStatusCode -}; -extern NSString *const kBITCrashErrorDomain; - - -/** - * HockeySDK Feedback error domain - */ -typedef NS_ENUM(NSInteger, BITFeedbackErrorReason) { - /** - * Unknown error - */ - BITFeedbackErrorUnknown, - /** - * API Server returned invalid status - */ - BITFeedbackAPIServerReturnedInvalidStatus, - /** - * API Server returned invalid data - */ - BITFeedbackAPIServerReturnedInvalidData, - /** - * API Server returned empty response - */ - BITFeedbackAPIServerReturnedEmptyResponse, - /** - * Authorization secret missing - */ - BITFeedbackAPIClientAuthorizationMissingSecret, - /** - * No internet connection - */ - BITFeedbackAPIClientCannotCreateConnection -}; -extern NSString *const kBITFeedbackErrorDomain; - - -/** - * HockeySDK global error domain - */ -typedef NS_ENUM(NSInteger, BITHockeyErrorReason) { - /** - * Unknown error - */ - BITHockeyErrorUnknown -}; -extern NSString *const __attribute__((unused)) kBITHockeyErrorDomain; -// HockeySDK diff --git a/HockeySDK.framework/Versions/A/Headers/HockeySDKEnums.h b/HockeySDK.framework/Versions/A/Headers/HockeySDKEnums.h deleted file mode 100644 index 5c1ec95fb2..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/HockeySDKEnums.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef HockeySDKEnums_h -#define HockeySDKEnums_h - -/** - * HockeySDK Log Levels - */ -typedef NS_ENUM(NSUInteger, BITLogLevel) { - /** - * Logging is disabled - */ - BITLogLevelNone = 0, - /** - * Only errors will be logged - */ - BITLogLevelError = 1, - /** - * Errors and warnings will be logged - */ - BITLogLevelWarning = 2, - /** - * Debug information will be logged - */ - BITLogLevelDebug = 3, - /** - * Logging will be very chatty - */ - BITLogLevelVerbose = 4 -}; - -typedef NSString *(^BITLogMessageProvider)(void); -typedef void (^BITLogHandler)(BITLogMessageProvider messageProvider, BITLogLevel logLevel, const char *file, const char *function, uint line); - -#endif /* HockeySDKEnums_h */ diff --git a/HockeySDK.framework/Versions/A/Headers/HockeySDKNullability.h b/HockeySDK.framework/Versions/A/Headers/HockeySDKNullability.h deleted file mode 100644 index fcd3c160d4..0000000000 --- a/HockeySDK.framework/Versions/A/Headers/HockeySDKNullability.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// HockeySDKNullability.h -// HockeySDK -// -// Created by Andreas Linde on 12/06/15. -// -// - -#ifndef HockeySDK_HockeyNullability_h -#define HockeySDK_HockeyNullability_h - -// Define nullability fallback for backwards compatibility -#if !__has_feature(nullability) -#define NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_END -#define nullable -#define nonnull -#define null_unspecified -#define null_resettable -#define __nullable -#define __nonnull -#define __null_unspecified -#endif - -// Fallback for convenience syntax which might not be available in older SDKs -#ifndef NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") -#endif -#ifndef NS_ASSUME_NONNULL_END -#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") -#endif - -#endif diff --git a/HockeySDK.framework/Versions/A/HockeySDK b/HockeySDK.framework/Versions/A/HockeySDK deleted file mode 100755 index 6c28237d90..0000000000 Binary files a/HockeySDK.framework/Versions/A/HockeySDK and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Modules/module.modulemap b/HockeySDK.framework/Versions/A/Modules/module.modulemap deleted file mode 100644 index e6cbc2c826..0000000000 --- a/HockeySDK.framework/Versions/A/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module HockeySDK { - umbrella header "HockeySDK.h" - - export * - module * { export * } -} diff --git a/HockeySDK.framework/Versions/A/PrivateHeaders/BITHockeyLoggerPrivate.h b/HockeySDK.framework/Versions/A/PrivateHeaders/BITHockeyLoggerPrivate.h deleted file mode 100644 index 279cd11384..0000000000 --- a/HockeySDK.framework/Versions/A/PrivateHeaders/BITHockeyLoggerPrivate.h +++ /dev/null @@ -1,3 +0,0 @@ -#import "BITHockeyLogger.h" - -FOUNDATION_EXPORT BITLogHandler const defaultLogHandler; diff --git a/HockeySDK.framework/Versions/A/Resources/BITCrashReportUI.nib b/HockeySDK.framework/Versions/A/Resources/BITCrashReportUI.nib deleted file mode 100644 index 7accb5cd34..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/BITCrashReportUI.nib and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/BITFeedbackWindowController.nib b/HockeySDK.framework/Versions/A/Resources/BITFeedbackWindowController.nib deleted file mode 100644 index 2de9706a08..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/BITFeedbackWindowController.nib and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/Info.plist b/HockeySDK.framework/Versions/A/Resources/Info.plist deleted file mode 100644 index e534e790e3..0000000000 --- a/HockeySDK.framework/Versions/A/Resources/Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - BuildMachineOSBuild - 16G29 - CFBundleDevelopmentRegion - English - CFBundleExecutable - HockeySDK - CFBundleIdentifier - net.hockeyapp.sdk.mac - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - HockeySDK - CFBundlePackageType - FMWK - CFBundleShortVersionString - 5.0.0 - CFBundleSignature - ???? - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 63 - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 8E3004b - DTPlatformVersion - GM - DTSDKBuild - 16E185 - DTSDKName - macosx10.12 - DTXcode - 0833 - DTXcodeBuild - 8E3004b - NSHumanReadableCopyright - Copyright © Microsoft Corporation. All rights reserved. - - diff --git a/HockeySDK.framework/Versions/A/Resources/de.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/de.lproj/HockeySDK.strings deleted file mode 100644 index df82980e71..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/de.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/en.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/en.lproj/HockeySDK.strings deleted file mode 100644 index c9b3750a63..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/en.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/fi.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/fi.lproj/HockeySDK.strings deleted file mode 100644 index 2f2f7fb235..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/fi.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/fr.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/fr.lproj/HockeySDK.strings deleted file mode 100644 index f7c68e1a6e..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/fr.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/it.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/it.lproj/HockeySDK.strings deleted file mode 100644 index 5634156233..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/it.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/ja.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/ja.lproj/HockeySDK.strings deleted file mode 100644 index 9c8e96bab0..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/ja.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/nb.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/nb.lproj/HockeySDK.strings deleted file mode 100644 index 2cb8932b0e..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/nb.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/Resources/sv.lproj/HockeySDK.strings b/HockeySDK.framework/Versions/A/Resources/sv.lproj/HockeySDK.strings deleted file mode 100644 index 5807f82cb6..0000000000 Binary files a/HockeySDK.framework/Versions/A/Resources/sv.lproj/HockeySDK.strings and /dev/null differ diff --git a/HockeySDK.framework/Versions/A/_CodeSignature/CodeResources b/HockeySDK.framework/Versions/A/_CodeSignature/CodeResources deleted file mode 100644 index 2756b37d63..0000000000 --- a/HockeySDK.framework/Versions/A/_CodeSignature/CodeResources +++ /dev/null @@ -1,536 +0,0 @@ - - - - - files - - Resources/BITCrashReportUI.nib - - 3PMlLH/T9ihjR9hTJmlfhoYlTTo= - - Resources/BITFeedbackWindowController.nib - - U7138Kz9mVv0MYl0vqFDlXTQ50g= - - Resources/Info.plist - - h0M1SxUeldVGyV1UYD0S4nFfEAY= - - Resources/de.lproj/HockeySDK.strings - - hash - - y7EHyQ0RgIoSEhvNFom4v5X8UDg= - - optional - - - Resources/en.lproj/HockeySDK.strings - - hash - - AKYswjSpEoQ27x2n9DDQpTU0cWI= - - optional - - - Resources/fi.lproj/HockeySDK.strings - - hash - - 7DWjpj+VdXhYZkryUxbU5Prazf4= - - optional - - - Resources/fr.lproj/HockeySDK.strings - - hash - - suxOXWFc6JEfYQzFi/zoGOYNYg8= - - optional - - - Resources/it.lproj/HockeySDK.strings - - hash - - NJw3EFVb4Oh/T5bLbt3ZHFZczd0= - - optional - - - Resources/ja.lproj/HockeySDK.strings - - hash - - TECOwkRhKVts/HxhErq91PeiDRU= - - optional - - - Resources/nb.lproj/HockeySDK.strings - - hash - - Dt7iPI2Bv05u24cdQxX9iLrL5qw= - - optional - - - Resources/sv.lproj/HockeySDK.strings - - hash - - BZjpUHORIoU7zIfpLpttsqkVvVg= - - optional - - - - files2 - - Headers/BITCrashDetails.h - - hash - - QJpS1iLzgJyvZ7PlJGQ92NTqraw= - - hash2 - - rCKw9nYmrVue7lxcIZogn8CRlXfKkSRnnCJXXrLd7Uo= - - - Headers/BITCrashExceptionApplication.h - - hash - - CnAjiJPcrwY4FnZT4gcbbUF/cis= - - hash2 - - HBtmsUcjqK86q6l2B81huD53VBcyZA5VGcRqeMvncWQ= - - - Headers/BITCrashManager.h - - hash - - ns2xwXIeh5E0NUYv1LRzY19UwFM= - - hash2 - - qvp6ae+k7RNYX9TWdExICzGKCM03Iy2/KAcYrMnnmtQ= - - - Headers/BITCrashManagerDelegate.h - - hash - - fdD+yyIt4Q9R92+R3VZIov5cWt8= - - hash2 - - AK1gODWZBPb7wpwt7obZ+1AhAbK+QGh40vp/JOamc3c= - - - Headers/BITCrashMetaData.h - - hash - - z3EPOJuS2Q9CJWLHdvERSwjJdQw= - - hash2 - - 8Ib92FRU2+8LHGqgUiTUmNoDPbS0arq6qa54q6dC9/g= - - - Headers/BITFeedbackManager.h - - hash - - 8SboRqHv+QKX4VJAHSnNGqi8an4= - - hash2 - - VPeYBf8PB9IJWGTXvyabGdoZoylZxWX6/L3fRjxyfCw= - - - Headers/BITFeedbackWindowController.h - - hash - - YPSJ/IOJ+8xdDe9E7vxKunmoBbw= - - hash2 - - yirwMfEo2EiL3sZRpMU9p39wErq4bwyFMe0QU5lcstY= - - - Headers/BITHockeyAttachment.h - - hash - - JSmTX4EAKu9TWU5Pd17ZAOLSjQ4= - - hash2 - - htj8SuvYHGyMPTPZjRG9as4KGmXDA6S0EZ29OqKeQP8= - - - Headers/BITHockeyBaseManager.h - - hash - - SMWk+44pA/RtGH5FYoL+mVyQ8NE= - - hash2 - - HcqaPSgqBUHG/+36MBKHfBenEcRr/hzVBqOz+iIzb6A= - - - Headers/BITHockeyManager.h - - hash - - NxzbmgJaQ8HOEFEBXT2bBQYTJRY= - - hash2 - - RNl/bqMwbM3tUAxNWpw1iNXwJgzo9UitWDMWlhBc9OI= - - - Headers/BITHockeyManagerDelegate.h - - hash - - Zj+MlMn4bYTRzL8I4JARHhr1ec0= - - hash2 - - q1/tCrlRvXxfYisuuVxiMEAuxRT1VLMy5irnysDzyhQ= - - - Headers/BITMetricsManager.h - - hash - - qWhXB7JYsqsqvTC851q5UFEUTzI= - - hash2 - - gQhOEtypxjLjjrUytWttK/lpEl/BswFjnRx5OHTBLt8= - - - Headers/BITSystemProfile.h - - hash - - bJtVVlH1ZlPxJ58JIRPwfkQbusI= - - hash2 - - b24vir8WuGAEZPouNvgqNzjVp6Fszdm2PDAl4jCxmIw= - - - Headers/HockeySDK.h - - hash - - ZkiUTj9hi/AcNU2/g6olcr6o0is= - - hash2 - - Rp5ynNJGtmgHOXI2yG5JaKEToBNx5/MjvuVAwgT1+2k= - - - Headers/HockeySDKEnums.h - - hash - - 7ELC6eqCDz4yjv1UCiciRNIY6RQ= - - hash2 - - jqvsQwd0m+ipBHmrgGkruOiV0qjHXJdSywNcsLJG2Bc= - - - Headers/HockeySDKNullability.h - - hash - - +iyPYq4RIO6vV67WYzO4PyQkNS0= - - hash2 - - 8+bfuR2FtE3P7o+D1jEB4W5xVehtqZW2bV9Z1lEh8GA= - - - Modules/module.modulemap - - hash - - XqNxHBjpNA+vmtw20+/D9PQwPx8= - - hash2 - - 1MjRn+z/i5P2zT/JGA+OlkSGE/Hr63Rrfp4loNHpZTc= - - - PrivateHeaders/BITHockeyLoggerPrivate.h - - hash - - 41vaag/XwWLo0GpB7j3xsdbdG6k= - - hash2 - - e42bIPN/Hp+jC6G7GbZHYPK9GwzixGOaEhfLixGwSvk= - - - Resources/BITCrashReportUI.nib - - hash - - 3PMlLH/T9ihjR9hTJmlfhoYlTTo= - - hash2 - - BnfITmSkCJY49DaG1Z5xoaxFx1y5RvFlJ9kZzzQwJZM= - - - Resources/BITFeedbackWindowController.nib - - hash - - U7138Kz9mVv0MYl0vqFDlXTQ50g= - - hash2 - - 9b32gwvSUkWnc/wgCrhdawCpBEE1huzAkXqFEkUNrTE= - - - Resources/Info.plist - - hash - - h0M1SxUeldVGyV1UYD0S4nFfEAY= - - hash2 - - 0x2hvQ5GSR082qIngDqCeYZgbHd/IYWV6tLp3kzrWjw= - - - Resources/de.lproj/HockeySDK.strings - - hash - - y7EHyQ0RgIoSEhvNFom4v5X8UDg= - - hash2 - - gjVOvn490tuAXiQ+tw8gY2U3mn2BA8kMKgYcTF9weGY= - - optional - - - Resources/en.lproj/HockeySDK.strings - - hash - - AKYswjSpEoQ27x2n9DDQpTU0cWI= - - hash2 - - eFrvSgciUp4mmTmTFnIuDjo/OdB8pSODl/+ad3cRw0Y= - - optional - - - Resources/fi.lproj/HockeySDK.strings - - hash - - 7DWjpj+VdXhYZkryUxbU5Prazf4= - - hash2 - - l/qYWKlOCj+YOqmf+sj1dLwrjTy17qBM36gMr8Ft588= - - optional - - - Resources/fr.lproj/HockeySDK.strings - - hash - - suxOXWFc6JEfYQzFi/zoGOYNYg8= - - hash2 - - DT0NTIIiqwo2wTTK3kU7oG5FCmdL88E3bC3xplj4EnU= - - optional - - - Resources/it.lproj/HockeySDK.strings - - hash - - NJw3EFVb4Oh/T5bLbt3ZHFZczd0= - - hash2 - - g7CCohKaKeOX5rhtdPNLh3mPCjhcjYoemkGssoSNSeg= - - optional - - - Resources/ja.lproj/HockeySDK.strings - - hash - - TECOwkRhKVts/HxhErq91PeiDRU= - - hash2 - - 5c3+sfUws6TpX5200QAXQYYxv19Q1HeYbp05PzAzZms= - - optional - - - Resources/nb.lproj/HockeySDK.strings - - hash - - Dt7iPI2Bv05u24cdQxX9iLrL5qw= - - hash2 - - 0dmd8IZMJQ5Aoxc+4yZnHqWnVyRupJH65qvT553+7eU= - - optional - - - Resources/sv.lproj/HockeySDK.strings - - hash - - BZjpUHORIoU7zIfpLpttsqkVvVg= - - hash2 - - 3/MmYvPGxm5H65DlVNwmcEpl3hH356AxT/0cRK0QWu8= - - optional - - - - rules - - ^Resources/ - - ^Resources/.*\.lproj/ - - optional - - weight - 1000 - - ^Resources/.*\.lproj/locversion.plist$ - - omit - - weight - 1100 - - ^Resources/Base\.lproj/ - - weight - 1010 - - ^version.plist$ - - - rules2 - - .*\.dSYM($|/) - - weight - 11 - - ^(.*/)?\.DS_Store$ - - omit - - weight - 2000 - - ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ - - nested - - weight - 10 - - ^.* - - ^Info\.plist$ - - omit - - weight - 20 - - ^PkgInfo$ - - omit - - weight - 20 - - ^Resources/ - - weight - 20 - - ^Resources/.*\.lproj/ - - optional - - weight - 1000 - - ^Resources/.*\.lproj/locversion.plist$ - - omit - - weight - 1100 - - ^Resources/Base\.lproj/ - - weight - 1010 - - ^[^/]+$ - - nested - - weight - 10 - - ^embedded\.provisionprofile$ - - weight - 20 - - ^version\.plist$ - - weight - 20 - - - - diff --git a/HockeySDK.framework/Versions/Current b/HockeySDK.framework/Versions/Current deleted file mode 120000 index 8c7e5a667f..0000000000 --- a/HockeySDK.framework/Versions/Current +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000000..583585ecdd --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,22 @@ +# How to Build Telegram for macOS + +1. Clone this repository with submodules: + + ```sh + git clone https://github.com/overtake/TelegramSwift.git --recurse-submodules + ``` +2. ```brew install cmake ninja openssl@1.1 zlib``` +3. Open `Telegram-Mac.xcworkspace` in **Xcode 10.3**. Avoid Xcode 10.11+ because it causes additional errors when building the libraries with optimizations turned on. +4. Select build target to **Github** and **Run** build. + + + +# If you want to develop a fork + +1. Do first and second step above. +2. Change bundle Identifier and team-id. Easiest way is to search all mentions `ru.keepcoder.Telegram` and change it to your own. Team-id you can find on apple developer portal. +3. Obtain your [API ID](https://core.telegram.org/api/obtaining_api_id). **Note:** The built-in `apiId` is highly limited for api usage. **Do not use it** in any circumstances except verify binaries. +4. Open `Telegram-Mac/Config.swift` and repalce `apiId` and `apiHash` from previous step. **Note:** Do not forget to change `teamId` either. +5. Replace or remove `SFEED_URL` and `APPCENTER_SECRET` in `*.xcconfig` files. (First uses for in-app updates and second for collecting crashes on [appcenter](https://appcenter.ms)) +6. Write new better code. +7. If you still have a questions feel free to open new issue [here](https://github.com/overtake/TelegramSwift/issues/new). diff --git a/README.md b/README.md index c5f3743ffb..f0ceaa10a7 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,72 @@ -[Telegram](https://telegram.org) is a messaging app with a focus on speed and security. It’s superfast, simple and free. -This repo contains the official source code for [Telegram App for MacOS](https://macos.telegram.org). +
+ +

Telegram for macOS

+
-## Creating your Telegram Application +![Telegram macOS screenshot](images/tg.png) -We welcome all developers to use our API and source code to create applications on our platform. -There are several things we require from **all developers** for the moment. +[**Telegram**](https://telegram.org) is a messaging app with a focus on speed and security. It’s superfast, simple, and free! This repo contains the official source code for [Telegram for macOS](https://macos.telegram.org/). -1. [**Obtain your own api_id**](https://core.telegram.org/api/obtaining_api_id) for your application. -2. Please **do not** use the name Telegram for your app — or make sure your users understand that it is unofficial. -3. Kindly **do not** use our standard logo (white paper plane in a blue circle) as your app's logo. -3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy. -4. Please remember to publish **your** code too in order to comply with the licences. +## Get it -## Usage +[![Download on the Mac App Store](images/mas_badge.png)](https://itunes.apple.com/us/app/telegram/id747648890?mt=12) + + +### Using Homebrew -1. Clone repo with submodules ``` -git clone https://github.com/overtake/TelegramSwift.git --recursive +brew cask install telegram ``` -2. Open Telegram-Mac.xcworkspace -3. Create Config.swift file with + +### Using `mas-cli` + ``` -let API_ID:Int32 = 'api_id' -let API_HASH:String = "api_hash" -let TEST_SERVER:Bool = false -let languagesCategory = "macos" +mas install 747648890 ``` -4. build and enjoy +### Manual download + +If you would like, you can [download the non-MAS version](https://telegram.org/dl/macos). + +You can also [download the beta version](https://telegram.org/dl/macos/beta) if you want to try the latest features and you are prepared for bugs and crashes. If you are running the beta, join the [beta testing chat on Telegram](https://t.me/macswift) to report bugs. + +## Contributors + +### Contributors on GitHub +See [this repository’s contributors graph](https://github.com/overtake/TelegramSwift/graphs/contributors). + +### Translations +You can help translate Telegram for macOS on [Telegram’s translations platform](https://translations.telegram.org). Pick your language, then look for the macOS translation set. + + + + +## Permissions +Telegram strives to protect your privacy. This app asks for as few permissions as possible: + +* **Microphone**: You can send voice messages and make audio calls with Telegram. +* **Camera**: You can set your profile picture using your Mac’s iSight camera. +* **Location**: You can send your location to friends. +* **Outgoing network connections**: Telegram needs to connect to the internet to send your messages to your friends. +* **Incoming network connections**: Telegram needs to accept incoming connections for peer-to-peer voice calls. +* **User-selected files**: You can save files or images to your Mac. +* **Downloads folder**: Telegram can automatically download files or images you receive. + +## Shortcuts +With [Shortcuts](https://github.com/overtake/TelegramSwift/wiki) you can learn how easy is navigate using your devices. + +## License +Telegram for macOS is licensed under the GNU Public License, version 2.0. See [LICENSE](LICENSE) for more information. + +## Forking +You can fork this application and make something awesome! Make sure that your fork follows these five requirements: + +1. **Do** [get your own API ID](https://core.telegram.org/api/obtaining_api_id). +2. **Don’t** call your fork **Telegram** — or at least make sure your users understand that yours is unofficial. +3. **Don’t** use our standard logo (white paper plane in a blue circle) for your fork. +3. **Do** read and follow our [security guidelines](https://core.telegram.org/mtproto/security_guidelines) to make sure you take good care of your users’ data and protect their privacy. +4. **Do** publish your code. The [GPL license](LICENSE) requires it! +## How to Build +Instructions for building Telegram for macOS are in [INSTALL.md](INSTALL.md). diff --git a/Sparkle.framework/Headers b/Sparkle.framework/Headers deleted file mode 120000 index a177d2a6b9..0000000000 --- a/Sparkle.framework/Headers +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Headers \ No newline at end of file diff --git a/Sparkle.framework/Modules b/Sparkle.framework/Modules deleted file mode 120000 index 5736f3186e..0000000000 --- a/Sparkle.framework/Modules +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Modules \ No newline at end of file diff --git a/Sparkle.framework/PrivateHeaders b/Sparkle.framework/PrivateHeaders deleted file mode 120000 index d8e5645269..0000000000 --- a/Sparkle.framework/PrivateHeaders +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/PrivateHeaders \ No newline at end of file diff --git a/Sparkle.framework/Resources b/Sparkle.framework/Resources deleted file mode 120000 index 953ee36f3b..0000000000 --- a/Sparkle.framework/Resources +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Resources \ No newline at end of file diff --git a/Sparkle.framework/Sparkle b/Sparkle.framework/Sparkle deleted file mode 120000 index b2c52731ea..0000000000 --- a/Sparkle.framework/Sparkle +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Sparkle \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcast.h b/Sparkle.framework/Versions/A/Headers/SUAppcast.h deleted file mode 100644 index d7363d0a70..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUAppcast.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// SUAppcast.h -// Sparkle -// -// Created by Andy Matuschak on 3/12/06. -// Copyright 2006 Andy Matuschak. All rights reserved. -// - -#ifndef SUAPPCAST_H -#define SUAPPCAST_H - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SUAppcastItem; -SU_EXPORT @interface SUAppcast : NSObject - -@property (copy, nullable) NSString *userAgentString; - -#if __has_feature(objc_generics) -@property (copy, nullable) NSDictionary *httpHeaders; -#else -@property (copy, nullable) NSDictionary *httpHeaders; -#endif - -- (void)fetchAppcastFromURL:(NSURL *)url inBackground:(BOOL)bg completionBlock:(void (^)(NSError *_Nullable))err; -- (SUAppcast *)copyWithoutDeltaUpdates; - -@property (readonly, copy, nullable) NSArray *items; -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h b/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h deleted file mode 100644 index 5c861ddd8e..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUAppcastItem.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// SUAppcastItem.h -// Sparkle -// -// Created by Andy Matuschak on 3/12/06. -// Copyright 2006 Andy Matuschak. All rights reserved. -// - -#ifndef SUAPPCASTITEM_H -#define SUAPPCASTITEM_H - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" - -SU_EXPORT @interface SUAppcastItem : NSObject -@property (copy, readonly) NSString *title; -@property (copy, readonly) NSString *dateString; -@property (copy, readonly) NSString *itemDescription; -@property (strong, readonly) NSURL *releaseNotesURL; -@property (copy, readonly) NSString *DSASignature; -@property (copy, readonly) NSString *minimumSystemVersion; -@property (copy, readonly) NSString *maximumSystemVersion; -@property (strong, readonly) NSURL *fileURL; -@property (nonatomic, readonly) uint64_t contentLength; -@property (copy, readonly) NSString *versionString; -@property (copy, readonly) NSString *osString; -@property (copy, readonly) NSString *displayVersionString; -@property (copy, readonly) NSDictionary *deltaUpdates; -@property (strong, readonly) NSURL *infoURL; - -// Initializes with data from a dictionary provided by the RSS class. -- (instancetype)initWithDictionary:(NSDictionary *)dict; -- (instancetype)initWithDictionary:(NSDictionary *)dict failureReason:(NSString **)error; - -@property (getter=isDeltaUpdate, readonly) BOOL deltaUpdate; -@property (getter=isCriticalUpdate, readonly) BOOL criticalUpdate; -@property (getter=isMacOsUpdate, readonly) BOOL macOsUpdate; -@property (getter=isInformationOnlyUpdate, readonly) BOOL informationOnlyUpdate; - -// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions. -@property (readonly, copy) NSDictionary *propertiesDictionary; - -- (NSURL *)infoURL; - -@end - -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUErrors.h b/Sparkle.framework/Versions/A/Headers/SUErrors.h deleted file mode 100644 index 8557d7fbb4..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUErrors.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// SUErrors.h -// Sparkle -// -// Created by C.W. Betts on 10/13/14. -// Copyright (c) 2014 Sparkle Project. All rights reserved. -// - -#ifndef SUERRORS_H -#define SUERRORS_H - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" - -/** - * Error domain used by Sparkle - */ -SU_EXPORT extern NSString *const SUSparkleErrorDomain; - -typedef NS_ENUM(OSStatus, SUError) { - // Appcast phase errors. - SUAppcastParseError = 1000, - SUNoUpdateError = 1001, - SUAppcastError = 1002, - SURunningFromDiskImageError = 1003, - - // Download phase errors. - SUTemporaryDirectoryError = 2000, - SUDownloadError = 2001, - - // Extraction phase errors. - SUUnarchivingError = 3000, - SUSignatureError = 3001, - - // Installation phase errors. - SUFileCopyFailure = 4000, - SUAuthenticationFailure = 4001, - SUMissingUpdateError = 4002, - SUMissingInstallerToolError = 4003, - SURelaunchError = 4004, - SUInstallationError = 4005, - SUDowngradeError = 4006, - SUInstallationCancelledError = 4007, - - // System phase errors - SUSystemPowerOffError = 5000 -}; - -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUExport.h b/Sparkle.framework/Versions/A/Headers/SUExport.h deleted file mode 100644 index 3e3f8a1646..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUExport.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// SUExport.h -// Sparkle -// -// Created by Jake Petroules on 2014-08-23. -// Copyright (c) 2014 Sparkle Project. All rights reserved. -// - -#ifndef SUEXPORT_H -#define SUEXPORT_H - -#ifdef BUILDING_SPARKLE -#define SU_EXPORT __attribute__((visibility("default"))) -#else -#define SU_EXPORT -#endif - -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h b/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h deleted file mode 100644 index ed11921a51..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUStandardVersionComparator.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// SUStandardVersionComparator.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef SUSTANDARDVERSIONCOMPARATOR_H -#define SUSTANDARDVERSIONCOMPARATOR_H - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" -#import "SUVersionComparisonProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! - Sparkle's default version comparator. - - This comparator is adapted from MacPAD, by Kevin Ballard. - It's "dumb" in that it does essentially string comparison, - in components split by character type. -*/ -SU_EXPORT @interface SUStandardVersionComparator : NSObject - -/*! - Initializes a new instance of the standard version comparator. - */ -- (instancetype)init; - -/*! - Returns a singleton instance of the comparator. - - It is usually preferred to alloc/init new a comparator instead. -*/ -+ (SUStandardVersionComparator *)defaultComparator; - -/*! - Compares version strings through textual analysis. - - See the implementation for more details. -*/ -- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUUpdater.h b/Sparkle.framework/Versions/A/Headers/SUUpdater.h deleted file mode 100644 index a47447559d..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUUpdater.h +++ /dev/null @@ -1,235 +0,0 @@ -// -// SUUpdater.h -// Sparkle -// -// Created by Andy Matuschak on 1/4/06. -// Copyright 2006 Andy Matuschak. All rights reserved. -// - -#ifndef SUUPDATER_H -#define SUUPDATER_H - -#if __has_feature(modules) -@import Cocoa; -#else -#import -#endif -#import "SUExport.h" -#import "SUVersionComparisonProtocol.h" -#import "SUVersionDisplayProtocol.h" - -@class SUAppcastItem, SUAppcast; - -@protocol SUUpdaterDelegate; - -/*! - The main API in Sparkle for controlling the update mechanism. - - This class is used to configure the update paramters as well as manually - and automatically schedule and control checks for updates. - */ -SU_EXPORT @interface SUUpdater : NSObject - -@property (unsafe_unretained) IBOutlet id delegate; - -/*! - The shared updater for the main bundle. - - This is equivalent to passing [NSBundle mainBundle] to SUUpdater::updaterForBundle: - */ -+ (SUUpdater *)sharedUpdater; - -/*! - The shared updater for a specified bundle. - - If an updater has already been initialized for the provided bundle, that shared instance will be returned. - */ -+ (SUUpdater *)updaterForBundle:(NSBundle *)bundle; - -/*! - Designated initializer for SUUpdater. - - If an updater has already been initialized for the provided bundle, that shared instance will be returned. - */ -- (instancetype)initForBundle:(NSBundle *)bundle; - -/*! - Explicitly checks for updates and displays a progress dialog while doing so. - - This method is meant for a main menu item. - Connect any menu item to this action in Interface Builder, - and Sparkle will check for updates and report back its findings verbosely - when it is invoked. - - This will find updates that the user has opted into skipping. - */ -- (IBAction)checkForUpdates:(id)sender; - -/*! - The menu item validation used for the -checkForUpdates: action - */ -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem; - -/*! - Checks for updates, but does not display any UI unless an update is found. - - This is meant for programmatically initating a check for updates. That is, - it will display no UI unless it actually finds an update, in which case it - proceeds as usual. - - If automatic downloading of updates it turned on and allowed, however, - this will invoke that behavior, and if an update is found, it will be downloaded - in the background silently and will be prepped for installation. - - This will not find updates that the user has opted into skipping. - */ -- (void)checkForUpdatesInBackground; - -/*! - A property indicating whether or not to check for updates automatically. - - Setting this property will persist in the host bundle's user defaults. - The update schedule cycle will be reset in a short delay after the property's new value is set. - This is to allow reverting this property without kicking off a schedule change immediately - */ -@property BOOL automaticallyChecksForUpdates; - -/*! - A property indicating whether or not updates can be automatically downloaded in the background. - - Note that automatic downloading of updates can be disallowed by the developer - or by the user's system if silent updates cannot be done (eg: if they require authentication). - In this case, -automaticallyDownloadsUpdates will return NO regardless of how this property is set. - - Setting this property will persist in the host bundle's user defaults. - */ -@property BOOL automaticallyDownloadsUpdates; - -/*! - A property indicating the current automatic update check interval. - - Setting this property will persist in the host bundle's user defaults. - The update schedule cycle will be reset in a short delay after the property's new value is set. - This is to allow reverting this property without kicking off a schedule change immediately - */ -@property NSTimeInterval updateCheckInterval; - -/*! - Begins a "probing" check for updates which will not actually offer to - update to that version. - - However, the delegate methods - SUUpdaterDelegate::updater:didFindValidUpdate: and - SUUpdaterDelegate::updaterDidNotFindUpdate: will be called, - so you can use that information in your UI. - - Updates that have been skipped by the user will not be found. - */ -- (void)checkForUpdateInformation; - -/*! - The URL of the appcast used to download update information. - - Setting this property will persist in the host bundle's user defaults. - If you don't want persistence, you may want to consider instead implementing - SUUpdaterDelegate::feedURLStringForUpdater: or SUUpdaterDelegate::feedParametersForUpdater:sendingSystemProfile: - - This property must be called on the main thread. - */ -@property (copy) NSURL *feedURL; - -/*! - The host bundle that is being updated. - */ -@property (readonly, strong) NSBundle *hostBundle; - -/*! - The bundle this class (SUUpdater) is loaded into. - */ -@property (strong, readonly) NSBundle *sparkleBundle; - -/*! - The user agent used when checking for updates. - - The default implementation can be overrided. - */ -@property (nonatomic, copy) NSString *userAgentString; - -/*! - The HTTP headers used when checking for updates. - - The keys of this dictionary are HTTP header fields (NSString) and values are corresponding values (NSString) - */ -#if __has_feature(objc_generics) -@property (copy) NSDictionary *httpHeaders; -#else -@property (copy) NSDictionary *httpHeaders; -#endif - -/*! - A property indicating whether or not the user's system profile information is sent when checking for updates. - - Setting this property will persist in the host bundle's user defaults. - */ -@property BOOL sendsSystemProfile; - -/*! - A property indicating the decryption password used for extracting updates shipped as Apple Disk Images (dmg) - */ -@property (nonatomic, copy) NSString *decryptionPassword; - -/*! - This function ignores normal update schedule, ignores user preferences, - and interrupts users with an unwanted immediate app update. - - WARNING: this function should not be used in regular apps. This function - is a user-unfriendly hack only for very special cases, like unstable - rapidly-changing beta builds that would not run correctly if they were - even one day out of date. - - Instead of this function you should set `SUAutomaticallyUpdate` to `YES`, - which will gracefully install updates when the app quits. - - For UI-less/daemon apps that aren't usually quit, instead of this function, - you can use the delegate method - SUUpdaterDelegate::updater:willInstallUpdateOnQuit:immediateInstallationInvocation: - to immediately start installation when an update was found. - - A progress dialog is shown but the user will never be prompted to read the - release notes. - - This function will cause update to be downloaded twice if automatic updates are - enabled. - - You may want to respond to the userDidCancelDownload delegate method in case - the user clicks the "Cancel" button while the update is downloading. - */ -- (void)installUpdatesIfAvailable; - -/*! - Returns the date of last update check. - - \returns \c nil if no check has been performed. - */ -@property (readonly, copy) NSDate *lastUpdateCheckDate; - -/*! - Appropriately schedules or cancels the update checking timer according to - the preferences for time interval and automatic checks. - - This call does not change the date of the next check, - but only the internal NSTimer. - */ -- (void)resetUpdateCycle; - -/*! - A property indicating whether or not an update is in progress. - - Note this property is not indicative of whether or not user initiated updates can be performed. - Use SUUpdater::validateMenuItem: for that instead. - */ -@property (readonly) BOOL updateInProgress; - -@end - -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h b/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h deleted file mode 100644 index dbc1402401..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUUpdaterDelegate.h +++ /dev/null @@ -1,281 +0,0 @@ -// -// SUUpdaterDelegate.h -// Sparkle -// -// Created by Mayur Pawashe on 12/25/16. -// Copyright © 2016 Sparkle Project. All rights reserved. -// - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif - -#import "SUExport.h" - -@protocol SUVersionComparison, SUVersionDisplay; -@class SUUpdater, SUAppcast, SUAppcastItem; - -NS_ASSUME_NONNULL_BEGIN - -// ----------------------------------------------------------------------------- -// SUUpdater Notifications for events that might be interesting to more than just the delegate -// The updater will be the notification object -// ----------------------------------------------------------------------------- -SU_EXPORT extern NSString *const SUUpdaterDidFinishLoadingAppCastNotification; -SU_EXPORT extern NSString *const SUUpdaterDidFindValidUpdateNotification; -SU_EXPORT extern NSString *const SUUpdaterDidNotFindUpdateNotification; -SU_EXPORT extern NSString *const SUUpdaterWillRestartNotification; -#define SUUpdaterWillRelaunchApplicationNotification SUUpdaterWillRestartNotification; -#define SUUpdaterWillInstallUpdateNotification SUUpdaterWillRestartNotification; - -// Key for the SUAppcastItem object in the SUUpdaterDidFindValidUpdateNotification userInfo -SU_EXPORT extern NSString *const SUUpdaterAppcastItemNotificationKey; -// Key for the SUAppcast object in the SUUpdaterDidFinishLoadingAppCastNotification userInfo -SU_EXPORT extern NSString *const SUUpdaterAppcastNotificationKey; - -// ----------------------------------------------------------------------------- -// SUUpdater Delegate: -// ----------------------------------------------------------------------------- - -/*! - Provides methods to control the behavior of an SUUpdater object. - */ -@protocol SUUpdaterDelegate -@optional - -/*! - Returns whether to allow Sparkle to pop up. - - For example, this may be used to prevent Sparkle from interrupting a setup assistant. - - \param updater The SUUpdater instance. - */ -- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)updater; - -/*! - Returns additional parameters to append to the appcast URL's query string. - - This is potentially based on whether or not Sparkle will also be sending along the system profile. - - \param updater The SUUpdater instance. - \param sendingProfile Whether the system profile will also be sent. - - \return An array of dictionaries with keys: "key", "value", "displayKey", "displayValue", the latter two being specifically for display to the user. - */ -#if __has_feature(objc_generics) -- (NSArray *> *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile; -#else -- (NSArray *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile; -#endif - -/*! - Returns a custom appcast URL. - - Override this to dynamically specify the entire URL. - - An alternative may be to use SUUpdaterDelegate::feedParametersForUpdater:sendingSystemProfile: - and let the server handle what kind of feed to provide. - - \param updater The SUUpdater instance. - */ -- (nullable NSString *)feedURLStringForUpdater:(SUUpdater *)updater; - -/*! - Returns whether Sparkle should prompt the user about automatic update checks. - - Use this to override the default behavior. - - \param updater The SUUpdater instance. - */ -- (BOOL)updaterShouldPromptForPermissionToCheckForUpdates:(SUUpdater *)updater; - -/*! - Called after Sparkle has downloaded the appcast from the remote server. - - Implement this if you want to do some special handling with the appcast once it finishes loading. - - \param updater The SUUpdater instance. - \param appcast The appcast that was downloaded from the remote server. - */ -- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast; - -/*! - Returns the item in the appcast corresponding to the update that should be installed. - - If you're using special logic or extensions in your appcast, - implement this to use your own logic for finding a valid update, if any, - in the given appcast. - - \param appcast The appcast that was downloaded from the remote server. - \param updater The SUUpdater instance. - */ -- (nullable SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forUpdater:(SUUpdater *)updater; - -/*! - Called when a valid update is found by the update driver. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that is proposed to be installed. - */ -- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)item; - -/*! - Called when a valid update is not found. - - \param updater The SUUpdater instance. - */ -- (void)updaterDidNotFindUpdate:(SUUpdater *)updater; - -/*! - Called immediately before downloading the specified update. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that is proposed to be downloaded. - \param request The mutable URL request that will be used to download the update. - */ -- (void)updater:(SUUpdater *)updater willDownloadUpdate:(SUAppcastItem *)item withRequest:(NSMutableURLRequest *)request; - -/*! - Called after the specified update failed to download. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that failed to download. - \param error The error generated by the failed download. - */ -- (void)updater:(SUUpdater *)updater failedToDownloadUpdate:(SUAppcastItem *)item error:(NSError *)error; - -/*! - Called when the user clicks the cancel button while and update is being downloaded. - - \param updater The SUUpdater instance. - */ -- (void)userDidCancelDownload:(SUUpdater *)updater; - -/*! - Called immediately before installing the specified update. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that is proposed to be installed. - */ -- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)item; - -/*! - Returns whether the relaunch should be delayed in order to perform other tasks. - - This is not called if the user didn't relaunch on the previous update, - in that case it will immediately restart. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that is proposed to be installed. - \param invocation The invocation that must be completed with `[invocation invoke]` before continuing with the relaunch. - - \return \c YES to delay the relaunch until \p invocation is invoked. - */ -- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)item untilInvoking:(NSInvocation *)invocation; - -/*! - Returns whether the application should be relaunched at all. - - Some apps \b cannot be relaunched under certain circumstances. - This method can be used to explicitly prevent a relaunch. - - \param updater The SUUpdater instance. - */ -- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater; - -/*! - Called immediately before relaunching. - - \param updater The SUUpdater instance. - */ -- (void)updaterWillRelaunchApplication:(SUUpdater *)updater; - -/*! - Called immediately after relaunching. SUUpdater delegate must be set before applicationDidFinishLaunching: to catch this event. - - \param updater The SUUpdater instance. - */ -- (void)updaterDidRelaunchApplication:(SUUpdater *)updater; - -/*! - Returns an object that compares version numbers to determine their arithmetic relation to each other. - - This method allows you to provide a custom version comparator. - If you don't implement this method or return \c nil, - the standard version comparator will be used. - - \sa SUStandardVersionComparator - - \param updater The SUUpdater instance. - */ -- (nullable id)versionComparatorForUpdater:(SUUpdater *)updater; - -/*! - Returns an object that formats version numbers for display to the user. - - If you don't implement this method or return \c nil, - the standard version formatter will be used. - - \sa SUUpdateAlert - - \param updater The SUUpdater instance. - */ -- (nullable id)versionDisplayerForUpdater:(SUUpdater *)updater; - -/*! - Returns the path which is used to relaunch the client after the update is installed. - - The default is the path of the host bundle. - - \param updater The SUUpdater instance. - */ -- (nullable NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater; - -/*! - Called before an updater shows a modal alert window, - to give the host the opportunity to hide attached windows that may get in the way. - - \param updater The SUUpdater instance. - */ -- (void)updaterWillShowModalAlert:(SUUpdater *)updater; - -/*! - Called after an updater shows a modal alert window, - to give the host the opportunity to hide attached windows that may get in the way. - - \param updater The SUUpdater instance. - */ -- (void)updaterDidShowModalAlert:(SUUpdater *)updater; - -/*! - Called when an update is scheduled to be silently installed on quit. - This is after an update has been automatically downloaded in the background. - (i.e. SUUpdater::automaticallyDownloadsUpdates is YES) - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that is proposed to be installed. - \param invocation Can be used to trigger an immediate silent install and relaunch. - */ -- (void)updater:(SUUpdater *)updater willInstallUpdateOnQuit:(SUAppcastItem *)item immediateInstallationInvocation:(NSInvocation *)invocation; - -/*! - Calls after an update that was scheduled to be silently installed on quit has been canceled. - - \param updater The SUUpdater instance. - \param item The appcast item corresponding to the update that was proposed to be installed. - */ -- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)item; - -/*! - Called after an update is aborted due to an error. - - \param updater The SUUpdater instance. - \param error The error that caused the abort - */ -- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h b/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h deleted file mode 100644 index c654fc4d0f..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUVersionComparisonProtocol.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SUVersionComparisonProtocol.h -// Sparkle -// -// Created by Andy Matuschak on 12/21/07. -// Copyright 2007 Andy Matuschak. All rights reserved. -// - -#ifndef SUVERSIONCOMPARISONPROTOCOL_H -#define SUVERSIONCOMPARISONPROTOCOL_H - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" - -NS_ASSUME_NONNULL_BEGIN - -/*! - Provides version comparison facilities for Sparkle. -*/ -@protocol SUVersionComparison - -/*! - An abstract method to compare two version strings. - - Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, - and NSOrderedSame if they are equivalent. -*/ -- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; // *** MAY BE CALLED ON NON-MAIN THREAD! - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h b/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h deleted file mode 100644 index 980efb3fe7..0000000000 --- a/Sparkle.framework/Versions/A/Headers/SUVersionDisplayProtocol.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// SUVersionDisplayProtocol.h -// EyeTV -// -// Created by Uli Kusterer on 08.12.09. -// Copyright 2009 Elgato Systems GmbH. All rights reserved. -// - -#if __has_feature(modules) -@import Foundation; -#else -#import -#endif -#import "SUExport.h" - -/*! - Applies special display formatting to version numbers. -*/ -@protocol SUVersionDisplay - -/*! - Formats two version strings. - - Both versions are provided so that important distinguishing information - can be displayed while also leaving out unnecessary/confusing parts. -*/ -- (void)formatVersion:(NSString *_Nonnull*_Nonnull)inOutVersionA andVersion:(NSString *_Nonnull*_Nonnull)inOutVersionB; - -@end diff --git a/Sparkle.framework/Versions/A/Headers/Sparkle.h b/Sparkle.framework/Versions/A/Headers/Sparkle.h deleted file mode 100644 index d3f6ff2b80..0000000000 --- a/Sparkle.framework/Versions/A/Headers/Sparkle.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Sparkle.h -// Sparkle -// -// Created by Andy Matuschak on 3/16/06. (Modified by CDHW on 23/12/07) -// Copyright 2006 Andy Matuschak. All rights reserved. -// - -#ifndef SPARKLE_H -#define SPARKLE_H - -// This list should include the shared headers. It doesn't matter if some of them aren't shared (unless -// there are name-space collisions) so we can list all of them to start with: - -#import "SUAppcast.h" -#import "SUAppcastItem.h" -#import "SUStandardVersionComparator.h" -#import "SUUpdater.h" -#import "SUUpdaterDelegate.h" -#import "SUVersionComparisonProtocol.h" -#import "SUVersionDisplayProtocol.h" -#import "SUErrors.h" - -#endif diff --git a/Sparkle.framework/Versions/A/Modules/module.modulemap b/Sparkle.framework/Versions/A/Modules/module.modulemap deleted file mode 100644 index af3fe6d050..0000000000 --- a/Sparkle.framework/Versions/A/Modules/module.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Sparkle { - umbrella header "Sparkle.h" - - export * - module * { export * } -} diff --git a/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h b/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h deleted file mode 100644 index a52bf5a2dd..0000000000 --- a/Sparkle.framework/Versions/A/PrivateHeaders/SUUnarchiver.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// SUUnarchiver.h -// Sparkle -// -// Created by Andy Matuschak on 3/16/06. -// Copyright 2006 Andy Matuschak. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol SUUnarchiverProtocol; - -@interface SUUnarchiver : NSObject - -+ (nullable id )unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist deleted file mode 100644 index 74e911743c..0000000000 --- a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Info.plist +++ /dev/null @@ -1,54 +0,0 @@ - - - - - BuildMachineOSBuild - 17A330h - CFBundleDevelopmentRegion - English - CFBundleExecutable - Autoupdate - CFBundleIconFile - AppIcon.icns - CFBundleIdentifier - org.sparkle-project.Sparkle.Autoupdate - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.18.1 21-g558cfd21 - CFBundleSignature - ???? - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1.18.1 - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 9M136h - DTPlatformVersion - GM - DTSDKBuild - 17A263z - DTSDKName - macosx10.13 - DTXcode - 0900 - DTXcodeBuild - 9M136h - LSBackgroundOnly - 1 - LSMinimumSystemVersion - 10.7 - LSUIElement - 1 - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate deleted file mode 100755 index 1afce79acf..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/Autoupdate and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop deleted file mode 100755 index cd0ae0b6aa..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/MacOS/fileop and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo deleted file mode 100644 index bd04210fb4..0000000000 --- a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns deleted file mode 100644 index 7f2a571c80..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/AppIcon.icns and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib deleted file mode 100644 index 5fa3ecf6c0..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/SUStatus.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings deleted file mode 100644 index 4cd92c0dd7..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ar.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings deleted file mode 100644 index cc238f685a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ca.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings deleted file mode 100644 index c93688a316..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/cs.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings deleted file mode 100644 index 10e3c5a5d8..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/da.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings deleted file mode 100644 index 9b0968f68a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/de.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings deleted file mode 100644 index deed9efb22..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/el.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings deleted file mode 100644 index 842d2551f5..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/en.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings deleted file mode 100644 index 3027ecdcb6..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/es.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings deleted file mode 100644 index 32d3107f92..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fi.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings deleted file mode 100644 index e4294a58ec..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/fr.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings deleted file mode 100644 index 99124ccc88..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/he.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings deleted file mode 100644 index 74ae72802a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/is.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings deleted file mode 100644 index 68b6d366bc..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/it.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings deleted file mode 100644 index 5d2315cf55..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ja.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings deleted file mode 100644 index 92c18eeb2a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ko.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings deleted file mode 100644 index ec2561b8ad..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nb.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings deleted file mode 100644 index 58be0e82bb..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/nl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings deleted file mode 100644 index 2b9c461520..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings deleted file mode 100644 index c94db0986b..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_BR.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings deleted file mode 100644 index 00df86ff13..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/pt_PT.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings deleted file mode 100644 index 318baa960d..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ro.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings deleted file mode 100644 index c33086d89f..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/ru.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings deleted file mode 100644 index a7d2ebce67..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sk.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings deleted file mode 100644 index 1be2a80798..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings deleted file mode 100644 index 738c9008b4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/sv.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings deleted file mode 100644 index eca2570247..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/th.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings deleted file mode 100644 index 4def140e5a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/tr.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings deleted file mode 100644 index f7eb257b7e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/uk.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings deleted file mode 100644 index 214331cd13..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_CN.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings deleted file mode 100644 index 533e208624..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/Autoupdate.app/Contents/Resources/zh_TW.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/Info.plist b/Sparkle.framework/Versions/A/Resources/Info.plist deleted file mode 100644 index f70151a2c8..0000000000 --- a/Sparkle.framework/Versions/A/Resources/Info.plist +++ /dev/null @@ -1,44 +0,0 @@ - - - - - BuildMachineOSBuild - 17A330h - CFBundleDevelopmentRegion - en - CFBundleExecutable - Sparkle - CFBundleIdentifier - org.sparkle-project.Sparkle - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Sparkle - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.18.1 21-g558cfd21 - CFBundleSignature - ???? - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - 1.18.1 - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 9M136h - DTPlatformVersion - GM - DTSDKBuild - 17A263z - DTSDKName - macosx10.13 - DTXcode - 0900 - DTXcodeBuild - 9M136h - - diff --git a/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist b/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist deleted file mode 100644 index 1f75b248c5..0000000000 --- a/Sparkle.framework/Versions/A/Resources/SUModelTranslation.plist +++ /dev/null @@ -1,314 +0,0 @@ - - - - - ADP2,1 - Developer Transition Kit - iMac1,1 - iMac G3 (Rev A-D) - iMac4,1 - iMac (Core Duo) - iMac4,2 - iMac for Education (17 inch, Core Duo) - iMac5,1 - iMac (Core 2 Duo, 17 or 20 inch, SuperDrive) - iMac5,2 - iMac (Core 2 Duo, 17 inch, Combo Drive) - iMac6,1 - iMac (Core 2 Duo, 24 inch, SuperDrive) - iMac7,1 - iMac Intel Core 2 Duo (aluminum enclosure) - iMac8,1 - iMac (Core 2 Duo, 20 or 24 inch, Early 2008 ) - iMac9,1 - iMac (Core 2 Duo, 20 or 24 inch, Early or Mid 2009 ) - iMac10,1 - iMac (Core 2 Duo, 21.5 or 27 inch, Late 2009 ) - iMac11,1 - iMac (Core i5 or i7, 27 inch Late 2009) - iMac11,2 - 21.5" iMac (mid 2010) - iMac11,3 - iMac (Core i5 or i7, 27 inch Mid 2010) - iMac12,1 - iMac (Core i3 or i5 or i7, 21.5 inch Mid 2010 or Late 2011) - iMac12,2 - iMac (Core i5 or i7, 27 inch Mid 2011) - iMac13,1 - iMac (Core i3 or i5 or i7, 21.5 inch Late 2012 or Early 2013) - iMac13,2 - iMac (Core i5 or i7, 27 inch Late 2012) - iMac14,1 - iMac (Core i5, 21.5 inch Late 2013) - iMac14,2 - iMac (Core i5 or i7, 27 inch Late 2013) - iMac14,3 - iMac (Core i5 or i7, 21.5 inch Late 2013) - iMac14,4 - iMac (Core i5, 21.5 inch Mid 2014) - iMac15,1 - iMac (Retina 5K Core i5 or i7, 27 inch Late 2014 or Mid 2015) - iMac16,1 - iMac (Core i5, 21,5 inch Late 2015) - iMac16,2 - iMac (Retina 4K Core i5 or i7, 21.5 inch Late 2015) - iMac17,1 - iMac (Retina 5K Core i5 or i7, 27 inch Late 2015) - MacBook1,1 - MacBook (Core Duo) - MacBook2,1 - MacBook (Core 2 Duo) - MacBook4,1 - MacBook (Core 2 Duo Feb 2008) - MacBook5,1 - MacBook (Core 2 Duo, Late 2008, Unibody) - MacBook5,2 - MacBook (Core 2 Duo, Early 2009, White) - MacBook6,1 - MacBook (Core 2 Duo, Late 2009, Unibody) - MacBook7,1 - MacBook (Core 2 Duo, Mid 2010, White) - MacBook8,1 - MacBook (Core M, 12 inch, Early 2015) - MacBookAir1,1 - MacBook Air (Core 2 Duo, 13 inch, Early 2008) - MacBookAir2,1 - MacBook Air (Core 2 Duo, 13 inch, Mid 2009) - MacBookAir3,1 - MacBook Air (Core 2 Duo, 11 inch, Late 2010) - MacBookAir3,2 - MacBook Air (Core 2 Duo, 13 inch, Late 2010) - MacBookAir4,1 - MacBook Air (Core i5 or i7, 11 inch, Mid 2011) - MacBookAir4,2 - MacBook Air (Core i5 or i7, 13 inch, Mid 2011) - MacBookAir5,1 - MacBook Air (Core i5 or i7, 11 inch, Mid 2012) - MacBookAir5,2 - MacBook Air (Core i5 or i7, 13 inch, Mid 2012) - MacBookAir6,1 - MacBook Air (Core i5 or i7, 11 inch, Mid 2013 or Early 2014) - MacBookAir6,2 - MacBook Air (Core i5 or i7, 13 inch, Mid 2013 or Early 2014) - MacBookAir7,1 - MacBook Air (Core i5 or i7, 11 inch, Early 2015) - MacBookAir7,2 - MacBook Air (Core i5 or i7, 13 inch, Early 2015) - MacBookPro1,1 - MacBook Pro Core Duo (15-inch) - MacBookPro1,2 - MacBook Pro Core Duo (17-inch) - MacBookPro2,1 - MacBook Pro Core 2 Duo (17-inch) - MacBookPro2,2 - MacBook Pro Core 2 Duo (15-inch) - MacBookPro3,1 - MacBook Pro Core 2 Duo (15-inch LED, Core 2 Duo) - MacBookPro3,2 - MacBook Pro Core 2 Duo (17-inch HD, Core 2 Duo) - MacBookPro4,1 - MacBook Pro (Core 2 Duo Feb 2008) - MacBookPro5,1 - MacBook Pro Intel Core 2 Duo (aluminum unibody) - MacBookPro5,2 - MacBook Pro Intel Core 2 Duo (aluminum unibody) - MacBookPro5,3 - MacBook Pro Intel Core 2 Duo (aluminum unibody) - MacBookPro5,4 - MacBook Pro Intel Core 2 Duo (aluminum unibody) - MacBookPro5,5 - MacBook Pro Intel Core 2 Duo (aluminum unibody) - MacBookPro6,1 - MacBook Pro Intel Core i5, Intel Core i7 (mid 2010) - MacBookPro6,2 - MacBook Pro Intel Core i5, Intel Core i7 (mid 2010) - MacBookPro7,1 - MacBook Pro Intel Core 2 Duo (mid 2010) - MacBookPro8,1 - MacBook Pro Intel Core i5, Intel Core i7, 13" (early 2011) - MacBookPro8,2 - MacBook Pro Intel Core i7, 15" (early 2011) - MacBookPro8,3 - MacBook Pro Intel Core i7, 17" (early 2011) - MacBookPro9,1 - MacBook Pro (15-inch, Mid 2012) - MacBookPro9,2 - MacBook Pro (13-inch, Mid 2012) - MacBookPro10,1 - MacBook Pro (Retina, Mid 2012) - MacBookPro10,2 - MacBook Pro (Retina, 13-inch, Late 2012) - MacBookPro11,1 - MacBook Pro (Retina, 13-inch, Late 2013) - MacBookPro11,2 - MacBook Pro (Retina, 15-inch, Late 2013) - MacBookPro11,3 - MacBook Pro (Retina, 15-inch, Late 2013) - MacbookPro11,4 - MacBook Pro (Retina, 15-inch, Mid 2015) - MacbookPro11,5 - MacBook Pro (Retina, 15-inch, Mid 2015) - MacbookPro12,1  - MacBook Pro (Retina, 13-inch, Early 2015) - Macmini1,1 - Mac Mini (Core Solo/Duo) - Macmini2,1 - Mac mini Intel Core - Macmini3,1 - Mac mini Intel Core - Macmini4,1 - Mac mini Intel Core (Mid 2010) - Macmini5,1 - Mac mini (Core i5, Mid 2011) - Macmini5,2 - Mac mini (Core i5 or Core i7, Mid 2011) - Macmini5,3 - Mac mini (Core i7, Server, Mid 2011) - Macmini6,1 - Mac mini (Core i5, Late 2012) - Macmini6,2 - Mac mini (Core i7, Normal or Server, Late 2012) - Macmini7,1 - Mac mini (Core i5 or Core i7, Late 2014) - MacPro1,1,Quad - Mac Pro - MacPro1,1 - Mac Pro (four-core) - MacPro2,1 - Mac Pro (eight-core) - MacPro3,1 - Mac Pro (January 2008 4- or 8- core "Harpertown") - MacPro4,1 - Mac Pro (March 2009) - MacPro5,1 - Mac Pro (2010 or 2012) - MacPro6,1 - Mac Pro (Late 2013) - PowerBook1,1 - PowerBook G3 - PowerBook2,1 - iBook G3 - PowerBook2,2 - iBook G3 (FireWire) - PowerBook2,3 - iBook G3 - PowerBook2,4 - iBook G3 - PowerBook3,1 - PowerBook G3 (FireWire) - PowerBook3,2 - PowerBook G4 - PowerBook3,3 - PowerBook G4 (Gigabit Ethernet) - PowerBook3,4 - PowerBook G4 (DVI) - PowerBook3,5 - PowerBook G4 (1GHz / 867MHz) - PowerBook4,1 - iBook G3 (Dual USB, Late 2001) - PowerBook4,2 - iBook G3 (16MB VRAM) - PowerBook4,3 - iBook G3 Opaque 16MB VRAM, 32MB VRAM, Early 2003) - PowerBook5,1 - PowerBook G4 (17 inch) - PowerBook5,2 - PowerBook G4 (15 inch FW 800) - PowerBook5,3 - PowerBook G4 (17-inch 1.33GHz) - PowerBook5,4 - PowerBook G4 (15 inch 1.5/1.33GHz) - PowerBook5,5 - PowerBook G4 (17-inch 1.5GHz) - PowerBook5,6 - PowerBook G4 (15 inch 1.67GHz/1.5GHz) - PowerBook5,7 - PowerBook G4 (17-inch 1.67GHz) - PowerBook5,8 - PowerBook G4 (Double layer SD, 15 inch) - PowerBook5,9 - PowerBook G4 (Double layer SD, 17 inch) - PowerBook6,1 - PowerBook G4 (12 inch) - PowerBook6,2 - PowerBook G4 (12 inch, DVI) - PowerBook6,3 - iBook G4 - PowerBook6,4 - PowerBook G4 (12 inch 1.33GHz) - PowerBook6,5 - iBook G4 (Early-Late 2004) - PowerBook6,7 - iBook G4 (Mid 2005) - PowerBook6,8 - PowerBook G4 (12 inch 1.5GHz) - PowerMac1,1 - Power Macintosh G3 (Blue & White) - PowerMac1,2 - Power Macintosh G4 (PCI Graphics) - PowerMac2,1 - iMac G3 (Slot-loading CD-ROM) - PowerMac2,2 - iMac G3 (Summer 2000) - PowerMac3,1 - Power Macintosh G4 (AGP Graphics) - PowerMac3,2 - Power Macintosh G4 (AGP Graphics) - PowerMac3,3 - Power Macintosh G4 (Gigabit Ethernet) - PowerMac3,4 - Power Macintosh G4 (Digital Audio) - PowerMac3,5 - Power Macintosh G4 (Quick Silver) - PowerMac3,6 - Power Macintosh G4 (Mirrored Drive Door) - PowerMac4,1 - iMac G3 (Early/Summer 2001) - PowerMac4,2 - iMac G4 (Flat Panel) - PowerMac4,4 - eMac - PowerMac4,5 - iMac G4 (17-inch Flat Panel) - PowerMac5,1 - Power Macintosh G4 Cube - PowerMac5,2 - Power Mac G4 Cube - PowerMac6,1 - iMac G4 (USB 2.0) - PowerMac6,3 - iMac G4 (20-inch Flat Panel) - PowerMac6,4 - eMac (USB 2.0, 2005) - PowerMac7,2 - Power Macintosh G5 - PowerMac7,3 - Power Macintosh G5 - PowerMac8,1 - iMac G5 - PowerMac8,2 - iMac G5 (Ambient Light Sensor) - PowerMac9,1 - Power Macintosh G5 (Late 2005) - PowerMac10,1 - Mac Mini G4 - PowerMac10,2 - Mac Mini (Late 2005) - PowerMac11,2 - Power Macintosh G5 (Late 2005) - PowerMac12,1 - iMac G5 (iSight) - RackMac1,1 - Xserve G4 - RackMac1,2 - Xserve G4 (slot-loading, cluster node) - RackMac3,1 - Xserve G5 - Xserve1,1 - Xserve (Intel Xeon) - Xserve2,1 - Xserve (January 2008 quad-core) - Xserve3,1 - Xserve (early 2009) - - diff --git a/Sparkle.framework/Versions/A/Resources/SUStatus.nib b/Sparkle.framework/Versions/A/Resources/SUStatus.nib deleted file mode 100644 index 5fa3ecf6c0..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/SUStatus.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index ee833b0c8c..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib deleted file mode 100644 index a3bf0ac030..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 6889325724..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ar.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings deleted file mode 100644 index 4cd92c0dd7..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ar.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings deleted file mode 100644 index cc238f685a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ca.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 114105bf35..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib deleted file mode 100644 index f4323fb491..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index d5f66622f8..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/cs.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings deleted file mode 100644 index c93688a316..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/cs.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 25e40b2ffd..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/da.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib deleted file mode 100644 index 043d4b8ee3..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index c9d44e9780..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/da.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings deleted file mode 100644 index 10e3c5a5d8..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/da.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 0447897bdd..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/de.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib deleted file mode 100644 index cc343d41cd..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 5d584dcb6b..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/de.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings deleted file mode 100644 index 9b0968f68a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/de.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index c4a64b4a69..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/el.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib deleted file mode 100644 index 46cbdc34dd..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index c24a4aa946..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/el.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings deleted file mode 100644 index deed9efb22..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/el.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 4236118ee3..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/en.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib deleted file mode 100644 index 12b3e8e1f8..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 1dd0f78492..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/en.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings deleted file mode 100644 index 842d2551f5..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/en.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 130d71a265..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/es.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib deleted file mode 100644 index e24c3ca5b6..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index caeb75c579..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/es.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings deleted file mode 100644 index 3027ecdcb6..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/es.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings deleted file mode 100644 index 32d3107f92..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/fi.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index aded902855..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib deleted file mode 100644 index 247aab8464..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index a2468beff9..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/fr.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings deleted file mode 100644 index e4294a58ec..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/fr.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/fr_CA.lproj b/Sparkle.framework/Versions/A/Resources/fr_CA.lproj deleted file mode 120000 index f9834a395e..0000000000 --- a/Sparkle.framework/Versions/A/Resources/fr_CA.lproj +++ /dev/null @@ -1 +0,0 @@ -fr.lproj \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings deleted file mode 100644 index 99124ccc88..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/he.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index b10697c0c1..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/is.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib deleted file mode 100644 index 049132f2e4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 7533345bdc..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/is.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings deleted file mode 100644 index 74ae72802a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/is.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 75d8251794..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/it.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib deleted file mode 100644 index 14b4e51a74..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 3bd60d3d0d..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/it.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings deleted file mode 100644 index 68b6d366bc..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/it.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index c36e920f86..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib deleted file mode 100644 index ebbd509c06..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index a7b0bddbc5..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings deleted file mode 100644 index 5d2315cf55..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ja.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 86f6040197..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib deleted file mode 100644 index 9fb502c4d9..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 149e81d516..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ko.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings deleted file mode 100644 index 92c18eeb2a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ko.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 4808185707..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib deleted file mode 100644 index 211dc8fd10..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 86e42a6940..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nb.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings deleted file mode 100644 index ec2561b8ad..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nb.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index e4ec625c70..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib deleted file mode 100644 index 0ba6762f0c..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 4f9b38ba97..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nl.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings deleted file mode 100644 index 58be0e82bb..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/nl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index e48d71c36b..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib deleted file mode 100644 index fe98402073..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 0e8f33d47e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pl.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings deleted file mode 100644 index 2b9c461520..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt.lproj b/Sparkle.framework/Versions/A/Resources/pt.lproj deleted file mode 120000 index 3c1c9f6dce..0000000000 --- a/Sparkle.framework/Versions/A/Resources/pt.lproj +++ /dev/null @@ -1 +0,0 @@ -pt_BR.lproj \ No newline at end of file diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index d185ad8f23..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib deleted file mode 100644 index 93a0e0e3a3..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 10f4cdeea4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings deleted file mode 100644 index c94db0986b..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_BR.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 2f95438888..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib deleted file mode 100644 index 90a40d4a3b..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 4e84e87de4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings deleted file mode 100644 index 00df86ff13..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/pt_PT.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 8e791fa3f7..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib deleted file mode 100644 index 244b6787ff..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index e86d1d05d2..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ro.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings deleted file mode 100644 index 318baa960d..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ro.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index c7f72e387c..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib deleted file mode 100644 index 5a5626abe2..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index c09c353967..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ru.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings deleted file mode 100644 index c33086d89f..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/ru.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 0573ebc6dc..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib deleted file mode 100644 index 91e8c17cae..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 5b732b8db4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sk.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings deleted file mode 100644 index a7d2ebce67..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sk.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 4fa1df5e0c..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib deleted file mode 100644 index 382d671e59..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 5333ab24ff..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sl.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings deleted file mode 100644 index 1be2a80798..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sl.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 4604b86648..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib deleted file mode 100644 index a26c7fda4d..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index ddc182489e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sv.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings deleted file mode 100644 index 738c9008b4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/sv.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index e45b9a15a2..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/th.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib deleted file mode 100644 index 5a66b95cca..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 9172044316..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/th.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings deleted file mode 100644 index eca2570247..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/th.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index ec8ff840f1..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib deleted file mode 100644 index 26f8576817..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 5c7359ad2e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/tr.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings deleted file mode 100644 index 4def140e5a..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/tr.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index d8920a5103..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib deleted file mode 100644 index 5c6c5213cc..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 72912e9b42..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/uk.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings deleted file mode 100644 index f7eb257b7e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/uk.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 23759231db..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib deleted file mode 100644 index f6dcd869f4..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 1107ca76ef..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings deleted file mode 100644 index 214331cd13..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_CN.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib deleted file mode 100644 index 9abca3c2e5..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUAutomaticUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib deleted file mode 100644 index 9065be8439..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdateAlert.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib deleted file mode 100644 index 919230b50e..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/SUUpdatePermissionPrompt.nib and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings b/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings deleted file mode 100644 index 533e208624..0000000000 Binary files a/Sparkle.framework/Versions/A/Resources/zh_TW.lproj/Sparkle.strings and /dev/null differ diff --git a/Sparkle.framework/Versions/A/Sparkle b/Sparkle.framework/Versions/A/Sparkle deleted file mode 100755 index ce286a23fb..0000000000 Binary files a/Sparkle.framework/Versions/A/Sparkle and /dev/null differ diff --git a/Sparkle.framework/Versions/Current b/Sparkle.framework/Versions/Current deleted file mode 120000 index 8c7e5a667f..0000000000 --- a/Sparkle.framework/Versions/Current +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/TGUIKit/TGUIKit.xcodeproj/project.pbxproj b/TGUIKit/TGUIKit.xcodeproj/project.pbxproj deleted file mode 100644 index 9088c89dec..0000000000 --- a/TGUIKit/TGUIKit.xcodeproj/project.pbxproj +++ /dev/null @@ -1,993 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - C20232B61D845B0B007C9ADE /* TextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232B51D845B0B007C9ADE /* TextNode.swift */; }; - C20232B81D85525C007C9ADE /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232B71D85525C007C9ADE /* ViewUtils.swift */; }; - C21178021F17E78E00AC706D /* TimableProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21178011F17E78E00AC706D /* TimableProgressView.swift */; }; - C21656C61EE0A7050041A6BA /* SegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21656C51EE0A7050041A6BA /* SegmentedControl.swift */; }; - C2167E5D1DC2534300F98E03 /* SelectingControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E5C1DC2534300F98E03 /* SelectingControl.swift */; }; - C2167E611DC356FD00F98E03 /* LinearProgressControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */; }; - C219E1D51D87F4A00042F0C8 /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C219E1D41D87F4A00042F0C8 /* SwiftSignalKitMac.framework */; }; - C219E1DD1D8978960042F0C8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C219E1DC1D8978960042F0C8 /* QuartzCore.framework */; }; - C219E1E01D8A71820042F0C8 /* SImageLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */; }; - C219E1E21D8A93050042F0C8 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1E11D8A93050042F0C8 /* TextView.swift */; }; - C219E1EB1D8AD1470042F0C8 /* NavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */; }; - C219E1ED1D8ADEE00042F0C8 /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */; }; - C219E1EF1D8AE3310042F0C8 /* AnimationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */; }; - C219E1F11D8AFD140042F0C8 /* AnimationBlockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */; }; - C219E1F31D8B02FA0042F0C8 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */; }; - C219E1F81D8C14930042F0C8 /* BarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F71D8C14930042F0C8 /* BarView.swift */; }; - C219E1FA1D8C316C0042F0C8 /* TitledBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */; }; - C21AAE381DB233F7007638C5 /* UIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE371DB233F7007638C5 /* UIUtils.swift */; }; - C21AAE3A1DB2398B007638C5 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */; }; - C21AAE3C1DB239ED007638C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C21AAE3B1DB239ED007638C5 /* Foundation.framework */; }; - C224A72B1EB7581500F43F3F /* MajorNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */; }; - C226741B1DBCD6E8000BA9ED /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226741A1DBCD6E8000BA9ED /* GridNode.swift */; }; - C226741D1DBCDBA8000BA9ED /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226741C1DBCDBA8000BA9ED /* ContainableController.swift */; }; - C2271DA61DAD16B4001792B6 /* BadgeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DA51DAD16B4001792B6 /* BadgeNode.swift */; }; - C2271DC81DAEAAF9001792B6 /* SwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DC71DAEAAF9001792B6 /* SwitchView.swift */; }; - C2271DD41DAF766A001792B6 /* WeakReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DD31DAF766A001792B6 /* WeakReference.swift */; }; - C2271DED1DAFE4D0001792B6 /* TransformImageArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */; }; - C2271F331DB4CEB30045E719 /* HorizontalTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */; }; - C2271F361DB4CF270045E719 /* HorizontalRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F351DB4CF270045E719 /* HorizontalRowView.swift */; }; - C2271F431DB4EE2F0045E719 /* TableStickItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F421DB4EE2F0045E719 /* TableStickItem.swift */; }; - C2271F451DB4EE3C0045E719 /* TableStickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F441DB4EE3C0045E719 /* TableStickView.swift */; }; - C22E062E1D7F3CC000A11C88 /* TGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E062D1D7F3CC000A11C88 /* TGColor.swift */; }; - C22E06351D804C8200A11C88 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06341D804C8200A11C88 /* TableView.swift */; }; - C22E06371D804D3B00A11C88 /* TableRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06361D804D3B00A11C88 /* TableRowItem.swift */; }; - C22E06391D804DCD00A11C88 /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06381D804DCD00A11C88 /* ScrollView.swift */; }; - C22E063B1D8067A000A11C88 /* TableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E063A1D8067A000A11C88 /* TableRowView.swift */; }; - C22E06451D80967E00A11C88 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C22E06441D80967E00A11C88 /* CoreText.framework */; }; - C2303E6F1D9950E000098E12 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E6E1D9950E000098E12 /* Button.swift */; }; - C2303E711D9956C400098E12 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E701D9956C400098E12 /* Style.swift */; }; - C2303E761D996E6300098E12 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E751D996E6300098E12 /* ImageButton.swift */; }; - C2303E7A1D9987FE00098E12 /* Popover.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E791D9987FE00098E12 /* Popover.swift */; }; - C2303E7C1D99880B00098E12 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E7B1D99880B00098E12 /* Modal.swift */; }; - C2303E7F1D99BEAE00098E12 /* OverlayControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */; }; - C2303E821D9A692B00098E12 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E811D9A692B00098E12 /* TabBarController.swift */; }; - C2303E841D9A69BD00098E12 /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E831D9A69BD00098E12 /* TabBarView.swift */; }; - C2303E861D9A6C2A00098E12 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E851D9A6C2A00098E12 /* TabItem.swift */; }; - C2303E8C1D9A8B3000098E12 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E8B1D9A8B3000098E12 /* SearchView.swift */; }; - C230B9181DD3A6350057F596 /* ProgressModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B9171DD3A6350057F596 /* ProgressModal.swift */; }; - C232EA151E165AEF00C4D38C /* ImageBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C232EA141E165AEF00C4D38C /* ImageBarView.swift */; }; - C234CA8E1D97DF26003023F7 /* DrawingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA8B1D97DF26003023F7 /* DrawingContext.swift */; }; - C234CA8F1D97DF26003023F7 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA8C1D97DF26003023F7 /* Image.swift */; }; - C234CA971D981F00003023F7 /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA961D981F00003023F7 /* Control.swift */; }; - C2449A151F0E490C00DF5650 /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */; }; - C24A37161F324A36004ADE5C /* ShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24A37151F324A36004ADE5C /* ShadowView.swift */; }; - C24A371A1F338406004ADE5C /* SectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24A37191F338406004ADE5C /* SectionViewController.swift */; }; - C253A9521D9147A400CDC850 /* Layer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9511D9147A400CDC850 /* Layer.swift */; }; - C253A9651D91B24100CDC850 /* TextViewLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9641D91B24100CDC850 /* TextViewLabel.swift */; }; - C253A9761D9430A900CDC850 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9751D9430A900CDC850 /* ImageView.swift */; }; - C25FC7F71D86DC370041E303 /* TGClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25FC7F61D86DC370041E303 /* TGClipView.swift */; }; - C25FC7F91D86E53A0041E303 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25FC7F81D86E53A0041E303 /* CoreVideo.framework */; }; - C26505901E02EBEE001954DC /* MagnifyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265058F1E02EBEE001954DC /* MagnifyView.swift */; }; - C28BAB0D1DF8550E0027CE3A /* WindowSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */; }; - C28CFC7D1F387A7F00C55596 /* TokenizedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */; }; - C291ED991DB35BC5008C6B2A /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291ED981DB35BC5008C6B2A /* Window.swift */; }; - C291ED9B1DB361ED008C6B2A /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */; }; - C296AF841D8D9D7D001DBB59 /* RadialProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */; }; - C29B5F3C1DC66DAC00D13E65 /* DraggingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */; }; - C29B5F471DC8CA2B00D13E65 /* NavigationModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */; }; - C29B5F491DC8CA3C00D13E65 /* NavigationModalAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */; }; - C2A71CEB1DDB382F00C69F73 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */; }; - C2B1A1181DA1673300ACB1DD /* TableAnimationInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */; }; - C2B1A11E1DA26BA400ACB1DD /* TransactionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */; }; - C2B1A1251DA2F3DC00ACB1DD /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */; }; - C2B1A1291DA3DDED00ACB1DD /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1281DA3DDED00ACB1DD /* Node.swift */; }; - C2B1A12C1DA50CC600ACB1DD /* TitleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */; }; - C2B1A12E1DA5148B00ACB1DD /* TextButtonBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */; }; - C2B1A1301DA53E7D00ACB1DD /* BackNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */; }; - C2B1A1321DA57E8300ACB1DD /* EditableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */; }; - C2B9BE861EFC373D00D6B96F /* PresentationTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */; }; - C2B9BE871EFC373D00D6B96F /* PresentationResourceCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */; }; - C2CBCABB1D80AD1F00142EC0 /* TGFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */; }; - C2CBCABD1D814D4B00142EC0 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCABC1D814D4B00142EC0 /* System.swift */; }; - C2CBCAC31D81595900142EC0 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCAC21D81595900142EC0 /* Extensions.swift */; }; - C2E0DEAC1D7EF51C00EF1C8D /* TGUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C2E0DEB71D7EF58200EF1C8D /* TGSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */; }; - C2E0DEB91D7EF7F300EF1C8D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */; }; - C2E0DEBB1D7F05AB00EF1C8D /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEBA1D7F05AB00EF1C8D /* View.swift */; }; - C2E8694F1F44481400BDD0A2 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E8694E1F44481400BDD0A2 /* GridItem.swift */; }; - C2E869511F4449A700BDD0A2 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E869501F4449A700BDD0A2 /* GridItemNode.swift */; }; - C2FD33E71E6952A8008D13D4 /* RadialProgressContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - C20232B51D845B0B007C9ADE /* TextNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextNode.swift; sourceTree = ""; }; - C20232B71D85525C007C9ADE /* ViewUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewUtils.swift; sourceTree = ""; }; - C21178011F17E78E00AC706D /* TimableProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimableProgressView.swift; sourceTree = ""; }; - C21656C51EE0A7050041A6BA /* SegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControl.swift; sourceTree = ""; }; - C2167E5C1DC2534300F98E03 /* SelectingControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectingControl.swift; sourceTree = ""; }; - C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearProgressControl.swift; sourceTree = ""; }; - C219E1D41D87F4A00042F0C8 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKitMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/SwiftSignalKitMac.framework"; sourceTree = ""; }; - C219E1DC1D8978960042F0C8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SImageLayer.swift; sourceTree = ""; }; - C219E1E11D8A93050042F0C8 /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; - C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationViewController.swift; sourceTree = ""; }; - C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; - C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationStyle.swift; sourceTree = ""; }; - C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationBlockDelegate.swift; sourceTree = ""; }; - C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; - C219E1F71D8C14930042F0C8 /* BarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarView.swift; sourceTree = ""; }; - C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitledBarView.swift; sourceTree = ""; }; - C21AAE371DB233F7007638C5 /* UIUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIUtils.swift; sourceTree = ""; }; - C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; - C21AAE3B1DB239ED007638C5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MajorNavigationController.swift; sourceTree = ""; }; - C226741A1DBCD6E8000BA9ED /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; - C226741C1DBCDBA8000BA9ED /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; - C2271DA51DAD16B4001792B6 /* BadgeNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeNode.swift; sourceTree = ""; }; - C2271DC71DAEAAF9001792B6 /* SwitchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchView.swift; sourceTree = ""; }; - C2271DD31DAF766A001792B6 /* WeakReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakReference.swift; sourceTree = ""; }; - C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformImageArguments.swift; sourceTree = ""; }; - C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalTableView.swift; sourceTree = ""; }; - C2271F351DB4CF270045E719 /* HorizontalRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalRowView.swift; sourceTree = ""; }; - C2271F421DB4EE2F0045E719 /* TableStickItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableStickItem.swift; sourceTree = ""; }; - C2271F441DB4EE3C0045E719 /* TableStickView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableStickView.swift; sourceTree = ""; }; - C22E062D1D7F3CC000A11C88 /* TGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGColor.swift; sourceTree = ""; }; - C22E06341D804C8200A11C88 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = ""; }; - C22E06361D804D3B00A11C88 /* TableRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowItem.swift; sourceTree = ""; }; - C22E06381D804DCD00A11C88 /* ScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = ""; }; - C22E063A1D8067A000A11C88 /* TableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowView.swift; sourceTree = ""; }; - C22E06441D80967E00A11C88 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; - C2303E6E1D9950E000098E12 /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; - C2303E701D9956C400098E12 /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Style.swift; sourceTree = ""; }; - C2303E751D996E6300098E12 /* ImageButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageButton.swift; sourceTree = ""; }; - C2303E791D9987FE00098E12 /* Popover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Popover.swift; sourceTree = ""; }; - C2303E7B1D99880B00098E12 /* Modal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; - C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayControl.swift; sourceTree = ""; }; - C2303E811D9A692B00098E12 /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; - C2303E831D9A69BD00098E12 /* TabBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = ""; }; - C2303E851D9A6C2A00098E12 /* TabItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; - C2303E8B1D9A8B3000098E12 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; - C230B9171DD3A6350057F596 /* ProgressModal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressModal.swift; sourceTree = ""; }; - C232EA141E165AEF00C4D38C /* ImageBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageBarView.swift; sourceTree = ""; }; - C234CA881D97DEB6003023F7 /* TGLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TGLibrary.framework; path = ../TGLibrary/build/Debug/TGLibrary.framework; sourceTree = ""; }; - C234CA8B1D97DF26003023F7 /* DrawingContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawingContext.swift; sourceTree = ""; }; - C234CA8C1D97DF26003023F7 /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; - C234CA921D97E151003023F7 /* PostboxMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PostboxMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/PostboxMac.framework"; sourceTree = ""; }; - C234CA941D97E163003023F7 /* TelegramCoreMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TelegramCoreMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/TelegramCoreMac.framework"; sourceTree = ""; }; - C234CA961D981F00003023F7 /* Control.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = ""; }; - C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = ""; }; - C24A37151F324A36004ADE5C /* ShadowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowView.swift; sourceTree = ""; }; - C24A37191F338406004ADE5C /* SectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionViewController.swift; sourceTree = ""; }; - C253A9511D9147A400CDC850 /* Layer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layer.swift; sourceTree = ""; }; - C253A9641D91B24100CDC850 /* TextViewLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewLabel.swift; sourceTree = ""; }; - C253A9751D9430A900CDC850 /* ImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = ""; }; - C25FC7F61D86DC370041E303 /* TGClipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGClipView.swift; sourceTree = ""; }; - C25FC7F81D86E53A0041E303 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; - C265058F1E02EBEE001954DC /* MagnifyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagnifyView.swift; sourceTree = ""; }; - C27624881D95AD7300FE5B2B /* ObjcExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjcExtension.framework; path = ../ObjcExtension/build/Debug/ObjcExtension.framework; sourceTree = ""; }; - C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowSaver.swift; sourceTree = ""; }; - C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizedView.swift; sourceTree = ""; }; - C291ED981DB35BC5008C6B2A /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; - C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardUtils.swift; sourceTree = ""; }; - C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadialProgressView.swift; sourceTree = ""; }; - C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DraggingView.swift; sourceTree = ""; }; - C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModalView.swift; sourceTree = ""; }; - C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModalAction.swift; sourceTree = ""; }; - C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; - C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableAnimationInterface.swift; sourceTree = ""; }; - C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHandler.swift; sourceTree = ""; }; - C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; - C2B1A1281DA3DDED00ACB1DD /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; - C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleButton.swift; sourceTree = ""; }; - C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextButtonBarView.swift; sourceTree = ""; }; - C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackNavigationBar.swift; sourceTree = ""; }; - C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditableViewController.swift; sourceTree = ""; }; - C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationTheme.swift; sourceTree = ""; }; - C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationResourceCache.swift; sourceTree = ""; }; - C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGFont.swift; sourceTree = ""; }; - C2CBCABC1D814D4B00142EC0 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; - C2CBCAC21D81595900142EC0 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; - C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TGUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TGUIKit.h; sourceTree = ""; }; - C2E0DEAB1D7EF51C00EF1C8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGSplitView.swift; sourceTree = ""; }; - C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - C2E0DEBA1D7F05AB00EF1C8D /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; - C2E8694E1F44481400BDD0A2 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; - C2E869501F4449A700BDD0A2 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; - C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadialProgressContainerView.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - C2E0DEA31D7EF51C00EF1C8D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C21AAE3C1DB239ED007638C5 /* Foundation.framework in Frameworks */, - C219E1DD1D8978960042F0C8 /* QuartzCore.framework in Frameworks */, - C219E1D51D87F4A00042F0C8 /* SwiftSignalKitMac.framework in Frameworks */, - C25FC7F91D86E53A0041E303 /* CoreVideo.framework in Frameworks */, - C22E06451D80967E00A11C88 /* CoreText.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - C219E1DE1D8A71680042F0C8 /* layers */ = { - isa = PBXGroup; - children = ( - C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */, - C253A9511D9147A400CDC850 /* Layer.swift */, - ); - name = layers; - sourceTree = ""; - }; - C219E1E91D8AD1330042F0C8 /* navigation */ = { - isa = PBXGroup; - children = ( - C24A37181F3383DF004ADE5C /* SectionController */, - C219E1F41D8BEA1F0042F0C8 /* bar */, - C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */, - C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */, - C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */, - C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */, - C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */, - ); - name = navigation; - sourceTree = ""; - }; - C219E1F41D8BEA1F0042F0C8 /* bar */ = { - isa = PBXGroup; - children = ( - C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */, - C219E1F71D8C14930042F0C8 /* BarView.swift */, - C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */, - C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */, - C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */, - C232EA141E165AEF00C4D38C /* ImageBarView.swift */, - ); - name = bar; - sourceTree = ""; - }; - C22674191DBCD6CA000BA9ED /* gridnode */ = { - isa = PBXGroup; - children = ( - C226741A1DBCD6E8000BA9ED /* GridNode.swift */, - C2E8694E1F44481400BDD0A2 /* GridItem.swift */, - C2E869501F4449A700BDD0A2 /* GridItemNode.swift */, - ); - name = gridnode; - sourceTree = ""; - }; - C2271DA71DAD1728001792B6 /* nodes */ = { - isa = PBXGroup; - children = ( - C2271DA51DAD16B4001792B6 /* BadgeNode.swift */, - ); - name = nodes; - sourceTree = ""; - }; - C2271F341DB4CF040045E719 /* horizontal */ = { - isa = PBXGroup; - children = ( - C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */, - C2271F351DB4CF270045E719 /* HorizontalRowView.swift */, - ); - name = horizontal; - sourceTree = ""; - }; - C2271F411DB4EE170045E719 /* stick */ = { - isa = PBXGroup; - children = ( - C2271F421DB4EE2F0045E719 /* TableStickItem.swift */, - C2271F441DB4EE3C0045E719 /* TableStickView.swift */, - ); - name = stick; - sourceTree = ""; - }; - C22E062A1D7F3C2700A11C88 /* utils */ = { - isa = PBXGroup; - children = ( - C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */, - C2CBCAC11D81593B00142EC0 /* extensions */, - C22E062D1D7F3CC000A11C88 /* TGColor.swift */, - C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */, - C2CBCABC1D814D4B00142EC0 /* System.swift */, - C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */, - C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */, - C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */, - C2303E701D9956C400098E12 /* Style.swift */, - C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */, - C2271DD31DAF766A001792B6 /* WeakReference.swift */, - C21AAE371DB233F7007638C5 /* UIUtils.swift */, - C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */, - C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */, - C226741C1DBCDBA8000BA9ED /* ContainableController.swift */, - C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */, - ); - name = utils; - sourceTree = ""; - }; - C22E06331D804C5800A11C88 /* table */ = { - isa = PBXGroup; - children = ( - C22674191DBCD6CA000BA9ED /* gridnode */, - C2271F411DB4EE170045E719 /* stick */, - C2271F341DB4CF040045E719 /* horizontal */, - C22E06341D804C8200A11C88 /* TableView.swift */, - C22E06361D804D3B00A11C88 /* TableRowItem.swift */, - C22E063A1D8067A000A11C88 /* TableRowView.swift */, - C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */, - ); - name = table; - sourceTree = ""; - }; - C22E06431D80967E00A11C88 /* Frameworks */ = { - isa = PBXGroup; - children = ( - C21AAE3B1DB239ED007638C5 /* Foundation.framework */, - C234CA941D97E163003023F7 /* TelegramCoreMac.framework */, - C234CA921D97E151003023F7 /* PostboxMac.framework */, - C234CA881D97DEB6003023F7 /* TGLibrary.framework */, - C27624881D95AD7300FE5B2B /* ObjcExtension.framework */, - C219E1DC1D8978960042F0C8 /* QuartzCore.framework */, - C219E1D41D87F4A00042F0C8 /* SwiftSignalKitMac.framework */, - C25FC7F81D86E53A0041E303 /* CoreVideo.framework */, - C22E06441D80967E00A11C88 /* CoreText.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - C2303E7D1D99881500098E12 /* overlay */ = { - isa = PBXGroup; - children = ( - C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */, - C2303E791D9987FE00098E12 /* Popover.swift */, - C2303E7B1D99880B00098E12 /* Modal.swift */, - C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */, - C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */, - C230B9171DD3A6350057F596 /* ProgressModal.swift */, - ); - name = overlay; - sourceTree = ""; - }; - C2303E801D9A691300098E12 /* tabbar */ = { - isa = PBXGroup; - children = ( - C2303E811D9A692B00098E12 /* TabBarController.swift */, - C2303E831D9A69BD00098E12 /* TabBarView.swift */, - C2303E851D9A6C2A00098E12 /* TabItem.swift */, - ); - name = tabbar; - sourceTree = ""; - }; - C2449A0F1F0E475200DF5650 /* thrid-party */ = { - isa = PBXGroup; - children = ( - ); - name = "thrid-party"; - sourceTree = ""; - }; - C24A37181F3383DF004ADE5C /* SectionController */ = { - isa = PBXGroup; - children = ( - C24A37191F338406004ADE5C /* SectionViewController.swift */, - ); - name = SectionController; - sourceTree = ""; - }; - C2B9BE831EFC373200D6B96F /* presentation */ = { - isa = PBXGroup; - children = ( - C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */, - C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */, - ); - name = presentation; - sourceTree = ""; - }; - C2CBCAC11D81593B00142EC0 /* extensions */ = { - isa = PBXGroup; - children = ( - C234CA8B1D97DF26003023F7 /* DrawingContext.swift */, - C234CA8C1D97DF26003023F7 /* Image.swift */, - C2CBCAC21D81595900142EC0 /* Extensions.swift */, - ); - name = extensions; - sourceTree = ""; - }; - C2E0DE9D1D7EF51C00EF1C8D = { - isa = PBXGroup; - children = ( - C2E0DEA91D7EF51C00EF1C8D /* TGUIKit */, - C2E0DEA81D7EF51C00EF1C8D /* Products */, - C22E06431D80967E00A11C88 /* Frameworks */, - ); - sourceTree = ""; - }; - C2E0DEA81D7EF51C00EF1C8D /* Products */ = { - isa = PBXGroup; - children = ( - C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */, - ); - name = Products; - sourceTree = ""; - }; - C2E0DEA91D7EF51C00EF1C8D /* TGUIKit */ = { - isa = PBXGroup; - children = ( - C2449A0F1F0E475200DF5650 /* thrid-party */, - C2B9BE831EFC373200D6B96F /* presentation */, - C2271DA71DAD1728001792B6 /* nodes */, - C2303E801D9A691300098E12 /* tabbar */, - C2303E7D1D99881500098E12 /* overlay */, - C219E1E91D8AD1330042F0C8 /* navigation */, - C22E06331D804C5800A11C88 /* table */, - C22E062A1D7F3C2700A11C88 /* utils */, - C2E0DEBC1D7F071300EF1C8D /* view */, - C2E0DEB41D7EF53900EF1C8D /* split-view */, - C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */, - C2E0DEAB1D7EF51C00EF1C8D /* Info.plist */, - ); - path = TGUIKit; - sourceTree = ""; - }; - C2E0DEB41D7EF53900EF1C8D /* split-view */ = { - isa = PBXGroup; - children = ( - C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */, - ); - name = "split-view"; - sourceTree = ""; - }; - C2E0DEBC1D7F071300EF1C8D /* view */ = { - isa = PBXGroup; - children = ( - C219E1DE1D8A71680042F0C8 /* layers */, - C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */, - C219E1E11D8A93050042F0C8 /* TextView.swift */, - C2E0DEBA1D7F05AB00EF1C8D /* View.swift */, - C22E06381D804DCD00A11C88 /* ScrollView.swift */, - C20232B51D845B0B007C9ADE /* TextNode.swift */, - C20232B71D85525C007C9ADE /* ViewUtils.swift */, - C25FC7F61D86DC370041E303 /* TGClipView.swift */, - C253A9641D91B24100CDC850 /* TextViewLabel.swift */, - C253A9751D9430A900CDC850 /* ImageView.swift */, - C234CA961D981F00003023F7 /* Control.swift */, - C2303E6E1D9950E000098E12 /* Button.swift */, - C2303E751D996E6300098E12 /* ImageButton.swift */, - C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */, - C2303E8B1D9A8B3000098E12 /* SearchView.swift */, - C2B1A1281DA3DDED00ACB1DD /* Node.swift */, - C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */, - C2271DC71DAEAAF9001792B6 /* SwitchView.swift */, - C291ED981DB35BC5008C6B2A /* Window.swift */, - C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */, - C2167E5C1DC2534300F98E03 /* SelectingControl.swift */, - C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */, - C265058F1E02EBEE001954DC /* MagnifyView.swift */, - C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */, - C21656C51EE0A7050041A6BA /* SegmentedControl.swift */, - C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */, - C21178011F17E78E00AC706D /* TimableProgressView.swift */, - C24A37151F324A36004ADE5C /* ShadowView.swift */, - C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */, - ); - name = view; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - C2E0DEA41D7EF51C00EF1C8D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C2E0DEAC1D7EF51C00EF1C8D /* TGUIKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - C2E0DEA61D7EF51C00EF1C8D /* TGUIKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = C2E0DEAF1D7EF51C00EF1C8D /* Build configuration list for PBXNativeTarget "TGUIKit" */; - buildPhases = ( - C2E0DEA21D7EF51C00EF1C8D /* Sources */, - C2E0DEA31D7EF51C00EF1C8D /* Frameworks */, - C2E0DEA41D7EF51C00EF1C8D /* Headers */, - C2E0DEA51D7EF51C00EF1C8D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = TGUIKit; - productName = TGUIKit; - productReference = C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - C2E0DE9E1D7EF51C00EF1C8D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0900; - ORGANIZATIONNAME = Telegram; - TargetAttributes = { - C2E0DEA61D7EF51C00EF1C8D = { - CreatedOnToolsVersion = 8.0; - DevelopmentTeam = 6N38VWS5BX; - LastSwiftMigration = 0830; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = C2E0DEA11D7EF51C00EF1C8D /* Build configuration list for PBXProject "TGUIKit" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = C2E0DE9D1D7EF51C00EF1C8D; - productRefGroup = C2E0DEA81D7EF51C00EF1C8D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - C2E0DEA61D7EF51C00EF1C8D /* TGUIKit */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - C2E0DEA51D7EF51C00EF1C8D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - C2E0DEA21D7EF51C00EF1C8D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C219E1F81D8C14930042F0C8 /* BarView.swift in Sources */, - C2303E861D9A6C2A00098E12 /* TabItem.swift in Sources */, - C219E1EF1D8AE3310042F0C8 /* AnimationStyle.swift in Sources */, - C2271DA61DAD16B4001792B6 /* BadgeNode.swift in Sources */, - C234CA8F1D97DF26003023F7 /* Image.swift in Sources */, - C2B1A1181DA1673300ACB1DD /* TableAnimationInterface.swift in Sources */, - C2271F331DB4CEB30045E719 /* HorizontalTableView.swift in Sources */, - C2E0DEBB1D7F05AB00EF1C8D /* View.swift in Sources */, - C2271DD41DAF766A001792B6 /* WeakReference.swift in Sources */, - C22E06351D804C8200A11C88 /* TableView.swift in Sources */, - C232EA151E165AEF00C4D38C /* ImageBarView.swift in Sources */, - C2167E611DC356FD00F98E03 /* LinearProgressControl.swift in Sources */, - C22E062E1D7F3CC000A11C88 /* TGColor.swift in Sources */, - C2271DED1DAFE4D0001792B6 /* TransformImageArguments.swift in Sources */, - C2E0DEB71D7EF58200EF1C8D /* TGSplitView.swift in Sources */, - C2B1A1291DA3DDED00ACB1DD /* Node.swift in Sources */, - C2B9BE861EFC373D00D6B96F /* PresentationTheme.swift in Sources */, - C234CA8E1D97DF26003023F7 /* DrawingContext.swift in Sources */, - C219E1E01D8A71820042F0C8 /* SImageLayer.swift in Sources */, - C28CFC7D1F387A7F00C55596 /* TokenizedView.swift in Sources */, - C2B1A11E1DA26BA400ACB1DD /* TransactionHandler.swift in Sources */, - C2303E8C1D9A8B3000098E12 /* SearchView.swift in Sources */, - C234CA971D981F00003023F7 /* Control.swift in Sources */, - C2303E6F1D9950E000098E12 /* Button.swift in Sources */, - C21656C61EE0A7050041A6BA /* SegmentedControl.swift in Sources */, - C21AAE381DB233F7007638C5 /* UIUtils.swift in Sources */, - C20232B61D845B0B007C9ADE /* TextNode.swift in Sources */, - C2FD33E71E6952A8008D13D4 /* RadialProgressContainerView.swift in Sources */, - C219E1F31D8B02FA0042F0C8 /* CAAnimationUtils.swift in Sources */, - C291ED991DB35BC5008C6B2A /* Window.swift in Sources */, - C296AF841D8D9D7D001DBB59 /* RadialProgressView.swift in Sources */, - C24A371A1F338406004ADE5C /* SectionViewController.swift in Sources */, - C219E1EB1D8AD1470042F0C8 /* NavigationViewController.swift in Sources */, - C253A9761D9430A900CDC850 /* ImageView.swift in Sources */, - C24A37161F324A36004ADE5C /* ShadowView.swift in Sources */, - C2B1A1321DA57E8300ACB1DD /* EditableViewController.swift in Sources */, - C2167E5D1DC2534300F98E03 /* SelectingControl.swift in Sources */, - C230B9181DD3A6350057F596 /* ProgressModal.swift in Sources */, - C21178021F17E78E00AC706D /* TimableProgressView.swift in Sources */, - C2E0DEB91D7EF7F300EF1C8D /* ViewController.swift in Sources */, - C2303E841D9A69BD00098E12 /* TabBarView.swift in Sources */, - C22E063B1D8067A000A11C88 /* TableRowView.swift in Sources */, - C2303E7A1D9987FE00098E12 /* Popover.swift in Sources */, - C29B5F3C1DC66DAC00D13E65 /* DraggingView.swift in Sources */, - C226741D1DBCDBA8000BA9ED /* ContainableController.swift in Sources */, - C253A9651D91B24100CDC850 /* TextViewLabel.swift in Sources */, - C219E1FA1D8C316C0042F0C8 /* TitledBarView.swift in Sources */, - C28BAB0D1DF8550E0027CE3A /* WindowSaver.swift in Sources */, - C2303E7C1D99880B00098E12 /* Modal.swift in Sources */, - C219E1E21D8A93050042F0C8 /* TextView.swift in Sources */, - C2449A151F0E490C00DF5650 /* ProgressIndicator.swift in Sources */, - C21AAE3A1DB2398B007638C5 /* DisplayLinkDispatcher.swift in Sources */, - C2271F431DB4EE2F0045E719 /* TableStickItem.swift in Sources */, - C2E8694F1F44481400BDD0A2 /* GridItem.swift in Sources */, - C2B9BE871EFC373D00D6B96F /* PresentationResourceCache.swift in Sources */, - C2271F451DB4EE3C0045E719 /* TableStickView.swift in Sources */, - C2E869511F4449A700BDD0A2 /* GridItemNode.swift in Sources */, - C25FC7F71D86DC370041E303 /* TGClipView.swift in Sources */, - C2CBCAC31D81595900142EC0 /* Extensions.swift in Sources */, - C219E1ED1D8ADEE00042F0C8 /* NavigationBarView.swift in Sources */, - C2303E821D9A692B00098E12 /* TabBarController.swift in Sources */, - C2CBCABB1D80AD1F00142EC0 /* TGFont.swift in Sources */, - C2B1A12E1DA5148B00ACB1DD /* TextButtonBarView.swift in Sources */, - C20232B81D85525C007C9ADE /* ViewUtils.swift in Sources */, - C2271F361DB4CF270045E719 /* HorizontalRowView.swift in Sources */, - C22E06391D804DCD00A11C88 /* ScrollView.swift in Sources */, - C219E1F11D8AFD140042F0C8 /* AnimationBlockDelegate.swift in Sources */, - C253A9521D9147A400CDC850 /* Layer.swift in Sources */, - C22E06371D804D3B00A11C88 /* TableRowItem.swift in Sources */, - C2303E711D9956C400098E12 /* Style.swift in Sources */, - C2271DC81DAEAAF9001792B6 /* SwitchView.swift in Sources */, - C2B1A1251DA2F3DC00ACB1DD /* ContextMenu.swift in Sources */, - C224A72B1EB7581500F43F3F /* MajorNavigationController.swift in Sources */, - C2B1A1301DA53E7D00ACB1DD /* BackNavigationBar.swift in Sources */, - C2303E761D996E6300098E12 /* ImageButton.swift in Sources */, - C226741B1DBCD6E8000BA9ED /* GridNode.swift in Sources */, - C2CBCABD1D814D4B00142EC0 /* System.swift in Sources */, - C2303E7F1D99BEAE00098E12 /* OverlayControl.swift in Sources */, - C29B5F471DC8CA2B00D13E65 /* NavigationModalView.swift in Sources */, - C26505901E02EBEE001954DC /* MagnifyView.swift in Sources */, - C2B1A12C1DA50CC600ACB1DD /* TitleButton.swift in Sources */, - C291ED9B1DB361ED008C6B2A /* KeyboardUtils.swift in Sources */, - C29B5F491DC8CA3C00D13E65 /* NavigationModalAction.swift in Sources */, - C2A71CEB1DDB382F00C69F73 /* TableViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - C22069D21E8EB4D700E82730 /* Release Hockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - 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 = gnu99; - 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; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Release Hockeyapp"; - }; - C22069D31E8EB4D700E82730 /* Release Hockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 6N38VWS5BX; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = TGUIKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = mh_dylib; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 4.0; - }; - name = "Release Hockeyapp"; - }; - C232EA391E1BCFDE00C4D38C /* Release AppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - 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 = gnu99; - 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; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Release AppStore"; - }; - C232EA3A1E1BCFDE00C4D38C /* Release AppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 6N38VWS5BX; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = TGUIKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = mh_dylib; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 4.0; - }; - name = "Release AppStore"; - }; - C2E0DEAD1D7EF51C00EF1C8D /* Debug Hockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - 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; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Debug Hockeyapp"; - }; - C2E0DEAE1D7EF51C00EF1C8D /* Debug AppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - 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; - MACOSX_DEPLOYMENT_TARGET = 10.10; - MTL_ENABLE_DEBUG_INFO = NO; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = "Debug AppStore"; - }; - C2E0DEB01D7EF51C00EF1C8D /* Debug Hockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 6N38VWS5BX; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = TGUIKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = mh_dylib; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - }; - name = "Debug Hockeyapp"; - }; - C2E0DEB11D7EF51C00EF1C8D /* Debug AppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 6N38VWS5BX; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = TGUIKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = mh_dylib; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 4.0; - }; - name = "Debug AppStore"; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - C2E0DEA11D7EF51C00EF1C8D /* Build configuration list for PBXProject "TGUIKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C2E0DEAD1D7EF51C00EF1C8D /* Debug Hockeyapp */, - C2E0DEAE1D7EF51C00EF1C8D /* Debug AppStore */, - C22069D21E8EB4D700E82730 /* Release Hockeyapp */, - C232EA391E1BCFDE00C4D38C /* Release AppStore */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Debug AppStore"; - }; - C2E0DEAF1D7EF51C00EF1C8D /* Build configuration list for PBXNativeTarget "TGUIKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C2E0DEB01D7EF51C00EF1C8D /* Debug Hockeyapp */, - C2E0DEB11D7EF51C00EF1C8D /* Debug AppStore */, - C22069D31E8EB4D700E82730 /* Release Hockeyapp */, - C232EA3A1E1BCFDE00C4D38C /* Release AppStore */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Debug AppStore"; - }; -/* End XCConfigurationList section */ - }; - rootObject = C2E0DE9E1D7EF51C00EF1C8D /* Project object */; -} diff --git a/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 2755b71a46..0000000000 --- a/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - SchemeUserState - - TGUIKit.xcscheme - - isShown - - orderHint - 0 - - - SuppressBuildableAutocreation - - C2E0DEA61D7EF51C00EF1C8D - - primary - - - - - diff --git a/TGUIKit/TGUIKit/AnimationStyle.swift b/TGUIKit/TGUIKit/AnimationStyle.swift deleted file mode 100644 index e776e8528d..0000000000 --- a/TGUIKit/TGUIKit/AnimationStyle.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// AnimationStyle.swift -// TGUIKit -// -// Created by keepcoder on 15/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public struct AnimationStyle { - - public let duration:CFTimeInterval - public let function:String - -} diff --git a/TGUIKit/TGUIKit/BackNavigationBar.swift b/TGUIKit/TGUIKit/BackNavigationBar.swift deleted file mode 100644 index fb154ac20b..0000000000 --- a/TGUIKit/TGUIKit/BackNavigationBar.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// BackNavigationBar.swift -// TGUIKit -// -// Created by keepcoder on 05/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -open class BackNavigationBar: TextButtonBarView { - - - public init(_ controller:ViewController) { - let backSettings = controller.backSettings() - super.init(controller: controller, text: backSettings.0, style: navigationButtonStyle) - - if let image = backSettings.1 { - button.set(image: image, for: .Normal) - } - button.disableActions() - button.set(handler: { [weak self] _ in - self?.controller?.executeReturn() - }, for: .Up) - } - - public func requestUpdate() { - let backSettings = controller?.backSettings() ?? ("",nil) - button.set(text: backSettings.0, for: .Normal) - if let image = backSettings.1 { - button.set(image: image, for: .Normal) - } else { - button.removeImage(for: .Normal) - } - style = navigationButtonStyle - button.style = navigationButtonStyle - needsLayout = true - } - - open override func layout() { - super.layout() - if controller?.backSettings().1 != nil { - button.centerY(x: 10) - } else { - button.center() - } - } - - deinit { - var bp:Int = 0 - bp += 1 - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/BarView.swift b/TGUIKit/TGUIKit/BarView.swift deleted file mode 100644 index 8f86ddfe50..0000000000 --- a/TGUIKit/TGUIKit/BarView.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// BarView.swift -// TGUIKit -// -// Created by keepcoder on 16/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -open class BarView: OverlayControl { - - - public var clickHandler:()->Void = {} - - public var minWidth:CGFloat = 80 - public private(set) weak var controller: ViewController? - - - override open func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - } - - - open override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - self.setNeedsDisplay() - } - - public init(_ width:CGFloat = 80, controller: ViewController) { - self.minWidth = width - self.controller = controller - super.init() - animates = false - frame = NSMakeRect(0, 0, minWidth, 50) - overlayInitEvent() - } - - override open func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - set(background: presentation.colors.background, for: .Normal) - backgroundColor = presentation.colors.background - } - - - func overlayInitEvent() -> Void { - set(handler: { [weak self] control in - self?.clickHandler() - }, for: .Click) - updateLocalizationAndTheme() - } - - required public init(frame frameRect: NSRect) { - super.init(frame:frameRect) - overlayInitEvent() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - -} diff --git a/TGUIKit/TGUIKit/CAAnimationUtils.swift b/TGUIKit/TGUIKit/CAAnimationUtils.swift deleted file mode 100644 index 5db353b86d..0000000000 --- a/TGUIKit/TGUIKit/CAAnimationUtils.swift +++ /dev/null @@ -1,385 +0,0 @@ -// -// CAAnimationUtils.swift -// TGUIKit -// -// Created by keepcoder on 15/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -@objc public class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { - var completion: ((Bool) -> Void)? - - public init(completion: ((Bool) -> Void)?) { - self.completion = completion - - super.init() - } - - @objc public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { - if let completion = self.completion { - completion(flag) - } - } -} - -private let completionKey = "CAAnimationUtils_completion" - -public let kCAMediaTimingFunctionSpring = "CAAnimationUtilsSpringCurve" - -public extension CAAnimation { - public var completion: ((Bool) -> Void)? { - get { - if let delegate = self.delegate as? CALayerAnimationDelegate { - return delegate.completion - } else { - return nil - } - } set(value) { - if let delegate = self.delegate as? CALayerAnimationDelegate { - delegate.completion = value - } else { - self.delegate = CALayerAnimationDelegate(completion: value) - } - } - } -} - -public func makeSpringAnimation(_ path:String) -> CABasicAnimation { - if #available(OSX 10.11, *) { - let springAnimation:CASpringAnimation = CASpringAnimation(keyPath: path) - springAnimation.mass = 3.0; - springAnimation.stiffness = 1000.0; - springAnimation.damping = 500.0; - springAnimation.initialVelocity = 0.0; - springAnimation.duration = 0.5;//springAnimation.settlingDuration; - springAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - return springAnimation; - } else { - let anim:CABasicAnimation = CABasicAnimation(keyPath: path) - anim.duration = 0.2 - anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - - return anim - } - -} - -public func makeSpringBounceAnimation(_ path:String, _ initialVelocity:CGFloat, _ damping: CGFloat = 88.0) -> CABasicAnimation { - if #available(OSX 10.11, *) { - let springAnimation:CASpringAnimation = CASpringAnimation(keyPath: path) - springAnimation.mass = 5.0 - springAnimation.stiffness = 900.0 - springAnimation.damping = damping - springAnimation.initialVelocity = initialVelocity - springAnimation.duration = springAnimation.settlingDuration - springAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - return springAnimation; - } else { - let anim:CABasicAnimation = CABasicAnimation(keyPath: path) - anim.duration = 0.2 - anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - - return anim - } - -} - - -public extension CALayer { - public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if timingFunction == kCAMediaTimingFunctionSpring { - let animation = makeSpringAnimation(keyPath) - animation.fromValue = from - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - let k = Float(1.0) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - self.add(animation, forKey: keyPath) - } else { - let k = Float(1.0) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - let animation = CABasicAnimation(keyPath: keyPath) - animation.fromValue = from - animation.toValue = to - animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - animation.speed = speed - animation.isAdditive = additive - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - self.add(animation, forKey: keyPath) - } - } - - public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - let k = Float(1.0) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - let animation = CABasicAnimation(keyPath: keyPath) - animation.fromValue = from - animation.toValue = to - animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - animation.speed = speed - animation.isAdditive = true - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - self.add(animation, forKey: key) - } - - public func animateScaleSpring(from: CGFloat, to: CGFloat, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = makeSpringBounceAnimation("transform", initialVelocity) - - var fr = CATransform3DIdentity - fr = CATransform3DTranslate(fr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - fr = CATransform3DScale(fr, from, from, 1) - fr = CATransform3DTranslate(fr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - - animation.fromValue = NSValue(caTransform3D: fr) - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - let speed: Float = 1.0 - - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - var tr = CATransform3DIdentity - tr = CATransform3DTranslate(tr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - tr = CATransform3DScale(tr, to, to, 1) - tr = CATransform3DTranslate(tr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - animation.toValue = NSValue(caTransform3D: tr) - - - self.add(animation, forKey: "transform") - } - - public func animateScaleCenter(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = CABasicAnimation(keyPath: "transform") - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - - var fr = CATransform3DIdentity - fr = CATransform3DTranslate(fr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - fr = CATransform3DScale(fr, from, from, 1) - fr = CATransform3DTranslate(fr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - - animation.fromValue = NSValue(caTransform3D: fr) - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - - - animation.duration = duration - animation.isAdditive = additive - - var tr = CATransform3DIdentity - tr = CATransform3DTranslate(tr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - tr = CATransform3DScale(tr, to, to, 1) - tr = CATransform3DTranslate(tr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - animation.toValue = NSValue(caTransform3D: tr) - - - self.add(animation, forKey: "transform") - } - - public func animateRotateCenter(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - - - let animation = makeSpringAnimation("transform") - - var fr = CATransform3DIdentity - fr = CATransform3DTranslate(fr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - fr = CATransform3DRotate(fr, from * CGFloat.pi / 180, 0, 0, 1.0) - fr = CATransform3DTranslate(fr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - - animation.fromValue = NSValue(caTransform3D: fr) - - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - let speed: Float = 1.0 - - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - var tr = CATransform3DIdentity - tr = CATransform3DTranslate(tr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) - tr = CATransform3DRotate(fr, to * CGFloat.pi / 180, 0, 0, 1.0) - tr = CATransform3DTranslate(tr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) - animation.toValue = NSValue(caTransform3D: tr) - - - self.add(animation, forKey: "transform") - -// let animation = CABasicAnimation(keyPath: "transform") -// animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) -// var fr = CATransform3DIdentity -// fr = CATransform3DTranslate(fr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) -// fr = CATransform3DRotate(fr, from * CGFloat.pi / 180, 0, 0, 1.0) -// fr = CATransform3DTranslate(fr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) -// -// animation.fromValue = NSValue(caTransform3D: fr) -// animation.isRemovedOnCompletion = removeOnCompletion -// // animation.fillMode = kCAFillModeForwards -// if let completion = completion { -// animation.delegate = CALayerAnimationDelegate(completion: completion) -// } -// -// let speed: Float = 1.0 -// -// -// animation.speed = speed * Float(animation.duration / duration) -// animation.isAdditive = additive -// -// var tr = CATransform3DIdentity -// // tr = CATransform3DTranslate(tr, floorToScreenPixels(frame.width / 2), floorToScreenPixels(frame.height / 2), 0) -// tr = CATransform3DRotate(fr, to * CGFloat.pi / 180, 0, 0, 1.0) -// //tr = CATransform3DTranslate(tr, -floorToScreenPixels(frame.width / 2), -floorToScreenPixels(frame.height / 2), 0) -// animation.toValue = NSValue(caTransform3D: tr) -// -// -// self.add(animation, forKey: "transform") - } - - - public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) - } - - public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation: CABasicAnimation - if #available(iOS 9.0, *) { - animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) - } else { - animation = makeSpringAnimation(keyPath) - } - animation.fromValue = from - animation.toValue = to - animation.isRemovedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - let k = Float(1) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - - animation.speed = speed * Float(animation.duration / duration) - animation.isAdditive = additive - - self.add(animation, forKey: keyPath) - } - - public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) - } - - func animatePosition(from: NSPoint, to: NSPoint, duration: Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { - if let completion = completion { - completion(true) - } - return - } - self.animate(from: NSValue(point: from), to: NSValue(point: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) - } - - func animateBounds(from: NSRect, to: NSRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { - if let completion = completion { - completion(true) - } - return - } - self.animate(from: NSValue(rect: from), to: NSValue(rect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) - } - - public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { - self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseOut, duration: duration, removeOnCompletion: true) - } - - public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { - if let completion = completion { - completion(true) - } - return - } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: nil) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) - } - - public func shake(_ duration:CFTimeInterval, from:NSPoint, to:NSPoint) { - let animation = CABasicAnimation(keyPath: "position") - animation.duration = duration; - animation.repeatCount = 4 - animation.autoreverses = true - animation.isRemovedOnCompletion = true - - animation.fromValue = NSValue(point: from) - animation.toValue = NSValue(point: to) - - self.add(animation, forKey: "position") - } - - /* - + (CAAnimation *)shakeWithDuration:(float)duration fromValue:(CGPoint)fromValue toValue:(CGPoint)toValue { - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; - animation.duration = duration; - animation.repeatCount = 4; - animation.autoreverses = YES; - animation.removedOnCompletion = YES; - NSValue *fromValueValue = [NSValue value:&fromValue withObjCType:@encode(CGPoint)]; - NSValue *toValueValue = [NSValue value:&toValue withObjCType:@encode(CGPoint)]; - - animation.fromValue = fromValueValue; - animation.toValue = toValueValue; - return animation; - } - */ -} diff --git a/TGUIKit/TGUIKit/ContextMenu.swift b/TGUIKit/TGUIKit/ContextMenu.swift deleted file mode 100644 index a8fbf7b1c0..0000000000 --- a/TGUIKit/TGUIKit/ContextMenu.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// ContextMenu.swift -// TGUIKit -// -// Created by keepcoder on 03/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - -public class ContextSeparatorItem : ContextMenuItem { - public init() { - super.init("", handler: {}, image: nil) - } - - public override var isSeparatorItem: Bool { - return true - } - - required public init(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -public class ContextMenuItem : NSMenuItem { - - fileprivate var handler:()->Void = {() in} - - public init(_ title:String, handler:@escaping()->Void, image:NSImage? = nil) { - self.handler = handler - - super.init(title: title, action: nil, keyEquivalent: "") - - self.title = title - self.action = #selector(click) - self.target = self - self.isEnabled = true - self.image = image - } - - required public init(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc func click() -> Void { - handler() - } - -} - -public final class ContextMenu : NSMenu, NSMenuDelegate { - - var onShow:(ContextMenu)->Void = {(ContextMenu) in} - var onClose:()->Void = {() in} - - weak var view:NSView? - - public static func show(items:[ContextMenuItem], view:NSView, event:NSEvent, onShow:@escaping(ContextMenu)->Void, onClose:@escaping()->Void) -> Void { - - let menu = ContextMenu.init() - menu.onShow = onShow - menu.onClose = onClose - menu.view = view - - for item in items { - menu.addItem(item) - } - - menu.delegate = menu - NSMenu.popUpContextMenu(menu, with: event, for: view) - } - - - public override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { - return true - } - - public func menuWillOpen(_ menu: NSMenu) { - onShow(self) - } - - public func menuDidClose(_ menu: NSMenu) { - onClose() - } - -} diff --git a/TGUIKit/TGUIKit/Control.swift b/TGUIKit/TGUIKit/Control.swift deleted file mode 100644 index 1bbba35f26..0000000000 --- a/TGUIKit/TGUIKit/Control.swift +++ /dev/null @@ -1,394 +0,0 @@ -// -// Control.swift -// TGUIKit -// -// Created by keepcoder on 25/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -public enum ControlState { - case Normal - case Hover - case Highlight - case Other -} - -public enum ControlEvent { - case Down - case Up - case Click - case SingleClick - case RightClick - case MouseDragging - case LongMouseDown - case LongMouseUp - case LongOver -} - -open class Control: View { - - open var isEnabled:Bool = true { - didSet { - if isEnabled != oldValue { - apply(state: controlState) - } - } - } - open var hideAnimated:Bool = false - - private let longHandleDisposable = MetaDisposable() - private let longOverHandleDisposable = MetaDisposable() - public var isSelected:Bool { - didSet { - if isSelected != oldValue { - apply(state: isSelected ? .Highlight : self.controlState) - } - } - } - - open var animationStyle:AnimationStyle = AnimationStyle(duration:0.3, function:kCAMediaTimingFunctionSpring) - - var trackingArea:NSTrackingArea? - - public var interactionStateForRestore:Bool? = nil - - public var userInteractionEnabled:Bool = true - - private var handlers:[(ControlEvent,(Control) -> Void)] = [] - private var stateHandlers:[(ControlState,(Control) -> Void)] = [] - - private var backgroundState:[ControlState:NSColor] = [:] - private var mouseMovedInside: Bool = true - private var longInvoked: Bool = false - open override var backgroundColor: NSColor { - get{ - return self.style.backgroundColor - } - set { - if self.style.backgroundColor != newValue { - self.style.backgroundColor = newValue - self.setNeedsDisplayLayer() - } - } - } - - public var style:ControlStyle = ControlStyle() { - didSet { - if style != oldValue { - apply(style:style) - } - } - } - - open override func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - apply(state: self.controlState) - } - - public private(set) var controlState:ControlState = .Normal { - didSet { - if oldValue != controlState { - apply(state: isSelected ? .Highlight : controlState) - - for (state,handler) in stateHandlers { - if state == controlState { - handler(self) - } - } - - } - } - } - - public func apply(state:ControlState) -> Void { - let state:ControlState = self.isSelected ? .Highlight : state - if let color = backgroundState[state] { - self.layer?.backgroundColor = color.cgColor - } else { - self.layer?.backgroundColor = self.backgroundColor.cgColor - } - if animates { - self.layer?.animateBackground() - } - } - - private var mouseIsDown:Bool = false - - open override func updateTrackingAreas() { - super.updateTrackingAreas(); - - if let trackingArea = trackingArea { - self.removeTrackingArea(trackingArea) - } - - trackingArea = nil - - if let _ = window { - let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.activeInKeyWindow, NSTrackingArea.Options.inVisibleRect] - self.trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) - - self.addTrackingArea(self.trackingArea!) - } - - } - - open override func viewDidMoveToSuperview() { - super.viewDidMoveToSuperview() - updateTrackingAreas() - } - - deinit { - if let trackingArea = self.trackingArea { - self.removeTrackingArea(trackingArea) - } - longHandleDisposable.dispose() - longOverHandleDisposable.dispose() - } - - public var controlIsHidden: Bool { - return super.isHidden || layer!.opacity < Float(1.0) - } - - open override var isHidden: Bool { - get { - return super.isHidden - } - set { - if newValue != super.isHidden { - if hideAnimated { - if !newValue { - super.isHidden = newValue - } - self.layer?.opacity = newValue ? 0.0 : 1.0 - self.layer?.animateAlpha(from: newValue ? 1.0 : 0.0, to: newValue ? 0.0 : 1.0, duration: 0.2, completion:{[weak self](completed) in - self?.updateHiddenState(newValue) - }) - } else { - updateHiddenState(newValue) - } - } - } - } - - public func forceHide() -> Void { - super.isHidden = true - self.layer?.removeAllAnimations() - } - - private func updateHiddenState(_ value:Bool) -> Void { - super.isHidden = value - } - - - public var canHighlight: Bool = true - - public func set(handler:@escaping (Control) -> Void, for event:ControlEvent) -> Void { - handlers.append((event,handler)) - } - - public func set(handler:@escaping (Control) -> Void, for event:ControlState) -> Void { - stateHandlers.append((event,handler)) - } - - public func set(background:NSColor, for state:ControlState) -> Void { - backgroundState[state] = background - apply(state: self.controlState) - self.setNeedsDisplayLayer() - } - - public func removeLastHandler() -> Void { - if !handlers.isEmpty { - handlers.removeLast() - } - } - - public func removeLastStateHandler() -> Void { - if !stateHandlers.isEmpty { - stateHandlers.removeLast() - } - } - - public func removeAllStateHandler() -> Void { - stateHandlers.removeAll() - } - - public func removeAllHandlers() ->Void { - handlers.removeAll() - } - - override open func mouseDown(with event: NSEvent) { - mouseIsDown = true - longInvoked = false - longOverHandleDisposable.set(nil) - - if event.modifierFlags.contains(.control) { - super.mouseDown(with: event) - return - } - - if userInteractionEnabled { - send(event: .Down) - updateState() - - let disposable = (Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(next: { [weak self] in - if let inside = self?.mouseInside(), inside { - self?.longInvoked = true - self?.send(event: .LongMouseDown) - } - }) - - longHandleDisposable.set(disposable) - - } else { - super.mouseDown(with: event) - } - } - - override open func mouseUp(with event: NSEvent) { - - longHandleDisposable.set(nil) - longOverHandleDisposable.set(nil) - mouseIsDown = false - - if userInteractionEnabled && !event.modifierFlags.contains(.control) { - if isEnabled { - send(event: .Up) - - if mouseInside() && !longInvoked { - if event.clickCount == 1 { - send(event: .SingleClick) - } - send(event: .Click) - } - } - - updateState() - - } else { - super.mouseUp(with: event) - } - } - - func send(event:ControlEvent) -> Void { - for (e,handler) in handlers { - if e == event { - handler(self) - } - } - } - - override open func mouseMoved(with event: NSEvent) { - if userInteractionEnabled { - updateState() - } else { - super.mouseMoved(with: event) - } - } - - open override func rightMouseDown(with event: NSEvent) { - if userInteractionEnabled { - updateState() - super.rightMouseDown(with: event) - } else { - super.rightMouseDown(with: event) - } - } - - public func updateState() -> Void { - if mouseInside() { - if mouseIsDown && canHighlight { - self.controlState = .Highlight - } else if mouseMovedInside { - self.controlState = .Hover - } else { - self.controlState = .Normal - } - } else { - self.controlState = .Normal - } - } - - override open func mouseEntered(with event: NSEvent) { - if userInteractionEnabled { - - let disposable = (Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(next: { [weak self] in - if let strongSelf = self, strongSelf.mouseInside(), strongSelf.controlState == .Hover { - strongSelf.send(event: .LongOver) - } - }) - longOverHandleDisposable.set(disposable) - - updateState() - } else { - super.mouseEntered(with: event) - } - } - - override open func mouseExited(with event: NSEvent) { - if userInteractionEnabled { - - updateState() - } else { - super.mouseExited(with: event) - } - } - - - - override open func mouseDragged(with event: NSEvent) { - if userInteractionEnabled { - send(event: .MouseDragging) - updateState() - } else { - super.mouseDragged(with: event) - } - } - - func apply(style:ControlStyle) -> Void { - set(background: style.backgroundColor, for: .Normal) - self.backgroundColor = style.backgroundColor - self.setNeedsDisplayLayer() - } - - - - required public init(frame frameRect: NSRect) { - self.isSelected = false - super.init(frame: frameRect) - animates = true - guard #available(OSX 10.12, *) else { - layer?.opacity = 0.99 - return - } - //self.wantsLayer = true - //self.layer?.isOpaque = true - } - - public override init() { - self.isSelected = false - super.init(frame: NSZeroRect) - animates = true - - guard #available(OSX 10.12, *) else { - layer?.opacity = 0.99 - return - } - //self.wantsLayer = true - //self.layer?.isOpaque = true - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - open override func becomeFirstResponder() -> Bool { - if let window = kitWindow { - return window.makeFirstResponder(self) - } - return false - } - -} diff --git a/TGUIKit/TGUIKit/DraggingView.swift b/TGUIKit/TGUIKit/DraggingView.swift deleted file mode 100644 index b06ebc5e83..0000000000 --- a/TGUIKit/TGUIKit/DraggingView.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// DragController.swift -// Telegram-Mac -// -// Created by keepcoder on 30/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public class DragItem { - let title:String - let desc:String - let handler:()->Void - - let attr:NSAttributedString - - public init(title:String, desc:String,handler:@escaping()->Void) { - self.title = title - self.desc = desc - self.handler = handler - - let attr:NSMutableAttributedString = NSMutableAttributedString() - _ = attr.append(string: title, color: presentation.colors.grayText, font: .normal(.huge)) - _ = attr.append(string: "\n") - - _ = attr.append(string: desc, color: presentation.colors.text, font: .medium(.custom(16))) - - self.attr = attr.copy() as! NSAttributedString - } -} - -class DragView : OverlayControl { - var item:DragItem - - var textView:TextView = TextView() - - init(item:DragItem) { - self.item = item - super.init(frame: NSZeroRect) - - addSubview(textView) - textView.backgroundColor = presentation.colors.background - self.layer?.cornerRadius = .cornerRadius - self.layer?.borderWidth = 2.0 - self.layer?.backgroundColor = presentation.colors.background.cgColor - self.layer?.borderColor = presentation.colors.border.cgColor - self.backgroundColor = presentation.colors.background - self.set(handler: { control in - control.layer?.borderColor = presentation.colors.blueUI.cgColor - control.layer?.animateBorder() - }, for: .Hover) - - self.set(handler: { control in - control.layer?.borderColor = presentation.colors.border.cgColor - control.layer?.animateBorder() - }, for: .Normal) - - - } - - - - override func layout() { - super.layout() - let layout:TextViewLayout = TextViewLayout(item.attr, maximumNumberOfLines: 2, truncationType: .middle, alignment:.center) - layout.measure(width: frame.width - 20) - - textView.update(layout) - textView.center() - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } -} - -public class DraggingView: SplitView { - - var container:View = View() - - public weak var controller:ViewController? - - required public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - container.backgroundColor = .clear - self.registerForDraggedTypes([.string, .tiff, .kUrl, .kFilenames]) - } - - override public func performDragOperation(_ sender: NSDraggingInfo) -> Bool { - - for itemView in container.subviews as! [DragView] { - if itemView.mouseInside() { - itemView.item.handler() - return true - } - } - - return false - } - - func layoutItems(with items:[DragItem]) { - container.removeAllSubviews() - - let itemSize = NSMakeSize(frame.width - 10, ceil((frame.height - 10 - (5 * (CGFloat(items.count) - 1))) / CGFloat(items.count))) - - var y:CGFloat = 5 - for item in items { - let view:DragView = DragView(item:item) - view.frame = NSMakeRect(5, y, itemSize.width, itemSize.height) - container.addSubview(view) - y += itemSize.height + 5 - - } - - - } - - override public func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { - - if let items = controller?.draggingItems(for: sender.draggingPasteboard()), items.count > 0, !sender.draggingSourceOperationMask().isEmpty { - - container.frame = bounds - - if container.superview == nil { - layoutItems(with: items) - addSubview(container) - } - container.layer?.removeAllAnimations() - container.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - - - - return sender.draggingSourceOperationMask() - } - - override public func draggingExited(_ sender: NSDraggingInfo?) { - container.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion:false, completion:{[weak self] (completed) in - if completed { - self?.container.removeFromSuperview() - } - }) - } - - public override func draggingEnded(_ sender: NSDraggingInfo?) { - draggingExited(sender) - } - - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/DrawingContext.swift b/TGUIKit/TGUIKit/DrawingContext.swift deleted file mode 100644 index 200b0546a1..0000000000 --- a/TGUIKit/TGUIKit/DrawingContext.swift +++ /dev/null @@ -1,299 +0,0 @@ -// -// DrawingContext.swift -// TGLibrary -// -// Created by keepcoder on 18/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - -public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false) -> CGImage? { - let scale:CGFloat = 2.0 - let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) - let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) - let length = bytesPerRow * Int(scaledSize.height) - let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) - - guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in - free(bytes) - }) - else { - return nil - } - - let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) - - guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) - else { - return nil - } - - context.scaleBy(x: scale, y: scale) - - contextGenerator(size, context) - - guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) - else { - return nil - } - - return image -} - -public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? = nil, rotatedContext: (CGSize, CGContext) -> Void) -> CGImage? { - let selectedScale = scale ?? System.backingScale - let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale) - let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) - let length = bytesPerRow * Int(scaledSize.height) - let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) - - guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in - free(bytes) - }) - else { - return nil - } - - let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) - - guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { - return nil - } - - context.scaleBy(x: selectedScale, y: selectedScale) - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - - rotatedContext(size, context) - - guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) - else { - return nil - } - - return image -} - -let deviceColorSpace = CGColorSpaceCreateDeviceRGB() - -public enum DrawingContextBltMode { - case Alpha -} - -public class DrawingContext { - public let size: CGSize - public let scale: CGFloat - public let scaledSize: CGSize - public let bytesPerRow: Int - private let bitmapInfo: CGBitmapInfo - public let length: Int - public let bytes: UnsafeMutableRawPointer - let provider: CGDataProvider? - - private var _context: CGContext? - - public func withContext(_ f: (CGContext) -> ()) { - if self._context == nil { - if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { - c.scaleBy(x: scale, y: scale) - self._context = c - } - } - - if let _context = self._context { - f(_context) - } - } - - public func withFlippedContext(horizontal: Bool = false, vertical: Bool = false, _ f: (CGContext) -> ()) { - if self._context == nil { - if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { - c.scaleBy(x: scale, y: scale) - self._context = c - } - } - - if let _context = self._context { - _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) - _context.scaleBy(x: horizontal ? -1.0 : 1.0, y: vertical ? -1.0 : 1.0) - _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) - - f(_context) - - _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) - _context.scaleBy(x: horizontal ? -1.0 : 1.0, y: vertical ? -1.0 : 1.0) - _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) - } - } - - public init(size: CGSize, scale: CGFloat, clear: Bool = false) { - self.size = size - self.scale = scale - self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) - - self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) - self.length = bytesPerRow * Int(scaledSize.height) - - self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) - - self.bytes = malloc(length)! - if clear { - memset(self.bytes, 0, self.length) - } - self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in - free(bytes) - }) - } - - public func generateImage() -> CGImage? { - if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) { - return image - } else { - return nil - } - } - - public func colorAt(_ point: CGPoint) -> NSColor { - let x = Int(point.x * self.scale) - let y = Int(point.y * self.scale) - if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { - let srcLine = self.bytes.advanced(by: y * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) - let pixel = srcLine + x - let colorValue = pixel.pointee - return NSColor(UInt32(colorValue)) - } else { - return NSColor.clear - } - } - - public func blt(_ other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { - if abs(other.scale - self.scale) < CGFloat.ulpOfOne { - let srcX = 0 - var srcY = 0 - let dstX = Int(at.x * self.scale) - var dstY = Int(at.y * self.scale) - - let width = min(Int(self.size.width * self.scale) - dstX, Int(other.size.width * self.scale)) - let height = min(Int(self.size.height * self.scale) - dstY, Int(other.size.height * self.scale)) - - let maxDstX = dstX + width - let maxDstY = dstY + height - - switch mode { - case .Alpha: - while dstY < maxDstY { - let srcLine = other.bytes.advanced(by: max(0, srcY) * other.bytesPerRow).assumingMemoryBound(to: UInt32.self) - let dstLine = self.bytes.advanced(by: max(0, dstY) * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) - - var dx = dstX - var sx = srcX - while dx < maxDstX { - let srcPixel = srcLine + sx - let dstPixel = dstLine + dx - - let baseColor = dstPixel.pointee - let baseAlpha = (baseColor >> 24) & 0xff - let baseR = (baseColor >> 16) & 0xff - let baseG = (baseColor >> 8) & 0xff - let baseB = baseColor & 0xff - - let alpha = min(baseAlpha, srcPixel.pointee >> 24) - - let r = (baseR * alpha) / 255 - let g = (baseG * alpha) / 255 - let b = (baseB * alpha) / 255 - - dstPixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b - - dx += 1 - sx += 1 - } - - dstY += 1 - srcY += 1 - } - } - } - } -} - -public enum ParsingError: Error { - case Generic -} - -public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { - let begin = index - var seenPoint = false - while index <= end { - let c = index.pointee - index = index.successor() - - if c == 46 { // . - if seenPoint { - throw ParsingError.Generic - } else { - seenPoint = true - } - } else if c == separator { - break - } else if c < 48 || c > 57 { - throw ParsingError.Generic - } - } - - if index == begin { - throw ParsingError.Generic - } - - if let value = NSString(bytes: UnsafeRawPointer(begin), length: index - begin, encoding: String.Encoding.utf8.rawValue)?.floatValue { - return CGFloat(value) - } else { - throw ParsingError.Generic - } -} - -public func drawSvgPath(_ context: CGContext, path: StaticString) throws { - var index: UnsafePointer = path.utf8Start - let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount) - while index < end { - let c = index.pointee - index = index.successor() - - if c == 77 { // M - let x = try readCGFloat(&index, end: end, separator: 44) - let y = try readCGFloat(&index, end: end, separator: 32) - - //print("Move to \(x), \(y)") - context.move(to: CGPoint(x: x, y: y)) - } else if c == 76 { // L - let x = try readCGFloat(&index, end: end, separator: 44) - let y = try readCGFloat(&index, end: end, separator: 32) - - //print("Line to \(x), \(y)") - context.addLine(to: CGPoint(x: x, y: y)) - } else if c == 67 { // C - let x1 = try readCGFloat(&index, end: end, separator: 44) - let y1 = try readCGFloat(&index, end: end, separator: 32) - let x2 = try readCGFloat(&index, end: end, separator: 44) - let y2 = try readCGFloat(&index, end: end, separator: 32) - let x = try readCGFloat(&index, end: end, separator: 44) - let y = try readCGFloat(&index, end: end, separator: 32) - context.addCurve(to: CGPoint(x: x1, y: y1), control1: CGPoint(x: x2, y: y2), control2: CGPoint(x: x, y: y)) - - //print("Line to \(x), \(y)") - - } else if c == 90 { // Z - if index != end && index.pointee != 32 { - throw ParsingError.Generic - } - - //CGContextClosePath(context) - context.fillPath() - //CGContextBeginPath(context) - //print("Close") - } - } -} diff --git a/TGUIKit/TGUIKit/Extensions.swift b/TGUIKit/TGUIKit/Extensions.swift deleted file mode 100644 index 81a5022ac0..0000000000 --- a/TGUIKit/TGUIKit/Extensions.swift +++ /dev/null @@ -1,1041 +0,0 @@ -// -// Extensions.swift -// TGUIKit -// -// Created by keepcoder on 08/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Foundation - - -public extension NSAttributedString { - - func CTSize(_ width:CGFloat, framesetter:CTFramesetter?) -> (CTFramesetter,NSSize) { - - var fs = framesetter - - if fs == nil { - fs = CTFramesetterCreateWithAttributedString(self); - } - - var textSize:CGSize = CTFramesetterSuggestFrameSizeWithConstraints(fs!, CFRangeMake(0,self.length), nil, NSMakeSize(width, CGFloat.greatestFiniteMagnitude), nil); - - textSize.width = ceil(textSize.width) - textSize.height = ceil(textSize.height) - - return (fs!,textSize); - - } - - public var range:NSRange { - return NSMakeRange(0, self.length) - } - - public func trimRange(_ range:NSRange) -> NSRange { - let loc:Int = min(range.location,self.length) - let length:Int = min(range.length, self.length - loc) - return NSMakeRange(loc, length) - } - - public static func initialize(string:String?, color:NSColor? = nil, font:NSFont? = nil, coreText:Bool = true) -> NSAttributedString { - let attr:NSMutableAttributedString = NSMutableAttributedString() - _ = attr.append(string: string, color: color, font: font, coreText: true) - - return attr.copy() as! NSAttributedString - } - - -} - -public extension String { - - - public static func prettySized(with size:Int) -> String { - var converted:Double = Double(size) - var factor:Int = 0 - - let tokens:[String] = ["Bytes", "KB", "MB", "GB", "TB"] - - while converted > 1024.0 { - converted /= 1024.0 - factor += 1 - } - - if factor == 0 { - converted = 1.0 - } - factor = Swift.max(1,factor) - - if ceil(converted) - converted != 0.0 { - return String(format: "%.2f %@", converted, tokens[factor]) - } else { - return String(format: "%.0f %@", converted, tokens[factor]) - } - - } - -} - -public extension NSAttributedStringKey { - public static var preformattedCode: NSAttributedStringKey { - return NSAttributedStringKey(rawValue: "TGPreformattedCodeAttributeName") - } - public static var preformattedPre: NSAttributedStringKey { - return NSAttributedStringKey(rawValue: "TGPreformattedPreAttributeName") - } - public static var selectedColor: NSAttributedStringKey { - return NSAttributedStringKey(rawValue: "KSelectedColorAttributeName") - } -} - -public extension NSPasteboard.PasteboardType { - public static var kUrl:NSPasteboard.PasteboardType { - return NSPasteboard.PasteboardType(kUTTypeURL as String) - } - public static var kFilenames:NSPasteboard.PasteboardType { - return NSPasteboard.PasteboardType("NSFilenamesPboardType") - } - public static var kFileUrl: NSPasteboard.PasteboardType { - return NSPasteboard.PasteboardType(kUTTypeFileURL as String) - } -} - -public struct ParsingType: OptionSet { - public var rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - - public init() { - self.rawValue = 0 - } - - public init(_ flags: ParsingType) { - var rawValue: UInt32 = 0 - - if flags.contains(ParsingType.Links) { - rawValue |= ParsingType.Links.rawValue - } - - if flags.contains(ParsingType.Mentions) { - rawValue |= ParsingType.Mentions.rawValue - } - - if flags.contains(ParsingType.Commands) { - rawValue |= ParsingType.Commands.rawValue - } - - if flags.contains(ParsingType.Hashtags) { - rawValue |= ParsingType.Hashtags.rawValue - } - - self.rawValue = rawValue - } - - public static let Links = ParsingType(rawValue: 1) - public static let Mentions = ParsingType(rawValue: 2) - public static let Commands = ParsingType(rawValue: 4) - public static let Hashtags = ParsingType(rawValue: 8) -} - -public extension NSMutableAttributedString { - - public func append(string:String?, color:NSColor? = nil, font:NSFont? = nil, coreText:Bool = true) -> NSRange { - - if(string == nil) { - return NSMakeRange(0, 0) - } - - let slength:Int = self.length - - - var range:NSRange - - self.append(NSAttributedString(string: string!)) - let nlength:Int = self.length - slength - range = NSMakeRange(self.length - nlength, nlength) - - if let c = color { - self.addAttribute(NSAttributedStringKey.foregroundColor, value: c, range:range ) - } - - if let f = font { - if coreText { - self.setCTFont(font: f, range: range) - } - self.setFont(font: f, range: range) - } - - - return range - - } - - public func add(link:Any, for range:NSRange, color: NSColor = presentation.colors.link) { - self.addAttribute(NSAttributedStringKey.link, value: link, range: range) - self.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: range) - } - - public func setCTFont(font:NSFont, range:NSRange) -> Void { - self.addAttribute(NSAttributedStringKey(kCTFontAttributeName as String), value: CTFontCreateWithFontDescriptor(font.fontDescriptor, 0, nil), range: range) - } - - public func setSelected(color:NSColor,range:NSRange) -> Void { - self.addAttribute(.selectedColor, value: color, range: range) - } - - - public func setFont(font:NSFont, range:NSRange) -> Void { - self.addAttribute(NSAttributedStringKey.font, value: font, range: range) - } - -} - - -public extension CALayer { - - public func disableActions() -> Void { - - self.actions = ["onOrderIn":NSNull(),"sublayers":NSNull(),"bounds":NSNull(),"frame":NSNull(),"position":NSNull(),"contents":NSNull(),"backgroundColor":NSNull(),"border":NSNull(), "shadowOffset": NSNull()] - - } - - public func animateBackground() ->Void { - let animation = CABasicAnimation(keyPath: "backgroundColor") - animation.duration = 0.2 - self.add(animation, forKey: "backgroundColor") - } - - - - public func animateBorder() ->Void { - let animation = CABasicAnimation(keyPath: "borderWidth") - animation.duration = 0.2 - self.add(animation, forKey: "borderWidth") - } - - public func animateContents() ->Void { - let animation = CABasicAnimation(keyPath: "contents") - animation.duration = 0.2 - self.add(animation, forKey: "contents") - } - -} - -public extension String { - - public var nsstring:NSString { - return self as NSString - } - - public var length:Int { - return self.nsstring.length - } -} - - -public extension NSView { - - public var snapshot: NSImage { - guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return NSImage() } - cacheDisplay(in: bounds, to: bitmapRep) - let image = NSImage() - image.addRepresentation(bitmapRep) - bitmapRep.size = bounds.size - return NSImage(data: dataWithPDF(inside: bounds))! - } - - public func _mouseInside() -> Bool { - if let window = self.window { - var location:NSPoint = window.mouseLocationOutsideOfEventStream - location = self.convert(location, from: nil) - - if let view = window.contentView!.hitTest(window.mouseLocationOutsideOfEventStream) { - if let view = view as? View { - if view.isEventLess { - return NSPointInRect(location, self.bounds) - } - } - if view == self { - return NSPointInRect(location, self.bounds) - } else { - var s = view.superview - while let sv = s { - if sv == self { - return NSPointInRect(location, self.bounds) - } - s = sv.superview - } - } - } - - } - return false - } - - public var backingScaleFactor: CGFloat { - if let window = window { - return window.backingScaleFactor - } else { - return System.backingScale - } - } - - public func removeAllSubviews() -> Void { - while (self.subviews.count > 0) { - self.subviews[0].removeFromSuperview(); - } - } - - public func isInnerView(_ view:NSView?) -> Bool { - var inner = false - for i in 0 ..< subviews.count { - inner = subviews[i] == view - if !inner && !subviews[i].subviews.isEmpty { - inner = subviews[i].isInnerView(view) - } - if inner { - break - } - } - return inner - } - - public func setFrameSize(_ width:CGFloat, _ height:CGFloat) { - self.setFrameSize(NSMakeSize(width, height)) - } - - public func setFrameOrigin(_ x:CGFloat, _ y:CGFloat) { - self.setFrameOrigin(NSMakePoint(x, y)) - } - - public var background:NSColor { - get { - if let view = self as? View { - return view.backgroundColor - } - if let backgroundColor = layer?.backgroundColor { - return NSColor(cgColor: backgroundColor) ?? .white - } - return .white - } - set { - if let view = self as? View { - view.backgroundColor = newValue - } else { - self.layer?.backgroundColor = newValue.cgColor - } - } - } - - public func centerX(_ superView:NSView? = nil, y:CGFloat? = nil) -> Void { - - var x:CGFloat = 0 - - if let sv = superView { - x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) - } else if let sv = self.superview { - x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) - } - - self.setFrameOrigin(NSMakePoint(x, y == nil ? NSMinY(self.frame) : y!)) - } - - public func focus(_ size:NSSize) -> NSRect { - var x:CGFloat = 0 - var y:CGFloat = 0 - - x = CGFloat(roundf(Float((frame.width - size.width)/2.0))) - y = CGFloat(roundf(Float((frame.height - size.height)/2.0))) - - - return NSMakeRect(x, y, size.width, size.height) - } - - public func focus(_ size:NSSize, inset:NSEdgeInsets) -> NSRect { - let x:CGFloat = CGFloat(roundf(Float((frame.width - size.width + (inset.left + inset.right))/2.0))) - let y:CGFloat = CGFloat(roundf(Float((frame.height - size.height + (inset.top + inset.bottom))/2.0))) - return NSMakeRect(x, y, size.width, size.height) - } - - public func centerY(_ superView:NSView? = nil, x:CGFloat? = nil) -> Void { - - var y:CGFloat = 0 - - if let sv = superView { - y = CGFloat(roundf(Float((sv.frame.height - frame.height)/2.0))) - } else if let sv = self.superview { - y = CGFloat(roundf(Float((sv.frame.height - frame.height)/2.0))) - } - - self.setFrameOrigin(NSMakePoint(x ?? frame.minX, y)) - } - - - public func center(_ superView:NSView? = nil) -> Void { - - var x:CGFloat = 0 - var y:CGFloat = 0 - - if let sv = superView { - x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) - y = CGFloat(roundf(Float((sv.frame.height - frame.height)/2.0))) - } else if let sv = self.superview { - x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) - y = CGFloat(roundf(Float((sv.frame.height - frame.height)/2.0))) - } - - self.setFrameOrigin(NSMakePoint(x, y)) - - } - - - public func _change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) -> Void { - if animated { - - var presentX = NSMinX(self.frame) - var presentY = NSMinY(self.frame) - let presentation:CALayer? = self.layer?.presentation() - if let presentation = presentation, self.layer?.animation(forKey:"position") != nil { - presentY = NSMinY(presentation.frame) - presentX = NSMinX(presentation.frame) - } - - self.layer?.animatePosition(from: NSMakePoint(presentX, presentY), to: position, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) - } else { - self.layer?.removeAnimation(forKey: "position") - } - if save { - self.setFrameOrigin(position) - if let completion = completion, !animated { - completion(true) - } - } - - } - - public func shake() { - let a:CGFloat = 3 - if let layer = layer { - self.layer?.shake(0.04, from:NSMakePoint(-a + layer.position.x,layer.position.y), to:NSMakePoint(a + layer.position.x, layer.position.y)) - } - NSSound.beep() - } - - public func _change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - if animated { - var presentBounds:NSRect = self.layer?.bounds ?? self.bounds - let presentation = self.layer?.presentation() - if let presentation = presentation, self.layer?.animation(forKey:"bounds") != nil { - presentBounds.size.width = NSWidth(presentation.bounds) - presentBounds.size.height = NSHeight(presentation.bounds) - } - - self.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, 0, size.width, size.height), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) - - } else { - self.layer?.removeAnimation(forKey: "bounds") - } - if save { - self.frame = NSMakeRect(NSMinX(self.frame), NSMinY(self.frame), size.width, size.height) - } - } - - public func _changeBounds(from: NSRect, to: NSRect, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - - if save { - self.bounds = to - } - - if animated { - self.layer?.animateBounds(from: from, to: to, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) - - } else { - self.layer?.removeAnimation(forKey: "bounds") - } - - if !animated { - completion?(true) - } - } - - public func _change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - if animated { - if let layer = self.layer { - var opacity:CGFloat = CGFloat(layer.opacity) - if let presentation = self.layer?.presentation(), self.layer?.animation(forKey:"opacity") != nil { - opacity = CGFloat(presentation.opacity) - } - - layer.animateAlpha(from: opacity, to: to, duration:duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) - } - - - } else { - layer?.removeAnimation(forKey: "opacity") - } - if save { - self.layer?.opacity = Float(to) - if let completion = completion, !animated { - completion(true) - } - } - } - - public func disableHierarchyInteraction() -> Void { - for sub in self.subviews { - if let sub = sub as? Control { - sub.interactionStateForRestore = sub.userInteractionEnabled - sub.userInteractionEnabled = false - } - sub.disableHierarchyInteraction() - } - } - - public func restoreHierarchyInteraction() -> Void { - for sub in self.subviews { - if let sub = sub as? Control, let resporeState = sub.interactionStateForRestore { - sub.userInteractionEnabled = resporeState - sub.interactionStateForRestore = nil - } - sub.restoreHierarchyInteraction() - } - } - -} - - - - -public extension NSTableView.AnimationOptions { - public static var none: NSTableView.AnimationOptions { get { - return NSTableView.AnimationOptions(rawValue: 0) - } - } - -} - -public extension CGSize { - public func fitted(_ size: CGSize) -> CGSize { - var fittedSize = self - if fittedSize.width > size.width { - fittedSize = CGSize(width: size.width, height: floor((fittedSize.height * size.width / max(fittedSize.width, 1.0)))) - } - if fittedSize.height > size.height { - fittedSize = CGSize(width: floor((fittedSize.width * size.height / max(fittedSize.height, 1.0))), height: size.height) - } - return fittedSize - } - - func fit(_ maxSize: CGSize) -> CGSize { - var size = self - if self.width < 1.0 { - return CGSize() - } - if self.height < 1.0 { - return CGSize() - } - - if size.width > maxSize.width { - size.height = floor((size.height * maxSize.width / size.width)); - size.width = maxSize.width; - } - if size.height > maxSize.height { - size.width = floor((size.width * maxSize.height / size.height)); - size.height = maxSize.height; - } - return size; - } - - public func fittedToArea(_ area: CGFloat) -> CGSize { - if self.height < 1.0 || self.width < 1.0 { - return CGSize() - } - let aspect = self.width / self.height - let height = sqrt(area / aspect) - let width = aspect * height - return CGSize(width: floor(width), height: floor(height)) - } - - public func aspectFilled(_ size: CGSize) -> CGSize { - let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) - return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) - } - - public func aspectFitted(_ size: CGSize) -> CGSize { - let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) - return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) - } - - public func multipliedByScreenScale() -> CGSize { - let scale:CGFloat = 2.0 - return CGSize(width: self.width * scale, height: self.height * scale) - } - - public func dividedByScreenScale() -> CGSize { - let scale:CGFloat = 2.0 - return CGSize(width: self.width / scale, height: self.height / scale) - } -} - -public extension NSImage { - - func precomposed(_ color:NSColor? = nil, flipVertical:Bool = false, flipHorizontal:Bool = false) -> CGImage { - - let drawContext:DrawingContext = DrawingContext(size: self.size, scale: 2.0, clear: true) - - let image:NSImage = self - - let make:(CGContext) -> Void = { ctx in - let rect = NSMakeRect(0, 0, drawContext.size.width, drawContext.size.height) - //ctx.interpolationQuality = .high - - - var imageRect:CGRect = NSMakeRect(0, 0, image.size.width, image.size.height) - - let cimage = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) - //CGImageSourceCreateImageAtIndex(CGImageSourceCreateWithData(image.tiffRepresentation! as CFData, nil)!, 0, nil) - - if let color = color { - ctx.clip(to: rect, mask: cimage!) - ctx.setFillColor(color.cgColor) - ctx.fill(rect) - } else { - ctx.draw(cimage!, in: imageRect) - } - - } - - drawContext.withFlippedContext(horizontal: flipHorizontal, vertical: flipVertical, make) - - - - - return drawContext.generateImage()! - -// var image:NSImage = self.copy() as! NSImage -// if let color = color { -// image.lockFocus() -// color.set() -// var imageRect = NSMakeRect(0, 0, image.size.width * 2.0, image.size.height * 2.0) -// NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop) -// image.unlockFocus() -// } - - // return roundImage(image.tiffRepresentation!, self.size, cornerRadius: 0, reversed:reversed)! - } - -} - -public extension CGRect { - public var topLeft: CGPoint { - return self.origin - } - - public var topRight: CGPoint { - return CGPoint(x: self.maxX, y: self.minY) - } - - public var bottomLeft: CGPoint { - return CGPoint(x: self.minX, y: self.maxY) - } - - public var bottomRight: CGPoint { - return CGPoint(x: self.maxX, y: self.maxY) - } - - public var center: CGPoint { - return CGPoint(x: self.midX, y: self.midY) - } -} - -public extension CGPoint { - public func offsetBy(dx: CGFloat, dy: CGFloat) -> CGPoint { - return CGPoint(x: self.x + dx, y: self.y + dy) - } -} - -public extension CGImage { - - var backingSize:NSSize { - return NSMakeSize(CGFloat(width) / 2.0, CGFloat(height) / 2.0) - } - - var size:NSSize { - return NSMakeSize(CGFloat(width), CGFloat(height)) - } - - var scale:CGFloat { - return 2.0 - } - -} - -extension Array { - static func fromCFArray(records : CFArray?) -> Array? { - var result: [Element]? - if let records = records { - for i in 0.. Bool { - return NSLocationInRange(index, self) - } -} - -public extension NSBezierPath { - public var cgPath:CGPath? { - if self.elementCount == 0 { - return nil - } - - let path = CGMutablePath() - var didClosePath = false - - for i in 0 ..< self.elementCount { - var points = [NSPoint](repeating: NSZeroPoint, count: 3) - - switch self.element(at: i, associatedPoints: &points) { - case .moveToBezierPathElement: - path.move(to: points[0]) - case .lineToBezierPathElement: - path.addLine(to: points[0]) - didClosePath = false - case .curveToBezierPathElement: - path.addCurve(to: points[0], control1: points[1], control2: points[2]) - didClosePath = false - case .closePathBezierPathElement: - path.closeSubpath() - didClosePath = true; - } - } - - if !didClosePath { - path.closeSubpath() - } - - return path - } -} - - -public extension NSEdgeInsets { - - public init(left:CGFloat = 0, right:CGFloat = 0, top:CGFloat = 0, bottom:CGFloat = 0) { - self.left = left - self.right = right - self.top = top - self.bottom = bottom - } -} - -public extension NSColor { - public convenience init(_ rgbValue:UInt32, _ alpha:CGFloat = 1.0) { - self.init(deviceRed: ((CGFloat)((rgbValue & 0xFF0000) >> 16))/255.0, green: ((CGFloat)((rgbValue & 0xFF00) >> 8))/255.0, blue: ((CGFloat)(rgbValue & 0xFF))/255.0, alpha: alpha) - } -} - -public extension Int { - - func prettyFormatter(_ n: Int, iteration: Int) -> String { - let keys = ["K", "M", "B", "T"] - let d = Double((n / 100)) / 10.0 - let isRound:Bool = (Int(d) * 10) % 10 == 0 - if d < 1000 { - if d == 1 { - return "\(Int(d))\(keys[iteration])" - } else { - return "\((d > 99.9 || isRound || (!isRound && d > 9.99)) ? d * 10 / 10 : d)\(keys[iteration])" - } - } - else { - return self.prettyFormatter(Int(d), iteration: iteration + 1) - } - } - - public var prettyNumber:String { - if self < 1000 { - return "\(self)" - } - return self.prettyFormatter(self, iteration: 0) - } - public var separatedNumber: String { - if self < 1000 { - return "\(self)" - } - let string = "\(self)" - - let length: Int = string.length - var result:String = "" - var index:Int = 0 - while index < length { - let modulo = length % 3 - if index == 0 && modulo != 0 { - result = string.nsstring.substring(with: NSMakeRange(index, modulo)) - index += modulo - } else { - let count:Int = 3 - let value = string.nsstring.substring(with: NSMakeRange(index, count)) - if index == 0 { - result = value - } else { - result += " " + value - } - index += count - } - } - return result - } -} - - -public extension ProgressIndicator { - public func set(color:NSColor) { - let color = color.usingColorSpace(NSColorSpace.sRGB) - - let colorPoly = CIFilter(name: "CIColorPolynomial") - if let colorPoly = colorPoly, let color = color { - colorPoly.setDefaults() - let redVector = CIVector(x: color.redComponent, y: 0, z: 0, w: 0) - let greenVector = CIVector(x: color.greenComponent, y: 0, z: 0, w: 0) - let blueVector = CIVector(x: color.blueComponent, y: 0, z: 0, w: 0) - - colorPoly.setValue(redVector, forKey: "inputRedCoefficients") - colorPoly.setValue(greenVector, forKey: "inputGreenCoefficients") - colorPoly.setValue(blueVector, forKey: "inputBlueCoefficients") - self.contentFilters = [colorPoly] - } - } -} - -public extension String { - public func prefix(_ by:Int) -> String { - if let index = index(startIndex, offsetBy: by, limitedBy: endIndex) { - return String(self[.. String { - if let index = index(startIndex, offsetBy: by, limitedBy: endIndex) { - return String(self[index.. String { - let h = elapsed / 3600 - let m = (elapsed / 60) % 60 - let s = elapsed % 60 - - if h > 0 { - return String.init(format: "%d:%02d:%02d", h, m, s) - } else { - return String.init(format: "%02d:%02d", m, s) - } - } - - -} - -public extension NSTextField { - public func setSelectionRange(_ range: NSRange) { - textView?.setSelectedRange(range) - } - - public var selectedRange: NSRange { - if let textView = textView { - return textView.selectedRange - } - return NSMakeRange(0, 0) - } - - public func setCursorToEnd() { - self.setSelectionRange(NSRange(location: self.stringValue.length, length: 0)) - } - - public func setCursorToStart() { - self.setSelectionRange(NSRange(location: 0, length: 0)) - } - - public var textView:NSTextView? { - return (self.window?.fieldEditor(true, for: self) as? NSTextView) - } -} - - -public extension String { - public var emojiSkinToneModifiers: [String] { - return [ "🏻", "🏼", "🏽", "🏾", "🏿" ] - } - - public var emojiVisibleLength: Int { - var count = 0 - enumerateSubstrings(in: startIndex..(uncheckedBounds: (self.index(after: self.startIndex), self.endIndex)) - return String(self[range]) - } - - public var canHaveSkinToneModifier: Bool { - if self.characters.isEmpty { - return false - } - - let modified = self.emojiUnmodified + self.emojiSkinToneModifiers[0] - return modified.emojiVisibleLength == 1 - } - - public var glyphCount: Int { - - let richText = NSAttributedString(string: self) - let line = CTLineCreateWithAttributedString(richText) - return CTLineGetGlyphCount(line) - } - - public var isSingleEmoji: Bool { - return glyphCount == 1 && containsEmoji - } - - public var containsEmoji: Bool { - - return !unicodeScalars.filter { $0.isEmoji }.isEmpty - } - - public var containsOnlyEmoji: Bool { - - return unicodeScalars.first(where: { !$0.isEmoji && !$0.isZeroWidthJoiner }) == nil - } - - - public var emojiString: String { - - return emojiScalars.map { String($0) }.reduce("", +) - } - - var emojis: [String] { - - var scalars: [[UnicodeScalar]] = [] - var currentScalarSet: [UnicodeScalar] = [] - var previousScalar: UnicodeScalar? - - for scalar in emojiScalars { - - if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner { - - scalars.append(currentScalarSet) - currentScalarSet = [] - } - currentScalarSet.append(scalar) - - previousScalar = scalar - } - - scalars.append(currentScalarSet) - - return scalars.map { $0.map{ String($0) } .reduce("", +) } - } - - fileprivate var emojiScalars: [UnicodeScalar] { - - var chars: [UnicodeScalar] = [] - var previous: UnicodeScalar? - for cur in unicodeScalars { - - if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji { - chars.append(previous) - chars.append(cur) - - } else if cur.isEmoji { - chars.append(cur) - } - - previous = cur - } - - return chars - } - -} - -extension UnicodeScalar { - - var isEmoji: Bool { - - switch value { - case 0x3030, 0x00AE, 0x00A9, - 0x1D000 ... 0x1F77F, - 0x2100 ... 0x27BF, - 0xFE00 ... 0xFE0F, - 0x1F900 ... 0x1F9FF: - return true - - default: return false - } - } - - var isZeroWidthJoiner: Bool { - return value == 8205 - } -} - diff --git a/TGUIKit/TGUIKit/HorizontalRowView.swift b/TGUIKit/TGUIKit/HorizontalRowView.swift deleted file mode 100644 index 530a6545f3..0000000000 --- a/TGUIKit/TGUIKit/HorizontalRowView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// HorizontalRowView.swift -// TGUIKit -// -// Created by keepcoder on 17/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -open class HorizontalRowView: TableRowView { - - private var container:View = View() - - required public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.layer?.delegate = nil - container.layer?.delegate = self - super.addSubview(container) - - container.frame = NSMakeRect(0, 0, frame.height, frame.width) - container.frameCenterRotation = 90 - } - - - - open override func set(item: TableRowItem, animated: Bool) { - super.set(item: item, animated: animated) - container.backgroundColor = backdorColor - } - - open override func draw(_ dirtyRect: NSRect) { - - } - - open override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) -// ctx.translateBy(x: frame.width / 2.0, y: frame.height / 2.0) -// ctx.scaleBy(x: -1.0, y: 1.0) -// ctx.rotate(by: 90 * CGFloat(M_PI) / 180) -// ctx.translateBy(x: -frame.width / 2.0, y: -frame.height / 2.0) - } - - open override func addSubview(_ view: NSView) { - container.addSubview(view) - } - - deinit { - container.removeAllSubviews() - } - - open override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - container.setFrameSize(newSize) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/HorizontalTableView.swift b/TGUIKit/TGUIKit/HorizontalTableView.swift deleted file mode 100644 index e2e9a51241..0000000000 --- a/TGUIKit/TGUIKit/HorizontalTableView.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// HorizontalTableView.swift -// TGUIKit -// -// Created by keepcoder on 17/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public class HorizontalTableView: TableView { - - public required init(frame frameRect: NSRect, isFlipped: Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { - super.init(frame: frameRect, isFlipped: isFlipped, bottomInset: bottomInset, drawBorder: drawBorder) - // [[self.scrollView verticalScroller] setControlSize:NSSmallControlSize]; - //self.verticalScroller?.controlSize = NSControlSize.small - self.rotate(byDegrees: 270) - - self.clipView.border = [] - self.tableView.border = [] - } - - - open override var hasVerticalScroller: Bool { - get { - return false - } - set { - super.hasVerticalScroller = newValue - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func rowView(item:TableRowItem) -> TableRowView { - let identifier:String = NSStringFromClass(item.viewClass()) - var view = self.tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier), owner: self.tableView) - if(view == nil) { - let vz = item.viewClass() as! TableRowView.Type - - view = vz.init(frame:NSMakeRect(0, 0, item.height, frame.height)) - - view?.identifier = NSUserInterfaceItemIdentifier(rawValue: identifier) - - } - - return view as! TableRowView; - } - -} diff --git a/TGUIKit/TGUIKit/Image.swift b/TGUIKit/TGUIKit/Image.swift deleted file mode 100644 index 8f45c78a62..0000000000 --- a/TGUIKit/TGUIKit/Image.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation -import ImageIO -import Accelerate - - - - -public func roundImage(_ data:Data, _ s:NSSize, cornerRadius:CGFloat = -1, reversed:Bool = false, scale:CGFloat = 1.0) -> CGImage? { - let image:CGImageSource? = CGImageSourceCreateWithData(data as CFData, nil) - - let size = NSMakeSize(s.width * scale, s.height * scale) - - let context:CGContext? = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: Int(4*size.width), space: NSColorSpace.genericRGB.cgColorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) - - - if let ctx = context { - if let img = image { - let cimage = CGImageSourceCreateImageAtIndex(img, 0, nil) - if let c = cimage { - - if cornerRadius == -1 { - var startAngle: Float = Float(2 * Double.pi) - var endAngle: Float = 0.0 - let radius:Float = Float(size.width/2.0) - let center = NSMakePoint(size.width/2.0, size.height/2.0) - - startAngle = startAngle - Float(Double.pi / 2) - endAngle = endAngle - Float(Double.pi / 2) - ctx.addArc(center: center, radius: CGFloat(radius), startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: false) - } else if cornerRadius > 0 { - - let minx:CGFloat = 0, midx = size.width/2.0, maxx = size.width - let miny:CGFloat = 0, midy = size.height/2.0, maxy = size.height - - - ctx.move(to: NSMakePoint(minx, midy)) - ctx.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: cornerRadius) - - } - - if cornerRadius > 0 || cornerRadius == -1 { - ctx.closePath() - ctx.clip() - } - - if reversed { - ctx.translateBy(x: size.width/2.0, y: size.height/2.0) - ctx.scaleBy(x: 1.0, y: -1.0) - ctx.translateBy(x: -(size.width/2.0), y: -(size.height/2.0)) - - } - ctx.draw(c, in: NSMakeRect(0, 0, size.width, size.height)) - - - return ctx.makeImage() - - } - - } - } - - return nil -} - - - - diff --git a/TGUIKit/TGUIKit/ImageButton.swift b/TGUIKit/TGUIKit/ImageButton.swift deleted file mode 100644 index 1117c555f6..0000000000 --- a/TGUIKit/TGUIKit/ImageButton.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// ImageButton.swift -// TGUIKit -// -// Created by keepcoder on 26/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -open class ImageButton: Button { - - var imageView:ImageView = ImageView() - - private var images:[ControlState:CGImage] = [:] - private var backgroundImage:[ControlState:CGImage] = [:] - - - public func removeImage(for state:ControlState) { - images.removeValue(forKey: state) - apply(state: self.controlState) - } - - public func set(image:CGImage, for state:ControlState) -> Void { - - images[state] = image - - apply(state: self.controlState) - } - - open override func viewDidChangeBackingProperties() { - super.viewDidChangeBackingProperties() - } - - override func prepare() { - super.prepare() - imageView.animates = true - self.addSubview(imageView) - } - - override public func apply(state: ControlState) { - let state:ControlState = self.isSelected ? .Highlight : state - super.apply(state: state) - - if let image = images[state] { - imageView.image = image - } else if state == .Highlight && autohighlight, let image = images[.Normal] { - imageView.image = style.highlight(image: image) - } else if state == .Hover && highlightHovered, let image = images[.Normal] { - imageView.image = style.highlight(image: image) - } else { - imageView.image = images[.Normal] - } - updateLayout() - } - - public func disableActions() { - animates = false - self.layer?.disableActions() - imageView.animates = false - } - - - override public func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) { - super.sizeToFit(addition) - - if let image = images[.Normal] { - var size = image.backingSize - - if maxSize.width > 0 || maxSize.height > 0 { - size = maxSize - } - - size.width += addition.width - size.height += addition.height - self.setFrameSize(size) - } - } - - public override func updateLayout() { - if let image = images[controlState] { - imageView.setFrameSize(image.backingSize) - } - imageView.center() - } - -} diff --git a/TGUIKit/TGUIKit/ImageView.swift b/TGUIKit/TGUIKit/ImageView.swift deleted file mode 100644 index 797bdeda93..0000000000 --- a/TGUIKit/TGUIKit/ImageView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// ImageView.swift -// TGUIKit -// -// Created by keepcoder on 22/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public class ImageView: NSView { - - public var animates:Bool = false - - public var image:CGImage? { - didSet { - self.layer?.contents = image - if animates { - animate() - } - } - } - - public func sizeToFit() { - if let image = self.image { - setFrameSize(image.backingSize) - } - } - - override public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.wantsLayer = true - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func animate() -> Void { - let animation = CABasicAnimation(keyPath: "contents") - animation.duration = 0.2 - self.layer?.add(animation, forKey: "contents") - } - - override public func viewDidChangeBackingProperties() { - if let window = self.window { - self.layer?.contentsScale = window.backingScaleFactor/2.0; - } - } - - public func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) -> Void { - super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - - public func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - public func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - -} diff --git a/TGUIKit/TGUIKit/LinearProgressControl.swift b/TGUIKit/TGUIKit/LinearProgressControl.swift deleted file mode 100644 index 4c98d1a19b..0000000000 --- a/TGUIKit/TGUIKit/LinearProgressControl.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// LinearProgressControl.swift -// TGUIKit -// -// Created by keepcoder on 28/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public class LinearProgressControl: Control { - - private var progressView:View! - private var containerView:Control! - private var progress:CGFloat = 0 - public var progressHeight:CGFloat - public var onUserChanged:((Float)->Void)? - - public override func mouseDragged(with event: NSEvent) { - super.mouseDragged(with: event) - if let onUserChanged = onUserChanged { - let location = convert(event.locationInWindow, from: nil) - let progress = Float(location.x / frame.width) - onUserChanged(progress) - } - } - - public var interactiveValue:Float { - if let window = window { - let location = convert(window.mouseLocationOutsideOfEventStream, from: nil) - return Float(location.x / frame.width) - } - return 0 - } - - open override func updateTrackingAreas() { - super.updateTrackingAreas(); - - - if let trackingArea = trackingArea { - self.removeTrackingArea(trackingArea) - } - - trackingArea = nil - - if let _ = window { - let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.enabledDuringMouseDrag, NSTrackingArea.Options.activeInKeyWindow,NSTrackingArea.Options.inVisibleRect] - self.trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) - - self.addTrackingArea(self.trackingArea!) - } - } - - deinit { - if let trackingArea = self.trackingArea { - self.removeTrackingArea(trackingArea) - } - } - - public override var style: ControlStyle { - set { - self.progressView.layer?.backgroundColor = newValue.foregroundColor.cgColor - containerView.style = newValue - } - get { - return super.style - } - } - - public func set(progress:CGFloat, animated:Bool = false) { - let progress:CGFloat = progress.isNaN ? 1 : progress - self.progress = progress - let size = NSMakeSize(floorToScreenPixels(frame.width * progress), progressHeight) - progressView.change(size: size, animated: animated) - progressView.setFrameOrigin(NSMakePoint(0, frame.height - progressHeight)) - } - - - - - public init(progressHeight:CGFloat = 4) { - self.progressHeight = progressHeight - super.init() - - initialize() - } - - public override func layout() { - super.layout() - progressView.setFrameSize(progressView.frame.width, progressHeight) - containerView.setFrameOrigin(0, frame.height - containerView.frame.height) - } - - - private func initialize() { - - containerView = Control(frame:NSMakeRect(0, 0, 0, progressHeight)) - containerView.wantsLayer = true - containerView.layer?.backgroundColor = style.foregroundColor.cgColor - addSubview(containerView) - - - progressView = View(frame:NSMakeRect(0, 0, 0, progressHeight)) - progressView.backgroundColor = style.foregroundColor - addSubview(progressView) - } - - required public init(frame frameRect: NSRect) { - self.progressHeight = frameRect.height - super.init(frame:frameRect) - initialize() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/MagnifyView.swift b/TGUIKit/TGUIKit/MagnifyView.swift deleted file mode 100644 index 76fa254320..0000000000 --- a/TGUIKit/TGUIKit/MagnifyView.swift +++ /dev/null @@ -1,227 +0,0 @@ -// -// MagnifyView.swift -// TGUIKit -// -// Created by keepcoder on 15/12/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac -public class MagnifyView : NSView { - - public private(set) var magnify:CGFloat = 1.0 { - didSet { - magnifyUpdater.set(magnify) - } - } - public var maxMagnify:CGFloat = 8.0 - public var minMagnify:CGFloat = 1.0 - public let smartUpdater:Promise = Promise() - public let magnifyUpdater:ValuePromise = ValuePromise(ignoreRepeated: true) - private var mov_start:NSPoint = NSZeroPoint - private var mov_content_start:NSPoint = NSZeroPoint - - - public let contentView:NSView - let containerView:NSView = NSView() - public var contentSize:NSSize = NSZeroSize { - didSet { - contentView.frame = focus(magnifiedSize) - } - } - private var magnifiedSize:NSSize { - return NSMakeSize(floorToScreenPixels(contentSize.width * magnify), floorToScreenPixels(contentSize.height * magnify)) - } - public init(_ contentView:NSView, contentSize:NSSize) { - self.contentView = contentView - contentView.setFrameSize(contentSize) - self.contentSize = contentSize - contentView.wantsLayer = true - super.init(frame: NSZeroRect) - wantsLayer = true - containerView.wantsLayer = true - addSubview(containerView) - containerView.addSubview(contentView) - contentView.background = NSColor.clear - smartUpdater.set(.single(contentSize)) - } - - public func resetMagnify() { - magnify = 1.0 - contentView.setFrameSize(magnifiedSize) - contentView.center() - } - - public func zoomIn() { - add(magnify: 0.5, for: NSMakePoint(containerView.frame.width/2, containerView.frame.height/2), animated: true) - } - - public func zoomOut() { - add(magnify: -0.5, for: NSMakePoint(containerView.frame.width/2, containerView.frame.height/2), animated: true) - } - - public override func layout() { - super.layout() - containerView.setFrameSize(frame.size) - contentView.center() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func magnify(with event: NSEvent) { - super.magnify(with: event) - - add(magnify: event.magnification, for: containerView.convert(event.locationInWindow, from: nil)) - - if event.phase == .ended { - smartUpdater.set(.single(magnifiedSize) |> delay(0.3, queue: Queue.mainQueue())) - } else if event.phase == .began { - smartUpdater.set(smartUpdater.get()) - } - } - - override public func smartMagnify(with event: NSEvent) { - super.smartMagnify(with: event) - addSmart(for: containerView.convert(event.locationInWindow, from: nil)) - smartUpdater.set(.single(magnifiedSize) |> delay(0.2, queue: Queue.mainQueue())) - } - - func addSmart(for location:NSPoint) { - var minFactor:CGFloat = min(max(frame.size.width / magnifiedSize.width,frame.size.height / magnifiedSize.height),2.0) - if magnify > 1.0 { - minFactor = 1 - magnify - } - add(magnify: minFactor, for: location, animated: true) - } - - public func add(magnify:CGFloat, for location:NSPoint, animated:Bool = false) { - self.magnify += magnify - self.magnify = min(max(minMagnify,self.magnify),maxMagnify) - let point = magnifyOrigin( for: location, from:contentView.frame, factor: magnify) - - //contentView.change(pos: point, animated: animated) - // contentView.change(size: magnifiedSize, animated: animated) - let content = animated ? contentView.animator() : contentView - content.frame = NSMakeRect(point.x, point.y, magnifiedSize.width, magnifiedSize.height) - } - - func magnifyOrigin(for location:NSPoint, from past:NSRect, factor:CGFloat) -> NSPoint { - - var point:NSPoint = past.origin - let focused = focus(magnifiedSize).origin - if NSPointInRect(location, contentView.frame) { - if magnifiedSize.width < frame.width || magnifiedSize.height < frame.height { - point = focused - } else { - point.x -= (magnifiedSize.width - past.width) * ((location.x - past.minX) / past.width) - point.y -= (magnifiedSize.height - past.height) * ((location.y - past.minY) / past.height) - - point = adjust(with: point) - - } - } else { - point = focused - } - return point - } - - override public func mouseDown(with theEvent: NSEvent) { - self.mov_start = convert(theEvent.locationInWindow, from: nil) - self.mov_content_start = contentView.frame.origin - } - - override public func mouseUp(with theEvent: NSEvent) { - self.mov_start = NSZeroPoint - self.mov_content_start = NSZeroPoint - super.mouseUp(with: theEvent) - } - - override public func mouseDragged(with theEvent: NSEvent) { - super.mouseDragged(with: theEvent) - if (mov_start.x == 0 || mov_start.y == 0) || (frame.width > magnifiedSize.width && frame.height > magnifiedSize.height) { - return - } - var current = convert(theEvent.locationInWindow, from: nil) - current = NSMakePoint(current.x - mov_start.x, current.y - mov_start.y) - - let adjust = self.adjust(with: NSMakePoint(mov_content_start.x + current.x, mov_content_start.y + current.y)) - - var point = contentView.frame.origin - if magnifiedSize.width > frame.width { - point.x = adjust.x - } - if magnifiedSize.height > frame.height { - point.y = adjust.y - } - - contentView.setFrameOrigin(point) - - - } - - private func adjust(with point:NSPoint) -> NSPoint { - var point = point - point.x = floorToScreenPixels(max(min(0, point.x), point.x + (frame.width - (point.x + magnifiedSize.width)))) - point.y = floorToScreenPixels(max(min(0, point.y), point.y + (frame.height - (point.y + magnifiedSize.height)))) - return point - } - - override public func scrollWheel(with event: NSEvent) { - - if magnify == minMagnify { - super.scrollWheel(with: event) - return - } - - if event.type == .smartMagnify || event.type == .magnify || (event.scrollingDeltaY == 0 && event.scrollingDeltaX == 0) { - return - } - - - let content_f = contentView.frame.origin - if (content_f.x == 0 && event.scrollingDeltaX > 0) || (content_f.x == (frame.width - magnifiedSize.width) && event.scrollingDeltaX < 0) { - // super.scrollWheel(with: event) - return - } - - if (content_f.y == 0 && event.scrollingDeltaY < 0) || (content_f.y == (frame.height - magnifiedSize.height) && event.scrollingDeltaY > 0) { - // super.scrollWheel(with: event) - return - } - - var point = content_f - let adjust = self.adjust(with: NSMakePoint(content_f.x + event.scrollingDeltaX, content_f.y + -event.scrollingDeltaY)) - if event.scrollingDeltaX != 0 && magnifiedSize.width > frame.width { - point.x = adjust.x - } - if event.scrollingDeltaY != 0 && magnifiedSize.height > frame.height { - point.y = adjust.y - } - if point.equalTo(content_f) { - // super.scrollWheel(with: event) - return - } - - contentView.setFrameOrigin(point) - } - - deinit { - var bp:Int = 0 - bp += 1 - } - - - public var mouseInContent:Bool { - if let window = window { - let point = window.mouseLocationOutsideOfEventStream - return NSPointInRect(convert(point, from: nil), contentView.frame) - } - return false - } - - -} - diff --git a/TGUIKit/TGUIKit/MajorNavigationController.swift b/TGUIKit/TGUIKit/MajorNavigationController.swift deleted file mode 100644 index 98611853a1..0000000000 --- a/TGUIKit/TGUIKit/MajorNavigationController.swift +++ /dev/null @@ -1,318 +0,0 @@ -// -// SingleChatNavigationController.swift -// Telegram-Mac -// -// Created by keepcoder on 13/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - - -public protocol MajorControllerListener : class { - func navigationWillShowMajorController(_ controller:ViewController); -} - -open class MajorNavigationController: NavigationViewController, SplitViewDelegate { - - public var alwaysAnimate: Bool = false - private var majorClass:AnyClass - private var defaultEmpty:ViewController - private var listeners:[WeakReference] = [] - - private let container:GenericViewController = GenericViewController() - - override var containerView:View { - get { - return container.genericView - } - set { - super.containerView = newValue - } - } - - - - open override func loadView() { - super.loadView() - - genericView.setProportion(proportion: SplitProportion(min:380, max: .greatestFiniteMagnitude), state: .single) - - controller._frameRect = bounds - controller.viewWillAppear(false) - controller.navigationController = self - - containerView.addSubview(navigationBar) - containerView.frame = bounds - navigationBar.frame = NSMakeRect(0, 0, containerView.frame.width, controller.bar.height) - controller.view.frame = NSMakeRect(0, controller.bar.height , containerView.frame.width, containerView.frame.height - controller.bar.height) - - navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: .none, animationStyle: controller.animationStyle) - - containerView.addSubview(controller.view) - Queue.mainQueue().justDispatch { - self.controller.viewDidAppear(false) - } - - } - - public func closeSidebar() { - genericView.removeProportion(state: .dual) - genericView.setProportion(proportion: SplitProportion(min:380, max: .greatestFiniteMagnitude), state: .single) - genericView.layout() - } - - public init(_ majorClass:AnyClass, _ empty:ViewController) { - self.majorClass = majorClass - self.defaultEmpty = empty - container.bar = .init(height: 0) - assert(majorClass is ViewController.Type) - - super.init(empty) - } - - open override func currentControllerDidChange() { - if let view = view as? DraggingView { - view.controller = controller - } - for listener in listeners { - listener.value?.navigationWillChangeController() - } - } - - open override func viewDidLoad() { - //super.viewDidLoad() - - genericView.delegate = self - genericView.update() - - } - - public func splitViewDidNeedSwapToLayout(state: SplitViewState) { - genericView.removeAllControllers(); - - switch state { - case .dual: - genericView.addController(controller: container, proportion: SplitProportion(min: 800, max: .greatestFiniteMagnitude)) - if let sidebar = sidebar { - genericView.addController(controller: sidebar, proportion: SplitProportion(min:350, max: 350)) - } - case .single: - genericView.addController(controller: container, proportion: SplitProportion(min: 800, max: .greatestFiniteMagnitude)) - default: - break - } - } - - public func splitViewDidNeedMinimisize(controller: ViewController) { - - } - - public func splitViewDidNeedFullsize(controller: ViewController) { - - } - - public func splitViewIsCanMinimisize() -> Bool { - return false; - } - - public func splitViewDrawBorder() -> Bool { - return true - } - - open override func viewClass() ->AnyClass { - return DraggingView.self - } - - public var genericView:SplitView { - return view as! SplitView - } - - override open func push(_ controller: ViewController, _ animated: Bool, style:ViewControllerStyle? = nil) { - - assertOnMainThread() - - controller.navigationController = self - controller.loadViewIfNeeded(self.container.bounds) - - genericView.update() - - - pushDisposable.set((controller.ready.get() |> deliverOnMainQueue |> take(1)).start(next: {[weak self] _ in - if let strongSelf = self { - strongSelf.lock = true - let isMajorController = controller.className == NSStringFromClass(strongSelf.majorClass) - let removeAnimateFlag = strongSelf.stackCount == 2 && isMajorController && !strongSelf.alwaysAnimate - - if isMajorController { - for controller in strongSelf.stack { - controller.didRemovedFromStack() - } - strongSelf.stack.removeAll() - - strongSelf.stack.append(strongSelf.empty) - } - - if let index = strongSelf.stack.index(of: controller) { - strongSelf.stack.remove(at: index) - } - - strongSelf.stack.append(controller) - - let anim = animated && (!isMajorController || strongSelf.controller != strongSelf.defaultEmpty) && !removeAnimateFlag - - let newStyle:ViewControllerStyle - if let style = style { - newStyle = style - } else { - newStyle = anim ? .push : .none - } - - - strongSelf.show(controller, newStyle) - - - } - })) - } - - open override func back(animated:Bool = true) -> Void { - if stackCount > 1 && !isLocked, let last = stack.last, last.invokeNavigationBack() { - let ncontroller = stack[stackCount - 2] - let removeAnimateFlag = (ncontroller == defaultEmpty || !animated) && !alwaysAnimate - last.didRemovedFromStack() - stack.removeLast() - - show(ncontroller, removeAnimateFlag ? .none : .pop) - } - } - - open override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - self.window?.set(handler: { [weak self] in - if let strongSelf = self { - return strongSelf.escapeKeyAction() - } - return .rejected - }, with: self, for: .Escape, priority:.medium) - - self.window?.set(handler: { [weak self] in - if let strongSelf = self { - return strongSelf.returnKeyAction() - } - return .rejected - }, with: self, for: .Return, priority:.medium) - - - self.window?.set(handler: { [weak self] in - if let strongSelf = self { - return strongSelf.backKeyAction() - } - return .rejected - }, with: self, for: .LeftArrow, priority:.medium) - - self.window?.set(handler: { [weak self] in - if let strongSelf = self { - return strongSelf.nextKeyAction() - } - return .rejected - }, with: self, for: .RightArrow, priority:.medium) - - self.window?.add(swipe: { [weak self] direction -> KeyHandlerResult in - if let strongSelf = self, let window = strongSelf.window, !hasPopover(window) && !hasModals() && !strongSelf.isLocked { - switch direction { - case .left: - return strongSelf.backKeyAction() - case .right: - return strongSelf.nextKeyAction() - case .none: - var nextResult = strongSelf.nextKeyAction() - if nextResult != .rejected { - nextResult = strongSelf.backKeyAction() - } - return nextResult - } - } - - return .invokeNext - }, with: self) - - - } - - - - open override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - self.window?.removeAllHandlers(for: self) - } - - open override func backKeyAction() -> KeyHandlerResult { - let status:KeyHandlerResult = stackCount > 1 ? .invoked : .rejected - - let cInvoke = self.controller.backKeyAction() - - if cInvoke == .invokeNext { - return .invokeNext - } else if cInvoke == .invoked { - return .invoked - } - self.back() - return status - } - - open override func nextKeyAction() -> KeyHandlerResult { - return self.controller.nextKeyAction() - } - - - open override func escapeKeyAction() -> KeyHandlerResult { - let status:KeyHandlerResult = stackCount > 1 ? .invoked : .rejected - - let cInvoke = self.controller.escapeKeyAction() - - if cInvoke == .invokeNext { - return .invokeNext - } else if cInvoke == .invoked { - return .invoked - } - self.back() - return status - } - - open override func returnKeyAction() -> KeyHandlerResult { - let status:KeyHandlerResult = .rejected - - let cInvoke = self.controller.returnKeyAction() - - if cInvoke == .invokeNext { - return .invokeNext - } else if cInvoke == .invoked { - return .invoked - } - return status - } - - public func add(listener:WeakReference) -> Void { - let index = listeners.index(where: { (weakView) -> Bool in - return listener.value == weakView.value - }) - if index == nil { - listeners.append(listener) - } - } - - public func remove(listener:WeakReference) -> Void { - - let index = listeners.index(where: { (weakView) -> Bool in - return listener.value == weakView.value - }) - - if let index = index { - listeners.remove(at: index) - } - } - -} diff --git a/TGUIKit/TGUIKit/Modal.swift b/TGUIKit/TGUIKit/Modal.swift deleted file mode 100644 index d6be79c6ca..0000000000 --- a/TGUIKit/TGUIKit/Modal.swift +++ /dev/null @@ -1,430 +0,0 @@ -// -// Modal.swift -// TGUIKit -// -// Created by keepcoder on 26/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - -private class ModalBackground : Control { - fileprivate override func scrollWheel(with event: NSEvent) { - - } -} - -private var activeModals:[WeakReference] = [] - -public class ModalInteractions { - let accept:(()->Void)? - let cancel:(()->Void)? - let acceptTitle:String - let cancelTitle:String? - let drawBorder:Bool - let height:CGFloat - var enables:((Bool)->Void)? = nil - - - var doneUpdatable:(((TitleButton)->Void)->Void)? = nil - var cancelUpdatable:(((TitleButton)->Void)->Void)? = nil - - public init(acceptTitle:String, accept:(()->Void)? = nil, cancelTitle:String? = nil, cancel:(()->Void)? = nil, drawBorder:Bool = false, height:CGFloat = 50) { - self.drawBorder = drawBorder - self.accept = accept - self.cancel = cancel - self.acceptTitle = acceptTitle - self.cancelTitle = cancelTitle - self.height = height - } - - public func updateEnables(_ enable:Bool) -> Void { - if let enables = enables { - enables(enable) - } - } - - public func updateDone(_ f:@escaping (TitleButton) -> Void) -> Void { - doneUpdatable?(f) - } - public func updateCancel(_ f:@escaping(TitleButton) -> Void) -> Void { - cancelUpdatable?(f) - } - -} - -private class ModalInteractionsContainer : View { - let acceptView:TitleButton - let cancelView:TitleButton? - let interactions:ModalInteractions - let borderView:View? - - override func mouseUp(with event: NSEvent) { - - } - override func mouseDown(with event: NSEvent) { - - } - - init(interactions:ModalInteractions, modal:Modal) { - self.interactions = interactions - acceptView = TitleButton() - acceptView.style = ControlStyle(font:.medium(.text), foregroundColor: presentation.colors.blueUI, backgroundColor: presentation.colors.background) - acceptView.set(text: interactions.acceptTitle, for: .Normal) - acceptView.disableActions() - acceptView.sizeToFit() - if let cancelTitle = interactions.cancelTitle { - cancelView = TitleButton() - cancelView?.style = ControlStyle(font:.medium(.text), foregroundColor: presentation.colors.blueUI, backgroundColor: presentation.colors.background) - cancelView?.set(text: cancelTitle, for: .Normal) - cancelView?.sizeToFit() - - } else { - cancelView = nil - } - - if interactions.drawBorder { - borderView = View() - borderView?.backgroundColor = presentation.colors.border - } else { - borderView = nil - } - - - - super.init() - self.backgroundColor = presentation.colors.background - if let cancel = interactions.cancel { - cancelView?.set(handler: { _ in - cancel() - }, for: .Click) - } else { - cancelView?.set(handler: { [weak modal] _ in - modal?.close() - }, for: .Click) - } - - if let accept = interactions.accept { - acceptView.set(handler: { _ in - accept() - }, for: .Click) - } else { - acceptView.set(handler: { [weak modal] _ in - modal?.close() - }, for: .Click) - - } - - addSubview(acceptView) - if let cancelView = cancelView { - addSubview(cancelView) - } - if let borderView = borderView { - addSubview(borderView) - } - - interactions.enables = { [weak self] enable in - self?.acceptView.isEnabled = enable - self?.acceptView.apply(state: .Normal) - } - - interactions.doneUpdatable = { [weak self] f in - if let strongSelf = self { - f(strongSelf.acceptView) - } - self?.updateDone() - } - interactions.cancelUpdatable = { [weak self] f in - if let strongSelf = self, let cancelView = strongSelf.cancelView { - f(cancelView) - } - self?.updateCancel() - } - - - } - - public func updateDone() { - acceptView.sizeToFit() - needsLayout = true - } - - public func updateCancel() { - cancelView?.sizeToFit() - needsLayout = true - } - public func updateThrid(_ text:String) { - acceptView.set(text: text, for: .Normal) - acceptView.sizeToFit() - - needsLayout = true - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - fileprivate override func layout() { - super.layout() - - acceptView.centerY(x:frame.width - acceptView.frame.width - 30) - if let cancelView = cancelView { - cancelView.centerY(x:acceptView.frame.minX - cancelView.frame.width - 30) - } - borderView?.frame = NSMakeRect(0, 0, frame.width, .borderSize) - } - - - -} - -private class ModalContainerView: View { - - deinit { - var bp:Int = 0 - bp += 1 - } - - - - fileprivate override func mouseDown(with event: NSEvent) { - - } - - fileprivate override func mouseUp(with event: NSEvent) { - - } -} - -public class Modal: NSObject { - - private var background:ModalBackground - fileprivate var controller:ModalViewController? - private var container:ModalContainerView! - let window:Window - private let disposable:MetaDisposable = MetaDisposable() - private var interactionsView:ModalInteractionsContainer? - public let interactions:ModalInteractions? - fileprivate let animated: Bool - private let isOverlay: Bool - public init(controller:ModalViewController, for window:Window, animated: Bool = true, isOverlay: Bool) { - - self.controller = controller - self.window = window - self.animated = animated - self.isOverlay = isOverlay - background = ModalBackground() - background.backgroundColor = controller.background - background.layer?.disableActions() - self.interactions = controller.modalInteractions - super.init() - - if let interactions = interactions { - interactionsView = ModalInteractionsContainer(interactions: interactions, modal:self) - interactionsView?.frame = NSMakeRect(0, controller.bounds.height, controller.bounds.width, interactions.height) - } - - if controller.isFullScreen { - controller._frameRect = window.contentView!.bounds - } - - container = ModalContainerView(frame: containerRect) - container.layer?.cornerRadius = .cornerRadius - container.layer?.shouldRasterize = true - container.layer?.rasterizationScale = CGFloat(System.backingScale) - container.backgroundColor = controller.containerBackground - - container.addSubview(controller.view) - - if let interactionsView = interactionsView { - container.addSubview(interactionsView) - } - - background.addSubview(container) - - background.userInteractionEnabled = controller.handleEvents - - if controller.handleEvents { - window.set(responder: { [weak controller] () -> NSResponder? in - return controller?.firstResponder() - }, with: self, priority: .modal) - - if controller.handleAllEvents { - window.set(handler: { () -> KeyHandlerResult in - return .invokeNext - }, with: self, for: .All, priority: .modal) - } - - - window.set(escape: {[weak self] () -> KeyHandlerResult in - if self?.controller?.escapeKeyAction() == .rejected { - self?.close() - } - return .invoked - }, with: self, priority: .modal) - - window.set(handler: { [weak self] () -> KeyHandlerResult in - if let controller = self?.controller { - return controller.returnKeyAction() - } - return .invokeNext - }, with: self, for: .Return, priority: .modal) - } - - - - background.set(handler: { [weak self] _ in - if let closable = self?.controller?.closable, closable { - self?.close() - } - }, for: .Click) - - if controller.dynamicSize { - background.customHandler.size = { [weak self] (size) in - self?.controller?.measure(size: size) - } - } - activeModals.append(WeakReference(value: self)) - } - - - public func resize(with size:NSSize, animated:Bool = true) { - - let focus:NSRect - if let interactions = controller?.modalInteractions { - focus = background.focus(NSMakeSize(size.width, size.height + interactions.height)) - interactionsView?.change(pos: NSMakePoint(0, size.height), animated: animated) - } else { - focus = background.focus(size) - } - container.change(size: focus.size, animated: animated) - container.change(pos: focus.origin, animated: animated) - - controller?.view._change(size: size, animated: animated) - } - - private var containerRect:NSRect { - if let controller = controller { - var containerRect = controller.bounds - if let interactions = controller.modalInteractions { - containerRect.size.height += interactions.height - } - return containerRect - } - return NSZeroRect - } - - public func close(_ callAcceptInteraction:Bool = false) ->Void { - window.removeAllHandlers(for: self) - controller?.viewWillDisappear(true) - - if callAcceptInteraction, let interactionsView = interactionsView { - interactionsView.interactions.accept?() - } - - background.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: {[weak self] (complete) in - if let stongSelf = self { - stongSelf.background.removeFromSuperview() - stongSelf.controller?.viewDidDisappear(true) - stongSelf.controller?.modal = nil - stongSelf.controller = nil - } - }) - - } - - deinit { - disposable.dispose() - for i in stride(from: activeModals.count - 1, to: -1, by: -1) { - if activeModals[i].value == self { - activeModals.remove(at: i) - break - } - } - - } - - func show() -> Void { - // if let view - if let controller = controller { - disposable.set((controller.ready.get() |> take(1)).start(next: { [weak self, weak controller] ready in - if let strongSelf = self, let view = (strongSelf.isOverlay ? strongSelf.window.contentView?.superview : strongSelf.window.contentView), let controller = controller { - strongSelf.controller?.viewWillAppear(true) - strongSelf.background.frame = view.bounds - strongSelf.container.center() - strongSelf.background.background = controller.isFullScreen ? controller.containerBackground : controller.background - if strongSelf.animated { - if !controller.isFullScreen { - strongSelf.container.layer?.animateScaleSpring(from: 0.1, to: 1.0, duration: 0.3) - } else { - strongSelf.container.layer?.animateAlpha(from: 0.1, to: 1.0, duration: 0.3) - } - } - - strongSelf.background.autoresizingMask = [.width,.height] - strongSelf.background.customHandler.layout = { [weak strongSelf] view in - strongSelf?.container.center() - } - - if controller.isFullScreen { - strongSelf.background.customHandler.size = { [weak strongSelf] size in - strongSelf?.container.setFrameSize(size) - } - } - - view.addSubview(strongSelf.background) - if let value = strongSelf.controller?.becomeFirstResponder(), value { - strongSelf.window.makeFirstResponder(strongSelf.controller?.firstResponder()) - } - - if strongSelf.animated { - strongSelf.background.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, completion:{[weak strongSelf] (completed) in - strongSelf?.controller?.viewDidAppear(true) - }) - } else { - strongSelf.controller?.viewDidAppear(false) - } - } - })) - } - - } - -} - -public func hasModals() -> Bool { - - for i in stride(from: activeModals.count - 1, to: -1, by: -1) { - if activeModals[i].value == nil { - activeModals.remove(at: i) - } - } - - return !activeModals.isEmpty -} - -public func closeAllModals() { - for modal in activeModals { - modal.value?.close() - } -} - -public func showModal(with controller:ModalViewController, for window:Window, isOverlay: Bool = false) -> Void { - assert(controller.modal == nil) - for weakModal in activeModals { - if weakModal.value?.controller?.className == controller.className { - weakModal.value?.close() - } - } - - controller.modal = Modal(controller: controller, for: window, isOverlay: isOverlay) - controller.modal?.show() -} - - diff --git a/TGUIKit/TGUIKit/NavigationBarView.swift b/TGUIKit/TGUIKit/NavigationBarView.swift deleted file mode 100644 index ff57ec0683..0000000000 --- a/TGUIKit/TGUIKit/NavigationBarView.swift +++ /dev/null @@ -1,227 +0,0 @@ -// -// NavigationBarView.swift -// TGUIKit -// -// Created by keepcoder on 15/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public struct NavigationBarStyle { - public let height:CGFloat - public let enableBorder:Bool - public init(height:CGFloat, enableBorder:Bool = true) { - self.height = height - self.enableBorder = enableBorder - } -} - -class NavigationBarView: View { - - private var bottomBorder:View = View() - - private var leftView:View = View() - private var centerView:View = View() - private var rightView:View = View() - - override init() { - super.init() - self.autoresizingMask = [.width] - updateLocalizationAndTheme() - } - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.autoresizingMask = [.width] - updateLocalizationAndTheme() - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - bottomBorder.backgroundColor = presentation.colors.border - backgroundColor = presentation.colors.background - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override func draw(_ layer: CALayer, in ctx: CGContext) { - - super.draw(layer, in: ctx) -// ctx.setFillColor(NSColor.white.cgColor) -// ctx.fill(self.bounds) -// -// ctx.setFillColor(theme.colors.border.cgColor) -// ctx.fill(NSMakeRect(0, NSHeight(self.frame) - .borderSize, NSWidth(self.frame), .borderSize)) - } - - override func layout() { - super.layout() - self.bottomBorder.setNeedsDisplay() - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - self.bottomBorder.frame = NSMakeRect(0, newSize.height - .borderSize, newSize.width, .borderSize) - self.layout(left: leftView, center: centerView, right: rightView) - } - - - func layout(left: View, center: View, right: View) -> Void { - if frame.height > 0 { - left.frame = NSMakeRect(0, 0, NSWidth(left.frame), frame.height - .borderSize); - center.frame = NSMakeRect(left.frame.maxX, 0, frame.width - (left.frame.width + right.frame.width), frame.height - .borderSize); - right.frame = NSMakeRect(center.frame.maxX, 0, NSWidth(right.frame), frame.height - .borderSize); - } - } - - // ! PUSH ! - // left from center - // right cross fade - // center from right - - // ! POP ! - // old left -> new center - // old center -> right - // old right -> fade - - - - public func switchViews(left:BarView, center:BarView, right:BarView, controller:ViewController, style:ViewControllerStyle, animationStyle:AnimationStyle) { - - layout(left: left, center: center, right: right) - self.bottomBorder.isHidden = !controller.bar.enableBorder - if style != .none { - - CATransaction.begin() - - self.addSubview(left) - self.addSubview(center) - self.addSubview(right) - self.addSubview(bottomBorder) - - left.setNeedsDisplay() - center.setNeedsDisplay() - right.setNeedsDisplay() - - let pLeft = self.leftView - let pCenter = self.centerView - let pRight = self.rightView - - self.leftView = left - self.centerView = center - self.rightView = right - - var pLeft_from:CGFloat = 0,pRight_from:CGFloat = 0, pCenter_from:CGFloat = 0, pLeft_to:CGFloat = 0, pRight_to:CGFloat = 0, pCenter_to:CGFloat = 0 - var nLeft_from:CGFloat = 0, nRight_from:CGFloat = 0, nCenter_from:CGFloat = 0, nLeft_to:CGFloat = 0, nRight_to:CGFloat = 0, nCenter_to:CGFloat = 0 - - switch style { - case .push: - - //left - pLeft_from = 0 - pLeft_to = 0 - nLeft_from = round(NSWidth(self.frame) - NSWidth(left.frame))/2.0 - nLeft_to = 0 - - //center - pCenter_from = NSMinX(pCenter.frame) - pCenter_to = 0 - nCenter_from = NSMinX(right.frame) - nCenter_to = NSMaxX(left.frame) - - //right - pRight_from = NSMinX(right.frame) - pRight_to = NSMinX(right.frame) - nRight_from = NSMinX(right.frame) - nRight_to = NSMinX(right.frame) - - break - case .pop: - - //left - pLeft_from = 0 - pLeft_to = 0 - nLeft_from = 0 - nLeft_to = 0 - - //center - pCenter_from = NSMinX(center.frame) - pCenter_to = NSMinX(right.frame) - nCenter_from = 0 - nCenter_to = NSMaxX(left.frame) - - //right - pRight_from = NSMinX(right.frame) - pRight_to = NSMinX(right.frame) - nRight_from = NSMinX(right.frame) - nRight_to = NSMinX(right.frame) - - - break - case .none: - break - } - - - // old - pLeft.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion: false, completion:{ [weak pLeft] (completed) in - if completed { - pLeft?.removeFromSuperview() - } - }) - pLeft.layer?.animate(from: pLeft_from as NSNumber, to: pLeft_to as NSNumber, keyPath: "position.x", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration) - - pCenter.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration, removeOnCompletion: false, completion:{ [weak pCenter] (completed) in - if completed { - pCenter?.removeFromSuperview() - } - }) - pCenter.layer?.animate(from: pCenter_from as NSNumber, to: pCenter_to as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) - - pRight.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration, removeOnCompletion: false, completion:{ [weak pRight] (completed) in - if completed { - pRight?.removeFromSuperview() - } - }) - pRight.layer?.animate(from: pRight_from as NSNumber, to: pRight_to as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) - - // new - if !left.isHidden { - left.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration) - } - left.layer?.animate(from: nLeft_from as NSNumber, to: nLeft_to as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) - if !center.isHidden { - center.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration) - } - center.layer?.animate(from: nCenter_from as NSNumber, to: nCenter_to as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) - - if !right.isHidden { - right.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: kCAMediaTimingFunctionSpring, duration: animationStyle.duration) - } - right.layer?.animate(from: nRight_from as NSNumber, to: nRight_to as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) - - - - CATransaction.commit() - - } else { - self.removeAllSubviews() - self.addSubview(left) - self.addSubview(center) - self.addSubview(right) - - self.leftView = left - self.centerView = center - self.rightView = right - - self.addSubview(bottomBorder) - } - - - - } - -} diff --git a/TGUIKit/TGUIKit/NavigationViewController.swift b/TGUIKit/TGUIKit/NavigationViewController.swift deleted file mode 100644 index dbcf2aea41..0000000000 --- a/TGUIKit/TGUIKit/NavigationViewController.swift +++ /dev/null @@ -1,594 +0,0 @@ -// -// NavigationViewController.swift -// TGUIKit -// -// Created by keepcoder on 15/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac -public enum ViewControllerStyle { - case push; - case pop; - case none; -} - -open class NavigationHeaderView : View { - public private(set) weak var header:NavigationHeader? - public let ready:Promise = Promise() - public init(_ header:NavigationHeader) { - self.header = header - super.init() - self.autoresizingMask = [.width] - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} - -open class NavigationHeader { - fileprivate var callHeader:NavigationHeader? - let height:CGFloat - let initializer:(NavigationHeader)->NavigationHeaderView - weak var navigation:NavigationViewController? - fileprivate var _view:NavigationHeaderView? - fileprivate let disposable:MetaDisposable = MetaDisposable() - fileprivate var isShown:Bool = false - public var needShown:Bool = false - public init(_ height:CGFloat, initializer:@escaping(NavigationHeader)->NavigationHeaderView) { - self.height = height - self.initializer = initializer - } - - public var view:NavigationHeaderView { - if _view == nil { - _view = initializer(self) - } - return _view! - } - - deinit { - disposable.dispose() - } - - open func show(_ animated:Bool) { - assert(navigation != nil) - needShown = true - if isShown { - return - } - isShown = true - if let navigation = navigation { - let view = self.view - let height = self.height - view.frame = NSMakeRect(0, 0, navigation.containerView.frame.width, height) - - disposable.set((view.ready.get() |> filter {$0} |> take(1)).start(next: { [weak navigation, weak self, weak view] (ready) in - if let navigation = navigation, let view = view { - let contentInset = navigation.controller.bar.height + height - navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) - - var inset:CGFloat = navigation.controller.bar.height - - if let callHeader = self?.callHeader, callHeader.needShown { - inset += callHeader.height - } - - view.change(pos: NSMakePoint(0, inset), animated: animated, completion: { [weak navigation] completed in - if let navigation = navigation, completed { - navigation.controller.view.frame = NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset) - navigation.controller.view.needsLayout = true - } - }) - - } - })) - } - - } - - open func hide(_ animated:Bool) { - assert(navigation != nil) - if !isShown { - return - } - needShown = false - isShown = false - - if let navigation = navigation { - if animated { - view.change(pos: NSMakePoint(0, 0), animated: animated, removeOnCompletion: false, completion: { [weak self] completed in - self?._view?.removeFromSuperview() - self?._view = nil - }) - } else { - view.removeFromSuperview() - _view = nil - } - - var inset:CGFloat = navigation.controller.bar.height - - if let callHeader = callHeader, callHeader.needShown { - inset += callHeader.height - } - navigation.controller.view.setFrameSize(NSMakeSize(navigation.controller.frame.width, navigation.frame.height - inset)) - navigation.controller.view.setFrameOrigin(NSMakePoint(0, inset)) - } - - } -} - -public class CallNavigationHeader : NavigationHeader { - fileprivate weak var simpleHeader:NavigationHeader? - public override func show(_ animated:Bool) { - assert(navigation != nil) - needShown = true - if isShown { - return - } - isShown = true - if let navigation = navigation { - let view = self.view - let height = self.height - view.frame = NSMakeRect(0, 0, navigation.containerView.frame.width, height) - - disposable.set((view.ready.get() |> take(1)).start(next: { [weak navigation, weak view] (ready) in - if let navigation = navigation, let view = view { - let contentInset = navigation.controller.bar.height + height - navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) - - navigation.navigationBar.change(pos: NSMakePoint(0, height), animated: animated) - - self.simpleHeader?.view.change(pos: NSMakePoint(0, height + navigation.controller.bar.height), animated: animated) - - view.change(pos: NSMakePoint(0, 0), animated: animated, completion: { [weak navigation] completed in - if let navigation = navigation, completed { - navigation.controller.view.frame = NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset) - navigation.controller.view.needsLayout = true - } - }) - - } - })) - } - - } - - public override func hide(_ animated:Bool) { - assert(navigation != nil) - if !isShown { - return - } - needShown = false - isShown = false - - if let navigation = navigation { - if animated { - view.change(pos: NSMakePoint(0, -height), animated: animated, removeOnCompletion: false, completion: { [weak self] completed in - self?._view?.removeFromSuperview() - self?._view = nil - }) - } else { - view.removeFromSuperview() - _view = nil - } - - if let header = simpleHeader, header.needShown { - header.view.change(pos: NSMakePoint(0, navigation.controller.bar.height), animated: animated) - } - - navigation.navigationBar.change(pos: NSZeroPoint, animated: animated) - navigation.controller.view.frame = NSMakeRect(0, navigation.controller.bar.height, navigation.controller.frame.width, navigation.frame.height - navigation.controller.bar.height) - navigation.controller.view.needsLayout = true - } - - } -} - -open class NavigationViewController: ViewController, CALayerDelegate,CAAnimationDelegate { - - public private(set) var modalAction:NavigationModalAction? - - var stack:[ViewController] = [ViewController]() - var lock:Bool = false - - public var empty:ViewController { - didSet { - empty.navigationController = self - - let prev = self.stack.last - self.stack.remove(at: 0) - self.stack.insert(empty, at: 0) - - - var controllerInset:CGFloat = 0 - - if let header = header, header.needShown { - controllerInset += header.height - } - if let header = callHeader, header.needShown { - controllerInset += header.height - } - - empty.loadViewIfNeeded(NSMakeRect(0, controllerInset, self.bounds.width, self.bounds.height - controllerInset)) - - if prev == oldValue { - controller = empty - oldValue.removeFromSuperview() - containerView.addSubview(empty.view) - - if let header = header, header.needShown { - header.view.removeFromSuperview() - containerView.addSubview(header.view) - } - if let header = callHeader, header.needShown { - header.view.removeFromSuperview() - containerView.addSubview(header.view) - } - - } - - empty.view.frame = NSMakeRect(0, controllerInset, self.bounds.width, self.bounds.height - controllerInset - bar.height) - - - } - } - - open var isLocked:Bool { - return lock - } - - public private(set) var controller:ViewController { - didSet { - currentControllerDidChange() - } - } - - func _setController(_ controller:ViewController) { - self.controller = controller - } - - var navigationBar:NavigationBarView = NavigationBarView() - - var pushDisposable:MetaDisposable = MetaDisposable() - var popDisposable:MetaDisposable = MetaDisposable() - - private(set) public var header:NavigationHeader? - private(set) public var callHeader:CallNavigationHeader? - - var containerView:View = View() - - - public func set(header:NavigationHeader?) { - self.header?.hide(false) - header?.navigation = self - header?.callHeader = callHeader - callHeader?.simpleHeader = header - self.header = header - } - - public func set(callHeader:CallNavigationHeader?) { - self.callHeader?.hide(false) - callHeader?.navigation = self - header?.callHeader = callHeader - callHeader?.simpleHeader = header - self.callHeader = callHeader - } - - open override func loadView() { - super.loadView(); - viewDidLoad() - } - - open override func viewDidLoad() { - super.viewDidLoad() - - containerView.frame = bounds - self.view.autoresizesSubviews = true - containerView.autoresizingMask = [.width, .height] - self.view.addSubview(containerView, positioned: .below, relativeTo: self.view.subviews.first) - controller._frameRect = bounds - controller.viewWillAppear(false) - controller.navigationController = self - - containerView.addSubview(navigationBar) - - navigationBar.frame = NSMakeRect(0, 0, NSWidth(containerView.frame), controller.bar.height) - controller.view.frame = NSMakeRect(0, controller.bar.height , NSWidth(containerView.frame), NSHeight(containerView.frame) - controller.bar.height) - - navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: .none, animationStyle: controller.animationStyle) - - containerView.addSubview(controller.view) - Queue.mainQueue().justDispatch { - self.controller.viewDidAppear(false) - } - - } - - - - - open override var canBecomeResponder: Bool { - return false - } - open func currentControllerDidChange() { - - } - - public override var backgroundColor: NSColor { - set { - self.view.background = newValue - containerView.backgroundColor = newValue - navigationBar.backgroundColor = newValue - } - get { - return self.view.background - } - } - - public init(_ empty:ViewController) { - self.empty = empty - self.controller = empty - self.stack.append(controller) - - super.init() - bar = .init(height: 0) - } - - public var stackCount:Int { - return stack.count - } - - deinit { - self.popDisposable.dispose() - self.pushDisposable.dispose() - } - - public func stackInsert(_ controller:ViewController, at: Int) -> Void { - stack.insert(controller, at: at) - } - - open func push(_ controller:ViewController, _ animated:Bool = true, style: ViewControllerStyle? = nil) -> Void { - -// if isLocked { -// return -// } - - - controller.navigationController = self - controller.loadViewIfNeeded(self.bounds) - self.pushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in - if let strongSelf = self { - strongSelf.lock = true - controller.navigationController = strongSelf - - if let index = strongSelf.stack.index(of: controller) { - strongSelf.stack.remove(at: index) - } - - strongSelf.stack.append(controller) - - let newStyle:ViewControllerStyle - if let style = style { - newStyle = style - } else { - newStyle = animated && strongSelf.stack.count > 1 ? .push : .none - } - - strongSelf.show(controller, newStyle) - } - })) - } - - - func show(_ controller:ViewController,_ style:ViewControllerStyle) -> Void { - - let previous:ViewController = self.controller; - self.controller = controller - controller.navigationController = self - - - if(previous === controller) { - previous.viewWillDisappear(false) - previous.viewDidDisappear(false) - - controller.viewWillAppear(false) - controller.viewDidAppear(false) - _ = controller.becomeFirstResponder() - - return; - } - - var contentInset = controller.bar.height - - var barInset:CGFloat = 0 - if let header = callHeader, header.needShown { - header.view.frame = NSMakeRect(0, 0, containerView.frame.width, header.height) - contentInset += header.height - barInset += header.height - } - - self.navigationBar.frame = NSMakeRect(0, barInset, NSWidth(containerView.frame), controller.bar.height) - - - if let header = header, header.needShown { - header.view.frame = NSMakeRect(0, contentInset, containerView.frame.width, header.height) - containerView.addSubview(header.view, positioned: .below, relativeTo: self.navigationBar) - contentInset += header.height - } - - controller.view.removeFromSuperview() - controller.view.frame = NSMakeRect(0, contentInset , NSWidth(containerView.frame), NSHeight(containerView.frame) - contentInset) - if #available(OSX 10.12, *) { - - } else { - controller.view.needsLayout = true - } - - - let reloadHeaders = { [weak self] in - if let header = self?.header, header.needShown { - header.view.removeFromSuperview() - self?.containerView.addSubview(header.view, positioned: .above, relativeTo: controller.view) - } - - if let header = self?.callHeader, header.needShown { - header.view.removeFromSuperview() - self?.containerView.addSubview(header.view, positioned: .below, relativeTo: self?.navigationBar) - } - } - - var pfrom:CGFloat = 0, pto:CGFloat = 0, nto:CGFloat = 0, nfrom:CGFloat = 0; - - switch style { - case .push: - nfrom = NSWidth(containerView.frame) - nto = 0 - pfrom = 0 - pto = -100//round(NSWidth(self.frame)/3.0) - containerView.addSubview(controller.view, positioned: .above, relativeTo: previous.view) - case .pop: - nfrom = -round(NSWidth(containerView.frame)/3.0) - nto = 0 - pfrom = 0 - pto = NSWidth(containerView.frame) - previous.view.setFrameOrigin(NSMakePoint(pto, previous.frame.minY)) - containerView.addSubview(controller.view, positioned: .below, relativeTo: previous.view) - case .none: - previous.viewWillDisappear(false); - previous.view.removeFromSuperview() - containerView.addSubview(controller.view) - controller.viewWillAppear(false); - previous.viewDidDisappear(false); - controller.viewDidAppear(false); - _ = controller.becomeFirstResponder(); - - self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: style, animationStyle: controller.animationStyle) - lock = false - - navigationBar.removeFromSuperview() - containerView.addSubview(navigationBar) - - reloadHeaders() - - return // without animations - } - - - - if previous.removeAfterDisapper, let index = stack.index(of: previous) { - self.stack.remove(at: index) - } - - navigationBar.removeFromSuperview() - containerView.addSubview(navigationBar) - - reloadHeaders() - - previous.viewWillDisappear(true); - controller.viewWillAppear(true); - - - CATransaction.begin() - - - self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: style, animationStyle: controller.animationStyle) - - previous.view.layer?.animate(from: pfrom as NSNumber, to: pto as NSNumber, keyPath: "position.x", timingFunction: kCAMediaTimingFunctionSpring, duration: previous.animationStyle.duration, removeOnCompletion: true, additive: false, completion: { [weak self] completed in - if completed { - previous.view.removeFromSuperview() - previous.viewDidDisappear(true); - } - - self?.lock = false - }); - - - controller.view.layer?.animate(from: nfrom as NSNumber, to: nto as NSNumber, keyPath: "position.x", timingFunction: kCAMediaTimingFunctionSpring, duration: controller.animationStyle.duration, removeOnCompletion: true, additive: false, completion: { completed in - if completed { - controller.viewDidAppear(true); - _ = controller.becomeFirstResponder() - } - - }); - - - CATransaction.commit() - - } - - open override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - navigationBar.updateLocalizationAndTheme() - } - - open func back(animated:Bool = true) -> Void { - if stackCount > 1 && !isLocked, let last = stack.last, last.invokeNavigationBack() { - let controller = stack[stackCount - 2] - last.didRemovedFromStack() - stack.removeLast() - show(controller, animated ? .pop : .none) - } - } - - public func to( index:Int? = nil) -> Void { - if stackCount > 1, let index = index { - if index < 0 { - gotoEmpty(false) - } else { - let controller = stack[index] - stack.removeSubrange(min(max(1, index + 1), stackCount) ..< stackCount) - show(controller, .none) - } - } - } - - public func gotoEmpty(_ animated:Bool = true) -> Void { - if controller != empty { - stack.removeSubrange(1 ..< stackCount - 1) - show(empty, animated ? .pop : .none) - } - } - - public func close(animated:Bool = true) ->Void { - if stackCount > 1 && !isLocked { - let controller = stack[0] - stack.last?.didRemovedFromStack() - stack.removeLast() - show(controller, animated ? .pop : .none) - } - } - - public func set(modalAction:NavigationModalAction, _ showView:Bool = true) { - self.modalAction?.view?.removeFromSuperview() - self.modalAction = modalAction - modalAction.navigation = self - if showView { - let actionView = NavigationModalView(action: modalAction, viewController: self) - modalAction.view = actionView - actionView.frame = bounds - view.addSubview(actionView) - } - } - - public func removeModalAction() { - self.modalAction?.view?.removeFromSuperview() - self.modalAction = nil - } - - public func enumerateControllers(_ f:(ViewController, Int)->Bool) { - for i in stride(from: stack.count - 1, to: -1, by: -1) { - if f(stack[i], i) { - break - } - } - } - -} diff --git a/TGUIKit/TGUIKit/Popover.swift b/TGUIKit/TGUIKit/Popover.swift deleted file mode 100644 index 554fc97fc4..0000000000 --- a/TGUIKit/TGUIKit/Popover.swift +++ /dev/null @@ -1,360 +0,0 @@ -// -// Popover.swift -// TGUIKit -// -// Created by keepcoder on 26/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -class PopoverBackground: Control { - fileprivate weak var popover:Popover? -} - -open class Popover: NSObject { - - private weak var window:Window? - - private var disposable:MetaDisposable = MetaDisposable() - - public var animates:Bool = true - - public var controller:ViewController? - - private weak var control:Control? - - public var isShown:Bool = false - - public var overlay:OverlayControl! - private var background:PopoverBackground = PopoverBackground(frame: NSZeroRect) - - public var animationStyle:AnimationStyle = AnimationStyle(duration:0.2, function:kCAMediaTimingFunctionSpring) - - var readyDisposable:MetaDisposable = MetaDisposable() - - required public init(controller:ViewController) { - self.controller = controller - self.background.layer?.shadowOpacity = 0.4 - self.background.layer?.rasterizationScale = CGFloat(System.backingScale) - self.background.layer?.shouldRasterize = true - self.background.layer?.isOpaque = false - self.background.layer?.shadowOffset = NSMakeSize(0, 0) - self.background.layer?.cornerRadius = 4 - super.init() - - background.popover = self - } - - - - open func show(for control:Control, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, contentRect:NSRect = NSMakeRect(7, 7, 0, 0), delayBeforeShown: Double = 0.2) -> Void { - - if let controller = controller, let parentView = control.window?.contentView { - - controller.loadViewIfNeeded() - controller.viewWillAppear(animates) - - self.window = control.kitWindow - - var signal = controller.ready.get() |> filter {$0} |> take(1) - if control.controlState == .Hover && delayBeforeShown > 0.0 { - signal = signal |> delay(delayBeforeShown, queue: Queue.mainQueue()) - } - self.readyDisposable.set(signal.start(next: {[weak self, weak controller, weak parentView] (ready) in - - if let parentView = parentView { - for subview in parentView.subviews { - if let view = subview as? PopoverBackground { - view.popover?.hide(false) - } - } - } - - if let strongSelf = self, let controller = controller, let parentView = parentView, (strongSelf.inside() || (control.controlState == .Hover || control.controlState == .Highlight) || !control.userInteractionEnabled), control.window != nil, control.visibleRect != NSZeroRect { - - control.isSelected = true - - strongSelf.window?.set(escape: { [weak strongSelf] () -> KeyHandlerResult in - strongSelf?.hide() - return .invoked - }, with: strongSelf, priority: .modal) - - strongSelf.window?.set(handler: { () -> KeyHandlerResult in - return .invokeNext - }, with: strongSelf, for: .All) - - strongSelf.window?.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - if let strongSelf = self, !strongSelf.inside() { - strongSelf.hide() - } - return .invokeNext - }, with: strongSelf, for: .leftMouseUp, priority: .high) - - - strongSelf.window?.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - if let strongSelf = self, !strongSelf.inside() && !control.mouseInside() { - strongSelf.hide() - } - return .invokeNext - }, with: strongSelf, for: .scrollWheel, priority: .high) - - strongSelf.control = control - strongSelf.background.flip = false - var point:NSPoint = control.convert(NSMakePoint(0, 0), to: parentView) - - if let edge = edge { - - switch edge { - case .maxX: - point.x -= controller.frame.width - case .maxY: - // point.x += floorToScreenPixels((control.superview!.frame.width - controller.frame.width) / 2.0) - point.y -= controller.frame.height - strongSelf.background.flip = true - case .minX: - point.x -= (controller.frame.width - control.frame.width) - point.y -= controller.frame.height - strongSelf.background.flip = true - default: - fatalError("Not Implemented") - } - - - } - - - - if inset.x != 0 { - point.x += (inset.x) - - } - if inset.y != 0 { - point.y += inset.y - } - - - controller.viewDidAppear(strongSelf.animates) - - var rect = controller.bounds - if !NSIsEmptyRect(contentRect) { - rect = contentRect - } - - point.x = min(max(5, point.x), (parentView.frame.width - rect.width - 12) - 5) - point.y = min(max(5, point.y), (parentView.frame.height - rect.height - 12) - 5) - - parentView.layer?.isOpaque = true - - //.borderSize * 2 - strongSelf.background.frame = NSMakeRect(point.x, point.y, rect.width + 14, rect.height + 14) - strongSelf.background.backgroundColor = .clear - strongSelf.background.layer?.cornerRadius = .cornerRadius - - strongSelf.overlay = OverlayControl(frame: NSMakeRect(contentRect.minX, contentRect.minY, controller.frame.width , controller.frame.height )) - strongSelf.overlay.backgroundColor = presentation.colors.background - strongSelf.overlay.layer?.cornerRadius = .cornerRadius - strongSelf.overlay.layer?.opacity = 0.99 - - - let bg = View(frame: NSMakeRect(strongSelf.overlay.frame.minX + 2, strongSelf.overlay.frame.minY + 2, strongSelf.overlay.frame.width - 4, strongSelf.overlay.frame.height - 4)) - bg.layer?.cornerRadius = .cornerRadius - bg.backgroundColor = presentation.colors.background - - strongSelf.background.addSubview(bg) - - strongSelf.background.addSubview(strongSelf.overlay) - - - controller.view.layer?.cornerRadius = .cornerRadius - controller.view.setFrameOrigin(NSMakePoint(0, 0)) - - - strongSelf.overlay.addSubview(controller.view) - - parentView.addSubview(strongSelf.background) - - //strongSelf.overlay.center() - - _ = controller.becomeFirstResponder() - - strongSelf.isShown = true - - if let _ = strongSelf.overlay { - if strongSelf.animates { - - var once:Bool = false - - for sub in strongSelf.background.subviews { - sub.layer?.animate(from: (-strongSelf.background.frame.height) as NSNumber, to: (sub.frame.minY) as NSNumber, keyPath: "position.y", timingFunction: strongSelf.animationStyle.function, duration: strongSelf.animationStyle.duration, removeOnCompletion: true, additive: false, completion:{ [weak controller] (comple) in - if let strongSelf = self, !once { - once = true - controller?.viewDidAppear(strongSelf.animates) - } - - }) - - // sub.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: strongSelf.animationStyle.function, duration: strongSelf.animationStyle.duration) - } - - - - } - - let nHandler:(Control) -> Void = { [weak strongSelf] control in - if let strongSelf = strongSelf { - let s = Signal.single(Void()) |> delay(0.2, queue: Queue.mainQueue()) |> then(Signal.single(Void()) |> delay(0.1, queue: Queue.mainQueue()) |> restart) - - strongSelf.disposable.set(s.start(next: { [weak strongSelf] () in - if let strongSelf = strongSelf { - if !strongSelf.inside() && !control.mouseInside() { - strongSelf.hide() - } - } - - })) - } - - - } - - var first: Bool = true - - control.kitWindow?.set(mouseHandler: { [weak strongSelf, weak control] _ -> KeyHandlerResult in - if let strongSelf = strongSelf, first, let control = control { - if !strongSelf.inside() && !control.mouseInside() { - first = false - nHandler(control) - } - } - return .invokeNext - }, with: strongSelf, for: .mouseMoved, priority: .high) - - let hHandler:(Control) -> Void = { [weak strongSelf] _ in - - strongSelf?.disposable.set(nil) - - } - - strongSelf.background.set(handler: nHandler, for: .Normal) - strongSelf.background.set(handler: hHandler, for: .Hover) - - - control.set(handler: nHandler, for: .Normal) - control.set(handler: hHandler, for: .Hover) - - - } - } else if let strongSelf = self { - controller?.viewWillDisappear(false) - controller?.viewDidDisappear(false) - controller?.popover = nil - strongSelf.controller = nil - strongSelf.window?.removeAllHandlers(for: strongSelf) - strongSelf.window?.remove(object: strongSelf, for: .All) - } - - })) - - } - - } - - - public func addSubview(_ subview: View) -> Void { - self.background.addSubview(subview) - } - - func inside() -> Bool { - - // return true - - if let window = control?.window { - let g:NSPoint = NSEvent.mouseLocation - let w:NSPoint = window.contentView!.convert(window.convertFromScreen(NSMakeRect(g.x, g.y, 1, 1)).origin, from: nil) - //if w.x > background.frame.minX && background - return NSPointInRect(w, background.frame) - } - return false - } - - - deinit { - self.disposable.dispose() - self.readyDisposable.dispose() - window?.remove(object: self, for: .All) - background.removeFromSuperview() - } - - public func hide(_ removeHandlers:Bool = true) -> Void { - if !isShown { - return - } - isShown = false - control?.isSelected = false - window?.removeAllHandlers(for: self) - window?.remove(object: self, for: .All) - - overlay?.removeLastStateHandler() - overlay?.removeLastStateHandler() - - if removeHandlers { - control?.removeLastStateHandler() - control?.removeLastStateHandler() - } - - self.disposable.dispose() - self.readyDisposable.dispose() - controller?.viewWillDisappear(true) - if animates { - var once:Bool = false - background.change(opacity: 0, animated: animates) - for sub in background.subviews { - - sub._change(opacity: 0, animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] complete in - if let strongSelf = self, !once { - once = true - strongSelf.controller?.viewDidDisappear(true) - strongSelf.controller?.popover = nil - strongSelf.controller = nil - strongSelf.background.removeFromSuperview() - } - }) - - } - } else { - controller?.viewDidDisappear(false) - controller?.popover = nil - controller = nil - background.removeFromSuperview() - } - } - -} - -public func hasPopover(_ window:Window) -> Bool { - if !window.sheets.isEmpty { - return true - } - for subview in window.contentView!.subviews { - if let subview = subview as? PopoverBackground, let popover = subview.popover { - return popover.isShown - } - } - return false -} - -public func showPopover(for control:Control, with controller:ViewController, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, delayBeforeShown: Double = 0.2) -> Void { - if let window = control.window as? Window, !hasPopover(window) { - if controller.popover == nil { - controller.popover = (controller.popoverClass as! Popover.Type).init(controller: controller) - } - - if let popover = controller.popover { - popover.show(for: control, edge: edge, inset: inset, delayBeforeShown: delayBeforeShown) - } - } -} - - diff --git a/TGUIKit/TGUIKit/PresentationTheme.swift b/TGUIKit/TGUIKit/PresentationTheme.swift deleted file mode 100644 index 9098ffa3ba..0000000000 --- a/TGUIKit/TGUIKit/PresentationTheme.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// PresentationTheme.swift -// Telegram -// -// Created by keepcoder on 22/06/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -// - - - - - -public struct SearchTheme { - public let backgroundColor: NSColor - public let searchImage:CGImage - public let clearImage:CGImage - public let placeholder:String - public let textColor: NSColor - public let placeholderColor: NSColor - public init(_ backgroundColor: NSColor, _ searchImage:CGImage, _ clearImage:CGImage, _ placeholder:String, _ textColor: NSColor, _ placeholderColor: NSColor) { - self.backgroundColor = backgroundColor - self.searchImage = searchImage - self.clearImage = clearImage - self.placeholder = placeholder - self.textColor = textColor - self.placeholderColor = placeholderColor - } -} - -public struct ColorPallete { - public let background: NSColor - public let text: NSColor - public let grayText:NSColor - public let link:NSColor - public let blueUI:NSColor - public let redUI:NSColor - public let greenUI:NSColor - public let blackTransparent:NSColor - public let grayTransparent:NSColor - public let grayUI:NSColor - public let darkGrayText:NSColor - public let blueText:NSColor - public let blueSelect:NSColor - public let selectText:NSColor - public let blueFill:NSColor - public let border:NSColor - public let grayBackground:NSColor - public let grayForeground:NSColor - public let grayIcon:NSColor - public let blueIcon:NSColor - public let badgeMuted:NSColor - public let badge:NSColor - public let indicatorColor: NSColor - public let selectMessage: NSColor - public init(background:NSColor, text: NSColor, grayText: NSColor, link: NSColor, blueUI:NSColor, redUI:NSColor, greenUI:NSColor, blackTransparent:NSColor, grayTransparent:NSColor, grayUI:NSColor, darkGrayText:NSColor, blueText:NSColor, blueSelect:NSColor, selectText:NSColor, blueFill:NSColor, border:NSColor, grayBackground:NSColor, grayForeground:NSColor, grayIcon:NSColor, blueIcon:NSColor, badgeMuted:NSColor, badge:NSColor, indicatorColor: NSColor, selectMessage: NSColor) { - self.background = background - self.text = text - self.grayText = grayText - self.link = link - self.blueUI = blueUI - self.redUI = redUI - self.greenUI = greenUI - self.blackTransparent = blackTransparent - self.grayTransparent = grayTransparent - self.grayUI = grayUI - self.darkGrayText = darkGrayText - self.blueText = blueText - self.blueSelect = blueSelect - self.selectText = selectText - self.blueFill = blueFill - self.border = border - self.grayBackground = grayBackground - self.grayForeground = grayForeground - self.grayIcon = grayIcon - self.blueIcon = blueIcon - self.badgeMuted = badgeMuted - self.badge = badge - self.indicatorColor = indicatorColor - self.selectMessage = selectMessage - } -} - - - -open class PresentationTheme : Equatable { - - public let colors:ColorPallete - public let search: SearchTheme - - public let resourceCache = PresentationsResourceCache() - - public init(colors: ColorPallete, search: SearchTheme) { - self.colors = colors - self.search = search - } - - static var current: PresentationTheme { - return presentation - } - - - public static func ==(lhs: PresentationTheme, rhs: PresentationTheme) -> Bool { - return lhs === rhs - } - -// public func image(_ key: Int32, _ generate: (PresentationTheme) -> CGImage?) -> CGImage? { -// return self.resourceCache.image(key, self, generate) -// } -// -// public func object(_ key: Int32, _ generate: (PresentationTheme) -> AnyObject?) -> AnyObject? { -// return self.resourceCache.object(key, self, generate) -// } -} - - -public var navigationButtonStyle:ControlStyle { - return ControlStyle(font: .normal(.title), foregroundColor: presentation.colors.link, backgroundColor: presentation.colors.background, highlightColor: presentation.colors.blueUI) -} -public var switchViewAppearance: SwitchViewAppearance { - return SwitchViewAppearance(backgroundColor: presentation.colors.background, stateOnColor: presentation.colors.blueUI, stateOffColor: presentation.colors.grayBackground, disabledColor: presentation.colors.grayTransparent, borderColor: presentation.colors.border) -} -//0xE3EDF4 -public let whitePallete = ColorPallete(background: .white, text: NSColor(0x000000), grayText: NSColor(0x999999), link: NSColor(0x2481cc), blueUI: NSColor(0x2481cc), redUI: NSColor(0xff3b30), greenUI:NSColor(0x63DA6E), blackTransparent: NSColor(0x000000, 0.6), grayTransparent: NSColor(0xf4f4f4, 0.4), grayUI: NSColor(0xFaFaFa), darkGrayText:NSColor(0x333333), blueText:NSColor(0x2481CC), blueSelect:NSColor(0x4c91c7), selectText:NSColor(0xeaeaea), blueFill:NSColor(0x4ba3e2), border:NSColor(0xeaeaea), grayBackground:NSColor(0xf4f4f4), grayForeground:NSColor(0xe4e4e4), grayIcon:NSColor(0x9e9e9e), blueIcon:NSColor(0x0f8fe4), badgeMuted:NSColor(0xd7d7d7), badge:NSColor(0x4ba3e2), indicatorColor: NSColor(0x464a57), selectMessage: NSColor(0xE3EDF4)) - -//04afc8 -//282b35 -public let darkPallete = ColorPallete(background: NSColor(0x292b36), text: NSColor(0xe9e9e9), grayText: NSColor(0x8699a3), link: NSColor(0x04afc8), blueUI: NSColor(0x04afc8), redUI: NSColor(0xec6657), greenUI:NSColor(0x49ad51), blackTransparent: NSColor(0x000000, 0.6), grayTransparent: NSColor(0x2f313d, 0.5), grayUI: NSColor(0x292b36), darkGrayText:NSColor(0x8699a3), blueText:NSColor(0x04afc8), blueSelect:NSColor(0x20889a), selectText: NSColor(0x8699a3), blueFill: NSColor(0x04afc8), border: NSColor(0x464a57), grayBackground:NSColor(0x464a57), grayForeground:NSColor(0x3d414d), grayIcon: NSColor(0x8699a3), blueIcon: NSColor(0x04afc8), badgeMuted:NSColor(0x8699a3), badge:NSColor(0x04afc8), indicatorColor: .white, selectMessage: NSColor(0x3d414d)) - -/* - public let darkPallete = ColorPallete(background: NSColor(0x282e33), text: NSColor(0xe9e9e9), grayText: NSColor(0x999999), link: NSColor(0x20eeda), blueUI: NSColor(0x20eeda), redUI: NSColor(0xec6657), greenUI:NSColor(0x63DA6E), blackTransparent: NSColor(0x000000, 0.6), grayTransparent: NSColor(0xf4f4f4, 0.4), grayUI: NSColor(0xFaFaFa), darkGrayText:NSColor(0x333333), blueText:NSColor(0x009687), blueSelect:NSColor(0x009687), selectText:NSColor(0xeaeaea), blueFill: NSColor(0x20eeda), border: NSColor(0x3d444b), grayBackground:NSColor(0x3d444b), grayForeground:NSColor(0xe4e4e4), grayIcon:NSColor(0x757676), blueIcon: NSColor(0x20eeda), badgeMuted:NSColor(0xd7d7d7), badge:NSColor(0x4ba3e2), indicatorColor: .white) - */ - - -private var _theme:Atomic = Atomic(value: whiteTheme) - -public let whiteTheme = PresentationTheme(colors: whitePallete, search: SearchTheme(.grayBackground, #imageLiteral(resourceName: "Icon_SearchField").precomposed(), #imageLiteral(resourceName: "Icon_SearchClear").precomposed(), localizedString("SearchField.Search"), .text, .grayText)) - - - -public var presentation:PresentationTheme { - return _theme.modify {$0} -} - -public func updateTheme(_ theme:PresentationTheme) { - assertOnMainThread() - _ = _theme.swap(theme) -} - - diff --git a/TGUIKit/TGUIKit/ProgressIndicator.swift b/TGUIKit/TGUIKit/ProgressIndicator.swift deleted file mode 100644 index 93f43a6aa2..0000000000 --- a/TGUIKit/TGUIKit/ProgressIndicator.swift +++ /dev/null @@ -1,356 +0,0 @@ -// -// ProgressIndicator.swift -// TGUIKit -// -// Created by keepcoder on 06/07/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa - -/*fileprivate let kITSpinAnimationKey: String = "spinAnimation" -fileprivate let kITProgressPropertyKey: String = "progress" - - -extension NSBezierPath { - func it_rotatedBezierPath(_ angle: Float) -> NSBezierPath { - return it_rotatedBezierPath(angle, aboutPoint: NSMakePoint(NSMidX(bounds), NSMidY(bounds))) - } - - func it_rotatedBezierPath(_ angle: Float, aboutPoint point: NSPoint) -> NSBezierPath { - if angle == 0.0 { - return self - } else { - let copy: NSBezierPath = self - let xfm: NSAffineTransform = it_rotationTransform(withAngle: angle, aboutPoint: point) - copy.transform(using: xfm as AffineTransform) - return copy - } - } - - func it_rotationTransform(withAngle angle: Float, aboutPoint: NSPoint) -> NSAffineTransform { - let xfm = NSAffineTransform() - xfm.translateX(by: aboutPoint.x, yBy: aboutPoint.y) - xfm.rotate(byRadians: CGFloat(angle)) - xfm.translateX(by: -aboutPoint.x, yBy: -aboutPoint.y) - return xfm - } -} - -public class ProgressIndicator: View { - public var isIndeterminate: Bool = false { - didSet { - if (!isIndeterminate) { - self.animates = false; - } - } - } - public var progress:CGFloat = 0 { - didSet { - if (isIndeterminate) { - reloadIndicatorContent() - } - } - } - public override var animates: Bool { - didSet { - reloadIndicatorContent() - reloadAnimation() - reloadVisibility() - } - } - public var hideWhenStopped:Bool { - didSet { - reloadVisibility() - } - } - public var lengthOfLine:CGFloat { - didSet { - reloadIndicatorContent() - } - } - public var widthOfLine:CGFloat { - didSet { - reloadIndicatorContent() - } - } - public var numberOfLines:Int32 { - didSet { - reloadAnimation() - reloadIndicatorContent() - } - } - public var innerMargin:CGFloat { - didSet { - reloadIndicatorContent() - } - } - public var animationDuration:CGFloat { - didSet { - reloadAnimation() - } - } - public var steppedAnimation:Bool { - didSet { - reloadAnimation() - } - } - public var color:NSColor { - didSet { - reloadIndicatorContent() - } - } - - private let progressIndicatorLayer: CALayer = CALayer() - - public required init(frame frameRect: NSRect) { - self.color = presentation.colors.indicatorColor - self.innerMargin = 4; - self.widthOfLine = 3; - self.lengthOfLine = 6; - self.numberOfLines = 8; - self.animationDuration = 0.6; - self.isIndeterminate = true; - self.steppedAnimation = true; - self.hideWhenStopped = true; - - - super.init(frame: frameRect) - self.animates = true; - self.wantsLayer = true - self.backgroundColor = .clear - self.progressIndicatorLayer.frame = bounds - self.layer!.addSublayer(progressIndicatorLayer) - self.flip = false - reloadIndicatorContent() - reloadAnimation() - } - - - public override func viewDidMoveToWindow() { - super.viewDidMoveToWindow() - if let _ = window { - animates = true - } else { - animates = false - } - } - convenience override init() { - self.init(frame: NSMakeRect(0, 0, 20, 20)) - } - - public override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - self.progressIndicatorLayer.frame = bounds - } - - func reloadIndicatorContent() { - progressIndicatorLayer.contents = progressImage - } - - func reloadAnimation() { - progressIndicatorLayer.removeAnimation(forKey: kITSpinAnimationKey) - if animates { - progressIndicatorLayer.add(keyFrameAnimationForCurrentPreferences(), forKey: kITSpinAnimationKey) - } - } - - - var progressImage: NSImage { - let progressImage = NSImage(size: bounds.size) - progressImage.lockFocus() - do { - NSGraphicsContext.saveGraphicsState() - do { - color.set() - let r: NSRect = bounds - let numberOfLines:Float = Float(self.numberOfLines) - let line = NSBezierPath(roundedRect: NSMakeRect((r.width / 2) - (widthOfLine / 2), (r.height / 2) - innerMargin - lengthOfLine, widthOfLine, lengthOfLine), xRadius: widthOfLine / 2, yRadius: widthOfLine / 2) - - let lineDrawingBlock: (_ line: Int32) -> Void = { (_ lineNumber: Int32) -> Void in - var lineInstance: NSBezierPath = line.copy() as! NSBezierPath - lineInstance = lineInstance.it_rotatedBezierPath(((2 * Float.pi) / numberOfLines * Float(lineNumber)) + Float.pi, aboutPoint: NSMakePoint(r.width / 2, r.height / 2)) - - if self.isIndeterminate { - self.color.withAlphaComponent(CGFloat(1.0 - (1.0 / Float(self.numberOfLines) * Float(lineNumber)))).set() - } - lineInstance.fill() - } - - if !isIndeterminate { - var i = self.numberOfLines - - while i > Int32(round(numberOfLines - (numberOfLines * Float(progress)))) { - lineDrawingBlock(i) - i -= 1 - } - } - else { - for i in 0 ..< self.numberOfLines { - lineDrawingBlock(i) - } - } - } - NSGraphicsContext.restoreGraphicsState() - } - progressImage.unlockFocus() - return progressImage - } - - func keyFrameAnimationForCurrentPreferences() -> CAKeyframeAnimation { - var keyFrameValues:[NSNumber] = [] - var keyTimeValues:[NSNumber] = [] - if steppedAnimation { - do { - keyFrameValues.append(NSNumber(value: 0.0)) - for i in 0 ..< numberOfLines { - let i:Float = Float(i) - keyFrameValues.append(NSNumber(value: -Float.pi * (2.0 / Float(numberOfLines) * i))) - keyFrameValues.append(NSNumber(value: -Float.pi * (2.0 / Float(numberOfLines) * i))) - } - keyFrameValues.append(NSNumber(value: -Float.pi * 2.0)) - } - do { - keyTimeValues.append(NSNumber(value: 0.0)) - for i in 0 ..< (numberOfLines - 1) { - let i:Float = Float(i) - keyTimeValues.append(NSNumber(value: 1.0 / Float(numberOfLines) * i)) - keyTimeValues.append(NSNumber(value: 1.0 / Float(numberOfLines) * (i + 1))) - } - keyTimeValues.append(NSNumber(value: 1.0 / Float(numberOfLines) * (Float(numberOfLines) - 1))) - } - } - else { - do { - keyFrameValues.append(NSNumber(value: -Float.pi * 0.0)) - keyFrameValues.append(NSNumber(value: -Float.pi * 0.5)) - keyFrameValues.append(NSNumber(value: -Float.pi * 1.0)) - keyFrameValues.append(NSNumber(value: -Float.pi * 1.5)) - keyFrameValues.append(NSNumber(value: -Float.pi * 2.0)) - } - } - let animation = CAKeyframeAnimation(keyPath: "transform") - animation.repeatCount = .greatestFiniteMagnitude - animation.values = keyFrameValues - animation.keyTimes = keyTimeValues - animation.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ) - animation.duration = CFTimeInterval(animationDuration) - animation.beginTime = 1 - return animation - } - - func reloadVisibility() { -// if hideWhenStopped && !animates && isIndeterminate { -// isHidden = true -// } -// else { -// isHidden = false -// } - } - - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -}*/ - - -private class ProgressLayer : CALayer { - - fileprivate func update(_ hasAnimation: Bool) { - if hasAnimation { - var fromValue: Float = 0 - - if let layer = presentation(), let from = layer.value(forKeyPath: "transform.rotation.z") as? Float { - fromValue = from - } - let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - basicAnimation.duration = 0.8 - basicAnimation.fromValue = fromValue - basicAnimation.toValue = Double.pi * 2.0 - basicAnimation.repeatCount = Float.infinity - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - add(basicAnimation, forKey: "progressRotation") - } else { - removeAllAnimations() - } - } - - - override func draw(in ctx: CGContext) { - - ctx.setStrokeColor(PresentationTheme.current.colors.indicatorColor.cgColor) - - let startAngle = 2.0 * (CGFloat.pi) * 0.8 - CGFloat.pi / 2 - let endAngle = -(CGFloat.pi / 2) - - let lineWidth: CGFloat = 2.0 - let diameter = floorToScreenPixels(frame.height) - - let pathDiameter = diameter - lineWidth - lineWidth * 2 - ctx.addArc(center: NSMakePoint(diameter / 2.0, floorToScreenPixels(diameter / 2.0)), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true) - - ctx.setLineWidth(lineWidth); - ctx.setLineCap(.round); - ctx.strokePath() - } -} - -public class ProgressIndicator : View { - private let indicator: ProgressLayer = ProgressLayer() - public required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - indicator.frame = bounds - layer?.addSublayer(indicator) - indicator.isOpaque = false - indicator.contentsScale = System.backingScale - } - - public override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - indicator.frame = bounds - indicator.setNeedsDisplay() - } - - public override init() { - super.init(frame: NSMakeRect(0, 0, 20, 20)) - indicator.frame = bounds - layer?.addSublayer(indicator) - indicator.isOpaque = false - indicator.contentsScale = System.backingScale - } - - public override func viewDidMoveToSuperview() { - updateWantsAnimation() - } - - public override func viewDidMoveToWindow() { - updateWantsAnimation() - } - - public override func viewDidHide() { - updateWantsAnimation() - } - - public override func viewDidUnhide() { - updateWantsAnimation() - } - - private func updateWantsAnimation() { - indicator.update(!isHidden && superview != nil && window != nil) - indicator.setNeedsDisplay() - } - - - override public func draw(_ layer: CALayer, in ctx: CGContext) { - //super.draw(layer, in: ctx) - - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - diff --git a/TGUIKit/TGUIKit/ProgressModal.swift b/TGUIKit/TGUIKit/ProgressModal.swift deleted file mode 100644 index 9bf4930e5e..0000000000 --- a/TGUIKit/TGUIKit/ProgressModal.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ProgressModal.swift -// TGUIKit -// -// Created by keepcoder on 09/11/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac -class ProgressModalController: ModalViewController { - - private var progressView:RadialProgressView? - private var timer:SwiftSignalKitMac.Timer? - private var progress:Float = 0.2 - override var background: NSColor { - return .clear - } - - override var containerBackground: NSColor { - return .clear - } - - override func loadView() { - super.loadView() - - progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, icon: nil)) - progressView?.state = .ImpossibleFetching(progress: progress, force: false) - view.background = NSColor(0x000000,0.8) - view.addSubview(progressView!) - progressView?.center() - - viewDidLoad() - } - - override func viewDidResized(_ size: NSSize) { - super.viewDidResized(size) - } - - override func viewDidLoad() { - super.viewDidLoad() - readyOnce() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - - self.timer = SwiftSignalKitMac.Timer(timeout: 0.05, repeat: true, completion: { [weak self] in - if let strongSelf = self { - strongSelf.progress += 0.05 - strongSelf.progressView?.state = .ImpossibleFetching(progress: strongSelf.progress, force: false) - if strongSelf.progress >= 0.8 { - strongSelf.timer?.invalidate() - } - } - }, queue: Queue.mainQueue()) - self.timer?.start() - - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - timer?.invalidate() - timer = nil - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override init() { - super.init(frame:NSMakeRect(0,0,100,100)) - self.bar = .init(height: 0) - } - - -} - -public func showModalProgress(signal:Signal, for window:Window) -> Signal { - return Signal { subscriber in - - let signal = signal |> deliverOnMainQueue - - let modal = ProgressModalController() - let beforeModal:Signal = .single(Void()) |> delay(0.25, queue: Queue.mainQueue()) - - let beforeDisposable:DisposableSet = DisposableSet() - - beforeDisposable.add(beforeModal.start(completed: { - showModal(with: modal, for: window) - })) - - - - beforeDisposable.add(signal.start(next: { next in - subscriber.putNext(next) - }, error: { error in - subscriber.putError(error) - //beforeDisposable.dispose() - modal.close() - }, completed: { - subscriber.putCompletion() - beforeDisposable.dispose() - modal.close() - })) - - return beforeDisposable - } - - -} diff --git a/TGUIKit/TGUIKit/RadialProgressView.swift b/TGUIKit/TGUIKit/RadialProgressView.swift deleted file mode 100644 index edfed2b1f4..0000000000 --- a/TGUIKit/TGUIKit/RadialProgressView.swift +++ /dev/null @@ -1,428 +0,0 @@ -// -// RadialProgressLayer.swift -// TGUIKit -// -// Created by keepcoder on 17/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -private let progressInteractiveThumb:CGImage = { - - let context = DrawingContext(size: NSMakeSize(40, 40), scale: 1.0, clear: true) - - context.withContext { (ctx) in - - ctx.round(context.size, context.size.height/2.0) - ctx.setFillColor(NSColor.blueFill.cgColor) - - let image = #imageLiteral(resourceName: "Icon_MessageFile").precomposed() - - ctx.fill(NSMakeRect(0, 0, context.size.width, context.size.height)) - ctx.draw(image, in: NSMakeRect(floorToScreenPixels((context.size.width - image.backingSize.width) / 2.0), floorToScreenPixels((context.size.height - image.backingSize.height) / 2.0), image.backingSize.width, image.backingSize.height)) - - } - - return context.generateImage()! - -}() - -public struct FetchControls { - public let fetch: () -> Void - public init(fetch:@escaping()->Void) { - self.fetch = fetch - } -} - - -private class RadialProgressParameters: NSObject { - let theme: RadialProgressTheme - let diameter: CGFloat - let twist: Bool - let state: RadialProgressState - - init(theme: RadialProgressTheme, diameter: CGFloat, state: RadialProgressState, twist: Bool = true) { - self.theme = theme - self.diameter = diameter - self.state = state - self.twist = twist - super.init() - } -} - -public struct RadialProgressTheme : Equatable { - public let backgroundColor: NSColor - public let foregroundColor: NSColor - public let icon: CGImage? - public let iconInset:NSEdgeInsets - public let diameter:CGFloat? - public let lineWidth: CGFloat - public init(backgroundColor:NSColor, foregroundColor:NSColor, icon:CGImage? = nil, iconInset:NSEdgeInsets = NSEdgeInsets(), diameter: CGFloat? = nil, lineWidth: CGFloat = 2) { - self.iconInset = iconInset - self.backgroundColor = backgroundColor - self.foregroundColor = foregroundColor - self.icon = icon - self.diameter = diameter - self.lineWidth = lineWidth - } -} - -public func ==(lhs:RadialProgressTheme, rhs:RadialProgressTheme) -> Bool { - return lhs.backgroundColor == rhs.backgroundColor && lhs.foregroundColor == rhs.foregroundColor && ((lhs.icon == nil) == (rhs.icon == nil)) -} - -public enum RadialProgressState: Equatable { - case None - case Remote - case Fetching(progress: Float, force: Bool) - case ImpossibleFetching(progress: Float, force: Bool) - case Play - case Icon(image:CGImage, mode:CGBlendMode) -} - -public func ==(lhs:RadialProgressState, rhs:RadialProgressState) -> Bool { - switch lhs { - case .None: - if case .None = rhs { - return true - } else { - return false - } - case .Remote: - if case .Remote = rhs { - return true - } else { - return false - } - case .Play: - if case .Play = rhs { - return true - } else { - return false - } - case let .Fetching(lhsProgress): - if case let .Fetching(rhsProgress) = rhs, lhsProgress == rhsProgress { - return true - } else { - return false - } - case let .ImpossibleFetching(lhsProgress): - if case let .ImpossibleFetching(rhsProgress) = rhs, lhsProgress == rhsProgress { - return true - } else { - return false - } - case .Icon: - if case .Icon = rhs { - return true - } else { - return false - } - } -} - - -private class RadialProgressOverlayLayer: Layer { - let theme: RadialProgressTheme - let twist: Bool - private var timer: SwiftSignalKitMac.Timer? - private var _progress: Float = 0 - private var progress: Float = 0 - var parameters:RadialProgressParameters { - return RadialProgressParameters(theme: self.theme, diameter: theme.diameter ?? frame.width, state: self.state, twist: twist) - } - - - var state: RadialProgressState = .None { - didSet { - switch state { - case .None, .Play, .Remote, .Icon: - self.progress = 0 - self._progress = 0 - case let .Fetching(progress, f), let .ImpossibleFetching(progress, f): - self.progress = progress - if f { - _progress = progress - } - } - let fps: Float = 60 - let difference = progress - _progress - let tick: Float = Float(difference / (fps * 0.2)) - if difference > 0 { - timer = SwiftSignalKitMac.Timer(timeout: TimeInterval(1 / fps), repeat: true, completion: { [weak self] in - if let strongSelf = self { - strongSelf._progress += tick - strongSelf.setNeedsDisplay() - if strongSelf._progress == strongSelf.progress || strongSelf._progress < 0 || strongSelf._progress > strongSelf.progress { - strongSelf.stopAnimation() - } - } - }, queue: Queue.mainQueue()) - timer?.start() - } else { - stopAnimation() - _progress = progress - } - - self.setNeedsDisplay() - } - } - - func stopAnimation() { - timer?.invalidate() - timer = nil - self.setNeedsDisplay() - } - - init(theme: RadialProgressTheme, twist: Bool) { - self.theme = theme - self.twist = twist - super.init() - - self.isOpaque = false - } - - fileprivate override func draw(in ctx: CGContext) { - ctx.setStrokeColor(parameters.theme.foregroundColor.cgColor) - - let startAngle = 2.0 * (CGFloat.pi) * CGFloat(_progress) - CGFloat.pi / 2 - let endAngle = -(CGFloat.pi / 2) - - let pathDiameter = !twist ? parameters.diameter - parameters.theme.lineWidth : parameters.diameter - parameters.theme.lineWidth - parameters.theme.lineWidth * parameters.theme.lineWidth - ctx.addArc(center: NSMakePoint(parameters.diameter / 2.0, floorToScreenPixels(parameters.diameter / 2.0)), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true) - - ctx.setLineWidth(parameters.theme.lineWidth); - ctx.setLineCap(.round); - ctx.strokePath() - } - - override func layerMoved(to superlayer: CALayer?) { - - super.layerMoved(to: superlayer) - - if let _ = superlayer, parameters.twist { - let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - basicAnimation.duration = 2.0 - basicAnimation.fromValue = NSNumber(value: Float(0.0)) - basicAnimation.toValue = NSNumber(value: Float.pi * 2.0) - basicAnimation.repeatCount = Float.infinity - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - self.add(basicAnimation, forKey: "progressRotation") - } else { - self.removeAllAnimations() - } - } - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - - -public class RadialProgressView: Control { - - - public var fetchControls:FetchControls? { - didSet { - self.removeAllHandlers() - if let fetchControls = fetchControls { - set(handler: { _ in - fetchControls.fetch() - }, for: .Click) - } - } - } - - public var theme:RadialProgressTheme { - didSet { - self.setNeedsDisplay() - } - } - private let overlay: RadialProgressOverlayLayer - private var parameters:RadialProgressParameters { - return RadialProgressParameters(theme: self.theme, diameter: NSWidth(self.frame), state: self.state) - } - - - - - public var state: RadialProgressState = .None { - didSet { - self.overlay.state = self.state - if case .Fetching = state { - if self.overlay.superlayer == nil { - self.layer?.addSublayer(self.overlay) - } - } else if case .ImpossibleFetching = state { - if self.overlay.superlayer == nil { - self.layer?.addSublayer(self.overlay) - } - } else { - if self.overlay.superlayer != nil { - self.overlay.removeFromSuperlayer() - } - } - switch oldValue { - case .Fetching: - switch self.state { - case .Fetching: - break - default: - self.setNeedsDisplay() - } - case .ImpossibleFetching: - switch self.state { - case .ImpossibleFetching: - break - default: - self.setNeedsDisplay() - } - case .Remote: - switch self.state { - case .Remote: - break - default: - self.setNeedsDisplay() - } - case .None: - switch self.state { - case .None: - break - default: - self.setNeedsDisplay() - } - case .Play: - switch self.state { - case .Play: - break - default: - self.setNeedsDisplay() - } - case .Icon: - switch self.state { - case .Icon: - break - default: - self.setNeedsDisplay() - } - } - - } - } - - public override func viewDidMoveToSuperview() { - if self.superview == nil { - self.state = .None - } - } - - - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - override public var frame: CGRect { - get { - return super.frame - } set (value) { - let redraw = value.size != self.frame.size - super.frame = value - - if redraw { - self.overlay.frame = CGRect(origin: CGPoint(), size: value.size) - self.setNeedsDisplay() - self.overlay.setNeedsDisplay() - } - } - } - - public init(theme: RadialProgressTheme = RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: nil), twist: Bool = true) { - self.theme = theme - self.overlay = RadialProgressOverlayLayer(theme: theme, twist: twist) - super.init() - self.overlay.contentsScale = backingScaleFactor - self.frame = NSMakeRect(0, 0, 40, 40) - - } - - - public override func draw(_ layer: CALayer, in context: CGContext) { - context.setFillColor(parameters.theme.backgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: parameters.diameter, height: parameters.diameter))) - - switch parameters.state { - case .None: - break - case .Fetching: - context.setStrokeColor(parameters.theme.foregroundColor.cgColor) - context.setLineWidth(2.0) - context.setLineCap(.round) - - let crossSize: CGFloat = 14.0 - context.move(to: CGPoint(x: parameters.diameter / 2.0 - crossSize / 2.0, y: parameters.diameter / 2.0 - crossSize / 2.0)) - context.addLine(to: CGPoint(x: parameters.diameter / 2.0 + crossSize / 2.0, y: parameters.diameter / 2.0 + crossSize / 2.0)) - context.strokePath() - context.move(to: CGPoint(x: parameters.diameter / 2.0 + crossSize / 2.0, y: parameters.diameter / 2.0 - crossSize / 2.0)) - context.addLine(to: CGPoint(x: parameters.diameter / 2.0 - crossSize / 2.0, y: parameters.diameter / 2.0 + crossSize / 2.0)) - context.strokePath() - case .Remote: - context.setStrokeColor(parameters.theme.foregroundColor.cgColor) - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setLineJoin(.round) - - let arrowHeadSize: CGFloat = 15.0 - let arrowLength: CGFloat = 18.0 - let arrowHeadOffset: CGFloat = 1.0 - - context.move(to: CGPoint(x: parameters.diameter / 2.0, y: parameters.diameter / 2.0 - arrowLength / 2.0 + arrowHeadOffset)) - context.addLine(to: CGPoint(x: parameters.diameter / 2.0, y: parameters.diameter / 2.0 + arrowLength / 2.0 - 1.0 + arrowHeadOffset)) - context.strokePath() - - context.move(to: CGPoint(x: parameters.diameter / 2.0 - arrowHeadSize / 2.0, y: parameters.diameter / 2.0 + arrowLength / 2.0 - arrowHeadSize / 2.0 + arrowHeadOffset)) - context.addLine(to: CGPoint(x: parameters.diameter / 2.0, y: parameters.diameter / 2.0 + arrowLength / 2.0 + arrowHeadOffset)) - context.addLine(to: CGPoint(x: parameters.diameter / 2.0 + arrowHeadSize / 2.0, y: parameters.diameter / 2.0 + arrowLength / 2.0 - arrowHeadSize / 2.0 + arrowHeadOffset)) - context.strokePath() - case .Play: - if let icon = parameters.theme.icon { - var f = focus(icon.backingSize) - f.origin.x += parameters.theme.iconInset.left - f.origin.x -= parameters.theme.iconInset.right - f.origin.y += parameters.theme.iconInset.top - f.origin.y -= parameters.theme.iconInset.bottom - context.draw(icon, in: f) - } - case .ImpossibleFetching: - break - case let .Icon(image: icon, mode:blendMode): - var f = focus(icon.backingSize) - f.origin.x += parameters.theme.iconInset.left - f.origin.x -= parameters.theme.iconInset.right - f.origin.y += parameters.theme.iconInset.top - f.origin.y -= parameters.theme.iconInset.bottom - context.setBlendMode(blendMode) - context.draw(icon, in: f) - } - - } - - public override func copy() -> Any { - let view = View() - view.frame = self.frame - view.layer?.contents = progressInteractiveThumb - return view - - } - - public override func apply(state: ControlState) { - - } - -} diff --git a/TGUIKit/TGUIKit/ScrollView.swift b/TGUIKit/TGUIKit/ScrollView.swift deleted file mode 100644 index a800353bd8..0000000000 --- a/TGUIKit/TGUIKit/ScrollView.swift +++ /dev/null @@ -1,261 +0,0 @@ -// -// ScrollView.swift -// TGUIKit -// -// Created by keepcoder on 07/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import Foundation -public enum ScrollDirection { - case top; - case bottom; - case none; -} - - - -public struct ScrollPosition : Equatable { - public private(set) var rect:NSRect - public private(set) var visibleRows: NSRange - public private(set) var direction:ScrollDirection - public init(_ rect:NSRect = NSZeroRect, _ direction:ScrollDirection = .none, _ visibleRows: NSRange = NSMakeRange(NSNotFound, 0)) { - self.rect = rect - self.visibleRows = visibleRows - self.direction = direction - } -} - -public func ==(lhs:ScrollPosition, rhs:ScrollPosition) -> Bool { - return NSEqualRects(lhs.rect, rhs.rect) && lhs.direction == rhs.direction && NSEqualRanges(lhs.visibleRows, rhs.visibleRows) -} - -open class ScrollView: NSScrollView, CALayerDelegate{ - private var currentpos:ScrollPosition = ScrollPosition() - public var deltaCorner:Int64 = 60 - - public func scrollPosition(_ visibleRange: NSRange = NSMakeRange(NSNotFound, 0)) -> (current: ScrollPosition, previous: ScrollPosition) { - - let rect = NSMakeRect(contentView.bounds.minX, contentView.bounds.maxY,contentView.documentRect.width, contentView.documentRect.height) - - var d:ScrollDirection = .none - - - if abs(currentpos.rect.minY - rect.minY) < 5 { - return (currentpos, currentpos) - } - - if(currentpos.rect.minY > rect.minY) { - d = .top - } else if(currentpos.rect.minY < rect.minY) { - d = .bottom - } - - let n = ScrollPosition(rect, d, visibleRange) - let previous = currentpos - currentpos = n - return (n, previous) - } - - func updateScroll(_ visibleRange: NSRange = NSMakeRange(NSNotFound, 0)) -> Void { - self.currentpos = ScrollPosition(NSMakeRect(contentView.bounds.minX, contentView.bounds.maxY,contentView.documentRect.width, contentView.documentRect.height), .none, visibleRange) - } - - public var documentOffset:NSPoint { - return NSMakePoint(NSMinX(self.contentView.bounds), NSMinY(self.contentView.bounds)) - } - - public var documentSize:NSSize { - return self.contentView.documentRect.size; - } - - - open override func draw(_ dirtyRect: NSRect) { - - } - - open func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(presentation.colors.background.cgColor) - ctx.fill(bounds) - } - - public var clipView:TGClipView { - return self.contentView as! TGClipView - } - - - override public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - self.wantsLayer = true; - - self.layer?.delegate = self -// self.canDrawSubviewsIntoLayer = true -// self.layer?.drawsAsynchronously = System.drawAsync - - // self.contentView.wantsLayer = true - // self.contentView.layerContentsRedrawPolicy = .onSetNeedsDisplay - // self.contentView.layer?.drawsAsynchronously = System.drawAsync - - // self.layerContentsRedrawPolicy = .onSetNeedsDisplay; - // self.layer?.isOpaque = false - - let clipView = TGClipView(frame:self.contentView.frame) - self.contentView = clipView; - - - - self.horizontalScroller?.scrollerStyle = .overlay - self.verticalScroller?.scrollerStyle = .overlay - - // verticalScrollElasticity = .automatic - //allowsMagnification = true - //self.hasVerticalScroller = false - - // self.scrollerStyle = .overlay - - } - - open override var scrollerStyle: NSScroller.Style { - set { - super.scrollerStyle = .overlay - } - get { - return .overlay - } - } - - -// -// open override var hasVerticalScroller: Bool { -// get { -// return true -// } -// set { -// super.hasVerticalScroller = newValue -// } -// } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - assertOnMainThread() - } - - -// override open func scrollWheel(with event: NSEvent) { -// NSLog("\(event)") -// var scrollPoint = self.contentView.bounds.origin -// // var isInverted = CBool(UserDefaults.standard.object(forKey: "com.apple.swipescrolldirection")!) -//// if !isInverted { -//// scrollPoint.x += (event.scrollingDeltaY() + event.scrollingDeltaX()) -//// } -//// else { -// scrollPoint.y -= (event.scrollingDeltaY + event.scrollingDeltaY) -// // } -// self.clipView.scroll(to: scrollPoint) -// } - - let dynamic:CGFloat = 100.0 - - open override func scrollWheel(with event: NSEvent) { - - if deltaCorner > 0 { - var origin = clipView.bounds.origin - - deltaCorner = max(Int64(floorToScreenPixels(frame.height / 6.0)),40) - - - - let deltaScrollY = min(max(Int64(event.scrollingDeltaY),-deltaCorner),deltaCorner) - - - // NSLog("\(event.deltaY)") - - if let cgEvent = event.cgEvent?.copy() { - - - - // cgEvent.setDoubleValueField(.scrollWheelEventDeltaAxis1, value: Double(min(max(-4,event.deltaY),4))) - - - //if delta == deltaCorner || delta == -deltaCorner || delta == 0 { - cgEvent.setIntegerValueField(.scrollWheelEventScrollCount, value: min(1,cgEvent.getIntegerValueField(.scrollWheelEventScrollCount))) - // } - cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: deltaScrollY) - // if event.scrollingDeltaY > 0 { - // - // } else { - // cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: ) - // } - - // NSLog("\(cgEvent.getIntegerValueField(.scrollWheelEventScrollCount)) == \(delta)") - - // cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: 10) - // cgEvent.setIntegerValueField(.scrollWheelEventScrollCount, value: Int64(delta)) - - let newEvent = NSEvent(cgEvent: cgEvent)! - - super.scrollWheel(with: newEvent) - - - } else { - //NSLog("\(cgEvent.getIntegerValueField(.scrollWheelEventScrollCount))") - - super.scrollWheel(with: event) - } - - - if origin == clipView.bounds.origin, abs(deltaScrollY) >= deltaCorner - { - - if let documentView = documentView, !(self is HorizontalTableView) { - - if frame.minY < origin.y - frame.height - 50 { - if origin.y > documentView.frame.maxY + dynamic { - clipView.scroll(to: NSMakePoint(origin.x, documentView.frame.minY)) - } - - if origin.y < documentView.frame.height { - if documentView.isFlipped { - if origin.y < documentView.frame.height - (frame.height + frame.minY) { - origin.y -= CGFloat(deltaScrollY) - clipView.scroll(to: origin) - reflectScrolledClipView(clipView) - } - } else { - if origin.y + frame.height < documentView.frame.height { - origin.y += CGFloat(deltaScrollY) - clipView.scroll(to: origin) - reflectScrolledClipView(clipView) - } - - } - } - } else if origin.y < -dynamic { - clipView.scroll(to: NSMakePoint(origin.x, 0)) - } - } - } - } else { - super.scrollWheel(with: event) - } - } - - - public func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) -> Void { - super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - - public func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - public func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - - } - -} diff --git a/TGUIKit/TGUIKit/SearchView.swift b/TGUIKit/TGUIKit/SearchView.swift deleted file mode 100644 index 4ef249035e..0000000000 --- a/TGUIKit/TGUIKit/SearchView.swift +++ /dev/null @@ -1,435 +0,0 @@ -// -// SearchView.swift -// TGUIKit -// -// Created by keepcoder on 27/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - - - -class SearchTextField: NSTextView { - - - - override func resignFirstResponder() -> Bool { - self.delegate?.textDidEndEditing?(Notification(name: NSControl.textDidChangeNotification)) - return super.resignFirstResponder() - } - - override func becomeFirstResponder() -> Bool { - self.delegate?.textDidBeginEditing?(Notification(name: NSControl.textDidChangeNotification)) - return super.becomeFirstResponder() - } - -} - -public enum SearchFieldState { - case None; - case Focus; -} - -public struct SearchState : Equatable { - public let state:SearchFieldState - public let request:String - public init(state:SearchFieldState, request:String?) { - self.state = state - self.request = request ?? "" - } -} - -public func ==(lhs:SearchState, rhs:SearchState) -> Bool { - return lhs.state == rhs.state && lhs.request == rhs.request -} - -public final class SearchInteractions { - public let stateModified:(SearchState) -> Void - public let textModified:(SearchState) -> Void - - public init(_ state:@escaping(SearchState)->Void, _ text:@escaping(SearchState)->Void) { - stateModified = state - textModified = text - } -} - -open class SearchView: OverlayControl, NSTextViewDelegate { - - public private(set) var state:SearchFieldState = .None - - private(set) public var input:NSTextView = SearchTextField() - - private var lock:Bool = false - - private let clear:ImageButton = ImageButton() - private let search:ImageView = ImageView() - private let progressIndicator:ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 18, 18)) - private let placeholder:TextViewLabel = TextViewLabel() - - private let animateContainer:View = View() - - public let inset:CGFloat = 6 - public let leftInset:CGFloat = 10.0 - - public var searchInteractions:SearchInteractions? - - - - - public var isLoading:Bool = false { - didSet { - if oldValue != isLoading { - self.updateLoading() - needsLayout = true - } - } - } - - override open func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - - input.textColor = presentation.search.textColor - placeholder.attributedString = NSAttributedString.initialize(string: presentation.search.placeholder, color: presentation.search.placeholderColor, font: .normal(.text)) - placeholder.backgroundColor = presentation.search.backgroundColor - self.backgroundColor = presentation.search.backgroundColor - placeholder.sizeToFit() - search.frame = NSMakeRect(0, 0, presentation.search.searchImage.backingSize.width, presentation.search.searchImage.backingSize.height) - search.image = presentation.search.searchImage - animateContainer.setFrameSize(NSMakeSize(placeholder.frame.width + placeholderTextInset, max(placeholder.frame.height, search.frame.height))) - - clear.set(image: presentation.search.clearImage, for: .Normal) - clear.sizeToFit() - - placeholder.centerY(x: placeholderTextInset) - search.centerY() - input.insertionPointColor = presentation.search.textColor - - needsLayout = true - - } - - open var startTextInset: CGFloat { - return search.frame.width + inset - } - - open var placeholderTextInset: CGFloat { - return startTextInset - } - - required public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.backgroundColor = .grayBackground - self.layer?.cornerRadius = .cornerRadius - - progressIndicator.isHidden = true -// progressIndicator.numberOfLines = 8 -// progressIndicator.innerMargin = 3; -// progressIndicator.widthOfLine = 3; -// progressIndicator.lengthOfLine = 6; - // input.isBordered = false - // input.isBezeled = false - input.focusRingType = .none - input.frame = self.bounds - input.autoresizingMask = [.width, .height] - input.backgroundColor = NSColor.clear - input.delegate = self - input.isRichText = false - - input.textContainer?.widthTracksTextView = true - input.textContainer?.heightTracksTextView = false - - // input.maxSize = NSMakeSize(100, .greatestFiniteMagnitude) - input.isHorizontallyResizable = false - input.isVerticallyResizable = false - - - //input.placeholderAttributedString = NSAttributedString.initialize(string: localizedString("SearchField.Search"), color: .grayText, font: .normal(.text), coreText: false) - - input.font = .normal(.text) - input.textColor = .text - input.isHidden = true - - - animateContainer.backgroundColor = .clear - - placeholder.sizeToFit() - animateContainer.addSubview(placeholder) - - animateContainer.addSubview(search) - - self.animateContainer.setFrameSize(NSMakeSize(NSWidth(placeholder.frame) + NSWidth(search.frame) + inset, max(NSHeight(placeholder.frame), NSHeight(search.frame)))) - - placeholder.centerY(nil, x: NSWidth(search.frame) + inset) - search.centerY() - - addSubview(animateContainer) - addSubview(input) - - - clear.backgroundColor = .clear - - - clear.set(handler: { [weak self] _ in - self?.cancelSearch() - }, for: .Click) - - addSubview(clear) - - clear.isHidden = true - - animateContainer.center() - - self.set(handler: {[weak self] (event) in - if let strongSelf = self { - if strongSelf.isEmpty { - strongSelf.change(state:strongSelf.state == .None ? .Focus : .None,true) - } - } - }, for: .Click) - - updateLocalizationAndTheme() - } - - open func cancelSearch() { - change(state: .None, true) - } - - open func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { - if let replacementString = replacementString { - return !replacementString.contains("\n") && !replacementString.contains("\r") - } - return false - } - - - - open func textDidChange(_ notification: Notification) { - - if let searchInteractions = searchInteractions { - searchInteractions.textModified(SearchState(state: state, request: input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")))) - } - let pHidden = !input.string.isEmpty - if placeholder.isHidden != pHidden { - placeholder.isHidden = pHidden - } - let iHidden = !(state == .Focus && !input.string.isEmpty) - if input.isHidden != iHidden { - // input.isHidden = iHidden - window?.makeFirstResponder(input) - } - - } - - open func textDidEndEditing(_ notification: Notification) { - didResignResponder() - } - - open func textDidBeginEditing(_ notification: Notification) { - didBecomeResponder() - } - - open var isEmpty: Bool { - return query.isEmpty - } - - open func didResignResponder() { - if isEmpty { - change(state: .None, true) - } - self.kitWindow?.removeAllHandlers(for: self) - self.kitWindow?.removeObserver(for: self) - } - - open func didBecomeResponder() { - change(state: .Focus, true) - - self.kitWindow?.set(escape: {[weak self] () -> KeyHandlerResult in - if let strongSelf = self { - return strongSelf.changeResponder() ? .invoked : .rejected - } - return .rejected - - }, with: self, priority: .high) - - self.kitWindow?.set(responder: {[weak self] () -> NSResponder? in - return self?.input - }, with: self, priority: .high) - } - - - open override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - } - - - - open func change(state:SearchFieldState, _ animated:Bool) -> Void { - - if state != self.state && !lock { - self.state = state - - if let searchInteractions = searchInteractions { - let text = input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")) - searchInteractions.stateModified(SearchState(state: state, request: state == .None ? nil : text)) - } - - lock = true - - if state == .Focus { - - window?.makeFirstResponder(input) - - let inputInset = placeholderTextInset + 5 - - let fromX:CGFloat = animateContainer.frame.minX - animateContainer.centerY(x: leftInset) - - - self.input.frame = NSMakeRect(inputInset, NSMinY(self.animateContainer.frame), NSWidth(self.frame) - inputInset - inset - clear.frame.width, NSHeight(placeholder.frame)) - - if animated { - animateContainer.layer?.animate(from: fromX as NSNumber, to: leftInset as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion: true, additive: false, completion: {[weak self] (complete) in - self?.input.isHidden = false - self?.window?.makeFirstResponder(self?.input) - self?.lock = false - }) - } else { - self.input.isHidden = false - self.window?.makeFirstResponder(self.input) - self.lock = false - } - - - clear.isHidden = false - clear.layer?.opacity = 1.0 - if animated { - clear.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration) - } - } - - if state == .None { - - self.kitWindow?.removeAllHandlers(for: self) - self.kitWindow?.removeObserver(for: self) - - self.input.isHidden = true - self.input.string = "" - self.window?.makeFirstResponder(nil) - self.placeholder.isHidden = false - - animateContainer.center() - if animated { - animateContainer.layer?.animate(from: leftInset as NSNumber, to: NSMinX(animateContainer.frame) as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion: true) - - clear.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion:true, additive:false, completion: {[weak self] (complete) in - self?.clear.isHidden = true - self?.lock = false - }) - } else { - clear.isHidden = true - lock = false - } - - clear.layer?.opacity = 0.0 - } - self.needsLayout = true - } - - } - - open override func viewDidMoveToSuperview() { - guard let _ = superview else { - return - } - self.kitWindow?.removeAllHandlers(for: self) - self.kitWindow?.removeObserver(for: self) - } - - func updateLoading() { - if isLoading { - if progressIndicator.superview == nil { - addSubview(progressIndicator) - } - progressIndicator.isHidden = false - clear.isHidden = true - rightAccessory.isHidden = true - progressIndicator.animates = true - } else { - progressIndicator.animates = false - progressIndicator.removeFromSuperview() - progressIndicator.isHidden = true - clear.isHidden = self.state == .None || !clearVisibility - rightAccessory.isHidden = self.state == .None - } - if window?.firstResponder == input { - window?.makeFirstResponder(input) - } - } - private var clearVisibility: Bool = true - - public func updateClearVisibility(_ visible: Bool) { - clearVisibility = visible - clear.isHidden = !visible || isLoading - } - - open var rightAccessory: NSView { - return clear - } - - - open override func layout() { - super.layout() - switch state { - case .None: - animateContainer.center() - case .Focus: - animateContainer.centerY(x: leftInset) - } - clear.centerY(x: frame.width - inset - clear.frame.width) - progressIndicator.centerY(x: frame.width - inset - progressIndicator.frame.width) - - self.input.setFrameOrigin(placeholderTextInset + 5, animateContainer.frame.minY) - - } - - public func changeResponder(_ animated:Bool = true) -> Bool { - if state == .Focus { - cancelSearch() - } else { - change(state: .Focus, animated) - } - return true - } - - deinit { - self.kitWindow?.removeAllHandlers(for: self) - self.kitWindow?.removeObserver(for: self) - } - - public var query:String { - return self.input.string - } - - public override func change(size: NSSize, animated: Bool = true, _ save: Bool = true, removeOnCompletion: Bool = false, duration: Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion: ((Bool) -> Void)? = nil) { - super.change(size: size, animated: animated, save, duration: duration, timingFunction: timingFunction) - clear.change(pos: NSMakePoint(frame.width - inset - clear.frame.width, clear.frame.minY), animated: animated) - } - - - public func setString(_ string:String) { - self.input.string = string - textDidChange(Notification(name: NSText.didChangeNotification)) - needsLayout = true - } - - public func cancel(_ animated:Bool) -> Void { - change(state: .None, animated) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/SectionViewController.swift b/TGUIKit/TGUIKit/SectionViewController.swift deleted file mode 100644 index 868dc8e95f..0000000000 --- a/TGUIKit/TGUIKit/SectionViewController.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// SectionViewController.swift -// TGUIKit -// -// Created by keepcoder on 03/08/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -private final class SectionControllerArguments { - let select:(Int)->Void - init(select:@escaping(Int)->Void) { - self.select = select - } -} - -public class SectionControllerView : View { - private var header: View = View() - private let selector:View = View() - private let container: View = View() - private weak var current: ViewController? - - fileprivate var selectorIndex:Int = 0 { - didSet { - if selectorIndex != oldValue { - var index:Int = 0 - for hContainer in header.subviews { - for t in hContainer.subviews { - if let t = t as? TextView { - t.update(TextViewLayout(.initialize(string: t.layout?.attributedString.string, color: selectorIndex == index ? presentation.colors.blueUI : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle)) - } - - } - index += 1 - } - } - } - } - public required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(header) - addSubview(container) - addSubview(selector) - updateLocalizationAndTheme() - needsLayout = true - } - - fileprivate func layout(sections: [SectionControllerItem], selected: Int, arguments: SectionControllerArguments) { - header.removeAllSubviews() - self.selectorIndex = selected - for i in 0 ..< sections.count { - let section = sections[i] - let headerContainer = Control(frame: NSMakeRect(0, 0, 0, 50)) - let title: TextView = TextView() - title.isSelectable = false - title.userInteractionEnabled = false - title.update(TextViewLayout(.initialize(string: section.title, color: i == selected ? presentation.colors.blueUI : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle)) - headerContainer.addSubview(title) - header.addSubview(headerContainer) - headerContainer.border = [.Bottom] - headerContainer.set(handler: { _ in - arguments.select(i) - }, for: .Click) - } - needsLayout = true - } - - fileprivate func select(controller: ViewController, index: Int, animated: Bool) { - let previousIndex = self.selectorIndex - let previous = self.current - self.current = controller - selectorIndex = index - - controller.view.frame = container.bounds - previous?.viewWillDisappear(animated) - - container.addSubview(controller.view) - - if animated { - CATransaction.begin() - let container = header.subviews[index] - selector.change(pos: NSMakePoint(container.frame.minX, selector.frame.minY), animated: animated, timingFunction: kCAMediaTimingFunctionSpring) - - - let pto: NSPoint - let nfrom: NSPoint - - - if previousIndex < index { - pto = NSMakePoint(-container.frame.width, 0) - nfrom = NSMakePoint(container.frame.width, 0) - } else { - pto = NSMakePoint(container.frame.width, 0) - nfrom = NSMakePoint(-container.frame.width, 0) - } - - previous?.view._change(pos: pto, animated: animated, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak previous] complete in - if complete { - previous?.view.removeFromSuperview() - previous?.viewDidDisappear(animated) - } - }) - controller.view.layer?.animatePosition(from: nfrom, to: NSZeroPoint, timingFunction: kCAMediaTimingFunctionSpring) - CATransaction.commit() - } else { - container.removeAllSubviews() - previous?.viewDidDisappear(animated) - container.addSubview(controller.view) - controller.viewDidAppear(animated) - } - needsLayout = true - } - - public override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - self.backgroundColor = presentation.colors.background - selector.backgroundColor = presentation.colors.blueUI - container.backgroundColor = presentation.colors.background - var index:Int = 0 - for hContainer in header.subviews { - hContainer.background = presentation.colors.background - for t in hContainer.subviews { - if let t = t as? TextView { - t.update(TextViewLayout(.initialize(string: t.layout?.attributedString.string, color: selectorIndex == index ? presentation.colors.blueUI : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle)) - } - - t.background = presentation.colors.background - } - index += 1 - } - } - - public override func layout() { - super.layout() - header.setFrameSize(NSMakeSize(frame.width, 50)) - let width = floorToScreenPixels(frame.width / CGFloat(max(header.subviews.count, 3))) - - selector.frame = NSMakeRect(CGFloat(selectorIndex) * width, 50 - .borderSize, width, .borderSize) - container.frame = NSMakeRect(0, header.frame.maxY, frame.width, frame.height - header.frame.height) - container.subviews.first?.frame = container.bounds - - var x:CGFloat = 0 - for i in 0 ..< header.subviews.count { - let hContainer = header.subviews[i] - let width = i == header.subviews.count - 1 ? frame.width - x : width - hContainer.frame = NSMakeRect(x, 0, width, hContainer.frame.height) - if let textView = hContainer.subviews.first as? TextView { - textView.layout?.measure(width: width - 10) - textView.update(textView.layout) - textView.center() - } - x += width - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -public class SectionControllerItem { - let title: String - let controller: ViewController - public init(title: String, controller: ViewController) { - self.title = title - self.controller = controller - } -} - - -public class SectionViewController: GenericViewController { - - private var sections:[SectionControllerItem] = [] - public var selectedSection:SectionControllerItem - public private(set) var selectedIndex: Int = -1 - private let disposable = MetaDisposable() - - public var selectionUpdateHandler:((Int)->Void)? - - public func addSection(_ section: SectionControllerItem) { - sections.append(section) - } - - deinit { - disposable.dispose() - } - - fileprivate func select(_ index:Int, _ animated: Bool) { - if selectedIndex != index || !animated { - selectedSection = sections[index] - sections[index].controller._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) - sections[index].controller.loadViewIfNeeded() - let controller = sections[index].controller - selectedIndex = index - selectionUpdateHandler?(index) - sections[index].controller.viewWillAppear(animated) - disposable.set((sections[index].controller.ready.get() |> filter {$0} |> take(1)).start(next: { [weak self, weak controller] ready in - if let strongSelf = self, let controller = controller { - strongSelf.genericView.select(controller: controller, index: index, animated: animated) - } - })) - } - } - override public func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - selectedSection.controller._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) - selectedSection.controller.viewWillAppear(animated) - self.ready.set(sections[selectedIndex].controller.ready.get()) - } - override public func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - selectedSection.controller.viewWillDisappear(animated) - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - selectedSection.controller.viewDidAppear(animated) - } - override public func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - selectedSection.controller.viewDidDisappear(animated) - } - - public override func viewDidLoad() { - super.viewDidLoad() - - let arguments = SectionControllerArguments { [weak self] index in - self?.select(index, true) - } - genericView.layout(sections: sections, selected: selectedIndex, arguments: arguments) - select(selectedIndex, false) - } - - public init(sections: [SectionControllerItem], selected: Int = 0) { - assert(!sections.isEmpty) - self.sections = sections - self.selectedSection = sections[selected] - self.selectedIndex = selected - super.init() - bar = .init(height: 0) - } - -} diff --git a/TGUIKit/TGUIKit/SegmentedControl.swift b/TGUIKit/TGUIKit/SegmentedControl.swift deleted file mode 100644 index d3002c5012..0000000000 --- a/TGUIKit/TGUIKit/SegmentedControl.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SegmentedControl.swift -// TGUIKit -// -// Created by keepcoder on 01.06.17. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa - -public class SegmentedItem { - let title:String - init(title:String) { - self.title = title - } -} - -public class SegmentedControl: View { - - - - override public func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - // Drawing code here. - } - - public func addItem() { - - } - -} diff --git a/TGUIKit/TGUIKit/ShadowView.swift b/TGUIKit/TGUIKit/ShadowView.swift deleted file mode 100644 index 3f7b3f7c8e..0000000000 --- a/TGUIKit/TGUIKit/ShadowView.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ShadowView.swift -// TGUIKit -// -// Created by keepcoder on 02/08/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa - -public class ShadowView: View { - - override public func draw(_ layer: CALayer, in ctx: CGContext) { - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [backgroundColor.withAlphaComponent(0).cgColor, backgroundColor.cgColor]), locations: nil)! - - ctx.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: layer.bounds.height), options: CGGradientDrawingOptions()) - } - -} diff --git a/TGUIKit/TGUIKit/TGColor.swift b/TGUIKit/TGUIKit/TGColor.swift deleted file mode 100644 index 6ee56afd31..0000000000 --- a/TGUIKit/TGUIKit/TGColor.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// Color.swift -// TGUIKit -// -// Created by keepcoder on 06/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Foundation - -public extension NSColor { - - public static func colorFromRGB(rgbValue:UInt32) ->NSColor { - return NSColor.init(deviceRed: ((CGFloat)((rgbValue & 0xFF0000) >> 16))/255.0, green: ((CGFloat)((rgbValue & 0xFF00) >> 8))/255.0, blue: ((CGFloat)(rgbValue & 0xFF))/255.0, alpha: 1.0) - } - - public static func colorFromRGB(rgbValue:UInt32, alpha:CGFloat) ->NSColor { - return NSColor.init(deviceRed: ((CGFloat)((rgbValue & 0xFF0000) >> 16))/255.0, green: ((CGFloat)((rgbValue & 0xFF00) >> 8))/255.0, blue: ((CGFloat)(rgbValue & 0xFF))/255.0, alpha:alpha) - } - - - public static var link:NSColor { - return .colorFromRGB(rgbValue: 0x2481cc) - } - - public static var blueUI:NSColor { - return .colorFromRGB(rgbValue: 0x2481cc) - } - - public static var redUI:NSColor { - return colorFromRGB(rgbValue: 0xff3b30) - } - - public static var greenUI:NSColor { - return colorFromRGB(rgbValue: 0x63DA6E) - } - - public static var blackTransparent:NSColor { - return colorFromRGB(rgbValue: 0x000000, alpha: 0.6) - } - - public static var grayTransparent:NSColor { - return colorFromRGB(rgbValue: 0xf4f4f4, alpha: 0.4) - } - - public static var grayUI:NSColor { - return colorFromRGB(rgbValue: 0xFaFaFa, alpha: 1.0) - } - - public static var darkGrayText:NSColor { - return NSColor(0x333333) - } - - public static var text:NSColor { - return NSColor.black - } - - - public static var blueText:NSColor { - get { - return colorFromRGB(rgbValue: 0x4ba3e2) - } - } - - public static var blueSelect:NSColor { - get { - return colorFromRGB(rgbValue: 0x4c91c7) - } - } - - - public static var selectText:NSColor { - get { - return colorFromRGB(rgbValue: 0xeaeaea, alpha:1.0) - } - } - - public static var random:NSColor { - get { - return colorFromRGB(rgbValue: arc4random_uniform(16000000)) - } - } - - public static var blueFill:NSColor { - get { - return colorFromRGB(rgbValue: 0x4ba3e2) - } - } - - - public static var border:NSColor { - get { - return colorFromRGB(rgbValue: 0xeaeaea) - } - } - - - - public static var grayBackground:NSColor { - get { - return colorFromRGB(rgbValue: 0xf4f4f4) - } - } - - public static var grayForeground:NSColor { - get { - return colorFromRGB(rgbValue: 0xe4e4e4) - } - } - - - - public static var grayIcon:NSColor { - get { - return colorFromRGB(rgbValue: 0x9e9e9e) - } - } - - - public static var blueIcon:NSColor { - get { - return colorFromRGB(rgbValue: 0x0f8fe4) - } - } - - public static var badgeMuted:NSColor { - get { - return colorFromRGB(rgbValue: 0xd7d7d7) - } - } - - public static var badge:NSColor { - get { - return .blueFill - } - } - - public static var grayText:NSColor { - get { - return colorFromRGB(rgbValue: 0x999999) - } - } -} - -public extension NSColor { - convenience init(rgb: UInt32) { - self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) - } - - convenience init(rgb: UInt32, alpha: CGFloat) { - self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: alpha) - } - - convenience init(argb: UInt32) { - self.init(red: CGFloat((argb >> 16) & 0xff) / 255.0, green: CGFloat((argb >> 8) & 0xff) / 255.0, blue: CGFloat(argb & 0xff) / 255.0, alpha: CGFloat((argb >> 24) & 0xff) / 255.0) - } - - var argb: UInt32 { - - let color = self.usingColorSpaceName(NSColorSpaceName.calibratedRGB)! - var red: CGFloat = 0.0 - var green: CGFloat = 0.0 - var blue: CGFloat = 0.0 - var alpha: CGFloat = 0.0 - color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - - return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) - } - - var rgb: UInt32 { - - let color = self.usingColorSpaceName(NSColorSpaceName.calibratedRGB) - if let color = color { - let red: CGFloat = color.redComponent - let green: CGFloat = color.greenComponent - let blue: CGFloat = color.blueComponent - - return (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) - } - return 0x000000 - } -} - -public extension CGFloat { - - - public static var cornerRadius:CGFloat { - return 5 - } - - public static var borderSize:CGFloat { - get { - return 1 - } - } - - - -} - - diff --git a/TGUIKit/TGUIKit/TGFont.swift b/TGUIKit/TGUIKit/TGFont.swift deleted file mode 100644 index e904734ccc..0000000000 --- a/TGUIKit/TGUIKit/TGFont.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// TGFont.swift -// TGUIKit -// -// Created by keepcoder on 07/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - -public func systemFont(_ size:CGFloat) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.regular) - } else { - return NSFont.init(name: "HelveticaNeue", size: size)! - } -} - -public func systemMediumFont(_ size:CGFloat) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.semibold) - } else { - return NSFont.init(name: "HelveticaNeue-Medium", size: size)! - } - -} - -public func systemBoldFont(_ size:CGFloat) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.bold) - } else { - return NSFont.init(name: "HelveticaNeue-Bold", size: size)! - } -} - -public extension NSFont { - public static func normal(_ size:FontSize) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: convert(from:size), weight: NSFont.Weight.regular) - } else { - return NSFont(name: "HelveticaNeue", size: convert(from:size))! - } - } - - public static func italic(_ size: FontSize) -> NSFont { - return NSFontManager.shared.convert(.normal(size), toHaveTrait: .italicFontMask) - } - - public static func avatar(_ size: FontSize) -> NSFont { - - if let font = NSFont(name: ".SFCompactRounded-Semibold", size: convert(from:size)) { - return font - } else { - return .medium(size) - } - } - - public static func medium(_ size:FontSize) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: convert(from:size), weight: NSFont.Weight.medium) - } else { - return NSFont(name: "HelveticaNeue-Medium", size: convert(from:size))! - } - - } - - public static func bold(_ size:FontSize) ->NSFont { - - if #available(OSX 10.11, *) { - return NSFont.systemFont(ofSize: convert(from:size), weight: NSFont.Weight.bold) - } else { - return NSFont(name: "HelveticaNeue-Bold", size: convert(from:size))! - } - } - - public static func code(_ size:FontSize) ->NSFont { - return NSFont(name: "Menlo-Regular", size: convert(from:size)) ?? NSFont.systemFont(ofSize: 17.0) - } -} - -public enum FontSize { - case small - case short - case text - case title - case header - case huge - case custom(CGFloat) -} - -fileprivate func convert(from s:FontSize) -> CGFloat { - switch s { - case .small: - return 11.0 - case .short: - return 12.0 - case .text: - return 13.0 - case .title: - return 14.0 - case .header: - return 15.0 - case .huge: - return 18.0 - case let .custom(size): - return size - } -} - - - -public struct TGFont { - - public static var shortSize:CGFloat { - return 12 - } - - public static var textSize:CGFloat { - return 13 - } - - public static var headerSize:CGFloat { - return 15 - } - - public static var titleSize:CGFloat { - return 14 - } - - public static var hugeSize:CGFloat { - return 18 - } - -} diff --git a/TGUIKit/TGUIKit/TGSplitView.swift b/TGUIKit/TGUIKit/TGSplitView.swift deleted file mode 100644 index e55816706f..0000000000 --- a/TGUIKit/TGUIKit/TGSplitView.swift +++ /dev/null @@ -1,386 +0,0 @@ -// -// SplitView.swift -// TGUIKit -// -// Created by keepcoder on 06/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Foundation -import SwiftSignalKitMac -fileprivate class SplitMinimisizeView : Control { - - private var startPoint:NSPoint = NSZeroPoint - weak var splitView:SplitView? - override init() { - super.init() - userInteractionEnabled = false - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - fileprivate override func mouseMoved(with event: NSEvent) { - super.mouseMoved(with: event) - checkCursor() - } - - func checkCursor() { - if let splitView = splitView { - if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize { - if mouseInside() { - if splitView.state == .minimisize { - NSCursor.resizeRight.set() - } else { - NSCursor.resizeLeft.set() - } - } else { - NSCursor.arrow.set() - } - } - } - } - - fileprivate override func mouseEntered(with event: NSEvent) { - super.mouseEntered(with: event) - checkCursor() - } - - - - fileprivate override func mouseExited(with event: NSEvent) { - super.mouseExited(with: event) - checkCursor() - } - - fileprivate override func mouseDragged(with event: NSEvent) { - super.mouseDragged(with: event) - - if let splitView = splitView { - if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize { - if splitView.state == .minimisize { - NSCursor.resizeRight.set() - } else { - NSCursor.resizeLeft.set() - } - - let current = splitView.convert(event.locationInWindow, from: nil) - - - if startPoint.x - current.x >= 100, splitView.state != .minimisize { - splitView.needMinimisize() - startPoint = current - } else if current.x - startPoint.x >= 100, splitView.state == .minimisize { - splitView.needFullsize() - startPoint = current - } - } - - } - } - - fileprivate override func mouseUp(with event: NSEvent) { - startPoint = NSZeroPoint - } - - fileprivate override func mouseDown(with event: NSEvent) { - if let splitView = splitView { - startPoint = splitView.convert(event.locationInWindow, from: nil) - } - } - - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - if let splitView = splitView { - if let drawBorder = splitView.delegate?.splitViewDrawBorder(), drawBorder { - ctx.setFillColor(presentation.colors.border.cgColor) - ctx.fill(NSMakeRect(floorToScreenPixels(frame.width / 2), 0, .borderSize, frame.height)) - } - } - } -} - -public struct SplitProportion { - var min:CGFloat = 0; - var max:CGFloat = 0; - - public init(min:CGFloat, max:CGFloat) { - self.min = min; - self.max = max; - } -} - -public enum SplitViewState : Int { - case none = -1; - case single = 0; - case dual = 1; - case triple = 2; - case minimisize = 3 -} - - -public protocol SplitViewDelegate : class { - func splitViewDidNeedSwapToLayout(state:SplitViewState) -> Void - func splitViewDidNeedMinimisize(controller:ViewController) -> Void - func splitViewDidNeedFullsize(controller:ViewController) -> Void - func splitViewIsCanMinimisize() -> Bool - func splitViewDrawBorder() -> Bool -} - - -public class SplitView : View { - - private let minimisizeOverlay:SplitMinimisizeView = SplitMinimisizeView() - private let container:View - private var forceNotice:Bool = false - public var state: SplitViewState = .none { - didSet { - let notify:Bool = state != oldValue; - assert(notify); - if(notify) { - self.delegate?.splitViewDidNeedSwapToLayout(state: state); - } - } - } - - - public var canChangeState:Bool = true; - public weak var delegate:SplitViewDelegate? - - - private var _proportions:[Int:SplitProportion] = [Int:SplitProportion]() - private var _startSize:[Int:NSSize] = [Int:NSSize]() - private var _controllers:[ViewController] = [ViewController]() - private var _issingle:Bool? - private var _layoutProportions:[SplitViewState:SplitProportion] = [SplitViewState:SplitProportion]() - - private var _splitIdx:Int? - - - public override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - } - - required public init(frame frameRect: NSRect) { - container = View(frame: NSMakeRect(0,0,frameRect.width, frameRect.height)) - super.init(frame: frameRect); - self.autoresizingMask = [.width, .height] - self.autoresizesSubviews = true - container.autoresizesSubviews = false - container.autoresizingMask = [.width, .height] - addSubview(container) - minimisizeOverlay.splitView = self - - } - - public override var backgroundColor: NSColor { - didSet { - container.backgroundColor = backgroundColor - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - public func addController(controller:ViewController, proportion:SplitProportion) ->Void { - controller._frameRect = NSMakeRect(0, 0, proportion.min, frame.height) - container.addSubview(controller.view); - controller.viewWillAppear(false) - _controllers.append(controller); - _startSize.updateValue(controller.view.frame.size, forKey: controller.internalId); - _proportions.updateValue(proportion, forKey: controller.internalId) - controller.viewDidAppear(false) - } - - func removeController(controller:ViewController) -> Void { - - controller.viewWillDisappear(false) - let idx = _controllers.index(of: controller)!; - container.subviews[idx].removeFromSuperview(); - _controllers.remove(at: idx); - _startSize.removeValue(forKey: controller.internalId); - _proportions.removeValue(forKey: controller.internalId); - - controller.viewDidDisappear(false) - } - - public func removeAllControllers() -> Void { - - var copy:[ViewController] = [] - - - for controller in _controllers { - copy.append(controller) - } - - for controller in copy { - controller.viewWillDisappear(false) - } - - container.removeAllSubviews(); - _controllers.removeAll(); - _startSize.removeAll(); - _proportions.removeAll(); - - for controller in copy { - controller.viewDidDisappear(false) - } - } - - public func setProportion(proportion:SplitProportion, state:SplitViewState) -> Void { - _layoutProportions[state] = proportion; - } - - public func removeProportion(state:SplitViewState) -> Void { - _layoutProportions.removeValue(forKey: state); - if(_controllers.count > state.rawValue) { - _controllers.remove(at: state.rawValue) - } - } - - public func updateStartSize(size:NSSize, controller:ViewController) -> Void { - _startSize[controller.internalId] = size; - - _proportions[controller.internalId] = SplitProportion(min:size.width, max:size.height); - - update(); - - } - - public func update(_ forceNotice:Bool = false) -> Void { - Queue.mainQueue().justDispatch { - self.forceNotice = forceNotice - self.needsLayout = true - } - } - - public override func layout() { - super.layout() - - //assert(!_controllers.isEmpty) - - let single:SplitProportion! = _layoutProportions[.single] - let dual:SplitProportion! = _layoutProportions[.dual] - let triple:SplitProportion! = _layoutProportions[.triple] - - - - if acceptLayout(prop: single) && canChangeState && state != .minimisize { - if frame.width < single.max { - if self.state != .single { - self.state = .single; - } - } else if acceptLayout(prop: dual) { - if acceptLayout(prop: triple) { - if frame.width >= dual.min && frame.width <= dual.max { - if state != .dual { - state = .dual; - } - } else if state != .triple { - self.state = .triple; - } - } else { - if state != .dual && frame.width >= dual.min { - self.state = .dual; - } - } - - } - } - - if forceNotice { - forceNotice = false - self.delegate?.splitViewDidNeedSwapToLayout(state: state) - } - - var x:CGFloat = 0; - - for (index, obj) in _controllers.enumerated() { - - let proportion:SplitProportion = _proportions[obj.internalId]!; - let startSize:NSSize = _startSize[obj.internalId]!; - var size:NSSize = NSMakeSize(x, frame.height); - var min:CGFloat = startSize.width; - - - - min = proportion.min; - - if(proportion.max == CGFloat.greatestFiniteMagnitude && index != _controllers.count-1) { - - var m2:CGFloat = 0; - - for i:Int in index + 1 ..< _controllers.count - index { - - let split:ViewController = _controllers[i]; - - let proportion:SplitProportion = _proportions[split.internalId]!; - - m2+=proportion.min; - } - - min = frame.width - x - m2; - - } - - - if(index == _controllers.count - 1) { - min = frame.width - x; - } - - size = NSMakeSize(x + min > frame.width ? (frame.width - x) : min, frame.height); - - let rect:NSRect = NSMakeRect(x, 0, size.width, size.height); - - if(!NSEqualRects(rect, obj.view.frame)) { - obj.view.frame = rect; - } - - x+=size.width; - - } - - //assert(state != .none) - if state != .none { - if state == .dual || state == .minimisize { - if let first = container.subviews.first { - if minimisizeOverlay.superview == nil { - addSubview(minimisizeOverlay) - } - minimisizeOverlay.frame = NSMakeRect(first.frame.maxX - 5, 0, 10, frame.height) - } - - } else { - minimisizeOverlay.removeFromSuperview() - } - } - - } - - - - public func needFullsize() { - self.state = .none - self.needsLayout = true - } - - public func needMinimisize() { - self.state = .minimisize - self.needsLayout = true - - } - - - func acceptLayout(prop:SplitProportion!) -> Bool { - return prop != nil ? (prop!.min > 0 && prop!.max > 0) : false; - } - -} diff --git a/TGUIKit/TGUIKit/TabBarController.swift b/TGUIKit/TGUIKit/TabBarController.swift deleted file mode 100644 index eb140a5986..0000000000 --- a/TGUIKit/TGUIKit/TabBarController.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// TabBarController.swift -// TGUIKit -// -// Created by keepcoder on 27/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -private class TabBarViewController : View { - let tabView:TabBarView - - - required init(frame frameRect: NSRect) { - tabView = TabBarView(frame: NSMakeRect(0, frameRect.height - 50, frameRect.width, 50)) - super.init(frame: frameRect) - addSubview(tabView) - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - self.background = presentation.colors.background - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - } - - override func layout() { - super.layout() - tabView.frame = NSMakeRect(0, frame.height - 50, frame.width, 50) - } -} - -public class TabBarController: ViewController, TabViewDelegate { - - - public var didChangedIndex:(Int)->Void = {_ in} - - public weak var current:ViewController? { - didSet { - current?.navigationController = self.navigationController - } - } - - private var genericView:TabBarViewController { - return view as! TabBarViewController - } - - public override func viewClass() -> AnyClass { - return TabBarViewController.self - } - - public override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - genericView.tabView.enumerateItems({ item in - if item.controller.isLoaded() { - item.controller.updateLocalizationAndTheme() - } - return false - }) - } - - public override func loadView() { - super.loadView() - genericView.tabView.delegate = self - genericView.autoresizingMask = [] - } - - public func didChange(selected item: TabItem, index: Int) { - - if current != item.controller { - if let current = current { - current.window?.makeFirstResponder(nil) - current.viewWillDisappear(false) - current.view.removeFromSuperview() - current.viewDidDisappear(false) - } - item.controller._frameRect = NSMakeRect(0, 0, bounds.width, bounds.height - genericView.tabView.frame.height) - item.controller.view.frame = item.controller._frameRect - item.controller.viewWillAppear(false) - view.addSubview(item.controller.view) - item.controller.viewDidAppear(false) - current = item.controller - didChangedIndex(index) - } - } - - public override func scrollup() { - current?.scrollup() - } - - public func hideTabView(_ hide:Bool) { - genericView.tabView.isHidden = hide - current?.view.frame = hide ? bounds : NSMakeRect(0, 0, bounds.width, bounds.height - genericView.tabView.frame.height) - - } - - public func select(index:Int) -> Void { - genericView.tabView.setSelectedIndex(index, respondToDelegate: true) - } - - public func add(tab:TabItem) -> Void { - genericView.tabView.addTab(tab) - } - public func tab(at index:Int) -> TabItem { - return genericView.tabView.tab(at: index) - } - public func replace(tab: TabItem, at index:Int) -> Void { - genericView.tabView.replaceTab(tab, at: index) - } - public var isEmpty:Bool { - return genericView.tabView.isEmpty - } - -} diff --git a/TGUIKit/TGUIKit/TabBarView.swift b/TGUIKit/TGUIKit/TabBarView.swift deleted file mode 100644 index 0988e92b86..0000000000 --- a/TGUIKit/TGUIKit/TabBarView.swift +++ /dev/null @@ -1,194 +0,0 @@ -// -// TabBarView.swift -// TGUIKit -// -// Created by keepcoder on 27/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - -public protocol TabViewDelegate : class { - func didChange(selected item:TabItem, index:Int) - func scrollup() -} - -public class TabBarView: View { - - private var tabs:[TabItem] = [] - public private(set) var selectedIndex:Int = 0 - - public weak var delegate:TabViewDelegate? - - - required public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - autoresizesSubviews = false - autoresizingMask = [] - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - ctx.setFillColor(presentation.colors.border.cgColor) - ctx.fill(self.bounds) - } - - - - func addTab(_ tab: TabItem) { - self.tabs.append(tab) - self.redraw() - } - func replaceTab(_ tab: TabItem, at index:Int) { - self.tabs.remove(at: index) - self.tabs.insert(tab, at: index) - self.redraw() - } - - func insertTab(_ tab: TabItem, at index: Int) { - self.tabs.insert(tab, at: index) - self.redraw() - } - - func removeTab(_ tab: TabItem) { - self.tabs.remove(at: self.tabs.index(of: tab)!) - self.redraw() - } - - func enumerateItems(_ f:(TabItem)->Bool) { - for item in tabs { - if f(item) { - break - } - } - } - - func removeTab(at index: Int) { - self.tabs.remove(at: index) - self.redraw() - } - public var isEmpty:Bool { - return tabs.isEmpty - } - public func tab(at index:Int) -> TabItem { - return self.tabs[index] - } - - func redraw() { - let width = NSWidth(self.bounds) - let height = NSHeight(self.bounds) - .borderSize - let defWidth = width / CGFloat(self.tabs.count) - self.removeAllSubviews() - var xOffset:CGFloat = 0 - - - for i in 0 ..< tabs.count { - let tab = tabs[i] - let itemWidth = defWidth - let view = Control(frame: NSMakeRect(xOffset, .borderSize, itemWidth, height)) - view.backgroundColor = presentation.colors.background - let container = View(frame: view.bounds) - view.set(handler: { [weak tab] control in - tab?.longHoverHandler?(control) - }, for: .LongOver) - - view.set(handler: { [weak self] control in - if let strongSelf = self { - if strongSelf.selectedIndex == i { - strongSelf.delegate?.scrollup() - } else { - strongSelf.setSelectedIndex(i, respondToDelegate:true) - } - } - }, for: .Click) - view.autoresizingMask = [.minXMargin, .maxXMargin, .width] - view.autoresizesSubviews = true - let imageView = ImageView(frame: NSMakeRect(0, 0, tab.image.backingSize.width, tab.image.backingSize.height)) - imageView.image = tab.image - container.addSubview(imageView) - container.backgroundColor = presentation.colors.background - container.setFrameSize(NSMakeSize(NSWidth(imageView.frame), NSHeight(container.frame))) - view.addSubview(container) - - if let subView = tab.subNode?.view { - view.addSubview(subView) - } - - imageView.center() - container.center() - - self.addSubview(view) - xOffset += itemWidth - } - - self.setSelectedIndex(self.selectedIndex, respondToDelegate: false) - setFrameSize(frame.size) - } - - public override func updateLocalizationAndTheme() { - for subview in subviews { - subview.background = presentation.colors.background - for container in subview.subviews { - container.background = presentation.colors.background - } - } - self.backgroundColor = presentation.colors.background - needsDisplay = true - super.updateLocalizationAndTheme() - } - - - override public func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - // if previous != newSize.width { - let width = NSWidth(self.bounds) - let height = NSHeight(self.bounds) - .borderSize - let defWidth = floorToScreenPixels(width / CGFloat( max(1, self.tabs.count) )) - var xOffset:CGFloat = 0 - - var idx:Int = 0 - - for subview in subviews { - let w = idx == subviews.count - 1 ? defWidth - .borderSize : defWidth - let child = subview.subviews[0] - subview.frame = NSMakeRect(xOffset, .borderSize, w, height) - child.center() - xOffset += w - - idx += 1 - } - // } - } - - - public func setSelectedIndex(_ selectedIndex: Int, respondToDelegate: Bool) { - if selectedIndex > self.tabs.count || self.tabs.count == 0 { - return - } - let deselectItem = self.tabs[self.selectedIndex] - let deselectView = self.subviews[self.selectedIndex] - - var image:ImageView = deselectView.subviews[0].subviews[0] as! ImageView - image.image = deselectItem.image - self.selectedIndex = selectedIndex - let selectItem = self.tabs[self.selectedIndex] - let selectView = self.subviews[self.selectedIndex] - - image = selectView.subviews[0].subviews[0] as! ImageView - image.image = selectItem.selectedImage - if respondToDelegate { - self.delegate?.didChange(selected: selectItem, index: selectedIndex) - } - - } - - - - -} diff --git a/TGUIKit/TGUIKit/TabItem.swift b/TGUIKit/TGUIKit/TabItem.swift deleted file mode 100644 index 088f7072ca..0000000000 --- a/TGUIKit/TGUIKit/TabItem.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// TabItem.swift -// TGUIKit -// -// Created by keepcoder on 27/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - - - -open class TabItem: NSObject { - let image: CGImage - let selectedImage: CGImage - let controller:ViewController - let subNode:Node? - let longHoverHandler:((Control)->Void)? - public init(image: CGImage, selectedImage: CGImage, controller:ViewController, subNode:Node? = nil, longHoverHandler:((Control)->Void)? = nil) { - self.image = image - self.longHoverHandler = longHoverHandler - self.selectedImage = selectedImage - self.controller = controller - self.subNode = subNode - super.init() - } - - public func withUpdatedImages(_ image: CGImage, _ selectedImage: CGImage) -> TabItem { - return TabItem(image: image, selectedImage: selectedImage, controller: self.controller, subNode: self.subNode, longHoverHandler: self.longHoverHandler) - } -} diff --git a/TGUIKit/TGUIKit/TableAnimationInterface.swift b/TGUIKit/TGUIKit/TableAnimationInterface.swift deleted file mode 100644 index 21c9f40b9d..0000000000 --- a/TGUIKit/TGUIKit/TableAnimationInterface.swift +++ /dev/null @@ -1,106 +0,0 @@ - -// -// TableAnimationInterface.swift -// TGUIKit -// -// Created by keepcoder on 02/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -open class TableAnimationInterface: NSObject { - - public let scrollBelow:Bool - public let saveIfAbove:Bool - public init(_ scrollBelow:Bool = true, _ saveIfAbove:Bool = true) { - self.scrollBelow = scrollBelow - self.saveIfAbove = saveIfAbove - } - - public func animate(table:TableView, added:[TableRowItem], removed:[TableRowItem]) -> Void { - - var height:CGFloat = 0 - - for item in added { - height += item.height - } - - for item in removed { - height -= item.height - } - - if added.isEmpty && removed.isEmpty { - return - } - - let contentView = table.contentView - let bounds = contentView.bounds - - let scrollBelow = self.scrollBelow || (bounds.minY - height) < 0 - - - - if bounds.minY > height, scrollBelow { -// height = bounds.minY -// -// let presentation = contentView.layer?.presentation() -// if let presentation = presentation, contentView.layer?.animation(forKey:"bounds") != nil { -// height += presentation.bounds.minY -// } -// - - table.scroll(to: .down(true)) - - - - } else if height - bounds.height < table.frame.height, scrollBelow { - - if scrollBelow { - contentView.bounds = NSMakeRect(0, 0, contentView.bounds.width, contentView.bounds.height) - } - - let range:NSRange = table.visibleRows(height) - - for item in added { - if item.index < range.location || item.index > range.location + range.length { - return - } - } - - CATransaction.begin() - for idx in range.location ..< range.length { - - if let view = table.viewNecessary(at: idx), let layer = view.layer { - - var inset = (layer.frame.minY - height); - if let presentLayer = layer.presentation(), presentLayer.animation(forKey: "position") != nil { - inset = presentLayer.position.y - } - layer.animatePosition(from: NSMakePoint(0, inset), to: NSMakePoint(0, layer.position.y), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseOut) - - for item in added { - if item.index == idx { - layer.animateAlpha(from: 0, to: 1, duration: 0.2) - } - } - - } - - } - - CATransaction.commit() - - } else if !scrollBelow { - contentView.bounds = NSMakeRect(0, bounds.minY + height, contentView.bounds.width, contentView.bounds.height) - table.reflectScrolledClipView(contentView) - } - - } - - public func scroll(table:TableView, from:NSRect, to:NSRect) -> Void { - table.contentView.layer?.animateBounds(from: from, to: to, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseOut) - } - - -} diff --git a/TGUIKit/TGUIKit/TableRowItem.swift b/TGUIKit/TGUIKit/TableRowItem.swift deleted file mode 100644 index f43eb995dc..0000000000 --- a/TGUIKit/TGUIKit/TableRowItem.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// TableRowItem.swift -// TGUIKit -// -// Created by keepcoder on 07/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - -open class TableRowItem: NSObject { - public weak var table:TableView? { - didSet { - tableViewDidUpdated() - } - } - public let initialSize:NSSize - - open func tableViewDidUpdated() { - - } - - open var animatable:Bool { - return true - } - - open var instantlyResize:Bool { - return false - } - - open private(set) var height:CGFloat = 60; - - - public var size:NSSize { - return NSMakeSize(width, height) - } - - public var oldWidth:CGFloat = 0 - - public var width:CGFloat { - if let table = table { - return table.frame.width - } else { - return initialSize.width - } - } - - open var stableId:AnyHashable { - return 0 - } - - public var index:Int { - get { - if let table = table, let index = table.index(of:self) { - return index - } else { - return -1 - } - } - - } - - public init(_ initialSize:NSSize) { - self.initialSize = initialSize - } - - open func prepare(_ selected:Bool) { - - } - - open var isVisible: Bool { - if let table = table { - let visible = table.visibleRows() - return visible.indexIn(index) - } - return false - } - - - open func menuItems() -> Signal<[ContextMenuItem], Void> { - return .single([]) - } - - public func redraw()->Void { - table?.reloadData(row: index) - } - - public var isSelected:Bool { - if let table = table { - return table.isSelected(self) - } else { - return false - } - } - - open var identifier:String { - return NSStringFromClass(viewClass()) - } - - open func viewClass() ->AnyClass { - return TableRowView.self; - } - - open func makeSize(_ width:CGFloat = CGFloat.greatestFiniteMagnitude, oldWidth:CGFloat = 0) -> Bool { - self.oldWidth = width - return true; - } -} diff --git a/TGUIKit/TGUIKit/TableRowView.swift b/TGUIKit/TGUIKit/TableRowView.swift deleted file mode 100644 index b8a17b814b..0000000000 --- a/TGUIKit/TGUIKit/TableRowView.swift +++ /dev/null @@ -1,283 +0,0 @@ -// -// TableRowView.swift -// TGUIKit -// -// Created by keepcoder on 07/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - -open class TableRowView: NSTableRowView, CALayerDelegate { - - open private(set) weak var item:TableRowItem? - private let menuDisposable = MetaDisposable() - // var selected:Bool? - - open var border:BorderType? - public var animates:Bool = true - - public private(set) var contextMenu:ContextMenu? - - - required public override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - // self.layer = (self.layerClass() as! CALayer.Type).init() - self.wantsLayer = true - - self.layerContentsRedrawPolicy = .onSetNeedsDisplay - self.layer?.delegate = self - self.layer?.drawsAsynchronously = System.drawAsync - autoresizesSubviews = false - pressureConfiguration = NSPressureConfiguration(pressureBehavior: .primaryDeepClick) - } - - - open func updateColors() { - - } - - open func layerClass() ->AnyClass { - return CALayer.self; - } - - open var backdorColor: NSColor { - return presentation.colors.background - } - - open var isSelect: Bool { - return item?.isSelected ?? false - } - - open override func draw(_ dirtyRect: NSRect) { - - } - - open func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(backdorColor.cgColor) - ctx.fill(layer.bounds) - - if let border = border { - - ctx.setFillColor(presentation.colors.border.cgColor) - - if border.contains(.Top) { - ctx.fill(NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize)) - } - if border.contains(.Bottom) { - ctx.fill(NSMakeRect(0, 0, frame.width, .borderSize)) - } - if border.contains(.Left) { - ctx.fill(NSMakeRect(0, 0, .borderSize, frame.height)) - } - if border.contains(.Right) { - ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) - } - - } - - } - - open var interactionContentView:NSView { - return self - } - - open var firstResponder:NSResponder? { - return self - } - - open override func mouseDown(with event: NSEvent) { - if event.modifierFlags.contains(.control) && event.clickCount == 1 { - showContextMenu(event) - } else { - if event.clickCount == 2 { - doubleClick(in: convert(event.locationInWindow, from: nil)) - return - } - super.mouseDown(with: event) - } - } - - private var lastPressureEventStage = 0 - - open override func pressureChange(with event: NSEvent) { - super.pressureChange(with: event) - if event.stage == 2 && lastPressureEventStage < 2 { - forceClick(in: convert(event.locationInWindow, from: nil)) - } - lastPressureEventStage = event.stage - } - - open override func rightMouseDown(with event: NSEvent) { - super.rightMouseDown(with: event) - showContextMenu(event) - } - - open func doubleClick(in location:NSPoint) -> Void { - - } - - open func forceClick(in location: NSPoint) { - - } - - open func showContextMenu(_ event:NSEvent) -> Void { - - menuDisposable.set(nil) - contextMenu = nil - - if let item = item { - menuDisposable.set((item.menuItems() |> deliverOnMainQueue |> take(1)).start(next: { [weak self] items in - if let strongSelf = self { - let menu = ContextMenu() - menu.onShow = { [weak strongSelf] menu in - strongSelf?.contextMenu = menu - strongSelf?.onShowContextMenu() - } - menu.delegate = menu - menu.onClose = { [weak strongSelf] in - strongSelf?.contextMenu = nil - strongSelf?.onCloseContextMenu() - } - for item in items { - menu.addItem(item) - } - - menu.delegate = menu - NSMenu.popUpContextMenu(menu, with: event, for: strongSelf) - } - - })) - } - - - } - - open override func menu(for event: NSEvent) -> NSMenu? { - return NSMenu() - } - - - - - open func onShowContextMenu() ->Void { - self.layer?.setNeedsDisplay() - } - - open func onCloseContextMenu() ->Void { - self.layer?.setNeedsDisplay() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - open func updateMouse() { - - } - - public var isInsertionAnimated:Bool { - if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { - return true - } - return false - } - - public var rect:NSRect { - if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { - let rect = NSMakeRect(layer.position.x, layer.position.y, frame.width, frame.height) - return rect - } - return frame - } - - open override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - guard #available(OSX 10.12, *) else { - needsLayout = true - return - } - } - - open override func setFrameOrigin(_ newOrigin: NSPoint) { - super.setFrameOrigin(newOrigin) - guard #available(OSX 10.12, *) else { - needsLayout = true - return - } - } - - open override func viewDidMoveToSuperview() { - if superview != nil { - guard #available(OSX 10.12, *) else { - needsLayout = true - return - } - } - } - - open override func layout() { - super.layout() - } - - public func notifySubviewsToLayout(_ subview:NSView) -> Void { - for sub in subview.subviews { - sub.needsLayout = true - } - } - - open override var needsLayout: Bool { - set { - super.needsLayout = newValue - if newValue { - guard #available(OSX 10.12, *) else { - layout() - notifySubviewsToLayout(self) - return - } - } - } - get { - return super.needsLayout - } - } - - deinit { - menuDisposable.dispose() - } - - - open override func copy() -> Any { - let view:View = View(frame:bounds) - view.backgroundColor = self.backdorColor - return view - } - - open func set(item:TableRowItem, animated:Bool = false) -> Void { - self.item = item; - updateColors() - } - - open func focusAnimation() { - - } - - public func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) -> Void { - super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - - public func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - public func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - - open func mouseInside() -> Bool { - return super._mouseInside() - } - -} diff --git a/TGUIKit/TGUIKit/TableView.swift b/TGUIKit/TGUIKit/TableView.swift deleted file mode 100644 index 4a5b6d2493..0000000000 --- a/TGUIKit/TGUIKit/TableView.swift +++ /dev/null @@ -1,1747 +0,0 @@ -// -// TableView.swift -// TGUIKit -// -// Created by keepcoder on 07/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - -public enum TableSeparator { - case bottom; - case top; - case right; - case left; - case none; -} - -public class UpdateTransition { - public let inserted:[(Int,T)] - public let updated:[(Int,T)] - public let deleted:[Int] - public let animateVisibleOnly: Bool - public init(deleted:[Int], inserted:[(Int,T)], updated:[(Int,T)], animateVisibleOnly: Bool = true) { - self.inserted = inserted - self.updated = updated - self.deleted = deleted - self.animateVisibleOnly = animateVisibleOnly - } - - public var isEmpty:Bool { - return inserted.isEmpty && updated.isEmpty && deleted.isEmpty - } - - public var description: String { - return "inserted: \(inserted.count), updated:\(updated.count), deleted:\(deleted.count)" - } -} - - -public class TableUpdateTransition : UpdateTransition { - public let state:TableScrollState - public let animated:Bool - public let grouping:Bool - - public init(deleted:[Int], inserted:[(Int,TableRowItem)], updated:[(Int,TableRowItem)], animated:Bool = false, state:TableScrollState = .none(nil), grouping:Bool = true, animateVisibleOnly: Bool = true) { - self.animated = animated - self.state = state - self.grouping = grouping - super.init(deleted: deleted, inserted: inserted, updated: updated, animateVisibleOnly: animateVisibleOnly) - } - -} - -public final class TableEntriesTransition : TableUpdateTransition { - public let entries:T - public init(deleted:[Int], inserted:[(Int,TableRowItem)], updated:[(Int,TableRowItem)], entries:T, animated:Bool = false, state:TableScrollState = .none(nil), grouping:Bool = true) { - self.entries = entries - super.init(deleted: deleted, inserted: inserted, updated: updated, animated:animated, state: state, grouping:grouping) - } -} - -public protocol TableViewDelegate : class { - - func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void; - func selectionWillChange(row:Int, item:TableRowItem) -> Bool; - func isSelectable(row:Int, item:TableRowItem) -> Bool; - -} - -public enum TableSavingSide { - case lower - case upper -} - -public enum TableScrollState :Equatable { - case top(id: AnyHashable, animated: Bool, focus: Bool, inset: CGFloat); // stableId, animated, focus, inset - case bottom(id: AnyHashable, animated: Bool, focus: Bool, inset: CGFloat); // stableId, animated, focus, inset - case center(id: AnyHashable, animated: Bool, focus: Bool, inset: CGFloat); // stableId, animated, focus, inset - case saveVisible(TableSavingSide) - case none(TableAnimationInterface?); - case down(Bool); - case up(Bool); -} - -public extension TableScrollState { - public func swap(to stableId:AnyHashable) -> TableScrollState { - switch self { - case let .top(_, animated, focus, inset): - return .top(id: stableId, animated: animated, focus: focus, inset: inset) - case let .bottom(_, animated, focus, inset): - return .bottom(id: stableId, animated: animated, focus: focus, inset: inset) - case let .center(_, animated, focus, inset): - return .center(id: stableId, animated: animated, focus: focus, inset: inset) - default: - return self - } - } - - public var animated: Bool { - switch self { - case let .top(_, animated, _, _): - return animated - case let .bottom(_, animated, _, _): - return animated - case let .center(_, animated, _, _): - return animated - case .down(let animated): - return animated - case .up(let animated): - return animated - default: - return false - } - } -} - -public func ==(lhs:TableScrollState, rhs:TableScrollState) -> Bool { - switch lhs { - case let .top(stableId, animated, focus, inset): - if case .top(stableId, animated, focus, inset) = rhs { - return true - } else { - return false - } - case let .bottom(stableId, animated, focus, inset): - if case .bottom(stableId, animated, focus, inset) = rhs { - return true - } else { - return false - } - case let .center(stableId, animated, focus, inset): - if case .center(stableId, animated, focus, inset) = rhs { - return true - } else { - return false - } - case let .down(lhsAnimated): - switch rhs { - case let .down(rhsAnimated): - return lhsAnimated == rhsAnimated - default: - return false - } - case let .up(lhsAnimated): - switch rhs { - case let .up(rhsAnimated): - return lhsAnimated == rhsAnimated - default: - return false - } - case .none: - switch rhs { - case .none: - return true - default: - return false - } - case let .saveVisible(lhsType): - switch rhs { - case let .saveVisible(rhsType): - return lhsType == rhsType - default: - return false - } - } -} - -protocol SelectDelegate : class { - func selectRow(index:Int) -> Void; -} - -class TGFlipableTableView : NSTableView, CALayerDelegate { - - var bottomInset:CGFloat = 0 - - public var flip:Bool = true - - public weak var sdelegate:SelectDelegate? - weak var table:TableView? - var border:BorderType? - - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.autoresizesSubviews = false - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var isFlipped: Bool { - return flip - } - - override func draw(_ dirtyRect: NSRect) { - - } - -// override public func setNeedsDisplay(_ invalidRect: NSRect) { -// -// } - - override func addSubview(_ view: NSView) { - super.addSubview(view) - } - - func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(presentation.colors.background.cgColor) - ctx.fill(self.bounds) - - if let border = border { - - ctx.setFillColor(presentation.colors.border.cgColor) - - if border.contains(.Top) { - ctx.fill(NSMakeRect(0, NSHeight(self.frame) - .borderSize, NSWidth(self.frame), .borderSize)) - } - if border.contains(.Bottom) { - ctx.fill(NSMakeRect(0, 0, NSWidth(self.frame), .borderSize)) - } - if border.contains(.Left) { - ctx.fill(NSMakeRect(0, 0, .borderSize, NSHeight(self.frame))) - } - if border.contains(.Right) { - ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize, NSHeight(self.frame))) - } - - } - } - - - override func mouseDown(with event: NSEvent) { - let point = self.convert(event.locationInWindow, from: nil) - let range = self.rows(in: NSMakeRect(point.x, point.y, 1, 1)); - sdelegate?.selectRow(index: range.location) - } - - - override func setFrameSize(_ newSize: NSSize) { - let oldWidth: CGFloat = frame.width - super.setFrameSize(newSize) - - if oldWidth != frame.width { - if let table = table { - table.layoutIfNeeded(with: table.visibleRows(), oldWidth: oldWidth) - } - } - - } - - - - var liveWidth:CGFloat = 0 - - override func viewWillStartLiveResize() { - liveWidth = frame.width - } - - - override func viewDidEndLiveResize() { - if liveWidth != frame.width { - liveWidth = 0 - table?.layoutItems() - } - } - - - override func mouseUp(with event: NSEvent) { - - } - -} - -public protocol InteractionContentViewProtocol : class { - func contentInteractionView(for stableId: AnyHashable) -> NSView? -} - -public class TableScrollListener : NSObject { - fileprivate let uniqueId:UInt32 = arc4random() - fileprivate let handler:(ScrollPosition)->Void - fileprivate let dispatchWhenVisibleRangeUpdated: Bool - fileprivate var first: Bool = true - public init(dispatchWhenVisibleRangeUpdated: Bool = true, _ handler:@escaping(ScrollPosition)->Void) { - self.dispatchWhenVisibleRangeUpdated = dispatchWhenVisibleRangeUpdated - self.handler = handler - } - -} - -open class TableView: ScrollView, NSTableViewDelegate,NSTableViewDataSource,SelectDelegate,InteractionContentViewProtocol { - - public var separator:TableSeparator = .none - - var list:[TableRowItem] = [TableRowItem](); - var tableView:TGFlipableTableView - weak public var delegate:TableViewDelegate? - private var trackingArea:NSTrackingArea? - private var listhash:[AnyHashable:TableRowItem] = [AnyHashable:TableRowItem](); - - private let mergePromise:Promise = Promise() - private let mergeDisposable:MetaDisposable = MetaDisposable() - - public let selectedhash:Atomic = Atomic(value: nil); - - private var updating:Bool = false - - private var previousScroll:ScrollPosition? - public var needUpdateVisibleAfterScroll:Bool = false - private var scrollHandler:(_ scrollPosition:ScrollPosition) ->Void = {_ in} - - - private var scrollListeners:[TableScrollListener] = [] - - - public var emptyItem:TableRowItem? { - didSet { - if let _ = emptyView { - updateEmpties() - } - } - } - private var emptyView:TableRowView? - - public func addScroll(listener:TableScrollListener) { - scrollListeners.append(listener) - } - - - public var bottomInset:CGFloat = 0 { - didSet { - tableView.bottomInset = bottomInset - } - } - - - - public func removeScroll(listener:TableScrollListener) { - var index:Int = 0 - var found:Bool = false - for enumerate in scrollListeners { - if enumerate.uniqueId == listener.uniqueId { - found = true - break - } - index += 1 - } - - if found { - scrollListeners.remove(at: index) - } - - } - - public var count:Int { - get { - return self.list.count - } - } - - open override func setNeedsDisplay(_ invalidRect: NSRect) { - - } - - open override var isFlipped: Bool { - return true - } - - convenience override init(frame frameRect: NSRect) { - self.init(frame:frameRect, isFlipped:true, drawBorder: false) - } - - public var border:BorderType? { - didSet { - self.clipView.border = border - self.tableView.border = border - } - } - - open override var backgroundColor: NSColor { - didSet { - documentView?.background = backgroundColor - contentView.background = backgroundColor - self.clipView.backgroundColor = backgroundColor - self.clipView.needsDisplay = true - documentView?.needsDisplay = true - } - } - - public func setIsFlipped(_ flipped: Bool) { - self.tableView.flip = flipped - } - - public required init(frame frameRect: NSRect, isFlipped:Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { - - let table = TGFlipableTableView(frame:frameRect) - table.flip = isFlipped - - self.tableView = table - self.tableView.wantsLayer = true - - self.tableView.layerContentsRedrawPolicy = .onSetNeedsDisplay - - - super.init(frame: frameRect); - - table.table = self - - self.bottomInset = bottomInset - table.bottomInset = bottomInset - - if drawBorder { - self.clipView.border = BorderType([.Right]) - self.tableView.border = BorderType([.Right]) - } - - self.hasVerticalScroller = true; - - self.documentView = self.tableView; - self.autoresizesSubviews = true; - self.autoresizingMask = [.width, .height] - - self.tableView.delegate = self; - self.tableView.dataSource = self; - self.tableView.sdelegate = self - self.tableView.allowsColumnReordering = true - self.tableView.headerView = nil; - self.tableView.intercellSpacing = NSMakeSize(0, 0) - - let tableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "column")) - tableColumn.width = frame.width - self.tableView.addTableColumn(tableColumn) - - - mergeDisposable.set(mergePromise.get().start(next: { [weak self] (transition) in - self?.merge(with: transition) - })) - - } - - - open override func layout() { - super.layout() - emptyView?.frame = bounds - if needsLayouItemsOnNextTransition { - layoutItems() - } - } - - open override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - } - - func layoutIfNeeded(with range:NSRange, oldWidth:CGFloat) { - for i in range.min ..< range.max { - let item = self.item(at: i) - let before = item.height - let updated = item.makeSize(tableView.frame.width, oldWidth: oldWidth) - let after = item.height - if (before != after && updated) || item.instantlyResize { - reloadData(row: i, animated: false) - noteHeightOfRow(i, false) - } - } - } - - open override func viewDidMoveToSuperview() { - if superview != nil { - let clipView = self.contentView - - NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: nil, using: { [weak self] notification in - if let strongSelf = self { - - let reqCount = strongSelf.count / 6 - - strongSelf.updateStickAfterScroll(false) - - let scroll = strongSelf.scrollPosition(strongSelf.visibleRows()) - - if (!strongSelf.updating && !strongSelf.clipView.isAnimateScrolling) { - - let range = scroll.current.visibleRows - - if(scroll.current.direction != strongSelf.previousScroll?.direction && scroll.current.rect != strongSelf.previousScroll?.rect) { - - switch(scroll.current.direction) { - case .top: - if(range.location <= reqCount) { - strongSelf.scrollHandler(scroll.current) - strongSelf.previousScroll = scroll.current - - } - case .bottom: - if(strongSelf.count - (range.location + range.length) <= reqCount) { - strongSelf.scrollHandler(scroll.current) - strongSelf.previousScroll = scroll.current - - } - case .none: - strongSelf.scrollHandler(scroll.current) - strongSelf.previousScroll = scroll.current - - } - } - - } - for listener in strongSelf.scrollListeners { - if !listener.dispatchWhenVisibleRangeUpdated || listener.first || !NSEqualRanges(scroll.current.visibleRows, scroll.previous.visibleRows) { - listener.handler(scroll.current) - listener.first = false - } - } - } - - }) - } else { - NotificationCenter.default.removeObserver(self) - } - } - - - private var stickClass:AnyClass? - private var stickView:TableStickView? - private var stickItem:TableStickItem? { - didSet { - if stickItem != oldValue { - if let stickHandler = stickHandler { - stickHandler(stickItem) - } - } - } - } - private var stickHandler:((TableStickItem?)->Void)? - private var firstTime: Bool = false - public func set(stickClass:AnyClass?, visible: Bool = true, handler:@escaping(TableStickItem?)->Void) { - self.stickClass = stickClass - self.stickHandler = handler - self.firstTime = true - if let stickClass = stickClass as? TableStickItem.Type { - if stickView == nil { - let stickItem:TableStickItem = stickClass.init(frame.size) - - self.stickItem = stickItem - if visible { - let vz = stickItem.viewClass() as! TableStickView.Type - stickView = vz.init(frame:NSMakeRect(0, 0, NSWidth(self.frame), stickItem.height)) - stickView!.header = true - stickView!.set(item: stickItem, animated: false) - tableView.addSubview(stickView!) - } - } - - updateStickAfterScroll(false) - - } else { - stickView?.removeFromSuperview() - stickView = nil - stickItem = nil - } - - } - - func optionalItem(at:Int) -> TableRowItem? { - return at < count && at >= 0 ? self.item(at: at) : nil - } - - private var needsLayouItemsOnNextTransition:Bool = false - - public func layouItemsOnNextTransition() { - needsLayouItemsOnNextTransition = true - } - - public func layoutItems() { - - let visibleItems = self.visibleItems() - - beginTableUpdates() - enumerateItems { item in - _ = item.makeSize(frame.width, oldWidth: item.width) - reloadData(row: item.index, animated: false) - NSAnimationContext.current.duration = 0.0 - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: item.index)) - return true - } - endTableUpdates() - - saveScrollState(visibleItems) - - needsLayouItemsOnNextTransition = false - } - - private func saveScrollState(_ visibleItems: [(TableRowItem,CGFloat,CGFloat)]) -> Void { - if !visibleItems.isEmpty, clipView.bounds.minY > 0 { - var nrect:NSRect = NSZeroRect - - let strideTo:StrideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) - - for i in strideTo { - let visible = visibleItems[i] - if let item = self.item(stableId: visible.0.stableId) { - - nrect = rectOf(item: item) - - if let view = viewNecessary(at: i) { - if view.isInsertionAnimated { - break - } - } - - let y:CGFloat - if !tableView.isFlipped { - y = nrect.minY - (frame.height - visible.1) + nrect.height - } else { - y = nrect.minY - visible.1 - } - - //clipView.scroll(to: NSMakePoint(0, y + frame.minY), animated: false) - self.contentView.bounds = NSMakeRect(0, y, 0, clipView.bounds.height) - reflectScrolledClipView(clipView) - break - } - } - } - } - - private let stickTimeoutDisposable = MetaDisposable() - - public func updateStickAfterScroll(_ animated: Bool) -> Void { - let range = self.visibleRows() - - if let stickClass = stickClass { - if documentSize.height > frame.height { - var index:Int = range.location + range.length - 1 - - let flipped = tableView.isFlipped - - let scrollInset = self.documentOffset.y + (flipped ? 0 : frame.height) - var item:TableRowItem? = optionalItem(at: index) - - while let s = item, !s.isKind(of: stickClass) { - index += 1 - item = self.optionalItem(at: index) - } - - if item == nil { - index = range.location + range.length - while item == nil && index < count { - if let s = self.optionalItem(at: index), s.isKind(of: stickClass) { - item = s - } - index += 1 - } - } - - if let item = item as? TableStickItem { - var currentStick:TableStickItem? - - for index in stride(from: item.index - 1, to: -1, by: -1) { - let item = self.optionalItem(at: index) - if let item = item, item.isKind(of: stickClass) { - currentStick = item as? TableStickItem - break - } - } - - if stickView?.item != item { - stickView?.set(item: item, animated: tableView.subviews.last == stickView) - } - - if let item = stickItem { - (viewNecessary(at: item.index) as? TableStickView)?.updateIsVisible(!firstTime, animated: false) - - - } - - stickItem = currentStick ?? item - - (viewNecessary(at: item.index) as? TableStickView)?.updateIsVisible(false, animated: false) - - if let stickView = stickView { - if tableView.subviews.last != stickView { - stickView.removeFromSuperview() - tableView.addSubview(stickView) - } - } - - stickView?.setFrameSize(tableView.frame.width, item.height) - let itemRect:NSRect = tableView.rect(ofRow: item.index) - - if let item = stickItem, item.isKind(of: stickClass), let stickView = stickView { - let rect:NSRect = tableView.rect(ofRow: item.index) - let dif:CGFloat - if currentStick != nil { - dif = min(scrollInset - rect.maxY, item.height) - } else { - dif = item.height - } - let yTopOffset:CGFloat = min(max(scrollInset - dif, 0), documentSize.height - item.height) - if stickView.frame.minY != yTopOffset { - stickView.isHidden = firstTime - if !animated || stickView.layer?.opacity != 0 { - stickView.change(opacity: firstTime ? 0 : 1, animated: !firstTime) - firstTime = false - } - } - stickView.change(pos: NSMakePoint(0, yTopOffset), animated: animated) - stickView.header = fabs(dif) <= item.height - stickTimeoutDisposable.set((Signal.single(Void()) |> delay(2.0, queue: Queue.mainQueue())).start(next: { [weak stickView, weak item] in - if let item = item, abs(itemRect.minY - yTopOffset) > item.height, let stickView = stickView { - stickView.change(opacity: 0.0, completion: { [weak stickView] completed in - if completed { - stickView?.isHidden = true - } - }) - } - - })) - - } - - } else if let stickView = stickView { - stickView.setFrameOrigin(0, max(0,scrollInset)) - stickView.header = true - } - - } - - } - } - - - public func resetScrollNotifies() ->Void { - self.previousScroll = nil - updateScroll() - } - - public func notifyScrollHandlers() -> Void { - let scroll = scrollPosition(visibleRows()).current - for listener in scrollListeners { - listener.handler(scroll) - } - } - - public var topVisibleRow:Int? { - let visible = visibleItems() - if !isFlipped { - return visible.first?.0.index - } else { - return visible.last?.0.index - } - } - - public var bottomVisibleRow:Int? { - let visible = visibleItems() - if isFlipped { - return visible.first?.0.index - } else { - return visible.last?.0.index - } - } - - open override func setFrameOrigin(_ newOrigin: NSPoint) { - super.setFrameOrigin(newOrigin); - self.updateTrackingAreas(); - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func selectedItem() -> TableRowItem? { - - let hash = selectedhash.modify({$0}) - if let hash = hash { - return self.item(stableId:hash) - } - return nil - } - - public func isSelected(_ item:TableRowItem) ->Bool { - return selectedhash.modify({$0}) == item.stableId - } - - public func item(stableId:AnyHashable) -> TableRowItem? { - return self.listhash[stableId]; - } - - public func index(of:TableRowItem) -> Int? { - - if let it = self.listhash[of.stableId] { - return self.list.index(of: it); - } - - return nil - } - - public func index(hash:AnyHashable) -> Int? { - - if let it = self.listhash[hash] { - return self.list.index(of: it); - } - - return nil - } - - public func insert(item:TableRowItem, at:Int = 0, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Bool { - - assert(self.item(stableId:item.stableId) == nil, "inserting existing row inTable: \(self.item(stableId:item.stableId)!.className), new: \(item.className)") - self.listhash[item.stableId] = item; - self.list.insert(item, at: min(at, list.count)); - item.table = self; - - let animation = animation != .none ? item.animatable ? animation : .none : .none - NSAnimationContext.current.duration = animation != .none ? 0.2 : 0.0 - - if(redraw) { - self.tableView.insertRows(at: IndexSet(integer: at), withAnimation: animation) - } - - return true; - - } - - public func addItem(item:TableRowItem, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Bool { - return self.insert(item: item, at: self.count, redraw: redraw, animation:animation) - } - - public func insert(items:[TableRowItem], at:Int = 0, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { - - - var current:Int = 0; - for item in items { - - if(self.insert(item: item, at: at + current, redraw: false)) { - current += 1; - } - - } - - if(current != 0 && redraw) { - self.tableView.insertRows(at: IndexSet(integersIn: at ..< current + at), withAnimation: animation) - } - - } - - public var firstItem:TableRowItem? { - return self.list.first - } - - public var lastItem:TableRowItem? { - return self.list.last - } - - public func noteHeightOfRow(_ row:Int, _ animated:Bool = true) { - if !animated { - NSAnimationContext.current.duration = 0 - } - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) - } - - - - public func reloadData(row:Int, animated:Bool = false) -> Void { - if let view = self.viewNecessary(at: row) { - let item = self.item(at: row) - if view.isKind(of: item.viewClass()) { - if let viewItem = view.item { - if viewItem.height != item.height { - NSAnimationContext.current.duration = animated ? 0.2 : 0.0 - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) - } - } else { - NSAnimationContext.current.duration = animated ? 0.2 : 0.0 - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) - } - - view.set(item: item, animated: animated) - view.needsDisplay = true - } else { - self.tableView.removeRows(at: IndexSet(integer: row), withAnimation: !animated ? .none : .effectFade) - self.tableView.insertRows(at: IndexSet(integer: row), withAnimation: !animated ? .none : .effectFade) - } - } else { - NSAnimationContext.current.duration = 0.0 - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) - } - //self.moveItem(from: row, to: row) - } - - public func moveItem(from:Int, to:Int, changeItem:TableRowItem? = nil, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { - - - var item:TableRowItem = self.item(at:from); - let animation: NSTableView.AnimationOptions = animation != .none ? item.animatable ? animation : .none : .none - NSAnimationContext.current.duration = animation != .none ? NSAnimationContext.current.duration : 0.0 - - if let change = changeItem { - assert(change.stableId == item.stableId) - change.table = self - self.listhash.removeValue(forKey: item.stableId) - self.listhash[change.stableId] = change - item = change - } - - self.list.remove(at: from); - - self.list.insert(item, at: to); - - - if(redraw) { - - if from == to { - self.reloadData(row: to) - } else { - self.tableView.removeRows(at: IndexSet(integer:from), withAnimation: from == to ? .none : animation) - self.tableView.insertRows(at: IndexSet(integer:to), withAnimation: from == to ? .none : animation) - } - - } - - } - - public func beginUpdates() -> Void { - updating = true - updateScroll(visibleRows()) - self.previousScroll = nil - CATransaction.begin() - } - - public func endUpdates() -> Void { - updating = false - updateScroll(visibleRows()) - self.previousScroll = nil - CATransaction.commit() - } - - public func rectOf(item:TableRowItem) -> NSRect { - if let index = self.index(of: item) { - return self.tableView.rect(ofRow: index) - } else { - return NSZeroRect - } - } - - public func rectOf(index:Int) -> NSRect { - return self.tableView.rect(ofRow: index) - } - - public func remove(at:Int, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { - if at < count { - let item = self.item(at: at) - let animation: NSTableView.AnimationOptions = animation != .none ? item.animatable ? animation : .none : .none - NSAnimationContext.current.duration = animation == .none ? 0.0 : 0.2 - - self.list.remove(at: at); - self.listhash.removeValue(forKey: item.stableId) - - if(redraw) { - self.tableView.removeRows(at: IndexSet(integer:at), withAnimation: animation != .none ? .effectFade : .none) - } - } - } - - public func remove(range:Range, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { - - for i in range.lowerBound ..< range.upperBound { - remove(at: i, redraw: false) - } - - if(redraw) { - self.tableView.removeRows(at: IndexSet(integersIn:range), withAnimation: animation != .none ? .effectFade : .none) - } - } - - - - public func removeAll(redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { - let count:Int = self.count; - self.list.removeAll() - self.listhash.removeAll() - - if(redraw) { - - self.tableView.removeRows(at: IndexSet(integersIn: 0 ..< count), withAnimation: animation != .none ? .effectFade : .none) - } - } - - public func selectNext(_ scroll:Bool = true, _ animated:Bool = false) -> Void { - - if let hash = selectedhash.modify({$0}) { - let selectedItem = self.item(stableId: hash) - if let selectedItem = selectedItem { - var selectedIndex = self.index(of: selectedItem)! - selectedIndex += 1 - - if selectedIndex == count { - selectedIndex = 0 - } - if let delegate = delegate { - let sIndex = selectedIndex - for i in sIndex ..< list.count { - if delegate.selectionWillChange(row: i, item: item(at: i)) { - selectedIndex = i - break - } - } - } - - - _ = select(item: item(at: selectedIndex)) - } - - - } else { - if let delegate = delegate { - for item in list { - if delegate.selectionWillChange(row: item.index, item: item) { - _ = self.select(item: item) - break - } - } - } - - } - if let hash = selectedhash.modify({$0}), scroll { - self.scroll(to: .top(id: hash, animated: animated, focus: false, inset: 0), inset: NSEdgeInsets(), true) - } - } - - public func selectPrev(_ scroll:Bool = true, _ animated:Bool = false) -> Void { - - if let hash = selectedhash.modify({$0}) { - let selectedItem = self.item(stableId: hash) - if let selectedItem = selectedItem { - var selectedIndex = self.index(of: selectedItem)! - selectedIndex -= 1 - - if selectedIndex == -1 { - selectedIndex = count - 1 - } - - if let delegate = delegate { - let sIndex = selectedIndex - for i in stride(from: sIndex, to: -1, by: -1) { - if delegate.selectionWillChange(row: i, item: item(at: i)) { - selectedIndex = i - break - } - } - } - - - _ = select(item: item(at: selectedIndex)) - } - - - } else { - if let delegate = delegate { - for i in stride(from: list.count - 1, to: -1, by: -1) { - if delegate.selectionWillChange(row: i, item: item(at: i)) { - _ = self.select(item: item(at: i)) - break - } - } - } - - } - - if let hash = selectedhash.modify({$0}), scroll { - self.scroll(to: .bottom(id: hash, animated: animated, focus: false, inset: 0), inset: NSEdgeInsets(), true) - } - } - - public var isEmpty:Bool { - return self.list.isEmpty || (!tableView.isFlipped && list.count == 1) - } - - public func reloadData() -> Void { - self.tableView.reloadData() - } - - public func item(at:Int) -> TableRowItem { - return self.list[at] - } - - public func visibleRows(_ insetHeight:CGFloat = 0) -> NSRange { - return self.tableView.rows(in: NSMakeRect(self.tableView.visibleRect.minX, self.tableView.visibleRect.minY, self.tableView.visibleRect.width, self.tableView.visibleRect.height + insetHeight)) - } - - public var listHeight:CGFloat { - var height:CGFloat = 0 - for item in list { - height += item.height - } - return height - } - - public func row(at point:NSPoint) -> Int { - return tableView.row(at: NSMakePoint(point.x, point.y - bottomInset)) - } - - public func viewNecessary(at row:Int) -> TableRowView? { - if row < 0 || row > count - 1 { - return nil - } - return self.tableView.rowView(atRow: row, makeIfNecessary: false) as? TableRowView - } - - - public func select(item:TableRowItem, notify:Bool = true, byClick:Bool = false) -> Bool { - - if let delegate = delegate, delegate.isSelectable(row: item.index, item: item) { - if(self.item(stableId:item.stableId) != nil) { - if delegate.selectionWillChange(row: item.index, item: item) { - let new = item.stableId != selectedhash.modify({$0}) - self.cancelSelection(); - let _ = selectedhash.swap(item.stableId) - item.prepare(true) - self.reloadData(row:item.index) - if notify { - delegate.selectionDidChange(row: item.index, item: item, byClick:byClick, isNew:new) - } - return true; - } - } - } - return false; - - } - - public func changeSelection(stableId:AnyHashable?) { - if let stableId = stableId { - if let item = self.item(stableId: stableId) { - _ = self.select(item:item, notify:false) - } else { - cancelSelection() - _ = self.selectedhash.swap(stableId) - } - } else { - cancelSelection() - } - } - - public func cancelSelection() -> Void { - if let hash = selectedhash.modify({$0}) { - if let item = self.item(stableId: hash) { - item.prepare(false) - let _ = selectedhash.swap(nil) - self.reloadData(row:item.index) - } else { - let _ = selectedhash.swap(nil) - } - } - - } - - - func rowView(item:TableRowItem) -> TableRowView { - let identifier:String = item.identifier - - var view = self.tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier), owner: self.tableView) - if(view == nil) { - let vz = item.viewClass() as! TableRowView.Type - - view = vz.init(frame:NSMakeRect(0, 0, NSWidth(self.frame), item.height)) - - view?.identifier = NSUserInterfaceItemIdentifier(rawValue: identifier) - - } - return view as! TableRowView; - } - - public func numberOfRows(in tableView: NSTableView) -> Int { - return self.count; - } - - public func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - return max(self.item(at: row).height, 1) - } - - public func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { - return false; - } - - public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - - return nil - } - - - public func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { - let item:TableRowItem = self.item(at: row); - - let view:TableRowView = self.rowView(item: item); - - - view.set(item: item, animated: false) - - return view - } - - - func visibleItems() -> [(TableRowItem,CGFloat,CGFloat)] { // item, top offset, bottom offset - - var list:[(TableRowItem,CGFloat,CGFloat)] = [] - - let visible = visibleRows() - - for i in visible.location ..< visible.location + visible.length { - let item = self.item(at: i) - let rect = rectOf(index: i) - if rect.height == item.height { - if !tableView.isFlipped { - let top = frame.height - (rect.minY - documentOffset.y) - rect.height - let bottom = (rect.minY - documentOffset.y) - list.append((item,top,bottom)) - } else { - let top = rect.minY - documentOffset.y - let bottom = frame.height - (rect.minY - documentOffset.y) - rect.height - list.append((item,top,bottom)) - //fatalError("not supported") - } - } - - // list.append(item,) - } - - - return list; - - } - - func itemRects() -> [(TableRowItem, NSRect, Int)] { - var ilist:[(TableRowItem,NSRect,Int)] = [(TableRowItem,NSRect,Int)]() - - for i in 0 ..< self.list.count { - ilist.append((item(at: i),self.rectOf(index: i), i)) - - } - - return ilist; - - } - - public func beginTableUpdates() { - self.tableView.beginUpdates() - } - - public func endTableUpdates() { - self.tableView.endUpdates() - } - - public func stopMerge() { - mergeDisposable.set(nil) - mergePromise.set(.single(TableUpdateTransition(deleted: [], inserted: [], updated: []))) - } - - public func startMerge() { - mergeDisposable.set((mergePromise.get() |> deliverOnMainQueue).start(next: { [weak self] transition in - self?.merge(with: transition) - })) - } - - public func merge(with transition:Signal) { - mergePromise.set(transition) - } - - - private var first:Bool = true - - public func merge(with transition:TableUpdateTransition) -> Void { - - assertOnMainThread() - assert(!updating) - - let oldEmpty = self.isEmpty - - self.beginUpdates() - - let visibleItems = self.visibleItems() - let visibleRange = self.visibleRows() - if transition.grouping && !transition.isEmpty { - self.tableView.beginUpdates() - } - - var inserted:[TableRowItem] = [] - var removed:[TableRowItem] = [] - - for rdx in transition.deleted.reversed() { - let effect:NSTableView.AnimationOptions - if case let .none(interface) = transition.state, interface != nil { - effect = (visibleRange.indexIn(rdx) || !transition.animateVisibleOnly) ? .effectFade : .none - } else { - effect = transition.animated && (visibleRange.indexIn(rdx) || !transition.animateVisibleOnly) ? .effectFade : .none - } - if rdx < visibleRange.location { - removed.append(item(at: rdx)) - } - self.remove(at: rdx, redraw: true, animation:effect) - } - - NSAnimationContext.current.duration = transition.animated ? 0.2 : 0.0 - - - for (idx,item) in transition.inserted { - let effect:NSTableView.AnimationOptions = transition.animated ? .effectFade : .none - _ = self.insert(item: item, at:idx, redraw: true, animation: effect) - if item.animatable { - inserted.append(item) - } - } - - - for (index,item) in transition.updated { - let animated:Bool - if case .none = transition.state { - animated = true - } else { - animated = false - } - replace(item:item, at:index, animated: animated) - } - - if transition.grouping && !transition.isEmpty { - self.tableView.endUpdates() - } - let state: TableScrollState - - if case .none = transition.state { - let isSomeOfItemVisible = !inserted.filter({$0.isVisible}).isEmpty || !removed.filter({$0.isVisible}).isEmpty - if isSomeOfItemVisible { - state = transition.state - } else { - state = .saveVisible(.upper) - } - } else { - state = transition.state - } - - //reflectScrolledClipView(clipView) - switch state { - case let .none(animation): - // print("scroll do nothing") - animation?.animate(table:self, added: inserted, removed:removed) - - case .bottom, .top, .center: - self.scroll(to: transition.state) - case .up(_), .down(_): - self.scroll(to: transition.state) - case let .saveVisible(side): - -// if transition.isEmpty { -// break -// } - - var nrect:NSRect = NSZeroRect - - let strideTo:StrideTo - - if !tableView.isFlipped { - switch side { - case .lower: - strideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) - case .upper: - strideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) //stride(from: 0, to: visibleItems.count, by: 1) - } - } else { - switch side { - case .upper: - strideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) - case .lower: - strideTo = stride(from: 0, to: visibleItems.count, by: 1) - } - } - - - for i in strideTo { - let visible = visibleItems[i] - if let item = self.item(stableId: visible.0.stableId) { - - nrect = rectOf(item: item) - - if let view = viewNecessary(at: i) { - if view.isInsertionAnimated { - break - } - } - - let y:CGFloat - - switch side { - case .lower: - if !tableView.isFlipped { - y = nrect.minY - (frame.height - visible.1) + nrect.height - } else { - y = nrect.minY - visible.1 - } - break - case .upper: - if !tableView.isFlipped { - y = nrect.minY - (frame.height - visible.1) + nrect.height - } else { - y = nrect.minY - visible.1 - } - break - } - self.contentView.bounds = NSMakeRect(0, y, 0, clipView.bounds.height) - reflectScrolledClipView(clipView) - break - } - } - - break - } - - - self.endUpdates() - - - - if oldEmpty != isEmpty || first { - updateEmpties() - } - - first = false - performScrollEvent() - } - - func updateEmpties() { - if let emptyItem = emptyItem { - if isEmpty { - if let empt = emptyView, !empt.isKind(of: emptyItem.viewClass()) || empt.item != emptyItem { - emptyView?.removeFromSuperview() - emptyView = nil - } - if emptyView == nil { - let vz = emptyItem.viewClass() as! TableRowView.Type - emptyView = vz.init(frame:bounds) - emptyView?.identifier = identifier - } - emptyView?.frame = bounds - if emptyView?.superview == nil { - addSubview(emptyView!) - } - emptyView?.set(item: emptyItem) - emptyView?.needsLayout = true - } else { - emptyView?.removeFromSuperview() - emptyView = nil - } - } - - } - - - public func replace(item:TableRowItem, at index:Int, animated:Bool) { - list[index] = item - listhash[item.stableId] = item - item.table = self - reloadData(row: index, animated: animated) - } - - public func contentInteractionView(for stableId: AnyHashable) -> NSView? { - if let item = self.item(stableId: stableId) { - let view = viewNecessary(at:item.index) - if let view = view, !NSIsEmptyRect(view.visibleRect) { - return view.interactionContentView - } - - } - - return nil - } - - - func selectRow(index: Int) { - if self.count > index { - _ = self.select(item: self.item(at: index), byClick:true) - } - } - - public override func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - - - if animated { - - if !tableView.isFlipped { - - CATransaction.begin() - var presentBounds:NSRect = self.layer?.bounds ?? self.bounds - let presentation = self.layer?.presentation() - if let presentation = presentation, self.layer?.animation(forKey:"bounds") != nil { - presentBounds = presentation.bounds - } - - self.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, self.bounds.minY, size.width, size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseOut) - let y = (size.height - presentBounds.height) - - presentBounds = contentView.layer?.bounds ?? contentView.bounds - if let presentation = contentView.layer?.presentation(), contentView.layer?.animation(forKey:"bounds") != nil { - presentBounds = presentation.bounds - } - - if y > 0 { - presentBounds.origin.y -= y - presentBounds.size.height += y - } else { - presentBounds.origin.y += y - presentBounds.size.height -= y - } - - contentView.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, contentView.bounds.minY, size.width, size.height), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) - CATransaction.commit() - } else { - super.change(size: size, animated: animated, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - return - } - } - self.setFrameSize(size) - updateStickAfterScroll(animated) - } - - - - public func scroll(to state:TableScrollState, inset:NSEdgeInsets = NSEdgeInsets(), _ toVisible:Bool = false) { - // if let index = self.index(of: item) { - - var rowRect:NSRect = bounds - - var item:TableRowItem? - var animate:Bool = false - var focus: Bool = false - var relativeInset: CGFloat = 0 - switch state { - case let .center(stableId, _animate, _focus, _inset): - item = self.item(stableId: stableId) - animate = _animate - relativeInset = _inset - focus = _focus - case let .bottom(stableId, _animate, _focus, _inset): - item = self.item(stableId: stableId) - animate = _animate - relativeInset = _inset - focus = _focus - case let .top(stableId, _animate, _focus, _inset): - item = self.item(stableId: stableId) - animate = _animate - relativeInset = _inset - focus = _focus - case let .down(_animate): - animate = _animate - if !tableView.isFlipped { - rowRect.origin = NSZeroPoint - } else { - rowRect.origin = NSMakePoint(0, max(0,documentSize.height - frame.height)) - } - case let .up(_animate): - animate = _animate - if !tableView.isFlipped { - rowRect.origin = NSMakePoint(0, max(documentSize.height,frame.height)) - } else { - rowRect.origin = NSZeroPoint - } - default: - fatalError("for scroll to item, you can use only .top, center, .bottom enumeration") - } - - let bottomInset = self.bottomInset != 0 ? (self.bottomInset) : 0 - let height:CGFloat = self is HorizontalTableView ? frame.width : frame.height - - if let item = item { - rowRect = self.rectOf(item: item) - - switch state { - case .bottom: - if tableView.isFlipped { - rowRect.origin.y -= (height - rowRect.height) - bottomInset - } - case .top: - // break - if !tableView.isFlipped { - rowRect.origin.y -= (height - rowRect.height) - bottomInset - } - case .center: - if !tableView.isFlipped { - rowRect.origin.y -= floorToScreenPixels((height - rowRect.height) / 2.0) - bottomInset - } else { - - if rowRect.maxY > height/2.0 { - rowRect.origin.y -= floorToScreenPixels((height - rowRect.height) / 2.0) - bottomInset - } else { - rowRect.origin.y = 0 - } - - - // fatalError("not implemented") - } - - default: - fatalError("not implemented") - } - - if toVisible { - let view = self.viewNecessary(at: item.index) - if let view = view, view.visibleRect.height == item.height { - if focus { - view.focusAnimation() - } - return - } - } - } - rowRect.origin.y = round(min(max(rowRect.minY + relativeInset,0), documentSize.height - height) + inset.top) - if clipView.bounds.minY != rowRect.minY { - - var applied = false - let scrollListener = TableScrollListener({ [weak self, weak item] position in - if let item = item, !applied, let view = self?.viewNecessary(at: item.index), view.visibleRect.height > 10 { - applied = true - if focus { - view.focusAnimation() - } - } - }) - - addScroll(listener: scrollListener) - - let bounds = NSMakeRect(0, rowRect.minY, clipView.bounds.width, clipView.bounds.height) - - - let getEdgeInset:()->CGFloat = { - if bounds.minY > self.clipView.bounds.minY { - return height - } else { - return -height - } - } - - if abs(bounds.minY - clipView.bounds.minY) < height { - clipView.scroll(to: bounds.origin, animated: animate, completion: { [weak self] _ in - self?.removeScroll(listener: scrollListener) - }) - } else { - let edgeRect:NSRect = NSMakeRect(clipView.bounds.minX, bounds.minY - getEdgeInset() - frame.minY, clipView.bounds.width, clipView.bounds.height) - clipView._changeBounds(from: edgeRect, to: bounds, animated: animate, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in - self?.removeScroll(listener: scrollListener) - }) - } - } else { - if let item = item, focus { - viewNecessary(at: item.index)?.focusAnimation() - } - } - - } - - open override func setFrameSize(_ newSize: NSSize) { - let visible = visibleItems() - let oldWidth = frame.width - super.setFrameSize(newSize) - //updateStickAfterScroll(false) - if oldWidth != newSize.width { - saveScrollState(visible) - } - } - - public func setScrollHandler(_ handler: @escaping (_ scrollPosition:ScrollPosition) ->Void) -> Void { - - scrollHandler = handler - - } - - override open func scrollWheel(with event: NSEvent) { - super.scrollWheel(with: event) - if needUpdateVisibleAfterScroll { - let range = visibleRows() - for i in range.location ..< range.location + range.length { - if let view = viewNecessary(at: i) { - view.updateMouse() - } - } - } - - } - - public func enumerateItems(with callback:(TableRowItem)->Bool) { - for item in list { - if !callback(item) { - break - } - } - } - - public func enumerateVisibleItems(reversed: Bool = false, with callback:(TableRowItem)->Bool) { - let visible = visibleRows() - - if reversed { - for i in stride(from: visible.location + visible.length - 1, to: visible.location - 1, by: -1) { - if !callback(list[i]) { - break - } - } - } else { - for i in visible.location ..< visible.location + visible.length { - if !callback(list[i]) { - break - } - } - } - - } - - public func enumerateViews(with callback:(TableRowView)->Bool) { - for index in 0 ..< list.count { - if let view = viewNecessary(at: index) { - if !callback(view) { - break - } - } - } - } - - public func enumerateVisibleViews(with callback:(TableRowView)->Void) { - let visibleRows = self.visibleRows() - for index in visibleRows.location ..< visibleRows.location + visibleRows.length { - if let view = viewNecessary(at: index) { - callback(view) - } - } - } - - public func performScrollEvent() -> Void { - self.updateScroll(visibleRows()) - NotificationCenter.default.post(name: NSView.boundsDidChangeNotification, object: self.contentView) - } - - deinit { - mergeDisposable.dispose() - stickTimeoutDisposable.dispose() - } - - - -} diff --git a/TGUIKit/TGUIKit/TextButtonBarView.swift b/TGUIKit/TGUIKit/TextButtonBarView.swift deleted file mode 100644 index fc57ac4cc3..0000000000 --- a/TGUIKit/TGUIKit/TextButtonBarView.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// TextButtonBarView.swift -// TGUIKit -// -// Created by keepcoder on 05/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public enum TextBarAligment { - case Left - case Right - case Center -} - -open class TextButtonBarView: BarView { - - private(set) public var button:TitleButton - - public var alignment:TextBarAligment = .Center - - public init(controller: ViewController, text:String, style:ControlStyle = navigationButtonStyle, alignment:TextBarAligment = .Center) { - - - button = TitleButton(frame:NSZeroRect) - button.style = style - button.set(text: text, for: .Normal) - button.disableActions() - - super.init(controller: controller) - - self.alignment = alignment - - - self.addSubview(button) - - } - - override open func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - - button.set(background: presentation.colors.background, for: .Normal) - } - - open override func layout() { - switch alignment { - case .Center: - button.sizeToFit(NSZeroSize,NSMakeSize(frame.width, frame.height - .borderSize)) - case .Left: - button.sizeToFit(NSZeroSize,NSMakeSize(frame.width, frame.height - .borderSize)) - case .Right: - button.sizeToFit(NSZeroSize,NSMakeSize(frame.width - 20, frame.height - .borderSize), thatFit: true) - let f = focus(button.frame.size) - button.setFrameOrigin(NSMakePoint(frame.width - button.frame.width - 16, f.minY)) - } - super.layout() - } - - - - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/TextView.swift b/TGUIKit/TGUIKit/TextView.swift deleted file mode 100644 index b1445a8732..0000000000 --- a/TGUIKit/TGUIKit/TextView.swift +++ /dev/null @@ -1,1116 +0,0 @@ -// -// TextView.swift -// TGUIKit -// -// Created by keepcoder on 15/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - - -private enum CornerType { - case topLeft - case topRight - case bottomLeft - case bottomRight -} - -private func drawFullCorner(context: CGContext, color: NSColor, at point: CGPoint, type: CornerType, radius: CGFloat) { - context.setFillColor(color.cgColor) - switch type { - case .topLeft: - context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius))) - context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .topRight: - context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .bottomLeft: - context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .bottomRight: - context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - } -} - -private func drawConnectingCorner(context: CGContext, color: NSColor, at point: CGPoint, type: CornerType, radius: CGFloat) { - context.setFillColor(color.cgColor) - switch type { - case .topLeft: - context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) - context.setFillColor(NSColor.clear.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .topRight: - context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius))) - context.setFillColor(NSColor.clear.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .bottomLeft: - context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) - context.setFillColor(NSColor.clear.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - case .bottomRight: - context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) - context.setFillColor(NSColor.clear.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) - } -} - -private func generateRectsImage(color: NSColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, CGImage?) { - if rects.isEmpty { - return (CGPoint(), nil) - } - - var topLeft = rects[0].origin - var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY) - for i in 1 ..< rects.count { - topLeft.x = min(topLeft.x, rects[i].origin.x) - topLeft.y = min(topLeft.y, rects[i].origin.y) - bottomRight.x = max(bottomRight.x, rects[i].maxX) - bottomRight.y = max(bottomRight.y, rects[i].maxY) - } - - topLeft.x -= inset - topLeft.y -= inset - bottomRight.x += inset - bottomRight.y += inset - - return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) - - context.setBlendMode(.copy) - - for i in 0 ..< rects.count { - let rect = rects[i].insetBy(dx: -inset, dy: -inset) - context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) - } - - for i in 0 ..< rects.count { - let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - - var previous: CGRect? - if i != 0 { - previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - } - - var next: CGRect? - if i != rects.count - 1 { - next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - } - - if let previous = previous { - if previous.contains(rect.topLeft) { - if abs(rect.topLeft.x - previous.minX) >= innerRadius { - var radius = innerRadius - if let next = next { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) - } - if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { - if abs(rect.topRight.x - previous.maxX) >= innerRadius { - var radius = innerRadius - if let next = next { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) - drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) - } - - if let next = next { - if next.contains(rect.bottomLeft) { - if abs(rect.bottomRight.x - next.maxX) >= innerRadius { - var radius = innerRadius - if let previous = previous { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) - } - if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { - if abs(rect.bottomRight.x - next.maxX) >= innerRadius { - var radius = innerRadius - if let previous = previous { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) - drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) - } - } - })) - -} - - - -public final class TextViewInteractions { - public var processURL:(Any!)->Void // link, isPresent - public var copy:(()->Bool)? - public var menuItems:(()->Signal<[ContextMenuItem], Void>)? - public init(processURL:@escaping (Any!)->Void = {_ in}, copy:(()-> Bool)? = nil, menuItems:(()->Signal<[ContextMenuItem], Void>)? = nil) { - self.processURL = processURL - self.copy = copy - self.menuItems = menuItems - } -} - -public final class TextViewLine { - public let line: CTLine - public let frame: NSRect - - init(line: CTLine, frame: CGRect) { - self.line = line - self.frame = frame - } - -} - - -public enum TextViewCutoutPosition { - case TopLeft - case TopRight -} - -public struct TextViewCutout: Equatable { - public let position: TextViewCutoutPosition - public let size: NSSize - public init(position:TextViewCutoutPosition, size:NSSize) { - self.position = position - self.size = size - } -} - -public func ==(lhs: TextViewCutout, rhs: TextViewCutout) -> Bool { - return lhs.position == rhs.position && lhs.size == rhs.size -} - -private let defaultFont:NSFont = .normal(.text) - -public final class TextViewLayout : Equatable { - - - public fileprivate(set) var attributedString:NSAttributedString - public fileprivate(set) var constrainedWidth:CGFloat = 0 - public var interactions:TextViewInteractions = TextViewInteractions() - public var selectedRange:TextSelectedRange = TextSelectedRange() - public var penFlush:CGFloat - public var insets:NSSize = NSZeroSize - public fileprivate(set) var lines:[TextViewLine] = [] - public fileprivate(set) var isPerfectSized:Bool = true - public let maximumNumberOfLines:Int32 - public let truncationType:CTLineTruncationType - public var cutout:TextViewCutout? - - fileprivate var monospacedImage:(CGPoint, CGImage?) = (CGPoint(), nil) - - public fileprivate(set) var lineSpacing:CGFloat? - - public private(set) var layoutSize:NSSize = NSZeroSize - public private(set) var perfectSize:NSSize = NSZeroSize - public init(_ attributedString:NSAttributedString, constrainedWidth:CGFloat = 0, maximumNumberOfLines:Int32 = INT32_MAX, truncationType: CTLineTruncationType = .end, cutout:TextViewCutout? = nil, alignment:NSTextAlignment = .left, lineSpacing:CGFloat? = nil) { - self.truncationType = truncationType - self.maximumNumberOfLines = maximumNumberOfLines - self.cutout = cutout - self.attributedString = attributedString - self.constrainedWidth = constrainedWidth - - switch alignment { - case .center: - penFlush = 0.5 - case .right: - penFlush = 1.0 - default: - penFlush = 0.0 - } - self.lineSpacing = lineSpacing - } - - func calculateLayout() -> Void { - - isPerfectSized = true - - let font: CTFont - if attributedString.length != 0 { - if let stringFont = attributedString.attribute(NSAttributedStringKey(kCTFontAttributeName as String), at: 0, effectiveRange: nil) { - font = stringFont as! CTFont - } else { - font = defaultFont - } - } else { - font = defaultFont - } - - self.lines.removeAll() - - let fontAscent = CTFontGetAscent(font) - let fontDescent = CTFontGetDescent(font) - - let fontLineHeight = floor(fontAscent + fontDescent) - - var monospacedRects:[NSRect] = [] - - var fontLineSpacing:CGFloat = floor(fontLineHeight * 0.12) - - - var maybeTypesetter: CTTypesetter? - maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) - - let typesetter = maybeTypesetter! - - var lastLineCharacterIndex: CFIndex = 0 - var layoutSize = NSSize() - - var cutoutEnabled = false - var cutoutMinY: CGFloat = 0.0 - var cutoutMaxY: CGFloat = 0.0 - var cutoutWidth: CGFloat = 0.0 - var cutoutOffset: CGFloat = 0.0 - if let cutout = cutout { - cutoutMinY = -fontLineSpacing - cutoutMaxY = cutout.size.height + fontLineSpacing - cutoutWidth = cutout.size.width - if case .TopLeft = cutout.position { - cutoutOffset = cutoutWidth - } - cutoutEnabled = true - } - - var first = true - var breakInset: CGFloat = 0 - var isWasPreformatted: Bool = false - while true { - var lineConstrainedWidth = constrainedWidth - var lineOriginY: CGFloat = 0 - - var lineCutoutOffset: CGFloat = 0.0 - var lineAdditionalWidth: CGFloat = 0.0 - - var isPreformattedLine: CGFloat? = nil - - fontLineSpacing = floor(fontLineHeight * 0.12) - - - - if attributedString.length > 0, let space = (attributedString.attribute(.preformattedPre, at: min(lastLineCharacterIndex, attributedString.length - 1), effectiveRange: nil) as? NSNumber) { - breakInset = CGFloat(space.floatValue * 2) - lineCutoutOffset += CGFloat(space.floatValue) - lineAdditionalWidth += breakInset - - lineOriginY += CGFloat(space.floatValue/2) - - if !isWasPreformatted && !first { - lineOriginY += CGFloat(space.floatValue) - fontLineSpacing = CGFloat(space.floatValue) - fontLineSpacing - } else { - if isWasPreformatted || first { - fontLineSpacing = -CGFloat(space.floatValue/2) - lineOriginY -= (CGFloat(space.floatValue + space.floatValue/2)) - } - } - - isPreformattedLine = CGFloat(space.floatValue) - isWasPreformatted = true - } else { - - if isWasPreformatted && !first { - lineOriginY -= (2 - fontLineSpacing) - } - - isWasPreformatted = false - } - - lineOriginY += floor(layoutSize.height + fontLineHeight - fontLineSpacing * 2.0) - - if !first { - lineOriginY += fontLineSpacing - } - - if cutoutEnabled { - if lineOriginY < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY { - lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth) - lineCutoutOffset = cutoutOffset - lineAdditionalWidth = cutoutWidth - } - } - - - - let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth - breakInset)) - - - var lineHeight = fontLineHeight - - let lineString = attributedString.attributedSubstring(from: NSMakeRange(lastLineCharacterIndex, lineCharacterCount)) - if !lineString.string.emojiString.isEmpty { - lineHeight += floor(fontDescent) - if first { - lineOriginY += floor(fontDescent) - } - } - - if maximumNumberOfLines != 0 && lines.count == (Int(maximumNumberOfLines) - 1) && lineCharacterCount > 0 { - if first { - first = false - } else { - layoutSize.height += fontLineSpacing - } - - let coreTextLine: CTLine - - let originalLine = CTTypesetterCreateLineWithOffset(typesetter, CFRange(location: lastLineCharacterIndex, length: attributedString.length - lastLineCharacterIndex), 0.0) - - if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedWidth) { - coreTextLine = originalLine - } else { - var truncationTokenAttributes: [NSAttributedStringKey : Any] = [:] - truncationTokenAttributes[NSAttributedStringKey(kCTFontAttributeName as String)] = font - truncationTokenAttributes[NSAttributedStringKey(kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber - let tokenString = "\u{2026}" - let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) - let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) - - coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(constrainedWidth), truncationType, truncationToken) ?? truncationToken - isPerfectSized = false - } - - - let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) - let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: lineHeight) - layoutSize.height += lineHeight + fontLineSpacing - layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) - - lines.append(TextViewLine(line: coreTextLine, frame: lineFrame)) - - break - } else { - if lineCharacterCount > 0 { - - - if first { - first = false - } else { - layoutSize.height += fontLineSpacing - } - - let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastLineCharacterIndex, lineCharacterCount), 100.0) - - - let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) - let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: lineHeight) - layoutSize.height += lineHeight - layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) - - if let space = lineString.attribute(.preformattedPre, at: 0, effectiveRange: nil) as? NSNumber { - - layoutSize.width = self.constrainedWidth - let preformattedSpace = CGFloat(space.floatValue) * 2 - - monospacedRects.append(NSMakeRect(0, lineFrame.minY - lineFrame.height, layoutSize.width, lineFrame.height + preformattedSpace)) - } - - lines.append(TextViewLine(line: coreTextLine, frame: lineFrame)) - lastLineCharacterIndex += lineCharacterCount - } else { - if !lines.isEmpty { - layoutSize.height += fontLineSpacing - } - break - } - } - - - if let isPreformattedLine = isPreformattedLine { - layoutSize.height += isPreformattedLine * 2 - if lastLineCharacterIndex == attributedString.length { - layoutSize.height += isPreformattedLine/2 - } - // fontLineSpacing = isPreformattedLine - } - - } - - let sortedIndices = (0 ..< monospacedRects.count).sorted(by: { monospacedRects[$0].width > monospacedRects[$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(monospacedRects[index + j].width - monospacedRects[index].width) < 40.0 { - monospacedRects[index + j].size.width = max(monospacedRects[index + j].width, monospacedRects[index].width) - } - } - } - } - - self.monospacedImage = generateRectsImage(color: presentation.colors.grayBackground, rects: monospacedRects, inset: 0, outerRadius: .cornerRadius, innerRadius: .cornerRadius) - - //self.monospacedStrokeImage = generateRectsImage(color: presentation.colors.border, rects: monospacedRects, inset: 0, outerRadius: .cornerRadius, innerRadius: .cornerRadius) - - - self.layoutSize = layoutSize - } - - public func measure(width: CGFloat = 0) -> Void { - - if width != 0 { - constrainedWidth = width - } - calculateLayout() - - } - - public func clearSelect() { - self.selectedRange.range = NSMakeRange(NSNotFound, 0) - } - - public func selectedRange(startPoint:NSPoint, currentPoint:NSPoint) -> NSRange { - - var selectedRange:NSRange = NSMakeRange(NSNotFound, 0) - - if (currentPoint.x != -1 && currentPoint.y != -1 && !lines.isEmpty) { - - - let startSelectLineIndex = findIndex(location: startPoint) - let currentSelectLineIndex = findIndex(location: currentPoint) - let dif = abs(startSelectLineIndex - currentSelectLineIndex) - let isReversed = currentSelectLineIndex < startSelectLineIndex - var i = startSelectLineIndex - while isReversed ? i >= currentSelectLineIndex : i <= currentSelectLineIndex { - let line = lines[i].line - let lineRange = CTLineGetStringRange(line) - var startIndex: CFIndex = CTLineGetStringIndexForPosition(line, startPoint) - var endIndex: CFIndex = CTLineGetStringIndexForPosition(line, currentPoint) - if dif > 0 { - if i != currentSelectLineIndex { - endIndex = (lineRange.length + lineRange.location) - } - if i != startSelectLineIndex { - startIndex = lineRange.location - } - if isReversed { - if i == startSelectLineIndex { - endIndex = startIndex - startIndex = lineRange.location - } - if i == currentSelectLineIndex { - startIndex = endIndex - endIndex = (lineRange.length + lineRange.location) - } - } - } - if startIndex > endIndex { - startIndex = endIndex + startIndex - endIndex = startIndex - endIndex - startIndex = startIndex - endIndex - } - if abs(Int(startIndex) - Int(endIndex)) > 0 && (selectedRange.location == NSNotFound || selectedRange.location > startIndex) { - selectedRange.location = startIndex - } - selectedRange.length += (endIndex - startIndex) - i += isReversed ? -1 : 1 - } - } - return selectedRange - } - - - public func findIndex(location:NSPoint) -> Int { - - if location.y == .greatestFiniteMagnitude { - return lines.count - 1 - } else if location.y == 0 { - return 0 - } - - for idx in 0 ..< lines.count { - if isCurrentLine(pos: location, index: idx) { - return idx - } - } - - return location.y <= layoutSize.height ? 0 : (lines.count - 1) - - } - - public func inSelectedRange(_ location:NSPoint) -> Bool { - let index = findCharacterIndex(at: location) - return selectedRange.range.location < index && selectedRange.range.location + selectedRange.range.length > index - } - - public func isCurrentLine(pos:NSPoint, index:Int) -> Bool { - - let line = lines[index] - var rect = line.frame - - var ascent:CGFloat = 0 - var descent:CGFloat = 0 - var leading:CGFloat = 0 - - CTLineGetTypographicBounds(line.line, &ascent, &descent, &leading) - - rect.origin.y = rect.minY - rect.height + ceil(descent - leading) - rect.size.height += ceil(descent - leading) - - return (pos.y > rect.minY) && pos.y < rect.maxY - - } - - public func link(at point:NSPoint) -> (Any, NSRect)? { - - let index = findIndex(location: point) - - guard index != -1 else { - return nil - } - - let line = lines[index] - var ascent:CGFloat = 0 - var descent:CGFloat = 0 - var leading:CGFloat = 0 - - let width:CGFloat = CGFloat(CTLineGetTypographicBounds(line.line, &ascent, &descent, &leading)); - - if width > point.x { - var pos = CTLineGetStringIndexForPosition(line.line, point); - pos = min(max(0,pos),attributedString.length - 1) - var range:NSRange = NSMakeRange(NSNotFound, 0) - let attrs = attributedString.attributes(at: pos, effectiveRange: &range) - - let link:Any? = attrs[NSAttributedStringKey.link] - - if let link = link { - let startOffset = CTLineGetOffsetForStringIndex(line.line, range.location, nil); - let endOffset = CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil); - return (link, NSMakeRect(startOffset, line.frame.minY, endOffset - startOffset, ceil(ascent + ceil(descent) + leading))) - } - } - return nil - } - - func findCharacterIndex(at point:NSPoint) -> Int { - let index = findIndex(location: point) - - guard index != -1 else { - return -1 - } - - let line = lines[index] - let width:CGFloat = CGFloat(CTLineGetTypographicBounds(line.line, nil, nil, nil)); - if width > point.x { - let charIndex = Int(CTLineGetStringIndexForPosition(line.line, point)) - return charIndex == attributedString.length ? charIndex - 1 : charIndex - } - return -1 - } - - public func selectAll(at point:NSPoint) -> Void { - - let startIndex = findCharacterIndex(at: point) - if startIndex == -1 { - return - } - - var blockRange: NSRange = NSMakeRange(NSNotFound, 0) - if let _ = attributedString.attribute(.preformattedPre, at: startIndex, effectiveRange: &blockRange) { - self.selectedRange = TextSelectedRange(range: blockRange, color: .selectText, def: true) - } else { - self.selectedRange = TextSelectedRange(range: NSMakeRange(0,attributedString.length), color: .selectText, def: true) - } - - } - - public func selectWord(at point:NSPoint) -> Void { - let startIndex = findCharacterIndex(at: point) - if startIndex == -1 { - return - } - var prev = startIndex - var next = startIndex - var range = NSMakeRange(startIndex, 1) - let char:NSString = attributedString.string.nsstring.substring(with: range) as NSString - var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) - let check = attributedString.attribute(NSAttributedStringKey.link, at: range.location, effectiveRange: &effectiveRange) - if check != nil && effectiveRange.location != NSNotFound { - self.selectedRange = TextSelectedRange(range: effectiveRange, color: presentation.colors.selectText, def: true) - return - } - if char == "" { - self.selectedRange = TextSelectedRange() - return - } - let valid:Bool = char.trimmingCharacters(in: NSCharacterSet.alphanumerics) == "" || char == "_" - let string:NSString = attributedString.string.nsstring - while valid { - let prevChar = string.substring(with: NSMakeRange(prev, 1)) - let nextChar = string.substring(with: NSMakeRange(next, 1)) - var prevValid:Bool = prevChar.trimmingCharacters(in: NSCharacterSet.alphanumerics) == "" || prevChar == "_" - var nextValid:Bool = nextChar.trimmingCharacters(in: NSCharacterSet.alphanumerics) == "" || nextChar == "_" - if (prevValid && prev > 0) { - prev -= 1 - } - if(nextValid && next < string.length - 1) { - next += 1 - } - range.location = prevValid ? prev : prev + 1; - range.length = next - range.location; - if prev == 0 { - prevValid = false - } - if(next == string.length - 1) { - nextValid = false - range.length += 1 - } - if !prevValid && !nextValid { - break - } - if prev == 0 && !nextValid { - break - } - } - - self.selectedRange = TextSelectedRange(range: range, color: presentation.colors.selectText, def: true) - } - - -} - -public func ==(lhs:TextViewLayout, rhs:TextViewLayout) -> Bool { - return lhs.constrainedWidth == rhs.constrainedWidth && lhs.attributedString.isEqual(to: rhs.attributedString) && lhs.selectedRange == rhs.selectedRange && lhs.maximumNumberOfLines == rhs.maximumNumberOfLines && lhs.cutout == rhs.cutout && lhs.truncationType == rhs.truncationType && lhs.constrainedWidth == rhs.constrainedWidth -} - -public struct TextSelectedRange: Equatable { - public var range:NSRange = NSMakeRange(NSNotFound, 0) - public var color:NSColor = presentation.colors.selectText - public var def:Bool = true - - public var hasSelectText:Bool { - return range.location != NSNotFound - } -} - -public func ==(lhs:TextSelectedRange, rhs:TextSelectedRange) -> Bool { - return lhs.def == rhs.def && lhs.range.location == rhs.range.location && lhs.range.length == rhs.range.length -} - - -public class TextView: Control { - - private let menuDisposable = MetaDisposable() - - private(set) public var layout:TextViewLayout? - - private var beginSelect:NSPoint = NSZeroPoint - private var endSelect:NSPoint = NSZeroPoint - - public var canBeResponder:Bool = true - - public var isSelectable:Bool = true { - didSet { - if oldValue != isSelectable { - self.setNeedsDisplayLayer() - } - } - } - - - public override init() { - super.init(); - self.style = ControlStyle(backgroundColor:.white) -// wantsLayer = false -// self.layer?.delegate = nil - } - - public override var isFlipped: Bool { - return true - } - - public required init(frame frameRect: NSRect) { - super.init(frame:frameRect) - self.style = ControlStyle(backgroundColor:.white) -// wantsLayer = false -// self.layer?.delegate = nil - // self.layer?.drawsAsynchronously = System.drawAsync - } - - - public override func draw(_ layer: CALayer, in ctx: CGContext) { - //backgroundColor = .random - super.draw(layer, in: ctx) - - if let layout = layout { - - - ctx.setAllowsAntialiasing(true) - - ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) - ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) - - - if let image = layout.monospacedImage.1 { - ctx.draw(image, in: NSMakeRect(layout.monospacedImage.0.x, layout.monospacedImage.0.y, image.backingSize.width, image.backingSize.height)) - } - - - - if layout.selectedRange.range.location != NSNotFound && isSelectable { - - var lessRange = layout.selectedRange.range - - var lines:[TextViewLine] = layout.lines - - let beginIndex:Int = 0 - let endIndex:Int = layout.lines.count - 1 - - - let isReversed = endIndex < beginIndex - - var i:Int = beginIndex - - while isReversed ? i >= endIndex : i <= endIndex { - - - let line = lines[i].line - var rect:NSRect = lines[i].frame - let lineRange = CTLineGetStringRange(line) - - var beginLineIndex:CFIndex = 0 - var endLineIndex:CFIndex = 0 - - if (lineRange.location + lineRange.length >= lessRange.location) && lessRange.length > 0 { - beginLineIndex = lessRange.location - let max = lineRange.length + lineRange.location - let maxSelect = max - beginLineIndex - - let selectLength = min(maxSelect,lessRange.length) - - lessRange.length-=selectLength - lessRange.location+=selectLength - - endLineIndex = beginLineIndex + selectLength - - var ascent:CGFloat = 0 - var descent:CGFloat = 0 - var leading:CGFloat = 0 - - var width:CGFloat = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading)); - - let startOffset = CTLineGetOffsetForStringIndex(line, beginLineIndex, nil); - let endOffset = CTLineGetOffsetForStringIndex(line, endLineIndex, nil); - - width = endOffset - startOffset; - - let blockValue:CGFloat = CGFloat((layout.attributedString.attribute(.preformattedPre, at: beginLineIndex, effectiveRange: nil) as? NSNumber)?.floatValue ?? 0) - - - - rect.size.width = width - blockValue / 2 - - rect.origin.x = startOffset + blockValue - rect.origin.y = rect.minY - rect.height + blockValue / 2 - rect.size.height += ceil(descent - leading) - let color:NSColor = presentation.colors.selectText - - ctx.setFillColor(color.cgColor) - ctx.fill(rect) - } - - i += isReversed ? -1 : 1 - - } - - } - - let textMatrix = ctx.textMatrix - let textPosition = ctx.textPosition - let startPosition = focus(layout.layoutSize).origin - - - - ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - - for i in 0 ..< layout.lines.count { - let line = layout.lines[i] - - let penOffset = CGFloat( CTLineGetPenOffsetForFlush(line.line, layout.penFlush, Double(frame.width))) + line.frame.minX - - ctx.textPosition = CGPoint(x: penOffset, y: startPosition.y + line.frame.minY) - CTLineDraw(line.line, ctx) - - } - - ctx.textMatrix = textMatrix - ctx.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) - - } - - } - - - public override func rightMouseDown(with event: NSEvent) { - if let layout = layout, userInteractionEnabled { - if !layout.selectedRange.hasSelectText || !layout.inSelectedRange(convert(event.locationInWindow, from: nil)) { - layout.selectWord(at : self.convert(event.locationInWindow, from: nil)) - } - self.setNeedsDisplayLayer() - if layout.selectedRange.hasSelectText { - if let menuItems = layout.interactions.menuItems?() { - menuDisposable.set((menuItems |> deliverOnMainQueue).start(next:{ [weak self] items in - if let strongSelf = self { - let menu = NSMenu() - for item in items { - menu.addItem(item) - } - NSMenu.popUpContextMenu(menu, with: event, for: strongSelf) - } - })) - } else { - let menu = NSMenu() - let copy = NSMenuItem(title: localizedString("Text.Copy"), action: #selector(copy(_:)), keyEquivalent: "") - menu.addItem(copy) - NSMenu.popUpContextMenu(menu, with: event, for: self) - } - } else { - super.rightMouseDown(with: event) - } - } else { - super.rightMouseDown(with: event) - } - } - - /* - var view: NSTextView? = (self.window?.fieldEditor(true, forObject: self) as? NSTextView) - view?.isEditable = false - view?.isSelectable = true - view?.string = layout.attributedString.string - view?.selectedRange = NSRange(location: 0, length: view?.string?.length) - NSMenu.popUpContextMenu(view?.menu(for: event), with: event, for: view) - */ - - public override func menu(for event: NSEvent) -> NSMenu? { - - return nil - } - - deinit { - menuDisposable.dispose() - } - - public func isEqual(to layout:TextViewLayout) -> Bool { - return self.layout == layout - } - - public func update(_ layout:TextViewLayout?, origin:NSPoint? = nil) -> Void { - self.layout = layout - - - if let layout = layout { - self.set(selectedRange: layout.selectedRange.range, display: false) - let point:NSPoint - if let origin = origin { - point = origin - } else { - point = frame.origin - } - self.frame = NSMakeRect(point.x, point.y, layout.layoutSize.width + layout.insets.width, layout.layoutSize.height + layout.insets.height) - } else { - self.set(selectedRange: NSMakeRange(NSNotFound, 0), display: false) - } - self.setNeedsDisplayLayer() - } - - public func set(layout:TextViewLayout?) { - self.layout = layout - self.setNeedsDisplayLayer() - } - - func set(selectedRange range:NSRange, display:Bool = true) -> Void { - - - layout?.selectedRange = TextSelectedRange(range:range, color:.selectText, def:true) - - beginSelect = NSMakePoint(-1, -1) - endSelect = NSMakePoint(-1, -1) - - - if display { - self.setNeedsDisplayLayer() - } - - } - - public override func mouseDown(with event: NSEvent) { - if isSelectable { - self.window?.makeFirstResponder(nil) - } - super.mouseDown(with: event) - _mouseDown(with: event) - } - - func _mouseDown(with event: NSEvent) -> Void { - - if !isSelectable || !userInteractionEnabled { - return - } - - _ = self.becomeFirstResponder() - - set(selectedRange: NSMakeRange(NSNotFound, 0), display: false) - self.beginSelect = self.convert(event.locationInWindow, from: nil) - - self.setNeedsDisplayLayer() - - } - - public override func mouseDragged(with event: NSEvent) { - super.mouseDragged(with: event) - checkCursor(event) - _mouseDragged(with: event) - } - - func _mouseDragged(with event: NSEvent) -> Void { - if !isSelectable || !userInteractionEnabled { - return - } - - endSelect = self.convert(event.locationInWindow, from: nil) - if let layout = layout { - layout.selectedRange.range = layout.selectedRange(startPoint: beginSelect, currentPoint: endSelect) - } - self.setNeedsDisplayLayer() - } - - - public override func mouseEntered(with event: NSEvent) { - super.mouseEntered(with: event) - checkCursor(event) - } - - public override func mouseExited(with event: NSEvent) { - super.mouseExited(with: event) - checkCursor(event) - } - - public override func mouseMoved(with event: NSEvent) { - super.mouseMoved(with: event) - checkCursor(event) - } - - - - public override func mouseUp(with event: NSEvent) { - super.mouseUp(with: event) - - if let layout = layout, userInteractionEnabled { - let point = self.convert(event.locationInWindow, from: nil) - if event.clickCount == 3 { - layout.selectAll(at: point) - } else if event.clickCount == 2 || (event.type == .rightMouseUp && !layout.selectedRange.hasSelectText) { - layout.selectWord(at : point) - } else if !layout.selectedRange.hasSelectText || !isSelectable && event.clickCount == 1 { - if let (link,_) = layout.link(at: point) { - layout.interactions.processURL(link) - } - } - setNeedsDisplay() - } - } - - - - func checkCursor(_ event:NSEvent) -> Void { - let location = self.convert(event.locationInWindow, from: nil) - - if self.mouse(location , in: self.visibleRect) && mouseInside() && userInteractionEnabled { - - if let layout = layout, let (_, _) = layout.link(at: location) { - NSCursor.pointingHand.set() - } else if isSelectable { - NSCursor.iBeam.set() - } else { - NSCursor.arrow.set() - } - } else { - NSCursor.arrow.set() - } - } - - - - - public override func becomeFirstResponder() -> Bool { - if canBeResponder { - if let window = self.window { - return window.makeFirstResponder(self) - } - } - - - return false - } - - - public override func resignFirstResponder() -> Bool { - _resignFirstResponder() - return super.resignFirstResponder() - } - - func _resignFirstResponder() -> Void { - self.set(selectedRange: NSMakeRange(NSNotFound, 0)) - } - - public override func responds(to aSelector: Selector!) -> Bool { - - if NSStringFromSelector(aSelector) == "copy:" { - return self.layout?.selectedRange.range.location != NSNotFound - } - - return super.responds(to: aSelector) - } - - @objc public func copy(_ sender:Any) -> Void { - if let layout = layout, layout.selectedRange.range.location != NSNotFound { - if let copy = layout.interactions.copy { - if !copy() { - let pb = NSPasteboard.general - pb.declareTypes([.string], owner: self) - pb.setString(layout.attributedString.string.nsstring.substring(with: layout.selectedRange.range), forType: .string) - } - } else { - let pb = NSPasteboard.general - pb.declareTypes([.string], owner: self) - pb.setString(layout.attributedString.string.nsstring.substring(with: layout.selectedRange.range), forType: .string) - } - } - } - - @objc func paste(_ sender:Any) { - - } - - - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - -} diff --git a/TGUIKit/TGUIKit/TitleButton.swift b/TGUIKit/TGUIKit/TitleButton.swift deleted file mode 100644 index 9242139a47..0000000000 --- a/TGUIKit/TGUIKit/TitleButton.swift +++ /dev/null @@ -1,242 +0,0 @@ -// -// TitleButton.swift -// TGUIKit -// -// Created by keepcoder on 05/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public enum TitleButtonImageDirection { - case left - case right -} - -class TextLayerExt: CATextLayer { - - override func draw(in ctx: CGContext) { - ctx.setAllowsAntialiasing(true) - ctx.setShouldAntialias(true) - ctx.setShouldSmoothFonts(false) - ctx.setAllowsFontSmoothing(false) - super.draw(in: ctx) - } - -} - - -public class TitleButton: ImageButton { - - private var text:TextLayerExt = TextLayerExt() - - private var stateText:[ControlState:String] = [:] - private var stateColor:[ControlState:NSColor] = [:] - private var stateFont:[ControlState:NSFont] = [:] - - - public var direction: TitleButtonImageDirection = .left { - didSet { - if direction != oldValue { - updateLayout() - } - } - } - - public override init() { - super.init() - } - - public func set(text:String, for state:ControlState) -> Void { - stateText[state] = text - apply(state: self.controlState) - sizeToFit(NSZeroSize, self.frame.size) - } - - public func set(color:NSColor, for state:ControlState) -> Void { - stateColor[state] = color - apply(state: self.controlState) - } - - public func set(font:NSFont, for state:ControlState) -> Void { - stateFont[state] = font - apply(state: self.controlState) - } - - override public func apply(state: ControlState) { - let state:ControlState = self.isSelected ? .Highlight : state - super.apply(state: state) - - if let stateText = stateText[state] { - text.string = stateText - } else { - text.string = stateText[.Normal] - } - - if isEnabled { - if let stateColor = stateColor[state] { - text.foregroundColor = stateColor.cgColor - } else if let stateColor = stateColor[.Normal] { - text.foregroundColor = stateColor.cgColor - } else { - text.foregroundColor = style.foregroundColor.cgColor - } - } else { - text.foregroundColor = presentation.colors.grayText.cgColor - } - - - if let stateFont = stateFont[state] { - text.font = stateFont.fontName as CFTypeRef - text.fontSize = stateFont.pointSize - } else if let stateFont = stateFont[.Normal] { - text.font = stateFont.fontName as CFTypeRef - text.fontSize = stateFont.pointSize - } else { - text.font = style.font.fontName as CFTypeRef - text.fontSize = style.font.pointSize - } - - } - - public override func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) { - super.sizeToFit(addition) - - - let size:NSSize = self.size(with: self.text.string as! String?, font:NSFont(name: self.text.font as! String, size: text.fontSize)) - - var msize:NSSize = size - - if maxSize.width < size.width { - if let image = imageView.image { - msize.width += (image.backingSize.width + 12) // max size - } - } - - let maxWidth:CGFloat = !thatFit ? ( maxSize.width > 0 ? maxSize.width : msize.width ) : min(maxSize.width, size.width) - - - - var textSize:CGFloat = maxWidth - - if let image = imageView.image { - - textSize = min(maxWidth,size.width) - - let iwidth:CGFloat = (image.backingSize.width + 12) - - if textSize == maxWidth { - textSize -= iwidth - } else { - textSize = (maxWidth - size.width) >= iwidth ? size.width : maxWidth - iwidth - } - } - - - - self.text.frame = NSMakeRect(0, 0, textSize, size.height) - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: maxWidth, height: max(size.height,maxSize.height)) - - } - - public override func updateLayout() { - super.updateLayout() - - let textFocus:NSRect = focus(self.text.frame.size) - if let _ = imageView.image { - let imageFocus:NSRect = focus(self.imageView.frame.size) - switch direction { - case .left: - self.imageView.frame = NSMakeRect(round((self.frame.width - textFocus.width - imageFocus.width)/2.0 - 6.0), imageFocus.minY, imageFocus.width, imageFocus.height) - self.text.frame = NSMakeRect(imageView.frame.maxX + 6.0, textFocus.minY, textFocus.width, textFocus.height) - case .right: - self.imageView.frame = NSMakeRect(round(frame.width - imageFocus.width - 6.0), imageFocus.minY, imageFocus.width, imageFocus.height) - self.text.frame = NSMakeRect(0, textFocus.minY, textFocus.width, textFocus.height) - } - - } else { - self.text.frame = textFocus - } - - } - - - func size(with string: String?, font: NSFont?) -> NSSize { - if font == nil || string == nil { - return NSZeroSize - } - let attributedString:NSAttributedString = NSAttributedString.initialize(string: string, font: font, coreText: true) - var size:NSSize = attributedString.CTSize(CGFloat.greatestFiniteMagnitude, framesetter: nil).1 - size.width = ceil(size.width) + 10 - size.height = ceil(size.height) - return size - } - - - public override var style: ControlStyle { - set { - super.style = newValue -// -// self.set(color: style.foregroundColor, for: .Normal) -// self.set(color: style.highlightColor, for: .Highlight) -// self.set(font: style.font, for: .Normal) -// self.backgroundColor = style.backgroundColor - } - get { - return super.style - } - } - - override func prepare() { - super.prepare() - text.truncationMode = "middle"; - text.alignmentMode = "center"; - self.layer?.addSublayer(text) - - text.actions = ["bounds":NSNull(),"position":NSNull()] - } - - public override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - } - - public required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - } - - public override var backgroundColor: NSColor { - set { - super.backgroundColor = newValue - self.text.backgroundColor = newValue.cgColor - } - get { - return super.backgroundColor - } - } - - public override func viewDidChangeBackingProperties() { - super.viewDidChangeBackingProperties() - if let screen = NSScreen.main { - self.text.contentsScale = screen.backingScaleFactor - } - - } - - public override func disableActions() { - super.disableActions() - - self.text.disableActions() - self.layer?.disableActions() - } - - public override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - } - - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/TitledBarView.swift b/TGUIKit/TGUIKit/TitledBarView.swift deleted file mode 100644 index f90691d2e4..0000000000 --- a/TGUIKit/TGUIKit/TitledBarView.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// TitledBarView.swift -// TGUIKit -// -// Created by keepcoder on 16/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -private class TitledContainerView : View { - - private var statusNode:TextNode = TextNode() - private var titleNode:TextNode = TextNode() - var titleImage:CGImage? { - didSet { - self.setNeedsDisplay() - } - } - - var inset:CGFloat = 50 - - var text:NSAttributedString? { - didSet { - if text != oldValue { - self.setNeedsDisplay() - } - } - } - - var status:NSAttributedString? { - didSet { - if status != oldValue { - self.setNeedsDisplay() - } - } - } - - var hiddenStatus:Bool = false { - didSet { - self.setNeedsDisplay() - } - } - - var textInset:CGFloat? = nil { - didSet { - self.setNeedsDisplay() - } - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - backgroundColor = presentation.colors.background - } - - fileprivate override func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(presentation.colors.background.cgColor) - ctx.fill(bounds) - if let text = text { - let (textLayout, textApply) = TextNode.layoutText(maybeNode: titleNode, text, nil, 1, .end, NSMakeSize(NSWidth(layer.bounds) - inset, NSHeight(layer.bounds)), nil,false, .left) - var tY = NSMinY(focus(textLayout.size)) - - if let status = status { - - let (statusLayout, statusApply) = TextNode.layoutText(maybeNode: statusNode, status, nil, 1, .end, NSMakeSize(NSWidth(layer.bounds) - inset, NSHeight(layer.bounds)), nil,false, .left) - - let t = textLayout.size.height + statusLayout.size.height + 2.0 - tY = (NSHeight(self.frame) - t) / 2.0 - - let sY = tY + textLayout.size.height + 2.0 - if !hiddenStatus { - statusApply.draw(NSMakeRect(textInset == nil ? floorToScreenPixels((layer.bounds.width - statusLayout.size.width)/2.0) : textInset!, sY, statusLayout.size.width, statusLayout.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } - } - - var textRect = NSMakeRect(textInset == nil ? floorToScreenPixels((layer.bounds.width - textLayout.size.width)/2.0) : textInset!, tY, textLayout.size.width, textLayout.size.height) - - if let titleImage = titleImage { - ctx.draw(titleImage, in: NSMakeRect(textInset == nil ? textRect.minX - titleImage.backingSize.width : textInset!, tY + 4, titleImage.backingSize.width, titleImage.backingSize.height)) - textRect.origin.x += floorToScreenPixels(titleImage.backingSize.width) + 4 - } - - textApply.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor) - } - } -} - -open class TitledBarView: BarView { - - public var titleImage:CGImage? { - didSet { - _containerView.titleImage = titleImage - } - } - - open override var backgroundColor: NSColor { - didSet { - containerView.backgroundColor = backgroundColor - } - } - - public var text:NSAttributedString? { - didSet { - if text != oldValue { - _containerView.inset = inset - _containerView.text = text - } - } - } - - public var status:NSAttributedString? { - didSet { - if status != oldValue { - _containerView.inset = inset - _containerView.status = status - } - } - } - - private let _containerView:TitledContainerView = TitledContainerView() - public var containerView:View { - return _containerView - } - - public var hiddenStatus:Bool = false { - didSet { - _containerView.hiddenStatus = hiddenStatus - } - } - - open var inset:CGFloat { - return 50 - } - - public var textInset:CGFloat? { - didSet { - _containerView.textInset = textInset - } - } - - open override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - containerView.setFrameSize(newSize) - containerView.setNeedsDisplay() - } - public init(controller: ViewController, _ text:NSAttributedString? = nil, _ status:NSAttributedString? = nil, textInset:CGFloat? = nil) { - self.text = text - self.status = status - self.textInset = textInset - super.init(controller: controller) - addSubview(containerView) - _containerView.text = text - _containerView.status = status - _containerView.textInset = textInset - } - - open override func draw(_ dirtyRect: NSRect) { - - } - - deinit { - var bp:Int = 0 - bp += 1 - } - - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/TGUIKit/TGUIKit/TokenizedView.swift b/TGUIKit/TGUIKit/TokenizedView.swift deleted file mode 100644 index 600596dfb0..0000000000 --- a/TGUIKit/TGUIKit/TokenizedView.swift +++ /dev/null @@ -1,399 +0,0 @@ -// -// TokenizedView.swift -// TGUIKit -// -// Created by keepcoder on 07/08/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac - - -public struct SearchToken : Equatable { - public let name:String - public let uniqueId:Int64 - public init(name:String, uniqueId: Int64) { - self.name = name - self.uniqueId = uniqueId - } -} - -public func ==(lhs:SearchToken, rhs: SearchToken) -> Bool { - return lhs.name == rhs.name && lhs.uniqueId == rhs.uniqueId -} - -private class TokenView : Control { - fileprivate let token:SearchToken - private let dismiss: ImageButton = ImageButton() - private let nameView: TextView = TextView() - fileprivate var immediatlyPaste: Bool = true - override var isSelected: Bool { - didSet { - updateLocalizationAndTheme() - } - } - - init(_ token: SearchToken, maxSize: NSSize, onDismiss:@escaping()->Void, onSelect: @escaping()->Void) { - self.token = token - super.init() - self.layer?.cornerRadius = .cornerRadius - let layout = TextViewLayout(.initialize(string: token.name, color: .white, font: .normal(.title)), maximumNumberOfLines: 1) - layout.measure(width: maxSize.width - 30) - self.nameView.update(layout) - - nameView.userInteractionEnabled = false - nameView.isSelectable = false - - setFrameSize(NSMakeSize(layout.layoutSize.width + 30, maxSize.height)) - dismiss.autohighlight = false - updateLocalizationAndTheme() - needsLayout = true - addSubview(nameView) - addSubview(dismiss) - dismiss.set(handler: { _ in - onDismiss() - }, for: .Click) - set(handler: { _ in - onSelect() - }, for: .Click) - } - - fileprivate var isPerfectSized: Bool { - return nameView.layout?.isPerfectSized ?? false - } - - override func change(size: NSSize, animated: Bool = true, _ save: Bool = true, removeOnCompletion: Bool = false, duration: Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion: ((Bool) -> Void)? = nil) { - nameView.layout?.measure(width: size.width - 30) - - let size = NSMakeSize(min(((nameView.layout?.layoutSize.width ?? 0) + 30), size.width), size.height) - - super.change(size: size, animated: animated, save, duration: duration, timingFunction: timingFunction) - - let point = focus(dismiss.frame.size) - dismiss.change(pos: NSMakePoint(frame.width - 5 - dismiss.frame.width, point.minY), animated: animated) - - nameView.update(nameView.layout) - } - - override func layout() { - super.layout() - nameView.centerY(x: 5) - nameView.setFrameOrigin(5, nameView.frame.minY - 1) - dismiss.centerY(x: frame.width - 5 - dismiss.frame.width) - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - dismiss.set(image: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(NSColor.white.withAlphaComponent(0.7)), for: .Normal) - dismiss.set(image: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(NSColor.white), for: .Highlight) - dismiss.sizeToFit() - nameView.backgroundColor = isSelected ? presentation.colors.blueSelect : presentation.colors.blueFill - self.background = isSelected ? presentation.colors.blueSelect : presentation.colors.blueFill - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - required public init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } -} - -public protocol TokenizedProtocol { - func tokenizedViewDidChangedHeight(_ view: TokenizedView, height: CGFloat, animated: Bool) -} - -public class TokenizedView: ScrollView, AppearanceViewProtocol, NSTextViewDelegate { - private var tokens:[SearchToken] = [] - private let container: View = View() - private let input:SearchTextField = SearchTextField() - private(set) public var state: SearchFieldState = .None { - didSet { - stateValue.set(state) - } - } - public let stateValue: ValuePromise = ValuePromise(.None, ignoreRepeated: true) - private var selectedIndex: Int? = nil { - didSet { - for view in container.subviews { - if let view = view as? TokenView { - view.isSelected = selectedIndex != nil && view.token == tokens[selectedIndex!] - } - } - } - } - - - private let _tokensUpdater:Promise<[SearchToken]> = Promise([]) - public var tokensUpdater:Signal<[SearchToken], Void> { - return _tokensUpdater.get() - } - - private let _textUpdater:ValuePromise = ValuePromise("", ignoreRepeated: true) - public var textUpdater:Signal { - return _textUpdater.get() - } - - public var delegate: TokenizedProtocol? = nil - private let placeholder: TextView = TextView() - - public func addToken(token: SearchToken, animated: Bool) -> Void { - tokens.append(token) - - let view = TokenView(token, maxSize: NSMakeSize(100, 22), onDismiss: { [weak self] in - self?.removeToken(uniqueId: token.uniqueId, animated: true) - }, onSelect: { [weak self] in - self?.selectedIndex = self?.tokens.index(of: token) - }) - - container.addSubview(view) - layoutContainer(animated: animated) - _tokensUpdater.set(.single(tokens)) - input.string = "" - textDidChange(Notification(name: NSText.didChangeNotification)) - (contentView as? TGClipView)?.scroll(to: NSMakePoint(0, container.frame.height - frame.height), animated: animated) - } - - public func removeToken(uniqueId: Int64, animated: Bool) { - var index:Int? = nil - for i in 0 ..< tokens.count { - if tokens[i].uniqueId == uniqueId { - index = i - break - } - } - if let index = index { - tokens.remove(at: index) - for view in container.subviews { - if let view = view as? TokenView { - if view.token.uniqueId == uniqueId { - view.change(opacity: 0, animated: animated, completion: { [weak view] completed in - if completed { - view?.removeFromSuperview() - } - }) - - } - } - } - layoutContainer(animated: animated) - } - _tokensUpdater.set(.single(tokens)) - } - - private func layoutContainer(animated: Bool) { - CATransaction.begin() - - let mainw = frame.width - let between = NSMakePoint(5, 4) - var point: NSPoint = between - var extraLine: Bool = false - let count = container.subviews.count - for i in 0 ..< count { - let subview = container.subviews[i] - let next = container.subviews[min(i + 1, count - 1)] - if let token = subview as? TokenView, token.layer?.opacity != 0 { - token.change(pos: point, animated: token.immediatlyPaste ? false : animated) - if animated, token.immediatlyPaste { - token.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) - } - //token.change(size: NSMakeSize(100, token.frame.height), animated: animated) - token.immediatlyPaste = false - - point.x += subview.frame.width + between.x - - let dif = mainw - (point.x + (i == count - 1 ? mainw/3 : next.frame.width) + between.x) - if dif < between.x { - // if !token.isPerfectSized { - // token.change(size: NSMakeSize(frame.width - startPointX - between.x, token.frame.height), animated: animated) - // } - point.x = between.x - point.y += token.frame.height + between.y - } - - } - if subview == container.subviews.last { - if mainw - point.x > mainw/3 { - extraLine = true - } - } - } - - input.frame = NSMakeRect(point.x, point.y + 3, mainw - point.x - between.x, 16) - placeholder.change(pos: NSMakePoint(point.x + 6, point.y + 3), animated: animated) - placeholder.change(opacity: tokens.isEmpty ? 1.0 : 0.0, animated: animated) - let contentHeight = max(point.y + between.y + (extraLine ? 22 : 0), 30) - container.change(size: NSMakeSize(container.frame.width, contentHeight), animated: animated) - - let height = min(contentHeight, 108) - if height != frame.height { - - _change(size: NSMakeSize(mainw, height), animated: animated) - delegate?.tokenizedViewDidChangedHeight(self, height: height, animated: animated) - } - CATransaction.commit() - } - - public func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { - let range = input.selectedRange() - if range.location == 0 { - if commandSelector == #selector(moveLeft(_:)) { - if let index = selectedIndex { - selectedIndex = max(index - 1, 0) - } else { - selectedIndex = tokens.count - 1 - } - return true - } else if commandSelector == #selector(moveRight(_:)) { - if let index = selectedIndex { - if index + 1 == tokens.count { - selectedIndex = nil - input.setSelectedRange(NSMakeRange(input.string.length, 0)) - } else { - selectedIndex = index + 1 - } - return true - } - } - - if commandSelector == #selector(deleteBackward(_:)) { - if let selectedIndex = selectedIndex { - removeToken(uniqueId: tokens[selectedIndex].uniqueId, animated: true) - if selectedIndex != tokens.count { - self.selectedIndex = min(selectedIndex, tokens.count - 1) - } else { - self.selectedIndex = nil - input.setSelectedRange(NSMakeRange(input.string.length, 0)) - } - - return true - } else { - if !tokens.isEmpty { - self.selectedIndex = tokens.count - 1 - return true - } - } - - } - } - - - return false - } - - open func textDidChange(_ notification: Notification) { - - let pHidden = !input.string.isEmpty - if placeholder.isHidden != pHidden { - placeholder.isHidden = pHidden - } - _textUpdater.set(input.string) - selectedIndex = nil - } - - - - public func textDidEndEditing(_ notification: Notification) { - didResignResponder() - } - - public func textDidBeginEditing(_ notification: Notification) { - didBecomeResponder() - } - - public override var needsLayout: Bool { - set { - super.needsLayout = false - } - get { - return super.needsLayout - } - } - - public override func layout() { - super.layout() - //layoutContainer(animated: false) - } - - - private let localizationFunc: (String)->String - private let placeholderKey: String - required public init(frame frameRect: NSRect, localizationFunc: @escaping(String)->String, placeholderKey:String) { - self.localizationFunc = localizationFunc - self.placeholderKey = placeholderKey - super.init(frame: frameRect) - - hasVerticalScroller = true - container.frame = bounds - container.autoresizingMask = [.width] - contentView.documentView = container - - input.focusRingType = .none - input.backgroundColor = NSColor.clear - input.delegate = self - input.isRichText = false - - input.textContainer?.widthTracksTextView = true - input.textContainer?.heightTracksTextView = false - - input.isHorizontallyResizable = false - input.isVerticallyResizable = false - - - placeholder.set(handler: { [weak self] _ in - self?.window?.makeFirstResponder(self?.responder) - }, for: .Click) - - input.font = .normal(.text) - container.addSubview(input) - container.addSubview(placeholder) - container.layer?.cornerRadius = .cornerRadius - wantsLayer = true - self.layer?.cornerRadius = .cornerRadius - self.layer?.backgroundColor = presentation.colors.grayBackground.cgColor - updateLocalizationAndTheme() - } - - - - open func didResignResponder() { - state = .None - } - - open func didBecomeResponder() { - state = .Focus - - } - - override public func becomeFirstResponder() -> Bool { - window?.makeFirstResponder(input) - return true - } - - public var responder: NSResponder? { - return input - } - - public func updateLocalizationAndTheme() { - background = presentation.colors.background - contentView.background = presentation.colors.background - self.container.backgroundColor = presentation.colors.grayBackground - input.textColor = presentation.colors.text - input.insertionPointColor = presentation.search.textColor - let placeholderLayout = TextViewLayout(.initialize(string: localizedString(placeholderKey), color: presentation.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1) - placeholderLayout.measure(width: .greatestFiniteMagnitude) - placeholder.update(placeholderLayout) - placeholder.backgroundColor = presentation.colors.grayBackground - placeholder.isSelectable = false - layoutContainer(animated: false) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - -} diff --git a/TGUIKit/TGUIKit/View.swift b/TGUIKit/TGUIKit/View.swift deleted file mode 100644 index 9fc28c94a6..0000000000 --- a/TGUIKit/TGUIKit/View.swift +++ /dev/null @@ -1,347 +0,0 @@ -// -// View.swift -// TGUIKit -// -// Created by keepcoder on 06/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Foundation -import SwiftSignalKitMac -public let kUIKitAnimationBackground = "UIKitAnimationBackground" - -public protocol AppearanceViewProtocol { - func updateLocalizationAndTheme() -} - -class ViewLayer : CALayer { - override init(layer: Any) { - super.init(layer: layer) - } - - override open class func needsDisplay(forKey:String) -> Bool { - if forKey == kUIKitAnimationBackground { - return true - } - return false - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -public struct BorderType: OptionSet { - public var rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - - public init() { - self.rawValue = 0 - } - - public init(_ flags: BorderType) { - var rawValue: UInt32 = 0 - - if flags.contains(BorderType.Top) { - rawValue |= BorderType.Top.rawValue - } - - if flags.contains(BorderType.Bottom) { - rawValue |= BorderType.Bottom.rawValue - } - - if flags.contains(BorderType.Left) { - rawValue |= BorderType.Left.rawValue - } - - if flags.contains(BorderType.Right) { - rawValue |= BorderType.Right.rawValue - } - - self.rawValue = rawValue - } - - public static let Top = BorderType(rawValue: 1) - public static let Bottom = BorderType(rawValue: 2) - public static let Left = BorderType(rawValue: 4) - public static let Right = BorderType(rawValue: 8) -} - -public protocol ViewDisplayDelegate : class { - func draw(_ layer: CALayer, in ctx: CGContext); -} - -public class CustomViewHandlers { - public var size:((NSSize) ->Void)? - public var origin:((NSPoint) ->Void)? - public var layout:((View) ->Void)? - - deinit { - var bp:Int = 0 - bp += 1 - } -} - - - -open class View : NSView, CALayerDelegate, AppearanceViewProtocol { - - public var animates:Bool = false - - public var isEventLess: Bool = false - - public weak var displayDelegate:ViewDisplayDelegate? - - public let customHandler:CustomViewHandlers = CustomViewHandlers() - - open var backgroundColor:NSColor = presentation.colors.background { - didSet { - if oldValue != backgroundColor { - setNeedsDisplay() - } - } - } - - - public var flip:Bool = true - - public var border:BorderType? - - - open override func layout() { - super.layout() - if let layout = customHandler.layout { - layout(self) - } - } - - open func draw(_ layer: CALayer, in ctx: CGContext) { - - - if let displayDelegate = displayDelegate { - displayDelegate.draw(layer, in: ctx) - } else { - - // ctx.setShadow(offset: NSMakeSize(5.0, 5.0), blur: 0.0, color: .shadow.cgColor) - - ctx.setFillColor(self.backgroundColor.cgColor) - ctx.fill(bounds) - - if let border = border { - ctx.setFillColor(presentation.colors.border.cgColor) - - if border.contains(.Top) { - ctx.fill(NSMakeRect(0, !self.isFlipped ? NSHeight(self.frame) - .borderSize : 0, NSWidth(self.frame), .borderSize)) - } - if border.contains(.Bottom) { - ctx.fill(NSMakeRect(0, self.isFlipped ? NSHeight(self.frame) - .borderSize : 0, NSWidth(self.frame), .borderSize)) - } - if border.contains(.Left) { - ctx.fill(NSMakeRect(0, 0, .borderSize, NSHeight(self.frame))) - } - if border.contains(.Right) { - ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize, NSHeight(self.frame))) - } - - } - } - } - - public func setNeedsDisplay() -> Void { - self.layer?.setNeedsDisplay() - assertOnMainThread() - } - - - - open override var isFlipped: Bool { - return flip - } - - public init() { - super.init(frame: NSZeroRect) - assertOnMainThread() - self.wantsLayer = true - acceptsTouchEvents = true - self.layerContentsRedrawPolicy = .onSetNeedsDisplay - - // self.layer?.delegate = self - // self.layer?.isOpaque = false - // self.autoresizesSubviews = false - // self.layerContentsRedrawPolicy = .onSetNeedsDisplay - // self.layer?.drawsAsynchronously = System.drawAsync - } - - override required public init(frame frameRect: NSRect) { - super.init(frame: frameRect) - assertOnMainThread() - acceptsTouchEvents = true - self.wantsLayer = true - - - - // self.layer?.delegate = self - // self.layer?.isOpaque = false - self.layerContentsRedrawPolicy = .onSetNeedsDisplay - // self.layer?.drawsAsynchronously = System.drawAsync - } - - open override var translatesAutoresizingMaskIntoConstraints: Bool { - get { - return true - } - set { - - } - } - - open func mouseInside() -> Bool { - return super._mouseInside() - } - - public func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) -> Void { - super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - - public func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - } - public func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: String = kCAMediaTimingFunctionEaseOut, completion:((Bool)->Void)? = nil) { - super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) - - } - - open override func swipe(with event: NSEvent) { - super.swipe(with: event) - } - - open override func beginGesture(with event: NSEvent) { - super.beginGesture(with: event) - } - - open func updateLocalizationAndTheme() { - for subview in subviews { - if let subview = subview as? AppearanceViewProtocol { - subview.updateLocalizationAndTheme() - } - } - } - - open override func viewDidMoveToSuperview() { - if superview != nil { - guard #available(OSX 10.12, *) else { - // self.needsLayout = true - return - } - } - } - - open override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - - if let size = customHandler.size { - size(newSize) - } - guard #available(OSX 10.12, *) else { - self.needsLayout = true - return - } - } - - public func notifySubviewsToLayout(_ subview:NSView) -> Void { - for sub in subview.subviews { - sub.needsLayout = true - } - } - - open override var needsLayout: Bool { - set { - super.needsLayout = newValue - if newValue { - - guard #available(OSX 10.12, *) else { - layout() - notifySubviewsToLayout(self) - return - } - - } - } - get { - return super.needsLayout - } - } - - - @objc func layoutInRunLoop() { - layout() - } - - open override func setFrameOrigin(_ newOrigin: NSPoint) { - super.setFrameOrigin(newOrigin) - if let origin = customHandler.origin { - origin(newOrigin) - } - guard #available(OSX 10.12, *) else { - self.needsLayout = true - return - } - } - - deinit { - assertOnMainThread() - } - - - open var responder:NSResponder? { - return self - } - - open func setNeedsDisplayLayer() -> Void { - self.layer?.setNeedsDisplay() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - open override func mouseDown(with event: NSEvent) { - // self.window?.makeFirstResponder(nil) - super.mouseDown(with: event) - } - - open override func draw(_ dirtyRect: NSRect) { - - } - - public var hasVisibleModal:Bool { - if let contentView = self.window?.contentView { - for subview in contentView.subviews { - if subview is PopoverBackground { - return true - } - } - } - - - return false - } - - open override func copy() -> Any { - let copy:View = View(frame:bounds) - copy.layer?.contents = self.layer?.contents - return copy - } - - - - open var kitWindow: Window? { - return super.window as? Window - } - - - -} diff --git a/TGUIKit/TGUIKit/ViewController.swift b/TGUIKit/TGUIKit/ViewController.swift deleted file mode 100644 index 1df8b79f9c..0000000000 --- a/TGUIKit/TGUIKit/ViewController.swift +++ /dev/null @@ -1,612 +0,0 @@ -// -// ViewController.swift -// TGUIKit -// -// Created by keepcoder on 06/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Foundation -import SwiftSignalKitMac - -class ControllerToasterView : View { - - private weak var toaster:ControllerToaster? - private let textView:TextView = TextView() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(textView) - textView.isSelectable = false - self.autoresizingMask = [.width] - self.border = [.Bottom] - updateLocalizationAndTheme() - } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - self.backgroundColor = presentation.colors.background - self.textView.backgroundColor = presentation.colors.background - } - - - func update(with toaster:ControllerToaster) { - self.toaster = toaster - } - - override func layout() { - super.layout() - if let toaster = toaster { - toaster.text.measure(width: frame.width - 40) - textView.update(toaster.text) - textView.center() - } - self.setNeedsDisplayLayer() - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -public class ControllerToaster { - let text:TextViewLayout - var view:ControllerToasterView? - let disposable:MetaDisposable = MetaDisposable() - private let height:CGFloat - public init(text:NSAttributedString, height:CGFloat = 30.0) { - self.text = TextViewLayout(text, maximumNumberOfLines: 1, truncationType: .middle) - self.height = height - } - - public init(text:String, height:CGFloat = 30.0) { - self.text = TextViewLayout(NSAttributedString.initialize(string: text, color: presentation.colors.text, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .middle) - self.height = height - } - - func show(for controller:ViewController, timeout:Double, animated:Bool) { - assert(view == nil) - view = ControllerToasterView(frame: NSMakeRect(0, 0, controller.frame.width, height)) - view?.update(with: self) - controller.addSubview(view!) - - if animated { - view?.layer?.animatePosition(from: NSMakePoint(0, -height), to: NSZeroPoint, duration: 0.2) - } - - let signal:Signal = .single(Void()) |> delay(timeout, queue: Queue.mainQueue()) - disposable.set(signal.start(next:{ [weak self] in - self?.hide(true) - })) - } - - func hide(_ animated:Bool) { - if animated { - view?.layer?.animatePosition(from: NSZeroPoint, to: NSMakePoint(0, -height), duration: 0.2, removeOnCompletion:false, completion:{ [weak self] (completed) in - self?.view?.removeFromSuperview() - self?.view = nil - }) - } else { - view?.removeFromSuperview() - view = nil - disposable.dispose() - } - } - - deinit { - let view = self.view - view?.layer?.animatePosition(from: NSZeroPoint, to: NSMakePoint(0, -height), duration: 0.2, removeOnCompletion:false, completion:{ (completed) in - view?.removeFromSuperview() - }) - disposable.dispose() - } - -} - -open class ViewController : NSObject { - fileprivate var _view:NSView?; - public var _frameRect:NSRect - - private var toaster:ControllerToaster? - - public var atomicSize:Atomic = Atomic(value:NSZeroSize) - - weak open var navigationController:NavigationViewController? { - didSet { - if navigationController != oldValue { - updateNavigation(navigationController) - } - } - } - - public var noticeResizeWhenLoaded: Bool = true - - public var animationStyle:AnimationStyle = AnimationStyle(duration:0.4, function:kCAMediaTimingFunctionSpring) - public var bar:NavigationBarStyle = NavigationBarStyle(height:50) - - public var leftBarView:BarView! - public var centerBarView:TitledBarView! - public var rightBarView:BarView! - - public var popover:Popover? - open var modal:Modal? - - - private let _ready = Promise() - open var ready: Promise { - return self._ready - } - public var didSetReady:Bool = false - - public let isKeyWindow:Promise = Promise(false) - - public var view:NSView { - get { - if(_view == nil) { - loadView(); - } - - return _view!; - } - - } - - public var backgroundColor: NSColor { - set { - self.view.background = newValue - } - get { - return self.view.background - } - } - - open var enableBack:Bool { - return false - } - - open func executeReturn() -> Void { - self.navigationController?.back() - } - - open func updateNavigation(_ navigation:NavigationViewController?) { - - } - - open func navigationWillChangeController() { - - } - - open var sidebar:ViewController? { - return nil - } - - open var sidebarWidth:CGFloat { - return 350 - } - - public private(set) var internalId:Int = 0; - - public override init() { - _frameRect = NSZeroRect - self.internalId = Int(arc4random()); - super.init() - } - - public init(frame frameRect:NSRect) { - _frameRect = frameRect; - self.internalId = Int(arc4random()); - } - - open func readyOnce() -> Void { - if !didSetReady { - didSetReady = true - ready.set(.single(true)) - } - } - - open func updateLocalizationAndTheme() { - (view as? AppearanceViewProtocol)?.updateLocalizationAndTheme() - self.navigationController?.updateLocalizationAndTheme() - } - - open func loadView() -> Void { - if(_view == nil) { - - leftBarView = getLeftBarViewOnce() - centerBarView = getCenterBarViewOnce() - rightBarView = getRightBarViewOnce() - - let vz = viewClass() as! NSView.Type - _view = vz.init(frame: _frameRect); - _view?.autoresizingMask = [.width,.height] - - NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) - - _ = atomicSize.swap(_view!.frame.size) - } - } - - open func requestUpdateBackBar() { - if isLoaded(), let leftBarView = leftBarView as? BackNavigationBar { - leftBarView.requestUpdate() - } - self.leftBarView.style = navigationButtonStyle - } - - open func requestUpdateCenterBar() { - setCenterTitle(defaultBarTitle) - } - - open func dismiss() { - if navigationController?.controller == self { - navigationController?.back() - } - } - - open func requestUpdateRightBar() { - (self.rightBarView as? TextButtonBarView)?.button.style = navigationButtonStyle - self.rightBarView.style = navigationButtonStyle - } - - - @objc func viewFrameChanged(_ notification:Notification) { - viewDidResized(frame.size) - } - - open func viewDidResized(_ size:NSSize) { - _ = atomicSize.swap(size) - } - - open func invokeNavigationBack() -> Bool { - return true - } - - open func getLeftBarViewOnce() -> BarView { - return enableBack ? BackNavigationBar(self) : BarView(controller: self) - } - - open var defaultBarTitle:String { - return localizedString(self.className) - } - - open func getCenterBarViewOnce() -> TitledBarView { - return TitledBarView(controller: self, .initialize(string: defaultBarTitle, color: presentation.colors.text, font: .medium(.title))) - } - - public func setCenterTitle(_ text:String) { - self.centerBarView.text = .initialize(string: text, color: presentation.colors.text, font: .medium(.title)) - } - - open func getRightBarViewOnce() -> BarView { - return BarView(controller: self) - } - - open func viewClass() ->AnyClass { - return View.self - } - - open func draggingItems(for pasteboard:NSPasteboard) -> [DragItem] { - return [] - } - - public func loadViewIfNeeded(_ frame:NSRect = NSZeroRect) -> Void { - - guard _view != nil else { - if !NSIsEmptyRect(frame) { - _frameRect = frame - } - self.loadView() - - return - } - } - - open func viewDidLoad() -> Void { - if noticeResizeWhenLoaded { - viewDidResized(view.frame.size) - } - } - - open func viewWillAppear(_ animated:Bool) -> Void { - - } - - deinit { - self.window?.removeObserver(for: self) - window?.removeAllHandlers(for: self) - NotificationCenter.default.removeObserver(self) - assertOnMainThread() - } - - open func viewWillDisappear(_ animated:Bool) -> Void { - //assert(self.window != nil) - if canBecomeResponder { - self.window?.removeObserver(for: self) - } - NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: window) - NotificationCenter.default.removeObserver(self, name: NSWindow.didResignKeyNotification, object: window) - isKeyWindow.set(.single(false)) - } - - public func isLoaded() -> Bool { - return _view != nil - } - - open func viewDidAppear(_ animated:Bool) -> Void { - //assert(self.window != nil) - if canBecomeResponder { - self.window?.set(responder: {[weak self] () -> NSResponder? in - return self?.firstResponder() - }, with: self, priority: responderPriority) - if let become = becomeFirstResponder(), become == true { - self.window?.applyResponderIfNeeded() - } - } - - NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window) - NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window) - if let window = window { - isKeyWindow.set(.single(window.isKeyWindow)) - } - } - - @objc open func windowDidBecomeKey() { - isKeyWindow.set(.single(true)) - } - - @objc open func windowDidResignKey() { - isKeyWindow.set(.single(false)) - } - - open var canBecomeResponder: Bool { - return true - } - - open var removeAfterDisapper:Bool { - return false - } - - open func escapeKeyAction() -> KeyHandlerResult { - return .rejected - } - - open func backKeyAction() -> KeyHandlerResult { - return .rejected - } - - open func nextKeyAction() -> KeyHandlerResult { - return .invokeNext - } - - open func returnKeyAction() -> KeyHandlerResult { - return .rejected - } - - open func didRemovedFromStack() -> Void { - - } - - open func viewDidDisappear(_ animated:Bool) -> Void { - - } - - open func scrollup() { - - } - - open func becomeFirstResponder() -> Bool? { - - return false - } - - open var window:Window? { - return _view?.window as? Window - } - - open func firstResponder() -> NSResponder? { - return nil - } - - open var responderPriority:HandlerPriority { - return .low - } - - - - public var frame:NSRect { - get { - return isLoaded() ? self.view.frame : _frameRect - } - set { - self.view.frame = newValue - } - } - public var bounds:NSRect { - return isLoaded() ? self.view.bounds : NSMakeRect(0, 0, _frameRect.width, _frameRect.height - bar.height) - } - - public func addSubview(_ subview:NSView) -> Void { - self.view.addSubview(subview) - } - - public func removeFromSuperview() ->Void { - self.view.removeFromSuperview() - } - - - open func backSettings() -> (String,CGImage?) { - return (localizedString("Navigation.back"),#imageLiteral(resourceName: "Icon_NavigationBack").precomposed(presentation.colors.blueIcon)) - } - - open var popoverClass:AnyClass { - return Popover.self - } - - open func show(for control:Control, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint) -> Void { - if popover == nil { - self.popover = (self.popoverClass as! Popover.Type).init(controller: self) - } - - if let popover = popover { - popover.show(for: control, edge: edge, inset: inset) - } - } - - open func closePopover() -> Void { - self.popover?.hide() - } - - open func invokeNavigation(action:NavigationModalAction) { - _ = (self.ready.get() |> take(1) |> deliverOnMainQueue).start(next: { (ready) in - action.close() - }) - } - - public func show(toaster:ControllerToaster, for delay:Double = 3.0, animated:Bool = true) { - assert(isLoaded()) - if let toaster = self.toaster { - toaster.hide(true) - } - - self.toaster = toaster - toaster.show(for: self, timeout: delay, animated: animated) - - } -} - - -open class GenericViewController : ViewController where T:NSView { - public var genericView:T { - return super.view as! T - } - - open override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - genericView.background = presentation.colors.background - } - - override open func loadView() -> Void { - if(_view == nil) { - - leftBarView = getLeftBarViewOnce() - centerBarView = getCenterBarViewOnce() - rightBarView = getRightBarViewOnce() - - _view = initializer() - _view?.autoresizingMask = [.width,.height] - - NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) - - _ = atomicSize.swap(_view!.frame.size) - } - viewDidLoad() - } - - - - open func initializer() -> T { - let vz = T.self as NSView.Type - //controller.bar.height - return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height)) as! T; - } - -} - - -open class ModalViewController : ViewController { - - open var closable:Bool { - return true - } - - - - open var background:NSColor { - return NSColor(0x000000, 0.27) - } - - - open var isFullScreen:Bool { - return false - } - - open var containerBackground: NSColor { - return presentation.colors.background - } - - open var dynamicSize:Bool { - return false - } - - - - open func measure(size:NSSize) { - - } - - open var modalInteractions:ModalInteractions? { - return nil - } - - open override var responderPriority: HandlerPriority { - return .modal - } - - open override func firstResponder() -> NSResponder? { - return self.view - } - - open func close() { - modal?.close() - } - - open var handleEvents:Bool { - return true - } - - open var handleAllEvents: Bool { - return true - } - - override open func loadView() -> Void { - if(_view == nil) { - - _view = initializer() - _view?.autoresizingMask = [.width,.height] - - NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) - - _ = atomicSize.swap(_view!.frame.size) - } - viewDidLoad() - } - - open func initializer() -> NSView { - let vz = viewClass() as! NSView.Type - return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height)); - } - - -} - -open class TableModalViewController : ModalViewController { - override open var dynamicSize: Bool { - return true - } - - override open func measure(size: NSSize) { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.listHeight)), animated: false) - } - - override open func viewClass() -> AnyClass { - return TableView.self - } - - public var genericView:TableView { - return self.view as! TableView - } - - public func updateSize(_ animated: Bool) { - if let contentSize = self.modal?.window.contentView?.frame.size { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 70, genericView.listHeight)), animated: animated) - } - } -} diff --git a/TGUIKit/TGUIKit/Window.swift b/TGUIKit/TGUIKit/Window.swift deleted file mode 100644 index 26a75274fe..0000000000 --- a/TGUIKit/TGUIKit/Window.swift +++ /dev/null @@ -1,481 +0,0 @@ -// -// Window.swift -// TGUIKit -// -// Created by keepcoder on 16/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -public enum HandlerPriority: Int, Comparable { - case low = 0 - case medium = 1 - case high = 2 - case modal = 3 - -} - -public func <(lhs: HandlerPriority, rhs: HandlerPriority) -> Bool { - return lhs.rawValue < rhs.rawValue -} - -class KeyHandler : Comparable { - let handler:()->KeyHandlerResult - let object:WeakReference - let priority:HandlerPriority - let modifierFlags:NSEvent.ModifierFlags? - - init(_ handler:@escaping()->KeyHandlerResult, _ object:NSObject?, _ priority:HandlerPriority, _ flags:NSEvent.ModifierFlags?) { - self.handler = handler - self.object = WeakReference(value: object) - self.priority = priority - self.modifierFlags = flags - } -} -func ==(lhs: KeyHandler, rhs: KeyHandler) -> Bool { - return lhs.priority == rhs.priority -} -func <(lhs: KeyHandler, rhs: KeyHandler) -> Bool { - return lhs.priority < rhs.priority -} - -public enum SwipeDirection { - case left - case right - case none -} - -class SwipeHandler : Comparable { - let handler:(SwipeDirection)->KeyHandlerResult - let object:WeakReference - let priority:HandlerPriority - - init(_ handler:@escaping(SwipeDirection)->KeyHandlerResult, _ object:NSObject?, _ priority:HandlerPriority) { - self.handler = handler - self.object = WeakReference(value: object) - self.priority = priority - } -} -func ==(lhs: SwipeHandler, rhs: SwipeHandler) -> Bool { - return lhs.priority == rhs.priority -} -func <(lhs: SwipeHandler, rhs: SwipeHandler) -> Bool { - return lhs.priority < rhs.priority -} - - -class ResponderObserver : Comparable { - let handler:()->NSResponder? - let object:WeakReference - let priority:HandlerPriority - - init(_ handler:@escaping()->NSResponder?, _ object:NSObject?, _ priority:HandlerPriority) { - self.handler = handler - self.object = WeakReference(value: object) - self.priority = priority - } -} -func ==(lhs: ResponderObserver, rhs: ResponderObserver) -> Bool { - return lhs.priority == rhs.priority -} -func <(lhs: ResponderObserver, rhs: ResponderObserver) -> Bool { - return lhs.priority < rhs.priority -} - -class MouseObserver : Comparable { - let handler:(NSEvent)->KeyHandlerResult - let object:WeakReference - let priority:HandlerPriority - let type:NSEvent.EventType? - - init(_ handler:@escaping(NSEvent)->KeyHandlerResult, _ object:NSObject?, _ priority:HandlerPriority, _ type:NSEvent.EventType) { - self.handler = handler - self.object = WeakReference(value: object) - self.priority = priority - self.type = type - } -} -func ==(lhs: MouseObserver, rhs: MouseObserver) -> Bool { - return lhs.priority == rhs.priority -} -func <(lhs: MouseObserver, rhs: MouseObserver) -> Bool { - return lhs.priority < rhs.priority -} - -public enum KeyHandlerResult { - case invoked // invoke and return - case rejected // can invoke next priprity event - case invokeNext // invoke and send global event -} - -public class Window: NSWindow { - public var name: String = "TGUIKit.Window" - private var keyHandlers:[KeyboardKey:[KeyHandler]] = [:] - private var swipeHandlers:[SwipeHandler] = [] - private var responsders:[ResponderObserver] = [] - private var mouseHandlers:[NSEvent.EventType:[MouseObserver]] = [:] - private var swipePoints:[NSPoint] = [] - private var saver:WindowSaver? - public var initFromSaver:Bool = false - public var copyhandler:(()->Void)? = nil - public var closeInterceptor:(()->Void)? = nil - public func set(responder:@escaping() -> NSResponder?, with object:NSObject?, priority:HandlerPriority) { - responsders.append(ResponderObserver(responder, object, priority)) - } - - public func removeObserver(for object:NSObject) { - var copy:[ResponderObserver] = [] - for observer in responsders { - copy.append(observer) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == object || copy[i].object.value == nil { - responsders.remove(at: i) - } - } - } - - - public func set(handler:@escaping() -> KeyHandlerResult, with object:NSObject, for key:KeyboardKey, priority:HandlerPriority = .low, modifierFlags:NSEvent.ModifierFlags? = nil) -> Void { - var handlers:[KeyHandler]? = keyHandlers[key] - if handlers == nil { - handlers = [] - keyHandlers[key] = handlers - } - keyHandlers[key]?.append(KeyHandler(handler, object, priority, modifierFlags)) - - } - - public func add(swipe handler:@escaping(SwipeDirection) -> KeyHandlerResult, with object:NSObject, priority:HandlerPriority = .low) -> Void { - swipeHandlers.append(SwipeHandler(handler, object, priority)) - } - - - public func removeAllHandlers(for object:NSObject) { - - var newKeyHandlers:[KeyboardKey:[KeyHandler]] = [:] - for (key, handlers) in keyHandlers { - newKeyHandlers[key] = handlers - } - - for (key, handlers) in keyHandlers { - var copy:[KeyHandler] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == object { - newKeyHandlers[key]?.remove(at: i) - } - } - } - self.keyHandlers = newKeyHandlers - - var newMouseHandlers:[NSEvent.EventType:[MouseObserver]] = [:] - for (key, handlers) in mouseHandlers { - newMouseHandlers[key] = handlers - } - - for (key, handlers) in mouseHandlers { - var copy:[MouseObserver] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == object { - newMouseHandlers[key]?.remove(at: i) - } - } - } - self.mouseHandlers = newMouseHandlers - - - var copyGesture:[SwipeHandler] = [] - for gesture in swipeHandlers { - copyGesture.append(gesture) - } - for i in stride(from: swipeHandlers.count - 1, to: -1 , by: -1) { - if copyGesture[i].object.value == object { - copyGesture.remove(at: i) - } - } - self.swipeHandlers = copyGesture - } - - public func remove(object:NSObject, for key:KeyboardKey) { - let handlers = keyHandlers[key] - if let handlers = handlers { - var copy:[KeyHandler] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == object || copy[i].object.value == nil { - keyHandlers[key]?.remove(at: i) - } - } - } - } - - private func cleanUndefinedHandlers() { - var newKeyHandlers:[KeyboardKey:[KeyHandler]] = [:] - for (key, handlers) in keyHandlers { - newKeyHandlers[key] = handlers - } - - for (key, handlers) in keyHandlers { - var copy:[KeyHandler] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == nil { - newKeyHandlers[key]?.remove(at: i) - } - } - } - self.keyHandlers = newKeyHandlers - - var newMouseHandlers:[NSEvent.EventType:[MouseObserver]] = [:] - for (key, handlers) in mouseHandlers { - newMouseHandlers[key] = handlers - } - - for (key, handlers) in mouseHandlers { - var copy:[MouseObserver] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == nil { - newMouseHandlers[key]?.remove(at: i) - } - } - } - self.mouseHandlers = newMouseHandlers - - var copyGesture:[SwipeHandler] = [] - for gesture in swipeHandlers { - copyGesture.append(gesture) - } - for i in stride(from: swipeHandlers.count - 1, to: -1 , by: -1) { - if copyGesture[i].object.value == nil { - copyGesture.remove(at: i) - } - } - self.swipeHandlers = copyGesture - - } - - public func set(mouseHandler:@escaping(NSEvent) -> KeyHandlerResult, with object:NSObject, for type:NSEvent.EventType, priority:HandlerPriority = .low) -> Void { - var handlers:[MouseObserver]? = mouseHandlers[type] - if handlers == nil { - handlers = [] - mouseHandlers[type] = handlers - } - mouseHandlers[type]?.append(MouseObserver(mouseHandler, object, priority, type)) - } - - public func remove(object:NSObject, for type:NSEvent.EventType) { - let handlers = mouseHandlers[type] - if let handlers = handlers { - var copy:[MouseObserver] = [] - for handle in handlers { - copy.append(handle) - } - for i in stride(from: copy.count - 1, to: -1, by: -1) { - if copy[i].object.value == object || copy[i].object.value == nil { - mouseHandlers[type]?.remove(at: i) - } - } - } - } - - - public func applyResponderIfNeeded() ->Void { - let sorted = responsders.sorted(by: >) - - for observer in sorted { - if let responder = observer.handler() { - if self.firstResponder != responder { - let _ = self.resignFirstResponder() - if responder.responds(to: NSSelectorFromString("window")) { - let window:NSWindow? = responder.value(forKey: "window") as? NSWindow - if window != self { - continue - } - } - self.makeFirstResponder(responder) - if let responder = responder as? NSTextField { - responder.setCursorToEnd() - } - } - break - } - } - } - - - - public override func close() { - if let closeInterceptor = closeInterceptor { - closeInterceptor() - return - } - if isReleasedWhenClosed { - super.close() - } else { - super.close() - } - } - - @objc public func pasteToFirstResponder(_ sender: Any) { - - applyResponderIfNeeded() - - if let firstResponder = firstResponder, firstResponder.responds(to: NSSelectorFromString("paste:")) { - firstResponder.performSelector(onMainThread: NSSelectorFromString("paste:"), with: sender, waitUntilDone: false) - } - } - - @objc public func copyFromFirstResponder(_ sender: Any) { - if let copyhandler = copyhandler { - copyhandler() - } else { - if let firstResponder = firstResponder, firstResponder.responds(to: NSSelectorFromString("copy:")) { - firstResponder.performSelector(onMainThread: NSSelectorFromString("copy:"), with: sender, waitUntilDone: false) - } - } - - } - - public override func sendEvent(_ event: NSEvent) { - - if sheets.isEmpty { - if event.type == .keyDown { - - - if KeyboardKey(rawValue:event.keyCode) != KeyboardKey.Escape { - applyResponderIfNeeded() - } - - cleanUndefinedHandlers() - - if let globalHandler = keyHandlers[.All]?.sorted(by: >).first, let keyCode = KeyboardKey(rawValue:event.keyCode) { - if let handle = keyHandlers[keyCode]?.sorted(by: >).first { - if globalHandler.object.value != handle.object.value { - if (handle.modifierFlags == nil || event.modifierFlags.contains(handle.modifierFlags!)) { - switch globalHandler.handler() { - case .invoked: - return - case .rejected: - break - case .invokeNext: - super.sendEvent(event) - return - } - } else { - // super.sendEvent(event) - // return - } - } - } - } - - if let keyCode = KeyboardKey(rawValue:event.keyCode), let handlers = keyHandlers[keyCode]?.sorted(by: >) { - loop: for handle in handlers { - - if (handle.modifierFlags == nil || event.modifierFlags.contains(handle.modifierFlags!)) { - - switch handle.handler() { - case .invoked: - return - case .rejected: - continue - case .invokeNext: - break loop - } - - } - } - } - - } else if let handlers = mouseHandlers[event.type] { - let sorted = handlers.sorted(by: >) - loop: for handle in sorted { - switch handle.handler(event) { - case .invoked: - return - case .rejected: - continue - case .invokeNext: - break loop - } - } - } - - super.sendEvent(event) - } else { - //super.sendEvent(event) - } - - - } - -// public func set(copy handler:@escaping()->Void) -> (()-> Void,NSEventModifierFlags?)? { -// return self.set(handler: handler, for: .C, priority:.low, modifierFlags: [.command]) -// } -// -// public func set(paste handler:@escaping()->Void) -> (()-> Void,NSEventModifierFlags?)? { -// return self.set(handler: handler, for: .V, modifierFlags: [.command]) -// } - - public func set(escape handler:@escaping() -> KeyHandlerResult, with object:NSObject, priority:HandlerPriority = .low, modifierFlags:NSEvent.ModifierFlags? = nil) -> Void { - set(handler: handler, with: object, for: .Escape, priority:priority, modifierFlags:modifierFlags) - } - - - - public override var canBecomeKey: Bool { - return true - } - - public func initSaver() { - self.initFromSaver = true - self.saver = .find(for: self) - if let saver = saver { - self.setFrame(saver.rect, display: true) - } - } - - @objc func windowDidNeedSaveState(_ notification: Notification) { - saver?.rect = frame - saver?.save() - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - public func windowImageShot() -> CGImage? { - return CGWindowListCreateImage(CGRect.null, [.optionIncludingWindow], CGWindowID(windowNumber), [.boundsIgnoreFraming]) - } - - - public override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing bufferingType: NSWindow.BackingStoreType, defer flag: Bool) { - super.init(contentRect: contentRect, styleMask: style, backing: bufferingType, defer: flag) - - self.acceptsMouseMovedEvents = true - self.contentView?.wantsLayer = true - - - self.contentView?.acceptsTouchEvents = true - NotificationCenter.default.addObserver(self, selector: #selector(windowDidNeedSaveState(_:)), name: NSWindow.didMoveNotification, object: self) - NotificationCenter.default.addObserver(self, selector: #selector(windowDidNeedSaveState(_:)), name: NSWindow.didResizeNotification, object: self) - - - - // self.contentView?.canDrawSubviewsIntoLayer = true - } -} diff --git a/Telegram-Mac.xcworkspace/contents.xcworkspacedata b/Telegram-Mac.xcworkspace/contents.xcworkspacedata index b725446cc7..cbb856a81a 100644 --- a/Telegram-Mac.xcworkspace/contents.xcworkspacedata +++ b/Telegram-Mac.xcworkspace/contents.xcworkspacedata @@ -2,22 +2,79 @@ + location = "group:core-xprojects/Reachability/Reachability.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + location = "group:core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj"> + location = "group:core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj"> + location = "group:core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj"> + location = "group:core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj"> + location = "group:submodules/Zip/Zip.xcodeproj"> + + + + diff --git a/Telegram-Mac.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Telegram-Mac.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Telegram-Mac.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Telegram-Mac.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..3ddf867a10 --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Latest + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/IDEFindNavigatorScopes.plist b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000000..5dd5da85fd --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/WorkspaceSettings.xcsettings b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f25782dd5d --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,18 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + EnabledFullIndexStoreVisibility + + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git "a/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhail\342\200\231s MacBook Pro's conflicted copy 2019-10-04).xcbkptlist" "b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhail\342\200\231s MacBook Pro's conflicted copy 2019-10-04).xcbkptlist" new file mode 100644 index 0000000000..95720ae75c --- /dev/null +++ "b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhail\342\200\231s MacBook Pro's conflicted copy 2019-10-04).xcbkptlist" @@ -0,0 +1,1496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Expressions.xcexplist b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Expressions.xcexplist new file mode 100644 index 0000000000..6138cd226c --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/keepcoder.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2016-09-07).xcuserstate b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2016-09-07).xcuserstate deleted file mode 100644 index b100371056..0000000000 Binary files a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2016-09-07).xcuserstate and /dev/null differ diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-03-15).xcuserstate b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-03-15).xcuserstate deleted file mode 100644 index 865c30d5b5..0000000000 Binary files a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-03-15).xcuserstate and /dev/null differ diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-04-14).xcuserstate b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-04-14).xcuserstate deleted file mode 100644 index 86c5f55c19..0000000000 Binary files a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (MacBook-Pro-Mikhail's conflicted copy 2017-04-14).xcuserstate and /dev/null differ diff --git "a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (Mikhail\342\200\231s iMac Pro's conflicted copy 2018-07-19).xcuserstate" "b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (Mikhail\342\200\231s iMac Pro's conflicted copy 2018-07-19).xcuserstate" new file mode 100644 index 0000000000..a98da8888f Binary files /dev/null and "b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState (Mikhail\342\200\231s iMac Pro's conflicted copy 2018-07-19).xcuserstate" differ diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState.xcuserstate b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState.xcuserstate old mode 100644 new mode 100755 index 51ea1f892f..198017cb14 Binary files a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState.xcuserstate and b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/WorkspaceSettings.xcsettings b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f25782dd5d --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,18 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + EnabledFullIndexStoreVisibility + + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index f3ddaa1b2d..81d8783658 100644 --- a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -9,13 +9,29 @@ shouldBeEnabled = "Yes" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "Telegram-Mac/StickersModalController.swift" - timestampString = "509905609.867761" + filePath = "submodules/TelegramCore/TelegramCore/ChannelState.swift" + timestampString = "533845621.929424" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "145" - endingLineNumber = "145" - landmarkName = "viewDidLoad()" + startingLineNumber = "71" + endingLineNumber = "71" + landmarkName = "channelUpdatesByPeerId(updates:)" + landmarkType = "9"> + + + + @@ -25,46 +41,46 @@ shouldBeEnabled = "Yes" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "submodules/MtProtoKit/MTProtoKit/MTTimeSyncMessageService.m" - timestampString = "510587744.414941" + filePath = "Telegram-Mac/StretchableImage.m" + timestampString = "534088722.442825" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "58" - endingLineNumber = "58" - landmarkName = "-mtProtoMessageTransaction:" + startingLineNumber = "126" + endingLineNumber = "126" + landmarkName = "-drawInRect:fromRect:operation:fraction:" landmarkType = "7"> + startingLineNumber = "130" + endingLineNumber = "130" + landmarkName = "-drawInRect:fromRect:operation:fraction:respectFlipped:hints:" + landmarkType = "7"> + startingLineNumber = "121" + endingLineNumber = "121" + landmarkName = "-drawRepresentation:inRect:" + landmarkType = "7"> @@ -105,14 +121,14 @@ shouldBeEnabled = "Yes" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "submodules/TelegramCore/TelegramCore/MultipartUpload.swift" - timestampString = "520347808.878908" + filePath = "thrid-party/objc/NumberPluralizationForm.m" + timestampString = "535363057.217066" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "263" - endingLineNumber = "263" - landmarkName = "checkState()" - landmarkType = "7"> + startingLineNumber = "70" + endingLineNumber = "70" + landmarkName = "numberPluralizationForm" + landmarkType = "9"> + startingLineNumber = "332" + endingLineNumber = "332" + landmarkName = "numberPluralizationForm" + landmarkType = "9"> + + + + @@ -185,13 +217,13 @@ shouldBeEnabled = "Yes" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "submodules/TelegramCore/TelegramCore/Account.swift" - timestampString = "529352687.463877" + filePath = "Telegram-Mac/GroupInfoEntries.swift" + timestampString = "545150551.87756" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "154" - endingLineNumber = "154" - landmarkName = "changedMasterDatacenterId(_:)" + startingLineNumber = "465" + endingLineNumber = "465" + landmarkName = "==(_:_:)" landmarkType = "7"> @@ -201,44 +233,108 @@ shouldBeEnabled = "Yes" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "Telegram-Mac/ChannelAdminsViewController.swift" - timestampString = "529433017.660489" + filePath = "Telegram-Mac/GroupInfoEntries.swift" + timestampString = "545150551.877636" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "628" - endingLineNumber = "628" - landmarkName = "viewDidLoad()" + startingLineNumber = "459" + endingLineNumber = "459" + landmarkName = "isEqual(to:)" landmarkType = "7"> + + + + + + + + + + + + + + + + + startingLineNumber = "193" + endingLineNumber = "193" + offsetFromSymbolStart = "14063"> + startingLineNumber = "194" + endingLineNumber = "194" + offsetFromSymbolStart = "43"> diff --git a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Expressions.xcexplist b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Expressions.xcexplist index ae4f22a9a5..e35f276df2 100644 --- a/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Expressions.xcexplist +++ b/Telegram-Mac.xcworkspace/xcuserdata/mikhailfilimonov.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -68,6 +68,14 @@ + + + + + + @@ -244,6 +252,9 @@ + + @@ -373,6 +384,9 @@ + + @@ -500,6 +514,9 @@ + + @@ -538,7 +555,7 @@ contextName = "historyEntriesForView(MessageHistoryView, Bool) -> [ChatHistoryEntry]:System.swift"> + contextName = "closure #5 in SearchController.viewDidLoad():SearchController.swift"> @@ -549,6 +566,12 @@ + + + + @@ -569,6 +592,17 @@ + + + + + + + + @@ -673,6 +707,14 @@ + + + + + + @@ -748,6 +790,9 @@ + + @@ -756,6 +801,9 @@ + + @@ -824,6 +872,9 @@ + + @@ -908,6 +959,14 @@ + + + + + + @@ -919,6 +978,9 @@ + + @@ -927,6 +989,9 @@ + + @@ -1047,6 +1112,14 @@ + + + + + + @@ -1080,6 +1153,17 @@ + + + + + + + + @@ -1091,9 +1175,6 @@ - - @@ -1111,6 +1192,14 @@ + + + + + + @@ -1223,6 +1312,9 @@ + + @@ -1314,6 +1406,9 @@ + + @@ -1388,6 +1483,9 @@ + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/overtake.xcuserdatad/IDEFindNavigatorScopes.plist b/Telegram-Mac.xcworkspace/xcuserdata/overtake.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000000..5dd5da85fd --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/overtake.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/IDEFindNavigatorScopes.plist b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000000..5dd5da85fd --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/WorkspaceSettings.xcsettings b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f25782dd5d --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,18 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + EnabledFullIndexStoreVisibility + + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhails-iMac-Pro.local's conflicted copy 2018-02-20).xcbkptlist b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhails-iMac-Pro.local's conflicted copy 2018-02-20).xcbkptlist new file mode 100644 index 0000000000..a7bf8f2c35 --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Breakpoints_v2 (Mikhails-iMac-Pro.local's conflicted copy 2018-02-20).xcbkptlist @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Expressions.xcexplist b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Expressions.xcexplist new file mode 100644 index 0000000000..e36250a122 --- /dev/null +++ b/Telegram-Mac.xcworkspace/xcuserdata/telegram.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/AboutModalController.swift b/Telegram-Mac/AboutModalController.swift index b009cac752..21ac17bef3 100644 --- a/Telegram-Mac/AboutModalController.swift +++ b/Telegram-Mac/AboutModalController.swift @@ -9,6 +9,22 @@ import Cocoa import TGUIKit +var APP_VERSION_STRING: String { + var vText = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "1") (\(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "0"))" + + + #if STABLE + vText += " Stable" + #elseif APP_STORE + vText += " AppStore" + #elseif ALPHA + vText += " Alpha" + #else + vText += " Beta" + #endif + return vText +} + fileprivate class AboutModalView : Control { fileprivate let copyright:TextView = TextView() fileprivate let descView:TextView = TextView() @@ -19,35 +35,42 @@ fileprivate class AboutModalView : Control { formatter.dateFormat = "yyyy" - let copyrightLayout = TextViewLayout(NSAttributedString.initialize(string: "Copyright © 2016 - \(formatter.string(from: Date(timeIntervalSinceReferenceDate: Date.timeIntervalSinceReferenceDate))) TELEGRAM MESSENGER", color: theme.colors.grayText, font: .normal(.text)), alignment: .center) + let copyrightLayout = TextViewLayout(.initialize(string: "Copyright © 2016 - \(formatter.string(from: Date(timeIntervalSinceReferenceDate: Date.timeIntervalSinceReferenceDate))) TELEGRAM MESSENGER", color: theme.colors.grayText, font: .normal(.text)), alignment: .center) copyrightLayout.measure(width:frameRect.width - 40) - var vText = "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "1").\(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "0")" - - - #if STABLE - vText += " Stable" - #elseif APP_STORE - vText += " AppStore" - #else - vText += " Beta" - #endif + let vText = APP_VERSION_STRING let attr = NSMutableAttributedString() _ = attr.append(string: appName, color: theme.colors.text, font: .medium(.header)) _ = attr.append(string: "\n\(vText)", color: theme.colors.grayText, font: .medium(.text)) + _ = attr.append(string: " (", color: theme.colors.grayText, font: .medium(.text)) + + let range = attr.append(string: L10n.x3vGGIWUTitle.lowercased(), color: theme.colors.accent, font: .medium(.text)) + attr.addAttribute(.link, value: "copy", range: range) + _ = attr.append(string: ")", color: theme.colors.grayText, font: .medium(.text)) + _ = attr.append(string: "\n\n") - _ = attr.append(string: tr(.aboutDescription), color: theme.colors.text, font: .normal(.text)) + + + _ = attr.append(string: L10n.aboutDescription, color: theme.colors.text, font: .normal(.text)) let descLayout = TextViewLayout(attr, alignment: .center) descLayout.measure(width:frameRect.width - 40) - + descLayout.interactions.copy = { + copyToClipboard(APP_VERSION_STRING) + return true + } + + descLayout.interactions.processURL = { _ in + var bp:Int = 0 + bp += 1 + } copyright.update(copyrightLayout) descView.update(descLayout) @@ -57,6 +80,8 @@ fileprivate class AboutModalView : Control { addSubview(descView) + + descView.isSelectable = false copyright.isSelectable = false } @@ -84,15 +109,23 @@ class AboutModalController: ModalViewController { bar = .init(height: 0) } + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + super.close(animationType: animationType) + } + override func viewDidLoad() { super.viewDidLoad() - genericView.descView.layout?.interactions = TextViewInteractions(processURL: {[weak self] (url) in + genericView.descView.layout?.interactions.processURL = { [weak self] url in if let url = url as? inAppLink { execute(inapp: url) + } else if let url = url as? String, url == "copy" { + copyToClipboard(APP_VERSION_STRING) + self?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) + return } self?.close() - }) + } readyOnce() } diff --git a/Telegram-Mac/AccentColorRowItem.swift b/Telegram-Mac/AccentColorRowItem.swift new file mode 100644 index 0000000000..56703d40cf --- /dev/null +++ b/Telegram-Mac/AccentColorRowItem.swift @@ -0,0 +1,316 @@ +// +// AccentColorRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import TGUIKit +import SwiftSignalKit + + +private func generateAccentColor(_ color: PaletteAccentColor, bubbled: Bool) -> CGImage { + return generateImage(CGSize(width: 42.0, height: 42.0), scale: System.backingScale, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + context.setFillColor(color.accent.cgColor) + context.fillEllipse(in: bounds) + + if let messages = color.messages, bubbled { + let imageSize = CGSize(width: 16, height: 16) + let image = generateImage(imageSize, contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, size.height / 2) + let colors = [messages.top, messages.bottom].reversed() + let gradientColors = colors.reversed().map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + })! + + context.draw(image, in: bounds.focus(imageSize)) + } + })! +} + +private func generateCustomSwatchImage() -> CGImage { + return generateImage(CGSize(width: 42.0, height: 42.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let dotSize = CGSize(width: 10.0, height: 10.0) + + context.setFillColor(NSColor(rgb: 0xd33213).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 14.0, y: 16.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0xf08200).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 14.0, y: 0.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0xedb400).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 28.0, y: 8.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0x70bb23).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 28.0, y: 24.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0x5396fa).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 14.0, y: 32.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0x9472ee).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 24.0), size: dotSize)) + + context.setFillColor(NSColor(rgb: 0xeb6ca4).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: dotSize)) + })! +} + +private func generateSelectedRing(backgroundColor: NSColor) -> CGImage { + return generateImage(CGSize(width: 32, height: 32), rotatedContext: { size, context in + context.clear(NSMakeRect(0, 0, size.width, size.height)) + context.setStrokeColor(backgroundColor.cgColor) + context.setLineWidth(2.0) + context.strokeEllipse(in: NSMakeRect(1.0, 1.0, size.width - 2.0, size.height - 2.0)) + })! +} + + +class AccentColorRowItem: GeneralRowItem { + let selectAccentColor:(AppearanceAccentColor?)->Void + let menuItems: (AppearanceAccentColor)->[ContextMenuItem] + let list: [AppearanceAccentColor] + let isNative: Bool + let theme: TelegramPresentationTheme + let context: AccountContext + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, list: [AppearanceAccentColor], isNative: Bool, theme: TelegramPresentationTheme, viewType: GeneralViewType = .legacy, selectAccentColor: @escaping(AppearanceAccentColor?)->Void, menuItems: @escaping(AppearanceAccentColor)->[ContextMenuItem]) { + self.selectAccentColor = selectAccentColor + self.list = list + self.theme = theme + self.isNative = isNative + self.menuItems = menuItems + self.context = context + super.init(initialSize, height: 36 + viewType.innerInset.top + viewType.innerInset.bottom, stableId: stableId, viewType: viewType) + } + + + override func viewClass() -> AnyClass { + return AccentColorRowView.self + } +} + + +private final class AccentScrollView : ScrollView { + override func scrollWheel(with event: NSEvent) { + + var scrollPoint = contentView.bounds.origin + let isInverted: Bool = System.isScrollInverted + if event.scrollingDeltaY != 0 { + if isInverted { + scrollPoint.x += -event.scrollingDeltaY + } else { + scrollPoint.x -= event.scrollingDeltaY + } + } + if event.scrollingDeltaX != 0 { + if !isInverted { + scrollPoint.x += -event.scrollingDeltaX + } else { + scrollPoint.x -= event.scrollingDeltaX + } + } + if documentView!.frame.width > frame.width { + scrollPoint.x = min(max(0, scrollPoint.x), documentView!.frame.width - frame.width) + clipView.scroll(to: scrollPoint) + } else { + superview?.scrollWheel(with: event) + } + } +} + +final class AccentColorRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let scrollView: AccentScrollView = AccentScrollView() + private let borderView:View = View() + private let documentView: View = View() + + private let disposable = MetaDisposable() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + containerView.addSubview(scrollView) + scrollView.documentView = documentView + scrollView.backgroundColor = .clear + scrollView.background = .clear + containerView.addSubview(borderView) + documentView.backgroundColor = .clear + addSubview(containerView) + } + + override var backdorColor: NSColor { + guard let item = item as? AccentColorRowItem else { + return theme.colors.background + } + return item.theme.colors.background + } + + override func updateColors() { + guard let item = item as? AccentColorRowItem else { + return + } + self.containerView.backgroundColor = backdorColor + borderView.backgroundColor = item.theme.colors.border + self.backgroundColor = item.viewType.rowBackground + } + + override func layout() { + super.layout() + + guard let item = item as? AccentColorRowItem else { + return + } + + let innerInset = item.viewType.innerInset + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + self.borderView.frame = NSMakeRect(innerInset.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - innerInset.left - innerInset.right, .borderSize) + + scrollView.frame = NSMakeRect(0, innerInset.top, item.blockWidth, containerView.frame.height - innerInset.top - innerInset.bottom) + } + + override func menu(for event: NSEvent) -> NSMenu? { + guard let item = item as? AccentColorRowItem else { + return nil + } + + let documentPoint = documentView.convert(event.locationInWindow, from: nil) + + for (_, subview) in documentView.subviews.enumerated() { + if NSPointInRect(documentPoint, subview.frame), let accent = (subview as? Button)?.contextObject as? AppearanceAccentColor { + let items = item.menuItems(accent) + let menu = ContextMenu() + menu.items = items + + return menu + } + } + + return nil + } + + private let selectedImageView = ImageView() + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + documentView.removeAllSubviews() + + guard let item = item as? AccentColorRowItem else { + return + } + + self.layout() + + selectedImageView.image = generateSelectedRing(backgroundColor: theme.colors.background) + selectedImageView.setFrameSize(NSMakeSize(32, 32)) + selectedImageView.removeFromSuperview() + var colorList: [AppearanceAccentColor] = item.list + + borderView.isHidden = !item.viewType.hasBorder + + let insetWidth: CGFloat = 20 + + var x: CGFloat = insetWidth + + + if item.isNative { + let custom = ImageButton(frame: NSMakeRect(x, 0, 36, 36)) + custom.autohighlight = false + custom.set(image: generateCustomSwatchImage(), for: .Normal) + custom.setImageContentGravity(.resize) + custom.set(handler: { _ in + item.selectAccentColor(nil) + }, for: .Click) + documentView.addSubview(custom) + + x += custom.frame.width + insetWidth + } + + if !colorList.contains(where: { $0.accent.accent == theme.colors.accent && $0.cloudTheme?.id == theme.cloudTheme?.id }) { + let button = ImageButton(frame: NSMakeRect(x, 0, 36, 36)) + button.autohighlight = false + button.layer?.cornerRadius = button.frame.height / 2 + button.set(background: theme.colors.accent, for: .Normal) + button.addSubview(selectedImageView) + selectedImageView.center() + x += button.frame.width + insetWidth + documentView.addSubview(button) + } + + let disposableSet = DisposableSet() + + for i in 0 ..< colorList.count { + + var accent = colorList[i] + + if let cloudTheme = accent.cloudTheme, let settings = cloudTheme.settings { + let signal = themeAppearanceThumbAndData(context: item.context, bubbled: false, source: .local(settings.palette, cloudTheme)) |> deliverOnMainQueue + disposableSet.add(signal.start(next: { result in + switch result.1 { + case let .cloud(_, cachedData): + accent = accent.withUpdatedCachedTheme(cachedData) + default: + break + } + })) + } + + let button = ImageButton(frame: NSMakeRect(x, 0, 36, 36)) + button.autohighlight = false + // button.layer?.cornerRadius = button.frame.height / 2 + let icon = generateAccentColor(accent.accent, bubbled: theme.bubbled) + button.contextObject = colorList[i] + button.setImageContentGravity(.resize) + button.set(image: icon, for: .Normal) + button.set(image: icon, for: .Hover) + button.set(image: icon, for: .Highlight) + button.set(handler: { _ in + item.selectAccentColor(accent) + }, for: .Click) + if accent.accent.accent == theme.colors.accent { + if accent.cloudTheme?.id == theme.cloudTheme?.id { + button.addSubview(selectedImageView) + selectedImageView.center() + } + } + documentView.addSubview(button) + x += button.frame.width + insetWidth + + if i == colorList.count - 1 { + x -= insetWidth + } + } + + + disposable.set(disposableSet) + + + + + documentView.setFrameSize(NSMakeSize(x + insetWidth, frame.height)) + } + + deinit { + disposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/AccountContext.swift b/Telegram-Mac/AccountContext.swift new file mode 100644 index 0000000000..e660b724ba --- /dev/null +++ b/Telegram-Mac/AccountContext.swift @@ -0,0 +1,780 @@ +// +// AccountContext.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit +//import WalletCore +import SyncCore + + + +struct TemporaryPasswordContainer { + let date: TimeInterval + let password: String + + var isActive: Bool { + return date + 15 * 60 > Date().timeIntervalSince1970 + } +} +// +//private final class TonInstanceData { +// var config: String? +// var blockchainName: String? +// var instance: TonInstance? +//} +// +//private final class TonNetworkProxyImpl: TonNetworkProxy { +// private let network: Network +// +// init(network: Network) { +// self.network = network +// } +// +// func request(data: Data, timeout timeoutValue: Double, completion: @escaping (TonNetworkProxyResult) -> Void) -> Disposable { +// return (walletProxyRequest(network: self.network, data: data) +// |> timeout(timeoutValue, queue: .concurrentDefaultQueue(), alternate: .fail(.generic(500, "Local Timeout")))).start(next: { data in +// completion(.reponse(data)) +// }, error: { error in +// switch error { +// case let .generic(_, text): +// completion(.error(text)) +// } +// }) +// } +//} +// +//final class WalletStorageInterfaceImpl: WalletStorageInterface { +// private let postbox: Postbox +// +// init(postbox: Postbox) { +// self.postbox = postbox +// } +// +// func watchWalletRecords() -> Signal<[WalletStateRecord], NoError> { +// return self.postbox.preferencesView(keys: [PreferencesKeys.walletCollection]) +// |> map { view -> [WalletStateRecord] in +// guard let walletCollection = view.values[PreferencesKeys.walletCollection] as? WalletCollection else { +// return [] +// } +// return walletCollection.wallets.compactMap { item -> WalletStateRecord? in +// do { +// return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) +// } catch { +// return nil +// } +// } +// } +// } +// +// func getWalletRecords() -> Signal<[WalletStateRecord], NoError> { +// return self.postbox.transaction { transaction -> [WalletStateRecord] in +// guard let walletCollection = transaction.getPreferencesEntry(key: PreferencesKeys.walletCollection) as? WalletCollection else { +// return [] +// } +// return walletCollection.wallets.compactMap { item -> WalletStateRecord? in +// do { +// return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) +// } catch { +// return nil +// } +// } +// } +// } +// +// func updateWalletRecords(_ f: @escaping ([WalletStateRecord]) -> [WalletStateRecord]) -> Signal<[WalletStateRecord], NoError> { +// return self.postbox.transaction { transaction -> [WalletStateRecord] in +// let updatedRecords: [WalletStateRecord] = [] +// transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in +// var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: []) +// let updatedItems = f(walletCollection.wallets.compactMap { item -> WalletStateRecord? in +// do { +// return WalletStateRecord(info: try JSONDecoder().decode(WalletInfo.self, from: item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { try? JSONDecoder().decode(CombinedWalletState.self, from: $0) }) +// } catch { +// return nil +// } +// }) +// walletCollection.wallets = updatedItems.compactMap { item in +// do { +// return WalletCollectionItem(info: try JSONEncoder().encode(item.info), exportCompleted: item.exportCompleted, state: item.state.flatMap { +// try? JSONEncoder().encode($0) +// }) +// } catch { +// return nil +// } +// } +// return walletCollection +// }) +// return updatedRecords +// } +// } +// +// func localWalletConfiguration() -> Signal { +// return .single(LocalWalletConfiguration(source: .string(""), blockchainName: "")) +// } +// +// func updateLocalWalletConfiguration(_ f: @escaping (LocalWalletConfiguration) -> LocalWalletConfiguration) -> Signal { +// return .complete() +// } +//} +// +//final class StoredTonContext { +// private let basePath: String +// private let postbox: Postbox +// private let network: Network +// let keychain: TonKeychain +// private let currentInstance = Atomic(value: TonInstanceData()) +// +// init(basePath: String, postbox: Postbox, network: Network, keychain: TonKeychain) { +// self.basePath = basePath +// self.postbox = postbox +// self.network = network +// self.keychain = keychain +// } +// +// func context(config: String, blockchainName: String, enableProxy: Bool) -> TonContext { +// return self.currentInstance.with { data -> TonContext in +// if let instance = data.instance, data.config == config, data.blockchainName == blockchainName { +// return TonContext(instance: instance, keychain: self.keychain, storage: WalletStorageInterfaceImpl(postbox: self.postbox)) +// } else { +// data.config = config +// let tonNetwork: TonNetworkProxy? +// if enableProxy { +// tonNetwork = TonNetworkProxyImpl(network: self.network) +// } else { +// tonNetwork = nil +// } +// let instance = TonInstance(basePath: self.basePath, config: config, blockchainName: blockchainName, proxy: tonNetwork) +// data.instance = instance +// return TonContext(instance: instance, keychain: self.keychain, storage: WalletStorageInterfaceImpl(postbox: self.postbox)) +// } +// } +// } +//} +// +////struct TonContext { +//// let instance: TonInstance +//// let keychain: TonKeychain +//// let storage: WalletStorageInterfaceImpl +//// init(instance: TonInstance, keychain: TonKeychain, storage: WalletStorageInterfaceImpl) { +//// self.instance = instance +//// self.keychain = keychain +//// self.storage = storage +//// } +////} + + +enum ApplyThemeUpdate { + case local(ColorPalette) + case cloud(TelegramTheme) +} + +final class AccountContextBindings { + #if !SHARE + let rootNavigation: () -> MajorNavigationController + let mainController: () -> MainViewController + let showControllerToaster: (ControllerToaster, Bool) -> Void + let globalSearch:(String)->Void + let switchSplitLayout:(SplitViewState)->Void + let entertainment:()->EntertainmentViewController + let needFullsize:()->Void + let displayUpgradeProgress:(CGFloat)->Void + init(rootNavigation: @escaping() -> MajorNavigationController = { fatalError() }, mainController: @escaping() -> MainViewController = { fatalError() }, showControllerToaster: @escaping(ControllerToaster, Bool) -> Void = { _, _ in fatalError() }, globalSearch: @escaping(String) -> Void = { _ in fatalError() }, entertainment: @escaping()->EntertainmentViewController = { fatalError() }, switchSplitLayout: @escaping(SplitViewState)->Void = { _ in fatalError() }, needFullsize: @escaping() -> Void = { fatalError() }, displayUpgradeProgress: @escaping(CGFloat)->Void = { _ in fatalError() }) { + self.rootNavigation = rootNavigation + self.mainController = mainController + self.showControllerToaster = showControllerToaster + self.globalSearch = globalSearch + self.entertainment = entertainment + self.switchSplitLayout = switchSplitLayout + self.needFullsize = needFullsize + self.displayUpgradeProgress = displayUpgradeProgress + } + #endif +} + +private var lastTimeFreeSpaceNotified: TimeInterval? + + +final class AccountContext { + let sharedContext: SharedAccountContext + let account: Account + let window: Window + + #if !SHARE + let fetchManager: FetchManager + let diceCache: DiceCache + #endif + private(set) var timeDifference:TimeInterval = 0 + #if !SHARE + let peerChannelMemberCategoriesContextsManager = PeerChannelMemberCategoriesContextsManager() + let chatUndoManager = ChatUndoManager() + let blockedPeersContext: BlockedPeersContext + let activeSessionsContext: ActiveSessionsContext + // let walletPasscodeTimeoutContext: WalletPasscodeTimeoutContext + #endif + + let cancelGlobalSearch:ValuePromise = ValuePromise(ignoreRepeated: false) + + + + var isCurrent: Bool = false { + didSet { + if !self.isCurrent { + //self.callManager = nil + } + } + } + + + let globalPeerHandler:Promise = Promise() + + func updateGlobalPeer() { + globalPeerHandler.set(globalPeerHandler.get() |> take(1)) + } + + let hasPassportSettings: Promise = Promise(false) + + private var _recentlyPeerUsed:[PeerId] = [] + + private(set) var recentlyPeerUsed:[PeerId] { + set { + _recentlyPeerUsed = newValue + } + get { + if _recentlyPeerUsed.count > 2 { + return Array(_recentlyPeerUsed.prefix(through: 2)) + } else { + return _recentlyPeerUsed + } + } + } + + var peerId: PeerId { + return account.peerId + } + + private let updateDifferenceDisposable = MetaDisposable() + private let temporaryPwdDisposable = MetaDisposable() + private let actualizeCloudTheme = MetaDisposable() + private let applyThemeDisposable = MetaDisposable() + private let cloudThemeObserver = MetaDisposable() + private let freeSpaceDisposable = MetaDisposable() + private let prefDisposable = DisposableSet() + private let _limitConfiguration: Atomic = Atomic(value: LimitsConfiguration.defaultValue) + + var limitConfiguration: LimitsConfiguration { + return _limitConfiguration.with { $0 } + } + + private let _appConfiguration: Atomic = Atomic(value: AppConfiguration.defaultValue) + + var appConfiguration: AppConfiguration { + return _appConfiguration.with { $0 } + } + + + private let isKeyWindowValue: ValuePromise = ValuePromise(ignoreRepeated: true) + + var isKeyWindow: Signal { + return isKeyWindowValue.get() |> deliverOnMainQueue + } + + private let _autoplayMedia: Atomic = Atomic(value: AutoplayMediaPreferences.defaultSettings) + + var autoplayMedia: AutoplayMediaPreferences { + return _autoplayMedia.with { $0 } + } + + + var isInGlobalSearch: Bool = false + + + private let _contentSettings: Atomic = Atomic(value: ContentSettings.default) + + var contentSettings: ContentSettings { + return _contentSettings.with { $0 } + } + + // public let tonContext: StoredTonContext! + + public var closeFolderFirst: Bool = false + + private let preloadGifsDisposable = MetaDisposable() + + //, tonContext: StoredTonContext? + init(sharedContext: SharedAccountContext, window: Window, account: Account) { + self.sharedContext = sharedContext + self.account = account + self.window = window + // self.tonContext = tonContext + #if !SHARE + self.diceCache = DiceCache(postbox: account.postbox, network: account.network) + self.fetchManager = FetchManager(postbox: account.postbox) + self.blockedPeersContext = BlockedPeersContext(account: account) + self.activeSessionsContext = ActiveSessionsContext(account: account) + // self.walletPasscodeTimeoutContext = WalletPasscodeTimeoutContext(postbox: account.postbox) + #endif + + + + + let limitConfiguration = _limitConfiguration + prefDisposable.add(account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration]).start(next: { view in + _ = limitConfiguration.swap(view.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration ?? LimitsConfiguration.defaultValue) + })) + let preloadGifsDisposable = self.preloadGifsDisposable + let appConfiguration = _appConfiguration + prefDisposable.add(account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]).start(next: { view in + let configuration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue + _ = appConfiguration.swap(configuration) + + + })) + + #if !SHARE + let signal:Signal = Signal { subscriber in + + let signal: Signal = account.postbox.transaction { + return $0.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration ?? AppConfiguration.defaultValue + } |> mapToSignal { configuration in + let value = GIFKeyboardConfiguration.with(appConfiguration: configuration) + var signals = value.emojis.map { + searchGifs(account: account, query: $0) + } + signals.insert(searchGifs(account: account, query: ""), at: 0) + return combineLatest(signals) |> ignoreValues + } + + let disposable = signal.start(completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + disposable.dispose() + } + } + + let updated = (signal |> then(.complete() |> suspendAwareDelay(20.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart + preloadGifsDisposable.set(updated.start()) + + #endif + + let autoplayMedia = _autoplayMedia + prefDisposable.add(account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.autoplayMedia]).start(next: { view in + _ = autoplayMedia.swap(view.values[ApplicationSpecificPreferencesKeys.autoplayMedia] as? AutoplayMediaPreferences ?? AutoplayMediaPreferences.defaultSettings) + })) + + let contentSettings = _contentSettings + prefDisposable.add(getContentSettings(postbox: account.postbox).start(next: { settings in + _ = contentSettings.swap(settings) + })) + + + globalPeerHandler.set(.single(nil)) + + if account.network.globalTime > 0 { + timeDifference = floor(account.network.globalTime - Date().timeIntervalSince1970) + } + + updateDifferenceDisposable.set((Signal.single(Void()) + |> delay(5, queue: Queue.mainQueue()) |> restart).start(next: { [weak self, weak account] in + if let account = account, account.network.globalTime > 0 { + self?.timeDifference = floor(account.network.globalTime - Date().timeIntervalSince1970) + } + })) + + + let cloudSignal = themeUnmodifiedSettings(accountManager: sharedContext.accountManager) |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs.cloudTheme == rhs.cloudTheme + }) + |> map { value in + return (value.cloudTheme, value.palette) + } + |> deliverOnMainQueue + + cloudThemeObserver.set(cloudSignal.start(next: { [weak self] (cloud, palette) in + let update: ApplyThemeUpdate + if let cloud = cloud { + update = .cloud(cloud) + } else { + update = .local(palette) + } + self?.updateTheme(update) + })) + + + NotificationCenter.default.addObserver(self, selector: #selector(updateKeyWindow), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updateKeyWindow), name: NSWindow.didResignKeyNotification, object: window) + + + #if !SHARE + var freeSpaceSignal:Signal = Signal { subscriber in + + subscriber.putNext(freeSystemGygabytes()) + subscriber.putCompletion() + + return ActionDisposable { + + } + } |> runOn(.concurrentDefaultQueue()) + + freeSpaceSignal = (freeSpaceSignal |> then(.complete() |> suspendAwareDelay(60.0 * 30, queue: Queue.concurrentDefaultQueue()))) |> restart + + + let isLocked = (NSApp.delegate as? AppDelegate)?.passlock ?? .single(false) + + + freeSpaceDisposable.set(combineLatest(queue: .mainQueue(), freeSpaceSignal, isKeyWindow, isLocked).start(next: { [weak self] space, isKeyWindow, locked in + + + var limit: UInt64 = 5 + + guard let `self` = self, isKeyWindow, !locked, let space = space, space < limit else { + return + } + if lastTimeFreeSpaceNotified == nil || (lastTimeFreeSpaceNotified! + 60.0 * 60.0 * 3 < Date().timeIntervalSince1970) { + lastTimeFreeSpaceNotified = Date().timeIntervalSince1970 + showOutOfMemoryWarning(window, freeSpace: space, context: self) + } + + })) + #endif + } + + @objc private func updateKeyWindow() { + self.isKeyWindowValue.set(window.isKeyWindow) + } + + private func updateTheme(_ update: ApplyThemeUpdate) { + switch update { + case let .cloud(cloudTheme): + _ = applyTheme(accountManager: self.sharedContext.accountManager, account: self.account, theme: cloudTheme).start() + let signal = actualizedTheme(account: self.account, accountManager: self.sharedContext.accountManager, theme: cloudTheme) |> deliverOnMainQueue + self.actualizeCloudTheme.set(signal.start(next: { [weak self] cloudTheme in + if let `self` = self { + self.applyThemeDisposable.set(downloadAndApplyCloudTheme(context: self, theme: cloudTheme, install: theme.cloudTheme?.id != cloudTheme.id).start()) + } + })) + case let .local(palette): + actualizeCloudTheme.set(applyTheme(accountManager: self.sharedContext.accountManager, account: self.account, theme: nil).start()) + applyThemeDisposable.set(updateThemeInteractivetly(accountManager: self.sharedContext.accountManager, f: { + return $0.withUpdatedPalette(palette).withUpdatedCloudTheme(nil) + }).start()) + } + } + + var timestamp: Int32 { + var time:TimeInterval = TimeInterval(Date().timeIntervalSince1970) + time -= self.timeDifference + return Int32(time) + } + + + private var _temporartPassword: String? + var temporaryPassword: String? { + return _temporartPassword + } + + func resetTemporaryPwd() { + _temporartPassword = nil + temporaryPwdDisposable.set(nil) + } + + func setTemporaryPwd(_ password: String) -> Void { + _temporartPassword = password + let signal = Signal.single(Void()) |> delay(30 * 60, queue: Queue.mainQueue()) + temporaryPwdDisposable.set(signal.start(next: { [weak self] in + self?._temporartPassword = nil + })) + } + + deinit { + cleanup() + } + + + func cleanup() { + updateDifferenceDisposable.dispose() + temporaryPwdDisposable.dispose() + prefDisposable.dispose() + actualizeCloudTheme.dispose() + applyThemeDisposable.dispose() + cloudThemeObserver.dispose() + preloadGifsDisposable.dispose() + freeSpaceDisposable.dispose() + NotificationCenter.default.removeObserver(self) + #if !SHARE + // self.walletPasscodeTimeoutContext.clear() + self.diceCache.cleanup() + #endif + } + + + func checkFirstRecentlyForDuplicate(peerId:PeerId) { + if let index = recentlyPeerUsed.firstIndex(of: peerId), index == 0 { + recentlyPeerUsed.remove(at: index) + } + } + + func addRecentlyUsedPeer(peerId:PeerId) { + if let index = recentlyPeerUsed.firstIndex(of: peerId) { + recentlyPeerUsed.remove(at: index) + } + recentlyPeerUsed.insert(peerId, at: 0) + if recentlyPeerUsed.count > 4 { + recentlyPeerUsed = Array(recentlyPeerUsed.prefix(through: 4)) + } + } + + + #if !SHARE + func composeCreateGroup() { + createGroup(with: self) + } + func composeCreateChannel() { + createChannel(with: self) + } + func composeCreateSecretChat() { + let account = self.account + let confirmationImpl:([PeerId])->Signal = { peerIds in + if let first = peerIds.first, peerIds.count == 1 { + return account.postbox.loadedPeerWithId(first) |> deliverOnMainQueue |> mapToSignal { peer in + return confirmSignal(for: mainWindow, information: L10n.composeConfirmStartSecretChat(peer.displayTitle)) + } + } + return confirmSignal(for: mainWindow, information: L10n.peerInfoConfirmAddMembers1Countable(peerIds.count)) + } + let select = selectModalPeers(context: self, title: L10n.composeSelectSecretChat, limit: 1, confirmation: confirmationImpl) + + let create = select |> map { $0.first! } |> mapToSignal { peerId in + return createSecretChat(account: account, peerId: peerId) |> `catch` {_ in .complete()} + } |> deliverOnMainQueue |> mapToSignal{ peerId -> Signal in + return showModalProgress(signal: .single(peerId), for: mainWindow) + } + + _ = create.start(next: { [weak self] peerId in + guard let `self` = self else {return} + self.sharedContext.bindings.rootNavigation().push(ChatController(context: self, chatLocation: .peer(peerId))) + }) + } + #endif +} + + +func downloadAndApplyCloudTheme(context: AccountContext, theme cloudTheme: TelegramTheme, install: Bool = false) -> Signal { + if let cloudSettings = cloudTheme.settings { + return Signal { subscriber in + #if !SHARE + let wallpaperDisposable = DisposableSet() + let palette = cloudSettings.palette + var wallpaper: Signal? = nil + let associated = theme.wallpaper.associated?.wallpaper + if let w = cloudSettings.wallpaper, theme.wallpaper.wallpaper == associated || install { + wallpaper = .single(w) + } else if install, let wrapper = palette.wallpaper.wallpaper.cloudWallpaper { + wallpaper = .single(wrapper) + } + + if let wallpaper = wallpaper { + wallpaperDisposable.add(wallpaper.start(next: { cloud in + if let cloud = cloud { + let wp = Wallpaper(cloud) + wallpaperDisposable.add(moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wp).start(next: { wallpaper in + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme) + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { _ in + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: AssociatedWallpaper(cloud: cloud, wallpaper: wp)) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + settings = settings.withUpdatedDefaultIsDark(palette.isDark) + return settings.updateWallpaper { value in + return value.withUpdatedWallpaper(wallpaper) + .withUpdatedAssociated(AssociatedWallpaper(cloud: cloud, wallpaper: wallpaper)) + }.saveDefaultWallpaper().withSavedAssociatedTheme().saveDefaultAccent(color: cloudSettings.accent) + }).start() + + subscriber.putCompletion() + })) + } else { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { _ in + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: AssociatedWallpaper(cloud: cloud, wallpaper: .none)) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + settings = settings.withUpdatedDefaultIsDark(palette.isDark) + + return settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme).updateWallpaper({ value in + return value.withUpdatedWallpaper(.none) + .withUpdatedAssociated(AssociatedWallpaper(cloud: cloud, wallpaper: .none)) + }).saveDefaultWallpaper().withSavedAssociatedTheme().saveDefaultAccent(color: cloudSettings.accent) + }).start() + subscriber.putCompletion() + } + }, error: { _ in + subscriber.putCompletion() + })) + } else { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme) + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { current in + let associated = current?.wallpaper ?? AssociatedWallpaper(cloud: nil, wallpaper: palette.wallpaper.wallpaper) + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: associated) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + return settings.withSavedAssociatedTheme().saveDefaultAccent(color: cloudSettings.accent) + }).start() + subscriber.putCompletion() + } + #endif + return ActionDisposable { + #if !SHARE + wallpaperDisposable.dispose() + #endif + } + } + |> runOn(.mainQueue()) + |> deliverOnMainQueue + } else if let file = cloudTheme.file { + return Signal { subscriber in + let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: file.resource)).start() + let wallpaperDisposable = DisposableSet() + + let resourceData = context.account.postbox.mediaBox.resourceData(file.resource) |> filter { $0.complete } |> take(1) + + let dataDisposable = resourceData.start(next: { data in + + if let palette = importPalette(data.path) { + var wallpaper: Signal? = nil + var newSettings: WallpaperSettings = WallpaperSettings() + #if !SHARE + switch palette.wallpaper { + case .none: + if theme.wallpaper.wallpaper == theme.wallpaper.associated?.wallpaper || install { + wallpaper = .single(nil) + } + case .builtin: + if theme.wallpaper.wallpaper == theme.wallpaper.associated?.wallpaper || install { + wallpaper = .single(.builtin(WallpaperSettings())) + } + case let .color(color): + if theme.wallpaper.wallpaper == theme.wallpaper.associated?.wallpaper || install { + wallpaper = .single(.color(color.argb)) + } + case let .url(string): + let link = inApp(for: string as NSString, context: context) + switch link { + case let .wallpaper(values): + switch values.preview { + case let .slug(slug, settings): + if theme.wallpaper.wallpaper == theme.wallpaper.associated?.wallpaper || install { + if let associated = theme.wallpaper.associated, let cloud = associated.cloud { + switch cloud { + case let .file(values): + if values.slug == values.slug && values.settings == settings { + wallpaper = .single(cloud) + } else { + wallpaper = getWallpaper(network: context.account.network, slug: slug) |> map(Optional.init) + } + default: + wallpaper = getWallpaper(network: context.account.network, slug: slug) |> map(Optional.init) + } + } else { + wallpaper = getWallpaper(network: context.account.network, slug: slug) |> map(Optional.init) + } + } + newSettings = settings + default: + break + } + default: + break + } + } + + #endif + + if let wallpaper = wallpaper { + #if !SHARE + wallpaperDisposable.add(wallpaper.start(next: { cloud in + if let cloud = cloud { + let wp = Wallpaper(cloud).withUpdatedSettings(newSettings) + wallpaperDisposable.add(moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wp).start(next: { wallpaper in + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme) + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { _ in + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: AssociatedWallpaper(cloud: cloud, wallpaper: wp)) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + settings = settings.withUpdatedDefaultIsDark(palette.isDark) + return settings.updateWallpaper { value in + return value.withUpdatedWallpaper(wallpaper) + .withUpdatedAssociated(AssociatedWallpaper(cloud: cloud, wallpaper: wallpaper)) + }.saveDefaultWallpaper() + }).start() + + subscriber.putCompletion() + })) + } else { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { _ in + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: AssociatedWallpaper(cloud: cloud, wallpaper: .none)) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + settings = settings.withUpdatedDefaultIsDark(palette.isDark) + + return settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme).updateWallpaper({ value in + return value.withUpdatedWallpaper(.none) + .withUpdatedAssociated(AssociatedWallpaper(cloud: cloud, wallpaper: .none)) + }).saveDefaultWallpaper() + }).start() + subscriber.putCompletion() + } + }, error: { _ in + subscriber.putCompletion() + })) + #endif + } else { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings.withUpdatedPalette(palette).withUpdatedCloudTheme(cloudTheme) + var updateDefault:DefaultTheme = palette.isDark ? settings.defaultDark : settings.defaultDay + updateDefault = updateDefault.updateCloud { current in + let associated = current?.wallpaper ?? AssociatedWallpaper(cloud: nil, wallpaper: palette.wallpaper.wallpaper) + return DefaultCloudTheme(cloud: cloudTheme, palette: palette, wallpaper: associated) + } + settings = palette.isDark ? settings.withUpdatedDefaultDark(updateDefault) : settings.withUpdatedDefaultDay(updateDefault) + return settings + }).start() + subscriber.putCompletion() + } + } + }) + + return ActionDisposable { + fetchDisposable.dispose() + dataDisposable.dispose() + wallpaperDisposable.dispose() + } + } + |> runOn(.mainQueue()) + |> deliverOnMainQueue + } else { + return .complete() + } +} + + diff --git a/Telegram-Mac/AccountInfoItem.swift b/Telegram-Mac/AccountInfoItem.swift index 304e9d5f6b..074ccb2948 100644 --- a/Telegram-Mac/AccountInfoItem.swift +++ b/Telegram-Mac/AccountInfoItem.swift @@ -8,66 +8,63 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit -enum AccountInfoItemState { - case normal - case edit -} -class AccountInfoItem: TableRowItem { - - let saveCallback:()->Void - var firstName:String - var lastName:String - - let account: Account - let peer: TelegramUser - let connectionStatus: ConnectionStatus - var state:AccountInfoItemState - let statusLayout:(TextNodeLayout, TextNode) - let nameLayout:(TextNodeLayout, TextNode) - let editCallback:()->Void - let imageCallback:()->Void - private let _stableId:AnyHashable - override var stableId: AnyHashable { - return _stableId - } + +class AccountInfoItem: GeneralRowItem { - override var height:CGFloat { - return 100 - } + fileprivate let textLayout: TextViewLayout + fileprivate let activeTextlayout: TextViewLayout + fileprivate let context: AccountContext + fileprivate let peer: TelegramUser + private(set) var photos: [TelegramPeerPhoto] = [] + + private let peerPhotosDisposable = MetaDisposable() - init(_ initialSize:NSSize, stableId:AnyHashable, account: Account, peer: TelegramUser, state:AccountInfoItemState, connectionStatus: ConnectionStatus, saveCallback:@escaping()->Void, editCallback:@escaping()->Void, imageCallback:@escaping()->Void) { - self.saveCallback = saveCallback - self.editCallback = editCallback - self.imageCallback = imageCallback - self.account = account - self._stableId = stableId + init(_ initialSize:NSSize, stableId:AnyHashable, context: AccountContext, peer: TelegramUser, action: @escaping()->Void) { + self.context = context self.peer = peer - self.state = state - self.connectionStatus = connectionStatus - self.firstName = peer.firstName ?? "" - self.lastName = peer.lastName ?? "" - let statusAttributed:NSAttributedString - switch connectionStatus { - case .connecting(let toProxy): - statusAttributed = .initialize(string: toProxy ? tr(.connectingStatusConnectingToProxy) : tr(.connectingStatusConnecting), color: theme.colors.grayText, font: .normal(.text)) - case .online: - statusAttributed = .initialize(string: tr(.connectingStatusOnline), color: theme.colors.blueUI, font: .normal(.text)) - case .updating: - statusAttributed = .initialize(string: tr(.connectingStatusUpdating), color: theme.colors.grayText, font: .normal(.text)) - case .waitingForNetwork: - statusAttributed = .initialize(string: tr(.connectingStatusWaitingNetwork), color: theme.colors.grayText, font: .normal(.text)) + let attr = NSMutableAttributedString() + + _ = attr.append(string: peer.displayTitle, color: theme.colors.text, font: .medium(.title)) + if let phone = peer.phone { + _ = attr.append(string: "\n") + _ = attr.append(string: formatPhoneNumber(phone), color: theme.colors.grayText, font: .normal(.text)) + } + if let username = peer.username, !username.isEmpty { + _ = attr.append(string: "\n") + _ = attr.append(string: "@\(username)", color: theme.colors.grayText, font: .normal(.text)) } - statusLayout = TextNode.layoutText(maybeNode: nil, statusAttributed, nil, 1, .end, NSMakeSize(initialSize.width - 140, 20), nil, false, .left) - nameLayout = TextNode.layoutText(maybeNode: nil, .initialize(string: peer.displayTitle, color: theme.colors.text, font: .normal(.title)), nil, 1, .end, NSMakeSize(initialSize.width - 140, 20), nil, false, .left) + textLayout = TextViewLayout(attr, maximumNumberOfLines: 4) - super.init(initialSize) + let active = attr.mutableCopy() as! NSMutableAttributedString + active.addAttribute(.foregroundColor, value: theme.colors.underSelectedColor, range: active.range) + activeTextlayout = TextViewLayout(active, maximumNumberOfLines: 4) + super.init(initialSize, height: 90, stableId: stableId, action: action) + + self.photos = syncPeerPhotos(peerId: peer.id) + let signal = peerPhotos(account: context.account, peerId: peer.id, force: true) |> deliverOnMainQueue + peerPhotosDisposable.set(signal.start(next: { [weak self] photos in + self?.photos = photos + self?.redraw() + })) + + } + + deinit { + peerPhotosDisposable.dispose() + } + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - 100) + activeTextlayout.measure(width: width - 100) + return success } override func viewClass() -> AnyClass { @@ -76,209 +73,182 @@ class AccountInfoItem: TableRowItem { } -class AccountInfoView : TableRowView, TGModernGrowingDelegate { +class AccountInfoView : TableRowView { - let avatarView:AvatarControl - let firstNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) - let lastNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) - private let editButton: ImageButton = ImageButton() - private let updoadPhotoCap:ImageButton = ImageButton() + private let avatarView:AvatarControl + private let textView: TextView = TextView() + private let actionView: ImageView = ImageView() + + private var photoVideoView: MediaPlayerView? + private var photoVideoPlayer: MediaPlayer? + + required init(frame frameRect: NSRect) { - avatarView = AvatarControl(font: .avatar(.custom(22))) + avatarView = AvatarControl(font: .avatar(22.0)) avatarView.setFrameSize(NSMakeSize(60, 60)) super.init(frame: frameRect) - + layerContentsRedrawPolicy = .onSetNeedsDisplay avatarView.animated = true - addSubview(avatarView) - addSubview(firstNameTextView) - addSubview(lastNameTextView) - addSubview(editButton) + textView.userInteractionEnabled = false + textView.isSelectable = false - updoadPhotoCap.backgroundColor = NSColor.black.withAlphaComponent(0.4) - updoadPhotoCap.setFrameSize(avatarView.frame.size) - updoadPhotoCap.layer?.cornerRadius = updoadPhotoCap.frame.width / 2 - updoadPhotoCap.set(image: ControlStyle(highlightColor: .white).highlight(image: theme.icons.chatAttachCamera), for: .Normal) - updoadPhotoCap.set(image: ControlStyle(highlightColor: theme.colors.blueIcon).highlight(image: theme.icons.chatAttachCamera), for: .Highlight) - - avatarView.addSubview(updoadPhotoCap) + addSubview(avatarView) + addSubview(actionView) + addSubview(textView) avatarView.set(handler: { [weak self] _ in if let item = self?.item as? AccountInfoItem, let _ = item.peer.largeProfileImage { - showPhotosGallery(account: item.account, peerId: item.peer.id, firstStableId: item.stableId, item.table, nil) + showPhotosGallery(context: item.context, peerId: item.peer.id, firstStableId: item.stableId, item.table, nil) } }, for: .Click) - firstNameTextView.textColor = theme.colors.text - lastNameTextView.textColor = theme.colors.text - - firstNameTextView.delegate = self - firstNameTextView.textFont = .normal(.title) - firstNameTextView.min_height = 17 - firstNameTextView.isSingleLine = true - firstNameTextView.max_height = 17 - - lastNameTextView.delegate = self - lastNameTextView.textFont = .normal(.title) - lastNameTextView.min_height = 17 - lastNameTextView.max_height = 17 - lastNameTextView.isSingleLine = true - editButton.set(handler: { [weak self] _ in - if let item = self?.item as? AccountInfoItem { - item.editCallback() - } - }, for: .Click) - - updoadPhotoCap.set(handler: { [weak self] _ in - if let item = self?.item as? AccountInfoItem { - item.imageCallback() - } - }, for: .Click) } - override var backdorColor: NSColor { - return theme.colors.background - } - - override var firstResponder: NSResponder? { - if window?.firstResponder == lastNameTextView.inputView || window?.firstResponder == firstNameTextView.inputView { - return window?.firstResponder + override func mouseUp(with event: NSEvent) { + if let item = item as? AccountInfoItem, mouseInside() { + item.action() } - return self.firstNameTextView - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func textViewHeightChanged(_ height: CGFloat, animated: Bool) { - } - func maxCharactersLimit() -> Int32 { - return 30 + override var backdorColor: NSColor { + return isSelect ? theme.colors.accentSelect : theme.colors.background } - - func textViewSize() -> NSSize { - return NSMakeSize(frame.width - (avatarView.frame.maxX + 10), 17) + + @objc func updatePlayerIfNeeded() { + let accept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) + if accept { + photoVideoPlayer?.play() + } else { + photoVideoPlayer?.pause() + } } - func textViewEnterPressed(_ event:NSEvent) -> Bool { - if FastSettings.checkSendingAbility(for: event) { - if let item = item as? AccountInfoItem { - item.firstName = self.firstNameTextView.string() - item.lastName = self.lastNameTextView.string() - item.saveCallback() - } - return true - } - return false + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + photoVideoPlayer?.seek(timestamp: 0) } - func textViewIsTypingEnabled() -> Bool { - return true + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + updateListeners() + updatePlayerIfNeeded() } - func textViewNeedClose(_ textView: Any) { - + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateListeners() + updatePlayerIfNeeded() } - func textViewTextDidChange(_ string: String) { - if let item = item as? AccountInfoItem { - item.firstName = self.firstNameTextView.string() - item.lastName = self.lastNameTextView.string() + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: item?.table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: item?.table?.view) + } else { + removeNotificationListeners() } } - func textViewTextDidChangeSelectedRange(_ range: NSRange) { - + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) } - func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { - return false + deinit { + removeNotificationListeners() } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var videoRepresentation: TelegramMediaImage.VideoRepresentation? override func set(item: TableRowItem, animated: Bool) { super.set(item: item) if let item = item as? AccountInfoItem { - editButton.set(image: theme.icons.settingsEditInfo, for: .Normal) - editButton.sizeToFit() - firstNameTextView.textColor = theme.colors.text - lastNameTextView.textColor = theme.colors.text - avatarView.setPeer(account: item.account, peer: item.peer) - firstNameTextView.setString(item.peer.firstName ?? "") - lastNameTextView.setString(item.peer.lastName ?? "") - - if item.state != .normal { - updoadPhotoCap.isHidden = false - } - if item.state == .normal { - editButton.isHidden = false - } - - - updoadPhotoCap.change(opacity: item.state == .normal ? 0 : 1, animated: animated, completion: { [weak self, weak item] completed in - if completed, let item = item { - self?.updoadPhotoCap.isHidden = item.state == .normal - } - }) - - editButton.change(opacity: item.state != .normal ? 0 : 1, animated: animated, completion: { [weak self, weak item] completed in - if completed, let item = item { - self?.editButton.isHidden = item.state != .normal + actionView.image = item.isSelected ? nil : theme.icons.generalNext + actionView.sizeToFit() + avatarView.setPeer(account: item.context.account, peer: item.peer) + textView.update(isSelect ? item.activeTextlayout : item.textLayout) + if !item.photos.isEmpty { + if let first = item.photos.first, let video = first.image.videoRepresentations.last { + let equal = videoRepresentation?.resource.id.isEqual(to: video.resource.id) ?? false + if !equal { + + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + + self.photoVideoView = MediaPlayerView() + self.photoVideoView!.layer?.cornerRadius = self.avatarView.frame.height / 2 + self.addSubview(self.photoVideoView!) + self.photoVideoView!.isEventLess = true + self.photoVideoView!.frame = self.avatarView.frame + + let file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: first.image.representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: video.resource.size, attributes: []) + + let mediaPlayer = MediaPlayer(postbox: item.context.account.postbox, reference: MediaResourceReference.standalone(resource: file.resource), streamable: true, video: true, preferSoftwareDecoding: false, enableSound: false, fetchAutomatically: true) + + mediaPlayer.actionAtEnd = .loop(nil) + + self.photoVideoPlayer = mediaPlayer + + mediaPlayer.play() + + if let seekTo = video.startTimestamp { + mediaPlayer.seek(timestamp: seekTo) + } + + mediaPlayer.attachPlayerView(self.photoVideoView!) + self.videoRepresentation = video + updatePlayerIfNeeded() + } + } else { + self.photoVideoPlayer = nil + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil } - }) - - firstNameTextView.isHidden = item.state == .normal - lastNameTextView.isHidden = item.state == .normal + } else { + self.photoVideoPlayer = nil + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + } needsDisplay = true + needsLayout = true } } + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) ctx.setFillColor(theme.colors.border.cgColor) ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) - - if let item = item as? AccountInfoItem { - if item.state == .normal { - var tY = NSMinY(focus(item.nameLayout.0.size)) - - let t = item.nameLayout.0.size.height + item.statusLayout.0.size.height + 4.0 - tY = (NSHeight(self.frame) - t) / 2.0 - - let sY = tY + item.statusLayout.0.size.height + 4.0 - item.statusLayout.1.draw(NSMakeRect(100, floorToScreenPixels(sY), item.statusLayout.0.size.width, item.statusLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - - item.nameLayout.1.draw(NSMakeRect(100, floorToScreenPixels(tY), item.nameLayout.0.size.width, item.nameLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } else { - ctx.fill(NSMakeRect((avatarView.frame.maxX + 12), 46, frame.width - (avatarView.frame.maxX + 20), .borderSize)) - ctx.fill(NSMakeRect((avatarView.frame.maxX + 12), 74, frame.width - (avatarView.frame.maxX + 20), .borderSize)) - } - - } } override func layout() { super.layout() avatarView.centerY(x:16) - firstNameTextView.setFrameSize(frame.width - (avatarView.frame.maxX + 16), 17) - lastNameTextView.setFrameSize(frame.width - (avatarView.frame.maxX + 16), 17) - - firstNameTextView.setFrameOrigin((avatarView.frame.maxX + 10), 27) - lastNameTextView.setFrameOrigin((avatarView.frame.maxX + 10), 55) - editButton.centerY(x: frame.width - editButton.frame.width - 19) + textView.centerY(x: avatarView.frame.maxX + 25) + actionView.centerY(x: frame.width - actionView.frame.width - 10) + photoVideoView?.frame = avatarView.frame } - override var interactionContentView: NSView { + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return avatarView } @@ -287,3 +257,4 @@ class AccountInfoView : TableRowView, TGModernGrowingDelegate { } } + diff --git a/Telegram-Mac/AccountUtils.swift b/Telegram-Mac/AccountUtils.swift new file mode 100644 index 0000000000..0c6599ef5c --- /dev/null +++ b/Telegram-Mac/AccountUtils.swift @@ -0,0 +1,53 @@ +// +// AccountUtils.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +let maximumNumberOfAccounts = 3 + + +func activeAccountsAndPeers(context: AccountContext, includePrimary: Bool = false) -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> { + let sharedContext = context.sharedContext + return context.sharedContext.activeAccounts + |> mapToSignal { primary, activeAccounts, _ -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> in + var accounts: [Signal<(Account, Peer, Int32)?, NoError>] = [] + func accountWithPeer(_ account: Account) -> Signal<(Account, Peer, Int32)?, NoError> { + return combineLatest(account.postbox.peerView(id: account.peerId), renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: account.postbox)) + |> map { view, totalUnreadCount -> (Peer?, Int32) in + return (view.peers[view.peerId], totalUnreadCount.0) + } + |> distinctUntilChanged { lhs, rhs in + return arePeersEqual(lhs.0, rhs.0) && lhs.1 == rhs.1 + } + |> map { peer, totalUnreadCount -> (Account, Peer, Int32)? in + if let peer = peer { + return (account, peer, totalUnreadCount) + } else { + return nil + } + } + } + for (_, account, _) in activeAccounts { + accounts.append(accountWithPeer(account)) + } + + return combineLatest(accounts) + |> map { accounts -> ((Account, Peer)?, [(Account, Peer, Int32)]) in + var primaryRecord: (Account, Peer)? + if let first = accounts.filter({ $0?.0.id == primary?.id }).first, let (account, peer, _) = first { + primaryRecord = (account, peer) + } + let accountRecords: [(Account, Peer, Int32)] = (includePrimary ? accounts : accounts.filter({ $0?.0.id != primary?.id })).compactMap({ $0 }) + return (primaryRecord, accountRecords) + } + } +} diff --git a/Telegram-Mac/AccountViewController.swift b/Telegram-Mac/AccountViewController.swift index 6b93c61115..7ae93598cd 100644 --- a/Telegram-Mac/AccountViewController.swift +++ b/Telegram-Mac/AccountViewController.swift @@ -8,17 +8,82 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +private final class AccountSearchBarView: TitledBarView { + fileprivate let searchView = SearchView(frame: NSMakeRect(0, 0, 100, 30)) + init(controller: ViewController) { + super.init(controller: controller) + addSubview(searchView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + searchView.updateLocalizationAndTheme(theme: theme) + } + + override func layout() { + super.layout() + searchView.setFrameSize(NSMakeSize(frame.width, 30)) + searchView.center() + } + +} + + +fileprivate final class AccountInfoArguments { + let context: AccountContext + let presentController:(ViewController, Bool) -> Void + let openFaq:()->Void + let ask:()->Void + let openUpdateApp:() -> Void + init(context: AccountContext, presentController:@escaping(ViewController, Bool)->Void, openFaq: @escaping()->Void, ask:@escaping()->Void, openUpdateApp: @escaping() -> Void) { + self.context = context + self.presentController = presentController + self.openFaq = openFaq + self.ask = ask + self.openUpdateApp = openUpdateApp + } +} + class AccountViewController: NavigationViewController { private var layoutController:LayoutAccountController - init(_ account:Account, accountManager: AccountManager) { - self.layoutController = LayoutAccountController(account, accountManager: accountManager) - super.init(layoutController) - layoutController.navigationController = self + private let disposable = MetaDisposable() + init(_ context: AccountContext) { + self.layoutController = LayoutAccountController(context) + super.init(layoutController, context.window) + self.ready.set(layoutController.ready.get()) + disposable.set(context.hasPassportSettings.get().start(next: { [weak self] value in + self?.layoutController.passportPromise.set(.single(value)) + })) + self.applyAppearOnLoad = false + } + + override func viewDidLoad() { + super.viewDidLoad() + (self.view as? View)?.border = [.Right] + } + + deinit { + disposable.dispose() + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + layoutController._frameRect = bounds + layoutController.frame = NSMakeRect(0, layoutController.bar.height, bounds.width, bounds.height - layoutController.bar.height) } override func viewWillAppear(_ animated: Bool) { @@ -26,6 +91,10 @@ class AccountViewController: NavigationViewController { layoutController.viewWillAppear(animated) } + override func scrollup(force: Bool = false) { + layoutController.scrollup() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) layoutController.viewDidAppear(animated) @@ -40,104 +109,126 @@ class AccountViewController: NavigationViewController { } } +private enum AccountInfoEntryId : Hashable { + case index(Int) + case account(AccountWithInfo) + + var hashValue: Int { + return 0 + } +} - -enum AccountInfoEntry : Comparable, Identifiable { - case info(index:Int, AccountInfoItemState, TelegramUser, ConnectionStatus) - case updatePhoto(index: Int) +private enum AccountInfoEntry : TableItemListNodeEntry { + case info(index:Int, TelegramUser) + case accountRecord(index: Int, info: AccountWithInfo) + case addAccount(index: Int) + case proxy(index: Int, status: String?) case general(index: Int) case stickers(index: Int) case notifications(index: Int) - case username(index: Int, username: String) - case bio(index: Int, about: String) case language(index: Int, current: String) case appearance(index: Int) - case phone(index: Int, phone: String) - case privacy(index: Int) + case privacy(index: Int, AccountPrivacySettings?, ([WebAuthorization], [PeerId : Peer])?) case dataAndStorage(index: Int) - case accounts(index: Int) + case activeSessions(index: Int, activeSessions: Int) + case passport(index: Int, peer: Peer) + case wallet(index: Int) + case update(index: Int, state: Any) + case filters(index: Int) + case readArticles(index: Int) case about(index: Int) case faq(index: Int) case ask(index: Int) - case logout(index: Int) case whiteSpace(index:Int, height:CGFloat) - var stableId: Int { + var stableId: AccountInfoEntryId { switch self { case .info: - return 0 - case .updatePhoto: - return 1 - case .username: - return 2 - case .bio: - return 3 - case .phone: - return 4 + return .index(0) + case let .accountRecord(_, info): + return .account(info) + case .addAccount: + return .index(1) case .general: - return 5 + return .index(2) + case .proxy: + return .index(3) case .notifications: - return 6 + return .index(4) case .dataAndStorage: - return 7 + return .index(5) + case .activeSessions: + return .index(6) case .privacy: - return 8 + return .index(7) case .language: - return 9 + return .index(8) case .stickers: - return 10 + return .index(9) + case .filters: + return .index(10) + case .update: + return .index(11) case .appearance: - return 11 - case .accounts: - return 12 + return .index(12) + case .passport: + return .index(13) + case .wallet: + return .index(14) + case .readArticles: + return .index(15) case .about: - return 13 + return .index(16) case .faq: - return 14 + return .index(17) case .ask: - return 15 - case .logout: - return 16 + return .index(18) case let .whiteSpace(index, _): - return 1000 + index + return .index(1000 + index) } } var index:Int { switch self { - case let .info(index, _, _, _): + case let .info(index, _): + return index + case let .accountRecord(index, _): return index - case let .updatePhoto(index): + case let .addAccount(index): return index case let .general(index): return index + case let .proxy(index, _): + return index case let .stickers(index): return index case let .notifications(index): return index - case let .username(index, _): - return index - case let .bio(index, _): - return index case let .language(index, _): return index case let .appearance(index): return index - case let .phone(index, _): - return index - case let .privacy(index): + case let .privacy(index, _, _): return index case let .dataAndStorage(index): return index - case let .accounts(index): + case let .activeSessions(index, _): return index case let .about(index): return index + case let .passport(index, _): + return index + case let .filters(index): + return index + case let .wallet(index): + return index + case let .readArticles(index): + return index case let .faq(index): return index case let .ask(index): return index - case let .logout(index): + case let .update(index, _): return index case let .whiteSpace(index, _): return index @@ -146,14 +237,20 @@ enum AccountInfoEntry : Comparable, Identifiable { static func ==(lhs:AccountInfoEntry, rhs:AccountInfoEntry) -> Bool { switch lhs { - case let .info(lhsIndex, lhsState, lhsPeer, lhsConnectionState): - if case let .info(rhsIndex, rhsState, rhsPeer, rhsConnectionState) = rhs { - return lhsIndex == rhsIndex && lhsState == rhsState && lhsIndex == rhsIndex && lhsConnectionState == rhsConnectionState && lhsPeer.isEqual(rhsPeer) + case let .info(lhsIndex, lhsPeer): + if case let .info(rhsIndex, rhsPeer) = rhs { + return lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) + } else { + return false + } + case let .accountRecord(lhsIndex, lhsInfo): + if case let .accountRecord(rhsIndex, rhsInfo) = rhs { + return lhsIndex == rhsIndex && lhsInfo == rhsInfo } else { return false } - case let .updatePhoto(lhsIndex): - if case let .updatePhoto(rhsIndex) = rhs { + case let .addAccount(lhsIndex): + if case let .addAccount(rhsIndex) = rhs { return lhsIndex == rhsIndex } else { return false @@ -170,21 +267,15 @@ enum AccountInfoEntry : Comparable, Identifiable { } else { return false } - case let .notifications(lhsIndex): - if case let .notifications(rhsIndex) = rhs { - return lhsIndex == rhsIndex + case let .proxy(lhsIndex, lhsStatus): + if case let .proxy(rhsIndex, rhsStatus) = rhs { + return lhsIndex == rhsIndex && lhsStatus == rhsStatus } else { return false } - case let .username(lhsIndex, lhsUsername): - if case let .username(rhsIndex, rhsUsername) = rhs { - return lhsIndex == rhsIndex && lhsUsername == rhsUsername - } else { - return false - } - case let .bio(lhsIndex, lhsAbout): - if case let .bio(rhsIndex, rhsAbout) = rhs { - return lhsIndex == rhsIndex && lhsAbout == rhsAbout + case let .notifications(lhsIndex): + if case let .notifications(rhsIndex) = rhs { + return lhsIndex == rhsIndex } else { return false } @@ -200,15 +291,16 @@ enum AccountInfoEntry : Comparable, Identifiable { } else { return false } - case let .phone(lhsIndex, lhsPhone): - if case let .phone(rhsIndex, rhsPhone) = rhs { - return lhsIndex == rhsIndex && lhsPhone == rhsPhone - } else { - return false - } - case let .privacy(lhsIndex): - if case let .privacy(rhsIndex) = rhs { - return lhsIndex == rhsIndex + case let .privacy(lhsIndex, lhsPrivacy, lhsWebSessions): + if case let .privacy(rhsIndex, rhsPrivacy, rhsWebSessions) = rhs { + if let lhsWebSessions = lhsWebSessions, let rhsWebSessions = rhsWebSessions { + if lhsWebSessions.0 != rhsWebSessions.0 { + return false + } + } else if (lhsWebSessions != nil) != (rhsWebSessions != nil) { + return false + } + return lhsIndex == rhsIndex && lhsPrivacy == rhsPrivacy } else { return false } @@ -218,9 +310,9 @@ enum AccountInfoEntry : Comparable, Identifiable { } else { return false } - case let .accounts(lhsIndex): - if case let .accounts(rhsIndex) = rhs { - return lhsIndex == rhsIndex + case let .activeSessions(index, activeSessions): + if case .activeSessions(index, activeSessions) = rhs { + return true } else { return false } @@ -230,6 +322,24 @@ enum AccountInfoEntry : Comparable, Identifiable { } else { return false } + case let .passport(lhsIndex, lhsPeer): + if case let .passport(rhsIndex, rhsPeer) = rhs { + return lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) + } else { + return false + } + case let .wallet(index): + if case .wallet(index) = rhs { + return true + } else { + return false + } + case let .readArticles(lhsIndex): + if case let .readArticles(rhsIndex) = rhs { + return lhsIndex == rhsIndex + } else { + return false + } case let .faq(lhsIndex): if case let .faq(rhsIndex) = rhs { return lhsIndex == rhsIndex @@ -242,8 +352,21 @@ enum AccountInfoEntry : Comparable, Identifiable { } else { return false } - case let .logout(lhsIndex): - if case let .logout(rhsIndex) = rhs { + case let .filters(lhsIndex): + if case let .filters(rhsIndex) = rhs { + return lhsIndex == rhsIndex + } else { + return false + } + case let .update(lhsIndex, lhsState): + if case let .update(rhsIndex, rhsState) = rhs { + #if !APP_STORE + let lhsState = lhsState as? AppUpdateState + let rhsState = rhsState as? AppUpdateState + if lhsState != rhsState { + return false + } + #endif return lhsIndex == rhsIndex } else { return false @@ -261,152 +384,534 @@ enum AccountInfoEntry : Comparable, Identifiable { return lhs.index < rhs.index } -} - + func item(_ arguments: AccountInfoArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .info(_, peer): + return AccountInfoItem(initialSize, stableId: stableId, context: arguments.context, peer: peer, action: { + let first: Atomic = Atomic(value: true) + EditAccountInfoController(context: arguments.context, f: { controller in + arguments.presentController(controller, first.swap(false)) + }) + }) + case let .accountRecord(_, info): + return ShortPeerRowItem(initialSize, peer: info.peer, account: info.account, height: 42, photoSize: NSMakeSize(28, 28), titleStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.text, highlightColor: theme.colors.underSelectedColor), borderType: [.Right], inset: NSEdgeInsets(left:16), action: { + arguments.context.sharedContext.switchToAccount(id: info.account.id, action: .settings) + }, contextMenuItems: { + return .single([ContextMenuItem(L10n.accountSettingsDeleteAccount, handler: { + confirm(for: arguments.context.window, information: L10n.accountConfirmLogoutText, successHandler: { _ in + _ = logoutFromAccount(id: info.account.id, accountManager: arguments.context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).start() + }) + })]) + }, alwaysHighlight: true, badgeNode: GlobalBadgeNode(info.account, sharedContext: arguments.context.sharedContext, getColor: { _ in theme.colors.accent }), compactText: true) + case .addAccount: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsAddAccount, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.accentIcon), type: .none, action: { + let testingEnvironment = NSApp.currentEvent?.modifierFlags.contains(.command) == true + arguments.context.sharedContext.beginNewAuth(testingEnvironment: testingEnvironment) + + }, thumb: GeneralThumbAdditional(thumb: theme.icons.peerInfoAddMember, textInset: 35, thumbInset: 0), border:[BorderType.Right], inset:NSEdgeInsets(left:15)) + case .general: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsGeneral, icon: theme.icons.settingsGeneral, activeIcon: theme.icons.settingsGeneralActive, type: .next, action: { + arguments.presentController(GeneralSettingsViewController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .proxy(_, let status): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsProxy, icon: theme.icons.settingsProxy, activeIcon: theme.icons.settingsProxyActive, type: .nextContext(status ?? ""), action: { + let controller = proxyListController(accountManager: arguments.context.sharedContext.accountManager, network: arguments.context.account.network, share: { servers in + var message: String = "" + for server in servers { + message += server.link + "\n\n" + } + message = message.trimmed + + showModal(with: ShareModalController(ShareLinkObject(arguments.context, link: message)), for: mainWindow) + }, pushController: { controller in + arguments.presentController(controller, false) + }) + arguments.presentController(controller, true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .stickers: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsStickers, icon: theme.icons.settingsStickers, activeIcon: theme.icons.settingsStickersActive, type: .next, action: { + arguments.presentController(InstalledStickerPacksController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .notifications: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsNotifications, icon: theme.icons.settingsNotifications, activeIcon: theme.icons.settingsNotificationsActive, type: .next, action: { + arguments.presentController(NotificationPreferencesController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case let .language(_, current): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsLanguage, icon: theme.icons.settingsLanguage, activeIcon: theme.icons.settingsLanguageActive, type: .nextContext(current), action: { + arguments.presentController(LanguageViewController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .appearance: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsTheme, icon: theme.icons.settingsAppearance, activeIcon: theme.icons.settingsAppearanceActive, type: .next, action: { + arguments.presentController(AppAppearanceViewController(context: arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case let .privacy(_, privacySettings, webSessions): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsPrivacyAndSecurity, icon: theme.icons.settingsSecurity, activeIcon: theme.icons.settingsSecurityActive, type: .next, action: { + arguments.presentController(PrivacyAndSecurityViewController(arguments.context, initialSettings: (privacySettings, webSessions)), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .dataAndStorage: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsDataAndStorage, icon: theme.icons.settingsStorage, activeIcon: theme.icons.settingsStorageActive, type: .next, action: { + arguments.presentController(DataAndStorageViewController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case let .activeSessions(_, count): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsActiveSessions, icon: theme.icons.settingsSessions, activeIcon: theme.icons.settingsSessionsActive, type: .nextContext("\(count)"), action: { + arguments.presentController(RecentSessionsController(arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .about: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsAbout, icon: theme.icons.settingsFaq, activeIcon: theme.icons.settingsFaqActive, type: .next, action: { + showModal(with: AboutModalController(), for: mainWindow) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case let .passport(_, peer): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsPassport, icon: theme.icons.settingsPassport, activeIcon: theme.icons.settingsPassportActive, type: .next, action: { + arguments.presentController(PassportController(arguments.context, peer, request: nil, nil), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .wallet: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsWallet, icon: theme.icons.settingsWallet, activeIcon: theme.icons.settingsWalletActive, type: .next, action: { + let context = arguments.context + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .faq: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsFAQ, icon: theme.icons.settingsFaq, activeIcon: theme.icons.settingsFaqActive, type: .next, action: { + + arguments.openFaq() + + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .readArticles: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsReadArticles, icon: theme.icons.settingsFaq, activeIcon: theme.icons.settingsFaqActive, type: .next, action: { + + + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .ask: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsAskQuestion, icon: theme.icons.settingsAskQuestion, activeIcon: theme.icons.settingsAskQuestionActive, type: .next, action: { + confirm(for: mainWindow, information: L10n.accountConfirmAskQuestion, thridTitle: L10n.accountConfirmGoToFaq, successHandler: { result in + switch result { + case .basic: + _ = showModalProgress(signal: supportPeerId(account: arguments.context.account), for: mainWindow).start(next: { peerId in + if let peerId = peerId { + arguments.presentController(ChatController(context: arguments.context, chatLocation: .peer(peerId)), true) + } + }) + case .thrid: + arguments.openFaq() + } + }) + + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .filters: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountSettingsFilters, icon: theme.icons.settingsFilters, activeIcon: theme.icons.settingsFiltersActive, type: .next, action: { + arguments.presentController(ChatListFiltersListController(context: arguments.context), true) + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case .update(_, let state): + + var text: String = "" + #if !APP_STORE + if let state = state as? AppUpdateState { + switch state.loadingState { + case let .loading(_, current, total): + text = "\(Int(Float(current) / Float(total) * 100))%" + case let .readyToInstall(item), let .unarchiving(item): + text = "\(item.displayVersionString!).\(item.versionString!)" + case .uptodate: + text = "" //L10n.accountViewControllerDescUpdated + case .failed: + text = L10n.accountViewControllerDescFailed + default: + text = "" + } + } + #endif + + + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.accountViewControllerUpdate, icon: theme.icons.settingsUpdate, activeIcon: theme.icons.settingsUpdateActive, type: .nextContext(text), action: { + arguments.openUpdateApp() + }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) + case let .whiteSpace(_, height): + return GeneralRowItem(initialSize, height: height, stableId: stableId, border:[BorderType.Right]) + } + } + +} -class LayoutAccountController : EditableViewController, TableViewDelegate { +private func accountInfoEntries(peerView:PeerView, accounts: [AccountWithInfo], language: TelegramLocalization, privacySettings: AccountPrivacySettings?, webSessions: ([WebAuthorization], [PeerId : Peer])?, proxySettings: (ProxySettings, ConnectionStatus), passportVisible: Bool, appUpdateState: Any?, hasWallet: Bool, hasFilters: Bool, sessionsCount: Int) -> [AccountInfoEntry] { + var entries:[AccountInfoEntry] = [] - func selectionDidChange(row: Int, item: TableRowItem, byClick: Bool, isNew: Bool) { + var index:Int = 0 + if let peer = peerViewMainPeer(peerView) as? TelegramUser { + entries.append(.info(index: index, peer)) + index += 1 } - func selectionWillChange(row: Int, item: TableRowItem) -> Bool { - return true + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 + + for account in accounts { + if account.peer.id != peerView.peerId { + entries.append(.accountRecord(index: index, info: account)) + index += 1 + } + } + if accounts.count < 3 { + entries.append(.addAccount(index: index)) + index += 1 } + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 - func isSelectable(row: Int, item: TableRowItem) -> Bool { - return row > 0 + if !proxySettings.0.servers.isEmpty { + let status: String + switch proxySettings.1 { + case .online: + status = proxySettings.0.enabled ? L10n.accountSettingsProxyConnected : L10n.accountSettingsProxyDisabled + default: + status = proxySettings.0.enabled ? L10n.accountSettingsProxyConnecting : L10n.accountSettingsProxyDisabled + } + entries.append(.proxy(index: index, status: status)) + index += 1 + + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 + } + + entries.append(.general(index: index)) + index += 1 + entries.append(.notifications(index: index)) + index += 1 + entries.append(.privacy(index: index, privacySettings, webSessions)) + index += 1 + entries.append(.dataAndStorage(index: index)) + index += 1 + entries.append(.activeSessions(index: index, activeSessions: sessionsCount)) + index += 1 + entries.append(.appearance(index: index)) + index += 1 + entries.append(.language(index: index, current: language.localizedName)) + index += 1 + entries.append(.stickers(index: index)) + index += 1 + + if hasFilters { + entries.append(.filters(index: index)) + index += 1 + } + + if let state = appUpdateState { + entries.append(.update(index: index, state: state)) + index += 1 + } + + + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 + + if let peer = peerViewMainPeer(peerView) as? TelegramUser, passportVisible { + entries.append(.passport(index: index, peer: peer)) + index += 1 } - private let accountManager:AccountManager - private let peer = Promise() - private let statePromise:ValuePromise = ValuePromise(ignoreRepeated: true) - private let connectionPromise = Promise(.online) - private let entries:Atomic<[AppearanceWrapperEntry]?> = Atomic(value: nil) + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 + entries.append(.faq(index: index)) + index += 1 + entries.append(.ask(index: index)) + index += 1 + + entries.append(.whiteSpace(index: index, height: 20)) + index += 1 + + return entries +} + +private func prepareEntries(left: [AppearanceWrapperEntry], right: [AppearanceWrapperEntry], arguments: AccountInfoArguments, initialSize: NSSize) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + + +class LayoutAccountController : TableViewController { private let disposable = MetaDisposable() - private let updatePhotoDisposable = MetaDisposable() + private var searchController: InputDataController? + private let searchState: ValuePromise = ValuePromise(ignoreRepeated: true) var navigation:NavigationViewController? { - return super.navigationController?.navigationController + return context.sharedContext.bindings.rootNavigation() } - override func viewDidLoad() { - super.viewDidLoad() - genericView.border = [.Right] - genericView.delegate = self - self.rightBarView.border = [.Right] - readyOnce() + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + self.searchController?.view.frame = bounds + } + + override func getCenterBarViewOnce() -> TitledBarView { + let searchBar = AccountSearchBarView(controller: self) + + searchBar.searchView.searchInteractions = SearchInteractions({ [weak self] state, animated in + guard let `self` = self else {return} + self.searchState.set(state) + switch state.state { + case .Focus: + self.showSearchController(animated: animated) + case .None: + self.hideSearchController(animated: animated) + } + + }, { [weak self] state in + self?.searchState.set(state) + }) + return searchBar + } + + private func showSearchController(animated: Bool) { + if searchController == nil { + let rect = genericView.bounds + let searchController = SearchSettingsController(context: context, searchQuery: self.searchState.get(), archivedStickerPacks: .single(nil), privacySettings: self.settings.get() |> map { $0.0 }) + searchController.bar = .init(height: 0) + searchController._frameRect = rect + searchController.tableView.border = [.Right] + self.searchController = searchController + searchController.navigationController = self.navigationController + searchController.viewWillAppear(true) + if animated { + searchController.view.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion:{ [weak self] complete in + if complete { + self?.searchController?.viewDidAppear(animated) + } + }) + } else { + searchController.viewDidAppear(animated) + } + + self.addSubview(searchController.view) + } } - private var editButton:ImageButton? = nil - private var doneButton:TitleButton? = nil + + + private func hideSearchController(animated: Bool) { + if let searchController = self.searchController { + searchController.viewWillDisappear(animated) + searchController.view.layer?.opacity = animated ? 1.0 : 0.0 + searchController.viewDidDisappear(true) + self.searchController = nil + let view = searchController.view + + searchController.view._change(opacity: 0, animated: animated, duration: 0.25, timingFunction: CAMediaTimingFunctionName.spring, completion: { [weak view] completed in + view?.removeFromSuperview() + }) + } + } + + override func escapeKeyAction() -> KeyHandlerResult { + guard context.sharedContext.layout != .minimisize else { + return .invoked + } + let searchView = (self.centerBarView as? AccountSearchBarView)?.searchView + if let searchView = searchView { + if searchView.state == .None { + return searchView.changeResponder() ? .invoked : .rejected + } else if searchView.state == .Focus && searchView.query.length > 0 { + searchView.change(state: .None, true) + return .invoked + } + } + return .rejected + } + + override func getRightBarViewOnce() -> BarView { + let button = TextButtonBarView(controller: self, text: L10n.navigationEdit, style: navigationButtonStyle, alignment:.Right) + let context = self.context + button.set(handler: { [weak self] _ in + guard let `self` = self else {return} + let first: Atomic = Atomic(value: true) + EditAccountInfoController(context: context, f: { [weak self] controller in + self?.arguments?.presentController(controller, first.swap(false)) + }) + }, for: .Click) + return button + } override func requestUpdateRightBar() { super.requestUpdateRightBar() - editButton?.style = navigationButtonStyle - editButton?.set(image: theme.icons.chatActions, for: .Normal) - editButton?.set(image: theme.icons.chatActionsActive, for: .Highlight) - - editButton?.setFrameSize(68, 50) - editButton?.center() - doneButton?.set(color: theme.colors.blueUI, for: .Normal) - doneButton?.style = navigationButtonStyle + (rightBarView as? TextButtonBarView)?.set(text: L10n.navigationEdit, for: .Normal) + (rightBarView as? TextButtonBarView)?.set(color: theme.colors.accent, for: .Normal) + (rightBarView as? TextButtonBarView)?.needsLayout = true } - override func getRightBarViewOnce() -> BarView { - let back = BarView(70, controller: self) - back.border = [.Right] - let editButton = ImageButton() - editButton.disableActions() - back.addSubview(editButton) + override func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { + return item is GeneralInteractedRowItem || item is AccountInfoItem || item is ShortPeerRowItem + } + + override func isSelectable(row: Int, item: TableRowItem) -> Bool { + return true + } + + private let settings: Promise<(AccountPrivacySettings?, ([WebAuthorization], [PeerId : Peer])?, (ProxySettings, ConnectionStatus), Bool)> = Promise() + private let syncLocalizations = MetaDisposable() + fileprivate let passportPromise: Promise = Promise(false) + fileprivate let hasWallet: Promise = Promise(false) + fileprivate let hasFilters: Promise = Promise(false) + + private weak var arguments: AccountInfoArguments? + override func viewDidLoad() { + super.viewDidLoad() + genericView.border = [.Right] + genericView.delegate = self + // self.rightBarView.border = [.Right] + let context = self.context + genericView.getBackgroundColor = { + return .clear + } - self.editButton = editButton - let doneButton = TitleButton() - doneButton.disableActions() - doneButton.set(font: .medium(.text), for: .Normal) - doneButton.set(text: tr(.navigationDone), for: .Normal) - doneButton.sizeToFit() - back.addSubview(doneButton) - doneButton.center() + settings.set(combineLatest(Signal.single(nil) |> then(requestAccountPrivacySettings(account: context.account) |> map {Optional($0)}), Signal<([WebAuthorization], [PeerId : Peer])?, NoError>.single(nil) |> then(webSessions(network: context.account.network) |> map {Optional($0)}), proxySettings(accountManager: context.sharedContext.accountManager) |> mapToSignal { settings in + return context.account.network.connectionStatus |> map {(settings, $0)} + }, passportPromise.get())) - self.doneButton = doneButton + syncLocalizations.set(synchronizedLocalizationListState(postbox: context.account.postbox, network: context.account.network).start()) - doneButton.isHidden = true + self.hasWallet.set(context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + |> map { view -> Bool in + let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue + let configuration = WalletConfiguration.with(appConfiguration: appConfiguration) + if #available(OSX 10.12, *) { + return configuration.config != nil + } else { + return false + } + }) + self.hasFilters.set(context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + |> map { view -> Bool in + let configuration = ChatListFilteringConfiguration(appConfiguration: view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue) + return configuration.isEnabled + }) - editButton.set(handler: { [weak self] control in - - if !hasPopover(mainWindow), let strongSelf = self { - var items: [SPopoverItem] = [] - items.append(SPopoverItem(tr(.accountSettingsAbout), { - showModal(with: AboutModalController(), for: mainWindow) - }, theme.icons.settingsAbout)) - items.append(SPopoverItem(tr(.accountSettingsLogout), { [weak strongSelf] in - confirm(for: mainWindow, with: tr(.accountConfirmLogout), and: tr(.accountConfirmLogoutText), successHandler: {_ in - if let strongSelf = strongSelf { - let _ = logoutFromAccount(id: strongSelf.account.id, accountManager: strongSelf.accountManager).start() - } - }) - - }, theme.icons.settingsLogout, theme.colors.redUI)) - showPopover(for: control, with: SPopoverViewController(items: items), edge: .maxY, inset: NSMakePoint(-60, -50)) + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + + let arguments = AccountInfoArguments(context: context, presentController: { [weak self] controller, main in + guard let navigation = self?.navigation as? MajorNavigationController else {return} + guard let singleLayout = self?.context.sharedContext.layout else {return} + var main = main + if let controller = navigation.controller as? InputDataController, controller.identifier == "wallet-create" { + main = false } - + if main { + navigation.removeExceptMajor() + } + navigation.push(controller, !main || singleLayout == .single) + }, openFaq: { + openFaq(context: context) + }, ask: { + + }, openUpdateApp: { [weak self] in + guard let navigation = self?.navigation as? MajorNavigationController else {return} + #if !APP_STORE + navigation.push(AppUpdateViewController(), false) + #endif + }) + + self.arguments = arguments + + let atomicSize = self.atomicSize + - }, for: .Hover) + let appUpdateState: Signal + #if APP_STORE + appUpdateState = .single(nil) + #else + appUpdateState = appUpdateStateSignal |> map(Optional.init) + #endif - doneButton.set(handler: { [weak self] _ in - self?.changeState() - }, for: .Click) - requestUpdateRightBar() - return back + let sessionsCount = context.activeSessionsContext.state |> map { + $0.sessions.count + } + + let apply = combineLatest(queue: prepareQueue, context.account.viewTracker.peerView(context.account.peerId), context.sharedContext.activeAccountsWithInfo, appearanceSignal, settings.get(), appUpdateState, hasWallet.get(), hasFilters.get(), sessionsCount) |> map { peerView, accounts, appearance, settings, appUpdateState, hasWallet, hasFilters, sessionsCount -> TableUpdateTransition in + let entries = accountInfoEntries(peerView: peerView, accounts: accounts.accounts, language: appearance.language, privacySettings: settings.0, webSessions: settings.1, proxySettings: settings.2, passportVisible: settings.3, appUpdateState: appUpdateState, hasWallet: hasWallet, hasFilters: hasFilters, sessionsCount: sessionsCount).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + var size = atomicSize.modify {$0} + size.width = max(size.width, 280) + return prepareEntries(left: previous.swap(entries), right: entries, arguments: arguments, initialSize: size) + } |> deliverOnMainQueue + + disposable.set(apply.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) } - + + + override func navigationWillChangeController() { if let navigation = navigation as? ExMajorNavigationController { - if navigation.controller is StorageUsageController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.dataAndStorage(index: 0).stableId)) { - _ = genericView.select(item: item) - } - } else if navigation.controller is NotificationSettingsViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.notifications(index: 0).stableId)) { + if navigation.controller is DataAndStorageViewController { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(5))) { _ = genericView.select(item: item) } } else if navigation.controller is PrivacyAndSecurityViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.privacy(index: 0).stableId)) { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(7))) { _ = genericView.select(item: item) } } else if navigation.controller is LanguageViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.language(index: 0, current: "").stableId)) { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(8))) { _ = genericView.select(item: item) } } else if navigation.controller is InstalledStickerPacksController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.stickers(index: 0).stableId)) { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(9))) { _ = genericView.select(item: item) } + } else if navigation.controller is GeneralSettingsViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.general(index: 0).stableId)) { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(2))) { _ = genericView.select(item: item) } - } else if navigation.controller is UsernameSettingsViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.username(index: 0, username: "").stableId)) { + } else if navigation.controller is RecentSessionsController { + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(6))) { _ = genericView.select(item: item) } - } else if navigation.controller is BioViewController { - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.bio(index: 0, about: "").stableId)) { + } else if navigation.controller is PassportController { + if let item = genericView.item(stableId: AccountInfoEntryId.index(Int(13))) { _ = genericView.select(item: item) } - } else if PhoneNumberIntroController.assciatedControllerTypes.contains(where: {navigation.controller.isKind(of: $0)}) { - - if let item = genericView.item(stableId: AnyHashable(AccountInfoEntry.phone(index: 0, phone: "").stableId)) { - _ = genericView.select(item: item) + } else if let controller = navigation.controller as? InputDataController { + switch true { + case controller.identifier == "proxy": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(3))) { + _ = genericView.select(item: item) + } + case controller.identifier == "account": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(0))) { + _ = genericView.select(item: item) + } + case controller.identifier == "passport": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(13))) { + _ = genericView.select(item: item) + } + case controller.identifier == "app_update": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(11))) { + _ = genericView.select(item: item) + } + case controller.identifier == "filters": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(10))) { + _ = genericView.select(item: item) + } + case controller.identifier == "notification-settings": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(4))) { + _ = genericView.select(item: item) + } + case controller.identifier == "app_appearance": + if let item = genericView.item(stableId: AnyHashable(AccountInfoEntryId.index(12))) { + _ = genericView.select(item: item) + } + default: + genericView.cancelSelection() } + } else { genericView.cancelSelection() } @@ -416,313 +921,82 @@ class LayoutAccountController : EditableViewController, TableViewDele override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) (navigation as? MajorNavigationController)?.add(listener: WeakReference(value: self)) - updateLocalizationAndTheme() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - let apply = combineLatest(account.viewTracker.peerView( account.peerId), connectionPromise.get(), statePromise.get(), appearanceSignal) |> deliverOn(Queue.mainQueue()) |> map { [weak self] peerView, connection, state, appearance -> TableUpdateTransition in - - if let strongSelf = self { - let entries = strongSelf.entries(for: state, account: strongSelf.account, connection: connection, peerView: peerView, language: appearance.language).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} - let previous = strongSelf.entries.swap(entries) - return strongSelf.prepareEntries(left: previous, right: entries, account: strongSelf.account, accountManager: strongSelf.accountManager, animated: true, atomicSize: strongSelf.atomicSize.modify({$0})) - } - return TableUpdateTransition(deleted: [], inserted: [], updated: []) - - } - disposable.set(apply.start(next: { [weak self] transition in - - self?.genericView.merge(with: transition) - self?.navigationWillChangeController() - })) - - peer.set(account.viewTracker.peerView(account.peerId) |> map { peerView -> TelegramUser? in - return peerView.peers[peerView.peerId] as? TelegramUser + passportPromise.set(twoStepAuthData(context.account.network) |> map { value in + return value.hasSecretValues + } |> `catch` { error -> Signal in + return .single(false) }) - connectionPromise.set(account.network.connectionStatus) - statePromise.set(.Normal) + updateLocalizationAndTheme(theme: theme) + + + context.window.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.escapeKeyAction() + } + return .invokeNext + }, with: self, for: .Escape, priority:.low) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - disposable.set(nil) - } - - - override func update(with state: ViewControllerState) { - if state == .Normal { - saveNamesIfNeeded(false) - } - super.update(with: state) - statePromise.set(state) - - editButton?.isHidden = state == .Edit - doneButton?.isHidden = state == .Normal - - switch state { - case .Normal: - window?.removeObserver(for: self) - case .Edit: - window?.set(responder: { [weak self] () -> NSResponder? in - if let view = self?.genericView.viewNecessary(at: 0) as? AccountInfoView { - return view.firstResponder - } - return nil - }, with: self, priority: .high) - default: - break - } - } - - override func getLeftBarViewOnce() -> BarView { - return BarView(controller: self) - } - - init(_ account:Account, accountManager:AccountManager) { - self.accountManager = accountManager - super.init(account) + window?.removeAllHandlers(for: self) } - func entries(for state:ViewControllerState, account:Account, connection:ConnectionStatus, peerView:PeerView, language: Language) -> [AccountInfoEntry] { - var entries:[AccountInfoEntry] = [] - - var index:Int = 0 - - let user = peerViewMainPeer(peerView) as? TelegramUser - - if let peer = peerViewMainPeer(peerView) as? TelegramUser { - entries.append(.info(index: index, state == .Edit ? .edit : .normal, peer, connection)) - index += 1 - } - - - - entries.append(.username(index: index, username: peerViewMainPeer(peerView)?.addressName ?? "")) - index += 1 + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + let context = self.context - let cachedData = peerView.cachedData as? CachedUserData - entries.append(.bio(index: index, about: cachedData?.about ?? "")) - index += 1 - entries.append(.phone(index: index, phone: user?.phone ?? "")) - index += 1 - entries.append(.whiteSpace(index: index, height: 30)) - index += 1 - entries.append(.general(index: index)) - index += 1 - entries.append(.notifications(index: index)) - index += 1 - entries.append(.dataAndStorage(index: index)) - index += 1 - entries.append(.privacy(index: index)) - index += 1 - entries.append(.language(index: index, current: tr(.accountSettingsCurrentLanguage))) - index += 1 - - entries.append(.stickers(index: index)) - index += 1 - - + settings.set(combineLatest(Signal.single(nil) |> then(requestAccountPrivacySettings(account: context.account) |> map {Optional($0)}), Signal<([WebAuthorization], [PeerId : Peer])?, NoError>.single(nil) |> then(webSessions(network: context.account.network) |> map {Optional($0)}), proxySettings(accountManager: context.sharedContext.accountManager) |> mapToSignal { settings in + return context.account.network.connectionStatus |> map {(settings, $0)} + }, passportPromise.get())) -// entries.append(.accounts(index: index)) -// index += 1 - entries.append(.whiteSpace(index: index, height: 30)) - index += 1 - // entries.append(.about(index: index)) - // index += 1 - entries.append(.faq(index: index)) - index += 1 - entries.append(.ask(index: index)) - - if state == .Edit { - index += 1 - entries.append(.whiteSpace(index: index, height: 20)) - index += 1 - entries.append(.logout(index: index)) - } + syncLocalizations.set(synchronizedLocalizationListState(postbox: context.account.postbox, network: context.account.network).start()) - return entries } - func saveNamesIfNeeded(_ cState:Bool = true) -> Void { - if let item = genericView.item(at: 0) as? AccountInfoItem { - if item.firstName != (item.peer.firstName ?? "") || item.lastName != (item.peer.lastName ?? "") { - _ = showModalProgress(signal: updateAccountPeerName(account: account, firstName: item.firstName, lastName: item.lastName), for: mainWindow).start() - } - if cState { - changeState() - } - } + override func getLeftBarViewOnce() -> BarView { + return BarView(10, controller: self) } - fileprivate func prepareEntries(left:[AppearanceWrapperEntry]?, right:[AppearanceWrapperEntry], account:Account, accountManager: AccountManager, animated:Bool, atomicSize:NSSize) -> TableUpdateTransition { - let atomicSize = NSMakeSize(max(280, atomicSize.width), atomicSize.height) - let (deleted,inserted,updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in - switch entry.entry { - case let .info(_, state, peer, connection): - return AccountInfoItem(atomicSize, stableId: entry.stableId, account: account, peer: peer, state: state, connectionStatus: connection, saveCallback: { [weak self] in - self?.saveNamesIfNeeded() - }, editCallback: { [weak self] in - self?.changeState() - }, imageCallback: { [weak self] in - if let strongSelf = self { - pickImage(for: mainWindow, completion:{ [weak strongSelf] image in - if let image = image { - strongSelf?.startUpdatePhoto(image, account: account) - } - }) - } - - }) - case .updatePhoto: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsSetProfilePhoto), nameStyle: blueActionButton, type: .none, action: {}, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .general: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsGeneral), icon: theme.icons.settingsGeneral, type: .none, action: {[weak self] in - if !(self?.navigation?.controller is GeneralSettingsViewController) { - self?.navigation?.push(GeneralSettingsViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .stickers: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsStickers), icon: theme.icons.settingsStickers, type: .none, action: { [weak self] in - if !(self?.navigation?.controller is InstalledStickerPacksController) { - self?.navigation?.push(InstalledStickerPacksController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .notifications: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsNotifications), icon: theme.icons.settingsNotifications, type: .none, action: { [weak self] in - if !(self?.navigation?.controller is NotificationSettingsViewController) { - self?.navigation?.push(NotificationSettingsViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case let .username(_, username): - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: username.isEmpty ? tr(.accountSettingsSetUsername) : username, icon: theme.icons.settingsUsername, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: username.isEmpty ? theme.colors.blueUI : theme.colors.text), type: .none, action: { [weak self] in - if !(self?.navigation?.controller is UsernameSettingsViewController) { - self?.navigation?.push(UsernameSettingsViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .bio(_, let about): - - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: about.isEmpty ? tr(.accountSettingsSetBio) : about, icon: theme.icons.settingsBio, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: about.isEmpty ? theme.colors.blueUI : theme.colors.text), type: .none, action: { [weak self] in - if !(self?.navigation?.controller is BioViewController) { - self?.navigation?.push(BioViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case let .phone(_, phone): - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: formatPhoneNumber(phone), icon: theme.icons.settingsPhoneNumber, type: .none, action: { [weak self] in - if !(self?.navigation?.controller is PhoneNumberIntroController) { - self?.navigation?.push(PhoneNumberIntroController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case let .language(_, current): - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsLanguage), icon: theme.icons.settingsLanguage, type: .context(stateback: { - return current - }), action: { [weak self] in - if !(self?.navigation?.controller is LanguageViewController) { - self?.navigation?.push(LanguageViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .appearance: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsAppearance), type: .none, action: { [weak self] in - if !(self?.navigation?.controller is AppearanceViewController) { - self?.navigation?.push(AppearanceViewController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .privacy: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsPrivacyAndSecurity), icon: theme.icons.settingsSecurity, type: .none, action: { [weak self] in - if !(self?.navigation?.controller is PrivacyAndSecurityViewController) { - self?.navigation?.push(PrivacyAndSecurityViewController(account, initialSettings: .single(nil) |> then(requestAccountPrivacySettings(account: account) |> map { Optional($0) }))) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .dataAndStorage: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsStorage), icon: theme.icons.settingsStorage, type: .none, action: { [weak self] in - if !(self?.navigation?.controller is StorageUsageController) { - self?.navigation?.push(StorageUsageController(account)) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .accounts: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: "tr(.accountSettingsAccounts)", type: .none, action: { [weak self] in - self?.navigation?.push(AccountsListViewController(account, accountManager: accountManager)) - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .about: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsAbout), icon: theme.icons.settingsFaq, type: .none, action: { [weak self] in - if let window = self?.window { - showModal(with: AboutModalController(), for: window) - } - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .faq: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsFAQ), icon: theme.icons.settingsFaq, type: .none, action: { - - let language = appCurrentLanguage.languageCode[appCurrentLanguage.languageCode.index(appCurrentLanguage.languageCode.endIndex, offsetBy: -2) ..< appCurrentLanguage.languageCode.endIndex] - - _ = showModalProgress(signal: webpagePreview(account: account, url: "https://telegram.org/faq/" + language) |> deliverOnMainQueue, for: mainWindow).start(next: { webpage in - if let webpage = webpage { - showInstantPage(InstantPageViewController(account, webPage: webpage, message: nil)) - } else { - execute(inapp: .external(link: "https://telegram.org/faq/" + language, true)) - } - }) - - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .ask: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsAskQuestion), icon: theme.icons.settingsAskQuestion, type: .none, action: { [weak self] in - - confirm(for: mainWindow, with: appName, and: tr(.accountConfirmAskQuestion), thridTitle: tr(.accountConfirmGoToFaq), successHandler: { [weak self] result in - switch result { - case .basic: - _ = showModalProgress(signal: supportPeerId(account: account), for: mainWindow).start(next: { [weak self] peerId in - if let peerId = peerId { - self?.navigation?.push(ChatController(account: account, peerId: peerId)) - } - }) - case .thrid: - execute(inapp: .external(link: "https://telegram.org/faq", false)) - } - }) - - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case .logout: - return GeneralInteractedRowItem(atomicSize, stableId: entry.stableId, name: tr(.accountSettingsLogout), nameStyle: redActionButton, type: .none, action: { [weak self] in - - confirm(for: mainWindow, with: tr(.accountConfirmLogout), and: tr(.accountConfirmLogoutText), successHandler: { [weak self] _ in - if let strongSelf = self { - let _ = logoutFromAccount(id: strongSelf.account.id, accountManager: strongSelf.accountManager).start() - } - }) - - - - }, border:[BorderType.Right], inset:NSEdgeInsets(left:16)) - case let .whiteSpace(_, height): - return GeneralRowItem(atomicSize, height: height, stableId: entry.stableId, border:[BorderType.Right]) - } - }) - - return TableUpdateTransition(deleted: deleted, inserted: inserted, updated: updated, animated: animated) + override init(_ context: AccountContext) { + super.init(context) + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + navigationController?.updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - navigationController?.updateLocalizationAndTheme() + override func firstResponder() -> NSResponder? { + return nil } + - func startUpdatePhoto(_ image: NSImage, account:Account) { + override func scrollup(force: Bool = false) { + + if searchController != nil { + let searchView = (self.centerBarView as? AccountSearchBarView)?.searchView + searchView?.cancel(true) + return + } + + if let currentEvent = NSApp.currentEvent, currentEvent.clickCount == 5 { + context.sharedContext.bindings.rootNavigation().push(DeveloperViewController(context: context)) + } - updatePhotoDisposable.set((putToTemp(image: image) |> mapToSignal { path -> Signal in - return updatePeerPhoto(account: account, peerId: account.peerId, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64())) - |> mapError {_ in} |> map {_ in} - }).start()) + genericView.scroll(to: .up(true)) } deinit { + syncLocalizations.dispose() disposable.dispose() - updatePhotoDisposable.dispose() } } diff --git a/Telegram-Mac/AccountsListViewController.swift b/Telegram-Mac/AccountsListViewController.swift deleted file mode 100644 index e632e78a31..0000000000 --- a/Telegram-Mac/AccountsListViewController.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// AccountsListViewController.swift -// Telegram -// -// Created by keepcoder on 20/02/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac - -enum AccountRecordEntryStableId : Hashable { - case record(AccountRecordId) - case newAccount - - static func ==(lhs:AccountRecordEntryStableId, rhs:AccountRecordEntryStableId) -> Bool { - switch lhs { - case let .record(id): - if case .record(id) = rhs { - return true - } else { - return false - } - case .newAccount: - if case .newAccount = rhs { - return true - } else { - return false - } - } - } - - var hashValue: Int { - switch self { - case let .record(id): - return id.hashValue - case .newAccount: - return 1000 - } - } -} - -enum AccountRecordEntry : Identifiable, Comparable { - case record(AccountRecord, Bool, Int) - case newAccount - - var stableId: AccountRecordEntryStableId { - switch self { - case let .record(record, _, _): - return .record(record.id) - case .newAccount: - return .newAccount - } - } - - var index:Int { - switch self { - case let .record(_, _, idx): - return idx - case .newAccount: - return 10000 - } - } -} - -func ==(lhs:AccountRecordEntry, rhs: AccountRecordEntry) -> Bool { - switch lhs { - case let .record(id, isCurrent, idx): - if case .record(id, isCurrent, idx) = rhs { - return true - } else { - return false - } - case .newAccount: - if case .newAccount = rhs { - return true - } else { - return false - } - } -} - -func <(lhs:AccountRecordEntry, rhs: AccountRecordEntry) -> Bool { - return lhs.index < rhs.index -} - - -class AccountsListViewController : GenericViewController { - private let account:Account - private let accountManager:AccountManager - private let statePromise:ValuePromise = ValuePromise(.Normal, ignoreRepeated: true) - init(_ account:Account, accountManager:AccountManager) { - self.account = account - self.accountManager = accountManager - super.init() - } - - override func viewDidLoad() { - super.viewDidLoad() - let entries:Atomic<[AccountRecordEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - self.genericView.merge(with: accountManager.accountRecords() |> mapToSignal { [weak self] records -> Signal in - if let strongSelf = self { - strongSelf.readyOnce() - let converted = strongSelf.entries(from: records) - return strongSelf.prepareEntries(left: entries.swap(converted), right: converted, initialSize: initialSize.modify {$0}) - } else { - return .complete() - } - } |> deliverOnMainQueue) - - - } - - private func entries(from: AccountRecordsView) -> [AccountRecordEntry] { - var entries:[AccountRecordEntry] = [] - entries.append(.newAccount) - - var i:Int = 0 - for record in from.records { - entries.append(.record(record, record == from.currentRecord, i)) - i += 1 - } - return entries - } - - func prepareEntries(left: [AccountRecordEntry], right:[AccountRecordEntry], initialSize:NSSize) -> Signal { - return Signal { subscriber in - - let (removed, inserted, updated) = proccessEntries(left, right: right, { (entry) -> TableRowItem in - - switch entry { - case .newAccount: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.accountsControllerNewAccount), nameStyle: blueActionButton, type: .none, action: { [weak self] in - let _ = self?.accountManager.modify({ modifier -> Void in - let id = modifier.createRecord([]) - modifier.setCurrentId(id) - }).start() - }) - case let .record(record, isCurrent, _): - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: "\(record.id.hashValue)", nameStyle: ControlStyle(font: .normal(.title), foregroundColor: isCurrent ? .grayText : .text), type: .none, action: { [weak self] in - let _ = self?.accountManager.modify({ modifier -> Void in - modifier.setCurrentId(record.id) - }).start() - }) - } - - }) - - subscriber.putNext(TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated)) - subscriber.putCompletion() - - return EmptyDisposable - } - } - - -} diff --git a/Telegram-Mac/AddContactModalController.swift b/Telegram-Mac/AddContactModalController.swift index 580d61206e..27953b052e 100644 --- a/Telegram-Mac/AddContactModalController.swift +++ b/Telegram-Mac/AddContactModalController.swift @@ -8,154 +8,174 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox -private class AddContactControllerView : View, NSTextFieldDelegate { - private let headerView:TextView = TextView() - fileprivate let firstName:NSTextField = NSTextField() - fileprivate let lastName:NSTextField = NSTextField() - fileprivate let phoneNumber:NSTextField = NSTextField() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - let layout = TextViewLayout(.initialize(string: tr(.contactsAddContact), color: theme.colors.text, font: .normal(.title)), maximumNumberOfLines: 1) - layout.measure(width: frameRect.width) - headerView.update(layout) - addSubview(headerView) - addSubview(firstName) - addSubview(lastName) - addSubview(phoneNumber) - - firstName.nextResponder = lastName - firstName.nextKeyView = lastName - - lastName.nextResponder = phoneNumber - lastName.nextKeyView = phoneNumber - - //phoneNumber.nextResponder = firstName - //phoneNumber.nextKeyView = firstName - - firstName.delegate = self - lastName.delegate = self - phoneNumber.delegate = self - - - firstName.isBordered = false - firstName.isBezeled = false - firstName.focusRingType = .none - - lastName.isBordered = false - lastName.isBezeled = false - lastName.focusRingType = .none - - phoneNumber.isBordered = false - phoneNumber.isBezeled = false - phoneNumber.focusRingType = .none - - - firstName.placeholderAttributedString = NSAttributedString.initialize(string: tr(.contactsFirstNamePlaceholder), color: theme.colors.grayText, font: .normal(.custom(13.5))) - lastName.placeholderAttributedString = NSAttributedString.initialize(string: tr(.contactsLastNamePlaceholder), color: theme.colors.grayText, font: .normal(.custom(13.5))) - phoneNumber.placeholderAttributedString = NSAttributedString.initialize(string: tr(.contactsPhoneNumberPlaceholder), color: theme.colors.grayText, font: .normal(.custom(13.5))) - - firstName.setFrameSize(NSMakeSize(frameRect.width - 40, 20)) - lastName.setFrameSize(NSMakeSize(frameRect.width - 40, 20)) - phoneNumber.setFrameSize(NSMakeSize(frameRect.width - 40, 20)) - - updateLocalizationAndTheme() - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - backgroundColor = theme.colors.background - headerView.backgroundColor = theme.colors.background - firstName.backgroundColor = theme.colors.background - lastName.backgroundColor = theme.colors.background - phoneNumber.backgroundColor = theme.colors.background - } +private struct AddContactState : Equatable { + let firstName: String + let lastName: String + let phoneNumber: String + let errors: [InputDataIdentifier : InputDataValueError] - override func controlTextDidChange(_ obj: Notification) { - firstName.stringValue = firstName.stringValue.nsstring.substring(with: NSMakeRange(0, min(firstName.stringValue.length, 20))) - lastName.stringValue = lastName.stringValue.nsstring.substring(with: NSMakeRange(0, min(lastName.stringValue.length, 20))) - phoneNumber.stringValue = formatPhoneNumber(phoneNumber.stringValue) + init(firstName: String, lastName: String, phoneNumber: String, errors: [InputDataIdentifier : InputDataValueError]) { + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + self.errors = errors } - - override func layout() { - super.layout() - headerView.centerX(y: floorToScreenPixels((50 - headerView.frame.height)/2)) - firstName.centerX(y: 50 + 35) - lastName.centerX(y: firstName.frame.maxY + 30) - phoneNumber.centerX(y: lastName.frame.maxY + 30) + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> AddContactState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return AddContactState(firstName: self.firstName, lastName: self.lastName, phoneNumber: self.phoneNumber, errors: errors) } - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(0, 50, frame.width, .borderSize)) - ctx.fill(NSMakeRect(firstName.frame.minX, firstName.frame.maxY + 2, firstName.frame.width, .borderSize)) - ctx.fill(NSMakeRect(lastName.frame.minX, lastName.frame.maxY + 2, lastName.frame.width, .borderSize)) - ctx.fill(NSMakeRect(phoneNumber.frame.minX, phoneNumber.frame.maxY + 2, phoneNumber.frame.width, .borderSize)) + func withUpdatedFirstName(_ firstName: String) -> AddContactState { + return AddContactState(firstName: firstName, lastName: self.lastName, phoneNumber: self.phoneNumber, errors: self.errors) } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + func withUpdatedLastName(_ lastName: String) -> AddContactState { + return AddContactState(firstName: self.firstName, lastName: lastName, phoneNumber: self.phoneNumber, errors: self.errors) + } + func withUpdatedPhoneNumber(_ phoneNumber: String) -> AddContactState { + return AddContactState(firstName: self.firstName, lastName: self.lastName, phoneNumber: phoneNumber, errors: self.errors) } } -class AddContactModalController: ModalViewController { +private let _id_input_first_name = InputDataIdentifier("_id_input_first_name") +private let _id_input_last_name = InputDataIdentifier("_id_input_last_name") +private let _id_input_phone_number = InputDataIdentifier("_id_input_phone_number") - private let account:Account - override func viewDidLoad() { - super.viewDidLoad() - readyOnce() - } +private func addContactEntries(state: AddContactState) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] - override func viewClass() -> AnyClass { - return AddContactControllerView.self - } + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.firstName), error: state.errors[_id_input_first_name], identifier: _id_input_first_name, mode: .plain, data: InputDataRowData(viewType: .firstItem), placeholder: nil, inputPlaceholder: L10n.contactsFirstNamePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.lastName), error: state.errors[_id_input_last_name], identifier: _id_input_last_name, mode: .plain, data: InputDataRowData(viewType: .innerItem), placeholder: nil, inputPlaceholder: L10n.contactsLastNamePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.phoneNumber), error: state.errors[_id_input_phone_number], identifier: _id_input_phone_number, mode: .plain, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.contactsPhoneNumberPlaceholder, filter: { text in + return text.trimmingCharacters(in: CharacterSet(charactersIn: "0987654321+ ").inverted) + }, limit: 30)) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} +func AddContactModalController(_ context: AccountContext) -> InputDataModalController { - override func firstResponder() -> NSResponder? { - if genericView.window?.firstResponder == genericView.firstName.textView || genericView.window?.firstResponder == genericView.lastName.textView || genericView.window?.firstResponder == genericView.phoneNumber.textView { - return genericView.window?.firstResponder - } - return genericView.firstName - } - private var genericView:AddContactControllerView { - return self.view as! AddContactControllerView + let initialState = AddContactState(firstName: "", lastName: "", phoneNumber: "", errors: [:]) + + let statePromise = ValuePromise(initialState, ignoreRepeated: false) + let stateValue = Atomic(value: initialState) + let updateState: ((AddContactState) -> AddContactState) -> Void = { f in + statePromise.set(stateValue.modify (f)) } - init(account: Account) { - self.account = account - super.init(frame: NSMakeRect(0, 0, 300, 240)) - bar = .init(height: 0) + let dataSignal = statePromise.get() |> map { state in + return addContactEntries(state: state) } - func importAndCloseIfPossible() { - if genericView.firstName.stringValue.length == 0 { - genericView.firstName.shake() - } else if genericView.phoneNumber.stringValue.length == 0 { - genericView.phoneNumber.shake() - } else { - close() - _ = (showModalProgress(signal: importContact(account: account, firstName: genericView.firstName.stringValue , lastName: genericView.lastName.stringValue, phoneNumber: genericView.phoneNumber.stringValue), for: mainWindow) |> deliverOnMainQueue).start(next: { [weak self] peerId in - if let peerId = peerId, let account = self?.account { - account.context.mainNavigation?.push(ChatController(account: account, peerId: peerId)) - } else { - alert(for: mainWindow, header: tr(.contactsNotRegistredTitle), info: tr(.contactsNotRegistredDescription)) + var close: (() -> Void)? + + var shouldMakeNextResponderAfterTransition: InputDataIdentifier? = nil + + let controller = InputDataController(dataSignal: dataSignal |> map { InputDataSignalValue(entries: $0) }, title: L10n.contactsAddContact, validateData: { data in + + return .fail(.doSomething { f in + let state = stateValue.with {$0} + + var fields: [InputDataIdentifier : InputDataValidationFailAction] = [:] + + if state.firstName.isEmpty { + fields[_id_input_first_name] = .shake + shouldMakeNextResponderAfterTransition = _id_input_first_name + } + + if state.phoneNumber.isEmpty { + fields[_id_input_phone_number] = .shake + if shouldMakeNextResponderAfterTransition == nil { + shouldMakeNextResponderAfterTransition = _id_input_phone_number + } + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.contactsPhoneNumberInvalid, target: .data), for: _id_input_phone_number) } - }) + } + + if !fields.isEmpty { + f(.fail(.fields(fields))) + } else { + _ = (showModalProgress(signal: importContact(account: context.account, firstName: state.firstName, lastName: state.lastName, phoneNumber: state.phoneNumber), for: mainWindow) |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId))) + close?() + } else { + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.contactsPhoneNumberNotRegistred, target: .data), for: _id_input_phone_number) + } + } + }) + } + + updateState { + $0 + } + }) + + + }, updateDatas: { data in + updateState { state in + return state + .withUpdatedFirstName(data[_id_input_first_name]?.stringValue ?? "") + .withUpdatedLastName(data[_id_input_last_name]?.stringValue ?? "") + .withUpdatedPhoneNumber(formatPhoneNumber(data[_id_input_phone_number]?.stringValue ?? "")) + .withUpdatedError(nil, for: _id_input_first_name) + .withUpdatedError(nil, for: _id_input_last_name) + .withUpdatedError(nil, for: _id_input_phone_number) } - } + + return .none + }, afterDisappear: { + + }, afterTransaction: { controller in + if let identifier = shouldMakeNextResponderAfterTransition { + controller.makeFirstResponderIfPossible(for: identifier) + } + shouldMakeNextResponderAfterTransition = nil + }) + + + let modalInteractions = ModalInteractions(acceptTitle: L10n.modalOK, accept: { [weak controller] in + controller?.validateInputValues() + }, drawBorder: true, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, size: NSMakeSize(300, 300)) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) - override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.contactsAddContact), accept: { [weak self] in - self?.importAndCloseIfPossible() - }, cancelTitle: tr(.modalCancel), drawBorder: false) + close = { [weak modalController] in + modalController?.close() } + return modalController } diff --git a/Telegram-Mac/AddContactTableItem.swift b/Telegram-Mac/AddContactTableItem.swift index e3bb2e3494..40fd872599 100644 --- a/Telegram-Mac/AddContactTableItem.swift +++ b/Telegram-Mac/AddContactTableItem.swift @@ -17,14 +17,16 @@ class AddContactTableItem: TableRowItem { fileprivate let addContact:()->Void init(_ initialSize: NSSize, stableId: AnyHashable, addContact: @escaping()->Void) { _stableId = stableId - self.text = TextViewLayout(.initialize(string: tr(.contactsAddContact), color: theme.colors.blueUI, font: .normal(.title)), maximumNumberOfLines: 1) + + self.text = TextViewLayout(.initialize(string: tr(L10n.contactsAddContact), color: theme.colors.accent, font: .normal(.title)), maximumNumberOfLines: 1) self.addContact = addContact super.init(initialSize) } override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) text.measure(width: width - 80) - return super.makeSize(width, oldWidth: oldWidth) + return success } override func viewClass() -> AnyClass { @@ -90,7 +92,7 @@ class AddContactTableRowView : TableRowView { override func layout() { super.layout() - imageView.centerY(x: floorToScreenPixels((56 - imageView.frame.width)/2)) + imageView.centerY(x: floorToScreenPixels(backingScaleFactor, (56 - imageView.frame.width)/2)) textView.layout?.measure(width: frame.width - 66) textView.update(textView.layout) textView.centerY(x: 56) diff --git a/Telegram-Mac/AdditionalSettings.swift b/Telegram-Mac/AdditionalSettings.swift new file mode 100644 index 0000000000..539e6f350e --- /dev/null +++ b/Telegram-Mac/AdditionalSettings.swift @@ -0,0 +1,71 @@ +// +// AdditionalSettings.swift +// Telegram +// +// Created by keepcoder on 13/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + + +import Cocoa +import Postbox +import SwiftSignalKit + + + +public struct AdditionalSettings: PreferencesEntry, Equatable { + public let useTouchId: Bool + + public static var defaultSettings: AdditionalSettings { + return AdditionalSettings(useTouchId: false) + } + + init(useTouchId: Bool) { + self.useTouchId = useTouchId + } + + public init(decoder: PostboxDecoder) { + self.useTouchId = decoder.decodeBoolForKey("ti", orElse: false) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeBool(self.useTouchId, forKey: "ti") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? AdditionalSettings { + return self == to + } else { + return false + } + } + + public static func ==(lhs: AdditionalSettings, rhs: AdditionalSettings) -> Bool { + return lhs.useTouchId == rhs.useTouchId + } + + func withUpdatedTouchId(_ useTouchId: Bool) -> AdditionalSettings { + return AdditionalSettings(useTouchId: useTouchId) + } +} + +func updateAdditionalSettingsInteractively(accountManager: AccountManager, _ f: @escaping (AdditionalSettings) -> AdditionalSettings) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.additionalSettings, { entry in + let currentSettings: AdditionalSettings + if let entry = entry as? AdditionalSettings { + currentSettings = entry + } else { + currentSettings = AdditionalSettings.defaultSettings + } + return f(currentSettings) + }) + } +} + +func additionalSettings(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.additionalSettings]) |> map { view in + return (view.entries[ApplicationSharedPreferencesKeys.additionalSettings] as? AdditionalSettings) ?? AdditionalSettings.defaultSettings + } +} + diff --git a/Telegram-Mac/Alpha.xcconfig b/Telegram-Mac/Alpha.xcconfig new file mode 100644 index 0000000000..54a91e80d5 --- /dev/null +++ b/Telegram-Mac/Alpha.xcconfig @@ -0,0 +1,18 @@ +// +// Alpha.xcconfig +// Telegram +// +// Created by Mikhail Filimonov on 13/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + + +DSA_PEM_FILE = dsa_pub.pem + +SIMPLE_SLASH=/ + +SFEED_URL = https:${SIMPLE_SLASH}/api.appcenter.ms/v0.1/public/sparkle/apps/f012091f-35d9-47bb-b3db-9cbd3b0232d3 +APPCENTER_SECRET = f012091f-35d9-47bb-b3db-9cbd3b0232d3 diff --git a/Telegram-Mac/AnimatedStickerUtils.swift b/Telegram-Mac/AnimatedStickerUtils.swift new file mode 100644 index 0000000000..c84ccb222d --- /dev/null +++ b/Telegram-Mac/AnimatedStickerUtils.swift @@ -0,0 +1,56 @@ +// +// AnimatedStickerUtils.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import SwiftSignalKit +import AVFoundation +import TGUIKit + +private func verifyLottieItems(_ items: [Any]?, shapes: Bool = true) -> Bool { + if let items = items { + for case let item as [AnyHashable: Any] in items { + if let type = item["ty"] as? String { + if type == "rp" || type == "sr" || type == "mm" || type == "gs" { + return false + } + } + + if shapes, let subitems = item["it"] as? [Any] { + if !verifyLottieItems(subitems, shapes: false) { + return false + } + } + } + } + return true; +} + +private func verifyLottieLayers(_ layers: [AnyHashable: Any]?) -> Bool { + return true +} + +func validateStickerComposition(json: [AnyHashable: Any]) -> Bool { + guard let tgs = json["tgs"] as? Int, tgs == 1 else { + return false + } + + return true +} + +private let writeQueue = DispatchQueue(label: "assetWriterQueue") + + +private func fillPixelBufferFromImage(_ image: CGImage, pixelBuffer: CVPixelBuffer) { + CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) + let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) + let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: pixelData, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) + context?.draw(image, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) + CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) +} diff --git a/Telegram-Mac/AppAppearanceViewController.swift b/Telegram-Mac/AppAppearanceViewController.swift new file mode 100644 index 0000000000..577dc4a1c9 --- /dev/null +++ b/Telegram-Mac/AppAppearanceViewController.swift @@ -0,0 +1,504 @@ +// +// AppAppearanceViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import TGUIKit +import SyncCore + + +struct AppearanceAccentColor : Equatable { + let accent: PaletteAccentColor + let cloudTheme: TelegramTheme? + let cachedTheme: InstallCloudThemeCachedData? + init(accent: PaletteAccentColor, cloudTheme: TelegramTheme?, cachedTheme: InstallCloudThemeCachedData? = nil) { + self.accent = accent + self.cloudTheme = cloudTheme + self.cachedTheme = cachedTheme + } + func withUpdatedCachedTheme(_ cachedTheme: InstallCloudThemeCachedData?) -> AppearanceAccentColor { + return .init(accent: self.accent, cloudTheme: self.cloudTheme, cachedTheme: cachedTheme) + } +} + +enum ThemeSettingsEntryTag: ItemListItemTag { + case fontSize + case theme + case autoNight + case chatMode + case accentColor + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? ThemeSettingsEntryTag, self == other { + return true + } else { + return false + } + } + + var stableId: InputDataEntryId { + switch self { + case .fontSize: + return .custom(_id_theme_text_size) + case .theme: + return .custom(_id_theme_list) + case .autoNight: + return .general(_id_theme_auto_night) + case .chatMode: + return .general(_id_theme_chat_mode) + case .accentColor: + return .custom(_id_theme_accent_list) + } + } +} + + +struct InstallCloudThemeCachedData : Equatable { + let palette: ColorPalette + let wallpaper: Wallpaper + let cloudWallpaper: TelegramWallpaper? +} +enum InstallThemeSource : Equatable { + case local(ColorPalette) + case cloud(TelegramTheme, InstallCloudThemeCachedData?) +} + +private final class AppAppearanceViewArguments { + let context: AccountContext + let togglePalette:(InstallThemeSource)->Void + let toggleBubbles:(Bool)->Void + let toggleFontSize:(CGFloat)->Void + let selectAccentColor:(AppearanceAccentColor?)->Void + let selectChatBackground:()->Void + let openAutoNightSettings:()->Void + let removeTheme:(TelegramTheme)->Void + let editTheme:(TelegramTheme)->Void + let shareTheme:(TelegramTheme)->Void + let shareLocal:(ColorPalette)->Void + init(context: AccountContext, togglePalette: @escaping(InstallThemeSource)->Void, toggleBubbles: @escaping(Bool)->Void, toggleFontSize: @escaping(CGFloat)->Void, selectAccentColor: @escaping(AppearanceAccentColor?)->Void, selectChatBackground:@escaping()->Void, openAutoNightSettings:@escaping()->Void, removeTheme:@escaping(TelegramTheme)->Void, editTheme: @escaping(TelegramTheme)->Void, shareTheme:@escaping(TelegramTheme)->Void, shareLocal:@escaping(ColorPalette)->Void) { + self.context = context + self.togglePalette = togglePalette + self.toggleBubbles = toggleBubbles + self.toggleFontSize = toggleFontSize + self.selectAccentColor = selectAccentColor + self.selectChatBackground = selectChatBackground + self.openAutoNightSettings = openAutoNightSettings + self.removeTheme = removeTheme + self.editTheme = editTheme + self.shareTheme = shareTheme + self.shareLocal = shareLocal + } +} + + +private let _id_theme_preview = InputDataIdentifier("_id_theme_preview") +private let _id_theme_list = InputDataIdentifier("_id_theme_list") +private let _id_theme_accent_list = InputDataIdentifier("_id_theme_accent_list") +private let _id_theme_chat_mode = InputDataIdentifier("_id_theme_chat_mode") +private let _id_theme_wallpaper1 = InputDataIdentifier("_id_theme_wallpaper") +private let _id_theme_wallpaper2 = InputDataIdentifier("_id_theme_wallpaper") +private let _id_theme_text_size = InputDataIdentifier("_id_theme_text_size") +private let _id_theme_auto_night = InputDataIdentifier("_id_theme_auto_night") + +private func appAppearanceEntries(appearance: Appearance, settings: ThemePaletteSettings, cloudThemes: [TelegramTheme], autoNightSettings: AutoNightThemePreferences, arguments: AppAppearanceViewArguments) -> [InputDataEntry] { + + var entries:[InputDataEntry] = [] + var sectionId: Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.appearanceSettingsColorThemeHeader), data: .init(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_theme_preview, equatable: InputDataEquatable(appearance), item: { initialSize, stableId in + return ThemePreviewRowItem(initialSize, stableId: stableId, context: arguments.context, theme: appearance.presentation, viewType: .firstItem) + })) + + var accentList = appearance.presentation.cloudTheme == nil || appearance.presentation.cloudTheme?.settings != nil ? appearance.presentation.colors.accentList.map { AppearanceAccentColor(accent: $0, cloudTheme: nil) } : [] + + var cloudThemes = cloudThemes + if let cloud = appearance.presentation.cloudTheme { + if !cloudThemes.contains(where: {$0.id == cloud.id}) { + cloudThemes.append(cloud) + } + } + if appearance.presentation.cloudTheme == nil || appearance.presentation.cloudTheme?.settings != nil { + let copy = cloudThemes + var cloudAccents:[AppearanceAccentColor] = [] + for cloudTheme in copy { + if let settings = cloudTheme.settings, settings.palette.parent == appearance.presentation.colors.parent { + cloudAccents.append(AppearanceAccentColor(accent: settings.accent, cloudTheme: cloudTheme)) + } + } + accentList.insert(contentsOf: cloudAccents, at: 0) + } + + cloudThemes.removeAll(where:{ $0.settings != nil }) + + struct ListEquatable : Equatable { + let theme: TelegramPresentationTheme + let cloudThemes:[TelegramTheme] + } + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_theme_list, equatable: InputDataEquatable(ListEquatable(theme: appearance.presentation, cloudThemes: cloudThemes)), item: { initialSize, stableId in + + let selected: ThemeSource + if let cloud = appearance.presentation.cloudTheme { + if let _ = cloud.settings { + selected = .local(appearance.presentation.colors, cloud) + } else { + selected = .cloud(cloud) + } + } else { + selected = .local(appearance.presentation.colors, nil) + } + + let dayClassicCloud = settings.associated.first(where: { $0.local == dayClassicPalette.parent })?.cloud?.cloud + let dayCloud = settings.associated.first(where: { $0.local == whitePalette.parent })?.cloud?.cloud + let nightAccentCloud = settings.associated.first(where: { $0.local == nightAccentPalette.parent })?.cloud?.cloud + + var locals: [LocalPaletteWithReference] = [LocalPaletteWithReference(palette: dayClassicPalette, cloud: dayClassicCloud), + LocalPaletteWithReference(palette: whitePalette, cloud: dayCloud), + LocalPaletteWithReference(palette: nightAccentPalette, cloud: nightAccentCloud), + LocalPaletteWithReference(palette: systemPalette, cloud: nil)] + + for (i, local) in locals.enumerated() { + if let accent = settings.accents.first(where: { $0.name == local.palette.parent }), accent.color.accent != local.palette.basicAccent { + locals[i] = local.withAccentColor(accent.color) + } + } + + return ThemeListRowItem(initialSize, stableId: stableId, context: arguments.context, theme: appearance.presentation, selected: selected, local: locals, cloudThemes: cloudThemes, viewType: accentList.isEmpty ? .lastItem : .innerItem, togglePalette: arguments.togglePalette, menuItems: { source in + var items:[ContextMenuItem] = [] + var cloud: TelegramTheme? + + switch source { + case let .cloud(c): + cloud = c + case let .local(_, c): + cloud = c + } + + if let cloud = cloud { + if cloud.isCreator { + items.append(ContextMenuItem(L10n.appearanceThemeEdit, handler: { + arguments.editTheme(cloud) + })) + } + items.append(ContextMenuItem(L10n.appearanceThemeShare, handler: { + arguments.shareTheme(cloud) + })) + items.append(ContextMenuItem(L10n.appearanceThemeRemove, handler: { + arguments.removeTheme(cloud) + })) + } + + return items + }) + })) + + + if !accentList.isEmpty { + + struct ALEquatable : Equatable { + let accentList: [AppearanceAccentColor] + let theme: TelegramPresentationTheme + } + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_theme_accent_list, equatable: InputDataEquatable(ALEquatable(accentList: accentList, theme: appearance.presentation)), item: { initialSize, stableId in + return AccentColorRowItem(initialSize, stableId: stableId, context: arguments.context, list: accentList, isNative: true, theme: appearance.presentation, viewType: .lastItem, selectAccentColor: arguments.selectAccentColor, menuItems: { accent in + var items:[ContextMenuItem] = [] + if let cloud = accent.cloudTheme { + items.append(ContextMenuItem(L10n.appearanceThemeShare, handler: { + arguments.shareTheme(cloud) + })) + items.append(ContextMenuItem(L10n.appearanceThemeRemove, handler: { + arguments.removeTheme(cloud) + })) + } + return items + }) + })) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_theme_chat_mode, data: InputDataGeneralData(name: L10n.appearanceSettingsBubblesMode, color: appearance.presentation.colors.text, type: .switchable(appearance.presentation.bubbled), viewType: appearance.presentation.bubbled ? .firstItem : .singleItem, action: { + arguments.toggleBubbles(!appearance.presentation.bubbled) + }))) + index += 1 + + if appearance.presentation.bubbled { + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_theme_wallpaper1, data: InputDataGeneralData(name: L10n.generalSettingsChatBackground, color: appearance.presentation.colors.text, type: .next, viewType: .lastItem, action: arguments.selectChatBackground))) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.appearanceSettingsTextSizeHeader), data: .init(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_theme_text_size, equatable: InputDataEquatable(appearance), item: { initialSize, stableId in + let sizes:[Int32] = [11, 12, 13, 14, 15, 16, 17, 18] + return SelectSizeRowItem(initialSize, stableId: stableId, current: Int32(appearance.presentation.fontSize), sizes: sizes, hasMarkers: true, viewType: .singleItem, selectAction: { index in + arguments.toggleFontSize(CGFloat(sizes[index])) + }) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.appearanceSettingsAutoNightHeader), data: .init(viewType: .textTopItem))) + index += 1 + + let autoNightText: String + if autoNightSettings.systemBased { + autoNightText = L10n.autoNightSettingsSystemBased + } else if let _ = autoNightSettings.schedule { + autoNightText = L10n.autoNightSettingsScheduled + } else { + autoNightText = L10n.autoNightSettingsDisabled + } + + sectionId += 1 + + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_theme_auto_night, data: InputDataGeneralData(name: L10n.appearanceSettingsAutoNight, color: appearance.presentation.colors.text, type: .nextContext(autoNightText), viewType: .singleItem, action: arguments.openAutoNightSettings))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func AppAppearanceViewController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> InputDataController { + + let applyCloudThemeDisposable = MetaDisposable() + let updateDisposable = MetaDisposable() + + + let applyTheme:(InstallThemeSource)->Void = { source in + switch source { + case let .local(palette): + updateDisposable.set(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + settings = settings.withUpdatedPalette(palette).withUpdatedCloudTheme(nil) + + let defaultTheme = DefaultTheme(local: palette.parent, cloud: nil) + if palette.isDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + return settings.installDefaultWallpaper().installDefaultAccent().withUpdatedDefaultIsDark(palette.isDark).withSavedAssociatedTheme() + }).start()) + case let .cloud(cloud, cached): + if let cached = cached { + updateDisposable.set(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + settings = settings.withUpdatedPalette(cached.palette) + settings = settings.withUpdatedCloudTheme(cloud) + settings = settings.updateWallpaper { _ in + return ThemeWallpaper(wallpaper: cached.wallpaper, associated: AssociatedWallpaper(cloud: cached.cloudWallpaper, wallpaper: cached.wallpaper)) + } + let defaultTheme = DefaultTheme(local: settings.palette.parent, cloud: DefaultCloudTheme(cloud: cloud, palette: cached.palette, wallpaper: AssociatedWallpaper(cloud: cached.cloudWallpaper, wallpaper: cached.wallpaper))) + if cached.palette.isDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + return settings.saveDefaultWallpaper().withUpdatedDefaultIsDark(cached.palette.isDark).withSavedAssociatedTheme() + }).start()) + + applyCloudThemeDisposable.set(downloadAndApplyCloudTheme(context: context, theme: cloud, install: true).start()) + } else if cloud.file != nil || cloud.settings != nil { + applyCloudThemeDisposable.set(showModalProgress(signal: downloadAndApplyCloudTheme(context: context, theme: cloud, install: true), for: context.window).start()) + } else { + showEditThemeModalController(context: context, theme: cloud) + } + } + } + + + let arguments = AppAppearanceViewArguments(context: context, togglePalette: { source in + + let nightSettings = autoNightSettings(accountManager: context.sharedContext.accountManager) |> take(1) |> deliverOnMainQueue + + _ = nightSettings.start(next: { settings in + if settings.systemBased || settings.schedule != nil { + confirm(for: context.window, header: L10n.darkModeConfirmNightModeHeader, information: L10n.darkModeConfirmNightModeText, okTitle: L10n.darkModeConfirmNightModeOK, successHandler: { _ in + let disableNightMode = context.sharedContext.accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.autoNight, { entry in + let settings: AutoNightThemePreferences = entry as? AutoNightThemePreferences ?? AutoNightThemePreferences.defaultSettings + return settings.withUpdatedSystemBased(false).withUpdatedSchedule(nil) + }) + } |> deliverOnMainQueue + _ = disableNightMode.start(next: { + applyTheme(source) + }) + }) + } else { + applyTheme(source) + } + }) + + + }, toggleBubbles: { value in + updateDisposable.set(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.withUpdatedBubbled(value) + }).start()) + }, toggleFontSize: { value in + updateDisposable.set(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.withUpdatedFontSize(value) + }).start()) + }, selectAccentColor: { value in + let updateColor:(AppearanceAccentColor)->Void = { color in + if let cloudTheme = color.cloudTheme { + applyTheme(.cloud(cloudTheme, color.cachedTheme)) + } else { + updateDisposable.set(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + let clearPalette = settings.palette.withoutAccentColor() + var settings = settings + if color.accent.accent == settings.palette.basicAccent { + settings = settings.withUpdatedPalette(clearPalette) + } else { + settings = settings.withUpdatedPalette(clearPalette.withAccentColor(color.accent)) + } + + let defaultTheme = DefaultTheme(local: settings.palette.parent, cloud: nil) + if settings.palette.isDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + + return settings.withUpdatedCloudTheme(nil).saveDefaultAccent(color: color.accent).installDefaultWallpaper().withSavedAssociatedTheme() + }).start()) + } + } + if let color = value { + updateColor(color) + } else { + showModal(with: CustomAccentColorModalController(context: context, updateColor: { accent in + updateColor(AppearanceAccentColor(accent: accent, cloudTheme: nil)) + }), for: context.window) + } + }, selectChatBackground: { + showModal(with: ChatWallpaperModalController(context), for: context.window) + }, openAutoNightSettings: { + context.sharedContext.bindings.rootNavigation().push(AutoNightSettingsController(context: context)) + }, removeTheme: { cloudTheme in + confirm(for: context.window, header: L10n.appearanceConfirmRemoveTitle, information: L10n.appearanceConfirmRemoveText, okTitle: L10n.appearanceConfirmRemoveOK, successHandler: { _ in + var signals:[Signal] = [] + if theme.cloudTheme?.id == cloudTheme.id { + signals.append(updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings.withUpdatedCloudTheme(nil) + .withUpdatedToDefault(dark: settings.defaultIsDark, onlyLocal: true) + let defaultTheme = DefaultTheme(local: settings.palette.parent, cloud: nil) + if settings.defaultIsDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + return settings.withSavedAssociatedTheme() + + })) + } + signals.append(deleteThemeInteractively(account: context.account, accountManager: context.sharedContext.accountManager, theme: cloudTheme)) + updateDisposable.set(combineLatest(signals).start()) + }) + }, editTheme: { value in + showEditThemeModalController(context: context, theme: value) + }, shareTheme: { value in + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/addtheme/\(value.slug)")), for: context.window) + }, shareLocal: { palette in + + }) + + let cloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + let nightSettings = autoNightSettings(accountManager: context.sharedContext.accountManager) + + let signal:Signal = combineLatest(queue: prepareQueue, appearanceSignal, themeUnmodifiedSettings(accountManager: context.sharedContext.accountManager), cloudThemes, nightSettings) |> map { appearance, themeSettings, cloudThemes, autoNightSettings in + return appAppearanceEntries(appearance: appearance, settings: themeSettings, cloudThemes: cloudThemes.reversed(), autoNightSettings: autoNightSettings, arguments: arguments) + } + |> map { entries in + return InputDataSignalValue(entries: entries, animated: false) + } |> deliverOnMainQueue + + + let controller = InputDataController(dataSignal: signal, title: L10n.telegramAppearanceViewController, removeAfterDisappear:false, identifier: "app_appearance", customRightButton: { controller in + + let view = ImageBarView(controller: controller, theme.icons.chatActions) + + view.button.set(handler: { control in + var items:[SPopoverItem] = [] + if theme.colors.parent != .system { + items.append(SPopoverItem(L10n.appearanceNewTheme, { + showModal(with: NewThemeController(context: context, palette: theme.colors.withUpdatedWallpaper(theme.wallpaper.paletteWallpaper)), for: context.window) + })) + items.append(SPopoverItem(L10n.appearanceExportTheme, { + exportPalette(palette: theme.colors.withUpdatedName(theme.cloudTheme?.title ?? theme.colors.name).withUpdatedWallpaper(theme.wallpaper.paletteWallpaper)) + })) + if let cloudTheme = theme.cloudTheme { + items.append(SPopoverItem(L10n.appearanceThemeShare, { + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/addtheme/\(cloudTheme.slug)")), for: context.window) + })) + } + + if theme.cloudTheme != nil || theme.colors.accent != theme.colors.basicAccent { + items.append(SPopoverItem(L10n.appearanceReset, { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + if settings.defaultIsDark { + settings = settings.withUpdatedDefaultDark(DefaultTheme(local: TelegramBuiltinTheme.nightAccent, cloud: nil)).saveDefaultAccent(color: PaletteAccentColor(nightAccentPalette.accent)) + } else { + settings = settings.withUpdatedDefaultDay(DefaultTheme(local: TelegramBuiltinTheme.dayClassic, cloud: nil)).saveDefaultAccent(color: PaletteAccentColor(dayClassicPalette.accent)) + } + + return settings.installDefaultAccent().withUpdatedCloudTheme(nil).updateWallpaper({ _ -> ThemeWallpaper in + return ThemeWallpaper(wallpaper: settings.palette.wallpaper.wallpaper, associated: nil) + }).installDefaultWallpaper() + }).start() + })) + } + + showPopover(for: control, with: SPopoverViewController(items: items), edge: .minX, inset: NSMakePoint(0,-50)) + } + }, for: .Click) + view.button.set(image: theme.icons.chatActions, for: .Normal) + view.button.set(image: theme.icons.chatActionsActive, for: .Highlight) + return view + + }) + + controller.updateRightBarView = { view in + if let view = view as? ImageBarView { + view.button.set(image: theme.icons.chatActions, for: .Normal) + view.button.set(image: theme.icons.chatActionsActive, for: .Highlight) + } + } + + controller.didLoaded = { controller, _ in + if let focusOnItemTag = focusOnItemTag { + controller.genericView.tableView.scroll(to: .center(id: focusOnItemTag.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets()) + } + } + + return controller +} diff --git a/Telegram-Mac/AppDelegate.swift b/Telegram-Mac/AppDelegate.swift index dbb1b88312..d508eb1d23 100644 --- a/Telegram-Mac/AppDelegate.swift +++ b/Telegram-Mac/AppDelegate.swift @@ -1,28 +1,52 @@ import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit import Quartz -import MtProtoKitMac - +import MtProtoKit +import CoreServices +import LocalAuthentication +//import WalletCore +import OpenSSLEncryption +import CoreSpotlight #if !APP_STORE - import HockeySDK - import Sparkle +import AppCenter +import AppCenterCrashes +#endif + + +#if !SHARE +extension Account { + var diceCache: DiceCache? { + return (NSApp.delegate as? AppDelegate)?.contextValue?.context.diceCache + } +} #endif +private final class SharedApplicationContext { + let sharedContext: SharedAccountContext + let notificationManager: SharedNotificationManager + let sharedWakeupManager: SharedWakeupManager + init(sharedContext: SharedAccountContext, notificationManager: SharedNotificationManager, sharedWakeupManager: SharedWakeupManager) { + self.sharedContext = sharedContext + self.notificationManager = notificationManager + self.sharedWakeupManager = sharedWakeupManager + } +} @NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate { +class AppDelegate: NSResponder, NSApplicationDelegate, NSUserNotificationCenterDelegate, NSWindowDelegate { - #if !APP_STORE - @IBOutlet weak var updater: SUUpdater! - #endif + @IBOutlet weak var window: Window! { didSet { + window.delegate = self + window.isOpaque = true window.initSaver() } } @@ -32,41 +56,90 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(handleURLEvent(_: with:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) } - /* - { - set { - - } - get { - // let path = "\(Bundle.main.bundlePath)/Contents/Frameworks/Sparkle.framework" - // return SUUpdater.init(for: Bundle.init(identifier: "")) - } - } - */ - + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + let presentAccountStatus = Promise(false) fileprivate let nofityDisposable:MetaDisposable = MetaDisposable() var containerUrl:String! - private let accountManagerPromise = Promise() - private var contextValue: ApplicationContext? - private let context = Promise() - private let contextDisposable = MetaDisposable() + private let sharedContextPromise = Promise() + private var sharedContextOnce: Signal { + return sharedContextPromise.get() |> take(1) |> deliverOnMainQueue + } + + + var passlock: Signal { + return sharedContextPromise.get() |> mapToSignal { + return $0.notificationManager.passlocked + } + } + + fileprivate var contextValue: AuthorizedApplicationContext? + private let context = Promise() + + private var authContextValue: UnauthorizedApplicationContext? + private let authContext = Promise() + + + private let handleEventContextDisposable = MetaDisposable() + private let proxyDisposable = MetaDisposable() private var activity:Any? + private var executeUrlAfterLogin: String? = nil + func applicationWillFinishLaunching(_ notification: Notification) { + + } + + var baseAppBundleId: String { + return Bundle.main.bundleIdentifier! + } + func applicationDidFinishLaunching(_ aNotification: Notification) { + + initializeSelectManager() + startLottieCacheCleaner() + if #available(OSX 10.12.2, *) { + NSApplication.shared.isAutomaticCustomizeTouchBarMenuItemEnabled = true + } - let appGroupName = "6N38VWS5BX.ru.keepcoder.Telegram" + let appGroupName = ApiEnvironment.group guard let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) else { return } + self.containerUrl = containerUrl.path + + let v = View() + v.flip = false + window.contentView = v + window.contentView?.autoresizingMask = [.width, .height] + window.contentView?.autoresizesSubviews = true + + let crashed = isCrashedLastTime(containerUrl.path) + deinitCrashHandler(containerUrl.path) + + if crashed { + let alert: NSAlert = NSAlert() + alert.addButton(withTitle: L10n.crashOnLaunchOK) + alert.addButton(withTitle: L10n.crashOnLaunchCancel) + alert.messageText = L10n.crashOnLaunchMessage + alert.informativeText = L10n.crashOnLaunchInformation + alert.alertStyle = .critical + if alert.runModal() == NSApplication.ModalResponse.alertFirstButtonReturn { + try? FileManager.default.removeItem(atPath: self.containerUrl) + } + } + + saveIntermediateDate() + uiLocalizationFunc = { key in return _NSLocalizedString(key) } @@ -75,163 +148,752 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele return _NSLocalizedString(key) }) + setInputLocalizationFunc { (key) -> String in + return _NSLocalizedString(key) + } + + var paths: [String?] = [] + paths.append(Bundle.main.path(forResource: "opening", ofType:"m4a")) + paths.append(Bundle.main.path(forResource: "voip_busy", ofType:"caf")) + paths.append(Bundle.main.path(forResource: "voip_ringback", ofType:"caf")) + paths.append(Bundle.main.path(forResource: "voip_connecting", ofType:"mp3")) + paths.append(Bundle.main.path(forResource: "voip_fail", ofType:"caf")) + paths.append(Bundle.main.path(forResource: "voip_end", ofType:"caf")) + paths.append(Bundle.main.path(forResource: "sent", ofType:"caf")) + + + for path in paths { + if let path = path { + let player = try? AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) + player?.prepareToPlay() + } + } + + FFMpegGlobals.initializeGlobals() + // applyMainMenuLocalization(window) mw = window - self.containerUrl = containerUrl.path #if !APP_STORE - self.updater.automaticallyChecksForUpdates = true - // self.updater.automaticallyDownloadsUpdates = false - self.updater.checkForUpdatesInBackground() + if let secret = Bundle.main.infoDictionary?["APPCENTER_SECRET"] as? String { + MSAppCenter.start(secret, withServices: [MSCrashes.self]) + } #endif - Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(checkUpdates), userInfo: nil, repeats: true) - + // Timer.scheduledTimer(timeInterval: 60 * 60, target: self, selector: #selector(checkUpdates), userInfo: nil, repeats: true) + Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(saveIntermediateDate), userInfo: nil, repeats: true) - for argument in CommandLine.arguments { - switch argument { - case "DEBUG_SESSION": - isDebug = true - default: - break - } - } - if !isDebug { - #if BETA - - let hockeyAppId:String = "6ed2ac3049e1407387c2f1ffcb74e81f" - BITHockeyManager.shared().configure(withIdentifier: hockeyAppId) - BITHockeyManager.shared().crashManager.isAutoSubmitCrashReport = true - BITHockeyManager.shared().start() - - #endif -// -// #if STABLE  -// let hockeyAppId:String = "d77af558b21e0878953100680b5ac66a" -// BITHockeyManager.shared().configure(withIdentifier: hockeyAppId) -// BITHockeyManager.shared().crashManager.isAutoSubmitCrashReport = false -// #endif - - } +// let test = View() +// test.backgroundColor = NSColor.black.withAlphaComponent(0.87) +// test.frame = NSMakeRect(0, 0, leftSidebarWidth, Window.statusBarHeight) +// window.titleView?.addSubview(test, positioned: .below, relativeTo: window.titleView?.subviews.first) - telegramUIDeclareEncodables() MTLogSetEnabled(UserDefaults.standard.bool(forKey: "enablelogs")) let logger = Logger(basePath: containerUrl.path + "/logs") - logger.logToConsole = UserDefaults.standard.bool(forKey: "enablelogs") + logger.logToConsole = false logger.logToFile = UserDefaults.standard.bool(forKey: "enablelogs") - #if APP_STORE || STABLE + #if DEBUG + MTLogSetEnabled(true) logger.logToConsole = false - MTLogSetEnabled(false) + logger.logToFile = true #endif - Logger.setSharedLogger(logger) + initializeMimeStore() - #if !APP_STORE - if let feedUrl = Bundle.main.infoDictionary?["SUFeedURL"] as? String, let url = URL(string: feedUrl) { - updater.feedURL = url - } - #endif +// #if APP_STORE || STABLE +// logger.logToConsole = false +// MTLogSetEnabled(false) +// #endif + Logger.setSharedLogger(logger) - + let bundleId = Bundle.main.bundleIdentifier if let bundleId = bundleId { LSSetDefaultHandlerForURLScheme("tg" as CFString, bundleId as CFString) } - - self.accountManagerPromise.set(accountManager(basePath: containerUrl.path + "/accounts-metadata")) + launchInterface() + + } + + + private func launchInterface() { + initializeAccountManagement() + + + let rootPath = containerUrl! + let window = self.window! + + + window.minSize = NSMakeSize(380, 500) - let _ = (accountManagerPromise.get() - |> mapToSignal { manager in - return managedCleanupAccounts(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), accountManager: manager, appGroupPath: containerUrl.path, auxiliaryMethods: telegramAccountAuxiliaryMethods) - }).start() + let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) + let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: true, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) + + _ = System.scaleFactor.swap(window.backingScaleFactor) + + let networkDisposable = MetaDisposable() - self.context.set(self.accountManagerPromise.get() |> deliverOnMainQueue |> mapToSignal { accountManager -> Signal in - return applicationContext(window: self.window, shouldOnlineKeeper: self.presentAccountStatus.get(), accountManager: accountManager, appGroupPath: containerUrl.path, testingEnvironment: TEST_SERVER) - }) + let accountManager = AccountManager(basePath: containerUrl + "/accounts-metadata") + + let displayUpgrade:(Float?) -> Void = { progress in + if let progress = progress { + let view = HackUtils.findElements(byClass: "Telegram.OpmizeDatabaseView", in: self.window.contentView!).first as? OpmizeDatabaseView ?? OpmizeDatabaseView(frame: self.window.bounds) + view.setProgress(progress) + self.window.contentView?.addSubview(view, positioned: .below, relativeTo: self.window.contentView?.subviews.first) + self.window.makeKeyAndOrderFront(self) + } else { + (HackUtils.findElements(byClass: "Telegram.OpmizeDatabaseView", in: self.window.contentView!).first as? NSView)?.removeFromSuperview() + } + } + + + let _ = (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters) |> deliverOnMainQueue).start(next: { value in + if value > 0 { + displayUpgrade(value) + } else { + displayUpgrade(nil) + } + }, completed: { + let themeSemaphore = DispatchSemaphore(value: 0) + var themeSettings: ThemePaletteSettings = ThemePaletteSettings.defaultTheme + _ = (themeSettingsView(accountManager: accountManager) |> take(1)).start(next: { settings in + themeSettings = settings + themeSemaphore.signal() + }) + themeSemaphore.wait() + + + var localization: LocalizationSettings? = nil + let localizationSemaphore = DispatchSemaphore(value: 0) + _ = (accountManager.transaction { transaction in + localization = transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings + localizationSemaphore.signal() + }).start() + localizationSemaphore.wait() + + if let localization = localization { + applyUILocalization(localization) + } + + + updateTheme(with: themeSettings, for: window) + + + let basicTheme = Atomic(value: themeSettings) + let viewDidChangedAppearance: ValuePromise = ValuePromise(true) + let backingProperties:ValuePromise = ValuePromise(System.backingScale, ignoreRepeated: true) + + + var previousBackingScale = System.backingScale + _ = combineLatest(queue: .mainQueue(), themeSettingsView(accountManager: accountManager), backingProperties.get()).start(next: { settings, backingScale in + let previous = basicTheme.swap(settings) + if previous?.palette != settings.palette || previous?.bubbled != settings.bubbled || previous?.wallpaper != settings.wallpaper || previous?.fontSize != settings.fontSize || previousBackingScale != backingScale { + updateTheme(with: settings, for: window, animated: window.isKeyWindow && ((previous?.fontSize == settings.fontSize && previous?.palette != settings.palette) || previous?.bubbled != settings.bubbled || previous?.cloudTheme?.id != settings.cloudTheme?.id || previous?.palette.isDark != settings.palette.isDark)) + self.contextValue?.applyNewTheme() + } + previousBackingScale = backingScale + }) + + NotificationCenter.default.addObserver(forName: NSWindow.didChangeBackingPropertiesNotification, object: window, queue: nil, using: { notification in + backingProperties.set(System.backingScale) + }) + + let autoNightSignal = viewDidChangedAppearance.get() |> mapToSignal { _ in + return combineLatest(autoNightSettings(accountManager: accountManager), Signal.single(Void()) |> then( Signal.single(Void()) |> delay(60, queue: Queue.mainQueue()) |> restart)) + } |> deliverOnMainQueue + + + _ = autoNightSignal.start(next: { preference, _ in + + let isEnabled: Bool + + if let schedule = preference.schedule { + let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + var now: time_t = time_t(nowTimestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + let t = timeinfoNow.tm_hour * 60 * 60 + timeinfoNow.tm_min * 60 + timeinfoNow.tm_sec + + switch schedule { + case let .sunrise(coordinate): + if coordinate.latitude == 0 || coordinate.longitude == 0 { + isEnabled = theme.colors.isDark + } else { + if let sunrise = EDSunriseSet(date: Date(), timezone: NSTimeZone.local, latitude: coordinate.latitude, longitude: coordinate.longitude) { + let from = Int32(sunrise.sunset.timeIntervalSince1970 - sunrise.sunset.startOfDay.timeIntervalSince1970) + let to = Int32(sunrise.sunrise.timeIntervalSince1970 - sunrise.sunrise.startOfDay.timeIntervalSince1970) + isEnabled = to > from && t >= from && t <= to || to < from && (t >= from || t <= to) + } else { + isEnabled = false + } + } + case let .timeSensitive(from, to): + let from = from * 60 * 60 + let to = to * 60 * 60 + isEnabled = to > from && t >= from && t < to || to < from && (t >= from || t < to) + } + + } else if preference.systemBased { + if #available(OSX 10.14, *) { + switch systemAppearance.name { + case NSAppearance.Name.aqua: + isEnabled = false + case NSAppearance.Name.darkAqua: + isEnabled = true + default: + isEnabled = false + } + } else { + isEnabled = false + } + } else { + isEnabled = false + } + + _ = updateThemeInteractivetly(accountManager: accountManager, f: { settings -> ThemePaletteSettings in + var settings = settings + if isEnabled { + if let theme = preference.theme.cloud { + settings = settings.withUpdatedCloudTheme(theme.cloud).withUpdatedPalette(theme.palette).updateWallpaper { current in + return ThemeWallpaper(wallpaper: theme.wallpaper.wallpaper, associated: theme.wallpaper) + } + } else { + settings = settings.withUpdatedPalette(preference.theme.local.palette).withUpdatedCloudTheme(nil).installDefaultWallpaper().installDefaultAccent() + } + } else { + settings = settings.withUpdatedToDefault(dark: settings.defaultIsDark) + } + return settings + }).start() + }) + + + let basicLocalization = Atomic(value: localization) + _ = (accountManager.sharedData(keys: [SharedDataKeys.localizationSettings]) |> deliverOnMainQueue).start(next: { view in + if let settings = view.entries[SharedDataKeys.localizationSettings] as? LocalizationSettings { + if basicLocalization.swap(settings) != settings { + applyUILocalization(settings) + } + } + }) + + let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: OngoingCallThreadLocalContext.maxLayer(), voipVersions: [OngoingCallThreadLocalContext.version()], appData: .single(ApiEnvironment.appData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()) + + let sharedContext = SharedAccountContext(accountManager: accountManager, networkArguments: networkArguments, rootPath: rootPath, encryptionParameters: encryptionParameters, displayUpgradeProgress: displayUpgrade) + + + let rawAccounts = sharedContext.activeAccounts + |> map { _, accounts, _ -> [Account] in + return accounts.map({ $0.1 }) + } + let _ = (sharedAccountInfos(accountManager: sharedContext.accountManager, accounts: rawAccounts) + |> deliverOn(Queue())).start(next: { infos in + storeAccountsData(rootPath: rootPath, accounts: infos) + }) + + + let notificationsBindings = SharedNotificationBindings(navigateToChat: { account, peerId in + + if let contextValue = self.contextValue, contextValue.context.account.id == account.id { + let navigation = contextValue.context.sharedContext.bindings.rootNavigation() + + if let controller = navigation.controller as? ChatController { + if controller.chatInteraction.peerId == peerId { + controller.scrollup() + } else { + navigation.push(ChatAdditionController(context: contextValue.context, chatLocation: .peer(peerId))) + } + } else { + navigation.push(ChatController(context: contextValue.context, chatLocation: .peer(peerId))) + } + + } else { + sharedContext.switchToAccount(id: account.id, action: .chat(peerId, necessary: true)) + } + NSApp.activate(ignoringOtherApps: true) + window.deminiaturize(nil) + }, updateCurrectController: { + if let contextValue = self.contextValue { + contextValue.context.sharedContext.bindings.rootNavigation().controller.updateController() + } + }) + + let sharedNotificationManager = SharedNotificationManager(activeAccounts: sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) }, accountManager: accountManager, window: window, bindings: notificationsBindings) + let sharedWakeupManager = SharedWakeupManager(sharedContext: sharedContext, inForeground: self.presentAccountStatus.get()) + let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: sharedNotificationManager, sharedWakeupManager: sharedWakeupManager) + + + + self.sharedContextPromise.set(accountManager.transaction { transaction -> (SharedApplicationContext, LoggingSettings) in + return (sharedApplicationContext, transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings ?? LoggingSettings.defaultSettings) + } + |> mapToSignal { sharedApplicationContext, loggingSettings -> Signal in + #if BETA || ALPHA + Logger.shared.logToFile = true + #else + Logger.shared.logToFile = loggingSettings.logToFile + #endif + Logger.shared.logToConsole = false//loggingSettings.logToConsole + Logger.shared.redactSensitiveData = true//loggingSettings.redactSensitiveData + return .single(sharedApplicationContext) + }) + + +// let tonKeychain: TonKeychain +// +// tonKeychain = TonKeychain(encryptionPublicKey: { +// return Signal { subscriber in +// return EmptyDisposable +// } +// }, encrypt: { data in +// return Signal { subscriber in +// if #available(OSX 10.12, *) { +// if let context = self.contextValue?.context, let publicKey = TKPublicKey.get(for: context.account) { +// if let result = publicKey.encrypt(data: data) { +// subscriber.putNext(TonKeychainEncryptedData(publicKey: publicKey.key, data: result)) +// subscriber.putCompletion() +// return EmptyDisposable +// } +// } +// } +// subscriber.putError(.generic) +// return EmptyDisposable +// } +// }, decrypt: { encryptedData in +// return Signal { subscriber in +// return EmptyDisposable +// } +// }) + + + + self.context.set(self.sharedContextPromise.get() + |> deliverOnMainQueue + |> mapToSignal { sharedApplicationContext -> Signal in + return sharedApplicationContext.sharedContext.activeAccounts + |> map { primary, _, _ -> Account? in + return primary + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs !== rhs { + return false + } + return true + }) + |> map { account in + if let account = account { + var settings: LaunchSettings? + if let action = sharedContext.getLaunchActionOnce(for: account.id) { + settings = LaunchSettings(applyText: nil, previousText: nil, navigation: action, openAtLaunch: true) + } else { + let semaphore = DispatchSemaphore(value: 0) + _ = account.postbox.transaction { transaction in + settings = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings) as? LaunchSettings + semaphore.signal() + }.start() + semaphore.wait() + } + // let tonContext = StoredTonContext(basePath: account.basePath, postbox: account.postbox, network: account.network, keychain: tonKeychain) + + let context = AccountContext(sharedContext: sharedApplicationContext.sharedContext, window: window, account: account) + return AuthorizedApplicationContext(window: window, context: context, launchSettings: settings ?? LaunchSettings.defaultSettings) + + } else { + return nil + } + } + }) + + + self.authContext.set(self.sharedContextPromise.get() + |> deliverOnMainQueue + |> mapToSignal { sharedApplicationContext -> Signal in + return sharedApplicationContext.sharedContext.activeAccounts + |> map { primary, accounts, auth -> (Account?, UnauthorizedAccount, [Account])? in + if let auth = auth { + return (primary, auth, Array(accounts.map({ $0.1 }))) + } else { + return nil + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs?.1 !== rhs?.1 { + return false + } + return true + }) + |> mapToSignal { authAndAccounts -> Signal<(UnauthorizedAccount, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))?, NoError> in + if let (primary, auth, accounts) = authAndAccounts { + let phoneNumbers = combineLatest(accounts.map { account -> Signal<(AccountRecordId, String, Bool)?, NoError> in + return account.postbox.transaction { transaction -> (AccountRecordId, String, Bool)? in + if let phone = (transaction.getPeer(account.peerId) as? TelegramUser)?.phone { + return (account.id, phone, account.testingEnvironment) + } else { + return nil + } + } + }) + return phoneNumbers + |> map { phoneNumbers -> (UnauthorizedAccount, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))? in + var primaryNumber: (String, AccountRecordId, Bool)? + if let primary = primary { + for idAndNumber in phoneNumbers { + if let (id, number, testingEnvironment) = idAndNumber, id == primary.id { + primaryNumber = (number, id, testingEnvironment) + break + } + } + } + return (auth, (primaryNumber, phoneNumbers.compactMap({ $0.flatMap({ ($0.1, $0.0, $0.2) }) }))) + } + } else { + return .single(nil) + } + } + |> mapToSignal { accountAndOtherAccountPhoneNumbers -> Signal<(UnauthorizedAccount, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))?, NoError> in + if let (account, otherAccountPhoneNumbers) = accountAndOtherAccountPhoneNumbers { + return .single((account, otherAccountPhoneNumbers)) + } else { + return .single(nil) + } + } + |> deliverOnMainQueue + |> mapToSignal { accountAndSettings -> Signal in + if let accountAndSettings = accountAndSettings { + return .single(UnauthorizedApplicationContext(window: window, sharedContext: sharedApplicationContext.sharedContext, account: accountAndSettings.0, otherAccountPhoneNumbers: accountAndSettings.1)) + } else { + return .single(nil) + } + } + }) + + + + + _ = (self.context.get() |> mapToSignal { context -> Signal in + if let context = context { + return context.ready |> map { [weak context] _ in + return context + } + } else { + return .single(nil) + } + + } |> deliverOnMainQueue).start(next: { context in + assert(Queue.mainQueue().isCurrent()) + + if let contextValue = self.contextValue { + contextValue.context.isCurrent = false + contextValue.rootView.removeFromSuperview() + } + + (HackUtils.findElements(byClass: "Telegram.OpmizeDatabaseView", in: self.window.contentView!).first as? NSView)?.removeFromSuperview() + + + //closeAllModals() + closeAllPopovers(for: window) + + self.contextValue = context + + if let context = context { + context.context.isCurrent = true + context.applyNewTheme() + self.window.contentView?.addSubview(context.rootView, positioned: .below, relativeTo: self.window.contentView?.subviews.first) + + context.runLaunchAction() + if let executeUrlAfterLogin = self.executeUrlAfterLogin { + self.executeUrlAfterLogin = nil + execute(inapp: inApp(for: executeUrlAfterLogin.nsstring, context: context.context)) + } + #if !APP_STORE + networkDisposable.set((context.context.account.postbox.preferencesView(keys: [PreferencesKeys.networkSettings]) |> delay(5.0, queue: Queue.mainQueue()) |> deliverOnMainQueue).start(next: { settings in + let settings = settings.values[PreferencesKeys.networkSettings] as? NetworkSettings + + let applicationUpdateUrlPrefix: String? + if let prefix = settings?.applicationUpdateUrlPrefix { + if prefix.range(of: "://") == nil { + applicationUpdateUrlPrefix = "https://" + prefix + } else { + applicationUpdateUrlPrefix = prefix + } + } else { + applicationUpdateUrlPrefix = nil + } + setAppUpdaterBaseDomain(applicationUpdateUrlPrefix) + #if STABLE + updater_resetWithUpdaterSource(.internal(context: context.context)) + #else + updater_resetWithUpdaterSource(.external(context: context.context)) + #endif + + })) + #endif + + if let url = AppDelegate.eventProcessed { + self.processURL(url) + } + if let action = AppDelegate.spotlightAction { + self.processSpotlightAction(action) + } + + if !self.window.isKeyWindow { + self.window.makeKeyAndOrderFront(self) + } + self.window.deminiaturize(self) + NSApp.activate(ignoringOtherApps: true) + + + } + }) + + + var presentAuthAnimated: Bool = false + + let authContextReadyDisposable = MetaDisposable() + + _ = (self.authContext.get() + |> deliverOnMainQueue).start(next: { context in + + (HackUtils.findElements(byClass: "Telegram.OpmizeDatabaseView", in: self.window.contentView!).first as? NSView)?.removeFromSuperview() + + if let authContextValue = self.authContextValue { + authContextValue.account.shouldBeServiceTaskMaster.set(.single(.never)) + authContextValue.modal.close() + } + self.authContextValue = context + if let context = context { + let isReady: Signal = .single(true) + authContextReadyDisposable.set((isReady + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + + window.makeKeyAndOrderFront(nil) + showModal(with: context.modal, for: window, animated: presentAuthAnimated) + + #if !APP_STORE + networkDisposable.set((context.account.postbox.preferencesView(keys: [PreferencesKeys.networkSettings]) |> delay(5.0, queue: Queue.mainQueue()) |> deliverOnMainQueue).start(next: { settings in + let settings = settings.values[PreferencesKeys.networkSettings] as? NetworkSettings + + let applicationUpdateUrlPrefix: String? + if let prefix = settings?.applicationUpdateUrlPrefix { + if prefix.range(of: "://") == nil { + applicationUpdateUrlPrefix = "https://" + prefix + } else { + applicationUpdateUrlPrefix = prefix + } + } else { + applicationUpdateUrlPrefix = nil + } + setAppUpdaterBaseDomain(applicationUpdateUrlPrefix) + #if STABLE + if let context = self.contextValue?.context { + updater_resetWithUpdaterSource(.internal(context: context)) + } else { + updater_resetWithUpdaterSource(.external(context: nil)) + } + #else + updater_resetWithUpdaterSource(.external(context: self.contextValue?.context)) + #endif - self.contextDisposable.set(self.context.get().start(next: { context in - assert(Queue.mainQueue().isCurrent()) - self.window.makeKeyAndOrderFront(self) - self.contextValue = context - self.window.contentView?.removeAllSubviews() + })) + #endif + + + })) + } else { + presentAuthAnimated = true + authContextReadyDisposable.set(nil) + } + }) + + + - context?.showRoot(for: self.window) - })) + // + + + self.saveIntermediateDate() + + + if #available(OSX 10.14, *) { + DistributedNotificationCenter.default().addObserver(forName: Notification.Name("AppleInterfaceThemeChangedNotification"), object: nil, queue: nil, using: { _ in + delay(0.1, closure: { + forceUpdateStatusBarIconByDockTile(sharedContext: sharedContext) + viewDidChangedAppearance.set(true) + }) + }) + + (window.contentView as? View)?.viewDidChangedEffectiveAppearance = { + viewDidChangedAppearance.set(true) + } + } + + NotificationCenter.default.addObserver(self, selector: #selector(self.windiwDidChangeBackingProperties), name: NSWindow.didChangeBackingPropertiesNotification, object: window) + + + + let fontSizes:[Int32] = [11, 12, 13, 14, 15, 16, 17, 18] + +// +// window.set(handler: { () -> KeyHandlerResult in +// _ = updateThemeInteractivetly(accountManager: accountManager, f: { current -> ThemePaletteSettings in +// if let index = fontSizes.firstIndex(of: Int32(current.fontSize)) { +// if index == fontSizes.count - 1 { +// return current +// } else { +// return current.withUpdatedFontSize(CGFloat(fontSizes[index + 1])) +// } +// } else { +// return current +// } +// }).start() +// if let index = fontSizes.firstIndex(of: Int32(theme.fontSize)), index == fontSizes.count - 1 { +// return .rejected +// } +// return .invoked +// }, with: self, for: .Equal, modifierFlags: [.command]) +// +// window.set(handler: { () -> KeyHandlerResult in +// _ = updateThemeInteractivetly(accountManager: accountManager, f: { current -> ThemePaletteSettings in +// if let index = fontSizes.firstIndex(of: Int32(current.fontSize)) { +// if index == 0 { +// return current +// } else { +// return current.withUpdatedFontSize(CGFloat(fontSizes[index - 1])) +// } +// } else { +// return current +// } +// }).start() +// if let index = fontSizes.firstIndex(of: Int32(theme.fontSize)), index == 0 { +// return .rejected +// } +// return .invoked +// }, with: self, for: .Minus, modifierFlags: [.command]) + + self.window.contentView?.wantsLayer = true + }) + - self.window.contentView?.wantsLayer = true + } + + + @objc public func windiwDidChangeBackingProperties() { + _ = System.scaleFactor.swap(window.backingScaleFactor) + } + + + @IBAction func checkForUpdates(_ sender: Any) { + #if !APP_STORE + showModal(with: InputDataModalController(AppUpdateViewController()), for: window) + #if STABLE + if let context = self.contextValue?.context { + updater_resetWithUpdaterSource(.internal(context: context)) + } else { + updater_resetWithUpdaterSource(.external(context: nil)) + } + #else + updater_resetWithUpdaterSource(.external(context: self.contextValue?.context)) + #endif + #endif + } + + override func awakeFromNib() { + #if APP_STORE + if let menu = NSApp.mainMenu?.item(at: 0)?.submenu, let sparkleItem = menu.item(withTag: 1000) { + menu.removeItem(sparkleItem) + } + #endif } + @objc func checkUpdates() { #if !APP_STORE - updater.checkForUpdatesInBackground() + showModal(with: InputDataModalController(AppUpdateViewController()), for: window) #endif } - private static var eventProcessed: Bool = false + + @objc func saveIntermediateDate() { + crashIntermediateDate(containerUrl) + } + + private static var eventProcessed: String? = nil + private static var spotlightAction: SpotlightIdentifier? = nil + @objc func handleURLEvent(_ event:NSAppleEventDescriptor, with replyEvent:NSAppleEventDescriptor) { - AppDelegate.eventProcessed = false let url = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue - self.handleEventContextDisposable.set((self.context.get()).start(next: { context in - if !AppDelegate.eventProcessed { - NSApp.activate(ignoringOtherApps: true) - self.window.deminiaturize(self) + processURL(url) + } + + private func processURL(_ url: String?) { + AppDelegate.eventProcessed = url + + if let url = AppDelegate.eventProcessed { + NSApp.activate(ignoringOtherApps: true) + self.window.deminiaturize(self) + if let context = self.contextValue?.context { + AppDelegate.eventProcessed = nil - if let url = url, let context = context { - switch context { - case let .authorized(context): - AppDelegate.eventProcessed = true - let link = inApp(for: url as NSString, account: context.account, openInfo: { (peerId, isChat, postId, action) in - context.rightController.push(ChatController(account: context.account, peerId: peerId, messageId:postId, initialAction:action), true) - }, applyProxy: { proxy in - applyExternalProxy(proxy, postbox: context.account.postbox, network: context.account.network) - }) - - execute(inapp: link) - case .unauthorized(let context): - let settings = proxySettings(from: url) - if settings.1 { - AppDelegate.eventProcessed = true - if let proxy = settings.0 { - applyExternalProxy(proxy, postbox: context.account.postbox, network: context.account.network) - } else { - _ = applyProxySettings(postbox: context.account.postbox, network: context.account.network, settings: nil).start() - } - } - default: - break + let link = inApp(for: url as NSString, context: context, openInfo: { (peerId, isChat, postId, action) in + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId), messageId:postId, initialAction:action), true) + }, applyProxy: { proxy in + applyExternalProxy(proxy, accountManager: context.sharedContext.accountManager) + }) + execute(inapp: link) + } else if let authContext = self.authContextValue { + let settings = proxySettings(from: url) + if settings.1 { + AppDelegate.eventProcessed = nil + if let proxy = settings.0 { + applyExternalProxy(proxy, accountManager: authContext.sharedContext.accountManager) + } else { + _ = updateProxySettingsInteractively(accountManager: authContext.sharedContext.accountManager, { current -> ProxySettings in + return current.withUpdatedActiveServer(nil) + }).start() } } + + if url.range(of: legacyPassportUsername) != nil || url.range(of: "tg://passport") != nil { + alert(for: mainWindow, info: L10n.secureIdLoginText) + self.executeUrlAfterLogin = url + } } - })) + } } + func window(_ window: NSWindow, willPositionSheet sheet: NSWindow, using rect: NSRect) -> NSRect { + var rect = rect + rect.origin.y -= 22 + return rect; + } + func applicationDidBecomeActive(_ notification: Notification) { presentAccountStatus.set(.single(true) |> then(.single(true) |> delay(50, queue: Queue.concurrentBackgroundQueue())) |> restart) } - func applicationDidResignActive(_ notification: Notification) { - presentAccountStatus.set(.single(false)) - } + func applicationDidHide(_ notification: Notification) { presentAccountStatus.set(.single(false)) @@ -242,7 +904,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - window.makeKeyAndOrderFront(sender) + if viewer != nil { + viewer?.windowDidResignKey() + } else if let passport = passport { + passport.window.makeKeyAndOrderFront(nil) + } else { + window.makeKeyAndOrderFront(nil) + } return true } @@ -278,61 +946,146 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } } - - - override func awakeFromNib() { - #if APP_STORE - if let menu = NSApp.mainMenu?.item(at: 0)?.submenu, let sparkleItem = menu.item(withTag: 1000) { - menu.removeItem(sparkleItem) + func applicationWillUnhide(_ notification: Notification) { + window.makeKeyAndOrderFront(nil) + } + + func applicationWillBecomeActive(_ notification: Notification) { + if contextValue != nil { + if viewer != nil { + viewer?.windowDidResignKey() + } else if let passport = passport { + passport.window.makeKeyAndOrderFront(nil) + } else { + // window.makeKeyAndOrderFront(nil) } - #endif + + + } } - @IBAction func checkForUpdates(_ sender: Any) { + + + func applicationDidResignActive(_ notification: Notification) { + presentAccountStatus.set(.single(false)) + if viewer != nil { + viewer?.window.orderOut(nil) + } + } + + func applicationWillTerminate(_ notification: Notification) { + deinitCrashHandler(containerUrl) + #if !APP_STORE - updater.checkForUpdates(sender) + updateAppIfNeeded() #endif } + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + + if let context = self.contextValue?.context { + let navigation = context.sharedContext.bindings.rootNavigation() + } + + return .terminateNow + } + + + func windowDidDeminiaturize(_ notification: Notification) { + window.orderOut(nil) + window.makeKeyAndOrderFront(nil) + } + + func windowDidMiniaturize(_ notification: Notification) { + window.resignMain() + } + + + var hasAuthorized: Bool { + return contextValue?.context != nil + } @IBAction func unhide(_ sender: Any) { window.makeKeyAndOrderFront(sender) } - // LocalizationWrapper.setLanguageCode("ru") @IBAction func aboutAction(_ sender: Any) { - window.makeKeyAndOrderFront(sender) showModal(with: AboutModalController(), for: window) + window.makeKeyAndOrderFront(sender) } @IBAction func preferencesAction(_ sender: Any) { + + if let context = contextValue?.context { + context.sharedContext.bindings.mainController().showPreferences() + } window.makeKeyAndOrderFront(sender) - if let context = self.contextValue { - switch context { - case let .authorized(appContext): - appContext.leftController.showPreferences() - if !(appContext.rightController.controller is GeneralSettingsViewController) { - appContext.rightController.push(GeneralSettingsViewController(appContext.account), false) - } - default: - break - } + + } + @IBAction func globalSearch(_ sender: Any) { + if let context = contextValue?.context { + context.sharedContext.bindings.mainController().focusSearch(animated: true) } } @IBAction func closeWindow(_ sender: Any) { NSApp.keyWindow?.close() } - @IBAction func showQuickSwitcher(_ sender: Any) { - window.makeKeyAndOrderFront(sender) - if let context = contextValue { - switch context { - case .authorized(let authorized): - if !authorized.isLocked { - showModal(with: QuickSwitcherModalController(account: authorized.account), for: mainWindow) + func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool { + if userActivity.activityType == CSSearchableItemActionType { + if let uniqueIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String { + if let identifier = parseSpotlightIdentifier(uniqueIdentifier) { + self.processSpotlightAction(identifier) } - default: - break } } + + return true + } + + private func processSpotlightAction(_ identifier: SpotlightIdentifier) { + if let context = contextValue?.context { + AppDelegate.spotlightAction = nil + if context.account.id == identifier.recordId { + switch identifier.source { + case let .peerId(peerId): + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId))) + } + } else { + switch identifier.source { + case let .peerId(peerId): + context.sharedContext.switchToAccount(id: identifier.recordId, action: .chat(peerId, necessary: true)) + } + } + } else { + AppDelegate.spotlightAction = identifier + } + + } + + func getLogFilesContentWithMaxSize() -> String { + + let semaphore = DispatchSemaphore(value: 0) + var result: String = "" + _ = Logger.shared.collectShortLog().start(next: { logs in + for log in logs.suffix(500) { + result += log.1 + "\n" + } + semaphore.signal() + }) + semaphore.wait() + + return result + } + + @IBAction func showQuickSwitcher(_ sender: Any) { + + if let context = contextValue?.context, authContextValue == nil { + _ = sharedContextOnce.start(next: { applicationContext in + if !applicationContext.notificationManager.isLocked { + showModal(with: QuickSwitcherModalController(context), for: self.window) + } + }) + } + window.makeKeyAndOrderFront(sender) } } diff --git a/Telegram-Mac/AppUpdateViewController.swift b/Telegram-Mac/AppUpdateViewController.swift new file mode 100644 index 0000000000..8f5583d4ba --- /dev/null +++ b/Telegram-Mac/AppUpdateViewController.swift @@ -0,0 +1,627 @@ +// +// AppUpdateViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 01/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#if !APP_STORE + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import Sparkle + + + +final class TelegramUpdater : NSObject, SUUpdaterPrivate { + var delegate: SUUpdaterDelegate! + + var userAgentString: String! + + var domain: String! = nil + var host: String! = nil + + var httpHeaders: [AnyHashable : Any]! + + var decryptionPassword: String! + + var sparkleBundle: Bundle! + + override init() { + self.sparkleBundle = Bundle(for: SUUpdateDriver.self) + } +} + +extension SUAppcastItem { + var updateText: String { + var updateText = (itemDescription.html2Attributed?.string ?? itemDescription).replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil) + while let range = updateText.range(of: " ") { + updateText = updateText.replacingOccurrences(of: " ", with: " ", options: [], range: range) + } + updateText = updateText.replacingOccurrences(of: "•", with: "\n•", options: [], range: nil) + + if updateText.first == "\n" { + updateText.removeFirst() + } + updateText = updateText.replacingOccurrences(of: "\t", with: " ", options: [], range: nil) + return updateText + } + + var versionTitle: String { + return "Version \(self.displayVersionString!) (\(self.versionString!))" + } +} + + + +enum AppUpdateLoadingState : Equatable { + case initializing + case loading(item:SUAppcastItem, current: Int, total: Int) + case hasUpdate(SUAppcastItem) + case readyToInstall(SUAppcastItem) + case uptodate + case unarchiving(SUAppcastItem) + case installing + case failed(NSError) +} + +private let initialState = AppUpdateState(items: [], loadingState: .initializing) +private let statePromise: ValuePromise = ValuePromise(initialState, ignoreRepeated: true) +private let stateValue = Atomic(value: initialState) + +var appUpdateStateSignal: Signal { + return statePromise.get() +} + +private let updateState:((AppUpdateState)->AppUpdateState) -> Void = { f in + statePromise.set(stateValue.modify(f)) +} +private let updater = TelegramUpdater() +private var driver:SUBasicUpdateDriver? +private let host = SUHost(bundle: Bundle.main) + +func updateApplication(sharedContext: SharedAccountContext) { + + + let state = stateValue.with {$0.loadingState} + switch state { + case let .readyToInstall(item): + var text: String = "Telegram was updated to \(item.versionTitle.lowercased())" + text += "\n\n" + + text += item.updateText + + _ = (sharedContext.activeAccountsWithInfo |> take(1) |> mapToSignal { _, accounts -> Signal in + return combineLatest(accounts.map { addAppUpdateText($0.account.postbox, applyText: text) }) |> ignoreValues + } |> deliverOnMainQueue).start(completed: { + driver?.install(withToolAndRelaunch: true) + + }) + + case .installing: + break + default: + resetUpdater() + } +} + + +struct AppUpdateState : Equatable { + let items: [SUAppcastItem] + let loadingState: AppUpdateLoadingState + + fileprivate init(items: [SUAppcastItem], loadingState: AppUpdateLoadingState) { + self.items = items + self.loadingState = loadingState + + } + func withUpdatedItems(_ items: [SUAppcastItem]) -> AppUpdateState { + return AppUpdateState(items: items, loadingState: self.loadingState) + } + func withUpdatedLoadingState(_ loadingState: AppUpdateLoadingState) -> AppUpdateState { + return AppUpdateState(items: self.items, loadingState: loadingState) + } +} + +extension String{ + var html2Attributed: NSAttributedString? { + do { + guard let data = data(using: String.Encoding.utf8) else { + return nil + } + return try NSAttributedString(data: data, + options: [.documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue], + documentAttributes: nil) + } catch { + print("error: ", error) + return nil + } + } +} + +private let _id_update_app: InputDataIdentifier = InputDataIdentifier("_id_update_app") +private let _id_initializing: InputDataIdentifier = InputDataIdentifier("_id_initializing") +private let _id_downloading: InputDataIdentifier = InputDataIdentifier("_id_downloading") +private let _id_download_update: InputDataIdentifier = InputDataIdentifier("_id_download_update") +private let _id_install_update: InputDataIdentifier = InputDataIdentifier("_id_install_update") +private let _id_check_for_updates: InputDataIdentifier = InputDataIdentifier("_id_check_for_updates") +private let _id_unarchiving: InputDataIdentifier = InputDataIdentifier("_id_unarchiving") + +private func appUpdateEntries(state: AppUpdateState) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + var currentItem: SUAppcastItem? + + switch state.loadingState { + case let .failed(error): + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_check_for_updates, data: InputDataGeneralData(name: L10n.appUpdateCheckForUpdates, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(error.localizedDescription), data: InputDataGeneralTextData(color: theme.colors.redUI, detectBold: false, viewType: .textBottomItem))) + index += 1 + + case let .hasUpdate(item): + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_download_update, data: InputDataGeneralData(name: L10n.appUpdateDownloadUpdate, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + currentItem = item + case .initializing: + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_initializing, data: InputDataGeneralData(name: L10n.appUpdateRetrievingInfo, color: theme.colors.grayText, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + case let .loading(item, current, total): + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_downloading, data: InputDataGeneralData(name: "\(L10n.appUpdateDownloading) \(String.prettySized(with: current) + " / " + String.prettySized(with: total))", color: theme.colors.grayText, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + currentItem = item + case .uptodate: + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_check_for_updates, data: InputDataGeneralData(name: L10n.appUpdateCheckForUpdates, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.appUpdateUptodate), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + index += 1 + case let .unarchiving(item): + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_unarchiving, data: InputDataGeneralData(name: L10n.appUpdateUnarchiving, color: theme.colors.grayText, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + currentItem = item + case let .readyToInstall(item): + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_install_update, data: InputDataGeneralData(name: L10n.updateUpdateTelegram, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + currentItem = item + case .installing: + break + } + + + if let item = currentItem { + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.appUpdateNewestAvailable), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let text = "**" + item.versionTitle + "**" + "\n" + item.updateText + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier(item.fileURL.path), equatable: nil, item: { initialSize, stableId in + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, textColor: theme.colors.listGrayText, fontSize: 13, isTextSelectable: true, viewType: .textTopItem) + })) + index += 1 + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + + + +func AppUpdateViewController() -> InputDataController { + + let signal: Signal = statePromise.get() |> deliverOnResourceQueue |> map { value in + return appUpdateEntries(state: value) + } |> map { InputDataSignalValue(entries: $0) } + + + return InputDataController(dataSignal: signal, title: L10n.appUpdateTitle, validateData: { data in + + if let _ = data[_id_download_update] { + driver?.downloadUpdate() + } + if let _ = data[_id_check_for_updates] { + resetUpdater() + } + if let _ = data[_id_install_update] { + driver?.install(withToolAndRelaunch: true) + } + + return .none + }, afterDisappear: { + + }, hasDone: false, identifier: "app_update") + + +} + +private let updates_channel_xml = "macos_stable_updates_xml" + + + +private final class InternalUpdaterDownloader : SPUDownloaderSession { + private let context: AccountContext + private let updateItem: SUAppcastItem + private let disposable = MetaDisposable() + init(context: AccountContext, updateItem: SUAppcastItem, delegate: SPUDownloaderDelegate) { + self.context = context + self.updateItem = updateItem + super.init(delegate: delegate) + } + + deinit { + disposable.dispose() + } + + override func suggestedFilename() -> String! { + return "Telegram.app.zip" + } + + + + override func moveItem(atPath fromPath: String!, toPath: String!, error: Error) -> Bool { + try? FileManager.default.removeItem(atPath: toPath) + do { + try FileManager.default.copyItem(atPath: fromPath, toPath: toPath) + return true + } catch { + return false + } + } + + + override func startDownload(with request: SPUURLRequest!) { + if let internalUrl = self.updateItem.internalUrl { + + let url = inApp(for: internalUrl as NSString, context: self.context, peerId: nil, openInfo: { _, _, _, _ in }, hashtag: nil, command: nil, applyProxy: nil, confirm: false) + switch url { + case let .followResolvedName(_, username, messageId, context, _, _): + if let messageId = messageId { + let signal = downloadAppUpdate(account: context.account, source: username, messageId: messageId) |> deliverOnMainQueue + disposable.set(signal.start(next: { [weak self] result in + guard let `self` = self else { + return + } + switch result { + case let .started(total): + self.delegate.downloaderDidReceiveExpectedContentLength(Int64(total)) + case let .progress(current, _): + self.delegate.downloaderDidReceiveData(ofLength: UInt64(current)) + case let .finished(path): + self.urlSession(URLSession(), downloadTask: URLSessionDownloadTask(), didFinishDownloadingTo: URL(fileURLWithPath: path)) + } + }, error: { [weak self] error in + self?.delegate.downloaderDidFailWithError(NSError(domain: "Failed to download archive. Please try again.", code: 0, userInfo: nil)) + })) + } else { + self.delegate.downloaderDidFailWithError(NSError(domain: "Wrong internal link. Please try again.", code: 0, userInfo: nil)) + } + + default: + self.delegate.downloaderDidFailWithError(NSError(domain: "Wrong internal link. Please try again.", code: 0, userInfo: nil)) + } + + + } else { + self.delegate.downloaderDidFailWithError(NSError(domain: "No internal link for this version. Please try again.", code: 0, userInfo: nil)) + } + + } + + + override func cancel() { + disposable.set(nil) + } + +} + +private final class InternalUpdateDriver : ExternalUpdateDriver { + + + private let disposabe = MetaDisposable() + private let context: AccountContext + + init(updater:TelegramUpdater, context: AccountContext) { + self.context = context + super.init(updater: updater) + } + + deinit { + disposabe.dispose() + } + + override func checkForUpdates(at URL: URL!, host aHost: SUHost!, domain: String) { + self.host = aHost + + updateState { + return $0.withUpdatedLoadingState(.initializing) + } + + let signal = requestUpdatesXml(account: self.context.account, source: updates_channel_xml) |> deliverOnMainQueue |> timeout(20.0, queue: .mainQueue(), alternate: .fail(.xmlLoad)) + + disposabe.set(signal.start(next: { [weak self] data in + let appcast = SUAppcast() + appcast.parseAppcastItems(fromXMLData: data, error: nil) + self?.appcastDidFinishLoading(appcast) + }, error: { [weak self] error in + self?.abortUpdateWithError(NSError(domain: "Failed to download updating info. Please try again.", code: 0, userInfo: nil)) + })) + } + + override func downloadUpdate() { + let downloader = InternalUpdaterDownloader(context: self.context, updateItem: self.updateItem, delegate: self) + self.download = downloader + let fileName = "Telegram \(self.updateItem.versionString ?? "")" + + downloader.startPersistentDownload(with: SPUURLRequest(), bundleIdentifier: host.bundle.bundleIdentifier!, desiredFilename: fileName) + } + + override func downloaderDidReceiveData(ofLength length: UInt64) { + updateState { state in + switch state.loadingState { + case let .loading(item, _, total): + return state.withUpdatedLoadingState(.loading(item: item, current: Int(length), total: total)) + default: + return state + } + } + } + + override func downloaderDidReceiveExpectedContentLength(_ expectedContentLength: Int64) { + updateState { state in + return state.withUpdatedLoadingState(.loading(item: self.updateItem, current: 0, total: Int(expectedContentLength))) + } + } + +} + +private class ExternalUpdateDriver : SUBasicUpdateDriver { + + override func extractUpdate() { + super.extractUpdate() + updateState { + return $0.withUpdatedLoadingState(.unarchiving(self.updateItem)) + } + } + + + + override func install(withToolAndRelaunch relaunch: Bool, displayingUserInterface showUI: Bool) { + updateState { + return $0.withUpdatedLoadingState(.installing) + } + resourcesQueue.async { + super.install(withToolAndRelaunch: relaunch, displayingUserInterface: showUI) + } + } + + override func appcastDidFinishLoading(_ ac: SUAppcast!) { + updateState { + return $0.withUpdatedItems(ac.items?.compactMap({$0 as? SUAppcastItem}) ?? []) + } + super.appcastDidFinishLoading(ac) + } + + override func didNotFindUpdate() { + updateState { + return $0.withUpdatedLoadingState(.uptodate) + } + } + + override func checkForUpdates(at url: URL!, host aHost: SUHost!, domain: String) { + updateState { + return $0.withUpdatedLoadingState(.initializing) + } + super.checkForUpdates(at: url, host: aHost, domain: domain) + + } + + override func downloadUpdate() { + updateState { + return $0.withUpdatedLoadingState(.loading(item: self.updateItem, current: 0, total: Int(self.updateItem.contentLength))) + } + super.downloadUpdate() + } + + override func downloaderDidFinish(withTemporaryDownloadData downloadData: SPUDownloadData!) { + super.downloaderDidFinish(withTemporaryDownloadData: downloadData) + } + + override func unarchiverDidFinish(_ ua: Any!) { + updateState { + return $0.withUpdatedLoadingState(.readyToInstall(self.updateItem)) + } + } + + override func unarchiver(_ ua: Any!, extractedProgress progress: Double) { + + } + + override func downloaderDidReceiveData(ofLength length: UInt64) { + updateState { state in + switch state.loadingState { + case let .loading(item, current, total): + return state.withUpdatedLoadingState(.loading(item: item, current: current + Int(length), total: total)) + default: + return state + } + } + } + + override func downloaderDidReceiveExpectedContentLength(_ expectedContentLength: Int64) { + updateState { state in + return state.withUpdatedLoadingState(.loading(item: self.updateItem, current: 0, total: Int(expectedContentLength))) + } + } + + override func downloaderDidFailWithError(_ error: Error!) { + super.downloaderDidFailWithError(error) + updateState { state in + return state.withUpdatedLoadingState(.failed(error as NSError? ?? NSError(domain: L10n.unknownError, code: 0, userInfo: nil))) + } + } + + override func abortUpdateWithError(_ error: Error!) { + super.abortUpdateWithError(error) + updateState { state in + return state.withUpdatedLoadingState(.failed(error as NSError? ?? NSError(domain: L10n.unknownError, code: 0, userInfo: nil))) + } + trySwitchUpdaterBetweenSources() + } + + override func installer(for host: SUHost!, failedWithError error: Error!) { + super.installer(for: host, failedWithError: error) + updateState { state in + return state.withUpdatedLoadingState(.failed(error as NSError? ?? NSError(domain: L10n.unknownError, code: 0, userInfo: nil))) + } + trySwitchUpdaterBetweenSources() + } +} + + + + +private let disposable = MetaDisposable() + +func setAppUpdaterBaseDomain(_ domain: String?) { + updater.domain = domain + if let domain = domain { + updater.host = URL(string: domain)?.host + } else { + updater.host = nil + } +} + + +func updateAppIfNeeded() { + let state = stateValue.with {$0.loadingState} + + switch state { + case .readyToInstall: + driver?.install(withToolAndRelaunch: false, displayingUserInterface: true) + default: + break + } +} + + +enum UpdaterSource : Equatable { + static func == (lhs: UpdaterSource, rhs: UpdaterSource) -> Bool { + switch lhs { + case let .external(lhsContext): + if case let .external(rhsContext) = rhs { + if let lhsContext = lhsContext, let rhsContext = rhsContext { + return lhsContext.account.peerId == rhsContext.account.peerId + } else if (lhsContext != nil) != (rhsContext != nil) { + return false + } + return true + } else { + return false + } + case let .internal(lhsContext): + if case let .internal(rhsContext) = rhs { + return lhsContext.account.peerId == rhsContext.account.peerId + } else { + return false + } + } + } + + case external(context: AccountContext?) + case `internal`(context: AccountContext) +} + + +private func resetUpdater() { + + #if !GITHUB + let update:()->Void = { + let url = updater.domain ?? Bundle.main.infoDictionary!["SUFeedURL"] as! String + let state = stateValue.with { $0.loadingState } + switch state { + case .readyToInstall, .installing, .unarchiving, .loading: + break + default: + driver?.checkForUpdates(at: URL(string: url)!, host: host, domain: updater.host) + } + } + + + let signal: Signal = Signal { subscriber in + update() + subscriber.putCompletion() + return EmptyDisposable + } |> delay(20 * 60, queue: .mainQueue()) |> restart + disposable.set(signal.start()) + + update() + #endif + + +} + +private var updaterSource: UpdaterSource? = nil + +func updater_resetWithUpdaterSource(_ source: UpdaterSource, force: Bool = true) { + + if updaterSource != source { + updaterSource = source + switch source { + case .external: + driver = ExternalUpdateDriver(updater: updater) + case let .internal(context): + driver = InternalUpdateDriver(updater: updater, context: context) + } + } + if force { + updateState { + $0.withUpdatedLoadingState(.initializing) + } + resetUpdater() + } +} + + +private func trySwitchUpdaterBetweenSources() { + if let source = updaterSource { + switch source { + case let .external(context): + #if STABLE || DEBUG + if let context = context { + updater_resetWithUpdaterSource(.internal(context: context), force: true) + } + #endif + case let .internal(context): + updater_resetWithUpdaterSource(.external(context: context), force: false) + } + } +} + +#endif + diff --git a/Telegram-Mac/Appearance.swift b/Telegram-Mac/Appearance.swift index ae08b61450..91850a254f 100644 --- a/Telegram-Mac/Appearance.swift +++ b/Telegram-Mac/Appearance.swift @@ -8,22 +8,815 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import SyncCore + +func generateFilledCircleImage(diameter: CGFloat, color: NSColor?, strokeColor: NSColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: NSColor? = nil) -> CGImage { + return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + + if let strokeColor = strokeColor, let strokeWidth = strokeWidth { + context.setFillColor(strokeColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(NSColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0))) + } else { + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(NSColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + } + })! +} + + +func generateTextIcon(_ text: NSAttributedString) -> CGImage { + + let textNode = TextNode.layoutText(text, nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, 20), nil, false, .center) + + return generateImage(textNode.0.size, rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + textNode.1.draw(rect.focus(textNode.0.size), in: ctx, backingScaleFactor: System.backingScale, backgroundColor: .clear) + })! +} + +private func generateGradientBubble(_ top: NSColor, _ bottom: NSColor) -> CGImage { + + var bottom = bottom + var top = top + if !System.supportsTransparentFontDrawing { + bottom = top.blended(withFraction: 0.5, of: bottom)! + top = bottom + } + + return generateImage(CGSize(width: 1.0, height: 100), opaque: true, scale: 1.0, rotatedContext: { size, context in + var locations: [CGFloat] = [0.0, 1.0] + let colors = [top.cgColor, bottom.cgColor] as NSArray + + let colorSpace = deviceColorSpace + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + })! +} + +private func generateProfileIcon(_ image: CGImage, backgroundColor: NSColor) -> CGImage { + return generateImage(image.backingSize, contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.setFillColor(backgroundColor.cgColor) + ctx.fillEllipse(in: NSMakeRect(2, 2, rect.width - 4, rect.height - 4)) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + + })! +} + +private func generateChatTabFiltersIcon(_ image: CGImage) -> CGImage { + return generateImage(image.backingSize, contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + + ctx.setBlendMode(.clear) + + var x: CGFloat = 14 + ctx.fillEllipse(in: NSMakeRect(x, 17, 3, 3)) + x += (3 + 2) + ctx.fillEllipse(in: NSMakeRect(x, 17, 3, 3)) + x += (3 + 2) + ctx.fillEllipse(in: NSMakeRect(x, 17, 3, 3)) + + })! +} + +private func generateChatAction(_ image: CGImage, background: NSColor) -> CGImage { + return generateImage(NSMakeSize(36, 36), contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.setFillColor(background.cgColor) + ctx.fillEllipse(in: rect) + ctx.draw(image, in: rect.focus(image.backingSize)) + + })! +} + +private func generatePollIcon(_ image: NSImage, backgound: NSColor) -> CGImage { + return generateImage(NSMakeSize(36 / System.backingScale, 36 / System.backingScale), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + ctx.setBlendMode(.copy) + ctx.round(size, size.height / 2) + ctx.setFillColor(backgound.cgColor) + ctx.fill(rect) + + ctx.setBlendMode(.normal) + let image = image.cgImage(forProposedRect: nil, context: nil, hints: nil)! + if backgound == NSColor(0xffffff) { + ctx.clip(to: rect, mask: image) + ctx.clear(rect) + } else { + ctx.draw(image, in: rect.focus(NSMakeSize(image.size.width / System.backingScale, image.size.height / System.backingScale))) + } + }, scale: System.backingScale)! +} + +private func generateSecretThumbSmall(_ image: CGImage) -> CGImage { + return generateImage(NSMakeSize(floor(image.size.width * 0.7), floor(image.size.height * 0.7)), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.clip(to: rect, mask: image) + ctx.setBlendMode(.difference) + ctx.setFillColor(.white) + ctx.fill(rect) + ctx.draw(image, in: rect) + }, scale: 1.0)! +} + +private func generateSecretThumb(_ image: CGImage) -> CGImage { + return generateImage(image.size, contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.clip(to: rect, mask: image) + ctx.setBlendMode(.difference) + ctx.setFillColor(.white) + ctx.fill(rect) + ctx.draw(image, in: rect) + }, scale: 1.0)! +} + +private func generateLoginQrEmptyCap() -> CGImage { + return generateImage(NSMakeSize(60, 60), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + })! +} + +private func generateSendIcon(_ image: NSImage, _ color: NSColor) -> CGImage { + let image = image.precomposed(color) + if color.lightness > 0.7 { + return image + } else { + return generateImage(image.backingSize, contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.setFillColor(.white) + ctx.fillEllipse(in: rect.focus(NSMakeSize(rect.width - 8, rect.height - 8))) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! + } +} + +private func generateUnslectedCap(_ color: NSColor) -> CGImage { + return generateImage(NSMakeSize(22, 22), contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.setStrokeColor(color.withAlphaComponent(0.7).cgColor) + ctx.setLineWidth(1.0) + ctx.strokeEllipse(in: NSMakeRect(1, 1, size.width - 2, size.height - 2)) + })! +} + +private func generatePollAddOption(_ color: NSColor) -> CGImage { + let image = NSImage(named: "Icon_PollAddOption")!.precomposed(color) + return generateImage(image.backingSize, contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + ctx.setFillColor(.white) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! +} + +func generateThemePreview(for palette: ColorPalette, wallpaper: Wallpaper, backgroundMode: TableBackgroundMode) -> CGImage { + return generateImage(NSMakeSize(320, 320), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + //background + ctx.setFillColor(palette.chatBackground.cgColor) + ctx.fill(rect) + + switch wallpaper { + case .builtin, .file, .color, .gradient: + switch backgroundMode { + case let .background(image): + let imageSize = image.size.aspectFilled(size) + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: 1.0, y: -1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + ctx.draw(image.cgImage(forProposedRect: nil, context: nil, hints: nil)!, in: rect.focus(imageSize)) + + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: 1.0, y: -1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + + break + case let .color(color): + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + case let .gradient(top, bottom, rotation): + let colors = [top, bottom].reversed() + + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + ctx.saveGState() + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.rotate(by: CGFloat(rotation ?? 0) * CGFloat.pi / -180.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + ctx.restoreGState() + default: + break + } + default: + break + } + + //top and bottom + ctx.setFillColor(palette.background.cgColor) + ctx.fill(NSMakeRect(0, 0, rect.width, 50)) + ctx.setFillColor(palette.background.cgColor) + ctx.fill(NSMakeRect(0, rect.height - 50, rect.width, 50)) + + + + //top border + ctx.setFillColor(palette.border.cgColor) + ctx.fill(NSMakeRect(0, 50, rect.width, .borderSize)) + + //bottom border + ctx.setFillColor(palette.border.cgColor) + ctx.fill(NSMakeRect(0, rect.height - 50, rect.width, .borderSize)) + + + //fill avatar + ctx.setFillColor(palette.grayForeground.cgColor) + ctx.fillEllipse(in: NSMakeRect(20, (50 - 36) / 2, 36, 36)) + + //fill chat actions + let chatAction = NSImage(named: "Icon_ChatActions")!.precomposed(palette.accentIcon) + ctx.draw(chatAction, in: NSMakeRect(rect.width - 20 - chatAction.backingSize.width, (50 - chatAction.backingSize.height) / 2, chatAction.backingSize.width, chatAction.backingSize.height)) + + //fill attach icon + let inputAttach = NSImage(named: "Icon_ChatAttach")!.precomposed(palette.grayIcon, flipVertical: true) + ctx.draw(inputAttach, in: NSMakeRect(20, rect.height - 50 + ((50 - inputAttach.backingSize.height) / 2), inputAttach.backingSize.width, inputAttach.backingSize.height)) + + //fill micro icon + let micro = NSImage(named: "Icon_RecordVoice")!.precomposed(palette.grayIcon, flipVertical: true) + ctx.draw(micro, in: NSMakeRect(rect.width - 20 - inputAttach.backingSize.width, (rect.height - 50 + (50 - micro.backingSize.height) / 2), micro.backingSize.width, micro.backingSize.height)) + + let chatServiceItemColor: NSColor + + + switch wallpaper { + case .builtin, .file, .color, .gradient: + switch backgroundMode { + case let .background(image): + chatServiceItemColor = getAverageColor(image) + case let .color(color): + if color != palette.background { + chatServiceItemColor = getAverageColor(color) + } else { + chatServiceItemColor = color + } + case let .gradient(top, bottom, _): + if let blended = top.blended(withFraction: 0.5, of: bottom) { + chatServiceItemColor = getAverageColor(blended) + } else { + chatServiceItemColor = getAverageColor(top) + } + case let .tiled(image): + chatServiceItemColor = getAverageColor(image) + case .plain: + chatServiceItemColor = palette.chatBackground + } + default: + chatServiceItemColor = getAverageColor(palette.chatBackground) + } + + + + //fill date + ctx.setFillColor(chatServiceItemColor.cgColor) + let path = NSBezierPath(roundedRect: NSMakeRect(rect.width / 2 - 30, rect.height - 50 - 10 - 60 - 5 - 20 - 5, 60, 20), xRadius: 10, yRadius: 10) + ctx.addPath(path.cgPath) + ctx.closePath() + ctx.fillPath() + + + //fill outgoing bubble + CATransaction.begin() + if true { + + let image = generateImage(NSMakeSize(150, 30), rotatedContext: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + + let data = messageBubbleImageModern(incoming: false, fillColor: palette.bubbleBackgroundTop_outgoing, strokeColor: palette.bubbleBorder_outgoing, neighbors: .none) + + let layer = CALayer() + layer.frame = NSMakeRect(0, 0, 150, 30) + layer.contentsScale = 2.0 + let imageSize = data.0.backingSize + let insets = data.1 + let halfPixelFudge: CGFloat = 0.49 + let otherPixelFudge: CGFloat = 0.02 + var contentsCenter: CGRect = NSMakeRect(0.0, 0.0, 1.0, 1.0); + if (insets.left > 0 || insets.right > 0) { + contentsCenter.origin.x = ((insets.left + halfPixelFudge) / imageSize.width); + contentsCenter.size.width = (imageSize.width - (insets.left + insets.right + 1.0) + otherPixelFudge) / imageSize.width; + } + if (insets.top > 0 || insets.bottom > 0) { + contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height); + contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.0) + otherPixelFudge) / imageSize.height; + } + layer.contentsGravity = .resize; + layer.contentsCenter = contentsCenter; + layer.contents = data.0 + + layer.render(in: ctx) + })! + + var bubble = image + if palette.bubbleBackgroundTop_outgoing != palette.bubbleBackgroundBottom_outgoing { + bubble = generateImage(NSMakeSize(150, 30), contextGenerator: { size, ctx in + let colors = [palette.bubbleBackgroundTop_outgoing, palette.bubbleBackgroundBottom_outgoing] + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.clip(to: rect, mask: image) + + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + })! + } + ctx.draw(bubble, in: NSMakeRect(160, 230, 150, 30)) + + } + + //fill incoming bubble + if true { + let image = generateImage(NSMakeSize(150, 30), rotatedContext: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + let data = messageBubbleImageModern(incoming: true, fillColor: palette.bubbleBackground_incoming, strokeColor: palette.bubbleBorder_incoming, neighbors: .none) + + let layer = CALayer() + layer.frame = NSMakeRect(0, 0, 150, 30) + layer.contentsScale = 2.0 + let imageSize = data.0.backingSize + let insets = data.1 + let halfPixelFudge: CGFloat = 0.49 + let otherPixelFudge: CGFloat = 0.02 + var contentsCenter: CGRect = NSMakeRect(0.0, 0.0, 1.0, 1.0); + if (insets.left > 0 || insets.right > 0) { + contentsCenter.origin.x = ((insets.left + halfPixelFudge) / imageSize.width); + contentsCenter.size.width = (imageSize.width - (insets.left + insets.right + 1.0) + otherPixelFudge) / imageSize.width; + } + if (insets.top > 0 || insets.bottom > 0) { + contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height); + contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.0) + otherPixelFudge) / imageSize.height; + } + layer.contentsGravity = .resize; + layer.contentsCenter = contentsCenter; + layer.contents = data.0 + + layer.render(in: ctx) + })! + + ctx.draw(image, in: NSMakeRect(10, 200, 150, 30)) + + } + CATransaction.commit() + + })! +} + +private func generateDialogVerify(background: NSColor, foreground: NSColor) -> CGImage { + return generateImage(NSMakeSize(24, 24), rotatedContext: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + let image = NSImage(named: "Icon_VerifyDialog")!.precomposed(foreground) + + ctx.setFillColor(background.cgColor) + ctx.fillEllipse(in: NSMakeRect(8, 8, size.width - 16, size.height - 16)) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! +} + +private func generatePollDeleteOption(_ color: NSColor) -> CGImage { + let image = NSImage(named: "Icon_PollDeleteOption")!.precomposed(color) + return generateImage(image.backingSize, contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + ctx.setFillColor(.white) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + ctx.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! +} + +private func generateStickerPackSelection(_ color: NSColor) -> CGImage { + return generateImage(NSMakeSize(35, 35), contextGenerator: { size, ctx in + ctx.interpolationQuality = .low + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, .cornerRadius) + ctx.setFillColor(color.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + })! +} + +private func generateHitActiveIcon(activeColor: NSColor, backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(12, 12), contextGenerator: { size, ctx in + ctx.interpolationQuality = .high + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, size.width / 2) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + ctx.setFillColor(activeColor.cgColor) + ctx.fillEllipse(in: NSMakeRect(2, 2, 8, 8)) + })! +} + +private func generateScamIcon(foregroundColor: NSColor, backgroundColor: NSColor) -> CGImage { + + let textNode = TextNode.layoutText(NSAttributedString.initialize(string: L10n.markScam, color: foregroundColor, font: .medium(9)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, 20), nil, false, .center) + + return generateImage(NSMakeSize(textNode.0.size.width + 8, 16), contextGenerator: { size, ctx in + ctx.interpolationQuality = .high + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + let borderPath = NSBezierPath(roundedRect: NSMakeRect(1, 1, size.width - 2, size.height - 2), xRadius: 2, yRadius: 2) + + ctx.setStrokeColor(foregroundColor.cgColor) + ctx.addPath(borderPath.cgPath) + ctx.closePath() + ctx.strokePath() + + let textRect = NSMakeRect((size.width - textNode.0.size.width) / 2, (size.height - textNode.0.size.height) / 2 + 1, textNode.0.size.width, textNode.0.size.height) + textNode.1.draw(textRect, in: ctx, backingScaleFactor: System.backingScale, backgroundColor: backgroundColor) + + })! +} + +private func generateScamIconReversed(foregroundColor: NSColor, backgroundColor: NSColor) -> CGImage { + + let textNode = TextNode.layoutText(NSAttributedString.initialize(string: L10n.markScam, color: foregroundColor, font: .medium(9)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, 20), nil, false, .center) + return generateImage(NSMakeSize(textNode.0.size.width + 8, 16), rotatedContext: { size, ctx in + ctx.interpolationQuality = .high + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + let borderPath = NSBezierPath(roundedRect: NSMakeRect(1, 1, size.width - 2, size.height - 2), xRadius: 2, yRadius: 2) + + ctx.setStrokeColor(foregroundColor.cgColor) + ctx.addPath(borderPath.cgPath) + ctx.closePath() + ctx.strokePath() + + let textRect = NSMakeRect((size.width - textNode.0.size.width) / 2, (size.height - textNode.0.size.height) / 2 + 1, textNode.0.size.width, textNode.0.size.height) + textNode.1.draw(textRect, in: ctx, backingScaleFactor: System.backingScale, backgroundColor: backgroundColor) + + })! +} + +private func generateVideoMessageChatCap(backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(200, 200), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + + ctx.setFillColor(.clear) + ctx.setBlendMode(.clear) + + let radius = size.width / 2 + + let center = NSMakePoint(100, 100) + + ctx.addArc(center: center, radius: radius - 0.54, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: false) + ctx.drawPath(using: .fill) + ctx.setBlendMode(.normal) +// CGContextAddArc(context, center.x, center.y, radius - 0.54, 0, 2 * M_PI, 0); +// CGContextDrawPath(context, kCGPathFill); +// CGContextSetBlendMode(context, kCGBlendModeNormal); + + + })! +} + +private func generateEditMessageMediaIcon(_ icon: CGImage, background: NSColor) -> CGImage { + return generateImage(NSMakeSize(icon.backingSize.width + 1, icon.backingSize.height + 1), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, size.width / 2) + + ctx.setFillColor(background.cgColor) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + ctx.draw(icon, in: imageRect) + + })! +} + +private func generatePlayerListAlbumPlaceholder(_ icon: CGImage?, background: NSColor, radius: CGFloat) -> CGImage { + return generateImage(NSMakeSize(40, 40), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, radius) + + ctx.setFillColor(background.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + + if let icon = icon { + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + ctx.draw(icon, in: imageRect) + } + + })! +} + +private func generateLocationPinIcon(_ background: NSColor) -> CGImage { + return generateImage(NSMakeSize(40, 40), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, size.width / 2) + + ctx.setFillColor(background.cgColor) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + let icon = #imageLiteral(resourceName: "Icon_LocationPin").precomposed(.white) + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + ctx.draw(icon, in: imageRect) + + })! +} + +private func generateChatTabSelected(_ color: NSColor, _ icon: CGImage) -> CGImage { + let main = #imageLiteral(resourceName: "Icon_TabChatList_Highlighted").precomposed(color) + return generateImage(main.backingSize, contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.draw(main, in: NSMakeRect(0, 0, size.width, size.height)) + + + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2) - 2, floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2) + 2, icon.backingSize.width, icon.backingSize.height) + ctx.draw(icon, in: imageRect) + + })! +} + + +private func generateTriangle(_ size: NSSize, color: NSColor) -> CGImage { + return generateImage(size, contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + ctx.beginPath() + ctx.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + ctx.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) + ctx.addLine(to: CGPoint(x: (rect.midX), y: rect.minY)) + ctx.closePath() + + ctx.setFillColor(color.cgColor) + ctx.fillPath() + })! +} + +private func generateLocationMapPinIcon(_ background: NSColor) -> CGImage { + return generateImage(NSMakeSize(40, 46), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + ctx.setFillColor(background.cgColor) + ctx.fillEllipse(in: NSMakeRect(0, 6, size.width, size.height - 6)) + + let icon = #imageLiteral(resourceName: "Icon_LocationPin").precomposed(.white) + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2) + 3, icon.backingSize.width, icon.backingSize.height) + ctx.draw(icon, in: imageRect) + + let triangle = generateTriangle(NSMakeSize(12, 10), color: background) + let triangleRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - triangle.backingSize.width) / 2), 0, triangle.backingSize.width, triangle.backingSize.height) + + ctx.draw(triangle, in: triangleRect) + + })! +} + +private func generateLockerBody(_ color: NSColor, backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(12.5, 12.5), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + + ctx.setFillColor(backgroundColor.cgColor) + ctx.fillEllipse(in: NSMakeRect(0, 0, size.width, size.height)) + + ctx.setFillColor(color.cgColor) + ctx.setStrokeColor(color.cgColor) + ctx.setLineWidth(1.0) + ctx.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + ctx.fillEllipse(in: NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - 2)/2), floorToScreenPixels(System.backingScale, (size.height - 2)/2), 2, 2)) + + })! +} +private func generateLockerHead(_ color: NSColor, backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(10, 20), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.round(size, size.width / 2) + + ctx.setFillColor(color.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + + ctx.setFillColor(backgroundColor.cgColor) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2, height: size.width - 2))) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: size.height - size.width + 1), size: CGSize(width: size.width - 2, height: size.width - 2))) + ctx.fill(NSMakeRect(1.0, 0, size.width - 1, 14)) + + ctx.clear(NSMakeRect(0, 0, size.width, 3)) + + + + })! +} private func generateChatMention(backgroundColor: NSColor, border: NSColor, foregroundColor: NSColor) -> CGImage { return generateImage(NSMakeSize(38, 38), contextGenerator: { size, ctx in ctx.clear(CGRect(origin: CGPoint(), size: size)) ctx.setFillColor(backgroundColor.cgColor) - ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) - ctx.setLineWidth(1.0) - ctx.setStrokeColor(border.withAlphaComponent(0.7).cgColor) - ctx.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + ctx.setLineWidth(1.0) + ctx.setStrokeColor(border.withAlphaComponent(0.7).cgColor) + // ctx.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + + let icon = #imageLiteral(resourceName: "Icon_ChatMention").precomposed(foregroundColor) + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + + ctx.draw(icon, in: imageRect) + })! +} + +private func generateChatFailed(backgroundColor: NSColor, border: NSColor, foregroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(38, 38), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + ctx.setLineWidth(1.0) + ctx.setStrokeColor(border.withAlphaComponent(0.7).cgColor) + //ctx.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + + let icon = NSImage(named: "Icon_DialogSendingError")!.precomposed(foregroundColor) + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + + ctx.draw(icon, in: imageRect) + })! +} + + +private func generateSettingsIcon(_ icon: CGImage) -> CGImage { + return generateImage(icon.backingSize, contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setFillColor(.white) + ctx.fill(CGRect(origin: CGPoint(x: 2, y: 2), size: NSMakeSize(size.width - 4, size.height - 4))) + ctx.draw(icon, in: CGRect(origin: CGPoint(), size: size)) + })! +} + + +private func generateSettingsActiveIcon(_ icon: CGImage, background: NSColor) -> CGImage { + return generateImage(icon.backingSize, contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setFillColor(background.cgColor) + ctx.fill(CGRect(origin: CGPoint(x: 2, y: 2), size: NSMakeSize(size.width - 4, size.height - 4))) + ctx.draw(icon, in: CGRect(origin: CGPoint(), size: size)) + })! +} + +private func generateStickersEmptySearch(color: NSColor) -> CGImage { + return generateImage(NSMakeSize(100, 100), contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.clear(rect) + + let icon = #imageLiteral(resourceName: "Icon_EmptySearchResults").precomposed(color) + let imageSize = icon.backingSize.fitted(size) + ctx.draw(icon, in: rect.focus(imageSize)) + }, scale: 1.0)! +} + +private func generateAlertCheckBoxSelected(backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(14, 14), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.setFillColor(backgroundColor.cgColor) + ctx.round(size, 2) + ctx.fill(rect) - let icon = #imageLiteral(resourceName: "Icon_ChatMention").precomposed(foregroundColor) - let imageRect = NSMakeRect(floorToScreenPixels((size.width - icon.backingSize.width) / 2), floorToScreenPixels((size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + let icon = #imageLiteral(resourceName: "Icon_AlertCheckBoxMark").precomposed() + ctx.draw(icon, in: NSMakeRect((rect.width - icon.backingSize.width) / 2, (rect.height - icon.backingSize.height) / 2, icon.backingSize.width, icon.backingSize.height)) - ctx.draw(icon, in: imageRect) + })! +} +private func generateAlertCheckBoxUnselected(border: NSColor) -> CGImage { + return generateImage(NSMakeSize(14, 14), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.setStrokeColor(border.cgColor) + ctx.setLineWidth(3.0) + ctx.round(size, 2) + ctx.stroke(rect) + })! +} + + +private func generateTransparentBackground() -> CGImage { + return generateImage(NSMakeSize(20, 20), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setFillColor(NSColor(0xcbcbcb).cgColor) + ctx.fill(NSMakeRect(0, 0, 10, 10)) + ctx.setFillColor(NSColor(0xfdfdfd).cgColor) + ctx.fill(NSMakeRect(10, 0, 10, 10)) + + ctx.setFillColor(NSColor(0xfdfdfd).cgColor) + ctx.fill(NSMakeRect(0, 10, 10, 10)) + ctx.setFillColor(NSColor(0xcbcbcb).cgColor) + ctx.fill(NSMakeRect(10, 10, 10, 10)) + + })! +} + +private func generateLottieTransparentBackground() -> CGImage { + return generateImage(NSMakeSize(10, 10), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setFillColor(.black) + ctx.fill(NSMakeRect(0, 0, 5, 5)) + ctx.setFillColor(NSColor.lightGray.cgColor) + ctx.fill(NSMakeRect(5, 0, 5, 5)) + + ctx.setFillColor(NSColor.lightGray.cgColor) + ctx.fill(NSMakeRect(0, 5, 5, 5)) + ctx.setFillColor(.black) + ctx.fill(NSMakeRect(5, 5, 5, 5)) + + })! +} + +private func generateIVAudioPlay(color: NSColor) -> CGImage { + return generateImage(NSMakeSize(40, 40), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setStrokeColor(color.cgColor) + ctx.setLineWidth(3) + ctx.strokeEllipse(in: NSMakeRect(2, 2, size.width - 4, size.height - 4)) + let icon = #imageLiteral(resourceName: "Icon_ChatMusicPlay").precomposed(color) + + ctx.draw(icon, in: NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height)) + + })! +} + +private func generateIVAudioPause(color: NSColor) -> CGImage { + return generateImage(NSMakeSize(40, 40), contextGenerator: { size, ctx in + ctx.clear(CGRect(origin: CGPoint(), size: size)) + ctx.setStrokeColor(color.cgColor) + ctx.setLineWidth(3) + ctx.strokeEllipse(in: NSMakeRect(2, 2, size.width - 4, size.height - 4)) + let icon = #imageLiteral(resourceName: "Icon_ChatMusicPause").precomposed(color) + ctx.draw(icon, in: NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height)) })! } @@ -34,12 +827,43 @@ private func generateBadgeMention(backgroundColor: NSColor, foregroundColor: NSC ctx.setFillColor(backgroundColor.cgColor) ctx.fill(NSMakeRect(0, 0, size.width, size.height)) let icon = #imageLiteral(resourceName: "Icon_ChatListMention").precomposed(foregroundColor, flipVertical: true) - let imageRect = NSMakeRect(floorToScreenPixels((size.width - icon.backingSize.width) / 2), floorToScreenPixels((size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) + let imageRect = NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height) ctx.draw(icon, in: imageRect) })! } +private func generateChatGroupToggleSelected(foregroundColor: NSColor, backgroundColor: NSColor) -> CGImage { + let icon = #imageLiteral(resourceName: "Icon_Check").precomposed(foregroundColor) + return generateImage(NSMakeSize(icon.size.width + 2, icon.size.height + 2), contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.round(size, size.width/2) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + let imageRect = NSMakeRect((size.width - icon.size.width) / 2, (size.height - icon.size.height) / 2, icon.size.width, icon.size.height) + ctx.draw(icon, in: imageRect) + }, scale: 1)! +} + +private func generateChatGroupToggleUnselected(foregroundColor: NSColor, backgroundColor: NSColor) -> CGImage { + let icon = #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed(foregroundColor) + return generateImage(NSMakeSize(icon.size.width, icon.size.height), contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.round(size, size.width/2) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + let imageRect = NSMakeRect((size.width - icon.size.width) / 2, (size.height - icon.size.height) / 2, icon.size.width, icon.size.height) + ctx.draw(icon, in: imageRect) + }, scale: 1)! +} +func generateAvatarPlaceholder(foregroundColor: NSColor, size: NSSize) -> CGImage { + return generateImage(size, contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.round(size, size.width/2) + ctx.setFillColor(foregroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + })! +} private func deleteItemIcon(_ color: NSColor) -> CGImage { return generateImage(NSMakeSize(24,24), contextGenerator: { (size, ctx) in @@ -78,29 +902,26 @@ private func generateSendingFrame(_ color: NSColor) -> CGImage { ctx.strokeEllipse(in: NSMakeRect(1.0, 1.0,size.width - 2,size.height - 2)) })! } -private func generateSendingHour(_ color: NSColor) -> CGImage { - return generateImage(NSMakeSize(12, 12), contextGenerator: { size, ctx in - ctx.clear(NSMakeRect(0, 0, size.width, size.height)) - ctx.setFillColor(color.cgColor) - ctx.fill(NSMakeRect(5,5,4,1.5)) - })! -} -private func generateSendingMin(_ color: NSColor) -> CGImage { - return generateImage(NSMakeSize(12, 12), contextGenerator: { size, ctx in - ctx.clear(NSMakeRect(0, 0, size.width, size.height)) - ctx.setFillColor(color.cgColor) - ctx.fill(NSMakeRect(5, 5, 4, 1)) +private func generateClockMinImage(_ color: NSColor) -> CGImage { + return generateImage(CGSize(width: 10, height: 10), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + let strokeWidth: CGFloat = 1 + context.fill(CGRect(x: (10 - strokeWidth) / 2.0, y: (10 - strokeWidth) / 2.0, width: 10 / 2.0 - strokeWidth, height: strokeWidth)) })! } -private func generateChatScrolldownImage(backgroundColor: NSColor, borderColor: NSColor, arrowColor: NSColor) -> CGImage { + +private func generateChatScrolldownImage(backgroundColor: NSColor, borderColor: NSColor, arrowColor: NSColor) -> CGImage { return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(backgroundColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) context.setLineWidth(1.0) - context.setStrokeColor(borderColor.withAlphaComponent(0.7).cgColor) - context.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + if borderColor != .clear { + context.setStrokeColor(borderColor.withAlphaComponent(0.7).cgColor) + context.strokeEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: size.width - 2.0, height: size.height - 2.0))) + } context.setStrokeColor(arrowColor.cgColor) context.setLineWidth(1.0) @@ -113,6 +934,55 @@ private func generateChatScrolldownImage(backgroundColor: NSColor, borderColor: })! } +private func generateConfirmDeleteMessagesAccessory(backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(50, 50), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, size.height / 2) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(rect) + let icon = #imageLiteral(resourceName: "Icon_ConfirmDeleteMessagesAccessory").precomposed() + let point = NSMakePoint((rect.width - icon.backingSize.width) / 2, (rect.height - icon.backingSize.height) / 2) + ctx.draw(icon, in: NSMakeRect(point.x, point.y, icon.backingSize.width, icon.backingSize.height)) + })! +} + +private func generateConfirmPinAccessory(backgroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(50, 50), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, size.height / 2) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(rect) + let icon = #imageLiteral(resourceName: "Icon_ConfirmPinAccessory").precomposed() + let point = NSMakePoint((rect.width - icon.backingSize.width) / 2, (rect.height - icon.backingSize.height) / 2) + ctx.draw(icon, in: NSMakeRect(point.x, point.y, icon.backingSize.width, icon.backingSize.height)) + })! +} + +private func generateConfirmDeleteChatAccessory(backgroundColor: NSColor, foregroundColor: NSColor) -> CGImage { + return generateImage(NSMakeSize(34, 34), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, size.height / 2) + + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(rect) + + ctx.setFillColor(foregroundColor.cgColor) + ctx.fillEllipse(in: NSMakeRect(2, 2, size.width - 4, size.height - 4)) + let icon = #imageLiteral(resourceName: "Icon_ConfirmDeleteChatAccessory").precomposed() + let point = NSMakePoint((rect.width - icon.backingSize.width) / 2, (rect.height - icon.backingSize.height) / 2) + ctx.draw(icon, in: NSMakeRect(point.x, point.y, icon.backingSize.width, icon.backingSize.height)) + })! +} + + + +/* + + */ + private func generateRecentActionsTriangle(_ color: NSColor) -> CGImage { return generateImage(NSMakeSize(10, 8), contextGenerator: { (size, ctx) in let bounds = NSMakeRect(0, 0, size.width, size.height) @@ -130,7 +1000,7 @@ private func generateRecentActionsTriangle(_ color: NSColor) -> CGImage { } var blueActionButton:ControlStyle { - return ControlStyle(font: NSFont.normal(.title), foregroundColor: theme.colors.blueUI) + return ControlStyle(font: NSFont.normal(.title), foregroundColor: theme.colors.accent) } var redActionButton:ControlStyle { return ControlStyle(font: .normal(.title), foregroundColor: theme.colors.redUI) @@ -138,7 +1008,7 @@ var redActionButton:ControlStyle { -class ActivitiesTheme { +struct ActivitiesTheme : Equatable { let text:[CGImage] let uploading:[CGImage] let recording:[CGImage] @@ -151,6 +1021,10 @@ class ActivitiesTheme { self.textColor = textColor self.backgroundColor = backgroundColor } + + static func ==(lhs: ActivitiesTheme, rhs: ActivitiesTheme) -> Bool { + return lhs.textColor.argb == rhs.textColor.argb && lhs.backgroundColor.argb == rhs.backgroundColor.argb + } } @@ -177,264 +1051,6 @@ final class TelegramTabBarTheme { } - - -struct TelegramIconsTheme { - let dialogMuteImage: CGImage - let dialogMuteImageSelected: CGImage - let outgoingMessageImage: CGImage - let readMessageImage: CGImage - let outgoingMessageImageSelected: CGImage - let readMessageImageSelected: CGImage - let sendingImage: CGImage - let sendingImageSelected: CGImage - let secretImage:CGImage - let secretImageSelected: CGImage - let pinnedImage: CGImage - let pinnedImageSelected: CGImage - let verifiedImage: CGImage - let verifiedImageSelected: CGImage - let errorImage: CGImage - let errorImageSelected: CGImage - - let chatSearch: CGImage - let chatCall: CGImage - let chatActions: CGImage - - let chatOutgoingFailedCall: CGImage - let chatIncomingFailedCall: CGImage - let chatOutgoingCall: CGImage - let chatIncomingCall: CGImage - let chatFallbackCall: CGImage - - let chatToggleSelected: CGImage - let chatToggleUnselected: CGImage - let chatShare: CGImage - let chatMusicPlay: CGImage - let chatMusicPause: CGImage - - let composeNewChat:CGImage - let composeNewChatActive: CGImage - let composeNewGroup:CGImage - let composeNewSecretChat: CGImage - let composeNewChannel: CGImage - - let contactsNewContact: CGImage - - let chatReadMark1: CGImage - let chatReadMark2: CGImage - let sentFailed: CGImage - let chatChannelViews:CGImage - - let chatNavigationBack: CGImage - - let peerInfoAddMember: CGImage - - - let chatSearchUp: CGImage - let chatSearchUpDisabled: CGImage - let chatSearchDown: CGImage - let chatSearchDownDisabled: CGImage - let chatSearchCalendar: CGImage - - let dismissAccessory: CGImage - - let chatScrollUp: CGImage - let chatScrollUpActive: CGImage - - - let audioPlayerPlay: CGImage - let audioPlayerPause: CGImage - let audioPlayerNext: CGImage - let audioPlayerPrev: CGImage - let auduiPlayerDismiss: CGImage - let audioPlayerRepeat: CGImage - let audioPlayerRepeatActive: CGImage - - let audioPlayerLockedPlay: CGImage - let audioPlayerLockedNext: CGImage - let audioPlayerLockedPrev: CGImage - - - - let chatSendMessage: CGImage - let chatRecordVoice: CGImage - let chatEntertainment: CGImage - let chatInlineDismiss: CGImage - let chatActiveReplyMarkup: CGImage - let chatDisabledReplyMarkup: CGImage - let chatSecretTimer: CGImage - - let chatForwardMessagesActive: CGImage - let chatForwardMessagesInactive: CGImage - let chatDeleteMessagesActive: CGImage - let chatDeleteMessagesInactive: CGImage - - let generalNext: CGImage - let generalSelect: CGImage - - - let chatVoiceRecording: CGImage - let chatVideoRecording: CGImage - let chatRecord: CGImage - - let deleteItem: CGImage - let deleteItemDisabled: CGImage - - let chatAttach: CGImage - let chatAttachFile: CGImage - let chatAttachPhoto: CGImage - let chatAttachCamera: CGImage - let chatAttachLocation: CGImage - - let mediaEmptyShared: CGImage - let mediaEmptyFiles: CGImage - let mediaEmptyMusic: CGImage - let mediaEmptyLinks: CGImage - - let mediaDropdown: CGImage - - let stickersAddFeatured: CGImage - let stickersAddedFeatured: CGImage - let stickersRemove: CGImage - - let peerMediaDownloadFileStart: CGImage - let peerMediaDownloadFilePause: CGImage - - let stickersShare: CGImage - - let emojiRecentTab: CGImage - let emojiSmileTab: CGImage - let emojiNatureTab: CGImage - let emojiFoodTab: CGImage - let emojiSportTab: CGImage - let emojiCarTab: CGImage - let emojiObjectsTab: CGImage - let emojiSymbolsTab: CGImage - let emojiFlagsTab: CGImage - - let emojiRecentTabActive: CGImage - let emojiSmileTabActive: CGImage - let emojiNatureTabActive: CGImage - let emojiFoodTabActive: CGImage - let emojiSportTabActive: CGImage - let emojiCarTabActive: CGImage - let emojiObjectsTabActive: CGImage - let emojiSymbolsTabActive: CGImage - let emojiFlagsTabActive: CGImage - - let stickerBackground: CGImage - let stickerBackgroundActive: CGImage - let stickersTabRecent: CGImage - let stickersTabGIF: CGImage - - let chatSendingFrame: CGImage - let chatSendingHour: CGImage - let chatSendingMin: CGImage - let chatActionUrl: CGImage - - let callInlineDecline: CGImage - let callInlineMuted: CGImage - let callInlineUnmuted: CGImage - let eventLogTriangle: CGImage - let channelIntro: CGImage - let chatFileThumb: CGImage - let chatSecretThumb: CGImage - let chatMapPin: CGImage - let chatSecretTitle: CGImage - let emptySearch: CGImage - let calendarBack: CGImage - let calendarNext: CGImage - let calendarBackDisabled: CGImage - let calendarNextDisabled: CGImage - let newChatCamera: CGImage - let peerInfoVerify: CGImage - let peerInfoCall: CGImage - let callOutgoing: CGImage - let recentDismiss: CGImage - let recentDismissActive: CGImage - let webgameShare: CGImage - - let chatSearchCancel: CGImage - let chatSearchFrom: CGImage - - let callWindowDecline: CGImage - let callWindowAccept: CGImage - let callWindowMute: CGImage - let callWindowUnmute: CGImage - let callWindowClose: CGImage - let callWindowDeviceSettings: CGImage - let callWindowCancel: CGImage - - let chatActionEdit: CGImage - let chatActionInfo: CGImage - let chatActionMute: CGImage - let chatActionUnmute: CGImage - let chatActionClearHistory: CGImage - - let dismissPinned: CGImage - let chatActionsActive: CGImage - let chatEntertainmentSticker: CGImage - let chatEmpty: CGImage - let stickerPackClose: CGImage - let stickerPackDelete: CGImage - - let modalShare: CGImage - let modalClose: CGImage - - let ivChannelJoined: CGImage - let chatListMention: CGImage - let chatListMentionActive: CGImage - - let chatMention: CGImage - let chatMentionActive: CGImage - - let sliderControl: CGImage - let sliderControlActive: CGImage - - let stickersTabFave: CGImage - let chatInstantView: CGImage - - let instantViewShare: CGImage - let instantViewActions: CGImage - let instantViewActionsActive: CGImage - let instantViewSafari: CGImage - let instantViewBack: CGImage - let instantViewCheck: CGImage - - let groupStickerNotFound: CGImage - - let settingsAskQuestion: CGImage - let settingsBio: CGImage - let settingsEditInfo: CGImage - let settingsFaq: CGImage - let settingsGeneral: CGImage - let settingsLanguage: CGImage - let settingsNotifications: CGImage - let settingsPhoneNumber: CGImage - let settingsSecurity: CGImage - let settingsStickers: CGImage - let settingsStorage: CGImage - let settingsUsername: CGImage - - let generalCheck: CGImage - let settingsAbout: CGImage - let settingsLogout: CGImage - - let fastSettingsLock: CGImage - let fastSettingsDark: CGImage - let fastSettingsSunny: CGImage - let fastSettingsMute: CGImage - let fastSettingsUnmute: CGImage - - let chatRecordVideo: CGImage - - let inputChannelMute: CGImage - let inputChannelUnmute: CGImage - - let changePhoneNumberIntro: CGImage -} - final class TelegramChatListTheme { let selectedBackgroundColor: NSColor let singleLayoutSelectedBackgroundColor: NSColor @@ -463,6 +1079,9 @@ final class TelegramChatListTheme { init(selectedBackgroundColor: NSColor, singleLayoutSelectedBackgroundColor: NSColor, activeDraggingBackgroundColor: NSColor, pinnedBackgroundColor: NSColor, contextMenuBackgroundColor: NSColor, textColor: NSColor, grayTextColor: NSColor, secretChatTextColor: NSColor, peerTextColor: NSColor, activityColor: NSColor, activitySelectedColor: NSColor, activityContextMenuColor: NSColor, activityPinnedColor: NSColor, badgeTextColor: NSColor, badgeBackgroundColor: NSColor, badgeSelectedTextColor: NSColor, badgeSelectedBackgroundColor: NSColor, badgeMutedTextColor: NSColor, badgeMutedBackgroundColor: NSColor) { + + + self.selectedBackgroundColor = selectedBackgroundColor self.singleLayoutSelectedBackgroundColor = singleLayoutSelectedBackgroundColor self.activeDraggingBackgroundColor = activeDraggingBackgroundColor @@ -488,35 +1107,603 @@ final class TelegramChatListTheme { } } -extension ColorPallete { - init(_ settings: ThemePalleteSettings) { - self.init(background: settings.background, text: settings.text, grayText: settings.grayText, link: settings.link, blueUI: settings.blueUI, redUI: settings.redUI, greenUI: settings.greenUI, blackTransparent: settings.blackTransparent, grayTransparent: settings.grayTransparent, grayUI: settings.grayUI, darkGrayText: settings.darkGrayText, blueText: settings.blueText, blueSelect: settings.blueSelect, selectText: settings.selectText, blueFill: settings.blueFill, border: settings.border, grayBackground: settings.grayBackground, grayForeground: settings.grayForeground, grayIcon: settings.grayIcon, blueIcon: settings.blueIcon, badgeMuted: settings.badgeMuted, badge: settings.badge, indicatorColor: settings.indicatorColor, selectMessage: settings.selectMessage) + + +extension WallpaperSettings { + func withUpdatedBlur(_ blur: Bool) -> WallpaperSettings { + return WallpaperSettings(blur: blur, motion: self.motion, color: self.color, intensity: self.intensity) + } + func withUpdatedColor(_ color: UInt32?) -> WallpaperSettings { + return WallpaperSettings(blur: self.blur, motion: self.motion, color: color, intensity: self.intensity) + } + + func isSemanticallyEqual(to other: WallpaperSettings) -> Bool { + return self.color == other.color && self.intensity == other.intensity + } +} + +enum Wallpaper : Equatable, PostboxCoding { + case builtin + case color(UInt32) + case gradient(UInt32, UInt32, Int32?) + case image([TelegramMediaImageRepresentation], settings: WallpaperSettings) + case file(slug: String, file: TelegramMediaFile, settings: WallpaperSettings, isPattern: Bool) + case none + case custom(TelegramMediaImageRepresentation, blurred: Bool) + + init(_ wallpaper: TelegramWallpaper) { + switch wallpaper { + case .builtin: + self = .builtin + case let .color(color): + self = .color(color) + case let .image(image, settings): + self = .image(image, settings: settings) + case let .file(values): + self = .file(slug: values.slug, file: values.file, settings: values.settings, isPattern: values.isPattern) + case let .gradient(top, bottom, settings): + self = .gradient(top, bottom, settings.rotation) + } + } + + static func ==(lhs: Wallpaper, rhs: Wallpaper) -> Bool { + switch lhs { + case .builtin: + if case .builtin = rhs { + return true + } else { + return false + } + case let .color(value): + if case .color(value) = rhs { + return true + } else { + return false + } + case let .gradient(top, bottom, rotation): + if case .gradient(top, bottom, rotation) = rhs { + return true + } else { + return false + } + case let .image(reps, settings): + if case .image(reps, settings: settings) = rhs { + return true + } else { + return false + } + case let .file(slug, lhsFile, settings, isPattern): + if case .file(slug, let rhsFile, settings, isPattern) = rhs, lhsFile.isSemanticallyEqual(to: rhsFile) { + return true + } else { + return false + } + case let .custom(rep, blurred): + if case .custom(rep, blurred) = rhs { + return true + } else { + return false + } + case .none: + if case .none = rhs { + return true + } else { + return false + } + } + } + + var wallpaperUrl: String? { + switch self { + case .builtin: + return "builtin" + case let .file(slug, _, settings, isPattern): + var options: [String] = [] + if settings.blur { + options.append("mode=blur") + } + if isPattern { + if let pattern = settings.color { + var color = NSColor(argb: pattern).withAlphaComponent(1.0).hexString.lowercased() + color = String(color[color.index(after: color.startIndex) ..< color.endIndex]) + options.append("bg_color=\(color)") + } + if let intensity = settings.intensity { + options.append("intensity=\(intensity)") + } + } + var optionsString = "" + if !options.isEmpty { + optionsString = "?\(options.joined(separator: "&"))" + } + return "https://t.me/bg/\(slug)\(optionsString)" + default: + return nil + } + } + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("v", orElse: 0) { + case 0: + self = .builtin + case 1: + self = .color(UInt32(bitPattern: decoder.decodeInt32ForKey("c", orElse: 0))) + case 2: + let settings = decoder.decodeObjectForKey("settings", decoder: { WallpaperSettings(decoder: $0) }) as? WallpaperSettings ?? WallpaperSettings() + self = .image(decoder.decodeObjectArrayWithDecoderForKey("i"), settings: settings) + case 3: + let settings = decoder.decodeObjectForKey("settings", decoder: { WallpaperSettings(decoder: $0) }) as? WallpaperSettings ?? WallpaperSettings() + self = .file(slug: decoder.decodeStringForKey("slug", orElse: ""), file: decoder.decodeObjectForKey("file", decoder: { TelegramMediaFile(decoder: $0) }) as! TelegramMediaFile, settings: settings, isPattern: decoder.decodeInt32ForKey("p", orElse: 0) == 1) + case 4: + self = .custom(decoder.decodeObjectForKey("rep", decoder: { TelegramMediaImageRepresentation(decoder: $0) }) as! TelegramMediaImageRepresentation, blurred: decoder.decodeInt32ForKey("b", orElse: 0) == 1) + case 5: + self = .none + case 6: + self = .gradient(UInt32(bitPattern: decoder.decodeInt32ForKey("ct", orElse: 0)), UInt32(bitPattern: decoder.decodeInt32ForKey("cb", orElse: 0)), decoder.decodeOptionalInt32ForKey("cr")) + + default: + assertionFailure() + self = .color(0xffffff) + } + } + + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .builtin: + encoder.encodeInt32(0, forKey: "v") + case let .color(color): + encoder.encodeInt32(1, forKey: "v") + encoder.encodeInt32(Int32(bitPattern: color), forKey: "c") + case let .image(representations, settings): + encoder.encodeInt32(2, forKey: "v") + encoder.encodeObjectArray(representations, forKey: "i") + encoder.encodeObject(settings, forKey: "settings") + case let .file(slug, file, settings, isPattern): + encoder.encodeInt32(3, forKey: "v") + encoder.encodeString(slug, forKey: "slug") + encoder.encodeObject(file, forKey: "file") + encoder.encodeObject(settings, forKey: "settings") + encoder.encodeInt32(isPattern ? 1 : 0, forKey: "p") + case let .custom(resource, blurred): + encoder.encodeInt32(4, forKey: "v") + encoder.encodeObject(resource, forKey: "rep") + encoder.encodeInt32(blurred ? 1 : 0, forKey: "b") + case .none: + encoder.encodeInt32(5, forKey: "v") + case let .gradient(top, bottom, rotation): + encoder.encodeInt32(6, forKey: "v") + encoder.encodeInt32(Int32(bitPattern: top), forKey: "ct") + encoder.encodeInt32(Int32(bitPattern: bottom), forKey: "cb") + if let rotation = rotation { + encoder.encodeInt32(rotation, forKey: "cr") + } else { + encoder.encodeNil(forKey: "cr") + } + } + } + + func withUpdatedBlurrred(_ blurred: Bool) -> Wallpaper { + switch self { + case .builtin: + return self + case .color: + return self + case .gradient: + return self + case let .image(representations, settings): + return .image(representations, settings: WallpaperSettings(blur: blurred, motion: settings.motion, color: settings.color, bottomColor: settings.bottomColor, intensity: settings.intensity, rotation: settings.rotation)) + case let .file(values): + return .file(slug: values.slug, file: values.file, settings: WallpaperSettings(blur: blurred, motion: settings.motion, color: settings.color, bottomColor: settings.bottomColor, intensity: settings.intensity, rotation: settings.rotation), isPattern: values.isPattern) + case let .custom(path, _): + return .custom(path, blurred: blurred) + case .none: + return self + } + } + + func withUpdatedSettings(_ settings: WallpaperSettings) -> Wallpaper { + switch self { + case .builtin: + return self + case .color: + return self + case .gradient: + return self + case let .image(representations, _): + return .image(representations, settings: settings) + case let .file(values): + return .file(slug: values.slug, file: values.file, settings: settings, isPattern: values.isPattern) + case .custom: + return self + case .none: + return self + } + } + + var isBlurred: Bool { + switch self { + case .builtin: + return false + case .color: + return false + case .gradient: + return false + case let .image(_, settings): + return settings.blur + case let .file(values): + return values.settings.blur + case let .custom(_, blurred): + return blurred + case .none: + return false + } + } + + var settings: WallpaperSettings { + switch self { + case let .image(_, settings): + return settings + case let .file(values): + return values.settings + case let .color(t): + return WallpaperSettings(color: t) + case let .gradient(t, b, r): + return WallpaperSettings(color: t, bottomColor: b, rotation: r) + default: + return WallpaperSettings() + } + } + + func isSemanticallyEqual(to other: Wallpaper) -> Bool { + switch self { + case .none: + return other == self + case .builtin: + return other == self + case .color: + return other == self + case .gradient: + return other == self + case let .custom(resource, _): + if case .custom(resource, _) = other { + return true + } else { + return false + } + case let .image(representations, _): + if case .image(representations, _) = other { + return true + } else { + return false + } + case let .file(values): + if case .file(slug: values.slug, _, _, _) = other { + return true + } else { + return false + } + } } } -extension TelegramPresentationTheme { - var appearance: NSAppearance? { - return dark ? NSAppearance(named: NSAppearance.Name.vibrantDark) : NSAppearance(named: NSAppearance.Name.vibrantLight) +func getAverageColor(_ image: NSImage) -> NSColor { + let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false) + context.withFlippedContext({ [weak image] context in + if let cgImage = image { + context.draw(cgImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) + } + }) + var color = context.colorAt(CGPoint()) + + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 +// color = color.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + saturation = min(1.0, saturation + 0.1 + 0.1 * (1.0 - saturation)) + brightness = max(0.0, brightness * 0.65) + alpha = 0.5 + color = NSColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) + + return color +} + +private func getAverageColor(_ color: NSColor) -> NSColor { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + let color = color.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + saturation = min(1.0, saturation + 0.1 + 0.1 * (1.0 - saturation)) + brightness = max(0.0, brightness * 0.65) + alpha = 0.5 + + return NSColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) +} + +func generateBackgroundMode(_ wallpaper: Wallpaper, palette: ColorPalette, maxSize: NSSize = NSMakeSize(1040, 1580)) -> TableBackgroundMode { + #if !SHARE + var backgroundMode: TableBackgroundMode + switch wallpaper { + case .builtin: + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + case let.color(color): + backgroundMode = .color(color: NSColor(color)) + case let .gradient(top, bottom, rotation): + backgroundMode = .gradient(top: NSColor(argb: top).withAlphaComponent(1.0), bottom: NSColor(argb: bottom).withAlphaComponent(1.0), rotation: rotation) + case let .image(representation, settings): + if let resource = largestImageRepresentation(representation)?.resource, let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(resource, settings: settings))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } + + case let .file(_, file, settings, _): + if let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(file.resource, settings: settings))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } + case .none: + backgroundMode = .color(color: palette.chatBackground) + case let .custom(representation, blurred): + if let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(representation.resource, settings: WallpaperSettings(blur: blurred)))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } } + return backgroundMode + #else + return .plain + #endif } class TelegramPresentationTheme : PresentationTheme { let chatList:TelegramChatListTheme + #if !SHARE + let chat: TelegramChatColors + #endif + let cloudTheme: TelegramTheme? let tabBar:TelegramTabBarTheme let icons: TelegramIconsTheme - let dark: Bool + let bubbled: Bool + let wallpaper: ThemeWallpaper + + + + private var _chatReadMarkServiceOverlayBubble1: CGImage? + private var _chatReadMarkServiceOverlayBubble2: CGImage? + var chatReadMarkServiceOverlayBubble1: CGImage { + if let icon = _chatReadMarkServiceOverlayBubble1 { + return icon + } else { + let new = NSImage(named: "Icon_MessageCheckMark1")!.precomposed(self.chatServiceItemTextColor) + _chatReadMarkServiceOverlayBubble1 = new + return new + } + } + var chatReadMarkServiceOverlayBubble2: CGImage { + if let icon = _chatReadMarkServiceOverlayBubble2 { + return icon + } else { + let new = NSImage(named: "Icon_MessageCheckmark2")!.precomposed(self.chatServiceItemTextColor) + _chatReadMarkServiceOverlayBubble2 = new + return new + } + } + + private var _chatSendingOverlayServiceFrame: CGImage? + private var _chatSendingOverlayServiceHour: CGImage? + private var _chatSendingOverlayServiceMin: CGImage? + var chatSendingOverlayServiceFrame: CGImage { + if let icon = _chatSendingOverlayServiceFrame { + return icon + } else { + let new = generateSendingFrame(self.chatServiceItemTextColor) + _chatSendingOverlayServiceFrame = new + return new + } + } + var chatSendingOverlayServiceHour: CGImage { + if let icon = _chatSendingOverlayServiceHour { + return icon + } else { + let new = generateClockMinImage(self.chatServiceItemTextColor) + _chatSendingOverlayServiceHour = new + return new + } + } + var chatSendingOverlayServiceMin: CGImage { + if let icon = _chatSendingOverlayServiceMin { + return icon + } else { + let new = generateClockMinImage(self.chatServiceItemTextColor) + _chatSendingOverlayServiceMin = new + return new + } + } + + private var _chatChannelViewsOverlayServiceBubble: CGImage? + var chatChannelViewsOverlayServiceBubble: CGImage { + if let icon = _chatChannelViewsOverlayServiceBubble { + return icon + } else { + let new = #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(self.chatServiceItemTextColor, flipVertical: true) + _chatChannelViewsOverlayServiceBubble = new + return new + } + } + + private var _chat_like_inside_bubble_service: CGImage? + var chat_like_inside_bubble_service: CGImage { + if let icon = _chat_like_inside_bubble_service { + return icon + } else { + + let new = NSImage(named: "Icon_Like_MessageInside")!.precomposed(self.chatServiceItemTextColor, flipVertical: true) + _chat_like_inside_bubble_service = new + return new + } + } + private var _chat_like_inside_empty_bubble_service: CGImage? + var chat_like_inside_empty_bubble_service: CGImage { + if let icon = _chat_like_inside_empty_bubble_service { + return icon + } else { + let new = NSImage(named: "Icon_Like_MessageInsideEmpty")!.precomposed(self.chatServiceItemTextColor, flipVertical: true) + _chat_like_inside_empty_bubble_service = new + return new + } + } + + private var _chatServiceItemColor: NSColor? + var chatServiceItemColor: NSColor { + if let value = _chatServiceItemColor { + return value + } else { + let chatServiceItemColor: NSColor + if bubbled { + switch backgroundMode { + case let .background(image): + chatServiceItemColor = getAverageColor(image) + case let .color(color): + if color != colors.background { + return getAverageColor(color) + } else { + return color + } + case let .gradient(top, bottom, rotation): + if let blended = top.blended(withFraction: 0.5, of: bottom) { + return getAverageColor(blended) + } else { + return getAverageColor(top) + } + case let .tiled(image): + chatServiceItemColor = getAverageColor(image) + case .plain: + chatServiceItemColor = colors.chatBackground + } + } else { + chatServiceItemColor = colors.chatBackground + } + + self._chatServiceItemColor = chatServiceItemColor + return chatServiceItemColor + } + } + private var _chatServiceItemTextColor: NSColor? + var chatServiceItemTextColor: NSColor { + if let value = _chatServiceItemTextColor { + return value + } else { + let chatServiceItemTextColor: NSColor + if bubbled { + switch backgroundMode { + case .background: + chatServiceItemTextColor = .white + case let .color(color): + if color != colors.background { + chatServiceItemTextColor = chatServiceItemColor.brightnessAdjustedColor + } else { + chatServiceItemTextColor = colors.grayText + } + case .gradient: + chatServiceItemTextColor = chatServiceItemColor.brightnessAdjustedColor + case .tiled: + chatServiceItemTextColor = chatServiceItemColor.brightnessAdjustedColor + case .plain: + chatServiceItemTextColor = colors.grayText + } + } else { + chatServiceItemTextColor = colors.grayText + } + + self._chatServiceItemTextColor = chatServiceItemTextColor + return chatServiceItemTextColor + } + } let fontSize: CGFloat - init(colors: ColorPallete, search: SearchTheme, chatList: TelegramChatListTheme, tabBar: TelegramTabBarTheme, icons: TelegramIconsTheme, dark: Bool, fontSize: CGFloat) { + + var controllerBackgroundMode: TableBackgroundMode { + if self.bubbled { + return self.backgroundMode + } else { + return .color(color: colors.chatBackground) + } + } + + var chatBackground: NSColor { + return self.colors.chatBackground + } + + var backgroundSize: NSSize = NSMakeSize(1040, 1580) + + private var _backgroundMode: TableBackgroundMode? + var backgroundMode: TableBackgroundMode { + if let value = _backgroundMode { + return value + } else { + let backgroundMode: TableBackgroundMode = generateBackgroundMode(wallpaper.wallpaper, palette: colors, maxSize: backgroundSize) + + self._backgroundMode = backgroundMode + return backgroundMode + } + } + init(colors: ColorPalette, cloudTheme: TelegramTheme?, search: SearchTheme, chatList: TelegramChatListTheme, tabBar: TelegramTabBarTheme, icons: TelegramIconsTheme, bubbled: Bool, fontSize: CGFloat, wallpaper: ThemeWallpaper) { self.chatList = chatList + #if !SHARE + self.chat = TelegramChatColors(colors, bubbled) + #endif self.tabBar = tabBar self.icons = icons - self.dark = dark + self.wallpaper = wallpaper + self.bubbled = bubbled self.fontSize = fontSize + self.cloudTheme = cloudTheme + super.init(colors: colors, search: search) } + var dark: Bool { + return colors.isDark + } + #if !SHARE + var insantPageThemeType: InstantPageThemeType { + if colors.isDark { + return .dark + } else { + return .light + } + } + #endif + + + deinit { + + } + + func withUpdatedColors(_ colors: ColorPalette) -> TelegramPresentationTheme { + return TelegramPresentationTheme(colors: colors, cloudTheme: self.cloudTheme, search: self.search, chatList: self.chatList, tabBar: self.tabBar, icons: generateIcons(from: colors, bubbled: self.bubbled), bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper) + } + func withUpdatedChatMode(_ bubbled: Bool) -> TelegramPresentationTheme { + return TelegramPresentationTheme(colors: colors, cloudTheme: self.cloudTheme, search: self.search, chatList: self.chatList, tabBar: self.tabBar, icons: generateIcons(from: colors, bubbled: bubbled), bubbled: bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper) + } + + func withUpdatedBackgroundSize(_ size: NSSize) -> TelegramPresentationTheme { + self.backgroundSize = size + return self + } + + func withUpdatedWallpaper(_ wallpaper: ThemeWallpaper) -> TelegramPresentationTheme { + return TelegramPresentationTheme(colors: self.colors, cloudTheme: self.cloudTheme, search: self.search, chatList: self.chatList, tabBar: self.tabBar, icons: generateIcons(from: colors, bubbled: self.bubbled), bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: wallpaper) + } + func activity(key:Int32, foregroundColor: NSColor, backgroundColor: NSColor) -> ActivitiesTheme { - return activityResources.object(key, { () -> AnyObject in + return activityResources.object(key, { () -> Any in return ActivitiesTheme(text: textActivityAnimation(foregroundColor), uploading: uploadFileActivityAnimation(foregroundColor, backgroundColor), recording: recordVoiceActivityAnimation(foregroundColor), textColor: foregroundColor, backgroundColor: backgroundColor) }) as! ActivitiesTheme } @@ -527,248 +1714,610 @@ class TelegramPresentationTheme : PresentationTheme { let _themeSignal:ValuePromise = ValuePromise(ignoreRepeated: true) -var themeSignal:Signal { +var themeSignal:Signal { return _themeSignal.get() |> distinctUntilChanged |> deliverOnMainQueue } +extension ColorPalette { + var transparentBackground: NSColor { + return NSColor(patternImage: NSImage(cgImage: theme.icons.transparentBackground, size: theme.icons.transparentBackground.backingSize)) + } + var lottieTransparentBackground: NSColor { + return NSColor(patternImage: NSImage(cgImage: theme.icons.lottieTransparentBackground, size: theme.icons.lottieTransparentBackground.backingSize)) + } +} + + +private func generateIcons(from palette: ColorPalette, bubbled: Bool) -> TelegramIconsTheme { + return TelegramIconsTheme(dialogMuteImage: { #imageLiteral(resourceName: "Icon_DialogMute").precomposed(palette.grayIcon) }, + dialogMuteImageSelected: { #imageLiteral(resourceName: "Icon_DialogMute").precomposed(palette.underSelectedColor) }, + outgoingMessageImage: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(palette.accentIcon, flipVertical:true) }, + readMessageImage: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(palette.accentIcon, flipVertical:true) }, + outgoingMessageImageSelected: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(palette.underSelectedColor, flipVertical:true) }, + readMessageImageSelected: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(palette.underSelectedColor, flipVertical:true) }, + sendingImage: { #imageLiteral(resourceName: "Icon_ChatStateSending").precomposed(palette.grayIcon, flipVertical:true) }, + sendingImageSelected: { #imageLiteral(resourceName: "Icon_ChatStateSending").precomposed(palette.underSelectedColor, flipVertical:true) }, + secretImage: { #imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(palette.accent, flipVertical:true) }, + secretImageSelected:{ #imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(palette.underSelectedColor, flipVertical:true) }, + pinnedImage: { #imageLiteral(resourceName: "Icon_ChatListPinned").precomposed(palette.grayIcon, flipVertical:true) }, + pinnedImageSelected: { #imageLiteral(resourceName: "Icon_ChatListPinned").precomposed(palette.underSelectedColor, flipVertical:true) }, + verifiedImage: { #imageLiteral(resourceName: "Icon_VerifyPeer").precomposed(flipVertical: true) }, + verifiedImageSelected: { #imageLiteral(resourceName: "Icon_VerifyPeerActive").precomposed(flipVertical: true) }, + errorImage: { #imageLiteral(resourceName: "Icon_MessageSentFailed").precomposed(flipVertical: true) }, + errorImageSelected: { #imageLiteral(resourceName: "Icon_DialogSendingError").precomposed(flipVertical: true) }, + chatSearch: { generateChatAction(#imageLiteral(resourceName: "Icon_SearchChatMessages").precomposed(palette.accentIcon), background: palette.background) }, + chatSearchActive: { generateChatAction( #imageLiteral(resourceName: "Icon_SearchChatMessages").precomposed(palette.accentIcon), background: palette.grayIcon.withAlphaComponent(0.1)) }, + chatCall: { #imageLiteral(resourceName: "Icon_callNavigationHeader").precomposed(palette.accentIcon) }, + chatActions: { generateChatAction(#imageLiteral(resourceName: "Icon_ChatActionsActive").precomposed(palette.accentIcon), background: palette.background) }, + chatFailedCall_incoming: { #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(palette.redUI) }, + chatFailedCall_outgoing: { #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(palette.redUI) }, + chatCall_incoming: { #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(palette.greenUI) }, + chatCall_outgoing: { #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(palette.greenUI) }, + chatFailedCallBubble_incoming: { #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(palette.redBubble_incoming) }, + chatFailedCallBubble_outgoing: { #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(palette.redBubble_outgoing) }, + chatCallBubble_incoming: { #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(palette.greenBubble_incoming) }, + chatCallBubble_outgoing: { #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(palette.greenBubble_outgoing) }, + chatFallbackCall: { #imageLiteral(resourceName: "Icon_MessageCall").precomposed(palette.accentIcon) }, + chatFallbackCallBubble_incoming: { #imageLiteral(resourceName: "Icon_MessageCall").precomposed(palette.greenBubble_incoming) }, + chatFallbackCallBubble_outgoing: { #imageLiteral(resourceName: "Icon_MessageCall").precomposed(palette.greenBubble_outgoing) }, + + chatToggleSelected: { generateChatGroupToggleSelected(foregroundColor: palette.accentIcon, backgroundColor: palette.underSelectedColor) }, + chatToggleUnselected: { generateChatGroupToggleUnselected(foregroundColor: palette.grayIcon.withAlphaComponent(0.6), backgroundColor: NSColor.black.withAlphaComponent(0.01)) }, + chatMusicPlay: { #imageLiteral(resourceName: "Icon_ChatMusicPlay").precomposed(palette.fileActivityForeground) }, + chatMusicPlayBubble_incoming: { #imageLiteral(resourceName: "Icon_ChatMusicPlay").precomposed(palette.fileActivityForegroundBubble_incoming) }, + chatMusicPlayBubble_outgoing: { #imageLiteral(resourceName: "Icon_ChatMusicPlay").precomposed(palette.fileActivityForegroundBubble_outgoing) }, + chatMusicPause: { #imageLiteral(resourceName: "Icon_ChatMusicPause").precomposed(palette.fileActivityForeground) }, + chatMusicPauseBubble_incoming: { #imageLiteral(resourceName: "Icon_ChatMusicPause").precomposed(palette.fileActivityForegroundBubble_incoming) }, + chatMusicPauseBubble_outgoing: { #imageLiteral(resourceName: "Icon_ChatMusicPause").precomposed(palette.fileActivityForegroundBubble_outgoing) }, + chatGradientBubble_incoming: { generateGradientBubble(palette.bubbleBackground_incoming, palette.bubbleBackground_incoming) }, + chatGradientBubble_outgoing: { generateGradientBubble(palette.bubbleBackgroundTop_outgoing, palette.bubbleBackgroundBottom_outgoing) }, + chatBubble_none_incoming_withInset: { messageBubbleImageModern(incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none) }, + chatBubble_none_outgoing_withInset: { messageBubbleImageModern(incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none) }, + chatBubbleBorder_none_incoming_withInset: { messageBubbleImageModern(incoming: true, fillColor: .clear, strokeColor: palette.bubbleBorder_incoming, neighbors: .none) }, + chatBubbleBorder_none_outgoing_withInset: { messageBubbleImageModern(incoming: false, fillColor: .clear, strokeColor: palette.bubbleBorder_outgoing, neighbors: .none) }, + chatBubble_both_incoming_withInset: { messageBubbleImageModern(incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .both) }, + chatBubble_both_outgoing_withInset: { messageBubbleImageModern(incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .both) }, + chatBubbleBorder_both_incoming_withInset: { messageBubbleImageModern(incoming: true, fillColor: .clear, strokeColor: palette.bubbleBorder_incoming, neighbors: .both) }, + chatBubbleBorder_both_outgoing_withInset: { messageBubbleImageModern(incoming: false, fillColor: .clear, strokeColor: palette.bubbleBorder_outgoing, neighbors: .both) }, + composeNewChat: { #imageLiteral(resourceName: "Icon_NewMessage").precomposed(palette.accentIcon) }, + composeNewChatActive: { #imageLiteral(resourceName: "Icon_NewMessage").precomposed(palette.underSelectedColor) }, + composeNewGroup: { #imageLiteral(resourceName: "Icon_NewGroup").precomposed(palette.accentIcon) }, + composeNewSecretChat: { #imageLiteral(resourceName: "Icon_NewSecretChat").precomposed(palette.accentIcon) }, + composeNewChannel: { #imageLiteral(resourceName: "Icon_NewChannel").precomposed(palette.accentIcon) }, + contactsNewContact: { #imageLiteral(resourceName: "Icon_NewContact").precomposed(palette.accentIcon) }, + chatReadMarkInBubble1_incoming: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(palette.accentIconBubble_incoming) }, + chatReadMarkInBubble2_incoming: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(palette.accentIconBubble_incoming) }, + chatReadMarkInBubble1_outgoing: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(palette.accentIconBubble_outgoing) }, + chatReadMarkInBubble2_outgoing: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(palette.accentIconBubble_outgoing) }, + chatReadMarkOutBubble1: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(palette.accentIcon) }, + chatReadMarkOutBubble2: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(palette.accentIcon) }, + chatReadMarkOverlayBubble1: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(.white) }, + chatReadMarkOverlayBubble2: { #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(.white) }, + sentFailed: { generateImage(NSMakeSize(13, 13), contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(#imageLiteral(resourceName: "Icon_MessageSentFailed").precomposed(), in: NSMakeRect(0, 0, size.width, size.height)) + })! }, + chatChannelViewsInBubble_incoming: { #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(palette.grayIconBubble_incoming, flipVertical: true) }, + chatChannelViewsInBubble_outgoing: { #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(palette.grayIconBubble_outgoing, flipVertical: true) }, + chatChannelViewsOutBubble: { #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(palette.grayIcon, flipVertical: true) }, + chatChannelViewsOverlayBubble: { #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(.white, flipVertical: true) }, + chatNavigationBack: { #imageLiteral(resourceName: "Icon_ChatNavigationBack").precomposed(palette.accentIcon) }, + peerInfoAddMember: { #imageLiteral(resourceName: "Icon_NewContact").precomposed(palette.accentIcon, flipVertical: true) }, + chatSearchUp: { #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(palette.accentIcon) }, + chatSearchUpDisabled: { #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(palette.grayIcon) }, + chatSearchDown: { #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(palette.accentIcon, flipVertical:true) }, + chatSearchDownDisabled: { #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(palette.grayIcon, flipVertical:true) }, + chatSearchCalendar: { #imageLiteral(resourceName: "Icon_Calendar").precomposed(palette.accentIcon) }, + dismissAccessory: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.grayIcon) }, + chatScrollUp: { generateChatScrolldownImage(backgroundColor: palette.background, borderColor: palette.chatBackground == palette.background && palette.isDark ? palette.grayIcon : .clear, arrowColor: palette.grayIcon) }, + chatScrollUpActive: { generateChatScrolldownImage(backgroundColor: palette.background, borderColor: palette.chatBackground == palette.background && palette.isDark ? palette.accentIcon : .clear, arrowColor: palette.accentIcon) }, + audioPlayerPlay: { #imageLiteral(resourceName: "Icon_InlinePlayerPlay").precomposed(palette.accentIcon) }, + audioPlayerPause: { #imageLiteral(resourceName: "Icon_InlinePlayerPause").precomposed(palette.accentIcon) }, + audioPlayerNext: { #imageLiteral(resourceName: "Icon_InlinePlayerNext").precomposed(palette.accentIcon) }, + audioPlayerPrev: { #imageLiteral(resourceName: "Icon_InlinePlayerPrevious").precomposed(palette.accentIcon) }, + auduiPlayerDismiss: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + audioPlayerRepeat: { #imageLiteral(resourceName: "Icon_RepeatAudio").precomposed(palette.grayIcon) }, + audioPlayerRepeatActive: { #imageLiteral(resourceName: "Icon_RepeatAudio").precomposed(palette.accentIcon) }, + audioPlayerLockedPlay: { #imageLiteral(resourceName: "Icon_InlinePlayerPlay").precomposed(palette.grayIcon) }, + audioPlayerLockedNext: { #imageLiteral(resourceName: "Icon_InlinePlayerNext").precomposed(palette.grayIcon) }, + audioPlayerLockedPrev: { #imageLiteral(resourceName: "Icon_InlinePlayerPrevious").precomposed(palette.grayIcon) }, + chatSendMessage: { #imageLiteral(resourceName: "Icon_SendMessage").precomposed(palette.accentIcon) }, + chatSaveEditedMessage: { generateSendIcon(NSImage(named: "Icon_SaveEditedMessage")!, palette.accentIcon) }, + chatRecordVoice: { #imageLiteral(resourceName: "Icon_RecordVoice").precomposed(palette.grayIcon) }, + chatEntertainment: { #imageLiteral(resourceName: "Icon_Entertainments").precomposed(palette.grayIcon) }, + chatInlineDismiss: { #imageLiteral(resourceName: "Icon_InlineResultCancel").precomposed(palette.grayIcon) }, + chatActiveReplyMarkup: { #imageLiteral(resourceName: "Icon_ReplyMarkupButton").precomposed(palette.accentIcon) }, + chatDisabledReplyMarkup: { #imageLiteral(resourceName: "Icon_ReplyMarkupButton").precomposed(palette.grayIcon) }, + chatSecretTimer: { #imageLiteral(resourceName: "Icon_SecretTimer").precomposed(palette.grayIcon) }, + chatForwardMessagesActive: { #imageLiteral(resourceName: "Icon_MessageActionPanelForward").precomposed(palette.accentIcon) }, + chatForwardMessagesInactive: { #imageLiteral(resourceName: "Icon_MessageActionPanelForward").precomposed(palette.grayIcon) }, + chatDeleteMessagesActive: { #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(palette.redUI) }, + chatDeleteMessagesInactive: { #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(palette.grayIcon) }, + generalNext: { #imageLiteral(resourceName: "Icon_GeneralNext").precomposed(palette.grayIcon.withAlphaComponent(0.5)) }, + generalNextActive: { #imageLiteral(resourceName: "Icon_GeneralNext").precomposed(.white) }, + generalSelect: { #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(palette.accentIcon) }, + chatVoiceRecording: { #imageLiteral(resourceName: "Icon_RecordingVoice").precomposed(palette.accentIcon) }, + chatVideoRecording: { #imageLiteral(resourceName: "Icon_RecordVideoMessage").precomposed(palette.accentIcon) }, + chatRecord: { #imageLiteral(resourceName: "Icon_RecordVoice").precomposed(palette.grayIcon) }, + deleteItem: { deleteItemIcon(palette.redUI) }, + deleteItemDisabled: { deleteItemIcon(palette.grayText) }, + chatAttach: { #imageLiteral(resourceName: "Icon_ChatAttach").precomposed(palette.grayIcon) }, + chatAttachFile: { #imageLiteral(resourceName: "Icon_AttachFile").precomposed(palette.accentIcon) }, + chatAttachPhoto: { #imageLiteral(resourceName: "Icon_AttachPhoto").precomposed(palette.accentIcon) }, + chatAttachCamera: { #imageLiteral(resourceName: "Icon_AttachCamera").precomposed(palette.accentIcon) }, + chatAttachLocation: { #imageLiteral(resourceName: "Icon_AttachLocation").precomposed(palette.accentIcon) }, + chatAttachPoll: { #imageLiteral(resourceName: "Icon_AttachPoll").precomposed(palette.accentIcon) }, + mediaEmptyShared: { #imageLiteral(resourceName: "Icon_EmptySharedMedia").precomposed(palette.grayIcon) }, + mediaEmptyFiles: { #imageLiteral(resourceName: "Icon_EmptySharedFiles").precomposed() }, + mediaEmptyMusic: { #imageLiteral(resourceName: "Icon_EmptySharedMusic").precomposed(palette.grayIcon) }, + mediaEmptyLinks: { #imageLiteral(resourceName: "Icon_EmptySharedLinks").precomposed(palette.grayIcon) }, + stickersAddFeatured: { #imageLiteral(resourceName: "Icon_GroupInfoAddMember").precomposed(palette.accentIcon) }, + stickersAddedFeatured: { #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(palette.grayIcon) }, + stickersRemove: { #imageLiteral(resourceName: "Icon_InlineResultCancel").precomposed(palette.grayIcon) }, + peerMediaDownloadFileStart: { #imageLiteral(resourceName: "Icon_MediaDownload").precomposed(palette.accentIcon) }, + peerMediaDownloadFilePause: { downloadFilePauseIcon(palette.accentIcon) }, + stickersShare: { #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(palette.accentIcon) }, + emojiRecentTab: { #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiSmileTab: { #imageLiteral(resourceName: "Icon_EmojiTabSmiles").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiNatureTab: { #imageLiteral(resourceName: "Icon_EmojiTabNature").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiFoodTab: { #imageLiteral(resourceName: "Icon_EmojiTabFood").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiSportTab: { #imageLiteral(resourceName: "Icon_EmojiTabSports").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiCarTab: { #imageLiteral(resourceName: "Icon_EmojiTabCar").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiObjectsTab: { #imageLiteral(resourceName: "Icon_EmojiTabObjects").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiSymbolsTab: { #imageLiteral(resourceName: "Icon_EmojiTabSymbols").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiFlagsTab: { #imageLiteral(resourceName: "Icon_EmojiTabFlag").precomposed(palette.grayIcon, flipVertical:true, flipHorizontal:true) }, + emojiRecentTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiSmileTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabSmiles").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiNatureTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabNature").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiFoodTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabFood").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiSportTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabSports").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiCarTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabCar").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiObjectsTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabObjects").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiSymbolsTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabSymbols").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + emojiFlagsTabActive: { #imageLiteral(resourceName: "Icon_EmojiTabFlag").precomposed(palette.accentIcon, flipVertical:true, flipHorizontal:true) }, + stickerBackground: { generateStickerBackground(NSMakeSize(83, 83), palette.background) }, + stickerBackgroundActive: { generateStickerBackground(NSMakeSize(83, 83), palette.grayBackground) }, + stickersTabRecent: { #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(palette.grayIcon) }, + stickersTabGIF: { #imageLiteral(resourceName: "Icon_GifToggle").precomposed(palette.grayIcon) }, + chatSendingInFrame_incoming: { generateSendingFrame(palette.grayIconBubble_incoming) }, + chatSendingInHour_incoming: { generateClockMinImage(palette.grayIconBubble_incoming) }, + chatSendingInMin_incoming: { generateClockMinImage(palette.grayIconBubble_incoming) }, + chatSendingInFrame_outgoing: { generateSendingFrame(palette.grayIconBubble_outgoing) }, + chatSendingInHour_outgoing: { generateClockMinImage(palette.grayIconBubble_outgoing) }, + chatSendingInMin_outgoing: { generateClockMinImage(palette.grayIconBubble_outgoing) }, + chatSendingOutFrame: { generateSendingFrame(palette.grayIcon) }, + chatSendingOutHour: { generateClockMinImage(palette.grayIcon) }, + chatSendingOutMin: { generateClockMinImage(palette.grayIcon) }, + chatSendingOverlayFrame: { generateSendingFrame(.white) }, + chatSendingOverlayHour: { generateClockMinImage(.white) }, + chatSendingOverlayMin: { generateClockMinImage(.white) }, + chatActionUrl: { #imageLiteral(resourceName: "Icon_InlineBotUrl").precomposed(palette.text) }, + callInlineDecline: { #imageLiteral(resourceName: "Icon_CallDecline_Inline").precomposed(.white) }, + callInlineMuted: { #imageLiteral(resourceName: "Icon_CallMute_Inline").precomposed(.white) }, + callInlineUnmuted: { #imageLiteral(resourceName: "Icon_CallUnmuted_Inline").precomposed(.white) }, + eventLogTriangle: { generateRecentActionsTriangle(palette.text) }, + channelIntro: { #imageLiteral(resourceName: "Icon_ChannelIntro").precomposed() }, + chatFileThumb: { #imageLiteral(resourceName: "Icon_MessageFile").precomposed(flipVertical:true) }, + chatFileThumbBubble_incoming: { #imageLiteral(resourceName: "Icon_MessageFile").precomposed(palette.fileActivityForegroundBubble_incoming, flipVertical:true) }, + chatFileThumbBubble_outgoing: { #imageLiteral(resourceName: "Icon_MessageFile").precomposed(palette.fileActivityForegroundBubble_outgoing, flipVertical:true) }, + chatSecretThumb: { generateSecretThumb(#imageLiteral(resourceName: "Icon_SecretAutoremoveMedia").precomposed(.black, flipVertical:true)) }, + chatSecretThumbSmall: { generateSecretThumbSmall(#imageLiteral(resourceName: "Icon_SecretAutoremoveMedia").precomposed(.black, flipVertical:true)) }, + chatMapPin: { #imageLiteral(resourceName: "Icon_MapPinned").precomposed() }, + chatSecretTitle: { #imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(palette.text, flipVertical:true) }, + emptySearch: { #imageLiteral(resourceName: "Icon_EmptySearchResults").precomposed(palette.grayIcon) }, + calendarBack: { #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(palette.accentIcon) }, + calendarNext: { #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(palette.accentIcon, flipHorizontal: true) }, + calendarBackDisabled: { #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(palette.grayIcon) }, + calendarNextDisabled: { #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(palette.grayIcon, flipHorizontal: true) }, + newChatCamera: { #imageLiteral(resourceName: "Icon_AttachCamera").precomposed(palette.grayIcon) }, + peerInfoVerify: { #imageLiteral(resourceName: "Icon_VerifyPeer").precomposed(flipVertical: true) }, + peerInfoVerifyProfile: { #imageLiteral(resourceName: "Icon_VerifyPeer").precomposed() }, + peerInfoCall: { #imageLiteral(resourceName: "Icon_ProfileCall").precomposed(palette.accent) }, + callOutgoing: { #imageLiteral(resourceName: "Icon_CallOutgoing").precomposed(palette.grayIcon, flipVertical: true) }, + recentDismiss: { #imageLiteral(resourceName: "Icon_SearchClear").precomposed(palette.grayIcon) }, + recentDismissActive: { #imageLiteral(resourceName: "Icon_SearchClear").precomposed(.white) }, + webgameShare: { #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(palette.accentIcon) }, + chatSearchCancel: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + chatSearchFrom: { #imageLiteral(resourceName: "Icon_ChatSearchFrom").precomposed(palette.accentIcon) }, + callWindowDecline: { #imageLiteral(resourceName: "Icon_CallDecline_Window").precomposed() }, + callWindowAccept: { #imageLiteral(resourceName: "Icon_CallAccept_Window").precomposed() }, + callWindowMute: { #imageLiteral(resourceName: "Icon_CallMic_Window").precomposed() }, + callWindowUnmute: { #imageLiteral(resourceName: "Icon_CallMute_Inline").precomposed() }, + callWindowClose: { #imageLiteral(resourceName: "Icon_CallWindowClose").precomposed(.white) }, + callWindowDeviceSettings: { #imageLiteral(resourceName: "Icon_CallDeviceSettings").precomposed(.white) }, + callSettings: { #imageLiteral(resourceName: "Icon_CallDeviceSettings").precomposed(palette.accentIcon) }, + callWindowCancel: { #imageLiteral(resourceName: "Icon_CallCancelIcon").precomposed(.white) }, + chatActionEdit: { #imageLiteral(resourceName: "Icon_ChatActionEdit").precomposed(palette.accentIcon) }, + chatActionInfo: { #imageLiteral(resourceName: "Icon_ChatActionInfo").precomposed(palette.accentIcon) }, + chatActionMute: { #imageLiteral(resourceName: "Icon_ChatActionMute").precomposed(palette.accentIcon) }, + chatActionUnmute: { #imageLiteral(resourceName: "Icon_ChatActionUnmute").precomposed(palette.accentIcon) }, + chatActionClearHistory: { #imageLiteral(resourceName: "Icon_ClearChat").precomposed(palette.accentIcon) }, + chatActionDeleteChat: { #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(palette.accentIcon) }, + dismissPinned: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + chatActionsActive: { generateChatAction(#imageLiteral(resourceName: "Icon_ChatActionsActive").precomposed(palette.accentIcon), background: palette.grayIcon.withAlphaComponent(0.1)) }, + chatEntertainmentSticker: { #imageLiteral(resourceName: "Icon_ChatEntertainmentSticker").precomposed(palette.grayIcon) }, + chatEmpty: { #imageLiteral(resourceName: "Icon_EmptyChat").precomposed(palette.grayForeground) }, + stickerPackClose: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + stickerPackDelete: { #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(palette.accentIcon) }, + modalShare: { #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(palette.accentIcon) }, + modalClose: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + ivChannelJoined: { #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(.white) }, + chatListMention: { generateBadgeMention(backgroundColor: palette.badge, foregroundColor: palette.background) }, + chatListMentionActive: { generateBadgeMention(backgroundColor: .white, foregroundColor: palette.accentSelect) }, + chatListMentionArchived: { generateBadgeMention(backgroundColor: palette.badgeMuted, foregroundColor: palette.background) }, + chatListMentionArchivedActive: { generateBadgeMention(backgroundColor: palette.underSelectedColor, foregroundColor: palette.accentSelect) }, + chatMention: { generateChatMention(backgroundColor: palette.background, border: palette.grayIcon, foregroundColor: palette.grayIcon) }, + chatMentionActive: { generateChatMention(backgroundColor: palette.background, border: palette.accentIcon, foregroundColor: palette.accentIcon) }, + sliderControl: { #imageLiteral(resourceName: "Icon_SliderNormal").precomposed() }, + sliderControlActive: { #imageLiteral(resourceName: "Icon_SliderNormal").precomposed() }, + stickersTabFave: { #imageLiteral(resourceName: "Icon_FaveStickers").precomposed(palette.grayIcon) }, + chatInstantView: { #imageLiteral(resourceName: "Icon_ChatIV").precomposed(palette.webPreviewActivity) }, + chatInstantViewBubble_incoming: { #imageLiteral(resourceName: "Icon_ChatIV").precomposed(palette.webPreviewActivityBubble_incoming) }, + chatInstantViewBubble_outgoing: { #imageLiteral(resourceName: "Icon_ChatIV").precomposed(palette.webPreviewActivityBubble_outgoing) }, + instantViewShare: { #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(palette.accentIcon) }, + instantViewActions: { #imageLiteral(resourceName: "Icon_ChatActions").precomposed(palette.accentIcon) }, + instantViewActionsActive: { #imageLiteral(resourceName: "Icon_ChatActionsActive").precomposed(palette.accentIcon) }, + instantViewSafari: { #imageLiteral(resourceName: "Icon_InstantViewSafari").precomposed(palette.accentIcon) }, + instantViewBack: { #imageLiteral(resourceName: "Icon_InstantViewBack").precomposed(palette.accentIcon) }, + instantViewCheck: { #imageLiteral(resourceName: "Icon_InstantViewCheck").precomposed(palette.accentIcon) }, + groupStickerNotFound: { #imageLiteral(resourceName: "Icon_GroupStickerNotFound").precomposed(palette.grayIcon) }, + settingsAskQuestion: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsAskQuestion").precomposed(flipVertical: true)) }, + settingsFaq: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsFaq").precomposed(flipVertical: true)) }, + settingsGeneral: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsGeneral").precomposed(flipVertical: true)) }, + settingsLanguage: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsLanguage").precomposed(flipVertical: true)) }, + settingsNotifications: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsNotifications").precomposed(flipVertical: true)) }, + settingsSecurity: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsSecurity").precomposed(flipVertical: true)) }, + settingsStickers: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsStickers").precomposed(flipVertical: true)) }, + settingsStorage: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsStorage").precomposed(flipVertical: true)) }, + settingsSessions: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_PrivacySettings_ActiveSessions").precomposed(flipVertical: true)) }, + settingsProxy: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsProxy").precomposed(flipVertical: true)) }, + settingsAppearance: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_AppearanceSettings").precomposed(flipVertical: true)) }, + settingsPassport: { generateSettingsIcon(#imageLiteral(resourceName: "Icon_SettingsSecurity").precomposed(flipVertical: true)) }, + settingsWallet: { generateSettingsIcon(NSImage(named: "Icon_SettingsWallet")!.precomposed(NSColor(0x59a7d8), flipVertical: true)) }, + settingsUpdate: { generateSettingsIcon(NSImage(named: "Icon_SettingsUpdate")!.precomposed(flipVertical: true)) }, + settingsFilters: { generateSettingsIcon(NSImage(named: "Icon_SettingsFilters")!.precomposed(flipVertical: true)) }, + settingsAskQuestionActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsAskQuestion").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsFaqActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsFaq").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsGeneralActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsGeneral").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsLanguageActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsLanguage").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsNotificationsActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsNotifications").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsSecurityActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsSecurity").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsStickersActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsStickers").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsStorageActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsStorage").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsSessionsActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_PrivacySettings_ActiveSessions").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsProxyActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsProxy").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsAppearanceActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_AppearanceSettings").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsPassportActive: { generateSettingsActiveIcon(#imageLiteral(resourceName: "Icon_SettingsSecurity").precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsWalletActive: { generateSettingsActiveIcon(NSImage(named: "Icon_SettingsWallet")!.precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsUpdateActive: { generateSettingsActiveIcon(NSImage(named: "Icon_SettingsUpdate")!.precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsFiltersActive: { generateSettingsActiveIcon(NSImage(named: "Icon_SettingsFilters")!.precomposed(palette.underSelectedColor, flipVertical: true), background: palette.accentSelect) }, + settingsProfile: { generateSettingsIcon(NSImage(named: "Icon_SettingsProfile")!.precomposed(flipVertical: true)) }, + generalCheck: { #imageLiteral(resourceName: "Icon_Check").precomposed(palette.accentIcon) }, + settingsAbout: { #imageLiteral(resourceName: "Icon_SettingsAbout").precomposed(palette.accentIcon) }, + settingsLogout: { #imageLiteral(resourceName: "Icon_SettingsLogout").precomposed(palette.redUI) }, + fastSettingsLock: { #imageLiteral(resourceName: "Icon_FastSettingsLock").precomposed(palette.accentIcon) }, + fastSettingsDark: { #imageLiteral(resourceName: "Icon_FastSettingsDark").precomposed(palette.accentIcon) }, + fastSettingsSunny: { #imageLiteral(resourceName: "Icon_FastSettingsSunny").precomposed(palette.accentIcon) }, + fastSettingsMute: { #imageLiteral(resourceName: "Icon_ChatActionMute").precomposed(palette.accentIcon) }, + fastSettingsUnmute: { #imageLiteral(resourceName: "Icon_ChatActionUnmute").precomposed(palette.accentIcon) }, + chatRecordVideo: { #imageLiteral(resourceName: "Icon_RecordVideoMessage").precomposed(palette.grayIcon) }, + inputChannelMute: { #imageLiteral(resourceName: "Icon_InputChannelMute").precomposed(palette.grayIcon) }, + inputChannelUnmute: { #imageLiteral(resourceName: "Icon_InputChannelUnmute").precomposed(palette.grayIcon) }, + changePhoneNumberIntro: { #imageLiteral(resourceName: "Icon_ChangeNumberIntro").precomposed() }, + peerSavedMessages: { #imageLiteral(resourceName: "Icon_SavedMessages").precomposed() }, + previewSenderCollage: { #imageLiteral(resourceName: "Icon_PreviewCollage").precomposed(palette.grayIcon) }, + previewSenderPhoto: { NSImage(named: "Icon_PreviewSenderPhoto")!.precomposed(palette.grayIcon) }, + previewSenderFile: { NSImage(named: "Icon_PreviewSenderFile")!.precomposed(palette.grayIcon) }, + previewSenderCrop: { NSImage(named: "Icon_PreviewSenderCrop")!.precomposed(.white) }, + previewSenderDelete: { NSImage(named: "Icon_PreviewSenderDelete")!.precomposed(.white) }, + previewSenderDeleteFile: { NSImage(named: "Icon_PreviewSenderDelete")!.precomposed(palette.accentIcon) }, + previewSenderArchive: { NSImage(named: "Icon_PreviewSenderArchive")!.precomposed(palette.grayIcon) }, + chatGroupToggleSelected: { generateChatGroupToggleSelected(foregroundColor: palette.accentIcon, backgroundColor: palette.underSelectedColor) }, + chatGroupToggleUnselected: { generateChatGroupToggleUnselected(foregroundColor: palette.grayIcon.withAlphaComponent(0.6), backgroundColor: NSColor.black.withAlphaComponent(0.01)) }, + successModalProgress: { #imageLiteral(resourceName: "Icon_ProgressWindowCheck").precomposed(palette.grayIcon) }, + accentColorSelect: { #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(.white) }, + transparentBackground: { generateTransparentBackground() }, + lottieTransparentBackground: { generateLottieTransparentBackground() }, + passcodeTouchId: { #imageLiteral(resourceName: "Icon_TouchId").precomposed(palette.underSelectedColor) }, + passcodeLogin: { #imageLiteral(resourceName: "Icon_PasscodeLogin").precomposed(palette.underSelectedColor) }, + confirmDeleteMessagesAccessory: { generateConfirmDeleteMessagesAccessory(backgroundColor: palette.redUI) }, + alertCheckBoxSelected: { generateAlertCheckBoxSelected(backgroundColor: palette.accentIcon) }, + alertCheckBoxUnselected: { generateAlertCheckBoxUnselected(border: palette.grayIcon) }, + confirmPinAccessory: { generateConfirmPinAccessory(backgroundColor: palette.accentIcon) }, + confirmDeleteChatAccessory: { generateConfirmDeleteChatAccessory(backgroundColor: palette.background, foregroundColor: palette.redUI) }, + stickersEmptySearch: { generateStickersEmptySearch(color: palette.grayIcon) }, + twoStepVerificationCreateIntro: { #imageLiteral(resourceName: "Icon_TwoStepVerification_Create").precomposed() }, + secureIdAuth: { #imageLiteral(resourceName: "Icon_SecureIdAuth").precomposed() }, + ivAudioPlay: { generateIVAudioPlay(color: palette.text) }, + ivAudioPause: { generateIVAudioPause(color: palette.text) }, + proxyEnable: { #imageLiteral(resourceName: "Icon_ProxyEnable").precomposed(palette.accent) }, + proxyEnabled: { #imageLiteral(resourceName: "Icon_ProxyEnabled").precomposed(palette.accent) }, + proxyState: { #imageLiteral(resourceName: "Icon_ProxyState").precomposed(palette.accent) }, + proxyDeleteListItem: { #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(palette.accentIcon) }, + proxyInfoListItem: { NSImage(named: "Icon_DetailedInfo")!.precomposed(palette.accentIcon) }, + proxyConnectedListItem: { #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(palette.accentIcon) }, + proxyAddProxy: { #imageLiteral(resourceName: "Icon_GroupInfoAddMember").precomposed(palette.accentIcon, flipVertical: true) }, + proxyNextWaitingListItem: { #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(palette.grayIcon) }, + passportForgotPassword: { #imageLiteral(resourceName: "Icon_SecureIdForgotPassword").precomposed(palette.grayIcon) }, + confirmAppAccessoryIcon: { #imageLiteral(resourceName: "Icon_ConfirmAppAccessory").precomposed() }, + passportPassport: { #imageLiteral(resourceName: "Icon_PassportPassport").precomposed(palette.accentIcon, flipVertical: true) }, + passportIdCardReverse: { #imageLiteral(resourceName: "Icon_PassportIdCardReverse").precomposed(palette.accentIcon, flipVertical: true) }, + passportIdCard: { #imageLiteral(resourceName: "Icon_PassportIdCard").precomposed(palette.accentIcon, flipVertical: true) }, + passportSelfie: { #imageLiteral(resourceName: "Icon_PassportSelfie").precomposed(palette.accentIcon, flipVertical: true) }, + passportDriverLicense: { #imageLiteral(resourceName: "Icon_PassportDriverLicense").precomposed(palette.accentIcon, flipVertical: true) }, + chatOverlayVoiceRecording: { #imageLiteral(resourceName: "Icon_RecordingVoice").precomposed(.white) }, + chatOverlayVideoRecording: { #imageLiteral(resourceName: "Icon_RecordVideoMessage").precomposed(.white) }, + chatOverlaySendRecording: { #imageLiteral(resourceName: "Icon_ChatOverlayRecordingSend").precomposed(.white) }, + chatOverlayLockArrowRecording: { #imageLiteral(resourceName: "Icon_DropdownArrow").precomposed(palette.accentIcon, flipVertical: true) }, + chatOverlayLockerBodyRecording: { generateLockerBody(palette.accentIcon, backgroundColor: palette.background) }, + chatOverlayLockerHeadRecording: { generateLockerHead(palette.accentIcon, backgroundColor: palette.background) }, + locationPin: { generateLocationPinIcon(palette.accentIcon) }, + locationMapPin: { generateLocationMapPinIcon(palette.accentIcon) }, + locationMapLocate: { #imageLiteral(resourceName: "Icon_MapLocate").precomposed(palette.grayIcon) }, + locationMapLocated: { #imageLiteral(resourceName: "Icon_MapLocate").precomposed(palette.accentIcon) }, + passportSettings: { #imageLiteral(resourceName: "Icon_PassportSettings").precomposed(palette.grayIcon) }, + passportInfo: { #imageLiteral(resourceName: "Icon_SettingsBio").precomposed(palette.accentIcon) }, + editMessageMedia: { generateEditMessageMediaIcon(#imageLiteral(resourceName: "Icon_ReplaceMessageMedia").precomposed(palette.accentIcon), background: palette.background) }, + playerMusicPlaceholder: { generatePlayerListAlbumPlaceholder(#imageLiteral(resourceName: "Icon_MusicPlayerSmallAlbumArtPlaceholder").precomposed(palette.listGrayText), background: palette.listBackground, radius: .cornerRadius) }, + chatMusicPlaceholder: { generatePlayerListAlbumPlaceholder(#imageLiteral(resourceName: "Icon_MusicPlayerSmallAlbumArtPlaceholder").precomposed(palette.fileActivityForeground), background: palette.fileActivityBackground, radius: 20) }, + chatMusicPlaceholderCap: { generatePlayerListAlbumPlaceholder(nil, background: palette.fileActivityBackground, radius: 20) }, + searchArticle: { #imageLiteral(resourceName: "Icon_SearchArticles").precomposed(.white) }, + searchSaved: { #imageLiteral(resourceName: "Icon_SearchSaved").precomposed(.white) }, + archivedChats: { #imageLiteral(resourceName: "Icon_ArchiveAvatar").precomposed(.white) }, + hintPeerActive: { generateHitActiveIcon(activeColor: palette.accent, backgroundColor: palette.background) }, + hintPeerActiveSelected: { generateHitActiveIcon(activeColor: palette.underSelectedColor, backgroundColor: palette.accentSelect) }, + chatSwiping_delete: { #imageLiteral(resourceName: "Icon_ChatSwipingDelete").precomposed(.white) }, + chatSwiping_mute: { #imageLiteral(resourceName: "Icon_ChatSwipingMute").precomposed(.white) }, + chatSwiping_unmute: { #imageLiteral(resourceName: "Icon_ChatSwipingUnmute").precomposed(.white) }, + chatSwiping_read: { #imageLiteral(resourceName: "Icon_ChatSwipingRead").precomposed(.white) }, + chatSwiping_unread: { #imageLiteral(resourceName: "Icon_ChatSwipingUnread").precomposed(.white) }, + chatSwiping_pin: { #imageLiteral(resourceName: "Icon_ChatSwipingPin").precomposed(.white) }, + chatSwiping_unpin: { #imageLiteral(resourceName: "Icon_ChatSwipingUnpin").precomposed(.white) }, + chatSwiping_archive: { #imageLiteral(resourceName: "Icon_ChatListSwiping_Archive").precomposed(.white) }, + chatSwiping_unarchive: { #imageLiteral(resourceName: "Icon_ChatListSwiping_Unarchive").precomposed(.white) }, + galleryPrev: { #imageLiteral(resourceName: "Icon_GalleryPrev").precomposed(.white) }, + galleryNext: { #imageLiteral(resourceName: "Icon_GalleryNext").precomposed(.white) }, + galleryMore: { #imageLiteral(resourceName: "Icon_GalleryMore").precomposed(.white) }, + galleryShare: { #imageLiteral(resourceName: "Icon_GalleryShare").precomposed(.white) }, + galleryFastSave: { NSImage(named: "Icon_Gallery_FastSave")!.precomposed(.white) }, + playingVoice1x: { #imageLiteral(resourceName: "Icon_PlayingVoice2x").precomposed(palette.grayIcon) }, + playingVoice2x: { #imageLiteral(resourceName: "Icon_PlayingVoice2x").precomposed(palette.accentIcon) }, + galleryRotate: {NSImage(named: "Icon_GalleryRotate")!.precomposed(.white) }, + galleryZoomIn: {NSImage(named: "Icon_GalleryZoomIn")!.precomposed(.white) }, + galleryZoomOut: { NSImage(named: "Icon_GalleryZoomOut")!.precomposed(.white) }, + editMessageCurrentPhoto: { NSImage(named: "Icon_EditMessageCurrentPhoto")!.precomposed(palette.accentIcon) }, + videoPlayerPlay: { NSImage(named: "Icon_VideoPlayer_Play")!.precomposed(.white) }, + videoPlayerPause: { NSImage(named: "Icon_VideoPlayer_Pause")!.precomposed(.white) }, + videoPlayerEnterFullScreen: { NSImage(named: "Icon_VideoPlayer_EnterFullScreen")!.precomposed(.white) }, + videoPlayerExitFullScreen: { NSImage(named: "Icon_VideoPlayer_ExitFullScreen")!.precomposed(.white) }, + videoPlayerPIPIn: { NSImage(named: "Icon_VideoPlayer_PIPIN")!.precomposed(.white) }, + videoPlayerPIPOut: { NSImage(named: "Icon_VideoPlayer_PIPOUT")!.precomposed(.white) }, + videoPlayerRewind15Forward: { NSImage(named: "Icon_VideoPlayer_Rewind15Forward")!.precomposed(.white) }, + videoPlayerRewind15Backward: { NSImage(named: "Icon_VideoPlayer_Rewind15Backward")!.precomposed(.white) }, + videoPlayerVolume: { NSImage(named: "Icon_VideoPlayer_Volume")!.precomposed(.white) }, + videoPlayerVolumeOff: { NSImage(named: "Icon_VideoPlayer_VolumeOff")!.precomposed(.white) }, + videoPlayerClose: { NSImage(named: "Icon_VideoPlayer_Close")!.precomposed(.white) }, + videoPlayerSliderInteractor: { NSImage(named: "Icon_Slider")!.precomposed() }, + streamingVideoDownload: { NSImage(named: "Icon_StreamingDownload")!.precomposed(.white) }, + videoCompactFetching: { NSImage(named: "Icon_VideoCompactFetching")!.precomposed(.white) }, + compactStreamingFetchingCancel: { NSImage(named: "Icon_CompactStreamingFetchingCancel")!.precomposed(.white) }, + customLocalizationDelete: { NSImage(named: "Icon_MessageActionPanelDelete")!.precomposed(palette.accentIcon) }, + pollAddOption: { generatePollAddOption(palette.accentIcon) }, + pollDeleteOption: { generatePollDeleteOption(palette.redUI) }, + resort: { NSImage(named: "Icon_Resort")!.precomposed(palette.grayIcon.withAlphaComponent(0.6)) }, + chatPollVoteUnselected: { #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed(palette.grayText.withAlphaComponent(0.3)) }, + chatPollVoteUnselectedBubble_incoming: { #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed(palette.grayTextBubble_incoming.withAlphaComponent(0.3)) }, + chatPollVoteUnselectedBubble_outgoing: { #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed(palette.grayTextBubble_outgoing.withAlphaComponent(0.3)) }, + peerInfoAdmins: { NSImage(named: "Icon_ChatAdmins")!.precomposed(flipVertical: true) }, + peerInfoPermissions: { NSImage(named: "Icon_ChatPermissions")!.precomposed(flipVertical: true) }, + peerInfoBanned: { NSImage(named: "Icon_ChatBanned")!.precomposed(flipVertical: true) }, + peerInfoMembers: { NSImage(named: "Icon_ChatMembers")!.precomposed(flipVertical: true) }, + chatUndoAction: { NSImage(named: "Icon_ChatUndoAction")!.precomposed(NSColor(0x29ACFF)) }, + appUpdate: { NSImage(named: "Icon_AppUpdate")!.precomposed() }, + inlineVideoSoundOff: { NSImage(named: "Icon_InlineVideoSoundOff")!.precomposed() }, + inlineVideoSoundOn: { NSImage(named: "Icon_InlineVideoSoundOn")!.precomposed() }, + logoutOptionAddAccount: { generateSettingsIcon(NSImage(named: "Icon_LogoutOption_AddAccount")!.precomposed(flipVertical: true)) }, + logoutOptionSetPasscode: { generateSettingsIcon(NSImage(named: "Icon_LogoutOption_SetPasscode")!.precomposed(flipVertical: true)) }, + logoutOptionClearCache: { generateSettingsIcon(NSImage(named: "Icon_LogoutOption_ClearCache")!.precomposed(flipVertical: true)) }, + logoutOptionChangePhoneNumber: { generateSettingsIcon(NSImage(named: "Icon_LogoutOption_ChangePhoneNumber")!.precomposed(flipVertical: true)) }, + logoutOptionContactSupport: { generateSettingsIcon(NSImage(named: "Icon_LogoutOption_ContactSupport")!.precomposed(flipVertical: true)) }, + disableEmojiPrediction: { NSImage(named: "Icon_CallWindowClose")!.precomposed(palette.grayIcon) }, + scam: { generateScamIcon(foregroundColor: palette.redUI, backgroundColor: .clear) }, + scamActive: { generateScamIcon(foregroundColor: palette.underSelectedColor, backgroundColor: .clear) }, + chatScam: { generateScamIconReversed(foregroundColor: palette.redUI, backgroundColor: .clear) }, + chatUnarchive: { NSImage(named: "Icon_ChatUnarchive")!.precomposed(palette.accentIcon) }, + chatArchive: { NSImage(named: "Icon_ChatArchive")!.precomposed(palette.accentIcon) }, + privacySettings_blocked: { generateSettingsIcon(NSImage(named: "Icon_PrivacySettings_Blocked")!.precomposed(flipVertical: true)) }, + privacySettings_activeSessions: { generateSettingsIcon(NSImage(named: "Icon_PrivacySettings_ActiveSessions")!.precomposed(flipVertical: true)) }, + privacySettings_passcode: { generateSettingsIcon(NSImage(named: "Icon_SettingsSecurity")!.precomposed(palette.greenUI, flipVertical: true)) }, + privacySettings_twoStep: { generateSettingsIcon(NSImage(named: "Icon_PrivacySettings_TwoStep")!.precomposed(flipVertical: true)) }, + deletedAccount: { NSImage(named: "Icon_DeletedAccount")!.precomposed() }, + stickerPackSelection: { generateStickerPackSelection(.clear) }, + stickerPackSelectionActive: { generateStickerPackSelection(palette.grayForeground) }, + entertainment_Emoji: { NSImage(named: "Icon_Entertainment_Emoji")!.precomposed(palette.grayIcon) }, + entertainment_Stickers: { NSImage(named: "Icon_Entertainment_Stickers")!.precomposed(palette.grayIcon) }, + entertainment_Gifs: { NSImage(named: "Icon_Entertainment_Gifs")!.precomposed(palette.grayIcon) }, + entertainment_Search: { NSImage(named: "Icon_Entertainment_Search")!.precomposed(palette.grayIcon) }, + entertainment_Settings: { NSImage(named: "Icon_Entertainment_Settings")!.precomposed(palette.grayIcon) }, + entertainment_SearchCancel: { NSImage(named: "Icon_Entertainment_SearchCancel")!.precomposed(palette.grayIcon) }, + scheduledAvatar: { NSImage(named: "Icon_AvatarScheduled")!.precomposed(.white) }, + scheduledInputAction: { NSImage(named: "Icon_ChatActionScheduled")!.precomposed(palette.accentIcon) }, + verifyDialog: { generateDialogVerify(background: .white, foreground: palette.basicAccent) }, + verifyDialogActive: { generateDialogVerify(background: palette.accentIcon, foreground: palette.underSelectedColor) }, + chatInputScheduled: { NSImage(named: "Icon_ChatInputScheduled")!.precomposed(palette.grayIcon) }, + appearanceAddPlatformTheme: { + let image = NSImage(named: "Icon_AppearanceAddTheme")!.precomposed(palette.accentIcon) + return generateImage(image.backingSize, contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(image, in: NSMakeRect(0, 0, size.width, size.height)) + }, scale: System.backingScale)! }, + wallet_close: { #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(palette.accentIcon) }, + wallet_qr: { NSImage(named: "Icon_WalletQR")!.precomposed(palette.accentIcon) }, + wallet_receive: { NSImage(named: "Icon_WalletReceive")!.precomposed(palette.underSelectedColor) }, + wallet_send: { NSImage(named: "Icon_WalletSend")!.precomposed(palette.underSelectedColor) }, + wallet_settings: { NSImage(named: "Icon_WalletSettings")!.precomposed(palette.accentIcon) }, + wallet_update: { NSImage(named: "Icon_WalletUpdate")!.precomposed(palette.grayIcon) }, + wallet_passcode_visible: { NSImage(named: "Icon_WalletPasscodeVisible")!.precomposed(palette.grayIcon) }, + wallet_passcode_hidden: { NSImage(named: "Icon_WalletPasscodeHidden")!.precomposed(palette.grayIcon) }, + wallpaper_color_close: { NSImage(named: "Icon_GradientClose")!.precomposed(palette.grayIcon) }, + wallpaper_color_add: { NSImage(named: "Icon_GradientAdd")!.precomposed(palette.grayIcon) }, + wallpaper_color_swap: { NSImage(named: "Icon_GradientSwap")!.precomposed(palette.grayIcon) }, + wallpaper_color_rotate: { NSImage(named: "Icon_GradientRotate")!.precomposed(palette.grayIcon) }, + login_cap: { NSImage(named: "Icon_LoginCap")!.precomposed(palette.accentIcon) }, + login_qr_cap: { NSImage(named: "Icon_loginQRCap")!.precomposed(palette.accentIcon) }, + login_qr_empty_cap: { generateLoginQrEmptyCap() }, + chat_failed_scroller: { generateChatFailed(backgroundColor: palette.background, border: palette.redUI, foregroundColor: palette.redUI) }, + chat_failed_scroller_active: { generateChatFailed(backgroundColor: palette.background, border: palette.accentIcon, foregroundColor: palette.accentIcon) }, + poll_quiz_unselected: { generateUnslectedCap(palette.grayText) }, + poll_selected: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.webPreviewActivity) }, + poll_selected_correct: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.greenUI) }, + poll_selected_incorrect: { generatePollIcon(NSImage(named: "Icon_PollSelectedIncorrect")!, backgound: palette.redUI) }, + poll_selected_incoming: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.webPreviewActivityBubble_incoming) }, + poll_selected_correct_incoming: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.greenBubble_incoming) }, + poll_selected_incorrect_incoming: { generatePollIcon(NSImage(named: "Icon_PollSelectedIncorrect")!, backgound: palette.redBubble_incoming) }, + poll_selected_outgoing: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.webPreviewActivityBubble_outgoing) }, + poll_selected_correct_outgoing: { generatePollIcon(NSImage(named: "Icon_PollSelected")!, backgound: palette.greenBubble_outgoing) }, + poll_selected_incorrect_outgoing: { generatePollIcon(NSImage(named: "Icon_PollSelectedIncorrect")!, backgound: palette.redBubble_outgoing) }, + chat_filter_edit: { NSImage(named: "Icon_FilterEdit")!.precomposed(palette.accentIcon) }, + chat_filter_add: { NSImage(named: "Icon_FilterAdd")!.precomposed(palette.accentIcon) }, + chat_filter_bots: { NSImage(named: "Icon_FilterBots")!.precomposed(palette.accentIcon) }, + chat_filter_channels: { NSImage(named: "Icon_FilterChannels")!.precomposed(palette.accentIcon) }, + chat_filter_custom: { NSImage(named: "Icon_FilterCustom")!.precomposed(palette.accentIcon) }, + chat_filter_groups: { NSImage(named: "Icon_FilterGroups")!.precomposed(palette.accentIcon) }, + chat_filter_muted: { NSImage(named: "Icon_FilterMuted")!.precomposed(palette.accentIcon) }, + chat_filter_private_chats: { NSImage(named: "Icon_FilterPrivateChats")!.precomposed(palette.accentIcon) }, + chat_filter_read: { NSImage(named: "Icon_FilterRead")!.precomposed(palette.accentIcon) }, + chat_filter_secret_chats: { NSImage(named: "Icon_FilterSecretChats")!.precomposed(palette.accentIcon) }, + chat_filter_unmuted: { NSImage(named: "Icon_FilterUnmuted")!.precomposed(palette.accentIcon) }, + chat_filter_unread: { NSImage(named: "Icon_FilterUnread")!.precomposed(palette.accentIcon) }, + chat_filter_large_groups: { NSImage(named: "Icon_FilterLargeGroups")!.precomposed(palette.accentIcon) }, + chat_filter_non_contacts: { NSImage(named: "Icon_FilterNonContacts")!.precomposed(palette.accentIcon) }, + chat_filter_archive: { NSImage(named: "Icon_FilterArchive")!.precomposed(palette.accentIcon) }, + chat_filter_bots_avatar: { NSImage(named: "Icon_FilterBots")!.precomposed(.white) }, + chat_filter_channels_avatar: { NSImage(named: "Icon_FilterChannels")!.precomposed(.white) }, + chat_filter_custom_avatar: { NSImage(named: "Icon_FilterCustom")!.precomposed(.white) }, + chat_filter_groups_avatar: { NSImage(named: "Icon_FilterGroups")!.precomposed(.white) }, + chat_filter_muted_avatar: { NSImage(named: "Icon_FilterMuted")!.precomposed(.white) }, + chat_filter_private_chats_avatar: { NSImage(named: "Icon_FilterPrivateChats")!.precomposed(.white) }, + chat_filter_read_avatar: { NSImage(named: "Icon_FilterRead")!.precomposed(.white) }, + chat_filter_secret_chats_avatar: { NSImage(named: "Icon_FilterSecretChats")!.precomposed(.white) }, + chat_filter_unmuted_avatar: { NSImage(named: "Icon_FilterUnmuted")!.precomposed(.white) }, + chat_filter_unread_avatar: { NSImage(named: "Icon_FilterUnread")!.precomposed(.white) }, + chat_filter_large_groups_avatar: { NSImage(named: "Icon_FilterLargeGroups")!.precomposed(.white) }, + chat_filter_non_contacts_avatar: { NSImage(named: "Icon_FilterNonContacts")!.precomposed(.white) }, + chat_filter_archive_avatar: { NSImage(named: "Icon_FilterArchive")!.precomposed(.white) }, + group_invite_via_link: { NSImage(named: "Icon_InviteViaLink")!.precomposed(palette.accentIcon) }, + tab_contacts: { NSImage(named: "Icon_TabContacts")!.precomposed(palette.grayIcon) }, + tab_contacts_active: { NSImage(named: "Icon_TabContacts")!.precomposed(palette.accentIcon) }, + tab_calls: { NSImage(named: "Icon_TabRecentCalls")!.precomposed(palette.grayIcon) }, + tab_calls_active: { NSImage(named: "Icon_TabRecentCalls")!.precomposed(palette.accentIcon) }, + tab_chats: { NSImage(named: "Icon_TabChatList")!.precomposed(palette.grayIcon) }, + tab_chats_active: { NSImage(named: "Icon_TabChatList")!.precomposed(palette.accentIcon) }, + tab_chats_active_filters: { generateChatTabFiltersIcon(NSImage(named: "Icon_TabChatList")!.precomposed(palette.accentIcon)) }, + tab_settings: { NSImage(named: "Icon_TabSettings")!.precomposed(palette.grayIcon) }, + tab_settings_active: { NSImage(named: "Icon_TabSettings")!.precomposed(palette.accentIcon) }, + profile_add_member: { generateProfileIcon(NSImage(named: "Icon_Profile_AddMember")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_call: { generateProfileIcon(NSImage(named: "Icon_Profile_Call")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_leave: { generateProfileIcon(NSImage(named: "Icon_Profile_Leave")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_message: { generateProfileIcon(NSImage(named: "Icon_Profile_Message")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_more: { generateProfileIcon(NSImage(named: "Icon_Profile_More")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_mute: { generateProfileIcon(NSImage(named: "Icon_Profile_Mute")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_unmute: { generateProfileIcon(NSImage(named: "Icon_Profile_Unmute")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_search: { generateProfileIcon(NSImage(named: "Icon_Profile_Search")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_secret_chat: { generateProfileIcon(NSImage(named: "Icon_Profile_SecretChat")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_edit_photo: { NSImage(named: "Icon_Profile_EditPhoto")!.precomposed(.white)}, + profile_block: { generateProfileIcon(NSImage(named: "Icon_Profile_Block")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_report: { generateProfileIcon(NSImage(named: "Icon_Profile_Report")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_share: { generateProfileIcon(NSImage(named: "Icon_Profile_Share")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_stats: { generateProfileIcon(NSImage(named: "Icon_Profile_Stats")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + profile_unblock: { generateProfileIcon(NSImage(named: "Icon_Profile_Unblock")!.precomposed(palette.accentIcon), backgroundColor: palette.underSelectedColor) }, + chat_quiz_explanation: { NSImage(named: "Icon_QuizExplanation")!.precomposed(palette.accentIcon) }, + chat_quiz_explanation_bubble_incoming: { NSImage(named: "Icon_QuizExplanation")!.precomposed(palette.accentIconBubble_incoming) }, + chat_quiz_explanation_bubble_outgoing: { NSImage(named: "Icon_QuizExplanation")!.precomposed(palette.accentIconBubble_outgoing) }, + stickers_add_featured: { NSImage(named: "Icon_AddFeaturedStickers")!.precomposed(palette.grayIcon) }, + channel_info_promo: { NSImage(named: "Icon_ChannelPromoInfo")!.precomposed(palette.grayIcon) }, + channel_info_promo_bubble_incoming: { NSImage(named: "Icon_ChannelPromoInfo")!.precomposed(palette.grayTextBubble_incoming) }, + channel_info_promo_bubble_outgoing: { NSImage(named: "Icon_ChannelPromoInfo")!.precomposed(palette.grayTextBubble_outgoing) }, + chat_share_message: { NSImage(named: "Icon_ChannelShare")!.precomposed(palette.accent) }, + chat_goto_message: { NSImage(named: "Icon_ChatGoMessage")!.precomposed(palette.accentIcon) }, + chat_swipe_reply: { NSImage(named: "Icon_ChannelShare")!.precomposed(palette.accentIcon, flipHorizontal: true) }, + chat_like_message: { NSImage(named: "Icon_Like_MessageButton")!.precomposed(palette.accentIcon) }, + chat_like_message_unlike: { NSImage(named: "Icon_Like_MessageButtonUnlike")!.precomposed(palette.accentIcon) }, + chat_like_inside: { NSImage(named: "Icon_Like_MessageInside")!.precomposed(palette.redUI, flipVertical: true) }, + chat_like_inside_bubble_incoming: { NSImage(named: "Icon_Like_MessageInside")!.precomposed(palette.redBubble_incoming, flipVertical: true) }, + chat_like_inside_bubble_outgoing: { NSImage(named: "Icon_Like_MessageInside")!.precomposed(palette.redBubble_outgoing, flipVertical: true) }, + chat_like_inside_bubble_overlay: { NSImage(named: "Icon_Like_MessageInside")!.precomposed(.white, flipVertical: true) }, + chat_like_inside_empty: { NSImage(named: "Icon_Like_MessageInsideEmpty")!.precomposed(palette.grayIcon, flipVertical: true) }, + chat_like_inside_empty_bubble_incoming: { NSImage(named: "Icon_Like_MessageInsideEmpty")!.precomposed(palette.grayIconBubble_incoming, flipVertical: true) }, + chat_like_inside_empty_bubble_outgoing: { NSImage(named: "Icon_Like_MessageInsideEmpty")!.precomposed(palette.grayIconBubble_outgoing, flipVertical: true) }, + chat_like_inside_empty_bubble_overlay: { NSImage(named: "Icon_Like_MessageInsideEmpty")!.precomposed(.white, flipVertical: true) }, + gif_trending: { NSImage(named: "Icon_GifTrending")!.precomposed(palette.grayIcon) }, + chat_list_thumb_play: { NSImage(named: "Icon_ChatListThumbPlay")!.precomposed() } + ) -private func generateIcons(from pallete: ColorPallete) -> TelegramIconsTheme { - return TelegramIconsTheme(dialogMuteImage: #imageLiteral(resourceName: "Icon_DialogMute").precomposed(pallete.grayIcon), - dialogMuteImageSelected: #imageLiteral(resourceName: "Icon_DialogMute").precomposed(.white), - outgoingMessageImage: #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(pallete.blueIcon, flipVertical:true), - readMessageImage: #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(pallete.blueIcon, flipVertical:true), - outgoingMessageImageSelected: #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(.white, flipVertical:true), - readMessageImageSelected: #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(.white, flipVertical:true), - sendingImage: #imageLiteral(resourceName: "Icon_ChatStateSending").precomposed(pallete.grayIcon, flipVertical:true), - sendingImageSelected: #imageLiteral(resourceName: "Icon_ChatStateSending").precomposed(.white, flipVertical:true), - secretImage:#imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(pallete.blueIcon, flipVertical:true), - secretImageSelected: #imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(.white, flipVertical:true), - pinnedImage: #imageLiteral(resourceName: "Icon_ChatListPinned").precomposed(pallete.grayIcon, flipVertical:true), - pinnedImageSelected: #imageLiteral(resourceName: "Icon_ChatListPinned").precomposed(.white, flipVertical:true), - verifiedImage: #imageLiteral(resourceName: "Icon_VerifyPeer").precomposed(flipVertical: true), - verifiedImageSelected: #imageLiteral(resourceName: "Icon_VerifyPeerActive").precomposed(flipVertical: true), - errorImage: #imageLiteral(resourceName: "Icon_DialogSendingError").precomposed(flipVertical: true), - errorImageSelected: #imageLiteral(resourceName: "Icon_MessageSentFailed").precomposed(flipVertical: true), - chatSearch: #imageLiteral(resourceName: "Icon_SearchChatMessages").precomposed(pallete.blueIcon), - chatCall: #imageLiteral(resourceName: "Icon_callNavigationHeader").precomposed(pallete.blueIcon), - chatActions: #imageLiteral(resourceName: "Icon_ChatActions").precomposed(pallete.blueIcon), - chatOutgoingFailedCall: #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(pallete.redUI), - chatIncomingFailedCall: #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(pallete.redUI), - chatOutgoingCall: #imageLiteral(resourceName: "Icon_MessageCallOutgoing").precomposed(pallete.greenUI), - chatIncomingCall: #imageLiteral(resourceName: "Icon_MessageCallIncoming").precomposed(pallete.greenUI), - chatFallbackCall: #imageLiteral(resourceName: "Icon_MessageCall").precomposed(pallete.blueUI), - chatToggleSelected: #imageLiteral(resourceName: "Icon_Check").precomposed(pallete.blueIcon), - chatToggleUnselected: #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed(), - chatShare: #imageLiteral(resourceName: "Icon_ChannelShare").precomposed(pallete.blueIcon), - chatMusicPlay: #imageLiteral(resourceName: "Icon_ChatMusicPlay").precomposed(), - chatMusicPause: #imageLiteral(resourceName: "Icon_ChatMusicPause").precomposed(), - composeNewChat:#imageLiteral(resourceName: "Icon_NewMessage").precomposed(pallete.blueIcon), - composeNewChatActive:#imageLiteral(resourceName: "Icon_NewMessage").precomposed(.white), - composeNewGroup:#imageLiteral(resourceName: "Icon_NewGroup").precomposed(pallete.blueIcon), - composeNewSecretChat: #imageLiteral(resourceName: "Icon_NewSecretChat").precomposed(pallete.blueIcon), - composeNewChannel: #imageLiteral(resourceName: "Icon_NewChannel").precomposed(pallete.blueIcon), - contactsNewContact: #imageLiteral(resourceName: "Icon_NewContact").precomposed(pallete.blueIcon), - chatReadMark1: #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(pallete.blueIcon), - chatReadMark2: #imageLiteral(resourceName: "Icon_MessageCheckmark2").precomposed(pallete.blueIcon), - sentFailed: #imageLiteral(resourceName: "Icon_MessageSentFailed").precomposed(), - chatChannelViews: #imageLiteral(resourceName: "Icon_ChannelViews").precomposed(pallete.grayIcon, flipVertical: true), - chatNavigationBack: #imageLiteral(resourceName: "Icon_ChatNavigationBack").precomposed(pallete.blueIcon), - peerInfoAddMember: #imageLiteral(resourceName: "Icon_GroupInfoAddMember").precomposed(pallete.blueIcon, flipVertical: true), - chatSearchUp: #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(pallete.blueIcon), - chatSearchUpDisabled: #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(pallete.grayIcon), - chatSearchDown: #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(pallete.blueIcon, flipVertical:true), - chatSearchDownDisabled: #imageLiteral(resourceName: "Icon_SearchArrow").precomposed(pallete.grayIcon, flipVertical:true), - chatSearchCalendar: #imageLiteral(resourceName: "Icon_Calendar").precomposed(pallete.blueIcon), - dismissAccessory: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.grayIcon), - chatScrollUp: generateChatScrolldownImage(backgroundColor: pallete.background, borderColor: pallete.grayIcon, arrowColor: pallete.grayIcon), - chatScrollUpActive: generateChatScrolldownImage(backgroundColor: pallete.background, borderColor: pallete.blueIcon, arrowColor: pallete.blueIcon), - audioPlayerPlay: #imageLiteral(resourceName: "Icon_InlinePlayerPlay").precomposed(pallete.blueIcon), - audioPlayerPause: #imageLiteral(resourceName: "Icon_InlinePlayerPause").precomposed(pallete.blueIcon), - audioPlayerNext: #imageLiteral(resourceName: "Icon_InlinePlayerNext").precomposed(pallete.blueIcon), - audioPlayerPrev: #imageLiteral(resourceName: "Icon_InlinePlayerPrevious").precomposed(pallete.blueIcon), - auduiPlayerDismiss: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.blueIcon), - audioPlayerRepeat: #imageLiteral(resourceName: "Icon_RepeatAudio").precomposed(pallete.grayIcon), - audioPlayerRepeatActive: #imageLiteral(resourceName: "Icon_RepeatAudio").precomposed(pallete.blueIcon), - audioPlayerLockedPlay: #imageLiteral(resourceName: "Icon_InlinePlayerPlay").precomposed(pallete.grayIcon), - audioPlayerLockedNext: #imageLiteral(resourceName: "Icon_InlinePlayerNext").precomposed(pallete.grayIcon), - audioPlayerLockedPrev: #imageLiteral(resourceName: "Icon_InlinePlayerPrevious").precomposed(pallete.grayIcon), - chatSendMessage: #imageLiteral(resourceName: "Icon_SendMessage").precomposed(pallete.blueIcon), - chatRecordVoice: #imageLiteral(resourceName: "Icon_RecordVoice").precomposed(pallete.grayIcon), - chatEntertainment: #imageLiteral(resourceName: "Icon_Entertainments").precomposed(pallete.grayIcon), - chatInlineDismiss: #imageLiteral(resourceName: "Icon_InlineResultCancel").precomposed(pallete.grayIcon), - chatActiveReplyMarkup: #imageLiteral(resourceName: "Icon_ReplyMarkupButton").precomposed(pallete.blueIcon), - chatDisabledReplyMarkup: #imageLiteral(resourceName: "Icon_ReplyMarkupButton").precomposed(pallete.grayIcon), - chatSecretTimer: #imageLiteral(resourceName: "Icon_SecretTimer").precomposed(pallete.grayIcon), - chatForwardMessagesActive: #imageLiteral(resourceName: "Icon_MessageActionPanelForward").precomposed(pallete.blueIcon), - chatForwardMessagesInactive: #imageLiteral(resourceName: "Icon_MessageActionPanelForward").precomposed(pallete.grayIcon), - chatDeleteMessagesActive: #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(pallete.redUI), - chatDeleteMessagesInactive: #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(pallete.grayIcon), - generalNext: #imageLiteral(resourceName: "Icon_GeneralNext").precomposed(pallete.grayIcon), - generalSelect: #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(pallete.blueIcon), - chatVoiceRecording: #imageLiteral(resourceName: "Icon_RecordingVoice").precomposed(pallete.blueIcon), - chatVideoRecording: #imageLiteral(resourceName: "Icon_RecordVideoMessage").precomposed(pallete.blueIcon), - chatRecord: #imageLiteral(resourceName: "Icon_RecordVoice").precomposed(pallete.grayIcon), - deleteItem: deleteItemIcon(pallete.redUI), - deleteItemDisabled: deleteItemIcon(pallete.grayTransparent), - chatAttach: #imageLiteral(resourceName: "Icon_ChatAttach").precomposed(pallete.grayIcon), - chatAttachFile: #imageLiteral(resourceName: "Icon_AttachFile").precomposed(pallete.blueIcon), - chatAttachPhoto: #imageLiteral(resourceName: "Icon_AttachPhoto").precomposed(pallete.blueIcon), - chatAttachCamera: #imageLiteral(resourceName: "Icon_AttachCamera").precomposed(pallete.blueIcon), - chatAttachLocation: #imageLiteral(resourceName: "Icon_AttachLocation").precomposed(pallete.blueIcon), - mediaEmptyShared: #imageLiteral(resourceName: "Icon_EmptySharedMedia").precomposed(pallete.grayIcon), - mediaEmptyFiles: #imageLiteral(resourceName: "Icon_EmptySharedFiles").precomposed(), - mediaEmptyMusic: #imageLiteral(resourceName: "Icon_EmptySharedMusic").precomposed(pallete.grayIcon), - mediaEmptyLinks: #imageLiteral(resourceName: "Icon_EmptySharedLinks").precomposed(pallete.grayIcon), - mediaDropdown: #imageLiteral(resourceName: "Icon_DropdownArrow").precomposed(pallete.blueIcon), - stickersAddFeatured: #imageLiteral(resourceName: "Icon_GroupInfoAddMember").precomposed(pallete.blueIcon), - stickersAddedFeatured: #imageLiteral(resourceName: "Icon_UsernameAvailability").precomposed(pallete.grayIcon), - stickersRemove: #imageLiteral(resourceName: "Icon_InlineResultCancel").precomposed(pallete.grayIcon), - peerMediaDownloadFileStart: #imageLiteral(resourceName: "Icon_MediaDownload").precomposed(pallete.blueIcon), - peerMediaDownloadFilePause: downloadFilePauseIcon(pallete.blueIcon), - stickersShare: #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(pallete.blueIcon), - emojiRecentTab: #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiSmileTab: #imageLiteral(resourceName: "Icon_EmojiTabSmiles").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiNatureTab: #imageLiteral(resourceName: "Icon_EmojiTabNature").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiFoodTab: #imageLiteral(resourceName: "Icon_EmojiTabFood").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiSportTab: #imageLiteral(resourceName: "Icon_EmojiTabSports").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiCarTab: #imageLiteral(resourceName: "Icon_EmojiTabCar").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiObjectsTab: #imageLiteral(resourceName: "Icon_EmojiTabObjects").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiSymbolsTab: #imageLiteral(resourceName: "Icon_EmojiTabSymbols").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiFlagsTab: #imageLiteral(resourceName: "Icon_EmojiTabFlag").precomposed(pallete.grayIcon, flipVertical:true, flipHorizontal:true), - emojiRecentTabActive: #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiSmileTabActive: #imageLiteral(resourceName: "Icon_EmojiTabSmiles").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiNatureTabActive: #imageLiteral(resourceName: "Icon_EmojiTabNature").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiFoodTabActive: #imageLiteral(resourceName: "Icon_EmojiTabFood").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiSportTabActive: #imageLiteral(resourceName: "Icon_EmojiTabSports").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiCarTabActive: #imageLiteral(resourceName: "Icon_EmojiTabCar").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiObjectsTabActive: #imageLiteral(resourceName: "Icon_EmojiTabObjects").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiSymbolsTabActive: #imageLiteral(resourceName: "Icon_EmojiTabSymbols").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - emojiFlagsTabActive: #imageLiteral(resourceName: "Icon_EmojiTabFlag").precomposed(pallete.blueIcon, flipVertical:true, flipHorizontal:true), - stickerBackground: generateStickerBackground(NSMakeSize(83, 83), pallete.background), - stickerBackgroundActive: generateStickerBackground(NSMakeSize(83, 83), pallete.grayBackground), - stickersTabRecent: #imageLiteral(resourceName: "Icon_EmojiTabRecent").precomposed(pallete.grayIcon), - stickersTabGIF: #imageLiteral(resourceName: "Icon_GifToggle").precomposed(pallete.grayIcon), - chatSendingFrame: generateSendingFrame(pallete.grayIcon), - chatSendingHour: generateSendingHour(pallete.grayIcon), - chatSendingMin: generateSendingMin(pallete.grayIcon), - chatActionUrl: #imageLiteral(resourceName: "Icon_InlineBotUrl").precomposed(pallete.text), - callInlineDecline: #imageLiteral(resourceName: "Icon_CallDecline_Inline").precomposed(.white), - callInlineMuted: #imageLiteral(resourceName: "Icon_CallMute_Inline").precomposed(.white), - callInlineUnmuted: #imageLiteral(resourceName: "Icon_CallUnmuted_Inline").precomposed(.white), - eventLogTriangle: generateRecentActionsTriangle(pallete.text), - channelIntro: #imageLiteral(resourceName: "Icon_ChannelIntro").precomposed(), - chatFileThumb: #imageLiteral(resourceName: "Icon_MessageFile").precomposed(flipVertical:true), - chatSecretThumb: #imageLiteral(resourceName: "Icon_SecretAutoremoveMedia").precomposed(.black, flipVertical:true), - chatMapPin: #imageLiteral(resourceName: "Icon_MapPinned").precomposed(), - chatSecretTitle: #imageLiteral(resourceName: "Icon_SecretChatLock").precomposed(pallete.text, flipVertical:true), - emptySearch: #imageLiteral(resourceName: "Icon_EmptySearchResults").precomposed(pallete.grayIcon), - calendarBack: #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(pallete.blueIcon), - calendarNext: #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(pallete.blueIcon, flipHorizontal: true), - calendarBackDisabled: #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(pallete.grayIcon), - calendarNextDisabled: #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(pallete.grayIcon, flipHorizontal: true), - newChatCamera: #imageLiteral(resourceName: "Icon_AttachCamera").precomposed(pallete.grayIcon), - peerInfoVerify: #imageLiteral(resourceName: "Icon_VerifyPeer").precomposed(flipVertical: true), - peerInfoCall: #imageLiteral(resourceName: "Icon_ProfileCall").precomposed(pallete.blueUI), - callOutgoing: #imageLiteral(resourceName: "Icon_CallOutgoing").precomposed(pallete.grayIcon, flipVertical: true), - recentDismiss: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(pallete.grayIcon), - recentDismissActive: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(.white), - webgameShare: #imageLiteral(resourceName: "Icon_ShareExternal").precomposed(pallete.blueIcon), - chatSearchCancel: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.blueIcon), - chatSearchFrom: #imageLiteral(resourceName: "Icon_ChatSearchFrom").precomposed(pallete.blueIcon), - callWindowDecline: #imageLiteral(resourceName: "Icon_CallDecline_Window").precomposed(), - callWindowAccept: #imageLiteral(resourceName: "Icon_CallAccept_Window").precomposed(), - callWindowMute: #imageLiteral(resourceName: "Icon_CallMic_Window").precomposed(), - callWindowUnmute: #imageLiteral(resourceName: "Icon_CallMute_Inline").precomposed(), - callWindowClose: #imageLiteral(resourceName: "Icon_CallWindowClose").precomposed(.white), - callWindowDeviceSettings: #imageLiteral(resourceName: "Icon_CallDeviceSettings").precomposed(.white), - callWindowCancel: #imageLiteral(resourceName: "Icon_CallCancelIcon").precomposed(.white), - chatActionEdit: #imageLiteral(resourceName: "Icon_ChatActionEdit").precomposed(pallete.blueIcon), - chatActionInfo: #imageLiteral(resourceName: "Icon_ChatActionInfo").precomposed(pallete.blueIcon), - chatActionMute: #imageLiteral(resourceName: "Icon_ChatActionMute").precomposed(pallete.blueIcon), - chatActionUnmute: #imageLiteral(resourceName: "Icon_ChatActionUnmute").precomposed(pallete.blueIcon), - chatActionClearHistory: #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(pallete.blueIcon), - dismissPinned: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.blueIcon), - chatActionsActive: #imageLiteral(resourceName: "Icon_ChatActionsActive").precomposed(pallete.blueIcon), - chatEntertainmentSticker: #imageLiteral(resourceName: "Icon_ChatEntertainmentSticker").precomposed(pallete.grayIcon), - chatEmpty: #imageLiteral(resourceName: "Icon_EmptyChat").precomposed(pallete.grayForeground), - stickerPackClose: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.blueIcon), - stickerPackDelete: #imageLiteral(resourceName: "Icon_MessageActionPanelDelete").precomposed(pallete.blueIcon), - modalShare: #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(pallete.blueIcon), - modalClose: #imageLiteral(resourceName: "Icon_ChatSearchCancel").precomposed(pallete.blueIcon), - ivChannelJoined: #imageLiteral(resourceName: "Icon_MessageCheckMark1").precomposed(.white), - chatListMention: generateBadgeMention(backgroundColor: pallete.blueUI, foregroundColor: pallete.background), - chatListMentionActive: generateBadgeMention(backgroundColor: pallete.background, foregroundColor: pallete.blueUI), - chatMention: generateChatMention(backgroundColor: pallete.background, border: pallete.grayIcon, foregroundColor: pallete.grayIcon), - chatMentionActive: generateChatMention(backgroundColor: pallete.background, border: pallete.blueIcon, foregroundColor: pallete.blueIcon), - sliderControl: #imageLiteral(resourceName: "Icon_SliderNormal").precomposed(), - sliderControlActive: #imageLiteral(resourceName: "Icon_SliderNormal").precomposed(), - stickersTabFave: #imageLiteral(resourceName: "Icon_FaveStickers").precomposed(pallete.grayIcon), - chatInstantView: #imageLiteral(resourceName: "Icon_ChatIV").precomposed(pallete.blueIcon), - instantViewShare: #imageLiteral(resourceName: "Icon_ShareStickerPack").precomposed(pallete.blueIcon), - instantViewActions: #imageLiteral(resourceName: "Icon_ChatActions").precomposed(pallete.blueIcon), - instantViewActionsActive: #imageLiteral(resourceName: "Icon_ChatActionsActive").precomposed(pallete.blueIcon), - instantViewSafari: #imageLiteral(resourceName: "Icon_InstantViewSafari").precomposed(pallete.blueIcon), - instantViewBack: #imageLiteral(resourceName: "Icon_InstantViewBack").precomposed(pallete.blueIcon), - instantViewCheck: #imageLiteral(resourceName: "Icon_InstantViewCheck").precomposed(pallete.blueIcon), - groupStickerNotFound: #imageLiteral(resourceName: "Icon_GroupStickerNotFound").precomposed(pallete.grayIcon), - settingsAskQuestion: #imageLiteral(resourceName: "Icon_SettingsAskQuestion").precomposed(pallete.blueIcon, flipVertical: true), - settingsBio: #imageLiteral(resourceName: "Icon_SettingsBio").precomposed(pallete.blueIcon, flipVertical: true), - settingsEditInfo: #imageLiteral(resourceName: "Icon_SettingsEditInfo").precomposed(pallete.blueIcon), - settingsFaq: #imageLiteral(resourceName: "Icon_SettingsFaq").precomposed(pallete.blueIcon, flipVertical: true), - settingsGeneral: #imageLiteral(resourceName: "Icon_SettingsGeneral").precomposed(pallete.blueIcon, flipVertical: true), - settingsLanguage: #imageLiteral(resourceName: "Icon_SettingsLanguage").precomposed(pallete.blueIcon, flipVertical: true), - settingsNotifications: #imageLiteral(resourceName: "Icon_SettingsNotifications").precomposed(pallete.blueIcon, flipVertical: true), - settingsPhoneNumber: #imageLiteral(resourceName: "Icon_SettingsPhoneNumber").precomposed(pallete.blueIcon, flipVertical: true), - settingsSecurity: #imageLiteral(resourceName: "Icon_SettingsSecurity").precomposed(pallete.blueIcon, flipVertical: true), - settingsStickers: #imageLiteral(resourceName: "Icon_SettingsStickers").precomposed(pallete.blueIcon, flipVertical: true), - settingsStorage: #imageLiteral(resourceName: "Icon_SettingsStorage").precomposed(pallete.blueIcon, flipVertical: true), - settingsUsername: #imageLiteral(resourceName: "Icon_SettingsUsername").precomposed(pallete.blueIcon, flipVertical: true), - generalCheck: #imageLiteral(resourceName: "Icon_Check").precomposed(pallete.blueIcon), - settingsAbout: #imageLiteral(resourceName: "Icon_SettingsAbout").precomposed(pallete.blueIcon), - settingsLogout: #imageLiteral(resourceName: "Icon_SettingsLogout").precomposed(pallete.redUI), - fastSettingsLock: #imageLiteral(resourceName: "Icon_FastSettingsLock").precomposed(pallete.blueIcon), - fastSettingsDark: #imageLiteral(resourceName: "Icon_FastSettingsDark").precomposed(pallete.blueIcon), - fastSettingsSunny: #imageLiteral(resourceName: "Icon_FastSettingsSunny").precomposed(pallete.blueIcon), - fastSettingsMute: #imageLiteral(resourceName: "Icon_ChatActionMute").precomposed(pallete.blueIcon), - fastSettingsUnmute: #imageLiteral(resourceName: "Icon_ChatActionUnmute").precomposed(pallete.blueIcon), - chatRecordVideo: #imageLiteral(resourceName: "Icon_RecordVideoMessage").precomposed(pallete.grayIcon), - inputChannelMute: #imageLiteral(resourceName: "Icon_InputChannelMute").precomposed(pallete.grayIcon), - inputChannelUnmute: #imageLiteral(resourceName: "Icon_InputChannelUnmute").precomposed(pallete.grayIcon), - changePhoneNumberIntro: #imageLiteral(resourceName: "Icon_ChangeNumberIntro").precomposed()) -} - - -private func generateTheme(pallete: ColorPallete, dark: Bool, fontSize: CGFloat) -> TelegramPresentationTheme { - - let chatList = TelegramChatListTheme(selectedBackgroundColor: pallete.blueSelect, - singleLayoutSelectedBackgroundColor: pallete.grayBackground, - activeDraggingBackgroundColor: pallete.border, - pinnedBackgroundColor: pallete.background, - contextMenuBackgroundColor: pallete.background, - textColor: pallete.text, - grayTextColor: pallete.grayText, - secretChatTextColor: pallete.blueUI, - peerTextColor: pallete.text, - activityColor: pallete.blueUI, - activitySelectedColor: .white, - activityContextMenuColor: pallete.blueUI, - activityPinnedColor: pallete.blueUI, - badgeTextColor: pallete.background, - badgeBackgroundColor: pallete.badge, - badgeSelectedTextColor: pallete.blueSelect, - badgeSelectedBackgroundColor: .white, +} +func generateTheme(palette: ColorPalette, cloudTheme: TelegramTheme?, bubbled: Bool, fontSize: CGFloat, wallpaper: ThemeWallpaper) -> TelegramPresentationTheme { + + let chatList = TelegramChatListTheme(selectedBackgroundColor: palette.accentSelect, + singleLayoutSelectedBackgroundColor: palette.grayBackground, + activeDraggingBackgroundColor: palette.border, + pinnedBackgroundColor: palette.background, + contextMenuBackgroundColor: palette.background, + textColor: palette.text, + grayTextColor: palette.grayText, + secretChatTextColor: palette.accent, + peerTextColor: palette.text, + activityColor: palette.accent, + activitySelectedColor: palette.underSelectedColor, + activityContextMenuColor: palette.accent, + activityPinnedColor: palette.accent, + badgeTextColor: palette.background, + badgeBackgroundColor: palette.badge, + badgeSelectedTextColor: palette.accentSelect, + badgeSelectedBackgroundColor: palette.underSelectedColor, badgeMutedTextColor: .white, - badgeMutedBackgroundColor: pallete.badgeMuted) + badgeMutedBackgroundColor: palette.badgeMuted) - let tabBar = TelegramTabBarTheme(color: pallete.grayIcon, selectedColor: pallete.blueIcon, badgeTextColor: .white, badgeColor: pallete.redUI) - return TelegramPresentationTheme(colors: pallete, search: SearchTheme(pallete.grayBackground, #imageLiteral(resourceName: "Icon_SearchField").precomposed(pallete.grayIcon), #imageLiteral(resourceName: "Icon_SearchClear").precomposed(pallete.grayIcon), tr(.searchFieldSearch), pallete.text, pallete.grayText), chatList: chatList, tabBar: tabBar, icons: generateIcons(from: pallete), dark: dark, fontSize: fontSize) + let tabBar = TelegramTabBarTheme(color: palette.grayIcon, selectedColor: palette.accentIcon, badgeTextColor: .white, badgeColor: palette.redUI) + return TelegramPresentationTheme(colors: palette, cloudTheme: cloudTheme, search: SearchTheme(palette.grayBackground, #imageLiteral(resourceName: "Icon_SearchField").precomposed(palette.grayIcon), #imageLiteral(resourceName: "Icon_SearchClear").precomposed(palette.grayIcon), { L10n.searchFieldSearch }, palette.text, palette.grayText), chatList: chatList, tabBar: tabBar, icons: generateIcons(from: palette, bubbled: bubbled), bubbled: bubbled, fontSize: fontSize, wallpaper: wallpaper) } -func updateTheme(with settings: ThemePalleteSettings, for window: Window? = nil, animated: Bool = false) { - telegramUpdateTheme(generateTheme(pallete: settings.dark ? darkPallete : whitePallete, dark: settings.dark, fontSize: settings.fontSize), window: window, animated: animated) +func updateTheme(with settings: ThemePaletteSettings, for window: Window? = nil, animated: Bool = false) { + let palette: ColorPalette + switch settings.palette.name { + case whitePalette.name: + if settings.palette.accent == whitePalette.accent { + palette = whitePalette + } else { + palette = settings.palette + } + case darkPalette.name: + if settings.palette.accent == darkPalette.accent { + palette = darkPalette + } else { + palette = settings.palette + } + case dayClassicPalette.name: + if settings.palette.accent == dayClassicPalette.accent { + palette = dayClassicPalette + } else { + palette = settings.palette + } + case nightAccentPalette.name: + if settings.palette.accent == nightAccentPalette.accent { + palette = nightAccentPalette + } else { + palette = settings.palette + } + case systemPalette.name: + palette = systemPalette + default: + palette = settings.palette + } + telegramUpdateTheme(generateTheme(palette: palette, cloudTheme: settings.cloudTheme, bubbled: settings.bubbled, fontSize: settings.fontSize, wallpaper: settings.wallpaper), window: window, animated: animated) } private let appearanceDisposable = MetaDisposable() @@ -779,46 +2328,39 @@ private func telegramUpdateTheme(_ theme: TelegramPresentationTheme, window: Win if let window = window { if animated, let contentView = window.contentView { - - var indexes:[Int] = [] - for i in 0 ..< contentView.subviews.count { - if contentView.subviews[i] is ImageView { - indexes.insert(i, at: 0) - } - } - - for index in indexes { - contentView.subviews[index].removeFromSuperview() - } - + let image = window.windowImageShot() let imageView = ImageView() imageView.image = image - imageView.frame = contentView.superview!.bounds + imageView.frame = window.bounds contentView.addSubview(imageView) - appearanceDisposable.set((Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(completed: { [weak imageView] in - if let strongImageView = imageView { - strongImageView.change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, completion: { [weak strongImageView] completed in - strongImageView?.removeFromSuperview() + let signal = Signal.single(Void()) |> delay(0.25, queue: Queue.mainQueue()) |> afterDisposed { [weak imageView] in + if let imageView = imageView { + imageView.change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, completion: { [weak imageView] completed in + imageView?.removeFromSuperview() }) } + } - })) + appearanceDisposable.set(signal.start()) } window.contentView?.background = theme.colors.background window.contentView?.subviews.first?.background = theme.colors.background window.appearance = theme.appearance + +// NSAppearance.current = theme.appearance + // window.titl window.backgroundColor = theme.colors.grayBackground - window.titlebarAppearsTransparent = theme.dark + window.titlebarAppearsTransparent = true//theme.dark + } _themeSignal.set(theme) } func setDefaultTheme(for window: Window? = nil) { - telegramUpdateTheme(generateTheme(pallete: whitePallete, dark: false, fontSize: 13.0), window: window, animated: false) + telegramUpdateTheme(generateTheme(palette: dayClassicPalette, cloudTheme: nil, bubbled: false, fontSize: 13.0, wallpaper: ThemeWallpaper()), window: window, animated: false) } - diff --git a/Telegram-Mac/AppearanceThumbs.swift b/Telegram-Mac/AppearanceThumbs.swift new file mode 100644 index 0000000000..84a6afc6bb --- /dev/null +++ b/Telegram-Mac/AppearanceThumbs.swift @@ -0,0 +1,282 @@ +// +// AppearanceThumbs.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit +import Postbox + +private func cloudThemeData(context: AccountContext, theme: TelegramTheme, file: TelegramMediaFile) -> Signal<(ColorPalette, Wallpaper, TelegramWallpaper?), NoError> { + return Signal { subscriber in + + let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.theme(theme: ThemeReference.slug(theme.slug), resource: file.resource)).start() + let wallpaperDisposable = DisposableSet() + + let resourceData = context.account.postbox.mediaBox.resourceData(file.resource) |> filter { $0.complete } |> take(1) + + let dataDisposable = resourceData.start(next: { data in + if let palette = importPalette(data.path) { + var wallpaper: Signal = .single(nil) + var newSettings: WallpaperSettings = WallpaperSettings() + switch palette.wallpaper { + case .none: + wallpaper = .single(nil) + case .builtin: + wallpaper = .single(.builtin(newSettings)) + case let .color(color): + wallpaper = .single(.color(color.argb)) + case let .url(string): + let link = inApp(for: string as NSString, context: context) + switch link { + case let .wallpaper(values): + switch values.preview { + case let .slug(slug, settings): + wallpaper = getWallpaper(network: context.account.network, slug: slug) |> map(Optional.init) + newSettings = settings + default: + break + } + default: + break + } + } + + wallpaperDisposable.add(wallpaper.start(next: { cloud in + if let cloud = cloud { + let wp = Wallpaper(cloud).withUpdatedSettings(newSettings) + wallpaperDisposable.add(moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wp).start(next: { wallpaper in + subscriber.putNext((palette, wallpaper, cloud)) + subscriber.putCompletion() + })) + } else { + subscriber.putNext((palette, .none, nil)) + subscriber.putCompletion() + } + }, error: { _ in + subscriber.putCompletion() + })) + } + + }) + + return ActionDisposable { + fetchDisposable.dispose() + dataDisposable.dispose() + wallpaperDisposable.dispose() + } + } +} + + +private func cloudThemeCrossplatformData(context: AccountContext, settings: TelegramThemeSettings) -> Signal<(ColorPalette, Wallpaper, TelegramWallpaper?), NoError> { + + let palette = settings.palette + let wallpaper: Wallpaper = settings.wallpaper?.uiWallpaper ?? .none + let cloud = settings.wallpaper + return moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wallpaper) |> map { wallpaper in + return (palette, wallpaper, cloud) + } +} + + +private func generateThumb(palette: ColorPalette, bubbled: Bool, wallpaper: Wallpaper) -> Signal { + return Signal { subscriber in + let image = generateImage(NSMakeSize(80, 55), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, 10) + + + let backgroundMode: TableBackgroundMode + if bubbled { + switch wallpaper { + case .builtin: + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + case let.color(color): + backgroundMode = .color(color: NSColor(argb: color).withAlphaComponent(1.0)) + case let .gradient(top, bottom, rotation): + backgroundMode = .gradient(top: NSColor(argb: top).withAlphaComponent(1.0), bottom: NSColor(argb: bottom).withAlphaComponent(1.0), rotation: rotation) + case let .image(representation, settings): + if let resource = largestImageRepresentation(representation)?.resource, let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(resource, settings: settings))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } + + case let .file(_, file, settings, isPattern): + if let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(file.resource, settings: settings))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } + case .none: + backgroundMode = .color(color: palette.chatBackground) + case let .custom(representation, blurred): + if let image = NSImage(contentsOf: URL(fileURLWithPath: wallpaperPath(representation.resource, settings: WallpaperSettings(blur: blurred)))) { + backgroundMode = .background(image: image) + } else { + backgroundMode = .background(image: #imageLiteral(resourceName: "builtin-wallpaper-0.jpg")) + } + } + } else { + backgroundMode = .color(color: palette.chatBackground) + } + + func applyBubbles() { + let bubbleImage = NSImage(named: "Icon_ThemeBubble") + if let incoming = bubbleImage?.precomposed(palette.bubbleBackground_incoming, flipVertical: true) { + ctx.draw(incoming, in: NSMakeRect(7, 9, 48, 16)) + } + if let outgoing = bubbleImage?.precomposed(palette.bubbleBackgroundTop_outgoing, bottomColor: palette.bubbleBackgroundBottom_outgoing, flipVertical: true, flipHorizontal: true) { + ctx.draw(outgoing, in: NSMakeRect(size.width - 57, size.height - 24, 48, 16)) + } + } + + func applyPlain() { + ctx.setFillColor(palette.accent.cgColor) + ctx.fillEllipse(in: NSMakeRect(10, 7, 17, 17)) + + if true { + let name1 = generateImage(NSMakeSize(20, 4), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, 2) + ctx.setFillColor(palette.accent.cgColor) + ctx.fill(rect) + })! + ctx.draw(name1, in: NSMakeRect(10 + 17 + 3, 7 + 2, name1.backingSize.width, name1.backingSize.height)) + + let text1 = generateImage(NSMakeSize(40, 4), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, 2) + ctx.setFillColor(palette.grayText.withAlphaComponent(0.5).cgColor) + ctx.fill(rect) + })! + ctx.draw(text1, in: NSMakeRect(10 + 17 + 3, 7 + 2 + 4 + 4, text1.backingSize.width, text1.backingSize.height)) + } + + if true { + ctx.setFillColor(palette.accent.cgColor) + ctx.fillEllipse(in: NSMakeRect(10, 7 + 17 + 7, 17, 17)) + + let name1 = generateImage(NSMakeSize(20, 4), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, 2) + ctx.setFillColor(palette.accent.cgColor) + ctx.fill(rect) + })! + ctx.draw(name1, in: NSMakeRect(10 + 17 + 3, 7 + 17 + 7 + 2, name1.backingSize.width, name1.backingSize.height)) + + let text1 = generateImage(NSMakeSize(40, 4), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + ctx.round(size, 2) + ctx.setFillColor(palette.grayText.withAlphaComponent(0.5).cgColor) + ctx.fill(rect) + })! + ctx.draw(text1, in: NSMakeRect(10 + 17 + 3, 7 + 17 + 7 + 2 + 4 + 4, text1.backingSize.width, text1.backingSize.height)) + } + + + } + + switch backgroundMode { + case let .background(image): + let imageSize = image.size.aspectFilled(NSMakeSize(300, 300)) + ctx.saveGState() + ctx.translateBy(x: 1, y: -1) + ctx.draw(image.cgImage(forProposedRect: nil, context: nil, hints: nil)!, in: rect.focus(imageSize)) + ctx.restoreGState() + applyBubbles() + case let .color(color): + if bubbled { + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + applyBubbles() + } else { + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + applyPlain() + } + case let .gradient(values): + if bubbled { + let colors = [values.top, values.bottom].reversed() + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + ctx.saveGState() + ctx.translateBy(x: rect.width / 2.0, y: rect.height / 2.0) + ctx.rotate(by: CGFloat(values.rotation ?? 0) * CGFloat.pi / -180.0) + ctx.translateBy(x: -rect.width / 2.0, y: -rect.height / 2.0) + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + ctx.restoreGState() + applyBubbles() + } else { + applyPlain() + } + + default: + break + } + + })! + + subscriber.putNext(image) + subscriber.putCompletion() + + return EmptyDisposable + } |> runOn(Queue.concurrentDefaultQueue()) +} + +func themeAppearanceThumbAndData(context: AccountContext, bubbled: Bool, source: ThemeSource) -> Signal<(TransformImageResult, InstallThemeSource), NoError> { + + switch source { + case let .cloud(cloud): + if let file = cloud.file { + return cloudThemeData(context: context, theme: cloud, file: file) |> mapToSignal { data in + return generateThumb(palette: data.0, bubbled: bubbled, wallpaper: data.1) |> map { image in + return (TransformImageResult(image, true), .cloud(cloud, InstallCloudThemeCachedData(palette: data.0, wallpaper: data.1, cloudWallpaper: data.2))) + } + } + } else { + return .single((TransformImageResult(theme.icons.appearanceAddPlatformTheme, true), .cloud(cloud, nil))) + } + case let .local(palette, cloud): + let settings = themeSettingsView(accountManager: context.sharedContext.accountManager) |> take(1) + + return settings |> map { settings -> (Wallpaper, ColorPalette) in + let settings = settings + .withUpdatedPalette(palette) + .withUpdatedCloudTheme(cloud) + .installDefaultAccent() + .installDefaultWallpaper() + return (settings.wallpaper.wallpaper, settings.palette) + } |> mapToSignal { wallpaper, palette in + if let cloud = cloud { + return generateThumb(palette: palette, bubbled: bubbled, wallpaper: wallpaper) |> map { image in + return (TransformImageResult(image, true), .cloud(cloud, InstallCloudThemeCachedData(palette: palette, wallpaper: wallpaper, cloudWallpaper: cloud.settings?.wallpaper))) + } + } else { + return generateThumb(palette: palette, bubbled: bubbled, wallpaper: wallpaper) |> map { image in + return (TransformImageResult(image, true), .local(palette)) + } + } + } + } +} + + diff --git a/Telegram-Mac/AppearanceViewController.swift b/Telegram-Mac/AppearanceViewController.swift deleted file mode 100644 index 22c8784886..0000000000 --- a/Telegram-Mac/AppearanceViewController.swift +++ /dev/null @@ -1,172 +0,0 @@ -// -// AppearanceViewController.swift -// Telegram -// -// Created by keepcoder on 07/07/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - -private final class AppearanceViewArguments { - let account:Account - let toggleDarkMode:(Bool)->Void - let toggleFontSize:(Int32)->Void - init(account:Account, toggleDarkMode: @escaping(Bool)->Void, toggleFontSize: @escaping(Int32)->Void) { - self.account = account - self.toggleDarkMode = toggleDarkMode - self.toggleFontSize = toggleFontSize - } -} - -private enum AppearanceViewEntry : TableItemListNodeEntry { - case darkMode(Int32, Bool) - case section(Int32) - case font(Int32, Int32) - case description(Int32, Int32, String) - - var stableId: Int32 { - switch self { - case .darkMode: - return 0 - case .section(let section): - return section + 1000 - case .font: - return 1 - case let .description(section, index, _): - return (section * 1000) + (index + 1) * 1000 - } - } - - var index:Int32 { - switch self { - case .darkMode(let section, _): - return (section * 1000) + 0 - case .section(let section): - return (section + 1) * 1000 - section - case .font(let section, _): - return (section * 1000) + 1 - case let .description(section, index, _): - return (section * 1000) + index + 2 - } - } - - func item(_ arguments: AppearanceViewArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case .darkMode(_, let enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsDarkMode), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { - arguments.toggleDarkMode(!enabled) - }) - case .description(_, _, let text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .font(_, let size): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsLargeFonts), type: .switchable(stateback: { () -> Bool in - return size == 15 - }), action: { - arguments.toggleFontSize(size == 13 ? 15 : 13) - }) - } - } -} -private func ==(lhs: AppearanceViewEntry, rhs: AppearanceViewEntry) -> Bool { - switch lhs { - case let .darkMode(section, enabled): - if case .darkMode(section, enabled) = rhs { - return true - } else { - return false - } - case .section(let section): - if case .section(section) = rhs { - return true - } else { - return false - } - case let .font(section, size): - if case .font(section, size) = rhs { - return true - } else { - return false - } - case let .description(section, index, description): - if case .description(section, index, description) = rhs { - return true - } else { - return false - } - } -} -private func <(lhs: AppearanceViewEntry, rhs: AppearanceViewEntry) -> Bool { - return lhs.index < rhs.index -} - -private func AppearanceViewEntries(dark:Bool, settings: BaseApplicationSettings?) -> [AppearanceViewEntry] { - var entries:[AppearanceViewEntry] = [] - - var sectionId:Int32 = 1 - var descIndex:Int32 = 1 - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.darkMode(sectionId, dark)) - sectionId += 1 - - entries.append(.description(sectionId, descIndex, tr(.generalSettingsDarkModeDescription))) - descIndex += 1 - - entries.append(.section(sectionId)) - sectionId += 1 - - - entries.append(.font(sectionId, settings?.fontSize ?? 13)) - sectionId += 1 - - entries.append(.description(sectionId, descIndex, tr(.generalSettingsFontDescription))) - descIndex += 1 -// - return entries -} - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:AppearanceViewArguments) -> TableUpdateTransition { - - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -class AppearanceViewController: TableViewController { - - override func viewDidLoad() { - super.viewDidLoad() - let account = self.account - let arguments = AppearanceViewArguments(account: account, toggleDarkMode: { enable in - _ = updateThemeSettings(postbox: account.postbox, pallete: enable ? darkPallete : whitePallete, dark: enable).start() - }, toggleFontSize: { size in - _ = updateBaseAppSettingsInteractively(postbox: account.postbox, { settings -> BaseApplicationSettings in - return settings.withUpdatedFontSize(size) - }).start() - }) - - let initialSize = self.atomicSize - - - let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - genericView.merge(with: combineLatest(account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.baseAppSettings]) |> deliverOnMainQueue, appearanceSignal |> deliverOnMainQueue) |> map { pref, appearance in - let entries = AppearanceViewEntries(dark: appearance.presentation.dark, settings: pref.values[ApplicationSpecificPreferencesKeys.baseAppSettings] as? BaseApplicationSettings).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) - } |> deliverOnMainQueue) - readyOnce() - - } - -} diff --git a/Telegram-Mac/ApplicationContext.swift b/Telegram-Mac/ApplicationContext.swift index 7722d2bd35..38722da167 100644 --- a/Telegram-Mac/ApplicationContext.swift +++ b/Telegram-Mac/ApplicationContext.swift @@ -1,307 +1,83 @@ import Foundation import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac -import MtProtoKitMac -import IOKit -func applicationContext(window: Window, shouldOnlineKeeper:Signal, accountManager: AccountManager, appGroupPath: String, testingEnvironment: Bool) -> Signal { - - return migrationData(accountManager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment) - |> deliverOnMainQueue - |> map { migration -> Signal in - - switch migration { - case let .auth(result, ignorepasslock): - if let result = result { - switch result { - case let .unauthorized(account): - return account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings]) |> take(1) |> deliverOnMainQueue |> map { preferences in - return ApplicationContext.unauthorized(UnauthorizedApplicationContext(window: window, account: account, localization: preferences.values[PreferencesKeys.localizationSettings] as? LocalizationSettings)) - } - case let .authorized(account): - let paslock:Signal = !ignorepasslock ? account.postbox.modify { modifier -> PostboxAccessChallengeData in - return modifier.getAccessChallengeData() - } |> deliverOnMainQueue : .single(.none) - - return paslock |> mapToSignal { access -> Signal in - let promise:Promise = Promise() - let auth: Signal = combineLatest(promise.get(), account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings, ApplicationSpecificPreferencesKeys.themeSettings]) |> take(1)) |> deliverOnMainQueue |> map { _, preferences in - return .authorized(AuthorizedApplicationContext(window: window, shouldOnlineKeeper: shouldOnlineKeeper, account: account, accountManager: accountManager, localization: preferences.values[PreferencesKeys.localizationSettings] as? LocalizationSettings, themeSettings: preferences.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings)) - } - switch access { - case .none: - promise.set(.single(Void())) - return auth - default: - return account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.themeSettings, PreferencesKeys.localizationSettings]) |> take(1) |> deliverOnMainQueue |> map { value in - return ApplicationContext.postboxAccess(PasscodeAccessContext(window, promise: promise, account: account, accountManager: accountManager, localization: value.values[PreferencesKeys.localizationSettings] as? LocalizationSettings, themeSettings: value.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings)) - } |> then(auth) - } - } - case .upgrading: - return .single(nil) - } - } else { - return .single(nil) - } - case let .migrationIntro(promise, data): - return .single(.legacyIntro(LegacyIntroContext(window, promise: promise, defaultLegacyData: data))) - } - } |> switchToLatest -} +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import IOKit -enum MigrationData { - case migrationIntro(Promise, AuthorizationLegacyData) - case auth(AccountResult?, ignorepasslock: Bool) +private final class AuthModalController : ModalController { + override var background: NSColor { + return theme.colors.background + } + override var dynamicSize: Bool { + return true + } + override var closable: Bool { + return false + } + + override func measure(size: NSSize) { + self.modal?.resize(with: NSMakeSize(size.width, size.height), animated: false) + } } -func migrationData(accountManager: AccountManager, appGroupPath:String, testingEnvironment: Bool) -> Signal { - - return accountManager.modify { modifier -> Signal in - - if modifier.getCurrentId() == nil { - - let auth = legacyAuthData(passcode: emptyPasscodeData()) - let promise:Promise = Promise() - - switch auth { - case .data: - break - case .passcodeRequired: - break - case .none: - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), supplementary: false, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> map { account in return .auth(account, ignorepasslock: false) } - } - - return .single(.migrationIntro(promise, auth)) |> then ( promise.get() |> take(1) |> mapToSignal { result in - return accountManager.modify { modifier -> Signal in - - switch result { - case let .data(migration): - let accountId = modifier.createRecord([]) - - let provider = ImportAccountProvider(mtProtoKeychain: { - return .single(migration.groups) - }, accountState: { - return .single(AuthorizedAccountState(masterDatacenterId: migration.masterDatacenterId, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: migration.userId), state: nil)) - }, peers: { - return .single([]) - }) - //if !isDebug { - clearLegacyData() - // } - return accountWithId(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), id: accountId, supplementary: false, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods, shouldKeepAutoConnection: false) |> mapToSignal { accountResult in - switch accountResult { - case .unauthorized(let left): - return importAccount(account: left, provider: provider) |> mapToSignal { - return accountManager.modify { modifier -> Void in - modifier.setCurrentId(accountId) - } |> mapToSignal { - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), supplementary: false, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> map { accountResult -> Signal in - - if let accountResult = accountResult { - switch accountResult { - case .authorized(let account): - for (resource, data) in migration.resources { - account.postbox.mediaBox.storeResourceData(resource.id, data: data) - } - return account.postbox.modify { modifier -> MigrationData in - - updatePeers(modifier: modifier, peers: migration.peers, update: { (_, updated) -> Peer? in - return updated - }) - - for (peerId, state) in migration.secretState { - modifier.setPeerChatState(peerId, state: terminateLegacySecretChat(modifier: modifier, peerId: peerId, state: state)) - } - - if let passcode = migration.passcode { - modifier.setAccessChallengeData(.plaintextPassword(value: passcode, timeout: 60 * 60, attempts: nil)) - } - _ = modifier.addMessages(migration.secretMessages, location: .Random) - - for message in migration.secretMessages { - if let attribute = message.attributes.first as? AutoremoveTimeoutMessageAttribute { - switch message.id { - case let .Id(id): - let begin:Int32 = attribute.countdownBeginTime ?? Int32(Date().timeIntervalSince1970) - modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: begin + attribute.timeout, messageId: id) - default: - break - } - } - } - - return .auth(accountResult, ignorepasslock: true) - } - default: - break - } - } - - return .single(.auth(accountResult, ignorepasslock: false)) - } |> switchToLatest - } - } - default: - break - } - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: 2834, languagesCategory: languagesCategory), supplementary: false, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> map { account in return .auth(account, ignorepasslock: false) } - } - case .none: - clearLegacyData() - default: - assertionFailure() - } - - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), supplementary: false, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> map { account in return .auth(account, ignorepasslock: false) } - } |> switchToLatest - - }) - - } - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: API_ID, languagesCategory: languagesCategory), supplementary: false, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: testingEnvironment, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> map { account in return .auth(account, ignorepasslock: false) } - - } |> switchToLatest - -} -enum ApplicationContext { - case unauthorized(UnauthorizedApplicationContext) - case authorized(AuthorizedApplicationContext) - case legacyIntro(LegacyIntroContext) - case postboxAccess(PasscodeAccessContext) +final class UnauthorizedApplicationContext { + let account: UnauthorizedAccount + let rootController: MajorNavigationController + let window:Window + let modal: ModalController + let sharedContext: SharedAccountContext - func showRoot(for window:Window) { - if let content = window.contentView { - switch self { - case let .postboxAccess(context): - showModal(with: context.rootController, for: window) - default: - content.addSubview(rootView) - rootView.frame = content.bounds - viewDidAppear() - } - } - } + private let updatesDisposable: DisposableSet = DisposableSet() var rootView: NSView { - switch self { - case let .unauthorized(context): - return context.rootController.view - case let .authorized(context): - return context.splitView - case let .legacyIntro(context): - return context.rootController.view - case let .postboxAccess(context): - return context.rootController.view - } + return rootController.view } - func viewDidAppear() { - switch self { - case let .unauthorized(context): - context.rootController.viewDidAppear(false) - default: - break - } - } -} + init(window:Window, sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])) { -final class PasscodeAccessContext { - let rootController:PasscodeLockController - private let logoutDisposable = MetaDisposable() - init(_ window:Window, promise:Promise, account:Account, accountManager:AccountManager, localization: LocalizationSettings?, themeSettings: ThemePalleteSettings?) { - - dropLocalization() - if let localization = localization { - applyUILocalization(localization) - } - if let theme = themeSettings { - updateTheme(with: theme, for: window) - } else { - setDefaultTheme(for: window) - } + window.maxSize = NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude) + window.minSize = NSMakeSize(380, 500) - rootController = PasscodeLockController(account, .login, logoutImpl: { - _ = (confirmSignal(for: window, header: appName, information: tr(.accountConfirmLogoutText)) |> filter {$0} |> mapToSignal {_ in return logoutFromAccount(id: account.id, accountManager: accountManager)}).start() - }) - rootController._frameRect = NSMakeRect(0, 0, window.frame.width, window.frame.height) - window.maxSize = NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude) - window.minSize = NSMakeSize(380, 440) + updatesDisposable.add(managedAppConfigurationUpdates(accountManager: sharedContext.accountManager, network: account.network).start()) - promise.set(rootController.doneValue |> filter {$0} |> map {_ in}) - + if !window.initFromSaver { + window.setFrame(NSMakeRect(0, 0, 800, 650), display: true) + window.center() + } + + if window.frame.height < window.minSize.height { + window.setFrame(NSMakeRect(window.frame.minX, window.frame.minY, window.minSize.width, window.minSize.height), display: true) + } - } - - deinit { - logoutDisposable.dispose() - } -} - -final class LegacyIntroContext { - let rootController:LegacyIntroController - init(_ window:Window, promise:Promise, defaultLegacyData: AuthorizationLegacyData) { - rootController = LegacyIntroController(promise: promise, defaultLegacyData: defaultLegacyData) - let authSize = NSMakeSize(650, 600) - window.maxSize = authSize - window.minSize = authSize - window.setFrame(NSMakeRect(0, 0, authSize.width, authSize.height), display: true) - window.center() - rootController._frameRect = NSMakeRect(0, 0, authSize.width, authSize.height) - } -} - -final class UnauthorizedApplicationContext { - let account: UnauthorizedAccount - let localizationDisposable:MetaDisposable = MetaDisposable() - let rootController: AuthController - let window:Window - init(window:Window, account: UnauthorizedAccount, localization: LocalizationSettings?) { self.account = account self.window = window - self.rootController = AuthController(account) - let authSize = NSMakeSize(650, 600) - - setDefaultTheme(for: window) + self.sharedContext = sharedContext + self.rootController = MajorNavigationController(AuthController.self, AuthController(account, sharedContext: sharedContext, otherAccountPhoneNumbers: otherAccountPhoneNumbers), window) + rootController._frameRect = NSMakeRect(0, 0, window.frame.width, window.frame.height) + + self.modal = AuthModalController(rootController) + rootController.alwaysAnimate = true + account.shouldBeServiceTaskMaster.set(.single(.now)) - window.maxSize = authSize - window.minSize = authSize - window.setFrame(NSMakeRect(0, 0, authSize.width, authSize.height), display: true) - window.center() - window.initFromSaver = false - rootController._frameRect = NSMakeRect(0, 0, authSize.width, authSize.height) - + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveWakeNote(_:)), name: NSWorkspace.screensDidWakeNotification, object: nil) - - - - dropLocalization() - if let localization = localization { - applyUILocalization(localization) - } - - localizationDisposable.set(account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings]).start(next: { view in - if let settings = view.values[PreferencesKeys.localizationSettings] as? LocalizationSettings { - applyUILocalization(settings) - } - })) - } deinit { account.shouldBeServiceTaskMaster.set(.single(.never)) + updatesDisposable.dispose() NSWorkspace.shared.notificationCenter.removeObserver(self) } @@ -311,109 +87,131 @@ final class UnauthorizedApplicationContext { } -private struct LockNotificationsData : Equatable { - let screenLock:Bool - let passcodeLock:Bool + +enum ApplicationContextLaunchAction { + case navigate(ViewController) + case preferences +} + + +let leftSidebarWidth: CGFloat = 72 + +private final class ApplicationContainerView: View { + fileprivate let splitView: SplitView - init() { - self.screenLock = false - self.passcodeLock = false - } + fileprivate private(set) var leftSideView: NSView? - init(screenLock: Bool, passcodeLock: Bool) { - self.screenLock = screenLock - self.passcodeLock = passcodeLock + required init(frame frameRect: NSRect) { + splitView = SplitView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + addSubview(splitView) + autoresizingMask = [.width, .height] } - func withUpdatedScreenLock(_ lock: Bool) -> LockNotificationsData { - return LockNotificationsData(screenLock: lock, passcodeLock: passcodeLock) + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - func withUpdatedPasscodeLock(_ lock: Bool) -> LockNotificationsData { - return LockNotificationsData(screenLock: screenLock, passcodeLock: lock) + + func updateLeftSideView(_ view: NSView?, animated: Bool) { + if let view = view { + addSubview(view) + } else { + self.leftSideView?.removeFromSuperview() + } + + self.leftSideView = view + needsLayout = true } - static func ==(lhs:LockNotificationsData, rhs: LockNotificationsData) -> Bool { - return lhs.screenLock == rhs.screenLock && lhs.passcodeLock == rhs.screenLock + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + splitView.backgroundColor = theme.colors.background } - var isLocked: Bool { - return screenLock || passcodeLock + override func layout() { + super.layout() + + if let leftSideView = leftSideView { + leftSideView.frame = NSMakeRect(0, 0, leftSidebarWidth, frame.height) + splitView.frame = NSMakeRect(leftSideView.frame.maxX, 0, frame.width - leftSideView.frame.maxX, frame.height) + } else { + splitView.frame = bounds + } + } } -final class AuthorizedApplicationContext: NSObject, SplitViewDelegate, NSUserNotificationCenterDelegate { +final class AuthorizedApplicationContext: NSObject, SplitViewDelegate { - private let nofityDisposable:MetaDisposable = MetaDisposable() private var mediaKeyTap:SPMediaKeyTap? - let applicationContext: TelegramApplicationContext - let account: Account - let accountManager: AccountManager - let window:Window - let splitView:SplitView - let leftController:MainViewController - let rightController:MajorNavigationController + var rootView: View { + return view + } + + let context: AccountContext + private let window:Window + private let view:ApplicationContainerView + private let leftController:MainViewController + private let rightController:MajorNavigationController private let emptyController:EmptyChatViewController + private var entertainment: EntertainmentViewController? + + private var leftSidebarController: LeftSidebarController? + private let loggedOutDisposable = MetaDisposable() - private let passlockDisposable = MetaDisposable() - private let logoutDisposable = MetaDisposable() private let ringingStatesDisposable = MetaDisposable() - private let lockedScreenPromise:Promise = Promise(LockNotificationsData()) - private var _lockedValue:LockNotificationsData = LockNotificationsData() - private var resignTimestamp:Int32? = nil - private let _passlock = Promise() private let settingsDisposable = MetaDisposable() - private let localizationDisposable = MetaDisposable() private let suggestedLocalizationDisposable = MetaDisposable() - private let appearanceDisposable = MetaDisposable() - private func updateLocked(_ f:(LockNotificationsData) -> LockNotificationsData) { - _lockedValue = f(_lockedValue) - lockedScreenPromise.set(.single(_lockedValue)) + private let alertsDisposable = MetaDisposable() + private let audioDisposable = MetaDisposable() + private let termDisposable = MetaDisposable() + private let someActionsDisposable = DisposableSet() + private let clearReadNotifiesDisposable = MetaDisposable() + private let chatUndoManagerDisposable = MetaDisposable() + private let appUpdateDisposable = MetaDisposable() + private let updatesDisposable = MetaDisposable() + private let updateFoldersDisposable = MetaDisposable() + private let _ready:Promise = Promise() + var ready: Signal { + return _ready.get() |> filter { $0 } |> take (1) + } + + func applyNewTheme() { + rightController.backgroundColor = theme.colors.background + rightController.backgroundMode = theme.controllerBackgroundMode + view.updateLocalizationAndTheme(theme: theme) } - private let query:NSMetadataQuery + private var launchAction: ApplicationContextLaunchAction? - init(window: Window, shouldOnlineKeeper:Signal, account: Account, accountManager: AccountManager, localization:LocalizationSettings?, themeSettings: ThemePalleteSettings?) { - emptyController = EmptyChatViewController(account) + init(window: Window, context: AccountContext, launchSettings: LaunchSettings) { + + self.context = context + emptyController = EmptyChatViewController(context) - self.account = account self.window = window - self.accountManager = accountManager - window.maxSize = NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude) - window.minSize = NSMakeSize(380, 440) - if let themeSettings = themeSettings { - updateTheme(with: themeSettings, for: window) - } else { - setDefaultTheme(for: window) - } - if !window.initFromSaver { window.setFrame(NSMakeRect(0, 0, 800, 650), display: true) window.center() } + context.account.importableContacts.set(.single([:])) - setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia) - - account.stateManager.reset() - account.shouldBeServiceTaskMaster.set(.single(.now)) - account.shouldKeepOnlinePresence.set(.single(true)) - account.shouldKeepOnlinePresence.set(shouldOnlineKeeper) - - self.splitView = SplitView(frame:mainWindow.contentView!.bounds) + self.view = ApplicationContainerView(frame: window.contentView!.bounds) - + + self.view.splitView.setProportion(proportion: SplitProportion(min:380, max:300+350), state: .single); + self.view.splitView.setProportion(proportion: SplitProportion(min:300+350, max:300+350+600), state: .dual) - splitView.setProportion(proportion: SplitProportion(min:380, max:300+350), state: .single); - splitView.setProportion(proportion: SplitProportion(min:300+350, max:300+350+600), state: .dual) - rightController = ExMajorNavigationController(account, ChatController.self, emptyController); + rightController = ExMajorNavigationController(context, ChatController.self, emptyController); rightController.set(header: NavigationHeader(44, initializer: { (header) -> NavigationHeaderView in let view = InlineAudioPlayerView(header) return view @@ -423,717 +221,650 @@ final class AuthorizedApplicationContext: NSObject, SplitViewDelegate, NSUserNot let view = CallNavigationHeaderView(header) return view })) + + window.rootViewController = rightController + + leftController = MainViewController(context); - applicationContext = TelegramApplicationContext(rightController, EntertainmentViewController(size: NSMakeSize(350, window.frame.height), account: account), network: account.network) - account.applicationContext = applicationContext + leftController.navigationController = rightController - leftController = MainViewController(account, accountManager: accountManager); + super.init() - leftController.navigationController = rightController - query = NSMetadataQuery() + updatesDisposable.set(managedAppConfigurationUpdates(accountManager: context.sharedContext.accountManager, network: context.account.network).start()) + context.sharedContext.bindings = AccountContextBindings(rootNavigation: { [weak self] () -> MajorNavigationController in + guard let `self` = self else { + return MajorNavigationController(ViewController.self, ViewController(), window) + } + return self.rightController + }, mainController: { [weak self] () -> MainViewController in + guard let `self` = self else { + fatalError("Cannot use bindings. Application context is not exists") + } + return self.leftController + }, showControllerToaster: { [weak self] toaster, animated in + guard let `self` = self else { + fatalError("Cannot use bindings. Application context is not exists") + } + self.rightController.controller.show(toaster: toaster, animated: animated) + }, globalSearch: { [weak self] search in + guard let `self` = self else { + fatalError("Cannot use bindings. Application context is not exists") + } + self.leftController.tabController.select(index: self.leftController.chatIndex) + self.leftController.chatList.globalSearch(search) + }, entertainment: { [weak self] () -> EntertainmentViewController in + guard let `self` = self else { + return EntertainmentViewController.init(size: NSZeroSize, context: context) + } + if self.entertainment == nil { + self.entertainment = EntertainmentViewController(size: NSMakeSize(350, 350), context: self.context) + } + return self.entertainment! + }, switchSplitLayout: { [weak self] state in + guard let `self` = self else { + fatalError("Cannot use bindings. Application context is not exists") + } + self.view.splitView.state = state + }, needFullsize: { [weak self] in + self?.view.splitView.needFullsize() + }, displayUpgradeProgress: { progress in + + }) - - super.init() - startNotifyListener(with: account) - NSUserNotificationCenter.default.delegate = self - - - #if BETA || STABLE + chatUndoManagerDisposable.set((context.chatUndoManager.allStatuses() |> deliverOnMainQueue).start(next: { [weak self] statuses in + guard let `self` = self else {return} - settingsDisposable.set((account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.baseAppSettings]) |> deliverOnMainQueue).start(next: { [weak self] settings in - - let baseSettings: BaseApplicationSettings - if let settings = settings.values[ApplicationSpecificPreferencesKeys.baseAppSettings] as? BaseApplicationSettings { - baseSettings = settings - } else { - baseSettings = BaseApplicationSettings.defaultSettings - } - - if baseSettings.handleInAppKeys { - self?.applicationContext.initMediaKeyTap() + if let header = self.rightController.undoHeader { + (header.view as? UndoOverlayHeaderView)?.removeAnimationForNextTransition = true + + if statuses.hasProcessingActions { + header.show(true) } else { - self?.applicationContext.deinitMediaKeyTap() + header.hide(true) } - - })) + } - #endif + })) + + termDisposable.set((context.account.stateManager.termsOfServiceUpdate |> deliverOnMainQueue).start(next: { terms in + if let terms = terms { + showModal(with: TermsModalController(context, terms: terms), for: mainWindow) + } else { + closeModal(TermsModalController.self) + } + })) + - var forceNotice:Bool = false + // var forceNotice:Bool = false if FastSettings.isMinimisize { - self.splitView.state = .minimisize - forceNotice = true + self.view.splitView.mustMinimisize = true + // forceNotice = true + } else { + self.view.splitView.mustMinimisize = false } - splitView.delegate = self; - splitView.update(forceNotice) + self.view.splitView.delegate = self; + self.view.splitView.update(false) - let accountId = account.id - self.loggedOutDisposable.set(account.loggedOut.start(next: { value in + let accountId = context.account.id + self.loggedOutDisposable.set(context.account.loggedOut.start(next: { value in if value { - let _ = logoutFromAccount(id: accountId, accountManager: accountManager).start() + let _ = logoutFromAccount(id: accountId, accountManager: context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).start() } })) - let passlock = Signal.single(Void()) |> delay(60, queue: Queue.concurrentDefaultQueue()) |> restart |> mapToSignal { () -> Signal in - return account.postbox.modify { modifier -> Int32? in - return modifier.getAccessChallengeData().timeout - } - } |> map { [weak self] timeout -> Bool in - if let timeout = timeout { - if let resignTimestamp = self?.resignTimestamp { - let current = Int32(Date().timeIntervalSince1970) - if current - resignTimestamp > timeout { - return true - } + + alertsDisposable.set((context.account.stateManager.displayAlerts |> deliverOnMainQueue).start(next: { alerts in + for text in alerts { + + let alert:NSAlert = NSAlert() + alert.window.appearance = theme.appearance + alert.alertStyle = .informational + alert.messageText = appName + alert.informativeText = text.text + + if text.isDropAuth { + alert.addButton(withTitle: L10n.editAccountLogout) + alert.addButton(withTitle: L10n.modalCancel) + } - return Int64(timeout) < SystemIdleTime() - } else { - return false - } - } - |> filter { [weak self] _ in - if let strongSelf = self { - return !strongSelf._lockedValue.passcodeLock + + alert.beginSheetModal(for: window, completionHandler: { result in + if result.rawValue == 1000 && text.isDropAuth { + let _ = logoutFromAccount(id: context.account.id, accountManager: context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).start() + } + }) } - return false - } - |> deliverOnMainQueue - - - let showPasslock = passlock + })) + - _passlock.set(showPasslock) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.rightController.push(ChatController(context: context, chatLocation: .peer(context.peerId))) + return .invoked + }, with: self, for: .Zero, priority: .low, modifierFlags: [.command]) - passlockDisposable.set((_passlock.get() |> deliverOnMainQueue |> mapToSignal { [weak self] show -> Signal in - if show { - let controller = PasscodeLockController(account, .login, logoutImpl: { [weak self] in - self?.logout() - }) - showModal(with: controller, for: window) - return .single(show) |> then( controller.doneValue |> map {_ in return false} |> take(1) ) - } - return .never() - } |> deliverOnMainQueue).start(next: { [weak self] lock in - - window.contentView?.subviews.first?.isHidden = lock - - self?.updateLocked { previous -> LockNotificationsData in - return previous.withUpdatedPasscodeLock(lock) - } - })) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(0, false) + return .invoked + }, with: self, for: .One, priority: .low, modifierFlags: [.command]) - ringingStatesDisposable.set((account.callSessionManager.ringingStates() |> deliverOn(callQueue)).start(next: { states in - pullCurrentSession( { session in - if let state = states.first { - if session == nil { - showPhoneCallWindow(PCallSession(account: account, peerId: state.peerId, id: state.id)) - } else { - account.callSessionManager.drop(internalId: state.id, reason: .busy) - } - } - } ) - })) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(1, false) + return .invoked + }, with: self, for: .Two, priority: .low, modifierFlags: [.command]) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(2, false) + return .invoked + }, with: self, for: .Three, priority: .low, modifierFlags: [.command]) - // NotificationCenter.default.addObserver(self, selector: #selector(windiwDidChangeBackingProperties), name: NSNotification.Name.NSWindowDidChangeBackingProperties, object: window) - + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(3, false) + return .invoked + }, with: self, for: .Four, priority: .low, modifierFlags: [.command]) - NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window) - NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window) - NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveWakeNote(_:)), name: NSWorkspace.screensDidWakeNotification, object: nil) - - DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenIsLocked), name: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), object: nil) - DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenIsUnlocked), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil) - -// -// NotificationCenter.default.addObserver(self, selector: #selector(queryUpdated(_:)), name: NSNotification.Name.NSMetadataQueryDidStartGathering, object: query) -// -// NotificationCenter.default.addObserver(self, selector: #selector(queryUpdated(_:)), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: query) -// -// NotificationCenter.default.addObserver(self, selector: #selector(queryUpdated(_:)), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query) -// -// query.predicate = NSPredicate(format: "kMDItemIsScreenCapture = 1") -// _ = query.start() + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(4, false) + return .invoked + }, with: self, for: .Five, priority: .low, modifierFlags: [.command]) window.set(handler: { [weak self] () -> KeyHandlerResult in - - if let strongSelf = self { - if !strongSelf._lockedValue.passcodeLock { - self?._passlock.set(account.postbox.modify { modifier -> Bool in - switch modifier.getAccessChallengeData() { - case .none: - return false - default: - return true - } - }) - } - } - + self?.openChat(5, false) return .invoked - }, with: self, for: .L, priority: .low, modifierFlags: [.command]) + }, with: self, for: .Six, priority: .low, modifierFlags: [.command]) window.set(handler: { [weak self] () -> KeyHandlerResult in - if let strongSelf = self { - strongSelf.applicationContext.mainNavigation?.push(ChatController(account: strongSelf.account, peerId: strongSelf.account.peerId)) - } + self?.openChat(6, false) return .invoked - }, with: self, for: .Zero, priority: .low, modifierFlags: [.command]) + }, with: self, for: .Seven, priority: .low, modifierFlags: [.command]) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(7, false) + return .invoked + }, with: self, for: .Eight, priority: .low, modifierFlags: [.command]) - if let localization = localization { - applyUILocalization(localization) - } + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(8, false) + return .invoked + }, with: self, for: .Nine, priority: .low, modifierFlags: [.command]) - suggestedLocalizationDisposable.set(( account.postbox.preferencesView(keys: [PreferencesKeys.suggestedLocalization]) |> mapToSignal { preferences -> Signal in - - let preferences = preferences.values[PreferencesKeys.suggestedLocalization] as? SuggestedLocalizationEntry - if preferences == nil || !preferences!.isSeen, preferences?.languageCode != appCurrentLanguage.languageCode, preferences?.languageCode != "en" { - return suggestedLocalizationInfo(network: account.network, languageCode: Locale.current.languageCode ?? "en", extractKeys: ["Suggest.Localization.Header", "Suggest.Localization.Other"]) |> take(1) - } - return .complete() - } |> deliverOnMainQueue).start(next: { suggestionInfo in - showModal(with: SuggestionLocalizationViewController(account, suggestionInfo: suggestionInfo), for: window) - })) - + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(0, true) + return .invoked + }, with: self, for: .One, priority: .low, modifierFlags: [.command, .option]) - localizationDisposable.set(account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings]).start(next: { view in - if let settings = view.values[PreferencesKeys.localizationSettings] as? LocalizationSettings { - applyUILocalization(settings) - } - })) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(1, true) + return .invoked + }, with: self, for: .Two, priority: .low, modifierFlags: [.command, .option]) - rightController.backgroundColor = theme.colors.background - splitView.backgroundColor = theme.colors.background - let basic = Atomic(value: themeSettings) - appearanceDisposable.set((account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.themeSettings]) |> deliverOnMainQueue).start(next: { [weak self] view in - if let settings = view.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings { - if basic.swap(nil)?.dark != settings.dark { - updateTheme(with: settings, for: window, animated: true) - self?.rightController.backgroundColor = theme.colors.background - self?.splitView.backgroundColor = theme.colors.background - } - } - })) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(2, true) + return .invoked + }, with: self, for: .Three, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(3, true) + return .invoked + }, with: self, for: .Four, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(4, true) + return .invoked + }, with: self, for: .Five, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(5, true) + return .invoked + }, with: self, for: .Six, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(6, true) + return .invoked + }, with: self, for: .Seven, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(7, true) + return .invoked + }, with: self, for: .Eight, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(8, true) + return .invoked + }, with: self, for: .Nine, priority: .low, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(9, true) + return .invoked + }, with: self, for: .Minus, priority: .low, modifierFlags: [.command, .option]) - } - - var isLocked: Bool { - return _lockedValue.isLocked - } - - @objc private func queryUpdated(_ notification: NSNotification) { - if query.resultCount != 0 { - var bp:Int = 0 - bp += 1 - } - } - - func logout() { - self.logoutDisposable.set((confirmSignal(for: window, header: appName, information: tr(.accountConfirmLogoutText)) |> filter {$0} |> mapToSignal { [weak self] _ -> Signal in - if let strongSelf = self { - return logoutFromAccount(id: strongSelf.account.id, accountManager: strongSelf.accountManager) - } - return .complete() - }).start()) - } - - - @objc open func windowDidBecomeKey() { - self.resignTimestamp = nil - } - - - @objc open func windowDidResignKey() { - self.resignTimestamp = Int32(Date().timeIntervalSince1970) - } - - - func splitViewDidNeedSwapToLayout(state: SplitViewState) { - splitView.removeAllControllers(); - let w:CGFloat = 300; - FastSettings.isMinimisize = false - switch state { - case .single: - rightController.empty = leftController + + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(0, true) + return .invoked + }, with: self, for: .One, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(1, true) + return .invoked + }, with: self, for: .Two, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(2, true) + return .invoked + }, with: self, for: .Three, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(3, true) + return .invoked + }, with: self, for: .Four, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(4, true) + return .invoked + }, with: self, for: .Five, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(5, true) + return .invoked + }, with: self, for: .Six, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(6, true) + return .invoked + }, with: self, for: .Seven, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(7, true) + return .invoked + }, with: self, for: .Eight, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(8, true) + return .invoked + }, with: self, for: .Nine, priority: .low, modifierFlags: [.control]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.openChat(9, true) + return .invoked + }, with: self, for: .Minus, priority: .low, modifierFlags: [.control]) + + + + +// window.set(handler: { [weak self] () -> KeyHandlerResult in +// self?.leftController.focusSearch(animated: true) +// return .invoked +// }, with: self, for: .F, priority: .supreme, modifierFlags: [.command, .shift]) + + window.set(handler: { () -> KeyHandlerResult in + context.sharedContext.bindings.rootNavigation().push(ShortcutListController(context: context)) + return .invoked + }, with: self, for: .Slash, priority: .low, modifierFlags: [.command]) + + + + #if DEBUG + window.set(handler: { () -> KeyHandlerResult in - if rightController.modalAction != nil { - if rightController.controller is ChatController { - rightController.push(ForwardChatListController(account), false) + +// filePanel(with: ["mov", "mp4"], allowMultiple: false, for: window, completion: { values in +// if let first = values?.first { +// let asset = AVURLAsset(url: URL(fileURLWithPath: first)) +// let track = asset.tracks(withMediaType: .video).first +// if let track = track { +// showModal(with: VideoAvatarModalController(context: context, asset: asset, track: track), for: window) +// } +// } +// }) + // showModal(with: VideoAvatarModalController(context: context), for: window) + + // context.sharedContext.bindings.rootNavigation().push(ShortcutListController(context: context)) + return .invoked + }, with: self, for: .T, priority: .supreme, modifierFlags: .command) + #endif + + + appUpdateDisposable.set((context.account.stateManager.appUpdateInfo |> deliverOnMainQueue).start(next: { info in + + })) + + + suggestedLocalizationDisposable.set(( context.account.postbox.preferencesView(keys: [PreferencesKeys.suggestedLocalization]) |> mapToSignal { preferences -> Signal in + + let preferences = preferences.values[PreferencesKeys.suggestedLocalization] as? SuggestedLocalizationEntry + if preferences == nil || !preferences!.isSeen, preferences?.languageCode != appCurrentLanguage.languageCode, preferences?.languageCode != "en" { + let current = Locale.preferredLanguages[0] + let split = current.split(separator: "-") + let lan: String = !split.isEmpty ? String(split[0]) : "en" + if lan != "en" { + return suggestedLocalizationInfo(network: context.account.network, languageCode: lan, extractKeys: ["Suggest.Localization.Header", "Suggest.Localization.Other"]) |> take(1) } } - splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) - case .dual: - rightController.empty = emptyController - if rightController.controller is ForwardChatListController { - rightController.back(animated:false) + return .complete() + } |> deliverOnMainQueue).start(next: { suggestionInfo in + if suggestionInfo.availableLocalizations.count >= 2 { + showModal(with: SuggestionLocalizationViewController(context, suggestionInfo: suggestionInfo), for: window) } - splitView.addController(controller: leftController, proportion: SplitProportion(min:w, max:w)) - splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) - case .minimisize: - FastSettings.isMinimisize = true - splitView.addController(controller: leftController, proportion: SplitProportion(min:70, max:70)) - splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) - default: - break; - } + })) + + someActionsDisposable.add(managedUpdatedRecentPeers(accountPeerId: context.account.peerId, postbox: context.account.postbox, network: context.account.network).start()) - account.context.layoutHandler.set(state) - splitView.layout() - } - - @objc func screenIsLocked() { - - if !_lockedValue.passcodeLock { - _passlock.set(account.postbox.modify { modifier -> Bool in - switch modifier.getAccessChallengeData() { - case .none: - return false - default: - return true - } - }) - } - updateLocked { (previous) -> LockNotificationsData in - return previous.withUpdatedScreenLock(true) - } - } - - @objc func screenIsUnlocked() { - updateLocked { (previous) -> LockNotificationsData in - return previous.withUpdatedScreenLock(false) - } - } - - func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } - - func splitViewDidNeedMinimisize(controller: ViewController) { + - } - - func splitViewDidNeedFullsize(controller: ViewController) { + clearReadNotifiesDisposable.set(context.account.stateManager.appliedIncomingReadMessages.start(next: { msgIds in + clearNotifies(by: msgIds) + })) - } - - func splitViewIsCanMinimisize() -> Bool { - return self.leftController.isCanMinimisize(); - } - - func splitViewDrawBorder() -> Bool { - return false - } - - deinit { - self.account.shouldKeepOnlinePresence.set(.single(false)) - self.account.shouldBeServiceTaskMaster.set(.single(.never)) - nofityDisposable.dispose() - NSWorkspace.shared.notificationCenter.removeObserver(self) - DistributedNotificationCenter.default().removeObserver(self) - NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: window) - NotificationCenter.default.removeObserver(self, name: NSWindow.didResignKeyNotification, object: window) - self.loggedOutDisposable.dispose() - passlockDisposable.dispose() - logoutDisposable.dispose() - window.removeAllHandlers(for: self) - settingsDisposable.dispose() - ringingStatesDisposable.dispose() - localizationDisposable.dispose() - suggestedLocalizationDisposable.dispose() - appearanceDisposable.dispose() - //query.stop() - } - - - func startNotifyListener(with account: Account) { + + + someActionsDisposable.add(applyUpdateTextIfNeeded(context.account.postbox).start()) - let lockedSreenSignal = lockedScreenPromise.get() + - self.nofityDisposable.set((account.stateManager.notificationMessages |> mapToSignal { messages -> Signal<([Message], InAppNotificationSettings), Void> in - return account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.inAppNotificationSettings]) |> mapToSignal { (settings) -> Signal<([Message], InAppNotificationSettings), Void> in + let foldersSemaphore = DispatchSemaphore(value: 0) + var folders: ChatListFolders = ChatListFolders(list: [], sidebar: false) + + _ = (chatListFilterPreferences(postbox: context.account.postbox) |> take(1)).start(next: { value in + folders = value + foldersSemaphore.signal() + }) + foldersSemaphore.wait() + + self.updateLeftSidebar(with: folders, animated: false) + + + self.view.splitView.layout() + + + + + if let navigation = launchSettings.navigation { + switch navigation { + case .settings: + self.launchAction = .preferences + _ready.set(leftController.settings.ready.get()) + leftController.tabController.select(index: leftController.settingsIndex) + case let .chat(peerId, necessary): - let inAppSettings: InAppNotificationSettings - if let settings = settings.values[ApplicationSpecificPreferencesKeys.inAppNotificationSettings] as? InAppNotificationSettings { - inAppSettings = settings - } else { - inAppSettings = InAppNotificationSettings.defaultSettings - } - if inAppSettings.enabled && inAppSettings.muteUntil < Int32(Date().timeIntervalSince1970) { - return .single((messages, inAppSettings)) - } else { - return .complete() - } + let peerSemaphore = DispatchSemaphore(value: 0) + var peer: Peer? + _ = context.account.postbox.transaction { transaction in + peer = transaction.getPeer(peerId) + peerSemaphore.signal() + }.start() + peerSemaphore.wait() - } - } - |> mapToSignal { messages, inAppSettings -> Signal<([Message],[MessageId:NSImage], InAppNotificationSettings), Void> in + _ready.set(leftController.chatList.ready.get()) + self.leftController.tabController.select(index: self.leftController.chatIndex) - var photos:[Signal<(MessageId, CGImage?),Void>] = [] - for message in messages { - var peer = message.author - if let mainPeer = messageMainPeer(message) { - if mainPeer is TelegramChannel || mainPeer is TelegramGroup { - peer = mainPeer - } - } + if (necessary || context.sharedContext.layout != .single) { if let peer = peer { - if let image = peerAvatarImage(account: account, peer: peer) { - photos.append(image |> map { image in return (message.id,image)}) - } - } - } - - return combineLatest(photos) |> map { resources in - var images:[MessageId:NSImage] = [:] - for (messageId,image) in resources { - if let image = image { - images[messageId] = NSImage(cgImage: image, size: NSMakeSize(50,50)) - } + let controller = ChatController(context: context, chatLocation: .peer(peer.id)) + controller.navigationController = self.rightController + controller.loadViewIfNeeded(self.rightController.bounds) + + self.launchAction = .navigate(controller) + + self._ready.set(combineLatest(self.leftController.chatList.ready.get(), controller.ready.get()) |> map { $0 && $1 }) + self.leftController.tabController.select(index: self.leftController.chatIndex) + } else { + // self._ready.set(self.leftController.chatList.ready.get()) + self.leftController.tabController.select(index: self.leftController.chatIndex) + self._ready.set(.single(true)) } - return (messages,images, inAppSettings) + } else { + // self._ready.set(.single(true)) + _ready.set(leftController.chatList.ready.get()) + self.leftController.tabController.select(index: self.leftController.chatIndex) } - } |> mapToSignal { messages, images, inAppSettings -> Signal<([Message],[MessageId:NSImage], InAppNotificationSettings, Bool), Void> in - return lockedSreenSignal |> take(1) - |> map { data in return (messages, images, inAppSettings, data.isLocked)} } - |> mapToSignal { values in - return _callSession() |> map { s in - return (values.0, values.1, values.2, values.3, s != nil) - } - } |> deliverOnMainQueue).start(next: { messages, images, inAppSettings, screenIsLocked, inCall in - for message in messages { - if message.author?.id != account.peerId { - var title:String = message.author?.displayTitle ?? "" - var hasReplyButton:Bool = true - if let peer = message.peers[message.id.peerId], peer is TelegramChannel || peer is TelegramGroup { - title = peer.displayTitle - hasReplyButton = peer.canSendMessage - } - var text = chatListText(account: account, for: message).string.nsstring - var subText:String? - if text.contains("\n") { - let parts = text.components(separatedBy: "\n") - text = parts[1] as NSString - subText = parts[0] - } - - if !inAppSettings.displayPreviews || message.peers[message.id.peerId] is TelegramSecretChat || screenIsLocked { - text = tr(.notificationLockedPreview).nsstring - subText = nil - } - - let notification = NSUserNotification() - notification.title = title - notification.informativeText = text as String - notification.subtitle = subText - notification.contentImage = images[message.id] - notification.hasReplyButton = hasReplyButton - - if localizedString(inAppSettings.tone) != tr(.notificationSettingsToneNone) { - notification.soundName = inAppSettings.tone - } else { - notification.soundName = nil - } - - if message.muted || inCall { - notification.soundName = nil - } - - let encoded:WriteBuffer = WriteBuffer() - message.id.encodeToBuffer(encoded) - notification.userInfo = ["encodedMessageId":encoded.makeData(), "peerId.namespace":message.id.peerId.namespace, "peerId.id":message.id.peerId.id] - NSUserNotificationCenter.default.deliver(notification) - } + } else { + // self._ready.set(.single(true)) + _ready.set(leftController.chatList.ready.get()) + leftController.tabController.select(index: leftController.chatIndex) + // _ready.set(leftController.ready.get()) + } + + + let callSessionSemaphore = DispatchSemaphore(value: 0) + var callSession: PCallSession? + _ = _callSession().start(next: { _session in + callSession = _session + callSessionSemaphore.signal() + }) + callSessionSemaphore.wait() + + + if let session = callSession { + _ = (session.state.get() |> take(1)).start(next: { [weak session] state in + if case .active = state, let session = session { + context.sharedContext.showCallHeader(with: session) } - })) + }) + } + + self.updateFoldersDisposable.set((chatListFilterPreferences(postbox: context.account.postbox) |> deliverOnMainQueue).start(next: { [weak self] value in + self?.updateLeftSidebar(with: value, animated: true) + })) + + // _ready.set(.single(true)) } - - - @objc func receiveWakeNote(_ notificaiton:Notification) { - account.shouldBeServiceTaskMaster.set(.single(.never) |> then(.single(.now))) - } - - - - func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { + private var folders: ChatListFolders? + private let foldersReadyDisposable = MetaDisposable() + private func updateLeftSidebar(with folders: ChatListFolders, animated: Bool) -> Void { + let currentSidebar = !folders.list.isEmpty && folders.sidebar + let previousSidebar = self.folders == nil ? nil : !self.folders!.list.isEmpty && self.folders!.sidebar + + let readySignal: Signal - if let encodedMessageId = notification.userInfo?["encodedMessageId"] as? Data { - let messageId = MessageId(ReadBuffer(memoryBufferNoCopy: MemoryBuffer(data: encodedMessageId))) - rightController.push(ChatController(account: account, peerId: messageId.peerId), false) + if currentSidebar != previousSidebar { + if folders.list.isEmpty || !folders.sidebar { + leftSidebarController?.removeFromSuperview() + leftSidebarController = nil + readySignal = .single(true) + } else { + let controller = LeftSidebarController(context, filterData: leftController.chatList.filterSignal, updateFilter: leftController.chatList.updateFilter) + controller._frameRect = NSMakeRect(0, 0, leftSidebarWidth, window.frame.height) + controller.loadViewIfNeeded() + self.leftSidebarController = controller + readySignal = controller.ready.get() |> take(1) + } + let enlarge: CGFloat - if notification.activationType == .replied, let text = notification.response?.string { - var replyToMessageId:MessageId? - if messageId.peerId.namespace != Namespaces.Peer.CloudUser { - replyToMessageId = messageId - } - _ = enqueueMessages(account: account, peerId: messageId.peerId, messages: [EnqueueMessage.message(text: text, attributes: [], media: nil, replyToMessageId: replyToMessageId)]).start() + if currentSidebar && previousSidebar != nil { + enlarge = leftSidebarWidth } else { - self.window.deminiaturize(self) - NSApp.activate(ignoringOtherApps: true) + if previousSidebar == true { + enlarge = -leftSidebarWidth + } else { + enlarge = 0 + } } + + foldersReadyDisposable.set(readySignal.start(next: { [weak self] _ in + guard let `self` = self else { + return + } + self.view.updateLeftSideView(self.leftSidebarController?.genericView, animated: animated) + if !self.window.isFullScreen { + self.window.setFrame(NSMakeRect(max(0, self.window.frame.minX - enlarge), self.window.frame.minY, self.window.frame.width + enlarge, self.window.frame.height), display: true, animate: false) + } + self.updateMinMaxWindowSize(animated: animated) + })) + + } + self.folders = folders } -} - - - - - - - - - -private class LegacyPasscodeHeaderView : View { - - private let logo:ImageView = ImageView() - private let header:TextView = TextView() - private let desc1:TextView = TextView() - - private let desc2:TextView = TextView() - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - let logoImage = #imageLiteral(resourceName: "Icon_LegacyIntro").precomposed() - self.logo.image = logoImage - self.logo.sizeToFit() - let headerLayout = TextViewLayout(NSAttributedString.initialize(string: appName, color: NSColor.text, font: NSFont.normal(.custom(28))), maximumNumberOfLines: 1) - headerLayout.measure(width: CGFloat.greatestFiniteMagnitude) - header.update(headerLayout) - - - let descLayout1 = TextViewLayout(NSAttributedString.initialize(string: tr(.legacyIntroDescription1), color: .grayText, font: NSFont.normal(FontSize.text)), alignment: .center) - descLayout1.measure(width: frameRect.width - 200) - desc1.update(descLayout1) - - let descLayout2 = TextViewLayout(NSAttributedString.initialize(string: tr(.legacyIntroDescription2), color: .grayText, font: NSFont.normal(FontSize.text)), alignment: .center) - descLayout2.measure(width: frameRect.width - 200) - desc2.update(descLayout2) - - addSubview(logo) - addSubview(header) - addSubview(desc1) - addSubview(desc2) - - logo.centerX() - header.centerX(y: logo.frame.maxY + 10) - desc1.centerX(y: header.frame.maxY + 10) - desc2.centerX(y: desc1.frame.maxY + 10) + private func updateMinMaxWindowSize(animated: Bool) { + window.maxSize = NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude) + var width: CGFloat = 380 + if leftSidebarController != nil { + width += leftSidebarWidth + } + if context.sharedContext.layout == .minimisize { + width += 70 + } + window.minSize = NSMakeSize(width, 500) - self.setFrameSize(frame.width, desc2.frame.maxY) + if window.frame.width < window.minSize.width { + window.setFrame(NSMakeRect(max(0, window.frame.minX - (window.minSize.width - window.frame.width)), window.frame.minY, window.minSize.width, window.frame.height), display: true, animate: false) + } } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class LegacyIntroView : View, NSTextFieldDelegate { - fileprivate let input:NSSecureTextField - fileprivate let logoutTextView:TextView = TextView() - fileprivate let doneButton:TitleButton = TitleButton() - fileprivate let legacyIntro: LegacyPasscodeHeaderView - fileprivate var layoutWithPasscode: Bool = false { - didSet { - self.input.isHidden = !layoutWithPasscode - self.logoutTextView.isHidden = !layoutWithPasscode - self.needsLayout = true - self.needsDisplay = true - } - } - required init(frame frameRect: NSRect) { - input = NSSecureTextField(frame: NSZeroRect) - legacyIntro = LegacyPasscodeHeaderView(frame: NSMakeRect(0,0, frameRect.width, 300)) - super.init(frame: frameRect) - - - doneButton.set(font: .medium(.header), for: .Normal) - doneButton.set(text: tr(.legacyIntroNext), for: .Normal) - - doneButton.set(color: .blueUI, for: .Normal) - - doneButton.sizeToFit() - addSubview(doneButton) - - addSubview(input) - addSubview(logoutTextView) - - input.isBordered = false - input.isBezeled = false - input.focusRingType = .none - input.alignment = .center - input.delegate = self - - let attr = NSMutableAttributedString()//Passcode.EnterPasscodePlaceholder - _ = attr.append(string: tr(.passcodeEnterPasscodePlaceholder), color: .grayText, font: NSFont.normal(FontSize.text)) - attr.setAlignment(.center, range: attr.range) - input.placeholderAttributedString = attr - input.font = NSFont.normal(FontSize.text) - input.textColor = .text - input.sizeToFit() - - let logoutAttr = NSMutableAttributedString() - _ = logoutAttr.append(string: tr(.passcodeLogoutDescription), color: .grayText, font: .normal(.text)) - _ = logoutAttr.append(string: " ") - let range = logoutAttr.append(string: tr(.passcodeLogoutLinkText), color: .link, font: .normal(.text)) - logoutAttr.add(link: inAppLink.logout({}), for: range) - logoutTextView.set(layout: TextViewLayout(logoutAttr)) - - - addSubview(legacyIntro) - } - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - if !input.isHidden { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(input.frame.minX, input.frame.maxY + 10, input.frame.width, .borderSize)) + func runLaunchAction() { + if let launchAction = launchAction { + switch launchAction { + case let .navigate(controller): + leftController.tabController.select(index: leftController.chatIndex) + context.sharedContext.bindings.rootNavigation().push(controller, context.sharedContext.layout == .single) + case .preferences: + leftController.tabController.select(index: leftController.settingsIndex) + } + self.launchAction = nil + } else { + leftController.tabController.select(index: leftController.chatIndex) + } + Queue.mainQueue().justDispatch { [weak self] in + self?.leftController.prepareControllers() } } - override func layout() { - super.layout() - - legacyIntro.centerX(y: 80) - - logoutTextView.layout?.measure(width: frame.width - 40) - logoutTextView.update(logoutTextView.layout) - - input.setFrameSize(200, input.frame.height) - input.centerX(y: legacyIntro.frame.maxY + 30) - logoutTextView.centerX(y:frame.height - logoutTextView.frame.height - 20) - - doneButton.centerX(y : (input.isHidden ? legacyIntro.frame.maxY : input.frame.maxY) + 30) - - setNeedsDisplayLayer() - + private func openChat(_ index: Int, _ force: Bool = false) { + leftController.openChat(index, force: force) } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + func splitResizeCursor(at point: NSPoint) -> NSCursor? { + if FastSettings.isMinimisize { + return NSCursor.resizeRight + } else { + if window.frame.width - point.x <= 380 { + return NSCursor.resizeLeft + } + return NSCursor.resizeLeftRight + } } -} - -class LegacyIntroController: GenericViewController { - private let disposable:MetaDisposable = MetaDisposable() - private let promise:Promise - private let defaultData:AuthorizationLegacyData - init(promise:Promise, defaultLegacyData:AuthorizationLegacyData) { - self.promise = promise - self.defaultData = defaultLegacyData - super.init() - } - - override func viewDidLoad() { - super.viewDidLoad() - readyOnce() - - genericView.doneButton.set(handler: { [weak self] _ in - self?.checkCodeAndAuth() - }, for: .Click) - - genericView.input.target = self - genericView.input.action = #selector(checkCodeAndAuth) - - switch defaultData { - case .data, .none: - genericView.layoutWithPasscode = false - case .passcodeRequired: - genericView.layoutWithPasscode = true + func splitViewShouldResize(at point: NSPoint) { + if !FastSettings.isMinimisize { + let max_w = window.frame.width - 380 + let result = round(min(max(point.x, 300), max_w)) + FastSettings.updateLeftColumnWidth(result) + self.view.splitView.updateStartSize(size: NSMakeSize(result, result), controller: leftController) } - mainWindow.set(responder: { [weak self] () -> NSResponder? in - return self?.firstResponder() - }, with: self, priority: .low) } - private func logout() { - promise.set(.single(.none)) - } + - @objc private func checkCodeAndAuth() { - - switch defaultData { - case .data: - promise.set(.single(defaultData)) - break - case .passcodeRequired: - if let md5Hash = ObjcUtils.md5(genericView.input.stringValue).data(using: .utf8) { - var part1: Data = md5Hash.subdata(in : 0 ..< 16) - var part2: Data = md5Hash.subdata(in : 16 ..< 32) - - var zero:UInt8 = 0 - for _ in 0 ..< 16 { - part1.append(&zero, count: 1) - part2.append(&zero, count: 1) - } - - part1.append(part2) - - let legacy = legacyAuthData(passcode: part1, textPasscode: genericView.input.stringValue) - - switch legacy { - case .data: - promise.set(.single(legacy)) - case .passcodeRequired: - genericView.input.shake() - case .none: - promise.set(.single(.none)) + func splitViewDidNeedSwapToLayout(state: SplitViewState) { + let previousState = self.view.splitView.state + self.view.splitView.removeAllControllers() + let w:CGFloat = FastSettings.leftColumnWidth + FastSettings.isMinimisize = false + self.view.splitView.mustMinimisize = false + switch state { + case .single: + rightController.empty = leftController + + if rightController.modalAction != nil { + if rightController.controller is ChatController { + rightController.push(ForwardChatListController(context), false) } - } - - - + if rightController.stackCount == 1, previousState != .none { + leftController.viewWillAppear(false) + } + self.view.splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) + if rightController.stackCount == 1, previousState != .none { + leftController.viewDidAppear(false) + } + case .dual: + rightController.empty = emptyController + if rightController.controller is ForwardChatListController { + rightController.back(animated:false) + } + self.view.splitView.addController(controller: leftController, proportion: SplitProportion(min:w, max:w)) + self.view.splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) + case .minimisize: + self.view.splitView.mustMinimisize = true + FastSettings.isMinimisize = true + self.view.splitView.addController(controller: leftController, proportion: SplitProportion(min:70, max:70)) + self.view.splitView.addController(controller: rightController, proportion: SplitProportion(min:380, max:CGFloat.greatestFiniteMagnitude)) default: - break + break; } + + context.sharedContext.layoutHandler.set(state) + updateMinMaxWindowSize(animated: false) + self.view.splitView.layout() + } - override func firstResponder() -> NSResponder? { - if !(window?.firstResponder is NSText) { - return genericView.input - } - let editor = self.window?.fieldEditor(true, for: genericView.input) - if window?.firstResponder != editor { - return genericView.input - } - return editor + + + func splitViewDidNeedMinimisize(controller: ViewController) { } - deinit { - disposable.dispose() - mainWindow.removeObserver(for: self) + func splitViewDidNeedFullsize(controller: ViewController) { + + } + + func splitViewIsCanMinimisize() -> Bool { + return self.leftController.isCanMinimisize(); } - override func viewClass() -> AnyClass { - return LegacyIntroView.self + func splitViewDrawBorder() -> Bool { + return false + } + + deinit { + self.loggedOutDisposable.dispose() + window.removeAllHandlers(for: self) + settingsDisposable.dispose() + ringingStatesDisposable.dispose() + suggestedLocalizationDisposable.dispose() + audioDisposable.dispose() + alertsDisposable.dispose() + termDisposable.dispose() + viewer?.close() + globalAudio?.cleanup() + someActionsDisposable.dispose() + clearReadNotifiesDisposable.dispose() + chatUndoManagerDisposable.dispose() + appUpdateDisposable.dispose() + updatesDisposable.dispose() + updateFoldersDisposable.dispose() + foldersReadyDisposable.dispose() + context.cleanup() + NotificationCenter.default.removeObserver(self) } } + + diff --git a/Telegram-Mac/ApplicationSpecificPreferencesKeys.swift b/Telegram-Mac/ApplicationSpecificPreferencesKeys.swift index 05091f0cac..b547c3d552 100644 --- a/Telegram-Mac/ApplicationSpecificPreferencesKeys.swift +++ b/Telegram-Mac/ApplicationSpecificPreferencesKeys.swift @@ -8,26 +8,72 @@ import Cocoa -import TelegramCoreMac - +import TelegramCore +import SyncCore +import SyncCore private enum ApplicationSpecificPreferencesKeyValues: Int32 { case inAppNotificationSettings case baseAppSettings - case automaticMediaDownloadSettings case generatedMediaStoreSettings - case voiceCallSettings - case themeSettings - case recentEmoji = 14 case instantViewAppearance = 11 + case additionalSettings = 15 + case themeSettings = 22 + case readArticles = 25 + case autoNight = 26 + case stickerSettings = 29 + case launchSettings = 30 + case automaticMediaDownloadSettings = 31 + case autoplayMedia = 32 + case voiceCallSettings = 34 + case downloadedPaths = 35 + case walletPasscodeTimeout = 37 + case passcodeSettings = 38 + case appConfiguration = 39 + case chatListSettings = 47 + case recentEmoji = 48 + case voipDerivedState = 49 } struct ApplicationSpecificPreferencesKeys { - static let inAppNotificationSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.inAppNotificationSettings.rawValue) - static let baseAppSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.baseAppSettings.rawValue) static let automaticMediaDownloadSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.automaticMediaDownloadSettings.rawValue) static let generatedMediaStoreSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.generatedMediaStoreSettings.rawValue) - static let voiceCallSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voiceCallSettings.rawValue) - static let themeSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.themeSettings.rawValue) static let recentEmoji = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.recentEmoji.rawValue) static let instantViewAppearance = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.instantViewAppearance.rawValue) + static let readArticles = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.readArticles.rawValue) + static let stickerSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.stickerSettings.rawValue) + static let launchSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.launchSettings.rawValue) + static let autoplayMedia = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.autoplayMedia.rawValue) + static let downloadedPaths = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.downloadedPaths.rawValue) + static let walletPasscodeTimeout = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.walletPasscodeTimeout.rawValue) + static let chatListSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatListSettings.rawValue) + static let voipDerivedState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voipDerivedState.rawValue) +} + +struct ApplicationSharedPreferencesKeys { + static let baseAppSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.baseAppSettings.rawValue) + static let inAppNotificationSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.inAppNotificationSettings.rawValue) + static let themeSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.themeSettings.rawValue) + static let autoNight = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.autoNight.rawValue) + static let additionalSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.additionalSettings.rawValue) + static let voiceCallSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voiceCallSettings.rawValue) + static let passcodeSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.passcodeSettings.rawValue) + static let appConfiguration = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.appConfiguration.rawValue) +} + + +private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { + case instantPageStoredState = 0 + case cachedInstantPages = 1 +} + +public struct ApplicationSpecificItemCacheCollectionId { + public static let instantPageStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.instantPageStoredState.rawValue) + public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue) +} +private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { + case settingsSearchRecentItems = 0 +} + +public struct ApplicationSpecificOrderedItemListCollectionId { + public static let settingsSearchRecentItems = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.settingsSearchRecentItems.rawValue) } diff --git a/Telegram-Mac/ArchivedStickerPacksController.swift b/Telegram-Mac/ArchivedStickerPacksController.swift index d5682efe36..4a4fc0aa8a 100644 --- a/Telegram-Mac/ArchivedStickerPacksController.swift +++ b/Telegram-Mac/ArchivedStickerPacksController.swift @@ -8,18 +8,19 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore private final class ArchivedStickerPacksControllerArguments { - let account: Account + let context: AccountContext let openStickerPack: (StickerPackCollectionInfo) -> Void let removePack: (StickerPackCollectionInfo) -> Void - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void) { - self.account = account + init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void) { + self.context = context self.openStickerPack = openStickerPack self.removePack = removePack } @@ -30,99 +31,46 @@ private final class ArchivedStickerPacksControllerArguments { private enum ArchivedStickerPacksEntryId: Hashable { case index(Int32) case pack(ItemCollectionId) - + case loading var hashValue: Int { switch self { case let .index(index): return index.hashValue case let .pack(id): return id.hashValue - } - } - - static func ==(lhs: ArchivedStickerPacksEntryId, rhs: ArchivedStickerPacksEntryId) -> Bool { - switch lhs { - case let .index(index): - if case .index(index) = rhs { - return true - } else { - return false - } - case let .pack(id): - if case .pack(id) = rhs { - return true - } else { - return false - } + case .loading: + return -100 } } } private enum ArchivedStickerPacksEntry: TableItemListNodeEntry { case section(sectionId:Int32) - case info(sectionId:Int32, String) - case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool, ItemListStickerPackItemEditing) - + case info(sectionId:Int32, String, GeneralViewType) + case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool, ItemListStickerPackItemEditing, GeneralViewType) + case loading(Bool) var stableId: ArchivedStickerPacksEntryId { switch self { case .info: return .index(0) - case let .pack(_, _, info, _, _, _, _): + case .loading: + return .loading + case let .pack(_, _, info, _, _, _, _, _): return .pack(info.id) case let .section(sectionId): return .index((sectionId + 1) * 1000 - sectionId) } } - static func ==(lhs: ArchivedStickerPacksEntry, rhs: ArchivedStickerPacksEntry) -> Bool { - switch lhs { - case let .info(sectionId, text): - if case .info(sectionId, text) = rhs { - return true - } else { - return false - } - case let .pack(lhsSectionId, lhsIndex, lhsInfo, lhsTopItem, lhsCount, lhsEnabled, lhsEditing): - if case let .pack(rhsSectionId, rhsIndex, rhsInfo, rhsTopItem, rhsCount, rhsEnabled, rhsEditing) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsSectionId != rhsSectionId { - return false - } - if lhsInfo != rhsInfo { - return false - } - if lhsTopItem != rhsTopItem { - return false - } - if lhsCount != rhsCount { - return false - } - if lhsEnabled != rhsEnabled { - return false - } - if lhsEditing != rhsEditing { - return false - } - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } + var stableIndex:Int32 { switch self { case .info: return 0 + case .loading: + return -1 case .pack: fatalError("") case let .section(sectionId): @@ -132,10 +80,11 @@ private enum ArchivedStickerPacksEntry: TableItemListNodeEntry { var index:Int32 { switch self { - - case let .info(sectionId, _): + case .loading: + return 0 + case let .info(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .pack( sectionId, index, _, _, _, _, _): + case let .pack( sectionId, index, _, _, _, _, _, _): return (sectionId * 1000) + 100 + index case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -148,10 +97,10 @@ private enum ArchivedStickerPacksEntry: TableItemListNodeEntry { func item(_ arguments: ArchivedStickerPacksControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .info(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .pack(_, _, info, topItem, count, enabled, editing): - return StickerSetTableRowItem(initialSize, account: arguments.account, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: editing, enabled: enabled, control: .remove, action: { + case let .info(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .pack(_, _, info, topItem, count, enabled, editing, viewType): + return StickerSetTableRowItem(initialSize, context: arguments.context, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: editing, enabled: enabled, control: .remove, viewType: viewType, action: { arguments.openStickerPack(info) }, addPack: { @@ -159,7 +108,9 @@ private enum ArchivedStickerPacksEntry: TableItemListNodeEntry { arguments.removePack(info) }) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case .loading(let loading): + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: loading, text: L10n.archivedStickersEmpty) } } } @@ -201,28 +152,40 @@ private struct ArchivedStickerPacksControllerState: Equatable { private func archivedStickerPacksControllerEntries(state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView) -> [ArchivedStickerPacksEntry] { var entries: [ArchivedStickerPacksEntry] = [] - var sectionId:Int32 = 1 - entries.append(.section(sectionId: sectionId)) - sectionId += 1 + if let packs = packs { - entries.append(.info(sectionId: sectionId, tr(.archivedStickersDescription))) - - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - var installedIds = Set() - if let view = installedView.views[.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionIdsView, let ids = view.idsByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - installedIds = ids - } - var index: Int32 = 0 - for item in packs { - if !installedIds.contains(item.info.id) { - entries.append(.pack(sectionId: sectionId, index, item.info, item.topItems.first, item.info.count, !state.removingPackIds.contains(item.info.id), ItemListStickerPackItemEditing(editable: true, editing: state.editing))) + if packs.isEmpty { + entries.append(.loading(false)) + } else { + var sectionId:Int32 = 1 + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + + entries.append(.info(sectionId: sectionId, L10n.archivedStickersDescription, .textTopItem)) + + 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 packs = packs.filter { item in + return !installedIds.contains(item.info.id) + } + + var index: Int32 = 0 + for item in packs { + entries.append(.pack(sectionId: sectionId, index, item.info, item.topItems.first, item.info.count, !state.removingPackIds.contains(item.info.id), ItemListStickerPackItemEditing(editable: true, editing: state.editing), bestGeneralViewType(packs, for: item))) index += 1 } + entries.append(.section(sectionId: sectionId)) + sectionId += 1 } + } else { + entries.append(.loading(true)) } return entries @@ -238,19 +201,33 @@ private func prepareTransition(left:[AppearanceWrapperEntry Void + init(_ context: AccountContext, archived: [ArchivedStickerPackItem]?, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void) { + self.archived = archived + self.updatedPacks = updatedPacks + super.init(context) + } + + deinit { + disposable.dispose() + } override func viewDidLoad() { super.viewDidLoad() - let account = self.account + let context = self.context let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ArchivedStickerPacksControllerState()) let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - let actionsDisposable = DisposableSet() + let updatedPacks = self.updatedPacks + let actionsDisposable = DisposableSet() + let resolveDisposable = MetaDisposable() actionsDisposable.add(resolveDisposable) @@ -258,15 +235,21 @@ class ArchivedStickerPacksController: TableViewController { actionsDisposable.add(removePackDisposables) let stickerPacks = Promise<[ArchivedStickerPackItem]?>() - stickerPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map { Optional($0) })) + stickerPacks.set(.single(archived) |> then(archivedStickerPacks(account: context.account) |> map { Optional($0) })) let installedStickerPacks = Promise() - installedStickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + installedStickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + + + actionsDisposable.add(stickerPacks.get().start(next: { packs in + updatedPacks(packs) + })) - let arguments = ArchivedStickerPacksControllerArguments(account: account, openStickerPack: { info in - showModal(with: StickersPackPreviewModalController(account, peerId: nil, reference: .name(info.shortName)), for: mainWindow) + + let arguments = ArchivedStickerPacksControllerArguments(context: context, openStickerPack: { info in + showModal(with: StickerPackPreviewModalController(context, peerId: nil, reference: .name(info.shortName)), for: mainWindow) }, removePack: { info in - confirm(for: mainWindow, with: appName, and: tr(.chatConfirmActionUndonable), successHandler: { _ in + confirm(for: context.window, information: tr(L10n.chatConfirmActionUndonable), successHandler: { _ in var remove = false updateState { state in var removingPackIds = state.removingPackIds @@ -295,7 +278,7 @@ class ArchivedStickerPacksController: TableViewController { return .complete() } - removePackDisposables.set((removeArchivedStickerPack(account: account, info: info) |> then(applyPacks) |> deliverOnMainQueue).start(completed: { + removePackDisposables.set((removeArchivedStickerPack(account: context.account, info: info) |> then(applyPacks) |> deliverOnMainQueue).start(completed: { updateState { state in var removingPackIds = state.removingPackIds removingPackIds.remove(info.id) @@ -309,15 +292,21 @@ class ArchivedStickerPacksController: TableViewController { let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = atomicSize - genericView.merge(with: combineLatest(statePromise.get(), stickerPacks.get(), installedStickerPacks.get(), appearanceSignal) |> deliverOnMainQueue + + let signal = combineLatest(queue: prepareQueue, statePromise.get(), stickerPacks.get(), installedStickerPacks.get(), appearanceSignal) |> map { state, packs, installedView, appearance -> TableUpdateTransition in let entries = archivedStickerPacksControllerEntries(state: state, packs: packs, installedView: installedView).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) - } |> afterDisposed { + } |> afterDisposed { actionsDisposable.dispose() - }) + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) + - readyOnce() } } diff --git a/Telegram-Mac/ArchiverContext.swift b/Telegram-Mac/ArchiverContext.swift new file mode 100644 index 0000000000..5379524a1f --- /dev/null +++ b/Telegram-Mac/ArchiverContext.swift @@ -0,0 +1,199 @@ +// +// ArchiverContext.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// +import Zip +import SwiftSignalKit + +enum ArchiveStatus : Equatable { + case none + case waiting + case done(URL) + case fail(ZipError) + case progress(Double) +} +enum ArchiveSource : Hashable { + static func == (lhs: ArchiveSource, rhs: ArchiveSource) -> Bool { + switch lhs { + case let .resource(lhsResource): + if case let .resource(rhsResource) = rhs { + return lhsResource.isEqual(to: rhsResource) + } else { + return false + } + } + } + + var contents:[URL] { + switch self { + case let .resource(resource): + if resource.path.contains("tg_temp_archive_") { + let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: resource.path), includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles) + return files ?? [URL(fileURLWithPath: resource.path)] + } + return [URL(fileURLWithPath: resource.path)] + } + } + + + var hashValue: Int { + switch self { + case let .resource(resource): + return resource.id.hashValue + } + } + + var destinationURL: URL { + return URL(fileURLWithPath: NSTemporaryDirectory() + "tarchive-\(self.uniqueId).zip") + } + + + var uniqueId: Int64 { + switch self { + case .resource(let resource): + return resource.randomId + } + } + + case resource(LocalFileArchiveMediaResource) +} + +private final class Archiver { + private let status: ValuePromise = ValuePromise(.waiting, ignoreRepeated: true) + var statusSignal:Signal { + return status.get() + } + let destination: URL + private let source: ArchiveSource + private let queue: Queue + init(source : ArchiveSource, queue: Queue) { + self.queue = queue + self.source = source + self.destination = source.destinationURL + } + + func start(cancelToken:@escaping()->Bool) { + let destination = self.destination + let source = self.source + queue.async { [weak status] in + guard let status = status else {return} + let contents = source.contents + if !contents.isEmpty { + do { + try Zip.zipFiles(paths: contents, zipFilePath: destination, password: nil, compression: ZipCompression.BestCompression, progress: { progress in + status.set(.progress(progress)) + }, cancel: cancelToken) + status.set(.done(destination)) + } catch { + if let error = error as? ZipError { + status.set(.fail(error)) + } + } + } + } + + } + +} +// добавить отмену архивирования если разлонигиваемся +private final class ArchiveStatusContext { + var status: ArchiveStatus = .none + let subscribers = Bag<(ArchiveStatus) -> Void>() +} +class ArchiverContext { + var statuses:[ArchiveSource : ArchiveStatus] = [:] + + private let queue = Queue(name: "ArchiverContext") + private var contexts: [ArchiveSource: Archiver] = [:] + private let archiveQueue: Queue = Queue.concurrentDefaultQueue() + private var statusContexts: [ArchiveSource: ArchiveStatusContext] = [:] + private var statusesDisposable:[ArchiveSource : Disposable] = [:] + private var cancelledTokens:[ArchiveSource : Any] = [:] + init() { + } + + deinit { + self.queue.sync { + self.contexts.removeAll() + for status in statusesDisposable { + status.value.dispose() + } + } + } + + func remove(_ source: ArchiveSource) { + queue.async { + self.contexts.removeValue(forKey: source) + self.statusesDisposable[source]?.dispose() + self.statuses.removeValue(forKey: source) + self.cancelledTokens[source] = true + } + } + + func archive(_ source: ArchiveSource, startIfNeeded: Bool = false) -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + guard let `self` = self else { return EmptyDisposable } + if self.statusContexts[source] == nil { + self.statusContexts[source] = ArchiveStatusContext() + } + + let statusContext = self.statusContexts[source]! + + let index = statusContext.subscribers.add({ status in + subscriber.putNext(status) + }) + + if let _ = self.contexts[source] { + if let statusContext = self.statusContexts[source] { + for subscriber in statusContext.subscribers.copyItems() { + subscriber(statusContext.status) + } + } + } else { + if startIfNeeded { + let archiver = Archiver(source: source, queue: self.archiveQueue) + self.contexts[source] = archiver + self.statusesDisposable[source] = (archiver.statusSignal |> deliverOn(queue)).start(next: { status in + statusContext.status = status + for subscriber in statusContext.subscribers.copyItems() { + subscriber(statusContext.status) + } + }, completed: { + subscriber.putCompletion() + }) + + archiver.start(cancelToken: { + var cancelled: Bool = false + queue.sync { + cancelled = self.cancelledTokens[source] != nil + self.cancelledTokens.removeValue(forKey: source) + } + return cancelled + }) + } else { + for subscriber in statusContext.subscribers.copyItems() { + subscriber(statusContext.status) + } + } + + } + + + return ActionDisposable { + self.queue.async { + if let current = self.statusContexts[source] { + current.subscribers.remove(index) + } + } + } + } |> runOn(queue) + } + +} + + + diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json index ae1eb5f152..fab0003b74 100644 --- a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,61 +3,61 @@ { "size" : "16x16", "idiom" : "mac", - "filename" : "icon2_16.png", + "filename" : "Default_16x16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", - "filename" : "icon2_16@2x.png", + "filename" : "Default_16x16@2x.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", - "filename" : "icon2_32.png", + "filename" : "Default_32x32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", - "filename" : "icon2_32@2x.png", + "filename" : "Default_32x32@2x.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", - "filename" : "icon2_128.png", + "filename" : "Default_128x128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", - "filename" : "icon2_128@2x.png", + "filename" : "Default_128x128@2x.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", - "filename" : "icon2_256.png", + "filename" : "Default_256x256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", - "filename" : "icon2_256@2x.png", + "filename" : "Default_256x256@2x.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", - "filename" : "icon2_512.png", + "filename" : "Default_512x512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", - "filename" : "icon2_512x512@2x.png", + "filename" : "Default_512x512@2x.png", "scale" : "2x" } ], diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128.png new file mode 100644 index 0000000000..36a98cdaf6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128@2x.png new file mode 100644 index 0000000000..b609122444 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_128x128@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16.png new file mode 100644 index 0000000000..b83a7c8600 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16@2x.png new file mode 100644 index 0000000000..dc2283863b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_16x16@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256.png new file mode 100644 index 0000000000..b609122444 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256@2x.png new file mode 100644 index 0000000000..362d36cbda Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_256x256@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32.png new file mode 100644 index 0000000000..dc2283863b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32@2x.png new file mode 100644 index 0000000000..4ddd146819 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_32x32@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512.png new file mode 100644 index 0000000000..362d36cbda Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512@2x.png new file mode 100644 index 0000000000..752c34d7a1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/Default_512x512@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16.png deleted file mode 100644 index c1684ec3d3..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16@2x.png deleted file mode 100644 index 5039300522..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_16@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256.png deleted file mode 100644 index 9ca9baf790..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256@2x.png deleted file mode 100644 index 11e91102bb..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_256@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32.png deleted file mode 100644 index 5039300522..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32@2x.png deleted file mode 100644 index cd940b583d..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_32@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512.png deleted file mode 100644 index b072e4f33c..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512x512@2x.png b/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512x512@2x.png deleted file mode 100644 index 38ac220d20..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_512x512@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/Contents.json new file mode 100644 index 0000000000..3655d21fb4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "DiscussDarkBluePreview.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "DiscussDarkBluePreview@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview.png b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview.png new file mode 100644 index 0000000000..b81f5d544d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview.png differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview@2x.png b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview@2x.png new file mode 100644 index 0000000000..f7c4f60b16 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDarkBluePreview.imageset/DiscussDarkBluePreview@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/Contents.json new file mode 100644 index 0000000000..6709d95eb3 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "DiscussDarkPreview.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "DiscussDarkPreview@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview.png b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview.png new file mode 100644 index 0000000000..7238c06b53 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview.png differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview@2x.png b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview@2x.png new file mode 100644 index 0000000000..581c987955 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDarkPreview.imageset/DiscussDarkPreview@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/Contents.json new file mode 100644 index 0000000000..031e41e0b0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "DiscussDayPreview.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "DiscussDayPreview@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview.png b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview.png new file mode 100644 index 0000000000..82279aa518 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview.png differ diff --git a/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview@2x.png b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview@2x.png new file mode 100644 index 0000000000..f945f76e5a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/DiscussDayPreview.imageset/DiscussDayPreview@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/Contents.json new file mode 100644 index 0000000000..e8ff6f4522 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_addstickers.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_addstickers@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers.png b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers.png new file mode 100644 index 0000000000..0633484d16 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers@2x.png new file mode 100644 index 0000000000..37fe08e93e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AddFeaturedStickers.imageset/ic_addstickers@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/Contents.json new file mode 100644 index 0000000000..e025fa4149 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "check@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "check@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@1x.png b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@1x.png new file mode 100644 index 0000000000..c226940609 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@2x.png new file mode 100644 index 0000000000..3a8fd97eb0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AlertCheckBoxMark.imageset/check@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Contents.json new file mode 100644 index 0000000000..42db5e5372 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Update.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Update@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update.png b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update.png new file mode 100644 index 0000000000..79bbd5cb48 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update@2x.png new file mode 100644 index 0000000000..b63f16afef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppUpdate.imageset/Update@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/Contents.json new file mode 100644 index 0000000000..303203107d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ThemeMac.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ThemeMac@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac.png b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac.png new file mode 100644 index 0000000000..e7a2283314 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac@2x.png new file mode 100644 index 0000000000..707dbebece Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppearanceAddTheme.imageset/ThemeMac@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/Contents.json new file mode 100644 index 0000000000..114988ec9d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_theme@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_theme@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@1x.png b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@1x.png new file mode 100644 index 0000000000..ae901ccbbf Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@2x.png new file mode 100644 index 0000000000..4f6ed792a0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AppearanceSettings.imageset/ic_theme@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/Contents.json new file mode 100644 index 0000000000..36797f720f --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "archiveavatar.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "archiveavatar@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar.png b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar.png new file mode 100644 index 0000000000..c7144e5714 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar@2x (1).png new file mode 100644 index 0000000000..aef04b1cbe Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ArchiveAvatar.imageset/archiveavatar@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/Contents.json new file mode 100644 index 0000000000..bc0e0d9bd6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_poll.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_poll@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll.png b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll.png new file mode 100644 index 0000000000..2205b5d55e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll@2x.png new file mode 100644 index 0000000000..0547bff5c9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AttachPoll.imageset/ic_poll@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/Contents.json new file mode 100644 index 0000000000..3b16380d65 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "avatar_scheduled.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "avatar_scheduled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled.png b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled.png new file mode 100644 index 0000000000..07450f288b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled@2x.png b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled@2x.png new file mode 100644 index 0000000000..21c9d2cf36 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_AvatarScheduled.imageset/avatar_scheduled@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/Contents.json index 10268ace8e..6ff3ad9b7a 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_datesearch@1x.png", + "filename" : "ic_calendar.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_datesearch@2x.png", + "filename" : "ic_calendar@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar.png b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar.png new file mode 100644 index 0000000000..293285142a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar@2x.png new file mode 100644 index 0000000000..9a034eb340 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_calendar@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@1x.png b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@1x.png deleted file mode 100644 index d9a74aabb6..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@2x.png deleted file mode 100644 index 6dee5b3c28..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_Calendar.imageset/ic_datesearch@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/Contents.json new file mode 100644 index 0000000000..d6f0cec6aa --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_help.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_help@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help.png new file mode 100644 index 0000000000..c952c47ebe Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help@2x.png new file mode 100644 index 0000000000..206743b52b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChannelPromoInfo.imageset/ic_help@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare.png deleted file mode 100644 index 92d6b30246..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare@2x.png deleted file mode 100644 index 7c08269920..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ChannelShare@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/Contents.json index 8a6b520186..445cb8e64f 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ChannelShare.png", + "filename" : "ic_forward.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ChannelShare@2x.png", + "filename" : "ic_forward@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward.png new file mode 100644 index 0000000000..b78eaccdd5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward@2x.png new file mode 100644 index 0000000000..de19303201 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChannelShare.imageset/ic_forward@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/Contents.json new file mode 100644 index 0000000000..f605e52e0b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "scheduledmenu.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "scheduledmenu@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu.png new file mode 100644 index 0000000000..80c1f0ca03 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu@2x.png new file mode 100644 index 0000000000..9812d7aed1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatActionScheduled.imageset/scheduledmenu@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/Contents.json index a7d9b14e4b..7706bf794c 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_more@1x.png", + "filename" : "ic_more.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more.png new file mode 100644 index 0000000000..b2d9e23aef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@1x.png deleted file mode 100644 index 903f7ae375..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@2x.png index 574f7a52a2..677a27f31a 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_ChatActions.imageset/ic_more@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/Contents.json index 60396c2489..7706bf794c 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_moreact@1x.png", + "filename" : "ic_more.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_moreact@2x.png", + "filename" : "ic_more@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more.png new file mode 100644 index 0000000000..b2d9e23aef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more@2x.png new file mode 100644 index 0000000000..677a27f31a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_more@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@1x.png deleted file mode 100644 index c1ac27724d..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@2x.png deleted file mode 100644 index 009e5c3d1a..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatActionsActive.imageset/ic_moreact@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin.png b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin.png new file mode 100644 index 0000000000..0ec218bae6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin@2x.png new file mode 100644 index 0000000000..cd0540ea10 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Admin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Contents.json new file mode 100644 index 0000000000..26f821ec4c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatAdmins.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Admin.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Admin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/Contents.json new file mode 100644 index 0000000000..d1e20f9543 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_archive (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_archive@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive (1).png b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive (1).png new file mode 100644 index 0000000000..f06aac3009 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive@2x (1).png new file mode 100644 index 0000000000..eea38c184d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatArchive.imageset/ic_archive@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned.png b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned.png new file mode 100644 index 0000000000..4fa8b77973 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned@2x.png new file mode 100644 index 0000000000..eb85609dbc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Banned@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Contents.json new file mode 100644 index 0000000000..e0f5be5473 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatBanned.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Banned.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Banned@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/Contents.json new file mode 100644 index 0000000000..364e260913 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_goto@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_goto@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@1x.png new file mode 100644 index 0000000000..0a0ec38112 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@2x.png new file mode 100644 index 0000000000..fa34235a0c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatGoMessage.imageset/ic_goto@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/Contents.json new file mode 100644 index 0000000000..ae2de6c2f7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "scheduled.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "scheduled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled.png b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled.png new file mode 100644 index 0000000000..2b00ab95ae Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled@2x.png new file mode 100644 index 0000000000..38039db642 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatInputScheduled.imageset/scheduled@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/Contents.json new file mode 100644 index 0000000000..7faef628d1 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_down@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_down@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@1x.png new file mode 100644 index 0000000000..53e3817944 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@2x.png new file mode 100644 index 0000000000..930be3537c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListScrollUnread.imageset/ic_down@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/Contents.json new file mode 100644 index 0000000000..64d209f0c8 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "archive.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "archive@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive.png new file mode 100644 index 0000000000..0a346e0813 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive@2x.png new file mode 100644 index 0000000000..2a0f3b54de Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Archive.imageset/archive@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/Contents.json new file mode 100644 index 0000000000..d6001b144d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unarchive.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "unarchive@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive.png new file mode 100644 index 0000000000..adb165674c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive@2x.png new file mode 100644 index 0000000000..826f2b9d0c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListSwiping_Unarchive.imageset/unarchive@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/Contents.json new file mode 100644 index 0000000000..34120fe1c8 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "playchats.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "playchats@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats.png new file mode 100644 index 0000000000..dd6663c62f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats@2x.png new file mode 100644 index 0000000000..673a46128d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatListThumbPlay.imageset/playchats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Contents.json new file mode 100644 index 0000000000..6435c03f53 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Members.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Members@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members.png b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members.png new file mode 100644 index 0000000000..3d8a7f7242 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members@2x.png new file mode 100644 index 0000000000..8188aed27c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatMembers.imageset/Members@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/Contents.json index af3c120c93..156658228a 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "arrow@1x.png", + "filename" : "ic_back.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "arrow@2x.png", + "filename" : "ic_back@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@1x.png deleted file mode 100644 index cffa5fc4ff..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@2x.png deleted file mode 100644 index 88a59d94f9..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/arrow@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back.png b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back.png new file mode 100644 index 0000000000..76229a2cf5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back@2x.png new file mode 100644 index 0000000000..e4ea9b097e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatNavigationBack.imageset/ic_back@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/Contents.json new file mode 100644 index 0000000000..a4d7985468 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "send@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "send@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@1x.png new file mode 100644 index 0000000000..80d0538db0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@2x.png new file mode 100644 index 0000000000..1941f769b9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatOverlayRecordingSend.imageset/send@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Contents.json new file mode 100644 index 0000000000..4f7b8946f2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Permissions.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Permissions@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions.png b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions.png new file mode 100644 index 0000000000..10340027a3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions@2x.png new file mode 100644 index 0000000000..27b5b7218e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatPermissions.imageset/Permissions@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/Contents.json index 5b0c487f74..81735d6821 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_cancel@1x.png", + "filename" : "ic_close.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_cancel@2x.png", + "filename" : "ic_close@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@1x.png deleted file mode 100644 index ac06a420ba..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@2x.png deleted file mode 100644 index 176df09770..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_cancel@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close.png new file mode 100644 index 0000000000..92e2be2e24 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close@2x.png new file mode 100644 index 0000000000..678a34747c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchCancel.imageset/ic_close@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/Contents.json index 5d0377a724..44507263bc 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_usersearch@1x.png", + "filename" : "ic_member.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_usersearch@2x.png", + "filename" : "ic_member@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member.png new file mode 100644 index 0000000000..08cfee9258 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member@2x.png new file mode 100644 index 0000000000..b6b3c4e05b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_member@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@1x.png deleted file mode 100644 index 95f57b764c..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@2x.png deleted file mode 100644 index 1863adb747..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ChatSearchFrom.imageset/ic_usersearch@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/Contents.json new file mode 100644 index 0000000000..9a309d8ebd --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "delete.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "delete@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete.png new file mode 100644 index 0000000000..ce8aea7d4d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete@2x.png new file mode 100644 index 0000000000..405430c95c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingDelete.imageset/delete@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/Contents.json new file mode 100644 index 0000000000..46ba9a2cda --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mute.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mute@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute.png new file mode 100644 index 0000000000..90640686ca Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute@2x.png new file mode 100644 index 0000000000..33ef79ce26 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingMute.imageset/mute@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/Contents.json new file mode 100644 index 0000000000..35ee767046 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pin.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin.png new file mode 100644 index 0000000000..3c670e5cb3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin@2x.png new file mode 100644 index 0000000000..e50ee21a31 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingPin.imageset/pin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/Contents.json new file mode 100644 index 0000000000..44c45f808f --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "read.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "read@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read.png new file mode 100644 index 0000000000..5977e6b37e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read@2x.png new file mode 100644 index 0000000000..b2a53720db Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingRead.imageset/read@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/Contents.json new file mode 100644 index 0000000000..20ffbe6a3d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unmute.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "unmute@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute.png new file mode 100644 index 0000000000..acd9d2c1cc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute@2x.png new file mode 100644 index 0000000000..ab8c7f901e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnmute.imageset/unmute@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/Contents.json new file mode 100644 index 0000000000..152ddde7fd --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unpin.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "unpin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin.png new file mode 100644 index 0000000000..b05f16cd21 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin@2x.png new file mode 100644 index 0000000000..417be73f3e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnpin.imageset/unpin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/Contents.json new file mode 100644 index 0000000000..21ec686112 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "unread.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "unread@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread.png new file mode 100644 index 0000000000..b14767bfc7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread@2x.png new file mode 100644 index 0000000000..b9106d7c57 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatSwipingUnread.imageset/unread@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/Contents.json new file mode 100644 index 0000000000..299e64531d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "link@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/link@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/link@2x.png new file mode 100644 index 0000000000..83b231ddd1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatTouchBarAddLink.imageset/link@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/Contents.json new file mode 100644 index 0000000000..32d2d2658d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unarchive.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unarchive@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive.png b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive.png new file mode 100644 index 0000000000..5332b05b91 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive@2x.png new file mode 100644 index 0000000000..e90cfb8cf0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatUnarchive.imageset/ic_unarchive@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/Contents.json new file mode 100644 index 0000000000..44904a161c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_undo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_undo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo.png b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo.png new file mode 100644 index 0000000000..714aef12be Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo@2x.png new file mode 100644 index 0000000000..a5f0d4a930 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ChatUndoAction.imageset/ic_undo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/Contents.json new file mode 100644 index 0000000000..ee71834bbd --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_clear@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_clear@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@1x.png new file mode 100644 index 0000000000..ededbd4bf7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@2x.png new file mode 100644 index 0000000000..3fdeb57429 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ClearChat.imageset/ic_clear@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/Contents.json new file mode 100644 index 0000000000..f469ef8a29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "canceldownload.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "canceldownload@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload.png b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload.png new file mode 100644 index 0000000000..9bb2cf890d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload@2x.png b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload@2x.png new file mode 100644 index 0000000000..71d8de08f6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_CompactStreamingFetchingCancel.imageset/canceldownload@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/Contents.json new file mode 100644 index 0000000000..4f69318514 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icon2_128.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "icon2_128@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_128.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/icon2_128.png similarity index 100% rename from Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_128.png rename to Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/icon2_128.png diff --git a/Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_128@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/icon2_128@2x.png similarity index 100% rename from Telegram-Mac/Assets.xcassets/AppIcon.appiconset/icon2_128@2x.png rename to Telegram-Mac/Assets.xcassets/Icon_ConfirmAppAccessory.imageset/icon2_128@2x.png diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/Contents.json new file mode 100644 index 0000000000..d7c8b8ab9b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "deletechat@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "deletechat@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@1x.png new file mode 100644 index 0000000000..3cdbbb3e6e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@2x.png new file mode 100644 index 0000000000..7271feb618 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteChatAccessory.imageset/deletechat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/Contents.json new file mode 100644 index 0000000000..9a30ab525e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "deletemessage@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "deletemessage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@1x.png new file mode 100644 index 0000000000..89c978292f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@2x.png new file mode 100644 index 0000000000..66ed6bf760 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmDeleteMessagesAccessory.imageset/deletemessage@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/Contents.json new file mode 100644 index 0000000000..e857164ef9 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pin@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@1x.png new file mode 100644 index 0000000000..f431cab3b2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@2x.png new file mode 100644 index 0000000000..1e2d9dc171 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ConfirmPinAccessory.imageset/pin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/Contents.json new file mode 100644 index 0000000000..c3d710f4d5 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "DFRRepeat2.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/DFRRepeat2.png b/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/DFRRepeat2.png new file mode 100644 index 0000000000..8046c5456b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DFRRepeat.imageset/DFRRepeat2.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/Contents.json new file mode 100644 index 0000000000..015498a1f8 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "DFRShuffle@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/DFRShuffle@2x.png b/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/DFRShuffle@2x.png new file mode 100644 index 0000000000..acee28ca6c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DFRShuffle.imageset/DFRShuffle@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Contents.json new file mode 100644 index 0000000000..ad76c2fcd9 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Ghost.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Ghost@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost.png b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost.png new file mode 100644 index 0000000000..3df848b36f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost@2x.png b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost@2x.png new file mode 100644 index 0000000000..4a03717943 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DeletedAccount.imageset/Ghost@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/Contents.json index 38924cc735..d000341f1d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "glyph-info.png", + "filename" : "ic_info.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "glyph-info@2x.png", + "filename" : "ic_info@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info.png b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info.png deleted file mode 100644 index 190cf3f3fa..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info@2x.png b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info@2x.png deleted file mode 100644 index f615bea5c9..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/glyph-info@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info.png b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info.png new file mode 100644 index 0000000000..afbc479ddc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info@2x.png b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info@2x.png new file mode 100644 index 0000000000..580d1ee2c1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_DetailedInfo.imageset/ic_info@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Contents.json new file mode 100644 index 0000000000..f6a7c7fd65 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Draw.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Draw@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw.png new file mode 100644 index 0000000000..717f9178e2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw@2x.png new file mode 100644 index 0000000000..9ac46e1dee Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageDraw.imageset/Draw@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Contents.json new file mode 100644 index 0000000000..3a4d4cee71 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Eraser.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Eraser@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser.png new file mode 100644 index 0000000000..81ab276be6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser@2x.png new file mode 100644 index 0000000000..b2e320d6c8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageEraser.imageset/Eraser@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/Contents.json new file mode 100644 index 0000000000..ed5bcc95f4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "flip.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "flip@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip.png new file mode 100644 index 0000000000..12a4d219d3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip@2x (1).png new file mode 100644 index 0000000000..14ece941c0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageFlip.imageset/flip@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/Contents.json new file mode 100644 index 0000000000..dff015368f --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "rotate.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "rotate@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate.png new file mode 100644 index 0000000000..af4642119b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate@2x (1).png new file mode 100644 index 0000000000..3e818cf844 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageRotate.imageset/rotate@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/Contents.json new file mode 100644 index 0000000000..c48da651b4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "sizes.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sizes@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes.png new file mode 100644 index 0000000000..196f1e4560 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes@2x (1).png new file mode 100644 index 0000000000..12174b3621 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageSizes.imageset/sizes@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Contents.json new file mode 100644 index 0000000000..290bfb05a6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Undo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Undo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo.png new file mode 100644 index 0000000000..32bcb92e9d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo@2x.png new file mode 100644 index 0000000000..ce7c5dd58a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditImageUndo.imageset/Undo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/Contents.json new file mode 100644 index 0000000000..30f8894de6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "editcurrent.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "editcurrent@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent.png b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent.png new file mode 100644 index 0000000000..5fe5629f8b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent@2x.png new file mode 100644 index 0000000000..a01143785c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EditMessageCurrentPhoto.imageset/editcurrent@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City.png new file mode 100644 index 0000000000..adbfd738f5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City@2x.png new file mode 100644 index 0000000000..ca37631ca2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/City@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/Contents.json index 975ca03420..2a525eecf5 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_city@1x.png", + "filename" : "City.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_city@2x.png", + "filename" : "City@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@1x.png deleted file mode 100644 index 6527124ce5..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@2x.png deleted file mode 100644 index 24c63434ff..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabCar.imageset/ic_city@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Contents.json index cd14e1a9ff..24e67803a4 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_flags@1x.png", + "filename" : "Flag.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_flags@2x.png", + "filename" : "Flag@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag.png new file mode 100644 index 0000000000..18c7972159 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag@2x.png new file mode 100644 index 0000000000..37ded39b53 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/Flag@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@1x.png deleted file mode 100644 index 4efda39374..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@2x.png deleted file mode 100644 index 423c9d5a25..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFlag.imageset/ic_flags@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Contents.json index 3299627275..2ee6157f04 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_eats@1x.png", + "filename" : "Food.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_eats@2x.png", + "filename" : "Food@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food.png new file mode 100644 index 0000000000..e0885c4c2d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food@2x.png new file mode 100644 index 0000000000..563736e7ac Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/Food@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@1x.png deleted file mode 100644 index f4db82edc1..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@2x.png deleted file mode 100644 index 7e81d6d9fe..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabFood.imageset/ic_eats@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals.png new file mode 100644 index 0000000000..a475d95716 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals@2x.png new file mode 100644 index 0000000000..01f9fc6937 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Animals@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Contents.json index 91b4081e8c..c35cc2ff72 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_animals@1x.png", + "filename" : "Animals.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_animals@2x.png", + "filename" : "Animals@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@1x.png deleted file mode 100644 index d5c2d3f42e..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@2x.png deleted file mode 100644 index f36f290ee9..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabNature.imageset/ic_animals@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Contents.json index d0228be721..db51039cd3 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_lamp@1x.png", + "filename" : "Lamp.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_lamp@2x.png", + "filename" : "Lamp@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp.png new file mode 100644 index 0000000000..48cc591464 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp@2x.png new file mode 100644 index 0000000000..c3511ee789 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/Lamp@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@1x.png deleted file mode 100644 index a9dafa9315..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@2x.png deleted file mode 100644 index 913421c8b4..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabObjects.imageset/ic_lamp@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Contents.json index 40149a86d2..5631d55a33 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_smiles@1x.png", + "filename" : "Smile.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_smiles@2x.png", + "filename" : "Smile@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile.png new file mode 100644 index 0000000000..a6d7657cdf Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile@2x.png new file mode 100644 index 0000000000..836dea9c97 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/Smile@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@1x.png deleted file mode 100644 index e6a56ee588..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@2x.png deleted file mode 100644 index d15a76de1f..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSmiles.imageset/ic_smiles@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Contents.json index ecd562bf81..a250d31a03 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_sports@1x.png", + "filename" : "Sport.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_sports@2x.png", + "filename" : "Sport@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport.png new file mode 100644 index 0000000000..a7db3fab0e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport@2x.png new file mode 100644 index 0000000000..8ab463a78a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/Sport@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@1x.png deleted file mode 100644 index c281d729b0..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@2x.png deleted file mode 100644 index 0d70bd6a40..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSports.imageset/ic_sports@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Contents.json index bc2009dd9d..92d8c9ff14 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_symb@1x.png", + "filename" : "Symbols.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_symb@2x.png", + "filename" : "Symbols@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols.png new file mode 100644 index 0000000000..916269be08 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols@2x.png new file mode 100644 index 0000000000..2355a28c1f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/Symbols@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@1x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@1x.png deleted file mode 100644 index cd0008aa38..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@2x.png b/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@2x.png deleted file mode 100644 index d8550d284c..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_EmojiTabSymbols.imageset/ic_symb@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Contents.json new file mode 100644 index 0000000000..d2764552ba --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Smiles.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Smiles@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles.png new file mode 100644 index 0000000000..d0ae54aa4d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles@2x.png new file mode 100644 index 0000000000..a1316b0dc9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Emoji.imageset/Smiles@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Contents.json new file mode 100644 index 0000000000..6890c0589c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Gifs.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Gifs@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs.png new file mode 100644 index 0000000000..45aee7d33f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs@2x.png new file mode 100644 index 0000000000..90d0299c3c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Gifs.imageset/Gifs@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Contents.json new file mode 100644 index 0000000000..915688990a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Search.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Search@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search.png new file mode 100644 index 0000000000..84a2d1f850 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search@2x.png new file mode 100644 index 0000000000..e2692c7cbc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Search.imageset/Search@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close.png new file mode 100644 index 0000000000..cb17d282f8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close@2x.png new file mode 100644 index 0000000000..1c476c3e80 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Close@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Contents.json new file mode 100644 index 0000000000..ae2baaff29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_SearchCancel.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Close.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Contents.json new file mode 100644 index 0000000000..7ceea51f3d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Settings.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Settings@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings.png new file mode 100644 index 0000000000..f7388f856d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings@2x.png new file mode 100644 index 0000000000..a74862d424 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Settings.imageset/Settings@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Contents.json new file mode 100644 index 0000000000..9d8bfee079 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Stickers.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Stickers@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers.png new file mode 100644 index 0000000000..4aa94d4f4b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers@2x.png new file mode 100644 index 0000000000..ac5e94a9f2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Entertainment_Stickers.imageset/Stickers@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/Contents.json new file mode 100644 index 0000000000..32fa579a18 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_add.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_add@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add.png b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add.png new file mode 100644 index 0000000000..d1421de182 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add@2x.png new file mode 100644 index 0000000000..fd6fdb33fb Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterAdd.imageset/ic_add@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/Contents.json new file mode 100644 index 0000000000..d1e20f9543 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_archive (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_archive@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive (1).png b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive (1).png new file mode 100644 index 0000000000..cc0e471f43 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive@2x (1).png new file mode 100644 index 0000000000..f20ff8de1f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterArchive.imageset/ic_archive@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/Contents.json new file mode 100644 index 0000000000..501805ac2a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_bot.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_bot@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot.png b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot.png new file mode 100644 index 0000000000..1531619419 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot@2x.png new file mode 100644 index 0000000000..1958600b96 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterBots.imageset/ic_bot@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/Contents.json new file mode 100644 index 0000000000..2595db3b7d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_channel.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_channel@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel.png b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel.png new file mode 100644 index 0000000000..36e6cf8395 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel@2x.png new file mode 100644 index 0000000000..b757cf2444 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterChannels.imageset/ic_channel@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/Contents.json new file mode 100644 index 0000000000..19c51d3606 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_filter.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_filter@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter.png b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter.png new file mode 100644 index 0000000000..49ac6e01f3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter@2x.png new file mode 100644 index 0000000000..2b2b26a2a3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterCustom.imageset/ic_filter@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/Contents.json new file mode 100644 index 0000000000..15fc6bb37a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_customlist.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_customlist@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist.png b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist.png new file mode 100644 index 0000000000..635068a6ab Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist@2x.png new file mode 100644 index 0000000000..430b2793e6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterEdit.imageset/ic_customlist@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/Contents.json new file mode 100644 index 0000000000..152058a20c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_group.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_group@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group.png b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group.png new file mode 100644 index 0000000000..60dec9ec12 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group@2x.png new file mode 100644 index 0000000000..230c153ae1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterGroups.imageset/ic_group@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/Contents.json new file mode 100644 index 0000000000..44464d1640 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_largegroup.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_largegroup@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup.png b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup.png new file mode 100644 index 0000000000..7198c5b1cb Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup@2x.png new file mode 100644 index 0000000000..e5236173ae Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterLargeGroups.imageset/ic_largegroup@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/Contents.json new file mode 100644 index 0000000000..70063b6f29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_muted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_muted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted.png b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted.png new file mode 100644 index 0000000000..072e34519f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted@2x.png new file mode 100644 index 0000000000..71da5d66a2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterMuted.imageset/ic_muted@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/Contents.json new file mode 100644 index 0000000000..ab3139cc68 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_noncontact.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_noncontact@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact.png b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact.png new file mode 100644 index 0000000000..e908c2bd6a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact@2x.png new file mode 100644 index 0000000000..4249fe835b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterNonContacts.imageset/ic_noncontact@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/Contents.json new file mode 100644 index 0000000000..7cf411fdb7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_user.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_user@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user.png b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user.png new file mode 100644 index 0000000000..b319abe040 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user@2x.png new file mode 100644 index 0000000000..bb0c490105 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterPrivateChats.imageset/ic_user@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/Contents.json new file mode 100644 index 0000000000..39a49c5b0b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_read.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_read@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read.png b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read.png new file mode 100644 index 0000000000..76f59b0f6b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read@2x.png new file mode 100644 index 0000000000..edb935ca51 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterRead.imageset/ic_read@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/Contents.json new file mode 100644 index 0000000000..1f0d15ef0d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_secretchat.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_secretchat@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat.png b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat.png new file mode 100644 index 0000000000..9900b01051 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat@2x.png new file mode 100644 index 0000000000..65ea4cc9a2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterSecretChats.imageset/ic_secretchat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/Contents.json new file mode 100644 index 0000000000..3d4d31a176 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unmuted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unmuted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted.png b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted.png new file mode 100644 index 0000000000..a534eca9b1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted@2x.png new file mode 100644 index 0000000000..5117cbce30 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterUnmuted.imageset/ic_unmuted@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/Contents.json new file mode 100644 index 0000000000..0fc3beb1c2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unread.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unread@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread.png b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread.png new file mode 100644 index 0000000000..1ff9519368 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread@2x.png b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread@2x.png new file mode 100644 index 0000000000..4aa1df6eb3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_FilterUnread.imageset/ic_unread@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Contents.json index 309f9f74a0..2a95784536 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "Photo_More.png", + "filename" : "morephoto@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "Photo_More@2x.png", + "filename" : "morephoto@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More.png deleted file mode 100644 index b1434293ad..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More@2x.png deleted file mode 100644 index 4bc2e1c34e..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/Photo_More@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@1x.png new file mode 100644 index 0000000000..4d2e970ff2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@2x.png new file mode 100644 index 0000000000..ddb825f374 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryMore.imageset/morephoto@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/Contents.json new file mode 100644 index 0000000000..4e824eaacf --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "rightphoto@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "rightphoto@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@1x.png new file mode 100644 index 0000000000..088bdee045 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@2x.png new file mode 100644 index 0000000000..344a66f97a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryNext.imageset/rightphoto@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/Contents.json new file mode 100644 index 0000000000..bf3bc00980 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "leftphoto@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "leftphoto@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@1x.png new file mode 100644 index 0000000000..5d2db4a7a8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@2x.png new file mode 100644 index 0000000000..10ce9fc7c8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryPrev.imageset/leftphoto@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/Contents.json new file mode 100644 index 0000000000..3ffe9ef165 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "turn 2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "turn 2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2.png new file mode 100644 index 0000000000..1546902f33 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2@2x.png new file mode 100644 index 0000000000..c420fdb448 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryRotate.imageset/turn 2@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/Contents.json new file mode 100644 index 0000000000..8fe5fa020c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "sharephoto@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sharephoto@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@1x.png new file mode 100644 index 0000000000..d009cd5175 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@2x.png new file mode 100644 index 0000000000..a4570cdaf2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryShare.imageset/sharephoto@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/Contents.json new file mode 100644 index 0000000000..501e522c58 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "zoomin 2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "zoomin 2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2.png new file mode 100644 index 0000000000..789c273281 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2@2x.png new file mode 100644 index 0000000000..0cb904e2c3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomIn.imageset/zoomin 2@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/Contents.json new file mode 100644 index 0000000000..c2b8d4441e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "zooout 2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "zooout 2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2.png new file mode 100644 index 0000000000..d80fb883f9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2@2x.png new file mode 100644 index 0000000000..2c229c8224 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GalleryZoomOut.imageset/zooout 2@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Contents.json new file mode 100644 index 0000000000..440eb19baa --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Download (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Download@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download (1).png b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download (1).png new file mode 100644 index 0000000000..959de0b716 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download@2x (1).png new file mode 100644 index 0000000000..acd6a9e690 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Gallery_FastSave.imageset/Download@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Contents.json new file mode 100644 index 0000000000..ee2034cd9b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Trending (2).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "trending@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Trending (2).png b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Trending (2).png new file mode 100644 index 0000000000..4b4b0c9b55 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/Trending (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/trending@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/trending@2x.png new file mode 100644 index 0000000000..0a77737c0e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GifTrending.imageset/trending@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/Contents.json new file mode 100644 index 0000000000..7bb7e08542 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_gomsg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_gomsg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg.png b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg.png new file mode 100644 index 0000000000..10ac426fa4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg@2x.png new file mode 100644 index 0000000000..f38e7c80be Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GotoBubbleMessage.imageset/ic_gomsg@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/Contents.json new file mode 100644 index 0000000000..62cc567b3d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_add@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_input_add@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@1x.png new file mode 100644 index 0000000000..7495574b51 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@2x.png new file mode 100644 index 0000000000..a1332b155e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientAdd.imageset/ic_input_add@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/Contents.json new file mode 100644 index 0000000000..ba6311a888 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_close@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_input_close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@1x.png new file mode 100644 index 0000000000..74e40b4d82 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@2x.png new file mode 100644 index 0000000000..dbdaa2b6d3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientClose.imageset/ic_input_close@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/Contents.json new file mode 100644 index 0000000000..6a0e0eed8b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_gradchange@1x (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_gradchange@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@1x (1).png b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@1x (1).png new file mode 100644 index 0000000000..9bd7c040a2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@1x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@2x (1).png new file mode 100644 index 0000000000..e128dfc108 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientRotate.imageset/ic_gradchange@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/Contents.json new file mode 100644 index 0000000000..213a2af044 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_input_change@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_input_change@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@1x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@1x.png new file mode 100644 index 0000000000..5c3889281c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@2x.png new file mode 100644 index 0000000000..b1ce00e994 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GradientSwap.imageset/ic_input_change@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/Contents.json index e78c0cc6b8..32fa579a18 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/Contents.json @@ -2,11 +2,12 @@ "images" : [ { "idiom" : "universal", + "filename" : "ic_add.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "GroupInfoIconAddMember@2x.png", + "filename" : "ic_add@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/GroupInfoIconAddMember@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/GroupInfoIconAddMember@2x.png deleted file mode 100644 index 8a96306a36..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/GroupInfoIconAddMember@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add.png b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add.png new file mode 100644 index 0000000000..7495574b51 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add@2x.png b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add@2x.png new file mode 100644 index 0000000000..a1332b155e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_GroupInfoAddMember.imageset/ic_add@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/Contents.json new file mode 100644 index 0000000000..0033ed83b6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "soundOFF.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "soundOFF@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF.png b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF.png new file mode 100644 index 0000000000..ce48a67a48 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF@2x.png b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF@2x.png new file mode 100644 index 0000000000..e54c946fb1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOff.imageset/soundOFF@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/Contents.json new file mode 100644 index 0000000000..4b5136039d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "soundON.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "soundON@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON.png b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON.png new file mode 100644 index 0000000000..a311164a0e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON@2x.png b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON@2x.png new file mode 100644 index 0000000000..837cc8ed96 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InlineVideoSoundOn.imageset/soundON@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/Contents.json new file mode 100644 index 0000000000..74dd81960b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_invitelink.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_invitelink@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink.png b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink.png new file mode 100644 index 0000000000..6654a4a387 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink@2x.png b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink@2x.png new file mode 100644 index 0000000000..4b164c9912 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_InviteViaLink.imageset/ic_invitelink@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/Contents.json new file mode 100644 index 0000000000..ecdf5c0b9e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_like (2).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_like@2x (2).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like (2).png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like (2).png new file mode 100644 index 0000000000..1f98666b88 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like@2x (2).png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like@2x (2).png new file mode 100644 index 0000000000..18ff31d388 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButton.imageset/ic_like@2x (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/Contents.json new file mode 100644 index 0000000000..2a3585b546 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unlike (2).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unlike@2x (2).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike (2).png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike (2).png new file mode 100644 index 0000000000..3fa8cea694 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike@2x (2).png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike@2x (2).png new file mode 100644 index 0000000000..aebbe9c1bb Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageButtonUnlike.imageset/ic_unlike@2x (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/Contents.json new file mode 100644 index 0000000000..c0ef6c6486 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_likedmsg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_likedmsg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg.png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg.png new file mode 100644 index 0000000000..0b4ded84ff Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg@2x.png new file mode 100644 index 0000000000..d7e96159c9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInside.imageset/ic_likedmsg@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/Contents.json new file mode 100644 index 0000000000..26a6beadac --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_likemsg.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_likemsg@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg.png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg.png new file mode 100644 index 0000000000..6290a48e99 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg@2x.png new file mode 100644 index 0000000000..f4b63e6df3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Like_MessageInsideEmpty.imageset/ic_likemsg@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/Contents.json new file mode 100644 index 0000000000..00631d365d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LiveLocationTitlePin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/LiveLocationTitlePin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/LiveLocationTitlePin@2x.png new file mode 100644 index 0000000000..602e0beea6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LocationPin.imageset/LiveLocationTitlePin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/Contents.json new file mode 100644 index 0000000000..5126757a29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "logo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo.png b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo.png new file mode 100644 index 0000000000..3eae4d36f1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo@2x.png new file mode 100644 index 0000000000..89b9bc2122 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LoginCap.imageset/logo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount.png new file mode 100644 index 0000000000..b35decd854 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount@2x.png new file mode 100644 index 0000000000..33887c5f68 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/AddAccount@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/Contents.json new file mode 100644 index 0000000000..ccebe0b502 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_AddAccount.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "AddAccount.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "AddAccount@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Contents.json new file mode 100644 index 0000000000..912e780340 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Sim.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Sim@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim.png new file mode 100644 index 0000000000..cc82eaf379 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim@2x.png new file mode 100644 index 0000000000..f98a79deca Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ChangePhoneNumber.imageset/Sim@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache.png new file mode 100644 index 0000000000..3b095d1545 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache@2x.png new file mode 100644 index 0000000000..6531eff77b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Cache@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Contents.json new file mode 100644 index 0000000000..4e325ac462 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ClearCache.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Cache.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Cache@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask.png new file mode 100644 index 0000000000..721ca0d46f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask@2x.png new file mode 100644 index 0000000000..032ff10e30 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Ask@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Contents.json new file mode 100644 index 0000000000..2bb9f0b1b2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_ContactSupport.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Ask.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Ask@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Contents.json new file mode 100644 index 0000000000..64ec213de6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Passcode.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Passcode@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode.png new file mode 100644 index 0000000000..3c33bdc783 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode@2x.png b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode@2x.png new file mode 100644 index 0000000000..51fd8eb1cf Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_LogoutOption_SetPasscode.imageset/Passcode@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/Contents.json new file mode 100644 index 0000000000..4dcc439f34 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "MapLocationIcon_Active@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/MapLocationIcon_Active@2x.png b/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/MapLocationIcon_Active@2x.png new file mode 100644 index 0000000000..8411f17bc5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_MapLocate.imageset/MapLocationIcon_Active@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/Contents.json index 4876759544..1402929627 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_delete@1x.png", + "filename" : "ic_delete.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete.png new file mode 100644 index 0000000000..93efe762b0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@1x.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@1x.png deleted file mode 100644 index 51dd74507f..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@2x.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@2x.png index c732c164b7..073f88cee8 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelDelete.imageset/ic_delete@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/Contents.json index 5dce8d6a62..445cb8e64f 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_forward@1x.png", + "filename" : "ic_forward.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward.png new file mode 100644 index 0000000000..a9109b438d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@1x.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@1x.png deleted file mode 100644 index e70c63caf1..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@2x.png b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@2x.png index d709a3a32a..1552f66ab2 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_MessageActionPanelForward.imageset/ic_forward@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/Contents.json new file mode 100644 index 0000000000..4b4c1425df --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_music@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_music@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@1x.png b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@1x.png new file mode 100644 index 0000000000..1c5a2ecf5e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@2x.png b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@2x.png new file mode 100644 index 0000000000..f0c0f565d1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_MusicPlayerSmallAlbumArtPlaceholder.imageset/ic_music@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/Contents.json index c64efbbc5a..156658228a 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "back.png", + "filename" : "ic_back.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "back@2x.png", + "filename" : "ic_back@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back.png b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back.png deleted file mode 100644 index 3303d0a889..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back@2x.png deleted file mode 100644 index 1fa5e5b04e..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/back@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back.png b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back.png new file mode 100644 index 0000000000..76229a2cf5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back@2x.png new file mode 100644 index 0000000000..e4ea9b097e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NavigationBack.imageset/ic_back@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/Contents.json index 5b61edff85..2595db3b7d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_newchannel@1x.png", + "filename" : "ic_channel.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_newchannel@2x.png", + "filename" : "ic_channel@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel.png b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel.png new file mode 100644 index 0000000000..36e6cf8395 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel@2x.png new file mode 100644 index 0000000000..b757cf2444 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_channel@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@1x.png b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@1x.png deleted file mode 100644 index 483979d56e..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@2x.png deleted file mode 100644 index 35b895906b..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewChannel.imageset/ic_newchannel@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/Contents.json index b794d43728..32fa579a18 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "NewContact.png", + "filename" : "ic_add.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "NewContact@2x.png", + "filename" : "ic_add@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact.png b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact.png deleted file mode 100644 index 425638c39f..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact@2x.png deleted file mode 100644 index 82dce03ff5..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/NewContact@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add.png b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add.png new file mode 100644 index 0000000000..615c7c6a7b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add@2x.png new file mode 100644 index 0000000000..b5144bdca4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewContact.imageset/ic_add@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/Contents.json index 640c2621a8..152058a20c 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_newgroup@1x.png", + "filename" : "ic_group.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_newgroup@2x.png", + "filename" : "ic_group@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group.png b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group.png new file mode 100644 index 0000000000..60dec9ec12 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group@2x.png new file mode 100644 index 0000000000..230c153ae1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_group@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@1x.png b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@1x.png deleted file mode 100644 index da4d227e9e..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@2x.png deleted file mode 100644 index f0efca75d9..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewGroup.imageset/ic_newgroup@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/Contents.json index 8377bed453..2d0df3765e 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_newmsg@1x.png", + "filename" : "ic_create.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_newmsg@2x.png", + "filename" : "ic_create@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create.png b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create.png new file mode 100644 index 0000000000..bb1b300a35 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create@2x.png new file mode 100644 index 0000000000..ac08a7c3ec Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_create@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@1x.png b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@1x.png deleted file mode 100644 index eaee9ab31d..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@2x.png deleted file mode 100644 index 2c6b7b7c0d..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewMessage.imageset/ic_newmsg@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/Contents.json index e5c08584b0..1f0d15ef0d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_newsecret@1x.png", + "filename" : "ic_secretchat.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_newsecret@2x.png", + "filename" : "ic_secretchat@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@1x.png b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@1x.png deleted file mode 100644 index a7426057ef..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@2x.png deleted file mode 100644 index f2cdeb0950..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_newsecret@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat.png b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat.png new file mode 100644 index 0000000000..9900b01051 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat@2x.png new file mode 100644 index 0000000000..65ea4cc9a2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_NewSecretChat.imageset/ic_secretchat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/Contents.json new file mode 100644 index 0000000000..46d9275d88 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "login@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "login@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@1x.png new file mode 100644 index 0000000000..8bdfe9c6bc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@2x.png new file mode 100644 index 0000000000..0b55a7e221 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PasscodeLogin.imageset/login@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/Contents.json new file mode 100644 index 0000000000..db466b143d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "driver@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "driver@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@1x.png new file mode 100644 index 0000000000..3c97b3b032 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@2x.png new file mode 100644 index 0000000000..b5434d65e6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportDriverLicense.imageset/driver@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/Contents.json new file mode 100644 index 0000000000..15ce916766 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "idcard@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "idcard@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@1x.png new file mode 100644 index 0000000000..36434c8976 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@2x.png new file mode 100644 index 0000000000..d202cc522f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCard.imageset/idcard@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/Contents.json new file mode 100644 index 0000000000..14d45b47df --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "reverse@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "reverse@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@1x.png new file mode 100644 index 0000000000..f39c051c86 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@2x.png new file mode 100644 index 0000000000..820b0c012d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportIdCardReverse.imageset/reverse@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/Contents.json new file mode 100644 index 0000000000..3ed0db215e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "passport@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "passport@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@1x.png new file mode 100644 index 0000000000..67fe4f1668 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@2x.png new file mode 100644 index 0000000000..26c388f98c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportPassport.imageset/passport@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/Contents.json new file mode 100644 index 0000000000..465c13492d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "selfie@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "selfie@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@1x.png new file mode 100644 index 0000000000..beda565071 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@2x.png new file mode 100644 index 0000000000..1ad4c54717 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportSelfie.imageset/selfie@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/Contents.json new file mode 100644 index 0000000000..6f4c65581b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pass@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pass@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@1x.png new file mode 100644 index 0000000000..55e1f22dc7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@2x.png new file mode 100644 index 0000000000..bde6f69774 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PassportSettings.imageset/pass@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/Contents.json new file mode 100644 index 0000000000..1cf668737e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "playbar.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/playbar.png b/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/playbar.png new file mode 100644 index 0000000000..ad43bcdbcf Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Playbar.imageset/playbar.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@1x.png new file mode 100644 index 0000000000..b65515e775 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@2x.png new file mode 100644 index 0000000000..cd5277c316 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/1x@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/Contents.json new file mode 100644 index 0000000000..6485c85a1d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice1x.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "1x@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "1x@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@1x.png b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@1x.png new file mode 100644 index 0000000000..fb90a2795d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@2x.png new file mode 100644 index 0000000000..9ab9ad6ec3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/2x@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/Contents.json new file mode 100644 index 0000000000..8a2976f984 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PlayingVoice2x.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "2x@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "2x@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/Contents.json new file mode 100644 index 0000000000..941640e592 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_addoption.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_addoption@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption.png b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption.png new file mode 100644 index 0000000000..bdb1040905 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption@2x.png new file mode 100644 index 0000000000..9855ca0179 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollAddOption.imageset/ic_addoption@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/Contents.json new file mode 100644 index 0000000000..b25139f4c0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_deleteoption.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_deleteoption@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption.png b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption.png new file mode 100644 index 0000000000..0dd32ce6be Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption@2x.png new file mode 100644 index 0000000000..515a9459c6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollDeleteOption.imageset/ic_deleteoption@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/Contents.json new file mode 100644 index 0000000000..117af6d461 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "check.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "check@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check.png b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check.png new file mode 100644 index 0000000000..125566e384 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check@2x.png new file mode 100644 index 0000000000..db471df9e2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollSelected.imageset/check@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/Contents.json new file mode 100644 index 0000000000..a01572a69b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "failpoll.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "failpoll@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll.png b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll.png new file mode 100644 index 0000000000..5d55ecbd2b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll@2x.png new file mode 100644 index 0000000000..331e4ded0f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PollSelectedIncorrect.imageset/failpoll@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/Contents.json new file mode 100644 index 0000000000..e34d4481a7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_album.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_album@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album.png new file mode 100644 index 0000000000..5b46055f1f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album@2x.png new file mode 100644 index 0000000000..669f4242ec Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewCollage.imageset/ic_album@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/Contents.json new file mode 100644 index 0000000000..844e041b78 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_archive.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_archive@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive.png new file mode 100644 index 0000000000..e25005b660 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive@2x.png new file mode 100644 index 0000000000..386e9aba89 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderArchive.imageset/ic_archive@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/Contents.json new file mode 100644 index 0000000000..c37512f6ac --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "crop.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "crop@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop.png new file mode 100644 index 0000000000..da8063f63e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop@2x.png new file mode 100644 index 0000000000..baefabd91f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderCrop.imageset/crop@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/Contents.json new file mode 100644 index 0000000000..0c6af7ddf6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "deletemedia.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "deletemedia@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia.png new file mode 100644 index 0000000000..b2f844b644 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia@2x.png new file mode 100644 index 0000000000..ddd4992559 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDelete.imageset/deletemedia@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/Contents.json new file mode 100644 index 0000000000..af3ef90682 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_editor_paint.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_editor_paint@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint.png new file mode 100644 index 0000000000..6c3fbcbbf6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint@2x.png new file mode 100644 index 0000000000..1dd00d22f9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderDraw.imageset/ic_editor_paint@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/Contents.json new file mode 100644 index 0000000000..301f7d5d72 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_file.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_file@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file.png new file mode 100644 index 0000000000..e4e497d680 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file@2x.png new file mode 100644 index 0000000000..917cae037e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderFile.imageset/ic_file@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/Contents.json new file mode 100644 index 0000000000..f27dea6285 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_photo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_photo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo.png new file mode 100644 index 0000000000..93e2114285 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo@2x.png new file mode 100644 index 0000000000..71efd46d56 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PreviewSenderPhoto.imageset/ic_photo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/Contents.json new file mode 100644 index 0000000000..fe3d2cc35e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "sessions.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sessions@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions.png new file mode 100644 index 0000000000..69673bc8df Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions@2x.png new file mode 100644 index 0000000000..c6794f9150 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_ActiveSessions.imageset/sessions@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/Contents.json new file mode 100644 index 0000000000..08cd625062 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "blocked.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "blocked@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked.png new file mode 100644 index 0000000000..0bdc5e4d67 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked@2x.png new file mode 100644 index 0000000000..788001eb52 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_Blocked.imageset/blocked@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step.png new file mode 100644 index 0000000000..1bbd735c96 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step@2x.png b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step@2x.png new file mode 100644 index 0000000000..3f1c1ca800 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/2step@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/Contents.json new file mode 100644 index 0000000000..f116ef99fa --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_PrivacySettings_TwoStep.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "2step.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "2step@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/Contents.json index 72d402c1ee..56c95501ec 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/Contents.json @@ -2,11 +2,12 @@ "images" : [ { "idiom" : "universal", + "filename" : "ic_call.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "TabIconCalls@2x.png", + "filename" : "ic_call@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/TabIconCalls@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/TabIconCalls@2x.png deleted file mode 100644 index fc1df7e1b3..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/TabIconCalls@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call.png b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call.png new file mode 100644 index 0000000000..6a48bc086e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call@2x.png new file mode 100644 index 0000000000..e2edd1f39b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProfileCall.imageset/ic_call@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/Contents.json new file mode 100644 index 0000000000..7656773d6c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_addmember.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_addmember@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember.png new file mode 100644 index 0000000000..ac395cc67b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember@2x.png new file mode 100644 index 0000000000..35b9ed6b35 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_AddMember.imageset/ic_pf_addmember@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/Contents.json new file mode 100644 index 0000000000..393677dfcc --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_block.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_block@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block.png new file mode 100644 index 0000000000..44fa58f195 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block@2x.png new file mode 100644 index 0000000000..a8123d33e9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Block.imageset/ic_pf_block@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/Contents.json new file mode 100644 index 0000000000..32778f68ca --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_call.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_call@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call.png new file mode 100644 index 0000000000..288e27f37e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call@2x.png new file mode 100644 index 0000000000..643bb61e9a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Call.imageset/ic_pf_call@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/Contents.json new file mode 100644 index 0000000000..c07376e531 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_photo (2).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_photo@2x (2).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo (2).png b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo (2).png new file mode 100644 index 0000000000..aedcef315e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo@2x (2).png b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo@2x (2).png new file mode 100644 index 0000000000..4a9f4262a3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_EditPhoto.imageset/ic_photo@2x (2).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/Contents.json new file mode 100644 index 0000000000..3fbe735ef7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_leave.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_leave@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave.png new file mode 100644 index 0000000000..06d896e379 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave@2x.png new file mode 100644 index 0000000000..89d4eb174e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Leave.imageset/ic_pf_leave@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/Contents.json new file mode 100644 index 0000000000..5bb843342d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_message.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_message@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message.png new file mode 100644 index 0000000000..65bb206a20 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message@2x.png new file mode 100644 index 0000000000..601423df6a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Message.imageset/ic_pf_message@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/Contents.json new file mode 100644 index 0000000000..43f7409fd5 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_more.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_more@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more.png new file mode 100644 index 0000000000..906321c358 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more@2x.png new file mode 100644 index 0000000000..9feb95c619 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_More.imageset/ic_pf_more@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/Contents.json new file mode 100644 index 0000000000..ad37f4c6cd --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_mute.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_mute@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute.png new file mode 100644 index 0000000000..9e9b474a3b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute@2x.png new file mode 100644 index 0000000000..627a76da88 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Mute.imageset/ic_pf_mute@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/Contents.json new file mode 100644 index 0000000000..b0e98ac7bc --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_report.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_report@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report.png new file mode 100644 index 0000000000..02f47c362c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report@2x.png new file mode 100644 index 0000000000..d3154cb751 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Report.imageset/ic_pf_report@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/Contents.json new file mode 100644 index 0000000000..1b56ce796c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_search.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_search@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search.png new file mode 100644 index 0000000000..11f37f2f8d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search@2x.png new file mode 100644 index 0000000000..febc0d56f7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Search.imageset/ic_pf_search@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/Contents.json new file mode 100644 index 0000000000..67ca02a72e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_secretchat.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_secretchat@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat.png new file mode 100644 index 0000000000..a7012e9455 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat@2x.png new file mode 100644 index 0000000000..cdfbdb1266 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_SecretChat.imageset/ic_pf_secretchat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/Contents.json new file mode 100644 index 0000000000..f66029d80d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_share.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_share@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share.png new file mode 100644 index 0000000000..cb8ad6959f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share@2x.png new file mode 100644 index 0000000000..9d97f36be0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Share.imageset/ic_pf_share@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/Contents.json new file mode 100644 index 0000000000..1bfd54ee26 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_stats.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_stats@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats.png new file mode 100644 index 0000000000..737bc88bb9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats@2x.png new file mode 100644 index 0000000000..616e52900e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Stats.imageset/ic_pf_stats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/Contents.json new file mode 100644 index 0000000000..4bccb75444 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_unblock.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_unblock@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock.png new file mode 100644 index 0000000000..9bbce4baad Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock@2x.png new file mode 100644 index 0000000000..44bc66c6b3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unblock.imageset/ic_pf_unblock@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/Contents.json new file mode 100644 index 0000000000..20c312c072 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_pf_unmute.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_pf_unmute@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute.png new file mode 100644 index 0000000000..220646b091 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute@2x.png new file mode 100644 index 0000000000..bebc82ea11 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Profile_Unmute.imageset/ic_pf_unmute@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/Contents.json new file mode 100644 index 0000000000..e4794c892d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ProgressWindowCheck@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/ProgressWindowCheck@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/ProgressWindowCheck@2x.png new file mode 100644 index 0000000000..0549db4699 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProgressWindowCheck.imageset/ProgressWindowCheck@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/Contents.json new file mode 100644 index 0000000000..d569f3f746 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_proxy2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_proxy2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2.png new file mode 100644 index 0000000000..45f9e1bece Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2@2x.png new file mode 100644 index 0000000000..163c4e2017 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnable.imageset/ic_proxy2@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/Contents.json new file mode 100644 index 0000000000..de89fa0e72 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_proxy1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_proxy1@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1.png new file mode 100644 index 0000000000..a8fa802e1b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1@2x.png new file mode 100644 index 0000000000..0f1239ed7c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyEnabled.imageset/ic_proxy1@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/Contents.json new file mode 100644 index 0000000000..2fc23be868 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_proxy3.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_proxy3@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3.png new file mode 100644 index 0000000000..f47b2d98e9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3@2x.png new file mode 100644 index 0000000000..1a2c12a3ab Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ProxyState.imageset/ic_proxy3@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/Contents.json new file mode 100644 index 0000000000..c60505c37b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_lamp.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_lamp@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp.png b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp.png new file mode 100644 index 0000000000..63270cb835 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp@2x.png b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp@2x.png new file mode 100644 index 0000000000..37c61eaa93 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_QuizExplanation.imageset/ic_lamp@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/Contents.json new file mode 100644 index 0000000000..3f20c1879d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "newmedia@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "newmedia@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@1x.png new file mode 100644 index 0000000000..08030152de Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@2x.png new file mode 100644 index 0000000000..cad63a848d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ReplaceMessageMedia.imageset/newmedia@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/Contents.json new file mode 100644 index 0000000000..461e29e54e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_burger (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_burger@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger (1).png b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger (1).png new file mode 100644 index 0000000000..f048be27b8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger@2x (1).png new file mode 100644 index 0000000000..80a00979a8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Resort.imageset/ic_burger@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/Contents.json new file mode 100644 index 0000000000..73d7b507ad --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_savemessage@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_savemessage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@1x.png new file mode 100644 index 0000000000..4d2aea58cb Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@2x.png new file mode 100644 index 0000000000..81b9747ae5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SaveEditedMessage.imageset/ic_savemessage@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/Contents.json new file mode 100644 index 0000000000..a13dd3987a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_saved@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/ic_saved@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/ic_saved@2x.png new file mode 100644 index 0000000000..de460c24ee Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SavedMessages.imageset/ic_saved@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/Contents.json index 64d50fcbc5..f3b87a4e05 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_up@1x.png", + "filename" : "ic_up.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up.png b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up.png new file mode 100644 index 0000000000..98cf7f692d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@1x.png deleted file mode 100644 index 773d6dfce5..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@2x.png index d96b2f1dfe..d08fb4fd7d 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SearchArrow.imageset/ic_up@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/Contents.json new file mode 100644 index 0000000000..7ba8c86a28 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "articles@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "articles@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@1x.png new file mode 100644 index 0000000000..df25970532 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@2x.png new file mode 100644 index 0000000000..0d407780b4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchArticles.imageset/articles@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/Contents.json index a38faa7a64..11f30f94df 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_search@1x.png", + "filename" : "ic_search.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search.png b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search.png new file mode 100644 index 0000000000..b0cbd8d4cf Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@1x.png deleted file mode 100644 index 094757dd61..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@2x.png index b6a9957ba6..53f6ff1c7f 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SearchChatMessages.imageset/ic_search@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/Contents.json new file mode 100644 index 0000000000..3cbfca8778 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "saved@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "saved@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@1x.png new file mode 100644 index 0000000000..339f96aa7a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@2x.png new file mode 100644 index 0000000000..231b3dd946 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SearchSaved.imageset/saved@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/Contents.json new file mode 100644 index 0000000000..9c270bc1e7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_security@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_security@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@1x.png new file mode 100644 index 0000000000..93c52feaa8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@2x.png new file mode 100644 index 0000000000..892abd456c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SecureIdAuth.imageset/ic_security@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/Contents.json new file mode 100644 index 0000000000..3575172b26 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_fr@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_fr@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@1x.png new file mode 100644 index 0000000000..d9f1533b1a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@2x.png new file mode 100644 index 0000000000..a4e7c6f034 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SecureIdForgotPassword.imageset/ic_fr@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/Contents.json index 876af2c1cf..3088d79699 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/Contents.json @@ -7,7 +7,7 @@ }, { "idiom" : "universal", - "filename" : "ic_send@2x.png", + "filename" : "ic_send@2x (1).png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/ic_send@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/ic_send@2x (1).png similarity index 100% rename from Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/ic_send@2x.png rename to Telegram-Mac/Assets.xcassets/Icon_SendMessage.imageset/ic_send@2x (1).png diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/Contents.json index ed3069f4af..aec0962898 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_askaq@1x.png", + "filename" : "ic_ask@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_askaq@2x.png", + "filename" : "ic_ask@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@1x.png new file mode 100644 index 0000000000..4d70ab3b09 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@2x.png new file mode 100644 index 0000000000..ee959d17e5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_ask@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@1x.png deleted file mode 100644 index 4065953569..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@2x.png deleted file mode 100644 index 8e30e4a039..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsAskQuestion.imageset/ic_askaq@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@1x.png index 24486ac758..5ce9a73978 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@1x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@2x.png index 857fd457d7..899bf0ebcb 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsFaq.imageset/ic_faq@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/Contents.json new file mode 100644 index 0000000000..29b5593836 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_filters.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_filters@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters.png new file mode 100644 index 0000000000..2e099b795b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters@2x.png new file mode 100644 index 0000000000..47a228e9d7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsFilters.imageset/ic_filters@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@1x.png index 51c9a9cd05..017043a430 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@1x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@2x.png index 4824e49e8c..a583fd4daf 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsGeneral.imageset/ic_general@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/Contents.json index 2b59027df0..dd141fc913 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_language@1x.png", + "filename" : "ic_lang@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_language@2x.png", + "filename" : "ic_lang@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@1x.png new file mode 100644 index 0000000000..362134a53f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@2x.png new file mode 100644 index 0000000000..44bfb20085 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_lang@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@1x.png deleted file mode 100644 index e9d0c218d2..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@2x.png deleted file mode 100644 index 510479d003..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsLanguage.imageset/ic_language@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@1x.png index bb3333c808..ad7c762007 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@1x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@2x.png index 41884afbec..9f92da3940 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsNotifications.imageset/ic_notifications@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/Contents.json new file mode 100644 index 0000000000..01c2f0b13c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_passport@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_passport@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@1x.png new file mode 100644 index 0000000000..dbe59e9de2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@2x.png new file mode 100644 index 0000000000..42a78722e7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsPassport.imageset/ic_passport@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/Contents.json new file mode 100644 index 0000000000..049d903b6f --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_prof.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_prof@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof.png new file mode 100644 index 0000000000..5201dbd9fb Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof@2x.png new file mode 100644 index 0000000000..07fae246ce Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsProfile.imageset/ic_prof@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/Contents.json new file mode 100644 index 0000000000..06d896bc14 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_proxy@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_proxy@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@1x.png new file mode 100644 index 0000000000..66f04f2027 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@2x.png new file mode 100644 index 0000000000..ef30339064 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsProxy.imageset/ic_proxy@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/Contents.json index 9c270bc1e7..fc32dbebed 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_security@1x.png", + "filename" : "ic_privacy@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_security@2x.png", + "filename" : "ic_privacy@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@1x.png new file mode 100644 index 0000000000..95b72d77ed Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@2x.png new file mode 100644 index 0000000000..37f2e44df7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_privacy@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@1x.png deleted file mode 100644 index da61970c43..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@2x.png deleted file mode 100644 index e5b8f534a4..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsSecurity.imageset/ic_security@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@1x.png index 946da83760..7ac2eeec66 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@1x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@2x.png index 655b51c80a..cd444da4e5 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_SettingsStickers.imageset/ic_stickers@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/Contents.json index 646741b1b1..af11311f7d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_storage@1x.png", + "filename" : "ic_data@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_storage@2x.png", + "filename" : "ic_data@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@1x.png new file mode 100644 index 0000000000..3b44a3f146 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@2x.png new file mode 100644 index 0000000000..75882f04b8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_data@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@1x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@1x.png deleted file mode 100644 index 261e0e1e84..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@2x.png deleted file mode 100644 index 78aeba3a22..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_SettingsStorage.imageset/ic_storage@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/Contents.json new file mode 100644 index 0000000000..e658e397c0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "update (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "update@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update (1).png b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update (1).png new file mode 100644 index 0000000000..c4abb15cd2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update@2x (1).png new file mode 100644 index 0000000000..2f5efe3e13 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsUpdate.imageset/update@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/Contents.json new file mode 100644 index 0000000000..26bf697314 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_wallet.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_wallet@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet.png new file mode 100644 index 0000000000..f2d4d738e9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet@2x.png b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet@2x.png new file mode 100644 index 0000000000..641bc754ad Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_SettingsWallet.imageset/ic_wallet@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/Contents.json index 2040e083a9..80acc0806f 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/Contents.json @@ -2,11 +2,12 @@ "images" : [ { "idiom" : "universal", + "filename" : "ic_share.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ShareExternalIcon@2x.png", + "filename" : "ic_share@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ShareExternalIcon@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ShareExternalIcon@2x.png deleted file mode 100644 index db4f34d225..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ShareExternalIcon@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share.png b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share.png new file mode 100644 index 0000000000..b6740770a9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share@2x.png new file mode 100644 index 0000000000..a678914461 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ShareExternal.imageset/ic_share@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/Contents.json index 899e0ebee9..80acc0806f 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_share@1x.png", + "filename" : "ic_share.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share.png b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share.png new file mode 100644 index 0000000000..b6740770a9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@1x.png b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@1x.png deleted file mode 100644 index dcbe6491e5..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@2x.png index 47e14f1567..a678914461 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_ShareStickerPack.imageset/ic_share@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/Contents.json new file mode 100644 index 0000000000..58d6ea74ef --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_allchats.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_allchats@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats.png new file mode 100644 index 0000000000..d5731c2a46 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats@2x.png new file mode 100644 index 0000000000..4f0ccf241c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_AllChats.imageset/ic_allchats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/Contents.json new file mode 100644 index 0000000000..e1b68ea24c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_animal.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_animal@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal.png new file mode 100644 index 0000000000..f8e3a75132 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal@2x.png new file mode 100644 index 0000000000..b17d52149b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Animal.imageset/ic_animal@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/Contents.json new file mode 100644 index 0000000000..274eccd216 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_book.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_book@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book.png new file mode 100644 index 0000000000..497ae2314f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book@2x.png new file mode 100644 index 0000000000..6c1a276be7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Book.imageset/ic_book@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/Contents.json new file mode 100644 index 0000000000..501805ac2a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_bot.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_bot@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot.png new file mode 100644 index 0000000000..f96a4d9eb0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot@2x.png new file mode 100644 index 0000000000..e66764359e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Bot.imageset/ic_bot@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/Contents.json new file mode 100644 index 0000000000..2595db3b7d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_channel.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_channel@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel.png new file mode 100644 index 0000000000..98dfbe8442 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel@2x.png new file mode 100644 index 0000000000..2363b1e987 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Channel.imageset/ic_channel@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/Contents.json new file mode 100644 index 0000000000..c96a6bdf24 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_coin.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_coin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin.png new file mode 100644 index 0000000000..48d4c4155d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin@2x.png new file mode 100644 index 0000000000..d56ff466b9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Coin.imageset/ic_coin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/Contents.json new file mode 100644 index 0000000000..f9a5641ffe --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_flash.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_flash@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash.png new file mode 100644 index 0000000000..19cc5358c2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash@2x.png new file mode 100644 index 0000000000..5c2989c4d8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Flash.imageset/ic_flash@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/Contents.json new file mode 100644 index 0000000000..14ccd7f4fb --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_folder.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_folder@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder.png new file mode 100644 index 0000000000..bf6ee9f11b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder@2x.png new file mode 100644 index 0000000000..70c602b8a3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Folder.imageset/ic_folder@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/Contents.json new file mode 100644 index 0000000000..9556655b27 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_gamepad.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_gamepad@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad.png new file mode 100644 index 0000000000..cf8ddbdfd2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad@2x.png new file mode 100644 index 0000000000..904b10ffb6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Game.imageset/ic_gamepad@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/Contents.json new file mode 100644 index 0000000000..152058a20c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_group.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_group@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group.png new file mode 100644 index 0000000000..62afae7802 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group@2x.png new file mode 100644 index 0000000000..bdafd3df4a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Group.imageset/ic_group@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/Contents.json new file mode 100644 index 0000000000..6a813c7971 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_home.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_home@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home.png new file mode 100644 index 0000000000..d60e3c8ff0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home@2x.png new file mode 100644 index 0000000000..a70943b721 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Home.imageset/ic_home@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/Contents.json new file mode 100644 index 0000000000..c60505c37b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_lamp.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_lamp@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp.png new file mode 100644 index 0000000000..d8662163a7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp@2x.png new file mode 100644 index 0000000000..985bf56e57 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lamp.imageset/ic_lamp@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/Contents.json new file mode 100644 index 0000000000..2e8a5cea30 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_like.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_like@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like.png new file mode 100644 index 0000000000..b87dd5168a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like@2x.png new file mode 100644 index 0000000000..7ad91954f3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Like.imageset/ic_like@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/Contents.json new file mode 100644 index 0000000000..d09de3c8f2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_lock.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_lock@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock.png new file mode 100644 index 0000000000..a15099cda7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock@2x.png new file mode 100644 index 0000000000..ef4c554b32 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Lock.imageset/ic_lock@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/Contents.json new file mode 100644 index 0000000000..c860da1d72 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_love.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_love@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love.png new file mode 100644 index 0000000000..5457d04fb2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love@2x.png new file mode 100644 index 0000000000..73250b3c43 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Love.imageset/ic_love@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/Contents.json new file mode 100644 index 0000000000..c9c9e7dc18 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_mask.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_mask@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask.png new file mode 100644 index 0000000000..838276322a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask@2x.png new file mode 100644 index 0000000000..77f7e73b60 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Mask.imageset/ic_mask@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/Contents.json new file mode 100644 index 0000000000..b52fb82ad9 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_math.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_math@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math.png new file mode 100644 index 0000000000..93920e7b88 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math@2x.png new file mode 100644 index 0000000000..9b1e7a095a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Math.imageset/ic_math@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/Contents.json new file mode 100644 index 0000000000..53454b7c78 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_nusic.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_nusic@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic.png new file mode 100644 index 0000000000..e6c4ebb26f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic@2x.png new file mode 100644 index 0000000000..f5053ea01b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Music.imageset/ic_nusic@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/Contents.json new file mode 100644 index 0000000000..70063b6f29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_muted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_muted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted.png new file mode 100644 index 0000000000..fbd6c02ac5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted@2x.png new file mode 100644 index 0000000000..7e63de96bc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Muted.imageset/ic_muted@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/Contents.json new file mode 100644 index 0000000000..b3f33de944 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_paint.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_paint@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint.png new file mode 100644 index 0000000000..e4e5e846d9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint@2x.png new file mode 100644 index 0000000000..eeaf621fbe Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Paint.imageset/ic_paint@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/Contents.json new file mode 100644 index 0000000000..b773bac788 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_personal.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_personal@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal.png new file mode 100644 index 0000000000..8b48dcfba1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal@2x.png new file mode 100644 index 0000000000..cbb88ce31a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Personal.imageset/ic_personal@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/Contents.json new file mode 100644 index 0000000000..68e233fd83 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_plane.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_plane@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane.png new file mode 100644 index 0000000000..20e6ef5b80 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane@2x.png new file mode 100644 index 0000000000..6d3fd0d6fd Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Plane.imageset/ic_plane@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/Contents.json new file mode 100644 index 0000000000..39a49c5b0b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_read.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_read@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read.png new file mode 100644 index 0000000000..e6117bbd1a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read@2x.png new file mode 100644 index 0000000000..caa5bec8a6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Read.imageset/ic_read@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/Contents.json new file mode 100644 index 0000000000..60f2dd0cf5 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_sport.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_sport@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport.png new file mode 100644 index 0000000000..c9aade47af Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport@2x.png new file mode 100644 index 0000000000..e11a2ca6d1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Sport.imageset/ic_sport@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/Contents.json new file mode 100644 index 0000000000..ed649ccf18 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_star.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_star@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star.png new file mode 100644 index 0000000000..5484dceaa0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star@2x.png new file mode 100644 index 0000000000..993885e6ae Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Star.imageset/ic_star@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/Contents.json new file mode 100644 index 0000000000..03d8bea9d4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_student.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_student@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student.png new file mode 100644 index 0000000000..aa1515bb54 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student@2x.png new file mode 100644 index 0000000000..94231d8a64 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Student.imageset/ic_student@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/Contents.json new file mode 100644 index 0000000000..a62f7b3bcb --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_telegram.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_telegram@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram.png new file mode 100644 index 0000000000..5be8517c6c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram@2x.png new file mode 100644 index 0000000000..12b4fb500b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Telegram.imageset/ic_telegram@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/Contents.json new file mode 100644 index 0000000000..3d4d31a176 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unmuted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unmuted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted.png new file mode 100644 index 0000000000..926bc050c6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted@2x.png new file mode 100644 index 0000000000..ec8a9928fc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unmuted.imageset/ic_unmuted@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/Contents.json new file mode 100644 index 0000000000..0fc3beb1c2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_unread.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_unread@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread.png new file mode 100644 index 0000000000..74330f46ef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread@2x.png new file mode 100644 index 0000000000..09e4b8b279 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Unread.imageset/ic_unread@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/Contents.json new file mode 100644 index 0000000000..2aed4f0c13 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_flow.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_flow@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow.png new file mode 100644 index 0000000000..6adea7e5ef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow@2x.png new file mode 100644 index 0000000000..b4a4cc5ce9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Virus.imageset/ic_flow@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/Contents.json new file mode 100644 index 0000000000..5e7e083c62 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_wine.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_wine@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine.png new file mode 100644 index 0000000000..c03638271b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine@2x.png new file mode 100644 index 0000000000..00e62417e9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Wine.imageset/ic_wine@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/Contents.json new file mode 100644 index 0000000000..591bbb841e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_work.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_work@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work.png new file mode 100644 index 0000000000..5026f0dd7d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work@2x.png b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work@2x.png new file mode 100644 index 0000000000..2b75a42837 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Sidebar_Work.imageset/ic_work@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Contents.json new file mode 100644 index 0000000000..bb5459de83 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Oval (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Oval@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval (1).png b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval (1).png new file mode 100644 index 0000000000..43406df4e1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval@2x (1).png new file mode 100644 index 0000000000..263b4d1b90 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_Slider.imageset/Oval@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Contents.json new file mode 100644 index 0000000000..f5fbbe94c3 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Download.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Download@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download.png b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download.png new file mode 100644 index 0000000000..ccb3725969 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download@2x.png b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download@2x.png new file mode 100644 index 0000000000..b090cb549f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_StreamingDownload.imageset/Download@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/Contents.json index a48b454e28..8f7c7eb224 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_chats_1@1x.png", + "filename" : "ic_chats.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_chats_1@2x.png", + "filename" : "ic_chats@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats.png new file mode 100644 index 0000000000..9370cf5135 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats@2x.png new file mode 100644 index 0000000000..2715b079b0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@1x.png deleted file mode 100644 index 0ca85775b4..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@2x.png deleted file mode 100644 index e3829970c1..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabChatList.imageset/ic_chats_1@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/Contents.json index ff26a57346..8f7c7eb224 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_chats_2@1x.png", + "filename" : "ic_chats.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_chats_2@2x.png", + "filename" : "ic_chats@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats.png new file mode 100644 index 0000000000..9370cf5135 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats@2x.png new file mode 100644 index 0000000000..2715b079b0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@1x.png deleted file mode 100644 index 99cf338aaf..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@2x.png deleted file mode 100644 index 6a177b96a2..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabChatList_Highlighted.imageset/ic_chats_2@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/Contents.json index 2d057742d9..6d3745d5ec 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_contacts_1@1x.png", + "filename" : "ic_contacts.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_contacts_1@2x.png", + "filename" : "ic_contacts@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts.png new file mode 100644 index 0000000000..37b6eb9c95 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts@2x.png new file mode 100644 index 0000000000..65e44c6a3e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@1x.png deleted file mode 100644 index 99e010ace8..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@2x.png deleted file mode 100644 index 0fd0b01ce0..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabContacts.imageset/ic_contacts_1@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/Contents.json deleted file mode 100644 index 25e4e58776..0000000000 --- a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_contacts_2@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ic_contacts_2@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@1x.png deleted file mode 100644 index eda01248f0..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@2x.png deleted file mode 100644 index dc03a4ef21..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabContacts_Highlighted.imageset/ic_contacts_2@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/Contents.json index b24728a360..45a0013e5d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_calls_1@1x.png", + "filename" : "ic_calls.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_calls_1@2x.png", + "filename" : "ic_calls@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls.png new file mode 100644 index 0000000000..beaa6d49a7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls@2x.png new file mode 100644 index 0000000000..4baf298c68 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@1x.png deleted file mode 100644 index 83781c5aab..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@2x.png deleted file mode 100644 index 95810c6a0b..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCalls.imageset/ic_calls_1@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/Contents.json deleted file mode 100644 index cda2d9a025..0000000000 --- a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_calls_2@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ic_calls_2@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@1x.png deleted file mode 100644 index 48c80fef53..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@2x.png deleted file mode 100644 index 819b882a6a..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabRecentCallsHighlighted.imageset/ic_calls_2@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/Contents.json index 5f66cf2b16..25de093c56 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_settings_1@1x.png", + "filename" : "ic_settings.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "ic_settings_1@2x.png", + "filename" : "ic_settings@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings.png new file mode 100644 index 0000000000..2dffab7701 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings@2x.png new file mode 100644 index 0000000000..70f1196940 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@1x.png deleted file mode 100644 index cd0e99956f..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@2x.png deleted file mode 100644 index 1acafe38c4..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabSettings.imageset/ic_settings_1@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/Contents.json deleted file mode 100644 index 3f0eb96621..0000000000 --- a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_settings_2@1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "ic_settings_2@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@1x.png deleted file mode 100644 index 5a36e0cf80..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@2x.png deleted file mode 100644 index 180d7240ae..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_TabSettings_Highlighted.imageset/ic_settings_2@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/Contents.json new file mode 100644 index 0000000000..17302c16da --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "themebubble@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/themebubble@2x.png b/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/themebubble@2x.png new file mode 100644 index 0000000000..17c90b1ce7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_ThemeBubble.imageset/themebubble@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/Contents.json new file mode 100644 index 0000000000..1f6801c0a0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logo@1x-1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "logo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@1x-1.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@1x-1.png new file mode 100644 index 0000000000..36dd72d033 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@1x-1.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@2x.png new file mode 100644 index 0000000000..d9ed1f0f87 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBarBackgroundIcon.imageset/logo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/Contents.json new file mode 100644 index 0000000000..6d782f5fb0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "document@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/document@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/document@2x.png new file mode 100644 index 0000000000..7df2920e28 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachFile.imageset/document@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/Contents.json new file mode 100644 index 0000000000..8017d3fb31 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "location@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/location@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/location@2x.png new file mode 100644 index 0000000000..2302a706ef Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachLocation.imageset/location@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/Contents.json new file mode 100644 index 0000000000..a23e5d8efb --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "photo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/photo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/photo@2x.png new file mode 100644 index 0000000000..9862baf685 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPhotoOrVideo.imageset/photo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/Contents.json new file mode 100644 index 0000000000..cc7769e2a4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/camera@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/camera@2x.png new file mode 100644 index 0000000000..27847be5c4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_AttachPicture.imageset/camera@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/Contents.json new file mode 100644 index 0000000000..6e91e28d90 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "call@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/call@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/call@2x.png new file mode 100644 index 0000000000..616978640d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Call.imageset/call@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/Contents.json new file mode 100644 index 0000000000..5d34c268a2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "attach@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/attach@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/attach@2x.png new file mode 100644 index 0000000000..13f1d3018f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatAttach.imageset/attach@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/Contents.json new file mode 100644 index 0000000000..5fead88de4 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "more@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/more@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/more@2x.png new file mode 100644 index 0000000000..b1c599688a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ChatMore.imageset/more@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/Contents.json new file mode 100644 index 0000000000..6f54ddaa6b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "newchat@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/newchat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/newchat@2x.png new file mode 100644 index 0000000000..3baa4dd040 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Compose.imageset/newchat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/Contents.json new file mode 100644 index 0000000000..f83025925d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "channel@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/channel@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/channel@2x.png new file mode 100644 index 0000000000..8de1a717db Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeChannel.imageset/channel@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/Contents.json new file mode 100644 index 0000000000..bf197f337e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "group@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/group@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/group@2x.png new file mode 100644 index 0000000000..33e19fb325 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeGroup.imageset/group@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/Contents.json new file mode 100644 index 0000000000..e4a80cf363 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "secretchat@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/secretchat@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/secretchat@2x.png new file mode 100644 index 0000000000..f4d08ab4af Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ComposeSecretChat.imageset/secretchat@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/Contents.json new file mode 100644 index 0000000000..8bb2eb39fc --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "emoji@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/emoji@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/emoji@2x.png new file mode 100644 index 0000000000..c1d85e3715 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Emoji.imageset/emoji@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/Contents.json new file mode 100644 index 0000000000..00fe980fda --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "fav@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/fav@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/fav@2x.png new file mode 100644 index 0000000000..7ab60da256 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Fav.imageset/fav@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/Contents.json new file mode 100644 index 0000000000..2aa77a3a95 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "bigtext@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/bigtext@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/bigtext@2x.png new file mode 100644 index 0000000000..2f416d11cd Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_BigText.imageset/bigtext@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/Contents.json new file mode 100644 index 0000000000..b571989405 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "georgia@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/georgia@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/georgia@2x.png new file mode 100644 index 0000000000..74c4b83e83 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_Georgia.imageset/georgia@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/Contents.json new file mode 100644 index 0000000000..69d486a116 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sanfrancisco@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/sanfrancisco@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/sanfrancisco@2x.png new file mode 100644 index 0000000000..b841972cbc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Font_SF.imageset/sanfrancisco@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/Contents.json new file mode 100644 index 0000000000..c4ac0f64d7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "safari@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/safari@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/safari@2x.png new file mode 100644 index 0000000000..9d1080f487 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_Safari.imageset/safari@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/Contents.json new file mode 100644 index 0000000000..4ec4c03f65 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "smalltext@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/smalltext@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/smalltext@2x.png new file mode 100644 index 0000000000..4832b37da4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_IV_SmallText.imageset/smalltext@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/Contents.json new file mode 100644 index 0000000000..fd18960fd6 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "info@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/info@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/info@2x.png new file mode 100644 index 0000000000..804618d66b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Info.imageset/info@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/Contents.json new file mode 100644 index 0000000000..08984b7ad7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "delete@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/delete@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/delete@2x (1).png new file mode 100644 index 0000000000..7fe4b6bf5c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesDelete.imageset/delete@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/Contents.json new file mode 100644 index 0000000000..4e1174120e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "share@2x (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/share@2x (1).png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/share@2x (1).png new file mode 100644 index 0000000000..e5569b356f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_MessagesForward.imageset/share@2x (1).png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/Contents.json new file mode 100644 index 0000000000..b763e76190 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "plus@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/plus@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/plus@2x.png new file mode 100644 index 0000000000..83507f7480 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Plus.imageset/plus@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/Contents.json new file mode 100644 index 0000000000..c2602b4da8 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "search@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/search@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/search@2x.png new file mode 100644 index 0000000000..bb586c73d1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Search.imageset/search@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/Contents.json new file mode 100644 index 0000000000..a0281e9bc1 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "share@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/share@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/share@2x.png new file mode 100644 index 0000000000..d690a77dc6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Share.imageset/share@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/Contents.json new file mode 100644 index 0000000000..9b91529fa0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "stickers@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/stickers@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/stickers@2x.png new file mode 100644 index 0000000000..31eeab4298 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_Stickers.imageset/stickers@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/Contents.json new file mode 100644 index 0000000000..b026edcfbf --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "zoomin@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/zoomin@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/zoomin@2x.png new file mode 100644 index 0000000000..b16b2ea1fe Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomIn.imageset/zoomin@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/Contents.json new file mode 100644 index 0000000000..29bd126ef8 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "zoomout@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/zoomout@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/zoomout@2x.png new file mode 100644 index 0000000000..90358dfbfd Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchBar_ZoomOut.imageset/zoomout@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/Contents.json new file mode 100644 index 0000000000..1666d66321 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "touchid@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/touchid@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/touchid@2x.png new file mode 100644 index 0000000000..d4bab798f1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TouchId.imageset/touchid@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/Contents.json new file mode 100644 index 0000000000..e6c7cd150d --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "macpass@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "macpass@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@1x.png b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@1x.png new file mode 100644 index 0000000000..762a91bcff Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@1x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@2x.png b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@2x.png new file mode 100644 index 0000000000..3fd63c0f81 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_TwoStepVerification_Create.imageset/macpass@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/Contents.json index 9b574c2dc4..f83b64c06d 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "UsernameCheck.png", + "filename" : "ic_check.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "UsernameCheck@2x.png", + "filename" : "ic_check@2x.png", "scale" : "2x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck.png b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck.png deleted file mode 100644 index 1f39ab8984..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck@2x.png b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck@2x.png deleted file mode 100644 index c1e1507701..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/UsernameCheck@2x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check.png b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check.png new file mode 100644 index 0000000000..733a6cf1c3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check@2x.png b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check@2x.png new file mode 100644 index 0000000000..785e20637f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_UsernameAvailability.imageset/ic_check@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Contents.json new file mode 100644 index 0000000000..f6cf5d6160 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Ic_verifychats.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Ic_verifychats@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats.png b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats.png new file mode 100644 index 0000000000..8330710975 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats@2x.png new file mode 100644 index 0000000000..1f5b2a180b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VerifyDialog.imageset/Ic_verifychats@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/Contents.json new file mode 100644 index 0000000000..1f570edf8a --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_download1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_download1@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1.png b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1.png new file mode 100644 index 0000000000..56b3ee49d8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1@2x.png new file mode 100644 index 0000000000..44517e91cd Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoCompactFetching.imageset/ic_download1@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close.png new file mode 100644 index 0000000000..1bec670b81 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close@2x.png new file mode 100644 index 0000000000..8aa195cc7b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Close@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Contents.json new file mode 100644 index 0000000000..ae2baaff29 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Close.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Close.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/Contents.json new file mode 100644 index 0000000000..0d1b3290d2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "fullscreen.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "fullscreen@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen.png new file mode 100644 index 0000000000..3c705ab8c8 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen@2x.png new file mode 100644 index 0000000000..19705233d0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_EnterFullScreen.imageset/fullscreen@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/Contents.json new file mode 100644 index 0000000000..cc0682599e --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "smallscreen.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "smallscreen@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen.png new file mode 100644 index 0000000000..5bee6ed1b9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen@2x.png new file mode 100644 index 0000000000..dbb0cb2abc Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_ExitFullScreen.imageset/smallscreen@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/Contents.json new file mode 100644 index 0000000000..483588c5ab --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pip.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pip@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip.png new file mode 100644 index 0000000000..22acb86b9e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip@2x.png new file mode 100644 index 0000000000..7d6d0eb107 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPIN.imageset/pip@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/Contents.json new file mode 100644 index 0000000000..f9f5d5b974 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pipout.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pipout@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout.png new file mode 100644 index 0000000000..83dc2835e7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout@2x.png new file mode 100644 index 0000000000..132684849b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_PIPOUT.imageset/pipout@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/Contents.json new file mode 100644 index 0000000000..7a67263af2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pause.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pause@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause.png new file mode 100644 index 0000000000..477e6133d1 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause@2x.png new file mode 100644 index 0000000000..1681f6f30e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Pause.imageset/pause@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/Contents.json new file mode 100644 index 0000000000..2f2216ebeb --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "play.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "play@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play.png new file mode 100644 index 0000000000..c532bb8905 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play@2x.png new file mode 100644 index 0000000000..140bc362fd Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Play.imageset/play@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left.png new file mode 100644 index 0000000000..b722d5a78a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left@2x.png new file mode 100644 index 0000000000..bc96d9408f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/15left@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/Contents.json new file mode 100644 index 0000000000..d62aa2ad2b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Backward.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "15left.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "15left@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right.png new file mode 100644 index 0000000000..069560364f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right@2x.png new file mode 100644 index 0000000000..f59827cee0 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/15right@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/Contents.json new file mode 100644 index 0000000000..bf6658572c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Rewind15Forward.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "15right.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "15right@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/Contents.json new file mode 100644 index 0000000000..a40329a525 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "volume.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "volume@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume.png new file mode 100644 index 0000000000..8f91cadcbe Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume@2x.png new file mode 100644 index 0000000000..256c8f6da2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_Volume.imageset/volume@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/Contents.json new file mode 100644 index 0000000000..470f703aa0 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "VolumeOff.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "VolumeOff@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff.png new file mode 100644 index 0000000000..4564d4ad9c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff@2x.png b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff@2x.png new file mode 100644 index 0000000000..9a9d17d8f2 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_VideoPlayer_VolumeOff.imageset/VolumeOff@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/Contents.json new file mode 100644 index 0000000000..81735d6821 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_close.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_close@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close.png b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close.png new file mode 100644 index 0000000000..95a6980281 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close@2x.png new file mode 100644 index 0000000000..7871880011 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletClose.imageset/ic_close@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Contents.json new file mode 100644 index 0000000000..8b08b1e251 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Hide.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Hide@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide.png b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide.png new file mode 100644 index 0000000000..94b57217d7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide@2x.png new file mode 100644 index 0000000000..5a345e2cee Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeHidden.imageset/Hide@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/Contents.json new file mode 100644 index 0000000000..2ffbdd46fc --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "View.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "View@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View.png b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View.png new file mode 100644 index 0000000000..5110808954 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View@2x.png new file mode 100644 index 0000000000..4f270a67d3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletPasscodeVisible.imageset/View@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/Contents.json new file mode 100644 index 0000000000..b9dcf0b68c --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_qr.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_qr@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr.png b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr.png new file mode 100644 index 0000000000..bfd8f23121 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr@2x.png new file mode 100644 index 0000000000..3ff359ff23 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletQR.imageset/ic_qr@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/Contents.json new file mode 100644 index 0000000000..d69346dd07 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_receive.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_receive@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive.png b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive.png new file mode 100644 index 0000000000..a5d24bc973 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive@2x.png new file mode 100644 index 0000000000..7143ca6949 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletReceive.imageset/ic_receive@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/Contents.json new file mode 100644 index 0000000000..eea57b6522 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_send.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_send@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send.png b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send.png new file mode 100644 index 0000000000..e0f3889182 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send@2x.png new file mode 100644 index 0000000000..8b690e5fc4 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletSend.imageset/ic_send@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/Contents.json new file mode 100644 index 0000000000..25de093c56 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_settings.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_settings@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings.png b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings.png new file mode 100644 index 0000000000..b507291bb9 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings@2x.png new file mode 100644 index 0000000000..a4a18cfb04 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletSettings.imageset/ic_settings@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/Contents.json new file mode 100644 index 0000000000..89ac135498 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_update.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_update@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update.png b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update.png new file mode 100644 index 0000000000..697097cdf3 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update@2x.png b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update@2x.png new file mode 100644 index 0000000000..1d8a7b927a Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_WalletUpdate.imageset/ic_update@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/Contents.json index 903813afc2..56c95501ec 100644 --- a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/Contents.json +++ b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_call@1x.png", + "filename" : "ic_call.png", "scale" : "1x" }, { diff --git a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call.png b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call.png new file mode 100644 index 0000000000..6a48bc086e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@1x.png b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@1x.png deleted file mode 100644 index d0a0fdf7e8..0000000000 Binary files a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@1x.png and /dev/null differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@2x.png b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@2x.png index e77828ee93..e2edd1f39b 100644 Binary files a/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@2x.png and b/Telegram-Mac/Assets.xcassets/Icon_callNavigationHeader.imageset/ic_call@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/Contents.json new file mode 100644 index 0000000000..5dbb141b6b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "qrlogo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "qrlogo@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo.png b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo.png new file mode 100644 index 0000000000..22aba5ef7b Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo.png differ diff --git a/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo@2x.png b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo@2x.png new file mode 100644 index 0000000000..333368427e Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/Icon_loginQRCap.imageset/qrlogo@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/Contents.json new file mode 100644 index 0000000000..42d2f32bfe --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "StatusIcon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "StatusIcon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon.png b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon.png new file mode 100644 index 0000000000..b668fdfe25 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon.png differ diff --git a/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon@2x.png b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon@2x.png new file mode 100644 index 0000000000..7eccc34cf7 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/StatusIcon.imageset/StatusIcon@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/arrow_left.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/arrow_left.imageset/Contents.json new file mode 100644 index 0000000000..85628e19e2 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/arrow_left.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "arrow_right.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/arrow_left.imageset/arrow_right.pdf b/Telegram-Mac/Assets.xcassets/arrow_left.imageset/arrow_right.pdf new file mode 100644 index 0000000000..baf05c2435 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/arrow_left.imageset/arrow_right.pdf differ diff --git a/Telegram-Mac/Assets.xcassets/arrow_right.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/arrow_right.imageset/Contents.json new file mode 100644 index 0000000000..f78ba9e92b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/arrow_right.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "arrow_left.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/arrow_right.imageset/arrow_left.pdf b/Telegram-Mac/Assets.xcassets/arrow_right.imageset/arrow_left.pdf new file mode 100644 index 0000000000..7b20434672 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/arrow_right.imageset/arrow_left.pdf differ diff --git a/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/Contents.json new file mode 100644 index 0000000000..02187c6534 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icons8-circled-play-48.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "icons8-circled-play-96.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-48.png b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-48.png new file mode 100644 index 0000000000..8ca7c9365c Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-48.png differ diff --git a/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-96.png b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-96.png new file mode 100644 index 0000000000..cea69779f6 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/icons8-circled-play-48.imageset/icons8-circled-play-96.png differ diff --git a/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/Contents.json new file mode 100644 index 0000000000..8689d392bf --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "selection_frame_dark.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/selection_frame_dark.pdf b/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/selection_frame_dark.pdf new file mode 100644 index 0000000000..ae27128091 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/selection_frame_dark.imageset/selection_frame_dark.pdf differ diff --git a/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/Contents.json new file mode 100644 index 0000000000..3c2cd47618 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "selection_frame_light.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/selection_frame_light.pdf b/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/selection_frame_light.pdf new file mode 100644 index 0000000000..c426c5d695 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/selection_frame_light.imageset/selection_frame_light.pdf differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/Contents.json new file mode 100644 index 0000000000..fbf8b7ae00 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "tabsselect_left_gray.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "tabsselect_left_gray@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray.png b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray.png new file mode 100644 index 0000000000..ac78900b7f Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray@2x.png b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray@2x.png new file mode 100644 index 0000000000..7393f3176d Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_left_gray.imageset/tabsselect_left_gray@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/Contents.json new file mode 100644 index 0000000000..37138cc61f --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "tabsselect_left_systemcol.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "tabsselect_left_systemcol@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol.png b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol.png new file mode 100644 index 0000000000..0e3014a9e5 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol@2x.png b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol@2x.png new file mode 100644 index 0000000000..7d5fded832 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_left_systemcol.imageset/tabsselect_left_systemcol@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/Contents.json new file mode 100644 index 0000000000..4035b537c7 --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "tabsselect_top_gray.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "tabsselect_top_gray@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray.png b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray.png new file mode 100644 index 0000000000..37e77339ac Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray@2x.png b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray@2x.png new file mode 100644 index 0000000000..717879cc61 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_top_gray.imageset/tabsselect_top_gray@2x.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/Contents.json b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/Contents.json new file mode 100644 index 0000000000..fc677b297b --- /dev/null +++ b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "tabsselect_top_systemcol.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "tabsselect_top_systemcol@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol.png b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol.png new file mode 100644 index 0000000000..5403c2fd16 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol.png differ diff --git a/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol@2x.png b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol@2x.png new file mode 100644 index 0000000000..7e32ccad39 Binary files /dev/null and b/Telegram-Mac/Assets.xcassets/tabsselect_top_systemcol.imageset/tabsselect_top_systemcol@2x.png differ diff --git a/Telegram-Mac/AudioAnimatedSticker.swift b/Telegram-Mac/AudioAnimatedSticker.swift new file mode 100644 index 0000000000..bf09ebd10b --- /dev/null +++ b/Telegram-Mac/AudioAnimatedSticker.swift @@ -0,0 +1,134 @@ +// +// AudioAnimatedSticker.swift +// Telegram +// +// Created by Mikhail Filimonov on 06.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + + +private struct SelectFrameState : Equatable { + let frame: Int32 + init(frame: Int32) { + self.frame = frame + } + func withUpdatedFrame(_ frame: Int32) -> SelectFrameState { + return SelectFrameState(frame: frame) + } +} + +private let _id_input = InputDataIdentifier("frame") + +private func selectFrameEntries(_ state: SelectFrameState) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(String(state.frame)), error: nil, identifier: _id_input, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: "Start frame", filter: { $0 }, limit: 3)) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +private func selectFrameController(context: AccountContext, select:@escaping(Int32)->Void) -> InputDataModalController { + + + let initialState = SelectFrameState(frame: 1) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((SelectFrameState) -> SelectFrameState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: selectFrameEntries(state)) + } + + let controller = InputDataController(dataSignal: signal, title: "Sound Effect Frame") + + var close: (()->Void)? = nil + + let modalInteractions = ModalInteractions(acceptTitle: "Save", accept: { + select(stateValue.with { $0.frame }) + close?() + }, height: 50, singleButton: true) + + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { + close?() + }) + + controller.updateDatas = { data in + updateState { state in + if let rawFrame = data[_id_input]?.stringValue, let frame = Int32(rawFrame) { + return state.withUpdatedFrame(frame) + } + return state + } + return .none + } + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, closeHandler: { f in f() }, size: NSMakeSize(300, 300)) + + close = { [weak modalController] in + modalController?.close() + } + + return modalController + +} + +func addAudioToSticker(context: AccountContext) { + filePanel(with: ["tgs", "mp3"], allowMultiple: true, canChooseDirectories: false, for: mainWindow, completion: { files in + if let files = files { + let stickerPath = files.first(where: { $0.nsstring.pathExtension == "tgs" }) + let audioPath = files.first(where: { $0.nsstring.pathExtension == "mp3" }) + + if let stickerPath = stickerPath, let audioPath = audioPath { + let data = try! Data(contentsOf: URL.init(fileURLWithPath: stickerPath)) + let uncompressed = TGGUnzipData(data, 8 * 1024 * 1024)! + + let string = NSMutableString(data: uncompressed, encoding: String.Encoding.utf8.rawValue)! + + let mp3Data = try! Data(contentsOf: URL(fileURLWithPath: audioPath)) + + showModal(with: selectFrameController(context: context, select: { frame in + let effectString = "\"soundEffect\":{\"triggerOn\":\(frame),\"data\":\"\(mp3Data.base64EncodedString())\"}" + + let range = string.range(of: "\"tgs\":1,") + if range.location != NSNotFound { + string.insert(effectString + ",", at: range.max) + } + + let updatedData = string.data(using: String.Encoding.utf8.rawValue)! + + let zipData = TGGZipData(updatedData, -1)! + + let output = NSTemporaryDirectory() + "\(arc4random()).tgs" + + try! zipData.write(to: URL(fileURLWithPath: output)) + + + + if let controller = context.sharedContext.bindings.rootNavigation().controller as? ChatController { + showModal(with: PreviewSenderController(urls: [URL(fileURLWithPath: output)], chatInteraction: controller.chatInteraction), for: context.window) + } + }), for: context.window) + } + + } + }) +} diff --git a/Telegram-Mac/AudioPlayer.swift b/Telegram-Mac/AudioPlayer.swift index 5a7d0670f2..ebda7e91e8 100644 --- a/Telegram-Mac/AudioPlayer.swift +++ b/Telegram-Mac/AudioPlayer.swift @@ -7,8 +7,9 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore protocol AudioPlayerDelegate : class { func audioPlayerDidFinishPlaying(_ audioPlayer:AudioPlayer) diff --git a/Telegram-Mac/AudioPlayerController.swift b/Telegram-Mac/AudioPlayerController.swift index 7ffd29ca3a..557200592a 100644 --- a/Telegram-Mac/AudioPlayerController.swift +++ b/Telegram-Mac/AudioPlayerController.swift @@ -7,25 +7,38 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit +import AVKit + + + class APSingleWrapper { let resource:TelegramMediaResource let name:String? + let mimeType: String let performer:String? let id:AnyHashable - init(resource:TelegramMediaResource, name:String?, performer:String?, id: AnyHashable) { + init(resource:TelegramMediaResource, mimeType: String = "mp3", name:String?, performer:String?, id: AnyHashable) { self.resource = resource self.name = name + self.mimeType = mimeType self.performer = performer self.id = id } } -fileprivate(set) var globalAudio:APController? +let globalAudioPromise: Promise = Promise(nil) + +fileprivate(set) var globalAudio:APController? { + didSet { + globalAudioPromise.set(.single(globalAudio)) + } +} enum APState : Equatable { case waiting @@ -34,40 +47,7 @@ enum APState : Equatable { case stoped case fetching(Float, Bool) } -func ==(lhs:APState, rhs:APState) -> Bool { - switch lhs { - case .waiting: - if case .waiting = rhs { - return true - } else { - return false - } - case let .paused(lhsVars): - if case let .paused(rhsVars) = rhs, lhsVars.current == rhsVars.current && lhsVars.duration == rhsVars.duration { - return true - } else { - return false - } - case .stoped: - if case .stoped = rhs { - return true - } else { - return false - } - case let .playing(lhsVars): - if case let .playing(rhsVars) = rhs, lhsVars.current == rhsVars.current && lhsVars.duration == rhsVars.duration { - return true - } else { - return false - } - case let .fetching(lhsCurrent, _): - if case let .fetching(rhsCurrent, _) = rhs, lhsCurrent == rhsCurrent { - return true - } else { - return false - } - } -} + struct APResource { @@ -78,13 +58,47 @@ struct APResource { class APItem : Equatable { - fileprivate(set) var state:APState = .waiting { - didSet { - _state.set(.single(state)) + private(set) var status: MediaPlayerStatus = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0, dimensions: CGSize(), timestamp: 0, baseRate: 1.0, volume: 1.0, seekId: 0, status: .paused) + + func setStatus(_ status: MediaPlayerStatus, rate: Double) { + + let status = status.withUpdatedDuration(max(status.duration, self.status.duration)) + + var progress:TimeInterval = (status.timestamp / status.duration) + if progress.isNaN { + progress = 0 + } + + if !progress.isFinite { + progress = 1.0 + } + + switch status.status { + case .playing: + self.state = .playing(current: status.timestamp, duration: status.duration, progress: progress, animated: true) + case .paused: + self.state = .paused(current: status.timestamp, duration: status.duration, progress: progress, animated: true) + default: + self.state = .paused(current: status.timestamp, duration: status.duration, progress: progress, animated: true) } + self.status = status } - fileprivate let _state:Promise = Promise() + private var _state: APState = .waiting + fileprivate(set) var state:APState { + get { + return _state + } + set { + _state = newValue + _stateValue.set(.single(newValue)) + } + } + + private let _stateValue:Promise = Promise() + var stateValue: Signal { + return _stateValue.get() + } let entry:APEntry let account:Account @@ -101,21 +115,7 @@ func ==(lhs:APItem, rhs:APItem) -> Bool { return lhs.stableId == rhs.stableId } -class APHoleItem : APItem { - private let hole:MessageHistoryHole - override init(_ entry:APEntry, _ account:Account) { - if case let .hole(hole) = entry { - self.hole = hole - } else { - fatalError() - } - super.init(entry, account) - } - - override var stableId: ChatHistoryEntryId { - return hole.chatStableId - } -} + class APSongItem : APItem { let songName:String @@ -123,7 +123,7 @@ class APSongItem : APItem { let resource:TelegramMediaResource let ext:String private let fetchDisposable:MetaDisposable = MetaDisposable() - + override init(_ entry:APEntry, _ account:Account) { if case let .song(message) = entry { let file = (message.media.first as! TelegramMediaFile) @@ -139,7 +139,7 @@ class APSongItem : APItem { } if file.isVoice || file.isInstantVideo { if let forward = message.forwardInfo { - performerName = forward.author.displayTitle + performerName = forward.authorTitle } else if let peer = message.author { if peer.id == account.peerId { performerName = localizedString("You"); @@ -150,14 +150,14 @@ class APSongItem : APItem { performerName = "" } if file.isVoice { - songName = tr(.audioControllerVoiceMessage) + songName = tr(L10n.audioControllerVoiceMessage) } else { - songName = tr(.audioControllerVideoMessage) + songName = tr(L10n.audioControllerVideoMessage) } } else { var t:String? var p:String? - + for attribute in file.attributes { if case let .Audio(_, _, title, performer, _) = attribute { t = title @@ -168,15 +168,15 @@ class APSongItem : APItem { if let t = t { songName = t } else { - songName = tr(.audioUntitledSong) + songName = p != nil ? L10n.audioUntitledSong : "" } if let p = p { performerName = p } else { - performerName = tr(.audioUnknownArtist) + performerName = file.fileName ?? L10n.audioUnknownArtist } } - + } else if case let .single(wrapper) = entry { resource = wrapper.resource @@ -190,32 +190,47 @@ class APSongItem : APItem { } else { performerName = "" } - self.ext = "m4a" + if let _ = wrapper.mimeType.range(of: "m4a") { + self.ext = "m4a" + } else if let _ = wrapper.mimeType.range(of: "mp4") { + self.ext = "mp4" + } else { + self.ext = "mp3" + } } else { fatalError("🤔") } super.init(entry, account) } - + override var stableId: ChatHistoryEntryId { return entry.stableId } + var reference: MediaResourceReference { + switch entry { + case let .song(message): + return FileMediaReference.message(message: MessageReference(message), media: message.media.first as! TelegramMediaFile).resourceReference(resource) + default: + return MediaResourceReference.standalone(resource: resource) + } + } + private func fetch() { - fetchDisposable.set(account.postbox.mediaBox.fetchedResource(resource, tag: TelegramMediaResourceFetchTag(statsCategory: .audio)).start()) + fetchDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: reference).start()) } - + private func cancelFetching() { fetchDisposable.set(nil) } - + deinit { fetchDisposable.dispose() } - - fileprivate func pullResource()->Signal { + + fileprivate func pullResource()->Signal { fetch() - return account.postbox.mediaBox.resourceStatus(resource) |> deliverOnMainQueue |> mapToSignal { [weak self] status -> Signal in + return account.postbox.mediaBox.resourceStatus(resource) |> deliverOnMainQueue |> mapToSignal { [weak self] status -> Signal in if let strongSelf = self { let ext = strongSelf.ext switch status { @@ -233,10 +248,10 @@ class APSongItem : APItem { return .complete() } } |> deliverOnMainQueue - + } - - + + } struct APTransition { @@ -245,26 +260,24 @@ struct APTransition { let updated:[(Int,APItem)] } -fileprivate func prepareItems(from:[APEntry]?, to:[APEntry], account:Account) -> Signal { +fileprivate func prepareItems(from:[APEntry]?, to:[APEntry], account:Account) -> Signal { return Signal {(subscriber) in - + let (removed, inserted, updated) = proccessEntries(from, right: to, { (entry) -> APItem in - + switch entry { case .song: return APSongItem(entry,account) - case .hole: - return APHoleItem(entry,account) case .single: return APSongItem(entry,account) } - + }) - + subscriber.putNext(APTransition(inserted: inserted, removed: removed, updated:updated)) subscriber.putCompletion() return EmptyDisposable - + } |> runOn(prepareQueue) } @@ -275,7 +288,6 @@ enum APHistoryLocation : Equatable { enum APEntry : Comparable, Identifiable { case song(Message) - case hole(MessageHistoryHole) case single(APSingleWrapper) var stableId: ChatHistoryEntryId { switch self { @@ -285,24 +297,21 @@ enum APEntry : Comparable, Identifiable { if let stableId = wrapper.id.base as? ChatHistoryEntryId { return stableId } - return .maybeId(wrapper.id) - case let .hole(hole): - return hole.chatStableId + return .maybeId(wrapper.id) + } } - + func isEqual(to wrapper:APSingleWrapper) -> Bool { return stableId == .maybeId(wrapper.id) } - + func isEqual(to message:Message) -> Bool { return stableId == message.chatStableId } - + var index: MessageIndex { switch self { - case let .hole(hole): - return hole.maxIndex case let .song(message): return MessageIndex(message) case .single(_): @@ -321,12 +330,6 @@ func ==(lhs:APEntry, rhs:APEntry) -> Bool { } case .single(_): return false - case let .hole(lhsHole): - if case let .hole(rhsHole) = rhs, lhsHole == rhsHole { - return true - } else { - return false - } } } @@ -365,33 +368,60 @@ protocol APDelegate : class { func audioDidCompleteQueue(for controller:APController) } -class APController : NSObject, AudioPlayerDelegate { + + +class APController : NSResponder { + + private var mediaPlayer: MediaPlayer? + + private let statusDisposable = MetaDisposable() + private let readyDisposable = MetaDisposable() + + + public let ready:Promise = Promise() let account:Account - + private var _timebase: CMTimebase? + fileprivate let history:Promise = Promise() fileprivate let entries:Atomic = Atomic(value:nil) fileprivate let items:Atomic<[APItem]> = Atomic(value:[]) fileprivate let disposable:MetaDisposable = MetaDisposable() - + fileprivate let itemDisposable:MetaDisposable = MetaDisposable() fileprivate let songStateDisposable:MetaDisposable = MetaDisposable() - + fileprivate let timebaseDisposable:MetaDisposable = MetaDisposable() private var listeners:[WeakReference] = [] - - fileprivate var player:AudioPlayer? + + // fileprivate var player:AudioPlayer? fileprivate var current:Int = -1 - fileprivate(set) var needRepeat:Bool = false - fileprivate var timer:SwiftSignalKitMac.Timer? + private let bufferingStatusValuePromise = Promise<(IndexSet, Int)?>() + + private(set) var bufferingStatus: Signal<(IndexSet, Int)?, NoError> { + set { + self.bufferingStatusValuePromise.set(newValue) + } + get { + return bufferingStatusValuePromise.get() + } + } + + + fileprivate(set) var needRepeat:Bool = false + + fileprivate var timer:SwiftSignalKit.Timer? + + fileprivate var prevNextDisposable = DisposableSet() + private var _song:APSongItem? fileprivate var song:APSongItem? { set { self.stop() _song = newValue if let song = newValue { - songStateDisposable.set((song._state.get() |> distinctUntilChanged).start(next: {[weak self] (state) in + songStateDisposable.set((song.stateValue |> distinctUntilChanged).start(next: {[weak self] (state) in if let strongSelf = self { strongSelf.notifyStateChanged(item: song) } @@ -404,11 +434,41 @@ class APController : NSObject, AudioPlayerDelegate { return _song } } - + var timebase:CMTimebase? { - return self.player?.timebase + return _timebase//self.player?.timebase } - + + func notifyGlobalStateChanged() { + if let song = song { + notifyStateChanged(item: song) + } + } + + var isPlaying: Bool { + if let currentSong = currentSong { + switch currentSong.state { + case .playing: + return true + default: + return false + } + } + return false + } + + var isDownloading: Bool { + if let currentSong = currentSong { + switch currentSong.state { + case .fetching: + return true + default: + return false + } + } + return false + } + private func notifyStateChanged(item:APSongItem) { for listener in listeners { if let value = listener.value as? APDelegate { @@ -416,64 +476,98 @@ class APController : NSObject, AudioPlayerDelegate { } } } - + private func notifySongChanged(item:APSongItem) { - for listener in listeners { - if let value = listener.value as? APDelegate { - value.songDidChanged(song: item, for: self) + Queue.mainQueue().async { + for listener in self.listeners { + if let value = listener.value as? APDelegate { + value.songDidChanged(song: item, for: self) + } } } } - + private func notifySongDidStartPlaying(item:APSongItem) { - for listener in listeners { - if let value = listener.value as? APDelegate { - value.songDidStartPlaying(song: item, for: self) + Queue.mainQueue().async { + for listener in self.listeners { + if let value = listener.value as? APDelegate { + value.songDidStartPlaying(song: item, for: self) + } } } } private func notifySongDidChangedTimebase(item:APSongItem) { - for listener in listeners { - if let value = listener.value as? APDelegate { - value.playerDidChangedTimebase(song: item, for: self) + Queue.mainQueue().async { + for listener in self.listeners { + if let value = listener.value as? APDelegate { + value.playerDidChangedTimebase(song: item, for: self) + } } } } - - - + + + private func notifySongDidStopPlaying(item:APSongItem) { - for listener in listeners { - if let value = listener.value as? APDelegate { - value.songDidStopPlaying(song: item, for: self) + Queue.mainQueue().async { + for listener in self.listeners { + if let value = listener.value as? APDelegate { + value.songDidStopPlaying(song: item, for: self) + } } } } - + func notifyCompleteQueue() { - for listener in listeners { - if let value = listener.value as? APDelegate { - value.audioDidCompleteQueue(for: self) + Queue.mainQueue().async { + for listener in self.listeners { + if let value = listener.value as? APDelegate { + value.audioDidCompleteQueue(for: self) + } } } } - - - init(account:Account) { + + private let streamable: Bool + var baseRate: Double { + didSet { + mediaPlayer?.setBaseRate(baseRate) + } + } + init(account:Account, streamable: Bool, baseRate: Double) { self.account = account + self.streamable = streamable + self.baseRate = baseRate super.init() + +// readyDisposable.set((ready.get() |> filter {$0} |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in +// +// })) } - + + @objc open func windowDidBecomeKey() { + + } + + + @objc open func windowDidResignKey() { + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + func start() { globalAudio?.stop() globalAudio?.cleanup() + globalAudio = self - account.context.mediaKeyTap?.startWatchingMediaKeys() } - - + + fileprivate func merge(with transition:APTransition) { - + var previous:[APItem] = self.items.modify({$0}) let current = self.current let items = self.items.modify { items -> [APItem] in @@ -489,8 +583,8 @@ class APController : NSObject, AudioPlayerDelegate { } return new } - - if current != -1, current > 0 { + + if current != -1, current >= 0 { if current < previous.count { let previousCurrent = previous[current] var foundIndex:Int? = nil @@ -512,21 +606,21 @@ class APController : NSObject, AudioPlayerDelegate { } } } - + } - + fileprivate var pullItems:[APItem] { return items.modify({$0}) } - + func toggleRepeat() { needRepeat = !needRepeat } - + var needLoop:Bool { return true } - + func next() { if !nextEnabled { return @@ -538,39 +632,43 @@ class APController : NSObject, AudioPlayerDelegate { } dequeueCurrent() } - + func playOrPause() { if let song = song { if case .playing = song.state { - player?.pause() + // player?.pause() + mediaPlayer?.pause() } else if case .paused = song.state { - player?.play() + //player?.play() + mediaPlayer?.play() } else if song.state == .stoped { dequeueCurrent() } } } - + func pause() -> Bool { if let song = song { if case .playing = song.state { - player?.pause() + // player?.pause() + mediaPlayer?.pause() return true } } return false } - + func play() -> Bool { if let song = song { if case .paused = song.state { - player?.play() + // player?.play() + mediaPlayer?.play() return true } } return false } - + func prev() { if !prevEnabled { return @@ -582,176 +680,244 @@ class APController : NSObject, AudioPlayerDelegate { } dequeueCurrent() } - + var nextEnabled:Bool { return pullItems.count > 1 } - - + + var prevEnabled:Bool { return pullItems.count > 1 } - + var needNext:Bool { return true } - + func complete() { notifyCompleteQueue() + cleanup() } - + var currentSong:APSongItem? { if !pullItems.isEmpty, pullItems.count > current, let song = pullItems[max(0, current)] as? APSongItem { return song } return nil } - + fileprivate func dequeueCurrent() { if let current = currentSong { self.song = current - notifySongChanged(item: current) play(with: current) + notifySongChanged(item: current) } } - + + fileprivate func play(with item:APSongItem) { - itemDisposable.set(item.pullResource().start(next: { [weak self] resource in - if let strongSelf = self { - if resource.complete { - strongSelf.player = .player(for: resource.path) - strongSelf.player?.delegate = strongSelf - strongSelf.player?.play() - } else { - item.state = .fetching(resource.progress,true) - } + + + self.mediaPlayer?.seek(timestamp: 0) + + let player = MediaPlayer(postbox: account.postbox, reference: item.reference, streamable: streamable, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: baseRate, fetchAutomatically: false) + + player.play() + + player.actionAtEnd = .action({ [weak self] in + self?.audioPlayerDidFinishPlaying() + }) + + self.mediaPlayer = player + + + let size = item.resource.size ?? 0 + bufferingStatus = account.postbox.mediaBox.resourceRangesStatus(item.resource) + |> map { ranges -> (IndexSet, Int) in + return (ranges, size) + } + + timebaseDisposable.set((player.timebase |> deliverOnMainQueue).start(next: { [weak self] timebase in + self?._timebase = timebase + self?.notifySongDidChangedTimebase(item: item) + })) + + self.statusDisposable.set((player.status |> deliverOnMainQueue).start(next: { [weak self] status in + guard let `self` = self else {return} + item.setStatus(status, rate: self.baseRate) + switch status.status { + case .paused: + self.stopTimer() + case .playing: + self.startTimer() + default: + self.stopTimer() } + self.updateUIAfterTick(status) })) + +// + if !streamable { + itemDisposable.set(item.pullResource().start(next: { [weak self] resource in + if let strongSelf = self { + if resource.complete { + // strongSelf.player = .player(for: resource.path) + // strongSelf.player?.delegate = strongSelf + // strongSelf.player?.play() + + + let items = strongSelf.items.modify({$0}).filter({$0 is APSongItem}).map{$0 as! APSongItem} + if let index = items.index(of: item) { + let previous = index - 1 + let next = index + 1 + if previous >= 0 { + strongSelf.prevNextDisposable.add(fetchedMediaResource(mediaBox: strongSelf.account.postbox.mediaBox, reference: items[previous].reference, statsCategory: .audio).start()) + } + if next < items.count { + strongSelf.prevNextDisposable.add(fetchedMediaResource(mediaBox: strongSelf.account.postbox.mediaBox, reference: items[next].reference, statsCategory: .audio).start()) + } + } + + } else { + item.state = .fetching(resource.progress,true) + } + } + })) + } + } - - func audioPlayerDidStartPlaying(_ audioPlayer: AudioPlayer) { + + + var currentTime: TimeInterval { if let current = currentSong { - var progress:TimeInterval = (audioPlayer.currentTime / audioPlayer.duration) - if progress.isNaN { - progress = 1 + switch current.state { + case let .paused(current, _, _, _), let .playing(current, _, _, _): + return current + default: + break } - current.state = .playing(current: audioPlayer.currentTime, duration: audioPlayer.duration, progress:progress,animated: false) - notifySongDidStartPlaying(item: current) - startTimer() - if audioPlayer.duration == 0 { - audioPlayerDidFinishPlaying(audioPlayer) + } + return 0//self.player?.currentTime ?? 0 + } + + var duration: TimeInterval { + if let current = currentSong { + switch current.state { + case let .paused(_, duration, _, _), let .playing(_, duration, _, _): + return duration + default: + break } } + return 0//self.player?.currentTime ?? 0 } - + var isLatest:Bool { return current == 0 } - - func audioPlayerDidFinishPlaying(_ audioPlayer: AudioPlayer) { - stop() - - if needRepeat { - dequeueCurrent() - } else if needNext && nextEnabled { - if isLatest { - if needLoop { - next() + + func audioPlayerDidFinishPlaying() { + Queue.mainQueue().async { + self.stop() + + if self.needRepeat { + self.dequeueCurrent() + } else if self.needNext && self.nextEnabled { + if self.isLatest { + if self.needLoop { + self.next() + } else { + self.complete() + } } else { - complete() + self.next() } } else { - next() - } - } else { - complete() - } - } - - func audioPlayerDidPaused(_ audioPlayer: AudioPlayer) { - if let song = currentSong { - var progress:TimeInterval = (audioPlayer.currentTime / audioPlayer.duration) - if progress.isNaN { - progress = 1 + self.complete() } - song.state = .paused(current: audioPlayer.currentTime, duration: audioPlayer.duration, progress: progress, animated: false) - stopTimer() } } - - func audioPlayerDidChangedTimebase(_ audioPLayer: AudioPlayer) { + + + func audioPlayerDidChangedTimebase(_ audioPLayer: MediaPlayer) { if let current = currentSong { notifySongDidChangedTimebase(item: current) } } - + func stop() { - player?.stop() + // player?.stop() + mediaPlayer = nil if let item = song { notifySongDidStopPlaying(item: item) } song?.state = .stoped stopTimer() } - + func set(trackProgress:Float) { - if let player = player, let song = song { - let current = player.duration * Double(trackProgress) - player.set(position:current) - if case .paused = song.state { - var progress:TimeInterval = (current / player.duration) - if progress.isNaN { - progress = 1 - } - song.state = .playing(current: current, duration: player.duration, progress: progress, animated: true) - song.state = .paused(current: current, duration: player.duration, progress: progress, animated: true) - } + if let player = mediaPlayer, let song = song { + let current: Double = song.status.duration * Double(trackProgress) + player.seek(timestamp: current) +// if case .paused = song.state { +// var progress:TimeInterval = (current / song.status.duration) +// if progress.isNaN { +// progress = 1 +// } +// // song.state = .playing(current: current, duration: song.status.duration, progress: progress, animated: false) +// // song.state = .paused(current: current, duration: song.status.duration, progress: progress, animated: false) +// } } } - + func cleanup() { listeners.removeAll() globalAudio = nil - account.context.mediaKeyTap?.stopWatchingMediaKeys() + mainWindow.applyResponderIfNeeded() stop() } - - - + + private func updateUIAfterTick(_ status: MediaPlayerStatus) { + + } + + private func startTimer() { - if timer == nil { - timer = SwiftSignalKitMac.Timer(timeout: 0.2, repeat: true, completion: { [weak self] in - if let strongSelf = self, let player = strongSelf.player { - var progress:TimeInterval = (player.currentTime / player.duration) - if progress.isNaN { - progress = 1 - } - strongSelf.song?.state = .playing(current: player.currentTime, duration: player.duration, progress: progress, animated: true) - } - }, queue: Queue.mainQueue()) - timer?.start() - } + var additional: Double = 0.2 + let duration: TimeInterval = 0.2 + timer = SwiftSignalKit.Timer(timeout: duration, repeat: true, completion: { [weak self] in + if let `self` = self, let item = self.song { + let new = item.status.timestamp + additional * item.status.baseRate + item.state = .playing(current: new, duration: item.status.duration, progress: new / max((item.status.duration), 0.2), animated: true) + additional += duration + self.updateUIAfterTick(item.status) + } + }, queue: Queue.mainQueue()) + timer?.start() } private func stopTimer() { timer?.invalidate() timer = nil } - + deinit { disposable.dispose() itemDisposable.dispose() songStateDisposable.dispose() - cleanup() + prevNextDisposable.dispose() + readyDisposable.dispose() + statusDisposable.dispose() + timebaseDisposable.dispose() } - + fileprivate var tags:MessageTags { return .music } - + func add(listener:NSObject) { listeners.append(WeakReference(value: listener)) } - + func remove(listener:NSObject) { let index = listeners.index(where: { (weakValue) -> Bool in return listener == weakValue.value @@ -763,16 +929,20 @@ class APController : NSObject, AudioPlayerDelegate { } class APChatController : APController { - + private let peerId:PeerId private let index:MessageIndex? - - init(account: Account, peerId: PeerId, index: MessageIndex?) { + + init(account: Account, peerId: PeerId, index: MessageIndex?, streamable: Bool, baseRate: Double = 1.0) { self.peerId = peerId self.index = index - super.init(account: account) + super.init(account: account, streamable: streamable, baseRate: baseRate) } - + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func start() { super.start() let tagMask:MessageTags = self.tags @@ -781,40 +951,35 @@ class APChatController : APController { let account = self.account let peerId = self.peerId let index = self.index - let apply = history.get() |> distinctUntilChanged |> mapToSignal { location -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), Void> in + let apply = history.get() |> distinctUntilChanged |> mapToSignal { location -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in switch location { case .initial: - return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: MessageIndex.upperBound(peerId: peerId), count: 100, anchorIndex: MessageIndex.upperBound(peerId: peerId), fixedCombinedReadState: nil, tagMask: tagMask) + return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: MessageHistoryAnchorIndex.upperBound, anchorIndex: MessageHistoryAnchorIndex.upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: [], additionalData: []) case let .index(index): - return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 100, anchorIndex: index, fixedCombinedReadState: nil, tagMask: tagMask) + return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: MessageHistoryAnchorIndex.message(index), anchorIndex: MessageHistoryAnchorIndex.message(index), count: 100, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: [], additionalData: []) } - + } |> map { view -> (APHistory?,APHistory) in var entries:[APEntry] = [] for viewEntry in view.0.entries { - switch viewEntry { - case let .MessageEntry(message, _, _, _): - entries.append(.song(message)) - case let .HoleEntry(hole, _): - entries.append(.hole(hole)) - } + entries.append(.song(viewEntry.message)) } - + let new = APHistory(original: view.0, filtred: entries) return (list.swap(new),new) } - |> mapToQueue { view -> Signal in + |> mapToQueue { view -> Signal in let transition = prepareItems(from: view.0?.filtred, to: view.1.filtred, account: account) return transition } |> deliverOnMainQueue - + let first:Atomic = Atomic(value:true) disposable.set(apply.start(next: {[weak self] (transition) in - + let isFirst = first.swap(false) - + self?.merge(with: transition) - + if isFirst { if let index = index { let list:[APItem] = items.modify({$0}) @@ -825,27 +990,31 @@ class APChatController : APController { } } } - + self?.dequeueCurrent() self?.ready.set(.single(true)) } - + })) - + if let index = index { - history.set(.single(.index(index))) + history.set(.single(.index(index)) |> delay(0.1, queue: Queue.mainQueue())) } else { - history.set(.single(.initial)) + history.set(.single(.initial) |> delay(0.1, queue: Queue.mainQueue())) } } } class APChatMusicController : APChatController { - - override init(account: Account, peerId: PeerId, index: MessageIndex?) { - super.init(account: account, peerId: peerId, index: index) + + init(account: Account, peerId: PeerId, index: MessageIndex?, baseRate: Double = 1.0) { + super.init(account: account, peerId: peerId, index: index, streamable: true, baseRate: baseRate) } - + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + fileprivate override var tags: MessageTags { return .music } @@ -853,47 +1022,65 @@ class APChatMusicController : APChatController { class APChatVoiceController : APChatController { private let markAsConsumedDisposable = MetaDisposable() - override init(account: Account, peerId: PeerId, index: MessageIndex?) { - super.init(account: account, peerId: peerId, index:index) + init(account: Account, peerId: PeerId, index: MessageIndex?, baseRate: Double = 1.0) { + super.init(account: account, peerId: peerId, index:index, streamable: false, baseRate: baseRate) } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var nextEnabled: Bool { + return current > 0 + } + + override var prevEnabled: Bool { + return current < pullItems.count - 1 + } + override func play(with item: APSongItem) { super.play(with: item) markAsConsumedDisposable.set(markMessageContentAsConsumedInteractively(postbox: account.postbox, messageId: item.entry.index.id).start()) } - + deinit { markAsConsumedDisposable.dispose() } - + fileprivate override var tags: MessageTags { return .voiceOrInstantVideo } - + override var needLoop:Bool { return false } - + } class APSingleResourceController : APController { let wrapper:APSingleWrapper - init(account: Account, wrapper:APSingleWrapper) { + init(account: Account, wrapper:APSingleWrapper, streamable: Bool, baseRate: Double = 1.0) { self.wrapper = wrapper - super.init(account: account) + super.init(account: account, streamable: streamable, baseRate: baseRate) merge(with: APTransition(inserted: [(0,APSongItem(.single(wrapper), account))], removed: [], updated: [])) } - + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func start() { super.start() ready.set(.single(true)) dequeueCurrent() } - + override var needLoop:Bool { return false } - + override var needNext: Bool { return false } } + diff --git a/Telegram-Mac/AudioRecorder.swift b/Telegram-Mac/AudioRecorder.swift index 166543dcb6..efe265d870 100644 --- a/Telegram-Mac/AudioRecorder.swift +++ b/Telegram-Mac/AudioRecorder.swift @@ -9,10 +9,11 @@ import Cocoa import Foundation -import SwiftSignalKitMac +import SwiftSignalKit import CoreMedia import AVFoundation -import TelegramCoreMac +import TelegramCore +import SyncCore private let kOutputBus: UInt32 = 0 private let kInputBus: UInt32 = 1 @@ -144,13 +145,14 @@ struct RecordedAudioData { let path: String let duration: Double let waveform: Data? + let id:Int64? } final class ManagedAudioRecorderContext { private let id: Int32 private let micLevel: ValuePromise private let recordingState: ValuePromise - + private let liveUploading:PreUploadManager? private var paused = true private let queue: Queue @@ -166,21 +168,22 @@ final class ManagedAudioRecorderContext { private var micLevelPeak: Int16 = 0 private var micLevelPeakCount: Int = 0 + private var sampleRate: Int32 = 0 fileprivate var isPaused = false private var recordingStateUpdateTimestamp: Double? - init(queue: Queue, micLevel: ValuePromise, recordingState: ValuePromise) { + init(queue: Queue, micLevel: ValuePromise, recordingState: ValuePromise, dataItem: TGDataItem, liveUploading: PreUploadManager?) { assert(queue.isCurrent()) - + self.liveUploading = liveUploading self.id = getNextRecorderContextId() self.micLevel = micLevel self.recordingState = recordingState self.queue = queue - self.dataItem = TGDataItem(tempFile: ()) + self.dataItem = dataItem self.oggWriter = TGOggOpusWriter() addAudioRecorderContext(self.id, self) @@ -265,12 +268,14 @@ final class ManagedAudioRecorderContext { break } } + NSLog("\(inputSampleRate)") deviceDataRequest.mSelector = kAudioDevicePropertyNominalSampleRate guard AudioObjectSetPropertyData(deviceId, &deviceDataRequest, 0, nil, UInt32(MemoryLayout.size), &inputSampleRate) == noErr else { return } var audioStreamDescription = audioRecorderNativeStreamDescription(inputSampleRate.mMinimum) + sampleRate = Int32(inputSampleRate.mMinimum) guard AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &audioStreamDescription, UInt32(MemoryLayout.size)) == noErr else { AudioComponentInstanceDispose(audioUnit) return @@ -349,6 +354,24 @@ final class ManagedAudioRecorderContext { func processAndDisposeAudioBuffer(_ buffer: AudioBuffer) { assert(self.queue.isCurrent()) + var buffer = buffer + + if(sampleRate==16000){ + let initialBuffer=malloc(Int(buffer.mDataByteSize+2)); + memcpy(initialBuffer, buffer.mData, Int(buffer.mDataByteSize)); + buffer.mData=realloc(buffer.mData, Int(buffer.mDataByteSize*3)) + let values = initialBuffer!.assumingMemoryBound(to: Int16.self) + let resampled = buffer.mData!.assumingMemoryBound(to: Int16.self) + values[Int(buffer.mDataByteSize/2)]=values[Int(buffer.mDataByteSize/2)-1] + for i: Int in 0 ..< Int(buffer.mDataByteSize/2) { + resampled[i*3]=values[i] + resampled[i*3+1]=values[i]/3+values[i+1]/3*2 + resampled[i*3+2]=values[i]/3*2+values[i+1]/3 + } + free(initialBuffer) + buffer.mDataByteSize*=3 + } + defer { free(buffer.mData) } @@ -398,7 +421,7 @@ final class ManagedAudioRecorderContext { self.processWaveformPreview(samples: currentEncoderPacket.assumingMemoryBound(to: Int16.self), count: currentEncoderPacketSize / 2) self.oggWriter.writeFrame(currentEncoderPacket.assumingMemoryBound(to: UInt8.self), frameByteCount: UInt(currentEncoderPacketSize)) - + liveUploading?.fileDidChangedSize(false) let timestamp = CACurrentMediaTime() if self.recordingStateUpdateTimestamp == nil || self.recordingStateUpdateTimestamp! < timestamp + 0.1 { self.recordingStateUpdateTimestamp = timestamp @@ -498,8 +521,8 @@ final class ManagedAudioRecorderContext { } } - - return RecordedAudioData(path: self.dataItem.path(), duration: self.oggWriter.encodedDuration(), waveform: waveform) + liveUploading?.fileDidChangedSize(true) + return RecordedAudioData(path: self.dataItem.path(), duration: self.oggWriter.encodedDuration(), waveform: waveform, id: liveUploading?.id) } else { return nil } @@ -533,7 +556,6 @@ final class ManagedAudioRecorder { private var contextRef: Unmanaged? private let micLevelValue = ValuePromise(0.0) private let recordingStateValue = ValuePromise(.paused(duration: 0.0)) - var micLevel: Signal { return self.micLevelValue.get() } @@ -542,9 +564,10 @@ final class ManagedAudioRecorder { return self.recordingStateValue.get() } - init() { + init(liveUploading: PreUploadManager?, dataItem: TGDataItem) { + self.queue.async { - let context = ManagedAudioRecorderContext(queue: self.queue, micLevel: self.micLevelValue, recordingState: self.recordingStateValue) + let context = ManagedAudioRecorderContext(queue: self.queue, micLevel: self.micLevelValue, recordingState: self.recordingStateValue, dataItem: dataItem, liveUploading: liveUploading) self.contextRef = Unmanaged.passRetained(context) } } diff --git a/Telegram-Mac/AudioWaveformView.swift b/Telegram-Mac/AudioWaveformView.swift index 37fc7e9f6d..74b43915ce 100644 --- a/Telegram-Mac/AudioWaveformView.swift +++ b/Telegram-Mac/AudioWaveformView.swift @@ -10,7 +10,7 @@ import Cocoa import TGUIKit fileprivate class AudioWaveformContainerView : View { - var color:NSColor = .blueUI { + var color:NSColor = .accent { didSet { self.setNeedsDisplayLayer() } @@ -31,7 +31,7 @@ fileprivate class AudioWaveformContainerView : View { } override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) + //super.draw(layer, in: ctx) let sampleWidth:CGFloat = 2 let halfSampleWidth:CGFloat = 1 @@ -137,8 +137,10 @@ class AudioWaveformView: View { //foregroundClipingView.clipsToBounds = true; foregroundClipingView.addSubview(foregroundView) addSubview(foregroundClipingView) - - + } + + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) } override func setFrameSize(_ newSize: NSSize) { diff --git a/Telegram-Mac/AuthController.swift b/Telegram-Mac/AuthController.swift index bf402ecbf3..7e6a5f2f59 100644 --- a/Telegram-Mac/AuthController.swift +++ b/Telegram-Mac/AuthController.swift @@ -1,8 +1,9 @@ import Foundation import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox import TGUIKit private let manager = CountryManager() @@ -14,11 +15,165 @@ enum LoginAuthViewState { } +private enum QRTokenState { + case qr(CGImage) +} + +private final class ExportTokenOptionView : View { + private let textView: TextView = TextView() + private let optionText = TextView() + private let cap = View(frame: NSMakeRect(0, 0, 20, 20)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + cap.layer?.cornerRadius = cap.frame.height / 2 + + textView.isSelectable = false + textView.userInteractionEnabled = false + + optionText.isSelectable = false + optionText.userInteractionEnabled = false + addSubview(cap) + addSubview(self.textView) + addSubview(self.optionText) + } + + func update(title: String, number: String) { + let textAttr = NSMutableAttributedString() + _ = textAttr.append(string: title, color: theme.colors.text, font: .normal(.text)) + textAttr.detectBoldColorInString(with: .medium(.text)) + let text = TextViewLayout(textAttr, maximumNumberOfLines: 2) + text.measure(width: frame.width - cap.frame.width - 10) + textView.update(text) + + let option = TextViewLayout(.initialize(string: number, color: theme.colors.underSelectedColor, font: .normal(.text)), maximumNumberOfLines: 2) + option.measure(width: frame.width) + optionText.update(option) + + cap.backgroundColor = theme.colors.accent + + setFrameSize(NSMakeSize(frame.width, max(cap.frame.height, 4 + text.layoutSize.height + 4))) + } + + override func layout() { + super.layout() + + cap.setFrameOrigin(NSZeroPoint) + let offset: CGFloat = optionText.frame.width == 6 ? 7 : 6 + optionText.setFrameOrigin(NSMakePoint(offset, 2)) + textView.setFrameOrigin(NSMakePoint(cap.frame.maxX + 10, 2)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ExportTokenView : View { + fileprivate let imageView: ImageView = ImageView() + fileprivate let logoView = ImageView(frame: NSMakeRect(0, 0, 60, 60)) + private let containerView = View() + private let titleView = TextView() + fileprivate let cancelButton = TitleButton() + + private let firstHelp: ExportTokenOptionView + private let secondHelp: ExportTokenOptionView + private let thridHelp: ExportTokenOptionView + + required init(frame frameRect: NSRect) { + firstHelp = ExportTokenOptionView(frame: NSMakeRect(0, 0, frameRect.width, 0)) + secondHelp = ExportTokenOptionView(frame: NSMakeRect(0, 0, frameRect.width, 0)) + thridHelp = ExportTokenOptionView(frame: NSMakeRect(0, 0, frameRect.width, 0)) + super.init(frame: frameRect) + containerView.addSubview(self.imageView) + + self.imageView.addSubview(logoView) + containerView.addSubview(self.titleView) + containerView.addSubview(firstHelp) + containerView.addSubview(secondHelp) + containerView.addSubview(thridHelp) + containerView.addSubview(cancelButton) + addSubview(containerView) + titleView.isSelectable = false + titleView.userInteractionEnabled = false + updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + let theme = theme as! TelegramPresentationTheme + super.updateLocalizationAndTheme(theme: theme) + self.backgroundColor = theme.colors.background + + let titleLayout = TextViewLayout(.initialize(string: L10n.loginQRTitle, color: theme.colors.text, font: .normal(.header)), maximumNumberOfLines: 2, alignment: .center) + titleLayout.measure(width: frame.width) + titleView.update(titleLayout) + + firstHelp.update(title: L10n.loginQRHelp1, number: "1") + secondHelp.update(title: L10n.loginQRHelp2, number: "2") + thridHelp.update(title: L10n.loginQRHelp3, number: "3") + + cancelButton.set(font: .medium(.text), for: .Normal) + cancelButton.set(color: theme.colors.accent, for: .Normal) + cancelButton.set(text: L10n.loginQRCancel, for: .Normal) + _ = cancelButton.sizeToFit() + logoView.image = theme.icons.login_qr_cap + logoView.sizeToFit() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + func update(state: QRTokenState) { + switch state { + case let .qr(image): + self.imageView.image = image + imageView.sizeToFit() + } + needsLayout = true + } + + override func layout() { + + containerView.setFrameSize(NSMakeSize(frame.width, imageView.frame.height + 20 + self.titleView.frame.height + 20 + firstHelp.frame.height + 10 + secondHelp.frame.height + 10 + thridHelp.frame.height + 30 + cancelButton.frame.height)) + containerView.center() + + imageView.centerX(y: 0) + logoView.center() + titleView.updateWithNewWidth(containerView.frame.width) + titleView.centerX(y: imageView.frame.maxY + 10) + firstHelp.centerX(y: titleView.frame.maxY + 10) + secondHelp.centerX(y: firstHelp.frame.maxY + 10) + thridHelp.centerX(y: secondHelp.frame.maxY + 10) + cancelButton.centerX(y: thridHelp.frame.maxY + 20) + + } +} + class AuthHeaderView : View { + fileprivate var isQrEnabled: Bool? = nil { + didSet { + updateLocalizationAndTheme(theme: theme) + } + } + + fileprivate var isLoading: Bool = false + + + private var progressView: ProgressIndicator? + + private let containerView = View(frame: NSMakeRect(0, 0, 300, 480)) + + fileprivate let proxyButton:ImageButton = ImageButton() + private let proxyConnecting: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 12, 12)) + + fileprivate var exportTokenView:ExportTokenView? fileprivate var arguments:LoginAuthViewArguments? fileprivate let loginView:LoginAuthInfoView = LoginAuthInfoView(frame: NSZeroRect) fileprivate var state: UnauthorizedAccountStateContents = .empty + fileprivate var qrTokenState: QRTokenState? = nil private let logo:ImageView = ImageView() private let header:TextView = TextView() private let desc:TextView = TextView() @@ -26,37 +181,50 @@ class AuthHeaderView : View { let intro:View = View() private let switchLanguage:TitleButton = TitleButton() fileprivate let nextButton:TitleButton = TitleButton() + fileprivate let backButton = TitleButton() + fileprivate let cancelButton = TitleButton() + + + private let animatedLogoView = ImageView() + fileprivate var needShowSuggestedButton: Bool = false required init(frame frameRect: NSRect) { super.init(frame: frameRect) - intro.setFrameSize(frameRect.size) - addSubview(intro) + intro.setFrameSize(containerView.frame.size) - let logoImage = #imageLiteral(resourceName: "Icon_LegacyIntro").precomposed() - self.logo.image = logoImage - self.logo.sizeToFit() - - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) intro.addSubview(logo) intro.addSubview(header) intro.addSubview(desc) - addSubview(loginView) + containerView.addSubview(intro) - addSubview(textHeaderView) + desc.userInteractionEnabled = false + desc.isSelectable = false + + header.userInteractionEnabled = false + header.isSelectable = false + + textHeaderView.userInteractionEnabled = false + textHeaderView.isSelectable = false + + containerView.addSubview(loginView) + + + containerView.addSubview(textHeaderView) textHeaderView.userInteractionEnabled = false textHeaderView.isSelected = false - nextButton.style = ControlStyle(font: NSFont.medium(.custom(16)), foregroundColor: .white, backgroundColor: NSColor(0x32A3E2), highlightColor: .white) - nextButton.set(background: .blueUI, for: .Highlight) - nextButton.set(text: tr(.loginNext), for: .Normal) - nextButton.sizeToFit() + nextButton.autohighlight = false + nextButton.style = ControlStyle(font: NSFont.medium(16.0), foregroundColor: .white, backgroundColor: NSColor(0x32A3E2), highlightColor: .white) + nextButton.set(text: L10n.loginNext, for: .Normal) + _ = nextButton.sizeToFit(thatFit: true) nextButton.setFrameSize(76, 36) nextButton.layer?.cornerRadius = 18 - + nextButton.disableActions() nextButton.set(handler: { [weak self] _ in if let strongSelf = self { switch strongSelf.state { @@ -67,23 +235,63 @@ class AuthHeaderView : View { strongSelf.arguments?.checkCode(strongSelf.loginView.code) case .passwordEntry: strongSelf.arguments?.checkPassword(strongSelf.loginView.password) + case .signUp: + strongSelf.loginView.trySignUp() default: break } } }, for: .Click) - addSubview(nextButton) - addSubview(switchLanguage) + containerView.addSubview(nextButton) + containerView.addSubview(switchLanguage) switchLanguage.isHidden = true switchLanguage.disableActions() switchLanguage.set(font: .medium(.title), for: .Normal) - switchLanguage.set(color: .blueUI, for: .Normal) switchLanguage.set(text: "Continue on English", for: .Normal) - switchLanguage.sizeToFit() + _ = switchLanguage.sizeToFit() + + addSubview(proxyButton) + proxyButton.addSubview(proxyConnecting) + containerView.addSubview(backButton) + + addSubview(containerView) + + + addSubview(cancelButton) + + needsLayout = true + } + + fileprivate func updateProxyPref(_ pref: ProxySettings, _ connection: ConnectionStatus, _ isForceHidden: Bool = true) { + proxyButton.isHidden = isForceHidden && pref.servers.isEmpty + switch connection { + case .connecting: + proxyConnecting.isHidden = pref.effectiveActiveServer == nil + proxyButton.set(image: pref.effectiveActiveServer == nil ? theme.icons.proxyEnable : theme.icons.proxyState, for: .Normal) + case .online: + proxyConnecting.isHidden = true + if pref.enabled { + proxyButton.set(image: theme.icons.proxyEnabled, for: .Normal) + } else { + proxyButton.set(image: theme.icons.proxyEnable, for: .Normal) + } + case .waitingForNetwork: + proxyConnecting.isHidden = pref.effectiveActiveServer == nil + proxyButton.set(image: pref.effectiveActiveServer == nil ? theme.icons.proxyEnable : theme.icons.proxyState, for: .Normal) + default: + proxyConnecting.isHidden = true + } + proxyConnecting.isEventLess = true + proxyConnecting.userInteractionEnabled = false + _ = proxyButton.sizeToFit() + proxyConnecting.centerX() + proxyConnecting.centerY(addition: -1) + needsLayout = true } + func hideSwitchButton() { needShowSuggestedButton = false switchLanguage.change(opacity: 0, removeOnCompletion: false) { [weak self] completed in @@ -94,7 +302,7 @@ class AuthHeaderView : View { func showLanguageButton(title: String, callback:@escaping()->Void) -> Void { needShowSuggestedButton = true switchLanguage.set(text: title, for: .Normal) - switchLanguage.sizeToFit() + _ = switchLanguage.sizeToFit() switchLanguage.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) switchLanguage.set(handler: { _ in callback() @@ -105,95 +313,342 @@ class AuthHeaderView : View { override func layout() { super.layout() - switchLanguage.centerX(y: frame.height - switchLanguage.frame.height - 35) + switchLanguage.centerX(y: containerView.frame.height - switchLanguage.frame.height - 20) + + logo.centerX(y: 0) header.centerX(y: logo.frame.maxY + 10) - desc.centerX(y: header.frame.maxY + 10) + desc.centerX(y: header.frame.maxY) + intro.setFrameSize(containerView.frame.width, desc.frame.maxY) + intro.centerX(y: 20) + loginView.setFrameSize(300, containerView.frame.height) + loginView.centerX(y: intro.frame.maxY) + nextButton.centerX(y: containerView.frame.height - nextButton.frame.height - 50) - logo.centerX() - - intro.centerX(y: 60) + proxyConnecting.centerX() + proxyConnecting.centerY(addition: -1) + proxyButton.setFrameOrigin(frame.width - proxyButton.frame.width - 15, 15) - intro.setFrameSize(frame.width, desc.frame.maxY) + containerView.center() - loginView.setFrameSize(400, frame.height) - - loginView.centerX(y: intro.frame.maxY + 60) + cancelButton.setFrameOrigin(15, 15) + + self.exportTokenView?.setFrameSize(NSMakeSize(300, 500)) + self.exportTokenView?.center() - nextButton.centerX(y: frame.height - nextButton.frame.height - 80) + self.progressView?.center() - updateState(state, animated: false) + updateState(state, qrTokenState: self.qrTokenState, isLoading: self.isLoading, animated: false) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - let headerLayout = TextViewLayout(NSAttributedString.initialize(string: appName, color: NSColor.text, font: NSFont.normal(.custom(30))), maximumNumberOfLines: 1) - headerLayout.measure(width: CGFloat.greatestFiniteMagnitude) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + switchLanguage.set(color: theme.colors.accent, for: .Normal) + + + self.animatedLogoView.image = theme.icons.login_cap + self.animatedLogoView.sizeToFit() + + self.logo.image = theme.icons.login_cap + self.logo.sizeToFit() + + let headerLayout = TextViewLayout(.initialize(string: appName, color: theme.colors.text, font: NSFont.normal(30.0)), maximumNumberOfLines: 1) + headerLayout.measure(width: .greatestFiniteMagnitude) header.update(headerLayout) + header.backgroundColor = theme.colors.background + desc.backgroundColor = theme.colors.background + textHeaderView.backgroundColor = theme.colors.background - let descLayout = TextViewLayout(NSAttributedString.initialize(string: tr(.loginWelcomeDescription), color: .grayText, font: NSFont.normal(.custom(16))), maximumNumberOfLines: 1) - descLayout.measure(width: CGFloat.greatestFiniteMagnitude) + let descLayout = TextViewLayout(.initialize(string: tr(L10n.loginWelcomeDescription), color: theme.colors.grayText, font: .normal(16.0)), maximumNumberOfLines: 2, alignment: .center) + descLayout.measure(width: 300) desc.update(descLayout) - nextButton.set(text: tr(.loginNext), for: .Normal) + if let isQrEnabled = self.isQrEnabled, isQrEnabled { + nextButton.set(text: L10n.loginQRLogin, for: .Normal) + _ = nextButton.sizeToFit(NSMakeSize(30, 0), NSMakeSize(0, 36), thatFit: true) + nextButton.style = ControlStyle(font: .medium(15.0), foregroundColor: theme.colors.accent, backgroundColor: .clear) + } else { + nextButton.set(text: L10n.loginNext, for: .Normal) + _ = nextButton.sizeToFit(NSMakeSize(30, 0), NSMakeSize(0, 36), thatFit: true) + nextButton.style = ControlStyle(font: .medium(15.0), foregroundColor: theme.colors.underSelectedColor, backgroundColor: theme.colors.accent) + } + + + proxyConnecting.progressColor = theme.colors.accentIcon + + + backButton.set(font: .medium(.header), for: .Normal) + backButton.set(color: theme.colors.accent, for: .Normal) + backButton.set(image: theme.icons.chatNavigationBack, for: .Normal) + backButton.set(text: L10n.navigationBack, for: .Normal) + _ = backButton.sizeToFit() + + cancelButton.set(font: .medium(.header), for: .Normal) + cancelButton.set(color: theme.colors.accent, for: .Normal) + cancelButton.set(text: L10n.navigationCancel, for: .Normal) + _ = cancelButton.sizeToFit() + + progressView?.progressColor = theme.colors.text + + + updateState(self.state, qrTokenState: self.qrTokenState, isLoading: self.isLoading, animated: false) needsLayout = true + + } + + fileprivate func nextButtonAsForQr(_ isQrEnabled: Bool?) -> Void { + self.isQrEnabled = isQrEnabled + self.updateLocalizationAndTheme(theme: theme) + } + + private func animateAndCancelQr() { + + guard let exportTokenView = self.exportTokenView else { + return + } + CATransaction.begin() + + self.containerView.isHidden = false + + addSubview(animatedLogoView) + + exportTokenView.logoView.isHidden = true + + let point = NSMakePoint(exportTokenView.frame.midX - 20, exportTokenView.frame.minY + exportTokenView.imageView.frame.height / 2 - 16) + + animatedLogoView.frame = NSMakeRect(point.x, point.y, 60, 60) + + exportTokenView.imageView.layer?.animateScaleSpring(from: 1, to: animatedLogoView.frame.width / exportTokenView.imageView.frame.width, duration: 0.4, removeOnCompletion: false, bounce: true) + + animatedLogoView.layer?.animateScaleX(from: 1, to: logo.frame.width / animatedLogoView.frame.width, duration: 0.4, timingFunction: .spring, removeOnCompletion: false) + animatedLogoView.layer?.animateScaleY(from: 1, to: logo.frame.height / animatedLogoView.frame.height, duration: 0.4, timingFunction: .spring, removeOnCompletion: false) + + self.logo.isHidden = true + + animatedLogoView.layer?.animatePosition(from: animatedLogoView.frame.origin, to: NSMakePoint((round(frame.width / 2) - logo.frame.width / 2), containerView.frame.minY + 20), duration: 0.4, timingFunction: .spring, removeOnCompletion: false, completion: { [weak self] _ in + self?.exportTokenView?.logoView.isHidden = false + self?.animatedLogoView.removeFromSuperview() + self?.animatedLogoView.layer?.removeAllAnimations() + self?.logo.isHidden = false + }) + + CATransaction.commit() + + self.arguments?.cancelQrAuth() + } + + private func animateAndApplyQr() { + addSubview(animatedLogoView) + + guard let exportTokenView = self.exportTokenView else { + return + } + + + exportTokenView.logoView.isHidden = true + + let point = NSMakePoint(frame.width / 2 - logo.frame.width / 2 + 1, containerView.frame.minY + 20) + + animatedLogoView.frame = NSMakeRect(point.x, point.y, 60, 60) + + exportTokenView.imageView.layer?.animateScaleSpring(from: animatedLogoView.frame.height / exportTokenView.imageView.frame.width, to: 1, duration: 0.4, removeOnCompletion: false, bounce: true) + + animatedLogoView.layer?.animateScaleX(from: logo.frame.width / animatedLogoView.frame.width, to: 1, duration: 0.4, timingFunction: .spring, removeOnCompletion: false) + animatedLogoView.layer?.animateScaleY(from: logo.frame.height / animatedLogoView.frame.height, to: 1, duration: 0.4, timingFunction: .spring, removeOnCompletion: false) + + self.logo.isHidden = true + + + animatedLogoView.layer?.animatePosition(from: point, to: NSMakePoint(exportTokenView.frame.midX - 30, exportTokenView.frame.minY + exportTokenView.imageView.frame.height / 2 - 26), duration: 0.4, timingFunction: .spring, removeOnCompletion: false, completion: { [weak self] _ in + self?.exportTokenView?.logoView.isHidden = false + self?.animatedLogoView.removeFromSuperview() + self?.logo.isHidden = false + self?.containerView.isHidden = true + self?.animatedLogoView.layer?.removeAllAnimations() + }) } - fileprivate func updateState(_ state:UnauthorizedAccountStateContents, animated: Bool) { + fileprivate func updateState(_ state:UnauthorizedAccountStateContents, qrTokenState: QRTokenState?, isLoading: Bool, animated: Bool) { + let prevIsLoading = self.isLoading + self.isLoading = isLoading self.state = state + self.qrTokenState = qrTokenState + self.loginView.updateState(self.state, animated: animated) + + if let qrTokenState = qrTokenState { + + self.logo.change(opacity: 0, animated: animated) + var firstTime: Bool = false + if self.exportTokenView == nil { + self.exportTokenView = ExportTokenView(frame: NSMakeRect(0, 0, 300, 500)) + self.addSubview(self.exportTokenView!) + self.exportTokenView?.center() + + self.exportTokenView?.cancelButton.set(handler: { [weak self] _ in + self?.animateAndCancelQr() + }, for: .Click) + + if animated { + self.exportTokenView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.35, timingFunction: .spring) + } + firstTime = true + } + + guard let exportTokenView = self.exportTokenView else { + return + } + exportTokenView.update(state: qrTokenState) + + if firstTime && animated { + self.animateAndApplyQr() + } else { + self.containerView.isHidden = true + } + + } else { + self.containerView.isHidden = false + if let exportTokenView = self.exportTokenView { + if animated { + self.exportTokenView = nil + exportTokenView.layer?.animateAlpha(from: 1, to: 0, duration: 0.35, timingFunction: .spring, removeOnCompletion: false, completion: { [weak exportTokenView] _ in + exportTokenView?.removeFromSuperview() + }) + } else { + exportTokenView.removeFromSuperview() + self.exportTokenView = nil + } + } + self.logo.change(opacity: 1, animated: animated) + } + + + + backButton.isHidden = true switch self.state { case .phoneEntry, .empty: nextButton.change(opacity: 1, animated: animated) textHeaderView.change(opacity: 0, animated: animated) - intro.change(pos: NSMakePoint(intro.frame.minX, 50), animated: animated) + intro.change(pos: NSMakePoint(intro.frame.minX, 20), animated: animated) intro.change(opacity: 1, animated: animated) - loginView.change(pos: NSMakePoint(loginView.frame.minX, intro.frame.maxY + 50), animated: animated) - textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, floorToScreenPixels((frame.height - textHeaderView.frame.height)/2)), animated: animated) + loginView.change(pos: NSMakePoint(loginView.frame.minX, intro.frame.maxY + 30), animated: animated) + textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, floorToScreenPixels(backingScaleFactor, (frame.height - textHeaderView.frame.height)/2)), animated: animated) switchLanguage.isHidden = !needShowSuggestedButton case .confirmationCodeEntry: nextButton.change(opacity: 1, animated: animated) - let headerLayout = TextViewLayout(.initialize(string: tr(.loginHeaderCode), color: .text, font: .normal(.custom(30)))) + let headerLayout = TextViewLayout(.initialize(string: L10n.loginHeaderCode, color: theme.colors.text, font: .normal(25))) headerLayout.measure(width: .greatestFiniteMagnitude) textHeaderView.update(headerLayout) textHeaderView.centerX() textHeaderView.change(opacity: 1, animated: animated) - textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, 90), animated: animated) + textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, 30), animated: animated) intro.change(pos: NSMakePoint(intro.frame.minX, -intro.frame.height), animated: animated) intro.change(opacity: 0, animated: animated) - loginView.change(pos: NSMakePoint(loginView.frame.minX, 160), animated: animated) + loginView.change(pos: NSMakePoint(loginView.frame.minX, textHeaderView.frame.maxY + 30), animated: animated) switchLanguage.isHidden = true - break case .passwordEntry: nextButton.change(opacity: 1, animated: animated) - let headerLayout = TextViewLayout(.initialize(string: tr(.loginHeaderPassword), color: .text, font: .normal(.custom(30)))) + let headerLayout = TextViewLayout(.initialize(string: L10n.loginHeaderPassword, color: theme.colors.text, font: .normal(25))) headerLayout.measure(width: .greatestFiniteMagnitude) textHeaderView.update(headerLayout) - textHeaderView.centerX(y: 90) + textHeaderView.centerX(y: 30) textHeaderView.change(opacity: 1, animated: animated) intro.change(pos: NSMakePoint(intro.frame.minX, -intro.frame.height), animated: animated) intro.change(opacity: 0, animated: animated) - loginView.change(pos: NSMakePoint(loginView.frame.minX, 160), animated: animated) + loginView.change(pos: NSMakePoint(loginView.frame.minX, textHeaderView.frame.maxY + 30), animated: animated) switchLanguage.isHidden = true - break case .signUp: - nextButton.change(opacity: 0, animated: animated) - let headerLayout = TextViewLayout(.initialize(string: tr(.loginHeaderSignUp), color: .text, font: .normal(.custom(30)))) + nextButton.change(opacity: 1, animated: animated) + let headerLayout = TextViewLayout(.initialize(string: L10n.loginHeaderSignUp, color: theme.colors.text, font: .normal(25))) headerLayout.measure(width: .greatestFiniteMagnitude) textHeaderView.update(headerLayout) textHeaderView.centerX() textHeaderView.change(opacity: 1, animated: animated) - textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, 90), animated: animated) + textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, 50), animated: animated) intro.change(pos: NSMakePoint(intro.frame.minX, -intro.frame.height), animated: animated) intro.change(opacity: 0, animated: animated) - loginView.change(pos: NSMakePoint(loginView.frame.minX, 160), animated: animated) + loginView.change(pos: NSMakePoint(loginView.frame.minX, textHeaderView.frame.maxY + 50), animated: animated) switchLanguage.isHidden = true + backButton.isHidden = false + backButton.setFrameOrigin(loginView.frame.minX, textHeaderView.frame.minY + floorToScreenPixels(backingScaleFactor, (textHeaderView.frame.height - backButton.frame.height) / 2)) + case .passwordRecovery: + break + case .awaitingAccountReset: + let headerLayout = TextViewLayout(.initialize(string: L10n.loginResetAccountText, color: theme.colors.text, font: .normal(25))) + headerLayout.measure(width: .greatestFiniteMagnitude) + intro.change(pos: NSMakePoint(intro.frame.minX, -intro.frame.height), animated: animated) + intro.change(opacity: 0, animated: animated) + switchLanguage.isHidden = true + + textHeaderView.update(headerLayout) + textHeaderView.centerX() + textHeaderView.change(pos: NSMakePoint(textHeaderView.frame.minX, 50), animated: animated) + textHeaderView.change(opacity: 1, animated: animated) + + loginView.change(pos: NSMakePoint(loginView.frame.minX, textHeaderView.frame.maxY + 20), animated: animated) + + nextButton.change(opacity: 0, animated: animated) + backButton.isHidden = false + backButton.setFrameOrigin(loginView.frame.minX, textHeaderView.frame.minY + floorToScreenPixels(backingScaleFactor, (textHeaderView.frame.height - backButton.frame.height) / 2)) + } + + if prevIsLoading != isLoading { + exportTokenView?.layer?.opacity = isLoading ? 0 : 1 + containerView.layer?.opacity = isLoading ? 0 : 1 + + if animated { + if isLoading { + if let exportTokenView = self.exportTokenView { + exportTokenView.layer?.animateAlpha(from: 1, to: 0, duration: 0.35, timingFunction: .spring) + exportTokenView.layer?.animateScaleSpring(from: 1, to: 0.2, duration: 0.35) + } else { + containerView.layer?.animateAlpha(from: 1, to: 0, duration: 0.35, timingFunction: .spring) + containerView.layer?.animateScaleSpring(from: 1, to: 0.2, duration: 0.35) + } + } else { + if let exportTokenView = self.exportTokenView { + exportTokenView.layer?.animateAlpha(from: 0, to: 1, duration: 0.35, timingFunction: .spring) + exportTokenView.layer?.animateScaleSpring(from: 0.2, to: 1, duration: 0.35) + } else { + containerView.layer?.animateAlpha(from: 0, to: 1, duration: 0.35, timingFunction: .spring) + containerView.layer?.animateScaleSpring(from: 0.2, to: 1, duration: 0.35) + } + } + } + + if isLoading { + if self.progressView == nil { + let progressView = ProgressIndicator(frame: NSMakeRect(0, 0, 40, 40)) + self.progressView = progressView + + progressView.progressColor = theme.colors.text + addSubview(progressView) + } + + self.progressView?.center() + + if animated { + progressView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.35, timingFunction: .spring) + } + } else { + if let progressView = self.progressView { + self.progressView = nil + if animated { + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.35, timingFunction: .spring, removeOnCompletion: false, completion: { [weak progressView] _ in + progressView?.removeFromSuperview() + }) + } else { + progressView.removeFromSuperview() + } + } + } } } @@ -204,19 +659,51 @@ class AuthHeaderView : View { class AuthController : GenericViewController { - private let navigation:NavigationViewController private let disposable:MetaDisposable = MetaDisposable() private let actionDisposable = MetaDisposable() + private let proxyDisposable = DisposableSet() private let suggestedLanguageDisposable = MetaDisposable() private let localizationDisposable = MetaDisposable() + private let exportTokenDisposable = MetaDisposable() + private let tokenEventsDisposable = MetaDisposable() + private let configurationDisposable = MetaDisposable() private var account:UnauthorizedAccount - init(_ account:UnauthorizedAccount) { + private let sharedContext: SharedAccountContext + #if !APP_STORE + private let updateController: UpdateTabController + #endif + + private var state: UnauthorizedAccountStateContents = .empty + private var qrType: QRLoginType = .disabled + private var qrTokenState: (state: QRTokenState?, animated: Bool) = (state: nil, animated: false) { + didSet { + self.genericView.updateState(self.state, qrTokenState: self.qrTokenState.state, isLoading: self.isLoading.value, animated: self.qrTokenState.animated) + } + } + private var isLoading: (value: Bool, update: Bool) = (value: true, update: true) { + didSet { + if isLoading.update { + self.genericView.updateState(self.state, qrTokenState: self.qrTokenState.state, isLoading: isLoading.value, animated: !isFirst) + isFirst = false + } + } + } + + + + private let otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]) + + init(_ account:UnauthorizedAccount, sharedContext: SharedAccountContext, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])) { self.account = account - self.navigation = NavigationViewController(ViewController()) + self.sharedContext = sharedContext + self.otherAccountPhoneNumbers = otherAccountPhoneNumbers + #if !APP_STORE + updateController = UpdateTabController(sharedContext) + #endif super.init() - self.disposable.set((account.postbox.stateView() |> deliverOnMainQueue).start(next: { [weak self] view in - self?.updateState(state: view.state ?? UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .empty)) + self.disposable.set(combineLatest(account.postbox.stateView() |> deliverOnMainQueue, appearanceSignal).start(next: { [weak self] view, _ in + self?.updateState(state: view.state ?? UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) })) bar = .init(height: 0) } @@ -225,9 +712,11 @@ class AuthController : GenericViewController { func updateState(state: PostboxCoding?) { if let state = state as? UnauthorizedAccountState { - self.genericView.updateState(state.contents, animated: !isFirst) + self.state = state.contents + self.genericView.updateState(self.state, qrTokenState: self.qrTokenState.state, isLoading: self.isLoading.value, animated: !isFirst) } isFirst = false + readyOnce() } @@ -235,53 +724,193 @@ class AuthController : GenericViewController { disposable.dispose() actionDisposable.dispose() suggestedLanguageDisposable.dispose() + proxyDisposable.dispose() + exportTokenDisposable.dispose() + configurationDisposable.dispose() } + override func returnKeyAction() -> KeyHandlerResult { + return .invokeNext + } + + override func escapeKeyAction() -> KeyHandlerResult { + if !self.otherAccountPhoneNumbers.1.isEmpty { + _ = sharedContext.accountManager.transaction({ transaction in + transaction.removeAuth() + }).start() + } + return .invoked + } override func firstResponder() -> NSResponder? { - return genericView.loginView.firstResponder() + return genericView.exportTokenView ?? genericView.loginView.firstResponder() } override var canBecomeResponder: Bool { return true } + private func openProxySettings() { + + var pushController:((ViewController)->Void)? = nil + + let controller = proxyListController(accountManager: sharedContext.accountManager, network: account.network, showUseCalls: false, pushController: { controller in + pushController?(controller) + }) + let navigation:NavigationViewController = NavigationViewController(controller, mainWindow) + navigation._frameRect = NSMakeRect(0, 0, 350, 440) + navigation.readyOnce() + + pushController = { [weak navigation] controller in + navigation?.push(controller) + } + + showModal(with: navigation, for: mainWindow) + + } + override func viewDidLoad() { super.viewDidLoad() - + #if !APP_STORE + addSubview(updateController.view) + + updateController.frame = NSMakeRect(0, frame.height - 60, frame.width, 60) + #endif + + var arguments: LoginAuthViewArguments? + + let again:(String)-> Void = { number in + arguments?.sendCode(number) + } + + + let sharedContext = self.sharedContext + var forceHide = true + + + + var settings:(ProxySettings, ConnectionStatus)? = nil + + + let updateProxyUI:()->Void = { [weak self] in + if let settings = settings { + self?.genericView.updateProxyPref(settings.0, settings.1, forceHide) + } + } + + let openProxySettings:()->Void = { [weak self] in + self?.openProxySettings() + forceHide = false + updateProxyUI() + } + + proxyDisposable.add(combineLatest(proxySettings(accountManager: sharedContext.accountManager) |> deliverOnMainQueue, account.network.connectionStatus |> deliverOnMainQueue).start(next: { pref, connection in + settings = (pref, connection) + updateProxyUI() + })) + + + + let disposable = MetaDisposable() + proxyDisposable.add(disposable) + + + let defaultProxyVisibles: [String] = ["RU"] + + if defaultProxyVisibles.firstIndex(where: {$0 == Locale.current.regionCode}) != nil { + forceHide = false + updateProxyUI() + } - let arguments = LoginAuthViewArguments(sendCode: { [weak self] phoneNumber in - if let strongSelf = self { - self?.actionDisposable.set((showModalProgress(signal: sendAuthorizationCode(account: strongSelf.account, phoneNumber: phoneNumber, apiId: API_ID, apiHash: API_HASH) - |> map {Optional($0)} - |> deliverOnMainQueue, for: mainWindow) - |> filter({$0 != nil}) |> map {$0!} |> deliverOnMainQueue).start(next: { [weak strongSelf] account in - strongSelf?.account = account - }, error: { [weak self] error in - self?.genericView.loginView.updatePhoneError(error) - })) - _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.account.postbox, languageCode: Locale.current.languageCode ?? "en").start() + + let resetState:()->Void = { [weak self] in + guard let `self` = self else {return} + _ = resetAuthorizationState(account: self.account, to: .empty).start() + } + + genericView.proxyButton.set(handler: { _ in + if let _ = settings { + openProxySettings() } - },resendCode: { [weak self] in + }, for: .Click) + + arguments = LoginAuthViewArguments(sendCode: { [weak self] phoneNumber in if let strongSelf = self { - _ = resendAuthorizationCode(account: strongSelf.account).start() + + if let isQrEnabled = strongSelf.genericView.isQrEnabled, isQrEnabled { + strongSelf.refreshQrToken(true) + } else { + let logInNumber = formatPhoneNumber(phoneNumber) + for (number, accountId, isTestingEnvironment) in strongSelf.otherAccountPhoneNumbers.1 { + if isTestingEnvironment == strongSelf.account.testingEnvironment && formatPhoneNumber(number) == logInNumber { + confirm(for: mainWindow, information: L10n.loginPhoneNumberAlreadyAuthorized, okTitle: L10n.modalOK, cancelTitle: "", thridTitle: L10n.loginPhoneNumberAlreadyAuthorizedSwitch, successHandler: { result in + switch result { + case .thrid: + _ = (sharedContext.accountManager.transaction({ transaction in + transaction.removeAuth() + }) |> deliverOnMainQueue).start(completed: { + sharedContext.switchToAccount(id: accountId, action: nil) + }) + default: + break + } + }) + return + } + } + + + self?.actionDisposable.set((showModalProgress(signal: sendAuthorizationCode(accountManager: sharedContext.accountManager, account: strongSelf.account, phoneNumber: phoneNumber, apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, syncContacts: false) + |> map {Optional($0)} + |> mapError {Optional($0)} + |> timeout(20, queue: Queue.mainQueue(), alternate: .fail(nil)) + |> deliverOnMainQueue, for: mainWindow) + |> filter({$0 != nil}) |> map {$0!} |> deliverOnMainQueue).start(next: { [weak strongSelf] account in + strongSelf?.account = account + }, error: { [weak self] error in + if let error = error { + self?.genericView.loginView.updatePhoneError(error) + } else { + confirm(for: mainWindow, header: L10n.loginConnectionErrorHeader, information: L10n.loginConnectionErrorInfo, okTitle: L10n.loginConnectionErrorTryAgain, thridTitle: L10n.loginConnectionErrorUseProxy, successHandler: { result in + switch result { + case .basic: + again(phoneNumber) + case .thrid: + openProxySettings() + } + }) + } + })) + _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.account.postbox, languageCode: Locale.current.languageCode ?? "en").start() + } } - }, editPhone: { [weak self] in + },resendCode: { [weak self] in if let strongSelf = self { - _ = resetAuthorizationState(account: strongSelf.account, to: .empty).start() + _ = resendAuthorizationCode(account: strongSelf.account).start() } + }, editPhone: { + resetState() }, checkCode: { [weak self] code in if let strongSelf = self { - _ = (authorizeWithCode(account: strongSelf.account, code: code) |> deliverOnMainQueue ).start(error: { [weak self] error in + _ = (authorizeWithCode(accountManager: sharedContext.accountManager, account: strongSelf.account, code: code, termsOfService: nil) |> deliverOnMainQueue ).start(next: { [weak strongSelf] value in + if let strongSelf = strongSelf { + switch value { + case let .signUp(data): + _ = beginSignUp(account: strongSelf.account, data: data).start() + default: + break + } + } + }, error: { [weak self] error in self?.genericView.loginView.updateCodeError(error) }) } }, checkPassword: { [weak self] password in if let strongSelf = self { - _ = (authorizeWithPassword(account: strongSelf.account, password: password) + _ = (authorizeWithPassword(accountManager: sharedContext.accountManager, account: strongSelf.account, password: password, syncContacts: false) |> map { () -> AuthorizationPasswordVerificationError? in return nil } @@ -295,31 +924,198 @@ class AuthController : GenericViewController { }) } + }, requestPasswordRecovery: { [weak self] f in + guard let `self` = self else {return} + _ = showModalProgress(signal: requestPasswordRecovery(account: self.account) |> deliverOnMainQueue, for: mainWindow).start(next: { [weak self] option in + guard let `self` = self else {return} + f(option) + switch option { + case let .email(pattern): + showModal(with: ForgotUnauthorizedPasswordController(accountManager: sharedContext.accountManager, account: self.account, emailPattern: pattern), for: mainWindow) + default: + break + } + }, error: { error in + var bp:Int = 0 + bp += 1 + }) + }, resetAccount: { [weak self] in + guard let `self` = self else {return} + confirm(for: mainWindow, information: L10n.loginResetAccountDescription, okTitle: L10n.loginResetAccount, successHandler: { _ in + _ = showModalProgress(signal: performAccountReset(account: self.account) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + alert(for: mainWindow, info: L10n.unknownError) + }) + }) + }, signUp: { [weak self] firstName, lastName, photo in + guard let `self` = self else {return} + _ = showModalProgress(signal: signUpWithName(accountManager: sharedContext.accountManager, account: self.account, firstName: firstName, lastName: lastName, avatarData: photo != nil ? try? Data(contentsOf: photo!) : nil, avatarVideo: nil, videoStartTimestamp: nil) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + let text: String + switch error { + case .limitExceeded: + text = L10n.loginFloodWait + case .codeExpired: + text = L10n.phoneCodeExpired + case .invalidFirstName: + text = L10n.loginInvalidFirstNameError + case .invalidLastName: + text = L10n.loginInvalidLastNameError + case .generic: + text = L10n.unknownError + } + alert(for: mainWindow, info: text) + }) + }, cancelQrAuth: { [weak self] in + self?.cancelQrToken() + }, updatePhoneNumberField: { [weak self] updated in + if self?.qrType != .disabled { + self?.genericView.nextButtonAsForQr(updated.isEmpty) + } }) // genericView.loginView.arguments = arguments genericView.arguments = arguments + genericView.backButton.set(handler: { _ in + resetState() + }, for: .Click) - suggestedLanguageDisposable.set((currentlySuggestedLocalization(network: account.network, extractKeys: ["Login.ContinueOnLanguage"]) |> deliverOnMainQueue).start(next: { [weak self] info in - if let strongSelf = self, let info = info, info.languageCode != appCurrentLanguage.languageCode { - - strongSelf.genericView.showLanguageButton(title: info.localizedKey("Login.ContinueOnLanguage"), callback: { [weak strongSelf] in - if let strongSelf = strongSelf { - strongSelf.genericView.hideSwitchButton() - _ = showModalProgress(signal: downoadAndApplyLocalization(postbox: strongSelf.account.postbox, network: strongSelf.account.network, languageCode: info.languageCode), for: mainWindow).start() - } - }) - } - })) + genericView.cancelButton.isHidden = otherAccountPhoneNumbers.1.isEmpty + + genericView.cancelButton.set(handler: { _ in + _ = sharedContext.accountManager.transaction({ transaction in + transaction.removeAuth() + }).start() + }, for: .Click) + + if otherAccountPhoneNumbers.1.isEmpty { + suggestedLanguageDisposable.set((currentlySuggestedLocalization(network: account.network, extractKeys: ["Login.ContinueOnLanguage"]) |> deliverOnMainQueue).start(next: { [weak self] info in + if let strongSelf = self, let info = info, info.languageCode != appCurrentLanguage.baseLanguageCode { + + strongSelf.genericView.showLanguageButton(title: info.localizedKey("Login.ContinueOnLanguage"), callback: { [weak strongSelf] in + if let strongSelf = strongSelf { + strongSelf.genericView.hideSwitchButton() + _ = showModalProgress(signal: downloadAndApplyLocalization(accountManager: sharedContext.accountManager, postbox: strongSelf.account.postbox, network: strongSelf.account.network, languageCode: info.languageCode), for: mainWindow).start() + } + }) + } + })) + } + localizationDisposable.set(appearanceSignal.start(next: { [weak self] _ in - self?.updateLocalizationAndTheme() + self?.updateLocalizationAndTheme(theme: theme) })) + self.tokenEventsDisposable.set((self.account.updateLoginTokenEvents |> deliverOnMainQueue).start(next: { [weak self] _ in + self?.refreshQrToken() + })) + + + configurationDisposable.set((unauthorizedConfiguration(accountManager: self.sharedContext.accountManager) |> take(1) |> castError(Void.self) |> timeout(25.0, queue: .mainQueue(), alternate: .fail(Void())) |> deliverOnMainQueue).start(next: { [weak self] value in + + self?.qrType = value.qr + self?.genericView.isQrEnabled = value.qr != .disabled + switch value.qr { + case .disabled: + self?.isLoading = (value: false, update: true) + case .secondary: + self?.isLoading = (value: false, update: true) + case .primary: + self?.refreshQrToken() + } + + }, error: { [weak self] in + self?.qrType = .disabled + self?.isLoading = (value: false, update: true) + forceHide = false + updateProxyUI() + })) + + + } + + private func cancelQrToken() { + self.exportTokenDisposable.set(nil) + self.tokenEventsDisposable.set(nil) + self.qrTokenState = (state: nil, animated: true) } + private func refreshQrToken(_ showProgress: Bool = false) { + + let sharedContext = self.sharedContext + let account = self.account + + var tokenSignal: Signal = sharedContext.activeAccounts |> castError(ExportAuthTransferTokenError.self) |> take(1) |> mapToSignal { accounts in + return exportAuthTransferToken(accountManager: sharedContext.accountManager, account: account, otherAccountUserIds: accounts.accounts.map { $0.1.peerId.id }, syncContacts: false) + } + + if showProgress { + tokenSignal = showModalProgress(signal: tokenSignal |> take(1), for: mainWindow) + } + + self.exportTokenDisposable.set((tokenSignal + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let strongSelf = self else { + return + } + + switch result { + case let .displayToken(token): + var tokenString = token.value.base64EncodedString() + tokenString = tokenString.replacingOccurrences(of: "+", with: "-") + tokenString = tokenString.replacingOccurrences(of: "/", with: "_") + let urlString = "tg://login?token=\(tokenString)" + let _ = (qrCode(string: urlString, color: theme.colors.text, backgroundColor: theme.colors.background, icon: .custom(theme.icons.login_qr_empty_cap)) + |> deliverOnMainQueue).start(next: { _, generate in + guard let strongSelf = self else { + return + } + + let context = generate(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: 280, height: 280), boundingSize: CGSize(width: 280, height: 280), intrinsicInsets: NSEdgeInsets(), scale: 2.0)) + if let image = context?.generateImage() { + strongSelf.qrTokenState = (state: .qr(image), animated: !strongSelf.isLoading.value) + strongSelf.isLoading = (value: false, update: true) + } + }) + + let timestamp = Int32(Date().timeIntervalSince1970) + let timeout = max(5, token.validUntil - timestamp) + strongSelf.exportTokenDisposable.set((Signal.complete() + |> delay(Double(timeout), queue: .mainQueue())).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.refreshQrToken() + })) + case let .passwordRequested(account): + strongSelf.account = account + strongSelf.genericView.isQrEnabled = false + strongSelf.exportTokenDisposable.set(nil) + strongSelf.tokenEventsDisposable.set(nil) + strongSelf.qrTokenState = (state: nil, animated: true) + case let .changeAccountAndRetry(account): + strongSelf.exportTokenDisposable.set(nil) + strongSelf.account = account + strongSelf.tokenEventsDisposable.set((account.updateLoginTokenEvents + |> deliverOnMainQueue).start(next: { _ in + self?.refreshQrToken() + })) + strongSelf.refreshQrToken() + strongSelf.qrTokenState = (state: nil, animated: true) + case .loggedIn: + strongSelf.exportTokenDisposable.set(nil) + strongSelf.qrTokenState = (state: nil, animated: true) + } + })) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + #if !APP_STORE + updateController.updateLocalizationAndTheme(theme: theme) + #endif + } } diff --git a/Telegram-Mac/AutoNightThemePreferences.swift b/Telegram-Mac/AutoNightThemePreferences.swift new file mode 100644 index 0000000000..e78c13f322 --- /dev/null +++ b/Telegram-Mac/AutoNightThemePreferences.swift @@ -0,0 +1,138 @@ +// +// AutoNightThemePreferences.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/08/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit +import TGUIKit + + + +enum AutoNightSchedule : Equatable { + case sunrise(latitude: Double, longitude: Double, localizedGeo: String?) + case timeSensitive(from: Int32, to: Int32) + + fileprivate var typeValue: Int32 { + switch self { + case .sunrise: + return 1 + case .timeSensitive: + return 2 + } + } +} + +struct AutoNightThemePreferences: PreferencesEntry, Equatable { + let schedule: AutoNightSchedule? + let systemBased: Bool + let theme: DefaultTheme + static var defaultSettings: AutoNightThemePreferences { + return AutoNightThemePreferences() + } + + init() { + self.schedule = nil + self.theme = DefaultTheme(local: .nightAccent, cloud: nil) + if #available(OSX 10.14, *) { + self.systemBased = true + } else { + self.systemBased = false + } + } + + init(schedule: AutoNightSchedule?, theme: DefaultTheme, systemBased: Bool) { + self.schedule = schedule + self.theme = theme + self.systemBased = systemBased + } + + init(decoder: PostboxDecoder) { + let type = decoder.decodeInt32ForKey("t", orElse: 0) + + let defaultTheme = DefaultTheme(local: .nightAccent, cloud: nil) + + self.theme = decoder.decodeObjectForKey("defTheme", decoder: { DefaultTheme(decoder: $0) }) as? DefaultTheme ?? defaultTheme + self.systemBased = decoder.decodeBoolForKey("sb", orElse: false) + switch type { + case 1: + let latitude = decoder.decodeDoubleForKey("la", orElse: 0) + let longitude = decoder.decodeDoubleForKey("lo", orElse: 0) + let localizedGeo = decoder.decodeOptionalStringForKey("lg") + self.schedule = .sunrise(latitude: latitude, longitude: longitude, localizedGeo: localizedGeo) + case 2: + let from = decoder.decodeInt32ForKey("from", orElse: 22) + let to = decoder.decodeInt32ForKey("to", orElse: 9) + self.schedule = .timeSensitive(from: from, to: to) + default: + self.schedule = nil + } + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.theme, forKey: "defTheme") + encoder.encodeBool(self.systemBased, forKey: "sb") + if let schedule = schedule { + encoder.encodeInt32(schedule.typeValue, forKey: "t") + switch schedule { + case let .sunrise(location): + encoder.encodeDouble(location.latitude, forKey: "la") + encoder.encodeDouble(location.longitude, forKey: "lo") + if let localizedGeo = location.localizedGeo { + encoder.encodeString(localizedGeo, forKey: "lg") + } else { + encoder.encodeNil(forKey: "lg") + } + case let .timeSensitive(from, to): + encoder.encodeInt32(from, forKey: "from") + encoder.encodeInt32(to, forKey: "to") + } + } else { + encoder.encodeInt32(0, forKey: "t") + } + } + + + func withUpdatedSchedule(_ schedule: AutoNightSchedule?) -> AutoNightThemePreferences { + return AutoNightThemePreferences(schedule: schedule, theme: self.theme, systemBased: self.systemBased) + } + + func withUpdatedTheme(_ theme: DefaultTheme) -> AutoNightThemePreferences { + return AutoNightThemePreferences(schedule: self.schedule, theme: theme, systemBased: self.systemBased) + } + func withUpdatedSystemBased(_ systemBased: Bool) -> AutoNightThemePreferences { + return AutoNightThemePreferences(schedule: self.schedule, theme: self.theme, systemBased: systemBased) + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? AutoNightThemePreferences { + return self == to + } else { + return false + } + } + +} + + +func autoNightSettings(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.autoNight]) |> map { $0.entries[ApplicationSharedPreferencesKeys.autoNight] as? AutoNightThemePreferences ?? AutoNightThemePreferences.defaultSettings } +} + +func updateAutoNightSettingsInteractively(accountManager: AccountManager, _ f: @escaping (AutoNightThemePreferences) -> AutoNightThemePreferences) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.autoNight, { entry in + let currentSettings: AutoNightThemePreferences + if let entry = entry as? AutoNightThemePreferences { + currentSettings = entry + } else { + currentSettings = AutoNightThemePreferences.defaultSettings + } + return f(currentSettings) + }) + } +} diff --git a/Telegram-Mac/AutoNightViewController.swift b/Telegram-Mac/AutoNightViewController.swift new file mode 100644 index 0000000000..beb608438c --- /dev/null +++ b/Telegram-Mac/AutoNightViewController.swift @@ -0,0 +1,333 @@ +// +// AutoNightViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/08/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +fileprivate let _id_disabled = InputDataIdentifier("disabled") +fileprivate let _id_scheduled = InputDataIdentifier("enabled") + +fileprivate let _id_from = InputDataIdentifier("from") +fileprivate let _id_to = InputDataIdentifier("to") + +fileprivate let _id_sunrise = InputDataIdentifier("sunrise") + +fileprivate let _id_night_blue = InputDataIdentifier(nightAccentPalette.name) +fileprivate let _id_dark = InputDataIdentifier(darkPalette.name) +fileprivate let _id_update = InputDataIdentifier("update") + +private let _id_system_based = InputDataIdentifier("_id_system_based") +private let _id_list = InputDataIdentifier("_id_list") + +private final class AutoNightThemeArguments { + let context: AccountContext + let selectTheme:(InstallThemeSource)->Void + let disable:()->Void + let scheduled:()->Void + let sunrise:(Bool)->Void + let systemBased:()->Void + let selectTimeFrom:(Int32)->Void + let selectTimeTo:(Int32)->Void + let updateLocation:()->Void + init(context: AccountContext, selectTheme: @escaping(InstallThemeSource)->Void, disable:@escaping()->Void, scheduled: @escaping()->Void, sunrise:@escaping(Bool)->Void, systemBased: @escaping()->Void, selectTimeFrom: @escaping(Int32)->Void, selectTimeTo: @escaping(Int32)->Void, updateLocation:@escaping()->Void) { + self.context = context + self.disable = disable + self.selectTheme = selectTheme + self.scheduled = scheduled + self.sunrise = sunrise + self.selectTimeFrom = selectTimeFrom + self.selectTimeTo = selectTimeTo + self.systemBased = systemBased + self.updateLocation = updateLocation + } +} + +private func autoNightEntries(appearance: Appearance, settings: AutoNightThemePreferences, cloudThemes: [TelegramTheme], arguments: AutoNightThemeArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_disabled, data: InputDataGeneralData(name: L10n.autoNightSettingsDisabled, color: theme.colors.text, icon: nil, type: .selectable(settings.schedule == nil && !settings.systemBased), viewType: .firstItem, action: arguments.disable))) + index += 1 + + + + if #available(OSX 10.14, *) { + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_scheduled, data: InputDataGeneralData(name: L10n.autoNightSettingsScheduled, color: theme.colors.text, icon: nil, type: .selectable(settings.schedule != nil), viewType: .innerItem, action: arguments.scheduled))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_system_based, data: InputDataGeneralData(name: L10n.autoNightSettingsSystemBased, color: theme.colors.text, icon: nil, type: .selectable(settings.systemBased), viewType: .lastItem, action: arguments.systemBased))) + index += 1 + if settings.systemBased { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.autoNightSettingsSystemBasedDesc), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + } + } else { + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_scheduled, data: InputDataGeneralData(name: L10n.autoNightSettingsScheduled, color: theme.colors.text, icon: nil, type: .selectable(settings.schedule != nil), viewType: .lastItem, action: arguments.scheduled))) + index += 1 + } + + + + + if let schedule = settings.schedule { + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let sunriseEnabled: Bool + switch schedule { + case .sunrise: + sunriseEnabled = true + default: + sunriseEnabled = false + } + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_sunrise, data: InputDataGeneralData(name: L10n.autoNightSettingsSunsetAndSunrise, color: theme.colors.text, icon: nil, type: .switchable(sunriseEnabled), viewType: .firstItem, action: { + arguments.sunrise(!sunriseEnabled) + }))) + index += 1 + + switch schedule { + case let .sunrise(latitude, longitude, localizedGeo): + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_update, data: InputDataGeneralData(name: L10n.autoNightSettingsUpdateLocation, color: theme.colors.accent, icon: nil, type: .context(localizedGeo ?? ""), viewType: .lastItem, action: arguments.updateLocation))) + index += 1 + + let sunriseSet = EDSunriseSet(date: Date(), timezone: NSTimeZone.local, latitude: latitude, longitude: longitude) + if let sunriseSet = sunriseSet { + let formatter = DateFormatter() + formatter.timeStyle = .short + formatter.timeZone = NSTimeZone.local + formatter.dateStyle = .none + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.autoNightSettingsSunriseDesc(latitude == 0 ? "N/A" : formatter.string(from: sunriseSet.sunset), longitude == 0 ? "N/A" : formatter.string(from: sunriseSet.sunrise))), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + } else { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.autoNightSettingsSunriseDescNA), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + } + + case let .timeSensitive(from, to): + + func items(from:Int32, to:Int32, isTo: Bool) -> [SPopoverItem] { + var items:[SPopoverItem] = [] + for i in from ..< to { + items.append(SPopoverItem(i < 10 ? "0\(i):00" : "\(i):00", { + if isTo { + arguments.selectTimeTo(i) + } else { + arguments.selectTimeFrom(i) + } + })) + } + return items + } + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_from, data: InputDataGeneralData(name: L10n.autoNightSettingsFrom, color: theme.colors.text, icon: nil, type: .contextSelector(from < 10 ? "0\(from):00" : "\(from):00", items(from: 0, to: 24, isTo: false)), viewType: .innerItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_to, data: InputDataGeneralData(name: L10n.autoNightSettingsTo, color: theme.colors.text, icon: nil, type: .contextSelector(to < 10 ? "0\(to):00" : "\(to):00", items(from: 0, to: 24, isTo: true)), viewType: .lastItem, action: nil))) + index += 1 + } + } + + if settings.schedule != nil || settings.systemBased { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.autoNightSettingsPreferredTheme), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + var cloudThemes = Array(cloudThemes.filter { cloud in + return cloud.file != nil + }.reversed()) + + let selected: ThemeSource + if let theme = settings.theme.cloud { + selected = .cloud(theme.cloud) + } else { + selected = .local(settings.theme.local.palette, nil) + } + + if let cloud = settings.theme.cloud?.cloud { + if !cloudThemes.contains(where: {$0.id == cloud.id}) { + cloudThemes.append(cloud) + } + } + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_list, equatable: InputDataEquatable(settings), item: { initialSize, stableId in + return ThemeListRowItem(initialSize, stableId: stableId, context: arguments.context, theme: appearance.presentation, selected: selected, local: [LocalPaletteWithReference(palette: nightAccentPalette, cloud: nil), LocalPaletteWithReference(palette: systemPalette, cloud: nil)], cloudThemes: cloudThemes, viewType: .singleItem, togglePalette: arguments.selectTheme, menuItems: { source in + return [] + }) + })) + index += 1 + + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + + +/* + if location.latitude == 0 && location.longitude == 0 { + return requestUserLocation() + |> map {Optional($0)} + |> `catch` { error -> Signal in + return .single(nil) + } |> mapToSignal { value in + if let value = value { + return updateAutoNightSettingsInteractively(accountManager: sharedContext.accountManager, { pref -> AutoNightThemePreferences in + switch value { + case let .success(location): + return pref.withUpdatedSchedule(.sunrise(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)) + } + }) |> map { set in + return autoNightEntries(set) + } + } else { + return .single(autoNightEntries(settings)) + } + } + } + */ + +func AutoNightSettingsController(context: AccountContext) -> InputDataController { + + let updateDisposable = MetaDisposable() + let updateLocationDisposable = MetaDisposable() + + + let updateLocation:(Bool)->Void = { inBackground in + var signal: Signal<(Double, Double, String?), UserLocationError> = requestUserLocation() |> take(1) |> mapToSignal { value in + switch value { + case let .success(location): + return reverseGeocodeLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) + |> mapError { _ in return UserLocationError.denied } |> map { geocode in + return (location.coordinate.latitude, location.coordinate.longitude, geocode?.city) + } + } + } + if !inBackground { + signal = showModalProgress(signal: signal, for: context.window) + } + updateLocationDisposable.set(signal.start(next: { location in + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedSchedule(.sunrise(latitude: location.0, longitude: location.1, localizedGeo: location.2)) + }).start()) + }, error: { error in + if !inBackground { + alert(for: context.window, info: L10n.autoNightSettingsUpdateLocationError) + } + })) + } + + let arguments = AutoNightThemeArguments(context: context, selectTheme: { source in + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in + var settings = settings + switch source { + case let .local(palette): + settings = settings.withUpdatedTheme(DefaultTheme(local: palette.parent, cloud: nil)) + case let .cloud(theme, cachedData): + if let cached = cachedData { + settings = settings.withUpdatedTheme(DefaultTheme(local: cached.palette.parent, cloud: DefaultCloudTheme(cloud: theme, palette: cached.palette, wallpaper: AssociatedWallpaper(cloud: cached.cloudWallpaper, wallpaper: cached.wallpaper)))) + } + } + return settings + }).start()) + }, disable: { + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedSchedule(nil).withUpdatedSystemBased(false) + }).start()) + }, scheduled: { + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedSchedule(.timeSensitive(from: 22, to: 9)).withUpdatedSystemBased(false) + }).start()) + }, sunrise: { enable in + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + if enable { + return current.withUpdatedSchedule(.sunrise(latitude: 0, longitude: 0, localizedGeo: nil)) + } else { + return current.withUpdatedSchedule(.timeSensitive(from: 22, to: 9)) + } + }).start()) + + if enable { + updateLocation(true) + } + }, systemBased: { + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedSchedule(nil).withUpdatedSystemBased(true) + }).start()) + }, selectTimeFrom: { value in + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + if let schedule = current.schedule { + switch schedule { + case .sunrise: + return current + case let .timeSensitive(interval): + return current.withUpdatedSchedule(.timeSensitive(from: value, to: interval.to)) + } + } + return current + + }).start()) + }, selectTimeTo: { value in + updateDisposable.set(updateAutoNightSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + if let schedule = current.schedule { + switch schedule { + case .sunrise: + return current + case let .timeSensitive(interval): + return current.withUpdatedSchedule(.timeSensitive(from: interval.from, to: value)) + } + } + return current + }).start()) + }, updateLocation: { + updateLocation(false) + }) + + + + + + + let autoNight = autoNightSettings(accountManager: context.sharedContext.accountManager) + let cloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + + let signal: Signal<[InputDataEntry], NoError> = combineLatest(queue: prepareQueue, appearanceSignal, autoNight, cloudThemes) |> map { + autoNightEntries(appearance: $0, settings: $1, cloudThemes: $2, arguments: arguments) + } + + return InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0, animated: false) }, + title: L10n.autoNightSettingsTitle, + afterDisappear: { + updateDisposable.dispose() + updateLocationDisposable.dispose() + }, + removeAfterDisappear: true, + hasDone: false, + identifier: "auto-night") +} + + + diff --git a/Telegram-Mac/AutomaticMediaDownloadCategoryPeers.swift b/Telegram-Mac/AutomaticMediaDownloadCategoryPeers.swift index 9b4a7153bd..33b8dd4924 100644 --- a/Telegram-Mac/AutomaticMediaDownloadCategoryPeers.swift +++ b/Telegram-Mac/AutomaticMediaDownloadCategoryPeers.swift @@ -7,41 +7,66 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit public struct AutomaticMediaDownloadCategoryPeers: PostboxCoding, Equatable { public let privateChats: Bool - public let groupsAndChannels: Bool - - public init(privateChats: Bool, groupsAndChannels: Bool) { + public let groupChats: Bool + public let channels: Bool + public let fileSize: Int32? + public init(privateChats: Bool, groupChats: Bool, channels: Bool, fileSize: Int32?) { self.privateChats = privateChats - self.groupsAndChannels = groupsAndChannels + self.groupChats = groupChats + self.channels = channels + self.fileSize = fileSize } public init(decoder: PostboxDecoder) { - self.privateChats = decoder.decodeInt32ForKey("p", orElse: 0) != 0 - self.groupsAndChannels = decoder.decodeInt32ForKey("g", orElse: 0) != 0 + self.privateChats = decoder.decodeInt32ForKey("pc", orElse: 0) != 0 + self.groupChats = decoder.decodeInt32ForKey("g", orElse: 0) != 0 + self.channels = decoder.decodeInt32ForKey("c", orElse: 0) != 0 + self.fileSize = decoder.decodeOptionalInt32ForKey("fs") + } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.privateChats ? 1 : 0, forKey: "p") - encoder.encodeInt32(self.groupsAndChannels ? 1 : 0, forKey: "g") + encoder.encodeInt32(self.privateChats ? 1 : 0, forKey: "pc") + encoder.encodeInt32(self.groupChats ? 1 : 0, forKey: "g") + encoder.encodeInt32(self.channels ? 1 : 0, forKey: "c") + if let fileSize = self.fileSize { + encoder.encodeInt32(fileSize, forKey: "fs") + } else { + encoder.encodeNil(forKey: "fs") + } } public func withUpdatedPrivateChats(_ privateChats: Bool) -> AutomaticMediaDownloadCategoryPeers { - return AutomaticMediaDownloadCategoryPeers(privateChats: privateChats, groupsAndChannels: self.groupsAndChannels) + return AutomaticMediaDownloadCategoryPeers(privateChats: privateChats, groupChats: self.groupChats, channels: self.channels, fileSize: self.fileSize) + } + + public func withUpdatedGroupChats(_ groupChats: Bool) -> AutomaticMediaDownloadCategoryPeers { + return AutomaticMediaDownloadCategoryPeers(privateChats: self.privateChats, groupChats: groupChats, channels: self.channels, fileSize: self.fileSize) } - public func withUpdatedGroupsAndChannels(_ groupsAndChannels: Bool) -> AutomaticMediaDownloadCategoryPeers { - return AutomaticMediaDownloadCategoryPeers(privateChats: self.privateChats, groupsAndChannels: groupsAndChannels) + public func withUpdatedChannels(_ channels: Bool) -> AutomaticMediaDownloadCategoryPeers { + return AutomaticMediaDownloadCategoryPeers(privateChats: self.privateChats, groupChats: self.groupChats, channels: channels, fileSize: self.fileSize) + } + public func withUpdatedSizeLimit(_ sizeLimit: Int32?) -> AutomaticMediaDownloadCategoryPeers { + return AutomaticMediaDownloadCategoryPeers(privateChats: self.privateChats, groupChats: self.groupChats, channels: channels, fileSize: sizeLimit) } public static func ==(lhs: AutomaticMediaDownloadCategoryPeers, rhs: AutomaticMediaDownloadCategoryPeers) -> Bool { if lhs.privateChats != rhs.privateChats { return false } - if lhs.groupsAndChannels != rhs.groupsAndChannels { + if lhs.channels != rhs.channels { + return false + } + if lhs.groupChats != rhs.groupChats { + return false + } + if lhs.fileSize != rhs.fileSize { return false } return true @@ -50,58 +75,65 @@ public struct AutomaticMediaDownloadCategoryPeers: PostboxCoding, Equatable { public struct AutomaticMediaDownloadCategories: PostboxCoding, Equatable { public let photo: AutomaticMediaDownloadCategoryPeers - public let voice: AutomaticMediaDownloadCategoryPeers - public let instantVideo: AutomaticMediaDownloadCategoryPeers - public let gif: AutomaticMediaDownloadCategoryPeers + public let video: AutomaticMediaDownloadCategoryPeers + public let files: AutomaticMediaDownloadCategoryPeers +// public let instantVideo: AutomaticMediaDownloadCategoryPeers +// public let gif: AutomaticMediaDownloadCategoryPeers - public init(photo: AutomaticMediaDownloadCategoryPeers, voice: AutomaticMediaDownloadCategoryPeers, instantVideo: AutomaticMediaDownloadCategoryPeers, gif: AutomaticMediaDownloadCategoryPeers) { + public init(photo: AutomaticMediaDownloadCategoryPeers, video: AutomaticMediaDownloadCategoryPeers, files: AutomaticMediaDownloadCategoryPeers) { self.photo = photo - self.voice = voice - self.instantVideo = instantVideo - self.gif = gif + self.video = video + self.files = files +// self.instantVideo = instantVideo +// self.gif = gif } public init(decoder: PostboxDecoder) { self.photo = decoder.decodeObjectForKey("p", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers - self.voice = decoder.decodeObjectForKey("v", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers - self.instantVideo = decoder.decodeObjectForKey("iv", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers - self.gif = decoder.decodeObjectForKey("g", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers + self.video = decoder.decodeObjectForKey("vd", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers + self.files = decoder.decodeObjectForKey("f", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers +// self.instantVideo = decoder.decodeObjectForKey("iv", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers +// self.gif = decoder.decodeObjectForKey("g", decoder: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) as! AutomaticMediaDownloadCategoryPeers } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObject(self.photo, forKey: "p") - encoder.encodeObject(self.voice, forKey: "v") - encoder.encodeObject(self.instantVideo, forKey: "iv") - encoder.encodeObject(self.gif, forKey: "g") + encoder.encodeObject(self.video, forKey: "vd") + encoder.encodeObject(self.files, forKey: "f") +// encoder.encodeObject(self.instantVideo, forKey: "iv") +// encoder.encodeObject(self.gif, forKey: "g") } public func withUpdatedPhoto(_ photo: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { - return AutomaticMediaDownloadCategories(photo: photo, voice: self.voice, instantVideo: self.instantVideo, gif: self.gif) + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) + } + public func withUpdatedVideo(_ video: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) + } + public func withUpdatedFiles(_ files: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) } public func withUpdatedVoice(_ voice: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { - return AutomaticMediaDownloadCategories(photo: self.photo, voice: voice, instantVideo: self.instantVideo, gif: self.gif) + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) } public func withUpdatedInstantVideo(_ instantVideo: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { - return AutomaticMediaDownloadCategories(photo: self.photo, voice: self.voice, instantVideo: instantVideo, gif: self.gif) + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) } public func withUpdatedGif(_ gif: AutomaticMediaDownloadCategoryPeers) -> AutomaticMediaDownloadCategories { - return AutomaticMediaDownloadCategories(photo: self.photo, voice: self.voice, instantVideo: self.instantVideo, gif: gif) + return AutomaticMediaDownloadCategories(photo: photo, video: video, files: files) } public static func ==(lhs: AutomaticMediaDownloadCategories, rhs: AutomaticMediaDownloadCategories) -> Bool { if lhs.photo != rhs.photo { return false } - if lhs.voice != rhs.voice { - return false - } - if lhs.instantVideo != rhs.instantVideo { + if lhs.video != rhs.video { return false } - if lhs.gif != rhs.gif { + if lhs.files != rhs.files { return false } return true @@ -110,25 +142,35 @@ public struct AutomaticMediaDownloadCategories: PostboxCoding, Equatable { public struct AutomaticMediaDownloadSettings: PreferencesEntry, Equatable { public let categories: AutomaticMediaDownloadCategories - public let saveIncomingPhotos: Bool - + public let automaticDownload: Bool + public let downloadFolder: String + public let automaticSaveDownloadedFiles: Bool public static var defaultSettings: AutomaticMediaDownloadSettings { - return AutomaticMediaDownloadSettings(categories: AutomaticMediaDownloadCategories(photo: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupsAndChannels: true), voice: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupsAndChannels: true), instantVideo: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupsAndChannels: true), gif: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupsAndChannels: true)), saveIncomingPhotos: false) + let categories = AutomaticMediaDownloadCategories(photo: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupChats: true, channels: true, fileSize: nil), video: AutomaticMediaDownloadCategoryPeers(privateChats: true, groupChats: true, channels: true, fileSize: 10 * 1024 * 1024), files: AutomaticMediaDownloadCategoryPeers(privateChats: false, groupChats: false, channels: false, fileSize: 10 * 1024 * 1024)) + return AutomaticMediaDownloadSettings(categories: categories, automaticDownload: true, downloadFolder: "~/Downloads/".nsstring.expandingTildeInPath, automaticSaveDownloadedFiles: false) } - init(categories: AutomaticMediaDownloadCategories, saveIncomingPhotos: Bool) { + init(categories: AutomaticMediaDownloadCategories, automaticDownload: Bool, downloadFolder: String, automaticSaveDownloadedFiles: Bool) { self.categories = categories - self.saveIncomingPhotos = saveIncomingPhotos + self.automaticDownload = automaticDownload + self.downloadFolder = downloadFolder + self.automaticSaveDownloadedFiles = automaticSaveDownloadedFiles } public init(decoder: PostboxDecoder) { self.categories = decoder.decodeObjectForKey("c", decoder: { AutomaticMediaDownloadCategories(decoder: $0) }) as! AutomaticMediaDownloadCategories - self.saveIncomingPhotos = decoder.decodeInt32ForKey("siph", orElse: 0) != 0 + self.automaticDownload = decoder.decodeBoolForKey("a", orElse: true) + self.downloadFolder = decoder.decodeStringForKey("d", orElse: "~/Downloads/".nsstring.expandingTildeInPath) + self.automaticSaveDownloadedFiles = decoder.decodeBoolForKey("ad", orElse: false) + } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObject(self.categories, forKey: "c") - encoder.encodeInt32(self.saveIncomingPhotos ? 1 : 0, forKey: "siph") + encoder.encodeBool(self.automaticDownload, forKey: "a") + encoder.encodeString(self.downloadFolder, forKey: "d") + encoder.encodeBool(self.automaticSaveDownloadedFiles, forKey: "ad") + } public func isEqual(to: PreferencesEntry) -> Bool { @@ -140,21 +182,29 @@ public struct AutomaticMediaDownloadSettings: PreferencesEntry, Equatable { } public static func ==(lhs: AutomaticMediaDownloadSettings, rhs: AutomaticMediaDownloadSettings) -> Bool { - return lhs.categories == rhs.categories && lhs.saveIncomingPhotos == rhs.saveIncomingPhotos + return lhs.categories == rhs.categories && lhs.automaticDownload == rhs.automaticDownload && lhs.downloadFolder == rhs.downloadFolder && lhs.automaticSaveDownloadedFiles == rhs.automaticSaveDownloadedFiles } func withUpdatedCategories(_ categories: AutomaticMediaDownloadCategories) -> AutomaticMediaDownloadSettings { - return AutomaticMediaDownloadSettings(categories: categories, saveIncomingPhotos: self.saveIncomingPhotos) + return AutomaticMediaDownloadSettings(categories: categories, automaticDownload: automaticDownload, downloadFolder: self.downloadFolder, automaticSaveDownloadedFiles: self.automaticSaveDownloadedFiles) + } + + func withUpdatedAutomaticDownload(_ automaticDownload: Bool) -> AutomaticMediaDownloadSettings { + return AutomaticMediaDownloadSettings(categories: categories, automaticDownload: automaticDownload, downloadFolder: self.downloadFolder, automaticSaveDownloadedFiles: self.automaticSaveDownloadedFiles) + } + + func withUpdatedDownloadFolder(_ folder: String) -> AutomaticMediaDownloadSettings { + return AutomaticMediaDownloadSettings(categories: categories, automaticDownload: automaticDownload, downloadFolder: folder, automaticSaveDownloadedFiles: self.automaticSaveDownloadedFiles) } - func withUpdatedSaveIncomingPhotos(_ saveIncomingPhotos: Bool) -> AutomaticMediaDownloadSettings { - return AutomaticMediaDownloadSettings(categories: self.categories, saveIncomingPhotos: saveIncomingPhotos) + func withUpdatedAutomaticSaveDownloadedFiles(_ automaticSaveDownloadedFiles: Bool) -> AutomaticMediaDownloadSettings { + return AutomaticMediaDownloadSettings(categories: categories, automaticDownload: automaticDownload, downloadFolder: self.downloadFolder, automaticSaveDownloadedFiles: automaticSaveDownloadedFiles) } } func updateMediaDownloadSettingsInteractively(postbox: Postbox, _ f: @escaping (AutomaticMediaDownloadSettings) -> AutomaticMediaDownloadSettings) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings, { entry in + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings, { entry in let currentSettings: AutomaticMediaDownloadSettings if let entry = entry as? AutomaticMediaDownloadSettings { currentSettings = entry @@ -165,3 +215,9 @@ func updateMediaDownloadSettingsInteractively(postbox: Postbox, _ f: @escaping ( }) } } + +func automaticDownloadSettings(postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings]) |> map { value in + return value.values[ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings ?? AutomaticMediaDownloadSettings.defaultSettings + } +} diff --git a/Telegram-Mac/AutoplayPreferences.swift b/Telegram-Mac/AutoplayPreferences.swift new file mode 100644 index 0000000000..8d3126aa59 --- /dev/null +++ b/Telegram-Mac/AutoplayPreferences.swift @@ -0,0 +1,98 @@ +// +// AutoplayPreferences.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit + + +class AutoplayMediaPreferences : PreferencesEntry, Equatable { + let gifs: Bool + let videos: Bool + let soundOnHover: Bool + let preloadVideos: Bool + let loopAnimatedStickers: Bool + init(gifs: Bool, videos: Bool, soundOnHover: Bool, preloadVideos: Bool, loopAnimatedStickers: Bool ) { + self.gifs = gifs + self.videos = videos + self.soundOnHover = soundOnHover + self.preloadVideos = preloadVideos + self.loopAnimatedStickers = loopAnimatedStickers + } + + static var defaultSettings: AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: true, videos: true, soundOnHover: true, preloadVideos: true, loopAnimatedStickers: true) + } + + required init(decoder: PostboxDecoder) { + self.gifs = decoder.decodeInt32ForKey("g", orElse: 0) == 1 + self.videos = decoder.decodeInt32ForKey("v", orElse: 0) == 1 + self.soundOnHover = decoder.decodeInt32ForKey("soh", orElse: 0) == 1 + self.preloadVideos = decoder.decodeInt32ForKey("pv", orElse: 0) == 1 + self.loopAnimatedStickers = decoder.decodeInt32ForKey("las", orElse: 0) == 1 + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(gifs ? 1 : 0, forKey: "g") + encoder.encodeInt32(videos ? 1 : 0, forKey: "v") + encoder.encodeInt32(soundOnHover ? 1 : 0, forKey: "soh") + encoder.encodeInt32(preloadVideos ? 1 : 0, forKey: "pv") + encoder.encodeInt32(loopAnimatedStickers ? 1 : 0, forKey: "las") + } + + static func == (lhs: AutoplayMediaPreferences, rhs: AutoplayMediaPreferences) -> Bool { + return lhs.gifs == rhs.gifs && lhs.videos == rhs.videos && lhs.soundOnHover == rhs.soundOnHover && lhs.preloadVideos == rhs.preloadVideos && lhs.loopAnimatedStickers == rhs.loopAnimatedStickers + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? AutoplayMediaPreferences { + return self == to + } else { + return false + } + } + + func withUpdatedAutoplayGifs(_ gifs: Bool) -> AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: gifs, videos: self.videos, soundOnHover: self.soundOnHover, preloadVideos: self.preloadVideos, loopAnimatedStickers: self.loopAnimatedStickers) + } + func withUpdatedAutoplayVideos(_ videos: Bool) -> AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: self.gifs, videos: videos, soundOnHover: self.soundOnHover, preloadVideos: self.preloadVideos, loopAnimatedStickers: self.loopAnimatedStickers) + } + func withUpdatedAutoplaySoundOnHover(_ soundOnHover: Bool) -> AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: self.gifs, videos: self.videos, soundOnHover: soundOnHover, preloadVideos: self.preloadVideos, loopAnimatedStickers: self.loopAnimatedStickers) + } + func withUpdatedAutoplayPreloadVideos(_ preloadVideos: Bool) -> AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: self.gifs, videos: self.videos, soundOnHover: self.soundOnHover, preloadVideos: preloadVideos, loopAnimatedStickers: self.loopAnimatedStickers) + } + func withUpdatedLoopAnimatedStickers(_ loopAnimatedStickers: Bool) -> AutoplayMediaPreferences { + return AutoplayMediaPreferences(gifs: self.gifs, videos: self.videos, soundOnHover: self.soundOnHover, preloadVideos: self.preloadVideos, loopAnimatedStickers: loopAnimatedStickers) + } +} + + +func updateAutoplayMediaSettingsInteractively(postbox: Postbox, _ f: @escaping (AutoplayMediaPreferences) -> AutoplayMediaPreferences) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.autoplayMedia, { entry in + let currentSettings: AutoplayMediaPreferences + if let entry = entry as? AutoplayMediaPreferences { + currentSettings = entry + } else { + currentSettings = AutoplayMediaPreferences.defaultSettings + } + + return f(currentSettings) + }) + } +} + + +func autoplayMediaSettings(postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.autoplayMedia]) |> map { views in + return views.values[ApplicationSpecificPreferencesKeys.autoplayMedia] as? AutoplayMediaPreferences ?? AutoplayMediaPreferences.defaultSettings + } +} diff --git a/Telegram-Mac/AvatarLayer.swift b/Telegram-Mac/AvatarLayer.swift index 35b2b35749..2399d2000b 100644 --- a/Telegram-Mac/AvatarLayer.swift +++ b/Telegram-Mac/AvatarLayer.swift @@ -7,16 +7,19 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit +import SyncCore + private class AvatarNodeParameters: NSObject { let account: Account - let peerId: PeerId + let peerId: Peer let letters: [String] let font: NSFont - init(account: Account, peerId: PeerId, letters: [String], font: NSFont) { + init(account: Account, peerId: Peer, letters: [String], font: NSFont) { self.account = account self.peerId = peerId self.letters = letters @@ -27,17 +30,21 @@ private class AvatarNodeParameters: NSObject { -private enum AvatarNodeState: Equatable { +enum AvatarNodeState: Equatable { case Empty - case PeerAvatar(PeerId, [String], TelegramMediaImageRepresentation?, CGFloat) + case PeerAvatar(Peer, [String], TelegramMediaImageRepresentation?, Message?) + case ArchivedChats + } -private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { +func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { switch (lhs, rhs) { case (.Empty, .Empty): return true - case let (.PeerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations, lhsScale), .PeerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations, rhsScale)): - return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations && lhsScale == rhsScale + case let (.PeerAvatar(lhsPeer, lhsLetters, lhsPhotoRepresentations, _), .PeerAvatar(rhsPeer, rhsLetters, rhsPhotoRepresentations, _)): + return lhsPeer.isEqual(rhsPeer) && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations + case (.ArchivedChats, .ArchivedChats): + return true default: return false } @@ -54,9 +61,6 @@ class AvatarControl: NSView { var font: NSFont { didSet { if oldValue !== font { - if let parameters = self.parameters { - self.parameters = AvatarNodeParameters(account: parameters.account, peerId: parameters.peerId, letters: parameters.letters, font: self.font) - } if !self.displaySuspended { self.needsDisplay = true @@ -64,20 +68,30 @@ class AvatarControl: NSView { } } } - private var parameters: AvatarNodeParameters? private let disposable = MetaDisposable() private var state: AvatarNodeState = .Empty private var account:Account? - private var peer:Peer? - + private var contentScale: CGFloat = 0 public var animated: Bool = false + private var _attemptLoadNextSynchronous: Bool = false + public var attemptLoadNextSynchronous: Bool { + get { + let result = _attemptLoadNextSynchronous + _attemptLoadNextSynchronous = false + return result + } + set { + _attemptLoadNextSynchronous = newValue + } + } public init(font: NSFont) { self.font = font super.init(frame: NSZeroRect) wantsLayer = true + layerContentsRedrawPolicy = .never } @@ -101,10 +115,28 @@ class AvatarControl: NSView { } } - public func setPeer(account: Account, peer: Peer?) { + public func setState(account: Account, state: AvatarNodeState) { + self.account = account + if state != self.state { + contentScale = 0 + self.state = state + self.viewDidChangeBackingProperties() + } + } + + public func setPeer(account: Account, peer: Peer?, message: Message? = nil) { self.account = account - self.peer = peer - self.viewDidChangeBackingProperties() + let state: AvatarNodeState + if let peer = peer { + state = .PeerAvatar(peer, peer.displayLetters, peer.smallProfileImage, message) + } else { + state = .Empty + } + if self.state != state { + self.state = state + contentScale = 0 + self.viewDidChangeBackingProperties() + } } func set(handler:@escaping (AvatarControl) -> Void, for event:ControlEvent) -> Void { @@ -166,34 +198,50 @@ class AvatarControl: NSView { layer?.contentsScale = backingScaleFactor - if let account = account, let peer = peer { - let updatedState = AvatarNodeState.PeerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage, backingScaleFactor) - if updatedState != self.state { - self.state = updatedState - - let parameters = AvatarNodeParameters(account: account, peerId: peer.id, letters: peer.displayLetters, font: self.font) - + + if let account = account, self.state != .Empty { + if contentScale != backingScaleFactor { + contentScale = backingScaleFactor self.displaySuspended = true self.layer?.contents = nil - - if let signal = peerAvatarImage(account: account, peer: peer, displayDimensions:frame.size, scale:backingScaleFactor, font: self.font) { - setSignal(signal, animated: animated) - + let photo: PeerPhoto? + switch state { + case let .PeerAvatar(peer, letters, representation, message): + if let peer = peer as? TelegramUser, peer.firstName == nil && peer.lastName == nil { + photo = nil + self.setState(account: account, state: .Empty) + let icon = theme.icons.deletedAccount + self.setSignal(generateEmptyPhoto(frame.size, type: .icon(colors: theme.colors.peerColors(Int(peer.id.id % 7)), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(min(50, frame.size.width - 20), min(frame.size.height - 20, 50))), cornerRadius: nil)) |> map {($0, false)}) + return + } else { + photo = .peer(peer, representation, letters, message) + } + case .Empty: + photo = nil + default: + photo = nil + } + if let photo = photo { + setSignal(peerAvatarImage(account: account, photo: photo, displayDimensions: frame.size, scale:backingScaleFactor, font: self.font, synchronousLoad: attemptLoadNextSynchronous), force: false) } else { + let content = self.layer?.contents self.displaySuspended = false + self.layer?.contents = content } - if self.parameters == nil || self.parameters != parameters { - self.parameters = parameters - self.needsDisplay = true - } + } + } else { + self.state = .Empty } } - public func setSignal(_ signal: Signal, animated: Bool) { - self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] next in + public func setSignal(_ signal: Signal<(CGImage?, Bool), NoError>, force: Bool = true) { + if force { + self.state = .Empty + } + self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] image, animated in if let strongSelf = self { - strongSelf.layer?.contents = next + strongSelf.layer?.contents = image if animated { strongSelf.layer?.animateContents() } @@ -212,7 +260,7 @@ class AvatarControl: NSView { trackingArea = nil if let _ = window { - let options:NSTrackingArea.Options = [.cursorUpdate, .mouseEnteredAndExited, .mouseMoved, .activeInKeyWindow, .inVisibleRect] + let options:NSTrackingArea.Options = [.cursorUpdate, .mouseEnteredAndExited, .mouseMoved, .activeAlways, .inVisibleRect] self.trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) self.addTrackingArea(self.trackingArea!) diff --git a/Telegram-Mac/Base.lproj/MainMenu.xib b/Telegram-Mac/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000..c524e163ac --- /dev/null +++ b/Telegram-Mac/Base.lproj/MainMenu.xib @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/BaseApplicationSettings.swift b/Telegram-Mac/BaseApplicationSettings.swift index 619c28587f..93e15cbcca 100644 --- a/Telegram-Mac/BaseApplicationSettings.swift +++ b/Telegram-Mac/BaseApplicationSettings.swift @@ -7,47 +7,79 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit class BaseApplicationSettings: PreferencesEntry, Equatable { - let fontSize: Int32 let handleInAppKeys: Bool let sidebar: Bool - + let showCallsTab: Bool + let latestArticles: Bool + let predictEmoji: Bool + let bigEmoji: Bool + let statusBar: Bool static var defaultSettings: BaseApplicationSettings { - return BaseApplicationSettings(fontSize: 13, handleInAppKeys: false, sidebar: true) + return BaseApplicationSettings(handleInAppKeys: false, sidebar: true, showCallsTab: true, latestArticles: true, predictEmoji: true, bigEmoji: true, statusBar: true) } - init(fontSize:Int32, handleInAppKeys: Bool, sidebar: Bool) { - self.fontSize = fontSize + init(handleInAppKeys: Bool, sidebar: Bool, showCallsTab: Bool, latestArticles: Bool, predictEmoji: Bool, bigEmoji: Bool, statusBar: Bool) { self.handleInAppKeys = handleInAppKeys self.sidebar = sidebar + self.showCallsTab = showCallsTab + self.latestArticles = latestArticles + self.predictEmoji = predictEmoji + self.bigEmoji = bigEmoji + self.statusBar = statusBar } required init(decoder: PostboxDecoder) { - self.fontSize = decoder.decodeInt32ForKey("f", orElse: 0) + self.showCallsTab = decoder.decodeInt32ForKey("c", orElse: 1) != 0 self.handleInAppKeys = decoder.decodeInt32ForKey("h", orElse: 0) != 0 self.sidebar = decoder.decodeInt32ForKey("e", orElse: 0) != 0 + self.latestArticles = decoder.decodeInt32ForKey("la", orElse: 1) != 0 + self.predictEmoji = decoder.decodeInt32ForKey("pe", orElse: 1) != 0 + self.bigEmoji = decoder.decodeInt32ForKey("bi", orElse: 1) != 0 + self.statusBar = decoder.decodeInt32ForKey("sb", orElse: 1) != 0 } func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.fontSize, forKey: "f") + encoder.encodeInt32(self.showCallsTab ? 1 : 0, forKey: "c") encoder.encodeInt32(self.handleInAppKeys ? 1 : 0, forKey: "h") encoder.encodeInt32(self.sidebar ? 1 : 0, forKey: "e") + encoder.encodeInt32(self.latestArticles ? 1 : 0, forKey: "la") + encoder.encodeInt32(self.predictEmoji ? 1 : 0, forKey: "pe") + encoder.encodeInt32(self.bigEmoji ? 1 : 0, forKey: "bi") + encoder.encodeInt32(self.statusBar ? 1 : 0, forKey: "sb") } - func withUpdatedFontSize(_ fontSize: Int32) -> BaseApplicationSettings { - return BaseApplicationSettings(fontSize: fontSize, handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar) + func withUpdatedShowCallsTab(_ showCallsTab: Bool) -> BaseApplicationSettings { + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar, showCallsTab: showCallsTab, latestArticles: self.latestArticles, predictEmoji: self.predictEmoji, bigEmoji: self.bigEmoji, statusBar: self.statusBar) } func withUpdatedSidebar(_ sidebar: Bool) -> BaseApplicationSettings { - return BaseApplicationSettings(fontSize: self.fontSize, handleInAppKeys: self.handleInAppKeys, sidebar: sidebar) + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: sidebar, showCallsTab: self.showCallsTab, latestArticles: self.latestArticles, predictEmoji: self.predictEmoji, bigEmoji: self.bigEmoji, statusBar: self.statusBar) } func withUpdatedInAppKeyHandle(_ handleInAppKeys: Bool) -> BaseApplicationSettings { - return BaseApplicationSettings(fontSize: self.fontSize, handleInAppKeys: handleInAppKeys, sidebar: self.sidebar) + return BaseApplicationSettings(handleInAppKeys: handleInAppKeys, sidebar: self.sidebar, showCallsTab: self.showCallsTab, latestArticles: self.latestArticles, predictEmoji: self.predictEmoji, bigEmoji: self.bigEmoji, statusBar: self.statusBar) + } + + func withUpdatedLatestArticles(_ latestArticles: Bool) -> BaseApplicationSettings { + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar, showCallsTab: self.showCallsTab, latestArticles: latestArticles, predictEmoji: self.predictEmoji, bigEmoji: self.bigEmoji, statusBar: self.statusBar) } + func withUpdatedPredictEmoji(_ predictEmoji: Bool) -> BaseApplicationSettings { + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar, showCallsTab: self.showCallsTab, latestArticles: self.latestArticles, predictEmoji: predictEmoji, bigEmoji: self.bigEmoji, statusBar: self.statusBar) + } + + func withUpdatedBigEmoji(_ bigEmoji: Bool) -> BaseApplicationSettings { + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar, showCallsTab: self.showCallsTab, latestArticles: self.latestArticles, predictEmoji: self.predictEmoji, bigEmoji: bigEmoji, statusBar: self.statusBar) + } + + func withUpdatedStatusBar(_ statusBar: Bool) -> BaseApplicationSettings { + return BaseApplicationSettings(handleInAppKeys: self.handleInAppKeys, sidebar: self.sidebar, showCallsTab: self.showCallsTab, latestArticles: self.latestArticles, predictEmoji: self.predictEmoji, bigEmoji: self.bigEmoji, statusBar: statusBar) + } + + func isEqual(to: PreferencesEntry) -> Bool { if let to = to as? BaseApplicationSettings { return self == to @@ -57,7 +89,7 @@ class BaseApplicationSettings: PreferencesEntry, Equatable { } static func ==(lhs: BaseApplicationSettings, rhs: BaseApplicationSettings) -> Bool { - if lhs.fontSize != rhs.fontSize { + if lhs.showCallsTab != rhs.showCallsTab { return false } if lhs.handleInAppKeys != rhs.handleInAppKeys { @@ -66,15 +98,33 @@ class BaseApplicationSettings: PreferencesEntry, Equatable { if lhs.sidebar != rhs.sidebar { return false } + if lhs.latestArticles != rhs.latestArticles { + return false + } + if lhs.predictEmoji != rhs.predictEmoji { + return false + } + if lhs.bigEmoji != rhs.bigEmoji { + return false + } + if lhs.statusBar != rhs.statusBar { + return false + } return true } } -func updateBaseAppSettingsInteractively(postbox: Postbox, _ f: @escaping (BaseApplicationSettings) -> BaseApplicationSettings) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.baseAppSettings, { entry in +func baseAppSettings(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.baseAppSettings]) |> map { prefs in + return prefs.entries[ApplicationSharedPreferencesKeys.baseAppSettings] as? BaseApplicationSettings ?? BaseApplicationSettings.defaultSettings + } +} + +func updateBaseAppSettingsInteractively(accountManager: AccountManager, _ f: @escaping (BaseApplicationSettings) -> BaseApplicationSettings) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.baseAppSettings, { entry in let currentSettings: BaseApplicationSettings if let entry = entry as? BaseApplicationSettings { currentSettings = entry diff --git a/Telegram-Mac/Beta.xcconfig b/Telegram-Mac/Beta.xcconfig index d9d535c665..701281c37d 100644 --- a/Telegram-Mac/Beta.xcconfig +++ b/Telegram-Mac/Beta.xcconfig @@ -9,4 +9,5 @@ DSA_PEM_FILE = dsa_pub.pem SIMPLE_SLASH=/ -SFEED_URL = https:${SIMPLE_SLASH}/rink.hockeyapp.net/api/2/apps/6ed2ac3049e1407387c2f1ffcb74e81f +SFEED_URL = https:${SIMPLE_SLASH}/api.appcenter.ms/v0.1/public/sparkle/apps/6ed2ac30-49e1-4073-87c2-f1ffcb74e81f +APPCENTER_SECRET = 6ed2ac30-49e1-4073-87c2-f1ffcb74e81f diff --git a/Telegram-Mac/BioViewController.swift b/Telegram-Mac/BioViewController.swift deleted file mode 100644 index 3656dc6c52..0000000000 --- a/Telegram-Mac/BioViewController.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// BioViewController.swift -// Telegram -// -// Created by keepcoder on 12/07/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - -private let bioLimit: Int32 = 70 -private final class BioArguments { - let account: Account - let updateText:(String)->Void - init(account:Account, updateText:@escaping(String)->Void) { - self.account = account - self.updateText = updateText - } -} - -private enum BioEntry : TableItemListNodeEntry { - case section(Int32) - case text(Int32, String) - case description(Int32) - - var stableId: Int32 { - switch self { - case .section(let id): - return (id + 1) * 1000 - id - case .text: - return 1 - case .description: - return 2 - } - } - - var index:Int32 { - switch self { - case .section(let id): - return (id + 1) * 1000 - id - case .text(let sectionId, _): - return (sectionId * 1000) + stableId - case .description(let sectionId): - return (sectionId * 1000) + stableId - } - } - - func item(_ arguments: BioArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .text(_, let text): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.bioPlaceholder), text: text, limit: bioLimit, textChangeHandler: { updated in - arguments.updateText(updated) - }) - case .description: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.bioDescription)) - } - } -} - -private func <(lhs: BioEntry, rhs: BioEntry) -> Bool { - return lhs.index < rhs.index -} - -private func ==(lhs: BioEntry, rhs: BioEntry) -> Bool { - return lhs.index == rhs.index -} - -private func BioEntries(_ cachedData: CachedUserData?, state: BioState) -> [BioEntry] { - var entries:[BioEntry] = [] - - var sectionId:Int32 = 1 - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.text(sectionId, state.updatedText ?? cachedData?.about ?? "")) - entries.append(.description(sectionId)) - return entries -} - -private final class BioState : Equatable { - let updatedText:String? - let updating: Bool - let initiated: Bool - init(updatedText: String? = nil, updating: Bool = false, initiated: Bool = false) { - self.updatedText = updatedText - self.updating = updating - self.initiated = initiated - } - func withUpdateUpdating(_ updating: Bool) -> BioState { - return BioState(updatedText: self.updatedText, updating: updating, initiated: self.initiated) - } - - func withUpdatedInitiated(_ initiated:Bool) -> BioState { - return BioState(updatedText: self.updatedText, updating: self.updating, initiated: initiated) - } - - func withUpdatedText(_ updatedText:String) -> BioState { - return BioState(updatedText: updatedText, updating: self.updating, initiated: self.initiated) - } -} -private func ==(lhs:BioState, rhs: BioState) -> Bool { - return lhs.updatedText == rhs.updatedText && lhs.updating == rhs.updating && lhs.initiated == rhs.initiated -} - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:BioArguments) -> TableUpdateTransition { - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -class BioViewController: EditableViewController { - private let disposable = MetaDisposable() - private let stateValue = Atomic(value: BioState()) - private let statePromise = ValuePromise(BioState()) - - override var removeAfterDisapper:Bool { - return true - } - - override func viewDidLoad() { - super.viewDidLoad() - - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = atomicSize - - let arguments = BioArguments(account: account, updateText: { [weak self] text in - _ = self?.updateState({$0.withUpdatedText(text).withUpdatedInitiated(true)}) - - }) - - genericView.merge(with: combineLatest(account.viewTracker.peerView( account.peerId) |> deliverOnMainQueue, statePromise.get() |> deliverOnMainQueue, appearanceSignal |> deliverOnMainQueue) |> map { [weak self] view, state, appearance in - - - let userData = view.cachedData as? CachedUserData - let about = userData?.about ?? "" - self?.set(enabled: !state.updating && state.updatedText != about && state.initiated) - if state.updatedText == nil { - _ = self?.stateValue.modify({$0.withUpdatedText(about)}) - } - self?.requestUpdateCenterBar() - let entries = BioEntries(userData, state: state).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) - - } |> deliverOnMainQueue) - readyOnce() - } - - override func requestUpdateCenterBar() { - super.requestUpdateCenterBar() - let length = stateValue.modify({$0}).updatedText?.length ?? 0 - setCenterTitle(defaultBarTitle + " (\(bioLimit - Int32(length)))") - } - - private func updateState(_ f:(BioState)->BioState) -> BioState { - let updatedState = stateValue.modify(f) - statePromise.set(updatedState) - return updatedState - } - override func backKeyAction() -> KeyHandlerResult { - return .invokeNext - } - - override func firstResponder() -> NSResponder? { - if let item = genericView.item(stableId: AnyHashable(Int32(1))) { - if let view = genericView.viewNecessary(at: item.index) as? GeneralInputRowView { - return view.textView.inputView - } - } - return nil - } - - override func returnKeyAction() -> KeyHandlerResult { - changeState() - return .invoked - } - - override func becomeFirstResponder() -> Bool? { - return true - } - - override var normalString: String { - return tr(.bioSave) - } - - override func changeState() { - - let state = updateState { state -> BioState in - return state.withUpdateUpdating(true) - } - - disposable.set(showModalProgress(signal: (updateAbout(account: account, about: state.updatedText) |> deliverOnMainQueue), for: mainWindow).start(error: { [weak self] error in - _ = self?.updateState({$0.withUpdateUpdating(false).withUpdatedInitiated(true)}) - }, completed: { [weak self] in - _ = self?.updateState({$0.withUpdateUpdating(false).withUpdatedInitiated(false)}) - })) - } - -} diff --git a/Telegram-Mac/BlockedPeersViewController.swift b/Telegram-Mac/BlockedPeersViewController.swift index d9a4a15748..e2360e9f4c 100644 --- a/Telegram-Mac/BlockedPeersViewController.swift +++ b/Telegram-Mac/BlockedPeersViewController.swift @@ -1,92 +1,78 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private final class BlockedPeerControllerArguments { - let account: Account + let context: AccountContext let removePeer: (PeerId) -> Void - - init(account: Account, removePeer: @escaping (PeerId) -> Void) { - self.account = account + let openPeer:(PeerId) -> Void + init(context: AccountContext, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping(PeerId)->Void) { + self.context = context self.removePeer = removePeer + self.openPeer = openPeer } } private enum BlockedPeerEntryStableId: Hashable { case peer(PeerId) case empty - case whiteSpace + case sectionId(Int32) var hashValue: Int { switch self { case let .peer(peerId): return peerId.hashValue case .empty: return 0 - case .whiteSpace: + case .sectionId: return 1 } } - - static func ==(lhs: BlockedPeerEntryStableId, rhs: BlockedPeerEntryStableId) -> Bool { - switch lhs { - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - case .empty: - if case .empty = rhs { - return true - } else { - return false - } - case .whiteSpace: - if case .whiteSpace = rhs { - return true - } else { - return false - } - } - } + } private enum BlockedPeerEntry: Identifiable, Comparable { - case peerItem(Int32, Peer, ShortPeerDeleting?, Bool) + case section(Int32) + case peerItem(Int32, Int32, Peer, ShortPeerDeleting?, Bool, GeneralViewType) case empty(Bool) - case whiteSpace(CGFloat) var stableId: BlockedPeerEntryStableId { switch self { - case let .peerItem(_, peer, _, _): + case let .peerItem(_, _, peer, _, _, _): return .peer(peer.id) case .empty: return .empty - case .whiteSpace: - return .whiteSpace + case let .section(id): + return .sectionId(id) } } static func ==(lhs: BlockedPeerEntry, rhs: BlockedPeerEntry) -> Bool { switch lhs { - case let .peerItem(lhsIndex, lhsPeer, lhsEditing, lhsEnabled): - if case let .peerItem(rhsIndex, rhsPeer, rhsEditing, rhsEnabled) = rhs { + case let .peerItem(lhsSectionId, lhsIndex, lhsPeer, lhsEditing, lhsEnabled, lhsViewType): + if case let .peerItem(rhsSectionId, rhsIndex, rhsPeer, rhsEditing, rhsEnabled, rhsViewType) = rhs { if lhsIndex != rhsIndex { return false } if !lhsPeer.isEqual(rhsPeer) { return false } + if lhsSectionId != rhsSectionId { + return false + } if lhsEditing != rhsEditing { return false } if lhsEnabled != rhsEnabled { return false } + if rhsViewType != lhsViewType { + return false + } return true } else { return false @@ -97,8 +83,8 @@ private enum BlockedPeerEntry: Identifiable, Comparable { } else { return false } - case let .whiteSpace(height): - if case .whiteSpace(height) = rhs { + case let .section(id): + if case .section(id) = rhs { return true } else { return false @@ -106,35 +92,24 @@ private enum BlockedPeerEntry: Identifiable, Comparable { } } - static func <(lhs: BlockedPeerEntry, rhs: BlockedPeerEntry) -> Bool { - switch lhs { - case let .peerItem(index, _, _, _): - switch rhs { - case let .peerItem(rhsIndex, _, _, _): - return index < rhsIndex - case .empty: - return false - case .whiteSpace: - return false - } + var index: Int32 { + switch self { case .empty: - if case .empty = rhs { - return true - } else { - return false - } - case .whiteSpace: - if case .whiteSpace = rhs { - return true - } else { - return false - } + return 0 + case let .peerItem(sectionId, index, _, _, _, _): + return (sectionId * 1000) + index + case let .section(sectionId): + return (sectionId * 1000) + sectionId } } + static func <(lhs: BlockedPeerEntry, rhs: BlockedPeerEntry) -> Bool { + return lhs.index < rhs.index + } + func item(_ arguments: BlockedPeerControllerArguments, initialSize:NSSize) -> TableRowItem { switch self { - case let .peerItem(_, peer, editing, enabled): + case let .peerItem(_, _, peer, editing, enabled, viewType): let interactionType:ShortPeerItemInteractionType if let editing = editing { @@ -146,11 +121,22 @@ private enum BlockedPeerEntry: Identifiable, Comparable { interactionType = .plain } - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: enabled, height:44, photoSize: NSMakeSize(32, 32), drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, action: {}) + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, enabled: enabled, height: 46, photoSize: NSMakeSize(32, 32), inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, viewType: viewType, action: { + arguments.openPeer(peer.id) + }, contextMenuItems: { + if case .plain = interactionType { + return .single([ContextMenuItem(tr(L10n.chatInputUnblock), handler: { + arguments.removePeer(peer.id) + })]) + } else { + return .single([]) + } + + }) case let .empty(progress): - return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: progress, text: tr(.blockedPeersEmptyDescrpition)) - case let .whiteSpace(height): - return GeneralRowItem(initialSize, height: height, stableId: stableId) + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: progress, text: L10n.blockedPeersEmptyDescrpition, viewType: .singleItem) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } @@ -193,30 +179,33 @@ private struct BlockedPeerControllerState: Equatable { } } -private func blockedPeersControllerEntries(state: BlockedPeerControllerState, peers: [Peer]?) -> [BlockedPeerEntry] { +private func blockedPeersControllerEntries(state: BlockedPeerControllerState, blockedState: BlockedPeersContextState) -> [BlockedPeerEntry] { var entries: [BlockedPeerEntry] = [] - if let peers = peers { - var index: Int32 = 0 - - if !peers.isEmpty { - entries.append(.whiteSpace(16)) - } - - for peer in peers { + var index: Int32 = 0 + var sectionId: Int32 = 0 + if !blockedState.peers.isEmpty { + entries.append(.section(sectionId)) + sectionId += 1 + } + for rendered in blockedState.peers { + if let peer = rendered.peer { var deleting:ShortPeerDeleting? = nil if state.editing { deleting = ShortPeerDeleting(editable: true) } - - entries.append(.peerItem(index, peer, deleting, state.removingPeerId != peer.id)) + + entries.append(.peerItem(sectionId, index, peer, deleting, state.removingPeerId != peer.id, bestGeneralViewType(blockedState.peers, for: rendered))) index += 1 } - } - if entries.isEmpty { - entries.append(.empty(peers == nil)) + + if blockedState.peers.isEmpty { + entries.append(.empty(blockedState.peers.isEmpty)) + } else { + entries.append(.section(sectionId)) + sectionId += 1 } return entries @@ -228,7 +217,7 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry { private let disposable:MetaDisposable = MetaDisposable() - - override func viewDidLoad() { super.viewDidLoad() - let account = self.account + + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let context = self.context let updateState: ((BlockedPeerControllerState) -> BlockedPeerControllerState) -> Void = { [weak self] f in if let strongSelf = self { @@ -253,34 +245,15 @@ class BlockedPeersViewController: EditableViewController { } } - let peersPromise = Promise<[Peer]?>(nil) - - let arguments = BlockedPeerControllerArguments(account: account, removePeer: { [weak self] memberId in - + let arguments = BlockedPeerControllerArguments(context: context, removePeer: { [weak self] memberId in updateState { return $0.withUpdatedRemovingPeerId(memberId) } - - let applyPeers: Signal = peersPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { peers -> Signal in - if let peers = peers { - var updatedPeers = peers - for i in 0 ..< updatedPeers.count { - if updatedPeers[i].id == memberId { - updatedPeers.remove(at: i) - break - } - } - peersPromise.set(.single(updatedPeers)) - } - - return .complete() - } - - self?.removePeerDisposable.set((requestUpdatePeerIsBlocked(account: account, peerId: memberId, isBlocked: false) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in + self?.removePeerDisposable.set((context.blockedPeersContext.remove(peerId: memberId) |> deliverOnMainQueue).start(error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + } updateState { return $0.withUpdatedRemovingPeerId(nil) } @@ -288,25 +261,24 @@ class BlockedPeersViewController: EditableViewController { updateState { return $0.withUpdatedRemovingPeerId(nil) } - })) + }, openPeer: { [weak self] peerId in + guard let `self` = self else {return} + self.navigationController?.push(PeerInfoController(context: self.context, peerId: peerId)) }) - let peersSignal: Signal<[Peer]?, NoError> = .single(nil) |> then(requestBlockedPeers(account: account) |> map { Optional($0) }) - - peersPromise.set(peersSignal) let initialSize = atomicSize let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let signal = combineLatest(statePromise.get(), peersPromise.get(), appearanceSignal) + let signal = combineLatest(statePromise.get(), context.blockedPeersContext.state, appearanceSignal) |> deliverOnMainQueue - |> map { state, peers, appearance -> TableUpdateTransition in - let entries = blockedPeersControllerEntries(state: state, peers: peers).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + |> map { state, blockedState, appearance -> TableUpdateTransition in + let entries = blockedPeersControllerEntries(state: state, blockedState: blockedState).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) - } + } disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] transition in if let strongSelf = self { @@ -315,6 +287,15 @@ class BlockedPeersViewController: EditableViewController { strongSelf.rightBarView.isHidden = strongSelf.genericView.item(at: 0) is SearchEmptyRowItem } })) + + genericView.setScrollHandler { position in + switch position.direction { + case .bottom: + context.blockedPeersContext.loadMore() + default: + break + } + } } deinit { diff --git a/Telegram-Mac/CachedAdminIds.swift b/Telegram-Mac/CachedAdminIds.swift index d5d380cd9b..e4010c98d1 100644 --- a/Telegram-Mac/CachedAdminIds.swift +++ b/Telegram-Mac/CachedAdminIds.swift @@ -7,9 +7,10 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private final class CachedAdminIdsContext { var hash:Int32 = 0 @@ -21,11 +22,7 @@ private func hashForIdsReverse(_ ids: [Int32]) -> Int32 { var acc: UInt32 = 0 for id in ids { - let low = UInt32(UInt32(bitPattern: id) & (0xffffffff as UInt32)) - let high = UInt32((UInt32(bitPattern: id) >> 32) & (0xffffffff as UInt32)) - - acc = (acc &* 20261) &+ high - acc = (acc &* 20261) &+ low + acc = (acc &* 20261) &+ UInt32(bitPattern: id) } return Int32(bitPattern: acc & UInt32(0x7FFFFFFF)) } @@ -37,7 +34,7 @@ class CachedAdminIds: NSObject { private var idsContexts: [PeerId: CachedAdminIdsContext] = [:] private var disposableTokens:[PeerId: Disposable] = [:] - func ids(postbox: Postbox, network:Network, peerId:PeerId) -> Signal<[PeerId], Void> { + func ids(postbox: Postbox, network:Network, peerId:PeerId) -> Signal<[PeerId], NoError> { if peerId.namespace != Namespaces.Peer.CloudChannel { return .single([]) } @@ -64,7 +61,7 @@ class CachedAdminIds: NSObject { let signal = channelAdminIds(postbox: postbox, network: network, peerId: peerId, hash: idsContexts.hash) |> deliverOn(self.statusQueue) |> then( deferred { - return channelAdminIds(postbox: postbox, network: network, peerId: peerId, hash: idsContexts.hash) |> delay(60, queue: self.statusQueue) + return channelAdminIds(postbox: postbox, network: network, peerId: peerId, hash: idsContexts.hash) |> delay(60 * 5, queue: self.statusQueue) } |> restart) self.disposableTokens[peerId] = signal.start(next: { ids in diff --git a/Telegram-Mac/CachedChannelAdmins.swift b/Telegram-Mac/CachedChannelAdmins.swift new file mode 100644 index 0000000000..b8888e176a --- /dev/null +++ b/Telegram-Mac/CachedChannelAdmins.swift @@ -0,0 +1,86 @@ +// +// CachedChannelAdmins.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + +enum CachedChannelAdminRank: PostboxCoding, Equatable { + case owner + case admin + case custom(String) + + init(decoder: PostboxDecoder) { + let value: Int32 = decoder.decodeInt32ForKey("v", orElse: 0) + switch value { + case 0: + self = .owner + case 1: + self = .admin + case 2: + self = .custom(decoder.decodeStringForKey("s", orElse: "")) + default: + self = .admin + } + } + + func encode(_ encoder: PostboxEncoder) { + switch self { + case .owner: + encoder.encodeInt32(0, forKey: "v") + case .admin: + encoder.encodeInt32(1, forKey: "v") + case let .custom(rank): + encoder.encodeInt32(2, forKey: "v") + encoder.encodeString(rank, forKey: "s") + } + } +} + +final class CachedChannelAdminRanks: PostboxCoding { + let ranks: Dictionary + + init(ranks: Dictionary) { + self.ranks = ranks + } + + init(decoder: PostboxDecoder) { + self.ranks = decoder.decodeObjectDictionaryForKey("ranks", keyDecoder: { decoder in + return PeerId(decoder.decodeInt64ForKey("k", orElse: 0)) + }, valueDecoder: { decoder in + return CachedChannelAdminRank(decoder: decoder) + }) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectDictionary(self.ranks, forKey: "ranks", keyEncoder: { key, encoder in + encoder.encodeInt64(key.toInt64(), forKey: "k") + }) + } + + static func cacheKey(peerId: PeerId) -> ValueBoxKey { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + return key + } +} + +private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200) + +func cachedChannelAdminRanksEntryId(peerId: PeerId) -> ItemCacheEntryId { + return ItemCacheEntryId(collectionId: 100, key: CachedChannelAdminRanks.cacheKey(peerId: peerId)) +} + +func updateCachedChannelAdminRanks(postbox: Postbox, peerId: PeerId, ranks: Dictionary) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: 100, key: CachedChannelAdminRanks.cacheKey(peerId: peerId)), entry: CachedChannelAdminRanks(ranks: ranks), collectionSpec: collectionSpec) + } +} diff --git a/Telegram-Mac/CachedFaqInstantPage.swift b/Telegram-Mac/CachedFaqInstantPage.swift new file mode 100644 index 0000000000..5f97ef144d --- /dev/null +++ b/Telegram-Mac/CachedFaqInstantPage.swift @@ -0,0 +1,135 @@ +// +// CachedFaqInstantPage.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private func extractAnchor(string: String) -> (String, String?) { + var anchorValue: String? + if let anchorRange = string.range(of: "#") { + let anchor = string[anchorRange.upperBound...] + if !anchor.isEmpty { + anchorValue = String(anchor) + } + } + var trimmedUrl = string + if let anchor = anchorValue, let anchorRange = string.range(of: "#\(anchor)") { + let url = string[.. Signal { + let faqUrl = "https://telegram.org/faq#general-questions" + + + let (cachedUrl, anchor) = extractAnchor(string: faqUrl) + + return cachedInstantPage(postbox: context.account.postbox, url: cachedUrl) + |> mapToSignal { cachedInstantPage -> Signal in + let updated = resolveInstantViewUrl(account: context.account, url: faqUrl) + |> afterNext { result in + if case let .instantView(_, webPage, _) = result, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { + if instantPage.isComplete { + let _ = updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage).start() + } else { + let _ = (actualizedWebpage(postbox: context.account.postbox, network: context.account.network, webpage: webPage) + |> mapToSignal { webPage -> Signal in + if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, instantPage.isComplete { + return updateCachedInstantPage(postbox: context.account.postbox, url: cachedUrl, webPage: webPage) + } else { + return .complete() + } + }).start() + } + } + } + + let now = Int32(CFAbsoluteTimeGetCurrent()) + if let cachedInstantPage = cachedInstantPage, case let .Loaded(content) = cachedInstantPage.webPage.content, let instantPage = content.instantPage, instantPage.isComplete { + let current: Signal = .single(.instantView(link: faqUrl, webpage: cachedInstantPage.webPage, anchor: anchor)) + if now > cachedInstantPage.timestamp + refreshTimeout { + return current + |> then(updated) + } else { + return current + } + } else { + return updated + } + } +} + +func faqSearchableItems(context: AccountContext) -> Signal<[SettingsSearchableItem], NoError> { + return cachedFaqInstantPage(context: context) + |> map { resolvedUrl -> [SettingsSearchableItem] in + var results: [SettingsSearchableItem] = [] + var nextIndex: Int32 = 2 + if case let .instantView(_, webPage, _) = resolvedUrl { + if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { + var processingQuestions = false + var currentSection: String? + outer: for block in instantPage.blocks { + if !processingQuestions { + switch block { + case .blockQuote: + if results.isEmpty { + processingQuestions = true + } + default: + break + } + } else { + switch block { + case let .paragraph(text): + if case .bold = text { + currentSection = text.plainText + } else if case .concat = text { + processingQuestions = false + } + case let .list(items, false): + if let currentSection = currentSection { + for item in items { + if case let .text(itemText, _) = item, case let .url(text, url, _) = itemText { + let (_, anchor) = extractAnchor(string: url) + var index = nextIndex + if anchor?.contains("delete-my-account") ?? false { + index = 1 + } else { + nextIndex += 1 + } + let item = SettingsSearchableItem(id: .faq(index), title: text.plainText, alternate: [], icon: .faq, breadcrumbs: [L10n.accountSettingsFAQ, currentSection], present: { context, _, present in + showInstantPage(InstantPageViewController(context, webPage: webPage, message: nil, anchor: anchor)) + }) + if index == 1 { + results.insert(item, at: 0) + } else { + results.append(item) + } + } + } + } + default: + break + } + } + } + } + } + return results + } +} diff --git a/Telegram-Mac/CachedInstantPages.swift b/Telegram-Mac/CachedInstantPages.swift new file mode 100644 index 0000000000..168f98c0d5 --- /dev/null +++ b/Telegram-Mac/CachedInstantPages.swift @@ -0,0 +1,61 @@ +// +// CachedInstantPages.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +public final class CachedInstantPage: PostboxCoding { + public let webPage: TelegramMediaWebpage + public let timestamp: Int32 + + public init(webPage: TelegramMediaWebpage, timestamp: Int32) { + self.webPage = webPage + self.timestamp = timestamp + } + + public init(decoder: PostboxDecoder) { + self.webPage = decoder.decodeObjectForKey("webpage", decoder: { TelegramMediaWebpage(decoder: $0) }) as! TelegramMediaWebpage + self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.webPage, forKey: "webpage") + encoder.encodeInt32(self.timestamp, forKey: "timestamp") + } +} + +public func cachedInstantPage(postbox: Postbox, url: String) -> Signal { + return postbox.transaction { transaction -> CachedInstantPage? in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: Int64(bitPattern: url.persistentHashValue)) + if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedInstantPages, key: key)) as? CachedInstantPage { + return entry + } else { + return nil + } + } +} + +private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 5, highWaterItemCount: 10) + +public func updateCachedInstantPage(postbox: Postbox, url: String, webPage: TelegramMediaWebpage?) -> Signal { + return postbox.transaction { transaction -> Void in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: Int64(bitPattern: url.persistentHashValue)) + let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedInstantPages, key: key) + if let webPage = webPage { + transaction.putItemCacheEntry(id: id, entry: CachedInstantPage(webPage: webPage, timestamp: Int32(CFAbsoluteTimeGetCurrent())), collectionSpec: collectionSpec) + } else { + transaction.removeItemCacheEntry(id: id) + } + } +} diff --git a/Telegram-Mac/CachedResourceRepresentations.swift b/Telegram-Mac/CachedResourceRepresentations.swift index 4812c4fc09..8470e02e98 100644 --- a/Telegram-Mac/CachedResourceRepresentations.swift +++ b/Telegram-Mac/CachedResourceRepresentations.swift @@ -8,17 +8,19 @@ import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore final class CachedStickerAJpegRepresentation: CachedMediaResourceRepresentation { let size: CGSize? - + var keepDuration: CachedMediaRepresentationKeepDuration = .general var uniqueId: String { if let size = self.size { - return "sticker-ajpeg-\(Int(size.width))x\(Int(size.height))" + return "sticker-v1-png-\(Int(size.width))x\(Int(size.height))" } else { - return "sticker-ajpeg" + return "sticker-v1-png" } } @@ -35,9 +37,9 @@ final class CachedStickerAJpegRepresentation: CachedMediaResourceRepresentation } } -final class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { +class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { let size: CGSize - + var keepDuration: CachedMediaRepresentationKeepDuration = .general var uniqueId: String { return "scaled-image-\(Int(self.size.width))x\(Int(self.size.height))" } @@ -55,7 +57,11 @@ final class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { } } + + final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { + var keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { return "first-frame" } @@ -68,3 +74,156 @@ final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentati } } } + +final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { + let size: CGSize + var keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { + return "scaled-frame-\(Int(self.size.width))x\(Int(self.size.height))" + } + + init(size: CGSize) { + self.size = size + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedScaledVideoFirstFrameRepresentation { + return self.size == to.size + } else { + return false + } + } +} +final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepresentation { + var keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { + return CachedBlurredWallpaperRepresentation.uniqueId + } + + static var uniqueId: String { + return "blurred-wallpaper" + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if to is CachedBlurredWallpaperRepresentation { + return true + } else { + return false + } + } +} + + +final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation { + var keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { + let version: Int = 1 + if let fitzModifier = self.fitzModifier { + return "animated-sticker-v\(version)-\(self.thumb ? 1 : 0)-w:\(size.width)-h:\(size.height)-fitz\(fitzModifier.rawValue)" + } else { + return "animated-sticker-v\(version)-\(self.thumb ? 1 : 0)-w:\(size.width)-h:\(size.height)" + } + } + let thumb: Bool + let size: NSSize + let fitzModifier: EmojiFitzModifier? + init(thumb: Bool, size: NSSize, fitzModifier: EmojiFitzModifier? = nil) { + self.thumb = thumb + self.size = size + self.fitzModifier = fitzModifier + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedAnimatedStickerRepresentation { + return self.thumb == to.thumb && self.size == to.size && self.fitzModifier == to.fitzModifier + } else { + return false + } + } +} + +final class CachedPatternWallpaperMaskRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + + let size: CGSize? + let settings: WallpaperSettings? + var uniqueId: String { + + var color:String = "" + + if let settings = settings { + color += settings.stringValue + } + + if let size = self.size { + return "pattern-wallpaper-mask----\(Int(size.width))x\(Int(size.height))" + color + } else { + return "pattern-wallpaper-mask----" + color + } + } + + init(size: CGSize? = nil, settings: WallpaperSettings? = nil) { + self.size = size + self.settings = settings + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedPatternWallpaperMaskRepresentation { + return self.size == to.size && self.settings == to.settings + } else { + return false + } + } +} + + +final class CachedDiceRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let emoji: String + let value: String + let size: NSSize + var uniqueId: String { + return emoji + value + ":dice2" + } + + init(emoji: String, value: String, size: NSSize) { + self.value = value + self.size = size + self.emoji = emoji + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedDiceRepresentation { + return self.value == to.value && self.size == to.size && self.emoji == to.emoji + } else { + return false + } + } +} + + + +public enum EmojiFitzModifier: Int32, Equatable { + case type12 + case type3 + case type4 + case type5 + case type6 + + public init?(emoji: String) { + switch emoji.unicodeScalars.first?.value { + case 0x1f3fb: + self = .type12 + case 0x1f3fc: + self = .type3 + case 0x1f3fd: + self = .type4 + case 0x1f3fe: + self = .type5 + case 0x1f3ff: + self = .type6 + default: + return nil + } + } +} diff --git a/Telegram-Mac/CalendarController.swift b/Telegram-Mac/CalendarController.swift index b537ebc5b0..61001314a3 100644 --- a/Telegram-Mac/CalendarController.swift +++ b/Telegram-Mac/CalendarController.swift @@ -11,30 +11,30 @@ import TGUIKit class CalendarControllerView : View { +} + +private final class CalendarNavigation : NavigationViewController { + + + } class CalendarController: GenericViewController { - private var navigation:NavigationViewController! + private var navigation:CalendarNavigation! private var interactions:CalendarMonthInteractions! + private let onlyFuture: Bool + private let current: Date override func viewDidLoad() { super.viewDidLoad() addSubview(navigation.view) readyOnce() } - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - bar = .init(height: 0) - } - override init() { - super.init() - bar = .init(height: 0) - } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + self.navigation.viewDidAppear(animated) self.window?.set(handler: { [weak self] () -> KeyHandlerResult in if let current = self?.navigation.controller as? CalendarMonthController, current.isPrevEnabled, let backAction = self?.interactions.backAction { @@ -53,11 +53,23 @@ class CalendarController: GenericViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + self.navigation.viewWillDisappear(animated) self.window?.remove(object: self, for: .LeftArrow) self.window?.remove(object: self, for: .RightArrow) } - init(_ frameRect:NSRect, selectHandler:@escaping (Date)->Void) { + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.navigation.viewDidDisappear(animated) + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigation.viewWillAppear(animated) + } + + init(_ frameRect:NSRect, _ window: Window, current: Date = Date(), onlyFuture: Bool = false, selectHandler:@escaping (Date)->Void) { + self.onlyFuture = onlyFuture + self.current = current super.init(frame: frameRect) bar = .init(height: 0) self.interactions = CalendarMonthInteractions(selectAction: { [weak self] (selected) in @@ -71,14 +83,23 @@ class CalendarController: GenericViewController { if let strongSelf = self { strongSelf.navigation.push(strongSelf.stepMonth(date: CalendarUtils.stepMonth(1, date: date)), style: .push) } + }, changeYear: { [weak self] year, date in + if let strongSelf = self { + strongSelf.navigation.push(strongSelf.stepMonth(date: CalendarUtils.year(Int(year), date: date)), style: .push) + } }) - self.navigation = NavigationViewController(stepMonth(date: Date())) + self.navigation = CalendarNavigation(stepMonth(date: current), window) self.navigation._frameRect = frameRect + } func stepMonth(date:Date) -> CalendarMonthController { - return CalendarMonthController(date, interactions: interactions) + return CalendarMonthController(date, onlyFuture: self.onlyFuture, selectDayAnyway: CalendarUtils.isSameDate(current, date: date, checkDay: false), interactions: interactions) + } + + override var isAutoclosePopover: Bool { + return false } } diff --git a/Telegram-Mac/CalendarMonthController.swift b/Telegram-Mac/CalendarMonthController.swift index fcc7215d73..95148e1bbf 100644 --- a/Telegram-Mac/CalendarMonthController.swift +++ b/Telegram-Mac/CalendarMonthController.swift @@ -13,14 +13,16 @@ struct CalendarMonthInteractions { let selectAction:(Date)->Void let backAction:((Date)->Void)? let nextAction:((Date)->Void)? - init(selectAction:@escaping (Date)->Void, backAction:((Date)->Void)? = nil, nextAction:((Date)->Void)? = nil) { + let changeYear: (Int32, Date)->Void + init(selectAction:@escaping (Date)->Void, backAction:((Date)->Void)? = nil, nextAction:((Date)->Void)? = nil, changeYear: @escaping(Int32, Date)->Void) { self.selectAction = selectAction self.backAction = backAction self.nextAction = nextAction + self.changeYear = changeYear } } -struct CalendarMonthStruct { +final class CalendarMonthStruct { let month:Date let prevMonth:Date let nextMonth:Date @@ -30,12 +32,14 @@ struct CalendarMonthStruct { let lastDayOfNextMonth:Int let currentStartDay:Int - let selectedDay:Int? + var selectedDay:Int? let components:DateComponents let dayHandler:(Int)->Void - init(month:Date, dayHandler:@escaping (Int)->Void) { + let onlyFuture: Bool + init(month:Date, selectDayAnyway: Bool, onlyFuture: Bool, dayHandler:@escaping (Int)->Void) { self.month = month + self.onlyFuture = onlyFuture self.dayHandler = dayHandler self.prevMonth = CalendarUtils.stepMonth(-1, date: month) self.nextMonth = CalendarUtils.stepMonth(1, date: month) @@ -44,11 +48,11 @@ struct CalendarMonthStruct { self.lastDayOfNextMonth = CalendarUtils.lastDay(ofTheMonth: month) var calendar = NSCalendar.current - calendar.timeZone = TimeZone(abbreviation: "UTC")! +// calendar.timeZone = TimeZone(abbreviation: "UTC")! let components = calendar.dateComponents([.year, .month, .day], from: month) self.currentStartDay = CalendarUtils.weekDay(Date(timeIntervalSince1970: month.timeIntervalSince1970 - TimeInterval(components.day! * 24*60*60))) - if CalendarUtils.isSameDate(month, date: Date(), checkDay: false) { + if selectDayAnyway { selectedDay = components.day! } else { selectedDay = nil @@ -77,41 +81,67 @@ class CalendarMonthView : View { let day = TitleButton() day.set(font: .normal(.text), for: .Normal) day.set(background: theme.colors.background, for: .Normal) + let current:Int if i + 1 < month.currentStartDay { current = (month.lastDayOfPrevMonth - month.currentStartDay) + i + 2 - day.set(color: .grayText, for: .Normal) + day.set(color: theme.colors.grayText, for: .Normal) } else if (i + 2) - month.currentStartDay > month.lastDayOfMonth { current = (i + 2) - (month.currentStartDay + month.lastDayOfMonth) - day.set(color: .grayText, for: .Normal) + day.set(color: theme.colors.grayText, for: .Normal) } else { current = (i + 1) - month.currentStartDay + 1 - day.set(color: .white, for: .Highlight) - - if (i + 1) % 7 == 0 || (i + 2) % 7 == 0 { - day.set(color: theme.colors.redUI, for: .Normal) - } else { - day.set(color: theme.colors.text, for: .Normal) - } + var skipDay: Bool = false - day.layer?.cornerRadius = .cornerRadius + var calendar = NSCalendar.current +// calendar.timeZone = TimeZone(abbreviation: "UTC")! + let components = calendar.dateComponents([.day, .year, .month], from: Date()) - if let selectedDay = month.selectedDay, current == selectedDay { - day.isSelected = true - day.set(background: theme.colors.blueSelect, for: .Highlight) - day.apply(state: .Highlight) - } else { - day.set(background: theme.colors.blueUI, for: .Highlight) + if month.onlyFuture, CalendarUtils.isSameDate(month.month, date: Date(), checkDay: false) { + if current < components.day! { + day.set(color: theme.colors.grayText, for: .Normal) + skipDay = true + } + } else if month.onlyFuture, components.year! + 1 == month.components.year! && components.month! == month.components.month! { + if current > components.day! { + day.set(color: theme.colors.grayText, for: .Normal) + skipDay = true + } + } else if CalendarUtils.isSameDate(month.month, date: Date(), checkDay: false), current > components.day! { + day.set(color: theme.colors.grayText, for: .Normal) + skipDay = true } - - day.set(handler: { (control) in + if !skipDay { + day.set(color: theme.colors.underSelectedColor, for: .Highlight) + + if (i + 1) % 7 == 0 || (i + 2) % 7 == 0 { + day.set(color: theme.colors.redUI, for: .Normal) + } else { + day.set(color: theme.colors.text, for: .Normal) + } - month.dayHandler(current) + day.layer?.cornerRadius = .cornerRadius - }, for: .Click) - + if let selectedDay = month.selectedDay, current == selectedDay { + // day.isSelected = true + day.set(color: theme.colors.underSelectedColor, for: .Normal) + + day.set(background: theme.colors.accent, for: .Normal) + day.set(background: theme.colors.accent, for: .Highlight) + } else { + day.set(background: theme.colors.background, for: .Normal) + day.set(background: theme.colors.accent, for: .Highlight) + } + + day.set(handler: { [weak self] (control) in + month.selectedDay = current + month.dayHandler(current) + self?.layout(for: month) + + }, for: .Click) + } } day.set(text: "\(current)", for: .Normal) @@ -123,7 +153,7 @@ class CalendarMonthView : View { override func layout() { super.layout() - let oneSize:NSSize = NSMakeSize(floorToScreenPixels(frame.width / 7), floorToScreenPixels(frame.height / 6)) + let oneSize:NSSize = NSMakeSize(floorToScreenPixels(backingScaleFactor, frame.width / 7), floorToScreenPixels(backingScaleFactor, frame.height / 6)) var inset:NSPoint = NSMakePoint(0, 0) for i in 0 ..< subviews.count { subviews[i].frame = NSMakeRect(inset.x, inset.y, oneSize.width, oneSize.height) @@ -147,8 +177,10 @@ class CalendarMonthView : View { class CalendarMonthController: GenericViewController { let interactions:CalendarMonthInteractions let month:CalendarMonthStruct - init(_ month:Date, interactions:CalendarMonthInteractions) { - self.month = CalendarMonthStruct(month: month, dayHandler: { day in + let onlyFuture: Bool + init(_ month:Date, onlyFuture: Bool, selectDayAnyway: Bool, interactions:CalendarMonthInteractions) { + self.onlyFuture = onlyFuture + self.month = CalendarMonthStruct(month: month, selectDayAnyway: selectDayAnyway, onlyFuture: self.onlyFuture, dayHandler: { day in interactions.selectAction(CalendarUtils.monthDay(day, date: month)) }) self.interactions = interactions @@ -159,20 +191,68 @@ class CalendarMonthController: GenericViewController { override func getCenterBarViewOnce() -> TitledBarView { let formatter:DateFormatter = DateFormatter() - formatter.locale = Locale(identifier: "en_US") + formatter.locale = Locale(identifier: appAppearance.language.languageCode) formatter.dateFormat = "MMMM" let monthString:String = formatter.string(from: month.month) formatter.dateFormat = "yyyy" let yearString:String = formatter.string(from: month.month) - return TitledBarView(controller: self, .initialize(string: monthString, color: theme.colors.text, font:.medium(.text)), .initialize(string:yearString, color: theme.colors.grayText, font:.normal(.small))) + let barView = TitledBarView(controller: self, .initialize(string: monthString, color: theme.colors.text, font:.medium(.text)), .initialize(string:yearString, color: theme.colors.grayText, font:.normal(.small))) + + barView.set(handler: { [weak self] control in + + guard let `self` = self else { + return + } + + let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + var now: time_t = time_t(nowTimestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + + var items:[SPopoverItem] = [] + + for i in stride(from: 1900 + timeinfoNow.tm_year - 1, to: 2012, by: -1) { + items.append(.init("\(i)", { [weak self] in + guard let `self` = self else { + return + } + self.interactions.changeYear(i, self.month.month) + })) + } + if !items.isEmpty { + showPopover(for: control, with: SPopoverViewController(items: items), edge: .maxY, inset: NSMakePoint(30, -50)) + } + + }, for: .Click) + + return barView } var isNextEnabled:Bool { + if self.onlyFuture { + + var calendar = NSCalendar.current + +// calendar.timeZone = TimeZone(abbreviation: "UTC")! + let components = calendar.dateComponents([.year, .month, .day], from: Date()) + + + if month.components.year! == components.year! { + return true + } else if components.year! + 1 == month.components.year! { + return month.components.month! < components.month! + } + return true + } return !CalendarUtils.isSameDate(month.month, date: Date(), checkDay: false) } var isPrevEnabled:Bool { + if self.onlyFuture { + return !CalendarUtils.isSameDate(month.month, date: Date(), checkDay: false) + } return month.components.year! > 2013 || (month.components.year == 2013 && month.components.month! >= 9) } @@ -224,7 +304,10 @@ class CalendarMonthController: GenericViewController { readyOnce() } - + deinit { + var bp:Int = 0 + bp += 1 + } } diff --git a/Telegram-Mac/CalendarUtils.h b/Telegram-Mac/CalendarUtils.h index c0304bc20c..a4da0ab786 100644 --- a/Telegram-Mac/CalendarUtils.h +++ b/Telegram-Mac/CalendarUtils.h @@ -12,5 +12,5 @@ + (NSDate*) monthDay:(NSInteger)day date:(NSDate *)date; + (NSInteger)weekDay:(NSDate *)date; + (NSDate *) stepMonth:(NSInteger)dm date:(NSDate *)date; - ++ (NSDate *) year:(NSInteger)dm date:(NSDate *)date; @end diff --git a/Telegram-Mac/CalendarUtils.m b/Telegram-Mac/CalendarUtils.m index 0d3e4a59c8..82e8b08ac0 100644 --- a/Telegram-Mac/CalendarUtils.m +++ b/Telegram-Mac/CalendarUtils.m @@ -7,7 +7,7 @@ @implementation CalendarUtils + (BOOL) isSameDate:(NSDate*)d1 date:(NSDate*)d2 checkDay:(BOOL)checkDay { if(d1 && d2) { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + //cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; unsigned unitFlags = NSCalendarUnitDay | NSCalendarUnitYear | NSCalendarUnitMonth; NSDateComponents *components = [cal components:unitFlags fromDate:d1]; NSInteger ry = components.year; @@ -26,7 +26,7 @@ + (BOOL) isSameDate:(NSDate*)d1 date:(NSDate*)d2 checkDay:(BOOL)checkDay { + (NSDate*) monthDay:(NSInteger)day date:(NSDate *)date { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; +// cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; unsigned unitFlags = NSCalendarUnitDay| NSCalendarUnitYear | NSCalendarUnitMonth; NSDateComponents *components = [cal components:unitFlags fromDate:date]; NSDateComponents *comps = [[NSDateComponents alloc] init]; @@ -52,14 +52,14 @@ +(NSInteger)weekDay:(NSDate *)date { + (NSInteger) lastDayOfTheMonth:(NSDate *)date { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + // cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; NSRange daysRange = [cal rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date]; return daysRange.length; } + (NSInteger) colForDay:(NSInteger)day { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + // cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; NSInteger idx = day - cal.firstWeekday; if(idx < 0) idx = 7 + idx; @@ -68,7 +68,7 @@ + (NSInteger) colForDay:(NSInteger)day { + (NSString*) dd:(NSDate*)d { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + // cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; unsigned unitFlags = NSCalendarUnitDay | NSCalendarUnitYear | NSCalendarUnitMonth; NSDateComponents *cpt = [cal components:unitFlags fromDate:d]; return [NSString stringWithFormat:@"%ld-%ld-%ld",cpt.year, cpt.month, cpt.day]; @@ -76,7 +76,7 @@ + (NSString*) dd:(NSDate*)d { + (NSDate *) stepMonth:(NSInteger)dm date:(NSDate *)date { NSCalendar *cal = [NSCalendar currentCalendar]; - cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + // cal.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; unsigned unitFlags = NSCalendarUnitDay| NSCalendarUnitYear | NSCalendarUnitMonth; NSDateComponents *components = [cal components:unitFlags fromDate:date]; NSInteger month = components.month + dm; @@ -89,9 +89,20 @@ + (NSDate *) stepMonth:(NSInteger)dm date:(NSDate *)date { month = 12; year--; } + components.day = 1; components.year = year; components.month = month; return [cal dateFromComponents:components]; } ++ (NSDate *) year:(NSInteger)dm date:(NSDate *)date { + NSCalendar *cal = [NSCalendar currentCalendar]; + unsigned unitFlags = NSCalendarUnitDay| NSCalendarUnitYear | NSCalendarUnitMonth; + NSDateComponents *components = [cal components:unitFlags fromDate:date]; + components.day = 1; + components.year = dm; + components.month = components.month; + return [cal dateFromComponents:components]; +} + @end diff --git a/Telegram-Mac/CallBridge.h b/Telegram-Mac/CallBridge.h deleted file mode 100644 index 2985138b9b..0000000000 --- a/Telegram-Mac/CallBridge.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// CallsBridge.h -// Telegram -// -// Created by keepcoder on 03/05/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -#import -#import "TGCallConnectionDescription.h" - -@interface AudioDevice : NSObject -@property(nonatomic, strong, readonly) NSString *deviceId; -@property(nonatomic, strong, readonly) NSString *deviceName; --(id)initWithDeviceId:(NSString*)deviceId deviceName:(NSString *)deviceName; -@end - -@interface CallBridge : NSObject --(void)startTransmissionIfNeeded:(bool)outgoing connection:(TGCallConnection *)connection; - --(void)mute; --(void)unmute; --(BOOL)isMuted; - --(NSString *)currentOutputDeviceId; --(NSString *)currentInputDeviceId; --(NSArray *)outputDevices; --(NSArray *)inputDevices; --(void)setCurrentOutputDeviceId:(NSString *)deviceId; --(void)setCurrentInputDeviceId:(NSString *)deviceId; -@property (nonatomic, copy) void (^stateChangeHandler)(int); - -@end diff --git a/Telegram-Mac/CallBridge.mm b/Telegram-Mac/CallBridge.mm index dd1f25f8f2..6982e49af5 100644 --- a/Telegram-Mac/CallBridge.mm +++ b/Telegram-Mac/CallBridge.mm @@ -6,19 +6,97 @@ // Copyright © 2017 Telegram. All rights reserved. // -#import "CallBridge.h" +#import "OngoingCallThreadLocalContext.h" #import "VoIPController.h" #import "VoIPServerConfig.h" #import "TGCallUtils.h" -#import "TGCallConnectionDescription.h" +#import "OngoingCallConnectionDescription.h" +#import "TgVoip.h" #define CVoIPController tgvoip::VoIPController +#import +#import + +void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { + MTAesEncryptRaw(inBytes, outBytes, length, key, iv); +} + +void TGCallAesIgeDecrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { + MTAesDecryptRaw(inBytes, outBytes, length, key, iv); +} + +void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output) { + MTRawSha1(msg, length, output); +} + +void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output) { + MTRawSha256(msg, length, output); +} + +void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num) { + uint8_t *outData = (uint8_t *)malloc(length); + MTAesCtr *aesCtr = [[MTAesCtr alloc] initWithKey:key keyLength:32 iv:iv ecount:ecount num:*num]; + [aesCtr encryptIn:inOut out:outData len:length]; + memcpy(inOut, outData, length); + free(outData); + + [aesCtr getIv:iv]; + + memcpy(ecount, [aesCtr ecount], 16); + *num = [aesCtr num]; +} + +void TGCallRandomBytes(uint8_t *buffer, size_t length) { + arc4random_buf(buffer, length); +} + +static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkType type) { + switch (type) { + case OngoingCallNetworkTypeWifi: + return TgVoipNetworkType::WiFi; + case OngoingCallNetworkTypeCellularGprs: + return TgVoipNetworkType::Gprs; + case OngoingCallNetworkTypeCellular3g: + return TgVoipNetworkType::ThirdGeneration; + case OngoingCallNetworkTypeCellularLte: + return TgVoipNetworkType::Lte; + default: + return TgVoipNetworkType::ThirdGeneration; + } +} + +static TgVoipDataSaving callControllerDataSavingForType(OngoingCallDataSaving type) { + switch (type) { + case OngoingCallDataSavingNever: + return TgVoipDataSaving::Never; + case OngoingCallDataSavingCellular: + return TgVoipDataSaving::Mobile; + case OngoingCallDataSavingAlways: + return TgVoipDataSaving::Always; + default: + return TgVoipDataSaving::Never; + } +} + + + +@implementation CProxy +-(id)initWithHost:(NSString*)host port:(int32_t)port user:(NSString *)user pass:(NSString *)pass { + self = [super init]; + _host = host; + _port = port; + _user = user; + _pass = pass; + return self; +} +@end + @interface VoIPControllerHolder : NSObject { tgvoip::VoIPController *_controller; } - + @property (nonatomic, assign, readonly) tgvoip::VoIPController *controller; - + @end @implementation AudioDevice @@ -37,7 +115,7 @@ -(id)initWithDeviceId:(NSString *)deviceId deviceName:(NSString *)deviceName { const NSTimeInterval TGCallPacketTimeout = 10; @implementation VoIPControllerHolder - + - (instancetype)initWithController:( tgvoip::VoIPController *)controller { self = [super init]; if (self != nil) { @@ -45,51 +123,247 @@ - (instancetype)initWithController:( tgvoip::VoIPController *)controller { } return self; } - + - ( tgvoip::VoIPController *)controller { return _controller; } -(void)dealloc { + _controller->Stop(); delete _controller; int bp = 0; bp++; } - + @end -@interface CallBridge () +@interface OngoingCallThreadLocalContext () { + int32_t _contextId; + + OngoingCallNetworkType _networkType; + NSTimeInterval _callReceiveTimeout; + NSTimeInterval _callRingTimeout; + NSTimeInterval _callConnectTimeout; + NSTimeInterval _callPacketTimeout; + + TgVoip *_tgVoip; + + OngoingCallState _state; + int32_t _signalBars; + NSData *_lastDerivedState; + +} @property (nonatomic, strong) VoIPControllerHolder *controller; @property (nonatomic, assign) BOOL _isMuted; - (void)controllerStateChanged:(int)state; + + + @end static void controllerStateCallback(tgvoip::VoIPController *controller, int state) { - CallBridge *session = (__bridge CallBridge *)controller->implData; + OngoingCallThreadLocalContext *session = (__bridge OngoingCallThreadLocalContext *)controller->implData; [session controllerStateChanged:state]; } +static MTAtomic *callContexts() { + static MTAtomic *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[MTAtomic alloc] initWithValue:[[NSMutableDictionary alloc] init]]; + }); + return instance; +} + + +@interface OngoingCallThreadLocalContextReference : NSObject + +@property (nonatomic, weak) OngoingCallThreadLocalContext *context; +@property (nonatomic, strong, readonly) id queue; + +@end + + +@implementation OngoingCallThreadLocalContextReference + +- (instancetype)initWithContext:(OngoingCallThreadLocalContext *)context queue:(id)queue { + self = [super init]; + if (self != nil) { + self.context = context; + _queue = queue; + } + return self; +} + +@end + + +static int32_t nextId = 1; + +static int32_t addContext(OngoingCallThreadLocalContext *context, id queue) { + int32_t contextId = OSAtomicIncrement32(&nextId); + [callContexts() with:^id(NSMutableDictionary *dict) { + dict[@(contextId)] = [[OngoingCallThreadLocalContextReference alloc] initWithContext:context queue:queue]; + return nil; + }]; + return contextId; +} + +static void removeContext(int32_t contextId) { + [callContexts() with:^id(NSMutableDictionary *dict) { + [dict removeObjectForKey:@(contextId)]; + return nil; + }]; +} + +static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalContext *)) { + __block OngoingCallThreadLocalContextReference *reference = nil; + [callContexts() with:^id(NSMutableDictionary *dict) { + reference = dict[@(contextId)]; + return nil; + }]; + if (reference != nil) { + [reference.queue dispatch:^{ + __strong OngoingCallThreadLocalContext *context = reference.context; + if (context != nil) { + f(context); + } + }]; + } +} + -@implementation CallBridge +@implementation OngoingCallThreadLocalContext --(id)init { ++ (int32_t)maxLayer { + return 92; +} + + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { self = [super init]; if (self != nil) { - CVoIPController *controller = new CVoIPController(); - controller->implData = (__bridge void *)self; - controller->SetStateCallback(&controllerStateCallback); - - CVoIPController::crypto.sha1 = &TGCallSha1; - CVoIPController::crypto.sha256 = &TGCallSha256; - CVoIPController::crypto.rand_bytes = &TGCallRandomBytes; - CVoIPController::crypto.aes_ige_encrypt = &TGCallAesIgeEncryptInplace; - CVoIPController::crypto.aes_ige_decrypt = &TGCallAesIgeDecryptInplace; - CVoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; - _controller = [[VoIPControllerHolder alloc] initWithController:controller]; + + _contextId = addContext(self, queue); + + _callReceiveTimeout = 20.0; + _callRingTimeout = 90.0; + _callConnectTimeout = 30.0; + _callPacketTimeout = 10.0; + _networkType = networkType; + + + + std::unique_ptr proxyValue = nullptr; + if (proxy != nil) { + TgVoipProxy *proxyObject = new TgVoipProxy(); + proxyObject->host = proxy.host.UTF8String; + proxyObject->port = (uint16_t)proxy.port; + proxyObject->login = proxy.user.UTF8String ?: ""; + proxyObject->password = proxy.pass.UTF8String ?: ""; + proxyValue = std::unique_ptr(proxyObject); + } + + + + TgVoipCrypto crypto; + crypto.sha1 = &TGCallSha1; + crypto.sha256 = &TGCallSha256; + crypto.rand_bytes = &TGCallRandomBytes; + crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; + crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; + crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; + + std::vector endpoints; + NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; + for (OngoingCallConnectionDescription *connection in connections) { + unsigned char peerTag[16]; + [connection.peerTag getBytes:peerTag length:16]; + + TgVoipEndpoint endpoint; + endpoint.endpointId = connection.identifier; + endpoint.host = { + .ipv4 = std::string(connection.ipv4.UTF8String), + .ipv6 = std::string(connection.ipv6.UTF8String) + }; + endpoint.port = (uint16_t)connection.port; + endpoint.type = TgVoipEndpointType::UdpRelay; + memcpy(endpoint.peerTag, peerTag, 16); + endpoints.push_back(endpoint); + } + + TgVoipConfig config = { + .initializationTimeout = _callConnectTimeout, + .receiveTimeout = _callPacketTimeout, + .dataSaving = callControllerDataSavingForType(dataSaving), + .enableP2P = static_cast(allowP2P), + .enableAEC = false, + .enableNS = true, + .enableAGC = true, + .enableCallUpgrade = false, + .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), + .maxApiLayer = [OngoingCallThreadLocalContext maxLayer] + }; + + std::vector encryptionKeyValue; + encryptionKeyValue.resize(key.length); + memcpy(encryptionKeyValue.data(), key.bytes, key.length); + + TgVoipEncryptionKey encryptionKey = { + .value = encryptionKeyValue, + .isOutgoing = isOutgoing, + }; + + + _tgVoip = TgVoip::makeInstance( + config, + { derivedStateValue }, + endpoints, + proxyValue, + callControllerNetworkTypeForType(networkType), + encryptionKey, + crypto + ); + + _state = OngoingCallStateInitializing; + _signalBars = -1; + + __weak OngoingCallThreadLocalContext *weakSelf = self; + _tgVoip->setOnStateUpdated([weakSelf](TgVoipState state) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf controllerStateChanged:state]; + } + }); + _tgVoip->setOnSignalBarsUpdated([weakSelf](int signalBars) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf signalBarsChanged:signalBars]; + } + }); + + +// +// CVoIPController *controller = new CVoIPController(); +// controller->implData = (__bridge void *)self; +// tgvoip::VoIPController::Callbacks callbacks={0}; +// callbacks.connectionStateChanged=&controllerStateCallback; +// controller->SetCallbacks(callbacks); +// if (proxy != nil) { +// controller->SetProxy(tgvoip::PROXY_SOCKS5, std::string([proxy.host UTF8String]), proxy.port, std::string([proxy.user UTF8String]), std::string([proxy.pass UTF8String])); +// } +// +// CVoIPController::crypto.sha1 = &TGCallSha1; +// CVoIPController::crypto.sha256 = &TGCallSha256; +// CVoIPController::crypto.rand_bytes = &TGCallRandomBytes; +// CVoIPController::crypto.aes_ige_encrypt = &TGCallAesIgeEncryptInplace; +// CVoIPController::crypto.aes_ige_decrypt = &TGCallAesIgeDecryptInplace; +// CVoIPController::crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; +// _controller = [[VoIPControllerHolder alloc] initWithController:controller]; } return self; @@ -116,13 +390,19 @@ - (void)controllerStateChanged:(int)state } } ++(int32_t)voipMaxLayer { + return tgvoip::VoIPController::GetConnectionMaxLayer(); +} ++(NSString *)voipVersion { + return [NSString stringWithUTF8String:tgvoip::VoIPController::GetVersion()]; +} --(NSArray *)inputDevices { ++(NSArray *)inputDevices { - std::vector vector = _controller.controller->EnumerateAudioInputs(); + std::vector vector = tgvoip::VoIPController::EnumerateAudioInputs(); NSMutableArray * devices = [[NSMutableArray alloc] init]; - [devices addObject:[[AudioDevice alloc] initWithDeviceId:@"default" deviceName:@"Default"]]; + [devices addObject:[[AudioDevice alloc] initWithDeviceId:nil deviceName:@"Default"]]; for(std::vector::iterator it = vector.begin(); it != vector.end(); ++it) { std::string deviceId = it->id; std::string deviceName = it->displayName; @@ -132,12 +412,12 @@ - (void)controllerStateChanged:(int)state return devices; } --(NSArray *)outputDevices { ++(NSArray *)outputDevices { - std::vector vector = _controller.controller->EnumerateAudioOutputs(); + std::vector vector = tgvoip::VoIPController::EnumerateAudioOutputs(); NSMutableArray * devices = [[NSMutableArray alloc] init]; - [devices addObject:[[AudioDevice alloc] initWithDeviceId:@"default" deviceName:@"Default"]]; + [devices addObject:[[AudioDevice alloc] initWithDeviceId:nil deviceName:@"Default"]]; for(std::vector::iterator it = vector.begin(); it != vector.end(); ++it) { std::string deviceId = it->id; std::string deviceName = it->displayName; @@ -161,22 +441,28 @@ -(void)setCurrentInputDeviceId:(NSString *)deviceId { -(void)setCurrentOutputDeviceId:(NSString *)deviceId { _controller.controller->SetCurrentAudioOutput(std::string([deviceId UTF8String])); } - + +-(void)setMutedOtherSounds:(BOOL)mute { + _controller.controller->SetAudioOutputDuckingEnabled(mute); +} + // --(void)startTransmissionIfNeeded:(bool)outgoing connection:(TGCallConnection *)connection { +-(void)startTransmissionIfNeeded:(bool)outgoing allowP2p:(bool)allowP2p serializedData:(NSString *)serializedData connection:(TGCallConnection *)connection { - voip_config_t config = { 0 }; - config.init_timeout = TGCallConnectTimeout; - config.recv_timeout = TGCallPacketTimeout; - config.data_saving = false; + tgvoip::VoIPController::Config config = tgvoip::VoIPController::Config(); + config.initTimeout = TGCallConnectTimeout; + config.recvTimeout = TGCallPacketTimeout; + config.dataSaving = tgvoip::DATA_SAVING_NEVER; config.enableAEC = false; config.enableNS = true; config.enableAGC = true; - strncpy(config.logFilePath, [[@"~/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/voip.log" stringByExpandingTildeInPath] UTF8String], sizeof(config.logFilePath)); //memset(config.logFilePath, 0, sizeof(config.logFilePath)); - - _controller.controller->SetConfig(&config); + config.logFilePath = [[@"~/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/voip.log" stringByExpandingTildeInPath] UTF8String]; + // strncpy(config.logFilePath, [[@"~/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/voip.log" stringByExpandingTildeInPath] UTF8String], sizeof(config.logFilePath)); //memset(config.logFilePath, 0, sizeof(config.logFilePath)); + + _controller.controller->SetConfig(config); + tgvoip::ServerConfig::GetSharedInstance()->Update(serializedData.UTF8String); std::vector endpoints {}; std::vector::iterator it = endpoints.begin(); @@ -184,35 +470,42 @@ -(void)startTransmissionIfNeeded:(bool)outgoing connection:(TGCallConnection *)c NSArray *connections = [@[connection.defaultConnection] arrayByAddingObjectsFromArray:connection.alternativeConnections]; for (NSUInteger i = 0; i < connections.count; i++) { - TGCallConnectionDescription *desc = connections[i]; + OngoingCallConnectionDescription *desc = connections[i]; tgvoip::Endpoint endpoint {}; - + endpoint.id = desc.identifier; endpoint.port = (uint32_t)desc.port; - endpoint.address = tgvoip::IPv4Address(desc.ipv4.UTF8String); - endpoint.v6address = tgvoip::IPv6Address(desc.ipv6.UTF8String); - endpoint.type = EP_TYPE_UDP_RELAY; + + tgvoip::IPv4Address address(std::string(desc.ipv4.UTF8String)); + tgvoip::IPv6Address addressv6(std::string(desc.ipv6.UTF8String)); + + +// endpoint.address = tgvoip::NetworkAddress::IPv4(desc.ipv4.UTF8String); +// endpoint.v6address = tgvoip::NetworkAddress::IPv4(desc.ipv6.UTF8String); + endpoint.type = tgvoip::Endpoint::Type::UDP_RELAY; + endpoint.address = address; + endpoint.v6address = addressv6; [desc.peerTag getBytes:&endpoint.peerTag length:16]; it = endpoints.insert ( it , endpoint ); } _controller.controller->SetEncryptionKey((char *)connection.key.bytes, outgoing); - _controller.controller->SetRemoteEndpoints(endpoints, true); - + _controller.controller->SetRemoteEndpoints(endpoints, allowP2p, connection.maxLayer); + _controller.controller->Start(); _controller.controller->Connect(); } - + -(void)dealloc { int bp = 0; bp += 1; } - + @end diff --git a/Telegram-Mac/CallNavigationHeaderView.swift b/Telegram-Mac/CallNavigationHeaderView.swift index 0469cbdae0..3779715bee 100644 --- a/Telegram-Mac/CallNavigationHeaderView.swift +++ b/Telegram-Mac/CallNavigationHeaderView.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox class CallNavigationHeaderView: NavigationHeaderView { private let backgroundView = NSView() @@ -21,6 +22,7 @@ class CallNavigationHeaderView: NavigationHeaderView { private let dropCall:ImageButton = ImageButton() private let durationDisposable = MetaDisposable() private let stateDisposable = MetaDisposable() + private let peerDisposable = MetaDisposable() private var session:PCallSession? = nil { didSet { if let session = session { @@ -61,21 +63,23 @@ class CallNavigationHeaderView: NavigationHeaderView { func update(with session: PCallSession) { self.session = session - let signal = session.account.viewTracker.peerView( session.peerId) |> deliverOnMainQueue |> beforeNext { [weak self] peerView in - - if let peer = peerViewMainPeer(peerView), let strongSelf = self { - strongSelf.callInfo.set(text: peer.displayTitle, for: .Normal) - strongSelf.needsLayout = true + let signal = Signal.single(session.peer) |> then(session.account.postbox.loadedPeerWithId(session.peerId) |> map(Optional.init) |> deliverOnMainQueue) + + peerDisposable.set(signal.start(next: { [weak self] peer in + if let peer = peer { + self?.callInfo.set(text: peer.displayTitle, for: .Normal) + self?.needsLayout = true } - } |> map {_ in return true} + })) - self.ready.set(signal) + self.ready.set(.single(true)) updateMutedBg(session, animated: false) } deinit { stateDisposable.dispose() durationDisposable.dispose() + peerDisposable.dispose() } override init(_ header: NavigationHeader) { @@ -137,11 +141,11 @@ class CallNavigationHeaderView: NavigationHeaderView { } }, for: .Click) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } private var blueColor:NSColor { - return theme.colors.blueSelect + return theme.colors.accentSelect } private var grayColor:NSColor { return theme.colors.grayText @@ -153,7 +157,7 @@ class CallNavigationHeaderView: NavigationHeaderView { backgroundView.layer?.animateBackground() } muteControl.set(image: !session.isMute ? theme.icons.callInlineUnmuted : theme.icons.callInlineMuted, for: .Normal) - muteControl.sizeToFit() + _ = muteControl.sizeToFit() needsLayout = true } @@ -166,18 +170,18 @@ class CallNavigationHeaderView: NavigationHeaderView { callInfo.center() dropCall.centerY(x: frame.width - dropCall.frame.width - 20) endCall.centerY(x: dropCall.frame.minX - 6 - endCall.frame.width) - callInfo.sizeToFit(NSZeroSize, NSMakeSize(frame.width - durationView.frame.maxX - endCall.frame.width - 90, callInfo.frame.height), thatFit: true) + _ = callInfo.sizeToFit(NSZeroSize, NSMakeSize(frame.width - 30 - endCall.frame.width - 90, callInfo.frame.height), thatFit: true) callInfo.center() } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) dropCall.set(image: theme.icons.callInlineDecline, for: .Normal) - dropCall.sizeToFit() - endCall.set(text: tr(.callHeaderEndCall), for: .Normal) - endCall.sizeToFit(NSZeroSize, NSMakeSize(80, 20), thatFit: true) + _ = dropCall.sizeToFit() + endCall.set(text: tr(L10n.callHeaderEndCall), for: .Normal) + _ = endCall.sizeToFit(NSZeroSize, NSMakeSize(80, 20), thatFit: true) durationView.textColor = .white callInfo.set(color: .white, for: .Normal) endCall.set(color: .white, for: .Normal) diff --git a/Telegram-Mac/CallRatingModalViewController.swift b/Telegram-Mac/CallRatingModalViewController.swift index 90b18a09ac..d3c7e7ddd8 100644 --- a/Telegram-Mac/CallRatingModalViewController.swift +++ b/Telegram-Mac/CallRatingModalViewController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox @@ -43,7 +44,7 @@ private class CallRatingModalView: View { star.sizeToFit() star.setFrameOrigin(x, 0) rating.addSubview(star) - x += floorToScreenPixels(star.frame.width) + 10 + x += floorToScreenPixels(backingScaleFactor, star.frame.width) + 10 star.set(handler: { [weak self] current in for j in 0 ... i { @@ -56,11 +57,11 @@ private class CallRatingModalView: View { self?.starsChangeHandler?( Int32(i + 1) ) }, for: .Click) } - rating.setFrameSize(x - 10, floorToScreenPixels(rating.subviews[0].frame.height)) + rating.setFrameSize(x - 10, floorToScreenPixels(backingScaleFactor, rating.subviews[0].frame.height)) addSubview(rating) rating.center() - feedback.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(.callRatingModalPlaceholder), color: .grayText, font: .normal(.text)), update: false) + feedback.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(L10n.callRatingModalPlaceholder), color: .grayText, font: .normal(.text)), update: false) feedback.textFont = NSFont.normal(FontSize.text) feedback.textColor = .text @@ -105,12 +106,13 @@ private class CallRatingModalView: View { } class CallRatingModalViewController: ModalViewController, TGModernGrowingDelegate { - private let account:Account - private let report:ReportCallRating + + private let context:AccountContext + private let report:CallId private var starsCount:Int32? = nil private var comment:String = "" - init(_ account:Account, report:ReportCallRating) { - self.account = account + init(_ context: AccountContext, report:CallId) { + self.context = context self.report = report super.init(frame: NSMakeRect(0, 0, 260, 100)) bar = .init(height: 0) @@ -126,12 +128,12 @@ class CallRatingModalViewController: ModalViewController, TGModernGrowingDelegat } override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in + return ModalInteractions(acceptTitle: tr(L10n.modalOK), accept: { [weak self] in if let strongSelf = self, let stars = strongSelf.starsCount { - _ = rateCall(account: strongSelf.account, report: strongSelf.report, starsCount: stars, comment: strongSelf.comment).start() + _ = rateCall(account: strongSelf.context.account, callId: strongSelf.report, starsCount: stars, comment: strongSelf.comment, userInitiated: false).start() } self?.close() - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + }, cancelTitle: tr(L10n.modalCancel), drawBorder: true, height: 40) } func textViewHeightChanged(_ height: CGFloat, animated: Bool) { @@ -164,7 +166,7 @@ class CallRatingModalViewController: ModalViewController, TGModernGrowingDelegat return false } - func textViewSize() -> NSSize { + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { return NSMakeSize(genericView.feedback.frame.width, genericView.feedback.frame.height) } @@ -172,8 +174,8 @@ class CallRatingModalViewController: ModalViewController, TGModernGrowingDelegat return true } - func maxCharactersLimit() -> Int32 { - return 200 + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return 1024 } override func viewDidAppear(_ animated: Bool) { diff --git a/Telegram-Mac/CallSettingsModalController.swift b/Telegram-Mac/CallSettingsModalController.swift new file mode 100644 index 0000000000..41b6f3d3e7 --- /dev/null +++ b/Telegram-Mac/CallSettingsModalController.swift @@ -0,0 +1,149 @@ +// +// CallSettingsModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import TGUIKit + + +private final class CallSettingsArguments { + let updateInputDevice: (String?)->Void + let updateOutputDevice: (String?)->Void + let muteSound:(Bool)->Void + init(updateInputDevice: @escaping(String?)->Void, updateOutputDevice: @escaping(String?)->Void, muteSound:@escaping(Bool)->Void) { + self.updateInputDevice = updateInputDevice + self.updateOutputDevice = updateOutputDevice + self.muteSound = muteSound + } +} + +private let _id_output_device = InputDataIdentifier("_id_output_device") +private let _id_input_device = InputDataIdentifier("_id_input_device") + +private let _id_mute_sounds = InputDataIdentifier("_id_mute_sounds") +private let _id_open_settings = InputDataIdentifier("_id_open_settings") + + +private func callSettingsEntries(state: VoiceCallSettings, arguments: CallSettingsArguments, inputDevices: [AudioDevice], outputDevices: [AudioDevice]) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + +// entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.callSettingsOutputTitle), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) +// index += 1 +// +// let currentOutput = outputDevices.first(where: {$0.deviceId == state.outputDeviceId}) ?? inputDevices.first! +// +// let outputDevices = outputDevices.map { device -> SPopoverItem in +// return SPopoverItem(device.deviceName, { +// arguments.updateOutputDevice(device.deviceId) +// }) +// } +// +// entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_output_device, data: InputDataGeneralData(name: L10n.callSettingsOutputText, color: theme.colors.text, icon: nil, type: .contextSelector(currentOutput.deviceName, outputDevices), viewType: .singleItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.callSettingsInputTitle), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) +// index += 1 +// +// +// let currentInput = inputDevices.first(where: {$0.deviceId == state.inputDeviceId}) ?? inputDevices.first! +// +// let inputDevices = inputDevices.map { device -> SPopoverItem in +// return SPopoverItem(device.deviceName, { +// arguments.updateInputDevice(device.deviceId) +// }) +// } +// +// +// entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_input_device, data: InputDataGeneralData(name: L10n.callSettingsInputText, color: theme.colors.text, icon: nil, type: .contextSelector(currentInput.deviceName, inputDevices), viewType: .singleItem))) +// index += 1 +// +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.callSettingsOtherSettingsTitle), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) + index += 1 + + var hasMuteSettings: Bool = false + + #if !APP_STORE + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_mute_sounds, data: InputDataGeneralData(name: L10n.callSettingsMuteSound, color: theme.colors.text, icon: nil, type: .switchable(state.muteSounds), viewType: .firstItem, action: { + arguments.muteSound(!state.muteSounds) + }))) + index += 1 + hasMuteSettings = true + #endif + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_open_settings, data: InputDataGeneralData(name: L10n.callSettingsOpenSystemPreferences, color: theme.colors.text, icon: nil, type: .next, viewType: hasMuteSettings ? .lastItem : .singleItem, action: { + openSystemSettings(.microphone) + }))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +private func inputDevices() -> Signal<[AudioDevice], NoError> { + return Signal { subscriber in + subscriber.putNext([]) + subscriber.putCompletion() + return ActionDisposable { + } + } +} +private func outputDevices() -> Signal<[AudioDevice], NoError> { + return Signal { subscriber in + subscriber.putNext([]) + subscriber.putCompletion() + return ActionDisposable { + + } + } +} +func CallSettingsModalController(_ sharedContext: SharedAccountContext) -> InputDataModalController { + + + let arguments = CallSettingsArguments(updateInputDevice: { id in + _ = updateVoiceCallSettingsSettingsInteractively(accountManager: sharedContext.accountManager, { $0.withUpdatedInputDeviceId(id) }).start() + }, updateOutputDevice: { id in + _ = updateVoiceCallSettingsSettingsInteractively(accountManager: sharedContext.accountManager, { $0.withUpdatedOutputDeviceId(id) }).start() + }, muteSound: { value in + _ = updateVoiceCallSettingsSettingsInteractively(accountManager: sharedContext.accountManager, { $0.withUpdatedMuteSounds(value) }).start() + }) + + let signal = combineLatest(voiceCallSettings(sharedContext.accountManager), inputDevices(), outputDevices()) |> map { value, inputDevices, outputDevices in + return callSettingsEntries(state: value, arguments: arguments, inputDevices: inputDevices, outputDevices: outputDevices) + } + let controller = InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0) }, title: L10n.callSettingsTitle) + + let modalController = InputDataModalController(controller) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + return modalController + +} diff --git a/Telegram-Mac/CancelResetAccountController.swift b/Telegram-Mac/CancelResetAccountController.swift new file mode 100644 index 0000000000..6fa915940e --- /dev/null +++ b/Telegram-Mac/CancelResetAccountController.swift @@ -0,0 +1,330 @@ +// +// CancelResetAccountController.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore + +private let _id_input_code = InputDataIdentifier("_id_input_code") + +private struct CancelResetAccountState : Equatable { + let code: String + let error: InputDataValueError? + let checking: Bool + let limit: Int32 + init(code: String, error: InputDataValueError?, checking: Bool, limit: Int32) { + self.code = code + self.error = error + self.checking = checking + self.limit = limit + } + func withUpdatedCode(_ code: String) -> CancelResetAccountState { + return CancelResetAccountState(code: code, error: self.error, checking: self.checking, limit: self.limit) + } + func withUpdatedError(_ error: InputDataValueError?) -> CancelResetAccountState { + return CancelResetAccountState(code: self.code, error: error, checking: self.checking, limit: self.limit) + } + func withUpdatedChecking(_ checking: Bool) -> CancelResetAccountState { + return CancelResetAccountState(code: self.code, error: self.error, checking: checking, limit: self.limit) + } + func withUpdatedCodeLimit(_ limit: Int32) -> CancelResetAccountState { + return CancelResetAccountState(code: self.code, error: self.error, checking: self.checking, limit: limit) + } +} + + + + + +func authorizationNextOptionText(currentType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?) -> (current: String, next: String, codeLength: Int) { + + var codeLength: Int = 255 + var basic: String = "" + var nextText: String = "" + + switch currentType { + case let .otherSession(length: length): + codeLength = Int(length) + basic = L10n.loginEnterCodeFromApp + nextText = L10n.loginSendSmsIfNotReceivedAppCode + case let .sms(length: length): + codeLength = Int(length) + basic = L10n.loginJustSentSms + case let .call(length: length): + codeLength = Int(length) + basic = L10n.loginPhoneCalledCode + default: + break + } + + + if let nextType = nextType { + if let timeout = timeout { + let timeout = Int(timeout) + let minutes = timeout / 60; + let sec = timeout % 60; + let secValue = sec > 9 ? "\(sec)" : "0\(sec)" + if timeout > 0 { + switch nextType { + case .call: + nextText = L10n.loginWillCall(minutes, secValue) + break + case .sms: + nextText = L10n.loginWillSendSms(minutes, secValue) + break + default: + break + } + } else { + switch nextType { + case .call: + basic = L10n.loginPhoneCalledCode + nextText = L10n.loginPhoneDialed + break + default: + break + } + } + + } else { + nextText = L10n.loginSendSmsIfNotReceivedAppCode + } + } + + return (current: basic, next: nextText, codeLength: codeLength) +} + + +private func timeoutSignal(codeData: CancelAccountResetData) -> Signal { + if let _ = codeData.nextType, let timeout = codeData.timeout { + return Signal { subscriber in + let value = Atomic(value: timeout) + subscriber.putNext(timeout) + + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { + subscriber.putNext(value.modify { value in + return max(0, value - 1) + }) + }, queue: Queue.mainQueue()) + timer.start() + + return ActionDisposable { + timer.invalidate() + } + } + } else { + return .single(nil) + } +} + +private func cancelResetAccountEntries(state: CancelResetAccountState, data: CancelAccountResetData, timeout: Int32?, phone: String) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index:Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + +// + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.code), error: state.error, identifier: _id_input_code, mode: .plain, data: InputDataRowData(), placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: state.limit)) + index += 1 + + var nextOptionText = "" + if let nextType = data.nextType { + nextOptionText += authorizationNextOptionText(currentType: data.type, nextType: nextType, timeout: timeout).next + } + + let phoneNumber = phone.hasPrefix("+") ? phone : "+\(phone)" + + let formattedNumber = formatPhoneNumber(phoneNumber) + var result = L10n.cancelResetAccountTextSMS(formattedNumber) + + if !nextOptionText.isEmpty { + result += "\n\n" + nextOptionText + } + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(result), data: InputDataGeneralTextData())) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + + +func cancelResetAccountController(account: Account, phone: String, data: CancelAccountResetData) -> InputDataModalController { + + + let initialState = CancelResetAccountState(code: "", error: nil, checking: false, limit: 255) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((CancelResetAccountState) -> CancelResetAccountState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + + let actionsDisposable = DisposableSet() + + let updateCodeLimitDisposable = MetaDisposable() + actionsDisposable.add(updateCodeLimitDisposable) + + let confirmPhoneDisposable = MetaDisposable() + actionsDisposable.add(confirmPhoneDisposable) + + let nextTypeDisposable = MetaDisposable() + actionsDisposable.add(nextTypeDisposable) + + let currentDataPromise = Promise() + currentDataPromise.set(.single(data)) + + let timeout = Promise() + timeout.set(currentDataPromise.get() |> mapToSignal(timeoutSignal)) + + + updateCodeLimitDisposable.set((currentDataPromise.get() |> deliverOnMainQueue).start(next: { data in + updateState { current in + var limit:Int32 = 255 + switch data.type { + case let .call(length): + limit = length + case let .otherSession(length): + limit = length + case let .sms(length): + limit = length + default: + break + } + return current.withUpdatedCodeLimit(limit) + } + })) + + var close: (() -> Void)? = nil + + let checkCode: (String) -> InputDataValidation = { code in + return .fail(.doSomething { f in + + let checking = stateValue.with {$0.checking} + let code = stateValue.with { $0.code } + + updateState { current in + return current.withUpdatedChecking(true) + } + + if !checking { + confirmPhoneDisposable.set(showModalProgress(signal: requestCancelAccountReset(network: account.network, phoneCodeHash: data.hash, phoneCode: code) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + + let errorText: String + switch error { + case .generic: + errorText = L10n.twoStepAuthGenericError + case .invalidCode: + errorText = L10n.twoStepAuthRecoveryCodeInvalid + case .codeExpired: + errorText = L10n.twoStepAuthRecoveryCodeExpired + case .limitExceeded: + errorText = L10n.twoStepAuthFloodError + } + + updateState { + return $0.withUpdatedError(InputDataValueError(description: errorText, target: .data)).withUpdatedChecking(false) + } + + f(.fail(.fields([_id_input_code : .shake]))) + + }, completed: { + updateState { + return $0.withUpdatedChecking(false) + } + close?() + alert(for: mainWindow, info: L10n.cancelResetAccountSuccess(formatPhoneNumber(phone.hasPrefix("+") ? phone : "+\(phone)"))) + })) + } + }) + } + + + + + + let signal = combineLatest(statePromise.get(), currentDataPromise.get(), timeout.get()) |> map { state, data, timeout in + return InputDataSignalValue(entries: cancelResetAccountEntries(state: state, data: data, timeout: timeout, phone: phone)) + } + + let resendCode = currentDataPromise.get() + |> mapToSignal { [weak currentDataPromise] data -> Signal in + if let _ = data.nextType { + return timeout.get() + |> filter { $0 == 0 } + |> take(1) + |> mapToSignal { _ -> Signal in + return Signal { subscriber in + return requestNextCancelAccountResetOption(network: account.network, phoneNumber: phone, phoneCodeHash: data.hash).start(next: { next in + currentDataPromise?.set(.single(next)) + }, error: { error in + + }) + } + } + } else { + return .complete() + } + } + nextTypeDisposable.set(resendCode.start()) + + let controller = InputDataController(dataSignal: signal, title: L10n.cancelResetAccountTitle, validateData: { data in + + return checkCode(stateValue.with { $0.code }) + }, updateDatas: { data in + updateState { current in + return current.withUpdatedCode(data[_id_input_code]?.stringValue ?? current.code).withUpdatedError(nil) + } + + let codeLimit = stateValue.with { $0.limit } + let code = stateValue.with { $0.code } + + if code.length == codeLimit { + return checkCode(code) + } + return .none + }, afterDisappear: { + actionsDisposable.dispose() + }, updateDoneValue: { data in + return { f in + let checking = stateValue.with { $0.checking } + f(checking ? .loading : .invisible) + } + }, hasDone: true) + + controller.getBackgroundColor = { + theme.colors.background + } + + let modalInteractions = ModalInteractions(acceptTitle: L10n.modalSend, accept: { [weak controller] in + _ = controller?.returnKeyAction() + }, drawBorder: true, height: 50, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + close = { [weak modalController] in + modalController?.modal?.close() + } + + return modalController + +} diff --git a/Telegram-Mac/ChangePhoneNumberContainerView.swift b/Telegram-Mac/ChangePhoneNumberContainerView.swift new file mode 100644 index 0000000000..a1c2ff9314 --- /dev/null +++ b/Telegram-Mac/ChangePhoneNumberContainerView.swift @@ -0,0 +1,309 @@ +// +// ChangePhoneNumberContainerView.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + + +final class ChangePhoneNumberArguments { + let sendCode:(String)->Void + init(sendCode:@escaping(String)->Void) { + self.sendCode = sendCode + } +} + +class ChangePhoneNumberContainerView : View, NSTextFieldDelegate { + + var arguments:ChangePhoneNumberArguments? + + + private let countrySelector:TitleButton = TitleButton() + + let countryLabel:TextViewLabel = TextViewLabel() + let numberLabel:TextViewLabel = TextViewLabel() + + fileprivate let errorLabel:LoginErrorStateView = LoginErrorStateView() + + let codeText:NSTextField = NSTextField() + let numberText:NSTextField = NSTextField() + + fileprivate var selectedItem:CountryItem? + private let manager: CountryManager + + required init(frame frameRect: NSRect, manager: CountryManager) { + self.manager = manager + super.init(frame: frameRect) + + + countrySelector.style = ControlStyle(font: NSFont.medium(.title), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) + countrySelector.set(text: "France", for: .Normal) + _ = countrySelector.sizeToFit() + addSubview(countrySelector) + + + + + addSubview(countryLabel) + addSubview(numberLabel) + + countrySelector.set(handler: { [weak self] _ in + self?.showCountrySelector() + }, for: .Click) + + updateLocalizationAndTheme(theme: theme) + + codeText.stringValue = "+" + + codeText.textColor = theme.colors.text + codeText.font = NSFont.normal(.title) + numberText.textColor = theme.colors.text + numberText.font = NSFont.normal(.title) + + numberText.isBordered = false + numberText.isBezeled = false + numberText.drawsBackground = false + numberText.focusRingType = .none + + codeText.drawsBackground = false + codeText.isBordered = false + codeText.isBezeled = false + codeText.focusRingType = .none + + codeText.delegate = self + codeText.nextResponder = numberText + codeText.nextKeyView = numberText + + numberText.delegate = self + numberText.nextResponder = codeText + numberText.nextKeyView = codeText + addSubview(codeText) + addSubview(numberText) + + errorLabel.layer?.opacity = 0 + addSubview(errorLabel) + + let code = NSLocale.current.regionCode ?? "US" + update(selectedItem: manager.item(bySmallCountryName: code), update: true) + + + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.background + countryLabel.attributedString = .initialize(string: tr(L10n.loginCountryLabel), color: theme.colors.grayText, font: NSFont.normal(FontSize.title)) + countryLabel.sizeToFit() + + numberLabel.attributedString = .initialize(string: tr(L10n.loginYourPhoneLabel), color: theme.colors.grayText, font: NSFont.normal(FontSize.title)) + numberLabel.sizeToFit() + + numberText.placeholderAttributedString = NSAttributedString.initialize(string: tr(L10n.loginPhoneFieldPlaceholder), color: theme.colors.grayText, font: NSFont.normal(.header), coreText: false) + + needsLayout = true + } + + func setPhoneError(_ error: AuthorizationCodeRequestError) { + let text:String + switch error { + case .invalidPhoneNumber: + text = tr(L10n.phoneNumberInvalid) + case .limitExceeded: + text = tr(L10n.loginFloodWait) + case .generic: + text = "undefined error" + case .phoneLimitExceeded: + text = "undefined error" + case .phoneBanned: + text = "PHONE BANNED" + case .timeout: + text = "timeout" + } + errorLabel.state.set(.single(.error(text))) + } + + func update(countryCode: Int32, number: String) { + self.codeText.stringValue = "\(countryCode)" + self.numberText.stringValue = formatPhoneNumber(number) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func layout() { + super.layout() + codeText.sizeToFit() + numberText.sizeToFit() + + let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) + let contentInset = maxInset + 20 + 5 + countrySelector.setFrameOrigin(contentInset, floorToScreenPixels(backingScaleFactor, 25 - countrySelector.frame.height/2)) + + countryLabel.setFrameOrigin(maxInset - countryLabel.frame.width, floorToScreenPixels(backingScaleFactor, 25 - countryLabel.frame.height/2)) + numberLabel.setFrameOrigin(maxInset - numberLabel.frame.width, floorToScreenPixels(backingScaleFactor, 75 - numberLabel.frame.height/2)) + + codeText.setFrameOrigin(contentInset, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + numberText.setFrameOrigin(contentInset + separatorInset, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + errorLabel.centerX(y: 120) + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + + let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) + 20 + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(maxInset, 50, frame.width - maxInset, .borderSize)) + ctx.fill(NSMakeRect(maxInset, 100, frame.width - maxInset, .borderSize)) + // ctx.fill(NSMakeRect(maxInset + separatorInset, 50, .borderSize, 50)) + } + + + func showCountrySelector() { + + var items:[ContextMenuItem] = [] + for country in manager.countries { + let item = ContextMenuItem(country.fullName, handler: { [weak self] in + self?.update(selectedItem: country, update: true) + }) + items.append(item) + } + if let currentEvent = NSApp.currentEvent { + ContextMenu.show(items: items, view: countrySelector, event: currentEvent, onShow: {(menu) in + + }, onClose: {}) + } + + } + + func controlTextDidChange(_ obj: Notification) { + + if let field = obj.object as? NSTextField { + let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() + let dec = code.prefix(4) + + if field == codeText { + + + if code.length > 4 { + let list = Array(code).map {String($0)} + let reduced = list.reduce([], { current, value -> [String] in + var current = current + current.append((current.last ?? "") + value) + return current + }).map({Int($0)}).filter({$0 != nil}).map({$0!}) + + var found: Bool = false + for _code in reduced { + if let item = manager.item(byCodeNumber: _code) { + codeText.stringValue = "+" + String(_code) + update(selectedItem: item, update: true, updateCode: false) + + let codeString = String(_code) + var formated = formatPhoneNumber(codeString + String(code[codeString.endIndex.. Bool { + if commandSelector == #selector(insertNewline(_:)) { + if control == codeText { + self.window?.makeFirstResponder(self.numberText) + self.numberText.selectText(nil) + } else if !numberText.stringValue.isEmpty { + arguments?.sendCode(number) + } + //Queue.mainQueue().justDispatch { + (control as? NSTextField)?.setCursorToEnd() + //} + return true + } else if commandSelector == #selector(deleteBackward(_:)) { + if control == numberText { + if numberText.stringValue.isEmpty { + Queue.mainQueue().justDispatch { + self.window?.makeFirstResponder(self.codeText) + self.codeText.setCursorToEnd() + } + } + } + return false + + } + return false + } + + func update(selectedItem:CountryItem?, update:Bool, updateCode:Bool = true) -> Void { + self.selectedItem = selectedItem + if update { + countrySelector.set(text: selectedItem?.shortName ?? tr(L10n.loginInvalidCountryCode), for: .Normal) + _ = countrySelector.sizeToFit() + if updateCode { + codeText.stringValue = selectedItem != nil ? "+\(selectedItem!.code)" : "+" + } + needsLayout = true + setNeedsDisplayLayer() + + } + } + + + + var separatorInset:CGFloat { + return codeText.frame.width + 10 + } + +} diff --git a/Telegram-Mac/ChannelAdminController.swift b/Telegram-Mac/ChannelAdminController.swift index 2151b0431f..29df4c9151 100644 --- a/Telegram-Mac/ChannelAdminController.swift +++ b/Telegram-Mac/ChannelAdminController.swift @@ -8,74 +8,53 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private final class ChannelAdminControllerArguments { - let account: Account - let toggleRight: (TelegramChannelAdminRightsFlags, TelegramChannelAdminRightsFlags) -> Void + let context: AccountContext + let toggleRight: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void let dismissAdmin: () -> Void - - init(account: Account, toggleRight: @escaping (TelegramChannelAdminRightsFlags, TelegramChannelAdminRightsFlags) -> Void, dismissAdmin: @escaping () -> Void) { - self.account = account + let cantEditError: () -> Void + let transferOwnership:()->Void + let updateRank:(String)->Void + init(context: AccountContext, toggleRight: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, dismissAdmin: @escaping () -> Void, cantEditError: @escaping() -> Void, transferOwnership: @escaping()->Void, updateRank: @escaping(String)->Void) { + self.context = context self.toggleRight = toggleRight self.dismissAdmin = dismissAdmin + self.cantEditError = cantEditError + self.transferOwnership = transferOwnership + self.updateRank = updateRank } } private enum ChannelAdminEntryStableId: Hashable { case info - case right(TelegramChannelAdminRightsFlags) + case right(TelegramChatAdminRightsFlags) case description(Int32) + case changeOwnership case section(Int32) + case roleHeader + case role + case roleDesc + case dismiss var hashValue: Int { - switch self { - case .info: - return 0 - case .description(let index): - return Int(index) - case .section(let section): - return Int(section) - case let .right(flags): - return flags.rawValue.hashValue - } - } - - static func ==(lhs: ChannelAdminEntryStableId, rhs: ChannelAdminEntryStableId) -> Bool { - switch lhs { - case .info: - if case .info = rhs { - return true - } else { - return false - } - case let .right(flags): - if case .right(flags) = rhs { - return true - } else { - return false - } - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - case .description(let text): - if case .description(text) = rhs { - return true - } else { - return false - } - } + return 0 } + } private enum ChannelAdminEntry: TableItemListNodeEntry { - case info(Int32, Peer, TelegramUserPresence?) - case rightItem(Int32, Int, String, TelegramChannelAdminRightsFlags, TelegramChannelAdminRightsFlags, Bool, Bool) - case description(Int32, Int32, String) + case info(Int32, Peer, TelegramUserPresence?, GeneralViewType) + case rightItem(Int32, Int, String, TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags, Bool, Bool, GeneralViewType) + case roleHeader(Int32, GeneralViewType) + case roleDesc(Int32, GeneralViewType) + case role(Int32, String, String, GeneralViewType) + case description(Int32, Int32, String, GeneralViewType) + case changeOwnership(Int32, Int32, String, GeneralViewType) + case dismiss(Int32, Int32, String, GeneralViewType) case section(Int32) @@ -83,10 +62,20 @@ private enum ChannelAdminEntry: TableItemListNodeEntry { switch self { case .info: return .info - case let .rightItem(_, _, _, right, _, _, _): + case let .rightItem(_, _, _, right, _, _, _, _): return .right(right) - case .description(_, let index, _): + case .description(_, let index, _, _): return .description(index) + case .changeOwnership: + return .changeOwnership + case .dismiss: + return .dismiss + case .roleHeader: + return .roleHeader + case .roleDesc: + return .roleDesc + case .role: + return .role case .section(let sectionId): return .section(sectionId) } @@ -94,57 +83,59 @@ private enum ChannelAdminEntry: TableItemListNodeEntry { static func ==(lhs: ChannelAdminEntry, rhs: ChannelAdminEntry) -> Bool { switch lhs { - case let .info(lhsSectionId, lhsPeer, lhsPresence): - if case let .info(rhsSectionId, rhsPeer, rhsPresence) = rhs { - if lhsSectionId != rhsSectionId { - return false - } + case let .info(sectionId, lhsPeer, presence, viewType): + if case .info(sectionId, let rhsPeer, presence, viewType) = rhs { if !arePeersEqual(lhsPeer, rhsPeer) { return false } - if lhsPresence != rhsPresence { - return false - } - return true } else { return false } - case let .rightItem(lhsSectionId, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled): - if case let .rightItem(rhsSectionId, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled) = rhs { - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if lhsText != rhsText { - return false - } - if lhsRight != rhsRight { - return false - } - if lhsFlags != rhsFlags { - return false - } - if lhsValue != rhsValue { - return false - } - if lhsEnabled != rhsEnabled { - return false - } + case let .rightItem(sectionId, index, text, right, flags, value, enabled, viewType): + if case .rightItem(sectionId, index, text, right, flags, value, enabled, viewType) = rhs { + return true + } else { + return false + } + case let .description(sectionId, index, text, viewType): + if case .description(sectionId, index, text, viewType) = rhs{ return true } else { return false } - case let .description(sectionId, index, text): - if case .description(sectionId, index, text) = rhs{ + case let .changeOwnership(sectionId, index, text, viewType): + if case .changeOwnership(sectionId, index, text, viewType) = rhs{ + return true + } else { + return false + } + case let .dismiss(sectionId, index, text, viewType): + if case .dismiss(sectionId, index, text, viewType) = rhs{ + return true + } else { + return false + } + case let .roleHeader(section, viewType): + if case .roleHeader(section, viewType) = rhs { + return true + } else { + return false + } + case let .roleDesc(section, viewType): + if case .roleDesc(section, viewType) = rhs { + return true + } else { + return false + } + case let .role(section, text, placeholder, viewType): + if case .role(section, text, placeholder, viewType) = rhs { return true } else { return false } case let .section(sectionId): - if case .section(sectionId) = rhs{ + if case .section(sectionId) = rhs { return true } else { return false @@ -154,13 +145,23 @@ private enum ChannelAdminEntry: TableItemListNodeEntry { var index:Int32 { switch self { - case .info(let sectionId, _, _): + case .info(let sectionId, _, _, _): return (sectionId * 1000) + 0 - case .description(let sectionId, let index, _): + case .description(let sectionId, let index, _, _): + return (sectionId * 1000) + index + case let .changeOwnership(sectionId, index, _, _): + return (sectionId * 1000) + index + case let .dismiss(sectionId, index, _, _): return (sectionId * 1000) + index - case .rightItem(let sectionId, let index, _, _, _, _, _): + case .rightItem(let sectionId, let index, _, _, _, _, _, _): return (sectionId * 1000) + Int32(index) + 10 - case .section(let sectionId): + case let .roleHeader(sectionId, _): + return (sectionId * 1000) + case let .role(sectionId, _, _, _): + return (sectionId * 1000) + 1 + case let .roleDesc(sectionId, _): + return (sectionId * 1000) + 2 + case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } } @@ -172,90 +173,108 @@ private enum ChannelAdminEntry: TableItemListNodeEntry { func item(_ arguments: ChannelAdminControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .info(_, let peer, let presence): - var string:String = peer.isBot ? tr(.presenceBot) : tr(.peerStatusRecently) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .info(_, peer, presence, viewType): + var string:String = peer.isBot ? L10n.presenceBot : L10n.peerStatusRecently var color:NSColor = theme.colors.grayText - if let presence = presence { + if let presence = presence, !peer.isBot { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) } - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: true, height: 60, photoSize: NSMakeSize(50, 50), statusStyle: ControlStyle(font: NSFont.normal(.custom(14)), foregroundColor: color), status: string, borderType: [], drawCustomSeparator: false, drawLastSeparator: false, inset: NSEdgeInsets(left: 25, right: 25), drawSeparatorIgnoringInset: false, action: {}) - case let .rightItem(_, _, name, right, flags, value, enabled): + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, enabled: true, height: 60, photoSize: NSMakeSize(40, 40), statusStyle: ControlStyle(font: .normal(.title), foregroundColor: color), status: string, inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: {}) + case let .rightItem(_, _, name, right, flags, value, enabled, viewType): //ControlStyle(font: NSFont.) - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: enabled ? theme.colors.text : theme.colors.grayText), type: .switchable(stateback: { () -> Bool in - return value - }), action: { + + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: enabled ? theme.colors.text : theme.colors.grayText), type: .switchable(value), viewType: viewType, action: { arguments.toggleRight(right, flags) - }, enabled: enabled, switchAppearance: SwitchViewAppearance(backgroundColor: theme.colors.background, stateOnColor: enabled ? theme.colors.blueUI : theme.colors.blueUI.withAlphaComponent(0.6), stateOffColor: enabled ? theme.colors.redUI : theme.colors.redUI.withAlphaComponent(0.6), disabledColor: .grayBackground, borderColor: .clear)) - case .description(_, _, let name): - return GeneralTextRowItem(initialSize, stableId: stableId, text: name)//GeneralInteractedRowItem(initialSize, stableId: stableId, name: name) + }, enabled: enabled, switchAppearance: SwitchViewAppearance(backgroundColor: theme.colors.background, stateOnColor: enabled ? theme.colors.accent : theme.colors.accent.withAlphaComponent(0.6), stateOffColor: enabled ? theme.colors.redUI : theme.colors.redUI.withAlphaComponent(0.6), disabledColor: .grayBackground, borderColor: .clear), disabledAction: { + arguments.cantEditError() + }) + case let .changeOwnership(_, _, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, nameStyle: blueActionButton, type: .next, viewType: viewType, action: arguments.transferOwnership) + case let .dismiss(_, _, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, nameStyle: redActionButton, type: .next, viewType: viewType, action: arguments.dismissAdmin) + case let .roleHeader(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.channelAdminRoleHeader, viewType: viewType) + case let .role(_, text, placeholder, viewType): + return InputDataRowItem(initialSize, stableId: stableId, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: placeholder, filter: { text in + let filtered = text.filter { character -> Bool in + return !String(character).containsOnlyEmoji + } + return filtered + }, updated: arguments.updateRank, limit: 16) + case let .roleDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: "", viewType: viewType) + case let .description(_, _, name, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: name, viewType: viewType) } //return TableRowItem(initialSize) } } private struct ChannelAdminControllerState: Equatable { - let updatedFlags: TelegramChannelAdminRightsFlags? + let updatedFlags: TelegramChatAdminRightsFlags? let updating: Bool let editable:Bool - init(updatedFlags: TelegramChannelAdminRightsFlags? = nil, updating: Bool = false, editable: Bool = false) { + let rank:String? + let initialRank:String? + init(updatedFlags: TelegramChatAdminRightsFlags? = nil, updating: Bool = false, editable: Bool = false, rank: String?, initialRank: String?) { self.updatedFlags = updatedFlags self.updating = updating self.editable = editable + self.rank = rank + self.initialRank = initialRank } - static func ==(lhs: ChannelAdminControllerState, rhs: ChannelAdminControllerState) -> Bool { - if lhs.updatedFlags != rhs.updatedFlags { - return false - } - if lhs.updating != rhs.updating { - return false - } - if lhs.editable != rhs.editable { - return false - } - return true - } - - func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChannelAdminRightsFlags?) -> ChannelAdminControllerState { - return ChannelAdminControllerState(updatedFlags: updatedFlags, updating: self.updating, editable: self.editable) + func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChatAdminRightsFlags?) -> ChannelAdminControllerState { + return ChannelAdminControllerState(updatedFlags: updatedFlags, updating: self.updating, editable: self.editable, rank: self.rank, initialRank: self.initialRank) } func withUpdatedEditable(_ editable:Bool) -> ChannelAdminControllerState { - return ChannelAdminControllerState(updatedFlags: updatedFlags, updating: self.updating, editable: editable) + return ChannelAdminControllerState(updatedFlags: updatedFlags, updating: self.updating, editable: editable, rank: self.rank, initialRank: self.initialRank) } func withUpdatedUpdating(_ updating: Bool) -> ChannelAdminControllerState { - return ChannelAdminControllerState(updatedFlags: self.updatedFlags, updating: updating, editable: self.editable) + return ChannelAdminControllerState(updatedFlags: self.updatedFlags, updating: updating, editable: self.editable, rank: self.rank, initialRank: self.initialRank) + } + + func withUpdatedRank(_ rank: String?) -> ChannelAdminControllerState { + return ChannelAdminControllerState(updatedFlags: self.updatedFlags, updating: updating, editable: self.editable, rank: rank, initialRank: self.initialRank) } } -private func stringForRight(right: TelegramChannelAdminRightsFlags, isGroup: Bool) -> String { +private func stringForRight(right: TelegramChatAdminRightsFlags, isGroup: Bool, defaultBannedRights: TelegramChatBannedRights?) -> String { if right.contains(.canChangeInfo) { - return isGroup ? tr(.groupEditAdminPermissionChangeInfo) : tr(.channelEditAdminPermissionChangeInfo) + return isGroup ? L10n.groupEditAdminPermissionChangeInfo : L10n.channelEditAdminPermissionChangeInfo } else if right.contains(.canPostMessages) { - return tr(.channelEditAdminPermissionPostMessages) + return L10n.channelEditAdminPermissionPostMessages } else if right.contains(.canEditMessages) { - return tr(.channelEditAdminPermissionEditMessages) + return L10n.channelEditAdminPermissionEditMessages } else if right.contains(.canDeleteMessages) { - return tr(.channelEditAdminPermissionDeleteMessages) + return L10n.channelEditAdminPermissionDeleteMessages } else if right.contains(.canBanUsers) { - return tr(.channelEditAdminPermissionBanUsers) + return L10n.channelEditAdminPermissionBanUsers } else if right.contains(.canInviteUsers) { - return tr(.channelEditAdminPermissionInviteUsers) - } else if right.contains(.canChangeInviteLink) { - return "tr(.channelEditAdminPermissionInviteViaLink)" + if isGroup { + if let defaultBannedRights = defaultBannedRights, defaultBannedRights.flags.contains(.banAddMembers) { + return L10n.channelEditAdminPermissionInviteMembers + } else { + return L10n.channelEditAdminPermissionInviteViaLink + } + } else { + return L10n.channelEditAdminPermissionInviteSubscribers + } + } else if right.contains(.canPinMessages) { - return tr(.channelEditAdminPermissionPinMessages) + return L10n.channelEditAdminPermissionPinMessages } else if right.contains(.canAddAdmins) { - return tr(.channelEditAdminPermissionAddNewAdmins) + return L10n.channelEditAdminPermissionAddNewAdmins } else { return "" } } -private func rightDependencies(_ right: TelegramChannelAdminRightsFlags) -> [TelegramChannelAdminRightsFlags] { +private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [TelegramChatAdminRightsFlags] { if right.contains(.canChangeInfo) { return [] } else if right.contains(.canPostMessages) { @@ -268,8 +287,6 @@ private func rightDependencies(_ right: TelegramChannelAdminRightsFlags) -> [Tel return [] } else if right.contains(.canInviteUsers) { return [] - } else if right.contains(.canChangeInviteLink) { - return [.canInviteUsers] } else if right.contains(.canPinMessages) { return [] } else if right.contains(.canAddAdmins) { @@ -287,22 +304,29 @@ private func canEditAdminRights(accountPeerId: PeerId, channelView: PeerView, in switch initialParticipant { case .creator: return false - case let .member(_, _, adminInfo, _): + case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo { return adminInfo.canBeEditedByAccountPeer || adminInfo.promotedBy == accountPeerId } else { - return true + return channel.hasPermission(.addAdmins) } } } else { - return channel.hasAdminRights(.canAddAdmins) + return channel.hasPermission(.addAdmins) + } + } else if let group = channelView.peers[channelView.peerId] as? TelegramGroup { + if case .creator = group.role { + return true + } else { + return false } } else { return false } } -private func channelAdminControllerEntries(state: ChannelAdminControllerState, accountPeerId: PeerId, channelView: PeerView, adminView: PeerView, initialParticipant: ChannelParticipant?) -> ([ChannelAdminEntry], TelegramChannelAdminRightsFlags) { + +private func channelAdminControllerEntries(state: ChannelAdminControllerState, accountPeerId: PeerId, channelView: PeerView, adminView: PeerView, initialParticipant: ChannelParticipant?) -> [ChannelAdminEntry] { var entries: [ChannelAdminEntry] = [] var sectionId:Int32 = 1 @@ -310,19 +334,15 @@ private func channelAdminControllerEntries(state: ChannelAdminControllerState, a entries.append(.section(sectionId)) sectionId += 1 + var descId: Int32 = 0 + var addAdminsEnabled: Bool = false - var rights:TelegramChannelAdminRightsFlags = [] if let channel = channelView.peers[channelView.peerId] as? TelegramChannel, let admin = adminView.peers[adminView.peerId] { - entries.append(.info(sectionId, admin, adminView.peerPresences[admin.id] as? TelegramUserPresence)) - - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.description(sectionId, 1, tr(.channelAdminWhatCanAdminDo))) + entries.append(.info(sectionId, admin, adminView.peerPresences[admin.id] as? TelegramUserPresence, .singleItem)) let isGroup: Bool - let maskRightsFlags: TelegramChannelAdminRightsFlags - var rightsOrder: [TelegramChannelAdminRightsFlags] = [] + let maskRightsFlags: TelegramChatAdminRightsFlags + let rightsOrder: [TelegramChatAdminRightsFlags] switch channel.info { case .broadcast: @@ -330,72 +350,251 @@ private func channelAdminControllerEntries(state: ChannelAdminControllerState, a maskRightsFlags = .broadcastSpecific rightsOrder = [ .canChangeInfo, - .canInviteUsers, .canPostMessages, .canEditMessages, .canDeleteMessages, + .canInviteUsers, .canAddAdmins ] - case let .group(info): + case .group: isGroup = true maskRightsFlags = .groupSpecific + rightsOrder = [ + .canChangeInfo, + .canDeleteMessages, + .canBanUsers, + .canInviteUsers, + .canPinMessages, + .canAddAdmins + ] + } + + if canEditAdminRights(accountPeerId: accountPeerId, channelView: channelView, initialParticipant: initialParticipant) { - rightsOrder.append(.canChangeInfo) - rightsOrder.append(.canDeleteMessages) - rightsOrder.append(.canBanUsers) - if !info.flags.contains(.everyMemberCanInviteMembers) { - rightsOrder.append(.canInviteUsers) + var isCreator = false + if let initialParticipant = initialParticipant, case .creator = initialParticipant { + isCreator = true } - rightsOrder.append(.canPinMessages) - rightsOrder.append(.canAddAdmins) - } - if canEditAdminRights(accountPeerId: accountPeerId, channelView: channelView, initialParticipant: initialParticipant) { - let accountUserRightsFlags: TelegramChannelAdminRightsFlags - if channel.flags.contains(.isCreator) { - accountUserRightsFlags = maskRightsFlags - } else if let adminRights = channel.adminRights { - accountUserRightsFlags = maskRightsFlags.intersection(adminRights.flags) - } else { - accountUserRightsFlags = [] + if channel.isSupergroup { + entries.append(.section(sectionId)) + sectionId += 1 + let placeholder = isCreator ? L10n.channelAdminRolePlaceholderOwner : L10n.channelAdminRolePlaceholderAdmin + entries.append(.roleHeader(sectionId, .textTopItem)) + entries.append(.role(sectionId, state.rank ?? "", placeholder, .singleItem)) + entries.append(.description(sectionId, descId, isCreator ? L10n.channelAdminRoleOwnerDesc : L10n.channelAdminRoleAdminDesc, .textBottomItem)) + descId += 1 } + entries.append(.section(sectionId)) + sectionId += 1 + + + if !isCreator || channel.isChannel { + entries.append(.description(sectionId, descId, L10n.channelAdminWhatCanAdminDo, .textTopItem)) + descId += 1 + + let accountUserRightsFlags: TelegramChatAdminRightsFlags + if channel.flags.contains(.isCreator) { + accountUserRightsFlags = maskRightsFlags + } else if let adminRights = channel.adminRights { + accountUserRightsFlags = maskRightsFlags.intersection(adminRights.flags) + } else { + accountUserRightsFlags = [] + } + + let currentRightsFlags: TelegramChatAdminRightsFlags + if let updatedFlags = state.updatedFlags { + currentRightsFlags = updatedFlags + } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { + currentRightsFlags = adminRights.rights.flags + } else { + currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins) + } + + if accountUserRightsFlags.contains(.canAddAdmins) { + addAdminsEnabled = currentRightsFlags.contains(.canAddAdmins) + } + + var index = 0 + + + let list = rightsOrder.filter { + accountUserRightsFlags.contains($0) + } + + for (i, right) in list.enumerated() { + entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating, bestGeneralViewType(list, for: i))) + index += 1 + } + entries.append(.description(sectionId, descId, addAdminsEnabled ? L10n.channelAdminAdminAccess : L10n.channelAdminAdminRestricted, .textBottomItem)) + descId += 1 + + if channel.flags.contains(.isCreator), !admin.isBot { + if currentRightsFlags.contains(maskRightsFlags) { + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.changeOwnership(sectionId, descId, channel.isChannel ? L10n.channelAdminTransferOwnershipChannel : L10n.channelAdminTransferOwnershipGroup, .singleItem)) + } + } + } + + + } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo { + + entries.append(.section(sectionId)) + sectionId += 1 + + if let rank = state.rank { + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.roleHeader(sectionId, .textTopItem)) + entries.append(.description(sectionId, descId, rank, .textTopItem)) + descId += 1 + entries.append(.section(sectionId)) + sectionId += 1 + } + + var index = 0 + for (i, right) in rightsOrder.enumerated() { + entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, adminInfo.rights.flags, adminInfo.rights.flags.contains(right), false, bestGeneralViewType(rightsOrder, for: i))) + index += 1 + } + entries.append(.description(sectionId, descId, L10n.channelAdminCantEditRights, .textBottomItem)) + descId += 1 + } else if let initialParticipant = initialParticipant, case .creator = initialParticipant { + + entries.append(.section(sectionId)) + sectionId += 1 + + if let rank = state.rank { + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.roleHeader(sectionId, .textTopItem)) + entries.append(.description(sectionId, descId, rank, .textBottomItem)) + descId += 1 + entries.append(.section(sectionId)) + sectionId += 1 + } + + var index = 0 + for right in rightsOrder { + entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, TelegramChatAdminRightsFlags(rightsOrder), true, false, bestGeneralViewType(rightsOrder, for: right))) + index += 1 + } + entries.append(.description(sectionId, descId, L10n.channelAdminCantEditRights, .textBottomItem)) + descId += 1 + } + + + + } else if let group = channelView.peers[channelView.peerId] as? TelegramGroup, let admin = adminView.peers[adminView.peerId] { + entries.append(.info(sectionId, admin, adminView.peerPresences[admin.id] as? TelegramUserPresence, .singleItem)) + + var isCreator = false + if let initialParticipant = initialParticipant, case .creator = initialParticipant { + isCreator = true + } + + let placeholder = isCreator ? L10n.channelAdminRolePlaceholderOwner : L10n.channelAdminRolePlaceholderAdmin + + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.roleHeader(sectionId, .textTopItem)) + entries.append(.role(sectionId, state.rank ?? "", placeholder, .singleItem)) + entries.append(.description(sectionId, descId, isCreator ? L10n.channelAdminRoleOwnerDesc : L10n.channelAdminRoleAdminDesc, .textBottomItem)) + descId += 1 + + entries.append(.section(sectionId)) + sectionId += 1 + + if !isCreator { + entries.append(.description(sectionId, descId, L10n.channelAdminWhatCanAdminDo, .textTopItem)) + descId += 1 + + let isGroup = true + let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific + let rightsOrder: [TelegramChatAdminRightsFlags] = [ + .canChangeInfo, + .canDeleteMessages, + .canBanUsers, + .canInviteUsers, + .canPinMessages, + .canAddAdmins + ] - var currentRightsFlags: TelegramChannelAdminRightsFlags + let accountUserRightsFlags: TelegramChatAdminRightsFlags = maskRightsFlags + + let currentRightsFlags: TelegramChatAdminRightsFlags if let updatedFlags = state.updatedFlags { currentRightsFlags = updatedFlags - } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _) = initialParticipant, let adminRights = maybeAdminRights { - currentRightsFlags = adminRights.rights.flags + } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { + currentRightsFlags = adminRights.rights.flags.subtracting(.canAddAdmins) } else { currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins) } - - rights = currentRightsFlags + var index = 0 + + let list = rightsOrder.filter { + accountUserRightsFlags.contains($0) + } + + for (i, right) in list.enumerated() { + entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup, defaultBannedRights: group.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating, bestGeneralViewType(list, for: i))) + index += 1 + } if accountUserRightsFlags.contains(.canAddAdmins) { - addAdminsEnabled = currentRightsFlags.contains(.canAddAdmins) + entries.append(.description(sectionId, descId, currentRightsFlags.contains(.canAddAdmins) ? L10n.channelAdminAdminAccess : L10n.channelAdminAdminRestricted, .textBottomItem)) + descId += 1 } - var index = 0 - for right in rightsOrder { - if accountUserRightsFlags.contains(right) { - - entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating)) - index += 1 + if case .creator = group.role, !admin.isBot { + if currentRightsFlags.contains(maskRightsFlags) { + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.changeOwnership(sectionId, descId, L10n.channelAdminTransferOwnershipGroup, .singleItem)) + descId += 1 } } - entries.append(.description(sectionId, 50, addAdminsEnabled ? tr(.channelAdminAdminAccess) : tr(.channelAdminAdminRestricted))) - } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _) = initialParticipant, let adminInfo = maybeAdminInfo { - var index = 0 - for right in rightsOrder { - entries.append(.rightItem(sectionId, index, stringForRight(right: right, isGroup: isGroup), right, adminInfo.rights.flags, adminInfo.rights.flags.contains(right), false)) - index += 1 + } + } + + var canDismiss: Bool = false + if let channel = peerViewMainPeer(channelView) as? TelegramChannel { + + if let initialParticipant = initialParticipant { + if channel.flags.contains(.isCreator) { + canDismiss = initialParticipant.adminInfo != nil + } else { + switch initialParticipant { + case .creator: + break + case let .member(_, _, adminInfo, _, _): + if let adminInfo = adminInfo { + if adminInfo.promotedBy == accountPeerId || adminInfo.canBeEditedByAccountPeer { + canDismiss = true + } + } + } } - entries.append(.description(sectionId, 50, tr(.channelAdminCantEditRights))) } } - return (entries, rights) + if canDismiss { + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.dismiss(sectionId, descId, L10n.channelAdminDismiss, .singleItem)) + descId += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + return entries } fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ChannelAdminControllerArguments) -> TableUpdateTransition { @@ -408,117 +607,248 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry Void + private let updated:(TelegramChatAdminRights) -> Void private let disposable = MetaDisposable() - private let currentRightFlags:Atomic = Atomic(value: []) - private let stateValue = Atomic(value: ChannelAdminControllerState()) - init(account: Account, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChannelAdminRights) -> Void) { - self.account = account + private let upgradedToSupergroup: (PeerId, @escaping () -> Void) -> Void + private var okClick: (()-> Void)? + + init(_ context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatAdminRights) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) { + self.context = context self.peerId = peerId + self.upgradedToSupergroup = upgradedToSupergroup self.adminId = adminId self.initialParticipant = initialParticipant self.updated = updated - super.init(frame: NSMakeRect(0, 0, 300, 360)) + super.init(frame: NSMakeRect(0, 0, 350, 360)) bar = .init(height : 0) } - override var dynamicSize: Bool { - return true - } - - override func measure(size: NSSize) { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.listHeight)), animated: false) - } - - override func viewClass() -> AnyClass { - return TableView.self - } - - private var genericView:TableView { - return self.view as! TableView - } override func viewDidLoad() { super.viewDidLoad() - let account = self.account - let peerId = self.peerId + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let combinedPromise: Promise = Promise() + + let context = self.context + var peerId = self.peerId let adminId = self.adminId let initialParticipant = self.initialParticipant let updated = self.updated + let upgradedToSupergroup = self.upgradedToSupergroup - let stateValue = self.stateValue - let statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true) + + let initialValue = ChannelAdminControllerState(rank: initialParticipant?.rank, initialRank: initialParticipant?.rank) + let stateValue = Atomic(value: initialValue) + let statePromise = ValuePromise(initialValue, ignoreRepeated: true) let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } + let dismissImpl:()-> Void = { [weak self] in + self?.close() + } + let actionsDisposable = DisposableSet() let updateRightsDisposable = MetaDisposable() actionsDisposable.add(updateRightsDisposable) - let arguments = ChannelAdminControllerArguments(account: account, toggleRight: { right, flags in + let arguments = ChannelAdminControllerArguments(context: context, toggleRight: { right, flags in updateState { current in var updated = flags if flags.contains(right) { updated.remove(right) } else { - if right.contains(.canInviteUsers) { - updated.insert(.canChangeInviteLink) - } updated.insert(right) } return current.withUpdatedUpdatedFlags(updated) } - }, dismissAdmin: { [weak self] in - if let strongSelf = self { - updateState { current in - return current.withUpdatedUpdating(true) - } - updateRightsDisposable.set((updatePeerAdminRights(account: account, peerId: peerId, adminId: adminId, rights: TelegramChannelAdminRights(flags: [])) |> deliverOnMainQueue).start(error: { _ in + }, dismissAdmin: { + updateState { current in + return current.withUpdatedUpdating(true) + } + if peerId.namespace == Namespaces.Peer.CloudGroup { + updateRightsDisposable.set((removeGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + |> deliverOnMainQueue).start(error: { _ in + }, completed: { + updated(TelegramChatAdminRights(flags: [])) + dismissImpl() + })) + } else { + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: []), rank: stateValue.with { $0.rank }) |> deliverOnMainQueue).start(error: { _ in - }, completed: { [weak strongSelf] in - updated(TelegramChannelAdminRights(flags: [])) - strongSelf?.close() + }, completed: { + updated(TelegramChatAdminRights(flags: [])) + dismissImpl() })) } + }, cantEditError: { [weak self] in + self?.show(toaster: ControllerToaster(text: L10n.channelAdminCantEdit)) + }, transferOwnership: { + _ = (combineLatest(queue: .mainQueue(), context.account.postbox.loadedPeerWithId(peerId), context.account.postbox.loadedPeerWithId(adminId))).start(next: { peer, admin in + + let header: String + let text: String + if peer.isChannel { + header = L10n.channelAdminTransferOwnershipConfirmChannelTitle + text = L10n.channelAdminTransferOwnershipConfirmChannelText(peer.displayTitle, admin.displayTitle) + } else { + header = L10n.channelAdminTransferOwnershipConfirmGroupTitle + text = L10n.channelAdminTransferOwnershipConfirmGroupText(peer.displayTitle, admin.displayTitle) + } + + let checkPassword:(PeerId)->Void = { peerId in + showModal(with: InputPasswordController(context: context, title: L10n.channelAdminTransferOwnershipPasswordTitle, desc: L10n.channelAdminTransferOwnershipPasswordDesc, checker: { pwd in + return context.peerChannelMemberCategoriesContextsManager.transferOwnership(account: context.account, peerId: peerId, memberId: admin.id, password: pwd) + |> deliverOnMainQueue + |> ignoreValues + |> `catch` { error -> Signal in + switch error { + case .generic: + return .fail(.generic) + case .invalidPassword: + return .fail(.wrong) + default: + return .fail(.generic) + } + } |> afterCompleted { + dismissImpl() + _ = showModalSuccess(for: context.window, icon: theme.icons.successModalProgress, delay: 2.0) + } + }), for: context.window) + + } + + let transfer:(PeerId, Bool, Bool)->Void = { _peerId, isGroup, convert in + actionsDisposable.add(showModalProgress(signal: checkOwnershipTranfserAvailability(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, memberId: adminId), for: context.window).start(error: { error in + let errorText: String? + var install2Fa = false + switch error { + case .generic: + errorText = L10n.unknownError + case .tooMuchJoined: + errorText = L10n.inviteChannelsTooMuch + case .authSessionTooFresh: + errorText = L10n.channelTransferOwnerErrorText + case .invalidPassword: + preconditionFailure() + case .requestPassword: + errorText = nil + case .twoStepAuthMissing: + errorText = L10n.channelTransferOwnerErrorText + install2Fa = true + case .twoStepAuthTooFresh: + errorText = L10n.channelTransferOwnerErrorText + case .restricted, .userBlocked: + errorText = isGroup ? L10n.groupTransferOwnerErrorPrivacyRestricted : L10n.channelTransferOwnerErrorPrivacyRestricted + case .adminsTooMuch: + errorText = isGroup ? L10n.groupTransferOwnerErrorAdminsTooMuch : L10n.channelTransferOwnerErrorAdminsTooMuch + case .userPublicChannelsTooMuch: + errorText = L10n.channelTransferOwnerErrorPublicChannelsTooMuch + case .limitExceeded: + errorText = L10n.loginFloodWait + case .userLocatedGroupsTooMuch: + errorText = L10n.groupOwnershipTransferErrorLocatedGroupsTooMuch + } + + if let errorText = errorText { + confirm(for: context.window, header: L10n.channelTransferOwnerErrorTitle, information: errorText, okTitle: L10n.modalOK, cancelTitle: L10n.modalCancel, thridTitle: install2Fa ? L10n.channelTransferOwnerErrorEnable2FA : nil, successHandler: { result in + switch result { + case .basic: + break + case .thrid: + dismissImpl() + context.sharedContext.bindings.rootNavigation().removeUntil(EmptyChatViewController.self) + context.sharedContext.bindings.rootNavigation().push(twoStepVerificationUnlockController(context: context, mode: .access(nil), presentController: { (controller, isRoot, animated) in + let navigation = context.sharedContext.bindings.rootNavigation() + if isRoot { + navigation.removeUntil(EmptyChatViewController.self) + } + if !animated { + navigation.stackInsert(controller, at: navigation.stackCount) + } else { + navigation.push(controller) + } + })) + } + }) + } else { + if convert { + actionsDisposable.add(showModalProgress(signal: convertGroupToSupergroup(account: context.account, peerId: peer.id), for: context.window).start(next: { upgradedPeerId in + upgradedToSupergroup(upgradedPeerId, { + peerId = upgradedPeerId + combinedPromise.set(context.account.postbox.combinedView(keys: [.peer(peerId: upgradedPeerId, components: .all), .peer(peerId: adminId, components: .all)])) + checkPassword(upgradedPeerId) + }) + }, error: { error in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + })) + } else { + checkPassword(peer.id) + } + } + })) + } + + confirm(for: context.window, header: header, information: text, okTitle: L10n.channelAdminTransferOwnershipConfirmOK, successHandler: { _ in + transfer(peerId, peer.isSupergroup || peer.isGroup, peer.isGroup) + }) + }) + }, updateRank: { rank in + updateState { + $0.withUpdatedRank(rank) + } }) self.arguments = arguments - let combinedView = account.postbox.combinedView(keys: [.peer(peerId: peerId), .peer(peerId: adminId)]) + + combinedPromise.set(context.account.postbox.combinedView(keys: [.peer(peerId: peerId, components: .all), .peer(peerId: adminId, components: .all)])) let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = atomicSize - let signal = combineLatest(statePromise.get(), combinedView, appearanceSignal) + let signal = combineLatest(statePromise.get(), combinedPromise.get(), appearanceSignal) |> deliverOn(prepareQueue) - |> map { state, combinedView, appearance -> (transition: TableUpdateTransition, canEdit: Bool, canDismiss: Bool) in - let channelView = combinedView.views[.peer(peerId: peerId)] as! PeerView - let adminView = combinedView.views[.peer(peerId: adminId)] as! PeerView - let canEdit = canEditAdminRights(accountPeerId: account.peerId, channelView: channelView, initialParticipant: initialParticipant) + |> map { state, combinedView, appearance -> (transition: TableUpdateTransition, canEdit: Bool, canDismiss: Bool, channelView: PeerView) in + let channelView = combinedView.views[.peer(peerId: peerId, components: .all)] as! PeerView + let adminView = combinedView.views[.peer(peerId: adminId, components: .all)] as! PeerView + var canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelView: channelView, initialParticipant: initialParticipant) + + if canEdit, let flags = state.updatedFlags, flags.isEmpty { + canEdit = false + } + var canDismiss = false if let channel = peerViewMainPeer(channelView) as? TelegramChannel { if let initialParticipant = initialParticipant { if channel.flags.contains(.isCreator) { - canDismiss = true + canDismiss = initialParticipant.adminInfo != nil } else { switch initialParticipant { case .creator: break - case let .member(_, _, adminInfo, _): + case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo { - if adminInfo.promotedBy == account.peerId || adminInfo.canBeEditedByAccountPeer { + if adminInfo.promotedBy == context.account.peerId || adminInfo.canBeEditedByAccountPeer { canDismiss = true } } @@ -526,10 +856,10 @@ class ChannelAdminController: ModalViewController { } } } - let result = channelAdminControllerEntries(state: state, accountPeerId: account.peerId, channelView: channelView, adminView: adminView, initialParticipant: initialParticipant) - let entries = result.0.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - _ = stateValue.modify({$0.withUpdatedUpdatedFlags(result.1).withUpdatedEditable(canEdit)}) - return (transition: prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), canEdit: canEdit, canDismiss: canDismiss) + let result = channelAdminControllerEntries(state: state, accountPeerId: context.account.peerId, channelView: channelView, adminView: adminView, initialParticipant: initialParticipant) + let entries = result.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) + _ = stateValue.modify({$0.withUpdatedEditable(canEdit)}) + return (transition: prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), canEdit: canEdit, canDismiss: canDismiss, channelView: channelView) } |> afterDisposed { actionsDisposable.dispose() @@ -543,78 +873,226 @@ class ChannelAdminController: ModalViewController { self?.updateSize(updatedSize.swap(true)) self?.modal?.interactions?.updateDone { button in - button.set(text: tr(.modalOK), for: .Normal) - let flags = (stateValue.modify({$0}).updatedFlags ?? []).subtracting(.canChangeInviteLink) - button.isEnabled = !flags.isEmpty - if !values.canEdit { - button.isEnabled = true - button.set(text: tr(.navigationDone), for: .Normal) - } + + button.isEnabled = values.canEdit + button.set(text: L10n.navigationDone, for: .Normal) } - self?.modal?.interactions?.updateCancel { button in - button.set(text: values.canDismiss ? tr(.channelAdminDismiss) : "", for: .Normal) - button.set(color: values.canDismiss ? theme.colors.redUI : theme.colors.blueText, for: .Normal) + + self?.okClick = { + if let channel = values.channelView.peers[values.channelView.peerId] as? TelegramChannel { + if let initialParticipant = initialParticipant { + var updateFlags: TelegramChatAdminRightsFlags? + updateState { current in + updateFlags = current.updatedFlags + if let _ = updateFlags { + return current.withUpdatedUpdating(true) + } else { + return current + } + } + + if updateFlags == nil { + switch initialParticipant { + case .creator: + if stateValue.with ({ $0.rank != $0.initialRank }) { + updateFlags = .groupSpecific + } + case let .member(member): + if member.adminInfo?.rights == nil { + let maskRightsFlags: TelegramChatAdminRightsFlags + switch channel.info { + case .broadcast: + maskRightsFlags = .broadcastSpecific + case .group: + maskRightsFlags = .groupSpecific + } + + if channel.flags.contains(.isCreator) { + updateFlags = maskRightsFlags.subtracting(.canAddAdmins) + } else if let adminRights = channel.adminRights { + updateFlags = maskRightsFlags.intersection(adminRights.flags).subtracting(.canAddAdmins) + } else { + updateFlags = [] + } + } + } + } + if updateFlags == nil && stateValue.with ({ $0.rank != $0.initialRank }) { + updateFlags = initialParticipant.adminInfo?.rights.flags + } + + if let updateFlags = updateFlags { + updateState { current in + return current.withUpdatedUpdating(true) + } + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: updateFlags), rank: stateValue.with { $0.rank }) |> deliverOnMainQueue).start(error: { error in + + }, completed: { + updated(TelegramChatAdminRights(flags: updateFlags)) + dismissImpl() + })) + } else { + dismissImpl() + } + } else if values.canEdit { + var updateFlags: TelegramChatAdminRightsFlags? + updateState { current in + updateFlags = current.updatedFlags + return current.withUpdatedUpdating(true) + } + + if updateFlags == nil { + let maskRightsFlags: TelegramChatAdminRightsFlags + switch channel.info { + case .broadcast: + maskRightsFlags = .broadcastSpecific + case .group: + maskRightsFlags = .groupSpecific + } + + if channel.flags.contains(.isCreator) { + updateFlags = maskRightsFlags.subtracting(.canAddAdmins) + } else if let adminRights = channel.adminRights { + updateFlags = maskRightsFlags.intersection(adminRights.flags).subtracting(.canAddAdmins) + } else { + updateFlags = [] + } + } + + + + if let updateFlags = updateFlags { + updateState { current in + return current.withUpdatedUpdating(true) + } + updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: updateFlags), rank: stateValue.with { $0.rank }) |> deliverOnMainQueue).start(error: { _ in + + }, completed: { + updated(TelegramChatAdminRights(flags: updateFlags)) + dismissImpl() + })) + } + } + } else if let _ = values.channelView.peers[values.channelView.peerId] as? TelegramGroup { + var updateFlags: TelegramChatAdminRightsFlags? + updateState { current in + updateFlags = current.updatedFlags + return current + } + + let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific + let defaultFlags = maskRightsFlags.subtracting(.canAddAdmins) + + if updateFlags == nil { + updateFlags = defaultFlags + } + + if let updateFlags = updateFlags { + if initialParticipant?.adminInfo == nil && updateFlags == defaultFlags && stateValue.with ({ $0.rank == $0.initialRank }) { + updateState { current in + return current.withUpdatedUpdating(true) + } + updateRightsDisposable.set((addGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + |> deliverOnMainQueue).start(completed: { + dismissImpl() + })) + } else if updateFlags != defaultFlags || stateValue.with ({ $0.rank != $0.initialRank }) { + let signal = convertGroupToSupergroup(account: context.account, peerId: peerId) + |> map(Optional.init) + |> deliverOnMainQueue + |> `catch` { error -> Signal in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + updateState { current in + return current.withUpdatedUpdating(false) + } + return .single(nil) + } + |> mapToSignal { upgradedPeerId -> Signal in + guard let upgradedPeerId = upgradedPeerId else { + return .single(nil) + } + + return context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: upgradedPeerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: updateFlags), rank: stateValue.with { $0.rank }) + |> mapToSignal { _ -> Signal in + return .complete() + } + |> then(.single(upgradedPeerId)) + } + |> deliverOnMainQueue + + updateState { current in + return current.withUpdatedUpdating(true) + } + + + updateRightsDisposable.set(showModalProgress(signal: signal, for: mainWindow).start(next: { upgradedPeerId in + if let upgradedPeerId = upgradedPeerId { + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: upgradedPeerId, updated: { state in + + if case .ready = state.loadingState { + upgradedToSupergroup(upgradedPeerId, { + + }) + dismissImpl() + } + }) + actionsDisposable.add(disposable) + + } + }, error: { _ in + updateState { current in + return current.withUpdatedUpdating(false) + } + })) + } else { + dismissImpl() + } + } else { + dismissImpl() + } + } } })) } - private func updateSize(_ animated: Bool) { - if let contentSize = self.window?.contentView?.frame.size { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 70, genericView.listHeight)), animated: animated) - } - } deinit { disposable.dispose() } - func updateRights(_ updateFlags: TelegramChannelAdminRightsFlags) { - close() - - if !stateValue.modify({$0}).editable { - return - } - - let updated = self.updated - - _ = showModalProgress(signal: updatePeerAdminRights(account: account, peerId: peerId, adminId: adminId, rights: TelegramChannelAdminRights(flags: updateFlags)) |> deliverOnMainQueue, for: mainWindow).start(error: { error in - alert(for: mainWindow, info: tr(.channelAdminsAddAdminError)) - }, completed: { - updated(TelegramChannelAdminRights(flags: updateFlags)) - }) + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + disposable.set(nil) + super.close(animationType: animationType) } - func addAdmin(_ updateFlags: TelegramChannelAdminRightsFlags) { - close() - _ = showModalProgress(signal: addPeerAdmin(account: account, peerId: peerId, adminId: adminId, adminRightsFlags: updateFlags) |> deliverOnMainQueue, for: mainWindow).start(error: { error in - - }, completed: { [weak self] in - self?.updated(TelegramChannelAdminRights(flags: updateFlags)) - }) - + override func firstResponder() -> NSResponder? { + let view = self.genericView.item(stableId: ChannelAdminEntryStableId.role)?.view as? InputDataRowView + return view?.textView + } + + + override func returnKeyAction() -> KeyHandlerResult { + self.okClick?() + return .invoked + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: nil, center: ModalHeaderData(title: L10n.adminsAdmin), right: nil) } override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in - if let _ = self?.initialParticipant { - if let updatedFlags = self?.stateValue.modify({$0}).updatedFlags { - self?.updateRights(updatedFlags) - } else { - self?.close() - } - } else { - if let updatedFlags = self?.stateValue.modify({$0}).updatedFlags { - self?.addAdmin(updatedFlags) - } - } - - }, cancelTitle: tr(.modalCancel), cancel: { [weak self] in - self?.arguments?.dismissAdmin() - }, height: 40) + return ModalInteractions(acceptTitle: tr(L10n.modalOK), accept: { [weak self] in + self?.okClick?() + }, drawBorder: true, height: 50, singleButton: true) } } diff --git a/Telegram-Mac/ChannelAdminsViewController.swift b/Telegram-Mac/ChannelAdminsViewController.swift index dadc3a4c3c..97c5cbf8cc 100644 --- a/Telegram-Mac/ChannelAdminsViewController.swift +++ b/Telegram-Mac/ChannelAdminsViewController.swift @@ -8,21 +8,19 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit fileprivate final class ChannelAdminsControllerArguments { - let account: Account - - let updateCurrentAdministrationType: () -> Void + let context: AccountContext let addAdmin: () -> Void let openAdmin: (RenderedChannelParticipant) -> Void let removeAdmin: (PeerId) -> Void let eventLogs:() -> Void - init(account:Account, updateCurrentAdministrationType:@escaping()->Void, addAdmin:@escaping()->Void, openAdmin:@escaping(RenderedChannelParticipant) -> Void, removeAdmin:@escaping(PeerId)->Void, eventLogs: @escaping()->Void) { - self.account = account - self.updateCurrentAdministrationType = updateCurrentAdministrationType + init(context: AccountContext, addAdmin:@escaping()->Void, openAdmin:@escaping(RenderedChannelParticipant) -> Void, removeAdmin:@escaping(PeerId)->Void, eventLogs: @escaping()->Void) { + self.context = context self.addAdmin = addAdmin self.openAdmin = openAdmin self.removeAdmin = removeAdmin @@ -41,42 +39,19 @@ fileprivate enum ChannelAdminsEntryStableId: Hashable { return peerId.hashValue } } - - static func ==(lhs: ChannelAdminsEntryStableId, rhs: ChannelAdminsEntryStableId) -> Bool { - switch lhs { - case let .index(index): - if case .index(index) = rhs { - return true - } else { - return false - } - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - } - } } fileprivate enum ChannelAdminsEntry : Identifiable, Comparable { - case administrationType(sectionId:Int32, CurrentAdministrationType) - case administrationInfo(sectionId:Int32, String) - case eventLogs(sectionId:Int32) - case adminsHeader(sectionId:Int32, String) - case adminPeerItem(sectionId:Int32, Int32, RenderedChannelParticipant, ShortPeerDeleting?) - case addAdmin(sectionId:Int32) - case adminsInfo(sectionId:Int32, String) + case eventLogs(sectionId:Int32, GeneralViewType) + case adminsHeader(sectionId:Int32, String, GeneralViewType) + case adminPeerItem(sectionId:Int32, Int32, RenderedChannelParticipant, ShortPeerDeleting?, GeneralViewType) + case addAdmin(sectionId:Int32, GeneralViewType) + case adminsInfo(sectionId:Int32, String, GeneralViewType) case section(Int32) case loading var stableId: ChannelAdminsEntryStableId { switch self { - case .administrationType: - return .index(0) - case .administrationInfo: - return .index(1) case .adminsHeader: return .index(2) case .addAdmin: @@ -89,96 +64,25 @@ fileprivate enum ChannelAdminsEntry : Identifiable, Comparable { return .index(6) case let .section(sectionId): return .index((sectionId + 1) * 1000 - sectionId) - case let .adminPeerItem(_, _, participant, _): + case let .adminPeerItem(_, _, participant, _, _): return .peer(participant.peer.id) } } - - static func ==(lhs: ChannelAdminsEntry, rhs: ChannelAdminsEntry) -> Bool { - switch lhs { - case let .administrationType(_,type): - if case .administrationType(_,type) = rhs { - return true - } else { - return false - } - case let .administrationInfo(_,text): - if case .administrationInfo(_,text) = rhs { - return true - } else { - return false - } - case let .loading: - if case .loading = rhs { - return true - } else { - return false - } - case let .eventLogs(sectionId): - if case .eventLogs(sectionId) = rhs { - return true - } else { - return false - } - case let .adminsHeader(_,title): - if case .adminsHeader(_,title) = rhs { - return true - } else { - return false - } - case let .adminPeerItem(_,lhsIndex, lhsParticipant, lhsEditing): - if case let .adminPeerItem(_,rhsIndex, rhsParticipant, rhsEditing) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsParticipant != rhsParticipant { - return false - } - if lhsEditing != rhsEditing { - return false - } - return true - } else { - return false - } - case let .adminsInfo(_,text): - if case .adminsInfo(_,text) = rhs { - return true - } else { - return false - } - case .addAdmin: - if case .addAdmin = rhs { - return true - } else { - return false - } - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - } - } + var index:Int32 { switch self { case .loading: return 0 - case let .eventLogs(sectionId): + case let .eventLogs(sectionId, _): return (sectionId * 1000) + 1 - case let .administrationType(sectionId, _): + case let .adminsHeader(sectionId, _, _): return (sectionId * 1000) + 2 - case let .administrationInfo(sectionId, _): + case let .addAdmin(sectionId, _): return (sectionId * 1000) + 3 - case let .adminsHeader(sectionId, _): + case let .adminsInfo(sectionId, _, _): return (sectionId * 1000) + 4 - case let .addAdmin(sectionId): - return (sectionId * 1000) + 5 - case let .adminsInfo(sectionId, _): - return (sectionId * 1000) + 6 - case let .adminPeerItem(sectionId, index, _, _): + case let .adminPeerItem(sectionId, index, _, _, _): return (sectionId * 1000) + index + 20 case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -190,86 +94,55 @@ fileprivate enum ChannelAdminsEntry : Identifiable, Comparable { } } -fileprivate enum CurrentAdministrationType { - case everyoneCanAddMembers - case adminsCanAddMembers -} fileprivate struct ChannelAdminsControllerState: Equatable { - let selectedType: CurrentAdministrationType? let editing: Bool let removingPeerId: PeerId? let removedPeerIds: Set let temporaryAdmins: [RenderedChannelParticipant] init() { - self.selectedType = nil self.editing = false self.removingPeerId = nil self.removedPeerIds = Set() self.temporaryAdmins = [] } - init(selectedType: CurrentAdministrationType?, editing: Bool, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant]) { - self.selectedType = selectedType + init(editing: Bool, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant]) { self.editing = editing self.removingPeerId = removingPeerId self.removedPeerIds = removedPeerIds self.temporaryAdmins = temporaryAdmins } - static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool { - if lhs.selectedType != rhs.selectedType { - return false - } - if lhs.editing != rhs.editing { - return false - } - if lhs.removingPeerId != rhs.removingPeerId { - return false - } - if lhs.removedPeerIds != rhs.removedPeerIds { - return false - } - if lhs.temporaryAdmins != rhs.temporaryAdmins { - return false - } - - return true - } - - func withUpdatedSelectedType(_ selectedType: CurrentAdministrationType?) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: selectedType, editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) - } func withUpdatedEditing(_ editing: Bool) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) + return ChannelAdminsControllerState(editing: editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) } func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) + return ChannelAdminsControllerState(editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) } func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) + return ChannelAdminsControllerState(editing: self.editing, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins) } func withUpdatedRemovedPeerIds(_ removedPeerIds: Set) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins) + return ChannelAdminsControllerState(editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins) } func withUpdatedTemporaryAdmins(_ temporaryAdmins: [RenderedChannelParticipant]) -> ChannelAdminsControllerState { - return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins) + return ChannelAdminsControllerState(editing: self.editing, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins) } } -private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, isCreator: Bool) -> [ChannelAdminsEntry] { +private func channelAdminsControllerEntries(accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, isCreator: Bool) -> [ChannelAdminsEntry] { var entries: [ChannelAdminsEntry] = [] - guard let participants = participants else { - return [.loading] - } + let participants = participants ?? [] + var sectionId:Int32 = 1 entries.append(.section(sectionId)) @@ -277,47 +150,45 @@ private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdmins if let peer = view.peers[view.peerId] as? TelegramChannel { var isGroup = false - if case let .group(info) = peer.info { + if case .group = peer.info { isGroup = true - - if isCreator { - let selectedType: CurrentAdministrationType - if let current = state.selectedType { - selectedType = current - } else { - if info.flags.contains(.everyMemberCanInviteMembers) { - selectedType = .everyoneCanAddMembers - } else { - selectedType = .adminsCanAddMembers - } - } - - entries.append(.administrationType(sectionId: sectionId, selectedType)) - let infoText: String - switch selectedType { - case .everyoneCanAddMembers: - infoText = tr(.adminsEverbodyCanAddMembers) - case .adminsCanAddMembers: - infoText = tr(.adminsOnlyAdminsCanAddMembers) - } - entries.append(.administrationInfo(sectionId: sectionId, infoText)) - - } - } - entries.append(.eventLogs(sectionId: sectionId)) + entries.append(.eventLogs(sectionId: sectionId, .singleItem)) entries.append(.section(sectionId)) sectionId += 1 - entries.append(.adminsHeader(sectionId: sectionId, isGroup ? tr(.adminsGroupAdmins) : tr(.adminsChannelAdmins))) + entries.append(.adminsHeader(sectionId: sectionId, isGroup ? L10n.adminsGroupAdmins : L10n.adminsChannelAdmins, .textTopItem)) + + + if peer.hasPermission(.addAdmins) { + entries.append(.addAdmin(sectionId: sectionId, .singleItem)) + entries.append(.adminsInfo(sectionId: sectionId, isGroup ? L10n.adminsGroupDescription : L10n.adminsChannelDescription, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + } + var index: Int32 = 0 - for participant in participants.sorted(by: <) { + for (i, participant) in participants.sorted(by: <).enumerated() { var editable = true - if case .creator = participant.participant { + switch participant.participant { + case .creator: editable = false + case let .member(id, _, adminInfo, _, _): + if id == accountPeerId { + editable = false + } else if let adminInfo = adminInfo { + if peer.flags.contains(.isCreator) || adminInfo.promotedBy == accountPeerId { + editable = true + } else { + editable = false + } + } else { + editable = false + } } let editing:ShortPeerDeleting? @@ -327,18 +198,78 @@ private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdmins editing = nil } - entries.append(.adminPeerItem(sectionId: sectionId, index, participant, editing)) + entries.append(.adminPeerItem(sectionId: sectionId, index, participant, editing, bestGeneralViewType(participants, for: i))) index += 1 } + if index > 0 { + entries.append(.section(sectionId)) + sectionId += 1 + + } + } else if let peer = view.peers[view.peerId] as? TelegramGroup { + + entries.append(.adminsHeader(sectionId: sectionId, L10n.adminsGroupAdmins, .textTopItem)) - if peer.hasAdminRights(.canAddAdmins) { + if case .creator = peer.role { + entries.append(.addAdmin(sectionId: sectionId, .singleItem)) + entries.append(.adminsInfo(sectionId: sectionId, L10n.adminsGroupDescription, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + } + + + var combinedParticipants: [RenderedChannelParticipant] = participants + var existingParticipantIds = Set() + for participant in participants { + existingParticipantIds.insert(participant.peer.id) + } + + for participant in state.temporaryAdmins { + if !existingParticipantIds.contains(participant.peer.id) { + combinedParticipants.append(participant) + } + } + + var index: Int32 = 0 + for participant in combinedParticipants.sorted(by: <) { + if !state.removedPeerIds.contains(participant.peer.id) { + var editable = true + switch participant.participant { + case .creator: + editable = false + case let .member(id, _, adminInfo, _, _): + if id == accountPeerId { + editable = false + } else if let adminInfo = adminInfo { + var creator: Bool = false + if case .creator = peer.role { + creator = true + } + if creator || adminInfo.promotedBy == accountPeerId { + editable = true + } else { + editable = false + } + } else { + editable = false + } + } + let editing:ShortPeerDeleting? + if state.editing { + editing = ShortPeerDeleting(editable: editable) + } else { + editing = nil + } + entries.append(.adminPeerItem(sectionId: sectionId, index, participant, editing, .singleItem)) + index += 1 + } + } + if index > 0 { entries.append(.section(sectionId)) sectionId += 1 - - entries.append(.addAdmin(sectionId: sectionId)) - entries.append(.adminsInfo(sectionId: sectionId, isGroup ? tr(.adminsGroupDescription) : tr(.adminsChannelDescription))) } } @@ -350,32 +281,16 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry TableRowItem in switch entry.entry { - case let .administrationType(_, type): - let label: String - switch type { - case .adminsCanAddMembers: - label = tr(.adminsWhoCanInviteAdmins) - case .everyoneCanAddMembers: - label = tr(.adminsWhoCanInviteEveryone) - } - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.adminsWhoCanInviteText), type: .context(stateback: { () -> String in - return label - }), action: { - arguments.updateCurrentAdministrationType() - }) - - case let .administrationInfo(_, text), let .adminsHeader(_, text), let .adminsInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: text) - case let .adminPeerItem(_, _, participant, editing): + case let .adminPeerItem(_, _, participant, editing, viewType): let peerText: String switch participant.participant { case .creator: - peerText = tr(.adminsCreator) - case let .member(_, _, adminInfo, _): + peerText = L10n.adminsOwner + case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo, let peer = participant.peers[adminInfo.promotedBy] { - peerText = tr(.channelAdminsPromotedBy(peer.displayTitle)) + peerText = L10n.channelAdminsPromotedBy(peer.displayTitle) } else { - peerText = tr(.adminsAdmin) + peerText = L10n.adminsAdmin } } @@ -389,24 +304,26 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry { private let disposable:MetaDisposable = MetaDisposable() private let removeAdminDisposable:MetaDisposable = MetaDisposable() private let openPeerDisposable:MetaDisposable = MetaDisposable() - init(account:Account, peerId:PeerId) { + init( _ context:AccountContext, peerId:PeerId) { self.peerId = peerId - super.init(account) + super.init(context) } let actionsDisposable = DisposableSet() @@ -436,9 +353,21 @@ class ChannelAdminsViewController: EditableViewController { override func viewDidLoad() { super.viewDidLoad() - let account = self.account + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let context = self.context let peerId = self.peerId + + var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? + + let upgradedToSupergroup: (PeerId, @escaping () -> Void) -> Void = { upgradedPeerId, f in + upgradedToSupergroupImpl?(upgradedPeerId, f) + } + + let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil) let updateState: ((ChannelAdminsControllerState) -> ChannelAdminsControllerState) -> Void = { [weak self] f in @@ -449,209 +378,116 @@ class ChannelAdminsViewController: EditableViewController { let viewValue:Atomic = Atomic(value: nil) - let applyAdmin:(RenderedChannelParticipant, PeerId, TelegramChannelAdminRights) -> Void = { [weak self] participant, adminId, updatedRights in - - - let applyAdmin: Signal = combineLatest(adminsPromise.get(), account.postbox.loadedPeerWithId(adminId)) - |> filter { $0.0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { admins, peer -> Signal in - if let admins = admins { - let additionalPeers = viewValue.modify({$0})?.peers ?? [:] - var updatedAdmins = admins - if updatedRights.isEmpty { - for i in 0 ..< updatedAdmins.count { - if updatedAdmins[i].peer.id == adminId { - updatedAdmins.remove(at: i) - break - } - } - } else { - var found = false - for i in 0 ..< updatedAdmins.count { - if updatedAdmins[i].peer.id == adminId { - if case let .member(id, date, _, banInfo) = updatedAdmins[i].participant { - updatedAdmins[i] = RenderedChannelParticipant(participant: .member(id: id, invitedAt: date, adminInfo: ChannelParticipantAdminInfo(rights: updatedRights, promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil), peer: updatedAdmins[i].peer, peers: participant.peers + additionalPeers) - } - found = true - break - } - } - if !found { - updatedAdmins.append(RenderedChannelParticipant(participant: .member(id: adminId, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: updatedRights, promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil), peer: peer, peers: participant.peers + additionalPeers)) - } - } - adminsPromise.set(.single(updatedAdmins)) - } - - return account.context.cachedAdminIds.ids(postbox: account.postbox, network: account.network, peerId: peerId) |> take(1) |> mapToSignal { _ in - return Signal.complete() - } - } - self?.addAdminDisposable.set(applyAdmin.start()) - } - let arguments = ChannelAdminsControllerArguments(account: account, updateCurrentAdministrationType: { [weak self] in - - if let item = self?.genericView.item(stableId: AnyHashable(ChannelAdminsEntryStableId.index(0))) { - if let view = (self?.genericView.viewNecessary(at: item.index) as? GeneralInteractedRowView)?.textView { - let result = ValuePromise() - - let items = [SPopoverItem(tr(.adminsWhoCanInviteEveryone), { - result.set(true) - - }), SPopoverItem(tr(.adminsWhoCanInviteAdmins), { - result.set(false) - })] - - let updateSignal = result.get() - |> take(1) - |> mapToSignal { value -> Signal in - updateState { state in - return state.withUpdatedSelectedType(value ? .everyoneCanAddMembers : .adminsCanAddMembers) - } - - return account.postbox.loadedPeerWithId(peerId) - |> mapToSignal { peer -> Signal in - if let peer = peer as? TelegramChannel, case let .group(info) = peer.info { - var updatedValue: Bool? - if value && !info.flags.contains(.everyMemberCanInviteMembers) { - updatedValue = true - } else if !value && info.flags.contains(.everyMemberCanInviteMembers) { - updatedValue = false - } - if let updatedValue = updatedValue { - return updateGroupManagementType(account: account, peerId: peerId, type: updatedValue ? .unrestricted : .restrictedToAdmins) - } else { - return .complete() - } - } else { - return .complete() - } - } - } - self?.updateAdministrationDisposable.set(updateSignal.start()) - - showPopover(for: view, with: SPopoverViewController(items: items), edge: .minX, inset: NSMakePoint(0,-30)) - } - } - - }, addAdmin: { - let behavior = SelectChannelMembersBehavior(peerId: peerId, limit: 1) + let arguments = ChannelAdminsControllerArguments(context: context, addAdmin: { + let behavior = peerId.namespace == Namespaces.Peer.CloudGroup ? SelectGroupMembersBehavior(peerId: peerId, limit: 1) : SelectChannelMembersBehavior(peerId: peerId, limit: 1) - _ = (selectModalPeers(account: account, title: "", limit: 1, behavior: behavior, confirmation: { peerIds in - if let peerId = peerIds.first, let peerView = viewValue.modify({$0}), let channel = peerViewMainPeer(peerView) as? TelegramChannel { - if let participant = behavior.participants[peerId] { - switch participant.participant { - case .creator: - return .single(false) - case .member(_, _, let adminInfo, let banInfo): - if let adminInfo = adminInfo { - //if channel.flags.contains(.isCreator) && adminInfo.promotedBy != account.peerId && !adminInfo.canBeEditedByAccountPeer { - //alert(for: mainWindow, info: tr(.channelAdminsAddAdminError)) - // return .single(false) - //} - return .single(true) - } else { - if let _ = channel.adminRights { - if let _ = banInfo { - if !channel.hasAdminRights(.canBanUsers) { - alert(for: mainWindow, info: tr(.channelAdminsPromoteBannedAdminError)) - return .single(false) - } - } - } - - return .single(true) - } - } - } else { - if !channel.hasAdminRights(.canInviteUsers) { - alert(for: mainWindow, info: tr(.channelAdminsPromoteUnmemberAdminError)) - return .single(false) - } - } + _ = (selectModalPeers(context: context, title: L10n.adminsAddAdmin, limit: 1, behavior: behavior, confirmation: { peerIds in + if let _ = behavior.participants[peerId] { + return .single(true) + } else { + return .single(true) } - return .single(true) }) |> map {$0.first}).start(next: { adminId in if let adminId = adminId { - - showModal(with: ChannelAdminController(account: account, peerId: peerId, adminId: adminId, initialParticipant: behavior.participants[adminId]?.participant, updated: { updatedRights in - if let participant = behavior.participants[adminId] { - applyAdmin(participant, adminId, updatedRights) - } - - }), for: mainWindow) + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: adminId, initialParticipant: behavior.participants[adminId]?.participant, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup), for: mainWindow) } }) }, openAdmin: { participant in - if case let .member(adminId, _, _, _) = participant.participant { - showModal(with: ChannelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { updatedRights in - applyAdmin(participant, adminId, updatedRights) - - }), for: mainWindow) - } + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup), for: mainWindow) }, removeAdmin: { [weak self] adminId in updateState { return $0.withUpdatedRemovingPeerId(adminId) } - let applyPeers: Signal = adminsPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { peers -> Signal in - if let peers = peers { - var updatedPeers = peers - for i in 0 ..< updatedPeers.count { - if updatedPeers[i].peer.id == adminId { - updatedPeers.remove(at: i) + if peerId.namespace == Namespaces.Peer.CloudGroup { + self?.removeAdminDisposable.set((removeGroupAdmin(account: context.account, peerId: peerId, adminId: adminId) + |> deliverOnMainQueue).start(completed: { + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + })) + } else { + self?.removeAdminDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(flags: []), rank: nil) + |> deliverOnMainQueue).start(completed: { + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + })) + } + + }, eventLogs: { [weak self] in + self?.navigationController?.push(ChannelEventLogController(context, peerId: peerId)) + }) + + let peerView = Promise() + peerView.set(context.account.viewTracker.peerView(peerId)) + + + + let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) + if peerId.namespace == Namespaces.Peer.CloudChannel { + membersAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) { membersState in + if case .loading = membersState.loadingState, membersState.list.isEmpty { + adminsPromise.set(.single(nil)) + } else { + adminsPromise.set(.single(membersState.list)) + } + } + } else { + let membersDisposable = (peerView.get() + |> map { peerView -> [RenderedChannelParticipant]? in + guard let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants else { + return nil + } + var result: [RenderedChannelParticipant] = [] + var creatorPeer: Peer? + for participant in participants.participants { + if let peer = peerView.peers[participant.peerId] { + switch participant { + case .creator: + creatorPeer = peer + default: break } } - adminsPromise.set(.single(updatedPeers)) } - - return account.context.cachedAdminIds.ids(postbox: account.postbox, network: account.network, peerId: peerId) |> take(1) |> mapToSignal { _ in - return Signal.complete() - } - } - - self?.removeAdminDisposable.set((removePeerAdmin(account: account, peerId: peerId, adminId: adminId) - |> then(applyPeers |> mapError { _ -> RemovePeerAdminError in return .generic }) |> deliverOnMainQueue).start(error: { _ in - updateState { - return $0.withUpdatedRemovingPeerId(nil) + guard let creator = creatorPeer else { + return nil } - }, completed: { - updateState { state in - var updatedTemporaryAdmins = state.temporaryAdmins - for i in 0 ..< updatedTemporaryAdmins.count { - if updatedTemporaryAdmins[i].peer.id == adminId { - updatedTemporaryAdmins.remove(at: i) + for participant in participants.participants { + if let peer = peerView.peers[participant.peerId] { + switch participant { + case .creator: + result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)) + case .admin: + var peers: [PeerId: Peer] = [:] + peers[creator.id] = creator + peers[peer.id] = peer + result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers)) + case .member: break } } - return state.withUpdatedRemovingPeerId(nil).withUpdatedTemporaryAdmins(updatedTemporaryAdmins) } - })) - }, eventLogs: { [weak self] in - self?.navigationController?.push(ChannelEventLogController(account, peerId: peerId)) - }) - - let peerView = account.viewTracker.peerView(peerId) - + return result + }).start(next: { members in + adminsPromise.set(.single(members)) + }) + membersAndLoadMoreControl = (membersDisposable, nil) + } - let adminsSignal: Signal<[RenderedChannelParticipant]?, NoError> = .single(nil) |> then(channelAdmins(account: account, peerId: peerId) |> map { Optional($0) }) + let (membersDisposable, _) = membersAndLoadMoreControl + actionsDisposable.add(membersDisposable) - adminsPromise.set(adminsSignal) + let initialSize = atomicSize let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let signal = combineLatest(statePromise.get(), peerView, adminsPromise.get(), appearanceSignal) + let signal = combineLatest(statePromise.get(), peerView.get(), adminsPromise.get(), appearanceSignal) |> map { state, view, admins, appearance -> (TableUpdateTransition, Bool) in var isCreator = false @@ -661,7 +497,7 @@ class ChannelAdminsViewController: EditableViewController { isSupergroup = channel.isSupergroup } _ = viewValue.swap(view) - let entries = ChannelAdminsControllerEntries(view: view, state: state, participants: admins, isCreator: isCreator).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let entries = channelAdminsControllerEntries(accountPeerId: context.peerId, view: view, state: state, participants: admins, isCreator: isCreator).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return (prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments, isSupergroup: isSupergroup), isCreator) } @@ -672,6 +508,25 @@ class ChannelAdminsViewController: EditableViewController { })) + upgradedToSupergroupImpl = { [weak self] upgradedPeerId, f in + guard let `self` = self, let navigationController = self.navigationController else { + return + } + + let chatController = ChatController(context: context, chatLocation: .peer(upgradedPeerId)) + + navigationController.removeAll() + navigationController.push(chatController, false, style: .none) + let signal = chatController.ready.get() |> filter {$0} |> take(1) |> deliverOnMainQueue |> ignoreValues + + _ = signal.start(completed: { [weak navigationController] in + navigationController?.push(ChannelAdminsViewController(context, peerId: upgradedPeerId), false, style: .none) + f() + }) + + } + + } override func update(with state: ViewControllerState) { @@ -685,5 +540,6 @@ class ChannelAdminsViewController: EditableViewController { removeAdminDisposable.dispose() updateAdministrationDisposable.dispose() openPeerDisposable.dispose() + actionsDisposable.dispose() } } diff --git a/Telegram-Mac/ChannelBlacklistViewController.swift b/Telegram-Mac/ChannelBlacklistViewController.swift deleted file mode 100644 index 6dd818746c..0000000000 --- a/Telegram-Mac/ChannelBlacklistViewController.swift +++ /dev/null @@ -1,532 +0,0 @@ -// -// GroupBlackListViewController.swift -// Telegram -// -// Created by keepcoder on 22/02/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - - -private final class ChannelBlacklistControllerArguments { - let account: Account - - let removePeer: (PeerId) -> Void - let restrict:(RenderedChannelParticipant, Bool) -> Void - let addMember:()->Void - init(account: Account, removePeer: @escaping (PeerId) -> Void, restrict:@escaping(RenderedChannelParticipant, Bool) -> Void, addMember:@escaping()->Void) { - self.account = account - self.removePeer = removePeer - self.restrict = restrict - self.addMember = addMember - } -} - -private enum ChannelBlacklistEntryStableId: Hashable { - case peer(PeerId) - case empty - case addMember - case section(Int32) - case header(Int32) - var hashValue: Int { - switch self { - case let .peer(peerId): - return peerId.hashValue - case .empty: - return 0 - case .section: - return 1 - case .header: - return 2 - case .addMember: - return 3 - } - } - - static func ==(lhs: ChannelBlacklistEntryStableId, rhs: ChannelBlacklistEntryStableId) -> Bool { - switch lhs { - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - case .empty: - if case .empty = rhs { - return true - } else { - return false - } - case .addMember: - if case .addMember = rhs { - return true - } else { - return false - } - case .section(let section): - if case .section(section) = rhs { - return true - } else { - return false - } - case .header(let index): - if case .header(index) = rhs { - return true - } else { - return false - } - - } - } -} - -private enum ChannelBlacklistEntry: Identifiable, Comparable { - case peerItem(Int32, Int32, RenderedChannelParticipant, ShortPeerDeleting?, Bool) - case empty(Bool) - case header(Int32, Int32, String) - case section(Int32) - case addMember(Int32, Int32) - var stableId: ChannelBlacklistEntryStableId { - switch self { - case let .peerItem(_, _, participant, _, _): - return .peer(participant.peer.id) - case .empty: - return .empty - case .section(let section): - return .section(section) - case .header(_, let index, _): - return .header(index) - case .addMember: - return .addMember - } - } - - static func ==(lhs: ChannelBlacklistEntry, rhs: ChannelBlacklistEntry) -> Bool { - switch lhs { - case let .peerItem(lhsSectionId, lhsIndex, lhsParticipant, lhsEditing, lhsEnabled): - if case let .peerItem(rhsSectionId, rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsSectionId != rhsSectionId { - return false - } - if lhsParticipant != rhsParticipant { - return false - } - if lhsEditing != rhsEditing { - return false - } - if lhsEnabled != rhsEnabled { - return false - } - return true - } else { - return false - } - case let .empty(loading): - if case .empty(loading) = rhs { - return true - } else { - return false - } - case let .section(id): - if case .section(id) = rhs { - return true - } else { - return false - } - case let .addMember(sectionId, index): - if case .addMember(sectionId, index) = rhs { - return true - } else { - return false - } - case let .header(sectionId, index, text): - if case .header(sectionId, index, text) = rhs { - return true - } else { - return false - } - } - } - - var index:Int32 { - switch self { - case let .section(section): - return (section * 1000) - section - case let .header(section, index, _): - return (section * 1000) + index - case let .addMember(section, index): - return (section * 1000) + index - case .empty: - return 0 - case let .peerItem(section, index, _, _, _): - return (section * 1000) + index - - } - } - - static func <(lhs: ChannelBlacklistEntry, rhs: ChannelBlacklistEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(_ arguments: ChannelBlacklistControllerArguments, initialSize:NSSize) -> TableRowItem { - switch self { - case let .peerItem(_, _, participant, editing, enabled): - - let interactionType:ShortPeerItemInteractionType - if let editing = editing { - - interactionType = .deletable(onRemove: { peerId in - arguments.removePeer(peerId) - }, deletable: editing.editable) - } else { - interactionType = .plain - } - - var string:String = tr(.peerStatusRecently) - - if case let .member(_, _, _, banInfo) = participant.participant { - if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - if banInfo.rights.flags.contains(.banReadMessages) { - string = tr(.channelBlacklistBlockedBy(peer.displayTitle)) - } else { - string = tr(.channelBlacklistRestrictedBy(peer.displayTitle)) - } - } else { - if let presence = participant.presences[participant.peer.id] as? TelegramUserPresence { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string,_, _) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) - } else if let peer = participant.peer as? TelegramUser, let botInfo = peer.botInfo { - string = botInfo.flags.contains(.hasAccessToChatHistory) ? tr(.peerInfoBotStatusHasAccess) : tr(.peerInfoBotStatusHasNoAccess) - } - } - } - - - - return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.account, stableId: stableId, enabled: enabled, height:44, photoSize: NSMakeSize(32, 32), status: string, drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, action: { - if case .plain = interactionType { - arguments.restrict(participant, true) - } - }) - case let .empty(progress): - return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: progress, text: tr(.channelBlacklistEmptyDescrpition)) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .header(_, _, let text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text, drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case .addMember: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelBlacklistAddMember), nameStyle: blueActionButton, action: { - arguments.addMember() - }) - } - } -} - -private struct ChannelBlacklistControllerState: Equatable { - let editing: Bool - let removingPeerId: PeerId? - - init() { - self.editing = false - self.removingPeerId = nil - } - - init(editing: Bool, removingPeerId: PeerId?) { - self.editing = editing - self.removingPeerId = removingPeerId - } - - static func ==(lhs: ChannelBlacklistControllerState, rhs: ChannelBlacklistControllerState) -> Bool { - if lhs.editing != rhs.editing { - return false - } - if lhs.removingPeerId != rhs.removingPeerId { - return false - } - - return true - } - - func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState { - return ChannelBlacklistControllerState(editing: editing, removingPeerId: self.removingPeerId) - } - - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState { - return ChannelBlacklistControllerState(editing: self.editing, removingPeerId: self.removingPeerId) - } - - func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState { - return ChannelBlacklistControllerState(editing: self.editing, removingPeerId: removingPeerId) - } -} - -private func channelBlacklistControllerEntries(view: PeerView, state: ChannelBlacklistControllerState, participants: ChannelBlacklist?) -> [ChannelBlacklistEntry] { - - var entries: [ChannelBlacklistEntry] = [] - - var index:Int32 = 0 - var sectionId:Int32 = 1 - - - - if let peer = peerViewMainPeer(view) as? TelegramChannel { - if peer.hasAdminRights(.canBanUsers) { - - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.addMember(sectionId, index)) - index += 1 - } - - if let participants = participants { - if !participants.isEmpty { - entries.append(.section(sectionId)) - sectionId += 1 - } - - if !participants.restricted.isEmpty { - entries.append(.header(sectionId, index, tr(.channelBlacklistRestricted))) - index += 1 - for participant in participants.restricted.sorted(by: <) { - - let editable = peer.hasAdminRights(.canBanUsers) - - var deleting:ShortPeerDeleting? = nil - if state.editing { - deleting = ShortPeerDeleting(editable: editable) - } - - entries.append(.peerItem(sectionId, index, participant, deleting, state.removingPeerId != participant.peer.id)) - index += 1 - } - } - - - - if !participants.banned.isEmpty { - - if !participants.restricted.isEmpty { - entries.append(.section(sectionId)) - sectionId += 1 - } - - entries.append(.header(sectionId, index, tr(.channelBlacklistBlocked))) - index += 1 - for participant in participants.banned.sorted(by: <) { - - var editable = true - if case .creator = participant.participant { - editable = false - } - - var deleting:ShortPeerDeleting? = nil - if state.editing { - deleting = ShortPeerDeleting(editable: editable) - } - - entries.append(.peerItem(sectionId, index, participant, deleting, state.removingPeerId != participant.peer.id)) - index += 1 - } - } - } - } - if entries.isEmpty { - entries.append(.empty(participants == nil)) - } - - return entries -} - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ChannelBlacklistControllerArguments) -> TableUpdateTransition { - - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - - -class ChannelBlacklistViewController: EditableViewController { - - private let peerId:PeerId - - private let statePromise = ValuePromise(ChannelBlacklistControllerState(), ignoreRepeated: true) - private let stateValue = Atomic(value: ChannelBlacklistControllerState()) - private let removePeerDisposable:MetaDisposable = MetaDisposable() - private let updatePeerDisposable = MetaDisposable() - private let disposable:MetaDisposable = MetaDisposable() - - init(account:Account, peerId:PeerId) { - self.peerId = peerId - super.init(account) - } - - override func viewDidLoad() { - super.viewDidLoad() - let account = self.account - let peerId = self.peerId - - let updateState: ((ChannelBlacklistControllerState) -> ChannelBlacklistControllerState) -> Void = { [weak self] f in - if let strongSelf = self { - strongSelf.statePromise.set(strongSelf.stateValue.modify { f($0) }) - } - } - - let peersPromise = Promise(nil) - let viewValue:Atomic = Atomic(value: nil) - - let restrict:(RenderedChannelParticipant, Bool) -> Void = { [weak self] participant, unban in - let strongSelf = self - showModal(with: RestrictedModalViewController(account: account, peerId: peerId, participant: participant, unban: unban, updated: { [weak strongSelf] updatedRights in - let additional = viewValue.modify({$0})?.peers ?? [:] - switch participant.participant { - case let .member(memberId, _, _, _): - //if banInfo != updatedRights { - - let applyPeer: Signal = peersPromise.get() - |> filter { $0 != nil } - |> map {$0!} - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { peers -> Signal in - peersPromise.set(.single(peers.withRemovedParticipant(participant.withUpdatedBannedRights(ChannelParticipantBannedInfo(rights: updatedRights, restrictedBy: account.peerId, isMember: true)).withUpdatedAdditionalPeers(additional)))) - return .complete() - } - - let peerUpdate = account.postbox.modify { modifier -> Void in - updatePeers(modifier: modifier, peers: [participant.peer], update: { (_, updated) -> Peer? in - return updated - }) - } - - strongSelf?.updatePeerDisposable.set(showModalProgress(signal: peerUpdate |> then(updateChannelMemberBannedRights(account: account, peerId: peerId, memberId: memberId, rights: updatedRights)) |> then(applyPeer), for: mainWindow).start()) - // } - default: - break - } - - - }), for: mainWindow) - } - - let arguments = ChannelBlacklistControllerArguments(account: account, removePeer: { [weak self] memberId in - - updateState { - return $0.withUpdatedRemovingPeerId(memberId) - } - - let applyPeers: Signal = peersPromise.get() - |> filter { $0 != nil } - |> map {$0!} - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { peers -> Signal in - peersPromise.set(.single(peers.withRemovedPeerId(memberId))) - return .complete() - } - - self?.removePeerDisposable.set((updateChannelMemberBannedRights(account: account, peerId: peerId, memberId: memberId, rights: TelegramChannelBannedRights(flags: [], untilDate: 0)) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in - updateState { - return $0.withUpdatedRemovingPeerId(nil) - } - }, completed: { - updateState { - return $0.withUpdatedRemovingPeerId(nil) - } - - })) - }, restrict: restrict, addMember: { - let behavior = SelectChannelMembersBehavior(peerId: peerId, limit: 1) - - _ = (selectModalPeers(account: account, title: tr(.channelBlacklistSelectNewUserTitle), limit: 1, behavior: behavior, confirmation: { peerIds in - if let peerId = peerIds.first { - var adminError:Bool = false - if let participant = behavior.participants[peerId] { - if case let .member(_, _, adminInfo, _) = participant.participant { - if let adminInfo = adminInfo { - if !adminInfo.canBeEditedByAccountPeer && adminInfo.promotedBy != account.peerId { - adminError = true - } - } - } else { - adminError = true - } - } - if adminError { - alert(for: mainWindow, info: tr(.channelBlacklistDemoteAdminError)) - return .single(false) - } - } - return .single(true) - }) |> map {$0.first} |> filter {$0 != nil} |> map {$0!}).start(next: { memberId in - var participant:RenderedChannelParticipant? - if let p = behavior.participants[memberId] { - participant = p - } else if let temporary = behavior.result[memberId] { - participant = RenderedChannelParticipant(participant: ChannelParticipant.member(id: memberId, invitedAt: 0, adminInfo: nil, banInfo: nil), peer: temporary.peer, peers: [memberId: temporary.peer], presences: temporary.presence != nil ? [memberId: temporary.presence!] : [:]) - } - if let participant = participant { - if case .member(_, _, _, let banInfo) = participant.participant { - let info = ChannelParticipantBannedInfo(rights: TelegramChannelBannedRights(flags: [.banSendMessages, .banReadMessages, .banSendMedia, .banSendStickers, .banEmbedLinks], untilDate: .max), restrictedBy: account.peerId, isMember: true) - restrict(participant.withUpdatedBannedRights(info), !(banInfo == nil || !banInfo!.rights.flags.isEmpty)) - } - } - }) - }) - - let peerView = account.viewTracker.peerView(peerId) - - - - let peersSignal: Signal = .single(nil) |> then(channelBlacklistParticipants(account: account, peerId: peerId) |> map { Optional($0) }) - - peersPromise.set(peersSignal) - - let initialSize = atomicSize - let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - - - let signal = combineLatest(statePromise.get(), peerView, peersPromise.get(), appearanceSignal) - |> deliverOnMainQueue - |> map { state, view, blacklist, appearance -> (TableUpdateTransition, PeerView) in - _ = viewValue.swap(view) - let entries = channelBlacklistControllerEntries(view: view, state: state, participants: blacklist).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return (prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), view) - } - - disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] transition, peerView in - if let strongSelf = self { - strongSelf.genericView.merge(with: transition) - strongSelf.readyOnce() - strongSelf.rightBarView.isHidden = strongSelf.genericView.item(at: 0) is SearchEmptyRowItem - if let peer = peerViewMainPeer(peerView) as? TelegramChannel { - strongSelf.rightBarView.isHidden = strongSelf.rightBarView.isHidden || !peer.hasAdminRights(.canBanUsers) - } - } - })) - } - - deinit { - disposable.dispose() - removePeerDisposable.dispose() - updatePeerDisposable.dispose() - } - - override func update(with state: ViewControllerState) { - super.update(with: state) - self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) - } - -} - - diff --git a/Telegram-Mac/ChannelBlocklistViewController.swift b/Telegram-Mac/ChannelBlocklistViewController.swift new file mode 100644 index 0000000000..4dd04b1954 --- /dev/null +++ b/Telegram-Mac/ChannelBlocklistViewController.swift @@ -0,0 +1,514 @@ +// +// GroupBlackListViewController.swift +// Telegram +// +// Created by keepcoder on 22/02/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + + + + +private final class ChannelBlacklistControllerArguments { + let context: AccountContext + + let removePeer: (PeerId) -> Void + let openInfo:(PeerId) -> Void + let addMember:()->Void + let returnToGroup:(PeerId) -> Void + init(context: AccountContext, removePeer: @escaping (PeerId) -> Void, openInfo:@escaping(PeerId) -> Void, addMember:@escaping()->Void, returnToGroup: @escaping(PeerId) -> Void) { + self.context = context + self.removePeer = removePeer + self.openInfo = openInfo + self.addMember = addMember + self.returnToGroup = returnToGroup + } +} + +private enum ChannelBlacklistEntryStableId: Hashable { + case peer(PeerId) + case empty + case addMember + case section(Int32) + case header(Int32) + var hashValue: Int { + switch self { + case let .peer(peerId): + return peerId.hashValue + case .empty: + return 0 + case .section: + return 1 + case .header: + return 2 + case .addMember: + return 3 + } + } + +} + +private enum ChannelBlacklistEntry: Identifiable, Comparable { + case peerItem(Int32, Int32, RenderedChannelParticipant, ShortPeerDeleting?, Bool, Bool, GeneralViewType) + case empty(Bool) + case header(Int32, Int32, String, GeneralViewType) + case section(Int32) + case addMember(Int32, Int32, GeneralViewType) + var stableId: ChannelBlacklistEntryStableId { + switch self { + case let .peerItem(_, _, participant, _, _, _, _): + return .peer(participant.peer.id) + case .empty: + return .empty + case let .section(section): + return .section(section) + case let .header(_, index, _, _): + return .header(index) + case .addMember: + return .addMember + } + } + + + + var index:Int32 { + switch self { + case let .section(section): + return (section * 1000) - section + case let .header(section, index, _, _): + return (section * 1000) + index + case let .addMember(section, index, _): + return (section * 1000) + index + case .empty: + return 0 + case let .peerItem(section, index, _, _, _, _, _): + return (section * 1000) + index + + } + } + + static func <(lhs: ChannelBlacklistEntry, rhs: ChannelBlacklistEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(_ arguments: ChannelBlacklistControllerArguments, initialSize:NSSize) -> TableRowItem { + switch self { + case let .peerItem(_, _, participant, editing, enabled, isChannel, viewType): + + let interactionType:ShortPeerItemInteractionType + if let editing = editing { + + interactionType = .deletable(onRemove: { peerId in + arguments.removePeer(peerId) + }, deletable: editing.editable) + } else { + interactionType = .plain + } + + var string:String = L10n.peerStatusRecently + + if case let .member(_, _, _, banInfo, _) = participant.participant { + if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { + if banInfo.rights.flags.contains(.banReadMessages) { + string = L10n.channelBlacklistBlockedBy(peer.displayTitle) + } else { + string = L10n.channelBlacklistRestrictedBy(peer.displayTitle) + } + } else { + if let peer = participant.peer as? TelegramUser, let botInfo = peer.botInfo { + string = botInfo.flags.contains(.hasAccessToChatHistory) ? L10n.peerInfoBotStatusHasAccess : L10n.peerInfoBotStatusHasNoAccess + } else if let presence = participant.presences[participant.peer.id] as? TelegramUserPresence { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + (string,_, _) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) + } + } + } + + return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.context.account, stableId: stableId, enabled: enabled, height:50, photoSize: NSMakeSize(36, 36), status: string, drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, viewType: viewType, action: { + if case .plain = interactionType { + arguments.openInfo(participant.peer.id) + } + }, contextMenuItems: { + var items:[ContextMenuItem] = [] + items.append(ContextMenuItem(L10n.channelBlacklistContextRemove, handler: { + arguments.removePeer(participant.peer.id) + })) + if !isChannel { + items.append(ContextMenuItem(L10n.channelBlacklistContextAddToGroup, handler: { + arguments.returnToGroup(participant.peer.id) + })) + } + + return .single(items) + }) + case let .empty(progress): + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: progress, text: L10n.channelBlacklistEmptyDescrpition) + case let .header(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .addMember(_, _, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.channelBlacklistRemoveUser, nameStyle: blueActionButton, viewType: viewType, action: { + arguments.addMember() + }) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + } + } +} + +private struct ChannelBlacklistControllerState: Equatable { + let editing: Bool + let removingPeerId: PeerId? + + init() { + self.editing = false + self.removingPeerId = nil + } + + init(editing: Bool, removingPeerId: PeerId?) { + self.editing = editing + self.removingPeerId = removingPeerId + } + + + func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState { + return ChannelBlacklistControllerState(editing: editing, removingPeerId: self.removingPeerId) + } + + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState { + return ChannelBlacklistControllerState(editing: self.editing, removingPeerId: self.removingPeerId) + } + + func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState { + return ChannelBlacklistControllerState(editing: self.editing, removingPeerId: removingPeerId) + } +} + +private func channelBlacklistControllerEntries(view: PeerView, state: ChannelBlacklistControllerState, participants: [RenderedChannelParticipant]?, inSearch: Bool) -> [ChannelBlacklistEntry] { + + var entries: [ChannelBlacklistEntry] = [] + + var index:Int32 = 10 + var sectionId:Int32 = 1 + + + if let peer = peerViewMainPeer(view) as? TelegramChannel { + + entries.append(.section(sectionId)) + sectionId += 1 + + if peer.hasPermission(.banMembers), !inSearch { + entries.append(.addMember(sectionId, 0, .singleItem)) + entries.append(.header(sectionId, 1, peer.isGroup || peer.isSupergroup ? L10n.channelBlacklistDescGroup : L10n.channelBlacklistDescChannel, .textBottomItem)) + } + if let participants = participants { + if !participants.isEmpty, peer.hasPermission(.banMembers) || inSearch { + entries.append(.section(sectionId)) + sectionId += 1 + } + + if !participants.isEmpty { + + entries.append(.header(sectionId, index, L10n.channelBlacklistBlocked, .textTopItem)) + index += 1 + for (i, participant) in participants.sorted(by: <).enumerated() { + var editable = true + if case .creator = participant.participant { + editable = false + } + + var deleting:ShortPeerDeleting? = nil + if state.editing { + deleting = ShortPeerDeleting(editable: editable) + } + + entries.append(.peerItem(sectionId, index, participant, deleting, state.removingPeerId != participant.peer.id, peer.isChannel, bestGeneralViewType(participants, for: i))) + index += 1 + } + } + } + entries.append(.section(sectionId)) + sectionId += 1 + } + if entries.isEmpty { + entries.append(.empty(participants == nil)) + } + + return entries +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ChannelBlacklistControllerArguments, inSearch: Bool, searchData: TableSearchVisibleData) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + let searchState: TableSearchViewState? + if inSearch { + searchState = .visible(searchData) + } else { + searchState = .none + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true, searchState: searchState) +} + + +class ChannelBlacklistViewController: EditableViewController { + + private let peerId:PeerId + + private let statePromise = ValuePromise(ChannelBlacklistControllerState(), ignoreRepeated: true) + private let stateValue = Atomic(value: ChannelBlacklistControllerState()) + private let removePeerDisposable:MetaDisposable = MetaDisposable() + private let updatePeerDisposable = MetaDisposable() + private let disposable:MetaDisposable = MetaDisposable() + + private let _inSearch: ValuePromise = ValuePromise(false) + private var inSearch: Bool = false { + didSet { + _inSearch.set(self.inSearch) + } + } + init(_ context:AccountContext, peerId:PeerId) { + self.peerId = peerId + super.init(context) + } + + override func viewDidLoad() { + super.viewDidLoad() + let context = self.context + let peerId = self.peerId + + + + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let actionsDisposable = DisposableSet() + + let updateState: ((ChannelBlacklistControllerState) -> ChannelBlacklistControllerState) -> Void = { [weak self] f in + if let strongSelf = self { + strongSelf.statePromise.set(strongSelf.stateValue.modify { f($0) }) + } + } + + let blacklistPromise = Promise<[RenderedChannelParticipant]?>(nil) + let listDisposable = MetaDisposable() + + let viewValue:Atomic = Atomic(value: nil) + + let restrict:(PeerId, Bool) -> Void = { [weak self] memberId, unban in + let signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: unban ? nil : TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)) |> ignoreValues + + self?.updatePeerDisposable.set(showModalProgress(signal: signal, for: mainWindow).start(error: { _ in + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + }, completed: { + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + })) + } + + let arguments = ChannelBlacklistControllerArguments(context: context, removePeer: { memberId in + + updateState { + return $0.withUpdatedRemovingPeerId(memberId) + } + + restrict(memberId, true) + }, openInfo: { [weak self] peerId in + self?.navigationController?.push(PeerInfoController(context: context, peerId: peerId)) + }, addMember: { + let behavior = SelectChannelMembersBehavior(peerId: peerId, limit: 1) + + _ = (selectModalPeers(context: context, title: L10n.channelBlacklistSelectNewUserTitle, limit: 1, behavior: behavior, confirmation: { peerIds in + if let peerId = peerIds.first { + var adminError:Bool = false + if let participant = behavior.participants[peerId] { + if case let .member(_, _, adminInfo, _, _) = participant.participant { + if let adminInfo = adminInfo { + if !adminInfo.canBeEditedByAccountPeer && adminInfo.promotedBy != context.account.peerId { + adminError = true + } + } + } else { + adminError = true + } + } + if adminError { + alert(for: mainWindow, info: L10n.channelBlacklistDemoteAdminError) + return .single(false) + } + } + return .single(true) + }) |> map {$0.first} |> filter {$0 != nil} |> map {$0!}).start(next: { memberId in + restrict(memberId, false) + }) + }, returnToGroup: { [weak self] memberId in + updateState { + return $0.withUpdatedRemovingPeerId(memberId) + } + + let signal = context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerId, memberId: memberId) |> ignoreValues + + self?.updatePeerDisposable.set(showModalProgress(signal: signal, for: mainWindow).start(error: { _ in + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + }, completed: { + updateState { + return $0.withUpdatedRemovingPeerId(nil) + } + })) + }) + + let peerView = context.account.viewTracker.peerView(peerId) + + + var (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in + if case .loading(true) = listState.loadingState, listState.list.isEmpty { + blacklistPromise.set(.single(nil)) + } else { + blacklistPromise.set(.single(listState.list)) + } + }) + + listDisposable.set(disposable) + + actionsDisposable.add(listDisposable) + + let initialSize = atomicSize + let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + + let searchData = TableSearchVisibleData(cancelImage: theme.icons.chatSearchCancel, cancel: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.inSearch = !strongSelf.inSearch + + (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in + if case .loading(true) = listState.loadingState, listState.list.isEmpty { + blacklistPromise.set(.single(nil)) + } else { + blacklistPromise.set(.single(listState.list)) + } + }) + listDisposable.set(disposable) + + }, updateState: { state in + if !state.request.isEmpty { + (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerId, searchQuery: state.request, updated: { listState in + blacklistPromise.set(.single(listState.list)) + }) + listDisposable.set(disposable) + } else { + (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in + if case .loading(true) = listState.loadingState, listState.list.isEmpty { + blacklistPromise.set(.single(nil)) + } else { + blacklistPromise.set(.single(listState.list)) + } + }) + listDisposable.set(disposable) + } + }) + + + let signal = combineLatest(statePromise.get(), peerView, blacklistPromise.get(), appearanceSignal, _inSearch.get()) + |> deliverOnMainQueue + |> map { state, view, blacklist, appearance, inSearch -> (TableUpdateTransition, PeerView) in + _ = viewValue.swap(view) + let entries = channelBlacklistControllerEntries(view: view, state: state, participants: blacklist, inSearch: inSearch).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return (prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments, inSearch: inSearch, searchData: searchData), view) + } |> afterDisposed { + actionsDisposable.dispose() + } |> deliverOnMainQueue + + self.disposable.set(signal.start(next: { [weak self] transition, peerView in + guard let `self` = self else { + return + } + self.genericView.merge(with: transition) + self.readyOnce() + self.rightBarView.isHidden = self.genericView.item(at: 0) is SearchEmptyRowItem + if let peer = peerViewMainPeer(peerView) as? TelegramChannel { + self.rightBarView.isHidden = self.rightBarView.isHidden || !peer.hasPermission(.banMembers) + } + + var hasItems: Bool = false + self.genericView.enumerateItems(with: { item -> Bool in + if item is ShortPeerRowItem { + hasItems = true + } + return !hasItems + }) + (self.centerBarView as? SearchTitleBarView)?.updateSearchVisibility(hasItems || self.inSearch) + })) + + genericView.setScrollHandler { position in + if let loadMoreControl = loadMoreControl { + switch position.direction { + case .bottom: + context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl) + default: + break + } + } + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else { + return .rejected + } + self.inSearch = !self.inSearch + return .invoked + }, with: self, for: .F, modifierFlags: [.command]) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + override var defaultBarTitle: String { + return L10n.peerInfoRemovedUsers + } + + override func getCenterBarViewOnce() -> TitledBarView { + return SearchTitleBarView(controller: self, title:.initialize(string: defaultBarTitle, color: theme.colors.text, font: .medium(.title)), handler: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.inSearch = !strongSelf.inSearch + }) + } + + deinit { + disposable.dispose() + removePeerDisposable.dispose() + updatePeerDisposable.dispose() + } + + override func update(with state: ViewControllerState) { + super.update(with: state) + self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) + } + +} + + diff --git a/Telegram-Mac/ChannelDiscussionInputView.swift b/Telegram-Mac/ChannelDiscussionInputView.swift new file mode 100644 index 0000000000..1013b9f944 --- /dev/null +++ b/Telegram-Mac/ChannelDiscussionInputView.swift @@ -0,0 +1,100 @@ +// +// ChannelDiscussionInputView.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +class ChannelDiscussionInputView: View { + private let leftButton: TitleButton = TitleButton() + private let rightButton: TitleButton = TitleButton() + private var badge:BadgeNode? + private var badgeView:View = View() + private let disposable = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + leftButton.disableActions() + rightButton.disableActions() + addSubview(leftButton) + addSubview(rightButton) + addSubview(badgeView) + } + + func update(with chatInteraction: ChatInteraction, discussionGroupId: PeerId?, leftAction: String, rightAction: String) { + leftButton.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.accent) + leftButton.set(text: leftAction, for: .Normal) + leftButton.set(background: theme.colors.grayBackground, for: .Highlight) + + rightButton.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.accent) + rightButton.set(text: rightAction, for: .Normal) + rightButton.set(background: theme.colors.grayBackground, for: .Highlight) + + + leftButton.removeAllHandlers() + leftButton.set(handler: { [weak chatInteraction] _ in + chatInteraction?.toggleNotifications(nil) + }, for: .Click) + + rightButton.removeAllHandlers() + rightButton.set(handler: { [weak chatInteraction] _ in + chatInteraction?.openDiscussion() + }, for: .Click) + + let context = chatInteraction.context + if let discussionGroupId = discussionGroupId { + self.disposable.set((context.account.postbox.unreadMessageCountsView(items: [.peer(discussionGroupId)]) |> deliverOnMainQueue).start(next: { [weak self] unreadView in + if let strongSelf = self { + let count = unreadView.count(for: .peer(discussionGroupId)) ?? 0 + if count > 0 { + strongSelf.badge = BadgeNode(.initialize(string: Int(count).prettyNumber, color: .white, font: .bold(.small)), theme.colors.accent) + strongSelf.badge!.view = strongSelf.badgeView + strongSelf.badgeView.setFrameSize(strongSelf.badge!.size) + strongSelf.addSubview(strongSelf.badgeView) + } else { + strongSelf.badgeView.removeFromSuperview() + } + strongSelf.needsLayout = true + + } + })) + } else { + self.badgeView.removeFromSuperview() + self.disposable.set(nil) + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + leftButton.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.accent) + leftButton.set(background: theme.colors.grayBackground, for: .Highlight) + rightButton.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.accent) + rightButton.set(background: theme.colors.grayBackground, for: .Highlight) + + } + + override func layout() { + super.layout() + leftButton.frame = NSMakeRect(0, 0, frame.width / 2, frame.height) + rightButton.frame = NSMakeRect(frame.width / 2, 0, frame.width / 2, frame.height) + badgeView.centerY(x: rightButton.frame.maxX - (rightButton.frame.width - rightButton.textSize.width) / 2 + 5) + + } + + + deinit { + disposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ChannelDisscussionGroup.swift b/Telegram-Mac/ChannelDisscussionGroup.swift new file mode 100644 index 0000000000..5f920f22dc --- /dev/null +++ b/Telegram-Mac/ChannelDisscussionGroup.swift @@ -0,0 +1,524 @@ +// +// ChannelDiscussionGroup.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + +private final class DiscussionArguments { + let context: AccountContext + let createGroup:()->Void + let setup:(Peer)->Void + let openInfo:(PeerId) -> Void + let unlinkGroup: (Peer)->Void + init(context: AccountContext, createGroup: @escaping()->Void, setup: @escaping(Peer)->Void, openInfo: @escaping(PeerId)->Void, unlinkGroup: @escaping(Peer)->Void) { + self.context = context + self.createGroup = createGroup + self.setup = setup + self.openInfo = openInfo + self.unlinkGroup = unlinkGroup + } +} + +private func generateDiscussIcon() -> CGImage { + let image: CGImage + switch theme.colors.name { + case systemPalette.name: + image = NSImage(named: "DiscussDarkPreview")!.precomposed() + case nightAccentPalette.name: + image = NSImage(named: "DiscussDarkBluePreview")!.precomposed() + default: + if theme.colors.isDark { + image = NSImage(named: "DiscussDarkBluePreview")!.precomposed() + } else { + image = NSImage(named: "DiscussDayPreview")!.precomposed() + } + } + + + return generateImage(image.backingSize, contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(image, in: NSMakeRect(0, 0, size.width, size.height)) + + let palette = theme.colors + + let attributeString: NSAttributedString = .initialize(string: L10n.discussionControllerIconText, color: palette.accentIcon, font: .normal(12)) + + let node = TextNode.layoutText(maybeNode: nil, attributeString, palette.background, 1, .end, NSMakeSize(size.width - 10, size.height), nil, false, .center) + + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: 1.0, y: -1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + let xpos: CGFloat = (size.width - node.0.size.width) / 2 + node.1.draw(NSMakeRect(xpos, size.height - 26, node.0.size.width, node.0.size.height), in: ctx, backingScaleFactor: System.backingScale, backgroundColor: palette.background) + })! +} + +private enum DiscussionType { + case group + case channel +} + +private final class DiscussionState : Equatable { + let type: DiscussionType + let availablePeers:[Peer] + let associatedPeer: Peer? + let unlinkAbility: Bool + let searchState: SearchState? + init(type: DiscussionType, availablePeers: [Peer], associatedPeer: Peer?, unlinkAbility: Bool, searchState: SearchState?) { + self.type = type + self.searchState = searchState + self.availablePeers = availablePeers + self.associatedPeer = associatedPeer + self.unlinkAbility = unlinkAbility + } + + var filteredPeers: [Peer] { + return self.availablePeers.filter { peer in + if let search = self.searchState, !search.request.isEmpty { + return peer.displayTitle.lowercased().hasPrefix(search.request.lowercased()) || !peer.displayTitle.lowercased().components(separatedBy: " ").filter {$0.hasPrefix(search.request.lowercased())}.isEmpty + + } else { + return true + } + } + } + + func withUpdatedassociatedPeer(_ associatedPeer: Peer?) -> DiscussionState { + return DiscussionState(type: self.type, availablePeers: self.availablePeers, associatedPeer: associatedPeer, unlinkAbility: self.unlinkAbility, searchState: self.searchState) + } + func withUpdatedAvailablePeers(_ availablePeers: [Peer]) -> DiscussionState { + return DiscussionState(type: self.type, availablePeers: availablePeers, associatedPeer: self.associatedPeer, unlinkAbility: self.unlinkAbility, searchState: self.searchState) + } + + func withUpdatedUnlinkAbility(_ unlinkAbility: Bool) -> DiscussionState { + return DiscussionState(type: self.type, availablePeers: self.availablePeers, associatedPeer: self.associatedPeer, unlinkAbility: unlinkAbility, searchState: self.searchState) + } + + func withUpdatedSearchState(_ searchState: SearchState) -> DiscussionState { + return DiscussionState(type: self.type, availablePeers: self.availablePeers, associatedPeer: self.associatedPeer, unlinkAbility: self.unlinkAbility, searchState: searchState) + } + + static func == (lhs: DiscussionState, rhs: DiscussionState) -> Bool { + if let lhsassociatedPeer = lhs.associatedPeer, let rhsassociatedPeer = rhs.associatedPeer { + if !lhsassociatedPeer.isEqual(rhsassociatedPeer) { + return false + } + } else if (lhs.associatedPeer != nil) != (rhs.associatedPeer != nil) { + return false + } + + if lhs.searchState != rhs.searchState { + return false + } + + if lhs.availablePeers.count != rhs.availablePeers.count { + return false + } else { + for (i, lhsPeer) in lhs.availablePeers.enumerated() { + if !lhsPeer.isEqual(rhs.availablePeers[i]) { + return false + } + } + } + return true + } + +} +private let _id_channel_header = InputDataIdentifier("_id_channel_header") +private let _id_group_header = InputDataIdentifier("_id_group_header") + +private let _id_create_group = InputDataIdentifier("_id_create_group") +private let _id_unlink_group = InputDataIdentifier("_id_unlink_group") +private func _id_peer(_ peerId: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_peer_\(peerId.toInt64())") +} +private func _id_peer_info(_ peerId: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_peer_\(peerId.toInt64())_info") +} + + + +private func channelDiscussionEntries(state: DiscussionState, arguments: DiscussionArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + let applyList:()->Void = { + + let peers = state.filteredPeers + + for (i, peer) in peers.enumerated() { + + let status = peer.addressName != nil ? "@\(peer.addressName!)" : (peer.isSupergroup || peer.isGroup ? L10n.discussionControllerPrivateGroup : L10n.discussionControllerPrivateChannel) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_peer(peer.id), equatable: InputDataEquatable(PeerEquatable(peer: peer)), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, status: status, inset: NSEdgeInsetsMake(0, 30, 0, 30), viewType: i == 0 ? .innerItem : bestGeneralViewType(peers, for: i), action: { + arguments.setup(peer) + }) + })) + index += 1 + } + } + + switch state.type { + case .channel: + if let associatedPeer = state.associatedPeer { + let text = L10n.discussionControllerChannelSetHeader(associatedPeer.displayTitle) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_channel_header, equatable: InputDataEquatable(text), item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + _ = attributedString.append(string: text, color: theme.colors.grayText, font: .normal(.text)) + attributedString.detectBoldColorInString(with: .medium(.text)) + + return DiscussionHeaderItem(initialSize, stableId: stableId, icon: generateDiscussIcon(), text: attributedString) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let status = associatedPeer.addressName != nil ? "@\(associatedPeer.addressName!)" : (associatedPeer.isSupergroup ? L10n.discussionControllerPrivateGroup : L10n.discussionControllerPrivateChannel) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_peer_info(associatedPeer.id), equatable: InputDataEquatable(PeerEquatable(peer: associatedPeer)), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: associatedPeer, account: arguments.context.account, status: status, inset: NSEdgeInsetsMake(0, 30, 0, 30), viewType: .singleItem, action: { + arguments.openInfo(associatedPeer.id) + }) + })) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.discussionControllerChannelSetDescription), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + if state.unlinkAbility { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_unlink_group, data: InputDataGeneralData(name: L10n.discussionControllerChannelSetUnlinkGroup, color: theme.colors.redUI, viewType: .singleItem, action: { + arguments.unlinkGroup(associatedPeer) + }))) + index += 1 + } + + } else { + let text = L10n.discussionControllerChannelEmptyHeader + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_channel_header, equatable: InputDataEquatable(text), item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + _ = attributedString.append(string: text, color: theme.colors.grayText, font: .normal(.text)) + return DiscussionHeaderItem(initialSize, stableId: stableId, icon: generateDiscussIcon(), text: attributedString) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + if state.searchState == nil || state.searchState!.request.isEmpty { + + let viewType: GeneralViewType = state.filteredPeers.isEmpty ? .singleItem : .firstItem + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_create_group, equatable: InputDataEquatable(viewType), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.discussionControllerChannelEmptyCreateGroup, nameStyle: blueActionButton, viewType: viewType, action: arguments.createGroup, thumb: GeneralThumbAdditional(thumb: theme.icons.peerInfoAddMember, textInset: 52, thumbInset: 5)) + })) + index += 1 + } + + applyList() + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.discussionControllerChannelEmptyDescription), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + } + case .group: + if let associatedPeer = state.associatedPeer { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_channel_header, equatable: nil, item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + _ = attributedString.append(string: L10n.discussionControllerGroupSetHeader(associatedPeer.displayTitle), color: theme.colors.grayText, font: .normal(.text)) + attributedString.detectBoldColorInString(with: .medium(.text)) + + return DiscussionHeaderItem(initialSize, stableId: stableId, icon: generateDiscussIcon(), text: attributedString) + })) + + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let status = associatedPeer.addressName != nil ? "@\(associatedPeer.addressName!)" : (associatedPeer.isSupergroup ? L10n.discussionControllerPrivateGroup : L10n.discussionControllerPrivateChannel) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_peer_info(associatedPeer.id), equatable: InputDataEquatable(PeerEquatable(peer: associatedPeer)), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: associatedPeer, account: arguments.context.account, status: status, inset: NSEdgeInsetsMake(0, 30, 0, 30), viewType: .singleItem, action: { + arguments.openInfo(associatedPeer.id) + }) + })) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.discussionControllerGroupSetDescription), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_unlink_group, data: InputDataGeneralData(name: L10n.discussionControllerGroupSetUnlinkChannel, color: theme.colors.redUI, viewType: .singleItem, action: { + arguments.unlinkGroup(associatedPeer) + }))) + index += 1 + } else { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_group_header, equatable: nil, item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + _ = attributedString.append(string: L10n.discussionControllerGroupUnsetDescription, color: theme.colors.grayText, font: .normal(.text)) + return GeneralTextRowItem(initialSize, stableId: stableId, text: attributedString, alignment: .center, centerViewAlignment: true, viewType: .textBottomItem) + })) + + } + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func ChannelDiscussionSetupController(context: AccountContext, peer: Peer)-> InputDataController { + + let initialState = DiscussionState(type: peer.isChannel ? .channel : .group, availablePeers: [], associatedPeer: nil, unlinkAbility: false, searchState: nil) + + let stateValue: Atomic = Atomic(value: initialState) + let statePromise:ValuePromise = ValuePromise(ignoreRepeated: true) + + let updateState:((DiscussionState)->DiscussionState)->Void = { f in + statePromise.set(stateValue.modify(f)) + } + + + let searchValue:Atomic = Atomic(value: .none) + let searchPromise: ValuePromise = ValuePromise(.none, ignoreRepeated: true) + let updateSearchValue:((TableSearchViewState)->TableSearchViewState)->Void = { f in + searchPromise.set(searchValue.modify(f)) + } + + + let searchData = TableSearchVisibleData(cancelImage: theme.icons.chatSearchCancel, cancel: { + updateSearchValue { _ in + return .none + } + }, updateState: { searchState in + updateState { + $0.withUpdatedSearchState(searchState) + } + }) + + let actionsDisposable = DisposableSet() + + func setup(_ channelId: PeerId, _ groupId: PeerId?, updatePreHistory: Bool = false) -> Void { + let signal: Signal + + if let groupId = groupId, groupId.namespace == Namespaces.Peer.CloudGroup { + signal = convertGroupToSupergroup(account: context.account, peerId: groupId) + |> mapError { value in + return (value, nil) + } + |> mapToSignal { upgradedPeerId in + return updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: channelId, groupId: upgradedPeerId) |> mapError { value in return (nil, value) } + } + } else if updatePreHistory, let groupId = groupId { + signal = updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: groupId, historyAvailableForNewMembers: true) + |> mapError { error -> (ConvertGroupToSupergroupError?, ChannelDiscussionGroupError?) in + switch error { + case .generic: + return (nil, .generic) + case .hasNotPermissions: + return (nil, .hasNotPermissions) + } + } |> mapToSignal { _ in + return updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: channelId, groupId: groupId) |> mapError { value in return (nil, value) } + } + } else { + signal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: channelId, groupId: groupId) |> mapError { value in return (nil, value) } + } + + actionsDisposable.add(showModalProgress(signal: signal |> deliverOnMainQueue, for: context.window).start(next: { result in + if result && groupId == nil && initialState.type == .group { + context.sharedContext.bindings.rootNavigation().back() + } + updateSearchValue { current in + return .none + } + }, error: { upgradeError, discussError in + if let error = upgradeError { + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + } else if let error = discussError { + switch error { + case .groupHistoryIsCurrentlyPrivate: + confirm(for: context.window, information: L10n.discussionControllerErrorPreHistory, okTitle: L10n.discussionControllerErrorOK, successHandler: { _ in + setup(channelId, groupId, updatePreHistory: true) + }) + case .hasNotPermissions: + alert(for: context.window, info: L10n.channelErrorDontHavePermissions) + default: + alert(for: context.window, info: L10n.unknownError) + } + } + + })) + } + + let arguments = DiscussionArguments(context: context, createGroup: { + let controller = context.sharedContext.bindings.rootNavigation().controller + actionsDisposable.add(createSupergroup(with: context, defaultText: peer.displayTitle + " Chat").start(next: { [weak controller] peerId in + if let peerId = peerId, let controller = controller { + setup(peer.id, peerId) + context.sharedContext.bindings.rootNavigation().removeUntil(InputDataController.self) + context.sharedContext.bindings.rootNavigation().push(controller) + } + })) + }, setup: { selected in + showModal(with: DiscussionSetModalController(context: context, channel: peer, group: selected, accept: { + if selected.isChannel { + setup(selected.id, peer.id) + } else { + setup(peer.id, selected.id) + } + }), for: context.window) + }, openInfo: { peerId in + context.sharedContext.bindings.rootNavigation().push(ChatAdditionController(context: context, chatLocation: .peer(peerId))) + }, unlinkGroup: { associated in + if associated.isChannel { + confirm(for: context.window, information: L10n.discussionControllerConfrimUnlinkChannel, successHandler: { _ in + setup(associated.id, nil) + }) + } else { + confirm(for: context.window, information: L10n.discussionControllerConfrimUnlinkGroup, successHandler: { _ in + setup(peer.id, nil) + }) + } + }) + + + let dataSignal = statePromise.get() |> map { state in + return channelDiscussionEntries(state: state, arguments: arguments) + } + + var updateBarIsHidden:((Bool)->Void)? = nil + + + actionsDisposable.add(context.account.postbox.peerView(id: peer.id).start(next: { peerView in + updateState { current in + var current = current + let peer = peerViewMainPeer(peerView) + if let cachedData = peerView.cachedData as? CachedChannelData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + current = current.withUpdatedassociatedPeer(peerView.peers[linkedDiscussionPeerId]) + } else { + current = current.withUpdatedassociatedPeer(nil) + } + if let linkedPeer = current.associatedPeer as? TelegramChannel, linkedPeer.isChannel { + current = current.withUpdatedUnlinkAbility(linkedPeer.hasPermission(.pinMessages)) + } else if let peer = peer as? TelegramChannel { + current = current.withUpdatedUnlinkAbility(peer.hasPermission(.pinMessages)) + } + return current + } + })) + + + let availableSignal = peer.isChannel ? availableGroupsForChannelDiscussion(postbox: context.account.postbox, network: context.account.network) : .single([]) + + actionsDisposable.add(availableSignal.start(next: { peers in + updateState { + $0.withUpdatedAvailablePeers(peers) + } + }, error: { error in + + })) + + + + + + + return InputDataController(dataSignal: combineLatest(dataSignal, searchPromise.get()) |> map { InputDataSignalValue(entries: $0, searchState: $1) }, title: peer.isChannel ? L10n.discussionControllerChannelTitle : L10n.discussionControllerGroupTitle, afterDisappear: { + actionsDisposable.dispose() + }, removeAfterDisappear: false, hasDone: false, customRightButton: { controller in + let bar = ImageBarView(controller: controller, theme.icons.chatSearch) + bar.button.set(handler: { _ in + updateSearchValue { current in + switch current { + case .none: + return .visible(searchData) + case .visible: + return .none + } + } + }, for: .Click) + updateBarIsHidden = { [weak bar] isHidden in + bar?.button.alphaValue = isHidden ? 0 : 1 + } + //let isHidden = stateValue.with {$0.associatedPeer != nil && $0.availablePeers.count > 5} + + + return bar + }, afterTransaction: { controller in + + let isHidden = stateValue.with {$0.associatedPeer != nil || $0.availablePeers.count < 5} + updateBarIsHidden?(isHidden) + + }, returnKeyInvocation: { _, _ in + let state = stateValue.with { $0 } + + if state.associatedPeer == nil, state.type == .channel, state.filteredPeers.count == 1, let searchState = state.searchState, !searchState.request.isEmpty { + arguments.setup(state.filteredPeers[0]) + return .nothing + } + + return .default + }, deleteKeyInvocation: { _ in + + let state = stateValue.with { $0 } + + if let peer = state.associatedPeer, state.unlinkAbility { + arguments.unlinkGroup(peer) + return .invoked + } + + return .default + }, searchKeyInvocation: { + + let state = stateValue.with { $0 } + + if state.associatedPeer == nil, state.availablePeers.count > 5 { + updateSearchValue { current in + switch current { + case .none: + return .visible(searchData) + case .visible: + return .none + } + } + } + + + return .invoked + }) +} diff --git a/Telegram-Mac/ChannelEventFilterModalController.swift b/Telegram-Mac/ChannelEventFilterModalController.swift index 1c361e10af..a63a1ad05b 100644 --- a/Telegram-Mac/ChannelEventFilterModalController.swift +++ b/Telegram-Mac/ChannelEventFilterModalController.swift @@ -8,20 +8,21 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox private final class ChannelFilterArguments { - let account:Account + let context: AccountContext let toggleFlags:(FilterEvents)->Void let toggleAdmin:(PeerId)->Void let toggleAllAdmins:()->Void let toggleAllEvents:()->Void - init(account:Account, toggleFlags:@escaping(FilterEvents)->Void, toggleAdmin:@escaping(PeerId)->Void, toggleAllAdmins:@escaping()->Void, toggleAllEvents:@escaping()->Void) { - self.account = account + init(context: AccountContext, toggleFlags:@escaping(FilterEvents)->Void, toggleAdmin:@escaping(PeerId)->Void, toggleAllAdmins:@escaping()->Void, toggleAllEvents:@escaping()->Void) { + self.context = context self.toggleFlags = toggleFlags self.toggleAdmin = toggleAdmin self.toggleAllAdmins = toggleAllAdmins @@ -55,52 +56,6 @@ private enum ChannelEventFilterEntryId : Hashable { return 6 } } - static func ==(lhs: ChannelEventFilterEntryId, rhs: ChannelEventFilterEntryId) -> Bool { - switch lhs { - case .section(let value): - if case .section(value) = rhs { - return true - } else { - return false - } - case .header(let value): - if case .header(value) = rhs { - return true - } else { - return false - } - case .allEvents: - if case .allEvents = rhs { - return true - } else { - return false - } - case .filter(let value): - if case .filter(value) = rhs { - return true - } else { - return false - } - case .allAdmins: - if case .allAdmins = rhs { - return true - } else { - return false - } - case .admin(let value): - if case .admin(value) = rhs { - return true - } else { - return false - } - case .adminsLoading: - if case .adminsLoading = rhs { - return true - } else { - return false - } - } - } } private enum ChannelEventFilterEntry : TableItemListNodeEntry { @@ -156,21 +111,15 @@ private enum ChannelEventFilterEntry : TableItemListNodeEntry { case .header(_, _, let text): return GeneralTextRowItem(initialSize, stableId: stableId, text: text) case .allAdmins(_, _, let enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "All Admins", type: .switchable (stateback: { - return enabled - }), action: { + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chanelEventFilterAllAdmins, type: .switchable (enabled), action: { arguments.toggleAllAdmins() }) case .allEvents(_, _, let enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "All Events", type: .switchable (stateback: { - return enabled - }), action: { + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chanelEventFilterAllEvents, type: .switchable (enabled), action: { arguments.toggleAllEvents() }) case let .filter(_, _, flag, name, enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, type: .selectable(enabled), action: { arguments.toggleFlags(flag) }) case .adminsLoading: @@ -180,66 +129,17 @@ private enum ChannelEventFilterEntry : TableItemListNodeEntry { let status:String switch participant.participant { case .creator: - status = tr(.adminsCreator) + status = L10n.adminsOwner case .member: - status = tr(.adminsAdmin) + status = L10n.adminsAdmin } - return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.account, stableId: stableId, height: 40, photoSize: NSMakeSize(30, 30), status: status, inset: NSEdgeInsets(left: 30, right: 30), interactionType: .plain, generalType: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.context.account, stableId: stableId, height: 40, photoSize: NSMakeSize(30, 30), status: status, inset: NSEdgeInsets(left: 30, right: 30), interactionType: .plain, generalType: .selectable(enabled), action: { arguments.toggleAdmin(participant.peer.id) }) } } } -private func ==(lhs:ChannelEventFilterEntry, rhs:ChannelEventFilterEntry) -> Bool { - switch lhs { - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - case let .header(section, index, text): - if case .header(section, index, text) = rhs { - return true - } else { - return false - } - case let .allEvents(section, index, enabled): - if case .allEvents(section, index, enabled) = rhs { - return true - } else { - return false - } - case let .filter(section, index, flags, text, enabled): - if case .filter(section, index, flags, text, enabled) = rhs { - return true - } else { - return false - } - case let .allAdmins(section, index, enabled): - if case .allAdmins(section, index, enabled) = rhs { - return true - } else { - return false - } - case let .adminsLoading(section, index): - if case .adminsLoading(section, index) = rhs { - return true - } else { - return false - } - case let .admin(section, index, participant, enabled): - if case .admin(section, index, participant, enabled) = rhs { - return true - } else { - return false - } - } -} - private func <(lhs:ChannelEventFilterEntry, rhs: ChannelEventFilterEntry) -> Bool { return lhs.index < rhs.index } @@ -354,21 +254,21 @@ private enum FilterEvents { func localizedString(_ broadcast:Bool) -> String { switch self { case .newMembers: - return tr(.channelEventFilterNewMembers) + return tr(L10n.channelEventFilterNewMembers) case .newAdmins: - return tr(.channelEventFilterNewAdmins) + return tr(L10n.channelEventFilterNewAdmins) case .leavingMembers: - return tr(.channelEventFilterLeavingMembers) + return tr(L10n.channelEventFilterLeavingMembers) case .restrictions: - return tr(.channelEventFilterNewRestrictions) + return tr(L10n.channelEventFilterNewRestrictions) case .groupInfo: - return broadcast ? tr(.channelEventFilterChannelInfo) : tr(.channelEventFilterGroupInfo) + return broadcast ? tr(L10n.channelEventFilterChannelInfo) : tr(L10n.channelEventFilterGroupInfo) case .pinnedMessages: - return tr(.channelEventFilterPinnedMessages) + return tr(L10n.channelEventFilterPinnedMessages) case .editedMessages: - return tr(.channelEventFilterEditedMessages) + return tr(L10n.channelEventFilterEditedMessages) case .deletedMessages: - return tr(.channelEventFilterDeletedMessages) + return tr(L10n.channelEventFilterDeletedMessages) } } } @@ -391,7 +291,7 @@ private func channelEventFilterEntries(state: ChannelEventFilterState, peer:Peer entries.append(.section(section)) section += 1 - entries.append(.header(section, index, text: tr(.channelEventFilterEventsHeader))) + entries.append(.header(section, index, text: tr(L10n.channelEventFilterEventsHeader))) index += 1 entries.append(.allEvents(section, index, enabled: state.eventsException.isEmpty)) index += 1 @@ -404,7 +304,7 @@ private func channelEventFilterEntries(state: ChannelEventFilterState, peer:Peer entries.append(.section(section)) section += 1 - entries.append(.header(section, index, text: tr(.channelEventFilterAdminsHeader))) + entries.append(.header(section, index, text: tr(L10n.channelEventFilterAdminsHeader))) index += 1 entries.append(.allAdmins(section, index, enabled: state.adminsException.isEmpty)) @@ -433,14 +333,16 @@ fileprivate func prepareTransition(left:[ChannelEventFilterEntry], right: [Chann class ChannelEventFilterModalController: ModalViewController { private let peerId:PeerId - private let account:Account + private let context:AccountContext private let stateValue = Atomic(value: ChannelEventFilterState()) private let disposable = MetaDisposable() private let updated:(ChannelEventFilterState) -> Void - init(account:Account, peerId:PeerId, state: ChannelEventFilterState = ChannelEventFilterState(), updated:@escaping(ChannelEventFilterState) -> Void) { - self.account = account + private let admins: [RenderedChannelParticipant] + init(context: AccountContext, peerId:PeerId, admins: [RenderedChannelParticipant], state: ChannelEventFilterState = ChannelEventFilterState(), updated:@escaping(ChannelEventFilterState) -> Void) { + self.context = context self.peerId = peerId + self.admins = admins self.updated = updated _ = self.stateValue.swap(state) super.init(frame: NSMakeRect(0, 0, 300, 300)) @@ -480,7 +382,7 @@ class ChannelEventFilterModalController: ModalViewController { statePromise.set(stateValue.modify { f($0) }) } - let arguments = ChannelFilterArguments(account: account, toggleFlags: { flags in + let arguments = ChannelFilterArguments(context: context, toggleFlags: { flags in updateState({$0.withToggledEventsException(flags)}) }, toggleAdmin: { peerId in updateState({$0.withToggledAdminsException(peerId)}) @@ -493,11 +395,11 @@ class ChannelEventFilterModalController: ModalViewController { let previous: Atomic<[ChannelEventFilterEntry]> = Atomic(value: []) let initialSize = self.atomicSize - let adminsSignal = Signal<[RenderedChannelParticipant]?, Void>.single(nil) |> then ( channelAdmins(account: account, peerId: peerId) |> map {Optional($0)}) + let adminsSignal = Signal<[RenderedChannelParticipant], NoError>.single(admins) let updatedSize:Atomic = Atomic(value: false) - let signal:Signal = combineLatest(statePromise.get(), account.postbox.loadedPeerWithId(peerId), adminsSignal) |> map { state, peer, admins -> (ChannelEventFilterState, Peer, [RenderedChannelParticipant]?) in + let signal:Signal = combineLatest(statePromise.get(), context.account.postbox.loadedPeerWithId(peerId), adminsSignal) |> map { state, peer, admins -> (ChannelEventFilterState, Peer, [RenderedChannelParticipant]?) in - let state = stateValue.swap(state.withUpdatedAllAdmins(Set(admins?.map {$0.peer.id} ?? [])).withUpdatedAllEvents(Set(eventFilters(peer.isChannel)))) + let state = stateValue.swap(state.withUpdatedAllAdmins(Set(admins.map {$0.peer.id})).withUpdatedAllEvents(Set(eventFilters(peer.isChannel)))) return (state, peer, admins) } |> map { state, peer, admins in @@ -526,9 +428,9 @@ class ChannelEventFilterModalController: ModalViewController { } override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in + return ModalInteractions(acceptTitle: tr(L10n.modalOK), accept: { [weak self] in self?.noticeUpdated() - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + }, cancelTitle: L10n.modalCancel, drawBorder: true, height: 40) } deinit { diff --git a/Telegram-Mac/ChannelEventLogController.swift b/Telegram-Mac/ChannelEventLogController.swift index deddd5418f..5176891cbd 100644 --- a/Telegram-Mac/ChannelEventLogController.swift +++ b/Telegram-Mac/ChannelEventLogController.swift @@ -11,9 +11,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class ChannelEventLogTitledView : TitledBarView { @@ -27,23 +28,22 @@ class ChannelEventLogTitledView : TitledBarView { override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - ctx.setFillColor(theme.colors.background.cgColor) - ctx.fill(bounds) + layer.backgroundColor = theme.colors.background.cgColor - let (textLayout, textApply) = TextNode.layoutText(maybeNode: titleNode, attributedText, nil, 1, .end, NSMakeSize(bounds.width - 40, bounds.height), nil,false, .left) + let (textLayout, textApply) = TextNode.layoutText(maybeNode: titleNode, attributedText, nil, 1, .end, NSMakeSize(bounds.width - 40, bounds.height), nil,false, .left) let textRect = focus(textLayout.size) - textApply.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor) + textApply.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) var iconRect = focus(theme.icons.eventLogTriangle.backingSize) iconRect.origin.x = textRect.maxX + 6 iconRect.origin.y += 1 ctx.draw(theme.icons.eventLogTriangle, in: iconRect) - + } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) backgroundColor = theme.colors.background needsDisplay = true } @@ -74,20 +74,20 @@ private class SearchContainerView : View { addSubview(searchView) addSubview(separator) cancelButton.set(font: .medium(.text), for: .Normal) - cancelButton.set(text: tr(.chatCancel), for: .Normal) - cancelButton.sizeToFit() + cancelButton.set(text: tr(L10n.chatCancel), for: .Normal) + _ = cancelButton.sizeToFit() cancelButton.set(handler: { [weak self] _ in self?.hideSearch?() - }, for: .Click) + }, for: .Click) addSubview(cancelButton) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) cancelButton.set(background: theme.colors.background, for: .Normal) - cancelButton.set(color: theme.colors.blueUI, for: .Normal) + cancelButton.set(color: theme.colors.accent, for: .Normal) separator.backgroundColor = theme.colors.border backgroundColor = theme.colors.background @@ -135,18 +135,18 @@ class ChannelEventLogView : View { emptyTextView.isSelectable = false separator.backgroundColor = .border whatButton.set(font: .medium(.title), for: .Normal) - whatButton.set(text: tr(.channelEventLogWhat), for: .Normal) + whatButton.set(text: tr(L10n.channelEventLogWhat), for: .Normal) whatButton.set(handler: { _ in - alert(for: mainWindow, header: tr(.channelEventLogAlertHeader), info: tr(.channelEventLogAlertInfo)) + alert(for: mainWindow, header: tr(L10n.channelEventLogAlertHeader), info: tr(L10n.channelEventLogAlertInfo)) }, for: .Click) setFrameSize(frameRect.size) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - whatButton.set(color: theme.colors.blueUI, for: .Normal) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + whatButton.set(color: theme.colors.accent, for: .Normal) whatButton.set(background: theme.colors.grayTransparent, for: .Highlight) whatButton.set(background: theme.colors.background, for: .Normal) emptyTextView.backgroundColor = theme.colors.background @@ -236,19 +236,26 @@ extension AdminLogEventsResult { return false } - var banHelp:[TelegramChannelBannedRightsFlags] { - var order:[TelegramChannelBannedRightsFlags] = [] + var banHelp:[TelegramChatBannedRightsFlags] { + var order:[TelegramChatBannedRightsFlags] = [] order.append(.banSendMessages) + order.append(.banReadMessages) + order.append(.banChangeInfo) order.append(.banSendMedia) order.append(.banSendStickers) + order.append(.banSendGifs) + order.append(.banAddMembers) + order.append(.banPinMessages) + order.append(.banSendInline) + order.append(.banSendPolls) order.append(.banEmbedLinks) return order } - var rightsHelp:(specific: TelegramChannelAdminRightsFlags, order: [TelegramChannelAdminRightsFlags]) { + var rightsHelp:(specific: TelegramChatAdminRightsFlags, order: [TelegramChatAdminRightsFlags]) { if let peer = peers[peerId] as? TelegramChannel { - let maskRightsFlags: TelegramChannelAdminRightsFlags - let rightsOrder: [TelegramChannelAdminRightsFlags] + let maskRightsFlags: TelegramChatAdminRightsFlags + let rightsOrder: [TelegramChatAdminRightsFlags] switch peer.info { case .broadcast: @@ -267,7 +274,6 @@ extension AdminLogEventsResult { .canDeleteMessages, .canBanUsers, .canInviteUsers, - .canChangeInviteLink, .canPinMessages, .canAddAdmins ] @@ -283,18 +289,20 @@ extension AdminLogEventsResult { private func eventLogItems(_ result:AdminLogEventsResult, initialSize: NSSize, chatInteraction: ChatInteraction) -> [TableRowItem] { var items:[TableRowItem] = [] var index:Int = 0 - let timeDifference = Int32(chatInteraction.account.context.timeDifference) + let timeDifference = Int32(chatInteraction.context.timeDifference) for event in result.events { switch event.action { case let .editMessage(prev, new): - let item = ChatRowItem.item(initialSize, from: .MessageEntry(new.withUpdatedStableId(arc4random()), true, .Full(isAdmin: false), nil, nil), with: chatInteraction.account, interaction: chatInteraction) - items.append(ChannelEventLogEditedPanelItem(initialSize, previous: prev, item: item)) - items.append(item) + let item = ChatRowItem.item(initialSize, from: .MessageEntry(new.withUpdatedStableId(arc4random()), MessageIndex(new), true, .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)), interaction: chatInteraction, theme: theme) as? ChatRowItem + if let item = item { + items.append(ChannelEventLogEditedPanelItem(initialSize, previous: prev, item: item)) + items.append(item) + } case let .deleteMessage(message): - items.append(ChatRowItem.item(initialSize, from: .MessageEntry(message.withUpdatedStableId(arc4random()), true, .Full(isAdmin: false), nil, nil), with: chatInteraction.account, interaction: chatInteraction)) + items.append(ChatRowItem.item(initialSize, from: .MessageEntry(message.withUpdatedStableId(arc4random()), MessageIndex(message), true, .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)), interaction: chatInteraction, theme: theme)) case let .updatePinned(message): if let message = message?.withUpdatedStableId(arc4random()) { - items.append(ChatRowItem.item(initialSize, from: .MessageEntry(message, true, .Full(isAdmin: false), nil, nil), with: chatInteraction.account, interaction: chatInteraction)) + items.append(ChatRowItem.item(initialSize, from: .MessageEntry(message, MessageIndex(message), true, .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)), interaction: chatInteraction, theme: theme)) } default: break @@ -309,12 +317,12 @@ private func eventLogItems(_ result:AdminLogEventsResult, initialSize: NSSize, c let nextDateId = chatDateId(for: nextEvent.date - timeDifference) if dateId != nextDateId { let messageIndex = MessageIndex(id: MessageId(peerId: result.peerId, namespace: 0, id: INT_MAX), timestamp: Int32(dateId)) - items.append(ChatDateStickItem(initialSize, .DateEntry(messageIndex), interaction: chatInteraction)) + items.append(ChatDateStickItem(initialSize, .DateEntry(messageIndex, .list), interaction: chatInteraction, theme: theme)) } } index += 1 - + } for item in items { _ = item.makeSize(initialSize.width, oldWidth: initialSize.width) @@ -329,8 +337,8 @@ class ChannelEventLogController: TelegramGenericViewController = Promise() private var state:Atomic = Atomic(value: nil) private let disposable = MetaDisposable() - private let openPeerDisposable = MetaDisposable() private let searchState:ValuePromise = ValuePromise(SearchState(state: .None, request: nil), ignoreRepeated: true) + private let filterDisposable = MetaDisposable() override func viewClass() -> AnyClass { return ChannelEventLogView.self } @@ -352,17 +360,25 @@ class ChannelEventLogController: TelegramGenericViewController take(1) |> deliverOnMainQueue, for: mainWindow).start(next: { [weak self] admins in + showModal(with: ChannelEventFilterModalController(context: context, peerId: peerId, admins: admins, state: state, updated: { [weak self] updatedState in + self?.history.set(.single((0, updatedState))) + _ = self?.state.swap(updatedState) + }), for: mainWindow) + + })) + } } - init(_ account:Account, peerId:PeerId) { + init(_ context: AccountContext, peerId:PeerId) { self.peerId = peerId - chatInteraction = ChatInteraction(peerId: peerId, account: account, isLogInteraction: true) - super.init(account) + chatInteraction = ChatInteraction(chatLocation: .peer(peerId), context: context, isLogInteraction: true) + + super.init(context) + } override var enableBack: Bool { @@ -371,7 +387,7 @@ class ChannelEventLogController: TelegramGenericViewController deliverOnMainQueue).start(next: { [weak strongSelf] peer in - if let strongSelf = strongSelf { - strongSelf.navigationController?.push(PeerInfoController(account: strongSelf.account, peer: peer)) - } - })) + strongSelf.navigationController?.push(PeerInfoController(context: context, peerId: peerId)) } } @@ -435,7 +448,7 @@ class ChannelEventLogController: TelegramGenericViewController = Atomic(value: nil) let previousAppearance:Atomic = Atomic(value: nil) let previousSearchState:Atomic = Atomic(value: SearchState(state: .None, request: nil)) - disposable.set((combineLatest(searchState.get() |> map {SearchState(state: .None, request: $0.request)} |> distinctUntilChanged, history.get() |> filter {$0.0 != -1}) |> mapToSignal { values -> Signal in - + disposable.set((combineLatest(searchState.get() |> map {SearchState(state: .None, request: $0.request)} |> distinctUntilChanged, history.get() |> filter {$0.0 != -1}) |> mapToSignal { values -> Signal<(EventLogTableTransition?, Peer?), NoError> in + let state = values.1.1 let searchState = values.0 - return .single(nil) |> then (combineLatest(channelAdminLogEvents(account, peerId: peerId, maxId: values.1.0, minId: -1, limit: 50, query: searchState.request, filter: state.selectedFlags, admins: state.selectedAdmins) |> mapError { _ in} |> deliverOnPrepareQueue, appearanceSignal) |> map { result, appearance in - + return context.account.postbox.transaction { $0.getPeer(peerId) } |> map { (nil, $0) } |> then (combineLatest(channelAdminLogEvents(postbox: context.account.postbox, network: context.account.network, peerId: peerId, maxId: values.1.0, minId: -1, limit: 50, query: searchState.request, filter: state.selectedFlags, admins: state.selectedAdmins) |> `catch` { _ in .complete()} |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { result, appearance -> (EventLogTableTransition, [Peer]) in + let maxId = result.events.min(by: { (lhs, rhs) -> Bool in return lhs.id < rhs.id })?.id ?? -1 + + let items = eventLogItems(result, initialSize: initialSize.modify({$0}), chatInteraction: chatInteraction) let _previousState = previousState.swap(state) let _previousAppearance = previousAppearance.swap(appearance) let _previousSearchState = previousSearchState.swap(searchState) - return EventLogTableTransition(result: items, addition: _previousState == state && _previousSearchState == searchState && _previousAppearance == appearance, state: state, maxId: maxId, eventLog: result) - - } |> map {Optional($0)}) - - } - |> deliverOnMainQueue).start(next: { [weak self] transition in - if let tableView = self?.genericView.tableView { - if let transition = transition, let peer = transition.eventLog.peers[transition.eventLog.peerId] { - if !transition.addition { - tableView.removeAll() + return (EventLogTableTransition(result: items, addition: _previousState == state && _previousSearchState == searchState && _previousAppearance == appearance, state: state, maxId: maxId, eventLog: result), result.peers.map {$0.value}) + + } |> mapToSignal { transition, peers in + return context.account.postbox.transaction { transaction in + updatePeers(transaction: transaction, peers: peers, update: { (previous, updated) -> Peer? in + return updated + }) + return (Optional(transition), transaction.getPeer(peerId)) + } + }) + } + |> deliverOnMainQueue).start(next: { [weak self] transition, peer in + if let tableView = self?.genericView.tableView { + if let transition = transition, let peer = peer { + if !transition.addition { + tableView.removeAll() + _ = tableView.addItem(item: GeneralRowItem(initialSize.modify{$0}, height: 20, stableId: arc4random())) + } + tableView.insert(items: transition.result, at: tableView.count) + self?.genericView.updateState(tableView.isEmpty ? (transition.state.isEmpty && previousSearchState.modify({$0}).request.isEmpty ? .empty(peer.isChannel ? tr(L10n.channelEventLogEmptyText) : tr(L10n.groupEventLogEmptyText)) : .empty(tr(L10n.channelEventLogEmptySearch))) : .history) + } else { + self?.genericView.updateState(.loading) + self?.genericView.tableView.removeAll() _ = tableView.addItem(item: GeneralRowItem(initialSize.modify{$0}, height: 20, stableId: arc4random())) } - tableView.insert(items: transition.result, at: tableView.count) - self?.genericView.updateState(tableView.isEmpty ? (transition.state.isEmpty && previousSearchState.modify({$0}).request.isEmpty ? .empty(peer.isChannel ? tr(.channelEventLogEmptyText) : tr(.groupEventLogEmptyText)) : .empty(tr(.channelEventLogEmptySearch))) : .history) - } else { - self?.genericView.updateState(.loading) - self?.genericView.tableView.removeAll() - _ = tableView.addItem(item: GeneralRowItem(initialSize.modify{$0}, height: 20, stableId: arc4random())) + + tableView.resetScrollNotifies() + _ = currentMaxId.swap(transition?.maxId ?? -1) } - - tableView.resetScrollNotifies() - _ = currentMaxId.swap(transition?.maxId ?? -1) - } - })) + })) - genericView.tableView.setScrollHandler { [weak self] scroll in - if let strongSelf = self { - switch scroll.direction { - case .bottom: - strongSelf.history.set(strongSelf.history.get() |> take(1) |> map { (_, state) in - return (currentMaxId.modify({$0}), state) - }) - default: - break - } - } - } +// genericView.tableView.setScrollHandler { [weak self] scroll in +// if let strongSelf = self { +// switch scroll.direction { +// case .bottom: +// let signal = strongSelf.history.get() |> take(1) |> map { (_, state) in +// (currentMaxId.with { $0 }, state) +// } +// _ = signal.start(next: { [weak strongSelf] data in +// strongSelf?.history.set(.sing) +// }) +// +// default: +// break +// } +// } +// } readyOnce() history.set(.single((0, ChannelEventFilterState()))) @@ -511,3 +535,4 @@ class ChannelEventLogController: TelegramGenericViewController Bool { + let success = super.makeSize(width, oldWidth: oldWidth) textLayout.measure(width: width - (defaultContentInset.left + defaultContentInset.right)) contentMessageItem?.measure(width - (defaultContentInset.left + defaultContentInset.right)) - return super.makeSize(width, oldWidth: oldWidth) + return success } } @@ -559,7 +670,7 @@ private class ServiceEventLogRowView : TableRowView { if messageContent == nil { messageContent = ServiceEventLogMessageContainerView(frame: NSZeroRect) } - messageContent?.update(with: content, account: item.chatInteraction.account) + messageContent?.update(with: content, account: item.chatInteraction.context.account) messageContent?.setFrameSize(frame.width - (defaultContentInset.left + defaultContentInset.right), content.height) addSubview(messageContent!) } else { @@ -573,7 +684,8 @@ private class ServiceEventLogRowView : TableRowView { imageView?.setFrameSize(NSMakeSize(70, 70)) self.addSubview(imageView!) } - imageView?.setSignal(account: item.chatInteraction.account, signal: chatMessagePhoto(account: item.chatInteraction.account, photo: image, toRepresentationSize:NSMakeSize(100,100), scale: backingScaleFactor)) + + imageView?.setSignal(chatMessagePhoto(account: item.chatInteraction.context.account, imageReference: ImageMediaReference.standalone(media: image), toRepresentationSize:NSMakeSize(100,100), scale: backingScaleFactor)) } else { imageView?.removeFromSuperview() imageView = nil @@ -601,9 +713,9 @@ class ChannelEventLogEditedPanelItem : TableRowItem { init(_ initialSize: NSSize, previous:Message, item:ChatRowItem) { self.previous = previous self.associatedItem = item - let header = TextViewLayout(.initialize(string: tr(.channelEventLogOriginalMessage), color: theme.colors.blueUI, font: .medium(.text)), maximumNumberOfLines: 1) + let header = TextViewLayout(.initialize(string: tr(L10n.channelEventLogOriginalMessage), color: theme.colors.accent, font: .medium(.text)), maximumNumberOfLines: 1) - let text = TextViewLayout(.initialize(string: previous.text.isEmpty ? tr(.channelEventLogEmpty) : previous.text, color: theme.colors.text, font: .italic(.text))) + let text = TextViewLayout(.initialize(string: previous.text.isEmpty ? tr(L10n.channelEventLogEmpty) : previous.text, color: theme.colors.text, font: .italic(.text))) panel = ServiceEventLogMessagePanel(header: header, content: text) super.init(initialSize) @@ -614,14 +726,15 @@ class ChannelEventLogEditedPanelItem : TableRowItem { } override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) _ = associatedItem?.makeSize(width, oldWidth: oldWidth) if let item = associatedItem { - panel.content.measure(width: item.blockSize.width - 8) - panel.header.measure(width: item.blockSize.width - 8) + panel.content.measure(width: item.blockWidth - 8) + panel.header.measure(width: item.blockWidth - 8) } - return super.makeSize(width, oldWidth: oldWidth) + return success } override var height: CGFloat { @@ -650,7 +763,7 @@ class ChannelEventLogEditedPanelView : TableRowView { super.set(item: item) if let item = item as? ChannelEventLogEditedPanelItem, let associatedItem = item.associatedItem { panel.update(with: item.panel) - panel.setFrameSize(associatedItem.blockSize.width, item.panel.height) + panel.setFrameSize(associatedItem.blockWidth, item.panel.height) panel.setFrameOrigin(associatedItem.contentOffset.x, 0) } } diff --git a/Telegram-Mac/ChannelInfoEntries.swift b/Telegram-Mac/ChannelInfoEntries.swift index e7cb9e2b4a..9436eb9c69 100644 --- a/Telegram-Mac/ChannelInfoEntries.swift +++ b/Telegram-Mac/ChannelInfoEntries.swift @@ -7,10 +7,12 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit + struct ChannelInfoEditingState: Equatable { let editingName: String? @@ -134,9 +136,9 @@ class ChannelInfoArguments : PeerInfoArguments { } } - override func updateEditable(_ editable: Bool, peerView: PeerView) { + override func updateEditable(_ editable:Bool, peerView:PeerView, controller: PeerInfoController) -> Bool { - let account = self.account + let context = self.context let peerId = self.peerId let updateState:((ChannelInfoState)->ChannelInfoState)->Void = { [weak self] f in self?.updateState(f) @@ -152,34 +154,40 @@ class ChannelInfoArguments : PeerInfoArguments { var updateValues: (title: String?, description: String?) = (nil, nil) updateState { state in updateValues = valuesRequiringUpdate(state: state, view: peerView) + return state + } + + if let titleValue = updateValues.title, titleValue.isEmpty { + controller.genericView.item(stableId: IntPeerInfoEntryStableId(value: 1).hashValue)?.view?.shakeView() + return false + } + + updateState { state in if updateValues.0 != nil || updateValues.1 != nil { return state.withUpdatedSavingData(true) } else { return state.withUpdatedEditingState(nil) } } - - - - let updateTitle: Signal + let updateTitle: Signal if let titleValue = updateValues.title { - updateTitle = updatePeerTitle(account: account, peerId: peerId, title: titleValue) - |> mapError { _ in return Void() } + updateTitle = updatePeerTitle(account: context.account, peerId: peerId, title: titleValue) + |> `catch` { _ in return .complete() } } else { updateTitle = .complete() } - let updateDescription: Signal + let updateDescription: Signal if let descriptionValue = updateValues.description { - updateDescription = updatePeerDescription(account: account, peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) - |> mapError { _ in return Void() } + updateDescription = updatePeerDescription(account: context.account, peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) + |> `catch` { _ in return .complete() } } else { updateDescription = .complete() } let signal = combineLatest(updateTitle, updateDescription) - updatePeerNameDisposable.set(showModalProgress(signal: (signal |> deliverOnMainQueue), for: mainWindow).start(error: { _ in + updatePeerNameDisposable.set(showModalProgress(signal: (signal |> deliverOnMainQueue), for: context.window).start(error: { _ in updateState { state in return state.withUpdatedSavingData(false) } @@ -189,28 +197,143 @@ class ChannelInfoArguments : PeerInfoArguments { } })) } - - + return true } func visibilitySetup() { - pushViewController(ChannelVisibilityController(account: account, peerId: peerId)) + let setup = ChannelVisibilityController(context, peerId: peerId) + _ = (setup.onComplete.get() |> deliverOnMainQueue).start(next: { [weak self] _ in + self?.pullNavigation()?.back() + }) + pushViewController(setup) + } + + func setupDiscussion() { + _ = (self.context.account.postbox.loadedPeerWithId(self.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let `self` = self { + self.pushViewController(ChannelDiscussionSetupController(context: self.context, peer: peer)) + } + }) } func toggleSignatures( _ enabled: Bool) -> Void { - toggleSignaturesDisposable.set(toggleShouldChannelMessagesSignatures(account: account, peerId: peerId, enabled: enabled).start()) + toggleSignaturesDisposable.set(toggleShouldChannelMessagesSignatures(account: context.account, peerId: peerId, enabled: enabled).start()) } func members() -> Void { - pushViewController(ChannelMembersViewController(account: account, peerId: peerId)) + pushViewController(ChannelMembersViewController(context, peerId: peerId)) } func admins() -> Void { - pushViewController(ChannelAdminsViewController(account: account, peerId: peerId)) + pushViewController(ChannelAdminsViewController(context, peerId: peerId)) } func blocked() -> Void { - pushViewController(ChannelBlacklistViewController(account: account, peerId: peerId)) + pushViewController(ChannelBlacklistViewController(context, peerId: peerId)) + } + + func updateChannelPhoto(_ custom: NSImage?) { + + let context = self.context + + let invoke:(NSImage) -> Void = { image in + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: mainWindow, animationType: .scaleCenter) + _ = controller.result.start(next: { [weak self] url, _ in + self?.updatePhoto(url.path) + }) + controller.onClose = { + removeFile(at: path) + } + }) + } + if let image = custom { + invoke(image) + } else { + filePanel(with: photoExts + videoExts, allowMultiple: false, canChooseDirectories: false, for: context.window, completion: { [weak self] paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + invoke(image) + } else if let path = paths?.first { + selectVideoAvatar(context: context, path: path, localize: L10n.videoAvatarChooseDescChannel, signal: { [weak self] signal in + self?.updateVideo(signal) + }) + } + }) + } + } + + func updateVideo(_ signal:Signal) -> Void { + + let updateState:((ChannelInfoState)->ChannelInfoState)->Void = { [weak self] f in + self?.updateState(f) + } + + let cancel = { [weak self] in + self?.updatePhotoDisposable.set(nil) + updateState { state -> ChannelInfoState in + return state.withoutUpdatingPhotoState() + } + } + + let context = self.context + let peerId = self.peerId + + + let updateSignal: Signal = signal + |> mapError { _ in return UploadPeerPhotoError.generic } + |> mapToSignal { state in + switch state { + case .error: + return .fail(.generic) + case let .start(path): + updateState { (state) -> ChannelInfoState in + return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in + return PeerInfoUpdatingPhotoState(progress: 0, image: NSImage(contentsOfFile: path)?._cgImage, cancel: cancel) + } + } + return .next(.progress(0)) + case let .progress(value): + return .next(.progress(value * 0.2)) + case let .complete(thumb, video, keyFrame): + let (thumbResource, videoResource) = (LocalFileReferenceMediaResource(localFilePath: thumb, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true), + LocalFileReferenceMediaResource(localFilePath: video, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true)) + + return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: thumbResource), video: uploadedPeerVideo(postbox: context.account.postbox, network: context.account.network, messageMediaPreuploadManager: nil, resource: videoResource) |> map(Optional.init), videoStartTimestamp: keyFrame, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> map { result in + switch result { + case let .progress(current): + return .progress(0.2 + (current * 0.8)) + default: + return result + } + } + } + } + + updatePhotoDisposable.set((updateSignal |> deliverOnMainQueue).start(next: { status in + updateState { state -> ChannelInfoState in + switch status { + case .complete: + return state.withoutUpdatingPhotoState() + case let .progress(progress): + return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in + return previous?.withUpdatedProgress(progress) + } + } + } + }, error: { error in + updateState { (state) -> ChannelInfoState in + return state.withoutUpdatingPhotoState() + } + }, completed: { + updateState { (state) -> ChannelInfoState in + return state.withoutUpdatingPhotoState() + } + })) + + } func updatePhoto(_ path:String) -> Void { @@ -220,38 +343,29 @@ class ChannelInfoArguments : PeerInfoArguments { } let cancel = { [weak self] in - self?.updatePhotoDisposable.dispose() + self?.updatePhotoDisposable.set(nil) updateState { state -> ChannelInfoState in return state.withoutUpdatingPhotoState() } } - let account = self.account + let context = self.context let peerId = self.peerId - /* - filethumb(with: URL(fileURLWithPath: path), account: account, scale: System.backingScale) |> mapToSignal { res -> Signal in - guard let image = NSImage(contentsOf: URL(fileURLWithPath: path)) else { - return .complete() - } - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: image.size, boundingSize: NSMakeSize(640, 640), intrinsicInsets: NSEdgeInsets()) - if let image = res(arguments)?.generateImage() { - return putToTemp(image: NSImage(cgImage: image, size: image.backingSize)) - } - return .complete() - } - */ - let updateSignal = Signal.single(path) |> map { path -> TelegramMediaResource in + + let updateSignal = Signal.single(path) |> map { path -> TelegramMediaResource in return LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) } |> beforeNext { resource in updateState { (state) -> ChannelInfoState in return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in - return PeerInfoUpdatingPhotoState(progress: 0, cancel: cancel) + return PeerInfoUpdatingPhotoState(progress: 0, image: NSImage(contentsOfFile: path)?.cgImage(forProposedRect: nil, context: nil, hints: nil), cancel: cancel) } } } |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { resource -> Signal in - return updatePeerPhoto(account: account, peerId: peerId, resource: resource) + return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) } @@ -279,16 +393,38 @@ class ChannelInfoArguments : PeerInfoArguments { } + func stats(_ datacenterId: Int32) { + self.pushViewController(ChannelStatsViewController(context, peerId: peerId, datacenterId: datacenterId)) + } + func share() { + let peer = context.account.postbox.peerView(id: peerId) |> take(1) |> deliverOnMainQueue + let context = self.context + + _ = peer.start(next: { peerView in + if let peer = peerViewMainPeer(peerView) { + var link: String = "https://t.me/\(peer.id.id)" + if let address = peer.addressName, !address.isEmpty { + link = "https://t.me/\(address)" + } else if let cachedData = peerView.cachedData as? CachedChannelData, let invitation = cachedData.exportedInvitation { + link = invitation.link + } + showModal(with: ShareModalController(ShareLinkObject(context, link: link)), for: context.window) + } + + }) + + } + func report() -> Void { - let account = self.account + let context = self.context let peerId = self.peerId - let report = reportReasonSelector() |> mapToSignal { reason -> Signal in - return showModalProgress(signal: reportPeer(account: account, peerId: peerId, reason: reason), for: mainWindow) + let report = reportReasonSelector(context: context) |> mapToSignal { reason -> Signal in + return showModalProgress(signal: reportPeer(account: context.account, peerId: peerId, reason: reason), for: context.window) } |> deliverOnMainQueue reportPeerDisposable.set(report.start(next: { [weak self] in - self?.pullNavigation()?.controller.show(toaster: ControllerToaster(text: tr(.peerInfoChannelReported))) + self?.pullNavigation()?.controller.show(toaster: ControllerToaster(text: L10n.peerInfoChannelReported)) })) } @@ -310,6 +446,7 @@ class ChannelInfoArguments : PeerInfoArguments { } } } + deinit { reportPeerDisposable.dispose() @@ -320,25 +457,50 @@ class ChannelInfoArguments : PeerInfoArguments { } enum ChannelInfoEntry: PeerInfoEntry { - case info(sectionId:Int, peerView: PeerView, editable:Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?) - case about(sectionId:Int, text: String) - case userName(sectionId:Int, value: String) - case setPhoto(sectionId:Int) - case sharedMedia(sectionId:Int) - case notifications(sectionId:Int, settings: PeerNotificationSettings?) - case admins(sectionId:Int, count:Int32?) - case blocked(sectionId:Int, count:Int32?) - case members(sectionId:Int, count:Int32?) - case link(sectionId:Int, addressName:String) - case aboutInput(sectionId:Int, description:String) - case aboutDesc(sectionId:Int) - case signMessages(sectionId:Int, sign:Bool) - case signDesc(sectionId:Int) - case report(sectionId:Int) - case leave(sectionId:Int, isCreator: Bool) + case info(sectionId: ChannelInfoSection, peerView: PeerView, editable:Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?, viewType: GeneralViewType) + case scam(sectionId: ChannelInfoSection, text: String, viewType: GeneralViewType) + case about(sectionId: ChannelInfoSection, text: String, viewType: GeneralViewType) + case userName(sectionId: ChannelInfoSection, value: String, viewType: GeneralViewType) + case setTitle(sectionId: ChannelInfoSection, text: String, viewType: GeneralViewType) + case admins(sectionId: ChannelInfoSection, count:Int32?, viewType: GeneralViewType) + case blocked(sectionId: ChannelInfoSection, count:Int32?, viewType: GeneralViewType) + case members(sectionId: ChannelInfoSection, count:Int32?, viewType: GeneralViewType) + case link(sectionId: ChannelInfoSection, addressName:String, viewType: GeneralViewType) + case discussion(sectionId: ChannelInfoSection, group: Peer?, participantsCount: Int32?, viewType: GeneralViewType) + case discussionDesc(sectionId: ChannelInfoSection, viewType: GeneralViewType) + case aboutInput(sectionId: ChannelInfoSection, description:String, viewType: GeneralViewType) + case aboutDesc(sectionId: ChannelInfoSection, viewType: GeneralViewType) + case signMessages(sectionId: ChannelInfoSection, sign:Bool, viewType: GeneralViewType) + case signDesc(sectionId: ChannelInfoSection, viewType: GeneralViewType) + case report(sectionId: ChannelInfoSection, viewType: GeneralViewType) + case leave(sectionId: ChannelInfoSection, isCreator: Bool, viewType: GeneralViewType) + + case media(sectionId: ChannelInfoSection, controller: PeerMediaController, isVisible: Bool, viewType: GeneralViewType) case section(Int) - + func withUpdatedViewType(_ viewType: GeneralViewType) -> ChannelInfoEntry { + switch self { + case let .info(sectionId, peerView, editable, updatingPhotoState, _): return .info(sectionId: sectionId, peerView: peerView, editable: editable, updatingPhotoState: updatingPhotoState, viewType: viewType) + case let .scam(sectionId, text, _): return .scam(sectionId: sectionId, text: text, viewType: viewType) + case let .about(sectionId, text, _): return .about(sectionId: sectionId, text: text, viewType: viewType) + case let .userName(sectionId, value, _): return .userName(sectionId: sectionId, value: value, viewType: viewType) + case let .setTitle(sectionId, text, _): return .setTitle(sectionId: sectionId, text: text, viewType: viewType) + case let .admins(sectionId, count, _): return .admins(sectionId: sectionId, count: count, viewType: viewType) + case let .blocked(sectionId, count, _): return .blocked(sectionId: sectionId, count: count, viewType: viewType) + case let .members(sectionId, count, _): return .members(sectionId: sectionId, count: count, viewType: viewType) + case let .link(sectionId, addressName, _): return .link(sectionId: sectionId, addressName: addressName, viewType: viewType) + case let .discussion(sectionId, group, participantsCount, _): return .discussion(sectionId: sectionId, group: group, participantsCount: participantsCount, viewType: viewType) + case let .discussionDesc(sectionId, _): return .discussionDesc(sectionId: sectionId, viewType: viewType) + case let .aboutInput(sectionId, description, _): return .aboutInput(sectionId: sectionId, description: description, viewType: viewType) + case let .aboutDesc(sectionId, _): return .aboutDesc(sectionId: sectionId, viewType: viewType) + case let .signMessages(sectionId, sign, _): return .signMessages(sectionId: sectionId, sign: sign, viewType: viewType) + case let .signDesc(sectionId, _): return .signDesc(sectionId: sectionId, viewType: viewType) + case let .report(sectionId, _): return .report(sectionId: sectionId, viewType: viewType) + case let .leave(sectionId, isCreator, _): return .leave(sectionId: sectionId, isCreator: isCreator, viewType: viewType) + case let .media(sectionId, controller, isVisible, _): return .media(sectionId: sectionId, controller: controller, isVisible: isVisible, viewType: viewType) + case .section: return self + } + } var stableId: PeerInfoEntryStableId { return IntPeerInfoEntryStableId(value: self.stableIndex) @@ -349,29 +511,30 @@ enum ChannelInfoEntry: PeerInfoEntry { return false } switch self { - case let .info(lhsSectionId, lhsPeerView, lhsEditable, lhsUpdatingPhotoState): + case let .info(sectionId, lhsPeerView, editable, updatingPhotoState, viewType): switch entry { - case let .info(rhsSectionId, rhsPeerView, rhsEditable, rhsUpdatingPhotoState): - - if lhsSectionId != rhsSectionId || lhsEditable != rhsEditable { - return false - } - - if lhsUpdatingPhotoState != rhsUpdatingPhotoState { - return false - } + case .info(sectionId, let rhsPeerView, editable, updatingPhotoState, viewType): let lhsPeer = peerViewMainPeer(lhsPeerView) let lhsCachedData = lhsPeerView.cachedData + let lhsNotificationSettings = lhsPeerView.notificationSettings let rhsPeer = peerViewMainPeer(rhsPeerView) let rhsCachedData = rhsPeerView.cachedData - + let rhsNotificationSettings = rhsPeerView.notificationSettings if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { if !lhsPeer.isEqual(rhsPeer) { return false } - } else if (lhsPeer == nil) != (rhsPeer != nil) { + } else if (lhsPeer != nil) != (rhsPeer != nil) { + return false + } + + if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings { + if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) { + return false + } + } else if (lhsNotificationSettings == nil) != (rhsNotificationSettings == nil) { return false } if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData { @@ -385,108 +548,109 @@ enum ChannelInfoEntry: PeerInfoEntry { default: return false } - case let .about(sectionId, text): + case let .scam(sectionId, text, viewType): switch entry { - case .about(sectionId, text): + case .scam(sectionId, text, viewType): return true default: return false } - case let .userName(sectionId, value): + case let .about(sectionId, text, viewType): switch entry { - case .userName(sectionId, value): + case .about(sectionId, text, viewType): return true default: return false } - case let .setPhoto(sectionId): + case let .userName(sectionId, value, viewType): switch entry { - case .setPhoto(sectionId): + case .userName(sectionId, value, viewType): return true default: return false } - case let .sharedMedia(sectionId): + case let .setTitle(sectionId, text, viewType): switch entry { - case .sharedMedia(sectionId): + case .setTitle(sectionId, text, viewType): return true default: return false } - case let .notifications(lhsSectionId, lhsSettings): + case let .report(sectionId, viewType): switch entry { - case let .notifications(rhsSectionId, rhsSettings): - - if lhsSectionId != rhsSectionId { - return false - } - if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings { - return lhsSettings.isEqual(to: rhsSettings) - } else if (lhsSettings != nil) != (rhsSettings != nil) { - return false - } + case .report(sectionId, viewType): return true default: return false } - case .report: - switch entry { - case .report: + case let .admins(sectionId, count, viewType): + if case .admins(sectionId, count, viewType) = entry { return true - default: + } else { + return false + } + case let .blocked(sectionId, count, viewType): + if case .blocked(sectionId, count, viewType) = entry { + return true + } else { return false } - case let .admins(lhsSectionId, lhsCount): - if case let .admins(rhsSectionId, rhsCount) = entry { - return lhsSectionId == rhsSectionId && lhsCount == rhsCount + case let .members(sectionId, count, viewType): + if case .members(sectionId, count, viewType) = entry { + return true } else { return false } - case let .blocked(lhsSectionId, lhsCount): - if case let .blocked(rhsSectionId, rhsCount) = entry { - return lhsSectionId == rhsSectionId && lhsCount == rhsCount + case let .link(sectionId, addressName, viewType): + if case .link(sectionId, addressName, viewType) = entry { + return true } else { return false } - case let .members(lhsSectionId, lhsCount): - if case let .members(rhsSectionId, rhsCount) = entry { - return lhsSectionId == rhsSectionId && lhsCount == rhsCount + case let .discussion(sectionId, lhsGroup, participantsCount, viewType): + if case .discussion(sectionId, let rhsGroup, participantsCount, viewType) = entry { + if let lhsGroup = lhsGroup, let rhsGroup = rhsGroup { + return lhsGroup.isEqual(rhsGroup) + } else if (lhsGroup != nil) != (rhsGroup != nil) { + return false + } + return true } else { return false } - case let .link(sectionId, addressName): - if case .link(sectionId, addressName) = entry { + case let .discussionDesc(sectionId, viewType): + if case .discussionDesc(sectionId, viewType) = entry { return true } else { return false } - case let .aboutInput(sectionId, _): - if case .aboutInput(sectionId, _) = entry { + case let .aboutInput(sectionId, text, viewType): + if case .aboutInput(sectionId, text, viewType) = entry { return true } else { return false } - case let .aboutDesc(sectionId): - if case .aboutDesc(sectionId) = entry { + case let .aboutDesc(sectionId, viewType): + if case .aboutDesc(sectionId, viewType) = entry { return true } else { return false } - case let .signMessages(sectionId, sign): - if case .signMessages(sectionId, sign) = entry { + case let .signMessages(sectionId, sign, viewType): + if case .signMessages(sectionId, sign, viewType) = entry { return true } else { return false } - case let .signDesc(sectionId): - if case .signDesc(sectionId) = entry { + case let .signDesc(sectionId, viewType): + if case .signDesc(sectionId, viewType) = entry { return true } else { return false } - case .leave: + case let .leave(sectionId, isCreator, viewType): switch entry { - case .leave: + case .leave(sectionId, isCreator, viewType): return true default: return false @@ -498,6 +662,13 @@ enum ChannelInfoEntry: PeerInfoEntry { default: return false } + case let .media(sectionId, _, isVisible, viewType): + switch entry { + case .media(sectionId, _, isVisible, viewType): + return true + default: + return false + } } } @@ -505,75 +676,126 @@ enum ChannelInfoEntry: PeerInfoEntry { switch self { case .info: return 0 - case .setPhoto: + case .setTitle: return 1 - case .about: + case .scam: return 2 - case .userName: + case .about: return 3 - case .sharedMedia: + case .userName: return 4 - case .notifications: - return 5 case .admins: - return 6 - case .blocked: - return 7 - case .members: return 8 - case .link: + case .members: return 9 - case .aboutInput: + case .blocked: return 10 - case .aboutDesc: + case .link: return 11 - case .signMessages: + case .discussion: return 12 - case .signDesc: + case .discussionDesc: return 13 - case .report: + case .aboutInput: return 14 - case .leave: + case .aboutDesc: return 15 + case .signMessages: + return 16 + case .signDesc: + return 17 + case .report: + return 18 + case .leave: + return 19 + case .media: + return 20 case let .section(id): return (id + 1) * 1000 - id } } + fileprivate var sectionId: Int { + switch self { + case let .info(sectionId, _, _, _, _): + return sectionId.rawValue + case let .setTitle(sectionId, _, _): + return sectionId.rawValue + case let .scam(sectionId, _, _): + return sectionId.rawValue + case let .about(sectionId, _, _): + return sectionId.rawValue + case let .userName(sectionId, _, _): + return sectionId.rawValue + case let .admins(sectionId, _, _): + return sectionId.rawValue + case let .blocked(sectionId, _, _): + return sectionId.rawValue + case let .members(sectionId, _, _): + return sectionId.rawValue + case let .link(sectionId, _, _): + return sectionId.rawValue + case let .discussion(sectionId, _, _, _): + return sectionId.rawValue + case let .discussionDesc(sectionId, _): + return sectionId.rawValue + case let .aboutInput(sectionId, _, _): + return sectionId.rawValue + case let .aboutDesc(sectionId, _): + return sectionId.rawValue + case let .signMessages(sectionId, _, _): + return sectionId.rawValue + case let .signDesc(sectionId, _): + return sectionId.rawValue + case let .report(sectionId, _): + return sectionId.rawValue + case let .leave(sectionId, _, _): + return sectionId.rawValue + case let .media(sectionId, _, _, _): + return sectionId.rawValue + case let .section(sectionId): + return sectionId + } + } + private var sortIndex: Int { switch self { - case let .info(sectionId, _, _, _): - return (sectionId * 1000) + stableIndex - case let .setPhoto(sectionId): - return (sectionId * 1000) + stableIndex - case let .about(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .userName(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .sharedMedia(sectionId): - return (sectionId * 1000) + stableIndex - case let .notifications(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .admins(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .blocked(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .members(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .link(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .aboutInput(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .aboutDesc(sectionId): - return (sectionId * 1000) + stableIndex - case let .signMessages(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .signDesc(sectionId): - return (sectionId * 1000) + stableIndex - case let .report(sectionId): - return (sectionId * 1000) + stableIndex - case let .leave(sectionId, _): - return (sectionId * 1000) + stableIndex + case let .info(sectionId, _, _, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .setTitle(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .scam(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .about(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .userName(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .admins(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .blocked(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .members(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .link(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .discussion(sectionId, _, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .discussionDesc(sectionId, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .aboutInput(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .aboutDesc(sectionId, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .signMessages(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .signDesc(sectionId, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .report(sectionId, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .leave(sectionId, _, _): + return (sectionId.rawValue * 1000) + stableIndex + case let .media(sectionId, _, _, _): + return (sectionId.rawValue * 1000) + stableIndex case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } @@ -583,153 +805,135 @@ enum ChannelInfoEntry: PeerInfoEntry { guard let entry = entry as? ChannelInfoEntry else { return false } - return self.sortIndex > entry.sortIndex + return self.sortIndex < entry.sortIndex } func item(initialSize:NSSize, arguments:PeerInfoArguments) -> TableRowItem { let arguments = arguments as! ChannelInfoArguments - let state = arguments.state as! ChannelInfoState switch self { - case let .info(_, peerView, editable, updatingPhotoState): - return PeerInfoHeaderItem(initialSize, stableId: stableId.hashValue, account:arguments.account, peerView:peerView, editable: editable, updatingPhotoState: updatingPhotoState, firstNameEditableText: state.editingState?.editingName, textChangeHandler: { name, _ in - arguments.updateEditingName(name) - }) - case let .about(_, text): - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:tr(.peerInfoInfo), text:text, account: arguments.account, detectLinks:true, openInfo: { peerId, toChat, _, _ in + case let .info(_, peerView, editable, updatingPhotoState, viewType): + return PeerInfoHeadItem(initialSize, stableId: stableId.hashValue, context: arguments.context, arguments: arguments, peerView:peerView, viewType: viewType, editing: editable, updatingPhotoState: updatingPhotoState, updatePhoto: arguments.updateChannelPhoto) + case let .scam(_, text, viewType): + return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label: L10n.peerInfoScam, labelColor: theme.colors.redUI, text: text, context: arguments.context, viewType: viewType, detectLinks:false) + case let .about(_, text, viewType): + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label: L10n.peerInfoInfo, text:text, context: arguments.context, viewType: viewType, detectLinks:true, openInfo: { peerId, toChat, postId, _ in if toChat { - arguments.peerChat(peerId) + arguments.peerChat(peerId, postId: postId) } else { arguments.peerInfo(peerId) } - }, hashtag: arguments.account.context.globalSearch) - case let .userName(_, value): - let link = "https://t.me/\(value)" - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:tr(.peerInfoSharelink), text: link, account: arguments.account, isTextSelectable:false, callback:{ - showModal(with: ShareModalController(ShareLinkObject(arguments.account, link: link)), for: mainWindow) - }) - case .sharedMedia: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSharedMedia), type: .none, action: { () in - arguments.sharedMedia() - }) - case let .notifications(_, settings): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoNotifications), type: .switchable(stateback: { () -> Bool in - - if let settings = settings as? TelegramPeerNotificationSettings, case .muted = settings.muteState { - return false - } else { - return true - } - - }), action: { - arguments.toggleNotifications() - }) - case .report: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoReport), type: .none, action: { () in + }, hashtag: arguments.context.sharedContext.bindings.globalSearch) + case let .userName(_, value, viewType): + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label: L10n.peerInfoSharelink, text: value, context: arguments.context, viewType: viewType, isTextSelectable:false, callback: arguments.share, selectFullWord: true) + case let .report(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoReport, type: .none, viewType: viewType, action: { () in arguments.report() }) - case let .members(_, count: count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoMembers), type: .context(stateback: { () -> String in - if let count = count { - return "\(count)" + case let .members(_, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSubscribers, type: .nextContext(count != nil && count! > 0 ? "\(count!)" : ""), viewType: viewType, action: arguments.members) + case let .admins(_, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoAdministrators, type: .nextContext(count != nil && count! > 0 ? "\(count!)" : ""), viewType: viewType, action: arguments.admins) + case let .blocked(_, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoRemovedUsers, type: .nextContext(count != nil && count! > 0 ? "\(count!)" : ""), viewType: viewType, action: arguments.blocked) + case let .link(_, addressName: addressName, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoChannelType, type: .context(addressName.isEmpty ? L10n.channelPrivate : L10n.channelPublic), viewType: viewType, action: arguments.visibilitySetup) + case let .discussion(_, group, _, viewType): + let title: String + if let group = group { + if let address = group.addressName { + title = "@\(address)" } else { - return "" + title = group.displayTitle } - }), action: { () in - arguments.members() - }) - case let .admins(_, count: count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoAdmins), type: .context(stateback: { () -> String in - if let count = count { - return "\(count)" - } else { - return "" - } - }), action: { () in - arguments.admins() - }) - case let .blocked(_, count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoBlackList), type: .context(stateback: { () -> String in - if let count = count { - return "\(count)" - } else { - return "" - } - }), action: { () in - arguments.blocked() - }) - case let .link(_, addressName: addressName): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoChannelType), type: .context(stateback: { () -> String in - return addressName.isEmpty ? tr(.channelPrivate) : tr(.channelPublic) - }), action: { () in - arguments.visibilitySetup() - }) - case .setPhoto: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSetChannelPhoto), nameStyle: blueActionButton, type: .none, action: { - pickImage(for: mainWindow, completion: { image in - if let image = image { - _ = (putToTemp(image: image) |> deliverOnMainQueue).start(next: { path in - arguments.updatePhoto(path) - }) - } - }) - - }) - case let .aboutInput(_, text): - return GeneralInputRowItem(initialSize, stableId: stableId.hashValue, placeholder: tr(.peerInfoAboutPlaceholder), text: text, limit: 255, insets: NSEdgeInsets(left:25,right:25,top:8,bottom:3), textChangeHandler: { updatedText in - arguments.updateEditingDescriptionText(updatedText) - }) - case .aboutDesc: - return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: tr(.peerInfoSetAboutDescription)) - case let .signMessages(_, sign): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSignMessages), type: .switchable(stateback: { () -> Bool in - return sign - }), action: { + } else { + title = L10n.peerInfoDiscussionAdd + } + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoDiscussion, type: .nextContext(title), viewType: viewType, action: arguments.setupDiscussion) + case let .discussionDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: L10n.peerInfoDiscussionDesc, viewType: viewType) + case let .setTitle(_, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoChannelTitlePleceholder, filter: { $0 }, updated: arguments.updateEditingName, limit: 255) + case let .aboutInput(_, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoAboutPlaceholder, filter: { $0 }, updated: arguments.updateEditingDescriptionText, limit: 255) + case let .aboutDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: L10n.peerInfoSetAboutDescription, viewType: viewType) + case let .signMessages(_, sign, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSignMessages, type: .switchable(sign), viewType: viewType, action: { arguments.toggleSignatures(!sign) }) - case .signDesc: - return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: tr(.peerInfoSignMessagesDesc)) - case let .leave(_, isCreator): - - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: isCreator ? tr(.peerInfoDeleteChannel) : tr(.peerInfoLeaveChannel), nameStyle:redActionButton, type: .none, action: { () in - arguments.delete() - }) + case let .signDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: L10n.peerInfoSignMessagesDesc, viewType: viewType) + case let .leave(_, isCreator, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: isCreator ? L10n.peerInfoDeleteChannel : L10n.peerInfoLeaveChannel, nameStyle:redActionButton, type: .none, viewType: viewType, action: arguments.delete) + case let .media(_, controller, isVisible, viewType): + return PeerMediaBlockRowItem(initialSize, stableId: stableId.hashValue, controller: controller, isVisible: isVisible, viewType: viewType) case .section(_): - return GeneralRowItem(initialSize, height:20, stableId: stableId.hashValue) + return GeneralRowItem(initialSize, height:30, stableId: stableId.hashValue, viewType: .separator) } } } -func channelInfoEntries(view: PeerView, arguments:PeerInfoArguments) -> [PeerInfoEntry] { +enum ChannelInfoSection : Int { + case header = 1 + case desc = 2 + case info = 3 + case type = 4 + case sign = 5 + case manage = 6 + case addition = 7 + case destruct = 8 + case media = 9 +} + +func channelInfoEntries(view: PeerView, arguments:PeerInfoArguments, mediaTabsData: PeerMediaTabsData) -> [PeerInfoEntry] { let arguments = arguments as! ChannelInfoArguments - let state = arguments.state as! ChannelInfoState + var state:ChannelInfoState { + return arguments.state as! ChannelInfoState + } + var entries: [ChannelInfoEntry] = [] - var entries: [PeerInfoEntry] = [] - var sectionId:Int = 1 + var infoBlock:[ChannelInfoEntry] = [] - entries.append(ChannelInfoEntry.info(sectionId: sectionId, peerView: view, editable: state.editingState != nil, updatingPhotoState: state.updatingPhotoState)) + func applyBlock(_ block:[ChannelInfoEntry]) { + var block = block + for (i, item) in block.enumerated() { + block[i] = item.withUpdatedViewType(bestGeneralViewType(block, for: i)) + } + entries.append(contentsOf: block) + } + + infoBlock.append(.info(sectionId: .header, peerView: view, editable: state.editingState != nil, updatingPhotoState: state.updatingPhotoState, viewType: .singleItem)) + if let channel = peerViewMainPeer(view) as? TelegramChannel { if let editingState = state.editingState { - if channel.hasAdminRights(.canChangeInfo) { - entries.append(ChannelInfoEntry.setPhoto(sectionId:sectionId)) - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 - } - if channel.flags.contains(.isCreator) { - entries.append(ChannelInfoEntry.link(sectionId:sectionId, addressName: channel.username ?? "")) + if channel.hasPermission(.changeInfo) { + infoBlock.append(.setTitle(sectionId: .header, text: editingState.editingName ?? "", viewType: .singleItem)) } - if channel.hasAdminRights(.canChangeInfo) { - entries.append(ChannelInfoEntry.aboutInput(sectionId:sectionId, description: editingState.editingDescriptionText)) - entries.append(ChannelInfoEntry.aboutDesc(sectionId: sectionId)) - - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 + if channel.hasPermission(.changeInfo) && !channel.isScam { + infoBlock.append(.aboutInput(sectionId: .header, description: editingState.editingDescriptionText, viewType: .singleItem)) + } + applyBlock(infoBlock) + entries.append(.aboutDesc(sectionId: .header, viewType: .textBottomItem)) + + if channel.adminRights?.flags.contains(.canChangeInfo) == true || channel.flags.contains(.isCreator) { + if channel.flags.contains(.isCreator) { + entries.append(.link(sectionId: .type, addressName: channel.username ?? "", viewType: .firstItem)) + } + let group: Peer? + if let cachedData = view.cachedData as? CachedChannelData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + group = view.peers[linkedDiscussionPeerId] + } else { + group = nil + } + entries.append(.discussion(sectionId: .type, group: group, participantsCount: nil, viewType: channel.flags.contains(.isCreator) ? .lastItem : .singleItem)) + entries.append(.discussionDesc(sectionId: .type, viewType: .textBottomItem)) } let messagesShouldHaveSignatures:Bool @@ -740,71 +944,81 @@ func channelInfoEntries(view: PeerView, arguments:PeerInfoArguments) -> [PeerInf messagesShouldHaveSignatures = false } - if channel.hasAdminRights(.canChangeInfo) { - entries.append(ChannelInfoEntry.signMessages(sectionId: sectionId, sign: messagesShouldHaveSignatures)) - entries.append(ChannelInfoEntry.signDesc(sectionId: sectionId)) - - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 + if channel.hasPermission(.changeInfo) { + entries.append(.signMessages(sectionId: .sign, sign: messagesShouldHaveSignatures, viewType: .singleItem)) + entries.append(.signDesc(sectionId: .sign, viewType: .textBottomItem)) + } + if channel.flags.contains(.isCreator) { + entries.append(.leave(sectionId: .destruct, isCreator: channel.flags.contains(.isCreator), viewType: .singleItem)) } - - - entries.append(ChannelInfoEntry.leave(sectionId:sectionId, isCreator: channel.flags.contains(.isCreator))) } else { + applyBlock(infoBlock) + + var aboutBlock:[ChannelInfoEntry] = [] + if channel.isScam { + aboutBlock.append(.scam(sectionId: .desc, text: L10n.channelInfoScamWarning, viewType: .singleItem)) + } if let cachedData = view.cachedData as? CachedChannelData { - if let about = cachedData.about, !about.isEmpty { - entries.append(ChannelInfoEntry.about(sectionId:sectionId, text: about)) + if let about = cachedData.about, !about.isEmpty, !channel.isScam { + aboutBlock.append(.about(sectionId: .desc, text: about, viewType: .singleItem)) } } if let username = channel.username, !username.isEmpty { - entries.append(ChannelInfoEntry.userName(sectionId:sectionId, value: username)) + aboutBlock.append(.userName(sectionId: .desc, value: "https://t.me/\(username)", viewType: .singleItem)) + } else if let cachedData = view.cachedData as? CachedChannelData, let invitation = cachedData.exportedInvitation { + aboutBlock.append(.userName(sectionId: .desc, value: invitation.link, viewType: .singleItem)) } - if entries.count > 1 { - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 - } + applyBlock(aboutBlock) - if channel.groupAccess.canManageGroup { + + if channel.flags.contains(.isCreator) || (channel.adminRights != nil && !channel.adminRights!.isEmpty) { var membersCount:Int32? = nil var adminsCount:Int32? = nil var blockedCount:Int32? = nil + if let cachedData = view.cachedData as? CachedChannelData { membersCount = cachedData.participantsSummary.memberCount adminsCount = cachedData.participantsSummary.adminCount blockedCount = cachedData.participantsSummary.kickedCount } - entries.append(ChannelInfoEntry.admins(sectionId: sectionId, count: adminsCount)) - entries.append(ChannelInfoEntry.members(sectionId: sectionId, count: membersCount)) - - if let blockedCount = blockedCount { - entries.append(ChannelInfoEntry.blocked(sectionId: sectionId, count: blockedCount)) - } + entries.append(.admins(sectionId: .manage, count: adminsCount, viewType: .firstItem)) + entries.append(.members(sectionId: .manage, count: membersCount, viewType: .innerItem)) + + entries.append(.blocked(sectionId: .manage, count: blockedCount, viewType: .lastItem)) - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 } - - - - entries.append(ChannelInfoEntry.sharedMedia(sectionId:sectionId)) - entries.append(ChannelInfoEntry.notifications(sectionId:sectionId, settings: view.notificationSettings)) - - entries.append(ChannelInfoEntry.section(sectionId)) - sectionId += 1 - - if !channel.flags.contains(.isCreator) { - entries.append(ChannelInfoEntry.report(sectionId:sectionId)) - if channel.participationStatus == .member { - entries.append(ChannelInfoEntry.leave(sectionId:sectionId, isCreator: false)) - } + } + } + + if mediaTabsData.loaded && !mediaTabsData.collections.isEmpty, let controller = arguments.mediaController() { + entries.append(.media(sectionId: ChannelInfoSection.media, controller: controller, isVisible: state.editingState == nil, viewType: .singleItem)) + } + + var items:[ChannelInfoEntry] = [] + var sectionId:Int = 0 + for entry in entries { + if entry.sectionId != sectionId { + if entry.sectionId == ChannelInfoSection.media.rawValue { + sectionId = entry.sectionId + } else { + items.append(.section(sectionId)) + sectionId = entry.sectionId } - } + items.append(entry) } + sectionId += 1 + items.append(.section(sectionId)) + + + + + entries = items + return entries.sorted(by: { (p1, p2) -> Bool in return p1.isOrderedBefore(p2) }) diff --git a/Telegram-Mac/ChannelIntroViewController.swift b/Telegram-Mac/ChannelIntroViewController.swift index bb4e29ef5c..9550046232 100644 --- a/Telegram-Mac/ChannelIntroViewController.swift +++ b/Telegram-Mac/ChannelIntroViewController.swift @@ -7,10 +7,11 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChannelIntroView : NSScrollView, AppearanceViewProtocol { let imageView:ImageView = ImageView() @@ -23,22 +24,30 @@ class ChannelIntroView : NSScrollView, AppearanceViewProtocol { wantsLayer = true documentView?.addSubview(imageView) documentView?.addSubview(textView) - - updateLocalizationAndTheme() + documentView?.addSubview(button) + + button.set(font: .medium(.title), for: .Normal) + updateLocalizationAndTheme(theme: theme) } - func updateLocalizationAndTheme() { - + func updateLocalizationAndTheme(theme: PresentationTheme) { + let theme = (theme as! TelegramPresentationTheme) imageView.image = theme.icons.channelIntro imageView.sizeToFit() + + button.set(text: L10n.channelIntroCreateChannel, for: .Normal) + + button.set(color: theme.colors.accent, for: .Normal) + _ = button.sizeToFit() + backgroundColor = theme.colors.background textView.background = theme.colors.background documentView?.background = theme.colors.background let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.channelIntroDescriptionHeader), color: theme.colors.text, font: .medium(.header)) + _ = attr.append(string: tr(L10n.channelIntroDescriptionHeader), color: theme.colors.text, font: .medium(.header)) _ = attr.append(string:"\n\n") - _ = attr.append(string: tr(.channelIntroDescription), color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: tr(L10n.channelIntroDescription), color: theme.colors.grayText, font: .normal(.text)) textView.set(layout: TextViewLayout(attr, alignment:.center)) } @@ -52,7 +61,11 @@ class ChannelIntroView : NSScrollView, AppearanceViewProtocol { textView.update(textView.layout) imageView.centerX(y:30) textView.centerX(y:imageView.frame.maxY + 30) - containerView.setFrameSize(frame.width, textView.frame.maxY + 30) + + button.centerX(y: textView.frame.maxY + 30) + + containerView.setFrameSize(frame.width, button.frame.maxY + 30) + } required init?(coder: NSCoder) { @@ -65,7 +78,7 @@ class ChannelIntroViewController: EmptyComposeController BarView { - return TextButtonBarView(controller: self, text: tr(.channelCreate), style: navigationButtonStyle, alignment:.Right) + return TextButtonBarView(controller: self, text: tr(L10n.channelCreate), style: navigationButtonStyle, alignment:.Right) } override var removeAfterDisapper: Bool { @@ -80,6 +93,7 @@ class ChannelIntroViewController: EmptyComposeController KeyHandlerResult { executeNext() return .rejected @@ -88,7 +102,11 @@ class ChannelIntroViewController: EmptyComposeController ChannelMemberListState { + return ChannelMemberListState(list: list, loadingState: self.loadingState) + } + + func withUpdatedLoadingState(_ loadingState: ChannelMemberListLoadingState) -> ChannelMemberListState { + return ChannelMemberListState(list: self.list, loadingState: loadingState) + } +} + +enum ChannelMemberListCategory { + case recent + case recentSearch(String) + case admins(String?) + case contacts(String?) + case bots(String?) + case restricted(String?) + case banned(String?) +} + +private protocol ChannelMemberCategoryListContext { + var listStateValue: ChannelMemberListState { get } + var listState: Signal { get } + func loadMore() + func reset(_ force: Bool) + func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) + func forceUpdateHead() +} + +private func isParticipantMember(_ participant: ChannelParticipant, infoIsMember: Bool?) -> Bool { + if let banInfo = participant.banInfo { + return !banInfo.rights.flags.contains(.banReadMessages) && banInfo.isMember + } else if let infoIsMember = infoIsMember { + return infoIsMember + } else { + return true + } +} + +private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategoryListContext { + private let postbox: Postbox + private let network: Network + private let accountPeerId: PeerId + private let peerId: PeerId + private let category: ChannelMemberListCategory + + var listStateValue: ChannelMemberListState { + didSet { + self.listStatePromise.set(.single(self.listStateValue)) + if case .admins(nil) = self.category, case .ready = self.listStateValue.loadingState { + let ranks: [PeerId: CachedChannelAdminRank] = self.listStateValue.list.reduce([:]) { (ranks, participant) in + var ranks = ranks + ranks[participant.participant.peerId] = CachedChannelAdminRank(participant: participant.participant) + return ranks + } + let previousRanks: [PeerId: CachedChannelAdminRank] = oldValue.list.reduce([:]) { (ranks, participant) in + var ranks = ranks + ranks[participant.participant.peerId] = CachedChannelAdminRank(participant: participant.participant) + return ranks + } + if ranks != previousRanks { + let _ = updateCachedChannelAdminRanks(postbox: self.postbox, peerId: self.peerId, ranks: ranks).start() + } + } + } + } + + private var listStatePromise: Promise + var listState: Signal { + return self.listStatePromise.get() + } + + private let loadingDisposable = MetaDisposable() + private let headUpdateDisposable = MetaDisposable() + + private var headUpdateTimer: SwiftSignalKit.Timer? + + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMemberListCategory) { + self.postbox = postbox + self.network = network + self.accountPeerId = accountPeerId + self.peerId = peerId + self.category = category + + self.listStateValue = ChannelMemberListState(list: [], loadingState: .ready(hasMore: true)) + self.listStatePromise = Promise(self.listStateValue) + self.loadMoreInternal(initial: true) + } + + deinit { + self.loadingDisposable.dispose() + self.headUpdateDisposable.dispose() + self.headUpdateTimer?.invalidate() + } + + func loadMore() { + self.loadMoreInternal(initial: false) + } + + private func loadMoreInternal(initial: Bool) { + guard case .ready(true) = self.listStateValue.loadingState else { + return + } + + let loadCount: Int32 + if case .ready(true) = self.listStateValue.loadingState, self.listStateValue.list.isEmpty { + loadCount = initialBatchSize + } else { + loadCount = requestBatchSize + } + + self.listStateValue = self.listStateValue.withUpdatedLoadingState(.loading(initial: initial)) + + self.loadingDisposable.set((self.loadMoreSignal(count: loadCount) + |> deliverOnMainQueue).start(next: { [weak self] members in + self?.appendMembersAndFinishLoading(members) + })) + } + + func reset(_ force: Bool) { + if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty { + } else { + var list = self.listStateValue.list + var loadingState: ChannelMemberListLoadingState = .ready(hasMore: true) + if list.count > Int(initialBatchSize) && !force { + list.removeSubrange(Int(initialBatchSize) ..< list.count) + loadingState = .ready(hasMore: true) + } + + self.loadingDisposable.set(nil) + self.listStateValue = self.listStateValue.withUpdatedLoadingState(loadingState).withUpdatedList(list) + } + } + + private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> { + let requestCategory: ChannelMembersCategory + var adminQuery: String? = nil + switch self.category { + case .recent: + requestCategory = .recent(.all) + case let .recentSearch(query): + requestCategory = .recent(.search(query)) + case let .admins(query): + requestCategory = .admins + adminQuery = query + case let .contacts(query): + requestCategory = .contacts(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all) + case let .bots(query): + requestCategory = .bots(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all) + case let .restricted(query): + requestCategory = .restricted(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all) + case let .banned(query): + requestCategory = .banned(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all) + } + return channelMembers(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash) |> map { members in + switch requestCategory { + case .admins: + if let query = adminQuery { + return members?.filter({$0.peer.displayTitle.lowercased().components(separatedBy: " ").contains(where: {$0.hasPrefix(query.lowercased())})}) + } + default: + break + } + return members + } + } + + private func loadMoreSignal(count: Int32) -> Signal<[RenderedChannelParticipant], NoError> { + return self.loadSignal(offset: Int32(self.listStateValue.list.count), count: count, hash: 0) + |> map { value -> [RenderedChannelParticipant] in + return value ?? [] + } + } + + private func updateHeadMembers(_ headMembers: [RenderedChannelParticipant]?) { + if let headMembers = headMembers { + var existingIds = Set() + var list = headMembers + for member in list { + existingIds.insert(member.peer.id) + } + for member in self.listStateValue.list { + if !existingIds.contains(member.peer.id) { + list.append(member) + } + } + self.loadingDisposable.set(nil) + self.listStateValue = self.listStateValue.withUpdatedList(list) + if case .loading = self.listStateValue.loadingState { + self.loadMore() + } + } + + self.headUpdateTimer?.invalidate() + self.headUpdateTimer = nil + self.checkUpdateHead() + } + + private func appendMembersAndFinishLoading(_ members: [RenderedChannelParticipant]) { + var firstLoad = false + if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty { + firstLoad = true + } + var existingIds = Set() + var list = self.listStateValue.list + for member in list { + existingIds.insert(member.peer.id) + } + for member in members { + if !existingIds.contains(member.peer.id) { + list.append(member) + } + } + self.listStateValue = self.listStateValue.withUpdatedList(list).withUpdatedLoadingState(.ready(hasMore: members.count >= requestBatchSize)) + if firstLoad { + self.checkUpdateHead() + } + } + + func forceUpdateHead() { + self.headUpdateTimer = nil + self.checkUpdateHead() + } + + private func checkUpdateHead() { + if self.listStateValue.list.isEmpty { + return + } + + if self.headUpdateTimer == nil { + let headUpdateTimer = SwiftSignalKit.Timer(timeout: headUpdateTimeout, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + + var hash: UInt32 = 0 + + for i in 0 ..< min(strongSelf.listStateValue.list.count, Int(initialBatchSize)) { + let peerId = strongSelf.listStateValue.list[i].peer.id + hash = (hash &* 20261) &+ UInt32(peerId.id) + } + hash = hash % 0x7FFFFFFF + strongSelf.headUpdateDisposable.set((strongSelf.loadSignal(offset: 0, count: initialBatchSize, hash: Int32(bitPattern: hash)) + |> deliverOnMainQueue).start(next: { members in + self?.updateHeadMembers(members) + })) + }, queue: Queue.mainQueue()) + self.headUpdateTimer = headUpdateTimer + headUpdateTimer.start() + } + } + + fileprivate func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) { + var list = self.listStateValue.list + var updatedList = false + for (maybePrevious, updated, infoIsMember) in updates { + var previous: ChannelParticipant? = maybePrevious + if let participantId = maybePrevious?.peerId ?? updated?.peer.id { + inner: for participant in list { + if participant.peer.id == participantId { + previous = participant.participant + break inner + } + } + } + switch self.category { + case let .admins(query): + if let updated = updated, (query == nil || updated.peer.indexName.matchesByTokens(query!)) { + if case let .member(_, _, adminInfo, _, _) = updated.participant, adminInfo == nil { + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list.remove(at: i) + updatedList = true + break loop + } + } + } else { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } + } else if let previous = previous, let _ = previous.adminInfo { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + if let updated = updated, case .creator = updated.participant { + list.insert(updated, at: 0) + updatedList = true + } + } + case .restricted: + if let updated = updated, let banInfo = updated.participant.banInfo, !banInfo.rights.flags.contains(.banReadMessages) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } else if let previous = previous, let banInfo = previous.banInfo, !banInfo.rights.flags.contains(.banReadMessages) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + case .banned: + if let updated = updated, let banInfo = updated.participant.banInfo, banInfo.rights.flags.contains(.banReadMessages) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } else if let previous = previous, let banInfo = previous.banInfo, banInfo.rights.flags.contains(.banReadMessages) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + case .recent: + if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + case let .contacts(query): + if query == nil { + if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + //list.insert(updated, at: 0) + //updatedList = true + } + } else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + } + case let .bots(query): + if query == nil { + if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + //list.insert(updated, at: 0) + //updatedList = true + } + } else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + } + case let .recentSearch(query): + if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember), updated.peer.indexName.matchesByTokens(query) { + var found = false + loop: for i in 0 ..< list.count { + if list[i].peer.id == updated.peer.id { + list[i] = updated + found = true + updatedList = true + break loop + } + } + if !found { + list.insert(updated, at: 0) + updatedList = true + } + } else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) { + loop: for i in 0 ..< list.count { + if list[i].peer.id == previous.peerId { + list.remove(at: i) + updatedList = true + break loop + } + } + } + } + } + if updatedList { + self.listStateValue = self.listStateValue.withUpdatedList(list) + } + } + +} + +private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategoryListContext { + private var contexts: [ChannelMemberSingleCategoryListContext] = [] + + var listStateValue: ChannelMemberListState { + return ChannelMemberMultiCategoryListContext.reduceListStates(self.contexts.map { $0.listStateValue }) + } + + private static func reduceListStates(_ listStates: [ChannelMemberListState]) -> ChannelMemberListState { + var allReady = true + for listState in listStates { + if case .loading(true) = listState.loadingState, listState.list.isEmpty { + allReady = false + break + } + } + if !allReady { + return ChannelMemberListState(list: [], loadingState: .loading(initial: true)) + } + + var list: [RenderedChannelParticipant] = [] + var existingIds = Set() + var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false) + loop: for i in 0 ..< listStates.count { + for item in listStates[i].list { + if !existingIds.contains(item.peer.id) { + existingIds.insert(item.peer.id) + list.append(item) + } + } + switch listStates[i].loadingState { + case let .loading(initial): + loadingState = .loading(initial: initial) + break loop + case let .ready(hasMore): + if hasMore { + loadingState = .ready(hasMore: true) + break loop + } + } + } + return ChannelMemberListState(list: list, loadingState: loadingState) + } + + var listState: Signal { + let signals: [Signal] = self.contexts.map { context in + return context.listState + } + return combineLatest(signals) |> map { listStates -> ChannelMemberListState in + return ChannelMemberMultiCategoryListContext.reduceListStates(listStates) + } + } + + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, categories: [ChannelMemberListCategory]) { + self.contexts = categories.map { category in + return ChannelMemberSingleCategoryListContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, category: category) + } + } + + func loadMore() { + loop: for context in self.contexts { + switch context.listStateValue.loadingState { + case .loading: + break loop + case let .ready(hasMore): + if hasMore { + context.loadMore() + } + } + } + } + + func reset(_ force: Bool) { + for context in self.contexts { + context.reset(force) + } + } + + func forceUpdateHead() { + for context in self.contexts { + context.forceUpdateHead() + } + } + + func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) { + for context in self.contexts { + context.replayUpdates(updates) + } + } +} + +struct PeerChannelMemberCategoryControl { + fileprivate let key: PeerChannelMemberContextKey +} + +private final class PeerChannelMemberContextWithSubscribers { + let context: ChannelMemberCategoryListContext + private let emptyTimeout: Double + private let subscribers = Bag<(ChannelMemberListState) -> Void>() + private let disposable = MetaDisposable() + private let becameEmpty: () -> Void + + private var emptyTimer: SwiftSignalKit.Timer? + + init(context: ChannelMemberCategoryListContext, emptyTimeout: Double, becameEmpty: @escaping () -> Void) { + self.context = context + self.emptyTimeout = emptyTimeout + self.becameEmpty = becameEmpty + self.disposable.set((context.listState + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + for f in strongSelf.subscribers.copyItems() { + f(value) + } + } + })) + } + + deinit { + self.disposable.dispose() + self.emptyTimer?.invalidate() + } + + private func resetAndBeginEmptyTimer() { + self.context.reset(false) + self.emptyTimer?.invalidate() + let emptyTimer = SwiftSignalKit.Timer(timeout: self.emptyTimeout, repeat: false, completion: { [weak self] in + if let strongSelf = self { + if strongSelf.subscribers.isEmpty { + strongSelf.becameEmpty() + } + } + }, queue: Queue.mainQueue()) + self.emptyTimer = emptyTimer + emptyTimer.start() + } + + func subscribe(requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> Disposable { + let wasEmpty = self.subscribers.isEmpty + let index = self.subscribers.add(updated) + updated(self.context.listStateValue) + if wasEmpty { + self.emptyTimer?.invalidate() + if requestUpdate { + self.context.forceUpdateHead() + } + } + return ActionDisposable { [weak self] in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.subscribers.remove(index) + if strongSelf.subscribers.isEmpty { + strongSelf.resetAndBeginEmptyTimer() + } + } + } + } + } +} + +final class PeerChannelMemberCategoriesContext { + private let postbox: Postbox + private let network: Network + private let accountPeerId: PeerId + private let peerId: PeerId + private var becameEmpty: (Bool) -> Void + + private var contexts: [PeerChannelMemberContextKey: PeerChannelMemberContextWithSubscribers] = [:] + + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, becameEmpty: @escaping (Bool) -> Void) { + self.postbox = postbox + self.network = network + self.accountPeerId = accountPeerId + self.peerId = peerId + self.becameEmpty = becameEmpty + } + + func reset(_ key: PeerChannelMemberContextKey) { + for (contextKey, context) in contexts { + if contextKey == key { + context.context.reset(true) + context.context.loadMore() + } + } + } + + func getContext(key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { + assert(Queue.mainQueue().isCurrent()) + if let current = self.contexts[key] { + return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key)) + } + let context: ChannelMemberCategoryListContext + let emptyTimeout: Double + switch key { + case .admins(nil), .banned(nil), .recentSearch(nil), .restricted(nil), .restrictedAndBanned(nil), .recent: + emptyTimeout = defaultEmptyTimeout + default: + emptyTimeout = 0.0 + } + switch key { + case .recent, .recentSearch, .admins, .contacts, .bots: + let mappedCategory: ChannelMemberListCategory + switch key { + case .recent: + mappedCategory = .recent + case let .recentSearch(query): + mappedCategory = .recentSearch(query) + case let .admins(query): + mappedCategory = .admins(query) + case let .contacts(query): + mappedCategory = .contacts(query) + case let .bots(query): + mappedCategory = .bots(query) + default: + mappedCategory = .recent + } + context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: mappedCategory) + case let .restrictedAndBanned(query): + context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, categories: [.restricted(query), .banned(query)]) + case let .restricted(query): + context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .restricted(query)) + case let .banned(query): + context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .banned(query)) + } + let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, emptyTimeout: emptyTimeout, becameEmpty: { [weak self] in + assert(Queue.mainQueue().isCurrent()) + if let strongSelf = self { + strongSelf.contexts.removeValue(forKey: key) + } + }) + self.contexts[key] = contextWithSubscribers + return (contextWithSubscribers.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key)) + } + + func loadMore(_ control: PeerChannelMemberCategoryControl) { + assert(Queue.mainQueue().isCurrent()) + if let context = self.contexts[control.key] { + context.context.loadMore() + } + } + + func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) { + for (_, context) in self.contexts { + context.context.replayUpdates(updates) + } + } +} diff --git a/Telegram-Mac/ChannelMembersViewController.swift b/Telegram-Mac/ChannelMembersViewController.swift index 28bd139388..b281fe2cd5 100644 --- a/Telegram-Mac/ChannelMembersViewController.swift +++ b/Telegram-Mac/ChannelMembersViewController.swift @@ -8,21 +8,22 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private final class ChannelMembersControllerArguments { - let account: Account + let context: AccountContext let removePeer: (PeerId) -> Void let addMembers:()-> Void let inviteLink:()-> Void let openInfo:(Peer)->Void - init(account: Account, removePeer: @escaping (PeerId) -> Void, addMembers:@escaping()->Void, inviteLink:@escaping()->Void, openInfo:@escaping(Peer)->Void) { - self.account = account + init(context: AccountContext, removePeer: @escaping (PeerId) -> Void, addMembers:@escaping()->Void, inviteLink:@escaping()->Void, openInfo:@escaping(Peer)->Void) { + self.context = context self.removePeer = removePeer self.addMembers = addMembers self.inviteLink = inviteLink @@ -36,7 +37,7 @@ private enum ChannelMembersEntryStableId: Hashable { case inviteLink case membersDesc case section(Int) - + case loading var hashValue: Int { switch self { case let .peer(peerId): @@ -47,57 +48,26 @@ private enum ChannelMembersEntryStableId: Hashable { return 1 case .membersDesc: return 2 + case .loading: + return 3 case let .section(sectionId): return -(sectionId) } } - static func ==(lhs: ChannelMembersEntryStableId, rhs: ChannelMembersEntryStableId) -> Bool { - switch lhs { - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - case .addMembers: - if case .addMembers = rhs { - return true - } else { - return false - } - case .membersDesc: - if case .membersDesc = rhs { - return true - } else { - return false - } - case .inviteLink: - if case .inviteLink = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } } private enum ChannelMembersEntry: Identifiable, Comparable { - case peerItem(sectionId:Int, Int32, RenderedChannelParticipant, ShortPeerDeleting?, Bool) - case addMembers(sectionId:Int) - case inviteLink(sectionId:Int) - case membersDesc(sectionId:Int) + case peerItem(sectionId:Int, Int32, RenderedChannelParticipant, ShortPeerDeleting?, Bool, GeneralViewType) + case addMembers(sectionId:Int, Bool, GeneralViewType) + case inviteLink(sectionId:Int, GeneralViewType) + case membersDesc(sectionId:Int, GeneralViewType) case section(sectionId:Int) + case loading(sectionId: Int) var stableId: ChannelMembersEntryStableId { switch self { - case let .peerItem(_, _, participant, _, _): + case let .peerItem(_, _, participant, _, _, _): return .peer(participant.peer.id) case .addMembers: return .addMembers @@ -105,6 +75,8 @@ private enum ChannelMembersEntry: Identifiable, Comparable { return .inviteLink case .membersDesc: return .membersDesc + case .loading: + return .loading case let .section(sectionId): return .section(sectionId) } @@ -113,65 +85,21 @@ private enum ChannelMembersEntry: Identifiable, Comparable { var index:Int { switch self { - case let .peerItem(sectionId, index, _, _, _): + case let .peerItem(sectionId, index, _, _, _, _): return (sectionId * 1000) + Int(index) + 100 - case let .addMembers(sectionId): + case let .addMembers(sectionId, _, _): return (sectionId * 1000) + 0 - case let .inviteLink(sectionId): + case let .inviteLink(sectionId, _): return (sectionId * 1000) + 1 - case let .membersDesc(sectionId): + case let .membersDesc(sectionId, _): return (sectionId * 1000) + 2 + case let .loading(sectionId): + return (sectionId * 1000) + 4 case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } } - static func ==(lhs: ChannelMembersEntry, rhs: ChannelMembersEntry) -> Bool { - switch lhs { - case let .peerItem(_, lhsIndex, lhsParticipant, lhsEditing, lhsEnabled): - if case let .peerItem(_, rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsParticipant != rhsParticipant { - return false - } - if lhsEditing != rhsEditing { - return false - } - if lhsEnabled != rhsEnabled { - return false - } - return true - } else { - return false - } - case .addMembers: - if case .addMembers = rhs { - return true - } else { - return false - } - case .inviteLink: - if case .inviteLink = rhs { - return true - } else { - return false - } - case .membersDesc: - if case .membersDesc = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } static func <(lhs: ChannelMembersEntry, rhs: ChannelMembersEntry) -> Bool { return lhs.index < rhs.index @@ -179,7 +107,7 @@ private enum ChannelMembersEntry: Identifiable, Comparable { func item(_ arguments: ChannelMembersControllerArguments, initialSize:NSSize) -> TableRowItem { switch self { - case let .peerItem(_, _, participant, editing, enabled): + case let .peerItem(_, _, participant, editing, enabled, viewType): let interactionType:ShortPeerItemInteractionType if let editing = editing { @@ -191,24 +119,26 @@ private enum ChannelMembersEntry: Identifiable, Comparable { interactionType = .plain } - return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.account, stableId: stableId, enabled: enabled, height:44, photoSize: NSMakeSize(32, 32), drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, action: { + return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.context.account, stableId: stableId, enabled: enabled, height:46, photoSize: NSMakeSize(32, 32), drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, viewType: viewType, action: { if case .plain = interactionType { arguments.openInfo(participant.peer) } }) - case .addMembers: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelMembersAddMembers), nameStyle: blueActionButton, type: .none, action: { + case let .addMembers(_, isChannel, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: isChannel ? L10n.channelMembersAddSubscribers : L10n.channelMembersAddMembers, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.addMembers() }) - case .inviteLink: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelMembersInviteLink), nameStyle: blueActionButton, type: .none, action: { + case let .inviteLink(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.channelMembersInviteLink, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.inviteLink() }) - case .membersDesc: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.channelMembersMembersListDesc)) + case let .membersDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.channelMembersMembersListDesc, viewType: viewType) + case .loading: + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } @@ -251,51 +181,37 @@ private struct ChannelMembersControllerState: Equatable { } } -private func channelMembersControllerEntries(view: PeerView, account:Account, state: ChannelMembersControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelMembersEntry] { +private func channelMembersControllerEntries(view: PeerView, context: AccountContext, state: ChannelMembersControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelMembersEntry] { var entries: [ChannelMembersEntry] = [] + var sectionId:Int = 1 + if let participants = participants { - var sectionId:Int = 1 - - - if !participants.isEmpty { - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - } + entries.append(.section(sectionId: sectionId)) + sectionId += 1 if let peer = peerViewMainPeer(view) as? TelegramChannel { - var usersManage:Bool = false - if peer.hasAdminRights(.canInviteUsers) { - entries.append(.addMembers(sectionId: sectionId)) - usersManage = true - } - if peer.hasAdminRights(.canChangeInviteLink) { - entries.append(.inviteLink(sectionId: sectionId)) - usersManage = true - } - - if usersManage { + if peer.hasPermission(.inviteMembers) { + entries.append(.addMembers(sectionId: sectionId, peer.isChannel, .singleItem)) + entries.append(.membersDesc(sectionId: sectionId, .textBottomItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.membersDesc(sectionId: sectionId)) } var index: Int32 = 0 - for participant in participants.sorted(by: <) { - - + for (i, participant) in participants.sorted(by: <).enumerated() { let editable:Bool switch participant.participant { - case let .member(_, _, adminInfo, _): + case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo { editable = adminInfo.canBeEditedByAccountPeer } else { - editable = participant.participant.peerId != account.peerId + editable = participant.participant.peerId != context.account.peerId } default: editable = false @@ -305,14 +221,15 @@ private func channelMembersControllerEntries(view: PeerView, account:Account, st if state.editing { deleting = ShortPeerDeleting(editable: editable) } - - entries.append(.peerItem(sectionId: sectionId, index, participant, deleting, state.removingPeerId != participant.peer.id)) + entries.append(.peerItem(sectionId: sectionId, index, participant, deleting, state.removingPeerId != participant.peer.id, bestGeneralViewType(participants, for: i))) index += 1 } } - - + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + } else { + entries.append(.loading(sectionId: sectionId)) } return entries @@ -337,79 +254,122 @@ class ChannelMembersViewController: EditableViewController { private let removePeerDisposable:MetaDisposable = MetaDisposable() private let disposable:MetaDisposable = MetaDisposable() - - init(account:Account, peerId:PeerId) { + init(_ context: AccountContext, peerId:PeerId) { self.peerId = peerId - super.init(account) + super.init(context) + } + + override var defaultBarTitle: String { + return L10n.peerInfoSubscribers + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + self.searchChannelUsers() + return .invoked + }, with: self, for: .F, priority: .low, modifierFlags: [.command]) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) } override func viewDidLoad() { super.viewDidLoad() - let account = self.account + let context = self.context let peerId = self.peerId + genericView.getBackgroundColor = { + theme.colors.listBackground + } + let updateState: ((ChannelMembersControllerState) -> ChannelMembersControllerState) -> Void = { [weak self] f in if let strongSelf = self { strongSelf.statePromise.set(strongSelf.stateValue.modify { f($0) }) } } + let actionsDisposable = DisposableSet() let peersPromise = Promise<[RenderedChannelParticipant]?>(nil) - let arguments = ChannelMembersControllerArguments(account: account, removePeer: { [weak self] memberId in + let arguments = ChannelMembersControllerArguments(context: context, removePeer: { [weak self] memberId in updateState { return $0.withUpdatedRemovingPeerId(memberId) } - let applyPeers: Signal = peersPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { peers -> Signal in - if let peers = peers { - var updatedPeers = peers - for i in 0 ..< updatedPeers.count { - if updatedPeers[i].peer.id == memberId { - updatedPeers.remove(at: i) - break - } - } - peersPromise.set(.single(updatedPeers)) - } - - return .complete() - } - - self?.removePeerDisposable.set((removePeerMember(account: account, peerId: peerId, memberId: memberId) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in - updateState { - return $0.withUpdatedRemovingPeerId(nil) - } - }, completed: { + self?.removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: 0)) |> deliverOnMainQueue).start(completed: { updateState { return $0.withUpdatedRemovingPeerId(nil) } })) }, addMembers: { - peersPromise.set(selectModalPeers(account: account, title: tr(.channelMembersSelectTitle), settings: [.contacts, .remote]) |> mapToSignal { peers -> Signal<[RenderedChannelParticipant]?, Void> in - return showModalProgress(signal: addChannelMembers(account: account, peerId: peerId, memberIds: peers) |> mapToSignal { - return channelMembers(account: account, peerId: peerId) |> map { Optional($0) } - }, for: mainWindow) - }) + let signal = selectModalPeers(context: context, title: L10n.channelMembersSelectTitle, settings: [.contacts, .remote, .excludeBots]) |> mapError { _ in return AddChannelMemberError.generic} |> mapToSignal { peers -> Signal in + return showModalProgress(signal: context.peerChannelMemberCategoriesContextsManager.addMembers(account: context.account, peerId: peerId, memberIds: peers), for: mainWindow) + } |> deliverOnMainQueue + + actionsDisposable.add(signal.start(error: { error in + let text: String + switch error { + case .notMutualContact: + text = L10n.channelInfoAddUserLeftError + case .limitExceeded: + text = L10n.channelErrorAddTooMuch + case .botDoesntSupportGroups: + text = L10n.channelBotDoesntSupportGroups + case .tooMuchBots: + text = L10n.channelTooMuchBots + case .tooMuchJoined: + text = L10n.inviteChannelsTooMuch + case .generic: + text = L10n.unknownError + case let .bot(memberId): + let _ = (context.account.postbox.transaction { transaction in + return transaction.getPeer(peerId) + } + |> deliverOnMainQueue).start(next: { peer in + guard let peer = peer as? TelegramChannel else { + alert(for: context.window, info: L10n.unknownError) + return + } + if peer.hasPermission(.addAdmins) { + confirm(for: context.window, information: L10n.channelAddBotErrorHaveRights, okTitle: L10n.channelAddBotAsAdmin, successHandler: { _ in + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in }, upgradedToSupergroup: { _, f in f() }), for: context.window) + }) + } else { + alert(for: context.window, info: L10n.channelAddBotErrorHaveRights) + } + }) + return + case .restricted: + text = L10n.channelErrorAddBlocked + } + alert(for: mainWindow, info: text) + }, completed: { + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + })) }, inviteLink: { [weak self] in if let strongSelf = self { - strongSelf.navigationController?.push(LinkInvationController(account: strongSelf.account, peerId: strongSelf.peerId)) + strongSelf.navigationController?.push(LinkInvationController(strongSelf.context, peerId: strongSelf.peerId)) } }, openInfo: { [weak self] peer in - self?.navigationController?.push(PeerInfoController(account: account, peer: peer)) + self?.navigationController?.push(PeerInfoController(context: context, peerId: peer.id)) }) - let peerView = account.viewTracker.peerView(peerId) + let peerView = context.account.viewTracker.peerView(peerId) + + + let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerId, updated: { state in + peersPromise.set(.single(state.list)) + }) + actionsDisposable.add(disposable) + - let peersSignal: Signal<[RenderedChannelParticipant]?, NoError> = .single(nil) |> then(channelMembers(account: account, peerId: peerId) |> map { Optional($0) }) - peersPromise.set(peersSignal) let initialSize = atomicSize let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) @@ -418,16 +378,29 @@ class ChannelMembersViewController: EditableViewController { let signal = combineLatest(statePromise.get(), peerView, peersPromise.get(), appearanceSignal) |> deliverOnMainQueue |> map { state, view, peers, appearance -> TableUpdateTransition in - let entries = channelMembersControllerEntries(view: view, account: account, state: state, participants: peers).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let entries = channelMembersControllerEntries(view: view, context: context, state: state, participants: peers).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) + } |> afterDisposed { + actionsDisposable.dispose() } - disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] transition in + self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] transition in if let strongSelf = self { strongSelf.genericView.merge(with: transition) strongSelf.readyOnce() } })) + + genericView.setScrollHandler { position in + if let loadMoreControl = loadMoreControl { + switch position.direction { + case .bottom: + context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl) + default: + break + } + } + } } deinit { @@ -440,4 +413,18 @@ class ChannelMembersViewController: EditableViewController { self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) } + private func searchChannelUsers() { + _ = (selectModalPeers(context: context, title: L10n.selectPeersTitleSearchMembers, behavior: SelectChannelMembersBehavior(peerId: peerId, limit: 1, settings: [])) |> deliverOnMainQueue |> map {$0.first}).start(next: { [weak self] peerId in + if let peerId = peerId, let context = self?.context { + self?.navigationController?.push(PeerInfoController(context: context, peerId: peerId)) + } + }) + } + + override func getCenterBarViewOnce() -> TitledBarView { + return SearchTitleBarView(controller: self, title:.initialize(string: defaultBarTitle, color: theme.colors.text, font: .medium(.title)), handler: { [weak self] in + self?.searchChannelUsers() + }) + } + } diff --git a/Telegram-Mac/ChannelOverviewStatsRowItem.swift b/Telegram-Mac/ChannelOverviewStatsRowItem.swift new file mode 100644 index 0000000000..8cde30c939 --- /dev/null +++ b/Telegram-Mac/ChannelOverviewStatsRowItem.swift @@ -0,0 +1,247 @@ +// +// ChannelOverviewStatsRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 28.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore +import GraphCore + +struct ChannelOverviewItem : Equatable { + let title: String + let value: NSAttributedString +} + +extension StatsValue { + var attributedString: NSAttributedString { + + let deltaValue = self.current - self.previous + let deltaCompact = abs(Int(deltaValue)).prettyNumber + var delta = deltaValue == 0 ? "" : deltaValue > 0 ? " +\(deltaCompact)" : " -\(deltaCompact)" + var deltaPercentage = 0.0 + if self.previous > 0.0, deltaValue != 0 { + deltaPercentage = abs(deltaValue / self.previous) + delta += String(format: " (%.02f%%)", deltaPercentage * 100) + } + + let attr = NSMutableAttributedString() + + _ = attr.append(string: Int(self.current).prettyNumber, color: theme.colors.text, font: .medium(.header)) + if !delta.isEmpty { + _ = attr.append(string: delta, color: deltaValue < 0 ? theme.colors.redUI : theme.colors.greenUI, font: .normal(.small)) + } + + return attr + + } +} +extension StatsPercentValue { + var attributedString: NSAttributedString { + let attr = NSMutableAttributedString() + + let deltaPercentage = abs(self.value / self.total) + + _ = attr.append(string: String(format: "%.02f%%", deltaPercentage * 100), color: theme.colors.text, font: .medium(.header)) + + return attr + } +} + +private struct ChannelOverviewLayoutItem { + let title: TextViewLayout + let name: TextViewLayout + + init(item: ChannelOverviewItem) { + self.name = TextViewLayout(.initialize(string: item.title, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + + self.title = TextViewLayout(item.value, maximumNumberOfLines: 1) + } + + func measure(_ width: CGFloat) { + self.title.measure(width: width) + self.name.measure(width: width) + } + + var size: NSSize { + return NSMakeSize(max(self.title.layoutSize.width, self.name.layoutSize.width), title.layoutSize.height + 3 + name.layoutSize.height) + } +} + +class ChannelOverviewStatsRowItem: GeneralRowItem { + + + fileprivate let layoutItems:[ChannelOverviewLayoutItem] + + + init(_ initialSize: NSSize, stableId: AnyHashable, items: [ChannelOverviewItem], viewType: GeneralViewType) { + self.layoutItems = items.map { + return ChannelOverviewLayoutItem(item: $0) + } + super.init(initialSize, stableId: stableId, viewType: viewType) + + _ = makeSize(initialSize.width) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + for item in layoutItems { + item.measure((blockWidth - viewType.innerInset.left - viewType.innerInset.right - 20) / 2) + } + + return true + } + + override var height: CGFloat { + + var height: CGFloat = 0 + + for (i, item) in layoutItems.enumerated() { + if i % 2 == 0 { + height += item.size.height + if i < layoutItems.count - 2 { + height += 10 + } + } + } + + return height + viewType.innerInset.bottom + viewType.innerInset.top + } + + override func viewClass() -> AnyClass { + return ChannelOverviewStatsRowView.self + } +} + +private final class ChannelOverviewLayoutView : View { + private let titleView: TextView = TextView() + private let nameView: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(titleView) + addSubview(nameView) + + nameView.isSelectable = false + nameView.userInteractionEnabled = false + } + + override func layout() { + super.layout() + self.titleView.setFrameOrigin(.zero) + self.nameView.setFrameOrigin(NSMakePoint(0, self.titleView.frame.maxY + 3)) + } + + func update(_ item: ChannelOverviewLayoutItem) { + self.titleView.update(item.title) + self.nameView.update(item.name) + needsLayout = true + } + + func updateColors() { + + let backgroundColor = theme.colors.background + + self.backgroundColor = backgroundColor + self.titleView.backgroundColor = backgroundColor + self.nameView.backgroundColor = backgroundColor + } + + required init?(coder: NSCoder) { + fatalError("init(coder :) has not been implemented") + } +} + +private final class ChannelOverviewStatsRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.containerView) + } + + override func layout() { + super.layout() + guard let item = item as? GeneralRowItem else { + return + } + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + + var point: CGPoint = NSMakePoint(item.viewType.innerInset.left, item.viewType.innerInset.top) + for (i, subview) in self.containerView.subviews.enumerated() { + subview.setFrameOrigin(point) + if i < self.containerView.subviews.count - 1 { + if (i + 1) % 2 == 0 { + point.x = item.viewType.innerInset.left + point.y += self.containerView.subviews[i + 1].frame.height + if i < self.containerView.subviews.count - 1 { + point.y += 10 + } + } else { + var width: CGFloat = self.containerView.subviews[i + 1].frame.width + if i > 1 { + width = max(width, self.containerView.subviews[i - 1].frame.width) + } + if i + 3 < self.containerView.subviews.count { + width = max(width, self.containerView.subviews[i + 3].frame.width) + } + + point.x = self.containerView.frame.width - width - item.viewType.innerInset.right + + } + } + + } + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? GeneralRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + + for subview in self.containerView.subviews { + (subview as? ChannelOverviewLayoutView)?.updateColors() + } + } + + + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + + guard let item = item as? ChannelOverviewStatsRowItem else { + return + } + self.containerView.removeAllSubviews() + + for item in item.layoutItems { + let view = ChannelOverviewLayoutView(frame: CGRect(origin: .zero, size: item.size)) + view.update(item) + self.containerView.addSubview(view) + } + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} + + diff --git a/Telegram-Mac/ChannelPermissionsController.swift b/Telegram-Mac/ChannelPermissionsController.swift new file mode 100644 index 0000000000..256337a8ee --- /dev/null +++ b/Telegram-Mac/ChannelPermissionsController.swift @@ -0,0 +1,769 @@ +// +// ChannelPermissionsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 03/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +private final class ChannelPermissionsControllerArguments { + let context: AccountContext + + let updatePermission: (TelegramChatBannedRightsFlags, Bool) -> Void + let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let addPeer: () -> Void + let removePeer: (PeerId) -> Void + let openPeer: (ChannelParticipant) -> Void + let openPeerInfo: (Peer) -> Void + let openKicked: () -> Void + let presentRestrictedPublicGroupPermissionsAlert: () -> Void + let updateSlowMode:(Int32)->Void + init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (Peer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPublicGroupPermissionsAlert: @escaping() -> Void, updateSlowMode:@escaping(Int32)->Void) { + self.context = context + self.updatePermission = updatePermission + self.addPeer = addPeer + self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions + self.removePeer = removePeer + self.openPeer = openPeer + self.openPeerInfo = openPeerInfo + self.openKicked = openKicked + self.presentRestrictedPublicGroupPermissionsAlert = presentRestrictedPublicGroupPermissionsAlert + self.updateSlowMode = updateSlowMode + } +} + +private enum ChannelPermissionsSection: Int32 { + case permissions + case kicked + case exceptions +} + +private enum ChannelPermissionsEntryStableId: Hashable { + case index(Int32) + case peer(PeerId) + case section(Int32) + case permission(Int32) +} + +private enum ChannelPermissionsEntry: TableItemListNodeEntry { + case section(Int32) + case permissionsHeader(Int32, Int32, String, GeneralViewType) + case permission(Int32, Int32, String, Bool, TelegramChatBannedRightsFlags, Bool?, GeneralViewType) + case kicked(Int32, Int32, String, String, GeneralViewType) + case exceptionsHeader(Int32, Int32, String, GeneralViewType) + case add(Int32, Int32, String, GeneralViewType) + case peerItem(Int32, Int32, RenderedChannelParticipant, ShortPeerDeleting?, Bool, Bool, TelegramChatBannedRightsFlags, GeneralViewType) + case slowModeHeader(Int32, GeneralViewType) + case slowMode(Int32, Int32?, GeneralViewType) + case slowDesc(Int32, Int32?, GeneralViewType) + var stableId: ChannelPermissionsEntryStableId { + switch self { + case .permissionsHeader: + return .index(0) + case let .permission(_, index, _, _, _, _, _): + return .permission(1 + index) + case .kicked: + return .index(1000) + case .slowModeHeader: + return .index(1001) + case .slowMode: + return .index(1003) + case .slowDesc: + return .index(1004) + case .exceptionsHeader: + return .index(1005) + case .add: + return .index(1006) + case let .section(section): + return .section(section) + case let .peerItem( _, _, participant, _, _, _, _, _): + return .peer(participant.peer.id) + } + } + + var index: Int32 { + switch self { + case let .permissionsHeader(section, index, _, _): + return (section * 1000) + index + case let .permission(section, index, _, _, _, _, _): + return (section * 1000) + index + case let .kicked(section, index, _, _, _): + return (section * 1000) + index + case let .slowMode(section, _, _): + return (section * 1000) + 0 + case let .slowModeHeader(section, _): + return (section * 1000) + 1 + case let .slowDesc(section, _, _): + return (section * 1000) + 1 + case let .exceptionsHeader(section, index, _, _): + return (section * 1000) + index + case let .add(section, index, _, _): + return (section * 1000) + index + case let .section(section): + return (section + 1) * 1000 - section + case let .peerItem(section, index, _, _, _, _, _, _): + return (section * 1000) + index + } + } + + static func <(lhs: ChannelPermissionsEntry, rhs: ChannelPermissionsEntry) -> Bool { + return lhs.index < rhs.index + } + + + + func item(_ arguments: ChannelPermissionsControllerArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .permissionsHeader(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .permission(_, _, title, value, rights, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .switchable(value), viewType: viewType, action: { + if let _ = enabled { + arguments.updatePermission(rights, !value) + } else { + arguments.presentRestrictedPublicGroupPermissionsAlert() + } + }, enabled: enabled ?? true, switchAppearance: SwitchViewAppearance(backgroundColor: theme.colors.background, stateOnColor: enabled == true ? theme.colors.accent : theme.colors.accent.withAlphaComponent(0.6), stateOffColor: enabled == true ? theme.colors.redUI : theme.colors.redUI.withAlphaComponent(0.6), disabledColor: .grayBackground, borderColor: .clear), autoswitch: false) + case let .kicked(_, _, text, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .nextContext(value), viewType: viewType, action: { + arguments.openKicked() + }) +// return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { +// arguments.openKicked() +// }) + case let .exceptionsHeader(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .add(_, _, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: text, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { () in + arguments.addPeer() + }, thumb: GeneralThumbAdditional(thumb: theme.icons.peerInfoAddMember, textInset: 52, thumbInset: 5)) + + case let .peerItem(_, _, participant, _, enabled, canOpen, defaultBannedRights, viewType): + var text: String? + switch participant.participant { + case let .member(_, _, _, banInfo, _): + var exceptionsString = "" + if let banInfo = banInfo { + for rights in allGroupPermissionList { + if !defaultBannedRights.contains(rights) && banInfo.rights.flags.contains(rights) { + if !exceptionsString.isEmpty { + exceptionsString.append(", ") + } + exceptionsString.append(compactStringForGroupPermission(right: rights)) + } + } + text = exceptionsString + } + default: + break + } + + return ShortPeerRowItem(initialSize, peer: participant.peer, account: arguments.context.account, stableId: stableId, enabled: enabled, status: text, inset: NSEdgeInsetsMake(0, 30, 0, 30), viewType: viewType, action: { + if canOpen { + arguments.openPeer(participant.participant) + } else { + arguments.openPeerInfo(participant.peer) + } + }) + case let .slowModeHeader(_, viewType): + return GeneralTextRowItem(initialSize, text: L10n.channelPermissionsSlowModeHeader, viewType: viewType) + case let .slowMode(_, timeout, viewType): + let list:[Int32] = [0, 10, 30, 60, 300, 900, 3600] + let titles: [String] = [L10n.channelPermissionsSlowModeTimeoutOff, + L10n.channelPermissionsSlowModeTimeout10s, + L10n.channelPermissionsSlowModeTimeout30s, + L10n.channelPermissionsSlowModeTimeout1m, L10n.channelPermissionsSlowModeTimeout5m, + L10n.channelPermissionsSlowModeTimeout15m, + L10n.channelPermissionsSlowModeTimeout1h] + return SelectSizeRowItem(initialSize, stableId: stableId, current: timeout ?? 0, sizes: list, hasMarkers: false, titles: titles, viewType: viewType, selectAction: { index in + arguments.updateSlowMode(list[index]) + }) + case let .slowDesc(_, timeout, viewType): + let text: String + if let timeout = timeout, timeout > 0 { + text = L10n.channelPermissionsSlowModeTextSelected(autoremoveLocalized(Int(timeout))) + } else { + text = L10n.channelPermissionsSlowModeTextOff + } + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + } + } +} + +private struct ChannelPermissionsControllerState: Equatable { + var peerIdWithRevealedOptions: PeerId? + var removingPeerId: PeerId? + var searchingMembers: Bool = false + var modifiedRightsFlags: TelegramChatBannedRightsFlags? +} + +func stringForGroupPermission(right: TelegramChatBannedRightsFlags) -> String { + if right.contains(.banSendMessages) { + return L10n.channelBanUserPermissionSendMessages + } else if right.contains(.banSendMedia) { + return L10n.channelBanUserPermissionSendMedia + } else if right.contains(.banSendGifs) { + return L10n.channelBanUserPermissionSendStickersAndGifs + } else if right.contains(.banEmbedLinks) { + return L10n.channelBanUserPermissionEmbedLinks + } else if right.contains(.banSendPolls) { + return L10n.channelBanUserPermissionSendPolls + } else if right.contains(.banChangeInfo) { + return L10n.channelBanUserPermissionChangeGroupInfo + } else if right.contains(.banAddMembers) { + return L10n.channelBanUserPermissionAddMembers + } else if right.contains(.banPinMessages) { + return L10n.channelEditAdminPermissionPinMessages + } else { + return "" + } +} + +func compactStringForGroupPermission(right: TelegramChatBannedRightsFlags) -> String { + if right.contains(.banSendMessages) { + return L10n.groupPermissionNoSendMessages + } else if right.contains(.banSendMedia) { + return L10n.groupPermissionNoSendMedia + } else if right.contains(.banSendGifs) { + return L10n.groupPermissionNoSendGifs + } else if right.contains(.banEmbedLinks) { + return L10n.groupPermissionNoSendLinks + } else if right.contains(.banSendPolls) { + return L10n.groupPermissionNoSendPolls + } else if right.contains(.banChangeInfo) { + return L10n.groupPermissionNoChangeInfo + } else if right.contains(.banAddMembers) { + return L10n.groupPermissionNoAddMembers + } else if right.contains(.banPinMessages) { + return L10n.groupPermissionNoPinMessages + } else { + return "" + } +} + +let allGroupPermissionList: [TelegramChatBannedRightsFlags] = [ + .banSendMessages, + .banSendMedia, + .banSendGifs, + .banEmbedLinks, + .banSendPolls, + .banAddMembers, + .banPinMessages, + .banChangeInfo +] + +let publicGroupRestrictedPermissions: TelegramChatBannedRightsFlags = [ + .banPinMessages, + .banChangeInfo +] + + +func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags { + if right.contains(.banSendMedia) { + return [.banSendMessages] + } else if right.contains(.banSendGifs) { + return [.banSendMessages] + } else if right.contains(.banEmbedLinks) { + return [.banSendMessages] + } else if right.contains(.banSendPolls) { + return [.banSendMessages] + } else if right.contains(.banChangeInfo) { + return [] + } else if right.contains(.banAddMembers) { + return [] + } else if right.contains(.banPinMessages) { + return [] + } else { + return [] + } +} + +private func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags { + var result = flags + result.remove(.banReadMessages) + if result.contains(.banSendGifs) { + result.insert(.banSendStickers) + result.insert(.banSendGifs) + result.insert(.banSendGames) + result.insert(.banSendInline) + } else { + result.remove(.banSendStickers) + result.remove(.banSendGifs) + result.remove(.banSendGames) + result.remove(.banSendInline) + } + return result +} + +private func channelPermissionsControllerEntries(view: PeerView, state: ChannelPermissionsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelPermissionsEntry] { + var entries: [ChannelPermissionsEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.section(sectionId)) + sectionId += 1 + + + if let channel = view.peers[view.peerId] as? TelegramChannel, let participants = participants, let cachedData = view.cachedData as? CachedChannelData, let defaultBannedRights = channel.defaultBannedRights { + + + let effectiveRightsFlags: TelegramChatBannedRightsFlags + if let modifiedRightsFlags = state.modifiedRightsFlags { + effectiveRightsFlags = modifiedRightsFlags + } else { + effectiveRightsFlags = defaultBannedRights.flags + } + + + entries.append(.permissionsHeader(sectionId, index, L10n.groupInfoPermissionsSectionTitle, .textTopItem)) + index += 1 + for (i, rights) in allGroupPermissionList.enumerated() { + var enabled: Bool? = true + if channel.addressName != nil && publicGroupRestrictedPermissions.contains(rights) { + enabled = nil + } + entries.append(.permission(sectionId, index, stringForGroupPermission(right: rights), !effectiveRightsFlags.contains(rights), rights, enabled, bestGeneralViewType(allGroupPermissionList, for: i))) + index += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + + entries.append(.slowModeHeader(sectionId, .textTopItem)) + entries.append(.slowMode(sectionId, cachedData.slowModeTimeout, .singleItem)) + entries.append(.slowDesc(sectionId, cachedData.slowModeTimeout, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.kicked(sectionId, index, L10n.groupInfoPermissionsRemoved, cachedData.participantsSummary.kickedCount.flatMap({ "\($0 > 0 ? "\($0)" : "")" }) ?? "", .singleItem)) + index += 1 + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.exceptionsHeader(sectionId, index, L10n.groupInfoPermissionsExceptions, .textTopItem)) + index += 1 + + + + + + entries.append(.add(sectionId, index, L10n.groupInfoPermissionsAddException, participants.isEmpty ? .singleItem : .firstItem)) + index += 1 + for (i, participant) in participants.enumerated() { + entries.append(.peerItem(sectionId, index, participant, ShortPeerDeleting(editable: true), state.removingPeerId != participant.peer.id, true, effectiveRightsFlags, i == 0 ? .innerItem : bestGeneralViewType(participants, for: i))) + index += 1 + } + } else if let group = view.peers[view.peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData, let defaultBannedRights = group.defaultBannedRights { + let effectiveRightsFlags: TelegramChatBannedRightsFlags + if let modifiedRightsFlags = state.modifiedRightsFlags { + effectiveRightsFlags = modifiedRightsFlags + } else { + effectiveRightsFlags = defaultBannedRights.flags + } + + entries.append(.permissionsHeader(sectionId, index, L10n.groupInfoPermissionsSectionTitle, .textTopItem)) + index += 1 + + for (i, rights) in allGroupPermissionList.enumerated() { + entries.append(.permission(sectionId, index, stringForGroupPermission(right: rights), !effectiveRightsFlags.contains(rights), rights, true, bestGeneralViewType(allGroupPermissionList, for: i))) + index += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.slowModeHeader(sectionId, .textTopItem)) + entries.append(.slowMode(sectionId, nil, .singleItem)) + entries.append(.slowDesc(sectionId, nil, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.exceptionsHeader(sectionId, index, L10n.groupInfoPermissionsExceptions, .textTopItem)) + index += 1 + entries.append(.add(sectionId, index, L10n.groupInfoPermissionsAddException, .singleItem)) + index += 1 + + entries.append(.section(sectionId)) + sectionId += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + return entries +} +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ChannelPermissionsControllerArguments) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + + +final class ChannelPermissionsController : TableViewController { + + private let peerId: PeerId + private let disposable = MetaDisposable() + init(_ context: AccountContext, peerId: PeerId) { + self.peerId = peerId + super.init(context) + } + + fileprivate let interfaceFullReady: Promise = Promise() + + deinit { + disposable.dispose() + } + + override func viewDidLoad() { + super.viewDidLoad() + + let peerId = self.peerId + let context = self.context + + let statePromise = ValuePromise(ChannelPermissionsControllerState(), ignoreRepeated: true) + let stateValue = Atomic(value: ChannelPermissionsControllerState()) + let updateState: ((ChannelPermissionsControllerState) -> ChannelPermissionsControllerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + var stopMerging: Bool = false + + let actionsDisposable = DisposableSet() + + let updateBannedDisposable = MetaDisposable() + actionsDisposable.add(updateBannedDisposable) + + let removePeerDisposable = MetaDisposable() + actionsDisposable.add(removePeerDisposable) + + + var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? + + let upgradedToSupergroup: (PeerId, @escaping () -> Void) -> Void = { upgradedPeerId, f in + upgradedToSupergroupImpl?(upgradedPeerId, f) + } + + + let restrict:(ChannelParticipant, Bool) -> Void = { participant, unban in + showModal(with: RestrictedModalViewController(context, peerId: peerId, memberId: participant.peerId, initialParticipant: participant, updated: { updatedRights in + switch participant { + case let .member(memberId, _, _, _, _): + + + let signal: Signal + + if peerId.namespace == Namespaces.Peer.CloudGroup { + stopMerging = true + signal = convertGroupToSupergroup(account: context.account, peerId: peerId) + |> map(Optional.init) + |> mapToSignal { upgradedPeerId -> Signal in + guard let upgradedPeerId = upgradedPeerId else { + return .single(nil) + } + return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: upgradedPeerId, memberId: memberId, bannedRights: updatedRights) + |> castError(ConvertGroupToSupergroupError.self) + |> mapToSignal { _ -> Signal in + return .complete() + } + |> then(.single(upgradedPeerId) |> castError(ConvertGroupToSupergroupError.self)) + } + |> deliverOnMainQueue + } else { + signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: updatedRights) + |> map {_ in return nil} + |> castError(ConvertGroupToSupergroupError.self) + |> deliverOnMainQueue + } + + updateBannedDisposable.set(showModalProgress(signal: signal, for: context.window).start(next: { upgradedPeerId in + if let upgradedPeerId = upgradedPeerId { + upgradedToSupergroup(upgradedPeerId, { + + }) + } + }, error: { error in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + })) + default: + break + } + + + }), for: context.window) + } + + let peersPromise = Promise<[RenderedChannelParticipant]?>(nil) + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.restricted(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + peersPromise.set(.single(state.list)) + }) + actionsDisposable.add(disposable) + + let updateDefaultRightsDisposable = MetaDisposable() + actionsDisposable.add(updateDefaultRightsDisposable) + + let peerView = Promise() + peerView.set(context.account.viewTracker.peerView(peerId)) + + let arguments = ChannelPermissionsControllerArguments(context: context, updatePermission: { rights, value in + let _ = (peerView.get() + |> take(1) + |> deliverOnMainQueue).start(next: { view in + if let channel = view.peers[peerId] as? TelegramChannel, let _ = view.cachedData as? CachedChannelData { + updateState { state in + var state = state + var effectiveRightsFlags: TelegramChatBannedRightsFlags + if let modifiedRightsFlags = state.modifiedRightsFlags { + effectiveRightsFlags = modifiedRightsFlags + } else if let defaultBannedRightsFlags = channel.defaultBannedRights?.flags { + effectiveRightsFlags = defaultBannedRightsFlags + } else { + effectiveRightsFlags = TelegramChatBannedRightsFlags() + } + if value { + effectiveRightsFlags.remove(rights) + effectiveRightsFlags = effectiveRightsFlags.subtracting(groupPermissionDependencies(rights)) + } else { + effectiveRightsFlags.insert(rights) + for right in allGroupPermissionList { + if groupPermissionDependencies(right).contains(rights) { + effectiveRightsFlags.insert(right) + } + } + } + state.modifiedRightsFlags = effectiveRightsFlags + return state + } + let state = stateValue.with { $0 } + if let modifiedRightsFlags = state.modifiedRightsFlags { + updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: context.account, peerId: peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max)) + |> deliverOnMainQueue).start()) + } + } else if let group = view.peers[peerId] as? TelegramGroup, let _ = view.cachedData as? CachedGroupData { + updateState { state in + var state = state + var effectiveRightsFlags: TelegramChatBannedRightsFlags + if let modifiedRightsFlags = state.modifiedRightsFlags { + effectiveRightsFlags = modifiedRightsFlags + } else if let defaultBannedRightsFlags = group.defaultBannedRights?.flags { + effectiveRightsFlags = defaultBannedRightsFlags + } else { + effectiveRightsFlags = TelegramChatBannedRightsFlags() + } + if value { + effectiveRightsFlags.remove(rights) + effectiveRightsFlags = effectiveRightsFlags.subtracting(groupPermissionDependencies(rights)) + } else { + effectiveRightsFlags.insert(rights) + for right in allGroupPermissionList { + if groupPermissionDependencies(right).contains(rights) { + effectiveRightsFlags.insert(right) + } + } + } + state.modifiedRightsFlags = effectiveRightsFlags + return state + } + let state = stateValue.with { $0 } + if let modifiedRightsFlags = state.modifiedRightsFlags { + updateDefaultRightsDisposable.set((updateDefaultChannelMemberBannedRights(account: context.account, peerId: peerId, rights: TelegramChatBannedRights(flags: completeRights(modifiedRightsFlags), untilDate: Int32.max)) + |> deliverOnMainQueue).start()) + } + } + }) + }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in + updateState { state in + var state = state + if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { + state.peerIdWithRevealedOptions = peerId + } + return state + } + }, addPeer: { + let behavior = peerId.namespace == Namespaces.Peer.CloudGroup ? SelectGroupMembersBehavior(peerId: peerId, limit: 1) : SelectChannelMembersBehavior(peerId: peerId, limit: 1) + + _ = (selectModalPeers(context: context, title: L10n.channelBlacklistSelectNewUserTitle, limit: 1, behavior: behavior, confirmation: { peerIds in + if let peerId = peerIds.first { + var adminError:Bool = false + if let participant = behavior.participants[peerId] { + if case let .member(_, _, adminInfo, _, _) = participant.participant { + if let adminInfo = adminInfo { + if !adminInfo.canBeEditedByAccountPeer && adminInfo.promotedBy != context.account.peerId { + adminError = true + } + } + } else { + adminError = true + } + } + if adminError { + alert(for: mainWindow, info: L10n.channelBlacklistDemoteAdminError) + return .single(false) + } + } + return .single(true) + }) |> map {$0.first} |> filter {$0 != nil} |> map {$0!}).start(next: { memberId in + + var participant:RenderedChannelParticipant? + if let p = behavior.participants[memberId] { + participant = p + } else if let temporary = behavior.result[memberId] { + participant = RenderedChannelParticipant(participant: .member(id: memberId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: temporary.peer, peers: [memberId: temporary.peer], presences: temporary.presence != nil ? [memberId: temporary.presence!] : [:]) + } + if let participant = participant { + restrict(participant.participant, false) + } + }) + + }, removePeer: { memberId in + updateState { state in + var state = state + state.removingPeerId = memberId + return state + } + + removePeerDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: nil) + |> deliverOnMainQueue).start(error: { _ in + updateState { state in + var state = state + state.removingPeerId = nil + return state + } + }, completed: { + updateState { state in + var state = state + state.removingPeerId = nil + return state + } + })) + }, openPeer: { participant in + restrict(participant, true) + }, openPeerInfo: { [weak self] peer in + self?.navigationController?.push(PeerInfoController(context: context, peerId: peer.id)) + }, openKicked: { [weak self] in + self?.navigationController?.push(ChannelBlacklistViewController(context, peerId: peerId)) + }, presentRestrictedPublicGroupPermissionsAlert: { + alert(for: mainWindow, info: L10n.groupPermissionNotAvailableInPublicGroups) + }, updateSlowMode: { value in + let signal: Signal + + if peerId.namespace == Namespaces.Peer.CloudGroup { + stopMerging = true + signal = convertGroupToSupergroup(account: context.account, peerId: peerId) + |> map(Optional.init) + |> mapToSignal { upgradedPeerId -> Signal in + guard let upgradedPeerId = upgradedPeerId else { + return .fail(.generic) + } + return updateChannelSlowModeInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: upgradedPeerId, timeout: value) + |> map { _ in return Optional(upgradedPeerId) } + |> mapError { _ in + return ConvertGroupToSupergroupError.generic + } + } + + } else { + signal = updateChannelSlowModeInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: peerId, timeout: value) + |> mapError { _ in return ConvertGroupToSupergroupError.generic } + |> map { _ in return nil } + } + + _ = showModalProgress(signal: signal |> deliverOnMainQueue, for: context.window).start(next: { upgradedPeerId in + if let upgradedPeerId = upgradedPeerId { + upgradedToSupergroup(upgradedPeerId, { + + }) + } + }, error: { error in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }) + + }) + + let previous = Atomic<[AppearanceWrapperEntry]>(value: []) + let initialSize = self.atomicSize + + let signal = combineLatest(queue: .mainQueue(), appearanceSignal, statePromise.get(), peerView.get(), peersPromise.get()) + |> deliverOnMainQueue + |> map { appearance, state, view, participants -> TableUpdateTransition in + let entries = channelPermissionsControllerEntries(view: view, state: state, participants: participants).map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.with { $0 }, arguments: arguments) + } |> afterDisposed { + actionsDisposable.dispose() + } + + interfaceFullReady.set(combineLatest(queue: .mainQueue(), peerView.get(), peersPromise.get()) |> map { view, participants in + return view.cachedData != nil && (participants != nil) + }) + + + upgradedToSupergroupImpl = { [weak self] upgradedPeerId, f in + guard let `self` = self, let navigationController = self.navigationController else { + return + } + + var chatController: ChatController? = ChatController(context: context, chatLocation: .peer(upgradedPeerId)) + + + chatController!.navigationController = navigationController + chatController!.loadViewIfNeeded(navigationController.bounds) + + var signal = chatController!.ready.get() |> filter {$0} |> take(1) |> ignoreValues + + var controller: ChannelPermissionsController? = ChannelPermissionsController(context, peerId: upgradedPeerId) + + controller!.navigationController = navigationController + controller!.loadViewIfNeeded(navigationController.bounds) + + let mainSignal = combineLatest(controller!.ready.get(), controller!.interfaceFullReady.get()) |> map { $0 && $1 } |> filter {$0} |> take(1) |> ignoreValues + + signal = combineLatest(queue: .mainQueue(), signal, mainSignal) |> ignoreValues + + _ = signal.start(completed: { [weak navigationController] in + navigationController?.removeAll() + navigationController?.push(chatController!, false, style: .none) + navigationController?.push(controller!, false, style: .none) + + chatController = nil + controller = nil + }) + + } + + self.disposable.set(signal.start(next: { [weak self] transition in + guard let `self` = self, !stopMerging else { return } + self.genericView.merge(with: transition) + self.readyOnce() + })) + + } +} + diff --git a/Telegram-Mac/ChannelRecentPostRowItem.swift b/Telegram-Mac/ChannelRecentPostRowItem.swift new file mode 100644 index 0000000000..d5361888d3 --- /dev/null +++ b/Telegram-Mac/ChannelRecentPostRowItem.swift @@ -0,0 +1,216 @@ +// +// ChannelRecentPostRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 12.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + +class ChannelRecentPostRowItem: GeneralRowItem { + fileprivate let viewsCountLayout: TextViewLayout + fileprivate let sharesCountLayout: TextViewLayout + fileprivate let titleLayout: TextViewLayout + fileprivate let dateLayout: TextViewLayout + fileprivate let message: Message + fileprivate let contentImageMedia: TelegramMediaImage? + fileprivate let context: AccountContext + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, message: Message, interactions: ChannelStatsMessageInteractions?, viewType: GeneralViewType, action: @escaping()->Void) { + self.context = context + self.message = message + var contentImageMedia: TelegramMediaImage? + for media in message.media { + if let image = media as? TelegramMediaImage { + contentImageMedia = image + break + } else if let file = media as? TelegramMediaFile { + if file.isVideo && !file.isInstantVideo { + let iconImageRepresentation:TelegramMediaImageRepresentation? = smallestImageRepresentation(file.previewRepresentations) + if let iconImageRepresentation = iconImageRepresentation { + contentImageMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } + break + } + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let image = content.image { + contentImageMedia = image + break + } else if let file = content.file { + if file.isVideo && !file.isInstantVideo { + let iconImageRepresentation:TelegramMediaImageRepresentation? = smallestImageRepresentation(file.previewRepresentations) + if let iconImageRepresentation = iconImageRepresentation { + contentImageMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } + break + } + } + } + } + self.contentImageMedia = contentImageMedia + + self.titleLayout = TextViewLayout(NSAttributedString.initialize(string: pullText(from: message) as String, color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1) + self.dateLayout = TextViewLayout(NSAttributedString.initialize(string: stringForFullDate(timestamp: message.timestamp), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + + + let views = Int(max(message.channelViewsCount ?? 0, interactions?.views ?? 0)) + let shares = Int(interactions?.forwards ?? 0) + + let viewsString = L10n.channelStatsViewsCountCountable(views).replacingOccurrences(of: "\(views)", with: views.formattedWithSeparator) + let sharesString = L10n.channelStatsSharesCountCountable(shares).replacingOccurrences(of: "\(shares)", with: shares.formattedWithSeparator) + + viewsCountLayout = TextViewLayout(NSAttributedString.initialize(string: viewsString, color: theme.colors.text, font: .normal(.short)),maximumNumberOfLines: 1) + sharesCountLayout = TextViewLayout(NSAttributedString.initialize(string: sharesString, color: theme.colors.grayText, font: .normal(.short)),maximumNumberOfLines: 1) + + super.init(initialSize, height: 46, stableId: stableId, viewType: viewType, action: action) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + viewsCountLayout.measure(width: .greatestFiniteMagnitude) + sharesCountLayout.measure(width: .greatestFiniteMagnitude) + + let titleAndDateWidth: CGFloat = blockWidth - viewType.innerInset.left - (contentImageMedia != nil ? 34 + 10 : 0) - max(viewsCountLayout.layoutSize.width, sharesCountLayout.layoutSize.width) - 10 - viewType.innerInset.right + + titleLayout.measure(width: titleAndDateWidth) + dateLayout.measure(width: titleAndDateWidth) + + return true + } + + override func viewClass() -> AnyClass { + return ChannelRecentPostRowView.self + } +} + + +private final class ChannelRecentPostRowView : GeneralContainableRowView { + private let viewCountView = TextView() + private let sharesCountView = TextView() + private let titleView = TextView() + private let dateView = TextView() + private let fetchDisposable = MetaDisposable() + private var imageView: TransformImageView? + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(viewCountView) + addSubview(sharesCountView) + addSubview(titleView) + addSubview(dateView) + + sharesCountView.userInteractionEnabled = false + sharesCountView.isSelectable = false + sharesCountView.isEventLess = true + + titleView.userInteractionEnabled = false + titleView.isSelectable = false + titleView.isEventLess = true + + viewCountView.userInteractionEnabled = false + viewCountView.isSelectable = false + viewCountView.isEventLess = true + + dateView.userInteractionEnabled = false + dateView.isSelectable = false + dateView.isEventLess = true + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Highlight) + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Normal) + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Hover) + + containerView.set(handler: { [weak self] _ in + guard let item = self?.item as? ChannelRecentPostRowItem else { + return + } + item.action() + }, for: .Click) + } + + override func layout() { + super.layout() + + guard let item = item as? ChannelRecentPostRowItem else { + return + } + + viewCountView.setFrameOrigin(NSMakePoint(item.blockWidth - viewCountView.frame.width - item.viewType.innerInset.right, 5)) + sharesCountView.setFrameOrigin(NSMakePoint(item.blockWidth - sharesCountView.frame.width - item.viewType.innerInset.right, containerView.frame.height - sharesCountView.frame.height - 5)) + + let leftOffset: CGFloat = (imageView != nil ? 34 + 10 : 0) + item.viewType.innerInset.left + + titleView.setFrameOrigin(NSMakePoint(leftOffset, 5)) + dateView.setFrameOrigin(NSMakePoint(leftOffset, containerView.frame.height - dateView.frame.height - 5)) + + imageView?.centerY(x: item.viewType.innerInset.left) + } + + override var backdorColor: NSColor { + return isSelect ? theme.colors.accentSelect : theme.colors.background + } + + override func updateColors() { + super.updateColors() + if let item = item as? GeneralRowItem { + self.background = item.viewType.rowBackground + let highlighted = isSelect ? self.backdorColor : theme.colors.grayHighlight + titleView.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + dateView.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + viewCountView.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + sharesCountView.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + containerView.set(background: self.backdorColor, for: .Normal) + containerView.set(background: highlighted, for: .Highlight) + } + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? ChannelRecentPostRowItem else { + return + } + + viewCountView.update(item.viewsCountLayout) + sharesCountView.update(item.sharesCountLayout) + dateView.update(item.dateLayout) + titleView.update(item.titleLayout) + + + if let media = item.contentImageMedia { + if imageView == nil { + self.imageView = TransformImageView(frame: NSMakeRect(0, 0, 34, 34)) + imageView?.set(arguments: TransformImageArguments(corners: .init(radius: 4), imageSize: NSMakeSize(34, 34), boundingSize: NSMakeSize(34, 34), intrinsicInsets: NSEdgeInsets())) + addSubview(self.imageView!) + } + let updateIconImageSignal = chatWebpageSnippetPhoto(account: item.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: media), scale: backingScaleFactor, small:true) + imageView?.setSignal(updateIconImageSignal) + + fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: media)).start()) + + } else { + imageView?.removeFromSuperview() + imageView = nil + fetchDisposable.set(nil) + } + } + + deinit { + fetchDisposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChannelStatisticsController.swift b/Telegram-Mac/ChannelStatisticsController.swift new file mode 100644 index 0000000000..22248b0ac4 --- /dev/null +++ b/Telegram-Mac/ChannelStatisticsController.swift @@ -0,0 +1,75 @@ +// +// ChannelStatisticsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 22/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import WebKit + + + +class ChannelStatisticsController: TelegramGenericViewController { + private let peerId:PeerId + + private let uniqueId:String = "_\(arc4random())" + private let disposable = MetaDisposable() + private let statsUrl: String + init(_ context: AccountContext, _ peerId:PeerId, statsUrl: String) { + self.peerId = peerId + self.statsUrl = statsUrl + super.init(context) + load(with: statsUrl) + } + + override var enableBack: Bool { + return true + } + + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + genericView.mainFrame.load(URLRequest(url: URL(string:"file://blank")!)) + genericView.mainFrame.stopLoading() + } + + override func viewDidLoad() { + super.viewDidLoad() + genericView.wantsLayer = true + readyOnce() + } + + private func load(with url: String) { + + if let url = URL(string:url) { + genericView.mainFrame.load(URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 15)) + } + + } + + + + override func becomeFirstResponder() -> Bool? { + return true + } + + override func firstResponder() -> NSResponder? { + return genericView + } + + override func backKeyAction() -> KeyHandlerResult { + return .invokeNext + } + + deinit { + disposable.dispose() + } + +} diff --git a/Telegram-Mac/ChannelStatsViewController.swift b/Telegram-Mac/ChannelStatsViewController.swift new file mode 100644 index 0000000000..41998d0ee7 --- /dev/null +++ b/Telegram-Mac/ChannelStatsViewController.swift @@ -0,0 +1,298 @@ +// +// ChannelStatsViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import Postbox +import SyncCore +import GraphCore + + + +struct UIStatsState : Equatable { + + enum RevealSection : Hashable { + case topPosters + case topAdmins + case topInviters + + var id: InputDataIdentifier { + switch self { + case .topPosters: + return InputDataIdentifier("_id_top_posters") + case .topAdmins: + return InputDataIdentifier("_id_top_admins") + case .topInviters: + return InputDataIdentifier("_id_top_inviters") + } + } + } + + let loading: Set + let revealed:Set + init(loading: Set, revealed: Set = Set()) { + self.loading = loading + self.revealed = revealed + } + func withAddedLoading(_ token: InputDataIdentifier) -> UIStatsState { + var loading = self.loading + loading.insert(token) + return UIStatsState(loading: loading, revealed: self.revealed) + } + func withRemovedLoading(_ token: InputDataIdentifier) -> UIStatsState { + var loading = self.loading + loading.remove(token) + return UIStatsState(loading: loading, revealed: self.revealed) + } + + func withRevealedSection(_ section: RevealSection) -> UIStatsState { + var revealed = self.revealed + revealed.insert(section) + return UIStatsState(loading: self.loading, revealed: revealed) + } +} + +private func _id_message(_ messageId: MessageId) -> InputDataIdentifier { + return InputDataIdentifier("_id_message_\(messageId)") +} + +private func statsEntries(_ state: ChannelStatsContextState, uiState: UIStatsState, messages: [Message]?, interactions: [MessageId : ChannelStatsMessageInteractions]?, updateIsLoading: @escaping(InputDataIdentifier, Bool)->Void, openMessage: @escaping(MessageId)->Void, context: ChannelStatsContext, accountContext: AccountContext, detailedDisposable: DisposableDict) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + + if state.stats == nil { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("loading"), equatable: nil, item: { initialSize, stableId in + return StatisticsLoadingRowItem(initialSize, stableId: stableId, context: accountContext, text: L10n.channelStatsLoading) + })) + } else if let stats = state.stats { + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.channelStatsOverview), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + var overviewItems:[ChannelOverviewItem] = [] + + if stats.followers.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.channelStatsOverviewFollowers, value: stats.followers.attributedString)) + } + if stats.enabledNotifications.total != 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.channelStatsOverviewEnabledNotifications, value: stats.enabledNotifications.attributedString)) + } + if stats.viewsPerPost.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.channelStatsOverviewViewsPerPost, value: stats.viewsPerPost.attributedString)) + } + if stats.sharesPerPost.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.channelStatsOverviewSharesPerPost, value: stats.sharesPerPost.attributedString)) + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("overview"), equatable: InputDataEquatable(overviewItems), item: { initialSize, stableId in + return ChannelOverviewStatsRowItem(initialSize, stableId: stableId, items: overviewItems, viewType: .singleItem) + })) + index += 1 + + + struct Graph { + let graph: StatsGraph + let title: String + let identifier: InputDataIdentifier + let type: ChartItemType + let load:(InputDataIdentifier)->Void + } + + var graphs: [Graph] = [] + graphs.append(Graph(graph: stats.growthGraph, title: L10n.channelStatsGraphGrowth, identifier: InputDataIdentifier("growthGraph"), type: .lines, load: { identifier in + context.loadGrowthGraph() + updateIsLoading(identifier, true) + })) + graphs.append(Graph(graph: stats.followersGraph, title: L10n.channelStatsGraphFollowers, identifier: InputDataIdentifier("followersGraph"), type: .lines, load: { identifier in + context.loadFollowersGraph() + updateIsLoading(identifier, true) + })) + + graphs.append(Graph(graph: stats.muteGraph, title: L10n.channelStatsGraphNotifications, identifier: InputDataIdentifier("muteGraph"), type: .lines, load: { identifier in + context.loadMuteGraph() + updateIsLoading(identifier, true) + })) + + graphs.append(Graph(graph: stats.topHoursGraph, title: L10n.channelStatsGraphViewsByHours, identifier: InputDataIdentifier("topHoursGraph"), type: .hourlyStep, load: { identifier in + context.loadTopHoursGraph() + updateIsLoading(identifier, true) + })) + + graphs.append(Graph(graph: stats.viewsBySourceGraph, title: L10n.channelStatsGraphViewsBySource, identifier: InputDataIdentifier("viewsBySourceGraph"), type: .bars, load: { identifier in + context.loadViewsBySourceGraph() + updateIsLoading(identifier, true) + })) + + + graphs.append(Graph(graph: stats.newFollowersBySourceGraph, title: L10n.channelStatsGraphNewFollowersBySource, identifier: InputDataIdentifier("newFollowersBySourceGraph"), type: .bars, load: { identifier in + context.loadNewFollowersBySourceGraph() + updateIsLoading(identifier, true) + })) + graphs.append(Graph(graph: stats.languagesGraph, title: L10n.channelStatsGraphLanguage, identifier: InputDataIdentifier("languagesGraph"), type: .pie, load: { identifier in + context.loadLanguagesGraph() + updateIsLoading(identifier, true) + })) + + + + graphs.append(Graph(graph: stats.interactionsGraph, title: L10n.channelStatsGraphInteractions, identifier: InputDataIdentifier("interactionsGraph"), type: .twoAxisStep, load: { identifier in + context.loadInteractionsGraph() + updateIsLoading(identifier, true) + })) + + for graph in graphs { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(graph.title), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + switch graph.graph { + case let .Loaded(_, string): + ChartsDataManager.readChart(data: string.data(using: .utf8)!, sync: true, success: { collection in + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticRowItem(initialSize, stableId: stableId, context: accountContext, collection: collection, viewType: .singleItem, type: graph.type, getDetailsData: { date, completion in + detailedDisposable.set(context.loadDetailedGraph(graph.graph, x: Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in + if let graph = graph, case let .Loaded(_, data) = graph { + completion(data) + } + }), forKey: graph.identifier) + }) + })) + }, failure: { error in + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: error.localizedDescription) + })) + }) + + updateIsLoading(graph.identifier, false) + + index += 1 + case .OnDemand: + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: nil) + })) + index += 1 + if !uiState.loading.contains(graph.identifier) { + graph.load(graph.identifier) + } + case let .Failed(error): + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: error) + })) + index += 1 + updateIsLoading(graph.identifier, false) + case .Empty: + break + } + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + if let messages = messages, let interactions = interactions, !messages.isEmpty { + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.channelStatsRecentHeader), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + for (i, message) in messages.enumerated() { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_message(message.id), equatable: InputDataEquatable(message), item: { initialSize, stableId in + return ChannelRecentPostRowItem(initialSize, stableId: stableId, context: accountContext, message: message, interactions: interactions[message.id], viewType: bestGeneralViewType(messages, for: i), action: { + openMessage(message.id) + }) + })) + index += 1 + } + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + } + + + return entries +} + + +func ChannelStatsViewController(_ context: AccountContext, peerId: PeerId, datacenterId: Int32) -> ViewController { + + let initialState = UIStatsState(loading: []) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((UIStatsState) -> UIStatsState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + let statsContext = ChannelStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId) + + + let messagesPromise = Promise(nil) + + + let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil) + |> map { messageHistoryView, _, _ -> MessageHistoryView? in + return messageHistoryView + } + messagesPromise.set(.single(nil) |> then(messageView)) + + let openMessage: (MessageId)->Void = { messageId in + context.sharedContext.bindings.rootNavigation().push(ChatAdditionController(context: context, chatLocation: .peer(peerId), messageId: messageId)) + } + + let detailedDisposable = DisposableDict() + + let signal = combineLatest(queue: prepareQueue, statePromise.get(), statsContext.state, messagesPromise.get()) |> map { uiState, state, messageView in + + + let interactions = state.stats?.messageInteractions.reduce([MessageId : ChannelStatsMessageInteractions]()) { (map, interactions) -> [MessageId : ChannelStatsMessageInteractions] in + var map = map + map[interactions.messageId] = interactions + return map + } + + let messages = messageView?.entries.map { $0.message }.filter { interactions?[$0.id] != nil }.sorted(by: { (lhsMessage, rhsMessage) -> Bool in + return lhsMessage.timestamp > rhsMessage.timestamp + }) + + + + return statsEntries(state, uiState: uiState, messages: messages, interactions: interactions, updateIsLoading: { identifier, isLoading in + updateState { state in + if isLoading { + return state.withAddedLoading(identifier) + } else { + return state.withRemovedLoading(identifier) + } + } + }, openMessage: openMessage, context: statsContext, accountContext: context, detailedDisposable: detailedDisposable) + } |> map { + return InputDataSignalValue(entries: $0) + } + + + let controller = InputDataController(dataSignal: signal, title: L10n.channelStatsTitle, removeAfterDisappear: false, hasDone: false) + + controller.contextOject = statsContext + controller.didLoaded = { controller, _ in + controller.tableView.alwaysOpenRowsOnMouseUp = true + controller.tableView.needUpdateVisibleAfterScroll = true + } + + controller.onDeinit = { + detailedDisposable.dispose() + } + + return controller +} diff --git a/Telegram-Mac/ChannelVisibilityController.swift b/Telegram-Mac/ChannelVisibilityController.swift index fa77617362..813934c2b2 100644 --- a/Telegram-Mac/ChannelVisibilityController.swift +++ b/Telegram-Mac/ChannelVisibilityController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private enum CurrentChannelType { case publicChannel @@ -18,15 +19,15 @@ private enum CurrentChannelType { } private final class ChannelVisibilityControllerArguments { - let account: Account + let context: AccountContext let updateCurrentType: (CurrentChannelType) -> Void let updatePublicLinkText: (String?, String) -> Void let displayPrivateLinkMenu: (String) -> Void let revokePeerId: (PeerId) -> Void - init(account: Account, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, revokePeerId: @escaping (PeerId) -> Void) { - self.account = account + init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, revokePeerId: @escaping (PeerId) -> Void) { + self.context = context self.updateCurrentType = updateCurrentType self.updatePublicLinkText = updatePublicLinkText self.displayPrivateLinkMenu = displayPrivateLinkMenu @@ -38,49 +39,23 @@ private final class ChannelVisibilityControllerArguments { fileprivate enum ChannelVisibilityEntryStableId: Hashable { case index(Int32) case peer(PeerId) - - var hashValue: Int { - switch self { - case let .index(index): - return index.hashValue - case let .peer(peerId): - return peerId.hashValue - } - } - - static func ==(lhs: ChannelVisibilityEntryStableId, rhs: ChannelVisibilityEntryStableId) -> Bool { - switch lhs { - case let .index(index): - if case .index(index) = rhs { - return true - } else { - return false - } - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - } - } } -private enum ChannelVisibilityEntry: Identifiable, Comparable { - case typeHeader(sectionId:Int32, String) - case typePublic(sectionId:Int32, Bool) - case typePrivate(sectionId:Int32, Bool) - case typeInfo(sectionId:Int32, String) - - case publicLinkAvailability(sectionId:Int32, Bool) - case privateLink(sectionId:Int32, String?) - case editablePublicLink(sectionId:Int32, String?, String, AddressNameValidationStatus?) - case privateLinkInfo(sectionId:Int32, String) - case publicLinkInfo(sectionId:Int32, String) - case publicLinkStatus(sectionId:Int32, String, AddressNameValidationStatus) - - case existingLinksInfo(sectionId:Int32, String) - case existingLinkPeerItem(sectionId:Int32, Int32, Peer, ShortPeerDeleting?, Bool) +private enum ChannelVisibilityEntry: TableItemListNodeEntry { + case typeHeader(sectionId:Int32, String, GeneralViewType) + case typePublic(sectionId:Int32, Bool, GeneralViewType) + case typePrivate(sectionId:Int32, Bool, GeneralViewType) + case typeInfo(sectionId:Int32, String, GeneralViewType) + + case publicLinkAvailability(sectionId:Int32, Bool, GeneralViewType) + case privateLink(sectionId:Int32, String?, GeneralViewType) + case editablePublicLink(sectionId:Int32, String?, String, AddressNameValidationStatus?, GeneralViewType) + case privateLinkInfo(sectionId:Int32, String, GeneralViewType) + case publicLinkInfo(sectionId:Int32, String, GeneralViewType) + case publicLinkStatus(sectionId:Int32, String, AddressNameValidationStatus, GeneralViewType) + + case existingLinksInfo(sectionId:Int32, String, GeneralViewType) + case existingLinkPeerItem(sectionId:Int32, Int32, Peer, ShortPeerDeleting?, Bool, GeneralViewType) case section(sectionId:Int32) @@ -108,101 +83,91 @@ private enum ChannelVisibilityEntry: Identifiable, Comparable { return .index(9) case .existingLinksInfo: return .index(10) - case let .existingLinkPeerItem(_,_, peer, _, _): + case let .existingLinkPeerItem(_,_, peer, _, _, _): return .peer(peer.id) case let .section(sectionId: sectionId): return .index((sectionId + 1) * 1000 - sectionId) } } - static func ==(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool { switch lhs { - case let .typeHeader(_, title): - if case .typeHeader(_, title) = rhs { + case let .typeHeader(sectionId, title, viewType): + if case .typeHeader(sectionId, title, viewType) = rhs { return true } else { return false } - case let .typePublic(_, selected): - if case .typePublic(_, selected) = rhs { + case let .typePublic(sectionId, selected, viewType): + if case .typePublic(sectionId, selected, viewType) = rhs { return true } else { return false } - case let .typePrivate(_, selected): - if case .typePrivate(_, selected) = rhs { + case let .typePrivate(sectionId, selected, viewType): + if case .typePrivate(sectionId, selected, viewType) = rhs { return true } else { return false } - case let .typeInfo(_, text): - if case .typeInfo(_, text) = rhs { + case let .typeInfo(sectionId, text, viewType): + if case .typeInfo(sectionId, text, viewType) = rhs { return true } else { return false } - case let .publicLinkAvailability(_, value): - if case .publicLinkAvailability(_, value) = rhs { + case let .publicLinkAvailability(sectionId, value, viewType): + if case .publicLinkAvailability(sectionId, value, viewType) = rhs { return true } else { return false } - case let .privateLink(_, lhsLink): - if case let .privateLink(_, rhsLink) = rhs, lhsLink == rhsLink { + case let .privateLink(sectionId, link, viewType): + if case .privateLink(sectionId, link, viewType) = rhs { return true } else { return false } - case let .editablePublicLink(_, lhsCurrentText, lhsText, lhsStatus): - if case let .editablePublicLink(_, rhsCurrentText, rhsText, rhsStatus) = rhs, lhsCurrentText == rhsCurrentText, lhsText == rhsText, lhsStatus == rhsStatus { + case let .editablePublicLink(sectionId, currenttext, text, status, viewType): + if case .editablePublicLink(sectionId, currenttext, text, status, viewType) = rhs { return true } else { return false } - case let .privateLinkInfo(_, text): - if case .privateLinkInfo(_, text) = rhs { + case let .privateLinkInfo(sectionId, text, viewType): + if case .privateLinkInfo(sectionId, text, viewType) = rhs { return true } else { return false } - case let .publicLinkInfo(_, text): - if case .publicLinkInfo(_, text) = rhs { + case let .publicLinkInfo(sectionId, text, viewType): + if case .publicLinkInfo(sectionId, text, viewType) = rhs { return true } else { return false } - case let .publicLinkStatus(_, addressName, status): - if case .publicLinkStatus(_, addressName, status) = rhs { + case let .publicLinkStatus(sectionId, addressName, status, viewType): + if case .publicLinkStatus(sectionId, addressName, status, viewType) = rhs { return true } else { return false } - case let .existingLinksInfo(_, text): - if case .existingLinksInfo(_, text) = rhs { + case let .existingLinksInfo(sectionId, text, viewType): + if case .existingLinksInfo(sectionId, text, viewType) = rhs { return true } else { return false } - case let .existingLinkPeerItem(_, lhsIndex, lhsPeer, lhsEditing, lhsEnabled): - if case let .existingLinkPeerItem(_, rhsIndex, rhsPeer, rhsEditing, rhsEnabled) = rhs { - if lhsIndex != rhsIndex { - return false - } + case let .existingLinkPeerItem(sectionId, index, lhsPeer, editing, enabled, viewType): + if case .existingLinkPeerItem(sectionId, index, let rhsPeer, editing, enabled, viewType) = rhs { if !lhsPeer.isEqual(rhsPeer) { return false } - if lhsEditing != rhsEditing { - return false - } - if lhsEnabled != rhsEnabled { - return false - } return true } else { return false } - case let .section(sectionId: sectionId): - if case .section(sectionId: sectionId) = rhs { + case let .section(sectionId): + if case .section(sectionId) = rhs { return true } else { return false @@ -212,29 +177,29 @@ private enum ChannelVisibilityEntry: Identifiable, Comparable { var index: Int32 { switch self { - case let .typeHeader(sectionId: sectionId, _): + case let .typeHeader(sectionId: sectionId, _, _): return (sectionId * 1000) + 0 - case let .typePublic(sectionId: sectionId, _): + case let .typePublic(sectionId: sectionId, _, _): return (sectionId * 1000) + 1 - case let .typePrivate(sectionId: sectionId, _): + case let .typePrivate(sectionId: sectionId, _, _): return (sectionId * 1000) + 2 - case let .typeInfo(sectionId: sectionId, _): + case let .typeInfo(sectionId: sectionId, _, _): return (sectionId * 1000) + 3 - case let .publicLinkAvailability(sectionId: sectionId, _): + case let .publicLinkAvailability(sectionId: sectionId, _, _): return (sectionId * 1000) + 4 - case let .privateLink(sectionId: sectionId, _): + case let .privateLink(sectionId: sectionId, _, _): return (sectionId * 1000) + 5 - case let .editablePublicLink(sectionId: sectionId, _, _, _): + case let .editablePublicLink(sectionId: sectionId, _, _, _, _): return (sectionId * 1000) + 6 - case let .privateLinkInfo(sectionId: sectionId, _): + case let .privateLinkInfo(sectionId: sectionId, _, _): return (sectionId * 1000) + 7 - case let .publicLinkStatus(sectionId: sectionId, _, _): + case let .publicLinkStatus(sectionId: sectionId, _, _, _): return (sectionId * 1000) + 8 - case let .publicLinkInfo(sectionId: sectionId, _): + case let .publicLinkInfo(sectionId: sectionId, _, _): return (sectionId * 1000) + 9 - case let .existingLinksInfo(sectionId: sectionId, _): + case let .existingLinksInfo(sectionId: sectionId, _, _): return (sectionId * 1000) + 10 - case let .existingLinkPeerItem(sectionId, index, _, _, _): + case let .existingLinkPeerItem(sectionId, index, _, _, _, _): return (sectionId * 1000) + index + 20 case let .section(sectionId: sectionId): return (sectionId + 1) * 1000 - sectionId @@ -247,57 +212,74 @@ private enum ChannelVisibilityEntry: Identifiable, Comparable { func item(_ arguments: ChannelVisibilityControllerArguments, initialSize:NSSize) -> TableRowItem { switch self { - case let .typeHeader(_, title): - return GeneralTextRowItem(initialSize, stableId: stableId, text: title) - case let .typePublic(_, selected): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelPublic), type: .selectable(stateback: { return selected}), action: { + case let .typeHeader(_, title, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: title, viewType: viewType) + case let .typePublic(_, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.channelPublic, type: .selectable(selected), viewType: viewType, action: { arguments.updateCurrentType(.publicChannel) }) - case let .typePrivate(_, selected): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelPrivate), type: .selectable(stateback: { return selected}), action: { + case let .typePrivate(_, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.channelPrivate, type: .selectable(selected), viewType: viewType, action: { arguments.updateCurrentType(.privateChannel) }) - case let .typeInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .publicLinkAvailability(_, value): + case let .typeInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .publicLinkAvailability(_, value, viewType): + let color: NSColor + let text: String if value { - return GeneralTextRowItem(initialSize, stableId: stableId, text: .initialize(string: tr(.channelVisibilityChecking), color: theme.colors.redUI, font:.normal(.text))) + text = L10n.channelVisibilityChecking + color = theme.colors.grayText } else { - return GeneralTextRowItem(initialSize, stableId: stableId, text: NSAttributedString.initialize(string: tr(.channelPublicNamesLimitError), color: theme.colors.redUI, font:.normal(.text))) + text = L10n.channelPublicNamesLimitError + color = theme.colors.redUI } - case let .privateLink(_, link): + return GeneralTextRowItem(initialSize, stableId: stableId, text: .initialize(string: text, color: color, font: .normal(.text)), viewType: viewType) + + case let .privateLink(_, link, viewType): let color:NSColor if let _ = link { color = theme.colors.link } else { color = theme.colors.grayText } - return GeneralTextRowItem(initialSize, stableId: stableId, text: .initialize(string:link ?? tr(.channelVisibilityLoading), color: color, font:.normal(.text)), drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:5, bottom:8), action: { + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: link ?? L10n.channelVisibilityLoading, nameStyle: ControlStyle(font: .normal(.text), foregroundColor: color), type: .none, viewType: viewType, action: { if let link = link { - arguments.displayPrivateLinkMenu(link) + arguments.context.sharedContext.bindings.showControllerToaster(ControllerToaster(text: L10n.shareLinkCopied), true) + copyToClipboard(link) } }) - case let .editablePublicLink(_, currentText, text, status): - return UsernameInputRowItem(initialSize, stableId: stableId, placeholder: "t.me/", limit: 30, status: status, text: text, changeHandler: { updatedText in + case let .editablePublicLink(_, currentText, text, status, viewType): + var rightItem: InputDataRightItem? = nil + if let status = status { + switch status { + case .checking: + rightItem = .loading + default: + break + } + } + return InputDataRowItem(initialSize, stableId: stableId, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: "t.me", defaultText:"https://t.me/", rightItem: rightItem, filter: { $0 }, updated: { updatedText in arguments.updatePublicLinkText(currentText, updatedText) - }, holdText:true) - case let .privateLinkInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .publicLinkInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .publicLinkStatus(_, addressName, status): + }, limit: 30) + case let .privateLinkInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .publicLinkInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .publicLinkStatus(_, addressName, status, viewType): var text:String = "" var color:NSColor = .text + switch status { case let .invalidFormat(format): text = format.description color = theme.colors.redUI case let .availability(availability): - text = availability.description + text = availability.description(for: addressName) switch availability { case .available: - color = theme.colors.blueUI + color = theme.colors.accent default: color = theme.colors.redUI } @@ -305,15 +287,15 @@ private enum ChannelVisibilityEntry: Identifiable, Comparable { break } - return GeneralTextRowItem(initialSize, stableId: stableId, text: NSAttributedString.initialize(string: text, color: color, font: .normal(.text)), alignment: .left, inset:NSEdgeInsets(left: 30.0, right: 30.0, top:6, bottom:4)) - case let .existingLinksInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .existingLinkPeerItem(_, _, peer, _, _): - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, status:"t.me/\(peer.addressName ?? "unknown")", inset:NSEdgeInsets(left: 30, right:30), interactionType:.deletable(onRemove:{ peerId in + return GeneralTextRowItem(initialSize, stableId: stableId, text: NSAttributedString.initialize(string: text, color: color, font: .normal(.text)), viewType: viewType) + case let .existingLinksInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .existingLinkPeerItem(_, _, peer, _, _, viewType): + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, status: "t.me/\(peer.addressName ?? "unknown")", inset: NSEdgeInsets(left: 30, right:30), interactionType:.deletable(onRemove: { peerId in arguments.revokePeerId(peerId) - }, deletable: true)) + }, deletable: true), viewType: viewType) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } @@ -343,26 +325,6 @@ private struct ChannelVisibilityControllerState: Equatable { self.revokingPeerId = revokingPeerId } - static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool { - if lhs.selectedType != rhs.selectedType { - return false - } - if lhs.editingPublicLinkText != rhs.editingPublicLinkText { - return false - } - if lhs.addressNameValidationStatus != rhs.addressNameValidationStatus { - return false - } - if lhs.updatingAddressName != rhs.updatingAddressName { - return false - } - if lhs.revokingPeerId != rhs.revokingPeerId { - return false - } - - return true - } - func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState { return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revokingPeerId: self.revokingPeerId) } @@ -424,25 +386,102 @@ private func channelVisibilityControllerEntries(view: PeerView, publicChannelsTo } } - entries.append(.typeHeader(sectionId: sectionId, isGroup ? tr(.channelTypeHeaderGroup) : tr(.channelTypeHeaderChannel))) - entries.append(.typePublic(sectionId: sectionId, selectedType == .publicChannel)) - entries.append(.typePrivate(sectionId: sectionId, selectedType == .privateChannel)) + entries.append(.typeHeader(sectionId: sectionId, isGroup ? L10n.channelTypeHeaderGroup : L10n.channelTypeHeaderChannel, .textTopItem)) + entries.append(.typePublic(sectionId: sectionId, selectedType == .publicChannel, .firstItem)) + entries.append(.typePrivate(sectionId: sectionId, selectedType == .privateChannel, .lastItem)) + + switch selectedType { + case .publicChannel: + entries.append(.typeInfo(sectionId: sectionId, isGroup ? L10n.channelPublicAboutGroup : L10n.channelPublicAboutChannel, .textBottomItem)) + case .privateChannel: + entries.append(.typeInfo(sectionId: sectionId, isGroup ? L10n.channelPrivateAboutGroup : L10n.channelPrivateAboutChannel, .textBottomItem)) + } + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 switch selectedType { case .publicChannel: - if isGroup { - entries.append(.typeInfo(sectionId: sectionId, tr(.channelPublicAboutGroup))) + var displayAvailability = false + if peer.addressName == nil { + displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty) + } + if displayAvailability { + if let publicChannelsToRevoke = publicChannelsToRevoke { + entries.append(.publicLinkAvailability(sectionId: sectionId, false, .textTopItem)) + var index: Int32 = 0 + + let sorted = publicChannelsToRevoke.sorted(by: { lhs, rhs in + var lhsDate: Int32 = 0 + var rhsDate: Int32 = 0 + if let lhs = lhs as? TelegramChannel { + lhsDate = lhs.creationDate + } + if let rhs = rhs as? TelegramChannel { + rhsDate = rhs.creationDate + } + return lhsDate > rhsDate + }) + + for (i, peer) in sorted.enumerated() { + entries.append(.existingLinkPeerItem(sectionId: sectionId, index, peer, nil, state.revokingPeerId == nil, bestGeneralViewType(sorted, for: i))) + index += 1 + } + } else { + entries.append(.publicLinkAvailability(sectionId: sectionId, true, .singleItem)) + } } else { - entries.append(.typeInfo(sectionId: sectionId, tr(.channelPublicAboutChannel))) + entries.append(.editablePublicLink(sectionId: sectionId, peer.addressName, currentAddressName, state.addressNameValidationStatus, .singleItem)) + if let status = state.addressNameValidationStatus { + switch status { + case .invalidFormat, .availability: + entries.append(.publicLinkStatus(sectionId: sectionId, currentAddressName, status, .textBottomItem)) + default: + break + } + } + entries.append(.publicLinkInfo(sectionId: sectionId, isGroup ? L10n.channelUsernameAboutGroup : L10n.channelUsernameAboutChannel, .textBottomItem)) } case .privateChannel: - if isGroup { - entries.append(.typeInfo(sectionId: sectionId, tr(.channelPrivateAboutGroup))) + entries.append(.privateLink(sectionId: sectionId, (view.cachedData as? CachedChannelData)?.exportedInvitation?.link, .singleItem)) + entries.append(.publicLinkInfo(sectionId: sectionId, isGroup ? L10n.channelExportLinkAboutGroup : L10n.channelExportLinkAboutChannel, .textBottomItem)) + } + } else if let peer = view.peers[view.peerId] as? TelegramGroup { + + let selectedType: CurrentChannelType + if let current = state.selectedType { + selectedType = current + } else { + if let addressName = peer.addressName, !addressName.isEmpty { + selectedType = .publicChannel + } else { + selectedType = .privateChannel + } + } + + let currentAddressName: String + if let current = state.editingPublicLinkText { + currentAddressName = current + } else { + if let addressName = peer.addressName { + currentAddressName = addressName } else { - entries.append(.typeInfo(sectionId: sectionId, tr(.channelPrivateAboutChannel))) + currentAddressName = "" } } + entries.append(.typeHeader(sectionId: sectionId, L10n.channelTypeHeaderGroup, .textTopItem)) + entries.append(.typePublic(sectionId: sectionId, selectedType == .publicChannel, .firstItem)) + entries.append(.typePrivate(sectionId: sectionId, selectedType == .privateChannel, .lastItem)) + + switch selectedType { + case .publicChannel: + entries.append(.typeInfo(sectionId: sectionId, L10n.channelPublicAboutGroup, .textBottomItem)) + + case .privateChannel: + entries.append(.typeInfo(sectionId: sectionId, L10n.channelPrivateAboutGroup, .textBottomItem)) + } + entries.append(.section(sectionId: sectionId)) sectionId += 1 @@ -457,43 +496,45 @@ private func channelVisibilityControllerEntries(view: PeerView, publicChannelsTo if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.publicLinkAvailability(sectionId: sectionId, false)) + entries.append(.publicLinkAvailability(sectionId: sectionId, false, .singleItem)) var index: Int32 = 0 for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in var lhsDate: Int32 = 0 var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { + if let lhs = lhs as? TelegramGroup { lhsDate = lhs.creationDate } - if let rhs = rhs as? TelegramChannel { + if let rhs = rhs as? TelegramGroup { rhsDate = rhs.creationDate } return lhsDate > rhsDate }) { - entries.append(.existingLinkPeerItem(sectionId: sectionId, index, peer, nil, state.revokingPeerId == nil)) + entries.append(.existingLinkPeerItem(sectionId: sectionId, index, peer, nil, state.revokingPeerId == nil, .singleItem)) index += 1 } } else { - entries.append(.publicLinkAvailability(sectionId: sectionId, true)) + entries.append(.publicLinkAvailability(sectionId: sectionId, true, .textTopItem)) } } else { - entries.append(.editablePublicLink(sectionId: sectionId, peer.addressName, currentAddressName, state.addressNameValidationStatus)) + entries.append(.editablePublicLink(sectionId: sectionId, peer.addressName, currentAddressName, state.addressNameValidationStatus, .singleItem)) if let status = state.addressNameValidationStatus { switch status { case .invalidFormat, .availability: - entries.append(.publicLinkStatus(sectionId: sectionId, currentAddressName, status)) + entries.append(.publicLinkStatus(sectionId: sectionId, currentAddressName, status, .singleItem)) default: break } } - entries.append(.publicLinkInfo(sectionId: sectionId, isGroup ? tr(.channelUsernameAboutGroup) : tr(.channelUsernameAboutChannel))) + entries.append(.publicLinkInfo(sectionId: sectionId, L10n.channelUsernameAboutGroup, .textBottomItem)) } case .privateChannel: - entries.append(.privateLink(sectionId: sectionId, (view.cachedData as? CachedChannelData)?.exportedInvitation?.link)) - entries.append(.publicLinkInfo(sectionId: sectionId, isGroup ? tr(.channelExportLinkAboutGroup) : tr(.channelExportLinkAboutChannel))) + entries.append(.privateLink(sectionId: sectionId, (view.cachedData as? CachedGroupData)?.exportedInvitation?.link, .singleItem)) + entries.append(.publicLinkInfo(sectionId: sectionId, L10n.channelExportLinkAboutGroup, .textBottomItem)) } } - + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + return entries } private func effectiveChannelType(state: ChannelVisibilityControllerState, peer: TelegramChannel) -> CurrentChannelType { @@ -510,51 +551,62 @@ private func effectiveChannelType(state: ChannelVisibilityControllerState, peer: return selectedType } -private func updatedAddressName(state: ChannelVisibilityControllerState, peer: TelegramChannel) -> String? { - let selectedType = effectiveChannelType(state: state, peer: peer) - - let currentAddressName: String - - switch selectedType { - case .privateChannel: - currentAddressName = "" - case .publicChannel: - if let current = state.editingPublicLinkText { - currentAddressName = current - } else { - if let addressName = peer.addressName { - currentAddressName = addressName +private func updatedAddressName(state: ChannelVisibilityControllerState, peer: Peer) -> String? { + if let peer = peer as? TelegramChannel { + let selectedType = effectiveChannelType(state: state, peer: peer) + + let currentAddressName: String + + switch selectedType { + case .privateChannel: + currentAddressName = "" + case .publicChannel: + if let current = state.editingPublicLinkText { + currentAddressName = current } else { - currentAddressName = "" + if let addressName = peer.addressName { + currentAddressName = addressName + } else { + currentAddressName = "" + } } } - } - - if !currentAddressName.isEmpty { - if currentAddressName != peer.addressName { + + if !currentAddressName.isEmpty { + if currentAddressName != peer.addressName { + return currentAddressName + } else { + return nil + } + } else if peer.addressName != nil { + return "" + } else { + return nil + } + } else if let _ = peer as? TelegramGroup { + let currentAddressName = state.editingPublicLinkText ?? "" + if !currentAddressName.isEmpty { return currentAddressName } else { return nil } - } else if peer.addressName != nil { - return "" } else { return nil } } + + fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ChannelVisibilityControllerArguments) -> TableUpdateTransition { - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in return entry.entry.item(arguments, initialSize: initialSize) } - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } -class ChannelVisibilityController: EmptyComposeController { +class ChannelVisibilityController: EmptyComposeController { fileprivate let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true) fileprivate let stateValue = Atomic(value: ChannelVisibilityControllerState()) @@ -568,10 +620,10 @@ class ChannelVisibilityController: EmptyComposeController let peerId:PeerId let onlyUsername:Bool - init(account:Account, peerId:PeerId, onlyUsername: Bool = false) { + init(_ context: AccountContext, peerId:PeerId, onlyUsername: Bool = false) { self.peerId = peerId self.onlyUsername = onlyUsername - super.init(account) + super.init(context) } override var enableBack: Bool { @@ -581,6 +633,22 @@ class ChannelVisibilityController: EmptyComposeController return .invokeNext } + override func becomeFirstResponder() -> Bool? { + return true + } + + override func firstResponder() -> NSResponder? { + var responder: NSResponder? + genericView.enumerateViews { view -> Bool in + if responder == nil, let firstResponder = view.firstResponder { + responder = firstResponder + return false + } + return true + } + return responder + } + override var removeAfterDisapper: Bool { return true } @@ -588,7 +656,11 @@ class ChannelVisibilityController: EmptyComposeController override func viewDidLoad() { super.viewDidLoad() - let account = self.account + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let context = self.context let peerId = self.peerId let onlyUsername = self.onlyUsername @@ -599,17 +671,16 @@ class ChannelVisibilityController: EmptyComposeController } - peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: account, peerId: peerId) |> mapToSignal { result -> Signal<[Peer]?, NoError> in - + peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: context.account, peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in if case .addressNameLimitReached = result { - return adminedPublicChannels(account: account) + return adminedPublicChannels(account: context.account) |> map { Optional($0) } } else { return .single([]) } })) - let arguments = ChannelVisibilityControllerArguments(account: account, updateCurrentType: { type in + let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in updateState { state in return state.withUpdatedSelectedType(type) } @@ -629,7 +700,7 @@ class ChannelVisibilityController: EmptyComposeController return state.withUpdatedEditingPublicLinkText(text) } - self?.checkAddressNameDisposable.set((validateAddressNameInteractive(account: account, domain: .peer(peerId), name: text) + self?.checkAddressNameDisposable.set((validateAddressNameInteractive(account: context.account, domain: .peer(peerId), name: text) |> deliverOnMainQueue).start(next: { result in updateState { state in return state.withUpdatedAddressNameValidationStatus(result) @@ -637,14 +708,22 @@ class ChannelVisibilityController: EmptyComposeController })) } }, displayPrivateLinkMenu: { [weak self] text in - self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied))) + self?.show(toaster: ControllerToaster(text: tr(L10n.shareLinkCopied))) copyToClipboard(text) }, revokePeerId: { [weak self] peerId in updateState { state in return state.withUpdatedRevokingPeerId(peerId) } - self?.revokeAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: nil) |> deliverOnMainQueue).start(error: { _ in + self?.revokeAddressNameDisposable.set((confirmSignal(for: context.window, information: L10n.channelVisibilityConfirmRevoke) |> mapToSignalPromotingError { result -> Signal in + if !result { + return .fail(.generic) + } else { + return .single(true) + } + } |> mapToSignal { _ -> Signal in + return updateAddressName(account: context.account, domain: .peer(peerId), name: nil) + } |> deliverOnMainQueue).start(error: { _ in updateState { state in return state.withUpdatedRevokingPeerId(nil) } @@ -656,13 +735,13 @@ class ChannelVisibilityController: EmptyComposeController })) }) - let peerView = account.viewTracker.peerView(peerId) + let peerView = context.account.viewTracker.peerView(peerId) let initialSize = atomicSize let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let apply = combineLatest(statePromise.get(), peerView, peersDisablingAddressNameAssignment.get(), appearanceSignal) + let apply = combineLatest(queue: prepareQueue, statePromise.get(), peerView, peersDisablingAddressNameAssignment.get(), appearanceSignal) |> map { state, view, publicChannelsToRevoke, appearance -> (TableUpdateTransition, Peer?, Bool) in let peer = peerViewMainPeer(view) @@ -700,10 +779,9 @@ class ChannelVisibilityController: EmptyComposeController strongSelf.doneButton?.isEnabled = doneEnabled strongSelf.doneButton?.removeAllHandlers() strongSelf.doneButton?.set(handler: { [weak self] _ in - - var updatedAddressNameValue: String? - self?.updateState { state in - if let peer = peer as? TelegramChannel { + if let peer = peer { + var updatedAddressNameValue: String? + self?.updateState { state in updatedAddressNameValue = updatedAddressName(state: state, peer: peer) if updatedAddressNameValue != nil { @@ -711,32 +789,88 @@ class ChannelVisibilityController: EmptyComposeController } else { return state } - } else { - return state } - } - - if let updatedAddressNameValue = updatedAddressNameValue { - self?.updateAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue) - |> deliverOnMainQueue).start(error: { [weak self] _ in - self?.updateState { state in - return state.withUpdatedUpdatingAddressName(false) + + if let updatedAddressNameValue = updatedAddressNameValue { + + + let signal: Signal + + let csignal: Signal + + if updatedAddressNameValue.isEmpty && peer.addressName != updatedAddressNameValue, let address = peer.addressName { + let text: String + if peer.isChannel { + text = L10n.channelVisibilityConfirmMakePrivateChannel(address) + } else { + text = L10n.channelVisibilityConfirmMakePrivateGroup(address) + } + csignal = confirmSignal(for: context.window, information: text) |> filter { $0 } |> take(1) |> map { _ in + updateState { state in + return state.withUpdatedUpdatingAddressName(true) + } + } |> castError(UpdateAddressNameError.self) + } else { + csignal = .single(Void()) |> map { + updateState { state in + return state.withUpdatedUpdatingAddressName(true) + } } - }, completed: { [weak self] in - self?.updateState { state in + } + + if peer.isGroup { + signal = convertGroupToSupergroup(account: context.account, peerId: peerId) + |> mapToSignal { upgradedPeerId -> Signal in + return csignal + |> mapToSignal { + showModalProgress(signal: updateAddressName(account: context.account, domain: .peer(upgradedPeerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue), for: context.window) + } + |> mapError {_ in return ConvertGroupToSupergroupError.generic} + |> mapToSignal { _ in + return .single(Optional(upgradedPeerId)) + } + } + |> deliverOnMainQueue + } else { + + signal = csignal + |> mapToSignal { + showModalProgress(signal: updateAddressName(account: context.account, domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue), for: context.window) + } + |> mapToSignal { _ in + return .single(nil) + } + |> mapError {_ in + return ConvertGroupToSupergroupError.generic + } + } + + self?.updateAddressNameDisposable.set(signal.start(next: { updatedPeerId in + self?.onComplete.set(.single(updatedPeerId)) + }, error: { error in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + updateState { state in return state.withUpdatedUpdatingAddressName(false) } - self?.onComplete.set(.single(true)) })) - } else { - self?.onComplete.set(.single(true)) + } else { + self?.onComplete.set(.single(nil)) + } } + }, for: .SingleClick) } })) - exportedLinkDisposable.set((account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { _ in return ensuredExistingPeerExportedInvitation(account: account, peerId: peerId)}).start()) + exportedLinkDisposable.set((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { _ in + return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId) + }).start()) } @@ -744,15 +878,12 @@ class ChannelVisibilityController: EmptyComposeController statePromise.set(stateValue.modify { f($0) }) } - var doneButton:Button? { - if let button = rightBarView as? TextButtonBarView { - return button.button - } - return nil + var doneButton:Control? { + return rightBarView } override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.navigationDone)) + let button = TextButtonBarView(controller: self, text: tr(L10n.navigationDone)) return button } diff --git a/Telegram-Mac/ChatAccessoryModel.swift b/Telegram-Mac/ChatAccessoryModel.swift index f51dcac96b..8d9d5163d4 100644 --- a/Telegram-Mac/ChatAccessoryModel.swift +++ b/Telegram-Mac/ChatAccessoryModel.swift @@ -8,12 +8,24 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit class ChatAccessoryView : Control { var imageView: TransformImageView? } +struct ChatAccessoryPresentation { + let background: NSColor + let title: NSColor + let enabledText: NSColor + let disabledText: NSColor + let border: NSColor + + func withUpdatedBackground(_ backgroundColor: NSColor) -> ChatAccessoryPresentation { + return ChatAccessoryPresentation(background: backgroundColor, title: title, enabledText: enabledText, disabledText: disabledText, border: border) + } +} + class ChatAccessoryModel: NSObject, ViewDisplayDelegate { @@ -21,7 +33,22 @@ class ChatAccessoryModel: NSObject, ViewDisplayDelegate { open var backgroundColor:NSColor { - return view?.backgroundColor ?? theme.colors.background + didSet { + self.presentation = presentation.withUpdatedBackground(backgroundColor) + } + } + + var isSideAccessory: Bool = false + + private var _presentation: ChatAccessoryPresentation? = nil + var presentation: ChatAccessoryPresentation { + set { + _presentation = newValue + view?.needsDisplay = true + } + get { + return _presentation ?? ChatAccessoryPresentation(background: theme.colors.background, title: theme.colors.accent, enabledText: theme.colors.text, disabledText: theme.colors.grayText, border: theme.colors.accent) + } } private let _strongView:ChatAccessoryView? @@ -34,13 +61,15 @@ class ChatAccessoryModel: NSObject, ViewDisplayDelegate { } open var size:NSSize = NSZeroSize - + var width: CGFloat = 0 + var sizeToFit: Bool = false open var frame:NSRect { get { return self.view?.frame ?? NSZeroRect } set { self.view?.frame = newValue + self.view?.needsDisplay = true } } @@ -50,11 +79,13 @@ class ChatAccessoryModel: NSObject, ViewDisplayDelegate { } } - public init(_ view:ChatAccessoryView? = nil) { + public init(_ view:ChatAccessoryView? = nil, presentation: ChatAccessoryPresentation? = nil) { _strongView = view + _presentation = presentation if view != nil { assertOnMainThread() } + backgroundColor = theme.colors.background super.init() self.view = view @@ -95,11 +126,18 @@ class ChatAccessoryModel: NSObject, ViewDisplayDelegate { var header:(TextNodeLayout, TextNode)? var message:(TextNodeLayout, TextNode)? - func measureSize(_ width:CGFloat = 0) -> Void { + var topOffset: CGFloat = 0 + + + func measureSize(_ width:CGFloat = 0, sizeToFit: Bool = false) -> Void { + self.sizeToFit = sizeToFit header = TextNode.layoutText(maybeNode: headerNode, headerAttr, nil, 1, .end, NSMakeSize(width - leftInset, 20), nil,false, .left) message = TextNode.layoutText(maybeNode: messageNode, messageAttr, nil, 1, .end, NSMakeSize(width - leftInset, 20), nil,false, .left) //max(header!.0.size.width,message!.0.size.width) + leftInset - size = NSMakeSize(width, max(34, header!.0.size.height + message!.0.size.height + yInset)) + self.width = width + size = NSMakeSize(sizeToFit ? max(header!.0.size.width,message!.0.size.width) + leftInset + (isSideAccessory ? 20 : 0) : width, max(34, header!.0.size.height + message!.0.size.height + yInset + (isSideAccessory ? 10 : 0))) + size.height += topOffset + // super.measureSize(width) } @@ -108,22 +146,22 @@ class ChatAccessoryModel: NSObject, ViewDisplayDelegate { func draw(_ layer: CALayer, in ctx: CGContext) { if let view = view { - ctx.setFillColor(backgroundColor.cgColor) + ctx.setFillColor(presentation.background.cgColor) ctx.fill(layer.bounds) - ctx.setFillColor(theme.colors.blueFill.cgColor) + ctx.setFillColor(presentation.border.cgColor) let radius:CGFloat = 1.0 - ctx.fill(NSMakeRect(0, radius, 2, layer.bounds.height - radius * 2)) - ctx.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: radius + radius, height: radius + radius))) - ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: layer.bounds.height - radius * 2), size: CGSize(width: radius + radius, height: radius + radius))) + ctx.fill(NSMakeRect((isSideAccessory ? 10 : 0), radius + (isSideAccessory ? 5 : 0) + topOffset, 2, size.height - topOffset - radius * 2 - (isSideAccessory ? 10 : 0))) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: (isSideAccessory ? 10 : 0), y: (isSideAccessory ? 5 : 0) + topOffset), size: CGSize(width: radius + radius, height: radius + radius))) + ctx.fillEllipse(in: CGRect(origin: CGPoint(x: (isSideAccessory ? 10 : 0), y: size.height - radius * 2 - (isSideAccessory ? 5 : 0)), size: CGSize(width: radius + radius, height: radius + radius))) if let header = header, let message = message { - header.1.draw(NSMakeRect(leftInset, 0, header.0.size.width, header.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor) + header.1.draw(NSMakeRect(leftInset + (isSideAccessory ? 10 : 0), (isSideAccessory ? 5 : 0) + topOffset, header.0.size.width, header.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor, backgroundColor: presentation.background) if headerAttr == nil { - message.1.draw(NSMakeRect(leftInset, floorToScreenPixels((size.height - message.0.size.height)/2), message.0.size.width, message.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor) + message.1.draw(NSMakeRect(leftInset + (isSideAccessory ? 10 : 0), floorToScreenPixels(view.backingScaleFactor, topOffset + (size.height - topOffset - message.0.size.height)/2), message.0.size.width, message.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor, backgroundColor: presentation.background) } else { - message.1.draw(NSMakeRect(leftInset, header.0.size.height + yInset, message.0.size.width, message.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor) + message.1.draw(NSMakeRect(leftInset + (isSideAccessory ? 10 : 0), header.0.size.height + yInset + (isSideAccessory ? 5 : 0) + topOffset, message.0.size.width, message.0.size.height), in: ctx, backingScaleFactor: view.backingScaleFactor, backgroundColor: presentation.background) } } } diff --git a/Telegram-Mac/ChatActivitiesModel.swift b/Telegram-Mac/ChatActivitiesModel.swift index 383e7e102f..a50286edc3 100644 --- a/Telegram-Mac/ChatActivitiesModel.swift +++ b/Telegram-Mac/ChatActivitiesModel.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox enum ChatActivityAnimation { case none @@ -25,7 +26,7 @@ class ChatActivitiesView : View { private let textView:TextView = TextView() - private let animationView:ImageView = ImageView(frame: NSMakeRect(0,-4,30,20)) + private let animationView:NSView = NSView(frame: NSMakeRect(0,-4,30,20)) private var isAnimating:Bool = false private var type:ChatActivityAnimation? @@ -33,8 +34,10 @@ class ChatActivitiesView : View { override init() { super.init() + animationView.wantsLayer = true addSubview(textView) addSubview(animationView) + layer?.disableActions() } override func layout() { @@ -56,7 +59,6 @@ class ChatActivitiesView : View { } else { startAnimation(params.0, theme:theme) } - if let layout = params.1 { setFrameSize(layout.layoutSize.width + animationView.frame.width, layout.layoutSize.height) } @@ -64,24 +66,35 @@ class ChatActivitiesView : View { } func startAnimation(_ type:ChatActivityAnimation, theme:ActivitiesTheme) { - self.type = type - self.theme = theme - isAnimating = true - let animation = CAKeyframeAnimation(keyPath: "contents") - switch type { - case .recording: - animation.values = theme.recording - animation.duration = 0.7 - case .uploading: - animation.values = theme.uploading - animation.duration = 1.75 - default: - animation.values = theme.text - animation.duration = 0.7 + if self.type != type || theme != self.theme { + self.type = type + self.theme = theme + isAnimating = true + let animation = CAKeyframeAnimation(keyPath: "contents") + switch type { + case .recording: + animationView.layer?.contents = theme.recording.first + animationView.setFrameSize(theme.recording.first!.backingSize) + + animation.values = theme.recording + animation.duration = 0.7 + case .uploading: + animationView.layer?.contents = theme.uploading.first + animationView.setFrameSize(theme.recording.first!.backingSize) + animation.values = theme.uploading + animation.duration = 1.75 + default: + animationView.layer?.contents = theme.text.first + animationView.setFrameSize(theme.recording.first!.backingSize) + animation.values = theme.text + animation.duration = 0.7 + } + + animationView.layer?.removeAllAnimations() + animation.repeatCount = .infinity + animation.isRemovedOnCompletion = false + animationView.layer?.add(animation, forKey: "contents") } - animation.repeatCount = .greatestFiniteMagnitude - animationView.layer?.add(animation, forKey: "contents") - } override func viewDidMoveToWindow() { @@ -98,9 +111,6 @@ class ChatActivitiesView : View { if realyStop { isAnimating = false } - animationView.layer?.removeAllAnimations() - animationView.image = nil - animationView.sizeToFit() } required init(frame frameRect: NSRect) { @@ -120,9 +130,10 @@ class ChatActivitiesModel: Node { private(set) var isActive:Bool = false private let activityView:ChatActivitiesView private let disposable:MetaDisposable = MetaDisposable() - + private(set) var theme: ActivitiesTheme? func update(with activities:(PeerId, [(Peer, PeerInputActivity)]), for width:CGFloat, theme:ActivitiesTheme, layout:@escaping(Bool)->Void) { isActive = !activities.1.isEmpty + self.theme = theme activityView.updateBackground(theme.backgroundColor) disposable.set(renderedActivities(activities, for: width, theme:theme).start(next: { [weak self] data in self?.activityView.layout(with: data, width: width, theme: theme) @@ -130,11 +141,10 @@ class ChatActivitiesModel: Node { })) } - private func renderedActivities(_ activities:(PeerId, [(Peer, PeerInputActivity)]), for width:CGFloat, theme: ActivitiesTheme) -> Signal <(ChatActivityAnimation, TextViewLayout?), Void> { + private func renderedActivities(_ activities:(PeerId, [(Peer, PeerInputActivity)]), for width:CGFloat, theme: ActivitiesTheme) -> Signal <(ChatActivityAnimation, TextViewLayout?), NoError> { return Signal { subscriber in - if !activities.1.isEmpty { let layout:TextViewLayout var animation:ChatActivityAnimation = .text @@ -153,35 +163,37 @@ class ChatActivitiesModel: Node { } if isFew { + let firstTitle: String = activities.1[0].0.displayTitle if sameActivity { let activity = activities.1[0].1 switch activity { case .recordingVoice: animation = .recording - _ = text.append(string: tr(.peerActivityChatMultiRecordingAudio(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: + tr(L10n.peerActivityChatMultiRecordingAudio1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) case .uploadingFile: animation = .uploading - _ = text.append(string: tr(.peerActivityChatMultiSendingFile(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityChatMultiSendingFile1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) case .uploadingPhoto: animation = .uploading - _ = text.append(string: tr(.peerActivityChatMultiSendingPhoto(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityChatMultiSendingPhoto1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) case .uploadingVideo: animation = .uploading - _ = text.append(string: tr(.peerActivityChatMultiSendingVideo(activities.1.count)), color: theme.textColor, font: .normal(.text)) - //case .playingGame: - - //_ = text.append(string: tr(.peerActivityChatMultiPlayingGame(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityChatMultiSendingVideo1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) case .recordingInstantVideo: animation = .recording - _ = text.append(string: tr(.peerActivityChatMultiRecordingVideo(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityChatMultiRecordingVideo1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) + case .playingGame: + animation = .text + _ = text.append(string: tr(L10n.peerActivityChatMultiPlayingGame1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) default: animation = .text - break + _ = text.append(string: tr(L10n.peerActivityChatMultiTypingText1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) } } else { animation = .text if activities.1.count > 2 { - _ = text.append(string: tr(.peerActivityChatMultiTypingText(activities.1.count)), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityChatMultiTypingText1(firstTitle, activities.1.count - 1)), color: theme.textColor, font: .normal(.text)) } else { let names = activities.1.map({$0.0.compactDisplayTitle}).joined(separator: ", ") _ = text.append(string: names, color: theme.textColor, font: .normal(.text)) @@ -194,47 +206,54 @@ class ChatActivitiesModel: Node { case .recordingVoice: animation = .recording if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string: tr(.peerActivityUserRecordingAudio), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityUserRecordingAudio), color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatRecordingAudio(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } case .uploadingFile: animation = .uploading if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string:tr(.peerActivityUserSendingFile), color: theme.textColor, font: .normal(.text)) + _ = text.append(string:tr(L10n.peerActivityUserSendingFile), color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatSendingFile(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } case .uploadingVideo: animation = .uploading if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string:tr(.peerActivityUserSendingVideo), color: theme.textColor, font: .normal(.text)) + _ = text.append(string:tr(L10n.peerActivityUserSendingVideo), color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatSendingVideo(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } case .uploadingPhoto: animation = .uploading if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string:tr(.peerActivityUserSendingPhoto), color: theme.textColor, font: .normal(.text)) + _ = text.append(string:tr(L10n.peerActivityUserSendingPhoto), color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatSendingPhoto(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } case .recordingInstantVideo: animation = .recording if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string: tr(.peerActivityUserRecordingVideo), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityUserRecordingVideo), color: theme.textColor, font: .normal(.text)) + } else { + _ = text.append(string: L10n.peerActivityChatRecordingVideo(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) + } + case .playingGame: + animation = .text + if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { + _ = text.append(string: L10n.peerActivityUserPlayingGame, color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatPlayingGame(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } default: animation = .text if activities.0.namespace == Namespaces.Peer.CloudUser || activities.0.namespace == Namespaces.Peer.SecretChat { - _ = text.append(string: tr(.peerActivityUserTypingText), color: theme.textColor, font: .normal(.text)) + _ = text.append(string: tr(L10n.peerActivityUserTypingText), color: theme.textColor, font: .normal(.text)) } else { - _ = text.append(string: peer.compactDisplayTitle, color: theme.textColor, font: .normal(.text)) + _ = text.append(string: L10n.peerActivityChatTypingText(peer.compactDisplayTitle), color: theme.textColor, font: .normal(.text)) } } } diff --git a/Telegram-Mac/ChatAnimatedStickerItem.swift b/Telegram-Mac/ChatAnimatedStickerItem.swift new file mode 100644 index 0000000000..b45d5bd9f0 --- /dev/null +++ b/Telegram-Mac/ChatAnimatedStickerItem.swift @@ -0,0 +1,31 @@ +// +// ChatAnimatedStickerItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +final class ChatAnimatedStickerMediaLayoutParameters : ChatMediaLayoutParameters { + let playPolicy: LottiePlayPolicy? + let alwaysAccept: Bool? + let cache: ASCachePurpose? + let hidePlayer: Bool + init(playPolicy: LottiePlayPolicy?, alwaysAccept: Bool? = nil, cache: ASCachePurpose? = nil, hidePlayer: Bool = false, media: TelegramMediaFile) { + self.playPolicy = playPolicy + self.alwaysAccept = alwaysAccept + self.cache = cache + self.hidePlayer = hidePlayer + super.init(presentation: .empty, media: media, automaticDownload: true, autoplayMedia: AutoplayMediaPreferences.defaultSettings) + } +} + +class ChatAnimatedStickerItem: ChatMediaItem { + +} diff --git a/Telegram-Mac/ChatAudioContentView.swift b/Telegram-Mac/ChatAudioContentView.swift index 1eddd8126f..380483477e 100644 --- a/Telegram-Mac/ChatAudioContentView.swift +++ b/Telegram-Mac/ChatAudioContentView.swift @@ -7,9 +7,10 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit @@ -25,6 +26,7 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { let statusDisposable = MetaDisposable() let fetchDisposable = MetaDisposable() + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -32,6 +34,8 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { required init(frame frameRect: NSRect) { super.init(frame:frameRect) textView.isSelectable = false + textView.userInteractionEnabled = false + durationView.userInteractionEnabled = false self.addSubview(textView) self.addSubview(durationView) progressView.fetchControls = fetchControls @@ -39,25 +43,53 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { } + override var fetchStatus: MediaResourceStatus? { + didSet { + if let fetchStatus = fetchStatus { + switch fetchStatus { + case let .Fetching(_, progress): + progressView.state = .Fetching(progress: progress, force: false) + case .Remote: + progressView.state = .Remote + case .Local: + progressView.state = .Play + } + } + } + } + + override func mouseDown(with event: NSEvent) { +// if mouseInside(), userInteractionEnabled { +// progressView.fetchControls?.fetch() +// } else { +// super.mouseDown(with: event) +// } + } + + + override func layout() { super.layout() textView.centerY(x:leftInset) } + override func open() { - if let parameters = parameters as? ChatMediaMusicLayoutParameters, let account = account, let parent = parent { + if let parameters = parameters as? ChatMediaMusicLayoutParameters, let context = context, let parent = parent { if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: parent) { controller.playOrPause() } else { + + let controller:APController + if parameters.isWebpage { - controller = APSingleResourceController(account: account, wrapper: APSingleWrapper(resource: parameters.resource, name: parameters.title, performer: parameters.performer, id: parent.chatStableId)) + controller = APSingleResourceController(account: context.account, wrapper: APSingleWrapper(resource: parameters.resource, mimeType: parameters.file.mimeType, name: parameters.title, performer: parameters.performer, id: parent.chatStableId), streamable: true) } else { - controller = APChatMusicController(account: account, peerId: parent.id.peerId, index: MessageIndex(parent)) + controller = APChatMusicController(account: context.account, peerId: parent.id.peerId, index: MessageIndex(parent)) } parameters.showPlayer(controller) controller.start() - addGlobalAudioToVisible() } } } @@ -66,16 +98,15 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { override func fetch() { - if let account = account, let media = media as? TelegramMediaFile { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + if let context = context, let media = media as? TelegramMediaFile, let parent = parent { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start()) } - open() } override func cancelFetching() { - if let account = account, let media = media as? TelegramMediaFile { - chatMessageFileCancelInteractiveFetch(account: account, file: media) + if let context = context, let media = media as? TelegramMediaFile, let parent = parent { + messageMediaFileCancelInteractiveFetch(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)) } } @@ -87,10 +118,10 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { } func songDidStartPlaying(song:APSongItem, for controller:APController) { - + checkState() } func songDidStopPlaying(song:APSongItem, for controller:APController) { - + checkState() } func playerDidChangedTimebase(song:APSongItem, for controller:APController) { @@ -102,68 +133,58 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { func checkState() { + + let presentation: ChatMediaPresentation = parameters?.presentation ?? .Empty + if let parent = parent, let controller = globalAudio, let song = controller.currentSong { if song.entry.isEqual(to: parent), case .playing = song.state { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPause, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: presentation.activityBackground, foregroundColor: presentation.activityForeground, icon: presentation.pauseThumb, iconInset:NSEdgeInsets(left:0)) + progressView.state = .Icon(image: presentation.pauseThumb, mode: .normal) } else { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: presentation.activityBackground, foregroundColor: presentation.activityForeground, icon: presentation.playThumb, iconInset:NSEdgeInsets(left:1)) + progressView.state = .Play } } else { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: presentation.activityBackground, foregroundColor: presentation.activityForeground, icon: presentation.playThumb, iconInset:NSEdgeInsets(left:1)) } } - override func update(with media: Media, size:NSSize, account:Account, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false) { - - let file:TelegramMediaFile = media as! TelegramMediaFile - let mediaUpdated = self.media == nil || !self.media!.isEqual(media) + override func update(with media: Media, size:NSSize, context: AccountContext, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { - super.update(with: media, size: size, account: account, parent:parent,table:table, parameters:parameters, animated: animated) + super.update(with: media, size: size, context: context, parent:parent,table:table, parameters:parameters, animated: animated, positionFlags: positionFlags) var updatedStatusSignal: Signal? - if mediaUpdated { - - globalAudio?.add(listener: self) - - if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(parent.id)) - |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { - return .Fetching(isActive: true, progress: pendingStatus.progress) - } else { - return resourceStatus - } - } |> deliverOnMainQueue - } else { - updatedStatusSignal = chatMessageFileStatus(account: account, file: file) |> deliverOnMainQueue - } - - - - self.setNeedsDisplay() - } - + + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + updatedStatusSignal = context.account.pendingMessageManager.pendingMessageStatus(parent.id) |> map { pendingStatus in + if let pendingStatus = pendingStatus.0 { + return .Fetching(isActive: true, progress: pendingStatus.progress) + } else { + return .Local + } + } |> deliverOnMainQueue + } + if let updatedStatusSignal = updatedStatusSignal { self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { strongSelf.fetchStatus = status - - switch status { - case let .Fetching(_, progress): - strongSelf.progressView.state = .Fetching(progress: progress, force: false) - case .Remote: - strongSelf.progressView.state = .Remote - case .Local: - strongSelf.progressView.state = .Play - } } })) - checkState() } + + + globalAudio?.add(listener: self) + self.setNeedsDisplay() + + self.fetchStatus = .Local + progressView.state = .Play + checkState() + } var leftInset:CGFloat { @@ -184,7 +205,7 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { return view } - override var interactionContentView: NSView { + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return self.progressView } @@ -198,7 +219,7 @@ class ChatAudioContentView: ChatMediaContentView, APDelegate { } override func clean() { - fetchDisposable.dispose() + //fetchDisposable.dispose() statusDisposable.dispose() globalAudio?.remove(listener: self) } diff --git a/Telegram-Mac/ChatBackgroundView.swift b/Telegram-Mac/ChatBackgroundView.swift new file mode 100644 index 0000000000..de36a73e96 --- /dev/null +++ b/Telegram-Mac/ChatBackgroundView.swift @@ -0,0 +1,11 @@ +// +// BackgroundView.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + diff --git a/Telegram-Mac/ChatBubbleAccessoryForward.swift b/Telegram-Mac/ChatBubbleAccessoryForward.swift new file mode 100644 index 0000000000..b3e9a9e853 --- /dev/null +++ b/Telegram-Mac/ChatBubbleAccessoryForward.swift @@ -0,0 +1,70 @@ +// +// ChatBubbleAccessoryForward.swift +// Telegram +// +// Created by keepcoder on 14/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class ChatBubbleAccessoryForward: Control { + + private let textView: TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + layer?.cornerRadius = .cornerRadius + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateText(layout: TextViewLayout) { + textView.update(layout) + self.background = theme.colors.bubbleBackground_incoming + textView.backgroundColor = theme.colors.bubbleBackground_incoming + setFrameSize(textView.frame.width + 10, textView.frame.height + 10) + needsLayout = true + } + + override func layout() { + super.layout() + textView.center() + } +} + +class ChatBubbleViaAccessory : Control { + private let textView: TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + layer?.cornerRadius = .cornerRadius + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateText(layout: TextViewLayout) { + textView.update(layout) + self.background = theme.colors.bubbleBackground_incoming + textView.backgroundColor = theme.colors.bubbleBackground_incoming + setFrameSize(textView.frame.width + 10, textView.frame.height + 10) + needsLayout = true + } + + override func layout() { + super.layout() + textView.center() + } +} diff --git a/Telegram-Mac/ChatCallRowItem.swift b/Telegram-Mac/ChatCallRowItem.swift index efe3a72222..fc5520a7ac 100644 --- a/Telegram-Mac/ChatCallRowItem.swift +++ b/Telegram-Mac/ChatCallRowItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ChatCallRowItem: ChatRowItem { @@ -24,31 +25,32 @@ class ChatCallRowItem: ChatRowItem { return ChatCallRowView.self } - override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { let message = object.message! let action = message.media[0] as! TelegramMediaAction + let isIncoming: Bool = message.isIncoming(context.account, object.renderType == .bubble) outgoing = !message.flags.contains(.Incoming) - headerLayout = TextViewLayout(.initialize(string: outgoing ? tr(.chatCallOutgoing) : tr(.chatCallIncoming), color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1) + headerLayout = TextViewLayout(.initialize(string: outgoing ? tr(L10n.chatCallOutgoing) : tr(L10n.chatCallIncoming), color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .medium(.text)), maximumNumberOfLines: 1) switch action.action { - case let .phoneCall(_, reason, duration): + case let .phoneCall(_, reason, duration, _): let attr = NSMutableAttributedString() - + if let duration = duration, duration > 0 { - _ = attr.append(string: String.stringForShortCallDurationSeconds(for: duration), color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: String.stringForShortCallDurationSeconds(for: duration), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)) failed = false } else if let reason = reason { switch reason { case .busy: - _ = attr.append(string: outgoing ? "Cancelled" : "Missed", color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: outgoing ? tr(L10n.chatServiceCallCancelled) : tr(L10n.chatServiceCallMissed), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)) case .disconnect: - _ = attr.append(string: "Disconnected", color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: outgoing ? tr(L10n.chatServiceCallCancelled) : tr(L10n.chatServiceCallMissed), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)) case .hangup: - _ = attr.append(string: outgoing ? "Cancelled" : "Missed", color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: outgoing ? tr(L10n.chatServiceCallCancelled) : tr(L10n.chatServiceCallMissed), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)) case .missed: - _ = attr.append(string: outgoing ? "Cancelled" : "Missed", color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: outgoing ? tr(L10n.chatServiceCallCancelled) : tr(L10n.chatServiceCallMissed), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)) } failed = true } else { @@ -60,7 +62,7 @@ class ChatCallRowItem: ChatRowItem { failed = true } - super.init(initialSize, chatInteraction, account, object) + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) } override func makeContentSize(_ width: CGFloat) -> NSSize { @@ -74,10 +76,10 @@ class ChatCallRowItem: ChatRowItem { func requestCall() { if let peerId = message?.id.peerId { - let account = self.account! + let context = self.context - requestSessionId.set((phoneCall(account, peerId: peerId) |> deliverOnMainQueue).start(next: { result in - applyUIPCallResult(account, result) + requestSessionId.set((phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) })) } } @@ -123,10 +125,10 @@ private class ChatCallRowView : ChatRowView { if let item = item as? ChatCallRowItem { - fallbackControl.set(image: theme.icons.chatFallbackCall, for: .Normal) - fallbackControl.sizeToFit() + fallbackControl.set(image: theme.chat.chatCallFallbackIcon(item), for: .Normal) + _ = fallbackControl.sizeToFit() - imageView.image = item.outgoing ? (item.failed ? theme.icons.chatOutgoingFailedCall : theme.icons.chatOutgoingCall) : (item.failed ? theme.icons.chatIncomingFailedCall : theme.icons.chatIncomingCall) + imageView.image = theme.chat.chatCallIcon(item) imageView.sizeToFit() headerView.update(item.headerLayout, origin: NSMakePoint(fallbackControl.frame.maxX + 10, 0)) timeView.update(item.timeLayout, origin: NSMakePoint(fallbackControl.frame.maxX + 14 + imageView.frame.width, item.headerLayout.layoutSize.height + 3)) diff --git a/Telegram-Mac/ChatContactRowItem.swift b/Telegram-Mac/ChatContactRowItem.swift index 1fce1c82a9..eb95038dce 100644 --- a/Telegram-Mac/ChatContactRowItem.swift +++ b/Telegram-Mac/ChatContactRowItem.swift @@ -8,43 +8,90 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import Contacts + class ChatContactRowItem: ChatRowItem { let contactPeer:Peer? - let text:TextViewLayout - override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { + let phoneLayout:TextViewLayout + let nameLayout: TextViewLayout + let vCard: CNContact? + let contact: TelegramMediaContact + let appearance: WPLayoutPresentation + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { if let message = object.message, let contact = message.media[0] as? TelegramMediaContact { let attr = NSMutableAttributedString() + + let isIncoming: Bool = message.isIncoming(context.account, object.renderType == .bubble) + + + self.appearance = WPLayoutPresentation(text: theme.chat.textColor(isIncoming, object.renderType == .bubble), activity: theme.chat.webPreviewActivity(isIncoming, object.renderType == .bubble), link: theme.chat.linkColor(isIncoming, object.renderType == .bubble), selectText: theme.chat.selectText(isIncoming, object.renderType == .bubble), ivIcon: theme.chat.instantPageIcon(isIncoming, object.renderType == .bubble, presentation: theme), renderType: object.renderType) + + + if let vCard = contact.vCardData?.data(using: .utf8) { + //let contacts = try? CNContactVCardSerialization.contacts(with: vCard) + self.vCard = nil + } else { + self.vCard = nil + } + self.contact = contact + + let name = isNotEmptyStrings([contact.firstName + (!contact.firstName.isEmpty ? " " : "") + contact.lastName, vCard?.givenName, vCard?.organizationName]) + + if let peerId = contact.peerId { self.contactPeer = message.peers[peerId] - let range = attr.append(string: contact.firstName + " " + contact.lastName, color: theme.colors.link, font: .medium(.text)) - attr.add(link: inAppLink.peerInfo(peerId:peerId,action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - _ = attr.append(string: "\n") - _ = attr.append(string: formatPhoneNumber(contact.phoneNumber), color: theme.colors.text, font: .normal(.text)) + let range = attr.append(string: name, font: .medium(.text)) + attr.add(link: inAppLink.peerInfo(link: "", peerId:peerId,action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: theme.chat.linkColor(isIncoming, object.renderType == .bubble)) + phoneLayout = TextViewLayout(.initialize(string: formatPhoneNumber(contact.phoneNumber), color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end, alignment: .left) + } else { - self.contactPeer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 0), accessHash: nil, firstName: contact.firstName, lastName: contact.lastName, username: nil, phone: contact.phoneNumber, photo: [], botInfo: nil, flags: []) - _ = attr.append(string: contact.firstName + " " + contact.lastName, color: theme.colors.text, font: .medium(.text)) - _ = attr.append(string: "\n") - _ = attr.append(string: formatPhoneNumber(contact.phoneNumber), color: theme.colors.text, font: .normal(.text)) + self.contactPeer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 0), accessHash: nil, firstName: name.components(separatedBy: " ").first ?? name, lastName: name.components(separatedBy: " ").count == 2 ? name.components(separatedBy: " ").last : "", username: nil, phone: contact.phoneNumber, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + _ = attr.append(string: name, color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .medium(.text)) + + phoneLayout = TextViewLayout(.initialize(string: formatPhoneNumber(contact.phoneNumber), color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end, alignment: .left) } - text = TextViewLayout(attr, maximumNumberOfLines: 3, truncationType: .end, alignment: .left) - text.interactions = globalLinkExecutor - + nameLayout = TextViewLayout(attr, maximumNumberOfLines: 1) + nameLayout.interactions = globalLinkExecutor + } else { fatalError("contact not found for item") } - super.init(initialSize, chatInteraction, account, object) + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if vCard != nil { + return rightSize.height + } + if let line = phoneLayout.lines.last, (line.frame.width + 50) > realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return rightSize.height + } + return nil + } + + override var isFixedRightPosition: Bool { + if vCard != nil { + return super.isForceRightLine + } + + if let line = phoneLayout.lines.last, (line.frame.width + 50) < contentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return true + } + return super.isForceRightLine } override func makeContentSize(_ width: CGFloat) -> NSSize { - text.measure(width: width - 60) - return NSMakeSize(text.layoutSize.width + 60, 50) + nameLayout.measure(width: width - 50) + phoneLayout.measure(width: width - 50) + return NSMakeSize(max(nameLayout.layoutSize.width, phoneLayout.layoutSize.width) + 50, 40 + (vCard != nil ? 36 : 0)) } override func viewClass() -> AnyClass { @@ -57,18 +104,27 @@ class ChatContactRowItem: ChatRowItem { class ChatContactRowView : ChatRowView { private let photoView:AvatarControl = AvatarControl(font: .avatar(.title)) - private let textView:TextView = TextView() + private let nameView: TextView = TextView() + private let phoneView: TextView = TextView() + private var actionButton: TitleButton? + required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(photoView) - photoView.setFrameSize(50,50) - textView.isSelectable = false - addSubview(textView) + photoView.setFrameSize(40,40) + nameView.isSelectable = false + addSubview(nameView) + + phoneView.isSelectable = false + addSubview(phoneView) + + } override func updateColors() { super.updateColors() - textView.backgroundColor = backdorColor + nameView.backgroundColor = contentColor + phoneView.backgroundColor = contentColor } required init?(coder: NSCoder) { @@ -77,27 +133,57 @@ class ChatContactRowView : ChatRowView { override func layout() { super.layout() - if let item = self.item as? ChatContactRowItem { - textView.update(item.text) - textView.centerY(x:60) - } + nameView.setFrameOrigin(50, photoView.frame.minY + 3) + phoneView.setFrameOrigin(50, nameView.frame.maxY + 1) + + actionButton?.setFrameOrigin(0, photoView.frame.maxY + 6) + } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) if let item = item as? ChatContactRowItem { - photoView.setPeer(account: item.account, peer: item.contactPeer) + photoView.setPeer(account: item.context.account, peer: item.contactPeer) photoView.removeAllHandlers() if let peerId = item.contactPeer?.id { - photoView.set(handler: { control in - item.chatInteraction.openInfo(peerId, false , nil, nil) + photoView.set(handler: { [weak item] control in + item?.chatInteraction.openInfo(peerId, false , nil, nil) }, for: .Click) } + + + nameView.update(item.nameLayout) + phoneView.update(item.phoneLayout) + + if let _ = item.vCard { + if actionButton == nil { + actionButton = TitleButton() + actionButton?.layer?.cornerRadius = .cornerRadius + actionButton?.layer?.borderWidth = 1 + actionButton?.disableActions() + actionButton?.set(font: .normal(.text), for: .Normal) + addSubview(actionButton!) + } + actionButton?.removeAllHandlers() +// actionButton?.set(handler: { [weak item] _ in +// guard let item = item, let vCard = item.vCard else {return} +// let controller = VCardModalController(item.account, vCard: vCard, contact: item.contact) +// showModal(with: controller, for: mainWindow) +// }, for: .Click) + actionButton?.set(text: L10n.chatViewContact, for: .Normal) + actionButton?.layer?.borderColor = item.appearance.activity.cgColor + actionButton?.set(color: item.appearance.activity, for: .Normal) + _ = actionButton?.sizeToFit(NSZeroSize, NSMakeSize(item.contentSize.width, 30), thatFit: true) + + } else { + actionButton?.removeFromSuperview() + actionButton = nil + } + } - } } diff --git a/Telegram-Mac/ChatController.swift b/Telegram-Mac/ChatController.swift index af90378662..a5bdeb103f 100644 --- a/Telegram-Mac/ChatController.swift +++ b/Telegram-Mac/ChatController.swift @@ -8,26 +8,83 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +enum ChatMode { + case history + case scheduled +} + +extension ChatHistoryLocation { + var isAtUpperBound: Bool { + switch self { + case .Navigation(index: .upperBound, anchorIndex: .upperBound, count: _, side: _): + return true + case .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: _, scrollPosition: _, count: _, animated: _): + return true + default: + return false + } + } + +} + + +private var temporaryTouchBar: Any? + + +final class ChatWrapperEntry : Comparable, Identifiable { + let appearance: AppearanceWrapperEntry + let automaticDownload: AutomaticMediaDownloadSettings + init(appearance: AppearanceWrapperEntry, automaticDownload: AutomaticMediaDownloadSettings) { + self.appearance = appearance + self.automaticDownload = automaticDownload + } + var stableId: AnyHashable { + return appearance.entry.stableId + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + var entry: ChatHistoryEntry { + return appearance.entry + } +} + +func ==(lhs:ChatWrapperEntry, rhs: ChatWrapperEntry) -> Bool { + return lhs.appearance == rhs.appearance && lhs.automaticDownload == rhs.automaticDownload +} +func <(lhs:ChatWrapperEntry, rhs: ChatWrapperEntry) -> Bool { + return lhs.appearance.entry < rhs.appearance.entry +} final class ChatHistoryView { - let originalView: MessageHistoryView - let filteredEntries: [AppearanceWrapperEntry] + let originalView: MessageHistoryView? + let filteredEntries: [ChatWrapperEntry] - init(originalView:MessageHistoryView, filteredEntries: [AppearanceWrapperEntry]) { + init(originalView:MessageHistoryView?, filteredEntries: [ChatWrapperEntry]) { self.originalView = originalView self.filteredEntries = filteredEntries } + + deinit { + var bp:Int = 0 + bp += 1 + } } enum ChatControllerViewState { case visible case progress + //case IsNotAccessible } final class ChatHistoryState : Equatable { @@ -73,6 +130,7 @@ func ==(lhs:ChatHistoryState, rhs:ChatHistoryState) -> Bool { class ChatControllerView : View, ChatInputDelegate { + let tableView:TableView let inputView:ChatInputView @@ -81,23 +139,38 @@ class ChatControllerView : View, ChatInputDelegate { private var searchInteractions:ChatSearchInteractions! private let scroller:ChatNavigateScroller private var mentions:ChatNavigationMention? - private var progressView:View? + private var failed:ChatNavigateFailed? + private var progressView:ProgressIndicator? private let header:ChatHeaderController private var historyState:ChatHistoryState? private let chatInteraction: ChatInteraction + + private let gradientMaskView = BackgroundGradientView(frame: NSZeroRect) + var headerState: ChatHeaderState { return header.state } - required init(frame frameRect: NSRect, chatInteraction:ChatInteraction, account:Account) { + deinit { + var bp:Int = 0 + bp += 1 + } + + + + required init(frame frameRect: NSRect, chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction header = ChatHeaderController(chatInteraction) - scroller = ChatNavigateScroller(account, chatInteraction.peerId) - inputContextHelper = InputContextHelper(account: account, chatInteraction: chatInteraction) + scroller = ChatNavigateScroller(chatInteraction.context, chatInteraction.chatLocation) + inputContextHelper = InputContextHelper(chatInteraction: chatInteraction) tableView = TableView(frame:NSMakeRect(0,0,frameRect.width,frameRect.height - 50), isFlipped:false) inputView = ChatInputView(frame: NSMakeRect(0,tableView.frame.maxY, frameRect.width,50), chatInteraction: chatInteraction) - inputView.autoresizingMask = [.width] + //inputView.autoresizingMask = [.width] super.init(frame: frameRect) + +// self.layer = CAGradientLayer() +// self.layer?.disableActions() + addSubview(tableView) addSubview(inputView) inputView.delegate = self @@ -108,17 +181,25 @@ class ChatControllerView : View, ChatInputDelegate { }, for: .Click) scroller.forceHide() tableView.addSubview(scroller) + + let context = chatInteraction.context + - searchInteractions = ChatSearchInteractions( jump: { message in - chatInteraction.focusMessageId(nil, message.id, .center(id: 0, animated: false, focus: true, inset: 0)) + searchInteractions = ChatSearchInteractions(jump: { message in + chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: false, focus: .init(focus: true), inset: 0)) }, results: { query in chatInteraction.modalSearch(query) }, calendarAction: { date in chatInteraction.jumpToDate(date) }, cancel: { - chatInteraction.update({$0.updatedSearchMode(false)}) - }, searchRequest: { query, fromId -> Signal<[Message],Void> in - return searchMessages(account: account, peerId: chatInteraction.peerId, query: query, fromId: fromId) + chatInteraction.update({$0.updatedSearchMode((false, nil, nil))}) + }, searchRequest: { query, fromId, state in + let location: SearchMessagesLocation + switch chatInteraction.chatLocation { + case let .peer(peerId): + location = .peer(peerId: peerId, fromId: fromId, tags: nil) + } + return searchMessages(account: context.account, location: location, query: query, state: state) |> map {($0.0.messages.filter({ !($0.media.first is TelegramMediaAction) }), $0.1)} }) @@ -127,20 +208,37 @@ class ChatControllerView : View, ChatInputDelegate { self?.updateScroller(state) } }) - updateLocalizationAndTheme() + + tableView.backgroundColor = .clear + tableView.layer?.backgroundColor = .clear + + // updateLocalizationAndTheme(theme: theme) tableView.set(stickClass: ChatDateStickItem.self, handler: { stick in - var bp:Int = 0 - bp += 1 + }) + + tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self else { + return + } + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + })) + } + func updateScroller(_ historyState:ChatHistoryState) { self.historyState = historyState - let isHidden = tableView.documentOffset.y < 150 && historyState.isDownOfHistory + let isHidden = (tableView.documentOffset.y < 150 && historyState.isDownOfHistory) || tableView.isEmpty if !isHidden { scroller.isHidden = false } + scroller.change(opacity: isHidden ? 0 : 1, animated: true) { [weak scroller] completed in if completed { scroller?.isHidden = isHidden @@ -150,13 +248,30 @@ class ChatControllerView : View, ChatInputDelegate { if let mentions = mentions { mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: true ) } + if let failed = failed { + var offset = (scroller.controlIsHidden ? 0 : scroller.frame.height) + if let mentions = mentions { + offset += (mentions.frame.height + 6) + } + failed.change(pos: NSMakePoint(frame.width - failed.frame.width - 6, tableView.frame.maxY - failed.frame.height - 6 - offset), animated: true ) + } } - + + func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + if let view = header.currentView { +// view.layer?.animatePosition(from: NSMakePoint(0, -current), to: NSMakePoint(0, previous), duration: 0.2, removeOnCompletion: false) +// return { [weak view] in +// view?.layer?.removeAllAnimations() +// } + } + return {} + } + + private var previousHeight:CGFloat = 50 func inputChanged(height: CGFloat, animated: Bool) { if previousHeight != height { - previousHeight = height let header:CGFloat if let currentView = self.header.currentView { header = currentView.frame.height @@ -164,15 +279,41 @@ class ChatControllerView : View, ChatInputDelegate { header = 0 } let size = NSMakeSize(frame.width, frame.height - height - header) + let resizeAnimated = animated && tableView.contentOffset.y < height + //(previousHeight < height || tableView.contentOffset.y < height) + tableView.change(size: size, animated: animated) - inputView.change(pos: NSMakePoint(frame.minX, tableView.frame.maxY), animated: animated) + + + if tableView.contentOffset.y > height { + // tableView.clipView.scroll(to: NSMakePoint(0, tableView.contentOffset.y - (previousHeight - height))) + } + + inputView.change(pos: NSMakePoint(0, tableView.frame.maxY), animated: animated) if let view = inputContextHelper.accessoryView { - view._change(pos: NSMakePoint(0, size.height - view.frame.height), animated: animated) + view._change(pos: NSMakePoint(0, frame.height - inputView.frame.height - view.frame.height), animated: animated) } + + scroller.change(pos: NSMakePoint(frame.width - scroller.frame.width - 6, size.height - scroller.frame.height - 6), animated: animated) + if let mentions = mentions { mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: animated ) } - scroller.change(pos: NSMakePoint(frame.width - scroller.frame.width - 6, size.height - scroller.frame.height - 6), animated: animated) + if let failed = failed { + var offset = (scroller.controlIsHidden ? 0 : scroller.frame.height) + if let mentions = mentions { + offset += (mentions.frame.height + 6) + } + failed.change(pos: NSMakePoint(frame.width - failed.frame.width - 6, tableView.frame.maxY - failed.frame.height - 6 - offset), animated: animated) + } + + previousHeight = height + + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: animated) + } + }) } } @@ -186,97 +327,116 @@ class ChatControllerView : View, ChatInputDelegate { } override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) - if let view = inputContextHelper.accessoryView { - view.setFrameSize(NSMakeSize(newSize.width, view.frame.height)) - } - - if let currentView = header.currentView { - currentView.setFrameSize(newSize.width, currentView.frame.height) - tableView.setFrameSize(newSize.width, newSize.height - inputView.frame.height - currentView.frame.height) - } else { - tableView.setFrameSize(newSize.width, newSize.height - inputView.frame.height) - } - inputView.setFrameSize(newSize.width, inputView.frame.height) - - super.setFrameSize(newSize) - + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) } + override func layout() { super.layout() + updateFrame(frame, animated: false) + } + + func updateFrame(_ frame: NSRect, animated: Bool) { + if let view = inputContextHelper.accessoryView { + (animated ? view.animator() : view).frame = NSMakeRect(0, frame.height - inputView.frame.height - view.frame.height, frame.width, view.frame.height) + } if let currentView = header.currentView { - tableView.setFrameOrigin(0, currentView.frame.height) + (animated ? currentView.animator() : currentView).frame = NSMakeRect(0, 0, frame.width, currentView.frame.height) + (animated ? tableView.animator() : tableView).frame = NSMakeRect(0, currentView.frame.height, frame.width, frame.height - inputView.frame.height - currentView.frame.height) + currentView.needsDisplay = true - } else { - tableView.setFrameOrigin(0, 0) + (animated ? tableView.animator() : tableView).frame = NSMakeRect(0, 0, frame.width, frame.height - inputView.frame.height) } + (animated ? inputView.animator() : inputView).setFrameSize(NSMakeSize(frame.width, inputView.frame.height)) + (animated ? gradientMaskView.animator() : gradientMaskView).frame = tableView.frame - if let view = inputContextHelper.accessoryView { - view.setFrameOrigin(0, frame.height - inputView.frame.height - view.frame.height) - } - inputView.setFrameOrigin(NSMakePoint(0, tableView.frame.maxY)) + + (animated ? inputView.animator() : inputView).setFrameOrigin(NSMakePoint(0, tableView.frame.maxY)) if let indicator = progressView?.subviews.first { - indicator.center() + (animated ? indicator.animator() : indicator).center() } - scroller.setFrameOrigin(frame.width - scroller.frame.width - 6, tableView.frame.height - 6 - scroller.frame.height) + (animated ? progressView?.animator() : progressView)?.center() + + (animated ? scroller.animator() : scroller).setFrameOrigin(NSMakePoint(frame.width - scroller.frame.width - 6, tableView.frame.height - 6 - scroller.frame.height)) if let mentions = mentions { - mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: false ) + (animated ? mentions.animator() : mentions).setFrameOrigin(NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height))) + } + if let failed = failed { + var offset = (scroller.controlIsHidden ? 0 : scroller.frame.height) + if let mentions = mentions { + offset += (mentions.frame.height + 6) + } + (animated ? failed.animator() : failed).setFrameOrigin(NSMakePoint(frame.width - failed.frame.width - 6, tableView.frame.maxY - failed.frame.height - 6 - offset)) } } - override var responder: NSResponder? { return inputView.responder } func change(state:ChatControllerViewState, animated:Bool) { + let state = chatInteraction.presentation.isNotAccessible ? .visible : state if state != self.state { self.state = state switch state { case .progress: if progressView == nil { - progressView = View(frame:tableView.bounds) - progressView?.autoresizingMask = [.width, .height] - let indicator = ProgressIndicator(frame: NSMakeRect(0,0,30,30)) - progressView?.addSubview(indicator) - indicator.animates = true - tableView.addSubview(progressView!) - indicator.center() - } - progressView?.backgroundColor = theme.colors.background - // (progressView?.subviews.first as? ProgressIndicator)?.color = theme.colors.indicatorColor - break + self.progressView = ProgressIndicator(frame: NSMakeRect(0,0,30,30)) + self.progressView?.innerInset = 6 + progressView!.animates = true + addSubview(progressView!) + progressView!.center() + } + progressView?.backgroundColor = theme.colors.background.withAlphaComponent(0.7) + progressView?.layer?.cornerRadius = 15 case .visible: if animated { progressView?.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] (completed) in self?.progressView?.removeFromSuperview() - (self?.progressView?.subviews.first as? ProgressIndicator)?.animates = false + self?.progressView?.animates = false self?.progressView = nil }) } else { progressView?.removeFromSuperview() - (progressView?.subviews.first as? ProgressIndicator)?.animates = false progressView = nil } - - break } } + if chatInteraction.presentation.isNotAccessible { + tableView.updateEmpties() + } } func updateHeader(_ interfaceState:ChatPresentationInterfaceState, _ animated:Bool) { + + let state:ChatHeaderState - if interfaceState.isSearchMode { - state = .search(searchInteractions) - } else if interfaceState.reportStatus == .canReport { - state = .report + if let initialAction = interfaceState.initialAction, case let .ad(kind) = initialAction { + state = .promo(kind) + } else if interfaceState.isSearchMode.0 { + state = .search(searchInteractions, interfaceState.isSearchMode.1, interfaceState.isSearchMode.2) + }else if let peerStatus = interfaceState.peerStatus, let settings = peerStatus.peerStatusSettings, !settings.flags.isEmpty { + if peerStatus.canAddContact && settings.contains(.canAddContact) { + state = .addContact(block: settings.contains(.canReport) || settings.contains(.canBlock), autoArchived: settings.contains(.autoArchived)) + } else if settings.contains(.canReport) { + state = .report(autoArchived: settings.contains(.autoArchived)) + } else if settings.contains(.canShareContact) { + state = .shareInfo + } else { + state = .none + } } else if let pinnedMessageId = interfaceState.pinnedMessageId, pinnedMessageId != interfaceState.interfaceState.dismissedPinnedMessageId { state = .pinned(pinnedMessageId) } else if let canAdd = interfaceState.canAddContact, canAdd { @@ -296,7 +456,14 @@ class ChatControllerView : View, ChatInputDelegate { if let mentions = mentions { - mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: animated ) + mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: animated) + } + if let failed = failed { + var offset = (scroller.controlIsHidden ? 0 : scroller.frame.height) + if let mentions = mentions { + offset += (mentions.frame.height + 6) + } + failed.change(pos: NSMakePoint(frame.width - failed.frame.width - 6, tableView.frame.maxY - failed.frame.height - 6 - offset), animated: animated) } if let view = inputContextHelper.accessoryView { @@ -305,6 +472,51 @@ class ChatControllerView : View, ChatInputDelegate { CATransaction.commit() } + private(set) fileprivate var failedIds: Set = Set() + private var hasOnScreen: Bool = false + func updateFailedIds(_ ids: Set, hasOnScreen: Bool, animated: Bool) { + if hasOnScreen != self.hasOnScreen || self.failedIds != ids { + self.failedIds = ids + self.hasOnScreen = hasOnScreen + if !ids.isEmpty && !hasOnScreen { + if failed == nil { + failed = ChatNavigateFailed(chatInteraction.context) + if let failed = failed { + var offset = (scroller.controlIsHidden ? 0 : scroller.frame.height) + if let mentions = mentions { + offset += (mentions.frame.height + 6) + } + failed.setFrameOrigin(NSMakePoint(frame.width - failed.frame.width - 6, tableView.frame.maxY - failed.frame.height - 6 - offset)) + addSubview(failed) + } + if animated { + failed?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + failed?.removeAllHandlers() + failed?.set(handler: { [weak self] _ in + if let id = ids.min() { + self?.chatInteraction.focusMessageId(nil, id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + }, for: .Click) + } else { + if animated { + if let failed = self.failed { + self.failed = nil + failed.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak failed] _ in + failed?.removeFromSuperview() + }) + } + } else { + failed?.removeFromSuperview() + failed = nil + } + + } + needsLayout = true + } + } + func updateMentionsCount(_ count: Int32, animated: Bool) { if count > 0 { if mentions == nil { @@ -312,6 +524,11 @@ class ChatControllerView : View, ChatInputDelegate { mentions?.set(handler: { [weak self] _ in self?.chatInteraction.mentionPressed() }, for: .Click) + + mentions?.set(handler: { [weak self] _ in + self?.chatInteraction.clearMentions() + }, for: .LongMouseDown) + if let mentions = mentions { mentions.change(pos: NSMakePoint(frame.width - mentions.frame.width - 6, tableView.frame.maxY - mentions.frame.height - 6 - (scroller.controlIsHidden ? 0 : scroller.frame.height)), animated: animated ) addSubview(mentions) @@ -325,12 +542,17 @@ class ChatControllerView : View, ChatInputDelegate { needsLayout = true } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - self.backgroundColor = theme.colors.background + func applySearchResponder() { + (header.currentView as? ChatSearchHeader)?.applySearchResponder() + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) progressView?.backgroundColor = theme.colors.background - (progressView?.subviews.first as? ProgressIndicator)?.set(color: theme.colors.indicatorColor) - scroller.updateLocalizationAndTheme() + (progressView?.subviews.first as? NSProgressIndicator)?.set(color: theme.colors.indicatorColor) + scroller.updateLocalizationAndTheme(theme: theme) tableView.emptyItem = ChatEmptyPeerItem(tableView.frame.size, chatInteraction: chatInteraction) } @@ -340,10 +562,11 @@ class ChatControllerView : View, ChatInputDelegate { -fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHistoryView, account:Account, initialSize:NSSize, interaction:ChatInteraction, animated:Bool, scrollPosition:ChatHistoryViewScrollPosition?, reason:ChatHistoryViewUpdateType, animationInterface:TableAnimationInterface?) -> Signal { +fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHistoryView, timeDifference: TimeInterval, initialSize:NSSize, interaction:ChatInteraction, animated:Bool, scrollPosition:ChatHistoryViewScrollPosition?, reason:ChatHistoryViewUpdateType, animationInterface:TableAnimationInterface?, side: TableSavingSide?) -> Signal { return Signal { subscriber in - +// subscriber.putNext(TableUpdateTransition(deleted: [], inserted: [], updated: [], animated: animated, state: .none(nil), grouping: true)) +// subscriber.putCompletion() var scrollToItem:TableScrollState? = nil var animated = animated @@ -353,39 +576,37 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi case let .unread(unreadIndex): var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { - if case .UnreadEntry = entry.entry { - scrollToItem = .top(id: entry.stableId, animated: false, focus: false, inset: 0) + if case .UnreadEntry = entry.appearance.entry { + scrollToItem = .top(id: entry.stableId, innerId: nil, animated: false, focus: .init(focus: false), inset: -6) break } index -= 1 } if scrollToItem == nil { - var index = toView.filteredEntries.count - 1 - for entry in toView.filteredEntries { - if entry.entry.index >= unreadIndex { - scrollToItem = .top(id: entry.stableId, animated: false, focus: false, inset: 0) - break - } - index -= 1 - } + scrollToItem = .none(animationInterface) } if scrollToItem == nil { - var index = 0 - for entry in toView.filteredEntries.reversed() { - if entry.entry.index < unreadIndex { - scrollToItem = .top(id: entry.stableId, animated: false, focus: false, inset: 0) - break - } - index += 1 - } +// var index = 0 +// for entry in toView.filteredEntries.reversed() { +// if entry.appearance.entry.index < unreadIndex { +// scrollToItem = .top(id: entry.stableId, animated: false, focus: .init(focus: false), inset: 0) +// break +// } +// index += 1 +// } } case let .positionRestoration(scrollIndex, relativeOffset): + + let timestamp = Int32(min(TimeInterval(scrollIndex.timestamp) - timeDifference, TimeInterval(Int32.max))) + + + let scrollIndex = scrollIndex.withUpdatedTimestamp(timestamp) var index = toView.filteredEntries.count - 1 for entry in toView.filteredEntries { - if entry.entry.index >= scrollIndex { - scrollToItem = .top(id: entry.stableId, animated: false, focus: false, inset: relativeOffset) + if entry.appearance.entry.index >= scrollIndex { + scrollToItem = .top(id: entry.stableId, innerId: nil, animated: false, focus: .init(focus: false), inset: relativeOffset) break } index -= 1 @@ -394,28 +615,46 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi if scrollToItem == nil { var index = 0 for entry in toView.filteredEntries.reversed() { - if entry.entry.index < scrollIndex { - scrollToItem = .top(id: entry.stableId, animated: false, focus: false, inset: relativeOffset) + if entry.appearance.entry.index < scrollIndex { + scrollToItem = .top(id: entry.stableId, innerId: nil, animated: false, focus: .init(focus: false), inset: relativeOffset) break } index += 1 } } case let .index(scrollIndex, position, directionHint, animated): - var index = toView.filteredEntries.count - 1 + let scrollIndex = scrollIndex.withSubstractedTimestamp(Int32(timeDifference)) + for entry in toView.filteredEntries { - if entry.entry.index >= scrollIndex { - scrollToItem = position.swap(to: entry.entry.stableId) + if scrollIndex.isLessOrEqual(to: entry.appearance.entry.index) { + if case let .groupedPhotos(entries, _) = entry.appearance.entry { + for inner in entries { + if case let .MessageEntry(values) = inner { + + // if !scrollIndex.isLess(than: MessageIndex(values.0.withUpdatedTimestamp(values.0.timestamp - Int32(timeDifference)))) && scrollIndex.isLessOrEqual(to: MessageIndex(values.0.withUpdatedTimestamp(values.0.timestamp - Int32(timeDifference)))) { + + + let timestamp = Int32(min(TimeInterval(values.0.timestamp) - timeDifference, TimeInterval(Int32.max))) + + let messageIndex = MessageIndex(values.0.withUpdatedTimestamp(timestamp)) + + if !scrollIndex.isLess(than: messageIndex) && scrollIndex.isLessOrEqual(to: messageIndex) { + scrollToItem = position.swap(to: entry.appearance.entry.stableId, innerId: inner.stableId) + } + } + } + } else { + scrollToItem = position.swap(to: entry.appearance.entry.stableId) + } break } - index -= 1 } if scrollToItem == nil { var index = 0 for entry in toView.filteredEntries.reversed() { - if entry.entry.index < scrollIndex { - scrollToItem = position.swap(to: entry.entry.stableId) + if MessageHistoryAnchorIndex.message(entry.appearance.entry.index) < scrollIndex { + scrollToItem = position.swap(to: entry.appearance.entry.stableId) break } index += 1 @@ -425,7 +664,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } if scrollToItem == nil { - scrollToItem = .saveVisible(.upper) + scrollToItem = .saveVisible(side ?? .upper) switch reason { case let .Generic(type): @@ -438,23 +677,14 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi default: break } + } else { + var bp:Int = 0 + bp += 1 } - func makeItem(_ entry: ChatHistoryEntry) -> TableRowItem { - var item:TableRowItem; - switch entry { - case .HoleEntry: - item = ChatHoleRowItem(initialSize, interaction, account,entry) - case .UnreadEntry: - item = ChatUnreadRowItem(initialSize, interaction, account,entry) - case .MessageEntry: - item = ChatRowItem.item(initialSize, from:entry, with:account, interaction: interaction) - case .DateEntry: - item = ChatDateStickItem(initialSize,entry, interaction: interaction) - case .bottom: - item = GeneralRowItem(initialSize, height: 20, stableId: entry.stableId) - } + func makeItem(_ entry: ChatWrapperEntry) -> TableRowItem { + let item:TableRowItem = ChatRowItem.item(initialSize, from: entry.appearance.entry, interaction: interaction, downloadSettings: entry.automaticDownload, theme: theme) _ = item.makeSize(initialSize.width) return item; } @@ -463,14 +693,14 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi var cancelled = false if fromView == nil && firstTransition, let state = scrollToItem { - + var initialIndex:Int = 0 var height:CGFloat = 0 var firstInsertion:[(Int, TableRowItem)] = [] let entries = Array(toView.filteredEntries.reversed()) switch state { - case let .top(stableId, _, _, relativeOffset): + case let .top(stableId, _, _, _, relativeOffset): var index:Int? = nil height = relativeOffset for k in 0 ..< entries.count { @@ -484,7 +714,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi var success:Bool = false var j:Int = index for i in stride(from: index, to: -1, by: -1) { - let item = makeItem(entries[i].entry) + let item = makeItem(entries[i]) height += item.height firstInsertion.append((index - j, item)) j -= 1 @@ -496,7 +726,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi if !success { for i in (index + 1) ..< entries.count { - let item = makeItem(entries[i].entry) + let item = makeItem(entries[i]) height += item.height firstInsertion.insert((0, item), at: 0) if initialSize.height < height { @@ -523,7 +753,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } else { let alreadyInserted = firstInsertion.count for i in alreadyInserted ..< entries.count { - let item = makeItem(entries[i].entry) + let item = makeItem(entries[i]) height += item.height firstInsertion.append((i, item)) if initialSize.height < height { @@ -534,7 +764,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } - case let .center(stableId, _, _, _): + case let .center(stableId, _, _, _, _): var index:Int? = nil for k in 0 ..< entries.count { @@ -544,7 +774,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } } if let index = index { - let item = makeItem(entries[index].entry) + let item = makeItem(entries[index]) height += item.height firstInsertion.append((index, item)) @@ -559,32 +789,28 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi while !lowSuccess || !highSuccess { - if initialSize.height / 2 > lowHeight && !lowSuccess { - let item = makeItem(entries[low].entry) + if (initialSize.height / 2) >= lowHeight && !lowSuccess { + let item = makeItem(entries[low]) lowHeight += item.height firstInsertion.append((low, item)) } - if initialSize.height / 2 > highHeight && !highSuccess { - let item = makeItem(entries[high].entry) + if (initialSize.height / 2) >= highHeight && !highSuccess { + let item = makeItem(entries[high]) highHeight += item.height firstInsertion.append((high, item)) } - if ((initialSize.height / 2 < lowHeight ) || low == entries.count - 1) { + if (((initialSize.height / 2) <= lowHeight ) || low == entries.count - 1) { lowSuccess = true - } else { + } else if !lowSuccess { low += 1 } - if high == 0 { - var bp:Int = 0 - bp += 1 - } - if ((initialSize.height / 2 < highHeight) || high == 0) { + if (((initialSize.height / 2) <= highHeight) || high == 0) { highSuccess = true - } else { + } else if !highSuccess { high -= 1 } @@ -603,14 +829,14 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi for i in 0 ..< copy.count { firstInsertion.append((i, copy[i].1)) } - } + break default: for i in 0 ..< entries.count { - let item = makeItem(entries[i].entry) + let item = makeItem(entries[i]) firstInsertion.append((i, item)) height += item.height @@ -619,29 +845,29 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } } } - subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state:state)) - + messagesViewQueue.async { if !cancelled { var firstInsertedRange:NSRange = NSMakeRange(0, 0) - + if !firstInsertion.isEmpty { firstInsertedRange = NSMakeRange(initialIndex, firstInsertion.count) } var insertions:[(Int, TableRowItem)] = [] - var updates:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + for i in 0 ..< entries.count { let item:TableRowItem if firstInsertedRange.indexIn(i) { - item = firstInsertion[i - initialIndex].1 - updates.append((i, item)) + //item = firstInsertion[i - initialIndex].1 + //updates.append((i, item)) } else { - item = makeItem(entries[i].entry) + item = makeItem(entries[i]) insertions.append((i, item)) } @@ -653,7 +879,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } else if let state = scrollToItem { let (removed,inserted,updated) = proccessEntries(fromView?.filteredEntries, right: toView.filteredEntries, { entry -> TableRowItem in - return makeItem(entry.entry) + return makeItem(entry) }) let grouping: Bool if case .none = state { @@ -661,6 +887,8 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi } else { grouping = true } + + subscriber.putNext(TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: animated, state: state, grouping: grouping)) subscriber.putCompletion() } @@ -678,7 +906,7 @@ fileprivate func prepareEntries(from fromView:ChatHistoryView?, to toView:ChatHi private func maxIncomingMessageIndexForEntries(_ entries: [ChatHistoryEntry], indexRange: (Int, Int)) -> MessageIndex? { if !entries.isEmpty { for i in (indexRange.0 ... indexRange.1).reversed() { - if case let .MessageEntry(message, _, _, _, _) = entries[i], message.flags.contains(.Incoming) { + if case let .MessageEntry(message, _, _, _, _, _, _) = entries[i], message.flags.contains(.Incoming) { return MessageIndex(message) } } @@ -690,17 +918,17 @@ private func maxIncomingMessageIndexForEntries(_ entries: [ChatHistoryEntry], in enum ChatHistoryViewTransitionReason { case Initial(fadeIn: Bool) case InteractiveChanges - case HoleChanges(filledHoleDirections: [MessageIndex: HoleFillDirection], removeHoleDirections: [MessageIndex: HoleFillDirection]) + case HoleReload case Reload } -class ChatController: EditableViewController, Notifable { +class ChatController: EditableViewController, Notifable, TableViewDelegate { - private var peerId:PeerId - private let peerView = Promise() - private var peer:Peer? + private var chatLocation:ChatLocation + private let peerView = Promise() + private let undoTooltipControl: UndoTooltipControl private let historyDisposable:MetaDisposable = MetaDisposable() private let peerDisposable:MetaDisposable = MetaDisposable() @@ -711,7 +939,6 @@ class ChatController: EditableViewController, Notifable { private let peerInputActivitiesDisposable:MetaDisposable = MetaDisposable() private let connectionStatusDisposable:MetaDisposable = MetaDisposable() private let messagesActionDisposable:MetaDisposable = MetaDisposable() - private let openPeerInfoDisposable:MetaDisposable = MetaDisposable() private let unblockDisposable:MetaDisposable = MetaDisposable() private let updatePinnedDisposable:MetaDisposable = MetaDisposable() private let reportPeerDisposable:MetaDisposable = MetaDisposable() @@ -722,7 +949,40 @@ class ChatController: EditableViewController, Notifable { private let navigationActionDisposable:MetaDisposable = MetaDisposable() private let messageIndexDisposable: MetaDisposable = MetaDisposable() private let dateDisposable:MetaDisposable = MetaDisposable() + private let interactiveReadingDisposable: MetaDisposable = MetaDisposable() + private let showRightControlsDisposable: MetaDisposable = MetaDisposable() + private let deleteChatDisposable: MetaDisposable = MetaDisposable() + private let loadSelectionMessagesDisposable: MetaDisposable = MetaDisposable() + private let updateMediaDisposable = MetaDisposable() + private let editCurrentMessagePhotoDisposable = MetaDisposable() + private let failedMessageEventsDisposable = MetaDisposable() + private let selectMessagePollOptionDisposables: DisposableDict = DisposableDict() + private let updateReqctionsDisposable: DisposableDict = DisposableDict() + private let failedMessageIdsDisposable = MetaDisposable() + private let hasScheduledMessagesDisposable = MetaDisposable() + private let onlineMemberCountDisposable = MetaDisposable() + private let chatUndoDisposable = MetaDisposable() + private let discussionDataLoadDisposable = MetaDisposable() + private let slowModeDisposable = MetaDisposable() + private let slowModeInProgressDisposable = MetaDisposable() + private let forwardMessagesDisposable = MetaDisposable() + private let shiftSelectedDisposable = MetaDisposable() + private let updateUrlDisposable = MetaDisposable() + private let loadSharedMediaDisposable = MetaDisposable() + private let applyMaxReadIndexDisposable = MetaDisposable() + private let peekDisposable = MetaDisposable() + private let searchState: ValuePromise = ValuePromise(SearchMessagesResultState("", []), ignoreRepeated: true) + + private let pollAnswersLoading: ValuePromise<[MessageId : ChatPollStateData]> = ValuePromise([:], ignoreRepeated: true) + private let pollAnswersLoadingValue: Atomic<[MessageId : ChatPollStateData]> = Atomic(value: [:]) + private var pollAnswersLoadingSignal: Signal<[MessageId : ChatPollStateData], NoError> { + return pollAnswersLoading.get() + } + private func update(_ f:([MessageId : ChatPollStateData])-> [MessageId : ChatPollStateData]) -> Void { + pollAnswersLoading.set(pollAnswersLoadingValue.modify(f)) + } + var chatInteraction:ChatInteraction var nextTransaction:TransactionHandler = TransactionHandler() @@ -732,7 +992,13 @@ class ChatController: EditableViewController, Notifable { private let location:Promise = Promise() + private let _locationValue:Atomic = Atomic(value: nil) + private var locationValue:ChatHistoryLocation? { + return _locationValue.with { $0 } + } + private func setLocation(_ location: ChatHistoryLocation) { + _ = _locationValue.swap(location) self.location.set(.single(location)) } @@ -741,12 +1007,11 @@ class ChatController: EditableViewController, Notifable { private let initialDataHandler:Promise = Promise() - private let autoremovingUnreadMark:Promise = Promise(nil) let previousView = Atomic(value: nil) - private let botCallbackAlertMessage = Promise(nil) + private let botCallbackAlertMessage = Promise<(String?, Bool)>((nil, false)) private var botCallbackAlertMessageDisposable: Disposable? private var selectTextController:ChatSelectText! @@ -757,8 +1022,11 @@ class ChatController: EditableViewController, Notifable { let layoutDisposable:MetaDisposable = MetaDisposable() + private var afterNextTransaction:(()->Void)? + private let messageProcessingManager = ChatMessageThrottledProcessingManager() + private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager() private let messageMentionProcessingManager = ChatMessageThrottledProcessingManager(delay: 0.2) var historyState:ChatHistoryState = ChatHistoryState() { didSet { @@ -767,28 +1035,37 @@ class ChatController: EditableViewController, Notifable { //} } } + + func clearReplyStack() { + self.historyState = historyState.withClearReplies() + } + override var navigationController: NavigationViewController? { + didSet { + updateSidebar() + } + } - override func scrollup() -> Void { + override func scrollup(force: Bool = false) -> Void { if let reply = historyState.reply() { - - if let message = messageInCurrentHistoryView(reply) { - let stableId = ChatHistoryEntryId.message(message) // - genericView.tableView.scroll(to: .center(id: stableId, animated: true, focus: true, inset: 0)) - } else { - chatInteraction.focusMessageId(nil, reply, .center(id: 0, animated: true, focus: true, inset: 0)) - } + chatInteraction.focusMessageId(nil, reply, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) historyState = historyState.withRemovingReplies(max: reply) } else { - if previousView.modify({$0})?.originalView.laterId != nil { - setLocation(.Scroll(index: MessageIndex.upperBound(peerId: self.peerId), anchorIndex: MessageIndex.upperBound(peerId: self.peerId), sourceIndex: MessageIndex.lowerBound(peerId: self.peerId), scrollPosition: .down(true), animated: true)) + let laterId = previousView.with { $0?.originalView?.laterId } + if laterId != nil { + setLocation(.Scroll(index: MessageHistoryAnchorIndex.upperBound, anchorIndex: MessageHistoryAnchorIndex.upperBound, sourceIndex: MessageHistoryAnchorIndex.lowerBound, scrollPosition: .down(true), count: requestCount, animated: true)) } else { genericView.tableView.scroll(to: .down(true)) } + } } + private var requestCount: Int { + return Int(round(genericView.tableView.frame.height / 28)) + 10 + } + func readyHistory() { if !didSetHistoryReady { didSetHistoryReady = true @@ -797,35 +1074,100 @@ class ChatController: EditableViewController, Notifable { } override var sidebar:ViewController? { - return account.context.entertainment + return context.sharedContext.bindings.entertainment() } func updateSidebar() { if FastSettings.sidebarShown && FastSettings.sidebarEnabled { - (navigationController as? MajorNavigationController)?.genericView.setProportion(proportion: SplitProportion(min:380, max:800), state: .single) - (navigationController as? MajorNavigationController)?.genericView.setProportion(proportion: SplitProportion(min:380+350, max:700), state: .dual) + (navigationController as? MajorNavigationController)?.genericView.setProportion(proportion: SplitProportion(min:380, max:730), state: .single) + (navigationController as? MajorNavigationController)?.genericView.setProportion(proportion: SplitProportion(min:380+350, max:.greatestFiniteMagnitude), state: .dual) } else { (navigationController as? MajorNavigationController)?.genericView.removeProportion(state: .dual) (navigationController as? MajorNavigationController)?.genericView.setProportion(proportion: SplitProportion(min:380, max: .greatestFiniteMagnitude), state: .single) } } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + override func viewDidLoad() { super.viewDidLoad() - - updateSidebar() - // navigationController.genericView.setpropo + + self.undoTooltipControl.getYInset = { [weak self] in + guard let `self` = self else { + return 10 + } + return self.genericView.inputView.frame.height + 10 + } + + weak var previousView = self.previousView + let context = self.context + let atomicSize = self.atomicSize + let chatInteraction = self.chatInteraction + let nextTransaction = self.nextTransaction + + let peerId = self.chatInteraction.peerId + + + if chatInteraction.peerId.namespace == Namespaces.Peer.CloudChannel { + slowModeInProgressDisposable.set((context.account.postbox.unsentMessageIdsView() |> mapToSignal { view -> Signal<[MessageId], NoError> in + return context.account.postbox.messagesAtIds(Array(view.ids)) |> map { messages in + return messages.filter { $0.flags.contains(.Unsent) }.map { $0.id } + } + } |> deliverOnMainQueue).start(next: { [weak self] ids in + self?.chatInteraction.update({ $0.updateSlowMode { + $0?.withUpdatedSendingIds(ids) + }}) + })) + } + + + genericView.tableView.emptyChecker = { [weak self] items in + + let filtred = items.filter { item in + if let item = item as? ChatRowItem, let message = item.message { + if let action = message.media.first as? TelegramMediaAction { + switch action.action { + case .groupCreated: + return messageMainPeer(message)?.groupAccess.isCreator == false + case .groupMigratedToChannel: + return false + case .channelMigratedFromGroup: + return false + case .photoUpdated: + return true + default: + return true + } + } + return true + } + return false + } + + return filtred.isEmpty && self?.genericView.state != .progress + } - self.peerView.set(account.viewTracker.peerView(peerId)) + + genericView.tableView.delegate = self + + + switch chatLocation { + case let .peer(peerId): + self.peerView.set(context.account.viewTracker.peerView(peerId, updateData: true) |> map {Optional($0)}) + let _ = checkPeerChatServiceActions(postbox: context.account.postbox, peerId: peerId).start() + } + - globalPeerHandler.set(.single(self.peerId)) +// context.globalPeerHandler.set(.single(chatLocation)) - let layout:Atomic = Atomic(value:account.context.layout) - let fixedCombinedReadState = Atomic(value: nil) - layoutDisposable.set(account.context.layoutHandler.get().start(next: {[weak self] (state) in + let layout:Atomic = Atomic(value:context.sharedContext.layout) + layoutDisposable.set(context.sharedContext.layoutHandler.get().start(next: {[weak self] (state) in let previous = layout.swap(state) if previous != state, let navigation = self?.navigationController { self?.requestUpdateBackBar() @@ -837,102 +1179,219 @@ class ChatController: EditableViewController, Notifable { selectTextController = ChatSelectText(genericView.tableView) - // let additionalData: [AdditionalMessageHistoryViewData] = [.cachedPeerData(peerId), .totalUnreadCount] + let maxReadIndex:ValuePromise = ValuePromise() + var didSetReadIndex: Bool = false - let historyViewUpdate = location.get() |> distinctUntilChanged - |> mapToSignal { [weak self] location -> Signal in - if let strongSelf = self { + let historyViewUpdate1 = location.get() |> deliverOnMainQueue + |> mapToSignal { [weak self] location -> Signal<(ChatHistoryViewUpdate, TableSavingSide?), NoError> in + guard let `self` = self else { return .never() } + + + let peerId = self.chatInteraction.peerId + + var additionalData: [AdditionalMessageHistoryViewData] = [] + additionalData.append(.cachedPeerData(peerId)) + additionalData.append(.cachedPeerDataMessages(peerId)) + additionalData.append(.peerNotificationSettings(peerId)) + additionalData.append(.preferencesEntry(PreferencesKeys.limitsConfiguration)) + additionalData.append(.preferencesEntry(ApplicationSpecificPreferencesKeys.autoplayMedia)) + additionalData.append(.preferencesEntry(ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings)) + if peerId.namespace == Namespaces.Peer.CloudChannel { + additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: peerId))) + additionalData.append(.peer(peerId)) + } + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { + additionalData.append(.peerIsContact(peerId)) + } + + + return chatHistoryViewForLocation(location, account: context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: { nil }, tagMask: nil, mode: self.mode, additionalData: additionalData) |> beforeNext { viewUpdate in + switch viewUpdate { + case let .HistoryView(view, _, _, _): + if !didSetReadIndex { + maxReadIndex.set(view.maxReadIndex) + didSetReadIndex = true + } + default: + maxReadIndex.set(nil) + } + } |> map { view in + return (view, location.side) + } + } + let historyViewUpdate = historyViewUpdate1 - return chatHistoryViewForLocation(location, account: strongSelf.account, peerId: strongSelf.peerId, fixedCombinedReadState: fixedCombinedReadState.with { $0 }, tagMask: nil, additionalData: []) |> beforeNext { viewUpdate in - switch viewUpdate { - case let .HistoryView(view, _, _, _): - let _ = fixedCombinedReadState.swap(view.combinedReadState) - default: - break + + let animatedEmojiStickers = loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .animatedEmoji, forceActualized: false) + |> map { result -> [String: StickerPackItem] in + switch result { + case let .result(_, items, _): + var animatedEmojiStickers: [String: StickerPackItem] = [:] + for case let item as StickerPackItem in items { + if let emoji = item.getStringRepresentationsOfIndexKeys().first { + animatedEmojiStickers[emoji] = item } } + return animatedEmojiStickers + default: + return [:] } - return .never() } + - //let autoremovingUnreadRemoved:Atomic = Atomic(value: false) let previousAppearance:Atomic = Atomic(value: appAppearance) let firstInitialUpdate:Atomic = Atomic(value: true) + + let applyHole:() -> Void = { [weak self] in + guard let `self` = self else { return } + + let visibleRows = self.genericView.tableView.visibleRows() + var messageIndex: MessageIndex? + for i in stride(from: visibleRows.max - 1, to: -1, by: -1) { + if let item = self.genericView.tableView.item(at: i) as? ChatRowItem, let message = item.message { + messageIndex = MessageIndex(message) + break + } + } + + if let messageIndex = messageIndex { + self.setLocation(.Navigation(index: MessageHistoryAnchorIndex.message(messageIndex), anchorIndex: MessageHistoryAnchorIndex.message(messageIndex), count: self.requestCount, side: .upper)) + } else if let location = self.locationValue { + self.setLocation(location) + } + + } + + let clearHistoryUndoSignal = context.chatUndoManager.status(for: chatInteraction.peerId, type: .clearHistory) + + let _searchState: Atomic = Atomic(value: SearchMessagesResultState("", [])) + + let historyViewTransition = combineLatest(queue: messagesViewQueue, historyViewUpdate, appearanceSignal, combineLatest(maxReadIndex.get() |> deliverOnMessagesViewQueue, pollAnswersLoadingSignal), clearHistoryUndoSignal, searchState.get(), animatedEmojiStickers) |> mapToQueue { update, appearance, readIndexAndPollAnswers, clearHistoryStatus, searchState, animatedEmojiStickers -> Signal<(TableUpdateTransition, MessageHistoryView?, ChatHistoryCombinedInitialData, Bool), NoError> in + + //NSLog("get history") + + let maxReadIndex = readIndexAndPollAnswers.0 + let pollAnswersLoading = readIndexAndPollAnswers.1 + + let searchStateUpdated = _searchState.swap(searchState) != searchState + + let isLoading: Bool + let view: MessageHistoryView? + let initialData: ChatHistoryCombinedInitialData + let updateType: ChatHistoryViewUpdateType + let scrollPosition: ChatHistoryViewScrollPosition? + switch update.0 { + case let .Loading(data, ut): + view = nil + initialData = data + isLoading = true + updateType = ut + scrollPosition = nil + case let .HistoryView(values): + initialData = values.initialData + view = values.view + isLoading = values.view.isLoading + updateType = values.type + scrollPosition = searchStateUpdated ? nil : values.scrollPosition + } + + switch updateType { + case let .Generic(type: type): + switch type { + case .FillHole: + Queue.mainQueue().async { + applyHole() + } + return .complete() + default: + break + } + default: + break + } + + + let pAppearance = previousAppearance.swap(appearance) + var prepareOnMainQueue = pAppearance.presentation != appearance.presentation + switch updateType { + case .Initial: + prepareOnMainQueue = firstInitialUpdate.swap(false) || prepareOnMainQueue + default: + break + } + let animationInterface: TableAnimationInterface = TableAnimationInterface(nextTransaction.isExutable && view?.laterId == nil) + let timeDifference = context.timeDifference + let bigEmojiEnabled = context.sharedContext.baseSettings.bigEmoji - let historyViewTransition = combineLatest(historyViewUpdate |> deliverOnMainQueue, autoremovingUnreadMark.get() |> deliverOnMainQueue, appearanceSignal |> deliverOnMainQueue, account.context.cachedAdminIds.ids(postbox: account.postbox, network: account.network, peerId: peerId) |> deliverOnMainQueue) |> mapToQueue { [weak self] update, autoremoving, appearance, adminIds -> Signal<(TableUpdateTransition, ChatHistoryCombinedInitialData), NoError> in - if let strongSelf = self { - - switch update { - case let .Loading(initialData): - let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: nil, cachedData: nil, readStateData: nil) - strongSelf.initialDataHandler.set(.single(combinedInitialData) |> deliverOnMainQueue) - strongSelf.readyHistory() - strongSelf.genericView.change(state: .progress, animated: true) - - return .complete() - - case let .HistoryView(view, updateType, scrollPosition, initialData): - - var prepareOnMainQueue = previousAppearance.swap(appearance).presentation.dark != appearance.presentation.dark - switch updateType { - case .Initial: - prepareOnMainQueue = firstInitialUpdate.swap(false) || prepareOnMainQueue - default: - break - } - - - - let animated = autoremoving == nil ? false : autoremoving! - - if view.maxReadIndex != nil, autoremoving == nil { - strongSelf.autoremovingUnreadMark.set(.single(true) |> delay(5.0, queue: Queue.mainQueue()) |> then(.single(false))) + + var ranks: CachedChannelAdminRanks? + if let view = view { + for additionalEntry in view.additionalData { + if case let .cacheEntry(id, data) = additionalEntry { + if id == cachedChannelAdminRanksEntryId(peerId: chatInteraction.peerId), let data = data as? CachedChannelAdminRanks { + ranks = data + } + break } - - let animationInterface: TableAnimationInterface = TableAnimationInterface(strongSelf.nextTransaction.isExutable) - - - - let proccesedView = ChatHistoryView(originalView: view, filteredEntries: messageEntries(view.entries, maxReadIndex: autoremoving == nil ? view.maxReadIndex : nil, dayGrouping: true, includeBottom: true, timeDifference: strongSelf.account.context.timeDifference, adminIds: adminIds).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)})) - - - return prepareEntries(from: strongSelf.previousView.swap(proccesedView), to: proccesedView, account: strongSelf.account, initialSize: strongSelf.atomicSize.modify({$0}), interaction:strongSelf.chatInteraction, animated: animated, scrollPosition:scrollPosition, reason:updateType, animationInterface:animationInterface) |> map { transition in - return (transition,initialData) - } |> runOn(prepareOnMainQueue ? Queue.mainQueue(): messagesViewQueue) } } + + + let proccesedView:ChatHistoryView + if let view = view { + if let peer = chatInteraction.peer, peer.isRestrictedChannel(context.contentSettings) { + proccesedView = ChatHistoryView(originalView: view, filteredEntries: []) + } else if let clearHistoryStatus = clearHistoryStatus, clearHistoryStatus != .cancelled { + proccesedView = ChatHistoryView(originalView: view, filteredEntries: []) + } else { + let entries = messageEntries(view.entries, maxReadIndex: maxReadIndex, dayGrouping: true, renderType: appearance.presentation.bubbled ? .bubble : .list, includeBottom: true, timeDifference: timeDifference, ranks: ranks, pollAnswersLoading: pollAnswersLoading, groupingPhotos: true, autoplayMedia: initialData.autoplayMedia, searchState: searchState, animatedEmojiStickers: bigEmojiEnabled ? animatedEmojiStickers : [:]).map({ChatWrapperEntry(appearance: AppearanceWrapperEntry(entry: $0, appearance: appearance), automaticDownload: initialData.autodownloadSettings)}) + proccesedView = ChatHistoryView(originalView: view, filteredEntries: entries) + } + } else { + proccesedView = ChatHistoryView(originalView: nil, filteredEntries: []) + } + - return .never() + return prepareEntries(from: previousView?.swap(proccesedView), to: proccesedView, timeDifference: timeDifference, initialSize: atomicSize.modify({$0}), interaction: chatInteraction, animated: false, scrollPosition:scrollPosition, reason: updateType, animationInterface: animationInterface, side: update.1) |> map { transition in + return (transition, view, initialData, isLoading) + } |> runOn(prepareOnMainQueue ? Queue.mainQueue(): messagesViewQueue) } |> deliverOnMainQueue - let appliedTransition = historyViewTransition |> map { [weak self] transition, initialData in - self?.applyTransition(transition, initialData: initialData) + let appliedTransition = historyViewTransition |> map { [weak self] transition, view, initialData, isLoading in + self?.applyTransition(transition, view: view, initialData: initialData, isLoading: isLoading) } self.historyDisposable.set(appliedTransition.start()) - let previousMaxIncomingMessageIdByNamespace = Atomic<[MessageId.Namespace: MessageId]>(value: [:]) + let previousMaxIncomingMessageIdByNamespace = Atomic<[MessageId.Namespace: MessageIndex]>(value: [:]) let readHistory = combineLatest(self.maxVisibleIncomingMessageIndex.get(), self.isKeyWindow.get()) |> map { [weak self] messageIndex, canRead in + guard let `self` = self else {return} if canRead { var apply = false let _ = previousMaxIncomingMessageIdByNamespace.modify { dict in let previousIndex = dict[messageIndex.id.namespace] - if previousIndex == nil || previousIndex!.id < messageIndex.id.id { + if previousIndex == nil || previousIndex! < messageIndex { apply = true var dict = dict - dict[messageIndex.id.namespace] = messageIndex.id + dict[messageIndex.id.namespace] = messageIndex return dict } return dict } - if let account = self?.account, apply, let peerId = self?.peerId { - clearNotifies(peerId, maxId: messageIndex.id) - _ = applyMaxReadIndexInteractively(postbox: account.postbox, network: account.network, index: messageIndex).start() + if apply { + switch self.chatLocation { + case let .peer(peerId): + if !hasModals() { + clearNotifies(peerId, maxId: messageIndex.id) + let signal = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: messageIndex) + self.applyMaxReadIndexDisposable.set(signal.start()) + } + } } } } @@ -942,75 +1401,435 @@ class ChatController: EditableViewController, Notifable { - chatInteraction.setupReplyMessage = { [weak self] (messageId) in - self?.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(messageId)})}) + chatInteraction.setupReplyMessage = { [weak self] messageId in + guard let `self` = self, self.mode == .history else { return } + + self.chatInteraction.focusInputField() + let signal:Signal = messageId == nil ? .single(nil) : self.chatInteraction.context.account.postbox.messageAtId(messageId!) + _ = (signal |> deliverOnMainQueue).start(next: { [weak self] message in + self?.chatInteraction.update({ current in + var current = current.updatedInterfaceState({$0.withUpdatedReplyMessageId(messageId).withUpdatedReplyMessage(message)}) + if messageId == current.keyboardButtonsMessage?.replyAttribute?.messageId { + current = current.updatedInterfaceState({$0.withUpdatedDismissedForceReplyId(messageId)}) + } + return current + }) + }) + + + } + + chatInteraction.startRecording = { [weak self] hold, view in + guard let chatInteraction = self?.chatInteraction else {return} + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + if let last = slowMode.sendingIds.last { + chatInteraction.focusMessageId(nil, last, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + if let view = self?.genericView.inputView.currentActionView { + showSlowModeTimeoutTooltip(slowMode, for: view) + return + } + } + if chatInteraction.presentation.recordingState != nil || chatInteraction.presentation.state != .normal { + NSSound.beep() + return + } + if let peer = chatInteraction.presentation.peer { + if let permissionText = permissionText(from: peer, for: .banSendMedia) { + alert(for: context.window, info: permissionText) + return + } + if chatInteraction.presentation.effectiveInput.inputText.isEmpty { + + + + switch FastSettings.recordingState { + case .voice: + let permission: Signal = requestMediaPermission(.audio) |> deliverOnMainQueue + _ = permission.start(next: { [weak chatInteraction] access in + guard let chatInteraction = chatInteraction else { + return + } + if access { + let state = ChatRecordingAudioState(account: chatInteraction.context.account, liveUpload: chatInteraction.peerId.namespace != Namespaces.Peer.SecretChat, autohold: hold) + state.start() + delay(0.1, closure: { [weak chatInteraction] in + chatInteraction?.update({$0.withRecordingState(state)}) + }) + } else { + confirm(for: mainWindow, information: L10n.requestAccesErrorHaveNotAccessVoiceMessages, okTitle: L10n.modalOK, cancelTitle: "", thridTitle: L10n.requestAccesErrorConirmSettings, successHandler: { result in + switch result { + case .thrid: + openSystemSettings(.none) + default: + break + } + }) + } + }) + case .video: + let permission: Signal = combineLatest(requestMediaPermission(.video), requestMediaPermission(.audio)) |> map { $0 && $1 } |> deliverOnMainQueue + _ = permission.start(next: { [weak chatInteraction] access in + guard let chatInteraction = chatInteraction else { + return + } + if access { + let state = ChatRecordingVideoState(account: chatInteraction.context.account, liveUpload: chatInteraction.peerId.namespace != Namespaces.Peer.SecretChat, autohold: hold) + showModal(with: VideoRecorderModalController(chatInteraction: chatInteraction, pipeline: state.pipeline), for: context.window) + chatInteraction.update({$0.withRecordingState(state)}) + } else { + confirm(for: mainWindow, information: L10n.requestAccesErrorHaveNotAccessVideoMessages, okTitle: L10n.modalOK, cancelTitle: "", thridTitle: L10n.requestAccesErrorConirmSettings, successHandler: { result in + switch result { + case .thrid: + openSystemSettings(.none) + default: + break + } + }) + } + + }) + } + + + } + } } let scrollAfterSend:()->Void = { [weak self] in - self?.chatInteraction.scrollToLatest(true) + guard let `self` = self else { return } + self.chatInteraction.scrollToLatest(true) + self.context.sharedContext.bindings.entertainment().closePopover() + self.context.cancelGlobalSearch.set(true) } let afterSentTransition = { [weak self] in - self?.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(nil).withUpdatedInputState(ChatTextInputState()).withUpdatedForwardMessageIds([]).withUpdatedComposeDisableUrlPreview(nil)}).updatedUrlPreview(nil)}) + self?.chatInteraction.update({ presentation in + return presentation.updatedInputQueryResult({_ in return nil}).updatedInterfaceState { current in + + var value: ChatInterfaceState = current.withUpdatedReplyMessageId(nil).withUpdatedInputState(ChatTextInputState()).withUpdatedForwardMessageIds([]).withUpdatedComposeDisableUrlPreview(nil) + + + if let message = presentation.keyboardButtonsMessage, let replyMarkup = message.replyMarkup { + if replyMarkup.flags.contains(.setupReply) { + value = value.withUpdatedDismissedForceReplyId(message.id) + } + } + return value + }.updatedUrlPreview(nil) + + }) self?.chatInteraction.saveState(scrollState: self?.immediateScrollState()) } chatInteraction.jumpToDate = { [weak self] date in - if let window = self?.window, let account = self?.account, let peerId = self?.peerId { - let signal = searchMessageIdByTimestamp(account: account, peerId: peerId, timestamp: Int32(date.timeIntervalSince1970) - Int32(NSTimeZone.local.secondsFromGMT())) |> mapToSignal { messageId -> Signal in - if let messageId = messageId { - return downloadMessage(account: account, messageId: messageId) + if let strongSelf = self, let window = self?.window, let peerId = self?.chatInteraction.peerId { + + + switch strongSelf.mode { + case .history: + let signal = searchMessageIdByTimestamp(account: context.account, peerId: peerId, timestamp: Int32(date.timeIntervalSince1970)) + + self?.dateDisposable.set(showModalProgress(signal: signal, for: window).start(next: { messageId in + if let messageId = messageId { + self?.chatInteraction.focusMessageId(nil, messageId, .top(id: 0, innerId: nil, animated: true, focus: .init(focus: false), inset: 30)) + } + })) + case .scheduled: + var previousItem: ChatRowItem? + strongSelf.genericView.tableView.enumerateItems(with: { item -> Bool in + + if let item = item as? ChatDateStickItem { + var calendar = NSCalendar.current + + calendar.timeZone = TimeZone(abbreviation: "UTC")! + let date = Date(timeIntervalSince1970: TimeInterval(item.timestamp + 86400)) + let components = calendar.dateComponents([.year, .month, .day], from: date) + + if CalendarUtils.monthDay(components.day!, date: date) == date { + return false + } + } else if let item = item as? ChatRowItem { + previousItem = item + } + + return true + }) + + if let previousItem = previousItem { + self?.genericView.tableView.scroll(to: .top(id: previousItem.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 30)) } - return .single(nil) } - self?.dateDisposable.set(showModalProgress(signal: signal, for: window).start(next: { message in - if let message = message { - self?.chatInteraction.focusMessageId(nil, message.id, .top(id: 0, animated: true, focus: false, inset: 50)) - } - })) + } } + let editMessage:(ChatEditState, Date?)->Void = { [weak self] state, atDate in + guard let `self` = self else {return} + let presentation = self.chatInteraction.presentation + let inputState = state.inputState.subInputState(from: NSMakeRange(0, state.inputState.inputText.length)) + self.urlPreviewQueryState?.1.dispose() + self.chatInteraction.update({$0.updatedUrlPreview(nil).updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedLoadingState(state.editMedia == .keep ? .loading : .progress(0.2))})})}) + + let scheduleTime:Int32? = atDate != nil ? Int32(atDate!.timeIntervalSince1970) : nil + self.chatInteraction.editDisposable.set((requestEditMessage(account: context.account, messageId: state.message.id, text: inputState.inputText, media: state.editMedia, entities: TextEntitiesMessageAttribute(entities: inputState.messageTextEntities()), disableUrlPreview: presentation.interfaceState.composeDisableUrlPreview != nil, scheduleTime: scheduleTime) + |> deliverOnMainQueue).start(next: { [weak self] progress in + guard let `self` = self else {return} + switch progress { + case let .progress(progress): + if state.editMedia != .keep { + self.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedLoadingState(.progress(max(progress, 0.2)))})})}) + } + default: + break + } + + }, completed: { [weak self] in + guard let `self` = self else {return} + self.chatInteraction.beginEditingMessage(nil) + self.chatInteraction.update({ + $0.updatedInterfaceState({ + $0.withUpdatedComposeDisableUrlPreview(nil).updatedEditState({ + $0?.withUpdatedLoadingState(.none) + }) + }) + }) + })) + } - chatInteraction.sendMessage = { [weak self] in + chatInteraction.sendMessage = { [weak self] silent, atDate in if let strongSelf = self { let presentation = strongSelf.chatInteraction.presentation + let peerId = strongSelf.chatInteraction.peerId + if presentation.abilityToSend { - var setNextToTransaction = false - if let state = presentation.editState { - let inputState = state.inputState.subInputState(from: NSMakeRange(0, state.inputState.inputText.length)) - _ = (requestEditMessage(account: strongSelf.account, messageId:state.message.id, text: inputState.inputText, entities: TextEntitiesMessageAttribute(entities: inputState.messageTextEntities), disableUrlPreview: presentation.interfaceState.composeDisableUrlPreview != nil) |> deliverOnMainQueue).start(completed: { [weak self] in - self?.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedComposeDisableUrlPreview(nil)})}) - - }) - strongSelf.chatInteraction.beginEditingMessage(nil) - } else if !presentation.effectiveInput.inputText.isEmpty { - setNextToTransaction = true - let _ = (Sender.enqueue(input: presentation.effectiveInput, account: strongSelf.account, peerId: strongSelf.peerId, replyId: presentation.interfaceState.replyMessageId, disablePreview: presentation.interfaceState.composeDisableUrlPreview != nil) |> deliverOnMainQueue).start(completed: scrollAfterSend) + func apply(_ controller: ChatController, atDate: Date?) { + var invokeSignal:Signal = .complete() + + var setNextToTransaction = false + if let state = presentation.interfaceState.editState { + editMessage(state, atDate) + return + } else if !presentation.effectiveInput.inputText.trimmed.isEmpty { + setNextToTransaction = true + invokeSignal = Sender.enqueue(input: presentation.effectiveInput, context: context, peerId: controller.chatInteraction.peerId, replyId: presentation.interfaceState.replyMessageId, disablePreview: presentation.interfaceState.composeDisableUrlPreview != nil, silent: silent, atDate: atDate, secretMediaPreview: presentation.urlPreview?.1) |> deliverOnMainQueue |> ignoreValues + + } + + let fwdIds: [MessageId] = presentation.interfaceState.forwardMessageIds + if !fwdIds.isEmpty { + setNextToTransaction = true + + + let fwd = combineLatest(queue: .mainQueue(), context.account.postbox.messagesAtIds(fwdIds), context.account.postbox.loadedPeerWithId(peerId)) |> mapToSignal { messages, peer -> Signal<[MessageId?], NoError> in + let errors:[String] = messages.compactMap { message in + + for attr in message.attributes { + if let _ = attr as? InlineBotMessageAttribute, peer.hasBannedRights(.banSendInline) { + return permissionText(from: peer, for: .banSendInline) + } + } + + if let media = message.media.first { + switch media { + case _ as TelegramMediaPoll: + return permissionText(from: peer, for: .banSendPolls) + case _ as TelegramMediaImage: + return permissionText(from: peer, for: .banSendMedia) + case let file as TelegramMediaFile: + if file.isAnimated && file.isVideo { + return permissionText(from: peer, for: .banSendGifs) + } else if file.isStaticSticker { + return permissionText(from: peer, for: .banSendStickers) + } else { + return permissionText(from: peer, for: .banSendMedia) + } + case _ as TelegramMediaGame: + return permissionText(from: peer, for: .banSendGames) + default: + return nil + } + } + + return nil + } + + if !errors.isEmpty { + alert(for: context.window, info: errors.joined(separator: "\n\n")) + return .complete() + } + + return Sender.forwardMessages(messageIds: messages.map {$0.id}, context: context, peerId: peerId, silent: silent, atDate: atDate) + } + + invokeSignal = invokeSignal |> then( fwd |> ignoreValues) + + } + + _ = (invokeSignal |> deliverOnMainQueue).start(completed: scrollAfterSend) + + if setNextToTransaction { + if atDate != nil { + afterSentTransition() + } else { + controller.nextTransaction.set(handler: afterSentTransition) + } + } } - if !presentation.interfaceState.forwardMessageIds.isEmpty { - setNextToTransaction = true - let _ = (Sender.forwardMessages(messageIds:presentation.interfaceState.forwardMessageIds,account: strongSelf.account,peerId: strongSelf.peerId) |> deliverOnMainQueue).start(completed: scrollAfterSend) + switch strongSelf.mode { + case .scheduled: + if let atDate = atDate { + apply(strongSelf, atDate: atDate) + } else if presentation.state != .editing, let peer = chatInteraction.peer { + DispatchQueue.main.async { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let strongSelf = strongSelf { + apply(strongSelf, atDate: date) + } + }), for: context.window) + } + } else { + apply(strongSelf, atDate: nil) + } + case .history: + delay(0.1, closure: { + if atDate != nil { + strongSelf.openScheduledChat() + } + }) + apply(strongSelf, atDate: atDate) } - if setNextToTransaction { - strongSelf.nextTransaction.set(handler: afterSentTransition) + } else { + if let editState = presentation.interfaceState.editState, editState.inputState.inputText.isEmpty { + if editState.message.media.isEmpty || editState.message.media.first is TelegramMediaWebpage { + strongSelf.chatInteraction.deleteMessages([editState.message.id]) + return + } + } + let actionView = strongSelf.genericView.inputView.currentActionView + if let slowMode = presentation.slowMode { + if let errorText = presentation.slowModeErrorText { + if let slowMode = presentation.slowMode, slowMode.timeout != nil { + showSlowModeTimeoutTooltip(slowMode, for: actionView) + } else { + tooltip(for: actionView, text: errorText) + } + if let last = slowMode.sendingIds.last { + strongSelf.chatInteraction.focusMessageId(nil, last, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } else { + strongSelf.genericView.inputView.textView.shake() + } + } else { + strongSelf.genericView.inputView.textView.shake() + } + + } else { + strongSelf.genericView.inputView.textView.shake() } + } + } + } + + chatInteraction.updateEditingMessageMedia = { [weak self] exts, asMedia in + guard let `self` = self else {return} + + filePanel(with: exts, allowMultiple: false, for: context.window, completion: { [weak self] files in + guard let `self` = self else {return} + if let file = files?.first { + self.updateMediaDisposable.set((Sender.generateMedia(for: MediaSenderContainer(path: file, isFile: !asMedia), account: context.account, isSecretRelated: peerId.namespace == Namespaces.Peer.SecretChat) |> deliverOnMainQueue).start(next: { [weak self] media, _ in + self?.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + })) + } + }) + } + + + chatInteraction.addContact = { [weak self] in + if let peerId = self?.chatInteraction.presentation.mainPeer?.id { + showModal(with: NewContactController(context: context, peerId: peerId), for: context.window) + } + } + chatInteraction.blockContact = { [weak self] in + if let chatInteraction = self?.chatInteraction, let peer = chatInteraction.presentation.mainPeer { + if peer.isUser || peer.isBot { + let options: [ModalOptionSet] = [ModalOptionSet(title: L10n.blockContactOptionsReport, selected: true, editable: true), ModalOptionSet(title: L10n.blockContactOptionsDeleteChat, selected: true, editable: true)] + + showModal(with: ModalOptionSetController(context: chatInteraction.context, options: options, actionText: (L10n.blockContactOptionsAction(peer.compactDisplayTitle), theme.colors.redUI), desc: L10n.blockContactTitle(peer.compactDisplayTitle), title: L10n.blockContactOptionsTitle, result: { result in + + var signals:[Signal] = [] + + signals.append(context.blockedPeersContext.add(peerId: peer.id) |> `catch` { _ in return .complete() }) + + if result[1] == .selected { + signals.append(removePeerChat(account: context.account, peerId: chatInteraction.peerId, reportChatSpam: result[0] == .selected) |> ignoreValues) + } else if result[0] == .selected { + signals.append(reportPeer(account: context.account, peerId: peer.id) |> ignoreValues) + } + let closeChat = result[1] == .selected + + _ = showModalProgress(signal: combineLatest(signals), for: context.window).start(completed: { + if closeChat { + context.sharedContext.bindings.rootNavigation().back() + } + }) + + }), for: context.window) } else { - NSSound.beep() + chatInteraction.reportSpamAndClose() } + } + + } + + chatInteraction.unarchive = { + _ = updatePeerGroupIdInteractively(postbox: context.account.postbox, peerId: peerId, groupId: .root).start() + _ = context.account.postbox.transaction { transaction in + transaction.updatePeerCachedData(peerIds: [peerId], update: { peerId, cachedData in + if let cachedData = cachedData as? CachedUserData { + let current = cachedData.peerStatusSettings + var flags = current?.flags ?? [] + flags.remove(.autoArchived) + flags.remove(.canBlock) + flags.remove(.canReport) + return cachedData.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: flags, geoDistance: current?.geoDistance)) + } + if let cachedData = cachedData as? CachedChannelData { + let current = cachedData.peerStatusSettings + var flags = current?.flags ?? [] + flags.remove(.autoArchived) + flags.remove(.canBlock) + flags.remove(.canReport) + return cachedData.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: flags, geoDistance: current?.geoDistance)) + } + if let cachedData = cachedData as? CachedGroupData { + let current = cachedData.peerStatusSettings + var flags = current?.flags ?? [] + flags.remove(.autoArchived) + flags.remove(.canBlock) + flags.remove(.canReport) + return cachedData.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: flags, geoDistance: current?.geoDistance)) + } + return cachedData + }) + }.start() } - chatInteraction.forceSendMessage = { [weak self] (message) in + chatInteraction.sendPlainText = { [weak self] text in if let strongSelf = self, let peer = self?.chatInteraction.presentation.peer, peer.canSendMessage { - let _ = (Sender.enqueue(input: ChatTextInputState(inputText: message), account: strongSelf.account, peerId: strongSelf.peerId, replyId: strongSelf.chatInteraction.presentation.interfaceState.replyMessageId) |> deliverOnMainQueue).start(completed: scrollAfterSend) + let _ = (Sender.enqueue(input: ChatTextInputState(inputText: text), context: context, peerId: strongSelf.chatInteraction.peerId, replyId: strongSelf.chatInteraction.presentation.interfaceState.replyMessageId) |> deliverOnMainQueue).start(completed: scrollAfterSend) } } + chatInteraction.sendLocation = { [weak self] coordinate, venue in + let media = TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil) + self?.chatInteraction.sendMedias([media], ChatTextInputState(), false, nil, false, nil) + } + chatInteraction.scrollToLatest = { [weak self] removeStack in if let strongSelf = self { if removeStack { @@ -1020,74 +1839,145 @@ class ChatController: EditableViewController, Notifable { } } - chatInteraction.forwardMessages = { [weak self] forwardMessages in - if let strongSelf = self, let navigation = strongSelf.navigationController { - - strongSelf.loadFwdMessagesDisposable.set((strongSelf.account.postbox.messagesAtIds(forwardMessages) |> deliverOnMainQueue).start(next: { [weak strongSelf] messages in - if let strongSelf = strongSelf { - - let displayName:String = strongSelf.peer?.compactDisplayTitle ?? "Unknown" - let action = FWDNavigationAction(messages: messages, displayName: displayName) - navigation.set(modalAction: action, strongSelf.account.context.layout != .single) - - if strongSelf.account.context.layout == .single { - navigation.push(ForwardChatListController(strongSelf.account)) - } - - action.afterInvoke = { [weak strongSelf] in - strongSelf?.chatInteraction.update(animated: false, {$0.withoutSelectionState()}) - strongSelf?.chatInteraction.saveState(scrollState: strongSelf?.immediateScrollState()) - } - - } - })) - } + chatInteraction.forwardMessages = { forwardMessages in + showModal(with: ShareModalController(ForwardMessagesObject(context, messageIds: forwardMessages)), for: context.window) } chatInteraction.deleteMessages = { [weak self] messageIds in - if let strongSelf = self, let peer = strongSelf.peer { - let channelAdmin:Signal<[ChannelParticipant]?, Void> = peer.isSupergroup ? channelAdmins(account: strongSelf.account, peerId: strongSelf.peerId) - |> mapError {_ in return} |> map { admins -> [ChannelParticipant]? in - return admins.map({$0.participant}) - } : .single(nil) + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer { + let channelAdmin:Promise<[ChannelParticipant]?> = Promise() + + if peer.isSupergroup { + let disposable: MetaDisposable = MetaDisposable() + let result = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peer.id, updated: { state in + switch state.loadingState { + case .ready: + channelAdmin.set(.single(state.list.map({$0.participant}))) + disposable.dispose() + default: + break + } + }) + disposable.set(result.0) + } else { + channelAdmin.set(.single(nil)) + } + - self?.messagesActionDisposable.set(combineLatest(strongSelf.account.postbox.messagesAtIds(messageIds) |> deliverOnMainQueue, channelAdmin |> deliverOnMainQueue).start( next:{ [weak strongSelf] messages, admins in - if let strongSelf = strongSelf, let peer = strongSelf.peer { + self?.messagesActionDisposable.set(combineLatest(context.account.postbox.messagesAtIds(messageIds) |> deliverOnMainQueue, channelAdmin.get() |> deliverOnMainQueue).start( next:{ [weak strongSelf] messages, admins in + if let strongSelf = strongSelf, let peer = strongSelf.chatInteraction.peer { var canDelete:Bool = true var canDeleteForEveryone = true - + var otherCounter:Int32 = 0 + let peerId = peer.id + var _mustDeleteForEveryoneMessage: Bool = true for message in messages { - if !canDeleteMessage(message, account: strongSelf.account) { + if !canDeleteMessage(message, account: context.account) { canDelete = false } - if !canDeleteForEveryoneMessage(message, account: strongSelf.account) { + if !mustDeleteForEveryoneMessage(message) { + _mustDeleteForEveryoneMessage = false + } + if !canDeleteForEveryoneMessage(message, context: context) { canDeleteForEveryone = false + } else { + if message.effectiveAuthor?.id != context.peerId && !(context.limitConfiguration.canRemoveIncomingMessagesInPrivateChats && message.peers[message.id.peerId] is TelegramUser) { + if let peer = message.peers[message.id.peerId] as? TelegramGroup { + inner: switch peer.role { + case .member: + otherCounter += 1 + default: + break inner + } + } else { + otherCounter += 1 + } + } } } + if otherCounter > 0 || peer.id == context.peerId { + canDeleteForEveryone = false + } + if messages.isEmpty { + strongSelf.chatInteraction.update({$0.withoutSelectionState()}) + return + } + if canDelete { - let isAdmin = admins?.filter({$0.peerId == messages[0].author?.id}).first != nil - if mustManageDeleteMessages(messages, for: peer, account: strongSelf.account), let memberId = messages[0].author?.id, !isAdmin { - showModal(with: DeleteSupergroupMessagesModalController(account: strongSelf.account, messageIds: messages.map {$0.id}, peerId: peer.id, memberId: memberId, onComplete: { [weak strongSelf] in + if mustManageDeleteMessages(messages, for: peer, account: context.account), let memberId = messages[0].author?.id { + + var options:[ModalOptionSet] = [] + + options.append(ModalOptionSet(title: L10n.supergroupDeleteRestrictionDeleteMessage, selected: true, editable: true)) + + var hasRestrict: Bool = false + + if let channel = peer as? TelegramChannel { + if channel.hasPermission(.banMembers) { + options.append(ModalOptionSet(title: L10n.supergroupDeleteRestrictionBanUser, selected: false, editable: true)) + hasRestrict = true + } + } + options.append(ModalOptionSet(title: L10n.supergroupDeleteRestrictionReportSpam, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.supergroupDeleteRestrictionDeleteAllMessages, selected: false, editable: true)) + + + + showModal(with: ModalOptionSetController(context: context, options: options, actionText: (L10n.modalOK, theme.colors.accent), title: L10n.supergroupDeleteRestrictionTitle, result: { [weak strongSelf] result in + + var signals:[Signal] = [] + + var index:Int = 0 + if result[index] == .selected { + signals.append(deleteMessagesInteractively(account: context.account, messageIds: messageIds, type: .forEveryone)) + } + index += 1 + + if hasRestrict { + if result[index] == .selected { + signals.append(context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))) + } + index += 1 + } + + if result[index] == .selected { + signals.append(reportSupergroupPeer(account: context.account, peerId: memberId, memberId: memberId, messageIds: messageIds)) + } + index += 1 + + if result[index] == .selected { + signals.append(clearAuthorHistory(account: context.account, peerId: peerId, memberId: memberId)) + } + index += 1 + + _ = showModalProgress(signal: combineLatest(signals), for: context.window).start() strongSelf?.chatInteraction.update({$0.withoutSelectionState()}) - }), for: mainWindow) - } else { - let thrid:String? = canDeleteForEveryone ? tr(.chatConfirmDeleteMessagesForEveryone) : nil + }), for: context.window) + + } else if let `self` = self { + let thrid:String? = self.mode == .scheduled ? nil : (canDeleteForEveryone ? peer.isUser ? L10n.chatMessageDeleteForMeAndPerson(peer.compactDisplayTitle) : L10n.chatConfirmDeleteMessagesForEveryone : nil) - if let window = self?.window { - confirm(for: window, with: tr(.chatConfirmActionUndonable), and: tr(.chatConfirmDeleteMessages), thridTitle:thrid, successHandler: { result in - let type:InteractiveMessagesDeletionType - switch result { - case .basic: - type = .forLocalPeer - case .thrid: - type = .forEveryone + modernConfirm(for: context.window, account: context.account, peerId: nil, header: thrid == nil ? L10n.chatConfirmActionUndonable : L10n.chatConfirmDeleteMessagesCountable(messages.count), information: thrid == nil ? _mustDeleteForEveryoneMessage ? L10n.chatConfirmDeleteForEveryoneCountable(messages.count) : L10n.chatConfirmDeleteMessagesCountable(messages.count) : nil, okTitle: L10n.confirmDelete, thridTitle: thrid, successHandler: { [weak strongSelf] result in + + guard let strongSelf = strongSelf else {return} + + let type:InteractiveMessagesDeletionType + switch result { + case .basic: + type = .forLocalPeer + case .thrid: + type = .forEveryone + } + if let editingState = strongSelf.chatInteraction.presentation.interfaceState.editState { + if messageIds.contains(editingState.message.id) { + strongSelf.chatInteraction.cancelEditing() } - _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: messageIds, type: type).start() - strongSelf.chatInteraction.update({$0.withoutSelectionState()}) - }) - } + } + _ = deleteMessagesInteractively(account: context.account, messageIds: messageIds, type: type).start() + strongSelf.chatInteraction.update({$0.withoutSelectionState()}) + }) } } } @@ -1098,28 +1988,74 @@ class ChatController: EditableViewController, Notifable { chatInteraction.openInfo = { [weak self] (peerId, toChat, postId, action) in if let strongSelf = self { if toChat { - strongSelf.navigationController?.push(ChatAdditionController(account: strongSelf.account, peerId: peerId, messageId: postId, initialAction: action)) - } else { - strongSelf.openPeerInfoDisposable.set((strongSelf.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak strongSelf] peer in - if let strongSelf = strongSelf { - strongSelf.navigationController?.push(PeerInfoController(account: strongSelf.account, peer: peer)) + if peerId == strongSelf.chatInteraction.peerId { + if let postId = postId { + + var fromId: MessageId? = nil + if let action = action { + switch action { + case let .source(id): + fromId = id + default: + break + } + } + + strongSelf.chatInteraction.focusMessageId(fromId, postId, TableScrollState.center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) } - })) + if let action = action { + strongSelf.chatInteraction.update({ $0.updatedInitialAction(action) }) + } + } else { + strongSelf.navigationController?.push(ChatAdditionController(context: context, chatLocation: .peer(peerId), messageId: postId, initialAction: action)) + } + } else { + strongSelf.navigationController?.push(PeerInfoController(context: context, peerId: peerId)) } } } + chatInteraction.showNextPost = { [weak self] in + guard let `self` = self else {return} + if let bottomVisibleRow = self.genericView.tableView.bottomVisibleRow { + if bottomVisibleRow > 0 { + var item = self.genericView.tableView.item(at: bottomVisibleRow - 1) + if item.view?.visibleRect.height != item.view?.frame.height { + item = self.genericView.tableView.item(at: bottomVisibleRow) + } + self.genericView.tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets(), true) + } + + } + } + + chatInteraction.openFeedInfo = { [weak self] groupId in + guard let `self` = self else {return} + self.navigationController?.push(ChatListController(context, groupId: groupId)) + } + + chatInteraction.openProxySettings = { [weak self] in + let controller = proxyListController(accountManager: context.sharedContext.accountManager, network: context.account.network, pushController: { [weak self] controller in + self?.navigationController?.push(controller) + }) + self?.navigationController?.push(controller) + } + chatInteraction.inlineAudioPlayer = { [weak self] controller in if let navigation = self?.navigationController { if let header = navigation.header, let strongSelf = self { header.show(true) if let view = header.view as? InlineAudioPlayerView { - view.update(with: controller, tableView: strongSelf.genericView.tableView) + view.update(with: controller, context: context, tableView: strongSelf.genericView.tableView) } } } } - + chatInteraction.searchPeerMessages = { [weak self] peer in + guard let `self` = self else { return } + self.chatInteraction.update({$0.updatedSearchMode((false, nil, nil))}) + self.chatInteraction.update({$0.updatedSearchMode((true, peer, nil))}) + } chatInteraction.movePeerToInput = { [weak self] (peer) in if let strongSelf = self { let textInputState = strongSelf.chatInteraction.presentation.effectiveInput @@ -1149,10 +2085,26 @@ class ChatController: EditableViewController, Notifable { chatInteraction.sendInlineResult = { [weak self] (results,result) in if let strongSelf = self { - if let message = outgoingMessageWithChatContextResult(results, result) { - _ = (Sender.enqueue(message: message.withUpdatedReplyToMessageId(strongSelf.chatInteraction.presentation.interfaceState.replyMessageId), account: strongSelf.account, peerId: strongSelf.peerId) |> deliverOnMainQueue).start(completed: scrollAfterSend) - strongSelf.nextTransaction.set(handler: afterSentTransition) + func apply(_ controller: ChatController, atDate: Int32?) { + let chatInteraction = controller.chatInteraction + if let message = outgoingMessageWithChatContextResult(to: chatInteraction.peerId, results: results, result: result, scheduleTime: atDate) { + _ = (Sender.enqueue(message: message.withUpdatedReplyToMessageId(chatInteraction.presentation.interfaceState.replyMessageId), context: context, peerId: chatInteraction.peerId) |> deliverOnMainQueue).start(completed: scrollAfterSend) + controller.nextTransaction.set(handler: afterSentTransition) + } } + switch strongSelf.mode { + case .history: + apply(strongSelf, atDate: nil) + case .scheduled: + if let peer = strongSelf.chatInteraction.peer { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let strongSelf = strongSelf { + apply(strongSelf, atDate: Int32(date.timeIntervalSince1970)) + } + }), for: context.window) + } + } + } } @@ -1161,13 +2113,14 @@ class ChatController: EditableViewController, Notifable { if let message = message { self?.chatInteraction.update({$0.withEditMessage(message)}) } else { - self?.chatInteraction.update({$0.withoutEditMessage()}) + self?.chatInteraction.cancelEditing(true) } + self?.chatInteraction.focusInputField() } chatInteraction.mentionPressed = { [weak self] in if let strongSelf = self { - let signal = earliestUnseenPersonalMentionMessage(postbox: strongSelf.account.postbox, network: strongSelf.account.network, peerId: strongSelf.peerId) + let signal = earliestUnseenPersonalMentionMessage(account: context.account, peerId: strongSelf.chatInteraction.peerId) strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak strongSelf] result in if let strongSelf = strongSelf { switch result { @@ -1175,7 +2128,7 @@ class ChatController: EditableViewController, Notifable { break case .result(let messageId): if let messageId = messageId { - strongSelf.chatInteraction.focusMessageId(nil, messageId, .center(id: 0, animated: true, focus: true, inset: 0)) + strongSelf.chatInteraction.focusMessageId(nil, messageId, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) } } } @@ -1183,112 +2136,349 @@ class ChatController: EditableViewController, Notifable { } } - chatInteraction.requestMessageActionCallback = {[weak self] messageId, isGame, data in + chatInteraction.clearMentions = { [weak self] in + guard let `self` = self else {return} + _ = clearPeerUnseenPersonalMessagesInteractively(account: context.account, peerId: self.chatInteraction.peerId).start() + } + + chatInteraction.editEditingMessagePhoto = { [weak self] media in + guard let `self` = self else {return} + if let resource = media.representationForDisplayAtSize(PixelDimensions(1280, 1280))?.resource { + _ = (context.account.postbox.mediaBox.resourceData(resource) |> deliverOnMainQueue).start(next: { [weak self] resource in + guard let `self` = self else {return} + let url = URL(fileURLWithPath: link(path:resource.path, ext:kMediaImageExt)!) + let controller = EditImageModalController(url, defaultData: self.chatInteraction.presentation.interfaceState.editState?.editedData) + self.editCurrentMessagePhotoDisposable.set((controller.result |> deliverOnMainQueue).start(next: { [weak self] (new, data) in + guard let `self` = self else {return} + self.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedEditedData(data)})})}) + if new != url { + self.updateMediaDisposable.set((Sender.generateMedia(for: MediaSenderContainer(path: new.path, isFile: false), account: context.account, isSecretRelated: peerId.namespace == Namespaces.Peer.SecretChat) |> deliverOnMainQueue).start(next: { [weak self] media, _ in + self?.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + })) + } else { + self.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + } + + })) + showModal(with: controller, for: context.window, animationType: .scaleCenter) + }) + } + } + + chatInteraction.requestMessageActionCallback = { [weak self] messageId, isGame, data in if let strongSelf = self { - self?.messageActionCallbackDisposable.set((requestMessageActionCallback(account: strongSelf.account, messageId: messageId, isGame:isGame, data: data) |> deliverOnMainQueue).start(next: { [weak strongSelf] (result) in + switch strongSelf.mode { + case .history: + strongSelf.botCallbackAlertMessage.set(.single((L10n.chatInlineRequestLoading, false))) + strongSelf.messageActionCallbackDisposable.set((requestMessageActionCallback(account: context.account, messageId: messageId, isGame:isGame, data: data) |> deliverOnMainQueue).start(next: { [weak strongSelf] (result) in + + if let strongSelf = strongSelf { + switch result { + case .none: + strongSelf.botCallbackAlertMessage.set(.single(("", false))) + case let .toast(text): + strongSelf.botCallbackAlertMessage.set(.single((text, false))) + case let .alert(text): + strongSelf.botCallbackAlertMessage.set(.single((text, true))) + case let .url(url): + if isGame { + strongSelf.navigationController?.push(WebGameViewController(context, strongSelf.chatInteraction.peerId, messageId, url)) + } else { + execute(inapp: .external(link: url, !(strongSelf.chatInteraction.peer?.isVerified ?? false))) + } + } + } + })) + case .scheduled: + break + } + + } + } + + chatInteraction.updateSearchRequest = { [weak self] state in + self?.searchState.set(state) + } + + + chatInteraction.focusMessageId = { [weak self] fromId, toId, state in + + if let strongSelf = self { + + switch strongSelf.mode { + case .history: + if let fromId = fromId { + strongSelf.historyState = strongSelf.historyState.withAddingReply(fromId) + } - if let strongSelf = strongSelf { - switch result { - case .none: - break - case let .alert(text): - let message: Signal = .single(text) - let noMessage: Signal = .single(nil) - let delayedNoMessage: Signal = noMessage |> delay(1.0, queue: Queue.mainQueue()) - strongSelf.botCallbackAlertMessage.set(message |> then(delayedNoMessage)) - case let .url(url): - if isGame { - strongSelf.navigationController?.push(WebGameViewController(strongSelf.account, strongSelf.peerId, messageId, url)) + var fromIndex: MessageIndex? + + if let fromId = fromId, let message = strongSelf.messageInCurrentHistoryView(fromId) { + fromIndex = MessageIndex(message) + } else { + if let message = strongSelf.anchorMessageInCurrentHistoryView() { + fromIndex = MessageIndex(message) + } + } + if let fromIndex = fromIndex { + // if let message = strongSelf.messageInCurrentHistoryView(toId) { + // strongSelf.genericView.tableView.scroll(to: state.swap(to: ChatHistoryEntryId.message(message))) + // } else { + let historyView = chatHistoryViewForLocation(.InitialSearch(location: .id(toId), count: strongSelf.requestCount), account: context.account, chatLocation: strongSelf.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + + struct FindSearchMessage { + let message:Message? + let loaded:Bool + } + + let signal = historyView + |> mapToSignal { historyView -> Signal<(Message?, Bool), NoError> in + switch historyView { + case .Loading: + return .single((nil, true)) + case let .HistoryView(view, _, _, _): + for entry in view.entries { + if entry.message.id == toId { + return .single((entry.message, false)) + } + } + return .single((nil, false)) + } + } |> take(until: { index in + return SignalTakeAction(passthrough: index.0 != nil, complete: !index.1) + }) |> map { $0.0 } + + strongSelf.chatInteraction.loadingMessage.set(.single(true) |> delay(0.2, queue: Queue.mainQueue())) + strongSelf.messageIndexDisposable.set(showModalProgress(signal: signal, for: context.window).start(next: { [weak strongSelf] message in + self?.chatInteraction.loadingMessage.set(.single(false)) + if let strongSelf = strongSelf, let message = message { + let message = message + let toIndex = MessageIndex(message) + strongSelf.setLocation(.Scroll(index: MessageHistoryAnchorIndex.message(toIndex), anchorIndex: MessageHistoryAnchorIndex.message(toIndex), sourceIndex: MessageHistoryAnchorIndex.message(fromIndex), scrollPosition: state.swap(to: ChatHistoryEntryId.message(message)), count: strongSelf.requestCount, animated: state.animated)) + } + }, completed: { + + })) + // } + } + case .scheduled: + strongSelf.navigationController?.back() + (strongSelf.navigationController?.controller as? ChatController)?.chatInteraction.focusMessageId(fromId, toId, state) + } + } + + } + + chatInteraction.vote = { [weak self] messageId, opaqueIdentifiers, submit in + guard let `self` = self else {return} + + self.update { data -> [MessageId : ChatPollStateData] in + var data = data + data[messageId] = ChatPollStateData(identifiers: opaqueIdentifiers, isLoading: submit && !opaqueIdentifiers.isEmpty) + return data + } + + let signal:Signal + + if submit { + if opaqueIdentifiers.isEmpty { + signal = showModalProgress(signal: (requestMessageSelectPollOption(account: context.account, messageId: messageId, opaqueIdentifiers: []) |> deliverOnMainQueue), for: context.window) + } else { + signal = (requestMessageSelectPollOption(account: context.account, messageId: messageId, opaqueIdentifiers: opaqueIdentifiers) |> deliverOnMainQueue) + } + + self.selectMessagePollOptionDisposables.set(signal.start(next: { [weak self] poll in + if let poll = poll { + self?.update { data -> [MessageId : ChatPollStateData] in + var data = data + data.removeValue(forKey: messageId) + return data + } + self?.afterNextTransaction = { [weak self] in + if let tableView = self?.genericView.tableView { + tableView.enumerateVisibleItems(with: { item -> Bool in + if let item = item as? ChatRowItem, item.message?.id == messageId { + let view = item.view as? ChatPollItemView + view?.doAfterAnswer() + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + return false + } + return true + }) + } + } + } + }, error: { [weak self] error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + self?.update { data -> [MessageId : ChatPollStateData] in + var data = data + data.removeValue(forKey: messageId) + return data + } + + }), forKey: messageId) + } + + } + chatInteraction.closePoll = { [weak self] messageId in + guard let `self` = self else {return} + self.selectMessagePollOptionDisposables.set(requestClosePoll(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, messageId: messageId).start(), forKey: messageId) + } + + + chatInteraction.sendMedia = { [weak self] media in + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + + switch strongSelf.mode { + case .scheduled: + showModal(with: ScheduledMessageModalController(context: strongSelf.context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let strongSelf = strongSelf { + let _ = (Sender.enqueue(media: media, context: context, peerId: strongSelf.chatInteraction.peerId, chatInteraction: strongSelf.chatInteraction, atDate: date) |> deliverOnMainQueue).start(completed: scrollAfterSend) + strongSelf.nextTransaction.set(handler: {}) + } + }), for: strongSelf.context.window) + case .history: + let _ = (Sender.enqueue(media: media, context: context, peerId: strongSelf.chatInteraction.peerId, chatInteraction: strongSelf.chatInteraction) |> deliverOnMainQueue).start(completed: scrollAfterSend) + strongSelf.nextTransaction.set(handler: {}) + } + } + } + + chatInteraction.attachFile = { [weak self] asMedia in + if let `self` = self, let window = self.window { + if let slowMode = self.chatInteraction.presentation.slowMode, let errorText = slowMode.errorText { + tooltip(for: self.genericView.inputView.attachView, text: errorText) + if let last = slowMode.sendingIds.last { + self.chatInteraction.focusMessageId(nil, last, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + } else { + filePanel(canChooseDirectories: true, for: window, completion:{ result in + if let result = result { + + let previous = result.count + + let result = result.filter { path -> Bool in + if let size = fs(path) { + return size <= 2000 * 1024 * 1024 + } + return false + } + + let afterSizeCheck = result.count + + if afterSizeCheck == 0 && previous != afterSizeCheck { + alert(for: context.window, info: L10n.appMaxFileSize1) + } else { + self.chatInteraction.showPreviewSender(result.map{URL(fileURLWithPath: $0)}, asMedia, nil) + } + + } + }) + } + } + + } + chatInteraction.attachPhotoOrVideo = { [weak self] in + if let `self` = self, let window = self.window { + if let slowMode = self.chatInteraction.presentation.slowMode, let errorText = slowMode.errorText { + tooltip(for: self.genericView.inputView.attachView, text: errorText) + if let last = slowMode.sendingIds.last { + self.chatInteraction.focusMessageId(nil, last, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + } else { + filePanel(with: mediaExts, canChooseDirectories: true, for: window, completion:{ [weak self] result in + if let result = result { + let previous = result.count + + let result = result.filter { path -> Bool in + if let size = fs(path) { + return size <= 2000 * 1024 * 1024 + } + return false + } + + let afterSizeCheck = result.count + + if afterSizeCheck == 0 && previous != afterSizeCheck { + alert(for: context.window, info: L10n.appMaxFileSize1) } else { - execute(inapp: .external(link: url, !(strongSelf.peer?.isVerified ?? false))) + self?.chatInteraction.showPreviewSender(result.map{URL(fileURLWithPath: $0)}, true, nil) } } + }) + } + } + } + chatInteraction.attachPicture = { [weak self] in + guard let `self` = self else {return} + if let window = self.window { + pickImage(for: window, completion: { [weak self] image in + if let image = image { + self?.chatInteraction.mediaPromise.set(putToTemp(image: image) |> map({[MediaSenderContainer(path:$0)]})) } - })) + }) } } + chatInteraction.attachLocation = { [weak self] in + guard let `self` = self else {return} + showModal(with: LocationModalController(self.chatInteraction), for: context.window) + } + chatInteraction.sendAppFile = { [weak self] file, silent in + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + func apply(_ controller: ChatController, atDate: Date?) { + let _ = (Sender.enqueue(media: file, context: context, peerId: controller.chatInteraction.peerId, chatInteraction: controller.chatInteraction, silent: silent, atDate: atDate) |> deliverOnMainQueue).start(completed: scrollAfterSend) + controller.nextTransaction.set(handler: {}) + } + switch strongSelf.mode { + case .scheduled: + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let controller = strongSelf { + apply(controller, atDate: date) + } + }), for: context.window) + default: + apply(strongSelf, atDate: nil) + } + } + } - chatInteraction.focusMessageId = { [weak self] fromId, toId, state in - - if let strongSelf = self { - if let fromId = fromId { - strongSelf.historyState = strongSelf.historyState.withAddingReply(fromId) + chatInteraction.sendMedias = { [weak self] medias, caption, isCollage, additionText, silent, atDate in + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + func apply(_ controller: ChatController, atDate: Date?) { + let _ = (Sender.enqueue(media: medias, caption: caption, context: context, peerId: controller.chatInteraction.peerId, chatInteraction: controller.chatInteraction, isCollage: isCollage, additionText: additionText, silent: silent, atDate: atDate) |> deliverOnMainQueue).start(completed: scrollAfterSend) + controller.nextTransaction.set(handler: {}) } - - var fromIndex: MessageIndex? - - if let fromId = fromId, let message = strongSelf.messageInCurrentHistoryView(fromId) { - fromIndex = MessageIndex(message) - } else { - if let message = strongSelf.anchorMessageInCurrentHistoryView() { - fromIndex = MessageIndex(message) + switch strongSelf.mode { + case .history: + DispatchQueue.main.async { + if let _ = atDate { + strongSelf.openScheduledChat() + } } - } - if let fromIndex = fromIndex { - if let message = strongSelf.messageInCurrentHistoryView(toId) { - strongSelf.genericView.tableView.scroll(to: state.swap(to: ChatHistoryEntryId.message(message))) + apply(strongSelf, atDate: atDate) + case .scheduled: + if let atDate = atDate { + apply(strongSelf, atDate: atDate) } else { - let historyView = chatHistoryViewForLocation(.InitialSearch(location: .id(toId), count: 50), account: strongSelf.account, peerId: strongSelf.peerId, fixedCombinedReadState: nil, tagMask: nil, additionalData: []) - - struct FindSearchMessage { - let message:Message? - let loaded:Bool - } - - let signal = historyView - |> mapToSignal { historyView -> Signal in - switch historyView { - case .Loading: - return .complete() - case let .HistoryView(view, _, _, _): - for entry in view.entries { - if case let .MessageEntry(message, _, _, _) = entry { - if message.id == toId { - return .single(message) - } - } - } - return .single(nil) - } - } - |> take(1) - strongSelf.messageIndexDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak strongSelf] message in - if let strongSelf = strongSelf, let message = message { - let toIndex = MessageIndex(message) - strongSelf.setLocation(.Scroll(index: toIndex, anchorIndex: toIndex, sourceIndex: fromIndex, scrollPosition: state.swap(to: ChatHistoryEntryId.message(message)), animated: state.animated)) + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let strongSelf = strongSelf { + apply(strongSelf, atDate: date) } - }, completed: { - - })) + }), for: context.window) } } - - } - - } - - chatInteraction.sendMedia = { [weak self] media in - if let strongSelf = self, let peer = strongSelf.peer, peer.canSendMessage { - let _ = (Sender.enqueue(media: media, account: strongSelf.account, peerId: strongSelf.peerId, chatInteraction: strongSelf.chatInteraction) |> deliverOnMainQueue).start(completed: scrollAfterSend) - strongSelf.nextTransaction.set(handler: {}) - } - } - - chatInteraction.sendAppFile = { [weak self] file in - if let strongSelf = self, let peer = strongSelf.peer, peer.canSendMessage { - let _ = (Sender.enqueue(media: file, account: strongSelf.account, peerId: strongSelf.peerId, chatInteraction: strongSelf.chatInteraction) |> deliverOnMainQueue).start(completed: scrollAfterSend) - strongSelf.nextTransaction.set(handler: {}) - } } chatInteraction.shareSelfContact = { [weak self] replyId in - if let strongSelf = self, let peer = strongSelf.peer, peer.canSendMessage { - strongSelf.shareContactDisposable.set((strongSelf.account.viewTracker.peerView(strongSelf.account.peerId) |> take(1)).start(next: { [weak strongSelf] peerView in + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + strongSelf.shareContactDisposable.set((context.account.viewTracker.peerView(context.account.peerId) |> take(1)).start(next: { [weak strongSelf] peerView in if let strongSelf = strongSelf, let peer = peerViewMainPeer(peerView) as? TelegramUser { - - _ = Sender.enqueue(message: EnqueueMessage.message(text: "", attributes: [], media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: peer.phone ?? "", peerId: peer.id), replyToMessageId: replyId), account: strongSelf.account, peerId: strongSelf.peerId).start() + _ = Sender.enqueue(message: EnqueueMessage.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: peer.phone ?? "", peerId: peer.id, vCardData: nil)), replyToMessageId: replyId, localGroupingKey: nil), context: context, peerId: strongSelf.chatInteraction.peerId).start() } })) } @@ -1296,25 +2486,42 @@ class ChatController: EditableViewController, Notifable { chatInteraction.modalSearch = { [weak self] query in if let strongSelf = self { - let apply = showModalProgress(signal: searchMessages(account: strongSelf.account, peerId: strongSelf.peerId, query: query), for: mainWindow) - showModal(with: SearchResultModalController(strongSelf.account, request: apply, query: query, chatInteraction:strongSelf.chatInteraction), for: mainWindow) + strongSelf.chatInteraction.update({$0.updatedSearchMode((true, nil, query))}) + +// let apply = showModalProgress(signal: searchMessages(account: context.account, location: .peer(peerId: strongSelf.chatInteraction.peerId, fromId: nil, tags: nil), query: query, state: nil), for: context.window) +// showModal(with: SearchResultModalController(context, request: apply |> map {$0.0.messages}, query: query, chatInteraction:strongSelf.chatInteraction), for: context.window) } } chatInteraction.sendCommand = { [weak self] command in - if let strongSelf = self, let peer = strongSelf.peer, peer.canSendMessage { - var commandText = "/" + command.command.text - if strongSelf.peerId.namespace != Namespaces.Peer.CloudUser { - commandText += "@" + (command.peer.username ?? "") + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + func apply(_ controller: ChatController, atDate: Date?) { + var commandText = "/" + command.command.text + if controller.chatInteraction.peerId.namespace != Namespaces.Peer.CloudUser { + commandText += "@" + (command.peer.username ?? "") + } + _ = Sender.enqueue(input: ChatTextInputState(inputText: commandText), context: context, peerId: controller.chatLocation.peerId, replyId: controller.chatInteraction.presentation.interfaceState.replyMessageId, atDate: atDate).start(completed: scrollAfterSend) + controller.chatInteraction.updateInput(with: "") + controller.nextTransaction.set(handler: afterSentTransition) + } + switch strongSelf.mode { + case .scheduled: + DispatchQueue.main.async { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak strongSelf] date in + if let strongSelf = strongSelf { + apply(strongSelf, atDate: date) + } + }), for: context.window) + } + case .history: + apply(strongSelf, atDate: nil) } - strongSelf.chatInteraction.updateInput(with: "") - let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [EnqueueMessage.message(text: commandText, attributes:[], media: nil, replyToMessageId: nil)]).start() } } chatInteraction.switchInlinePeer = { [weak self] switchId, initialAction in if let strongSelf = self { - strongSelf.navigationController?.push(ChatSwitchInlineController(account: strongSelf.account, peerId: switchId, fallbackId:strongSelf.peerId, initialAction: initialAction)) + strongSelf.navigationController?.push(ChatSwitchInlineController(context: context, peerId: switchId, fallbackId:strongSelf.chatInteraction.peerId, fallbackMode: strongSelf.mode, initialAction: initialAction)) } } @@ -1322,98 +2529,224 @@ class ChatController: EditableViewController, Notifable { self?.navigationController?.set(modalAction: action) } - chatInteraction.showPreviewSender = { [weak self] urls, asMedia in - if let chatInteraction = self?.chatInteraction, let window = self?.navigationController?.window, let account = self?.account { - showModal(with: PreviewSenderController(urls: urls, account: account, chatInteraction: chatInteraction, asMedia: asMedia), for: window) + chatInteraction.showPreviewSender = { [weak self] urls, asMedia, attributedString in + if let `self` = self { + if let slowMode = self.chatInteraction.presentation.slowMode, let errorText = slowMode.errorText { + tooltip(for: self.genericView.inputView.attachView, text: errorText) + if !slowMode.sendingIds.isEmpty { + self.chatInteraction.focusMessageId(nil, slowMode.sendingIds.last!, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + } else { + showModal(with: PreviewSenderController(urls: urls, chatInteraction: self.chatInteraction, asMedia: asMedia, attributedString: attributedString), for: context.window) + } } } chatInteraction.setSecretChatMessageAutoremoveTimeout = { [weak self] seconds in - if let strongSelf = self, let peer = strongSelf.peer, peer.canSendMessage { - _ = setSecretChatMessageAutoremoveTimeoutInteractively(account: strongSelf.account, peerId: strongSelf.peerId, timeout:seconds).start() + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer, peer.canSendMessage { + _ = setSecretChatMessageAutoremoveTimeoutInteractively(account: context.account, peerId: strongSelf.chatInteraction.peerId, timeout:seconds).start() } + scrollAfterSend() } - chatInteraction.toggleNotifications = { [weak self] in + chatInteraction.toggleNotifications = { [weak self] isMuted in if let strongSelf = self { - _ = togglePeerMuted(account: strongSelf.account, peerId: strongSelf.peerId).start() + if isMuted == nil || isMuted == true { + _ = togglePeerMuted(account: context.account, peerId: strongSelf.chatInteraction.peerId).start() + } else { + var options:[ModalOptionSet] = [] + + options.append(ModalOptionSet(title: L10n.chatListMute1Hour, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute4Hours, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute8Hours, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute1Day, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute3Days, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMuteForever, selected: true, editable: true)) + + var intervals:[Int32] = [60 * 60, 60 * 60 * 4, 60 * 60 * 8, 60 * 60 * 24, 60 * 60 * 24 * 3, Int32.max] + + showModal(with: ModalOptionSetController(context: context, options: options, selectOne: true, actionText: (L10n.chatInputMute, theme.colors.accent), title: L10n.peerInfoNotifications, result: { result in + + for (i, option) in result.enumerated() { + inner: switch option { + case .selected: + _ = updatePeerMuteSetting(account: context.account, peerId: strongSelf.chatInteraction.peerId, muteInterval: intervals[i]).start() + break + default: + break inner + } + } + + }), for: context.window) + } } } + chatInteraction.openDiscussion = { [weak self] in + guard let `self` = self else { return } + let signal = showModalProgress(signal: context.account.viewTracker.peerView(self.chatLocation.peerId) |> filter { $0.cachedData is CachedChannelData } |> map { $0.cachedData as! CachedChannelData } |> take(1) |> deliverOnMainQueue, for: context.window) + self.discussionDataLoadDisposable.set(signal.start(next: { [weak self] cachedData in + if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + self?.chatInteraction.openInfo(linkedDiscussionPeerId, true, nil, nil) + } + })) + } + chatInteraction.removeAndCloseChat = { [weak self] in if let strongSelf = self, let window = strongSelf.window { - _ = showModalProgress(signal: removePeerChat(postbox: strongSelf.account.postbox, peerId: strongSelf.peerId, reportChatSpam: false), for: window).start(next: { [weak strongSelf] in + _ = showModalProgress(signal: removePeerChat(account: context.account, peerId: strongSelf.chatInteraction.peerId, reportChatSpam: false), for: window).start(next: { [weak strongSelf] in strongSelf?.navigationController?.close() }) } } + chatInteraction.removeChatInteractively = { [weak self] in + if let strongSelf = self { + let signal = removeChatInteractively(context: context, peerId: strongSelf.chatInteraction.peerId, userId: strongSelf.chatInteraction.peer?.id) |> filter {$0} |> mapToSignal { _ -> Signal in + return context.globalPeerHandler.get() |> take(1) + } |> deliverOnMainQueue + + strongSelf.deleteChatDisposable.set(signal.start(next: { [weak strongSelf] location in + if location == strongSelf?.chatInteraction.chatLocation { + strongSelf?.context.sharedContext.bindings.rootNavigation().close() + } + })) + } + } + chatInteraction.joinChannel = { [weak self] in if let strongSelf = self, let window = strongSelf.window { - _ = showModalProgress(signal: joinChannel(account: strongSelf.account, peerId: strongSelf.peerId), for: window).start() + _ = showModalProgress(signal: joinChannel(account: context.account, peerId: strongSelf.chatInteraction.peerId) |> deliverOnMainQueue, for: window).start(error: { error in + let text: String + switch error { + case .generic: + text = L10n.unknownError + case .tooMuchJoined: + showInactiveChannels(context: context, source: .join) + return + } + alert(for: context.window, info: text) + }) } } chatInteraction.returnGroup = { [weak self] in if let strongSelf = self, let window = strongSelf.window { - _ = showModalProgress(signal: returnGroup(account: strongSelf.account, peerId: strongSelf.peerId), for: window).start() + _ = showModalProgress(signal: returnGroup(account: context.account, peerId: strongSelf.chatInteraction.peerId), for: window).start() } } + chatInteraction.openScheduledMessages = { [weak self] in + self?.openScheduledChat() + } + chatInteraction.openBank = { card in + + _ = showModalProgress(signal: getBankCardInfo(account: context.account, cardNumber: card), for: context.window).start(next: { info in + if let info = info { + + let values: [ValuesSelectorValue] = info.urls.map { + return ValuesSelectorValue(localized: $0.title, value: $0.url) + } + + showModal(with: ValuesSelectorModalController(values: values, selected: nil, title: info.title, onComplete: { selected in + execute(inapp: .external(link: selected.value, false)) + }), for: context.window) + + } + }) + } chatInteraction.shareContact = { [weak self] peer in - if let strongSelf = self, let main = strongSelf.peer, main.canSendMessage { - _ = Sender.shareContact(account: strongSelf.account, peerId: strongSelf.peerId, contact: peer).start() + if let strongSelf = self, let main = strongSelf.chatInteraction.peer, main.canSendMessage { + _ = Sender.shareContact(context: context, peerId: strongSelf.chatInteraction.peerId, contact: peer).start() } } chatInteraction.unblock = { [weak self] in if let strongSelf = self { - self?.unblockDisposable.set(requestUpdatePeerIsBlocked(account: strongSelf.account, peerId: strongSelf.peerId, isBlocked: false).start()) + strongSelf.unblockDisposable.set(context.blockedPeersContext.remove(peerId: strongSelf.chatInteraction.peerId).start()) } } chatInteraction.updatePinned = { [weak self] pinnedId, dismiss, silent in - if let strongSelf = self, let peer = strongSelf.peer as? TelegramChannel { - if peer.hasAdminRights(.canPinMessages) { - - let pinnedUpdate: PinnedMessageUpdate = dismiss ? .clear : .pin(id: pinnedId, silent: silent) - - strongSelf.updatePinnedDisposable.set(((dismiss ? confirmSignal(for: mainWindow, header: appName, information: tr(.chatConfirmUnpin)) : Signal.single(true)) |> filter {$0} |> mapToSignal { _ in return showModalProgress(signal: requestUpdatePinnedMessage(account: strongSelf.account, peerId: strongSelf.peerId, update: pinnedUpdate) |> mapError {_ in}, for: mainWindow)}).start()) - } else { - strongSelf.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedDismissedPinnedId(pinnedId)})}) + if let `self` = self { + + let pinnedUpdate: PinnedMessageUpdate = dismiss ? .clear : .pin(id: pinnedId, silent: silent) + let peerId = self.chatInteraction.peerId + if let peer = self.chatInteraction.peer as? TelegramChannel { + if peer.hasPermission(.pinMessages) || (peer.isChannel && peer.hasPermission(.editAllMessages)) { + + self.updatePinnedDisposable.set(((dismiss ? confirmSignal(for: context.window, header: L10n.chatConfirmUnpinHeader, information: L10n.chatConfirmUnpin, okTitle: L10n.chatConfirmUnpinOK) : Signal.single(true)) |> filter {$0} |> mapToSignal { _ in return + showModalProgress(signal: requestUpdatePinnedMessage(account: context.account, peerId: peerId, update: pinnedUpdate) |> `catch` {_ in .complete() + }, for: context.window)}).start()) + } else { + self.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedDismissedPinnedId(pinnedId)})}) + } + } else if self.chatInteraction.peerId == context.peerId { + if dismiss { + confirm(for: context.window, header: L10n.chatConfirmUnpinHeader, information: L10n.chatConfirmUnpin, okTitle: L10n.chatConfirmUnpinOK, successHandler: { _ in + self.updatePinnedDisposable.set(showModalProgress(signal: requestUpdatePinnedMessage(account: context.account, peerId: peerId, update: pinnedUpdate), for: context.window).start()) + }) + } else { + self.updatePinnedDisposable.set(showModalProgress(signal: requestUpdatePinnedMessage(account: context.account, peerId: peerId, update: pinnedUpdate), for: context.window).start()) + } + } else if let peer = self.chatInteraction.peer as? TelegramGroup, peer.canPinMessage { + if dismiss { + confirm(for: context.window, header: L10n.chatConfirmUnpinHeader, information: L10n.chatConfirmUnpin, okTitle: L10n.chatConfirmUnpinOK, successHandler: { _ in + self.updatePinnedDisposable.set(showModalProgress(signal: requestUpdatePinnedMessage(account: context.account, peerId: peerId, update: pinnedUpdate), for: context.window).start()) + }) + } else { + self.updatePinnedDisposable.set(showModalProgress(signal: requestUpdatePinnedMessage(account: context.account, peerId: peerId, update: pinnedUpdate), for: context.window).start()) + } } } } chatInteraction.reportSpamAndClose = { [weak self] in if let strongSelf = self { - strongSelf.reportPeerDisposable.set((showModalProgress(signal: reportPeer(account: strongSelf.account, peerId: strongSelf.peerId) |> deliverOnMainQueue |> mapToSignal { [weak strongSelf] () -> Signal in - if let strongSelf = strongSelf, let peer = strongSelf.peer { - if peer.id.namespace == Namespaces.Peer.CloudUser { - return requestUpdatePeerIsBlocked(account: strongSelf.account, peerId: peer.id, isBlocked: true) |> deliverOnMainQueue |> mapToSignal { [weak strongSelf] () -> Signal in - if let strongSelf = strongSelf { - return removePeerChat(postbox: strongSelf.account.postbox, peerId: strongSelf.peerId, reportChatSpam: false) + + let title: String + if let peer = strongSelf.chatInteraction.peer { + if peer.isUser { + title = L10n.chatConfirmReportSpamUser + } else if peer.isChannel { + title = L10n.chatConfirmReportSpamChannel + } else if peer.isGroup || peer.isSupergroup { + title = L10n.chatConfirmReportSpamGroup + } else { + title = L10n.chatConfirmReportSpam + } + } else { + title = L10n.chatConfirmReportSpam + } + + strongSelf.reportPeerDisposable.set((confirmSignal(for: context.window, header: L10n.chatConfirmReportSpamHeader, information: title, okTitle: L10n.messageContextReport, cancelTitle: L10n.modalCancel) |> filter {$0} |> mapToSignal { _ in + return reportPeer(account: context.account, peerId: strongSelf.chatInteraction.peerId) |> deliverOnMainQueue |> mapToSignal { [weak self] _ -> Signal in + if let strongSelf = self, let peer = strongSelf.chatInteraction.peer { + if peer.id.namespace == Namespaces.Peer.CloudUser { + return removePeerChat(account: context.account, peerId: strongSelf.chatInteraction.peerId, reportChatSpam: false) |> deliverOnMainQueue + |> mapToSignal { _ in + return context.blockedPeersContext.add(peerId: peer.id) |> `catch` { _ in return .complete() } |> mapToSignal { _ in + return .complete() + } } - return .complete() + } else { + return removePeerChat(account: context.account, peerId: strongSelf.chatInteraction.peerId, reportChatSpam: true) } - } else { - return removePeerChat(postbox: strongSelf.account.postbox, peerId: strongSelf.peerId, reportChatSpam: true) } + return .complete() } - return .complete() - }, for: mainWindow) |> deliverOnMainQueue).start(completed: { [weak strongSelf] in - if let strongSelf = strongSelf { - strongSelf.navigationController?.back() - } + |> deliverOnMainQueue + }).start(completed: { [weak self] in + self?.navigationController?.back() })) } } - chatInteraction.dismissPeerReport = { [weak self] in + chatInteraction.dismissPeerStatusOptions = { [weak self] in if let strongSelf = self { - _ = dismissReportPeer(account:strongSelf.account, peerId:strongSelf.peerId).start() + _ = dismissPeerStatusOptions(account: context.account, peerId: strongSelf.chatInteraction.peerId).start() } } @@ -1423,24 +2756,165 @@ class ChatController: EditableViewController, Notifable { (self?.navigationController as? MajorNavigationController)?.genericView.update() } + chatInteraction.focusInputField = { [weak self] in + _ = self?.context.window.makeFirstResponder(self?.firstResponder()) + } + + chatInteraction.updateReactions = { [weak self] messageId, reaction, loading in + guard let `self` = self else { + return + } + self.updateReqctionsDisposable.set((updateMessageReactionsInteractively(postbox: self.context.account.postbox, messageId: messageId, reaction: reaction) |> deliverOnMainQueue).start(), forKey: messageId) + } + chatInteraction.withToggledSelectedMessage = { [weak self] f in + guard let `self` = self else { + return + } + let previous = self.chatInteraction.presentation.selectionState?.selectedIds + self.chatInteraction.update(f) + + if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift) { + if let selectionState = self.chatInteraction.presentation.selectionState, let lastMessageId = selectionState.lastSelectedId, let previous = previous { + if let messageId = selectionState.selectedIds.subtracting(previous).first { + let minId = min(lastMessageId.id, messageId.id) + let maxId = max(lastMessageId.id, messageId.id) + let cloudNamespace = self.mode == .scheduled ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud + let localNamespace = self.mode == .scheduled ? Namespaces.Message.ScheduledLocal : Namespaces.Message.Local + let selectMessages = context.account.postbox.transaction { transaction -> [Message] in + var messages:[Message] = [] + for id in minId ..< maxId { + let cloudId = MessageId(peerId: lastMessageId.peerId, namespace: cloudNamespace, id: id) + let localId = MessageId(peerId: lastMessageId.peerId, namespace: localNamespace, id: id) + let message = transaction.getMessage(cloudId) ?? transaction.getMessage(localId) + if let message = message { + if minId > maxId { + messages.append(message) + } else { + messages.insert(message, at: 0) + } + } + } + return messages + } |> deliverOnMainQueue + + self.shiftSelectedDisposable.set(selectMessages.start(next: { [weak self] messages in + guard let `self` = self else { + return + } + self.chatInteraction.update({ current in + var current = current + if let selectionState = current.selectionState, selectionState.selectedIds.count >= 100 { + return current + } + for message in messages { + current = current.withUpdatedSelectedMessage(message.id) + } + + return current + }) + })) + } + } + } + } + + chatInteraction.getGradientOffsetRect = { [weak self] in + guard let `self` = self else { + return .zero + } + let point = self.genericView.tableView.scrollPosition().current.rect.origin + return CGRect(origin: point, size: self.frame.size) + } + + chatInteraction.closeAfterPeek = { [weak self] peek in + + let showConfirm:()->Void = { + confirm(for: context.window, header: L10n.privateChannelPeekHeader, information: L10n.privateChannelPeekText, okTitle: L10n.privateChannelPeekOK, cancelTitle: L10n.privateChannelPeekCancel, successHandler: { _ in + self?.chatInteraction.joinChannel() + }, cancelHandler: { + self?.navigationController?.back() + }) + } + + let timeout = TimeInterval(peek) - Date().timeIntervalSince1970 + if timeout > 0 { + let signal = Signal.complete() |> delay(timeout, queue: .mainQueue()) + self?.peekDisposable.set(signal.start(completed: showConfirm)) + } else { + showConfirm() + } + } let initialData = initialDataHandler.get() |> take(1) |> beforeNext { [weak self] (combinedInitialData) in - if let strongSelf = self { + if let `self` = self { if let initialData = combinedInitialData.initialData { if let interfaceState = initialData.chatInterfaceState as? ChatInterfaceState { - strongSelf.chatInteraction.update(animated:false,{$0.updatedInterfaceState({_ in return interfaceState})}) - strongSelf.chatInteraction.invokeInitialAction(includeAuto: true) + self.chatInteraction.update(animated:false,{$0.updatedInterfaceState({_ in return interfaceState})}) + } + switch self.chatInteraction.mode { + case .history: + self.chatInteraction.update(animated:false,{ present in + var present = present + if let cachedData = combinedInitialData.cachedData as? CachedUserData { + present = present + .withUpdatedBlocked(cachedData.isBlocked) + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages) + } else if let cachedData = combinedInitialData.cachedData as? CachedChannelData { + present = present + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) + .withUpdatedIsNotAccessible(cachedData.isNotAccessible) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages) + if let peer = present.peer as? TelegramChannel { + switch peer.info { + case let .group(info): + if info.flags.contains(.slowModeEnabled), peer.adminRights == nil && !peer.flags.contains(.isCreator) { + present = present.updateSlowMode({ value in + var value = value ?? SlowMode() + value = value.withUpdatedValidUntil(cachedData.slowModeValidUntilTimestamp) + if let timeout = cachedData.slowModeValidUntilTimestamp { + if timeout > context.timestamp { + value = value.withUpdatedTimeout(timeout - context.timestamp) + } + } + return value + }) + } else { + present = present.updateSlowMode { _ in return nil } + } + default: + present = present.updateSlowMode { _ in return nil } + } + } + + + } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { + present = present + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages) + } else { + present = present.withUpdatedPinnedMessageId(nil) + } + if let messageId = present.pinnedMessageId { + present = present.withUpdatedCachedPinnedMessage(combinedInitialData.cachedDataMessages?[messageId]) + } + return present.withUpdatedLimitConfiguration(combinedInitialData.limitsConfiguration) + }) + case .scheduled: + break } - if let modalAction = strongSelf.navigationController?.modalAction { - strongSelf.invokeNavigation(action: modalAction) + if let modalAction = self.navigationController?.modalAction { + self.invokeNavigation(action: modalAction) } - strongSelf.state = strongSelf.chatInteraction.presentation.state == .selecting ? .Edit : .Normal - strongSelf.notify(with: strongSelf.chatInteraction.presentation, oldValue: ChatPresentationInterfaceState(), animated: false, force: true) - strongSelf.genericView.inputView.updateInterface(with: strongSelf.chatInteraction, account: strongSelf.account) + self.state = self.chatInteraction.presentation.state == .selecting ? .Edit : .Normal + self.notify(with: self.chatInteraction.presentation, oldValue: ChatPresentationInterfaceState(self.chatInteraction.chatLocation), animated: false, force: true) + + self.genericView.inputView.updateInterface(with: self.chatInteraction) + } } @@ -1448,192 +2922,361 @@ class ChatController: EditableViewController, Notifable { let first:Atomic = Atomic(value: true) + + peerDisposable.set((peerView.get() - |> deliverOnMainQueue |> beforeNext { [weak self] peerView in + |> deliverOnMainQueue |> beforeNext { [weak self] postboxView in + + guard let `self` = self else {return} + (self.centerBarView as? ChatTitleBarView)?.postboxView = postboxView - if let strongSelf = self { - if let peer = peerViewMainPeer(peerView) { - strongSelf.peer = peer - (strongSelf.centerBarView as? ChatTitleBarView)?.peerView = peerView + switch self.chatLocation { + case .peer: + let peerView = postboxView as? PeerView + + if let cachedData = peerView?.cachedData as? CachedChannelData { + let onlineMemberCount:Signal + if (cachedData.participantsSummary.memberCount ?? 0) > 200 { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: self.chatInteraction.peerId) |> map(Optional.init) |> deliverOnMainQueue + } else { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: self.chatInteraction.peerId) |> map(Optional.init) |> deliverOnMainQueue + } + + self.onlineMemberCountDisposable.set(onlineMemberCount.start(next: { [weak self] count in + (self?.centerBarView as? ChatTitleBarView)?.onlineMemberCount = count + })) } - strongSelf.chatInteraction.update(animated: !first.swap(false), { [weak peerView] presentation in - if let peerView = peerView { - var present = presentation.updatedPeer { [weak peerView] _ in - if let peerView = peerView { - return peerView.peers[peerView.peerId] - } - return nil - } - - if let cachedData = peerView.cachedData as? CachedUserData { - present = present.withUpdatedBlocked(cachedData.isBlocked).withUpdatedReportStatus(cachedData.reportStatus) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - present = present.withUpdatedReportStatus(cachedData.reportStatus).withUpdatedPinnedMessageId(cachedData.pinnedMessageId) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - present = present.withUpdatedReportStatus(cachedData.reportStatus) - } else if let cachedData = peerView.cachedData as? CachedSecretChatData { - present = present.withUpdatedReportStatus(cachedData.reportStatus) + + switch self.chatInteraction.mode { + case .history: + + + var wasGroupChannel: Bool? + if let peer = self.chatInteraction.presentation.mainPeer as? TelegramChannel { + if case .group = peer.info { + wasGroupChannel = true + } else { + wasGroupChannel = false } - - var canAddContact:Bool? = nil - if let peer = peerViewMainPeer(peerView) as? TelegramUser { - if let _ = peer.phone, !peerView.peerIsContact { - canAddContact = true - } + } + var isGroupChannel: Bool? + if let peerView = peerView, let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false } - present = present.withUpdatedContactAdding(canAddContact) - - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - present = present.updatedNotificationSettings(notificationSettings) + } + + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: chatInteraction.peerId, updated: { _ in }) + let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: chatInteraction.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + + self.updatedChannelParticipants.set(disposable) + } else { + self.updatedChannelParticipants.set(nil) } - return present + } - return presentation - }) + + self.chatInteraction.update(animated: !first.swap(false), { [weak peerView] presentation in + if let peerView = peerView { + var present = presentation.updatedPeer { [weak peerView] _ in + if let peerView = peerView { + return peerView.peers[peerView.peerId] + } + return nil + }.updatedMainPeer(peerViewMainPeer(peerView)) + + var discussionGroupId:PeerId? = nil + if let cachedData = peerView.cachedData as? CachedChannelData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + if let peer = peerViewMainPeer(peerView) as? TelegramChannel { + switch peer.info { + case let .broadcast(info): + if info.flags.contains(.hasDiscussionGroup) { + discussionGroupId = linkedDiscussionPeerId + } + default: + break + } + } + } + + present = present.withUpdatedDiscussionGroupId(discussionGroupId) + + var contactStatus: ChatPeerStatus? + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatPeerStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + contactStatus = ChatPeerStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + contactStatus = ChatPeerStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings) + } else if let cachedData = peerView.cachedData as? CachedSecretChatData { + contactStatus = ChatPeerStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings) + } + if let cachedData = peerView.cachedData as? CachedUserData { + present = present + .withUpdatedBlocked(cachedData.isBlocked) + .withUpdatedPeerStatusSettings(contactStatus) + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages && !(present.peer is TelegramSecretChat)) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + present = present + .withUpdatedPeerStatusSettings(contactStatus) + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) + .withUpdatedIsNotAccessible(cachedData.isNotAccessible) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages) + if let peer = peerViewMainPeer(peerView) as? TelegramChannel { + switch peer.info { + case let .group(info): + if info.flags.contains(.slowModeEnabled), peer.adminRights == nil && !peer.flags.contains(.isCreator) { + present = present.updateSlowMode({ value in + var value = value ?? SlowMode() + value = value.withUpdatedValidUntil(cachedData.slowModeValidUntilTimestamp) + if let timeout = cachedData.slowModeValidUntilTimestamp { + value = value.withUpdatedTimeout(timeout - context.timestamp) + } + return value + }) + } else { + present = present.updateSlowMode { _ in return nil } + } + default: + present = present.updateSlowMode { _ in return nil } + } + } + } else if let cachedData = peerView.cachedData as? CachedGroupData { + present = present + .withUpdatedPeerStatusSettings(contactStatus) + .withUpdatedPinnedMessageId(cachedData.pinnedMessageId) +// .withUpdatedHasScheduled(cachedData.hasScheduledMessages) + } else if let _ = peerView.cachedData as? CachedSecretChatData { + present = present + .withUpdatedPeerStatusSettings(contactStatus) + } + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + present = present.updatedNotificationSettings(notificationSettings) + } + return present + } + return presentation + }) + case .scheduled: + self.chatInteraction.update(animated: !first.swap(false), { presentation in + return presentation.updatedPeer { _ in + if let peerView = peerView { + return peerView.peers[peerView.peerId] + } + return nil + }.updatedMainPeer(peerView != nil ? peerViewMainPeer(peerView!) : nil) + }) + } } + + }).start()) - if peerId.namespace == Namespaces.Peer.CloudChannel { - - - let fetchParticipants = peerView.get() |> filter {$0.cachedData != nil} |> take(1) |> deliverOnMainQueue |> mapToSignal { [weak self] _ -> Signal in - if let account = self?.account, let peerId = self?.peerId { - return account.viewTracker.updatedCachedChannelParticipants(peerId, forceImmediateUpdate: true) - } - return .complete() - - } - - updatedChannelParticipants.set(fetchParticipants.start()) - } + + + let updating: Signal = context.account.stateManager.isUpdating |> mapToSignal { isUpdating in + return isUpdating ? .single(isUpdating) |> delay(1.0, queue: .mainQueue()) : .single(isUpdating) + } + let connecting: Signal = context.account.network.connectionStatus |> mapToSignal { status in + switch status { + case .online: + return .single(status) + default: + return .single(status) |> delay(1.0, queue: .mainQueue()) + } + } - let connectionStatus = account.network.connectionStatus |> deliverOnMainQueue |> beforeNext { [weak self] status -> Void in + let connectionStatus = combineLatest(queue: .mainQueue(), connecting, updating) |> deliverOnMainQueue |> beforeNext { [weak self] status, isUpdating -> Void in + var status = status + switch status { + case let .online(proxyAddress): + if isUpdating { + status = .updating(proxyAddress: proxyAddress) + } + default: + break + } (self?.centerBarView as? ChatTitleBarView)?.connectionStatus = status } let combine = combineLatest(_historyReady.get() |> deliverOnMainQueue , peerView.get() |> deliverOnMainQueue |> take(1) |> map {_ in} |> then(initialData), genericView.inputView.ready.get()) + + //self.ready.set(.single(true)) + self.ready.set(combine |> map { (hReady, _, iReady) in return hReady && iReady }) - connectionStatusDisposable.set((connectionStatus |> delay(0.5, queue: Queue.mainQueue())).start()) + connectionStatusDisposable.set((connectionStatus).start()) var beginPendingTime:CFAbsoluteTime? - self.sentMessageEventsDisposable.set((self.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId)).start(next: { _ in - - if FastSettings.inAppSounds { - let afterSentSound:NSSound? = { - - let p = Bundle.main.path(forResource: "sent", ofType: "caf") - var sound:NSSound? - if let p = p { - sound = NSSound(contentsOfFile: p, byReference: true) - sound?.volume = 1.0 - } - - return sound - }() + + switch chatLocation { + case let .peer(peerId): + self.sentMessageEventsDisposable.set((context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) |> deliverOn(Queue.concurrentDefaultQueue())).start(next: { _ in - if let beginPendingTime = beginPendingTime { - if CFAbsoluteTimeGetCurrent() - beginPendingTime < 0.2 { - return + if FastSettings.inAppSounds { + let afterSentSound:NSSound? = { + + let p = Bundle.main.path(forResource: "sent", ofType: "caf") + var sound:NSSound? + if let p = p { + sound = NSSound(contentsOfFile: p, byReference: true) + sound?.volume = 1.0 + } + + return sound + }() + + if let beginPendingTime = beginPendingTime { + if CFAbsoluteTimeGetCurrent() - beginPendingTime < 0.5 { + return + } } + beginPendingTime = CFAbsoluteTimeGetCurrent() + afterSentSound?.play() } - beginPendingTime = CFAbsoluteTimeGetCurrent() - afterSentSound?.play() - } - })) - - botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() - |> deliverOnMainQueue).start(next: { [weak self] message in - if let strongSelf = self, let message = message, !message.isEmpty { - strongSelf.show(toaster: ControllerToaster(text:.initialize(string: message.fixed, color: theme.colors.text, font: .normal(.text)))) - } - }) - - - self.chatUnreadMentionCountDisposable.set((self.account.viewTracker.unseenPersonalMessagesCount(peerId: self.peerId) |> deliverOnMainQueue).start(next: { [weak self] count in - self?.genericView.updateMentionsCount(count, animated: true) - })) - - let postbox = self.account.postbox - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) - self.peerInputActivitiesDisposable.set((self.account.peerInputActivities(peerId: peerId) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in - var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] - previousPeerCache.with { dict -> Void in - for (peerId, activity) in activities { - if let peer = dict[peerId] { - cachedResult.append((peer, activity)) + })) + + botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() + |> deliverOnMainQueue).start(next: { [weak self] (message, isAlert) in + + if let strongSelf = self, let message = message { + if !message.isEmpty { + if isAlert { + alert(for: context.window, info: message) + } else { + strongSelf.show(toaster: ControllerToaster(text:.initialize(string: message.fixed, color: theme.colors.text, font: .normal(.text)))) + } } else { - foundAllPeers = false - break + strongSelf.removeToaster() } } - } - if foundAllPeers { - return .single(cachedResult) - } else { - return postbox.modify { modifier -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] + + }) + + + self.chatUnreadMentionCountDisposable.set((context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in + self?.genericView.updateMentionsCount(count, animated: true) + })) + + let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + self.peerInputActivitiesDisposable.set((context.account.peerInputActivities(peerId: peerId) + |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in + var foundAllPeers = true + var cachedResult: [(Peer, PeerInputActivity)] = [] + previousPeerCache.with { dict -> Void in for (peerId, activity) in activities { - if let peer = modifier.getPeer(peerId) { - result.append((peer, activity)) - peerCache[peerId] = peer + if let peer = dict[peerId] { + cachedResult.append((peer, activity)) + } else { + foundAllPeers = false + break } } - _ = previousPeerCache.swap(peerCache) - return result + } + if foundAllPeers { + return .single(cachedResult) + } else { + return context.account.postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in + var result: [(Peer, PeerInputActivity)] = [] + var peerCache: [PeerId: Peer] = [:] + for (peerId, activity) in activities { + if let peer = transaction.getPeer(peerId) { + result.append((peer, activity)) + peerCache[peerId] = peer + } + } + _ = previousPeerCache.swap(peerCache) + return result + } } } - } - |> deliverOnMainQueue).start(next: { [weak self] activities in - if let strongSelf = self { - (strongSelf.centerBarView as? ChatTitleBarView)?.inputActivities = (strongSelf.peerId, activities) - } - })) + |> deliverOnMainQueue).start(next: { [weak self] activities in + if let strongSelf = self, strongSelf.chatInteraction.peerId != strongSelf.context.peerId { + (strongSelf.centerBarView as? ChatTitleBarView)?.inputActivities = (strongSelf.chatInteraction.peerId, activities) + } + })) + } + + + + // var beginHistoryTime:CFAbsoluteTime? + genericView.tableView.setScrollHandler({ [weak self] scroll in - if let strongSelf = self { - let view = strongSelf.previousView.modify({$0}) - if let view = view { - var messageIndex:MessageIndex? - - switch scroll.direction { - case .bottom: - messageIndex = view.originalView.earlierId - case .top: - messageIndex = view.originalView.laterId - case .none: - break + guard let `self` = self else {return} + let view = self.previousView.with {$0?.originalView} + if let view = view { + var messageIndex:MessageIndex? + + + let visible = self.genericView.tableView.visibleRows() + + + + switch scroll.direction { + case .top: + if view.laterId != nil { + for i in visible.min ..< visible.max { + if let item = self.genericView.tableView.item(at: i) as? ChatRowItem { + messageIndex = item.entry.index + break + } + } + } else if view.laterId == nil, !view.holeLater, let locationValue = self.locationValue, !locationValue.isAtUpperBound, view.anchorIndex != .upperBound { + messageIndex = .upperBound(peerId: self.chatInteraction.peerId) + } + case .bottom: + if view.earlierId != nil { + for i in stride(from: visible.max - 1, to: -1, by: -1) { + if let item = self.genericView.tableView.item(at: i) as? ChatRowItem { + messageIndex = item.entry.index + break + } + } } - if let messageIndex = messageIndex { - strongSelf.setLocation(.Navigation(index: messageIndex, anchorIndex: messageIndex)) + case .none: + break + } + if let messageIndex = messageIndex { + let location: ChatHistoryLocation = .Navigation(index: MessageHistoryAnchorIndex.message(messageIndex), anchorIndex: MessageHistoryAnchorIndex.message(messageIndex), count: 100, side: scroll.direction == .bottom ? .upper : .lower) + guard location != self.locationValue else { + return } + self.setLocation(location) } } }) + genericView.tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self else {return} + self.updateInteractiveReading() + })) + genericView.tableView.addScroll(listener: TableScrollListener { [weak self] position in let tableView = self?.genericView.tableView - /* - - */ if let strongSelf = self, let tableView = tableView { if let row = tableView.topVisibleRow, let item = tableView.item(at: row) as? ChatRowItem, let id = item.message?.id { @@ -1644,20 +3287,32 @@ class ChatController: EditableViewController, Notifable { var messageIdsWithViewCount: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] + var unsupportedMessagesIds: [MessageId] = [] + + var hasFailed: Bool = false tableView.enumerateVisibleItems(with: { item in if let item = item as? ChatRowItem { if message == nil { - message = item.message + message = item.messages.last } - if let message = item.message { + + if let message = message, message.flags.contains(.Failed) { + hasFailed = !(message.media.first is TelegramMediaAction) + } + + for message in item.messages { var hasUncocumedMention: Bool = false var hasUncosumedContent: Bool = false + if !hasFailed, message.flags.contains(.Failed) { + hasFailed = !(message.media.first is TelegramMediaAction) + } + if message.tags.contains(.unseenPersonalMessage) { for attribute in message.attributes { if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { - hasUncosumedContent = true + hasUncosumedContent = true } if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { hasUncocumedMention = true @@ -1673,15 +3328,23 @@ class ChatController: EditableViewController, Notifable { break inner } } + if message.media.first is TelegramMediaUnsupported { + unsupportedMessagesIds.append(message.id) + } } - + if let msg = message, let currentMsg = item.messages.last { + if msg.id.namespace == Namespaces.Message.Local && currentMsg.id.namespace == Namespaces.Message.Local { + if msg.id < currentMsg.id { + message = currentMsg + } + } + } } return true }) - - + strongSelf.genericView.updateFailedIds(strongSelf.genericView.failedIds, hasOnScreen: hasFailed, animated: true) if !messageIdsWithViewCount.isEmpty { strongSelf.messageProcessingManager.add(messageIdsWithViewCount) @@ -1690,6 +3353,9 @@ class ChatController: EditableViewController, Notifable { if !messageIdsWithUnseenPersonalMention.isEmpty { strongSelf.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention) } + if !unsupportedMessagesIds.isEmpty { + strongSelf.unsupportedMessageProcessingManager.add(unsupportedMessagesIds) + } if let message = message { strongSelf.updateMaxVisibleReadIncomingMessageIndex(MessageIndex(message)) @@ -1698,25 +3364,124 @@ class ChatController: EditableViewController, Notifable { } }) + + switch self.mode { + case .history: + let failed = context.account.postbox.failedMessageIdsView(peerId: peerId) |> deliverOnMainQueue + + var failedAnimate: Bool = true + failedMessageIdsDisposable.set(failed.start(next: { [weak self] view in + var hasFailed: Bool = false + + self?.genericView.tableView.enumerateVisibleItems(with: { item in + if let item = item as? ChatRowItem { + if let message = item.message, message.flags.contains(.Failed) { + hasFailed = !(message.media.first is TelegramMediaAction) + } + for message in item.messages { + if !hasFailed, message.flags.contains(.Failed) { + hasFailed = !(message.media.first is TelegramMediaAction) + } + } + } + return !hasFailed + }) + + self?.genericView.updateFailedIds(view.ids, hasOnScreen: hasFailed, animated: !failedAnimate) + failedAnimate = true + })) + + + + let hasScheduledMessages = peerView.get() + |> take(1) + |> mapToSignal { view -> Signal in + if let view = view as? PeerView, let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { + return .single(false) + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(.peer(peerId)) + |> map { view, _, _ in + return !view.entries.isEmpty + } + } + } |> deliverOnMainQueue + + hasScheduledMessagesDisposable.set(hasScheduledMessages.start(next: { [weak self] hasScheduledMessages in + self?.chatInteraction.update({ + $0.withUpdatedHasScheduled(hasScheduledMessages) + }) + })) + + default: + break + } + + + + + + let undoSignals = combineLatest(queue: .mainQueue(), context.chatUndoManager.status(for: chatInteraction.peerId, type: .deleteChat), context.chatUndoManager.status(for: chatInteraction.peerId, type: .leftChat), context.chatUndoManager.status(for: chatInteraction.peerId, type: .leftChannel), context.chatUndoManager.status(for: chatInteraction.peerId, type: .deleteChannel)) + + chatUndoDisposable.set(undoSignals.start(next: { [weak self] statuses in + let result: [ChatUndoActionStatus?] = [statuses.0, statuses.1, statuses.2, statuses.3] + for status in result { + if let status = status, status != .cancelled { + self?.navigationController?.close() + break + } + } + })) + + } + + override func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + return genericView.navigationHeaderDidNoticeAnimation(current, previous, animated) + } + + override func updateFrame(_ frame: NSRect, animated: Bool) { + super.updateFrame(frame, animated: animated) + self.genericView.updateFrame(frame, animated: animated) + } + + private func openScheduledChat() { + self.chatInteraction.saveState(scrollState: self.immediateScrollState()) + self.navigationController?.push(ChatScheduleController(context: context, chatLocation: self.chatLocation)) } + @available(OSX 10.12.2, *) + override func makeTouchBar() -> NSTouchBar? { + if let temporaryTouchBar = temporaryTouchBar as? ChatTouchBar { + temporaryTouchBar.updateChatInteraction(self.chatInteraction, textView: self.genericView.inputView.textView.inputView) + } else { + temporaryTouchBar = ChatTouchBar(chatInteraction: self.chatInteraction, textView: self.genericView.inputView.textView.inputView) + } + return temporaryTouchBar as? NSTouchBar + } override func windowDidBecomeKey() { super.windowDidBecomeKey() + if #available(OSX 10.12.2, *) { + (temporaryTouchBar as? ChatTouchBar)?.updateByKeyWindow() + } + updateInteractiveReading() chatInteraction.saveState(scrollState: immediateScrollState()) } override func windowDidResignKey() { super.windowDidResignKey() + if #available(OSX 10.12.2, *) { + (temporaryTouchBar as? ChatTouchBar)?.updateByKeyWindow() + } + updateInteractiveReading() chatInteraction.saveState(scrollState:immediateScrollState()) } private func anchorMessageInCurrentHistoryView() -> Message? { - if let historyView = self.previousView.modify({$0}) { + if let historyView = self.previousView.with({$0}) { let visibleRange = self.genericView.tableView.visibleRows() var index = 0 for entry in historyView.filteredEntries.reversed() { if index >= visibleRange.min && index <= visibleRange.max { - if case let .MessageEntry(message, _, _, _, _) = entry.entry { + if case let .MessageEntry(message, _, _, _, _, _, _) = entry.entry { return message } } @@ -1724,7 +3489,7 @@ class ChatController: EditableViewController, Notifable { } for entry in historyView.filteredEntries { - if let message = entry.entry.message { + if let message = entry.appearance.entry.message { return message } } @@ -1732,10 +3497,22 @@ class ChatController: EditableViewController, Notifable { return nil } + private func updateInteractiveReading() { + let scroll = genericView.tableView.scrollPosition().current + let hasEntries = (self.previousView.with { $0 }?.filteredEntries.count ?? 0) > 1 + if let window = window, window.isKeyWindow, self.historyState.isDownOfHistory && scroll.rect.minY == genericView.tableView.frame.height, hasEntries { + self.interactiveReadingDisposable.set(installInteractiveReadMessagesAction(postbox: context.account.postbox, stateManager: context.account.stateManager, peerId: chatInteraction.peerId)) + } else { + self.interactiveReadingDisposable.set(nil) + } + } + + + private func messageInCurrentHistoryView(_ id: MessageId) -> Message? { - if let historyView = self.previousView.modify({$0}) { + if let historyView = self.previousView.with({$0}) { for entry in historyView.filteredEntries { - if let message = entry.entry.message, message.id == id { + if let message = entry.appearance.entry.message, message.id == id { return message } } @@ -1743,66 +3520,115 @@ class ChatController: EditableViewController, Notifable { return nil } - - func applyTransition(_ transition:TableUpdateTransition, initialData:ChatHistoryCombinedInitialData) { + private var firstLoad: Bool = true + + func applyTransition(_ transition:TableUpdateTransition, view: MessageHistoryView?, initialData:ChatHistoryCombinedInitialData, isLoading: Bool) { - let view = previousView.modify({$0})! - // NSLog("1") - let _ = nextTransaction.execute() - // NSLog("2") + let wasEmpty = genericView.tableView.isEmpty + initialDataHandler.set(.single(initialData)) - // NSLog("3") + + historyState = historyState.withUpdatedStateOfHistory(view?.laterId == nil) + + let oldState = genericView.state + + genericView.change(state: isLoading ? .progress : .visible, animated: view != nil) + + genericView.tableView.merge(with: transition) - // NSLog("4") + + let _ = nextTransaction.execute() + + + if oldState != genericView.state { + genericView.tableView.updateEmpties(animated: view != nil) + } + genericView.tableView.notifyScrollHandlers() - // NSLog("5") - genericView.change(state: .visible, animated: true) - // NSLog("6") - historyState = historyState.withUpdatedStateOfHistory(view.originalView.laterId == nil) - // NSLog("7") - if !view.originalView.entries.isEmpty { + if !transition.isEmpty, let afterNextTransaction = self.afterNextTransaction { + delay(0.1, closure: afterNextTransaction) + self.afterNextTransaction = nil + } + + + if let view = view, !view.entries.isEmpty { let tableView = genericView.tableView - if !tableView.isEmpty { - - var earliest:Message? - var latest:Message? - self.genericView.tableView.enumerateVisibleItems(reversed: true, with: { item -> Bool in - - if let item = item as? ChatRowItem { - earliest = item.message - } - return earliest == nil - }) - - self.genericView.tableView.enumerateVisibleItems { item -> Bool in - - if let item = item as? ChatRowItem { - latest = item.message - } - return latest == nil - } - - if let earliest = earliest, let latest = latest { - account.postbox.updateMessageHistoryViewVisibleRange(view.originalView.id, earliestVisibleIndex: MessageIndex(earliest), latestVisibleIndex: MessageIndex(latest)) - } - } +// if !tableView.isEmpty { +// +// var earliest:Message? +// var latest:Message? +// self.genericView.tableView.enumerateVisibleItems(reversed: true, with: { item -> Bool in +// +// if let item = item as? ChatRowItem { +// earliest = item.message +// } +// return earliest == nil +// }) +// +// self.genericView.tableView.enumerateVisibleItems { item -> Bool in +// +// if let item = item as? ChatRowItem { +// latest = item.message +// } +// return latest == nil +// } +// } - } else if let peer = peer, peer.isBot { + } else if let peer = chatInteraction.peer, peer.isBot { if chatInteraction.presentation.initialAction == nil && self.genericView.state == .visible { chatInteraction.update(animated: false, {$0.updatedInitialAction(ChatInitialAction.start(parameter: "", behavior: .none))}) } } - // NSLog("8") - chatInteraction.update(animated: false, {$0.updatedHistoryCount(genericView.tableView.count - 1).updatedKeyboardButtonsMessage(initialData.buttonKeyboardMessage)}) - // NSLog("9") - if !didSetHistoryReady { - didSetHistoryReady = true - _historyReady.set(.single(true)) + chatInteraction.update(animated: !wasEmpty, { current in + var current = current.updatedHistoryCount(genericView.tableView.count - 1).updatedKeyboardButtonsMessage(initialData.buttonKeyboardMessage) + + if let message = initialData.buttonKeyboardMessage { + if message.requestsSetupReply { + if message.id != current.interfaceState.dismissedForceReplyId { + current = current.updatedInterfaceState({$0.withUpdatedReplyMessageId(message.id)}) + } + } + } + + return current + }) + + readyHistory() + + updateInteractiveReading() + + + self.centerBarView.animates = true + + self.chatInteraction.invokeInitialAction(includeAuto: true, animated: false) + + + genericView.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: transition.animated) + } + }) + + + if firstLoad { + firstLoad = false + + let peerId = self.chatLocation.peerId + + let tags: [MessageTags] = [.photoOrVideo, .file, .webPage, .music, .voiceOrInstantVideo] + + let tabItems: [Signal] = tags.map { tags -> Signal in + return context.account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(.peer(peerId), count: 20, tagMask: tags) + |> ignoreValues + } +// + loadSharedMediaDisposable.set(combineLatest(tabItems).start()) } } + override func getCenterBarViewOnce() -> TitledBarView { return ChatTitleBarView(controller: self, chatInteraction) } @@ -1816,26 +3642,30 @@ class ChatController: EditableViewController, Notifable { editButton?.set(image: theme.icons.chatActions, for: .Normal) editButton?.set(image: theme.icons.chatActionsActive, for: .Highlight) + editButton?.setFrameSize(70, 50) editButton?.center() - doneButton?.set(color: theme.colors.blueUI, for: .Normal) + doneButton?.set(color: theme.colors.accent, for: .Normal) doneButton?.style = navigationButtonStyle } + override func getRightBarViewOnce() -> BarView { let back = BarView(70, controller: self) //MajorBackNavigationBar(self, account: account, excludePeerId: peerId) let editButton = ImageButton() - editButton.disableActions() + // editButton.disableActions() back.addSubview(editButton) self.editButton = editButton // let doneButton = TitleButton() - doneButton.disableActions() + // doneButton.disableActions() doneButton.set(font: .medium(.text), for: .Normal) - doneButton.set(text: tr(.navigationDone), for: .Normal) - doneButton.sizeToFit() + doneButton.set(text: tr(L10n.navigationDone), for: .Normal) + + + _ = doneButton.sizeToFit() back.addSubview(doneButton) doneButton.center() @@ -1848,61 +3678,165 @@ class ChatController: EditableViewController, Notifable { editButton.userInteractionEnabled = false back.set(handler: { [weak self] _ in - if let state = self?.state { - switch state { - case .Normal: - if let button = self?.editButton, let strongSelf = self { - - let account = strongSelf.account - let peerId = strongSelf.peerId - - _ = (strongSelf.peerView.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak strongSelf] peerView in - if let strongSelf = strongSelf { - var items:[SPopoverItem] = [] - - items.append(SPopoverItem(tr(.chatContextInfo), { [weak strongSelf] in - if let strongSelf = strongSelf { - strongSelf.chatInteraction.openInfo(strongSelf.peerId, false, nil, nil) - } + if let window = self?.window { + self?.showRightControls() + } + }, for: .Click) + requestUpdateRightBar() + return back + } + + private func showRightControls() { + switch state { + case .Normal: + if let button = editButton { + let context = self.context + showRightControlsDisposable.set((peerView.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] view in + guard let `self` = self else {return} + var items:[SPopoverItem] = [] + let peerId = self.chatLocation.peerId + switch self.mode { + case .scheduled: + items.append(SPopoverItem(L10n.chatContextClearScheduled, { + confirm(for: context.window, header: L10n.chatContextClearScheduledConfirmHeader, information: L10n.chatContextClearScheduledConfirmInfo, okTitle: L10n.chatContextClearScheduledConfirmOK, successHandler: { _ in + _ = clearHistoryInteractively(postbox: context.account.postbox, peerId: peerId, type: .scheduledMessages).start() + }) + }, theme.icons.chatActionClearHistory)) + case .history: + switch self.chatLocation { + case let .peer(peerId): + guard let peerView = view as? PeerView else {return} + + items.append(SPopoverItem(tr(L10n.chatContextEdit1) + (FastSettings.tooltipAbility(for: .edit) ? " (\(L10n.chatContextEditHelp))" : ""), { [weak self] in + self?.changeState() + }, theme.icons.chatActionEdit)) + + +// items.append(SPopoverItem(L10n.chatContextSharedMedia, { [weak self] in +// guard let `self` = self else {return} +// self.navigationController?.push(PeerMediaController(context: self.context, peerId: self.chatInteraction.peerId)) +// }, theme.icons.chatAttachPhoto)) + + items.append(SPopoverItem(L10n.chatContextInfo, { [weak self] in + self?.chatInteraction.openInfo(peerId, false, nil, nil) }, theme.icons.chatActionInfo)) + + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, !self.isAdChat { + if self.chatInteraction.peerId != context.peerId { + items.append(SPopoverItem(!notificationSettings.isMuted ? L10n.chatContextEnableNotifications : L10n.chatContextDisableNotifications, { [weak self] in + self?.chatInteraction.toggleNotifications(notificationSettings.isMuted) + }, !notificationSettings.isMuted ? theme.icons.chatActionUnmute : theme.icons.chatActionMute)) + } + } + + if let peer = peerViewMainPeer(peerView) { - items.append(SPopoverItem(tr(.chatContextEdit), { [weak strongSelf] in - strongSelf?.changeState() - }, theme.icons.chatActionEdit)) + if let groupId = peerView.groupId, groupId != .root { + items.append(SPopoverItem(L10n.chatContextUnarchive, { + _ = updatePeerGroupIdInteractively(postbox: context.account.postbox, peerId: peerId, groupId: .root).start() + }, theme.icons.chatUnarchive)) + } else { + items.append(SPopoverItem(L10n.chatContextArchive, { + _ = updatePeerGroupIdInteractively(postbox: context.account.postbox, peerId: peerId, groupId: Namespaces.PeerGroup.archive).start() + }, theme.icons.chatArchive)) + } - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - items.append(SPopoverItem(notificationSettings.isMuted ? tr(.chatContextEnableNotifications) : tr(.chatContextDisableNotifications), { [weak strongSelf] in - strongSelf?.chatInteraction.toggleNotifications() - }, notificationSettings.isMuted ? theme.icons.chatActionUnmute : theme.icons.chatActionMute)) + if peer.canSendMessage, peerView.peerId.namespace != Namespaces.Peer.SecretChat { + let text: String + if peer.id != context.peerId { + text = L10n.chatRightContextScheduledMessages + } else { + text = L10n.chatRightContextReminder + } + items.append(SPopoverItem(text, { [weak self] in + self?.openScheduledChat() + }, theme.icons.scheduledInputAction)) } - if let peer = peerViewMainPeer(peerView) { - if peer.isGroup || peer.isUser || (peer.isSupergroup && peer.addressName == nil) { - items.append(SPopoverItem(tr(.chatContextClearHistory), { - confirm(for: mainWindow, with: appName, and: tr(.confirmDeleteChatUser), successHandler: { _ in - _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId).start() + if peer.isGroup || peer.isUser || (peer.isSupergroup && peer.addressName == nil) { + if let peer = peer as? TelegramChannel, peer.flags.contains(.hasGeo) {} else { + items.append(SPopoverItem(L10n.chatContextClearHistory, { [weak self] in + + var thridTitle: String? = nil + + var canRemoveGlobally: Bool = false + if peerId.namespace == Namespaces.Peer.CloudUser && peerId != context.account.peerId && !peer.isBot { + if context.limitConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever { + canRemoveGlobally = true + } + } + + if canRemoveGlobally { + thridTitle = L10n.chatMessageDeleteForMeAndPerson(peer.displayTitle) + } + + modernConfirm(for: context.window, account: context.account, peerId: peer.id, information: peer is TelegramUser ? peer.id == context.peerId ? L10n.peerInfoConfirmClearHistorySavedMesssages : canRemoveGlobally || peerId.namespace == Namespaces.Peer.SecretChat ? L10n.peerInfoConfirmClearHistoryUserBothSides : L10n.peerInfoConfirmClearHistoryUser : L10n.peerInfoConfirmClearHistoryGroup, okTitle: L10n.peerInfoConfirmClear, thridTitle: thridTitle, thridAutoOn: false, successHandler: { result in + self?.addUndoAction(ChatUndoAction(peerId: peerId, type: .clearHistory, action: { status in + switch status { + case .success: + context.chatUndoManager.clearHistoryInteractively(postbox: context.account.postbox, peerId: peerId, type: result == .thrid ? .forEveryone : .forLocalPeer) + break + default: + break + } + })) }) }, theme.icons.chatActionClearHistory)) } } - showPopover(for: button, with: SPopoverViewController(items: items), edge: .maxY, inset: NSMakePoint(0, -65)) - //ContextMenu.show(items: items, view: button, event: event, onShow: {_ in }, onClose: {}) + + let deleteChat = { [weak self] in + guard let `self` = self else {return} + let signal = removeChatInteractively(context: context, peerId: self.chatInteraction.peerId, userId: self.chatInteraction.peer?.id) |> filter {$0} |> mapToSignal { _ -> Signal in + return context.globalPeerHandler.get() |> take(1) + } |> deliverOnMainQueue + + self.deleteChatDisposable.set(signal.start(next: { [weak self] location in + if location == self?.chatInteraction.chatLocation { + self?.context.sharedContext.bindings.rootNavigation().close() + } + })) + } + + let text: String + if peer.isGroup { + text = L10n.chatListContextDeleteAndExit + } else if peer.isChannel { + text = L10n.chatListContextLeaveChannel + } else if peer.isSupergroup { + text = L10n.chatListContextLeaveGroup + } else { + text = L10n.chatListContextDeleteChat + } + + + items.append(SPopoverItem(text, deleteChat, theme.icons.chatActionDeleteChat)) + } - - }) - - + } } - case .Edit: - self?.changeState() - case .Some: - break - } + if !items.isEmpty { + if let popover = button.popover { + popover.hide() + } else { + showPopover(for: button, with: SPopoverViewController(items: items, visibility: 10), edge: .maxY, inset: NSMakePoint(0, -65)) + } + } + })) } - //self?.navigationController?.back() - }, for: .Click) - requestUpdateRightBar() - return back + case .Edit: + changeState() + case .Some: + break + } + } + + private func addUndoAction(_ action: ChatUndoAction) { + + self.context.chatUndoManager.add(action: action) + + self.undoTooltipControl.add(controller: self) + } override func getLeftBarViewOnce() -> BarView { @@ -1913,13 +3847,27 @@ class ChatController: EditableViewController, Notifable { return back } +// override func invokeNavigationBack() -> Bool { +// return !context.closeFolderFirst +// } + override func escapeKeyAction() -> KeyHandlerResult { - var result:KeyHandlerResult = self.chatInteraction.presentation.effectiveInput.inputText.isEmpty ? .rejected : .invokeNext + +// +// if context.closeFolderFirst { +// return .rejected +// } + + if genericView.inputView.textView.inputView.hasMarkedText() { + return .invokeNext + } + + var result:KeyHandlerResult = .rejected if chatInteraction.presentation.state == .selecting { self.changeState() result = .invoked } else if chatInteraction.presentation.state == .editing { - chatInteraction.update({$0.withoutEditMessage()}) + chatInteraction.cancelEditing() result = .invoked } else if case let .contextRequest(request) = chatInteraction.presentation.inputContext { if request.query.isEmpty { @@ -1928,21 +3876,61 @@ class ChatController: EditableViewController, Notifable { chatInteraction.clearContextQuery() } result = .invoked - } else if chatInteraction.presentation.isSearchMode { - chatInteraction.update({$0.updatedSearchMode(false)}) + } else if chatInteraction.presentation.isSearchMode.0 { + chatInteraction.update({$0.updatedSearchMode((false, nil, nil))}) result = .invoked + } else if chatInteraction.presentation.recordingState != nil { + chatInteraction.update({$0.withoutRecordingState()}) + return .invoked + } else if chatInteraction.presentation.interfaceState.replyMessageId != nil { + chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(nil)})}) + return .invoked } return result } override func backKeyAction() -> KeyHandlerResult { - return !self.chatInteraction.presentation.isSearchMode && self.chatInteraction.presentation.effectiveInput.inputText.isEmpty ? .rejected : .invokeNext + + if hasModals() { + return .invokeNext + } + if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift) { + if !selectManager.isEmpty { + _ = selectManager.selectPrevChar() + return .invoked + } + } + + return !self.chatInteraction.presentation.isSearchMode.0 && self.chatInteraction.presentation.effectiveInput.inputText.isEmpty ? .rejected : .invokeNext + } + + override func returnKeyAction() -> KeyHandlerResult { + if let recordingState = chatInteraction.presentation.recordingState { + recordingState.stop() + chatInteraction.mediaPromise.set(recordingState.data) + closeAllModals() + chatInteraction.update({$0.withoutRecordingState()}) + return .invoked + } + return super.returnKeyAction() } override func nextKeyAction() -> KeyHandlerResult { - if !self.chatInteraction.presentation.isSearchMode && chatInteraction.presentation.effectiveInput.inputText.isEmpty { - chatInteraction.openInfo(peerId, false, nil, nil) + + if hasModals() { + return .invokeNext + } + + if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift) { + if !selectManager.isEmpty { + _ = selectManager.selectNextChar() + return .invoked + } + } + + if !self.chatInteraction.presentation.isSearchMode.0 && chatInteraction.presentation.effectiveInput.inputText.isEmpty { + chatInteraction.openInfo(chatInteraction.peerId, false, nil, nil) return .invoked } return .rejected @@ -1950,6 +3938,7 @@ class ChatController: EditableViewController, Notifable { deinit { + failedMessageEventsDisposable.dispose() historyDisposable.dispose() peerDisposable.dispose() updatedChannelParticipants.dispose() @@ -1965,29 +3954,56 @@ class ChatController: EditableViewController, Notifable { peerInputActivitiesDisposable.dispose() connectionStatusDisposable.dispose() messagesActionDisposable.dispose() - openPeerInfoDisposable.dispose() unblockDisposable.dispose() updatePinnedDisposable.dispose() reportPeerDisposable.dispose() focusMessageDisposable.dispose() updateFontSizeDisposable.dispose() - account.context.addRecentlyUsedPeer(peerId: peerId) + context.addRecentlyUsedPeer(peerId: chatInteraction.peerId) loadFwdMessagesDisposable.dispose() chatUnreadMentionCountDisposable.dispose() navigationActionDisposable.dispose() messageIndexDisposable.dispose() dateDisposable.dispose() - account.context.cachedAdminIds.remove(for: peerId) + interactiveReadingDisposable.dispose() + showRightControlsDisposable.dispose() + deleteChatDisposable.dispose() + loadSelectionMessagesDisposable.dispose() + updateMediaDisposable.dispose() + editCurrentMessagePhotoDisposable.dispose() + selectMessagePollOptionDisposables.dispose() + onlineMemberCountDisposable.dispose() + chatUndoDisposable.dispose() + chatInteraction.clean() + discussionDataLoadDisposable.dispose() + slowModeDisposable.dispose() + slowModeInProgressDisposable.dispose() + forwardMessagesDisposable.dispose() + updateReqctionsDisposable.dispose() + shiftSelectedDisposable.dispose() + failedMessageIdsDisposable.dispose() + hasScheduledMessagesDisposable.dispose() + updateUrlDisposable.dispose() + loadSharedMediaDisposable.dispose() + applyMaxReadIndexDisposable.dispose() + peekDisposable.dispose() + _ = previousView.swap(nil) + + context.closeFolderFirst = false } public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + + + peekDisposable.set(nil) + genericView.inputContextHelper.viewWillRemove() self.chatInteraction.remove(observer: self) chatInteraction.saveState(scrollState: immediateScrollState()) - window?.removeAllHandlers(for: self) + context.window.removeAllHandlers(for: self) if let window = window { selectTextController.removeHandlers(for: window) @@ -1996,16 +4012,103 @@ class ChatController: EditableViewController, Notifable { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) } + + override func didRemovedFromStack() { + super.didRemovedFromStack() + chatInteraction.clean() + } + + private var splitStateFirstUpdate: Bool = true + override func viewDidChangedNavigationLayout(_ state: SplitViewState) -> Void { + super.viewDidChangedNavigationLayout(state) + chatInteraction.update(animated: false, {$0.withUpdatedLayout(state).withToggledSidebarEnabled(FastSettings.sidebarEnabled).withToggledSidebarShown(FastSettings.sidebarShown)}) + if !splitStateFirstUpdate { + Queue.mainQueue().justDispatch { [weak self] in + self?.genericView.tableView.layoutItems() + } + } + splitStateFirstUpdate = false + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + if let initialAction = self.chatInteraction.presentation.initialAction { + switch initialAction { + case let .closeAfter(peek): + self.chatInteraction.closeAfterPeek(peek) + default: + break + } + } + + let context = self.context + context.closeFolderFirst = false + + self.context.sharedContext.bindings.entertainment().update(with: self.chatInteraction) + + chatInteraction.update(animated: false, {$0.withToggledSidebarEnabled(FastSettings.sidebarEnabled).withToggledSidebarShown(FastSettings.sidebarShown)}) + //NSLog("chat apeeared") + + self.failedMessageEventsDisposable.set((context.account.pendingMessageManager.failedMessageEvents(peerId: chatInteraction.peerId) + |> deliverOnMainQueue).start(next: { [weak self] reason in + if let strongSelf = self { + let text: String + switch reason { + case .flood: + text = L10n.chatSendMessageErrorFlood + case .publicBan: + text = L10n.chatSendMessageErrorGroupRestricted + case .mediaRestricted: + text = L10n.chatSendMessageErrorGroupRestricted + case .slowmodeActive: + text = L10n.chatSendMessageSlowmodeError + case .tooMuchScheduled: + text = L10n.chatSendMessageErrorTooMuchScheduled + } + confirm(for: context.window, information: text, cancelTitle: "", thridTitle: L10n.genericErrorMoreInfo, successHandler: { [weak strongSelf] confirm in + guard let strongSelf = strongSelf else {return} + + switch confirm { + case .thrid: + execute(inapp: inAppLink.followResolvedName(link: "@spambot", username: "spambot", postId: nil, context: context, action: nil, callback: { [weak strongSelf] peerId, openChat, postid, initialAction in + strongSelf?.chatInteraction.openInfo(peerId, openChat, postid, initialAction) + })) + default: + break + } + }) + } + })) + + + if let peer = chatInteraction.peer { + if peer.isRestrictedChannel(context.contentSettings), let reason = peer.restrictionText { + alert(for: context.window, info: reason, completion: { [weak self] in + self?.dismiss() + }) + } else if chatInteraction.presentation.isNotAccessible { + alert(for: context.window, info: peer.isChannel ? L10n.chatChannelUnaccessible : L10n.chatGroupUnaccessible, completion: { [weak self] in + self?.dismiss() + }) + } + } + + - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in + self.context.window.set(handler: {[weak self] () -> KeyHandlerResult in if let strongSelf = self, !hasModals() { let result:KeyHandlerResult = strongSelf.chatInteraction.presentation.effectiveInput.inputText.isEmpty && strongSelf.chatInteraction.presentation.state == .normal ? .invoked : .rejected if result == .invoked { - strongSelf.findAndSetEditableMessage() + let setup = strongSelf.findAndSetEditableMessage() + if !setup { + strongSelf.genericView.tableView.scrollUp() + } + } else { + if strongSelf.chatInteraction.presentation.effectiveInput.inputText.isEmpty { + strongSelf.genericView.tableView.scrollUp() + } } return result @@ -2013,30 +4116,232 @@ class ChatController: EditableViewController, Notifable { return .rejected }, with: self, for: .UpArrow, priority: .low) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in - if let strongSelf = self { - strongSelf.chatInteraction.update({$0.updatedSearchMode(!$0.isSearchMode)}) + + self.context.window.set(handler: {[weak self] () -> KeyHandlerResult in + if let strongSelf = self, !hasModals() { + let result:KeyHandlerResult = strongSelf.chatInteraction.presentation.effectiveInput.inputText.isEmpty ? .invoked : .invokeNext + + + if result == .invoked { + strongSelf.genericView.tableView.scrollDown() + } + + return result + } + return .rejected + }, with: self, for: .DownArrow, priority: .low) + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let `self` = self, !hasModals(), self.chatInteraction.presentation.interfaceState.editState == nil, self.chatInteraction.presentation.interfaceState.inputState.inputText.isEmpty { + var currentReplyId = self.chatInteraction.presentation.interfaceState.replyMessageId + self.genericView.tableView.enumerateItems(with: { item in + if let item = item as? ChatRowItem, let message = item.message { + if canReplyMessage(message, peerId: self.chatInteraction.peerId), currentReplyId == nil || (message.id < currentReplyId!) { + currentReplyId = message.id + self.genericView.tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsetsZero, timingFunction: .linear) + return false + } + } + return true + }) + + let result:KeyHandlerResult = currentReplyId != nil ? .invoked : .rejected + self.chatInteraction.setupReplyMessage(currentReplyId) + + return result + } + return .rejected + }, with: self, for: .UpArrow, priority: .low, modifierFlags: [.command]) + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let `self` = self, !hasModals(), self.chatInteraction.presentation.interfaceState.editState == nil, self.chatInteraction.presentation.interfaceState.inputState.inputText.isEmpty { + var currentReplyId = self.chatInteraction.presentation.interfaceState.replyMessageId + self.genericView.tableView.enumerateItems(reversed: true, with: { item in + if let item = item as? ChatRowItem, let message = item.message { + if canReplyMessage(message, peerId: self.chatInteraction.peerId), currentReplyId != nil && (message.id > currentReplyId!) { + currentReplyId = message.id + self.genericView.tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsetsZero, timingFunction: .linear) + return false + } + } + return true + }) + + let result:KeyHandlerResult = currentReplyId != nil ? .invoked : .rejected + self.chatInteraction.setupReplyMessage(currentReplyId) + + return result } + return .rejected + }, with: self, for: .DownArrow, priority: .low, modifierFlags: [.command]) + + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self, !hasModals() else {return .rejected} + + if let selectionState = self.chatInteraction.presentation.selectionState, !selectionState.selectedIds.isEmpty { + self.chatInteraction.deleteSelectedMessages() + return .invoked + } + + return .rejected + }, with: self, for: .Delete, priority: .low) + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + if let selectionState = self.chatInteraction.presentation.selectionState, !selectionState.selectedIds.isEmpty { + self.chatInteraction.deleteSelectedMessages() + return .invoked + } + + return .rejected + }, with: self, for: .ForwardDelete, priority: .low) + + + + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let strongSelf = self, strongSelf.context.window.firstResponder != strongSelf.genericView.inputView.textView.inputView { + _ = strongSelf.context.window.makeFirstResponder(strongSelf.genericView.inputView) + return .invoked + } else if (self?.navigationController as? MajorNavigationController)?.genericView.state == .single { + return .invoked + } + return .rejected + }, with: self, for: .Tab, priority: .high) + + + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + if !self.chatInteraction.presentation.isSearchMode.0 { + self.chatInteraction.update({$0.updatedSearchMode((true, nil, nil))}) + } else { + self.genericView.applySearchResponder() + } + return .invoked }, with: self, for: .F, priority: .medium, modifierFlags: [.command]) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + + +// self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in +// guard let `self` = self else {return .rejected} +// if let editState = self.chatInteraction.presentation.interfaceState.editState, let media = editState.originalMedia as? TelegramMediaImage { +// self.chatInteraction.editEditingMessagePhoto(media) +// } +// return .invoked +// }, with: self, for: .E, priority: .medium, modifierFlags: [.command]) + + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in self?.genericView.inputView.makeBold() return .invoked }, with: self, for: .B, priority: .medium, modifierFlags: [.command]) + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.inputView.makeUrl() + return .invoked + }, with: self, for: .U, priority: .medium, modifierFlags: [.command]) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in self?.genericView.inputView.makeItalic() return .invoked }, with: self, for: .I, priority: .medium, modifierFlags: [.command]) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else { return .rejected } + self.chatInteraction.startRecording(true, nil) + return .invoked + }, with: self, for: .R, priority: .medium, modifierFlags: [.command]) + + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in self?.genericView.inputView.makeMonospace() return .invoked }, with: self, for: .K, priority: .medium, modifierFlags: [.command, .shift]) - if !(window?.firstResponder is NSTextView) { + + #if BETA || ALPHA || DEBUG + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let `self` = self { + addAudioToSticker(context: self.context) + } + return .invoked + }, with: self, for: .Y, priority: .medium, modifierFlags: [.command, .shift]) + #endif + + self.context.window.add(swipe: { [weak self] direction, _ -> SwipeHandlerResult in + guard let `self` = self, let window = self.window, self.chatInteraction.presentation.state == .normal else {return .failed} + let swipeState: SwipeState? + switch direction { + case .left: + return .failed + case let .right(_state): + swipeState = _state + case .none: + swipeState = nil + } + + guard let state = swipeState else {return .failed} + + + + switch state { + case .start: + let row = self.genericView.tableView.row(at: self.genericView.tableView.clipView.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + if row != -1 { + guard let item = self.genericView.tableView.item(at: row) as? ChatRowItem, let message = item.message, canReplyMessage(message, peerId: self.chatInteraction.peerId) else {return .failed} + self.removeRevealStateIfNeeded(message.id) + (item.view as? RevealTableView)?.initRevealState() + return .success(RevealTableItemController(item: item)) + } else { + return .failed + } + + case let .swiping(_delta, controller): + let controller = controller as! RevealTableItemController + + guard let view = controller.item.view as? RevealTableView else {return .nothing} + + var delta:CGFloat + switch direction { + case .left: + delta = _delta//max(0, _delta) + case .right: + delta = -_delta//min(-_delta, 0) + default: + delta = _delta + } + + let newDelta = min(min(300, view.width) * log2(abs(delta) + 1) * log2(min(300, view.width)) / 100.0, abs(delta)) + + if delta < 0 { + delta = -newDelta + } else { + delta = newDelta + } + + + view.moveReveal(delta: delta) + + case let .success(_, controller), let .failed(_, controller): + let controller = controller as! RevealTableItemController + guard let view = (controller.item.view as? RevealTableView) else {return .nothing} + + + view.completeReveal(direction: direction) + } + + // return .success() + + return .nothing + }, with: self.genericView.tableView, identifier: "chat-reply-swipe") + + + + if !(context.window.firstResponder is NSTextView) { self.genericView.inputView.makeFirstResponder() } @@ -2044,22 +4349,30 @@ class ChatController: EditableViewController, Notifable { selectTextController.initializeHandlers(for: window, chatInteraction:chatInteraction) } - window?.makeFirstResponder(genericView.inputView.textView.inputView) + _ = context.window.makeFirstResponder(genericView.inputView.textView.inputView) + + } + + private func removeRevealStateIfNeeded(_ messageId: MessageId) -> Void { } - func findAndSetEditableMessage() -> Void { - let view = self.previousView.modify({$0}) + func findAndSetEditableMessage(_ bottom: Bool = false) -> Bool { + let view = self.previousView.with { $0 } if let view = view?.originalView, view.laterId == nil { - for entry in view.entries.reversed() { - if case let .MessageEntry(message,_,_,_) = entry { - if canEditMessage(message, account:account) { - chatInteraction.beginEditingMessage(message) - return + for entry in (!bottom ? view.entries.reversed() : view.entries) { + if let messageId = chatInteraction.presentation.interfaceState.editState?.message.id { + if (messageId <= entry.message.id && !bottom) || (messageId >= entry.message.id && bottom) { + continue } } + if canEditMessage(entry.message, context: context) { + chatInteraction.beginEditingMessage(entry.message) + return true + } } } + return false } override func firstResponder() -> NSResponder? { @@ -2072,10 +4385,13 @@ class ChatController: EditableViewController, Notifable { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - globalPeerHandler.set(.single(peerId)) - chatInteraction.update(animated: false, {$0.withToggledSidebarEnabled(FastSettings.sidebarEnabled).withToggledSidebarShown(FastSettings.sidebarShown)}) - account.context.entertainment.update(with: chatInteraction) self.chatInteraction.add(observer: self) + self.context.globalPeerHandler.set(.single(chatLocation)) + + if let controller = globalAudio { + (self.navigationController?.header?.view as? InlineAudioPlayerView)?.update(with: controller, context: context, tableView: genericView.tableView) + } + } private func updateMaxVisibleReadIncomingMessageIndex(_ index: MessageIndex) { @@ -2088,28 +4404,52 @@ class ChatController: EditableViewController, Notifable { chatInteraction.applyAction(action: action) } - public init(account:Account, peerId:PeerId, messageId:MessageId? = nil, initialAction:ChatInitialAction? = nil) { - self.peerId = peerId - self.chatInteraction = ChatInteraction(peerId:peerId, account:account) - super.init(account) + private let isAdChat: Bool + private let messageId: MessageId? + let mode: ChatMode + + public init(context: AccountContext, chatLocation:ChatLocation, mode: ChatMode = .history, messageId:MessageId? = nil, initialAction:ChatInitialAction? = nil) { + self.chatLocation = chatLocation + self.messageId = messageId + self.mode = mode + self.undoTooltipControl = UndoTooltipControl(context: context) + self.chatInteraction = ChatInteraction(chatLocation: chatLocation, context: context, mode: mode) + if let action = initialAction { + switch action { + case .ad: + isAdChat = true + default: + isAdChat = false + } + } else { + isAdChat = false + } + super.init(context) + + + //NSLog("init chat controller") self.chatInteraction.update(animated: false, {$0.updatedInitialAction(initialAction)}) - account.context.checkFirstRecentlyForDuplicate(peerId:peerId) + context.checkFirstRecentlyForDuplicate(peerId: chatInteraction.peerId) - self.messageProcessingManager.process = { [weak account] messageIds in - account?.viewTracker.updateViewCountForMessageIds(messageIds: messageIds) + self.messageProcessingManager.process = { messageIds in + context.account.viewTracker.updateViewCountForMessageIds(messageIds: messageIds.filter({$0.namespace == Namespaces.Message.Cloud})) } - - self.messageMentionProcessingManager.process = { [weak account] messageIds in - account?.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) + + self.unsupportedMessageProcessingManager.process = { messageIds in + context.account.viewTracker.updateUnsupportedMediaForMessageIds(messageIds: messageIds.filter({$0.namespace == Namespaces.Message.Cloud})) + } + self.messageMentionProcessingManager.process = { messageIds in + context.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds.filter({$0.namespace == Namespaces.Message.Cloud})) } + self.location.set(peerView.get() |> take(1) |> deliverOnMainQueue |> map { [weak self] view -> ChatHistoryLocation in if let strongSelf = self { - let count = Int(round(strongSelf.view.frame.height / 28)) + 30 + let count = Int(round(strongSelf.view.frame.height / 28)) + 2 let location:ChatHistoryLocation if let messageId = messageId { - location = .InitialSearch(location: .id(messageId), count: count) + location = .InitialSearch(location: .id(messageId), count: count + 10) } else { location = .Initial(count: count) } @@ -2118,22 +4458,102 @@ class ChatController: EditableViewController, Notifable { } return .Initial(count: 30) }) - + _ = (self.location.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] location in + _ = self?._locationValue.swap(location) + }) } func notify(with value: Any, oldValue: Any, animated:Bool) { notify(with: value, oldValue: oldValue, animated: animated, force: false) } + private var isPausedGlobalPlayer: Bool = false func notify(with value: Any, oldValue: Any, animated:Bool, force:Bool) { if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { + let context = self.context + + + if value.selectionState != oldValue.selectionState { + if let selectionState = value.selectionState { + let ids = Array(selectionState.selectedIds) + loadSelectionMessagesDisposable.set((context.account.postbox.messagesAtIds(ids) |> deliverOnMainQueue).start( next:{ [weak self] messages in + var canDelete:Bool = !ids.isEmpty + var canForward:Bool = !ids.isEmpty + for message in messages { + if !canDeleteMessage(message, account: context.account) { + canDelete = false + } + if !canForwardMessage(message, account: context.account) { + canForward = false + } + } + self?.chatInteraction.update({$0.withUpdatedBasicActions((canDelete, canForward))}) + })) + } else { + DispatchQueue.main.async { [weak self] in + self?.chatInteraction.update({$0.withUpdatedBasicActions((false, false))}) + } + } + if value.selectionState != nil { + _ = window?.makeFirstResponder(selectManager) + } else { + _ = window?.makeFirstResponder(self.firstResponder()) + } + } + +// if #available(OSX 10.12.2, *) { +// self.context.window.touchBar = self.context.window.makeTouchBar() +// } + + if oldValue.recordingState == nil && value.recordingState != nil { + if let pause = globalAudio?.pause() { + isPausedGlobalPlayer = pause + } + } else if value.recordingState == nil && oldValue.recordingState != nil { + if isPausedGlobalPlayer { + _ = globalAudio?.play() + } + } + if let until = value.slowMode?.validUntil, until > self.context.timestamp { + let signal = Signal.single(Void()) |> then(.single(Void()) |> delay(0.2, queue: .mainQueue()) |> restart) + slowModeDisposable.set(signal.start(next: { [weak self] in + if let `self` = self { + if until < self.context.timestamp { + self.chatInteraction.update({$0.updateSlowMode({ $0?.withUpdatedTimeout(nil) })}) + } else { + self.chatInteraction.update({$0.updateSlowMode({ $0?.withUpdatedTimeout(until - self.context.timestamp) })}) + } + } + })) + + } else { + self.slowModeDisposable.set(nil) + if let slowMode = value.slowMode, slowMode.timeout != nil { + DispatchQueue.main.async { [weak self] in + self?.chatInteraction.update({$0.updateSlowMode({ $0?.withUpdatedTimeout(nil) })}) + } + } + } + if value.inputQueryResult != oldValue.inputQueryResult { genericView.inputContextHelper.context(with: value.inputQueryResult, for: genericView, relativeView: genericView.inputView, animated: animated) } if value.interfaceState.inputState != oldValue.interfaceState.inputState { chatInteraction.saveState(false, scrollState: immediateScrollState()) + + } + + if value.interfaceState.forwardMessageIds != oldValue.interfaceState.forwardMessageIds { + let signal = (context.account.postbox.messagesAtIds(value.interfaceState.forwardMessageIds)) |> deliverOnMainQueue + forwardMessagesDisposable.set(signal.start(next: { [weak self] messages in + self?.chatInteraction.update(animated: animated, { + $0.updatedInterfaceState { + $0.withUpdatedForwardMessages(messages) + } + }) + })) } if value.selectionState != oldValue.selectionState { @@ -2142,7 +4562,7 @@ class ChatController: EditableViewController, Notifable { } if value.effectiveInput != oldValue.effectiveInput || force { - if let (updatedContextQueryState, updatedContextQuerySignal) = contextQueryResultStateForChatInterfacePresentationState(chatInteraction.presentation, account: self.account, currentQuery: self.contextQueryState?.0) { + if let (updatedContextQueryState, updatedContextQuerySignal) = contextQueryResultStateForChatInterfacePresentationState(chatInteraction.presentation, context: self.context, currentQuery: self.contextQueryState?.0) { self.contextQueryState?.1.dispose() var inScope = true var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? @@ -2172,48 +4592,69 @@ class ChatController: EditableViewController, Notifable { } - - if let (updatedUrlPreviewUrl, updatedUrlPreviewSignal) = urlPreviewStateForChatInterfacePresentationState(chatInteraction.presentation, account: self.account, currentQuery: self.urlPreviewQueryState?.0) { - self.urlPreviewQueryState?.1.dispose() - var inScope = true - var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? - self.urlPreviewQueryState = (updatedUrlPreviewUrl, (updatedUrlPreviewSignal |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - if Thread.isMainThread && inScope { - inScope = false - inScopeResult = result - } else { - strongSelf.chatInteraction.update(animated: animated, { - if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = result($0.urlPreview?.1) { - return $0.updatedUrlPreview((updatedUrlPreviewUrl, webpage)) - } else { - return $0.updatedUrlPreview(nil) - } - }) + + let updateUrl = urlPreviewStateForChatInterfacePresentationState(chatInteraction.presentation, context: context, currentQuery: self.urlPreviewQueryState?.0) |> delay(value.effectiveInput.inputText.isEmpty ? 0.0 : 0.1, queue: .mainQueue()) |> deliverOnMainQueue + + updateUrlDisposable.set(updateUrl.start(next: { [weak self] result in + if let `self` = self, let (updatedUrlPreviewUrl, updatedUrlPreviewSignal) = result { + self.urlPreviewQueryState?.1.dispose() + var inScope = true + var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? + self.urlPreviewQueryState = (updatedUrlPreviewUrl, (updatedUrlPreviewSignal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + strongSelf.chatInteraction.update(animated: true, { + if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = result($0.urlPreview?.1) { + return $0.updatedUrlPreview((updatedUrlPreviewUrl, webpage)) + } else { + return $0.updatedUrlPreview(nil) + } + }) + } } + })) + inScope = false + if let inScopeResult = inScopeResult { + self.chatInteraction.update(animated: true, { + if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = inScopeResult($0.urlPreview?.1) { + return $0.updatedUrlPreview((updatedUrlPreviewUrl, webpage)) + } else { + return $0.updatedUrlPreview(nil) + } + }) } - })) - inScope = false - if let inScopeResult = inScopeResult { - chatInteraction.update(animated: animated, { - if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = inScopeResult($0.urlPreview?.1) { - return $0.updatedUrlPreview((updatedUrlPreviewUrl, webpage)) - } else { - return $0.updatedUrlPreview(nil) - } - }) } - } + })) + + } } - if value.isSearchMode != oldValue.isSearchMode || value.pinnedMessageId != oldValue.pinnedMessageId || value.reportStatus != oldValue.reportStatus || value.interfaceState.dismissedPinnedMessageId != oldValue.interfaceState.dismissedPinnedMessageId || value.canAddContact != oldValue.canAddContact { + if value.isSearchMode.0 != oldValue.isSearchMode.0 || value.pinnedMessageId != oldValue.pinnedMessageId || value.peerStatus != oldValue.peerStatus || value.interfaceState.dismissedPinnedMessageId != oldValue.interfaceState.dismissedPinnedMessageId || value.initialAction != oldValue.initialAction || value.restrictionInfo != oldValue.restrictionInfo { genericView.updateHeader(value, animated) } + if value.peer != nil && oldValue.peer == nil { + genericView.tableView.emptyItem = ChatEmptyPeerItem(genericView.tableView.frame.size, chatInteraction: chatInteraction) + } + + var upgradedToPeerId: PeerId? + if let previous = oldValue.peer, let group = previous as? TelegramGroup, group.migrationReference == nil, let updatedGroup = value.peer as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { + upgradedToPeerId = migrationReference.peerId + } + self.state = value.selectionState != nil ? .Edit : .Normal + if let upgradedToPeerId = upgradedToPeerId { + let controller = ChatController(context: context, chatLocation: .peer(upgradedToPeerId)) + navigationController?.removeAll() + navigationController?.push(controller, false, style: .none) + } + } } @@ -2254,35 +4695,84 @@ class ChatController: EditableViewController, Notifable { } + public override func draggingExited() { + super.draggingExited() + genericView.inputView.isHidden = false + } + public override func draggingEntered() { + super.draggingEntered() + genericView.inputView.isHidden = true + } public override func draggingItems(for pasteboard:NSPasteboard) -> [DragItem] { + if hasModals() { + return [] + } + + let peerId = self.chatInteraction.peerId + if let types = pasteboard.types, types.contains(.kFilenames) { let list = pasteboard.propertyList(forType: .kFilenames) as? [String] - if let list = list, list.count > 0, let peer = peer, peer.canSendMessage { + if let list = list, list.count > 0, let peer = chatInteraction.peer, peer.canSendMessage { - if peer.mediaRestricted { - return [] + if let text = permissionText(from: peer, for: .banSendMedia) { + return [DragItem(title: "", desc: text, handler: { + + })] } var items:[DragItem] = [] let list = list.filter { path -> Bool in - if let size = fileSize(path) { - return size <= 1500000000 + if let size = fs(path) { + return size <= 2000 * 1024 * 1024 } return false } + if list.count == 1, let editState = chatInteraction.presentation.interfaceState.editState, editState.canEditMedia { + return [DragItem(title: L10n.chatDropEditTitle, desc: L10n.chatDropEditDesc, handler: { [weak self] in + guard let strongSelf = self else { + return + } + _ = (Sender.generateMedia(for: MediaSenderContainer(path: list[0], isFile: false), account: strongSelf.chatInteraction.context.account, isSecretRelated: peerId.namespace == Namespaces.Peer.SecretChat) |> deliverOnMainQueue).start(next: { media, _ in + self?.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + }) + })] + } + + if !list.isEmpty { - let asMediaItem = DragItem(title:tr(.chatDropTitle), desc: tr(.chatDropQuickDesc), handler:{ [weak self] in - self?.chatInteraction.showPreviewSender(list.map { URL(fileURLWithPath: $0) }, true) + + + let asMediaItem = DragItem(title:tr(L10n.chatDropTitle), desc: tr(L10n.chatDropQuickDesc), handler:{ [weak self] in + let shift = NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false + if shift { + self?.chatInteraction.sendMedia(list.map{MediaSenderContainer(path: $0, caption: "", isFile: false)}) + } else { + self?.chatInteraction.showPreviewSender(list.map { URL(fileURLWithPath: $0) }, true, nil) + } }) + let fileTitle: String + let fileDesc: String - let asFileItem = DragItem(title:tr(.chatDropTitle), desc: tr(.chatDropAsFilesDesc), handler:{ [weak self] in - self?.chatInteraction.showPreviewSender(list.map { URL(fileURLWithPath: $0) }, false) + if list.count == 1, list[0].isDirectory { + fileTitle = L10n.chatDropFolderTitle + fileDesc = L10n.chatDropFolderDesc + } else { + fileTitle = L10n.chatDropTitle + fileDesc = L10n.chatDropAsFilesDesc + } + let asFileItem = DragItem(title: fileTitle, desc: fileDesc, handler: { [weak self] in + let shift = NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false + if shift { + self?.chatInteraction.sendMedia(list.map{MediaSenderContainer(path: $0, caption: "", isFile: true)}) + } else { + self?.chatInteraction.showPreviewSender(list.map { URL(fileURLWithPath: $0) }, false, nil) + } }) items.append(asFileItem) @@ -2309,18 +4799,29 @@ class ChatController: EditableViewController, Notifable { let data = pasteboard.data(forType: .tiff) if let data = data, let image = NSImage(data: data) { + if let editState = chatInteraction.presentation.interfaceState.editState, editState.canEditMedia { + return [DragItem(title: L10n.chatDropEditTitle, desc: L10n.chatDropEditDesc, handler: { [weak self] in + guard let strongSelf = self else { + return + } + _ = (putToTemp(image: image) |> mapToSignal {Sender.generateMedia(for: MediaSenderContainer(path: $0, isFile: false), account: strongSelf.chatInteraction.context.account, isSecretRelated: peerId.namespace == Namespaces.Peer.SecretChat) } |> deliverOnMainQueue).start(next: { media, _ in + self?.chatInteraction.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + }) + })] + } + var items:[DragItem] = [] - let asMediaItem = DragItem(title:tr(.chatDropTitle), desc: tr(.chatDropQuickDesc), handler:{ [weak self] in + let asMediaItem = DragItem(title:tr(L10n.chatDropTitle), desc: tr(L10n.chatDropQuickDesc), handler:{ [weak self] in _ = (putToTemp(image: image) |> deliverOnMainQueue).start(next: { [weak self] path in - self?.chatInteraction.sendMedia([MediaSenderContainer(path:path, isFile:false)]) + self?.chatInteraction.showPreviewSender([URL(fileURLWithPath: path)], true, nil) }) }) - let asFileItem = DragItem(title:tr(.chatDropTitle), desc: tr(.chatDropAsFilesDesc), handler:{ [weak self] in + let asFileItem = DragItem(title:tr(L10n.chatDropTitle), desc: tr(L10n.chatDropAsFilesDesc), handler:{ [weak self] in _ = (putToTemp(image: image) |> deliverOnMainQueue).start(next: { [weak self] path in - self?.chatInteraction.sendMedia([MediaSenderContainer(path:path, isFile: true)]) + self?.chatInteraction.showPreviewSender([URL(fileURLWithPath: path)], false, nil) }) }) @@ -2333,32 +4834,79 @@ class ChatController: EditableViewController, Notifable { return [] } + + override public var isOpaque: Bool { + return false + } + + override func updateController() { + genericView.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }, force: true) + } override open func backSettings() -> (String,CGImage?) { - if account.context.layout == .single { + if context.sharedContext.layout == .single { return super.backSettings() } - return (tr(.navigationClose),nil) + return (tr(L10n.navigationClose),nil) } override public func update(with state:ViewControllerState) -> Void { super.update(with:state) chatInteraction.update({state == .Normal ? $0.withoutSelectionState() : $0.withSelectionState()}) + context.window.applyResponderIfNeeded() } override func initializer() -> ChatControllerView { - return ChatControllerView.self.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - self.bar.height), chatInteraction:chatInteraction, account:account); + return ChatControllerView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - self.bar.height), chatInteraction:chatInteraction); } override func requestUpdateCenterBar() { } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - self.centerBarView.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + updateBackgroundColor(theme.controllerBackgroundMode) (centerBarView as? ChatTitleBarView)?.updateStatus() } + + + func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void { + + } + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { + return false + } + func isSelectable(row:Int, item:TableRowItem) -> Bool { + return false + } + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + if let view = previousView.with({$0}), let stableId = stableId.base as? ChatHistoryEntryId { + switch stableId { + case let .message(message): + for entry in view.filteredEntries { + s: switch entry.entry { + case let .groupedPhotos(entries, _): + for groupedEntry in entries { + if message.id == groupedEntry.message?.id { + return entry.stableId + } + } + default: + break s + } + } + default: + break + } + } + return nil + } } diff --git a/Telegram-Mac/ChatDiceContentView.swift b/Telegram-Mac/ChatDiceContentView.swift new file mode 100644 index 0000000000..ecf8da1a7e --- /dev/null +++ b/Telegram-Mac/ChatDiceContentView.swift @@ -0,0 +1,394 @@ +// +// ChatDiceContentView.swift +// Telegram +// +// Created by Mikhail Filimonov on 27.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit + +let diceSide1: String = "1️⃣" +let diceSide2: String = "2️⃣" +let diceSide3: String = "3️⃣" +let diceSide4: String = "4️⃣" +let diceSide5: String = "5️⃣" +let diceSide6: String = "6️⃣" +let diceSide7: String = "7️⃣" +let diceSide8: String = "8️⃣" +let diceSide9: String = "9️⃣" +let diceIdle: String = "#️⃣" + + + + + + +private extension Int32 { + var diceSide: String { + switch self { + case 1: + return diceSide1 + case 2: + return diceSide2 + case 3: + return diceSide3 + case 4: + return diceSide4 + case 5: + return diceSide5 + case 6: + return diceSide6 + case 7: + return diceSide7 + case 8: + return diceSide8 + case 9: + return diceSide9 + default: + preconditionFailure() + } + } +} + + +private enum DicePlay : Equatable { + case idle + case failed + case end(animated: Bool) +} + +private struct DiceState : Equatable { + let messageId: MessageId + let message: Message + let play: DicePlay + + static func ==(lhs: DiceState, rhs: DiceState) -> Bool { + return lhs.play == rhs.play && isEqualMessages(lhs.message, rhs.message) + } + + init(message: Message) { + self.message = message + self.messageId = message.id + if let dice = message.media.first as? TelegramMediaDice, dice.value == 0 { + play = .idle + } else if message.forwardInfo != nil { + play = .end(animated: false) + } else { + if message.flags.contains(.Failed) { + self.play = .failed + } else if message.flags.isSending { + play = .idle + } else { + if !FastSettings.diceHasAlreadyPlayed(message) { + play = .end(animated: true) + } else { + play = .end(animated: false) + } + } + } + + } +} + +class ChatDiceContentView: ChatMediaContentView { + private let playerView: LottiePlayerView = LottiePlayerView(frame: NSMakeRect(0, 0, 240, 240)) + private let thumbView = TransformImageView() + private let loadResourceDisposable = MetaDisposable() + private let stateDisposable = MetaDisposable() + private var diceState: DiceState? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.playerView) + addSubview(self.thumbView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func clean() { + loadResourceDisposable.dispose() + } + + deinit { + clean() + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + + + + @objc func updatePlayerIfNeeded() { + + } + + private var nextForceAccept: Bool = false + + + override func executeInteraction(_ isControl: Bool) { + + let media = self.media as? TelegramMediaDice + + if let media = media, let message = self.parent { + let item = self.table?.item(stableId: ChatHistoryEntryId.message(message)) + + if let item = item as? ChatRowItem, let peer = item.peer, peer.canSendMessage { + let text: String + + switch media.emoji { + case diceSymbol: + text = L10n.chatEmojiDiceResultNew + case dartSymbol: + text = L10n.chatEmojiDartResultNew + default: + text = L10n.chatEmojiDefResultNew(media.emoji) + } + let view: NSView + if !thumbView.isHidden { + view = thumbView + } else { + view = playerView + } + tooltip(for: view, text: text, interactions: globalLinkExecutor, button: (L10n.chatEmojiSend, { [weak item] in + item?.chatInteraction.sendPlainText(media.emoji) + }), offset: NSMakePoint(0, -30)) + } + } + // alert(for: window, info: L10n.chatDiceResult) + } + + var chatLoopAnimated: Bool { + if let context = self.context { + return context.autoplayMedia.loopAnimatedStickers + } + return true + } + + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: table?.contentView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: self.enclosingScrollView?.documentView) + } else { + removeNotificationListeners() + } + } + + override func viewWillDraw() { + super.viewWillDraw() + updatePlayerIfNeeded() + } + + override func willRemove() { + super.willRemove() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToSuperview() { + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToWindow() { + updateListeners() + updatePlayerIfNeeded() + } + + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool, positionFlags: LayoutPositionFlags?, approximateSynchronousValue: Bool) { + + + if parent?.stableId != self.parent?.stableId { + self.playerView.set(nil) + } + + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags, approximateSynchronousValue: approximateSynchronousValue) + + guard let media = media as? TelegramMediaDice, let parent = parent else { + return + } + + let baseSymbol: String = media.emoji + let sideSymbol: String + + let currentValue = media.value + + if let currentValue = currentValue, currentValue > 0 && currentValue <= 9 { + sideSymbol = currentValue.diceSide + } else { + sideSymbol = diceIdle + } + + let settings = InteractiveEmojiConfiguration.with(appConfiguration: context.appConfiguration) + + let diceState = DiceState(message: parent) + + + self.diceState = diceState + + + + + let data: Signal<(Data?, TelegramMediaFile), NoError> + data = context.diceCache.interactiveSymbolData(baseSymbol: baseSymbol, side: sideSymbol, synchronous: approximateSynchronousValue) + + + self.playerView.isHidden = true + self.thumbView.isHidden = false + + self.playerView.animation?.triggerOn = nil + self.playerView.animation?.onFinish = nil + + self.loadResourceDisposable.set((data |> deliverOnMainQueue).start(next: { [weak self] data in + guard let `self` = self else { + return + } + let playPolicy: LottiePlayPolicy + + var saveContext: Bool = false + switch diceState.play { + case .failed: + playPolicy = .framesCount(1) + case .idle: + playPolicy = .loop + case let .end(toEndWithAnimation): + if !toEndWithAnimation || approximateSynchronousValue || self.visibleRect.height == 0 { + if self.visibleRect.height == 0 && toEndWithAnimation && !approximateSynchronousValue { + let item = self.table?.item(stableId: ChatHistoryEntryId.message(parent)) + if let item = item, let table = self.table, table.visibleRows().contains(item.index) { + playPolicy = .toEnd(from: 0) + saveContext = true + } else { + playPolicy = .toEnd(from: .max) + FastSettings.markDiceAsPlayed(parent) + } + } else { + playPolicy = .toEnd(from: .max) + FastSettings.markDiceAsPlayed(parent) + } + + } else { + saveContext = true + playPolicy = .toEnd(from: 0) + } + + } + if let bytes = data.0 { + let animation = LottieAnimation(compressed: bytes, key: LottieAnimationEntryKey(key: .media(data.1.id), size: size), cachePurpose: .none, playPolicy: playPolicy, maximumFps: 60) + + animation.onFinish = { + if case .end = diceState.play { + FastSettings.markDiceAsPlayed(parent) + } + } + switch diceState.play { + case let .end(animated): + if let previous = self.playerView.animation, animated { + switch self.playerView.currentState { + case .playing: + previous.triggerOn = (.last, { [weak self] in + self?.playerView.set(animation, saveContext: saveContext) + if animated, let confetti = settings.playConfetti(baseSymbol), confetti.value == currentValue { + animation.triggerOn = (.custom(confetti.playAt), { [weak self] in + if self?.visibleRect.height == self?.frame.height { + PlayConfetti(for: context.window) + } + }, {}) + } + }, { [weak self] in + self?.playerView.set(animation) + }) + default: + self.playerView.set(animation) + } + + } else { + self.playerView.set(animation) + } + default: + self.playerView.set(animation) + } + } else { + self.playerView.set(nil) + } + + self.stateDisposable.set((self.playerView.state |> deliverOnMainQueue).start(next: { [weak self] state in + guard let `self` = self else { return } + switch state { + case .playing: + self.playerView.isHidden = false + self.thumbView.isHidden = true + case .initializing, .failed: + switch diceState.play { + case let .end(animated): + if animated { + self.playerView.isHidden = false + self.thumbView.isHidden = true + } else { + self.playerView.isHidden = true + self.thumbView.isHidden = false + } + default: + self.playerView.isHidden = false + self.thumbView.isHidden = true + } + case .stoped: + switch diceState.play { + case let .end(animated): + if animated { + self.playerView.isHidden = false + self.thumbView.isHidden = true + } else { + self.playerView.isHidden = true + self.thumbView.isHidden = false + } + default: + self.playerView.isHidden = false + self.thumbView.isHidden = false + } + } + })) + + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + + self.thumbView.setSignal(signal: cachedMedia(media: data.1, arguments: arguments, scale: self.backingScaleFactor), clearInstantly: true) + //if !self.thumbView.isFullyLoaded { + self.thumbView.setSignal(chatMessageDiceSticker(postbox: context.account.postbox, file: data.1, emoji: baseSymbol, value: sideSymbol, scale: self.backingScaleFactor, size: size), cacheImage: { result in + cacheMedia(result, media: data.1, arguments: arguments, scale: System.backingScale) + }) + self.thumbView.set(arguments: arguments) + // } + })) + // } else { + var bp:Int = 0 + bp += 1 + // } + + + + } + + override func layout() { + super.layout() + self.playerView.frame = bounds + self.thumbView.frame = bounds + } + +} diff --git a/Telegram-Mac/ChatEmptyPeerItem.swift b/Telegram-Mac/ChatEmptyPeerItem.swift index 2226ee7f9c..012ac11fd0 100644 --- a/Telegram-Mac/ChatEmptyPeerItem.swift +++ b/Telegram-Mac/ChatEmptyPeerItem.swift @@ -8,11 +8,14 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + class ChatEmptyPeerItem: TableRowItem { - let textViewLayout:TextViewLayout + private(set) var textViewLayout:TextViewLayout override var stableId: AnyHashable { return 0 @@ -23,6 +26,10 @@ class ChatEmptyPeerItem: TableRowItem { return false } + override var index: Int { + return -1000 + } + override var height: CGFloat { if let table = table { return table.frame.height @@ -30,31 +37,102 @@ class ChatEmptyPeerItem: TableRowItem { return initialSize.height } + private let peerViewDisposable = MetaDisposable() + init(_ initialSize: NSSize, chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction let attr = NSMutableAttributedString() - if chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat { - _ = attr.append(string: tr(.chatSecretChatEmptyHeader), color: theme.colors.grayText, font: .normal(.text)) - _ = attr.append(string: "\n\n") - _ = attr.append(string: tr(.chatSecretChat1Feature), color: theme.colors.grayText, font: .normal(.text)) - _ = attr.append(string: "\n") - _ = attr.append(string: tr(.chatSecretChat2Feature), color: theme.colors.grayText, font: .normal(.text)) - _ = attr.append(string: "\n") - _ = attr.append(string: tr(.chatSecretChat3Feature), color: theme.colors.grayText, font: .normal(.text)) - _ = attr.append(string: "\n") - _ = attr.append(string: tr(.chatSecretChat4Feature), color: theme.colors.grayText, font: .normal(.text)) - - } else { - _ = attr.append(string: tr(.chatEmptyChat), color: theme.colors.grayText, font: .normal(.text)) + var lineSpacing: CGFloat? = 5 + switch chatInteraction.mode { + case .history: + if chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat { + _ = attr.append(string: L10n.chatSecretChatEmptyHeader, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.chatSecretChat1Feature, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.chatSecretChat2Feature, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.chatSecretChat3Feature, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.chatSecretChat4Feature, color: theme.chatServiceItemTextColor, font: .medium(.text)) + + } else if let peer = chatInteraction.peer, peer.isGroup || peer.isSupergroup, peer.groupAccess.isCreator { + _ = attr.append(string: L10n.emptyGroupInfoTitle, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.emptyGroupInfoSubtitle, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.emptyGroupInfoLine1(chatInteraction.presentation.limitConfiguration.maxSupergroupMemberCount.formattedWithSeparator), color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.emptyGroupInfoLine2, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.emptyGroupInfoLine3, color: theme.chatServiceItemTextColor, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: L10n.emptyGroupInfoLine4, color: theme.chatServiceItemTextColor, font: .medium(.text)) + } else { + if let restriction = chatInteraction.presentation.restrictionInfo { + var hasRule: Bool = false + for rule in restriction.rules { + #if APP_STORE + if rule.platform == "ios" || rule.platform == "all" { + if !chatInteraction.context.contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) { + _ = attr.append(string: rule.text, color: theme.chatServiceItemTextColor, font: .medium(.text)) + hasRule = true + break + } + } + #endif + } + if !hasRule { + _ = attr.append(string: L10n.chatEmptyChat, color: theme.chatServiceItemTextColor, font: .medium(.text)) + lineSpacing = nil + } + + } else { + lineSpacing = nil + _ = attr.append(string: L10n.chatEmptyChat, color: theme.chatServiceItemTextColor, font: .medium(.text)) + } + } + case .scheduled: + lineSpacing = nil + _ = attr.append(string: L10n.chatEmptyChat, color: theme.chatServiceItemTextColor, font: .medium(.text)) } - textViewLayout = TextViewLayout(attr, alignment: .center) + + + textViewLayout = TextViewLayout(attr, alignment: .center, lineSpacing: lineSpacing, alwaysStaticItems: true) + textViewLayout.interactions = globalLinkExecutor + super.init(initialSize) + + + if chatInteraction.peerId.namespace == Namespaces.Peer.CloudUser { + peerViewDisposable.set((chatInteraction.context.account.postbox.peerView(id: chatInteraction.peerId) |> deliverOnMainQueue).start(next: { [weak self] peerView in + if let cachedData = peerView.cachedData as? CachedUserData, let user = peerView.peers[peerView.peerId], let botInfo = cachedData.botInfo { + var about = botInfo.description + if about.isEmpty { + about = cachedData.about ?? L10n.chatEmptyChat + } + if about.isEmpty { + about = L10n.chatEmptyChat + } + if user.isScam { + about = L10n.peerInfoScamWarning + } + guard let `self` = self else {return} + let attr = NSMutableAttributedString() + _ = attr.append(string: about, color: theme.chatServiceItemTextColor, font: .medium(.text)) + attr.detectLinks(type: [.Links, .Mentions, .Hashtags, .Commands], context: chatInteraction.context, color: theme.colors.link, openInfo:chatInteraction.openInfo, hashtag: chatInteraction.context.sharedContext.bindings.globalSearch, command: chatInteraction.sendPlainText, applyProxy: chatInteraction.applyProxy, dotInMention: false) + self.textViewLayout = TextViewLayout(attr, alignment: .left) + self.textViewLayout.interactions = globalLinkExecutor + self.view?.layout() + } + })) + } + } - override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - textViewLayout.measure(width: width - 40) - return super.makeSize(width) + deinit { + peerViewDisposable.dispose() } override func viewClass() -> AnyClass { @@ -69,19 +147,56 @@ class ChatEmptyPeerView : TableRowView { required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(textView) + //containerView.addSubview(textView) + textView.isSelectable = false + textView.userInteractionEnabled = true + textView.disableBackgroundDrawing = true + } override func updateColors() { super.updateColors() - textView.background = backdorColor + textView.background = theme.chatServiceItemColor + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + override var backdorColor: NSColor { + return theme.wallpaper.wallpaper != .none ? .clear : theme.chatBackground + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item) + needsLayout = true } override func layout() { super.layout() if let item = item as? ChatEmptyPeerItem { - item.textViewLayout.measure(width: frame.width - 40) + item.textViewLayout.measure(width: frame.width / 2) + + if item.textViewLayout.lineSpacing != nil { + for (i, line) in item.textViewLayout.lines.enumerated() { + if i == 0 { + line.penFlush = 0.5 + } else { + line.penFlush = 0.0 + } + } + } + textView.update(item.textViewLayout) + + let singleLine = item.textViewLayout.lines.count == 1 + + textView.setFrameSize( singleLine ? item.textViewLayout.layoutSize.width + 16 : item.textViewLayout.layoutSize.width + 30, singleLine ? 24 : item.textViewLayout.layoutSize.height + 20) textView.center() + + + + textView.layer?.cornerRadius = singleLine ? textView.frame.height / 2 : 8 } } diff --git a/Telegram-Mac/ChatFileContentView.swift b/Telegram-Mac/ChatFileContentView.swift index 23977695d1..1685ce5e9c 100644 --- a/Telegram-Mac/ChatFileContentView.swift +++ b/Telegram-Mac/ChatFileContentView.swift @@ -7,34 +7,41 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit class ChatFileContentView: ChatMediaContentView { - var actionsLayout:TextViewLayout? - - let progressView:RadialProgressView = RadialProgressView() + private var actionsLayout:TextViewLayout? + private var progressView:RadialProgressView? + private var thumbProgress: RadialProgressView? private let thumbView:TransformImageView = TransformImageView() - var titleNode:TextNode = TextNode() - var actionText:TextView = TextView() + private var titleNode:TextNode = TextNode() + private var actionText:TextView = TextView() - var actionInteractions:TextViewInteractions = TextViewInteractions() + private var actionInteractions:TextViewInteractions = TextViewInteractions() private let statusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() - + private let openFileDisposable = MetaDisposable() required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func previewMediaIfPossible() -> Bool { + guard let context = self.context, let window = self.kitWindow, let table = self.table, media?.isGraphicFile == true, fetchStatus == .Local else {return false} + _ = startModalPreviewHandle(table, window: window, context: context) + return true + } + required init(frame frameRect: NSRect) { super.init(frame:frameRect) actionText.isSelectable = false @@ -42,15 +49,12 @@ class ChatFileContentView: ChatMediaContentView { self.thumbView.setFrameSize(70,70) addSubview(thumbView) - progressView.fetchControls = fetchControls - addSubview(progressView) - actionInteractions.processURL = {[weak self] (link) in if let link = link as? String, link.hasSuffix("download") { self?.executeInteraction(false) } else if let link = link as? String, link.hasSuffix("finder") { - if let account = self?.account, let file = self?.media as? TelegramMediaFile { - showInFinder(file, account:account) + if let context = self?.context, let file = self?.media as? TelegramMediaFile { + showInFinder(file, account: context.account) } } } @@ -66,146 +70,347 @@ class ChatFileContentView: ChatMediaContentView { } override func fetch() { - if let account = account, let media = media as? TelegramMediaFile { - fetchDisposable.set((chatMessageFileInteractiveFetched(account: account, file: media) |> mapToSignal { source -> Signal in - if source == .remote { - return copyToDownloads(media, account: account) - } else { - return .single(Void()) - } - }).start()) + if let context = context, let media = media as? TelegramMediaFile { + if let parent = parent { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start()) + } else { + fetchDisposable.set(freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.standalone(media: media)).start()) + } } } override func open() { - if let account = account, let media = media, let parent = parent { - if media.isGraphicFile { - showChatGallery(account: account, message: parent, table, parameters as? ChatMediaGalleryParameters) + if let context = context, let media = media as? TelegramMediaFile, let parent = parent { + if media.isGraphicFile || media.isVideoFile { + showChatGallery(context: context, message: parent, table, parameters as? ChatMediaGalleryParameters, type: media.isVideoFile ? .alone : .history) } else { - QuickLookPreview.current.show(account: account, with: media, stableId:parent.chatStableId, table) + QuickLookPreview.current.show(context: context, with: media, stableId: parent.chatStableId, self.table) } } } override func cancelFetching() { - if let account = account, let media = media as? TelegramMediaFile { - chatMessageFileCancelInteractiveFetch(account: account, file: media) + if let context = context, let media = media as? TelegramMediaFile { + if let parent = parent { + messageMediaFileCancelInteractiveFetch(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)) + } else { + cancelFreeMediaFileInteractiveFetch(context: context, resource: media.resource) + } + if let resource = media.resource as? LocalFileArchiveMediaResource { + archiver.remove(.resource(resource)) + } } } override func draggingAbility(_ event:NSEvent) -> Bool { - return NSPointInRect(convert(event.locationInWindow, from: nil), progressView.frame) + return NSPointInRect(convert(event.locationInWindow, from: nil), progressView?.frame ?? NSZeroRect) + } + + deinit { + openFileDisposable.dispose() } - func actionLayout(status:MediaResourceStatus, file:TelegramMediaFile) -> TextViewLayout { + func actionLayout(status:MediaResourceStatus, archiveStatus: ArchiveStatus?, file:TelegramMediaFile, presentation: ChatMediaPresentation, paremeters: ChatFileLayoutParameters?) -> TextViewLayout? { let attr:NSMutableAttributedString = NSMutableAttributedString() - + if let archiveStatus = archiveStatus { + switch archiveStatus { + case let .progress(progress): + switch status { + case .Fetching: + if parent != nil { + _ = attr.append(string: progress == 0 ? L10n.messageStatusArchivePreparing : L10n.messageStatusArchiving(Int(progress * 100)), color: presentation.grayText, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + } else { + _ = attr.append(string: L10n.messageStatusArchived, color: presentation.grayText, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + } + + default: + break + } + case .none, .waiting: + _ = attr.append(string: L10n.messageStatusArchivePreparing, color: presentation.grayText, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + case .done: + if parent == nil { + _ = attr.append(string: L10n.messageStatusArchived, color: presentation.grayText, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + } + case let .fail(error): + if parent == nil { + let errorText: String + switch error { + case .sizeLimit: + errorText = L10n.messageStatusArchiveFailedSizeLimit + default: + errorText = L10n.messageStatusArchiveFailed + } + _ = attr.append(string: errorText, color: theme.colors.redUI, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + } + } + + } switch status { case let .Fetching(_, progress): if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - let _ = attr.append(string: tr(.messagesFileStateFetchingOut1(Int(progress * 100.0))), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) + let _ = attr.append(string: tr(L10n.messagesFileStateFetchingOut1(Int(progress * 100.0))), color: presentation.grayText, font: .normal(.text)) } else { - let _ = attr.append(string: tr(.messagesFileStateFetchingIn1(Int(progress * 100.0))), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) + let current = String.prettySized(with: Int(Float(file.elapsedSize) * progress), removeToken: true) + let size = "\(current) / \(String.prettySized(with: file.elapsedSize))" + let _ = attr.append(string: size, color: presentation.grayText, font: .normal(.text)) } - case .Local: - - let _ = attr.append(string: .prettySized(with: file.elapsedSize) + " - ", color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout - let range = attr.append(string: tr(.messagesFileStateLocal), color: theme.colors.link, font: NSFont.normal(FontSize.text)) - attr.addAttribute(NSAttributedStringKey.link, value: "chat://file/finder", range: range) + case .Local: + if let _ = archiveStatus { + let size = L10n.messageStatusArchived + let _ = attr.append(string: size, color: presentation.grayText, font: .normal(.text)) + let layout = TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) + layout.measure() + return layout + } + return paremeters?.finderLayout case .Remote: - let _ = attr.append(string: .prettySized(with: file.elapsedSize) + " - ", color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) - let range = attr.append(string: tr(.messagesFileStateRemote), color: theme.colors.link, font: NSFont.normal(FontSize.text)) - attr.addAttribute(NSAttributedStringKey.link, value: "chat://file/download", range: range) + return paremeters?.downloadLayout } - - return TextViewLayout(attr, constrainedWidth:frame.width - leftInset, maximumNumberOfLines:1) } - override func update(with media: Media, size:NSSize, account:Account, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool) { + override func update(with media: Media, size:NSSize, context: AccountContext, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { let file:TelegramMediaFile = media as! TelegramMediaFile - let mediaUpdated = true//self.media == nil || !self.media!.isEqual(media) + let semanticMedia = self.media?.id == media.id + + let presentation: ChatMediaPresentation = parameters?.presentation ?? .Empty - super.update(with: media, size: size, account: account, parent:parent,table:table, parameters:parameters, animated: animated) + super.update(with: media, size: size, context: context, parent:parent,table:table, parameters:parameters, animated: animated, positionFlags: positionFlags) - var updatedStatusSignal: Signal? + var updatedStatusSignal: Signal<(MediaResourceStatus, ArchiveStatus?), NoError>? let parameters = parameters as? ChatFileLayoutParameters - + actionText.backgroundColor = theme.colors.background - if mediaUpdated { - if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(parent.id)) - |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { - return .Fetching(isActive: true, progress: pendingStatus.progress) - } else { - return resourceStatus + var archiveSignal:Signal = .single(nil) + if let resource = file.resource as? LocalFileArchiveMediaResource { + archiveSignal = archiver.archive(.resource(resource)) |> map {Optional($0)} + } + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: file), context.account.pendingMessageManager.pendingMessageStatus(parent.id), archiveSignal) + |> map { resourceStatus, pendingStatus, archiveStatus in + if let archiveStatus = archiveStatus { + switch archiveStatus { + case let .progress(progress): + return (.Fetching(isActive: true, progress: Float(progress)), archiveStatus) + default: + break } - } |> deliverOnMainQueue - } else { - updatedStatusSignal = chatMessageFileStatus(account: account, file: file) |> deliverOnMainQueue - } + } + if let pendingStatus = pendingStatus.0 { + return (.Fetching(isActive: true, progress: pendingStatus.progress), archiveStatus) + } else { + return (resourceStatus, archiveStatus) + } + } |> deliverOnMainQueue + } else { + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: file, approximateSynchronousValue: approximateSynchronousValue), archiveSignal) |> map { resourceStatus, archiveStatus in + if let archiveStatus = archiveStatus { + switch archiveStatus { + case let .progress(progress): + return (.Fetching(isActive: true, progress: Float(progress)), archiveStatus) + default: + break + } + } + return (resourceStatus, archiveStatus) + } |> deliverOnMainQueue + } + + let stableId:Int64 + if let sId = parent?.stableId { + stableId = Int64(sId) + } else { + stableId = file.id?.id ?? 0 + } + + if !file.previewRepresentations.isEmpty { - if !file.previewRepresentations.isEmpty { - thumbView.setSignal(account: account, signal: chatMessageImageFile(account: account, file: file, progressive: false, scale: backingScaleFactor)) - thumbView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: 4), imageSize: file.previewRepresentations[0].dimensions, boundingSize: NSMakeSize(70, 70), intrinsicInsets: NSEdgeInsets())) - } else { - thumbView.setSignal(signal: .single(nil)) - } - - self.setNeedsDisplay() + let arguments = TransformImageArguments(corners: ImageCorners(radius: 8), imageSize: file.previewRepresentations[0].dimensions.size, boundingSize: NSMakeSize(70, 70), intrinsicInsets: NSEdgeInsets()) + thumbView.setSignal(signal: cachedMedia(messageId: stableId, arguments: arguments, scale: backingScaleFactor), clearInstantly: !semanticMedia) + + let reference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: file) : FileMediaReference.standalone(media: file) + thumbView.setSignal(chatMessageImageFile(account: context.account, fileReference: reference, progressive: false, scale: backingScaleFactor, synchronousLoad: false), clearInstantly: false, animate: true, synchronousLoad: false, cacheImage: { [weak file] result in + if let media = file { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) + } + }) + + + thumbView.set(arguments: arguments) + } else { + thumbView.setSignal(signal: .single(TransformImageResult(nil, false))) } + self.setNeedsDisplay() + if let updatedStatusSignal = updatedStatusSignal { - self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self { - strongSelf.fetchStatus = status + self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self, weak file] status, archiveStatus in + guard let `self` = self, let file = file else { return } + let oldStatus = self.fetchStatus + self.fetchStatus = status + + var statusWasUpdated: Bool = false + if let oldStatus = oldStatus { + switch oldStatus { + case .Fetching: + if case .Fetching = status {} else { + statusWasUpdated = true + } + case .Local: + if case .Local = status {} else { + statusWasUpdated = true + } + case .Remote: + if case .Remote = status {} else { + statusWasUpdated = true + } + } + } + + let layout = self.actionLayout(status: status, archiveStatus: archiveStatus, file: file, presentation: presentation, paremeters: parameters) + if !self.actionText.isEqual(to: layout) { + layout?.interactions = self.actionInteractions + self.actionText.update(layout) + } + + var removeThumbProgress: Bool = false + if case .Local = status { + removeThumbProgress = true + } + + if !file.previewRepresentations.isEmpty { + self.progressView?.removeFromSuperview() + self.progressView = nil + if !removeThumbProgress { + if self.thumbProgress == nil { + let progressView = RadialProgressView(theme:RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) + progressView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0)) + self.thumbProgress = progressView + self.addSubview(progressView) + let f = self.thumbView.focus(progressView.frame.size) + self.thumbProgress?.setFrameOrigin(f.origin) + progressView.fetchControls = self.fetchControls + } + switch status { + case .Remote: + self.thumbProgress?.state = .Remote + case let .Fetching(_, progress): + self.thumbProgress?.state = .Fetching(progress: progress, force: false) + default: + break + } + } else { + if let progressView = self.thumbProgress { + switch progressView.state { + case .Fetching: + progressView.state = .Fetching(progress:1.0, force: false) + default: + break + } + self.thumbProgress = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) + + } + } - let layout = strongSelf.actionLayout(status: status, file: file) - if !strongSelf.actionText.isEqual(to :layout) { - layout.interactions = strongSelf.actionInteractions - layout.measure() + } else { + self.thumbProgress?.removeFromSuperview() + self.thumbProgress = nil + + if self.progressView == nil { + self.progressView = RadialProgressView() + self.addSubview(self.progressView!) + } else if statusWasUpdated { + let progressView = self.progressView + self.progressView = RadialProgressView() + self.addSubview(self.progressView!) - strongSelf.actionText.update(layout) - var width = strongSelf.leftInset + layout.layoutSize.width - if let name = parameters?.name { - width = max(width, strongSelf.leftInset + name.0.size.width) - } + progressView?.layer?.animateAlpha(from: 1, to: 0.5, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) + self.progressView?.layer?.animateAlpha(from: 0.5, to: 1, duration: 0.25, timingFunction: .linear) } - switch status { - case let .Fetching(_, progress): - strongSelf.progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? theme.colors.blueFill : theme.colors.blackTransparent, foregroundColor: .white, icon: nil) - strongSelf.progressView.state = .Fetching(progress: progress, force: false) - case .Local: - strongSelf.progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? theme.colors.blueFill : .clear, foregroundColor: .white, icon: file.previewRepresentations.isEmpty ? theme.icons.chatFileThumb : nil) - strongSelf.progressView.state = .Play - case .Remote: - strongSelf.progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? theme.colors.blueFill : theme.colors.blackTransparent, foregroundColor: .white, icon: nil) - strongSelf.progressView.state = .Remote + } + + + guard let progressView = self.progressView else { + return + } + + progressView.fetchControls = self.fetchControls + + switch status { + case let .Fetching(_, progress): + var progress = progress + if let archiveStatus = archiveStatus { + switch archiveStatus { + case .progress: + if parent != nil { + progress = 0.1 + } + default: + break + } } + progress = max(progress, 0.1) + progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? presentation.activityBackground : theme.colors.blackTransparent, foregroundColor: file.previewRepresentations.isEmpty ? presentation.activityForeground : .white, icon: nil) + progressView.state = archiveStatus != nil && self.parent == nil ? .Icon(image: presentation.fileThumb, mode: .normal) : .Fetching(progress: progress, force: false) - strongSelf.progressView.userInteractionEnabled = status != .Local + case .Local: + progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? presentation.activityBackground : .clear, foregroundColor: file.previewRepresentations.isEmpty ? presentation.activityForeground : .clear, icon: nil) + progressView.state = !file.previewRepresentations.isEmpty ? .None : .Icon(image: presentation.fileThumb, mode: .normal) + case .Remote: + progressView.theme = RadialProgressTheme(backgroundColor: file.previewRepresentations.isEmpty ? presentation.activityBackground : theme.colors.blackTransparent, foregroundColor: file.previewRepresentations.isEmpty ? presentation.activityForeground : .white, icon: nil) + progressView.state = archiveStatus != nil && self.parent == nil ? .Icon(image: presentation.fileThumb, mode: .normal) : .Remote } + + progressView.userInteractionEnabled = status != .Local })) } - } override func layout() { super.layout() if let parameters = parameters as? ChatFileLayoutParameters { - let center = floorToScreenPixels((parameters.hasThumb ? 70 : 40) / 2) + let center = floorToScreenPixels(backingScaleFactor, (parameters.hasThumb ? 70 : 40) / 2) actionText.setFrameOrigin(leftInset, parameters.hasThumb ? center + 2 : 20) if parameters.hasThumb { - let f = thumbView.focus(progressView.frame.size) - progressView.setFrameOrigin(f.origin) + if let thumbProgress = thumbProgress { + let f = thumbView.focus(thumbProgress.frame.size) + thumbProgress.setFrameOrigin(f.origin) + } } else { - progressView.setFrameOrigin(NSZeroPoint) + progressView?.setFrameOrigin(NSZeroPoint) } } @@ -234,8 +439,8 @@ class ChatFileContentView: ChatMediaContentView { let parameters = self.parameters as? ChatFileLayoutParameters if let name = parameters?.name { - let center = floorToScreenPixels(frame.height/2) - name.1.draw(NSMakeRect(leftInset, isHasThumb ? center - name.0.size.height - 2 : 1, name.0.size.width, name.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + let center = floorToScreenPixels(backingScaleFactor, frame.height/2) + name.1.draw(NSMakeRect(leftInset, isHasThumb ? center - name.0.size.height - 2 : 1, name.0.size.width, name.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } } @@ -243,14 +448,25 @@ class ChatFileContentView: ChatMediaContentView { if let media = media as? TelegramMediaFile, !media.previewRepresentations.isEmpty { return thumbView.copy() } - return progressView.copy() + return progressView?.copy() ?? self + } + + override var contents: Any? { + return (copy() as? NSView)?.layer?.contents + } + + override var contentFrame: NSRect { + if let media = media as? TelegramMediaFile, !media.previewRepresentations.isEmpty { + return thumbView.frame + } + return progressView?.frame ?? frame } - override var interactionContentView: NSView { + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { if let media = media as? TelegramMediaFile, !media.previewRepresentations.isEmpty { return thumbView } - return progressView + return progressView ?? self } override func setContent(size: NSSize) { diff --git a/Telegram-Mac/ChatFileMediaItem.swift b/Telegram-Mac/ChatFileMediaItem.swift index 5d02f888f0..1707cd7fc1 100644 --- a/Telegram-Mac/ChatFileMediaItem.swift +++ b/Telegram-Mac/ChatFileMediaItem.swift @@ -7,40 +7,147 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit -class ChatFileLayoutParameters : ChatMediaLayoutParameters { +class ChatFileLayoutParameters : ChatMediaGalleryParameters { var nameNode:TextNode = TextNode() var name:(TextNodeLayout, TextNode)? let hasThumb:Bool let fileName:String - init(fileName:String, hasThumb: Bool) { + let finderLayout: TextViewLayout + let downloadLayout: TextViewLayout + fileprivate let uploadingLayout: TextViewLayout + fileprivate let downloadingLayout: TextViewLayout + init(fileName:String, hasThumb: Bool, presentation: ChatMediaPresentation, media: Media, automaticDownload: Bool, isIncoming: Bool, autoplayMedia: AutoplayMediaPreferences, isChatRelated: Bool = false) { self.fileName = fileName self.hasThumb = hasThumb + + let file = media as! TelegramMediaFile + + + self.uploadingLayout = TextViewLayout(.initialize(string: L10n.messagesFileStateFetchingOut1(100), font: .normal(.text)), alwaysStaticItems: true) + self.downloadingLayout = TextViewLayout(.initialize(string: L10n.messagesFileStateFetchingIn1(100), font: .normal(.text)), alwaysStaticItems: true) + + + var attr:NSMutableAttributedString = NSMutableAttributedString() + let _ = attr.append(string: .prettySized(with: file.elapsedSize), color: presentation.grayText, font: .normal(.text)) + if !(file.resource is LocalFileReferenceMediaResource) || isChatRelated { + let _ = attr.append(string: " - ", color: presentation.grayText, font: .normal(.text)) + + let range = attr.append(string: tr(L10n.messagesFileStateLocal), color: theme.bubbled && !isIncoming ? presentation.grayText : presentation.link, font: .medium(FontSize.text)) + attr.addAttribute(NSAttributedString.Key.link, value: "chat://file/finder", range: range) + } + finderLayout = TextViewLayout(attr, maximumNumberOfLines: 1, alwaysStaticItems: true) + + + attr = NSMutableAttributedString() + let _ = attr.append(string: .prettySized(with: file.elapsedSize), color: presentation.grayText, font: .normal(.text)) + if !(file.resource is LocalFileReferenceMediaResource) || isChatRelated { + let _ = attr.append(string: " - ", color: presentation.grayText, font: .normal(.text)) + let range = attr.append(string: tr(L10n.messagesFileStateRemote), color: theme.bubbled && !isIncoming ? presentation.grayText : presentation.link, font: .medium(.text)) + attr.addAttribute(NSAttributedString.Key.link, value: "chat://file/download", range: range) + } + downloadLayout = TextViewLayout(attr, maximumNumberOfLines: 1, alwaysStaticItems: true) + + + super.init(isWebpage: false, presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: autoplayMedia) + + } + override func makeLabelsForWidth(_ width: CGFloat) { + self.name = TextNode.layoutText(maybeNode: nameNode, .initialize(string: fileName , color: presentation.text, font: .medium(.text)), nil, 1, .middle, NSMakeSize(width, 20), nil,false, .left) + + + uploadingLayout.measure(width: width) + downloadingLayout.measure(width: width) + + downloadLayout.measure(width: width) + finderLayout.measure(width: width) + } } class ChatFileMediaItem: ChatMediaItem { - - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { - super.init(initialSize, chatInteraction, account, object) - self.parameters = ChatMediaLayoutParameters.layout(for: (self.media as! TelegramMediaFile), isWebpage: false, chatInteraction: chatInteraction) + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) + self.parameters = ChatMediaLayoutParameters.layout(for: (self.media as! TelegramMediaFile), isWebpage: false, chatInteraction: chatInteraction, presentation: .make(for: object.message!, account: context.account, renderType: object.renderType), automaticDownload: downloadSettings.isDownloable(object.message!), isIncoming: object.message!.isIncoming(context.account, object.renderType == .bubble), isFile: true, autoplayMedia: object.autoplayMedia, isChatRelated: true) + + (self.parameters as? ChatFileLayoutParameters)?.showMedia = { [weak self] message in + guard let `self` = self else {return} + + var type:GalleryAppearType = .history + if let parameters = self.parameters as? ChatMediaGalleryParameters, parameters.isWebpage { + type = .alone + } else if message.containsSecretMedia { + type = .secret + } + showChatGallery(context: context, message: message, self.table, self.parameters as? ChatMediaGalleryParameters, type: type) + } + + (self.parameters as? ChatFileLayoutParameters)?.showMessage = { [weak self] message in + self?.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + } override func makeContentSize(_ width: CGFloat) -> NSSize { + var width = width + let parameters = self.parameters as! ChatFileLayoutParameters + let file = media as! TelegramMediaFile + parameters.makeLabelsForWidth( width - (file.previewRepresentations.isEmpty ? 50 : 80)) + + + let progressMaxWidth = max(parameters.uploadingLayout.layoutSize.width, parameters.downloadingLayout.layoutSize.width) - parameters.name = TextNode.layoutText(maybeNode: parameters.nameNode, NSAttributedString.initialize(string: parameters.fileName , color: theme.colors.text, font: .medium(.text)), nil, 1, .middle, NSMakeSize(width - (parameters.hasThumb ? 80 : 50), 20), nil,false, .left) + let optionalWidth = max(parameters.name?.0.size.width ?? 0, max(max(parameters.finderLayout.layoutSize.width, parameters.downloadLayout.layoutSize.width), progressMaxWidth)) + (file.previewRepresentations.isEmpty ? 50 : 80) + + + if let captionLayout = self.captionLayout { + captionLayout.measure(width: width) + width = max(optionalWidth, captionLayout.layoutSize.width) + } else { + width = optionalWidth + } return NSMakeSize(width, parameters.hasThumb ? 70 : 40) } + override var additionalLineForDateInBubbleState: CGFloat? { + let file = media as! TelegramMediaFile + let parameters = self.parameters as! ChatFileLayoutParameters + + let progressMaxWidth = max(parameters.uploadingLayout.layoutSize.width, parameters.downloadingLayout.layoutSize.width) + + let accesoryWidth = max(max(parameters.finderLayout.layoutSize.width, parameters.downloadLayout.layoutSize.width), progressMaxWidth) + (file.previewRepresentations.isEmpty ? 50 : 80) + + if file.previewRepresentations.isEmpty, accesoryWidth > realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return super.additionalLineForDateInBubbleState + } + + + return file.previewRepresentations.isEmpty || captionLayout != nil ? super.additionalLineForDateInBubbleState : nil + } + override var isFixedRightPosition: Bool { + let file = media as! TelegramMediaFile + + let parameters = self.parameters as! ChatFileLayoutParameters + + let progressMaxWidth = max(parameters.uploadingLayout.layoutSize.width, parameters.downloadingLayout.layoutSize.width) + let accesoryWidth = max(max(parameters.finderLayout.layoutSize.width, parameters.downloadLayout.layoutSize.width), progressMaxWidth) + (file.previewRepresentations.isEmpty ? 50 : 80) + + if file.previewRepresentations.isEmpty, accesoryWidth < realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return true + } + + return file.previewRepresentations.isEmpty || captionLayout != nil ? super.isFixedRightPosition : true + } override func contentNode() -> ChatMediaContentView.Type { return ChatFileContentView.self diff --git a/Telegram-Mac/ChatGIFContentView.swift b/Telegram-Mac/ChatGIFContentView.swift index f7c482c02f..87effe1eda 100644 --- a/Telegram-Mac/ChatGIFContentView.swift +++ b/Telegram-Mac/ChatGIFContentView.swift @@ -7,25 +7,42 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit class ChatGIFContentView: ChatMediaContentView { - private var player:GIFPlayerView = GIFPlayerView() + private var player:GifPlayerBufferView = GifPlayerBufferView() private var progressView:RadialProgressView? + private let statusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() private let playerDisposable = MetaDisposable() +// private let nextTimebase: Atomic = Atomic(value: nil) +// private var data:AVGifData? { +// didSet { +// updatePlayerIfNeeded() +// } +// } - private var path:String? { - didSet { - updatePlayerIfNeeded() + override var backgroundColor: NSColor { + set { + super.backgroundColor = .clear + } + get { + return super.backgroundColor } } + override func previewMediaIfPossible() -> Bool { + guard let context = self.context, let window = self.kitWindow, let table = self.table, fetchStatus == .Local else {return false} + _ = startModalPreviewHandle(table, window: window, context: context) + return true + } + required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(player) @@ -48,162 +65,273 @@ class ChatGIFContentView: ChatMediaContentView { } override func open() { - if let parent = parent, let account = account { - showChatGallery(account:account, message:parent, table) + if let parent = parent, let parameters = parameters { + if !parameters.autoplay { + parameters.autoplay = true + + if let status = fetchStatus { + switch status { + case .Local: + progressView?.change(opacity: 0) + default: + progressView?.change(opacity: 1) + } + } + + updatePlayerIfNeeded() + } else if !(parent.media.first is TelegramMediaGame) { + parameters.showMedia(parent) + } } } - - +// override func videoTimebase() -> CMTimebase? { +// return player.controlTimebase +// } +// override func applyTimebase(timebase: CMTimebase?) { +// _ = nextTimebase.swap(timebase) +// } + override func cancelFetching() { - if let account = account, let media = media as? TelegramMediaFile { - chatMessageFileCancelInteractiveFetch(account: account, file: media) + if let context = context, let media = media as? TelegramMediaFile { + if let parent = parent { + messageMediaFileCancelInteractiveFetch(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)) + } else { + cancelFreeMediaFileInteractiveFetch(context: context, resource: media.resource) + } } } override func fetch() { - if let account = account, let media = media as? TelegramMediaFile { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + if let context = context, let media = media as? TelegramMediaFile { + if let parent = parent { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start()) + } else { + fetchDisposable.set(freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.standalone(media: media)).start()) + } } } override func layout() { super.layout() player.frame = bounds + + self.player.positionFlags = positionFlags progressView?.center() + updatePlayerIfNeeded() } func removeNotificationListeners() { NotificationCenter.default.removeObserver(self) } + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } @objc func updatePlayerIfNeeded() { - - let accept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) - player.set(path: accept ? path : nil) + let accept = parameters?.autoplay == true && window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) && !self.isDynamicContentLocked + + player.ticking = accept - - /* var s:Signal = .single() - s = s |> delay(0.01, queue: Queue.mainQueue()) - playerDisposable.set(s.start(next: {[weak self] (next) in - if let strongSelf = self { - let accept = strongSelf.window != nil && strongSelf.window!.isKeyWindow && !NSIsEmptyRect(strongSelf.visibleRect) - strongSelf.player.set(path: accept ? strongSelf.path : nil) - } - })) - */ } + func updateListeners() { if let window = window { + NotificationCenter.default.removeObserver(self) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: table?.view) } else { removeNotificationListeners() } } + override func willRemove() { + super.willRemove() + updateListeners() + updatePlayerIfNeeded() + } + override func viewDidMoveToWindow() { updateListeners() updatePlayerIfNeeded() } deinit { - player.set(path: nil) + //player.set(data: nil) + } + + var blurBackground: Bool { + return (parent != nil && parent?.groupingKey == nil) || parent == nil } - override func update(with media: Media, size: NSSize, account: Account, parent: Message?, table: TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false) { - let mediaUpdated = self.media == nil || !self.media!.isEqual(media) + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + let mediaUpdated = self.media == nil || !self.media!.isSemanticallyEqual(to: media) - super.update(with: media, size: size, account: account, parent:parent,table:table, parameters:parameters, animated: animated) + super.update(with: media, size: size, context: context, parent:parent,table:table, parameters:parameters, animated: animated, positionFlags: positionFlags) + + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius - updateListeners() + if let positionFlags = positionFlags { + if positionFlags.contains(.top) && positionFlags.contains(.left) { + topLeftRadius = topLeftRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + topRightRadius = topRightRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + bottomLeftRadius = bottomLeftRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + bottomRightRadius = bottomRightRadius * 3 + 2 + } + } + + + updateListeners() + self.player.positionFlags = positionFlags + if let media = media as? TelegramMediaFile { - if mediaUpdated { - - path = nil - - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: media.previewRepresentations) - var updatedStatusSignal: Signal? - - player.setSignal(account: account, signal: chatMessagePhoto(account: account, photo: image, scale: backingScaleFactor)) - let arguments = TransformImageArguments(corners: ImageCorners(radius:.cornerRadius), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) - player.set(arguments: arguments) - - if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: media), account.pendingMessageManager.pendingMessageStatus(parent.id)) - |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { - return .Fetching(isActive: true, progress: pendingStatus.progress) - } else { - return resourceStatus - } - } |> deliverOnMainQueue - } else { - updatedStatusSignal = chatMessageFileStatus(account: account, file: media) + let dimensions = media.dimensions?.size ?? size + var updatedStatusSignal: Signal? + + let reference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: media) : FileMediaReference.standalone(media: media) + let fitted = dimensions.aspectFilled(size) + player.setVideoLayerGravity(.resizeAspect) + + + let arguments = TransformImageArguments(corners: ImageCorners(topLeft: .Corner(topLeftRadius), topRight: .Corner(topRightRadius), bottomLeft: .Corner(bottomLeftRadius), bottomRight: .Corner(bottomRightRadius)), imageSize: fitted, boundingSize: size, intrinsicInsets: NSEdgeInsets(), resizeMode: .blurBackground) + + player.update(reference, context: context, resizeInChat: blurBackground) + + player.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor, positionFlags: positionFlags), clearInstantly: mediaUpdated) + + player.setSignal(chatMessageVideo(postbox: context.account.postbox, fileReference: reference, scale: backingScaleFactor), animate: true, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale, positionFlags: positionFlags) } + }) + player.set(arguments: arguments) + + + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: media), context.account.pendingMessageManager.pendingMessageStatus(parent.id)) + |> map { resourceStatus, pendingStatus -> MediaResourceStatus in + if let pendingStatus = pendingStatus.0 { + return .Fetching(isActive: true, progress: min(pendingStatus.progress, pendingStatus.progress * 85 / 100)) + } else { + return resourceStatus + } + } |> deliverOnMainQueue + } else { + updatedStatusSignal = chatMessageFileStatus(account: context.account, file: media, approximateSynchronousValue: approximateSynchronousValue) + } + + if let updatedStatusSignal = updatedStatusSignal { - if let updatedStatusSignal = updatedStatusSignal { - - - self.statusDisposable.set((combineLatest(updatedStatusSignal, account.postbox.mediaBox.resourceData(media.resource)) |> deliverOnMainQueue).start(next: { [weak self] (status,resource) in + self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { - strongSelf.fetchStatus = status - if case .Local = status { + strongSelf.updatePlayerIfNeeded() + + let needSetStatus: Bool + if case .Local = status, parameters?.autoplay == true { if let progressView = strongSelf.progressView { - progressView.removeFromSuperview() + progressView.state = parent == nil ? .ImpossibleFetching(progress: 1, force: false) : .Fetching(progress: 1, force: false) strongSelf.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) } - strongSelf.path = resource.path - + needSetStatus = false } else { if strongSelf.progressView == nil { - let progressView = RadialProgressView() + let progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) progressView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0)) strongSelf.progressView = progressView strongSelf.addSubview(progressView) strongSelf.progressView?.center() strongSelf.progressView?.fetchControls = strongSelf.fetchControls } + needSetStatus = true } - - switch status { - case let .Fetching(_, progress): - strongSelf.progressView?.state = .Fetching(progress: progress, force: false) - case .Local: - strongSelf.progressView?.state = .Play - case .Remote: - strongSelf.progressView?.state = .Remote + if needSetStatus { + switch status { + case let .Fetching(_, progress): + strongSelf.progressView?.state = parent == nil ? .ImpossibleFetching(progress: progress, force: false) : .Fetching(progress: progress, force: false) + case .Local: + if parent != nil { + strongSelf.progressView?.state = .Play + } + case .Remote: + if parent != nil { + strongSelf.progressView?.state = .Remote + } + } } } })) - } - - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) - } } + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + var bp:Int = 0 + bp += 1 } - override func copy() -> Any { - let view = View() - view.backgroundColor = .clear - let layer:CALayer = CALayer() - layer.frame = NSMakeRect(0, visibleRect.minY == 0 ? 0 : player.visibleRect.height - player.frame.height, player.frame.width, player.frame.height) - layer.contents = player.layer?.contents - layer.masksToBounds = true - view.frame = player.visibleRect - layer.shouldRasterize = true - layer.rasterizationScale = backingScaleFactor - view.layer?.addSublayer(layer) - return view + override var contents: Any? { + return player.layer?.contents + } + + override open func copy() -> Any { + return player.copy() + +// let view = NSView() +// view.wantsLayer = true +// +// view.background = .clear +// view.layer?.contents = player.layer?.contents +// view.frame = self.visibleRect +// view.layer?.masksToBounds = true +// +// +// if bounds != visibleRect { +// if let image = player.layer?.contents { +// view.layer?.contents = generateImage(player.bounds.size, contextGenerator: { size, ctx in +// ctx.clear(player.bounds) +// ctx.setFillColor(.clear) +// ctx.fill(player.bounds) +// +// if player.visibleRect.minY == 0 { +// ctx.clip(to: NSMakeRect(0, 0, player.bounds.width, player.bounds.height - ( player.bounds.height - player.visibleRect.height))) +// } else { +// ctx.clip(to: NSMakeRect(0, (player.bounds.height - player.visibleRect.height), player.bounds.width, player.bounds.height - ( player.bounds.height - player.visibleRect.height))) +// } +// ctx.draw(image as! CGImage, in: player.bounds) +// }, opaque: false) +// } +// } +// +// view.layer?.shouldRasterize = true +// view.layer?.rasterizationScale = backingScaleFactor +// +// return view } } diff --git a/Telegram-Mac/ChatGradientModel.swift b/Telegram-Mac/ChatGradientModel.swift new file mode 100644 index 0000000000..cdaf1cd1ea --- /dev/null +++ b/Telegram-Mac/ChatGradientModel.swift @@ -0,0 +1,105 @@ +// +// ChatGradientModel.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +private let maskInset: CGFloat = 1.0 + + +final class ChatMessageBubbleBackdrop: NSView { + private let backgroundContent: NSView + private let borderView: SImageView = SImageView() + private var currentMaskMode: Bool? + + private var maskView: SImageView? + + override var frame: CGRect { + didSet { + if let maskView = self.maskView { + let maskFrame = self.bounds + if maskView.frame != maskFrame { + maskView.frame = maskFrame + } + } + } + } + + init() { + self.backgroundContent = NSView() + + super.init(frame: NSZeroRect) + autoresizingMask = [] + autoresizesSubviews = false + self.backgroundContent.wantsLayer = true + wantsLayer = true + self.layer?.masksToBounds = true + self.addSubview(self.backgroundContent) + self.addSubview(self.borderView) + self.layer?.disableActions() + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) -> Void { + super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + + } + + func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + maskView?._change(size: size, animated: animated, duration: duration, timingFunction: timingFunction) + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + self.borderView._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + + func setType(image: (CGImage, NSEdgeInsets)?, border: (CGImage, NSEdgeInsets)?, background: CGImage) { + if let _ = image { + let maskView: SImageView + if let current = self.maskView { + maskView = current + } else { + maskView = SImageView() + maskView.frame = self.bounds + self.maskView?.layer?.disableActions() + self.maskView = maskView + self.layer?.mask = maskView.layer + } + } else { + if let _ = self.maskView { + self.layer?.mask = nil + self.maskView = nil + } + } + self.borderView.data = border + self.backgroundContent.layer?.contents = background + if let maskView = self.maskView { + maskView.data = image + } + self.backgroundContent.isHidden = image == nil + } + + override func layout() { + super.layout() + self.borderView.frame = bounds + } + + func update(rect: CGRect, within containerSize: CGSize, animated: Bool, rotated: Bool = false) { + self.backgroundContent._change(size: containerSize, animated: animated) + self.backgroundContent._change(pos: CGPoint(x: -rect.minX, y: -rect.minY), animated: animated, forceAnimateIfHasAnimation: true) + if rotated { + backgroundContent.rotate(byDegrees: 180) + } else { + backgroundContent.rotate(byDegrees: 0) + } + } +} diff --git a/Telegram-Mac/ChatGroupedItem.swift b/Telegram-Mac/ChatGroupedItem.swift new file mode 100644 index 0000000000..08cbcd46f0 --- /dev/null +++ b/Telegram-Mac/ChatGroupedItem.swift @@ -0,0 +1,1040 @@ +// +// ChatGroupedItem.swift +// Telegram +// +// Created by keepcoder on 31/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class ChatGroupedItem: ChatRowItem { + + fileprivate(set) var parameters: ChatMediaGalleryParameters? + fileprivate let layout: GroupedLayout + + override var messages: [Message] { + return layout.messages + } + + + + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ entry: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + + var captionLayout: TextViewLayout? + + if case let .groupedPhotos(messages, _) = entry { + + let messages = messages.map{$0.message!}.filter({!$0.media.isEmpty}) + self.layout = GroupedLayout(messages) + + var captionMessage: Message? = nil + for message in messages { + if let _ = captionMessage, !message.text.isEmpty { + captionMessage = nil + break + } + if !message.text.isEmpty { + captionMessage = message + } + } + + if let message = captionMessage { + + let isIncoming: Bool = message.isIncoming(context.account, entry.renderType == .bubble) + + var caption:NSMutableAttributedString = NSMutableAttributedString() + NSAttributedString.initialize() + _ = caption.append(string: message.text, color: theme.chat.textColor(isIncoming, entry.renderType == .bubble), font: NSFont.normal(theme.fontSize)) + var types:ParsingType = [.Links, .Mentions, .Hashtags] + + if let peer = messageMainPeer(message) as? TelegramUser { + if peer.botInfo != nil { + types.insert(.Commands) + } + } else if let peer = messageMainPeer(message) as? TelegramChannel { + switch peer.info { + case .group: + types.insert(.Commands) + default: + break + } + } else { + types.insert(.Commands) + } + + var hasEntities: Bool = false + for attr in message.attributes { + if attr is TextEntitiesMessageAttribute { + hasEntities = true + break + } + } + if hasEntities { + caption = ChatMessageItem.applyMessageEntities(with: message.attributes, for: message.text.fixed, context: context, fontSize: theme.fontSize, openInfo:chatInteraction.openInfo, botCommand:chatInteraction.sendPlainText, hashtag: chatInteraction.modalSearch, applyProxy: chatInteraction.applyProxy, textColor: theme.chat.textColor(isIncoming, entry.renderType == .bubble), linkColor: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), monospacedPre: theme.chat.monospacedPreColor(isIncoming, entry.renderType == .bubble), monospacedCode: theme.chat.monospacedCodeColor(isIncoming, entry.renderType == .bubble), openBank: chatInteraction.openBank).mutableCopy() as! NSMutableAttributedString + } + + if !hasEntities || message.flags.contains(.Failed) || message.flags.contains(.Unsent) || message.flags.contains(.Sending) { + caption.detectLinks(type: types, context: context, color: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), openInfo:chatInteraction.openInfo, hashtag: context.sharedContext.bindings.globalSearch, command: chatInteraction.sendPlainText, applyProxy: chatInteraction.applyProxy) + } +// caption.detectLinks(type: types, context: context, color: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), openInfo:chatInteraction.openInfo, hashtag: context.sharedContext.bindings.globalSearch, command: chatInteraction.sendPlainText, applyProxy: chatInteraction.applyProxy) + captionLayout = TextViewLayout(caption, alignment: .left, selectText: theme.chat.selectText(isIncoming, entry.renderType == .bubble), strokeLinks: entry.renderType == .bubble, alwaysStaticItems: true) + captionLayout?.interactions = globalLinkExecutor + + } + + } else { + fatalError("") + } + + super.init(initialSize, chatInteraction, context, entry, downloadSettings, theme: theme) + + self.captionLayout = captionLayout + + guard let message = message else {return} + + self.parameters = ChatMediaGalleryParameters(showMedia: { [weak self] message in + guard let `self` = self else {return} + + var type:GalleryAppearType = .history + if let parameters = self.parameters, parameters.isWebpage { + type = .alone + } else if message.containsSecretMedia { + type = .secret + } + showChatGallery(context: context, message: message, self.table, self.parameters, type: type) + + }, showMessage: { [weak self] message in + self?.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + }, isWebpage: chatInteraction.isLogInteraction, presentation: .make(for: message, account: context.account, renderType: entry.renderType), media: message.media.first!, automaticDownload: downloadSettings.isDownloable(message), autoplayMedia: entry.autoplayMedia) + + self.parameters?.automaticDownloadFunc = { message in + return downloadSettings.isDownloable(message) + } + + if isBubbleFullFilled, layout.messages.count == 1 { + var positionFlags: LayoutPositionFlags = [] + if captionLayout == nil { + positionFlags.insert(.bottom) + positionFlags.insert(.left) + positionFlags.insert(.right) + } + if authorText == nil && replyModel == nil && forwardNameLayout == nil { + positionFlags.insert(.top) + positionFlags.insert(.left) + positionFlags.insert(.right) + } + self.positionFlags = positionFlags + } + } + + override func share() { + if let message = message { + showModal(with: ShareModalController(ShareMessageObject(context, message, layout.messages)), for: mainWindow) + } + + } + + override var hasBubble: Bool { + get { + return isBubbled && (captionLayout != nil || message?.replyAttribute != nil || forwardNameLayout != nil || layout.messages.count == 1) + } + set { + super.hasBubble = newValue + } + } + + override var isBubbleFullFilled: Bool { + return isBubbled + } + + var mediaBubbleCornerInset: CGFloat { + return 1 + } + + override var bubbleFrame: NSRect { + var frame = super.bubbleFrame + + if isBubbleFullFilled { + frame.size.width = contentSize.width + additionBubbleInset + if hasBubble { + frame.size.width += self.mediaBubbleCornerInset * 2 + } + } + + return frame + } + + override var defaultContentTopOffset: CGFloat { + if isBubbled && !hasBubble { + return 2 + } + return super.defaultContentTopOffset + } + + fileprivate var positionFlags: LayoutPositionFlags? + + override var contentOffset: NSPoint { + var offset = super.contentOffset + // + if hasBubble { + if forwardNameLayout != nil { + offset.y += defaultContentInnerInset + } else if authorText == nil, !isBubbleFullFilled { + offset.y += (defaultContentInnerInset + 2) + } + } + + if hasBubble && authorText == nil && replyModel == nil && forwardNameLayout == nil { + offset.y -= (defaultContentInnerInset + self.mediaBubbleCornerInset * 2 - 1) + } + return offset + } + + override var elementsContentInset: CGFloat { + if hasBubble && isBubbleFullFilled { + return bubbleContentInset + } + return super.elementsContentInset + } + + override var _defaultHeight: CGFloat { + if hasBubble && isBubbleFullFilled && captionLayout == nil { + return contentOffset.y + defaultContentInnerInset - mediaBubbleCornerInset * 2 + } + + return super._defaultHeight + } + + override var realContentSize: NSSize { + var size = super.realContentSize + + if isBubbleFullFilled { + size.width -= bubbleContentInset * 2 + } + return size + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if let caption = captionLayout { + if let line = caption.lines.last, line.frame.width > realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return rightSize.height + } + } + return super.additionalLineForDateInBubbleState + } + + override var isFixedRightPosition: Bool { + return true + } + + override func makeContentSize(_ width: CGFloat) -> NSSize { + layout.measure(NSMakeSize(min(width, 360), min(width, 320)), spacing: hasBubble ? 2 : 4) + return layout.dimensions + } + + override var topInset:CGFloat { + return 4 + } + + func contentNode(for index: Int) -> ChatMediaContentView.Type { + return ChatLayoutUtils.contentNode(for: layout.messages[index].media[0]) + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var _message: Message? = nil + let context = self.context + + for i in 0 ..< layout.count { + if NSPointInRect(location, layout.frame(at: i)) { + _message = layout.messages[i] + break + } + } + if let message = _message { + return chatMenuItems(for: message, chatInteraction: self.chatInteraction) + } + + guard let message = layout.messages.first else { + return .single([]) + } + + var items: [ContextMenuItem] = [] + + if chatInteraction.mode == .scheduled, let peer = chatInteraction.peer { + items.append(ContextMenuItem(L10n.chatContextScheduledSendNow, handler: { + _ = sendScheduledMessageNowInteractively(postbox: context.account.postbox, messageId: message.id).start() + })) + items.append(ContextMenuItem(L10n.chatContextScheduledReschedule, handler: { + showModal(with: ScheduledMessageModalController(context: context, defaultDate: Date(timeIntervalSince1970: TimeInterval(message.timestamp)), peerId: peer.id, scheduleAt: { date in + _ = showModalProgress(signal: requestEditMessage(account: context.account, messageId: message.id, text: message.text, media: .keep, scheduleTime: Int32(date.timeIntervalSince1970)), for: context.window).start() + }), for: context.window) + })) + items.append(ContextSeparatorItem()) + } + + + items.append(ContextMenuItem(tr(L10n.messageContextSelect), handler: { [weak self] in + guard let `self` = self else {return} + let messageIds = self.layout.messages.map{$0.id} + self.chatInteraction.withToggledSelectedMessage({ current in + var current = current + for id in messageIds { + current = current.withToggledSelectedMessage(id) + } + return current + }) + })) + + var canDelete = true + for i in 0 ..< layout.count { + if !canDeleteMessage(layout.messages[i], account: context.account) { + canDelete = false + break + } + } + + var canPin = true + for i in 0 ..< layout.count { + if let peer = peer { + if !canPinMessage(layout.messages[i], for: peer, account: context.account) { + canPin = false + break + } + } + } + + let chatInteraction = self.chatInteraction + let account = self.context.account + + if let peer = message.peers[message.id.peerId] as? TelegramChannel, peer.hasPermission(.pinMessages) || (peer.isChannel && peer.hasPermission(.editAllMessages)), chatInteraction.mode == .history { + if !message.flags.contains(.Unsent) && !message.flags.contains(.Failed) { + items.append(ContextMenuItem(tr(L10n.messageContextPin), handler: { + if peer.isSupergroup { + modernConfirm(for: mainWindow, account: account, peerId: nil, header: L10n.messageContextConfirmPin1, information: nil, thridTitle: L10n.messageContextConfirmNotifyPin, successHandler: { result in + chatInteraction.updatePinned(message.id, false, result != .thrid) + }) + } else { + chatInteraction.updatePinned(message.id, false, true) + } + })) + } + } else if message.id.peerId == account.peerId, chatInteraction.mode == .history { + items.append(ContextMenuItem(L10n.messageContextPin, handler: { + chatInteraction.updatePinned(message.id, false, true) + })) + } else if let peer = message.peers[message.id.peerId] as? TelegramGroup, peer.canPinMessage, chatInteraction.mode == .history { + items.append(ContextMenuItem(L10n.messageContextPin, handler: { + modernConfirm(for: mainWindow, account: account, peerId: nil, header: L10n.messageContextConfirmPin1, information: nil, thridTitle: L10n.messageContextConfirmNotifyPin, successHandler: { result in + chatInteraction.updatePinned(message.id, false, result == .thrid) + }) + })) + } + + + if canDelete { + items.append(ContextMenuItem(tr(L10n.messageContextDelete), handler: { [weak self] in + guard let `self` = self else {return} + self.chatInteraction.deleteMessages(self.layout.messages.map{$0.id}) + })) + } + + if let message = layout.messages.first, let peer = peer, peer.canSendMessage, chatInteraction.peerId == message.id.peerId, chatInteraction.mode == .history { + items.append(ContextMenuItem(tr(L10n.messageContextReply1) + (FastSettings.tooltipAbility(for: .edit) ? " (\(tr(L10n.messageContextReplyHelp)))" : ""), handler: { [weak self] in + self?.chatInteraction.setupReplyMessage(message.id) + })) + } + + if let message = layout.messages.last, !message.flags.contains(.Failed), !message.flags.contains(.Unsent), chatInteraction.mode == .history { + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + items.append(ContextMenuItem(L10n.messageContextCopyMessageLink1, handler: { + _ = showModalProgress(signal: exportMessageLink(account: context.account, peerId: peer.id, messageId: message.id), for: context.window).start(next: { link in + if let link = link { + copyToClipboard(link) + } + }) + })) + } + } + + var editMessage: Message? = nil + for message in layout.messages { + if let _ = editMessage, !message.text.isEmpty { + editMessage = nil + break + } + if !message.text.isEmpty { + editMessage = message + } + } + if let editMessage = editMessage { + if canEditMessage(editMessage, context: context) { + items.append(ContextMenuItem(tr(L10n.messageContextEdit), handler: { [weak self] in + self?.chatInteraction.beginEditingMessage(editMessage) + })) + } + } + var canForward: Bool = true + for message in layout.messages { + if !canForwardMessage(message, account: context.account) { + canForward = false + break + } + } + + if canForward { + items.append(ContextMenuItem(tr(L10n.messageContextForward), handler: { [weak self] in + guard let `self` = self else {return} + self.chatInteraction.forwardMessages(self.layout.messages.map {$0.id}) + })) + } + + return .single(items) |> map { [weak self] items in + var items = items + if let captionLayout = self?.captionLayout { + let text = captionLayout.attributedString.string + items.insert(ContextMenuItem(tr(L10n.textCopy), handler: { + copyToClipboard(text) + }), at: 1) + + if let view = self?.view as? ChatRowView, let textView = view.captionView, let window = textView.window { + let point = textView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let layout = textView.layout { + if let (link, _, range, _) = layout.link(at: point) { + var text:String = layout.attributedString.string.nsstring.substring(with: range) + if let link = link as? inAppLink { + if case let .external(link, _) = link { + text = link + } + } + + for i in 0 ..< items.count { + if items[i].title == tr(L10n.messageContextCopyMessageLink1) { + items.remove(at: i) + break + } + } + + items.insert(ContextMenuItem(tr(L10n.messageContextCopyMessageLink1), handler: { + copyToClipboard(text) + }), at: 1) + } + } + } + } + + return items + } + } + + override var instantlyResize: Bool { + return true + } + + override func viewClass() -> AnyClass { + return ChatGroupedView.self + } + +} + +private class ChatGroupedView : ChatRowView , ModalPreviewRowViewProtocol { + + private var contents: [ChatMediaContentView] = [] + private var selectionBackground: CornerView = CornerView() + + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + guard let item = item as? ChatGroupedItem, let window = window as? Window else { return nil } + + let location = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i)) { + let contentNode = contents[i] + if contentNode is ChatGIFContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, GifPreviewModalView.self), contentNode) + } + } else if contentNode is ChatInteractiveContentView { + if let image = contentNode.media as? TelegramMediaImage { + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } + } + } + + return nil + } + + override func forceClick(in location: NSPoint) { + if previewMediaIfPossible() { + + } else { + super.forceClick(in: location) + } + } + + override func previewMediaIfPossible() -> Bool { + guard let item = item as? ChatGroupedItem, let window = window as? Window else { return false } + + let location = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + if contentView.mouseInside() { + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i)) { + let result = contents[i].previewMediaIfPossible() + return result + } + } + } + return false + } + + override func updateColors() { + super.updateColors() + selectionBackground.layer?.cornerRadius = .cornerRadius + selectionBackground.background = .blackTransparent + } + + override func notify(with value: Any, oldValue: Any, animated: Bool) { + super.notify(with: value, oldValue: oldValue, animated: animated) + } + + override func canDropSelection(in location: NSPoint) -> Bool { + let point = self.convert(location, from: nil) + return true//!NSPointInRect(point, contentView.frame) + } + + override func draw(_ dirtyRect: NSRect) { + + } + + override func updateMouse() { + super.updateMouse() + for content in contents { + content.updateMouse() + } + } + + override func updateSelectingState(_ animated: Bool, selectingMode: Bool, item: ChatRowItem?, needUpdateColors: Bool) { + + + if let item = item as? ChatGroupedItem { + + if selectingMode { + if contents.count > 1 { + for content in contents { + let subviews = content.subviews + var selectingControl: SelectingControl? + for subview in subviews { + if subview is SelectingControl { + selectingControl = subview as? SelectingControl + break + } + } + if selectingControl == nil { + selectingControl = SelectingControl(unselectedImage: theme.icons.chatGroupToggleUnselected, selectedImage: theme.icons.chatGroupToggleSelected) + content.addSubview(selectingControl!) + if animated { + selectingControl?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + selectingControl?.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: 0.2) + } + } + if let selectingControl = selectingControl { + selectingControl.setFrameOrigin(content.frame.width - selectingControl.frame.width - 5, 5) + } + } + } + } else { + for content in contents { + let subviews = content.subviews + for subview in subviews { + if subview is SelectingControl { + if animated { + subview.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false) + subview.layer?.animateScaleSpring(from: 1, to: 0.2, duration: 0.2, removeOnCompletion: false, completion: { [weak subview] completed in + if completed { + subview?.removeFromSuperview() + } + }) + } + break + } + } + } + } + if let selectionState = item.chatInteraction.presentation.selectionState { + for i in 0 ..< contents.count { + loop: for subview in contents[i].subviews { + if let select = subview as? SelectingControl { + select.set(selected: selectionState.selectedIds.contains(item.layout.messages[i].id), animated: animated) + break loop + } + } + } + } + } + super.updateSelectingState(animated, selectingMode: selectingMode, item: item, needUpdateColors: needUpdateColors) + } + + override func updateSelectionViewAfterUpdateState(item: ChatRowItem, animated: Bool) { + guard let item = item as? ChatGroupedItem else {return} + guard let selectingView = selectingView else {return} + + + + var selected: Bool = true + for message in item.layout.messages { + if !item.chatInteraction.presentation.isSelectedMessageId(message.id) { + selected = false + break + } + } + selectingView.set(selected: selected, animated: animated) + } + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? ChatGroupedItem else {return} + + + + if contents.count > item.layout.count { + let contentCount = contents.count + let layoutCount = item.layout.count + + for i in layoutCount ..< contentCount { + contents[i].removeFromSuperview() + } + contents = contents.subarray(with: NSMakeRange(0, layoutCount)) + + for i in 0 ..< contents.count { + if !contents[i].isKind(of: item.contentNode(for: i)) { + let node = item.contentNode(for: i) + let view = node.init(frame:NSZeroRect) + replaceSubview(contents[i], with: view) + contents[i] = view + } + } + } else if contents.count < item.layout.count { + let contentCount = contents.count + for i in contentCount ..< item.layout.count { + let node = item.contentNode(for: i) + let view = node.init(frame:NSZeroRect) + //view.progressDimension = NSMakeSize(20, 20) + contents.append(view) + } + } + + for content in contents { + addSubview(content) + } + + super.set(item: item, animated: animated) + + assert(contents.count == item.layout.count) + + let approximateSynchronousValue = item.approximateSynchronousValue + + contentView.frame = self.contentFrame + + for i in 0 ..< item.layout.count { + contents[i].change(size: item.layout.frame(at: i).size, animated: animated) + var positionFlags: LayoutPositionFlags = item.isBubbled ? item.positionFlags ?? item.layout.position(at: i) : [] + + if item.hasBubble { + if item.captionLayout != nil { + positionFlags.remove(.bottom) + } + if item.authorText != nil || item.replyModel != nil || item.forwardNameLayout != nil { + positionFlags.remove(.top) + } + } + + + contents[i].update(with: item.layout.messages[i].media[0], size: item.layout.frame(at: i).size, context: item.context, parent: item.layout.messages[i], table: item.table, parameters: item.parameters, animated: animated, positionFlags: positionFlags, approximateSynchronousValue: approximateSynchronousValue) + + contents[i].change(pos: item.layout.frame(at: i).origin, animated: animated) + } + + needsLayout = true + } + + override var needsDisplay: Bool { + get { + return super.needsDisplay + } + set { + super.needsDisplay = newValue + for content in contents { + content.needsDisplay = newValue + } + } + } + override var backgroundColor: NSColor { + didSet { + for content in contents { + content.backgroundColor = backdorColor + } + } + } + + + override func toggleSelected(_ select: Bool, in point: NSPoint) { + guard let item = item as? ChatGroupedItem else { return } + + let location = contentView.convert(point, from: nil) + var applied: Bool = contentView.mouseInside() + if contentView.mouseInside() { + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i)) { + let id = item.layout.messages[i].id + item.chatInteraction.withToggledSelectedMessage({ current in + if (select && !current.isSelectedMessageId(id)) || (!select && current.isSelectedMessageId(id)) { + return current.withToggledSelectedMessage(id) + } + return current + }) + applied = true + break + } + } + } + + if !applied { + item.chatInteraction.withToggledSelectedMessage({ current in + return item.layout.messages.reduce(current, { current, message -> ChatPresentationInterfaceState in + if (select && !current.isSelectedMessageId(message.id)) || (!select && current.isSelectedMessageId(message.id)) { + return current.withToggledSelectedMessage(message.id) + } + return current + }) + }) + } + + } + + + + override func forceSelectItem(_ item: ChatRowItem, onRightClick: Bool) { + + guard let item = item as? ChatGroupedItem else {return} + guard let window = window as? Window else {return} + + if onRightClick { + item.chatInteraction.withToggledSelectedMessage({ current in + var current: ChatPresentationInterfaceState = current + for message in item.layout.messages { + current = current.withToggledSelectedMessage(message.id) + } + return current + }) + return + } + + guard item.chatInteraction.presentation.state == .selecting else {return} + + let location = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + var selected: Bool = contentView.mouseInside() + if contentView.mouseInside() { + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i)) { + item.chatInteraction.withToggledSelectedMessage({ + $0.withToggledSelectedMessage(item.layout.messages[i].id) + }) + selected = true + break + } + } + } + + + if !selected { + let select = !isHasSelectedItem + item.chatInteraction.withToggledSelectedMessage({ current in + return item.layout.messages.reduce(current, { current, message -> ChatPresentationInterfaceState in + if (select && !current.isSelectedMessageId(message.id)) || (!select && current.isSelectedMessageId(message.id)) { + return current.withToggledSelectedMessage(message.id) + } + return current + }) + }) + } + + } + + override func viewWillMove(toSuperview newSuperview: NSView?) { + if newSuperview == nil { + for content in contents { + content.willRemove() + } + } + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + + if let innerId = innerId.base as? ChatHistoryEntryId { + switch innerId { + case .message(let message): + for content in contents { + if content.parent?.id == message.id { + return content + } + } + default: + break + } + } + + return super.interactionContentView(for: innerId, animateIn: animateIn) + } + + override func interactionControllerDidFinishAnimation(interactive: Bool, innerId: AnyHashable) { + guard let item = item as? ChatRowItem else {return} +// if let innerId = innerId.base as? ChatHistoryEntryId, interactive { +// switch innerId { +// case .message(let message): +// for content in contents { +// if content.parent?.id == message.id { +// content.interactionControllerDidFinishAnimation(interactive: interactive) +// let rect = rightView.convert(rightView.bounds, to: content.superview) +// if NSIntersectsRect(rect, content.frame), item.isStateOverlayLayout { +// animateInStateView() +// } +// } +// } +// default: +// break +// } +// } + } + + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + + guard let item = item as? ChatRowItem else {return} + if let innerId = innerId.base as? ChatHistoryEntryId { + switch innerId { + case .message(let message): + for content in contents { + if content.parent?.id == message.id { + let rect = rightView.convert(rightView.bounds, to: content.superview) + if NSIntersectsRect(rect, content.frame), item.isStateOverlayLayout { + let rightView = ChatRightView(frame: NSZeroRect) + rightView.set(item: item, animated: false) + var rect = self.rightView.convert(self.rightView.bounds, to: content) + + if content.visibleRect.minY < rect.midY && content.visibleRect.minY + content.visibleRect.height > rect.midY { + rect.origin.y = content.frame.height - rect.maxY + rightView.frame = rect + view.addSubview(rightView) + } + + } + content.addAccesoryOnCopiedView(view: view) + + } + } + default: + break + } + } + + + } + + + override func isSelectInGroup(_ location: NSPoint) -> Bool { + guard let item = item as? ChatGroupedItem else {return false} + + guard item.chatInteraction.presentation.state == .selecting else {return false} + + let location = contentView.convert(location, from: nil) + + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i)) { + return item.chatInteraction.presentation.isSelectedMessageId(item.layout.messages[i].id) + } + } + return false + } + + private var isHasSelectedItem: Bool { + guard let item = item as? ChatGroupedItem else { + return false + } + for message in item.layout.messages { + if item.chatInteraction.presentation.isSelectedMessageId(message.id) { + return true + } + } + return false + } + + override var backdorColor: NSColor { + guard let item = item as? ChatGroupedItem, !item.isBubbled else { + return super.backdorColor + } + + + if let _ = contextMenu { + return theme.colors.selectMessage + } + + + for message in item.layout.messages { + if item.chatInteraction.presentation.isSelectedMessageId(message.id) { + return theme.colors.selectMessage + } + } + + return super.backdorColor + } + + override func focusAnimation(_ innerId: AnyHashable?) { + if let innerId = innerId { + guard let item = item as? ChatGroupedItem else {return} + + for i in 0 ..< item.layout.count { + if AnyHashable(ChatHistoryEntryId.message(item.layout.messages[i])) == innerId { + selectionBackground.removeFromSuperview() + selectionBackground.setFrameSize(item.layout.frame(at: i).size) + + var positionFlags: LayoutPositionFlags = item.isBubbled ? item.positionFlags ?? item.layout.position(at: i) : [] + + if item.hasBubble { + if item.captionLayout != nil { + positionFlags.remove(.bottom) + } + if item.authorText != nil || item.replyModel != nil || item.forwardNameLayout != nil { + positionFlags.remove(.top) + } + } + selectionBackground.layer?.opacity = 0 + + selectionBackground.positionFlags = positionFlags + contents[i].addSubview(selectionBackground) + + let animation: CABasicAnimation = makeSpringAnimation("opacity") + + animation.fromValue = selectionBackground.layer?.presentation()?.opacity ?? 0 + animation.toValue = 1.0 + animation.autoreverses = true + animation.isRemovedOnCompletion = true + animation.fillMode = .forwards + + animation.delegate = CALayerAnimationDelegate(completion: { [weak self] completed in + if completed { + self?.selectionBackground.removeFromSuperview() + } + }) + animation.isAdditive = false + + selectionBackground.layer?.add(animation, forKey: "opacity") + + break + } + } + } else { + super.focusAnimation(innerId) + } + } + + + override func onShowContextMenu() { + guard let window = window as? Window else {return} + guard let item = item as? ChatGroupedItem else {return} + + let point = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + var selected: Bool = false + + for i in 0 ..< item.layout.count { + if NSPointInRect(point, item.layout.frame(at: i)) { + selectionBackground.removeFromSuperview() + selectionBackground.layer?.opacity = 1.0 + selectionBackground.setFrameSize(item.layout.frame(at: i).size) + + var positionFlags: LayoutPositionFlags = item.isBubbled ? item.positionFlags ?? item.layout.position(at: i) : [] + + if item.hasBubble { + if item.captionLayout != nil { + positionFlags.remove(.bottom) + } + if item.authorText != nil || item.replyModel != nil || item.forwardNameLayout != nil { + positionFlags.remove(.top) + } + } + + selectionBackground.positionFlags = positionFlags + contents[i].addSubview(selectionBackground) + selected = true + break + } + } + + if !selected { + super.onShowContextMenu() + } + } + + override func onCloseContextMenu() { + super.onCloseContextMenu() + selectionBackground.removeFromSuperview() + } + + override func canMultiselectTextIn(_ location: NSPoint) -> Bool { + let point = contentView.convert(location, from: nil) + for content in contents { + if NSPointInRect(point, content.frame) { + return false + } + } + return true + } + + override var contentFrame: NSRect { + var rect = super.contentFrame + + guard let item = item as? ChatGroupedItem else { return rect } + + if item.isBubbled, item.isBubbleFullFilled { + rect.origin.x -= item.bubbleContentInset + if item.hasBubble { + rect.origin.x += item.mediaBubbleCornerInset + } + } + + return rect + } + + override func layout() { + super.layout() + guard let item = item as? ChatGroupedItem else {return} + + assert(contents.count == item.layout.count) + + for i in 0 ..< item.layout.count { + contents[i].setFrameOrigin(item.layout.frame(at: i).origin) + } + + for content in contents { + let subviews = content.subviews + for subview in subviews { + if subview is SelectingControl { + subview.setFrameOrigin(content.frame.width - subview.frame.width - 5, 5) + break + } + } + } + + } + +} diff --git a/Telegram-Mac/ChatHeaderController.swift b/Telegram-Mac/ChatHeaderController.swift index ba0d671351..fa08dfe722 100644 --- a/Telegram-Mac/ChatHeaderController.swift +++ b/Telegram-Mac/ChatHeaderController.swift @@ -8,18 +8,21 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox enum ChatHeaderState : Identifiable, Equatable { case none - case search(ChatSearchInteractions) - case addContact + case search(ChatSearchInteractions, Peer?, String?) + case addContact(block: Bool, autoArchived: Bool) + case shareInfo case pinned(MessageId) - case report + case report(autoArchived: Bool) + case promo(PromoChatListItem.Kind) var stableId:Int { switch self { case .none: @@ -32,6 +35,10 @@ enum ChatHeaderState : Identifiable, Equatable { return 3 case .pinned: return 4 + case .promo: + return 5 + case .shareInfo: + return 6 } } @@ -45,8 +52,12 @@ enum ChatHeaderState : Identifiable, Equatable { return 44 case .addContact: return 44 + case .shareInfo: + return 44 case .pinned: return 44 + case .promo: + return 44 } } @@ -58,6 +69,12 @@ enum ChatHeaderState : Identifiable, Equatable { } else { return false } + case let .addContact(block, autoArchive): + if case .addContact(block, autoArchive) = rhs { + return true + } else { + return false + } default: return lhs.stableId == rhs.stableId } @@ -65,8 +82,6 @@ enum ChatHeaderState : Identifiable, Equatable { } - - class ChatHeaderController { @@ -102,9 +117,12 @@ class ChatHeaderController { if let newView = currentView { view.addSubview(newView) + (newView as? ChatSearchHeader)?.applySearchResponder() newView.layer?.removeAllAnimations() if animated { - newView.layer?.animatePosition(from: NSMakePoint(0,-state.height), to: NSZeroPoint, duration: 0.2) + newView.layer?.animatePosition(from: NSMakePoint(0,-state.height), to: NSZeroPoint, duration: 0.2, completion: { [weak newView] _ in + + }) } } } @@ -113,14 +131,18 @@ class ChatHeaderController { private func viewIfNecessary(_ size:NSSize) -> View? { let view:View? switch _headerState { - case .addContact: - view = AddContactView(chatInteraction) + case let .addContact(block, autoArchived): + view = AddContactView(chatInteraction, canBlock: block, autoArchived: autoArchived) + case .shareInfo: + view = ShareInfoView(chatInteraction) case let .pinned(messageId): view = ChatPinnedView(messageId, chatInteraction: chatInteraction) - case let .search(interactions): - view = ChatSearchHeader(interactions, chatInteraction: chatInteraction) - case .report: - view = ChatReportView(chatInteraction) + case let .search(interactions, initialPeer, initialString): + view = ChatSearchHeader(interactions, chatInteraction: chatInteraction, initialPeer: initialPeer, initialString: initialString) + case let .report(autoArchived): + view = ChatReportView(chatInteraction, autoArchived: autoArchived) + case let .promo(kind): + view = ChatSponsoredView(chatInteraction: chatInteraction, kind: kind) case .none: view = nil @@ -140,7 +162,145 @@ struct ChatSearchInteractions { let results:(String)->Void let calendarAction:(Date)->Void let cancel:()->Void - let searchRequest:(String, PeerId?) -> Signal<[Message],Void> + let searchRequest:(String, PeerId?, SearchMessagesState?) -> Signal<([Message], SearchMessagesState?), NoError> +} + +private class ChatSponsoredModel: ChatAccessoryModel { + + + init(title: String, text: String) { + super.init() + update(title: title, text: text) + } + + func update(title: String, text: String) { + //L10n.chatProxySponsoredCapTitle + self.headerAttr = .initialize(string: title, color: theme.colors.link, font: .medium(.text)) + self.messageAttr = .initialize(string: text, color: theme.colors.text, font: .normal(.text)) + nodeReady.set(.single(true)) + self.setNeedDisplay() + } +} + +private extension PromoChatListItem.Kind { + var title: String { + switch self { + case .proxy: + return L10n.chatProxySponsoredCapTitle + case .psa: + return L10n.psaChatTitle + } + } + var text: String { + switch self { + case .proxy: + return L10n.chatProxySponsoredCapDesc + case let .psa(type, _): + return localizedPsa("psa.chat.text", type: type) + } + } + var learnMore: String? { + switch self { + case .proxy: + return nil + case let .psa(type, _): + let localized = localizedPsa("psa.chat.alert.learnmore", type: type) + return localized != localized ? localized : nil + } + } +} + +private final class ChatSponsoredView : Control { + private let chatInteraction:ChatInteraction + private let container:ChatAccessoryView = ChatAccessoryView() + private let dismiss:ImageButton = ImageButton() + private let node: ChatSponsoredModel + private let kind: PromoChatListItem.Kind + init(chatInteraction:ChatInteraction, kind: PromoChatListItem.Kind) { + self.chatInteraction = chatInteraction + + self.kind = kind + + node = ChatSponsoredModel(title: kind.title, text: kind.text) + super.init() + + dismiss.disableActions() + self.dismiss.set(image: theme.icons.dismissPinned, for: .Normal) + _ = self.dismiss.sizeToFit() + + self.set(handler: { _ in + + switch kind { + case .proxy: + confirm(for: chatInteraction.context.window, header: L10n.chatProxySponsoredAlertHeader, information: L10n.chatProxySponsoredAlertText, cancelTitle: "", thridTitle: L10n.chatProxySponsoredAlertSettings, successHandler: { result in + switch result { + case .thrid: + chatInteraction.openProxySettings() + default: + break + } + }) + case .psa: + if let learnMore = kind.learnMore { + confirm(for: chatInteraction.context.window, header: kind.title, information: kind.text, cancelTitle: "", thridTitle: learnMore, successHandler: { result in + switch result { + case .thrid: + execute(inapp: .external(link: learnMore, false)) + default: + break + } + }) + } + + } + + + }, for: .Click) + + dismiss.set(handler: { _ in + FastSettings.removePromoTitle(for: chatInteraction.peerId) + chatInteraction.update({$0.withoutInitialAction()}) + }, for: .SingleClick) + + node.view = container + + addSubview(dismiss) + container.userInteractionEnabled = false + self.style = ControlStyle(backgroundColor: theme.colors.background) + addSubview(container) + updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + self.backgroundColor = theme.colors.background + self.dismiss.set(image: theme.icons.dismissPinned, for: .Normal) + container.backgroundColor = theme.colors.background + node.update(title: self.kind.title, text: self.kind.text) + } + + override func layout() { + node.update(title: self.kind.title, text: self.kind.text) + node.measureSize(frame.width - 70) + container.setFrameSize(frame.width - 70, node.size.height) + container.centerY(x: 20) + dismiss.centerY(x: frame.width - 20 - dismiss.frame.width) + node.setNeedDisplay() + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(0, layer.frame.height - .borderSize, layer.frame.width, .borderSize)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } } class ChatPinnedView : Control { @@ -151,15 +311,16 @@ class ChatPinnedView : Control { private let dismiss:ImageButton = ImageButton() private let loadMessageDisposable = MetaDisposable() init(_ messageId:MessageId, chatInteraction:ChatInteraction) { - node = ReplyModel(replyMessageId: messageId, account: chatInteraction.account, isPinned: true) + node = ReplyModel(replyMessageId: messageId, account: chatInteraction.context.account, replyMessage: chatInteraction.presentation.cachedPinnedMessage, isPinned: true) self.chatInteraction = chatInteraction super.init() + dismiss.disableActions() self.dismiss.set(image: theme.icons.dismissPinned, for: .Normal) - self.dismiss.sizeToFit() + _ = self.dismiss.sizeToFit() self.set(handler: { [weak self] _ in - self?.chatInteraction.focusMessageId(nil, messageId, .center(id: 0, animated: true, focus: true, inset: 0)) + self?.chatInteraction.focusMessageId(nil, messageId, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) }, for: .Click) dismiss.set(handler: { [weak self] _ in @@ -175,31 +336,35 @@ class ChatPinnedView : Control { self?.needsLayout = true if !result, let chatInteraction = self?.chatInteraction { - _ = requestUpdatePinnedMessage(account: chatInteraction.account, peerId: chatInteraction.peerId, update: .clear).start() + _ = requestUpdatePinnedMessage(account: chatInteraction.context.account, peerId: chatInteraction.peerId, update: .clear).start() } })) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) node.update() self.backgroundColor = theme.colors.background self.dismiss.set(image: theme.icons.dismissPinned, for: .Normal) container.backgroundColor = theme.colors.background } + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + override func layout() { node.measureSize(frame.width - 70) container.setFrameSize(frame.width - 70, node.size.height) container.centerY(x: 20) - dismiss.centerY(x: frame.width - 21 - dismiss.frame.width) + dismiss.centerY(x: frame.width - 20 - dismiss.frame.width) node.setNeedDisplay() } override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) ctx.setFillColor(theme.colors.border.cgColor) ctx.fill(NSMakeRect(0, layer.frame.height - .borderSize, layer.frame.width, .borderSize)) } @@ -221,38 +386,59 @@ class ChatPinnedView : Control { class ChatReportView : Control { private let chatInteraction:ChatInteraction private let report:TitleButton = TitleButton() + private let unarchiveButton = TitleButton() private let dismiss:ImageButton = ImageButton() - init(_ chatInteraction:ChatInteraction) { + private let buttonsContainer = View() + + init(_ chatInteraction:ChatInteraction, autoArchived: Bool) { self.chatInteraction = chatInteraction super.init() + dismiss.disableActions() + self.style = ControlStyle(backgroundColor: theme.colors.background) - report.set(text: tr(.chatHeaderReportSpam), for: .Normal) - report.sizeToFit() + report.set(text: L10n.chatHeaderReportSpam, for: .Normal) + _ = report.sizeToFit() self.dismiss.set(image: theme.icons.dismissPinned, for: .Normal) - self.dismiss.sizeToFit() + _ = self.dismiss.sizeToFit() report.set(handler: { _ in - chatInteraction.reportSpamAndClose() + chatInteraction.blockContact() }, for: .SingleClick) dismiss.set(handler: { _ in - chatInteraction.dismissPeerReport() + chatInteraction.dismissPeerStatusOptions() }, for: .SingleClick) + unarchiveButton.set(handler: { _ in + chatInteraction.unarchive() + }, for: .SingleClick) + + buttonsContainer.addSubview(report) + + if autoArchived { + buttonsContainer.addSubview(unarchiveButton) + } + addSubview(buttonsContainer) + addSubview(dismiss) - addSubview(report) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) dismiss.set(image: theme.icons.dismissPinned, for: .Normal) - report.set(text: tr(.chatHeaderReportSpam), for: .Normal) - report.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.blueUI, backgroundColor: theme.colors.background, highlightColor: theme.colors.blueSelect) - report.sizeToFit() + report.set(text: tr(L10n.chatHeaderReportSpam), for: .Normal) + report.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.redUI, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentSelect) + _ = report.sizeToFit() + + unarchiveButton.set(text: L10n.peerInfoUnarchive, for: .Normal) + + unarchiveButton.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentSelect) + self.backgroundColor = theme.colors.background needsLayout = true } @@ -266,6 +452,25 @@ class ChatReportView : Control { override func layout() { report.center() dismiss.centerY(x: frame.width - dismiss.frame.width - 20) + + + buttonsContainer.frame = NSMakeRect(0, 0, frame.width - (frame.width - dismiss.frame.minX), frame.height - .borderSize) + + + var buttons:[Control] = [] + if report.superview != nil { + buttons.append(report) + } + if unarchiveButton.superview != nil { + buttons.append(unarchiveButton) + } + + let buttonWidth: CGFloat = floor(buttonsContainer.frame.width / CGFloat(buttons.count)) + var x: CGFloat = 0 + for button in buttons { + button.frame = NSMakeRect(x, 0, buttonWidth, buttonsContainer.frame.height) + x += buttonWidth + } } required init?(coder: NSCoder) { @@ -277,39 +482,148 @@ class ChatReportView : Control { } } -class AddContactView : Control { +class ShareInfoView : Control { private let chatInteraction:ChatInteraction - private let add:TitleButton = TitleButton() + private let share:TitleButton = TitleButton() private let dismiss:ImageButton = ImageButton() - init(_ chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction super.init() self.style = ControlStyle(backgroundColor: theme.colors.background) + dismiss.disableActions() + + dismiss.set(image: theme.icons.dismissPinned, for: .Normal) + _ = dismiss.sizeToFit() + + share.set(handler: { _ in + chatInteraction.shareSelfContact(nil) + chatInteraction.dismissPeerStatusOptions() + }, for: .SingleClick) + + dismiss.set(handler: { _ in + chatInteraction.dismissPeerStatusOptions() + }, for: .SingleClick) + + + + addSubview(share) + addSubview(dismiss) + updateLocalizationAndTheme(theme: theme) + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + + if window == nil { + var bp:Int = 0 + bp += 1 + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + dismiss.set(image: theme.icons.dismissPinned, for: .Normal) + share.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentSelect) - add.set(text: tr(.peerInfoAddContact), for: .Normal) - add.sizeToFit() + share.set(text: L10n.peerInfoShareMyInfo, for: .Normal) + + self.backgroundColor = theme.colors.background + needsLayout = true + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(0, layer.frame.height - .borderSize, layer.frame.width, .borderSize)) + } + + override func layout() { + super.layout() + dismiss.centerY(x: frame.width - dismiss.frame.width - 20) + share.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +class AddContactView : Control { + private let chatInteraction:ChatInteraction + private let add:TitleButton = TitleButton() + private let dismiss:ImageButton = ImageButton() + private let blockButton: TitleButton = TitleButton() + private let unarchiveButton = TitleButton() + private let buttonsContainer = View() + init(_ chatInteraction:ChatInteraction, canBlock: Bool, autoArchived: Bool) { + self.chatInteraction = chatInteraction + super.init() + self.style = ControlStyle(backgroundColor: theme.colors.background) + dismiss.disableActions() dismiss.set(image: theme.icons.dismissPinned, for: .Normal) - dismiss.sizeToFit() + _ = dismiss.sizeToFit() add.set(handler: { _ in chatInteraction.addContact() }, for: .SingleClick) dismiss.set(handler: { _ in - + chatInteraction.dismissPeerStatusOptions() }, for: .SingleClick) - addSubview(add) - updateLocalizationAndTheme() + blockButton.set(handler: { _ in + chatInteraction.blockContact() + }, for: .SingleClick) + + unarchiveButton.set(handler: { _ in + chatInteraction.unarchive() + }, for: .SingleClick) + + + + if canBlock { + buttonsContainer.addSubview(blockButton) + } + if autoArchived { + buttonsContainer.addSubview(unarchiveButton) + } + + if !autoArchived && canBlock { + buttonsContainer.addSubview(add) + } else if !autoArchived && !canBlock { + buttonsContainer.addSubview(add) + } + + addSubview(buttonsContainer) + addSubview(dismiss) + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) dismiss.set(image: theme.icons.dismissPinned, for: .Normal) - add.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.blueUI, backgroundColor: theme.colors.background, highlightColor: theme.colors.blueSelect) + add.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentSelect) + blockButton.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.redUI, backgroundColor: theme.colors.background, highlightColor: theme.colors.redUI) + + if blockButton.superview == nil, let peer = chatInteraction.peer { + add.set(text: L10n.peerInfoAddUserToContact(peer.compactDisplayTitle), for: .Normal) + } else { + add.set(text: L10n.peerInfoAddContact, for: .Normal) + } + blockButton.set(text: L10n.peerInfoBlockUser, for: .Normal) + unarchiveButton.set(text: L10n.peerInfoUnarchive, for: .Normal) + + unarchiveButton.style = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentSelect) + self.backgroundColor = theme.colors.background + needsLayout = true } override func draw(_ layer: CALayer, in ctx: CGContext) { @@ -319,7 +633,30 @@ class AddContactView : Control { } override func layout() { - add.center() + dismiss.centerY(x: frame.width - dismiss.frame.width - 20) + + var buttons:[Control] = [] + + + if add.superview != nil { + buttons.append(add) + } + if blockButton.superview != nil { + buttons.append(blockButton) + } + if unarchiveButton.superview != nil { + buttons.append(unarchiveButton) + } + + buttonsContainer.frame = NSMakeRect(0, 0, frame.width - (frame.width - dismiss.frame.minX), frame.height - .borderSize) + + + let buttonWidth: CGFloat = floor(buttonsContainer.frame.width / CGFloat(buttons.count)) + var x: CGFloat = 0 + for button in buttons { + button.frame = NSMakeRect(x, 0, buttonWidth, buttonsContainer.frame.height) + x += buttonWidth + } } required init?(coder: NSCoder) { @@ -335,24 +672,49 @@ private final class CSearchContextState : Equatable { let inputQueryResult: ChatPresentationInputQueryResult? let tokenState: TokenSearchState let peerId:PeerId? - init(inputQueryResult: ChatPresentationInputQueryResult? = nil, tokenState: TokenSearchState = .none, peerId: PeerId? = nil) { + let messages: ([Message], SearchMessagesState?) + let selectedIndex: Int + let searchState: SearchState + + init(inputQueryResult: ChatPresentationInputQueryResult? = nil, messages: ([Message], SearchMessagesState?) = ([], nil), selectedIndex: Int = -1, searchState: SearchState = SearchState(state: .None, request: ""), tokenState: TokenSearchState = .none, peerId: PeerId? = nil) { self.inputQueryResult = inputQueryResult self.tokenState = tokenState self.peerId = peerId + self.messages = messages + self.selectedIndex = selectedIndex + self.searchState = searchState } func updatedInputQueryResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> CSearchContextState { - return CSearchContextState(inputQueryResult: f(self.inputQueryResult), tokenState: self.tokenState, peerId: self.peerId) + return CSearchContextState(inputQueryResult: f(self.inputQueryResult), messages: self.messages, selectedIndex: self.selectedIndex, searchState: self.searchState, tokenState: self.tokenState, peerId: self.peerId) } func updatedTokenState(_ token: TokenSearchState) -> CSearchContextState { - return CSearchContextState(inputQueryResult: self.inputQueryResult, tokenState: token, peerId: self.peerId) + return CSearchContextState(inputQueryResult: self.inputQueryResult, messages: self.messages, selectedIndex: self.selectedIndex, searchState: self.searchState, tokenState: token, peerId: self.peerId) } func updatedPeerId(_ peerId: PeerId?) -> CSearchContextState { - return CSearchContextState(inputQueryResult: self.inputQueryResult, tokenState: self.tokenState, peerId: peerId) + return CSearchContextState(inputQueryResult: self.inputQueryResult, messages: self.messages, selectedIndex: self.selectedIndex, searchState: self.searchState, tokenState: self.tokenState, peerId: peerId) + } + func updatedMessages(_ messages: ([Message], SearchMessagesState?)) -> CSearchContextState { + return CSearchContextState(inputQueryResult: self.inputQueryResult, messages: messages, selectedIndex: self.selectedIndex, searchState: self.searchState, tokenState: self.tokenState, peerId: self.peerId) + } + func updatedSelectedIndex(_ selectedIndex: Int) -> CSearchContextState { + return CSearchContextState(inputQueryResult: self.inputQueryResult, messages: self.messages, selectedIndex: selectedIndex, searchState: self.searchState, tokenState: self.tokenState, peerId: self.peerId) + } + func updatedSearchState(_ searchState: SearchState) -> CSearchContextState { + return CSearchContextState(inputQueryResult: self.inputQueryResult, messages: self.messages, selectedIndex: self.selectedIndex, searchState: searchState, tokenState: self.tokenState, peerId: self.peerId) } } private func ==(lhs: CSearchContextState, rhs: CSearchContextState) -> Bool { - return lhs.inputQueryResult == rhs.inputQueryResult && lhs.tokenState == rhs.tokenState + if lhs.messages.0.count != rhs.messages.0.count { + return false + } else { + for i in 0 ..< lhs.messages.0.count { + if !isEqualMessages(lhs.messages.0[i], rhs.messages.0[i]) { + return false + } + } + } + return lhs.inputQueryResult == rhs.inputQueryResult && lhs.tokenState == rhs.tokenState && lhs.selectedIndex == rhs.selectedIndex && lhs.searchState == rhs.searchState && lhs.messages.1 == rhs.messages.1 } private final class CSearchInteraction : InterfaceObserver { @@ -365,39 +727,95 @@ private final class CSearchInteraction : InterfaceObserver { notifyObservers(value: state, oldValue:oldValue, animated: animated) } } + + var currentMessage: Message? { + if state.messages.0.isEmpty { + return nil + } else if state.messages.0.count <= state.selectedIndex || state.selectedIndex < 0 { + return nil + } + return state.messages.0[state.selectedIndex] + } +} + +struct SearchStateQuery : Equatable { + let query: String? + let state: SearchMessagesState? + init(_ query: String?, _ state: SearchMessagesState?) { + self.query = query + self.state = state + } +} + +struct SearchMessagesResultState : Equatable { + static func == (lhs: SearchMessagesResultState, rhs: SearchMessagesResultState) -> Bool { + if lhs.query != rhs.query { + return false + } + if lhs.messages.count != rhs.messages.count { + return false + } else { + for i in 0 ..< lhs.messages.count { + if !isEqualMessages(lhs.messages[i], rhs.messages[i]) { + return false + } + } + } + return true + } + + let query: String + let messages: [Message] + init(_ query: String, _ messages: [Message]) { + self.query = query + self.messages = messages + } + + func containsMessage(_ message: Message) -> Bool { + return self.messages.contains(where: { $0.id == message.id }) + } } class ChatSearchHeader : View, Notifable { private let searchView:ChatSearchView = ChatSearchView(frame: NSZeroRect) private let cancel:ImageButton = ImageButton() - private let prev:ImageButton = ImageButton() - private let next:ImageButton = ImageButton() private let from:ImageButton = ImageButton() private let calendar:ImageButton = ImageButton() + private let prev:ImageButton = ImageButton() + private let next:ImageButton = ImageButton() + + private let separator:View = View() private let interactions:ChatSearchInteractions private let chatInteraction: ChatInteraction - private let query:Promise = Promise() + private let query:ValuePromise = ValuePromise() + private let disposable:MetaDisposable = MetaDisposable() private var contextQueryState: (ChatPresentationInputQuery?, Disposable)? private let inputContextHelper: InputContextHelper private let inputInteraction: CSearchInteraction = CSearchInteraction() - - private var messages:[Message] = [] - private var currentIndex:Int = 0 { - didSet { - searchView.countValue = (current: currentIndex + 1, total: messages.count) - } - } - init(_ interactions:ChatSearchInteractions, chatInteraction: ChatInteraction) { + private let parentInteractions: ChatInteraction + private let loadingDisposable = MetaDisposable() + + private let calendarController: CalendarController + init(_ interactions:ChatSearchInteractions, chatInteraction: ChatInteraction, initialPeer: Peer?, initialString: String?) { self.interactions = interactions - self.chatInteraction = ChatInteraction(peerId: chatInteraction.peerId, account: chatInteraction.account) + self.parentInteractions = chatInteraction + self.calendarController = CalendarController(NSMakeRect(0, 0, 250, 250), chatInteraction.context.window, selectHandler: interactions.calendarAction) + self.chatInteraction = ChatInteraction(chatLocation: chatInteraction.chatLocation, context: chatInteraction.context) self.chatInteraction.update({$0.updatedPeer({_ in chatInteraction.presentation.peer})}) - self.inputContextHelper = InputContextHelper(account: chatInteraction.account, chatInteraction: self.chatInteraction) + self.inputContextHelper = InputContextHelper(chatInteraction: self.chatInteraction, highlightInsteadOfSelect: true) + + if let initialString = initialString { + searchView.setString(initialString) + self.query.set(SearchStateQuery(initialString, nil)) + } + + super.init() self.chatInteraction.movePeerToInput = { [weak self] peer in @@ -405,45 +823,133 @@ class ChatSearchHeader : View, Notifable { self?.inputInteraction.update({$0.updatedPeerId(peer.id)}) } + + self.chatInteraction.focusMessageId = { [weak self] fromId, messageId, state in + self?.parentInteractions.focusMessageId(fromId, messageId, state) + self?.inputInteraction.update({$0.updatedSelectedIndex($0.messages.0.firstIndex(where: {$0.id == messageId}) ?? -1)}) + _ = self?.window?.makeFirstResponder(nil) + } + + + initialize() + + + + parentInteractions.loadingMessage.set(.single(false)) + inputInteraction.add(observer: self) + self.loadingDisposable.set((parentInteractions.loadingMessage.get() |> deliverOnMainQueue).start(next: { [weak self] loading in + self?.searchView.isLoading = loading + })) + if let initialPeer = initialPeer { + self.chatInteraction.movePeerToInput(initialPeer) + Queue.mainQueue().justDispatch { + self.searchView.change(state: .Focus, false) + } + } + + } + + func applySearchResponder() { + // _ = window?.makeFirstResponder(searchView.input) + searchView.layout() + if searchView.state == .Focus && window?.firstResponder != searchView.input { + _ = window?.makeFirstResponder(searchView.input) + } + searchView.change(state: .Focus, false) + } + + private var calendarAbility: Bool { + return true + } + + private var fromAbility: Bool { + if let peer = chatInteraction.presentation.peer { + return peer.isSupergroup || peer.isGroup + } else { + return false + } } func notify(with value: Any, oldValue: Any, animated: Bool) { - let account = chatInteraction.account + let context = chatInteraction.context if let value = value as? CSearchContextState, let oldValue = oldValue as? CSearchContextState, let view = superview { + + let stateValue = self.query + + prev.isEnabled = !value.messages.0.isEmpty && value.selectedIndex < value.messages.0.count - 1 + next.isEnabled = !value.messages.0.isEmpty && value.selectedIndex > 0 + next.set(image: next.isEnabled ? theme.icons.chatSearchDown : theme.icons.chatSearchDownDisabled, for: .Normal) + prev.set(image: prev.isEnabled ? theme.icons.chatSearchUp : theme.icons.chatSearchUpDisabled, for: .Normal) + + + if let peer = chatInteraction.presentation.peer { if value.inputQueryResult != oldValue.inputQueryResult { - inputContextHelper.context(with: value.inputQueryResult, for: view, relativeView: self, position: .below, animated: animated) + inputContextHelper.context(with: value.inputQueryResult, for: view, relativeView: self, position: .below, selectIndex: value.selectedIndex != -1 ? value.selectedIndex : nil, animated: animated) } switch value.tokenState { case .none: - messages = [] - currentIndex = -1 - from.isHidden = false - calendar.isHidden = false + from.isHidden = !fromAbility + calendar.isHidden = !calendarAbility needsLayout = true searchView.change(size: NSMakeSize(searchWidth, searchView.frame.height), animated: animated) - inputInteraction.update(animated: animated, { - $0.updatedInputQueryResult { previousResult in - return .mentions([]) - }.updatedPeerId(nil) - }) + + if peer.isSupergroup || peer.isGroup { + if let (updatedContextQueryState, updatedContextQuerySignal) = chatContextQueryForSearchMention(peer: peer, .mention(query: value.searchState.request, includeRecent: false), currentQuery: self.contextQueryState?.0, context: context) { + self.contextQueryState?.1.dispose() + self.contextQueryState = (updatedContextQueryState, (updatedContextQuerySignal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + strongSelf.inputInteraction.update(animated: animated, { state in + return state.updatedInputQueryResult { previousResult in + let messages = state.searchState.responder ? state.messages : ([], nil) + var suggestedPeers:[Peer] = [] + let inputQueryResult = result(previousResult) + if let inputQueryResult = inputQueryResult, state.searchState.responder, !state.searchState.request.isEmpty, messages.1 != nil { + switch inputQueryResult { + case let .mentions(mentions): + suggestedPeers = mentions + default: + break + } + } + return .searchMessages((messages.0, messages.1, { searchMessagesState in + stateValue.set(SearchStateQuery(state.searchState.request, searchMessagesState)) + }), suggestedPeers, state.searchState.request) + } + }) + } + })) + } + } else { + inputInteraction.update(animated: animated, { state in + return state.updatedInputQueryResult { previousResult in + let result = state.searchState.responder ? state.messages : ([], nil) + return .searchMessages((result.0, result.1, { searchMessagesState in + stateValue.set(SearchStateQuery(state.searchState.request, searchMessagesState)) + }), [], state.searchState.request) + } + }) + } + + case let .from(query, complete): from.isHidden = true calendar.isHidden = true searchView.change(size: NSMakeSize(searchWidth, searchView.frame.height), animated: animated) needsLayout = true if complete { - inputInteraction.update(animated: animated, { - $0.updatedInputQueryResult { previousResult in - return .mentions([]) + inputInteraction.update(animated: animated, { state in + return state.updatedInputQueryResult { previousResult in + let result = state.searchState.responder ? state.messages : ([], nil) + return .searchMessages((result.0, result.1, { searchMessagesState in + stateValue.set(SearchStateQuery(state.searchState.request, searchMessagesState)) + }), [], state.searchState.request) } }) } else { - messages = [] - currentIndex = -1 - if let (updatedContextQueryState, updatedContextQuerySignal) = chatContextQueryForSearchMention(peer: peer, .mention(query: query, includeRecent: false), currentQuery: self.contextQueryState?.0, account: account) { + if let (updatedContextQueryState, updatedContextQuerySignal) = chatContextQueryForSearchMention(peer: peer, .mention(query: query, includeRecent: false), currentQuery: self.contextQueryState?.0, context: context) { self.contextQueryState?.1.dispose() var inScope = true var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? @@ -456,7 +962,7 @@ class ChatSearchHeader : View, Notifable { strongSelf.inputInteraction.update(animated: animated, { $0.updatedInputQueryResult { previousResult in return result(previousResult) - } + }.updatedMessages(([], nil)).updatedSelectedIndex(-1) }) } @@ -467,7 +973,7 @@ class ChatSearchHeader : View, Notifable { inputInteraction.update(animated: animated, { $0.updatedInputQueryResult { previousResult in return inScopeResult(previousResult) - } + }.updatedMessages(([], nil)).updatedSelectedIndex(-1) }) } } @@ -487,135 +993,168 @@ class ChatSearchHeader : View, Notifable { + + private func initialize() { - if let peer = chatInteraction.presentation.peer { - self.from.isHidden = !peer.isSupergroup && !peer.isGroup - } else { - self.from.isHidden = true - } + self.from.isHidden = !fromAbility _ = self.searchView.tokenPromise.get().start(next: { [weak self] state in self?.inputInteraction.update({$0.updatedTokenState(state)}) }) - self.searchView.searchInteractions = SearchInteractions({ [weak self] state in + self.searchView.searchInteractions = SearchInteractions({ [weak self] state, _ in if state.state == .None { - self?.searchView.isLoading = false + self?.parentInteractions.loadingMessage.set(.single(false)) + self?.parentInteractions.updateSearchRequest(SearchMessagesResultState(state.request, [])) + self?.inputInteraction.update({$0.updatedMessages(([], nil)).updatedSelectedIndex(-1).updatedSearchState(state)}) } }, { [weak self] state in - if let strongSelf = self { - strongSelf.messages = [] - strongSelf.currentIndex = -1 - strongSelf.updateSearchState() - switch strongSelf.searchView.tokenState { - case .none: - if state.request == tr(.chatSearchFrom), let peer = strongSelf.chatInteraction.presentation.peer, peer.isGroup || peer.isSupergroup { - strongSelf.query.set(.single("")) - strongSelf.searchView.initToken() - } else { - strongSelf.searchView.isLoading = true - strongSelf.query.set(.single(state.request)) - } - - case .from(_, let complete): - if complete { - strongSelf.searchView.isLoading = true - strongSelf.query.set(.single(state.request)) - } + guard let `self` = self else {return} + + self.inputInteraction.update({$0.updatedMessages(([], nil)).updatedSelectedIndex(-1).updatedSearchState(state)}) + + self.updateSearchState() + switch self.searchView.tokenState { + case .none: + if state.request == L10n.chatSearchFrom, let peer = self.chatInteraction.presentation.peer, peer.isGroup || peer.isSupergroup { + self.query.set(SearchStateQuery("", nil)) + self.parentInteractions.updateSearchRequest(SearchMessagesResultState("", [])) + self.searchView.initToken() + } else { + self.parentInteractions.updateSearchRequest(SearchMessagesResultState(state.request, [])) + self.parentInteractions.loadingMessage.set(.single(true)) + self.query.set(SearchStateQuery(state.request, nil)) + } + + case .from(_, let complete): + if complete { + self.parentInteractions.updateSearchRequest(SearchMessagesResultState(state.request, [])) + self.parentInteractions.loadingMessage.set(.single(true)) + self.query.set(SearchStateQuery(state.request, nil)) } - } + }, responderModified: { [weak self] state in + self?.inputInteraction.update({$0.updatedSearchState(state)}) }) - let apply = query.get() |> mapToSignal { [weak self] query -> Signal<[Message], Void> in - if let strongSelf = self, let query = query { - return .single(Void()) |> delay(0.3, queue: Queue.mainQueue()) |> mapToSignal { [weak strongSelf] () -> Signal<[Message], Void> in - if let strongSelf = strongSelf { - return strongSelf.interactions.searchRequest(query, strongSelf.inputInteraction.state.peerId) + let apply = query.get() |> mapToSignal { [weak self] state -> Signal<([Message], SearchMessagesState?, String), NoError> in + + guard let `self` = self else { return .single(([], nil, "")) } + if let query = state.query { + + let stateSignal: Signal + if state.state == nil { + stateSignal = .single(state.state) |> delay(0.3, queue: Queue.mainQueue()) + } else { + stateSignal = .single(state.state) + } + + return stateSignal |> mapToSignal { [weak self] state in + + guard let `self` = self else { return .single(([], nil, "")) } + + let emptyRequest: Bool + if case .from = self.inputInteraction.state.tokenState { + emptyRequest = true + } else { + emptyRequest = !query.isEmpty + } + if emptyRequest { + return self.interactions.searchRequest(query, self.inputInteraction.state.peerId, state) |> map { ($0.0, $0.1, query) } + } else { + return .single(([], nil, "")) } - return .single([]) } } else { - return .single([]) + return .single(([], nil, "")) } } |> deliverOnMainQueue self.disposable.set(apply.start(next: { [weak self] messages in - self?.messages = messages - self?.currentIndex = -1 - self?.prevAction() - self?.searchView.isLoading = false - - }, error: { [weak self] in - self?.messages = [] - self?.currentIndex = -1 - self?.prevAction() - self?.searchView.isLoading = false + guard let `self` = self else {return} + self.parentInteractions.updateSearchRequest(SearchMessagesResultState(messages.2, messages.0)) + self.inputInteraction.update({$0.updatedMessages((messages.0, messages.1)).updatedSelectedIndex(-1)}) + self.parentInteractions.loadingMessage.set(.single(false)) })) - + next.autohighlight = false prev.autohighlight = false - calendar.sizeToFit() + + + _ = calendar.sizeToFit() addSubview(next) addSubview(prev) + + addSubview(from) + + addSubview(calendar) - cancel.sizeToFit() + calendar.isHidden = !calendarAbility + + _ = cancel.sizeToFit() let interactions = self.interactions let searchView = self.searchView cancel.set(handler: { [weak self] _ in - self?.inputInteraction.update {$0.updatedTokenState(.none)} + self?.inputInteraction.update {$0.updatedTokenState(.none).updatedSelectedIndex(-1).updatedMessages(([], nil)).updatedSearchState(SearchState(state: .None, request: ""))} + self?.parentInteractions.updateSearchRequest(SearchMessagesResultState("", [])) interactions.cancel() }, for: .Click) next.set(handler: { [weak self] _ in self?.nextAction() - }, for: .Click) + }, for: .Click) prev.set(handler: { [weak self] _ in self?.prevAction() }, for: .Click) + + from.set(handler: { [weak self] _ in self?.searchView.initToken() }, for: .Click) + + calendar.set(handler: { [weak self] calendar in - if let strongSelf = self { - showPopover(for: calendar, with: CalendarController(NSMakeRect(0,0,250,250), selectHandler: strongSelf.interactions.calendarAction), edge: .maxY, inset: NSMakePoint(-160, -40)) - } + guard let `self` = self else {return} + showPopover(for: calendar, with: self.calendarController, edge: .maxY, inset: NSMakePoint(-160, -40)) }, for: .Click) addSubview(searchView) addSubview(cancel) addSubview(separator) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) backgroundColor = theme.colors.background + next.set(image: theme.icons.chatSearchDown, for: .Normal) - next.sizeToFit() + _ = next.sizeToFit() prev.set(image: theme.icons.chatSearchUp, for: .Normal) - prev.sizeToFit() + _ = prev.sizeToFit() + calendar.set(image: theme.icons.chatSearchCalendar, for: .Normal) - calendar.sizeToFit() + _ = calendar.sizeToFit() cancel.set(image: theme.icons.chatSearchCancel, for: .Normal) - cancel.sizeToFit() + _ = cancel.sizeToFit() from.set(image: theme.icons.chatSearchFrom, for: .Normal) - from.sizeToFit() + _ = from.sizeToFit() separator.backgroundColor = theme.colors.border self.backgroundColor = theme.colors.background @@ -624,31 +1163,24 @@ class ChatSearchHeader : View, Notifable { } func updateSearchState() { - prev.isEnabled = !messages.isEmpty && currentIndex < messages.count - 1 - next.isEnabled = !messages.isEmpty && currentIndex > 0 - next.set(image: next.isEnabled ? theme.icons.chatSearchDown : theme.icons.chatSearchDownDisabled, for: .Normal) - prev.set(image: prev.isEnabled ? theme.icons.chatSearchUp : theme.icons.chatSearchUpDisabled, for: .Normal) + } func prevAction() { - if !messages.isEmpty { - currentIndex += 1 - currentIndex = min(messages.count - 1, currentIndex) - perform() - } + inputInteraction.update({$0.updatedSelectedIndex(min($0.selectedIndex + 1, $0.messages.0.count - 1))}) + perform() } func perform() { - interactions.jump(messages[min(max(0,currentIndex), messages.count - 1)]) - updateSearchState() + _ = window?.makeFirstResponder(nil) + if let currentMessage = inputInteraction.currentMessage { + interactions.jump(currentMessage) + } } func nextAction() { - if !messages.isEmpty { - currentIndex -= 1 - currentIndex = max(0, currentIndex) - perform() - } + inputInteraction.update({$0.updatedSelectedIndex(max($0.selectedIndex - 1, 0))}) + perform() } private var searchWidth: CGFloat { @@ -658,13 +1190,16 @@ class ChatSearchHeader : View, Notifable { override func layout() { super.layout() + prev.centerY(x:10) next.centerY(x:prev.frame.maxX) + cancel.centerY(x:frame.width - cancel.frame.width - 20) searchView.setFrameSize(NSMakeSize(searchWidth, 30)) - searchView.centerY(x:80) + inputContextHelper.controller.view.setFrameSize(frame.width, inputContextHelper.controller.frame.height) + searchView.centerY(x: 80) separator.frame = NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize) from.centerY(x: searchView.frame.maxX + 20) @@ -675,35 +1210,28 @@ class ChatSearchHeader : View, Notifable { override func viewDidMoveToWindow() { if let _ = window { layout() - self.searchView.change(state: .Focus, false) + //self.searchView.change(state: .Focus, false) } } override func viewWillMove(toWindow newWindow: NSWindow?) { - if let window = newWindow as? Window { - window.set(handler: { [weak self] () -> KeyHandlerResult in - self?.prevAction() - return .invoked - }, with: self, for: .UpArrow, priority: .medium) - - window.set(handler: { [weak self] () -> KeyHandlerResult in - self?.nextAction() - return .invoked - }, with: self, for: .DownArrow, priority: .medium) - } else { - if let window = window as? Window { - window.remove(object: self, for: .UpArrow) - window.remove(object: self, for: .DownArrow) - self.searchView.change(state: .None, false) - } - + if newWindow == nil { + // self.searchView.change(state: .None, false) } } deinit { + inputInteraction.update(animated: false, { state in + return state.updatedInputQueryResult( { _ in return nil } ) + }) + parentInteractions.updateSearchRequest(SearchMessagesResultState("", [])) disposable.dispose() inputInteraction.remove(observer: self) + loadingDisposable.set(nil) + if let window = window as? Window { + window.removeAllHandlers(for: self) + } } required init?(coder: NSCoder) { @@ -713,7 +1241,9 @@ class ChatSearchHeader : View, Notifable { init(frame frameRect: NSRect, interactions:ChatSearchInteractions, chatInteraction: ChatInteraction) { self.interactions = interactions self.chatInteraction = chatInteraction - self.inputContextHelper = InputContextHelper(account: chatInteraction.account, chatInteraction: chatInteraction) + self.parentInteractions = chatInteraction + self.inputContextHelper = InputContextHelper(chatInteraction: chatInteraction, highlightInsteadOfSelect: true) + self.calendarController = CalendarController(NSMakeRect(0,0,250,250), chatInteraction.context.window, selectHandler: interactions.calendarAction) super.init(frame: frameRect) initialize() } diff --git a/Telegram-Mac/ChatHistoryEntry.swift b/Telegram-Mac/ChatHistoryEntry.swift index d8ec593ed6..c30634ae1e 100644 --- a/Telegram-Mac/ChatHistoryEntry.swift +++ b/Telegram-Mac/ChatHistoryEntry.swift @@ -7,27 +7,29 @@ // import Cocoa - -import PostboxMac -import TelegramCoreMac +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import MtProtoKit enum ChatHistoryEntryId : Hashable { - case hole(MessageHistoryHole) case message(Message) + case groupedPhotos(groupInfo: MessageGroupInfo) case unread case date(MessageIndex) case undefined case maybeId(AnyHashable) var hashValue: Int { switch self { - case let .hole(index): - return index.stableId.hashValue case .message(let message): return message.stableId.hashValue case .unread: return 2 << 1 case .date(let index): return index.hashValue + case .groupedPhotos(let info): + return Int(info.stableId) case .undefined: return 3 << 1 case .maybeId(let id): @@ -37,18 +39,18 @@ enum ChatHistoryEntryId : Hashable { static func ==(lhs:ChatHistoryEntryId, rhs: ChatHistoryEntryId) -> Bool { switch lhs { - case let .hole(index): - if case .hole(index) = rhs { - return true - } else { - return false - } case .message(let lhsMessage): if case .message(let rhsMessage) = rhs { return lhsMessage.stableId == rhsMessage.stableId } else { return false } + case let .groupedPhotos(groupingKey): + if case .groupedPhotos(groupingKey) = rhs { + return true + } else { + return false + } case .unread: if case .unread = rhs { return true @@ -78,54 +80,131 @@ enum ChatHistoryEntryId : Hashable { var stableIndex: UInt64 { switch self { - case .hole: - return UInt64(0) << 40 case .message: return UInt64(1) << 40 + case .groupedPhotos: + return UInt64(2) << 40 case .unread: - return UInt64(2) << 40 + return UInt64(3) << 40 case .date: - return UInt64(3) << 40 - case .undefined: return UInt64(4) << 40 - case .maybeId: + case .undefined: return UInt64(5) << 40 + case .maybeId: + return UInt64(6) << 40 } } } +struct ChatPollStateData : Equatable { + let identifiers: [Data] + let isLoading: Bool + init(identifiers: [Data] = [], isLoading: Bool = false) { + self.identifiers = identifiers + self.isLoading = isLoading + } +} + +struct MessageEntryAdditionalData : Equatable { + let pollStateData: ChatPollStateData + let highlightFoundText: HighlightFoundText? + init(pollStateData: ChatPollStateData = ChatPollStateData(), highlightFoundText: HighlightFoundText? = nil) { + self.pollStateData = pollStateData + self.highlightFoundText = highlightFoundText + } +} + +struct HighlightFoundText : Equatable { + let query: String + let isMessage: Bool + init(query: String, isMessage: Bool) { + self.query = query + self.isMessage = isMessage + } +} + +final class ChatHistoryEntryData : Equatable { + let location: MessageHistoryEntryLocation? + let additionData: MessageEntryAdditionalData + let autoPlay: AutoplayMediaPreferences? + init(_ location: MessageHistoryEntryLocation?, _ additionData: MessageEntryAdditionalData, _ autoPlay: AutoplayMediaPreferences?) { + self.location = location + self.additionData = additionData + self.autoPlay = autoPlay + } + static func ==(lhs: ChatHistoryEntryData, rhs: ChatHistoryEntryData) -> Bool { + return lhs.location == rhs.location && lhs.additionData == rhs.additionData && lhs.autoPlay == rhs.autoPlay + } +} + enum ChatHistoryEntry: Identifiable, Comparable { - case HoleEntry(MessageHistoryHole) - case MessageEntry(Message, Bool, ChatItemType, ForwardItemType?, MessageHistoryEntryLocation?) - case UnreadEntry(MessageIndex) - case DateEntry(MessageIndex) + case MessageEntry(Message, MessageIndex, Bool, ChatItemRenderType, ChatItemType, ForwardItemType?, ChatHistoryEntryData) + case groupedPhotos([ChatHistoryEntry], groupInfo: MessageGroupInfo) + case UnreadEntry(MessageIndex, ChatItemRenderType) + case DateEntry(MessageIndex, ChatItemRenderType) case bottom var message:Message? { switch self { - case let .MessageEntry(message,_,_,_,_): + case let .MessageEntry(message,_, _,_,_,_,_): return message default: return nil } } + + var autoplayMedia: AutoplayMediaPreferences { + switch self { + case let .MessageEntry(_,_,_,_,_,_,data): + return data.autoPlay ?? AutoplayMediaPreferences.defaultSettings + case let .groupedPhotos(entries, _): + return entries.first?.autoplayMedia ?? AutoplayMediaPreferences.defaultSettings + default: + return AutoplayMediaPreferences.defaultSettings + } + } + + var renderType: ChatItemRenderType { + switch self { + case let .MessageEntry(_,_,_, renderType,_,_,_): + return renderType + case .groupedPhotos(let entries, _): + return entries.first!.renderType + case let .DateEntry(_, renderType): + return renderType + case .UnreadEntry(_, let renderType): + return renderType + case .bottom: + return .list + } + } + var location:MessageHistoryEntryLocation? { switch self { - case let .MessageEntry(_,_,_,_,location): - return location + case let .MessageEntry(_,_,_,_,_,_,data): + return data.location default: return nil } } + var additionalData: MessageEntryAdditionalData { + switch self { + case let .MessageEntry(_,_,_,_,_,_,data): + return data.additionData + default: + return MessageEntryAdditionalData() + } + } + var stableId: ChatHistoryEntryId { switch self { - case let .HoleEntry(hole): - return .hole(hole) - case let .MessageEntry(message,_,_,_,_): + case let .MessageEntry(message, _, _, _, _, _, _): return .message(message) - case let .DateEntry(index): + case .groupedPhotos(_, let info): + return .groupedPhotos(groupInfo: info) + case let .DateEntry(index, _): return .date(index) case .UnreadEntry: return .unread @@ -136,73 +215,131 @@ enum ChatHistoryEntry: Identifiable, Comparable { var index: MessageIndex { switch self { - case let .HoleEntry(hole): - return hole.maxIndex - case let .MessageEntry(message,_,_, _,_): + case let .MessageEntry(_,index, _, _, _, _,_): + return index + case let .groupedPhotos(entries, _): + return entries.last!.index + case let .UnreadEntry(index, _): + return index + case let .DateEntry(index, _): + return index + case .bottom: + return MessageIndex.absoluteUpperBound() + } + } + + + var scrollIndex: MessageIndex { + switch self { + case let .MessageEntry(message, _, _, _, _, _, _): return MessageIndex(message) - case let .UnreadEntry(index): + case let .groupedPhotos(entries, _): + return entries.last!.index + case let .UnreadEntry(index, _): return index - case let .DateEntry(index): + case let .DateEntry(index, _): return index case .bottom: return MessageIndex.absoluteUpperBound() } } + + func withUpdatedItemType(_ itemType: ChatItemType) -> ChatHistoryEntry { + switch self { + case let .MessageEntry(values): + return .MessageEntry(values.0, values.1, values.2, values.3, itemType, values.5, values.6) + default: + return self + } + } } -func ==(lhs: ChatHistoryEntry, rhs: ChatHistoryEntry) -> Bool { - switch lhs { - case let .HoleEntry(lhsHole): - switch rhs { - case let .HoleEntry(rhsHole) where lhsHole == rhsHole: - return true - default: +func isEqualMessageList(lhs:[Message], rhs:[Message]) -> Bool { + if lhs.count != rhs.count { + return false + } else { + for (i, message) in lhs.enumerated() { + if !isEqualMessages(message, rhs[i]) { + return false + } + } + } + return true +} + +func isEqualMessages(_ lhsMessage: Message, _ rhsMessage: Message) -> Bool { + + + if MessageIndex(lhsMessage) != MessageIndex(rhsMessage) || lhsMessage.stableVersion != rhsMessage.stableVersion { + return false + } + if lhsMessage.flags != rhsMessage.flags { + return false + } + + if lhsMessage.media.count != rhsMessage.media.count { + return false + } + for i in 0 ..< lhsMessage.media.count { + if !lhsMessage.media[i].isEqual(to: rhsMessage.media[i]) { return false } - case let .MessageEntry(lhsMessage,lhsRead,lhsType, lhsFwdType, _): - switch rhs { - case let .MessageEntry(rhsMessage,rhsRead,rhsType, rhsFwdType, _) where MessageIndex(lhsMessage) == MessageIndex(rhsMessage) && lhsMessage.stableVersion == rhsMessage.stableVersion && lhsRead == rhsRead && lhsType == rhsType && lhsFwdType == rhsFwdType: - if lhsMessage.media.count != rhsMessage.media.count { + } + + if lhsMessage.associatedMessages.count != rhsMessage.associatedMessages.count { + return false + } else { + for (messageId, lhsAssociatedMessage) in lhsMessage.associatedMessages { + if let rhsAssociatedMessage = rhsMessage.associatedMessages[messageId] { + if lhsAssociatedMessage.stableVersion != rhsAssociatedMessage.stableVersion { + return false + } + } else { return false } - for i in 0 ..< lhsMessage.media.count { - if !lhsMessage.media[i].isEqual(rhsMessage.media[i]) { + } + } + + if lhsMessage.peers.count != rhsMessage.peers.count { + return false + } else { + for (lhsPeerId, lhsPeer) in lhsMessage.peers { + if let rhsPeer = rhsMessage.peers[lhsPeerId] { + if rhsPeer.displayTitle != lhsPeer.displayTitle { return false } - } - - - if lhsMessage.associatedMessages.count != rhsMessage.associatedMessages.count { - return false } else { - for (messageId, lhsAssociatedMessage) in lhsMessage.associatedMessages { - if let rhsAssociatedMessage = rhsMessage.associatedMessages[messageId] { - if lhsAssociatedMessage.stableVersion != rhsAssociatedMessage.stableVersion { - return false - } - } else { - return false - } - } + return false } - - if lhsMessage.peers.count != rhsMessage.peers.count { + } + } + + return true +} + +func ==(lhs: ChatHistoryEntry, rhs: ChatHistoryEntry) -> Bool { + switch lhs { + case let .MessageEntry(message, index, read, renderType, type, fwdType, data): + switch rhs { + case .MessageEntry(message, index, read, renderType, type, fwdType, data): + return true + default: + return false + } + case let .groupedPhotos(lhsEntries, lhsGroupingKey): + if case let .groupedPhotos(rhsEntries, rhsGroupingKey) = rhs { + if lhsEntries.count != rhsEntries.count { return false } else { - for (lhsPeerId, lhsPeer) in lhsMessage.peers { - if let rhsPeer = rhsMessage.peers[lhsPeerId] { - if !lhsPeer.isEqual(rhsPeer) { - return false - } - } else { + for i in 0 ..< lhsEntries.count { + if lhsEntries[i] != rhsEntries[i] { return false } } + return lhsGroupingKey == rhsGroupingKey } - - return true - default: + } else { return false } case let .UnreadEntry(lhsIndex): @@ -241,56 +378,157 @@ func <(lhs: ChatHistoryEntry, rhs: ChatHistoryEntry) -> Bool { } -func messageEntries(_ messagesEntries: [MessageHistoryEntry], maxReadIndex:MessageIndex? = nil, includeHoles: Bool = true, dayGrouping: Bool = false, includeBottom:Bool = false, timeDifference: TimeInterval = 0, adminIds:[PeerId] = []) -> [ChatHistoryEntry] { +func messageEntries(_ messagesEntries: [MessageHistoryEntry], maxReadIndex:MessageIndex? = nil, includeHoles: Bool = true, dayGrouping: Bool = false, renderType: ChatItemRenderType = .list, includeBottom:Bool = false, timeDifference: TimeInterval = 0, ranks:CachedChannelAdminRanks? = nil, pollAnswersLoading: [MessageId : ChatPollStateData] = [:], groupingPhotos: Bool = false, autoplayMedia: AutoplayMediaPreferences? = nil, searchState: SearchMessagesResultState? = nil, animatedEmojiStickers: [String: StickerPackItem] = [:]) -> [ChatHistoryEntry] { var entries: [ChatHistoryEntry] = [] - + - var i:Int = 0 - for entry in messagesEntries { - switch entry { - case let .HoleEntry(hole, _): - if includeHoles { - entries.append(.HoleEntry(hole)) + var groupedPhotos:[ChatHistoryEntry] = [] + var groupInfo: MessageGroupInfo? + + + for (i, entry) in messagesEntries.enumerated() { + var message = entry.message + if message.media.isEmpty, let server = proxySettings(from: message.text).0 { + var textInfo = "" + let name: String + switch server.connection { + case let .socks5(username, password): + if let user = username { + textInfo += (!textInfo.isEmpty ? "\n" : "") + L10n.proxyForceEnableTextUsername(user) + } + if let pass = password { + textInfo += (!textInfo.isEmpty ? "\n" : "") + L10n.proxyForceEnableTextPassword(pass) + } + name = L10n.chatMessageSocks5Config + case let .mtp(secret): + textInfo += (!textInfo.isEmpty ? "\n" : "") + L10n.proxyForceEnableTextSecret(MTProxySecret.parseData(secret)?.serializeToString() ?? "") + name = L10n.chatMessageMTProxyConfig } - case let .MessageEntry(message,read, location, _): - var disableEntry = false - if let action = message.media.first as? TelegramMediaAction { - switch action.action { - case .historyCleared: - disableEntry = true - default: - break + let media = TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: message.text, displayUrl: "", hash: 0, type: "proxy", websiteName: name, title: L10n.proxyForceEnableTextIP(server.host) + "\n" + L10n.proxyForceEnableTextPort(Int(server.port)), text: textInfo, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, attributes: [], instantPage: nil))) + message = message.withUpdatedMedia([media]).withUpdatedText("") + } + + + if message.media.isEmpty { + if message.text.length <= 6 { + let original = message.text.fixed + let unmodified = original.emojiUnmodified + if let item = animatedEmojiStickers[unmodified] { + var file = item.file + var attributes = file.attributes + attributes.removeAll { attr in + if case .FileName = attr { + return true + } else { + return false + } + } + attributes = attributes.map { attribute -> TelegramMediaFileAttribute in + switch attribute { + case let .Sticker(_, packReference, maskData): + return .Sticker(displayText: original, packReference: packReference, maskData: maskData) + default: + return attribute + } + } + var disableStickers: Bool = false + if let peer = messageMainPeer(message) as? TelegramChannel { + if permissionText(from: peer, for: [.banSendGifs, .banSendStickers]) != nil { + disableStickers = true + } + } + if !disableStickers { + attributes.append(.FileName(fileName: "telegram-animoji.tgs")) + file = file.withUpdatedAttributes(attributes) + message = message.withUpdatedMedia([file]) + } } } - - if disableEntry { + } + + + var disableEntry = false + if let action = message.media.first as? TelegramMediaAction { + switch action.action { + case .historyCleared: + disableEntry = true + case .groupMigratedToChannel: + disableEntry = true + case .channelMigratedFromGroup: + disableEntry = true + case .peerJoined: + disableEntry = false + default: break } + } + + if disableEntry { + continue + } + + + + var prev:MessageHistoryEntry? = nil + var next:MessageHistoryEntry? = nil + + if i > 0 { + loop: for k in stride(from: i - 1, to: -1, by: -1) { + let current = messagesEntries[k] + if let groupInfo = message.groupInfo { + if current.message.groupInfo == groupInfo { + continue loop + } else { + prev = current + break loop + } + } else { + prev = current + break loop + } + } - var prev:MessageHistoryEntry? = nil - var next:MessageHistoryEntry? = nil - - if i > 0 { - prev = messagesEntries[i - 1] + } + if i < messagesEntries.count - 1 { + loop: for k in i + 1 ..< messagesEntries.count { + let current = messagesEntries[k] + if let groupInfo = message.groupInfo { + if current.message.groupInfo == groupInfo { + continue loop + } else { + next = current + break loop + } + } else { + next = current + break loop + } } - if i < messagesEntries.count - 1 { - next = messagesEntries[i + 1] + } + + + let rawRank = ranks?.ranks[message.author?.id ?? PeerId(0)] + var rank:String? = nil + if let rawRank = rawRank { + switch rawRank { + case .admin: + rank = L10n.chatAdminBadge + case .owner: + rank = L10n.chatOwnerBadge + case let .custom(string): + rank = string } - - let isAdmin = adminIds.contains(message.author?.id ?? PeerId(0)) - - var itemType:ChatItemType = .Full(isAdmin: isAdmin) - var fwdType:ForwardItemType? = nil - - - - - if let prev = prev, case let .MessageEntry(prevMessage,_, _, _) = prev { - - + } + + var itemType:ChatItemType = .Full(rank: rank) + var fwdType:ForwardItemType? = nil + + + if renderType == .list { + if let prev = prev { var actionShortAccess: Bool = true - if let action = prevMessage.media.first as? TelegramMediaAction { + if let action = prev.message.media.first as? TelegramMediaAction { switch action.action { case .phoneCall: actionShortAccess = true @@ -299,93 +537,176 @@ func messageEntries(_ messagesEntries: [MessageHistoryEntry], maxReadIndex:Messa } } - if message.author?.id == prevMessage.author?.id, (message.timestamp - prevMessage.timestamp) < simpleDif, actionShortAccess, let peer = message.peers[message.id.peerId] { + if message.author?.id == prev.message.author?.id, (message.timestamp - prev.message.timestamp) < simpleDif, actionShortAccess, let peer = message.peers[message.id.peerId] { if let peer = peer as? TelegramChannel, case .broadcast(_) = peer.info { - itemType = .Full(isAdmin: isAdmin) + itemType = .Full(rank: rank) } else { - var canShort:Bool = true - for attr in message.attributes { - if !(attr is OutgoingMessageInfoAttribute) && !(attr is TextEntitiesMessageAttribute) && !(attr is EditedMessageAttribute) && !(attr is ForwardSourceInfoAttribute) && !(attr is ViewCountMessageAttribute) && !(attr is ConsumableContentMessageAttribute) && !(attr is NotificationInfoMessageAttribute) && !(attr is ChannelMessageStateVersionAttribute) { + var canShort:Bool = (message.media.isEmpty || message.media.first?.isInteractiveMedia == false) || message.forwardInfo == nil || renderType == .list + attrsLoop: for attr in message.attributes { + if !(attr is OutgoingMessageInfoAttribute) && !(attr is TextEntitiesMessageAttribute) && !(attr is EditedMessageAttribute) && !(attr is ForwardSourceInfoAttribute) && !(attr is ViewCountMessageAttribute) && !(attr is ConsumableContentMessageAttribute) && !(attr is NotificationInfoMessageAttribute) && !(attr is ChannelMessageStateVersionAttribute) && !(attr is AutoremoveTimeoutMessageAttribute) { canShort = false - break + break attrsLoop } } - itemType = !canShort ? .Full(isAdmin: isAdmin) : .Short + itemType = !canShort ? .Full(rank: rank) : .Short } } else { - itemType = .Full(isAdmin: isAdmin) + itemType = .Full(rank: rank) } } else { - itemType = .Full(isAdmin: isAdmin) + itemType = .Full(rank: rank) } - - - if message.forwardInfo != nil { - if case .Short = itemType { - if let prev = prev, case let .MessageEntry(prevMessage,_, _, _) = prev { - if prevMessage.forwardInfo != nil, message.timestamp - prevMessage.timestamp < simpleDif { - fwdType = .Inside - if let next = next, case let .MessageEntry(nextMessage,_, _, _) = next { - - if message.author?.id != nextMessage.author?.id || nextMessage.timestamp - message.timestamp > simpleDif || nextMessage.forwardInfo == nil { - fwdType = .Bottom - } - - } else { + } else { + if let next = next { + if message.author?.id == next.message.author?.id, let peer = message.peers[message.id.peerId] { + if peer.isChannel || ((peer.isGroup || peer.isSupergroup) && message.flags.contains(.Incoming)) { + itemType = .Full(rank: rank) + } else { + itemType = message.inlinePeer == nil ? .Short : .Full(rank: rank) + } + } else { + itemType = .Full(rank: rank) + } + } else { + itemType = .Full(rank: rank) + } + } + + + + + if message.forwardInfo != nil { + if case .Short = itemType { + if let prev = prev { + if prev.message.forwardInfo != nil, message.timestamp - prev.message.timestamp < simpleDif { + fwdType = .Inside + if let next = next { + if message.author?.id != next.message.author?.id || next.message.timestamp - message.timestamp > simpleDif || next.message.forwardInfo == nil { fwdType = .Bottom } - } else { - fwdType = .ShortHeader + fwdType = .Bottom } + } else { + fwdType = .ShortHeader } - } else { - fwdType = .ShortHeader } + } else { + fwdType = .ShortHeader } - - if let forwardType = fwdType, forwardType == .ShortHeader || forwardType == .FullHeader { - itemType = .Full(isAdmin: isAdmin) - if forwardType == .ShortHeader { - if let next = next, case let .MessageEntry(nextMessage,_, _, _) = next { - if nextMessage.forwardInfo != nil && (message.author?.id == nextMessage.author?.id || nextMessage.timestamp - message.timestamp < simpleDif) { - fwdType = .FullHeader - } - + } + + if let forwardType = fwdType, forwardType == .ShortHeader || forwardType == .FullHeader { + itemType = .Full(rank: rank) + if forwardType == .ShortHeader { + if let next = next { + if next.message.forwardInfo != nil && (message.author?.id == next.message.author?.id || next.message.timestamp - message.timestamp < simpleDif) { + fwdType = .FullHeader } + } } - - - if prev == nil && dayGrouping { - var time = TimeInterval(message.timestamp) - time -= timeDifference - let dateId = chatDateId(for: Int32(time)) - let index = MessageIndex(id: message.id, timestamp: Int32(dateId)) - entries.append(.DateEntry(index)) + } + + let additionalData: MessageEntryAdditionalData + var highlightFoundText: HighlightFoundText? = nil + + + if let searchState = searchState, !message.text.isEmpty { + highlightFoundText = HighlightFoundText(query: searchState.query, isMessage: searchState.containsMessage(message)) + } + + + if let data = pollAnswersLoading[message.id] { + additionalData = MessageEntryAdditionalData(pollStateData: data, highlightFoundText: highlightFoundText) + } else { + additionalData = MessageEntryAdditionalData(pollStateData: ChatPollStateData(), highlightFoundText: highlightFoundText) + } + let data = ChatHistoryEntryData(entry.location, additionalData, autoplayMedia) + + + let timestamp = Int32(min(TimeInterval(message.timestamp) - timeDifference, TimeInterval(Int32.max))) + + + var isRead = entry.isRead + if !message.flags.contains(.Incoming) { + var k = i + loop: while k < messagesEntries.count - 1 { + let next = messagesEntries[k + 1] + if next.message.flags.contains(.Incoming) { + isRead = true + break loop + } + k += 1 } - - entries.append(.MessageEntry(message,read,itemType,fwdType, location)) - - if let next = next, case let .MessageEntry(nextMessage,_, _, _) = next, dayGrouping { - let dateId = chatDateId(for: message.timestamp - Int32(timeDifference)) - let nextDateId = chatDateId(for: nextMessage.timestamp - Int32(timeDifference)) - if dateId != nextDateId { - let index = MessageIndex(id: MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: INT_MAX), timestamp: Int32(nextDateId)) - entries.append(.DateEntry(index)) + } + + let entry: ChatHistoryEntry = .MessageEntry(message, MessageIndex(message.withUpdatedTimestamp(timestamp)), isRead, renderType, itemType, fwdType, data) + + if let key = message.groupInfo, groupingPhotos, message.id.peerId.namespace == Namespaces.Peer.SecretChat || !message.containsSecretMedia, !message.media.isEmpty { + if groupInfo == nil { + groupInfo = key + groupedPhotos.append(entry.withUpdatedItemType(.Full(rank: rank))) + } else if groupInfo == key { + groupedPhotos.append(entry.withUpdatedItemType(.Full(rank: rank))) + } else { + if groupedPhotos.count > 0 { + if let groupInfo = groupInfo { + if groupedPhotos.count > 1 { + entries.append(.groupedPhotos(groupedPhotos, groupInfo: groupInfo)) + } else { + entries.append(groupedPhotos[0]) + } + } + groupedPhotos.removeAll() } + + groupInfo = key + groupedPhotos.append(entry.withUpdatedItemType(.Full(rank: rank))) } - + } else { + entries.append(entry) + } + + prev = nil + next = nil + + if i > 0 { + prev = messagesEntries[i - 1] + } + if i < messagesEntries.count - 1 { + next = messagesEntries[i + 1] } - i += 1 + if prev == nil && dayGrouping { + let timestamp = Int32(min(TimeInterval(message.timestamp) - timeDifference, TimeInterval(Int32.max))) + + let dateId = chatDateId(for: timestamp) + let index = MessageIndex(id: MessageId(peerId: message.id.peerId, namespace: Namespaces.Message.Local, id: 0), timestamp: Int32(dateId)) + entries.append(.DateEntry(index, renderType)) + } + + if let next = next, dayGrouping { + let timestamp = Int32(min(TimeInterval(message.timestamp) - timeDifference, TimeInterval(Int32.max))) + let nextTimestamp = Int32(min(TimeInterval(next.message.timestamp) - timeDifference, TimeInterval(Int32.max))) + + let dateId = chatDateId(for: timestamp) + let nextDateId = chatDateId(for: nextTimestamp) + + + if dateId != nextDateId { + let index = MessageIndex(id: MessageId(peerId: message.id.peerId, namespace: Namespaces.Message.Local, id: INT_MAX), timestamp: Int32(nextDateId)) + entries.append(.DateEntry(index, renderType)) + } + } } var hasUnread = false if let maxReadIndex = maxReadIndex { - entries.append(.UnreadEntry(maxReadIndex)) + let timestamp = Int32(min(TimeInterval(maxReadIndex.timestamp) - timeDifference, TimeInterval(Int32.max))) + entries.append(.UnreadEntry(maxReadIndex.withUpdatedTimestamp(timestamp), renderType)) hasUnread = true } @@ -394,8 +715,13 @@ func messageEntries(_ messagesEntries: [MessageHistoryEntry], maxReadIndex:Messa entries.append(.bottom) } - - + if !groupedPhotos.isEmpty, let key = groupInfo { + if groupedPhotos.count == 1 { + entries.append(groupedPhotos[0]) + } else { + entries.append(.groupedPhotos(groupedPhotos, groupInfo: key)) + } + } var sorted = entries.sorted() if hasUnread, sorted.count >= 2 { @@ -403,7 +729,7 @@ func messageEntries(_ messagesEntries: [MessageHistoryEntry], maxReadIndex:Messa sorted.remove(at: sorted.count - 2) } } - + return sorted } diff --git a/Telegram-Mac/ChatHistoryViewForLocation.swift b/Telegram-Mac/ChatHistoryViewForLocation.swift index 8c89e74a4c..25ac58c09c 100644 --- a/Telegram-Mac/ChatHistoryViewForLocation.swift +++ b/Telegram-Mac/ChatHistoryViewForLocation.swift @@ -8,9 +8,10 @@ import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit import TGUIKit enum ChatHistoryInitialSearchLocation { @@ -21,15 +22,37 @@ enum ChatHistoryInitialSearchLocation { enum ChatHistoryLocation: Equatable { case Initial(count: Int) case InitialSearch(location: ChatHistoryInitialSearchLocation, count: Int) - case Navigation(index: MessageIndex, anchorIndex: MessageIndex) - case Scroll(index: MessageIndex, anchorIndex: MessageIndex, sourceIndex: MessageIndex, scrollPosition: TableScrollState, animated: Bool) + case Navigation(index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, side: TableSavingSide) + case Scroll(index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, sourceIndex: MessageHistoryAnchorIndex, scrollPosition: TableScrollState, count: Int, animated: Bool) + + var count: Int { + switch self { + case let .Initial(count): + return count + case let .InitialSearch(_, count): + return count + case let .Navigation(_, _, count, _): + return count + case let .Scroll(_, _, _, _, count, _): + return count + } + } + + var side: TableSavingSide? { + switch self { + case let .Navigation(_, _, _, side): + return side + default: + return nil + } + } } func ==(lhs: ChatHistoryLocation, rhs: ChatHistoryLocation) -> Bool { switch lhs { - case let .Navigation(lhsIndex, lhsAnchorIndex): + case let .Navigation(lhsIndex, lhsAnchorIndex, lhsCount, lhsSide): switch rhs { - case let .Navigation(rhsIndex, rhsAnchorIndex) where lhsIndex == rhsIndex && lhsAnchorIndex == rhsAnchorIndex: + case let .Navigation(rhsIndex, rhsAnchorIndex, rhsCount, rhsSide) where lhsIndex == rhsIndex && lhsAnchorIndex == rhsAnchorIndex && lhsCount == rhsCount && lhsSide == rhsSide: return true default: return false @@ -41,17 +64,44 @@ func ==(lhs: ChatHistoryLocation, rhs: ChatHistoryLocation) -> Bool { -enum ChatHistoryViewScrollPosition { +enum ChatHistoryViewScrollPosition : Equatable { case unread(index: MessageIndex) case positionRestoration(index: MessageIndex, relativeOffset: CGFloat) - case index(index: MessageIndex, position: TableScrollState, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) + case index(index: MessageHistoryAnchorIndex, position: TableScrollState, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) +} + +func ==(lhs: ChatHistoryViewScrollPosition, rhs: ChatHistoryViewScrollPosition) -> Bool { + switch lhs { + case let .unread(index): + if case .unread(index: index) = rhs { + return true + } else { + return false + } + case let .positionRestoration(index, relativeOffset): + if case .positionRestoration(index: index, relativeOffset: relativeOffset) = rhs { + return true + } else { + return false + } + case let .index(index, position, directionHint, animated): + if case .index(index: index, position: position, directionHint: directionHint, animated: animated) = rhs { + return true + } else { + return false + } + } } public struct ChatHistoryCombinedInitialData { let initialData: InitialMessageHistoryData? let buttonKeyboardMessage: Message? let cachedData: CachedPeerData? - let readStateData: ChatHistoryCombinedInitialReadStateData? + let cachedDataMessages:[MessageId: Message]? + let readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]? + let limitsConfiguration: LimitsConfiguration + let autoplayMedia: AutoplayMediaPreferences + let autodownloadSettings: AutomaticMediaDownloadSettings } enum ChatHistoryViewUpdateType { @@ -62,46 +112,47 @@ enum ChatHistoryViewUpdateType { public struct ChatHistoryCombinedInitialReadStateData { public let unreadCount: Int32 public let totalUnreadCount: Int32 + public let notificationSettings: PeerNotificationSettings? } + + enum ChatHistoryViewUpdate { - case Loading(initialData: InitialMessageHistoryData?) + case Loading(initialData: ChatHistoryCombinedInitialData, type: ChatHistoryViewUpdateType) case HistoryView(view: MessageHistoryView, type: ChatHistoryViewUpdateType, scrollPosition: ChatHistoryViewScrollPosition?, initialData: ChatHistoryCombinedInitialData) } -func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Account, peerId: PeerId, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData] = [], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { +func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: (()->MessageHistoryViewReadState?)?, tagMask: MessageTags?, mode: ChatMode = .history, additionalData: [AdditionalMessageHistoryViewData] = [], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + + + + switch location { case let .Initial(count): var preloaded = false var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> - if let tagMask = tagMask { - signal = account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: MessageIndex.upperBound(peerId: peerId), count: count, anchorIndex: MessageIndex.upperBound(peerId: peerId), fixedCombinedReadState: nil, tagMask: tagMask, orderStatistics: orderStatistics) - } else { - signal = account.viewTracker.aroundMessageOfInterestHistoryViewForPeerId(peerId, count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + + switch mode { + case .history: + if let tagMask = tagMask { + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) + } else { + signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(chatLocation, count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + } + case .scheduled: + signal = account.viewTracker.scheduledMessagesViewForLocation(chatLocation) } + + return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in - var cachedData: CachedPeerData? - var readStateData: ChatHistoryCombinedInitialReadStateData? - for data in view.additionalData { - switch data { - case let .cachedPeerData(peerIdValue, value): - if peerIdValue == peerId { - cachedData = value - } - case let .totalUnreadCount(totalUnreadCount): - if let readState = view.combinedReadState { - readStateData = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalUnreadCount: totalUnreadCount) - } - default: - break - } - } + let (cachedData, cachedDataMessages, readStateData, limitsConfiguration, autoplayMedia, autodownloadSettings) = extractAdditionalData(view: view, chatLocation: chatLocation) + let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, limitsConfiguration: limitsConfiguration, autoplayMedia: autoplayMedia, autodownloadSettings: autodownloadSettings) if preloaded { - return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: combinedInitialData) } else { var scrollPosition: ChatHistoryViewScrollPosition? @@ -117,49 +168,43 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun } } - let maxIndex = min(view.entries.count, targetIndex + count / 2) + let maxIndex = targetIndex + count / 2 + let minIndex = targetIndex - count / 2 + if minIndex <= 0 && view.holeEarlier { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) + } if maxIndex >= targetIndex { - for i in targetIndex ..< maxIndex { - if case .HoleEntry = view.entries[i] { - var incomingCount: Int32 = 0 - inner: for entry in view.entries.reversed() { - switch entry { - case .HoleEntry: - break inner - case let .MessageEntry(message, _, _, _): - if message.flags.contains(.Incoming) { - incomingCount += 1 - } - } - } - if let combinedReadState = view.combinedReadState, combinedReadState.count == incomingCount { - - } else { - fadeIn = true - return .Loading(initialData: initialData) + if view.holeLater { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) + } + if view.holeEarlier { + var incomingCount: Int32 = 0 + inner: for entry in view.entries.reversed() { + if entry.message.flags.contains(.Incoming) { + incomingCount += 1 } } + if case let .peer(peerId) = chatLocation, let combinedReadStates = view.fixedReadStates, case let .peer(readStates) = combinedReadStates, let readState = readStates[peerId], readState.count == incomingCount { + } else { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) + } } } + } else if let historyScrollState = (initialData?.chatInterfaceState as? ChatInterfaceState)?.historyScrollState { scrollPosition = .positionRestoration(index: historyScrollState.messageIndex, relativeOffset: CGFloat(historyScrollState.relativeOffset)) } else { - var messageCount = 0 - for entry in view.entries.reversed() { - if case .HoleEntry = entry { - fadeIn = true - return .Loading(initialData: initialData) - } else { - messageCount += 1 - } - if messageCount >= 1 { - break - } + if view.entries.isEmpty && (view.holeEarlier || view.holeLater) { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) } } preloaded = true - return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: scrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: scrollPosition, initialData: combinedInitialData) } } case let .InitialSearch(searchLocation, count): @@ -167,86 +212,98 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> - switch searchLocation { - case let .index(index): - signal = account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: index, fixedCombinedReadState: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) - case let .id(id): - signal = account.viewTracker.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + + switch mode { + case .history: + switch searchLocation { + case let .index(index): + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: MessageHistoryAnchorIndex.message(index), anchorIndex: MessageHistoryAnchorIndex.message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + case let .id(id): + signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + } + case .scheduled: + signal = account.viewTracker.scheduledMessagesViewForLocation(chatLocation) } + + return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in - var cachedData: CachedPeerData? - var readStateData: ChatHistoryCombinedInitialReadStateData? - for data in view.additionalData { - switch data { - case let .cachedPeerData(peerIdValue, value): - if peerIdValue == peerId { - cachedData = value - } - case let .totalUnreadCount(totalUnreadCount): - if let readState = view.combinedReadState { - readStateData = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalUnreadCount: totalUnreadCount) - } - default: - break - } - } + let (cachedData, cachedDataMessages, readStateData, limitsConfiguration, autoplayMedia, autodownloadSettings) = extractAdditionalData(view: view, chatLocation: chatLocation) + let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, limitsConfiguration: limitsConfiguration, autoplayMedia: autoplayMedia, autodownloadSettings: autodownloadSettings) + if preloaded { - return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: combinedInitialData) } else { let anchorIndex = view.anchorIndex var targetIndex = 0 for i in 0 ..< view.entries.count { - if view.entries[i].index >= anchorIndex { + //if view.entries[i].index >= anchorIndex + if anchorIndex.isLessOrEqual(to: view.entries[i].index) { targetIndex = i break } } - let maxIndex = min(view.entries.count, targetIndex + count / 2) - if maxIndex >= targetIndex { - for i in targetIndex ..< maxIndex { - if case .HoleEntry = view.entries[i] { - fadeIn = true - return .Loading(initialData: initialData) - } + + if !view.entries.isEmpty { + let minIndex = max(0, targetIndex - count / 2) + let maxIndex = min(view.entries.count, targetIndex + count / 2) + if minIndex == 0 && view.holeEarlier { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) + } + if maxIndex == view.entries.count && view.holeLater { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) } + } else if view.holeEarlier || view.holeLater { + fadeIn = true + return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) } + + var reportUpdateType: ChatHistoryViewUpdateType = .Initial(fadeIn: fadeIn) + if case .FillHole = updateType { + reportUpdateType = .Generic(type: updateType) + } + preloaded = true var scroll: TableScrollState - - if view.entries.count > targetIndex, let message = view.entries[targetIndex].message { - scroll = .center(id: ChatHistoryEntryId.message(message), animated: false, focus: true, inset: 0) + if view.entries.count > targetIndex { + let focusMessage = view.entries[targetIndex].message + let mustToFocus: Bool + switch searchLocation { + case let .index(index): + mustToFocus = view.entries[targetIndex].index == index + case let .id(id): + mustToFocus = view.entries[targetIndex].message.id == id + } + scroll = .center(id: ChatHistoryEntryId.message(focusMessage), innerId: nil, animated: false, focus: .init(focus: mustToFocus), inset: 0) } else { scroll = .none(nil) } - return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: .index(index: anchorIndex, position: scroll, directionHint: .Down, animated: false), initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: reportUpdateType, scrollPosition: .index(index: anchorIndex, position: scroll, directionHint: .Down, animated: false), initialData: combinedInitialData) } } - case let .Navigation(index, anchorIndex): + case let .Navigation(index, anchorIndex, count, _): var first = true - return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in - var cachedData: CachedPeerData? - var readStateData: ChatHistoryCombinedInitialReadStateData? - for data in view.additionalData { - switch data { - case let .cachedPeerData(peerIdValue, value): - if peerIdValue == peerId { - cachedData = value - } - case let .totalUnreadCount(totalUnreadCount): - if let readState = view.combinedReadState { - readStateData = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalUnreadCount: totalUnreadCount) - } - default: - break - } - } + + let signal:Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> + switch mode { + case .history: + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates?(), tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + case .scheduled: + signal = account.viewTracker.scheduledMessagesViewForLocation(chatLocation) + } + + return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + + let (cachedData, cachedDataMessages, readStateData, limitsConfiguration, autoplayMedia, autodownloadSettings) = extractAdditionalData(view: view, chatLocation: chatLocation) + let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, limitsConfiguration: limitsConfiguration, autoplayMedia: autoplayMedia, autodownloadSettings: autodownloadSettings) let genericType: ViewUpdateType if first { @@ -255,29 +312,24 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun } else { genericType = updateType } - return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: nil, initialData: combinedInitialData) } - case let .Scroll(index, anchorIndex, sourceIndex, scrollPosition, animated): + case let .Scroll(index, anchorIndex, sourceIndex, scrollPosition, count, animated): let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in - var cachedData: CachedPeerData? - var readStateData: ChatHistoryCombinedInitialReadStateData? - for data in view.additionalData { - switch data { - case let .cachedPeerData(peerIdValue, value): - if peerIdValue == peerId { - cachedData = value - } - case let .totalUnreadCount(totalUnreadCount): - if let readState = view.combinedReadState { - readStateData = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalUnreadCount: totalUnreadCount) - } - default: - break - } - } + + let signal:Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> + switch mode { + case .history: + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates?(), tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + case .scheduled: + signal = account.viewTracker.scheduledMessagesViewForLocation(chatLocation) + } + + return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + let (cachedData, cachedDataMessages, readStateData, limitsConfiguration, autoplayMedia, autodownloadSettings) = extractAdditionalData(view: view, chatLocation: chatLocation) + let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, limitsConfiguration: limitsConfiguration, autoplayMedia: autoplayMedia, autodownloadSettings: autodownloadSettings) let genericType: ViewUpdateType let scrollPosition: ChatHistoryViewScrollPosition? = first ? chatScrollPosition : nil @@ -287,7 +339,78 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun } else { genericType = updateType } - return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, readStateData: readStateData)) + return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, initialData: combinedInitialData) } } } + +private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatLocation) -> ( + cachedData: CachedPeerData?, + cachedDataMessages: [MessageId: Message]?, + readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, + limitsConfiguration: LimitsConfiguration, + autoplayMedia: AutoplayMediaPreferences, + autodownloadSettings: AutomaticMediaDownloadSettings + ) { + var cachedData: CachedPeerData? + var cachedDataMessages: [MessageId: Message]? + var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData] = [:] + var notificationSettings: PeerNotificationSettings? + var limitsConfiguration: LimitsConfiguration = LimitsConfiguration.defaultValue + var autoplayMedia: AutoplayMediaPreferences = AutoplayMediaPreferences.defaultSettings + var autodownloadSettings: AutomaticMediaDownloadSettings = AutomaticMediaDownloadSettings.defaultSettings + loop: for data in view.additionalData { + switch data { + case let .peerNotificationSettings(value): + notificationSettings = value + break loop + default: + break + } + } + + for data in view.additionalData { + switch data { + case let .peerNotificationSettings(value): + notificationSettings = value + case let .cachedPeerData(peerIdValue, value): + if case .peer(peerIdValue) = chatLocation { + cachedData = value + } + case let .cachedPeerDataMessages(peerIdValue, value): + if case .peer(peerIdValue) = chatLocation { + cachedDataMessages = value + } + case let .preferencesEntry(key, value): + if key == PreferencesKeys.limitsConfiguration { + limitsConfiguration = value as? LimitsConfiguration ?? LimitsConfiguration.defaultValue + } + if key == ApplicationSpecificPreferencesKeys.autoplayMedia { + autoplayMedia = value as? AutoplayMediaPreferences ?? AutoplayMediaPreferences.defaultSettings + + } + + if key == ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings { + autodownloadSettings = value as? AutomaticMediaDownloadSettings ?? AutomaticMediaDownloadSettings.defaultSettings + } + case let .totalUnreadState(unreadState): + + switch chatLocation { + case let .peer(peerId): + break + if let combinedReadStates = view.fixedReadStates { + if case let .peer(readStates) = combinedReadStates, let readState = readStates[peerId] { + readStateData[peerId] = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalUnreadCount: 0, notificationSettings: notificationSettings) + } + } + } + default: + break + } + } + + autoplayMedia = autoplayMedia.withUpdatedAutoplayPreloadVideos(autoplayMedia.preloadVideos && autodownloadSettings.automaticDownload && (autodownloadSettings.categories.video.fileSize ?? 0) >= 5 * 1024 * 1024) + + + return (cachedData, cachedDataMessages, readStateData, limitsConfiguration, autoplayMedia, autodownloadSettings) +} diff --git a/Telegram-Mac/ChatHoleRowItem.swift b/Telegram-Mac/ChatHoleRowItem.swift index 5b0962f940..eceba3119e 100644 --- a/Telegram-Mac/ChatHoleRowItem.swift +++ b/Telegram-Mac/ChatHoleRowItem.swift @@ -8,23 +8,26 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore class ChatHoleRowItem: ChatRowItem { - + override var canBeAnchor: Bool { + return false + } override var height: CGFloat { - return 20 + return 0 } override open var animatable:Bool { return false } - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account:Account, _ entry:ChatHistoryEntry) { - super.init(initialSize, chatInteraction, entry) + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ entry:ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, entry, downloadSettings, theme: theme) } @@ -32,3 +35,27 @@ class ChatHoleRowItem: ChatRowItem { return ChatHoleRowView.self } } + + +class ChatHoleRowView: TableRowView { + + // private let progress: ProgressIndicator = ProgressIndicator() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + // addSubview(progress) + } + + override var backdorColor: NSColor { + return .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + // progress.center() + } + +} diff --git a/Telegram-Mac/ChatHoleRowView.swift b/Telegram-Mac/ChatHoleRowView.swift deleted file mode 100644 index 18a2712cb1..0000000000 --- a/Telegram-Mac/ChatHoleRowView.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ChatHoleRowView.swift -// Telegram-Mac -// -// Created by keepcoder on 13/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -class ChatHoleRowView: TableRowView { - - private let progress: ProgressIndicator = ProgressIndicator() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(progress) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layout() { - super.layout() - progress.center() - } - -} diff --git a/Telegram-Mac/ChatInfoTouchbar.swift b/Telegram-Mac/ChatInfoTouchbar.swift new file mode 100644 index 0000000000..812eb97b85 --- /dev/null +++ b/Telegram-Mac/ChatInfoTouchbar.swift @@ -0,0 +1,155 @@ +// +// ChatInfoTouchbar.swift +// Telegram +// +// Created by Mikhail Filimonov on 18/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import TGUIKit + +@available(OSX 10.12.2, *) +fileprivate extension NSTouchBarItem.Identifier { + static let edit = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat-info.edit") + static let share = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat-info.share") + + static let sharedMediaAndInfo = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat-info.sharedMediaAndInfo") + static let userActions = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat-info.userActions") + +} + +@available(OSX 10.12.2, *) +class ChatInfoTouchbar: NSTouchBar, NSTouchBarDelegate { + private let chatInteraction: ChatInteraction + private let dismiss:()->Void + init(chatInteraction: ChatInteraction, dismiss: @escaping()->Void) { + self.chatInteraction = chatInteraction + self.dismiss = dismiss + super.init() + self.delegate = self + guard let peer = chatInteraction.peer else {return} + var items: [NSTouchBarItem.Identifier] = [] + items.append(.edit) + if peer.isBot || (peer.isUser && (peer as! TelegramUser).phone != nil) || peer.addressName != nil { + items.append(.share) + } + items.append(.flexibleSpace) + items.append(.sharedMediaAndInfo) + if peer.isUser && peer.id != chatInteraction.context.peerId, !peer.isBot { + items.append(.userActions) + } + items.append(.flexibleSpace) + self.defaultItemIdentifiers = items + self.customizationAllowedItemIdentifiers = self.defaultItemIdentifiers + self.customizationIdentifier = .popoverBar + } + + @objc private func userInfoActions(_ sender: Any?) { + guard let segment = sender as? NSSegmentedControl else {return} + switch segment.selectedSegment { + case 0: + _ = showModalProgress(signal: createSecretChat(account: chatInteraction.context.account, peerId: chatInteraction.peerId) |> deliverOnMainQueue, for: mainWindow).start(next: { [weak self] peerId in + if let strongSelf = self { + strongSelf.chatInteraction.context.sharedContext.bindings.rootNavigation().push(ChatController(context: strongSelf.chatInteraction.context, chatLocation: .peer(peerId))) + } + }) + case 1: + let context = chatInteraction.context + _ = (phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: chatInteraction.peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) + }) + default: + break + } + dismiss() + } + @objc private func editChat() { + chatInteraction.update({$0.selectionState == nil ? $0.withSelectionState() : $0.withoutSelectionState()}) + dismiss() + } + @objc private func shareAction() { + guard let peer = chatInteraction.peer else {return} + + if peer.isUser, let peer = peer as? TelegramUser { + showModal(with: ShareModalController(ShareContactObject(chatInteraction.context, user: peer)), for: mainWindow) + } else if let address = peer.addressName { + showModal(with: ShareModalController(ShareLinkObject(chatInteraction.context, link: "https://t.me/\(address)")), for: mainWindow) + } + + dismiss() + } + @objc private func sharedMediaAction() { + chatInteraction.context.sharedContext.bindings.rootNavigation().push(PeerMediaController(context: chatInteraction.context, peerId: chatInteraction.peerId)) + dismiss() + } + @objc private func peerInfoActions(_ sender: Any?) { + guard let segment = sender as? NSSegmentedControl else {return} + switch segment.selectedSegment { + case 0: + chatInteraction.context.sharedContext.bindings.rootNavigation().push(PeerMediaController(context: chatInteraction.context, peerId: chatInteraction.peerId)) + case 1: + chatInteraction.context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: chatInteraction.context, peerId: chatInteraction.peerId)) + default: + break + } + dismiss() + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .edit: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: chatInteraction.presentation.selectionState != nil ? L10n.navigationCancel : L10n.navigationEdit, target: self, action: #selector(editChat)) + item.view = button + item.customizationLabel = button.title + return item + case .share: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(image: NSImage(named: NSImage.Name("Icon_TouchBar_Share"))!, target: self, action: #selector(shareAction)) + item.view = button + item.customizationLabel = button.title + return item + case .sharedMediaAndInfo: + let item = NSCustomTouchBarItem(identifier: identifier) + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 1 + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_AttachPhotoOrVideo"))!, forSegment: 0) + segment.setLabel(L10n.telegramPeerMediaController, forSegment: 0) + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(peerInfoActions(_:)) + item.view = segment + return item + case .userActions: + let item = NSCustomTouchBarItem(identifier: identifier) + guard let peer = chatInteraction.peer as? TelegramUser else {return nil} + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = peer.canCall ? 2 : 1 + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_ComposeSecretChat"))!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_Call"))!, forSegment: 1) + segment.setLabel(L10n.touchBarStartSecretChat, forSegment: 0) + segment.setLabel(L10n.touchBarCall, forSegment: 1) + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(userInfoActions(_:)) + item.view = segment + return item + default: + break + } + return nil + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + +} diff --git a/Telegram-Mac/ChatInputAccessory.swift b/Telegram-Mac/ChatInputAccessory.swift index 7d92a748df..4db746d784 100644 --- a/Telegram-Mac/ChatInputAccessory.swift +++ b/Telegram-Mac/ChatInputAccessory.swift @@ -7,10 +7,11 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit class ChatInputAccessory: Node { @@ -20,9 +21,9 @@ class ChatInputAccessory: Node { private var displayNode:ChatAccessoryModel? private let dismiss:ImageButton = ImageButton() + private var progress: Control? let container:ChatAccessoryView = ChatAccessoryView() - var dismissForward:(()->Void)! var dismissReply:(()->Void)! var dismissEdit:(()->Void)! @@ -35,10 +36,10 @@ class ChatInputAccessory: Node { self?.chatInteraction.update({$0.updatedInterfaceState({$0.withoutForwardMessages()})}) } dismissReply = { [weak self] in - self?.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(nil)})}) + self?.chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(nil).withUpdatedDismissedForceReplyId($0.replyMessageId)})}) } dismissEdit = { [weak self] in - self?.chatInteraction.update({$0.withoutEditMessage()}) + self?.chatInteraction.cancelEditing() } dismissUrlPreview = { [weak self] in self?.chatInteraction.update({ state -> ChatPresentationInterfaceState in @@ -47,10 +48,10 @@ class ChatInputAccessory: Node { } dismiss.set(image: theme.icons.dismissAccessory, for: .Normal) - dismiss.sizeToFit() + _ = dismiss.sizeToFit() + view?.addSubview(dismiss) - self.view = view } @@ -65,29 +66,58 @@ class ChatInputAccessory: Node { func update(with state:ChatPresentationInterfaceState, account:Account, animated:Bool) -> Void { + dismiss.isHidden = false + progress?.isHidden = true + + displayNode = nil dismiss.removeAllHandlers() + container.removeAllHandlers() + + if let urlPreview = state.urlPreview, state.interfaceState.composeDisableUrlPreview != urlPreview.0, let peer = state.peer, !peer.webUrlRestricted { displayNode = ChatUrlPreviewModel(account: account, webpage: urlPreview.1, url:urlPreview.0) dismiss.set(handler: { [weak self ] _ in self?.dismissUrlPreview() - }, for: .Click) - - } else if let editState = state.editState { - displayNode = EditMessageModel(message:editState.message, account:account) + }, for: .Click) + } else if let editState = state.interfaceState.editState { + displayNode = EditMessageModel(state: editState, account:account) + dismiss.isHidden = editState.loadingState != .none + progress?.isHidden = editState.loadingState == .none + updateProgress(editState.loadingState) dismiss.set(handler: { [weak self] _ in self?.dismissEdit() }, for: .Click) - } else if !state.interfaceState.forwardMessageIds.isEmpty { - displayNode = ForwardPanelModel(forwardIds:state.interfaceState.forwardMessageIds,account:account) + progress?.set(handler: { [weak self] _ in + self?.dismiss.send(event: .Click) + }, for: .Click) + + } else if !state.interfaceState.forwardMessages.isEmpty && !state.interfaceState.forwardMessageIds.isEmpty { + displayNode = ForwardPanelModel(forwardMessages:state.interfaceState.forwardMessages,account:account) dismiss.set(handler: { [weak self] _ in self?.dismissForward() }, for: .Click) + + container.set(handler: { [weak self] _ in + guard let context = self?.chatInteraction.context else { + return + } + let fwdMessages = state.interfaceState.forwardMessageIds + showModal(with: ShareModalController(ForwardMessagesObject(context, messageIds: fwdMessages, emptyPerformOnClose: true)), for: context.window) + delay(0.15, closure: { + self?.chatInteraction.update({$0.updatedInterfaceState({$0.withoutForwardMessages()})}) + }) + }, for: .Click) + } else if let replyMessageId = state.interfaceState.replyMessageId { - displayNode = ReplyModel(replyMessageId: replyMessageId, account:account) + displayNode = ReplyModel(replyMessageId: replyMessageId, account:account, replyMessage: state.interfaceState.replyMessage) dismiss.set(handler: { [weak self ] _ in self?.dismissReply() }, for: .Click) + + container.set(handler: { [weak self] _ in + self?.chatInteraction.focusMessageId(nil, replyMessageId, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + }, for: .Click) } if let displayNode = displayNode { @@ -97,6 +127,39 @@ class ChatInputAccessory: Node { } container.removeAllSubviews() displayNode?.view = container + + + + + } + + private func updateProgress(_ loadingState: EditStateLoading) { + switch loadingState { + case .none: + progress?.removeFromSuperview() + progress = nil + case .loading: + + let indicator:ProgressIndicator + if let _indicator = progress as? ProgressIndicator { + indicator = _indicator + } else { + indicator = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) + progress = indicator + view?.addSubview(indicator) + } + indicator.progressColor = theme.colors.text + case let .progress(progress): + let radial: RadialProgressView + if let _radial = self.progress as? RadialProgressView { + radial = _radial + } else { + radial = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: theme.colors.accent), twist: true, size: NSMakeSize(20, 20)) + self.progress = radial + view?.addSubview(radial) + } + radial.state = .ImpossibleFetching(progress: progress, force: false) + } } @@ -109,6 +172,7 @@ class ChatInputAccessory: Node { super.frame = newValue self.container.frame = NSMakeRect(49, 0, newValue.width, size.height) dismiss.centerY(x: 0) + progress?.centerY(x: 5) displayNode?.setNeedDisplay() } } @@ -134,6 +198,9 @@ class ChatInputAccessory: Node { } } + deinit { + } + override func setNeedDisplay() { super.setNeedDisplay() displayNode?.setNeedDisplay() diff --git a/Telegram-Mac/ChatInputActionsView.swift b/Telegram-Mac/ChatInputActionsView.swift index 7364d3b20c..a3136c1df0 100644 --- a/Telegram-Mac/ChatInputActionsView.swift +++ b/Telegram-Mac/ChatInputActionsView.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit // @@ -22,66 +23,61 @@ class ChatInputActionsView: View, Notifable { private let voice:ImageButton = ImageButton() private let muteChannelMessages:ImageButton = ImageButton() private let entertaiments:ImageButton = ImageButton() + private let slowModeTimeout:TitleButton = TitleButton() private let inlineCancel:ImageButton = ImageButton() private let keyboard:ImageButton = ImageButton() + private var scheduled:ImageButton? + private var secretTimer:ImageButton? + private var inlineProgress: ProgressIndicator? = nil + + private var prevView: View init(frame frameRect: NSRect, chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction - + self.prevView = self.send super.init(frame: frameRect) + keyboard.autohighlight = false + addSubview(keyboard) addSubview(send) addSubview(voice) addSubview(inlineCancel) addSubview(muteChannelMessages) + addSubview(slowModeTimeout) inlineCancel.isHidden = true send.isHidden = true voice.isHidden = true muteChannelMessages.isHidden = true - + slowModeTimeout.isHidden = true voice.autohighlight = false muteChannelMessages.autohighlight = false + send.autohighlight = false voice.set(handler: { [weak self] _ in + guard let `self` = self else { return } + FastSettings.toggleRecordingState() - self?.voice.set(image: FastSettings.recordingState == .voice ? theme.icons.chatRecordVoice : theme.icons.chatRecordVideo, for: .Normal) + + self.voice.set(image: FastSettings.recordingState == .voice ? theme.icons.chatRecordVoice : theme.icons.chatRecordVideo, for: .Normal) + + getAppTooltip(for: FastSettings.recordingState == .voice ? .voiceRecording : .videoRecording, callback: { value in + tooltip(for: self.voice, text: value) + }) + }, for: .Click) - voice.set(handler: { [weak self] _ in - if let peer = self?.chatInteraction.presentation.peer, peer.mediaRestricted { - alertForMediaRestriction(peer) - } - }, for: .Up) - - voice.set(handler: { [weak self] _ in - self?.stop() - }, for: .Up) - - - voice.set(handler: { [weak self] _ in - if let strongSelf = self, let peer = strongSelf.chatInteraction.presentation.peer { - if peer.mediaRestricted { - return alertForMediaRestriction(peer) - } - if strongSelf.chatInteraction.presentation.effectiveInput.inputText.isEmpty { - strongSelf.start() - } - } + voice.set(handler: { [weak self] control in + self?.chatInteraction.startRecording(false, control) }, for: .LongMouseDown) - - voice.set(handler: { [weak self] _ in - if let peer = self?.chatInteraction.presentation.peer, peer.mediaRestricted { - alertForMediaRestriction(peer) - } - }, for: .Up) + muteChannelMessages.set(handler: { [weak self] _ in if let chatInteraction = self?.chatInteraction { FastSettings.toggleChannelMessagesMuted(chatInteraction.peerId) - self?.updateLocalizationAndTheme() + (self?.superview?.superview as? View)?.updateLocalizationAndTheme(theme: theme) } }, for: .Click) @@ -106,61 +102,93 @@ class ChatInputActionsView: View, Notifable { addHoverObserver() addClickObserver() entertaiments.canHighlight = false + muteChannelMessages.hideAnimated = false - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - send.set(image: theme.icons.chatSendMessage, for: .Normal) - send.sizeToFit() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + send.set(image: self.chatInteraction.presentation.state == .editing ? theme.icons.chatSaveEditedMessage : theme.icons.chatSendMessage, for: .Normal) + _ = send.sizeToFit() voice.set(image: FastSettings.recordingState == .voice ? theme.icons.chatRecordVoice : theme.icons.chatRecordVideo, for: .Normal) - voice.sizeToFit() + _ = voice.sizeToFit() let muted = FastSettings.isChannelMessagesMuted(chatInteraction.peerId) muteChannelMessages.set(image: !muted ? theme.icons.inputChannelMute : theme.icons.inputChannelUnmute, for: .Normal) - muteChannelMessages.sizeToFit() + _ = muteChannelMessages.sizeToFit() + updateEntertainmentIcon() + keyboard.set(image: theme.icons.chatActiveReplyMarkup, for: .Normal) - keyboard.sizeToFit() + _ = keyboard.sizeToFit() inlineCancel.set(image: theme.icons.chatInlineDismiss, for: .Normal) - inlineCancel.sizeToFit() - entertaiments.set(image: chatInteraction.presentation.isEmojiSection ? theme.icons.chatEntertainment : theme.icons.chatEntertainmentSticker, for: .Normal) - entertaiments.sizeToFit() - secretTimer?.set(image: theme.icons.chatSecretTimer, for: .Normal) + _ = inlineCancel.sizeToFit() + + + if let messageSecretTimeout = chatInteraction.presentation.messageSecretTimeout { + secretTimer?.set(image: theme.chat.messageSecretTimer(shortTimeIntervalString(value: messageSecretTimeout)), for: .Normal) + } else { + secretTimer?.set(image: theme.icons.chatSecretTimer, for: .Normal) + } + + + scheduled?.set(image: theme.icons.scheduledInputAction, for: .Normal) + + slowModeTimeout.set(font: .normal(.text), for: .Normal) + slowModeTimeout.set(color: theme.colors.grayIcon, for: .Normal) + _ = self.slowModeTimeout.sizeToFit(NSZeroSize, NSMakeSize(38, 30), thatFit: true) + + } + + private func updateEntertainmentIcon() { + entertaiments.set(image: chatInteraction.presentation.isEmojiSection || chatInteraction.presentation.state == .editing ? theme.icons.chatEntertainment : theme.icons.chatEntertainmentSticker, for: .Normal) + entertaiments.setFrameSize(60, 40) + } + + var entertaimentsPopover: ViewController { + if chatInteraction.presentation.state == .editing { + let emoji = EmojiViewController(chatInteraction.context) + if let interactions = chatInteraction.context.sharedContext.bindings.entertainment().interactions { + emoji.update(with: interactions) + } + return emoji + } + return chatInteraction.context.sharedContext.bindings.entertainment() } private func addHoverObserver() { entertaiments.set(handler: { [weak self] (state) in - if let strongSelf = self { - let chatInteraction = strongSelf.chatInteraction - var enabled = false - - if let sidebarEnabled = chatInteraction.presentation.sidebarEnabled { - enabled = sidebarEnabled - } - if !((mainWindow.frame.width >= 1100 && chatInteraction.account.context.layout == .dual) || (mainWindow.frame.width >= 880 && chatInteraction.account.context.layout == .minimisize)) || !enabled { - if !hasPopover(mainWindow) { - let rect = NSMakeRect(0, 0, 350, 350) - chatInteraction.account.context.entertainment._frameRect = rect - chatInteraction.account.context.entertainment.view.frame = rect - showPopover(for: strongSelf.entertaiments, with: chatInteraction.account.context.entertainment, edge: .maxX, inset:NSMakePoint(strongSelf.frame.width - strongSelf.entertaiments.frame.maxX + 15, 10), delayBeforeShown: 0.0) - } - - } + guard let `self` = self else {return} + let chatInteraction = self.chatInteraction + var enabled = false + + if let sidebarEnabled = chatInteraction.presentation.sidebarEnabled { + enabled = sidebarEnabled + } + if !((chatInteraction.context.window.frame.width >= 1100 && chatInteraction.context.sharedContext.layout == .dual) || (mainWindow.frame.width >= 880 && chatInteraction.context.sharedContext.layout == .minimisize)) || !enabled { + self.showEntertainment() } }, for: .Hover) } + private func showEntertainment() { + let rect = NSMakeRect(0, 0, 350, min(max(chatInteraction.context.window.frame.height - 250, 300), 550)) + entertaimentsPopover._frameRect = rect + entertaimentsPopover.view.frame = rect + showPopover(for: entertaiments, with: entertaimentsPopover, edge: .maxX, inset:NSMakePoint(frame.width - entertaiments.frame.maxX + 38, 10), delayBeforeShown: 0.0) + } + private func addClickObserver() { entertaiments.set(handler: { [weak self] (state) in if let strongSelf = self { let chatInteraction = strongSelf.chatInteraction if let sidebarEnabled = chatInteraction.presentation.sidebarEnabled, sidebarEnabled { - if mainWindow.frame.width >= 1100 && chatInteraction.account.context.layout == .dual || mainWindow.frame.width >= 880 && chatInteraction.account.context.layout == .minimisize { + if mainWindow.frame.width >= 1100 && chatInteraction.context.sharedContext.layout == .dual || mainWindow.frame.width >= 880 && chatInteraction.context.sharedContext.layout == .minimisize { chatInteraction.toggleSidebar() } @@ -168,7 +196,9 @@ class ChatInputActionsView: View, Notifable { } }, for: .Click) } - + override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + } func toggleKeyboard() { let keyboardId = chatInteraction.presentation.keyboardButtonsMessage?.id @@ -180,14 +210,23 @@ class ChatInputActionsView: View, Notifable { override func layout() { super.layout() - inlineCancel.centerY(x:frame.width - inlineCancel.frame.width - iconsInset) + inlineCancel.centerY(x:frame.width - inlineCancel.frame.width - iconsInset - 6) + inlineProgress?.centerY(x: frame.width - inlineCancel.frame.width - iconsInset - 10) voice.centerY(x:frame.width - voice.frame.width - iconsInset) send.centerY(x: frame.width - send.frame.width - iconsInset) - entertaiments.centerY(x: voice.frame.minX - entertaiments.frame.width - iconsInset) - secretTimer?.centerY(x: entertaiments.frame.minX - keyboard.frame.width - iconsInset) - keyboard.centerY(x: entertaiments.frame.minX - keyboard.frame.width - iconsInset) - muteChannelMessages.centerY(x: entertaiments.frame.minX - muteChannelMessages.frame.width - iconsInset) + slowModeTimeout.centerY(x: frame.width - slowModeTimeout.frame.width - iconsInset) + entertaiments.centerY(x: voice.frame.minX - entertaiments.frame.width - 0) + secretTimer?.centerY(x: entertaiments.frame.minX - keyboard.frame.width) + keyboard.centerY(x: entertaiments.frame.minX - keyboard.frame.width) + muteChannelMessages.centerY(x: entertaiments.frame.minX - muteChannelMessages.frame.width) + if let scheduled = scheduled { + if muteChannelMessages.isHidden { + scheduled.centerY(x: (keyboard.isHidden ? entertaiments.frame.minX : keyboard.frame.minX) - scheduled.frame.width) + } else { + scheduled.centerY(x: muteChannelMessages.frame.minX - scheduled.frame.width - iconsInset) + } + } } func stop() { @@ -215,48 +254,72 @@ class ChatInputActionsView: View, Notifable { return false } - func start() { - let state: ChatRecordingState - - switch FastSettings.recordingState { - case .voice: - state = ChatRecordingAudioState() - state.start() - case .video: - state = ChatRecordingVideoState() - showModal(with: VideoRecorderModalController(chatInteraction: chatInteraction, pipeline: (state as! ChatRecordingVideoState).pipeline), for: mainWindow) + var currentActionView: NSView { + if !self.send.isHidden { + return self.send + } else if !self.voice.isHidden { + return self.voice + } else if !self.slowModeTimeout.isHidden { + return self.slowModeTimeout + } else { + return self } - - chatInteraction.update({$0.withRecordingState(state)}) } + private var first:Bool = true func notify(with value: Any, oldValue: Any, animated:Bool) { if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { - if value.interfaceState != oldValue.interfaceState || value.editState != oldValue.editState || !animated || value.inputQueryResult != oldValue.inputQueryResult || value.inputContext != oldValue.inputContext || value.sidebarEnabled != oldValue.sidebarEnabled || value.sidebarShown != oldValue.sidebarShown || value.layout != oldValue.layout { + if value.interfaceState != oldValue.interfaceState || !animated || value.inputQueryResult != oldValue.inputQueryResult || value.inputContext != oldValue.inputContext || value.sidebarEnabled != oldValue.sidebarEnabled || value.sidebarShown != oldValue.sidebarShown || value.layout != oldValue.layout || value.isKeyboardActive != oldValue.isKeyboardActive || value.isKeyboardShown != oldValue.isKeyboardShown || value.slowMode != oldValue.slowMode || value.hasScheduled != oldValue.hasScheduled || value.messageSecretTimeout != oldValue.messageSecretTimeout { - var size:NSSize = NSMakeSize(send.frame.width + iconsInset + entertaiments.frame.width + iconsInset * 2, frame.height) + var size:NSSize = NSMakeSize(send.frame.width + iconsInset + entertaiments.frame.width, frame.height) if chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat { size.width += theme.icons.chatSecretTimer.backingSize.width + iconsInset } + send.animates = false + send.set(image: value.state == .editing ? theme.icons.chatSaveEditedMessage : theme.icons.chatSendMessage, for: .Normal) + send.animates = true + + if let messageSecretTimeout = value.messageSecretTimeout { + secretTimer?.set(image: theme.chat.messageSecretTimer(shortTimeIntervalString(value: messageSecretTimeout)), for: .Normal) + } else if value.messageSecretTimeout == nil { + secretTimer?.set(image: theme.icons.chatSecretTimer, for: .Normal) + } if let peer = value.peer { - muteChannelMessages.isHidden = !peer.isChannel || !peer.canSendMessage + muteChannelMessages.isHidden = !peer.isChannel || !peer.canSendMessage || !value.effectiveInput.inputText.isEmpty || value.interfaceState.editState != nil } if !muteChannelMessages.isHidden { - size.width += muteChannelMessages.frame.width + iconsInset + size.width += muteChannelMessages.frame.width } var newInlineRequest = value.inputQueryResult != oldValue.inputQueryResult var oldInlineRequest = newInlineRequest + var newInlineLoading: Bool = false + var oldInlineLoading: Bool = false + + if let query = value.inputQueryResult, case .contextRequestResult(_, let data) = query { + newInlineLoading = data == nil && !value.effectiveInput.inputText.isEmpty + } + + if let query = value.inputQueryResult, case .contextRequestResult = query, newInlineRequest || first { newInlineRequest = true } else { newInlineRequest = false } + + + if let query = oldValue.inputQueryResult, case .contextRequestResult(_, let data) = query { + oldInlineLoading = data == nil + } + + let newSlowModeCounter: Bool = value.slowMode?.timeout != nil && value.interfaceState.editState == nil && !newInlineLoading && !newInlineRequest + let oldSlowModeCounter: Bool = oldValue.slowMode?.timeout != nil && oldValue.interfaceState.editState == nil && !oldInlineLoading && !oldInlineRequest + if let query = oldValue.inputQueryResult, case .contextRequestResult = query, oldInlineRequest || first { oldInlineRequest = true @@ -264,27 +327,32 @@ class ChatInputActionsView: View, Notifable { oldInlineRequest = false } +// newInlineLoading = newInlineLoading && newInlineRequest +// oldInlineLoading = oldInlineLoading && oldInlineRequest + + let sNew = !value.effectiveInput.inputText.isEmpty || !value.interfaceState.forwardMessageIds.isEmpty || value.state == .editing let sOld = !oldValue.effectiveInput.inputText.isEmpty || !oldValue.interfaceState.forwardMessageIds.isEmpty || oldValue.state == .editing - let anim = animated && (sNew != sOld || newInlineRequest != oldInlineRequest) - if sNew != sOld || first || newInlineRequest != oldInlineRequest { + if sNew != sOld || first || newInlineRequest != oldInlineRequest || oldInlineLoading != newInlineLoading || newSlowModeCounter != oldSlowModeCounter { first = false - let prevView:View + let prevView:View = self.prevView let newView:View - if newInlineRequest { - prevView = !sOld ? voice : send + if newSlowModeCounter { + newView = slowModeTimeout + } else if newInlineRequest { newView = inlineCancel } else if oldInlineRequest { - prevView = inlineCancel newView = sNew ? send : voice } else { - prevView = sNew ? voice : send newView = sNew ? send : voice } + self.prevView = newView + + let anim = animated && prevView != newView newView.isHidden = false newView.layer?.opacity = 1.0 @@ -297,13 +365,46 @@ class ChatInputActionsView: View, Notifable { prevView.isHidden = true } }) - } else { + } else if prevView != newView { prevView.isHidden = true + } else { + prevView.isHidden = false + prevView.layer?.opacity = 1.0 } } + inlineCancel.isHidden = inlineCancel.isHidden || newInlineLoading + + if newInlineLoading { + if inlineProgress == nil { + inlineProgress = ProgressIndicator(frame: NSMakeRect(0, 0, 22, 22)) + inlineProgress?.progressColor = theme.colors.grayIcon + addSubview(inlineProgress!, positioned: .below, relativeTo: inlineCancel) + inlineProgress?.set(handler: { [weak self] _ in + if let inputContext = self?.chatInteraction.presentation.inputContext, case let .contextRequest(request) = inputContext { + if request.query.isEmpty { + self?.chatInteraction.clearInput() + } else { + self?.chatInteraction.clearContextQuery() + } + } + }, for: .Click) + } + } else { + if let inlineProgress = inlineProgress { + self.inlineProgress = nil + if animated { + inlineProgress.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak inlineProgress] _ in + inlineProgress?.removeFromSuperview() + }) + } else { + inlineProgress.removeFromSuperview() + } + } + } + entertaiments.apply(state: .Normal) - entertaiments.isSelected = value.isShowSidebar || (chatInteraction.account.context.entertainment.popover?.isShown ?? false) + entertaiments.isSelected = value.isShowSidebar keyboard.isHidden = !value.isKeyboardActive @@ -318,13 +419,35 @@ class ChatInputActionsView: View, Notifable { } } - self.change(size: size, animated: false) + if let slowMode = value.slowMode, let timeout = slowMode.timeout, timeout >= 0 { + let minutes = timeout / 60 + let seconds = timeout % 60 + let string = String(format: "%@:%@", minutes < 10 ? "0\(minutes)" : "\(minutes)", seconds < 10 ? "0\(seconds)" : "\(seconds)") + self.slowModeTimeout.set(text: string, for: .Normal) + } - + if value.hasScheduled && value.effectiveInput.inputText.isEmpty && value.interfaceState.editState == nil { + if scheduled == nil { + scheduled = ImageButton() + addSubview(scheduled!) + } + scheduled?.removeAllHandlers() + scheduled?.set(handler: { [weak self] _ in + self?.chatInteraction.openScheduledMessages() + }, for: .Click) + scheduled!.set(image: theme.icons.chatInputScheduled, for: .Normal) + _ = scheduled!.sizeToFit() + size.width += scheduled!.frame.width + iconsInset + (muteChannelMessages.isHidden ? 0 : iconsInset) + } else { + scheduled?.removeFromSuperview() + scheduled = nil + } - self.needsLayout = true + setFrameSize(size) + updateEntertainmentIcon() + needsLayout = true } else if value.isEmojiSection != oldValue.isEmojiSection { - entertaiments.set(image: value.isEmojiSection ? theme.icons.chatEntertainment : theme.icons.chatEntertainmentSticker, for: .Normal) + updateEntertainmentIcon() } } } @@ -341,17 +464,60 @@ class ChatInputActionsView: View, Notifable { } func prepare(with chatInteraction:ChatInteraction) -> Void { - send.set(handler: { _ in - chatInteraction.sendMessage() + + let handler:(Control)->Void = { [weak chatInteraction] control in + if let chatInteraction = chatInteraction, let peer = chatInteraction.peer, !peer.isSecretChat { + let context = chatInteraction.context + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + return + } + if chatInteraction.presentation.state != .normal { + return + } + var items:[SPopoverItem] = [] + + if peer.id != chatInteraction.context.account.peerId { + items.append(SPopoverItem(L10n.chatSendWithoutSound, { [weak chatInteraction] in + chatInteraction?.sendMessage(true, nil) + })) + } + switch chatInteraction.mode { + case .history: + items.append(SPopoverItem(peer.id == chatInteraction.context.peerId ? L10n.chatSendSetReminder : L10n.chatSendScheduledMessage, { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak chatInteraction] date in + chatInteraction?.sendMessage(false, date) + }), for: context.window) + })) + case .scheduled: + break + } + + if !items.isEmpty { + showPopover(for: control, with: SPopoverViewController(items: items)) + } + } + } + + send.set(handler: handler, for: .RightDown) + send.set(handler: handler, for: .LongMouseDown) + + + send.set(handler: { [weak chatInteraction] control in + chatInteraction?.sendMessage(false, nil) + }, for: .Click) + + slowModeTimeout.set(handler: { [weak chatInteraction] control in + if let slowMode = chatInteraction?.presentation.slowMode { + showSlowModeTimeoutTooltip(slowMode, for: control) + } }, for: .Click) chatInteraction.add(observer: self) - notify(with: chatInteraction.presentation, oldValue: chatInteraction.presentation, animated: false) if chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat { secretTimer = ImageButton() secretTimer?.set(image: theme.icons.chatSecretTimer, for: .Normal) - secretTimer?.sizeToFit() + _ = secretTimer?.sizeToFit() addSubview(secretTimer!) secretTimer?.set(handler: { [weak self] control in @@ -360,6 +526,8 @@ class ChatInputActionsView: View, Notifable { } }, for: .Click) } + + notify(with: chatInteraction.presentation, oldValue: chatInteraction.presentation, animated: false) } func performSendMessage() { @@ -381,7 +549,7 @@ class ChatInputActionsView: View, Notifable { if let peer = chatInteraction.presentation.peer as? TelegramSecretChat { if peer.messageAutoremoveTimeout != nil { - items.append(SPopoverItem(tr(.secretTimerOff), { [weak self] in + items.append(SPopoverItem(tr(L10n.secretTimerOff), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(nil) })) } @@ -390,24 +558,24 @@ class ChatInputActionsView: View, Notifable { for i in 0 ..< 30 { - items.append(SPopoverItem(tr(.timerSecondsCountable(i + 1)), { [weak self] in + items.append(SPopoverItem(tr(L10n.timerSecondsCountable(i + 1)), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(Int32(i + 1)) })) } - items.append(SPopoverItem(tr(.timerMinutesCountable(1)), { [weak self] in + items.append(SPopoverItem(tr(L10n.timerMinutesCountable(1)), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(60) })) - items.append(SPopoverItem(tr(.timerHoursCountable(1)), { [weak self] in + items.append(SPopoverItem(tr(L10n.timerHoursCountable(1)), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(60 * 60) })) - items.append(SPopoverItem(tr(.timerDaysCountable(1)), { [weak self] in + items.append(SPopoverItem(tr(L10n.timerDaysCountable(1)), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(60 * 60 * 24) })) - items.append(SPopoverItem(tr(.timerWeeksCountable(1)), { [weak self] in + items.append(SPopoverItem(tr(L10n.timerWeeksCountable(1)), { [weak self] in self?.chatInteraction.setSecretChatMessageAutoremoveTimeout(60 * 60 * 24 * 7) })) diff --git a/Telegram-Mac/ChatInputAttachView.swift b/Telegram-Mac/ChatInputAttachView.swift index 60ef4bf23f..838c39238b 100644 --- a/Telegram-Mac/ChatInputAttachView.swift +++ b/Telegram-Mac/ChatInputAttachView.swift @@ -8,15 +8,19 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox -class ChatInputAttachView: ImageButton { - +class ChatInputAttachView: ImageButton, Notifable { + + + private var chatInteraction:ChatInteraction private var controller:SPopoverViewController? + private let editMediaAccessory: ImageView = ImageView() init(frame frameRect: NSRect, chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction super.init(frame: frameRect) @@ -27,107 +31,182 @@ class ChatInputAttachView: ImageButton { updateLayout() - set(handler: { (event) in - - }, for: .Click) - let attachPhotoOrVideo = { [weak self] in - if let strongSelf = self, let window = strongSelf.kitWindow { - filePanel(with:mediaExts, for:window, completion:{(result) in - if let result = result { - let previous = result.count - - let result = result.filter { path -> Bool in - if let size = fileSize(path) { - return size <= 1500000000 - } - return false - } - - let afterSizeCheck = result.count - - if afterSizeCheck == 0 && previous != afterSizeCheck { - alert(for: mainWindow, header: appName, info: tr(.appMaxFileSize)) - } else { - strongSelf.chatInteraction.showPreviewSender(result.map{URL(fileURLWithPath: $0)}, true) - } - } - }) - } - } - - set(handler: { [weak self] (state) in - if let strongSelf = self, let peer = strongSelf.chatInteraction.presentation.peer { + set(handler: { [weak self] control in + + guard let `self` = self else {return} + if let peer = chatInteraction.presentation.peer { - if let peer = peer as? TelegramChannel { - if peer.hasBannedRights(.banSendMedia) { + var items:[SPopoverItem] = [] + if let editState = chatInteraction.presentation.interfaceState.editState, let media = editState.originalMedia, media is TelegramMediaFile || media is TelegramMediaImage { + + items.append(SPopoverItem(L10n.inputAttachPopoverPhotoOrVideo, { [weak self] in + self?.chatInteraction.updateEditingMessageMedia(mediaExts, true) + }, theme.icons.chatAttachPhoto)) + + if editState.message.groupingKey == nil { + items.append(SPopoverItem(L10n.inputAttachPopoverFile, { [weak self] in + self?.chatInteraction.updateEditingMessageMedia(nil, false) + }, theme.icons.chatAttachFile)) + } + + if media is TelegramMediaImage { + items.append(SPopoverItem(L10n.editMessageEditCurrentPhoto, { [weak self] in + self?.chatInteraction.editEditingMessagePhoto(media as! TelegramMediaImage) + }, theme.icons.editMessageCurrentPhoto)) + } + + + } else if chatInteraction.presentation.interfaceState.editState == nil { + + if let slowMode = self.chatInteraction.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: control) return } - } - - let items = [SPopoverItem(tr(.inputAttachPopoverPhotoOrVideo), { - attachPhotoOrVideo() - }, theme.icons.chatAttachPhoto), SPopoverItem(tr(.inputAttachPopoverPicture), { [weak strongSelf] in - if let strongSelf = strongSelf, let window = strongSelf.kitWindow { - pickImage(for: window, completion: { (image) in - if let image = image { - strongSelf.chatInteraction.mediaPromise.set(putToTemp(image: image) |> map({[MediaSenderContainer(path:$0)]})) - } - }) + + items.append(SPopoverItem(L10n.inputAttachPopoverPhotoOrVideo, { [weak self] in + if let permissionText = permissionText(from: peer, for: .banSendMedia) { + alert(for: mainWindow, info: permissionText) + return + } + self?.chatInteraction.attachPhotoOrVideo() + }, theme.icons.chatAttachPhoto)) + + items.append(SPopoverItem(L10n.inputAttachPopoverPicture, { [weak self] in + guard let `self` = self else {return} + if let permissionText = permissionText(from: peer, for: .banSendMedia) { + alert(for: mainWindow, info: permissionText) + return + } + self.chatInteraction.attachPicture() + }, theme.icons.chatAttachCamera)) + + var canAttachPoll: Bool = false + if let peer = chatInteraction.presentation.peer, peer.isGroup || peer.isSupergroup { + canAttachPoll = true + } + if let peer = chatInteraction.presentation.mainPeer, peer.isBot { + canAttachPoll = true } - }, theme.icons.chatAttachCamera), SPopoverItem(tr(.inputAttachPopoverFile), { [weak strongSelf] in - if let strongSelf = strongSelf, let window = strongSelf.kitWindow { - filePanel(for:window, completion:{(result) in - if let result = result { - - let previous = result.count - - let result = result.filter { path -> Bool in - if let size = fileSize(path) { - return size <= 1500000000 - } - return false - } - - let afterSizeCheck = result.count - - if afterSizeCheck == 0 && previous != afterSizeCheck { - alert(for: mainWindow, header: appName, info: tr(.appMaxFileSize)) - } else { - strongSelf.chatInteraction.showPreviewSender(result.map{URL(fileURLWithPath: $0)}, false) - } - + if let peer = chatInteraction.presentation.peer as? TelegramChannel { + if peer.hasPermission(.sendMessages) { + canAttachPoll = true + } + } + if canAttachPoll && permissionText(from: peer, for: .banSendPolls) != nil { + canAttachPoll = false + } + + if canAttachPoll { + items.append(SPopoverItem(L10n.inputAttachPopoverPoll, { [weak self] in + guard let `self` = self else {return} + if let permissionText = permissionText(from: peer, for: .banSendPolls) { + alert(for: mainWindow, info: permissionText) + return } - }) + showModal(with: NewPollController(chatInteraction: self.chatInteraction), for: mainWindow) + }, theme.icons.chatAttachPoll)) } - }, theme.icons.chatAttachFile)] + + items.append(SPopoverItem(L10n.inputAttachPopoverFile, { [weak self] in + if let permissionText = permissionText(from: peer, for: .banSendMedia) { + alert(for: mainWindow, info: permissionText) + return + } + self?.chatInteraction.attachFile(false) + }, theme.icons.chatAttachFile)) + + items.append(SPopoverItem(L10n.inputAttachPopoverLocation, { [weak self] in + self?.chatInteraction.attachLocation() + }, theme.icons.chatAttachLocation)) + } - strongSelf.controller = SPopoverViewController(items: items) - showPopover(for: strongSelf, with: strongSelf.controller!, edge: nil, inset: NSMakePoint(0,0)) + + if !items.isEmpty { + self.controller = SPopoverViewController(items: items, visibility: 10) + showPopover(for: self, with: self.controller!, edge: nil, inset: NSMakePoint(0,0)) + } + } }, for: .Hover) - set(handler: { [weak self] _ in - if let peer = self?.chatInteraction.presentation.peer { - if peer.mediaRestricted { - alertForMediaRestriction(peer) + set(handler: { [weak self] control in + guard let `self` = self else {return} + + if let editState = chatInteraction.presentation.interfaceState.editState { + return + } + + if let peer = self.chatInteraction.presentation.peer { + if let permissionText = permissionText(from: peer, for: .banSendMedia) { + alert(for: mainWindow, info: permissionText) return } - self?.controller?.popover?.hide() + self.controller?.popover?.hide() Queue.mainQueue().justDispatch { - attachPhotoOrVideo() + if self.chatInteraction.presentation.interfaceState.editState != nil { + self.chatInteraction.updateEditingMessageMedia(nil, true) + } else { + self.chatInteraction.attachFile(true) + } } - } - - }, for: .Click) + chatInteraction.add(observer: self) + addSubview(editMediaAccessory) + editMediaAccessory.layer?.opacity = 0 + updateLocalizationAndTheme(theme: theme) + } + + func isEqual(to other: Notifable) -> Bool { + if let view = other as? ChatInputAttachView { + return view === self + } else { + return false + } + } + + func notify(with value: Any, oldValue: Any, animated: Bool) { + let value = value as? ChatPresentationInterfaceState + let oldValue = oldValue as? ChatPresentationInterfaceState + + if value?.interfaceState.editState != oldValue?.interfaceState.editState { + if let editState = value?.interfaceState.editState { + let isMedia = editState.message.media.first is TelegramMediaFile || editState.message.media.first is TelegramMediaImage + editMediaAccessory.change(opacity: isMedia ? 1 : 0) + self.highlightHovered = isMedia + self.autohighlight = isMedia + } else { + editMediaAccessory.change(opacity: 0) + self.highlightHovered = true + self.autohighlight = true + } + } + +// if let slowMode = value?.slowMode { +// if slowMode.hasError { +// self.highlightHovered = false +// self.autohighlight = false +// } +// } + } + + override func layout() { + super.layout() + editMediaAccessory.setFrameOrigin(46 - editMediaAccessory.frame.width, 23) + } + + deinit { + chatInteraction.remove(observer: self) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + editMediaAccessory.image = theme.icons.editMessageMedia + editMediaAccessory.sizeToFit() set(image: theme.icons.chatAttach, for: .Normal) } diff --git a/Telegram-Mac/ChatInputRecordingView.swift b/Telegram-Mac/ChatInputRecordingView.swift index ad66b40c68..562c11a0a6 100644 --- a/Telegram-Mac/ChatInputRecordingView.swift +++ b/Telegram-Mac/ChatInputRecordingView.swift @@ -8,7 +8,7 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit enum ChatInputRecodingState { case none case recoding(TimeInterval) @@ -18,27 +18,23 @@ enum ChatInputRecodingState { class ChatInputRecordingView: View { - let descView:TextView = TextView() - let timerView:TextView = TextView() - let peakLayer:CALayer = CALayer() + private let descView:TextView = TextView() + private let timerView:TextView = TextView() + private let statusImage:ImageView = ImageView() + private let recView: View = View(frame: NSMakeRect(0, 0, 14, 14)) - let statusImage:ImageView = ImageView() + private let chatInteraction:ChatInteraction + private let recorder:ChatRecordingState - var state:ChatInputRecodingState = .none - let chatInteraction:ChatInteraction - let recorder:ChatRecordingState - var inside:Bool = false - var currentLevel:CGFloat = 1 - - let disposable:MetaDisposable = MetaDisposable() + private let disposable:MetaDisposable = MetaDisposable() + private let overlayController: ChatRecorderOverlayWindowController init(frame frameRect: NSRect, chatInteraction:ChatInteraction, recorder:ChatRecordingState) { self.chatInteraction = chatInteraction self.recorder = recorder + overlayController = ChatRecorderOverlayWindowController(parent: mainWindow, chatInteraction: chatInteraction) super.init(frame: frameRect) - peakLayer.frame = NSMakeRect(0, 0, 14, 14) - peakLayer.cornerRadius = peakLayer.frame.width / 2 statusImage.image = FastSettings.recordingState == .voice ? theme.icons.chatVoiceRecording : theme.icons.chatVideoRecording statusImage.animates = true @@ -46,62 +42,59 @@ class ChatInputRecordingView: View { - layer?.addSublayer(peakLayer) + // layer?.addSublayer(peakLayer) addSubview(descView) addSubview(timerView) - addSubview(statusImage) + // addSubview(statusImage) + addSubview(recView) + + recView.layer?.cornerRadius = recView.frame.width / 2 - disposable.set((combineLatest(recorder.micLevel, recorder.status) |> deliverOnMainQueue).start(next: { [weak self] (micLevel, state) in + disposable.set(combineLatest(recorder.status |> deliverOnMainQueue, recorder.holdpromise.get() |> deliverOnMainQueue).start(next: { [weak self] state, hold in if case let .recording(duration) = state { - self?.update(duration, CGFloat(micLevel), true) + self?.update(duration, true, hold) } })) - updateLocalizationAndTheme() + + + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) statusImage.image = FastSettings.recordingState == .voice ? theme.icons.chatVoiceRecording : theme.icons.chatVideoRecording backgroundColor = theme.colors.background descView.backgroundColor = theme.colors.background timerView.backgroundColor = theme.colors.background - peakLayer.backgroundColor = theme.colors.redUI.cgColor - + recView.backgroundColor = theme.colors.accent } override func viewWillMove(toWindow newWindow: NSWindow?) { - if let window = newWindow as? Window { - window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in - self?.updateInside() - return .rejected - }, with: self, for: .leftMouseDragged, priority: .modal) + if let _ = newWindow as? Window { + overlayController.show(animated: true) + let animate = CABasicAnimation(keyPath: "opacity") + animate.fromValue = 1.0 + animate.toValue = 0.3 + animate.repeatCount = 10000 + animate.duration = 1.5 + + animate.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + recView.layer?.add(animate, forKey: "opacity") } else { (window as? Window)?.removeAllHandlers(for: self) + overlayController.hide(animated: true) + recView.layer?.removeAllAnimations() } } override func viewDidMoveToWindow() { - update(0, 0, false) + update(0, false, false) } - private func updateInside() { - guard let superview = superview, let window = window else { - return; - } - let mouse = superview.convert(window.mouseLocationOutsideOfEventStream, from: nil) - let inside = mouse.x > 0 && mouse.y > 0 && (mouse.x < superview.frame.width && mouse.y < superview.frame.height) - - - if inside != self.inside { - self.inside = inside - let descLayout = TextViewLayout(.initialize(string:tr(.audioRecordReleaseOut), color: inside ? theme.colors.text : theme.colors.redUI, font: .normal(.text)), maximumNumberOfLines: 2, truncationType: .middle, alignment: .center) - descLayout.measure(width: frame.width - 50 - 100 - 60) - descView.update(descLayout) - } - } - func update(_ duration:TimeInterval, _ peakLevel:CGFloat, _ animated:Bool) { + func update(_ duration:TimeInterval, _ animated:Bool, _ hold: Bool) { let intDuration:Int = Int(duration) let ms = duration - TimeInterval(intDuration); @@ -110,20 +103,9 @@ class ChatInputRecordingView: View { timerLayout.measure(width: .greatestFiniteMagnitude) timerView.update(timerLayout) - - updateInside() - - - //let scale = min(max(currentLevel * 0.8 + peakLevel * 0.2,1),2); - let power = min(mappingRange(Double(peakLevel), 0, 1, 1, 1.5),1.5); - // mappingRange(<#T##x: Double##Double#>, <#T##in_min: Double##Double#>, <#T##in_max: Double##Double#>, <#T##out_min: Double##Double#>, <#T##out_max: Double##Double#>) - - - //if peakLayer.presentation()?.animation(forKey: "transform") == nil { - peakLayer.animateScale(from:currentLevel, to: CGFloat(power), duration: 0.1, removeOnCompletion:false) - self.currentLevel = CGFloat(power) - // } - + let descLayout = TextViewLayout(.initialize(string: hold ? L10n.audioRecordHelpFixed : L10n.audioRecordHelpPlain, color: theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 2, truncationType: .middle, alignment: .center) + descLayout.measure(width: frame.width - 50 - 100 - 60) + descView.update(descLayout) self.needsLayout = true @@ -143,12 +125,12 @@ class ChatInputRecordingView: View { override func layout() { super.layout() - peakLayer.frame = NSMakeRect(20, floorToScreenPixels((frame.height - peakLayer.frame.height) / 2), 14, 14) - timerView.centerY(x:peakLayer.frame.maxX + 10) + recView.centerY(x: 20) + timerView.centerY(x: recView.frame.maxX + 10) statusImage.centerY(x: frame.width - statusImage.frame.width - 20) let max = (frame.width - (statusImage.frame.width + 20 + 50)) - descView.centerY(x:60 + floorToScreenPixels((max - descView.frame.width)/2)) + descView.centerY(x:60 + floorToScreenPixels(backingScaleFactor, (max - descView.frame.width)/2)) } diff --git a/Telegram-Mac/ChatInputView.swift b/Telegram-Mac/ChatInputView.swift index b3ce755004..fe918acea3 100644 --- a/Telegram-Mac/ChatInputView.swift +++ b/Telegram-Mac/ChatInputView.swift @@ -5,12 +5,12 @@ // Created by keepcoder on 24/09/2016. // Copyright © 2016 Telegram. All rights reserved. // - import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox @@ -21,12 +21,12 @@ protocol ChatInputDelegate : class { let yInset:CGFloat = 8; -class ChatInputView: Control, TGModernGrowingDelegate, Notifable { +class ChatInputView: View, TGModernGrowingDelegate, Notifable { private let sendActivityDisposable = MetaDisposable() public let ready = Promise() - + weak var delegate:ChatInputDelegate? let accessoryDispose:MetaDisposable = MetaDisposable() @@ -34,7 +34,6 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { var chatInteraction:ChatInteraction var accessory:ChatInputAccessory! - var account:Account! private var _ts:View! @@ -47,17 +46,19 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { private var messageActionsPanelView:MessageActionsPanelView? private var recordingPanelView:ChatInputRecordingView? private var blockedActionView:TitleButton? + private var chatDiscussionView: ChannelDiscussionInputView? private var restrictedView:RestrictionWrappedView? //views private(set) var textView:TGModernGrowingTextView! private var actionsView:ChatInputActionsView! - private var attachView:ChatInputAttachView! + private(set) var attachView:ChatInputAttachView! - private let emojiReplacementDisposable:MetaDisposable = MetaDisposable() - + + + private let slowModeUntilDisposable = MetaDisposable() private var replyMarkupModel:ReplyMarkupNode? override var isFlipped: Bool { @@ -67,12 +68,18 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { private var standart:CGFloat = 50.0 private var bottomHeight:CGFloat = 0 + static let bottomPadding:CGFloat = 10 + static let maxBottomHeight = ReplyMarkupNode.rowHeight * 3 + ReplyMarkupNode.buttonHeight / 2 + + + private let rtfAttachmentsDisposable = MetaDisposable() + init(frame frameRect: NSRect, chatInteraction:ChatInteraction) { self.chatInteraction = chatInteraction super.init(frame: frameRect) self.animates = true - + _ts = View(frame: NSMakeRect(0, 0, NSWidth(frameRect), .borderSize)) _ts.backgroundColor = .border; @@ -84,7 +91,6 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { actionsView = ChatInputActionsView(frame: NSMakeRect(contentView.frame.width - 100, 0, 100, contentView.frame.height), chatInteraction:chatInteraction); - contentView.addSubview(actionsView) attachView = ChatInputAttachView(frame: NSMakeRect(0, 0, 60, contentView.frame.height), chatInteraction:chatInteraction) contentView.addSubview(attachView) @@ -94,50 +100,47 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { textView = TGModernGrowingTextView(frame: NSMakeRect(attachView.frame.width, yInset, contentView.frame.width - actionsView.frame.width, contentView.frame.height - yInset * 2.0)) textView.textFont = .normal(.text) + contentView.addSubview(textView) + contentView.addSubview(actionsView) self.background = theme.colors.background accessory = ChatInputAccessory(accessoryView, chatInteraction:chatInteraction) self.addSubview(accessoryView) - + self.addSubview(contentView) self.addSubview(bottomView) - + bottomView.documentView = View() - + self.addSubview(_ts) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } public override var responder:NSResponder? { - return textView + return textView.inputView } - func updateInterface(with interaction:ChatInteraction, account:Account) -> Void { + func updateInterface(with interaction:ChatInteraction) -> Void { self.chatInteraction = interaction - self.account = account - actionsView.prepare(with: chatInteraction) - - needUpdateChatState(with: chatState, false) needUpdateReplyMarkup(with: interaction.presentation, false) - - setFrameSize(frame.size) + setFrameSize(frame.size) textView.textColor = theme.colors.text textView.linkColor = theme.colors.link - textView.textFont = .normal(.custom(theme.fontSize)) + textView.textFont = .normal(CGFloat(theme.fontSize)) - updateInput(interaction.presentation, prevState: ChatPresentationInterfaceState(),false) - textView.setPlaceholderAttributedString(.initialize(string: textPlaceholder, color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize)), coreText: false), update: false) + updateInput(interaction.presentation, prevState: ChatPresentationInterfaceState(interaction.chatLocation), false) + textView.setPlaceholderAttributedString(.initialize(string: textPlaceholder, color: theme.colors.grayText, font: NSFont.normal(theme.fontSize), coreText: false), update: false) textView.delegate = self - + updateAdditions(interaction.presentation, false) - + chatInteraction.add(observer: self) ready.set(accessory.nodeReady.get() |> map {_ in return true} |> take(1) ) } @@ -145,15 +148,22 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { private var textPlaceholder: String { if let peer = chatInteraction.presentation.peer { if peer.isChannel { - return tr(.messagesPlaceholderBroadcast) + if textView.frame.width < 150 { + return L10n.messagesPlaceholderBroadcastSmall + } + return FastSettings.isChannelMessagesMuted(peer.id) ? L10n.messagesPlaceholderSilentBroadcast : L10n.messagesPlaceholderBroadcast } } - return tr(.messagesPlaceholderSentMessage) + if textView.frame.width < 150 { + return L10n.messagesPlaceholderSentMessageSmall + } + return L10n.messagesPlaceholderSentMessage } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - textView.setPlaceholderAttributedString(.initialize(string: textPlaceholder, color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize)), coreText: false), update: false) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + textView.setPlaceholderAttributedString(.initialize(string: textPlaceholder, color: theme.colors.grayText, font: NSFont.normal(theme.fontSize), coreText: false), update: false) _ts.backgroundColor = theme.colors.border backgroundColor = theme.colors.background contentView.backgroundColor = theme.colors.background @@ -161,32 +171,32 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { textView.textColor = theme.colors.text actionsView.backgroundColor = theme.colors.background blockedActionView?.disableActions() - textView.textFont = .normal(.custom(theme.fontSize)) - - blockedActionView?.style = ControlStyle(font: .normal(.title), foregroundColor: theme.colors.blueUI,backgroundColor: theme.colors.background, highlightColor: theme.colors.grayBackground) + textView.textFont = .normal(theme.fontSize) + chatDiscussionView?.updateLocalizationAndTheme(theme: theme) + blockedActionView?.style = ControlStyle(font: .normal(.title), foregroundColor: theme.colors.accent,backgroundColor: theme.colors.background, highlightColor: theme.colors.grayBackground) bottomView.backgroundColor = theme.colors.background bottomView.documentView?.background = theme.colors.background replyMarkupModel?.layout() + accessory.update(with: chatInteraction.presentation, account: chatInteraction.context.account, animated: false) accessoryView.backgroundColor = theme.colors.background accessory.container.backgroundColor = theme.colors.background + textView.setBackgroundColor(theme.colors.background) + } func notify(with value: Any, oldValue:Any, animated:Bool) { if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { - if value.effectiveInput != oldValue.effectiveInput { updateInput(value, prevState: oldValue, animated) } - - updateAttachments(value.interfaceState,animated) var urlPreviewChanged:Bool if value.urlPreview?.0 != oldValue.urlPreview?.0 { urlPreviewChanged = true } else if let valuePreview = value.urlPreview?.1, let oldValuePreview = oldValue.urlPreview?.1 { - urlPreviewChanged = !valuePreview.isEqual(oldValuePreview) + urlPreviewChanged = !valuePreview.isEqual(to: oldValuePreview) } else if (value.urlPreview?.1 == nil) != (oldValue.urlPreview?.1 == nil) { urlPreviewChanged = true } else { @@ -196,7 +206,7 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { urlPreviewChanged = urlPreviewChanged || value.interfaceState.composeDisableUrlPreview != oldValue.interfaceState.composeDisableUrlPreview - if value.interfaceState.forwardMessageIds != oldValue.interfaceState.forwardMessageIds || value.interfaceState.replyMessageId != oldValue.interfaceState.replyMessageId || value.editState?.message.id != oldValue.editState?.message.id || urlPreviewChanged { + if !isEqualMessageList(lhs: value.interfaceState.forwardMessages, rhs: oldValue.interfaceState.forwardMessages) || value.interfaceState.forwardMessageIds != oldValue.interfaceState.forwardMessageIds || value.interfaceState.replyMessageId != oldValue.interfaceState.replyMessageId || value.interfaceState.editState != oldValue.interfaceState.editState || urlPreviewChanged { updateAdditions(value,animated) } @@ -222,17 +232,20 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { needUpdateReplyMarkup(with: value, animated) textViewHeightChanged(defaultContentHeight, animated: animated) } + update() } } + func needUpdateReplyMarkup(with state:ChatPresentationInterfaceState, _ animated:Bool) { if let keyboardMessage = state.keyboardButtonsMessage, let attribute = keyboardMessage.replyMarkup, state.isKeyboardShown { - replyMarkupModel = ReplyMarkupNode(attribute.rows, attribute.flags, chatInteraction.processBotKeyboard(with: keyboardMessage), bottomView.documentView as? View) + replyMarkupModel = ReplyMarkupNode(attribute.rows, attribute.flags, chatInteraction.processBotKeyboard(with: keyboardMessage), bottomView.documentView as? View, true) replyMarkupModel?.measureSize(frame.width - 40) replyMarkupModel?.redraw() replyMarkupModel?.layout() - } + bottomView.contentView.scroll(to: NSZeroPoint) + } } func isEqual(to other: Notifable) -> Bool { @@ -247,30 +260,27 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { } var defaultContentHeight:CGFloat { - return chatState == .normal || chatState == .editing ? textView.frame.height : CGFloat(textView.min_height) } - - func needUpdateChatState(with state:ChatState, _ animated:Bool) -> Void { - CATransaction.begin() - if animated { textViewHeightChanged(defaultContentHeight, animated: animated) } - + recordingPanelView?.removeFromSuperview() recordingPanelView = nil blockedActionView?.removeFromSuperview() blockedActionView = nil + chatDiscussionView?.removeFromSuperview() + chatDiscussionView = nil restrictedView?.removeFromSuperview() restrictedView = nil messageActionsPanelView?.removeFromSuperview() messageActionsPanelView = nil textView.isHidden = false - + let chatInteraction = self.chatInteraction switch state { case .normal, .editing: @@ -291,17 +301,16 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { break case .block(_): break - case let .action(text,action): self.messageActionsPanelView?.removeFromSuperview() self.blockedActionView = TitleButton(frame: bounds) - self.blockedActionView?.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.blueUI) + self.blockedActionView?.style = ControlStyle(font: .normal(.title),foregroundColor: theme.colors.accent) self.blockedActionView?.set(text: text, for: .Normal) self.blockedActionView?.set(background: theme.colors.grayBackground, for: .Highlight) if animated { self.blockedActionView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) } - self.blockedActionView?.set(handler: {_ in + self.blockedActionView?.set(handler: {_ in action(chatInteraction) }, for:.Click) @@ -309,7 +318,15 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { self.contentView.isHidden = true self.contentView.change(opacity: 0.0, animated: animated) self.accessoryView.change(opacity: 0.0, animated: animated) - break + case let .channelWithDiscussion(discussionGroupId, leftAction, rightAction): + self.messageActionsPanelView?.removeFromSuperview() + self.chatDiscussionView = ChannelDiscussionInputView(frame: bounds) + self.chatDiscussionView?.update(with: chatInteraction, discussionGroupId: discussionGroupId, leftAction: leftAction, rightAction: rightAction) + + self.addSubview(self.chatDiscussionView!, positioned: .below, relativeTo: _ts) + self.contentView.isHidden = true + self.contentView.change(opacity: 0.0, animated: animated) + self.accessoryView.change(opacity: 0.0, animated: animated) case let .recording(recorder): textView.isHidden = true recordingPanelView = ChatInputRecordingView(frame: NSMakeRect(0,0,frame.width,standart), chatInteraction:chatInteraction, recorder:recorder) @@ -317,15 +334,12 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { if animated { self.recordingPanelView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) } - break case let.restricted( text): self.messageActionsPanelView?.removeFromSuperview() self.restrictedView = RestrictionWrappedView(text) if animated { self.restrictedView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) } - - self.addSubview(self.restrictedView!, positioned: .below, relativeTo: _ts) self.contentView.isHidden = true self.contentView.change(opacity: 0.0, animated: animated) @@ -343,19 +357,32 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { if textView.selectedRange().location != range.location || textView.selectedRange().length != range.length { textView.setSelectedRange(range) } + if prevState.effectiveInput.inputText.isEmpty { + self.textView.scrollToCursor() + } + } - + private var updateFirstTime: Bool = true func updateAdditions(_ state:ChatPresentationInterfaceState, _ animated:Bool = true) -> Void { - accessory.update(with: state, account: account, animated: animated) + accessory.update(with: state, account: chatInteraction.context.account, animated: animated) accessoryDispose.set(accessory.nodeReady.get().start(next: { [weak self] (animated) in if let strongSelf = self { strongSelf.accessory.measureSize(strongSelf.frame.width - 40.0) strongSelf.textViewHeightChanged(strongSelf.defaultContentHeight, animated: animated) strongSelf.update() + if strongSelf.updateFirstTime { + strongSelf.updateFirstTime = false + strongSelf.textView.scrollToCursor() + } } })) - + + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + textView.setSelectedRange(NSMakeRange(textView.string().length, 0)) } func update() { @@ -372,22 +399,20 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { } - + override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) let keyboardWidth = frame.width - 40 bottomView.setFrameSize( NSMakeSize(keyboardWidth, bottomHeight)) - - if let markup = replyMarkupModel, markup.hasButtons { markup.measureSize(keyboardWidth) markup.view?.setFrameSize(NSMakeSize(markup.size.width, markup.size.height + 5)) markup.layout() } contentView.setFrameSize(frame.width, contentView.frame.height) - textView.setFrameSize(textViewSize()) + textView.setFrameSize(textViewSize(textView)) actionsView.setFrameSize(NSWidth(actionsView.frame), NSHeight(actionsView.frame)) attachView.setFrameSize(NSWidth(attachView.frame), NSHeight(attachView.frame)) _ts.setFrameSize(frame.width, .borderSize) @@ -396,26 +421,35 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { accessory.frame = NSMakeRect(15, contentView.frame.maxY, accessory.measuredWidth, accessory.size.height) messageActionsPanelView?.setFrameSize(frame.size) blockedActionView?.setFrameSize(frame.size) + chatDiscussionView?.setFrameSize(frame.size) restrictedView?.setFrameSize(frame.size) + + guard let superview = superview else {return} + textView.max_height = Int32(superview.frame.height / 2 + 50) + + if textView.placeholderAttributedString?.string != self.textPlaceholder { + textView.setPlaceholderAttributedString(.initialize(string: textPlaceholder, color: theme.colors.grayText, font: NSFont.normal(theme.fontSize), coreText: false), update: false) + } + + } override func layout() { super.layout() - let bottomInset = chatInteraction.presentation.isKeyboardShown ? bottomHeight : 0 bottomView.setFrameOrigin(20, chatInteraction.presentation.isKeyboardShown ? 0 : -bottomHeight) - - contentView.setFrameOrigin(0, bottomInset) - actionsView.setFrameOrigin(NSMaxX(textView.frame), 0) + textView.setFrameSize(NSMakeSize(frame.width - actionsView.frame.width - attachView.frame.width, textView.frame.height)) + contentView.setFrameOrigin(0, bottomInset) + actionsView.setFrameOrigin(frame.width - actionsView.frame.width, 0) attachView.setFrameOrigin(0, 0) _ts.setFrameOrigin(0, frame.height - .borderSize) - + } override func setFrameOrigin(_ newOrigin: NSPoint) { super.setFrameOrigin(newOrigin) } - + var stringValue:String { return textView.string() @@ -435,7 +469,10 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { let contentHeight:CGFloat = defaultContentHeight + yInset * 2.0 var sumHeight:CGFloat = contentHeight + (accessory.isVisibility() ? accessory.size.height + 5 : 0) if let markup = replyMarkupModel { - bottomHeight = min(110,markup.size.height) + 10 + bottomHeight = min( + ChatInputView.maxBottomHeight, + markup.size.height + ChatInputView.bottomPadding + ) } else { bottomHeight = 0 } @@ -456,37 +493,48 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { bottomView._change(size: NSMakeSize(frame.width - 40, bottomHeight), animated: animated) bottomView._change(pos: NSMakePoint(20, chatInteraction.presentation.isKeyboardShown ? 0 : -bottomHeight), animated: animated) - - accessory.view?.change(opacity: accessory.isVisibility() ? 1.0 : 0.0, animated: animated) - accessory.view?.change(pos: NSMakePoint(15, contentHeight), animated: animated) + accessory.view?.change(pos: NSMakePoint(15, contentHeight + bottomHeight), animated: animated) change(size: NSMakeSize(NSWidth(frame), sumHeight), animated: animated) delegate?.inputChanged(height: sumHeight, animated: animated) } - + } public func textViewEnterPressed(_ event: NSEvent) -> Bool { if FastSettings.checkSendingAbility(for: event) { - if !textView.string().trimmed.isEmpty || !chatInteraction.presentation.interfaceState.forwardMessageIds.isEmpty { - chatInteraction.sendMessage() - chatInteraction.account.updateLocalInputActivity(peerId: chatInteraction.peerId, activity: .typingText, isPresent: false) + let text = textView.string().trimmed + if text.length > chatInteraction.presentation.maxInputCharacters { + alert(for: chatInteraction.context.window, info: L10n.chatInputErrorMessageTooLongCountable(text.length - Int(chatInteraction.presentation.maxInputCharacters))) + return false + } + if !text.isEmpty || !chatInteraction.presentation.interfaceState.forwardMessageIds.isEmpty || chatInteraction.presentation.state == .editing { + chatInteraction.sendMessage(false, nil) + chatInteraction.context.account.updateLocalInputActivity(peerId: chatInteraction.peerId, activity: .typingText, isPresent: false) + markNextTextChangeToFalseActivity = true } return true } - return false } - + var currentActionView: NSView { + return self.actionsView.currentActionView + } + + + func makeBold() { self.textView.boldWord() } + func makeUrl() { + self.makeUrl(of: textView.selectedRange()) + } func makeItalic() { self.textView.italicWord() } @@ -501,67 +549,167 @@ class ChatInputView: Control, TGModernGrowingDelegate, Notifable { func makeFirstResponder() { self.window?.makeFirstResponder(self.textView.inputView) } - + private var previousString: String = "" func textViewTextDidChange(_ string: String) { - if FastSettings.isPossibleReplaceEmojies { - let replacedEmojies = string.stringEmojiReplacements - if string != replacedEmojies { - self.textView.setString(replacedEmojies) - } - } + + + let attributed = self.textView.attributedString() + let range = self.textView.selectedRange() + let state = ChatTextInputState(inputText: attributed.string, selectionRange: range.location ..< range.location + range.length, attributes: chatTextAttributes(from: attributed)) + chatInteraction.update({$0.withUpdatedEffectiveInputState(state)}) + } func canTransformInputText() -> Bool { - if let editState = chatInteraction.presentation.editState { - return editState.message.media.isEmpty - } return true } - + private var markNextTextChangeToFalseActivity: Bool = false public func textViewTextDidChangeSelectedRange(_ range: NSRange) { let attributed = self.textView.attributedString() - let state = ChatTextInputState(inputText: attributed.string, selectionRange: range.location ..< range.location + range.length, attributes: chatTextAttributes(from: attributed)) - chatInteraction.update({$0.withUpdatedEffectiveInputState(state)}) + let state = ChatTextInputState(inputText: attributed.string, selectionRange: range.min ..< range.max, attributes: chatTextAttributes(from: attributed)) - if chatInteraction.account.peerId != chatInteraction.peerId, let peer = chatInteraction.presentation.peer, !peer.isChannel { + chatInteraction.update({ current in + var current = current + current = current.withUpdatedEffectiveInputState(state) + if let disabledPreview = current.interfaceState.composeDisableUrlPreview { + if !current.effectiveInput.inputText.contains(disabledPreview) { + + var detectedUrl: String? + current.effectiveInput.attributedString.enumerateAttribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), in: current.effectiveInput.attributedString.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in + if let tag = value as? TGInputTextTag, let url = tag.attachment as? String { + detectedUrl = url + } + let s: ObjCBool = (detectedUrl != nil) ? true : false + stop.pointee = s + }) + if detectedUrl == nil { + current = current.updatedUrlPreview(nil).updatedInterfaceState {$0.withUpdatedComposeDisableUrlPreview(nil)} + } + } + } + return current + }) + + if chatInteraction.context.peerId != chatInteraction.peerId, let peer = chatInteraction.presentation.peer, !peer.isChannel && !markNextTextChangeToFalseActivity { - sendActivityDisposable.set((Signal.single(true) |> then(Signal.single(false) |> delay(4.0, queue: Queue.mainQueue()))).start(next: { [weak self] isPresent in - if let chatInteraction = self?.chatInteraction, let peer = chatInteraction.presentation.peer, !peer.isChannel { - chatInteraction.account.updateLocalInputActivity(peerId: chatInteraction.peerId, activity: .typingText, isPresent: isPresent) + sendActivityDisposable.set((Signal.single(!state.inputText.isEmpty) |> then(Signal.single(false) |> delay(4.0, queue: Queue.mainQueue()))).start(next: { [weak self] isPresent in + if let chatInteraction = self?.chatInteraction, let peer = chatInteraction.presentation.peer, !peer.isChannel && chatInteraction.presentation.state != .editing { + chatInteraction.context.account.updateLocalInputActivity(peerId: chatInteraction.peerId, activity: .typingText, isPresent: isPresent) } })) } + markNextTextChangeToFalseActivity = false } + deinit { chatInteraction.remove(observer: self) self.accessoryDispose.dispose() - emojiReplacementDisposable.dispose() + rtfAttachmentsDisposable.dispose() + slowModeUntilDisposable.dispose() } - func textViewSize() -> NSSize { + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { return NSMakeSize(NSWidth(contentView.frame) - NSWidth(actionsView.frame) - NSWidth(attachView.frame), NSHeight(textView.frame)) } func textViewIsTypingEnabled() -> Bool { + if let editState = chatInteraction.presentation.interfaceState.editState { + if editState.loadingState != .none { + return false + } + } return self.chatState == .normal || self.chatState == .editing } - func maxCharactersLimit() -> Int32 { - return chatInteraction.presentation.maxInputCharacters + func makeUrl(of range: NSRange) { + guard range.min != range.max, let window = kitWindow else { + return + } + var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) + let defaultTag: TGInputTextTag? = self.textView.attributedString().attribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), at: range.location, effectiveRange: &effectiveRange) as? TGInputTextTag + + + let defaultUrl = defaultTag?.attachment as? String + + if effectiveRange.location == NSNotFound || defaultTag == nil { + effectiveRange = range + } + + showModal(with: InputURLFormatterModalController(string: self.textView.string().nsstring.substring(with: effectiveRange), defaultUrl: defaultUrl, completion: { [weak self] url in + self?.textView.addLink(url, range: effectiveRange) + }), for: window) + + } + + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return ChatPresentationInterfaceState.maxInput + } + + @available(OSX 10.12.2, *) + func textView(_ textView: NSTextView!, shouldUpdateTouchBarItemIdentifiers identifiers: [NSTouchBarItem.Identifier]!) -> [NSTouchBarItem.Identifier]! { + return inputChatTouchBarItems(presentation: chatInteraction.presentation) + } + + func supportContinuityCamera() -> Bool { + return true + } + + func copyText(withRTF rtf: NSAttributedString!) -> Bool { + return globalLinkExecutor.copyAttributedString(rtf) } func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { - if let window = kitWindow, self.chatState == .normal { - return !InputPasteboardParser.proccess(pasteboard: pasteboard, account: self.account, chatInteraction:self.chatInteraction, window: window) + if let window = kitWindow, self.chatState == .normal || self.chatState == .editing { + + if let string = pasteboard.string(forType: .string) { + chatInteraction.update { current in + if let disabled = current.interfaceState.composeDisableUrlPreview, disabled.lowercased() == string.lowercased() { + return current.updatedInterfaceState {$0.withUpdatedComposeDisableUrlPreview(nil)} + } + return current + } + } + + let result = InputPasteboardParser.proccess(pasteboard: pasteboard, chatInteraction:self.chatInteraction, window: window) + if result { + if let data = pasteboard.data(forType: .rtf) { + if let attributed = (try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtfd], documentAttributes: nil)) ?? (try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)) { + + let (attributed, attachments) = attributed.applyRtf() + + if !attachments.isEmpty { + rtfAttachmentsDisposable.set((prepareTextAttachments(attachments) |> deliverOnMainQueue).start(next: { [weak self] urls in + if !urls.isEmpty, let chatInteraction = self?.chatInteraction { + chatInteraction.showPreviewSender(urls, true, attributed) + } + })) + } else { + let current = textView.attributedString().copy() as! NSAttributedString + let currentRange = textView.selectedRange() + let (attributedString, range) = current.appendAttributedString(attributed, selectedRange: currentRange) + let item = SimpleUndoItem(attributedString: current, be: attributedString, wasRange: currentRange, be: range) + self.textView.addSimpleItem(item) + } + Queue.mainQueue().async { [weak self] in + self?.textView.scrollToCursor() + } + return true + } + } + } + + + return !result } - return self.chatState == .normal + + return self.chatState != .normal } - + } diff --git a/Telegram-Mac/ChatInteractiveContentView.swift b/Telegram-Mac/ChatInteractiveContentView.swift index dc822527a9..5aa427592b 100644 --- a/Telegram-Mac/ChatInteractiveContentView.swift +++ b/Telegram-Mac/ChatInteractiveContentView.swift @@ -7,192 +7,667 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit +import SyncCore + +extension AutoremoveTimeoutMessageAttribute : Equatable { + public static func == (lhs: AutoremoveTimeoutMessageAttribute, rhs: AutoremoveTimeoutMessageAttribute) -> Bool { + return lhs.timeout == rhs.timeout && lhs.countdownBeginTime == rhs.countdownBeginTime && lhs.associatedMessageIds == rhs.associatedMessageIds + } + + +} + +final class ChatVideoAutoplayView { + let mediaPlayer: MediaPlayer + let view: MediaPlayerView + + fileprivate var playTimer: SwiftSignalKit.Timer? + var status: MediaPlayerStatus? + + private var timer: SwiftSignalKit.Timer? = nil + + init(mediaPlayer: MediaPlayer, view: MediaPlayerView) { + self.mediaPlayer = mediaPlayer + self.view = view + mediaPlayer.actionAtEnd = .loop(nil) + + + } + + func toggleVolume(_ enabled: Bool, animated: Bool) { + if !animated { + mediaPlayer.setVolume(enabled ? 1 : 0) + timer?.invalidate() + timer = nil + } else { + timer = nil + + let start:(Float) -> Void = { [weak self] volume in + let fps = Float(1000 / 60) + var current:Float = volume + + let tick = (enabled ? 1 - current : -current) / (fps * 0.3) + + self?.timer = SwiftSignalKit.Timer(timeout: abs(Double(tick)), repeat: true, completion: { [weak self] in + current += tick + self?.mediaPlayer.setVolume(min(1, max(0, current))) + + if current >= 1 || current <= 0 { + self?.timer?.invalidate() + } + }, queue: .mainQueue()) + + self?.timer?.start() + } + + mediaPlayer.getVolume { volume in + Queue.mainQueue().justDispatch { + start(volume) + } + } + } + } + + deinit { + view.removeFromSuperview() + timer?.invalidate() + playTimer?.invalidate() + } +} class ChatInteractiveContentView: ChatMediaContentView { private let image:TransformImageView = TransformImageView() - private var videoAccessory: ChatVideoAccessoryView? = nil + private var videoAccessory: ChatMessageAccessoryView? = nil private var progressView:RadialProgressView? private var timableProgressView: TimableProgressView? = nil private let statusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() + private let partDisposable = MetaDisposable() + + private var authenticFetchStatus: MediaResourceStatus? + + + private let mediaPlayerStatusDisposable = MetaDisposable() + private var autoplayVideoView: ChatVideoAutoplayView? + + override var backgroundColor: NSColor { + get { + return super.backgroundColor + } + set { + super.backgroundColor = .clear + } + } + + override func previewMediaIfPossible() -> Bool { + guard let context = self.context, let window = self.kitWindow, let table = self.table, media is TelegramMediaImage, parent == nil || parent?.containsSecretMedia == false, fetchStatus == .Local else {return false} + _ = startModalPreviewHandle(table, window: window, context: context) + return true + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } required init(frame frameRect: NSRect) { super.init(frame:frameRect) + //background = .random self.addSubview(image) } + + + override func updateMouse() { + + } + override func open() { - if let parent = parent, let account = account { - let parameters = self.parameters as? ChatMediaGalleryParameters - var type:GalleryAppearType = .history - if let parameters = parameters, parameters.isWebpage { - type = .alone - } else if parent.containsSecretMedia { - type = .secret + if let parent = parent { + parameters?.showMedia(parent) + autoplayVideoView?.toggleVolume(false, animated: false) + } + } + + private func updateMediaStatus(_ status: MediaPlayerStatus, animated: Bool = false) { + if let autoplayVideoView = autoplayVideoView, let media = self.media as? TelegramMediaFile { + autoplayVideoView.status = status + updateVideoAccessory(.Local, file: media, mediaPlayerStatus: status, animated: animated) + + switch status.status { + case .playing: + autoplayVideoView.playTimer?.invalidate() + autoplayVideoView.playTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.updateVideoAccessory(.Local, file: media, mediaPlayerStatus: status, animated: animated) + }, queue: .mainQueue()) + + autoplayVideoView.playTimer?.start() + default: + autoplayVideoView.playTimer?.invalidate() } - showChatGallery(account: account,message: parent, table, parameters, type: type) + + } } + + override func interactionControllerDidFinishAnimation(interactive: Bool) { + + } + override func addAccesoryOnCopiedView(view: NSView) { + if let videoAccessory = videoAccessory?.copy() as? NSView { + if visibleRect.minY < videoAccessory.frame.midY && visibleRect.minY + visibleRect.height > videoAccessory.frame.midY { + videoAccessory.frame.origin.y = frame.height - videoAccessory.frame.maxY + view.addSubview(videoAccessory) + } + + } + if let progressView = progressView { + let pView = RadialProgressView(theme: progressView.theme, twist: true) + pView.state = progressView.state + pView.frame = progressView.frame + if visibleRect.minY < progressView.frame.midY && visibleRect.minY + visibleRect.height > progressView.frame.midY { + pView.frame.origin.y = frame.height - progressView.frame.maxY + view.addSubview(pView) + } + } + self.autoplayVideoView?.mediaPlayer.seek(timestamp: 0) + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + deinit { + removeNotificationListeners() + mediaPlayerStatusDisposable.dispose() + partDisposable.dispose() + } + + + @objc func updatePlayerIfNeeded() { + let accept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) && !self.isDynamicContentLocked + if let autoplayView = autoplayVideoView { + if accept { + autoplayView.mediaPlayer.play() + } else { + autoplayView.mediaPlayer.pause() + autoplayVideoView?.playTimer?.invalidate() + } + } + } + + + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: table?.view) + } else { + removeNotificationListeners() + } + } + override func willRemove() { + super.willRemove() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToWindow() { + updateListeners() + DispatchQueue.main.async { [weak self] in + self?.updatePlayerIfNeeded() + } + } + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + if superview == nil { + self.autoplayVideoView = nil + } + } + override func layout() { super.layout() progressView?.center() timableProgressView?.center() - videoAccessory?.setFrameOrigin(5, 5) - + videoAccessory?.setFrameOrigin(8, 8) self.image.setFrameSize(frame.size) - } + + if let file = media as? TelegramMediaFile { + let dimensions = file.dimensions?.size ?? frame.size + let size = blurBackground ? dimensions.aspectFitted(frame.size) : frame.size + self.autoplayVideoView?.view.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - size.width) / 2), floorToScreenPixels(backingScaleFactor, (frame.height - size.height) / 2), size.width, size.height) + let positionFlags = self.autoplayVideoView?.view.positionFlags + self.autoplayVideoView?.view.positionFlags = positionFlags - override func update(with media: Media, size:NSSize, account:Account, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool) { + } + + } + + private func updateVideoAccessory(_ status: MediaResourceStatus, file: TelegramMediaFile, mediaPlayerStatus: MediaPlayerStatus? = nil, animated: Bool = false) { + let maxWidth = frame.width - 10 + let text: String + + var isBuffering: Bool = false + if let fetchStatus = self.fetchStatus, let status = mediaPlayerStatus { + switch status.status { + case .buffering: + switch fetchStatus { + case .Local: + break + default: + isBuffering = true + } + default: + break + } + + } + + switch status { + case let .Fetching(_, progress): + let current = String.prettySized(with: Int(Float(file.elapsedSize) * progress), afterDot: 1) + var size = "\(current) / \(String.prettySized(with: file.elapsedSize))" + if (maxWidth < 100 && parent?.groupingKey != nil) || file.elapsedSize == 0 { + size = "\(Int(progress * 100))%" + } + if file.isStreamable, parent?.groupingKey == nil, maxWidth > 100 { + if let parent = parent { + if !parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + size = String.durationTransformed(elapsed: file.videoDuration) + ", \(size)" + } + } else { + size = String.durationTransformed(elapsed: file.videoDuration) + ", \(size)" + } + } + text = size + case .Remote: + var size = String.durationTransformed(elapsed: file.videoDuration) + if file.isStreamable, parent?.groupingKey == nil, maxWidth > 100 { + size = size + ", " + String.prettySized(with: file.elapsedSize) + } + text = size + case .Local: + if let status = mediaPlayerStatus, status.generationTimestamp > 0, status.duration > 0 { + text = String.durationTransformed(elapsed: Int(status.duration - (status.timestamp + (CACurrentMediaTime() - status.generationTimestamp)))) + } else { + text = String.durationTransformed(elapsed: file.videoDuration) + } + } + + let isStreamable: Bool + if let parent = parent { + isStreamable = !parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) && file.isStreamable + } else { + isStreamable = file.isStreamable + } - let mediaUpdated = true//self.media == nil || !self.media!.isEqual(media) + videoAccessory?.updateText(text, maxWidth: maxWidth, status: status, isStreamable: isStreamable, isCompact: parent?.groupingKey != nil, soundOffOnImage: nil, isBuffering: isBuffering, animated: animated, fetch: { [weak self] in + self?.fetch() + }, cancelFetch: { [weak self] in + self?.cancelFetching() + }, click: { + + }) + + } + + override func executeInteraction(_ isControl: Bool) { + if let progressView = progressView { + switch progressView.state { + case .Fetching: + if isControl { + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + delete() + } + cancelFetching() + } + default: + super.executeInteraction(isControl) + } + } else { + if autoplayVideo { + open() + } else { + super.executeInteraction(isControl) + } + } + } + + var autoplayVideo: Bool { + if #available(OSX 10.12, *) { + } else { + return false + } - super.update(with: media, size: size, account: account, parent:parent, table:table, parameters:parameters) + if let autoremoveAttribute = parent?.autoremoveAttribute, autoremoveAttribute.timeout <= 60 { + return false + } + + if let media = media as? TelegramMediaFile, let parameters = self.parameters { + return (media.isStreamable || authenticFetchStatus == .Local) && (autoDownload || authenticFetchStatus == .Local) && parameters.autoplay && (parent?.groupingKey == nil || self.frame.width == superview?.frame.width) + } + return false + } + + var blurBackground: Bool { + return (parent != nil && parent?.groupingKey == nil) || parent == nil + } - var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedStatusSignal: Signal? + override func update(with media: Media, size:NSSize, context:AccountContext, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + + partDisposable.set(nil) + let versionUpdated = parent?.stableVersion != self.parent?.stableVersion + + + let mediaUpdated = self.media == nil || !media.isSemanticallyEqual(to: self.media!) || (parent?.autoremoveAttribute != self.parent?.autoremoveAttribute) if mediaUpdated { + self.autoplayVideoView = nil + } + + var clearInstantly: Bool = mediaUpdated + if clearInstantly, parent?.stableId == self.parent?.stableId { + clearInstantly = false + } + + super.update(with: media, size: size, context: context, parent:parent, table: table, parameters:parameters, positionFlags: positionFlags) + + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius + + + if let positionFlags = positionFlags { + if positionFlags.contains(.top) && positionFlags.contains(.left) { + topLeftRadius = topLeftRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + topRightRadius = topRightRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + bottomLeftRadius = bottomLeftRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + bottomRightRadius = bottomRightRadius * 3 + 2 + } + } + + var dimensions: NSSize = size + + if let image = media as? TelegramMediaImage { + dimensions = image.representationForDisplayAtSize(PixelDimensions(size))?.dimensions.size ?? size + } else if let file = media as? TelegramMediaFile { + dimensions = file.dimensions?.size ?? size + } + + let arguments = TransformImageArguments(corners: ImageCorners(topLeft: .Corner(topLeftRadius), topRight: .Corner(topRightRadius), bottomLeft: .Corner(bottomLeftRadius), bottomRight: .Corner(bottomRightRadius)), imageSize: blurBackground ? dimensions.aspectFitted(size) : dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: NSEdgeInsets(), resizeMode: blurBackground ? .blurBackground : .none) + + + + var updateImageSignal: Signal? + var updatedStatusSignal: Signal<(MediaResourceStatus, MediaResourceStatus), NoError>? + + if mediaUpdated /*mediaUpdated*/ { - var dimensions: NSSize = size if let image = media as? TelegramMediaImage { + + autoplayVideoView = nil videoAccessory?.removeFromSuperview() videoAccessory = nil - dimensions = image.representationForDisplayAtSize(size)?.dimensions ?? size + dimensions = image.representationForDisplayAtSize(PixelDimensions(size))?.dimensions.size ?? size if let parent = parent, parent.containsSecretMedia { - updateImageSignal = chatSecretPhoto(account: account, photo: image, scale: backingScaleFactor) + updateImageSignal = chatSecretPhoto(account: context.account, imageReference: ImageMediaReference.message(message: MessageReference(parent), media: image), scale: backingScaleFactor, synchronousLoad: approximateSynchronousValue) } else { - updateImageSignal = chatMessagePhoto(account: account, photo: image, scale: backingScaleFactor) + updateImageSignal = chatMessagePhoto(account: context.account, imageReference: parent != nil ? ImageMediaReference.message(message: MessageReference(parent!), media: image) : ImageMediaReference.standalone(media: image), scale: backingScaleFactor, synchronousLoad: approximateSynchronousValue) } if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessagePhotoStatus(account: account, photo: image), account.pendingMessageManager.pendingMessageStatus(parent.id)) - |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { - return .Fetching(isActive: true, progress: pendingStatus.progress) + updatedStatusSignal = combineLatest(chatMessagePhotoStatus(account: context.account, photo: image), context.account.pendingMessageManager.pendingMessageStatus(parent.id)) + |> map { resourceStatus, pendingStatus in + if let pendingStatus = pendingStatus.0, parent.forwardInfo == nil || resourceStatus != .Local { + return (.Fetching(isActive: true, progress: min(pendingStatus.progress, pendingStatus.progress * 85 / 100)), .Fetching(isActive: true, progress: min(pendingStatus.progress, pendingStatus.progress * 85 / 100))) } else { - return resourceStatus + return (resourceStatus, resourceStatus) } } |> deliverOnMainQueue } else { - updatedStatusSignal = chatMessagePhotoStatus(account: account, photo: image) |> deliverOnMainQueue + updatedStatusSignal = chatMessagePhotoStatus(account: context.account, photo: image, approximateSynchronousValue: approximateSynchronousValue) |> map {($0, $0)} |> deliverOnMainQueue } } else if let file = media as? TelegramMediaFile { - if file.isVideo { + + let fileReference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: file) : FileMediaReference.standalone(media: file) + + + + if file.isVideo, size.height > 80 { if videoAccessory == nil { - videoAccessory = ChatVideoAccessoryView(frame: NSZeroRect) + videoAccessory = ChatMessageAccessoryView(frame: NSMakeRect(5, 5, 0, 0)) addSubview(videoAccessory!) } - videoAccessory?.updateText(String.durationTransformed(elapsed: file.videoDuration) + ", \(String.prettySized(with: file.size ?? 0))", maxWidth: size.width - 20) } else { videoAccessory?.removeFromSuperview() videoAccessory = nil } if let parent = parent, parent.containsSecretMedia { - updateImageSignal = chatSecretMessageVideo(account: account, video: file, scale: backingScaleFactor) + updateImageSignal = chatSecretMessageVideo(account: context.account, fileReference: fileReference, scale: backingScaleFactor) } else { - updateImageSignal = chatMessageVideo(account: account, video: file, scale: backingScaleFactor) + updateImageSignal = chatMessageVideo(postbox: context.account.postbox, fileReference: fileReference, scale: backingScaleFactor) //chatMessageVideo(account: account, video: file, scale: backingScaleFactor) } - dimensions = file.dimensions ?? size + dimensions = file.dimensions?.size ?? size + + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(parent.id)) - |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { - return .Fetching(isActive: true, progress: pendingStatus.progress) + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: file), context.account.pendingMessageManager.pendingMessageStatus(parent.id)) + |> map { resourceStatus, pendingStatus in + if let pendingStatus = pendingStatus.0 { + return (.Fetching(isActive: true, progress: pendingStatus.progress), .Fetching(isActive: true, progress: pendingStatus.progress)) } else { - return resourceStatus + if file.isStreamable && parent.id.peerId.namespace != Namespaces.Peer.SecretChat { + return (.Local, resourceStatus) + } + return (resourceStatus, resourceStatus) } - } |> deliverOnMainQueue + } |> deliverOnMainQueue } else { - updatedStatusSignal = chatMessageFileStatus(account: account, file: file) |> deliverOnMainQueue + if file.resource is LocalFileVideoMediaResource { + updatedStatusSignal = .single((.Local, .Local)) + } else { + updatedStatusSignal = chatMessageFileStatus(account: context.account, file: file, approximateSynchronousValue: approximateSynchronousValue) |> deliverOnMainQueue |> map { [weak parent, weak file] status in + if let parent = parent, let file = file { + if file.isStreamable && parent.id.peerId.namespace != Namespaces.Peer.SecretChat { + return (.Local, status) + } + } + return (status, status) + } + } + } } - let arguments = TransformImageArguments(corners: ImageCorners(radius:.cornerRadius), imageSize: dimensions, boundingSize: frame.size, intrinsicInsets: NSEdgeInsets()) - self.image.set(arguments: arguments) - - if !animated { - self.image.setSignal(signal: cachedMedia(media: media, size: arguments.imageSize, scale: backingScaleFactor)) - } - if let updateImageSignal = updateImageSignal { - self.image.setSignal(account: account, signal: updateImageSignal, clearInstantly: false, animate: true, cacheImage: { [weak self] image in - if let strongSelf = self { - return cacheMedia(signal: image, media: media, size: arguments.imageSize, scale: strongSelf.backingScaleFactor) - } else { - return .complete() + self.image.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor, positionFlags: positionFlags), clearInstantly: clearInstantly) + + if let updateImageSignal = updateImageSignal, !self.image.isFullyLoaded { + self.image.setSignal( updateImageSignal, animate: !versionUpdated, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale, positionFlags: positionFlags) } }) } } + + self.image.set(arguments: arguments) + if arguments.imageSize.width == arguments.boundingSize.width { + if let positionFlags = positionFlags { + autoplayVideoView?.view.positionFlags = positionFlags + } else { + autoplayVideoView?.view.positionFlags = nil + autoplayVideoView?.view.layer?.cornerRadius = .cornerRadius + } + } else { + autoplayVideoView?.view.positionFlags = nil + autoplayVideoView?.view.layer?.cornerRadius = 0 + } + - + var first: Bool = true if let updateStatusSignal = updatedStatusSignal { - self.statusDisposable.set(updateStatusSignal.start(next: { [weak self] (status) in + self.statusDisposable.set(updateStatusSignal.start(next: { [weak self] (status, authentic) in + + if let strongSelf = self { - strongSelf.fetchStatus = status + strongSelf.authenticFetchStatus = authentic + + + var authentic = authentic + if strongSelf.autoplayVideo { + strongSelf.fetchStatus = authentic + authentic = .Local + } else { + switch authentic { + case .Fetching: + strongSelf.fetchStatus = status + default: + strongSelf.fetchStatus = status + } + } + + + if let file = strongSelf.media as? TelegramMediaFile, strongSelf.autoplayVideo { + if strongSelf.autoplayVideoView == nil { + let autoplay: ChatVideoAutoplayView + + let fileReference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: file) : FileMediaReference.standalone(media: file) + + autoplay = ChatVideoAutoplayView(mediaPlayer: MediaPlayer(postbox: context.account.postbox, reference: fileReference.resourceReference(fileReference.media.resource), streamable: file.isStreamable, video: true, preferSoftwareDecoding: false, enableSound: false, volume: 0.0, fetchAutomatically: true), view: MediaPlayerView(backgroundThread: true)) + + strongSelf.autoplayVideoView = autoplay + if !strongSelf.blurBackground { + strongSelf.autoplayVideoView?.view.setVideoLayerGravity(.resizeAspectFill) + } else { + strongSelf.autoplayVideoView?.view.setVideoLayerGravity(.resize) + } + strongSelf.updatePlayerIfNeeded() + } + if let autoplay = strongSelf.autoplayVideoView { + let dimensions = (file.dimensions?.size ?? size) + let value = strongSelf.blurBackground ? dimensions.aspectFitted(size) : size + + autoplay.view.frame = NSMakeRect(0, 0, value.width, value.height) + if let positionFlags = positionFlags { + autoplay.view.positionFlags = positionFlags + } else { + autoplay.view.layer?.cornerRadius = .cornerRadius + } + strongSelf.addSubview(autoplay.view, positioned: .above, relativeTo: strongSelf.image) + autoplay.mediaPlayer.attachPlayerView(autoplay.view) + autoplay.view.center() + } + + } else { + strongSelf.autoplayVideoView = nil + } + + if let autoplay = strongSelf.autoplayVideoView { + strongSelf.mediaPlayerStatusDisposable.set((autoplay.mediaPlayer.status |> deliverOnMainQueue).start(next: { [weak strongSelf] status in + strongSelf?.updateMediaStatus(status, animated: !first) + })) + } + + + + if let file = media as? TelegramMediaFile, strongSelf.autoplayVideoView == nil { + strongSelf.updateVideoAccessory(parent == nil ? .Local : authentic, file: file, animated: !first) + first = false + } var containsSecretMedia:Bool = false if let message = parent { containsSecretMedia = message.containsSecretMedia } - if let _ = parent?.autoremoveAttribute?.countdownBeginTime { + if let autoremoveAttribute = parent?.autoremoveAttribute, autoremoveAttribute.timeout <= 60, autoremoveAttribute.countdownBeginTime != nil { strongSelf.progressView?.removeFromSuperview() strongSelf.progressView = nil if strongSelf.timableProgressView == nil { - strongSelf.timableProgressView = TimableProgressView() + strongSelf.timableProgressView = TimableProgressView(size: NSMakeSize(parent?.groupingKey != nil ? 30 : 40.0, parent?.groupingKey != nil ? 30 : 40.0)) strongSelf.addSubview(strongSelf.timableProgressView!) } } else { strongSelf.timableProgressView?.removeFromSuperview() strongSelf.timableProgressView = nil - if case .Local = status, media is TelegramMediaImage, !containsSecretMedia { + switch status { + case .Local: self?.image.animatesAlphaOnFirstTransition = false - - if let progressView = strongSelf.progressView { - progressView.state = .Fetching(progress:1.0, force: false) - progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion:false, completion: { [weak strongSelf] (completion) in - if completion { - progressView.removeFromSuperview() - strongSelf?.progressView = nil + default: + self?.image.animatesAlphaOnFirstTransition = false + } + + var removeProgress: Bool = strongSelf.autoplayVideo + if case .Local = status, media is TelegramMediaImage, !containsSecretMedia { + removeProgress = true + } + + if removeProgress { + if let progressView = strongSelf.progressView { + switch progressView.state { + case .Fetching: + progressView.state = .Fetching(progress:1.0, force: false) + case .ImpossibleFetching: + progressView.state = .ImpossibleFetching(progress:1.0, force: false) + default: + break + } + strongSelf.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() } }) + } } else { - self?.image.animatesAlphaOnFirstTransition = true strongSelf.progressView?.layer?.removeAllAnimations() if strongSelf.progressView == nil { let progressView = RadialProgressView(theme:RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) - progressView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0)) + progressView.frame = CGRect(origin: CGPoint(), size: CGSize(width: parent?.groupingKey != nil ? 30 : 40.0, height: parent?.groupingKey != nil ? 30 : 40.0)) strongSelf.progressView = progressView strongSelf.addSubview(progressView) strongSelf.progressView?.center() @@ -202,15 +677,26 @@ class ChatInteractiveContentView: ChatMediaContentView { } + let progressStatus: MediaResourceStatus + if strongSelf.parent?.groupingKey != nil { + switch authentic { + case .Fetching: + progressStatus = authentic + default: + progressStatus = status + } + } else { + progressStatus = status + } + - - switch status { + switch progressStatus { case let .Fetching(_, progress): - strongSelf.progressView?.state = .Fetching(progress: progress, force: false) + strongSelf.progressView?.state = parent == nil ? .ImpossibleFetching(progress: progress, force: false) : (progress == 1.0 && strongSelf.parent?.groupingKey != nil ? .Success : .Fetching(progress: progress, force: false)) case .Local: var state: RadialProgressState = .None if containsSecretMedia { - state = .Icon(image: theme.icons.chatSecretThumb, mode:.destinationOut) + state = .Icon(image: parent?.groupingKey != nil ? theme.icons.chatSecretThumbSmall : theme.icons.chatSecretThumb, mode:.normal) if let attribute = parent?.autoremoveAttribute, let countdownBeginTime = attribute.countdownBeginTime { let difference:TimeInterval = TimeInterval((countdownBeginTime + attribute.timeout)) - (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) @@ -234,16 +720,18 @@ class ChatInteractiveContentView: ChatMediaContentView { } strongSelf.needsLayout = true } - })) - if media is TelegramMediaImage { - fetch() - } } } + override func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + image._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + override func setContent(size: NSSize) { super.setContent(size: size) } @@ -258,21 +746,40 @@ class ChatInteractiveContentView: ChatMediaContentView { } override func cancelFetching() { - if let account = account { + if let context = context, let parent = parent { if let media = media as? TelegramMediaFile { - chatMessageFileCancelInteractiveFetch(account: account, file: media) + messageMediaFileCancelInteractiveFetch(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)) } else if let media = media as? TelegramMediaImage { - chatMessagePhotoCancelInteractiveFetch(account: account, photo: media) + chatMessagePhotoCancelInteractiveFetch(account: context.account, photo: media) } } } override func fetch() { - if let account = account { + if let context = context { if let media = media as? TelegramMediaFile { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + if let parent = parent { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start()) + } else { + fetchDisposable.set(freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.standalone(media: media)).start()) + } } else if let media = media as? TelegramMediaImage { - fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photo: media).start()) + fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: parent != nil ? ImageMediaReference.message(message: MessageReference(parent!), media: media) : ImageMediaReference.standalone(media: media)).start()) + } + } + } + + + override func preloadStreamblePart() { + if let context = context { + if let media = media as? TelegramMediaFile, let fileSize = media.size { + let reference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: media) : FileMediaReference.standalone(media: media) + + + let preload = combineLatest(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(media.resource), range: (0 ..< Int(2.0 * 1024 * 1024), .default), statsCategory: .video), fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(media.resource), range: (max(0, fileSize - Int(256 * 1024)) ..< Int(Int32.max), .default), statsCategory: .video)) + + partDisposable.set(preload.start()) + } } } @@ -281,6 +788,8 @@ class ChatInteractiveContentView: ChatMediaContentView { override func copy() -> Any { return image.copy() } - + override var contents: Any? { + return image.layer?.contents + } } diff --git a/Telegram-Mac/ChatInterfaceInputContext.swift b/Telegram-Mac/ChatInterfaceInputContext.swift index 8f70a0942d..b2ce8cbace 100644 --- a/Telegram-Mac/ChatInterfaceInputContext.swift +++ b/Telegram-Mac/ChatInterfaceInputContext.swift @@ -7,8 +7,9 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox struct PossibleContextQueryTypes: OptionSet { var rawValue: Int32 @@ -27,6 +28,7 @@ struct PossibleContextQueryTypes: OptionSet { static let contextRequest = PossibleContextQueryTypes(rawValue: (1 << 3)) static let stickers = PossibleContextQueryTypes(rawValue: (1 << 4)) static let emoji = PossibleContextQueryTypes(rawValue: (1 << 5)) + static let emojiFast = PossibleContextQueryTypes(rawValue: (1 << 6)) } private func makeScalar(_ c: Character) -> Character { @@ -80,15 +82,8 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in } } - if inputText.isSingleEmoji { - var inputText = inputText - if inputText.canHaveSkinToneModifier { - inputText = inputText.emojiUnmodified - } - return (inputText.startIndex ..< inputText.endIndex, [.stickers], nil) - } - let maxUtfIndex = inputText.utf16.index(inputText.utf16.startIndex, offsetBy: inputState.selectionRange.lowerBound) + let maxUtfIndex = inputText.utf16.index(inputText.utf16.startIndex, offsetBy: min(inputState.selectionRange.lowerBound, inputText.utf16.count)) guard let maxIndex = maxUtfIndex.samePosition(in: inputText) else { return nil } @@ -97,9 +92,19 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in } var index = inputText.index(before: maxIndex) + if inputText.length <= 7, inputText.isSingleEmoji { + var inputText = inputText + if inputText.canHaveSkinToneModifier { + inputText = inputText.emojiUnmodified + } + return (inputText.startIndex ..< maxIndex, [.stickers], nil) + } + + + var possibleQueryRange: Range? - var possibleTypes = PossibleContextQueryTypes([.command, .mention, .emoji]) + var possibleTypes = PossibleContextQueryTypes([.command, .mention, .emoji, .hashtag, .emojiFast]) //var possibleTypes = PossibleContextQueryTypes([.command, .mention]) @@ -107,15 +112,38 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in func check() { if inputText.startIndex != inputText.index(before: index) { let prev = inputText.index(before: inputText.index(before: index)) - if (inputText[prev] != spaceScalar && inputText[prev] != newlineScalar) { + let scalars:CharacterSet = CharacterSet.alphanumerics + if let scalar = inputText[prev].unicodeScalars.first, scalars.contains(scalar) && inputText[prev] != newlineScalar { possibleTypes = [] } + switch possibleTypes { + case .emoji: + if index != inputText.endIndex { + if let scalar = inputText[index].unicodeScalars.first { + if !scalars.contains(scalar) { + possibleTypes = [] + } + } else { + possibleTypes = [] + } + } else { + // possibleTypes = [] + } + + default: + break + } } } var definedType = false - while true { + var characterSet = CharacterSet.alphanumerics + characterSet.insert(hashScalar.unicodeScalars.first!) + characterSet.insert(atScalar.unicodeScalars.first!) + characterSet.insert(slashScalar.unicodeScalars.first!) + characterSet.insert(emojiScalar.unicodeScalars.first!) + for _ in 0 ..< 20 { let c = inputText[index] @@ -123,7 +151,7 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in //if index == inputText.startIndex { //|| (inputText[inputText.index(before: index)] == spaceScalar || inputText[inputText.index(before: index)] == newlineScalar) - if c == spaceScalar || c == newlineScalar { + if !characterSet.contains(c.unicodeScalars.first!) { possibleTypes = [] } else if c == hashScalar { possibleTypes = possibleTypes.intersection([.hashtag]) @@ -173,6 +201,13 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in } } + if inputText.trimmingCharacters(in: CharacterSet.letters).isEmpty, !inputText.isEmpty { + possibleTypes = possibleTypes.intersection([.emojiFast]) + definedType = true + possibleQueryRange = index ..< maxIndex + } + + if let possibleQueryRange = possibleQueryRange, definedType && !possibleTypes.isEmpty { return (possibleQueryRange, possibleTypes, nil) } @@ -183,22 +218,49 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState, in func inputContextQueryForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, includeContext: Bool) -> ChatPresentationInputQuery { let inputState = chatPresentationInterfaceState.effectiveInput if let (possibleQueryRange, possibleTypes, additionalStringRange) = textInputStateContextQueryRangeAndType(inputState, includeContext: includeContext) { - let query = String(inputState.inputText[possibleQueryRange]) //.substring(with: possibleQueryRange) + + if chatPresentationInterfaceState.state == .editing && (possibleTypes != [.contextRequest] && possibleTypes != [.mention] && possibleTypes != [.emoji]) { + return .none + } + var possibleQueryRange = possibleQueryRange +// if possibleQueryRange.upperBound > inputState.inputText.endIndex { +// possibleQueryRange = possibleQueryRange.lowerBound ..< inputState.inputText.endIndex +// } + +// possibleQueryRange.lowerBound.encodedOffset +// +// if let index = inputState.inputText.index(possibleQueryRange.upperBound, offsetBy: 0, limitedBy: inputState.inputText.endIndex) { +// possibleQueryRange = possibleQueryRange.lowerBound ..< index +// } else { +// return .none +// } + + + + let value = inputState.inputText[possibleQueryRange] + let query = String(value) if possibleTypes == [.hashtag] { return .hashtag(query) } else if possibleTypes == [.mention] { - return .mention(query: query, includeRecent: inputState.inputText.startIndex == inputState.inputText.index(before: possibleQueryRange.lowerBound)) + return .mention(query: query, includeRecent: inputState.inputText.startIndex == inputState.inputText.index(before: possibleQueryRange.lowerBound) && chatPresentationInterfaceState.state == .normal) } else if possibleTypes == [.command] { return .command(query) } else if possibleTypes == [.contextRequest], let additionalStringRange = additionalStringRange { - let additionalString = inputState.inputText.substring(with: additionalStringRange) + let additionalString = String(inputState.inputText[additionalStringRange]) return .contextRequest(addressName: query, query: additionalString) - } else if possibleTypes == [.stickers], chatPresentationInterfaceState.editState == nil { - return .stickers(query) + } else if possibleTypes == [.stickers] { + return .stickers(query.emojiUnmodified) } else if possibleTypes == [.emoji] { - return .emoji(query) + if query.trimmingCharacters(in: CharacterSet.letters).isEmpty { + return .emoji(query, firstWord: false) + } else { + return .none + } + } else if possibleTypes == [.emojiFast] { + return .emoji(query, firstWord: true) } return .none + } else { return .none } diff --git a/Telegram-Mac/ChatInterfaceInteraction.swift b/Telegram-Mac/ChatInterfaceInteraction.swift index 36ab82a8d2..63a1c4a447 100644 --- a/Telegram-Mac/ChatInterfaceInteraction.swift +++ b/Telegram-Mac/ChatInterfaceInteraction.swift @@ -7,17 +7,18 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac - +import SwiftSignalKit +import MapKit final class ReplyMarkupInteractions { - let proccess:(ReplyMarkupButton, (Bool)->Void) -> Void + let proccess:(ReplyMarkupButton, @escaping(Bool)->Void) -> Void - init(proccess:@escaping (ReplyMarkupButton, (Bool)->Void)->Void) { + init(proccess:@escaping (ReplyMarkupButton, @escaping(Bool)->Void)->Void) { self.proccess = proccess } @@ -26,24 +27,42 @@ final class ReplyMarkupInteractions { final class ChatInteraction : InterfaceObserver { - let peerId:PeerId - let account:Account + let chatLocation: ChatLocation + let mode: ChatMode + var peerId : PeerId { + switch chatLocation { + case let .peer(peerId): + return peerId + } + } + + var peer: Peer? { + return presentation.peer + } + + let context: AccountContext let isLogInteraction:Bool + let disableSelectAbility: Bool + let isGlobalSearchMessage: Bool private let modifyDisposable:MetaDisposable = MetaDisposable() private let mediaDisposable:MetaDisposable = MetaDisposable() private let startBotDisposable:MetaDisposable = MetaDisposable() private let addContactDisposable:MetaDisposable = MetaDisposable() private let requestSessionId:MetaDisposable = MetaDisposable() + let editDisposable = MetaDisposable() private let disableProxyDisposable = MetaDisposable() private let enableProxyDisposable = MetaDisposable() - init(peerId:PeerId, account:Account, isLogInteraction: Bool = false) { - self.peerId = peerId - self.account = account + init(chatLocation: ChatLocation, context: AccountContext, mode: ChatMode = .history, isLogInteraction: Bool = false, disableSelectAbility: Bool = false, isGlobalSearchMessage: Bool = false) { + self.chatLocation = chatLocation + self.context = context + self.disableSelectAbility = disableSelectAbility self.isLogInteraction = isLogInteraction - self.presentation = ChatPresentationInterfaceState() + self.isGlobalSearchMessage = isGlobalSearchMessage + self.presentation = ChatPresentationInterfaceState(chatLocation) + self.mode = mode super.init() - let signal = mediaPromise.get() |> deliverOnMainQueue |> mapToQueue { [weak self] (media) -> Signal in + let signal = mediaPromise.get() |> deliverOnMainQueue |> mapToQueue { [weak self] (media) -> Signal in self?.sendMedia(media) return .single(Void()) } @@ -60,17 +79,22 @@ final class ChatInteraction : InterfaceObserver { } } + var withToggledSelectedMessage:((ChatPresentationInterfaceState)->ChatPresentationInterfaceState)->Void = { _ in } + var setupReplyMessage: (MessageId?) -> Void = {_ in} var beginMessageSelection: (MessageId?) -> Void = {_ in} var deleteMessages: ([MessageId]) -> Void = {_ in } var forwardMessages: ([MessageId]) -> Void = {_ in} - var sendMessage: () -> Void = {} - var forceSendMessage: (String) -> Void = {_ in} + var sendMessage: (Bool, Date?) -> Void = { _, _ in } + var sendPlainText: (String) -> Void = {_ in} + // var focusMessageId: (MessageId?, MessageId, TableScrollState) -> Void = {_,_,_ in} // from, to, animated, position var sendMedia:([MediaSenderContainer]) -> Void = {_ in} - var sendAppFile:(TelegramMediaFile) -> Void = {_ in} + var sendAppFile:(TelegramMediaFile, Bool) -> Void = { _,_ in} + var sendMedias:([Media], ChatTextInputState, Bool, ChatTextInputState?, Bool, Date?) -> Void = {_,_,_,_,_,_ in} + var focusInputField:()->Void = {} var openInfo:(PeerId, Bool, MessageId?, ChatInitialAction?) -> Void = {_,_,_,_ in} // peerId, isNeedOpenChat, postId, initialAction var beginEditingMessage:(Message?) -> Void = {_ in} var requestMessageActionCallback:(MessageId, Bool, MemoryBuffer?) -> Void = {_,_,_ in} @@ -84,9 +108,9 @@ final class ChatInteraction : InterfaceObserver { var sendCommand:(PeerCommand)->Void = {_ in } var setNavigationAction:(NavigationModalAction)->Void = {_ in} var switchInlinePeer:(PeerId, ChatInitialAction)->Void = {_,_ in} - var showPreviewSender:([URL], Bool)->Void = {_,_ in} + var showPreviewSender:([URL], Bool, NSAttributedString?)->Void = {_,_,_ in} var setSecretChatMessageAutoremoveTimeout:(Int32?)->Void = {_ in} - var toggleNotifications:()->Void = {} + var toggleNotifications:(Bool?)->Void = { _ in } var removeAndCloseChat:()->Void = {} var joinChannel:()->Void = {} var returnGroup:()->Void = {} @@ -94,30 +118,55 @@ final class ChatInteraction : InterfaceObserver { var unblock:()->Void = {} var updatePinned:(MessageId, Bool, Bool)->Void = {_,_,_ in} var reportSpamAndClose:()->Void = {} - var dismissPeerReport:()->Void = {} + var dismissPeerStatusOptions:()->Void = {} var toggleSidebar:()->Void = {} var mentionPressed:()->Void = {} var jumpToDate:(Date)->Void = {_ in} + var openFeedInfo: (PeerGroupId)->Void = {_ in} + var showNextPost:()->Void = {} + var startRecording:(Bool, NSView?)->Void = {_,_ in} + var openProxySettings: ()->Void = {} + var sendLocation: (CLLocationCoordinate2D, MapVenue?) -> Void = {_, _ in} + var clearMentions:()->Void = {} + var attachFile:(Bool)->Void = { _ in } + var attachPhotoOrVideo:()->Void = {} + var attachPicture:()->Void = {} + var attachLocation:()->Void = {} + var updateEditingMessageMedia:([String]?, Bool) -> Void = { _, _ in} + var editEditingMessagePhoto:(TelegramMediaImage) -> Void = { _ in} + var removeChatInteractively:()->Void = { } + var updateSearchRequest: (SearchMessagesResultState)->Void = { _ in } + var searchPeerMessages: (Peer) -> Void = { _ in } + var vote:(MessageId, [Data], Bool) -> Void = { _, _, _ in } + var closePoll:(MessageId) -> Void = { _ in } + var openDiscussion:()->Void = { } + var addContact:()->Void = {} + var blockContact: ()->Void = {} + var openScheduledMessages: ()->Void = {} + var openBank: (String)->Void = { _ in } + var getGradientOffsetRect:()->NSRect = { return .zero } + + var unarchive: ()->Void = { } + + var closeAfterPeek:(Int32)->Void = { _ in } + + var updateReactions: (MessageId, String, @escaping(Bool)->Void)->Void = { _, _, _ in } + + let loadingMessage: Promise = Promise() let mediaPromise:Promise<[MediaSenderContainer]> = Promise() - func addContact() { - addContactDisposable.set(addContactPeerInteractively(account: account, peerId: peerId, phone: (presentation.peer as? TelegramUser)?.phone).start()) - } + + func disableProxy() { - let account = self.account - disableProxyDisposable.set((account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]) |> take(1) |> map { prefs -> ProxySettings? in - return prefs.values[PreferencesKeys.proxySettings] as? ProxySettings - } |> deliverOnMainQueue |> mapToSignal { setting in - return confirmSignal(for: mainWindow, header: appName, information: tr(.proxyForceDisable(setting?.host ?? ""))) - } |> filter {$0} |> mapToSignal { _ in - return applyProxySettings(postbox: account.postbox, network: account.network, settings: nil) + disableProxyDisposable.set(updateProxySettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> ProxySettings in + return current.withUpdatedEnabled(false) }).start()) } - func applyProxy(_ proxy:ProxySettings) -> Void { - applyExternalProxy(proxy, postbox: account.postbox, network: account.network) + func applyProxy(_ server:ProxyServerSettings) -> Void { + applyExternalProxy(server, accountManager: context.sharedContext.accountManager) } @@ -129,16 +178,15 @@ final class ChatInteraction : InterfaceObserver { } else { peerId = peer.id } - requestSessionId.set((phoneCall(account, peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - applyUIPCallResult(strongSelf.account, result) - } + let context = self.context + requestSessionId.set((phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) })) } } func startBot(_ payload:String? = nil) { - startBotDisposable.set((requestStartBot(account: self.account, botPeerId: self.peerId, payload: payload) |> deliverOnMainQueue).start(completed: { [weak self] in + startBotDisposable.set((requestStartBot(account: context.account, botPeerId: self.peerId, payload: payload) |> deliverOnMainQueue).start(completed: { [weak self] in self?.update({$0.updatedInitialAction(nil)}) })) } @@ -167,15 +215,22 @@ final class ChatInteraction : InterfaceObserver { } func updateInput(with text:String) { - let state = ChatTextInputState(inputText: text, selectionRange: text.length ..< text.length, attributes: []) - self.update({$0.updatedInterfaceState({$0.withUpdatedInputState(state)})}) + if self.presentation.state == .normal { + let state = ChatTextInputState(inputText: text, selectionRange: text.length ..< text.length, attributes: []) + self.update({$0.updatedInterfaceState({$0.withUpdatedInputState(state)})}) + } } func appendText(_ text:String, selectedRange:Range? = nil) -> Range { + + + var selectedRange = selectedRange ?? presentation.effectiveInput.selectionRange let inputText = presentation.effectiveInput.attributedString.mutableCopy() as! NSMutableAttributedString - + if self.presentation.state != .normal && presentation.state != .editing { + return selectedRange.lowerBound ..< selectedRange.lowerBound + } if selectedRange.upperBound - selectedRange.lowerBound > 0 { // let minUtfIndex = inputText.utf16.index(inputText.utf16.startIndex, offsetBy: selectedRange.lowerBound) @@ -187,7 +242,7 @@ final class ChatInteraction : InterfaceObserver { inputText.replaceCharacters(in: NSMakeRange(selectedRange.lowerBound, selectedRange.upperBound - selectedRange.lowerBound), with: NSAttributedString(string: text)) selectedRange = selectedRange.lowerBound ..< selectedRange.lowerBound } else { - inputText.insert(NSAttributedString(string: text), at: selectedRange.lowerBound) + inputText.insert(NSAttributedString(string: text, font: .normal(theme.fontSize)), at: selectedRange.lowerBound) } @@ -206,7 +261,32 @@ final class ChatInteraction : InterfaceObserver { return selectedRange.lowerBound ..< selectedRange.lowerBound + text.length } - func invokeInitialAction(includeAuto:Bool = false) { + func cancelEditing(_ force: Bool = false) { + if let editState = self.presentation.interfaceState.editState { + let oldState = ChatEditState(message: editState.message) + if force { + self.update({$0.withoutEditMessage().updatedUrlPreview(nil)}) + } else { + switch editState.loadingState { + case .loading, .progress: + editDisposable.set(nil) + self.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedLoadingState(.none)})})}) + return + default: + if oldState.inputState.attributedString != editState.inputState.attributedString, !editState.inputState.attributedString.string.isEmpty { + confirm(for: context.window, information: L10n.chatEditCancelText, okTitle: L10n.alertDiscard, cancelTitle: L10n.alertNO, successHandler: { [weak self] _ in + self?.update({$0.withoutEditMessage().updatedUrlPreview(nil)}) + }) + } else { + self.update({$0.withoutEditMessage().updatedUrlPreview(nil)}) + } + } + } + } + + } + + func invokeInitialAction(includeAuto:Bool = false, animated: Bool = true) { if let action = presentation.initialAction { switch action { case let .start(parameter: parameter, behavior: behavior): @@ -221,8 +301,10 @@ final class ChatInteraction : InterfaceObserver { } if invoke { startBot(parameter) + update({ + $0.withoutInitialAction() + }) } - case let .inputText(text: text, behavior: behavior): var invoke:Bool = !includeAuto if includeAuto { @@ -235,6 +317,9 @@ final class ChatInteraction : InterfaceObserver { } if invoke { updateInput(with: text) + update({ + $0.withoutInitialAction() + }) } case let .files(list: list, behavior: behavior): var invoke:Bool = !includeAuto @@ -247,9 +332,24 @@ final class ChatInteraction : InterfaceObserver { } } if invoke { - showPreviewSender( list.map { URL(fileURLWithPath: $0) }, true ) + showPreviewSender( list.map { URL(fileURLWithPath: $0) }, true, nil ) + update({ + $0.withoutInitialAction() + }) } + case let .forward(messageIds, text, _): + update(animated: animated, {$0.updatedInterfaceState({$0.withUpdatedForwardMessageIds(messageIds).withUpdatedInputState(text != nil ? ChatTextInputState(inputText: text!) : $0.inputState)})}) + update({ + $0.withoutInitialAction() + }) + case .ad: + break + case .source: + break + case let .closeAfter(peek): + break } + } } @@ -261,31 +361,64 @@ final class ChatInteraction : InterfaceObserver { if let strongSelf = self { switch button.action { case let .url(url): - execute(inapp: inApp(for: url.nsstring, account: strongSelf.account, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.forceSendMessage)) + execute(inapp: inApp(for: url.nsstring, context: strongSelf.context, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.sendPlainText, applyProxy: strongSelf.applyProxy)) case .text: - _ = (enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [EnqueueMessage.message(text: button.title, attributes: [], media: nil, replyToMessageId: strongSelf.presentation.interfaceState.messageActionsState.processedSetupReplyMessageId)]) |> deliverOnMainQueue).start(next: { [weak strongSelf] _ in + _ = (enqueueMessages(context: strongSelf.context, peerId: strongSelf.peerId, messages: [EnqueueMessage.message(text: button.title, attributes: [], mediaReference: nil, replyToMessageId: strongSelf.presentation.interfaceState.messageActionsState.processedSetupReplyMessageId, localGroupingKey: nil)]) |> deliverOnMainQueue).start(next: { [weak strongSelf] _ in strongSelf?.scrollToLatest(true) }) case .requestPhone: - strongSelf.shareSelfContact(nil) + FastSettings.requstPermission(with: .contact, for: keyboardMessage.id.peerId, success: { [weak strongSelf] in + strongSelf?.shareSelfContact(nil) + if attribute.flags.contains(.once) { + strongSelf?.update({$0.updatedInterfaceState({$0.withUpdatedMessageActionsState({$0.withUpdatedClosedButtonKeyboardMessageId(keyboardMessage.id)})})}) + } + }) + + return case .openWebApp: strongSelf.requestMessageActionCallback(keyboardMessage.id, true, nil) case let .callback(data): strongSelf.requestMessageActionCallback(keyboardMessage.id, false, data) case let .switchInline(samePeer: same, query: query): - let text = "@\(keyboardMessage.inlinePeer?.username ?? "") \(query)" + let text = "@\(keyboardMessage.inlinePeer?.username ?? keyboardMessage.author?.username ?? "") \(query)" if same { strongSelf.updateInput(with: text) } else { - if let peer = keyboardMessage.inlinePeer { - strongSelf.account.context.mainNavigation?.set(modalAction: ShareInlineResultNavigationAction(payload: text, botName: peer.displayTitle), strongSelf.account.context.layout != .single) - if strongSelf.account.context.layout == .single { - strongSelf.account.context.mainNavigation?.push(ForwardChatListController(strongSelf.account)) + if let peer = keyboardMessage.inlinePeer ?? keyboardMessage.effectiveAuthor { + strongSelf.context.sharedContext.bindings.rootNavigation().set(modalAction: ShareInlineResultNavigationAction(payload: text, botName: peer.displayTitle), strongSelf.context.sharedContext.layout != .single) + if strongSelf.context.sharedContext.layout == .single { + strongSelf.context.sharedContext.bindings.rootNavigation().push(ForwardChatListController(strongSelf.context)) } } + } case .payment: - alert(for: mainWindow, info: tr(.paymentsUnsupported)) + alert(for: strongSelf.context.window, info: L10n.paymentsUnsupported) + case let .urlAuth(url, buttonId): + let context = strongSelf.context + _ = showModalProgress(signal: requestMessageActionUrlAuth(account: strongSelf.context.account, messageId: keyboardMessage.id, buttonId: buttonId), for: context.window).start(next: { result in + switch result { + case let .accepted(url): + execute(inapp: inApp(for: url.nsstring, context: strongSelf.context, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.sendPlainText, applyProxy: strongSelf.applyProxy)) + case .default: + execute(inapp: inApp(for: url.nsstring, context: strongSelf.context, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.sendPlainText, applyProxy: strongSelf.applyProxy, confirm: true)) + case let .request(requestURL, peer, writeAllowed): + showModal(with: InlineLoginController(context: context, url: requestURL, originalURL: url, writeAllowed: writeAllowed, botPeer: peer, authorize: { allowWriteAccess in + _ = showModalProgress(signal: acceptMessageActionUrlAuth(account: context.account, messageId: keyboardMessage.id, buttonId: buttonId, allowWriteAccess: allowWriteAccess), for: context.window).start(next: { result in + switch result { + case .default: + execute(inapp: inApp(for: url.nsstring, context: strongSelf.context, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.sendPlainText, applyProxy: strongSelf.applyProxy, confirm: true)) + case let .accepted(url): + execute(inapp: inApp(for: url.nsstring, context: strongSelf.context, openInfo: strongSelf.openInfo, hashtag: strongSelf.modalSearch, command: strongSelf.sendPlainText, applyProxy: strongSelf.applyProxy)) + default: + break + } + }) + }), for: context.window) + } + }) + case let .setupPoll(isQuiz): + showModal(with: NewPollController(chatInteraction: strongSelf, isQuiz: isQuiz), for: strongSelf.context.window) default: break } @@ -298,14 +431,16 @@ final class ChatInteraction : InterfaceObserver { return ReplyMarkupInteractions(proccess: {_,_ in}) } + + public func saveState(_ force:Bool = true, scrollState: ChatInterfaceHistoryScrollState? = nil) { let timestamp = Int32(Date().timeIntervalSince1970) let interfaceState = presentation.interfaceState.withUpdatedTimestamp(timestamp).withUpdatedHistoryScrollState(scrollState) - var s:Signal = updatePeerChatInterfaceState(account: account, peerId: peerId, state: interfaceState) - if !force { + var s:Signal = updatePeerChatInterfaceState(account: context.account, peerId: peerId, state: interfaceState) + if !force && !interfaceState.inputState.inputText.isEmpty { s = s |> delay(10, queue: Queue.mainQueue()) } @@ -315,12 +450,17 @@ final class ChatInteraction : InterfaceObserver { deinit { + clean() + } + + func clean() { addContactDisposable.dispose() mediaDisposable.dispose() startBotDisposable.dispose() requestSessionId.dispose() disableProxyDisposable.dispose() enableProxyDisposable.dispose() + editDisposable.dispose() } diff --git a/Telegram-Mac/ChatInterfaceState.swift b/Telegram-Mac/ChatInterfaceState.swift index 4e02c130a7..2de1bb1240 100644 --- a/Telegram-Mac/ChatInterfaceState.swift +++ b/Telegram-Mac/ChatInterfaceState.swift @@ -8,47 +8,49 @@ // import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + +struct ChatTextFontAttributes: OptionSet { + var rawValue: Int32 = 0 + + static let bold = ChatTextFontAttributes(rawValue: 1 << 0) + static let italic = ChatTextFontAttributes(rawValue: 1 << 1) + static let monospace = ChatTextFontAttributes(rawValue: 1 << 2) + static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3) +} -import PostboxMac -import SwiftSignalKitMac -import TelegramCoreMac -struct ChatInterfaceSelectionState: PostboxCoding, Equatable { +struct ChatInterfaceSelectionState: Equatable { let selectedIds: Set + let lastSelectedId: MessageId? - static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { - return lhs.selectedIds == rhs.selectedIds - } - - init(selectedIds: Set) { + init(selectedIds: Set, lastSelectedId: MessageId?) { self.selectedIds = selectedIds + self.lastSelectedId = lastSelectedId } - - init(decoder: PostboxDecoder) { - if let data = decoder.decodeBytesForKeyNoCopy("i") { - self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data)) - } else { - self.selectedIds = Set() - } + func withUpdatedSelectedIds(_ ids: Set) -> ChatInterfaceSelectionState { + return ChatInterfaceSelectionState(selectedIds: ids, lastSelectedId: self.lastSelectedId) } - - func encode(_ encoder: PostboxEncoder) { - let buffer = WriteBuffer() - MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer) - encoder.encodeBytes(buffer, forKey: "i") + func withUpdatedLastSelected(_ lastSelectedId: MessageId?) -> ChatInterfaceSelectionState { + return ChatInterfaceSelectionState(selectedIds: self.selectedIds, lastSelectedId: lastSelectedId) } } enum ChatTextInputAttribute : Equatable, PostboxCoding { case bold(Range) + case strikethrough(Range) case italic(Range) case pre(Range) case code(Range) case uid(Range, Int32) - + case url(Range, String) init(decoder: PostboxDecoder) { - let range = Range(Int(decoder.decodeInt32ForKey("start", orElse: 0)) ..< Int(decoder.decodeInt32ForKey("end", orElse: 0))) + let range = Int(decoder.decodeInt32ForKey("start", orElse: 0)) ..< Int(decoder.decodeInt32ForKey("end", orElse: 0)) // Range() let type: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) switch type { @@ -62,6 +64,10 @@ enum ChatTextInputAttribute : Equatable, PostboxCoding { self = .uid(range, decoder.decodeInt32ForKey("uid", orElse: 0)) case 4: self = .code(range) + case 5: + self = .url(range, decoder.decodeStringForKey("url", orElse: "")) + case 6: + self = .strikethrough(range) default: fatalError("input attribute not supported") } @@ -79,9 +85,14 @@ enum ChatTextInputAttribute : Equatable, PostboxCoding { encoder.encodeInt32(2, forKey: "_rawValue") case .code: encoder.encodeInt32(4, forKey: "_rawValue") + case .strikethrough: + encoder.encodeInt32(6, forKey: "_rawValue") case let .uid(_, uid): encoder.encodeInt32(3, forKey: "_rawValue") encoder.encodeInt32(uid, forKey: "uid") + case let .url(_, url): + encoder.encodeInt32(5, forKey: "_rawValue") + encoder.encodeString(url, forKey: "url") } } @@ -91,61 +102,34 @@ extension ChatTextInputAttribute { var attribute:(String, Any, NSRange) { switch self { case let .bold(range): - return (NSAttributedStringKey.font.rawValue, NSFont.bold(.text), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + return (NSAttributedString.Key.font.rawValue, NSFont.bold(.text), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + case let .strikethrough(range): + return (NSAttributedString.Key.font.rawValue, NSFont.normal(.text), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) case let .italic(range): - return (NSAttributedStringKey.font.rawValue, NSFontManager.shared.convert(.normal(.text), toHaveTrait: .italicFontMask), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + return (NSAttributedString.Key.font.rawValue, NSFontManager.shared.convert(.normal(.text), toHaveTrait: .italicFontMask), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) case let .pre(range), let .code(range): - return (NSAttributedStringKey.font.rawValue, NSFont.code(.text), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + return (NSAttributedString.Key.font.rawValue, NSFont.code(.text), NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) case let .uid(range, uid): - let tag = TGInputTextTag(uniqueId: Int64(arc4random()), attachment: NSNumber(value: uid), attribute: TGInputTextAttribute(name: NSAttributedStringKey.foregroundColor.rawValue, value: theme.colors.link)) - return (TGMentionUidAttributeName, tag, NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + let tag = TGInputTextTag(uniqueId: Int64(arc4random()), attachment: NSNumber(value: uid), attribute: TGInputTextAttribute(name: NSAttributedString.Key.foregroundColor.rawValue, value: theme.colors.link)) + return (TGCustomLinkAttributeName, tag, NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) + case let .url(range, url): + let tag = TGInputTextTag(uniqueId: Int64(arc4random()), attachment: url, attribute: TGInputTextAttribute(name: NSAttributedString.Key.foregroundColor.rawValue, value: theme.colors.link)) + return (TGCustomLinkAttributeName, tag, NSMakeRange(range.lowerBound, range.upperBound - range.lowerBound)) } } var range:Range { switch self { - case let .bold(range), let .italic(range), let .pre(range), let .code(range): + case let .bold(range), let .italic(range), let .pre(range), let .code(range), let .strikethrough(range): return range case let .uid(range, _): return range + case let .url(range, _): + return range } } } -func ==(lhs: ChatTextInputAttribute, rhs: ChatTextInputAttribute) -> Bool { - switch lhs { - case let .bold(range): - if case .bold(range) = rhs { - return true - } else { - return false - } - case let .italic(range): - if case .italic(range) = rhs { - return true - } else { - return false - } - case let .pre(range): - if case .pre(range) = rhs { - return true - } else { - return false - } - case let .code(range): - if case .code(range) = rhs { - return true - } else { - return false - } - case let .uid(range, uid): - if case .uid(range, uid) = rhs { - return true - } else { - return false - } - } -} func chatTextAttributes(from entities:TextEntitiesMessageAttribute) -> [ChatTextInputAttribute] { var inputAttributes:[ChatTextInputAttribute] = [] @@ -161,6 +145,10 @@ func chatTextAttributes(from entities:TextEntitiesMessageAttribute) -> [ChatText inputAttributes.append(.pre(entity.range)) case let .TextMention(peerId: peerId): inputAttributes.append(.uid(entity.range, peerId.id)) + case let .TextUrl(url): + inputAttributes.append(.url(entity.range, url)) + case .Strikethrough: + inputAttributes.append(.strikethrough(entity.range)) default: break } @@ -172,45 +160,55 @@ func chatTextAttributes(from attributed:NSAttributedString) -> [ChatTextInputAtt var inputAttributes:[ChatTextInputAttribute] = [] - attributed.enumerateAttribute(NSAttributedStringKey.font, in: NSMakeRange(0, attributed.length), options: .init(rawValue: 0)) { font, range, _ in - if let font = font as? NSFont { - let descriptor = font.fontDescriptor - let symTraits = descriptor.symbolicTraits - let traitSet = NSFontTraitMask(rawValue: UInt(symTraits.rawValue)) - let isBold = traitSet.contains(.boldFontMask) - let isItalic = traitSet.contains(.italicFontMask) - let isMonospace = font.fontName == "Menlo-Regular" - - if isBold { - inputAttributes.append(.bold(range.location ..< range.location + range.length)) - } else if isItalic { - inputAttributes.append(.italic(range.location ..< range.location + range.length)) - } else if isMonospace { - inputAttributes.append(.code(range.location ..< range.location + range.length)) + + attributed.enumerateAttributes(in: attributed.range, options: []) { (keys, range, _) in + for (_, value) in keys { + if let font = value as? NSFont { + let descriptor = font.fontDescriptor + let symTraits = descriptor.symbolicTraits + let traitSet = NSFontTraitMask(rawValue: UInt(symTraits.rawValue)) + let isBold = traitSet.contains(.boldFontMask) + let isItalic = traitSet.contains(.italicFontMask) + let isMonospace = font.fontName == "Menlo-Regular" + + if isItalic { + inputAttributes.append(.italic(range.location ..< range.location + range.length)) + } + if isBold { + inputAttributes.append(.bold(range.location ..< range.location + range.length)) + } + if isMonospace { + inputAttributes.append(.code(range.location ..< range.location + range.length)) + } + } else if let tag = value as? TGInputTextTag { + if let uid = tag.attachment as? NSNumber { + inputAttributes.append(.uid(range.location ..< range.location + range.length, uid.int32Value)) + } else if let url = tag.attachment as? String { + inputAttributes.append(.url(range.location ..< range.location + range.length, url)) + } } } } - attributed.enumerateAttribute(NSAttributedStringKey(rawValue: TGMentionUidAttributeName), in: NSMakeRange(0, attributed.length), options: .init(rawValue: 0)) { tag, range, _ in - if let tag = tag as? TGInputTextTag, let uid = tag.attachment as? NSNumber { - inputAttributes.append(.uid(range.location ..< range.location + range.length, uid.int32Value)) - } - } - return inputAttributes + + return Array(inputAttributes.prefix(100)) } -private let markdownRegexFormat = "(^|\\s)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`)([^\\n]+?)\\7([\\s\\.,:?!;]|$)" +//x/m +private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)" + + private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines]) -struct ChatTextInputState: PostboxCoding, Equatable { +final class ChatTextInputState: PostboxCoding, Equatable { + static func == (lhs: ChatTextInputState, rhs: ChatTextInputState) -> Bool { + return lhs.selectionRange == rhs.selectionRange && lhs.attributes == rhs.attributes && lhs.inputText == rhs.inputText + } + let inputText: String let attributes:[ChatTextInputAttribute] let selectionRange: Range - static func ==(lhs: ChatTextInputState, rhs: ChatTextInputState) -> Bool { - return lhs.inputText == rhs.inputText && lhs.selectionRange == rhs.selectionRange && lhs.attributes == rhs.attributes - } - init() { self.inputText = "" self.selectionRange = 0 ..< 0 @@ -244,24 +242,120 @@ struct ChatTextInputState: PostboxCoding, Equatable { var attributedString:NSAttributedString { let string = NSMutableAttributedString() - _ = string.append(string: inputText, color: theme.colors.text, font: .normal(.text), coreText: false) + _ = string.append(string: inputText, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false) + + + string.fixEmojiesFont(theme.fontSize) + + var fontAttributes: [NSRange: ChatTextFontAttributes] = [:] + + loop: for attribute in attributes { + let attr = attribute.attribute + + inner: switch attribute { + case .bold: + if let fontAttribute = fontAttributes[attr.2] { + fontAttributes[attr.2] = fontAttribute.union(.bold) + } else { + fontAttributes[attr.2] = .bold + } + continue loop + case .italic: + if let fontAttribute = fontAttributes[attr.2] { + fontAttributes[attr.2] = fontAttribute.union(.italic) + } else { + fontAttributes[attr.2] = .italic + } + continue loop + case .pre, .code: + if let fontAttribute = fontAttributes[attr.2] { + fontAttributes[attr.2] = fontAttribute.union(.monospace) + } else { + fontAttributes[attr.2] = .monospace + } + continue loop + default: + break inner + } + + string.addAttribute(NSAttributedString.Key(rawValue: attr.0), value: attr.1, range: attr.2) + } + for (range, fontAttributes) in fontAttributes { + var font: NSFont? + if fontAttributes.contains(.blockQuote) { + font = .code(theme.fontSize) + } else if fontAttributes == [.bold, .italic] { + font = .boldItalic(theme.fontSize) + } else if fontAttributes == [.bold] { + font = .bold(theme.fontSize) + } else if fontAttributes == [.italic] { + font = .italic(theme.fontSize) + }else if fontAttributes == [.monospace] { + font = .code(theme.fontSize) + } + if let font = font { + string.addAttribute(.font, value: font, range: range) + } + } + return string.copy() as! NSAttributedString + } + + func makeAttributeString(addPreAsBlock: Bool = false) -> NSAttributedString { + let string = NSMutableAttributedString() + _ = string.append(string: inputText, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false) + var pres:[Range] = [] + var strikethrough:[Range] = [] + for attribute in attributes { let attr = attribute.attribute - string.addAttribute(NSAttributedStringKey(rawValue: attr.0), value: attr.1, range: attr.2) + + switch attribute { + case let .pre(range): + if addPreAsBlock { + pres.append(range) + } else { + string.addAttribute(NSAttributedString.Key(rawValue: attr.0), value: attr.1, range: attr.2) + } + case let .strikethrough(range): + strikethrough.append(range) + default: + string.addAttribute(NSAttributedString.Key(rawValue: attr.0), value: attr.1, range: attr.2) + } } + if addPreAsBlock { + var offset: Int = 0 + for pre in pres.sorted(by: { $0.lowerBound < $1.lowerBound }) { + let symbols = "```" + string.insert(.initialize(string: symbols, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false), at: pre.lowerBound + offset) + offset += symbols.count + string.insert(.initialize(string: symbols, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false), at: pre.upperBound + offset) + offset += symbols.count + } + for strikethrough in strikethrough.sorted(by: { $0.lowerBound < $1.lowerBound }) { + let symbols = "~~" + string.insert(.initialize(string: symbols, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false), at: strikethrough.lowerBound + offset) + offset += symbols.count + string.insert(.initialize(string: symbols, color: theme.colors.text, font: .normal(theme.fontSize), coreText: false), at: strikethrough.upperBound + offset) + offset += symbols.count + } + } + return string.copy() as! NSAttributedString } func subInputState(from range: NSRange) -> ChatTextInputState { - var subText = inputText.nsstring.substring(with: range) + var subText = attributedString.attributedSubstring(from: range).trimmed - var raw:String = subText + let localAttributes = chatTextAttributes(from: subText) - var attributes:[ChatTextInputAttribute] = [] + var raw:String = subText.string + var appliedText = subText.string + var attributes:[ChatTextInputAttribute] = [] + var offsetRanges:[(NSRange, Int)] = [] if let regex = markdownRegex { var rawOffset:Int = 0 @@ -275,63 +369,93 @@ struct ChatTextInputState: PostboxCoding, Equatable { if pre.location != NSNotFound { - let text = raw.nsstring.substring(with: pre) + let text = raw.nsstring.substring(with: pre).trimmed rawOffset -= match.range(at: 2).length + match.range(at: 4).length newText.append(raw.nsstring.substring(with: match.range(at: 1)) + text + raw.nsstring.substring(with: match.range(at: 5))) attributes.append(.pre(matchIndex + match.range(at: 1).length ..< matchIndex + match.range(at: 1).length + text.length)) + offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 1).length, text.length), 6)) } pre = match.range(at: 8) if pre.location != NSNotFound { let text = raw.nsstring.substring(with: pre) - newText.append(raw.nsstring.substring(with: match.range(at: 6)) + text + raw.nsstring.substring(with: match.range(at: 9))) - attributes.append(.code(matchIndex + match.range(at: 6).length ..< matchIndex + match.range(at: 6).length + text.length)) + let entity = raw.nsstring.substring(with: match.range(at: 7)) + + newText.append(raw.nsstring.substring(with: match.range(at: 6)) + text + raw.nsstring.substring(with: match.range(at: 9))) + + switch entity { + case "`": + attributes.append(.code(matchIndex + match.range(at: 6).length ..< matchIndex + match.range(at: 6).length + text.length)) + offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.length), match.range(at: 6).length * 2)) + case "**": + offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.length), 4)) + attributes.append(.bold(matchIndex + match.range(at: 6).length ..< matchIndex + match.range(at: 6).length + text.length)) + case "~~": + offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.length), 4)) + attributes.append(.strikethrough(matchIndex + match.range(at: 6).length ..< matchIndex + match.range(at: 6).length + text.length)) + case "__": + offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 6).length, text.length), 4)) + attributes.append(.italic(matchIndex + match.range(at: 6).length ..< matchIndex + match.range(at: 6).length + text.length)) + default: + break + } + rawOffset -= match.range(at: 7).length * 2 } - raw = raw.nsstring.substring(from: match.range.location + match.range(at: 0).length) rawOffset += match.range.location + match.range(at: 0).length } newText.append(raw) - subText = newText.joined() + appliedText = newText.joined() } - for attr in self.attributes { - let newRange = Range(attr.range.lowerBound - range.location ..< attr.range.upperBound - range.location) - if newRange.lowerBound >= range.location && newRange.upperBound <= range.location + range.length { + for attr in localAttributes { + var newRange = NSMakeRange(attr.range.lowerBound, (attr.range.upperBound - attr.range.lowerBound)) //Range(attr.range.lowerBound - range.location ..< attr.range.upperBound - range.location) + for offsetRange in offsetRanges { + if offsetRange.0.max < newRange.location { + newRange.location -= offsetRange.1 + } + } + //if newRange.lowerBound >= range.location && newRange.upperBound <= range.location + range.length { switch attr { case .bold: - attributes.append(.bold(newRange)) + attributes.append(.bold(newRange.min ..< newRange.max)) case .italic: - attributes.append(.italic(newRange)) + attributes.append(.italic(newRange.min ..< newRange.max)) case .pre: - attributes.append(.pre(newRange)) + attributes.append(.pre(newRange.min ..< newRange.max)) case .code: - attributes.append(.code(newRange)) + attributes.append(.code(newRange.min ..< newRange.max)) + case .strikethrough: + attributes.append(.strikethrough(newRange.min ..< newRange.max)) case let .uid(_, uid): - attributes.append(.uid(newRange, uid)) + attributes.append(.uid(newRange.min ..< newRange.max, uid)) + case let .url(_, url): + attributes.append(.url(newRange.min ..< newRange.max, url)) } - } + // } } - return ChatTextInputState(inputText: subText, selectionRange: 0 ..< 0, attributes: attributes) + return ChatTextInputState(inputText: appliedText, selectionRange: 0 ..< 0, attributes: attributes) } - var messageTextEntities:[MessageTextEntity] { + func messageTextEntities(_ detectLinks: ParsingType = [.Hashtags]) -> [MessageTextEntity] { var entities:[MessageTextEntity] = [] for attribute in attributes { switch attribute { case let .bold(range): entities.append(.init(range: range, type: .Bold)) + case let .strikethrough(range): + entities.append(.init(range: range, type: .Strikethrough)) case let .italic(range): entities.append(.init(range: range, type: .Italic)) case let .pre(range): @@ -340,8 +464,29 @@ struct ChatTextInputState: PostboxCoding, Equatable { entities.append(.init(range: range, type: .Code)) case let .uid(range, uid): entities.append(.init(range: range, type: .TextMention(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: uid)))) + case let .url(range, url): + entities.append(.init(range: range, type: .TextUrl(url: url))) } } + + let attr = NSMutableAttributedString(string: inputText) + attr.detectLinks(type: detectLinks) + + attr.enumerateAttribute(NSAttributedString.Key.link, in: attr.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in + if let value = value as? inAppLink { + switch value { + case let .external(link, _): + if link.hasPrefix("#") { + entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Hashtag)) + } else if detectLinks.contains(.Links) { + entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Url)) + } + default: + break + } + } + }) + return entities } @@ -365,6 +510,11 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId + + if processedSetupReplyMessageId?.id == 349 { + var bp:Int = 0 + bp += 1 + } } init(decoder: PostboxDecoder) { @@ -376,6 +526,11 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { if let processedMessageIdPeerId = (decoder.decodeOptionalInt64ForKey("pb.p") as Int64?), let processedMessageIdNamespace = (decoder.decodeOptionalInt32ForKey("pb.n") as Int32?), let processedMessageIdId = (decoder.decodeOptionalInt32ForKey("pb.i") as Int32?) { self.processedSetupReplyMessageId = MessageId(peerId: PeerId(processedMessageIdPeerId), namespace: processedMessageIdNamespace, id: processedMessageIdId) + + if processedMessageIdId == 349 { + var bp:Int = 0 + bp += 1 + } } else { self.processedSetupReplyMessageId = nil } @@ -403,9 +558,6 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { } } - static func ==(lhs: ChatInterfaceMessageActionsState, rhs: ChatInterfaceMessageActionsState) -> Bool { - return lhs.closedButtonKeyboardMessageId == rhs.closedButtonKeyboardMessageId && lhs.processedSetupReplyMessageId == rhs.processedSetupReplyMessageId - } func withUpdatedClosedButtonKeyboardMessageId(_ closedButtonKeyboardMessageId: MessageId?) -> ChatInterfaceMessageActionsState { return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId) @@ -478,23 +630,109 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { } } +enum EditStateLoading : Equatable { + case none + case loading + case progress(Float) +} + +final class ChatEditState : Equatable { + let inputState:ChatTextInputState + let originalMedia: Media? + let message:Message + let editMedia: RequestEditMessageMedia + let loadingState: EditStateLoading + let editedData: EditedImageData? + + init(message:Message, originalMedia: Media? = nil, state:ChatTextInputState? = nil, loadingState: EditStateLoading = .none, editMedia: RequestEditMessageMedia = .keep, editedData: EditedImageData? = nil) { + self.message = message + if originalMedia == nil { + self.originalMedia = message.media.first + } else { + self.originalMedia = originalMedia + } + if let state = state { + self.inputState = state + } else { + var attribute:TextEntitiesMessageAttribute? + for attr in message.attributes { + if let attr = attr as? TextEntitiesMessageAttribute { + attribute = attr + } + } + var attributes:[ChatTextInputAttribute] = [] + if let attribute = attribute { + attributes = chatTextAttributes(from: attribute) + } + let temporaryState = ChatTextInputState(inputText:message.text, selectionRange: 0 ..< 0, attributes: attributes) + + + let newText = temporaryState.makeAttributeString(addPreAsBlock: true) + + self.inputState = ChatTextInputState(inputText: newText.string, selectionRange: newText.string.length ..< newText.string.length, attributes: chatTextAttributes(from: newText)) + + } + self.loadingState = loadingState + self.editMedia = editMedia + self.editedData = editedData + } + + var canEditMedia: Bool { + return !message.media.isEmpty && (message.media[0] is TelegramMediaImage || message.media[0] is TelegramMediaFile) + } + func withUpdatedMedia(_ media: Media) -> ChatEditState { + + return ChatEditState(message: self.message.withUpdatedMedia([media]), originalMedia: self.originalMedia ?? self.message.media.first, state: self.inputState, loadingState: loadingState, editMedia: .update(AnyMediaReference.standalone(media: media)), editedData: self.editedData) + } + func withUpdatedLoadingState(_ loadingState: EditStateLoading) -> ChatEditState { + return ChatEditState(message: self.message, originalMedia: self.originalMedia, state: self.inputState, loadingState: loadingState, editMedia: self.editMedia, editedData: self.editedData) + } + func withUpdated(state:ChatTextInputState) -> ChatEditState { + return ChatEditState(message: self.message, originalMedia: self.originalMedia, state: state, loadingState: loadingState, editMedia: self.editMedia, editedData: self.editedData) + } + + func withUpdatedEditedData(_ editedData: EditedImageData?) -> ChatEditState { + return ChatEditState(message: self.message, originalMedia: self.originalMedia, state: self.inputState, loadingState: self.loadingState, editMedia: self.editMedia, editedData: editedData) + } + + static func ==(lhs:ChatEditState, rhs:ChatEditState) -> Bool { + return lhs.message.id == rhs.message.id && lhs.inputState == rhs.inputState && lhs.loadingState == rhs.loadingState && lhs.editMedia == rhs.editMedia && lhs.editedData == rhs.editedData + } + +} + -final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { +struct ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { + static func == (lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool { + + return lhs.associatedMessageIds == rhs.associatedMessageIds && lhs.historyScrollMessageIndex == rhs.historyScrollMessageIndex && lhs.historyScrollState == rhs.historyScrollState && lhs.editState == rhs.editState && lhs.timestamp == rhs.timestamp && lhs.inputState == rhs.inputState && lhs.replyMessageId == rhs.replyMessageId && lhs.forwardMessageIds == rhs.forwardMessageIds && lhs.dismissedPinnedMessageId == rhs.dismissedPinnedMessageId && lhs.composeDisableUrlPreview == rhs.composeDisableUrlPreview && lhs.dismissedForceReplyId == rhs.dismissedForceReplyId && lhs.messageActionsState == rhs.messageActionsState && isEqualMessageList(lhs: lhs.forwardMessages, rhs: rhs.forwardMessages) + } + + + + var associatedMessageIds: [MessageId] { + return [] + } + + var historyScrollMessageIndex: MessageIndex? { return self.historyScrollState?.messageIndex } let historyScrollState: ChatInterfaceHistoryScrollState? - + let editState:ChatEditState? let timestamp: Int32 let inputState: ChatTextInputState let replyMessageId: MessageId? + let replyMessage: Message? + let forwardMessageIds: [MessageId] + let forwardMessages: [Message] let dismissedPinnedMessageId:MessageId? let composeDisableUrlPreview: String? - + let dismissedForceReplyId: MessageId? let messageActionsState: ChatInterfaceMessageActionsState var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? { @@ -506,15 +744,21 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { } var synchronizeableInputState: SynchronizeableChatInputState? { - if self.inputState.inputText.isEmpty { + if self.inputState.inputText.isEmpty && self.replyMessageId == nil { return nil } else { - return SynchronizeableChatInputState(replyToMessageId: self.replyMessageId, text: self.inputState.inputText, timestamp: self.timestamp) + return SynchronizeableChatInputState(replyToMessageId: self.replyMessageId, text: self.inputState.inputText, entities: self.inputState.messageTextEntities(), timestamp: self.timestamp) } } func withUpdatedSynchronizeableInputState(_ state: SynchronizeableChatInputState?) -> SynchronizeableChatInterfaceState { - return self.withUpdatedInputState(ChatTextInputState(inputText: state?.text ?? "")).withUpdatedReplyMessageId(state?.replyToMessageId) + var result = self.withUpdatedInputState(ChatTextInputState(inputText: state?.text ?? "")).withUpdatedReplyMessageId(state?.replyToMessageId) + + if let timestamp = state?.timestamp { + result = result.withUpdatedTimestamp(timestamp) + } + + return result } @@ -522,14 +766,18 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { self.timestamp = 0 self.inputState = ChatTextInputState() self.replyMessageId = nil + self.replyMessage = nil self.forwardMessageIds = [] + self.forwardMessages = [] self.messageActionsState = ChatInterfaceMessageActionsState() self.dismissedPinnedMessageId = nil self.composeDisableUrlPreview = nil self.historyScrollState = nil + self.dismissedForceReplyId = nil + self.editState = nil } - init(timestamp: Int32, inputState: ChatTextInputState, replyMessageId: MessageId?, forwardMessageIds: [MessageId], messageActionsState:ChatInterfaceMessageActionsState, dismissedPinnedMessageId: MessageId?, composeDisableUrlPreview: String?, historyScrollState: ChatInterfaceHistoryScrollState?) { + init(timestamp: Int32, inputState: ChatTextInputState, replyMessageId: MessageId?, replyMessage: Message?, forwardMessageIds: [MessageId], messageActionsState:ChatInterfaceMessageActionsState, dismissedPinnedMessageId: MessageId?, composeDisableUrlPreview: String?, historyScrollState: ChatInterfaceHistoryScrollState?, dismissedForceReplyId:MessageId?, editState: ChatEditState?, forwardMessages:[Message]) { self.timestamp = timestamp self.inputState = inputState self.replyMessageId = replyMessageId @@ -538,11 +786,15 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { self.dismissedPinnedMessageId = dismissedPinnedMessageId self.composeDisableUrlPreview = composeDisableUrlPreview self.historyScrollState = historyScrollState + self.dismissedForceReplyId = dismissedForceReplyId + self.editState = editState + self.replyMessage = replyMessage + self.forwardMessages = forwardMessages } init(decoder: PostboxDecoder) { self.timestamp = decoder.decodeInt32ForKey("ts", orElse: 0) - if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState { + if let inputState = decoder.decodeObjectForKey("is", decoder: { ChatTextInputState(decoder: $0) }) as? ChatTextInputState { self.inputState = inputState } else { self.inputState = ChatTextInputState() @@ -586,6 +838,18 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { self.historyScrollState = decoder.decodeObjectForKey("hss", decoder: { ChatInterfaceHistoryScrollState(decoder: $0) }) as? ChatInterfaceHistoryScrollState + let dismissedForceReplyIdPeerId: Int64? = decoder.decodeOptionalInt64ForKey("d.f.p") + let dismissedForceReplyIdNamespace: Int32? = decoder.decodeOptionalInt32ForKey("d.f.n") + let dismissedForceReplyIdId: Int32? = decoder.decodeOptionalInt32ForKey("d.f.i") + if let dismissedForceReplyIdPeerId = dismissedForceReplyIdPeerId, let dismissedForceReplyIdNamespace = dismissedForceReplyIdNamespace, let dismissedForceReplyIdId = dismissedForceReplyIdId { + self.dismissedForceReplyId = MessageId(peerId: PeerId(dismissedForceReplyIdPeerId), namespace: dismissedForceReplyIdNamespace, id: dismissedForceReplyIdId) + } else { + self.dismissedForceReplyId = nil + } + //TODO + self.editState = nil + self.replyMessage = nil + self.forwardMessages = [] } func encode(_ encoder: PostboxEncoder) { @@ -635,6 +899,17 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { encoder.encodeNil(forKey: "hss") } + if let dismissedForceReplyId = self.dismissedForceReplyId { + encoder.encodeInt64(dismissedForceReplyId.peerId.toInt64(), forKey: "d.f.p") + encoder.encodeInt32(dismissedForceReplyId.namespace, forKey: "d.f.n") + encoder.encodeInt32(dismissedForceReplyId.id, forKey: "d.f.i") + } else { + encoder.encodeNil(forKey: "d.f.p") + encoder.encodeNil(forKey: "d.f.n") + encoder.encodeNil(forKey: "d.f.i") + } + + //TODO } func isEqual(to: PeerChatInterfaceState) -> Bool { @@ -645,45 +920,65 @@ final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { } } - static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool { - return lhs.inputState == rhs.inputState && lhs.replyMessageId == rhs.replyMessageId && lhs.forwardMessageIds == rhs.forwardMessageIds && lhs.messageActionsState == rhs.messageActionsState && lhs.timestamp == rhs.timestamp && lhs.dismissedPinnedMessageId == rhs.dismissedPinnedMessageId && lhs.composeDisableUrlPreview == rhs.composeDisableUrlPreview && lhs.historyScrollState == rhs.historyScrollState - } - func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.editState == nil ? inputState : self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState?.withUpdated(state: inputState), forwardMessages: self.forwardMessages) } func withUpdatedDismissedPinnedId(_ dismissedPinnedId: MessageId?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: dismissedPinnedId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: dismissedPinnedId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) + } + + func withUpdatedDismissedForceReplyId(_ dismissedId: MessageId?) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: dismissedId, editState: self.editState, forwardMessages: self.forwardMessages) + } + + func updatedEditState(_ f:(ChatEditState?)->ChatEditState?) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: f(self.editState), forwardMessages: self.forwardMessages) + } + + func withEditMessage(_ message:Message) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: ChatEditState(message: message), forwardMessages: self.forwardMessages) + } + + func withoutEditMessage() -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: nil, forwardMessages: self.forwardMessages) } func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: replyMessageId, replyMessage: nil, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) + } + + func withUpdatedReplyMessage(_ replyMessage: Message?) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) + } + + func withUpdatedForwardMessages(_ forwardMessages: [Message]) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: forwardMessages) } func withoutForwardMessages() -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: [], messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: [], messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState:f(self.messageActionsState), dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState:f(self.messageActionsState), dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState: self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: disableUrlPreview, historyScrollState: self.historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState: self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: disableUrlPreview, historyScrollState: self.historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, messageActionsState: self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: historyScrollState) + return ChatInterfaceState(timestamp: self.timestamp, inputState: self.inputState, replyMessageId: self.replyMessageId, replyMessage: self.replyMessage, forwardMessageIds: self.forwardMessageIds, messageActionsState: self.messageActionsState, dismissedPinnedMessageId: self.dismissedPinnedMessageId, composeDisableUrlPreview: self.composeDisableUrlPreview, historyScrollState: historyScrollState, dismissedForceReplyId: self.dismissedForceReplyId, editState: self.editState, forwardMessages: self.forwardMessages) } diff --git a/Telegram-Mac/ChatInterfaceStateContextQueries.swift b/Telegram-Mac/ChatInterfaceStateContextQueries.swift index 7ec10fe526..31434e9c55 100644 --- a/Telegram-Mac/ChatInterfaceStateContextQueries.swift +++ b/Telegram-Mac/ChatInterfaceStateContextQueries.swift @@ -8,17 +8,18 @@ import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox -func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, currentQuery: ChatPresentationInputQuery?) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { +func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQuery: ChatPresentationInputQuery?) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { let inputQuery = chatPresentationInterfaceState.inputContext if inputQuery != .none { if inputQuery == currentQuery { return nil } else { - return makeInlineResult(inputQuery, chatPresentationInterfaceState: chatPresentationInterfaceState, currentQuery: currentQuery, account: account) + return makeInlineResult(inputQuery, chatPresentationInterfaceState: chatPresentationInterfaceState, currentQuery: currentQuery, context: context) } } else { @@ -26,27 +27,104 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation } } -private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPresentationInterfaceState: ChatPresentationInterfaceState, currentQuery: ChatPresentationInputQuery?, account:Account) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { +private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPresentationInterfaceState: ChatPresentationInterfaceState, currentQuery: ChatPresentationInputQuery?, context: AccountContext) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { switch inputQuery { case .none: return (nil, .single({ _ in return nil })) - case .hashtag(_): + case let .hashtag(query): + + var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete() + if let currentQuery = currentQuery { + switch currentQuery { + case .hashtag: + break + default: + signal = .single({ _ in return nil }) + } + } + + let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = recentlyUsedHashtags(postbox: context.account.postbox) |> map { hashtags -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let normalizedQuery = query.lowercased() + var result: [String] = [] + for hashtag in hashtags { + if hashtag.lowercased().hasPrefix(normalizedQuery) { + result.append(hashtag) + } + } + return { _ in return .hashtags(result) } + } + + return (inputQuery, signal |> then(hashtags)) - return (nil, .single({ _ in return nil })) case let .stickers(query): - return (inputQuery, searchStickers(postbox: account.postbox, query: query) |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - return { _ in return .stickers(stickers) } + return (inputQuery, context.account.postbox.transaction { transaction -> StickerSettings in + let stickerSettings: StickerSettings = (transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings + return stickerSettings + } + |> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], NoError> in + let scope: SearchStickersScope + switch stickerSettings.emojiStickerSuggestionMode { + case .none: + scope = [] + case .all: + scope = [.installed, .remote] + case .installed: + scope = [.installed] + } + return searchStickers(account: context.account, query: query, scope: scope) + } + |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + return { _ in + return .stickers(stickers) + } }) - case let .emoji(query): - return (inputQuery, searchEmojiClue(query: query, postbox: account.postbox) |> map { clues -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - return { _ in return .emoji(clues) } - }) +// return (inputQuery, searchStickers(account: account, query: query) |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in +// return { _ in return .stickers(stickers) } +// }) + case let .emoji(query, firstWord): + if !query.isEmpty { + let signal = context.sharedContext.inputSource.searchEmoji(postbox: context.account.postbox, sharedContext: context.sharedContext, query: query, completeMatch: query.length < 3, checkPrediction: firstWord) |> delay(firstWord ? 0.3 : 0, queue: .concurrentDefaultQueue()) + + if firstWord { + return (inputQuery, .single({ _ in return nil }) |> then(combineLatest(signal, recentUsedEmoji(postbox: context.account.postbox)) |> map { matches, emojies -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let sorted = matches.sorted(by: { lhs, rhs in + let lhsIndex = emojies.emojies.firstIndex(of: lhs) ?? Int.max + let rhsIndex = emojies.emojies.firstIndex(of: rhs) ?? Int.max + return lhsIndex < rhsIndex + }) + + return { _ in return .emoji(sorted, firstWord) } + })) + } else { + return (inputQuery, combineLatest(signal, recentUsedEmoji(postbox: context.account.postbox)) |> map { matches, emojies -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let sorted = matches.sorted(by: { lhs, rhs in + let lhsIndex = emojies.emojies.firstIndex(of: lhs) ?? Int.max + let rhsIndex = emojies.emojies.firstIndex(of: rhs) ?? Int.max + return lhsIndex < rhsIndex + }) + + + return { _ in return .emoji(sorted, firstWord) } + }) + } + + + } else { + if firstWord { + return (nil, .single({ _ in return nil })) + } else { + return (inputQuery, recentUsedEmoji(postbox: context.account.postbox) |> map { emojis -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + return { _ in return .emoji(emojis.emojies, firstWord) } + }) + } + } + case let .mention(query: query, includeRecent: includeRecent): let normalizedQuery = query.lowercased() - if let peer = chatPresentationInterfaceState.peer { + if let global = chatPresentationInterfaceState.peer { var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete() if let currentQuery = currentQuery { switch currentQuery { @@ -59,10 +137,37 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres var inlineSignal: Signal<[(Peer, Double)], NoError> = .single([]) if includeRecent { - inlineSignal = recentlyUsedInlineBots(postbox: account.postbox) + inlineSignal = recentlyUsedInlineBots(postbox: context.account.postbox) |> take(1) } - let participants = combineLatest(inlineSignal, peerParticipants(account: account, id: peer.id)) + let participants = combineLatest(inlineSignal, searchPeerMembers(context: context, peerId: global.id, query: query) |> take(1) |> mapToSignal { participants -> Signal<[Peer], NoError> in + return context.account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(.peer(global.id), count: 100, tagMask: nil, orderStatistics: [], additionalData: []) |> take(1) |> map { view in + let latestIds:[PeerId] = view.0.entries.reversed().compactMap({ entry in + if entry.message.media.first is TelegramMediaAction { + return nil + } + return entry.message.author?.id + }) + + let sorted = participants.sorted{ lhs, rhs in + let lhsIndex = latestIds.firstIndex(where: {$0 == lhs.id}) + let rhsIndex = latestIds.firstIndex(where: {$0 == rhs.id}) + if let lhsIndex = lhsIndex, let rhsIndex = rhsIndex { + return lhsIndex < rhsIndex + } else if lhsIndex == nil && rhsIndex != nil { + return false + } else if lhsIndex != nil && rhsIndex == nil { + return true + } else { + return lhs.displayTitle < rhs.displayTitle + } + + } + + return sorted + } + + }) |> map { recent, participants -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let filteredRecent = recent.filter ({ recent in @@ -79,6 +184,16 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres }).map {$0.0} let filteredParticipants = participants.filter ({ peer in + if peer.id == context.peerId { + return false + } + if peer.rawDisplayTitle.isEmpty { + return false + } + + if global.isChannel, let peer = peer as? TelegramUser, peer.botInfo?.inlinePlaceholder == nil { + return false + } if peer.indexName.matchesByTokens(normalizedQuery) { return true } @@ -86,9 +201,6 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres return true } return peer.addressName == nil && normalizedQuery.isEmpty - }).sorted(by: { lhs, rhs in - let result = lhs.indexName.indexName(.lastNameFirst).compare(rhs.indexName.indexName(.lastNameFirst)) - return result == .orderedAscending }) return { _ in return .mentions(filteredRecent + filteredParticipants) } @@ -112,7 +224,7 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres } } - let participants = peerCommands(account: account, id: peer.id) + let participants = peerCommands(account: context.account, id: peer.id) |> map { commands -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let filteredCommands = commands.commands.filter { command in if command.command.text.hasPrefix(normalizedQuery) { @@ -147,10 +259,10 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres } } - let contextBot = resolvePeerByName(account: account, name: addressName) + let contextBot = resolvePeerByName(account: context.account, name: addressName) |> mapToSignal { peerId -> Signal in if let peerId = peerId { - return account.postbox.loadedPeerWithId(peerId) + return context.account.postbox.loadedPeerWithId(peerId) |> map { peer -> Peer? in return peer } @@ -161,10 +273,10 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let contextResults = requestChatContextResults(account: account, botId: user.id, peerId: chatPeer.id, query: query, offset: "") + let contextResults = requestChatContextResults(account: context.account, botId: user.id, peerId: chatPeer.id, query: query, offset: "") |> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in return { _ in - return .contextRequestResult(user, results) + return .contextRequestResult(user, results?.results) } } @@ -182,9 +294,9 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> if delayRequest { - maybeDelayedContextResults = contextResults |> delay(0.4, queue: Queue.concurrentDefaultQueue()) + maybeDelayedContextResults = contextResults |> `catch` { _ in return .complete() } |> delay(0.4, queue: Queue.concurrentDefaultQueue()) } else { - maybeDelayedContextResults = contextResults + maybeDelayedContextResults = contextResults |> `catch` { _ in return .complete() } } return botResult |> then(maybeDelayedContextResults) @@ -195,10 +307,40 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres case let .mention(query: query, includeRecent: _): let normalizedQuery = query.lowercased() - if let peer = chatPresentationInterfaceState.peer { - return peerParticipants(account: account, id: peer.id) - |> map { participants -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + if let global = chatPresentationInterfaceState.peer { + return searchPeerMembers(context: context, peerId: global.id, query: normalizedQuery) |> take(1) |> mapToSignal { participants -> Signal<[Peer], NoError> in + return context.account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(.peer(global.id), count: 100, tagMask: nil, orderStatistics: [], additionalData: []) |> take(1) |> map { view in + let latestIds:[PeerId] = view.0.entries.reversed().compactMap({ entry in + if entry.message.media.first is TelegramMediaAction { + return nil + } + return entry.message.author?.id + }) + let sorted = participants.sorted{ lhs, rhs in + let lhsIndex = latestIds.firstIndex(where: {$0 == lhs.id}) + let rhsIndex = latestIds.firstIndex(where: {$0 == rhs.id}) + if let lhsIndex = lhsIndex, let rhsIndex = rhsIndex { + return lhsIndex < rhsIndex + } else if lhsIndex == nil && rhsIndex != nil { + return false + } else if lhsIndex != nil && rhsIndex == nil { + return true + } else { + return lhs.displayTitle < rhs.displayTitle + } + } + return sorted + } + + } |> map { participants -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let filteredParticipants = participants.filter ({ peer in + if peer.id == context.peerId { + return false + } + if global.isChannel, let peer = peer as? TelegramUser, peer.botInfo?.inlinePlaceholder == nil { + return false + } + if peer.indexName.matchesByTokens(normalizedQuery) { return true } @@ -206,9 +348,6 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres return true } return peer.addressName == nil && normalizedQuery.isEmpty - }).sorted(by: { lhs, rhs in - let result = lhs.indexName.indexName(.lastNameFirst).compare(rhs.indexName.indexName(.lastNameFirst)) - return result == .orderedAscending }) return { _ in return .mentions(filteredParticipants) } @@ -226,8 +365,13 @@ private func makeInlineResult(_ inputQuery: ChatPresentationInputQuery, chatPres } } +enum ContextQueryForSearchMentionFilter { + case plain(includeNameless: Bool, includeInlineBots: Bool) + case filterSelf(includeNameless: Bool, includeInlineBots: Bool) +} + -func chatContextQueryForSearchMention(peer: Peer, _ inputQuery: ChatPresentationInputQuery, currentQuery: ChatPresentationInputQuery?, account:Account) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { +func chatContextQueryForSearchMention(peer: Peer, _ inputQuery: ChatPresentationInputQuery, currentQuery: ChatPresentationInputQuery?, context: AccountContext, filter: ContextQueryForSearchMentionFilter = .plain(includeNameless: true, includeInlineBots: false)) -> (ChatPresentationInputQuery?, Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>)? { switch inputQuery { case let .mention(query: query, includeRecent: _): let normalizedQuery = query.lowercased() @@ -242,25 +386,114 @@ func chatContextQueryForSearchMention(peer: Peer, _ inputQuery: ChatPresentation } } - let participants = peerParticipants(account: account, id: peer.id) - |> map { participants -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - let filteredParticipants = participants.filter ({ peer in + let participants = searchPeerMembers(context: context, peerId: peer.id, query: normalizedQuery) |> take(1) |> mapToSignal { participants -> Signal<[Peer], NoError> in + return context.account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(.peer(peer.id), count: 100, tagMask: nil, orderStatistics: [], additionalData: []) |> take(1) |> map { view in + let latestIds:[PeerId] = view.0.entries.reversed().compactMap({ entry in + if entry.message.media.first is TelegramMediaAction { + return nil + } + return entry.message.author?.id + }) + + var sorted = participants.sorted{ lhs, rhs in + let lhsIndex = latestIds.firstIndex(where: {$0 == lhs.id}) + let rhsIndex = latestIds.firstIndex(where: {$0 == rhs.id}) + if let lhsIndex = lhsIndex, let rhsIndex = rhsIndex { + return lhsIndex < rhsIndex + } else if lhsIndex == nil && rhsIndex != nil { + return false + } else if lhsIndex != nil && rhsIndex == nil { + return true + } else { + return lhs.displayTitle < rhs.displayTitle + } + + } + + if let index = sorted.firstIndex(where: {$0.id == context.peerId}) { + sorted.move(at: index, to: 0) + } + + return sorted + } + + } |> map { participants -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let filteredParticipants = participants.filter { peer in + + switch filter { + case let .plain(includeNameless, includeInlineBots): + if !includeNameless, peer.addressName == nil || peer.addressName!.isEmpty { + return false + } + if !includeInlineBots, let peer = peer as? TelegramUser, peer.botInfo?.inlinePlaceholder != nil { + return false + } + case let .filterSelf(includeNameless, includeInlineBots): + if !includeNameless, peer.addressName == nil || peer.addressName!.isEmpty { + return false + } + if peer.id == context.peerId { + return false + } + + if !includeInlineBots, let peer = peer as? TelegramUser, peer.botInfo?.inlinePlaceholder != nil { + return false + } + } + if peer.displayTitle == L10n.peerDeletedUser { + return false + } if peer.indexName.matchesByTokens(normalizedQuery) { return true } if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) { return true } + return peer.addressName == nil && normalizedQuery.isEmpty - }).sorted(by: { lhs, rhs in - let result = lhs.indexName.indexName(.lastNameFirst).compare(rhs.indexName.indexName(.lastNameFirst)) - return result == .orderedAscending - }) + } return { _ in return .mentions(filteredParticipants) } } return (inputQuery, signal |> then(participants)) + case let .emoji(query, firstWord): + if !query.isEmpty { + let signal = context.sharedContext.inputSource.searchEmoji(postbox: context.account.postbox, sharedContext: context.sharedContext, query: query, completeMatch: query.length < 3, checkPrediction: firstWord) |> delay(firstWord ? 0.3 : 0, queue: .concurrentDefaultQueue()) + + if firstWord { + return (inputQuery, .single({ _ in return nil }) |> then(combineLatest(signal, recentUsedEmoji(postbox: context.account.postbox)) |> map { matches, emojies -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let sorted = matches.sorted(by: { lhs, rhs in + let lhsIndex = emojies.emojies.firstIndex(of: lhs) ?? Int.max + let rhsIndex = emojies.emojies.firstIndex(of: rhs) ?? Int.max + return lhsIndex < rhsIndex + }) + + return { _ in return .emoji(sorted, firstWord) } + })) + } else { + return (inputQuery, combineLatest(signal, recentUsedEmoji(postbox: context.account.postbox)) |> map { matches, emojies -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let sorted = matches.sorted(by: { lhs, rhs in + let lhsIndex = emojies.emojies.firstIndex(of: lhs) ?? Int.max + let rhsIndex = emojies.emojies.firstIndex(of: rhs) ?? Int.max + return lhsIndex < rhsIndex + }) + + + return { _ in return .emoji(sorted, firstWord) } + }) + } + + + } else { + if firstWord { + return (nil, .single({ _ in return nil })) + } else { + return (inputQuery, recentUsedEmoji(postbox: context.account.postbox) |> map { emojis -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + return { _ in return .emoji(emojis.emojies, firstWord) } + }) + } + } default: return (nil, .single({ _ in return nil })) } @@ -269,32 +502,142 @@ func chatContextQueryForSearchMention(peer: Peer, _ inputQuery: ChatPresentation private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) -func urlPreviewStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, account: Account, currentQuery: String?) -> (String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? { +func urlPreviewStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQuery: String?) -> Signal<(String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)?, NoError> { - if let dataDetector = dataDetector { - let text = chatPresentationInterfaceState.effectiveInput.inputText - let utf16 = text.utf16 + return Signal { subscriber in - var detectedUrl: String? + var detector = dataDetector + - let matches = dataDetector.matches(in: text, options: [], range: NSRange(location: 0, length: utf16.count)) - if let match = matches.first { - let urlText = (text as NSString).substring(with: match.range) - detectedUrl = urlText + if chatPresentationInterfaceState.state == .editing, let media = chatPresentationInterfaceState.interfaceState.editState?.message.media.first { + if media is TelegramMediaFile || media is TelegramMediaImage { + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + detector = nil + } } + - if detectedUrl != currentQuery { - if let detectedUrl = detectedUrl { - return (detectedUrl, webpagePreview(account: account, url: detectedUrl) |> map { value in - return { _ in return value } - }) + if let peer = chatPresentationInterfaceState.peer, peer.webUrlRestricted { + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + detector = nil + } + + if let dataDetector = detector { + + var detectedUrl: String? + + var detectedRange: NSRange = NSMakeRange(NSNotFound, 0) + let text = chatPresentationInterfaceState.effectiveInput.inputText.prefix(4096) + + var attr = chatPresentationInterfaceState.effectiveInput.attributedString + attr = attr.attributedSubstring(from: NSMakeRange(0, min(attr.length, 4096))) + attr.enumerateAttribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), in: attr.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in + + if let tag = value as? TGInputTextTag, let url = tag.attachment as? String { + detectedUrl = url + detectedRange = range + } + let s: ObjCBool = (detectedUrl != nil) ? true : false + stop.pointee = s + + }) + + let utf16 = text.utf16 + let matches = dataDetector.matches(in: text, options: [], range: NSRange(location: 0, length: utf16.count)) + if let match = matches.first { + let urlText = (text as NSString).substring(with: match.range) + if match.range.location < detectedRange.location { + detectedUrl = urlText + } + } + + if detectedUrl != currentQuery { + if let detectedUrl = detectedUrl { + let link = inApp(for: detectedUrl.nsstring, context: context, peerId: nil, openInfo: { _, _, _, _ in }, hashtag: { _ in }, command: { _ in }, applyProxy: { _ in }, confirm: false) + + + let invoke:(inAppLink)->Void = { link in + switch link { + case let .external(detectedUrl, _): + subscriber.putNext((detectedUrl, webpagePreview(account: context.account, url: detectedUrl) |> map { value in + return { _ in return value } + })) + case let .followResolvedName(_, username, _, _, _, _): + if username.hasPrefix("_private_") { + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + } else { + subscriber.putNext((detectedUrl, webpagePreview(account: context.account, url: detectedUrl) |> map { value in + return { _ in return value } + })) + } + default: + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + } + } + + if chatPresentationInterfaceState.chatLocation.peerId.namespace == Namespaces.Peer.SecretChat { + let value = FastSettings.isSecretChatWebPreviewAvailable(for: context.account.id.int64) + + if let value = value { + if !value { + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + return EmptyDisposable + } else { + invoke(link) + } + } else { + + var canLoad: Bool = false + switch link { + case .external: + canLoad = true + case let .followResolvedName(_, username, _, _, _, _): + if !username.hasPrefix("_private_") { + canLoad = true + } + default: + canLoad = false + } + + if canLoad { + confirm(for: context.window, header: L10n.chatSecretChatPreviewHeader, information: L10n.chatSecretChatPreviewText, okTitle: L10n.chatSecretChatPreviewOK, cancelTitle: L10n.chatSecretChatPreviewNO, successHandler: { result in + FastSettings.setSecretChatWebPreviewAvailable(for: context.account.id.int64, value: true) + invoke(link) + }, cancelHandler: { + FastSettings.setSecretChatWebPreviewAvailable(for: context.account.id.int64, value: false) + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + }) + } + + } + } else { + invoke(link) + } + + + } else { + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + } } else { - return (nil, .single({ _ in return nil })) + subscriber.putNext(nil) + subscriber.putCompletion() } } else { - return nil + subscriber.putNext((nil, .single({ _ in return nil }))) + subscriber.putCompletion() + } + + return ActionDisposable { + } - } else { - return (nil, .single({ _ in return nil })) } + + } diff --git a/Telegram-Mac/ChatInvoiceItem.swift b/Telegram-Mac/ChatInvoiceItem.swift index 5f4f99d795..35394626fe 100644 --- a/Telegram-Mac/ChatInvoiceItem.swift +++ b/Telegram-Mac/ChatInvoiceItem.swift @@ -7,25 +7,29 @@ // import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class ChatInvoiceItem: ChatRowItem { fileprivate let media:TelegramMediaInvoice fileprivate let textLayout:TextViewLayout fileprivate var arguments:TransformImageArguments? - override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { let message = object.message! + + let isIncoming: Bool = message.isIncoming(context.account, object.renderType == .bubble) + self.media = message.media[0] as! TelegramMediaInvoice let attr = NSMutableAttributedString() - _ = attr.append(string: media.description, color: theme.colors.text, font: .normal(.text)) - attr.detectLinks(type: [.Links]) + _ = attr.append(string: media.description, color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .normal(.text)) + attr.detectLinks(type: [.Links], color: theme.chat.linkColor(isIncoming, object.renderType == .bubble)) textLayout = TextViewLayout(attr) - super.init(initialSize, chatInteraction, account, object) + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) } @@ -37,10 +41,9 @@ class ChatInvoiceItem: ChatRowItem { for attr in photo.attributes { switch attr { - case .ImageSize(let size): - //videoSize.fitted() - contentSize = size.fitted(NSMakeSize(200, 200)) - arguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: size, boundingSize: contentSize, intrinsicInsets: NSEdgeInsets()) + case let .ImageSize(size): + contentSize = size.size.fitted(NSMakeSize(200, 200)) + arguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: size.size, boundingSize: contentSize, intrinsicInsets: NSEdgeInsets()) default: break @@ -85,10 +88,11 @@ class ChatInvoiceView : ChatRowView { textView.update(item.textLayout) if let photo = item.media.photo, let arguments = item.arguments { addSubview(imageView) - imageView.setSignal(account: item.account, signal: chatMessageWebFilePhoto(account: item.account, photo: photo, scale: backingScaleFactor)) + imageView.setSignal( chatMessageWebFilePhoto(account: item.context.account, photo: photo, scale: backingScaleFactor)) imageView.set(arguments: arguments) imageView.setFrameSize(arguments.boundingSize) - _ = item.account.postbox.mediaBox.fetchedResource(photo.resource, tag: nil).start() + _ = fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: photo.resource)).start() + // _ = item.account.postbox.mediaBox.fetchedResource(photo.resource, tag: nil).start() } else { imageView.removeFromSuperview() diff --git a/Telegram-Mac/ChatLayoutUtils.swift b/Telegram-Mac/ChatLayoutUtils.swift index 1080e6d095..5c13e6fdbf 100644 --- a/Telegram-Mac/ChatLayoutUtils.swift +++ b/Telegram-Mac/ChatLayoutUtils.swift @@ -7,39 +7,89 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChatLayoutUtils: NSObject { - static func contentSize(for media:Media, with width: CGFloat) -> NSSize { + static func contentSize(for media:Media, with width: CGFloat, hasText: Bool = false) -> NSSize { var size:NSSize = NSMakeSize(width, 40.0) let maxSize = NSMakeSize(min(width,320), min(width,320)) if let image = media as? TelegramMediaImage { - size = image.representationForDisplayAtSize(maxSize)?.dimensions.fitted(maxSize) ?? maxSize - size = NSMakeSize(max(40, size.width), max(40, size.height)) + size = image.representationForDisplayAtSize(PixelDimensions(maxSize))?.dimensions.size.fitted(maxSize) ?? maxSize + if size.width < 100 && size.height < 100 { + size = size.aspectFitted(NSMakeSize(200, 200)) + } + if hasText { + size.width = max(maxSize.width, size.width) + } + size.width = max(size.width, 100) + size = NSMakeSize(max(46, size.width), max(46, size.height)) } else if let file = media as? TelegramMediaFile { var contentSize:NSSize = NSZeroSize for attr in file.attributes { if case let .ImageSize(size) = attr { - contentSize = size + contentSize = size.size } else if case let .Video(_,video, _) = attr { - contentSize = video + contentSize = video.size + if contentSize.width < 50 && contentSize.height < 50 { + contentSize = maxSize + } + } else if case .Audio = attr { + return NSMakeSize(width, 40) } } - - if file.isSticker { - size = contentSize.aspectFitted(NSMakeSize(180, 180)) + if file.isAnimatedSticker { + let dimensions = file.dimensions?.size + size = NSMakeSize(240, 240) + if file.isEmojiAnimatedSticker { + size = NSMakeSize(112, 112) + } + if let dimensions = dimensions { + size = dimensions.aspectFitted(size) + } + } else if file.isStaticSticker { + if contentSize == NSZeroSize { + return NSMakeSize(210, 210) + } + size = contentSize.aspectFitted(NSMakeSize(210, 210)) + size = NSMakeSize(max(size.width, 40), max(size.height, 40)) } else if file.isInstantVideo { - size = contentSize.fitted(NSMakeSize(200, 200)) - } else if file.isVideo || file.isAnimated { - size = contentSize.fitted(maxSize) + size = NSMakeSize(250, 250) + } else if file.isVideo || (file.isAnimated && !file.mimeType.lowercased().hasSuffix("gif")) { + +// var contentSize = contentSize +// let addition = max(300 - contentSize.width, 300 - contentSize.height) +// if addition > 0 { +// contentSize.width += addition +// contentSize.height += addition +// } + + if file.isVideo && contentSize.width > contentSize.height { + size = contentSize.aspectFitted(NSMakeSize(min(420, width), contentSize.height)) + } else { + size = contentSize.fitted(maxSize) + if hasText { + // size.width = max(maxSize.width, size.width) + } + } + + + + + if hasText { + size.width = max(maxSize.width, size.width) + } + } else if contentSize.height > 0 { size = NSMakeSize(width, 70) + } else if !file.previewRepresentations.isEmpty { + size = NSMakeSize(width, 70) } } else if let media = media as? TelegramMediaMap { @@ -52,6 +102,8 @@ class ChatLayoutUtils: NSObject { if let file = media.file { return contentSize(for: file, with: width) } + } else if media is TelegramMediaDice { + size = NSMakeSize(128, 128) } return size @@ -62,13 +114,15 @@ class ChatLayoutUtils: NSObject { if media is TelegramMediaImage { return ChatInteractiveContentView.self } else if let file = media as? TelegramMediaFile { - if file.isSticker { + if file.isAnimatedSticker { + return MediaAnimatedStickerView.self + } else if file.isStaticSticker { return ChatStickerContentView.self } else if file.isInstantVideo { return ChatVideoMessageContentView.self } else if file.isVideo && !file.isAnimated { return ChatInteractiveContentView.self - } else if file.isAnimated { + } else if file.isAnimated && !file.mimeType.lowercased().hasSuffix("gif") { return ChatGIFContentView.self } else if file.isVoice { return ChatVoiceContentView.self @@ -79,6 +133,8 @@ class ChatLayoutUtils: NSObject { } } else if media is TelegramMediaMap { return ChatMapContentView.self + } else if media is TelegramMediaDice { + return ChatDiceContentView.self } else if let media = media as? TelegramMediaGame { if let file = media.file { return contentNode(for: file) diff --git a/Telegram-Mac/ChatListController.swift b/Telegram-Mac/ChatListController.swift index b5417509a8..aff1588a9b 100644 --- a/Telegram-Mac/ChatListController.swift +++ b/Telegram-Mac/ChatListController.swift @@ -8,110 +8,685 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore -extension ChatListEntry: Identifiable { - public var stableId: AnyHashable { +enum UIChatListEntryId : Hashable { + case chatId(PeerId, Int32?) + case groupId(PeerGroupId) + case reveal + case empty + case loading +} + +struct ChatListInputActivity : Equatable { + let peer: PeerEquatable + let activity: PeerInputActivity + init(_ peer: Peer, _ activity: PeerInputActivity) { + self.peer = PeerEquatable(peer) + self.activity = activity + } +} + +struct ChatListPeerInputActivities : Equatable { + let activities: [PeerId: [ChatListInputActivity]] + + init(activities: [PeerId: [ChatListInputActivity]]) { + self.activities = activities + } + func withUpdatedActivities(_ activities: [PeerId: [ChatListInputActivity]]) -> ChatListPeerInputActivities { + return ChatListPeerInputActivities(activities: activities) + } +} + +struct ChatListState: Equatable { + let activities: ChatListPeerInputActivities + + func updateActivities(_ f:(ChatListPeerInputActivities)->ChatListPeerInputActivities) -> ChatListState { + return ChatListState(activities: f(self.activities)) + } +} + +struct UIChatAdditionalItem : Equatable { + static func == (lhs: UIChatAdditionalItem, rhs: UIChatAdditionalItem) -> Bool { + return lhs.item.isEqual(to: rhs.item) && lhs.index == rhs.index + } + + let item: AdditionalChatListItem + let index: Int +} + + +enum UIChatListEntry : Identifiable, Comparable { + case chat(ChatListEntry, [ChatListInputActivity], UIChatAdditionalItem?, filter: ChatListFilter?) + case group(Int, PeerGroupId, [ChatListGroupReferencePeer], Message?, PeerGroupUnreadCountersCombinedSummary, TotalUnreadCountDisplayCategory, Bool, HiddenArchiveStatus) + case reveal([ChatListFilter], ChatListFilter?, ChatListFilterBadges) + case empty(ChatListFilter?) + case loading(ChatListFilter?) + static func == (lhs: UIChatListEntry, rhs: UIChatListEntry) -> Bool { + switch lhs { + case let .chat(entry, activity, additionItem, filter): + if case .chat(entry, activity, additionItem, filter) = rhs { + return true + } else { + return false + } + case let .group(index, groupId, peers, lhsMessage, unreadState, unreadCountDisplayCategory, animated, isHidden): + if case .group(index, groupId, peers, let rhsMessage, unreadState, unreadCountDisplayCategory, animated, isHidden) = rhs { + if let lhsMessage = lhsMessage, let rhsMessage = rhsMessage { + return isEqualMessages(lhsMessage, rhsMessage) + } else if (lhsMessage != nil) != (rhsMessage != nil) { + return false + } else { + return true + } + } else { + return false + } + case let .reveal(filters, current, counters): + if case .reveal(filters, current, counters) = rhs { + return true + } else { + return false + } + case let .empty(filter): + if case .empty(filter) = rhs { + return true + } else { + return false + } + case let .loading(filter): + if case .loading(filter) = rhs { + return true + } else { + return false + } + } + } + + var index: ChatListIndex { switch self { - case let .HoleEntry(hole): - return Int64(hole.index.id.id) - default: - return index.messageIndex.id.peerId.toInt64() + case let .chat(entry, _, additionItem, _): + if let additionItem = additionItem { + var current = MessageIndex.absoluteUpperBound().predecessor() + for _ in 0 ..< additionItem.index { + current = current.predecessor() + } + return ChatListIndex(pinningIndex: 0, messageIndex: current) + } + switch entry { + case let .HoleEntry(hole): + return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) + case let .MessageEntry(values): + return values.0 + } + case .reveal: + return ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex.absoluteUpperBound()) + case let .group(values): + var index = MessageIndex.absoluteUpperBound().predecessor() + for _ in 0 ..< values.0 { + index = index.predecessor() + } + return ChatListIndex(pinningIndex: 0, messageIndex: index) + case .empty: + return ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex.absoluteUpperBound().predecessor()) + case .loading: + return ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex.absoluteUpperBound().predecessor()) } } + + static func < (lhs: UIChatListEntry, rhs: UIChatListEntry) -> Bool { + return lhs.index < rhs.index + } + + var stableId: UIChatListEntryId { + switch self { + case let .chat(entry, _, _, filterId): + return .chatId(entry.index.messageIndex.id.peerId, filterId?.id) + case let .group(_, groupId, _, _, _, _, _, _): + return .groupId(groupId) + case .reveal: + return .reveal + case .empty: + return .empty + case .loading: + return .loading + } + } + } -fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, animated:Bool, scrollState:TableScrollState? = nil, onMainQueue: Bool = false) -> Signal { +fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], adIndex: UInt16?, context: AccountContext, initialSize:NSSize, animated:Bool, scrollState:TableScrollState? = nil, groupId: PeerGroupId, setupFilter: @escaping(ChatListFilter?)->Void, openFilterSettings: @escaping(ChatListFilter?)->Void, tabsMenuItems: @escaping(ChatListFilter?)->[ContextMenuItem]) -> Signal { return Signal { subscriber in - let (deleted,inserted,updated) = proccessEntries(from, right: to, { entry -> TableRowItem in - + var cancelled: Bool = false + + func makeItem(_ entry: AppearanceWrapperEntry) -> TableRowItem { switch entry.entry { - case let .HoleEntry(hole): - return ChatListHoleRowItem(initialSize, account, hole) - case let .MessageEntry(index, message, readState, notifySettings,embeddedState, renderedPeer, summaryInfo): - - var pinnedType: ChatListPinnedType = .some - if let i = to.index(of: entry) { - if index.pinningIndex != nil { - if i > 0 { - if case let .MessageEntry(index, _, _, _ ,_ , _, _) = to[i - 1].entry, index.pinningIndex == nil { - pinnedType = .last - } - } - } else { + case let .chat(inner, activities, addition, filter): + switch inner { + case let .HoleEntry(hole): + return ChatListHoleRowItem(initialSize, context, hole) + case let .MessageEntry(index, messages, readState, isMuted, embeddedState, renderedPeer, peerPresence, summaryInfo, hasFailed, isContact): + var pinnedType: ChatListPinnedType = .some + if let addition = addition { + pinnedType = .ad(addition.item) + } else if index.pinningIndex == nil { pinnedType = .none } + return ChatListRowItem(initialSize, context: context, messages: messages, index: inner.index, readState: readState, isMuted: isMuted, embeddedState: embeddedState, pinnedType: pinnedType, renderedPeer: renderedPeer, peerPresence: peerPresence, summaryInfo: summaryInfo, activities: activities, associatedGroupId: groupId, hasFailed: hasFailed, filter: filter) } - return ChatListRowItem(initialSize, account: account, message: message, readState:readState, notificationSettings: notifySettings, embeddedState: embeddedState, pinnedType: pinnedType, renderedPeer: renderedPeer, summaryInfo: summaryInfo) + case let .group(_, groupId, peers, message, unreadState, unreadCountDisplayCategory, animated, archiveStatus): + return ChatListRowItem(initialSize, context: context, pinnedType: .none, groupId: groupId, peers: peers, messages: message != nil ? [message!] : [], unreadState: unreadState, unreadCountDisplayCategory: unreadCountDisplayCategory, animateGroup: animated, archiveStatus: archiveStatus) + case let .reveal(tabs, selected, counters): + return ChatListRevealItem(initialSize, context: context, tabs: tabs, selected: selected, counters: counters, action: setupFilter, openSettings: { + openFilterSettings(nil) + }, menuItems: tabsMenuItems) + case let .empty(filter): + return ChatListEmptyRowItem(initialSize, stableId: entry.stableId, filter: filter, context: context, openFilterSettings: openFilterSettings) + case let .loading(filter): + return ChatListLoadingRowItem(initialSize, stableId: entry.stableId, filter: filter, context: context) } - - }) - let nState = scrollState ?? (animated ? .none(nil) : .saveVisible(.lower)) - let transition = TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated, state: nState) + } + + let (deleted,inserted,updated) = proccessEntries(from, right: to, { entry -> TableRowItem in + return makeItem(entry) + }) + + let nState = scrollState ?? (animated ? .none(nil) : .saveVisible(.lower)) + let transition = TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated: animated, state: nState, animateVisibleOnly: false) + subscriber.putNext(transition) subscriber.putCompletion() - return EmptyDisposable - } |> runOn(onMainQueue ? Queue.mainQueue() : prepareQueue) + return ActionDisposable { + cancelled = true + } + } +} + +enum HiddenArchiveStatus : Equatable { + case normal + case collapsed + case hidden(Bool) + + var rawValue: Int { + switch self { + case .normal: + return 0 + case .collapsed: + return 1 + case .hidden: + return 2 + } + } + var isHidden: Bool { + switch self { + case .hidden: + return true + default: + return false + } + } + + init?(rawValue: Int) { + switch rawValue { + case 0: + self = .normal + case 1: + self = .collapsed + case 2: + self = .hidden(true) + default: + return nil + } + } } +struct FilterData : Equatable { + let filter: ChatListFilter? + let tabs: [ChatListFilter] + let sidebar: Bool + init(filter: ChatListFilter?, tabs: [ChatListFilter], sidebar: Bool) { + self.filter = filter + self.tabs = tabs + self.sidebar = sidebar + } + func withUpdatedFilter(_ filter: ChatListFilter?) -> FilterData { + return FilterData(filter: filter, tabs: self.tabs, sidebar: self.sidebar) + } + func withUpdatedTabs(_ tabs: [ChatListFilter]) -> FilterData { + return FilterData(filter: self.filter, tabs: tabs, sidebar: self.sidebar) + } + func withUpdatedSidebar(_ sidebar: Bool) -> FilterData { + return FilterData(filter: self.filter, tabs: self.tabs, sidebar: sidebar) + } +} +private struct HiddenItems : Equatable { + let archive: HiddenArchiveStatus + let promo: Set +} class ChatListController : PeersListController { - + + private let filter = ValuePromise(ignoreRepeated: true) + private let _filterValue = Atomic(value: FilterData(filter: nil, tabs: [], sidebar: false)) + private var filterValue: FilterData? { + return _filterValue.with { $0 } + } + + var filterSignal : Signal { + return self.filter.get() + } + + func updateFilter(_ f:(FilterData)->FilterData) { + let previous = filterValue + let current = _filterValue.modify(f) + self.genericView.searchView.change(state: .None, true) + if previous?.filter?.id != current.filter?.id { + scrollup(force: true) + _ = first.swap(true) + _ = animated.swap(false) + self.request.set(.single(.Initial(max(Int(context.window.frame.height / 70) + 3, 12), nil))) + } + filter.set(current) + setCenterTitle(self.defaultBarTitle) + } + private let request = Promise() private let previousChatList:Atomic = Atomic(value: nil) private let first = Atomic(value:true) - + private let animated = Atomic(value: false) + private let removePeerIdGroupDisposable = MetaDisposable() + private let disposable = MetaDisposable() + private let scrollDisposable = MetaDisposable() + private let reorderDisposable = MetaDisposable() + private let globalPeerDisposable = MetaDisposable() + private let archivationTooltipDisposable = MetaDisposable() + private let undoTooltipControl: UndoTooltipControl + private let animateGroupNextTransition:Atomic = Atomic(value: nil) + private var activityStatusesDisposable:Disposable? + + private let suggestAutoarchiveDisposable = MetaDisposable() + + private var didSuggestAutoarchive: Bool = false + + private let hiddenItemsValue: Atomic = Atomic(value: HiddenItems(archive: FastSettings.archiveStatus, promo: Set())) + private let hiddenItemsState: ValuePromise = ValuePromise(HiddenItems(archive: FastSettings.archiveStatus, promo: Set()), ignoreRepeated: true) + + private let filterDisposable = MetaDisposable() + + private func updateHiddenStateState(_ f:(HiddenItems)->HiddenItems) { + let result = hiddenItemsValue.modify(f) + FastSettings.archiveStatus = result.archive + hiddenItemsState.set(result) + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + override func viewDidLoad() { super.viewDidLoad() + + let initialSize = self.atomicSize - let account = self.account + let context = self.context let previousChatList = self.previousChatList - let first = self.first - let onMainQueue:Atomic = Atomic(value: true) - let previousEntries:Atomic<[AppearanceWrapperEntry]?> = Atomic(value: nil) - let list:Signal = (request.get() |> distinctUntilChanged |> mapToSignal { (location) -> Signal in - - var signal:Signal<(ChatListView,ViewUpdateType),Void> - var scroll:TableScrollState? = nil - switch(location) { + let first = Atomic<(ChatListIndex?, ChatListIndex?)>(value: (nil, nil)) + let scrollUp:Atomic = self.first + let groupId = self.mode.groupId + let previousEntries:Atomic<[AppearanceWrapperEntry]?> = Atomic(value: nil) + let animated: Atomic = self.animated + let animateGroupNextTransition = self.animateGroupNextTransition + var scroll:TableScrollState? = nil + + let initialState = ChatListState(activities: ChatListPeerInputActivities(activities: [:])) + let statePromise:ValuePromise = ValuePromise(initialState) + let stateValue: Atomic = Atomic(value: initialState) + + let updateState:((ChatListState)->ChatListState)->Void = { f in + statePromise.set(stateValue.modify(f)) + } + + + let postbox = context.account.postbox + let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + let previousActivities = Atomic(value: nil) + self.activityStatusesDisposable = (context.account.allPeerInputActivities() + |> mapToSignal { activitiesByPeerId -> Signal<[PeerId: [ChatListInputActivity]], NoError> in + var foundAllPeers = true + var cachedResult: [PeerId: [ChatListInputActivity]] = [:] + previousPeerCache.with { dict -> Void in + for (chatPeerId, activities) in activitiesByPeerId { + var cachedChatResult: [ChatListInputActivity] = [] + for (peerId, activity) in activities { + if let peer = dict[peerId] { + cachedChatResult.append(ChatListInputActivity(peer, activity)) + } else { + foundAllPeers = false + break + } + cachedResult[chatPeerId] = cachedChatResult + } + } + } + if foundAllPeers { + return .single(cachedResult) + } else { + return postbox.transaction { transaction -> [PeerId: [ChatListInputActivity]] in + var result: [PeerId: [ChatListInputActivity]] = [:] + var peerCache: [PeerId: Peer] = [:] + for (chatPeerId, activities) in activitiesByPeerId { + var chatResult: [ChatListInputActivity] = [] + + for (peerId, activity) in activities { + if let peer = transaction.getPeer(peerId) { + chatResult.append(ChatListInputActivity(peer, activity)) + peerCache[peerId] = peer + } + } + + result[chatPeerId] = chatResult + } + let _ = previousPeerCache.swap(peerCache) + return result + } + } + } + |> map { activities -> ChatListPeerInputActivities? in + return previousActivities.modify { current in + var updated = false + let currentList: [PeerId: [ChatListInputActivity]] = current?.activities ?? [:] + if currentList.count != activities.count { + updated = true + } else { + outer: for (peerId, currentValue) in currentList { + if let value = activities[peerId] { + if currentValue.count != value.count { + updated = true + break outer + } else { + for i in 0 ..< currentValue.count { + if currentValue[i] != value[i] { + updated = true + break outer + } + } + } + } else { + updated = true + break outer + } + } + } + if updated { + if activities.isEmpty { + return nil + } else { + return ChatListPeerInputActivities(activities: activities) + } + } else { + return current + } + } + } + |> deliverOnMainQueue).start(next: { activities in + updateState { + $0.updateActivities { _ in + activities ?? ChatListPeerInputActivities(activities: [:]) + } + } + }) + + let previousLocation: Atomic = Atomic(value: nil) + globalPeerDisposable.set(context.globalPeerHandler.get().start(next: { [weak self] location in + if previousLocation.swap(location) != location { + self?.removeRevealStateIfNeeded(nil) + } + + self?.removeHighlightEvents() + + if let searchController = self?.searchController { + searchController.updateHighlightEvents(location != nil) + } + if location == nil { + self?.setHighlightEvents() + } + })) + + + let foldersSignal = filter.get() |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs.filter == rhs.filter + }) + + let foldersTopBarUpdate = filter.get() |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.tabs != rhs.tabs { + return false + } + if lhs.sidebar != rhs.sidebar { + return false + } + if lhs.filter != rhs.filter { + return false + } + return true + }) + + let signal = combineLatest(request.get() |> distinctUntilChanged, foldersSignal) + + let previousfilter = Atomic(value: self.filterValue) + + let chatHistoryView: Signal<(ChatListView, ViewUpdateType, Bool, FilterData, Bool), NoError> = signal |> mapToSignal { location, data -> Signal<(ChatListView, ViewUpdateType, Bool, FilterData, Bool), NoError> in + + var signal:Signal<(ChatListView,ViewUpdateType), NoError> + var removeNextAnimation: Bool = false + switch location { case let .Initial(count, st): - signal = account.viewTracker.tailChatListView(count: count) + signal = context.account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: chatListFilterPredicate(for: data.filter), count: count) scroll = st - case let .Index(index): - signal = account.viewTracker.aroundChatListView(index: index, count: 100) + case let .Index(index, st): + signal = context.account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: chatListFilterPredicate(for: data.filter), index: index, count: 100) + scroll = st + removeNextAnimation = st != nil + } + return signal |> map { ($0.0, $0.1, removeNextAnimation, data, previousfilter.swap(data)?.filter?.id != data.filter?.id)} + } + + let setupFilter:(ChatListFilter?)->Void = { [weak self] filter in + self?.updateFilter { + $0.withUpdatedFilter(filter) + } + self?.scrollup(force: true) + } + let openFilterSettings:(ChatListFilter?)->Void = { filter in + if let filter = filter { + context.sharedContext.bindings.rootNavigation().push(ChatListFilterController(context: context, filter: filter)) + } else { + context.sharedContext.bindings.rootNavigation().push(ChatListFiltersListController(context: context)) } + } + + + let list:Signal = combineLatest(queue: prepareQueue, chatHistoryView, appearanceSignal, statePromise.get(), context.chatUndoManager.allStatuses(), hiddenItemsState.get(), appNotificationSettings(accountManager: context.sharedContext.accountManager), chatListFilterItems(account: context.account, accountManager: context.sharedContext.accountManager), foldersTopBarUpdate) |> mapToQueue { value, appearance, state, undoStatuses, hiddenItems, inAppSettings, filtersCounter, filterData -> Signal in + + let removeNextAnimation = value.2 - return combineLatest(signal, appearanceSignal |> deliverOnPrepareQueue) |> mapToQueue { (value, appearance) -> Signal in - - if !first.modify({$0}) { - scroll = nil + let previous = first.swap((value.0.earlierIndex, value.0.laterIndex)) + + let ignoreFlags = scrollUp.swap(false) + + if !ignoreFlags || (!ignoreFlags && (previous.0 != value.0.earlierIndex || previous.1 != value.0.laterIndex) && !removeNextAnimation) { + scroll = nil + } + + + _ = previousChatList.swap(value.0) + + var prepare:[(ChatListEntry, UIChatAdditionalItem?)] = [] + for value in value.0.entries { + prepare.append((value, nil)) + } + if value.0.laterIndex == nil, filterData.filter == nil { + let items = value.0.additionalItemEntries.filter { + !hiddenItems.promo.contains($0.info.peerId) } - _ = previousChatList.swap(value.0) - let entries = value.0.entries.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - - return prepareEntries(from: previousEntries.swap(entries), to: entries, account: account, initialSize: initialSize.modify({$0}), animated: !first.swap(false), scrollState: scroll, onMainQueue: onMainQueue.swap(false)) + for (i, current) in items.enumerated() { + prepare.append((current.entry, UIChatAdditionalItem(item: current.info, index: i + value.0.groupEntries.count))) + } + } + var mapped: [UIChatListEntry] = prepare.map { + return .chat($0, state.activities.activities[$0.index.messageIndex.id.peerId] ?? [], $1, filter: filterData.filter) + } + + if filterData.filter != nil, mapped.isEmpty {} else { + if value.0.laterIndex == nil { + for (i, group) in value.0.groupEntries.reversed().enumerated() { + mapped.append(.group(i, group.groupId, group.renderedPeers, group.message, group.unreadState, inAppSettings.totalUnreadCountDisplayCategory, animateGroupNextTransition.swap(nil) == group.groupId, hiddenItems.archive)) + } + } + } + + + if mapped.isEmpty { + let hasHole = !value.0.entries.filter({ value in + switch value { + case .HoleEntry: + return true + default: + return false + } + }).isEmpty + if !hasHole { + mapped.append(.empty(filterData.filter)) + } + } else { + let isLoading = mapped.filter { value in + switch value { + case let .chat(entry, _, _, _): + if case .HoleEntry = entry { + return false + } else { + return true + } + default: + return true + } + }.isEmpty + if isLoading { + mapped.append(.loading(filterData.filter)) + + } + } + + + if !filterData.tabs.isEmpty && !filterData.sidebar { + mapped.append(.reveal(filterData.tabs, filterData.filter, filtersCounter)) } + let entries = mapped.sorted().compactMap { entry -> AppearanceWrapperEntry? in + switch entry { + case let .chat(inner, activities, additionItem, filter): + switch inner { + case .HoleEntry: + return nil + case let .MessageEntry(values): + if undoStatuses.isActive(peerId: inner.index.messageIndex.id.peerId, types: [.deleteChat, .leftChat, .leftChannel, .deleteChannel]) { + return nil + } else if undoStatuses.isActive(peerId: inner.index.messageIndex.id.peerId, types: [.clearHistory]) { + let entry: ChatListEntry = ChatListEntry.MessageEntry(index: values.0, messages: [], readState: values.2, isRemovedFromTotalUnreadCount: values.3, embeddedInterfaceState: values.4, renderedPeer: values.5, presence: values.6, summaryInfo: values.7, hasFailed: values.8, isContact: values.9) + return AppearanceWrapperEntry(entry: .chat(entry, activities, additionItem, filter: filter), appearance: appearance) + } else if undoStatuses.isActive(peerId: inner.index.messageIndex.id.peerId, types: [.archiveChat]) { + if groupId == .root { + return nil + } else { + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + } + } else { + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + } + } + case .group: + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + case .reveal: + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + case .empty: + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + case .loading: + return AppearanceWrapperEntry(entry: entry, appearance: appearance) + } + } + + let prev = previousEntries.swap(entries) + + + var animated = animated.swap(true) + + if value.4 { + animated = false + scroll = .up(true) + } + + return prepareEntries(from: prev, to: entries, adIndex: nil, context: context, initialSize: initialSize.with { $0 }, animated: animated, scrollState: scroll, groupId: groupId, setupFilter: setupFilter, openFilterSettings: openFilterSettings, tabsMenuItems: { filter in + return filterContextMenuItems(filter, context: context) + }) + } + + + let appliedTransition = list |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in + self?.enqueueTransition(transition) + return .complete() + } + + disposable.set(appliedTransition.start()) + + + request.set(.single(.Initial(max(Int(context.window.frame.height / 70) + 3, 13), nil))) + + var pinnedCount: Int = 0 + self.genericView.tableView.enumerateItems { item -> Bool in + guard let item = item as? ChatListRowItem, item.isFixedItem else {return false} + pinnedCount += 1 + return item.isFixedItem + } + + genericView.tableView.resortController = TableResortController(resortRange: NSMakeRange(0, pinnedCount), start: { row in + + }, resort: { row in + + }, complete: { [weak self] from, to in + self?.resortPinned(from, to) }) - |> deliverOnMainQueue - genericView.tableView.merge(with: list) - request.set(.single(.Initial(50, nil))) + genericView.tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] scroll in + guard let `self` = self else { + return + } + self.removeRevealStateIfNeeded(nil) + })) + + genericView.tableView.set(stickClass: ChatListRevealItem.self, handler: { _ in + + }) + + genericView.tableView.emptyChecker = { items in + let filter = items.filter { !($0 is ChatListEmptyRowItem) } + return filter.isEmpty + } + genericView.tableView.setScrollHandler({ [weak self] scroll in let view = previousChatList.modify({$0}) @@ -128,50 +703,811 @@ class ChatListController : PeersListController { break } if let messageIndex = messageIndex { - _ = first.swap(true) - strongSelf.request.set(.single(.Index(messageIndex))) + _ = animated.swap(false) + strongSelf.request.set(.single(.Index(messageIndex, nil))) } } + }) + + + + + let filterView = chatListFilterPreferences(postbox: context.account.postbox) |> deliverOnMainQueue + switch mode { + case .folder: + self.updateFilter( { + $0.withUpdatedTabs([]).withUpdatedFilter(nil) + } ) + case let .filter(filterId): + filterDisposable.set(filterView.start(next: { [weak self] filters in + var shouldBack: Bool = false + self?.updateFilter { current in + var current = current + if let updated = filters.list.first(where: { $0.id == filterId }) { + current = current.withUpdatedFilter(updated) + } else { + shouldBack = true + current = current.withUpdatedFilter(nil) + } + current = current.withUpdatedTabs([]) + return current + } + if shouldBack { + self?.navigationController?.back() + } + })) + default: + filterDisposable.set(filterView.start(next: { [weak self] filters in + self?.updateFilter( { current in + var current = current + if let filter = current.filter { + if let updated = filters.list.first(where: { $0.id == filter.id }) { + current = current.withUpdatedFilter(updated) + } else { + current = current.withUpdatedFilter(nil) + } + } + + current = current.withUpdatedTabs(filters.list).withUpdatedSidebar(filters.sidebar) + return current + } ) + })) + } + } + + func collapseOrExpandArchive() { + updateHiddenStateState { current in + switch current.archive { + case .collapsed: + return HiddenItems(archive: .normal, promo: current.promo) + default: + return HiddenItems(archive: .collapsed, promo: current.promo) + } + } + } + + func hidePromoItem(_ peerId: PeerId) { + updateHiddenStateState { current in + var promo = current.promo + promo.insert(peerId) + return HiddenItems(archive: current.archive, promo: promo) + } + _ = hideAccountPromoInfoChat(account: self.context.account, peerId: peerId).start() + } + + func toggleHideArchive() { + updateHiddenStateState { current in + switch current.archive { + case .hidden: + return HiddenItems(archive: .normal, promo: current.promo) + default: + return HiddenItems(archive: .hidden(true), promo: current.promo) + } + } + } + + + func setAnimateGroupNextTransition(_ groupId: PeerGroupId) { + _ = self.animateGroupNextTransition.swap(groupId) + + } + + func addUndoAction(_ action:ChatUndoAction) { + let context = self.context + context.chatUndoManager.add(action: action) + guard self.context.sharedContext.layout != .minimisize else { return } + self.undoTooltipControl.add(controller: self) + } + + private func enqueueTransition(_ transition: TableUpdateTransition) { + self.genericView.tableView.merge(with: transition) + readyOnce() + switch self.mode { + case .folder: + if self.genericView.tableView.isEmpty { + self.navigationController?.close() + } + default: + break + } + + var first: ChatListRowItem? + self.genericView.tableView.enumerateItems { item -> Bool in + if let item = item as? ChatListRowItem, item.archiveStatus != nil { + first = item + } + + return first == nil + } + + if let first = first, let archiveStatus = first.archiveStatus { + self.genericView.tableView.autohide = TableAutohide(item: first, hideUntilOverscroll: archiveStatus.isHidden, hideHandler: { [weak self] hidden in + self?.updateHiddenStateState { current in + return HiddenItems(archive: .hidden(hidden), promo: current.promo) + } + }) + } else { + self.genericView.tableView.autohide = nil + } + + var pinnedRange: NSRange = NSMakeRange(NSNotFound, 0) + self.genericView.tableView.enumerateItems { item -> Bool in + guard let item = item as? ChatListRowItem else {return true} + switch item.pinnedType { + case .some, .last: + if pinnedRange.location == NSNotFound { + pinnedRange.location = item.index + } + pinnedRange.length += 1 + default: + break + } + return item.isFixedItem || item.groupId != .root + } + + self.searchController?.pinnedItems = self.collectPinnedItems + self.genericView.tableView.resortController?.resortRange = pinnedRange + + + let needPreload = previousChatList.with { $0?.laterIndex == nil } + if needPreload { + var preloadItems:[ChatHistoryPreloadItem] = [] + self.genericView.tableView.enumerateItems(with: { item -> Bool in + guard let item = item as? ChatListRowItem, let index = item.chatListIndex else {return true} + preloadItems.append(.init(index: index, isMuted: item.isMuted, hasUnread: item.hasUnread)) + return preloadItems.count < 30 + }) + context.account.viewTracker.chatListPreloadItems.set(.single(preloadItems) |> delay(0.2, queue: prepareQueue)) + } else { + context.account.viewTracker.chatListPreloadItems.set(.single([])) + } + } + + private func resortPinned(_ from: Int, _ to: Int) { + + var items:[PinnedItemId] = [] + + var offset: Int = 0 + + let groupId: PeerGroupId = self.mode.groupId + + let location: TogglePeerChatPinnedLocation + + if let filter = self.filterValue?.filter { + location = .filter(filter.id) + } else { + location = .group(groupId) + } + + self.genericView.tableView.enumerateItems { item -> Bool in + guard let item = item as? ChatListRowItem else { + offset += 1 + return true + } + if item.groupId != .root || item.isAd { + offset += 1 + } + if let location = item.chatLocation { + switch item.pinnedType { + case .some, .last: + items.append(location.pinnedItemId) + default: + break + } + } + + return item.isFixedItem || item.groupId != .root + } + + + + items.move(at: from - offset, to: to - offset) + + reorderDisposable.set(context.account.postbox.transaction { transaction -> Void in + _ = reorderPinnedItemIds(transaction: transaction, location: location, itemIds: items) + }.start()) + } + + override var collectPinnedItems:[PinnedItemId] { + var items:[PinnedItemId] = [] + + + self.genericView.tableView.enumerateItems { item -> Bool in + guard let item = item as? ChatListRowItem else {return false} + if let location = item.chatLocation { + switch item.pinnedType { + case .some, .last: + items.append(location.pinnedItemId) + default: + break + } + } + return item.isFixedItem || item.groupId != .root + } + return items + } + + private var lastScrolledIndex: ChatListIndex? = nil + + + override func scrollup(force: Bool = false) { + + if force { + self.genericView.tableView.scroll(to: .up(true), ignoreLayerAnimation: true) + return + } + + if searchController != nil { + self.genericView.searchView.change(state: .None, true) + return + } + + let view = self.previousChatList.with { $0 } + + if self.genericView.tableView.contentOffset.y == 0, view?.laterIndex == nil { + switch mode { + case .folder: + navigationController?.back() + return + case .filter: + navigationController?.back() + return + case .plain: + break + } + + } + + + let scrollToTop:()->Void = { [weak self] in + guard let `self` = self else {return} + + let view = self.previousChatList.modify({$0}) + if view?.laterIndex != nil { + _ = self.first.swap(true) + self.request.set(.single(.Initial(50, .up(true)))) + } else { + if self.genericView.tableView.documentOffset.y == 0 { + if self.filterValue?.filter != nil { + self.updateFilter { + $0.withUpdatedFilter(nil) + } + } else { + self.context.sharedContext.bindings.mainController().showFastChatSettings() + } + } else { + self.genericView.tableView.scroll(to: .up(true), ignoreLayerAnimation: true) + } + } + } + scrollToTop() + + + } + + var filterMenuItems: Signal<[SPopoverItem], NoError> { + let context = self.context + + let isEnabled = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + |> map { view -> Bool in + let configuration = ChatListFilteringConfiguration(appConfiguration: view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue) + return configuration.isEnabled + } + + return combineLatest(chatListFilterPreferences(postbox: context.account.postbox), isEnabled) + |> take(1) + |> deliverOnMainQueue + |> map { [weak self] filters, isEnabled -> [SPopoverItem] in + var items:[SPopoverItem] = [] + if isEnabled { + items.append(SPopoverItem(filters.list.isEmpty ? L10n.chatListFilterSetupEmpty : L10n.chatListFilterSetup, { + context.sharedContext.bindings.rootNavigation().push(ChatListFiltersListController(context: context)) + }, filters.list.isEmpty ? theme.icons.chat_filter_add : theme.icons.chat_filter_edit)) + + if self?.filterValue?.filter != nil { + items.append(SPopoverItem(L10n.chatListFilterAll, { + self?.updateFilter { + $0.withUpdatedFilter(nil) + } + })) + } + + if !filters.list.isEmpty { + items.append(SPopoverItem(false)) + } + for filter in filters.list { + let badge = GlobalBadgeNode(context.account, sharedContext: context.sharedContext, view: View(), layoutChanged: { + + }, getColor: { isSelected in + return isSelected ? .white : theme.colors.accent + }, filter: filter) + let additionView: SPopoverAdditionItemView = SPopoverAdditionItemView(context: badge, view: badge.view!, updateIsSelected: { [weak badge] isSelected in + badge?.isSelected = isSelected + }) + + items.append(SPopoverItem(filter.title, { [weak self] in + guard let `self` = self, filter.id != self.filterValue?.filter?.id else { + return + } + self.updateFilter { + $0.withUpdatedFilter(filter) + } + self.scrollup(force: true) + }, filter.icon, additionView: additionView)) + } + } + return items + } + + } + + + func globalSearch(_ query: String) { + let invoke = { [weak self] in + self?.genericView.searchView.change(state: .Focus, false) + self?.genericView.searchView.setString(query) + } + + switch context.sharedContext.layout { + case .single: + context.sharedContext.bindings.rootNavigation().back() + Queue.mainQueue().justDispatch(invoke) + case .minimisize: + context.sharedContext.bindings.needFullsize() + Queue.mainQueue().justDispatch(invoke) + default: + invoke() + } + } + + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + + let isLocked = (NSApp.delegate as? AppDelegate)?.passlock ?? .single(false) + + + + self.suggestAutoarchiveDisposable.set(combineLatest(queue: .mainQueue(), isLocked, context.isKeyWindow, getServerProvidedSuggestions(postbox: self.context.account.postbox)).start(next: { [weak self] locked, isKeyWindow, values in + guard let strongSelf = self, let navigation = strongSelf.navigationController else { + return + } + if strongSelf.didSuggestAutoarchive { + return + } + if !values.contains(.autoarchivePopular) { + return + } + if !isKeyWindow { + return + } + if navigation.stackCount > 1 { + return + } + if locked { + return + } + strongSelf.didSuggestAutoarchive = true + + let context = strongSelf.context + + _ = dismissServerProvidedSuggestion(account: strongSelf.context.account, suggestion: .autoarchivePopular).start() + + confirm(for: context.window, header: L10n.alertHideNewChatsHeader, information: L10n.alertHideNewChatsText, okTitle: L10n.alertHideNewChatsOK, cancelTitle: L10n.alertHideNewChatsCancel, successHandler: { _ in + execute(inapp: .settings(link: "tg://settings/privacy", context: context, section: .privacy)) + }) + + })) + + + context.window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + if event.modifierFlags.contains(.control) { + if self.genericView.tableView._mouseInside() { + let row = self.genericView.tableView.row(at: self.genericView.tableView.clipView.convert(event.locationInWindow, from: nil)) + if row >= 0 { + let view = self.genericView.hitTest(self.genericView.convert(event.locationInWindow, from: nil)) + if view?.className.contains("Segment") == false { + self.genericView.tableView.item(at: row).view?.mouseDown(with: event) + return .invoked + } else { + return .rejected + } + } + } + } + return .rejected + }, with: self, for: .leftMouseDown, priority: .high) + + + context.window.add(swipe: { [weak self] direction, _ -> SwipeHandlerResult in + guard let `self` = self, let window = self.window else {return .failed} + let swipeState: SwipeState? + + var checkFolder: Bool = true + let row = self.genericView.tableView.row(at: self.genericView.tableView.clipView.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + if row != -1 { + + let hitTestView = self.genericView.hitTest(self.genericView.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + if let view = hitTestView, view.isInSuperclassView(ChatListRevealView.self) { + return .failed + } + let item = self.genericView.tableView.item(at: row) as? ChatListRowItem + if let item = item { + let view = item.view as? ChatListRowView + if view?.endRevealState != nil { + checkFolder = false + } + + if !item.hasRevealState { + return .failed + } + } else { + return .failed + } + + } + + + switch direction { + case let .left(_state): + if !self.mode.isPlain && checkFolder { + swipeState = nil + } else { + swipeState = _state + } + + case let .right(_state): + swipeState = _state + case .none: + swipeState = nil + } + + + guard let state = swipeState, self.context.sharedContext.layout != .minimisize else {return .failed} + + switch state { + case .start: + let row = self.genericView.tableView.row(at: self.genericView.tableView.clipView.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + if row != -1 { + let item = self.genericView.tableView.item(at: row) as! ChatListRowItem + guard !item.isAd else {return .failed} + self.removeRevealStateIfNeeded(item.peerId) + (item.view as? RevealTableView)?.initRevealState() + return .success(RevealTableItemController(item: item)) + } else { + return .failed + } + + case let .swiping(_delta, controller): + let controller = controller as! RevealTableItemController + + guard let view = controller.item.view as? RevealTableView else {return .nothing} + + var delta:CGFloat + switch direction { + case .left: + delta = _delta//max(0, _delta) + case .right: + delta = -_delta//min(-_delta, 0) + default: + delta = _delta + } + + + delta -= view.additionalRevealDelta + + let newDelta = min(view.width * log2(abs(delta) + 1) * log2(delta < 0 ? view.width * 8 : view.width) / 100.0, abs(delta)) + + if delta < 0 { + delta = -newDelta + } else { + delta = newDelta + } + + + + view.moveReveal(delta: delta) + case let .success(_, controller), let .failed(_, controller): + let controller = controller as! RevealTableItemController + guard let view = (controller.item.view as? RevealTableView) else {return .nothing} + + var direction = direction + + switch direction { + case let .left(state): + + if view.containerX < 0 && abs(view.containerX) > view.rightRevealWidth / 2 { + direction = .right(state.withAlwaysSuccess()) + } else if abs(view.containerX) < view.rightRevealWidth / 2 && view.containerX < view.leftRevealWidth / 2 { + direction = .left(state.withAlwaysFailed()) + } else { + direction = .left(state.withAlwaysSuccess()) + } + case .right: + if view.containerX > 0 && view.containerX > view.leftRevealWidth / 2 { + direction = .left(state.withAlwaysSuccess()) + } else if abs(view.containerX) < view.rightRevealWidth / 2 && view.containerX < view.leftRevealWidth / 2 { + direction = .right(state.withAlwaysFailed()) + } else { + direction = .right(state.withAlwaysSuccess()) + } + default: + break + } + + view.completeReveal(direction: direction) + } + + // return .success() + + return .nothing + }, with: self.genericView.tableView, identifier: "chat-list", priority: .high) + + + + if context.sharedContext.bindings.rootNavigation().stackCount == 1 { + setHighlightEvents() + } } - override func scrollup() { + private func setHighlightEvents() { + + removeHighlightEvents() - let view = previousChatList.modify({$0}) - if view?.laterIndex != nil { - _ = first.swap(true) - request.set(.single(.Initial(100, .up(true)))) + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let item = self?.genericView.tableView.highlightedItem(), item.index > 0 { + self?.genericView.tableView.highlightPrev(turnDirection: false) + while self?.genericView.tableView.highlightedItem() is PopularPeersRowItem || self?.genericView.tableView.highlightedItem() is SeparatorRowItem { + self?.genericView.tableView.highlightNext(turnDirection: false) + } + } + return .invoked + }, with: self, for: .UpArrow, priority: .low) + + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.tableView.highlightNext(turnDirection: false) + while self?.genericView.tableView.highlightedItem() is PopularPeersRowItem || self?.genericView.tableView.highlightedItem() is SeparatorRowItem { + self?.genericView.tableView.highlightNext(turnDirection: false) + } + return .invoked + }, with: self, for: .DownArrow, priority: .low) + + } + + private func removeHighlightEvents() { + genericView.tableView.cancelHighlight() + context.window.remove(object: self, for: .DownArrow, forceCheckFlags: true) + context.window.remove(object: self, for: .UpArrow, forceCheckFlags: true) + } + + private func removeRevealStateIfNeeded(_ ignoreId: PeerId?) { + genericView.tableView.enumerateItems { item -> Bool in + if let item = item as? ChatListRowItem, item.peerId != ignoreId { + (item.view as? ChatListRowView)?.endRevealState = nil + } + return true + } + } + + private func _openChat(_ index: Int) { + if !genericView.tableView.isEmpty { + let archiveItem = genericView.tableView.item(at: 0) as? ChatListRowItem + var index: Int = index + if let item = archiveItem, item.isAutohidden || item.archiveStatus == .collapsed { + index += 1 + } + if archiveItem == nil { + index += 1 + if genericView.tableView.count > 1 { + let archiveItem = genericView.tableView.item(at: 1) as? ChatListRowItem + if let item = archiveItem, item.isAutohidden || item.archiveStatus == .collapsed { + index += 1 + } + } + } + + if genericView.tableView.count > index { + _ = genericView.tableView.select(item: genericView.tableView.item(at: index), notify: true, byClick: true) + } + } + } + + func openChat(_ index: Int, force: Bool = false) { + if case .folder = self.mode { + _openChat(index) + } else if force { + _openChat(index) } else { - genericView.tableView.scroll(to: .up(true)) + let prefs = chatListFilterPreferences(postbox: context.account.postbox) |> deliverOnMainQueue |> take(1) + + _ = prefs.start(next: { [weak self] filters in + if filters.list.isEmpty { + self?._openChat(index) + } else if index == 0 { + self?.updateFilter { + $0.withUpdatedFilter(nil) + } + self?.scrollup(force: true) + } else if filters.list.count >= index { + self?.updateFilter { + $0.withUpdatedFilter(filters.list[index - 1]) + } + self?.scrollup(force: true) + } else { + self?._openChat(index) + } + }) + } + } + + override var removeAfterDisapper: Bool { + switch self.mode { + case .plain: + return false + default: + return true } + } + + + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + context.window.removeAllHandlers(for: self) + context.window.removeAllHandlers(for: genericView.tableView) + removeRevealStateIfNeeded(nil) + + suggestAutoarchiveDisposable.set(nil) + } + +// override func getLeftBarViewOnce() -> BarView { +// return MajorBackNavigationBar(self, context: context, excludePeerId: context.peerId) +// } + + + deinit { + removePeerIdGroupDisposable.dispose() + disposable.dispose() + scrollDisposable.dispose() + reorderDisposable.dispose() + globalPeerDisposable.dispose() + archivationTooltipDisposable.dispose() + activityStatusesDisposable?.dispose() + filterDisposable.dispose() + suggestAutoarchiveDisposable.dispose() + } + + + override var enableBack: Bool { + switch mode { + case .folder, .filter: + return true + default: + return false + } + } + + override var defaultBarTitle: String { + switch mode { + case .plain: + return super.defaultBarTitle + case .folder: + return L10n.chatListArchivedChats + case .filter: + return _filterValue.with { $0.filter?.title ?? "Filter" } + } } - init(_ account:Account, modal:Bool = false) { - super.init(account, followGlobal:!modal) + override func escapeKeyAction() -> KeyHandlerResult { + if !mode.isPlain, let navigation = navigationController { + navigation.back() + return .invoked + } + if self.filterValue?.filter != nil { + updateFilter { + $0.withUpdatedFilter(nil) + } + return .invoked + } + return super.escapeKeyAction() + } + + + init(_ context: AccountContext, modal:Bool = false, groupId: PeerGroupId? = nil, filterId: Int32? = nil) { + self.undoTooltipControl = UndoTooltipControl(context: context) + + let mode: PeerListMode + if let filterId = filterId { + mode = .filter(filterId) + } else if let groupId = groupId { + mode = .folder(groupId) + } else { + mode = .plain + } + + super.init(context, followGlobal: !modal, mode: mode) + + if groupId != nil { + context.closeFolderFirst = true + } } - override func selectionWillChange(row:Int, item:TableRowItem) -> Bool { - if let item = item as? ChatListRowItem, let peer = item.peer, let modalAction = navigationController?.modalAction { + override func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { + if let item = item as? ChatListRowItem, let peer = item.peer, let modalAction = context.sharedContext.bindings.rootNavigation().modalAction { if !modalAction.isInvokable(for: peer) { - modalAction.alertError(for: peer, with:window!) + modalAction.alertError(for: peer, with:mainWindow) return false } modalAction.afterInvoke() + + if let modalAction = modalAction as? FWDNavigationAction { + if item.peerId == context.peerId { + _ = Sender.forwardMessages(messageIds: modalAction.messages.map{$0.id}, context: context, peerId: context.peerId).start() + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + navigationController?.removeModalAction() + return false + } + } + + } + if let item = item as? ChatListRowItem { + if item.groupId != .root { + if byClick { + item.view?.focusAnimation(nil) + open(with: item.entryId, initialAction: nil, addition: false) + } + return false + } + } + if item is ChatListRevealItem { + return false } return true } override func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void { - if let item = item as? ChatListRowItem, let navigation = navigationController { - if !isNew && navigation.controller is ChatController { - if let modalAction = navigation.modalAction { - navigation.controller.invokeNavigation(action: modalAction) + let navigation = context.sharedContext.bindings.rootNavigation() + if let item = item as? ChatListRowItem { + if !isNew, let controller = navigation.controller as? ChatController { + switch controller.mode { + case .history: + if let modalAction = navigation.modalAction { + navigation.controller.invokeNavigation(action: modalAction) + } + controller.clearReplyStack() + controller.scrollup(force: true) + case .scheduled: + navigation.back() } - navigation.controller.scrollup() + } else { - open(with: item.peerId) + + let context = self.context + + _ = (context.globalPeerHandler.get() |> take(1)).start(next: { location in + context.globalPeerHandler.set(.single(location)) + }) + + let initialAction: ChatInitialAction? + + switch item.pinnedType { + case let .ad(info): + if let info = info as? PromoChatListItem { + initialAction = .ad(info.kind) + } else { + initialAction = nil + } + default: + initialAction = nil + } + + open(with: item.entryId, initialAction: initialAction, addition: false) } } } diff --git a/Telegram-Mac/ChatListEmptyRowItem.swift b/Telegram-Mac/ChatListEmptyRowItem.swift new file mode 100644 index 0000000000..7684eb6843 --- /dev/null +++ b/Telegram-Mac/ChatListEmptyRowItem.swift @@ -0,0 +1,274 @@ +// +// ChatListEmptyRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import TGUIKit +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +class ChatListEmptyRowItem: TableRowItem { + private let _stableId: AnyHashable + + override var stableId: AnyHashable { + return _stableId + } + let context: AccountContext + let filter: ChatListFilter? + let openFilterSettings: (ChatListFilter?)->Void + init(_ initialSize: NSSize, stableId: AnyHashable, filter: ChatListFilter?, context: AccountContext, openFilterSettings: @escaping(ChatListFilter?)->Void) { + self.context = context + self.filter = filter + self._stableId = stableId + self.openFilterSettings = openFilterSettings + super.init(initialSize) + } + + override var height: CGFloat { + if let table = table { + var tableHeight: CGFloat = 0 + table.enumerateItems { item -> Bool in + if item.index < self.index { + tableHeight += item.height + } + return true + } + let height = table.frame.height == 0 ? initialSize.height : table.frame.height + return height - tableHeight + } + return initialSize.height + } + + override func viewClass() -> AnyClass { + return ChatListEmptyRowView.self + } +} + + +private class ChatListEmptyRowView : TableRowView { + private let disposable = MetaDisposable() + private let textView = TextView() + private let separator = View() + private let sticker: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + + addSubview(separator) + addSubview(sticker) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + + guard let item = item as? ChatListEmptyRowItem else { + return + } + + let animatedSticker: LocalAnimatedSticker + + if let _ = item.filter { + animatedSticker = .folder_empty + } else { + animatedSticker = .chiken_born + } + sticker.update(with: animatedSticker.file, size: NSMakeSize(112, 112), context: item.context, parent: nil, table: item.table, parameters: animatedSticker.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: false) + + needsLayout = true + } + + deinit { + disposable.dispose() + } + + + override func layout() { + super.layout() + + separator.background = theme.colors.border + + guard let item = item as? ChatListEmptyRowItem else { + return + } + + + let text: String + if let _ = item.filter { + text = L10n.chatListFilterEmpty + } else { + text = L10n.chatListEmptyText + } + + + let attr = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.title), textColor: theme.colors.text), bold: MarkdownAttributeSet(font: .medium(.title), textColor: theme.colors.text), link: MarkdownAttributeSet(font: .normal(.title), textColor: theme.colors.link), linkAttribute: { [weak item] contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { [weak item] value in + if value == "filter" { + item?.openFilterSettings(item?.filter) + } + + })) + })).mutableCopy() as! NSMutableAttributedString + + + attr.detectBoldColorInString(with: .medium(.title)) + + let layout = TextViewLayout(attr, alignment: .center) + + layout.measure(width: frame.width - 40) + layout.interactions = globalLinkExecutor + textView.update(layout) + textView.center() + + textView.isHidden = frame.width <= 70 + sticker.isHidden = frame.width <= 70 + + separator.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + + sticker.centerX(y: textView.frame.minY - sticker.frame.height - 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + + + + + + + + + +class ChatListLoadingRowItem: TableRowItem { + private let _stableId: AnyHashable + + override var stableId: AnyHashable { + return _stableId + } + let context: AccountContext + let filter: ChatListFilter? + init(_ initialSize: NSSize, stableId: AnyHashable, filter: ChatListFilter?, context: AccountContext) { + self.context = context + self.filter = filter + self._stableId = stableId + super.init(initialSize) + } + + override var height: CGFloat { + if let table = table { + var tableHeight: CGFloat = 0 + table.enumerateItems { item -> Bool in + if item.index < self.index { + tableHeight += item.height + } + return true + } + let height = table.frame.height == 0 ? initialSize.height : table.frame.height + return height - tableHeight + } + return initialSize.height + } + + override func viewClass() -> AnyClass { + return ChatListLoadingRowView.self + } +} + + +private class ChatListLoadingRowView : TableRowView { + private let disposable = MetaDisposable() + private let textView = TextView() + private let separator = View() + private let sticker: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + private let indicator: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 30, 30)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + + addSubview(separator) + addSubview(sticker) + addSubview(indicator) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + + guard let item = item as? ChatListLoadingRowItem else { + return + } + + + if let _ = item.filter { + let animatedSticker: LocalAnimatedSticker = LocalAnimatedSticker.new_folder + sticker.update(with: animatedSticker.file, size: NSMakeSize(112, 112), context: item.context, parent: nil, table: item.table, parameters: animatedSticker.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: false) + sticker.isHidden = false + indicator.isHidden = true + } else { + sticker.isHidden = true + indicator.isHidden = false + } + + needsLayout = true + } + + deinit { + disposable.dispose() + } + + + override func layout() { + super.layout() + + separator.background = theme.colors.border + + guard let item = item as? ChatListLoadingRowItem else { + return + } + + let text: String + if let _ = item.filter { + text = L10n.chatListFilterLoading + } else { + text = "Loading" + } + + let attr = NSAttributedString.initialize(string: text, color: theme.colors.text, font: .normal(.text)).mutableCopy() as! NSMutableAttributedString + + attr.detectBoldColorInString(with: .medium(.text)) + + let layout = TextViewLayout(attr, alignment: .center) + + layout.measure(width: frame.width - 40) + layout.interactions = globalLinkExecutor + textView.update(layout) + textView.center() + + textView.isHidden = frame.width <= 70 || item.filter == nil + sticker.isHidden = frame.width <= 70 || item.filter == nil + + indicator.isHidden = item.filter != nil + + separator.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + + sticker.centerX(y: textView.frame.minY - sticker.frame.height - 20) + indicator.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ChatListFilterController.swift b/Telegram-Mac/ChatListFilterController.swift new file mode 100644 index 0000000000..41c2d3481a --- /dev/null +++ b/Telegram-Mac/ChatListFilterController.swift @@ -0,0 +1,823 @@ +// +// ChatListPresetController.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/01/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + + + +enum ChatListFilterType { + case generic + case unmuted + case unread + case channels + case groups + case bots + case contacts + case nonContacts +} + +func chatListFilterType(_ filter: ChatListFilter) -> ChatListFilterType { + let filterType: ChatListFilterType + if filter.data.includePeers.peers.isEmpty { + if filter.data.categories == .all { + if filter.data.excludeRead { + filterType = .unread + } else if filter.data.excludeMuted { + filterType = .unmuted + } else { + filterType = .generic + } + } else { + if filter.data.categories == .channels { + filterType = .channels + } else if filter.data.categories == .groups { + filterType = .groups + } else if filter.data.categories == .bots { + filterType = .bots + } else if filter.data.categories == .contacts { + filterType = .contacts + } else if filter.data.categories == .nonContacts { + filterType = .nonContacts + } else { + filterType = .generic + } + } + } else { + filterType = .generic + } + return filterType +} + + + +private let maximumPeers: Int = 100 + +private extension ChatListFilter { + var additionIncludeItems: [ShareAdditionItem] { + var items:[ShareAdditionItem] = [] + + items.append(.init(peer: TelegramFilterCategory(category: .contacts), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .nonContacts), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .groups), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .channels), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .bots), status: "")) + return items + } + var selectedIncludeItems: [ShareAdditionItem] { + var items:[ShareAdditionItem] = [] + + if self.data.categories.contains(.contacts) { + items.append(.init(peer: TelegramFilterCategory(category: .contacts), status: "")) + } + if self.data.categories.contains(.nonContacts) { + items.append(.init(peer: TelegramFilterCategory(category: .nonContacts), status: "")) + } + if self.data.categories.contains(.groups) { + items.append(.init(peer: TelegramFilterCategory(category: .groups), status: "")) + } + if self.data.categories.contains(.channels) { + items.append(.init(peer: TelegramFilterCategory(category: .channels), status: "")) + } + if self.data.categories.contains(.bots) { + items.append(.init(peer: TelegramFilterCategory(category: .bots), status: "")) + } + return items + } + var additionExcludeItems: [ShareAdditionItem] { + var items:[ShareAdditionItem] = [] + items.append(.init(peer: TelegramFilterCategory(category: .excludeMuted), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .excludeRead), status: "")) + items.append(.init(peer: TelegramFilterCategory(category: .excludeArchived), status: "")) + return items + } + var selectedExcludeItems: [ShareAdditionItem] { + var items:[ShareAdditionItem] = [] + + if self.data.excludeMuted { + items.append(.init(peer: TelegramFilterCategory(category: .excludeMuted), status: "")) + } + if self.data.excludeRead { + items.append(.init(peer: TelegramFilterCategory(category: .excludeRead), status: "")) + } + if self.data.excludeArchived { + items.append(.init(peer: TelegramFilterCategory(category: .excludeArchived), status: "")) + } + return items + } +} + +//extension ChatListFiltersState { +// mutating func withAddedFilter(_ filter: ChatListFilter, onlyReplace: Bool = false) { +// if let index = filters.firstIndex(where: {$0.id == filter.id}) { +// filters[index] = filter +// } else if !onlyReplace { +// filters.append(filter) +// } +// } +// +// mutating func withRemovedFilter(_ filter: ChatListFilter) { +// filters.removeAll(where: {$0.id == filter.id }) +// } +// +// mutating func withMoveFilter(_ from: Int, _ to: Int) { +// filters.insert(filters.remove(at: from), at: to) +// } +//} + +class SelectCallbackObject : ShareObject { + private let callback:([PeerId])->Signal + private let limitReachedText: String + init(_ context: AccountContext, defaultSelectedIds: Set, additionTopItems: ShareAdditionItems?, limit: Int?, limitReachedText: String, callback:@escaping([PeerId])->Signal) { + self.callback = callback + self.limitReachedText = limitReachedText + super.init(context, defaultSelectedIds: defaultSelectedIds, additionTopItems: additionTopItems, limit: limit) + } + + override var hasCaptionView: Bool { + return false + } + + override func perform(to peerIds:[PeerId], comment: String? = nil) -> Signal { + return callback(peerIds) |> mapError { _ in return String() } + } + override func limitReached() { + alert(for: context.window, info: limitReachedText) + } + override var searchPlaceholderKey: String { + return "ChatList.Add.Placeholder" + } + override var interactionOk: String { + return L10n.chatListFilterAddDone + } + override var alwaysEnableDone: Bool { + return true + } + override func possibilityPerformTo(_ peer: Peer) -> Bool { + if peer is TelegramSecretChat { + return false + } + return true + } + +} + +private struct ChatListFiltersListState: Equatable { + var filter: ChatListFilter + var showAllInclude: Bool + var showAllExclude: Bool + let isNew: Bool + var changedName: Bool + init(filter: ChatListFilter, isNew: Bool, showAllInclude: Bool, showAllExclude: Bool, changedName: Bool) { + self.filter = filter + self.isNew = isNew + self.showAllInclude = showAllInclude + self.showAllExclude = showAllExclude + self.changedName = changedName + } + + + + mutating func withUpdatedFilter(_ f:(ChatListFilter)->ChatListFilter) { + self.filter = f(self.filter) + } +} + +private final class ChatListPresetArguments { + let context: AccountContext + let toggleOption:(ChatListFilterPeerCategories)->Void + let toggleExcludeMuted:(Bool)->Void + let toggleExcludeRead:(Bool)->Void + let addInclude:()->Void + let addExclude:()->Void + let removeIncluded:(PeerId)->Void + let removeExcluded:(PeerId)->Void + let openInfo:(PeerId)->Void + let showAllInclude: ()->Void + let showAllExclude: ()->Void + let updateIcon:(FolderIcon)->Void + init(context: AccountContext, toggleOption:@escaping(ChatListFilterPeerCategories)->Void, addInclude: @escaping()->Void, addExclude: @escaping()->Void, removeIncluded: @escaping(PeerId)->Void, removeExcluded: @escaping(PeerId)->Void, openInfo: @escaping(PeerId)->Void, toggleExcludeMuted:@escaping(Bool)->Void, toggleExcludeRead: @escaping(Bool)->Void, showAllInclude:@escaping()->Void, showAllExclude:@escaping()->Void, updateIcon: @escaping(FolderIcon)->Void) { + self.context = context + self.toggleOption = toggleOption + self.toggleExcludeMuted = toggleExcludeMuted + self.toggleExcludeRead = toggleExcludeRead + self.addInclude = addInclude + self.addExclude = addExclude + self.removeIncluded = removeIncluded + self.removeExcluded = removeExcluded + self.openInfo = openInfo + self.showAllInclude = showAllInclude + self.showAllExclude = showAllExclude + self.updateIcon = updateIcon + } +} + +private let _id_name_input = InputDataIdentifier("_id_name_input") +private let _id_private_chats = InputDataIdentifier("_id_private_chats") + +private let _id_public_groups = InputDataIdentifier("_id_public_groups") +private let _id_private_groups = InputDataIdentifier("_id_private_groups") +private let _id_secret_chats = InputDataIdentifier("_id_secret_chats") + + +private let _id_channels = InputDataIdentifier("_id_channels") +private let _id_bots = InputDataIdentifier("_id_bots") +private let _id_exclude_muted = InputDataIdentifier("_id_exclude_muted") +private let _id_exclude_read = InputDataIdentifier("_id_exclude_read") + +private let _id_add_include = InputDataIdentifier("_id_add_include") +private let _id_add_exclude = InputDataIdentifier("_id_add_exclude") + +private let _id_show_all_include = InputDataIdentifier("_id_show_all_include") +private let _id_show_all_exclude = InputDataIdentifier("_id_show_all_exclude") +private let _id_header = InputDataIdentifier("_id_header") +private func _id_include(_ peerId: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_include_\(peerId)") +} +private func _id_exclude(_ peerId: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_exclude_\(peerId)") +} +private func chatListFilterEntries(state: ChatListFiltersListState, includePeers: [Peer], excludePeers: [Peer], arguments: ChatListPresetArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var includePeers:[Peer] = includePeers + + if state.filter.data.categories.contains(.groups) { + includePeers.insert(TelegramFilterCategory(category: .groups), at: 0) + } + if state.filter.data.categories.contains(.channels) { + includePeers.insert(TelegramFilterCategory(category: .channels), at: 0) + } + if state.filter.data.categories.contains(.contacts) { + includePeers.insert(TelegramFilterCategory(category: .contacts), at: 0) + } + if state.filter.data.categories.contains(.nonContacts) { + includePeers.insert(TelegramFilterCategory(category: .nonContacts), at: 0) + } + if state.filter.data.categories.contains(.bots) { + includePeers.insert(TelegramFilterCategory(category: .bots), at: 0) + } + + + var excludePeers:[Peer] = excludePeers + + if state.filter.data.excludeMuted { + excludePeers.insert(TelegramFilterCategory(category: .excludeMuted), at: 0) + } + if state.filter.data.excludeRead { + excludePeers.insert(TelegramFilterCategory(category: .excludeRead), at: 0) + } + if state.filter.data.excludeArchived { + excludePeers.insert(TelegramFilterCategory(category: .excludeArchived), at: 0) + } + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + if state.isNew { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_header, equatable: nil, item: { initialSize, stableId in + let attributedString = NSMutableAttributedString() + return ChatListFiltersHeaderItem(initialSize, context: arguments.context, stableId: stableId, sticker: LocalAnimatedSticker.new_folder, text: attributedString) + })) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterNameHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.filter.title), error: nil, identifier: _id_name_input, mode: .plain, data: .init(viewType: .singleItem, rightItem: InputDataRightItem.action(FolderIcon(state.filter).icon(for: .settings), .custom{ item, control in + showPopover(for: control, with: ChatListFilterFolderIconController(arguments.context, select: arguments.updateIcon), edge: .minX, inset: NSMakePoint(0,-45)) + })), placeholder: nil, inputPlaceholder: L10n.chatListFilterNamePlaceholder, filter: { $0 }, limit: 12)) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterIncludeHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + let hasAddInclude = state.filter.data.includePeers.peers.count < maximumPeers || state.filter.data.categories != .all + + if hasAddInclude { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_add_include, equatable: InputDataEquatable(state), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterIncludeAddChat, nameStyle: blueActionButton, type: .none, viewType: includePeers.isEmpty ? .singleItem : .firstItem, action: arguments.addInclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chat_filter_add, textInset: 46, thumbInset: 4)) + })) + index += 1 + } + + + + var fake:[Int] = [] + fake.append(0) + for (i, _) in includePeers.enumerated() { + if hasAddInclude { + fake.append(i + 1) + } else { + fake.append(i) + } + } + + for (i, peer) in includePeers.enumerated() { + + struct E : Equatable { + let viewType: GeneralViewType + let peer: PeerEquatable + } + + if i > 10, !state.showAllInclude { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_show_all_include, equatable: InputDataEquatable(includePeers.count), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterShowMoreCountable(includePeers.count - i), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: arguments.showAllInclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + break + } else { + var viewType = bestGeneralViewType(fake, for: hasAddInclude ? i + 1 : i) + + if excludePeers.count > 10, i == includePeers.count - 1, state.showAllInclude { + viewType = .innerItem + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_include(peer.id), equatable: InputDataEquatable(E(viewType: viewType, peer: PeerEquatable(peer))), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 44, photoSize: NSMakeSize(30, 30), inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: { + arguments.openInfo(peer.id) + }, contextMenuItems: { + return .single([ContextMenuItem(L10n.chatListFilterIncludeRemoveChat, handler: { + arguments.removeIncluded(peer.id) + })]) + }) + })) + index += 1 + } + } + + if includePeers.count > 10, state.showAllInclude { + struct T: Equatable { + let a: Bool + let b: Int + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_show_all_include, equatable: InputDataEquatable(T(a: state.showAllInclude, b: includePeers.count)), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterHideCountable(includePeers.count - 11), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: arguments.showAllInclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchDown, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterIncludeDesc), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterExcludeHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + let hasAddExclude = state.filter.data.excludePeers.count < maximumPeers || !state.filter.data.excludeRead || !state.filter.data.excludeMuted || !state.filter.data.excludeArchived + + + if hasAddExclude { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_add_exclude, equatable: InputDataEquatable(state), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterExcludeAddChat, nameStyle: blueActionButton, type: .none, viewType: excludePeers.isEmpty ? .singleItem : .firstItem, action: arguments.addExclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chat_filter_add, textInset: 46, thumbInset: 2)) + })) + index += 1 + } + + + + fake = [] + fake.append(0) + for (i, _) in excludePeers.enumerated() { + if hasAddExclude { + fake.append(i + 1) + } else { + fake.append(i) + } + } + + for (i, peer) in excludePeers.enumerated() { + struct E : Equatable { + let viewType: GeneralViewType + let peer: PeerEquatable + } + if i > 10, !state.showAllExclude { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_show_all_exclude, equatable: InputDataEquatable(excludePeers.count), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterShowMoreCountable(excludePeers.count - i), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: arguments.showAllExclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + break + } else { + var viewType = bestGeneralViewType(fake, for: hasAddExclude ? i + 1 : i) + + if excludePeers.count > 10, i == excludePeers.count - 1, state.showAllExclude { + viewType = .innerItem + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_exclude(peer.id), equatable: InputDataEquatable(E(viewType: viewType, peer: PeerEquatable(peer))), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 44, photoSize: NSMakeSize(30, 30), inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: { + arguments.openInfo(peer.id) + }, contextMenuItems: { + return .single([ContextMenuItem.init(L10n.chatListFilterExcludeRemoveChat, handler: { + arguments.removeExcluded(peer.id) + })]) + }) + })) + index += 1 + } + + } + + if excludePeers.count > 10, state.showAllExclude { + + struct T: Equatable { + let a: Bool + let b: Int + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_show_all_exclude, equatable: InputDataEquatable(T(a: state.showAllExclude, b: excludePeers.count)), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.chatListFilterHideCountable(excludePeers.count - 11), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: arguments.showAllExclude, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchDown, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterExcludeDesc), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func ChatListFilterController(context: AccountContext, filter: ChatListFilter, isNew: Bool = false) -> InputDataController { + + + let initialState = ChatListFiltersListState(filter: filter, isNew: isNew, showAllInclude: false, showAllExclude: false, changedName: !isNew) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((ChatListFiltersListState) -> ChatListFiltersListState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let updateDisposable = MetaDisposable() + + let save:(Bool)->Void = { replace in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + let filter = stateValue.with { $0.filter } + var filters = filters + if let index = filters.firstIndex(where: {$0.id == filter.id}) { + filters[index] = filter + } else if !replace { + filters.append(filter) + } + return filters + }).start() + } + + + let arguments = ChatListPresetArguments(context: context, toggleOption: { option in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + if filter.data.categories.contains(option) { + filter.data.categories.remove(option) + } else { + filter.data.categories.insert(option) + } + return filter + } + return state + } + // save(true) + + }, addInclude: { + + let items = stateValue.with { $0.filter.additionIncludeItems } + + let additionTopItems = items.isEmpty ? nil : ShareAdditionItems(items: items, topSeparator: L10n.chatListAddTopSeparator, bottomSeparator: L10n.chatListAddBottomSeparator) + + showModal(with: ShareModalController(SelectCallbackObject(context, defaultSelectedIds: Set(stateValue.with { $0.filter.data.includePeers.peers + $0.filter.selectedIncludeItems.map { $0.peer.id } }), additionTopItems: additionTopItems, limit: maximumPeers, limitReachedText: L10n.chatListFilterIncludeLimitReached, callback: { peerIds in + updateState { state in + var state = state + + let categories = peerIds.filter { + $0.namespace == ChatListFilterPeerCategories.Namespace + } + let peerIds = Set(peerIds).subtracting(categories) + + state.withUpdatedFilter { filter in + var filter = filter + filter.data.includePeers.setPeers(Array(peerIds.uniqueElements.prefix(maximumPeers))) + var updatedCats: ChatListFilterPeerCategories = [] + let cats = categories.map { ChatListFilterPeerCategories(rawValue: $0.id) } + for cat in cats { + updatedCats.insert(cat) + } + filter.data.categories = updatedCats + return filter + } + return state + } + // save(true) + return .complete() + })), for: context.window) + }, addExclude: { + + let items = stateValue.with { $0.filter.additionExcludeItems } + let additionTopItems = items.isEmpty ? nil : ShareAdditionItems(items: items, topSeparator: L10n.chatListAddTopSeparator, bottomSeparator: L10n.chatListAddBottomSeparator) + + showModal(with: ShareModalController(SelectCallbackObject(context, defaultSelectedIds: Set(stateValue.with { $0.filter.data.excludePeers + $0.filter.selectedExcludeItems.map { $0.peer.id } }), additionTopItems: additionTopItems, limit: maximumPeers, limitReachedText: L10n.chatListFilterExcludeLimitReached, callback: { peerIds in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + + let categories = peerIds.filter { + $0.namespace == ChatListFilterPeerCategories.Namespace + } + let peerIds = Set(peerIds).subtracting(categories) + filter.data.excludePeers = Array(peerIds.uniqueElements.prefix(maximumPeers)) + for cat in categories { + if ChatListFilterPeerCategories(rawValue: cat.id) == .excludeMuted { + filter.data.excludeMuted = true + } + if ChatListFilterPeerCategories(rawValue: cat.id) == .excludeRead { + filter.data.excludeRead = true + } + if ChatListFilterPeerCategories(rawValue: cat.id) == .excludeArchived { + filter.data.excludeArchived = true + } + } + + return filter + } + return state + } + // save(true) + return .complete() + })), for: context.window) + }, removeIncluded: { peerId in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + var peers = filter.data.includePeers.peers + peers.removeAll(where: { $0 == peerId }) + filter.data.includePeers.setPeers(peers) + if peerId.namespace == ChatListFilterPeerCategories.Namespace { + filter.data.categories.remove(ChatListFilterPeerCategories(rawValue: peerId.id)) + } + return filter + } + return state + } + //save(true) + }, removeExcluded: { peerId in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + var peers = filter.data.excludePeers + peers.removeAll(where: { $0 == peerId }) + filter.data.excludePeers = peers + if peerId.namespace == ChatListFilterPeerCategories.Namespace { + if ChatListFilterPeerCategories(rawValue: peerId.id) == .excludeMuted { + filter.data.excludeMuted = false + } + if ChatListFilterPeerCategories(rawValue: peerId.id) == .excludeRead { + filter.data.excludeRead = false + } + if ChatListFilterPeerCategories(rawValue: peerId.id) == .excludeArchived { + filter.data.excludeArchived = false + } + } + return filter + } + return state + } + //save(true) + }, openInfo: { peerId in + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + }, toggleExcludeMuted: { updated in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + filter.data.excludeMuted = updated + return filter + } + return state + } + // save(true) + }, toggleExcludeRead: { updated in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + filter.data.excludeRead = updated + return filter + } + return state + } + //save(true) + }, showAllInclude: { + updateState { state in + var state = state + state.showAllInclude = !state.showAllInclude + return state + } + }, showAllExclude: { + updateState { state in + var state = state + state.showAllExclude = !state.showAllExclude + return state + } + }, updateIcon: { icon in + updateState { state in + var state = state + state.withUpdatedFilter { filter in + var filter = filter + filter.emoticon = icon.emoticon.emoji + return filter + } + return state + } + }) + + + let dataSignal = combineLatest(queue: prepareQueue, appearanceSignal, statePromise.get()) |> mapToSignal { _, state -> Signal<(ChatListFiltersListState, ([Peer], [Peer])), NoError> in + return context.account.postbox.transaction { transaction -> ([Peer], [Peer]) in + return (state.filter.data.includePeers.peers.compactMap { transaction.getPeer($0) }, state.filter.data.excludePeers.compactMap { transaction.getPeer($0) }) + } |> map { + (state, $0) + } + } |> map { + return chatListFilterEntries(state: $0, includePeers: $1.0, excludePeers: $1.1, arguments: arguments) + } |> map { + return InputDataSignalValue(entries: $0) + } + + let controller = InputDataController(dataSignal: dataSignal, title: isNew ? L10n.chatListFilterNewTitle : L10n.chatListFilterTitle, removeAfterDisappear: false) + + controller.updateDatas = { data in + + if let name = data[_id_name_input]?.stringValue { + updateState { state in + var state = state + if state.filter.title != name { + state.changedName = true + } + state.withUpdatedFilter { filter in + var filter = filter + filter.title = name + return filter + } + return state + } + } + + return .none + } + + controller.backInvocation = { data, f in + if stateValue.with({ $0.filter != filter }) { + confirm(for: context.window, header: L10n.chatListFilterDiscardHeader, information: L10n.chatListFilterDiscardText, okTitle: L10n.chatListFilterDiscardOK, cancelTitle: L10n.chatListFilterDiscardCancel, successHandler: { _ in + f(true) + }) + } else { + f(true) + } + + } + + controller.updateDoneValue = { data in + return { f in + if isNew { + f(.enabled(L10n.chatListFilterDone)) + } else { + f(.enabled(L10n.navigationDone)) + } + } + } + + controller.onDeinit = { + updateDisposable.dispose() + } + + + controller.afterTransaction = { controller in + let type = stateValue.with { chatListFilterType($0.filter) } + let nameIsUpdated = stateValue.with { $0.changedName } + if !nameIsUpdated { + switch type { + case .generic: + break + case .unmuted: + //state.name = presentationData.strings.ChatListFolder_NameNonMuted + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultUnmuted + // state.filter.emoticon = + return state + } + case .unread: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultUnread + return state + } + case .channels: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultChannels + return state + } + case .groups: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultGroups + return state + } + case .bots: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultBots + return state + } + case .contacts: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultContacts + return state + } + case .nonContacts: + updateState { state in + var state = state + state.filter.title = L10n.chatListFilterTilteDefaultNonContacts + return state + } + } + + } + } + + controller.validateData = { data in + + return .fail(.doSomething(next: { f in + let emptyTitle = stateValue.with { $0.filter.title.isEmpty } + if emptyTitle { + f(.fail(.fields([_id_name_input : .shake]))) + return + } + + let filter = stateValue.with { $0.filter } + + if filter.isFullfilled { + alert(for: context.window, info: L10n.chatListFilterErrorLikeChats) + } else if filter.isEmpty { + alert(for: context.window, info: L10n.chatListFilterErrorEmpty) + f(.fail(.fields([_id_add_include : .shake]))) + } else { + _ = showModalProgress(signal: requestUpdateChatListFilter(postbox: context.account.postbox, network: context.account.network, id: filter.id, filter: filter), for: context.window).start(error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }, completed: { + save(false) + f(.success(.navigationBack)) + }) + } + + + })) + + + } + + return controller + +} + + + diff --git a/Telegram-Mac/ChatListFilterFolderIconController.swift b/Telegram-Mac/ChatListFilterFolderIconController.swift new file mode 100644 index 0000000000..708648293a --- /dev/null +++ b/Telegram-Mac/ChatListFilterFolderIconController.swift @@ -0,0 +1,71 @@ +// +// ChatListFilterFolderIconController.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +final class ChatListFolderIconsView : View { + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func initialize(callback: @escaping(FolderIcon)->Void) { + removeAllSubviews() + + for icon in allSidebarFolderIcons { + let control = ImageButton(frame: NSMakeRect(0, 0, 40, 40)) + control.set(image: icon.icon(for: .settings), for: .Normal) + addSubview(control) + control.set(handler: { _ in + callback(icon) + }, for: .Click) + } + + needsLayout = true + } + + override func layout() { + super.layout() + + var x: CGFloat = 10 + var y: CGFloat = 10 + for (i, subview) in subviews.enumerated() { + subview.setFrameOrigin(NSMakePoint(x, y)) + x += subview.frame.width + if (i + 1) % 5 == 0 { + x = 10 + y += subview.frame.height + } + } + } +} + +class ChatListFilterFolderIconController: TelegramGenericViewController { + private let select:(FolderIcon)->Void + init(_ context: AccountContext, select: @escaping(FolderIcon)->Void) { + self.select = select + super.init(context) + _frameRect = NSMakeRect(0, 0, 40 * 5 + 20, ceil(CGFloat(allSidebarFolderIcons.count) / 5) * 40 + 20) + bar = .init(height: 0) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.initialize(callback: { [weak self] value in + self?.select(value) + self?.closePopover() + }) + readyOnce() + } +} diff --git a/Telegram-Mac/ChatListFilterPredicate.swift b/Telegram-Mac/ChatListFilterPredicate.swift new file mode 100644 index 0000000000..d863289257 --- /dev/null +++ b/Telegram-Mac/ChatListFilterPredicate.swift @@ -0,0 +1,98 @@ +// +// ChatListFilterPredicate.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +func chatListFilterPredicate(for filter: ChatListFilter?) -> ChatListFilterPredicate? { + let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool) + + guard let filter = filter?.data else { + return nil + } + let includePeers = Set(filter.includePeers.peers) + let excludePeers = Set(filter.excludePeers) + var includeAdditionalPeerGroupIds: [PeerGroupId] = [] + if !filter.excludeArchived { + includeAdditionalPeerGroupIds.append(Namespaces.PeerGroup.archive) + } + var messageTagSummary: ChatListMessageTagSummaryResultCalculation? + if filter.excludeRead || filter.excludeMuted { + messageTagSummary = ChatListMessageTagSummaryResultCalculation(addCount: ChatListMessageTagSummaryResultComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), subtractCount: ChatListMessageTagActionsSummaryResultComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)) + } + + return ChatListFilterPredicate(includePeerIds: includePeers, excludePeerIds: excludePeers, pinnedPeerIds: filter.includePeers.pinnedPeers, messageTagSummary: messageTagSummary, includeAdditionalPeerGroupIds: includeAdditionalPeerGroupIds, include: { peer, isMuted, isUnread, isContact, messageTagSummaryResult in + if filter.excludeRead { + var effectiveUnread = isUnread + if let messageTagSummaryResult = messageTagSummaryResult, messageTagSummaryResult { + effectiveUnread = true + } + if !effectiveUnread { + return false + } + } + if filter.excludeMuted { + if isMuted { + if let messageTagSummaryResult = messageTagSummaryResult, messageTagSummaryResult { + } else { + return false + } + } + + } + if !filter.categories.contains(.contacts) && isContact { + if let user = peer as? TelegramUser { + if user.botInfo == nil { + return false + } + } else if let _ = peer as? TelegramSecretChat { + return false + } + } + if !filter.categories.contains(.nonContacts) && !isContact { + if let user = peer as? TelegramUser { + if user.botInfo == nil { + return false + } + } else if let _ = peer as? TelegramSecretChat { + return false + } + } + if !filter.categories.contains(.bots) { + if let user = peer as? TelegramUser { + if user.botInfo != nil { + return false + } + } + } + if !filter.categories.contains(.groups) { + if let _ = peer as? TelegramGroup { + return false + } else if let channel = peer as? TelegramChannel { + if case .group = channel.info { + return false + } + } + } + if !filter.categories.contains(.channels) { + if let channel = peer as? TelegramChannel { + if case .broadcast = channel.info { + return false + } + } + } + return true + }) + + +} diff --git a/Telegram-Mac/ChatListFilterPreferences.swift b/Telegram-Mac/ChatListFilterPreferences.swift new file mode 100644 index 0000000000..a5469cfb84 --- /dev/null +++ b/Telegram-Mac/ChatListFilterPreferences.swift @@ -0,0 +1,315 @@ +// +// ChatListFilterPreferences.swift +// Telegram +// +// Created by Mikhail Filimonov on 24.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Postbox +import SwiftSignalKit +import TelegramCore +import Postbox +import SyncCore + + +extension ChatListFilter { + + + var isFullfilled: Bool { + return self.data.categories == .all && data.includePeers.peers.isEmpty && data.excludePeers.isEmpty && !data.excludeMuted && !data.excludeRead && data.excludeArchived + } + var isEmpty: Bool { + return self.data.categories.isEmpty && data.includePeers.peers.isEmpty && data.excludePeers.isEmpty && !data.excludeMuted && !data.excludeRead + } + var icon: CGImage { + + if data.categories == .all && data.excludeMuted && !data.excludeRead { + return theme.icons.chat_filter_unmuted + } else if data.categories == .all && !data.excludeMuted && data.excludeRead { + return theme.icons.chat_filter_unread + } else if data.categories == .groups { + return theme.icons.chat_filter_groups + } else if data.categories == .channels { + return theme.icons.chat_filter_channels + } else if data.categories == .contacts { + return theme.icons.chat_filter_private_chats + } else if data.categories == .nonContacts { + return theme.icons.chat_filter_non_contacts + } else if data.categories == .bots { + return theme.icons.chat_filter_bots + } + return theme.icons.chat_filter_custom + } + + static func new(excludeIds: [Int32]) -> ChatListFilter { + var id:Int32! = nil + while id == nil { + let tempId = abs(Int32(bitPattern: arc4random())) % 255 + if tempId != 0 && tempId != 1 && !excludeIds.contains(tempId) { + id = tempId + } + } + return ChatListFilter(id: id, title: "", emoticon: nil, data: ChatListFilterData(categories: [], excludeMuted: false, excludeRead: false, excludeArchived: false, includePeers: ChatListFilterIncludePeers(), excludePeers: [])) + } +} + + + + +struct ChatListFoldersSettings: PreferencesEntry, Equatable { + + let sidebar: Bool + + static var defaultValue: ChatListFoldersSettings { + return ChatListFoldersSettings(sidebar: false) + } + + init(sidebar: Bool) { + self.sidebar = sidebar + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let other = to as? ChatListFoldersSettings { + return other == self + } else { + return false + } + } + + init(decoder: PostboxDecoder) { + self.sidebar = decoder.decodeOptionalInt32ForKey("t") == 1 + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(sidebar ? 1 : 0, forKey: "t") + } + + func withUpdatedSidebar(_ sidebar: Bool) -> ChatListFoldersSettings { + return ChatListFoldersSettings(sidebar: sidebar) + } +} + + + +func chatListFolderSettings(_ postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListSettings]) |> map { view in + return view.values[ApplicationSpecificPreferencesKeys.chatListSettings] as? ChatListFoldersSettings ?? ChatListFoldersSettings.defaultValue + } +} + +func updateChatListFolderSettings(_ postbox: Postbox, _ f: @escaping(ChatListFoldersSettings) -> ChatListFoldersSettings) -> Signal { + return postbox.transaction { transaction in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.chatListSettings, { entry in + let current = entry as? ChatListFoldersSettings ?? ChatListFoldersSettings.defaultValue + return f(current) + }) + } |> ignoreValues +} + + + +struct ChatListFolders : Equatable { + let list: [ChatListFilter] + let sidebar: Bool +} + +func chatListFilterPreferences(postbox: Postbox) -> Signal { + return combineLatest(updatedChatListFilters(postbox: postbox), chatListFolderSettings(postbox)) |> map { + return ChatListFolders(list: $0, sidebar: $1.sidebar) + } +} + +struct ChatListFilterBadge : Equatable { + let filter: ChatListFilter + let count: Int + let hasUnmutedUnread: Bool +} +struct ChatListFilterBadges : Equatable { + let total:Int + let filters:[ChatListFilterBadge] + + func count(for filter: ChatListFilter?) -> ChatListFilterBadge? { + return filters.first(where: { $0.filter.id == filter?.id }) + } +} + +func chatListFilterItems(account: Account, accountManager: AccountManager) -> Signal { + + let settings = appNotificationSettings(accountManager: accountManager) |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs.badgeEnabled == rhs.badgeEnabled + }) + + return combineLatest(updatedChatListFilters(postbox: account.postbox), settings) + |> mapToSignal { filters, inAppSettings -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> in + + if !inAppSettings.badgeEnabled { + return .single((0, [])) + } + + var unreadCountItems: [UnreadMessageCountsItem] = [] + unreadCountItems.append(.totalInGroup(.root)) + var additionalPeerIds = Set() + var additionalGroupIds = Set() + for filter in filters { + additionalPeerIds.formUnion(filter.data.includePeers.peers) + additionalPeerIds.formUnion(filter.data.excludePeers) + if !filter.data.excludeArchived { + additionalGroupIds.insert(Namespaces.PeerGroup.archive) + } + } + if !additionalPeerIds.isEmpty { + for peerId in additionalPeerIds { + unreadCountItems.append(.peer(peerId)) + } + } + for groupId in additionalGroupIds { + unreadCountItems.append(.totalInGroup(groupId)) + } + let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems) + var keys: [PostboxViewKey] = [] + keys.append(unreadKey) + for peerId in additionalPeerIds { + keys.append(.basicPeer(peerId)) + } + + return combineLatest(queue: account.postbox.queue, + account.postbox.combinedView(keys: keys), + Signal.single(true) + ) + |> map { view, _ -> (Int, [(ChatListFilter, Int, Bool)]) in + guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else { + return (0, []) + } + + var result: [(ChatListFilter, Int, Bool)] = [] + + var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool)] = [:] + + var totalStates: [PeerGroupId: ChatListTotalUnreadState] = [:] + for entry in unreadCounts.entries { + switch entry { + case let .total(_, state): + totalStates[.root] = state + case let .totalInGroup(groupId, state): + 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 { + let tag = account.postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact) + + var peerCount = Int(state.count) + if state.isUnread { + peerCount = max(1, peerCount) + } + + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState { + peerTagAndCount[peerId] = (tag, peerCount, false) + } else { + peerTagAndCount[peerId] = (tag, peerCount, true) + } + } + } + } + } + + let totalBadge = 0 + + for filter in filters { + var tags: [PeerSummaryCounterTags] = [] + if filter.data.categories.contains(.contacts) { + tags.append(.contact) + } + if filter.data.categories.contains(.nonContacts) { + tags.append(.nonContact) + } + if filter.data.categories.contains(.groups) { + tags.append(.group) + } + if filter.data.categories.contains(.bots) { + tags.append(.bot) + } + if filter.data.categories.contains(.channels) { + tags.append(.channel) + } + + var count = 0 + var hasUnmutedUnread = false + if let totalState = totalStates[.root] { + for tag in tags { + if filter.data.excludeMuted { + if let value = totalState.filteredCounters[tag] { + if value.chatCount != 0 { + count += Int(value.chatCount) + hasUnmutedUnread = true + } + } + } else { + if let value = totalState.absoluteCounters[tag] { + count += Int(value.chatCount) + } + if let value = totalState.filteredCounters[tag] { + if value.chatCount != 0 { + hasUnmutedUnread = true + } + } + } + } + } + if !filter.data.excludeArchived { + if let totalState = totalStates[Namespaces.PeerGroup.archive] { + for tag in tags { + if filter.data.excludeMuted { + if let value = totalState.filteredCounters[tag] { + if value.chatCount != 0 { + count += Int(value.chatCount) + hasUnmutedUnread = true + } + } + } else { + if let value = totalState.absoluteCounters[tag] { + count += Int(value.chatCount) + } + if let value = totalState.filteredCounters[tag] { + if value.chatCount != 0 { + hasUnmutedUnread = true + } + } + } + } + } + } + for peerId in filter.data.includePeers.peers { + if let (tag, peerCount, hasUnmuted) = peerTagAndCount[peerId] { + if !tags.contains(tag) { + if peerCount != 0 { + count += 1 + if hasUnmuted { + hasUnmutedUnread = true + } + } + } + } + } + for peerId in filter.data.excludePeers { + if let (tag, peerCount, _) = peerTagAndCount[peerId] { + if tags.contains(tag) { + if peerCount != 0 { + count -= 1 + } + } + } + } + result.append((filter, count, hasUnmutedUnread)) + } + + return (totalBadge, result) + } + } |> map { value -> ChatListFilterBadges in + return ChatListFilterBadges(total: value.0, filters: value.1.map { ChatListFilterBadge(filter: $0.0, count: max(0, $0.1), hasUnmutedUnread: $0.2) }) + } |> mapToSignal { badges -> Signal in + return renderedTotalUnreadCount(accountManager: accountManager, postbox: account.postbox) |> map { + return ChatListFilterBadges(total: Int(max($0.0, 0)), filters: badges.filters) + } + } +} diff --git a/Telegram-Mac/ChatListFilterRecommendedItem.swift b/Telegram-Mac/ChatListFilterRecommendedItem.swift new file mode 100644 index 0000000000..64dca7b3af --- /dev/null +++ b/Telegram-Mac/ChatListFilterRecommendedItem.swift @@ -0,0 +1,104 @@ +// +// ChatListFilterRecommendedItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 04.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + +class ChatListFilterRecommendedItem: GeneralRowItem { + fileprivate let textLayout: TextViewLayout + init(_ initialSize: NSSize, stableId: AnyHashable, title: String, description: String, viewType: GeneralViewType, add: @escaping()->Void) { + + let attr = NSMutableAttributedString() + + _ = attr.append(string: title, color: theme.colors.text, font: .normal(.title)) + _ = attr.append(string: "\n", color: theme.colors.text, font: .normal(.title)) + _ = attr.append(string: description, color: theme.colors.grayText, font: .normal(.text)) + self.textLayout = TextViewLayout(attr) + super.init(initialSize, height: 40, stableId: stableId, viewType: viewType, action: add) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + self.textLayout.measure(width: self.blockWidth - viewType.innerInset.left - viewType.innerInset.right - 60) + + return true + } + + override var instantlyResize: Bool { + return false + } + + override var height: CGFloat { + return self.textLayout.layoutSize.height + viewType.innerInset.top + viewType.innerInset.bottom + } + + override func viewClass() -> AnyClass { + return ChatListFilterRecommendedView.self + } + +} + +private final class ChatListFilterRecommendedView : GeneralContainableRowView { + private let textView: TextView = TextView() + private let button = TitleButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + addSubview(button) + button.autohighlight = true + textView.userInteractionEnabled = false + textView.isSelectable = false + } + + override func layout() { + super.layout() + guard let item = item as? GeneralRowItem else { + return + } + textView.centerY(x: item.viewType.innerInset.left) + button.centerY(x: item.blockWidth - button.frame.width - item.viewType.innerInset.right) + } + + override func updateColors() { + super.updateColors() + + textView.backgroundColor = backdorColor + button.set(background: theme.colors.accent, for: .Normal) + button.set(background: theme.colors.accent.withAlphaComponent(0.85), for: .Highlight) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? ChatListFilterRecommendedItem else { + return + } + textView.update(item.textLayout) + + button.set(font: .medium(.text), for: .Normal) + button.set(color: theme.colors.underSelectedColor, for: .Normal) + button.set(text: L10n.chatListFilterRecommendedAdd, for: .Normal) + _ = button.sizeToFit(NSMakeSize(8, 8)) + button.layer?.cornerRadius = button.frame.height / 2 + + + button.removeAllHandlers() + button.set(handler: { [weak item] _ in + item?.action() + }, for: .Click) + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatListFiltersHeaderItem.swift b/Telegram-Mac/ChatListFiltersHeaderItem.swift new file mode 100644 index 0000000000..db4db693ef --- /dev/null +++ b/Telegram-Mac/ChatListFiltersHeaderItem.swift @@ -0,0 +1,81 @@ +// +// ChatListFiltersHeaderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 03.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class ChatListFiltersHeaderItem: GeneralRowItem { + fileprivate let textLayout: TextViewLayout + fileprivate let context: AccountContext + fileprivate let sticker: LocalAnimatedSticker + init(_ initialSize: NSSize, context: AccountContext, stableId: AnyHashable, sticker: LocalAnimatedSticker, text: NSAttributedString) { + self.textLayout = TextViewLayout(text, alignment: .center, alwaysStaticItems: true) + self.context = context + self.sticker = sticker + super.init(initialSize, stableId: stableId, inset: NSEdgeInsets(left: 30.0, right: 30.0, top: 0, bottom: 10)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + textLayout.measure(width: width - inset.left - inset.right) + return super.makeSize(width, oldWidth: oldWidth) + } + + override func viewClass() -> AnyClass { + return ChatListFiltersHeaderView.self + } + + override var height: CGFloat { + return 112 + textLayout.layoutSize.height + (textLayout.layoutSize.height > 0 ? inset.bottom : 0) + } +} + + +private final class ChatListFiltersHeaderView : TableRowView { + private let stickerView: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + private let textView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(stickerView) + addSubview(textView) + textView.userInteractionEnabled = false + textView.isSelectable = false + } + + override var backdorColor: NSColor { + return theme.colors.listBackground + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? ChatListFiltersHeaderItem else { return } + + self.stickerView.update(with: item.sticker.file, size: NSMakeSize(112, 112), context: item.context, parent: nil, table: item.table, parameters: item.sticker.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: false) + + self.textView.update(item.textLayout) + + needsLayout = true + } + + override func layout() { + super.layout() + guard let item = item as? ChatListFiltersHeaderItem else { return } + + self.stickerView.centerX(y: 0) + self.textView.centerX(y: self.stickerView.frame.maxY + item.inset.bottom) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatListFiltersListController.swift b/Telegram-Mac/ChatListFiltersListController.swift new file mode 100644 index 0000000000..54245f57fb --- /dev/null +++ b/Telegram-Mac/ChatListFiltersListController.swift @@ -0,0 +1,282 @@ +// +// ChatListPresentController.swift +// Telegram +// +// Created by Mikhail Filimonov on 28.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + + +private final class ChatListPresetArguments { + let context: AccountContext + let openPreset:(ChatListFilter, Bool)->Void + let removePreset: (ChatListFilter)->Void + let addFeatured: (ChatListFeaturedFilter)->Void + let toggleSidebar: (Bool)->Void + init(context: AccountContext, openPreset: @escaping(ChatListFilter, Bool)->Void, removePreset: @escaping(ChatListFilter)->Void, addFeatured: @escaping(ChatListFeaturedFilter)->Void, toggleSidebar: @escaping(Bool)->Void) { + self.context = context + self.openPreset = openPreset + self.removePreset = removePreset + self.addFeatured = addFeatured + self.toggleSidebar = toggleSidebar + } +} +private func _id_preset(_ filter: ChatListFilter) -> InputDataIdentifier { + return InputDataIdentifier("_id_filter_\(filter.id)") +} +private func _id_recommended(_ index: Int32) -> InputDataIdentifier { + return InputDataIdentifier("_id_recommended\(index)") +} +private let _id_add_new = InputDataIdentifier("_id_add_new") +private let _id_add_tabs = InputDataIdentifier("_id_add_tabs") +private let _id_badge_tabs = InputDataIdentifier("_id_badge_tabs") + +private let _id_header = InputDataIdentifier("_id_header") + +private func chatListPresetEntries(filtersWithCounts: [(ChatListFilter, Int)], sidebar: Bool, suggested: ChatListFiltersFeaturedState?, arguments: ChatListPresetArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_header, equatable: nil, item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + + _ = attributedString.append(string: L10n.chatListFilterHeader, color: theme.colors.listGrayText, font: .normal(.text)) + + return ChatListFiltersHeaderItem(initialSize, context: arguments.context, stableId: stableId, sticker: LocalAnimatedSticker.folder, text: attributedString) + })) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterListHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + for (filter, count) in filtersWithCounts { + var viewType = bestGeneralViewType(filtersWithCounts.map { $0.0 }, for: filter) + if filtersWithCounts.count == 1 { + viewType = .firstItem + } else if filter == filtersWithCounts.last?.0, filtersWithCounts.count < 10 { + viewType = .innerItem + } + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_preset(filter), data: .init(name: filter.title, color: theme.colors.text, icon: FolderIcon(filter).icon(for: .preview), type: .nextContext(count > 0 ? "\(count)" : ""), viewType: viewType, enabled: true, description: nil, justUpdate: arc4random64(), action: { + arguments.openPreset(filter, false) + }, menuItems: { + return [ContextMenuItem(L10n.chatListFilterListRemove, handler: { + arguments.removePreset(filter) + })] + }))) + index += 1 + } + + if filtersWithCounts.count < 10 { + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_add_new, data: InputDataGeneralData(name: L10n.chatListFilterListAddNew, color: theme.colors.accent, type: .next, viewType: filtersWithCounts.isEmpty ? .singleItem : .lastItem, action: { + arguments.openPreset(ChatListFilter.new(excludeIds: filtersWithCounts.map { $0.0.id }), true) + }))) + index += 1 + } + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterListDesc), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + + + + + + + if let suggested = suggested, filtersWithCounts.count < 10 { + + let filtered = suggested.filters.filter { value -> Bool in + return filtersWithCounts.first(where: { $0.0.data == value.data }) == nil + } + if !filtered.isEmpty { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterRecommendedHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + var suggeted_index:Int32 = 0 + for filter in filtered { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_recommended(suggeted_index), equatable: InputDataEquatable(filter), item: { initialSize, stableId in + return ChatListFilterRecommendedItem(initialSize, stableId: stableId, title: filter.title, description: filter.description, viewType: bestGeneralViewType(filtered, for: filter), add: { + arguments.addFeatured(filter) + }) + })) + suggeted_index += 1 + index += 1 + } + } + + + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + if !filtersWithCounts.isEmpty { + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterTabBarHeader), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("sidebar"), equatable: InputDataEquatable(sidebar), item: { initialSize, stableId in + return ChatListFilterVisibilityItem(initialSize, stableId: stableId, sidebar: sidebar, viewType: .singleItem, toggle: { sidebar in + arguments.toggleSidebar(sidebar) + }) + })) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.chatListFilterTabBarDesc), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + + return entries +} + +func ChatListFiltersListController(context: AccountContext) -> InputDataController { + + let arguments = ChatListPresetArguments(context: context, openPreset: { filter, isNew in + context.sharedContext.bindings.rootNavigation().push(ChatListFilterController(context: context, filter: filter, isNew: isNew)) + }, removePreset: { filter in + confirm(for: context.window, header: L10n.chatListFilterConfirmRemoveHeader, information: L10n.chatListFilterConfirmRemoveText, okTitle: L10n.chatListFilterConfirmRemoveOK, successHandler: { _ in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + filters.removeAll(where: { $0.id == filter.id }) + return filters + }).start() + }) + + }, addFeatured: { featured in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + var new = ChatListFilter.new(excludeIds: filters.map { $0.id }) + new.data = featured.data + new.title = featured.title + filters.append(new) + return filters + }).start() + }, toggleSidebar: { sidebar in + _ = updateChatListFolderSettings(context.account.postbox, { + $0.withUpdatedSidebar(sidebar) + }).start() + }) + + + let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:]) + + let filtersWithCounts = chatListFilterPreferences(postbox: context.account.postbox) + |> distinctUntilChanged + |> mapToSignal { filters -> Signal<([(ChatListFilter, Int)], Bool), NoError> in + return context.account.postbox.transaction { transaction -> ([(ChatListFilter, Int)], Bool) in + return (filters.list.map { filter -> (ChatListFilter, Int) in + let count: Int + if let cachedValue = chatCountCache.with({ dict -> Int? in + return dict[filter.data] + }) { + count = cachedValue + } else if let predicate = chatListFilterPredicate(for: filter) { + count = transaction.getChatCountMatchingPredicate(predicate) + let _ = chatCountCache.modify { dict in + var dict = dict + dict[filter.data] = count + return dict + } + } else { + count = 0 + } + return (filter, count) + }, filters.sidebar) + } + } + + let suggested: Signal = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) |> map { view in + return view.values[PreferencesKeys.chatListFiltersFeaturedState] as? ChatListFiltersFeaturedState + } + + + let dataSignal = combineLatest(queue: prepareQueue, appearanceSignal, filtersWithCounts, suggested) |> map { _, filtersWithCounts, suggested in + return chatListPresetEntries(filtersWithCounts: filtersWithCounts.0, sidebar: filtersWithCounts.1, suggested: suggested, arguments: arguments) + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + + let controller = InputDataController(dataSignal: dataSignal, title: L10n.chatListFilterListTitle, removeAfterDisappear: false, hasDone: false, identifier: "filters") + + + controller.updateDatas = { data in + return .none + } + + + controller.validateData = { data in + return .success(.custom { + + }) + } + + + controller.afterTransaction = { controller in + var range: NSRange = NSMakeRange(NSNotFound, 0) + + controller.tableView.enumerateItems(with: { item in + if let stableId = item.stableId.base as? InputDataEntryId { + switch stableId { + case let .general(identifier): + if identifier.identifier.hasPrefix("_id_filter") { + if range.location == NSNotFound { + range.location = item.index + } + range.length += 1 + } + default: + if range.location != NSNotFound { + return false + } + } + } + return true + }) + + if range.location != NSNotFound { + controller.tableView.resortController = TableResortController(resortRange: range, start: { row in + + }, resort: { row in + + }, complete: { from, to in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + filters.move(at: from - range.location, to: to - range.location) + return filters + }).start() + + }) + } else { + controller.tableView.resortController = nil + } + + + } + + return controller + +} diff --git a/Telegram-Mac/ChatListHoleRowItem.swift b/Telegram-Mac/ChatListHoleRowItem.swift index ca63194efd..e9cee821ac 100644 --- a/Telegram-Mac/ChatListHoleRowItem.swift +++ b/Telegram-Mac/ChatListHoleRowItem.swift @@ -8,11 +8,12 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore class ChatListHoleRowItem: TableRowItem { - private var account:Account + private var context: AccountContext private var hole:ChatListHole override var stableId: AnyHashable { @@ -23,16 +24,18 @@ class ChatListHoleRowItem: TableRowItem { return 20 } - public init(_ initialSize:NSSize, _ account:Account, _ object: ChatListHole) { + public init(_ initialSize:NSSize, _ context: AccountContext, _ object: ChatListHole) { self.hole = object - self.account = account + self.context = context super.init(initialSize) } override func viewClass() -> AnyClass { return ChatListHoleRowView.self } - - +} + + +class ChatListHoleRowView: TableRowView { } diff --git a/Telegram-Mac/ChatListHoleRowView.swift b/Telegram-Mac/ChatListHoleRowView.swift index eaba6cee31..3ebdd1eba1 100644 --- a/Telegram-Mac/ChatListHoleRowView.swift +++ b/Telegram-Mac/ChatListHoleRowView.swift @@ -8,6 +8,3 @@ import Cocoa import TGUIKit -class ChatListHoleRowView: TableRowView { - -} diff --git a/Telegram-Mac/ChatListMessageRowItem.swift b/Telegram-Mac/ChatListMessageRowItem.swift index 0f38c4e6f7..cae66c14c6 100644 --- a/Telegram-Mac/ChatListMessageRowItem.swift +++ b/Telegram-Mac/ChatListMessageRowItem.swift @@ -8,11 +8,17 @@ import Cocoa import TGUIKit +import TelegramCore +import SyncCore +import Postbox + class ChatListMessageRowItem: ChatListRowItem { + init(_ initialSize:NSSize, context: AccountContext, message: Message, query: String, renderedPeer:RenderedPeer, readState: CombinedPeerReadState?) { + super.init(initialSize, context: context, messages: [message], readState: readState, renderedPeer: renderedPeer, highlightText: query, showBadge: false) + } + override var stableId: AnyHashable { return message!.id } - - } diff --git a/Telegram-Mac/ChatListNothingItem.swift b/Telegram-Mac/ChatListNothingItem.swift index 1c8dc1381f..58a4fceaee 100644 --- a/Telegram-Mac/ChatListNothingItem.swift +++ b/Telegram-Mac/ChatListNothingItem.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChatListNothingItem: TableRowItem { let stableIndex:ChatListIndex diff --git a/Telegram-Mac/ChatListPresetListController.swift b/Telegram-Mac/ChatListPresetListController.swift new file mode 100644 index 0000000000..c670b2c990 --- /dev/null +++ b/Telegram-Mac/ChatListPresetListController.swift @@ -0,0 +1,160 @@ +// +// ChatListPresentController.swift +// Telegram +// +// Created by Mikhail Filimonov on 28.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + + +private final class ChatListPresetArguments { + let context: AccountContext + let openPreset:(ChatListFilter)->Void + let removePreset: (ChatListFilter)->Void + init(context: AccountContext, openPreset: @escaping(ChatListFilter)->Void, removePreset: @escaping(ChatListFilter)->Void) { + self.context = context + self.openPreset = openPreset + self.removePreset = removePreset + } +} +private func _id_preset(_ filter: ChatListFilter) -> InputDataIdentifier { + return InputDataIdentifier("_id_preset_\(filter.id)") +} +private let _id_add_new = InputDataIdentifier("_id_add_new") +private let _id_add_tabs = InputDataIdentifier("_id_add_tabs") +private let _id_badge_tabs = InputDataIdentifier("_id_badge_tabs") + +private func chatListPresetEntries(state: ChatListFiltersState, arguments: ChatListPresetArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain("FILTERS"), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textTopItem))) + index += 1 + + for filter in state.filters { + var viewType = bestGeneralViewType(state.filters, for: filter) + if state.filters.count == 1 { + viewType = .firstItem + } else if filter == state.filters.last, state.filters.count < 10 { + viewType = .innerItem + } + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_preset(filter), data: .init(name: filter.title, color: theme.colors.text, type: .nextContext(filter.desc), viewType: viewType, enabled: true, description: nil, justUpdate: arc4random64(), action: { + arguments.openPreset(filter) + }, menuItems: { + return [ContextMenuItem("Remove", handler: { + arguments.removePreset(filter) + })] + }))) + index += 1 + } + + if state.filters.count < 10 { + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_add_new, data: InputDataGeneralData(name: L10n.chatListFilterListAddNew, color: theme.colors.accent, type: .next, viewType: state.filters.isEmpty ? .singleItem : .lastItem, action: { + // arguments.openPreset(ChatListFilter.new) + }))) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain("You can add \(10 - state.filters.count) more filters. Drag and drop filter to sort it."), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + } else { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain("Drag and drop filter to sort it. Right click to remove."), data: .init(color: theme.colors.listGrayText, detectBold: true, viewType: .textBottomItem))) + index += 1 + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func ChatListFiltersListController(context: AccountContext) -> InputDataController { + + let arguments = ChatListPresetArguments(context: context, openPreset: { filter in + context.sharedContext.bindings.rootNavigation().push(ChatListFilterController(context: context, filter: filter)) + }, removePreset: { filter in + _ = updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { + $0.withRemovedFilter(filter) + }).start() + }) + + let dataSignal = combineLatest(queue: prepareQueue, appearanceSignal, chatListFilterPreferences(postbox: context.account.postbox)) |> map { _, state in + return chatListPresetEntries(state: state, arguments: arguments) + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + + let controller = InputDataController(dataSignal: dataSignal, title: L10n.chatListFilterListTitle, removeAfterDisappear: false, hasDone: false) + + controller._abolishWhenNavigationSame = true + + controller.updateDatas = { data in + return .none + } + + + controller.validateData = { data in + return .success(.custom { + + }) + } + + + controller.afterTransaction = { controller in + var range: NSRange = NSMakeRange(NSNotFound, 0) + + controller.tableView.enumerateItems(with: { item in + if let stableId = item.stableId.base as? InputDataEntryId { + switch stableId { + case let .general(identifier): + if identifier.identifier.hasPrefix("_id_preset") { + if range.location == NSNotFound { + range.location = item.index + } + range.length += 1 + } + default: + if range.location != NSNotFound { + return false + } + } + } + return true + }) + + if range.location != NSNotFound { + controller.tableView.resortController = TableResortController(resortRange: range, start: { row in + + }, resort: { row in + + }, complete: { from, to in + _ = updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { + $0.withMoveFilter(from - range.location, to - range.location) + }).start() + }) + } else { + controller.tableView.resortController = nil + } + + + } + + return controller + +} diff --git a/Telegram-Mac/ChatListRevealItem.swift b/Telegram-Mac/ChatListRevealItem.swift new file mode 100644 index 0000000000..0f1feb0944 --- /dev/null +++ b/Telegram-Mac/ChatListRevealItem.swift @@ -0,0 +1,281 @@ +// +// ChatListRevealItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 27.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import Postbox +import SwiftSignalKit +import SyncCore + +class ChatListRevealItem: TableStickItem { + fileprivate let action:((ChatListFilter?)->Void)? + fileprivate let context: AccountContext? + fileprivate let tabs: [ChatListFilter] + fileprivate let selected: ChatListFilter? + fileprivate let openSettings: (()->Void)? + fileprivate let counters: ChatListFilterBadges + fileprivate let _menuItems: ((ChatListFilter?)->[ContextMenuItem])? + init(_ initialSize: NSSize, context: AccountContext, tabs: [ChatListFilter], selected: ChatListFilter?, counters: ChatListFilterBadges, action: ((ChatListFilter?)->Void)? = nil, openSettings: (()->Void)? = nil, menuItems: ((ChatListFilter?)->[ContextMenuItem])? = nil) { + self.action = action + self.context = context + self.tabs = tabs + self.selected = selected + self.openSettings = openSettings + self.counters = counters + self._menuItems = menuItems + super.init(initialSize) + } + + required init(_ initialSize: NSSize) { + self.action = nil + self.context = nil + self.tabs = [] + self.selected = nil + self.openSettings = nil + self._menuItems = nil + self.counters = ChatListFilterBadges(total: 0, filters: []) + super.init(initialSize) + } + + override var singletonItem: Bool { + return true + } + + func menuItems(for item: ChatListFilter?) -> [ContextMenuItem] { + return self._menuItems?(item) ?? [] + } + + override var stableId: AnyHashable { + return UIChatListEntryId.reveal + } + + override func viewClass() -> AnyClass { + return ChatListRevealView.self + } + + override var identifier: String { + return "ChatListRevealView" + } + + override var height: CGFloat { + return 36 + } +} + + +final class ChatListRevealView : TableStickView { + private let containerView = View() + private var animated: Bool = false + let segmentView: ScrollableSegmentView = ScrollableSegmentView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(containerView) + containerView.addSubview(segmentView) + border = [.Right] + + NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: segmentView.scrollView.contentView, queue: OperationQueue.main, using: { [weak self] notification in + guard let `self` = self else { + return + } + guard let item = self.item else { + return + } + guard let view = item.view as? ChatListRevealView else { + return + } + if !self.segmentView.scrollView.clipView.isAnimateScrolling { + if view !== self { + view.segmentView.scrollView.contentView.scroll(to: self.segmentView.scrollView.documentOffset) + } else if let view = item.table?.p_stickView as? ChatListRevealView, view !== self { + view.segmentView.scrollView.contentView.scroll(to: self.segmentView.scrollView.documentOffset) + } + } + }) + + } + + + override func mouseUp(with event: NSEvent) { + + } + override func mouseDown(with event: NSEvent) { + if mouseInside() { + } + } + + + override func updateIsVisible(_ visible: Bool, animated: Bool) { + super.updateIsVisible(visible, animated: animated) + +// var visible = visible +// if let table = item?.table { +// visible = visible && table.documentOffset.y > 0 +// } +// separator.change(opacity: visible ? 1 : 0, animated: false) + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + super.updateColors() + backgroundColor = backdorColor + segmentView.updateLocalizationAndTheme(theme: theme) + needsDisplay = true + } + + private var splitViewState: SplitViewState? + + private var removeAnimationForNextTransition: Bool = false + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? ChatListRevealItem else { + return + } + + var animated = (self.animated || animated) + self.animated = true + + + guard let context = item.context else { + return + } + + let generateIcon:(ChatListFilter?)->CGImage? = { tab in + let unreadCount:ChatListFilterBadge? = item.counters.count(for: tab) + let icon: CGImage? + if let unreadCount = unreadCount, unreadCount.count > 0 { + let attributedString = NSAttributedString.initialize(string: "\(unreadCount.count.prettyNumber)", color: theme.colors.background, font: .medium(.short), coreText: true) + let textLayout = TextNode.layoutText(maybeNode: nil, attributedString, nil, 1, .start, NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude), nil, false, .center) + var size = NSMakeSize(textLayout.0.size.width + 8, textLayout.0.size.height + 5) + size = NSMakeSize(max(size.height,size.width), size.height) + + icon = generateImage(size, rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + if item.selected == tab || unreadCount.hasUnmutedUnread { + ctx.setFillColor(theme.colors.accent.cgColor) + } else { + ctx.setFillColor(theme.colors.grayText.cgColor) + } + + + ctx.round(size, size.height/2.0) + ctx.fill(rect) + + let focus = rect.focus(textLayout.0.size) + textLayout.1.draw(focus.offsetBy(dx: 0, dy: -1), in: ctx, backingScaleFactor: 2.0, backgroundColor: .white) + + })! + } else if let _ = tab { + icon = nil + } else { + icon = nil + } + return icon + } + + animated = animated && splitViewState == context.sharedContext.layout + self.splitViewState = context.sharedContext.layout + + let segmentTheme = ScrollableSegmentTheme(background: presentation.colors.background, border: presentation.colors.border, selector: presentation.colors.accent, inactiveText: presentation.colors.grayText, activeText: presentation.colors.accent, textFont: .normal(.title)) + var index: Int = 0 + let insets = NSEdgeInsets(left: 10, right: 10, bottom: 6) + var items:[ScrollableSegmentItem] = [.init(title: L10n.chatListFilterAllChats, index: 0, uniqueId: -1, selected: item.selected == nil, insets: insets, icon: generateIcon(nil), theme: segmentTheme, equatable: UIEquatable(L10n.chatListFilterAllChats))] + index += 1 + for tab in item.tabs { + let unreadCount = item.counters.count(for: tab) + let icon: CGImage? = generateIcon(tab) + let title: String = tab.title + + items.append(ScrollableSegmentItem(title: title, index: index, uniqueId: tab.id, selected: item.selected == tab, insets: insets, icon: icon, theme: segmentTheme, equatable: UIEquatable(unreadCount))) + index += 1 + } +// if let _ = item.openSettings { +// items.append(.init(title: "", index: index, uniqueId: -2, selected: false, insets: NSEdgeInsets(left: 5, right: 10, bottom: 6), icon: theme.icons.chat_filter_add, theme: segmentTheme, equatable: UIEquatable(0))) +// index += 1 +// } +// + + + segmentView.updateItems(items, animated: animated) + + segmentView.resortRange = NSMakeRange(1, items.count - 1) + segmentView.resortHandler = { from, to in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { state in + var state = state + state.move(at: from - 1, to: to - 1) + return state + }).start() + } + segmentView.didChangeSelectedItem = { [weak item] selected in + if let item = item { + if selected.uniqueId == -1 { + item.action?(nil) + } else if selected.uniqueId == -2 { + item.openSettings?() + } else { + item.action?(item.tabs[selected.index - 1]) + } + } + } + segmentView.menuItems = { [weak item] selected in + if let item = item, selected.uniqueId != -1 && selected.uniqueId != -2 { + return item.menuItems(for: item.tabs[selected.index - 1]) + } else if let item = item, selected.uniqueId == -1 { + return item.menuItems(for: nil) + } else { + return [] + } + } + + } + + + override var isHidden: Bool { + didSet { + if isHidden { + var bp:Int = 0 + bp += 1 + } + } + } + + override var isAlwaysUp: Bool { + return true + } + + override func removeFromSuperview() { + super.removeFromSuperview() + } + + public override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func layout() { + super.layout() + + containerView.frame = NSMakeRect(0, 0, bounds.width - 1, bounds.height) + + segmentView.frame = containerView.bounds + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatListRowItem.swift b/Telegram-Mac/ChatListRowItem.swift index 390342413e..2680c91e84 100644 --- a/Telegram-Mac/ChatListRowItem.swift +++ b/Telegram-Mac/ChatListRowItem.swift @@ -8,32 +8,142 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit enum ChatListPinnedType { case some case last case none + case ad(AdditionalChatListItem) } + +final class SelectChatListItemPresentation : Equatable { + let selected:Set + static func ==(lhs:SelectChatListItemPresentation, rhs:SelectChatListItemPresentation) -> Bool { + return lhs.selected == rhs.selected + } + + init(_ selected:Set = Set()) { + self.selected = selected + } + + func deselect(chatLocation:ChatLocation) -> SelectChatListItemPresentation { + var chatLocations:Set = Set() + chatLocations.formUnion(selected) + let _ = chatLocations.remove(chatLocation) + return SelectChatListItemPresentation(chatLocations) + } + + func withToggledSelected(_ chatLocation: ChatLocation) -> SelectChatListItemPresentation { + var chatLocations:Set = Set() + chatLocations.formUnion(selected) + if chatLocations.contains(chatLocation) { + let _ = chatLocations.remove(chatLocation) + } else { + chatLocations.insert(chatLocation) + } + return SelectChatListItemPresentation(chatLocations) + } + +} + +final class SelectChatListInteraction : InterfaceObserver { + private(set) var presentation:SelectChatListItemPresentation = SelectChatListItemPresentation() + + func update(animated:Bool = true, _ f:(SelectChatListItemPresentation)->SelectChatListItemPresentation)->Void { + let oldValue = self.presentation + presentation = f(presentation) + if oldValue != presentation { + notifyObservers(value: presentation, oldValue:oldValue, animated:animated) + } + } + +} + +enum ChatListRowState : Equatable { + case plain + case deletable(onRemove:(ChatLocation)->Void, deletable:Bool) + + static func ==(lhs: ChatListRowState, rhs: ChatListRowState) -> Bool { + switch lhs { + case .plain: + if case .plain = rhs { + return true + } else { + return false + } + case .deletable(_, let deletable): + if case .deletable(_, deletable) = rhs { + return true + } else { + return false + } + } + } +} + + + class ChatListRowItem: TableRowItem { - public private(set) var message:Message? + public private(set) var messages:[Message] - var account:Account - var peer:Peer? - let renderedPeer:RenderedPeer - var peerId:PeerId { - return renderedPeer.peerId + var message: Message? { + var effective: Message? + + let filtered = messages.filter { !$0.text.isEmpty } + if filtered.count == 1 { + effective = filtered[0] + } + + if effective == nil { + effective = messages.first + } + return effective + } + + let context: AccountContext + let peer:Peer? + let renderedPeer:RenderedPeer? + let groupId: PeerGroupId + //let groupUnreadCounters: GroupReferenceUnreadCounters? + let chatListIndex:ChatListIndex? + var peerId:PeerId? { + return renderedPeer?.peerId + } + + let photo: AvatarNodeState + + var isGroup: Bool { + return groupId != .root } private let requestSessionId:MetaDisposable = MetaDisposable() override var stableId: AnyHashable { - return renderedPeer.peerId + return entryId + } + + var entryId: UIChatListEntryId { + if groupId != .root { + return .groupId(groupId) + } else if let index = chatListIndex { + return .chatId(index.messageIndex.id.peerId, nil) + } else { + preconditionFailure() + } + } + + var chatLocation: ChatLocation? { + if let index = chatListIndex { + return ChatLocation.peer(index.messageIndex.id.peerId) + } + return nil } let mentionsCount: Int32? @@ -41,20 +151,25 @@ class ChatListRowItem: TableRowItem { private var date:NSAttributedString? private var displayLayout:(TextNodeLayout, TextNode)? + private var chatNameLayout:(TextNodeLayout, TextNode)? + private var messageLayout:(TextNodeLayout, TextNode)? private var displaySelectedLayout:(TextNodeLayout, TextNode)? private var messageSelectedLayout:(TextNodeLayout, TextNode)? private var dateLayout:(TextNodeLayout, TextNode)? private var dateSelectedLayout:(TextNodeLayout, TextNode)? - + private var chatNameSelectedLayout:(TextNodeLayout, TextNode)? + private var displayNode:TextNode = TextNode() private var messageNode:TextNode = TextNode() private var displaySelectedNode:TextNode = TextNode() private var messageSelectedNode:TextNode = TextNode() - - private let messageText:NSAttributedString? + private var chatNameSelectedNode:TextNode = TextNode() + private var chatNameNode:TextNode = TextNode() + + private var messageText:NSAttributedString? private let titleText:NSAttributedString? - + private var chatTitleAttributed: NSAttributedString? private(set) var peerNotificationSettings:PeerNotificationSettings? private(set) var readState:CombinedPeerReadState? @@ -62,28 +177,76 @@ class ChatListRowItem: TableRowItem { private var badgeNode:BadgeNode? = nil private var badgeSelectedNode:BadgeNode? = nil + private var additionalBadgeNode:BadgeNode? = nil + private var additionalBadgeSelectedNode:BadgeNode? = nil + + private var typingLayout:(TextNodeLayout, TextNode)? private var typingSelectedLayout:(TextNodeLayout, TextNode)? private let clearHistoryDisposable = MetaDisposable() private let deleteChatDisposable = MetaDisposable() + private let _animateArchive:Atomic = Atomic(value: false) - var isMuted:Bool { - if let peerNotificationSettings = peerNotificationSettings as? TelegramPeerNotificationSettings { - if case .muted(_) = peerNotificationSettings.muteState { - return true + var animateArchive:Bool { + return _animateArchive.swap(false) + } + + let filter: ChatListFilter? + + var isCollapsed: Bool { + if let archiveStatus = archiveStatus { + switch archiveStatus { + case .collapsed: + return context.sharedContext.layout != .minimisize + default: + return false } } return false } - let isVerified: Bool + var hasRevealState: Bool { + return canArchive || (groupId != .root && !isCollapsed) + } + + var canArchive: Bool { + if groupId != .root { + return false + } + if context.peerId == peerId { + return false + } + if case .ad = pinnedType { + return false + } + let supportId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) + if self.peer?.id == supportId { + return false + } + + return true + } + + let associatedGroupId: PeerGroupId + let isMuted:Bool + + var hasUnread: Bool { + return ctxBadgeNode != nil + } + + let isVerified: Bool + let isScam: Bool + var isOutMessage:Bool { if let message = message { - return !message.flags.contains(.Incoming) + if message.text.isEmpty, let _ = message.media.first as? TelegramMediaAction { + return false + } + return !message.flags.contains(.Incoming) && message.id.peerId != context.peerId } return false } @@ -92,7 +255,7 @@ class ChatListRowItem: TableRowItem { if let _ = peer.botInfo { return true } - if peer.id == account.peerId { + if peer.id == context.peerId { return true } } @@ -110,8 +273,21 @@ class ChatListRowItem: TableRowItem { return false } + + + var isUnreadMarked: Bool { + if let readState = readState { + return readState.markedUnread + } + return false + } + var isSecret:Bool { - return renderedPeer.peers[renderedPeer.peerId] is TelegramSecretChat + if let renderedPeer = renderedPeer { + return renderedPeer.peers[renderedPeer.peerId] is TelegramSecretChat + } else { + return false + } } var isSending:Bool { @@ -122,55 +298,313 @@ class ChatListRowItem: TableRowItem { } var isFailed: Bool { - if let message = message { - return message.flags.contains(.Failed) - } - return false + return self.hasFailed } - let hasDraft:Bool + var isSavedMessage: Bool { + return peer?.id == context.peerId + } + + + let hasDraft:Bool + private let hasFailed: Bool let pinnedType:ChatListPinnedType + let activities: [ChatListInputActivity] + + var toolTip: String? { + return messageText?.string + } + + private(set) var isOnline: Bool? + private var presenceManager:PeerPresenceStatusManager? + + let archiveStatus: HiddenArchiveStatus? + private var groupLatestPeers:[ChatListGroupReferencePeer] = [] + + private var textLeftCutout: CGFloat = 0.0 + let contentImageSize = CGSize(width: 16, height: 16) + let contentImageSpacing: CGFloat = 2.0 + let contentImageTrailingSpace: CGFloat = 5.0 + private(set) var contentImageSpecs: [(message: Message, media: Media, size: CGSize)] = [] + - init(_ initialSize:NSSize, account:Account, message: Message?, readState:CombinedPeerReadState? = nil, notificationSettings:PeerNotificationSettings? = nil, embeddedState:PeerChatListEmbeddedInterfaceState? = nil, pinnedType:ChatListPinnedType = .none, renderedPeer:RenderedPeer, summaryInfo: ChatListMessageTagSummaryInfo = ChatListMessageTagSummaryInfo()) { + + init(_ initialSize:NSSize, context: AccountContext, pinnedType: ChatListPinnedType, groupId: PeerGroupId, peers: [ChatListGroupReferencePeer], messages: [Message], unreadState: PeerGroupUnreadCountersCombinedSummary, unreadCountDisplayCategory: TotalUnreadCountDisplayCategory, activities: [ChatListInputActivity] = [], animateGroup: Bool = false, archiveStatus: HiddenArchiveStatus = .normal, hasFailed: Bool = false, filter: ChatListFilter? = nil) { + self.groupId = groupId + self.peer = nil + self.messages = messages + self.chatListIndex = nil + self.activities = activities + self.context = context + self.mentionsCount = nil + self.pinnedType = pinnedType + self.renderedPeer = nil + self.associatedGroupId = .root + self.isMuted = false + self.isOnline = nil + self.archiveStatus = archiveStatus + self.groupLatestPeers = peers + self.isVerified = false + self.isScam = false + self.filter = filter + self.hasFailed = hasFailed + let titleText:NSMutableAttributedString = NSMutableAttributedString() + let _ = titleText.append(string: L10n.chatListArchivedChats, color: theme.chatList.textColor, font: .medium(.title)) + titleText.setSelected(color: theme.colors.underSelectedColor ,range: titleText.range) + + + var message: Message? + + let filtered = messages.filter { !$0.text.isEmpty } + if filtered.count == 1 { + message = filtered[0] + } + if message == nil { + message = messages.first + } + + self.titleText = titleText + if peers.count == 1 { + self.messageText = chatListText(account: context.account, for: message, messagesCount: messages.count, folder: true) + } else { + let textString = NSMutableAttributedString(string: "") + var isFirst = true + for peer in peers { + if let chatMainPeer = peer.peer.chatMainPeer { + let peerTitle = chatMainPeer.compactDisplayTitle + if !peerTitle.isEmpty { + if isFirst { + isFirst = false + } else { + textString.append(.initialize(string: ", ", color: theme.chatList.textColor, font: .normal(.text))) + } + textString.append(.initialize(string: peerTitle, color: peer.isUnread ? theme.chatList.textColor : theme.chatList.grayTextColor, font: .normal(.text))) + } + } + } + self.messageText = textString + } + hasDraft = false + + + + + if let message = message { + let date:NSMutableAttributedString = NSMutableAttributedString() + var time:TimeInterval = TimeInterval(message.timestamp) + time -= context.timeDifference + let range = date.append(string: DateUtils.string(forMessageListDate: Int32(time)), color: theme.colors.grayText, font: .normal(.short)) + date.setSelected(color: theme.colors.underSelectedColor,range: range) + self.date = date.copy() as? NSAttributedString + + dateLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, false, .left) + dateSelectedLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, true, .left) + } + + let mutedCount = unreadState.count(countingCategory: unreadCountDisplayCategory == .chats ? .chats : .messages, mutedCategory: .all) + + self.highlightText = nil + self.embeddedState = nil + + photo = .ArchivedChats + + super.init(initialSize) + + if case .hidden(true) = archiveStatus { + hideItem(animated: false, reload: false) + } + + + _ = _animateArchive.swap(animateGroup) + + if mutedCount > 0 { + badgeNode = BadgeNode(.initialize(string: "\(mutedCount)", color: theme.chatList.badgeTextColor, font: .medium(.small)), theme.chatList.badgeMutedBackgroundColor) + badgeSelectedNode = BadgeNode(.initialize(string: "\(mutedCount)", color: theme.chatList.badgeSelectedTextColor, font: .medium(.small)), theme.chatList.badgeSelectedBackgroundColor) + } + + + //theme.chatList.badgeBackgroundColor + + + + _ = makeSize(initialSize.width, oldWidth: 0) + } + + private let highlightText: String? + private let embeddedState:PeerChatListEmbeddedInterfaceState? + + init(_ initialSize:NSSize, context: AccountContext, messages: [Message], index: ChatListIndex? = nil, readState:CombinedPeerReadState? = nil, isMuted:Bool = false, embeddedState:PeerChatListEmbeddedInterfaceState? = nil, pinnedType:ChatListPinnedType = .none, renderedPeer:RenderedPeer, peerPresence: PeerPresence? = nil, summaryInfo: ChatListMessageTagSummaryInfo = ChatListMessageTagSummaryInfo(), activities: [ChatListInputActivity] = [], highlightText: String? = nil, associatedGroupId: PeerGroupId = .root, hasFailed: Bool = false, showBadge: Bool = true, filter: ChatListFilter? = nil) { + + + var embeddedState = embeddedState + + if let peer = renderedPeer.chatMainPeer as? TelegramChannel { + if !peer.hasPermission(.sendMessages) { + embeddedState = nil + } + } + let supportId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) + + if let peerPresence = peerPresence as? TelegramUserPresence, context.peerId != renderedPeer.peerId, renderedPeer.peerId != supportId { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + let relative = relativeUserPresenceStatus(peerPresence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp)) + + switch relative { + case .online: + self.isOnline = true + default: + self.isOnline = false + } + + + + } else { + self.isOnline = nil + } + + + var message: Message? + + let filtered = messages.filter { !$0.text.isEmpty } + if filtered.count == 1 { + message = filtered[0] + } + if message == nil { + message = messages.first + } + + self.chatListIndex = index self.renderedPeer = renderedPeer - self.account = account - self.message = message + self.context = context + self.messages = messages + self.activities = activities self.pinnedType = pinnedType + self.archiveStatus = nil self.hasDraft = embeddedState != nil + self.embeddedState = embeddedState self.peer = renderedPeer.chatMainPeer - + self.groupId = .root + self.hasFailed = hasFailed + self.filter = filter + self.associatedGroupId = associatedGroupId + self.highlightText = highlightText if let peer = peer { - isVerified = peer.isVerified + self.isVerified = peer.isVerified + self.isScam = peer.isScam } else { - isVerified = false + self.isVerified = false + self.isScam = false } + - self.peerNotificationSettings = notificationSettings + self.isMuted = isMuted self.readState = readState let titleText:NSMutableAttributedString = NSMutableAttributedString() - let _ = titleText.append(string: peer?.displayTitle, color: renderedPeer.peers[renderedPeer.peerId] is TelegramSecretChat ? theme.chatList.secretChatTextColor : theme.chatList.textColor, font: .medium(.title)) - titleText.setSelected(color: .white ,range: titleText.range) + let _ = titleText.append(string: peer?.id == context.peerId ? L10n.peerSavedMessages : peer?.displayTitle, color: renderedPeer.peers[renderedPeer.peerId] is TelegramSecretChat ? theme.chatList.secretChatTextColor : theme.chatList.textColor, font: .medium(.title)) + titleText.setSelected(color: theme.colors.underSelectedColor ,range: titleText.range) self.titleText = titleText - self.messageText = chatListText(account: account, for: message, renderedPeer: renderedPeer, embeddedState:embeddedState) - + - if let message = message { + if case let .ad(item) = pinnedType, let promo = item as? PromoChatListItem { + let sponsored:NSMutableAttributedString = NSMutableAttributedString() + let range: NSRange + switch promo.kind { + case let .psa(type, _): + range = sponsored.append(string: localizedPsa("psa.chatlist", type: type), color: theme.colors.grayText, font: .normal(.short)) + case .proxy: + range = sponsored.append(string: L10n.chatListSponsoredChannel, color: theme.colors.grayText, font: .normal(.short)) + } + sponsored.setSelected(color: theme.colors.underSelectedColor, range: range) + self.date = sponsored + dateLayout = TextNode.layoutText(maybeNode: nil, sponsored, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, false, .left) + dateSelectedLayout = TextNode.layoutText(maybeNode: nil, sponsored, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, true, .left) + } else if let message = message { let date:NSMutableAttributedString = NSMutableAttributedString() var time:TimeInterval = TimeInterval(message.timestamp) - time -= account.context.timeDifference + time -= context.timeDifference let range = date.append(string: DateUtils.string(forMessageListDate: Int32(time)), color: theme.colors.grayText, font: .normal(.short)) - date.setSelected(color: .white,range: range) + date.setSelected(color: theme.colors.underSelectedColor, range: range) self.date = date.copy() as? NSAttributedString dateLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, false, .left) dateSelectedLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, true, .left) + + + if let author = message.author as? TelegramUser, let peer = peer, peer as? TelegramUser == nil, !peer.isChannel, embeddedState == nil { + let peerText: String = (author.id == context.account.peerId ? "\(L10n.chatListYou)" : author.displayTitle) + + let attr = NSMutableAttributedString() + _ = attr.append(string: peerText, color: theme.chatList.peerTextColor, font: .normal(.text)) + attr.setSelected(color: theme.colors.underSelectedColor, range: attr.range) + + self.chatTitleAttributed = attr + } + + let contentImageFillSize = CGSize(width: 8.0, height: contentImageSize.height) + _ = contentImageFillSize + if embeddedState == nil { + for message in messages { + inner: for media in message.media { + if !message.containsSecretMedia { + if let image = media as? TelegramMediaImage { + if let _ = largestImageRepresentation(image.representations) { + //let imageSize = largest.dimensions.cgSize + //let fitSize = imageSize.aspectFilled(contentImageFillSize) + let fitSize = contentImageSize + contentImageSpecs.append((message, image, fitSize)) + } + break inner + } else if let file = media as? TelegramMediaFile { + if file.isVideo, !file.isInstantVideo, let _ = file.dimensions { + //let imageSize = dimensions.cgSize + //let fitSize = imageSize.aspectFilled(contentImageFillSize) + let fitSize = contentImageSize + contentImageSpecs.append((message, file, fitSize)) + } + break inner + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, false { + let imageTypes = ["photo", "video", "embed", "gif", "document", "telegram_album"] + if let image = content.image, let type = content.type, imageTypes.contains(type) { + if let _ = largestImageRepresentation(image.representations) { + //let imageSize = largest.dimensions.cgSize + let fitSize = contentImageSize + contentImageSpecs.append((message, image, fitSize)) + } + break inner + } else if let file = content.file { + if file.isVideo, !file.isInstantVideo, let _ = file.dimensions { + //let imageSize = dimensions.cgSize + let fitSize = contentImageSize + contentImageSpecs.append((message, file, fitSize)) + } + break inner + } + } + } + } + } + } + } + + contentImageSpecs = Array(contentImageSpecs.prefix(3)) + + for i in 0 ..< contentImageSpecs.count { + if i != 0 { + textLeftCutout += contentImageSpacing + } + textLeftCutout += contentImageSpecs[i].size.width + if i == contentImageSpecs.count - 1 { + textLeftCutout += contentImageTrailingSpace + } } + + let tagSummaryCount = summaryInfo.tagSummaryCount ?? 0 let actionsSummaryCount = summaryInfo.actionsSummaryCount ?? 0 @@ -181,166 +615,531 @@ class ChatListRowItem: TableRowItem { self.mentionsCount = nil } + if let peer = peer, peer.id != context.peerId { + self.photo = .PeerAvatar(peer, peer.displayLetters, peer.smallProfileImage, nil) + } else { + self.photo = .Empty + } + super.init(initialSize) - if let unreadCount = readState?.count, unreadCount > 0, mentionsCount == nil || (unreadCount > 1 || mentionsCount! != unreadCount) { + if showBadge { + if let unreadCount = readState?.count, unreadCount > 0, mentionsCount == nil || (unreadCount > 1 || mentionsCount! != unreadCount) { + badgeNode = BadgeNode(.initialize(string: "\(unreadCount)", color: theme.chatList.badgeTextColor, font: .medium(.small)), isMuted ? theme.chatList.badgeMutedBackgroundColor : theme.chatList.badgeBackgroundColor) + badgeSelectedNode = BadgeNode(.initialize(string: "\(unreadCount)", color: theme.chatList.badgeSelectedTextColor, font: .medium(.small)), theme.chatList.badgeSelectedBackgroundColor) + } else if isUnreadMarked && mentionsCount == nil { + badgeNode = BadgeNode(.initialize(string: " ", color: theme.chatList.badgeTextColor, font: .medium(.small)), isMuted ? theme.chatList.badgeMutedBackgroundColor : theme.chatList.badgeBackgroundColor) + badgeSelectedNode = BadgeNode(.initialize(string: " ", color: theme.chatList.badgeSelectedTextColor, font: .medium(.small)), theme.chatList.badgeSelectedBackgroundColor) + } + } + + + + if let _ = self.isOnline, let presence = peerPresence as? TelegramUserPresence { + presenceManager = PeerPresenceStatusManager(update: { [weak self] in + self?.isOnline = false + self?.redraw(animated: true) + }) - badgeNode = BadgeNode(.initialize(string: "\(unreadCount)", color: theme.chatList.badgeTextColor, font: .medium(.small)), isMuted ? theme.chatList.badgeMutedBackgroundColor : theme.chatList.badgeBackgroundColor) - badgeSelectedNode = BadgeNode(.initialize(string: "\(unreadCount)", color: theme.chatList.badgeSelectedTextColor, font: .medium(.small)), theme.chatList.badgeSelectedBackgroundColor) + presenceManager?.reset(presence: presence, timeDifference: Int32(context.timeDifference)) } + _ = makeSize(initialSize.width, oldWidth: 0) } let margin:CGFloat = 9 + + var isPinned: Bool { + switch pinnedType { + case .some: + return true + case .last: + return true + default: + return false + } + } + + var isLastPinned: Bool { + switch pinnedType { + case .last: + return true + default: + return false + } + } + + + var isFixedItem: Bool { + switch pinnedType { + case .some, .ad, .last: + return true + default: + return false + } + } + +// var contentDimensions: NSSize? { +// var dimensions: CGSize? +// if let contentImageMedia = contentImageMedia as? TelegramMediaImage { +// dimensions = largestRepresentationForPhoto(contentImageMedia)?.dimensions.size +// } else if let contentImageMedia = contentImageMedia as? TelegramMediaFile { +// dimensions = contentImageMedia.dimensions?.size +// } +// return dimensions +// } + + var isAd: Bool { + switch pinnedType { + case .ad: + return true + default: + return false + } + } + var titleWidth:CGFloat { var dateSize:CGFloat = 0 if let dateLayout = dateLayout { dateSize = dateLayout.0.size.width } - return max(300, size.width) - 50 - margin * 4 - dateSize - (isMuted ? theme.icons.dialogMuteImage.backingSize.width + 4 : 0) - (isOutMessage ? isRead ? 14 : 8 : 0) - (isVerified ? 10 : 0) - (isSecret ? 10 : 0) + return max(300, size.width) - 50 - margin * 4 - dateSize - (isMuted ? theme.icons.dialogMuteImage.backingSize.width + 4 : 0) - (isOutMessage ? isRead ? 14 : 8 : 0) - (isVerified ? 20 : 0) - (isSecret ? 10 : 0) - (isScam ? theme.icons.scam.backingSize.width : 0) } var messageWidth:CGFloat { if let badgeNode = badgeNode { - return (max(300, size.width) - 50 - margin * 3) - badgeNode.size.width - 5 - (mentionsCount != nil ? 24 : 0) + return (max(300, size.width) - 50 - margin * 3) - (badgeNode.size.width + 5) - (mentionsCount != nil ? 30 : 0) - (additionalBadgeNode != nil ? additionalBadgeNode!.size.width + 15 : 0) - (chatTitleAttributed != nil ? textLeftCutout : 0) } - return (max(300, size.width) - 50 - margin * 4) - (pinnedType != .none ? 20 : 0) - (mentionsCount != nil ? 24 : 0) + + return (max(300, size.width) - 50 - margin * 4) - (isPinned ? 20 : 0) - (mentionsCount != nil ? 24 : 0) - (additionalBadgeNode != nil ? additionalBadgeNode!.size.width + 15 : 0) - (chatTitleAttributed != nil ? textLeftCutout : 0) } let leftInset:CGFloat = 50 + (10 * 2.0); override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - if self.oldWidth == 0 || self.oldWidth != width { - - if displayLayout == nil || !displayLayout!.0.isPerfectSized || self.oldWidth > width { - displayLayout = TextNode.layoutText(maybeNode: displayNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, false, .left) + let result = super.makeSize(width, oldWidth: oldWidth) + + if self.groupId == .root { + var text: NSAttributedString? + if case let .ad(promo) = pinnedType, message == nil { + if let promo = promo as? PromoChatListItem { + switch promo.kind { + case let .psa(_, message): + if let message = message { + let attr = NSMutableAttributedString() + _ = attr.append(string: message, color: theme.colors.grayText, font: .normal(.text)) + attr.setSelected(color: theme.colors.underSelectedColor, range: attr.range) + text = attr + } + default: + break + } + } + } + if text == nil { + var messageText = chatListText(account: context.account, for: message, messagesCount: self.messages.count, renderedPeer: renderedPeer, embeddedState: embeddedState) + if let query = highlightText, let copy = messageText.mutableCopy() as? NSMutableAttributedString, let range = rangeOfSearch(query, in: copy.string) { + if copy.range.contains(range.min) && copy.range.contains(range.max - 1), copy.range != range { + copy.addAttribute(.foregroundColor, value: theme.colors.text, range: range) + copy.addAttribute(.font, value: NSFont.medium(.text), range: range) + messageText = copy + } + } + text = messageText } - if messageLayout == nil || !messageLayout!.0.isPerfectSized || self.oldWidth > width { - messageLayout = TextNode.layoutText(maybeNode: messageNode, messageText, nil, 2, .end, NSMakeSize(messageWidth, size.height), nil, false, .left) + self.messageText = text! + } + + + + if displayLayout == nil || !displayLayout!.0.isPerfectSized || self.oldWidth > width { + displayLayout = TextNode.layoutText(maybeNode: displayNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, false, .left) + } + + if displaySelectedLayout == nil || !displaySelectedLayout!.0.isPerfectSized || self.oldWidth > width { + displaySelectedLayout = TextNode.layoutText(maybeNode: displaySelectedNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, true, .left) + } + + if chatNameLayout == nil || !chatNameLayout!.0.isPerfectSized || self.oldWidth > width, let chatTitleAttributed = chatTitleAttributed { + chatNameLayout = TextNode.layoutText(maybeNode: chatNameNode, chatTitleAttributed, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, false, .left) + } + + if chatNameSelectedLayout == nil || !chatNameSelectedLayout!.0.isPerfectSized || self.oldWidth > width, let chatTitleAttributed = chatTitleAttributed { + chatNameSelectedLayout = TextNode.layoutText(maybeNode: chatNameSelectedNode, chatTitleAttributed, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, true, .left) + } + + var textCutout: TextNodeCutout? + if !textLeftCutout.isZero { + textCutout = TextNodeCutout(position: .TopLeft, size: CGSize(width: textLeftCutout, height: 14)) + } + + + if messageLayout == nil || !messageLayout!.0.isPerfectSized || self.oldWidth > width { + messageLayout = TextNode.layoutText(maybeNode: messageNode, messageText, nil, chatTitleAttributed != nil ? 1 : 2, .end, NSMakeSize(messageWidth, size.height), textCutout, false, .left, 1) + } + if messageSelectedLayout == nil || !messageSelectedLayout!.0.isPerfectSized || self.oldWidth > width { + messageSelectedLayout = TextNode.layoutText(maybeNode: messageSelectedNode, messageText, nil, chatTitleAttributed != nil ? 1 : 2, .end, NSMakeSize(messageWidth, size.height), textCutout, true, .left, 1) + } + return result + } + + + var markAsUnread: Bool { + return !isSecret && !isUnreadMarked && badgeNode == nil && mentionsCount == nil + } + + func collapseOrExpandArchive() { + context.sharedContext.bindings.mainController().chatList.collapseOrExpandArchive() + } + func hidePromoItem(_ peerId: PeerId) { + context.sharedContext.bindings.mainController().chatList.hidePromoItem(peerId) + } + + func toggleHideArchive() { + context.sharedContext.bindings.mainController().chatList.toggleHideArchive() + } + + func toggleUnread() { + if let peerId = peerId { + _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() + } + } + func toggleMuted() { + let context = self.context + if let peerId = peerId { + if isMuted { + _ = togglePeerMuted(account: context.account, peerId: peerId).start() + } else { + var options:[ModalOptionSet] = [] + + options.append(ModalOptionSet(title: L10n.chatListMute1Hour, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute4Hours, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute8Hours, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute1Day, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMute3Days, selected: false, editable: true)) + options.append(ModalOptionSet(title: L10n.chatListMuteForever, selected: true, editable: true)) + + var intervals:[Int32] = [60 * 60, 60 * 60 * 4, 60 * 60 * 8, 60 * 60 * 24, 60 * 60 * 24 * 3, Int32.max] + + showModal(with: ModalOptionSetController(context: context, options: options, selectOne: true, actionText: (L10n.chatInputMute, theme.colors.accent), title: L10n.peerInfoNotifications, result: { result in + + for (i, option) in result.enumerated() { + inner: switch option { + case .selected: + _ = updatePeerMuteSetting(account: context.account, peerId: peerId, muteInterval: intervals[i]).start() + break + default: + break inner + } + } + + }), for: context.window) } - if displaySelectedLayout == nil || !displaySelectedLayout!.0.isPerfectSized || self.oldWidth > width { - displaySelectedLayout = TextNode.layoutText(maybeNode: displaySelectedNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, true, .left) + + + } + } + + func togglePinned() { + if let chatLocation = chatLocation { + let location: TogglePeerChatPinnedLocation + + if let filter = self.filter { + location = .filter(filter.id) + } else { + location = .group(self.associatedGroupId) } - if messageSelectedLayout == nil || !messageSelectedLayout!.0.isPerfectSized || self.oldWidth > width { - messageSelectedLayout = TextNode.layoutText(maybeNode: messageSelectedNode, messageText, nil, 2, .end, NSMakeSize(messageWidth, size.height), nil, true, .left) + let context = self.context + + _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: chatLocation.pinnedItemId) |> deliverOnMainQueue).start(next: { result in + switch result { + case .limitExceeded: + confirm(for: context.window, information: L10n.chatListContextPinErrorNew2, okTitle: L10n.alertOK, cancelTitle: "", thridTitle: L10n.chatListContextPinErrorNewSetupFolders, successHandler: { result in + + switch result { + case .thrid: + context.sharedContext.bindings.rootNavigation().push(ChatListFiltersListController(context: context)) + default: + break + } + + }) + //alert(for: mainWindow, info: L10n.chatListContextPinErrorNew2) + default: + break + } + }) + } + + } + + func toggleArchive() { + if let peerId = peerId { + switch associatedGroupId { + case .root: + let postbox = context.account.postbox + context.sharedContext.bindings.mainController().chatList.setAnimateGroupNextTransition(Namespaces.PeerGroup.archive) + context.sharedContext.bindings.mainController().chatList.addUndoAction(ChatUndoAction(peerId: peerId, type: .archiveChat, action: { status in + switch status { + case .cancelled: + break + //_ = updatePeerGroupIdInteractively(postbox: postbox, peerId: peerId, groupId: .root).start() + case .success: + _ = updatePeerGroupIdInteractively(postbox: postbox, peerId: peerId, groupId: Namespaces.PeerGroup.archive).start() + default: + break + } + })) + default: + _ = updatePeerGroupIdInteractively(postbox: context.account.postbox, peerId: peerId, groupId: .root).start() } } - return super.makeSize(width, oldWidth: oldWidth) } - + func delete() { + if let peerId = peerId { + let signal = removeChatInteractively(context: context, peerId: peerId, userId: peer?.id) + _ = signal.start() + } + } - override func menuItems() -> Signal<[ContextMenuItem], Void> { + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + + let context = self.context + let peerId = self.peerId if let peer = peer { - var items:[ContextMenuItem] = [] - let deleteChat = {[weak self] in - if let strongSelf = self { - let signal = removeChatInteractively(account: strongSelf.account, peerId: strongSelf.peerId) |> filter {$0} |> mapToSignal { _ -> Signal in - return globalPeerHandler.get() |> take(1) - } |> deliverOnMainQueue - - strongSelf.deleteChatDisposable.set(signal.start(next: { [weak self] peerId in - if peerId == self?.peerId { - self?.account.context.mainNavigation?.close() - } - })) - } + let deleteChat:()->Void = { [weak self] in + self?.delete() + } + + + guard let peerId = self.peerId else { + return .single([]) } let clearHistory = { [weak self] in - if let strongSelf = self { - confirm(for: mainWindow, with: appName, and: tr(.confirmDeleteChatUser), successHandler: { _ in - strongSelf.clearHistoryDisposable.set(clearHistoryInteractively(postbox: strongSelf.account.postbox, peerId: strongSelf.peerId).start()) + if let strongSelf = self, let peer = strongSelf.peer { + + var thridTitle: String? = nil + + var canRemoveGlobally: Bool = false + if peerId.namespace == Namespaces.Peer.CloudUser && peerId != context.account.peerId && !peer.isBot { + if context.limitConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever { + canRemoveGlobally = true + } + } + + if canRemoveGlobally { + thridTitle = L10n.chatMessageDeleteForMeAndPerson(peer.displayTitle) + } + + modernConfirm(for: mainWindow, account: strongSelf.context.account, peerId: strongSelf.peer?.id, information: strongSelf.peer is TelegramUser ? strongSelf.peerId == context.peerId ? L10n.peerInfoConfirmClearHistorySavedMesssages : canRemoveGlobally ? L10n.peerInfoConfirmClearHistoryUserBothSides : L10n.peerInfoConfirmClearHistoryUser : L10n.peerInfoConfirmClearHistoryGroup, okTitle: L10n.peerInfoConfirmClear, thridTitle: thridTitle, thridAutoOn: false, successHandler: { result in + + context.sharedContext.bindings.mainController().chatList.addUndoAction(ChatUndoAction(peerId: peerId, type: .clearHistory, action: { status in + switch status { + case .success: + context.chatUndoManager.clearHistoryInteractively(postbox: context.account.postbox, peerId: peerId, type: result == .thrid ? .forEveryone : .forLocalPeer) + break + default: + break + } + })) }) } } let call = { [weak self] in - if let peerId = self?.peer?.id, let account = self?.account { - self?.requestSessionId.set((phoneCall(account, peerId: peerId) |> deliverOnMainQueue).start(next: { result in - applyUIPCallResult(account, result) + if let peerId = self?.peer?.id, let context = self?.context { + self?.requestSessionId.set((phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) })) } } - let togglePin = {[weak self] in - if let strongSelf = self { - _ = togglePeerChatPinned(postbox: strongSelf.account.postbox, peerId: strongSelf.peerId).start() - } + let togglePin:()->Void = { [weak self] in + self?.togglePinned() } - let toggleMute = {[weak self] in - if let strongSelf = self { - _ = togglePeerMuted(account: strongSelf.account, peerId: strongSelf.peerId).start() - } + let toggleArchive:()->Void = { [weak self] in + self?.toggleArchive() } - let leaveGroup = { [weak self] in - if let strongSelf = self { - confirm(for: mainWindow, with: appName, and: tr(.confirmLeaveGroup), successHandler: { _ in - strongSelf.deleteChatDisposable.set(leftGroup(account: strongSelf.account, peerId: strongSelf.peerId).start()) - }) - } + let toggleMute:()->Void = { [weak self] in + self?.toggleMuted() } - let rGroup = { [weak self] in - if let strongSelf = self { - _ = returnGroup(account: strongSelf.account, peerId: strongSelf.peerId).start() - } + let leaveGroup = { + modernConfirm(for: mainWindow, account: context.account, peerId: peerId, information: L10n.confirmLeaveGroup, okTitle: L10n.peerInfoConfirmLeave, successHandler: { _ in + _ = leftGroup(account: context.account, peerId: peerId).start() + }) } - items.append(ContextMenuItem(pinnedType == .none ? tr(.chatListContextPin) : tr(.chatListContextUnpin), handler: togglePin)) + let rGroup = { + _ = returnGroup(account: context.account, peerId: peerId).start() + } - items.append(ContextMenuItem(isMuted ? tr(.chatListContextUnmute) : tr(.chatListContextMute), handler: toggleMute)) + if !isAd && groupId == .root { + items.append(ContextMenuItem(!isPinned ? tr(L10n.chatListContextPin) : tr(L10n.chatListContextUnpin), handler: togglePin)) + } + + if groupId == .root, (canArchive || associatedGroupId != .root), filter == nil { + items.append(ContextMenuItem(associatedGroupId == .root ? L10n.chatListSwipingArchive : L10n.chatListSwipingUnarchive, handler: toggleArchive)) + } + + if context.peerId != peer.id, !isAd { + items.append(ContextMenuItem(isMuted ? tr(L10n.chatListContextUnmute) : tr(L10n.chatListContextMute), handler: toggleMute)) + } if peer is TelegramUser { - if peer.canCall && peer.id != account.peerId { - items.append(ContextMenuItem(tr(.chatListContextCall), handler: call)) + if peer.canCall && peer.id != context.peerId { + items.append(ContextMenuItem(tr(L10n.chatListContextCall), handler: call)) } - items.append(ContextMenuItem(tr(.chatListContextClearHistory), handler: clearHistory)) - items.append(ContextMenuItem(tr(.chatListContextDeleteChat), handler: deleteChat)) + items.append(ContextMenuItem(L10n.chatListContextClearHistory, handler: clearHistory)) + items.append(ContextMenuItem(L10n.chatListContextDeleteChat, handler: deleteChat)) + } + + if !isSecret { + if markAsUnread { + items.append(ContextMenuItem(tr(L10n.chatListContextMaskAsUnread), handler: { [weak self] in + guard let `self` = self else {return} + _ = togglePeerUnreadMarkInteractively(postbox: self.context.account.postbox, viewTracker: self.context.account.viewTracker, peerId: peerId).start() + + })) + + } else if badgeNode != nil || mentionsCount != nil || isUnreadMarked { + items.append(ContextMenuItem(tr(L10n.chatListContextMaskAsRead), handler: { [weak self] in + guard let `self` = self else {return} + _ = togglePeerUnreadMarkInteractively(postbox: self.context.account.postbox, viewTracker: self.context.account.viewTracker, peerId: peerId).start() + })) + } + } + + if isAd { + items.append(ContextMenuItem(tr(L10n.chatListContextHidePromo), handler: { [weak self] in + guard let `self` = self, let peerId = self.peerId else {return} + self.hidePromoItem(peerId) + })) } + - if let peer = peer as? TelegramGroup { - items.append(ContextMenuItem(tr(.chatListContextClearHistory), handler: clearHistory)) + if let peer = peer as? TelegramGroup, !isAd { + items.append(ContextMenuItem(tr(L10n.chatListContextClearHistory), handler: clearHistory)) switch peer.membership { case .Member: - items.append(ContextMenuItem(tr(.chatListContextLeaveGroup), handler: leaveGroup)) + items.append(ContextMenuItem(L10n.chatListContextLeaveGroup, handler: leaveGroup)) case .Left: - items.append(ContextMenuItem(tr(.chatListContextReturnGroup), handler: rGroup)) + items.append(ContextMenuItem(L10n.chatListContextReturnGroup, handler: rGroup)) default: break } - items.append(ContextMenuItem(tr(.chatListContextDeleteAndExit), handler: deleteChat)) - } else if let peer = peer as? TelegramChannel { + items.append(ContextMenuItem(L10n.chatListContextDeleteAndExit, handler: deleteChat)) + } else if let peer = peer as? TelegramChannel, !isAd, !peer.flags.contains(.hasGeo) { if case .broadcast = peer.info { - items.append(ContextMenuItem(tr(.chatListContextLeaveChannel), handler: deleteChat)) - } else { + items.append(ContextMenuItem(L10n.chatListContextLeaveChannel, handler: deleteChat)) + } else if !isAd { if peer.addressName == nil { - items.append(ContextMenuItem(tr(.chatListContextClearHistory), handler: clearHistory)) + items.append(ContextMenuItem(L10n.chatListContextClearHistory, handler: clearHistory)) } - items.append(ContextMenuItem(tr(.chatListContextLeaveGroup), handler: deleteChat)) + items.append(ContextMenuItem(L10n.chatListContextLeaveGroup, handler: deleteChat)) } } - return .single(items) + } else { + if !isAd, groupId == .root { + items.append(ContextMenuItem(!isPinned ? tr(L10n.chatListContextPin) : tr(L10n.chatListContextUnpin), handler: { [weak self] in + self?.togglePinned() + })) + } + } + + if groupId != .root, context.sharedContext.layout != .minimisize, let archiveStatus = archiveStatus { + switch archiveStatus { + case .collapsed: + items.append(ContextMenuItem(L10n.chatListRevealActionExpand , handler: { [weak self] in + self?.collapseOrExpandArchive() + })) + default: + items.append(ContextMenuItem(L10n.chatListRevealActionCollapse, handler: { [weak self] in + self?.collapseOrExpandArchive() + })) + } } - return .single([]) + + let filter = self.filter + + return .single(items) |> mapToSignal { items in + return chatListFilterPreferences(postbox: context.account.postbox) |> deliverOnMainQueue |> take(1) |> map { filters -> [ContextMenuItem] in + + var items = items + + var submenu: [ContextMenuItem] = [] + + + + if let peerId = peerId, peerId.namespace != Namespaces.Peer.SecretChat { + for item in filters.list { + + submenu.append(ContextMenuItem(item.title, handler: { + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { list in + var list = list + for (i, folder) in list.enumerated() { + var folder = folder + if folder.id == item.id { + if item.data.includePeers.peers.contains(peerId) { + var peers = folder.data.includePeers.peers + peers.removeAll(where: { $0 == peerId }) + folder.data.includePeers.setPeers(peers) + } else { + folder.data.includePeers.setPeers(folder.data.includePeers.peers + [peerId]) + } + list[i] = folder + + } + } + return list + }).start() + }, state: item.data.includePeers.peers.contains(peerId) ? NSControl.StateValue.on : nil)) + } + } + + if !submenu.isEmpty { + items.append(ContextSeparatorItem()) + let item = ContextMenuItem(L10n.chatListFilterAddToFolder) + let menu = NSMenu() + for item in submenu { + menu.addItem(item) + } + item.submenu = menu + items.append(item) + } + + return items + } + } } var ctxDisplayLayout:(TextNodeLayout, TextNode)? { - if isSelected && account.context.layout != .single { + if isSelected && context.sharedContext.layout != .single { return displaySelectedLayout } return displayLayout } + + var ctxChatNameLayout:(TextNodeLayout, TextNode)? { + if isSelected && context.sharedContext.layout != .single { + return chatNameSelectedLayout + } + return chatNameLayout + } + var ctxMessageLayout:(TextNodeLayout, TextNode)? { - if isSelected && account.context.layout != .single { + if isSelected && context.sharedContext.layout != .single { if let typingSelectedLayout = typingSelectedLayout { return typingSelectedLayout } @@ -352,19 +1151,27 @@ class ChatListRowItem: TableRowItem { return messageLayout } var ctxDateLayout:(TextNodeLayout, TextNode)? { - if isSelected && account.context.layout != .single { + if isSelected && context.sharedContext.layout != .single { return dateSelectedLayout } return dateLayout } var ctxBadgeNode:BadgeNode? { - if isSelected && account.context.layout != .single { + if isSelected && context.sharedContext.layout != .single { return badgeSelectedNode } return badgeNode } + var ctxAdditionalBadgeNode:BadgeNode? { + if isSelected && context.sharedContext.layout != .single { + return additionalBadgeSelectedNode + } + return additionalBadgeNode + } + + override var instantlyResize: Bool { return true } @@ -380,7 +1187,15 @@ class ChatListRowItem: TableRowItem { } override var height: CGFloat { - return 66; + if let archiveStatus = archiveStatus, context.sharedContext.layout != .minimisize { + switch archiveStatus { + case .collapsed: + return 30 + default: + return 70 + } + } + return 70 } } diff --git a/Telegram-Mac/ChatListRowView.swift b/Telegram-Mac/ChatListRowView.swift index c99b682101..21c32f3182 100644 --- a/Telegram-Mac/ChatListRowView.swift +++ b/Telegram-Mac/ChatListRowView.swift @@ -8,27 +8,244 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox -class ChatListRowView: TableRowView { + + +private class ChatListDraggingContainerView : View { + fileprivate var item: ChatListRowItem? + fileprivate var activeDragging:Bool = false + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + registerForDraggedTypes([.tiff, .string, .kUrl, .kFileUrl]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override public func performDragOperation(_ sender: NSDraggingInfo) -> Bool { + if activeDragging { + activeDragging = false + needsDisplay = true + if let tiff = sender.draggingPasteboard.data(forType: .tiff), let image = NSImage(data: tiff) { + _ = (putToTemp(image: image) |> deliverOnMainQueue).start(next: { [weak item] path in + guard let item = item, let chatLocation = item.chatLocation else {return} + item.context.sharedContext.bindings.rootNavigation().push(ChatController(context: item.context, chatLocation: chatLocation, initialAction: .files(list: [path], behavior: .automatic))) + }) + } else { + let list = sender.draggingPasteboard.propertyList(forType: .kFilenames) as? [String] + if let item = item, let list = list { + let list = list.filter { path -> Bool in + if let size = fs(path) { + return size <= 2000 * 1024 * 1024 + } + return false + } + if !list.isEmpty, let chatLocation = item.chatLocation { + item.context.sharedContext.bindings.rootNavigation().push(ChatController(context: item.context, chatLocation: chatLocation, initialAction: .files(list: list, behavior: .automatic))) + } + } + } + + + return true + } + return false + } + + override public func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + if let item = item, let peer = item.peer, peer.canSendMessage, mouseInside() { + activeDragging = true + needsDisplay = true + } + superview?.draggingEntered(sender) + return .generic + + } + + + + override public func draggingExited(_ sender: NSDraggingInfo?) { + activeDragging = false + needsDisplay = true + superview?.draggingExited(sender) + } + + public override func draggingEnded(_ sender: NSDraggingInfo) { + activeDragging = false + needsDisplay = true + superview?.draggingEnded(sender) + } +} + +private final class ChatListExpandView: View { + private let titleView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + titleView.userInteractionEnabled = false + titleView.isSelectable = false + + self.addSubview(titleView) + } + override func updateLocalizationAndTheme(theme: PresentationTheme) { + let titleLayout = TextViewLayout(.initialize(string: L10n.chatListArchivedChats, color: theme.colors.grayText, font: .medium(12)), maximumNumberOfLines: 1, alwaysStaticItems: true) + titleLayout.measure(width: .greatestFiniteMagnitude) + titleView.update(titleLayout) + needsLayout = true + } + + override func layout() { + super.layout() + titleView.center() + } + + func animateOnce() { + titleView.layer?.animateScaleSpring(from: 0.7, to: 1, duration: 0.35, removeOnCompletion: true, bounce: true, completion: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +private final class ChatListMediaPreviewView: View { + private let context: AccountContext + private let message: Message + private let media: Media + + private let imageView: TransformImageView + + private let playIcon: ImageView = ImageView() + + private var requestedImage: Bool = false + private var disposable: Disposable? + + init(context: AccountContext, message: Message, media: Media) { + self.context = context + self.message = message + self.media = media + + self.imageView = TransformImageView() + self.playIcon.image = theme.icons.chat_list_thumb_play + self.playIcon.sizeToFit() + super.init() + + self.addSubview(self.imageView) + self.addSubview(self.playIcon) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + } + + func updateLayout(size: CGSize) { + var dimensions = CGSize(width: 100.0, height: 100.0) + if let image = self.media as? TelegramMediaImage { + playIcon.isHidden = true + if let largest = largestImageRepresentation(image.representations) { + dimensions = largest.dimensions.size + if !self.requestedImage { + self.requestedImage = true + let signal = mediaGridMessagePhoto(account: self.context.account, imageReference: .message(message: MessageReference(self.message), media: image), scale: backingScaleFactor) + self.imageView.setSignal(signal) + } + } + } else if let file = self.media as? TelegramMediaFile { + if file.isAnimated { + self.playIcon.isHidden = true + } else { + self.playIcon.isHidden = false + } + + if let mediaDimensions = file.dimensions { + dimensions = mediaDimensions.size + if !self.requestedImage { + self.requestedImage = true + let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, fileReference: .message(message: MessageReference(self.message), media: file), scale: backingScaleFactor) + self.imageView.setSignal(signal) + } + } + } + + self.imageView.frame = CGRect(origin: CGPoint(), size: size) + //self.playIcon.center() + self.imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: NSEdgeInsets())) + + } +} + + +class ChatListRowView: TableRowView, ViewDisplayDelegate, RevealTableView { - private var titleText:TextNode - private var messageText:TextNode + private let revealLeftView: View = View() + + private var internalDelta: CGFloat? + + private let revealRightView: View = View() + private var titleText:TextNode = TextNode() + private var messageText:TextNode = TextNode() private var badgeView:View? + private var additionalBadgeView:View? + + private var activeImage: ImageView? + private var activitiesModel:ChatActivitiesModel? - private var photo:AvatarControl = AvatarControl(font: .avatar(.custom(22))) - private var activeDragging:Bool = false + private var photo:AvatarControl = AvatarControl(font: .avatar(22)) private var hiddemMessage:Bool = false private let peerInputActivitiesDisposable:MetaDisposable = MetaDisposable() + private var removeControl:ImageButton? = nil + private var animatedView: RowAnimateView? + private var archivedPhoto: LAnimationButton? + private let containerView: ChatListDraggingContainerView = ChatListDraggingContainerView(frame: NSZeroRect) + private var expandView: ChatListExpandView? + + + private var currentTextLeftCutout: CGFloat = 0.0 + private var currentMediaPreviewSpecs: [(message: Message, media: Media, size: CGSize)] = [] + private var mediaPreviewViews: [MediaId: ChatListMediaPreviewView] = [:] + + + private var revealActionInvoked: Bool = false { + didSet { + animateOnceAfterDelta = true + } + } + var endRevealState: SwipeDirection? { + didSet { + internalDelta = nil + if let oldValue = oldValue, endRevealState == nil { + switch oldValue { + case .left, .right: + revealActionInvoked = true + completeReveal(direction: .none) + default: + break + } + } + } + } override var isFlipped: Bool { return true } /* let theme:ChatActivitiesTheme - if item.isSelected && item.account.context.layout != .single { + if item.isSelected && item.context.sharedContext.layout != .single { theme = ChatActivitiesWhiteTheme() } else if item.isSelected || item.isPinned { theme = ChatActivitiesTheme(backgroundColor: .grayUI) @@ -42,44 +259,64 @@ class ChatListRowView: TableRowView { var inputActivities:(PeerId, [(Peer, PeerInputActivity)])? { didSet { + + for (_, media, _) in self.currentMediaPreviewSpecs { + guard let mediaId = media.id else { + continue + } + if let previewView = self.mediaPreviewViews[mediaId] { + previewView.isHidden = inputActivities != nil && !inputActivities!.1.isEmpty + } + } + if let inputActivities = inputActivities, let item = item as? ChatListRowItem { + let oldValue = oldValue?.1.map { + ChatListInputActivity($0, $1) + } if inputActivities.1.isEmpty { activitiesModel?.clean() activitiesModel?.view?.removeFromSuperview() activitiesModel = nil - self.needsLayout = true self.hiddemMessage = false - self.needsDisplay = true + containerView.needsDisplay = true } else if activitiesModel == nil { activitiesModel = ChatActivitiesModel() - addSubview(activitiesModel!.view!) + containerView.addSubview(activitiesModel!.view!) } + let activity:ActivitiesTheme - if item.isSelected && item.account.context.layout != .single { + if item.isSelected && item.context.sharedContext.layout != .single { activity = theme.activity(key: 10 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activitySelectedColor, backgroundColor: theme.chatList.selectedBackgroundColor) } else if item.isSelected { activity = theme.activity(key: 11 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activityPinnedColor, backgroundColor: theme.chatList.singleLayoutSelectedBackgroundColor) - } else if item.pinnedType != .none { + } else if self.containerView.activeDragging || item.isHighlighted { + activity = theme.activity(key: 13 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activityColor, backgroundColor: theme.chatList.activeDraggingBackgroundColor) + } else if item.isFixedItem { activity = theme.activity(key: 12 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activityPinnedColor, backgroundColor: theme.chatList.pinnedBackgroundColor) } else if contextMenu != nil { activity = theme.activity(key: 13 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activityContextMenuColor, backgroundColor: theme.chatList.contextMenuBackgroundColor) } else { activity = theme.activity(key: 14 + (theme.dark ? 10 : 20), foregroundColor: theme.chatList.activityColor, backgroundColor: theme.colors.background) } + if oldValue != item.activities || activity != activitiesModel?.theme { + activitiesModel?.update(with: inputActivities, for: item.messageWidth, theme: activity, layout: { [weak self] show in + if let item = self?.item as? ChatListRowItem, let displayLayout = item.ctxDisplayLayout { + self?.activitiesModel?.view?.setFrameOrigin(item.leftInset, displayLayout.0.size.height + item.margin + 3) + } + self?.hiddemMessage = show + self?.containerView.needsDisplay = true + }) + } + - activitiesModel?.update(with: inputActivities, for: item.messageWidth, theme: activity, layout: { [weak self] show in - self?.needsLayout = true - self?.hiddemMessage = show - self?.needsDisplay = true - }) - - activitiesModel?.view?.isHidden = item.account.context.layout == .minimisize + activitiesModel?.view?.isHidden = item.context.sharedContext.layout == .minimisize } else { activitiesModel?.clean() activitiesModel?.view?.removeFromSuperview() activitiesModel = nil + hiddemMessage = false } } } @@ -96,17 +333,58 @@ class ChatListRowView: TableRowView { self.inputActivities = inputActivities } + + override func focusAnimation(_ innerId: AnyHashable?) { + + if animatedView == nil { + self.animatedView = RowAnimateView(frame:bounds) + self.animatedView?.isEventLess = true + containerView.addSubview(animatedView!) + animatedView?.backgroundColor = theme.colors.focusAnimationColor + animatedView?.layer?.opacity = 0 + + } + animatedView?.stableId = item?.stableId + + + let animation: CABasicAnimation = makeSpringAnimation("opacity") + + animation.fromValue = animatedView?.layer?.presentation()?.opacity ?? 0 + animation.toValue = 0.5 + animation.autoreverses = true + animation.isRemovedOnCompletion = true + animation.fillMode = CAMediaTimingFillMode.forwards + + animation.delegate = CALayerAnimationDelegate(completion: { [weak self] completed in + if completed { + self?.animatedView?.removeFromSuperview() + self?.animatedView = nil + } + }) + animation.isAdditive = false + + animatedView?.layer?.add(animation, forKey: "opacity") + + } + override var backdorColor: NSColor { if let item = item as? ChatListRowItem { - if item.account.context.layout == .single, item.isSelected { + if item.isCollapsed { + return theme.colors.grayBackground + } + if item.isHighlighted && !item.isSelected { + return theme.chatList.activeDraggingBackgroundColor + } + if item.context.sharedContext.layout == .single, item.isSelected { return theme.chatList.singleLayoutSelectedBackgroundColor } - if !item.isSelected && activeDragging { + if !item.isSelected && containerView.activeDragging { return theme.chatList.activeDraggingBackgroundColor } - if item.pinnedType != .none && !item.isSelected { + if item.isFixedItem && !item.isSelected { return theme.chatList.pinnedBackgroundColor } + return item.isSelected ? theme.chatList.selectedBackgroundColor : contextMenu != nil ? theme.chatList.contextMenuBackgroundColor : theme.colors.background } return theme.colors.background @@ -114,92 +392,115 @@ class ChatListRowView: TableRowView { override func draw(_ layer: CALayer, in ctx: CGContext) { - - ctx.setFillColor(theme.colors.background.cgColor) - ctx.fill(bounds) + super.draw(layer, in: ctx) + // if let item = self.item as? ChatListRowItem { - - if(!item.isSelected) { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.pinnedType == .last ? 0 : item.leftInset, NSHeight(layer.bounds) - .borderSize, item.pinnedType == .last ? layer.frame.width : layer.bounds.width - item.leftInset, .borderSize)) + if !item.isSelected { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(layer.bounds.width - .borderSize, 0, .borderSize, NSHeight(self.frame))) - } - - if let context = item.account.applicationContext as? TelegramApplicationContext { - if context.layout == .minimisize { - return + if layer != containerView.layer { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) + } else { + + if item.context.sharedContext.layout == .minimisize { + return + } + + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(layer.bounds) + } + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.isLastPinned ? 0 : item.leftInset, NSHeight(layer.bounds) - .borderSize, item.isLastPinned ? layer.frame.width : layer.bounds.width - item.leftInset, .borderSize)) } } - let highlighted = item.isSelected && item.account.context.layout != .single - - - if item.ctxBadgeNode == nil && (item.pinnedType == .some || item.pinnedType == .last) { - ctx.draw(highlighted ? theme.icons.pinnedImageSelected : theme.icons.pinnedImage, in: NSMakeRect(frame.width - theme.icons.pinnedImage.backingSize.width - item.margin, frame.height - theme.icons.pinnedImage.backingSize.height - item.margin + 1, theme.icons.pinnedImage.backingSize.width, theme.icons.pinnedImage.backingSize.height)) + if item.context.sharedContext.layout == .minimisize { + return } - if let displayLayout = item.ctxDisplayLayout { - - var addition:CGFloat = 0 - if item.isSecret { - ctx.draw(item.isSelected ? theme.icons.secretImageSelected : theme.icons.secretImage, in: NSMakeRect(item.leftInset, item.margin + 3, theme.icons.secretImage.backingSize.width, theme.icons.secretImage.backingSize.height)) - addition += theme.icons.secretImage.backingSize.height - - } - displayLayout.1.draw(NSMakeRect(item.leftInset + addition, item.margin - 1, displayLayout.0.size.width, displayLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - - - var mutedInset:CGFloat = item.isSecret ? theme.icons.secretImage.backingSize.width + 2 : 0 + if layer == containerView.layer { - if item.isVerified { - ctx.draw(highlighted ? theme.icons.verifiedImageSelected : theme.icons.verifiedImage, in: NSMakeRect(displayLayout.0.size.width + item.leftInset + addition + 2, item.margin + 1, theme.icons.verifiedImage.backingSize.width, theme.icons.verifiedImage.backingSize.height)) - mutedInset += theme.icons.verifiedImage.backingSize.width + 3 - } - - if let messageLayout = item.ctxMessageLayout, !hiddemMessage { - messageLayout.1.draw(NSMakeRect(item.leftInset, displayLayout.0.size.height + item.margin + 1 , messageLayout.0.size.width, messageLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } + let highlighted = item.isSelected && item.context.sharedContext.layout != .single - if item.isMuted { - ctx.draw(highlighted ? theme.icons.dialogMuteImageSelected : theme.icons.dialogMuteImage, in: NSMakeRect(item.leftInset + displayLayout.0.size.width + 4 + mutedInset, item.margin + round((displayLayout.0.size.height - theme.icons.dialogMuteImage.backingSize.height) / 2.0) - 1, theme.icons.dialogMuteImage.backingSize.width, theme.icons.dialogMuteImage.backingSize.height)) - } - if let _ = item.mentionsCount { - ctx.draw(highlighted ? theme.icons.chatListMentionActive : theme.icons.chatListMention, in: NSMakeRect(frame.width - (item.ctxBadgeNode != nil ? item.ctxBadgeNode!.size.width + item.margin : 0) - theme.icons.chatListMentionActive.backingSize.width - item.margin, frame.height - theme.icons.chatListMention.backingSize.height - item.margin + 1, theme.icons.chatListMention.backingSize.width, theme.icons.chatListMention.backingSize.height)) + if item.ctxBadgeNode == nil && (item.isPinned || item.isLastPinned) { + ctx.draw(highlighted ? theme.icons.pinnedImageSelected : theme.icons.pinnedImage, in: NSMakeRect(frame.width - theme.icons.pinnedImage.backingSize.width - item.margin, frame.height - theme.icons.pinnedImage.backingSize.height - (item.margin + 1), theme.icons.pinnedImage.backingSize.width, theme.icons.pinnedImage.backingSize.height)) } - if let dateLayout = item.ctxDateLayout, !item.hasDraft { - let dateX = frame.width - dateLayout.0.size.width - item.margin - dateLayout.1.draw(NSMakeRect(dateX, item.margin, dateLayout.0.size.width, dateLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + if let displayLayout = item.ctxDisplayLayout { + + var addition:CGFloat = 0 + if item.isSecret { + ctx.draw(highlighted ? theme.icons.secretImageSelected : theme.icons.secretImage, in: NSMakeRect(item.leftInset, item.margin + 3, theme.icons.secretImage.backingSize.width, theme.icons.secretImage.backingSize.height)) + addition += theme.icons.secretImage.backingSize.height + + } + displayLayout.1.draw(NSMakeRect(item.leftInset + addition, item.margin - 1, displayLayout.0.size.width, displayLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + + var mutedInset:CGFloat = item.isSecret ? theme.icons.secretImage.backingSize.width + 2 : 0 + + if item.isVerified { + ctx.draw(highlighted ? theme.icons.verifyDialogActive : theme.icons.verifyDialog, in: NSMakeRect(displayLayout.0.size.width + item.leftInset + addition - 2, item.margin - 3, 24, 24)) + mutedInset += 15 + 3 + } + + if item.isScam { + ctx.draw(highlighted ? theme.icons.scamActive : theme.icons.scam, in: NSMakeRect(displayLayout.0.size.width + item.leftInset + addition + 2, item.margin + 1, theme.icons.scam.backingSize.width, theme.icons.scam.backingSize.height)) + mutedInset += theme.icons.scam.backingSize.width + 3 + } + var messageOffset: CGFloat = 0 + if let chatNameLayout = item.ctxChatNameLayout, !hiddemMessage { + chatNameLayout.1.draw(NSMakeRect(item.leftInset, displayLayout.0.size.height + item.margin + 2, chatNameLayout.0.size.width, chatNameLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + messageOffset += chatNameLayout.0.size.height + 2 + } + if let messageLayout = item.ctxMessageLayout, !hiddemMessage { + messageLayout.1.draw(NSMakeRect(item.leftInset, displayLayout.0.size.height + item.margin + 1 + messageOffset, messageLayout.0.size.width, messageLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + if item.isMuted { + ctx.draw(highlighted ? theme.icons.dialogMuteImageSelected : theme.icons.dialogMuteImage, in: NSMakeRect(item.leftInset + displayLayout.0.size.width + 4 + mutedInset, item.margin + round((displayLayout.0.size.height - theme.icons.dialogMuteImage.backingSize.height) / 2.0) - 1, theme.icons.dialogMuteImage.backingSize.width, theme.icons.dialogMuteImage.backingSize.height)) + } + + if let _ = item.mentionsCount { + let icon: CGImage + if item.associatedGroupId == .root { + icon = highlighted ? theme.icons.chatListMentionActive : theme.icons.chatListMention + } else { + icon = highlighted ? theme.icons.chatListMentionArchivedActive : theme.icons.chatListMentionArchived + } + ctx.draw(icon, in: NSMakeRect(frame.width - (item.ctxBadgeNode != nil ? item.ctxBadgeNode!.size.width + item.margin : 0) - icon.backingSize.width - item.margin, frame.height - icon.backingSize.height - (item.margin + 1), icon.backingSize.width, icon.backingSize.height)) + } - if item.isOutMessage { + if let dateLayout = item.ctxDateLayout, !item.hasDraft { + let dateX = frame.width - dateLayout.0.size.width - item.margin + dateLayout.1.draw(NSMakeRect(dateX, item.margin, dateLayout.0.size.width, dateLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + if !item.isFailed { if item.isSending { let outX = dateX - theme.icons.sendingImage.backingSize.width - 4 ctx.draw(highlighted ? theme.icons.sendingImageSelected : theme.icons.sendingImage, in: NSMakeRect(outX,item.margin + 2, theme.icons.sendingImage.backingSize.width, theme.icons.sendingImage.backingSize.height)) } else { - let outX = dateX - theme.icons.outgoingMessageImage.backingSize.width - (item.isRead ? 4.0 : 0.0) - 2 - ctx.draw(highlighted ? theme.icons.outgoingMessageImageSelected : theme.icons.outgoingMessageImage, in: NSMakeRect(outX, item.margin + 2, theme.icons.outgoingMessageImage.backingSize.width, theme.icons.outgoingMessageImage.backingSize.height)) - if item.isRead { - ctx.draw(highlighted ? theme.icons.readMessageImageSelected : theme.icons.readMessageImage, in: NSMakeRect(outX + 4, item.margin + 2, theme.icons.readMessageImage.backingSize.width, theme.icons.readMessageImage.backingSize.height)) + if item.isOutMessage { + let outX = dateX - theme.icons.outgoingMessageImage.backingSize.width - (item.isRead ? 4.0 : 0.0) - 2 + ctx.draw(highlighted ? theme.icons.outgoingMessageImageSelected : theme.icons.outgoingMessageImage, in: NSMakeRect(outX, item.margin + 2, theme.icons.outgoingMessageImage.backingSize.width, theme.icons.outgoingMessageImage.backingSize.height)) + if item.isRead { + ctx.draw(highlighted ? theme.icons.readMessageImageSelected : theme.icons.readMessageImage, in: NSMakeRect(outX + 4, item.margin + 2, theme.icons.readMessageImage.backingSize.width, theme.icons.readMessageImage.backingSize.height)) + } } - } } else { let outX = dateX - theme.icons.errorImageSelected.backingSize.width - 4 ctx.draw(highlighted ? theme.icons.errorImageSelected : theme.icons.errorImage, in: NSMakeRect(outX,item.margin, theme.icons.errorImage.backingSize.width, theme.icons.errorImage.backingSize.height)) - } - + } - } } - } } @@ -208,14 +509,21 @@ class ChatListRowView: TableRowView { required init(frame frameRect: NSRect) { - titleText = TextNode(); - messageText = TextNode(); + super.init(frame: frameRect) + + + addSubview(revealRightView) + addSubview(revealLeftView) + self.layerContentsRedrawPolicy = .onSetNeedsDisplay photo.userInteractionEnabled = false - photo.frame = NSMakeRect(10, 8, 50, 50) - addSubview(photo) - self.registerForDraggedTypes([.tiff, .string, .kUrl, .kFilenames]) - + photo.frame = NSMakeRect(10, 10, 50, 50) + containerView.addSubview(photo) + addSubview(containerView) + + containerView.displayDelegate = self + containerView.frame = bounds + } required init?(coder: NSCoder) { @@ -223,62 +531,213 @@ class ChatListRowView: TableRowView { } - override public func performDragOperation(_ sender: NSDraggingInfo) -> Bool { - if activeDragging { - activeDragging = false - needsDisplay = true - let list = sender.draggingPasteboard().propertyList(forType: .kFilenames) as? [String] - if let item = item as? ChatListRowItem, let context = item.account.applicationContext as? TelegramApplicationContext, let list = list { - let list = list.filter { path -> Bool in - if let size = fileSize(path) { - return size <= 1500000000 - } - - return false - } - if !list.isEmpty { - context.mainNavigation?.push(ChatController(account: item.account, peerId: item.peerId, initialAction: .files(list: list, behavior: .automatic))) - } - } - return true - } - return false + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) } override public func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { - if let item = item as? ChatListRowItem, let peer = item.peer, peer.canSendMessage { - activeDragging = true - needsDisplay = true - } + needsDisplay = true + updateColors() return .generic - + } override public func draggingExited(_ sender: NSDraggingInfo?) { - activeDragging = false needsDisplay = true + updateColors() } - public override func draggingEnded(_ sender: NSDraggingInfo?) { - activeDragging = false + public override func draggingEnded(_ sender: NSDraggingInfo) { needsDisplay = true + updateColors() } + override func updateColors() { + super.updateColors() + let inputActivities = self.inputActivities + self.inputActivities = inputActivities + self.containerView.background = backdorColor + expandView?.backgroundColor = theme.colors.grayBackground + } override func set(item:TableRowItem, animated:Bool = false) { + let oldItem = self.item as? ChatListRowItem + + if let item = item as? ChatListRowItem { + if item.isCollapsed { + if expandView == nil { + expandView = ChatListExpandView(frame: NSMakeRect(0, frame.height, frame.width, item.height)) + self.addSubview(expandView!, positioned: .below, relativeTo: containerView) + } + expandView?.updateLocalizationAndTheme(theme: theme) + } + } + + let wasHidden: Bool = (self.item as? ChatListRowItem)?.isCollapsed ?? false super.set(item:item, animated:animated) + - if let item = self.item as? ChatListRowItem { + if let item = item as? ChatListRowItem { - if let peer = item.peer { - photo.setPeer(account: item.account, peer: peer) + self.currentMediaPreviewSpecs = item.contentImageSpecs + + var validMediaIds: [MediaId] = [] + for (message, media, mediaSize) in item.contentImageSpecs { + guard let mediaId = media.id, item.context.sharedContext.layout != .minimisize else { + continue + } + validMediaIds.append(mediaId) + let previewView: ChatListMediaPreviewView + if let current = self.mediaPreviewViews[mediaId] { + previewView = current + } else { + previewView = ChatListMediaPreviewView(context: item.context, message: message, media: media) + self.mediaPreviewViews[mediaId] = previewView + self.containerView.addSubview(previewView) + } + previewView.updateLayout(size: mediaSize) + } + var removeMediaIds: [MediaId] = [] + for (mediaId, itemView) in self.mediaPreviewViews { + if !validMediaIds.contains(mediaId) { + removeMediaIds.append(mediaId) + itemView.removeFromSuperview() + } + } + for mediaId in removeMediaIds { + self.mediaPreviewViews.removeValue(forKey: mediaId) + } + + +// var updateImageSignal: Signal? +// if let contentImageMedia = item.contentImageMedia { +// if let oldContentImageMedia = oldItem?.contentImageMedia, contentImageMedia.isSemanticallyEqual(to: oldContentImageMedia) { +// } else { +// if let message = item.message { +// if let image = contentImageMedia as? TelegramMediaImage { +// updateImageSignal = mediaGridMessagePhoto(account: item.context.account, imageReference: .message(message: MessageReference(message), media: image), scale: backingScaleFactor) +// } else if let file = contentImageMedia as? TelegramMediaFile { +// updateImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), scale: backingScaleFactor) +// } +// } +// } +// } +// +// if let dimensions = item.contentDimensions { +// let previewView: TransformImageView +// if let current = self.previewView { +// previewView = current +// } else { +// previewView = TransformImageView() +// previewView.setFrameSize(NSMakeSize(18, 18)) +// self.previewView = previewView +// self.containerView.addSubview(previewView) +// } +// if let updateImageSignal = updateImageSignal { +// previewView.setSignal(updateImageSignal) +// } +// +// let contentImageSize = CGSize(width: 18.0, height: 18.0) +// +// let arguments = TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(contentImageSize), boundingSize: contentImageSize, intrinsicInsets: NSEdgeInsets()) +// +// previewView.set(arguments: arguments) +// +// } else { +// previewView?.removeFromSuperview() +// previewView = nil +// } +// + + if item.isCollapsed != wasHidden { + expandView?.change(pos: NSMakePoint(0, item.isCollapsed ? 0 : item.height), animated: animated) + containerView.change(pos: NSMakePoint(0, item.isCollapsed ? -70 : 0), animated: !revealActionInvoked && animated) + } + + if let isOnline = item.isOnline, item.context.sharedContext.layout != .minimisize { + if isOnline { + var animate: Bool = false + if activeImage == nil { + activeImage = ImageView() + self.containerView.addSubview(activeImage!) + animate = true + } + guard let activeImage = self.activeImage else { return } + activeImage.image = item.isSelected && item.context.sharedContext.layout != .single ? theme.icons.hintPeerActiveSelected : theme.icons.hintPeerActive + activeImage.sizeToFit() + + activeImage.setFrameOrigin(photo.frame.maxX - activeImage.frame.width - 3, photo.frame.maxY - 12) + + if animated && animate { + activeImage.layer?.animateAlpha(from: 0.5, to: 1.0, duration: 0.2) + activeImage.layer?.animateScaleSpring(from: 0.1, to: 1.0, duration: 0.3) + } + } else { + if animated { + let activeImage = self.activeImage + self.activeImage = nil + activeImage?.layer?.animateAlpha(from: 1, to: 0.5, duration: 0.2) + activeImage?.layer?.animateScaleSpring(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak activeImage] completed in + activeImage?.removeFromSuperview() + }) + } else { + activeImage?.removeFromSuperview() + activeImage = nil + } + } + } else { + activeImage?.removeFromSuperview() + activeImage = nil + } + + + containerView.item = item + if self.animatedView != nil && self.animatedView?.stableId != item.stableId { + self.animatedView?.removeFromSuperview() + self.animatedView = nil + } + + + photo.setState(account: item.context.account, state: item.photo) + + if item.isSavedMessage { + self.archivedPhoto?.removeFromSuperview() + self.archivedPhoto = nil + let icon = theme.icons.searchSaved + photo.setState(account: item.context.account, state: .Empty) + photo.setSignal(generateEmptyPhoto(photo.frame.size, type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(photo.frame.size.width - 20, photo.frame.size.height - 20)), cornerRadius: nil)) |> map {($0, false)}) + } else if case .ArchivedChats = item.photo { + if self.archivedPhoto == nil { + self.archivedPhoto = LAnimationButton(animation: "archiveAvatar", size: NSMakeSize(46, 46), offset: NSMakeSize(0, 0)) + containerView.addSubview(self.archivedPhoto!, positioned: .above, relativeTo: self.photo) + } + self.archivedPhoto?.frame = self.photo.frame + self.archivedPhoto?.userInteractionEnabled = false + self.archivedPhoto?.set(keysToColor: ["box2.box2.Fill 1"], color: item.archiveStatus?.isHidden == false ? theme.colors.revealAction_accent_background : theme.colors.grayForeground) + self.archivedPhoto?.background = item.archiveStatus?.isHidden == false ? theme.colors.revealAction_accent_background : theme.colors.grayForeground + self.archivedPhoto?.layer?.cornerRadius = photo.frame.height / 2 + + let animateArchive = item.animateArchive && animated + if animateArchive { + archivedPhoto?.loop() + if item.isCollapsed { + self.expandView?.animateOnce() + } + } + + // let icon = theme.icons.archivedChats + photo.setState(account: item.context.account, state: .Empty) + // photo.setSignal(generateEmptyPhoto(photo.frame.size, type: .icon(colors: (theme.colors.grayForeground, theme.colors.grayForeground), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(photo.frame.size.width - 17, photo.frame.size.height - 17)), cornerRadius: nil)) |> map {($0, false)}) + } else { + self.archivedPhoto?.removeFromSuperview() + self.archivedPhoto = nil } if let badgeNode = item.ctxBadgeNode { if badgeView == nil { badgeView = View() - addSubview(badgeView!) + containerView.addSubview(badgeView!) } badgeView?.setFrameSize(badgeNode.size) badgeNode.view = badgeView @@ -288,53 +747,624 @@ class ChatListRowView: TableRowView { badgeView = nil } - if !(item is ChatListMessageRowItem) { - let postbox = item.account.postbox - let peerId = item.peerId - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) - self.peerInputActivitiesDisposable.set((item.account.peerInputActivities(peerId: peerId) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in - var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] - previousPeerCache.with { dict -> Void in - for (peerId, activity) in activities { - if let peer = dict[peerId] { - cachedResult.append((peer, activity)) - } else { - foundAllPeers = false - break - } - } + if let badgeNode = item.ctxAdditionalBadgeNode { + if additionalBadgeView == nil { + additionalBadgeView = View() + containerView.addSubview(additionalBadgeView!) + } + additionalBadgeView?.setFrameSize(badgeNode.size) + badgeNode.view = additionalBadgeView + badgeNode.setNeedDisplay() + } else { + additionalBadgeView?.removeFromSuperview() + additionalBadgeView = nil + } + + if let peerId = item.peerId { + let activities = item.activities.map { + ($0.peer.peer, $0.activity) + } + self.inputActivities = (peerId, activities) + } else { + self.inputActivities = nil + } + } + + if let _ = endRevealState { + initRevealState() + } + containerView.needsDisplay = true + + containerView.customHandler.layout = { [weak self] _ in + guard let `self` = self else { return } + + if let item = self.item as? ChatListRowItem, let displayLayout = item.ctxDisplayLayout { + self.activitiesModel?.view?.setFrameOrigin(item.leftInset, displayLayout.0.size.height + item.margin + 3) + + var additionalOffset: CGFloat = 0 + + if let badgeNode = item.ctxAdditionalBadgeNode { + self.additionalBadgeView?.setFrameOrigin(self.containerView.frame.width - badgeNode.size.width - item.margin, self.containerView.frame.height - badgeNode.size.height - (item.margin + 1)) + additionalOffset += (badgeNode.size.width + item.margin) + } + + if let badgeNode = item.ctxBadgeNode { + self.badgeView?.setFrameOrigin(self.containerView.frame.width - badgeNode.size.width - item.margin - additionalOffset, self.containerView.frame.height - badgeNode.size.height - (item.margin + 1)) + } + if let activeImage = self.activeImage { + activeImage.setFrameOrigin(self.photo.frame.maxX - activeImage.frame.width - 3, self.photo.frame.maxY - 12) + } + } + } + + containerView.needsLayout = true + revealActionInvoked = false + needsDisplay = true + needsLayout = true + } + + func initRevealState() { + guard let item = item as? ChatListRowItem, endRevealState == nil else {return} + + revealLeftView.removeAllSubviews() + revealRightView.removeAllSubviews() + + revealLeftView.backgroundColor = backdorColor + revealRightView.backgroundColor = backdorColor + + if item.groupId == .root { + + let unreadBackground = !item.markAsUnread ? theme.colors.revealAction_inactive_background : theme.colors.revealAction_accent_background + let unreadForeground = !item.markAsUnread ? theme.colors.revealAction_inactive_foreground : theme.colors.revealAction_accent_foreground + + let unread: LAnimationButton = LAnimationButton(animation: !item.markAsUnread ? "anim_read" : "anim_unread", size: NSMakeSize(frame.height, frame.height), keysToColor: !item.markAsUnread ? nil : ["Oval.Oval.Stroke 1"], color: unreadBackground, offset: NSMakeSize(0, 0), autoplaySide: .right) + let unreadTitle = TextViewLabel() + unreadTitle.attributedString = .initialize(string: !item.markAsUnread ? L10n.chatListSwipingRead : L10n.chatListSwipingUnread, color: unreadForeground, font: .medium(12)) + unreadTitle.sizeToFit() + unread.addSubview(unreadTitle) + unread.set(background: unreadBackground, for: .Normal) + unread.customHandler.layout = { [weak unreadTitle] view in + if let unreadTitle = unreadTitle { + unreadTitle.centerX(y: view.frame.height - unreadTitle.frame.height - 10) + } + } + + let mute: LAnimationButton = LAnimationButton(animation: item.isMuted ? "anim_unmute" : "anim_mute", size: NSMakeSize(frame.height, frame.height), keysToColor: item.isMuted ? nil : ["un Outlines.Group 1.Stroke 1"], color: theme.colors.revealAction_neutral2_background, offset: NSMakeSize(0, 0), autoplaySide: .right) + let muteTitle = TextViewLabel() + muteTitle.attributedString = .initialize(string: item.isMuted ? L10n.chatListSwipingUnmute : L10n.chatListSwipingMute, color: theme.colors.revealAction_neutral2_foreground, font: .medium(12)) + muteTitle.sizeToFit() + mute.addSubview(muteTitle) + mute.set(background: theme.colors.revealAction_neutral2_background, for: .Normal) + mute.customHandler.layout = { [weak muteTitle] view in + if let muteTitle = muteTitle { + muteTitle.centerX(y: view.frame.height - muteTitle.frame.height - 10) + } + } + + + let pin: LAnimationButton = LAnimationButton(animation: !item.isPinned ? "anim_pin" : "anim_unpin", size: NSMakeSize(frame.height, frame.height), keysToColor: !item.isPinned ? nil : ["un Outlines.Group 1.Stroke 1"], color: theme.colors.revealAction_constructive_background, offset: NSMakeSize(0, 0), autoplaySide: .left) + let pinTitle = TextViewLabel() + pinTitle.attributedString = .initialize(string: !item.isPinned ? L10n.chatListSwipingPin : L10n.chatListSwipingUnpin, color: theme.colors.revealAction_constructive_foreground, font: .medium(12)) + pinTitle.sizeToFit() + pin.addSubview(pinTitle) + pin.set(background: theme.colors.revealAction_constructive_background, for: .Normal) + pin.customHandler.layout = { [weak pinTitle] view in + if let pinTitle = pinTitle { + pinTitle.centerX(y: view.frame.height - pinTitle.frame.height - 10) + } + } + + pin.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + item.togglePinned() + self?.endRevealState = nil + }, for: .Click) + unread.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + item.toggleUnread() + self?.endRevealState = nil + }, for: .Click) + + + + + + + let archive: LAnimationButton = LAnimationButton(animation: item.associatedGroupId != .root ? "anim_unarchive" : "anim_archive", size: item.associatedGroupId != .root ? NSMakeSize(45, 45) : NSMakeSize(frame.height, frame.height), keysToColor: ["box2.box2.Fill 1"], color: theme.colors.revealAction_inactive_background, offset: NSMakeSize(0, item.associatedGroupId != .root ? 9.0 : 0.0), autoplaySide: .left) + let archiveTitle = TextViewLabel() + archiveTitle.attributedString = .initialize(string: item.associatedGroupId != .root ? L10n.chatListSwipingUnarchive : L10n.chatListSwipingArchive, color: theme.colors.revealAction_inactive_foreground, font: .medium(12)) + archiveTitle.sizeToFit() + archive.addSubview(archiveTitle) + archive.set(background: theme.colors.revealAction_inactive_background, for: .Normal) + archive.customHandler.layout = { [weak archiveTitle] view in + if let archiveTitle = archiveTitle { + archiveTitle.centerX(y: view.frame.height - archiveTitle.frame.height - 10) + } + } + + + + + let delete: LAnimationButton = LAnimationButton(animation: "anim_delete", size: NSMakeSize(frame.height, frame.height), keysToColor: nil, offset: NSMakeSize(0, 0), autoplaySide: .left) + let deleteTitle = TextViewLabel() + deleteTitle.attributedString = .initialize(string: L10n.chatListSwipingDelete, color: theme.colors.revealAction_destructive_foreground, font: .medium(12)) + deleteTitle.sizeToFit() + delete.addSubview(deleteTitle) + delete.set(background: theme.colors.revealAction_destructive_background, for: .Normal) + delete.customHandler.layout = { [weak deleteTitle] view in + if let deleteTitle = deleteTitle { + deleteTitle.centerX(y: view.frame.height - deleteTitle.frame.height - 10) + } + } + + + archive.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + self?.endRevealState = nil + item.toggleArchive() + }, for: .Click) + + mute.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + self?.endRevealState = nil + item.toggleMuted() + }, for: .Click) + + delete.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + self?.endRevealState = nil + item.delete() + }, for: .Click) + + + revealRightView.addSubview(pin) + + revealRightView.addSubview(delete) + + if item.filter == nil { + revealRightView.addSubview(archive) + } + + + + revealLeftView.addSubview(mute) + revealLeftView.addSubview(unread) + + + + revealLeftView.backgroundColor = unreadBackground + revealRightView.backgroundColor = item.filter == nil ? theme.colors.revealAction_inactive_background : theme.colors.revealAction_destructive_background + + + unread.setFrameSize(frame.height, frame.height) + mute.setFrameSize(frame.height, frame.height) + + + archive.setFrameSize(frame.height, frame.height) + pin.setFrameSize(frame.height, frame.height) + delete.setFrameSize(frame.height, frame.height) + + delete.setFrameOrigin(archive.frame.maxX, 0) + archive.setFrameOrigin(delete.frame.maxX, 0) + + + mute.setFrameOrigin(unread.frame.maxX, 0) + + + revealRightView.setFrameSize(rightRevealWidth, frame.height) + revealLeftView.setFrameSize(leftRevealWidth, frame.height) + } else { + + + let collapse: LAnimationButton = LAnimationButton(animation: "anim_hide", size: NSMakeSize(frame.height, frame.height), keysToColor: ["Path 2.Path 2.Fill 1"], color: theme.colors.revealAction_inactive_background, offset: NSMakeSize(0, 0), autoplaySide: .left) + let collapseTitle = TextViewLabel() + collapseTitle.attributedString = .initialize(string: L10n.chatListRevealActionCollapse, color: theme.colors.revealAction_inactive_foreground, font: .medium(12)) + collapseTitle.sizeToFit() + collapse.addSubview(collapseTitle) + collapse.set(background: theme.colors.revealAction_inactive_background, for: .Normal) + collapse.customHandler.layout = { [weak collapseTitle] view in + if let collapseTitle = collapseTitle { + collapseTitle.centerX(y: view.frame.height - collapseTitle.frame.height - 10) + } + } + + collapse.setFrameSize(frame.height, frame.height) + revealRightView.addSubview(collapse) + revealRightView.backgroundColor = theme.colors.revealAction_inactive_background + revealRightView.setFrameSize(rightRevealWidth, frame.height) + + collapse.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + item.collapseOrExpandArchive() + self?.endRevealState = nil + }, for: .Click) + + + + if let archiveStatus = item.archiveStatus { + + + let hideOrPin: LAnimationButton + let hideOrPinTitle = TextViewLabel() + + switch archiveStatus { + case .hidden: + hideOrPin = LAnimationButton(animation: "anim_hide", size: NSMakeSize(frame.height, frame.height), keysToColor: ["Path 2.Path 2.Fill 1"], color: theme.colors.revealAction_accent_background, offset: NSMakeSize(0, 0), autoplaySide: .left, rotated: true) + hideOrPinTitle.attributedString = .initialize(string: L10n.chatListRevealActionPin, color: theme.colors.revealAction_accent_foreground, font: .medium(12)) + hideOrPin.set(background: theme.colors.revealAction_accent_background, for: .Normal) + default: + hideOrPin = LAnimationButton(animation: "anim_hide", size: NSMakeSize(frame.height, frame.height), keysToColor: ["Path 2.Path 2.Fill 1"], color: theme.colors.revealAction_inactive_background, offset: NSMakeSize(0, 0), autoplaySide: .left, rotated: false) + hideOrPinTitle.attributedString = .initialize(string: L10n.chatListRevealActionHide, color: theme.colors.revealAction_inactive_foreground, font: .medium(12)) + hideOrPin.set(background: theme.colors.revealAction_inactive_background, for: .Normal) + } + + hideOrPinTitle.sizeToFit() + hideOrPin.addSubview(hideOrPinTitle) + hideOrPin.customHandler.layout = { [weak hideOrPinTitle] view in + if let hideOrPinTitle = hideOrPinTitle { + hideOrPinTitle.centerX(y: view.frame.height - hideOrPinTitle.frame.height - 10) + } + } + + hideOrPin.setFrameSize(frame.height, frame.height) + revealLeftView.addSubview(hideOrPin) + revealLeftView.backgroundColor = item.archiveStatus?.isHidden == true ? theme.colors.revealAction_accent_background : theme.colors.revealAction_inactive_background + revealLeftView.setFrameSize(leftRevealWidth, frame.height) + + hideOrPin.set(handler: { [weak self] _ in + guard let item = self?.item as? ChatListRowItem else {return} + item.toggleHideArchive() + self?.endRevealState = nil + }, for: .Click) + + } + + + } + + + } + + var additionalRevealDelta: CGFloat { + let additionalDelta: CGFloat + if let state = endRevealState { + switch state { + case .left: + additionalDelta = -leftRevealWidth + case .right: + additionalDelta = rightRevealWidth + case .none: + additionalDelta = 0 + } + } else { + additionalDelta = 0 + } + return additionalDelta + } + + var containerX: CGFloat { + return containerView.frame.minX + } + + var width: CGFloat { + return containerView.frame.width + } + + var rightRevealWidth: CGFloat { + return revealRightView.subviewsSize.width + } + + var leftRevealWidth: CGFloat { + return revealLeftView.subviewsSize.width + } + + private var animateOnceAfterDelta: Bool = true + func moveReveal(delta: CGFloat) { + + + if revealLeftView.subviews.isEmpty && revealRightView.subviews.isEmpty { + initRevealState() + } + + self.internalDelta = delta + + let delta = delta// - additionalRevealDelta + + containerView.change(pos: NSMakePoint(delta, containerView.frame.minY), animated: false) + revealLeftView.change(pos: NSMakePoint(min(-leftRevealWidth + delta, 0), revealLeftView.frame.minY), animated: false) + revealRightView.change(pos: NSMakePoint(frame.width + delta, revealRightView.frame.minY), animated: false) + + + revealLeftView.change(size: NSMakeSize(max(leftRevealWidth, delta), revealLeftView.frame.height), animated: false) + + revealRightView.change(size: NSMakeSize(max(rightRevealWidth, abs(delta)), revealRightView.frame.height), animated: false) + + + + if delta > 0, !revealLeftView.subviews.isEmpty { + let action = revealLeftView.subviews.last! + + let subviews = revealLeftView.subviews + let leftPercent: CGFloat = max(min(delta / leftRevealWidth, 1), 0) + + if delta > frame.width - (frame.width / 3) { + if animateOnceAfterDelta { + animateOnceAfterDelta = false + action.layer?.animatePosition(from: NSMakePoint(-(revealLeftView.frame.width - action.frame.width), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + + for i in 0 ..< subviews.count - 1 { + let action = revealLeftView.subviews[i] + action.layer?.animatePosition(from: NSMakePoint(-(action.frame.width), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + } + + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + } + + for i in 0 ..< subviews.count - 1 { + revealLeftView.subviews[i].setFrameOrigin(NSMakePoint(revealLeftView.frame.width, 0)) + } + + action.setFrameOrigin(NSMakePoint((revealLeftView.frame.width - action.frame.width), action.frame.minY)) + + + } else { + + if !animateOnceAfterDelta { + animateOnceAfterDelta = true + action.layer?.animatePosition(from: NSMakePoint(revealLeftView.frame.width - action.frame.width - (leftRevealWidth - action.frame.width), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + + for i in stride(from: revealLeftView.subviews.count - 1, to: 0, by: -1) { + let action = revealLeftView.subviews[i] + action.layer?.animatePosition(from: NSMakePoint((action.frame.width), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + } + + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + } + if subviews.count == 1 { + action.setFrameOrigin(NSMakePoint(min(revealLeftView.frame.width - action.frame.width, 0), action.frame.minY)) + } else { + action.setFrameOrigin(NSMakePoint(action.frame.width - action.frame.width * leftPercent, action.frame.minY)) + for i in 0 ..< subviews.count - 1 { + let action = subviews[i] + subviews[i].setFrameOrigin(NSMakePoint(revealLeftView.frame.width - action.frame.width, 0)) + } + } + } + } + + var rightPercent: CGFloat = delta / rightRevealWidth + if rightPercent < 0, !revealRightView.subviews.isEmpty { + rightPercent = 1 - min(1, abs(rightPercent)) + let subviews = revealRightView.subviews + + + let action = subviews.last! + + if rightPercent == 0 , delta < 0 { + if delta + action.frame.width * CGFloat(max(1, revealRightView.subviews.count - 1)) - 35 < -frame.midX { + if animateOnceAfterDelta { + animateOnceAfterDelta = false + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .default) + action.layer?.animatePosition(from: NSMakePoint((revealRightView.frame.width - rightRevealWidth), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + + for i in 0 ..< subviews.count - 1 { + subviews[i].layer?.animatePosition(from: NSMakePoint((subviews[i].frame.width * CGFloat(i + 1)), subviews[i].frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) } - if foundAllPeers { - return .single(cachedResult) - } else { - return postbox.modify { modifier -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] - for (peerId, activity) in activities { - if let peer = modifier.getPeer(peerId) { - result.append((peer, activity)) - peerCache[peerId] = peer - } - } - _ = previousPeerCache.swap(peerCache) - return result - } + + } + + for i in 0 ..< subviews.count - 1 { + subviews[i].setFrameOrigin(NSMakePoint(-subviews[i].frame.width, 0)) + } + + action.setFrameOrigin(NSMakePoint(0, action.frame.minY)) + + } else { + if !animateOnceAfterDelta { + animateOnceAfterDelta = true + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .default) + + action.layer?.animatePosition(from: NSMakePoint(-(revealRightView.frame.width - rightRevealWidth), action.frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + + for i in 0 ..< subviews.count - 1 { + subviews[i].layer?.animatePosition(from: NSMakePoint(-(subviews[i].frame.width * CGFloat(i + 1)), subviews[i].frame.minY), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) } + } - |> deliverOnMainQueue).start(next: { [weak self] activities in - self?.inputActivities = (peerId, activities) - })) - - let inputActivities = self.inputActivities - self.inputActivities = inputActivities + action.setFrameOrigin(NSMakePoint((revealRightView.frame.width - action.frame.width), action.frame.minY)) + + for i in 0 ..< subviews.count - 1 { + subviews[i].setFrameOrigin(NSMakePoint(CGFloat(i) * subviews[i].frame.width, 0)) + } + } + } else { + for (i, subview) in subviews.enumerated() { + let i = CGFloat(i) + subview.setFrameOrigin(subview.frame.width * i - subview.frame.width * i * rightPercent, 0) + } +// subviews[0].setFrameOrigin(0, 0) +// subviews[1].setFrameOrigin(subviews[0].frame.width - subviews[1].frame.width * rightPercent, 0) +// subviews[2].setFrameOrigin((subviews[0].frame.width * 2) - (subviews[2].frame.width * 2) * rightPercent, 0) + } + } + } + + func completeReveal(direction: SwipeDirection) { + self.endRevealState = direction + + if revealLeftView.subviews.isEmpty || revealRightView.subviews.isEmpty { + initRevealState() + } + + + let updateRightSubviews:(Bool) -> Void = { [weak self] animated in + guard let `self` = self else {return} + let subviews = self.revealRightView.subviews + var x: CGFloat = 0 + for subview in subviews { + if subview != subviews.last { + subview._change(pos: NSMakePoint(x, 0), animated: animated, timingFunction: .spring) + x += subview.frame.width + } else { + subview._change(pos: NSMakePoint(self.rightRevealWidth - subview.frame.width, 0), animated: animated, timingFunction: .spring) + } } + } + + let updateLeftSubviews:(Bool) -> Void = { [weak self] animated in + guard let `self` = self else {return} + let subviews = self.revealLeftView.subviews + var x: CGFloat = 0 + for subview in subviews.reversed() { + subview._change(pos: NSMakePoint(x, 0), animated: animated, timingFunction: .spring) + x += subview.frame.width + } + } + + let failed:(@escaping(Bool)->Void)->Void = { [weak self] completion in + guard let `self` = self else {return} + self.containerView.change(pos: NSMakePoint(0, self.containerView.frame.minY), animated: true, timingFunction: .spring) + self.revealLeftView.change(pos: NSMakePoint(-self.revealLeftView.frame.width, self.revealLeftView.frame.minY), animated: true, timingFunction: .spring) + self.revealRightView.change(pos: NSMakePoint(self.frame.width, self.revealRightView.frame.minY), animated: true, timingFunction: .spring, completion: completion) + updateRightSubviews(true) + updateLeftSubviews(true) + self.endRevealState = nil + } + + let animateRightLongReveal:(@escaping(Bool)->Void)->Void = { [weak self] completion in + guard let `self` = self else {return} + updateRightSubviews(true) + self.endRevealState = nil + let duration: Double = 0.2 + + self.containerView.change(pos: NSMakePoint(-self.containerView.frame.width, self.containerView.frame.minY), animated: true, duration: duration, timingFunction: .spring) + self.revealRightView.change(size: NSMakeSize(self.frame.width + self.rightRevealWidth, self.revealRightView.frame.height), animated: true, duration: duration, timingFunction: .spring) + self.revealRightView.change(pos: NSMakePoint(-self.rightRevealWidth, self.revealRightView.frame.minY), animated: true, duration: duration, timingFunction: .spring, completion: completion) - } + } - needsDisplay = true + + + + switch direction { + case let .left(state): + + if revealLeftView.subviews.isEmpty { + failed( { [weak self] _ in + self?.revealRightView.removeAllSubviews() + self?.revealLeftView.removeAllSubviews() + } ) + return + } + + switch state { + case .success: + + let invokeLeftAction = containerX > frame.width - (frame.width / 3) + + let duration: Double = 0.2 + + containerView.change(pos: NSMakePoint(leftRevealWidth, containerView.frame.minY), animated: true, duration: duration, timingFunction: .spring) + revealLeftView.change(size: NSMakeSize(leftRevealWidth, revealLeftView.frame.height), animated: true, duration: duration, timingFunction: .spring) + + revealRightView.change(pos: NSMakePoint(frame.width, revealRightView.frame.minY), animated: true) + updateLeftSubviews(true) + + var last = self.revealLeftView.subviews.last as? Control + + revealLeftView.change(pos: NSMakePoint(0, revealLeftView.frame.minY), animated: true, duration: duration, timingFunction: .spring, completion: { [weak self] completed in + if completed, invokeLeftAction { + last?.send(event: .Click) + last = nil + self?.needsLayout = true + } + }) + case .failed: + failed( { [weak self] _ in + self?.revealRightView.removeAllSubviews() + self?.revealLeftView.removeAllSubviews() + } ) + default: + break + } + case let .right(state): + + if revealRightView.subviews.isEmpty { + failed( { [weak self] _ in + self?.revealRightView.removeAllSubviews() + self?.revealLeftView.removeAllSubviews() + } ) + return + } + + switch state { + case .success: + let invokeRightAction = containerX + revealRightView.subviews.last!.frame.minX < -frame.midX + + var last = self.revealRightView.subviews.last as? Control + + + if invokeRightAction { + if self.revealRightView.subviews.count < 3 { + failed({ completed in + if invokeRightAction { + DispatchQueue.main.async { + last?.send(event: .Click) + last = nil + } + } + }) + } else { + animateRightLongReveal({ completed in + if invokeRightAction { + DispatchQueue.main.async { + last?.send(event: .Click) + last = nil + } + } + }) + } + + } else { + revealRightView.change(pos: NSMakePoint(frame.width - rightRevealWidth, revealRightView.frame.minY), animated: true, timingFunction: .spring) + revealRightView.change(size: NSMakeSize(rightRevealWidth, revealRightView.frame.height), animated: true, timingFunction: .spring) + containerView.change(pos: NSMakePoint(-rightRevealWidth, containerView.frame.minY), animated: true, timingFunction: .spring) + revealLeftView.change(pos: NSMakePoint(-leftRevealWidth, revealLeftView.frame.minY), animated: true, timingFunction: .spring) + + + let handler = (revealRightView.subviews.last as? Control)?.removeLastHandler() + (revealRightView.subviews.last as? Control)?.set(handler: { control in + var _control:Control? = control + animateRightLongReveal({ completed in + if let control = _control { + DispatchQueue.main.async { + handler?(control) + _control = nil + } + + } + }) + }, for: .Click) + + } + updateRightSubviews(true) + case .failed: + failed( { [weak self] _ in + self?.revealRightView.removeAllSubviews() + self?.revealLeftView.removeAllSubviews() + } ) + default: + break + } + default: + self.endRevealState = nil + failed( { [weak self] _ in + self?.revealRightView.removeAllSubviews() + self?.revealLeftView.removeAllSubviews() + } ) + } + // } deinit { @@ -343,15 +1373,55 @@ class ChatListRowView: TableRowView { override func layout() { super.layout() - if let item = self.item as? ChatListRowItem, let displayLayout = item.ctxDisplayLayout { - self.activitiesModel?.view?.setFrameOrigin(item.leftInset, displayLayout.0.size.height + item.margin + 3) + + guard let item = item as? ChatListRowItem else { return } + + expandView?.frame = NSMakeRect(0, item.isCollapsed ? 0 : item.height, frame.width - .borderSize, frame.height) + + if let delta = internalDelta { + moveReveal(delta: delta) + } else { + let additionalDelta: CGFloat + if let state = endRevealState { + switch state { + case .left: + additionalDelta = -leftRevealWidth + case .right: + additionalDelta = rightRevealWidth + case .none: + additionalDelta = 0 + } + } else { + additionalDelta = 0 + } - if let badgeNode = item.ctxBadgeNode { - badgeView?.setFrameOrigin(self.frame.width - badgeNode.size.width - item.margin, self.frame.height - badgeNode.size.height - item.margin + 1) + containerView.frame = NSMakeRect(-additionalDelta, item.isCollapsed ? -70 : 0, frame.width - .borderSize, 70) + revealLeftView.frame = NSMakeRect(-leftRevealWidth - additionalDelta, 0, leftRevealWidth, frame.height) + revealRightView.frame = NSMakeRect(frame.width - additionalDelta, 0, rightRevealWidth, frame.height) + + + if let displayLayout = item.ctxDisplayLayout { + var offset: CGFloat = 0 + if let chatName = item.ctxChatNameLayout { + offset += chatName.0.size.height + 1 + } + + var mediaPreviewOffset = NSMakePoint(item.leftInset, displayLayout.0.size.height + item.margin + 2 + offset) + let contentImageSpacing: CGFloat = 2.0 + + for (_, media, mediaSize) in self.currentMediaPreviewSpecs { + guard let mediaId = media.id else { + continue + } + if let previewView = self.mediaPreviewViews[mediaId] { + previewView.frame = CGRect(origin: mediaPreviewOffset, size: mediaSize) + } + mediaPreviewOffset.x += mediaSize.width + contentImageSpacing + } + } } } - } diff --git a/Telegram-Mac/ChatListTouchBar.swift b/Telegram-Mac/ChatListTouchBar.swift new file mode 100644 index 0000000000..9028b98f2c --- /dev/null +++ b/Telegram-Mac/ChatListTouchBar.swift @@ -0,0 +1,457 @@ +// +// ChatListTouchBar.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +@available(OSX 10.12.2, *) +private extension NSTouchBarItem.Identifier { + static let chatListSearch = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chatListSearch") + static let chatListNewChat = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chatListNewChat") + static let chatListRecent = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chatListRecent") + + static let composeNewGroup = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.composeNewGroup") + static let composeNewChannel = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.composeNewChannel") + static let composeNewSecretChat = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.composeNewSecretChat") + +} + + +@available(OSX 10.12.2, *) +private class TouchBarRecentPeerItemView: NSScrubberItemView { + private let selectView = View() + private var imageView: AvatarControl = AvatarControl.init(font: .avatar(12)) + private let fetchDisposable = MetaDisposable() + + private var badgeNode: BadgeNode? + private var badgeView:View? + + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(selectView) + addSubview(imageView) + imageView.setFrameSize(NSMakeSize(30, 30)) + selectView.setFrameSize(NSMakeSize(30, 30)) + + selectView.layer?.cornerRadius = 15 + selectView.layer?.borderColor = theme.colors.accent.cgColor + selectView.layer?.borderWidth = 1.5 + selectView.layer?.opacity = 0 + + selectView.isEventLess = true + } + + private(set) var peerId: PeerId? + + func update(context: AccountContext, peer: TouchBarPeerItem, selected: Bool) { + + self.peerId = peer.peer.id + + if peer.unreadCount > 0 { + if badgeView == nil { + badgeView = View() + self.addSubview(badgeView!) + } + guard let badgeView = self.badgeView else { + return + } + badgeView.removeAllSubviews() + + if peer.muted { + self.badgeNode = BadgeNode(.initialize(string: "\(peer.unreadCount)", color: theme.chatList.badgeTextColor, font: .medium(8)), theme.colors.grayText) + } else { + self.badgeNode = BadgeNode(.initialize(string: "\(peer.unreadCount)", color: theme.chatList.badgeTextColor, font: .medium(8)), theme.colors.accent) + } + guard let badgeNode = self.badgeNode else { + return + } + + badgeNode.additionSize = NSMakeSize(0, 0) + + + + badgeView.setFrameSize(badgeNode.size) + badgeNode.view = badgeView + badgeNode.setNeedDisplay() + needsLayout = true + } else { + self.badgeView?.removeFromSuperview() + self.badgeView = nil + } + + imageView.setPeer(account: context.account, peer: peer.peer) + } + private var _selected: Bool = false + func updateSelected(_ selected: Bool) { + if self._selected != selected { + self._selected = selected + selectView.change(opacity: selected ? 1 : 0, animated: true, duration: 0.1, timingFunction: .spring) + if selected { + selectView.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: 0.2, removeOnCompletion: true) + imageView.layer?.animateScaleSpring(from: 1, to: 0.75, duration: 0.2, removeOnCompletion: false) + badgeView?.layer?.animateScaleSpring(from: 1, to: 0.75, duration: 0.2, removeOnCompletion: false) + } else { + selectView.layer?.animateScaleSpring(from: 1.0, to: 0.2, duration: 0.2, removeOnCompletion: false) + imageView.layer?.animateScaleSpring(from: 0.75, to: 1.0, duration: 0.2, removeOnCompletion: true) + badgeView?.layer?.animateScaleSpring(from: 0.75, to: 1.0, duration: 0.2, removeOnCompletion: true) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + } + + deinit { + fetchDisposable.dispose() + } + + override func layout() { + super.layout() + selectView.center() + imageView.center() + + guard let badgeView = self.badgeView else { + return + } + badgeView.setFrameOrigin(NSMakePoint(frame.width - badgeView.frame.width, badgeView.frame.height - 11)) + + } +} + + +@available(OSX 10.12.2, *) +private class RecentPeersScrubberBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate { + + private static let peerIdentifier = "peerIdentifier" + + var entries: [TouchBarPeerItem] + private let context: AccountContext + private let selected: ChatLocation? + init(identifier: NSTouchBarItem.Identifier, context: AccountContext, entries: [TouchBarPeerItem], selected: ChatLocation?) { + self.entries = entries + self.context = context + self.selected = selected + super.init(identifier: identifier) + + let scrubber = NSScrubber() + scrubber.register(TouchBarRecentPeerItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: RecentPeersScrubberBarItem.peerIdentifier)) + + scrubber.mode = .free + scrubber.selectionBackgroundStyle = .none + scrubber.floatsSelectionViews = true + scrubber.delegate = self + scrubber.dataSource = self + + + let gesture = NSPressGestureRecognizer(target: self, action: #selector(self.pressGesture(_:))) + gesture.allowedTouchTypes = NSTouch.TouchTypeMask.direct + gesture.allowableMovement = 0 + gesture.minimumPressDuration = 0 + scrubber.addGestureRecognizer(gesture) + + self.view = scrubber + } + + @objc private func pressGesture(_ gesture: NSPressGestureRecognizer) { + + let context = self.context + + let runSelector:(Bool, Bool)->Void = { [weak self] cancelled, navigate in + guard let `self` = self else { + return + } + let scrollView = HackUtils.findElements(byClass: "NSScrollView", in: self.view)?.first as? NSScrollView + + guard let container = scrollView?.documentView?.subviews.first else { + return + } + var point = gesture.location(in: container) + point.y = 0 + for itemView in container.subviews { + if let itemView = itemView as? TouchBarRecentPeerItemView { + if NSPointInRect(point, itemView.frame) { + itemView.updateSelected(!cancelled) + if navigate, let peerId = itemView.peerId { + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId))) + } + } else { + itemView.updateSelected(false) + } + } + } + } + + switch gesture.state { + case .began: + runSelector(false, false) + case .failed, .cancelled: + runSelector(true, false) + case .ended: + runSelector(true, true) + case .changed: + runSelector(false, false) + case .possible: + break + @unknown default: + runSelector(false, false) + } + } + fileprivate var modalPreview: PreviewModalController? + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + + func numberOfItems(for scrubber: NSScrubber) -> Int { + return entries.count + } + + func scrubber(_ scrubber: NSScrubber, didHighlightItemAt highlightedIndex: Int) { + scrubber.selectionBackgroundStyle = .none + } + + func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let itemView: NSScrubberItemView + + let peer = self.entries[index] + + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: RecentPeersScrubberBarItem.peerIdentifier), owner: nil) as! TouchBarRecentPeerItemView + view.update(context: context, peer: peer, selected: self.selected?.peerId == peer.peer.id) + itemView = view + + return itemView + } + + func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize { + return NSSize(width: 40, height: 40) + } + + + func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) { + + } +} + + + +@available(OSX 10.12.2, *) +final class ComposePopoverTouchBar : NSTouchBar, NSTouchBarDelegate { + + private let newGroup:()->Void + private let newSecretChat:()->Void + private let newChannel:()->Void + init(newGroup:@escaping()->Void, newSecretChat:@escaping()->Void, newChannel:@escaping()->Void) { + self.newGroup = newGroup + self.newSecretChat = newSecretChat + self.newChannel = newChannel + super.init() + + delegate = self + defaultItemIdentifiers = [.flexibleSpace, .composeNewGroup, .composeNewSecretChat, .composeNewChannel, .flexibleSpace] + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func newGroupAction() { + newGroup() + } + @objc private func newSecretChatAction() { + newSecretChat() + } + @objc private func newChannelAction() { + newChannel() + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .composeNewGroup: + let item: NSCustomTouchBarItem = NSCustomTouchBarItem(identifier: identifier) + let image = NSImage(named: NSImage.Name("Icon_TouchBar_ComposeGroup"))! + let button = NSButton(title: L10n.composePopoverNewGroup, image: image, target: self, action: #selector(newGroupAction)) + item.view = button + item.customizationLabel = L10n.composePopoverNewGroup + return item + case .composeNewChannel: + let item: NSCustomTouchBarItem = NSCustomTouchBarItem(identifier: identifier) + let image = NSImage(named: NSImage.Name("Icon_TouchBar_ComposeChannel"))! + let button = NSButton(title: L10n.composePopoverNewChannel, image: image, target: self, action: #selector(newChannelAction)) + item.view = button + item.customizationLabel = L10n.composePopoverNewChannel + return item + case .composeNewSecretChat: + let item: NSCustomTouchBarItem = NSCustomTouchBarItem(identifier: identifier) + let image = NSImage(named: NSImage.Name("Icon_TouchBar_ComposeSecretChat"))! + let button = NSButton(title: L10n.composePopoverNewSecretChat, image: image, target: self, action: #selector(newSecretChatAction)) + item.view = button + item.customizationLabel = L10n.composePopoverNewSecretChat + return item + default: + break + } + return nil + } +} + +private struct TouchBarPeerItem : Equatable { + let peer: Peer + let unreadCount: Int32 + let muted: Bool + static func ==(lhs: TouchBarPeerItem, rhs: TouchBarPeerItem) -> Bool { + return lhs.peer.id == rhs.peer.id + } +} + +@available(OSX 10.12.2, *) +class ChatListTouchBar: NSTouchBar, NSTouchBarDelegate { + + private let search:()->Void + private let newGroup:()->Void + private let newSecretChat:()->Void + private let newChannel:()->Void + private let context: AccountContext + private var peers:[TouchBarPeerItem] = [] + private var selected: ChatLocation? + private let disposable = MetaDisposable() + init(context: AccountContext, search:@escaping()->Void, newGroup:@escaping()->Void, newSecretChat:@escaping()->Void, newChannel:@escaping()->Void) { + self.search = search + self.newGroup = newGroup + self.newSecretChat = newSecretChat + self.newChannel = newChannel + self.context = context + super.init() + delegate = self + customizationIdentifier = .windowBar + defaultItemIdentifiers = [.chatListNewChat, .flexibleSpace, .chatListSearch, .flexibleSpace] + customizationAllowedItemIdentifiers = defaultItemIdentifiers + + +// let recent:Signal<[TouchBarPeerItem], NoError> = recentlySearchedPeers(postbox: context.account.postbox) |> map { recent in +// return recent.prefix(10).compactMap { $0.peer.peer != nil ? TouchBarPeerItem(peer: $0.peer.peer!, unreadCount: $0.unreadCount, muted: $0.notificationSettings?.isMuted ?? false) : nil } +// } +// let top:Signal<[TouchBarPeerItem], NoError> = recentPeers(account: context.account) |> mapToSignal { top in +// switch top { +// case .disabled: +// return .single([]) +// case let .peers(peers): +// let peers = Array(peers.prefix(7)) +// return combineLatest(peers.map {context.account.viewTracker.peerView($0.id)}) |> mapToSignal { peerViews -> Signal<[TouchBarPeerItem], NoError> in +// return context.account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in +// var peers:[TouchBarPeerItem] = [] +// for peerView in peerViews { +// if let peer = peerViewMainPeer(peerView) { +// let isMuted = peerView.isMuted +// let unreadCount = values.count(for: .peer(peerView.peerId)) +// peers.append(TouchBarPeerItem(peer: peer, unreadCount: unreadCount ?? 0, muted: isMuted)) +// } +// } +// return peers +// } +// } +// } +// } +// + +// let signal = combineLatest(queue: .mainQueue(), recent, top) +// disposable.set(signal.start(next: { [weak self] recent, top in +// self?.peers = (top + recent).prefix(14).uniqueElements +// self?.updateInterface() +// })) + } + + private func identifiers() -> [NSTouchBarItem.Identifier] { + var items:[NSTouchBarItem.Identifier] = [] + + items.append(.chatListNewChat) + if peers.isEmpty { + items.append(.flexibleSpace) + items.append(.chatListSearch) + items.append(.flexibleSpace) + } else { + items.append(.fixedSpaceSmall) + items.append(.chatListRecent) + items.append(.fixedSpaceSmall) + } + return items + } + + private func updateInterface() { + defaultItemIdentifiers = identifiers() + customizationAllowedItemIdentifiers = defaultItemIdentifiers + + for identifier in itemIdentifiers { + switch identifier { + case .chatListRecent: + let view = (item(forIdentifier: identifier) as? RecentPeersScrubberBarItem) + view?.entries = self.peers + (view?.view as? NSScrubber)?.reloadData() + default: + break + } + } + + + } + + deinit { + disposable.dispose() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .chatListNewChat: + let item = NSPopoverTouchBarItem(identifier: identifier) + let button = NSButton(image: NSImage(named: NSImage.Name("Icon_TouchBar_Compose"))!, target: item, action: #selector(NSPopoverTouchBarItem.showPopover(_:))) + + item.popoverTouchBar = ComposePopoverTouchBar(newGroup: self.newGroup, newSecretChat: self.newSecretChat, newChannel: self.newChannel) + item.collapsedRepresentation = button + item.customizationLabel = L10n.touchBarLabelNewChat + return item + case .chatListSearch: + let item = NSCustomTouchBarItem(identifier: identifier) + let image = NSImage(named: NSImage.Name("Icon_TouchBar_Search"))! + let button = NSButton(title: L10n.touchBarSearchUsersOrMessages, image: image, target: self, action: #selector(searchAction)) + button.imagePosition = .imageLeft + button.imageHugsTitle = true + button.addWidthConstraint(relation: .equal, size: 350) + item.view = button + item.customizationLabel = button.title + return item + case .chatListRecent: + let scrubberItem: NSCustomTouchBarItem = RecentPeersScrubberBarItem(identifier: identifier, context: context, entries: self.peers, selected: self.selected) + return scrubberItem + default: + break + } + return nil + } + + @objc private func composeAction() { + + } + + @objc private func searchAction() { + self.search() + } +} diff --git a/Telegram-Mac/ChatMapContentView.swift b/Telegram-Mac/ChatMapContentView.swift index 60b6519b33..2a8fddcdd6 100644 --- a/Telegram-Mac/ChatMapContentView.swift +++ b/Telegram-Mac/ChatMapContentView.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore class ChatMapContentView: ChatMediaContentView { @@ -45,15 +46,65 @@ class ChatMapContentView: ChatMediaContentView { } } - override func update(with media: Media, size: NSSize, account: Account, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool = false) { - let mediaUpdated = self.media == nil || !self.media!.isEqual(media) + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + let versionUpdated = parent?.stableVersion != self.parent?.stableVersion + var mediaUpdated = self.media == nil || !media.isSemanticallyEqual(to: self.media!) || versionUpdated iconView.image = theme.icons.chatMapPin iconView.sizeToFit() - super.update(with: media, size: size, account: account, parent: parent, table: table, parameters: parameters, animated: animated) + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags) - if mediaUpdated, let parameters = parameters as? ChatMediaMapLayoutParameters { - imageView.setSignal(account: account, signal: chatWebpageSnippetPhoto(account: account, photo: parameters.image, scale: backingScaleFactor, small: parameters.isVenue)) + if let positionFlags = positionFlags { + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius + + + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + topLeftRadius = topLeftRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + topRightRadius = topRightRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.left) { + bottomLeftRadius = bottomLeftRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + bottomRightRadius = bottomRightRadius * 3 + 2 + } + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topLeftRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topRightRadius) + + let maskLayer: CAShapeLayer = CAShapeLayer() + maskLayer.path = path + layer?.mask = maskLayer + } else { + layer?.mask = nil + } + + + + if let parameters = parameters as? ChatMediaMapLayoutParameters { + + imageView.setSignal(signal: cachedMedia(media: media, arguments: parameters.arguments, scale: backingScaleFactor, positionFlags: positionFlags), clearInstantly: false) + mediaUpdated = mediaUpdated && !self.imageView.hasImage + + imageView.setSignal( chatWebpageSnippetPhoto(account: context.account, imageReference: parent != nil ? ImageMediaReference.message(message: MessageReference(parent!), media: parameters.image) : ImageMediaReference.standalone(media: parameters.image), scale: backingScaleFactor, small: parameters.isVenue), clearInstantly: false, animate: mediaUpdated, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: parameters.arguments, scale: System.backingScale, positionFlags: positionFlags) + } + }) if parameters.isVenue { if textView == nil { @@ -65,6 +116,7 @@ class ChatMapContentView: ChatMediaContentView { } } + needsLayout = true } } diff --git a/Telegram-Mac/ChatMapRowItem.swift b/Telegram-Mac/ChatMapRowItem.swift index 08dd90e1b2..af21338b8b 100644 --- a/Telegram-Mac/ChatMapRowItem.swift +++ b/Telegram-Mac/ChatMapRowItem.swift @@ -8,52 +8,87 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore final class ChatMediaMapLayoutParameters : ChatMediaLayoutParameters { let map:TelegramMediaMap - let resource:HttpReferenceMediaResource + let resource:TelegramMediaResource let image:TelegramMediaImage let venueText:TextViewLayout? let isVenue:Bool let defaultImageSize:NSSize let url:String + fileprivate(set) var arguments:TransformImageArguments - init(map:TelegramMediaMap, resource:HttpReferenceMediaResource) { + init(map:TelegramMediaMap, resource:TelegramMediaResource, presentation: ChatMediaPresentation, automaticDownload: Bool) { self.map = map self.isVenue = map.venue != nil self.resource = resource self.defaultImageSize = isVenue ? NSMakeSize(60, 60) : NSMakeSize(320, 120) - self.url = "https://maps.google.com/maps?q=\(map.latitude),\(map.longitude)" - let representation = TelegramMediaImageRepresentation(dimensions: defaultImageSize, resource: resource) - self.image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [representation]) + self.url = "https://maps.google.com/maps?q=\(String(format:"%f", map.latitude)),\(String(format:"%f", map.longitude))" + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(defaultImageSize), resource: resource) + self.image = TelegramMediaImage(imageId: map.id ?? MediaId(namespace: 0, id: arc4random64()), representations: [representation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - self.arguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: defaultImageSize, boundingSize: defaultImageSize, intrinsicInsets: NSEdgeInsets()) + self.arguments = TransformImageArguments(corners: ImageCorners(radius: 8), imageSize: defaultImageSize, boundingSize: defaultImageSize, intrinsicInsets: NSEdgeInsets()) if let venue = map.venue { let attr = NSMutableAttributedString() - _ = attr.append(string: venue.title, color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: venue.title, color: presentation.text, font: .normal(.text)) _ = attr.append(string: "\n") - _ = attr.append(string: venue.address, color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: venue.address, color: presentation.grayText, font: .normal(.text)) venueText = TextViewLayout(attr, maximumNumberOfLines: 4, truncationType: .middle, alignment: .left) } else { venueText = nil } + super.init(presentation: presentation, media: map, automaticDownload: automaticDownload, autoplayMedia: AutoplayMediaPreferences.defaultSettings) } } func ==(lhs:ChatMediaMapLayoutParameters, rhs:ChatMediaMapLayoutParameters) -> Bool { - return lhs.resource.url == rhs.resource.url + return lhs.resource.isEqual(to: rhs.resource) } class ChatMapRowItem: ChatMediaItem { - - override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { - super.init(initialSize, chatInteraction, account, object) + fileprivate var liveText: TextViewLayout? + fileprivate var updatedText: TextViewLayout? + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) let map = media as! TelegramMediaMap - let isVenue = map.venue != nil - let resource = HttpReferenceMediaResource(url: "https://maps.googleapis.com/maps/api/staticmap?center=\(map.latitude),\(map.longitude)&zoom=15&size=\(isVenue ? 60 * Int(2.0) : 320 * Int(2.0))x\(isVenue ? 60 * Int(2.0) : 120 * Int(2.0))&sensor=true", size: 0) - self.parameters = ChatMediaMapLayoutParameters(map: map, resource: resource) + // let isVenue = map.venue != nil + let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: 320 * 2, height: 120 * 2, zoom: 15) + //let resource = HttpReferenceMediaResource(url: "https://maps.googleapis.com/maps/api/staticmap?center=\(map.latitude),\(map.longitude)&zoom=15&size=\(isVenue ? 60 * Int(2.0) : 320 * Int(2.0))x\(isVenue ? 60 * Int(2.0) : 120 * Int(2.0))&sensor=true", size: 0) + self.parameters = ChatMediaMapLayoutParameters(map: map, resource: resource, presentation: .make(for: object.message!, account: context.account, renderType: object.renderType), automaticDownload: downloadSettings.isDownloable(object.message!)) + + if isLiveLocationView { + liveText = TextViewLayout(.initialize(string: L10n.chatLiveLocation, color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .bold(.text)), maximumNumberOfLines: 1, truncationType: .end) + + var editedDate:Int32 = object.message!.timestamp + for attr in object.message!.attributes { + if let attr = attr as? EditedMessageAttribute { + editedDate = attr.date + } + } + + var time:TimeInterval = Date().timeIntervalSince1970 + time -= context.timeDifference + let timeUpdated = Int32(time) - editedDate + + updatedText = TextViewLayout(.initialize(string: timeUpdated < 60 ? L10n.chatLiveLocationUpdatedNow : L10n.chatLiveLocationUpdatedCountable(Int(timeUpdated / 60)), color: theme.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.text)), maximumNumberOfLines: 1) + } + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if let parameters = parameters as? ChatMediaMapLayoutParameters { + if parameters.isVenue { + return rightSize.width > (_contentSize.width - 70) ? rightSize.height : nil + } + } + return nil + } + + override var isFixedRightPosition: Bool { + return true } override var instantlyResize:Bool { @@ -63,6 +98,56 @@ class ChatMapRowItem: ChatMediaItem { return false } + override var isBubbleFullFilled: Bool { + if let media = media as? TelegramMediaMap { + return media.venue == nil && isBubbled + } + return false + } + + var isLiveLocationView: Bool { + if let media = media as? TelegramMediaMap, let message = message { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout { + var time:TimeInterval = Date().timeIntervalSince1970 + time -= context.timeDifference + if Int32(time) < message.timestamp + liveBroadcastingTimeout { + return true + } + } + } + return false + } + + var liveLocationTimeout: Int32 { + if let media = media as? TelegramMediaMap { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout { + return liveBroadcastingTimeout + } + } + return 0 + } + + var liveLocationProgress: TimeInterval { + if let media = media as? TelegramMediaMap { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout, let message = message { + var time:TimeInterval = Date().timeIntervalSince1970 + time -= context.timeDifference + return 100.0 - (Double(time) - Double(message.timestamp)) / Double(liveBroadcastingTimeout) * 100.0 + } + } + return 0 + } + + override func viewClass() -> AnyClass { + return isLiveLocationView ? LiveLocationRowView.self : super.viewClass() + } + + override var isStateOverlayLayout: Bool { + return !isLiveLocationView && hasBubble && isBubbleFullFilled + } + + + override func makeContentSize(_ width: CGFloat) -> NSSize { if let parameters = parameters as? ChatMediaMapLayoutParameters { parameters.venueText?.measure(width: width - 70) @@ -71,9 +156,98 @@ class ChatMapRowItem: ChatMediaItem { size = parameters.defaultImageSize.aspectFitted(NSMakeSize(min(width,parameters.defaultImageSize.width), parameters.defaultImageSize.height)) parameters.arguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) } - return NSMakeSize(width, size.height) + var venueSize: CGFloat = 0 + if let venueText = parameters.venueText { + venueSize = venueText.layoutSize.width + 10 + } + + return NSMakeSize(venueSize + size.width, size.height) } return super.makeContentSize(width) } + override var height: CGFloat { + if isLiveLocationView { + liveText?.measure(width: _contentSize.width - elementsContentInset * 2) + updatedText?.measure(width: _contentSize.width - elementsContentInset * 2) + return super.height + (renderType == .bubble ? 46 : 40) + } + return super.height + } + +} + +private class LiveLocationRowView : ChatMediaView { + private let liveText: TextView = TextView() + private let updatedText: TextView = TextView() + private let progress:TimableProgressView = TimableProgressView(theme: TimableProgressTheme(seconds: 20)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + rowView.addSubview(updatedText) + rowView.addSubview(liveText) + rowView.addSubview(progress) + } + + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? ChatMapRowItem else {return} + + liveText.update(item.liveText) + updatedText.update(item.updatedText) + +// let difference:()->TimeInterval = { +// return TimeInterval((countdownBeginTime + attribute.timeout)) - (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) +// } +// let start = difference() / Double(attribute.timeout) * 100.0 + if item.isLiveLocationView { + progress.theme = TimableProgressTheme(backgroundColor: backdorColor, foregroundColor: theme.chat.textColor(item.isIncoming, item.entry.renderType == .bubble), seconds: Double(item.liveLocationTimeout), start: item.liveLocationProgress, borderWidth: 2) + progress.progress = 0 + progress.isHidden = false + rightView.isHidden = true + } else { + progress.isHidden = true + rightView.isHidden = false + } + + + progress.startAnimation() + super.set(item: item, animated: animated) + + } + + override func updateColors() { + super.updateColors() + liveText.backgroundColor = contentColor + updatedText.backgroundColor = contentColor + } + + private var textFrame: NSRect { + guard let item = item as? ChatMapRowItem, let liveText = item.liveText else {return NSZeroRect} + + return NSMakeRect(contentFrame.minX + item.elementsContentInset, contentFrame.maxY + item.defaultContentInnerInset, liveText.layoutSize.width, liveText.layoutSize.height) + } + private var updateFrame: NSRect { + guard let item = item as? ChatMapRowItem, let updatedText = item.updatedText else {return NSZeroRect} + + return NSMakeRect(contentFrame.minX + item.elementsContentInset, contentFrame.maxY + item.defaultContentInnerInset + liveText.frame.height, updatedText.layoutSize.width, updatedText.layoutSize.height) + } + + private var progressFrame: NSRect { + guard let item = item as? ChatMapRowItem else {return NSZeroRect} + + return NSMakeRect(contentFrame.maxX - progress.frame.width - (item.isBubbled ? item.defaultContentInnerInset : 0) - 3, contentFrame.maxY + item.defaultContentInnerInset + 5, 25, 25) + } + + override func layout() { + super.layout() + + liveText.frame = textFrame + updatedText.frame = updateFrame + progress.frame = progressFrame + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } diff --git a/Telegram-Mac/ChatMediaAnimatedStickerView.swift b/Telegram-Mac/ChatMediaAnimatedStickerView.swift new file mode 100644 index 0000000000..f7ff5b60ee --- /dev/null +++ b/Telegram-Mac/ChatMediaAnimatedStickerView.swift @@ -0,0 +1,259 @@ +// +// ChatMediaAnimatedSticker.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import PostboxMac +import TelegramCoreMac +import TGUIKit +import SwiftSignalKitMac +import Lottie + + +class MediaAnimatedStickerView: ChatMediaContentView { + + private let loadResourceDisposable = MetaDisposable() + private let stateDisposable = MetaDisposable() + private let playThrottleDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private let playerView: LottiePlayerView = LottiePlayerView(frame: NSMakeRect(0, 0, 240, 240)) + private let thumbView = TransformImageView() + private var sticker:LottieAnimation? = nil { + didSet { + if oldValue != sticker { + self.previousAccept = false + } + updatePlayerIfNeeded() + } + } + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.playerView) + addSubview(self.thumbView) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func clean() { + stateDisposable.set(nil) + loadResourceDisposable.set(nil) + playThrottleDisposable.set(nil) + fetchDisposable.set(nil) + } + + deinit { + loadResourceDisposable.dispose() + stateDisposable.dispose() + playThrottleDisposable.dispose() + fetchDisposable.dispose() + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + private var previousAccept: Bool = false + + + @objc func updatePlayerIfNeeded() { + let accept = ((self.window != nil && self.window!.isKeyWindow) || (self.window != nil && !(self.window is Window))) && !NSIsEmptyRect(self.visibleRect) && !self.isDynamicContentLocked && self.sticker != nil + + var signal = Signal.single(Void()) + if accept && !nextForceAccept { + signal = signal |> delay(accept ? 0.25 : 0, queue: .mainQueue()) + } + if accept && self.sticker != nil { + nextForceAccept = false + } + + if let sticker = self.sticker, previousAccept { + switch sticker.playPolicy { + case .once: + return + default: + break + } + } + + if previousAccept != accept { + self.playThrottleDisposable.set(signal.start(next: { [weak self] in + guard let `self` = self else { + return + } + self.playerView.set(accept ? self.sticker : nil) + self.previousAccept = accept + })) + } + previousAccept = accept + + + } + + private var nextForceAccept: Bool = false + + + override func previewMediaIfPossible() -> Bool { + if let table = table, let context = context, let window = window as? Window { + _ = startModalPreviewHandle(table, window: window, context: context) + } + return true + } + + override func executeInteraction(_ isControl: Bool) { + if let window = window as? Window { + if let context = context, let peerId = parent?.id.peerId, let media = media as? TelegramMediaFile, !media.isEmojiAnimatedSticker, let reference = media.stickerReference { + showModal(with:StickersPackPreviewModalController(context, peerId: peerId, reference: reference), for:window) + } else { + self.playerView.playIfNeeded() + } + } + } + + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self.enclosingScrollView?.contentView) + } else { + removeNotificationListeners() + } + } + + override func viewWillDraw() { + super.viewWillDraw() + updatePlayerIfNeeded() + } + + override func willRemove() { + super.willRemove() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToSuperview() { + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToWindow() { + updateListeners() + updatePlayerIfNeeded() + } + + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool, positionFlags: LayoutPositionFlags?, approximateSynchronousValue: Bool) { + + + guard let file = media as? TelegramMediaFile else { return } + + let updated = self.media != nil ? !file.isSemanticallyEqual(to: self.media!) : true + + if parent?.stableId != self.parent?.stableId { + self.sticker = nil + } else if parent == nil, updated { + self.sticker = nil + } + self.nextForceAccept = approximateSynchronousValue || parent?.id.namespace == Namespaces.Message.Local + + + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags, approximateSynchronousValue: approximateSynchronousValue) + + + let reference: MediaResourceReference + + if let message = parent { + reference = FileMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource) + } else if let stickerReference = file.stickerReference { + if file.resource is CloudStickerPackThumbnailMediaResource { + reference = MediaResourceReference.stickerPackThumbnail(stickerPack: stickerReference, resource: file.resource) + } else { + reference = FileMediaReference.stickerPack(stickerPack: stickerReference, media: file).resourceReference(file.resource) + } + } else { + reference = FileMediaReference.standalone(media: file).resourceReference(file.resource) + } + + let data: Signal + if let resource = file.resource as? LocalBundleResource { + data = Signal { subscriber in + if let path = Bundle.main.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true)) + subscriber.putCompletion() + } + return EmptyDisposable + } |> runOn(resourcesQueue) + } else { + data = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: approximateSynchronousValue) + } + + self.loadResourceDisposable.set((data |> map { resourceData -> Data? in + + if resourceData.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + return data + } + return nil + } |> deliverOnMainQueue).start(next: { [weak file, weak self] data in + if let data = data, let file = file { + let playPolicy: LottiePlayPolicy = file.isEmojiAnimatedSticker ? .once : .loop + let maximumFps: Int = size.width < 200 && !file.isEmojiAnimatedSticker ? 30 : 60 + let fitzModifier = file.animatedEmojiFitzModifier + self?.sticker = LottieAnimation(compressed: data, key: LottieAnimationEntryKey(key: .media(file.id), size: size, fitzModifier: fitzModifier), cachePurpose: size.width < 200 ? .temporaryLZ4(.thumb) : self?.parent != nil ? .temporaryLZ4(.chat) : .none, playPolicy: playPolicy, maximumFps: maximumFps) + self?.fetchStatus = .Local + } else { + self?.sticker = nil + self?.fetchStatus = .Remote + } + })) + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + + + self.thumbView.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor), clearInstantly: updated) + if !self.thumbView.isFullyLoaded { + self.thumbView.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, scale: backingScaleFactor, size: size, fetched: false), cacheImage: { [weak file] result in + if let file = file { + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + } + }) + self.thumbView.set(arguments: arguments) + } else { + self.thumbView.dispose() + } + + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference).start()) + stateDisposable.set((self.playerView.state |> deliverOnMainQueue).start(next: { [weak self] state in + guard let `self` = self else { return } + switch state { + case .playing: + self.playerView.isHidden = false + self.thumbView.isHidden = true + default: + self.playerView.isHidden = true + self.thumbView.isHidden = false + } + })) + } + + override var contents: Any? { + return self.thumbView.image + } + + override func layout() { + super.layout() + self.playerView.frame = bounds + self.thumbView.frame = bounds + } + +} diff --git a/Telegram-Mac/ChatMediaContentView.swift b/Telegram-Mac/ChatMediaContentView.swift index f61ad69621..b272421909 100644 --- a/Telegram-Mac/ChatMediaContentView.swift +++ b/Telegram-Mac/ChatMediaContentView.swift @@ -7,13 +7,13 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit - class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvider { private var acceptDragging:Bool = false @@ -21,12 +21,12 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi private var mouseDownPoint: NSPoint = NSZeroPoint var parent:Message? var media:Media? - var account:Account? + var context:AccountContext? var parameters:ChatMediaLayoutParameters? private(set) var fetchControls:FetchControls! - var fetchStatus: MediaResourceStatus? + var fetchStatus: MediaResourceStatus? var dragDisposable:MetaDisposable = MetaDisposable() - + var positionFlags: LayoutPositionFlags? override var backgroundColor: NSColor { get { return super.backgroundColor @@ -34,7 +34,7 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi set { super.backgroundColor = newValue for view in subviews { - if !(view is TransformImageView) { + if !(view is TransformImageView) && !(view is SelectingControl) && !(view is GIFPlayerView) && !(view is ChatMessageAccessoryView) && !(view is MediaPreviewEditControl) && !(view is ProgressIndicator) { view.background = newValue } } @@ -43,11 +43,15 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi weak var table:TableView? - + override func updateTrackingAreas() { + + } + override init() { super.init() fetchControls = FetchControls(fetch: { [weak self] in self?.executeInteraction(true) + self?.open() }) } @@ -62,23 +66,8 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi fatalError("init(coder:) has not been implemented") } - func addGlobalAudioToVisible() { - if let controller = globalAudio { - table?.enumerateViews(with: { (view) in - if let view = (view as? ChatRowView)?.contentView.subviews.last as? ChatAudioContentView { - controller.add(listener: view) - } else if let view = (view as? ChatRowView)?.contentView.subviews.last as? ChatVideoMessageContentView { - controller.add(listener: view) - } else if let view = (view as? ChatRowView)?.contentView.subviews.last as? WPMediaContentView { - if let contentNode = view.contentNode as? ChatAudioContentView { - controller.add(listener: contentNode) - } - } - return true - }) - } - } - + + func willRemove() -> Void { //self.cancel() } @@ -92,13 +81,18 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } func delete() -> Void { - if let parent = parent { - _ = account?.postbox.modify({ modifier -> Void in - modifier.deleteMessages([parent.id]) + cancel() + if let parentId = parent?.id, let mediaBox = context?.account.postbox.mediaBox { + _ = context?.account.postbox.transaction({ transaction -> Void in + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: [parentId]) }).start() } } + override var allowsVibrancy: Bool { + return true + } + func cancelFetching() { } @@ -111,6 +105,14 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } + func preloadStreamblePart() { + + } + + func updateMouse() { + + } + func executeInteraction(_ isControl:Bool) -> Void { if let fetchStatus = self.fetchStatus { switch fetchStatus { @@ -125,7 +127,7 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } case .Remote: fetch() - //open() + //open() case .Local: open() break @@ -133,18 +135,59 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } } + func previewMediaIfPossible() -> Bool { + return false + } + deinit { self.clean() dragDisposable.dispose() } - func update(with media: Media, size:NSSize, account:Account, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false) -> Void { + func update(with media: Media, size:NSSize, context:AccountContext, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) -> Void { self.setContent(size: size) - self.media = media self.parameters = parameters - self.account = account + self.positionFlags = positionFlags + self.context = context self.parent = parent self.table = table + + + + self.media = media + + if let parameters = parameters { + if let parent = parent { + if parameters.automaticDownloadFunc(parent) { + fetch() + preloadStreamblePart() + } else { + if parameters.preload { + preloadStreamblePart() + } + } + } else if parameters.automaticDownload { + fetch() + preloadStreamblePart() + } else if parameters.preload { + preloadStreamblePart() + } + + } + + } + + var autoDownload: Bool { + if let parameters = parameters { + if let parent = parent { + if parameters.automaticDownloadFunc(parent) { + return true + } + } else if parameters.automaticDownload { + return true + } + } + return false } func addSublayer(_ layer:CALayer) -> Void { @@ -161,10 +204,25 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi return view } - var interactionContentView: NSView { + func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return self } + func interactionControllerDidFinishAnimation(interactive: Bool) { + + } + + func videoTimebase() -> CMTimebase? { + return nil + } + func applyTimebase(timebase: CMTimebase?) { + + } + + func addAccesoryOnCopiedView(view: NSView) { + + } + func draggingAbility(_ event:NSEvent) -> Bool { if let superview = superview { return NSPointInRect(superview.convert(event.locationInWindow, from: nil), frame) @@ -173,12 +231,18 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } override func mouseDown(with event: NSEvent) { + + if event.modifierFlags.contains(.control) { + super.mouseDown(with: event) + return + } + if userInteractionEnabled { inDragging = false - acceptDragging = false dragpath = nil mouseDownPoint = convert(event.locationInWindow, from: nil) - acceptDragging = draggingAbility(event) + acceptDragging = draggingAbility(event) && parent != nil && !parent!.containsSecretMedia + if let parent = parent, parent.id.peerId.id == Namespaces.Peer.SecretChat { acceptDragging = false } @@ -188,19 +252,22 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi super.superview?.mouseDown(with: event) } } - + func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { switch context { case .outsideApplication: return .copy case .withinApplication: return [] + @unknown default: + return [] } } private var dragpath:String? = nil - + func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType) { if let dragpath = dragpath { + pasteboard?.clearContents() pasteboard?.declareTypes([.kFilenames, .string], owner: self) pasteboard?.setPropertyList([dragpath], forType: .kFilenames) pasteboard?.setString(dragpath, forType: .string) @@ -209,7 +276,7 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } - + override func mouseDragged(with event: NSEvent) { if self.fetchStatus == .Local { @@ -219,10 +286,10 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi return } - if let account = account, let resource = mediaResource(from: media), let mimeType = mediaResourceMIMEType(from: media) { - let result = account.postbox.mediaBox.resourceData(resource) |> mapToSignal { [weak media] resource -> Signal in + if let context = context, let resource = mediaResource(from: media), let mimeType = mediaResourceMIMEType(from: media) { + let result = context.account.postbox.mediaBox.resourceData(resource) |> mapToSignal { [weak media] resource -> Signal in if resource.complete { - return resourceType( mimeType: mimeType) |> mapToSignal { [weak media] ext -> Signal in + return resourceType( mimeType: mimeType) |> mapToSignal { [weak media] ext -> Signal in return putFileToTemp(from: resource.path, named: mediaResourceName(from: media, ext: ext)) } } else { @@ -234,14 +301,14 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi dragDisposable.set(result.start(next: { [weak self] path in if let strongSelf = self, let path = path { strongSelf.dragpath = path - if let copy = (strongSelf.copy() as? NSView), let cgImage = copy.layer?.contents { - let image = NSImage(cgImage: cgImage as! CGImage, size: copy.frame.size) + if let cgImage = strongSelf.contents { + let image = NSImage(cgImage: cgImage as! CGImage, size: strongSelf.contentFrame.size) let writer = NSPasteboardItem() writer.setDataProvider(strongSelf, forTypes: [.kFileUrl]) let item = NSDraggingItem( pasteboardWriter: writer ) - item.setDraggingFrame(copy.bounds, contents: image) + item.setDraggingFrame(strongSelf.contentFrame, contents: image) strongSelf.beginDraggingSession(with: [item], event: event, source: strongSelf) } @@ -252,14 +319,24 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi } else { super.mouseDragged(with: event) } - + + } else { + super.mouseDragged(with: event) } - + + } else { + super.mouseDragged(with: event) } } override func mouseUp(with event: NSEvent) { + if event.modifierFlags.contains(.control) { + super.mouseUp(with: event) + return + } + + if !inDragging && draggingAbility(event) && userInteractionEnabled, event.clickCount <= 1 { executeInteraction(false) } else { @@ -271,6 +348,14 @@ class ChatMediaContentView: Control, NSDraggingSource, NSPasteboardItemDataProvi acceptDragging = false } + var contents: Any? { + return nil + } + + var contentFrame: NSRect { + return bounds + } } + diff --git a/Telegram-Mac/ChatMediaDice.swift b/Telegram-Mac/ChatMediaDice.swift new file mode 100644 index 0000000000..52963baa47 --- /dev/null +++ b/Telegram-Mac/ChatMediaDice.swift @@ -0,0 +1,38 @@ +// +// ChatMediaDice.swift +// Telegram +// +// Created by Mikhail Filimonov on 27.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class ChatMediaDice: ChatMediaItem { + override var additionalLineForDateInBubbleState: CGFloat? { + return rightSize.height + 5 + } + override var isFixedRightPosition: Bool { + return true + } + override var isBubbleFullFilled: Bool { + return true + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + return super.menuItems(in: location) |> map { [weak self] items in + var items = items + items.insert(ContextMenuItem(L10n.textCopyText, handler: { + if let media = self?.media as? TelegramMediaDice { + copyToClipboard(media.emoji) + } + }), at: 0) + return items + } + } +} diff --git a/Telegram-Mac/ChatMediaItem.swift b/Telegram-Mac/ChatMediaItem.swift index 40101f4e0b..801c7ed694 100644 --- a/Telegram-Mac/ChatMediaItem.swift +++ b/Telegram-Mac/ChatMediaItem.swift @@ -7,15 +7,90 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit class ChatMediaLayoutParameters : Equatable { - static func layout(for media:TelegramMediaFile, isWebpage: Bool, chatInteraction:ChatInteraction) -> ChatMediaLayoutParameters { - if media.isInstantVideo { + var showMedia:(Message)->Void = {_ in } + var showMessage:(Message)->Void = {_ in } + + let presentation: ChatMediaPresentation + let media: Media + + + private var _timeCodeInitializer: Double? = nil + + var timeCodeInitializer:Double? { + let current = self._timeCodeInitializer + self._timeCodeInitializer = nil + return current + } + + func remove_timeCodeInitializer() { + self._timeCodeInitializer = nil + } + + func set_timeCodeInitializer(_ timecode: Double?) { + self._timeCodeInitializer = timecode + } + + private var _automaticDownload: Bool + + var automaticDownload: Bool { + get { + let value = _automaticDownload +// _automaticDownload = false + return value + } + } + + let autoplayMedia: AutoplayMediaPreferences + + var autoplay: Bool + var soundOnHover: Bool { + return autoplayMedia.soundOnHover + } + var preload: Bool { + return autoplayMedia.preloadVideos + } + +// var autoplay: Bool { +// get { +// let value = _automaticDownload +// return value +// } +// } +// + + var automaticDownloadFunc:(Message)->Bool + + + init(presentation: ChatMediaPresentation, media: Media, automaticDownload: Bool, autoplayMedia: AutoplayMediaPreferences) { + self.automaticDownloadFunc = { _ in + return automaticDownload + } + self.presentation = presentation + self.media = media + self.autoplayMedia = autoplayMedia + self._automaticDownload = automaticDownload + if let media = media as? TelegramMediaFile { + if media.isVideo && media.isAnimated { + self.autoplay = autoplayMedia.gifs + } else { + self.autoplay = autoplayMedia.videos + } + } else { + self.autoplay = false + } + } + + + static func layout(for media:TelegramMediaFile, isWebpage: Bool, chatInteraction:ChatInteraction, presentation: ChatMediaPresentation, automaticDownload: Bool, isIncoming: Bool, isFile: Bool = false, autoplayMedia: AutoplayMediaPreferences, isChatRelated: Bool = false) -> ChatMediaLayoutParameters { + if media.isInstantVideo && !isFile { var duration:Int = 0 for attr in media.attributes { switch attr { @@ -26,8 +101,8 @@ class ChatMediaLayoutParameters : Equatable { } } - return ChatMediaVideoMessageLayoutParameters(showPlayer:chatInteraction.inlineAudioPlayer, duration: duration, isMarked: true, isWebpage: isWebpage || chatInteraction.isLogInteraction, resource: media.resource) - } else if media.isVoice { + return ChatMediaVideoMessageLayoutParameters(showPlayer:chatInteraction.inlineAudioPlayer, duration: duration, isMarked: true, isWebpage: isWebpage || chatInteraction.isLogInteraction, resource: media.resource, presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: autoplayMedia) + } else if media.isVoice && !isFile { var waveform:AudioWaveform? = nil var duration:Int = 0 for attr in media.attributes { @@ -35,15 +110,15 @@ class ChatMediaLayoutParameters : Equatable { case let .Audio(params): if let data = params.waveform?.makeData() { waveform = AudioWaveform(bitstream: data, bitsPerSample: 5) - duration = params.duration } + duration = params.duration default: break } } - return ChatMediaVoiceLayoutParameters(showPlayer:chatInteraction.inlineAudioPlayer, waveform:waveform, duration:duration, isMarked: true, isWebpage: isWebpage || chatInteraction.isLogInteraction, resource: media.resource) - } else if media.isMusic { + return ChatMediaVoiceLayoutParameters(showPlayer:chatInteraction.inlineAudioPlayer, waveform:waveform, duration:duration, isMarked: true, isWebpage: isWebpage || chatInteraction.isLogInteraction, resource: media.resource, presentation: presentation, media: media, automaticDownload: automaticDownload) + } else if media.isMusic && !isFile { var audioTitle:String? var audioPerformer:String? @@ -62,36 +137,40 @@ class ChatMediaLayoutParameters : Equatable { if let _audioTitle = audioTitle, let audioPerformer = audioPerformer { if _audioTitle.isEmpty && audioPerformer.isEmpty { - _ = attr.append(string: media.fileName, color: theme.colors.text, font: NSFont.normal(.title)) + _ = attr.append(string: media.fileName, color: presentation.text, font: NSFont.medium(.title)) audioTitle = media.fileName } else { - _ = attr.append(string: _audioTitle + " - " + audioPerformer, color: theme.colors.text, font: NSFont.normal(.title)) + _ = attr.append(string: _audioTitle + " - " + audioPerformer, color: presentation.text, font: NSFont.medium(.title)) } } else { - _ = attr.append(string: media.fileName, color: theme.colors.text, font: NSFont.normal(.title)) + _ = attr.append(string: media.fileName, color: presentation.text, font: NSFont.medium(.title)) audioTitle = media.fileName } - return ChatMediaMusicLayoutParameters(nameLayout: TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .middle), durationLayout: TextViewLayout(.initialize(string: String.durationTransformed(elapsed: duration), color: theme.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1, truncationType: .middle), sizeLayout: TextViewLayout(.initialize(string: (media.size ?? 0).prettyNumber, color: theme.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1, truncationType: .middle), resource: media.resource, isWebpage: isWebpage, title: audioTitle, performer: audioPerformer, showPlayer:chatInteraction.inlineAudioPlayer) + return ChatMediaMusicLayoutParameters(nameLayout: TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end), durationLayout: TextViewLayout(.initialize(string: String.durationTransformed(elapsed: duration), color: presentation.grayText, font: .normal(.title)), maximumNumberOfLines: 1, truncationType: .end), sizeLayout: TextViewLayout(.initialize(string: (media.size ?? 0).prettyNumber, color: presentation.grayText, font: .normal(.title)), maximumNumberOfLines: 1, truncationType: .middle), resource: media.resource, isWebpage: isWebpage, title: audioTitle, performer: audioPerformer, showPlayer:chatInteraction.inlineAudioPlayer, presentation: presentation, media: media, automaticDownload: automaticDownload) } else { var fileName:String = "Unknown.file" if let name = media.fileName { fileName = name } - return ChatFileLayoutParameters(fileName: fileName, hasThumb: !media.previewRepresentations.isEmpty) + return ChatFileLayoutParameters(fileName: fileName, hasThumb: !media.previewRepresentations.isEmpty, presentation: presentation, media: media, automaticDownload: automaticDownload, isIncoming: isIncoming, autoplayMedia: autoplayMedia, isChatRelated: isChatRelated) } } + func makeLabelsForWidth(_ width: CGFloat) { + + } + } class ChatMediaGalleryParameters : ChatMediaLayoutParameters { let isWebpage: Bool - let showMedia:()->Void - let showMessage:(Message)->Void - init(showMedia:@escaping()->Void, showMessage:@escaping(Message)->Void, isWebpage: Bool) { + + init(showMedia:@escaping(Message)->Void = { _ in }, showMessage:@escaping(Message)->Void = { _ in }, isWebpage: Bool, presentation: ChatMediaPresentation = .Empty, media: Media, automaticDownload: Bool, autoplayMedia: AutoplayMediaPreferences = AutoplayMediaPreferences.defaultSettings) { + self.isWebpage = isWebpage + super.init(presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: autoplayMedia) self.showMedia = showMedia self.showMessage = showMessage - self.isWebpage = isWebpage } } @@ -102,51 +181,177 @@ func ==(lhs:ChatMediaLayoutParameters, rhs:ChatMediaLayoutParameters) -> Bool { class ChatMediaItem: ChatRowItem { - var _media:Media - var media:Media { - if let _media = _media as? TelegramMediaGame { - if let file = _media.file { - return file - } else if let image = _media.image { - return image - } - } - return _media - } + let media:Media + var parameters:ChatMediaLayoutParameters? - let gameTitleLayout:TextViewLayout? + override var topInset:CGFloat { return 4 } + var mediaBubbleCornerInset: CGFloat { + return 1 + } + + override var bubbleFrame: NSRect { + var frame = super.bubbleFrame + + if isBubbleFullFilled { + frame.size.width = contentSize.width + additionBubbleInset + if hasBubble { + frame.size.width += self.mediaBubbleCornerInset * 2 + } + } + + return frame + } + + override var defaultContentTopOffset: CGFloat { + if isBubbled && !hasBubble { + return 2 + } + return isBubbled && !isBubbleFullFilled ? 14 : super.defaultContentTopOffset + } + - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { + override var contentOffset: NSPoint { + var offset = super.contentOffset + // + if hasBubble { + if forwardNameLayout != nil { + offset.y += defaultContentInnerInset + } else if !isBubbleFullFilled { + offset.y += (defaultContentInnerInset + 2) + } + } + + if hasBubble && authorText == nil && replyModel == nil && forwardNameLayout == nil { + offset.y -= (defaultContentInnerInset + self.mediaBubbleCornerInset * 2 - (isBubbleFullFilled ? 1 : 0)) + } + return offset + } + + + override var elementsContentInset: CGFloat { + if hasBubble && isBubbleFullFilled { + return bubbleContentInset + } + return super.elementsContentInset + } + + + + override var _defaultHeight: CGFloat { + if hasBubble && isBubbleFullFilled && captionLayout == nil { + return contentOffset.y + defaultContentInnerInset - mediaBubbleCornerInset * 2 - 1 + } - if case let .MessageEntry(message,_,_,_,_) = object { - _media = message.media[0] + return super._defaultHeight + } + + override var realContentSize: NSSize { + var size = super.realContentSize + + if isBubbleFullFilled { + size.width -= bubbleContentInset * 2 + } + return size + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if isForceRightLine { + return rightSize.height + } + if let file = self.media as? TelegramMediaFile, file.isEmojiAnimatedSticker { + return rightSize.height + 3 + } + if let caption = captionLayout { + if let line = caption.lines.last, line.frame.width > realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return rightSize.height + } + } + if postAuthor != nil { + return isStateOverlayLayout ? nil : rightSize.height + } + return super.additionalLineForDateInBubbleState + } + + override var isFixedRightPosition: Bool { + if media is TelegramMediaImage { + return true + } else if let media = media as? TelegramMediaFile { - if let media = _media as? TelegramMediaGame { - gameTitleLayout = TextViewLayout(.initialize(string: media.name, color: theme.colors.blueText, font: .medium(.text))) - } else { - gameTitleLayout = nil + if let captionLayout = captionLayout, let line = captionLayout.lines.last, line.frame.width < realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return true } + return media.isVideo || media.isAnimated || media.isVoice || media.isMusic || media.isStaticSticker || media.isAnimatedSticker + } + return super.isFixedRightPosition + } + + override var instantlyResize: Bool { + if captionLayout != nil && media.isInteractiveMedia { + return true } else { - fatalError("no media for message") + return super.instantlyResize } + } + + + override var isBubbleFullFilled: Bool { + return (media.isInteractiveMedia || isSticker) && isBubbled + } + + var positionFlags: LayoutPositionFlags? = nil + + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + + let message = object.message! + + let isIncoming: Bool = message.isIncoming(context.account, object.renderType == .bubble) + + media = message.media[0] - super.init(initialSize, chatInteraction, account, object) + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) - if let message = message, !message.text.isEmpty { + var canAddCaption: Bool = true + if let media = media as? TelegramMediaFile, media.isAnimatedSticker || media.isStaticSticker { + canAddCaption = false + } + if media is TelegramMediaDice { + canAddCaption = false + } + + + self.parameters = ChatMediaGalleryParameters(showMedia: { [weak self] message in + guard let `self` = self else {return} + + var type:GalleryAppearType = .history + if let parameters = self.parameters as? ChatMediaGalleryParameters, parameters.isWebpage { + type = .alone + } else if message.containsSecretMedia { + type = .secret + } + showChatGallery(context: context, message: message, self.table, self.parameters as? ChatMediaGalleryParameters, type: type) + + }, showMessage: { [weak self] message in + self?.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + }, isWebpage: chatInteraction.isLogInteraction, presentation: .make(for: message, account: context.account, renderType: object.renderType), media: media, automaticDownload: downloadSettings.isDownloable(message), autoplayMedia: object.autoplayMedia) + + + if !message.text.isEmpty, canAddCaption { + + + var caption:NSMutableAttributedString = NSMutableAttributedString() - NSAttributedString.initialize() - _ = caption.append(string: message.text, color: theme.colors.text, font: NSFont.normal(.custom(theme.fontSize))) + _ = caption.append(string: message.text, color: theme.chat.textColor(isIncoming, object.renderType == .bubble), font: .normal(theme.fontSize)) var types:ParsingType = [.Links, .Mentions, .Hashtags] if let peer = messageMainPeer(message) as? TelegramUser { @@ -171,19 +376,76 @@ class ChatMediaItem: ChatRowItem { break } } - if hasEntities { - caption = ChatMessageItem.applyMessageEntities(with: message.attributes, for: message.text.fixed, account:account, fontSize: theme.fontSize, openInfo:chatInteraction.openInfo, botCommand:chatInteraction.forceSendMessage, hashtag:chatInteraction.modalSearch, applyProxy: chatInteraction.applyProxy).mutableCopy() as! NSMutableAttributedString + var mediaDuration: Double? = nil + if let file = message.media.first as? TelegramMediaFile, file.isVideo && !file.isAnimated, let duration = file.duration { + mediaDuration = Double(duration) + } + + caption = ChatMessageItem.applyMessageEntities(with: message.attributes, for: message.text.fixed, context: context, fontSize: theme.fontSize, openInfo:chatInteraction.openInfo, botCommand:chatInteraction.sendPlainText, hashtag: chatInteraction.modalSearch, applyProxy: chatInteraction.applyProxy, textColor: theme.chat.textColor(isIncoming, object.renderType == .bubble), linkColor: theme.chat.linkColor(isIncoming, object.renderType == .bubble), monospacedPre: theme.chat.monospacedPreColor(isIncoming, entry.renderType == .bubble), monospacedCode: theme.chat.monospacedCodeColor(isIncoming, entry.renderType == .bubble), mediaDuration: mediaDuration, timecode: { [weak self] timecode in + self?.parameters?.set_timeCodeInitializer(timecode) + self?.parameters?.showMedia(message) + }, openBank: chatInteraction.openBank).mutableCopy() as! NSMutableAttributedString + + + if !hasEntities || message.flags.contains(.Failed) || message.flags.contains(.Unsent) || message.flags.contains(.Sending) { + caption.detectLinks(type: types, context: context, color: theme.chat.linkColor(isIncoming, object.renderType == .bubble), openInfo:chatInteraction.openInfo, hashtag: context.sharedContext.bindings.globalSearch, command: chatInteraction.sendPlainText, applyProxy: chatInteraction.applyProxy) + } + captionLayout = TextViewLayout(caption, alignment: .left, selectText: theme.chat.selectText(isIncoming, object.renderType == .bubble), strokeLinks: object.renderType == .bubble, alwaysStaticItems: true, disableTooltips: false) + + let interactions = globalLinkExecutor + + interactions.copyToClipboard = { text in + copyToClipboard(text) + context.sharedContext.bindings.rootNavigation().controller.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) + } + captionLayout?.interactions = interactions + + if let textLayout = self.captionLayout { + if let highlightFoundText = entry.additionalData.highlightFoundText { + if highlightFoundText.isMessage { + if let range = rangeOfSearch(highlightFoundText.query, in: caption.string) { + textLayout.additionalSelections = [TextSelectedRange(range: range, color: theme.colors.accentIcon.withAlphaComponent(0.5), def: false)] + } + } else { + var additionalSelections:[TextSelectedRange] = [] + let string = caption.string.lowercased().nsstring + var searchRange = NSMakeRange(0, string.length) + var foundRange:NSRange = NSMakeRange(NSNotFound, 0) + while (searchRange.location < string.length) { + searchRange.length = string.length - searchRange.location + foundRange = string.range(of: highlightFoundText.query.lowercased(), options: [], range: searchRange) + if (foundRange.location != NSNotFound) { + additionalSelections.append(TextSelectedRange(range: foundRange, color: theme.colors.grayIcon.withAlphaComponent(0.5), def: false)) + searchRange.location = foundRange.location+foundRange.length; + } else { + break + } + } + textLayout.additionalSelections = additionalSelections + } + } } - caption.detectLinks(type: types, account: account, openInfo:chatInteraction.openInfo, hashtag: chatInteraction.modalSearch, command: chatInteraction.forceSendMessage) - captionLayout = TextViewLayout(caption, alignment: .left) - captionLayout?.interactions = globalLinkExecutor + } - self.parameters = ChatMediaGalleryParameters(showMedia: { - - }, showMessage: { [weak self] message in - self?.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, animated: true, focus: true, inset: 0)) - }, isWebpage: chatInteraction.isLogInteraction) + + + + + if isBubbleFullFilled { + var positionFlags: LayoutPositionFlags = [] + if captionLayout == nil { + positionFlags.insert(.bottom) + positionFlags.insert(.left) + positionFlags.insert(.right) + } + if authorText == nil && replyModel == nil && forwardNameLayout == nil { + positionFlags.insert(.top) + positionFlags.insert(.left) + positionFlags.insert(.right) + } + self.positionFlags = positionFlags + } } @@ -192,98 +454,250 @@ class ChatMediaItem: ChatRowItem { } override func makeContentSize(_ width: CGFloat) -> NSSize { - gameTitleLayout?.measure(width: width) - if let gameTitleLayout = gameTitleLayout { - var contentSize = ChatLayoutUtils.contentSize(for: media, with: width) - contentSize.height += gameTitleLayout.layoutSize.height + 6 - return contentSize - } else { - return ChatLayoutUtils.contentSize(for: media, with: width) - } + let size = ChatLayoutUtils.contentSize(for: media, with: width, hasText: message?.text.isEmpty == false) + return size } - override func menuItems() -> Signal<[ContextMenuItem], Void> { - - if self.chatInteraction.isLogInteraction { - return .single([]) + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:Signal<[ContextMenuItem], NoError> = .complete() + if let message = message { + items = chatMenuItems(for: message, chatInteraction: chatInteraction) } - - let signal = super.menuItems() - if let account = account { - if let file = self.media as? TelegramMediaFile { - return signal |> mapToSignal { items -> Signal<[ContextMenuItem], Void> in - var items = items - return account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue |> mapToSignal { data in - if data.complete { - items.append(ContextMenuItem(tr(.contextCopyMedia), handler: { - saveAs(file, account: account) - })) - } - - if file.isSticker, let fileId = file.id { - return account.postbox.modify { modifier -> [ContextMenuItem] in - let saved = getIsStickerSaved(modifier: modifier, fileId: fileId) - items.append(ContextMenuItem( !saved ? tr(.chatContextAddFavoriteSticker) : tr(.chatContextRemoveFavoriteSticker), handler: { - - if !saved { - _ = addSavedSticker(postbox: account.postbox, network: account.network, file: file).start() - } else { - _ = removeSavedSticker(postbox: account.postbox, mediaId: fileId).start() - } - })) - - return items + return items |> map { [weak self] items in + var items = items + if let captionLayout = self?.captionLayout { + let text = captionLayout.attributedString.string + items.insert(ContextMenuItem(L10n.textCopyText, handler: { + copyToClipboard(text) + }), at: min(items.count, 1)) + + if let view = self?.view as? ChatRowView, let textView = view.captionView, let window = textView.window { + let point = textView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let layout = textView.layout { + if let (link, _, range, _) = layout.link(at: point) { + var text:String = layout.attributedString.string.nsstring.substring(with: range) + if let link = link as? inAppLink { + if case let .external(link, _) = link { + text = link + } } - } - - return .single(items) - } - } - } else if let image = self.media as? TelegramMediaImage { - - return signal |> mapToSignal { items -> Signal<[ContextMenuItem], Void> in - var items = items - if let resource = image.representations.last?.resource { - return account.postbox.mediaBox.resourceData(resource) |> take(1) |> deliverOnMainQueue |> map { data in - if data.complete { - items.append(ContextMenuItem(tr(.galleryContextCopyToClipboard), handler: { - if let path = link(path: data.path, ext: "jpg") { - let pb = NSPasteboard.general - pb.clearContents() - pb.writeObjects([NSURL(fileURLWithPath: path)]) - } - })) - items.append(ContextMenuItem(tr(.contextCopyMedia), handler: { - savePanel(file: data.path, ext: "jpg", for: mainWindow) - })) + + for i in 0 ..< items.count { + if items[i].title == tr(L10n.messageContextCopyMessageLink1) { + items.remove(at: i) + break + } } - return items + + items.insert(ContextMenuItem(tr(L10n.messageContextCopyMessageLink1), handler: { + copyToClipboard(text) + }), at: 1) } - } else { - return .single(items) } } } + return items } - - return signal + } + + override func canMultiselectTextIn(_ location: NSPoint) -> Bool { + if let view = view as? ChatMediaView, let content = view.contentNode { + let point = view.contentView.convert(location, from: nil) + return !NSPointInRect(point, content.frame) + } + return false } override var identifier: String { - return super.identifier + "\(stableId)" + return super.identifier } public func contentNode() -> ChatMediaContentView.Type { + if let file = media as? TelegramMediaFile, message?.id.peerId.namespace == Namespaces.Peer.SecretChat, file.isAnimatedSticker, file.stickerReference == nil { + return ChatFileContentView.self + } return ChatLayoutUtils.contentNode(for: media) } override func viewClass() -> AnyClass { - if _media is TelegramMediaGame { - return ChatMediaGameView.self + return ChatMediaView.self + } + +} + + + +class ChatMediaView: ChatRowView, ModalPreviewRowViewProtocol { + + + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let contentNode = contentNode { + if contentNode is ChatStickerContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, StickerPreviewModalView.self), contentNode) + } + } else if contentNode is ChatGIFContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, GifPreviewModalView.self), contentNode) + } + } else if contentNode is ChatInteractiveContentView { + if let image = contentNode.media as? TelegramMediaImage { + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } else if contentNode is ChatFileContentView { + if let file = contentNode.media as? TelegramMediaFile, file.isGraphicFile, let mediaId = file.id, let dimension = file.dimensions { + var representations: [TelegramMediaImageRepresentation] = [] + representations.append(contentsOf: file.previewRepresentations) + representations.append(TelegramMediaImageRepresentation(dimensions: dimension, resource: file.resource)) + let image = TelegramMediaImage(imageId: mediaId, representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: file.partialReference, flags: []) + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } else if contentNode is MediaAnimatedStickerView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, AnimatedStickerPreviewModalView.self), contentNode) + } + } + } + + return nil + } + + override func previewMediaIfPossible() -> Bool { + + return contentNode?.previewMediaIfPossible() ?? false + } + + override func forceClick(in location: NSPoint) { + + if contentNode?.mouseInside() == true { + let result = previewMediaIfPossible() + if !result { + super.forceClick(in: location) + } } else { - return ChatMediaView.self + super.forceClick(in: location) } + } + + fileprivate(set) var contentNode:ChatMediaContentView? + + override var needsDisplay: Bool { + get { + return super.needsDisplay + } + set { + super.needsDisplay = true + contentNode?.needsDisplay = true + } + } + + override var backgroundColor: NSColor { + didSet { + + contentNode?.backgroundColor = contentColor + } + } + + override func shakeView() { + contentNode?.shake() + } + + + override func updateMouse() { + super.updateMouse() + self.contentNode?.updateMouse() + } + + override var contentFrame: NSRect { + var rect = super.contentFrame + + guard let item = item as? ChatMediaItem else { return rect } + + if item.isBubbled, item.isBubbleFullFilled { + rect.origin.x -= item.bubbleContentInset + if item.hasBubble { + rect.origin.x += item.mediaBubbleCornerInset + } + } + + return rect + } + + override func viewWillMove(toSuperview newSuperview: NSView?) { + if newSuperview == nil { + self.contentNode?.willRemove() + } + } + + override func draw(_ dirtyRect: NSRect) { + + } + + override func set(item:TableRowItem, animated:Bool = false) { + if let item:ChatMediaItem = item as? ChatMediaItem { + if contentNode == nil || !contentNode!.isKind(of: item.contentNode()) { + self.contentNode?.removeFromSuperview() + let node = item.contentNode() + self.contentNode = node.init(frame:NSZeroRect) + self.addSubview(self.contentNode!) + } + + self.contentNode?.update(with: item.media, size: item.contentSize, context: item.context, parent:item.message, table:item.table, parameters:item.parameters, animated: animated, positionFlags: item.positionFlags, approximateSynchronousValue: item.approximateSynchronousValue) + } + super.set(item: item, animated: animated) + } + + open override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + if let content = self.contentNode?.interactionContentView(for: innerId, animateIn: animateIn) { + return content + } + return self + } + + override func videoTimebase(for innerId: AnyHashable) -> CMTimebase? { + return self.contentNode?.videoTimebase() + } + override func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + self.contentNode?.applyTimebase(timebase: timebase) + } + + override func interactionControllerDidFinishAnimation(interactive: Bool, innerId: AnyHashable) { + + if interactive { + self.contentNode?.interactionControllerDidFinishAnimation(interactive: interactive) + } + } + + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + guard let item = item as? ChatRowItem, let contentNode = contentNode else {return} + + + + + let rightView = ChatRightView(frame: NSZeroRect) + rightView.set(item: item, animated: false) + var rect = self.rightView.convert(self.rightView.bounds, to: contentNode) + + if contentNode.visibleRect.minY < rect.midY && contentNode.visibleRect.minY + contentNode.visibleRect.height > rect.midY { + rect.origin.y = contentNode.frame.height - rect.maxY + rightView.frame = rect + view.addSubview(rightView) + } + + + contentNode.addAccesoryOnCopiedView(view: view) + } + } + + + diff --git a/Telegram-Mac/ChatMediaView.swift b/Telegram-Mac/ChatMediaView.swift deleted file mode 100644 index 3ba8f5afd8..0000000000 --- a/Telegram-Mac/ChatMediaView.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// ChatMediaView.swift -// Telegram-Mac -// -// Created by keepcoder on 18/09/16. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -class ChatMediaView: ChatRowView { - - var contentNode:ChatMediaContentView? - - override var needsDisplay: Bool { - get { - return super.needsDisplay - } - set { - super.needsDisplay = true - contentNode?.needsDisplay = true - } - } - - override func draw(_ dirtyRect: NSRect) { - - } - - override func set(item:TableRowItem, animated:Bool = false) { - if let item:ChatMediaItem = item as? ChatMediaItem { - if contentNode == nil || !contentNode!.isKind(of: item.contentNode()) { - self.contentNode?.removeFromSuperview() - let node = item.contentNode() - self.contentNode = node.init(frame:NSZeroRect) - self.addSubview(self.contentNode!) - } - - self.contentNode?.update(with: item.media, size: item.contentSize, account: item.account!, parent:item.message, table:item.table, parameters:item.parameters, animated: animated) - } - super.set(item: item, animated: animated) - } - - open override var interactionContentView:NSView { - if let content = self.contentNode?.interactionContentView { - return content - } - return self - } - - - override var backgroundColor: NSColor { - didSet { - contentNode?.backgroundColor = backdorColor - } - } - - override func viewWillMove(toSuperview newSuperview: NSView?) { - if newSuperview == nil { - self.contentNode?.willRemove() - } - } - -} - - -class ChatMediaGameView: ChatRowView { - - var contentNode:ChatMediaContentView? - private let title:TextView = TextView() - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - let layout = TextViewLayout(.initialize(string: "supergame", color: .blueUI, font: .normal(.text))) - layout.measure(width: 1000) - title.update(layout) - title.userInteractionEnabled = false - addSubview(title) - } - - - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func draw(_ dirtyRect: NSRect) { - - } - - override func layout() { - super.layout() - - if let item = item as? ChatMediaItem { - title.update(item.gameTitleLayout) - } - } - - override func set(item:TableRowItem, animated:Bool = false) { - if let item:ChatMediaItem = item as? ChatMediaItem { - if contentNode == nil || !contentNode!.isKind(of: item.contentNode()) { - self.contentNode?.removeFromSuperview() - let node = item.contentNode() - self.contentNode = node.init(frame:NSZeroRect) - self.addSubview(self.contentNode!) - } - self.contentNode?.userInteractionEnabled = false - self.contentNode?.update(with: item.media, size: NSMakeSize(item.contentSize.width, item.contentSize.height - item.gameTitleLayout!.layoutSize.height - 6), account: item.account!, parent:item.message, table:item.table, parameters:item.parameters) - - title.update(item.gameTitleLayout) - - self.contentNode?.setFrameOrigin(0, item.gameTitleLayout!.layoutSize.height + 6) - } - super.set(item: item, animated: animated) - } - - - override var backgroundColor: NSColor { - didSet { - contentNode?.backgroundColor = backgroundColor - } - } - - override func viewWillMove(toSuperview newSuperview: NSView?) { - if newSuperview == nil { - self.contentNode?.willRemove() - } - } - -} - - - diff --git a/Telegram-Mac/ChatMessageAccessoryView.swift b/Telegram-Mac/ChatMessageAccessoryView.swift new file mode 100644 index 0000000000..6bbfbe5e86 --- /dev/null +++ b/Telegram-Mac/ChatMessageAccessoryView.swift @@ -0,0 +1,259 @@ +// +// ChatMessageAccessoryView.swift +// Telegram +// +// Created by keepcoder on 05/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox + + + +class ChatMessageAccessoryView: Control { + + private let textView:TextView = TextView() + private let backgroundView = View() + private var maxWidth: CGFloat = 0 + private let unread = View() + private var stringValue: String = "" + private let progress: RadialProgressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, cancelFetchingIcon: stopFetchStreamableControl), twist: true, size: NSMakeSize(24, 24)) + private let bufferingIndicator: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 10, 10)) + private let download: ImageButton = ImageButton(frame: NSMakeRect(0, 0, 24, 24)) + + private var status: MediaResourceStatus? + private var isStreamable: Bool = true + private var isCompact: Bool = false + + private var imageView: ImageView? + + var soundOffOnImage: CGImage? { + didSet { + if let soundOffOnImage = soundOffOnImage { + if imageView == nil { + imageView = ImageView() + imageView?.animates = true + addSubview(imageView!) + } + imageView?.image = soundOffOnImage + imageView?.sizeToFit() + } else { + imageView?.removeFromSuperview() + imageView = nil + } + } + } + + private let progressCap: View = View() + var isUnread: Bool = false + override func draw(_ layer: CALayer, in ctx: CGContext) { + + } + + + override func layout() { + super.layout() + download.centerY(x: 6) + progress.centerY(x: 6) + backgroundView.frame = bounds + + bufferingIndicator.centerY(x: frame.width - bufferingIndicator.frame.width - 7) + if let imageView = imageView { + imageView.centerY(x: frame.width - imageView.frame.width - 6) + } + + if let textLayout = textView.layout { + var rect = focus(textLayout.layoutSize) + rect.origin.x = 6 + if hasStremingControls { + rect.origin.x += download.frame.width + 6 + } + if backingScaleFactor == 2 { + rect.origin.y += 0.5 + } else { + rect.origin.y += 1 + } + textView.frame = rect + + unread.centerY(x: rect.maxX + 2) + + } + + } + + var hasStremingControls: Bool { + return !download.isHidden || !progress.isHidden + } + + private var fetch:(()->Void)? + private var cancelFetch:(()->Void)? + private var click:(()->Void)? + + private var isVideoMessage: Bool = false + + func updateText(_ text: String, maxWidth: CGFloat, status: MediaResourceStatus?, isStreamable: Bool, isCompact: Bool = false, soundOffOnImage: CGImage? = nil, isBuffering: Bool = false, isUnread: Bool = false, animated: Bool = false, isVideoMessage: Bool = false, fetch: @escaping()-> Void = { }, cancelFetch: @escaping()-> Void = { }, click: @escaping()-> Void = { }) -> Void { + + + let animated = animated && self.isCompact != isCompact + + let updatedText = TextViewLayout(.initialize(string: isStreamable ? text.components(separatedBy: ", ").joined(separator: "\n") : text, color: isVideoMessage ? theme.chatServiceItemTextColor : .white, font: .normal(10.0)), maximumNumberOfLines: isStreamable && !isCompact ? 2 : 1, truncationType: .end, alwaysStaticItems: true) //TextNode.layoutText(maybeNode: textNode, .initialize(string: isStreamable ? text.components(separatedBy: ", ").joined(separator: "\n") : text, color: isVideoMessage ? theme.chatServiceItemTextColor : .white, font: .normal(10.0)), nil, isStreamable && !isCompact ? 2 : 1, .end, NSMakeSize(maxWidth, 20), nil, false, .left) + updatedText.measure(width: maxWidth) + textView.update(updatedText) + + backgroundView.backgroundColor = isVideoMessage ? theme.chatServiceItemColor : .blackTransparent + + + self.isStreamable = isStreamable + self.status = status + self.stringValue = text + self.maxWidth = maxWidth + self.fetch = fetch + self.isCompact = isCompact + self.cancelFetch = cancelFetch + self.click = click + self.soundOffOnImage = soundOffOnImage + self.isUnread = isUnread + + self.bufferingIndicator.isHidden = !isBuffering + self.unread.isHidden = !isUnread + + if let status = status, isStreamable { + + download.set(image: isCompact ? theme.icons.videoCompactFetching : theme.icons.streamingVideoDownload, for: .Normal) + + + switch status { + case .Remote: + progress.isHidden = true + download.isHidden = false + progress.state = .None + case .Local: + progress.isHidden = true + download.isHidden = true + progress.state = .None + case let .Fetching(_, progress): + self.progress.state = !isCompact ? .Fetching(progress: progress, force: false) : .None + self.progress.isHidden = isCompact + download.isHidden = !isCompact + download.set(image: isCompact ? theme.icons.compactStreamingFetchingCancel : theme.icons.streamingVideoDownload, for: .Normal) + } + if isCompact { + download.setFrameSize(10, 10) + } else { + download.setFrameSize(28, 28) + + } + } else { + progress.isHidden = true + download.isHidden = true + progress.state = .None + } + + let newSize = NSMakeSize(min(max(soundOffOnImage != nil ? 30 : updatedText.layoutSize.width, updatedText.layoutSize.width) + 12 + (isUnread ? 8 : 0) + (hasStremingControls ? download.frame.width + 6 : 0) + (soundOffOnImage != nil ? soundOffOnImage!.backingSize.width + 2 : 0) + (isBuffering ? bufferingIndicator.frame.width + 4 : 0), maxWidth), hasStremingControls && !isCompact ? 36 : updatedText.layoutSize.height + 6) + change(size: newSize, animated: animated) + backgroundView.change(size: newSize, animated: animated) + + + backgroundView.layer?.cornerRadius = isStreamable ? 8 : newSize.height / 2 + + + var rect = focus(updatedText.layoutSize) + rect.origin.x = 6 + if hasStremingControls { + rect.origin.x += download.frame.width + 6 + } + if backingScaleFactor == 2 { + rect.origin.y += 0.5 + } + textView.change(pos: rect.origin, animated: animated) + + if animated, let layer = backgroundView.layer { + let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius") + cornerAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + cornerAnimation.fromValue = layer.presentation()?.cornerRadius ?? layer.cornerRadius + cornerAnimation.toValue = isStreamable ? 8 : newSize.height / 2 + cornerAnimation.duration = 0.2 + layer.add(cornerAnimation, forKey: "cornerRadius") + } + + needsLayout = true + } + + override func copy() -> Any { + let view = ChatMessageAccessoryView(frame: frame) + view.updateText(self.stringValue, maxWidth: self.maxWidth, status: self.status, isStreamable: self.isStreamable, isCompact: self.isCompact) + return view + } + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + + unread.setFrameSize(NSMakeSize(6, 6)) + unread.layer?.cornerRadius = 3 + unread.backgroundColor = isVideoMessage ? theme.chatServiceItemTextColor : .white + textView.isSelectable = false + textView.userInteractionEnabled = false + textView.disableBackgroundDrawing = true + + addSubview(backgroundView) + addSubview(textView) + addSubview(progress) + addSubview(download) + addSubview(unread) + bufferingIndicator.background = .clear + bufferingIndicator.progressColor = isVideoMessage ? theme.chatServiceItemTextColor : .white + bufferingIndicator.layer?.cornerRadius = bufferingIndicator.frame.height / 2 +// bufferingIndicator.lineWidth = 1.0 + bufferingIndicator.isHidden = true + progress.isHidden = true + download.isHidden = true + download.autohighlight = false + progress.fetchControls = FetchControls(fetch: { [weak self] in + self?.cancelFetch?() + }) + + progressCap.layer?.borderColor = NSColor.white.withAlphaComponent(0.3).cgColor + progressCap.layer?.borderWidth = 2.0 + progressCap.frame = NSMakeRect(2, 2, progress.frame.width - 4, progress.frame.height - 4) + progressCap.layer?.cornerRadius = progressCap.frame.width / 2 + + progress.addSubview(progressCap) + + addSubview(bufferingIndicator) + + + download.set(handler: { [weak self] _ in + guard let `self` = self, let status = self.status else {return} + switch status { + case .Remote: + self.fetch?() + case .Fetching: + self.cancelFetch?() + default: + break + } + }, for: .Click) + + set(handler: { [weak self] _ in + guard let `self` = self, let status = self.status else {return} + switch status { + case .Remote: + self.fetch?() + case .Fetching: + self.cancelFetch?() + default: + self.click?() + } + }, for: .Click) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ChatMessageBubbleImages.swift b/Telegram-Mac/ChatMessageBubbleImages.swift new file mode 100644 index 0000000000..99f0663753 --- /dev/null +++ b/Telegram-Mac/ChatMessageBubbleImages.swift @@ -0,0 +1,196 @@ +// +// ChatMessageBubbleImages.swift +// Telegram +// +// Created by keepcoder on 04/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + +enum MessageBubbleImageNeighbors { + case none + case top + case bottom + case both +} + +func messageSingleBubbleLikeImage(fillColor: NSColor, strokeColor: NSColor) -> CGImage { + let diameter: CGFloat = 36.0 + return generateImage(CGSize(width: 36.0, height: diameter), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let lineWidth: CGFloat = 0.5 + + context.setFillColor(strokeColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: lineWidth, y: lineWidth), size: CGSize(width: size.width - lineWidth * 2.0, height: size.height - lineWidth * 2.0))) + })! +} + + +func messageBubbleImageModern(incoming: Bool, fillColor: NSColor, strokeColor: NSColor, neighbors: MessageBubbleImageNeighbors, mask: Bool = false) -> (CGImage, NSEdgeInsets) { + + let diameter: CGFloat = 36.0 + let corner: CGFloat = 7.0 + + let image = generateImage(CGSize(width: 42.0, height: diameter), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let additionalOffset: CGFloat + switch neighbors { + case .none, .bottom: + additionalOffset = 0.0 + case .both, .top: + additionalOffset = 6.0 + } + + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: incoming ? 1.0 : -1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0 + 0.5 + additionalOffset, y: -size.height / 2.0 + 0.5) + + let lineWidth: CGFloat = 1.0 + + if mask { + context.setBlendMode(.copy) + context.setFillColor(NSColor.clear.cgColor) + context.setStrokeColor(NSColor.clear.cgColor) + } else { + context.setFillColor(fillColor.cgColor) + context.setLineWidth(lineWidth) + context.setStrokeColor(strokeColor.cgColor) + } + + + switch neighbors { + case .none: + let _ = try? drawSvgPath(context, path: "M6,17.5 C6,7.83289181 13.8350169,0 23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41102995e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ") + context.strokePath() + let _ = try? drawSvgPath(context, path: "M6,17.5 C6,7.83289181 13.8350169,0 23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41102995e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ") + context.fillPath() + case .top: + let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") + context.strokePath() + let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") + context.fillPath() + case .bottom: + let _ = try? drawSvgPath(context, path: "M6,17.5 L6,5.99681848 C6,2.6882755 8.68486709,0 11.9968185,0 L23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41103066e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ") + context.strokePath() + let _ = try? drawSvgPath(context, path: "M6,17.5 L6,5.99681848 C6,2.6882755 8.68486709,0 11.9968185,0 L23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41103066e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ") + context.fillPath() + case .both: + + let _ = try? drawSvgPath(context, path: "M17.5,0 C27.1649831,1.94289029e-15 35,7.83501688 35,17.5 C35,27.1649831 27.1649831,35 17.5,35 C7.83501688,35 0,27.1649831 0,17.5 C3.88578059e-15,7.83501688 7.83501688,-1.94289029e-15 17.5,0 ") + context.strokePath() + + let _ = try? drawSvgPath(context, path: "M17.5,0 C27.1649831,1.94289029e-15 35,7.83501688 35,17.5 C35,27.1649831 27.1649831,35 17.5,35 C7.83501688,35 0,27.1649831 0,17.5 C3.88578059e-15,7.83501688 7.83501688,-1.94289029e-15 17.5,0 ") + context.fillPath() + + } + + })! + + + let leftCapWidth: CGFloat = CGFloat(incoming ? Int(corner + diameter / 2.0) : Int(diameter / 2.0)) + let topCapHeight: CGFloat = diameter / 2.0 + let rightCapWidth: CGFloat = image.backingSize.width - leftCapWidth - 1.0 + let bottomCapHeight: CGFloat = image.backingSize.height - topCapHeight - 1.0 + + return (image, NSEdgeInsetsMake(topCapHeight, leftCapWidth, bottomCapHeight, rightCapWidth)) +} + + +func ninePartPiecesFromImageWithInsets(_ image: CGImage, capInsets: RHEdgeInsets) -> [CGImage] { + + let imageWidth: CGFloat = image.backingSize.width + let imageHeight: CGFloat = image.backingSize.height + + let leftCapWidth: CGFloat = capInsets.left + let topCapHeight: CGFloat = capInsets.top + let rightCapWidth: CGFloat = capInsets.right + let bottomCapHeight: CGFloat = capInsets.bottom + + let centerSize: NSSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight); + + let topLeftCorner: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(0.0, imageHeight - topCapHeight, leftCapWidth, topCapHeight)) + let topEdgeFill: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight)) + let topRightCorner: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight)) + + let leftEdgeFill: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(0.0, bottomCapHeight, leftCapWidth, centerSize.height)) + let centerFill: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height)) + let rightEdgeFill: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height)) + + let bottomLeftCorner: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(0.0, 0.0, leftCapWidth, bottomCapHeight)) + let bottomEdgeFill: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, 0.0, centerSize.width, bottomCapHeight)) + let bottomRightCorner: CGImage = imageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, 0.0, rightCapWidth, bottomCapHeight)) + + return [topLeftCorner, topEdgeFill, topRightCorner, leftEdgeFill, centerFill, rightEdgeFill, bottomLeftCorner, bottomEdgeFill, bottomRightCorner] +} + +func drawNinePartImage(_ context: CGContext, frame: NSRect, topLeftCorner: CGImage, topEdgeFill: CGImage, topRightCorner: CGImage, leftEdgeFill: CGImage, centerFill: CGImage, rightEdgeFill: CGImage, bottomLeftCorner: CGImage, bottomEdgeFill: CGImage, bottomRightCorner: CGImage){ + + let imageWidth: CGFloat = frame.size.width; + let imageHeight: CGFloat = frame.size.height; + + let leftCapWidth: CGFloat = topLeftCorner.backingSize.width; + let topCapHeight: CGFloat = topLeftCorner.backingSize.height; + let rightCapWidth: CGFloat = bottomRightCorner.backingSize.width; + let bottomCapHeight: CGFloat = bottomRightCorner.backingSize.height; + + let centerSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight); + + let topLeftCornerRect: NSRect = NSMakeRect(0.0, imageHeight - topCapHeight, leftCapWidth, topCapHeight); + let topEdgeFillRect: NSRect = NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight); + let topRightCornerRect: NSRect = NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight); + + let leftEdgeFillRect: NSRect = NSMakeRect(0.0, bottomCapHeight, leftCapWidth, centerSize.height); + let centerFillRect: NSRect = NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height); + let rightEdgeFillRect: NSRect = NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height); + + let bottomLeftCornerRect: NSRect = NSMakeRect(0.0, 0.0, leftCapWidth, bottomCapHeight); + let bottomEdgeFillRect: NSRect = NSMakeRect(leftCapWidth, 0.0, centerSize.width, bottomCapHeight); + let bottomRightCornerRect: NSRect = NSMakeRect(imageWidth - rightCapWidth, 0.0, rightCapWidth, bottomCapHeight); + + + drawStretchedImageInRect(topLeftCorner, context: context, rect: topLeftCornerRect); + drawStretchedImageInRect(topEdgeFill, context: context, rect: topEdgeFillRect); + drawStretchedImageInRect(topRightCorner, context: context, rect: topRightCornerRect); + + drawStretchedImageInRect(leftEdgeFill, context: context, rect: leftEdgeFillRect); + drawStretchedImageInRect(centerFill, context: context, rect: centerFillRect); + drawStretchedImageInRect(rightEdgeFill, context: context, rect: rightEdgeFillRect); + + drawStretchedImageInRect(bottomLeftCorner, context: context, rect: bottomLeftCornerRect); + drawStretchedImageInRect(bottomEdgeFill, context: context, rect: bottomEdgeFillRect); + drawStretchedImageInRect(bottomRightCorner, context: context, rect: bottomRightCornerRect); + +} + + +func imageByReferencingRectOfExistingImage(_ image: CGImage, _ rect: NSRect) -> CGImage { + if (!NSIsEmptyRect(rect)){ + + let pixelsHigh = CGFloat(image.height) + + let scaleFactor:CGFloat = pixelsHigh / image.backingSize.height + var captureRect = NSMakeRect(scaleFactor * rect.origin.x, scaleFactor * rect.origin.y, scaleFactor * rect.size.width, scaleFactor * rect.size.height) + + captureRect.origin.y = pixelsHigh - captureRect.origin.y - captureRect.size.height; + + return image.cropping(to: captureRect)! + } + return image.cropping(to: NSMakeRect(0, 0, image.size.width, image.size.height))! +} + +func drawStretchedImageInRect(_ image: CGImage, context: CGContext, rect: NSRect) -> Void { + context.saveGState() + context.setBlendMode(.normal) //NSCompositeSourceOver + context.clip(to: rect) + + context.draw(image, in: rect) + context.restoreGState() +} diff --git a/Telegram-Mac/ChatMessageDateHeader.swift b/Telegram-Mac/ChatMessageDateHeader.swift index 8ef7fb95db..ecb11eb6d9 100644 --- a/Telegram-Mac/ChatMessageDateHeader.swift +++ b/Telegram-Mac/ChatMessageDateHeader.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox private let timezoneOffset: Int32 = { let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) @@ -24,26 +25,26 @@ private let granularity: Int32 = 60 * 60 * 24 func chatDateId(for timestamp:Int32) -> Int64 { - /* var roundedTimestamp:Int32 - if timestamp == Int32.max { - roundedTimestamp = timestamp / (granularity) * (granularity) - } else { - roundedTimestamp = ((timestamp + timezoneOffset) / (granularity)) * (granularity) - } */ - - return Int64(Calendar.current.startOfDay(for: Date(timeIntervalSince1970: TimeInterval(timestamp))).timeIntervalSince1970) + return Int64(Calendar.autoupdatingCurrent.startOfDay(for: Date(timeIntervalSince1970: TimeInterval(timestamp))).timeIntervalSince1970) +} +func mediaDateId(for timestamp:Int32) -> Int64 { + let startMonth = Calendar.autoupdatingCurrent.date(from: Calendar.current.dateComponents([.year, .month], from: Date(timeIntervalSince1970: TimeInterval(timestamp))))! + let endMonth = Calendar.autoupdatingCurrent.date(byAdding: DateComponents(month: 1, day: -1), to: startMonth)! + return Int64(endMonth.timeIntervalSince1970) } class ChatDateStickItem : TableStickItem { private let entry:ChatHistoryEntry - fileprivate let timestamp:Int32 + let timestamp:Int32 fileprivate let chatInteraction:ChatInteraction? + let isBubbled: Bool let layout:TextViewLayout - init(_ initialSize:NSSize, _ entry:ChatHistoryEntry, interaction: ChatInteraction) { + init(_ initialSize:NSSize, _ entry:ChatHistoryEntry, interaction: ChatInteraction, theme: TelegramPresentationTheme) { self.entry = entry + self.isBubbled = entry.renderType == .bubble self.chatInteraction = interaction - if case let .DateEntry(index) = entry { + if case let .DateEntry(index, _) = entry { self.timestamp = index.timestamp } else { fatalError() @@ -59,35 +60,64 @@ class ChatDateStickItem : TableStickItem { var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) - let text: String + var text: String if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday { - text = tr(.dateToday) + + switch interaction.mode { + case .scheduled: + text = L10n.chatDateScheduledForToday + default: + text = L10n.dateToday + } + } else { let dateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: appCurrentLanguage.languageCode) + dateFormatter.calendar = Calendar.autoupdatingCurrent + //dateFormatter.timeZone = NSTimeZone.local dateFormatter.dateFormat = "dd MMMM"; - text = dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(timestamp))) - + //&& (timeinfoNow.tm_mon >= timeinfo.tm_mon || (timeinfoNow.tm_year - timeinfo.tm_year) >= 2) + if timeinfoNow.tm_year > timeinfo.tm_year { + dateFormatter.dateFormat = "dd MMMM yyyy"; + } else if timeinfoNow.tm_year < timeinfo.tm_year { + dateFormatter.dateFormat = "dd MMMM yyyy"; + } + let dateString = dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(timestamp))) + switch interaction.mode { + case .scheduled: + if timestamp == 2147457600 { + text = L10n.chatDateScheduledUntilOnline + } else { + text = L10n.chatDateScheduledFor(dateString) + } + default: + text = dateString + } } - let attributedString = NSAttributedString.initialize(string: text, color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) - self.layout = TextViewLayout(attributedString, maximumNumberOfLines: 1, truncationType: .end, alignment: .center) + + self.layout = TextViewLayout(.initialize(string: text, color: theme.chatServiceItemTextColor, font: .medium(theme.fontSize)), maximumNumberOfLines: 1, truncationType: .end, alignment: .center) super.init(initialSize) } + override var canBeAnchor: Bool { + return false + } + required init(_ initialSize: NSSize) { - entry = .DateEntry(MessageIndex.absoluteLowerBound()) + entry = .DateEntry(MessageIndex.absoluteLowerBound(), .list) timestamp = 0 + self.isBubbled = false self.layout = TextViewLayout(NSAttributedString()) self.chatInteraction = nil super.init(initialSize) } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) layout.measure(width: width - 40) - return super.makeSize(width, oldWidth: oldWidth) + return success } override var stableId: AnyHashable { @@ -95,7 +125,7 @@ class ChatDateStickItem : TableStickItem { } override var height: CGFloat { - return 50 + return 30 } override func viewClass() -> AnyClass { @@ -112,40 +142,79 @@ class ChatDateStickView : TableStickView { required init(frame frameRect: NSRect) { self.textView = TextView() self.textView.isSelectable = false - self.textView.userInteractionEnabled = false + // self.textView.userInteractionEnabled = false self.containerView.wantsLayer = true - textView.isEventLess = true + self.textView.disableBackgroundDrawing = true + // textView.isEventLess = false super.init(frame: frameRect) - addSubview(containerView) addSubview(textView) - addSubview(borderView) - containerView.set(handler: { [weak self] _ in - if let strongSelf = self, let item = strongSelf.item as? ChatDateStickItem, strongSelf.header { - - var calendar = NSCalendar.current + textView.set(handler: { [weak self] control in + if let strongSelf = self, let item = strongSelf.item as? ChatDateStickItem, let table = item.table { - calendar.timeZone = TimeZone(abbreviation: "UTC")! - let date = Date(timeIntervalSince1970: TimeInterval(item.timestamp + 86400)) - let components = calendar.dateComponents([.year, .month, .day], from: date) + let row = table.visibleRows() + var ignore: Bool = false + if row.length > 1 { + if let underItem = table.item(at: row.location + row.length - 1) as? ChatDateStickItem { + ignore = item.timestamp == underItem.timestamp + } + } - item.chatInteraction?.jumpToDate(CalendarUtils.monthDay(components.day!, date: date)) + if strongSelf.header && !ignore { + var calendar = NSCalendar.current + + calendar.timeZone = TimeZone(abbreviation: "UTC")! + let date = Date(timeIntervalSince1970: TimeInterval(item.timestamp + 86400)) + let components = calendar.dateComponents([.year, .month, .day], from: date) + + item.chatInteraction?.jumpToDate(CalendarUtils.monthDay(components.day!, date: date)) + } else if let chatInteraction = item.chatInteraction, chatInteraction.mode == .history { + if !hasPopover(chatInteraction.context.window) { + let controller = CalendarController(NSMakeRect(0, 0, 250, 250), chatInteraction.context.window, current: Date(timeIntervalSince1970: TimeInterval(item.timestamp)), selectHandler: chatInteraction.jumpToDate) + showPopover(for: control, with: controller, edge: .maxY, inset: NSMakePoint(-100, -40)) + } + } + } }, for: .Click) } + override func hitTest(_ point: NSPoint) -> NSView? { + return header && textView.layer?.opacity == 0 ? nil : super.hitTest(point) + } + + override func mouseDown(with event: NSEvent) { + guard header, let tableView = superview as? TableView else { + super.mouseDown(with: event) + return + } + + tableView.documentView!.hitTest(tableView.documentView!.convert(event.locationInWindow, from: nil))?.mouseDown(with: event) + + } + + override func mouseUp(with event: NSEvent) { + guard header, let tableView = superview as? TableView else { + super.mouseUp(with: event) + return + } + + tableView.documentView!.hitTest(tableView.documentView!.convert(event.locationInWindow, from: nil))?.mouseUp(with: event) + + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var backdorColor: NSColor { - return header ? .clear : theme.colors.background + return .clear } override func updateIsVisible(_ visible: Bool, animated: Bool) { textView.change(opacity: visible ? 1 : 0, animated: animated) - containerView.change(opacity: visible ? 1 : 0, animated: animated) } + override var header: Bool { didSet { updateColors() @@ -154,14 +223,12 @@ class ChatDateStickView : TableStickView { override func updateColors() { super.updateColors() - textView.backgroundColor = theme.colors.background - containerView.backgroundColor = .clear - containerView.layer?.borderColor = theme.colors.border.cgColor - containerView.layer?.borderWidth = header ? 1.0 : 0 - - containerView.backgroundColor = theme.colors.background + + textView.backgroundColor = theme.chatServiceItemColor + //containerView.layer?.borderColor = theme.colors.border.cgColor + // containerView.layer?.borderWidth = header || (theme.wallpaper != .none) ? 1.0 : 0 } @@ -173,19 +240,16 @@ class ChatDateStickView : TableStickView { override func layout() { super.layout() textView.center() - containerView.center() - borderView.center() } override func set(item: TableRowItem, animated: Bool) { if let item = item as? ChatDateStickItem { textView.update(item.layout) - containerView.setFrameSize(textView.frame.width + 16, textView.frame.height + 8) - containerView.layer?.cornerRadius = containerView.frame.height / 2 - borderView.layer?.cornerRadius = containerView.frame.height / 2 - if animated { - containerView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) - } + textView.setFrameSize(item.layout.layoutSize.width + 16, item.layout.layoutSize.height + 6) + textView.layer?.cornerRadius = textView.frame.height / 2 +// if animated { +// containerView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) +// } self.needsLayout = true } diff --git a/Telegram-Mac/ChatMessageItem.swift b/Telegram-Mac/ChatMessageItem.swift index ce43e32d17..21ffa1b5e7 100644 --- a/Telegram-Mac/ChatMessageItem.swift +++ b/Telegram-Mac/ChatMessageItem.swift @@ -8,13 +8,19 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + + class ChatMessageItem: ChatRowItem { public private(set) var messageText:NSAttributedString public private(set) var textLayout:TextViewLayout + private let youtubeExternalLoader = MetaDisposable() + override var selectableLayout:[TextViewLayout] { return [textLayout] } @@ -23,28 +29,180 @@ class ChatMessageItem: ChatRowItem { webpageLayout?.table = self.table } + override var isSharable: Bool { + if let webpage = webpageLayout { + if webpage.content.type == "proxy" { + return true + } + } + return super.isSharable + } + + override var isBubbleFullFilled: Bool { + return containsBigEmoji || super.isBubbleFullFilled + } + + override var isStateOverlayLayout: Bool { + return containsBigEmoji && renderType == .bubble || super.isStateOverlayLayout + } + + override var bubbleContentInset: CGFloat { + return containsBigEmoji && renderType == .bubble ? 0 : super.bubbleContentInset + } + + override var defaultContentTopOffset: CGFloat { + if isBubbled && !hasBubble { + return 2 + } + return super.defaultContentTopOffset + } + + override var hasBubble: Bool { + get { + if containsBigEmoji { + return false + } else { + return super.hasBubble + } + } + set { + super.hasBubble = newValue + } + } + + let containsBigEmoji: Bool + + var unsupported: Bool { + + if let message = message, message.text.isEmpty && (message.media.isEmpty || message.media.first is TelegramMediaUnsupported) { + return message.inlinePeer == nil + } else { + return false + } + } + + var actionButtonWidth: CGFloat { + if let webpage = webpageLayout { + if webpage.isTheme { + return webpage.size.width + } + } + return self.contentSize.width + } + + var actionButtonText: String? { + if let webpage = webpageLayout, !webpage.hasInstantPage { + let link = inApp(for: webpage.content.url.nsstring, context: context, openInfo: chatInteraction.openInfo) + switch link { + case let .followResolvedName(_, _, postId, _, _, _): + if let postId = postId, postId > 0 { + return L10n.chatMessageActionShowMessage + } + default: + break + } + if webpage.wallpaper != nil { + return L10n.chatViewBackground + } + if webpage.isTheme { + return L10n.chatActionViewTheme + } + } + + if unsupported { + return L10n.chatUnsupportedUpdatedApp + } + + return nil + } + + override var isEditMarkVisible: Bool { + if containsBigEmoji { + return false + } else { + return super.isEditMarkVisible + } + } + + func invokeAction() { + if let webpage = webpageLayout { + let link = inApp(for: webpage.content.url.nsstring, context: context, openInfo: chatInteraction.openInfo) + execute(inapp: link) + } else if unsupported { + #if APP_STORE + execute(inapp: inAppLink.external(link: "https://apps.apple.com/us/app/telegram/id747648890", false)) + #else + (NSApp.delegate as? AppDelegate)?.checkForUpdates("") + #endif + } + } + + let wpPresentation: WPLayoutPresentation + var webpageLayout:WPLayout? - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction,_ account:Account, _ entry: ChatHistoryEntry) { + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction,_ context: AccountContext, _ entry: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { - if let message = entry.message { - + if let message = entry.message { + + let isIncoming: Bool = message.isIncoming(context.account, entry.renderType == .bubble) + + var openSpecificTimecodeFromReply:((Double?)->Void)? = nil + let messageAttr:NSMutableAttributedString - if message.text.isEmpty && message.media.isEmpty { + if message.inlinePeer == nil, message.text.isEmpty && (message.media.isEmpty || message.media.first is TelegramMediaUnsupported) { let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.chatMessageUnsupported), color: theme.colors.text, font: .code(.custom(theme.fontSize))) + _ = attr.append(string: L10n.chatMessageUnsupportedNew, color: theme.chat.textColor(isIncoming, entry.renderType == .bubble), font: .code(theme.fontSize)) messageAttr = attr } else { - messageAttr = ChatMessageItem.applyMessageEntities(with: message.attributes, for: message.text, account:account, fontSize: theme.fontSize, openInfo:chatInteraction.openInfo, botCommand:chatInteraction.forceSendMessage, hashtag:account.context.globalSearch ?? {_ in }, applyProxy: chatInteraction.applyProxy).mutableCopy() as! NSMutableAttributedString -// - if message.flags.contains(.Sending) { - messageAttr.detectLinks(type: [.Links, .Mentions, .Hashtags], account: account, openInfo:chatInteraction.openInfo, applyProxy: chatInteraction.applyProxy) + + var mediaDuration: Double? = nil + var mediaDurationMessage:Message? + + var canAssignToReply: Bool = true + + if let media = message.media.first as? TelegramMediaWebpage { + switch media.content { + case let .Loaded(content): + canAssignToReply = !ExternalVideoLoader.isPlayable(content) + default: + break + } + } + + if canAssignToReply, let reply = message.replyAttribute { + mediaDurationMessage = message.associatedMessages[reply.messageId] + } else { + mediaDurationMessage = message + } + if let message = mediaDurationMessage { + if let file = message.media.first as? TelegramMediaFile, file.isVideo && !file.isAnimated, let duration = file.duration { + mediaDuration = Double(duration) + } else if let media = message.media.first as? TelegramMediaWebpage { + switch media.content { + case let .Loaded(content): + if ExternalVideoLoader.isPlayable(content) { + mediaDuration = 10 * 60 * 60 + } + default: + break + } + } } + let openInfo:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void = { [weak chatInteraction] peerId, toChat, postId, initialAction in + chatInteraction?.openInfo(peerId, toChat, postId, initialAction ?? .source(message.id)) + } + + + messageAttr = ChatMessageItem.applyMessageEntities(with: message.attributes, for: message.text, context: context, fontSize: theme.fontSize, openInfo:openInfo, botCommand:chatInteraction.sendPlainText, hashtag: chatInteraction.modalSearch, applyProxy: chatInteraction.applyProxy, textColor: theme.chat.textColor(isIncoming, entry.renderType == .bubble), linkColor: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), monospacedPre: theme.chat.monospacedPreColor(isIncoming, entry.renderType == .bubble), monospacedCode: theme.chat.monospacedCodeColor(isIncoming, entry.renderType == .bubble), mediaDuration: mediaDuration, timecode: { timecode in + openSpecificTimecodeFromReply?(timecode) + }, openBank: chatInteraction.openBank).mutableCopy() as! NSMutableAttributedString + messageAttr.fixUndefinedEmojies() - var formatting: Bool = true + var formatting: Bool = messageAttr.length > 0 var index:Int = 0 while formatting { var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) @@ -64,15 +222,15 @@ class ChatMessageItem: ChatRowItem { } if effectiveRange.min > 0 { - let increment = beforeAndAfter(effectiveRange.min - 1) + let increment = beforeAndAfter(effectiveRange.min) if increment { - effectiveRange = NSMakeRange(effectiveRange.location - 1, effectiveRange.length) + effectiveRange = NSMakeRange(effectiveRange.location, effectiveRange.length + 1) } } if effectiveRange.max < messageAttr.length - 1 { let increment = beforeAndAfter(effectiveRange.max) if increment { - effectiveRange = NSMakeRange(effectiveRange.location + 1, effectiveRange.length) + effectiveRange = NSMakeRange(effectiveRange.location, effectiveRange.length + 1) } } } @@ -86,97 +244,280 @@ class ChatMessageItem: ChatRowItem { formatting = index < messageAttr.length } - - +// if message.isScam { +// _ = messageAttr.append(string: "\n\n") +// _ = messageAttr.append(string: L10n.chatScamWarning, color: theme.chat.textColor(isIncoming, entry.renderType == .bubble), font: .normal(theme.fontSize)) +// } } + + let copy = messageAttr.mutableCopy() as! NSMutableAttributedString if let peer = message.peers[message.id.peerId] { if peer is TelegramSecretChat { - copy.detectLinks(type: .Links, account: account) + copy.detectLinks(type: [.Links, .Hashtags, .Mentions], context: context, color: theme.chat.linkColor(isIncoming, entry.renderType == .bubble)) } } + + let containsBigEmoji: Bool + if message.media.first == nil, bigEmojiMessage(context.sharedContext, message: message) { + switch copy.string.glyphCount { + case 1: + copy.addAttribute(.font, value: NSFont.normal(theme.fontSize * 5.8), range: copy.range) + containsBigEmoji = true + case 2: + copy.addAttribute(.font, value: NSFont.normal(theme.fontSize * 4.8), range: copy.range) + containsBigEmoji = true + case 3: + copy.addAttribute(.font, value: NSFont.normal(theme.fontSize * 3.8), range: copy.range) + containsBigEmoji = true + default: + containsBigEmoji = false + } + } else { + containsBigEmoji = false + } + self.containsBigEmoji = containsBigEmoji + + if message.flags.contains(.Failed) || message.flags.contains(.Unsent) || message.flags.contains(.Sending) { + copy.detectLinks(type: [.Links, .Mentions, .Hashtags, .Commands], context: context, color: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), openInfo: chatInteraction.openInfo, hashtag: { _ in }, command: { _ in }, applyProxy: chatInteraction.applyProxy) + } + self.messageText = copy + + + textLayout = TextViewLayout(self.messageText, selectText: theme.chat.selectText(isIncoming, entry.renderType == .bubble), strokeLinks: entry.renderType == .bubble && !containsBigEmoji, alwaysStaticItems: true, disableTooltips: false) + textLayout.mayBlocked = entry.renderType != .bubble + + if let highlightFoundText = entry.additionalData.highlightFoundText { + if highlightFoundText.isMessage { + let range = copy.string.lowercased().nsstring.range(of: highlightFoundText.query.lowercased()) + if range.location != NSNotFound { + textLayout.additionalSelections = [TextSelectedRange(range: range, color: theme.colors.accentIcon.withAlphaComponent(0.5), def: false)] + } + } else { + var additionalSelections:[TextSelectedRange] = [] + let string = copy.string.lowercased().nsstring + var searchRange = NSMakeRange(0, string.length) + var foundRange:NSRange = NSMakeRange(NSNotFound, 0) + while (searchRange.location < string.length) { + searchRange.length = string.length - searchRange.location + foundRange = string.range(of: highlightFoundText.query.lowercased(), options: [], range: searchRange) + if (foundRange.location != NSNotFound) { + additionalSelections.append(TextSelectedRange(range: foundRange, color: theme.colors.grayIcon.withAlphaComponent(0.5), def: false)) + searchRange.location = foundRange.location+foundRange.length; + } else { + break + } + } + textLayout.additionalSelections = additionalSelections + } + + } - textLayout = TextViewLayout(self.messageText) - if let range = selectManager.find(entry.stableId) { textLayout.selectedRange.range = range } + var media = message.media.first + if let game = media as? TelegramMediaGame { + media = TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: TelegramMediaWebpageContent.Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: "photo", websiteName: game.name, title: game.name, text: game.description, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: game.image, file: game.file, attributes: [], instantPage: nil))) + } + + self.wpPresentation = WPLayoutPresentation(text: theme.chat.textColor(isIncoming, entry.renderType == .bubble), activity: theme.chat.webPreviewActivity(isIncoming, entry.renderType == .bubble), link: theme.chat.linkColor(isIncoming, entry.renderType == .bubble), selectText: theme.chat.selectText(isIncoming, entry.renderType == .bubble), ivIcon: theme.chat.instantPageIcon(isIncoming, entry.renderType == .bubble, presentation: theme), renderType: entry.renderType) + - if let webpage = message.media.first as? TelegramMediaWebpage { + if let webpage = media as? TelegramMediaWebpage { switch webpage.content { case let .Loaded(content): - if content.file == nil { - webpageLayout = WPArticleLayout(with: content, account:account, chatInteraction: chatInteraction, parent:message, fontSize: theme.fontSize) + var forceArticle: Bool = false + if let instantPage = content.instantPage { + if instantPage.blocks.count == 3 { + switch instantPage.blocks[2] { + case .collage, .slideshow: + forceArticle = true + default: + break + } + } + } + if content.type == "telegram_background" { + forceArticle = true + } + if content.file == nil || forceArticle { + webpageLayout = WPArticleLayout(with: content, context: context, chatInteraction: chatInteraction, parent:message, fontSize: theme.fontSize, presentation: wpPresentation, approximateSynchronousValue: Thread.isMainThread, downloadSettings: downloadSettings, autoplayMedia: entry.autoplayMedia) } else { - webpageLayout = WPMediaLayout(with: content, account:account, chatInteraction: chatInteraction, parent:message, fontSize: theme.fontSize) + webpageLayout = WPMediaLayout(with: content, context: context, chatInteraction: chatInteraction, parent:message, fontSize: theme.fontSize, presentation: wpPresentation, approximateSynchronousValue: Thread.isMainThread, downloadSettings: downloadSettings, autoplayMedia: entry.autoplayMedia) } default: break } } - super.init(initialSize,chatInteraction,account,entry) + super.init(initialSize, chatInteraction, context, entry, downloadSettings, theme: theme) - textLayout.interactions = TextViewInteractions(processURL:{ link in - if let link = link as? inAppLink { - execute(inapp:link) + + (webpageLayout as? WPMediaLayout)?.parameters?.showMedia = { [weak self] message in + if let webpage = message.media.first as? TelegramMediaWebpage { + switch webpage.content { + case let .Loaded(content): + if content.embedType == "iframe" && content.type != kBotInlineTypeGif { + showModal(with: WebpageModalController(content: content, context: context), for: mainWindow) + return + } + default: + break + } } - }, copy: { + showChatGallery(context: context, message: message, self?.table, (self?.webpageLayout as? WPMediaLayout)?.parameters, type: .alone) + } + + openSpecificTimecodeFromReply = { [weak self] timecode in + if let timecode = timecode { + var canAssignToReply: Bool = true + if let media = message.media.first as? TelegramMediaWebpage { + switch media.content { + case let .Loaded(content): + canAssignToReply = !ExternalVideoLoader.isPlayable(content) + default: + break + } + } + var assignMessage: Message? + if canAssignToReply, let reply = message.replyAttribute { + assignMessage = message.associatedMessages[reply.messageId] + } else { + assignMessage = message + } + if let message = assignMessage { + let id = ChatHistoryEntryId.message(message) + if let item = self?.table?.item(stableId: id) as? ChatMediaItem { + item.parameters?.set_timeCodeInitializer(timecode) + item.parameters?.showMedia(message) + } else if let groupInfo = message.groupInfo { + let id = ChatHistoryEntryId.groupedPhotos(groupInfo: groupInfo) + if let item = self?.table?.item(stableId: id) as? ChatGroupedItem { + item.parameters?.set_timeCodeInitializer(timecode) + item.parameters?.showMedia(message) + } + } else if let item = self?.table?.item(stableId: id) as? ChatMessageItem { + if let content = item.webpageLayout?.content { + self?.youtubeExternalLoader.set((sharedVideoLoader.status(for: content) |> deliverOnMainQueue).start(next: { [weak item] status in + if let item = item, let message = item.message { + if let status = status { + let content = content.withUpdatedYoutubeTimecode(timecode) + if let media = message.media.first as? TelegramMediaWebpage { + switch status { + case .fail: + execute(inapp: .external(link: content.url, false)) + case .loaded: + let message = message.withUpdatedMedia([TelegramMediaWebpage(webpageId: media.webpageId, content: .Loaded(content))]) + showChatGallery(context: item.context, message: message, item.table) + default: + break + } + } + + + } + } + + })) + } + } + } + } + } + + let interactions = globalLinkExecutor + interactions.copy = { selectManager.copy(selectManager) return !selectManager.isEmpty - }, menuItems: { [weak self] in + } + interactions.copyToClipboard = { text in + copyToClipboard(text) + context.sharedContext.bindings.rootNavigation().controller.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) + } + interactions.menuItems = { [weak self] type in var items:[ContextMenuItem] = [] - if let strongSelf = self { - items.append(ContextMenuItem(tr(.textCopy), handler: { [weak strongSelf] in - let result = strongSelf?.textLayout.interactions.copy?() - if let result = result, let strongSelf = strongSelf, !result { + if let strongSelf = self, let layout = self?.textLayout { + + let text: String + if let type = type { + text = copyContextText(from: type) + items.append(ContextMenuItem(text, handler: { + if let strongSelf = self { + let pb = NSPasteboard.general + pb.clearContents() + pb.declareTypes([.string], owner: strongSelf) + var effectiveRange = strongSelf.textLayout.selectedRange.range + let selectedText = strongSelf.textLayout.attributedString.attributedSubstring(from: effectiveRange) + let attribute = strongSelf.textLayout.attributedString.attribute(NSAttributedString.Key.link, at: strongSelf.textLayout.selectedRange.range.location, effectiveRange: &effectiveRange) + if let attribute = attribute as? inAppLink { + pb.setString(attribute.link.isEmpty ? selectedText.string : attribute.link, forType: .string) + } else { + pb.setString(selectedText.string, forType: .string) + } + } + })) + + } + + items.append(ContextMenuItem(layout.selectedRange.hasSelectText ? L10n.chatCopySelectedText : L10n.textCopy, handler: { + let result = self?.textLayout.interactions.copy?() + if let result = result, let strongSelf = self, !result { if strongSelf.textLayout.selectedRange.hasSelectText { let pb = NSPasteboard.general + pb.clearContents() pb.declareTypes([.string], owner: strongSelf) var effectiveRange = strongSelf.textLayout.selectedRange.range - - let attribute = strongSelf.textLayout.attributedString.attribute(NSAttributedStringKey.link, at: strongSelf.textLayout.selectedRange.range.location, effectiveRange: &effectiveRange) - - if let attribute = attribute as? inAppLink, case let .external(link, confirm) = attribute { - if confirm { - pb.setString(link, forType: .string) - return + let selectedText = strongSelf.textLayout.attributedString.attributedSubstring(from: strongSelf.textLayout.selectedRange.range) + let isCopied = globalLinkExecutor.copyAttributedString(selectedText) + if !isCopied { + let attribute = strongSelf.textLayout.attributedString.attribute(NSAttributedString.Key.link, at: strongSelf.textLayout.selectedRange.range.location, effectiveRange: &effectiveRange) + + if let attribute = attribute as? inAppLink { + pb.setString(attribute.link.isEmpty ? selectedText.string : attribute.link, forType: .string) + } else { + pb.setString(selectedText.string, forType: .string) } } - pb.setString(strongSelf.textLayout.attributedString.string.nsstring.substring(with: strongSelf.textLayout.selectedRange.range), forType: .string) } } })) + if strongSelf.textLayout.selectedRange.hasSelectText { var effectiveRange: NSRange = NSMakeRange(NSNotFound, 0) if let _ = strongSelf.textLayout.attributedString.attribute(.preformattedPre, at: strongSelf.textLayout.selectedRange.range.location, effectiveRange: &effectiveRange) { let blockText = strongSelf.textLayout.attributedString.attributedSubstring(from: effectiveRange).string - items.append(ContextMenuItem(tr(.chatContextCopyBlock), handler: { + items.append(ContextMenuItem(tr(L10n.chatContextCopyBlock), handler: { copyToClipboard(blockText) })) } } - return strongSelf.menuItems() |> map { basic in + return strongSelf.menuItems(in: NSZeroPoint) |> map { basic in var basic = basic - basic.remove(at: 1) - return items + basic + if basic.count > 1 { + basic.remove(at: 1) + basic.insert(contentsOf: items, at: 1) + } + + return basic } } return .complete() - - }) + } + + textLayout.interactions = interactions return } @@ -192,74 +533,193 @@ class ChatMessageItem: ChatRowItem { } } + override var isForceRightLine: Bool { + if self.webpageLayout?.content.type == "proxy" { + return true + } else { + return super.isForceRightLine + } + } + + override var isFixedRightPosition: Bool { + if containsBigEmoji { + return true + } + if let webpageLayout = webpageLayout { + if let webpageLayout = webpageLayout as? WPArticleLayout, let textLayout = webpageLayout.textLayout { + if textLayout.lines.count > 1, let line = textLayout.lines.last, line.frame.width < contentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return true + } + } + return super.isFixedRightPosition + } + + if textLayout.lines.count > 1, let line = textLayout.lines.last, line.frame.width < contentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return true + } + return super.isForceRightLine + } + + override var additionalLineForDateInBubbleState: CGFloat? { + + if containsBigEmoji { + return rightSize.height + 3 + } + if isForceRightLine { + return rightSize.height + } + if unsupported { + return rightSize.height + } + if rightSize.width + insetBetweenContentAndDate + bubbleDefaultInnerInset + contentSize.width + 30 > self.width { + // return rightSize.height + } + + if let webpageLayout = webpageLayout { + if let webpageLayout = webpageLayout as? WPArticleLayout { + if let textLayout = webpageLayout.textLayout { + if webpageLayout.hasInstantPage { + return rightSize.height + 4 + } + if textLayout.lines.count > 1, let line = textLayout.lines.last, line.frame.width > realContentSize.width - (rightSize.width + insetBetweenContentAndDate) { + return rightSize.height + } + if let _ = webpageLayout.imageSize, webpageLayout.isFullImageSize || textLayout.layoutSize.height - 10 <= webpageLayout.contrainedImageSize.height { + return rightSize.height + } + if actionButtonText != nil { + return rightSize.height + 4 + } + if webpageLayout.groupLayout != nil { + return rightSize.height + } + } else { + return rightSize.height + } + + + } else if webpageLayout is WPMediaLayout { + return rightSize.height + } + return nil + } + + if textLayout.lines.count == 1 { + if contentOffset.x + textLayout.layoutSize.width - (rightSize.width + insetBetweenContentAndDate) > width { + return rightSize.height + } + } else if let line = textLayout.lines.last, max(realContentSize.width, maxTitleWidth) < line.frame.width + (rightSize.width + insetBetweenContentAndDate) { + return rightSize.height + } + return nil + } + override func makeContentSize(_ width: CGFloat) -> NSSize { let size:NSSize = super.makeContentSize(width) - textLayout.measure(width: width) - webpageLayout?.measure(width: min(width, 400)) + webpageLayout?.measure(width: min(width, 380)) + let textBlockWidth: CGFloat = isBubbled ? max((webpageLayout?.size.width ?? width), min(240, width)) : width + textLayout.measure(width: textBlockWidth, isBigEmoji: containsBigEmoji) + var contentSize = NSMakeSize(max(webpageLayout?.contentRect.width ?? 0, textLayout.layoutSize.width), size.height + textLayout.layoutSize.height) if let webpageLayout = webpageLayout { - contentSize.height += webpageLayout.size.height + defaultContentTopOffset + contentSize.height += webpageLayout.size.height + defaultContentInnerInset contentSize.width = max(webpageLayout.size.width, contentSize.width) + + } + if let _ = actionButtonText { + contentSize.height += 36 } - return contentSize } - override func menuItems() -> Signal<[ContextMenuItem], Void> { - var items = super.menuItems() + + override var instantlyResize: Bool { + return true + } + + override var bubbleFrame: NSRect { + var frame = super.bubbleFrame + + + if isBubbleFullFilled { + frame.size.width = contentSize.width + additionBubbleInset + return frame + } + + if replyMarkupModel != nil, webpageLayout == nil, textLayout.layoutSize.width < 200 { + frame.size.width = max(blockWidth, frame.width) + } + return frame + } + + + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items = super.menuItems(in: location) let text = messageText.string - let account = self.account! + let context = self.context + + var media: Media? = webpageLayout?.content.file ?? webpageLayout?.content.image - if let file = webpageLayout?.content.file { - items = items |> mapToSignal { items -> Signal<[ContextMenuItem], Void> in + if let groupLayout = (webpageLayout as? WPArticleLayout)?.groupLayout { + if let message = groupLayout.message(at: location) { + media = message.media.first + } + } + + if let file = media as? TelegramMediaFile, let message = message { + items = items |> mapToSignal { items -> Signal<[ContextMenuItem], NoError> in var items = items - return account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue |> mapToSignal { data in + return context.account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue |> mapToSignal { data in if data.complete { - items.append(ContextMenuItem(tr(.contextCopyMedia), handler: { - saveAs(file, account: account) + items.append(ContextMenuItem(L10n.contextCopyMedia, handler: { + saveAs(file, account: context.account) })) } - if file.isSticker, let fileId = file.id { - return account.postbox.modify { modifier -> [ContextMenuItem] in - let saved = getIsStickerSaved(modifier: modifier, fileId: fileId) - items.append(ContextMenuItem( !saved ? tr(.chatContextAddFavoriteSticker) : tr(.chatContextRemoveFavoriteSticker), handler: { + if file.isStaticSticker, let fileId = file.id { + return context.account.postbox.transaction { transaction -> [ContextMenuItem] in + let saved = getIsStickerSaved(transaction: transaction, fileId: fileId) + items.append(ContextMenuItem( !saved ? L10n.chatContextAddFavoriteSticker : L10n.chatContextRemoveFavoriteSticker, handler: { if !saved { - _ = addSavedSticker(postbox: account.postbox, network: account.network, file: file).start() + _ = addSavedSticker(postbox: context.account.postbox, network: context.account.network, file: file).start() } else { - _ = removeSavedSticker(postbox: account.postbox, mediaId: fileId).start() + _ = removeSavedSticker(postbox: context.account.postbox, mediaId: fileId).start() } })) return items } + } else if file.isVideo && file.isAnimated { + items.append(ContextMenuItem(L10n.messageContextSaveGif, handler: { + let _ = addSavedGif(postbox: context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(message), media: file)).start() + })) } - return .single(items) } } - } else if let image = webpageLayout?.content.image { - items = items |> mapToSignal { items -> Signal<[ContextMenuItem], Void> in + } else if let image = media as? TelegramMediaImage { + items = items |> mapToSignal { items -> Signal<[ContextMenuItem], NoError> in var items = items if let resource = image.representations.last?.resource { - return account.postbox.mediaBox.resourceData(resource) |> take(1) |> deliverOnMainQueue |> map { data in + return context.account.postbox.mediaBox.resourceData(resource) |> take(1) |> deliverOnMainQueue |> map { data in if data.complete { - items.append(ContextMenuItem(tr(.galleryContextCopyToClipboard), handler: { + items.append(ContextMenuItem(L10n.galleryContextCopyToClipboard, handler: { if let path = link(path: data.path, ext: "jpg") { let pb = NSPasteboard.general pb.clearContents() pb.writeObjects([NSURL(fileURLWithPath: path)]) } })) - items.append(ContextMenuItem(tr(.contextCopyMedia), handler: { + items.append(ContextMenuItem(L10n.contextCopyMedia, handler: { savePanel(file: data.path, ext: "jpg", for: mainWindow) })) } @@ -272,97 +732,250 @@ class ChatMessageItem: ChatRowItem { } - return items |> map { items in + return items |> deliverOnMainQueue |> map { [weak self] items in var items = items - items.insert(ContextMenuItem(tr(.textCopy), handler: { - copyToClipboard(text) - }), at: 1) + + var index: Int? = nil + for i in 0 ..< items.count { + if items[i].title == tr(L10n.messageContextCopyMessageLink1) { + index = i + } + } + + if index == nil { + for i in 0 ..< items.count { + if items[i].title == L10n.messageContextReply1 { + index = i + 1 + } + } + } + + let insert = min(index ?? 0, items.count) + items.insert(ContextMenuItem(L10n.textCopyText, handler: { [weak self] in + if let string = self?.textLayout.attributedString { + if !globalLinkExecutor.copyAttributedString(string) { + copyToClipboard(string.string) + } + } + }), at: insert) + + + + if let view = self?.view as? ChatRowView, let textView = view.selectableTextViews.first, let window = textView.window, index == nil { + let point = textView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let layout = textView.layout { + if let (link, _, range, _) = layout.link(at: point) { + var text:String = layout.attributedString.string.nsstring.substring(with: range) + if let link = link as? inAppLink { + if case let .external(link, _) = link { + text = link + } + } + + for i in 0 ..< items.count { + if items[i].title == tr(L10n.messageContextCopyMessageLink1) { + items.remove(at: i) + break + } + } + + items.insert(ContextMenuItem(tr(L10n.messageContextCopyMessageLink1), handler: { + copyToClipboard(text) + }), at: min(1, items.count)) + + + } + } + } + if let content = self?.webpageLayout?.content, content.type == "proxy" { + items.insert(ContextMenuItem(L10n.chatCopyProxyConfiguration, handler: { + copyToClipboard(content.url) + }), at: items.isEmpty ? 0 : 1) + } return items } } + deinit { + youtubeExternalLoader.dispose() + } + override func viewClass() -> AnyClass { return ChatMessageView.self } - static func applyMessageEntities(with attributes:[MessageAttribute], for text:String, account:Account, fontSize: CGFloat, openInfo:@escaping (PeerId, Bool, MessageId?, ChatInitialAction?)->Void, botCommand:@escaping (String)->Void, hashtag:@escaping (String)->Void, applyProxy:@escaping (ProxySettings)->Void) -> NSAttributedString { - var entities: TextEntitiesMessageAttribute? + static func applyMessageEntities(with attributes:[MessageAttribute], for text:String, context: AccountContext, fontSize: CGFloat, openInfo:@escaping (PeerId, Bool, MessageId?, ChatInitialAction?)->Void, botCommand:@escaping (String)->Void = { _ in }, hashtag:@escaping (String)->Void = { _ in }, applyProxy:@escaping (ProxyServerSettings)->Void = { _ in }, textColor: NSColor = theme.colors.text, linkColor: NSColor = theme.colors.link, monospacedPre:NSColor = theme.colors.monospacedPre, monospacedCode: NSColor = theme.colors.monospacedCode, mediaDuration: Double? = nil, timecode: @escaping(Double?)->Void = { _ in }, openBank: @escaping(String)->Void = { _ in }) -> NSAttributedString { + var entities: [MessageTextEntity] = [] for attribute in attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { - entities = attribute + entities = attribute.entities break } } + var fontAttributes: [NSRange: ChatTextFontAttributes] = [:] - let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: NSFont.normal(.custom(fontSize)), NSAttributedStringKey.foregroundColor: theme.colors.text]) - if let entities = entities { - var nsString: NSString? - for entity in entities.entities { - let range = string.trimRange(NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)) - switch entity.type { - case .Url: - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - if nsString == nil { - nsString = text as NSString - } - let link = inApp(for:nsString!.substring(with: range) as NSString, account:account, openInfo:openInfo, applyProxy: applyProxy) - string.addAttribute(NSAttributedStringKey.link, value: link, range: range) - case .Email: - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - if nsString == nil { - nsString = text as NSString - } - string.addAttribute(NSAttributedStringKey.link, value: inAppLink.external(link: "mailto:\(nsString!.substring(with: range))", false), range: range) - case let .TextUrl(url): - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - if nsString == nil { - nsString = text as NSString - } + + let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: NSFont.normal(fontSize), NSAttributedString.Key.foregroundColor: textColor]) + + let new = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: mediaDuration) + var nsString: NSString? + entities = entities + (new ?? []) + for entity in entities { + let range = string.trimRange(NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)) + + switch entity.type { + case .Url: + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + if nsString == nil { + nsString = text as NSString + } + let link = inApp(for:nsString!.substring(with: range) as NSString, context:context, openInfo:openInfo, applyProxy: applyProxy) + string.addAttribute(NSAttributedString.Key.link, value: link, range: range) + case .Email: + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + if nsString == nil { + nsString = text as NSString + } + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.external(link: "mailto:\(nsString!.substring(with: range))", false), range: range) + case let .TextUrl(url): + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + if nsString == nil { + nsString = text as NSString + } + + string.addAttribute(NSAttributedString.Key.link, value: inApp(for: url as NSString, context: context, openInfo: openInfo, hashtag: hashtag, command: botCommand, applyProxy: applyProxy, confirm: nsString?.substring(with: range).trimmed != url), range: range) + case .Bold: + if let fontAttribute = fontAttributes[range] { + fontAttributes[range] = fontAttribute.union(.bold) + } else { + fontAttributes[range] = .bold + } + case .Italic: + if let fontAttribute = fontAttributes[range] { + fontAttributes[range] = fontAttribute.union(.italic) + } else { + fontAttributes[range] = .italic + } + case .Mention: + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + if nsString == nil { + nsString = text as NSString + } + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.followResolvedName(link: nsString!.substring(with: range), username: nsString!.substring(with: range), postId:nil, context:context, action:nil, callback: openInfo), range: range) + case let .TextMention(peerId): + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.peerInfo(link: "", peerId: peerId, action:nil, openChat: false, postId: nil, callback: openInfo), range: range) + case .BotCommand: + string.addAttribute(NSAttributedString.Key.foregroundColor, value: textColor, range: range) + if nsString == nil { + nsString = text as NSString + } + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.botCommand(nsString!.substring(with: range), botCommand), range: range) + case .Code: + string.addAttribute(.preformattedCode, value: 4.0, range: range) + if let fontAttribute = fontAttributes[range] { + fontAttributes[range] = fontAttribute.union(.monospace) + } else { + fontAttributes[range] = .monospace + } + string.addAttribute(NSAttributedString.Key.foregroundColor, value: monospacedCode, range: range) + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.code(text.nsstring.substring(with: range), { link in + copyToClipboard(link) + context.sharedContext.bindings.showControllerToaster(ControllerToaster(text: L10n.shareLinkCopied), true) + }), range: range) + case .Pre: + string.addAttribute(.preformattedCode, value: 4.0, range: range) + if let fontAttribute = fontAttributes[range] { + fontAttributes[range] = fontAttribute.union(.monospace) + } else { + fontAttributes[range] = .monospace + } + // string.addAttribute(.preformattedPre, value: 4.0, range: range) + string.addAttribute(NSAttributedString.Key.foregroundColor, value: monospacedPre, range: range) + case .Hashtag: + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + if nsString == nil { + nsString = text as NSString + } + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.hashtag(nsString!.substring(with: range), hashtag), range: range) + if let color = NSColor(hexString: nsString!.substring(with: range)) { - string.addAttribute(NSAttributedStringKey.link, value: inApp(for: url as NSString, account: account, openInfo: openInfo, hashtag: hashtag, command: botCommand, applyProxy: applyProxy, confirm: true), range: range) - case .Bold: - string.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(fontSize)), range: range) - case .Italic: - string.addAttribute(NSAttributedStringKey.font, value: NSFontManager.shared.convert(.normal(.custom(fontSize)), toHaveTrait: .italicFontMask), range: range) - case .Mention: - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - if nsString == nil { - nsString = text as NSString + struct RunStruct { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat } - string.addAttribute(NSAttributedStringKey.link, value: inAppLink.followResolvedName(username:nsString!.substring(with: range), postId:nil, account:account, action:nil, callback: openInfo), range: range) - case let .TextMention(peerId): - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - string.addAttribute(NSAttributedStringKey.link, value: inAppLink.peerInfo(peerId: peerId, action:nil, openChat: false, postId: nil, callback: openInfo), range: range) - case .BotCommand: - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) - if nsString == nil { - nsString = text as NSString - } - string.addAttribute(NSAttributedStringKey.link, value: inAppLink.botCommand(nsString!.substring(with: range), botCommand), range: range) - case .Code: - string.addAttribute(.preformattedCode, value: 4.0, range: range) - string.addAttribute(NSAttributedStringKey.font, value: NSFont.code(.custom(fontSize)), range: range) - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.redUI, range: range) - case .Pre: - string.addAttribute(.preformattedPre, value: 4.0, range: range) - string.addAttribute(NSAttributedStringKey.font, value: NSFont.code(.custom(fontSize)), range: range) - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.text, range: range) - case .Hashtag: - string.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) + + let dimensions = NSMakeSize(theme.fontSize + 6, theme.fontSize + 6) + let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) + extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.width)) + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in + }, 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 key = kCTRunDelegateAttributeName as String + let attrDictionaryDelegate:[NSAttributedString.Key : Any] = [NSAttributedString.Key(key): delegate as Any, .hexColorMark : color, .hexColorMarkDimensions: dimensions] + + string.addAttributes(attrDictionaryDelegate, range: NSMakeRange(range.upperBound - 1, 1)) + } + + case .Strikethrough: + string.addAttribute(NSAttributedString.Key.strikethroughStyle, value: true, range: range) + case .Underline: + string.addAttribute(NSAttributedString.Key.underlineStyle, value: true, range: range) + case .BankCard: + if nsString == nil { + nsString = text as NSString + } + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.callback(nsString!.substring(with: range), { bankCard in + openBank(bankCard) + }), range: range) + case let .Custom(type): + if type == ApplicationSpecificEntityType.Timecode { + string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(NSAttributedStringKey.link, value: inAppLink.hashtag(nsString!.substring(with: range), hashtag), range: range) - break - default: - break + string.addAttribute(NSAttributedString.Key.link, value: inAppLink.callback(nsString!.substring(with: range), { code in + timecode(parseTimecodeString(code)) + }), range: range) + } + default: + break } - } + for (range, fontAttributes) in fontAttributes { + var font: NSFont? + if fontAttributes.contains(.blockQuote) { + font = .code(fontSize) + } else if fontAttributes == [.bold, .italic] { + font = .boldItalic(fontSize) + } else if fontAttributes == [.bold] { + font = .bold(fontSize) + } else if fontAttributes == [.italic] { + font = .italic(fontSize) + } else if fontAttributes == [.monospace] { + font = .code(fontSize) + } + if let font = font { + string.addAttribute(.font, value: font, range: range) + } + } + return string.copy() as! NSAttributedString } } diff --git a/Telegram-Mac/ChatMessageThrottledProcessingManager.swift b/Telegram-Mac/ChatMessageThrottledProcessingManager.swift index 923e026e89..f2647642ba 100644 --- a/Telegram-Mac/ChatMessageThrottledProcessingManager.swift +++ b/Telegram-Mac/ChatMessageThrottledProcessingManager.swift @@ -7,11 +7,12 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit + +private let queue = Queue(name: "ChatMessageThrottledProcessingManager") final class ChatMessageThrottledProcessingManager { - private let queue = Queue(target: Queue.concurrentBackgroundQueue()) private let delay: TimeInterval init(delay: TimeInterval = 1.0) { @@ -20,18 +21,18 @@ final class ChatMessageThrottledProcessingManager { var process: ((Set) -> Void)? - private var timer: SwiftSignalKitMac.Timer? + private var timer: SwiftSignalKit.Timer? private var processed = Set() private var buffer = Set() func setProcess(process: @escaping (Set) -> Void) { - self.queue.async { + queue.async { self.process = process } } func add(_ messageIds: [MessageId]) { - self.queue.async { + queue.async { for id in messageIds { if !self.processed.contains(id) { self.processed.insert(id) @@ -41,9 +42,9 @@ final class ChatMessageThrottledProcessingManager { if self.timer == nil { var completionImpl: (() -> Void)? - let timer = SwiftSignalKitMac.Timer(timeout: self.delay, repeat: false, completion: { + let timer = SwiftSignalKit.Timer(timeout: self.delay, repeat: false, completion: { completionImpl?() - }, queue: self.queue) + }, queue: queue) completionImpl = { [weak self, weak timer] in if let strongSelf = self { if let timer = timer, strongSelf.timer === timer { diff --git a/Telegram-Mac/ChatMessageView.swift b/Telegram-Mac/ChatMessageView.swift index 81be3be4ed..e0a24ea397 100644 --- a/Telegram-Mac/ChatMessageView.swift +++ b/Telegram-Mac/ChatMessageView.swift @@ -8,12 +8,35 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -class ChatMessageView: ChatRowView { - private var text:TextView = TextView() - - private var webpageContent:WPContentView? +import SwiftSignalKit +class ChatMessageView: ChatRowView, ModalPreviewRowViewProtocol { + + + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let webpageContent = webpageContent { + return webpageContent.fileAtPoint(convert(point, from: self)) + } + + return nil + } + + override func forceClick(in location: NSPoint) { + if previewMediaIfPossible() { + + } else { + super.forceClick(in: location) + } + } + override func previewMediaIfPossible() -> Bool { + return webpageContent?.previewMediaIfPossible() ?? false + } + + private let text:TextView = TextView() + + private(set) var webpageContent:WPContentView? + private var actionButton: TitleButton? override func draw(_ dirtyRect: NSRect) { // Drawing code here. @@ -22,18 +45,27 @@ class ChatMessageView: ChatRowView { required init(frame frameRect: NSRect) { super.init(frame: frameRect) + // self.layerContentsRedrawPolicy = .never self.addSubview(text) } override func layout() { super.layout() if let item = self.item as? ChatMessageItem { - self.text.update(item.textLayout) - + if let webpageLayout = item.webpageLayout { - webpageContent?.frame = NSMakeRect(0, text.frame.maxY + item.defaultContentTopOffset, webpageLayout.size.width, webpageLayout.size.height) + webpageContent?.frame = NSMakeRect(0, text.frame.maxY + item.defaultContentInnerInset, webpageLayout.size.width, webpageLayout.size.height) + + if let webpageContent = webpageContent, let actionButton = actionButton { + actionButton.setFrameOrigin(0, webpageContent.frame.maxY + 6) + } + } else { + if let actionButton = actionButton { + actionButton.setFrameOrigin(0, contentView.frame.height - actionButton.frame.height) + } } - } + + } } override func canStartTextSelecting(_ event: NSEvent) -> Bool { @@ -54,10 +86,27 @@ class ChatMessageView: ChatRowView { // } return views } + + override func updateMouse() { + super.updateMouse() + webpageContent?.updateMouse() + } + + override func canMultiselectTextIn(_ location: NSPoint) -> Bool { + let point = self.contentView.convert(location, from: nil) + if let webpageContent = webpageContent { + return !NSPointInRect(point, webpageContent.frame) + } + return true + } override func set(item:TableRowItem, animated:Bool = false) { if let item = item as? ChatMessageItem { + + self.text.update(item.textLayout) + + if let webpageLayout = item.webpageLayout { let updated = webpageContent == nil || !webpageContent!.isKind(of: webpageLayout.viewClass()) @@ -72,21 +121,63 @@ class ChatMessageView: ChatRowView { webpageContent?.removeFromSuperview() webpageContent = nil } + + + if let text = item.actionButtonText { + if actionButton == nil { + actionButton = TitleButton() + actionButton?.layer?.cornerRadius = .cornerRadius + actionButton?.layer?.borderWidth = 1 + actionButton?.disableActions() + actionButton?.set(font: .normal(.text), for: .Normal) + addSubview(actionButton!) + } + actionButton?.removeAllHandlers() + actionButton?.set(handler: { [weak item] _ in + item?.invokeAction() + }, for: .Click) + actionButton?.set(text: text, for: .Normal) + actionButton?.layer?.borderColor = item.wpPresentation.activity.cgColor + actionButton?.set(color: item.wpPresentation.activity, for: .Normal) + _ = actionButton?.sizeToFit(NSZeroSize, NSMakeSize(item.actionButtonWidth, 30), thatFit: true) + + } else { + actionButton?.removeFromSuperview() + actionButton = nil + } + } super.set(item: item, animated: animated) } + override func clickInContent(point: NSPoint) -> Bool { + guard let item = item as? ChatMessageItem else {return true} + + let point = text.convert(point, from: self) + let layout = item.textLayout + + let index = layout.findIndex(location: point) + return index >= 0 && point.x < layout.lines[index].frame.maxX + } - - override var interactionContentView: NSView { + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { if let webpageContent = webpageContent { - return webpageContent.interactionContentView + return webpageContent.interactionContentView(for: innerId, animateIn: animateIn) } return self } + override func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + let main = super.convertWindowPointToContent(point) + + if let webpageContent = webpageContent, NSPointInRect(main, webpageContent.frame) { + return webpageContent.convertWindowPointToContent(point) + } else { + return main + } + } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") diff --git a/Telegram-Mac/ChatMusicContentView.swift b/Telegram-Mac/ChatMusicContentView.swift index 720d6f439c..3ebd98e7e1 100644 --- a/Telegram-Mac/ChatMusicContentView.swift +++ b/Telegram-Mac/ChatMusicContentView.swift @@ -7,26 +7,136 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit class ChatMusicContentView: ChatAudioContentView { - - override func update(with media: Media, size: NSSize, account: Account, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool) { - super.update(with: media, size: size, account: account, parent: parent, table: table, parameters: parameters, animated: animated) + private let imageView: TransformImageView = TransformImageView(frame: NSMakeRect(0, 0, 40, 40)) + private var playAnimationView: PeerMediaPlayerAnimationView? + private let partHeaderDisposable = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView, positioned: .below, relativeTo: progressView) + progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blackTransparent, foregroundColor: .white, icon: nil) + } + + override var fetchStatus: MediaResourceStatus? { + didSet { + if let fetchStatus = fetchStatus { + switch fetchStatus { + case let .Fetching(_, progress): + progressView.state = .Fetching(progress: progress, force: false) + progressView.isHidden = false + case .Remote: + progressView.isHidden = true + case .Local: + progressView.isHidden = true + } + } + } + } + + override func viewDidMoveToWindow() { + if window != nil { + if let playAnimationView = playAnimationView { + if playAnimationView.isPlaying { + playAnimationView.animateToPlaying() + } else { + playAnimationView.animateToPaused() + } + } + } else { + playAnimationView?.animateToPaused() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags) if let parameters = parameters as? ChatMediaMusicLayoutParameters { textView.update(parameters.nameLayout) durationView.update(parameters.durationLayout) } + + let iconSize = CGSize(width: 40, height: 40) + let imageCorners = ImageCorners(radius: 20) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: NSEdgeInsets()) + + let file = media as! TelegramMediaFile + + let resource: TelegramMediaResource + if file.previewRepresentations.isEmpty { + resource = ExternalMusicAlbumArtResource(title: file.musicText.0, performer: file.musicText.1, isThumbnail: true) + } else { + resource = file.previewRepresentations.first!.resource + } + imageView.layer?.contents = theme.icons.chatMusicPlaceholder + + + let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(iconSize), resource: resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + + imageView.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor, positionFlags: positionFlags), clearInstantly: false) + + imageView.setSignal( chatMessagePhotoThumbnail(account: context.account, imageReference: parent != nil ? ImageMediaReference.message(message: MessageReference(parent!), media: image) : ImageMediaReference.standalone(media: image)), animate: true, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale, positionFlags: positionFlags) + } + }) + + imageView.set(arguments: arguments) + // imageView.layer?.cornerRadius = 20 } + override func checkState() { + if let parent = parent, let controller = globalAudio, let song = controller.currentSong { + if song.entry.isEqual(to: parent) { + if playAnimationView == nil { + playAnimationView = PeerMediaPlayerAnimationView() + playAnimationView?.layer?.cornerRadius = 20 + imageView.addSubview(playAnimationView!) + } + if case .playing = song.state { + playAnimationView?.isPlaying = true + } else if case .stoped = song.state { + playAnimationView?.removeFromSuperview() + playAnimationView = nil + } else { + playAnimationView?.isPlaying = false + } + } else { + playAnimationView?.removeFromSuperview() + playAnimationView = nil + } + } else { + playAnimationView?.removeFromSuperview() + playAnimationView = nil + } + } + + override func preloadStreamblePart() { + if let context = context { + if let media = media as? TelegramMediaFile { + let reference = parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: media) : FileMediaReference.standalone(media: media) + partHeaderDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(media.resource), range: (0 ..< 500 * 1024, .default), statsCategory: .audio).start()) + + } + } + } + + deinit { + partHeaderDisposable.dispose() + } override func layout() { super.layout() - let center = floorToScreenPixels(frame.height / 2.0) + let center = floorToScreenPixels(backingScaleFactor, frame.height / 2.0) textView.setFrameOrigin(leftInset, center - textView.frame.height - 2) durationView.setFrameOrigin(leftInset, center + 2) } diff --git a/Telegram-Mac/ChatMusicRowItem.swift b/Telegram-Mac/ChatMusicRowItem.swift index bea4cfac5c..af47f5fc7c 100644 --- a/Telegram-Mac/ChatMusicRowItem.swift +++ b/Telegram-Mac/ChatMusicRowItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ChatMediaMusicLayoutParameters : ChatMediaLayoutParameters { let resource: TelegramMediaResource let title:String? @@ -20,7 +21,7 @@ class ChatMediaMusicLayoutParameters : ChatMediaLayoutParameters { let showPlayer:(APController) -> Void let durationLayout:TextViewLayout let sizeLayout:TextViewLayout - init(nameLayout:TextViewLayout, durationLayout:TextViewLayout, sizeLayout:TextViewLayout, resource:TelegramMediaResource, isWebpage: Bool, title:String?, performer:String?, showPlayer:@escaping(APController) -> Void) { + init(nameLayout:TextViewLayout, durationLayout:TextViewLayout, sizeLayout:TextViewLayout, resource:TelegramMediaResource, isWebpage: Bool, title:String?, performer:String?, showPlayer:@escaping(APController) -> Void, presentation: ChatMediaPresentation, media: Media, automaticDownload: Bool) { self.nameLayout = nameLayout self.sizeLayout = sizeLayout self.durationLayout = durationLayout @@ -29,16 +30,41 @@ class ChatMediaMusicLayoutParameters : ChatMediaLayoutParameters { self.title = title self.performer = performer self.resource = resource + super.init(presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: AutoplayMediaPreferences.defaultSettings) } + var file: TelegramMediaFile { + return media as! TelegramMediaFile + } + + override func makeLabelsForWidth(_ width: CGFloat) { + nameLayout.measure(width: width - 40) + durationLayout.measure(width: width - 40) + sizeLayout.measure(width: width - 40) + } } class ChatMusicRowItem: ChatMediaItem { - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { - super.init(initialSize, chatInteraction, account, object) - self.parameters = ChatMediaLayoutParameters.layout(for: (self.media as! TelegramMediaFile), isWebpage: chatInteraction.isLogInteraction, chatInteraction: chatInteraction) + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) + + + self.parameters = ChatMediaLayoutParameters.layout(for: (self.media as! TelegramMediaFile), isWebpage: chatInteraction.isLogInteraction, chatInteraction: chatInteraction, presentation: .make(for: object.message!, account: context.account, renderType: object.renderType), automaticDownload: downloadSettings.isDownloable(object.message!), isIncoming: object.message!.isIncoming(context.account, object.renderType == .bubble), autoplayMedia: object.autoplayMedia) + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if isForceRightLine { + return rightSize.height + } + if let parameters = parameters as? ChatMediaMusicLayoutParameters { + if parameters.durationLayout.layoutSize.width + 50 + rightSize.width + insetBetweenContentAndDate > contentSize.width { + return rightSize.height + } + } + + return super.additionalLineForDateInBubbleState } override var instantlyResize: Bool { @@ -47,10 +73,8 @@ class ChatMusicRowItem: ChatMediaItem { override func makeContentSize(_ width: CGFloat) -> NSSize { if let parameters = parameters as? ChatMediaMusicLayoutParameters { - parameters.nameLayout.measure(width: width - 20) - parameters.durationLayout.measure(width: width - 20) - parameters.sizeLayout.measure(width: width - 20) - return NSMakeSize(parameters.nameLayout.layoutSize.width + 50, 40) + parameters.makeLabelsForWidth(width) + return NSMakeSize(max(parameters.nameLayout.layoutSize.width, parameters.durationLayout.layoutSize.width) + 50, 40) } return NSZeroSize } diff --git a/Telegram-Mac/ChatNavigateFailed.swift b/Telegram-Mac/ChatNavigateFailed.swift new file mode 100644 index 0000000000..9e69198159 --- /dev/null +++ b/Telegram-Mac/ChatNavigateFailed.swift @@ -0,0 +1,66 @@ +// +// ChatNavigateFailed.swift +// Telegram +// +// Created by Mikhail Filimonov on 20.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + + +class ChatNavigateFailed: ImageButton { + + private let context:AccountContext + init(_ context: AccountContext) { + self.context = context + super.init() + autohighlight = false + set(image: theme.icons.chat_failed_scroller, for: .Normal) + set(image: theme.icons.chat_failed_scroller_active, for: .Highlight) + self.setFrameSize(60,60) + + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.1) + shadow.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + set(image: theme.icons.chat_failed_scroller, for: .Normal) + set(image: theme.icons.chat_failed_scroller_active, for: .Highlight) + } + + func updateCount(_ count: Int) { + //needsLayout = true + } + + override func scrollWheel(with event: NSEvent) { + + } + + override func layout() { + super.layout() + } + + deinit { + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ChatNavigateScroller.swift b/Telegram-Mac/ChatNavigateScroller.swift index 54e68f139a..3c3fb7d77f 100644 --- a/Telegram-Mac/ChatNavigateScroller.swift +++ b/Telegram-Mac/ChatNavigateScroller.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ChatNavigateScroller: ImageButton { @@ -18,22 +19,20 @@ class ChatNavigateScroller: ImageButton { private let disposable:MetaDisposable = MetaDisposable() private var badge:BadgeNode? private var badgeView:View = View() - private let peerId:PeerId - private let account:Account - init(_ account:Account, _ peerId:PeerId) { - self.account = account - self.peerId = peerId + private let context:AccountContext + init(_ context: AccountContext, _ chatLocation: ChatLocation) { + self.context = context super.init() autohighlight = false set(image: theme.icons.chatScrollUp, for: .Normal) set(image: theme.icons.chatScrollUpActive, for: .Highlight) self.setFrameSize(60,60) - self.disposable.set((account.postbox.unreadMessageCountsView(items: [.peer(peerId)]) |> deliverOnMainQueue).start(next: { [weak self] unreadView in + self.disposable.set((context.account.postbox.unreadMessageCountsView(items: [chatLocation.unreadMessageCountsItem]) |> deliverOnMainQueue).start(next: { [weak self] unreadView in if let strongSelf = self { - let count = unreadView.count(for: .peer(peerId)) ?? 0 + let count = unreadView.count(for: chatLocation.unreadMessageCountsItem) ?? 0 if count > 0 { - strongSelf.badge = BadgeNode(.initialize(string: Int(count).prettyNumber, color: .white, font: .bold(.small)), theme.colors.blueUI) + strongSelf.badge = BadgeNode(.initialize(string: Int(count).prettyNumber, color: theme.colors.underSelectedColor, font: .bold(.small)), theme.colors.accent) strongSelf.badge!.view = strongSelf.badgeView strongSelf.badgeView.setFrameSize(strongSelf.badge!.size) strongSelf.addSubview(strongSelf.badgeView) @@ -44,13 +43,26 @@ class ChatNavigateScroller: ImageButton { } })) + + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) set(image: theme.icons.chatScrollUp, for: .Normal) set(image: theme.icons.chatScrollUpActive, for: .Highlight) - badge?.fillColor = theme.colors.blueUI + badge?.fillColor = theme.colors.accent + + if theme.colors.chatBackground == theme.colors.background && theme.colors.isDark { + + + } + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.1) + shadow.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow } override func scrollWheel(with event: NSEvent) { diff --git a/Telegram-Mac/ChatNavigationMention.swift b/Telegram-Mac/ChatNavigationMention.swift index 4fa0d158cc..bce6fd4f66 100644 --- a/Telegram-Mac/ChatNavigationMention.swift +++ b/Telegram-Mac/ChatNavigationMention.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChatNavigationMention: ImageButton { @@ -24,11 +25,17 @@ class ChatNavigationMention: ImageButton { set(image: theme.icons.chatMentionActive, for: .Highlight) self.setFrameSize(60,60) + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.1) + shadow.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow + } func updateCount(_ count: Int32) { if count > 0 { - badge = BadgeNode(.initialize(string: Int(count).prettyNumber, color: .white, font: .bold(.small)), theme.colors.blueUI) + badge = BadgeNode(.initialize(string: Int(count).prettyNumber, color: .white, font: .bold(.small)), theme.colors.accent) badge!.view = badgeView badgeView.setFrameSize(badge!.size) addSubview(badgeView) @@ -38,8 +45,9 @@ class ChatNavigationMention: ImageButton { needsLayout = true } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) set(image: theme.icons.chatMention, for: .Normal) set(image: theme.icons.chatMentionActive, for: .Highlight) } diff --git a/Telegram-Mac/ChatPollItem.swift b/Telegram-Mac/ChatPollItem.swift new file mode 100644 index 0000000000..791795c903 --- /dev/null +++ b/Telegram-Mac/ChatPollItem.swift @@ -0,0 +1,1397 @@ +// +// ChatPollItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 18/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import SyncCore + +private enum PeerAvatarReference : Equatable { + static func == (lhs: PeerAvatarReference, rhs: PeerAvatarReference) -> Bool { + switch lhs { + case let .image(lhsPeer, rep): + if case .image(let rhsPeer, rep) = rhs { + return lhsPeer.isEqual(rhsPeer) + } else { + return false + } + } + } + + case image(Peer, TelegramMediaImageRepresentation?) + + var peerId: PeerId { + switch self { + case let .image(value, _): + return value.id + } + } +} + +private extension PeerAvatarReference { + init(peer: Peer) { + self = .image(peer, peer.smallProfileImage) + } +} + +func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) -> Bool { + if poll.isClosed { + return true + } else if let deadlineTimeout = poll.deadlineTimeout, message.id.namespace == Namespaces.Message.Cloud { + let startDate: Int32 + if let forwardInfo = message.forwardInfo { + startDate = forwardInfo.date + } else { + startDate = message.timestamp + } + + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if timestamp >= startDate + deadlineTimeout { + return true + } else { + return false + } + } else { + return false + } +} + + + +private let mergedImageSize: CGFloat = 16.0 +private let mergedImageSpacing: CGFloat = 15.0 + +private let avatarFont = NSFont.avatar(8.0) + +private final class MergedAvatarsView: Control { + private var peers: [PeerAvatarReference] = [] + private var images: [PeerId: CGImage] = [:] + private var disposables: [PeerId: Disposable] = [:] + + + deinit { + for (_, disposable) in self.disposables { + disposable.dispose() + } + } + + func update(context: AccountContext, peers: [Peer], message: Message?, synchronousLoad: Bool) { + var filteredPeers = Array(peers.map(PeerAvatarReference.init).prefix(3)) + + if filteredPeers != self.peers { + self.peers = filteredPeers + + var validImageIds: [PeerId] = [] + for peer in filteredPeers { + if case .image = peer { + validImageIds.append(peer.peerId) + } + } + + var removedImageIds: [PeerId] = [] + for (id, _) in self.images { + if !validImageIds.contains(id) { + removedImageIds.append(id) + } + } + var removedDisposableIds: [PeerId] = [] + for (id, disposable) in self.disposables { + if !validImageIds.contains(id) { + disposable.dispose() + removedDisposableIds.append(id) + } + } + for id in removedImageIds { + self.images.removeValue(forKey: id) + } + for id in removedDisposableIds { + self.disposables.removeValue(forKey: id) + } + for peer in filteredPeers { + switch peer { + case let .image(peer, representation): + if self.disposables[peer.id] == nil { + let signal = peerAvatarImage(account: context.account, photo: PeerPhoto.peer(peer, representation, peer.displayLetters, message), displayDimensions: NSMakeSize(mergedImageSize, mergedImageSize), scale: backingScaleFactor, font: avatarFont, synchronousLoad: synchronousLoad) + let disposable = (signal + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let strongSelf = self else { + return + } + if let image = image.0 { + strongSelf.images[peer.id] = image + strongSelf.setNeedsDisplay() + } + }) + self.disposables[peer.id] = disposable + } + } + } + self.setNeedsDisplay() + } + } + + override func draw(_ layer: CALayer, in context: CGContext) { + super.draw(layer, in: context) + + + context.setBlendMode(.copy) + context.setFillColor(NSColor.clear.cgColor) + context.fill(bounds) + + + context.setBlendMode(.copy) + + var currentX = mergedImageSize + mergedImageSpacing * CGFloat(self.peers.count - 1) - mergedImageSize + for i in (0 ..< self.peers.count).reversed() { + context.saveGState() + + context.translateBy(x: frame.width / 2.0, y: frame.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -frame.width / 2.0, y: -frame.height / 2.0) + + let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize)) + context.setFillColor(NSColor.clear.cgColor) + context.fillEllipse(in: imageRect.insetBy(dx: -1.0, dy: -1.0)) + + if let image = self.images[self.peers[i].peerId] { + context.draw(image, in: imageRect) + } else { + context.setFillColor(NSColor.gray.cgColor) + context.fillEllipse(in: imageRect) + } + + currentX -= mergedImageSpacing + context.restoreGState() + } + } +} + + + +extension TelegramMediaPoll { + var title: String { + if isClosed { + return L10n.chatPollTypeClosed + } else { + switch self.kind { + case .quiz: + switch self.publicity { + case .anonymous: + return L10n.chatPollTypeAnonymousQuiz + case .public: + return L10n.chatPollTypeQuiz + } + default: + switch self.publicity { + case .anonymous: + return L10n.chatPollTypeAnonymous + case .public: + return L10n.chatPollTypePublic + } + } + } + } + + var isMultiple: Bool { + switch kind { + case let .poll(multipleAnswers): + return multipleAnswers + default: + return false + } + } + var isQuiz: Bool { + switch kind { + case .poll: + return false + default: + return true + } + } +} + +private struct PercentCounterItem : Comparable { + var index: Int = 0 + var percent: Int = 0 + var remainder: Int = 0 + + static func <(lhs: PercentCounterItem, rhs: PercentCounterItem) -> Bool { + if lhs.remainder > rhs.remainder { + return true + } else if lhs.remainder < rhs.remainder { + return false + } + return lhs.percent < rhs.percent + } + +} + +private func adjustPercentCount(_ items: [PercentCounterItem], left: Int) -> [PercentCounterItem] { + var left = left + var items = items.sorted(by: <) + var i:Int = 0 + while i != items.count { + let item = items[i] + var j = i + 1 + loop: while j != items.count { + if items[j].percent != item.percent || items[j].remainder != item.remainder { + break loop + } + j += 1 + } + if items[i].remainder == 0 { + break + } + let equal = j - i + if equal <= left { + left -= equal + while i != j { + items[i].percent += 1 + i += 1 + } + } else { + i = j + } + } + return items +} + +func countNicePercent(votes:[Int], total: Int) -> [Int] { + var result:[Int] = Array(repeating: 0, count: votes.count) + var items:[PercentCounterItem] = Array(repeating: PercentCounterItem(), count: votes.count) + + guard total > 0 else { + return result + } + + let count = votes.count + + var left:Int = 100 + for i in 0 ..< votes.count { + let votes = votes[i] + items[i].index = i + items[i].percent = Int((Float(votes) * 100) / Float(total)) + items[i].remainder = (votes * 100) - (items[i].percent * total) + left -= items[i].percent + } + + if left > 0 && left <= count { + items = adjustPercentCount(items, left: left) + } + for item in items { + result[item.index] = item.percent + } + + return result +} + + + +private final class PollOption : Equatable { + let option: TelegramMediaPollOption + let nameText: TextViewLayout + let percent: Float? + let voteCount: Int32 + let realPercent: Float + let isSelected: Bool + let voted: Bool + let isIncoming: Bool + let isBubbled: Bool + let isLoading: Bool + let presentation: TelegramPresentationTheme + let contentSize: NSSize + let vote:(Control)-> Void + let isCorrect: Bool? + let isQuiz: Bool + let isMultipleSelected: Bool + init(option:TelegramMediaPollOption, nameText: TextViewLayout, percent: Float?, realPercent: Float, voteCount: Int32, isSelected: Bool, isIncoming: Bool, isBubbled: Bool, voted: Bool, isLoading: Bool, presentation: TelegramPresentationTheme, isCorrect: Bool?, isQuiz: Bool, isMultipleSelected: Bool, vote: @escaping(Control)->Void = { _ in }, contentSize: NSSize = NSZeroSize) { + self.option = option + self.nameText = nameText + self.percent = percent + self.realPercent = realPercent + self.isSelected = isSelected + self.voted = voted + self.presentation = presentation + self.isIncoming = isIncoming + self.isBubbled = isBubbled + self.isLoading = isLoading + self.vote = vote + self.voteCount = voteCount + self.contentSize = contentSize + self.isCorrect = isCorrect + self.isQuiz = isQuiz + self.isMultipleSelected = isMultipleSelected + } + + func withUpdatedLoading(_ isLoading: Bool) -> PollOption { + return PollOption(option: self.option, nameText: self.nameText, percent: self.percent, realPercent: self.realPercent, voteCount: self.voteCount, isSelected: self.isSelected, isIncoming: self.isIncoming, isBubbled: self.isBubbled, voted: self.voted, isLoading: isLoading, presentation: self.presentation, isCorrect: self.isCorrect, isQuiz: self.isQuiz, isMultipleSelected: self.isMultipleSelected, vote: self.vote, contentSize: self.contentSize) + } + func withUpdatedContentSize(_ contentSize: NSSize) -> PollOption { + return PollOption(option: self.option, nameText: self.nameText, percent: self.percent, realPercent: self.realPercent, voteCount: self.voteCount, isSelected: self.isSelected, isIncoming: self.isIncoming, isBubbled: self.isBubbled, voted: self.voted, isLoading: self.isLoading, presentation: self.presentation, isCorrect: self.isCorrect, isQuiz: self.isQuiz, isMultipleSelected: self.isMultipleSelected, vote: self.vote, contentSize: contentSize) + } + func withUpdatedSelected(_ isSelected: Bool) -> PollOption { + return PollOption(option: self.option, nameText: self.nameText, percent: self.percent, realPercent: self.realPercent, voteCount: self.voteCount, isSelected: isSelected, isIncoming: self.isIncoming, isBubbled: self.isBubbled, voted: self.voted, isLoading: self.isLoading, presentation: self.presentation, isCorrect: self.isCorrect, isQuiz: self.isQuiz, isMultipleSelected: self.isMultipleSelected, vote: self.vote, contentSize: self.contentSize) + } + + + static func ==(lhs: PollOption, rhs: PollOption) -> Bool { + return lhs.option == rhs.option && lhs.percent == rhs.percent && lhs.isSelected == rhs.isSelected && lhs.isIncoming == rhs.isIncoming && lhs.isLoading == rhs.isLoading && lhs.contentSize == rhs.contentSize && lhs.voted == rhs.voted && lhs.realPercent == rhs.realPercent && lhs.voteCount == rhs.voteCount && lhs.isCorrect == rhs.isCorrect && lhs.isQuiz == rhs.isQuiz && lhs.isMultipleSelected == rhs.isMultipleSelected + } + + + var leftOptionInset: CGFloat { + return 40 + PollOption.spaceBetweenTexts + } + var currentPercentImage: CGImage? { + return presentation.chat.pollPercentAnimatedIcon(isIncoming, isBubbled, value: Int(realPercent)) + } + + static var spaceBetweenTexts: CGFloat { + return 6 + } + static var spaceBetweenOptions: CGFloat { + return 5 + } + + var tooltip: String { + var totalOptionVotes = self.isQuiz ? L10n.chatQuizTooltipVotesCountable(Int(self.voteCount)) : L10n.chatPollTooltipVotesCountable(Int(self.voteCount)) + totalOptionVotes = totalOptionVotes.replacingOccurrences(of: "\(self.voteCount)", with: Int(self.voteCount).separatedNumber) + return self.voteCount == 0 ? (self.isQuiz ? L10n.chatQuizTooltipNoVotes : L10n.chatPollTooltipNoVotes) : totalOptionVotes + } + + func measure(width: CGFloat) -> NSSize { + nameText.measure(width: width - leftOptionInset) + let contentSize = NSMakeSize(nameText.layoutSize.width + leftOptionInset, 10 + nameText.layoutSize.height + PollOption.spaceBetweenOptions) + return contentSize + } +} + +class ChatPollItem: ChatRowItem { + private(set) fileprivate var titleText:TextViewLayout! + private(set) fileprivate var titleTypeText:TextViewLayout! + + private(set) fileprivate var options:[PollOption] = [] + private(set) fileprivate var totalVotesText:TextViewLayout? + + fileprivate let poll: TelegramMediaPoll + + var actionButtonText: String? { + if isBotQuiz { + return nil + } + if self.isClosed { + if poll.results.totalVoters == 0 || poll.results.totalVoters == nil { + return nil + } + if poll.publicity != .anonymous { + return L10n.chatPollViewResults + } else { + return nil + } + } + let hasSelected = options.contains(where: { $0.isSelected }) + if poll.isMultiple { + if !hasSelected { + return L10n.chatPollSubmitVote + } else { + if poll.publicity != .anonymous { + if hasSelected { + return L10n.chatPollViewResults + } + } + } + } else { + if poll.publicity != .anonymous { + if hasSelected { + return L10n.chatPollViewResults + } + } + } + return nil + } + + var actionButtonIsEnabled: Bool { + guard let message = message else { + return false + } + if message.flags.contains(.Failed) || message.flags.contains(.Sending) || message.flags.contains(.Unsent) { + return false + } + let hasSelected = options.contains(where: { $0.isMultipleSelected }) || options.contains(where: { $0.isSelected }) + if poll.isMultiple { + return hasSelected + } else { + return true + } + } + + var isClosed: Bool { + return isPollEffectivelyClosed(message: message!, poll: poll) + } + var isBotQuiz: Bool { + if let message = message { + if self.poll.isQuiz { + return messageMainPeer(message)?.isBot == true + } + } + return false + } + + override init(_ initialSize: NSSize, _ chatInteraction: ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + + let poll = object.message!.media[0] as! TelegramMediaPoll + self.poll = poll + + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) + + + + var options: [PollOption] = [] + + + var votes:[Int] = [] + + for option in poll.options { + let count = Int(poll.results.voters?.first(where: {$0.opaqueIdentifier == option.opaqueIdentifier})?.count ?? 0) + votes.append(count) + } + + + + let percents = countNicePercent(votes: votes, total: Int(poll.results.totalVoters ?? 0)) + let maximum: Int = percents.max() ?? 0 + + + for (i, option) in poll.options.enumerated() { + + let percent: Float? + let realPercent: Float + let isSelected: Bool + let isCorrect: Bool? + let voted = poll.results.voters?.first(where: {$0.selected}) != nil + + var votedCount: Int32 = 0 + if let vote = poll.results.voters?.first(where: {$0.opaqueIdentifier == option.opaqueIdentifier}), let totalVoters = poll.results.totalVoters, (voted || self.isClosed) { + percent = maximum == 0 ? 0 : (Float(percents[i]) / Float(maximum)) + realPercent = totalVoters == 0 ? 0 : Float(percents[i]) + isSelected = vote.selected + votedCount = vote.count + if poll.kind == .quiz { + isCorrect = vote.isCorrect + } else { + isCorrect = nil + } + } else { + percent = poll.results.totalVoters == nil || poll.results.totalVoters == 0 ? (isClosed ? 0 : nil) : voted ? 0 : (isClosed ? 0 : nil) + realPercent = 0 + isSelected = false + isCorrect = nil + } + + let nameFont: NSFont = .normal(.text)//voted && isSelected ? .bold(.text) : .normal(.text) + let nameLayout = TextViewLayout(.initialize(string: option.text, color: self.presentation.chat.textColor(isIncoming, renderType == .bubble), font: nameFont), alwaysStaticItems: true) + + + let wrapper = PollOption(option: option, nameText: nameLayout, percent: percent, realPercent: realPercent, voteCount: votedCount, isSelected: isSelected, isIncoming: isIncoming, isBubbled: renderType == .bubble, voted: voted, isLoading: object.additionalData.pollStateData.identifiers.contains(option.opaqueIdentifier) && object.additionalData.pollStateData.isLoading, presentation: self.presentation, isCorrect: isCorrect, isQuiz: poll.kind == .quiz, isMultipleSelected: object.additionalData.pollStateData.identifiers.contains(option.opaqueIdentifier), vote: { [weak self] control in + self?.voteOption(option, for: control) + }) + + options.append(wrapper) + } + self.options = options + + + let totalCount = poll.results.totalVoters ?? 0 + + var totalText = poll.isQuiz ? L10n.chatQuizTotalVotesCountable(Int(totalCount)) : L10n.chatPollTotalVotes1Countable(Int(totalCount)) + totalText = totalText.replacingOccurrences(of: "\(totalCount)", with: Int(totalCount).separatedNumber) + + if actionButtonText == nil && !isBotQuiz { + let text: String + if totalCount > 0 { + text = totalText + } else { + if poll.isQuiz { + text = self.isClosed ? L10n.chatQuizTotalVotesResultEmpty : L10n.chatQuizTotalVotesEmpty + } else { + text = self.isClosed ? L10n.chatPollTotalVotesResultEmpty : L10n.chatPollTotalVotesEmpty + } + } + self.totalVotesText = TextViewLayout(.initialize(string: text, color: self.presentation.chat.grayText(isIncoming, renderType == .bubble), font: .normal(12)), maximumNumberOfLines: 1, alwaysStaticItems: true) + } else { + self.totalVotesText = nil + } + + + + self.titleText = TextViewLayout(.initialize(string: poll.text, color: self.presentation.chat.textColor(isIncoming, renderType == .bubble), font: .medium(.text)), alwaysStaticItems: true) + + let typeText: String = self.isBotQuiz ? L10n.chatQuizTextType : poll.title + + self.titleTypeText = TextViewLayout(.initialize(string: typeText, color: self.presentation.chat.grayText(isIncoming, renderType == .bubble), font: .normal(12)), maximumNumberOfLines: 1, alwaysStaticItems: true) + } + + override var additionalLineForDateInBubbleState: CGFloat? { + var size: NSSize = .zero + if let action = self.actionButtonText { + size = TitleButton.size(with: action, font: .normal(.text)) + } else if let totalVotesText = self.totalVotesText { + size = totalVotesText.layoutSize + } + + if size.width > 0 { + let dif = contentSize.width - (contentSize.width / 2 + size.width / 2) + if dif < (rightSize.width + insetBetweenContentAndDate) { + return 20 + } + + } + + if isBotQuiz { + return 10 + } + + return super.additionalLineForDateInBubbleState + } + + + override var isFixedRightPosition: Bool { + return true + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + return super.menuItems(in: location) |> map { [weak self] items in + guard let `self` = self, let message = self.message else { return items } + var items = items + if let poll = message.media.first as? TelegramMediaPoll { + if !self.isClosed && !message.flags.contains(.Unsent) && !message.flags.contains(.Failed) { + var index: Int = 0 + if let _ = poll.results.voters?.first(where: {$0.selected}), poll.kind != .quiz { + items.insert(ContextMenuItem(L10n.chatPollUnvote, handler: { [weak self] in + self?.unvote() + }), at: index) + index += 1 + } + if message.forwardInfo == nil { + var canClose: Bool = message.author?.id == self.context.peerId + if let peer = self.peer as? TelegramChannel { + canClose = peer.hasPermission(.sendMessages) || peer.hasPermission(.editAllMessages) + } + if canClose { + + items.insert(ContextMenuItem(poll.kind == .quiz ? L10n.chatQuizStop : L10n.chatPollStop, handler: { [weak self] in + confirm(for: mainWindow, header: poll.kind == .quiz ? L10n.chatQuizStopConfirmHeader : L10n.chatPollStopConfirmHeader, information: poll.kind == .quiz ? L10n.chatQuizStopConfirmText : L10n.chatPollStopConfirmText, okTitle: L10n.alertConfirmStop, successHandler: { [weak self] _ in + self?.stop() + }) + }), at: index) + index += 1 + } + } + if index != 0 { + items.insert(ContextSeparatorItem(), at: index) + } + } + + } + return items + } + } + + private func stop() { + if let message = message { + chatInteraction.closePoll(message.id) + } + } + + private func unvote() { + + if canInvokeVote { + guard let message = message else { return } + self.chatInteraction.vote(message.id, [], true) + } + + } + + private func voteOption(_ option: TelegramMediaPollOption, for control: Control) { + if canInvokeVote, !self.options.contains(where: { $0.isSelected }) { + guard let message = message else { return } + var identifiers = self.entry.additionalData.pollStateData.identifiers + if let index = identifiers.firstIndex(of: option.opaqueIdentifier) { + identifiers.remove(at: index) + } else { + identifiers.append(option.opaqueIdentifier) + } + chatInteraction.vote(message.id, identifiers, !self.poll.isMultiple) + } else { + if self.options.contains(where: { $0.isSelected }) || self.isClosed, self.poll.publicity == .public { + guard let message = message else { + return + } + if message.flags.contains(.Failed) || message.flags.contains(.Unsent) || message.flags.contains(.Sending) || self.options.contains(where: { $0.isLoading }) { + return + } + self.invokeAction(fromOption: option.opaqueIdentifier) + } else if let option = self.options.first(where: { $0.option.opaqueIdentifier == option.opaqueIdentifier }) { + tooltip(for: control, text: option.tooltip) + } + } + } + + + private var canInvokeVote: Bool { + guard let message = message else { + return false + } + if message.flags.contains(.Failed) || message.flags.contains(.Unsent) || message.flags.contains(.Sending) { + return false + } + if self.isClosed { + return false + } + if self.options.contains(where: { $0.isLoading }) { + return false + } + + return true + } + + fileprivate func invokeAction(fromOption: Data? = nil) { + + guard let message = message else { return } + let hasSelected = self.options.contains(where: { $0.isSelected }) + if canInvokeVote, !hasSelected { + let identifiers = self.entry.additionalData.pollStateData.identifiers + chatInteraction.vote(message.id, identifiers, true) + } else { + if !isBotQuiz { + showModal(with: PollResultController(context: context, message: message, scrollToOption: fromOption), for: context.window) + } + } + } + + override func viewClass() -> AnyClass { + return ChatPollItemView.self + } + + override var instantlyResize: Bool { + return true + } + + override func makeContentSize(_ width: CGFloat) -> NSSize { + + let width = min(width, 320) + + + var rightInset: CGFloat = 0 + + if let _ = poll.results.solution, options.contains(where: { $0.isSelected }) || self.isClosed { + rightInset += 10 + } + let deadlineTimeout = poll.deadlineTimeout + let displayDeadline = !options.contains(where: { $0.isSelected }) + + + + titleText.measure(width: width - bubbleContentInset - rightInset) + titleTypeText.measure(width: width - bubbleContentInset - rightInset) + totalVotesText?.measure(width: width - bubbleContentInset) + + + + var maxOptionNameWidth: CGFloat = 0 + for (i, option) in options.enumerated() { + let size = option.measure(width: width) + self.options[i] = option.withUpdatedContentSize(size) + if maxOptionNameWidth < size.width { + maxOptionNameWidth = size.width + } + } + + + let contentWidth:CGFloat = max(max(maxOptionNameWidth, titleText.layoutSize.width), titleTypeText.layoutSize.width) + + var contentHeight: CGFloat = 0 + + contentHeight += titleText.layoutSize.height + defaultContentInnerInset + contentHeight += titleTypeText.layoutSize.height + defaultContentInnerInset + contentHeight += options.reduce(0, { $0 + $1.contentSize.height }) + (CGFloat(options.count - 1) * PollOption.spaceBetweenOptions) + + if let totalVotesText = totalVotesText { + contentHeight += defaultContentInnerInset + contentHeight += totalVotesText.layoutSize.height + } + if let _ = self.actionButtonText { + contentHeight += defaultContentInnerInset + contentHeight += 15 + } + + return NSMakeSize(max(width, contentWidth), contentHeight) + } + + override func copyAndUpdate(animated: Bool) { + if let table = self.table { + let item = ChatRowItem.item(table.frame.size, from: self.entry, interaction: self.chatInteraction, downloadSettings: self.downloadSettings, theme: self.presentation) + _ = item.makeSize(table.frame.width, oldWidth: 0) + let transaction = TableUpdateTransition(deleted: [], inserted: [], updated: [(self.index, item)], animated: animated) + table.merge(with: transaction) + } + } + +} + + +final class ChatPollItemView : ChatRowView { + private var contentNode:PollView = PollView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(contentNode) + } + + override var contentFrameModifier: NSRect { + guard let item = item as? ChatRowItem else {return NSZeroRect} + + if item.isBubbled { + var frame = bubbleFrame + frame.size.width -= item.additionBubbleInset + frame.origin.y = super.contentFrameModifier.minY + if item.isIncoming { + frame.origin.x += item.additionBubbleInset + } + return frame + } else { + var frame = super.contentFrameModifier + frame.origin.x -= item.bubbleContentInset + return frame + } + } + + + func doAfterAnswer() { + guard let item = item as? ChatPollItem else { return } + + let selected = item.options.first(where: { $0.isSelected }) + + if let selected = selected { + if item.poll.kind == .quiz { + if let isCorrect = selected.isCorrect { + if isCorrect { + doWhenCorrectAnswer() + } else { + doWhenIncorrectAnswer() + } + } + } + } + } + + func doWhenCorrectAnswer() { + guard let item = item as? ChatPollItem else { return } + PlayConfetti(for: item.context.window) + if FastSettings.inAppSounds { + playSoundEffect(.confetti) + } + } + func doWhenIncorrectAnswer() { + shakeContentView() + + if FastSettings.inAppSounds { + NSSound.beep() + } + self.contentNode.showSolution() + } + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? ChatPollItem else { return } + super.set(item: item, animated: animated) + + contentNode.change(size: NSMakeSize(contentFrameModifier.width, item.contentSize.height), animated: animated) + contentNode.update(with: item, animated: animated) + + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + } + + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func canStartTextSelecting(_ event: NSEvent) -> Bool { + + let point = contentView.convert(event.locationInWindow, from: nil) + return NSPointInRect(point, NSMakeRect(0, contentNode.titleView.frame.minY, contentNode.frame.width, contentNode.titleView.frame.height)) + } + + override var selectableTextViews: [TextView] { + return [contentNode.titleView] + } + + override func canMultiselectTextIn(_ location: NSPoint) -> Bool { + let point = contentView.convert(location, from: nil) + return NSPointInRect(point, NSMakeRect(0, contentNode.titleView.frame.minY, contentNode.frame.width, contentNode.titleView.frame.height)) + } + + override var needsDisplay: Bool { + get { + return super.needsDisplay + } + set { + super.needsDisplay = true + contentNode.needsDisplay = true + } + } + + override var backgroundColor: NSColor { + didSet { + + contentNode.backgroundColor = .clear//contentColor + } + } + + override func shakeView() { + contentNode.shake() + } + + + override func draw(_ dirtyRect: NSRect) { + + } + + override func updateColors() { + super.updateColors() + contentNode.backgroundColor = .clear//contentColor + } + + +} + + +private final class PollOptionView : Control { + private var percentView: ImageView? + private let nameView: TextView = TextView() + private var selectingView:ImageView? + private let progressView: LinearProgressControl = LinearProgressControl(progressHeight: 5) + private var progressIndicator: ProgressIndicator? + private let borderView: View = View(frame: NSZeroRect) + + private var selectedImageView: ImageView? + + private var option: PollOption? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + nameView.userInteractionEnabled = false + nameView.isSelectable = false + progressView.hasMinumimVisibility = true + addSubview(nameView) + addSubview(progressView) + addSubview(borderView) + borderView.userInteractionEnabled = false + progressView.userInteractionEnabled = false + progressView.roundCorners = true + + progressView.isEventLess = true + + set(handler: { [weak self] control in + self?.option?.vote(control) + }, for: .Click) + } + + var defaultInset: CGFloat { + return 13 + } + + func update(with option: PollOption, animated: Bool) { + let animated = animated && self.option != option + let previousOption = self.option + + let previousPercent = self.option?.realPercent + + self.option = option + + + let duration: Double = 0.4 + let timingFunction: CAMediaTimingFunctionName = .spring + + nameView.update(option.nameText, origin: NSMakePoint(option.leftOptionInset, 0)) + progressView.setFrameOrigin(NSMakePoint(nameView.frame.minX, nameView.frame.maxY + 5)) + borderView.backgroundColor = option.presentation.chat.pollOptionBorder(option.isIncoming, option.isBubbled) + borderView.frame = NSMakeRect(nameView.frame.minX, nameView.frame.maxY + 5 - .borderSize + progressView.progressHeight, frame.width - nameView.frame.minX, .borderSize) + borderView.change(opacity: option.percent != nil ? 0 : 1, animated: animated, duration: duration, timingFunction: timingFunction) + progressView.change(opacity: option.percent == nil ? 0 : 1, animated: animated, duration: duration, timingFunction: timingFunction) + + let votedColor: NSColor + + + if option.isSelected { + var justAdded = false + if self.selectedImageView == nil { + self.selectedImageView = ImageView() + addSubview(self.selectedImageView!) + justAdded = true + } + + guard let selectedImageView = self.selectedImageView else { + return + } + + if option.isQuiz, let isCorrect = option.isCorrect { + if isCorrect { + selectedImageView.image = option.presentation.chat.pollSelectedCorrect(option.isIncoming, option.isBubbled, icons: option.presentation.icons) + } else { + selectedImageView.image = option.presentation.chat.pollSelectedIncorrect(option.isIncoming, option.isBubbled, icons: option.presentation.icons) + } + } else { + selectedImageView.image = option.presentation.chat.pollSelected(option.isIncoming, option.isBubbled, icons: option.presentation.icons) + } + selectedImageView.setFrameSize(NSMakeSize(12, 12)) + + selectedImageView.setFrameOrigin(NSMakePoint(progressView.frame.minX - selectedImageView.frame.width - 4, floorToScreenPixels(backingScaleFactor, progressView.frame.midY - selectedImageView.frame.height / 2))) + + if justAdded && animated { + selectedImageView.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: duration) + selectedImageView.layer?.animateAlpha(from: 0, to: 1, duration: duration, timingFunction: timingFunction) + } + } else { + if option.isQuiz, let isCorrect = option.isCorrect, isCorrect { + var justAdded = false + if self.selectedImageView == nil { + self.selectedImageView = ImageView() + addSubview(self.selectedImageView!) + justAdded = true + } + + guard let selectedImageView = self.selectedImageView else { + return + } + + selectedImageView.image = option.presentation.chat.pollSelected(option.isIncoming, option.isBubbled, icons: option.presentation.icons) + + selectedImageView.setFrameSize(NSMakeSize(12, 12)) + + selectedImageView.setFrameOrigin(NSMakePoint(progressView.frame.minX - selectedImageView.frame.width - 4, floorToScreenPixels(backingScaleFactor, progressView.frame.midY - selectedImageView.frame.height / 2))) + + if justAdded && animated { + selectedImageView.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: duration) + selectedImageView.layer?.animateAlpha(from: 0, to: 1, duration: duration, timingFunction: timingFunction) + } + + } else { + if let selectedImageView = self.selectedImageView { + self.selectedImageView = nil + if animated { + selectedImageView.layer?.animateScaleSpring(from: 1, to: 0.2, duration: duration, removeOnCompletion: false) + selectedImageView.layer?.animateAlpha(from: 1, to: 0, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak selectedImageView] _ in + selectedImageView?.removeFromSuperview() + }) + } else { + selectedImageView.removeFromSuperview() + } + } + } + + + + } + + if option.isSelected, let isCorrect = option.isCorrect { + votedColor = isCorrect ? option.presentation.chat.greenUI(option.isIncoming, option.isBubbled) : option.presentation.chat.redUI(option.isIncoming, option.isBubbled) + } else { + votedColor = option.presentation.chat.webPreviewActivity(option.isIncoming, option.isBubbled) + } + progressView.style = ControlStyle(foregroundColor: votedColor, backgroundColor: .clear) + + if let progress = option.percent { + //toolTip = option.tooltip + + progressView.frame = NSMakeRect(nameView.frame.minX, nameView.frame.maxY + 5, frame.width - nameView.frame.minX - defaultInset, progressView.frame.height) + progressView.set(progress: CGFloat(progress), animated: animated, duration: duration / 2, timingFunction: .spring, bounce: true) + if percentView == nil { + percentView = ImageView() + addSubview(percentView!) + if animated { + percentView!.layer?.animateAlpha(from: 0, to: 1, duration: duration / 2) + } + } + + + percentView?.animates = animated + percentView?.image = option.currentPercentImage + percentView?.setFrameSize(36, 16) + percentView?.setFrameOrigin(NSMakePoint(nameView.frame.minX - percentView!.frame.width - PollOption.spaceBetweenTexts, nameView.frame.minY + 1)) + + if previousPercent != option.realPercent, animated { + let images = option.presentation.chat.pollPercentAnimatedIcons(option.isIncoming, option.isBubbled, from: CGFloat(previousPercent ?? 0), to: CGFloat(option.realPercent), duration: duration / 2) + if !images.isEmpty { + let animation = CAKeyframeAnimation(keyPath: "contents") + animation.values = images + animation.duration = duration / 2 + animation.calculationMode = .discrete + percentView?.layer?.add(animation, forKey: "image") + } + + } + + if let selectingView = selectingView { + self.selectingView = nil + if animated { + selectingView.layer?.animateAlpha(from: 1, to: 0, duration: duration / 2, removeOnCompletion: false, completion: { [weak selectingView] completed in + if completed { + selectingView?.removeFromSuperview() + } + }) + } else { + selectingView.removeFromSuperview() + } + } + if let progressIndicator = progressIndicator { + self.progressIndicator = nil + if animated { + progressIndicator.layer?.animateAlpha(from: 1, to: 0, duration: duration / 2, removeOnCompletion: false, completion: { [weak progressIndicator] completed in + if completed { + progressIndicator?.removeFromSuperview() + } + }) + } else { + progressIndicator.removeFromSuperview() + } + } + } else { + toolTip = nil + if let percentView = self.percentView { + self.percentView = nil + if animated { + + if previousPercent != 0 { + let images = option.presentation.chat.pollPercentAnimatedIcons(option.isIncoming, option.isBubbled, from: CGFloat(previousPercent ?? 0), to: CGFloat(0), duration: duration / 2) + if !images.isEmpty { + let animation = CAKeyframeAnimation(keyPath: "contents") + animation.values = images + animation.duration = duration / 2 + animation.calculationMode = .discrete + percentView.layer?.add(animation, forKey: "image") + } + } + + percentView.layer?.animateAlpha(from: 1, to: 0, duration: duration / 2, removeOnCompletion: false, completion: { [weak percentView] completed in + if completed { + percentView?.removeFromSuperview() + } + }) + } else { + percentView.removeFromSuperview() + } + } + + progressView.set(progress: 0, animated: animated) + + if option.isLoading { + if let selectingView = selectingView { + self.selectingView = nil + if animated { + selectingView.layer?.animateAlpha(from: 1, to: 0, duration: duration / 2, removeOnCompletion: false, completion: { [weak selectingView] completed in + if completed { + selectingView?.removeFromSuperview() + } + }) + } else { + selectingView.removeFromSuperview() + } + } + if progressIndicator == nil { + progressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 18, 18)) + addSubview(progressIndicator!) + if animated { + progressIndicator?.layer?.animateAlpha(from: 0, to: 1, duration: duration / 2) + } + } +// progressIndicator?.lineWidth = 1.0 + progressIndicator?.progressColor = option.presentation.chat.webPreviewActivity(option.isIncoming, option.isBubbled) + progressIndicator?.setFrameOrigin(NSMakePoint(defaultInset, 0)) + + } else { + if let progressIndicator = progressIndicator { + self.progressIndicator = nil + if animated { + progressIndicator.layer?.animateAlpha(from: 1, to: 0, duration: duration / 2, removeOnCompletion: false, completion: { [weak progressIndicator] completed in + if completed { + progressIndicator?.removeFromSuperview() + } + }) + } else { + progressIndicator.removeFromSuperview() + } + } + + if selectingView == nil { + selectingView = ImageView(frame: NSMakeRect(0, 0, 22, 22)) + addSubview(selectingView!) + if animated { + selectingView?.layer?.animateAlpha(from: 0, to: 1, duration: duration / 2) + } + } + selectingView?.animates = animated || (previousOption != nil && previousOption?.isMultipleSelected != option.isMultipleSelected) + if option.isMultipleSelected { + selectingView?.image = option.presentation.chat.pollSelected(option.isIncoming, option.isBubbled, icons: option.presentation.icons) + } else { + selectingView?.image = option.presentation.chat.pollOptionUnselectedImage(option.isIncoming, option.isBubbled) + } + selectingView?.sizeToFit() + selectingView?.setFrameOrigin(NSMakePoint(defaultInset, 0)) + } + + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class PollView : Control { + fileprivate let titleView: TextView = TextView() + private let typeView: TextView = TextView() + private var actionButton: TitleButton? + private var totalVotesTextView: TextView? + + private var mergedAvatarsView: MergedAvatarsView? + + private var solutionButton: ImageButton? + private var timerView: PollBubbleTimerView? + + private var options:[PollOptionView] = [] + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + typeView.isSelectable = false + typeView.userInteractionEnabled = false + addSubview(titleView) + addSubview(typeView) + } + + func update(with item: ChatPollItem, animated: Bool) { + + titleView.update(item.titleText) + typeView.update(item.titleTypeText) + + var y: CGFloat = 0 + + titleView.setFrameOrigin(NSMakePoint(item.bubbleContentInset, y)) + y += titleView.frame.height + item.defaultContentInnerInset + typeView.setFrameOrigin(NSMakePoint(item.bubbleContentInset, y)) + y += typeView.frame.height + item.defaultContentInnerInset + + while options.count < item.options.count { + let option = PollOptionView(frame: NSZeroRect) + options.append(option) + addSubview(option) + } + while options.count > item.options.count { + let option = options.removeLast() + option.removeFromSuperview() + } + for (i, option) in item.options.enumerated() { + + + self.options[i].frame = NSMakeRect(0, y - (i > 0 ? PollOption.spaceBetweenOptions : 0), frame.width, option.contentSize.height) + self.options[i].update(with: option, animated: animated) + y += option.contentSize.height + if i != item.options.count - 1 { + y += PollOption.spaceBetweenOptions + } + } + + if let totalVotesText = item.totalVotesText { + y += item.defaultContentInnerInset + if totalVotesTextView == nil { + totalVotesTextView = TextView() + totalVotesTextView!.userInteractionEnabled = false + totalVotesTextView!.isSelectable = false + addSubview(totalVotesTextView!) + } + guard let totalVotesTextView = self.totalVotesTextView else { + return + } + totalVotesTextView.update(totalVotesText, origin: NSMakePoint(floorToScreenPixels(backingScaleFactor, (frame.width - totalVotesText.layoutSize.width) / 2), y)) + } else { + totalVotesTextView?.removeFromSuperview() + totalVotesTextView = nil + } + + if let actionText = item.actionButtonText { + y += item.defaultContentInnerInset - 4 + if self.actionButton == nil { + self.actionButton = TitleButton() + self.addSubview(self.actionButton!) + } + guard let actionButton = self.actionButton else { + return + } + + actionButton.isEnabled = item.actionButtonIsEnabled + + actionButton.removeAllHandlers() + actionButton.set(handler: { [weak item] _ in + item?.invokeAction() + }, for: .SingleClick) + + actionButton.set(font: .normal(.text), for: .Normal) + actionButton.set(color: item.presentation.chat.webPreviewActivity(item.isIncoming, item.isBubbled), for: .Normal) + actionButton.set(text: actionText, for: .Normal) + _ = actionButton.sizeToFit(NSMakeSize(10, 4), thatFit: false) + actionButton.centerX(y: y) + } else { + self.actionButton?.removeFromSuperview() + self.actionButton = nil + } + + guard let message = item.message else { + return + } + + var avatarPeers: [Peer] = [] + if !item.isBotQuiz { + for peerId in item.poll.results.recentVoters { + if let peer = message.peers[peerId] { + avatarPeers.append(peer) + } + } + } + + if !avatarPeers.isEmpty { + if self.mergedAvatarsView == nil { + self.mergedAvatarsView = MergedAvatarsView(frame: NSMakeRect(0, 0, mergedImageSpacing * CGFloat(avatarPeers.count) + 2, mergedImageSize)) + addSubview(self.mergedAvatarsView!) + } + self.mergedAvatarsView?.frame = CGRect(origin: NSMakePoint(typeView.frame.maxX + 6, typeView.frame.minY), size: NSMakeSize(mergedImageSpacing * CGFloat(avatarPeers.count) + 2, mergedImageSize)) + self.mergedAvatarsView?.update(context: item.context, peers: avatarPeers, message: message, synchronousLoad: false) + self.mergedAvatarsView?.removeAllHandlers() + + self.mergedAvatarsView?.set(handler: { [weak item] _ in + if item?.actionButtonText == L10n.chatPollViewResults, item?.actionButtonIsEnabled == true { + item?.invokeAction() + } + }, for: .Click) + } else { + self.mergedAvatarsView?.removeFromSuperview() + self.mergedAvatarsView = nil + } + + if let solution = item.poll.results.solution, item.options.contains(where: { $0.isSelected }) || item.isClosed { + var mayApplyAnimation = false + if solutionButton == nil { + solutionButton = ImageButton() + addSubview(solutionButton!) + mayApplyAnimation = animated + } + if let solutionButton = self.solutionButton { + solutionButton.set(image: item.presentation.chat.quizSolution(item), for: .Normal) + solutionButton.style = ControlStyle(font: nil, foregroundColor: theme.colors.accent, highlightColor: theme.colors.accent.withAlphaComponent(0.7)) + _ = solutionButton.sizeToFit() + solutionButton.setFrameOrigin(NSMakePoint(frame.width - solutionButton.frame.width - 6, typeView.frame.minY - 6)) + + if mayApplyAnimation { + solutionButton.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: 0.3) + } + } + solutionButton?.removeAllHandlers() + + + solutionButton?.set(handler: { [weak item] control in + + guard let item = item else { + return + } + let text = ChatMessageItem.applyMessageEntities(with: [TextEntitiesMessageAttribute(entities: solution.entities)], for: solution.text, context: item.context, fontSize: .text, openInfo: item.chatInteraction.openInfo, textColor: .white, linkColor: nightAccentPalette.link, monospacedPre: .redUI, monospacedCode: .greenUI, mediaDuration: nil) + + tooltip(for: control, text: solution.text, attributedText: text, interactions: globalLinkExecutor, timeout: 10.0) + }, for: .Click) + + } else { + solutionButton?.removeFromSuperview() + solutionButton = nil + } + + let deadlineTimeout = item.poll.deadlineTimeout + var displayDeadline = true + if let voters = item.poll.results.voters { + for voter in voters { + if voter.selected { + displayDeadline = false + break + } + } + } + + + if let deadlineTimeout = deadlineTimeout, displayDeadline, !item.isClosed { + let timerView: PollBubbleTimerView + if let current = self.timerView { + timerView = current + } else { + timerView = PollBubbleTimerView(frame: NSMakeRect(frame.width - 70 - 8, typeView.frame.minY, 70, 22)) + self.addSubview(timerView) + self.timerView = timerView + + if animated { + timerView.layer?.animateScaleSpring(from: 0.2, to: 1.0, duration: 0.3) + timerView.layer?.animateAlpha(from: 0, to: 1, duration: 0.3) + } + } + + timerView.reachedTimeout = { [weak item] in + item?.copyAndUpdate(animated: true) + } + var endDate: Int32? + if message.id.namespace == Namespaces.Message.Cloud { + let startDate: Int32 + if let forwardInfo = message.forwardInfo { + startDate = forwardInfo.date + } else { + startDate = message.timestamp + } + endDate = startDate + deadlineTimeout + } + timerView.update(regularColor: item.presentation.chat.textColor(item.isIncoming, item.isBubbled), proximityColor: item.presentation.chat.redUI(item.isIncoming, item.isBubbled), timeout: deadlineTimeout, deadlineTimestamp: endDate) + + } else if let timerView = self.timerView { + self.timerView = nil + if animated { + timerView.layer?.animateScaleSpring(from: 1, to: 0, duration: 0.3, removeOnCompletion: false, completion: { [weak timerView] _ in + timerView?.removeFromSuperview() + }) + timerView.layer?.animateAlpha(from: 1, to: 0.2, duration: 0.3) + } else { + timerView.removeFromSuperview() + } + } + + + + + + } + + func showSolution() { + self.solutionButton?.send(event: .Click) + } + + override func layout() { + super.layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatPresentationInterfaceState.swift b/Telegram-Mac/ChatPresentationInterfaceState.swift index e8bbb3e972..ac7bc561b8 100644 --- a/Telegram-Mac/ChatPresentationInterfaceState.swift +++ b/Telegram-Mac/ChatPresentationInterfaceState.swift @@ -8,10 +8,11 @@ import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit enum ChatPresentationInputContext { case none @@ -21,7 +22,11 @@ enum ChatPresentationInputContext { case emoji } - +enum RestrictedMediaType { + case stickers + case media + +} enum ChatPresentationInputQuery: Equatable { @@ -30,54 +35,8 @@ enum ChatPresentationInputQuery: Equatable { case mention(query: String, includeRecent: Bool) case command(String) case contextRequest(addressName: String, query: String) - case emoji(String) + case emoji(String, firstWord: Bool) case stickers(String) - static func ==(lhs: ChatPresentationInputQuery, rhs: ChatPresentationInputQuery) -> Bool { - switch lhs { - case let .hashtag(query): - if case .hashtag(query) = rhs { - return true - } else { - return false - } - case let .stickers(query): - if case .stickers(query) = rhs { - return true - } else { - return false - } - case let .emoji(query): - if case .emoji(query) = rhs { - return true - } else { - return false - } - case let .mention(query, includeInline): - if case .mention(query, includeInline) = rhs { - return true - } else { - return false - } - case let .command(query): - if case .command(query) = rhs { - return true - } else { - return false - } - case let .contextRequest(addressName, query): - if case .contextRequest(addressName, query) = rhs { - return true - } else { - return false - } - case .none: - if case .none = rhs { - return true - } else { - return false - } - } - } } enum ChatPresentationInputQueryResult: Equatable { @@ -85,7 +44,8 @@ enum ChatPresentationInputQueryResult: Equatable { case mentions([Peer]) case commands([PeerCommand]) case stickers([FoundStickerItem]) - case emoji([EmojiClue]) + case emoji([String], Bool) + case searchMessages(([Message], SearchMessagesState?, (SearchMessagesState?)-> Void), [Peer], String) case contextRequestResult(Peer, ChatContextResultCollection?) static func ==(lhs: ChatPresentationInputQueryResult, rhs: ChatPresentationInputQueryResult) -> Bool { @@ -102,9 +62,33 @@ enum ChatPresentationInputQueryResult: Equatable { } else { return false } - case let .emoji(lhsResults): - if case let .emoji(rhsResults) = rhs { - return lhsResults == rhsResults + case let .emoji(lhsResults, lhsFirstWord): + if case let .emoji(rhsResults, rhsFirstWord) = rhs { + return lhsResults == rhsResults && lhsFirstWord == rhsFirstWord + } else { + return false + } + case let .searchMessages(lhsMessages, lhsPeers, lhsSearchText): + if case let .searchMessages(rhsMessages, rhsPeers, rhsSearchText) = rhs { + if lhsPeers.count == rhsPeers.count { + for i in 0 ..< rhsPeers.count { + if !lhsPeers[i].isEqual(rhsPeers[i]) { + return false + } + } + } else { + return false + } + if lhsMessages.0.count == rhsMessages.0.count { + for i in 0 ..< lhsMessages.0.count { + if !isEqualMessages(lhsMessages.0[i], rhsMessages.0[i]) { + return false + } + } + return lhsSearchText == rhsSearchText && lhsMessages.1 == rhsMessages.1 + } else { + return false + } } else { return false } @@ -149,36 +133,6 @@ enum ChatPresentationInputQueryResult: Equatable { } -final class ChatEditState : Equatable { - let inputState:ChatTextInputState - let message:Message - init(message:Message, state:ChatTextInputState? = nil) { - self.message = message - if let state = state { - self.inputState = state - } else { - var attribute:TextEntitiesMessageAttribute? - for attr in message.attributes { - if let attr = attr as? TextEntitiesMessageAttribute { - attribute = attr - } - } - var attributes:[ChatTextInputAttribute] = [] - if let attribute = attribute { - attributes = chatTextAttributes(from: attribute) - } - self.inputState = ChatTextInputState(inputText:message.text, selectionRange:message.text.length ..< message.text.length, attributes: attributes ) - } - } - - func withUpdated(state:ChatTextInputState) -> ChatEditState { - return ChatEditState(message:message, state:state) - } - - static func ==(lhs:ChatEditState, rhs:ChatEditState) -> Bool { - return lhs.message.id == rhs.message.id && lhs.inputState == rhs.inputState - } -} enum ChatRecordingStatus : Equatable { @@ -186,24 +140,16 @@ enum ChatRecordingStatus : Equatable { case recording(duration: Double) } -func ==(lhs: ChatRecordingStatus, rhs: ChatRecordingStatus) -> Bool { - switch lhs { - case .paused: - if case .paused = rhs { - return true - } else { - return false - } - case .recording(let duration): - if case .recording(duration) = rhs { - return true - } else { - return false - } - } -} class ChatRecordingState : Equatable { + + let autohold: Bool + let holdpromise: ValuePromise = ValuePromise() + init(autohold: Bool) { + self.autohold = autohold + holdpromise.set(autohold) + } + var micLevel: Signal { return .complete() } @@ -236,9 +182,12 @@ func ==(lhs:ChatRecordingState, rhs:ChatRecordingState) -> Bool { final class ChatRecordingVideoState : ChatRecordingState { let pipeline: VideoRecorderPipeline - private let path: String = NSTemporaryDirectory() + "video_message\(arc4random()).mp4" - override init() { - pipeline = VideoRecorderPipeline(url: URL(fileURLWithPath: path)) + private let path: String + init(account: Account, liveUpload:Bool, autohold: Bool) { + let id:Int64 = arc4random64() + self.path = NSTemporaryDirectory() + "video_message\(id).mp4" + self.pipeline = VideoRecorderPipeline(url: URL(fileURLWithPath: path), liveUploading: liveUpload ? PreUploadManager(path, account: account, id: id) : nil) + super.init(autohold: autohold) } override var micLevel: Signal { @@ -259,8 +208,8 @@ final class ChatRecordingVideoState : ChatRecordingState { } } |> take(1) |> map { state in switch state { - case let .finishRecording(path, duration, _): - return [VideoMessageSenderContainer(path: path, duration: duration, size: CGSize(width: 200, height: 200))] + case let .finishRecording(path, duration, id, _): + return [VideoMessageSenderContainer(path: path, duration: duration, size: CGSize(width: 200, height: 200), id: id)] default: return [] } @@ -300,7 +249,7 @@ final class ChatRecordingAudioState : ChatRecordingState { override var data: Signal<[MediaSenderContainer], NoError> { return recorder.takenRecordedData() |> map { value in if let value = value, value.duration > 0.5 { - return [VoiceSenderContainer(data: value)] + return [VoiceSenderContainer(data: value, id: value.id)] } return [] } @@ -312,10 +261,18 @@ final class ChatRecordingAudioState : ChatRecordingState { - override init() { - recorder = ManagedAudioRecorder() + init(account: Account, liveUpload: Bool, autohold: Bool) { + let id = arc4random64() + let path = NSTemporaryDirectory() + "voice_message\(id).ogg" + let uploadManager:PreUploadManager? = liveUpload ? PreUploadManager(path, account: account, id: id) : nil + let dataItem = TGDataItem(filePath: path) + + recorder = ManagedAudioRecorder(liveUploading: uploadManager, dataItem: dataItem) + super.init(autohold: autohold) } + + override func start() { recorder.start() } @@ -328,7 +285,7 @@ final class ChatRecordingAudioState : ChatRecordingState { recorder.stop() _ = data.start(next: { data in for container in data { - try? FileManager.default.removeItem(atPath: container.path) + // try? FileManager.default.removeItem(atPath: container.path) } }) } @@ -346,6 +303,7 @@ enum ChatState : Equatable { case selecting case block(String) case action(String, (ChatInteraction)->Void) + case channelWithDiscussion(discussionGroupId: PeerId?, leftAction: String, rightAction: String) case editing case recording(ChatRecordingState) case restricted(String) @@ -359,6 +317,12 @@ func ==(lhs:ChatState, rhs:ChatState) -> Bool { } else { return false } + case let .channelWithDiscussion(discussionGroupId, leftAction, rightAction): + if case .channelWithDiscussion(discussionGroupId, leftAction, rightAction) = rhs { + return true + } else { + return false + } case .selecting: if case .selecting = rhs { return true @@ -398,31 +362,86 @@ func ==(lhs:ChatState, rhs:ChatState) -> Bool { } } +struct ChatPeerStatus : Equatable { + let canAddContact: Bool + let peerStatusSettings: PeerStatusSettings? + init(canAddContact: Bool, peerStatusSettings: PeerStatusSettings?) { + self.canAddContact = canAddContact + self.peerStatusSettings = peerStatusSettings + } +} + +struct SlowMode : Equatable { + let validUntil: Int32? + let timeout: Int32? + let sendingIds: [MessageId] + init(validUntil: Int32? = nil, timeout: Int32? = nil, sendingIds: [MessageId] = []) { + self.validUntil = validUntil + self.timeout = timeout + self.sendingIds = sendingIds + } + func withUpdatedValidUntil(_ validUntil: Int32?) -> SlowMode { + return SlowMode(validUntil: validUntil, timeout: self.timeout, sendingIds: self.sendingIds) + } + func withUpdatedTimeout(_ timeout: Int32?) -> SlowMode { + return SlowMode(validUntil: self.validUntil, timeout: timeout, sendingIds: self.sendingIds) + } + func withUpdatedSendingIds(_ sendingIds: [MessageId]) -> SlowMode { + return SlowMode(validUntil: self.validUntil, timeout: self.timeout, sendingIds: sendingIds) + } + + var hasLocked: Bool { + return timeout != nil || !sendingIds.isEmpty + } + + var sendingLocked: Bool { + return timeout != nil + } + + var errorText: String? { + if let timeout = timeout { + return slowModeTooltipText(timeout) + } else if !sendingIds.isEmpty { + return L10n.slowModeMultipleError + } else { + return nil + } + } +} + struct ChatPresentationInterfaceState: Equatable { let interfaceState: ChatInterfaceState let peer: Peer? - let isSearchMode:Bool + let mainPeer: Peer? + let chatLocation: ChatLocation + let isSearchMode:(Bool, Peer?, String?) let notificationSettings: TelegramPeerNotificationSettings? let inputQueryResult: ChatPresentationInputQueryResult? let keyboardButtonsMessage: Message? let initialAction:ChatInitialAction? let historyCount:Int? let isBlocked:Bool? - let editState:ChatEditState? let recordingState:ChatRecordingState? - let reportStatus:PeerReportStatus + let peerStatus:ChatPeerStatus? let pinnedMessageId:MessageId? + let cachedPinnedMessage: Message? let urlPreview: (String, TelegramMediaWebpage)? let selectionState: ChatInterfaceSelectionState? - + let limitConfiguration: LimitsConfiguration + let sidebarEnabled:Bool? let sidebarShown:Bool? let layout:SplitViewState? - + let discussionGroupId: PeerId? let canAddContact:Bool? let isEmojiSection: Bool + let canInvokeBasicActions:(delete: Bool, forward: Bool) + let isNotAccessible: Bool + let hasScheduled: Bool + let slowMode: SlowMode? + let failedMessageIds:Set - + let restrictionInfo: PeerAccessRestrictionInfo? var inputContext: ChatPresentationInputQuery { return inputContextQueryForChatPresentationIntefaceState(self, includeContext: true) } @@ -437,59 +456,91 @@ struct ChatPresentationInterfaceState: Equatable { var state:ChatState { if self.selectionState == nil { - if self.editState != nil { + if self.interfaceState.editState != nil { return .editing } + if let peer = peer as? TelegramChannel { + #if APP_STORE + if let restrictionInfo = restrictionInfo { + for rule in restrictionInfo.rules { + if rule.platform == "ios" || rule.platform == "all" { + return .action(L10n.chatInputClose, { chatInteraction in + chatInteraction.context.sharedContext.bindings.rootNavigation().back() + }) + } + } + } + #endif + + + if peer.participationStatus == .left { - return .action(tr(.chatInputJoin), { chatInteraction in + return .action(L10n.chatInputJoin, { chatInteraction in chatInteraction.joinChannel() }) } else if peer.participationStatus == .kicked { - return .action(tr(.chatInputDelete), { chatInteraction in + return .action(L10n.chatInputDelete, { chatInteraction in chatInteraction.removeAndCloseChat() }) - } else if peer.hasBannedRights(.banSendMessages), let bannedRights = peer.bannedRights { - - return .restricted(bannedRights.untilDate != Int32.max ? tr(.channelPersmissionDeniedSendMessagesUntil(bannedRights.formattedUntilDate)) : tr(.channelPersmissionDeniedSendMessagesForever)) + } else if let permissionText = permissionText(from: peer, for: .banSendMessages) { + return .restricted(permissionText) } else if !peer.canSendMessage, let notificationSettings = notificationSettings { - return .action(notificationSettings.isMuted ? tr(.chatInputUnmute) : tr(.chatInputMute), { chatInteraction in - chatInteraction.toggleNotifications() + switch peer.info { + case let .broadcast(info): + if info.flags.contains(.hasDiscussionGroup) { + return .channelWithDiscussion(discussionGroupId: discussionGroupId, leftAction: notificationSettings.isMuted ? L10n.chatInputUnmute : L10n.chatInputMute, rightAction: L10n.chatInputDiscuss) + } + default: + break + } + return .action(notificationSettings.isMuted ? L10n.chatInputUnmute : L10n.chatInputMute, { chatInteraction in + chatInteraction.toggleNotifications(nil) }) } } else if let peer = peer as? TelegramGroup { if peer.membership == .Left { - return .action(tr(.chatInputReturn),{ chatInteraction in + return .action(L10n.chatInputReturn,{ chatInteraction in chatInteraction.returnGroup() }) } else if peer.membership == .Removed { - return .action(tr(.chatInputDelete), { chatInteraction in + return .action(L10n.chatInputDelete, { chatInteraction in chatInteraction.removeAndCloseChat() }) } - } else if let peer = peer as? TelegramSecretChat { + } else if let peer = peer as? TelegramSecretChat, let mainPeer = mainPeer { switch peer.embeddedState { case .terminated: - return .action(tr(.chatInputDelete), { chatInteraction in + return .action(L10n.chatInputDelete, { chatInteraction in chatInteraction.removeAndCloseChat() }) case .handshake: - return .action(tr(.chatInputSecretChatWaitingToOnline), { chatInteraction in - - }) + return .restricted(L10n.chatInputSecretChatWaitingToUserOnline(mainPeer.compactDisplayTitle)) default: break } } if let blocked = isBlocked, blocked { - return .action(tr(.chatInputUnblock), { chatInteraction in + + if let peer = peer, peer.isBot { + return .action(L10n.chatInputRestart, { chatInteraction in + chatInteraction.unblock() + chatInteraction.startBot() + }) + } + + return .action(tr(L10n.chatInputUnblock), { chatInteraction in chatInteraction.unblock() }) } - if self.editState != nil { + if let peer = peer, let permissionText = permissionText(from: peer, for: .banSendMessages) { + return .restricted(permissionText) + } + + if self.interfaceState.editState != nil { return .editing } @@ -498,7 +549,7 @@ struct ChatPresentationInterfaceState: Equatable { } if let initialAction = initialAction, case .start(_) = initialAction { - return .action(tr(.chatInputStartBot), { chatInteraction in + return .action(tr(L10n.chatInputStartBot), { chatInteraction in chatInteraction.invokeInitialAction() }) } @@ -506,11 +557,12 @@ struct ChatPresentationInterfaceState: Equatable { if let peer = peer as? TelegramUser { if peer.botInfo != nil, let historyCount = historyCount, historyCount == 0 { - return .action(tr(.chatInputStartBot), { chatInteraction in + return .action(tr(L10n.chatInputStartBot), { chatInteraction in chatInteraction.startBot() }) } } + return .normal } else { @@ -518,6 +570,10 @@ struct ChatPresentationInterfaceState: Equatable { } } + var messageSecretTimeout: Int32? { + return (peer as? TelegramSecretChat)?.messageAutoremoveTimeout + } + var isKeyboardShown:Bool { if let keyboard = keyboardButtonsMessage, let attribute = keyboard.replyMarkup { return interfaceState.messageActionsState.closedButtonKeyboardMessageId != keyboard.id && attribute.hasButtons && state == .normal @@ -533,12 +589,54 @@ struct ChatPresentationInterfaceState: Equatable { return false } - + var slowModeMultipleLocked: Bool { + if let _ = self.slowMode { + + var keys:[Int64:Int64] = [:] + var forwardMessages:[Message] = [] + for message in self.interfaceState.forwardMessages { + if let groupingKey = message.groupingKey { + if keys[groupingKey] == nil { + keys[groupingKey] = groupingKey + forwardMessages.append(message) + } + } else { + forwardMessages.append(message) + } + } + if forwardMessages.count > 1 || (!effectiveInput.inputText.isEmpty && forwardMessages.count == 1) { + return true + } else if effectiveInput.inputText.length > 4096 { + return true + } + } + return false + } + + var slowModeErrorText: String? { + if let slowMode = self.slowMode, slowMode.hasLocked { + return slowMode.errorText + } else if slowModeMultipleLocked { + if effectiveInput.inputText.length > 4096 { + return L10n.slowModeTooLongError + } + return L10n.slowModeForwardCommentError + } else { + return nil + } + } var abilityToSend:Bool { if state == .normal { - return !effectiveInput.inputText.isEmpty || !interfaceState.forwardMessageIds.isEmpty - } else if let editState = editState { + if let slowMode = self.slowMode { + if slowMode.hasLocked { + return false + } else if self.slowModeMultipleLocked { + return false + } + } + return (!effectiveInput.inputText.isEmpty || !interfaceState.forwardMessageIds.isEmpty) + } else if let editState = interfaceState.editState { if editState.message.media.count == 0 { return !effectiveInput.inputText.isEmpty } else { @@ -554,37 +652,37 @@ struct ChatPresentationInterfaceState: Equatable { return false } - let maxInput:Int32 = 10000 - let maxShortInput:Int32 = 200 - + static let maxInput:Int32 = 50000 + static let maxShortInput:Int32 = 1024 + static let textLimit: Int32 = 4096 var maxInputCharacters:Int32 { if state == .normal { - return maxInput - } else if let editState = editState { + return ChatPresentationInterfaceState.maxInput + } else if let editState = interfaceState.editState { if editState.message.media.count == 0 { - return maxInput + return ChatPresentationInterfaceState.textLimit } else { for media in editState.message.media { if !(media is TelegramMediaWebpage) { - return maxShortInput + return ChatPresentationInterfaceState.maxShortInput } } - return maxInput + return ChatPresentationInterfaceState.textLimit } } - return 0 + return ChatPresentationInterfaceState.maxInput } var effectiveInput:ChatTextInputState { - if let editState = editState { + if let editState = interfaceState.editState { return editState.inputState } else { return interfaceState.inputState } } - init() { + init(_ chatLocation: ChatLocation) { self.interfaceState = ChatInterfaceState() self.peer = nil self.notificationSettings = nil @@ -592,11 +690,10 @@ struct ChatPresentationInterfaceState: Equatable { self.keyboardButtonsMessage = nil self.initialAction = nil self.historyCount = 0 - self.isSearchMode = false + self.isSearchMode = (false, nil, nil) self.recordingState = nil - self.editState = nil self.isBlocked = nil - self.reportStatus = .unknown + self.peerStatus = nil self.pinnedMessageId = nil self.urlPreview = nil self.selectionState = nil @@ -605,9 +702,20 @@ struct ChatPresentationInterfaceState: Equatable { self.layout = nil self.canAddContact = nil self.isEmojiSection = FastSettings.entertainmentState == .emoji - } - - init(interfaceState: ChatInterfaceState, peer: Peer?, notificationSettings:TelegramPeerNotificationSettings?, inputQueryResult: ChatPresentationInputQueryResult?, keyboardButtonsMessage:Message?, initialAction:ChatInitialAction?, historyCount:Int?, isSearchMode:Bool, editState: ChatEditState?, recordingState: ChatRecordingState?, isBlocked:Bool?, reportStatus: PeerReportStatus, pinnedMessageId:MessageId?, urlPreview: (String, TelegramMediaWebpage)?, selectionState: ChatInterfaceSelectionState?, sidebarEnabled: Bool?, sidebarShown: Bool?, layout:SplitViewState?, canAddContact:Bool?, isEmojiSection: Bool) { + self.chatLocation = chatLocation + self.canInvokeBasicActions = (delete: false, forward: false) + self.isNotAccessible = false + self.restrictionInfo = nil + self.cachedPinnedMessage = nil + self.mainPeer = nil + self.limitConfiguration = LimitsConfiguration.defaultValue + self.discussionGroupId = nil + self.slowMode = nil + self.hasScheduled = false + self.failedMessageIds = Set() + } + + init(interfaceState: ChatInterfaceState, peer: Peer?, notificationSettings:TelegramPeerNotificationSettings?, inputQueryResult: ChatPresentationInputQueryResult?, keyboardButtonsMessage:Message?, initialAction:ChatInitialAction?, historyCount:Int?, isSearchMode:(Bool, Peer?, String?), recordingState: ChatRecordingState?, isBlocked:Bool?, peerStatus: ChatPeerStatus?, pinnedMessageId:MessageId?, urlPreview: (String, TelegramMediaWebpage)?, selectionState: ChatInterfaceSelectionState?, sidebarEnabled: Bool?, sidebarShown: Bool?, layout:SplitViewState?, canAddContact:Bool?, isEmojiSection: Bool, chatLocation: ChatLocation, canInvokeBasicActions: (delete: Bool, forward: Bool), isNotAccessible: Bool, restrictionInfo: PeerAccessRestrictionInfo?, cachedPinnedMessage: Message?, mainPeer: Peer?, limitConfiguration: LimitsConfiguration, discussionGroupId: PeerId?, slowMode: SlowMode?, hasScheduled: Bool, failedMessageIds: Set) { self.interfaceState = interfaceState self.peer = peer self.notificationSettings = notificationSettings @@ -616,10 +724,9 @@ struct ChatPresentationInterfaceState: Equatable { self.initialAction = initialAction self.historyCount = historyCount self.isSearchMode = isSearchMode - self.editState = editState self.recordingState = recordingState self.isBlocked = isBlocked - self.reportStatus = reportStatus + self.peerStatus = peerStatus self.pinnedMessageId = pinnedMessageId self.urlPreview = urlPreview self.selectionState = selectionState @@ -628,12 +735,26 @@ struct ChatPresentationInterfaceState: Equatable { self.layout = layout self.canAddContact = canAddContact self.isEmojiSection = isEmojiSection + self.chatLocation = chatLocation + self.canInvokeBasicActions = canInvokeBasicActions + self.isNotAccessible = isNotAccessible + self.restrictionInfo = restrictionInfo + self.cachedPinnedMessage = cachedPinnedMessage + self.mainPeer = mainPeer + self.limitConfiguration = limitConfiguration + self.discussionGroupId = discussionGroupId + self.slowMode = slowMode + self.hasScheduled = hasScheduled + self.failedMessageIds = failedMessageIds } static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool { if lhs.interfaceState != rhs.interfaceState { return false } + if lhs.discussionGroupId != rhs.discussionGroupId { + return false + } if let lhsPeer = lhs.peer, let rhsPeer = rhs.peer { if !lhsPeer.isEqual(rhsPeer) { return false @@ -642,15 +763,44 @@ struct ChatPresentationInterfaceState: Equatable { return false } - if lhs.inputContext != rhs.inputContext { + if let lhsPeer = lhs.mainPeer, let rhsPeer = rhs.mainPeer { + if !lhsPeer.isEqual(rhsPeer) { + return false + } + } else if (lhs.mainPeer == nil) != (rhs.mainPeer == nil) { return false } - if lhs.state != rhs.state { + if lhs.restrictionInfo != rhs.restrictionInfo { + return false + } + if lhs.limitConfiguration != rhs.limitConfiguration { + return false + } + + if let lhsMessage = lhs.cachedPinnedMessage, let rhsMessage = rhs.cachedPinnedMessage { + if !isEqualMessages(lhsMessage, rhsMessage) { + return false + } + } else if (lhs.cachedPinnedMessage != nil) != (rhs.cachedPinnedMessage != nil) { return false } - if lhs.isSearchMode != rhs.isSearchMode { + + if lhs.chatLocation != rhs.chatLocation { + return false + } + if lhs.hasScheduled != rhs.hasScheduled { + return false + } + + if lhs.state != rhs.state { + return false + } + if lhs.slowMode != rhs.slowMode { + return false + } + if lhs.isSearchMode.0 != rhs.isSearchMode.0 { return false } if lhs.sidebarEnabled != rhs.sidebarEnabled { @@ -670,10 +820,6 @@ struct ChatPresentationInterfaceState: Equatable { return false } - if lhs.editState != rhs.editState { - return false - } - if lhs.inputQueryResult != rhs.inputQueryResult { return false } @@ -690,7 +836,10 @@ struct ChatPresentationInterfaceState: Equatable { return false } - if lhs.reportStatus != rhs.reportStatus { + if lhs.peerStatus != rhs.peerStatus { + return false + } + if lhs.isNotAccessible != rhs.isNotAccessible { return false } @@ -704,12 +853,15 @@ struct ChatPresentationInterfaceState: Equatable { if lhs.isEmojiSection != rhs.isEmojiSection { return false } + if lhs.failedMessageIds != rhs.failedMessageIds { + return false + } if let lhsUrlPreview = lhs.urlPreview, let rhsUrlPreview = rhs.urlPreview { if lhsUrlPreview.0 != rhsUrlPreview.0 { return false } - if !lhsUrlPreview.1.isEqual(rhsUrlPreview.1) { + if !lhsUrlPreview.1.isEqual(to: rhsUrlPreview.1) { return false } } else if (lhs.urlPreview != nil) != (rhs.urlPreview != nil) { @@ -724,16 +876,22 @@ struct ChatPresentationInterfaceState: Equatable { return false } + if lhs.inputContext != rhs.inputContext { + return false + } + if lhs.canInvokeBasicActions != rhs.canInvokeBasicActions { + return false + } return true } func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - let interface = ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:message, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + let interface = ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:message, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) if let peerId = peer?.id, let keyboardMessage = interface.keyboardButtonsMessage { if keyboardButtonsMessage?.id != keyboardMessage.id || keyboardButtonsMessage?.stableVersion != keyboardMessage.stableVersion { @@ -747,69 +905,82 @@ struct ChatPresentationInterfaceState: Equatable { } func updatedPeer(_ f: (Peer?) -> Peer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: f(self.peer), notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + + let peer = f(self.peer) + + var restrictionInfo: PeerAccessRestrictionInfo? = self.restrictionInfo + if let peer = peer as? TelegramChannel, let info = peer.restrictionInfo { + restrictionInfo = info + } + + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func updatedMainPeer(_ mainPeer: Peer?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func updatedNotificationSettings(_ notificationSettings:TelegramPeerNotificationSettings?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func updatedHistoryCount(_ historyCount:Int?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } - func updatedSearchMode(_ searchMode: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: historyCount, isSearchMode: searchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + func updatedSearchMode(_ isSearchMode: (Bool, Peer?, String?)) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer:self.peer, notificationSettings: notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: historyCount, isSearchMode: isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func updatedInputQueryResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: f(self.inputQueryResult), keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: f(self.inputQueryResult), keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func updatedInitialAction(_ initialAction:ChatInitialAction?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } - func withEditMessage(_ message:Message) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: ChatEditState(message: message), recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) - } - - func withoutEditMessage() -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: nil, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) - } func withRecordingState(_ state:ChatRecordingState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: state, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: state, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withoutRecordingState() -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: nil, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: nil, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedBlocked(_ blocked:Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: blocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: blocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedPinnedMessageId(_ messageId:MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: messageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: messageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedCachedPinnedMessage(_ cachedPinnedMessage:Message?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedPeerStatusSettings(_ peerStatus:ChatPeerStatus?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withEditMessage(_ message:Message) -> ChatPresentationInterfaceState { + return self.updatedInterfaceState({$0.withEditMessage(message)}) } - func withUpdatedReportStatus(_ reportStatus:PeerReportStatus) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + func withoutEditMessage() -> ChatPresentationInterfaceState { + return self.updatedInterfaceState({$0.withoutEditMessage()}) } func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatPresentationInterfaceState { - if let editState = self.editState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage: self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: ChatEditState(message: editState.message, state: inputState), recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) - } else { - return self.updatedInterfaceState({$0.withUpdatedInputState(inputState)}) - } + return self.updatedInterfaceState({$0.withUpdatedInputState(inputState)}) } func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } @@ -825,13 +996,17 @@ struct ChatPresentationInterfaceState: Equatable { if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } - selectedIds.insert(messageId) + if selectedIds.count < 100 { + selectedIds.insert(messageId) + } else { + NSSound.beep() + } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState?.withUpdatedSelectedIds(selectedIds), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedSelectedMessages(_ ids:Set) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: ChatInterfaceSelectionState(selectedIds: ids), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState?.withUpdatedSelectedIds(ids), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withToggledSelectedMessage(_ messageId: MessageId) -> ChatPresentationInterfaceState { @@ -839,41 +1014,112 @@ struct ChatPresentationInterfaceState: Equatable { if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } - if selectedIds.contains(messageId) { + let isSelected: Bool = selectedIds.contains(messageId) + if isSelected { let _ = selectedIds.remove(messageId) } else { - selectedIds.insert(messageId) + if selectedIds.count < 100 { + selectedIds.insert(messageId) + } else { + NSSound.beep() + } + } + + var selectionState: ChatInterfaceSelectionState = self.selectionState?.withUpdatedSelectedIds(selectedIds) ?? ChatInterfaceSelectionState(selectedIds: selectedIds, lastSelectedId: nil) + + if let event = NSApp.currentEvent { + if !event.modifierFlags.contains(.shift) { + if !isSelected { + selectionState = selectionState.withUpdatedLastSelected(messageId) + } else { + var foundBestOption: Bool = false + for id in selectedIds { + if id > messageId { + selectionState = selectionState.withUpdatedLastSelected(id) + foundBestOption = true + break + } + } + if !foundBestOption { + selectionState = selectionState.withUpdatedLastSelected(messageId) + } + } + } } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } + func withRemovedSelectedMessage(_ messageId: MessageId) -> ChatPresentationInterfaceState { + var selectedIds = Set() + if let selectionState = self.selectionState { + selectedIds.formUnion(selectionState.selectedIds) + } + let _ = selectedIds.remove(messageId) + + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState?.withUpdatedSelectedIds(selectedIds), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withoutSelectionState() -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState:nil, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: nil, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withSelectionState() -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: ChatInterfaceSelectionState(selectedIds: []), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: ChatInterfaceSelectionState(selectedIds: [], lastSelectedId: nil), sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withToggledSidebarEnabled(_ enabled: Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: enabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: enabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withToggledSidebarShown(_ shown: Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: shown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: shown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedLayout(_ layout: SplitViewState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withoutInitialAction() -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: nil, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedContactAdding(_ canAddContact:Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: canAddContact, isEmojiSection: self.isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } func withUpdatedIsEmojiSection(_ isEmojiSection:Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, editState: self.editState, recordingState: self.recordingState, isBlocked: self.isBlocked, reportStatus: self.reportStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: isEmojiSection) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction:initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedBasicActions(_ canInvokeBasicActions:(delete: Bool, forward: Bool)) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) } + + func withUpdatedIsNotAccessible(_ isNotAccessible:Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedRestrictionInfo(_ restrictionInfo:PeerAccessRestrictionInfo?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: self.limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedLimitConfiguration(_ limitConfiguration:LimitsConfiguration) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + + func withUpdatedDiscussionGroupId(_ discussionGroupId: PeerId?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: limitConfiguration, discussionGroupId: discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + func updateSlowMode(_ f: (SlowMode?)->SlowMode?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: limitConfiguration, discussionGroupId: self.discussionGroupId, slowMode: f(self.slowMode), hasScheduled: self.hasScheduled, failedMessageIds: self.failedMessageIds) + } + func withUpdatedHasScheduled(_ hasScheduled: Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: limitConfiguration, discussionGroupId: discussionGroupId, slowMode: self.slowMode, hasScheduled: hasScheduled, failedMessageIds: self.failedMessageIds) + } + func withUpdatedFailedMessageIds(_ failedMessageIds: Set) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, peer: self.peer, notificationSettings: self.notificationSettings, inputQueryResult: self.inputQueryResult, keyboardButtonsMessage:self.keyboardButtonsMessage, initialAction: self.initialAction, historyCount: self.historyCount, isSearchMode: self.isSearchMode, recordingState: self.recordingState, isBlocked: self.isBlocked, peerStatus: self.peerStatus, pinnedMessageId: self.pinnedMessageId, urlPreview: self.urlPreview, selectionState: self.selectionState, sidebarEnabled: self.sidebarEnabled, sidebarShown: self.sidebarShown, layout: self.layout, canAddContact: self.canAddContact, isEmojiSection: self.isEmojiSection, chatLocation: self.chatLocation, canInvokeBasicActions: self.canInvokeBasicActions, isNotAccessible: self.isNotAccessible, restrictionInfo: self.restrictionInfo, cachedPinnedMessage: self.cachedPinnedMessage, mainPeer: self.mainPeer, limitConfiguration: limitConfiguration, discussionGroupId: discussionGroupId, slowMode: self.slowMode, hasScheduled: self.hasScheduled, failedMessageIds: failedMessageIds) + } } diff --git a/Telegram-Mac/ChatPresentationUtils.swift b/Telegram-Mac/ChatPresentationUtils.swift new file mode 100644 index 0000000000..aa9d6fb5a7 --- /dev/null +++ b/Telegram-Mac/ChatPresentationUtils.swift @@ -0,0 +1,461 @@ +// +// ChatPresentationUtils.swift +// Telegram +// +// Created by keepcoder on 23/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +final class ChatMediaPresentation : Equatable { + + private let isIncoming: Bool + private let isBubble: Bool + + let activityBackground: NSColor + let activityForeground: NSColor + let waveformBackground: NSColor + let waveformForeground: NSColor + let text: NSColor + let grayText: NSColor + let link: NSColor + + init(isIncoming: Bool, isBubble: Bool, activityBackground: NSColor, activityForeground: NSColor, text: NSColor, grayText: NSColor, link: NSColor, waveformBackground: NSColor, waveformForeground: NSColor) { + self.isIncoming = isIncoming + self.isBubble = isBubble + self.activityForeground = activityForeground + self.activityBackground = activityBackground + self.text = text + self.grayText = grayText + self.link = link + self.waveformBackground = waveformBackground + self.waveformForeground = waveformForeground + } + + static func make(for message: Message, account: Account, renderType: ChatItemRenderType) -> ChatMediaPresentation { + let isIncoming: Bool = message.isIncoming(account, renderType == .bubble) + return ChatMediaPresentation(isIncoming: isIncoming, + isBubble: renderType == .bubble, + activityBackground: theme.chat.activityBackground(isIncoming, renderType == .bubble), + activityForeground: theme.chat.activityForeground(isIncoming, renderType == .bubble), + text: theme.chat.textColor(isIncoming, renderType == .bubble), + grayText: theme.chat.grayText(isIncoming, renderType == .bubble), + link: theme.chat.linkColor(isIncoming, renderType == .bubble), + waveformBackground: theme.chat.waveformBackground(isIncoming, renderType == .bubble), + waveformForeground: theme.chat.waveformForeground(isIncoming, renderType == .bubble)) + } + + static var empty: ChatMediaPresentation { + return .init(isIncoming: true, isBubble: true, activityBackground: .clear, activityForeground: .clear, text: .clear, grayText: .clear, link: .clear, waveformBackground: .clear, waveformForeground: .clear) + } + + var fileThumb: CGImage { + if isBubble { + return isIncoming ? theme.icons.chatFileThumbBubble_incoming : theme.icons.chatFileThumbBubble_outgoing + } else { + return theme.icons.chatFileThumb + } + } + + + var pauseThumb: CGImage { + if isBubble { + return isIncoming ? theme.icons.chatMusicPauseBubble_incoming : theme.icons.chatMusicPauseBubble_outgoing + } else { + return theme.icons.chatMusicPause + } + } + var playThumb: CGImage { + if isBubble { + return isIncoming ? theme.icons.chatMusicPlayBubble_incoming : theme.icons.chatMusicPlayBubble_outgoing + } else { + return theme.icons.chatMusicPlay + } + } + + static var Empty: ChatMediaPresentation { + return ChatMediaPresentation(isIncoming: false, isBubble: false, activityBackground: theme.colors.accent, activityForeground: theme.colors.underSelectedColor, text: theme.colors.text, grayText: theme.colors.grayText, link: theme.colors.link, waveformBackground: theme.colors.waveformBackground, waveformForeground: theme.colors.waveformForeground) + } + + static func ==(lhs: ChatMediaPresentation, rhs: ChatMediaPresentation) -> Bool { + return lhs === rhs + } +} + +private func generatePercentageImage(color: NSColor, value: Int, font: NSFont) -> CGImage { + return generateImage(CGSize(width: 36.0, height: 16.0), rotatedContext: { size, context in + + + context.clear(CGRect(origin: CGPoint(), size: size)) + + + let layout = TextViewLayout(.initialize(string: "\(value)%", color: color, font: font), maximumNumberOfLines: 1, alignment: .right) + layout.measure(width: size.width) + if !layout.lines.isEmpty { + let line = layout.lines[0] + context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + let penOffset = CGFloat( CTLineGetPenOffsetForFlush(line.line, layout.penFlush, Double(size.width))) + line.frame.minX + + context.setAllowsFontSubpixelPositioning(true) + context.setShouldSubpixelPositionFonts(true) + context.setAllowsAntialiasing(true) + context.setShouldAntialias(true) + context.setAllowsFontSmoothing(System.backingScale == 1.0) + context.setShouldSmoothFonts(System.backingScale == 1.0) + + context.textPosition = CGPoint(x: penOffset, y: line.frame.minY) + + CTLineDraw(line.line, context) + } + + })! +} + + +final class TelegramChatColors { + + + + private var _generatedPercentageAnimationImages:[CGImage]? + private var _generatedPercentageAnimationImagesIncomingBubbled:[CGImage]? + private var _generatedPercentageAnimationImagesOutgoingBubbled:[CGImage]? + private var _generatedPercentageAnimationImagesPlain:[CGImage]? + private var _generatedPercentageAnimationImagesIncomingBubbledPlain:[CGImage]? + private var _generatedPercentageAnimationImagesOutgoingBubbledPlain:[CGImage]? + + private var generatedPercentageAnimationImages:[CGImage] { + if let _generatedPercentageAnimationImages = self._generatedPercentageAnimationImages { + return _generatedPercentageAnimationImages + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.text, value: i, font: .bold(12))) + } + self._generatedPercentageAnimationImages = images + return images + } + } + private var generatedPercentageAnimationImagesIncomingBubbled:[CGImage] { + if let value = self._generatedPercentageAnimationImagesIncomingBubbled { + return value + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.textBubble_incoming, value: i, font: .bold(12))) + } + self._generatedPercentageAnimationImagesIncomingBubbled = images + return images + } + } + private var generatedPercentageAnimationImagesOutgoingBubbled:[CGImage] { + if let value = self._generatedPercentageAnimationImagesOutgoingBubbled { + return value + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.textBubble_outgoing, value: i, font: .bold(12))) + } + self._generatedPercentageAnimationImagesOutgoingBubbled = images + return images + } + } + private var generatedPercentageAnimationImagesPlain:[CGImage] { + if let value = self._generatedPercentageAnimationImagesPlain { + return value + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.text, value: i, font: .bold(12))) + } + self._generatedPercentageAnimationImagesPlain = images + return images + } + } + private var generatedPercentageAnimationImagesIncomingBubbledPlain:[CGImage] { + if let value = self._generatedPercentageAnimationImagesIncomingBubbledPlain { + return value + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.textBubble_incoming, value: i, font: .normal(12))) + } + self._generatedPercentageAnimationImagesIncomingBubbledPlain = images + return images + } + } + private var generatedPercentageAnimationImagesOutgoingBubbledPlain:[CGImage] { + if let value = self._generatedPercentageAnimationImagesOutgoingBubbledPlain { + return value + } else { + var images:[CGImage] = [] + for i in 0 ... 100 { + images.append(generatePercentageImage(color: palette.textBubble_outgoing, value: i, font: .normal(12))) + } + self._generatedPercentageAnimationImagesOutgoingBubbledPlain = images + return images + } + } + + + private let palette: ColorPalette + init(_ palette: ColorPalette, _ bubbled: Bool) { + self.palette = palette + } + + private var cacheDict: [String: CGImage] = [:] + + func messageSecretTimer(_ value: String) -> CGImage { + if let value = cacheDict[value] { + return value + } else { + let node = TextNode.layoutText(.initialize(string: value, color: theme.colors.grayIcon, font: .normal(15)), nil, 1, .end, NSMakeSize(30, 30), nil, false, .left) + + let image = generateImage(NSMakeSize(30, 30), rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + node.1.draw(rect.focus(node.0.size), in: ctx, backingScaleFactor: 1.0, backgroundColor: .clear) + })! + cacheDict[value] = image + + return image + } + + } + + //chatGotoMessageWallpaper / chatShareWallpaper / chatSwipeReplyWallpaper + + func chat_goto_message_bubble(theme: TelegramPresentationTheme) -> CGImage { + if let value = cacheDict["chat_goto_message_bubble"] { + return value + } else { + let image = NSImage(named: "Icon_GotoBubbleMessage")!.precomposed(theme.chatServiceItemTextColor) + cacheDict["chat_goto_message_bubble"] = image + return image + } + } + func chat_share_bubble(theme: TelegramPresentationTheme) -> CGImage { + if let value = cacheDict["chat_share_bubble"] { + return value + } else { + let image = NSImage(named: "Icon_ChannelShare")!.precomposed(theme.chatServiceItemTextColor) + cacheDict["chat_share_bubble"] = image + return image + } + } + func chat_reply_swipe_bubble(theme: TelegramPresentationTheme) -> CGImage { + if let value = cacheDict["chat_reply_swipe_bubble"] { + return value + } else { + let image = NSImage(named: "Icon_ChannelShare")!.precomposed(theme.chatServiceItemTextColor) + cacheDict["chat_reply_swipe_bubble"] = image + return image + } + } + func chat_like_message_bubble(theme: TelegramPresentationTheme) -> CGImage { + if let value = cacheDict["chat_like_message_bubble"] { + return value + } else { + let image = NSImage(named: "Icon_Like_MessageButton")!.precomposed(theme.chatServiceItemTextColor) + cacheDict["chat_like_message_bubble"] = image + return image + } + } + func chat_like_message_unlike_bubble(theme: TelegramPresentationTheme) -> CGImage { + if let value = cacheDict["chat_like_message_unlike_bubble"] { + return value + } else { + let image = NSImage(named: "Icon_Like_MessageButtonUnlike")!.precomposed(theme.chatServiceItemTextColor) + cacheDict["chat_like_message_unlike_bubble"] = image + return image + } + } + + private var _chatActionUrl: CGImage? + func chatActionUrl(theme: TelegramPresentationTheme) -> CGImage { + if let chatActionUrl = _chatActionUrl { + return chatActionUrl + } else { + let image = #imageLiteral(resourceName: "Icon_InlineBotUrl").precomposed(theme.chatServiceItemTextColor) + _chatActionUrl = image + return image + } + } + + func pollPercentAnimatedIcons(_ incoming: Bool, _ bubbled: Bool, from fromValue: CGFloat, to toValue: CGFloat, duration: Double) -> [CGImage] { + let minimumFrameDuration = 1.0 / 60 + let numberOfFrames = max(1, Int(duration / minimumFrameDuration)) + var images: [CGImage] = [] + + let generated = bubbled ? incoming ? generatedPercentageAnimationImagesIncomingBubbledPlain : generatedPercentageAnimationImagesOutgoingBubbledPlain : generatedPercentageAnimationImagesPlain + + for i in 0 ..< numberOfFrames { + let t = CGFloat(i) / CGFloat(numberOfFrames) + let value = (1.0 - t) * fromValue + t * toValue + images.append(generated[Int(round(value))]) + } + return images + } + + func pollPercentAnimatedIcon(_ incoming: Bool, _ bubbled: Bool, value: Int) -> CGImage { + let generated = bubbled ? incoming ? generatedPercentageAnimationImagesIncomingBubbledPlain : generatedPercentageAnimationImagesOutgoingBubbledPlain : generatedPercentageAnimationImagesPlain + return generated[max(min(generated.count - 1, value), 0)] + } + + func activityBackground(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.fileActivityBackgroundBubble_incoming : palette.fileActivityBackgroundBubble_outgoing : palette.fileActivityBackground + } + func activityForeground(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.fileActivityForegroundBubble_incoming : palette.fileActivityForegroundBubble_outgoing : palette.fileActivityForeground + } + + func webPreviewActivity(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.webPreviewActivityBubble_incoming : palette.webPreviewActivityBubble_outgoing : palette.webPreviewActivity + } + func pollOptionBorder(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return (bubbled ? incoming ? grayText(incoming, bubbled) : grayText(incoming, bubbled) : palette.grayText).withAlphaComponent(0.2) + } + func pollOptionUnselectedImage(_ incoming: Bool, _ bubbled: Bool) -> CGImage { + return bubbled ? incoming ? theme.icons.chatPollVoteUnselectedBubble_incoming : theme.icons.chatPollVoteUnselectedBubble_outgoing : theme.icons.chatPollVoteUnselected + } + func waveformBackground(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.waveformBackgroundBubble_incoming : palette.waveformBackgroundBubble_outgoing : palette.waveformBackground + } + func waveformForeground(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.waveformForegroundBubble_incoming : palette.waveformForegroundBubble_outgoing : palette.waveformForeground + } + + + + func backgroundColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? System.supportsTransparentFontDrawing ? .clear : palette.bubbleBackground_incoming : System.supportsTransparentFontDrawing ? .clear : palette.bubbleBackgroundTop_outgoing.blended(withFraction: 0.5, of: palette.bubbleBackgroundBottom_outgoing)! : palette.chatBackground + } + + func backgoundSelectedColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.bubbleBackgroundHighlight_incoming : palette.bubbleBackgroundHighlight_outgoing : palette.background + } + + func bubbleBorderColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return incoming ? palette.bubbleBorder_incoming : palette.bubbleBorder_outgoing//.clear//palette.bubbleBorder_outgoing + } + func bubbleBackgroundColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.bubbleBackground_incoming : palette.bubbleBackgroundTop_outgoing : .clear//.clear//palette.bubbleBorder_outgoing + } + + func textColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.textBubble_incoming : palette.textBubble_outgoing : palette.text + } + + func monospacedPreColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.monospacedPreBubble_incoming : palette.monospacedPreBubble_outgoing : palette.monospacedPre + } + func monospacedCodeColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.monospacedCodeBubble_incoming : palette.monospacedCodeBubble_outgoing : palette.monospacedCode + } + + func selectText(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.selectTextBubble_incoming : palette.selectTextBubble_outgoing : palette.selectText + } + + func grayText(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.grayTextBubble_incoming : palette.grayTextBubble_outgoing : palette.grayText + } + func redUI(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.redBubble_incoming : palette.redBubble_outgoing : palette.redUI + } + func greenUI(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.greenBubble_incoming : palette.greenBubble_outgoing : palette.greenUI + } + func linkColor(_ incoming: Bool, _ bubbled: Bool) -> NSColor { + return bubbled ? incoming ? palette.linkBubble_incoming : palette.linkBubble_outgoing : palette.accent + } + + func pollSelected(_ incoming: Bool, _ bubbled: Bool, icons: TelegramIconsTheme) -> CGImage { + return bubbled ? incoming ? icons.poll_selected_incoming : icons.poll_selected_outgoing : icons.poll_selected + } + func pollSelectedCorrect(_ incoming: Bool, _ bubbled: Bool, icons: TelegramIconsTheme) -> CGImage { + return bubbled ? incoming ? icons.poll_selected_correct_incoming : icons.poll_selected_correct_outgoing : icons.poll_selected_correct + } + func pollSelectedIncorrect(_ incoming: Bool, _ bubbled: Bool, icons: TelegramIconsTheme) -> CGImage { + return bubbled ? incoming ? icons.poll_selected_incorrect_incoming : icons.poll_selected_incorrect_outgoing : icons.poll_selected_incorrect + } + + func channelInfoPromo(_ incoming: Bool, _ bubbled: Bool, icons: TelegramIconsTheme) -> CGImage { + return bubbled ? incoming ? icons.channel_info_promo_bubble_incoming : icons.channel_info_promo_bubble_outgoing : icons.channel_info_promo + } + + func channelViewsIcon(_ item: ChatRowItem) -> CGImage { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatChannelViewsOverlayServiceBubble : item.presentation.icons.chatChannelViewsOverlayBubble : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatChannelViewsInBubble_incoming : item.presentation.icons.chatChannelViewsInBubble_outgoing : item.presentation.icons.chatChannelViewsOutBubble + } + func likedIcon(_ item: ChatRowItem) -> CGImage { + if item.isLiked { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chat_like_inside_bubble_service : item.presentation.icons.chat_like_inside_bubble_overlay : item.hasBubble ? item.isIncoming ? item.presentation.icons.chat_like_inside_bubble_incoming : item.presentation.icons.chat_like_inside_bubble_outgoing : item.presentation.icons.chat_like_inside + } else { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chat_like_inside_empty_bubble_service : item.presentation.icons.chat_like_inside_empty_bubble_overlay : item.hasBubble ? item.isIncoming ? item.presentation.icons.chat_like_inside_empty_bubble_incoming : item.presentation.icons.chat_like_inside_empty_bubble_outgoing : item.presentation.icons.chat_like_inside_empty + } + } + + func stateStateIcon(_ item: ChatRowItem) -> CGImage { + return item.isFailed ? item.presentation.icons.sentFailed : (item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatReadMarkServiceOverlayBubble1 : theme.icons.chatReadMarkOverlayBubble1 : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatReadMarkInBubble1_incoming : item.presentation.icons.chatReadMarkInBubble1_outgoing : item.presentation.icons.chatReadMarkOutBubble1) + } + func readStateIcon(_ item: ChatRowItem) -> CGImage { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatReadMarkServiceOverlayBubble2 : item.presentation.icons.chatReadMarkOverlayBubble2 : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatReadMarkInBubble2_incoming : item.presentation.icons.chatReadMarkInBubble2_outgoing : item.presentation.icons.chatReadMarkOutBubble2 + } + + func quizSolution(_ item: ChatRowItem) -> CGImage { + return item.hasBubble ? item.isIncoming ? item.presentation.icons.chat_quiz_explanation_bubble_incoming : item.presentation.icons.chat_quiz_explanation_bubble_outgoing : item.presentation.icons.chat_quiz_explanation + } + + func instantPageIcon(_ incoming: Bool, _ bubbled: Bool, presentation: TelegramPresentationTheme) -> CGImage { + return bubbled ? incoming ? presentation.icons.chatInstantViewBubble_incoming : presentation.icons.chatInstantViewBubble_outgoing : presentation.icons.chatInstantView + } + + func sendingFrameIcon(_ item: ChatRowItem) -> CGImage { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatSendingOverlayServiceFrame : item.presentation.icons.chatSendingOverlayFrame : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatSendingInFrame_incoming : item.presentation.icons.chatSendingInFrame_outgoing : item.presentation.icons.chatSendingOutFrame + } + func sendingHourIcon(_ item: ChatRowItem) -> CGImage { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatSendingOverlayServiceHour : item.presentation.icons.chatSendingOverlayHour : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatSendingInHour_incoming : item.presentation.icons.chatSendingInHour_outgoing : item.presentation.icons.chatSendingOutHour + } + func sendingMinIcon(_ item: ChatRowItem) -> CGImage { + return item.isStateOverlayLayout ? !item.isInteractiveMedia ? item.presentation.chatSendingOverlayServiceMin : item.presentation.icons.chatSendingOverlayMin : item.hasBubble ? item.isIncoming ? item.presentation.icons.chatSendingInMin_incoming : item.presentation.icons.chatSendingInMin_outgoing : item.presentation.icons.chatSendingOutMin + } + + func chatCallIcon(_ item: ChatCallRowItem) -> CGImage { + if item.hasBubble { + return !item.isIncoming ? (item.failed ? item.presentation.icons.chatFailedCallBubble_outgoing : item.presentation.icons.chatCallBubble_outgoing) : (item.failed ? item.presentation.icons.chatFailedCallBubble_incoming : item.presentation.icons.chatCallBubble_outgoing) + } else { + return !item.isIncoming ? (item.failed ? item.presentation.icons.chatFailedCall_outgoing : item.presentation.icons.chatCall_outgoing) : (item.failed ? item.presentation.icons.chatFailedCall_incoming : item.presentation.icons.chatCall_outgoing) + } + } + + func chatCallFallbackIcon(_ item: ChatCallRowItem) -> CGImage { + return item.hasBubble ? item.isIncoming ? item.presentation.icons.chatFallbackCallBubble_incoming : item.presentation.icons.chatFallbackCallBubble_outgoing : item.presentation.icons.chatFallbackCall + } + + func peerName(_ index: Int) -> NSColor { + let array = [theme.colors.groupPeerNameRed, + theme.colors.groupPeerNameOrange, + theme.colors.groupPeerNameViolet, + theme.colors.groupPeerNameGreen, + theme.colors.groupPeerNameCyan, + theme.colors.groupPeerNameLightBlue, + theme.colors.groupPeerNameBlue] + + return array[index] + } + + func replyTitle(_ item: ChatRowItem) -> NSColor { + return item.hasBubble ? (item.isIncoming ? item.presentation.colors.chatReplyTitleBubble_incoming : item.presentation.colors.chatReplyTitleBubble_outgoing) : item.presentation.colors.chatReplyTitle + } + func replyText(_ item: ChatRowItem) -> NSColor { + return item.hasBubble ? (item.isIncoming ? item.presentation.colors.chatReplyTextEnabledBubble_incoming : item.presentation.colors.chatReplyTextEnabledBubble_outgoing) : item.presentation.colors.chatReplyTextEnabled + } + func replyDisabledText(_ item: ChatRowItem) -> NSColor { + return item.hasBubble ? (item.isIncoming ? item.presentation.colors.chatReplyTextDisabledBubble_incoming : item.presentation.colors.chatReplyTextDisabledBubble_outgoing) : item.presentation.colors.chatReplyTextDisabled + } +} diff --git a/Telegram-Mac/ChatRecorderOverlayWindow.swift b/Telegram-Mac/ChatRecorderOverlayWindow.swift new file mode 100644 index 0000000000..2c2e19745c --- /dev/null +++ b/Telegram-Mac/ChatRecorderOverlayWindow.swift @@ -0,0 +1,332 @@ +// +// ChatRecorderOverlayWindow.swift +// Telegram +// +// Created by Mikhail Filimonov on 07/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + +private enum ChatRecordingOverlayState { + case voice + case video + case fixed +} + +private final class LockControl : View { + private let head: ImageView = ImageView() + private let body: ImageView = ImageView() + private let arrow: ImageView = ImageView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layer?.cornerRadius = frameRect.width / 2 + addSubview(head) + addSubview(arrow) + addSubview(body) + updateLocalizationAndTheme(theme: theme) + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + backgroundColor = theme.colors.background + head.image = theme.icons.chatOverlayLockerHeadRecording + head.sizeToFit() + body.image = theme.icons.chatOverlayLockerBodyRecording + body.sizeToFit() + arrow.image = theme.icons.chatOverlayLockArrowRecording + arrow.sizeToFit() + layer?.borderColor = theme.colors.accent.cgColor + layer?.borderWidth = .borderSize + } + + private var currentPercent: CGFloat = 1.0 + + override func layout() { + super.layout() + arrow.centerX(y: frame.height - arrow.frame.height - 8) + body.centerX(y: floorToScreenPixels(backingScaleFactor, (30 - body.frame.height)/2) + 3) + head.centerX(y: 4) + } + + fileprivate func updatePercent(_ percent: CGFloat) { + arrow.change(opacity: percent, animated: true) + +// let dh: CGFloat = 4 +// let dm: CGFloat = 7 +// let y = max(dm, min(dh, dm + percent * dm)) +// head.centerX(y: y) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +private class ChatRecorderOverlayView : Control { + private let innerContainer: Control = Control() + private let outerContainer: Control = Control() + private let stateView: ImageView = ImageView() + private var currentLevel: Double = 1.0 + private var previousTime: Date = Date() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layer?.cornerRadius = frameRect.width / 2 + backgroundColor = .clear + + outerContainer.setFrameSize(NSMakeSize(frameRect.width - 30, frameRect.height - 30)) + outerContainer.backgroundColor = theme.colors.accent.withAlphaComponent(0.5) + outerContainer.layer?.cornerRadius = outerContainer.frame.width / 2 + addSubview(outerContainer) + outerContainer.center() + // self.outerContainer.animates = true + + innerContainer.setFrameSize(NSMakeSize(frameRect.width - 30, frameRect.height - 30)) + innerContainer.backgroundColor = theme.colors.accent + innerContainer.layer?.cornerRadius = innerContainer.frame.width / 2 + addSubview(innerContainer) + innerContainer.center() + // self.innerContainer.animates = true + addSubview(stateView) + + } + + fileprivate func updateState(_ overlayState: ChatRecordingOverlayState) { + switch overlayState { + case .voice: + stateView.image = theme.icons.chatOverlayVoiceRecording + case .video: + stateView.image = theme.icons.chatOverlayVideoRecording + case .fixed: + stateView.image = theme.icons.chatOverlaySendRecording + } + stateView.sizeToFit() + stateView.center() + } + + func updatePeakLevel(_ peakLevel: Float) { + let power = mappingRange(Double(peakLevel), 0.3, 3, 1.05, 1.5); + if abs(self.currentLevel - power) > 0.1 || (Date().timeIntervalSinceNow - previousTime.timeIntervalSinceNow) > 0.2 { + + let previous = outerContainer.layer?.presentation()?.value(forKeyPath: "transform.scale") as? CGFloat ?? CGFloat(currentLevel) + outerContainer.layer?.animateScaleCenter(from: previous, to: CGFloat(power), duration: 0.2, removeOnCompletion:false, timingFunction: .linear) + self.currentLevel = Double(power) + self.previousTime = Date() + } + } + + func updateInside() { + innerContainer.backgroundColor = mouseInside() ? theme.colors.accent : theme.colors.redUI + outerContainer.backgroundColor = (mouseInside() ? theme.colors.accent : theme.colors.redUI).withAlphaComponent(0.5) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class ChatRecorderOverlayWindowController : NSObject { + let window: Window + private let parent: Window + private let disposable = MetaDisposable() + private let chatInteraction: ChatInteraction + private var state: ChatRecordingOverlayState + private let startMouseLocation: NSPoint + private let lockWindow: Window + init(parent: Window, chatInteraction: ChatInteraction) { + self.parent = parent + self.chatInteraction = chatInteraction + self.state = chatInteraction.presentation.recordingState is ChatRecordingAudioState ? .voice : .video + let size = NSMakeSize(120, 120) + + window = Window(contentRect: NSMakeRect(parent.frame.maxX - size.width + 25, parent.frame.minY - 35, size.width, size.height), styleMask: [], backing: .buffered, defer: true) + window.backgroundColor = .clear + window.contentView = ChatRecorderOverlayView(frame: NSMakeRect(0, 0, size.width, size.height)) + + lockWindow = Window(contentRect: NSMakeRect(window.frame.midX - 12.5, parent.frame.minY + 160, 26, 50), styleMask: [], backing: .buffered, defer: true) + lockWindow.contentView?.addSubview(LockControl(frame: NSMakeRect(0, 0, 26, 50))) + lockWindow.backgroundColor = .clear + startMouseLocation = window.mouseLocationOutsideOfEventStream + super.init() + self.view.updateState(state) + window.setFrameOrigin(NSMakePoint(minX, window.frame.minY)) + } + + private var view: ChatRecorderOverlayView { + return window.contentView as! ChatRecorderOverlayView + } + + func stopAndSend() { + if let recorder = chatInteraction.presentation.recordingState { + recorder.stop() + delay(0.1, closure: { + self.chatInteraction.mediaPromise.set(recorder.data) + closeAllModals() + }) + } + chatInteraction.update({$0.withoutRecordingState()}) + } + + func stopAndCancel() { + let proccess = { [weak self] in + guard let `self` = self else {return} + if let recorder = self.chatInteraction.presentation.recordingState { + recorder.stop() + recorder.dispose() + closeAllModals() + } + self.chatInteraction.update({$0.withoutRecordingState()}) + } + if state == .fixed { + confirm(for: parent, information: L10n.chatRecordingCancel, okTitle: L10n.alertDiscard, cancelTitle: L10n.alertNO, successHandler: { _ in + proccess() + }) + } else { + proccess() + } + } + + var minX: CGFloat { + let navigation = chatInteraction.context.sharedContext.bindings.rootNavigation() + if navigation.genericView.state == .dual, let sidebar = navigation.sidebar { + return parent.frame.maxX - window.frame.width - sidebar.frame.width + 35 + } + return parent.frame.maxX - window.frame.width + 25 + } + + private func moveWindow() -> Void { + let location = window.mouseLocationOutsideOfEventStream + let defaultY = parent.frame.minY - 35 + window.setFrameOrigin(NSMakePoint(minX, max(window.frame.minY - (startMouseLocation.y - location.y), defaultY))) + let dif = window.frame.minY - defaultY + let maxDif: CGFloat = 100 + if dif > maxDif { + hold(animated: false) + } else { + let view = self.lockControl + let dh: CGFloat = 50 + let dm: CGFloat = 30 + let percent = 1.0 - dif / 100 + let h = max(dm, min(dh, dm + percent * dm)) + view.frame = NSMakeRect(0, view.superview!.frame.height - h, view.frame.width, h) + view.updatePercent(percent) + } + } + + private func hold(animated: Bool) { + + chatInteraction.presentation.recordingState?.holdpromise.set(true) + + view.updateInside() + + let defaultY = parent.frame.minY - 35 + + self.state = .fixed + view.updateState(.fixed) + window.animator().setFrame(window.frame.offsetBy(dx: 0, dy: -(window.frame.minY - defaultY)), display: true) + parent.remove(object: self, for: .leftMouseDown) + parent.remove(object: self, for: .leftMouseUp) + window.remove(object: self, for: .leftMouseUp) + + let proccessMouseUp:(NSEvent)->KeyHandlerResult = { [weak self] _ in + guard let `self` = self else {return .rejected} + return self.proccessMouseUp() + } + parent.set(mouseHandler: proccessMouseUp, with: self, for: .leftMouseDown, priority: .modal) + window.set(mouseHandler: proccessMouseUp, with: self, for: .leftMouseDown, priority: .modal) + + parent.removeChildWindow(lockWindow) + + lockControl.change(opacity: 0, animated: animated) { [weak self] _ in + self?.lockWindow.orderOut(nil) + } + } + + private var lockControl: LockControl { + return lockWindow.contentView!.subviews.first! as! LockControl + } + + private func proccessMouseUp()-> KeyHandlerResult { + if self.view.mouseInside() { + self.stopAndSend() + } else { + self.stopAndCancel() + } + return .invoked + } + + func show(animated: Bool) { + + guard let recorder = chatInteraction.presentation.recordingState else { return } + + + parent.addChildWindow(lockWindow, ordered: .above) + parent.addChildWindow(window, ordered: .above) + + disposable.set((recorder.micLevel |> deliverOnMainQueue).start(next: { [weak self] value in + self?.view.updatePeakLevel(value) + })) + + view.layer?.animateScaleSpring(from: 0.1, to: 1.0, duration: 0.4) + view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + + parent.set(mouseHandler: { [weak self] _ -> KeyHandlerResult in + self?.view.updateInside() + return .invoked + }, with: self, for: .mouseMoved, priority: .modal) + + window.set(mouseHandler: { [weak self] _ -> KeyHandlerResult in + self?.view.updateInside() + return .invoked + }, with: self, for: .mouseMoved, priority: .modal) + + parent.set(mouseHandler: { [weak self] _ -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + self.view.updateInside() + if self.state != .fixed { + self.moveWindow() + } + return .invoked + }, with: self, for: .leftMouseDragged, priority: .modal) + + let proccessMouseUp:(NSEvent)->KeyHandlerResult = { [weak self] _ in + guard let `self` = self else {return .rejected} + return self.proccessMouseUp() + } + + parent.set(mouseHandler: { _ in return .invoked}, with: self, for: .leftMouseDown, priority: .modal) + + parent.set(mouseHandler: proccessMouseUp, with: self, for: .leftMouseUp, priority: .modal) + window.set(mouseHandler: proccessMouseUp, with: self, for: .leftMouseUp, priority: .modal) + + if recorder.autohold { + hold(animated: false) + } + + } + + func hide(animated: Bool) { + parent.removeChildWindow(window) + parent.removeChildWindow(lockWindow) + lockWindow.contentView?._change(opacity: 0, animated: true) + var strongSelf:ChatRecorderOverlayWindowController? = self + view.layer?.animateAlpha(from: 1.0, to: 0, duration: 0.2, removeOnCompletion: false, completion: { complete in + strongSelf?.window.orderOut(nil) + strongSelf?.lockWindow.orderOut(nil) + strongSelf = nil + }) + parent.removeAllHandlers(for: self) + } + + deinit { + var bp:Int = 0 + bp += 1 + } +} diff --git a/Telegram-Mac/ChatReplyPreviewController.swift b/Telegram-Mac/ChatReplyPreviewController.swift deleted file mode 100644 index bf09c8ff7c..0000000000 --- a/Telegram-Mac/ChatReplyPreviewController.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// ChatReplyPreviewController.swift -// Telegram -// -// Created by keepcoder on 04/09/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - -class ChatReplyPreviewView : View { - var container: ChatRowView? - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - } - - func update(_ message: Message, account: Account, chatInteraction: ChatInteraction) { - let item = ChatRowItem.item(frame.size, from: .MessageEntry(message, true, .Full(isAdmin: false), .FullHeader, nil), with: account, interaction: chatInteraction) - _ = item.makeSize(frame.width, oldWidth: 0) - - container?.removeFromSuperview() - let vz = item.viewClass() as! TableRowView.Type - - container = vz.init(frame:NSMakeRect(0, 0, NSWidth(self.frame), item.height)) as? ChatRowView - - container?.identifier = identifier - addSubview(container!) - - container!.setFrameSize(NSMakeSize(frame.width, item.height)) - container!.set(item: item, animated: false) - setFrameSize(NSMakeSize(frame.width, container!.frame.height + 12)) - needsLayout = true - } - - override func layout() { - super.layout() - container?.center() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class ChatReplyPreviewController: TelegramGenericViewController { - private let messageId: MessageId - private let disposable = MetaDisposable() - private let chatInteraction: ChatInteraction - init(_ account: Account, messageId: MessageId, width: CGFloat) { - self.messageId = messageId - self.chatInteraction = ChatInteraction(peerId: messageId.peerId, account: account, isLogInteraction: true) - super.init(account) - _frameRect = NSMakeRect(0, 0, width, 0) - bar = .init(height: 0) - } - - - - override func viewDidLoad() { - super.viewDidLoad() - - disposable.set((account.postbox.messageView(messageId) |> deliverOnMainQueue).start(next: { [weak self] view in - if let message = view.message, let strongSelf = self { - self?.genericView.update(message, account: strongSelf.account, chatInteraction: strongSelf.chatInteraction) - self?.readyOnce() - } - })) - } - - deinit { - disposable.dispose() - } - -} diff --git a/Telegram-Mac/ChatRightView.swift b/Telegram-Mac/ChatRightView.swift index 351299e3b8..e7c108881f 100644 --- a/Telegram-Mac/ChatRightView.swift +++ b/Telegram-Mac/ChatRightView.swift @@ -8,7 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore + class ChatRightView: View { @@ -20,72 +22,73 @@ class ChatRightView: View { private weak var item:ChatRowItem? + var isReversed: Bool { + guard let item = item else {return false} + + return item.isBubbled && !item.isIncoming + } + func set(item:ChatRowItem, animated:Bool) { self.item = item self.toolTip = item.fullDate - if let message = item.message { - if !message.flags.contains(.Incoming) && !item.chatInteraction.isLogInteraction { + if !item.isIncoming || item.isUnsent || item.isFailed + && !item.chatInteraction.isLogInteraction { + if item.isUnsent { + stateView?.removeFromSuperview() + stateView = nil + readImageView?.removeFromSuperview() + readImageView = nil + if sendingView == nil { + sendingView = SendingClockProgress() + addSubview(sendingView!) + needsLayout = true + } + } else { + + sendingView?.removeFromSuperview() + sendingView = nil - if message.flags.contains(.Unsent) { + + if let peer = item.peer as? TelegramChannel, peer.isChannel && !item.isFailed { stateView?.removeFromSuperview() stateView = nil readImageView?.removeFromSuperview() readImageView = nil - sendingView?.removeFromSuperview() - sendingView = nil - - if sendingView == nil { - sendingView = SendingClockProgress() - sendingView?.setFrameOrigin(0,2) - addSubview(sendingView!) - } } else { + let stateImage = item.presentation.chat.stateStateIcon(item) - sendingView?.removeFromSuperview() - sendingView = nil - + if stateView == nil { + stateView = ImageView() + self.addSubview(stateView!) + } - if let peer = item.peer as? TelegramChannel, case .broadcast = peer.info { - stateView?.removeFromSuperview() - stateView = nil - readImageView?.removeFromSuperview() - readImageView = nil - } else { - let stateImage = message.flags.contains(.Failed) ? theme.icons.sentFailed : theme.icons.chatReadMark1 - - if stateView == nil { - stateView = ImageView() - self.addSubview(stateView!) + if item.isRead && !item.isFailed && !item.isStorage { + if readImageView == nil { + readImageView = ImageView() + addSubview(readImageView!) } - if item.isRead && !message.flags.contains(.Failed) { - if readImageView == nil { - readImageView = ImageView(frame: NSMakeRect(0, 0, theme.icons.chatReadMark2.backingSize.width, theme.icons.chatReadMark2.backingSize.height)) - addSubview(readImageView!) - } - - } else { - readImageView?.removeFromSuperview() - readImageView = nil - } - - stateView?.image = stateImage - stateView?.setFrameSize(NSMakeSize(stateImage.backingSize.width, stateImage.backingSize.height)) + } else { + readImageView?.removeFromSuperview() + readImageView = nil } - + + stateView?.image = stateImage + stateView?.setFrameSize(NSMakeSize(stateImage.backingSize.width, stateImage.backingSize.height)) } - } else { - stateView?.removeFromSuperview() - stateView = nil - readImageView?.removeFromSuperview() - readImageView = nil - sendingView?.removeFromSuperview() - sendingView = nil + } - readImageView?.image = theme.icons.chatReadMark2 - self.sendingView?.backgroundColor = theme.colors.background + } else { + stateView?.removeFromSuperview() + stateView = nil + readImageView?.removeFromSuperview() + readImageView = nil + sendingView?.removeFromSuperview() + sendingView = nil } - + readImageView?.image = item.presentation.chat.readStateIcon(item) + readImageView?.sizeToFit() + sendingView?.set(item: item) self.needsLayout = true } @@ -93,48 +96,97 @@ class ChatRightView: View { override func layout() { super.layout() - if let item = item, let message = item.message { + if let item = item { var rightInset:CGFloat = 0 if let date = item.date { - rightInset = date.0.size.width + 20 + if !isReversed { + rightInset = date.0.size.width + (item.isBubbled ? 16 : 20) + } } if let stateView = stateView { - stateView.setFrameOrigin(frame.width - rightInset, message.flags.contains(.Failed) ? 0 : 2) + rightInset += (isReversed ? stateView.frame.width : 0) + if isReversed { + rightInset += 3 + } + if item.isFailed { + rightInset -= 2 + } + stateView.setFrameOrigin(frame.width - rightInset - item.stateOverlayAdditionCorner, item.isFailed ? (item.isStateOverlayLayout ? 2 : 1) : (item.isStateOverlayLayout ? 3 : 2)) } + + if let sendingView = sendingView { + if isReversed { + sendingView.setFrameOrigin(frame.width - sendingView.frame.width - item.stateOverlayAdditionCorner, (item.isStateOverlayLayout ? 2 : 1)) + } else { + sendingView.setFrameOrigin(frame.width - rightInset - item.stateOverlayAdditionCorner, (item.isStateOverlayLayout ? 2 : 1)) + } + } + + if let readImageView = readImageView { - readImageView.setFrameOrigin((frame.width - rightInset) + 4, 2) + readImageView.setFrameOrigin((frame.width - rightInset) + 4 - item.stateOverlayAdditionCorner, (item.isStateOverlayLayout ? 3 : 2)) } } self.setNeedsDisplay() } + override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) if let item = item { + + if item.isStateOverlayLayout { + ctx.round(frame.size, frame.height/2) + ctx.setFillColor(item.stateOverlayBackgroundColor.cgColor) + ctx.fill(layer.bounds) + } + + // super.draw(layer, in: ctx) + + let additional: CGFloat = 0 + if let date = item.date { - date.1.draw(NSMakeRect(NSWidth(layer.bounds) - date.0.size.width, 0, date.0.size.width, date.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + date.1.draw(NSMakeRect(frame.width - date.0.size.width - (isReversed ? 16 : 0) - item.stateOverlayAdditionCorner - additional, item.isBubbled ? (item.isStateOverlayLayout ? 2 : 1) : 0, date.0.size.width, date.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + if let editLabel = item.editedLabel { + editLabel.1.draw(NSMakeRect(frame.width - date.0.size.width - editLabel.0.size.width - item.stateOverlayAdditionCorner - (isReversed || (stateView != nil) ? 23 : 5), item.isBubbled ? (item.isStateOverlayLayout ? 2 : 1) : 0, editLabel.0.size.width, editLabel.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } } + + var viewsOffset: CGFloat = 0 + + if let likes = item.likes { + viewsOffset += likes.0.size.width + 18 + let icon = item.presentation.chat.likedIcon(item) + + ctx.draw(icon, in: NSMakeRect(likes.0.size.width + 2 + item.stateOverlayAdditionCorner, item.isBubbled ? (item.isStateOverlayLayout ? 1 : 0) : 0, icon.backingSize.width, icon.backingSize.height)) + + likes.1.draw(NSMakeRect(item.stateOverlayAdditionCorner, item.isBubbled ? (item.isStateOverlayLayout ? 2 : 1) : 0, likes.0.size.width, likes.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + } + if let channelViews = item.channelViews { - ctx.draw(theme.icons.chatChannelViews, in: NSMakeRect(channelViews.0.size.width + 2, 0, theme.icons.chatChannelViews.backingSize.width, theme.icons.chatChannelViews.backingSize.height)) + let icon = item.presentation.chat.channelViewsIcon(item) + ctx.draw(icon, in: NSMakeRect(channelViews.0.size.width + 2 + item.stateOverlayAdditionCorner + viewsOffset, item.isBubbled ? (item.isStateOverlayLayout ? 1 : 0) : 0, icon.backingSize.width, icon.backingSize.height)) - channelViews.1.draw(NSMakeRect(0, 0, channelViews.0.size.width, channelViews.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + channelViews.1.draw(NSMakeRect(item.stateOverlayAdditionCorner + viewsOffset, item.isBubbled ? (item.isStateOverlayLayout ? 2 : 1) : 0, channelViews.0.size.width, channelViews.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) if let postAuthor = item.postAuthor { - postAuthor.1.draw(NSMakeRect(theme.icons.chatChannelViews.backingSize.width + channelViews.0.size.width + 8, 0, postAuthor.0.size.width, postAuthor.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + postAuthor.1.draw(NSMakeRect(icon.backingSize.width + channelViews.0.size.width + 8 + item.stateOverlayAdditionCorner + viewsOffset, item.isBubbled ? (item.isStateOverlayLayout ? 2 : 1) : 0, postAuthor.0.size.width, postAuthor.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } - } else { - if let editLabel = item.editedLabel { - editLabel.1.draw(NSMakeRect(0, 0, editLabel.0.size.width, editLabel.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } } + } } + override func mouseUp(with event: NSEvent) { + superview?.mouseUp(with: event) + } + deinit { var bp:Int = 0 diff --git a/Telegram-Mac/ChatRowItem.swift b/Telegram-Mac/ChatRowItem.swift index 14543a3d55..e0f561673a 100644 --- a/Telegram-Mac/ChatRowItem.swift +++ b/Telegram-Mac/ChatRowItem.swift @@ -8,9 +8,11 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + let simpleDif:Int32 = 10 * 60 @@ -34,16 +36,6 @@ func makeChatItems(items:[(Int,TableRowItem)], maxHeight:CGFloat? = nil) -> [(In -let userChatColors:[Int:NSColor] = { - var colors:[Int:NSColor] = [:] - colors[0] = NSColor(0xce5247); - colors[1] = NSColor(0xcda322); - colors[2] = NSColor(0x5eaf33); - colors[3] = NSColor(0x468ec4); - colors[4] = NSColor(0xac6bc8); - colors[5] = NSColor(0xe28941); - return colors -}() enum ForwardItemType { @@ -54,41 +46,48 @@ enum ForwardItemType { } enum ChatItemType : Equatable { - case Full(isAdmin: Bool) + case Full(rank: String?) case Short } -func ==(lhs: ChatItemType, rhs: ChatItemType) -> Bool { - switch lhs { - case .Full(let isAdmin): - if case .Full(isAdmin: isAdmin) = rhs { - return true - } else { - return false - } - case .Short: - if case .Short = rhs { - return true - } else { - return false - } - } +enum ChatItemRenderType { + case bubble + case list } class ChatRowItem: TableRowItem { private(set) var chatInteraction:ChatInteraction - var account:Account! + let context: AccountContext private(set) var peer:Peer? private(set) var entry:ChatHistoryEntry private(set) var message:Message? - private(set) var fontSize:Int32 = 13 - private(set) var itemType:ChatItemType = .Full(isAdmin: false) + + var messages: [Message] { + if let message = message { + return [message] + } + return [] + } + + private(set) var itemType:ChatItemType = .Full(rank: nil) + + var isFullItemType: Bool { + if case .Full = itemType { + return true + } else { + return false + } + } //right view private(set) var date:(TextNodeLayout,TextNode)? + private(set) var likesNode:TextNode? + private(set) var likes:(TextNodeLayout,TextNode)? + private(set) var likesAttributed:NSAttributedString? + private(set) var channelViewsNode:TextNode? private(set) var channelViews:(TextNodeLayout,TextNode)? private(set) var channelViewsAttributed:NSAttributedString? @@ -99,8 +98,10 @@ class ChatRowItem: TableRowItem { private(set) var editedLabel:(TextNodeLayout,TextNode)? - var fullDate:String? - + private(set) var fullDate:String? + private(set) var forwardHid: String? + private(set) var nameHide: String? + var forwardType:ForwardItemType? { didSet { @@ -123,7 +124,8 @@ class ChatRowItem: TableRowItem { var forwardNameLayout:TextViewLayout? var captionLayout:TextViewLayout? private(set) var authorText:TextViewLayout? - + private(set) var adminBadge:TextViewLayout? + var replyModel:ReplyModel? var replyMarkupModel:ReplyMarkupNode? @@ -137,10 +139,20 @@ class ChatRowItem: TableRowItem { var topInset:CGFloat { return 2 } - let defaultContentTopOffset:CGFloat = 6 + var defaultContentTopOffset:CGFloat { + if isBubbled { + return 10 + } else { + return 6 + } + } var rightInset:CGFloat { - return chatInteraction.presentation.selectionState != nil ? 42.0 : 20.0 + if isBubbled { + return 15 + } else { + return chatInteraction.presentation.selectionState != nil ? 42.0 : 20.0 + } } let leftInset:CGFloat = 20 @@ -150,9 +162,43 @@ class ChatRowItem: TableRowItem { } var _contentSize:NSSize = NSZeroSize; + var previousBlockWidth:CGFloat = 0; + + var bubbleDefaultInnerInset: CGFloat { + return bubbleContentInset * 2 + additionBubbleInset + } - public var blockSize:NSSize { - return NSMakeSize(width - contentOffset.x - rightSize.width - 44, height) + var blockWidth:CGFloat { + + var widthForContent: CGFloat = 0 + + if isBubbled { + + var tempWidth: CGFloat = width - self.contentOffset.x - bubbleDefaultInnerInset - (20 + 10 + additionBubbleInset) - 20 + + if isSharable || isStorage { + tempWidth -= 35 + } + if isLikable { + tempWidth -= 35 + } + widthForContent = min(tempWidth, 450) + + + } else { + if case .Full = itemType { + let additionWidth:CGFloat = date?.0.size.width ?? 20 + widthForContent = width - self.contentOffset.x - 44 - additionWidth + } else { + widthForContent = width - self.contentOffset.x - rightSize.width - 44 + } + } + + if forwardType != nil { + widthForContent -= leftContentInset + } + + return widthForContent } public var rightSize:NSSize { @@ -160,81 +206,179 @@ class ChatRowItem: TableRowItem { var size:NSSize = NSZeroSize if let date = date { - size = NSMakeSize(date.0.size.width, 16) + size = NSMakeSize(date.0.size.width, isBubbled && !isFailed ? 15 : 16) } - if let message = message { - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - size.width += 0 - } else { - if !message.flags.contains(.Incoming) { + if let peer = peer as? TelegramChannel, case .broadcast = peer.info, (!isUnsent && !isFailed) { + size.width += 0 + } else { + if (!isIncoming || (isUnsent || isFailed)) && date != nil { + if isBubbled { + size.width += 16 + if isFailed { + size.width += 4 + } + } else { size.width += 20 } } - - - if let channelViews = channelViews { - size.width += channelViews.0.size.width + 8 + 16 - } - if let postAuthor = postAuthor { - size.width += postAuthor.0.size.width + 8 - } - - if let editedLabel = editedLabel { - size.width += editedLabel.0.size.width + 8 - } } - size.width = max(50,size.width) + + if let channelViews = channelViews { + size.width += channelViews.0.size.width + 8 + 16 + } + if let likes = likes { + size.width += likes.0.size.width + 18 + } + + if let postAuthor = postAuthor { + size.width += postAuthor.0.size.width + 8 + } + + if let editedLabel = editedLabel { + size.width += editedLabel.0.size.width + 7 + } + + size.width = max(isBubbled ? size.width : 54, size.width) + + size.width += stateOverlayAdditionCorner * 2 + size.height = isStateOverlayLayout ? 17 : size.height return size } - public var contentSize:NSSize { + var stateOverlayAdditionCorner: CGFloat { + return isStateOverlayLayout ? 5 : 0 + } + + var contentSize:NSSize { return _contentSize } + var realContentSize: NSSize { + return _contentSize + } + + var isSticker: Bool { + let file = message?.media.first as? TelegramMediaFile + return file?.isStaticSticker == true || file?.isAnimatedSticker == true + } + override var height: CGFloat { var height:CGFloat = self.contentSize.height + _defaultHeight + + if !isBubbled, case .Full = self.itemType, self is ChatMessageItem { + height += 2 + } + if let captionLayout = captionLayout { - height += captionLayout.layoutSize.height + defaultContentTopOffset + height += captionLayout.layoutSize.height + defaultContentInnerInset } if let replyMarkupModel = replyMarkupModel { - height += replyMarkupModel.size.height + defaultContentTopOffset + height += replyMarkupModel.size.height + defaultReplyMarkupInset } + + if isBubbled { + if let additional = additionalLineForDateInBubbleState { + height += additional + } + + if hasPhoto || replyModel?.isSideAccessory == true { + height = max(48, height) + } + +// if self is ChatMessageItem { +// height += 4 +// } else { +// height += 2 +// } + //height = max(height, 40) + + //height = max(height, 48) + } + return max(rightSize.height + 8, height) } + var defaultReplyMarkupInset: CGFloat { + return (isBubbled ? 4 : defaultContentInnerInset) + } + + var defaultContentInnerInset: CGFloat { + return 6 + } + + var elementsContentInset: CGFloat { + return 0 + } + var replyOffset:CGFloat { var top:CGFloat = defaultContentTopOffset - + if isBubbled && authorText != nil { + top -= topInset + } if let author = authorText { - top += author.layoutSize.height + defaultContentTopOffset + top += author.layoutSize.height + defaultContentInnerInset } return top } + var isBubbleFullFilled: Bool { + return false + } + + var isStateOverlayLayout: Bool { + if let message = message, let media = message.media.first { + if let file = media as? TelegramMediaFile { + if file.isStaticSticker || file.isAnimatedSticker { + return isBubbled + } + } + if media is TelegramMediaDice { + return isBubbled + } + if let media = media as? TelegramMediaMap { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout { + var time:TimeInterval = Date().timeIntervalSince1970 + time -= context.timeDifference + if Int32(time) < message.timestamp + liveBroadcastingTimeout { + return false + } + } + return media.venue == nil + } + return isBubbled && media.isInteractiveMedia && captionLayout == nil + } + return false + } + + private(set) var isForceRightLine: Bool = false + var forwardHeaderInset:NSPoint { - var top:CGFloat = 0 + var top:CGFloat = defaultContentTopOffset + + if !isBubbled, forwardHeader == nil { + top -= topInset + } if let author = authorText { - top += author.layoutSize.height + 7 + top += author.layoutSize.height } return NSMakePoint(defLeftInset, top) } var forwardNameInset:NSPoint { + var top:CGFloat = forwardHeaderInset.y - var top:CGFloat = forwardHeaderInset.y + 4 - - if let header = forwardHeader { - top += header.0.size.height + 4 + if let header = forwardHeader, !isBubbled { + top += header.0.size.height + defaultContentInnerInset } return NSMakePoint(self.contentOffset.x, top) @@ -245,7 +389,50 @@ class ChatRowItem: TableRowItem { } var defLeftInset:CGFloat { - return leftInset + 36 + 10 + var inset: CGFloat = leftInset + if isBubbled { + if hasPhoto { + inset += 36 + 6 + } + } else { + inset += 36 + 10 + } + + return inset + } + + var hasPhoto: Bool { + if !isBubbled { + if case .Full = itemType { + return true + } else { + return false + } + } else { + if case .Full = itemType, let message = message, let peer = message.peers[message.id.peerId] { + + switch chatInteraction.chatLocation { + case .peer: + if isIncoming && message.id.peerId == context.peerId { + return true + } + if !peer.isUser && !peer.isSecretChat && !peer.isChannel && isIncoming { + return true + } + } + } + } + if chatInteraction.isGlobalSearchMessage { + return true + } + return false + } + + var isInstantVideo: Bool { + if let media = message?.media.first as? TelegramMediaFile { + return media.isInstantVideo + } + return false } var contentOffset:NSPoint { @@ -254,57 +441,210 @@ class ChatRowItem: TableRowItem { var top:CGFloat = defaultContentTopOffset + if let author = authorText { - top += author.layoutSize.height + topInset + top += author.layoutSize.height + if !isBubbled { + top += topInset + } } if let replyModel = replyModel { - top += max(34, replyModel.size.height) + 8 + var apply: Bool = true + if isBubbled { + if !hasBubble { + apply = false + } + } + if apply { + top += max(34, replyModel.size.height) + ((!isBubbleFullFilled && isBubbled && self is ChatMediaItem) ? 0 : 8) + if (authorText != nil) && self is ChatMessageItem { + top += topInset + //top -= defaultContentInnerInset + } else if hasBubble && self is ChatMessageItem { + top -= topInset + } + } } - if let forwardNameLayout = forwardNameLayout { - top += forwardNameLayout.layoutSize.height + topInset + if let forwardNameLayout = forwardNameLayout, !isBubbled || !isInstantVideo { + top += forwardNameLayout.layoutSize.height + //if !isBubbled { + top += 2 + //} } - if let forwardType = forwardType { + if let forwardType = forwardType, !isBubbled { if forwardType == .FullHeader || forwardType == .ShortHeader { if let forwardHeader = forwardHeader { - top += forwardHeader.0.size.height + 6 - } - } else { - if self is ChatMessageItem { - top -= topInset + top += forwardHeader.0.size.height + defaultContentInnerInset + } else { + top += bubbleDefaultInnerInset } } - + } + + if isBubbled, self is ChatMessageItem { + top -= 1 } if forwardNameLayout != nil { - left += 10 + left += leftContentInset } - if isGame { - left += 10 - } + return NSMakePoint(left, top) } + var leftContentInset: CGFloat { + return 10 + } + private(set) var isRead:Bool = false - private(set) var isGame:Bool = false override var stableId: AnyHashable { return entry.stableId } + var isStorage: Bool { + if let message = message { + for attr in message.attributes { + if let attr = attr as? SourceReferenceMessageAttribute { + if authorIsChannel { + return true + } + return (chatInteraction.peerId == context.peerId && context.peerId != attr.messageId.peerId) + } + } + + } + return false + } + + var isSelectedMessage: Bool { + if let message = message { + return chatInteraction.presentation.isSelectedMessageId(message.id) + } + return false + } + + + func gotoSourceMessage() { + if let message = message { + for attr in message.attributes { + if let attr = attr as? SourceReferenceMessageAttribute { + chatInteraction.openInfo(attr.messageId.peerId, true, attr.messageId, nil) + } + } + } + } + + var isVideoOrBigEmoji: Bool { + return self is ChatVideoMessageItem || (message != nil && bigEmojiMessage(context.sharedContext, message: message!)) + } + + func share() { + if let message = message { + showModal(with: ShareModalController(ShareMessageObject(context, message)), for: mainWindow) + } + } + + var authorIsChannel: Bool { + guard let message = message else { + return false + } + return ChatRowItem.authorIsChannel(message: message, account: context.account) + } + + private static func authorIsChannel(message: Message, account: Account) -> Bool { + + let isCrosspostFromChannel = message.isCrosspostFromChannel(account: account) + + var sourceReference: SourceReferenceMessageAttribute? + for attribute in message.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + sourceReference = attribute + break + } + } + + var authorIsChannel: Bool = false + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + if case .broadcast = peer.info { + + } else { + if isCrosspostFromChannel, let sourceReference = sourceReference, let _ = message.peers[sourceReference.messageId.peerId] as? TelegramChannel { + authorIsChannel = true + } + } + } else { + if isCrosspostFromChannel, let _ = message.forwardInfo?.source as? TelegramChannel { + authorIsChannel = true + } + } + + + return authorIsChannel + } + + var isLikable: Bool { + return false + } + + var isLiked: Bool { + return false + } + + func toggleLike() { + + } + + override func copyAndUpdate(animated: Bool) { + if let table = self.table { + let item = ChatRowItem.item(table.frame.size, from: self.entry, interaction: self.chatInteraction, downloadSettings: self.downloadSettings, theme: self.presentation) + _ = item.makeSize(table.frame.width, oldWidth: 0) + let transaction = TableUpdateTransition(deleted: [], inserted: [], updated: [(self.index, item)], animated: animated) + table.merge(with: transaction) + } + } + + var shareVisible: Bool { + + guard let message = message else { + return false + } + + if isSharable { + if message.isScheduledMessage || message.flags.contains(.Sending) || message.flags.contains(.Failed) || message.flags.contains(.Unsent) { + return false + } else { + return true + } + } + return false + } + var isSharable: Bool { var peers:[Peer] = [] if let peer = peer { peers.append(peer) } - if let info = message?.forwardInfo { - peers.append(info.author) + + guard let message = message else { + return false + } + + if authorIsChannel { + return false + } + + + if let info = message.forwardInfo { + if let author = info.author { + peers.append(author) + } if let peer = info.source { peers.append(peer) @@ -322,11 +662,36 @@ class ChatRowItem: TableRowItem { } if let peer = peer as? TelegramUser { if peer.botInfo != nil { - return self is ChatMediaItem && !chatInteraction.isLogInteraction + if self is ChatMediaItem && !chatInteraction.isLogInteraction { + return true + } else if let item = self as? ChatMessageItem { + return item.webpageLayout != nil + } + return false } } } + + return false + } + + let isScam: Bool + private(set) var isForwardScam: Bool + + var isFailed: Bool { + if let message = message { + return message.flags.contains(.Failed) + } + return false + } + + let isIncoming: Bool + + var isUnsent: Bool { + if let message = message { + return message.flags.contains(.Unsent) + } return false } @@ -335,19 +700,19 @@ class ChatRowItem: TableRowItem { if let peer = peer { peers.append(peer) } - if let info = message?.forwardInfo { - peers.append(info.author) + if let info = message?.forwardInfo?.author { + peers.append(info) } for peer in peers { - if let peer = peer as? TelegramChannel { - switch peer.info { - case .broadcast: - return false - default: - break - } - } +// if let peer = peer as? TelegramChannel { +// switch peer.info { +// case .broadcast: +// return false +// default: +// break +// } +// } if let peer = peer as? TelegramUser { if peer.botInfo != nil { return false @@ -356,6 +721,9 @@ class ChatRowItem: TableRowItem { } if let message = message { + if message.isScheduledMessage { + return false + } for attr in message.attributes { if attr is InlineBotMessageAttribute { return false @@ -368,96 +736,474 @@ class ChatRowItem: TableRowItem { } } - return !chatInteraction.isLogInteraction + return !chatInteraction.isLogInteraction && message?.groupingKey == nil && message?.id.peerId != context.peerId + } + + private static func canFillAuthorName(_ message: Message, chatInteraction: ChatInteraction, renderType: ChatItemRenderType, isIncoming: Bool, hasBubble: Bool) -> Bool { + var canFillAuthorName: Bool = true + switch chatInteraction.chatLocation { + case .peer: + if renderType == .bubble, let peer = messageMainPeer(message) { + canFillAuthorName = isIncoming && (peer.isGroup || peer.isSupergroup || message.id.peerId == chatInteraction.context.peerId) + if let media = message.media.first { + canFillAuthorName = canFillAuthorName && !media.isInteractiveMedia && hasBubble && isIncoming + } else if bigEmojiMessage(chatInteraction.context.sharedContext, message: message) { + canFillAuthorName = false + } + + } + } + return canFillAuthorName + } + + var canFillAuthorName: Bool { + if let message = message { + return ChatRowItem.canFillAuthorName(message, chatInteraction: chatInteraction, renderType: renderType, isIncoming: isIncoming, hasBubble: hasBubble) + } + return true + } + + var isBubbled: Bool { + return renderType == .bubble + } + + var psaButton: NSAttributedString? { + if let info = message?.forwardInfo?.psaType { + let text = localizedPsa("psa.text", type: info) + + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.text), textColor: .white), bold: MarkdownAttributeSet(font: .bold(.text), textColor: .white), link: MarkdownAttributeSet(font: .normal(.text), textColor: .link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { url in + execute(inapp: .external(link: url, false)) + })) + })) + return attributedText + } + return nil + } + + var isPsa: Bool { + return message?.forwardInfo?.psaType != nil + } + + var hasBubble: Bool + + static func hasBubble(_ message: Message?, entry: ChatHistoryEntry, type: ChatItemType, sharedContext: SharedAccountContext) -> Bool { + if let message = message, let media = message.media.first { + + if let file = media as? TelegramMediaFile { + if file.isStaticSticker { + return false + } + if file.isAnimatedSticker { + return false + } + if file.isInstantVideo { + return false //!message.text.isEmpty || (message.replyAttribute != nil && !file.isInstantVideo) || (message.forwardInfo != nil && !file.isInstantVideo) + } + } + if media is TelegramMediaDice { + return false + } + + for attr in message.attributes { + if let _ = attr as? InlineBotMessageAttribute { + return true + } + } + + var peer: Peer? + for attr in message.attributes { + if let _ = attr as? SourceReferenceMessageAttribute { + if let info = message.forwardInfo { + peer = info.author + } + break + } + } + + if let _peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = _peer.info { + peer = _peer + } else if let author = message.effectiveAuthor, peer == nil { + if author is TelegramSecretChat { + peer = messageMainPeer(message) + } else { + peer = author + } + } + + if message.groupInfo != nil { + switch entry { + case .groupedPhotos(let entries, _): + return !message.text.isEmpty || message.replyAttribute != nil || message.forwardInfo != nil || entries.count == 1 + default: + return true + } + } + + } else if let message = message { + return !bigEmojiMessage(sharedContext, message: message) + } + return true } - init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account:Account, _ object: ChatHistoryEntry) { + let renderType: ChatItemRenderType + var bubbleImage:(CGImage, NSEdgeInsets)? = nil + var bubbleBorderImage:(CGImage, NSEdgeInsets)? = nil + + let downloadSettings: AutomaticMediaDownloadSettings + + let presentation: TelegramPresentationTheme + + + + private var _approximateSynchronousValue: Bool = false + var approximateSynchronousValue: Bool { + get { + let result = _approximateSynchronousValue + _approximateSynchronousValue = false + return result + } + } + + private var _avatarSynchronousValue: Bool = false + var avatarSynchronousValue: Bool { + get { + let result = _avatarSynchronousValue + _avatarSynchronousValue = false + return result + } + } + + var forceBackgroundColor: NSColor? = nil + + init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { self.entry = object - self.account = account + self.context = chatInteraction.context + self.presentation = theme self.chatInteraction = chatInteraction + self.downloadSettings = downloadSettings + self._approximateSynchronousValue = Thread.isMainThread + self._avatarSynchronousValue = Thread.isMainThread + var message: Message? + var isRead: Bool = true + var itemType: ChatItemType = .Full(rank: nil) + var fwdType: ForwardItemType? = nil + var renderType:ChatItemRenderType = .list + var object = object - if case let .MessageEntry(message,isRead,itemType, fwdType, _) = object { + var hiddenFwdTooltip:(()->Void)? = nil + + var captionMessage: Message? = object.message + + var hasGroupCaption: Bool = object.message?.text.isEmpty == false + if case let .groupedPhotos(entries, _) = object { + object = entries.filter({!$0.message!.media.isEmpty}).last! - self.itemType = itemType - self.message = message - self.isRead = isRead - self.isGame = message.media.first is TelegramMediaGame - if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { - self.peer = peer - - if let author = message.author, author.id != peer.id, !message.flags.contains(.Unsent), !message.flags.contains(.Failed) { - postAuthorAttributed = .initialize(string: author.displayTitle, color: theme.colors.grayText, font: NSFont.normal(.short)) + loop: for entry in entries { + if let _ = captionMessage, !entry.message!.text.isEmpty { + captionMessage = nil + hasGroupCaption = false + break loop + } + if !entry.message!.text.isEmpty { + captionMessage = entry.message! + hasGroupCaption = true } + } + if captionMessage == nil { + captionMessage = object.message! + } + } + + if case let .MessageEntry(_message, _, _isRead, _renderType, _itemType, _fwdType, _) = object { + message = _message + isRead = _isRead + itemType = _itemType + switch _itemType { + case .Full: + fwdType = .FullHeader + default: + fwdType = _fwdType + } + renderType = _renderType + } + + var stateOverlayTextColor: NSColor { + if let media = message?.media.first, media.isInteractiveMedia { + return NSColor(0xffffff) + } else { + return theme.chatServiceItemTextColor + } + } + + var isStateOverlayLayout: Bool { + if renderType == .bubble, let message = captionMessage, let media = message.media.first { + if let file = media as? TelegramMediaFile { + if file.isStaticSticker || file.isAnimatedSticker { + return renderType == .bubble + } + + } + if media is TelegramMediaDice { + return renderType == .bubble + } + if let media = media as? TelegramMediaMap { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout { + var time:TimeInterval = Date().timeIntervalSince1970 + time -= context.timeDifference + if Int32(time) < message.timestamp + liveBroadcastingTimeout { + return false + } + } + return media.venue == nil + } + return media.isInteractiveMedia && !hasGroupCaption + } else if let message = message, bigEmojiMessage(context.sharedContext, message: message), renderType == .bubble { + return true + } + return false + } + + if message?.id.peerId == context.peerId { + itemType = .Full(rank: nil) + } + self.renderType = renderType + self.message = message + + var isForwardScam: Bool = false + var isScam = false + if let message = message, let peer = messageMainPeer(message) { + if peer.isGroup || peer.isSupergroup { + if let author = message.forwardInfo?.author { + isForwardScam = author.isScam + } + if let author = message.author, case .Full = itemType { + isScam = author.isScam + } + } + } + self.isScam = isScam + self.isForwardScam = isForwardScam - } else if let author = message.author { - if author is TelegramSecretChat { - peer = messageMainPeer(message) + if let message = message { + let isBubbled = renderType == .bubble + let hasBubble = ChatRowItem.hasBubble(captionMessage ?? message, entry: entry, type: itemType, sharedContext: context.sharedContext) + self.hasBubble = isBubbled && hasBubble + + let isIncoming: Bool = message.isIncoming(context.account, renderType == .bubble) + self.isIncoming = isIncoming + + + if case .bubble = renderType , hasBubble{ + let isFull: Bool + if case .Full = itemType { + isFull = true + } else { - peer = author + isFull = false + } + let icons = presentation.icons + let neighbors: MessageBubbleImageNeighbors = isFull && !message.isHasInlineKeyboard ? .none : .both + bubbleImage = isIncoming ? (neighbors == .none ? icons.chatBubble_none_incoming_withInset : icons.chatBubble_both_incoming_withInset) : (neighbors == .none ? icons.chatBubble_none_outgoing_withInset : icons.chatBubble_both_outgoing_withInset) + bubbleBorderImage = isIncoming ? (neighbors == .none ? icons.chatBubbleBorder_none_incoming_withInset : icons.chatBubbleBorder_both_incoming_withInset) : (neighbors == .none ? icons.chatBubbleBorder_none_outgoing_withInset : icons.chatBubbleBorder_both_outgoing_withInset) + } + + self.itemType = itemType + self.isRead = isRead + + if let info = message.forwardInfo, chatInteraction.peerId == context.account.peerId || (object.renderType == .list && info.psaType != nil) { + if info.author == nil, let signature = info.authorSignature { + self.peer = TelegramUser(id: PeerId(namespace: 0, id: 0), accessHash: nil, firstName: signature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + } else if (object.renderType == .list && info.psaType != nil) { + self.peer = info.author ?? message.chatPeer(context.peerId) + } else { + self.peer = message.chatPeer(context.peerId) + } + } else { + self.peer = message.chatPeer(context.peerId) + } + + var isHasSource: Bool = false + + for attr in message.attributes { + if let _ = attr as? SourceReferenceMessageAttribute { + isHasSource = true + break } } - if let peer = messageMainPeer(message) as? TelegramUser, peer.botInfo != nil || peer.id == account.peerId { + if let peer = peer, peer.isChannel { + for attr in message.attributes { + if let attr = attr as? AuthorSignatureMessageAttribute { + if !message.flags.contains(.Failed) { + postAuthorAttributed = .initialize(string: attr.signature, color: isStateOverlayLayout ? stateOverlayTextColor : !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: renderType == .bubble ? .italic(.small) : .normal(.short)) + } + break + } + } + } + if postAuthorAttributed == nil, ChatRowItem.authorIsChannel(message: message, account: context.account) { + if let author = message.forwardInfo?.authorSignature { + postAuthorAttributed = .initialize(string: author, color: isStateOverlayLayout ? stateOverlayTextColor : !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: renderType == .bubble ? .italic(.small) : .normal(.short)) + } + } + + + if let peer = messageMainPeer(message) as? TelegramUser, peer.botInfo != nil || peer.id == context.peerId { self.isRead = true } if let info = message.forwardInfo { - var accept:Bool = true + + var accept:Bool = !isHasSource && message.id.peerId != context.peerId if let media = message.media.first as? TelegramMediaFile { + if media.isAnimatedSticker { + accept = false + } + for attr in media.attributes { switch attr { case .Sticker: accept = false case let .Audio(isVoice, _, _, _, _): - accept = isVoice + if !isVoice, let forwardInfo = message.forwardInfo, let source = forwardInfo.source, source.isChannel { + accept = accept && forwardInfo.author?.id == forwardInfo.source?.id + } else { + accept = accept && isVoice + } default: break } } } + if !hasBubble && renderType == .bubble, message.forwardInfo?.psaType != nil { + accept = false + } else if (entry.renderType == .list && message.forwardInfo?.psaType != nil) { + accept = false + } - if accept { + if accept || (ChatRowItem.authorIsChannel(message: message, account: context.account) && info.author?.id != message.chatPeer(context.peerId)?.id) { forwardType = fwdType - let attr = NSMutableAttributedString() - if let source = info.source, source.isChannel { - var range = attr.append(string: source.displayTitle, color: theme.colors.link, font: .medium(.text)) - if info.author.id != source.id { - let subrange = attr.append(string: " (\(info.author.displayTitle))", color: theme.colors.link, font: .medium(.text)) - range.length += subrange.length + + var attr = NSMutableAttributedString() + + if ChatRowItem.authorIsChannel(message: message, account: context.account) { + if let author = info.author { + var range = attr.append(string: author.displayTitle, color: presentation.chat.linkColor(isIncoming, object.renderType == .bubble), font: .medium(.text)) + + let appLink = inAppLink.peerInfo(link: "", peerId: author.id, action: nil, openChat: !(author is TelegramUser), postId: info.sourceMessageId?.id, callback: chatInteraction.openInfo) + attr.add(link: appLink, for: range, color: presentation.chat.linkColor(isIncoming, object.renderType == .bubble)) + } else { + let range = attr.append(string: info.authorTitle, color: presentation.chat.linkColor(isIncoming, object.renderType == .bubble), font: .normal(.text)) + attr.add(link: inAppLink.callback("hid", { _ in + hiddenFwdTooltip?() + }), for: range) } - attr.add(link: inAppLink.peerInfo(peerId: source.id, action:nil, openChat: true, postId: nil, callback:chatInteraction.openInfo), for: range) - } else { - let range = attr.append(string: info.author.displayTitle, color: theme.colors.link, font: .medium(.text)) - var linkAbility: Bool = true - if let channel = info.author as? TelegramChannel { - if channel.username == nil && channel.participationStatus != .member { - linkAbility = false - } + + let color: NSColor + if message.forwardInfo?.psaType != nil { + color = presentation.chat.greenUI(isIncoming, object.renderType == .bubble) + } else { + color = presentation.chat.linkColor(isIncoming, object.renderType == .bubble) } - if linkAbility { - attr.add(link: inAppLink.peerInfo(peerId: info.author.id, action:nil, openChat: info.author.isChannel, postId: info.sourceMessageId?.id, callback:chatInteraction.openInfo), for: range) + + if let source = info.source, source.isChannel { + var range = attr.append(string: source.displayTitle, color: color, font: .medium(.text)) + if info.author?.id != source.id { + let subrange = attr.append(string: " (\(info.authorTitle))", color: color, font: .medium(.text)) + range.length += subrange.length + } + + let link = source.addressName == nil ? "https://t.me/c/\(source.id.id)/\(info.sourceMessageId?.id != nil ? "\(info.sourceMessageId!.id)" : "")" : "https://t.me/\(source.addressName!)/\(info.sourceMessageId?.id != nil ? "\(info.sourceMessageId!.id)" : "")" + let appLink = inApp(for: link.nsstring, context: context, peerId: nil, openInfo: chatInteraction.openInfo) + attr.add(link: appLink, for: range, color: color) + + } else { + let range = attr.append(string: info.authorTitle, color: color, font: info.author == nil ? .normal(.text) : .medium(.text)) + + var linkAbility: Bool = true + if let channel = info.author as? TelegramChannel { + if channel.username == nil && channel.participationStatus != .member { + linkAbility = false + } + } + if linkAbility, let author = info.author { + attr.add(link: inAppLink.peerInfo(link: "", peerId: author.id, action:nil, openChat: author.isChannel, postId: info.sourceMessageId?.id, callback:chatInteraction.openInfo), for: range) + } else if info.author == nil { + attr.add(link: inAppLink.callback("hid", { _ in + hiddenFwdTooltip?() + }), for: range) + + } } } + var isInstantVideo: Bool { + if let media = message.media.first as? TelegramMediaFile { + return media.isInstantVideo + } + return false + } + + let forwardNameColor: NSColor + if message.forwardInfo?.psaType != nil { + forwardNameColor = theme.chat.greenUI(isIncoming, object.renderType == .bubble) + } else if isForwardScam { + forwardNameColor = theme.chat.redUI(isIncoming, object.renderType == .bubble) + } else if !hasBubble { + forwardNameColor = presentation.colors.grayText + } else if isIncoming { + forwardNameColor = presentation.chat.linkColor(isIncoming, object.renderType == .bubble) + } else { + forwardNameColor = presentation.chat.grayText(isIncoming || isInstantVideo, object.renderType == .bubble) + } - _ = attr.append(string: " ") - _ = attr.append(string: DateUtils.string(forLastSeen: info.date), color: theme.colors.grayText, font: .normal(.short)) + if renderType == .bubble { + + let text: String + if let psaType = message.forwardInfo?.psaType { + text = localizedPsa("psa.title.bubbles", type: psaType, args: [attr.string]) + } else { + text = L10n.chatBubblesForwardedFrom(attr.string) + } + + let newAttr = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.short), textColor: forwardNameColor), link: MarkdownAttributeSet(font: hasBubble && info.author != nil ? .medium(.short) : .normal(.short), textColor: forwardNameColor), linkAttribute: { [weak attr] contents in + if let attr = attr, !attr.string.isEmpty, let link = attr.attribute(NSAttributedString.Key.link, at: 0, effectiveRange: nil) { + return (NSAttributedString.Key.link.rawValue, link) + } + return nil + })) + attr = newAttr.mutableCopy() as! NSMutableAttributedString + } else { + _ = attr.append(string: " ") + _ = attr.append(string: DateUtils.string(forLastSeen: info.date), color: renderType == .bubble ? forwardNameColor : presentation.colors.grayText, font: .normal(.short)) + } + - forwardNameLayout = TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end) + forwardNameLayout = TextViewLayout(attr, maximumNumberOfLines: renderType == .bubble ? 2 : 1, truncationType: .end, alwaysStaticItems: true) forwardNameLayout?.interactions = globalLinkExecutor - } + } } - if case .Full(let isAdmin) = itemType { + if case let .Full(rank) = itemType { + + + let canFillAuthorName: Bool = ChatRowItem.canFillAuthorName(message, chatInteraction: chatInteraction, renderType: renderType, isIncoming: isIncoming, hasBubble: hasBubble) + + if isForwardScam || canFillAuthorName { + self.isForwardScam = false + } var titlePeer:Peer? = self.peer var title:String = peer?.displayTitle ?? "" - if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { + if object.renderType == .list, let _ = message.forwardInfo?.psaType { + + } else if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { title = peer.displayTitle titlePeer = peer } @@ -465,139 +1211,280 @@ class ChatRowItem: TableRowItem { let attr:NSMutableAttributedString = NSMutableAttributedString() if let peer = titlePeer { - var nameColor:NSColor = theme.colors.link + var nameColor:NSColor = presentation.chat.linkColor(isIncoming, object.renderType == .bubble) if messageMainPeer(message) is TelegramChannel || messageMainPeer(message) is TelegramGroup { if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { - nameColor = theme.colors.link - } else if account.peerId != peer.id { - let value = ObjcUtils.colorMask(peer.id.id, mainId: account.peerId.id) - nameColor = userChatColors[Int(value) % userChatColors.count] ?? theme.colors.blueText + nameColor = presentation.chat.linkColor(isIncoming, object.renderType == .bubble) + } else if context.peerId != peer.id { + let value = abs(Int(peer.id.id) % 7) + nameColor = presentation.chat.peerName(value) } } - let range = attr.append(string: title, color: nameColor, font:.medium(.text)) - attr.addAttribute(NSAttributedStringKey.link, value: inAppLink.peerInfo(peerId:peer.id, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), range: range) + if message.forwardInfo?.psaType != nil, object.renderType == .list { + nameColor = presentation.colors.greenUI + } + + if canFillAuthorName { + let range = attr.append(string: title, color: nameColor, font: .medium(.text)) + if peer.id.id != 0 { + attr.addAttribute(NSAttributedString.Key.link, value: inAppLink.peerInfo(link: "", peerId:peer.id, action:nil, openChat: peer.isChannel, postId: nil, callback: chatInteraction.openInfo), range: range) + } else { + nameHide = L10n.chatTooltipHiddenForwardName + } + } - for attribute in message.attributes { - if let attribute = attribute as? InlineBotMessageAttribute, let bot = message.peers[attribute.peerId] as? TelegramUser, let address = bot.username { - _ = attr.append(string: " \(tr(.chatMessageVia)) ", color: theme.colors.grayText, font:.medium(.text)) - let range = attr.append(string: "@" + address, color: theme.colors.blueText, font:.medium(.text)) - attr.addAttribute(NSAttributedStringKey.link, value: inAppLink.callback("@" + address, { (parameter) in + if let bot = message.inlinePeer, message.hasInlineAttribute, let address = bot.username { + if message.forwardInfo?.psaType == nil { + if attr.length > 0 { + _ = attr.append(string: " ") + } + _ = attr.append(string: "\(L10n.chatMessageVia) ", color: !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font:.medium(.text)) + let range = attr.append(string: "@" + address, color: presentation.chat.linkColor(isIncoming, hasBubble && isBubbled), font:.medium(.text)) + attr.addAttribute(NSAttributedString.Key.link, value: inAppLink.callback("@" + address, { (parameter) in chatInteraction.updateInput(with: parameter + " ") }), range: range) } } - - if isAdmin { - _ = attr.append(string: " \(tr(.chatAdminBadge))", color: theme.colors.grayText, font: .normal(.short)) + if canFillAuthorName { + var badge: NSAttributedString? = nil + if let rank = rank { + badge = .initialize(string: " " + rank, color: !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.short)) + + } + else if ChatRowItem.authorIsChannel(message: message, account: context.account) { + badge = .initialize(string: " " + L10n.chatChannelBadge, color: !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: .normal(.short)) + } + if let badge = badge { + adminBadge = TextViewLayout(badge, maximumNumberOfLines: 1, truncationType: .end, alignment: .left) + adminBadge?.mayItems = false + adminBadge?.measure(width: .greatestFiniteMagnitude) + } } - authorText = TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end, alignment: .left) - - authorText?.interactions = globalLinkExecutor - + if attr.length > 0 { + authorText = TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end, alignment: .left) + authorText?.mayItems = false + authorText?.interactions = globalLinkExecutor + } } + } - var time:TimeInterval = TimeInterval(message.timestamp) - time -= account.context.timeDifference - date = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: DateUtils.string(forMessageListDate: Int32(time)), color: theme.colors.grayText, font: NSFont.normal(.short)), nil, 1, .end, NSMakeSize(CGFloat.greatestFiniteMagnitude, 20), nil, false, .left) - - } + if message.timestamp != scheduleWhenOnlineTimestamp { + var time:TimeInterval = TimeInterval(message.timestamp) + time -= context.timeDifference + + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .none + dateFormatter.timeZone = NSTimeZone.local + + date = TextNode.layoutText(maybeNode: nil, .initialize(string: dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(time))), color: isStateOverlayLayout ? stateOverlayTextColor : (!hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble)), font: renderType == .bubble ? .italic(.small) : .normal(.short)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, 20), nil, false, .left) + } + + } else { + self.isIncoming = false + self.hasBubble = false + } + super.init(initialSize) + hiddenFwdTooltip = { [weak self] in + guard let view = self?.view as? ChatRowView, let forwardName = view.forwardName else { return } + tooltip(for: forwardName, text: L10n.chatTooltipHiddenForwardName, autoCorner: false) + } + if let message = message { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .medium - formatter.locale = Locale(identifier: appCurrentLanguage.languageCode) - var fullDate: String = formatter.string(from: Date(timeIntervalSince1970: TimeInterval(message.timestamp) - account.context.timeDifference)) + formatter.timeZone = NSTimeZone.local + // + var fullDate: String = message.timestamp == scheduleWhenOnlineTimestamp ? "" : formatter.string(from: Date(timeIntervalSince1970: TimeInterval(message.timestamp) - context.timeDifference)) for attribute in message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - self.replyModel = ReplyModel(replyMessageId: attribute.messageId, account:account, replyMessage:message.associatedMessages[attribute.messageId]) + if let attribute = attribute as? ReplyMessageAttribute, let replyMessage = message.associatedMessages[attribute.messageId] { + let replyPresentation = ChatAccessoryPresentation(background: hasBubble ? presentation.chat.backgroundColor(isIncoming, object.renderType == .bubble) : isBubbled ? presentation.colors.grayForeground : presentation.colors.background, title: presentation.chat.replyTitle(self), enabledText: presentation.chat.replyText(self), disabledText: presentation.chat.replyDisabledText(self), border: presentation.chat.replyTitle(self)) + + self.replyModel = ReplyModel(replyMessageId: attribute.messageId, account: context.account, replyMessage:replyMessage, autodownload: downloadSettings.isDownloable(replyMessage), presentation: replyPresentation, makesizeCallback: { [weak self] in + guard let `self` = self else {return} + _ = self.makeSize(self.oldWidth, oldWidth: 0) + Queue.mainQueue().async { [weak self] in + self?.redraw() + } + }) + replyModel?.isSideAccessory = isBubbled && !hasBubble } if let attribute = attribute as? ViewCountMessageAttribute { - channelViewsAttributed = NSAttributedString.initialize(string: attribute.count.prettyNumber, color: theme.colors.grayText, font: NSFont.normal(.short)) + channelViewsAttributed = .initialize(string: max(1, attribute.count).prettyNumber, color: isStateOverlayLayout ? stateOverlayTextColor : !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: renderType == .bubble ? .italic(.small) : .normal(.short)) + + var author: String = "" + loop: for attr in message.attributes { + if let attr = attr as? AuthorSignatureMessageAttribute { + author = "\(attr.signature), " + break loop + } + } + if attribute.count >= 1000 { - fullDate = "\(attribute.count.separatedNumber) \(tr(.chatMessageTooltipViews)), \(fullDate)" + fullDate = "\(author)\(attribute.count.separatedNumber) \(tr(L10n.chatMessageTooltipViews)), \(fullDate)" + } else { + fullDate = "\(author)\(fullDate)" } } + +// if FastSettings.isTestLiked(message.id) { +// likesAttributed = .initialize(string: "1", color: isStateOverlayLayout ? stateOverlayTextColor : !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: renderType == .bubble ? .italic(.small) : .normal(.short)) +// } + if let attribute = attribute as? EditedMessageAttribute { if isEditMarkVisible { - editedLabel = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: tr(.chatMessageEdited), color: theme.colors.grayText, font: NSFont.normal(.short)), nil, 1, .end, NSMakeSize(CGFloat.greatestFiniteMagnitude, 20), nil, false, .left) + editedLabel = TextNode.layoutText(maybeNode: nil, .initialize(string: tr(L10n.chatMessageEdited), color: isStateOverlayLayout ? stateOverlayTextColor : !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, object.renderType == .bubble), font: renderType == .bubble ? .italic(.small) : .normal(.short)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, 20), nil, false, .left) } let formatterEdited = DateFormatter() - formatterEdited.dateStyle = .short + formatterEdited.dateStyle = .medium formatterEdited.timeStyle = .medium - formatterEdited.locale = Locale(identifier: appCurrentLanguage.languageCode) + formatterEdited.timeZone = NSTimeZone.local fullDate = "\(fullDate) (\(formatterEdited.string(from: Date(timeIntervalSince1970: TimeInterval(attribute.date)))))" } if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline) { replyMarkupModel = ReplyMarkupNode(attribute.rows, attribute.flags, chatInteraction.processBotKeyboard(with: message)) } +// else if let attribute = attribute as? ReactionsMessageAttribute { +// var buttons:[ReplyMarkupButton] = [] +// let sorted = attribute.reactions.sorted(by: { $0.count > $1.count }) +// for reaction in sorted { +// buttons.append(ReplyMarkupButton(title: reaction.value + " \(reaction.count)", titleWhenForwarded: nil, action: .url(reaction.value))) +// } +// if !buttons.isEmpty { +// replyMarkupModel = ReplyMarkupNode([ReplyMarkupRow(buttons: buttons)], [], ReplyMarkupInteractions(proccess: { (button, _) in +// switch button.action { +// case let .url(buttonReaction): +// if let index = sorted.firstIndex(where: { $0.value == buttonReaction}) { +// let reaction = sorted[index] +// var newValues = sorted +// if reaction.isSelected { +// newValues.remove(at: index) +// } else { +// newValues[index] = MessageReaction(value: reaction.value, count: reaction.count + 1, isSelected: true) +// } +// chatInteraction.updateReactions(message.id, buttonReaction, { value in +// }) +// } +// +// default: +// break +// } +// })) +// } +// } + + /* + let reactions = object.message?.attributes.first(where: { attr -> Bool in + return attr is ReactionsMessageAttribute + }) + + if let reactions = reactions as? ReactionsMessageAttribute { + var bp:Int = 0 + bp += 1 + } + */ + } + self.fullDate = fullDate } } - init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ entry: ChatHistoryEntry) { + init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ entry: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { self.entry = entry + self.context = chatInteraction.context self.message = entry.message self.chatInteraction = chatInteraction + self.renderType = entry.renderType + self.downloadSettings = downloadSettings + self.presentation = theme + self.isIncoming = false + self.hasBubble = false + self.isScam = false + self.isForwardScam = false super.init(initialSize) } - public static func item(_ initialSize:NSSize, from entry:ChatHistoryEntry, with account:Account, interaction:ChatInteraction) -> ChatRowItem { + public static func item(_ initialSize:NSSize, from entry:ChatHistoryEntry, interaction:ChatInteraction, downloadSettings: AutomaticMediaDownloadSettings = AutomaticMediaDownloadSettings.defaultSettings, theme: TelegramPresentationTheme) -> TableRowItem { + + switch entry { + case .UnreadEntry: + return ChatUnreadRowItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + case .groupedPhotos: + return ChatGroupedItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + case .DateEntry: + return ChatDateStickItem(initialSize, entry, interaction: interaction, theme: theme) + case .bottom: + return GeneralRowItem(initialSize, height: theme.bubbled ? 10 : 20, stableId: entry.stableId, backgroundColor: .clear) + default: + break + } if let message = entry.message { - if message.media.count == 0 || (message.media.count == 1 && message.media[0] is TelegramMediaWebpage) { - return ChatMessageItem(initialSize, interaction, account,entry) + if message.media.count == 0 || message.media.first is TelegramMediaWebpage { + return ChatMessageItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } else { if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let _ = message.autoremoveAttribute { - return ChatServiceItem(initialSize,interaction,account,entry) + return ChatServiceItem(initialSize,interaction, interaction.context,entry, downloadSettings, theme: theme) } else if let file = message.media[0] as? TelegramMediaFile { if file.isInstantVideo { - return ChatVideoMessageItem(initialSize,interaction,account,entry) + return ChatVideoMessageItem(initialSize, interaction, interaction.context,entry, downloadSettings, theme: theme) } else if file.isVideo && !file.isAnimated { - return ChatMediaItem(initialSize,interaction,account,entry) - } else if file.isSticker { - return ChatMediaItem(initialSize,interaction,account,entry) + return ChatMediaItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + } else if file.isStaticSticker { + return ChatMediaItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } else if file.isVoice { - return ChatVoiceRowItem(initialSize,interaction,account,entry) + return ChatVoiceRowItem(initialSize,interaction, interaction.context,entry, downloadSettings, theme: theme) } else if file.isVideo && file.isAnimated { - return ChatGIFMediaItem(initialSize,interaction,account,entry) - } else if !file.isVideo && file.isAnimated { - return ChatMediaItem(initialSize,interaction,account,entry) + return ChatMediaItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + } else if !file.isVideo && (file.isAnimated && !file.mimeType.hasSuffix("gif")) { + return ChatMediaItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } else if file.isMusic { - return ChatMusicRowItem(initialSize,interaction,account,entry) + return ChatMusicRowItem(initialSize,interaction, interaction.context, entry, downloadSettings, theme: theme) + } else if file.isAnimatedSticker { + return ChatAnimatedStickerItem(initialSize,interaction, interaction.context, entry, downloadSettings, theme: theme) } - return ChatFileMediaItem(initialSize,interaction,account,entry) + return ChatFileMediaItem(initialSize,interaction, interaction.context, entry, downloadSettings, theme: theme) } else if let action = message.media[0] as? TelegramMediaAction { switch action.action { case .phoneCall: - return ChatCallRowItem(initialSize, interaction, account, entry) + return ChatCallRowItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) default: - return ChatServiceItem(initialSize, interaction, account, entry) + return ChatServiceItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } } else if message.media[0] is TelegramMediaMap { - return ChatMapRowItem(initialSize,interaction,account,entry) + return ChatMapRowItem(initialSize,interaction, interaction.context, entry, downloadSettings, theme: theme) } else if message.media[0] is TelegramMediaContact { - return ChatContactRowItem(initialSize,interaction,account,entry) + return ChatContactRowItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } else if message.media[0] is TelegramMediaInvoice { - return ChatInvoiceItem(initialSize,interaction,account,entry) + return ChatInvoiceItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } else if message.media[0] is TelegramMediaExpiredContent { - return ChatServiceItem(initialSize,interaction,account,entry) + return ChatServiceItem(initialSize, interaction,interaction.context, entry, downloadSettings, theme: theme) + } else if message.media.first is TelegramMediaGame { + return ChatMessageItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + } else if message.media.first is TelegramMediaPoll { + return ChatPollItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) + } else if message.media.first is TelegramMediaUnsupported { + return ChatMessageItem(initialSize, interaction, interaction.context,entry, downloadSettings, theme: theme) + } else if message.media.first is TelegramMediaDice { + return ChatMediaDice(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } - return ChatMediaItem(initialSize,interaction,account,entry) + return ChatMediaItem(initialSize, interaction, interaction.context, entry, downloadSettings, theme: theme) } } @@ -607,53 +1494,194 @@ class ChatRowItem: TableRowItem { } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + + let result = super.makeSize(width, oldWidth: oldWidth) + isForceRightLine = false - + captionLayout?.dropLayoutSize() if let channelViewsAttributed = channelViewsAttributed { - channelViews = TextNode.layoutText(maybeNode: channelViewsNode, channelViewsAttributed, theme.colors.grayText, 1, .end, NSMakeSize(max(150,width - contentOffset.x - 44 - 150), 20), nil, false, .left) + channelViews = TextNode.layoutText(maybeNode: channelViewsNode, channelViewsAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize(hasBubble ? 60 : max(150,width - contentOffset.x - 44 - 150), 20), nil, false, .left) } - if let postAuthorAttributed = postAuthorAttributed { - postAuthor = TextNode.layoutText(maybeNode: postAuthorNode, postAuthorAttributed, theme.colors.grayText, 1, .end, NSMakeSize((width - contentOffset.x - 44) / 2, 20), nil, false, .left) + + if let likesAttributed = likesAttributed { + likes = TextNode.layoutText(maybeNode: likesNode, likesAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize(hasBubble ? 60 : max(150,width - contentOffset.x - 44 - 150), 20), nil, false, .left) + } + + var widthForContent: CGFloat = blockWidth + if previousBlockWidth != widthForContent { + self.previousBlockWidth = widthForContent + _contentSize = self.makeContentSize(widthForContent) + } + + + func layout() -> Bool { + if additionalLineForDateInBubbleState == nil && !isFixedRightPosition { + if _contentSize.width + rightSize.width + insetBetweenContentAndDate > widthForContent { + // widthForContent = _contentSize.width - 5 + self.isForceRightLine = true + //_contentSize = self.makeContentSize(widthForContent) + return true + } + } + return true } - //let additionWidth:CGFloat = date?.0.size.width ?? 20 - // _contentSize = self.makeContentSize(width - self.contentOffset.x - rightSize.width - 44) - if case .Full = itemType { - let additionWidth:CGFloat = date?.0.size.width ?? 20 - _contentSize = self.makeContentSize(width - self.contentOffset.x - 44 - additionWidth) - } else { - _contentSize = self.makeContentSize(width - self.contentOffset.x - rightSize.width - 44) + if hasBubble { + + while !layout() {} } - if let captionLayout = captionLayout { - captionLayout.measure(width: _contentSize.width) + + + var maxContentWidth = _contentSize.width + if hasBubble { + maxContentWidth -= bubbleDefaultInnerInset + } + + if isBubbled && isBubbleFullFilled { + widthForContent = maxContentWidth + } + + if let captionLayout = captionLayout, captionLayout.layoutSize == .zero { + captionLayout.measure(width: maxContentWidth) } - authorText?.measure(width: blockSize.width) if let forwardNameLayout = forwardNameLayout { - forwardNameLayout.measure(width: width - self.contentOffset.x - rightSize.width - 20) + var w = widthForContent + if isBubbled && !hasBubble { + w = width - _contentSize.width - 85 + } + forwardNameLayout.measure(width: min(w, 250)) } - if forwardType == .FullHeader || forwardType == .ShortHeader { - forwardHeader = TextNode.layoutText(maybeNode: forwardHeaderNode, NSAttributedString.initialize(string: tr(.messagesForwardHeader), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)), nil, 1, .end, NSMakeSize(width - self.contentOffset.x - 44, 20), nil,false, .left) + if (forwardType == .FullHeader || forwardType == .ShortHeader) && (entry.renderType == .bubble || message?.forwardInfo?.psaType == nil) { + + let color: NSColor + let text: String + if let psaType = message?.forwardInfo?.psaType { + color = presentation.chat.greenUI(isIncoming, isBubbled) + text = localizedPsa("psa.title", type: psaType) + } else { + color = !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble) + text = L10n.messagesForwardHeader + } + + forwardHeader = TextNode.layoutText(maybeNode: forwardHeaderNode, .initialize(string: text, color: color, font: .normal(.text)), nil, 1, .end, NSMakeSize(width - self.contentOffset.x - 44, 20), nil,false, .left) } else { forwardHeader = nil } + if !isBubbled { + replyModel?.measureSize(widthForContent, sizeToFit: true) + } else if let replyModel = replyModel { + if let item = self as? ChatMessageItem, item.webpageLayout == nil && !replyModel.isSideAccessory { + if isBubbled { + replyModel.measureSize(max(blockWidth, 200), sizeToFit: true) + } else { + replyModel.measureSize(max(contentSize.width, 200), sizeToFit: true) + } + } else { + if !hasBubble { + replyModel.measureSize(min(width - _contentSize.width - contentOffset.x - 80, 300), sizeToFit: true) + } else { + replyModel.measureSize(min(_contentSize.width - bubbleDefaultInnerInset, 300), sizeToFit: true) + } + } + } + + + + if !canFillAuthorName, let replyModel = replyModel, let authorText = authorText, replyModel.isSideAccessory { + var adminWidth: CGFloat = 0 + if let adminBadge = adminBadge { + adminWidth = adminBadge.layoutSize.width + } + + authorText.measure(width: replyModel.size.width - 10 - adminWidth) + + replyModel.topOffset = authorText.layoutSize.height + 6 + replyModel.measureSize(replyModel.width, sizeToFit: replyModel.sizeToFit) + } else { + var adminWidth: CGFloat = 0 + if let adminBadge = adminBadge { + adminWidth = adminBadge.layoutSize.width + } + + let channelOffset = (channelViews != nil ? channelViews!.0.size.width + 20 : 0) + authorText?.measure(width: widthForContent - adminWidth - (postAuthorAttributed != nil ? 50 + channelOffset : 0) - rightSize.width) - replyModel?.measureSize(width - self.contentOffset.x - 44) + + + } + + + if let postAuthorAttributed = postAuthorAttributed { + + postAuthor = TextNode.layoutText(maybeNode: postAuthorNode, postAuthorAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize(hasBubble ? 60 : (width - (authorText != nil ? authorText!.layoutSize.width : 0) - contentOffset.x - 44) / 2, 20), nil, false, .left) + } - if !(self is ChatMessageItem) { - replyMarkupModel?.measureSize(_contentSize.width) + if hasBubble && isBubbleFullFilled { + if let postAuthor = postAuthor, let postAuthorAttributed = postAuthorAttributed { + let width: CGFloat = _contentSize.width - (rightSize.width - postAuthor.0.size.width - 8) - bubbleContentInset - additionBubbleInset - 10 + if width < 0 { + self.postAuthor = nil + } else { + self.postAuthor = TextNode.layoutText(maybeNode: postAuthorNode, postAuthorAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize( width, 20), nil, false, .left) + } + } + } + + if hasBubble && !isBubbleFullFilled { + if let postAuthorAttributed = postAuthorAttributed, let postAuthor = postAuthor { + if bubbleFrame.width < width - 150 { + let size = rightSize.width - postAuthor.0.size.width - 8 + var w = width - bubbleFrame.width - 150 + if let _ = self as? ChatMessageItem, additionalLineForDateInBubbleState != nil { + w = _contentSize.width - size + } + self.postAuthor = TextNode.layoutText(maybeNode: postAuthorNode, postAuthorAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize( w, 20), nil, false, .left) + } else if bubbleFrame.width > _contentSize.width + rightSize.width + bubbleDefaultInnerInset { + var size = bubbleFrame.width - (_contentSize.width + rightSize.width + bubbleDefaultInnerInset) + if !postAuthor.0.isPerfectSized { + size = bubbleFrame.width - (_contentSize.width + bubbleDefaultInnerInset) + } + self.postAuthor = TextNode.layoutText(maybeNode: postAuthorNode, postAuthorAttributed, !hasBubble ? presentation.colors.grayText : presentation.chat.grayText(isIncoming, renderType == .bubble), 1, .end, NSMakeSize( size, 20), nil, false, .left) + while !layout() {} + } + + } + + + + if _contentSize.width < rightSize.width { + if !(self is ChatMessageItem) { + _contentSize.width = rightSize.width + } else if additionalLineForDateInBubbleState != nil { + _contentSize.width = rightSize.width + } + } + } + + if isBubbled { + replyMarkupModel?.measureSize(bubbleFrame.width - additionBubbleInset) } else { - replyMarkupModel?.measureSize(max(_contentSize.width, blockSize.width)) + if let item = self as? ChatMessageItem { + if item.webpageLayout != nil { + replyMarkupModel?.measureSize(_contentSize.width) + } else if _contentSize.width < 200 { + replyMarkupModel?.measureSize(max(_contentSize.width, blockWidth)) + } else { + replyMarkupModel?.measureSize(_contentSize.width) + } + } else { + replyMarkupModel?.measureSize(_contentSize.width) + } } - - return super.makeSize(width, oldWidth: oldWidth) + return result } deinit { @@ -661,18 +1689,119 @@ class ChatRowItem: TableRowItem { bp += 1 } + var bubbleContentInset: CGFloat { + return 13 + } + + var additionBubbleInset: CGFloat { + return 6 + } + + var insetBetweenContentAndDate: CGFloat { + return 10 + } + + var bubbleCornerInset: CGFloat { + if isIncoming { + if let message = message, let peer = message.peers[message.id.peerId] { + if peer.isGroup || peer.isSupergroup { + return additionBubbleInset + 36 + } + } + } + return additionBubbleInset + } + + var maxTitleWidth: CGFloat { + let nameWidth:CGFloat + if hasBubble { + nameWidth = (authorText?.layoutSize.width ?? 0) + (isScam ? theme.icons.chatScam.backingSize.width + 3 : 0) + (adminBadge?.layoutSize.width ?? 0) + } else { + nameWidth = 0 + } + let forwardWidth = hasBubble ? (forwardNameLayout?.layoutSize.width ?? 0) + (isForwardScam ? theme.icons.chatScam.backingSize.width + 3 : 0) + (isPsa ? 30 : 0) : 0 + + let replyWidth = min(hasBubble ? (replyModel?.size.width ?? 0) : 0, 200) + + return min(max(max(nameWidth, forwardWidth), replyWidth), contentSize.width) + } + + var bubbleFrame: NSRect { + let nameWidth:CGFloat + if hasBubble { + nameWidth = (authorText?.layoutSize.width ?? 0) + (isScam ? theme.icons.chatScam.backingSize.width + 3 : 0) + (adminBadge?.layoutSize.width ?? 0) + } else { + nameWidth = 0 + } + //hasBubble ? ((authorText?.layoutSize.width ?? 0) + (isScam ? theme.icons.chatScam.backingSize.width + 3 : 0) + (adminBadge?.layoutSize.width ?? 0)) : 0 + + let forwardWidth = hasBubble ? (forwardNameLayout?.layoutSize.width ?? 0) + (isForwardScam ? theme.icons.chatScam.backingSize.width + 3 : 0) + (isPsa ? 30 : 0) : 0 + let replyWidth: CGFloat = hasBubble ? (replyModel?.size.width ?? 0) : 0 + + var rect = NSMakeRect(defLeftInset, 2, contentSize.width, height - 4) + + + if isBubbled, let replyMarkup = replyMarkupModel { + rect.size.height -= (replyMarkup.size.height + defaultContentInnerInset) + } + + //if forwardType != nil { + // rect.origin.x -= leftContentInset + //} + + if additionalLineForDateInBubbleState == nil && !isFixedRightPosition && rightSize.width > 0 { + rect.size.width += rightSize.width + insetBetweenContentAndDate + bubbleDefaultInnerInset + } else { + rect.size.width += bubbleContentInset * 2 + insetBetweenContentAndDate + } + + + + rect.size.width = max(nameWidth + bubbleDefaultInnerInset, rect.width) + + rect.size.width = max(rect.size.width, replyWidth + bubbleDefaultInnerInset) + + rect.size.width = max(rect.size.width, forwardWidth + bubbleDefaultInnerInset) + + return rect + } + + var isFixedRightPosition: Bool { + return additionalLineForDateInBubbleState != nil + } + + var additionalLineForDateInBubbleState: CGFloat? { + return isForceRightLine ? rightSize.height : nil + } + func deleteMessage() { - _ = account.postbox.modify { [weak message] modifier -> Void in + _ = context.account.postbox.transaction { [weak message] transaction -> Void in if let message = message { - modifier.deleteMessages([message.id]) + transaction.deleteMessages([message.id], forEachMedia: { media in + + }) } }.start() } - func resendMessage() { - if let message = message { - _ = resendMessages(account: account, messageIds: [message.id]).start() + func openInfo() { + switch chatInteraction.chatLocation { + case .peer: + if let peer = peer { + let messageId: MessageId? + if chatInteraction.isGlobalSearchMessage { + messageId = self.message?.id + } else { + messageId = nil + } + chatInteraction.openInfo(peer.id, !(peer is TelegramUser), messageId, nil) + } } + + } + + func resendMessage(_ ids: [MessageId]) { + _ = resendMessages(account: context.account, messageIds: ids).start() } func makeContentSize(_ width:CGFloat) -> NSSize { @@ -693,7 +1822,7 @@ class ChatRowItem: TableRowItem { } func editAction() -> Bool { if chatInteraction.presentation.state == .normal || chatInteraction.presentation.state == .editing { - if let message = message, canEditMessage(message, account: account) { + if let message = message, canEditMessage(message, context: context) { chatInteraction.beginEditingMessage(message) return true } @@ -702,7 +1831,7 @@ class ChatRowItem: TableRowItem { } func forwardAction() -> Bool { if chatInteraction.presentation.state != .selecting, let message = message { - if canForwardMessage(message, account: account) { + if canForwardMessage(message, account: context.account) { chatInteraction.forwardMessages([message.id]) return true } @@ -710,94 +1839,361 @@ class ChatRowItem: TableRowItem { return false } - override func menuItems() -> Signal<[ContextMenuItem], Void> { - - if self.chatInteraction.isLogInteraction { - return .single([]) + override var instantlyResize: Bool { + return forwardType != nil + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + if chatInteraction.disableSelectAbility { + return super.menuItems(in: location) + } + if let message = message { + return chatMenuItems(for: message, chatInteraction: chatInteraction) + } + return super.menuItems(in: location) + } + + var stateOverlayBackgroundColor: NSColor { + guard let media = self.message?.media.first else { + return self.presentation.chatServiceItemColor + } + if media is TelegramMediaImage { + return self.presentation.colors.blackTransparent.withAlphaComponent(0.5) + } else if let media = media as? TelegramMediaFile, media.isVideo && !media.isInstantVideo { + return self.presentation.colors.blackTransparent.withAlphaComponent(0.5) + } else { + return self.presentation.chatServiceItemColor + } + } + + var stateOverlayTextColor: NSColor { + guard let media = self.message?.media.first else { + return self.presentation.chatServiceItemTextColor + } + if let file = media as? TelegramMediaFile, file.isInstantVideo { + return self.presentation.chatServiceItemTextColor + } + if media.isInteractiveMedia { + return NSColor(0xffffff) + } else { + return self.presentation.chatServiceItemTextColor + } + } + var isInteractiveMedia: Bool { + guard let media = self.message?.media.first else { + return false } + return media.isInteractiveMedia + } +} + +func chatMenuItems(for message: Message, chatInteraction: ChatInteraction) -> Signal<[ContextMenuItem], NoError> { + + let account = chatInteraction.context.account + let context = chatInteraction.context + let peerId = chatInteraction.peerId + let peer = chatInteraction.peer + + if chatInteraction.isLogInteraction || chatInteraction.presentation.state == .selecting { + return .single([]) + } + + var items:[ContextMenuItem] = [] + + + + if message.isScheduledMessage, let peer = peer { + items.append(ContextMenuItem(L10n.chatContextScheduledSendNow, handler: { + _ = sendScheduledMessageNowInteractively(postbox: account.postbox, messageId: message.id).start() + })) + items.append(ContextMenuItem(L10n.chatContextScheduledReschedule, handler: { + showModal(with: ScheduledMessageModalController(context: context, defaultDate: Date(timeIntervalSince1970: TimeInterval(message.timestamp)), peerId: peer.id, scheduleAt: { date in + _ = showModalProgress(signal: requestEditMessage(account: account, messageId: message.id, text: message.text, media: .keep, scheduleTime: Int32(min(date.timeIntervalSince1970, Double(scheduleWhenOnlineTimestamp)))), for: context.window).start(next: { result in + + }, error: { error in + + }) + }), for: context.window) + })) + items.append(ContextSeparatorItem()) + } + + if canReplyMessage(message, peerId: chatInteraction.peerId) && !message.isScheduledMessage { + items.append(ContextMenuItem(tr(L10n.messageContextReply1) + (FastSettings.tooltipAbility(for: .edit) ? " (\(tr(L10n.messageContextReplyHelp)))" : ""), handler: { + chatInteraction.setupReplyMessage(message.id) + })) + } + + if let file = message.media.first as? TelegramMediaFile, file.isEmojiAnimatedSticker { + items.append(ContextMenuItem(L10n.textCopyText, handler: { + copyToClipboard(message.text) + })) + } + + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + if !message.flags.contains(.Failed), !message.flags.contains(.Unsent), !message.isScheduledMessage { + items.append(ContextMenuItem(tr(L10n.messageContextCopyMessageLink1), handler: { + _ = showModalProgress(signal: exportMessageLink(account: account, peerId: peer.id, messageId: message.id), for: context.window).start(next: { link in + if let link = link { + copyToClipboard(link) + } + }) + + })) + } + } + + items.append(ContextSeparatorItem()) + + if canEditMessage(message, context: context){ + items.append(ContextMenuItem(tr(L10n.messageContextEdit), handler: { + chatInteraction.beginEditingMessage(message) + })) + } + + if !message.isScheduledMessage { - var items:[ContextMenuItem] = [] - let chatInteraction = self.chatInteraction - if chatInteraction.presentation.state != .selecting { - if let message = message, let peer = peer { - let account = self.account! - - if peer.canSendMessage { - items.append(ContextMenuItem(tr(.messageContextReply), handler: { - chatInteraction.setupReplyMessage(message.id) - })) + let pinText = chatInteraction.presentation.pinnedMessageId == message.id ? L10n.messageContextUnpin : L10n.messageContextPin + let needUnpin = chatInteraction.presentation.pinnedMessageId == message.id + if let peer = message.peers[message.id.peerId] as? TelegramChannel, peer.hasPermission(.pinMessages) || (peer.isChannel && peer.hasPermission(.editAllMessages)) { + if !message.flags.contains(.Unsent) && !message.flags.contains(.Failed) { + items.append(ContextMenuItem(pinText, handler: { + if peer.isSupergroup, !needUnpin { + modernConfirm(for: context.window, account: account, peerId: nil, header: L10n.messageContextConfirmPin1, information: nil, thridTitle: L10n.messageContextConfirmNotifyPin, successHandler: { result in + chatInteraction.updatePinned(message.id, chatInteraction.presentation.pinnedMessageId == message.id, result != .thrid) + }) + } else { + chatInteraction.updatePinned(message.id, needUnpin, true) + } + })) + } + } else if message.id.peerId == account.peerId { + items.append(ContextMenuItem(pinText, handler: { + chatInteraction.updatePinned(message.id, needUnpin, true) + })) + } else if let peer = message.peers[message.id.peerId] as? TelegramGroup, peer.canPinMessage { + items.append(ContextMenuItem(pinText, handler: { + if !needUnpin { + modernConfirm(for: context.window, account: account, peerId: nil, header: L10n.messageContextConfirmPin1, information: nil, thridTitle: L10n.messageContextConfirmNotifyPin, successHandler: { result in + chatInteraction.updatePinned(message.id, needUnpin, result == .thrid) + }) + } else { + chatInteraction.updatePinned(message.id, needUnpin, false) } - - if let peer = message.peers[message.id.peerId] as? TelegramChannel, peer.isSupergroup { - if let address = peer.addressName { - items.append(ContextMenuItem(tr(.messageContextCopyMessageLink), handler: { - copyToClipboard("t.me/\(address)/\(message.id.id)") + })) + } + } + + + if canForwardMessage(message, account: account) { + items.append(ContextMenuItem(tr(L10n.messageContextForward), handler: { + chatInteraction.forwardMessages([message.id]) + })) + } else if message.id.peerId.namespace == Namespaces.Peer.SecretChat, !message.containsSecretMedia { + items.append(ContextMenuItem(L10n.messageContextShare, handler: { + chatInteraction.forwardMessages([message.id]) + })) + } + + if canDeleteMessage(message, account: account) { + items.append(ContextMenuItem(tr(L10n.messageContextDelete), handler: { + chatInteraction.deleteMessages([message.id]) + })) + } + + + items.append(ContextMenuItem(tr(L10n.messageContextSelect), handler: { + chatInteraction.withToggledSelectedMessage({$0.withToggledSelectedMessage(message.id)}) + })) + + + + if canForwardMessage(message, account: account), chatInteraction.peerId != account.peerId { + items.append(ContextMenuItem(tr(L10n.messageContextForwardToCloud), handler: { + _ = Sender.forwardMessages(messageIds: [message.id], context: chatInteraction.context, peerId: account.peerId).start() + })) + items.append(ContextSeparatorItem()) + } + + + + + + + var signal:Signal<[ContextMenuItem], NoError> = .single(items) + + + if let file = message.media.first as? TelegramMediaFile, let mediaId = file.id { + signal = signal |> mapToSignal { items -> Signal<[ContextMenuItem], NoError> in + var items = items + + return account.postbox.transaction { transaction -> [ContextMenuItem] in + if file.isAnimated && file.isVideo { + let gifItems = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs).compactMap {$0.contents as? RecentMediaItem} + if let _ = gifItems.firstIndex(where: {$0.media.id == mediaId}) { + items.append(ContextMenuItem(L10n.messageContextRemoveGif, handler: { + let _ = removeSavedGif(postbox: account.postbox, mediaId: mediaId).start() })) - } - if peer.hasAdminRights(.canPinMessages) { - items.append(ContextMenuItem(tr(.messageContextPin), handler: { - confirm(for: mainWindow, with: appName, and: tr(.messageContextConfirmPin), thridTitle: tr(.messageContextConfirmOnlyPin), successHandler: { result in - chatInteraction.updatePinned(message.id, false, result == .thrid) - }) + } else { + items.append(ContextMenuItem(L10n.messageContextSaveGif, handler: { + let _ = addSavedGif(postbox: account.postbox, fileReference: FileMediaReference.message(message: MessageReference(message), media: file)).start() })) } } + return items + } |> mapToSignal { items in + var items = items - items.append(ContextSeparatorItem()) - - if canEditMessage(message, account:account) { - items.append(ContextMenuItem(tr(.messageContextEdit), handler: { - chatInteraction.beginEditingMessage(message) - })) - } - - if canForwardMessage(message, account: account) { - items.append(ContextMenuItem(tr(.messageContextForward), handler: { - chatInteraction.forwardMessages([message.id]) - })) - } - - if canDeleteMessage(message, account: account) { - items.append(ContextMenuItem(tr(.messageContextDelete), handler: { - chatInteraction.deleteMessages([message.id]) - })) - } - - - items.append(ContextMenuItem(tr(.messageContextSelect), handler: { - chatInteraction.update({$0.withToggledSelectedMessage(message.id)}) - })) - - - if canForwardMessage(message, account: account) { - items.append(ContextSeparatorItem()) - items.append(ContextMenuItem(tr(.messageContextForwardToCloud), handler: { - _ = Sender.forwardMessages(messageIds: [message.id], account: account, peerId: account.peerId).start() - })) - - } - - - for media in message.media { - if let file = media as? TelegramMediaFile { - if file.isVideo && file.isAnimated { - - if !canForwardMessage(message, account: account) { - items.append(ContextSeparatorItem()) + return combineLatest(queue: .mainQueue(), account.postbox.mediaBox.resourceData(file.resource), fileFinderPath(file, context.account.postbox)) |> mapToSignal { data, downloadPath in + if !file.isInteractiveMedia && !file.isVoice && !file.isMusic && !file.isStaticSticker && !file.isGraphicFile && !file.isAnimatedSticker { + let quickLook = ContextMenuItem(L10n.contextOpenInQuickLook, handler: { + FastSettings.toggleOpenInQuickLook(fileExtenstion(file)) + }) + quickLook.state = FastSettings.openInQuickLook(fileExtenstion(file)) ? .on : .off + items.append(quickLook) + } + + if data.complete, !message.containsSecretMedia { + items.append(ContextMenuItem(tr(L10n.contextCopyMedia), handler: { + saveAs(file, account: account) + })) + + #if BETA || ALPHA || DEBUG + if file.isAnimatedSticker, let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + items.append(ContextMenuItem("Copy thumbnail (Dev.)", handler: { + _ = getAnimatedStickerThumb(data: data).start(next: { path in + if let path = path { + let pb = NSPasteboard.general + pb.clearContents() + pb.writeObjects([NSURL(fileURLWithPath: path)]) + } + }) + })) + } + #endif + + if let downloadPath = downloadPath { + if !file.isVoice { + let path: String + if FileManager.default.fileExists(atPath: downloadPath) { + path = downloadPath + } else { + path = data.path + "." + fileExtenstion(file) + try? FileManager.default.removeItem(atPath: path) + try? FileManager.default.linkItem(atPath: data.path, toPath: path) + } + let result = ObjcUtils.apps(forFileUrl: path) + if let result = result, !result.isEmpty { + let item = ContextMenuItem(L10n.messageContextOpenWith, handler: {}) + let menu = NSMenu() + item.submenu = menu + for item in result { + menu.addItem(ContextMenuItem(item.fullname, handler: { + NSWorkspace.shared.openFile(path, withApplication: item.app.path) + }, image: item.icon)) + } + items.append(item) + } } - - items.append(ContextMenuItem(tr(.messageContextSaveGif), handler: { - let _ = addSavedGif(postbox: account.postbox, file: file).start() + } + + } + + if file.isStaticSticker, let fileId = file.id { + return account.postbox.transaction { transaction -> [ContextMenuItem] in + let saved = getIsStickerSaved(transaction: transaction, fileId: fileId) + items.append(ContextMenuItem( !saved ? tr(L10n.chatContextAddFavoriteSticker) : tr(L10n.chatContextRemoveFavoriteSticker), handler: { + + if !saved { + _ = addSavedSticker(postbox: account.postbox, network: account.network, file: file).start() + } else { + _ = removeSavedSticker(postbox: account.postbox, mediaId: fileId).start() + } })) + + return items } } + + return .single(items) } - + } + + + } + } else if let image = message.media.first as? TelegramMediaImage { + signal = signal |> mapToSignal { items -> Signal<[ContextMenuItem], NoError> in + var items = items + if let resource = image.representations.last?.resource { + return account.postbox.mediaBox.resourceData(resource) |> take(1) |> deliverOnMainQueue |> map { data in + if data.complete { + items.append(ContextMenuItem(tr(L10n.galleryContextCopyToClipboard), handler: { + if let path = link(path: data.path, ext: "jpg") { + let pb = NSPasteboard.general + pb.clearContents() + pb.writeObjects([NSURL(fileURLWithPath: path)]) + } + })) + items.append(ContextMenuItem(tr(L10n.contextCopyMedia), handler: { + savePanel(file: data.path, ext: "jpg", for: mainWindow) + })) + } + return items + } + } else { + return .single(items) } } - - return .single(items) } -} - + + + signal = signal |> map { items in + if let peer = chatInteraction.peer as? TelegramChannel, peer.isSupergroup { + if peer.hasPermission(.banMembers), let author = message.author, author.id != account.peerId { + var items = items + items.append(ContextMenuItem(L10n.chatContextRestrict, handler: { + _ = showModalProgress(signal: fetchChannelParticipant(account: account, peerId: chatInteraction.peerId, participantId: author.id), for: mainWindow).start(next: { participant in + if let participant = participant { + switch participant { + case let .member(memberId, _, _, _, _): + showModal(with: RestrictedModalViewController(context, peerId: peerId, memberId: memberId, initialParticipant: participant, updated: { updatedRights in + _ = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: author.id, bannedRights: updatedRights).start() + }), for: context.window) + default: + break + } + } + }) + })) + return items + } + } + return items + } +// + signal = signal |> map { items in + var items = items + if canReportMessage(message, account) { + items.append(ContextMenuItem(L10n.messageContextReport, handler: { + _ = reportReasonSelector(context: context).start(next: { reason in + _ = showModalProgress(signal: reportPeerMessages(account: account, messageIds: [message.id], reason: reason), for: mainWindow).start(completed: { + alert(for: context.window, info: L10n.messageContextReportAlertOK) + }) + }) + })) + } + return items + } + + return signal |> map { items in + var items = items + if let peer = peer, peer.isGroup || peer.isSupergroup, let author = message.author, !message.isScheduledMessage { + items.append(ContextSeparatorItem()) + items.append(ContextMenuItem(L10n.chatServiceSearchAllMessages(author.compactDisplayTitle), handler: { + chatInteraction.searchPeerMessages(author) + })) + } + return items + } +} diff --git a/Telegram-Mac/ChatRowView.swift b/Telegram-Mac/ChatRowView.swift index 21f2170068..d887a626f2 100644 --- a/Telegram-Mac/ChatRowView.swift +++ b/Telegram-Mac/ChatRowView.swift @@ -8,14 +8,16 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + -private class ChatRowAnimateView: View { - var stableId:AnyHashable? -} -class ChatRowView: TableRowView, Notifable, MultipleSelectable { +class ChatRowView: TableRowView, Notifable, MultipleSelectable, ViewDisplayDelegate, RevealTableView { + + var header: String? { @@ -32,24 +34,78 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { var contentView:View = View() private var replyView:ChatAccessoryView? private var replyMarkupView:View? - private var forwardName:TextView? - private var captionView:TextView? - private var shareControl:ImageButton? + private(set) var forwardName:TextView? + private(set) var captionView:TextView? + private var shareView:ImageButton? + private var likeView:ImageButton? + private var nameView:TextView? - private var rightView:ChatRightView = ChatRightView(frame:NSZeroRect) - private var selectingView:SelectingControl? + private var adminBadge: TextView? + let rightView:ChatRightView = ChatRightView(frame:NSZeroRect) + private(set) var selectingView:SelectingControl? + private var mouseDragged: Bool = false + private var animatedView:RowAnimateView? - private var animatedView:ChatRowAnimateView? + private var forwardAccessory: ChatBubbleAccessoryForward? = nil + private var viaAccessory: ChatBubbleViaAccessory? = nil + let bubbleView = ChatMessageBubbleBackdrop() + private var scamButton: ImageButton? = nil + private var scamForwardButton: ImageButton? = nil + private var psaButton: ImageButton? = nil + + let rowView: View + required init(frame frameRect: NSRect) { + rowView = View(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) super.init(frame: frameRect) - super.addSubview(rightView) - super.addSubview(contentView) + super.addSubview(rowView) + + + + rowView.addSubview(bubbleView) + rowView.addSubview(contentView) + rowView.addSubview(rightView) + + rowView.displayDelegate = self + + super.addSubview(swipingRightView) + + + } + + override func setFrameSize(_ newSize: NSSize) { + if !inLiveResize || !NSIsEmptyRect(visibleRect) { + super.setFrameSize(newSize) + rowView.setFrameSize(newSize) + } + + } + + override func setFrameOrigin(_ newOrigin: NSPoint) { + let oldOrigin = self.frame.origin + super.setFrameOrigin(newOrigin) - + if oldOrigin != newOrigin, oldOrigin == .zero { + updateBackground(animated: false) + } + } + + func updateBackground(animated: Bool, rotated: Bool = false) -> Void { + + guard let item = self.item as? ChatRowItem else { + return + } + let gradientRect = item.chatInteraction.getGradientOffsetRect() + let size = NSMakeSize(gradientRect.width, gradientRect.height + 60) + + let inset = size.height - gradientRect.minY + (frame.height - bubbleFrame.maxY) - 30 + if visibleRect.height > 0 { + bubbleView.update(rect: self.frame.offsetBy(dx: 0, dy: inset), within: size, animated: animated, rotated: rotated) + } } var selectableTextViews: [TextView] { @@ -59,6 +115,13 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { return [] } + func clickInContent(point: NSPoint) -> Bool { + guard let item = item as? ChatRowItem, let layout = item.captionLayout, let captionView = captionView else {return true} + let point = captionView.convert(point, from: self) + let index = layout.findIndex(location: point) + return point.x < layout.lines[index].frame.maxX + } + func isEqual(to other: Notifable) -> Bool { if let other = other as? ChatRowView { return self == other @@ -68,9 +131,8 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { func notify(with value: Any, oldValue: Any, animated:Bool) { if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { - if (value.selectionState != nil && oldValue.selectionState == nil) || (value.selectionState == nil && oldValue.selectionState != nil) { - updateSelectingState(!NSIsEmptyRect(visibleRect), selectingMode:value.selectionState != nil, item: self.item as? ChatRowItem) - updateColors() + if (value.selectionState != oldValue.selectionState) { + updateSelectingState(!NSIsEmptyRect(visibleRect), selectingMode:value.selectionState != nil, item: self.item as? ChatRowItem, needUpdateColors: true) self.needsLayout = true } else if let item = item as? ChatRowItem, let message = item.message { if value.selectionState?.selectedIds.contains(message.id) != oldValue.selectionState?.selectedIds.contains(message.id) { @@ -86,41 +148,52 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } - func updateSelectingState(_ animated:Bool = false, selectingMode:Bool, item: ChatRowItem?) { + func updateSelectingState(_ animated:Bool = false, selectingMode:Bool, item: ChatRowItem?, needUpdateColors: Bool) { if let item = item { let defRight = frame.width - item.rightSize.width - item.rightInset - rightView.change(pos: NSMakePoint(defRight, rightView.frame.minY), animated: animated) + + if !item.isBubbled { + rightView.change(pos: NSMakePoint(defRight, rightView.frame.minY), animated: animated) + } else { + if rowView.frame.origin != rowPoint { + rowView.change(pos: rowPoint, animated: animated) + } + } + + + updateMouse() if selectingMode { + let force: Bool = selectingView == nil if selectingView == nil { - selectingView = SelectingControl(unselectedImage: theme.icons.chatToggleUnselected, selectedImage: theme.icons.chatToggleSelected) - selectingView?.setFrameOrigin(NSMakePoint(frame.width, item.defaultContentTopOffset - 1)) + selectingView = SelectingControl(unselectedImage: item.presentation.icons.chatGroupToggleUnselected, selectedImage: item.presentation.icons.chatGroupToggleSelected, selected: item.isSelectedMessage) + selectingView?.setFrameOrigin(NSMakePoint(frame.width, selectingPoint.y)) + selectingView?.layer?.opacity = 0 super.addSubview(selectingView!) } - if animated { - selectingView?.layer?.removeAnimation(forKey: "opacity") - selectingView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + if selectingView!.isSelected != item.isSelectedMessage || force { + selectingView?.change(opacity: 1.0, animated: animated) + selectingView?.change(pos: selectingPoint, animated: animated) } - - selectingView?.change(pos: NSMakePoint(rightView.frame.maxX + 4,item.defaultContentTopOffset - 1), animated: animated) - } else { + } else { if animated { selectingView?.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion:false, completion:{ [weak self] (completed) in - if completed { + //if completed { self?.selectingView?.removeFromSuperview() self?.selectingView = nil - } + //} }) } else { self.selectingView?.removeFromSuperview() self.selectingView = nil } - - selectingView?.change(pos: NSMakePoint(frame.width,item.defaultContentTopOffset - 1), animated: animated) + selectingView?.change(pos: NSMakePoint(frame.width, selectingPoint.y), animated: animated) } - if let selectionState = item.chatInteraction.presentation.selectionState, let message = item.message { - selectingView?.set(selected: selectionState.selectedIds.contains(message.id), animated: animated) + + updateSelectionViewAfterUpdateState(item: item, animated: animated) + if needUpdateColors { + renderLayoutType(item, animated: animated) updateColors() } if item.chatInteraction.presentation.state == .selecting { @@ -132,6 +205,13 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } } + func updateSelectionViewAfterUpdateState(item: ChatRowItem, animated: Bool) { + + if let selectionState = item.chatInteraction.presentation.selectionState, let message = item.message { + selectingView?.set(selected: selectionState.selectedIds.contains(message.id), animated: animated) + } + } + func canStartTextSelecting(_ event:NSEvent) -> Bool { return false } @@ -141,87 +221,182 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } override var isSelect: Bool { - if let item = item as? ChatRowItem, let message = item.message, let selectionState = item.chatInteraction.presentation.selectionState { + if let item = item as? ChatRowItem { + return isSelectedItem(item) + } + return false + } + + private func isSelectedItem(_ item: ChatRowItem) -> Bool { + if let message = item.message, let selectionState = item.chatInteraction.presentation.selectionState { return selectionState.selectedIds.contains(message.id) } return false } + func isSelectInGroup(_ location: NSPoint) -> Bool { + return isSelect + } + override var backdorColor: NSColor { - return contextMenu != nil || isSelect ? theme.colors.selectMessage : theme.colors.background + guard let item = item as? ChatRowItem else {return super.backdorColor} + if let forceBackgroundColor = item.forceBackgroundColor { + return forceBackgroundColor + } + return item.renderType == .bubble ? .clear : contextMenu != nil || isSelect ? item.presentation.colors.selectMessage : item.presentation.chatBackground } + var contentColor: NSColor { + guard let item = item as? ChatRowItem else {return backdorColor} + + if item.hasBubble { + return System.supportsTransparentFontDrawing ? .clear : item.presentation.chat.backgroundColor(item.isIncoming, item.renderType == .bubble) + //return .clear//isSelect || contextMenu != nil ? item.presentation.chat.backgoundSelectedColor(item.isIncoming, item.renderType == .bubble) : item.presentation.chat.backgroundColor(item.isIncoming, item.renderType == .bubble) + } else { + return backdorColor//backdorColor + } + } + + override func updateColors() -> Void { + super.updateColors() - rightView.backgroundColor = backdorColor - contentView.backgroundColor = backdorColor - replyView?.backgroundColor = backdorColor - nameView?.backgroundColor = backdorColor - forwardName?.backgroundColor = backdorColor - captionView?.backgroundColor = backdorColor + guard let item = item as? ChatRowItem else {return} + + rowView.backgroundColor = backdorColor + rightView.backgroundColor = item.isStateOverlayLayout ? .clear : contentColor + contentView.backgroundColor = .clear + item.replyModel?.backgroundColor = item.hasBubble ? contentColor : item.isBubbled ? item.presentation.colors.bubbleBackground_incoming : contentColor + nameView?.backgroundColor = contentColor + forwardName?.backgroundColor = contentColor + captionView?.backgroundColor = contentColor replyMarkupView?.backgroundColor = backdorColor - self.backgroundColor = backdorColor + bubbleView.background = item.presentation.chat.bubbleBackgroundColor(item.isIncoming, item.hasBubble) + for view in contentView.subviews { if let view = view as? View { - view.backgroundColor = backdorColor + view.backgroundColor = contentColor } } - if let item = item as? ChatRowItem { - item.replyModel?.setNeedDisplay() - } } + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + mouseDragged = true + } + override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) + mouseDragged = false + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) - if let item = item as? ChatRowItem, !item.chatInteraction.isLogInteraction, !item.sending { + if let item = item as? ChatRowItem, !item.chatInteraction.isLogInteraction && !item.chatInteraction.disableSelectAbility, !item.sending, mouseInside(), !mouseDragged { - if item.chatInteraction.presentation.state == .selecting, let message = item.message { - item.chatInteraction.update({$0.withToggledSelectedMessage(message.id)}) - } else if let message = item.message { + if item.chatInteraction.presentation.state == .selecting { + forceSelectItem(item, onRightClick: false) + } else { let location = self.convert(event.locationInWindow, from: nil) if NSPointInRect(location, rightView.frame) { - if message.flags.contains(.Failed) { - confirm(for: mainWindow, with: tr(.alertSendErrorHeader), and: tr(.alertSendErrorText), okTitle: tr(.alertSendErrorResend), cancelTitle: tr(.alertSendErrorIgnore), thridTitle: tr(.alertSendErrorDelete), successHandler: { result in + if item.isFailed, let messageId = item.message?.id { + + + + let signal = item.context.account.postbox.transaction { transaction -> [MessageId] in + return transaction.getMessageFailedGroup(messageId)?.compactMap({$0.id}) ?? [] + } |> deliverOnMainQueue + + + _ = signal.start(next: { ids in + let alert:NSAlert = NSAlert() + alert.window.appearance = theme.appearance + alert.alertStyle = .informational + alert.messageText = L10n.alertSendErrorHeader + alert.informativeText = L10n.alertSendErrorText + + - switch result { - case .thrid: - item.deleteMessage() - default: - item.resendMessage() + alert.addButton(withTitle: L10n.alertSendErrorResend) + + if ids.count > 1 { + alert.addButton(withTitle: L10n.alertSendErrorResendItemsCountable(ids.count)) } + alert.addButton(withTitle: L10n.alertSendErrorDelete) + + + alert.addButton(withTitle: L10n.alertSendErrorIgnore) + + + alert.beginSheetModal(for: mainWindow, completionHandler: { [weak item] response in + switch response.rawValue { + case 1000: + item?.resendMessage([messageId]) + case 1001: + if ids.count > 1 { + item?.resendMessage(ids) + } else { + item?.deleteMessage() + } + case 1002: + if ids.count > 1 { + item?.deleteMessage() + } + default: + break + } + }) }) } else { - item.chatInteraction.update({$0.withToggledSelectedMessage(message.id)}) + forceSelectItem(item, onRightClick: true) } } } } } + func forceSelectItem(_ item: ChatRowItem, onRightClick: Bool) { + if let message = item.message { + item.chatInteraction.withToggledSelectedMessage({$0.withToggledSelectedMessage(message.id)}) + } + } + override func onShowContextMenu() { + guard let item = item as? ChatRowItem else {return} + renderLayoutType(item, animated: true) + updateColors() + item.chatInteraction.focusInputField() super.onCloseContextMenu() } override func onCloseContextMenu() { + guard let item = item as? ChatRowItem else {return} + renderLayoutType(item, animated: true) + self.rowView.change(pos: NSZeroPoint, animated: true) updateColors() super.onCloseContextMenu() } override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) + // super.draw(layer, in: ctx) if let item = self.item as? ChatRowItem { - if let fwdHeader = item.forwardHeader { - fwdHeader.1.draw(NSMakeRect(item.defLeftInset, item.forwardHeaderInset.y, fwdHeader.0.size.width, fwdHeader.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + if let fwdHeader = item.forwardHeader, !item.isBubbled, layer == rowView.layer { + let rect = NSMakeRect(item.defLeftInset, item.forwardHeaderInset.y, fwdHeader.0.size.width, fwdHeader.0.size.height) + if backingScaleFactor == 1.0 { + ctx.setFillColor(contentColor.cgColor) + ctx.fill(rect) + } + fwdHeader.1.draw(rect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } let radius:CGFloat = 1.0 @@ -230,8 +405,15 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { // ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: layer.bounds.height - radius * 2), size: CGSize(width: radius + radius, height: radius + radius))) //draw separator - if let fwdType = item.forwardType { - ctx.setFillColor(theme.colors.blueFill.cgColor) + if let fwdType = item.forwardType, !item.isBubbled, layer == rowView.layer { + + let color: NSColor + if item.isPsa { + color = item.presentation.colors.greenUI + } else { + color = item.presentation.colors.link + } + ctx.setFillColor(color.cgColor) switch fwdType { case .ShortHeader: let height = frame.height - item.forwardNameInset.y - item.defaultContentTopOffset @@ -239,8 +421,6 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { ctx.fillEllipse(in: CGRect(origin: CGPoint(x: item.defLeftInset, y: item.forwardNameInset.y), size: CGSize(width: radius + radius, height: radius + radius))) ctx.fillEllipse(in: CGRect(origin: CGPoint(x: item.defLeftInset, y: item.forwardNameInset.y + height - radius * 2), size: CGSize(width: radius + radius, height: radius + radius))) - - break case .FullHeader: ctx.fill(NSMakeRect(item.defLeftInset, item.forwardNameInset.y + radius, 2, frame.height - item.forwardNameInset.y - radius)) @@ -256,28 +436,21 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } } - - if item.isGame { - ctx.setFillColor(theme.colors.blueFill.cgColor) - let height = frame.height - item.gameInset.y - item.defaultContentTopOffset - ctx.fill(NSMakeRect(item.gameInset.x, item.gameInset.y + radius, 2, height - radius * 2)) - - ctx.fillEllipse(in: CGRect(origin: CGPoint(x: item.gameInset.x, y: item.gameInset.y), size: CGSize(width: radius + radius, height: radius + radius))) - ctx.fillEllipse(in: CGRect(origin: CGPoint(x: item.gameInset.x, y: item.gameInset.y + height - radius * 2), size: CGSize(width: radius + radius, height: radius + radius))) - } + } } override func updateMouse() { - if let shareControl = self.shareControl, let item = item as? ChatRowItem { - shareControl.change(opacity: item.chatInteraction.presentation.state != .selecting && mouseInside() ? 1.0 : 0.0, animated: true) + if let shareView = self.shareView, let item = item as? ChatRowItem { + shareView.change(opacity: item.chatInteraction.presentation.state != .selecting && mouseInside() ? 1.0 : 0.0, animated: true) + } + if let likeControl = self.likeView, let item = item as? ChatRowItem { + likeControl.change(opacity: item.chatInteraction.presentation.state != .selecting && mouseInside() ? 1.0 : 0.0, animated: true) } } - var contentFrame:NSRect { - return self.contentView.frame - } + override func addSubview(_ view: NSView) { self.contentView.addSubview(view) @@ -289,26 +462,26 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { if replyView == nil { replyView = ChatAccessoryView() - replyView?.backgroundColor = backdorColor - super.addSubview(replyView!) + rowView.addSubview(replyView!) + } + + if reply.isSideAccessory { + replyView?.layer?.cornerRadius = .cornerRadius + } else { + replyView?.layer?.cornerRadius = 0 } replyView?.removeAllHandlers() replyView?.set(handler: { [weak item, weak reply] _ in + item?.chatInteraction.focusInputField() if let replyMessage = reply?.replyMessage, let fromMessage = item?.message { - item?.chatInteraction.focusMessageId(fromMessage.id, replyMessage.id, .center(id: 0, animated: true, focus: true, inset: 0)) + item?.chatInteraction.focusMessageId(fromMessage.id, replyMessage.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) } }, for: .Click) -// replyView?.set(handler: { [weak reply, weak item] control in -// if let replyMessageId = reply?.replyMessage?.id, let item = item { -// showPopover(for: control, with: ChatReplyPreviewController(item.account, messageId: replyMessageId, width: min(item.width - 160, 500)), inset: NSMakePoint(-8, 1)) -// } -// }, for: .LongOver) - reply.view = replyView - reply.view?.needsDisplay = true + //reply.view?.needsDisplay = true } else { replyView?.removeFromSuperview() replyView = nil @@ -316,36 +489,302 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } + var bubbleFrame: NSRect { + guard let item = item as? ChatRowItem else {return NSZeroRect} + let bubbleFrame = item.bubbleFrame + return NSMakeRect(item.isIncoming ? bubbleFrame.minX : frame.width - bubbleFrame.width - item.leftInset, bubbleFrame.minY, bubbleFrame.width, bubbleFrame.height) + } + + var rightFrame: NSRect { + guard let item = item as? ChatRowItem else {return NSZeroRect} + + let rightSize = item.rightSize + + var rect = NSMakeRect(frame.width - rightSize.width - item.rightInset, item.defaultContentTopOffset, rightSize.width, rightSize.height) + let bubbleFrame = self.bubbleFrame + let hasBubble = item.hasBubble + if item.isBubbled { + rect.origin = NSMakePoint((hasBubble ? bubbleFrame.maxX : contentFrame.maxX) - rightSize.width - item.bubbleContentInset - (item.isIncoming ? 0 : item.additionBubbleInset), bubbleFrame.maxY - rightSize.height - 6 - (item.isStateOverlayLayout && !hasBubble ? 2 : 0)) + + if item.isStateOverlayLayout { + if item.isInstantVideo { + rect.origin.y = contentFrame.maxY - rect.height - 3 + } else { + rect.origin.x += 5 + rect.origin.y -= 2 + rect.origin.x = max(20, rect.origin.x) + } + } + if item is ChatVideoMessageItem { + rect.origin.x = item.isIncoming ? contentFrame.maxX - 40 : contentFrame.maxX - rightSize.width + rect.origin.y += 3 + } + if let item = item as? ChatMessageItem, item.containsBigEmoji { + rect.origin.y = contentFrame.maxY + item.defaultContentTopOffset + } + } + + return rect + } + + var avatarFrame: NSRect { + guard let item = item as? ChatRowItem else {return NSZeroRect} + + var rect = NSMakeRect(item.leftInset, 6, 36, 36) + + if item.isBubbled { + rect.origin.y = frame.height - 36 + } + + return rect + } + var captionFrame: NSRect { + guard let item = item as? ChatRowItem, let captionLayout = item.captionLayout else {return NSZeroRect} + + return NSMakeRect(contentFrame.minX + item.elementsContentInset, contentFrame.maxY + item.defaultContentInnerInset, captionLayout.layoutSize.width, captionLayout.layoutSize.height) + } + + var replyMarkupFrame: NSRect { + guard let item = item as? ChatRowItem, let replyMarkup = item.replyMarkupModel else {return NSZeroRect} + + var frame = NSMakeRect(contentFrame.minX + item.elementsContentInset, contentFrame.maxY + item.defaultReplyMarkupInset, replyMarkup.size.width, replyMarkup.size.height) + + if let captionLayout = item.captionLayout { + frame.origin.y += captionLayout.layoutSize.height + item.defaultContentInnerInset + } + + if item.hasBubble { + frame.origin.y = bubbleFrame.maxY + item.defaultReplyMarkupInset + frame.origin.x = bubbleFrame.minX + (item.isIncoming ? item.additionBubbleInset : 0) + } + + return frame + } + + var replyFrame: NSRect { + guard let item = item as? ChatRowItem, let reply = item.replyModel else {return NSZeroRect} + + var frame: NSRect = NSMakeRect(contentFrame.minX + item.elementsContentInset, item.replyOffset, reply.size.width, reply.size.height) + if item.isBubbled, !item.hasBubble { + if item.isIncoming { + frame.origin.x = contentFrame.maxX + 10 + } else { + frame.origin.x = contentFrame.minX - reply.size.width - 10 + } + } + return frame + } + + var viaAccesoryPoint: NSPoint { + guard let item = item as? ChatRowItem, let viaAccessory = viaAccessory else {return NSZeroPoint} + + if viaAccessory.superview == replyView { + return NSMakePoint(5, 0) + } + + var point: NSPoint = NSMakePoint(contentFrame.minX + item.elementsContentInset, item.defaultContentTopOffset) + if item.isBubbled, !item.hasBubble { + if item.isIncoming { + point.x = contentFrame.maxX + 10 + } else { + point.x = contentFrame.minX - viaAccessory.frame.width - 10 + } + } + return point + } + + var namePoint: NSPoint { + guard let item = item as? ChatRowItem else {return NSZeroPoint} + + var point = NSMakePoint(contentFrame.minX, item.defaultContentTopOffset) + if item.isBubbled { + point.y -= item.topInset + } else { + if item.forwardType != nil { + point.x -= item.leftContentInset + } + } + point.x += item.elementsContentInset + return point + + } + + var scamPoint: NSPoint { + guard let item = item as? ChatRowItem, let authorText = item.authorText else {return NSZeroPoint} + + var point = self.namePoint + point.x += authorText.layoutSize.width + 3 + point.y += 1 + return point + } + + var psaPoint: NSPoint { + guard let item = item as? ChatRowItem else {return NSZeroPoint} + var point: NSPoint = .zero + if item.isBubbled, let _ = item.forwardNameLayout { + point.x = item.bubbleFrame.width - 20 + point.y = self.forwardNamePoint.y + } else if item.entry.renderType == .list, let name = item.authorText { + point = self.namePoint + point.x += name.layoutSize.width + point.y -= 6 + } + + // point.y -= 7 + return point + } + + var scamForwardPoint: NSPoint { + guard let item = item as? ChatRowItem, let forwardName = item.forwardNameLayout else {return NSZeroPoint} + + var point = self.forwardNamePoint + point.x += forwardName.layoutSize.width + 3 + //point.y += 1 + return point + } + + var adminBadgePoint: NSPoint { + guard let item = item as? ChatRowItem, let adminBadge = item.adminBadge, let authorText = item.authorText else {return NSZeroPoint} + + var point = NSMakePoint( item.isBubbled ? bubbleFrame.maxX - item.bubbleContentInset - adminBadge.layoutSize.width : namePoint.x + authorText.layoutSize.width, item.defaultContentTopOffset + 1) + if item.isBubbled { + point.y -= item.topInset + } + return point + } + + var selectingPoint: NSPoint { + + guard let item = item as? ChatRowItem else {return NSZeroPoint} + + var point = NSZeroPoint + + if let selectingView = selectingView { + if item.isBubbled { + let f = focus(selectingView.frame.size) + point.y = f.minY + point.x = frame.width - selectingView.frame.width - 15 + } else { + point = NSMakePoint(rightFrame.maxX + 4, item.defaultContentTopOffset - 1) + } + } + return point + } + + var contentFrame:NSRect { + guard let item = item as? ChatRowItem else {return NSZeroRect} + var rect = NSMakeRect(item.contentOffset.x, item.contentOffset.y, item.contentSize.width, item.contentSize.height) + if item.isBubbled { + if !item.isIncoming { + rect.origin.x = bubbleFrame.minX + item.bubbleContentInset + } else { + rect.origin.x = bubbleFrame.minX + item.bubbleContentInset + item.additionBubbleInset + } + + } + return rect + } + + var contentFrameModifier: NSRect { + return self.contentFrame + } + + var rowPoint: NSPoint { + guard let item = item as? ChatRowItem else {return NSZeroPoint} + + if item.isBubbled { + return NSMakePoint((item.chatInteraction.presentation.state == .selecting && !item.isIncoming ? -20 : 0), 0) + } else { + return NSMakePoint(0, 0) + } + } + + var forwardNamePoint: NSPoint { + guard let item = item as? ChatRowItem else {return NSZeroPoint} + + var point = item.forwardNameInset + + if item.isBubbled && item.hasBubble { + point.x = bubbleFrame.minX + (item.isIncoming ? item.bubbleContentInset + item.additionBubbleInset : item.bubbleContentInset) + } else if item.isBubbled, let forwardAccessory = forwardAccessory { + point.x = item.isIncoming ? contentFrame.maxX : contentFrame.minX - forwardAccessory.frame.width + } + + return point + } override func layout() { - super.layout() + // super.layout() if let item = item as? ChatRowItem { - forwardName?.setFrameOrigin(item.forwardNameInset.x, item.forwardNameInset.y) - contentView.frame = NSMakeRect(item.contentOffset.x, item.contentOffset.y, item.contentSize.width, item.contentSize.height) - rightView.frame = NSMakeRect(frame.width - item.rightSize.width - item.rightInset, item.defaultContentTopOffset, item.rightSize.width, item.rightSize.height) - if let reply = item.replyModel { - reply.frame = NSMakeRect(contentFrame.minX, item.replyOffset, reply.size.width,reply.size.height) - } - avatar?.frame = NSMakeRect(item.leftInset, item.defaultContentTopOffset, 36, 36) + bubbleView.frame = bubbleFrame + contentView.frame = contentFrameModifier - var additionInset:CGFloat = contentView.frame.maxY + item.defaultContentTopOffset - if let captionLayout = item.captionLayout { - captionView?.frame = NSMakeRect(contentView.frame.minX, additionInset, captionLayout.layoutSize.width, captionLayout.layoutSize.height) - additionInset += captionLayout.layoutSize.height + item.defaultContentTopOffset - } + - item.replyModel?.view?.needsDisplay = true + rowView.setFrameOrigin(rowPoint) - if let replyMarkup = item.replyMarkupModel { - replyMarkupView?.frame = NSMakeRect(contentView.frame.minX, additionInset, replyMarkup.size.width, replyMarkup.size.height) - replyMarkup.layout() - } + forwardName?.setFrameOrigin(forwardNamePoint) + forwardAccessory?.setFrameOrigin(forwardNamePoint) + + rightView.frame = rightFrame + + nameView?.setFrameOrigin(namePoint) + + adminBadge?.setFrameOrigin(adminBadgePoint) + + viaAccessory?.setFrameOrigin(viaAccesoryPoint) + item.replyModel?.frame = replyFrame + + + scamButton?.setFrameOrigin(scamPoint) + scamForwardButton?.setFrameOrigin(scamForwardPoint) + + psaButton?.setFrameOrigin(psaPoint) + + avatar?.frame = avatarFrame + captionView?.frame = captionFrame + + replyMarkupView?.frame = replyMarkupFrame + item.replyMarkupModel?.layout() + + + selectingView?.setFrameOrigin(selectingPoint) + + animatedView?.frame = bounds + + + swipingRightView.frame = NSMakeRect(frame.width, 0, rightRevealWidth, frame.height) + + shareView?.setFrameOrigin(shareViewPoint(item)) + likeView?.setFrameOrigin(likeViewPoint(item)) - selectingView?.setFrameOrigin(rightView.frame.maxX + 4,item.defaultContentTopOffset - 1) - if let shareControl = shareControl { - shareControl.setFrameOrigin(frame.width - 20.0 - shareControl.frame.width, rightView.frame.maxY + 5) - } + } + } + + func shareViewPoint(_ item: ChatRowItem) -> NSPoint { + guard let shareView = self.shareView else { + return .zero + } + if item.isBubbled { + return NSMakePoint(item.isIncoming ? max(bubbleFrame.maxX + 10, item.isStateOverlayLayout ? rightFrame.width + 10 : 0) : bubbleFrame.minX - shareView.frame.width - 10, bubbleFrame.maxY - (shareView.frame.height - 2) - (item.isVideoOrBigEmoji ? rightFrame.height + 14 : 0)) + } else { + return NSMakePoint(frame.width - 20.0 - shareView.frame.width, rightView.frame.maxY) + } + } + + func likeViewPoint(_ item: ChatRowItem) -> NSPoint { + guard let likeView = self.likeView else { + return .zero + } + var controlOffset: CGFloat = 0 + if let shareView = shareView { + controlOffset += shareView.frame.width + 10 + } + if item.isBubbled { + return NSMakePoint(item.isIncoming ? max(bubbleFrame.maxX + 10 + controlOffset, item.isStateOverlayLayout ? rightFrame.width + 10 + controlOffset : 0) : bubbleFrame.minX - likeView.frame.width - 10 - controlOffset, bubbleFrame.maxY - (likeView.frame.height - 2) - (item.isVideoOrBigEmoji ? rightFrame.height + 14 : 0)) + } else { + return NSMakePoint(frame.width - 20.0 - likeView.frame.width, rightView.frame.maxY) } } @@ -353,42 +792,52 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { func fillForward(_ item:ChatRowItem) -> Void { if let forwardNameLayout = item.forwardNameLayout { - if forwardName == nil { - forwardName = TextView() - forwardName?.isSelectable = false - super.addSubview(forwardName!) - } - if !forwardName!.isEqual(to: forwardNameLayout) { + if item.isBubbled && !item.hasBubble { + forwardName?.removeFromSuperview() + forwardName = nil + + if forwardAccessory == nil { + forwardAccessory = ChatBubbleAccessoryForward(frame: NSZeroRect) + rowView.addSubview(forwardAccessory!) + } + + forwardAccessory?.updateText(layout: forwardNameLayout) + + } else { + forwardAccessory?.removeFromSuperview() + forwardAccessory = nil + + if forwardName == nil { + forwardName = TextView() + forwardName?.isSelectable = false + rowView.addSubview(forwardName!) + } forwardName?.update(forwardNameLayout) + } + } else { forwardName?.removeFromSuperview() forwardName = nil + forwardAccessory?.removeFromSuperview() + forwardAccessory = nil } } func fillPhoto(_ item:ChatRowItem) -> Void { - if case .Full = item.itemType, item.peer != nil { + if item.hasPhoto, let peer = item.peer { if avatar == nil { avatar = AvatarControl(font: .avatar(.text)) avatar?.setFrameSize(36,36) - super.addSubview(avatar!) + rowView.addSubview(avatar!) } avatar?.removeAllHandlers() - avatar?.set(handler: { control in - if let peerId = item.peer?.id { - item.chatInteraction.openInfo(peerId, false, nil, nil) - } + avatar?.set(handler: { [weak item] control in + item?.openInfo() }, for: .Click) - - avatar?.set(handler: { control in - if let peerId = item.peer?.id { - showDetailInfoPopover(forPeerId: peerId, account: item.account, fromView: control) - } - }, for: .LongOver) - - self.avatar?.setPeer(account: item.account, peer: item.peer!) + avatar?.toolTip = item.nameHide + self.avatar?.setPeer(account: item.context.account, peer: peer, message: item.message) } else { avatar?.removeFromSuperview() @@ -396,95 +845,352 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } } - func fillCaption(_ item:ChatRowItem) -> Void { + func fillPsaButton(_ item: ChatRowItem) -> Void { + if let text = item.psaButton, item.forwardNameLayout != nil || !item.isBubbled { + + let icon = item.presentation.chat.channelInfoPromo(item.isIncoming, item.isBubbled, icons: theme.icons) + + if psaButton == nil { + psaButton = ImageButton() + psaButton?.autohighlight = false + psaButton?.setFrameSize(icon.backingSize) + rowView.addSubview(psaButton!) + psaButton?.set(handler: { control in + tooltip(for: control, text: "", attributedText: text, interactions: globalLinkExecutor) + }, for: .Click) + } + psaButton?.set(image: icon, for: .Normal) + + } else { + psaButton?.removeFromSuperview() + psaButton = nil + } + } + + func fillScamButton(_ item: ChatRowItem) -> Void { + if item.isScam, item.canFillAuthorName { + if scamButton == nil { + scamButton = ImageButton() + scamButton?.autohighlight = false + scamButton?.setFrameSize(item.presentation.icons.chatScam.backingSize) + rowView.addSubview(scamButton!) + scamButton?.set(handler: { control in + tooltip(for: control, text: L10n.peerInfoScamWarning) + }, for: .Click) + } + scamButton?.set(image: item.presentation.icons.chatScam, for: .Normal) + + } else { + scamButton?.removeFromSuperview() + scamButton = nil + } + } + + func fillScamForwardButton(_ item: ChatRowItem) -> Void { + if item.isForwardScam { + if scamForwardButton == nil { + scamForwardButton = ImageButton() + scamForwardButton?.autohighlight = false + scamForwardButton?.setFrameSize(item.presentation.icons.chatScam.backingSize) + rowView.addSubview(scamForwardButton!) + scamForwardButton?.set(handler: { control in + tooltip(for: control, text: L10n.peerInfoScamWarning) + }, for: .Click) + } + scamForwardButton?.set(image: item.presentation.icons.chatScam, for: .Normal) + + } else { + scamForwardButton?.removeFromSuperview() + scamForwardButton = nil + } + } + + func fillCaption(_ item:ChatRowItem, animated: Bool) -> Void { if let layout = item.captionLayout { if captionView == nil { captionView = TextView() - super.addSubview(captionView!) + rowView.addSubview(captionView!) + rowView.addSubview(rightView) + captionView?.frame = captionFrame } + //addSubview(captionView!, positioned: .below, relativeTo: rightView) captionView?.update(layout) } else { - captionView?.removeFromSuperview() - captionView = nil + if animated, let captionView = self.captionView { + self.captionView = nil + captionView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak captionView] _ in + captionView?.removeFromSuperview() + }) + } else { + captionView?.removeFromSuperview() + captionView = nil + } + } + } + + func fillShareView(_ item:ChatRowItem, animated: Bool) -> Void { + if item.shareVisible || item.isStorage { + var isPresented: Bool = true + if shareView == nil { + shareView = ImageButton() + shareView?.set(hoverAdditionPolicy: .enlarge(value: 1.05), for: .Hover) + shareView?.set(hoverAdditionPolicy: .enlarge(value: 1.0), for: .Normal) + shareView?.set(hoverAdditionPolicy: .enlarge(value: 1.05), for: .Highlight) + shareView?.set(additionBackgroundMultiplier: 0.95, for: .Normal) + shareView?.set(additionBackgroundMultiplier: 0.95, for: .Hover) + shareView?.set(additionBackgroundMultiplier: 0.95, for: .Highlight) + shareView?.disableActions() + shareView?.change(opacity: 0, animated: false) + rowView.addSubview(shareView!) + isPresented = false + } + + guard let control = shareView else {return} + control.autohighlight = false + + + if animated && isPresented { + control.change(pos: shareViewPoint(item), animated: true) + } else { + control.setFrameOrigin(shareViewPoint(item)) + } + + if item.isBubbled && item.presentation.backgroundMode.hasWallpapaer { + + control.set(image: item.isStorage ? item.presentation.chat.chat_goto_message_bubble(theme: item.presentation) : item.presentation.chat.chat_share_bubble(theme: item.presentation), for: .Normal) + _ = control.sizeToFit() + let size = NSMakeSize(control.frame.width, control.frame.height) + control.setFrameSize(NSMakeSize(floorToScreenPixels(backingScaleFactor, (size.width + 4) * 1.05), floorToScreenPixels(backingScaleFactor, (size.height + 4) * 1.05))) + control.set(additionBackgroundColor: item.presentation.chatServiceItemColor, for: .Normal) + control.set(additionBackgroundColor: item.presentation.chatServiceItemColor, for: .Hover) + + control.set(cornerRadius: .half, for: .Normal) + } else { + control.set(image: item.isStorage ? item.presentation.icons.chat_goto_message : item.presentation.icons.chat_share_message, for: .Normal) + _ = control.sizeToFit() + control.background = .clear + } + + control.removeAllHandlers() + control.set(handler: { [ weak item] _ in + if let item = item { + if item.isStorage { + item.gotoSourceMessage() + } else { + item.share() + } + } + }, for: .Click) + } else { + shareView?.removeFromSuperview() + shareView = nil } } - func fillShareControl(_ item:ChatRowItem) -> Void { - if item.isSharable { - if shareControl == nil { - shareControl = ImageButton() - shareControl?.disableActions() - shareControl?.change(opacity: 0, animated: false) - super.addSubview(shareControl!) + private func likeImage(_ item: ChatRowItem) -> CGImage { + if item.isLiked { + return item.presentation.chat.chat_like_message_unlike_bubble(theme: item.presentation) + } else { + return item.presentation.chat.chat_like_message_bubble(theme: item.presentation) + } + } + + override func change(size: NSSize, animated: Bool, _ save: Bool = true, removeOnCompletion: Bool = true, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + + rowView.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + super.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + } + + func fillLikeView(_ item: ChatRowItem, animated: Bool) { + if item.isLikable { + var isPresented: Bool = true + if likeView == nil { + likeView = ImageButton() + likeView?.set(hoverAdditionPolicy: .enlarge(value: 1.05), for: .Hover) + likeView?.set(hoverAdditionPolicy: .enlarge(value: 1.0), for: .Normal) + likeView?.set(hoverAdditionPolicy: .enlarge(value: 1.05), for: .Highlight) + likeView?.set(additionBackgroundMultiplier: 0.95, for: .Normal) + likeView?.set(additionBackgroundMultiplier: 0.95, for: .Hover) + likeView?.set(additionBackgroundMultiplier: 0.95, for: .Highlight) + likeView?.autohighlight = false + likeView?.disableActions() + likeView?.change(opacity: 0, animated: false) + rowView.addSubview(likeView!) + isPresented = false } - shareControl?.set(image: theme.icons.chatShare, for: .Normal) - shareControl?.sizeToFit() - shareControl?.removeAllHandlers() - shareControl?.set(handler: { [weak self] _ in - if let window = self?.contentView.kitWindow, let message = item.message { - showModal(with: ShareModalController(ShareMessageObject(item.account, message)), for: window) + + guard let control = likeView else {return} + + if animated && isPresented { + control.change(pos: likeViewPoint(item), animated: true) + } + + let isLiked = item.isLiked + + if item.isBubbled && item.presentation.backgroundMode.hasWallpapaer { + control.set(image: likeImage(item), for: .Normal) + + _ = control.sizeToFit() + let size = NSMakeSize(control.frame.width, control.frame.height) + control.setFrameSize(NSMakeSize(floorToScreenPixels(backingScaleFactor, (size.width + 4) * 1.05), floorToScreenPixels(backingScaleFactor, (size.height + 4) * 1.05))) + control.set(additionBackgroundColor: item.presentation.chatServiceItemColor, for: .Normal) + + control.set(cornerRadius: .half, for: .Normal) + } else { + control.set(image: item.presentation.icons.chat_like_message, for: .Normal) + _ = control.sizeToFit() + control.background = .clear + } + + control.removeAllHandlers() + control.set(handler: { [weak item] control in + if let item = item { + let presentation = item.presentation.chat + let from = isLiked ? presentation.chat_like_message_unlike_bubble(theme: item.presentation) : presentation.chat_like_message_bubble(theme: item.presentation) + let to = isLiked ? presentation.chat_like_message_bubble(theme: item.presentation) : presentation.chat_like_message_unlike_bubble(theme: item.presentation) + + (control as? ImageButton)?.applyAnimation(from: from, to: to, animation: .replaceScale) + + item.toggleLike() } + + }, for: .Click) } else { - shareControl?.removeFromSuperview() - shareControl = nil + likeView?.removeFromSuperview() + likeView = nil } } - func fillReplyMarkup(_ item:ChatRowItem) -> Void { + func fillReplyMarkup(_ item:ChatRowItem, animated: Bool) -> Void { if let replyMarkup = item.replyMarkupModel { if replyMarkupView == nil { replyMarkupView = View() - super.addSubview(replyMarkupView!) + rowView.addSubview(replyMarkupView!) + replyMarkupView?.frame = replyMarkupFrame } replyMarkupView?.setFrameSize(replyMarkup.size.width, replyMarkup.size.height) replyMarkup.view = replyMarkupView - replyMarkup.view?.backgroundColor = theme.colors.background replyMarkup.redraw() } else { - replyMarkupView?.removeFromSuperview() - replyMarkupView = nil + if let replyMarkupView = self.replyMarkupView, animated { + self.replyMarkupView = nil + replyMarkupView.layer?.animateScaleCenter(from: 1, to: 0.1, duration: 0.2, removeOnCompletion: false) + replyMarkupView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyMarkupView] _ in + replyMarkupView?.removeFromSuperview() + }) + } else { + replyMarkupView?.removeFromSuperview() + replyMarkupView = nil + } } } + + func fillName(_ item:ChatRowItem) -> Void { if let author = item.authorText { - if nameView == nil { - nameView = TextView() - nameView?.isSelectable = false - super.addSubview(nameView!) + if item.isBubbled && !item.hasBubble { + nameView?.removeFromSuperview() + nameView = nil + + adminBadge?.removeFromSuperview() + adminBadge = nil + + if viaAccessory == nil { + viaAccessory = ChatBubbleViaAccessory(frame: NSZeroRect) + } + + guard let viaAccessory = viaAccessory else {return} + + viaAccessory.removeFromSuperview() + if replyView != nil { + replyView?.addSubview(viaAccessory) + } else { + rowView.addSubview(viaAccessory) + } + + viaAccessory.updateText(layout: author) + + + } else { + + viaAccessory?.removeFromSuperview() + viaAccessory = nil + + if nameView == nil { + nameView = TextView() + nameView?.isSelectable = false + + rowView.addSubview(nameView!) + } + + if let adminBadge = item.adminBadge { + if self.adminBadge == nil { + self.adminBadge = TextView() + self.adminBadge?.isSelectable = false + rowView.addSubview(self.adminBadge!) + } + self.adminBadge?.update(adminBadge, origin: adminBadgePoint) + } else { + adminBadge?.removeFromSuperview() + adminBadge = nil + } + + nameView?.update(author, origin: namePoint) + nameView?.toolTip = item.nameHide } - nameView?.update(author, origin:NSMakePoint(item.defLeftInset, item.defaultContentTopOffset)) + } else { + + viaAccessory?.removeFromSuperview() + viaAccessory = nil + nameView?.removeFromSuperview() nameView = nil + + adminBadge?.removeFromSuperview() + adminBadge = nil } } - override func focusAnimation() { + override func focusAnimation(_ innerId: AnyHashable?) { if animatedView == nil { - self.animatedView = ChatRowAnimateView(frame:bounds) + self.animatedView = RowAnimateView(frame:bounds) self.animatedView?.isEventLess = true - super.addSubview(animatedView!) - animatedView?.backgroundColor = NSColor(0x68A8E2) + rowView.addSubview(animatedView!) + animatedView?.backgroundColor = theme.colors.focusAnimationColor animatedView?.layer?.opacity = 0 } animatedView?.stableId = item?.stableId - animatedView?.change(opacity: 0.5, animated: true, false, removeOnCompletion: false, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in + + + let animation: CABasicAnimation = makeSpringAnimation("opacity") + + animation.fromValue = animatedView?.layer?.presentation()?.opacity ?? 0 + animation.toValue = 0.5 + animation.autoreverses = true + animation.isRemovedOnCompletion = true + animation.fillMode = CAMediaTimingFillMode.forwards + + animation.delegate = CALayerAnimationDelegate(completion: { [weak self] completed in if completed { - self?.animatedView?.change(opacity: 0, animated: true, false, removeOnCompletion: true, duration: 1.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in - if completed { - self?.animatedView?.removeFromSuperview() - self?.animatedView = nil - } - }) + self?.animatedView?.removeFromSuperview() + self?.animatedView = nil } }) - + animation.isAdditive = false + animatedView?.layer?.add(animation, forKey: "opacity") + } + + func canDropSelection(in location: NSPoint) -> Bool { + return true } override func rightMouseDown(with event: NSEvent) { @@ -496,8 +1202,96 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { super.rightMouseDown(with: event) } + + private func renderLayoutType(_ item: ChatRowItem, animated: Bool) { + if item.isBubbled, item.hasBubble { + bubbleView.setType(image: item.bubbleImage, border: item.bubbleBorderImage, background: item.isIncoming ? item.presentation.icons.chatGradientBubble_incoming : item.presentation.icons.chatGradientBubble_outgoing) + } else { + bubbleView.setType(image: nil, border: nil, background: item.isIncoming ? item.presentation.icons.chatGradientBubble_incoming : item.presentation.icons.chatGradientBubble_outgoing) + } + } + + func animateInStateView() { + rightView.layer?.animateAlpha(from: 0, to: 1.0, duration: 0.15) + } + + func shakeContentView() { + + guard let item = item as? ChatRowItem else { return } + + if bubbleView.layer?.animation(forKey: "shake") != nil { + return + } + + let translation = CAKeyframeAnimation(keyPath: "transform.translation.x"); + translation.timingFunction = CAMediaTimingFunction(name: .linear) + translation.values = [-2, 2, -2, 2, -2, 2, -2, 2, 0] + + let rotation = CAKeyframeAnimation(keyPath: "transform.rotation.z") + rotation.values = [-0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0].map { + ( degrees: Double) -> Double in + let radians: Double = (.pi * degrees) / 180.0 + return radians + } + + let shakeGroup: CAAnimationGroup = CAAnimationGroup() + shakeGroup.isRemovedOnCompletion = true + shakeGroup.animations = [rotation] + shakeGroup.timingFunction = .init(name: .easeInEaseOut) + shakeGroup.duration = 0.5 + + + + let frame = bubbleFrame + let contentFrame = self.contentFrameModifier + + contentView.layer?.position = NSMakePoint(contentFrame.minX + contentFrame.width / 2, contentFrame.minY + contentFrame.height / 2) + contentView.layer?.anchorPoint = NSMakePoint(0.5, 0.5); + + if item.hasBubble { + + struct ShakeItem { + let view: NSView + let rect: NSRect + let tempRect: NSRect + } + let views:[NSView] = [self.rightView, self.nameView, self.scamButton, self.replyView, self.adminBadge, self.forwardName, self.scamForwardButton, self.viaAccessory, self.captionView].compactMap { $0 } + let shakeItems = views.map { view -> ShakeItem in + return ShakeItem(view: view, rect: view.frame, tempRect: self.bubbleView.convert(view.frame, from: view.superview)) + } + + for item in shakeItems { + item.view.removeFromSuperview() + item.view.frame = item.tempRect + bubbleView.addSubview(item.view) + } + + + shakeGroup.delegate = CALayerAnimationDelegate(completion: { [weak self] _ in + guard let `self` = self else { + return + } + for item in shakeItems { + item.view.removeFromSuperview() + item.view.frame = item.rect + self.rowView.addSubview(item.view) + } + }) + } + + bubbleView.layer?.position = NSMakePoint(frame.minX + frame.width / 2, frame.minY + frame.height / 2) + bubbleView.layer?.anchorPoint = NSMakePoint(0.5, 0.5); + + + bubbleView.layer?.add(shakeGroup, forKey: "shake") + contentView.layer?.add(shakeGroup, forKey: "shake") + + + } + override func set(item:TableRowItem, animated:Bool = false) { + if let item = self.item as? ChatRowItem { item.chatInteraction.remove(observer: self) } @@ -506,47 +1300,101 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { self.animatedView = nil } + let animated = animated && ((item as? ChatRowItem)?.isBubbled ?? false) + if let item = item as? ChatRowItem { + renderLayoutType(item, animated: animated) + + + item.chatInteraction.add(observer: self) + + updateSelectingState(selectingMode:item.chatInteraction.presentation.selectionState != nil, item: item, needUpdateColors: false) + } + + super.set(item: item, animated: animated) + + if let item = item as? ChatRowItem { rightView.set(item:item, animated:animated) - fillName(item) fillReplyIfNeeded(item.replyModel, item) + fillName(item) fillForward(item) fillPhoto(item) - fillCaption(item) - fillReplyMarkup(item) - fillShareControl(item) - item.chatInteraction.add(observer: self) - - updateSelectingState(selectingMode:item.chatInteraction.presentation.selectionState != nil, item: item) + fillForward(item) + fillScamButton(item) + fillScamForwardButton(item) + fillPsaButton(item) + fillShareView(item, animated: animated) + fillLikeView(item, animated: animated) + fillReplyMarkup(item, animated: animated) + fillCaption(item, animated: animated) + + if animated { + + let bubbleFrame = self.bubbleFrame + let contentFrameModifier = self.contentFrameModifier + + bubbleView.change(pos: bubbleFrame.origin, animated: animated) + bubbleView.change(size: bubbleFrame.size, animated: animated) + contentView.change(pos: contentFrameModifier.origin, animated: animated) + contentView.change(size: contentFrameModifier.size, animated: animated) + updateBackground(animated: animated) + + if rightFrame.width != rightView.frame.width && rightFrame.minX < rightView.frame.minX { + rightView.setFrameOrigin(NSMakePoint(rightFrame.minX, rightView.frame.minY)) + } + rightView.change(pos: rightFrame.origin, animated: animated) + replyView?._change(pos: replyFrame.origin, animated: animated) + replyMarkupView?.change(pos: replyMarkupFrame.origin, animated: animated) + captionView?._change(pos: captionFrame.origin, animated: animated) + } } - super.set(item: item, animated: animated) - self.needsLayout = true + rowView.needsDisplay = true + needsLayout = true } - open override var interactionContentView:NSView { + open override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return self.contentView } override func doubleClick(in location: NSPoint) { if let item = self.item as? ChatRowItem, item.chatInteraction.presentation.state == .normal { - if self.hitTest(location) == nil || self.hitTest(location) == self || self.hitTest(location) == replyView { + if self.hitTest(location) == nil || self.hitTest(location) == self || !clickInContent(point: location) || self.hitTest(location) == rowView || self.hitTest(location) == bubbleView || self.hitTest(location) == replyView { if let avatar = avatar { if NSPointInRect(location, avatar.frame) { return } } - item.chatInteraction.setupReplyMessage(item.message?.id) + if NSPointInRect(location, bubbleFrame), item.isBubbled { + return + } + if let message = item.message, canReplyMessage(message, peerId: item.chatInteraction.peerId) { + item.chatInteraction.setupReplyMessage(item.message?.id) + } } } } + func toggleSelected(_ select: Bool, in point: NSPoint) { + guard let item = item as? ChatRowItem else { return } + + item.chatInteraction.withToggledSelectedMessage({ current in + if let message = item.message { + if (select && !current.isSelectedMessageId(message.id)) || (!select && current.isSelectedMessageId(message.id)) { + return current.withToggledSelectedMessage(message.id) + } + } + return current + }) + } + override func forceClick(in location: NSPoint) { guard let item = item as? ChatRowItem else { return } + let hitTestView = self.hitTest(location) - if hitTestView == nil || hitTestView == self || hitTestView == replyView || hitTestView?.isDescendant(of: contentView) == true { + if hitTestView == nil || hitTestView == self || hitTestView == replyView || hitTestView?.isDescendant(of: contentView) == true || hitTestView == rowView || hitTestView == self.animatedView { if let avatar = avatar { if NSPointInRect(location, avatar.frame) { return @@ -560,14 +1408,22 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { result = item.replyAction() case .forward: result = item.forwardAction() + case .previewMedia: + result = false } if result { - focusAnimation() + focusAnimation(nil) + } else { + // NSSound.beep() } } } + func previewMediaIfPossible() -> Bool { + return false + } + deinit { if let item = self.item as? ChatRowItem { item.chatInteraction.remove(observer: self) @@ -575,6 +1431,10 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { contentView.removeAllSubviews() } + override func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + return contentView.convert(point, from: nil) + } + override func viewDidMoveToWindow() { super.viewDidMoveToWindow() if let item = self.item as? ChatRowItem { @@ -586,4 +1446,155 @@ class ChatRowView: TableRowView, Notifable, MultipleSelectable { } } + + // swiping methods + + private var swipingRightView: View = View() + + private var animateOnceAfterDelta: Bool = true + + var additionalRevealDelta: CGFloat { + return 0 + } + + var containerX: CGFloat { + return rowView.frame.minX + } + var width: CGFloat { + return rowView.frame.width + } + + var rightRevealWidth: CGFloat { + return 40 + } + + var leftRevealWidth: CGFloat { + return 0 + } + + var endRevealState: SwipeDirection? + + func initRevealState() { + swipingRightView.removeAllSubviews() + swipingRightView.setFrameSize(rightRevealWidth, frame.height) + + + guard let item = item as? ChatRowItem else {return} + + let control = ImageButton() + control.disableActions() + + + if item.isBubbled && item.presentation.backgroundMode.hasWallpapaer { + control.set(image: item.presentation.chat.chat_reply_swipe_bubble(theme: item.presentation), for: .Normal) + control.autohighlight = false + _ = control.sizeToFit() + control.setFrameSize(NSMakeSize(control.frame.width + 4, control.frame.height + 4)) + control.set(background: item.presentation.chatServiceItemColor, for: .Normal) + control.set(background: item.presentation.chatServiceItemColor.withAlphaComponent(0.8), for: .Highlight) + + + + control.layer?.cornerRadius = control.frame.height / 2 + } else { + control.set(image: item.presentation.icons.chat_swipe_reply, for: .Normal) + _ = control.sizeToFit() + control.background = .clear + } + swipingRightView.addSubview(control) + + control.centerY() + + } + + func moveReveal(delta: CGFloat) { + if swipingRightView.subviews.isEmpty { + initRevealState() + } + + let delta = delta - additionalRevealDelta + + + rowView.setFrameOrigin(NSMakePoint(delta, rowView.frame.minY)) + swipingRightView.change(pos: NSMakePoint(frame.width + delta, swipingRightView.frame.minY), animated: false) + + swipingRightView.change(size: NSMakeSize(max(rightRevealWidth, -delta), swipingRightView.frame.height), animated: false) + + + + let subviews = swipingRightView.subviews + let action = subviews[0] + action.centerY() + + if swipingRightView.frame.width > 100 { + if animateOnceAfterDelta { + animateOnceAfterDelta = false + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + action.layer?.animatePosition(from: NSMakePoint((swipingRightView.frame.width - action.frame.width), 0), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + } + action.setFrameOrigin(NSMakePoint(0, action.frame.minY)) + } else { + if !animateOnceAfterDelta { + animateOnceAfterDelta = true + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + action.layer?.animatePosition(from: NSMakePoint(-(swipingRightView.frame.width), 0), to: NSMakePoint(0, 0), duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, removeOnCompletion: true, additive: true) + } + action.setFrameOrigin(NSMakePoint(max(swipingRightView.frame.width, 0), action.frame.minY)) + } + + } + + func completeReveal(direction: SwipeDirection) { + + if swipingRightView.subviews.isEmpty { + initRevealState() + } + + CATransaction.begin() + + let updateRightSubviews:(Bool) -> Void = { [weak self] animated in + guard let `self` = self else {return} + let subviews = self.swipingRightView.subviews + subviews[0]._change(pos: NSMakePoint(0, subviews[0].frame.minY), animated: animated, completion: { [weak self] completed in + self?.swipingRightView.removeAllSubviews() + }) + } + + let failed:(@escaping(Bool)->Void)->Void = { [weak self] completion in + guard let `self` = self else {return} + self.rowView.change(pos: NSMakePoint(0, self.rowView.frame.minY), animated: true) + self.swipingRightView.change(pos: NSMakePoint(self.frame.width, self.swipingRightView.frame.minY), animated: true, completion: completion) + updateRightSubviews(true) + self.endRevealState = nil + } + + + + + switch direction { + case .left: + failed({_ in}) + case .right: + let invokeRightAction = swipingRightView.frame.width > 100 + if invokeRightAction { + _ = (item as? ChatRowItem)?.replyAction() + } + failed({ completed in }) + default: + self.endRevealState = nil + failed({_ in}) + } + + CATransaction.commit() + } + + + override var interactableView: NSView { + return self.rightView + } + + override func removeFromSuperview() { + super.removeFromSuperview() + } + } diff --git a/Telegram-Mac/ChatScheduleController.swift b/Telegram-Mac/ChatScheduleController.swift new file mode 100644 index 0000000000..004de6c9c0 --- /dev/null +++ b/Telegram-Mac/ChatScheduleController.swift @@ -0,0 +1,52 @@ +// +// ChatScheduleController.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox + +class ChatScheduleController: ChatController { + public override init(context: AccountContext, chatLocation:ChatLocation, mode: ChatMode = .scheduled, messageId:MessageId? = nil, initialAction:ChatInitialAction? = nil) { + super.init(context: context, chatLocation: chatLocation, mode: mode, messageId: messageId, initialAction: initialAction) + } + + + override var removeAfterDisapper: Bool { + return true + } + + override func viewDidLoad() { + super.viewDidLoad() + chatInteraction.sendPlainText = { _ in + + } + let context = self.context + + chatInteraction.requestMessageActionCallback = { _, _, _ in + alert(for: context.window, info: L10n.chatScheduledInlineButtonError) + } + + chatInteraction.vote = { _, _, _ in + alert(for: context.window, info: L10n.chatScheduledInlineButtonError) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + let controller = self.navigationController?.controller as? ChatController + let current = self.chatInteraction.presentation.interfaceState + + let count = self.genericView.tableView.count + + controller?.chatInteraction.update(animated: false, { $0.withUpdatedHasScheduled(count > 1).updatedInterfaceState { _ in return current } }) + + } + +} diff --git a/Telegram-Mac/ChatSearchView.swift b/Telegram-Mac/ChatSearchView.swift index f45f743a53..9bd11165f0 100644 --- a/Telegram-Mac/ChatSearchView.swift +++ b/Telegram-Mac/ChatSearchView.swift @@ -8,29 +8,13 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit enum TokenSearchState : Equatable { case none case from(query: String, complete: Bool) } -func ==(lhs: TokenSearchState, rhs: TokenSearchState) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case let .from(query, complete): - if case .from(query, complete) = rhs { - return true - } else { - return false - } - } -} class ChatSearchView: SearchView { private let fromView: TextView = TextView() @@ -49,7 +33,7 @@ class ChatSearchView: SearchView { countView.removeFromSuperview() updateClearVisibility(true) } - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) self.needsLayout = true } } @@ -69,8 +53,8 @@ class ChatSearchView: SearchView { fatalError("init(coder:) has not been implemented") } - override func updateLocalizationAndTheme() { - let fromLayout = TextViewLayout(.initialize(string: "\(tr(.chatSearchFrom)) ", color: theme.colors.text, font: .normal(.text))) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + let fromLayout = TextViewLayout(.initialize(string: "\(tr(L10n.chatSearchFrom)) ", color: theme.colors.text, font: .normal(.text))) fromLayout.measure(width: .greatestFiniteMagnitude) fromView.update(fromLayout) fromView.backgroundColor = theme.colors.grayBackground @@ -78,10 +62,10 @@ class ChatSearchView: SearchView { countView.backgroundColor = theme.colors.grayBackground - let countLayout = TextViewLayout(.initialize(string: tr(.chatSearchCount(countValue.current, countValue.total)), color: theme.search.placeholderColor, font: .normal(.text))) + let countLayout = TextViewLayout(.initialize(string: tr(L10n.chatSearchCount(countValue.current, countValue.total)), color: theme.search.placeholderColor, font: .normal(.text))) countLayout.measure(width: .greatestFiniteMagnitude) countView.update(countLayout) - super.updateLocalizationAndTheme() + super.updateLocalizationAndTheme(theme: theme) } override func cancelSearch() { @@ -180,7 +164,7 @@ class ChatSearchView: SearchView { tokenView.removeFromSuperview() } } - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) self.needsLayout = true } } diff --git a/Telegram-Mac/ChatSelectText.swift b/Telegram-Mac/ChatSelectText.swift index 6beeb5d5ef..eed6d37123 100644 --- a/Telegram-Mac/ChatSelectText.swift +++ b/Telegram-Mac/ChatSelectText.swift @@ -8,80 +8,201 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit struct SelectContainer { - let text:String + let text:NSAttributedString let range:NSRange let header:String? } class SelectManager : NSResponder { + fileprivate weak var chatInteraction: ChatInteraction? + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - private var ranges:[(AnyHashable,WeakReference, SelectContainer)] = [] + private var ranges:Atomic<[(AnyHashable,WeakReference, SelectContainer)]> = Atomic(value: []) - func add(range:NSRange, textView: TextView, text: String, header: String?, stableId: AnyHashable) { - ranges.append((stableId, WeakReference(value: textView), SelectContainer(text: text, range: range, header: header))) + func add(range:NSRange, textView: TextView, text: NSAttributedString, header: String?, stableId: AnyHashable) { + _ = ranges.modify { ranges in + var ranges = ranges + ranges.append((stableId, WeakReference(value: textView), SelectContainer(text: text, range: range, header: header))) + return ranges + } } func removeAll() { - for selection in ranges { - if let value = selection.1.value { - value.layout?.clearSelect() - value.canBeResponder = true - value.setNeedsDisplay() + _ = ranges.modify { ranges in + for selection in ranges { + if let value = selection.1.value { + value.layout?.clearSelect() + value.canBeResponder = true + value.setNeedsDisplay() + } } + return [] } - ranges.removeAll() } func remove(for id:Int64) { } var isEmpty:Bool { - return ranges.isEmpty + return ranges.with { $0.isEmpty } } - @objc func copy(_ sender:Any) { - - var string:String = "" - - for i in stride(from: ranges.count - 1, to: -1, by: -1) { - let container = ranges[i].2 - if let header = container.header, ranges.count > 1 { - string += header + "\n" - } - - if container.range.location != NSNotFound { - if container.range.location != 0, ranges.count > 1 { - string += "..." + var selectedText: NSAttributedString { + let string:NSMutableAttributedString = NSMutableAttributedString() + _ = ranges.with { ranges in + for i in stride(from: ranges.count - 1, to: -1, by: -1) { + let container = ranges[i].2 + if let header = container.header, ranges.count > 1 { + _ = string.append(string: header + "\n", color: nil, font: .normal(.text)) + } + + if container.range.location != NSNotFound { + if container.range.location != 0, ranges.count > 1 { + _ = string.append(string: "...", color: nil, font: .normal(.text)) + } + string.append(container.text.attributedSubstring(from: container.range)) + if container.range.location + container.range.length != container.text.length, ranges.count > 1 { + _ = string.append(string: "...", color: nil, font: .normal(.text)) + } } - string += container.text.nsstring.substring(with: container.range) - if container.range.location + container.range.length != container.text.length, ranges.count > 1 { - string += "..." + + if i != 0 { + _ = string.append(string: "\n\n", color: nil, font: .normal(.text)) } } - - if i != 0 { - string += "\n\n" + } + return string + } + + @objc func copy(_ sender:Any) { + let selectedText = self.selectedText + if !selectedText.string.isEmpty { + if !globalLinkExecutor.copyAttributedString(selectedText) { + NSPasteboard.general.declareTypes([.string], owner: self) + NSPasteboard.general.setString(selectedText.string, forType: .string) + } + } else if let chatInteraction = self.chatInteraction { + if let selectionState = chatInteraction.presentation.selectionState { + _ = chatInteraction.context.account.postbox.messagesAtIds(Array(selectionState.selectedIds.sorted(by: <))).start(next: { messages in + var text: String = "" + for message in messages { + if !text.isEmpty { + text += "\n\n" + } + if let forwardInfo = message.forwardInfo { + text += "> " + forwardInfo.authorTitle + ":" + } else { + text += "> " + (message.effectiveAuthor?.displayTitle ?? "") + ":" + } + text += "\n" + text += pullText(from: message) as String + } + copyToClipboard(text) + }) } } - let pb = NSPasteboard.general - pb.declareTypes([.string], owner: self) - pb.setString(string, forType: .string) - + } + + func selectNextChar() -> Bool { + var result: Bool = false + _ = ranges.modify { ranges in + var ranges = ranges + if let last = ranges.last, let textView = last.1.value { + if last.2.range.max < last.2.text.length, let layout = textView.layout { + + var range = last.2.range + + switch layout.selectedRange.cursorAlignment { + case let .min(cursorAlignment), let .max(cursorAlignment): + if range.min >= cursorAlignment { + range.length += 1 + } else { + range.location += 1 + if range.length > 1 { + range.length -= 1 + } + } + } + let location = min(max(0, range.location), last.2.text.length) + let length = max(min(range.length, last.2.text.length - location), 0) + range = NSMakeRange(location, length) + + layout.selectedRange.range = range + ranges[ranges.count - 1] = (last.0, last.1, SelectContainer(text: last.2.text, range: range, header: last.2.header)) + textView.needsDisplay = true + result = true + return ranges + } + } + result = false + return ranges + } + return result + } + + func selectPrevChar() -> Bool { + var result: Bool = false + _ = ranges.modify { ranges in + var ranges = ranges + if let first = ranges.first, let textView = first.1.value { + if let layout = textView.layout { + + var range = first.2.range + + switch layout.selectedRange.cursorAlignment { + case let .min(cursorAlignment), let .max(cursorAlignment): + if range.location >= cursorAlignment { + if range.length > 1 { + range.length -= 1 + } else { + range.location -= 1 + } + } else { + if range.location > 0 { + range.location -= 1 + range.length += 1 + } + } + } + + let location = min(max(0, range.location), first.2.text.length) + let length = max(min(range.length, first.2.text.length - location), 0) + range = NSMakeRange(location, length) + layout.selectedRange.range = range + ranges[0] = (first.0, first.1, SelectContainer(text: first.2.text, range: range, header: first.2.header)) + textView.needsDisplay = true + result = true + return ranges + } + } + result = false + return ranges + } + return result } func find(_ stableId:AnyHashable) -> NSRange? { - for range in ranges { - if range.0 == stableId { - return range.2.range + return ranges.with { ranges -> NSRange? in + for range in ranges { + if range.0 == stableId { + return range.2.range + } } + return nil } - return nil } override func becomeFirstResponder() -> Bool { @@ -94,10 +215,11 @@ class SelectManager : NSResponder { } } -let selectManager:SelectManager = { - let manager = SelectManager() - return manager -}() +let selectManager:SelectManager = SelectManager() + +func initializeSelectManager() { + _ = selectManager.isEmpty +} protocol MultipleSelectable { var selectableTextViews:[TextView] { get } @@ -114,16 +236,24 @@ class ChatSelectText : NSObject { private var startMessageId:MessageId? = nil private var lastPressureEventStage = 0 private var inPressedState = false + private var locationInWindow: NSPoint? = nil + + private var lastSelectdMessageId: MessageId? init(_ table:TableView) { self.table = table } - + deinit { + var bp:Int = 0 + bp += 1 + } func initializeHandlers(for window:Window, chatInteraction:ChatInteraction) { - table.addScroll(listener: TableScrollListener ({ [weak table] _ in + selectManager.chatInteraction = chatInteraction + + table.addScroll(listener: TableScrollListener (dispatchWhenVisibleRangeUpdated: false, { [weak table] _ in table?.enumerateVisibleViews(with: { view in view.updateMouse() }) @@ -135,108 +265,163 @@ class ChatSelectText : NSObject { view.updateMouse() }) - return .invokeNext + return .rejected }, with: self, for: .mouseMoved, priority:.medium) window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in self?.started = false self?.inPressedState = false + self?.locationInWindow = event.locationInWindow if let table = self?.table, let superview = table.superview, let documentView = table.documentView { - let point = superview.convert(window.mouseLocationOutsideOfEventStream, from: nil) - let documentPoint = documentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + let point = superview.convert(event.locationInWindow, from: nil) + let documentPoint = documentView.convert(event.locationInWindow, from: nil) let row = table.row(at: documentPoint) - if !NSPointInRect(point, table.frame) { - self?.beginInnerLocation = NSZeroPoint + + var isCurrentTableView: (NSView?)->Bool = { _ in return false} + + isCurrentTableView = { [weak table] view in + if view === table { + return true + } else if let superview = view?.superview { + if superview is TableView, view is TableRowView || view is NSClipView { + return isCurrentTableView(superview) + } else if superview is TableView { + return false + } else { + return isCurrentTableView(superview) + } + } else { + return false + } + } + + if row < 0 || (!NSPointInRect(point, table.frame) || hasModals() || (!table.item(at: row).canMultiselectTextIn(event.locationInWindow) && chatInteraction.presentation.state != .selecting)) || !isCurrentTableView(window.contentView?.hitTest(event.locationInWindow)) { self?.beginInnerLocation = NSZeroPoint } else { self?.beginInnerLocation = documentPoint } - Queue.mainQueue().justDispatch { [weak self] in - if chatInteraction.presentation.state == .selecting { - if let beginInnerLocation = self?.beginInnerLocation, let selectionState = chatInteraction.presentation.selectionState { - let row = table.row(at: beginInnerLocation) - if row != -1, let item = table.item(at: row) as? ChatRowItem, let message = item.message { - if self?.startMessageId == nil { - self?.startMessageId = message.id - } - self?.deselect = !selectionState.selectedIds.contains(message.id) - } - } - } else { - if let view = table.viewNecessary(at: row) as? ChatRowView, !view.canStartTextSelecting(event) { - self?.beginInnerLocation = NSZeroPoint + + + if row != -1, let item = table.item(at: row) as? ChatRowItem, let view = item.view as? ChatRowView { + if chatInteraction.presentation.state == .selecting || (theme.bubbled && !NSPointInRect(view.convert(window.mouseLocationOutsideOfEventStream, from: nil), view.bubbleFrame)) { + if self?.startMessageId == nil { + self?.startMessageId = item.message?.id } - self?.startMessageId = nil + self?.deselect = !view.isSelectInGroup(window.mouseLocationOutsideOfEventStream) } - self?.started = self?.beginInnerLocation != NSZeroPoint - } + + self?.started = self?.beginInnerLocation != NSZeroPoint } return .invokeNext - }, with: self, for: .leftMouseDown, priority:.medium) + }, with: self, for: .leftMouseDown, priority:.medium) - window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + self?.beginInnerLocation = NSZeroPoint + self?.locationInWindow = nil - let point = self?.table.documentView?.convert(window.mouseLocationOutsideOfEventStream, from: nil) ?? NSZeroPoint - if let index = self?.table.row(at: point), index > 0, let item = self?.table.item(at: index) as? ChatRowItem { + Queue.mainQueue().justDispatch { + guard let table = self?.table else {return} + guard let documentView = table.documentView else {return} - if item.message?.id == self?.startMessageId { - if let result = item.chatInteraction.presentation.selectionState?.selectedIds.isEmpty, result { - self?.startMessageId = nil - item.chatInteraction.update({$0.withoutSelectionState()}) + var cleanStartId: Bool = false + let documentPoint = documentView.convert(event.locationInWindow, from: nil) + let row = table.row(at: documentPoint) + if chatInteraction.presentation.state != .selecting { + if let view = table.viewNecessary(at: row) as? ChatRowView, !view.canStartTextSelecting(event) { + self?.beginInnerLocation = NSZeroPoint } + cleanStartId = true } + + let point = self?.table.documentView?.convert(event.locationInWindow, from: nil) ?? NSZeroPoint + if let index = self?.table.row(at: point), index > 0, let item = self?.table.item(at: index), let view = item.view as? ChatRowView { + + if event.clickCount > 1, selectManager.isEmpty { + _ = window.makeFirstResponder(view.selectableTextViews.first) + } + + if view.canDropSelection(in: event.locationInWindow) { + if let result = chatInteraction.presentation.selectionState?.selectedIds.isEmpty, result { + self?.startMessageId = nil + chatInteraction.update({$0.withoutSelectionState()}) + } + } + } else { + if let result = chatInteraction.presentation.selectionState?.selectedIds.isEmpty, result { + self?.startMessageId = nil + chatInteraction.update({$0.withoutSelectionState()}) + } + } + if cleanStartId { + self?.startMessageId = nil + } } - - return .invokeNext - }, with: self, for: .leftMouseUp, priority:.medium) + }, with: self, for: .leftMouseUp, priority:.medium) window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in - self?.endInnerLocation = self?.table.documentView?.convert(window.mouseLocationOutsideOfEventStream, from: nil) ?? NSZeroPoint - if let overView = window.contentView?.hitTest(window.mouseLocationOutsideOfEventStream) as? Control { - if overView.userInteractionEnabled == false { - self?.started = false - } + guard let `self` = self else {return .rejected} + +// if let locationInWindow = self.locationInWindow { +// let old = (ceil(locationInWindow.x), ceil(locationInWindow.y)) +// let new = (ceil(event.locationInWindow.x), round(event.locationInWindow.y)) +// if abs(old.0 - new.0) <= 1 && abs(old.1 - new.1) <= 1 { +// return .rejected +// } +// } + + self.endInnerLocation = self.table.documentView?.convert(window.mouseLocationOutsideOfEventStream, from: nil) ?? NSZeroPoint + +// if let overView = window.contentView?.hitTest(window.mouseLocationOutsideOfEventStream) as? Control { +// self?.started = overView.userInteractionEnabled == true +// } + if self.started { + self.started = !hasPopover(window) && self.beginInnerLocation != NSZeroPoint } - if self?.started == true { - self?.started = !hasPopover(window) + if event.clickCount > 1 { + self.started = false } - if self?.started == true { - - self?.table.clipView.autoscroll(with: event) - + // NSLog("\(!NSPointInRect(event.locationInWindow, window.bounds))") + + if self.started { + self.table.clipView.autoscroll(with: event) if chatInteraction.presentation.state != .selecting { - if window.firstResponder != selectManager { - window.makeFirstResponder(selectManager) - } - if self?.inPressedState == false { - self?.runSelector(window: window, chatInteraction: chatInteraction) + if !self.inPressedState { + self.runSelector(window: window, chatInteraction: chatInteraction) + if window.firstResponder != selectManager { + _ = window.makeFirstResponder(selectManager) + } } return .invoked } else if chatInteraction.presentation.state == .selecting { - self?.runSelector(false, window: window, chatInteraction:chatInteraction) - return .invoked + self.runSelector(false, window: window, chatInteraction: chatInteraction) + return .invokeNext } } return .invokeNext - }, with: self, for: .leftMouseDragged, priority:.medium) + }, with: self, for: .leftMouseDragged, priority:.medium) window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in - guard let `self` = self else { return .invokeNext } + guard let `self` = self else { return .rejected } if event.stage == 2 && self.lastPressureEventStage < 2 { self.inPressedState = true } self.lastPressureEventStage = event.stage - return .invokeNext + return .rejected }, with: self, for: .pressure, priority: .medium) + + window.set(handler: { () -> KeyHandlerResult in + + return .rejected + }, with: self, for: .A, priority: .medium, modifierFlags: [.command]) } private func runSelector(_ selectingText:Bool = true, window: Window, chatInteraction:ChatInteraction) { @@ -245,6 +430,7 @@ class ChatSelectText : NSObject { var startIndex = table.row(at: beginInnerLocation) var endIndex = table.row(at: endInnerLocation) + let reversed = endIndex < startIndex; if(endIndex < startIndex) { @@ -253,10 +439,26 @@ class ChatSelectText : NSObject { startIndex = startIndex - endIndex; } - if startIndex < 0 && endIndex < 0 { + if startIndex < 0 || endIndex < 0 { return } + let beginRow = table.row(at: beginInnerLocation) + if let view = table.item(at: beginRow).view as? ChatRowView, selectingText, table._mouseInside() { + let rowPoint = view.convert(beginInnerLocation, from: table.documentView) + if (!NSPointInRect(rowPoint, view.bubbleFrame) && theme.bubbled) { + if startIndex != endIndex { + for i in max(0,startIndex) ... min(endIndex,table.count - 1) { + let item = table.item(at: i) as? ChatRowItem + if let view = item?.view as? ChatRowView { + view.toggleSelected(deselect, in: window.mouseLocationOutsideOfEventStream) + } + } + } + return + } + } + if selectingText { selectManager.removeAll() @@ -309,7 +511,8 @@ class ChatSelectText : NSObject { selectableView.canBeResponder = false layout.selectedRange.range = layout.selectedRange(startPoint:startPoint, currentPoint:endPoint) - selectManager.add(range: layout.selectedRange.range, textView: selectableView, text:layout.attributedString.string, header: view?.header, stableId: table.item(at: i).stableId) + layout.selectedRange.cursorAlignment = startPoint.x > endPoint.x ? .min(layout.selectedRange.range.max) : .max(layout.selectedRange.range.min) + selectManager.add(range: layout.selectedRange.range, textView: selectableView, text:layout.attributedString, header: view?.header, stableId: table.item(at: i).stableId) selectableView.setNeedsDisplay() @@ -319,23 +522,13 @@ class ChatSelectText : NSObject { } } else { - if let selectionState = chatInteraction.presentation.selectionState { - - var ids:Set = selectionState.selectedIds + if chatInteraction.presentation.state == .selecting { for i in max(0,startIndex) ... min(endIndex,table.count - 1) { - if let item = table.item(at: i) as? ChatRowItem, let message = item.message { - - if deselect { - ids.remove(message.id) - } else { - ids.insert(message.id) - } + let item = table.item(at: i) as? ChatRowItem + if let view = item?.view as? ChatRowView { + view.toggleSelected(deselect, in: window.mouseLocationOutsideOfEventStream) } - - } - chatInteraction.update({$0.withUpdatedSelectedMessages(ids)}) - } } diff --git a/Telegram-Mac/ChatServiceItem.swift b/Telegram-Mac/ChatServiceItem.swift index a047594a3e..bcb850facf 100644 --- a/Telegram-Mac/ChatServiceItem.swift +++ b/Telegram-Mac/ChatServiceItem.swift @@ -8,26 +8,53 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class ChatServiceItem: ChatRowItem { + + static var photoSize = NSMakeSize(200, 200) let text:TextViewLayout private(set) var imageArguments:TransformImageArguments? private(set) var image:TelegramMediaImage? - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account:Account, _ entry: ChatHistoryEntry) { + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ entry: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { let message:Message = entry.message! + + + + let linkColor: NSColor = theme.controllerBackgroundMode.hasWallpapaer ? theme.chatServiceItemTextColor : entry.renderType == .bubble ? theme.chat.linkColor(true, entry.renderType == .bubble) : theme.colors.link + let grayTextColor: NSColor = theme.chatServiceItemTextColor let authorId:PeerId? = message.author?.id var authorName:String = "" if let displayTitle = message.author?.displayTitle { authorName = displayTitle - if account.peerId == message.author?.id { - authorName = tr(.chatServiceYou) + } + + let isIncoming: Bool = message.isIncoming(context.account, entry.renderType == .bubble) + + + let nameColor:(PeerId) -> NSColor = { peerId in + + if theme.controllerBackgroundMode.hasWallpapaer { + return theme.chatServiceItemTextColor + } + + if messageMainPeer(message) is TelegramChannel || messageMainPeer(message) is TelegramGroup { + if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { + return theme.chat.linkColor(isIncoming, entry.renderType == .bubble) + } else if context.peerId != peerId { + let value = abs(Int(peerId.id) % 7) + return theme.chat.peerName(value) + } } + return theme.chat.linkColor(isIncoming, false) } + + let attributedString:NSMutableAttributedString = NSMutableAttributedString() if let media = message.media[0] as? TelegramMediaAction { @@ -36,32 +63,30 @@ class ChatServiceItem: ChatRowItem { switch media.action { case let .groupCreated(title: title): if !peer.isChannel { - let _ = attributedString.append(string: tr(.chatServiceGroupCreated(authorName, title)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: L10n.chatServiceGroupCreated(authorName, title), color: grayTextColor, font: .normal(theme.fontSize)) if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) } } else { - let _ = attributedString.append(string: tr(.chatServiceChannelCreated), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceChannelCreated), color: grayTextColor, font: .normal(theme.fontSize)) } case let .addedMembers(peerIds): if peerIds.first == authorId { - let _ = attributedString.append(string: tr(.chatServiceGroupAddedSelf(authorName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupAddedSelf(authorName)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } else { - let _ = attributedString.append(string: tr(.chatServiceGroupAddedMembers(authorName, "")), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupAddedMembers(authorName, "")), color: grayTextColor, font: NSFont.normal(theme.fontSize)) for peerId in peerIds { if let peer = message.peers[peerId] { - let range = attributedString.append(string: peer.displayTitle, color: theme.colors.link, font: .medium(.custom(theme.fontSize))) - attributedString.add(link:inAppLink.peerInfo(peerId:peerId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) + let range = attributedString.append(string: peer.displayTitle, color: nameColor(peer.id), font: .medium(theme.fontSize)) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:peerId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(peerId)) if peerId != peerIds.last { - _ = attributedString.append(string: ", ", color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + _ = attributedString.append(string: ", ", color: grayTextColor, font: .normal(theme.fontSize)) } } @@ -69,25 +94,22 @@ class ChatServiceItem: ChatRowItem { } if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) - + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) } case let .removedMembers(peerIds): if peerIds.first == message.author?.id { - let _ = attributedString.append(string: tr(.chatServiceGroupRemovedSelf(authorName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupRemovedSelf(authorName)), color: grayTextColor, font: .normal(theme.fontSize)) } else { - let _ = attributedString.append(string: tr(.chatServiceGroupRemovedMembers(authorName, "")), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupRemovedMembers(authorName, "")), color: grayTextColor, font: .normal(theme.fontSize)) for peerId in peerIds { if let peer = message.peers[peerId] { - let range = attributedString.append(string: peer.displayTitle, color: theme.colors.link, font: .medium(.custom(theme.fontSize))) - attributedString.add(link:inAppLink.peerInfo(peerId:peerId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) + let range = attributedString.append(string: peer.displayTitle, color: nameColor(peerId), font: .medium(theme.fontSize)) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:peerId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(peer.id)) if peerId != peerIds.last { - _ = attributedString.append(string: ", ", color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + _ = attributedString.append(string: ", ", color: grayTextColor, font: .normal(theme.fontSize)) } } @@ -95,120 +117,136 @@ class ChatServiceItem: ChatRowItem { } if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) - + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.medium(theme.fontSize), range: range) } case let .photoUpdated(image): - if let _ = image { - let _ = attributedString.append(string: peer.isChannel ? tr(.chatServiceChannelUpdatedPhoto) : tr(.chatServiceGroupUpdatedPhoto(authorName)), color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) - let size = NSMakeSize(70, 70) - imageArguments = TransformImageArguments(corners: ImageCorners(radius: size.width / 2), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + if let image = image { + + let text: String + if image.videoRepresentations.isEmpty { + text = peer.isChannel ? L10n.chatServiceChannelUpdatedPhoto : L10n.chatServiceGroupUpdatedPhoto(authorName) + } else { + text = peer.isChannel ? L10n.chatServiceChannelUpdatedVideo : L10n.chatServiceGroupUpdatedVideo(authorName) + } + + let _ = attributedString.append(string: text, color: grayTextColor, font: .normal(theme.fontSize)) + let size = ChatServiceItem.photoSize + imageArguments = TransformImageArguments(corners: ImageCorners(radius: 10), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) } else { - let _ = attributedString.append(string: peer.isChannel ? tr(.chatServiceChannelRemovedPhoto) : tr(.chatServiceGroupRemovedPhoto(authorName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: peer.isChannel ? L10n.chatServiceChannelRemovedPhoto : L10n.chatServiceGroupRemovedPhoto(authorName), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) - + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.medium(theme.fontSize), range: range) } self.image = image case let .titleUpdated(title): - let _ = attributedString.append(string: peer.isChannel ? tr(.chatServiceChannelUpdatedTitle(title)) : tr(.chatServiceGroupUpdatedTitle(authorName, title)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: peer.isChannel ? tr(L10n.chatServiceChannelUpdatedTitle(title)) : tr(L10n.chatServiceGroupUpdatedTitle(authorName, title)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + if let authorId = authorId { + + let range = attributedString.string.nsstring.range(of: authorName) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) + } + case .customText(let text, _): + let _ = attributedString.append(string: text, color: grayTextColor, font: NSFont.normal(theme.fontSize)) case .pinnedMessageUpdated: var replyMessageText = "" + var pinnedId: MessageId? for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] { replyMessageText = pullText(from: message) as String + pinnedId = attribute.messageId } } - var cutted = replyMessageText.prefix(30) - if cutted.length != replyMessageText.length { - cutted += "..." + let cutted = replyMessageText.prefixWithDots(30) + _ = attributedString.append(string: tr(L10n.chatServiceGroupUpdatedPinnedMessage(authorName, cutted)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + let pinnedRange = attributedString.string.nsstring.range(of: cutted) + if pinnedRange.location != NSNotFound { + attributedString.add(link: inAppLink.callback("", { [weak chatInteraction] _ in + if let pinnedId = pinnedId { + chatInteraction?.focusMessageId(nil, pinnedId, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + }), for: pinnedRange, color: grayTextColor) + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.medium(theme.fontSize), range: pinnedRange) } - let _ = attributedString.append(string: tr(.chatServiceGroupUpdatedPinnedMessage(authorName, cutted)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + + + if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.medium(theme.fontSize), range: range) + } case .joinedByLink: - let _ = attributedString.append(string: tr(.chatServiceGroupJoinedByLink(authorName)), color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupJoinedByLink(authorName)), color: grayTextColor, font: .normal(theme.fontSize)) if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) - + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) } case .channelMigratedFromGroup, .groupMigratedToChannel: - let _ = attributedString.append(string: tr(.chatServiceGroupMigratedToSupergroup), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupMigratedToSupergroup), color: grayTextColor, font: NSFont.normal(theme.fontSize)) case let .messageAutoremoveTimeoutUpdated(seconds): if let authorId = authorId { - if authorId == account.peerId { + if authorId == context.peerId { if seconds > 0 { - let _ = attributedString.append(string: tr(.chatServiceSecretChatSetTimerSelf(autoremoveLocalized(Int(seconds)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceSecretChatSetTimerSelf(autoremoveLocalized(Int(seconds)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } else { - let _ = attributedString.append(string: tr(.chatServiceSecretChatDisabledTimerSelf), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceSecretChatDisabledTimerSelf), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } } else { if seconds > 0 { - let _ = attributedString.append(string: tr(.chatServiceSecretChatSetTimer(authorName, autoremoveLocalized(Int(seconds)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceSecretChatSetTimer(authorName, autoremoveLocalized(Int(seconds)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } else { - let _ = attributedString.append(string: tr(.chatServiceSecretChatDisabledTimer(authorName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceSecretChatDisabledTimer(authorName)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } let range = attributedString.string.nsstring.range(of: authorName) - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.medium(theme.fontSize), range: range) } } case .historyScreenshot: - let _ = attributedString.append(string: tr(.chatServiceGroupTookScreenshot(authorName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + let _ = attributedString.append(string: tr(L10n.chatServiceGroupTookScreenshot(authorName)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) if let authorId = authorId { let range = attributedString.string.nsstring.range(of: authorName) - if account.peerId != authorId { - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) - } - attributedString.addAttribute(NSAttributedStringKey.font, value: NSFont.medium(.custom(theme.fontSize)), range: range) - + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) } - case let .phoneCall(callId: _, discardReason: reason, duration: duration): + case let .phoneCall(callId: _, discardReason: reason, duration: duration, _): if let reason = reason { switch reason { case .busy: - _ = attributedString.append(string: tr(.chatListServiceCallCancelled), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceCallCancelled), color: grayTextColor, font: NSFont.normal(theme.fontSize)) case .disconnect: - _ = attributedString.append(string: tr(.chatListServiceCallDisconnected), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceCallMissed), color: grayTextColor, font: NSFont.normal(theme.fontSize)) case .hangup: if let duration = duration { - if message.author?.id == account.peerId { - _ = attributedString.append(string: tr(.chatListServiceCallOutgoing(.durationTransformed(elapsed: Int(duration)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + if message.author?.id == context.peerId { + _ = attributedString.append(string: tr(L10n.chatListServiceCallOutgoing(.durationTransformed(elapsed: Int(duration)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } else { - _ = attributedString.append(string: tr(.chatListServiceCallIncoming(.durationTransformed(elapsed: Int(duration)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceCallIncoming(.durationTransformed(elapsed: Int(duration)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } } case .missed: - _ = attributedString.append(string: tr(.chatListServiceCallMissed), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceCallMissed), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } } else if let duration = duration { - if authorId == account.peerId { - _ = attributedString.append(string: tr(.chatListServiceCallOutgoing(.durationTransformed(elapsed: Int(duration)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + if authorId == context.peerId { + _ = attributedString.append(string: tr(L10n.chatListServiceCallOutgoing(.durationTransformed(elapsed: Int(duration)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } else { - _ = attributedString.append(string: tr(.chatListServiceCallIncoming(.durationTransformed(elapsed: Int(duration)))), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceCallIncoming(.durationTransformed(elapsed: Int(duration)))), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } } case let .gameScore(gameId: _, score: score): @@ -222,15 +260,12 @@ class ChatServiceItem: ChatRowItem { } } - if authorId == account.peerId { - _ = attributedString.append(string: authorName, color: theme.colors.grayText, font: NSFont.medium(.custom(theme.fontSize))) - _ = attributedString.append(string: " ") - } else if let authorId = authorId { - let range = attributedString.append(string: authorName, color: theme.colors.link, font: NSFont.medium(.custom(theme.fontSize))) - attributedString.add(link:inAppLink.peerInfo(peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range) + if let authorId = authorId { + let range = attributedString.append(string: authorName, color: linkColor, font: NSFont.medium(theme.fontSize)) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) _ = attributedString.append(string: " ") } - _ = attributedString.append(string: tr(.chatListServiceGameScored(Int(score), gameName)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatListServiceGameScored1Countable(Int(score), gameName)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) case let .paymentSent(currency, totalAmount): var paymentMessage:Message? for attr in message.attributes { @@ -242,13 +277,25 @@ class ChatServiceItem: ChatRowItem { } if let message = paymentMessage, let media = message.media.first as? TelegramMediaInvoice, let peer = messageMainPeer(message) { - _ = attributedString.append(string: tr(.chatServicePaymentSent(TGCurrencyFormatter.shared().formatAmount(totalAmount, currency: currency), peer.displayTitle, media.title)), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) - attributedString.detectBoldColorInString(with: NSFont.medium(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatServicePaymentSent(TGCurrencyFormatter.shared().formatAmount(totalAmount, currency: currency), peer.displayTitle, media.title)), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + attributedString.detectBoldColorInString(with: .medium(theme.fontSize)) } else { - _ = attributedString.append(string: tr(.chatServicePaymentSent("", "", "")), color: theme.colors.grayText, font: NSFont.normal(.custom(theme.fontSize))) + _ = attributedString.append(string: tr(L10n.chatServicePaymentSent("", "", "")), color: grayTextColor, font: NSFont.normal(theme.fontSize)) } - default: + case let .botDomainAccessGranted(domain): + _ = attributedString.append(string: L10n.chatServiceBotPermissionAllowed(domain), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + case let .botSentSecureValues(types): + let permissions = types.map({$0.rawValue}).joined(separator: ", ") + _ = attributedString.append(string: L10n.chatServiceSecureIdAccessGranted(peer.displayTitle, permissions), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + case .peerJoined: + let _ = attributedString.append(string: L10n.chatServicePeerJoinedTelegram(authorName), color: grayTextColor, font: NSFont.normal(theme.fontSize)) + if let authorId = authorId { + let range = attributedString.string.nsstring.range(of: authorName) + attributedString.add(link:inAppLink.peerInfo(link: "", peerId:authorId, action:nil, openChat: false, postId: nil, callback: chatInteraction.openInfo), for: range, color: nameColor(authorId)) + attributedString.addAttribute(.font, value: NSFont.medium(theme.fontSize), range: range) + } + default: break } } @@ -256,45 +303,52 @@ class ChatServiceItem: ChatRowItem { let text:String switch media.data { case .image: - text = tr(.serviceMessageExpiredPhoto) + text = tr(L10n.serviceMessageExpiredPhoto) case .file: if message.id.peerId.namespace == Namespaces.Peer.SecretChat { - text = tr(.serviceMessageExpiredFile) + text = tr(L10n.serviceMessageExpiredVideo) } else { - text = tr(.serviceMessageExpiredVideo) + text = tr(L10n.serviceMessageExpiredVideo) } } - _ = attributedString.append(string: text, color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + _ = attributedString.append(string: text, color: grayTextColor, font: .normal(theme.fontSize)) } else if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let _ = message.autoremoveAttribute { let isPhoto: Bool = message.media.first is TelegramMediaImage - if authorId == account.peerId { - _ = attributedString.append(string: isPhoto ? tr(.serviceMessageDesturctingPhotoYou(authorName)) : tr(.serviceMessageDesturctingVideoYou(authorName)), color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + if authorId == context.peerId { + _ = attributedString.append(string: isPhoto ? tr(L10n.serviceMessageDesturctingPhotoYou(authorName)) : tr(L10n.serviceMessageDesturctingVideoYou(authorName)), color: grayTextColor, font: .normal(theme.fontSize)) } else if let _ = authorId { - _ = attributedString.append(string: isPhoto ? tr(.serviceMessageDesturctingPhoto(authorName)) : tr(.serviceMessageDesturctingVideo(authorName)), color: theme.colors.grayText, font: .normal(.custom(theme.fontSize))) + _ = attributedString.append(string: isPhoto ? tr(L10n.serviceMessageDesturctingPhoto(authorName)) : tr(L10n.serviceMessageDesturctingVideo(authorName)), color: grayTextColor, font: .normal(theme.fontSize)) } } text = TextViewLayout(attributedString, truncationType: .end, cutout: nil, alignment: .center) + text.mayItems = false text.interactions = globalLinkExecutor - super.init(initialSize, chatInteraction, entry) - self.account = account + super.init(initialSize, chatInteraction, entry, downloadSettings, theme: theme) } override func makeContentSize(_ width: CGFloat) -> NSSize { return NSZeroSize } + override var isBubbled: Bool { + return presentation.wallpaper.wallpaper != .none + } + override var height: CGFloat { - var height:CGFloat = text.layoutSize.height + 12 + var height:CGFloat = text.layoutSize.height + (isBubbled ? 0 : 12) if let imageArguments = imageArguments { - height += imageArguments.imageSize.height + 6 + height += imageArguments.imageSize.height + (isBubbled ? 9 : 6) } return height } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { text.measure(width: width - 40) + if isBubbled { + text.generateAutoBlock(backgroundColor: theme.chatServiceItemColor) + } return true } @@ -302,20 +356,20 @@ class ChatServiceItem: ChatRowItem { return ChatServiceRowView.self } - override func menuItems() -> Signal<[ContextMenuItem], Void> { + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { var items:[ContextMenuItem] = [] let chatInteraction = self.chatInteraction if chatInteraction.presentation.state != .selecting { if let message = message, let peer = messageMainPeer(message) { - if peer.canSendMessage, !message.containsSecretMedia { - items.append(ContextMenuItem(tr(.messageContextReply), handler: { + if peer.canSendMessage, !message.containsSecretMedia, canReplyMessage(message, peerId: peer.id) { + items.append(ContextMenuItem(tr(L10n.messageContextReply1), handler: { chatInteraction.setupReplyMessage(message.id) })) } - if canDeleteMessage(message, account: account) { - items.append(ContextMenuItem(tr(.messageContextDelete), handler: { + if canDeleteMessage(message, account: context.account) { + items.append(ContextMenuItem(tr(L10n.messageContextDelete), handler: { chatInteraction.deleteMessages([message.id]) })) } @@ -326,3 +380,189 @@ class ChatServiceItem: ChatRowItem { } } + +class ChatServiceRowView: TableRowView { + + private var textView:TextView + private var imageView:TransformImageView? + + private var photoVideoView: MediaPlayerView? + private var photoVideoPlayer: MediaPlayer? + + required init(frame frameRect: NSRect) { + textView = TextView() + textView.isSelectable = false + //textView.userInteractionEnabled = false + //do not enable + // textView.isEventLess = true + super.init(frame: frameRect) + //layerContentsRedrawPolicy = .onSetNeedsDisplay + addSubview(textView) + } + + override var backdorColor: NSColor { + if let item = item as? ChatServiceItem { + return item.isBubbled ? .clear : theme.chatBackground + } else { + return .clear + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + + if let item = item as? ChatServiceItem { + textView.update(item.text) + textView.centerX(y:6) + if let imageArguments = item.imageArguments { + imageView?.setFrameSize(imageArguments.imageSize) + imageView?.centerX(y:textView.frame.maxY + (item.isBubbled ? 0 : 6)) + self.imageView?.set(arguments: imageArguments) + self.photoVideoView?.centerX(y:textView.frame.maxY + (item.isBubbled ? 0 : 6)) + } + + } + } + + + override func doubleClick(in location: NSPoint) { + if let item = self.item as? ChatRowItem, item.chatInteraction.presentation.state == .normal { + if self.hitTest(location) == nil || self.hitTest(location) == self { + item.chatInteraction.setupReplyMessage(item.message?.id) + } + } + } + + + @objc func updatePlayerIfNeeded() { + let accept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) && !self.isDynamicContentLocked + if let photoVideoPlayer = photoVideoPlayer { + if accept { + photoVideoPlayer.play() + } else { + photoVideoPlayer.pause() + } + } + } + + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + photoVideoPlayer?.seek(timestamp: 0) + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: item?.table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: item?.table?.view) + } else { + removeNotificationListeners() + } + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + deinit { + removeNotificationListeners() + } + + + override func mouseUp(with event: NSEvent) { + if let imageView = imageView, imageView._mouseInside() { + if let item = self.item as? ChatServiceItem { + showPhotosGallery(context: item.context, peerId: item.chatInteraction.peerId, firstStableId: item.stableId, item.table, nil) + } + } else { + super.mouseUp(with: event) + } + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool) -> NSView { + return imageView ?? self + } + + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated:animated) + textView.disableBackgroundDrawing = true + + if let item = item as? ChatServiceItem, let arguments = item.imageArguments { + + if let image = item.image { + if imageView == nil { + self.imageView = TransformImageView() + self.addSubview(imageView!) + } + imageView?.setSignal(signal: cachedMedia(media: image, arguments: arguments, scale: backingScaleFactor)) + imageView?.setSignal( chatMessagePhoto(account: item.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message!), media: image), toRepresentationSize:NSMakeSize(100,100), scale: backingScaleFactor, autoFetchFullSize: true), cacheImage: { [weak image] result in + if let media = image { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale, positionFlags: nil) + } + }) + + + imageView?.set(arguments: arguments) + + + if let video = image.videoRepresentations.last { + if self.photoVideoView == nil { + self.photoVideoView = MediaPlayerView() + self.photoVideoView!.layer?.cornerRadius = 10 + self.addSubview(self.photoVideoView!) + self.photoVideoView!.isEventLess = true + } + self.photoVideoView!.frame = NSMakeRect(0, 0, ChatServiceItem.photoSize.width, ChatServiceItem.photoSize.height) + + let file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: video.resource.size, attributes: []) + + let mediaPlayer = MediaPlayer(postbox: item.context.account.postbox, reference: MediaResourceReference.standalone(resource: file.resource), streamable: true, video: true, preferSoftwareDecoding: false, enableSound: false, fetchAutomatically: true) + + mediaPlayer.actionAtEnd = .loop(nil) + self.photoVideoPlayer = mediaPlayer + mediaPlayer.play() + + if let seekTo = video.startTimestamp { + mediaPlayer.seek(timestamp: seekTo) + } + mediaPlayer.attachPlayerView(self.photoVideoView!) + + } else { + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + } + + } else { + imageView?.removeFromSuperview() + imageView = nil + } + self.needsLayout = true + updateListeners() + } + } + +} diff --git a/Telegram-Mac/ChatServiceRowView.swift b/Telegram-Mac/ChatServiceRowView.swift index 4ef0b1a981..6a92e2b76c 100644 --- a/Telegram-Mac/ChatServiceRowView.swift +++ b/Telegram-Mac/ChatServiceRowView.swift @@ -8,65 +8,4 @@ import Cocoa import TGUIKit -class ChatServiceRowView: TableRowView { - - private var textView:TextView - private var imageView:TransformImageView? - required init(frame frameRect: NSRect) { - textView = TextView() - textView.isSelectable = false - super.init(frame: frameRect) - addSubview(textView) - } - - override var backdorColor: NSColor { - return theme.colors.background - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layout() { - super.layout() - - if let item = item as? ChatServiceItem { - textView.update(item.text) - textView.centerX(y:6) - if let imageArguments = item.imageArguments { - imageView?.setFrameSize(imageArguments.imageSize) - imageView?.centerX(y:textView.frame.maxY + 6) - self.imageView?.set(arguments: imageArguments) - } - - } - } - - override func doubleClick(in location: NSPoint) { - if let item = self.item as? ChatRowItem, item.chatInteraction.presentation.state == .normal { - if self.hitTest(location) == nil || self.hitTest(location) == self { - item.chatInteraction.setupReplyMessage(item.message?.id) - } - } - } - - override func set(item: TableRowItem, animated: Bool) { - super.set(item: item, animated:animated) - - if let item = item as? ChatServiceItem { - if let image = item.image { - if imageView == nil { - self.imageView = TransformImageView() - self.addSubview(imageView!) - } - imageView?.setSignal(account: item.account, signal: chatMessagePhoto(account: item.account, photo: image, toRepresentationSize:NSMakeSize(100,100), scale: backingScaleFactor)) - } else { - imageView?.removeFromSuperview() - imageView = nil - } - textView.backgroundColor = backdorColor - self.needsLayout = true - } - } - -} + diff --git a/Telegram-Mac/ChatStickerContentView.swift b/Telegram-Mac/ChatStickerContentView.swift index b01cf5646d..7f086cdfd7 100644 --- a/Telegram-Mac/ChatStickerContentView.swift +++ b/Telegram-Mac/ChatStickerContentView.swift @@ -8,62 +8,98 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ChatStickerContentView: ChatMediaContentView { - + private let statusDisposable = MetaDisposable() private var image:TransformImageView = TransformImageView() - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + statusDisposable.dispose() + } + + + override func clean() { + statusDisposable.set(nil) + } + + override var backgroundColor: NSColor { + didSet { + + } + } + + override func previewMediaIfPossible() -> Bool { + if let table = table, let context = context, let window = window as? Window { + _ = startModalPreviewHandle(table, window: window, context: context) + } + return true + } + required init(frame frameRect: NSRect) { super.init(frame:frameRect) self.addSubview(image) + } override func executeInteraction(_ isControl: Bool) { if let window = window as? Window { - if let account = account, let peerId = parent?.id.peerId, let media = media as? TelegramMediaFile, let reference = media.stickerReference { + if let context = context, let peerId = parent?.id.peerId, let media = media as? TelegramMediaFile, let reference = media.stickerReference { - showModal(with:StickersPackPreviewModalController(account, peerId: peerId, reference: reference), for:window) + showModal(with:StickerPackPreviewModalController(context, peerId: peerId, reference: reference), for:window) } } } - override func update(with media: Media, size: NSSize, account: Account, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false) { + override func update(with media: Media, size: NSSize, context: AccountContext, parent:Message?, table:TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { - let mediaUpdated = self.media == nil || !self.media!.isEqual(media) - - super.update(with: media, size: size, account: account, parent:parent,table:table, parameters:parameters, animated: animated) + super.update(with: media, size: size, context: context, parent:parent,table:table, parameters:parameters, animated: animated, positionFlags: positionFlags) - if let file = media as? TelegramMediaFile, mediaUpdated { - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + if let file = media as? TelegramMediaFile { - self.image.animatesAlphaOnFirstTransition = true + let dimensions = file.dimensions?.size.aspectFitted(size) ?? size - - self.image.setSignal(signal: cachedMedia(media: file, size: arguments.imageSize, scale: backingScaleFactor)) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) - if self.image.layer?.contents == nil { - self.image.setSignal(account: account, signal: chatMessageSticker(account: account, file: file, type: .chatMessage, scale: backingScaleFactor), cacheImage: { [weak self] signal in - if let strongSelf = self { - return cacheMedia(signal: signal, media: file, size: arguments.imageSize, scale: strongSelf.backingScaleFactor) - } else { - return .complete() + self.image.animatesAlphaOnFirstTransition = false + + self.image.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor), clearInstantly: true) + if !self.image.isFullyLoaded { + self.image.setSignal( chatMessageSticker(postbox: context.account.postbox, file: file, small: size.width < 120, scale: backingScaleFactor, fetched: true), cacheImage: { [weak file] result in + if let media = file { + return cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) } }) + self.image.set(arguments: arguments) + } else { + self.image.dispose() } - self.image.set(arguments: arguments) - self.image.setFrameSize(arguments.imageSize) - _ = fileInteractiveFetched(account: account, file: file).start() + self.image.setFrameSize(dimensions) + self.image.center() + self.fetchStatus = .Local + + let signal = context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue + + statusDisposable.set(signal.start(next: { [weak self] status in + self?.fetchStatus = status + })) } } - + override func layout() { + super.layout() + self.image.center() + } + + override var contents: Any? { + return self.image.layer?.contents + } } diff --git a/Telegram-Mac/ChatStickersTouchBarPopover.swift b/Telegram-Mac/ChatStickersTouchBarPopover.swift new file mode 100644 index 0000000000..3bffe2d40d --- /dev/null +++ b/Telegram-Mac/ChatStickersTouchBarPopover.swift @@ -0,0 +1,201 @@ +// +// ChatStickersTouchBarPopover.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +@available(OSX 10.12.2, *) +fileprivate extension NSTouchBarItem.Identifier { + static let sticker = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.sticker") +} + +@available(OSX 10.12.2, *) +private extension NSTouchBar.CustomizationIdentifier { + static let stickersScrubber = NSTouchBar.CustomizationIdentifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.StickersScrubber") +} + +enum TouchBarStickerEntry { + case header(TextViewLayout) + case sticker(TelegramMediaFile) +} + + + +@available(OSX 10.12.2, *) +class StickersScrubberBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate { + + private static let stickerItemViewIdentifier = "StickersItemViewIdentifier" + private static let headerItemViewIdentifier = "HeaderItemViewIdentifier" + + private let entries: [TouchBarStickerEntry] + private let context: AccountContext + private let sendSticker: (TelegramMediaFile)->Void + private let animated: Bool + init(identifier: NSTouchBarItem.Identifier, context: AccountContext, animated: Bool, sendSticker:@escaping(TelegramMediaFile)->Void, entries: [TouchBarStickerEntry]) { + self.entries = entries + self.context = context + self.sendSticker = sendSticker + self.animated = animated + super.init(identifier: identifier) + + let scrubber = TGScrubber() + scrubber.register(TouchBarStickerItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: StickersScrubberBarItem.stickerItemViewIdentifier)) + scrubber.register(TouchBarScrubberHeaderItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: StickersScrubberBarItem.headerItemViewIdentifier)) + + scrubber.mode = .free + scrubber.selectionBackgroundStyle = .roundedBackground + scrubber.floatsSelectionViews = true + scrubber.delegate = self + scrubber.dataSource = self + + let gesture = NSPressGestureRecognizer(target: self, action: #selector(self.pressGesture(_:))) + gesture.allowedTouchTypes = NSTouch.TouchTypeMask.direct + gesture.minimumPressDuration = 0.3 + gesture.allowableMovement = 0 + scrubber.addGestureRecognizer(gesture) + + + self.view = scrubber + } + + fileprivate var modalPreview: PreviewModalController? + + @objc private func pressGesture(_ gesture: NSPressGestureRecognizer) { + + let runSelector:()->Void = { [weak self] in + guard let `self` = self else { + return + } + let scrollView = HackUtils.findElements(byClass: "NSScrollView", in: self.view)?.first as? NSScrollView + + guard let container = scrollView?.documentView?.subviews.first else { + return + } + var point = gesture.location(in: container) + point.y = 0 + for itemView in container.subviews { + if NSPointInRect(point, itemView.frame) { + if let itemView = itemView as? TouchBarStickerItemView { + self.modalPreview?.update(with: itemView.quickPreview) + } + } + } + } + + switch gesture.state { + case .began: + modalPreview = PreviewModalController(context) + showModal(with: modalPreview!, for: context.window) + runSelector() + case .failed, .cancelled, .ended: + modalPreview?.close() + modalPreview = nil + case .changed: + runSelector() + case .possible: + break + @unknown default: + break + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + + func numberOfItems(for scrubber: NSScrubber) -> Int { + return entries.count + } + + func scrubber(_ scrubber: NSScrubber, didHighlightItemAt highlightedIndex: Int) { + switch entries[highlightedIndex] { + case .header: + scrubber.selectionBackgroundStyle = nil + scrubber.selectedIndex = -1 + default: + scrubber.selectionBackgroundStyle = .roundedBackground + } + } + + func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let itemView: NSScrubberItemView + switch entries[index] { + case let .header(title): + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: StickersScrubberBarItem.headerItemViewIdentifier), owner: nil) as! TouchBarScrubberHeaderItemView + view.update(title) + itemView = view + case let .sticker(file): + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: StickersScrubberBarItem.stickerItemViewIdentifier), owner: nil) as! TouchBarStickerItemView + view.update(context: context, file: file, animated: self.animated) + itemView = view + } + + return itemView + } + + func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize { + switch entries[itemIndex] { + case let .header(layout): + return NSMakeSize(layout.layoutSize.width + 20, 30) + case .sticker: + return NSSize(width: 40, height: 30) + } + } + + + func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) { + switch entries[index] { + case let .sticker(file): + sendSticker(file) + default: + break + } + } +} + + +@available(OSX 10.12.2, *) +final class ChatStickersTouchBarPopover : NSTouchBar, NSTouchBarDelegate { + private let chatInteraction: ChatInteraction + private let entries: [TouchBarStickerEntry] + private let dismiss:(TelegramMediaFile?) -> Void + init(chatInteraction: ChatInteraction, dismiss:@escaping(TelegramMediaFile?)->Void, entries: [TouchBarStickerEntry]) { + self.dismiss = dismiss + + self.entries = entries + self.chatInteraction = chatInteraction + super.init() + delegate = self + customizationIdentifier = .stickersScrubber + defaultItemIdentifiers = [.sticker] + customizationAllowedItemIdentifiers = [.sticker] + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .sticker: + let scrubberItem: NSCustomTouchBarItem = StickersScrubberBarItem(identifier: identifier, context: chatInteraction.context, animated: true, sendSticker: { [weak self] file in + self?.dismiss(file) + }, entries: self.entries) + return scrubberItem + default: + return nil + } + } + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatStorageManagmentModalController.swift b/Telegram-Mac/ChatStorageManagmentModalController.swift index 146fad6e63..3834faa833 100644 --- a/Telegram-Mac/ChatStorageManagmentModalController.swift +++ b/Telegram-Mac/ChatStorageManagmentModalController.swift @@ -8,119 +8,12 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit -/*let controller = ActionSheetController() - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - - var sizeIndex: [PeerCacheUsageCategory: (Bool, Int64)] = [:] - - var itemIndex = 0 - - let updateTotalSize: () -> Void = { [weak controller] in - controller?.updateItem(groupIndex: 0, itemIndex: itemIndex, { item in - let title: String - let filteredSize = sizeIndex.values.reduce(0, { $0 + ($1.0 ? $1.1 : 0) }) - - if filteredSize == 0 { - title = "Clear" - } else { - title = "Clear (\(dataSizeString(Int(filteredSize))))" - } - - if let item = item as? ActionSheetButtonItem { - return ActionSheetButtonItem(title: title, color: filteredSize != 0 ? .accent : .disabled, enabled: filteredSize != 0, action: item.action) - } - return item - }) - } - - let toggleCheck: (PeerCacheUsageCategory, Int) -> Void = { [weak controller] category, itemIndex in - if let (value, size) = sizeIndex[category] { - sizeIndex[category] = (!value, size) - } - controller?.updateItem(groupIndex: 0, itemIndex: itemIndex, { item in - if let item = item as? ActionSheetCheckboxItem { - return ActionSheetCheckboxItem(title: item.title, label: item.label, value: !item.value, action: item.action) - } - return item - }) - updateTotalSize() - } - var items: [ActionSheetItem] = [] - - let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file] - - var totalSize: Int64 = 0 - - for categoryId in validCategories { - if let media = categories[categoryId] { - var categorySize: Int64 = 0 - for (_, size) in media { - categorySize += size - } - sizeIndex[categoryId] = (true, categorySize) - totalSize += categorySize - let index = itemIndex - items.append(ActionSheetCheckboxItem(title: stringForCategory(categoryId), label: dataSizeString(Int(categorySize)), value: true, action: { value in - toggleCheck(categoryId, index) - })) - itemIndex += 1 - } - } - - if !items.isEmpty { - items.append(ActionSheetButtonItem(title: "Clear (\(dataSizeString(Int(totalSize))))", action: { - if let statsPromise = statsPromise { - var clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 }) - //var clearSize: Int64 = 0 - - var clearMediaIds = Set() - - var media = stats.media - if var categories = media[peerId] { - for category in clearCategories { - if let contents = categories[category] { - for (mediaId, size) in contents { - clearMediaIds.insert(mediaId) - //clearSize += size - } - } - categories.removeValue(forKey: category) - } - - media[peerId] = categories - } - - var clearResourceIds = Set() - for id in clearMediaIds { - if let ids = stats.mediaResourceIds[id] { - for resourceId in ids { - clearResourceIds.insert(WrappedMediaResourceId(resourceId)) - } - } - } - - statsPromise.set(.single(.result(CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers)))) - - clearDisposable.set(clearCachedMediaResources(account: account, mediaResourceIds: clearResourceIds).start()) - } - - dismissAction() - })) - - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: "Cancel", action: { dismissAction() })]) - ]) - presentControllerImpl?(controller) - } */ - class ChatStorageManagmentModalController: ModalViewController { @@ -131,30 +24,50 @@ class ChatStorageManagmentModalController: ModalViewController { self.categories = categories self.clear = clear super.init(frame: NSMakeRect(0, 0, 300, CGFloat(categories.count) * 40 + 40 + 50)) + bar = .init(height: 0) + } + + override var dynamicSize: Bool { + return true + } + + override func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.listHeight)), animated: false) } override func viewDidLoad() { super.viewDidLoad() + genericView.getBackgroundColor = { + theme.colors.listBackground + } + reloadData() + readyOnce() + } + + private func reloadData() { let initialSize = atomicSize.modify({$0}) - _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 20, stableId: arc4random())) + genericView.removeAll() + _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 30, stableId: arc4random(), viewType: .separator)) - let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file] + + let validCategories: [PeerCacheUsageCategory] = [.image, .video, .audio, .file].filter { + categories[$0] != nil + } var totalSize: Int64 = 0 var itemIndex = 0 - - for categoryId in validCategories { + for (i, categoryId) in validCategories.enumerated() { if let media = categories[categoryId] { var categorySize: Int64 = 0 for (_, size) in media { categorySize += size } - sizeIndex[categoryId] = (true, categorySize) + sizeIndex[categoryId] = (sizeIndex[categoryId]?.0 ?? true, categorySize) totalSize += categorySize let index = itemIndex @@ -167,21 +80,18 @@ class ChatStorageManagmentModalController: ModalViewController { let filteredSize = strongSelf.sizeIndex.values.reduce(0, { $0 + ($1.0 ? $1.1 : 0) }) if filteredSize == 0 { - title = "Clear" + title = L10n.storageUsageClear } else { - title = "Clear (\(dataSizeString(Int(filteredSize))))" + title = "\(L10n.storageUsageClear) (\(dataSizeString(Int(filteredSize))))" } strongSelf.modal?.interactions?.updateDone( { button in button.set(text: title, for: .Normal) }) - strongSelf.genericView.reloadData() + strongSelf.reloadData() } } - - _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: index, name: stringForCategory(categoryId) + " (\(dataSizeString(Int(categorySize))))" , type: .selectable(stateback: { [weak self] () -> Bool in - return self?.sizeIndex[categoryId]?.0 ?? false - }), action: { + _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: index, name: stringForCategory(categoryId) + " (\(dataSizeString(Int(categorySize))))" , type: .selectable(sizeIndex[categoryId]?.0 ?? false), viewType: bestGeneralViewType(validCategories, for: i), action: { toggleCheck(categoryId, index) })) @@ -190,25 +100,27 @@ class ChatStorageManagmentModalController: ModalViewController { } - _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 20, stableId: arc4random())) - - readyOnce() + _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 30, stableId: arc4random(), viewType: .separator)) } private func stringForCategory(_ category: PeerCacheUsageCategory) -> String { switch category { case .image: - return tr(.storageClearPhotos) + return L10n.storageClearPhotos case .video: - return tr(.storageClearVideos) + return L10n.storageClearVideos case .audio: - return tr(.storageClearAudio) + return L10n.storageClearAudio case .file: - return tr(.storageClearDocuments) + return L10n.storageClearDocuments } } + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: nil, center: ModalHeaderData.init(title: L10n.telegramStorageUsageController), right: nil) + } + override var modalInteractions: ModalInteractions? { var totalSize: Int64 = 0 @@ -221,14 +133,12 @@ class ChatStorageManagmentModalController: ModalViewController { totalSize += categorySize } - - return ModalInteractions(acceptTitle: tr(.storageClear(dataSizeString(Int(totalSize)))), accept: { [weak self] in + return ModalInteractions(acceptTitle: tr(L10n.storageClear(dataSizeString(Int(totalSize)))), accept: { [weak self] in if let strongSelf = self { self?.clear(strongSelf.sizeIndex) } - self?.close() - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + }, cancelTitle: L10n.modalCancel, drawBorder: true, height: 50) } private var genericView:TableView { diff --git a/Telegram-Mac/ChatSwitchInlineController.swift b/Telegram-Mac/ChatSwitchInlineController.swift index c0385e08aa..856f24683c 100644 --- a/Telegram-Mac/ChatSwitchInlineController.swift +++ b/Telegram-Mac/ChatSwitchInlineController.swift @@ -8,17 +8,20 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class ChatSwitchInlineController: ChatController { private let fallbackId:PeerId - init(account:Account, peerId:PeerId, fallbackId:PeerId, initialAction:ChatInitialAction? = nil) { + private let fallbackMode: ChatMode + init(context:AccountContext, peerId:PeerId, fallbackId:PeerId, fallbackMode: ChatMode, initialAction:ChatInitialAction? = nil) { self.fallbackId = fallbackId - super.init(account: account, peerId: peerId, initialAction: initialAction) + self.fallbackMode = fallbackMode + super.init(context: context, chatLocation: .peer(peerId), initialAction: initialAction) } override var removeAfterDisapper: Bool { @@ -26,11 +29,11 @@ class ChatSwitchInlineController: ChatController { } override open func backSettings() -> (String,CGImage?) { - return (tr(.navigationCancel),nil) + return (L10n.navigationCancel,nil) } - override func applyTransition(_ transition: TableUpdateTransition, initialData: ChatHistoryCombinedInitialData) { - super.applyTransition(transition, initialData: initialData) + override func applyTransition(_ transition:TableUpdateTransition, view: MessageHistoryView?, initialData:ChatHistoryCombinedInitialData, isLoading: Bool) { + super.applyTransition(transition, view: view, initialData: initialData, isLoading: isLoading) if case let .none(interface) = transition.state, let _ = interface { for (_, item) in transition.inserted { @@ -41,7 +44,14 @@ class ChatSwitchInlineController: ChatController { for button in row.buttons { if case let .switchInline(samePeer: _, query: query) = button.action { let text = "@\(message.inlinePeer?.username ?? "") \(query)" - self.navigationController?.push(ChatController(account: account, peerId: fallbackId, initialAction: .inputText(text: text, behavior: .automatic))) + let controller: ChatController + switch self.fallbackMode { + case .history: + controller = ChatController(context: context, chatLocation: .peer(fallbackId), initialAction: .inputText(text: text, behavior: .automatic)) + case .scheduled: + controller = ChatScheduleController(context: context, chatLocation: .peer(fallbackId), initialAction: .inputText(text: text, behavior: .automatic)) + } + self.navigationController?.push(controller) } } } diff --git a/Telegram-Mac/ChatTitleBarView.swift b/Telegram-Mac/ChatTitleBarView.swift index a65c51a550..e7a16efc84 100644 --- a/Telegram-Mac/ChatTitleBarView.swift +++ b/Telegram-Mac/ChatTitleBarView.swift @@ -8,77 +8,107 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import AVFoundation private class ConnectionStatusView : View { private var textViewLayout:TextViewLayout? private var disableProxyButton: TitleButton? + private(set) var backButton: ImageButton? + + var isSingleLayout: Bool = false { + didSet { + updateBackButton() + } + } + var disableProxy:(()->Void)? - var status:ConnectionStatus = .online { + var status:ConnectionStatus = .online(proxyAddress: nil) { didSet { let attr:NSAttributedString - if case .connecting(true) = status { - disableProxyButton = TitleButton() - disableProxyButton?.set(color: theme.colors.grayText, for: .Normal) - disableProxyButton?.set(font: .medium(.text), for: .Normal) - disableProxyButton?.set(text: tr(.connectingStatusDisableProxy), for: .Normal) - disableProxyButton?.sizeToFit() - addSubview(disableProxyButton!) - - disableProxyButton?.set(handler: { [weak self] _ in - self?.disableProxy?() - }, for: .Click) + if case let .connecting(proxy, _) = status { + if let _ = proxy { + if disableProxyButton == nil { + disableProxyButton = TitleButton() + } + disableProxyButton?.set(color: theme.colors.grayText, for: .Normal) + disableProxyButton?.set(font: .medium(.text), for: .Normal) + disableProxyButton?.set(text: tr(L10n.connectingStatusDisableProxy), for: .Normal) + _ = disableProxyButton?.sizeToFit() + addSubview(disableProxyButton!) + + disableProxyButton?.set(handler: { [weak self] _ in + self?.disableProxy?() + }, for: .Click) + } else { + disableProxyButton?.removeFromSuperview() + disableProxyButton = nil + } } else { disableProxyButton?.removeFromSuperview() disableProxyButton = nil } switch status { - case .connecting(let toProxy): - attr = .initialize(string: toProxy ? tr(.chatConnectingStatusConnectingToProxy) : tr(.chatConnectingStatusConnecting), color: theme.colors.text, font: .medium(.header)) + case let .connecting(proxy, _): + attr = .initialize(string: proxy != nil ? L10n.chatConnectingStatusConnectingToProxy : L10n.chatConnectingStatusConnecting, color: theme.colors.text, font: .medium(.header)) case .updating: - attr = .initialize(string: tr(.chatConnectingStatusUpdating), color: theme.colors.text, font: .medium(.header)) + attr = .initialize(string: L10n.chatConnectingStatusUpdating, color: theme.colors.text, font: .medium(.header)) case .waitingForNetwork: - attr = .initialize(string: tr(.chatConnectingStatusWaitingNetwork), color: theme.colors.text, font: .medium(.header)) + attr = .initialize(string: L10n.chatConnectingStatusWaitingNetwork, color: theme.colors.text, font: .medium(.header)) case .online: attr = NSAttributedString() } textViewLayout = TextViewLayout(attr, maximumNumberOfLines: 1) needsLayout = true - // indicator.animates = true } } private let textView:TextView = TextView() private let indicator:ProgressIndicator = ProgressIndicator() required init(frame frameRect: NSRect) { super.init(frame: frameRect) - // indicator.setFrameSize(18,18) -// indicator.numberOfLines = 8 -// indicator.innerMargin = 3 -// indicator.widthOfLine = 3 -// indicator.lengthOfLine = 6 textView.userInteractionEnabled = false textView.isSelectable = false addSubview(textView) addSubview(indicator) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) backgroundColor = theme.colors.background textView.backgroundColor = theme.colors.background disableProxyButton?.set(background: theme.colors.background, for: .Normal) - // indicator.color = theme.colors.indicatorColor + indicator.progressColor = theme.colors.text let status = self.status self.status = status } + + private func updateBackButton() { + if isSingleLayout { + let button: ImageButton + if let b = self.backButton { + button = b + } else { + button = ImageButton() + self.backButton = button + addSubview(button) + } + button.autohighlight = false + button.set(image: theme.icons.chatNavigationBack, for: .Normal) + _ = button.sizeToFit() + } else { + backButton?.removeFromSuperview() + backButton = nil + } + needsLayout = true + } deinit { //indicator.animates = false @@ -92,30 +122,170 @@ private class ConnectionStatusView : View { super.layout() if let textViewLayout = textViewLayout { + + let offset: CGFloat = backButton != nil ? 16 : 0 + textViewLayout.measure(width: frame.width) let f = focus(textViewLayout.layoutSize, inset:NSEdgeInsets(left: 12, top: 3)) - indicator.centerY(x:0) - + indicator.centerY(x: offset) textView.update(textViewLayout) if let disableProxyButton = disableProxyButton { - disableProxyButton.setFrameOrigin(indicator.frame.maxX + 4, floorToScreenPixels(frame.height / 2) + 2) - textView.setFrameOrigin(indicator.frame.maxX + 8, floorToScreenPixels(frame.height / 2) - textView.frame.height + 2) + disableProxyButton.setFrameOrigin(indicator.frame.maxX + 3, floorToScreenPixels(backingScaleFactor, frame.height / 2) + 2) + textView.setFrameOrigin(indicator.frame.maxX + 8, floorToScreenPixels(backingScaleFactor, frame.height / 2) - textView.frame.height + 2) } else { textView.setFrameOrigin(NSMakePoint(indicator.frame.maxX + 4, f.origin.y)) } - + backButton?.centerY(x: 0) + } + + } + +} + + +private final class VideoAvatarProgressView: View { + private let progressView = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.progressView) + backgroundColor = .blackTransparent + progressView.progressColor = .white + layer?.cornerRadius = frameRect.width / 2 + } + + override func layout() { + progressView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +private final class VideoAvatarContainer : View { + let circle: View = View() + + private var mediaPlayer: MediaPlayer? + private var view: MediaPlayerView? + + private let fetchDisposable = MetaDisposable() + private let statusDisposable = MetaDisposable() + + private var progressView: VideoAvatarProgressView? + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(circle) + circle.frame = bounds + + circle.layer?.cornerRadius = bounds.width / 2 + circle.layer?.borderWidth = 1 + circle.layer?.borderColor = theme.colors.accent.cgColor + + + isEventLess = true + + } + + func animateIn() { + // circle.layer?.animateScaleCenter(from: 0.2, to: 1.0, duration: 0.2) + } + func animateOut() { + // circle.layer?.animateScaleCenter(from: 1.0, to: 0.2, duration: 0.2) + } + + func updateWith(file: TelegramMediaFile, seekTo: TimeInterval?, reference: PeerReference?, context: AccountContext) { + // player.update(FileMediaReference.standalone(media: file), context: context) + if let reference = reference { + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.avatar(peer: reference, resource: file.resource)).start()) + } else { + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: file.resource)).start()) + } + + let mediaReference: MediaResourceReference + if let reference = reference { + mediaReference = MediaResourceReference.avatar(peer: reference, resource: file.resource) + } else { + mediaReference = MediaResourceReference.standalone(resource: file.resource) } + let mediaPlayer = MediaPlayer(postbox: context.account.postbox, reference: mediaReference, streamable: true, video: true, preferSoftwareDecoding: false, enableSound: false, fetchAutomatically: false) + + + let view = MediaPlayerView() + + view.setVideoLayerGravity(.resizeAspectFill) + + mediaPlayer.attachPlayerView(view) + + mediaPlayer.actionAtEnd = .loop(nil) + + view.frame = NSMakeRect(2, 2, frame.width - 4, frame.height - 4) + view.layer?.cornerRadius = bounds.width / 2 + + addSubview(view) + + self.mediaPlayer = mediaPlayer + self.view = view + + mediaPlayer.play() + if let seekTo = seekTo { + mediaPlayer.seek(timestamp: seekTo) + } + + let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue + + statusDisposable.set(statusSignal.start(next: { [weak self] status in + switch status { + case .Local: + if let progressView = self?.progressView { + self?.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressView] _ in + progressView?.removeFromSuperview() + }) + } + default: + if self?.progressView == nil, let frame = self?.frame { + let view = VideoAvatarProgressView(frame: NSMakeRect(2, 2, frame.width - 4, frame.height - 4)) + self?.progressView = view + self?.addSubview(view) + view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + })) + } + deinit { + fetchDisposable.dispose() + statusDisposable.dispose() + } + + override func layout() { + super.layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } -class ChatTitleBarView: TitledBarView { +class ChatTitleBarView: TitledBarView, InteractionContentViewProtocol { + - private var isSingleLayout:Bool = false + private var isSingleLayout:Bool = false { + didSet { + connectionStatusView?.isSingleLayout = isSingleLayout + connectionStatusView?.backButton?.removeAllHandlers() + connectionStatusView?.backButton?.set(handler: { [weak self] _ in + self?.chatInteraction.context.sharedContext.bindings.rootNavigation().back() + }, for: .Click) + } + } private var connectionStatusView:ConnectionStatusView? = nil private let activities:ChatActivitiesModel private let searchButton:ImageButton = ImageButton() @@ -125,50 +295,87 @@ class ChatTitleBarView: TitledBarView { private let badgeNode:GlobalBadgeNode private let disposable = MetaDisposable() private let closeButton = ImageButton() - var connectionStatus:ConnectionStatus = .online { + private var lastestUsersController: ViewController? + private let fetchPeerAvatar = DisposableSet() + + private var videoAvatarView: VideoAvatarContainer? + + var connectionStatus:ConnectionStatus = .online(proxyAddress: nil) { didSet { if connectionStatus != oldValue { - if connectionStatus == .online { - containerView.change(pos: NSMakePoint(0, 0), animated: true) + if case .online = connectionStatus { + + //containerView.change(pos: NSMakePoint(0, 0), animated: true) if let connectionStatusView = connectionStatusView { connectionStatusView.change(pos: NSMakePoint(0, -frame.height), animated: true) - connectionStatusView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion:false, completion:{ [weak self] _ in + connectionStatusView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion:false, completion:{ [weak self] completed in self?.connectionStatusView?.removeFromSuperview() self?.connectionStatusView = nil }) } + } else { if connectionStatusView == nil { connectionStatusView = ConnectionStatusView(frame: NSMakeRect(0, -frame.height, frame.width, frame.height)) + connectionStatusView?.isSingleLayout = isSingleLayout connectionStatusView?.disableProxy = chatInteraction.disableProxy addSubview(connectionStatusView!) connectionStatusView?.change(pos: NSMakePoint(0,0), animated: true) - containerView.change(pos: NSMakePoint(0, frame.height), animated: true) } - connectionStatusView?.status = connectionStatus - + applyVideoAvatarIfNeeded(nil) } } } } - var peerView:PeerView? { + var postboxView:PostboxView? { didSet { updateStatus() } } + var onlineMemberCount:Int32? = nil { + didSet { + updateStatus() + } + } + + + var inputActivities:(PeerId, [(Peer, PeerInputActivity)])? { didSet { - if let inputActivities = inputActivities { - activities.update(with: inputActivities, for: max(frame.width - 60, 160), theme:theme.activity(key: 4, foregroundColor: theme.colors.blueUI, backgroundColor: theme.colors.background), layout: { [weak self] show in - self?.needsLayout = true - self?.hiddenStatus = show - self?.setNeedsDisplay() - self?.activities.view?.isHidden = !show + if let inputActivities = inputActivities, self.chatInteraction.mode == .history { + activities.update(with: inputActivities, for: max(frame.width - 80, 160), theme:theme.activity(key: 4, foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background), layout: { [weak self] show in + guard let `self` = self else { return } + self.needsLayout = true + self.hiddenStatus = show + self.setNeedsDisplay() + if let view = self.activities.view { + if self.animates { + if show { + if view.isHidden { + + } + view.isHidden = false + view.change(opacity: 1, duration: 0.2) + } else { + view.change(opacity: 0, completion: { [weak view] completed in + if completed { + view?.isHidden = true + } + }) + } + + } else { + view.layer?.opacity = 1 + view.layer?.removeAllAnimations() + view.isHidden = !show + } + } + }) } else { activities.clean() @@ -186,21 +393,18 @@ class ChatTitleBarView: TitledBarView { searchButton.disableActions() callButton.disableActions() - var layoutChanged:(()->Void)? + videoAvatarDisposable.set(peerPhotos(account: chatInteraction.context.account, peerId: chatInteraction.chatLocation.peerId).start()) - badgeNode = GlobalBadgeNode(chatInteraction.account, excludePeerId: self.chatInteraction.peerId, layoutChanged: { - layoutChanged?() + badgeNode = GlobalBadgeNode(chatInteraction.context.account, sharedContext: chatInteraction.context.sharedContext, excludePeerId: self.chatInteraction.peerId, view: View(), layoutChanged: { }) - - super.init(controller: controller, textInset: 46) - layoutChanged = { - //self?.needsLayout = true - } + super.init(controller: controller, textInset: 46) + addSubview(activities.view!) + searchButton.set(handler: { [weak self] _ in - self?.chatInteraction.update({$0.updatedSearchMode(!$0.isSearchMode)}) + self?.chatInteraction.update({$0.updatedSearchMode((!$0.isSearchMode.0, nil, nil))}) }, for: .Click) addSubview(searchButton) @@ -212,7 +416,6 @@ class ChatTitleBarView: TitledBarView { chatInteraction.call() }, for: .Click) - addSubview(activities.view!) activities.view?.isHidden = true callButton.isHidden = true addSubview(callButton) @@ -220,19 +423,23 @@ class ChatTitleBarView: TitledBarView { avatarControl.setFrameSize(36,36) addSubview(avatarControl) - disposable.set(chatInteraction.account.context.layoutHandler.get().start(next: { [weak self] state in + disposable.set(chatInteraction.context.sharedContext.layoutHandler.get().start(next: { [weak self] state in if let strongSelf = self { switch state { case .single: strongSelf.isSingleLayout = true strongSelf.badgeNode.view?.isHidden = false strongSelf.closeButton.isHidden = false + strongSelf.searchButton.isHidden = false + strongSelf.avatarControl.isHidden = false default: - strongSelf.isSingleLayout = strongSelf.controller is ChatAdditionController + strongSelf.isSingleLayout = strongSelf.controller?.className != "Telegram.ChatController" //( is ChatAdditionController) || (strongSelf.controller is ChatSwitchInlineController) || (strongSelf.controller is ChatScheduleController) strongSelf.badgeNode.view?.isHidden = true - strongSelf.closeButton.isHidden = !(strongSelf.controller is ChatAdditionController) + strongSelf.closeButton.isHidden = strongSelf.controller?.className == "Telegram.ChatController" + strongSelf.searchButton.isHidden = strongSelf.controller is ChatScheduleController + strongSelf.avatarControl.isHidden = strongSelf.controller is ChatScheduleController } - strongSelf.textInset = strongSelf.isSingleLayout ? 66 : 46 + strongSelf.textInset = strongSelf.avatarControl.isHidden ? 24 : strongSelf.isSingleLayout ? 66 : 46 strongSelf.needsLayout = true } })) @@ -241,19 +448,23 @@ class ChatTitleBarView: TitledBarView { closeButton.autohighlight = false closeButton.set(image: theme.icons.chatNavigationBack, for: .Normal) closeButton.set(handler: { [weak self] _ in - self?.chatInteraction.account.context.mainNavigation?.back() + self?.chatInteraction.context.sharedContext.bindings.rootNavigation().back() }, for: .Click) - closeButton.sizeToFit() + _ = closeButton.sizeToFit() closeButton.setFrameSize(closeButton.frame.width, frame.height) addSubview(closeButton) avatarControl.userInteractionEnabled = false - + addSubview(badgeNode.view!) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) + + self.continuesAction = true + } + override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) self.connectionStatusView?.setFrameSize(newSize) @@ -262,27 +473,158 @@ class ChatTitleBarView: TitledBarView { } + + func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { + if chatInteraction.peer?.largeProfileImage?.resource.id.uniqueId == stableId.base as? String { + return avatarControl + } + return nil + } + func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + + } + func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + + } + func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + return nil + } + public func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + + } + + private let videoAvatarDisposable = MetaDisposable() + + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + applyVideoAvatarIfNeeded(nil) + } + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + applyVideoAvatarIfNeeded(nil) + } + + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + + let point = convert(event.locationInWindow, from: nil) + + + if NSPointInRect(point, avatarControl.frame), chatInteraction.mode != .scheduled { + let signal = peerPhotos(account: chatInteraction.context.account, peerId: chatInteraction.chatLocation.peerId) |> deliverOnMainQueue + videoAvatarDisposable.set(signal.start(next: { [weak self] photos in + self?.applyVideoAvatarIfNeeded(photos.first) + })) + } else { + videoAvatarDisposable.set(nil) + applyVideoAvatarIfNeeded(nil) + } + } + + private var currentPhoto: TelegramPeerPhoto? + + private func applyVideoAvatarIfNeeded(_ photo: TelegramPeerPhoto?) { + guard let window = self.window as? Window, currentPhoto?.image != photo?.image else { + return + } + + currentPhoto = photo + + let point = convert(window.mouseLocationOutsideOfEventStream, from: nil) + + + let file: TelegramMediaFile? + let seekTo: TimeInterval? + if let photo = photo, let video = photo.image.videoRepresentations.last { + file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.image.representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: video.resource.size, attributes: []) + seekTo = video.startTimestamp + } else { + seekTo = nil + file = nil + } + + if NSPointInRect(point, avatarControl.frame), chatInteraction.mode != .scheduled, chatInteraction.peerId != chatInteraction.context.peerId, self.connectionStatusView == nil, let file = file, let peer = chatInteraction.presentation.mainPeer { + let control: VideoAvatarContainer + if let view = self.videoAvatarView { + control = view + } else { + control = VideoAvatarContainer(frame: NSMakeRect(avatarControl.frame.minX - 2, avatarControl.frame.minY - 2, avatarControl.frame.width + 4, avatarControl.frame.height + 4)) + addSubview(control, positioned: .below, relativeTo: badgeNode.view) + control.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + control.animateIn() + self.videoAvatarView = control + } + control.updateWith(file: file, seekTo: seekTo, reference: PeerReference(peer), context: chatInteraction.context) + + } else { + if let view = self.videoAvatarView { + self.videoAvatarView = nil + view.animateOut() + view.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in + view?.removeFromSuperview() + }) + } + } + + } + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + + let point = convert(event.locationInWindow, from: nil) + + + if NSPointInRect(point, avatarControl.frame), chatInteraction.mode != .scheduled, chatInteraction.peerId != chatInteraction.context.peerId { + if let peer = chatInteraction.peer, let large = peer.largeProfileImage { + showPhotosGallery(context: chatInteraction.context, peerId: chatInteraction.peerId, firstStableId: AnyHashable(large.resource.id.uniqueId), self, nil) + return + } + } + if isSingleLayout { - let point = convert(event.locationInWindow, from: nil) if point.x > 20 { - chatInteraction.openInfo(chatInteraction.peerId, false, nil, nil) + if chatInteraction.mode != .scheduled { + if chatInteraction.peerId == chatInteraction.context.peerId { + chatInteraction.context.sharedContext.bindings.rootNavigation().push(PeerMediaController(context: chatInteraction.context, peerId: chatInteraction.peerId)) + } else { + switch chatInteraction.chatLocation { + case let .peer(peerId): + chatInteraction.openInfo(peerId, false, nil, nil) + } + } + } + } else { - chatInteraction.account.context.mainNavigation?.back() + chatInteraction.context.sharedContext.bindings.rootNavigation().back() } } else { - chatInteraction.openInfo(chatInteraction.peerId, false, nil, nil) + if chatInteraction.peerId == chatInteraction.context.peerId { + chatInteraction.context.sharedContext.bindings.rootNavigation().push(PeerMediaController(context: chatInteraction.context, peerId: chatInteraction.peerId)) + } else { + switch chatInteraction.chatLocation { + case let .peer(peerId): + chatInteraction.openInfo(peerId, false, nil, nil) + } + } } } deinit { disposable.dispose() + fetchPeerAvatar.dispose() + videoAvatarDisposable.dispose() + } + + + override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) } override func layout() { super.layout() - let additionInset:CGFloat = isSingleLayout ? 20 : 0 + let additionInset:CGFloat = isSingleLayout ? 20 : 2 avatarControl.centerY(x: additionInset) searchButton.centerY(x:frame.width - searchButton.frame.width) @@ -291,6 +633,7 @@ class ChatTitleBarView: TitledBarView { badgeNode.view!.setFrameOrigin(6,4) closeButton.centerY() + } @@ -306,39 +649,95 @@ class ChatTitleBarView: TitledBarView { return 36 + 50 + (callButton.isHidden ? 20 : callButton.frame.width + 30) } + + private var currentRepresentations: [TelegramMediaImageRepresentation] = [] + + private func checkPhoto(_ peer: Peer?) { + if let peer = peer { + var representations:[TelegramMediaImageRepresentation] = []//peer.profileImageRepresentations + if let representation = peer.smallProfileImage { + representations.append(representation) + } + if let representation = peer.largeProfileImage { + representations.append(representation) + } + + if self.currentRepresentations != representations { + applyVideoAvatarIfNeeded(nil) + videoAvatarDisposable.set(peerPhotos(account: chatInteraction.context.account, peerId: chatInteraction.peerId, force: true).start()) + + + if let peerReference = PeerReference(peer) { + if let largeProfileImage = peer.largeProfileImage { + fetchPeerAvatar.add(fetchedMediaResource(mediaBox: chatInteraction.context.account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: largeProfileImage.resource)).start()) + } + if let smallProfileImage = peer.smallProfileImage { + fetchPeerAvatar.add(fetchedMediaResource(mediaBox: chatInteraction.context.account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource)).start()) + } + } + } + self.currentRepresentations = representations + } + } func updateStatus(_ force:Bool = false) { var shouldUpdateLayout = false - if let peerView = self.peerView { + if let peerView = self.postboxView as? PeerView { - if let peer = peerViewMainPeer(peerView) { - callButton.isHidden = !peer.canCall || chatInteraction.peerId == chatInteraction.account.peerId - } else { + checkPhoto(peerViewMainPeer(peerView)) + + switch chatInteraction.mode { + case .history: + if let peer = peerViewMainPeer(peerView) { + callButton.isHidden = !peer.canCall || chatInteraction.peerId == chatInteraction.context.peerId + } else { + callButton.isHidden = true + } + case .scheduled: callButton.isHidden = true } - // if let peer = peerView.peers[peerView.peerId] { - // searchButton.isHidden = peer is TelegramSecretChat - // } if let peer = peerViewMainPeer(peerView) { - avatarControl.setPeer(account: chatInteraction.account, peer: peer) + if peer.id == chatInteraction.context.peerId { + let icon = theme.icons.searchSaved + avatarControl.setSignal(generateEmptyPhoto(avatarControl.frame.size, type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(avatarControl.frame.size.width - 15, avatarControl.frame.size.height - 15)), cornerRadius: nil)) |> map {($0, false)}) + } else { + avatarControl.setPeer(account: chatInteraction.context.account, peer: peer) + } } if peerView.peers[peerView.peerId] is TelegramSecretChat { - titleImage = theme.icons.chatSecretTitle + titleImage = (theme.icons.chatSecretTitle, .left) + } else if let peer = peerViewMainPeer(peerView) { + if peer.isVerified { + titleImage = (theme.icons.verifiedImage, .right) + } else if peer.isScam { + titleImage = (theme.icons.scam, .right) + } else { + titleImage = nil + } } else { titleImage = nil } - var result = stringStatus(for: peerView, theme: PeerStatusStringTheme(titleFont: .medium(.title))) - if chatInteraction.account.peerId == peerView.peerId { - result = PeerStatusStringResult(result.title, .initialize(string: tr(.chatTitleSelf), color: theme.colors.grayText, font: .normal(.short)), presence: result.presence) + var result = stringStatus(for: peerView, context: chatInteraction.context, theme: PeerStatusStringTheme(titleFont: .medium(.title)), onlineMemberCount: self.onlineMemberCount) + + if chatInteraction.context.peerId == peerView.peerId { + if chatInteraction.mode == .scheduled { + result = result.withUpdatedTitle(L10n.chatTitleReminder) + } else { + result = result.withUpdatedTitle(L10n.peerSavedMessages) + } + } else if chatInteraction.mode == .scheduled { + result = result.withUpdatedTitle(L10n.chatTitleScheduledMessages) } - if status == nil || !status!.isEqual(to: result.status) || force { + if chatInteraction.context.peerId == peerView.peerId { + status = nil + } else if (status == nil || !status!.isEqual(to: result.status) || force) && chatInteraction.mode != .scheduled { status = result.status shouldUpdateLayout = true } @@ -349,33 +748,49 @@ class ChatTitleBarView: TitledBarView { } if let presence = result.presence { - self.presenceManager?.reset(presence: presence) + self.presenceManager?.reset(presence: presence, timeDifference: Int32(chatInteraction.context.timeDifference)) } if shouldUpdateLayout { self.setNeedsDisplay() } - } + } } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) searchButton.set(image: theme.icons.chatSearch, for: .Normal) - searchButton.sizeToFit() + searchButton.set(image: theme.icons.chatSearchActive, for: .Highlight) + + + _ = searchButton.sizeToFit() callButton.set(image: theme.icons.chatCall, for: .Normal) - callButton.sizeToFit() + _ = callButton.sizeToFit() closeButton.set(image: theme.icons.chatNavigationBack, for: .Normal) let inputActivities = self.inputActivities self.inputActivities = inputActivities - if let peerView = peerView, peerView.peers[peerView.peerId] is TelegramSecretChat { - titleImage = theme.icons.chatSecretTitle + if let peerView = postboxView as? PeerView { + if peerView.peers[peerView.peerId] is TelegramSecretChat { + titleImage = (theme.icons.chatSecretTitle, .left) + } else if peerView.peers[peerView.peerId] is TelegramSecretChat { + titleImage = (theme.icons.chatSecretTitle, .left) + } else if let peer = peerViewMainPeer(peerView) { + if peer.isVerified { + titleImage = (theme.icons.verifiedImage, .right) + } else if peer.isScam { + titleImage = (theme.icons.scam, .right) + } else { + titleImage = nil + } + } else { + titleImage = nil + } } else { titleImage = nil } - } } diff --git a/Telegram-Mac/ChatTouchBar.swift b/Telegram-Mac/ChatTouchBar.swift new file mode 100644 index 0000000000..a95c8930ba --- /dev/null +++ b/Telegram-Mac/ChatTouchBar.swift @@ -0,0 +1,650 @@ +// +// ChatTouchBar.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +@available(OSX 10.12.2, *) +extension NSTouchBar.CustomizationIdentifier { + static let windowBar = NSTouchBar.CustomizationIdentifier("\(Bundle.main.bundleIdentifier!).windowBar") + static let popoverBar = NSTouchBar.CustomizationIdentifier("\(Bundle.main.bundleIdentifier!).popoverBar") +} + + +@available(OSX 10.12.2, *) +private extension NSTouchBarItem.Identifier { + static let chatNextAndPrev = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.chatNextAndPrev") + + static let chatStickersAndEmojiPicker = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.StickerAndEmojiPicker") + + static let chatInfoAndAttach = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.chatInfoAndAttach") + static let markdown = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.markdown") + + static func chatInputAction(_ key:String) -> NSTouchBarItem.Identifier { + return NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.InputAction\(key)") + } + static let chatDeleteMessages = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.DeleteMessages") + static let chatForwardMessages = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.ForwardMessages") + + static let chatEditMessageDone = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.EditMessageDone") + static let chatEditMessageCancel = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.EditMessageCancel") + static let chatEditMessageUpdateMedia = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.EditMessage.UpdateMedia") + static let chatEditMessageUpdateFile = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.EditMessage.UpdateFile") + + static let chatSuggestStickers = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.chat.SuggestStickers") + +} +@available(OSX 10.12.2, *) +func inputChatTouchBarItems(presentation: ChatPresentationInterfaceState) -> [NSTouchBarItem.Identifier] { + if let result = presentation.inputQueryResult { + switch result { + case .stickers: + return [] + default: + break + } + } + + if presentation.state == .editing { + return [] + } else { + switch presentation.state { + case .normal: + return [.candidateList] + default: + return [] + } + } +} +@available(OSX 10.12.2, *) +func touchBarChatItems(presentation: ChatPresentationInterfaceState, layout: SplitViewState, isKeyWindow: Bool) -> (items: [NSTouchBarItem.Identifier], escapeReplacement: NSTouchBarItem.Identifier?) { + + if presentation.isSearchMode.0 { + return (items: [], escapeReplacement: nil) + } + if presentation.state == .editing { + var items: [NSTouchBarItem.Identifier] = [] + items.append(.chatEditMessageDone) + + if let editState = presentation.interfaceState.editState, let media = editState.message.media.first, media is TelegramMediaFile || media is TelegramMediaImage { + items.append(.flexibleSpace) + items.append(.chatEditMessageUpdateMedia) + if editState.message.groupingKey == nil { + items.append(.chatEditMessageUpdateFile) + } + items.append(.flexibleSpace) + } + if !presentation.effectiveInput.selectionRange.isEmpty { + items.append(.flexibleSpace) + items.append(.markdown) + items.append(.flexibleSpace) + } + if isKeyWindow { + items.append(.otherItemsProxy) + } + + return (items: items, escapeReplacement: .chatEditMessageCancel) + } else { + //if presentation.effectiveInput.inputText.isEmpty { + var items: [NSTouchBarItem.Identifier] = [] + if layout != .single { + // items.append(.chatNextAndPrev) + } + // items.append(.chatInfoAndSearch) + //items.append(.fixedSpaceSmall) + switch presentation.state { + case .normal: + // items.append(.characterPicker) + if let peer = presentation.peer, permissionText(from: peer, for: .banSendStickers) == nil { + items.append(.chatStickersAndEmojiPicker) + // items.append(.fixedSpaceSmall) + } + + + if let peer = presentation.peer, permissionText(from: peer, for: .banSendMedia) == nil { + // items.append(.flexibleSpace) + var appendAttachment: Bool = true + if let result = presentation.inputQueryResult { + switch result { + case .stickers: + if permissionText(from: peer, for: .banSendStickers) == nil { + items.append(.chatSuggestStickers) + appendAttachment = false + } + default: + break + } + } + if appendAttachment { + items.append(.chatInfoAndAttach) + } + items.append(.flexibleSpace) + } + + if !presentation.effectiveInput.selectionRange.isEmpty { + //items.append(.flexibleSpace) + items.append(.markdown) + items.append(.flexibleSpace) + } + + if isKeyWindow { + items.append(.otherItemsProxy) + } + + case .selecting: + items.append(.flexibleSpace) + items.append(.chatDeleteMessages) + items.append(.chatForwardMessages) + items.append(.flexibleSpace) + + case let .action(text, _): + if !(presentation.peer is TelegramSecretChat) { + items.append(.flexibleSpace) + items.append(.chatInputAction(text)) + items.append(.flexibleSpace) + } + case let .channelWithDiscussion(_, leftAction, rightAction): + items.append(.flexibleSpace) + items.append(.chatInputAction(leftAction)) + items.append(.chatInputAction(rightAction)) + items.append(.flexibleSpace) + default: + break + } + return (items: items, escapeReplacement: nil) + } +} + + + + +@available(OSX 10.12.2, *) +class ChatTouchBar: NSTouchBar, NSTouchBarDelegate, Notifable { + + private let loadStickersDisposable = MetaDisposable() + private let loadRecentEmojiDisposable = MetaDisposable() + + private var chatInteraction: ChatInteraction? + private var textView: NSTextView + private let candidateListItem = NSCandidateListTouchBarItem(identifier: .candidateList) + private let layoutStateDisposable = MetaDisposable() + init(chatInteraction: ChatInteraction, textView: NSTextView) { + self.chatInteraction = chatInteraction + self.textView = textView + super.init() + self.delegate = self + let result = touchBarChatItems(presentation: chatInteraction.presentation, layout: chatInteraction.context.sharedContext.layout, isKeyWindow: true) + self.defaultItemIdentifiers = result.items + self.escapeKeyReplacementItemIdentifier = result.escapeReplacement + self.customizationAllowedItemIdentifiers = self.defaultItemIdentifiers + self.textView.updateTouchBarItemIdentifiers() + self.customizationIdentifier = .windowBar + layoutStateDisposable.set(chatInteraction.context.sharedContext.layoutHandler.get().start(next: { [weak self] _ in + guard let `self` = self, let chatInteraction = self.chatInteraction else {return} + self.notify(with: chatInteraction.presentation, oldValue: chatInteraction.presentation, animated: true) + })) + } + + func updateChatInteraction(_ chatInteraction: ChatInteraction, textView: NSTextView) -> Void { + self.chatInteraction?.remove(observer: self) + prevIsKeyWindow = nil + chatInteraction.add(observer: self) + self.chatInteraction = chatInteraction + textView.updateTouchBarItemIdentifiers() + self.textView = textView + // self.notify(with: chatInteraction.presentation, oldValue: chatInteraction.presentation, animated: false) + } + + func updateByKeyWindow() { + if let chatInteraction = self.chatInteraction { + self.notify(with: chatInteraction.presentation, oldValue: chatInteraction.presentation, animated: false) + } + } + + func isEqual(to other: Notifable) -> Bool { + return false + } + + deinit { + chatInteraction?.remove(observer: self) + loadRecentEmojiDisposable.dispose() + loadStickersDisposable.dispose() + layoutStateDisposable.dispose() + } + private var prevIsKeyWindow: Bool? = nil + + func notify(with value: Any, oldValue: Any, animated: Bool) { + if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState, let chatInteraction = self.chatInteraction { + if !animated || oldValue.state != value.state || oldValue.effectiveInput.selectionRange.isEmpty != value.effectiveInput.selectionRange.isEmpty || prevIsKeyWindow != textView.window?.isKeyWindow || oldValue.inputQueryResult != value.inputQueryResult || oldValue.selectionState != value.selectionState || oldValue.canInvokeBasicActions != value.canInvokeBasicActions { + self.prevIsKeyWindow = textView.window?.isKeyWindow + let result = touchBarChatItems(presentation: value, layout: chatInteraction.context.sharedContext.layout, isKeyWindow: textView.window?.isKeyWindow ?? false) + self.defaultItemIdentifiers = result.items + self.escapeKeyReplacementItemIdentifier = result.escapeReplacement + self.customizationAllowedItemIdentifiers = self.defaultItemIdentifiers + self.textView.updateTouchBarItemIdentifiers() + updateUserInterface() + } + } + } + + + @objc private func chatInfoAction() { + guard let item = self.item(forIdentifier: .chatInfoAndAttach) as? NSPopoverTouchBarItem, let chatInteraction = self.chatInteraction else {return} + item.popoverTouchBar = ChatInfoTouchbar(chatInteraction: chatInteraction, dismiss: { [weak item] in + item?.dismissPopover(nil) + }) + item.showPopover(item) + } + @objc private func searchAction() { + chatInteraction?.update({$0.updatedSearchMode((!$0.isSearchMode.0, nil, nil))}) + } + + @objc private func attachPhotoOrVideo() { + chatInteraction?.attachPhotoOrVideo() + } + @objc private func attachPicture() { + chatInteraction?.attachPicture() + } + @objc private func attachFile() { + chatInteraction?.attachFile(false) + } + @objc private func attachLocation() { + chatInteraction?.attachLocation() + } + @objc private func invokeInputAction(_ sender: Any?) { + if let chatInteraction = self.chatInteraction { + switch chatInteraction.presentation.state { + case .action(_, let action): + action(chatInteraction) + case let .channelWithDiscussion(_, leftAction, rightAction): + if let sender = sender as? NSButton { + switch sender.title { + case leftAction: + chatInteraction.toggleNotifications(nil) + case rightAction: + chatInteraction.openDiscussion() + default: + break + } + } + default: + break + } + } + + } + + private func showEmojiPickerPopover(recent: [String], segments: [EmojiSegment : [String]]) { + guard let item = self.item(forIdentifier: .chatStickersAndEmojiPicker) as? NSPopoverTouchBarItem else {return} + + item.popoverTouchBar = TouchBarEmojiPicker(recent: recent, segments: segments, selectedEmoji: { [weak self, weak item] emoji in + guard let chatInteraction = self?.chatInteraction else {return} + if chatInteraction.presentation.effectiveInput.inputText.isEmpty { + item?.dismissPopover(nil) + } + _ = chatInteraction.appendText(emoji) + }) + item.showPopover(item) + } + + private func showStickersPopover(_ itemCollectionView: ItemCollectionsView) { + guard let item = self.item(forIdentifier: .chatStickersAndEmojiPicker) as? NSPopoverTouchBarItem, let chatInteraction = self.chatInteraction else {return} + var stickers: (favorite: [TelegramMediaFile], recent: [TelegramMediaFile], packs: [(StickerPackCollectionInfo, [TelegramMediaFile])]) = (favorite: [], recent: [], packs: []) + + stickers.favorite = Array(itemCollectionView.orderedItemListsViews[0].items.compactMap {($0.contents as? SavedStickerItem)?.file}.prefix(5)) + stickers.recent = Array(itemCollectionView.orderedItemListsViews[1].items.compactMap {($0.contents as? RecentMediaItem)?.media as? TelegramMediaFile}.prefix(20)) + + var collections: [ItemCollectionId : [TelegramMediaFile]] = [:] + + for entry in itemCollectionView.entries { + var collection = collections[entry.index.collectionId] + if collection == nil { + collection = [] + collections[entry.index.collectionId] = collection + } + if let item = entry.item as? StickerPackItem { + collections[entry.index.collectionId]?.append(item.file) + } + } + + for (key, value) in collections { + let info = itemCollectionView.collectionInfos.first(where: {$0.0 == key}) + if let info = info?.1 as? StickerPackCollectionInfo { + stickers.packs.append((info, value)) + } + } + + var entries: [TouchBarStickerEntry] = [] + if !stickers.favorite.isEmpty { + let layout = TextViewLayout(.initialize(string: L10n.touchBarFavorite, color: .grayText, font: .normal(.header))) + layout.measure(width: .greatestFiniteMagnitude) + entries.append(.header(layout)) + entries.append(contentsOf: stickers.favorite.map {.sticker($0)}) + } + if !stickers.recent.isEmpty { + let layout = TextViewLayout(.initialize(string: L10n.touchBarRecent, color: .grayText, font: .normal(.header))) + layout.measure(width: .greatestFiniteMagnitude) + entries.append(.header(layout)) + entries.append(contentsOf: stickers.recent.map {.sticker($0)}) + } + for pack in stickers.packs { + let layout = TextViewLayout(.initialize(string: "\(pack.0.title)", color: .grayText, font: .normal(.header))) + layout.measure(width: .greatestFiniteMagnitude) + entries.append(.header(layout)) + entries.append(contentsOf: pack.1.map {.sticker($0)}) + } + + item.popoverTouchBar = ChatStickersTouchBarPopover(chatInteraction: chatInteraction, dismiss: { [weak item, weak self] file in + if let file = file { + self?.chatInteraction?.sendAppFile(file, false) + } + item?.dismissPopover(nil) + }, entries: entries) + item.showPopover(item) + } + + + @objc private func openEmojiOrStickersPicker(_ sender: Any?) { + if let segmentControl = sender as? NSSegmentedControl, let chatInteraction = self.chatInteraction { + switch segmentControl.selectedSegment { + case 0: + loadRecentEmojiDisposable.set((recentUsedEmoji(postbox: chatInteraction.context.account.postbox) |> deliverOnPrepareQueue |> map { ($0, emojiesInstance)} |> take(1) |> deliverOnMainQueue).start(next: { [weak self] recent, segments in + self?.showEmojiPickerPopover(recent: recent.emojies, segments: segments) + })) + case 1: + loadStickersDisposable.set((chatInteraction.context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 200) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] itemCollectionView in + self?.showStickersPopover(itemCollectionView) + })) + default: + break + } + } + + } + + @objc private func forwardMessages() { + chatInteraction?.forwardSelectedMessages() + } + @objc private func deleteMessages() { + chatInteraction?.deleteSelectedMessages() + } + + @objc private func saveEditingMessage() { + chatInteraction?.sendMessage(false, nil) + } + @objc private func replaceWithFile() { + chatInteraction?.updateEditingMessageMedia(nil, false) + } + @objc private func replaceWithMedia() { + chatInteraction?.updateEditingMessageMedia(mediaExts, true) + } + @objc private func cancelMessageEditing() { + chatInteraction?.cancelEditing() + } + @objc private func infoAndAttach(_ sender: Any?) { + + if let segmentControl = sender as? NSSegmentedControl { + switch segmentControl.selectedSegment { + case 1: + chatInfoAction() + case 0: + attachFile() + default: + break + } + } + } + @objc private func upOrNext(_ sender: Any?) { + if let segmentControl = sender as? NSSegmentedControl { + switch segmentControl.selectedSegment { + case 0: + mainWindow.sendKeyEvent(KeyboardKey.Tab, modifierFlags: [.control, .shift]) + case 1: + mainWindow.sendKeyEvent(KeyboardKey.Tab, modifierFlags: [.control]) + default: + break + } + } + } + @objc private func markdown(_ sender: Any?) { + if let segmentControl = sender as? NSSegmentedControl { + switch segmentControl.selectedSegment { + case 0: + mainWindow.sendKeyEvent(KeyboardKey.B, modifierFlags: [.command]) + case 1: + mainWindow.sendKeyEvent(KeyboardKey.I, modifierFlags: [.command]) + case 2: + mainWindow.sendKeyEvent(KeyboardKey.U, modifierFlags: [.command]) + default: + break + } + } + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + +// let actionKey: String +// switch chatInteraction.presentation.state { +// case let .action(title, _): +// actionKey = title +// default: +// actionKey = "" +// } + + if let range = identifier.rawValue.range(of: NSTouchBarItem.Identifier.chatInputAction("").rawValue) { + let actionKey = String(identifier.rawValue[range.upperBound...]) + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: actionKey, target: self, action: #selector(invokeInputAction(_:))) + button.addWidthConstraint(size: 200) + button.bezelColor = actionKey == L10n.chatInputMute || actionKey == L10n.chatInputUnmute ? nil : theme.colors.accent + item.view = button + item.customizationLabel = button.title + return item + } + + switch identifier { + case .chatNextAndPrev: + let item = NSPopoverTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 2 + segment.setImage(NSImage(named: NSImage.touchBarGoUpTemplateName)!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.touchBarGoDownTemplateName)!, forSegment: 1) + segment.setWidth(93, forSegment: 0) + segment.setWidth(93, forSegment: 1) + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(upOrNext(_:)) + item.collapsedRepresentation = segment + return item +// case .chatInfoAndSearch: +// let item = NSPopoverTouchBarItem(identifier: identifier) +// +// let segment = NSSegmentedControl() +// segment.segmentStyle = .separated +// segment.segmentCount = 2 +// segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_Info"))!, forSegment: 0) +// segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_Search"))!, forSegment: 1) +// segment.setWidth(93, forSegment: 0) +// segment.setWidth(93, forSegment: 1) +// segment.trackingMode = .momentary +// segment.target = self +// segment.action = #selector(infoOrSearchAction(_:)) +// item.collapsedRepresentation = segment +// return item + case .chatStickersAndEmojiPicker: + + let item = NSPopoverTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 2 + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_Emoji"))!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_Stickers"))!, forSegment: 1) + segment.setWidth(92, forSegment: 0) + segment.setWidth(92, forSegment: 1) + segment.target = self + segment.action = #selector(openEmojiOrStickersPicker(_:)) + segment.trackingMode = .momentary + item.visibilityPriority = .high + item.collapsedRepresentation = segment + item.customizationLabel = L10n.touchBarLabelEmojiAndStickers; + return item + +// let item = NSPopoverTouchBarItem(identifier: identifier) +// +// let icon = NSImage(named: NSImage.Name("Icon_TouchBar_Stickers"))! +// let button = NSButton(image: icon, target: self, action: #selector(loadStickers)) +// +// item.collapsedRepresentation = button +// item.customizationLabel = button.title +// return item + + case .chatInfoAndAttach: + let item = NSPopoverTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 2 + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_ChatAttach"))!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_ChatMore"))!, forSegment: 1) + segment.setWidth(98, forSegment: 0) + segment.setWidth(98, forSegment: 1) + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(infoAndAttach(_:)) + item.collapsedRepresentation = segment + item.customizationLabel = L10n.touchBarLabelChatActions; + return item + case .markdown: + let item = NSPopoverTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 3 + segment.setImage(NSImage(named: NSImage.touchBarTextBoldTemplateName)!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.touchBarTextItalicTemplateName)!, forSegment: 1) + segment.setImage(NSImage(named: NSImage.Name("Icon_ChatTouchBarAddLink"))!, forSegment: 2) + + +// segment.setWidth(98, forSegment: 0) +// segment.setWidth(98, forSegment: 1) + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(markdown(_:)) + item.collapsedRepresentation = segment + return item + case .chatEditMessageDone: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: L10n.navigationDone, target: self, action: #selector(saveEditingMessage)) + button.bezelColor = theme.colors.accent + item.view = button + item.customizationLabel = button.title + return item + case .chatEditMessageUpdateMedia: + let item = NSCustomTouchBarItem(identifier: identifier) + let icon = NSImage(named: NSImage.Name("Icon_TouchBar_AttachPhotoOrVideo"))! + let button = NSButton(title: L10n.touchBarEditMessageReplaceWithMedia, image: icon, target: self, action: #selector(replaceWithMedia)) + button.imageHugsTitle = true + item.view = button + item.customizationLabel = button.title + return item + case .chatEditMessageUpdateFile: + let item = NSCustomTouchBarItem(identifier: identifier) + let icon = NSImage(named: NSImage.Name("Icon_TouchBar_AttachFile"))! + let button = NSButton(title: L10n.touchBarEditMessageReplaceWithFile, image: icon, target: self, action: #selector(replaceWithFile)) + button.imageHugsTitle = true + item.view = button + item.customizationLabel = button.title + return item + case .chatEditMessageDone: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: L10n.navigationDone, target: self, action: #selector(attachFile)) + button.bezelColor = theme.colors.accent + item.view = button + item.customizationLabel = button.title + return item + case .chatEditMessageCancel: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: L10n.navigationCancel, target: self, action: #selector(cancelMessageEditing)) + item.view = button + item.customizationLabel = button.title + return item +// case chatInputAction(actionKey): +// let item = NSCustomTouchBarItem(identifier: identifier) +// let button = NSButton(title: actionKey, target: self, action: #selector(invokeInputAction)) +// button.addWidthConstraint(size: 200) +// button.bezelColor = actionKey == L10n.chatInputMute || actionKey == L10n.chatInputUnmute ? nil : theme.colors.accent +// item.view = button +// item.customizationLabel = button.title +// return item + case .chatForwardMessages: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: L10n.messageActionsPanelForward, target: self, action: #selector(forwardMessages)) + button.addWidthConstraint(size: 160) + button.bezelColor = theme.colors.accent + button.imageHugsTitle = true + button.isEnabled = self.chatInteraction?.presentation.canInvokeBasicActions.forward ?? false + item.view = button + item.customizationLabel = button.title + return item + case .chatDeleteMessages: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: L10n.messageActionsPanelDelete, target: self, action: #selector(deleteMessages)) + button.addWidthConstraint(size: 160) + button.bezelColor = theme.colors.redUI + button.imageHugsTitle = true + button.isEnabled = self.chatInteraction?.presentation.canInvokeBasicActions.delete ?? false + item.view = button + item.customizationLabel = button.title + return item + case .chatSuggestStickers: + if let result = self.chatInteraction?.presentation.inputQueryResult, let chatInteraction = self.chatInteraction { + switch result { + case let .stickers(stickers): + return StickersScrubberBarItem(identifier: identifier, context: chatInteraction.context, animated: false, sendSticker: { [weak self] file in + self?.chatInteraction?.sendAppFile(file, false) + self?.chatInteraction?.clearInput() + }, entries: stickers.map({.sticker($0.file)})) + default: + break + } + } + + default: + break + } + return nil + } + + private func updateUserInterface() { + for identifier in itemIdentifiers { + switch identifier { + case .chatForwardMessages: + let button = (item(forIdentifier: identifier) as? NSCustomTouchBarItem)?.view as? NSButton + button?.bezelColor = self.chatInteraction?.presentation.canInvokeBasicActions.forward ?? false ? theme.colors.accent : nil + button?.isEnabled = self.chatInteraction?.presentation.canInvokeBasicActions.forward ?? false + + case .chatDeleteMessages: + let button = (item(forIdentifier: identifier) as? NSCustomTouchBarItem)?.view as? NSButton + button?.bezelColor = self.chatInteraction?.presentation.canInvokeBasicActions.delete ?? false ? theme.colors.redUI : nil + button?.isEnabled = self.chatInteraction?.presentation.canInvokeBasicActions.delete ?? false + default: + break + } + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ChatUndoManager.swift b/Telegram-Mac/ChatUndoManager.swift new file mode 100644 index 0000000000..c4484bbec7 --- /dev/null +++ b/Telegram-Mac/ChatUndoManager.swift @@ -0,0 +1,458 @@ +// +// ChatUndoManager.swift +// Telegram +// +// Created by Mikhail Filimonov on 09/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private let queue: Queue = Queue() + + +enum ChatUndoActionType : Equatable { + case clearHistory + case deleteChat + case deleteChannel + case leftChat + case leftChannel + case archiveChat +} + + +enum ChatUndoActionStatus : Equatable { + case processing + case success + case cancelled + case none +} + +private final class ChatUndoActionStatusContext { + var status: ChatUndoActionStatus? { + didSet { + for subscriber in subscribers.copyItems() { + subscriber(status) + } + } + } + let subscribers = Bag<(ChatUndoActionStatus?) -> Void>() +} + +private struct ChatUndoActionKey : Hashable { + private let peerId: PeerId + private let type: ChatUndoActionType + init(peerId: PeerId, type: ChatUndoActionType) { + self.peerId = peerId + self.type = type + } + var hashValue: Int { + return Int(peerId.toInt64()) + } +} + +struct ChatUndoAction : Hashable { + static func == (lhs: ChatUndoAction, rhs: ChatUndoAction) -> Bool { + return lhs.peerId == rhs.peerId && lhs.type == rhs.type + } + fileprivate let endpoint: Double + fileprivate let duration: Double + fileprivate let peerId: PeerId + let type: ChatUndoActionType + fileprivate let action: (ChatUndoActionStatus) -> Void + init(peerId: PeerId, type: ChatUndoActionType, duration: Double = 5, action: @escaping(ChatUndoActionStatus) -> Void = { _ in}) { + self.peerId = peerId + self.type = type + self.action = action + self.duration = duration + self.endpoint = Date().timeIntervalSince1970 + duration + } + + + func withUpdatedEndpoint(_ endpoint: Double) -> ChatUndoAction { + return ChatUndoAction.init(peerId: self.peerId, type: self.type, duration: endpoint - Date().timeIntervalSince1970, action: self.action) + } + + func isEqual(with peerId: PeerId, type: ChatUndoActionType) -> Bool { + return self.peerId == peerId && self.type == type + } + + var hashValue: Int { + return Int(peerId.toInt64()) + } +} + +struct ChatUndoStatuses { + private let statuses: [ChatUndoAction : ChatUndoActionStatus] + fileprivate init(_ statuses: [ChatUndoAction : ChatUndoActionStatus]) { + self.statuses = statuses + } + + func contains(peerId: PeerId, type: ChatUndoActionType) -> Bool { + return statuses.first(where: { key, _ -> Bool in + return key.isEqual(with: peerId, type: type) + }) != nil + } + + func isActive(peerId: PeerId, types: [ChatUndoActionType]) -> Bool { + for type in types { + let result = statuses.first(where: { current -> Bool in + return current.key.isEqual(with: peerId, type: type) && (current.value == .processing || (current.value == .success)) + }) != nil + + if result { + return result + } + } + return false + } + + func status(for peerId: PeerId, type: ChatUndoActionType) -> ChatUndoActionStatus? { + return statuses.first(where: { key, _ -> Bool in + return key.isEqual(with: peerId, type: type) + })?.value + } + + var hasProcessingActions: Bool { + return !statuses.filter ({ _, value in + return value == .processing + }).isEmpty + } + + var maximumDuration: Double { + var max: Double = 0 + for (action, value) in statuses { + if value == .processing, max < action.duration { + max = action.duration + } + } + return max + } + + var actionsCount: Int { + return statuses.filter {$0.value == .processing}.count + } + + var endpoint: Double { + var max: Double = 0 + for (action, value) in statuses { + if value == .processing, max < action.duration { + max = action.endpoint + } + } + return max + } + var secondsUntilFinish: Double { + return endpoint - Date().timeIntervalSince1970 + } + + var activeDescription: String { + let clearingCount = statuses.filter {$0.key.type == .clearHistory && $0.value == .processing}.count + let deleteCount = statuses.filter {$0.key.type == .deleteChat && $0.value == .processing}.count + let deleteChannelCount = statuses.filter {$0.key.type == .deleteChannel && $0.value == .processing}.count + + let leftChatCount = statuses.filter {$0.key.type == .leftChat && $0.value == .processing}.count + let leftChannelCount = statuses.filter {$0.key.type == .leftChannel && $0.value == .processing}.count + let archiveChatCount = statuses.filter {$0.key.type == .archiveChat && $0.value == .processing}.count + + + var text: String = "" + + if archiveChatCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChatsArchivedCountable(archiveChatCount) + } + + if leftChatCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChatLeftCountable(leftChatCount) + } + + if leftChannelCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChannelLeftCountable(leftChannelCount) + } + if deleteCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChatsDeletedCountable(deleteCount) + } + if deleteChannelCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChannelDeletedCountable(deleteChannelCount) + } + if clearingCount > 0 { + if !text.isEmpty { + text += ", " + } + text += L10n.chatUndoManagerChatsHistoryClearedCountable(clearingCount) + } + return text + } +} + +private final class ChatUndoManagerContext { + private let disposableDict: DisposableDict = DisposableDict() + private var actions: Set = Set() + private var statuses:[ChatUndoAction : ChatUndoActionStatusContext] = [:] + private let allSubscribers = Bag<(ChatUndoStatuses) -> Void>() + init() { + + } + + deinit { + disposableDict.dispose() + } + + private func restartProcessingActions(_ except: ChatUndoAction) { + self.actions = Set(self.actions.map { action in + if action == except { + return action + } else { + return action.withUpdatedEndpoint(except.endpoint) + } + }) + + for action in self.actions { + if except != action { + run(for: action) + } + } + } + + func add(action: ChatUndoAction) { + if let previous = actions.first(where: { $0 == action }) { + previous.action(.cancelled) + } + + actions.insert(action) + if statuses[action] == nil { + statuses[action] = ChatUndoActionStatusContext() + } + + statuses[action]?.status = .processing + + + restartProcessingActions(action) + notifyAllSubscribers() + run(for: action) + } + + private func run(for action: ChatUndoAction) { + disposableDict.set((Signal.complete() |> delay(action.endpoint - Date().timeIntervalSince1970, queue: queue)).start(completed: { [weak self] in + self?.statuses[action]?.status = .success + self?.notifyAllSubscribers() + action.action(.success) + }), forKey: action) + } + + + + func cancel(action: ChatUndoAction) { + actions.remove(action) + statuses[action]?.status = .cancelled + notifyAllSubscribers() + action.action(.cancelled) + disposableDict.set(nil, forKey: action) + } + + private func notifyAllSubscribers() { + var values:[ChatUndoAction : ChatUndoActionStatus] = [:] + + for action in self.actions { + if let status = self.statuses[action]?.status { + values[action] = status + } + } + for subscribers in allSubscribers.copyItems() { + subscribers(ChatUndoStatuses(values)) + } + } + + private func status(for peerId: PeerId, type: ChatUndoActionType) -> ChatUndoAction? { + return actions.first(where: {$0.isEqual(with: peerId, type: type)}) + } + + func status(for peerId: PeerId, type: ChatUndoActionType) -> Signal { + return Signal { [weak self] subscriber -> Disposable in + + let keyAction = ChatUndoAction(peerId: peerId, type: type, action: {_ in}) + + if self?.statuses[keyAction] == nil { + self?.statuses[keyAction] = ChatUndoActionStatusContext() + } + + let index = self?.statuses[keyAction]?.subscribers.add { status in + subscriber.putNext(status) + } + subscriber.putNext(self?.statuses[keyAction]?.status) + + return ActionDisposable { [weak self] in + if let index = index, let status = self?.statuses[keyAction] { + status.subscribers.remove(index) + if status.subscribers.copyItems().count == 0 { + self?.statuses.removeValue(forKey: keyAction) + } + } + } + } + } + + func cancelAll() { + for action in actions.reversed() { + if statuses[action] == nil { + disposableDict.set(nil, forKey: action) + statuses[action]?.status = .cancelled + action.action(.cancelled) + actions.remove(action) + } else if let status = statuses[action], status.status == .processing { + disposableDict.set(nil, forKey: action) + statuses[action]?.status = .cancelled + action.action(.cancelled) + actions.remove(action) + } + } + notifyAllSubscribers() + } + + func allStatuses() -> Signal { + return Signal { [weak self] subscriber -> Disposable in + + guard let `self` = self else { return EmptyDisposable } + + var values:[ChatUndoAction : ChatUndoActionStatus] = [:] + + for action in self.actions { + if let status = self.statuses[action]?.status { + values[action] = status + } + } + + let index = self.allSubscribers.add { statuses in + subscriber.putNext(statuses) + } + + subscriber.putNext(ChatUndoStatuses(values)) + + return ActionDisposable { [weak self] in + self?.allSubscribers.remove(index) + } + } + } + + fileprivate func finishAction(for peerId: PeerId, type: ChatUndoActionType) { + let keyAction = ChatUndoAction(peerId: peerId, type: type) + statuses[keyAction]?.status = nil + actions.remove(keyAction) + disposableDict.set(nil, forKey: keyAction) + notifyAllSubscribers() + } + fileprivate func invokeNow(for peerId: PeerId, type: ChatUndoActionType) { + let keyAction = ChatUndoAction(peerId: peerId, type: type) + if let action = actions.first(where: {$0 == keyAction}) { + statuses[keyAction]?.status = .success + action.action(.success) + self.actions.remove(action) + disposableDict.set(nil, forKey: keyAction) + notifyAllSubscribers() + } + } + + fileprivate func invokeAll() { + let actions = self.actions + for action in actions { + invokeNow(for: action.peerId, type: action.type) + } + } +} + + + +final class ChatUndoManager { + + private let context: ChatUndoManagerContext + init() { + context = ChatUndoManagerContext() + } + + func cancel(action: ChatUndoAction) { + queue.async { [weak context] in + context?.cancel(action: action) + } + } + + func cancelAll() -> Void { + queue.async { [weak context] in + context?.cancelAll() + } + } + + func add(action: ChatUndoAction) { + queue.async { [weak context] in + context?.add(action: action) + } + } + + func allStatuses() -> Signal { + var status: Signal = .complete() + queue.sync { + status = context.allStatuses() + } + return status + } + + func status(for peerId: PeerId, type: ChatUndoActionType) -> Signal { + var status: Signal = .complete() + queue.sync { + status = context.status(for: peerId, type: type) + } + return status + } + + func clearHistoryInteractively(postbox: Postbox, peerId: PeerId, type: InteractiveHistoryClearingType = .forLocalPeer) { + _ = TelegramCore.clearHistoryInteractively(postbox: postbox, peerId: peerId, type: type).start(completed: { [weak context] in + queue.async { + context?.finishAction(for: peerId, type: .clearHistory) + } + }) + } + func removePeerChat(account: Account, peerId: PeerId, type: ChatUndoActionType, reportChatSpam: Bool, deleteGloballyIfPossible: Bool = false) { + _ = TelegramCore.removePeerChat(account: account, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: deleteGloballyIfPossible).start(completed: { [weak context] in + queue.async { + context?.finishAction(for: peerId, type: type) + } + }) + } + + func invokeNow(for peerId: PeerId, type: ChatUndoActionType) { + queue.sync { [weak context] in + context?.invokeNow(for: peerId, type: .clearHistory) + } + } + + func invokeAll() { + queue.sync { [weak context] in + context?.invokeAll() + } + } +} + + +func enqueueMessages(context: AccountContext, peerId: PeerId, messages: [EnqueueMessage]) -> Signal<[MessageId?], NoError> { + context.chatUndoManager.invokeNow(for: peerId, type: .clearHistory) + return TelegramCore.enqueueMessages(account: context.account, peerId: peerId, messages: messages) +} diff --git a/Telegram-Mac/ChatUnreadRowItem.swift b/Telegram-Mac/ChatUnreadRowItem.swift index 991ca659f7..3853f58b72 100644 --- a/Telegram-Mac/ChatUnreadRowItem.swift +++ b/Telegram-Mac/ChatUnreadRowItem.swift @@ -8,30 +8,34 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChatUnreadRowItem: ChatRowItem { override var height: CGFloat { - return 20 + return 32 } + override var canBeAnchor: Bool { + return false + } public var text:NSAttributedString; - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account:Account, _ entry:ChatHistoryEntry) { + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ entry:ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { let titleAttr:NSMutableAttributedString = NSMutableAttributedString() - let _ = titleAttr.append(string:tr(.messagesUnreadMark), color: theme.colors.grayText, font: .normal(.text)) + let _ = titleAttr.append(string:tr(L10n.messagesUnreadMark), color: theme.colors.grayText, font: .normal(.text)) text = titleAttr.copy() as! NSAttributedString - super.init(initialSize,chatInteraction,entry) + super.init(initialSize,chatInteraction,entry, downloadSettings, theme: theme) } override var messageIndex:MessageIndex? { switch entry { - case .UnreadEntry(let index): + case .UnreadEntry(let index, _): return index default: break @@ -39,8 +43,61 @@ class ChatUnreadRowItem: ChatRowItem { return super.messageIndex } + override var instantlyResize: Bool { + return true + } + override func viewClass() -> AnyClass { return ChatUnreadRowView.self } } + +private class ChatUnreadRowView: TableRowView { + + private var text:TextNode = TextNode() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.layerContentsRedrawPolicy = .onSetNeedsDisplay + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ dirtyRect: NSRect) { + + // Drawing code here. + } + + override func updateColors() { + layer?.backgroundColor = .clear + } + + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + needsDisplay = true + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + + + ctx.setFillColor(theme.colors.grayBackground.cgColor) + ctx.fill(NSMakeRect(0, 6, frame.width, frame.height - 12)) + + if let item = self.item as? ChatUnreadRowItem { + let (layout, apply) = TextNode.layoutText(maybeNode: text, item.text, nil, 1, .end, NSMakeSize(NSWidth(self.frame), NSHeight(self.frame)), nil,false, .left) + apply.draw(NSMakeRect(round((NSWidth(layer.bounds) - layout.size.width)/2.0), round((NSHeight(layer.bounds) - layout.size.height)/2.0), layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + } + + deinit { + var bp:Int = 0 + bp += 1 + } + +} diff --git a/Telegram-Mac/ChatUnreadRowView.swift b/Telegram-Mac/ChatUnreadRowView.swift index b40b9794cf..afdafb5b79 100644 --- a/Telegram-Mac/ChatUnreadRowView.swift +++ b/Telegram-Mac/ChatUnreadRowView.swift @@ -8,32 +8,4 @@ import Cocoa import TGUIKit -class ChatUnreadRowView: TableRowView { - - private var text:TextNode = TextNode() - override func draw(_ dirtyRect: NSRect) { - - // Drawing code here. - } - - - override func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(theme.colors.grayBackground.cgColor) - - ctx.fill(self.bounds) - - - if let item = self.item as? ChatUnreadRowItem { - let (layout, apply) = TextNode.layoutText(maybeNode: text, item.text, nil, 1, .end, NSMakeSize(NSWidth(self.frame), NSHeight(self.frame)), nil,false, .left) - apply.draw(NSMakeRect(round((NSWidth(layer.bounds) - layout.size.width)/2.0), round((NSHeight(layer.bounds) - layout.size.height)/2.0), layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } - - } - - deinit { - var bp:Int = 0 - bp += 1 - } - -} diff --git a/Telegram-Mac/ChatUrlPreviewModel.swift b/Telegram-Mac/ChatUrlPreviewModel.swift index 45bb366f38..429edd3995 100644 --- a/Telegram-Mac/ChatUrlPreviewModel.swift +++ b/Telegram-Mac/ChatUrlPreviewModel.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore class ChatUrlPreviewModel: ChatAccessoryModel { private let webpageDisposable = MetaDisposable() @@ -30,9 +31,10 @@ class ChatUrlPreviewModel: ChatAccessoryModel { private func updateWebpage() { var authorName = "" var text = "" + var isEmptyText: Bool = false switch self.webpage.content { case .Pending: - authorName = "Loading..." + authorName = L10n.chatInlineRequestLoading text = self.url case let .Loaded(content): if let title = content.websiteName { @@ -42,11 +44,14 @@ class ChatUrlPreviewModel: ChatAccessoryModel { } else { authorName = content.displayUrl } - text = content.text ?? content.title ?? "" + if content.text == nil && content.title == nil { + isEmptyText = true + } + text = content.text ?? content.title ?? L10n.chatEmptyLinkPreview } - self.headerAttr = .initialize(string: authorName, color: theme.colors.link, font: .medium(.text)) - self.messageAttr = .initialize(string: text, color: theme.colors.text, font: .normal(.text)) + self.headerAttr = .initialize(string: authorName, color: theme.colors.accent, font: .medium(.text)) + self.messageAttr = .initialize(string: text, color: isEmptyText ? theme.colors.grayText : theme.colors.text, font: .normal(.text)) nodeReady.set(.single(true)) self.setNeedDisplay() diff --git a/Telegram-Mac/ChatUserPopover.swift b/Telegram-Mac/ChatUserPopover.swift deleted file mode 100644 index 51f69fa911..0000000000 --- a/Telegram-Mac/ChatUserPopover.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// ChatUserPopover.swift -// Telegram -// -// Created by keepcoder on 5/6/17. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - - - - - -private class ChatUserPopoverView : View { - private let avatar:AvatarControl = AvatarControl(font: .avatar(.text)) - private let nameView:TextView = TextView() - private let lastSeen:TextView = TextView() - private let callButton: ImageButton = ImageButton() - private let messageButton:ImageButton = ImageButton() - private let infoButton:ImageButton = ImageButton() - private let buttonsContainer:View = View() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - avatar.setFrameSize(NSMakeSize(40, 40)) - addSubview(avatar) - addSubview(nameView) - addSubview(lastSeen) - addSubview(buttonsContainer) - buttonsContainer.addSubview(callButton) - buttonsContainer.addSubview(messageButton) - buttonsContainer.addSubview(infoButton) - - - callButton.set(image: #imageLiteral(resourceName: "Icon_DetailedCall").precomposed(.blueUI), for: .Normal) - callButton.setFrameSize(NSMakeSize(26, 26)) - callButton.layer?.cornerRadius = 13 - callButton.layer?.borderColor = NSColor(0xe7e7ec).cgColor - callButton.layer?.borderWidth = 1 - callButton.set(background: NSColor(0xf8f8fe), for: .Normal) - - - messageButton.set(image: #imageLiteral(resourceName: "Icon_DetailedMessage").precomposed(.blueUI), for: .Normal) - messageButton.setFrameSize(NSMakeSize(26, 26)) - messageButton.layer?.cornerRadius = 13 - messageButton.layer?.borderColor = NSColor(0xe7e7ec).cgColor - messageButton.layer?.borderWidth = 1 - messageButton.set(background: NSColor(0xf8f8fe), for: .Normal) - - - infoButton.set(image: #imageLiteral(resourceName: "Icon_DetailedInfo").precomposed(.blueUI), for: .Normal) - infoButton.setFrameSize(NSMakeSize(26, 26)) - infoButton.layer?.cornerRadius = 13 - infoButton.layer?.borderColor = NSColor(0xe7e7ec).cgColor - infoButton.layer?.borderWidth = 1 - infoButton.set(background: NSColor(0xf8f8fe), for: .Normal) - - messageButton.setFrameOrigin(0, 0) - callButton.setFrameOrigin(messageButton.frame.maxX + 20, 0) - infoButton.setFrameOrigin(callButton.frame.maxX + 20, 0) - - buttonsContainer.setFrameSize(infoButton.frame.maxX, 26) - - nameView.userInteractionEnabled = false - lastSeen.userInteractionEnabled = false - } - - override func layout() { - super.layout() - avatar.setFrameOrigin(10, 10) - nameView.setFrameOrigin(avatar.frame.maxX + 10, 10 + 20 - nameView.frame.height - 2) - lastSeen.setFrameOrigin(avatar.frame.maxX + 10, 10 + 20 + 2) - - buttonsContainer.centerX(y: 55) - - - } - - func update(with peerView:PeerView, account:Account) { - if let peer = peerViewMainPeer(peerView) { - avatar.setPeer(account: account, peer: peer) - let result = stringStatus(for: peerView) - let statusLayout = TextViewLayout(result.status, maximumNumberOfLines: 1) - let titleLayout = TextViewLayout(result.title, maximumNumberOfLines: 1) - statusLayout.measure(width: frame.width - 80) - titleLayout.measure(width: frame.width - 80) - nameView.update(titleLayout) - lastSeen.update(statusLayout) - } - - needsLayout = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private class ChatUserPopover: NSObject { - private let controller:NSViewController = NSViewController() - private let ready:Promise = Promise() - private let popover:NSPopover - private let account:Account - private let peerId:PeerId - private weak var parentView:NSView? - private let peerDisposable = MetaDisposable() - init(account:Account, peerId:PeerId, parentView:NSView) { - self.account = account - self.peerId = peerId - self.popover = NSPopover() - self.controller.view = ChatUserPopoverView(frame: NSMakeRect(0, 0, 200, 90)) - self.popover.contentViewController = controller - - self.popover.behavior = .transient - self.parentView = parentView - } - - private var view:ChatUserPopoverView { - return controller.view as! ChatUserPopoverView - } - - deinit { - peerDisposable.dispose() - mainWindow.removeAllHandlers(for: self) - } - - func show() { - if let parentView = parentView { - popover.show(relativeTo: NSMakeRect(0, 0, 200, 200), of: parentView, preferredEdge: .maxX) - peerDisposable.set((account.viewTracker.peerView(peerId) |> deliverOnMainQueue).start(next: { [weak self] peerView in - if let strongSelf = self { - strongSelf.view.update(with: peerView, account: strongSelf.account) - } - })) - } - } - - func close() { - popover.close() - } -} - -private var popover:ChatUserPopover? -func showDetailInfoPopover(forPeerId peerId:PeerId, account: Account, fromView:NSView) { - // popover = ChatUserPopover(account: account, peerId: peerId, parentView: fromView) - // popover?.show() - - -// -// mainWindow.set(handler: { () -> KeyHandlerResult in -// popover?.close() -// return .invoked -// }, with: popover!, for: .Escape, priority: .modal) -// mainWindow.set(handler: { () -> KeyHandlerResult in -// popover?.close() -// return .invoked -// }, with: popover!, for: .Space, priority: .modal) -} - diff --git a/Telegram-Mac/ChatVideoAccessoryView.swift b/Telegram-Mac/ChatVideoAccessoryView.swift deleted file mode 100644 index d082f37fa3..0000000000 --- a/Telegram-Mac/ChatVideoAccessoryView.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ChatVideoAccessoryView.swift -// Telegram -// -// Created by keepcoder on 05/10/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit - - -class ChatVideoAccessoryView: View { - - private var text:(TextNodeLayout, TextNode)? - private var textNode:TextNode? - - override func draw(_ layer: CALayer, in ctx: CGContext) { - - ctx.round(frame.size, frame.height / 2) - - ctx.setFillColor(NSColor.blackTransparent.cgColor) - ctx.fill(bounds) - - if let text = text { - text.1.draw(focus(text.0.size), in: ctx, backingScaleFactor: backingScaleFactor) - } - } - - func updateText(_ text: String, maxWidth: CGFloat) -> Void { - let updatedText = TextNode.layoutText(maybeNode: textNode, .initialize(string: text, color: .white, font: .normal(.custom(11))), nil, 1, .end, NSMakeSize(maxWidth, 20), nil, false, .left) - self.text = updatedText - setFrameSize(NSMakeSize(updatedText.0.size.width + 12, updatedText.0.size.height + 4)) - } - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Telegram-Mac/ChatVideoMessageContentView.swift b/Telegram-Mac/ChatVideoMessageContentView.swift index 3271c8dabc..8eb98000b5 100644 --- a/Telegram-Mac/ChatVideoMessageContentView.swift +++ b/Telegram-Mac/ChatVideoMessageContentView.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit /* func songDidStopPlaying(song:APSongItem, for controller:APController) { @@ -36,9 +37,35 @@ private let instantVideoMutedThumb = generateImage(NSMakeSize(30, 30), contextGe ctx.round(size, size.width / 2.0) ctx.fill(CGRect(origin: CGPoint(), size: size)) let icon = #imageLiteral(resourceName: "Icon_VideoMessageMutedIcon").precomposed() - ctx.draw(icon, in: NSMakeRect(floorToScreenPixels((size.width - icon.backingSize.width) / 2), floorToScreenPixels((size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height)) + ctx.draw(icon, in: NSMakeRect(floorToScreenPixels(System.backingScale, (size.width - icon.backingSize.width) / 2), floorToScreenPixels(System.backingScale, (size.height - icon.backingSize.height) / 2), icon.backingSize.width, icon.backingSize.height)) }) +final class VideoMessageCorner : View { + + override var backgroundColor: NSColor { + set { + super.backgroundColor = .clear + borderBackground = newValue + } + get { + return super.backgroundColor + } + } + private var borderBackground: NSColor = .black { + didSet { + needsLayout = true + } + } + override func draw(_ layer: CALayer, in ctx: CGContext) { + //ctx.round(frame.size, frame.size.height / 2) + ctx.setStrokeColor(theme.colors.background.cgColor) + ctx.setLineWidth(2.0) + ctx.setLineCap(.round) + + ctx.strokeEllipse(in: NSMakeRect(1, 1, bounds.width - 2, bounds.height - 2)) + } +} + class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { @@ -49,10 +76,11 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { private let statusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() private let playerDisposable = MetaDisposable() + private let updateMouseDisposable = MetaDisposable() - private var durationView:TextView = TextView() - - private var path:String? { + private var durationView:ChatMessageAccessoryView = ChatMessageAccessoryView(frame: NSZeroRect) + private let videoCorner: VideoMessageCorner = VideoMessageCorner() + private var data:AVGifData? { didSet { updatePlayerIfNeeded() } @@ -61,12 +89,14 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(player) + videoCorner.userInteractionEnabled = false playingProgressView.userInteractionEnabled = false stateThumbView.image = instantVideoMutedThumb stateThumbView.sizeToFit() player.addSubview(stateThumbView) - addSubview(durationView) + player.addSubview(videoCorner) addSubview(playingProgressView) + addSubview(durationView) } required init?(coder: NSCoder) { @@ -75,18 +105,6 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - - if let parent = parent, let parameters = parameters as? ChatMediaVideoMessageLayoutParameters { - for attr in parent.attributes { - if let attr = attr as? ConsumableContentMessageAttribute { - if !attr.consumed { - ctx.setFillColor(theme.colors.blueUI.cgColor) - ctx.fillEllipse(in: NSMakeRect(parameters.durationLayout.layoutSize.width + 3, frame.height - floorToScreenPixels((durationView.frame.height - 5)/2) - 5, 5, 5)) - } - break - } - } - } } var isIncomingConsumed:Bool { @@ -109,6 +127,7 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { removeNotificationListeners() } + override func cancel() { fetchDisposable.set(nil) statusDisposable.set(nil) @@ -116,27 +135,27 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { private var singleWrapper:APSingleWrapper? { if let media = media as? TelegramMediaFile { - return APSingleWrapper(resource: media.resource, name: tr(.audioControllerVideoMessage), performer: parent?.author?.displayTitle, id: media.fileId) + return APSingleWrapper(resource: media.resource, mimeType: media.mimeType, name: L10n.audioControllerVideoMessage, performer: parent?.author?.displayTitle, id: media.fileId) } return nil } override func open() { - if let parent = parent, let account = account { + if let parent = parent, let context = context { if let parameters = parameters as? ChatMediaVideoMessageLayoutParameters { if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: parent) { controller.playOrPause() } else { let controller:APController if parameters.isWebpage, let wrapper = singleWrapper { - controller = APSingleResourceController(account: account, wrapper: wrapper) + controller = APSingleResourceController(account: context.account, wrapper: wrapper, streamable: false) + // controller.set(trackProgress: controller.c) } else { - controller = APChatVoiceController(account: account, peerId: parent.id.peerId, index: MessageIndex(parent)) + controller = APChatVoiceController(account: context.account, peerId: parent.id.peerId, index: MessageIndex(parent)) } parameters.showPlayer(controller) controller.start() - addGlobalAudioToVisible() } } @@ -147,31 +166,31 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { } func songDidChangedState(song: APSongItem, for controller: APController) { - - if let parent = parent, let controller = globalAudio, let song = controller.currentSong, let parameters = parameters as? ChatMediaVideoMessageLayoutParameters { - if song.entry.isEqual(to: parent) { - + var singleEqual: Bool = false + if let single = singleWrapper { + singleEqual = song.entry.isEqual(to: single) + } + if song.entry.isEqual(to: parent) || singleEqual { switch song.state { case let .playing(data): playingProgressView.state = .ImpossibleFetching(progress: Float(data.progress), force: false) - let layout = parameters.duration(for: data.current) - layout.measure(width: frame.width - 50) - durationView.update(layout) - break + durationView.updateText(String.durationTransformed(elapsed: Int(data.current)), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: true, isVideoMessage: true) + stateThumbView.isHidden = true case .stoped, .waiting, .fetching: playingProgressView.state = .None - durationView.update(parameters.durationLayout) + durationView.updateText(String.durationTransformed(elapsed: parameters.duration), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: true, isVideoMessage: true) + stateThumbView.isHidden = false case let .paused(data): playingProgressView.state = .ImpossibleFetching(progress: Float(data.progress), force: true) - let layout = parameters.duration(for: data.current) - layout.measure(width: frame.width - 50) - durationView.update(layout) + durationView.updateText(String.durationTransformed(elapsed: Int(data.current)), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: true, isVideoMessage: true) + stateThumbView.isHidden = false } } else { playingProgressView.state = .None - durationView.update(parameters.durationLayout) + durationView.updateText(String.durationTransformed(elapsed: parameters.duration), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: true, isVideoMessage: true) + stateThumbView.isHidden = false } } } @@ -202,7 +221,11 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { func audioDidCompleteQueue(for controller:APController) { - + if let parameters = parameters as? ChatMediaVideoMessageLayoutParameters { + playingProgressView.state = .None + durationView.updateText(String.durationTransformed(elapsed: parameters.duration), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: true, isVideoMessage: true) + stateThumbView.isHidden = false + } } func checkState() { @@ -211,21 +234,22 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { override func cancelFetching() { - if let account = account, let media = media as? TelegramMediaFile { - chatMessageFileCancelInteractiveFetch(account: account, file: media) + if let context = context, let media = media as? TelegramMediaFile, let parent = parent { + messageMediaFileCancelInteractiveFetch(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)) } } override func fetch() { - if let account = account, let media = media as? TelegramMediaFile { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + if let context = context, let media = media as? TelegramMediaFile, let parent = parent { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start()) } } override func layout() { super.layout() player.frame = bounds - playingProgressView.frame = bounds + videoCorner.frame = NSMakeRect(bounds.minX - 0.5, bounds.minY - 0.5, bounds.width + 1.0, bounds.height + 1.0) + playingProgressView.frame = NSMakeRect(1.5, 1.5, bounds.width - 3, bounds.height - 3) progressView?.center() stateThumbView.centerX(y: 10) durationView.setFrameOrigin(0, frame.height - durationView.frame.height) @@ -239,12 +263,18 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { var acceptVisibility:Bool { - return window != nil && !NSIsEmptyRect(visibleRect) + return window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) && !isDynamicContentLocked + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() } @objc func updatePlayerIfNeeded() { let timebase:CMTimebase? = globalAudio?.currentSong?.stableId == parent?.chatStableId ? globalAudio?.timebase : nil - player.set(path: acceptVisibility ? path : nil, timebase: timebase) + player.set(data: acceptVisibility ? data : nil, timebase: timebase) + } func updateListeners() { @@ -252,6 +282,8 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: table?.view) + } else { removeNotificationListeners() } @@ -263,65 +295,89 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { } deinit { - player.set(path: nil) + player.set(data: nil) + updateMouseDisposable.dispose() + } - override func update(with media: Media, size: NSSize, account: Account, parent: Message?, table: TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false) { - let mediaUpdated = self.media == nil || !self.media!.isEqual(media) + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters:ChatMediaLayoutParameters? = nil, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + let mediaUpdated = self.media == nil || !self.media!.isSemanticallyEqual(to: media) - super.update(with: media, size: size, account: account, parent:parent,table:table, parameters:parameters, animated: animated) + super.update(with: media, size: size, context: context, parent:parent,table:table, parameters:parameters, animated: animated, positionFlags: positionFlags) updateListeners() if let media = media as? TelegramMediaFile { if let parameters = parameters as? ChatMediaVideoMessageLayoutParameters { - durationView.update(parameters.durationLayout) + durationView.updateText(String.durationTransformed(elapsed: parameters.duration), maxWidth: 50, status: nil, isStreamable: false, isUnread: !isIncomingConsumed, animated: animated, isVideoMessage: true) } + if mediaUpdated { - player.layer?.cornerRadius = size.height / 2 - path = nil + globalAudio?.add(listener: self) - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: media.previewRepresentations) + player.layer?.cornerRadius = size.height / 2 + data = nil var updatedStatusSignal: Signal? - - player.setSignal(account: account, signal: chatMessagePhoto(account: account, photo: image, scale: backingScaleFactor)) let arguments = TransformImageArguments(corners: ImageCorners(radius:size.width/2), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + + player.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor), clearInstantly: mediaUpdated) + if player.hasImage { + var bp:Int = 0 + bp += 1 + } + + player.setSignal(chatMessageVideo(postbox: context.account.postbox, fileReference: parent != nil ? FileMediaReference.message(message: MessageReference(parent!), media: media) : FileMediaReference.standalone(media: media), scale: backingScaleFactor), cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) + } + }) + + player.set(arguments: arguments) if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: media), account.pendingMessageManager.pendingMessageStatus(parent.id)) + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: media), context.account.pendingMessageManager.pendingMessageStatus(parent.id)) |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { + if let pendingStatus = pendingStatus.0 { return .Fetching(isActive: true, progress: pendingStatus.progress) } else { return resourceStatus } } |> deliverOnMainQueue } else { - updatedStatusSignal = chatMessageFileStatus(account: account, file: media) + updatedStatusSignal = chatMessageFileStatus(account: context.account, file: media, approximateSynchronousValue: approximateSynchronousValue) } if let updatedStatusSignal = updatedStatusSignal { - self.statusDisposable.set((combineLatest(updatedStatusSignal, account.postbox.mediaBox.resourceData(media.resource)) |> deliverOnMainQueue).start(next: { [weak self] (status,resource) in + self.statusDisposable.set((combineLatest(updatedStatusSignal, context.account.postbox.mediaBox.resourceData(media.resource)) |> deliverOnResourceQueue |> map { status, resource -> (MediaResourceStatus, AVGifData?) in + if resource.complete { + return (status, AVGifData.dataFrom(resource.path)) + } else if status == .Local, let resource = media.resource as? LocalFileReferenceMediaResource { + return (status, AVGifData.dataFrom(resource.localFilePath)) + } else { + return (status, nil) + } + } |> deliverOnMainQueue).start(next: { [weak self] status,data in if let strongSelf = self { - if resource.complete { - strongSelf.path = resource.path - } else { - strongSelf.path = nil - } + strongSelf.data = data strongSelf.fetchStatus = status if case .Local = status { if let progressView = strongSelf.progressView { - progressView.removeFromSuperview() + progressView.state = .Fetching(progress: 1.0, force: false) strongSelf.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) } } else { @@ -346,14 +402,16 @@ class ChatVideoMessageContentView: ChatMediaContentView, APDelegate { } })) } - - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) - + } } } + override var contents: Any? { + return player.layer?.contents + } + override func copy() -> Any { let view = View() view.backgroundColor = .clear diff --git a/Telegram-Mac/ChatVideoMessageItem.swift b/Telegram-Mac/ChatVideoMessageItem.swift index 2d784030ae..a370071b01 100644 --- a/Telegram-Mac/ChatVideoMessageItem.swift +++ b/Telegram-Mac/ChatVideoMessageItem.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class ChatMediaVideoMessageLayoutParameters : ChatMediaLayoutParameters { @@ -19,13 +20,14 @@ class ChatMediaVideoMessageLayoutParameters : ChatMediaLayoutParameters { let isMarked:Bool let duration:Int let durationLayout:TextViewLayout - init(showPlayer:@escaping(APController) -> Void, duration:Int, isMarked:Bool, isWebpage: Bool, resource: TelegramMediaResource) { + init(showPlayer:@escaping(APController) -> Void, duration:Int, isMarked:Bool, isWebpage: Bool, resource: TelegramMediaResource, presentation: ChatMediaPresentation, media: Media, automaticDownload: Bool, autoplayMedia: AutoplayMediaPreferences) { self.showPlayer = showPlayer self.duration = duration self.isMarked = isMarked self.isWebpage = isWebpage self.resource = resource self.durationLayout = TextViewLayout(NSAttributedString.initialize(string: String.durationTransformed(elapsed: duration), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType:.end, alignment: .left) + super.init(presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: autoplayMedia) } func duration(for duration:TimeInterval) -> TextViewLayout { @@ -35,11 +37,15 @@ class ChatMediaVideoMessageLayoutParameters : ChatMediaLayoutParameters { class ChatVideoMessageItem: ChatMediaItem { - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { - super.init(initialSize, chatInteraction, account, object) + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) - self.parameters = ChatMediaLayoutParameters.layout(for: media as! TelegramMediaFile, isWebpage: false, chatInteraction: chatInteraction) + self.parameters = ChatMediaLayoutParameters.layout(for: media as! TelegramMediaFile, isWebpage: false, chatInteraction: chatInteraction, presentation: .make(for: object.message!, account: context.account, renderType: object.renderType), automaticDownload: downloadSettings.isDownloable(object.message!), isIncoming: object.message!.isIncoming(context.account, object.renderType == .bubble), autoplayMedia: object.autoplayMedia) + } + + override var instantlyResize: Bool { + return true } override func makeContentSize(_ width: CGFloat) -> NSSize { diff --git a/Telegram-Mac/ChatVoiceContentView.swift b/Telegram-Mac/ChatVoiceContentView.swift index e9e28589f8..134f07e4fa 100644 --- a/Telegram-Mac/ChatVoiceContentView.swift +++ b/Telegram-Mac/ChatVoiceContentView.swift @@ -7,10 +7,11 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit class ChatVoiceContentView: ChatAudioContentView { @@ -29,6 +30,11 @@ class ChatVoiceContentView: ChatAudioContentView { } let waveformView:AudioWaveformView + private var acceptDragging: Bool = false + private var playAfterDragging: Bool = false + + private var downloadingView: RadialProgressView? + required init(frame frameRect: NSRect) { waveformView = AudioWaveformView(frame: NSMakeRect(0, 20, 100, 20)) super.init(frame: frameRect) @@ -41,29 +47,34 @@ class ChatVoiceContentView: ChatAudioContentView { } override func open() { - if let parameters = parameters as? ChatMediaVoiceLayoutParameters, let account = account, let parent = parent { + if let parameters = parameters as? ChatMediaVoiceLayoutParameters, let context = context, let parent = parent { if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: parent) { controller.playOrPause() } else { let controller:APController if parameters.isWebpage { - controller = APSingleResourceController(account: account, wrapper: APSingleWrapper(resource: parameters.resource, name: tr(.audioControllerVoiceMessage), performer: parent.author?.displayTitle, id: parent.chatStableId)) + controller = APSingleResourceController(account: context.account, wrapper: APSingleWrapper(resource: parameters.resource, name: tr(L10n.audioControllerVoiceMessage), performer: parent.author?.displayTitle, id: parent.chatStableId), streamable: false) } else { - controller = APChatVoiceController(account: account, peerId: parent.id.peerId, index: MessageIndex(parent)) + controller = APChatVoiceController(account: context.account, peerId: parent.id.peerId, index: MessageIndex(parent)) } parameters.showPlayer(controller) controller.start() - addGlobalAudioToVisible() } } } var wBackgroundColor:NSColor { + if let parameters = parameters { + return parameters.presentation.waveformBackground + } return theme.colors.grayIcon.withAlphaComponent(0.7) } var wForegroundColor:NSColor { - return theme.colors.blueFill + if let parameters = parameters { + return parameters.presentation.waveformForeground + } + return theme.colors.accent } override func checkState() { @@ -78,16 +89,16 @@ class ChatVoiceContentView: ChatAudioContentView { switch song.state { case let .playing(data): waveformView.set(foregroundColor: wForegroundColor, backgroundColor: wBackgroundColor) - let width = floorToScreenPixels(parameters.waveformWidth * CGFloat(data.progress)) - waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: data.animated) + let width = floorToScreenPixels(backingScaleFactor, parameters.waveformWidth * CGFloat(data.progress)) + waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: data.animated && !acceptDragging) let layout = parameters.duration(for: data.current) layout.measure(width: frame.width - 50) durationView.update(layout) break case let .fetching(progress, animated): waveformView.set(foregroundColor: wForegroundColor, backgroundColor: wBackgroundColor) - let width = floorToScreenPixels(parameters.waveformWidth * CGFloat(progress)) - waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: animated) + let width = floorToScreenPixels(backingScaleFactor, parameters.waveformWidth * CGFloat(progress)) + waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: animated && !acceptDragging) durationView.update(parameters.durationLayout) case .stoped, .waiting: waveformView.set(foregroundColor: isIncomingConsumed ? wBackgroundColor : wForegroundColor, backgroundColor: wBackgroundColor) @@ -95,8 +106,8 @@ class ChatVoiceContentView: ChatAudioContentView { durationView.update(parameters.durationLayout) case let .paused(data): waveformView.set(foregroundColor: wForegroundColor, backgroundColor: wBackgroundColor) - let width = floorToScreenPixels(parameters.waveformWidth * CGFloat(data.progress)) - waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: data.animated) + let width = floorToScreenPixels(backingScaleFactor, parameters.waveformWidth * CGFloat(data.progress)) + waveformView.foregroundClipingView.change(size: NSMakeSize(width, waveformView.frame.height), animated: data.animated && !acceptDragging) let layout = parameters.duration(for: data.current) layout.measure(width: frame.width - 50) durationView.update(layout) @@ -117,8 +128,106 @@ class ChatVoiceContentView: ChatAudioContentView { } - override func update(with media: Media, size: NSSize, account: Account, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool = false) { - super.update(with: media, size: size, account: account, parent: parent, table: table, parameters: parameters, animated: animated) + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + + if acceptDragging, let parent = parent, let controller = globalAudio, let song = controller.currentSong { + if song.entry.isEqual(to: parent) { + let point = waveformView.convert(event.locationInWindow, from: nil) + let progress = Float(min(max(point.x, 0), waveformView.frame.width)/waveformView.frame.width) + switch song.state { + case .playing: + _ = controller.pause() + playAfterDragging = true + default: + break + } + controller.set(trackProgress: progress) + } else { + super.mouseDragged(with: event) + } + } + } + + override func mouseDown(with event: NSEvent) { + acceptDragging = waveformView.mouseInside() + if !acceptDragging { + super.mouseDown(with: event) + } + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + if acceptDragging && playAfterDragging { + _ = globalAudio?.play() + } + playAfterDragging = false + acceptDragging = false + } + + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool = false, positionFlags: LayoutPositionFlags? = nil, approximateSynchronousValue: Bool = false) { + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags) + + + var updatedStatusSignal: Signal + + let file:TelegramMediaFile = media as! TelegramMediaFile + + self.progressView.state = .None + + if let parent = parent, parent.flags.contains(.Unsent) && !parent.flags.contains(.Failed) { + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: context.account, file: file), context.account.pendingMessageManager.pendingMessageStatus(parent.id)) + |> map { resourceStatus, pendingStatus -> MediaResourceStatus in + if let pendingStatus = pendingStatus.0 { + return .Fetching(isActive: true, progress: pendingStatus.progress) + } else { + return resourceStatus + } + } |> deliverOnMainQueue + } else { + updatedStatusSignal = chatMessageFileStatus(account: context.account, file: file, approximateSynchronousValue: approximateSynchronousValue) |> deliverOnMainQueue + } + + self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self { + strongSelf.fetchStatus = status + + var state: RadialProgressState? = nil + switch status { + case let .Fetching(_, progress): + state = .Fetching(progress: progress, force: false) + strongSelf.progressView.state = .Fetching(progress: progress, force: false) + case .Remote: + state = .Remote + strongSelf.progressView.state = .Remote + case .Local: + strongSelf.progressView.state = .Play + } + if let state = state { + let current: RadialProgressView + if let value = strongSelf.downloadingView { + current = value + } else { + current = RadialProgressView(theme: strongSelf.progressView.theme, twist: true, size: NSMakeSize(40, 40)) + current.fetchControls = strongSelf.fetchControls + strongSelf.downloadingView = current + strongSelf.addSubview(current) + current.frame = strongSelf.progressView.frame + + if !approximateSynchronousValue && animated { + current.layer?.animateAlpha(from: 0.2, to: 1, duration: 0.3) + } + } + current.state = state + } else if let download = strongSelf.downloadingView { + download.state = .Fetching(progress: 1.0, force: false) + strongSelf.downloadingView = nil + download.layer?.animateAlpha(from: 1, to: 0.2, duration: 0.25, removeOnCompletion: false, completion: { [weak download] _ in + download?.removeFromSuperview() + }) + } + } + })) if let parameters = parameters as? ChatMediaVoiceLayoutParameters { waveformView.waveform = parameters.waveform @@ -127,10 +236,9 @@ class ChatVoiceContentView: ChatAudioContentView { checkState() } + + needsLayout = true - if let media = media as? TelegramMediaFile { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) - } } override func draw(_ layer: CALayer, in ctx: CGContext) { @@ -140,8 +248,8 @@ class ChatVoiceContentView: ChatAudioContentView { for attr in parent.attributes { if let attr = attr as? ConsumableContentMessageAttribute { if !attr.consumed { - let center = floorToScreenPixels(frame.height / 2.0) - ctx.setFillColor(theme.colors.blueUI.cgColor) + let center = floorToScreenPixels(backingScaleFactor, frame.height / 2.0) + ctx.setFillColor(parameters.presentation.activityBackground.cgColor) ctx.fillEllipse(in: NSMakeRect(leftInset + parameters.durationLayout.layoutSize.width + 3, center + 8, 5, 5)) } break @@ -153,7 +261,7 @@ class ChatVoiceContentView: ChatAudioContentView { override func layout() { super.layout() - let center = floorToScreenPixels(frame.height / 2.0) + let center = floorToScreenPixels(backingScaleFactor, frame.height / 2.0) if let parameters = parameters as? ChatMediaVoiceLayoutParameters { waveformView.setFrameSize(parameters.waveformWidth, waveformView.frame.height) } diff --git a/Telegram-Mac/ChatVoiceRowItem.swift b/Telegram-Mac/ChatVoiceRowItem.swift index 4383c69abe..39d7c85657 100644 --- a/Telegram-Mac/ChatVoiceRowItem.swift +++ b/Telegram-Mac/ChatVoiceRowItem.swift @@ -9,9 +9,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ChatMediaVoiceLayoutParameters : ChatMediaLayoutParameters { let showPlayer:(APController) -> Void let waveform:AudioWaveform? @@ -21,29 +22,45 @@ class ChatMediaVoiceLayoutParameters : ChatMediaLayoutParameters { let resource: TelegramMediaResource fileprivate(set) var waveformWidth:CGFloat = 120 let duration:Int - init(showPlayer:@escaping(APController) -> Void, waveform:AudioWaveform?, duration:Int, isMarked:Bool, isWebpage: Bool, resource: TelegramMediaResource) { + init(showPlayer:@escaping(APController) -> Void, waveform:AudioWaveform?, duration:Int, isMarked:Bool, isWebpage: Bool, resource: TelegramMediaResource, presentation: ChatMediaPresentation, media: Media, automaticDownload: Bool) { self.showPlayer = showPlayer self.waveform = waveform self.duration = duration self.isMarked = isMarked self.isWebpage = isWebpage self.resource = resource - durationLayout = TextViewLayout(NSAttributedString.initialize(string: String.durationTransformed(elapsed: duration), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType:.end, alignment: .left) - - + durationLayout = TextViewLayout(NSAttributedString.initialize(string: String.durationTransformed(elapsed: duration), color: presentation.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType:.end, alignment: .left) + super.init(presentation: presentation, media: media, automaticDownload: automaticDownload, autoplayMedia: AutoplayMediaPreferences.defaultSettings) } func duration(for duration:TimeInterval) -> TextViewLayout { - return TextViewLayout(NSAttributedString.initialize(string: String.durationTransformed(elapsed: Int(duration)), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType:.end, alignment: .left) + return TextViewLayout(NSAttributedString.initialize(string: String.durationTransformed(elapsed: Int(round(duration))), color: presentation.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType:.end, alignment: .left) } } class ChatVoiceRowItem: ChatMediaItem { - override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ account: Account, _ object: ChatHistoryEntry) { - super.init(initialSize, chatInteraction, account, object) + override init(_ initialSize:NSSize, _ chatInteraction:ChatInteraction, _ context: AccountContext, _ object: ChatHistoryEntry, _ downloadSettings: AutomaticMediaDownloadSettings, theme: TelegramPresentationTheme) { + super.init(initialSize, chatInteraction, context, object, downloadSettings, theme: theme) + + self.parameters = ChatMediaLayoutParameters.layout(for: media as! TelegramMediaFile, isWebpage: false, chatInteraction: chatInteraction, presentation: .make(for: object.message!, account: context.account, renderType: object.renderType), automaticDownload: downloadSettings.isDownloable(object.message!), isIncoming: object.message!.isIncoming(context.account, object.renderType == .bubble), autoplayMedia: object.autoplayMedia) + } + + override func canMultiselectTextIn(_ location: NSPoint) -> Bool { + return super.canMultiselectTextIn(location) + } + + override var additionalLineForDateInBubbleState: CGFloat? { + if isForceRightLine { + return rightSize.height + } + if let parameters = parameters as? ChatMediaVoiceLayoutParameters { + if parameters.durationLayout.layoutSize.width + 50 + rightSize.width + insetBetweenContentAndDate > contentSize.width { + return rightSize.height + } + } - self.parameters = ChatMediaLayoutParameters.layout(for: media as! TelegramMediaFile, isWebpage: false, chatInteraction: chatInteraction) + return super.additionalLineForDateInBubbleState } override func makeContentSize(_ width: CGFloat) -> NSSize { @@ -61,8 +78,12 @@ class ChatVoiceRowItem: ChatMediaItem { parameters.waveformWidth = floor(min(w, 200)) - return NSMakeSize(parameters.waveformWidth + 60, 40) + return NSMakeSize(parameters.waveformWidth + 50, 40) } return NSZeroSize } + + override var instantlyResize: Bool { + return true + } } diff --git a/Telegram-Mac/ChatWallpaperModalController.swift b/Telegram-Mac/ChatWallpaperModalController.swift new file mode 100644 index 0000000000..3e189a3fd0 --- /dev/null +++ b/Telegram-Mac/ChatWallpaperModalController.swift @@ -0,0 +1,341 @@ +// +// ChatBackgroundModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import TGUIKit +import SwiftSignalKit + + +final class ThemeGridControllerInteraction { + let openWallpaper: (Wallpaper, TelegramWallpaper?) -> Void + let deleteWallpaper: (Wallpaper, TelegramWallpaper) -> Void + init(openWallpaper: @escaping (Wallpaper, TelegramWallpaper?) -> Void, deleteWallpaper: @escaping (Wallpaper, TelegramWallpaper) -> Void) { + self.openWallpaper = openWallpaper + self.deleteWallpaper = deleteWallpaper + } +} + +private struct ThemeGridControllerEntry: Comparable, Identifiable { + let index: Int + let wallpaper: Wallpaper + let telegramWallapper: TelegramWallpaper? + let selected: Bool + + + static func <(lhs: ThemeGridControllerEntry, rhs: ThemeGridControllerEntry) -> Bool { + return lhs.index < rhs.index + } + + var stableId: Int { + return self.index + } + + func item(account: Account, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem { + return ThemeGridControllerItem(account: account, wallpaper: self.wallpaper, telegramWallpaper: self.telegramWallapper, interaction: interaction, isSelected: selected) + } +} + +private struct ThemeGridEntryTransition { + let deletions: [Int] + let insertions: [GridNodeInsertItem] + let updates: [GridNodeUpdateItem] + let updateFirstIndexInSectionOffset: Int? + let stationaryItems: GridNodeStationaryItems + let scrollToItem: GridNodeScrollToItem? +} + +private func preparedThemeGridEntryTransition(context: AccountContext, from fromEntries: [ThemeGridControllerEntry], to toEntries: [ThemeGridControllerEntry], interaction: ThemeGridControllerInteraction) -> ThemeGridEntryTransition { + let stationaryItems: GridNodeStationaryItems = .none + let scrollToItem: GridNodeScrollToItem? = nil + + 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: context.account, interaction: interaction), previousIndex: $0.2) } + let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: context.account, interaction: interaction)) } + + return ThemeGridEntryTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: nil, stationaryItems: stationaryItems, scrollToItem: scrollToItem) +} + +private final class ChatWallpaperView : View { + fileprivate let gridNode: GridNode = GridNode() + fileprivate let header:TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(gridNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + gridNode.frame = NSMakeRect(0, 10, frame.width, frame.height - 10) + } +} + +class ChatWallpaperModalController: ModalViewController { + private let context: AccountContext + + + override func viewClass() -> AnyClass { + return ChatWallpaperView.self + } + + private var genericView: ChatWallpaperView { + return self.view as! ChatWallpaperView + } + + var gridNode: GridNode { + return genericView.gridNode + } + + private var queuedTransitions: [ThemeGridEntryTransition] = [] + private var disposable: Disposable? + + init(_ context: AccountContext) { + self.context = context + + super.init(frame: NSMakeRect(0, 0, 380, 400)) + } + + override var modalInteractions: ModalInteractions? { + let context = self.context + let interactions = ModalInteractions(acceptTitle: L10n.chatWPSelectFromFile, accept: { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { paths in + if let path = paths?.first { + let size = fs(path) + if let size = size, size < 10 * 1024 * 1024, let image = NSImage(contentsOf: URL(fileURLWithPath: path))?.cgImage(forProposedRect: nil, context: nil, hints: nil), image.size.width > 500 && image.size.height > 500 { + + let options = NSMutableDictionary() + options.setValue(90 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + var representations: [TelegramMediaImageRepresentation] = [] + let colorQuality: Float = 0.1 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + let mutableData: CFMutableData = NSMutableData() as CFMutableData + + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) { + CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let thumdResource = LocalFileMediaResource(fileId: arc4random64()) + context.account.postbox.mediaBox.storeResourceData(thumdResource.id, data: mutableData as Data) + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size.aspectFitted(NSMakeSize(90, 90))), resource: thumdResource)) + } + } + + let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource)) + + showModal(with: WallpaperPreviewController(context, wallpaper: .image(representations, settings: WallpaperSettings()), source: .none), for: context.window) + + } else { + alert(for: context.window, header: appName, info: L10n.appearanceCustomBackgroundFileError) + } + } + }) + }, drawBorder: true, height: 50, singleButton: true) + + return interactions + } + + override var dynamicSize: Bool { + return true + } + public override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: ModalHeaderData(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: L10n.chatWPBackgroundTitle), right: nil) + } + + override func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(frame.width, size.height - 150), animated: false) + } + + override func viewDidResized(_ size: NSSize) { + containerLayoutUpdated() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + + override func viewDidLoad() { + super.viewDidLoad() + + + containerLayoutUpdated() + + let context = self.context + let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil) + + let close = { [weak self] in + self?.close() + } + + let deleted: Promise<[Wallpaper]> = Promise([]) + let deletedValue:Atomic<[Wallpaper]> = Atomic(value: []) + + let updateDeleted: (([Wallpaper]) -> [Wallpaper]) -> Void = { f in + deleted.set(.single(deletedValue.modify(f))) + } + + let interaction = ThemeGridControllerInteraction(openWallpaper: { wallpaper, telegramWallpaper in + switch wallpaper { + case .image, .file, .color, .gradient: + showModal(with: WallpaperPreviewController(context, wallpaper: wallpaper, source: telegramWallpaper != nil ? .gallery(telegramWallpaper!) : .none), for: context.window) + default: + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.updateWallpaper{ $0.withUpdatedWallpaper(wallpaper) }.saveDefaultWallpaper() + }).start() + delay(0.15, closure: { + close() + }) + } + + }, deleteWallpaper: { wallpaper, telegramWallpaper in + if wallpaper.isSemanticallyEqual(to: theme.wallpaper.wallpaper) { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.updateWallpaper({ $0.withUpdatedWallpaper(settings.palette.wallpaper.wallpaper) }).saveDefaultWallpaper() + }).start() + } + + _ = deleteWallpaper(account: context.account, wallpaper: telegramWallpaper).start() + + updateDeleted { current in + return current + [wallpaper] + } + }) + + + let transition = combineLatest(queue: prepareQueue, telegramWallpapers(postbox: context.account.postbox, network: context.account.network), deleted.get(), appearanceSignal) + |> map { wallpapers, deletedWallpapers, appearance -> (ThemeGridEntryTransition, Bool) in + var entries: [ThemeGridControllerEntry] = [] + var index = 0 + + entries.append(ThemeGridControllerEntry(index: index, wallpaper: .none, telegramWallapper: nil, selected: appearance.presentation.wallpaper.wallpaper.isSemanticallyEqual(to: .none))) + index += 1 + + + let telegramWallpaper: TelegramWallpaper? = wallpapers.first(where: { wallpaper -> Bool in + let wallpaper: Wallpaper = Wallpaper(wallpaper) + return wallpaper.isSemanticallyEqual(to: theme.wallpaper.wallpaper) + }) + let selected: Wallpaper = theme.wallpaper.wallpaper + + + let wallpaper: Wallpaper + + switch theme.wallpaper.wallpaper { + case .gradient: + entries.append(ThemeGridControllerEntry(index: index, wallpaper: theme.wallpaper.wallpaper, telegramWallapper: nil, selected: true)) + default: + if theme.colors.accent != theme.colors.basicAccent { + wallpaper = .color(theme.colors.basicAccent.argb) + } else { + wallpaper = .color(theme.colors.basicAccent.lighter(amount: 0.25).argb) + } + entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, telegramWallapper: nil, selected: theme.wallpaper.wallpaper.isSemanticallyEqual(to: wallpaper))) + } + + + + switch selected { + case .none, .color, .gradient: + break + default: + entries.append(ThemeGridControllerEntry(index: index, wallpaper: selected, telegramWallapper: telegramWallpaper, selected: true)) + index += 1 + } + + for item in wallpapers { + let wallpaper = Wallpaper(item) + if !deletedWallpapers.contains(where: {$0.isSemanticallyEqual(to: wallpaper)}) { + switch item { + case let .file(_, _, _, _, isPattern, _, _, _, settings): + if isPattern, settings.color == nil { + continue + } + default: + break + } + if selected.isSemanticallyEqual(to: wallpaper) { + continue + } + entries.append(ThemeGridControllerEntry(index: index, wallpaper: wallpaper, telegramWallapper: item, selected: appearance.presentation.wallpaper.wallpaper.isSemanticallyEqual(to: wallpaper))) + index += 1 + } + } + let previous = previousEntries.swap(entries) + return (preparedThemeGridEntryTransition(context: context, from: previous ?? [], to: entries, interaction: interaction), previous == nil) + } + + self.disposable = (transition |> deliverOnMainQueue).start(next: { [weak self] (transition, _) in + if let strongSelf = self { + strongSelf.enqueueTransition(transition) + } + }) + } + + deinit { + self.disposable?.dispose() + } + + + private func enqueueTransition(_ transition: ThemeGridEntryTransition) { + self.queuedTransitions.append(transition) + self.dequeueTransitions() + } + + private func dequeueTransitions() { + while !self.queuedTransitions.isEmpty { + let transition = self.queuedTransitions.removeFirst() + self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.readyOnce() + } + }) + } + } + + func containerLayoutUpdated() { + var insets: NSEdgeInsets = NSEdgeInsets() + let scrollIndicatorInsets = insets + + let referenceImageSize = CGSize(width: 108.0, height: 163.0) + + let minSpacing: CGFloat = 10.0 + + let imageCount = Int((frame.width - minSpacing * 2.0) / (referenceImageSize.width + minSpacing)) + + let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((frame.width - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) + + let spacing = floor((frame.width - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) + + insets.top += 0 + + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: frame.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 380, type: .fixed(itemSize: imageSize, lineSpacing: spacing)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: frame.width, height: frame.height) + + let dequeue = true + if dequeue { + self.dequeueTransitions() + } + } + + + func scrollToTop() { + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + } +} diff --git a/Telegram-Mac/ChatlistFilterVisibilityItem.swift b/Telegram-Mac/ChatlistFilterVisibilityItem.swift new file mode 100644 index 0000000000..d4f1dfa761 --- /dev/null +++ b/Telegram-Mac/ChatlistFilterVisibilityItem.swift @@ -0,0 +1,178 @@ +// +// ChatListFilterVisibilityItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +private func generateThumb(_ basic: CGImage, active: CGImage?) -> CGImage { + return generateImage(basic.backingSize, contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + ctx.draw(basic, in: rect) + + if let active = active { + ctx.draw(active, in: rect) + } + })! +} + +class ChatListFilterVisibilityItem: GeneralRowItem { + + fileprivate let topViewLayout: TextViewLayout + fileprivate let leftViewLayout: TextViewLayout + + fileprivate let topThumb: CGImage + fileprivate let leftThumb: CGImage + + fileprivate let sidebar: Bool + fileprivate let toggle:(Bool)->Void + init(_ initialSize: NSSize, stableId: AnyHashable, sidebar: Bool, viewType: GeneralViewType, toggle:@escaping(Bool)->Void) { + + self.sidebar = sidebar + self.toggle = toggle + topViewLayout = TextViewLayout.init(.initialize(string: L10n.chatListFilterTabBarOnTheTop, color: sidebar ? theme.colors.grayText : theme.colors.accent, font: .normal(.text))) + + leftViewLayout = TextViewLayout.init(.initialize(string: L10n.chatListFilterTabBarOnTheLeft, color: sidebar ? theme.colors.accent : theme.colors.grayText, font: .normal(.text))) + + topThumb = generateThumb(NSImage(named: "tabsselect_top_gray")!.precomposed(theme.colors.grayIcon.withAlphaComponent(0.8)), active: NSImage(named: "tabsselect_top_systemcol")!.precomposed(theme.colors.accent)) + + leftThumb = generateThumb(NSImage(named: "tabsselect_left_gray")!.precomposed(theme.colors.grayIcon.withAlphaComponent(0.8)), active: NSImage(named: "tabsselect_left_systemcol")!.precomposed(theme.colors.accent)) + + + + super.init(initialSize, stableId: stableId, viewType: viewType) + } + + override var height: CGFloat { + if blockWidth < (150 * 2) + viewType.innerInset.right * 3 { + + + + return viewType.innerInset.top + viewType.innerInset.bottom + viewType.innerInset.top + 120 * 2 + 20 + leftViewLayout.layoutSize.height + topViewLayout.layoutSize.height + } + + return viewType.innerInset.top + viewType.innerInset.bottom + max(leftViewLayout.layoutSize.height, topViewLayout.layoutSize.height) + 10 + 120 + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + topViewLayout.measure(width: 140) + leftViewLayout.measure(width: 140) + + return true + } + + + override func viewClass() -> AnyClass { + return ChatlistFilterVisibilityView.self + } +} + +private final class VisibilityContainerView : Control { + private let imageView: ImageView = ImageView() + private let textView: TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + + imageView.isEventLess = true + textView.isEventLess = true + textView.userInteractionEnabled = false + textView.isSelectable = false + } + + func update( _ text: TextViewLayout, image: CGImage, selected: Bool) -> Void { + imageView.image = image + imageView.sizeToFit() + textView.update(text) + + imageView.layer?.cornerRadius = 8 + imageView.layer?.borderWidth = selected ? 2 : 0 + imageView.layer?.borderColor = selected ? theme.colors.accent.cgColor : theme.colors.grayIcon.cgColor + } + + override func layout() { + super.layout() + imageView.centerX(y: 0) + textView.centerX(y: imageView.frame.maxY + 10) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +private final class ChatlistFilterVisibilityView : GeneralContainableRowView { + private let leftItem:VisibilityContainerView = VisibilityContainerView(frame: .zero) + private let topItem:VisibilityContainerView = VisibilityContainerView(frame: .zero) + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(leftItem) + addSubview(topItem) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? ChatListFilterVisibilityItem else { + return + } + + leftItem.setFrameSize(NSMakeSize(150, 120 + 10 + item.leftViewLayout.layoutSize.height)) + topItem.setFrameSize(NSMakeSize(150, 120 + 10 + item.topViewLayout.layoutSize.height)) + + leftItem.update(item.leftViewLayout, image: item.leftThumb, selected: item.sidebar) + topItem.update(item.topViewLayout, image: item.topThumb, selected: !item.sidebar) + + leftItem.removeAllHandlers() + topItem.removeAllHandlers() + + + topItem.set(handler: { [weak item] _ in + item?.toggle(false) + }, for: .Click) + + leftItem.set(handler: { [weak item] _ in + item?.toggle(true) + }, for: .Click) + + needsLayout = true + } + + override func layout() { + super.layout() + + guard let item = item as? ChatListFilterVisibilityItem else { + return + } + + if containerView.frame.width < (leftItem.frame.width + topItem.frame.width) + item.viewType.innerInset.right * 3 { + topItem.centerX(y: item.viewType.innerInset.top) + leftItem.centerX(y: topItem.frame.maxY + item.viewType.innerInset.bottom) + } else { + let inset = (containerView.frame.width - (leftItem.frame.width + topItem.frame.width)) / 3 + topItem.setFrameOrigin(NSMakePoint(inset, item.viewType.innerInset.top)) + leftItem.setFrameOrigin(NSMakePoint(containerView.frame.width - leftItem.frame.width - inset, item.viewType.innerInset.top)) + } + } + + +} diff --git a/Telegram-Mac/ClearUserNotifies.swift b/Telegram-Mac/ClearUserNotifies.swift index 199349c372..5416c985d5 100644 --- a/Telegram-Mac/ClearUserNotifies.swift +++ b/Telegram-Mac/ClearUserNotifies.swift @@ -7,26 +7,57 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit -private let queue:Queue = Queue(name: "clearUserNotifiesQueue", target: nil) +private let queue:Queue = Queue(name: "clearUserNotifiesQueue") func clearNotifies(_ peerId:PeerId, maxId:MessageId) { - queue.async { + Queue.concurrentDefaultQueue().async { let deliveredNotifications = NSUserNotificationCenter.default.deliveredNotifications + + for notification in deliveredNotifications { - if let encodedMessageId = notification.userInfo?["encodedMessageId"] as? Data, let namespace = notification.userInfo?["peerId.namespace"] as? Int32, let id = notification.userInfo?["peerId.id"] as? Int32 { - let notificationMessageId = MessageId(ReadBuffer(memoryBufferNoCopy: MemoryBuffer(data: encodedMessageId))) + if let msgId = notification.userInfo?["message.id"] as? Int32, let msgNamespace = notification.userInfo?["message.namespace"] as? Int32, let namespace = notification.userInfo?["peer.namespace"] as? Int32, let id = notification.userInfo?["peer.id"] as? Int32 { + + let timestamp = notification.userInfo?["timestamp"] as? Int32 ?? 0 + let notificationPeerId = PeerId(namespace: namespace, id: id) + + let notificationMessageId = MessageId(peerId: notificationPeerId, namespace: msgNamespace, id: msgId) if notificationPeerId == peerId, notificationMessageId <= maxId { NSUserNotificationCenter.default.removeDeliveredNotification(notification) + } else if timestamp == 0 || timestamp + 24 * 60 * 60 < Int32(Date().timeIntervalSince1970) { + NSUserNotificationCenter.default.removeDeliveredNotification(notification) } } } } } + + +func clearNotifies(by msgIds: [MessageId]) { + queue.async { + let deliveredNotifications = NSUserNotificationCenter.default.deliveredNotifications + + for notification in deliveredNotifications { + if let msgId = notification.userInfo?["message.id"] as? Int32, let msgNamespace = notification.userInfo?["message.namespace"] as? Int32, let namespace = notification.userInfo?["peer.namespace"] as? Int32, let id = notification.userInfo?["peer.id"] as? Int32 { + + let notificationPeerId = PeerId(namespace: namespace, id: id) + + let notificationMessageId = MessageId(peerId: notificationPeerId, namespace: msgNamespace, id: msgId) + + for msgId in msgIds { + if notificationMessageId == msgId { + NSUserNotificationCenter.default.removeDeliveredNotification(notification) + } + } + } + } + } +} diff --git a/Telegram-Mac/ComposeActions.swift b/Telegram-Mac/ComposeActions.swift index d12f142934..92e5bdd930 100644 --- a/Telegram-Mac/ComposeActions.swift +++ b/Telegram-Mac/ComposeActions.swift @@ -7,40 +7,142 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore import TGUIKit -func createGroup(with account:Account, for navigation:NavigationViewController) { +func createGroup(with context: AccountContext) { + let select = { SelectPeersController(titles: ComposeTitles(L10n.composeSelectUsers, L10n.composeNext), context: context, settings: [.contacts, .remote], isNewGroup: true) } + let chooseName = { CreateGroupViewController(titles: ComposeTitles(L10n.groupNewGroup, L10n.composeCreate), context: context) } + let signal = execute(context: context, select, chooseName) |> mapError { _ in return CreateGroupError.generic } |> mapToSignal { (_, result) -> Signal<(PeerId?, String?), CreateGroupError> in + let signal = showModalProgress(signal: createGroup(account: context.account, title: result.title, peerIds: result.peerIds) |> map { return ($0, result.picture)}, for: mainWindow, disposeAfterComplete: false) + return signal + } |> mapToSignal{ peerId, picture -> Signal<(PeerId?, Bool), CreateGroupError> in + if let peerId = peerId, let picture = picture { + let resource = LocalFileReferenceMediaResource(localFilePath: picture, randomId: arc4random64()) + let signal:Signal<(PeerId?, Bool), NoError> = updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> `catch` {_ in .complete()} |> map { value in + switch value { + case .complete: + return (Optional(peerId), false) + default: + return (nil, false) + } + } + + return .single((peerId, true)) |> then(signal |> mapError { _ in return CreateGroupError.generic}) + } + return .single((peerId, true)) + } |> deliverOnMainQueue |> filter {$0.1} - let select = SelectPeersController(titles: ComposeTitles(tr(.composeSelectUsers), tr(.composeNext)), account: account, settings: [.contacts, .remote]) - let chooseName = CreateGroupViewController(titles: ComposeTitles(tr(.groupNewGroup), tr(.composeCreate)), account: account) - let signal = execute(navigation:navigation, select, chooseName) |> mapToSignal { (_, result) -> Signal in - return showModalProgress(signal: createGroup(account: account, title: result.title, peerIds: result.peerIds), for: mainWindow) - } - _ = signal.start(next: { [weak navigation] (peerId) in - if let peerId = peerId { - navigation?.push(ChatController(account: account, peerId: peerId)) + + _ = signal.start(next: { peerId, complete in + if let peerId = peerId, complete { + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId))) + } + }, error: { error in + let text: String + switch error { + case .privacy: + text = L10n.privacyGroupsAndChannelsInviteToChannelMultipleError + case .generic: + text = L10n.unknownError + case .restricted: + text = L10n.unknownError + case .tooMuchLocationBasedGroups: + text = L10n.unknownError + case let .serverProvided(error): + text = error + case .tooMuchJoined: + text = L10n.channelErrorAddTooMuch } + alert(for: context.window, info: text) }) } -func createChannel(with account:Account, for navigation:NavigationViewController) { - let intro = ChannelIntroViewController(account) - navigation.push(intro) + +func createSupergroup(with context: AccountContext, defaultText: String = "") -> Signal { + let chooseName = CreateGroupViewController(titles: ComposeTitles(L10n.groupNewGroup, L10n.composeCreate), context: context, defaultText: defaultText) + context.sharedContext.bindings.rootNavigation().push(chooseName) + chooseName.restart(with: ComposeState([])) + let signal = chooseName.onComplete.get() |> mapToSignal { result -> Signal<(PeerId?, Bool), NoError> in + + let createSignal: Signal<(PeerId?, String?), CreateChannelError> = showModalProgress(signal: createSupergroup(account: context.account, title: result.title, description: nil) |> map { return ($0, result.picture) }, for: mainWindow, disposeAfterComplete: false) + + return createSignal + |> `catch` { _ in + return .single((nil, nil)) + } + |> mapToSignal { peerId, picture -> Signal<(PeerId?, Bool), NoError> in + if let peerId = peerId { + var additionalSignals:[Signal] = [] + + if let picture = picture { + let resource = LocalFileReferenceMediaResource(localFilePath: picture, randomId: arc4random64()) + let signal:Signal = updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> `catch` { _ in .complete() } |> map { _ in } + additionalSignals.append(signal) + } + + let combined:Signal<(PeerId?, Bool), NoError> = combineLatest(additionalSignals) |> map { _ in (nil, false) } + + + + return .single((peerId, true)) |> then(combined) + } + return .single((peerId, true)) + } |> deliverOnMainQueue + } + + return signal |> filter { $0.1 } |> map { $0.0 } +} + +func createChannel(with context: AccountContext) { + + let intro = ChannelIntroViewController(context) + if FastSettings.needShowChannelIntro { + context.sharedContext.bindings.rootNavigation().push(intro) + } - let create = intro.onComplete.get() |> mapToSignal{ () -> Signal in - let create = CreateChannelViewController(titles: ComposeTitles(tr(.channelNewChannel), tr(.composeNext)), account: account) - navigation.push(create) - return create.onComplete.get() |> deliverOnMainQueue |> mapToSignal { peerId -> Signal in + let introCompletion: Signal = FastSettings.needShowChannelIntro ? intro.onComplete.get() : Signal.single(Void()) + + let create = introCompletion |> mapToSignal { () -> Signal in + let create = CreateChannelViewController(titles: ComposeTitles(L10n.channelNewChannel, L10n.composeNext), context: context) + context.sharedContext.bindings.rootNavigation().push(create) + return create.onComplete.get() |> deliverOnMainQueue |> filter {$0.1} |> mapToSignal { peerId, _ -> Signal in if let peerId = peerId { - navigation.push(ChatController(account: account, peerId: peerId), style: .none) - let visibility = ChannelVisibilityController(account: account, peerId: peerId) - navigation.push(visibility) - return visibility.onComplete.get() |> filter {$0} |> map {_ in return peerId} + FastSettings.markChannelIntroHasSeen() + context.sharedContext.bindings.rootNavigation().removeAll() + + var chat: ChatController? = ChatController(context: context, chatLocation: .peer(peerId)) + var visibility: ChannelVisibilityController? = ChannelVisibilityController(context, peerId: peerId) + + chat!.navigationController = context.sharedContext.bindings.rootNavigation() + visibility!.navigationController = context.sharedContext.bindings.rootNavigation() + + chat!.loadViewIfNeeded(context.sharedContext.bindings.rootNavigation().bounds) + visibility!.loadViewIfNeeded(context.sharedContext.bindings.rootNavigation().bounds) + + + + let chatSignal = chat!.ready.get() |> filter { $0 } |> take(1) |> ignoreValues + let visibilitySignal = visibility!.ready.get() |> filter { $0 } |> take(1) |> ignoreValues + + _ = combineLatest(queue: .mainQueue(), chatSignal, visibilitySignal).start(completed: { + context.sharedContext.bindings.rootNavigation().push(chat!) + context.sharedContext.bindings.rootNavigation().push(visibility!) + + chat = nil + visibility = nil + }) + + return visibility!.onComplete.get() |> map {_ in return peerId} } return .single(nil) } @@ -48,42 +150,44 @@ func createChannel(with account:Account, for navigation:NavigationViewController _ = create.start(next: { peerId in if let peerId = peerId { - navigation.push(ChatController(account: account, peerId: peerId)) + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId))) } else { - navigation.close() + context.sharedContext.bindings.rootNavigation().close() } }) } -private func execute(navigation:NavigationViewController, _ c1:EmptyComposeController, _ c2: EmptyComposeController) -> Signal<(T1,T2), Void> { +private func execute(context: AccountContext, _ c1: @escaping() -> EmptyComposeController, _ c2: @escaping() -> EmptyComposeController) -> Signal<(T1,T2), NoError> { - navigation.push(c1) - return c1.onComplete.get() |> mapToSignal { (c1Next) -> Signal<(T1,T2), Void> in - navigation.push(c2) - c2.restart(with: ComposeState(c1Next)) - return c2.onComplete.get() |> mapToSignal{ (c2Next) -> Signal<(T1,T2), Void> in + let c1Controller = c1() + context.sharedContext.bindings.rootNavigation().push(c1Controller) + return c1Controller.onComplete.get() |> mapToSignal { (c1Next) -> Signal<(T1,T2), NoError> in + let c2Controller = c2() + context.sharedContext.bindings.rootNavigation().push(c2Controller) + c2Controller.restart(with: ComposeState(c1Next)) + return c2Controller.onComplete.get() |> mapToSignal{ (c2Next) -> Signal<(T1,T2), NoError> in return .single((c1Next,c2Next)) } } } -private func push(navigation:NavigationViewController, controller:EmptyComposeController) -> Signal { - navigation.push(controller) +private func push(context: AccountContext, controller:EmptyComposeController) -> Signal { + context.sharedContext.bindings.rootNavigation().push(controller) return controller.onComplete.get() } -private func push(navigation:NavigationViewController, controller:EmptyComposeController, input:I) -> Signal { - navigation.push(controller) +private func push(context: AccountContext, controller:EmptyComposeController, input:I) -> Signal { + context.sharedContext.bindings.rootNavigation().push(controller) controller.restart(with: ComposeState(input)) return controller.onComplete.get() } -private func execute(navigation:NavigationViewController, _ c1:EmptyComposeController, _ c2: EmptyComposeController, _ c3: EmptyComposeController) -> Signal { +private func execute(context: AccountContext, _ c1: @escaping () -> EmptyComposeController, _ c2: @escaping() -> EmptyComposeController, _ c3: @escaping() -> EmptyComposeController) -> Signal { - return push(navigation: navigation, controller: c1) |> mapToSignal { (c1Next) -> Signal in - return push(navigation: navigation, controller: c2, input:c1Next) |> mapToSignal{ (c2Next) -> Signal in - return push(navigation: navigation, controller: c3, input:c2Next) |> mapToSignal{ (c3Next) -> Signal in + return push(context: context, controller: c1()) |> mapToSignal { (c1Next) -> Signal in + return push(context: context, controller: c2(), input:c1Next) |> mapToSignal{ (c2Next) -> Signal in + return push(context: context, controller: c3(), input:c2Next) |> mapToSignal{ (c3Next) -> Signal in return .single(c3Next) } } diff --git a/Telegram-Mac/ComposeViewController.swift b/Telegram-Mac/ComposeViewController.swift index e2f1a496b6..4b67e62ad9 100644 --- a/Telegram-Mac/ComposeViewController.swift +++ b/Telegram-Mac/ComposeViewController.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore struct ComposeTitles { let center:String let done:String @@ -31,13 +32,13 @@ class ComposeViewController: EmptyComposeController where V: N } override func executeReturn() -> Void { - onCancel.set(Signal.single(Void())) + onCancel.set(Signal.single(Void())) super.executeReturn() } override func requestUpdateRightBar() { super.requestUpdateRightBar() - (self.rightBarView as? TextButtonBarView)?.button.style = navigationButtonStyle + rightBarView.style = navigationButtonStyle } public override func returnKeyAction() -> KeyHandlerResult { @@ -47,7 +48,7 @@ class ComposeViewController: EmptyComposeController where V: N func nextEnabled(_ enable:Bool) { self.enableNext = enable - (self.rightBarView as? TextButtonBarView)?.button.isEnabled = enable + rightBarView.isEnabled = enable } func executeNext() -> Void { @@ -62,15 +63,15 @@ class ComposeViewController: EmptyComposeController where V: N super.loadView() setCenterTitle(titles.center) - (self.rightBarView as? TextButtonBarView)?.button.set(handler:{ [weak self] _ in + self.rightBarView.set(handler:{ [weak self] _ in self?.executeNext() }, for: .Click) } - public init(titles:ComposeTitles, account:Account) { + public init(titles:ComposeTitles, context: AccountContext) { self.titles = titles - super.init(account) + super.init(context) } } diff --git a/Telegram-Mac/Confetti.swift b/Telegram-Mac/Confetti.swift new file mode 100644 index 0000000000..3e146ce194 --- /dev/null +++ b/Telegram-Mac/Confetti.swift @@ -0,0 +1,369 @@ +// +// Confetti.swift +// Telegram +// +// Created by Mikhail Filimonov on 09.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import CoreGraphics +import QuartzCore + + +private enum Colors { + static var red: NSColor { + return theme.colors.redUI + } + static var blue: NSColor { + return theme.colors.accent + } + static var green: NSColor { + return theme.colors.greenUI + } + static var yellow: NSColor { + return theme.colors.peerAvatarOrangeTop + } +} + +private enum Images { + static let box = NSImage(named: "Confetti_Box")! + static let triangle = NSImage(named: "Confetti_Triangle")! + static let circle = NSImage(named: "Confetti_Circle")! + static let swirl = NSImage(named: "Confetti_Spiral")! +} + +private let colors:[NSColor] = [ + Colors.red, + Colors.blue, + Colors.green, + Colors.yellow +] + +private let images:[NSImage] = [ + Images.box, + Images.triangle, + Images.circle, + Images.swirl +] + +private let velocities:[Int] = [ + 150, + 135, + 200, + 250 +] + +private func getRandomVelocity() -> Int { + return velocities[getRandomNumber()] * 2 +} + +private func getRandomNumber() -> Int { + return Int(arc4random_uniform(4)) +} + +private func getNextColor(i:Int) -> CGColor { + if i <= 4 { + return colors[0].cgColor + } else if i <= 8 { + return colors[1].cgColor + } else if i <= 12 { + return colors[2].cgColor + } else { + return colors[3].cgColor + } +} + +private func getNextImage(i:Int) -> NSImage { + return images[i % 4] +} + + +func PlayConfetti(for window: Window, playEffect: Bool = false) { + let contentView = window.contentView! + + contentView.addSubview(ConfettiView(frame: contentView.bounds)) + +// +// let rightBottomView = View(frame: contentView.bounds) +// rightBottomView.isEventLess = true +// let rightEmitter = CAEmitterLayer() +// rightEmitter.emitterPosition = CGPoint(x: contentView.frame.size.width , y: contentView.frame.size.height) +// rightEmitter.emitterShape = .point +// rightEmitter.emitterSize = CGSize(width: contentView.frame.size.width, height: 2.0) +// rightEmitter.emitterCells = generateEmitterCells(left: false) +// +// rightBottomView.layer = rightEmitter +// contentView.addSubview(rightBottomView) +// +// let leftBottomView = View(frame: contentView.bounds) +// leftBottomView.isEventLess = true +// let leftEmitter = CAEmitterLayer() +// leftEmitter.emitterPosition = CGPoint(x: 0, y: contentView.frame.size.height) +// leftEmitter.emitterShape = .point +// leftEmitter.emitterSize = CGSize(width: contentView.frame.size.width, height: 2.0) +// leftEmitter.emitterCells = generateEmitterCells(left: true) +// +// leftBottomView.layer = leftEmitter +// contentView.addSubview(leftBottomView) +// +// +// delay(0.1, closure: { +// rightEmitter.birthRate = 0 +// leftEmitter.birthRate = 0 +// }) +// +// delay(2.0, closure: { +// rightBottomView.removeFromSuperview() +// leftBottomView.removeFromSuperview() +// }) +} +private func generateEmitterCells(left: Bool) -> [CAEmitterCell] { + var cells:[CAEmitterCell] = [CAEmitterCell]() + for index in 0 ..< 16 { + let cell = CAEmitterCell() + cell.birthRate = 20 + cell.lifetime = 2.0 + cell.lifetimeRange = 0 + cell.velocity = CGFloat(getRandomVelocity()) * 1.5 + cell.velocityRange = -CGFloat(arc4random() % 300) + + cell.alphaSpeed = -1.0/4.0 + cell.alphaRange = cell.lifetime * cell.alphaSpeed + + // cell.emissionRange = CGFloat.pi / 8 + // cell.emissionLongitude = CGFloat.pi * 2 + + cell.emissionLongitude = left ? -60 * (.pi / 180) : CGFloat(-Double.pi + 1.0) + cell.emissionRange = 30 * (.pi / 180) + cell.yAcceleration = max(400, CGFloat(arc4random() % 1000)) + cell.spin = max(3.5, CGFloat(arc4random() % 14)) + cell.spinRange = 10 + cell.color = getNextColor(i: index) + cell.contents = getNextImage(i: index).cgImage(forProposedRect: nil, context: nil, hints: nil) + cell.scaleRange = 0.25 + cell.scale = 0.1 + cells.append(cell) + } + return cells +} + + + + +private struct Vector2 { + var x: Float + var y: Float +} + +private final class NullActionClass: NSObject, CAAction { + @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { + } +} + +private let nullAction = NullActionClass() + +private final class ParticleLayer: CALayer { + let mass: Float + var velocity: Vector2 + var angularVelocity: Float + var rotationAngle: Float = 0.0 + + init(image: CGImage, size: CGSize, position: CGPoint, mass: Float, velocity: Vector2, angularVelocity: Float) { + self.mass = mass + self.velocity = velocity + self.angularVelocity = angularVelocity + + super.init() + + self.contents = image + self.bounds = CGRect(origin: CGPoint(), size: size) + self.position = position + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func action(forKey event: String) -> CAAction? { + return nullAction + } +} + +final class ConfettiView: View { + private var particles: [ParticleLayer] = [] + private var displayLink: ConstantDisplayLinkAnimator? + + private var localTime: Float = 0.0 + + + required init(frame: CGRect) { + super.init(frame: frame) + + self.isEventLess = true + + let colors: [NSColor] = ([ + 0x56CE6B, + 0xCD89D0, + 0x1E9AFF, + 0xFF8724 + ] as [UInt32]).map(NSColor.init(rgb:)) + let imageSize = CGSize(width: 8.0, height: 8.0) + var images: [(CGImage, CGSize)] = [] + for imageType in 0 ..< 2 { + for color in colors { + if imageType == 0 { + images.append((generateFilledCircleImage(diameter: imageSize.width, color: color), imageSize)) + } else { + let spriteSize = CGSize(width: 2.0, height: 6.0) + images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))) + context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width))) + })!, spriteSize)) + } + } + } + let imageCount = images.count + + let originXRange = 0 ..< Int(frame.width) + let originYRange = Int(-frame.height) ..< Int(0) + let topMassRange: Range = 40.0 ..< 50.0 + let velocityYRange = Float(3.0) ..< Float(5.0) + let angularVelocityRange = Float(1.0) ..< Float(6.0) + let sizeVariation = Float(0.8) ..< Float(1.6) + + for i in 0 ..< 70 { + let (image, size) = images[i % imageCount] + let sizeScale = CGFloat(Float.random(in: sizeVariation)) + let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: CGFloat(Int.random(in: originXRange)), y: CGFloat(Int.random(in: originYRange))), mass: Float.random(in: topMassRange), velocity: Vector2(x: 0.0, y: Float.random(in: velocityYRange)), angularVelocity: Float.random(in: angularVelocityRange)) + self.particles.append(particle) + self.layer?.addSublayer(particle) + } + + let sideMassRange: Range = 100.0 ..< 110.0 + let sideOriginYBase: Float = Float(frame.size.height * 8.5 / 10.0) + let sideOriginYVariation: Float = Float(frame.size.height / 12.0) + let sideOriginYRange = Float(sideOriginYBase - sideOriginYVariation) ..< Float(sideOriginYBase + sideOriginYVariation) + let sideOriginXRange = Float(0.0) ..< Float(100.0) + let sideOriginVelocityValueRange = Float(1.1) ..< Float(1.6) + let sideOriginVelocityValueScaling: Float = 1200.0 + let sideOriginVelocityBase: Float = Float.pi / 2.0 + atanf(Float(CGFloat(sideOriginYBase) / (frame.size.width * 0.8))) + let sideOriginVelocityVariation: Float = 0.2 + let sideOriginVelocityAngleRange = Float(sideOriginVelocityBase - sideOriginVelocityVariation) ..< Float(sideOriginVelocityBase + sideOriginVelocityVariation) + + for sideIndex in 0 ..< 2 { + let sideSign: Float = sideIndex == 0 ? 1.0 : -1.0 + let originX: CGFloat = sideIndex == 0 ? -5.0 : (frame.width + 5.0) + for i in 0 ..< 40 { + let offsetX = CGFloat(Float.random(in: sideOriginXRange) * (-sideSign)) + let velocityValue = Float.random(in: sideOriginVelocityValueRange) * sideOriginVelocityValueScaling + let velocityAngle = Float.random(in: sideOriginVelocityAngleRange) + let velocityX = sideSign * velocityValue * sinf(velocityAngle) + let velocityY = velocityValue * cosf(velocityAngle) + let (image, size) = images[i % imageCount] + let sizeScale = CGFloat(Float.random(in: sizeVariation)) + let particle = ParticleLayer(image: image, size: CGSize(width: size.width * sizeScale, height: size.height * sizeScale), position: CGPoint(x: originX + offsetX, y: CGFloat(Float.random(in: sideOriginYRange))), mass: Float.random(in: sideMassRange), velocity: Vector2(x: velocityX, y: velocityY), angularVelocity: Float.random(in: angularVelocityRange)) + self.particles.append(particle) + self.layer?.addSublayer(particle) + } + } + + self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.step() + }) + + self.displayLink?.isPaused = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func step() { + var haveParticlesAboveGround = false + let minPositionY: CGFloat = 0.0 + let maxPositionY = self.bounds.height + 30.0 + let minDampingX: CGFloat = 40.0 + let maxDampingX: CGFloat = self.bounds.width - 40.0 + let centerX: CGFloat = self.bounds.width / 2.0 + let currentTime = self.localTime + let dt: Float = 1.0 / 60.0 + let slowdownDt: Float + let slowdownStart: Float = 0.27 + let slowdownDuration: Float = 0.9 + let damping: Float + if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration { + let slowdownTimestamp: Float = currentTime - slowdownStart + + let slowdownRampInDuration: Float = 0.15 + let slowdownRampOutDuration: Float = 0.5 + let slowdownTransition: Float + if slowdownTimestamp < slowdownRampInDuration { + slowdownTransition = slowdownTimestamp / slowdownRampInDuration + } else if slowdownTimestamp >= slowdownDuration - slowdownRampOutDuration { + let reverseTransition = (slowdownTimestamp - (slowdownDuration - slowdownRampOutDuration)) / slowdownRampOutDuration + slowdownTransition = 1.0 - reverseTransition + } else { + slowdownTransition = 1.0 + } + + let slowdownFactor: Float = 0.3 * slowdownTransition + 1.0 * (1.0 - slowdownTransition) + slowdownDt = dt * slowdownFactor + let dampingFactor: Float = 0.94 * slowdownTransition + 1.0 * (1.0 - slowdownTransition) + damping = dampingFactor + } else { + slowdownDt = dt + damping = 1.0 + } + self.localTime += 1.0 / 60.0 + + let g: Vector2 = Vector2(x: 0.0, y: 9.8) + CATransaction.begin() + CATransaction.setDisableActions(true) + var turbulenceVariation: [Float] = [] + for _ in 0 ..< 20 { + turbulenceVariation.append(Float.random(in: -9.0 ..< 9.0)) + } + let turbulenceVariationCount = turbulenceVariation.count + var index = 0 + for particle in self.particles { + var position = particle.position + + let localDt: Float = slowdownDt + + position.x += CGFloat(particle.velocity.x * localDt) + position.y += CGFloat(particle.velocity.y * localDt) + particle.position = position + + particle.rotationAngle += particle.angularVelocity * localDt + particle.transform = CATransform3DMakeRotation(CGFloat(particle.rotationAngle), 0.0, 0.0, 1.0) + + let acceleration = g + + var velocity = particle.velocity + velocity.x += acceleration.x * particle.mass * localDt + velocity.y += acceleration.y * particle.mass * localDt + velocity.x += turbulenceVariation[index % turbulenceVariationCount] + if position.y > minPositionY { + velocity.x *= damping + velocity.y *= damping + } + particle.velocity = velocity + + index += 1 + + if position.y < maxPositionY { + haveParticlesAboveGround = true + } + } + CATransaction.commit() + if !haveParticlesAboveGround { + self.displayLink?.isPaused = true + self.removeFromSuperview() + } + } +} diff --git a/Telegram-Mac/Config.swift b/Telegram-Mac/Config.swift new file mode 100644 index 0000000000..d1869864e1 --- /dev/null +++ b/Telegram-Mac/Config.swift @@ -0,0 +1,47 @@ +final class ApiEnvironment { + static var apiId:Int32 { + return 9 + } + static var apiHash:String { + return "3975f648bb682ee889f35483bc618d1c" + } + + static var bundleId: String { + return "ru.keepcoder.Telegram" + } + static var teamId: String { + return "6N38VWS5BX" + } + + static var group: String { + return teamId + "." + bundleId + } + + static var appData: Data { + let apiData = evaluateApiData() ?? "" + let dict:[String: String] = ["bundleId": bundleId, "data": apiData] + return try! JSONSerialization.data(withJSONObject: dict, options: []) + } + static var language: String { + return "macos" + } + static var version: String { + var suffix: String = "" + #if STABLE + suffix = "STABLE" + #elseif APP_STORE + suffix = "APPSTORE" + #elseif ALPHA + suffix = "ALPHA" + #elseif GITHUB + suffix = "GITHUB" + #else + suffix = "BETA" + #endif + let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "" + return "\(shortVersion) \(suffix)" + } +} + + + diff --git a/Telegram-Mac/ContactsController.swift b/Telegram-Mac/ContactsController.swift index 7409526bf1..07a2e2cf65 100644 --- a/Telegram-Mac/ContactsController.swift +++ b/Telegram-Mac/ContactsController.swift @@ -8,23 +8,18 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore private enum ContactsControllerEntryId: Hashable { - case vcard - case separator case peerId(Int64) case addContact var hashValue: Int { switch self { - case .vcard: - return 1 - case .separator: - return 2 case .addContact: - return 3 + return 0 case let .peerId(peerId): return peerId.hashValue @@ -32,91 +27,45 @@ private enum ContactsControllerEntryId: Hashable { } } -private func <(lhs: ContactsControllerEntryId, rhs: ContactsControllerEntryId) -> Bool { - return lhs.hashValue < rhs.hashValue -} - -private func ==(lhs: ContactsControllerEntryId, rhs: ContactsControllerEntryId) -> Bool { - switch lhs { - case .vcard: - switch rhs { - case .vcard: - return true - default: - return false - } - case .separator: - switch rhs { - case .separator: - return true - default: - return false - } - case .addContact: - switch rhs { - case .addContact: - return true - default: - return false - } - case let .peerId(lhsId): - switch rhs { - case let .peerId(rhsId): - return lhsId == rhsId - default: - return false - } - } -} private enum ContactsEntry: Comparable, Identifiable { - case vcard(Peer) - case separator(String) - case peer(Peer, PeerPresence?) + case peer(Peer, PeerPresence?, Int32) case addContact var stableId: ContactsControllerEntryId { switch self { - case .vcard: - return .vcard - case .separator: - return .separator case .addContact: return .addContact - case let .peer(peer,_): + case let .peer(peer,_, _): return .peerId(peer.id.toInt64()) } } + + var index: Int32 { + switch self { + case .addContact: + return -1 + case let .peer(_, _, index): + return index + } + } } private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool { switch lhs { - - case let .vcard(lhsPeer): - switch rhs { - case let .vcard(rhsPeer): - return lhsPeer.id == rhsPeer.id - default: - return false - } - case let .separator(ls): - switch rhs { - case let .separator(rs): - return ls == rs - default: - return false - } case .addContact: - switch rhs { - case .addContact: + if case .addContact = rhs { return true - default: + } else { return false } - case let .peer(lhsPeer, lhsPresence): + case let .peer(lhsPeer, lhsPresence, lhsIndex): switch rhs { - case let .peer(rhsPeer, rhsPresence): - if lhsPeer.id != rhsPeer.id { + case let .peer(rhsPeer, rhsPresence, rhsIndex): + if !lhsPeer.isEqual(rhsPeer) { + return false + } + if lhsIndex != rhsIndex { return false } if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { @@ -134,62 +83,44 @@ private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool { } private func <(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool { - switch lhs { - case .vcard(_): - return false - case .separator: - switch rhs { - case .vcard, .separator: - return true - case .peer: - return false - case .addContact: - return false - } - case let .peer(lhsPeer, lhsPresence): - switch rhs { - case .separator, .vcard, .addContact: - return true - case let .peer(rhsPeer, rhsPresence): + return lhs.index < rhs.index +} + + +private func entriesForView(_ view: ContactPeersView) -> [ContactsEntry] { + var entries: [ContactsEntry] = [] + if let accountPeer = view.accountPeer { + + entries.append(.addContact) + + var peerIds: Set = Set() + var index: Int32 = 0 + + let orderedPeers = view.peers.sorted(by: { lhsPeer, rhsPeer in + let lhsPresence = view.peerPresences[lhsPeer.id] + let rhsPresence = view.peerPresences[rhsPeer.id] if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence { if lhsPresence.status < rhsPresence.status { - return true - } else if lhsPresence.status > rhsPresence.status { return false + } else if lhsPresence.status > rhsPresence.status { + return true } } else if let _ = lhsPresence { - return false - } else if let _ = rhsPresence { return true + } else if let _ = rhsPresence { + return false } return lhsPeer.id < rhsPeer.id - } - case .addContact: - switch rhs { - case .vcard, .separator, .addContact: - return true - case .peer: - return false - } - } -} - -private func entriesForView(_ view: ContactPeersView) -> [ContactsEntry] { - var entries: [ContactsEntry] = [] - if let accountPeer = view.accountPeer { + }) - for peer in view.peers { - if !peer.isEqual(accountPeer) { - entries.append(.peer(peer,view.peerPresences[peer.id])) + for peer in orderedPeers { + if !peer.isEqual(accountPeer), !peerIds.contains(peer.id) { + entries.append(.peer(peer, view.peerPresences[peer.id], index)) + peerIds.insert(peer.id) + index += 1 } } - entries.append(.addContact) - entries.append(.separator(tr(.contactsContacsSeparator))) - entries.append(.vcard(accountPeer)) - - entries.sort() - } return entries @@ -202,55 +133,85 @@ private final class ContactsArguments { } } -fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, arguments: ContactsArguments, animated:Bool) -> Signal { +fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], context: AccountContext, initialSize:NSSize, arguments: ContactsArguments, animated:Bool) -> Signal { return Signal { subscriber in - let (deleted,inserted,updated) = proccessEntries(from, right: to, { (entry) -> TableRowItem in - - var item:TableRowItem + + func makeItem(_ entry: ContactsEntry) -> TableRowItem { + let item:TableRowItem - switch entry.entry { - case let .vcard(peer): - - var status:String? = nil - let phone = (peer as! TelegramUser).phone - if let phone = phone { - status = formatPhoneNumber( phone ) - } - - item = ShortPeerRowItem(initialSize, peer: peer, account:account, height:60, photoSize:NSMakeSize(50,50), status: status, borderType: [.Right], drawCustomSeparator:false) - case let .peer(peer, presence): - + switch entry { + case let .peer(peer, presence, _): var color:NSColor = theme.colors.grayText - var string:String = tr(.peerStatusRecently) + var string:String = L10n.peerStatusRecently if let presence = presence as? TelegramUserPresence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp)) } - - item = ShortPeerRowItem(initialSize, peer: peer, account:account,statusStyle: ControlStyle(foregroundColor:color), status: string, borderType: [.Right]) - case let .separator(str): - item = SeparatorRowItem(initialSize, 1, string: str.uppercased()) + item = ShortPeerRowItem(initialSize, peer: peer, account: context.account, stableId: entry.stableId,statusStyle: ControlStyle(foregroundColor:color), status: string, borderType: [.Right]) case .addContact: - return AddContactTableItem(initialSize, stableId: entry.stableId, addContact: { + item = AddContactTableItem(initialSize, stableId: entry.stableId, addContact: { arguments.addContact() }) } + return item + } + + var cancelled = false + + + if Thread.isMainThread { + var initialIndex:Int = 0 + var height:CGFloat = 0 + var firstInsertion:[(Int, TableRowItem)] = [] + let entries = Array(to) + + let index:Int = 0 + + for i in index ..< entries.count { + let item = makeItem(entries[i].entry) + height += item.height + firstInsertion.append((i, item)) + if initialSize.height < height { + break + } + } - let _ = item.makeSize(initialSize.width) - return item + initialIndex = firstInsertion.count + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state: .none(nil))) + prepareQueue.async { + if !cancelled { + + + var insertions:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + + for i in initialIndex ..< entries.count { + let item:TableRowItem + item = makeItem(entries[i].entry) + insertions.append((i, item)) + } + - }) - - subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated, state: .none(nil))) - subscriber.putCompletion() - - return EmptyDisposable + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: insertions, updated: updates, state: .none(nil))) + subscriber.putCompletion() + } + } + } else { + let (deleted,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in + return makeItem(entry.entry) + }) - + subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated, state: .none(nil))) + subscriber.putCompletion() + } + + return ActionDisposable { + cancelled = true + } } } @@ -260,7 +221,7 @@ class ContactsController: PeersListController { private var previousEntries:Atomic<[AppearanceWrapperEntry]?> = Atomic(value:nil) private let index: PeerNameIndex = .lastNameFirst - + private let disposable = MetaDisposable() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -271,62 +232,75 @@ class ContactsController: PeersListController { } - override func loadView() { - super.loadView() + override func viewDidLoad() { + super.viewDidLoad() backgroundColor = theme.colors.background } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - genericView.tableView.startMerge() - genericView.tableView.clipView.scroll(to: NSZeroPoint) - let account = self.account + let context = self.context let previousEntries = self.previousEntries let initialSize = self.atomicSize let first:Atomic = Atomic(value:false) let arguments = ContactsArguments(addContact: { - showModal(with: AddContactModalController(account: account), for: mainWindow) + showModal(with: AddContactModalController(context), for: mainWindow) }) - let transition = combineLatest(account.postbox.contactPeersView(accountPeerId: account.peerId), appearanceSignal) |> deliverOn(prepareQueue) - |> mapToQueue { view, appearance -> Signal in + + let transition = combineLatest(queue: prepareQueue, context.account.postbox.contactPeersView(accountPeerId: context.peerId, includePresences: true), appearanceSignal) + |> mapToQueue { view, appearance -> Signal in let first:Bool = !first.swap(true) let entries = entriesForView(view).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - if first { - let subEntries = Array(entries.suffix(50)) - let firstSignal = prepareEntries(from: previousEntries.swap(subEntries), to: subEntries, account: account, initialSize: initialSize.modify({$0}), arguments: arguments, animated: !first) - let secondSignal = prepareEntries(from: previousEntries.swap(entries), to: entries, account: account, initialSize: initialSize.modify({$0}), arguments: arguments, animated: !first) - return firstSignal |> then(secondSignal) - } else { - return prepareEntries(from: previousEntries.swap(entries), to: entries, account: account, initialSize: initialSize.modify({$0}), arguments: arguments, animated: !first) - } + return prepareEntries(from: previousEntries.swap(entries), to: entries, context: context, initialSize: initialSize.modify({$0}), arguments: arguments, animated: !first) |> runOn(first ? .mainQueue() : prepareQueue) + } |> deliverOnMainQueue - genericView.tableView.merge(with: transition) + disposable.set(transition.start(next: { [weak self] transition in + self?.genericView.tableView.merge(with: transition) + self?.readyOnce() + })) + } - override func scrollup() { + override func scrollup(force: Bool = false) { genericView.tableView.scroll(to: .up(true)) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - genericView.tableView.stopMerge() _ = previousEntries.swap(nil) + genericView.tableView.cancelSelection() genericView.tableView.removeAll() + genericView.tableView.documentView?.removeAllSubviews() + disposable.set(nil) } + deinit { + disposable.dispose() + } - init(_ account:Account) { - super.init(account) + init(_ context:AccountContext) { + super.init(context, searchOptions: [.chats]) } - override func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + override func changeSelection(_ location: ChatLocation?) { + if let location = location { + switch location { + case let .peer(peerId): + genericView.tableView.changeSelection(stableId: ContactsControllerEntryId.peerId(peerId.toInt64())) + } + } else { + genericView.tableView.cancelSelection() + } + } + + override func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { if let item = item as? ShortPeerRowItem, let modalAction = navigationController?.modalAction { if !modalAction.isInvokable(for: item.peer) { modalAction.alertError(for: item.peer, with:window!) @@ -347,7 +321,14 @@ class ContactsController: PeersListController { navigation.controller.invokeNavigation(action: modalAction) } } else { - let chat:ChatController = ChatController(account: self.account, peerId:item.peer.id) + + let context = self.context + + _ = (context.globalPeerHandler.get() |> take(1)).start(next: { location in + context.globalPeerHandler.set(.single(location)) + }) + + let chat:ChatController = ChatController(context: self.context, chatLocation: .peer(item.peer.id)) navigation.push(chat) } diff --git a/Telegram-Mac/ContextClueRowItem.swift b/Telegram-Mac/ContextClueRowItem.swift index a6caa2e50d..daf245bbff 100644 --- a/Telegram-Mac/ContextClueRowItem.swift +++ b/Telegram-Mac/ContextClueRowItem.swift @@ -12,28 +12,32 @@ import TGUIKit class ContextClueRowItem: TableRowItem { private let _stableId:AnyHashable - let clue:EmojiClue - + let clues:[String] + var selectedIndex:Int? = nil + override var stableId: AnyHashable { return _stableId } - - fileprivate let clueLayout: TextViewLayout - fileprivate let emojiLayout: TextViewLayout - init(_ initialSize: NSSize, stableId:AnyHashable, clue: EmojiClue) { + fileprivate let context: AccountContext + fileprivate let canDisablePrediction: Bool + fileprivate let callback:((String)->Void)? + fileprivate let selected: String? + init(_ initialSize: NSSize, stableId:AnyHashable, context: AccountContext, clues: [String], selected: String?, canDisablePrediction: Bool, callback:((String)->Void)? = nil) { self._stableId = stableId - self.clue = clue - clueLayout = TextViewLayout(.initialize(string: clue.label, color: theme.colors.text, font: .normal(.title))) - emojiLayout = TextViewLayout(.initialize(string: clue.emoji, color: theme.colors.text, font: .normal(.title))) - emojiLayout.measure(width: .greatestFiniteMagnitude) + self.clues = clues + self.context = context + self.callback = callback + self.selected = selected + + if let selected = selected, let index = clues.firstIndex(of: selected) { + self.selectedIndex = index + } + + self.canDisablePrediction = canDisablePrediction super.init(initialSize) _ = makeSize(initialSize.width, oldWidth: 0) } - override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { - clueLayout.measure(width: width - 50) - return super.makeSize(width, oldWidth: oldWidth) - } override var height: CGFloat { return 40 @@ -45,63 +49,172 @@ class ContextClueRowItem: TableRowItem { } -private class ContextClueRowView : TableRowView { - private let clueTextView:TextView = TextView() - private let emojiTextView: TextView = TextView() +private final class ClueRowItem : TableRowItem { + private let _stableId = arc4random() + override var stableId: AnyHashable { + return _stableId + } + let layout: TextViewLayout + + init(_ initialSize: NSSize, clue: String) { + self.layout = TextViewLayout(.initialize(string: clue, color: nil, font: .normal(17))) + super.init(initialSize) + layout.measure(width: .greatestFiniteMagnitude) + } + + + override func viewClass() -> AnyClass { + return ClueRowView.self + } + + override var height: CGFloat { + return 40 + } + override var width: CGFloat { + return 40 + } +} + +private final class ClueRowView : HorizontalRowView { + private let textView: TextView = TextView() + private let containerView = View() required init(frame frameRect: NSRect) { super.init(frame: frameRect) - clueTextView.userInteractionEnabled = false - clueTextView.isSelectable = false - emojiTextView.userInteractionEnabled = false - emojiTextView.isSelectable = false - addSubview(clueTextView) - addSubview(emojiTextView) + textView.userInteractionEnabled = false + textView.isSelectable = false + addSubview(containerView) + addSubview(textView) + containerView.layer?.cornerRadius = .cornerRadius } override var backdorColor: NSColor { - if let item = item { - return item.isSelected ? theme.colors.blueSelect : theme.colors.background - } else { - return theme.colors.background + return theme.colors.background + } + + override func updateColors() { + super.updateColors() + containerView.backgroundColor = item?.isSelected == true ? theme.colors.accent : theme.colors.background + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + if let item = item as? ClueRowItem { + textView.update(item.layout) + } + } + + override func layout() { + super.layout() + containerView.frame = NSMakeRect(4, 4, frame.width - 8, frame.height - 8) + textView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private class ContextClueRowView : TableRowView, TableViewDelegate { + func selectionDidChange(row: Int, item: TableRowItem, byClick: Bool, isNew: Bool) { + if let clues = self.item as? ContextClueRowItem { + clues.selectedIndex = row + if byClick, let window = window as? Window { + if let callback = clues.callback { + callback(clues.clues[row]) + } else { + window.sendKeyEvent(.Return, modifierFlags: []) + } + } } } + func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { + return true + } + + func isSelectable(row: Int, item: TableRowItem) -> Bool { + return true + } + + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + private let button = ImageButton() + + private let tableView = HorizontalTableView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tableView) + layerContentsRedrawPolicy = .onSetNeedsDisplay + tableView.delegate = self + addSubview(button) + + button.set(handler: { [weak self] _ in + self?.disablePrediction() + }, for: .Click) + } + + private func disablePrediction() { + guard let window = self.window as? Window, let item = item as? ContextClueRowItem else { return } + let sharedContext = item.context.sharedContext + confirm(for: window, information: L10n.generalSettingsEmojiPredictionDisableText, okTitle: L10n.generalSettingsEmojiPredictionDisable, successHandler: { _ in + _ = updateBaseAppSettingsInteractively(accountManager: sharedContext.accountManager, { current in + return current.withUpdatedPredictEmoji(false) + }).start() + }) + } + + + override var backdorColor: NSColor { + return theme.colors.background + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - - if let item = item { - if !item.isSelected { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(40, frame.height - .borderSize, frame.width - 20, .borderSize)) - } - } } override func layout() { super.layout() - clueTextView.update(clueTextView.layout) - clueTextView.centerY(x: 40) - - emojiTextView.update(emojiTextView.layout) - emojiTextView.centerY(x: 10) + tableView.frame = NSMakeRect(0, 0, frame.width - (button.isHidden ? 0 : button.frame.width), frame.height) + button.centerY(x: frame.width - button.frame.width) } override func updateColors() { super.updateColors() - self.emojiTextView.backgroundColor = backdorColor - self.clueTextView.backgroundColor = backdorColor } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) + + + + button.set(image: theme.icons.disableEmojiPrediction, for: .Normal) + _ = button.sizeToFit(NSZeroSize, NSMakeSize(40, 40), thatFit: true) + + tableView.beginTableUpdates() + tableView.removeAll(redraw: true, animation: .none) if let item = item as? ContextClueRowItem { - clueTextView.update(item.clueLayout) - emojiTextView.update(item.emojiLayout) + + button.isHidden = !item.canDisablePrediction + + for clue in item.clues { + _ = tableView.addItem(item: ClueRowItem(bounds.size, clue: clue), animation: .none) + } + if let selectedIndex = item.selectedIndex { + let item = tableView.item(at: selectedIndex) + _ = tableView.select(item: item) + } + } + tableView.endTableUpdates() + + if let selectedItem = tableView.selectedItem() { + tableView.scroll(to: .center(id: selectedItem.stableId, innerId: nil, animated: animated, focus: .init(focus: false), inset: 0)) } + needsLayout = true } } diff --git a/Telegram-Mac/ContextCommandRowItem.swift b/Telegram-Mac/ContextCommandRowItem.swift index 6cdcb09c7f..a2963726a1 100644 --- a/Telegram-Mac/ContextCommandRowItem.swift +++ b/Telegram-Mac/ContextCommandRowItem.swift @@ -8,14 +8,15 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class ContextCommandRowItem: TableRowItem { fileprivate let _stableId:Int64 - fileprivate let account:Account + fileprivate let account: Account let command:PeerCommand private let title:TextViewLayout @@ -35,8 +36,8 @@ class ContextCommandRowItem: TableRowItem { title = TextViewLayout(.initialize(string: "/" + command.command.text, color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) desc = TextViewLayout(.initialize(string: command.command.description, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end) - titleSelected = TextViewLayout(.initialize(string: "/" + command.command.text, color: .white, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) - descSelected = TextViewLayout(.initialize(string: command.command.description, color: .white, font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end) + titleSelected = TextViewLayout(.initialize(string: "/" + command.command.text, color: theme.colors.underSelectedColor, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) + descSelected = TextViewLayout(.initialize(string: command.command.description, color: theme.colors.underSelectedColor, font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end) super.init(initialSize) _ = makeSize(initialSize.width, oldWidth: initialSize.width) } @@ -50,11 +51,12 @@ class ContextCommandRowItem: TableRowItem { } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) title.measure(width: width - 60) desc.measure(width: width - 60) titleSelected.measure(width: width - 60) descSelected.measure(width: width - 60) - return super.makeSize(width, oldWidth: oldWidth) + return success } var ctxTitle:TextViewLayout { @@ -75,6 +77,7 @@ class ContextCommandRowView : TableRowView { private let photoView:AvatarControl = AvatarControl(font: .avatar(.title)) required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay textView.userInteractionEnabled = false descView.userInteractionEnabled = false photoView.userInteractionEnabled = false @@ -87,8 +90,8 @@ class ContextCommandRowView : TableRowView { override func layout() { super.layout() if let item = item as? ContextCommandRowItem { - textView.update(item.ctxTitle, origin:NSMakePoint(50, floorToScreenPixels(frame.height / 2 - item.ctxTitle.layoutSize.height))) - descView.update(item.ctxDesc, origin:NSMakePoint(50, floorToScreenPixels(frame.height / 2))) + textView.update(item.ctxTitle, origin:NSMakePoint(50, floorToScreenPixels(backingScaleFactor, frame.height / 2 - item.ctxTitle.layoutSize.height))) + descView.update(item.ctxDesc, origin:NSMakePoint(50, floorToScreenPixels(backingScaleFactor, frame.height / 2))) } } @@ -98,7 +101,7 @@ class ContextCommandRowView : TableRowView { override var backdorColor: NSColor { if let item = item { - return item.isSelected ? theme.colors.blueSelect : theme.colors.background + return item.isSelected ? theme.colors.accentSelect : theme.colors.background } else { return theme.colors.background } diff --git a/Telegram-Mac/ContextHashtagRowItem.swift b/Telegram-Mac/ContextHashtagRowItem.swift new file mode 100644 index 0000000000..1883c20daa --- /dev/null +++ b/Telegram-Mac/ContextHashtagRowItem.swift @@ -0,0 +1,95 @@ +// +// ContextHashtagRowItem.swift +// Telegram +// +// Created by keepcoder on 24/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class ContextHashtagRowItem: TableRowItem { + + let hashtag: String + fileprivate let selectedTextLayout: TextViewLayout + fileprivate let textLayout: TextViewLayout + init(_ initialSize: NSSize, hashtag:String) { + self.hashtag = hashtag + textLayout = TextViewLayout(.initialize(string: hashtag, color: theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) + selectedTextLayout = TextViewLayout(.initialize(string: hashtag, color: .white, font: .normal(.text)), maximumNumberOfLines: 1) + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override var height: CGFloat { + return 40 + } + + override var stableId: AnyHashable { + return "hashtag_\(hashtag)".hashValue + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - 40) + selectedTextLayout.measure(width: width - 40) + return success + } + + override func viewClass() -> AnyClass { + return ContextHashtagRowView.self + } + +} + + +private class ContextHashtagRowView : TableRowView { + private let textView: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay + textView.userInteractionEnabled = false + textView.isSelectable = false + addSubview(textView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + textView.centerY(x: 20) + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + if let item = item, !item.isSelected, !item.isLast { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(20, frame.height - .borderSize, frame.width - 20, .borderSize)) + } + } + + override var backdorColor: NSColor { + if let item = item { + return item.isSelected ? theme.colors.accentSelect : theme.colors.background + } else { + return theme.colors.background + } + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? ContextHashtagRowItem else {return} + + textView.update(item.isSelected ? item.selectedTextLayout : item.textLayout) + needsLayout = true + } +} diff --git a/Telegram-Mac/ContextListRowItem.swift b/Telegram-Mac/ContextListRowItem.swift index 49f4ecca17..40f5395a5b 100644 --- a/Telegram-Mac/ContextListRowItem.swift +++ b/Telegram-Mac/ContextListRowItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox @@ -19,83 +20,79 @@ class ContextListRowItem: TableRowItem { let result:ChatContextResult let results:ChatContextResultCollection private let _index:Int64 - let account:Account - let iconSignal:Signal<(TransformImageArguments)->DrawingContext?,Void> + let context: AccountContext + let iconSignal:Signal let arguments:TransformImageArguments? var textLayout:(TextNodeLayout, TextNode)? let capImage:CGImage? var fileResource:TelegramMediaResource? let chatInteraction:ChatInteraction var audioWrapper:APSingleWrapper? + private(set) var file: TelegramMediaFile? private var vClass:AnyClass = ContextListImageView.self private let text:NSAttributedString override var stableId: AnyHashable { return Int64(_index) } - init(_ initialSize: NSSize, _ results:ChatContextResultCollection, _ result:ChatContextResult, _ index:Int64, _ account:Account, _ chatInteraction:ChatInteraction) { + init(_ initialSize: NSSize, _ results:ChatContextResultCollection, _ result:ChatContextResult, _ index:Int64, _ context: AccountContext, _ chatInteraction:ChatInteraction) { self.result = result self.results = results self.chatInteraction = chatInteraction self._index = index - self.account = account - var imageResource: TelegramMediaResource? + self.context = context + var representation: TelegramMediaImageRepresentation? var iconText:NSAttributedString? = nil switch result { - case let .externalReference(_, _, title, description, url, thumbnailUrl, contentUrl, contentType, _, _, _): - if let thumbnailUrl = thumbnailUrl { - imageResource = HttpReferenceMediaResource(url: thumbnailUrl, size: nil) + case let .externalReference(values): + if let thumbnail = values.thumbnail { + representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(NSMakeSize(50, 50)), resource: thumbnail.resource) } - if let contentUrl = contentUrl { - fileResource = HttpReferenceMediaResource(url: contentUrl, size: nil) - if let contentType = contentType { - if contentType.hasPrefix("audio") { - vClass = ContextListAudioView.self - audioWrapper = APSingleWrapper(resource: fileResource!, name: title, performer: description, id:result.maybeId) - } else if contentType == "video/mp4" { - vClass = ContextListGIFView.self - } + if let content = values.content { + if content.mimeType.hasPrefix("audio") { + vClass = ContextListAudioView.self + audioWrapper = APSingleWrapper(resource: content.resource, name: values.title, performer: values.description, id: result.maybeId) + } else if content.mimeType == "video/mp4" { + vClass = ContextListGIFView.self } } var selectedUrl: String? - if let url = url { + if let url = values.url { selectedUrl = url - } else if let contentUrl = contentUrl { - selectedUrl = contentUrl } if let selectedUrl = selectedUrl, let parsedUrl = URL(string: selectedUrl) { if let host = parsedUrl.host, !host.isEmpty { - iconText = NSAttributedString.initialize(string: host.substring(to: host.index(after: host.startIndex)).uppercased(), color: .white, font: .medium(.custom(25))) + iconText = NSAttributedString.initialize(string: host.substring(to: host.index(after: host.startIndex)).uppercased(), color: .white, font: .medium(25.0)) } } - case let .internalReference(_, _, title, description, image, file, _): + case let .internalReference(values): if let file = file { + self.file = file fileResource = file.resource if file.isMusic || file.isVoice { vClass = ContextListAudioView.self - audioWrapper = APSingleWrapper(resource: fileResource!, name: title, performer: description, id:result.maybeId) + audioWrapper = APSingleWrapper(resource: fileResource!, name: values.title, performer: values.description, id: result.maybeId) } else if file.isVideo && file.isAnimated { vClass = ContextListGIFView.self } } - if let image = image { - imageResource = smallestImageRepresentation(image.representations)?.resource + if let image = values.image { + representation = smallestImageRepresentation(image.representations) } else if let file = file { - imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource + representation = smallestImageRepresentation(file.previewRepresentations) } } - if let imageResource = imageResource { - let iconRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 55.0, height: 55.0), resource: imageResource) - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconRepresentation]) - iconSignal = chatWebpageSnippetPhoto(account: account, photo: tmpImage, scale: 2.0, small:true) + if let representation = representation { + let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [representation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + iconSignal = chatWebpageSnippetPhoto(account: context.account, imageReference: ImageMediaReference.standalone(media: tmpImage), scale: 2.0, small:true) - let iconSize = iconRepresentation.dimensions.aspectFilled(CGSize(width: 50, height: 50)) + let iconSize = representation.dimensions.size.aspectFilled(CGSize(width: 50, height: 50)) let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0)) - arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: NSEdgeInsets()) + arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.size, boundingSize: iconSize, intrinsicInsets: NSEdgeInsets()) iconText = nil } else { arguments = nil @@ -104,7 +101,7 @@ class ContextListRowItem: TableRowItem { if iconText == nil { if let title = result.title, !title.isEmpty { let titleText = title.substring(to: title.index(after: title.startIndex)).uppercased() - iconText = NSAttributedString.initialize(string: titleText, color: .white, font: .medium(.custom(25))) + iconText = .initialize(string: titleText, color: .white, font: .medium(25.0)) } } } @@ -131,12 +128,13 @@ class ContextListRowItem: TableRowItem { self.text = attr.copy() as! NSAttributedString super.init(initialSize) - prepare(isSelected) + _ = makeSize(initialSize.width, oldWidth: 0) } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) prepare(isSelected) - return super.makeSize(width, oldWidth: oldWidth) + return success } override func prepare(_ selected: Bool) { @@ -157,7 +155,7 @@ class ContextListRowItem: TableRowItem { class ContextListRowView : TableRowView { override var backdorColor: NSColor { - return item?.isSelected ?? false ? theme.colors.blueSelect : theme.colors.background + return item?.isSelected ?? false ? theme.colors.accentSelect : theme.colors.background } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) @@ -171,18 +169,25 @@ class ContextListRowView : TableRowView { if let layout = item.textLayout { let f = focus(layout.0.size) - layout.1.draw(NSMakeRect(item.textInset.left, f.minY, layout.0.size.width, layout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + layout.1.draw(NSMakeRect(item.textInset.left, f.minY, layout.0.size.width, layout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } - needsLayout = true + } } + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + needsDisplay = true + needsLayout = true + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay } } @@ -190,12 +195,13 @@ class ContextListImageView : TableRowView { let image:TransformImageView = TransformImageView() required init(frame frameRect: NSRect) { super.init(frame:frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay image.setFrameSize(NSMakeSize(50, 50)) addSubview(image) } override var backdorColor: NSColor { - return item?.isSelected ?? false ? theme.colors.blueSelect : theme.colors.background + return item?.isSelected ?? false ? theme.colors.accentSelect : theme.colors.background } override func layout() { @@ -218,7 +224,7 @@ class ContextListImageView : TableRowView { if let layout = item.textLayout { let f = focus(layout.0.size) - layout.1.draw(NSMakeRect(item.textInset.left, f.minY, layout.0.size.width, layout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + layout.1.draw(NSMakeRect(item.textInset.left, f.minY, layout.0.size.width, layout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } needsLayout = true } @@ -232,9 +238,10 @@ class ContextListImageView : TableRowView { if let capImage = item.capImage { self.image.layer?.contents = capImage } else { - image.setSignal(account: item.account, signal: item.iconSignal) + image.setSignal( item.iconSignal) } } + needsDisplay = true } required init?(coder: NSCoder) { @@ -264,10 +271,10 @@ class ContextListGIFView : ContextListRowView { override func set(item: TableRowItem, animated: Bool) { let updated = self.item != item - super.set(item: item) + super.set(item: item, animated: animated) - if let item = item as? ContextListRowItem, updated, let resource = item.fileResource { - player.update(with: resource, size: NSMakeSize(50,50), viewSize: NSMakeSize(50,50), account: item.account, table: item.table, iconSignal: item.iconSignal) + if let item = item as? ContextListRowItem, updated, let file = item.file { + player.update(with: FileMediaReference.standalone(media: file), size: NSMakeSize(50,50), viewSize: NSMakeSize(50,50), context: item.context, table: item.table, iconSignal: item.iconSignal) player.needsLayout = true } } @@ -286,6 +293,7 @@ class ContextListAudioView : ContextListRowView, APDelegate { progressView.fetchControls = FetchControls(fetch: { [weak self] in self?.checkOperation() }) + layerContentsRedrawPolicy = .onSetNeedsDisplay addSubview(progressView) } @@ -300,7 +308,7 @@ class ContextListAudioView : ContextListRowView, APDelegate { if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: wrapper) { controller.playOrPause() } else { - let controller = APSingleResourceController(account: item.account, wrapper: wrapper) + let controller = APSingleResourceController(account: item.context.account, wrapper: wrapper, streamable: false) controller.add(listener: self) item.chatInteraction.inlineAudioPlayer(controller) controller.start() @@ -337,12 +345,12 @@ class ContextListAudioView : ContextListRowView, APDelegate { func checkState() { if let item = item as? ContextListRowItem, let wrapper = item.audioWrapper, let controller = globalAudio, let song = controller.currentSong { if song.entry.isEqual(to: wrapper), case .playing = song.state { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPause, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.accent, foregroundColor: .white, icon: theme.icons.chatMusicPause, iconInset:NSEdgeInsets(left:1)) } else { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.accent, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) } } else { - progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + progressView.theme = RadialProgressTheme(backgroundColor: theme.colors.accent, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) } } @@ -353,11 +361,11 @@ class ContextListAudioView : ContextListRowView, APDelegate { override func set(item: TableRowItem, animated: Bool) { let updated = self.item != item - super.set(item: item) + super.set(item: item, animated: animated) if let item = item as? ContextListRowItem, updated, let resource = item.fileResource { - let updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(resource) |> deliverOnMainQueue + let updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(resource) |> deliverOnMainQueue statusDisposable.set(updatedStatusSignal.start(next: { [weak self] status in if let strongSelf = self { diff --git a/Telegram-Mac/ContextMediaRowItem.swift b/Telegram-Mac/ContextMediaRowItem.swift index 23b8d5f8ef..9001b460cb 100644 --- a/Telegram-Mac/ContextMediaRowItem.swift +++ b/Telegram-Mac/ContextMediaRowItem.swift @@ -8,27 +8,41 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +final class ContextMediaArguments { + let sendResult: (ChatContextResult, NSView) -> Void + let menuItems: (TelegramMediaFile, NSView) -> Signal<[ContextMenuItem], NoError> + let openMessage: (Message) -> Void + let messageMenuItems: (Message, NSView) -> Signal<[ContextMenuItem], NoError> + + init(sendResult: @escaping(ChatContextResult, NSView) -> Void = { _, _ in }, menuItems: @escaping(TelegramMediaFile, NSView) -> Signal<[ContextMenuItem], NoError> = { _, _ in return .single([]) }, openMessage: @escaping(Message) -> Void = { _ in }, messageMenuItems:@escaping (Message, NSView) -> Signal<[ContextMenuItem], NoError> = { _, _ in return .single([]) }) { + self.sendResult = sendResult + self.menuItems = menuItems + self.openMessage = openMessage + self.messageMenuItems = messageMenuItems + } +} + class ContextMediaRowItem: TableRowItem { let result:InputMediaContextRow - let results:ChatContextResultCollection private let _index:Int64 - let account:Account - let chatInteraction:ChatInteraction + let context: AccountContext + let arguments: ContextMediaArguments override var stableId: AnyHashable { return Int64(_index) } - init(_ initialSize: NSSize, _ results:ChatContextResultCollection, _ result:InputMediaContextRow, _ index:Int64, _ account:Account, _ chatInteraction:ChatInteraction) { + init(_ initialSize: NSSize, _ result:InputMediaContextRow, _ index:Int64, _ context: AccountContext, _ arguments: ContextMediaArguments) { self.result = result - self.results = results - self.chatInteraction = chatInteraction + self.arguments = arguments self._index = index - self.account = account + self.context = context dif = 0 super.init(initialSize) } @@ -41,56 +55,230 @@ class ContextMediaRowItem: TableRowItem { return height } + func contains(_ messageId: MessageId) -> Bool { + if self.result.messages.contains(where: { $0.id == messageId }) { + return true + } + return false + } + override func viewClass() -> AnyClass { return ContextMediaRowView.self } + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var inset:CGFloat = 0 + var i:Int = 0 + for size in result.sizes { + if location.x > inset && location.x < inset + size.width { + if !result.messages.isEmpty { + if let view = self.view { + let items = arguments.messageMenuItems(result.messages[i], view.subviews[i]) + return items + } + } else { + switch result.results[i] { + case let .internalReference(values): + if let file = values.file, let view = self.view { + let items = arguments.menuItems(file, view.subviews[i]) + return items + } + default: + break + } + } + break + } + inset += size.width + i += 1 + } + return .single([]) + } + } private var dif:CGFloat = 0 -class ContextMediaRowView: TableRowView { - private let stickerFetchedDisposable:MetaDisposable = MetaDisposable() +class ContextMediaRowView: TableRowView, ModalPreviewRowViewProtocol { + + private let stickerFetchedDisposable:MetaDisposable = MetaDisposable() + private let longDisposable = MetaDisposable() deinit { stickerFetchedDisposable.dispose() + longDisposable.dispose() } + func previewMediaIfPossible() -> Bool { + if let item = self.item as? ContextMediaRowItem, let table = item.table, let window = window as? Window { + _ = startModalPreviewHandle(table, window: window, context: item.context) + } + return true + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + + let signal = Signal.complete() |> delay(0.2, queue: .mainQueue()) + + let downIndex = self.index(at: convert(event.locationInWindow, to: nil)) + + longDisposable.set(signal.start(completed: { [weak self] in + guard let `self` = self, let window = self.window else { + return + } + let nextIndex = self.index(at: self.convert(window.mouseLocationOutsideOfEventStream, to: nil)) + if nextIndex == downIndex { + _ = self.previewMediaIfPossible() + } + })) + } + + + override func forceClick(in location: NSPoint) { + if mouseInside() == true { + let result = previewMediaIfPossible() + if !result { + super.forceClick(in: location) + } + } else { + super.forceClick(in: location) + } + + } + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + guard let item = item as? ContextMediaRowItem else {return nil} + for i in 0 ..< self.subviews.count { + if NSPointInRect(point, self.subviews[i].frame) { + switch item.result.entries[i] { + case let .gif(data): + return (.file(data.file, GifPreviewModalView.self), self.subviews[i]) + case let .sticker(_, file): + let reference = file.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file) : FileMediaReference.standalone(media: file) + if file.isAnimatedSticker { + return (.file(reference, AnimatedStickerPreviewModalView.self), self.subviews[i]) + } else { + return (.file(reference, StickerPreviewModalView.self), self.subviews[i]) + } + default: + break + } + } + } + return nil + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool) -> NSView { + if let innerId = innerId.base as? MessageId { + let view = self.subviews.first(where: { + ($0 as? GIFContainerView)?.associatedMessageId == innerId + }) + return view ?? self + } + return self + } + + + override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) + + var subviews = self.subviews + + self.removeAllSubviews() - removeAllSubviews() if let item = item as? ContextMediaRowItem { var inset:CGFloat = 0 for i in 0 ..< item.result.entries.count { let container:NSView switch item.result.entries[i] { case let .gif(data): - let view = GIFContainerView() - let signal:Signal<(TransformImageArguments) -> DrawingContext?, NoError> - if let thumb = data.thumb { - signal = chatWebpageSnippetPhoto(account: item.account, photo: thumb, scale: backingScaleFactor, small:true) + let view: GIFContainerView + let index = subviews.firstIndex(where: { $0 is GIFContainerView }) + if let index = index { + view = subviews.remove(at: index) as! GIFContainerView + inner: for view in view.subviews { + if view.identifier == NSUserInterfaceItemIdentifier("gif-separator") { + view.removeFromSuperview() + break inner + } + } } else { - signal = .never() + view = GIFContainerView() } - view.update(with: data.file, size: NSMakeSize(item.result.sizes[i].width, item.height), viewSize: item.result.sizes[i], account: item.account, table: item.table, iconSignal: signal) - container = view - case let .sticker(data): - let view = TransformImageView() - view.setSignal(account: item.account, signal: chatMessageSticker(account: item.account, file: data.file, type: .small, scale: backingScaleFactor)) - _ = fileInteractiveFetched(account: item.account, file: data.file).start() + var effectiveFile = data.file - let imageSize = item.result.sizes[i].aspectFitted(NSMakeSize(item.height, item.height - 8)) - view.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets())) + if let preview = data.file.media.videoThumbnails.first { + + let file = effectiveFile.media.withUpdatedResource(preview.resource) + + switch data.file { + case let .message(message, _): + effectiveFile = FileMediaReference.message(message: message, media: file) + case .standalone: + effectiveFile = FileMediaReference.standalone(media: file) + case .savedGif: + effectiveFile = FileMediaReference.savedGif(media: file) + case let .stickerPack(stickerPack, _): + effectiveFile = FileMediaReference.stickerPack(stickerPack: stickerPack, media: file) + case let .webPage(webPage, _): + effectiveFile = FileMediaReference.webPage(webPage: webPage, media: file) + case let .avatarList(peer: reference, media: media): + effectiveFile = FileMediaReference.avatarList(peer: reference, media: media) + } + + } + let signal = chatMessageVideo(postbox: item.context.account.postbox, fileReference: effectiveFile, scale: backingScaleFactor) - view.setFrameSize(imageSize) + + view.update(with: effectiveFile, size: NSMakeSize(item.result.sizes[i].width, item.height - 2), viewSize: item.result.sizes[i], context: item.context, table: item.table, iconSignal: signal) + if i != (item.result.entries.count - 1) { + let layer = View() + layer.identifier = NSUserInterfaceItemIdentifier("gif-separator") + layer.frame = NSMakeRect(view.frame.width - 2.0, 0, 2.0, view.frame.height) + layer.background = theme.colors.background + view.addSubview(layer) + } + view.userInteractionEnabled = false container = view + case let .sticker(data): + if data.file.isAnimatedSticker { + let view: MediaAnimatedStickerView + let index = subviews.firstIndex(where: { $0 is MediaAnimatedStickerView}) + if let index = index { + view = subviews.remove(at: index) as! MediaAnimatedStickerView + } else { + view = MediaAnimatedStickerView(frame: NSZeroRect) + } + let size = NSMakeSize(round(item.result.sizes[i].width), round(item.result.sizes[i].height)) + view.update(with: data.file, size: size, context: item.context, parent: nil, table: item.table, parameters: nil, animated: false, positionFlags: nil, approximateSynchronousValue: false) + view.userInteractionEnabled = false + + container = view + } else { + let view: TransformImageView + let index = subviews.firstIndex(where: { $0 is TransformImageView}) + if let index = index { + view = subviews.remove(at: index) as! TransformImageView + } else { + view = TransformImageView() + } + + view.setSignal(chatMessageSticker(postbox: item.context.account.postbox, file: data.file, small: true, scale: backingScaleFactor, fetched: true)) + let imageSize = item.result.sizes[i].aspectFitted(NSMakeSize(item.height, item.height - 8)) + view.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets())) + + view.setFrameSize(imageSize) + container = view + } + case let .photo(data): let view = View() let imageView = TransformImageView() - imageView.setSignal(account: item.account, signal: chatWebpageSnippetPhoto(account: item.account, photo: data, scale: backingScaleFactor, small:false)) - _ = chatMessagePhotoInteractiveFetched(account: item.account, photo: data).start() + imageView.setSignal(chatWebpageSnippetPhoto(account: item.context.account, imageReference: ImageMediaReference.standalone(media: data), scale: backingScaleFactor, small:false)) + _ = chatMessagePhotoInteractiveFetched(account: item.context.account, imageReference: ImageMediaReference.standalone(media: data)).start() let imageSize = item.result.sizes[i] imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets())) @@ -101,9 +289,8 @@ class ContextMediaRowView: TableRowView { imageView.center() view.addSubview(imageView) container = view - - break } + container.setFrameOrigin(inset, 0) container.background = theme.colors.background addSubview(container) @@ -114,22 +301,30 @@ class ContextMediaRowView: TableRowView { } } - + func index(at point: NSPoint) -> Int? { + if let _ = item as? ContextMediaRowItem { + for (i, subview) in self.subviews.enumerated() { + if NSPointInRect(point, subview.frame) { + return i + } + } + } + return nil + } override func mouseUp(with event: NSEvent) { super.mouseUp(with: event) - let point = convert(event.locationInWindow, from: nil) - if let item = item as? ContextMediaRowItem { - var inset:CGFloat = 0 - var i:Int = 0 - for size in item.result.sizes { - - if point.x > inset && point.x < inset + size.width { - item.chatInteraction.sendInlineResult(item.results, item.result.results[i]) - break + + longDisposable.set(nil) + + if let item = item as? ContextMediaRowItem, event.clickCount == 1 { + let point = convert(event.locationInWindow, from: nil) + if let index = self.index(at: point) { + if !item.result.messages.isEmpty { + item.arguments.openMessage(item.result.messages[index]) + } else { + item.arguments.sendResult(item.result.results[index], self.subviews[index]) } - inset += size.width - i += 1 } } } @@ -160,4 +355,7 @@ class ContextMediaRowView: TableRowView { } } + + + } diff --git a/Telegram-Mac/ContextSearchMessageItem.swift b/Telegram-Mac/ContextSearchMessageItem.swift new file mode 100644 index 0000000000..3355db5648 --- /dev/null +++ b/Telegram-Mac/ContextSearchMessageItem.swift @@ -0,0 +1,301 @@ +// +// ContextSearchMessageItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/11/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import Postbox + + +class ContextSearchMessageItem: GeneralRowItem { + + let message:Message + + let context: AccountContext + let peer:Peer + var peerId:PeerId { + return peer.id + } + + let photo: AvatarNodeState + + + override var stableId: AnyHashable { + return message.id + } + + + private var date:NSAttributedString? + + private var displayLayout:(TextNodeLayout, TextNode)? + + private var displaySelectedLayout:(TextNodeLayout, TextNode)? + private var dateLayout:(TextNodeLayout, TextNode)? + private var dateSelectedLayout:(TextNodeLayout, TextNode)? + + private var displayNode:TextNode = TextNode() + private var displaySelectedNode:TextNode = TextNode() + + private let titleText:NSAttributedString + + private var messageLayout: TextViewLayout + private var messageSelectedLayout: TextViewLayout + + + init(_ initialSize:NSSize, context: AccountContext, message: Message, searchText: String, action: @escaping()->Void) { + self.context = context + self.message = message + + + self.peer = message.chatPeer(context.peerId)! + + var peer:Peer = self.peer + + var title:String = peer.displayTitle + if let _peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = _peer.info { + title = _peer.displayTitle + peer = _peer + } + + + var nameColor:NSColor = theme.chat.linkColor(true, false) + + if messageMainPeer(message) is TelegramChannel || messageMainPeer(message) is TelegramGroup { + if let peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = peer.info { + nameColor = theme.chat.linkColor(true, false) + } else if context.peerId != peer.id { + let value = abs(Int(peer.id.id) % 7) + nameColor = theme.chat.peerName(value) + } + } + + let titleText:NSMutableAttributedString = NSMutableAttributedString() + let _ = titleText.append(string: title, color: nameColor, font: .medium(.text)) + titleText.setSelected(color: theme.colors.underSelectedColor ,range: titleText.range) + + self.titleText = titleText + let messageTitle = NSMutableAttributedString() + + var text = pullText(from: message) as String + if text.isEmpty { + text = serviceMessageText(message, account: context.account) + } + _ = messageTitle.append(string: text, color: theme.colors.text, font: .normal(.text)) + + + self.messageLayout = TextViewLayout(messageTitle, maximumNumberOfLines: 1, truncationType: .end, strokeLinks: true) + let selectRange = messageTitle.string.lowercased().nsstring.range(of: searchText.lowercased()) + if selectRange.location != NSNotFound { + self.messageLayout.additionalSelections = [TextSelectedRange(range: selectRange, color: theme.colors.accentIcon.withAlphaComponent(0.5), def: false)] + } + + + let selectedAttrText = messageTitle.mutableCopy() as! NSMutableAttributedString + selectedAttrText.addAttribute(.foregroundColor, value: NSColor.white, range: selectedAttrText.range) + self.messageSelectedLayout = TextViewLayout(selectedAttrText, maximumNumberOfLines: 1, truncationType: .end, strokeLinks: true) + + + let date:NSMutableAttributedString = NSMutableAttributedString() + var time:TimeInterval = TimeInterval(message.timestamp) + time -= context.timeDifference + let range = date.append(string: DateUtils.string(forMessageListDate: Int32(time)), color: theme.colors.grayText, font: .normal(.short)) + date.setSelected(color: theme.colors.underSelectedColor, range: range) + self.date = date.copy() as? NSAttributedString + + dateLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, false, .left) + dateSelectedLayout = TextNode.layoutText(maybeNode: nil, date, nil, 1, .end, NSMakeSize( .greatestFiniteMagnitude, 20), nil, true, .left) + + self.photo = .PeerAvatar(peer, peer.displayLetters, peer.smallProfileImage, message) + + super.init(initialSize, height: 44, action: action) + + _ = makeSize(initialSize.width, oldWidth: 0) + } + + let margin:CGFloat = 5 + + var titleWidth:CGFloat { + var dateSize:CGFloat = 0 + if let dateLayout = dateLayout { + dateSize = dateLayout.0.size.width + } + + return size.width - 50 - margin * 4 - dateSize + } + var messageWidth:CGFloat { + var dateSize:CGFloat = 0 + if let dateLayout = dateLayout { + dateSize = dateLayout.0.size.width + } + return size.width - 70 - margin * 4 - dateSize + } + + let leftInset:CGFloat = 40 + 20; + + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let result = super.makeSize(width, oldWidth: oldWidth) + if displayLayout == nil || !displayLayout!.0.isPerfectSized || self.oldWidth > width { + displayLayout = TextNode.layoutText(maybeNode: displayNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, false, .left) + } + + messageLayout.measure(width: messageWidth) + messageSelectedLayout.measure(width: messageWidth) + + if displaySelectedLayout == nil || !displaySelectedLayout!.0.isPerfectSized || self.oldWidth > width { + displaySelectedLayout = TextNode.layoutText(maybeNode: displaySelectedNode, titleText, nil, 1, .end, NSMakeSize(titleWidth, size.height), nil, true, .left) + } + + return result + } + + + + + var ctxDisplayLayout:(TextNodeLayout, TextNode)? { + if isSelected { + return displaySelectedLayout + } + return displayLayout + } + var ctxMessageLayout: TextViewLayout { + if isSelected { + return messageSelectedLayout + } + + return messageLayout + } + var ctxDateLayout:(TextNodeLayout, TextNode)? { + if isSelected { + return dateSelectedLayout + } + return dateLayout + } + + override var instantlyResize: Bool { + return true + } + + + override func viewClass() -> AnyClass { + return ContextSearchMessageView.self + } +} + +private class ContextSearchMessageView : GeneralRowView { + + + private var titleText:TextNode = TextNode() + private var messageText:TextView = TextView() + private var photo:AvatarControl = AvatarControl(font: .avatar(22)) + + + + override var isFlipped: Bool { + return true + } + + + + + override var backdorColor: NSColor { + if let item = item { + if item.isHighlighted && !item.isSelected { + return theme.colors.grayHighlight + } else if item.isSelected { + return theme.chatList.selectedBackgroundColor + } + + } + + return theme.colors.background + } + + + override func draw(_ layer: CALayer, in ctx: CGContext) { + + + super.draw(layer, in: ctx) + // + if let item = self.item as? ContextSearchMessageItem { + + + if !item.isSelected { + + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(layer.bounds) + } + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.leftInset, NSHeight(layer.bounds) - .borderSize, layer.bounds.width - item.leftInset, .borderSize)) + } + + + + if let displayLayout = item.ctxDisplayLayout { + + displayLayout.1.draw(NSMakeRect(item.leftInset, item.margin - 1, displayLayout.0.size.width, displayLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + + + if let dateLayout = item.ctxDateLayout { + let dateX = frame.width - dateLayout.0.size.width - 20 + let dateFrame = focus(dateLayout.0.size) + dateLayout.1.draw(NSMakeRect(dateX, dateFrame.minY, dateLayout.0.size.width, dateLayout.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + } + } + } + + } + + + + required init(frame frameRect: NSRect) { + + + super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay + photo.userInteractionEnabled = false + photo.frame = NSMakeRect(20, 8, 30, 30) + addSubview(photo) + addSubview(messageText) + messageText.userInteractionEnabled = false + messageText.isSelectable = false + + } + + override func layout() { + super.layout() + guard let item = item as? ContextSearchMessageItem else {return} + photo.centerY(x: 20) + messageText.setFrameOrigin(item.leftInset, frame.height - messageText.frame.height - item.margin - 1) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func updateColors() { + super.updateColors() + messageText.backgroundColor = backdorColor + } + + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? ContextSearchMessageItem else {return} + + photo.setState(account: item.context.account, state: item.photo) + messageText.update(item.ctxMessageLayout) + } + +} diff --git a/Telegram-Mac/ContextShowPeersHolder.swift b/Telegram-Mac/ContextShowPeersHolder.swift new file mode 100644 index 0000000000..c6528a4de3 --- /dev/null +++ b/Telegram-Mac/ContextShowPeersHolder.swift @@ -0,0 +1,66 @@ +// +// ContextShowPeersHolder.swift +// Telegram +// +// Created by Mikhail Filimonov on 06.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +class ContextShowPeersHolderItem: GeneralRowItem { + fileprivate let textLayout: TextViewLayout + init(_ initialSize: NSSize, stableId: AnyHashable, action: @escaping()->Void) { + textLayout = TextViewLayout.init(.initialize(string: "Show All Users", color: theme.colors.accent, font: .normal(.text)), maximumNumberOfLines: 1) + super.init(initialSize, height: 40, stableId: stableId, action: action) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + self.textLayout.measure(width: width - 60) + return true + } + + override func viewClass() -> AnyClass { + return ContextShowPeersHolderView.self + } +} + + +private final class ContextShowPeersHolderView : TableRowView { + private let textView: TextView = TextView() + private let borderView = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + addSubview(borderView) + textView.isSelectable = false + textView.userInteractionEnabled = false + } + + override func updateColors() { + super.updateColors() + self.textView.backgroundColor = theme.colors.background + borderView.backgroundColor = theme.colors.border + } + + override func layout() { + super.layout() + self.textView.center() + self.borderView.frame = NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + guard let item = item as? ContextShowPeersHolderItem else { + return + } + self.textView.update(item.textLayout) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ContextStickerRowItem.swift b/Telegram-Mac/ContextStickerRowItem.swift index d12767ad3f..787b982db8 100644 --- a/Telegram-Mac/ContextStickerRowItem.swift +++ b/Telegram-Mac/ContextStickerRowItem.swift @@ -7,23 +7,24 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox import TGUIKit class ContextStickerRowItem: TableRowItem { let result:InputMediaStickersRow - fileprivate let account:Account + fileprivate let context:AccountContext fileprivate let _stableId:Int64 fileprivate let chatInteraction:ChatInteraction var selectedIndex:Int? = nil override var stableId: AnyHashable { return _stableId } - init(_ initialSize:NSSize, _ account:Account, _ entry:InputMediaStickersRow, _ stableId:Int64, _ chatInteraction:ChatInteraction) { - self.account = account + init(_ initialSize:NSSize, _ context: AccountContext, _ entry:InputMediaStickersRow, _ stableId:Int64, _ chatInteraction:ChatInteraction) { + self.context = context self.result = entry self.chatInteraction = chatInteraction self._stableId = stableId @@ -42,15 +43,21 @@ class ContextStickerRowItem: TableRowItem { -class ContextStickerRowView : TableRowView, StickerPreviewRowViewProtocol { +class ContextStickerRowView : TableRowView, ModalPreviewRowViewProtocol { - func fileAtPoint(_ point:NSPoint) -> TelegramMediaFile? { + func fileAtPoint(_ point:NSPoint) -> (QuickPreviewMedia, NSView?)? { if let item = item as? ContextStickerRowItem { var i:Int = 0 for subview in subviews { if point.x > subview.frame.minX && point.x < subview.frame.maxX { - return item.result.results[i].file + let file = item.result.results[i].file + let reference = file.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file) : FileMediaReference.standalone(media: file) + if file.isAnimatedSticker { + return (.file(reference, AnimatedStickerPreviewModalView.self), subview) + } else { + return (.file(reference, StickerPreviewModalView.self), subview) + } } i += 1 } @@ -58,56 +65,136 @@ class ContextStickerRowView : TableRowView, StickerPreviewRowViewProtocol { return nil } + override func menu(for event: NSEvent) -> NSMenu? { + let menu = NSMenu() + if let item = item as? ContextStickerRowItem { + + let reference = fileAtPoint(convert(event.locationInWindow, from: nil)) + + if let reference = reference?.0.fileReference?.media.stickerReference { + menu.addItem(ContextMenuItem(L10n.contextViewStickerSet, handler: { + showModal(with: StickerPackPreviewModalController(item.context, peerId: item.chatInteraction.peerId, reference: reference), for: mainWindow) + })) + } + if let file = reference?.0.fileReference?.media { + menu.addItem(ContextMenuItem(L10n.chatSendWithoutSound, handler: { [weak item] in + item?.chatInteraction.sendAppFile(file, true) + item?.chatInteraction.clearInput() + })) + } + + } + return menu + } + override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) - removeAllSubviews() if let item = item as? ContextStickerRowItem { + + while subviews.count > item.result.entries.count { + subviews.last?.removeFromSuperview() + } + while subviews.count < item.result.entries.count { + addSubview(Control()) + } + + for i in 0 ..< item.result.entries.count { - let container:Control = Control() - - - container.set(background: theme.colors.grayBackground, for: .Highlight) + let container:Control = self.subviews[i] as! Control + container.removeAllHandlers() + + if item.selectedIndex == i { - container.set(background: theme.colors.blueSelect, for: .Normal) - container.set(background: theme.colors.blueSelect, for: .Hover) - container.set(background: theme.colors.blueUI, for: .Highlight) + container.set(background: theme.colors.grayBackground, for: .Normal) + container.set(background: theme.colors.grayBackground, for: .Hover) + container.set(background: theme.colors.grayBackground, for: .Highlight) + container.apply(state: .Normal) + } else { + container.set(background: theme.colors.background, for: .Normal) + container.set(background: theme.colors.background, for: .Hover) + container.set(background: theme.colors.background, for: .Highlight) container.apply(state: .Normal) } + container.layer?.cornerRadius = .cornerRadius switch item.result.entries[i] { case let .sticker(data): - container.set(handler: { [weak item] (control) in - item?.chatInteraction.sendAppFile(data.file) - item?.chatInteraction.clearInput() + container.set(handler: { [weak item] control in + if let slowMode = item?.chatInteraction.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: control) + } else { + item?.chatInteraction.sendAppFile(data.file, false) + item?.chatInteraction.clearInput() + } }, for: .Click) container.set(handler: { [weak self, weak item] (control) in if let window = self?.window as? Window, let item = item, let table = item.table { - _ = startStickerPreviewHandle(table, window: window, account: item.account) + _ = startModalPreviewHandle(table, window: window, context: item.context) } }, for: .LongMouseDown) - let view = TransformImageView() - view.setSignal(account: item.account, signal: chatMessageSticker(account: item.account, file: data.file, type: .small, scale: backingScaleFactor)) - _ = fileInteractiveFetched(account: item.account, file: data.file).start() - - let imageSize = data.file.dimensions?.aspectFitted(NSMakeSize(item.result.sizes[i].width - 8, item.result.sizes[i].height - 8)) ?? item.result.sizes[i] - view.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets())) + + if data.file.isAnimatedSticker { + let view: MediaAnimatedStickerView + if container.subviews.isEmpty { + view = MediaAnimatedStickerView(frame: .zero) + container.addSubview(view) + } else { + let temp = container.subviews.first as? MediaAnimatedStickerView + if temp == nil { + view = MediaAnimatedStickerView(frame: .zero) + container.subviews.removeFirst() + container.addSubview(view, positioned: .below, relativeTo: container.subviews.first) + } else { + view = temp! + } + } + let size = NSMakeSize(round(item.result.sizes[i].width - 8), round(item.result.sizes[i].height - 8)) + view.update(with: data.file, size: size, context: item.context, parent: nil, table: item.table, parameters: nil, animated: false, positionFlags: nil, approximateSynchronousValue: false) + view.userInteractionEnabled = false + } else { + let file = data.file + let imageSize = file.dimensions?.size.aspectFitted(NSMakeSize(item.result.sizes[i].width - 8, item.result.sizes[i].height - 8)) ?? item.result.sizes[i] + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) + + let view: TransformImageView + if container.subviews.isEmpty { + view = TransformImageView() + container.addSubview(view) + } else { + let temp = container.subviews.first as? TransformImageView + if temp == nil { + view = TransformImageView() + container.subviews.removeFirst() + container.addSubview(view, positioned: .below, relativeTo: container.subviews.first) + } else { + view = temp! + } + } + + view.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor), clearInstantly: false) + view.setSignal( chatMessageSticker(postbox: item.context.account.postbox, file: data.file, small: false, scale: backingScaleFactor, fetched: true), cacheImage: { [weak file] result in + if let file = file { + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + } + }) + + view.set(arguments: arguments) + + view.setFrameSize(imageSize) + } - view.setFrameSize(imageSize) - container.addSubview(view) container.setFrameSize(NSMakeSize(item.result.sizes[i].width - 4, item.result.sizes[i].height - 4)) default: fatalError("ContextStickerRowItem support only stickers") } - addSubview(container) - } needsLayout = true @@ -122,7 +209,7 @@ class ContextStickerRowView : TableRowView, StickerPreviewRowViewProtocol { if let item = item as? ContextStickerRowItem { let defSize = NSMakeSize( item.result.sizes[0].width - 4, item.result.sizes[0].height - 4) - let defInset = floorToScreenPixels((frame.width - defSize.width * CGFloat(item.result.maxCount)) / CGFloat(item.result.maxCount + 1)) + let defInset = floorToScreenPixels(backingScaleFactor, (frame.width - defSize.width * CGFloat(item.result.maxCount)) / CGFloat(item.result.maxCount + 1)) var inset = defInset for i in 0 ..< item.result.entries.count { diff --git a/Telegram-Mac/ContextSwitchPeerRowItem.swift b/Telegram-Mac/ContextSwitchPeerRowItem.swift index c59dc3f247..51b2ea86c0 100644 --- a/Telegram-Mac/ContextSwitchPeerRowItem.swift +++ b/Telegram-Mac/ContextSwitchPeerRowItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ContextSwitchPeerRowItem: TableRowItem { fileprivate let account:Account fileprivate let peerId:PeerId @@ -28,8 +29,9 @@ class ContextSwitchPeerRowItem: TableRowItem { } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) layout.measure(width: width - 40) - return super.makeSize(width, oldWidth: oldWidth) + return success } override func viewClass() -> AnyClass { @@ -47,6 +49,7 @@ class ContextSwitchPeerRowView: TableRowView { private let overlay:OverlayControl = OverlayControl() required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay textView.userInteractionEnabled = false addSubview(overlay) addSubview(textView) diff --git a/Telegram-Mac/ControllerExtension.swift b/Telegram-Mac/ControllerExtension.swift index 3e75d88088..82bb3fd458 100644 --- a/Telegram-Mac/ControllerExtension.swift +++ b/Telegram-Mac/ControllerExtension.swift @@ -7,35 +7,33 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit class TelegramGenericViewController: GenericViewController where T:NSView { - let account:Account + let context:AccountContext private let languageDisposable:MetaDisposable = MetaDisposable() - init(_ account:Account) { - self.account = account + init(_ context:AccountContext) { + self.context = context super.init() } - + override func viewDidLoad() { super.viewDidLoad() - let ignore:Atomic = Atomic(value: true) - languageDisposable.set(combineLatest(appearanceSignal, ready.get() |> deliverOnMainQueue |> take(1)).start(next: { [weak self] _ in - if !ignore.swap(false) { - self?.updateLocalizationAndTheme() - } + languageDisposable.set(appearanceSignal.start(next: { [weak self] appearance in + self?.updateLocalizationAndTheme(theme: appearance.presentation) })) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) - self.genericView.background = theme.colors.background + (self.genericView as? AppearanceViewProtocol)?.updateLocalizationAndTheme(theme: theme) requestUpdateBackBar() requestUpdateCenterBar() requestUpdateRightBar() @@ -46,7 +44,7 @@ class TelegramGenericViewController: GenericViewController where T:NSView } } -class TelegramViewController: TelegramGenericViewController { +class TelegramViewController: TelegramGenericViewController { } @@ -55,6 +53,7 @@ class TelegramViewController: TelegramGenericViewController { class TableViewController: TelegramGenericViewController, TableViewDelegate { + override func loadView() { super.loadView() @@ -63,20 +62,28 @@ class TableViewController: TelegramGenericViewController, TableViewDe override func viewDidLoad() { super.viewDidLoad() - + genericView.getBackgroundColor = { + return theme.colors.listBackground + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) } func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void { } - func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { return false } func isSelectable(row:Int, item:TableRowItem) -> Bool { return false } - + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } override var enableBack: Bool { return true @@ -115,13 +122,14 @@ class EditableViewController: TelegramGenericViewController where T: NSVie func changeState() ->Void { + let new: ViewControllerState if case .Normal = state { - self.state = .Edit + new = .Edit } else { - self.state = .Normal + new = .Normal } - update(with:state) + update(with: new) } var doneString:String { @@ -147,13 +155,13 @@ class EditableViewController: TelegramGenericViewController where T: NSVie func updateEditStateTitles() -> Void { switch state { case .Edit: - editBar.button.set(text: doneString, for: .Normal) + editBar.set(text: doneString, for: .Normal) case .Normal: - editBar.button.set(text: normalString, for: .Normal) + editBar.set(text: normalString, for: .Normal) case .Some: - editBar.button.set(text: someString, for: .Normal) + editBar.set(text: someString, for: .Normal) } - editBar.button.set(color: presentation.colors.blueUI, for: .Normal) + editBar.set(color: presentation.colors.accent, for: .Normal) self.editBar.needsLayout = true } @@ -163,36 +171,42 @@ class EditableViewController: TelegramGenericViewController where T: NSVie } func addHandler() -> Void { - editBar.button.set (handler:{[weak self] _ in + editBar.set (handler:{[weak self] _ in if let strongSelf = self { strongSelf.changeState() } }, for:.Click) } - override init(_ account:Account) { - super.init(account) + override func loadView() { editBar = TextButtonBarView(controller: self, text: "", style: navigationButtonStyle, alignment:.Right) addHandler() + rightBarView = editBar + updateEditStateTitles() + super.loadView() + } + + override init(_ context:AccountContext) { + super.init(context) } func update(with state:ViewControllerState) -> Void { + self.state = state updateEditStateTitles() } public func set(editable: Bool) ->Void { - editBar.button.isHidden = !editable + editBar.isHidden = !editable } public func set(enabled: Bool) ->Void { - editBar.button.isEnabled = enabled + editBar.isEnabled = enabled } override func updateNavigation(_ navigation: NavigationViewController?) { super.updateNavigation(navigation) if navigation != nil { - rightBarView = editBar - updateEditStateTitles() + } } @@ -203,16 +217,20 @@ class EditableViewController: TelegramGenericViewController where T: NSVie } final class Appearance : Equatable { - let language:Language + let language: TelegramLocalization var presentation: TelegramPresentationTheme - init(language: Language, presentation: TelegramPresentationTheme) { + init(language: TelegramLocalization, presentation: TelegramPresentationTheme) { self.language = language self.presentation = presentation } + + var newAllocation: Appearance { + return Appearance(language: language, presentation: presentation) + } } func ==(lhs:Appearance, rhs:Appearance) -> Bool { - return lhs.language === rhs.language && lhs.presentation == rhs.presentation + return lhs === rhs //lhs.language === rhs.language && lhs.presentation === rhs.presentation } var theme: TelegramPresentationTheme { @@ -227,16 +245,52 @@ var appAppearance:Appearance { return Appearance(language: appCurrentLanguage, presentation: theme) } -var appearanceSignal:Signal { - return combineLatest(languageSignal, themeSignal) |> map { +var appearanceSignal:Signal { + + var timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + let dateSignal:Signal = Signal { subscriber in + + let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + + var now: time_t = time_t(nowTimestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + + + var t: time_t = time_t(timestamp) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + + if timeinfo.tm_year != timeinfoNow.tm_year || timeinfo.tm_yday != timeinfoNow.tm_yday { + timestamp = nowTimestamp + subscriber.putNext(true) + } else { + subscriber.putNext(false) + } + subscriber.putCompletion() + + return EmptyDisposable + } + + let dateUpdateSignal: Signal = .single(true) |> then(dateSignal |> delay(1.0, queue: resourcesQueue) |> restart) + + let updateSignal = dateUpdateSignal |> filter {$0} + + return combineLatest(languageSignal, themeSignal, updateSignal |> deliverOnMainQueue) |> map { return Appearance(language: $0.0, presentation: $0.1) } } -struct AppearanceWrapperEntry: Comparable, Identifiable where E: Comparable, E:Identifiable { +final class AppearanceWrapperEntry: Comparable, Identifiable where E: Comparable, E:Identifiable { let entry: E let appearance: Appearance - + init(entry: E, appearance: Appearance) { + self.entry = entry + self.appearance = appearance + } var stableId: AnyHashable { return entry.stableId } diff --git a/Telegram-Mac/ConvertGroupViewController.swift b/Telegram-Mac/ConvertGroupViewController.swift deleted file mode 100644 index 226541a25f..0000000000 --- a/Telegram-Mac/ConvertGroupViewController.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// ConvertToSupergroupViewController.swift -// Telegram -// -// Created by keepcoder on 21/02/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - -class ConvertGroupViewController: TableViewController { - - private let peerId:PeerId - private let convertDisposable:MetaDisposable = MetaDisposable() - init(account:Account, peerId:PeerId) { - self.peerId = peerId - super.init(account) - } - - override func viewDidLoad() { - super.viewDidLoad() - - let initialSize = atomicSize.modify({$0}) - - let desc = NSMutableAttributedString() - _ = desc.append(string: tr(.supergroupConvertDescription), color: theme.colors.grayText, font: .normal(.text)) - desc.detectBoldColorInString(with: .medium(.text)) - - _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 16)) - _ = genericView.addItem(item: GeneralTextRowItem(initialSize, text: desc)) - _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, name: tr(.supergroupConvertButton), nameStyle: blueActionButton, type: .none, action: { [weak self] in - self?.convert() - })) - let undone = NSMutableAttributedString() - _ = undone.append(string: tr(.supergroupConvertUndone), color: theme.colors.grayText, font: .normal(.text)) - undone.detectBoldColorInString(with: .medium(.text)) - _ = genericView.addItem(item: GeneralTextRowItem(initialSize, text: undone)) - - readyOnce() - } - - func convert() { - - - confirm(for: mainWindow, with: appName, and: tr(.convertToSuperGroupConfirm)) { [weak self] result in - - if let strongSelf = self { - let signal = convertGroupToSupergroup(account: strongSelf.account, peerId: strongSelf.peerId) - |> map { Optional($0) } - |> `catch` { error -> Signal in - return .single(nil) - } |> mapError {_ in} - - - self?.convertDisposable.set(showModalProgress(signal: signal |> deliverOnMainQueue, for: mainWindow).start(next: { [weak strongSelf] peerId in - if let peerId = peerId, let account = strongSelf?.account { - strongSelf?.navigationController?.push(ChatController(account: account, peerId: peerId)) - } else { - alert(for: mainWindow, info: tr(.convertToSupergroupAlertError)) - } - })) - } - - } - - - } - - deinit { - convertDisposable.dispose() - } - -} diff --git a/Telegram-Mac/CoreExtension.swift b/Telegram-Mac/CoreExtension.swift index f60deb2e4f..79303a15b8 100644 --- a/Telegram-Mac/CoreExtension.swift +++ b/Telegram-Mac/CoreExtension.swift @@ -8,185 +8,23 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit -extension Peer { - - var mediaRestricted:Bool { - if let peer = self as? TelegramChannel { - if peer.hasBannedRights(.banSendMedia) { - return true - } - } - return false - } - - var stickersRestricted: Bool { - if let peer = self as? TelegramChannel { - if peer.hasBannedRights([.banSendStickers, .banSendGifs]) { - return true - } - } - return false - } - - var inlineRestricted: Bool { - if let peer = self as? TelegramChannel { - if peer.hasBannedRights([.banSendInline]) { - return true - } - } - return false - } - - var webUrlRestricted: Bool { - if let peer = self as? TelegramChannel { - if peer.hasBannedRights([.banEmbedLinks]) { - return true - } - } - return false - } - - - var canSendMessage: Bool { - if let channel = self as? TelegramChannel { - if case .broadcast(_) = channel.info { - return channel.hasAdminRights(.canPostMessages) - } else if case .group(_) = channel.info { - return !channel.hasBannedRights(.banSendMessages) - } - } else if let group = self as? TelegramGroup { - return group.membership == .Member - } else if let secret = self as? TelegramSecretChat { - switch secret.embeddedState { - case .terminated: - return false - case .handshake: - return false - default: - return true - } - } - - return true - } - - var username:String? { - if let peer = self as? TelegramChannel { - return peer.username - } else if let peer = self as? TelegramGroup { - return peer.username - } else if let peer = self as? TelegramUser { - return peer.username - } - return nil - } - - public var displayTitle: String { - switch self { - case let user as TelegramUser: - return user.name.isEmpty ? tr(.peerDeletedUser) : user.name - case let group as TelegramGroup: - return group.title - case let channel as TelegramChannel: - return channel.title - default: - return "" - } - } - - public var compactDisplayTitle: String { - switch self { - case let user as TelegramUser: - if let firstName = user.firstName { - return firstName - } else if let lastName = user.lastName { - return lastName - } else { - return tr(.peerDeletedUser) - } - case let group as TelegramGroup: - return group.title - case let channel as TelegramChannel: - return channel.title - default: - return "" - } - } - - public var displayLetters: [String] { - switch self { - case let user as TelegramUser: - if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty && !lastName.isEmpty { - return [firstName.substring(to: firstName.index(after: firstName.startIndex)).uppercased(), lastName.substring(to: lastName.index(after: lastName.startIndex)).uppercased()] - } else if let firstName = user.firstName, !firstName.isEmpty { - return [firstName.substring(to: firstName.index(after: firstName.startIndex)).uppercased()] - } else if let lastName = user.lastName, !lastName.isEmpty { - return [lastName.substring(to: lastName.index(after: lastName.startIndex)).uppercased()] - } else { - let name = tr(.peerDeletedUser) - if !name.isEmpty { - return [name.substring(to: name.index(after: name.startIndex)).uppercased()] - } - } - - return [] - case let group as TelegramGroup: - if group.title.startIndex != group.title.endIndex { - return [group.title.substring(to: group.title.index(after: group.title.startIndex)).uppercased()] - } else { - return [] - } - case let channel as TelegramChannel: - if channel.title.startIndex != channel.title.endIndex { - return [channel.title.substring(to: channel.title.index(after: channel.title.startIndex)).uppercased()] - } else { - return [] - } - default: - return [] - } - } - - var isVerified: Bool { - if let peer = self as? TelegramUser { - return peer.flags.contains(.isVerified) - } else if let peer = self as? TelegramChannel { - return peer.flags.contains(.isVerified) - } else { - return false - } - } - -} +import SyncCore +import MtProtoKit -extension AdminLogEventsFlags { - - /* - "ChannelEventFilter.NewRestrictions" = "New Restrictions"; - "ChannelEventFilter.NewAdmins" = "New Admins"; - "ChannelEventFilter.NewMembers" = "New Members"; - "ChannelEventFilter.GroupInfo" = "Group Info"; - "ChannelEventFilter.DeletedMessages" = "Deleted Messages"; - "ChannelEventFilter.EditedMessages" = "Edited Messages"; - "ChannelEventFilter.PinnedMessages" = "Pinned Messages"; - "ChannelEventFilter.LeavingMembers" = "Leaving Members"; - */ - - -} extension RenderedChannelParticipant { func withUpdatedBannedRights(_ info: ChannelParticipantBannedInfo) -> RenderedChannelParticipant { let updated: ChannelParticipant switch participant { - case let.member(id, invitedAt, adminInfo, _): - updated = ChannelParticipant.member(id: id, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: info) - case let.creator(id): - updated = ChannelParticipant.creator(id: id) + case let.member(id, invitedAt, adminInfo, _, rank): + updated = ChannelParticipant.member(id: id, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: info, rank: rank) + case let .creator(id, rank): + updated = ChannelParticipant.creator(id: id, rank: rank) } return RenderedChannelParticipant(participant: updated, peer: peer, presences: presences) } @@ -211,60 +49,66 @@ extension ChannelParticipant { } } -extension TelegramChannelAdminRightsFlags { +extension TelegramChatAdminRightsFlags { var localizedString:String { switch self { //EventLog.Service.Restriction.AddNewAdmins - case TelegramChannelAdminRightsFlags.canAddAdmins: - return tr(.eventLogServicePromoteAddNewAdmins) - case TelegramChannelAdminRightsFlags.canBanUsers: - return tr(.eventLogServicePromoteBanUsers) - case TelegramChannelAdminRightsFlags.canChangeInfo: - return tr(.eventLogServicePromoteChangeInfo) - case TelegramChannelAdminRightsFlags.canInviteUsers: - return tr(.eventLogServicePromoteAddUsers) - case TelegramChannelAdminRightsFlags.canChangeInviteLink: - return tr(.eventLogServicePromoteInviteViaLink) - case TelegramChannelAdminRightsFlags.canDeleteMessages: - return tr(.eventLogServicePromoteDeleteMessages) - case TelegramChannelAdminRightsFlags.canEditMessages: - return tr(.eventLogServicePromoteEditMessages) - case TelegramChannelAdminRightsFlags.canPinMessages: - return tr(.eventLogServicePromotePinMessages) - case TelegramChannelAdminRightsFlags.canPostMessages: - return tr(.eventLogServicePromotePostMessages) + case TelegramChatAdminRightsFlags.canAddAdmins: + return tr(L10n.eventLogServicePromoteAddNewAdmins) + case TelegramChatAdminRightsFlags.canBanUsers: + return tr(L10n.eventLogServicePromoteBanUsers) + case TelegramChatAdminRightsFlags.canChangeInfo: + return tr(L10n.eventLogServicePromoteChangeInfo) + case TelegramChatAdminRightsFlags.canInviteUsers: + return tr(L10n.eventLogServicePromoteAddUsers) + case TelegramChatAdminRightsFlags.canDeleteMessages: + return tr(L10n.eventLogServicePromoteDeleteMessages) + case TelegramChatAdminRightsFlags.canEditMessages: + return tr(L10n.eventLogServicePromoteEditMessages) + case TelegramChatAdminRightsFlags.canPinMessages: + return tr(L10n.eventLogServicePromotePinMessages) + case TelegramChatAdminRightsFlags.canPostMessages: + return tr(L10n.eventLogServicePromotePostMessages) default: return "Undefined Promotion" } } } -extension TelegramChannelBannedRightsFlags { +extension TelegramChatBannedRightsFlags { var localizedString:String { switch self { - case TelegramChannelBannedRightsFlags.banSendGifs: - return tr(.eventLogServiceDemoteSendStickers) - case TelegramChannelBannedRightsFlags.banEmbedLinks: - return tr(.eventLogServiceDemoteEmbedLinks) - case TelegramChannelBannedRightsFlags.banReadMessages: + case TelegramChatBannedRightsFlags.banSendGifs: + return L10n.eventLogServiceDemoteSendGifs + case TelegramChatBannedRightsFlags.banPinMessages: + return L10n.eventLogServiceDemotePinMessages + case TelegramChatBannedRightsFlags.banAddMembers: + return L10n.eventLogServiceDemoteAddMembers + case TelegramChatBannedRightsFlags.banSendPolls: + return L10n.eventLogServiceDemotePostPolls + case TelegramChatBannedRightsFlags.banEmbedLinks: + return L10n.eventLogServiceDemoteEmbedLinks + case TelegramChatBannedRightsFlags.banReadMessages: return "" - case TelegramChannelBannedRightsFlags.banSendGames: - return tr(.eventLogServiceDemoteEmbedLinks) - case TelegramChannelBannedRightsFlags.banSendInline: - return tr(.eventLogServiceDemoteSendInline) - case TelegramChannelBannedRightsFlags.banSendMedia: - return tr(.eventLogServiceDemoteSendMedia) - case TelegramChannelBannedRightsFlags.banSendMessages: - return tr(.eventLogServiceDemoteSendMessages) - case TelegramChannelBannedRightsFlags.banSendStickers: - return tr(.eventLogServiceDemoteSendStickers) + case TelegramChatBannedRightsFlags.banSendGames: + return L10n.eventLogServiceDemoteEmbedLinks + case TelegramChatBannedRightsFlags.banSendInline: + return L10n.eventLogServiceDemoteSendInline + case TelegramChatBannedRightsFlags.banSendMedia: + return L10n.eventLogServiceDemoteSendMedia + case TelegramChatBannedRightsFlags.banSendMessages: + return L10n.eventLogServiceDemoteSendMessages + case TelegramChatBannedRightsFlags.banSendStickers: + return L10n.eventLogServiceDemoteSendStickers + case TelegramChatBannedRightsFlags.banChangeInfo: + return L10n.eventLogServiceDemoteChangeInfo default: return "" } } } /* - public struct TelegramChannelBannedRightsFlags: OptionSet { + public struct TelegramChatBannedRightsFlags: OptionSet { public var rawValue: Int32 public init(rawValue: Int32) { @@ -275,48 +119,148 @@ extension TelegramChannelBannedRightsFlags { self.rawValue = 0 } - public static let banReadMessages = TelegramChannelBannedRightsFlags(rawValue: 1 << 0) - public static let banSendMessages = TelegramChannelBannedRightsFlags(rawValue: 1 << 1) - public static let banSendMedia = TelegramChannelBannedRightsFlags(rawValue: 1 << 2) - public static let banSendStickers = TelegramChannelBannedRightsFlags(rawValue: 1 << 3) - public static let banSendGifs = TelegramChannelBannedRightsFlags(rawValue: 1 << 4) - public static let banSendGames = TelegramChannelBannedRightsFlags(rawValue: 1 << 5) - public static let banSendInline = TelegramChannelBannedRightsFlags(rawValue: 1 << 6) - public static let banEmbedLinks = TelegramChannelBannedRightsFlags(rawValue: 1 << 7) + public static let banReadMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 0) + public static let banSendMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 1) + public static let banSendMedia = TelegramChatBannedRightsFlags(rawValue: 1 << 2) + public static let banSendStickers = TelegramChatBannedRightsFlags(rawValue: 1 << 3) + public static let banSendGifs = TelegramChatBannedRightsFlags(rawValue: 1 << 4) + public static let banSendGames = TelegramChatBannedRightsFlags(rawValue: 1 << 5) + public static let banSendInline = TelegramChatBannedRightsFlags(rawValue: 1 << 6) + public static let banEmbedLinks = TelegramChatBannedRightsFlags(rawValue: 1 << 7) } */ -extension TelegramChannelBannedRights { +extension TelegramChatBannedRights { var formattedUntilDate: String { let formatter = DateFormatter() formatter.dateStyle = .short - formatter.locale = Locale(identifier: appCurrentLanguage.languageCode) + //formatter.timeZone = NSTimeZone.local + + formatter.timeZone = NSTimeZone.local formatter.timeStyle = .short return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(untilDate))) } } -func alertForMediaRestriction(_ peer:Peer) { - if let peer = peer as? TelegramChannel, let bannedRights = peer.bannedRights { - alert(for: mainWindow, info: bannedRights.untilDate != .max ? tr(.channelPersmissionDeniedSendMediaUntil(bannedRights.formattedUntilDate)) : tr(.channelPersmissionDeniedSendMediaForever)) + +func permissionText(from peer: Peer, for flags: TelegramChatBannedRightsFlags) -> String? { + let bannedPermission: (Int32, Bool)? + if let channel = peer as? TelegramChannel { + bannedPermission = channel.hasBannedPermission(flags) + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(flags) { + bannedPermission = (Int32.max, false) + } else { + bannedPermission = nil + } + } else { + bannedPermission = nil + } + + if let (untilDate, personal) = bannedPermission { + + switch flags { + case .banSendMessages: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendMessagesUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendMessagesForever + } else { + return L10n.channelPersmissionDeniedSendMessagesDefaultRestrictedText + } + case .banSendStickers: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendStickersUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendStickersForever + } else { + return L10n.channelPersmissionDeniedSendStickersDefaultRestrictedText + } + case .banSendGifs: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendGifsUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendGifsForever + } else { + return L10n.channelPersmissionDeniedSendGifsDefaultRestrictedText + } + case .banSendMedia: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendMediaUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendMediaForever + } else { + return L10n.channelPersmissionDeniedSendMediaDefaultRestrictedText + } + case .banSendPolls: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendPollUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendPollForever + } else { + return L10n.channelPersmissionDeniedSendPollDefaultRestrictedText + } + case .banSendInline: + if personal && untilDate != 0 && untilDate != Int32.max { + return L10n.channelPersmissionDeniedSendInlineUntil(stringForFullDate(timestamp: untilDate)) + } else if personal { + return L10n.channelPersmissionDeniedSendInlineForever + } else { + return L10n.channelPersmissionDeniedSendInlineDefaultRestrictedText + } + default: + return nil + } + + } + + return nil } +extension RenderedPeer { + convenience init(_ foundPeer: FoundPeer) { + self.init(peerId: foundPeer.peer.id, peers: SimpleDictionary([foundPeer.peer.id : foundPeer.peer])) + } +} extension TelegramMediaFile { var videoSize:NSSize { for attr in attributes { if case let .Video(_,size, _) = attr { - return size + return size.size } } return NSZeroSize } + var isStreamable: Bool { + for attr in attributes { + if case let .Video(_, _, flags) = attr { + return flags.contains(.supportsStreaming) + } + } + return true + } + +// var streaming: MediaPlayerStreaming { +// for attr in attributes { +// if case let .Video(_, _, flags) = attr { +// if flags.contains(.supportsStreaming) { +// return .earlierStart +// } else { +// return .none +// } +// } +// } +// return .none +// } + + var imageSize:NSSize { for attr in attributes { if case let .ImageSize(size) = attr { - return size + return size.size } } return NSZeroSize @@ -330,6 +274,14 @@ extension TelegramMediaFile { } return 0 } + + var isTheme: Bool { + return mimeType == "application/x-tgtheme-macos" + } + + func withUpdatedResource(_ resource: TelegramMediaResource) -> TelegramMediaFile { + return TelegramMediaFile(fileId: self.fileId, partialReference: self.partialReference, resource: resource, previewRepresentations: self.previewRepresentations, videoThumbnails: self.videoThumbnails, immediateThumbnailData: self.immediateThumbnailData, mimeType: self.mimeType, size: self.size, attributes: self.attributes) + } } extension ChatContextResult { @@ -347,50 +299,63 @@ extension ChatContextResult { } } -extension Account { - var context:TelegramApplicationContext { - return self.applicationContext as! TelegramApplicationContext - } -} extension TelegramMediaFile { var elapsedSize:Int { if let size = size { return size } + if let resource = resource as? LocalFileReferenceMediaResource, let size = resource.size { + return Int(size) + } return 0 } } -enum ChatListIndexRequest :Equatable { - case Initial(Int, TableScrollState?) - case Index(ChatListIndex) -} - -func ==(lhs:ChatListIndexRequest, rhs:ChatListIndexRequest) -> Bool { - switch lhs { - case let .Initial(lhsCount, _): - if case let .Initial(rhsCount, _) = rhs { - return rhsCount == lhsCount - } - - case let .Index(lhsIndex): - if case let .Index(rhsIndex) = rhs { - return lhsIndex == rhsIndex +extension Media { + var isInteractiveMedia: Bool { + if self is TelegramMediaImage { + return true + } else if let file = self as? TelegramMediaFile { + return file.isVideo || (file.isAnimated && !file.mimeType.lowercased().hasSuffix("gif")) + } else if let map = self as? TelegramMediaMap { + return map.venue == nil + } else if self is TelegramMediaDice { + return false } + return false } - return false + var canHaveCaption: Bool { + if self is TelegramMediaImage { + return true + } else if let file = self as? TelegramMediaFile { + if file.isInstantVideo || file.isAnimatedSticker || file.isStaticSticker || file.isVoice { + return false + } else { + return true + } + } + return false + } +} + +enum ChatListIndexRequest :Equatable { + case Initial(Int, TableScrollState?) + case Index(ChatListIndex, TableScrollState?) } + public extension PeerView { var isMuted:Bool { if let settings = self.notificationSettings as? TelegramPeerNotificationSettings { switch settings.muteState { - case .muted: - return true + case let .muted(until): + return until > Int32(Date().timeIntervalSince1970) case .unmuted: return false + case .default: + return false } } else { return false @@ -401,10 +366,12 @@ public extension PeerView { public extension TelegramPeerNotificationSettings { var isMuted:Bool { switch self.muteState { - case .muted: - return true + case let .muted(until): + return until > Int32(Date().timeIntervalSince1970) case .unmuted: return false + case .default: + return false } } } @@ -429,6 +396,32 @@ public extension TelegramMediaFile { return nil } + var maskData: StickerMaskCoords? { + for attr in attributes { + if case let .Sticker(_, _, mask) = attr { + return mask + } + } + return nil + } + + var isEmojiAnimatedSticker: Bool { + if let fileName = fileName { + return fileName.hasPrefix("telegram-animoji") && fileName.hasSuffix("tgs") && isSticker + } + return false + } + + var animatedEmojiFitzModifier: EmojiFitzModifier? { + if isEmojiAnimatedSticker, let fitz = self.stickerText?.basicEmoji.1 { + return EmojiFitzModifier(emoji: fitz) + } else { + return nil + } + } + + + var musicText:(String,String) { var audioTitle:String? @@ -456,36 +449,12 @@ public extension TelegramMediaFile { } } -public extension MessageHistoryEntry { - var location:MessageHistoryEntryLocation? { - switch self { - case let .MessageEntry(_, _, location, _): - return location - case let .HoleEntry(_, location): - return location - } - } - - var message:Message? { - switch self { - case let .MessageEntry(message, _, _, _): - return message - default: - return nil - } - } -} public extension MessageHistoryView { func index(for messageId: MessageId) -> Int? { for i in 0 ..< entries.count { - switch entries[i] { - case let .MessageEntry(lhsMessage,_, _, _): - if lhsMessage.id == messageId { - return i - } - default: - break + if entries[i].index.id == messageId { + return i } } return nil @@ -504,63 +473,236 @@ public extension Message { return nil } - var autoremoveAttribute:AutoremoveTimeoutMessageAttribute? { - for attr in attributes { - if let attr = attr as? AutoremoveTimeoutMessageAttribute { - return attr + func isCrosspostFromChannel(account: Account) -> Bool { + + var sourceReference: SourceReferenceMessageAttribute? + for attribute in self.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + sourceReference = attribute + break + } + } + + var isCrosspostFromChannel = false + if let _ = sourceReference { + if self.id.peerId != account.peerId { + isCrosspostFromChannel = true + } + } + + return isCrosspostFromChannel + } + + var channelViewsCount: Int32? { + for attribute in self.attributes { + if let attribute = attribute as? ViewCountMessageAttribute { + return Int32(attribute.count) } } return nil } - var inlinePeer:Peer? { - for attribute in attributes { - if let attribute = attribute as? InlineBotMessageAttribute { - return peers[attribute.peerId] + var isScheduledMessage: Bool { + return self.id.namespace == Namespaces.Message.ScheduledCloud || self.id.namespace == Namespaces.Message.ScheduledLocal + } + + var wasScheduled: Bool { + for attr in attributes { + if attr is OutgoingScheduleInfoMessageAttribute { + return true } } - return author + return self.flags.contains(.WasScheduled) } - func withUpdatedStableId(_ stableId:UInt32) -> Message { - return Message(stableId: stableId, stableVersion: stableVersion, id: id, globallyUniqueId: globallyUniqueId, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, forwardInfo: forwardInfo, author: author, text: text, attributes: attributes, media: media, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + var isPublicPoll: Bool { + if let media = self.media.first as? TelegramMediaPoll { + return media.publicity == .public + } + return false } - func possibilityForwardTo(_ peer:Peer) -> Bool { - if !peer.canSendMessage { + var isHasInlineKeyboard: Bool { + return replyMarkup?.flags.contains(.inline) ?? false + } + + func isIncoming(_ account: Account, _ isBubbled: Bool) -> Bool { + if isBubbled, let peer = chatPeer(account.peerId), peer.isChannel { + return true + } + + if id.peerId == account.peerId { + if let _ = forwardInfo { + return true + } return false - } else if let peer = peer as? TelegramChannel { - - if let media = media.first, !(media is TelegramMediaWebpage) { - if let media = media as? TelegramMediaFile { - if media.isSticker { - return !peer.hasBannedRights(.banSendStickers) - } else if media.isVideo && media.isAnimated { - return !peer.hasBannedRights(.banSendGifs) + } + return flags.contains(.Incoming) + } + + func chatPeer(_ accountPeerId: PeerId) -> Peer? { + var _peer: Peer? + for attr in attributes { + if let source = attr as? SourceReferenceMessageAttribute { + if let info = forwardInfo { + if let peer = peers[source.messageId.peerId], peer is TelegramChannel, accountPeerId != id.peerId { + _peer = peer + } else { + _peer = info.author } } - return !peer.hasBannedRights(.banSendMedia) + break } } - return true + + if let peer = messageMainPeer(self) as? TelegramChannel, case .broadcast(_) = peer.info { + _peer = peer + } else if let author = effectiveAuthor, _peer == nil { + if author is TelegramSecretChat { + return messageMainPeer(self) + } else { + _peer = author + } + } + return _peer } -} - -extension SuggestedLocalizationInfo { - func localizedKey(_ key:String) -> String { - for entry in extractedEntries { - switch entry { - case let.string(_key, _value): - if _key == key { - return _value - } - default: - break + + var replyAttribute: ReplyMessageAttribute? { + for attr in attributes { + if let attr = attr as? ReplyMessageAttribute { + return attr } } - return NSLocalizedString(key, comment: "") + return nil } -} + + var autoremoveAttribute:AutoremoveTimeoutMessageAttribute? { + for attr in attributes { + if let attr = attr as? AutoremoveTimeoutMessageAttribute { + return attr + } + } + return nil + } + + var hasInlineAttribute: Bool { + for attribute in attributes { + if let _ = attribute as? InlineBotMessageAttribute { + return true + } + } + return false + } + + var inlinePeer:Peer? { + for attribute in attributes { + if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId { + return peers[peerId] + } + } + if let peer = messageMainPeer(self), peer.isBot { + return peer + } + return nil + } + + func withUpdatedStableId(_ stableId:UInt32) -> Message { + return Message(stableId: stableId, stableVersion: stableVersion, id: id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, author: author, text: text, attributes: attributes, media: media, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + } + func withUpdatedId(_ messageId:MessageId) -> Message { + return Message(stableId: stableId, stableVersion: stableVersion, id: messageId, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, author: author, text: text, attributes: attributes, media: media, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + } + + func withUpdatedGroupingKey(_ groupingKey:Int64?) -> Message { + return Message(stableId: stableId, stableVersion: stableVersion, id: id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, author: author, text: text, attributes: attributes, media: media, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + } + + func withUpdatedTimestamp(_ timestamp: Int32) -> Message { + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + } + + + func withUpdatedText(_ text:String) -> Message { + return Message(stableId: stableId, stableVersion: stableVersion, id: id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, author: author, text: text, attributes: attributes, media: media, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + } + + func possibilityForwardTo(_ peer:Peer) -> Bool { + if !peer.canSendMessage { + return false + } else if let peer = peer as? TelegramChannel { + if let media = media.first, !(media is TelegramMediaWebpage) { + if let media = media as? TelegramMediaFile { + if media.isStaticSticker { + return !peer.hasBannedRights(.banSendStickers) + } else if media.isVideo && media.isAnimated { + return !peer.hasBannedRights(.banSendGifs) + } + } + return !peer.hasBannedRights(.banSendMedia) + } + } + return true + } + + convenience init(_ media: Media, stableId: UInt32, messageId: MessageId) { + self.init(stableId: stableId, stableVersion: 0, id: messageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) + } +} + +extension ChatLocation { + var unreadMessageCountsItem: UnreadMessageCountsItem { + switch self { + case let .peer(peerId): + return .peer(peerId) + } + } + + var postboxViewKey: PostboxViewKey { + switch self { + case let .peer(peerId): + return .peer(peerId: peerId, components: []) + } + } + + var pinnedItemId: PinnedItemId { + switch self { + case let .peer(peerId): + return .peer(peerId) + } + } + + var peerId: PeerId { + switch self { + case let .peer(peerId): + return peerId + } + } +} + +extension ChatLocation : Hashable { + public var hashValue: Int { + switch self { + case let .peer(peerId): + return peerId.hashValue + } + } +} + +extension SuggestedLocalizationInfo { + func localizedKey(_ key:String) -> String { + for entry in extractedEntries { + switch entry { + case let.string(_key, _value): + if _key == key { + return _value + } + default: + break + } + } + return NSLocalizedString(key, comment: "") + } +} public extension MessageId { func toInt64() -> Int64 { @@ -582,11 +724,11 @@ func canDeleteMessage(_ message:Message, account:Account) -> Bool { if let channel = message.peers[message.id.peerId] as? TelegramChannel { if case .broadcast = channel.info { if !message.flags.contains(.Incoming) { - return channel.hasAdminRights(.canPostMessages) + return channel.hasPermission(.sendMessages) } - return channel.hasAdminRights(.canDeleteMessages) + return channel.hasPermission(.deleteAllMessages) } - return channel.hasAdminRights(.canDeleteMessages) || !message.flags.contains(.Incoming) + return channel.hasPermission(.deleteAllMessages) || !message.flags.contains(.Incoming) } else if message.peers[message.id.peerId] is TelegramSecretChat { return true } else { @@ -611,6 +753,14 @@ func canForwardMessage(_ message:Message, account:Account) -> Bool { if message.peers[message.id.peerId] is TelegramSecretChat { return false } + + if message.flags.contains(.Failed) || message.flags.contains(.Unsent) { + return false + } + if message.isScheduledMessage { + return false + } + if message.media.first is TelegramMediaAction { return false } @@ -623,33 +773,85 @@ func canForwardMessage(_ message:Message, account:Account) -> Bool { return true } -func canDeleteForEveryoneMessage(_ message:Message, account:Account) -> Bool { +public struct ChatAvailableMessageActionOptions: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let deleteLocally = ChatAvailableMessageActionOptions(rawValue: 1 << 0) + public static let deleteGlobally = ChatAvailableMessageActionOptions(rawValue: 1 << 1) + public static let unsendPersonal = ChatAvailableMessageActionOptions(rawValue: 1 << 7) +} + + + +func canDeleteForEveryoneMessage(_ message:Message, context: AccountContext) -> Bool { if message.peers[message.id.peerId] is TelegramChannel || message.peers[message.id.peerId] is TelegramSecretChat { return false } else if message.peers[message.id.peerId] is TelegramUser || message.peers[message.id.peerId] is TelegramGroup { - if message.author?.id == account.peerId && edit_limit_time + message.timestamp > Int32(Date().timeIntervalSince1970) { - if account.peerId != messageMainPeer(message)?.id { - return !(message.media.first is TelegramMediaAction) + if context.limitConfiguration.canRemoveIncomingMessagesInPrivateChats && message.peers[message.id.peerId] is TelegramUser { + + if message.media.first is TelegramMediaDice, message.peers[message.id.peerId] is TelegramUser { + if Int(message.timestamp) + 24 * 60 * 60 > context.timestamp { + return false + } } - } else if let peer = message.peers[message.id.peerId] as? TelegramGroup { + + return true + } + if let peer = message.peers[message.id.peerId] as? TelegramGroup { switch peer.role { case .creator, .admin: return true default: + if Int(context.limitConfiguration.maxMessageEditingInterval) + Int(message.timestamp) > Int(Date().timeIntervalSince1970) { + if context.account.peerId == message.effectiveAuthor?.id { + return !(message.media.first is TelegramMediaAction) + } + } return false } + } else if Int(context.limitConfiguration.maxMessageEditingInterval) + Int(message.timestamp) > Int(Date().timeIntervalSince1970) { + if context.account.peerId == message.author?.id { + return !(message.media.first is TelegramMediaAction) + } + } + } + return false +} + +func mustDeleteForEveryoneMessage(_ message:Message) -> Bool { + if message.peers[message.id.peerId] is TelegramChannel || message.peers[message.id.peerId] is TelegramSecretChat { + return true + } + return false +} + +func canReplyMessage(_ message: Message, peerId: PeerId) -> Bool { + if let peer = messageMainPeer(message) { + if message.isScheduledMessage { + return false + } + if peer.canSendMessage, peerId == message.id.peerId, !message.flags.contains(.Unsent) && !message.flags.contains(.Failed) && (message.id.namespace != Namespaces.Message.Local || message.id.peerId.namespace == Namespaces.Peer.SecretChat) { + return true } } return false } -func canEditMessage(_ message:Message, account:Account) -> Bool { +func canEditMessage(_ message:Message, context: AccountContext) -> Bool { if message.forwardInfo != nil { return false } - if message.flags.contains(.Unsent) || message.flags.contains(.Failed) { + if message.flags.contains(.Unsent) || message.flags.contains(.Failed) || message.id.namespace == Namespaces.Message.Local { return false } @@ -659,29 +861,55 @@ func canEditMessage(_ message:Message, account:Account) -> Bool { if let media = message.media.first { if let file = media as? TelegramMediaFile { - if file.isSticker { + if file.isStaticSticker || (file.isAnimatedSticker && !file.isEmojiAnimatedSticker) { return false } if file.isInstantVideo { return false } +// if file.isVoice { +// return false +// } + } + if media is TelegramMediaContact { + return false } if media is TelegramMediaAction { return false } + if media is TelegramMediaMap { + return false + } + if media is TelegramMediaPoll { + return false + } + if media is TelegramMediaDice { + return false + } + } + + for attr in message.attributes { + if attr is InlineBotMessageAttribute { + return false + } else if attr is AutoremoveTimeoutMessageAttribute { + return false + } } if let peer = messageMainPeer(message) as? TelegramChannel { if case .broadcast = peer.info { - if peer.hasAdminRights(.canEditMessages) { - return message.timestamp + edit_limit_time > Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - } else if !peer.hasAdminRights(.canPostMessages) { - return false + return (peer.hasPermission(.sendMessages) || peer.hasPermission(.editAllMessages)) + } else if case .group = peer.info { + if !message.flags.contains(.Incoming) { + if peer.hasPermission(.pinMessages) { + return true + } + return Int(message.timestamp) + Int(context.limitConfiguration.maxMessageEditingInterval) > Int(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) } } } - if message.id.peerId == account.peerId { + if message.id.peerId == context.account.peerId { return true } @@ -690,22 +918,38 @@ func canEditMessage(_ message:Message, account:Account) -> Bool { return false } - if message.timestamp + edit_limit_time < Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + + if Int(message.timestamp) + Int(context.limitConfiguration.maxMessageEditingInterval) < Int(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { return false } - return true + + + return !message.flags.contains(.Unsent) && !message.flags.contains(.Failed) } + func canPinMessage(_ message:Message, for peer:Peer, account:Account) -> Bool { return false } +func canReportMessage(_ message: Message, _ account: Account) -> Bool { + if message.isScheduledMessage || message.flags.contains(.Failed) || message.flags.contains(.Sending) { + return false + } + if let peer = messageMainPeer(message), message.author?.id != account.peerId { + return peer.isChannel || peer.isGroup || peer.isSupergroup || (message.chatPeer(account.peerId)?.isBot == true) + } else { + return false + } +} + func mustManageDeleteMessages(_ messages:[Message], for peer:Peer, account: Account) -> Bool { - if peer.isSupergroup, peer.groupAccess.canManageGroup { + + if let peer = peer as? TelegramChannel, peer.isSupergroup, peer.hasPermission(.deleteAllMessages) { let peerId:PeerId? = messages[0].author?.id if account.peerId != peerId { for message in messages { @@ -723,7 +967,13 @@ func mustManageDeleteMessages(_ messages:[Message], for peer:Peer, account: Acco extension Media { var isGraphicFile:Bool { if let media = self as? TelegramMediaFile { - return media.mimeType.hasPrefix("image") + return media.mimeType.hasPrefix("image") && (media.mimeType.contains("png") || media.mimeType.contains("jpg") || media.mimeType.contains("jpeg") || media.mimeType.contains("tiff")) + } + return false + } + var isVideoFile:Bool { + if let media = self as? TelegramMediaFile { + return media.mimeType.hasPrefix("video/mp4") || media.mimeType.hasPrefix("video/mov") || media.mimeType.hasPrefix("video/avi") } return false } @@ -733,28 +983,29 @@ extension AddressNameFormatError { var description:String { switch self { case .startsWithUnderscore: - return tr(.errorUsernameUnderscopeStart) + return tr(L10n.errorUsernameUnderscopeStart) case .endsWithUnderscore: - return tr(.errorUsernameUnderscopeEnd) + return tr(L10n.errorUsernameUnderscopeEnd) case .startsWithDigit: - return tr(.errorUsernameNumberStart) + return tr(L10n.errorUsernameNumberStart) case .invalidCharacters: - return tr(.errorUsernameInvalid) + return tr(L10n.errorUsernameInvalid) case .tooShort: - return tr(.errorUsernameMinimumLength) + return tr(L10n.errorUsernameMinimumLength) } } } extension AddressNameAvailability { - var description:String { + + func description(for username: String) -> String { switch self { case .available: - return "available" + return L10n.usernameSettingsAvailable(username) case .invalid: - return tr(.errorUsernameInvalid) + return L10n.errorUsernameInvalid case .taken: - return tr(.errorUsernameAlreadyTaken) + return L10n.errorUsernameAlreadyTaken } } } @@ -766,19 +1017,25 @@ func <(lhs:RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool switch lhs.participant { case .creator: lhsInvitedAt = Int32.min - case .member(_, let invitedAt, _, _): + case .member(_, let invitedAt, _, _, _): lhsInvitedAt = invitedAt } switch rhs.participant { case .creator: rhsInvitedAt = Int32.min - case .member(_, let invitedAt, _, _): + case .member(_, let invitedAt, _, _, _): rhsInvitedAt = invitedAt } return lhsInvitedAt < rhsInvitedAt } +extension TelegramGroup { + var canPinMessage: Bool { + return !hasBannedRights(.banPinMessages) + } +} + extension Peer { var isUser:Bool { return self is TelegramUser @@ -790,6 +1047,34 @@ extension Peer { return self is TelegramGroup } + func isRestrictedChannel(_ contentSettings: ContentSettings) -> Bool { + if let peer = self as? TelegramChannel { + if let restrictionInfo = peer.restrictionInfo { + for rule in restrictionInfo.rules { + #if APP_STORE + if rule.platform == "ios" || rule.platform == "all" { + return !contentSettings.ignoreContentRestrictionReasons.contains(rule.reason) + } + #endif + } + } + } + return false + } + + var restrictionText:String? { + if let peer = self as? TelegramChannel { + if let restrictionInfo = peer.restrictionInfo { + for rule in restrictionInfo.rules { + if rule.platform == "ios" || rule.platform == "all" { + return rule.text + } + } + } + } + return nil + } + var isSupergroup:Bool { if let peer = self as? TelegramChannel { switch peer.info { @@ -871,106 +1156,1846 @@ public func ==(lhs:AddressNameAvailabilityState, rhs:AddressNameAvailabilityStat } } - - -public func peerCompactDisplayTitles(_ peerIds: [PeerId], _ dict: SimpleDictionary) -> String { - var names:String = "" - for peerId in peerIds { - if let peer = dict[peerId] { - names += peer.compactDisplayTitle - if peerId != peerIds.last { - names += ", " - } +extension Signal { + + public static func next(_ value: T) -> Signal { + return Signal { subscriber in + subscriber.putNext(value) + + return EmptyDisposable } } - return names } -func mediaResource(from media:Media?) -> TelegramMediaResource? { - if let media = media as? TelegramMediaFile { - return media.resource - } else if let media = media as? TelegramMediaImage { - return largestImageRepresentation(media.representations)?.resource +extension SentSecureValueType { + var rawValue: String { + switch self { + case .email: + return L10n.secureIdRequestPermissionEmail + case .phone: + return L10n.secureIdRequestPermissionPhone + case .passport: + return L10n.secureIdRequestPermissionPassport + case .address: + return L10n.secureIdRequestPermissionResidentialAddress + case .personalDetails: + return L10n.secureIdRequestPermissionPersonalDetails + case .driversLicense: + return L10n.secureIdRequestPermissionDriversLicense + case .utilityBill: + return L10n.secureIdRequestPermissionUtilityBill + case .rentalAgreement: + return L10n.secureIdRequestPermissionTenancyAgreement + case .idCard: + return L10n.secureIdRequestPermissionIDCard + case .bankStatement: + return L10n.secureIdRequestPermissionBankStatement + case .internalPassport: + return L10n.secureIdRequestPermissionInternalPassport + case .passportRegistration: + return L10n.secureIdRequestPermissionPassportRegistration + case .temporaryRegistration: + return L10n.secureIdRequestPermissionTemporaryRegistration + } } - return nil } - -func mediaResourceMIMEType(from media:Media?) -> String? { - if let media = media as? TelegramMediaFile { - return media.mimeType - } else if media is TelegramMediaImage { - return "image/jpeg" +extension TwoStepVerificationPendingEmail : Equatable { + public static func == (lhs: TwoStepVerificationPendingEmail, rhs: TwoStepVerificationPendingEmail) -> Bool { + return lhs.codeLength == rhs.codeLength && lhs.pattern == rhs.pattern } - return nil -} - -func mediaResourceName(from media:Media?, ext:String?) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - let ext = ext ?? ".file" - if let media = media as? TelegramMediaFile { - return media.fileName ?? "FILE " + dateFormatter.string(from: Date()) + "." + ext - } else if media is TelegramMediaImage { - return "IMAGE " + dateFormatter.string(from: Date()) + "." + ext - } - return "FILE " + dateFormatter.string(from: Date()) + "." + ext + } - -func removeChatInteractively(account:Account, peerId:PeerId) -> Signal { - return account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in - let text:String - if let peer = peer as? TelegramChannel { - switch peer.info { - case .broadcast: - if peer.flags.contains(.isCreator) { - text = tr(.confirmDeleteAdminedChannel) - } else { - text = tr(.peerInfoConfirmLeaveChannel) - } - case .group: - text = tr(.peerInfoConfirmLeaveGroup) +extension UpdateTwoStepVerificationPasswordResult : Equatable { + public static func ==(lhs: UpdateTwoStepVerificationPasswordResult, rhs: UpdateTwoStepVerificationPasswordResult) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false } - } else if let peer = peer as? TelegramGroup { - text = tr(.peerInfoConfirmDeleteChat(peer.title)) - } else { - text = tr(.confirmDeleteChatUser) - } - - return confirmSignal(for: mainWindow, header: appName, information: text) |> mapToSignal { result -> Signal in - if result { - return removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false) |> map {_ in return true} + case let .password(password, lhsPendingEmailPattern): + if case .password(password, let rhsPendingEmailPattern) = rhs { + return lhsPendingEmailPattern == rhsPendingEmailPattern + } else { + return false } - return .single(false) } } - } -func applyExternalProxy(_ proxy:ProxySettings, postbox:Postbox, network: Network) { - var textInfo = tr(.proxyForceEnableTextIP(proxy.host)) + "\n" + tr(.proxyForceEnableTextPort(Int(proxy.port))) - if let user = proxy.username { - textInfo += "\n" + tr(.proxyForceEnableTextUsername(user)) - } - if let pass = proxy.password { - textInfo += "\n" + tr(.proxyForceEnableTextPassword(pass)) - } - textInfo += "\n\n" + tr(.proxyForceEnableText) - - _ = (confirmSignal(for: mainWindow, header: tr(.proxyForceEnableHeader), information: textInfo) - |> filter {$0} |> map {_ in} |> mapToSignal { - return applyProxySettings(postbox: postbox, network: network, settings: proxy) - }).start() -} -extension PostboxAccessChallengeData { - var timeout:Int32? { - switch self { - case .none: - return nil - case let .numericalPassword(_, timeout, _), let .plaintextPassword(_, timeout, _): - return timeout + +extension SecureIdGender { + static func gender(from mrz: TGPassportMRZ) -> SecureIdGender { + switch mrz.gender.lowercased() { + case "f": + return .female + default: + return .male } } } + +extension SecureIdRequestedFormField { + var isIdentityField: Bool { + switch self { + case let .just(field): + switch field { + case .idCard, .passport, .driversLicense, .internalPassport: + return true + default: + return false + } + case let .oneOf(fields): + switch fields[0] { + case .idCard, .passport, .driversLicense, .internalPassport: + return true + default: + return false + } + } + } + + var valueKey: SecureIdValueKey? { + switch self { + case let .just(field): + return field.valueKey + default: + return nil + } + } + + var fieldValue: SecureIdRequestedFormFieldValue? { + switch self { + case let .just(field): + return field + default: + return nil + } + } + + var isAddressField: Bool { + switch self { + case let .just(field): + switch field { + case .utilityBill, .bankStatement, .rentalAgreement, .passportRegistration, .temporaryRegistration: + return true + default: + return false + } + case let .oneOf(fields): + switch fields[0] { + case .utilityBill, .bankStatement, .rentalAgreement, .passportRegistration, .temporaryRegistration: + return true + default: + return false + } + } + } +} + +extension SecureIdForm { + func searchContext(for field: SecureIdRequestedFormFieldValue) -> SecureIdValueWithContext? { + let index = values.index(where: { context -> Bool in + switch context.value { + case .address: + if case .address = field { + return true + } else { + return false + } + case .bankStatement: + if case .bankStatement = field { + return true + } else { + return false + } + case .driversLicense: + if case .driversLicense = field { + return true + } else { + return false + } + case .idCard: + if case .idCard = field { + return true + } else { + return false + } + case .passport: + if case .passport = field { + return true + } else { + return false + } + case .personalDetails: + if case .personalDetails = field { + return true + } else { + return false + } + case .rentalAgreement: + if case .rentalAgreement = field { + return true + } else { + return false + } + case .utilityBill: + if case .utilityBill = field { + return true + } else { + return false + } + case .phone: + if case .phone = field { + return true + } else { + return false + } + case .email: + if case .email = field { + return true + } else { + return false + } + case .internalPassport: + if case .internalPassport = field { + return true + } else { + return false + } + case .passportRegistration: + if case .passportRegistration = field { + return true + } else { + return false + } + case .temporaryRegistration: + if case .temporaryRegistration = field { + return true + } else { + return false + } + } + }) + if let index = index { + return values[index] + } else { + return nil + } + } + + +} + +extension SecureIdValue { + func isSame(of value: SecureIdValue) -> Bool { + switch self { + case .address: + if case .address = value { + return true + } else { + return false + } + case .bankStatement: + if case .bankStatement = value { + return true + } else { + return false + } + case .driversLicense: + if case .driversLicense = value { + return true + } else { + return false + } + case .idCard: + if case .idCard = value { + return true + } else { + return false + } + case .passport: + if case .passport = value { + return true + } else { + return false + } + case .personalDetails: + if case .personalDetails = value { + return true + } else { + return false + } + case .rentalAgreement: + if case .rentalAgreement = value { + return true + } else { + return false + } + case .utilityBill: + if case .utilityBill = value { + return true + } else { + return false + } + case .phone: + if case .phone = value { + return true + } else { + return false + } + case .email: + if case .email = value { + return true + } else { + return false + } + case .internalPassport(_): + if case .internalPassport = value { + return true + } else { + return false + } + case .passportRegistration(_): + if case .passportRegistration = value { + return true + } else { + return false + } + case .temporaryRegistration(_): + if case .temporaryRegistration = value { + return true + } else { + return false + } + } + } + func isSame(of value: SecureIdValueKey) -> Bool { + return self.key == value + } + + + + var secureIdValueAccessContext: SecureIdValueAccessContext? { + switch self { + case .email: + return generateSecureIdValueEmptyAccessContext() + case .phone: + return generateSecureIdValueEmptyAccessContext() + default: + return generateSecureIdValueAccessContext() + } + } + + + var addressValue: SecureIdAddressValue? { + switch self { + case let .address(value): + return value + default: + return nil + } + } + + var identifier: String? { + switch self { + case let .passport(value): + return value.identifier + case let .driversLicense(value): + return value.identifier + case let .idCard(value): + return value.identifier + case let .internalPassport(value): + return value.identifier + default: + return nil + } + } + + var personalDetails: SecureIdPersonalDetailsValue? { + switch self { + case let .personalDetails(value): + return value + default: + return nil + } + } + + var selfieVerificationDocument: SecureIdVerificationDocumentReference? { + switch self { + case let .idCard(value): + return value.selfieDocument + case let .passport(value): + return value.selfieDocument + case let .driversLicense(value): + return value.selfieDocument + case let .internalPassport(value): + return value.selfieDocument + default: + return nil + } + } + + var verificationDocuments: [SecureIdVerificationDocumentReference]? { + switch self { + case let .bankStatement(value): + return value.verificationDocuments + case let .rentalAgreement(value): + return value.verificationDocuments + case let .utilityBill(value): + return value.verificationDocuments + case let .passportRegistration(value): + return value.verificationDocuments + case let .temporaryRegistration(value): + return value.verificationDocuments + default: + return nil + } + } + + var translations: [SecureIdVerificationDocumentReference]? { + switch self { + case let .passport(value): + return value.translations + case let .idCard(value): + return value.translations + case let .driversLicense(value): + return value.translations + case let .internalPassport(value): + return value.translations + case let .utilityBill(value): + return value.translations + case let .rentalAgreement(value): + return value.translations + case let .temporaryRegistration(value): + return value.translations + case let .passportRegistration(value): + return value.translations + case let .bankStatement(value): + return value.translations + default: + return nil + } + } + + var frontSideVerificationDocument: SecureIdVerificationDocumentReference? { + switch self { + case let .idCard(value): + return value.frontSideDocument + case let .passport(value): + return value.frontSideDocument + case let .driversLicense(value): + return value.frontSideDocument + case let .internalPassport(value): + return value.frontSideDocument + default: + return nil + } + } + + var backSideVerificationDocument: SecureIdVerificationDocumentReference? { + switch self { + case let .idCard(value): + return value.backSideDocument + case let .driversLicense(value): + return value.backSideDocument + default: + return nil + } + } + + var hasBacksideDocument: Bool { + switch self { + case .idCard: + return true + case .driversLicense: + return true + default: + return false + } + } + + var passportValue: SecureIdPassportValue? { + switch self { + case let .passport(value): + return value + default: + return nil + } + } + + var phoneValue: SecureIdPhoneValue? { + switch self { + case let .phone(value): + return value + default: + return nil + } + } + var emailValue: SecureIdEmailValue? { + switch self { + case let .email(value): + return value + default: + return nil + } + } + + var requestFieldType: SecureIdRequestedFormFieldValue { + return key.requestFieldType + } + + var expiryDate: SecureIdDate? { + switch self { + case let .idCard(value): + return value.expiryDate + case let .passport(value): + return value.expiryDate + case let .driversLicense(value): + return value.expiryDate + default: + return nil + } + } +} + +extension SecureIdValueKey { + var requestFieldType: SecureIdRequestedFormFieldValue { + switch self { + case .address: + return .address + case .bankStatement: + return .bankStatement(translation: true) + case .driversLicense: + return .driversLicense(selfie: true, translation: true) + case .email: + return .email + case .idCard: + return .idCard(selfie: true, translation: true) + case .internalPassport: + return .internalPassport(selfie: true, translation: true) + case .passport: + return .passport(selfie: true, translation: true) + case .passportRegistration: + return .passportRegistration(translation: true) + case .personalDetails: + return .personalDetails(nativeName: true) + case .phone: + return .phone + case .rentalAgreement: + return .rentalAgreement(translation: true) + case .temporaryRegistration: + return .temporaryRegistration(translation: true) + case .utilityBill: + return .utilityBill(translation: true) + } + } +} + + +extension SecureIdRequestedFormFieldValue { + var rawValue: String { + switch self { + case .email: + return L10n.secureIdRequestPermissionEmail + case .phone: + return L10n.secureIdRequestPermissionPhone + case .address: + return L10n.secureIdRequestPermissionResidentialAddress + case .utilityBill: + return L10n.secureIdRequestPermissionUtilityBill + case .bankStatement: + return L10n.secureIdRequestPermissionBankStatement + case .rentalAgreement: + return L10n.secureIdRequestPermissionTenancyAgreement + case .passport: + return L10n.secureIdRequestPermissionPassport + case .idCard: + return L10n.secureIdRequestPermissionIDCard + case .driversLicense: + return L10n.secureIdRequestPermissionDriversLicense + case .personalDetails: + return L10n.secureIdRequestPermissionPersonalDetails + case .internalPassport: + return L10n.secureIdRequestPermissionInternalPassport + case .passportRegistration: + return L10n.secureIdRequestPermissionPassportRegistration + case .temporaryRegistration: + return L10n.secureIdRequestPermissionTemporaryRegistration + } + } + + func isKindOf(_ fieldValue: SecureIdRequestedFormFieldValue) -> Bool { + switch self { + case .email: + if case .email = fieldValue { + return true + } else { + return false + } + case .phone: + if case .phone = fieldValue { + return true + } else { + return false + } + case .address: + if case .address = fieldValue { + return true + } else { + return false + } + case .utilityBill: + if case .utilityBill = fieldValue { + return true + } else { + return false + } + case .bankStatement: + if case .bankStatement = fieldValue { + return true + } else { + return false + } + case .rentalAgreement: + if case .rentalAgreement = fieldValue { + return true + } else { + return false + } + case .passport: + if case .passport = fieldValue { + return true + } else { + return false + } + case .idCard: + if case .idCard = fieldValue { + return true + } else { + return false + } + case .driversLicense: + if case .driversLicense = fieldValue { + return true + } else { + return false + } + case .personalDetails: + if case .personalDetails = fieldValue { + return true + } else { + return false + } + case .internalPassport: + if case .internalPassport = fieldValue { + return true + } else { + return false + } + case .passportRegistration: + if case .passportRegistration = fieldValue { + return true + } else { + return false + } + case .temporaryRegistration: + if case .temporaryRegistration = fieldValue { + return true + } else { + return false + } + } + } + + var uploadFrontTitleText: String { + switch self { + case .idCard: + return L10n.secureIdUploadFront + case .driversLicense: + return L10n.secureIdUploadFront + default: + return L10n.secureIdUploadMain + } + } + var uploadBackTitleText: String { + switch self { + case .idCard: + return L10n.secureIdUploadReverse + case .driversLicense: + return L10n.secureIdUploadReverse + default: + return L10n.secureIdUploadMain + } + } + + var hasBacksideDocument: Bool { + switch self { + case .idCard: + return true + case .driversLicense: + return true + default: + return false + } + } + + var hasSelfie: Bool { + switch self { + case let .passport(selfie, _), let .idCard(selfie, _), let .driversLicense(selfie, _), let .internalPassport(selfie, _): + return selfie + default: + return false + } + } + + var hasTranslation: Bool { + switch self { + case let .passport(_, translation), let .idCard(_, translation), let .driversLicense(_, translation), let .internalPassport(_, translation): + return translation + case let .utilityBill(translation), let .rentalAgreement(translation), let .bankStatement(translation), let .passportRegistration(translation), let .temporaryRegistration(translation): + return translation + default: + return false + } + } + + var emptyDescription: String { + switch self { + case .email: + return L10n.secureIdRequestPermissionEmailEmpty + case .phone: + return L10n.secureIdRequestPermissionPhoneEmpty + case .utilityBill: + return L10n.secureIdEmptyDescriptionUtilityBill + case .bankStatement: + return L10n.secureIdEmptyDescriptionBankStatement + case .rentalAgreement: + return L10n.secureIdEmptyDescriptionTenancyAgreement + case .passportRegistration: + return L10n.secureIdEmptyDescriptionPassportRegistration + case .temporaryRegistration: + return L10n.secureIdEmptyDescriptionTemporaryRegistration + case .passport: + return L10n.secureIdEmptyDescriptionPassport + case .driversLicense: + return L10n.secureIdEmptyDescriptionDriversLicense + case .idCard: + return L10n.secureIdEmptyDescriptionIdentityCard + case .internalPassport: + return L10n.secureIdEmptyDescriptionInternalPassport + case .personalDetails: + return L10n.secureIdEmptyDescriptionPersonalDetails + case .address: + return L10n.secureIdEmptyDescriptionAddress + } + } + + var descAdd: String { + switch self { + case .email: + return "" + case .phone: + return "" + case .address: + return L10n.secureIdAddResidentialAddress + case .utilityBill: + return L10n.secureIdAddUtilityBill + case .bankStatement: + return L10n.secureIdAddBankStatement + case .rentalAgreement: + return L10n.secureIdAddTenancyAgreement + case .passport: + return L10n.secureIdAddPassport + case .idCard: + return L10n.secureIdAddID + case .driversLicense: + return L10n.secureIdAddDriverLicense + case .personalDetails: + return L10n.secureIdAddPersonalDetails + case .internalPassport: + return L10n.secureIdAddInternalPassport + case .passportRegistration: + return L10n.secureIdAddPassportRegistration + case .temporaryRegistration: + return L10n.secureIdAddTemporaryRegistration + } + } + + var descEdit: String { + switch self { + case .email: + return "" + case .phone: + return "" + case .address: + return L10n.secureIdEditResidentialAddress + case .utilityBill: + return L10n.secureIdEditUtilityBill + case .bankStatement: + return L10n.secureIdEditBankStatement + case .rentalAgreement: + return L10n.secureIdEditTenancyAgreement + case .passport: + return L10n.secureIdEditPassport + case .idCard: + return L10n.secureIdEditID + case .driversLicense: + return L10n.secureIdEditDriverLicense + case .personalDetails: + return L10n.secureIdEditPersonalDetails + case .internalPassport: + return L10n.secureIdEditInternalPassport + case .passportRegistration: + return L10n.secureIdEditPassportRegistration + case .temporaryRegistration: + return L10n.secureIdEditTemporaryRegistration + } + } +} + +var dateFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "dd.MM.yyyy" + // formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + return formatter +} + +extension SecureIdRequestedFormFieldValue { + var valueKey: SecureIdValueKey { + switch self { + case .address: + return .address + case .bankStatement: + return .bankStatement + case .driversLicense: + return .driversLicense + case .email: + return .email + case .idCard: + return .idCard + case .passport: + return .passport + case .personalDetails: + return .personalDetails + case .phone: + return .phone + case .rentalAgreement: + return .rentalAgreement + case .utilityBill: + return .utilityBill + case .internalPassport: + return .internalPassport + case .passportRegistration: + return .passportRegistration + case .temporaryRegistration: + return .temporaryRegistration + } + } + + var primary: SecureIdRequestedFormFieldValue { + if SecureIdRequestedFormField.just(self).isIdentityField { + return .personalDetails(nativeName: true) + } + if SecureIdRequestedFormField.just(self).isAddressField { + return .address + } + return self + } + + func isEqualToMRZ(_ mrz: TGPassportMRZ) -> Bool { + switch mrz.documentType.lowercased() { + case "p": + if case .passport = self { + return true + } else { + return false + } + default: + return false + } + } + +} + + + +extension InputDataValue { + var secureIdDate: SecureIdDate? { + switch self { + case let .date(day, month, year): + if let day = day, let month = month, let year = year { + return SecureIdDate(day: day, month: month, year: year) + } + + return nil + default: + return nil + } + } +} + +extension SecureIdDate { + var inputDataValue: InputDataValue { + return .date(day, month, year) + } +} + + +public func peerCompactDisplayTitles(_ peerIds: [PeerId], _ dict: SimpleDictionary) -> String { + var names:String = "" + for peerId in peerIds { + if let peer = dict[peerId] { + names += peer.compactDisplayTitle + if peerId != peerIds.last { + names += ", " + } + } + } + return names +} + +func mediaResource(from media:Media?) -> TelegramMediaResource? { + if let media = media as? TelegramMediaFile { + return media.resource + } else if let media = media as? TelegramMediaImage { + return largestImageRepresentation(media.representations)?.resource + } + return nil +} + +func mediaResourceMIMEType(from media:Media?) -> String? { + if let media = media as? TelegramMediaFile { + return media.mimeType + } else if media is TelegramMediaImage { + return "image/jpeg" + } + return nil +} + +func mediaResourceName(from media:Media?, ext:String?) -> String { + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + let ext = ext ?? ".file" + if let media = media as? TelegramMediaFile { + return media.fileName ?? "FILE " + dateFormatter.string(from: Date()) + "." + ext + } else if media is TelegramMediaImage { + return "IMAGE " + dateFormatter.string(from: Date()) + "." + ext + } + return "FILE " + dateFormatter.string(from: Date()) + "." + ext +} + + +func removeChatInteractively(context: AccountContext, peerId:PeerId, userId: PeerId? = nil, deleteGroup: Bool = false) -> Signal { + return context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in + let text:String + var okTitle: String? = nil + if let peer = peer as? TelegramChannel { + switch peer.info { + case .broadcast: + if peer.flags.contains(.isCreator) && deleteGroup { + text = L10n.confirmDeleteAdminedChannel + okTitle = L10n.confirmDelete + } else { + text = L10n.peerInfoConfirmLeaveChannel + } + case .group: + if deleteGroup && peer.flags.contains(.isCreator) { + text = L10n.peerInfoConfirmDeleteGroupConfirmation + okTitle = L10n.confirmDelete + } else { + text = L10n.confirmLeaveGroup + okTitle = L10n.peerInfoConfirmLeave + } + } + } else if let peer = peer as? TelegramGroup { + text = L10n.peerInfoConfirmDeleteChat(peer.title) + okTitle = L10n.confirmDelete + } else { + text = L10n.peerInfoConfirmDeleteUserChat + okTitle = L10n.confirmDelete + } + + + let type: ChatUndoActionType + + if let peer = peer as? TelegramChannel { + switch peer.info { + case .broadcast: + if peer.flags.contains(.isCreator) && deleteGroup { + type = .deleteChannel + } else { + type = .leftChannel + } + case .group: + if peer.flags.contains(.isCreator) && deleteGroup { + type = .deleteChat + } else { + type = .leftChat + } + } + } else { + type = .deleteChat + } + + var thridTitle: String? = nil + + var canRemoveGlobally: Bool = false + if peerId.namespace == Namespaces.Peer.CloudUser && peerId != context.account.peerId && !peer.isBot { + if context.limitConfiguration.maxMessageRevokeIntervalInPrivateChats == LimitsConfiguration.timeIntervalForever { + canRemoveGlobally = true + } + } + + if canRemoveGlobally { + thridTitle = L10n.chatMessageDeleteForMeAndPerson(peer.displayTitle) + } else if peer.isBot { + thridTitle = L10n.peerInfoStopBot + } + + + return modernConfirmSignal(for: mainWindow, account: context.account, peerId: userId ?? peerId, information: text, okTitle: okTitle ?? L10n.alertOK, thridTitle: thridTitle, thridAutoOn: false) |> mapToSignal { result -> Signal in + + context.sharedContext.bindings.mainController().chatList.addUndoAction(ChatUndoAction(peerId: peerId, type: type, action: { status in + switch status { + case .success: + context.chatUndoManager.removePeerChat(account: context.account, peerId: peerId, type: type, reportChatSpam: false, deleteGloballyIfPossible: deleteGroup || result == .thrid) + if peer.isBot && result == .thrid { + _ = context.blockedPeersContext.add(peerId: peerId).start() + } + default: + break + } + })) + + return .single(true) + } + } + +} + +func applyExternalProxy(_ server:ProxyServerSettings, accountManager: AccountManager) { + var textInfo = L10n.proxyForceEnableTextIP(server.host) + "\n" + L10n.proxyForceEnableTextPort(Int(server.port)) + switch server.connection { + case let .socks5(username, password): + if let user = username { + textInfo += "\n" + L10n.proxyForceEnableTextUsername(user) + } + if let pass = password { + textInfo += "\n" + L10n.proxyForceEnableTextPassword(pass) + } + case let .mtp(secret): + textInfo += "\n" + L10n.proxyForceEnableTextSecret(MTProxySecret.parseData(secret)?.serializeToString() ?? "") + } + + textInfo += "\n\n" + L10n.proxyForceEnableText + + if case .mtp = server.connection { + textInfo += "\n\n" + L10n.proxyForceEnableMTPDesc + } + + modernConfirm(for: mainWindow, account: nil, peerId: nil, header: L10n.proxyForceEnableHeader1, information: textInfo, okTitle: L10n.proxyForceEnableOK, thridTitle: L10n.proxyForceEnableEnable, successHandler: { result in + _ = updateProxySettingsInteractively(accountManager: accountManager, { current -> ProxySettings in + + var current = current.withAddedServer(server) + if result == .thrid { + current = current.withUpdatedActiveServer(server).withUpdatedEnabled(true) + } + return current + }).start() + }) + +// _ = (confirmSignal(for: mainWindow, header: tr(L10n.proxyForceEnableHeader), information: textInfo, okTitle: L10n.proxyForceEnableConnect) +// |> filter {$0} |> map {_ in} |> mapToSignal { +// return updateProxySettingsInteractively(postbox: postbox, network: network, { current -> ProxySettings in +// return current.withAddedServer(server).withUpdatedActiveServer(server).withUpdatedEnabled(true) +// }) +// }).start() +} + + +extension SecureIdGender { + var stringValue: String { + switch self { + case .female: + return L10n.secureIdGenderFemale + case .male: + return L10n.secureIdGenderMale + } + } +} + +extension SecureIdDate { + var stringValue: String { + return "\(day).\(month).\(year)" + } +} + + + +func clearCache(_ path: String, excludes: [(partial: String, complete: String)]) -> Signal { + return Signal { subscriber -> Disposable in + + let fileManager = FileManager.default + var enumerator = fileManager.enumerator(atPath: path + "/") + + while let file = enumerator?.nextObject() as? String { + if file != "cache" { + if excludes.filter ({ file.contains($0.partial.nsstring.lastPathComponent) || file.contains($0.complete.nsstring.lastPathComponent) }).isEmpty { + unlink(path + "/" + file) + } + } + } + + var p = path.nsstring.substring(to: path.nsstring.range(of: path.nsstring.lastPathComponent).location) + p = p.nsstring.substring(to: p.nsstring.range(of: p.nsstring.lastPathComponent).location) + "cached/" + + enumerator = fileManager.enumerator(atPath: p) + + while let file = enumerator?.nextObject() as? String { + + + if excludes.filter ({ file.contains($0.partial) || file.contains($0.complete) }).isEmpty { + unlink(p + file) + } + //try? fileManager.removeItem(atPath: p + file) + } + + subscriber.putNext(Void()) + subscriber.putCompletion() + return EmptyDisposable + } |> runOn(resourcesQueue) +} + +func moveWallpaperToCache(postbox: Postbox, resource: TelegramMediaResource, reference: WallpaperReference?, settings: WallpaperSettings, isPattern: Bool) -> Signal { + let resourceData: Signal + if isPattern { + resourceData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedPatternWallpaperMaskRepresentation(size: nil, settings: settings), complete: true) + } else if settings.blur { + resourceData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedBlurredWallpaperRepresentation(), complete: true) + } else { + resourceData = postbox.mediaBox.resourceData(resource) + } + + + return combineLatest(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: MediaResourceReference.wallpaper(wallpaper: reference, resource: resource), reportResultStatus: true) |> `catch` { _ in return .complete() }, resourceData) |> mapToSignal { _, data in + if data.complete { + return moveWallpaperToCache(postbox: postbox, path: data.path, resource: resource, settings: settings) + } else { + return .complete() + } + } +} + +func moveWallpaperToCache(postbox: Postbox, wallpaper: Wallpaper) -> Signal { + switch wallpaper { + case let .image(reps, settings): + return moveWallpaperToCache(postbox: postbox, resource: largestImageRepresentation(reps)!.resource, reference: nil, settings: settings, isPattern: false) |> map { _ in return wallpaper} + case let .custom(representation, blurred): + return moveWallpaperToCache(postbox: postbox, resource: representation.resource, reference: nil, settings: WallpaperSettings(blur: blurred), isPattern: false) |> map { _ in return wallpaper} + case let .file(slug, file, settings, isPattern): + return moveWallpaperToCache(postbox: postbox, resource: file.resource, reference: .slug(slug), settings: settings, isPattern: isPattern) |> map { _ in return wallpaper} + default: + return .single(wallpaper) + } +} + +func moveWallpaperToCache(postbox: Postbox, path: String, resource: TelegramMediaResource, settings: WallpaperSettings) -> Signal { + return Signal { subscriber in + + let wallpapers = "~/Library/Group Containers/\(ApiEnvironment.group)/Wallpapers/".nsstring.expandingTildeInPath + try? FileManager.default.createDirectory(at: URL(fileURLWithPath: wallpapers), withIntermediateDirectories: true, attributes: nil) + + let out = wallpapers + "/" + resource.id.uniqueId + "\(settings.stringValue)" + ".jpg" + + if !FileManager.default.fileExists(atPath: out) { + try? FileManager.default.removeItem(atPath: out) + try? FileManager.default.copyItem(atPath: path, toPath: out) + } + subscriber.putNext(out) + + subscriber.putCompletion() + return EmptyDisposable + + } +} + +extension WallpaperSettings { + var stringValue: String { + var value: String = "" + if let top = self.color { + value += "ctop\(top)" + } + if let top = self.bottomColor { + value += "cbottom\(top)" + } + if let rotation = self.rotation { + value += "rotation\(rotation)" + } + if self.blur { + value += "blur" + } + return value + } +} + +func wallpaperPath(_ resource: TelegramMediaResource, settings: WallpaperSettings) -> String { + + return "~/Library/Group Containers/\(ApiEnvironment.group)/Wallpapers/".nsstring.expandingTildeInPath + "/" + resource.id.uniqueId + "\(settings.stringValue)" + ".jpg" +} + + +func canCollagesFromUrl(_ urls:[URL]) -> Bool { + var canCollage: Bool = urls.count > 1 && urls.count <= 10 + if canCollage { + for url in urls { + let mime = MIMEType(url.path) + let attrs = Sender.fileAttributes(for: mime, path: url.path, isMedia: true) + let isGif = attrs.contains(where: { attr -> Bool in + switch attr { + case .Animated: + return true + default: + return false + } + }) + if mime.hasPrefix("image"), let image = NSImage(contentsOf: url) { + if image.size.width / 10 > image.size.height || image.size.height < 40 { + canCollage = false + break + } + } + if (!photoExts.contains(url.pathExtension.lowercased()) && !videoExts.contains(url.pathExtension.lowercased())) || isGif { + canCollage = false + break + } + } + } + + return canCollage +} + +extension AutomaticMediaDownloadSettings { + + func isDownloable(_ message: Message) -> Bool { + + if !automaticDownload { + return false + } + + + func ability(_ category: AutomaticMediaDownloadCategoryPeers, _ peer: Peer) -> Bool { + if peer.isGroup || peer.isSupergroup { + return category.groupChats + } else if peer.isChannel { + return category.channels + } else { + return category.privateChats + } + } + + func checkFile(_ media: TelegramMediaFile, _ peer: Peer, _ categories: AutomaticMediaDownloadCategories) -> Bool { + let size = Int32(media.size ?? 0) + + let dangerExts = "action app bin command csh osx workflow terminal url caction mpkg pkg xhtm webarchive" + + if let ext = media.fileName?.nsstring.pathExtension.lowercased(), dangerExts.components(separatedBy: " ").contains(ext) { + return false + } + + switch true { + case media.isInstantVideo: + return ability(categories.video, peer) && size <= (categories.video.fileSize ?? INT32_MAX) + case media.isVideo && media.isAnimated: + return ability(categories.video, peer) && size <= (categories.video.fileSize ?? INT32_MAX) + case media.isVideo: + return ability(categories.video, peer) && size <= (categories.video.fileSize ?? INT32_MAX) + case media.isVoice: + return size <= 1 * 1024 * 1024 + default: + return ability(categories.files, peer) && size <= (categories.files.fileSize ?? INT32_MAX) + } + } + + if let peer = messageMainPeer(message) { + if let _ = message.media.first as? TelegramMediaImage { + return ability(categories.photo, peer) + } else if let media = message.media.first as? TelegramMediaFile { + return checkFile(media, peer, categories) + } else if let media = message.media.first as? TelegramMediaWebpage { + switch media.content { + case let .Loaded(content): + if content.type == "telegram_background" { + return ability(categories.photo, peer) + } + if let file = content.file { + return checkFile(file, peer, categories) + } else if let _ = content.image { + return ability(categories.photo, peer) + } + default: + break + } + } else if let media = message.media.first as? TelegramMediaGame { + if let file = media.file { + return checkFile(file, peer, categories) + } else if let _ = media.image { + return ability(categories.photo, peer) + } + } + } + + return false + } +} + + +func fileExtenstion(_ file: TelegramMediaFile) -> String { + return fileExt(file.mimeType) ?? file.fileName?.nsstring.pathExtension ?? "" +} + +func proxySettings(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) |> map { view in + return view.entries[SharedDataKeys.proxySettings] as? ProxySettings ?? ProxySettings.defaultSettings + } +} + +extension ProxySettings { + func withUpdatedActiveServer(_ activeServer: ProxyServerSettings?) -> ProxySettings { + return ProxySettings(enabled: self.enabled, servers: servers, activeServer: activeServer, useForCalls: self.useForCalls) + } + + func withUpdatedEnabled(_ enabled: Bool) -> ProxySettings { + return ProxySettings(enabled: enabled, servers: self.servers, activeServer: self.activeServer, useForCalls: self.useForCalls) + } + + func withAddedServer(_ proxy: ProxyServerSettings) -> ProxySettings { + var servers = self.servers + if servers.first(where: {$0 == proxy}) == nil { + servers.append(proxy) + } + return ProxySettings(enabled: self.enabled, servers: servers, activeServer: self.activeServer, useForCalls: self.useForCalls) + } + + func withUpdatedServer(_ current: ProxyServerSettings, with updated: ProxyServerSettings) -> ProxySettings { + var servers = self.servers + if let index = servers.index(where: {$0 == current}) { + servers[index] = updated + } else { + servers.append(updated) + } + var activeServer = self.activeServer + if activeServer == current { + activeServer = updated + } + return ProxySettings(enabled: self.enabled, servers: servers, activeServer: activeServer, useForCalls: self.useForCalls) + } + + func withUpdatedUseForCalls(_ enable: Bool) -> ProxySettings { + return ProxySettings(enabled: self.enabled, servers: servers, activeServer: self.activeServer, useForCalls: enable) + } + + func withRemovedServer(_ proxy: ProxyServerSettings) -> ProxySettings { + var servers = self.servers + var activeServer = self.activeServer + var enabled: Bool = self.enabled + if let index = servers.firstIndex(where: {$0 == proxy}) { + _ = servers.remove(at: index) + } + if proxy == activeServer { + activeServer = nil + enabled = false + } + return ProxySettings(enabled: enabled, servers: servers, activeServer: activeServer, useForCalls: self.useForCalls) + } +} + +extension ProxyServerSettings { + var link: String { + let prefix: String + switch self.connection { + case .mtp: + prefix = "proxy" + case .socks5: + prefix = "socks" + } + var link = "tg://\(prefix)?server=\(self.host)&port=\(self.port)" + switch self.connection { + case let .mtp(secret): + link += "&secret=\((secret as NSData).hexString)" + case let .socks5(username, password): + if let username = username { + link += "&user=\(username)" + } + if let password = password { + link += "&pass=\(password)" + } + } + return link + } + + var isEmpty: Bool { + if host.isEmpty { + return true + } + if port == 0 { + return true + } + switch self.connection { + case let .mtp(secret): + if secret.isEmpty { + return true + } + default: + break + } + return false + } +} + + +struct SecureIdDocumentValue { + let document: SecureIdVerificationDocument + let stableId: AnyHashable + let context: SecureIdAccessContext + init(document: SecureIdVerificationDocument, context: SecureIdAccessContext, stableId: AnyHashable) { + self.document = document + self.stableId = stableId + self.context = context + } + var image: TelegramMediaImage { + return TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(100, 100), resource: document.resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } +} + +enum FaqDestination { + case telegram + case ton + case walletTOS + var url:String { + switch self { + case .telegram: + return "https://telegram.org/faq/" + case .ton: + return "https://telegram.org/faq/gram_wallet/" + case .walletTOS: + return "https://telegram.org/tos/wallet/" + } + } +} + +func openFaq(context: AccountContext, dest: FaqDestination = .telegram) { + let language = appCurrentLanguage.languageCode[appCurrentLanguage.languageCode.index(appCurrentLanguage.languageCode.endIndex, offsetBy: -2) ..< appCurrentLanguage.languageCode.endIndex] + + _ = showModalProgress(signal: webpagePreview(account: context.account, url: dest.url) |> deliverOnMainQueue, for: context.window).start(next: { webpage in + if let webpage = webpage { + showInstantPage(InstantPageViewController(context, webPage: webpage, message: nil)) + } else { + execute(inapp: .external(link: dest.url + language, true)) + } + }) +} + +func isNotEmptyStrings(_ strings: [String?]) -> String { + for string in strings { + if let string = string, !string.isEmpty { + return string + } + } + return "" +} + + +extension MessageIndex { + func withUpdatedTimestamp(_ timestamp: Int32) -> MessageIndex { + return MessageIndex(id: self.id, timestamp: timestamp) + } + init(_ message: Message) { + self.init(id: message.id, timestamp: message.timestamp) + } + +} + +func requestAudioPermission() -> Signal { + if #available(OSX 10.14, *) { + return Signal { subscriber in + let status = AVCaptureDevice.authorizationStatus(for: .audio) + var cancelled: Bool = false + switch status { + case .notDetermined: + AVCaptureDevice.requestAccess(for: .audio, completionHandler: { completed in + if !cancelled { + subscriber.putNext(completed) + subscriber.putCompletion() + } + }) + case .authorized: + subscriber.putNext(true) + subscriber.putCompletion() + case .denied: + subscriber.putNext(false) + subscriber.putCompletion() + case .restricted: + subscriber.putNext(false) + subscriber.putCompletion() + } + return ActionDisposable { + cancelled = true + } + } + } else { + return .single(true) + } +} + + +func requestMediaPermission(_ type: AVFoundation.AVMediaType) -> Signal { + if #available(OSX 10.14, *) { + return Signal { subscriber in + let status = AVCaptureDevice.authorizationStatus(for: type) + var cancelled: Bool = false + switch status { + case .notDetermined: + AVCaptureDevice.requestAccess(for: type, completionHandler: { completed in + if !cancelled { + subscriber.putNext(completed) + subscriber.putCompletion() + } + }) + case .authorized: + subscriber.putNext(true) + subscriber.putCompletion() + case .denied: + subscriber.putNext(false) + subscriber.putCompletion() + case .restricted: + subscriber.putNext(false) + subscriber.putCompletion() + @unknown default: + subscriber.putNext(false) + subscriber.putCompletion() + } + return ActionDisposable { + cancelled = true + } + } + } else { + return .single(true) + } +} + +enum SystemSettingsCategory : String { + case microphone = "Privacy_Microphone" + case storage = "Storage" + case none = "" +} + +func openSystemSettings(_ category: SystemSettingsCategory) { + switch category { + case .storage: + //if let url = URL(string: "/System/Applications/Utilities/System%20Information.app") { + NSWorkspace.shared.launchApplication("/System/Applications/Utilities/System Information.app") + // [[NSWorkspace sharedWorkspace] launchApplication:@"/Applications/Safari.app"]; + // } + case .microphone: + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?\(category.rawValue)") { + NSWorkspace.shared.open(url) + } + default: + break + } +} + +extension MessageHistoryAnchorIndex { + func withSubstractedTimestamp(_ timestamp: Int32) -> MessageHistoryAnchorIndex { + switch self { + case let .message(index): + return MessageHistoryAnchorIndex.message(MessageIndex(id: index.id, timestamp: index.timestamp - timestamp)) + default: + return self + } + } +} + + +extension ChatContextResultCollection { + func withAdditionalCollection(_ collection: ChatContextResultCollection) -> ChatContextResultCollection { + return ChatContextResultCollection(botId: collection.botId, peerId: collection.peerId, query: collection.query, geoPoint: collection.geoPoint, queryId: collection.queryId, nextOffset: collection.nextOffset, presentation: collection.presentation, switchPeer: collection.switchPeer, results: self.results + collection.results, cacheTimeout: collection.cacheTimeout) + } +} + +extension LocalFileReferenceMediaResource : Equatable { + public static func ==(lhs: LocalFileReferenceMediaResource, rhs: LocalFileReferenceMediaResource) -> Bool { + return lhs.isEqual(to: rhs) + } +} + + +public func removeFile(at path: String) { + try? FileManager.default.removeItem(atPath: path) +} + + +extension FileManager { + + func modificationDateForFileAtPath(path:String) -> NSDate? { + guard let attributes = try? self.attributesOfItem(atPath: path) else { return nil } + return attributes[.modificationDate] as? NSDate + } + + func creationDateForFileAtPath(path:String) -> NSDate? { + guard let attributes = try? self.attributesOfItem(atPath: path) else { return nil } + return attributes[.creationDate] as? NSDate + } + + +} + + +extension MessageForwardInfo { + var authorTitle: String { + return author?.displayTitle ?? authorSignature ?? "" + } +} + + +func bigEmojiMessage(_ sharedContext: SharedAccountContext, message: Message) -> Bool { + return sharedContext.baseSettings.bigEmoji && message.media.isEmpty && message.replyMarkup == nil && message.text.count <= 3 && message.text.containsOnlyEmoji +} + + + +struct PeerEquatable: Equatable { + let peer: Peer + init(peer: Peer) { + self.peer = peer + } + init(_ peer: Peer) { + self.peer = peer + } + static func ==(lhs: PeerEquatable, rhs: PeerEquatable) -> Bool { + return lhs.peer.isEqual(rhs.peer) + } +} + + +extension CGImage { + var cvPixelBuffer: CVPixelBuffer? { + var pixelBuffer: CVPixelBuffer? = nil + let options: [NSObject: Any] = [ + kCVPixelBufferCGImageCompatibilityKey: false, + kCVPixelBufferCGBitmapContextCompatibilityKey: false, + ] + let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32BGRA, options as CFDictionary, &pixelBuffer) + CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!) + let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: pixelData, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue) + context?.draw(self, in: CGRect(origin: .zero, size: size)) + CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + return pixelBuffer + } +} + + +private let emojis: [String: (String, CGFloat)] = [ + "👍": ("thumbs_up_1", 450.0), + "👍🏻": ("thumbs_up_2", 450.0), + "👍🏼": ("thumbs_up_3", 450.0), + "👍🏽": ("thumbs_up_4", 450.0), + "👍🏾": ("thumbs_up_5", 450.0), + "👍🏿": ("thumbs_up_6", 450.0), + "😂": ("lol", 350.0), + "😒": ("meh", 350.0), + "❤️": ("heart", 350.0), + "♥️": ("heart", 350.0), + "🥳": ("celeb", 430.0), + "😳": ("confused", 350.0) +] +func animatedEmojiResource(emoji: String) -> (LocalBundleResource, CGFloat)? { + if let (name, size) = emojis[emoji] { + return (LocalBundleResource(name: name, ext: "tgs"), size) + } else { + return nil + } +} + + +extension TelegramMediaWebpageLoadedContent { + func withUpdatedYoutubeTimecode(_ timecode: Double) -> TelegramMediaWebpageLoadedContent { + var newUrl = self.url + if let range = self.url.range(of: "t=") { + let substr = String(newUrl[range.upperBound...]) + var parsed: String = "" + for char in substr { + if "0987654321".contains(char) { + parsed += String(char) + } else { + break + } + } + newUrl = newUrl.replacingOccurrences(of: parsed, with: "\(Int(timecode))", options: .caseInsensitive, range: range.lowerBound ..< newUrl.endIndex) + } else { + if url.contains("?") { + newUrl = self.url + "&t=\(Int(timecode))" + } else { + newUrl = self.url + "?t=\(Int(timecode))" + } + } + return TelegramMediaWebpageLoadedContent(url: newUrl, displayUrl: self.displayUrl, hash: self.hash, type: self.type, websiteName: self.websiteName, title: self.title, text: self.text, embedUrl: self.embedUrl, embedType: self.embedType, embedSize: self.embedSize, duration: self.duration, author: self.author, image: self.image, file: self.file, attributes: self.attributes, instantPage: self.instantPage) + } + func withUpdatedFile(_ file: TelegramMediaFile) -> TelegramMediaWebpageLoadedContent { + return TelegramMediaWebpageLoadedContent(url: self.url, displayUrl: self.displayUrl, hash: self.hash, type: self.type, websiteName: self.websiteName, title: self.title, text: self.text, embedUrl: self.embedUrl, embedType: self.embedType, embedSize: self.embedSize, duration: self.duration, author: self.author, image: self.image, file: file, attributes: self.attributes, instantPage: self.instantPage) + } + + var isCrossplatformTheme: Bool { + for attr in attributes { + switch attr { + case let .theme(theme): + var hasFile: Bool = false + for file in theme.files { + if file.mimeType == "application/x-tgtheme-macos", !file.previewRepresentations.isEmpty { + hasFile = true + } + } + if let _ = theme.settings, !hasFile { + return true + } + default: + break + } + } + return false + } + + var crossplatformPalette: ColorPalette? { + for attr in attributes { + switch attr { + case let .theme(theme): + return theme.settings?.palette + default: + break + } + } + return nil + } + var crossplatformWallpaper: Wallpaper? { + for attr in attributes { + switch attr { + case let .theme(theme): + return theme.settings?.background?.uiWallpaper + default: + break + } + } + return nil + } + + var themeSettings: TelegramThemeSettings? { + for attr in attributes { + switch attr { + case let .theme(theme): + return theme.settings + default: + break + } + } + return nil + } +} + +extension TelegramBaseTheme { + var palette: ColorPalette { + switch self { + case .classic: + return dayClassicPalette + case .day: + return whitePalette + case .night: + return darkPalette + case .tinted: + return nightAccentPalette + } + } +} +extension TelegramThemeSettings { + var palette: ColorPalette { + return baseTheme.palette.withAccentColor(accent) + } + + var accent: PaletteAccentColor { + var messages: (top: NSColor, bottom: NSColor)? + if let message = self.messageColors { + let top = NSColor(argb: UInt32(bitPattern: message.top)) + let bottom = NSColor(argb: UInt32(bitPattern: message.bottom)) + messages = (top: top, bottom: bottom) + } else { + messages = nil + } + return PaletteAccentColor(NSColor(rgb: UInt32(bitPattern: self.accentColor)), messages) + } + + var background: TelegramWallpaper? { + if let wallpaper = self.wallpaper { + return wallpaper + } else { + if self.baseTheme == .classic { + return .builtin(WallpaperSettings()) + } + } + return nil + } + + var desc: String { + let wString: String + if let wallpaper = self.wallpaper { + wString = "\(wallpaper)" + } else { + wString = "" + } + return "\(self.accentColor)-\(self.baseTheme)-\(String(describing: self.messageColors?.top))-\(String(describing: self.messageColors?.bottom))-\(wString)" + } +} + +extension TelegramWallpaper { + var uiWallpaper: Wallpaper { + let t: Wallpaper + switch self { + case .builtin: + t = .builtin + case let .color(color): + t = .color(color) + case let .file(values): + t = .file(slug: values.slug, file: values.file, settings: values.settings, isPattern: values.isPattern) + case let .gradient(top, bottom, settings): + t = .gradient(top, bottom, settings.rotation) + case let .image(reps, settings): + t = .image(reps, settings: settings) + } + return t + } +} + +extension Wallpaper { + var cloudWallpaper: TelegramWallpaper? { + switch self { + case .builtin: + return .builtin(WallpaperSettings()) + case let .color(color): + return .color(color) + case let .gradient(top, bottom, rotation): + return .gradient(top, bottom, WallpaperSettings(rotation: rotation)) + default: + break + } + return nil + } +} + +// diff --git a/Telegram-Mac/CrashHandler.swift b/Telegram-Mac/CrashHandler.swift new file mode 100644 index 0000000000..74ca425ed8 --- /dev/null +++ b/Telegram-Mac/CrashHandler.swift @@ -0,0 +1,51 @@ +// +// CrashHandler.swift +// Telegram +// +// Created by Mikhail Filimonov on 07/02/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +func isCrashedLastTime(_ folder: String) -> Bool { + let url = folder + "/" + "crashhandler" + + if let dateString = try? String(contentsOf: URL(fileURLWithPath: url)) { + let components = dateString.components(separatedBy: " ") + if components.count == 2 { + let initedDate = Int32(components[0]) ?? 0 + let lastSavedDate = Int32(components[1]) ?? 0 + return lastSavedDate - initedDate < 10 + } else { + return true + } + } + + return FileManager.default.fileExists(atPath: url) +} + +func crashIntermediateDate(_ folder: String) { + let url = folder + "/" + "crashhandler" + if let dateString = try? String(contentsOf: URL(fileURLWithPath: url)) { + let time = "\(Int32(Date().timeIntervalSince1970))" + var components = dateString.components(separatedBy: " ") + if components.count == 2 { + components[1] = time + } else if components.count == 1 { + components.append(time) + } + try? FileManager.default.removeItem(atPath: url) + FileManager.default.createFile(atPath: url, contents: components.joined(separator: " ").data(using: .utf8), attributes: nil) + + } else { + let time = "\(Int32(Date().timeIntervalSince1970))".data(using: .utf8) + try? FileManager.default.removeItem(atPath: url) + FileManager.default.createFile(atPath: url, contents: time, attributes: nil) + } +} + +func deinitCrashHandler(_ folder: String) { + let url = folder + "/" + "crashhandler" + try? FileManager.default.removeItem(atPath: url) +} diff --git a/Telegram-Mac/CreateChannelViewController.swift b/Telegram-Mac/CreateChannelViewController.swift index f504171d02..d4b484a154 100644 --- a/Telegram-Mac/CreateChannelViewController.swift +++ b/Telegram-Mac/CreateChannelViewController.swift @@ -7,39 +7,138 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit -class CreateChannelViewController: ComposeViewController { +class CreateChannelViewController: ComposeViewController<(PeerId?, Bool), Void, TableView> { private var nameItem:GroupNameRowItem! - private var descItem:GeneralInputRowItem! - + private var descItem:InputDataRowItem! + private let disposable = MetaDisposable() + private var picture: String? { + didSet { + nameItem.photo = picture + genericView.reloadData() + } + } override func viewDidLoad() { super.viewDidLoad() self.nextEnabled(false) - nameItem = GroupNameRowItem(atomicSize.modify({$0}), stableId: 0, placeholder: tr(.channelChannelNameHolder), limit: 140, textChangeHandler:{ [weak self] text in + + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let initialSize = atomicSize.with { $0 } + + nameItem = GroupNameRowItem(initialSize, stableId: 0, account: context.account, placeholder: L10n.channelChannelNameHolder, viewType: .singleItem, limit: 140, textChangeHandler:{ [weak self] text in self?.nextEnabled(!text.isEmpty) + }, pickPicture: { [weak self] select in + if select { + filePanel(with: photoExts, allowMultiple: false, canChooseDirectories: false, for: mainWindow, completion: { paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: mainWindow, animationType: .scaleCenter) + _ = (controller.result |> deliverOnMainQueue).start(next: { url, _ in + self?.picture = url.path + }) + + controller.onClose = { + removeFile(at: path) + } + }) + } + }) + } else { + self?.picture = nil + } }) - descItem = GeneralInputRowItem(atomicSize.modify({$0}), stableId: 2, placeholder: tr(.channelDescriptionHolder), limit: 300) + descItem = InputDataRowItem(initialSize, stableId: arc4random(), mode: .plain, error: nil, viewType: .singleItem, currentText: "", placeholder: nil, inputPlaceholder: L10n.channelDescriptionHolder, filter: { $0 }, updated: { _ in }, limit: 255) + + _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 30, stableId: arc4random(), viewType: .separator)) + _ = genericView.addItem(item: GeneralTextRowItem(initialSize, stableId: arc4random(), text: L10n.channelNameHeader, viewType: .textTopItem)) _ = genericView.addItem(item: nameItem) - _ = genericView.addItem(item: GeneralRowItem(atomicSize.modify({$0}), height: 30, stableId: 1)) + _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 30, stableId: arc4random(), viewType: .separator)) + _ = genericView.addItem(item: GeneralTextRowItem(initialSize, stableId: arc4random(), text: L10n.channelDescHeader, viewType: .textTopItem)) _ = genericView.addItem(item: descItem) - _ = genericView.addItem(item: GeneralTextRowItem(atomicSize.modify({$0}), stableId: 3, text: tr(.channelDescriptionHolderDescrpiton))) + _ = genericView.addItem(item: GeneralTextRowItem(initialSize, stableId: arc4random(), text: L10n.channelDescriptionHolderDescrpiton, viewType: .textBottomItem)) + _ = genericView.addItem(item: GeneralRowItem(initialSize, height: 30, stableId: arc4random(), viewType: .separator)) readyOnce() } + override func backKeyAction() -> KeyHandlerResult { + return .invokeNext + } + override var removeAfterDisapper: Bool { return true } + override func returnKeyAction() -> KeyHandlerResult { + if let event = NSApp.currentEvent, let descView = genericView.viewNecessary(at: descItem.index) as? GeneralInputRowView { + if !descView.textViewEnterPressed(event), window?.firstResponder == descView.textView.inputView { + return .invokeNext + } + } + + return super.returnKeyAction() + } + override func executeNext() { - onComplete.set(showModalProgress(signal: createChannel(account: account, title: nameItem.text, description: descItem.text), for: window!)) + let picture = self.picture + let context = self.context + + if nameItem.currentText.string.isEmpty { + nameItem.view?.shakeView() + return + } + + let signal: Signal<(PeerId, Bool)?, CreateChannelError> = showModalProgress(signal: createChannel(account: context.account, title: nameItem.currentText.string, description: descItem.currentText.string), for: window!, disposeAfterComplete: false) |> mapToSignal { peerId in + if let picture = picture { + let resource = LocalFileReferenceMediaResource(localFilePath: picture, randomId: arc4random64()) + let signal:Signal<(PeerId, Bool)?, CreateChannelError> = updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> mapError { _ in CreateChannelError.generic } |> map { value in + switch value { + case .complete: + return (peerId, false) + default: + return nil + } + } + + return .single((peerId, true)) |> then(signal) + } + return .single((peerId, true)) + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] value in + if let value = value { + self?.onComplete.set(.single((value.0, value.1))) + } + }, error: { error in + let text: String + switch error { + case .generic: + text = L10n.unknownError + case .tooMuchJoined: + showInactiveChannels(context: context, source: .create) + return + case let .serverProvided(t): + text = t + default: + text = L10n.unknownError + } + alert(for: context.window, info: text) + })) + } override var canBecomeResponder: Bool { @@ -47,12 +146,12 @@ class CreateChannelViewController: ComposeViewController Bool? { - return false + return true } override func firstResponder() -> NSResponder? { if let window = window { - if let nameView = genericView.viewNecessary(at: nameItem.index) as? GroupNameRowView, let descView = genericView.viewNecessary(at: descItem.index) as? GeneralInputRowView { + if let nameView = genericView.viewNecessary(at: nameItem.index) as? GroupNameRowView, let descView = genericView.viewNecessary(at: descItem.index) as? InputDataRowView { nameView.textView.inputView.nextKeyView = descView.textView.inputView nameView.textView.inputView.nextResponder = descView.textView.inputView if window.firstResponder != nameView.textView.inputView && window.firstResponder != descView.textView.inputView { @@ -64,5 +163,9 @@ class CreateChannelViewController: ComposeViewController Signal { - - switch reference { - case let .remoteImage(imageId, accesshash): - let api = Api.functions.photos.deletePhotos(id: [Api.InputPhoto.inputPhoto(id: imageId, accessHash: accesshash)]) - return account.network.request(api) |> map {_ in} |> retryRequest - case .none: - let api = Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty) - return account.network.request(api) |> map { _ in } |> retryRequest - } - -} - - -func channelAdminIds(postbox: Postbox, network: Network, peerId: PeerId, hash: Int32) -> Signal<[PeerId], Void> { - return postbox.modify { modifier in - if let peer = modifier.getPeer(peerId) as? TelegramChannel, case .group = peer.info, let apiChannel = apiInputChannel(peer) { - let api = Api.functions.channels.getParticipants(channel: apiChannel, filter: .channelParticipantsAdmins, offset: 0, limit: 100, hash: hash) - return network.request(api) |> retryRequest |> mapToSignal { result in - switch result { - case let .channelParticipants(_, _, users): - return .single(users.map({TelegramUser(user: $0).id})) - default: - return .complete() - } - } - } - return .complete() - } |> switchToLatest -} diff --git a/Telegram-Mac/CreateGroupViewController.swift b/Telegram-Mac/CreateGroupViewController.swift index 71d466261c..d5bf7417e6 100644 --- a/Telegram-Mac/CreateGroupViewController.swift +++ b/Telegram-Mac/CreateGroupViewController.swift @@ -7,52 +7,75 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit + +fileprivate final class CreateGroupArguments { + let context: AccountContext + let choicePicture:(Bool)->Void + let updatedText:(String)->Void + init(context: AccountContext, choicePicture:@escaping(Bool)->Void, updatedText:@escaping(String)->Void) { + self.context = context + self.updatedText = updatedText + self.choicePicture = choicePicture + } +} + fileprivate enum CreateGroupEntry : Comparable, Identifiable { - case info - case peer(Peer, Int, PeerPresence?) - + case info(Int32, String?, String, GeneralViewType) + case peer(Int32, Peer, Int32, PeerPresence?, GeneralViewType) + case section(Int32) fileprivate var stableId:AnyHashable { switch self { case .info: - return Int32(0) - case let .peer(peer, _, _): + return -1 + case let .peer(_, peer, _, _, _): return peer.id + case let .section(sectionId): + return sectionId } } - var index:Int { + var index:Int32 { switch self { - case .info: - return 0 - case let .peer(_, index, _): - return index + 1 + case let .info(sectionId, _, _, _): + return (sectionId * 1000) + 0 + case let .peer(sectionId, _, index, _, _): + return (sectionId * 1000) + index + case let .section(sectionId): + return (sectionId + 1) * 1000 - sectionId } } } fileprivate func ==(lhs:CreateGroupEntry, rhs:CreateGroupEntry) -> Bool { switch lhs { - case .info: - if case .info = rhs { + case let .info(section, photo, text, viewType): + if case .info(section, photo, text, viewType) = rhs { return true } else { return false } - case let .peer(lhsPeer,lhsIndex, lhsPresence): - if case let .peer(rhsPeer,rhsIndex, rhsPresence) = rhs { + case let .section(sectionId): + if case .section(sectionId) = rhs { + return true + } else { + return false + } + case let .peer(sectionId, lhsPeer, index, lhsPresence, viewType): + if case .peer(sectionId, let rhsPeer, index, let rhsPresence, viewType) = rhs { if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { if !lhsPresence.isEqual(to: rhsPresence) { return false } - } else if (lhsPresence != nil) != (rhsPresence != nil) { + } else if (lhsPresence != nil) != (rhsPresence != nil) { return false } - return lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex + return lhsPeer.isEqual(rhsPeer) } else { return false } @@ -65,26 +88,29 @@ fileprivate func <(lhs:CreateGroupEntry, rhs:CreateGroupEntry) -> Bool { struct CreateGroupResult { let title:String + let picture: String? let peerIds:[PeerId] } -fileprivate func prepareEntries(from:[AppearanceWrapperEntry], to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, animated:Bool) -> Signal { +fileprivate func prepareEntries(from:[AppearanceWrapperEntry], to:[AppearanceWrapperEntry], arguments: CreateGroupArguments, initialSize:NSSize, animated:Bool) -> Signal { return Signal { subscriber in let (deleted,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in switch entry.entry { - case .info: - return GroupNameRowItem(initialSize, stableId:entry.stableId, placeholder:tr(.createGroupNameHolder), limit:140) - case let .peer(peer, _, presence): + case let .info(_, photo, currentText, viewType): + return GroupNameRowItem(initialSize, stableId:entry.stableId, account: arguments.context.account, placeholder: L10n.createGroupNameHolder, photo: photo, viewType: viewType, text: currentText, limit:140, textChangeHandler: arguments.updatedText, pickPicture: arguments.choicePicture) + case let .peer(_, peer, _, presence, viewType): var color:NSColor = theme.colors.grayText - var string:String = tr(.peerStatusRecently) + var string:String = L10n.peerStatusRecently if let presence = presence as? TelegramUserPresence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) } - return ShortPeerRowItem(initialSize, peer: peer, account:account, height:50, photoSize:NSMakeSize(36, 36), statusStyle: ControlStyle(foregroundColor: color), status: string, inset:NSEdgeInsets(left: 30, right:30)) + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, height:50, photoSize:NSMakeSize(36, 36), statusStyle: ControlStyle(foregroundColor: color), status: string, inset:NSEdgeInsets(left: 30, right:30), viewType: viewType) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: entry.stableId, viewType: .separator) } }) @@ -98,14 +124,31 @@ fileprivate func prepareEntries(from:[AppearanceWrapperEntry], } -private func createGroupEntries(_ view: MultiplePeersView, appearance: Appearance) -> [AppearanceWrapperEntry] { +private func createGroupEntries(_ view: MultiplePeersView, picture: String?, text: String, appearance: Appearance) -> [AppearanceWrapperEntry] { + + + + var entries:[CreateGroupEntry] = [] + var sectionId:Int32 = 0 + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.info(sectionId, picture, text, .singleItem)) - var entries:[CreateGroupEntry] = [.info] - var index:Int = 0 - for peer in view.peers.map({$1}) { - entries.append(.peer(peer, index, view.presences[peer.id])) + entries.append(.section(sectionId)) + sectionId += 1 + + var index:Int32 = 0 + let peers = view.peers.map({$1}) + for (i, peer) in peers.enumerated() { + entries.append(.peer(sectionId, peer, index, view.presences[peer.id], bestGeneralViewType(peers, for: i))) index += 1 } + + entries.append(.section(sectionId)) + sectionId += 1 + return entries.map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} } @@ -113,28 +156,64 @@ private func createGroupEntries(_ view: MultiplePeersView, appearance: Appearanc class CreateGroupViewController: ComposeViewController { // Title, photo path private let entries:Atomic<[AppearanceWrapperEntry]> = Atomic(value:[]) private let disposable:MetaDisposable = MetaDisposable() + private let pictureValue = Promise(nil) + private let textValue = ValuePromise("", ignoreRepeated: true) + + private let defaultText: String + init(titles: ComposeTitles, context: AccountContext, defaultText: String = "") { + self.defaultText = defaultText + super.init(titles: titles, context: context) + self.textValue.set(self.defaultText) + } override func restart(with result: ComposeState<[PeerId]>) { super.restart(with: result) assert(isLoaded()) let initialSize = self.atomicSize let table = self.genericView + let pictureValue = self.pictureValue + let textValue = self.textValue - let account: Account = self.account + let entries = self.entries + let arguments = CreateGroupArguments(context: context, choicePicture: { select in + if select { + + filePanel(with: photoExts, allowMultiple: false, canChooseDirectories: false, for: mainWindow, completion: { paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: mainWindow, animationType: .scaleCenter) + pictureValue.set(controller.result |> map {Optional($0.0.path)}) + + + controller.onClose = { + removeFile(at: path) + } + }) + } + }) + + } else { + pictureValue.set(.single(nil)) + } + + }, updatedText: { text in + textValue.set(text) + }) - let signal:Signal = combineLatest(account.postbox.multiplePeersView(result.result) |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> mapToSignal { view, appearance in - let list = createGroupEntries(view, appearance: appearance) + let signal:Signal = combineLatest(context.account.postbox.multiplePeersView(result.result) |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue, pictureValue.get() |> deliverOnPrepareQueue, textValue.get() |> deliverOnPrepareQueue) |> mapToSignal { view, appearance, picture, text in + let list = createGroupEntries(view, picture: picture, text: text, appearance: appearance) - return prepareEntries(from: entries.swap(list), to: list, account: account, initialSize: initialSize.modify({$0}), animated: true) + return prepareEntries(from: entries.swap(list), to: list, arguments: arguments, initialSize: initialSize.modify({$0}), animated: true) } |> deliverOnMainQueue disposable.set(signal.start(next: { (transition) in table.merge(with: transition) - table.reloadData() + //table.reloadData() })) } @@ -147,7 +226,7 @@ class CreateGroupViewController: ComposeViewController NSResponder? { - if let view = genericView.viewNecessary(at: 0) as? GroupNameRowView { + if let view = genericView.viewNecessary(at: 1) as? GroupNameRowView { return view.textView } return nil @@ -164,15 +243,27 @@ class CreateGroupViewController: ComposeViewController Void { - if let previousResult = previousResult, let item = self.genericView.item(at: 0) as? GroupNameRowItem { - onComplete.set(.single(CreateGroupResult(title: item.text, peerIds: previousResult.result))) + if let previousResult = previousResult { + let result = combineLatest(pictureValue.get() |> take(1), textValue.get() |> take(1)) |> map { value, text in + return CreateGroupResult(title: text, picture: value, peerIds: previousResult.result) + } + onComplete.set(result |> filter { + !$0.title.isEmpty + }) } } + override func backKeyAction() -> KeyHandlerResult { + return .invokeNext + } + } diff --git a/Telegram-Mac/CustomAccentColorModalController.swift b/Telegram-Mac/CustomAccentColorModalController.swift new file mode 100644 index 0000000000..ce892abbe8 --- /dev/null +++ b/Telegram-Mac/CustomAccentColorModalController.swift @@ -0,0 +1,233 @@ +// +// CustomAccentColorModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class CustomAccentColorView : View { + private let tableView: TableView = TableView(frame: NSZeroRect) + weak var controller: ModalViewController? + let colorPicker = WallpaperColorPickerContainerView(frame: NSZeroRect) + let tintedCheckbox: ApplyblurCheckbox = ApplyblurCheckbox(frame: NSMakeRect(0, 0, 70, 24), title: L10n.accentColorsTinted) + private let context: AccountContext + fileprivate var disableTint: Bool = false { + didSet { + //colorPicker.colorChanged?(colorPicker.colorPicker.color) + } + } + required init(frame frameRect: NSRect, theme: TelegramPresentationTheme, context: AccountContext) { + self.context = context + super.init(frame: frameRect) + self.addSubview(tableView) + self.addSubview(colorPicker) + self.addSubview(tintedCheckbox) + colorPicker.colorPicker.color = theme.colors.accent + // colorPicker.defaultColor = colorPicker.colorPicker.color + tintedCheckbox.update(by: nil) + tintedCheckbox.isSelected = theme.colors.tinted + tintedCheckbox.isHidden = true//!theme.colors.tinted || !theme.bubbled + colorPicker.colorPicker.colorChanged = { [weak self] color in + guard let `self` = self else {return} + self.colorPicker.updateMode(.single(color), animated: true) + } + + self.colorPicker.updateMode(.single(theme.colors.accent), animated: true) + + + tintedCheckbox.onChangedValue = { [weak self] value in + self?.disableTint = !value + } + + colorPicker.colorChanged = { [weak self] color in + guard let `self` = self else {return} + + switch color { + case let .single(color): + self.colorPicker.colorPicker.color = color + self.colorPicker.colorPicker.needsLayout = true + let colors = theme.colors.withoutAccentColor().withAccentColor(PaletteAccentColor(color), disableTint: self.disableTint) + let newTheme = theme.withUpdatedColors(colors) + self.addTableItems(self.context, theme: newTheme) + self.tableView.updateLocalizationAndTheme(theme: newTheme) + self.controller?.updateLocalizationAndTheme(theme: newTheme) + self.colorPicker.updateLocalizationAndTheme(theme: newTheme) + self.tintedCheckbox.update(by: nil) + self.colorPicker.updateMode(.single(color), animated: true) + default: + break + } + + + } + + + + tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self else { + return + } + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + })) + + layout() + } + + override func layout() { + super.layout() + tableView.frame = NSMakeRect(0, 0, frame.width, frame.height - 160) + colorPicker.frame = NSMakeRect(0, frame.height - 160, frame.width, 160) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + fileprivate func addTableItems(_ context: AccountContext, theme: TelegramPresentationTheme) { + + tableView.removeAll() + + self.tableView.getBackgroundColor = { + theme.colors.chatBackground + } + + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: 10, stableId: arc4random(), backgroundColor: theme.chatBackground)) + + let chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context, disableSelectAbility: true) + + chatInteraction.getGradientOffsetRect = { [weak self] in + guard let `self` = self else { + return .zero + } + let offset = self.tableView.scrollPosition().current.rect.origin + return CGRect(origin: offset, size: self.tableView.frame.size) + } + + let fromUser1 = TelegramUser(id: PeerId(1), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName1, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + let fromUser2 = TelegramUser(id: PeerId(2), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName2, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + + let replyMessage = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreviewZeroText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + + let firstMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 0), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 20 + 60*60*18, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser2, text: tr(L10n.appearanceSettingsChatPreviewFirstText), attributes: [ReplyMessageAttribute(messageId: replyMessage.id)], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary([replyMessage.id : replyMessage]), associatedMessageIds: []) + + let firstEntry: ChatHistoryEntry = .MessageEntry(firstMessage, MessageIndex(firstMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + let secondMessage = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreviewSecondText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let secondEntry: ChatHistoryEntry = .MessageEntry(secondMessage, MessageIndex(secondMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + let item1 = ChatRowItem.item(frame.size, from: firstEntry, interaction: chatInteraction, theme: theme) + let item2 = ChatRowItem.item(frame.size, from: secondEntry, interaction: chatInteraction, theme: theme) + + + _ = item1.makeSize(frame.width, oldWidth: 0) + _ = item2.makeSize(frame.width, oldWidth: 0) + + _ = tableView.addItem(item: item1) + _ = tableView.addItem(item: item2) + + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: max(10, 160 - tableView.listHeight), stableId: arc4random(), backgroundColor: theme.chatBackground)) + + + + } + +} + + +class CustomAccentColorModalController: ModalViewController { + + private let context: AccountContext + private let updateColor: (PaletteAccentColor)->Void + init(context: AccountContext, updateColor: @escaping(PaletteAccentColor)->Void) { + self.context = context + self.updateColor = updateColor + super.init(frame: NSMakeRect(0, 0, 350, 370)) + self.bar = .init(height: 0) + } + private var currentTheme: TelegramPresentationTheme = theme + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + currentTheme = theme as! TelegramPresentationTheme + self.modal?.updateLocalizationAndTheme(theme: theme) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.controller = self + + genericView.addTableItems(self.context, theme: theme) + + readyOnce() + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: nil, center: ModalHeaderData(title: L10n.generalSettingsAccentColor), right: ModalHeaderData(image: currentTheme.icons.modalClose, handler: { [weak self] in + self?.close() + })) + } + + private func saveAccent() { + let color = genericView.colorPicker.colorPicker.color + self.updateColor(PaletteAccentColor(color)) + + delay(0.1, closure: { [weak self] in + self?.close() + }) + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: L10n.modalSet, accept: { [weak self] in + self?.saveAccent() + }) + } + + override var dynamicSize: Bool { + return true + } + + override func initializer() -> NSView { + return CustomAccentColorView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), theme: currentTheme, context: self.context) + } + + override func measure(size: NSSize) { + self.modal?.resize(with: NSMakeSize(350, 370), animated: false) + } + + private var genericView:CustomAccentColorView { + return self.view as! CustomAccentColorView + } + override func viewClass() -> AnyClass { + return CustomAccentColorView.self + } + + override var handleAllEvents: Bool { + return false + } + + override func firstResponder() -> NSResponder? { + return nil//genericView.colorPicker.textView + } +} diff --git a/Telegram-Mac/DataAndStorageViewController.swift b/Telegram-Mac/DataAndStorageViewController.swift index e35400c1c1..712a895c77 100644 --- a/Telegram-Mac/DataAndStorageViewController.swift +++ b/Telegram-Mac/DataAndStorageViewController.swift @@ -8,38 +8,163 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit -private enum AutomaticDownloadCategory { +enum DataAndStorageEntryTag : ItemListItemTag { + case automaticDownloadReset + case autoplayGifs + case autoplayVideos + + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? DataAndStorageEntryTag, self == other { + return true + } else { + return false + } + } + var stableId: Int32 { + switch self { + case .automaticDownloadReset: + return 10 + case .autoplayGifs: + return 13 + case .autoplayVideos: + return 14 + } + } +} + + +public func autodownloadDataSizeString(_ size: Int64) -> String { + if size >= 1024 * 1024 * 1024 { + let remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102) + if remainder != 0 { + return "\(size / (1024 * 1024 * 1024)),\(remainder) GB" + } else { + return "\(size / (1024 * 1024 * 1024)) GB" + } + } else if size >= 1024 * 1024 { + let remainder = (size % (1024 * 1024)) / (1024 * 102) + if size < 10 * 1024 * 1024 { + return "\(size / (1024 * 1024)),\(remainder) MB" + } else { + return "\(size / (1024 * 1024)) MB" + } + } else if size >= 1024 { + return "\(size / 1024) KB" + } else { + return "\(size) B" + } +} + + +private struct AutomaticDownloadPeers { + let privateChats: Bool + let groups: Bool + let channels: Bool + let size: Int32? + + init(category: AutomaticMediaDownloadCategoryPeers) { + self.privateChats = category.privateChats + self.groups = category.groupChats + self.channels = category.channels + self.size = category.fileSize + } +} + + +private func stringForAutomaticDownloadPeers(peers: AutomaticDownloadPeers, category: AutomaticDownloadCategory) -> String { + var size: String? + if var peersSize = peers.size, category == .video || category == .file { + if peersSize == Int32.max { + peersSize = 1536 * 1024 * 1024 + } + size = autodownloadDataSizeString(Int64(peersSize)) + } + + if peers.privateChats && peers.groups && peers.channels { + if let size = size { + return L10n.autoDownloadSettingsUpToForAll(size) + } else { + return L10n.autoDownloadSettingsOnForAll + } + } else { + var types: [String] = [] + if peers.privateChats { + types.append(L10n.autoDownloadSettingsTypePrivateChats) + } + if peers.groups { + types.append(L10n.autoDownloadSettingsTypeGroupChats) + } + if peers.channels { + types.append(L10n.autoDownloadSettingsTypeChannels) + } + + if types.isEmpty { + return L10n.autoDownloadSettingsOffForAll + } + + var string: String = "" + for i in 0 ..< types.count { + if !string.isEmpty { + if i == types.count - 1 { + string.append(L10n.autoDownloadSettingsLastDelimeter) + } else { + string.append(L10n.autoDownloadSettingsDelimeter) + } + } + string.append(types[i]) + } + + if let size = size { + return L10n.autoDownloadSettingsUpToFor(size, string) + } else { + return L10n.autoDownloadSettingsOnFor(string) + } + } +} + + +enum AutomaticDownloadCategory { case photo - case voice - case instantVideo - case gif + case video + case file } -private enum AutomaticDownloadPeers { - case privateChats - case groupsAndChannels +private enum AutomaticDownloadPeerType { + case contact + case otherPrivate + case group + case channel } + private final class DataAndStorageControllerArguments { let openStorageUsage: () -> Void let openNetworkUsage: () -> Void - let toggleAutomaticDownload: (AutomaticDownloadCategory, AutomaticDownloadPeers, Bool) -> Void - let openVoiceUseLessData: () -> Void - let toggleSaveIncomingPhotos: (Bool) -> Void - let toggleSaveEditedPhotos: (Bool) -> Void - - init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, toggleAutomaticDownload: @escaping (AutomaticDownloadCategory, AutomaticDownloadPeers, Bool) -> Void, openVoiceUseLessData: @escaping () -> Void, toggleSaveIncomingPhotos: @escaping (Bool) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void) { + let openCategorySettings: (AutomaticMediaDownloadCategoryPeers, String) -> Void + let toggleAutomaticDownload:(Bool) -> Void + let resetDownloadSettings:()->Void + let selectDownloadFolder: ()->Void + let toggleAutoplayGifs:(Bool) -> Void + let toggleAutoplayVideos:(Bool) -> Void + let toggleAutoplaySoundOnHover:(Bool) -> Void + let openProxySettings:()->Void + init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openCategorySettings: @escaping(AutomaticMediaDownloadCategoryPeers, String) -> Void, toggleAutomaticDownload:@escaping(Bool) -> Void, resetDownloadSettings:@escaping()->Void, selectDownloadFolder: @escaping() -> Void, toggleAutoplayGifs: @escaping(Bool) -> Void, toggleAutoplayVideos:@escaping(Bool) -> Void, toggleAutoplaySoundOnHover:@escaping(Bool) -> Void, openProxySettings: @escaping()->Void) { self.openStorageUsage = openStorageUsage self.openNetworkUsage = openNetworkUsage + self.openCategorySettings = openCategorySettings self.toggleAutomaticDownload = toggleAutomaticDownload - self.openVoiceUseLessData = openVoiceUseLessData - self.toggleSaveIncomingPhotos = toggleSaveIncomingPhotos - self.toggleSaveEditedPhotos = toggleSaveEditedPhotos + self.resetDownloadSettings = resetDownloadSettings + self.selectDownloadFolder = selectDownloadFolder + self.toggleAutoplayGifs = toggleAutoplayGifs + self.toggleAutoplayVideos = toggleAutoplayVideos + self.toggleAutoplaySoundOnHover = toggleAutoplaySoundOnHover + self.openProxySettings = openProxySettings } } @@ -54,22 +179,26 @@ private enum DataAndStorageSection: Int32 { private enum DataAndStorageEntry: TableItemListNodeEntry { - case storageUsage(Int32, String) - case networkUsage(Int32, String) - case automaticPhotoDownloadHeader(Int32, String) - case automaticPhotoDownloadPrivateChats(Int32, String, Bool) - case automaticPhotoDownloadGroupsAndChannels(Int32, String, Bool) - case automaticVoiceDownloadHeader(Int32, String) - case automaticVoiceDownloadPrivateChats(Int32, String, Bool) - case automaticVoiceDownloadGroupsAndChannels(Int32, String, Bool) - case automaticInstantVideoDownloadHeader(Int32, String) - case automaticInstantVideoDownloadPrivateChats(Int32, String, Bool) - case automaticInstantVideoDownloadGroupsAndChannels(Int32, String, Bool) - case voiceCallsHeader(Int32, String) - case useLessVoiceData(Int32, String, String) - case otherHeader(Int32, String) - case saveIncomingPhotos(Int32, String, Bool) - case saveEditedPhotos(Int32, String, Bool) + case storageUsage(Int32, String, viewType: GeneralViewType) + case networkUsage(Int32, String, viewType: GeneralViewType) + case automaticMediaDownloadHeader(Int32, String, viewType: GeneralViewType) + case automaticDownloadMedia(Int32, Bool, viewType: GeneralViewType) + case photos(Int32, AutomaticMediaDownloadCategoryPeers, Bool, viewType: GeneralViewType) + case videos(Int32, AutomaticMediaDownloadCategoryPeers, Bool, Int32?, viewType: GeneralViewType) + case files(Int32, AutomaticMediaDownloadCategoryPeers, Bool, Int32?, viewType: GeneralViewType) + case voice(Int32, AutomaticMediaDownloadCategoryPeers, Bool, viewType: GeneralViewType) + case instantVideo(Int32, AutomaticMediaDownloadCategoryPeers, Bool, viewType: GeneralViewType) + case gifs(Int32, AutomaticMediaDownloadCategoryPeers, Bool, viewType: GeneralViewType) + + case autoplayHeader(Int32, viewType: GeneralViewType) + case autoplayGifs(Int32, Bool, viewType: GeneralViewType) + case autoplayVideos(Int32, Bool, viewType: GeneralViewType) + case soundOnHover(Int32, Bool, viewType: GeneralViewType) + case soundOnHoverDesc(Int32, viewType: GeneralViewType) + case resetDownloadSettings(Int32, Bool, viewType: GeneralViewType) + case downloadFolder(Int32, String, viewType: GeneralViewType) + case proxyHeader(Int32) + case proxySettings(Int32, String, viewType: GeneralViewType) case sectionId(Int32) var stableId: Int32 { @@ -78,34 +207,40 @@ private enum DataAndStorageEntry: TableItemListNodeEntry { return 0 case .networkUsage: return 1 - case .automaticPhotoDownloadHeader: + case .automaticMediaDownloadHeader: return 2 - case .automaticPhotoDownloadPrivateChats: + case .automaticDownloadMedia: return 3 - case .automaticPhotoDownloadGroupsAndChannels: + case .photos: return 4 - case .automaticVoiceDownloadHeader: + case .videos: return 5 - case .automaticVoiceDownloadPrivateChats: + case .files: return 6 - case .automaticVoiceDownloadGroupsAndChannels: + case .voice: return 7 - case .automaticInstantVideoDownloadHeader: + case .instantVideo: return 8 - case .automaticInstantVideoDownloadPrivateChats: + case .gifs: return 9 - case .automaticInstantVideoDownloadGroupsAndChannels: + case .resetDownloadSettings: return 10 - case .voiceCallsHeader: + case .downloadFolder: return 11 - case .useLessVoiceData: + case .autoplayHeader: return 12 - case .otherHeader: + case .autoplayGifs: return 13 - case .saveIncomingPhotos: + case .autoplayVideos: return 14 - case .saveEditedPhotos: + case .soundOnHover: return 15 + case .soundOnHoverDesc: + return 16 + case .proxyHeader: + return 17 + case .proxySettings: + return 18 case let .sectionId(sectionId): return (sectionId + 1) * 1000 - sectionId } @@ -113,147 +248,46 @@ private enum DataAndStorageEntry: TableItemListNodeEntry { var index:Int32 { switch self { - case .storageUsage(let sectionId, _): + case .storageUsage(let sectionId, _, _): return (sectionId * 1000) + stableId - case .networkUsage(let sectionId, _): + case .networkUsage(let sectionId, _, _): return (sectionId * 1000) + stableId - case .automaticPhotoDownloadHeader(let sectionId, _): + case .automaticMediaDownloadHeader(let sectionId, _, _): return (sectionId * 1000) + stableId - case .automaticPhotoDownloadPrivateChats(let sectionId, _, _): + case .automaticDownloadMedia(let sectionId, _, _): return (sectionId * 1000) + stableId - case .automaticPhotoDownloadGroupsAndChannels(let sectionId, _, _): + case let .photos(sectionId, _, _, _): return (sectionId * 1000) + stableId - case .automaticVoiceDownloadHeader(let sectionId, _): + case let .videos(sectionId, _, _, _, _): return (sectionId * 1000) + stableId - case .automaticVoiceDownloadPrivateChats(let sectionId, _, _): + case let .files(sectionId, _, _, _, _): return (sectionId * 1000) + stableId - case .automaticVoiceDownloadGroupsAndChannels(let sectionId, _, _): + case let .voice(sectionId, _, _, _): return (sectionId * 1000) + stableId - case .automaticInstantVideoDownloadHeader(let sectionId, _): + case let .instantVideo(sectionId, _, _, _): return (sectionId * 1000) + stableId - case .automaticInstantVideoDownloadPrivateChats(let sectionId, _, _): + case let .gifs(sectionId, _, _, _): return (sectionId * 1000) + stableId - case .automaticInstantVideoDownloadGroupsAndChannels(let sectionId, _, _): + case let .resetDownloadSettings(sectionId, _, _): return (sectionId * 1000) + stableId - case .voiceCallsHeader(let sectionId, _): + case let .autoplayHeader(sectionId, _): return (sectionId * 1000) + stableId - case .useLessVoiceData(let sectionId, _, _): + case let .autoplayGifs(sectionId, _, _): return (sectionId * 1000) + stableId - case .otherHeader(let sectionId, _): + case let .autoplayVideos(sectionId, _, _): return (sectionId * 1000) + stableId - case .saveIncomingPhotos(let sectionId, _, _): + case let .soundOnHover(sectionId, _, _): return (sectionId * 1000) + stableId - case .saveEditedPhotos(let sectionId, _, _): + case let .soundOnHoverDesc(sectionId, _): return (sectionId * 1000) + stableId - case .sectionId(let sectionId): - return (sectionId + 1) * 1000 - sectionId - } - } - - static func ==(lhs: DataAndStorageEntry, rhs: DataAndStorageEntry) -> Bool { - switch lhs { - case let .storageUsage(sectionId, text): - if case .storageUsage(sectionId, text) = rhs { - return true - } else { - return false - } - case let .networkUsage(sectionId, text): - if case .networkUsage(sectionId, text) = rhs { - return true - } else { - return false - } - case let .automaticPhotoDownloadHeader(sectionId, text): - if case .automaticPhotoDownloadHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .automaticPhotoDownloadPrivateChats(sectionId, text, value): - if case .automaticPhotoDownloadPrivateChats(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .automaticPhotoDownloadGroupsAndChannels(sectionId, text, value): - if case .automaticPhotoDownloadGroupsAndChannels(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .automaticVoiceDownloadHeader(sectionId, text): - if case .automaticVoiceDownloadHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .automaticVoiceDownloadPrivateChats(sectionId, text, value): - if case .automaticVoiceDownloadPrivateChats(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .automaticVoiceDownloadGroupsAndChannels(sectionId, text, value): - if case .automaticVoiceDownloadGroupsAndChannels(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .automaticInstantVideoDownloadHeader(sectionId, text): - if case .automaticInstantVideoDownloadHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .automaticInstantVideoDownloadPrivateChats(sectionId, text, value): - if case .automaticInstantVideoDownloadPrivateChats(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .automaticInstantVideoDownloadGroupsAndChannels(sectionId, text, value): - if case .automaticInstantVideoDownloadGroupsAndChannels(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .voiceCallsHeader(sectionId, text): - if case .voiceCallsHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .useLessVoiceData(sectionId, text, value): - if case .useLessVoiceData(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .otherHeader(sectionId, text): - if case .otherHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .saveIncomingPhotos(sectionId, text, value): - if case .saveIncomingPhotos(sectionId, text, value) = rhs { - return true - } else { - return false - } - case let .saveEditedPhotos(sectionId, text, value): - if case .saveEditedPhotos(sectionId, text, value) = rhs { - return true - } else { - return false - } + case let .downloadFolder(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .proxyHeader(sectionId): + return sectionId + case let .proxySettings(sectionId, _, _): + return sectionId case let .sectionId(sectionId): - if case .sectionId(sectionId) = rhs { - return true - } else { - return false - } + return (sectionId + 1) * 1000 - sectionId } } @@ -263,68 +297,77 @@ private enum DataAndStorageEntry: TableItemListNodeEntry { func item(_ arguments: DataAndStorageControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .storageUsage(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { + case let .storageUsage(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, viewType: viewType, action: { arguments.openStorageUsage() }) - case let .networkUsage(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { + case let .networkUsage(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, viewType: viewType, action: { arguments.openNetworkUsage() }) - case let .automaticPhotoDownloadHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .automaticPhotoDownloadPrivateChats(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.photo, .privateChats, value) - }) - case let .automaticPhotoDownloadGroupsAndChannels(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.photo, .groupsAndChannels, value) + case let .automaticMediaDownloadHeader(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .automaticDownloadMedia(_ , value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownload, type: .switchable(value), viewType: viewType, action: { + arguments.toggleAutomaticDownload(!value) }) - case let .automaticVoiceDownloadHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .automaticVoiceDownloadPrivateChats(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.voice, .privateChats, value) + case let .photos(_, category, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadPhoto, description: stringForAutomaticDownloadPeers(peers: AutomaticDownloadPeers(category: category), category: .photo), type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadPhoto) + }, enabled: enabled) + case let .videos(_, category, enabled, _, viewType): + + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadVideo, description: stringForAutomaticDownloadPeers(peers: AutomaticDownloadPeers(category: category), category: .video), type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadVideo) + }, enabled: enabled) + case let .files(_, category, enabled, _, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadFiles, description: stringForAutomaticDownloadPeers(peers: AutomaticDownloadPeers(category: category), category: .file), type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadFiles) + }, enabled: enabled) + case let .voice(_, category, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadVoice, type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadVoice) + }, enabled: enabled) + case let .instantVideo(_, category, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadInstantVideo, type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadInstantVideo) + }, enabled: enabled) + case let .gifs(_, category, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadGIFs, type: .next, viewType: viewType, action: { + arguments.openCategorySettings(category, L10n.dataAndStorageAutomaticDownloadGIFs) + }, enabled: enabled) + case let .resetDownloadSettings(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutomaticDownloadReset, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.accent), type: .none, viewType: viewType, action: { + arguments.resetDownloadSettings() + }, enabled: enabled) + case let .downloadFolder(_, path, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageDownloadFolder, type: .context(path), viewType: viewType, action: { + arguments.selectDownloadFolder() }) - case let .automaticVoiceDownloadGroupsAndChannels(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.voice, .groupsAndChannels, value) + case let .autoplayHeader(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.dataAndStorageAutoplayHeader, viewType: viewType) + case let .autoplayGifs(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutoplayGIFs, type: .switchable(value), viewType: viewType, action: { + arguments.toggleAutoplayGifs(!value) }) - case let .automaticInstantVideoDownloadHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .automaticInstantVideoDownloadPrivateChats(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.instantVideo, .privateChats, value) + case let .autoplayVideos(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutoplayVideos, type: .switchable(value), viewType: viewType, action: { + arguments.toggleAutoplayVideos(!value) }) - case let .automaticInstantVideoDownloadGroupsAndChannels(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .switchable(stateback: { - return value - }), action: { - arguments.toggleAutomaticDownload(.instantVideo, .groupsAndChannels, value) + case let .soundOnHover(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageAutoplaySoundOnHover, type: .switchable(value), viewType: viewType, action: { + arguments.toggleAutoplaySoundOnHover(!value) }) - case let .voiceCallsHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .useLessVoiceData(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .context(stateback: { - return value - }), action: { - arguments.openVoiceUseLessData() + case let .soundOnHoverDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.dataAndStorageAutoplaySoundOnHoverDesc, viewType: viewType) + case .proxyHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsProxyHeader, viewType: .textTopItem) + case let .proxySettings(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsUseProxy, type: .nextContext(text), viewType: viewType, action: { + arguments.openProxySettings() }) - case let .otherHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) default: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } @@ -352,59 +395,67 @@ private struct DataAndStorageData: Equatable { } -private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData) -> [DataAndStorageEntry] { +private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, proxy: ProxySettings, autoplayMedia: AutoplayMediaPreferences) -> [DataAndStorageEntry] { var entries: [DataAndStorageEntry] = [] var sectionId:Int32 = 1 entries.append(.sectionId(sectionId)) sectionId += 1 - entries.append(.storageUsage(sectionId, tr(.dataAndStorageStorageUsage))) - // entries.append(.networkUsage(sectionId, tr(.dataAndStorageNetworkUsage))) + entries.append(.storageUsage(sectionId, L10n.dataAndStorageStorageUsage, viewType: .firstItem)) + entries.append(.networkUsage(sectionId, L10n.dataAndStorageNetworkUsage, viewType: .lastItem)) entries.append(.sectionId(sectionId)) sectionId += 1 - entries.append(.automaticPhotoDownloadHeader(sectionId, tr(.dataAndStorageAutomaticPhotoDownloadHeader))) - // entries.append(.automaticPhotoDownloadPrivateChats(sectionId, tr(.dataAndStorageAutomaticDownloadPrivateChats), data.automaticMediaDownloadSettings.categories.photo.privateChats)) - entries.append(.automaticPhotoDownloadGroupsAndChannels(sectionId, tr(.dataAndStorageAutomaticDownloadGroupsChannels), data.automaticMediaDownloadSettings.categories.photo.groupsAndChannels)) + entries.append(.automaticMediaDownloadHeader(sectionId, L10n.dataAndStorageAutomaticDownloadHeader, viewType: .textTopItem)) + entries.append(.automaticDownloadMedia(sectionId, data.automaticMediaDownloadSettings.automaticDownload, viewType: .firstItem)) + entries.append(.photos(sectionId, data.automaticMediaDownloadSettings.categories.photo, data.automaticMediaDownloadSettings.automaticDownload, viewType: .innerItem)) + entries.append(.videos(sectionId, data.automaticMediaDownloadSettings.categories.video, data.automaticMediaDownloadSettings.automaticDownload, data.automaticMediaDownloadSettings.categories.video.fileSize, viewType: .innerItem)) + entries.append(.files(sectionId, data.automaticMediaDownloadSettings.categories.files, data.automaticMediaDownloadSettings.automaticDownload, data.automaticMediaDownloadSettings.categories.files.fileSize, viewType: .innerItem)) + entries.append(.resetDownloadSettings(sectionId, data.automaticMediaDownloadSettings != AutomaticMediaDownloadSettings.defaultSettings, viewType: .lastItem)) entries.append(.sectionId(sectionId)) sectionId += 1 - entries.append(.automaticVoiceDownloadHeader(sectionId, tr(.dataAndStorageAutomaticAudioDownloadHeader))) - // entries.append(.automaticVoiceDownloadPrivateChats(sectionId, tr(.dataAndStorageAutomaticDownloadPrivateChats), data.automaticMediaDownloadSettings.categories.voice.privateChats)) - entries.append(.automaticVoiceDownloadGroupsAndChannels(sectionId, tr(.dataAndStorageAutomaticDownloadGroupsChannels), data.automaticMediaDownloadSettings.categories.voice.groupsAndChannels)) + entries.append(.downloadFolder(sectionId, data.automaticMediaDownloadSettings.downloadFolder, viewType: .singleItem)) + entries.append(.sectionId(sectionId)) sectionId += 1 - entries.append(.automaticInstantVideoDownloadHeader(sectionId, tr(.dataAndStorageAutomaticVideoDownloadHeader))) - // entries.append(.automaticInstantVideoDownloadPrivateChats(sectionId, tr(.dataAndStorageAutomaticDownloadPrivateChats), data.automaticMediaDownloadSettings.categories.instantVideo.privateChats)) - entries.append(.automaticInstantVideoDownloadGroupsAndChannels(sectionId, tr(.dataAndStorageAutomaticDownloadGroupsChannels), data.automaticMediaDownloadSettings.categories.instantVideo.groupsAndChannels)) - // entries.append(.sectionId(sectionId)) - // sectionId += 1 + entries.append(.autoplayHeader(sectionId, viewType: .textTopItem)) + entries.append(.autoplayGifs(sectionId, autoplayMedia.gifs, viewType: .firstItem)) + entries.append(.autoplayVideos(sectionId, autoplayMedia.videos, viewType: .lastItem)) + + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + + entries.append(.proxyHeader(sectionId)) + let text: String + if let active = proxy.activeServer, proxy.enabled { + switch active.connection { + case .socks5: + text = L10n.proxySettingsSocks5 + case .mtp: + text = L10n.proxySettingsMTP + } + } else { + text = L10n.proxySettingsDisabled + } + entries.append(.proxySettings(sectionId, text, viewType: .singleItem)) - // entries.append(.voiceCallsHeader(sectionId, tr(.dataAndStorageVoiceCallsHeader))) - // entries.append(.useLessVoiceData(sectionId, tr(.dataAndStorageVoiceCallsLessData), stringForUseLessDataSetting(data.voiceCallSettings))) - + entries.append(.sectionId(sectionId)) + sectionId += 1 return entries } -private func stringForUseLessDataSetting(_ settings: VoiceCallSettings) -> String { - switch settings.dataSaving { - case .never: - return "Never" - case .cellular: - return "On Mobile Network" - case .always: - return "Always" - } -} private func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize: NSSize, arguments: DataAndStorageControllerArguments) -> TableUpdateTransition { let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in @@ -414,13 +465,18 @@ private func prepareTransition(left:[AppearanceWrapperEntry } class DataAndStorageViewController: TableViewController { - + private let disposable = MetaDisposable() + private var focusOnItemTag: DataAndStorageEntryTag? + init(_ context: AccountContext, focusOnItemTag: DataAndStorageEntryTag? = nil) { + self.focusOnItemTag = focusOnItemTag + super.init(context) + } + override func viewDidLoad() { super.viewDidLoad() - readyOnce() - let account = self.account + let context = self.context let initialState = DataAndStorageControllerState() let initialSize = self.atomicSize let statePromise = ValuePromise(initialState, ignoreRepeated: true) @@ -429,7 +485,7 @@ class DataAndStorageViewController: TableViewController { statePromise.set(stateValue.modify { f($0) }) } - let pushControllerImpl = { [weak self] controller in + let pushControllerImpl:(ViewController)->Void = { [weak self] controller in self?.navigationController?.push(controller) } @@ -437,91 +493,115 @@ class DataAndStorageViewController: TableViewController { let actionsDisposable = DisposableSet() let dataAndStorageDataPromise = Promise() - dataAndStorageDataPromise.set(account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings, ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings, ApplicationSpecificPreferencesKeys.voiceCallSettings]) - |> map { view -> DataAndStorageData in - let automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - if let value = view.values[ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings { - automaticMediaDownloadSettings = value - } else { - automaticMediaDownloadSettings = AutomaticMediaDownloadSettings.defaultSettings - } + dataAndStorageDataPromise.set(combineLatest(context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings, ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings]), voiceCallSettings(context.sharedContext.accountManager)) + |> map { view, voiceCallSettings -> DataAndStorageData in + let automaticMediaDownloadSettings: AutomaticMediaDownloadSettings = view.values[ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings ?? AutomaticMediaDownloadSettings.defaultSettings + - let generatedMediaStoreSettings: GeneratedMediaStoreSettings - if let value = view.values[ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings] as? GeneratedMediaStoreSettings { - generatedMediaStoreSettings = value - } else { - generatedMediaStoreSettings = GeneratedMediaStoreSettings.defaultSettings - } + let generatedMediaStoreSettings: GeneratedMediaStoreSettings = view.values[ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings] as? GeneratedMediaStoreSettings ?? GeneratedMediaStoreSettings.defaultSettings - let voiceCallSettings: VoiceCallSettings - if let value = view.values[ApplicationSpecificPreferencesKeys.voiceCallSettings] as? VoiceCallSettings { - voiceCallSettings = value - } else { - voiceCallSettings = VoiceCallSettings.defaultSettings - } return DataAndStorageData(automaticMediaDownloadSettings: automaticMediaDownloadSettings, generatedMediaStoreSettings: generatedMediaStoreSettings, voiceCallSettings: voiceCallSettings) }) - let arguments = DataAndStorageControllerArguments(openStorageUsage: { [weak self] in - pushControllerImpl(StorageUsageController(account)) + let arguments = DataAndStorageControllerArguments(openStorageUsage: { + pushControllerImpl(StorageUsageController(context)) }, openNetworkUsage: { - // pushControllerImpl?(networkUsageStatsController(account: account)) - }, toggleAutomaticDownload: { category, peers, value in - let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { current in - switch category { - case .photo: - switch peers { - case .privateChats: - return current.withUpdatedCategories(current.categories.withUpdatedPhoto(current.categories.photo.withUpdatedPrivateChats(value))) - case .groupsAndChannels: - return current.withUpdatedCategories(current.categories.withUpdatedPhoto(current.categories.photo.withUpdatedGroupsAndChannels(value))) - } - case .voice: - switch peers { - case .privateChats: - return current.withUpdatedCategories(current.categories.withUpdatedVoice(current.categories.voice.withUpdatedPrivateChats(value))) - case .groupsAndChannels: - return current.withUpdatedCategories(current.categories.withUpdatedVoice(current.categories.voice.withUpdatedGroupsAndChannels(value))) - } - case .instantVideo: - switch peers { - case .privateChats: - return current.withUpdatedCategories(current.categories.withUpdatedInstantVideo(current.categories.instantVideo.withUpdatedPrivateChats(value))) - case .groupsAndChannels: - return current.withUpdatedCategories(current.categories.withUpdatedInstantVideo(current.categories.instantVideo.withUpdatedGroupsAndChannels(value))) - } - case .gif: - switch peers { - case .privateChats: - return current.withUpdatedCategories(current.categories.withUpdatedGif(current.categories.gif.withUpdatedPrivateChats(value))) - case .groupsAndChannels: - return current.withUpdatedCategories(current.categories.withUpdatedGif(current.categories.gif.withUpdatedGroupsAndChannels(value))) + pushControllerImpl(networkUsageStatsController(context: context)) + }, openCategorySettings: { category, title in + pushControllerImpl(DownloadSettingsViewController(context, category, title, updateCategory: { category in + _ = updateMediaDownloadSettingsInteractively(postbox: context.account.postbox, { current -> AutomaticMediaDownloadSettings in + switch title { + case L10n.dataAndStorageAutomaticDownloadPhoto: + return current.withUpdatedCategories(current.categories.withUpdatedPhoto(category)) + case L10n.dataAndStorageAutomaticDownloadVideo: + return current.withUpdatedCategories(current.categories.withUpdatedVideo(category)) + case L10n.dataAndStorageAutomaticDownloadFiles: + return current.withUpdatedCategories(current.categories.withUpdatedFiles(category)) + case L10n.dataAndStorageAutomaticDownloadVoice: + return current.withUpdatedCategories(current.categories.withUpdatedVoice(category)) + case L10n.dataAndStorageAutomaticDownloadInstantVideo: + return current.withUpdatedCategories(current.categories.withUpdatedInstantVideo(category)) + case L10n.dataAndStorageAutomaticDownloadGIFs: + return current.withUpdatedCategories(current.categories.withUpdatedGif(category)) + default: + return current } - } + }).start() + })) + }, toggleAutomaticDownload: { enabled in + _ = updateMediaDownloadSettingsInteractively(postbox: context.account.postbox, { current -> AutomaticMediaDownloadSettings in + return current.withUpdatedAutomaticDownload(enabled) + }).start() + }, resetDownloadSettings: { + _ = (confirmSignal(for: mainWindow, header: appName, information: L10n.dataAndStorageConfirmResetSettings, okTitle: L10n.modalOK, cancelTitle: L10n.modalCancel) |> filter {$0} |> mapToSignal { _ -> Signal in + return updateMediaDownloadSettingsInteractively(postbox: context.account.postbox, { _ -> AutomaticMediaDownloadSettings in + return AutomaticMediaDownloadSettings.defaultSettings + }) + }).start() + }, selectDownloadFolder: { + selectFolder(for: mainWindow, completion: { newPath in + _ = updateMediaDownloadSettingsInteractively(postbox: context.account.postbox, { current -> AutomaticMediaDownloadSettings in + return current.withUpdatedDownloadFolder(newPath) + }).start() + }) + + }, toggleAutoplayGifs: { enable in + _ = updateAutoplayMediaSettingsInteractively(postbox: context.account.postbox, { + return $0.withUpdatedAutoplayGifs(enable) }).start() - }, openVoiceUseLessData: { - // pushControllerImpl?(voiceCallDataSavingController(account: account)) - }, toggleSaveIncomingPhotos: { value in - let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { current in - return current.withUpdatedSaveIncomingPhotos(value) + }, toggleAutoplayVideos: { enable in + _ = updateAutoplayMediaSettingsInteractively(postbox: context.account.postbox, { + return $0.withUpdatedAutoplayVideos(enable) }).start() - }, toggleSaveEditedPhotos: { value in - let _ = updateGeneratedMediaStoreSettingsInteractively(postbox: account.postbox, { current in - return current.withUpdatedStoreEditedPhotos(value) + }, toggleAutoplaySoundOnHover: { enable in + _ = updateAutoplayMediaSettingsInteractively(postbox: context.account.postbox, { + return $0.withUpdatedAutoplaySoundOnHover(enable) }).start() + }, openProxySettings: { + let controller = proxyListController(accountManager: context.sharedContext.accountManager, network: context.account.network, share: { servers in + var message: String = "" + for server in servers { + message += server.link + "\n\n" + } + message = message.trimmed + + showModal(with: ShareModalController(ShareLinkObject(context, link: message)), for: mainWindow) + }, pushController: { controller in + pushControllerImpl(controller) + }) + pushControllerImpl(controller) }) - self.genericView.merge(with: combineLatest(statePromise.get(), dataAndStorageDataPromise.get(), appearanceSignal) |> deliverOnMainQueue - |> map { state, dataAndStorageData, appearance -> TableUpdateTransition in - - let entries = dataAndStorageControllerEntries(state: state, data: dataAndStorageData).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) + let proxy:Signal = proxySettings(accountManager: context.sharedContext.accountManager) - } |> afterDisposed { - actionsDisposable.dispose() - }) + + let signal = combineLatest(queue: .mainQueue(), statePromise.get(), dataAndStorageDataPromise.get(), appearanceSignal, proxy, autoplayMediaSettings(postbox: context.account.postbox)) + |> map { state, dataAndStorageData, appearance, proxy, autoplayMediaSettings -> TableUpdateTransition in + let entries = dataAndStorageControllerEntries(state: state, data: dataAndStorageData, proxy: proxy, autoplayMedia: autoplayMediaSettings).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) + } |> beforeNext { [weak self] _ in + self?.readyOnce() + } |> afterDisposed { + actionsDisposable.dispose() + } |> deliverOnMainQueue + + + + self.disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + if let focusOnItemTag = self?.focusOnItemTag { + self?.genericView.scroll(to: .center(id: focusOnItemTag.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets()) + self?.focusOnItemTag = nil + } + })) + + } + + deinit { + disposable.dispose() } override func getRightBarViewOnce() -> BarView { diff --git a/Telegram-Mac/DateUtils.h b/Telegram-Mac/DateUtils.h index a8c1af32f9..0f7750f881 100644 --- a/Telegram-Mac/DateUtils.h +++ b/Telegram-Mac/DateUtils.h @@ -24,6 +24,7 @@ + (void)setDateLocalizationFunc:(NSString* (^)(NSString *key))localizationF; @end +NSString * NSLocalized(NSString * key, NSString *comment); #ifdef __cplusplus diff --git a/Telegram-Mac/DateUtils.mm b/Telegram-Mac/DateUtils.mm index f581db227d..642d26dd7d 100644 --- a/Telegram-Mac/DateUtils.mm +++ b/Telegram-Mac/DateUtils.mm @@ -1,5 +1,5 @@ #include "DateUtils.h" -#include +//#include static time_t midnightOnDay(time_t t) { diff --git a/Telegram-Mac/DeclareEncodables.swift b/Telegram-Mac/DeclareEncodables.swift index b2ff670f45..5f171efc8c 100644 --- a/Telegram-Mac/DeclareEncodables.swift +++ b/Telegram-Mac/DeclareEncodables.swift @@ -7,17 +7,50 @@ // import Cocoa -import PostboxMac +import Postbox private var telegramUIDeclaredEncodables: Void = { declareEncodable(ChatInterfaceState.self, f: { ChatInterfaceState(decoder: $0) }) declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) declareEncodable(BaseApplicationSettings.self, f: { BaseApplicationSettings(decoder: $0) }) - declareEncodable(ThemePalleteSettings.self, f: { ThemePalleteSettings(decoder: $0) }) + declareEncodable(ThemePaletteSettings.self, f: { ThemePaletteSettings(decoder: $0) }) declareEncodable(LocalFileGifMediaResource.self, f: { LocalFileGifMediaResource(decoder: $0) }) + declareEncodable(LottieSoundMediaResource.self, f: { LottieSoundMediaResource(decoder: $0) }) + declareEncodable(LocalFileVideoMediaResource.self, f: { LocalFileVideoMediaResource(decoder: $0) }) + declareEncodable(LocalFileArchiveMediaResource.self, f: { LocalFileArchiveMediaResource(decoder: $0) }) declareEncodable(RecentUsedEmoji.self, f: { RecentUsedEmoji(decoder: $0) }) declareEncodable(InstantViewAppearance.self, f: { InstantViewAppearance(decoder: $0) }) declareEncodable(IVReadState.self, f: { IVReadState(decoder: $0) }) + declareEncodable(AdditionalSettings.self, f: { AdditionalSettings(decoder: $0) }) + declareEncodable(AutomaticMediaDownloadCategoryPeers.self, f: { AutomaticMediaDownloadCategoryPeers(decoder: $0) }) + declareEncodable(AutomaticMediaDownloadCategories.self, f: { AutomaticMediaDownloadCategories(decoder: $0) }) + declareEncodable(AutomaticMediaDownloadSettings.self, f: { AutomaticMediaDownloadSettings(decoder: $0) }) + declareEncodable(ReadArticle.self, f: { ReadArticle(decoder: $0) }) + declareEncodable(ReadArticlesListPreferences.self, f: { ReadArticlesListPreferences(decoder: $0) }) + declareEncodable(AutoNightThemePreferences.self, f: { AutoNightThemePreferences(decoder: $0) }) + declareEncodable(StickerSettings.self, f: { StickerSettings(decoder: $0) }) + declareEncodable(EmojiSkinModifier.self, f: { AutoNightThemePreferences(decoder: $0) }) + declareEncodable(InstantPageStoredDetailsState.self, f: { InstantPageStoredDetailsState(decoder: $0) }) + declareEncodable(CachedChannelAdminRanks.self, f: { CachedChannelAdminRanks(decoder: $0) }) + declareEncodable(LaunchSettings.self, f: { LaunchSettings(decoder: $0)}) + declareEncodable(AutoplayMediaPreferences.self, f: { AutoplayMediaPreferences(decoder: $0)}) + declareEncodable(VoiceCallSettings.self, f: { VoiceCallSettings(decoder: $0)}) + declareEncodable(LaunchNavigation.self, f: { LaunchNavigation(decoder: $0)}) + declareEncodable(DownloadedFilesPaths.self, f: { DownloadedFilesPaths(decoder: $0)}) + declareEncodable(DownloadedPath.self, f: { DownloadedPath(decoder: $0)}) + declareEncodable(LocalBundleResource.self, f: { LocalBundleResource(decoder: $0)}) + declareEncodable(AssociatedWallpaper.self, f: { AssociatedWallpaper(decoder: $0) }) + declareEncodable(ThemeWallpaper.self, f: { ThemeWallpaper(decoder: $0) }) + declareEncodable(DefaultTheme.self, f: { DefaultTheme(decoder: $0) }) + declareEncodable(DefaultCloudTheme.self, f: { DefaultCloudTheme(decoder: $0) }) + declareEncodable(LocalWallapper.self, f: { LocalWallapper(decoder: $0) }) + declareEncodable(LocalAccentColor.self, f: { LocalAccentColor(decoder: $0) }) + // declareEncodable(WalletPasscodeTimeout.self, f: { WalletPasscodeTimeout(decoder: $0) }) + declareEncodable(PasscodeSettings.self, f: { PasscodeSettings(decoder: $0) }) + declareEncodable(CachedInstantPage.self, f: { CachedInstantPage(decoder: $0) }) + declareEncodable(RecentSettingsSearchQueryItem.self, f: { RecentSettingsSearchQueryItem(decoder: $0) }) + declareEncodable(ChatListFoldersSettings.self, f: { ChatListFoldersSettings(decoder: $0) }) + return }() diff --git a/Telegram-Mac/DeleteSupergroupMessagesModalController.swift b/Telegram-Mac/DeleteSupergroupMessagesModalController.swift index 97bea9716d..d7677a7359 100644 --- a/Telegram-Mac/DeleteSupergroupMessagesModalController.swift +++ b/Telegram-Mac/DeleteSupergroupMessagesModalController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit struct DeleteSupergroupMessagesSet : OptionSet { @@ -57,18 +58,18 @@ struct DeleteSupergroupMessagesSet : OptionSet { class DeleteSupergroupMessagesModalController: TableModalViewController { private let peerId:PeerId private let messageIds:[MessageId] - private let account:Account + private let context:AccountContext private let memberId:PeerId private var options:DeleteSupergroupMessagesSet = DeleteSupergroupMessagesSet(.deleteMessages) private let onComplete:()->Void private let peerViewDisposable = MetaDisposable() - init(account:Account, messageIds:[MessageId], peerId:PeerId, memberId: PeerId, onComplete: @escaping() -> Void) { - self.account = account + init(context: AccountContext, messageIds:[MessageId], peerId:PeerId, memberId: PeerId, onComplete: @escaping() -> Void) { + self.context = context self.messageIds = messageIds self.peerId = peerId self.memberId = memberId self.onComplete = onComplete - super.init(frame: NSMakeRect(0, 0, 280, 260)) + super.init(frame: NSMakeRect(0, 0, 350, 260)) bar = .init(height: 0) } @@ -81,66 +82,67 @@ class DeleteSupergroupMessagesModalController: TableModalViewController { super.viewDidLoad() let initialSize = atomicSize.modify({$0}) - peerViewDisposable.set((account.viewTracker.peerView( peerId) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerView in + let update: Promise = Promise(Void()) + + peerViewDisposable.set(combineLatest(context.account.viewTracker.peerView( peerId) |> take(1) |> deliverOnMainQueue, update.get()).start(next: { [weak self] peerView, _ in if let strongSelf = self, let peer = peerViewMainPeer(peerView) as? TelegramChannel { + + _ = strongSelf.genericView.removeAll() + _ = strongSelf.genericView.addItem(item: GeneralRowItem(initialSize, height: 20, stableId: 0)) - _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 1, name: tr(.supergroupDeleteRestrictionDeleteMessage), type: .selectable(stateback: { [weak strongSelf] () -> Bool in + _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 1, name: tr(L10n.supergroupDeleteRestrictionDeleteMessage), type: .selectable(strongSelf.options.contains(.deleteMessages)), action: { [weak strongSelf] in if let strongSelf = strongSelf { - return strongSelf.options.contains(.deleteMessages) + if !strongSelf.options.isEmpty { + strongSelf.options.remove(.deleteMessages) + } + update.set(.single(Void())) } - return false - }), action: { - })) - if peer.hasAdminRights(.canBanUsers) { - _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 2, name: tr(.supergroupDeleteRestrictionBanUser), type: .selectable(stateback: { [weak strongSelf] () -> Bool in - if let strongSelf = strongSelf { - return strongSelf.options.contains(.banUser) - } - return false - }), action: { [weak strongSelf] in + if peer.hasPermission(.banMembers) { + _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 2, name: tr(L10n.supergroupDeleteRestrictionBanUser), type: .selectable(strongSelf.options.contains(.banUser)), action: { [weak strongSelf] in if let strongSelf = strongSelf { if strongSelf.options.contains(.banUser) { strongSelf.options.remove(.banUser) + if strongSelf.options.isEmpty { + strongSelf.options.insert(.deleteMessages) + } } else { strongSelf.options.insert(.banUser) } - strongSelf.genericView.reloadData() + update.set(.single(Void())) } })) } - _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 3, name: tr(.supergroupDeleteRestrictionReportSpam), type: .selectable(stateback: { [weak strongSelf] () -> Bool in - if let strongSelf = strongSelf { - return strongSelf.options.contains(.reportSpam) - } - return false - }), action: { [weak strongSelf] in + _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 3, name: tr(L10n.supergroupDeleteRestrictionReportSpam), type: .selectable(strongSelf.options.contains(.reportSpam)), action: { [weak strongSelf] in if let strongSelf = strongSelf { if strongSelf.options.contains(.reportSpam) { strongSelf.options.remove(.reportSpam) + if strongSelf.options.isEmpty { + strongSelf.options.insert(.deleteMessages) + } } else { strongSelf.options.insert(.reportSpam) } strongSelf.genericView.reloadData() + update.set(.single(Void())) } })) - _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 4, name: tr(.supergroupDeleteRestrictionDeleteAllMessages), type: .selectable(stateback: { [weak strongSelf] () -> Bool in - if let strongSelf = strongSelf { - return strongSelf.options.contains(.deleteAllMessages) - } - return false - }), action: { [weak strongSelf] in + _ = strongSelf.genericView.addItem(item: GeneralInteractedRowItem(initialSize, stableId: 4, name: tr(L10n.supergroupDeleteRestrictionDeleteAllMessages), type: .selectable(strongSelf.options.contains(.deleteAllMessages)), action: { [weak strongSelf] in if let strongSelf = strongSelf { if strongSelf.options.contains(.deleteAllMessages) { strongSelf.options.remove(.deleteAllMessages) + if strongSelf.options.isEmpty { + strongSelf.options.insert(.deleteMessages) + } } else { strongSelf.options.insert(.deleteAllMessages) } strongSelf.genericView.reloadData() + update.set(.single(Void())) } })) @@ -152,25 +154,32 @@ class DeleteSupergroupMessagesModalController: TableModalViewController { } private func perform() { - var signals:[Signal] = [deleteMessagesInteractively(postbox: account.postbox, messageIds: messageIds, type: .forEveryone)] + var signals:[Signal] = [deleteMessagesInteractively(account: context.account, messageIds: messageIds, type: .forEveryone)] if options.contains(.banUser) { - signals.append(removePeerMember(account: account, peerId: peerId, memberId: memberId)) + + signals.append(context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))) } if options.contains(.reportSpam) { - signals.append(reportSupergroupPeer(account: account, peerId: memberId, memberId: memberId, messageIds: messageIds)) + signals.append(reportSupergroupPeer(account: context.account, peerId: memberId, memberId: memberId, messageIds: messageIds)) } if options.contains(.deleteAllMessages) { - signals.append(clearAuthorHistory(account: account, peerId: peerId, memberId: memberId)) + signals.append(clearAuthorHistory(account: context.account, peerId: peerId, memberId: memberId)) } _ = combineLatest(signals).start() onComplete() close() } + override func returnKeyAction() -> KeyHandlerResult { + perform() + close() + return .invoked + } + override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in + return ModalInteractions(acceptTitle: tr(L10n.modalOK), accept: { [weak self] in self?.perform() - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + }, cancelTitle: tr(L10n.modalCancel), drawBorder: true, height: 40) } diff --git a/Telegram-Mac/DeveloperViewController.swift b/Telegram-Mac/DeveloperViewController.swift new file mode 100644 index 0000000000..ed0f542fec --- /dev/null +++ b/Telegram-Mac/DeveloperViewController.swift @@ -0,0 +1,242 @@ +// +// DeveloperViewController.swift +// Telegram +// +// Created by keepcoder on 30/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import MtProtoKit +import Postbox + +private final class DeveloperArguments { + let importColors:()->Void + let exportColors:()->Void + let toggleLogs:(Bool)->Void + let navigateToLogs:()->Void + let addAccount:()->Void + init(importColors:@escaping()->Void, exportColors:@escaping()->Void, toggleLogs:@escaping(Bool)->Void, navigateToLogs:@escaping()->Void, addAccount: @escaping() -> Void) { + self.importColors = importColors + self.exportColors = exportColors + self.toggleLogs = toggleLogs + self.navigateToLogs = navigateToLogs + self.addAccount = addAccount + } +} + +private enum DeveloperEntryId : Hashable { + case importColors + case exportColors + case toggleLogs + case openLogs + case accounts + case enableFilters + case section(Int32) + var hashValue: Int { + switch self { + case .importColors: + return 0 + case .exportColors: + return 1 + case .toggleLogs: + return 2 + case .openLogs: + return 3 + case .accounts: + return 4 + case .enableFilters: + return 5 + case .section(let section): + return 6 + Int(section) + } + } +} + +private enum DeveloperEntry : TableItemListNodeEntry { + + case importColors(sectionId: Int32) + case exportColors(sectionId: Int32) + case toggleLogs(sectionId: Int32, enabled: Bool) + case openLogs(sectionId: Int32) + case accounts(sectionId: Int32) + case enableFilters(sectionId: Int32, enabled: Bool) + case section(Int32) + + var stableId:DeveloperEntryId { + switch self { + case .importColors: + return .importColors + case .exportColors: + return .exportColors + case .toggleLogs: + return .toggleLogs + case .openLogs: + return .openLogs + case .accounts: + return .accounts + case .enableFilters: + return .enableFilters + case .section(let section): + return .section(section) + } + } + + var index:Int32 { + switch self { + case .importColors(let sectionId): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .exportColors(let sectionId): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .toggleLogs(let sectionId, _): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .openLogs(let sectionId): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .accounts(let sectionId): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .enableFilters(let sectionId, _): + return (sectionId * 1000) + Int32(stableId.hashValue) + case .section(let sectionId): + return (sectionId + 1) * 1000 - sectionId + } + } + + static func <(lhs: DeveloperEntry, rhs: DeveloperEntry) -> Bool { + return lhs.index < rhs.index + } + + + func item(_ arguments: DeveloperArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case .importColors: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Import Palette", type: .next, action: { + arguments.importColors() + }) + case .exportColors: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Export Palette", type: .next, action: { + arguments.exportColors() + }) + case .openLogs: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Open Logs", type: .next, action: { + arguments.navigateToLogs() + }) + case .accounts: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Add Account", type: .next, action: { + arguments.addAccount() + }) + case let .enableFilters(_, enabled): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Enable Filters", type: .switchable(enabled), action: { + }) + case let .toggleLogs(_, enabled): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "Enable Logs", type: .switchable(enabled), action: { + arguments.toggleLogs(!enabled) + }) + case .section: + return GeneralRowItem(initialSize, height: 20, stableId: stableId) + } + } + +} + +private func developerEntries() -> [DeveloperEntry] { + var entries:[DeveloperEntry] = [] + + var sectionId:Int32 = 1 + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.accounts(sectionId: sectionId)) + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.toggleLogs(sectionId: sectionId, enabled: UserDefaults.standard.bool(forKey: "enablelogs"))) + + entries.append(.openLogs(sectionId: sectionId)) + + entries.append(.section(sectionId)) + sectionId += 1 + + + entries.append(.section(sectionId)) + sectionId += 1 + return entries +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:DeveloperArguments) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + +class DeveloperViewController: TableViewController { + + init(context: AccountContext) { + super.init(context) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.getBackgroundColor = { + theme.colors.background + } + + let context = self.context + let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + let initialSize = self.atomicSize + let arguments = DeveloperArguments(importColors: { + filePanel(with: ["palette"], allowMultiple: false, for: mainWindow, completion: { list in + if let path = list?.first { + if let theme = importPalette(path) { + let palettesDir = "~/Library/Group Containers/\(ApiEnvironment.group)/Palettes/".nsstring.expandingTildeInPath + try? FileManager.default.createDirectory(atPath: palettesDir, withIntermediateDirectories: true, attributes: nil) + try? FileManager.default.removeItem(atPath: palettesDir + "/" + path.nsstring.lastPathComponent) + try? FileManager.default.copyItem(atPath: path, toPath: palettesDir + "/" + path.nsstring.lastPathComponent) + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.withUpdatedPalette(theme).withUpdatedCloudTheme(nil) + }).start() + } else { + alert(for: mainWindow, info: "Parsing Error") + } + } + }) + }, exportColors: { + exportPalette(palette: theme.colors) + }, toggleLogs: { _ in + let enabled = !UserDefaults.standard.bool(forKey: "enablelogs") + MTLogSetEnabled(enabled) + UserDefaults.standard.set(enabled, forKey: "enablelogs") + Logger.shared.logToConsole = false + Logger.shared.logToFile = enabled + }, navigateToLogs: { + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: "~/Library/Group Containers/\(ApiEnvironment.group)/logs".nsstring.expandingTildeInPath)]) + }, addAccount: { + let testingEnvironment = NSApp.currentEvent?.modifierFlags.contains(.command) == true + context.sharedContext.beginNewAuth(testingEnvironment: testingEnvironment) + }) + + let signal = combineLatest(queue: prepareQueue, context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListSettings]), appearanceSignal) + + genericView.merge(with: signal |> map { preferences, appearance in + + let entries = developerEntries().map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) + } |> deliverOnMainQueue) + + readyOnce() + } + + override var defaultBarTitle: String { + return "Developer" + } + +} diff --git a/Telegram-Mac/DiceCache.swift b/Telegram-Mac/DiceCache.swift new file mode 100644 index 0000000000..e96e054f3e --- /dev/null +++ b/Telegram-Mac/DiceCache.swift @@ -0,0 +1,251 @@ +// +// DiceCache.swift +// Telegram +// +// Created by Mikhail Filimonov on 28.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +struct InteractiveEmojiConfetti : Equatable { + let playAt: Int32 + let value:Int32 +} + +struct InteractiveEmojiConfiguration : Equatable { + static var defaultValue: InteractiveEmojiConfiguration { + return InteractiveEmojiConfiguration(emojis: [], confettiCompitable: [:]) + } + + let emojis: [String] + private let confettiCompitable: [String: InteractiveEmojiConfetti] + + fileprivate init(emojis: [String], confettiCompitable: [String: InteractiveEmojiConfetti]) { + self.emojis = emojis.map { $0.fixed } + self.confettiCompitable = confettiCompitable + } + + static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration { + if let data = appConfiguration.data, let value = data["emojies_send_dice"] as? [String] { + let dict:[String : Any]? = data["emojies_send_dice_success"] as? [String:Any] + + var confetti:[String: InteractiveEmojiConfetti] = [:] + if let dict = dict { + for (key, value) in dict { + if let data = value as? [String: Any], let frameStart = data["frame_start"] as? Double, let value = data["value"] as? Double { + confetti[key] = InteractiveEmojiConfetti(playAt: Int32(frameStart), value: Int32(value)) + } + } + } + return InteractiveEmojiConfiguration(emojis: value, confettiCompitable: confetti) + } else { + return .defaultValue + } + } + + func playConfetti(_ emoji: String) -> InteractiveEmojiConfetti? { + return confettiCompitable[emoji] + } +} + +private final class DiceSideDataContext { + var data: (Data?, TelegramMediaFile)? + let subscribers = Bag<((Data?, TelegramMediaFile)) -> Void>() +} + +class DiceCache { + private let postbox: Postbox + private let network: Network + + private var dataContexts: [String : [String: DiceSideDataContext]] = [:] + + + private let fetchDisposable = MetaDisposable() + private let loadDataDisposable = MetaDisposable() + + init(postbox: Postbox, network: Network) { + self.postbox = postbox + self.network = network + + + + let availablePacks = postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> map { view in + return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue + } |> map { + return InteractiveEmojiConfiguration.with(appConfiguration: $0) + } |> distinctUntilChanged + + + let packs = availablePacks |> mapToSignal { config -> Signal<[(String, [String: StickerPackItem])], NoError> in + var signals: [Signal<(String, [String: StickerPackItem]), NoError>] = [] + for emoji in config.emojis { + signals.append(loadedStickerPack(postbox: postbox, network: network, reference: .dice(emoji), forceActualized: true) + |> map { result -> (String, [String: StickerPackItem]) in + switch result { + case let .result(_, items, _): + var dices: [String: StickerPackItem] = [:] + for case let item as StickerPackItem in items { + if let side = item.getStringRepresentationsOfIndexKeys().first { + dices[side.fixed] = item + } + } + return (emoji, dices) + default: + return (emoji, [:]) + } + }) + } + return combineLatest(signals) + } + + + let fetchDices = packs |> map { value in + return value.reduce([], { current, value in + return current + value.1 + }) + } |> mapToSignal { dices -> Signal in + let signals = dices.map { _, value -> Signal in + let reference: MediaResourceReference + if let stickerReference = value.file.stickerReference { + reference = FileMediaReference.stickerPack(stickerPack: stickerReference, media: value.file).resourceReference(value.file.resource) + } else { + reference = FileMediaReference.standalone(media: value.file).resourceReference(value.file.resource) + } + return fetchedMediaResource(mediaBox: postbox.mediaBox, reference: reference) + } + return combineLatest(signals) |> map { _ in return } |> `catch` { _ in return .complete() } + } + + fetchDisposable.set(fetchDices.start()) + + let data = packs |> mapToSignal { values -> Signal<[String : [(String, Data?, TelegramMediaFile)]], NoError> in + + var signals: [Signal<(String, [(String, Data?, TelegramMediaFile)]), NoError>] = [] + + for value in values { + let dices = value.1.map { key, value in + return postbox.mediaBox.resourceData(value.file.resource) |> mapToSignal { resourceData -> Signal in + if resourceData.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + return .single(data) + } else { + return .single(nil) + } + } |> map { (key.fixed, $0, value.file) } + } + signals.append(combineLatest(dices) |> map { (value.0, $0) }) + } + + return combineLatest(signals) |> map { values in + var dict: [String : [(String, Data?, TelegramMediaFile)]] = [:] + + for value in values { + dict[value.0] = value.1 + } + return dict + } + } |> deliverOnResourceQueue + + loadDataDisposable.set(data.start(next: { [weak self] data in + guard let `self` = self else { + return + } + for diceData in data { + + var dict = self.dataContexts[diceData.key] ?? [:] + + for diceData in diceData.value { + let context: DiceSideDataContext + if let current = dict[diceData.0] { + context = current + } else { + context = DiceSideDataContext() + dict[diceData.0] = context + } + context.data = (diceData.1, diceData.2) + for subscriber in context.subscribers.copyItems() { + subscriber((diceData.1, diceData.2)) + } + } + self.dataContexts[diceData.key] = dict + + } + + })) + + } + + func interactiveSymbolData(baseSymbol: String, side: String, synchronous: Bool) -> Signal<(Data?, TelegramMediaFile), NoError> { + return Signal { [weak self] subscriber in + + guard let `self` = self else { + return EmptyDisposable + } + var cancelled = false + let disposable = MetaDisposable() + + let invoke = { + if !cancelled { + var dataContext: [String: DiceSideDataContext] + if let dc = self.dataContexts[baseSymbol] { + dataContext = dc + } else { + dataContext = [:] + } + + let context: DiceSideDataContext + if let current = dataContext[side] { + context = current + } else { + context = DiceSideDataContext() + dataContext[side] = context + } + self.dataContexts[baseSymbol] = dataContext + + let index = context.subscribers.add({ data in + if !cancelled { + subscriber.putNext(data) + } + }) + + if let data = context.data { + subscriber.putNext(data) + } + disposable.set(ActionDisposable { [weak self] in + resourcesQueue.async { + if let current = self?.dataContexts[baseSymbol]?[side] { + current.subscribers.remove(index) + } + } + }) + } + + } + + // if synchronous { + resourcesQueue.sync(invoke) + // } else { + // resourcesQueue.async(invoke) + // } + + + return ActionDisposable { + disposable.dispose() + cancelled = true + } + } + } + + func cleanup() { + fetchDisposable.dispose() + loadDataDisposable.dispose() + } + + deinit { + cleanup() + } +} diff --git a/Telegram-Mac/DiscussionHeaderItem.swift b/Telegram-Mac/DiscussionHeaderItem.swift new file mode 100644 index 0000000000..c4f3b4dfad --- /dev/null +++ b/Telegram-Mac/DiscussionHeaderItem.swift @@ -0,0 +1,78 @@ +// +// DiscussionHeaderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import TGUIKit + + +class DiscussionHeaderItem: GeneralRowItem { + fileprivate let icon: CGImage + fileprivate let textLayout: TextViewLayout + init(_ initialSize: NSSize, stableId: AnyHashable, icon: CGImage, text: NSAttributedString) { + self.icon = icon + self.textLayout = TextViewLayout(text, alignment: .center, alwaysStaticItems: true) + super.init(initialSize, stableId: stableId, inset: NSEdgeInsets(left: 30.0, right: 30.0, top: 0, bottom: 10)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + textLayout.measure(width: width - inset.left - inset.right) + return super.makeSize(width, oldWidth: oldWidth) + } + + override func viewClass() -> AnyClass { + return DiscussionHeaderView.self + } + + override var height: CGFloat { + return inset.top + inset.bottom + icon.backingSize.height + inset.top + textLayout.layoutSize.height + } +} + + +private final class DiscussionHeaderView : TableRowView { + private let imageView: ImageView = ImageView() + private let textView: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + } + + override var backdorColor: NSColor { + return theme.colors.listBackground + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? DiscussionHeaderItem else { return } + + self.imageView.image = item.icon + self.imageView.sizeToFit() + + self.textView.update(item.textLayout) + + needsLayout = true + } + + override func layout() { + super.layout() + guard let item = item as? DiscussionHeaderItem else { return } + + self.imageView.centerX(y: item.inset.top) + self.textView.centerX(y: self.imageView.frame.maxY + item.inset.bottom) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/DiscussionSetModalController.swift b/Telegram-Mac/DiscussionSetModalController.swift new file mode 100644 index 0000000000..27ff593c47 --- /dev/null +++ b/Telegram-Mac/DiscussionSetModalController.swift @@ -0,0 +1,129 @@ +// +// DiscussionSetModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +private final class DiscussionSetView : View { + private let channelPhoto: AvatarControl = AvatarControl(font: .avatar(22)) + private let groupPhoto: AvatarControl = AvatarControl(font: .avatar(22)) + private let photoContainer: View = View() + private let textView: TextView = TextView() + private let maskView: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(photoContainer) + channelPhoto.setFrameSize(NSMakeSize(60, 60)) + groupPhoto.setFrameSize(NSMakeSize(60, 60)) + maskView.setFrameSize(NSMakeSize(64, 64)) + maskView.layer?.cornerRadius = maskView.frame.height / 2 + maskView.layer?.borderWidth = (maskView.frame.width - groupPhoto.frame.width) / 2 + maskView.layer?.borderColor = theme.colors.background.cgColor + photoContainer.addSubview(channelPhoto) + photoContainer.addSubview(groupPhoto) + photoContainer.addSubview(maskView) + + photoContainer.setFrameSize(NSMakeSize(groupPhoto.frame.width + channelPhoto.frame.width - 10, maskView.frame.height)) + textView.isSelectable = false + textView.userInteractionEnabled = false + addSubview(textView) + } + + override func layout() { + super.layout() + + photoContainer.centerX(y: 20) + groupPhoto.centerY(x: 2) + channelPhoto.centerY(x: channelPhoto.frame.maxX - 10) + textView.centerX(y: photoContainer.frame.maxY + 20) + } + + func update(context: AccountContext, channel: Peer, group: Peer) -> NSSize { + channelPhoto.setPeer(account: context.account, peer: channel) + groupPhoto.setPeer(account: context.account, peer: group) + + let attributedString = NSMutableAttributedString() + + if channel.addressName == nil && group.addressName != nil { + _ = attributedString.append(string: L10n.discussionSetModalTextPrivateChannelPublicGroup(group.displayTitle, channel.displayTitle), color: theme.colors.text, font: .normal(.text)) + } else if group.addressName == nil { + _ = attributedString.append(string: L10n.discussionSetModalTextChannelPrivateGroup(group.displayTitle, channel.displayTitle), color: theme.colors.text, font: .normal(.text)) + } else { + _ = attributedString.append(string: L10n.discussionSetModalTextPublicChannelPublicGroup(group.displayTitle, channel.displayTitle), color: theme.colors.text, font: .normal(.text)) + } + attributedString.detectBoldColorInString(with: .medium(.text)) + + let layout = TextViewLayout(attributedString, alignment: .center) + layout.measure(width: 300 - 40) + + textView.update(layout) + + return NSMakeSize(300, textView.frame.height + photoContainer.frame.height + 20 + 20 + 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class DiscussionSetModalController: ModalViewController { + + private let context: AccountContext + private let channel: Peer + private let group:Peer + private let accept:()->Void + init(context: AccountContext, channel: Peer, group:Peer, accept:@escaping()->Void) { + self.context = context + self.channel = channel + self.group = group + self.accept = accept + super.init(frame: NSMakeRect(0, 0, 300, 300)) + } + + override func viewDidLoad() { + super.viewDidLoad() + let size = genericView.update(context: self.context, channel: self.channel, group: self.group) + modal?.resize(with: size, animated: false) + readyOnce() + } + + override var handleEvents: Bool { + return true + } + + override func returnKeyAction() -> KeyHandlerResult { + self.close() + self.accept() + return .invoked + } + + override var handleAllEvents: Bool { + return true + } + + + private var genericView:DiscussionSetView { + return self.view as! DiscussionSetView + } + + override func viewClass() -> AnyClass { + return DiscussionSetView.self + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: L10n.discussionSetModalOK, accept: { [weak self] in + self?.close() + self?.accept() + }, cancelTitle: L10n.modalCancel, drawBorder: false, height: 50) + } +} diff --git a/Telegram-Mac/DisplayLink.swift b/Telegram-Mac/DisplayLink.swift new file mode 100644 index 0000000000..dec68baeaa --- /dev/null +++ b/Telegram-Mac/DisplayLink.swift @@ -0,0 +1,93 @@ +// +// DisplayLink.swift +// +// Created by Jose Canepa on 8/18/16. +// Copyright © 2016 Jose Canepa. All rights reserved. +// +import AppKit + +/** + Analog to the CADisplayLink in iOS. + */ +class DisplayLink +{ + let timer : CVDisplayLink + let source : DispatchSourceUserDataAdd + + var callback : Optional<() -> ()> = nil + + var running : Bool { return CVDisplayLinkIsRunning(timer) } + + init?(onQueue queue: DispatchQueue = DispatchQueue.main) + { + source = DispatchSource.makeUserDataAddSource(queue: queue) + + var timerRef : CVDisplayLink? = nil + + var successLink = CVDisplayLinkCreateWithActiveCGDisplays(&timerRef) + + if let timer = timerRef + { + + successLink = CVDisplayLinkSetOutputCallback(timer, + { + (timer : CVDisplayLink, currentTime : UnsafePointer, outputTime : UnsafePointer, _ : CVOptionFlags, _ : UnsafeMutablePointer, sourceUnsafeRaw : UnsafeMutableRawPointer?) -> CVReturn in + + if let sourceUnsafeRaw = sourceUnsafeRaw + { + let sourceUnmanaged = Unmanaged.fromOpaque(sourceUnsafeRaw) + sourceUnmanaged.takeUnretainedValue().add(data: 1) + } + + return kCVReturnSuccess + + }, Unmanaged.passUnretained(source).toOpaque()) + + guard successLink == kCVReturnSuccess else + { + NSLog("Failed to create timer with active display") + return nil + } + + successLink = CVDisplayLinkSetCurrentCGDisplay(timer, CGMainDisplayID()) + + guard successLink == kCVReturnSuccess else + { + return nil + } + + self.timer = timer + } + else + { + return nil + } + source.setEventHandler(handler: + { + [weak self] in self?.callback?() + }) + } + + func start() { + guard !running else { return } + + CVDisplayLinkStart(timer) + source.resume() + } + + func cancel() + { + guard running else { return } + + CVDisplayLinkStop(timer) + source.cancel() + } + + deinit + { + if running + { + cancel() + } + } +} diff --git a/Telegram-Mac/DownloadSettingsViewController.swift b/Telegram-Mac/DownloadSettingsViewController.swift new file mode 100644 index 0000000000..e9aa5295cc --- /dev/null +++ b/Telegram-Mac/DownloadSettingsViewController.swift @@ -0,0 +1,218 @@ +// +// DownloadSettingsViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 30/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import TGUIKit + +private final class DownloadSettingsArguments { + let context: AccountContext + let toggleCategory:(AutomaticMediaDownloadCategoryPeers)->Void + let togglePreloadLargeVideos:(Bool)->Void + init(_ context: AccountContext, toggleCategory: @escaping(AutomaticMediaDownloadCategoryPeers)->Void, togglePreloadLargeVideos: @escaping(Bool)->Void) { + self.context = context + self.toggleCategory = toggleCategory + self.togglePreloadLargeVideos = togglePreloadLargeVideos + } +} + +private enum DownloadSettingsEntry : TableItemListNodeEntry { + case contacts(sectionId: Int32, enabled: Bool, category: AutomaticMediaDownloadCategoryPeers, viewType: GeneralViewType) + case groupChats(sectionId: Int32, enabled: Bool, category: AutomaticMediaDownloadCategoryPeers, viewType: GeneralViewType) + case channels(sectionId: Int32, enabled: Bool, category: AutomaticMediaDownloadCategoryPeers, viewType: GeneralViewType) + case fileSizeLimitHeader(sectionId: Int32, viewType: GeneralViewType) + case fileSizeLimit(sectionId: Int32, limit: Int32, category: AutomaticMediaDownloadCategoryPeers, viewType: GeneralViewType) + case preloadLargeVideos(sectionId: Int32, Bool, Bool, viewType: GeneralViewType) + case preloadLargeVideosDesc(sectionId: Int32, String, viewType: GeneralViewType) + case sectionId(Int32) + + var stableId: Int32 { + switch self { + case .contacts: + return 0 + case .groupChats: + return 1 + case .channels: + return 2 + case .fileSizeLimitHeader: + return 3 + case .fileSizeLimit: + return 5 + case .preloadLargeVideos: + return 6 + case .preloadLargeVideosDesc: + return 7 + case .sectionId(let id): + return 1000 + id + } + } + + + func item(_ arguments: DownloadSettingsArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .contacts(_, enabled, category, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageCategorySettingsPrivateChats, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleCategory(category.withUpdatedPrivateChats(!enabled)) + }) + case let .groupChats(_, enabled, category, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageCategorySettingsGroupChats, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleCategory(category.withUpdatedGroupChats(!enabled)) + }) + case let .channels(_, enabled, category, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageCategorySettingsChannels, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleCategory(category.withUpdatedChannels(!enabled)) + }) + case let .fileSizeLimitHeader(_, viewType): + return GeneralTextRowItem(initialSize, text: L10n.dataAndStorageCateroryFileSizeLimitHeader, viewType: viewType) + case let .fileSizeLimit(_, limit, category, viewType): + let list:[Int32] = [Int32(1 * 1024 * 1024), Int32(5 * 1024 * 1024), Int32(10 * 1024 * 1024), Int32(50 * 1024 * 1024), Int32(100 * 1024 * 1024), Int32(300 * 1024 * 1024), Int32(500 * 1024 * 1024), Int32(2000 * 1024 * 1024)] + + var titles:[String] = [] + titles.append(String.prettySized(with: Int(limit))) + + return SelectSizeRowItem(initialSize, stableId: stableId, current: limit, sizes: list, hasMarkers: false, titles: titles, viewType: viewType, selectAction: { select in + arguments.toggleCategory(category.withUpdatedSizeLimit(list[select])) + }) + case let .preloadLargeVideos(_, enabled, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.dataAndStorageCategoryPreloadLargeVideos, type: .switchable(value), viewType: viewType, action: { + arguments.togglePreloadLargeVideos(!value) + }, enabled: enabled) + case let .preloadLargeVideosDesc(_, limit, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.dataAndStorageCategoryPreloadLargeVideosDesc(limit), viewType: viewType) + case .sectionId: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + } + } + + var index: Int32 { + switch self { + case let .contacts(sectionId, _, _, _): + return (sectionId * 1000) + stableId + case let .groupChats(sectionId, _, _, _): + return (sectionId * 1000) + stableId + case let .channels(sectionId, _, _, _): + return (sectionId * 1000) + stableId + case let .fileSizeLimitHeader(sectionId, _): + return (sectionId * 1000) + stableId + case let .fileSizeLimit(sectionId, _, _, _): + return (sectionId * 1000) + stableId + case let .preloadLargeVideos(sectionId, _, _, _): + return (sectionId * 1000) + stableId + case let .preloadLargeVideosDesc(sectionId, _, _): + return (sectionId * 1000) + stableId + case .sectionId(let sectionId): + return (sectionId + 1) * 1000 - sectionId + } + } +} + +private func <(lhs: DownloadSettingsEntry, rhs: DownloadSettingsEntry) -> Bool { + return lhs.index < rhs.index +} + + +private func downloadSettingsEntries(state: AutomaticMediaDownloadCategoryPeers, isVideo: Bool, autoplayMedia: AutoplayMediaPreferences) -> [DownloadSettingsEntry] { + var entries:[DownloadSettingsEntry] = [] + var sectionId:Int32 = 0 + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.contacts(sectionId: sectionId, enabled: state.privateChats, category: state, viewType: .firstItem)) + entries.append(.groupChats(sectionId: sectionId, enabled: state.groupChats, category: state, viewType: .innerItem)) + entries.append(.channels(sectionId: sectionId, enabled: state.channels, category: state, viewType: .lastItem)) + + if let fileSizeLimit = state.fileSize { + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.fileSizeLimitHeader(sectionId: sectionId, viewType: .textTopItem)) + + entries.append(.fileSizeLimit(sectionId: sectionId, limit: fileSizeLimit, category: state, viewType: isVideo ? .firstItem : .singleItem)) + + if isVideo { + let preloadEnabled = fileSizeLimit >= 5 * 1024 * 1024 + + entries.append(.preloadLargeVideos(sectionId: sectionId, preloadEnabled, autoplayMedia.preloadVideos, viewType: .lastItem)) + entries.append(.preloadLargeVideosDesc(sectionId: sectionId, "\(fileSizeLimit / 1024 / 1024)", viewType: .textBottomItem)) + } + + } + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + return entries + +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:DownloadSettingsArguments) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + + + +class DownloadSettingsViewController: TableViewController { + private let disposable = MetaDisposable() + private let stateValue: ValuePromise + private let title: String + private let isVideo: Bool + private let updateCategory:(AutomaticMediaDownloadCategoryPeers)->Void + init(_ context: AccountContext, _ state: AutomaticMediaDownloadCategoryPeers, _ title: String, updateCategory:@escaping(AutomaticMediaDownloadCategoryPeers) -> Void) { + self.stateValue = ValuePromise(state, ignoreRepeated: true) + self.title = title + self.isVideo = L10n.dataAndStorageAutomaticDownloadVideo == title + self.updateCategory = updateCategory + super.init(context) + } + + override var defaultBarTitle: String { + return title + } + + override func viewDidLoad() { + super.viewDidLoad() + let context = self.context + + let arguments = DownloadSettingsArguments(context, toggleCategory: { [weak self] category in + self?.updateCategory(category) + self?.stateValue.set(category) + }, togglePreloadLargeVideos: { enabled in + _ = updateAutoplayMediaSettingsInteractively(postbox: context.account.postbox, { + $0.withUpdatedAutoplayPreloadVideos(enabled) + }).start() + }) + + let initialSize = self.atomicSize + let isVideo = self.isVideo + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let signal = combineLatest(stateValue.get(), appearanceSignal, autoplayMediaSettings(postbox: context.account.postbox)) |> map { state, appearance, autoplayMedia -> TableUpdateTransition in + let entries = downloadSettingsEntries(state: state, isVideo: isVideo, autoplayMedia: autoplayMedia).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify {$0}, arguments: arguments) + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) + + } + + deinit { + disposable.dispose() + } + +} diff --git a/Telegram-Mac/DownloadedFilesPaths.swift b/Telegram-Mac/DownloadedFilesPaths.swift new file mode 100644 index 0000000000..6e81d0e4eb --- /dev/null +++ b/Telegram-Mac/DownloadedFilesPaths.swift @@ -0,0 +1,104 @@ +// +// DownloadedFilesPaths.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit + +struct DownloadedPath : PostboxCoding, Equatable { + let id: MediaId + let downloadedPath: String + let size: Int32 + let lastModified: Int32 + init(id: MediaId, downloadedPath: String, size: Int32, lastModified: Int32) { + self.id = id + self.downloadedPath = downloadedPath + self.size = size + self.lastModified = lastModified + } + + + init(decoder: PostboxDecoder) { + self.id = decoder.decodeObjectForKey("id", decoder: { MediaId(decoder: $0) }) as! MediaId + self.downloadedPath = decoder.decodeStringForKey("dp", orElse: "") + self.size = decoder.decodeInt32ForKey("s", orElse: 0) + self.lastModified = decoder.decodeInt32ForKey("lm", orElse: 0) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.id, forKey: "id") + encoder.encodeString(self.downloadedPath, forKey: "dp") + encoder.encodeInt32(self.size, forKey: "s") + encoder.encodeInt32(self.lastModified, forKey: "lm") + } +} + +struct DownloadedFilesPaths: PreferencesEntry, Equatable { + + private let paths: [DownloadedPath] + + static var defaultValue: DownloadedFilesPaths { + return DownloadedFilesPaths(paths: []) + } + + init(paths: [DownloadedPath]) { + self.paths = paths + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let other = to as? DownloadedFilesPaths { + return other == self + } else { + return false + } + } + + init(decoder: PostboxDecoder) { + self.paths = decoder.decodeObjectArrayForKey("p") + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.paths, forKey: "p") + } + + func path(for mediaId: MediaId) -> DownloadedPath? { + for path in paths { + if path.id == mediaId { + return path + } + } + return nil + } + + func withAddedPath(_ path: DownloadedPath) -> DownloadedFilesPaths { + var paths = self.paths + if let index = paths.firstIndex(where: {$0.id == path.id}) { + paths[index] = path + } else { + paths.append(path) + } + return DownloadedFilesPaths(paths: paths) + } +} + + +func downloadedFilePaths(_ postbox: Postbox) -> Signal { + return postbox.transaction { transaction in + return transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.downloadedPaths) as? DownloadedFilesPaths ?? DownloadedFilesPaths.defaultValue + } +} + +func updateDownloadedFilePaths(_ postbox: Postbox, _ f: @escaping(DownloadedFilesPaths) -> DownloadedFilesPaths) -> Signal { + return postbox.transaction { transaction in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.downloadedPaths, { entry in + let current = entry as? DownloadedFilesPaths ?? DownloadedFilesPaths.defaultValue + + return f(current) + }) + } |> ignoreValues +} diff --git a/Telegram-Mac/DynamicHeightRowItem.swift b/Telegram-Mac/DynamicHeightRowItem.swift new file mode 100644 index 0000000000..9d96e46685 --- /dev/null +++ b/Telegram-Mac/DynamicHeightRowItem.swift @@ -0,0 +1,60 @@ +// +// DynamicHeightRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 03/10/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +enum DynamicItemSide { + case top + case bottom +} +class DynamicHeightRowItem: GeneralRowItem { + private let side:DynamicItemSide + init(_ initialSize: NSSize, stableId: AnyHashable, side: DynamicItemSide) { + self.side = side + super.init(initialSize, stableId: stableId) + } + + override var height: CGFloat { + if let table = table { + var tableHeight: CGFloat = 0 + table.enumerateItems { item -> Bool in + if !item.reloadOnTableHeightChanged { + tableHeight += item.height + } + return true + } + + return max((table.frame.height - tableHeight) / 2, 0) + } else { + return 0 + } + } + + override var instantlyResize: Bool { + return true + } + override var reloadOnTableHeightChanged: Bool { + return true + } + + override func viewClass() -> AnyClass { + return DynamicHeightRowView.self + } +} + +private final class DynamicHeightRowView : TableRowView { + override func updateColors() { + + } + + override var firstResponder: NSResponder? { + return nil + } +} diff --git a/Telegram-Mac/EBlockItem.swift b/Telegram-Mac/EBlockItem.swift index 65b8f3f168..549a29e056 100644 --- a/Telegram-Mac/EBlockItem.swift +++ b/Telegram-Mac/EBlockItem.swift @@ -8,7 +8,8 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore class EBlockItem: TableRowItem { var _stableId:Int64 = Int64(arc4random()) diff --git a/Telegram-Mac/EBlockRowView.swift b/Telegram-Mac/EBlockRowView.swift index 30a873e9a8..8d80e855fb 100644 --- a/Telegram-Mac/EBlockRowView.swift +++ b/Telegram-Mac/EBlockRowView.swift @@ -8,7 +8,7 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit extension CATiledLayer { func fadeDuration() -> CFTimeInterval { @@ -16,7 +16,7 @@ extension CATiledLayer { } } -class ETiledLayer : CATiledLayer { +class ETiledLayer : CALayer { fileprivate var layoutNextRequest: Bool = true @@ -29,22 +29,23 @@ class ETiledLayer : CATiledLayer { // } } -private class EmojiSegmentView: NSView, CALayerDelegate { +private class EmojiSegmentView: View { fileprivate override var isFlipped: Bool { return true } - private let item:Atomic = Atomic(value: nil) + private var item: EBlockItem? - fileprivate func draw(_ layer: CALayer, in ctx: CGContext) { + fileprivate override func draw(_ layer: CALayer, in ctx: CGContext) { - if let item = item.modify({$0}) { + if let item = self.item { ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) var ts:NSPoint = NSMakePoint(17, 29) for segment in item.lineAttr { for line in segment { + ctx.textPosition = ts CTLineDraw(CTLineCreateWithAttributedString(line), ctx) ts.x+=xAdd @@ -57,17 +58,9 @@ private class EmojiSegmentView: NSView, CALayerDelegate { } - var tiled:ETiledLayer = ETiledLayer() - required override init(frame frameRect: NSRect) { + required init(frame frameRect: NSRect) { super.init(frame: frameRect) - wantsLayer = true - self.layer?.addSublayer(tiled) - tiled.frame = self.bounds - tiled.levelsOfDetailBias = Int(backingScaleFactor) - self.tiled.delegate = self - - //tiled.shouldRasterize } required init?(coder: NSCoder) { @@ -76,31 +69,11 @@ private class EmojiSegmentView: NSView, CALayerDelegate { override func viewDidChangeBackingProperties() { super.viewDidChangeBackingProperties() - tiled.levelsOfDetailBias = Int(backingScaleFactor) } - override var needsDisplay: Bool { - get { - return super.needsDisplay - } - set { - super.needsDisplay = true - } - } - - override func setNeedsDisplay(_ invalidRect: NSRect) { - - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - tiled.frame = bounds - tiled.tileSize = bounds.size - } func update(with item:EBlockItem?) -> Void { - _ = self.item.swap(item) - tiled.layoutNextRequest = true + self.item = item background = theme.colors.background self.needsDisplay = true } @@ -112,10 +85,8 @@ private let yAdd:CGFloat = 34 class EBlockRowView: TableRowView { - // var tiled:CATiledLayer = CATiledLayer() - var button:Control = Control() - private var segmentView:EmojiSegmentView = EmojiSegmentView() + private var segmentView:EmojiSegmentView = EmojiSegmentView(frame: NSZeroRect) var mouseDown:Bool = false private var popover: NSPopover? @@ -150,7 +121,7 @@ class EBlockRowView: TableRowView { func update(with location:NSPoint) -> Bool { - if self.mouse(location, in: self.visibleRect) { + if self.isMousePoint(location, in: self.visibleRect) { if let item = item as? EBlockItem { var point:NSPoint = location @@ -187,7 +158,7 @@ class EBlockRowView: TableRowView { if point != button.frame.origin { if self.button.isSelected { - button.layer?.animatePosition(from: button.frame.origin, to: point, duration: 0.1, timingFunction: kCAMediaTimingFunctionLinear) + button.layer?.animatePosition(from: button.frame.origin, to: point, duration: 0.1, timingFunction: CAMediaTimingFunctionName.linear) } button.frame = NSMakeRect(point.x, point.y, button.frame.width, button.frame.height) @@ -210,10 +181,11 @@ class EBlockRowView: TableRowView { if selectedEmoji.emojiUnmodified != selectedEmoji, let item = item as? EBlockItem { popover?.close() popover = NSPopover() - popover?.contentViewController = EmojiToleranceController(selectedEmoji.emojiUnmodified, postbox: item.account.postbox, handle: { [weak self, weak item] emoji in + popover?.contentViewController = EmojiToleranceController(selectedEmoji.emojiUnmodified, postbox: item.account.postbox, handle: { [weak self, weak item] emoji, modifier in if let item = item { - _ = modifySkinEmoji(emoji, postbox: item.account.postbox).start() + _ = modifySkinEmoji(emoji, modifier: modifier, postbox: item.account.postbox).start() } + self?.popover?.close() self?.popover = nil }) @@ -226,17 +198,21 @@ class EBlockRowView: TableRowView { self.button.isSelected = self.update(with: segmentView.convert(event.locationInWindow, from: nil)) let emoji = selectedEmoji - let lhs = emoji.emojiUnmodified.glyphCount - let rhs = ( emoji.emojiUnmodified + "🏻").glyphCount - longHandle.set((Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(next: { [weak self] in + let rhs = emoji.emojiUnmodified.emojiWithSkinModifier("🏻").glyphCount + longHandle.set((Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(next: { [weak self] in if let strongSelf = self, lhs == rhs, let item = self?.item as? EBlockItem { strongSelf.useEmoji = false strongSelf.popover?.close() strongSelf.popover = NSPopover() - strongSelf.popover?.contentViewController = EmojiToleranceController(emoji.emojiUnmodified, postbox: item.account.postbox, handle: { [weak strongSelf, weak item] emoji in + strongSelf.popover?.contentViewController = EmojiToleranceController(emoji.emojiUnmodified, postbox: item.account.postbox, handle: { [weak strongSelf, weak item] emoji, modifier in if let item = item { - _ = modifySkinEmoji(emoji, postbox: item.account.postbox).start() + _ = modifySkinEmoji(emoji, modifier: modifier, postbox: item.account.postbox).start() + if let modifier = modifier { + item.selectHandler(emoji + modifier) + } else { + item.selectHandler(emoji) + } } strongSelf?.popover?.close() strongSelf?.popover = nil @@ -296,6 +272,7 @@ class EBlockRowView: TableRowView { override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) + segmentView.frame = bounds // tiled.frame = bounds // tiled.tileSize = bounds.size } diff --git a/Telegram-Mac/EDSunriseSet.h b/Telegram-Mac/EDSunriseSet.h new file mode 100644 index 0000000000..73f2714c08 --- /dev/null +++ b/Telegram-Mac/EDSunriseSet.h @@ -0,0 +1,51 @@ +// +// EDSunriseSet.h +// +// Created by Ernesto García on 20/08/11. +// Copyright 2011 Ernesto García. All rights reserved. +// + +// C/C++ sun calculations created by Paul Schlyter +// sunriset.c +// http://stjarnhimlen.se/english.html +// SUNRISET.C - computes Sun rise/set times, start/end of twilight, and +// the length of the day at any date and latitude +// Written as DAYLEN.C, 1989-08-16 +// Modified to SUNRISET.C, 1992-12-01 +// (c) Paul Schlyter, 1989, 1992 +// Released to the public domain by Paul Schlyter, December 1992 +// + +#import + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag in this file. +#endif + +@interface EDSunriseSet : NSObject + +@property (readonly, strong) NSDate *date; +@property (readonly, strong) NSDate *sunset; +@property (readonly, strong) NSDate *sunrise; +@property (readonly, strong) NSDate *civilTwilightStart; +@property (readonly, strong) NSDate *civilTwilightEnd; +@property (readonly, strong) NSDate *nauticalTwilightStart; +@property (readonly, strong) NSDate *nauticalTwilightEnd; +@property (readonly, strong) NSDate *astronomicalTwilightStart; +@property (readonly, strong) NSDate *astronomicalTwilightEnd; + +@property (readonly, strong) NSDateComponents* localSunrise; +@property (readonly, strong) NSDateComponents* localSunset; +@property (readonly, strong) NSDateComponents* localCivilTwilightStart; +@property (readonly, strong) NSDateComponents* localCivilTwilightEnd; +@property (readonly, strong) NSDateComponents* localNauticalTwilightStart; +@property (readonly, strong) NSDateComponents* localNauticalTwilightEnd; +@property (readonly, strong) NSDateComponents* localAstronomicalTwilightStart; +@property (readonly, strong) NSDateComponents* localAstronomicalTwilightEnd; + + +-(instancetype)initWithDate:(NSDate*)date timezone:(NSTimeZone*)timezone latitude:(double)latitude longitude:(double)longitude NS_DESIGNATED_INITIALIZER; ++(instancetype)sunrisesetWithDate:(NSDate*)date timezone:(NSTimeZone*)timezone latitude:(double)latitude longitude:(double)longitude; +-(instancetype) init __attribute__((unavailable("init not available. Use initWithDate:timeZone:latitude:longitude: instead"))); + +@end diff --git a/Telegram-Mac/EDSunriseSet.m b/Telegram-Mac/EDSunriseSet.m new file mode 100644 index 0000000000..27b4632e0e --- /dev/null +++ b/Telegram-Mac/EDSunriseSet.m @@ -0,0 +1,447 @@ +// +// EDSunriseSet.m +// +// Created by Ernesto García on 20/08/11. +// Copyright 2011 Ernesto García. All rights reserved. +// + +// C/C++ sun calculations created by Paul Schlyter +// sunriset.c +// http://stjarnhimlen.se/english.html +// SUNRISET.C - computes Sun rise/set times, start/end of twilight, and +// the length of the day at any date and latitude +// Written as DAYLEN.C, 1989-08-16 +// Modified to SUNRISET.C, 1992-12-01 +// (c) Paul Schlyter, 1989, 1992 +// Released to the public domain by Paul Schlyter, December 1992 +// + +#import "EDSunriseSet.h" + +// +// Defines from sunriset.c +// +#define INV360 ( 1.0 / 360.0 ) + +#define RADEG ( 180.0 / M_PI ) +#define DEGRAD ( M_PI / 180.0 ) + +/* The trigonometric functions in degrees */ + +#define sind(x) sin((x)*DEGRAD) +#define cosd(x) cos((x)*DEGRAD) +#define tand(x) tan((x)*DEGRAD) + +#define atand(x) (RADEG*atan(x)) +#define asind(x) (RADEG*asin(x)) +#define acosd(x) (RADEG*acos(x)) +#define atan2d(y,x) (RADEG*atan2(y,x)) + +/* A macro to compute the number of days elapsed since 2000 Jan 0.0 */ +/* (which is equal to 1999 Dec 31, 0h UT) */ +#define days_since_2000_Jan_0(y,m,d) \ +(367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L) + + +#if defined(__IPHONE_8_0) || defined (__MAC_10_10) +#define EDGregorianCalendar NSCalendarIdentifierGregorian +#else +#define EDGregorianCalendar NSGregorianCalendar +#endif + + +#pragma mark - Readwrite accessors only private +@interface EDSunriseSet() + +@property (nonatomic) double latitude; +@property (nonatomic) double longitude; +@property (nonatomic, strong) NSTimeZone *timezone; +@property (nonatomic, strong) NSCalendar *calendar; +@property (nonatomic, strong) NSTimeZone *utcTimeZone; + +@property (readwrite, strong) NSDate *date; +@property (readwrite, strong) NSDate *sunset; +@property (readwrite, strong) NSDate *sunrise; +@property (readwrite, strong) NSDate *civilTwilightStart; +@property (readwrite, strong) NSDate *civilTwilightEnd; +@property (readwrite, strong) NSDate *nauticalTwilightStart; +@property (readwrite, strong) NSDate *nauticalTwilightEnd; +@property (readwrite, strong) NSDate *astronomicalTwilightStart; +@property (readwrite, strong) NSDate *astronomicalTwilightEnd; + +@property (readwrite, strong) NSDateComponents* localSunrise; +@property (readwrite, strong) NSDateComponents* localSunset; +@property (readwrite, strong) NSDateComponents* localCivilTwilightStart; +@property (readwrite, strong) NSDateComponents* localCivilTwilightEnd; +@property (readwrite, strong) NSDateComponents* localNauticalTwilightStart; +@property (readwrite, strong) NSDateComponents* localNauticalTwilightEnd; +@property (readwrite, strong) NSDateComponents* localAstronomicalTwilightStart; +@property (readwrite, strong) NSDateComponents* localAstronomicalTwilightEnd; + +@end + +#pragma mark - Calculations from sunriset.c +@implementation EDSunriseSet(Calculations) + +/*****************************************/ +/* Reduce angle to within 0..360 degrees */ +/*****************************************/ +-(double) revolution:(double) x +{ + return( x - 360.0 * floor( x * INV360 ) ); +} + +/*********************************************/ +/* Reduce angle to within -180..+180 degrees */ +/*********************************************/ +-(double) rev180:(double) x +{ + return( x - 360.0 * floor( x * INV360 + 0.5 ) ); +} + +-(double) GMST0:(double) d +{ + double sidtim0; + /* Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr */ + /* L = M + w, as defined in sunpos(). Since I'm too lazy to */ + /* add these numbers, I'll let the C compiler do it for me. */ + /* Any decent C compiler will add the constants at compile */ + /* time, imposing no runtime or code overhead. */ + sidtim0 = [self revolution: ( 180.0 + 356.0470 + 282.9404 ) + + ( 0.9856002585 + 4.70935E-5 ) * d]; + return sidtim0; +} + +/******************************************************/ +/* Computes the Sun's ecliptic longitude and distance */ +/* at an instant given in d, number of days since */ +/* 2000 Jan 0.0. The Sun's ecliptic latitude is not */ +/* computed, since it's always very near 0. */ +/******************************************************/ +-(void) sunposAtDay:(double)d longitude:(double*)lon r:(double *)r +{ + double M, /* Mean anomaly of the Sun */ + w, /* Mean longitude of perihelion */ + /* Note: Sun's mean longitude = M + w */ + e, /* Eccentricity of Earth's orbit */ + E, /* Eccentric anomaly */ + x, y, /* x, y coordinates in orbit */ + v; /* True anomaly */ + + /* Compute mean elements */ + M = [self revolution:( 356.0470 + 0.9856002585 * d )]; + w = 282.9404 + 4.70935E-5 * d; + e = 0.016709 - 1.151E-9 * d; + + /* Compute true longitude and radius vector */ + E = M + e * RADEG * sind(M) * ( 1.0 + e * cosd(M) ); + x = cosd(E) - e; + y = sqrt( 1.0 - e*e ) * sind(E); + *r = sqrt( x*x + y*y ); /* Solar distance */ + v = atan2d( y, x ); /* True anomaly */ + *lon = v + w; /* True solar longitude */ + if ( *lon >= 360.0 ) + *lon -= 360.0; /* Make it 0..360 degrees */ +} + +-(void) sun_RA_decAtDay:(double)d RA:(double*)RA decl:(double *)dec r:(double *)r +{ + double lon, obl_ecl; + double xs, ys, zs; + double xe, ye, ze; + + /* Compute Sun's ecliptical coordinates */ + //sunpos( d, &lon, r ); + [self sunposAtDay:d longitude:&lon r:r]; + + /* Compute ecliptic rectangular coordinates */ + xs = *r * cosd(lon); + ys = *r * sind(lon); + zs = 0; /* because the Sun is always in the ecliptic plane! */ + + /* Compute obliquity of ecliptic (inclination of Earth's axis) */ + obl_ecl = 23.4393 - 3.563E-7 * d; + + /* Convert to equatorial rectangular coordinates - x is unchanged */ + xe = xs; + ye = ys * cosd(obl_ecl); + ze = ys * sind(obl_ecl); + + /* Convert to spherical coordinates */ + *RA = atan2d( ye, xe ); + *dec = atan2d( ze, sqrt(xe*xe + ye*ye) ); + +} /* sun_RA_dec */ + +#define sun_rise_set(year,month,day,lon,lat,rise,set) \ +__sunriset__( year, month, day, lon, lat, -35.0/60.0, 1, rise, set ) + +-(int)sunRiseSetForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:(-35.0/60.0) + upper_limb:1 trise:trise tset:tset]; + +} +/* + #define civil_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -6.0, 0, start, end ) + */ +-(int) civilTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-6.0 + upper_limb:0 trise:trise tset:tset]; +} +/* + #define nautical_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -12.0, 0, start, end ) + */ +-(int) nauticalTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-12.0 + upper_limb:0 trise:trise tset:tset]; +} +/* + #define astronomical_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -18.0, 0, start, end ) + */ +-(int) astronomicalTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-18.0 + upper_limb:0 trise:trise tset:tset]; +} + +/***************************************************************************/ +/* Note: year,month,date = calendar date, 1801-2099 only. */ +/* Eastern longitude positive, Western longitude negative */ +/* Northern latitude positive, Southern latitude negative */ +/* The longitude value IS critical in this function! */ +/* altit = the altitude which the Sun should cross */ +/* Set to -35/60 degrees for rise/set, -6 degrees */ +/* for civil, -12 degrees for nautical and -18 */ +/* degrees for astronomical twilight. */ +/* upper_limb: non-zero -> upper limb, zero -> center */ +/* Set to non-zero (e.g. 1) when computing rise/set */ +/* times, and to zero when computing start/end of */ +/* twilight. */ +/* *rise = where to store the rise time */ +/* *set = where to store the set time */ +/* Both times are relative to the specified altitude, */ +/* and thus this function can be used to comupte */ +/* various twilight times, as well as rise/set times */ +/* Return value: 0 = sun rises/sets this day, times stored at */ +/* *trise and *tset. */ +/* +1 = sun above the specified "horizon" 24 hours. */ +/* *trise set to time when the sun is at south, */ +/* minus 12 hours while *tset is set to the south */ +/* time plus 12 hours. "Day" length = 24 hours */ +/* -1 = sun is below the specified "horizon" 24 hours */ +/* "Day" length = 0 hours, *trise and *tset are */ +/* both set to the time when the sun is at south. */ +/* */ +/**********************************************************************/ +-(int)sunRiseSetHelperForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + altitude:(double)altit upper_limb:(int)upper_limb trise:(double *)trise tset:(double *)tset +{ + double d, /* Days since 2000 Jan 0.0 (negative before) */ + sr, /* Solar distance, astronomical units */ + sRA, /* Sun's Right Ascension */ + sdec, /* Sun's declination */ + sradius, /* Sun's apparent radius */ + t, /* Diurnal arc */ + tsouth, /* Time when Sun is at south */ + sidtime; /* Local sidereal time */ + + int rc = 0; /* Return cde from function - usually 0 */ + + /* Compute d of 12h local mean solar time */ + d = days_since_2000_Jan_0(year,month,day) + 0.5 - lon/360.0; + + + /* Compute local sideral time of this moment */ + //sidtime = revolution( GMST0(d) + 180.0 + lon ); + sidtime = [self revolution:[self GMST0:d] + 180.0 + lon]; + /* Compute Sun's RA + Decl at this moment */ + //sun_RA_dec( d, &sRA, &sdec, &sr ); + [self sun_RA_decAtDay:d RA: &sRA decl:&sdec r:&sr]; + + /* Compute time when Sun is at south - in hours UT */ + //tsouth = 12.0 - rev180(sidtime - sRA)/15.0; + tsouth = 12.0 - [self rev180:sidtime - sRA] / 15.0; + + /* Compute the Sun's apparent radius, degrees */ + sradius = 0.2666 / sr; + + /* Do correction to upper limb, if necessary */ + if ( upper_limb ) + altit -= sradius; + + /* Compute the diurnal arc that the Sun traverses to reach */ + /* the specified altitide altit: */ + { + double cost; + cost = ( sind(altit) - sind(lat) * sind(sdec) ) / + ( cosd(lat) * cosd(sdec) ); + if ( cost >= 1.0 ) + rc = -1, t = 0.0; /* Sun always below altit */ + else if ( cost <= -1.0 ) + rc = +1, t = 12.0; /* Sun always above altit */ + else + t = acosd(cost)/15.0; /* The diurnal arc, hours */ + } + + /* Store rise and set times - in hours UT */ + *trise = tsouth - t; + *tset = tsouth + t; + + return rc; +} /* __sunriset__ */ + + +@end + + +#pragma mark - Private Implementation + +@implementation EDSunriseSet(Private) + +static const int kSecondsInHour= 60.0*60.0; + + +-(NSDate*)utcTime:(NSDateComponents*)dateComponents withOffset:(NSTimeInterval)interval +{ + [self.calendar setTimeZone:self.utcTimeZone]; + return [[self.calendar dateFromComponents:dateComponents] dateByAddingTimeInterval:(NSTimeInterval)(interval)]; +} + +-(NSDateComponents*)localTime:(NSDate*)refDate +{ + [self.calendar setTimeZone:self.timezone]; + // Return only hour, minute, seconds + NSDateComponents *dc = [self.calendar components:( NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond) fromDate:refDate] ; + + return dc; +} + +- (instancetype) init { + [super doesNotRecognizeSelector:_cmd]; + return nil; +} + +-(NSString *)description +{ + return [NSString stringWithFormat: + @"Date: %@\nTimeZone: %@\n" + @"Local Sunrise: %@\n" + @"Local Sunset: %@\n" + @"Local Civil Twilight Start: %@\n" + @"Local Civil Twilight End: %@\n" + @"Local Nautical Twilight Start: %@\n" + @"Local Nautical Twilight End: %@\n" + @"Local Astronomical Twilight Start: %@\n" + @"Local Astronomical Twilight End: %@\n", + self.date.description, self.timezone.name, + self.localSunrise.description, self.localSunset.description, + self.localCivilTwilightStart, self.localCivilTwilightEnd, + self.localNauticalTwilightStart, self.localNauticalTwilightEnd, + self.localAstronomicalTwilightStart, self.localAstronomicalTwilightEnd + ]; +} + +#pragma mark - Calculation methods + +-(void)calculateSunriseSunset +{ + // Get date components + [self.calendar setTimeZone:self.timezone]; + NSDateComponents *dateComponents = [self.calendar components:( NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay ) fromDate:self.date]; + + // Calculate sunrise and sunset + double rise=0.0, set=0.0; + [self sunRiseSetForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&rise tset:&set ]; + NSTimeInterval secondsRise = rise*kSecondsInHour; + NSTimeInterval secondsSet = set*kSecondsInHour; + + self.sunrise = [self utcTime:dateComponents withOffset:(NSTimeInterval)secondsRise]; + self.sunset = [self utcTime:dateComponents withOffset:(NSTimeInterval)secondsSet]; + self.localSunrise = [self localTime:self.sunrise]; + self.localSunset = [self localTime:self.sunset]; +} + +-(void)calculateTwilight +{ + // Get date components + [self.calendar setTimeZone:self.timezone]; + NSDateComponents *dateComponents = [self.calendar components:( NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay ) fromDate:self.date]; + double start=0.0, end=0.0; + + // Civil twilight + [self civilTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.civilTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.civilTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localCivilTwilightStart = [self localTime:self.civilTwilightStart]; + self.localCivilTwilightEnd = [self localTime:self.civilTwilightEnd]; + + // Nautical twilight + [self nauticalTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.nauticalTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.nauticalTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localNauticalTwilightStart = [self localTime:self.nauticalTwilightStart]; + self.localNauticalTwilightEnd = [self localTime:self.nauticalTwilightEnd]; + // Astronomical twilight + [self astronomicalTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.astronomicalTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.astronomicalTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localAstronomicalTwilightStart = [self localTime:self.astronomicalTwilightStart]; + self.localAstronomicalTwilightEnd = [self localTime:self.astronomicalTwilightEnd]; +} + +-(void)calculate +{ + [self calculateSunriseSunset]; + [self calculateTwilight]; +} + +@end + + +#pragma mark - Public Implementation + +@implementation EDSunriseSet + +#pragma mark - Initialization + +-(EDSunriseSet*)initWithDate:(NSDate*)date timezone:(NSTimeZone*)tz latitude:(double)latitude longitude:(double)longitude { + self = [super init]; + if( self ) + { + self.latitude = latitude; + self.longitude = longitude; + self.timezone = tz; + self.date = date; + + self.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:EDGregorianCalendar]; + self.utcTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + + [self calculate]; + + } + return self; +} + ++(EDSunriseSet*)sunrisesetWithDate:(NSDate*)date timezone:(NSTimeZone*)tz latitude:(double)latitude longitude:(double)longitude { + return [[EDSunriseSet alloc] initWithDate:date timezone:tz latitude:latitude longitude:longitude]; +} + +@end + + + diff --git a/Telegram-Mac/EStickView.swift b/Telegram-Mac/EStickView.swift index bf1e5291a7..29a9731807 100644 --- a/Telegram-Mac/EStickView.swift +++ b/Telegram-Mac/EStickView.swift @@ -12,12 +12,22 @@ class EStickView: TableStickView { required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override var backdorColor: NSColor { + return theme.colors.background + } + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + needsDisplay = true + } + override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) @@ -30,7 +40,7 @@ class EStickView: TableStickView { var f = focus(item.layout.0.size) f.origin.x = 20 f.origin.y -= 1 - item.layout.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor) + item.layout.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backdorColor) } } diff --git a/Telegram-Mac/EStickerGridEntries.swift b/Telegram-Mac/EStickerGridEntries.swift deleted file mode 100644 index 52ab749555..0000000000 --- a/Telegram-Mac/EStickerGridEntries.swift +++ /dev/null @@ -1,279 +0,0 @@ -// -// StickerGridEntries.swift -// Telegram-Mac -// -// Created by keepcoder on 23/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac -import TGUIKit - -enum ChatMediaInputGridEntryStableId : Hashable { - - case sticker(ItemCollectionId, ItemCollectionItemIndex.Id) - case speficicSticker(ItemCollectionId, ItemCollectionItemIndex.Id) - case recent(TelegramMediaFile) - case saved(TelegramMediaFile) - - static func ==(lhs: ChatMediaInputGridEntryStableId, rhs: ChatMediaInputGridEntryStableId) -> Bool { - switch lhs { - case let .sticker(lhsItemCollectionId, lhsItemId): - if case let .sticker(rhsItemCollectionId, rhsItemId) = rhs { - return lhsItemCollectionId == rhsItemCollectionId && lhsItemId == rhsItemId - } else { - return false - } - case let .speficicSticker(lhsItemCollectionId, lhsItemId): - if case let .speficicSticker(rhsItemCollectionId, rhsItemId) = rhs { - return lhsItemCollectionId == rhsItemCollectionId && lhsItemId == rhsItemId - } else { - return false - } - case let .recent(lhsFile): - if case let .recent(rhsFile) = rhs { - return lhsFile.isEqual(rhsFile) - } else { - return false - } - case let .saved(lhsFile): - if case let .saved(rhsFile) = rhs { - return lhsFile.isEqual(rhsFile) - } else { - return false - } - - } - } - - var hashValue: Int { - switch self { - case let .sticker(_, itemId): - return itemId.hashValue - case let .speficicSticker(_, itemId): - return itemId.hashValue - case let .recent(file): - return file.fileId.hashValue - case let .saved(file): - return file.fileId.hashValue - } - // return self.itemId.hashValue - } -} - -enum ChatMediaGridPackHeaderInfo { - case pack(StickerPackCollectionInfo?) - case speficicPack(StickerPackCollectionInfo?) - case recent - case saved -} - -extension ChatMediaGridPackHeaderInfo { - var title:String { - switch self { - case let .pack(info): - if let info = info { - return info.title.uppercased() - } else { - return "" - } - case .recent: - return tr(.stickersRecent) - case .saved: - return "tr(.stickersFavorite)" - case .speficicPack: - return tr(.stickersGroupStickers) - } - } -} - -enum ChatMediaGridCollectionStableId : Hashable { - case pack(ItemCollectionId) - case recent - case specificPack(ItemCollectionId) - case saved - - var hashValue: Int { - switch self { - case let .pack(collectionId): - return collectionId.hashValue - case let .specificPack(collectionId): - return collectionId.hashValue - case .recent: - return 1 - case .saved: - return 2 - } - } - - var itemCollectionId:ItemCollectionId? { - switch self { - case let .pack(collectionId): - return collectionId - case let .specificPack(collectionId): - return collectionId - - default: - return nil - } - } - - - static func ==(lhs: ChatMediaGridCollectionStableId, rhs: ChatMediaGridCollectionStableId) -> Bool { - switch lhs { - case let .pack(lhsCollectionId): - if case let .pack(rhsCollectionId) = rhs { - return lhsCollectionId == rhsCollectionId - } else { - return false - } - case .recent: - if case .recent = rhs { - return true - } else { - return false - } - case .saved: - if case .saved = rhs { - return true - } else { - return false - } - case let .specificPack(collectionId): - if case .specificPack(collectionId) = rhs { - return true - } else { - return false - } - } - } -} - -enum ChatMediaInputGridIndex : Hashable, Comparable { - case sticker(ItemCollectionViewEntryIndex) - case speficicSticker(ItemCollectionItemIndex) - case recent(Int) - case saved(Int) - - var packIndex:ItemCollectionViewEntryIndex { - switch self { - case let .sticker(index): - return index - case .saved(let index), .recent(let index): - return ItemCollectionViewEntryIndex.lowerBound(collectionIndex: Int32(index), collectionId: ItemCollectionId(namespace: 0, id: 0)) - case .speficicSticker: - return ItemCollectionViewEntryIndex.lowerBound(collectionIndex: 3, collectionId: ItemCollectionId(namespace: 0, id: 0)) - } - } - - - var hashValue: Int { - switch self { - case let .sticker(index): - return Int(index.itemIndex.index) - case let .recent(index): - return index - case let .saved(index): - return index - case .speficicSticker(let index): - return index.hashValue - } - } - - static func ==(lhs: ChatMediaInputGridIndex, rhs: ChatMediaInputGridIndex) -> Bool { - switch lhs { - case let .sticker(lhsIndex): - if case let .sticker(rhsIndex) = rhs { - return lhsIndex == rhsIndex - } else { - return false - } - case let .recent(lhsIndex): - if case let .recent(rhsIndex) = rhs { - return lhsIndex == rhsIndex - } else { - return false - } - case let .speficicSticker(index): - if case .speficicSticker(index) = rhs { - return true - } else { - return false - } - case let .saved(lhsIndex): - if case let .saved(rhsIndex) = rhs { - return lhsIndex == rhsIndex - } else { - return false - } - } - } - - static func <(lhs: ChatMediaInputGridIndex, rhs: ChatMediaInputGridIndex) -> Bool { - switch lhs { - case let .recent(lhsIndex): - if case let .recent(rhsIndex) = rhs { - return lhsIndex < rhsIndex - } else { - switch rhs { - case .saved: - return false - default: - return true - } - } - case let .sticker(lhsIndex): - if case let .sticker(rhsIndex) = rhs { - return lhsIndex < rhsIndex - } else { - switch rhs { - case .recent, .saved: - return true - default: - return false - } - } - case let .saved(lhsIndex): - if case let .saved(rhsIndex) = rhs { - return lhsIndex < rhsIndex - } else { - return true - } - case let .speficicSticker(lhsIndex): - if case let .speficicSticker(rhsIndex) = rhs { - return lhsIndex < rhsIndex - } else { - return true - } - } - } -} - -struct ChatMediaInputGridEntry: Comparable, Identifiable { - - - let index: ChatMediaInputGridIndex - let file: TelegramMediaFile - let packInfo: ChatMediaGridPackHeaderInfo - let _stableId:ChatMediaInputGridEntryStableId - let collectionId:ChatMediaGridCollectionStableId - - var stableId: ChatMediaInputGridEntryStableId { - return _stableId //ChatMediaInputGridEntryStableId(collectionId: self.index.collectionId, itemId: self.stickerItem.index.id) - } - - static func ==(lhs: ChatMediaInputGridEntry, rhs: ChatMediaInputGridEntry) -> Bool { - return lhs.file.isEqual(rhs.file) && lhs.collectionId == rhs.collectionId - } - - static func <(lhs: ChatMediaInputGridEntry, rhs: ChatMediaInputGridEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(account: Account, inputNodeInteraction: EStickersInteraction) -> GridItem { - return StickerGridItem(account: account, collectionId: self.collectionId, packInfo: packInfo, index: self.index, file: self.file, inputNodeInteraction: inputNodeInteraction, selected: { }) - } -} diff --git a/Telegram-Mac/EStickerGridItem.swift b/Telegram-Mac/EStickerGridItem.swift deleted file mode 100644 index 66afbe1a47..0000000000 --- a/Telegram-Mac/EStickerGridItem.swift +++ /dev/null @@ -1,243 +0,0 @@ -// -// StickerGridItem.swift -// Telegram-Mac -// -// Created by keepcoder on 23/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac -import TGUIKit - -final class StickerGridSection: GridSection { - let collectionId: ChatMediaGridCollectionStableId - let height: CGFloat = 30 - let reference:StickerPackReference? - let packInfo: ChatMediaGridPackHeaderInfo - let inputInteraction:EStickersInteraction - var hashValue: Int { - return self.collectionId.hashValue - } - - init(collectionId: ChatMediaGridCollectionStableId, packInfo: ChatMediaGridPackHeaderInfo, inputInteraction: EStickersInteraction, reference: StickerPackReference?) { - self.packInfo = packInfo - self.collectionId = collectionId - self.reference = reference - self.inputInteraction = inputInteraction - } - - func isEqual(to: GridSection) -> Bool { - if let to = to as? StickerGridSection { - return self.collectionId == to.collectionId - } else { - return false - } - } - - func node() -> View { - return StickerGridSectionNode(collectionInfo: self) - } -} - - -final class StickerGridSectionNode: View { - var textView:TextView = TextView() - private let collectionInfo:StickerGridSection - init(collectionInfo: StickerGridSection) { - self.collectionInfo = collectionInfo - self.textView.userInteractionEnabled = false - super.init() - addSubview(textView) - updateLocalizationAndTheme() - } - override func updateLocalizationAndTheme() { - backgroundColor = theme.colors.background - textView.backgroundColor = theme.colors.background - let textLayout = TextViewLayout(.initialize(string: collectionInfo.packInfo.title.uppercased(), color: theme.colors.grayText, font: .medium(.title)), constrainedWidth: 300, maximumNumberOfLines: 1, truncationType: .end) - textLayout.measure() - textView.update(textLayout) - needsLayout = true - } - - override func layout() { - super.layout() - textView.centerY(x:10) - } - - override func mouseUp(with event: NSEvent) { - if mouseInside() && event.clickCount == 1, let reference = collectionInfo.reference { - self.collectionInfo.inputInteraction.previewStickerSet(reference) - } - } - - required init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} - -final class StickerGridItem: GridItem { - - let account: Account - let index: ChatMediaInputGridIndex - let file: TelegramMediaFile - let selected: () -> Void - let inputNodeInteraction: EStickersInteraction - let collectionId:ChatMediaGridCollectionStableId - let section: GridSection? - - init(account: Account, collectionId: ChatMediaGridCollectionStableId, packInfo: ChatMediaGridPackHeaderInfo, index: ChatMediaInputGridIndex, file: TelegramMediaFile, inputNodeInteraction: EStickersInteraction, selected: @escaping () -> Void) { - self.account = account - self.index = index - self.file = file - self.collectionId = collectionId - self.inputNodeInteraction = inputNodeInteraction - self.selected = selected - - - let reference: StickerPackReference? - switch packInfo { - case .recent: - reference = nil - case .pack: - reference = file.stickerReference - case .saved: - reference = nil - case .speficicPack: - reference = file.stickerReference - } - if collectionId != .saved { - self.section = StickerGridSection(collectionId: collectionId, packInfo: packInfo, inputInteraction: inputNodeInteraction, reference: reference) - } else { - self.section = nil - } - } - - func node(layout: GridNodeLayout, gridNode:GridNode) -> GridItemNode { - let node = StickerGridItemView(gridNode) - node.inputNodeInteraction = self.inputNodeInteraction - node.setup(account: self.account, file: self.file, collectionId: self.collectionId) - node.selected = self.selected - return node - } - - func update(node: GridItemNode) { - guard let node = node as? StickerGridItemView else { - assertionFailure() - return - } - node.setup(account: self.account, file: self.file, collectionId: self.collectionId) - node.selected = self.selected - } -} - -let eStickerSize:NSSize = NSMakeSize(80, 80) - - - -final class StickerGridItemView: GridItemNode, StickerPreviewRowViewProtocol { - private var currentState: (Account, TelegramMediaFile, CGSize, ChatMediaGridCollectionStableId?)? - - - private let imageView: TransformImageView - - func fileAtPoint(_ point: NSPoint) -> TelegramMediaFile? { - return currentState?.1 - } - - override func menu(for event: NSEvent) -> NSMenu? { - if let currentState = currentState, let state = currentState.3 { - let menu = NSMenu() - let file = currentState.1 - if state == .recent { - if let reference = file.stickerReference, case let .id(id, _) = reference { - menu.addItem(ContextMenuItem(tr(.contextViewStickerSet), handler: { [weak self] in - self?.inputNodeInteraction?.navigateToCollectionId(.pack(ItemCollectionId.init(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id))) - })) - } - } else if state == .saved, let mediaId = file.id { - menu.addItem(ContextMenuItem(tr(.contextRemoveFaveSticker), handler: { - _ = removeSavedSticker(postbox: currentState.0.postbox, mediaId: mediaId).start() - })) - } - - return menu - } - return nil - } - - private let stickerFetchedDisposable = MetaDisposable() - - var inputNodeInteraction: EStickersInteraction? - var selected: (() -> Void)? - - override init(_ grid:GridNode) { - imageView = TransformImageView() - super.init(grid) - layer?.cornerRadius = .cornerRadius - self.autohighlight = false - - set(handler: { [weak self] _ in - if let (_, file, _, _) = self?.currentState { - self?.inputNodeInteraction?.sendSticker(file) - } - }, for: .Click) - - - set(handler: { [weak self] (control) in - if let window = self?.window as? Window, let currentState = self?.currentState, let grid = self?.grid { - _ = startStickerPreviewHandle(grid, window: window, account: currentState.0) - } - }, for: .LongMouseDown) - set(background: theme.colors.background, for: .Normal) - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - imageView.center() - - } - - required init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - stickerFetchedDisposable.dispose() - } - - func setup(account: Account, file: TelegramMediaFile, collectionId: ChatMediaGridCollectionStableId? = nil) { - if let dimensions = file.dimensions { - addSubview(imageView) - - set(image: theme.icons.stickerBackgroundActive, for: .Highlight) - set(image: theme.icons.stickerBackground, for: .Normal) - set(background: theme.colors.background, for: .Normal) - set(background: theme.colors.background, for: .Hover) - - imageView.setSignal(account: account, signal: chatMessageSticker(account: account, file: file, type: .small, scale: backingScaleFactor)) - stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: file).start()) - - let imageSize = dimensions.aspectFitted(eStickerSize) - imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: eStickerSize, intrinsicInsets: NSEdgeInsets())) - - imageView.setFrameSize(imageSize) - currentState = (account, file, dimensions, collectionId) - return - } - imageView.removeFromSuperview() - } - - -} diff --git a/Telegram-Mac/EStickerPackEntries.swift b/Telegram-Mac/EStickerPackEntries.swift deleted file mode 100644 index 90149d3b73..0000000000 --- a/Telegram-Mac/EStickerPackEntries.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// StickerPackEntries.swift -// Telegram-Mac -// -// Created by keepcoder on 25/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa - -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac -import TGUIKit - - - - - -enum ChatMediaInputPanelEntry: Comparable, Identifiable { - case stickerPack(index:Int, stableId: ChatMediaGridCollectionStableId, info: StickerPackCollectionInfo, topItem: StickerPackItem?) - case recent - case saved - case specificPack(info: StickerPackCollectionInfo, peer: Peer) - var stableId: ChatMediaGridCollectionStableId { - switch self { - case let .stickerPack(data): - return data.stableId - case .recent: - return .recent - case .saved: - return .saved - case let .specificPack(info, _): - return .specificPack(info.id) - - } - } - - static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool { - switch lhs { - case let .stickerPack(lhsIndex, lhsStableId, lhsInfo, lhsTopItem): - if case let .stickerPack(rhsIndex, rhsStableId, rhsInfo, rhsTopItem) = rhs { - return lhsIndex == rhsIndex && lhsStableId == rhsStableId && lhsInfo == rhsInfo && lhsTopItem == rhsTopItem - } else { - return false - } - case .recent: - if case .recent = rhs { - return true - } else { - return false - } - case let .specificPack(lhsInfo, lhsPeer): - if case let .specificPack(rhsInfo, rhsPeer) = rhs { - return lhsInfo == rhsInfo && lhsPeer.isEqual(rhsPeer) - } else { - return false - } - case .saved: - if case .saved = rhs { - return true - } else { - return false - } - } - } - - static func <(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool { - switch lhs { - case let .stickerPack(lhsIndex, _, lhsInfo, _): - switch rhs { - case let .stickerPack(rhsIndex, _, rhsInfo, _): - if lhsIndex == rhsIndex { - return lhsInfo.id.id > rhsInfo.id.id - } else { - return lhsIndex > rhsIndex - } - default: - return true - } - case .recent: - switch rhs { - case .saved: - return true - default: - return false - } - case .specificPack: - switch rhs { - case .stickerPack: - return false - default: - return true - } - case .saved: - switch rhs { - case .saved: - return true - default: - return false - } - } - } - - -} diff --git a/Telegram-Mac/EStickerPackItem.swift b/Telegram-Mac/EStickerPackItem.swift deleted file mode 100644 index f194532582..0000000000 --- a/Telegram-Mac/EStickerPackItem.swift +++ /dev/null @@ -1,310 +0,0 @@ -// -// StickerPackItem.swift -// Telegram-Mac -// -// Created by keepcoder on 25/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - - - -class EStickerPackRowItem: TableRowItem { - - override var height:CGFloat { - return 40.0 - } - - var info:StickerPackCollectionInfo - var topItem:StickerPackItem? - var account:Account - var interaction:EStickersInteraction - private var packIndex:Int - - let _stableId:ChatMediaGridCollectionStableId - override var stableId:AnyHashable { - return _stableId - } - - init(_ initialSize:NSSize, _ account:Account, _ index:Int, _ stableId:ChatMediaGridCollectionStableId, _ info:StickerPackCollectionInfo, _ topItem:StickerPackItem?, _ interaction:EStickersInteraction) { - self.account = account - self._stableId = stableId - self.info = info - self.topItem = topItem - self.packIndex = index - self.interaction = interaction - super.init(initialSize) - } - - override func viewClass() -> AnyClass { - return EStickerPackRowView.self - } -} - -class ERecentPackRowItem: TableRowItem { - - override var height:CGFloat { - return 40.0 - } - var interaction:EStickersInteraction - - let _stableId:ChatMediaGridCollectionStableId - override var stableId:AnyHashable { - return _stableId - } - - init(_ initialSize:NSSize, _ stableId:ChatMediaGridCollectionStableId, _ interaction:EStickersInteraction) { - self._stableId = stableId - self.interaction = interaction - super.init(initialSize) - } - - override func viewClass() -> AnyClass { - return ERecentPackRowView.self - } -} - - -class EStickerPackRowView: HorizontalRowView { - - private let boundingSize = CGSize(width: 40.0, height: 40.0) - private let imageSize = CGSize(width: 30.0, height: 30.0) - - private let stickerFetchedDisposable = MetaDisposable() - - var imageView:TransformImageView = TransformImageView() - - var overlay:OverlayControl = OverlayControl() - - required init(frame frameRect:NSRect) { - super.init(frame:frameRect) - - overlay.frame = NSMakeRect(2.0, 2.0, bounds.width - 4.0, bounds.height - 4.0) - overlay.layer?.cornerRadius = .cornerRadius - addSubview(overlay) - - - imageView.frame = self.bounds - addSubview(imageView) - - overlay.set(handler: { [weak self] _ in - - if let item = self?.item as? EStickerPackRowItem { - item.interaction.navigateToCollectionId(item._stableId) - } - - }, for: .Click) - } - - override func layout() { - super.layout() - - imageView.center() - - overlay.setFrameSize(38, 38) - overlay.layer?.cornerRadius = .cornerRadius - overlay.center() - } - - - deinit { - stickerFetchedDisposable.dispose() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func set(item:TableRowItem, animated:Bool = false) { - - var mediaUpdated = true - if let lhs = (self.item as? EStickerPackRowItem)?.topItem, let rhs = (item as? EStickerPackRowItem)?.topItem { - mediaUpdated = !lhs.file.isEqual(rhs.file) - } - - super.set(item: item, animated: animated) - overlay.set(background: theme.colors.grayBackground, for: .Highlight) - overlay.set(background: theme.colors.background, for: .Normal) - - overlay.isSelected = item.isSelected - - if let item = item as? EStickerPackRowItem, mediaUpdated { - if let topItem = item.topItem, let dimensions = topItem.file.dimensions { - imageView.setSignal(account: item.account, signal: chatMessageSticker(account: item.account, file: topItem.file, type: .thumb, scale: backingScaleFactor)) - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize:dimensions.aspectFitted(imageSize), boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) - imageView.set(arguments:arguments) - imageView.setFrameSize(arguments.imageSize) - _ = fileInteractiveFetched(account: item.account, file: topItem.file).start() - } - self.needsLayout = true - } - - } - -} - - - -class ERecentPackRowView: HorizontalRowView { - - private let boundingSize = CGSize(width: 40.0, height: 40.0) - private let imageSize = CGSize(width: 30.0, height: 30.0) - - - var imageView:ImageView = ImageView() - - var overlay:OverlayControl = OverlayControl() - - required init(frame frameRect:NSRect) { - super.init(frame:frameRect) - - overlay.frame = NSMakeRect(2.0, 2.0, bounds.width - 4.0, bounds.height - 4.0) - overlay.layer?.cornerRadius = .cornerRadius - - addSubview(overlay) - - addSubview(imageView) - - overlay.set(handler: { [weak self] _ in - if let item = self?.item as? ERecentPackRowItem { - item.interaction.navigateToCollectionId(item._stableId) - } - }, for: .Click) - } - - override func layout() { - super.layout() - - imageView.center() - - overlay.setFrameSize(38, 38) - overlay.layer?.cornerRadius = .cornerRadius - overlay.center() - } - - - deinit { - - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func set(item:TableRowItem, animated:Bool = false) { - - super.set(item: item, animated: animated) - overlay.isSelected = item.isSelected - overlay.set(background: theme.colors.background, for: .Normal) - overlay.set(background: theme.colors.grayBackground, for: .Highlight) - - if let item = item as? ERecentPackRowItem { - self.needsLayout = true - switch item._stableId { - case .saved: - imageView.image = theme.icons.stickersTabFave - case .recent: - imageView.image = theme.icons.stickersTabRecent - default: - break - } - imageView.sizeToFit() - - } - - } - -} - - - -class EStickerSpecificPackItem: TableRowItem { - override var height:CGFloat { - return 40.0 - } - let interaction:EStickersInteraction - fileprivate let specificPack: (StickerPackCollectionInfo, Peer) - fileprivate let account: Account - let _stableId:ChatMediaGridCollectionStableId - override var stableId:AnyHashable { - return _stableId - } - - init(_ initialSize:NSSize, _ stableId:ChatMediaGridCollectionStableId, specificPack: (StickerPackCollectionInfo, Peer), account: Account, _ interaction:EStickersInteraction) { - self._stableId = stableId - self.interaction = interaction - self.specificPack = specificPack - self.account = account - super.init(initialSize) - } - - override func viewClass() -> AnyClass { - return EStickerSpecificPackView.self - } -} - -class EStickerSpecificPackView: HorizontalRowView { - - private let boundingSize = CGSize(width: 40.0, height: 40.0) - private let imageSize = CGSize(width: 30.0, height: 30.0) - - - var imageView:AvatarControl = AvatarControl(font: .medium(.short)) - - var overlay:OverlayControl = OverlayControl() - - required init(frame frameRect:NSRect) { - super.init(frame:frameRect) - - overlay.frame = NSMakeRect(2.0, 2.0, bounds.width - 4.0, bounds.height - 4.0) - overlay.layer?.cornerRadius = .cornerRadius - - addSubview(overlay) - - addSubview(imageView) - imageView.setFrameSize(30, 30) - imageView.set(handler: { [weak self] _ in - if let item = self?.item as? EStickerSpecificPackItem { - item.interaction.navigateToCollectionId(item._stableId) - } - }, for: .Click) - } - - override func layout() { - super.layout() - - imageView.center() - - overlay.setFrameSize(38, 38) - overlay.layer?.cornerRadius = .cornerRadius - overlay.center() - } - - - deinit { - - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func set(item:TableRowItem, animated:Bool = false) { - - super.set(item: item, animated: animated) - overlay.isSelected = item.isSelected - overlay.set(background: theme.colors.background, for: .Normal) - overlay.set(background: theme.colors.grayBackground, for: .Highlight) - if let item = item as? EStickerSpecificPackItem { - imageView.setPeer(account: item.account, peer: item.specificPack.1) - } - - } - -} - diff --git a/Telegram-Mac/EStickersViewController.swift b/Telegram-Mac/EStickersViewController.swift deleted file mode 100644 index 43b7631a6c..0000000000 --- a/Telegram-Mac/EStickersViewController.swift +++ /dev/null @@ -1,538 +0,0 @@ -// -// StickersViewController.swift -// Telegram-Mac -// -// Created by keepcoder on 17/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - - -private struct ChatMediaInputGridTransition { - let deletions: [Int] - let insertions: [GridNodeInsertItem] - let updates: [GridNodeUpdateItem] - let updateFirstIndexInSectionOffset: Int? - let stationaryItems: GridNodeStationaryItems - let scrollToItem: GridNodeScrollToItem? - let animated: Bool -} - - - -private func preparedChatMediaInputGridEntryTransition(account: Account, from fromEntries: [AppearanceWrapperEntry], to toEntries: [AppearanceWrapperEntry], update: StickerPacksCollectionUpdate, inputNodeInteraction: EStickersInteraction) -> ChatMediaInputGridTransition { - var stationaryItems: GridNodeStationaryItems = .none - var scrollToItem: GridNodeScrollToItem? - var animated: Bool = false - switch update { - case .generic: - animated = true - case .scroll: - var fromStableIds = Set() - for entry in fromEntries { - fromStableIds.insert(entry.entry.stableId) - } - var index = 0 - var indices = Set() - for entry in toEntries { - if fromStableIds.contains(entry.entry.stableId) { - indices.insert(index) - } - index += 1 - } - stationaryItems = .indices(indices) - case let .navigate(index): - for i in 0 ..< toEntries.count { - if toEntries[i].entry.index >= index { - var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up - if !fromEntries.isEmpty && fromEntries[0].entry.index < toEntries[i].entry.index { - directionHint = .down - } - scrollToItem = GridNodeScrollToItem(index: i, position: .top, transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true) - break - } - } - } - - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices - let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.entry.item(account: account, inputNodeInteraction: inputNodeInteraction), previousIndex: $0.2) } - let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.entry.item(account: account, inputNodeInteraction: inputNodeInteraction)) } - - var firstIndexInSectionOffset = 0 - if !toEntries.isEmpty { - firstIndexInSectionOffset = Int(toEntries[0].entry.index.hashValue) - } - - return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem:scrollToItem, animated: animated) -} - -fileprivate func preparePackEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, stickersInteraction:EStickersInteraction) -> TableUpdateTransition { - - let (deleted,inserted,updated) = proccessEntries(from, right: to, { (entry) -> TableRowItem in - switch entry.entry { - case let .stickerPack(index, stableId, info, topItem): - return EStickerPackRowItem(initialSize, account, index, stableId, info, topItem, stickersInteraction) - case .recent: - return ERecentPackRowItem(initialSize, entry.entry.stableId, stickersInteraction) - case .saved: - return ERecentPackRowItem(initialSize, entry.entry.stableId, stickersInteraction) - case let .specificPack(info, peer): - return EStickerSpecificPackItem(initialSize, entry.entry.stableId, specificPack: (info, peer), account: account, stickersInteraction) - } - }) - - return TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:true, state: .none(nil)) - -} - -private func chatMediaInputPanelEntries(view: ItemCollectionsView, orderedItemListViews:[OrderedItemListView], specificPack:(StickerPackCollectionInfo?, Peer?)) -> [ChatMediaInputPanelEntry] { - var entries: [ChatMediaInputPanelEntry] = [] - var index = 0 -// - if !orderedItemListViews[1].items.isEmpty { - entries.append(.saved) - } - - if !orderedItemListViews[0].items.isEmpty { - entries.append(.recent) - } - - if let info = specificPack.0, let peer = specificPack.1 { - entries.append(.specificPack(info: info, peer: peer)) - } - - for (_, info, item) in view.collectionInfos { - if let info = info as? StickerPackCollectionInfo { - entries.append(.stickerPack(index: index, stableId: .pack(info.id), info: info, topItem: item as? StickerPackItem)) - index += 1 - } - } - entries.sort(by: <) - return entries -} - -private func chatMediaInputGridEntries(view: ItemCollectionsView, orderedItemListViews:[OrderedItemListView], specificPack:(StickerPackCollectionInfo, [ItemCollectionItem])?) -> [ChatMediaInputGridEntry] { - var entries: [ChatMediaInputGridEntry] = [] - - var stickerPackInfos: [ItemCollectionId: StickerPackCollectionInfo] = [:] - for (id, info, _) in view.collectionInfos { - if let info = info as? StickerPackCollectionInfo { - stickerPackInfos[id] = info - } - } - - var fileIds:[MediaId: MediaId] = [:] - - var j:Int = 0 - for item in orderedItemListViews[1].items { - if let entry = item.contents as? SavedStickerItem { - if let id = entry.file.id, fileIds[id] == nil { - fileIds[id] = id - entries.append(ChatMediaInputGridEntry(index: .saved(j), file: entry.file, packInfo: .saved, _stableId: .saved(entry.file), collectionId: .saved)) - j += 1 - } - - } - } - - var i:Int = 0 - for item in orderedItemListViews[0].items { - if let entry = item.contents as? RecentMediaItem { - if let file = entry.media as? TelegramMediaFile, let id = file.id, fileIds[id] == nil { - fileIds[id] = id - entries.append(ChatMediaInputGridEntry(index: .recent(i), file: file, packInfo: .recent, _stableId: .recent(file), collectionId: .recent)) - i += 1 - } - - } - } - - if let specificPack = specificPack { - for entry in specificPack.1 { - if let item = entry as? StickerPackItem { - entries.append(ChatMediaInputGridEntry(index: .speficicSticker(entry.index), file: item.file, packInfo: .speficicPack(specificPack.0), _stableId: .speficicSticker(specificPack.0.id, entry.index.id), collectionId: .specificPack(specificPack.0.id))) - } - } - } - - for entry in view.entries { - if let item = entry.item as? StickerPackItem { - entries.append(ChatMediaInputGridEntry(index: .sticker(entry.index), file: item.file, packInfo: .pack(stickerPackInfos[entry.index.collectionId]), _stableId: .sticker(entry.index.collectionId, entry.index.itemIndex.id), collectionId: .pack(entry.index.collectionId))) - } - } - return entries -} - -private enum StickerPacksCollectionPosition: Equatable { - case initial - case scroll(aroundIndex: ChatMediaInputGridIndex) - case navigate(index: ChatMediaInputGridIndex) - - static func ==(lhs: StickerPacksCollectionPosition, rhs: StickerPacksCollectionPosition) -> Bool { - switch lhs { - case .initial: - if case .initial = rhs { - return true - } else { - return false - } - case let .scroll(aroundIndex): - if case .scroll(aroundIndex) = rhs { - return true - } else { - return false - } - case .navigate: - return false - } - } -} - -private enum StickerPacksCollectionUpdate { - case generic - case scroll - case navigate(ChatMediaInputGridIndex) -} - -final class EStickersInteraction { - let navigateToCollectionId: (ChatMediaGridCollectionStableId) -> Void - - let sendSticker:(TelegramMediaFile) -> Void - let previewStickerSet:(StickerPackReference) -> Void - - var highlightedItemCollectionId: ChatMediaGridCollectionStableId? - - init(navigateToCollectionId: @escaping (ChatMediaGridCollectionStableId) -> Void, sendSticker: @escaping(TelegramMediaFile)-> Void, previewStickerSet: @escaping(StickerPackReference)-> Void) { - self.navigateToCollectionId = navigateToCollectionId - self.sendSticker = sendSticker - self.previewStickerSet = previewStickerSet - } -} - - -class StickersControllerView : View { - fileprivate var gridView:GridNode - fileprivate var packsTable:HorizontalTableView - private var separator:View! - fileprivate var restrictedView:RestrictionWrappedView? - required init(frame frameRect: NSRect) { - self.gridView = GridNode(frame:NSZeroRect) - self.packsTable = HorizontalTableView(frame: NSZeroRect) - separator = View(frame: NSMakeRect(0,0,frameRect.width,.borderSize)) - separator.backgroundColor = .border - - super.init(frame: frameRect) - - addSubview(gridView) - addSubview(packsTable) - addSubview(separator) - updateLocalizationAndTheme() - } - - func updateRestricion(_ peer: Peer?) { - if let peer = peer as? TelegramChannel { - if peer.stickersRestricted, let bannedRights = peer.bannedRights { - restrictedView = RestrictionWrappedView(bannedRights.untilDate != .max ? tr(.channelPersmissionDeniedSendStickersUntil(bannedRights.formattedUntilDate)) : tr(.channelPersmissionDeniedSendStickersForever)) - addSubview(restrictedView!) - } else { - restrictedView?.removeFromSuperview() - restrictedView = nil - } - } else { - restrictedView?.removeFromSuperview() - restrictedView = nil - } - setFrameSize(frame.size) - needsLayout = true - } - - override func updateLocalizationAndTheme() { - self.restrictedView?.updateLocalizationAndTheme() - self.separator.backgroundColor = theme.colors.border - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - gridView.setFrameSize(frame.width, frame.height - 50) - packsTable.setFrameSize(frame.width - 6.0, 49) - separator.setFrameSize(frame.width, .borderSize) - restrictedView?.setFrameSize(newSize) - } - - override func layout() { - super.layout() - packsTable.setFrameOrigin(3, frame.height - 50) - separator.setFrameOrigin(0, gridView.frame.maxY) - } - - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class StickersViewController: GenericViewController, TableViewDelegate, Notifable { - - private var interactions:EntertainmentInteractions? - private var chatInteraction:ChatInteraction? - private var account:Account - - private let peerIdPromise: ValuePromise = ValuePromise(ignoreRepeated: true) - - private let itemCollectionsViewPosition = Promise() - private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition? - private var currentView: ItemCollectionsView? - - private(set) var inputNodeInteraction: EStickersInteraction! - private let disposable = MetaDisposable() - - - func isSelectable(row: Int, item: TableRowItem) -> Bool { - return true - } -// - func selectionWillChange(row: Int, item: TableRowItem) -> Bool { - return true - } -// - func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) { - - } - - func update(with interactions:EntertainmentInteractions, chatInteraction: ChatInteraction) { - self.interactions = interactions - self.chatInteraction?.remove(observer: self) - self.chatInteraction = chatInteraction - self.peerIdPromise.set(chatInteraction.peerId) - chatInteraction.add(observer: self) - if isLoaded() { - genericView.updateRestricion(chatInteraction.presentation.peer) - } - } - - func notify(with value: Any, oldValue: Any, animated: Bool) { - if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState, let peer = value.peer, let oldPeer = oldValue.peer { - if peer.stickersRestricted != oldPeer.stickersRestricted { - genericView.updateRestricion(peer) - } - } - } - - override func updateLocalizationAndTheme() { - genericView.updateLocalizationAndTheme() - } - - func isEqual(to other: Notifable) -> Bool { - return other === self - } - - - init(account:Account) { - self.account = account - super.init() - self.bar = NavigationBarStyle(height: 0) - - self.inputNodeInteraction = EStickersInteraction(navigateToCollectionId: { [weak self] collectionId in - if let strongSelf = self, let currentView = strongSelf.currentView, collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId { - switch collectionId { - case .pack(let itemCollectionId): - var index: Int32 = 0 - for (id, _, _) in currentView.collectionInfos { - if id == itemCollectionId { - let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id) - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: .sticker(itemIndex)))) - return - } - index += 1 - } - case .saved: - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: .saved(0)))) - case .recent: - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: .recent(0)))) - case .specificPack: - strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: .speficicSticker(ItemCollectionItemIndex(index: 0, id: 0))))) - } - - - } - }, sendSticker: { [weak self] file in - self?.interactions?.sendSticker(file) - }, previewStickerSet: { [weak self] reference in - if let eInteraction = self?.interactions, let account = self?.account { - self?.account.context.entertainment.popover?.hide() - showModal(with: StickersPackPreviewModalController(account, peerId: eInteraction.peerId, reference: reference), for: mainWindow) - } - }) - - - } - - deinit { - disposable.dispose() - chatInteraction?.remove(observer: self) - } - - override func viewDidResized(_ size: NSSize) { - super.viewDidResized(size) - let layout = GridNodeLayout(size: CGSize(width: frame.width, height: frame.height - 50), insets: NSEdgeInsets(left: 10, right: 10, top: 10), preloadSize: size.height, type: .fixed(itemSize: CGSize(width: 80, height: 80), lineSpacing: 0)) - let updateLayout = GridNodeUpdateLayout(layout: layout, transition: .immediate) - - self.genericView.gridView.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - } - - override func viewDidLoad() { - super.viewDidLoad() - if let chatInteraction = chatInteraction { - genericView.updateRestricion(chatInteraction.presentation.peer) - } - let account = self.account - genericView.packsTable.delegate = self - - - let itemCollectionsView = itemCollectionsViewPosition.get() |> distinctUntilChanged - |> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in - - switch position { - case .initial: - return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50) - |> map { view in - return (view, .generic) - } - case let .scroll(aroundIndex): - var firstTime = true - - return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex.packIndex, count: 200) - |> map { view in - let update: StickerPacksCollectionUpdate - if firstTime { - firstTime = false - update = .scroll - } else { - update = .generic - } - return (view, update) - } - case let .navigate(index): - var firstTime = true - return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index.packIndex, count: 140) - |> map { view in - let update: StickerPacksCollectionUpdate - if firstTime { - firstTime = false - update = .navigate(index) - } else { - update = .generic - } - return (view, update) - } - } - } - - let previousEntries = Atomic<([AppearanceWrapperEntry],[AppearanceWrapperEntry])>(value: ([],[])) - - let inputNodeInteraction = self.inputNodeInteraction! - let initialSize = atomicSize - - let transitions = combineLatest(itemCollectionsView |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue, peerIdPromise.get() |> mapToSignal { - - combineLatest(account.viewTracker.peerView($0) |> take(1) |> map {peerViewMainPeer($0)}, peerSpecificStickerPack(postbox: account.postbox, network: account.network, peerId: $0)) - - } |> deliverOnPrepareQueue) - |> map { itemsView, appearance, specificData -> (ItemCollectionsView, TableUpdateTransition, Bool, ChatMediaInputGridTransition, Bool) in - - let update: StickerPacksCollectionUpdate = itemsView.1 - - let gridEntries = chatMediaInputGridEntries(view: itemsView.0, orderedItemListViews: itemsView.0.orderedItemListsViews, specificPack: specificData.1) - let panelEntries = chatMediaInputPanelEntries(view: itemsView.0, orderedItemListViews: itemsView.0.orderedItemListsViews, specificPack: (specificData.1?.0, specificData.0)) - - let panelEntriesMapped = panelEntries.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - let gridEntriesMapped = gridEntries.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - - let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntriesMapped, gridEntriesMapped)) - - return (itemsView.0, preparePackEntries(from: previousPanelEntries, to: panelEntriesMapped, account: account, initialSize: initialSize.modify({$0}), stickersInteraction:inputNodeInteraction),previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: account, from: previousGridEntries, to: gridEntriesMapped, update: update, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty) - } - - self.disposable.set((transitions |> deliverOnMainQueue).start(next: { [weak self] (view, packsTransition, packsFirstTime, gridTransition, gridFirstTime) in - if let strongSelf = self { - - strongSelf.currentView = view - strongSelf.genericView.packsTable.merge(with: packsTransition) - strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) - - if packsFirstTime { - strongSelf.readyOnce() - if !strongSelf.genericView.packsTable.isEmpty { - let stableId = strongSelf.genericView.packsTable.item(at: 0).stableId - strongSelf.genericView.packsTable.changeSelection(stableId: stableId) - } - } - } - })) - - genericView.gridView.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self { - if let topVisible = visibleItems.topVisible { - if let item = topVisible.1 as? StickerGridItem { - let collectionId = item.collectionId - if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId { - strongSelf.inputNodeInteraction.highlightedItemCollectionId = collectionId - strongSelf.genericView.packsTable.scroll(to: .center(id: collectionId, animated: true, focus: false, inset: 0)) - strongSelf.genericView.packsTable.changeSelection(stableId: collectionId) - } - } - } - - if let currentView = strongSelf.currentView, let (topIndex, _) = visibleItems.top, let (bottomIndex, _) = visibleItems.bottom { - if topIndex <= 5, let lower = currentView.lower { - let position: StickerPacksCollectionPosition = .scroll(aroundIndex: .sticker(lower.index)) - if strongSelf.currentStickerPacksCollectionPosition != position { - strongSelf.currentStickerPacksCollectionPosition = position - strongSelf.itemCollectionsViewPosition.set(.single(position)) - } - } else if bottomIndex >= visibleItems.count - 5, let higher = currentView.higher { - let position: StickerPacksCollectionPosition = .scroll(aroundIndex: .sticker(higher.index)) - if strongSelf.currentStickerPacksCollectionPosition != position { - strongSelf.currentStickerPacksCollectionPosition = position - strongSelf.itemCollectionsViewPosition.set(.single(position)) - } - } - } - } - } - - self.currentStickerPacksCollectionPosition = .initial - self.itemCollectionsViewPosition.set(.single(.initial)) - - } - - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - genericView.packsTable.clipView.scroll(to: NSZeroPoint) - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - - private func enqueueGridTransition(_ transition: ChatMediaInputGridTransition, firstTime: Bool) { - genericView.gridView.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in }) - } - -} diff --git a/Telegram-Mac/ETabRowItem.swift b/Telegram-Mac/ETabRowItem.swift index 47047c8bd9..f15eb7675e 100644 --- a/Telegram-Mac/ETabRowItem.swift +++ b/Telegram-Mac/ETabRowItem.swift @@ -29,6 +29,10 @@ class ETabRowItem: TableRowItem { return _height } + override var width: CGFloat { + return _height + } + init(_ initialSize:NSSize, icon:CGImage, iconSelected:CGImage, stableId:AnyHashable, width:CGFloat, clickHandler:@escaping(AnyHashable)->Void) { self.icon = icon self.iconSelected = iconSelected diff --git a/Telegram-Mac/ETabRowView.swift b/Telegram-Mac/ETabRowView.swift index af29278fe8..6675ab7e95 100644 --- a/Telegram-Mac/ETabRowView.swift +++ b/Telegram-Mac/ETabRowView.swift @@ -48,9 +48,10 @@ class ETabRowView: HorizontalRowView { override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated:animated) if let item = item as? ETabRowItem { - overlay.style = ControlStyle(highlightColor: theme.colors.blueIcon) + overlay.style = ControlStyle(highlightColor: theme.colors.accentIcon) overlay.set(image: item.icon, for: .Normal) - overlay.sizeToFit() + overlay.disableActions() + _ = overlay.frame = bounds overlay.isSelected = item.isSelected overlay.set(background: theme.colors.background, for: .Normal) } diff --git a/Telegram-Mac/EditAccountInfoController.swift b/Telegram-Mac/EditAccountInfoController.swift new file mode 100644 index 0000000000..8019678562 --- /dev/null +++ b/Telegram-Mac/EditAccountInfoController.swift @@ -0,0 +1,460 @@ +// +// EditAccountInfoController.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +enum EditSettingsEntryTag: ItemListItemTag { + case bio + + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? EditSettingsEntryTag, self == other { + return true + } else { + return false + } + } + var stableId: InputDataEntryId { + switch self { + case .bio: + return .input(_id_about) + } + } +} + + +private func valuesRequiringUpdate(state: EditInfoState, view: PeerView) -> ((fn: String, ln: String)?, about: String?) { + if let peer = view.peers[view.peerId] as? TelegramUser { + var names:(String, String)? = nil + if state.firstName != peer.firstName || state.lastName != peer.lastName { + names = (state.firstName, state.lastName) + } + var about: String? = nil + + if let cachedData = view.cachedData as? CachedUserData { + if state.about != (cachedData.about ?? "") { + about = state.about + } + } + + return (names, about) + } + return (nil, nil) +} + +private final class EditInfoControllerArguments { + let context: AccountContext + let uploadNewPhoto:()->Void + let logout:()->Void + let username:()->Void + let changeNumber:()->Void + let addAccount: ()->Void + init(context: AccountContext, uploadNewPhoto:@escaping()->Void, logout:@escaping()->Void, username: @escaping()->Void, changeNumber:@escaping()->Void, addAccount: @escaping() -> Void) { + self.context = context + self.logout = logout + self.username = username + self.changeNumber = changeNumber + self.uploadNewPhoto = uploadNewPhoto + self.addAccount = addAccount + } +} +struct EditInfoState : Equatable { + static func == (lhs: EditInfoState, rhs: EditInfoState) -> Bool { + + if let lhsPeer = lhs.peer, let rhsPeer = rhs.peer { + if !lhsPeer.isEqual(rhsPeer) { + return false + } + } else if (lhs.peer != nil) != (rhs.peer != nil) { + return false + } + + return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.username == rhs.username && lhs.phone == rhs.phone && lhs.representation == rhs.representation && lhs.updatingPhotoState == rhs.updatingPhotoState && lhs.stateInited == rhs.stateInited && lhs.peerStatusSettings == rhs.peerStatusSettings + } + + let firstName: String + let lastName: String + let about: String + let username: String? + let phone: String? + let representation:TelegramMediaImageRepresentation? + let updatingPhotoState: PeerInfoUpdatingPhotoState? + let stateInited: Bool + let peer: Peer? + let peerStatusSettings: PeerStatusSettings? + let addToException: Bool + init(stateInited: Bool = false, firstName: String = "", lastName: String = "", about: String = "", username: String? = nil, phone: String? = nil, representation: TelegramMediaImageRepresentation? = nil, updatingPhotoState: PeerInfoUpdatingPhotoState? = nil, peer: Peer? = nil, peerStatusSettings: PeerStatusSettings? = nil, addToException: Bool = true) { + self.firstName = firstName + self.lastName = lastName + self.about = about + self.username = username + self.phone = phone + self.representation = representation + self.updatingPhotoState = updatingPhotoState + self.stateInited = stateInited + self.peer = peer + self.peerStatusSettings = peerStatusSettings + self.addToException = addToException + } + + init(_ peerView: PeerView) { + let peer = peerView.peers[peerView.peerId] as? TelegramUser + self.peer = peer + self.firstName = peer?.firstName ?? "" + self.lastName = peer?.lastName ?? "" + self.username = peer?.username + self.phone = peer?.phone + self.about = (peerView.cachedData as? CachedUserData)?.about ?? "" + self.representation = peer?.smallProfileImage + self.updatingPhotoState = nil + self.stateInited = true + self.peerStatusSettings = (peerView.cachedData as? CachedUserData)?.peerStatusSettings + self.addToException = true + } + + func withUpdatedInited(_ stateInited: Bool) -> EditInfoState { + return EditInfoState(stateInited: stateInited, firstName: self.firstName, lastName: self.lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: self.updatingPhotoState, peer: self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + func withUpdatedAbout(_ about: String) -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: self.firstName, lastName: self.lastName, about: about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: self.updatingPhotoState, peer: self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + + + func withUpdatedFirstName(_ firstName: String) -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: firstName, lastName: self.lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: self.updatingPhotoState, peer: self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + func withUpdatedLastName(_ lastName: String) -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: self.firstName, lastName: lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: self.updatingPhotoState, peer: self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + + func withUpdatedPeerView(_ peerView: PeerView) -> EditInfoState { + let peer = peerView.peers[peerView.peerId] as? TelegramUser + let about = stateInited ? self.about : (peerView.cachedData as? CachedUserData)?.about ?? self.about + let peerStatusSettings = (peerView.cachedData as? CachedUserData)?.peerStatusSettings + return EditInfoState(stateInited: true, firstName: stateInited ? self.firstName : peer?.firstName ?? self.firstName, lastName: stateInited ? self.lastName : peer?.lastName ?? self.lastName, about: about, username: peer?.username, phone: peer?.phone, representation: peer?.smallProfileImage, updatingPhotoState: self.updatingPhotoState, peer: peer, peerStatusSettings: peerStatusSettings, addToException: self.addToException) + } + func withUpdatedUpdatingPhotoState(_ f: (PeerInfoUpdatingPhotoState?) -> PeerInfoUpdatingPhotoState?) -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: self.firstName, lastName: self.lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: f(self.updatingPhotoState), peer: self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + func withoutUpdatingPhotoState() -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: self.firstName, lastName: self.lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: nil, peer:self.peer, peerStatusSettings: self.peerStatusSettings, addToException: self.addToException) + } + + func withUpdatedAddToException(_ addToException: Bool) -> EditInfoState { + return EditInfoState(stateInited: self.stateInited, firstName: self.firstName, lastName: self.lastName, about: self.about, username: self.username, phone: self.phone, representation: self.representation, updatingPhotoState: self.updatingPhotoState, peer:self.peer, peerStatusSettings: self.peerStatusSettings, addToException: addToException) + } +} + +private let _id_info = InputDataIdentifier("_id_info") +private let _id_about = InputDataIdentifier("_id_about") +private let _id_username = InputDataIdentifier("_id_username") +private let _id_phone = InputDataIdentifier("_id_phone") +private let _id_logout = InputDataIdentifier("_id_logout") +private let _id_add_account = InputDataIdentifier("_id_add_account") + +private func editInfoEntries(state: EditInfoState, arguments: EditInfoControllerArguments, activeAccounts: [AccountWithInfo], updateState:@escaping ((EditInfoState)->EditInfoState)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_info, equatable: InputDataEquatable(state), item: { size, stableId -> TableRowItem in + return EditAccountInfoItem(size, stableId: stableId, account: arguments.context.account, state: state, viewType: .singleItem, updateText: { firstName, lastName in + updateState { current in + return current.withUpdatedFirstName(firstName).withUpdatedLastName(lastName).withUpdatedInited(true) + } + }, uploadNewPhoto: { + arguments.uploadNewPhoto() + }) + })) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.editAccountNameDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.bioHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.about), error: nil, identifier: _id_about, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.bioPlaceholder, filter: {$0}, limit: 70)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.bioDescription), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_username, data: InputDataGeneralData(name: L10n.editAccountUsername, color: theme.colors.text, icon: nil, type: .nextContext(state.username != nil ? "@\(state.username!)" : ""), viewType: .firstItem, action: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_phone, data: InputDataGeneralData(name: L10n.editAccountChangeNumber, color: theme.colors.text, icon: nil, type: .nextContext(state.phone != nil ? formatPhoneNumber(state.phone!) : ""), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + if activeAccounts.count < 3 { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_add_account, data: InputDataGeneralData(name: L10n.editAccountAddAccount, color: theme.colors.accent, icon: nil, type: .none, viewType: .firstItem, action: { + arguments.addAccount() + }))) + index += 1 + } + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_logout, data: InputDataGeneralData(name: L10n.editAccountLogout, color: theme.colors.redUI, icon: nil, type: .none, viewType: activeAccounts.count < 3 ? .lastItem : .singleItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + + +func EditAccountInfoController(context: AccountContext, focusOnItemTag: EditSettingsEntryTag? = nil, f: @escaping((ViewController)) -> Void) -> Void { + + let state: Promise = Promise() + let stateValue: Atomic = Atomic(value: EditInfoState()) + let actionsDisposable = DisposableSet() + let photoDisposable = MetaDisposable() + let peerDisposable = MetaDisposable() + let logoutDisposable = MetaDisposable() + let updateNameDisposable = MetaDisposable() + + actionsDisposable.add(photoDisposable) + actionsDisposable.add(peerDisposable) + actionsDisposable.add(logoutDisposable) + actionsDisposable.add(updateNameDisposable) + let updateState:((EditInfoState)->EditInfoState)->Void = { f in + state.set(.single(stateValue.modify(f))) + } + + var peerView:PeerView? = nil + + peerDisposable.set((context.account.postbox.peerView(id: context.peerId) |> deliverOnMainQueue).start(next: { pv in + peerView = pv + updateState { current in + return current.withUpdatedPeerView(pv) + } + })) + + let peerId = context.peerId + + let cancel = { + photoDisposable.set(nil) + updateState { state -> EditInfoState in + return state.withoutUpdatingPhotoState() + } + } + + var close:(()->Void)? = nil + + let updatePhoto:(NSImage)->Void = { image in + + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: context.window, animationType: .scaleCenter) + + let updateSignal = controller.result |> map { path, _ -> TelegramMediaResource in + return LocalFileReferenceMediaResource(localFilePath: path.path, randomId: arc4random64()) + } |> beforeNext { resource in + updateState { state -> EditInfoState in + return state.withUpdatedUpdatingPhotoState { _ in + return PeerInfoUpdatingPhotoState(progress: 0, cancel: cancel) + } + } + } |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { resource -> Signal in + return updateAccountPhoto(account: context.account, resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) + } |> deliverOnMainQueue + + + + photoDisposable.set(updateSignal.start(next: { status in + updateState { state -> EditInfoState in + switch status { + case .complete: + return state.withoutUpdatingPhotoState() + case let .progress(progress): + return state.withUpdatedUpdatingPhotoState { current -> PeerInfoUpdatingPhotoState? in + return current?.withUpdatedProgress(progress) + } + } + } + }, error: { error in + updateState { state in + return state.withoutUpdatingPhotoState() + } + }, completed: { + updateState { state -> EditInfoState in + return state.withoutUpdatingPhotoState() + } + })) + + controller.onClose = { + removeFile(at: path) + } + }) + } + + let updateVideo:(Signal) -> Void = { signal in + let updateSignal: Signal = signal + |> mapError { _ in return UploadPeerPhotoError.generic } + |> mapToSignal { state in + switch state { + case .error: + return .fail(.generic) + case let .start(path): + updateState { (state) -> EditInfoState in + return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in + return PeerInfoUpdatingPhotoState(progress: 0, image: NSImage(contentsOfFile: path)?._cgImage, cancel: cancel) + } + } + return .next(.progress(0)) + case let .progress(value): + return .next(.progress(value * 0.2)) + case let .complete(thumb, video, keyFrame): + let (thumbResource, videoResource) = (LocalFileReferenceMediaResource(localFilePath: thumb, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true), + LocalFileReferenceMediaResource(localFilePath: video, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true)) + + return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: thumbResource), video: uploadedPeerVideo(postbox: context.account.postbox, network: context.account.network, messageMediaPreuploadManager: nil, resource: videoResource) |> map(Optional.init), videoStartTimestamp: keyFrame, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> map { result in + switch result { + case let .progress(current): + return .progress(0.2 + (current * 0.8)) + default: + return result + } + } + } + } + photoDisposable.set(updateSignal.start(next: { status in + updateState { state -> EditInfoState in + switch status { + case .complete: + return state.withoutUpdatingPhotoState() + case let .progress(progress): + return state.withUpdatedUpdatingPhotoState { current -> PeerInfoUpdatingPhotoState? in + return current?.withUpdatedProgress(progress) + } + } + } + }, error: { error in + updateState { state in + return state.withoutUpdatingPhotoState() + } + }, completed: { + updateState { state -> EditInfoState in + return state.withoutUpdatingPhotoState() + } + })) + } + + let arguments = EditInfoControllerArguments(context: context, uploadNewPhoto: { + + filePanel(with: photoExts + videoExts, allowMultiple: false, canChooseDirectories: false, for: context.window, completion: { paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + updatePhoto(image) + } else if let path = paths?.first { + selectVideoAvatar(context: context, path: path, localize: L10n.videoAvatarChooseDescProfile, signal: { signal in + updateVideo(signal) + }) + } + }) + }, logout: { + showModal(with: LogoutViewController(context: context, f: f), for: context.window) + }, username: { + f(UsernameSettingsViewController(context)) + }, changeNumber: { + f(PhoneNumberIntroController(context)) + }, addAccount: { + let testingEnvironment = NSApp.currentEvent?.modifierFlags.contains(.command) == true + context.sharedContext.beginNewAuth(testingEnvironment: testingEnvironment) + }) + + let controller = InputDataController(dataSignal: combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue, context.sharedContext.activeAccountsWithInfo) |> map {editInfoEntries(state: $0.0, arguments: arguments, activeAccounts: $0.2.accounts, updateState: updateState)} |> map { InputDataSignalValue(entries: $0) }, title: L10n.editAccountTitle, validateData: { data -> InputDataValidation in + + if let _ = data[_id_logout] { + arguments.logout() + return .fail(.none) + } + if let _ = data[_id_username] { + arguments.username() + return .fail(.none) + } + if let _ = data[_id_phone] { + arguments.changeNumber() + return .fail(.none) + } + + return .fail(.doSomething { f in + let current = stateValue.modify {$0} + if current.firstName.isEmpty { + f(.fail(.fields([_id_info : .shake]))) + } + var signals:[Signal] = [] + if let peerView = peerView { + let updates = valuesRequiringUpdate(state: current, view: peerView) + if let names = updates.0 { + signals.append(updateAccountPeerName(account: context.account, firstName: names.fn, lastName: names.ln)) + } + if let about = updates.1 { + signals.append(updateAbout(account: context.account, about: about) |> `catch` { _ in .complete()}) + } + updateNameDisposable.set(showModalProgress(signal: combineLatest(signals) |> deliverOnMainQueue, for: context.window).start(completed: { + updateState { $0 } + close?() + })) + } + }) + }, updateDatas: { data in + updateState { current in + return current.withUpdatedAbout(data[_id_about]?.stringValue ?? "") + } + return .fail(.none) + }, afterDisappear: { + actionsDisposable.dispose() + }, updateDoneValue: { data in + return { f in + let current = stateValue.modify {$0} + if let peerView = peerView { + let updates = valuesRequiringUpdate(state: current, view: peerView) + f((updates.0 != nil || updates.1 != nil) ? .enabled(L10n.navigationDone) : .disabled(L10n.navigationDone)) + } else { + f(.disabled(L10n.navigationDone)) + } + } + }, removeAfterDisappear: false, identifier: "account") + + controller.didLoaded = { controller, _ in + if let focusOnItemTag = focusOnItemTag { + controller.genericView.tableView.scroll(to: .center(id: focusOnItemTag.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets()) + } + } + + close = { [weak controller] in + controller?.navigationController?.back() + } + + f(controller) +} diff --git a/Telegram-Mac/EditAccountInfoItem.swift b/Telegram-Mac/EditAccountInfoItem.swift new file mode 100644 index 0000000000..7fc34f07ed --- /dev/null +++ b/Telegram-Mac/EditAccountInfoItem.swift @@ -0,0 +1,326 @@ +// +// EditAccountInfoItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class EditAccountInfoItem: GeneralRowItem { + + fileprivate let account: Account + fileprivate let state: EditInfoState + fileprivate let photo: AvatarNodeState + fileprivate let updateText: (String, String)->Void + fileprivate let uploadNewPhoto: (()->Void)? + init(_ initialSize: NSSize, stableId: AnyHashable, account: Account, state: EditInfoState, viewType: GeneralViewType = .legacy, updateText:@escaping(String, String)->Void, uploadNewPhoto: (()->Void)? = nil) { + self.account = account + self.updateText = updateText + self.state = state + self.uploadNewPhoto = uploadNewPhoto + self.photo = state.peer != nil ? .PeerAvatar(state.peer!, [state.firstName.first, state.lastName.first].compactMap{$0}.map{String($0)}, state.representation, nil) : .Empty + + let height: CGFloat + switch viewType { + case .legacy: + height = 90 + case let .modern(_, insets): + height = 60 + insets.top + insets.bottom + } + + super.init(initialSize, height: height, stableId: stableId, viewType: viewType) + } + + override func viewClass() -> AnyClass { + return EditAccountInfoItemView.self + } +} + +private final class EditAccountInfoItemView : TableRowView, TGModernGrowingDelegate { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let firstNameTextView: TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) + private let lastNameTextView: TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) + private let avatar: AvatarControl = AvatarControl(font: .avatar(22)) + private let nameSeparator: View = View() + private let secondSeparator: View = View() + private var tempImageView: ImageView? = nil + + private let updoadPhotoCap:ImageButton = ImageButton() + private let progressView:RadialProgressContainerView = RadialProgressContainerView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, icon: nil)) + private var ignoreUpdates: Bool = false + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + avatar.setFrameSize(NSMakeSize(60, 60)) + avatar.layer?.cornerRadius = 30 + progressView.frame = avatar.bounds + firstNameTextView.delegate = self + lastNameTextView.delegate = self + + firstNameTextView.textFont = .normal(.text) + lastNameTextView.textFont = .normal(.text) + + containerView.addSubview(firstNameTextView) + containerView.addSubview(lastNameTextView) + containerView.addSubview(nameSeparator) + containerView.addSubview(secondSeparator) + containerView.addSubview(avatar) + + addSubview(containerView) + + updoadPhotoCap.backgroundColor = NSColor.black.withAlphaComponent(0.4) + updoadPhotoCap.setFrameSize(avatar.frame.size) + updoadPhotoCap.layer?.cornerRadius = updoadPhotoCap.frame.width / 2 + updoadPhotoCap.set(image: ControlStyle(highlightColor: .white).highlight(image: theme.icons.chatAttachCamera), for: .Normal) + updoadPhotoCap.set(image: ControlStyle(highlightColor: theme.colors.accentIcon).highlight(image: theme.icons.chatAttachCamera), for: .Highlight) + + + updoadPhotoCap.set(handler: { [weak self] _ in + guard let item = self?.item as? EditAccountInfoItem else {return} + item.uploadNewPhoto?() + }, for: .Click) + + avatar.addSubview(updoadPhotoCap) + + progressView.progress.fetchControls = FetchControls(fetch: { [weak self] in + guard let item = self?.item as? EditAccountInfoItem else {return} + item.state.updatingPhotoState?.cancel() + }) + } + + override var mouseInsideField: Bool { + return lastNameTextView._mouseInside() || firstNameTextView._mouseInside() + } + + override func hitTest(_ point: NSPoint) -> NSView? { +// switch true { +// case NSPointInRect(point, firstNameTextView.frame): +// return firstNameTextView.inputView +// case NSPointInRect(point, lastNameTextView.frame): +// return lastNameTextView.inputView +// default: + return super.hitTest(point) +// } + } + + override func hasFirstResponder() -> Bool { + return true + } + + override var firstResponder: NSResponder? { + let isKeyDown = NSApp.currentEvent?.type == NSEvent.EventType.keyDown && NSApp.currentEvent?.keyCode == KeyboardKey.Tab.rawValue + switch true { + case firstNameTextView._mouseInside() && !isKeyDown: + return firstNameTextView.inputView + case lastNameTextView._mouseInside() && !isKeyDown: + return lastNameTextView.inputView + default: + switch true { + case firstNameTextView.inputView == window?.firstResponder: + return firstNameTextView.inputView + case lastNameTextView.inputView == window?.firstResponder: + return lastNameTextView.inputView + default: + return firstNameTextView.inputView + } + } + } + + override func nextResponder() -> NSResponder? { + if window?.firstResponder == firstNameTextView.inputView { + return lastNameTextView.inputView + } + + return nil + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? EditAccountInfoItem else {return} + + avatar.setState(account: item.account, state: item.photo) + ignoreUpdates = true + firstNameTextView.animates = false + lastNameTextView.animates = false + + firstNameTextView.placeholderAttributedString = .initialize(string: L10n.peerInfoFirstNamePlaceholder, color: theme.colors.grayText, font: .normal(.text)) + lastNameTextView.placeholderAttributedString = .initialize(string: L10n.peerInfoLastNamePlaceholder, color: theme.colors.grayText, font: .normal(.text)) + + firstNameTextView.setString(item.state.firstName) + lastNameTextView.setString(item.state.lastName) + + if let uploadState = item.state.updatingPhotoState { + if progressView.superview == nil { + avatar.addSubview(progressView) + progressView.layer?.opacity = 0 + } + progressView.change(opacity: 1, animated: animated) + progressView.progress.state = .Fetching(progress: uploadState.progress, force: false) + self.updoadPhotoCap.isHidden = true + + if let _ = uploadState.image, self.tempImageView == nil { + self.tempImageView = ImageView() + self.tempImageView?.contentGravity = .resizeAspect + self.tempImageView!.frame = avatar.bounds + self.avatar.addSubview(tempImageView!, positioned: .below, relativeTo: self.updoadPhotoCap) + if animated { + self.tempImageView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + self.tempImageView?.image = uploadState.image + + } else { + if animated { + progressView.change(opacity: 0, animated: animated, removeOnCompletion: false, completion: { [weak self] complete in + if complete { + self?.progressView.removeFromSuperview() + self?.progressView.layer?.removeAllAnimations() + } + }) + } else { + progressView.removeFromSuperview() + } + updoadPhotoCap.isHidden = item.uploadNewPhoto == nil + + if let tempImageView = self.tempImageView { + self.tempImageView = nil + if animated { + tempImageView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempImageView] _ in + tempImageView?.removeFromSuperview() + }) + } else { + tempImageView.removeFromSuperview() + } + } + } + + secondSeparator.isHidden = item.uploadNewPhoto == nil + + super.set(item: item, animated: animated) + + + layout() + + ignoreUpdates = false + } + + override func updateColors() { + firstNameTextView.textColor = theme.colors.text + lastNameTextView.textColor = theme.colors.text + + firstNameTextView.setBackgroundColor(backdorColor) + lastNameTextView.setBackgroundColor(backdorColor) + + nameSeparator.backgroundColor = theme.colors.border + secondSeparator.backgroundColor = theme.colors.border + containerView.background = backdorColor + guard let item = item as? EditAccountInfoItem else {return} + self.background = item.viewType.rowBackground + } + + override func layout() { + super.layout() + + guard let item = item as? EditAccountInfoItem else {return} + + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + self.containerView.setCorners([]) + firstNameTextView.setFrameSize(NSMakeSize(self.containerView.frame.width - item.inset.left - item.inset.right - avatar.frame.width - 10, firstNameTextView.frame.height)) + lastNameTextView.setFrameSize(NSMakeSize(self.containerView.frame.width - item.inset.left - item.inset.right - avatar.frame.width - 10, lastNameTextView.frame.height)) + avatar.setFrameOrigin(item.inset.left, 16) + firstNameTextView.setFrameOrigin(NSMakePoint(avatar.frame.maxX + 10, avatar.frame.minY - 6)) + nameSeparator.frame = NSMakeRect(avatar.frame.maxX + 14, firstNameTextView.frame.maxY + 2, self.containerView.frame.width - avatar.frame.maxX - item.inset.right - 14, .borderSize) + lastNameTextView.setFrameOrigin(NSMakePoint(avatar.frame.maxX + 10, firstNameTextView.frame.maxY + 4)) + secondSeparator.frame = NSMakeRect(item.inset.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.right - item.inset.left, .borderSize) + secondSeparator.isHidden = false + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + firstNameTextView.setFrameSize(NSMakeSize(self.containerView.frame.width - innerInsets.left - innerInsets.right - avatar.frame.width - 10, firstNameTextView.frame.height)) + lastNameTextView.setFrameSize(NSMakeSize(self.containerView.frame.width - innerInsets.left - innerInsets.right - avatar.frame.width - 10, lastNameTextView.frame.height)) + avatar.setFrameOrigin(innerInsets.left, innerInsets.top) + firstNameTextView.setFrameOrigin(NSMakePoint(avatar.frame.maxX + 10, avatar.frame.minY - 6)) + nameSeparator.frame = NSMakeRect(avatar.frame.maxX + 14, firstNameTextView.frame.maxY + 2, self.containerView.frame.width - avatar.frame.maxX - item.inset.right - 14, .borderSize) + lastNameTextView.setFrameOrigin(NSMakePoint(avatar.frame.maxX + 10, firstNameTextView.frame.maxY + 4)) + secondSeparator.frame = NSMakeRect(innerInsets.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.right - item.inset.left, .borderSize) + + secondSeparator.isHidden = !position.border + + } + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return 64 + } + + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return textView.frame.size + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + func textViewEnterPressed(_ event:NSEvent) -> Bool { + if FastSettings.checkSendingAbility(for: event) { + return true + } + return false + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func textViewNeedClose(_ textView: Any) { + + } + + func textViewTextDidChange(_ string: String) { + guard let item = item as? EditAccountInfoItem else {return} + guard !ignoreUpdates else {return} + + item.updateText(firstNameTextView.string(), lastNameTextView.string()) + } + + func textViewDidReachedLimit(_ textView: Any) { +// if let responder = nextResponder() { +// window?.makeFirstResponder(responder) +// } + } + + func controlTextDidChange(_ obj: Notification) { + + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + return false + } +} diff --git a/Telegram-Mac/EditImageCanvasColorPicker.swift b/Telegram-Mac/EditImageCanvasColorPicker.swift new file mode 100644 index 0000000000..658a72d722 --- /dev/null +++ b/Telegram-Mac/EditImageCanvasColorPicker.swift @@ -0,0 +1,446 @@ +// +// EditImageCanvasColorPickerBackground.swift +// Telegram +// +// Created by Mikhail Filimonov on 16/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + +class EditImageCanvasColorPickerBackground: Control { + + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + let rect = bounds + + let radius: CGFloat = rect.size.width > rect.size.height ? rect.size.height / 2.0 : rect.size.width / 2.0 + addRoundedRectToPath(ctx, bounds, radius, radius) + ctx.clip() + + let colors = EditImageCanvasColorPickerBackground.colors + var locations = EditImageCanvasColorPickerBackground.locations + + let colorSpc = CGColorSpaceCreateDeviceRGB() + let gradient: CGGradient = CGGradient(colorsSpace: colorSpc, colors: colors as CFArray, locations: &locations)! + + if rect.size.width > rect.size.height { + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.size.height / 2.0), end: CGPoint(x: rect.size.width, y: rect.size.height / 2.0), options: .drawsAfterEndLocation) + } else { + ctx.drawLinearGradient(gradient, start: CGPoint(x: rect.size.width / 2.0, y: 0.0), end: CGPoint(x: rect.size.width / 2.0, y: rect.size.height), options: .drawsAfterEndLocation) + } + + ctx.setBlendMode(.clear) + ctx.setFillColor(.clear) + + } + + private func addRoundedRectToPath(_ context: CGContext, _ rect: CGRect, _ ovalWidth: CGFloat, _ ovalHeight: CGFloat) { + var fw: CGFloat + var fh: CGFloat + if ovalWidth == 0 || ovalHeight == 0 { + context.addRect(rect) + return + } + context.saveGState() + context.translateBy(x: rect.minX, y: rect.minY) + context.scaleBy(x: ovalWidth, y: ovalHeight) + fw = rect.width / ovalWidth + fh = rect.height / ovalHeight + context.move(to: CGPoint(x: fw, y: fh / 2)) + context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw / 2, y: fh), radius: 1) + context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh / 2), radius: 1) + context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw / 2, y: 0), radius: 1) + context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh / 2), radius: 1) + context.closePath() + context.restoreGState() + } + + func color(for location: CGFloat) -> NSColor { + let locations = EditImageCanvasColorPickerBackground.locations + let colors = EditImageCanvasColorPickerBackground.colors + + if location < .ulpOfOne { + return NSColor(cgColor: colors[0])! + } else if location > 1 - .ulpOfOne { + return NSColor(cgColor: colors[colors.count - 1])! + } + + var leftIndex: Int = -1 + var rightIndex: Int = -1 + + for (index, value) in locations.enumerated() { + if index > 0 { + if value > location { + leftIndex = index - 1 + rightIndex = index + break + } + } + } + + let leftLocation = locations[leftIndex] + let leftColor = NSColor(cgColor: colors[leftIndex])! + + let rightLocation = locations[rightIndex] + let rightColor = NSColor(cgColor: colors[rightIndex])! + + let factor = (location - leftLocation) / (rightLocation - leftLocation) + + return self.interpolateColor(color1: leftColor, color2: rightColor, factor: factor) + } + + private func interpolateColor(color1: NSColor, color2: NSColor, factor: CGFloat) -> NSColor { + let factor = min(max(factor, 0.0), 1.0) + + var r1: CGFloat = 0 + var r2: CGFloat = 0 + var g1: CGFloat = 0 + var g2: CGFloat = 0 + var b1: CGFloat = 0 + var b2: CGFloat = 0 + + + self.colorComponentsFor(color1, red: &r1, green: &g1, blue: &b1) + self.colorComponentsFor(color2, red: &r2, green: &g2, blue: &b2) + + let r = r1 + (r2 - r1) * factor; + let g = g1 + (g2 - g1) * factor; + let b = b1 + (b2 - b1) * factor; + + return NSColor(red: r, green: g, blue: b, alpha: 1.0) + } + + private func colorComponentsFor(_ color: NSColor, red:inout CGFloat, green:inout CGFloat, blue:inout CGFloat) { + let componentsCount = color.cgColor.numberOfComponents + let components = color.cgColor.components + + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + var a: CGFloat = 1.0 + + if componentsCount == 4 { + r = components?[0] ?? 0.0 + g = components?[1] ?? 0.0 + b = components?[2] ?? 0.0 + a = components?[3] ?? 0.0 + } else { + b = components?[0] ?? 0.0 + g = b + r = g + } + red = r + green = g + blue = b + } + + + static var colors: [CGColor] { + return [NSColor(0xea2739).cgColor, + NSColor(0xdb3ad2).cgColor, + NSColor(0x3051e3).cgColor, + NSColor(0x49c5ed).cgColor, + NSColor(0x80c864).cgColor, + NSColor(0xfcde65).cgColor, + NSColor(0xfc964d).cgColor, + NSColor(0x000000).cgColor, + NSColor(0xffffff).cgColor + ] + } + static var locations: [CGFloat] { + return [ 0.0, //red + 0.14, //pink + 0.24, //blue + 0.39, //cyan + 0.49, //green + 0.62, //yellow + 0.73, //orange + 0.85, //black + 1.0 + ] + } + +} + +private final class PaintColorPickerKnobCircleView : View { + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var strokeIntensity: CGFloat = 0 + + var strokesLowContrastColors: Bool = false + + + override var needsLayout: Bool { + didSet { + needsDisplay = true + } + } + + var color: NSColor = .black { + didSet { + if strokesLowContrastColors { + var strokeIntensity: CGFloat = 0.0 + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + + color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + if hue < CGFloat.ulpOfOne && saturation < CGFloat.ulpOfOne && brightness > 0.92 { + strokeIntensity = (brightness - 0.92) / 0.08 + } + self.strokeIntensity = strokeIntensity + } + needsDisplay = true + } + } + + override func draw(_ layer: CALayer, in context: CGContext) { + super.draw(layer, in: context) + + let rect = bounds + + context.setFillColor(color.cgColor) + context.fillEllipse(in: rect) + + if strokeIntensity > .ulpOfOne { + context.setLineWidth(1.0) + context.setStrokeColor(NSColor(white: 0.88, alpha: strokeIntensity).cgColor) + context.strokeEllipse(in: rect.insetBy(dx: 1.0, dy: 1.0)) + } + + } + + +} + +private let paintColorSmallCircle: CGFloat = 4.0 +private let paintColorLargeCircle: CGFloat = 20.0 +private let paintColorWeightGestureRange: CGFloat = 200 +private let paintVerticalThreshold: CGFloat = 5 +private let paintPreviewOffset: CGFloat = -60 +private let paintPreviewScale: CGFloat = 2.0 +private let paintDefaultBrushWeight: CGFloat = 0.22 +private let oaintDefaultColorLocation: CGFloat = 1.0 + + +private final class PaintColorPickerKnob: View { + + fileprivate var isZoomed: Bool = false + + fileprivate var weight: CGFloat = 0.5 + + fileprivate func updateWeight(_ weight: CGFloat, animated: Bool) { + self.weight = weight + var diameter = circleDiameter(forBrushWeight: weight, zoomed: self.isZoomed) + if Int(diameter) % 2 != 0 { + diameter -= 1 + } + colorView.setFrameSize(NSMakeSize(diameter, diameter)) + + backgroundView.setFrameSize(NSMakeSize(24 * (isZoomed ? paintPreviewScale : 1), 24 * (isZoomed ? paintPreviewScale : 1))) + backgroundView.center() + + + if animated { + if isZoomed { + colorView.layer?.animateScaleSpring(from: 0.5, to: 1, duration: 0.3) + backgroundView.layer?.animateScaleSpring(from: 0.5, to: 1, duration: 0.3) + } else { + colorView.layer?.animateScaleSpring(from: 2.0, to: 1, duration: 0.3) + backgroundView.layer?.animateScaleSpring(from: 2.0, to: 1, duration: 0.3) + } + } + + needsLayout = true + } + + fileprivate var color: NSColor = .random { + didSet { + colorView.color = color + } + } + fileprivate var width: CGFloat { + return circleDiameter(forBrushWeight: weight, zoomed: false) - 2 + } + + private let backgroundView = PaintColorPickerKnobCircleView(frame: .zero) + private let colorView = PaintColorPickerKnobCircleView(frame: .zero) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + + backgroundView.color = NSColor(0xffffff) + + colorView.color = NSColor.blue + colorView.strokesLowContrastColors = true + addSubview(backgroundView) + addSubview(colorView) + } + + + func circleDiameter(forBrushWeight size: CGFloat, zoomed: Bool) -> CGFloat { + var result = CGFloat(paintColorSmallCircle) + CGFloat((paintColorLargeCircle - paintColorSmallCircle)) * size + result = CGFloat(zoomed ? result * paintPreviewScale : floor(result)) + return floorToScreenPixels(backingScaleFactor, result) + } + + override func layout() { + super.layout() + backgroundView.setFrameSize(NSMakeSize(24 * (isZoomed ? paintPreviewScale : 1), 24 * (isZoomed ? paintPreviewScale : 1))) + backgroundView.center() + colorView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +final class EditImageColorPicker: View { + + var arguments: EditImageCanvasArguments? { + didSet { + arguments?.updateColorAndWidth(knobView.color, knobView.width) + } + } + + private let knobView = PaintColorPickerKnob(frame: NSMakeRect(0, 0, 24 * paintPreviewScale, 24 * paintPreviewScale)) + let backgroundView = EditImageCanvasColorPickerBackground() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(backgroundView) + addSubview(knobView) + knobView.isEventLess = true + + backgroundView.set(handler: { [weak self] _ in + self?.updateLocation(animated: false) + }, for: .MouseDragging) + + backgroundView.set(handler: { [weak self] _ in + self?.knobView.isZoomed = true + self?.updateLocation(animated: true) + }, for: .Down) + + backgroundView.set(handler: { [weak self] _ in + self?.knobView.isZoomed = false + self?.updateLocation(animated: true) + }, for: .Up) + + let colorValue = UserDefaults.standard.value(forKey: "painterColorLocation") as? CGFloat + let weightValue = UserDefaults.standard.value(forKey: "painterBrushWeight") as? CGFloat + + + let colorLocation: CGFloat + if let colorValue = colorValue { + colorLocation = colorValue + } else { + colorLocation = CGFloat(arc4random()) / CGFloat(UInt32.max) + UserDefaults.standard.setValue(colorLocation, forKey: "painterColorLocation") + } + self.location = colorLocation + knobView.color = backgroundView.color(for: colorLocation) + + let weight = weightValue ?? paintDefaultBrushWeight + knobView.updateWeight(weight, animated: false) + + } + + private var location: CGFloat = 0 + + private func updateLocation(animated: Bool) { + + guard let window = self.window else { + return + } + + let location = backgroundView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + let colorLocation = max(0.0, min(1.0, location.x / backgroundView.frame.width)) + self.location = colorLocation + + + knobView.color = backgroundView.color(for: colorLocation) + + let threshold = min(max(frame.height - backgroundView.frame.minY, frame.height - self.convert(window.mouseLocationOutsideOfEventStream, from: nil).y), paintColorWeightGestureRange + paintPreviewOffset) + + let weight = threshold / (paintColorWeightGestureRange + paintPreviewOffset); + + + knobView.updateWeight(weight, animated: animated) + + arguments?.updateColorAndWidth(knobView.color, knobView.width) + + UserDefaults.standard.set(Double(colorLocation), forKey: "painterColorLocation") + UserDefaults.standard.set(Double(weight), forKey: "painterBrushWeight") + + + if animated { + knobView.layer?.animatePosition(from: NSMakePoint(knobView.frame.minX - knobPosition.x, knobView.frame.minY - knobPosition.y), to: .zero, duration: 0.3, timingFunction: .spring, removeOnCompletion: true, additive: true) + } + + needsLayout = true + + + } + + override var isEventLess: Bool { + get { + guard let window = self.window else { + return false + } + let point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + if NSPointInRect(point, backgroundView.frame) || NSPointInRect(point, knobView.frame) { + return false + } else { + return true + } + } + set { + super.isEventLess = newValue + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var knobPosition: NSPoint { + + guard let window = self.window else { + return .zero + } + + let threshold: CGFloat + if knobView.isZoomed { + threshold = min(max(0, frame.height - self.convert(window.mouseLocationOutsideOfEventStream, from: nil).y - (frame.height - backgroundView.frame.minY)), paintColorWeightGestureRange) + } else { + threshold = 0 + } + let knobY: CGFloat = max(0, (backgroundView.frame.midY - knobView.frame.height / 2) + (knobView.isZoomed ? paintPreviewOffset : 0) + -threshold) + let knobX: CGFloat = max(0, min(backgroundView.frame.width * location, self.frame.width - knobView.frame.width)) + return NSMakePoint(knobX, knobY) + } + + override func layout() { + super.layout() + backgroundView.frame = NSMakeRect(24, frame.height - 24, frame.width - 48, 20) + knobView.frame = CGRect(x: knobPosition.x, y: knobPosition.y, width: knobView.frame.size.width, height: knobView.frame.size.height) + + } +} diff --git a/Telegram-Mac/EditImageCanvasController.swift b/Telegram-Mac/EditImageCanvasController.swift new file mode 100644 index 0000000000..370a8ea84c --- /dev/null +++ b/Telegram-Mac/EditImageCanvasController.swift @@ -0,0 +1,463 @@ +// +// EditImageSticker.swift +// Telegram +// +// Created by Mikhail Filimonov on 16/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +final class EditImageCanvasArguments { + let makeNewActionAt:(NSPoint)->Void + let addToLastAction:(NSPoint)->Void + + let switchAction:(EditImageCanvasAction)->Void + let undo:()->Void + let redo:()->Void + let updateColorAndWidth:(NSColor, CGFloat)->Void + + let save:()->Void + let cancel:()->Void + init(makeNewActionAt:@escaping(NSPoint)->Void, addToLastAction:@escaping(NSPoint)->Void, switchAction:@escaping(EditImageCanvasAction)->Void, undo: @escaping()->Void, redo: @escaping()->Void, updateColorAndWidth: @escaping(NSColor, CGFloat)->Void, save: @escaping()->Void, cancel: @escaping()->Void) { + self.makeNewActionAt = makeNewActionAt + self.addToLastAction = addToLastAction + self.switchAction = switchAction + self.undo = undo + self.redo = redo + self.updateColorAndWidth = updateColorAndWidth + self.save = save + self.cancel = cancel + } +} + +func applyPaints(_ touches: [EditImageDrawTouch], for context: CGContext, imageSize: NSSize) { + context.saveGState() + for touch in touches { + context.beginPath() + + let multiplier = NSMakePoint(imageSize.width / touch.canvasSize.width, imageSize.height / touch.canvasSize.height) + + for (i, point) in touch.lines.enumerated() { + let point = NSMakePoint(point.x * multiplier.x, point.y * multiplier.y) + if i == 0 { + context.move(to: point) + } else { + context.addLine(to: point) + } + } + + context.setLineWidth(touch.width * ((multiplier.x + multiplier.y) / 2)) + context.setLineCap(.round) + context.setLineJoin(.round) + context.setStrokeColor(touch.color.cgColor) + + switch touch.action { + case .draw: + context.setBlendMode(.normal) + case .clear: + context.setBlendMode(.clear) + } + context.strokePath() + } + context.restoreGState() + context.setBlendMode(.normal) +} + +final class EditImageDrawTouch : Equatable { + + static func == (lhs: EditImageDrawTouch, rhs: EditImageDrawTouch) -> Bool { + return lhs.lines == rhs.lines && + lhs.color.argb == rhs.color.argb && + lhs.width == rhs.width && + lhs.action == rhs.action && + lhs.canvasSize == rhs.canvasSize + } + private(set) var lines:[NSPoint] + let color: NSColor + let width: CGFloat + let action: EditImageCanvasAction + let canvasSize: NSSize + init(action: EditImageCanvasAction, point: NSPoint, canvasSize: NSSize, color: NSColor, width: CGFloat) { + self.action = action + self.lines = [point] + self.color = color + self.width = width + self.canvasSize = canvasSize + } + func addPoint(_ point: NSPoint) { + self.lines.append(point) + } +} + +class EditImageDrawView: Control { + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + fileprivate var arguments: EditImageCanvasArguments? = nil + private(set) fileprivate var state:EditImageCanvasState = EditImageCanvasState.default([]) + + func update(with state: EditImageCanvasState) { + self.state = state + needsDisplay = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + needsDisplay = true + } + + override func draw(_ layer: CALayer, in context: CGContext) { + super.draw(layer, in: context) + applyPaints(self.state.actionValues, for: context, imageSize: self.frame.size) + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + + let point = self.convert(event.locationInWindow, from: nil) + arguments?.makeNewActionAt(point) + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + } + + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + + let point = self.convert(event.locationInWindow, from: nil) + arguments?.addToLastAction(point) + + } +} + + +final class EditImageCanvasView : View { + + let imageContainer: View = View() + + // let magnifyView: MagnifyView + + let imageView: ImageView = ImageView() + let drawView: EditImageDrawView = EditImageDrawView(frame: .zero) + + let colorPicker: EditImageColorPicker + let shadowView: View = View() + private let controls: EditImageCanvasControlsView = EditImageCanvasControlsView(frame: NSMakeRect(0, 0, 350, 40)) + required init(frame frameRect: NSRect, image: CGImage) { + self.imageView.image = image + colorPicker = EditImageColorPicker(frame: NSMakeRect(0, 0, 348, 200)) + super.init(frame: frameRect) + + shadowView.isEventLess = true + + imageView.background = .white + + addSubview(imageContainer) + imageContainer.addSubview(imageView) + + + imageContainer.addSubview(drawView) + addSubview(colorPicker) + addSubview(controls) + + } + + fileprivate var arguments: EditImageCanvasArguments? = nil { + didSet { + controls.arguments = arguments + drawView.arguments = arguments + colorPicker.arguments = arguments + } + } + + + func update(with state: EditImageCanvasState) { + controls.update(with: state) + drawView.update(with: state) + } + + override func layout() { + super.layout() + imageContainer.setFrameSize(frame.width, frame.height - 120) + + let imageSize = self.imageView.image!.size.fitted(NSMakeSize(imageContainer.frame.width - 8, imageContainer.frame.height - 8)) + self.imageView.setFrameSize(imageSize) + + self.imageView.center() + self.drawView.frame = imageView.frame + + + controls.centerX(y: frame.height - controls.frame.height) + colorPicker.centerX(y: controls.frame.minY - colorPicker.frame.height - 20) + } + + func contentSize(maxSize: NSSize) -> NSSize { + return NSMakeSize(maxSize.width, maxSize.height) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +enum EditImageCanvasAction : Hashable { + case draw + case clear +} + +struct EditImageCanvasState : Equatable { + let action: EditImageCanvasAction + + let color: NSColor + let width: CGFloat + + let actionValues:[EditImageDrawTouch] + let removedActions:[EditImageDrawTouch] + + init(action: EditImageCanvasAction, actionValues:[EditImageDrawTouch], removedActions:[EditImageDrawTouch], color: NSColor, width: CGFloat) { + self.action = action + self.actionValues = actionValues + self.color = color + self.width = width + self.removedActions = removedActions + } + + func withUpdatedAction(_ action: EditImageCanvasAction) -> EditImageCanvasState { + return EditImageCanvasState(action: action, actionValues: self.actionValues, removedActions: self.removedActions, color: self.color, width: self.width) + } + + func withUpdatedCurrentActionValues(_ f:([EditImageDrawTouch])->[EditImageDrawTouch]) -> EditImageCanvasState { + return EditImageCanvasState(action: action, actionValues: f(self.actionValues), removedActions: self.removedActions, color: self.color, width: self.width) + } + + func withAddedRemovedAction(_ action: EditImageDrawTouch) -> EditImageCanvasState { + + var removedActions = self.removedActions + removedActions.append(action) + + return EditImageCanvasState(action: self.action, actionValues: self.actionValues, removedActions: removedActions, color: self.color, width: self.width) + } + + func withReturnedRemovedAction() -> EditImageCanvasState { + + var removedActions = self.removedActions + var actionValues = self.actionValues + if !removedActions.isEmpty { + let last = removedActions.removeLast() + actionValues.append(last) + } else { + NSSound.beep() + } + return EditImageCanvasState(action: self.action, actionValues: actionValues, removedActions: removedActions, color: self.color, width: self.width) + } + + func withClearedRemovedActions() -> EditImageCanvasState { + return EditImageCanvasState(action: self.action, actionValues: self.actionValues, removedActions: [], color: self.color, width: self.width) + } + + func withUpdateColorAndWidth(_ color: NSColor, _ width: CGFloat) -> EditImageCanvasState { + return EditImageCanvasState(action: self.action, actionValues: self.actionValues, removedActions: self.removedActions, color: color, width: width) + } + + static func `default`(_ actions: [EditImageDrawTouch]) -> EditImageCanvasState { + return EditImageCanvasState(action: .draw, actionValues: actions, removedActions: [], color: .random, width: 6) + } +} + +final class EditImageCanvasController : ModalViewController { + private let disposable = MetaDisposable() + private let image: CGImage + private let actions: [EditImageDrawTouch] + private let updatedImage: ([EditImageDrawTouch])->Void + private let closeHandler: ()->Void + init(image: CGImage, actions: [EditImageDrawTouch], updatedImage: @escaping([EditImageDrawTouch])->Void, closeHandler: @escaping() -> Void) { + self.stateValue = Atomic(value: EditImageCanvasState.default(actions)) + self.state = ValuePromise(EditImageCanvasState.default(actions), ignoreRepeated: false) + self.image = image + self.actions = actions + self.updatedImage = updatedImage + self.closeHandler = closeHandler + super.init() + bar = .init(height: 0) + } + + private let stateValue: Atomic + private let state: ValuePromise + + override var containerBackground: NSColor { + return .clear + } + override var isVisualEffectBackground: Bool { + return false + } + + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + super.close(animationType: animationType) + self.closeHandler() + } + + + override var background: NSColor { + return .clear + } + + + + override func returnKeyAction() -> KeyHandlerResult { + self.updatedImage(stateValue.with { $0.actionValues} ) + close() + return .invoked + } + + override func measure(size: NSSize) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with: genericView.contentSize(maxSize: NSMakeSize(contentSize.width - 80, contentSize.height - 80)), animated: false) + } + } + + func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with: genericView.contentSize(maxSize: NSMakeSize(contentSize.width - 80, contentSize.height - 80)), animated: animated) + } + } + + override var dynamicSize: Bool { + return true + } + + override func viewClass() -> AnyClass { + return EditImageCanvasView.self + } + + private var genericView: EditImageCanvasView { + return self.view as! EditImageCanvasView + } + + override func viewDidLoad() { + super.viewDidLoad() + + + let updateState:((EditImageCanvasState)->EditImageCanvasState)->Void = { [weak self] f in + guard let `self` = self else { + return + } + self.state.set(self.stateValue.modify(f)) + } + + let arguments = EditImageCanvasArguments(makeNewActionAt: { [weak self] point in + let canvasSize = self?.genericView.drawView.frame.size ?? .zero + updateState { state in + return state.withUpdatedCurrentActionValues { touches in + var touches = touches + touches.append(EditImageDrawTouch(action: state.action, point: point, canvasSize: canvasSize, color: state.color, width: state.width)) + return touches + }.withClearedRemovedActions() + } + }, addToLastAction: { point in + updateState { state in + return state.withUpdatedCurrentActionValues { touches in + touches.last?.addPoint(point) + return touches + } + } + }, switchAction: { action in + updateState { state in + return state.withUpdatedAction(action) + } + }, undo: { + updateState { state in + var state = state + var lastAction: EditImageDrawTouch? + state = state.withUpdatedCurrentActionValues { touches in + var touches = touches + if !touches.isEmpty { + lastAction = touches.removeLast() + } else { + NSSound.beep() + } + return touches + } + if let lastAction = lastAction { + state = state.withAddedRemovedAction(lastAction) + } + return state + } + }, redo: { + updateState { + $0.withReturnedRemovedAction() + } + }, updateColorAndWidth: { color, width in + updateState { + $0.withUpdateColorAndWidth(color, width) + } + }, save: { [weak self] in + _ = self?.returnKeyAction() + }, cancel: { [weak self] in + self?.close() + }) + + genericView.arguments = arguments + + disposable.set(state.get().start(next: { [weak self] state in + self?.genericView.update(with: state) + })) + + readyOnce() + } + override func initializer() -> NSView { + let vz = viewClass() as! EditImageCanvasView.Type + return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), image: image); + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.arguments?.undo() + + return .invoked + }, with: self, for: .Z, priority: .modal, modifierFlags: [.command]) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.arguments?.redo() + + return .invoked + }, with: self, for: .Z, priority: .modal, modifierFlags: [.command, .shift]) + + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.arguments?.switchAction(.clear) + + return .invoked + }, with: self, for: .E, priority: .modal) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.arguments?.switchAction(.draw) + + return .invoked + }, with: self, for: .L, priority: .modal) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + deinit { + disposable.dispose() + } +} diff --git a/Telegram-Mac/EditImageCanvasControls.swift b/Telegram-Mac/EditImageCanvasControls.swift new file mode 100644 index 0000000000..bdd92d0d4b --- /dev/null +++ b/Telegram-Mac/EditImageCanvasControls.swift @@ -0,0 +1,133 @@ +// +// EditImageCanvasControls.swift +// Telegram +// +// Created by Mikhail Filimonov on 16/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + + +final class EditImageCanvasControlsView : View { + let cancel = TitleButton() + private let success = TitleButton() + private let controlsContainer = View() + private let undo = ImageButton() + private let redo = ImageButton() + private let draw = ImageButton() + private let clear = ImageButton() + + private var currentData: EditedImageData? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + addSubview(cancel) + addSubview(success) + + addSubview(controlsContainer) + + controlsContainer.addSubview(undo) + controlsContainer.addSubview(redo) + controlsContainer.addSubview(draw) + controlsContainer.addSubview(clear) + + controlsContainer.border = [.Left, .Right] + controlsContainer.borderColor = NSColor.black.withAlphaComponent(0.2) + backgroundColor = NSColor(0x303030) + layer?.cornerRadius = 6 + + updateUserInterface() + } + + fileprivate func updateUserInterface() { + undo.set(image: NSImage(named: "Icon_EditImageUndo")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + redo.set(image: NSImage(named: "Icon_EditImageUndo")!.precomposed(NSColor.white.withAlphaComponent(0.8), flipHorizontal: true), for: .Normal) + draw.set(image: NSImage(named: "Icon_EditImageDraw")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + clear.set(image: NSImage(named: "Icon_EditImageEraser")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + + undo.appTooltip = "⌘Z" + redo.appTooltip = "⌘⇧Z" + draw.appTooltip = "L" + clear.appTooltip = "E" + + undo.set(image: NSImage(named: "Icon_EditImageUndo")!.precomposed(.white), for: .Hover) + redo.set(image: NSImage(named: "Icon_EditImageUndo")!.precomposed(NSColor.white, flipHorizontal: true), for: .Hover) + draw.set(image: NSImage(named: "Icon_EditImageDraw")!.precomposed(.white), for: .Hover) + clear.set(image: NSImage(named: "Icon_EditImageEraser")!.precomposed(.white), for: .Hover) + + + _ = undo.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = redo.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = draw.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = clear.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + + cancel.set(font: .medium(.title), for: .Normal) + success.set(font: .medium(.title), for: .Normal) + + success.set(color: nightAccentPalette.accent, for: .Normal) + cancel.set(color: .white, for: .Normal) + + cancel.set(text: L10n.modalCancel, for: .Normal) + success.set(text: L10n.navigationDone, for: .Normal) + + _ = cancel.sizeToFit(NSZeroSize, NSMakeSize(75, frame.height), thatFit: true) + _ = success.sizeToFit(NSZeroSize, NSMakeSize(75, frame.height), thatFit: true) + + + undo.set(handler: { [weak self] _ in + self?.arguments?.undo() + }, for: .Click) + + redo.set(handler: { [weak self] _ in + self?.arguments?.redo() + }, for: .Click) + + draw.set(handler: { [weak self] _ in + self?.arguments?.switchAction(.draw) + }, for: .Click) + + clear.set(handler: { [weak self] _ in + self?.arguments?.switchAction(.clear) + }, for: .Click) + + + success.set(handler: { [weak self] _ in + self?.arguments?.save() + }, for: .Click) + + cancel.set(handler: { [weak self] _ in + self?.arguments?.cancel() + }, for: .Click) + } + + override func layout() { + super.layout() + controlsContainer.setFrameSize(draw.frame.width + undo.frame.width + redo.frame.width + clear.frame.width, draw.frame.height) + undo.setFrameOrigin(NSMakePoint(0, 0)) + undo.setFrameOrigin(NSMakePoint(draw.frame.maxX, 0)) + draw.setFrameOrigin(NSMakePoint(undo.frame.maxX, 0)) + clear.setFrameOrigin(NSMakePoint(draw.frame.maxX, 0)) + controlsContainer.center() + + cancel.centerY() + success.centerY(x: frame.width - success.frame.width) + } + var arguments: EditImageCanvasArguments? = nil + + func update(with state: EditImageCanvasState) { + + undo.isEnabled = !state.actionValues.isEmpty + redo.isEnabled = !state.removedActions.isEmpty + draw.isSelected = state.action == .draw + clear.isSelected = state.action == .clear + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/EditImageControls.swift b/Telegram-Mac/EditImageControls.swift new file mode 100644 index 0000000000..e8a95dc805 --- /dev/null +++ b/Telegram-Mac/EditImageControls.swift @@ -0,0 +1,317 @@ +// +// EditImageControls.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +struct EditedImageData : Equatable { + let originalUrl: URL + let selectedRect: NSRect + let orientation: ImageOrientation? + let dimensions: SelectionRectDimensions + let isHorizontalFlipped: Bool + let paintings: [EditImageDrawTouch] + init(originalUrl: URL, selectedRect: NSRect = NSZeroRect, orientation: ImageOrientation? = nil, dimensions: SelectionRectDimensions = .none, isHorizontalFlipped: Bool = false, paintings: [EditImageDrawTouch] = []) { + self.originalUrl = originalUrl + self.dimensions = dimensions + self.selectedRect = selectedRect + self.orientation = orientation + self.isHorizontalFlipped = isHorizontalFlipped + self.paintings = paintings + } + var hasntData: Bool { + return orientation == nil && dimensions == .none && !isHorizontalFlipped && paintings.isEmpty + } + + + func withUpdatedOrientation( _ orientation: ImageOrientation?) -> EditedImageData { + return EditedImageData(originalUrl: self.originalUrl, selectedRect: selectedRect, orientation: orientation, dimensions: self.dimensions, isHorizontalFlipped: self.isHorizontalFlipped, paintings: self.paintings) + } + func withUpdatedFlip(_ isHorizontalFlipped: Bool) -> EditedImageData { + return EditedImageData(originalUrl: self.originalUrl, selectedRect: self.selectedRect, orientation: self.orientation, dimensions: self.dimensions, isHorizontalFlipped: isHorizontalFlipped, paintings: self.paintings) + } + func withUpdatedDimensions(_ dimensions: SelectionRectDimensions) -> EditedImageData { + return EditedImageData(originalUrl: self.originalUrl, selectedRect: self.selectedRect, orientation: self.orientation, dimensions: dimensions, isHorizontalFlipped: self.isHorizontalFlipped, paintings: self.paintings) + } + + func withUpdatedSelectedRect(_ selectedRect: NSRect) -> EditedImageData { + return EditedImageData(originalUrl: self.originalUrl, selectedRect: selectedRect, orientation: self.orientation, dimensions: self.dimensions, isHorizontalFlipped: self.isHorizontalFlipped, paintings: self.paintings) + } + + func withUpdatedPaintings(_ paintings: [EditImageDrawTouch]) -> EditedImageData { + return EditedImageData(originalUrl: self.originalUrl, selectedRect: self.selectedRect, orientation: self.orientation, dimensions: self.dimensions, isHorizontalFlipped: self.isHorizontalFlipped, paintings: paintings) + } + + func makeImage(_ image: CGImage) -> CGImage { + return EditedImageData.makeImage(image, data: self) + } + + func isNeedToRegenerate(_ data: EditedImageData?) -> Bool { + return data?.orientation != self.orientation || (data != nil && data!.isHorizontalFlipped != self.isHorizontalFlipped) || (data != nil && data!.paintings != self.paintings) || data == nil + } + + fileprivate static func makeImage(_ image: CGImage, data: EditedImageData) -> CGImage { + var image: CGImage = image + var orientation = data.orientation + + if !data.paintings.isEmpty { + image = generateImage(image.size, scale: 1.0, rotatedContext: { size, context in + let rect = NSMakeRect(0, 0, size.width, size.height) + context.clear(rect) + + context.saveGState() + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + context.draw(image, in: rect) + context.restoreGState() + + let paints = generateImage(size, contextGenerator: { size, ctx in + ctx.clear(rect) + applyPaints(data.paintings, for: ctx, imageSize: size) + }, scale: 1.0)! + + context.draw(paints, in: rect) + + })! + } + + if data.isHorizontalFlipped, let temp = orientation { + switch temp { + case .left: + orientation = .leftMirrored + case .right: + orientation = .rightMirrored + case .down: + orientation = .downMirrored + default: + orientation = nil + } + } + if let orientation = orientation { + image = image.createMatchingBackingDataWithImage(orienation: orientation)! + } else if data.isHorizontalFlipped { + return generateImage(image.size, contextGenerator: { size, ctx in + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: -1.0, y: 1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + ctx.draw(image, in: NSMakeRect(0, 0, size.width, size.height)) + + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: -1.0, y: 1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + }, scale: 1.0)! + // image = NSImage(cgImage: image, size: image.backingSize).precomposed(flipHorizontal: true) + } + + return image + } + + static func generateNewUrl(data: EditedImageData, selectedRect: NSRect) -> Signal { + return Signal { subscriber in + + if let image = NSImage(contentsOf: data.originalUrl)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + if selectedRect == NSMakeRect(0, 0, image.size.width, image.size.height) && data.hasntData { + subscriber.putNext(data.originalUrl) + subscriber.putCompletion() + } else { + if let image = self.makeImage(image, data: data).cropping(to: selectedRect) { + return putToTemp(image: NSImage(cgImage: image, size: image.size), compress: true).start(next: { url in + subscriber.putNext(URL(fileURLWithPath: url)) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + } + } + + + } + subscriber.putCompletion() + return EmptyDisposable + } |> runOn(resourcesQueue) + } + +} + +final class EditImageControlsArguments { + let cancel:()->Void + let success: () -> Void + let flip: () -> Void + let selectionDimensions: (SelectionRectDimensions) -> Void + let rotate: () -> Void + let draw: ()->Void + init(cancel:@escaping()->Void, success: @escaping()->Void, flip: @escaping()->Void, selectionDimensions: @escaping(SelectionRectDimensions)->Void, rotate: @escaping() -> Void, draw: @escaping()->Void) { + self.cancel = cancel + self.success = success + self.flip = flip + self.rotate = rotate + self.selectionDimensions = selectionDimensions + self.draw = draw + } +} + +final class EditImageControlsView : View { + let cancel = TitleButton() + private let success = TitleButton() + private let controlsContainer = View() + private let flipper = ImageButton() + private let draw = ImageButton() + private let rotate = ImageButton() + private let dimensions = ImageButton() + private var currentData: EditedImageData? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + addSubview(cancel) + addSubview(success) + + addSubview(controlsContainer) + + controlsContainer.addSubview(flipper) + controlsContainer.addSubview(draw) + controlsContainer.addSubview(rotate) + controlsContainer.addSubview(dimensions) + + controlsContainer.border = [.Left, .Right] + controlsContainer.borderColor = NSColor.black.withAlphaComponent(0.2) + backgroundColor = NSColor(0x303030) + layer?.cornerRadius = 6 + } + + fileprivate func updateUserInterface(_ data: EditedImageData) { + draw.set(image: NSImage(named: "Icon_EditImageDraw")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + flipper.set(image: NSImage(named: "Icon_EditImageFlip")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + rotate.set(image: NSImage(named: "Icon_EditImageRotate")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + dimensions.set(image: NSImage(named: "Icon_EditImageSizes")!.precomposed(NSColor.white.withAlphaComponent(0.8)), for: .Normal) + + draw.set(image: NSImage(named: "Icon_EditImageDraw")!.precomposed(.white), for: .Hover) + flipper.set(image: NSImage(named: "Icon_EditImageFlip")!.precomposed(.white), for: .Hover) + rotate.set(image: NSImage(named: "Icon_EditImageRotate")!.precomposed(.white), for: .Hover) + dimensions.set(image: NSImage(named: "Icon_EditImageSizes")!.precomposed(.white), for: .Hover) + + draw.appTooltip = "⌘D" + rotate.appTooltip = "⌘R" + + _ = draw.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = flipper.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = rotate.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + _ = dimensions.sizeToFit(NSZeroSize, NSMakeSize(50, frame.height), thatFit: true) + + cancel.set(font: .medium(.title), for: .Normal) + success.set(font: .medium(.title), for: .Normal) + + success.set(color: nightAccentPalette.accent, for: .Normal) + cancel.set(color: .white, for: .Normal) + + cancel.set(text: L10n.modalCancel, for: .Normal) + success.set(text: L10n.navigationDone, for: .Normal) + + _ = cancel.sizeToFit(NSZeroSize, NSMakeSize(75, frame.height), thatFit: true) + _ = success.sizeToFit(NSZeroSize, NSMakeSize(75, frame.height), thatFit: true) + + flipper.isSelected = data.isHorizontalFlipped + rotate.isSelected = data.orientation != nil + dimensions.isSelected = data.dimensions != .none + draw.isSelected = !data.paintings.isEmpty + } + + override func layout() { + super.layout() + controlsContainer.setFrameSize(rotate.frame.width + flipper.frame.width + draw.frame.width + dimensions.frame.width, rotate.frame.height) + rotate.setFrameOrigin(NSMakePoint(0, 0)) + flipper.setFrameOrigin(NSMakePoint(rotate.frame.maxX, 0)) + draw.setFrameOrigin(NSMakePoint(flipper.frame.maxX, 0)) + dimensions.setFrameOrigin(NSMakePoint(draw.frame.maxX, 0)) + controlsContainer.center() + + cancel.centerY() + success.centerY(x: frame.width - success.frame.width) + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + fileprivate func set(settings: EditControllerSettings, handlers: EditImageControlsArguments) { + + success.set(handler: { _ in + handlers.success() + }, for: .Click) + + cancel.set(handler: { _ in + handlers.cancel() + }, for: .Click) + + flipper.set(handler: { control in + handlers.flip() + }, for: .Click) + + rotate.set(handler: { _ in + handlers.rotate() + }, for: .Click) + + draw.set(handler: { _ in + handlers.draw() + }, for: .Click) + + dimensions.set(handler: { control in + switch settings { + case .disableSizes: + break + case .plain: + if control.isSelected { + handlers.selectionDimensions(.none) + } else { + let items: [SPopoverItem] = SelectionRectDimensions.all.map { value in + return SPopoverItem(value.description, { + handlers.selectionDimensions(value) + }) + } + showPopover(for: control, with: SPopoverViewController(items: items, visibility: SelectionRectDimensions.all.count, handlerDelay: 0)) + } + } + + + }, for: .Click) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class EditImageControls: GenericViewController { + let arguments: EditImageControlsArguments + private let stateDisposable = MetaDisposable() + private let stateValue: Signal + private let settings: EditControllerSettings + init(settings:EditControllerSettings, arguments: EditImageControlsArguments, stateValue: Signal) { + self.arguments = arguments + self.stateValue = stateValue + self.settings = settings + super.init(frame: NSMakeRect(0, 0, 350, 40)) + bar = .init(height: 0) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.set(settings: settings, handlers: arguments) + stateDisposable.set(stateValue.start(next: { [weak self] current in + self?.genericView.updateUserInterface(current) + })) + } + + deinit { + stateDisposable.dispose() + } +} diff --git a/Telegram-Mac/EditImageModalController.swift b/Telegram-Mac/EditImageModalController.swift new file mode 100644 index 0000000000..aed5cf1448 --- /dev/null +++ b/Telegram-Mac/EditImageModalController.swift @@ -0,0 +1,446 @@ +// +// EditImagModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 01/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + + + + + +private final class EditImageView : View { + fileprivate let imageView: ImageView = ImageView() + private var image: CGImage + fileprivate let selectionRectView: SelectionRectView = SelectionRectView(frame: NSMakeRect(0, 0, 100, 100)) + private let imageContainer: View = View() + private let reset: TitleButton = TitleButton() + private var currentData: EditedImageData? + private let fakeCorners: (topLeft: ImageView, topRight: ImageView, bottomLeft: ImageView, bottomRight: ImageView) + private var canReset: Bool = false + + required init(frame frameRect: NSRect, image: CGImage) { + self.image = image + fakeCorners = (topLeft: ImageView(), topRight: ImageView(), bottomLeft: ImageView(), bottomRight: ImageView()) + let corners = generateSelectionAreaCorners(.white) + fakeCorners.topLeft.image = corners.topLeft + fakeCorners.topRight.image = corners.topRight + fakeCorners.bottomLeft.image = corners.bottomLeft + fakeCorners.bottomRight.image = corners.bottomRight + + fakeCorners.topLeft.sizeToFit() + fakeCorners.topRight.sizeToFit() + fakeCorners.bottomLeft.sizeToFit() + fakeCorners.bottomRight.sizeToFit() + + imageView.background = .white + + super.init(frame: frameRect) + + + imageContainer.addSubview(fakeCorners.topLeft) + imageContainer.addSubview(fakeCorners.topRight) + imageContainer.addSubview(fakeCorners.bottomLeft) + imageContainer.addSubview(fakeCorners.bottomRight) + + + imageView.wantsLayer = true + imageView.image = image + addSubview(imageContainer) + imageContainer.addSubview(imageView) + imageView.addSubview(selectionRectView) + addSubview(reset) + // reset.isHidden = true + autoresizesSubviews = false + + + + reset.set(font: .medium(.title), for: .Normal) + reset.set(color: .white, for: .Normal) + reset.set(text: L10n.editImageControlReset, for: .Normal) + _ = reset.sizeToFit() + + } + + var controls: View? { + didSet { + oldValue?.removeFromSuperview() + if let controls = controls { + addSubview(controls) + } + } + } + + var selectedRect: NSRect { + let multiplierX = self.imageView.image!.size.width / selectionRectView.frame.width + let multiplierY = self.imageView.image!.size.height / selectionRectView.frame.height + let rect = NSMakeRect(selectionRectView.selectedRect.minX, selectionRectView.selectedRect.minY, selectionRectView.selectedRect.width, selectionRectView.selectedRect.height) + return rect.apply(multiplier: NSMakeSize(multiplierX, multiplierY)) + } + + fileprivate func updateVisibleCorners() { + + } + + func applyEditedData(_ value: EditedImageData, canReset: Bool, reset: @escaping()->Void) { + if value.isNeedToRegenerate(currentData) { + self.imageView.image = value.makeImage(self.image) + } + self.currentData = value + + setFrameSize(frame.size) + + + if value.selectedRect != NSZeroRect { + self.selectionRectView.applyRect(value.selectedRect, force: self.selectionRectView.dimensions != value.dimensions, dimensions: value.dimensions) + } else { + selectionRectView.applyRect(imageView.bounds, dimensions: value.dimensions) + } + self.canReset = canReset + self.reset.isHidden = !canReset + self.reset.removeAllHandlers() + self.reset.set(handler: { _ in + reset() + }, for: .Click) + + needsLayout = true + + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + + if !imageView._mouseInside() && !controls!.mouseInside() && !selectionRectView.inDragging { + if let data = self.currentData, selectionRectView.isWholeSelected && data.hasntData { + (controls as? EditImageControlsView)?.cancel.send(event: .Click) + } else { + confirm(for: mainWindow, information: L10n.editImageControlConfirmDiscard, successHandler: { [weak self] _ in + (self?.controls as? EditImageControlsView)?.cancel.send(event: .Click) + }) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + + override func setFrameSize(_ newSize: NSSize) { + let oldSize = self.frame.size + super.setFrameSize(newSize) + + imageContainer.setFrameSize(frame.width, frame.height - 120) + + let imageSize = imageView.image!.size.fitted(NSMakeSize(imageContainer.frame.width - 8, imageContainer.frame.height - 8)) + let oldImageSize = imageView.frame.size + imageView.setFrameSize(imageSize) + selectionRectView.frame = imageView.bounds + + imageView.center() + + if oldSize != newSize, oldSize != NSZeroSize, inLiveResize { + let multiplier = NSMakeSize(imageSize.width / oldImageSize.width, imageSize.height / oldImageSize.height) + selectionRectView.applyRect(selectionRectView.selectedRect.apply(multiplier: multiplier)) + } + + + if let controls = controls { + controls.centerX(y: frame.height - controls.frame.height) + reset.centerX(y: controls.frame.minY - (80 - reset.frame.height) / 2) + } + + } + + func hideElements(_ hide: Bool) { + imageContainer.isHidden = hide + reset.isHidden = hide || !canReset + } + + func contentSize(maxSize: NSSize) -> NSSize { + return NSMakeSize(maxSize.width, maxSize.height) + } + + override func layout() { + super.layout() + fakeCorners.topLeft.setFrameOrigin(selectionRectView.convert(selectionRectView.topLeftPosition, to: fakeCorners.topLeft.superview)) + fakeCorners.topRight.setFrameOrigin(selectionRectView.convert(selectionRectView.topRightPosition, to: fakeCorners.topRight.superview)) + + fakeCorners.bottomLeft.setFrameOrigin(selectionRectView.convert(selectionRectView.bottomLeftPosition, to: fakeCorners.bottomLeft.superview)) + fakeCorners.bottomRight.setFrameOrigin(selectionRectView.convert(selectionRectView.bottomRightPosition, to: fakeCorners.bottomRight.superview)) + } + +} + +enum EditControllerSettings { + case disableSizes(dimensions: SelectionRectDimensions) + case plain +} + +class EditImageModalController: ModalViewController { + private let path: URL + private let editValue: ValuePromise = ValuePromise(ignoreRepeated: true) + private let editState: Atomic + private let updateDisposable = MetaDisposable() + private let updatedRectDisposable = MetaDisposable() + private var controls: EditImageControls! + private let image: CGImage + private let settings: EditControllerSettings + private let resultValue: Promise<(URL, EditedImageData?)> = Promise() + private var canReset: Bool + + var onClose: () -> Void = {} + + init(_ path: URL, defaultData: EditedImageData? = nil, settings: EditControllerSettings = .plain) { + self.canReset = defaultData != nil + editState = Atomic(value: defaultData ?? EditedImageData(originalUrl: path)) + + self.image = NSImage(contentsOf: path)!.cgImage(forProposedRect: nil, context: nil, hints: nil)! + self.path = path + self.settings = settings + super.init() + bar = .init(height: 0) + editValue.set(defaultData ?? EditedImageData(originalUrl: path)) + } + + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + super.close(animationType: animationType) + + onClose() + } + + + var result:Signal<(URL, EditedImageData?), NoError> { + return resultValue.get() + } + + private var markAsClosed: Bool = false + + override func returnKeyAction() -> KeyHandlerResult { + + guard !markAsClosed else { return .invoked } + + let currentData = editState.modify {$0} + resultValue.set(EditedImageData.generateNewUrl(data: currentData, selectedRect: genericView.selectedRect) |> map { ($0, $0 == currentData.originalUrl ? nil : currentData)}) + + + + let signal = resultValue.get() |> take(1) |> deliverOnMainQueue |> delay(0.1, queue: .mainQueue()) + markAsClosed = true + _ = signal.start(next: { [weak self] _ in + self?.close() + }) + + return .invoked + } + + override open func measure(size: NSSize) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:genericView.contentSize(maxSize: NSMakeSize(contentSize.width - 80, contentSize.height - 80)), animated: false) + } + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:genericView.contentSize(maxSize: NSMakeSize(contentSize.width - 80, contentSize.height - 80)), animated: animated) + } + } + + override var dynamicSize: Bool { + return true + } + + override func initializer() -> NSView { + return EditImageView.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), image: image) + } + + override var containerBackground: NSColor { + return .clear + } + + override func viewClass() -> AnyClass { + return EditImageView.self + } + + private var genericView: EditImageView { + return self.view as! EditImageView + } + + override var background: NSColor { + return .clear + } + override var isVisualEffectBackground: Bool { + return true + } + + + private func updateValue(_ f:@escaping(EditedImageData) -> EditedImageData) { + self.editValue.set(editState.modify(f)) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + switch settings { + case let .disableSizes(dimensions): + let imageSize = self.genericView.imageView.frame.size + let size = NSMakeSize(200, 200).aspectFitted(imageSize) + let rect = NSMakeRect((imageSize.width - size.width) / 2, (imageSize.height - size.height) / 2, size.width, size.height) + genericView.selectionRectView.isCircleCap = true + updateValue { data in + return data.withUpdatedDimensions(dimensions).withUpdatedSelectedRect(rect) + } + default: + genericView.selectionRectView.isCircleCap = false + } + } + + override var responderPriority: HandlerPriority { + return .modal + } + + override var handleAllEvents: Bool { + return true + } + + private func loadCanvas() { + guard let window = self.window else { + return + } + genericView.hideElements(true) + showModal(with: EditImageCanvasController(image: self.image, actions: editState.with { $0.paintings }, updatedImage: { [weak self] paintings in + self?.updateValue { + $0.withUpdatedPaintings(paintings) + } + }, closeHandler: { [weak self] in + self?.genericView.hideElements(false) + }), for: window, animated: false, animationType: .alpha) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.controls.arguments.rotate() + return .invoked + }, with: self, for: .R, priority: .modal, modifierFlags: [.command]) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.loadCanvas() + return .invoked + }, with: self, for: .D, priority: .modal, modifierFlags: [.command]) + + + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + private func rotate() { + var rect = NSZeroRect + let imageSize = genericView.imageView.frame.size + var isFlipped: Bool = false + var newRotation: ImageOrientation? + self.updateValue { current in + rect = current.selectedRect + let orientation: ImageOrientation? + if let value = current.orientation { + switch value { + case .right: + orientation = .down + case .down: + orientation = .left + default: + orientation = nil + } + } else { + orientation = .right + } + newRotation = orientation + isFlipped = current.isHorizontalFlipped + return current.withUpdatedOrientation(orientation) + } + +// if isFlipped, let newRotation = newRotation, newRotation == .right { +// rect.origin.x = imageSize.width - rect.maxX +// } else if isFlipped, newRotation == nil { +// rect.origin.x = imageSize.width - rect.maxX +// } + + let newSize = genericView.imageView.frame.size + let multiplierWidth = newSize.height / imageSize.width + let multiplierHeight = newSize.width / imageSize.height + + rect = rect.rotate90Degress(parentSize: imageSize) + rect = rect.apply(multiplier: NSMakeSize(multiplierHeight, multiplierWidth)) + + self.updateValue { current in + return current.withUpdatedSelectedRect(rect) + } + } + + private func flip() { + let imageSize = genericView.imageView.frame.size + updateValue { value in + var rect = value.selectedRect + rect.origin.x = imageSize.width - rect.maxX + return value.withUpdatedFlip(!value.isHorizontalFlipped).withUpdatedSelectedRect(rect) + } + } + + + override func viewDidLoad() { + super.viewDidLoad() + + self.controls = EditImageControls(settings: settings, arguments: EditImageControlsArguments(cancel: { [weak self] in + self?.close() + }, success: { [weak self] in + _ = self?.returnKeyAction() + }, flip: { [weak self] in + self?.flip() + }, selectionDimensions: { [weak self] dimension in + self?.updateValue { value in + return value.withUpdatedDimensions(dimension) + } + }, rotate: { [weak self] in + self?.rotate() + }, draw: { [weak self] in + self?.loadCanvas() + }), stateValue: editValue.get()) + + + + + + genericView.controls = self.controls.genericView + updateDisposable.set((editValue.get() |> deliverOnMainQueue).start(next: { [weak self] data in + guard let `self` = self else {return} + self.readyOnce() + self.updateSize(false) + self.genericView.applyEditedData(data, canReset: self.canReset, reset: { [weak self] in + self?.canReset = false + self?.updateValue {$0.withUpdatedSelectedRect(NSZeroRect).withUpdatedFlip(false).withUpdatedDimensions(.none).withUpdatedOrientation(nil).withUpdatedPaintings([])} + }) + })) + + updatedRectDisposable.set(genericView.selectionRectView.updatedRect.start(next: { [weak self] rect in + self?.updateValue { $0.withUpdatedSelectedRect(rect) } + self?.genericView.updateVisibleCorners() + })) + } + + deinit { + updateDisposable.dispose() + updatedRectDisposable.dispose() + } + +} diff --git a/Telegram-Mac/EditMessageModel.swift b/Telegram-Mac/EditMessageModel.swift index d4c570450b..d83b650f66 100644 --- a/Telegram-Mac/EditMessageModel.swift +++ b/Telegram-Mac/EditMessageModel.swift @@ -8,30 +8,176 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class EditMessageModel: ChatAccessoryModel { private var account:Account - private(set) var editMessage:Message - - init(message:Message , account:Account) { + private(set) var state:ChatEditState + private let fetchDisposable = MetaDisposable() + private var previousMedia: Media? + init(state:ChatEditState, account:Account) { self.account = account - self.editMessage = message + self.state = state super.init() - make(with :message) + make(with: state.message) + } + + override var view: ChatAccessoryView? { + didSet { + updateImageIfNeeded() + } + } + + override var size:NSSize { + didSet { + updateImageIfNeeded() + } + } + + override var frame: NSRect { + didSet { + updateImageIfNeeded() + } } + + override var leftInset: CGFloat { + var imageDimensions: CGSize? + let message = state.message + if !message.containsSecretMedia { + for media in message.media { + if let image = media as? TelegramMediaImage { + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.size + } + break + } else if let file = media as? TelegramMediaFile, file.isVideo { + if let dimensions = file.dimensions?.size { + imageDimensions = dimensions + } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isStaticSticker { + imageDimensions = representation.dimensions.size + } + break + } + } + } + + + if let _ = imageDimensions { + return 30 + super.leftInset * 2 + } + + return super.leftInset + } + func make(with message:Message) -> Void { - self.headerAttr = .initialize(string: tr(.chatInputAccessoryEditMessage), color: theme.colors.blueUI, font: .medium(.text)) + let attr = NSMutableAttributedString() + _ = attr.append(string: L10n.chatInputAccessoryEditMessage, color: theme.colors.accent, font: .medium(.text)) + + self.headerAttr = attr self.messageAttr = .initialize(string: pullText(from:message) as String, color: message.media.isEmpty ? theme.colors.text : theme.colors.grayText, font: .normal(.text)) nodeReady.set(.single(true)) + updateImageIfNeeded() self.setNeedDisplay() } + private func updateImageIfNeeded() { + if let view = self.view, view.frame != NSZeroRect { + let message = self.state.message + var updatedMedia: Media? + var imageDimensions: CGSize? + var hasRoundImage = false + if !message.containsSecretMedia { + for media in message.media { + if let image = media as? TelegramMediaImage { + updatedMedia = image + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.size + } + break + } else if let file = media as? TelegramMediaFile, file.isVideo { + updatedMedia = file + + if let dimensions = file.dimensions { + imageDimensions = dimensions.size + } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isStaticSticker { + imageDimensions = representation.dimensions.size + } + if file.isInstantVideo { + hasRoundImage = true + } + break + } + } + } + + + if let imageDimensions = imageDimensions { + let boundingSize = CGSize(width: 30.0, height: 30.0) + let arguments = TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets()) + + if view.imageView == nil { + view.imageView = TransformImageView() + } + view.imageView?.setFrameSize(boundingSize) + if view.imageView?.superview == nil { + view.addSubview(view.imageView!) + } + view.imageView?.setFrameOrigin(super.leftInset + (self.isSideAccessory ? 10 : 0), floorToScreenPixels(System.backingScale, self.topOffset + (self.size.height - self.topOffset - boundingSize.height)/2)) + + + let mediaUpdated = true + + + var updateImageSignal: Signal? + if mediaUpdated { + if let image = updatedMedia as? TelegramMediaImage { + updateImageSignal = chatMessagePhotoThumbnail(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: image), scale: view.backingScaleFactor) + } else if let file = updatedMedia as? TelegramMediaFile { + if file.isVideo { + updateImageSignal = chatMessageVideoThumbnail(account: self.account, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), scale: view.backingScaleFactor) + } else if let iconImageRepresentation = smallestImageRepresentation(file.previewRepresentations) { + let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + updateImageSignal = chatWebpageSnippetPhoto(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: tmpImage), scale: view.backingScaleFactor, small: true) + } + } + } + + if let updateImageSignal = updateImageSignal, let media = updatedMedia { + view.imageView?.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: view.backingScaleFactor)) + view.imageView?.setSignal(updateImageSignal, animate: true, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) + } + }) + if let media = media as? TelegramMediaImage { + self.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: media)).start()) + } + + view.imageView?.set(arguments: arguments) + if hasRoundImage { + view.imageView!.layer?.cornerRadius = 15 + } else { + view.imageView?.layer?.cornerRadius = 0 + } + } + } else { + view.imageView?.removeFromSuperview() + view.imageView = nil + } + + self.previousMedia = updatedMedia + } else { + self.view?.imageView?.removeFromSuperview() + self.view?.imageView = nil + } + } - - + deinit { + fetchDisposable.dispose() + } } diff --git a/Telegram-Mac/EditThemeController.swift b/Telegram-Mac/EditThemeController.swift new file mode 100644 index 0000000000..2d461f193d --- /dev/null +++ b/Telegram-Mac/EditThemeController.swift @@ -0,0 +1,475 @@ +// +// EditThemeController.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + +private let _id_no_preview1 = InputDataIdentifier("_id_no_preview1") +private let _id_no_preview2 = InputDataIdentifier("_id_no_preview2") +private let _id_uploadFile = InputDataIdentifier("_id_uploadFile") +private let _id_input_title = InputDataIdentifier("_id_input_title") +private let _id_input_slug = InputDataIdentifier("_id_input_slug") + +private struct EditThemeState : Equatable { + let current: TelegramTheme + let presentation: TelegramPresentationTheme + let name: String + let slug: String? + let path: String? + let errors:[InputDataIdentifier : InputDataValueError] + init(current:TelegramTheme, presentation: TelegramPresentationTheme, name: String, slug: String?, path: String?, errors:[InputDataIdentifier : InputDataValueError]) { + self.current = current + self.presentation = presentation + self.name = name + self.slug = slug + self.path = path + self.errors = errors + } + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> EditThemeState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return EditThemeState(current: self.current, presentation: self.presentation, name: self.name, slug: self.slug, path: self.path, errors: errors) + } + func withUpdatedName(_ name: String) -> EditThemeState { + return EditThemeState(current: self.current, presentation: self.presentation, name: name, slug: self.slug, path: self.path, errors: self.errors) + } + func withUpdatedSlug(_ slug: String?) -> EditThemeState { + return EditThemeState(current: self.current, presentation: self.presentation, name: self.name, slug: slug, path: self.path, errors: self.errors) + } + func withUpdatedPath(_ path: String?) -> EditThemeState { + return EditThemeState(current: self.current, presentation: self.presentation, name: self.name, slug: self.slug, path: path, errors: self.errors) + } + func withUpdatedPresentation(_ presentation: TelegramPresentationTheme) -> EditThemeState { + return EditThemeState(current: self.current, presentation: presentation, name: self.name, slug: self.slug, path: self.path, errors: self.errors) + } +} + +private final class EditThemeArguments { + let context: AccountContext + let updateFile:(String)->Void + let updateSlug:(String)->Void + init(context: AccountContext, updateFile:@escaping(String)->Void, updateSlug:@escaping(String)->Void) { + self.context = context + self.updateFile = updateFile + self.updateSlug = updateSlug + } +} + +private func editThemeEntries(state: EditThemeState, chatInteraction: ChatInteraction, arguments: EditThemeArguments) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index:Int32 = 0 + + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.name), error: state.errors[_id_input_title], identifier: _id_input_title, mode: .plain, data: InputDataRowData(), placeholder: nil, inputPlaceholder: L10n.editThemeNamePlaceholder, filter: { $0 }, limit: 128)) + index += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.slug), error: state.errors[_id_input_slug], identifier: _id_input_slug, mode: .plain, data: InputDataRowData(viewType: .legacy, defaultText: "https://t.me/addtheme/"), placeholder: nil, inputPlaceholder: "", filter: { $0 }, limit: 64)) + + + let slugDesc = L10n.editThemeSlugDesc + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(slugDesc), data: InputDataGeneralTextData())) + index += 1 + + let previewTheme = state.presentation + + + + let fromUser1 = TelegramUser(id: PeerId(1), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName1, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let fromUser2 = TelegramUser(id: PeerId(2), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName2, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let replyMessage = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreviewZeroText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let firstMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 0), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 20 + 60*60*18, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser2, text: tr(L10n.appearanceSettingsChatPreviewFirstText), attributes: [ReplyMessageAttribute(messageId: replyMessage.id)], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary([replyMessage.id : replyMessage]), associatedMessageIds: []) + let firstEntry: ChatHistoryEntry = .MessageEntry(firstMessage, MessageIndex(firstMessage), true, previewTheme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + let secondMessage = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreviewSecondText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let secondEntry: ChatHistoryEntry = .MessageEntry(secondMessage, MessageIndex(secondMessage), true, previewTheme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + entries.append(.sectionId(sectionId, type: .custom(10))) + sectionId += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_no_preview1, equatable: InputDataEquatable(state.presentation), item: { size, stableId in + let item = ChatRowItem.item(size, from: firstEntry, interaction: chatInteraction, theme: previewTheme) + _ = item.makeSize(size.width, oldWidth: 0) + return item + })) + index += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_no_preview2, equatable: InputDataEquatable(state.presentation), item: { size, stableId in + let item = ChatRowItem.item(size, from: secondEntry, interaction: chatInteraction, theme: previewTheme) + _ = item.makeSize(size.width, oldWidth: 0) + return item + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .custom(10))) + sectionId += 1 + + let selectFileText: String + let selectFileDesc: String + + if state.current.file == nil { + selectFileText = L10n.editThemeSelectFile + selectFileDesc = L10n.editThemeSelectFileDesc + } else { + selectFileText = L10n.editThemeSelectUpdatedFile + selectFileDesc = L10n.editThemeSelectUpdatedFileDesc + } + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_uploadFile, data: .init(name: selectFileText, color: theme.colors.accent, type: .context(state.path ?? ""), action: { + filePanel(with: ["palette"], allowMultiple: false, for: arguments.context.window, completion: { paths in + if let first = paths?.first { + arguments.updateFile(first) + } + }) + }))) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(selectFileDesc), data: InputDataGeneralTextData())) + index += 1 + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + return entries +} + +func EditThemeController(context: AccountContext, telegramTheme: TelegramTheme, presentation: TelegramPresentationTheme) -> InputDataModalController { + let initialState = EditThemeState(current: telegramTheme, presentation: presentation, name: telegramTheme.title, slug: telegramTheme.slug, path: nil, errors: [:]) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((EditThemeState) -> EditThemeState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context, disableSelectAbility: true) + + + + let slugDisposable = MetaDisposable() + let disposable = MetaDisposable() + let updateWallpaper = MetaDisposable() + + + func checkSlug(_ slug: String)->Void { + if slug.length >= 5 && slug != telegramTheme.slug { + let signal = getTheme(account: context.account, slug: slug) |> deliverOnMainQueue |> delay(0.2, queue: .mainQueue()) + slugDisposable.set(signal.start(next: { value in + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.editThemeSlugErrorAlreadyExists, target: .data), for: _id_input_slug) + } + }, error: { error in + switch error { + case .slugInvalid: + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.editThemeSlugErrorFormat, target: .data), for: _id_input_slug) + } + default: + updateState { + $0.withUpdatedError(nil, for: _id_input_slug) + } + } + + })) + } else { + slugDisposable.set(nil) + updateState { + $0.withUpdatedError(nil, for: _id_input_slug) + } + } + } + + let arguments = EditThemeArguments(context: context, updateFile: { path in + if let palette = importPalette(path) { + let presentation = stateValue.with { $0.presentation } + if palette.wallpaper != presentation.colors.wallpaper { + switch palette.wallpaper { + case let .url(string): + let link = inApp(for: string as NSString, context: context) + switch link { + case let .wallpaper(values): + switch values.preview { + case let .slug(slug, settings): + let signal: Signal<(Wallpaper, TelegramWallpaper?), NoError> = getWallpaper(network: context.account.network, slug: slug) + |> mapToSignal { cloud in + return moveWallpaperToCache(postbox: context.account.postbox, wallpaper: Wallpaper(cloud).withUpdatedSettings(settings)) |> map { wallpaper in + return (wallpaper, cloud) + } |> castError(GetWallpaperError.self) + } + |> `catch` { _ in + return .single((.none, nil)) + } + + updateWallpaper.set(showModalProgress(signal: signal |> deliverOnMainQueue, for: context.window).start(next: { wallpaper, cloud in + updateState { + $0.withUpdatedPresentation(presentation.withUpdatedColors(palette) + .withUpdatedWallpaper(ThemeWallpaper(wallpaper: wallpaper, associated: AssociatedWallpaper(cloud: cloud, wallpaper: wallpaper)))) + } + })) + default: + break + } + default: + break + } + default: + updateState { + $0.withUpdatedPresentation(presentation.withUpdatedColors(palette) + .withUpdatedWallpaper(ThemeWallpaper(wallpaper: palette.wallpaper.wallpaper, associated: AssociatedWallpaper(cloud: nil, wallpaper: palette.wallpaper.wallpaper)))) + } + } + } else { + updateState { + $0.withUpdatedPresentation(presentation.withUpdatedColors(palette)) + } + } + + + } else { + alert(for: context.window, info: L10n.unknownError) + } + + }, updateSlug: { slug in + let oldSlug = stateValue.with { $0.slug } + updateState { value in + var value = value.withUpdatedSlug(slug) + if oldSlug != slug { + value = value.withUpdatedError(nil, for: _id_input_slug) + } + return value + } + if oldSlug != slug { + checkSlug(slug) + } + }) + + + var close: (() -> Void)? = nil + + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: editThemeEntries(state: state, chatInteraction: chatInteraction, arguments: arguments)) + } + + + let save:()->InputDataValidation = { + return .fail(.doSomething(next: { f in + let state = stateValue.with { $0 } + slugDisposable.set(nil) + + let slug = state.slug ?? "" + + var failed:[InputDataIdentifier : InputDataValidationFailAction] = [:] + if !slug.isEmpty, slug.length < 5 { + failed[_id_input_slug] = .shake + } + if state.name.isEmpty { + failed[_id_input_title] = .shake + } + if !failed.isEmpty { + f(.fail(.fields(failed))) + return + } + + var mediaResource: MediaResource? = nil + var thumbnailData: Data? = nil + let newTheme: TelegramPresentationTheme = state.presentation + + if newTheme.colors != presentation.colors || state.current.file == nil { + let temp = NSTemporaryDirectory() + "\(arc4random()).palette" + try? newTheme.colors.withUpdatedName(state.name).toString.write(to: URL(fileURLWithPath: temp), atomically: true, encoding: .utf8) + mediaResource = LocalFileReferenceMediaResource(localFilePath: temp, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true, size: fs(temp)) + } + + if let _ = mediaResource { + let preview = generateThemePreview(for: newTheme.colors, wallpaper: newTheme.wallpaper.wallpaper, backgroundMode: newTheme.backgroundMode) + if let mutableData = CFDataCreateMutable(nil, 0), let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) { + CGImageDestinationAddImage(destination, preview, nil) + if CGImageDestinationFinalize(destination) { + let data = mutableData as Data + thumbnailData = data + } + } + } + + let updateSignal = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + if settings.cloudTheme?.id == telegramTheme.id { + let defaultCloud = DefaultCloudTheme(cloud: telegramTheme, palette: newTheme.colors, wallpaper: AssociatedWallpaper(cloud: newTheme.wallpaper.associated?.cloud, wallpaper: newTheme.wallpaper.wallpaper)) + + let defaultTheme = DefaultTheme(local: newTheme.colors.parent, cloud: defaultCloud) + var settings = settings.withUpdatedCloudTheme(telegramTheme).withUpdatedPalette(newTheme.colors) + if presentation.colors.isDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + return settings.withUpdatedDefaultIsDark(presentation.colors.isDark) + } else { + return settings + } + + }) |> mapError { _ in CreateThemeError.generic } + |> mapToSignal { + updateTheme(account: context.account, accountManager: context.sharedContext.accountManager, theme: telegramTheme, title: state.name, slug: state.slug, resource: mediaResource, thumbnailData: thumbnailData, settings: nil) + |> filter { + switch $0 { + case .progress: + return false + case .result: + return true + } + } + |> take(1) + } + + disposable.set(showModalProgress(signal: updateSignal, for: context.window).start(next: { _ in + delay(0.2, closure: { + close?() + }) + }, error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + case .slugOccupied: + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.editThameNameAlreadyTaken, target: .data), for: _id_input_slug) + } + f(.fail(.fields([_id_input_slug : .shake]))) + case .slugInvalid: + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.editThemeSlugErrorFormat, target: .data), for: _id_input_slug) + } + f(.fail(.fields([_id_input_slug : .shake]))) + } + })) + })) + } + + let controller = InputDataController(dataSignal: signal, title: L10n.editThemeTitle, validateData: { data in + + return save() + + }, updateDatas: { data in + var checkNext: Bool = false + updateState { value in + let oldSlug = value.slug + var value = value + value = value.withUpdatedName(data[_id_input_title]?.stringValue ?? value.name) + .withUpdatedSlug(data[_id_input_slug]?.stringValue ?? oldSlug) + .withUpdatedError(nil, for: _id_input_title) + if oldSlug != value.slug { + value = value.withUpdatedError(nil, for: _id_input_slug) + checkNext = true + } + return value + } + if checkNext { + checkSlug(stateValue.with { $0.slug } ?? "") + } + return .none + }, afterDisappear: { + disposable.dispose() + slugDisposable.dispose() + updateWallpaper.dispose() + }, afterTransaction: { controller in + let theme = stateValue.with { $0.presentation } + controller.genericView.tableView.getBackgroundColor = { + if !theme.bubbled { + return theme.colors.chatBackground + } else { + return .clear + } + } + controller.genericView.tableView.updateLocalizationAndTheme(theme: theme) + controller.genericView.backgroundMode = theme.controllerBackgroundMode + }, getBackgroundColor: { + theme.colors.background + }) + + + chatInteraction.getGradientOffsetRect = { [weak controller] in + guard let controller = controller else { + return .zero + } + let offset = controller.tableView.scrollPosition().current.rect.origin + return CGRect(origin: offset, size: controller.tableView.frame.size) + } + + let modalInteractions = ModalInteractions(acceptTitle: L10n.editThemeEdit, accept: { [weak controller] in + _ = controller?.returnKeyAction() + }, drawBorder: true, height: 50, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + controller.didLoaded = { controller, _ in + controller.tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak controller] position in + guard let controller = controller else { + return + } + controller.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + })) + + controller.tableView.afterSetupItem = { [weak controller] view, item in + guard let controller = controller else { + return + } + if let view = view as? ChatRowView { + let offset = controller.tableView.scrollPosition().current.rect.origin + view.updateBackground(animated: false) + } + } + + } + + close = { [weak modalController] in + modalController?.modal?.close() + } + + return modalController +} + + +func showEditThemeModalController(context: AccountContext, theme telegramTheme: TelegramTheme) { + if let file = telegramTheme.file, telegramTheme != theme.cloudTheme { + let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: file.resource)).start() + + + let signal = loadCloudPaletteAndWallpaper(context: context, file: file) |> afterDisposed { + fetchDisposable.dispose() + } + _ = showModalProgress(signal: signal |> deliverOnMainQueue, for: context.window).start(next: { data in + if let (palette, wallpaper, cloudWallpaper) = data { + let newTheme = theme.withUpdatedColors(palette).withUpdatedWallpaper(ThemeWallpaper(wallpaper: wallpaper, associated: AssociatedWallpaper(cloud: cloudWallpaper, wallpaper: wallpaper))) + showModal(with: EditThemeController(context: context, telegramTheme: telegramTheme, presentation: newTheme), for: context.window) + } else { + alert(for: context.window, info: L10n.unknownError) + } + }) + } else { + showModal(with: EditThemeController(context: context, telegramTheme: telegramTheme, presentation: theme), for: context.window) + } +} + + diff --git a/Telegram-Mac/EmojiToleranceController.swift b/Telegram-Mac/EmojiToleranceController.swift index d22609a118..13df957788 100644 --- a/Telegram-Mac/EmojiToleranceController.swift +++ b/Telegram-Mac/EmojiToleranceController.swift @@ -8,36 +8,38 @@ import Cocoa import TGUIKit -import PostboxMac +import Postbox private class EmojiTolerance : View { - init(frame frameRect: NSRect, emoji:String, handle:@escaping(String)->Void) { + init(frame frameRect: NSRect, emoji:String, handle:@escaping(String, String?)->Void) { super.init(frame: frameRect) + + let modifiers = emoji.emojiSkinToneModifiers var x:CGFloat = 2 - let add:(String)->Void = { [weak self] emoji in + let add:(String, String, String?)->Void = { [weak self] emoji, notModified, modifier in let button: TitleButton = TitleButton() button.set(font: .normal(.header), for: .Normal) button.set(text: emoji, for: .Normal) button.setFrameSize(NSMakeSize(30, 30)) - button.centerY(x: x) - button.set(background: theme.colors.background, for: .Normal) + button.centerY(x: x, addition: 4) + button.set(background: .clear, for: .Normal) button.set(background: theme.colors.grayForeground, for: .Highlight) button.layer?.cornerRadius = .cornerRadius self?.addSubview(button) x += button.frame.width button.set(handler: { _ in - handle(emoji) + handle(notModified, modifier) }, for: .Click) } - add(emoji) + add(emoji, emoji, nil) for modifier in modifiers { - add("\(emoji)\(modifier)") + add(emoji.emojiWithSkinModifier(modifier), emoji, modifier) } } @@ -62,12 +64,12 @@ class EmojiToleranceController: NSViewController { private let emoji:String - init(_ emoji:String, postbox: Postbox, handle:@escaping(String)->Void) { + init(_ emoji:String, postbox: Postbox, handle:@escaping(String, String?)->Void) { self.emoji = emoji super.init(nibName: nil, bundle: nil) - self.view = EmojiTolerance(frame: NSMakeRect(0, 0, 30 * 6 + 4, 34), emoji: emoji, handle: handle) + self.view = EmojiTolerance(frame: NSMakeRect(0, 4, 30 * 6 + 4, 34), emoji: emoji, handle: handle) } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/EmojiViewController.swift b/Telegram-Mac/EmojiViewController.swift index ae4aad8449..1be5c03218 100644 --- a/Telegram-Mac/EmojiViewController.swift +++ b/Telegram-Mac/EmojiViewController.swift @@ -8,25 +8,12 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore -var segmentNames:(Int)->String = { value in - var list:[String] = [] - list.append(tr(.emojiRecent)) - list.append(tr(.emojiSmilesAndPeople)) - list.append(tr(.emojiAnimalsAndNature)) - list.append(tr(.emojiFoodAndDrink)) - list.append(tr(.emojiActivityAndSport)) - list.append(tr(.emojiTravelAndPlaces)) - list.append(tr(.emojiObjects)) - list.append(tr(.emojiSymbols)) - list.append(tr(.emojiFlags)) - return list[value] -} - enum EmojiSegment : Int64, Comparable { case Recent = 0 case People = 1 @@ -38,6 +25,19 @@ enum EmojiSegment : Int64, Comparable { case Symbols = 7 case Flags = 8 + var localizedString: String { + switch self { + case .Recent: return L10n.emojiRecent + case .People: return L10n.emojiSmilesAndPeople + case .AnimalsAndNature: return L10n.emojiAnimalsAndNature + case .FoodAndDrink: return L10n.emojiFoodAndDrink + case .ActivityAndSport: return L10n.emojiActivityAndSport + case .TravelAndPlaces: return L10n.emojiTravelAndPlaces + case .Objects: return L10n.emojiObjects + case .Symbols: return L10n.emojiSymbols + case .Flags: return L10n.emojiFlags + } + } var hashValue:Int { return Int(self.rawValue) @@ -52,12 +52,14 @@ func <(lhs:EmojiSegment, rhs:EmojiSegment) -> Bool { return lhs.rawValue < rhs.rawValue } -private let emoji:[EmojiSegment:[String]] = { +let emojiesInstance:[EmojiSegment:[String]] = { assertNotOnMainThread() var local:[EmojiSegment:[String]] = [EmojiSegment:[String]]() let resource:URL? - if #available(OSX 10.12, *) { + if #available(OSX 10.14.1, *) { + resource = Bundle.main.url(forResource:"emoji1014-1", withExtension:"txt") + } else if #available(OSX 10.12, *) { resource = Bundle.main.url(forResource:"emoji", withExtension:"txt") } else { resource = Bundle.main.url(forResource:"emoji11", withExtension:"txt") @@ -91,7 +93,7 @@ private let emoji:[EmojiSegment:[String]] = { }() -private func segments(_ emoji: [EmojiSegment : [String]], skinModifiers: [String]) -> [EmojiSegment:[[NSAttributedString]]] { +private func segments(_ emoji: [EmojiSegment : [String]], skinModifiers: [EmojiSkinModifier]) -> [EmojiSegment:[[NSAttributedString]]] { var segments:[EmojiSegment:[[NSAttributedString]]] = [:] for (key,list) in emoji { @@ -101,16 +103,26 @@ private func segments(_ emoji: [EmojiSegment : [String]], skinModifiers: [String for emoji in list { - var e:String = emoji + var e:String = emoji.emojiUnmodified for modifier in skinModifiers { - if emoji.emojiUnmodified == modifier.emojiUnmodified { - e = modifier + if e == modifier.emoji { + if e.length == 5 { + let mutable = NSMutableString() + mutable.insert(e, at: 0) + mutable.insert(modifier.modifier, at: 2) + e = mutable as String + } else { + e = e + modifier.modifier + } } + + } + if !line.contains(where: {$0.string == String(e.first!) }), let first = e.first { + line.append(.initialize(string: String(first), font: .normal(26.0))) + i += 1 } - line.append(.initialize(string: e, font: NSFont.normal(.custom(26)))) - i += 1 if i == 8 { lines.append(line) @@ -135,27 +147,78 @@ fileprivate var isReady:Bool = false class EmojiControllerView : View { fileprivate let tableView:TableView = TableView(frame:NSZeroRect) + private let tabsContainer = View() fileprivate let tabs:HorizontalTableView = HorizontalTableView(frame:NSZeroRect) private let borderView:View = View() + private let emptyResults: ImageView = ImageView() + let searchView = SearchView(frame: .zero) + private let searchContainer = View() required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(tableView) - addSubview(tabs) - addSubview(borderView) - + + searchContainer.addSubview(searchView) + addSubview(searchContainer) + + addSubview(emptyResults) + + tabsContainer.addSubview(tabs) + tabsContainer.addSubview(borderView) + addSubview(tabsContainer) + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) self.backgroundColor = theme.colors.background self.borderView.backgroundColor = theme.colors.border + emptyResults.image = theme.icons.stickersEmptySearch + emptyResults.sizeToFit() + searchView.updateLocalizationAndTheme(theme: theme) + } + + private var searchState: SearchState? = nil + + func updateSearchState(_ searchState: SearchState, animated: Bool) { + self.searchState = searchState + switch searchState.state { + case .Focus: + tabsContainer.change(pos: NSMakePoint(0, -tabsContainer.frame.height), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + case .None: + tabsContainer.change(pos: NSMakePoint(0, 0), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + } + tableView.change(size: NSMakeSize(frame.width, frame.height - searchContainer.frame.maxY), animated: animated) + tableView.change(pos: NSMakePoint(0, searchContainer.frame.maxY), animated: animated) } + + func updateVisibility(_ isEmpty: Bool, isSearch: Bool) { + emptyResults.isHidden = !isEmpty + tableView.isHidden = isEmpty + tabs.isHidden = isSearch + borderView.isHidden = isSearch + } + + override func layout() { super.layout() - tableView.frame = NSMakeRect(0, 3.0, bounds.width , frame.height - 3.0 - 50) - tabs.frame = NSMakeRect(0, tableView.frame.maxY + 1, frame.width,49) - borderView.frame = NSMakeRect(0, frame.height - 50, frame.width, .borderSize) + + let initial: CGFloat = searchState?.state == .Focus ? -50 : 0 + + tabsContainer.frame = NSMakeRect(0, initial, frame.width, 50) + tabs.setFrameSize(NSMakeSize(frame.width - 8, 40)) + tabs.center() + + searchContainer.frame = NSMakeRect(0, tabsContainer.frame.maxY, frame.width, 50) + searchView.setFrameSize(NSMakeSize(frame.width - 20, 30)) + searchView.center() + + borderView.frame = NSMakeRect(0, tabsContainer.frame.height - .borderSize, frame.width, .borderSize) + tableView.frame = NSMakeRect(0, searchContainer.frame.maxY, frame.width , frame.height - searchContainer.frame.maxY) + emptyResults.center() } required init?(coder: NSCoder) { @@ -164,21 +227,45 @@ class EmojiControllerView : View { } class EmojiViewController: TelegramGenericViewController, TableViewDelegate { - private var disposable:MetaDisposable = MetaDisposable() - + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + private let searchValue = ValuePromise(.init(state: .None, request: nil)) + private var searchState: SearchState = .init(state: .None, request: nil) { + didSet { + self.searchValue.set(searchState) + } + } + private let disposable:MetaDisposable = MetaDisposable() + private let searchStateDisposable = MetaDisposable() + private var interactions:EntertainmentInteractions? - - override init(_ account: Account) { - super.init(account) + var makeSearchCommand:((ESearchCommand)->Void)? + private func updateSearchState(_ state: SearchState) { + self.searchState = state + if !state.request.isEmpty { + self.makeSearchCommand?(.loading) + } + if self.isLoaded() == true { + self.genericView.updateSearchState(state, animated: true) + self.genericView.tableView.scroll(to: .up(true)) + } + } + + override init(_ context: AccountContext) { + super.init(context) + + _frameRect = NSMakeRect(0, 0, 350, 300) self.bar = .init(height: 0) } + override func loadView() { super.loadView() genericView.tabs.delegate = self - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } @@ -186,7 +273,7 @@ class EmojiViewController: TelegramGenericViewController, T return true } - func selectionWillChange(row: Int, item: TableRowItem) -> Bool { + func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { return true } @@ -194,9 +281,9 @@ class EmojiViewController: TelegramGenericViewController, T } - func loadResource() -> Signal { + func loadResource() -> Signal { return Signal { (subscriber) -> Disposable in - _ = emoji + _ = emojiesInstance subscriber.putNext(Void()) subscriber.putCompletion() return ActionDisposable(action: { @@ -207,10 +294,12 @@ class EmojiViewController: TelegramGenericViewController, T deinit { disposable.dispose() + searchStateDisposable.dispose() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + genericView.needsLayout = true } override func viewDidAppear(_ animated: Bool) { @@ -221,77 +310,45 @@ class EmojiViewController: TelegramGenericViewController, T override func viewDidLoad() { super.viewDidLoad() + + let searchInteractions = SearchInteractions({ [weak self] state, _ in + self?.updateSearchState(state) + }, { [weak self] state in + self?.updateSearchState(state) + }) + + genericView.searchView.searchInteractions = searchInteractions + + // DO NOT WRITE CODE OUTSIZE READY BLOCK - let ready:(RecentUsedEmoji)->Void = { [weak self] recent in + let ready:(RecentUsedEmoji, [String]?)->Void = { [weak self] recent, search in if let strongSelf = self { - strongSelf.readyForDisplay(recent) + strongSelf.makeSearchCommand?(.normal) + strongSelf.readyForDisplay(recent, search) strongSelf.readyOnce() - } - } - let s:Signal = combineLatest(loadResource(), recentUsedEmoji(postbox: account.postbox), appearanceSignal) |> deliverOnMainQueue + let context = self.context - disposable.set(s.start(next: { (_, recent, _) in + let s:Signal = combineLatest(queue: resourcesQueue, loadResource(), recentUsedEmoji(postbox: context.account.postbox), appearanceSignal, self.searchValue.get() |> distinctUntilChanged(isEqual: { prev, new in + return prev.request == new.request + }) |> mapToSignal { state -> Signal<[String]?, NoError> in + if state.request.isEmpty { + return .single(nil) + } else { + return context.sharedContext.inputSource.searchEmoji(postbox: context.account.postbox, sharedContext: context.sharedContext, query: state.request, completeMatch: false, checkPrediction: false) |> map(Optional.init) |> delay(0.2, queue: .concurrentDefaultQueue()) + } + }) |> deliverOnMainQueue + + disposable.set(s.start(next: { (_, recent, _, search) in isReady = true - ready(recent) + + ready(recent, search) })) - } - - func readyForDisplay(_ recent: RecentUsedEmoji) -> Void { - - genericView.tableView.removeAll() - genericView.tabs.removeAll() - var e = emoji - e[EmojiSegment.Recent] = recent.emojies - let seg = segments(e, skinModifiers: recent.skinModifiers) - let seglist = seg.map { (key,_) -> EmojiSegment in - return key - }.sorted(by: <) - let w = floorToScreenPixels(frame.width / CGFloat(seg.count)) - - genericView.tabs.setFrameSize(NSMakeSize(w * CGFloat(seg.count), genericView.tabs.frame.height)) - genericView.tabs.centerX() - let initialSize = atomicSize - var tabIcons:[CGImage] = [] - tabIcons.append(theme.icons.emojiRecentTab) - tabIcons.append(theme.icons.emojiSmileTab) - tabIcons.append(theme.icons.emojiNatureTab) - tabIcons.append(theme.icons.emojiFoodTab) - tabIcons.append(theme.icons.emojiSportTab) - tabIcons.append(theme.icons.emojiCarTab) - tabIcons.append(theme.icons.emojiObjectsTab) - tabIcons.append(theme.icons.emojiSymbolsTab) - tabIcons.append(theme.icons.emojiFlagsTab) - - var tabIconsSelected:[CGImage] = [] - tabIconsSelected.append(theme.icons.emojiRecentTabActive) - tabIconsSelected.append(theme.icons.emojiSmileTabActive) - tabIconsSelected.append(theme.icons.emojiNatureTabActive) - tabIconsSelected.append(theme.icons.emojiFoodTabActive) - tabIconsSelected.append(theme.icons.emojiSportTabActive) - tabIconsSelected.append(theme.icons.emojiCarTabActive) - tabIconsSelected.append(theme.icons.emojiObjectsTabActive) - tabIconsSelected.append(theme.icons.emojiSymbolsTabActive) - tabIconsSelected.append(theme.icons.emojiFlagsTabActive) - for key in seglist { - if key != .Recent { - let _ = genericView.tableView.addItem(item: EStickItem(initialSize.modify({$0}), segment:key, segmentName:segmentNames(key.hashValue))) - } - let _ = genericView.tableView.addItem(item: EBlockItem(initialSize.modify({$0}), attrLines: seg[key]!, segment: key, account: account, selectHandler: { [weak self] emoji in - if let interactions = self?.interactions { - interactions.sendEmoji(emoji) - } - } )) - let _ = genericView.tabs.addItem(item: ETabRowItem(initialSize.modify({$0}), icon: tabIcons[key.hashValue], iconSelected:tabIconsSelected[key.hashValue], stableId:key.rawValue, width:w, clickHandler:{[weak self] (stableId) in - self?.scrollTo(stableId: stableId) - })) - } - //set(stickClass: TableStickItem.self, handler:(Table)) genericView.tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] _ in if let view = self?.genericView { view.tableView.enumerateVisibleItems(with: { item -> Bool in @@ -306,10 +363,83 @@ class EmojiViewController: TelegramGenericViewController, T })) } + func readyForDisplay(_ recent: RecentUsedEmoji, _ search: [String]?) -> Void { + + + let initialSize = atomicSize.modify({$0}) + genericView.tableView.beginTableUpdates() + genericView.tableView.removeAll() + genericView.tabs.removeAll() + + if let search = search { + + let lines = search.chunks(8).map({ clues -> [NSAttributedString] in + return clues.map({NSAttributedString.initialize(string: $0, font: .normal(26.0))}) + }) + if lines.count > 0 { + let _ = genericView.tableView.addItem(item: EBlockItem(initialSize, attrLines: lines, segment: .Recent, account: context.account, selectHandler: { [weak self] emoji in + self?.interactions?.sendEmoji(emoji) + })) + } + + } else { + var e = emojiesInstance + e[EmojiSegment.Recent] = recent.emojies + let seg = segments(e, skinModifiers: recent.skinModifiers) + let seglist = seg.map { (key,_) -> EmojiSegment in + return key + }.sorted(by: <) + + + + let w = floorToScreenPixels(System.backingScale, frame.width / CGFloat(seg.count)) + + var tabIcons:[CGImage] = [] + tabIcons.append(theme.icons.emojiRecentTab) + tabIcons.append(theme.icons.emojiSmileTab) + tabIcons.append(theme.icons.emojiNatureTab) + tabIcons.append(theme.icons.emojiFoodTab) + tabIcons.append(theme.icons.emojiSportTab) + tabIcons.append(theme.icons.emojiCarTab) + tabIcons.append(theme.icons.emojiObjectsTab) + tabIcons.append(theme.icons.emojiSymbolsTab) + tabIcons.append(theme.icons.emojiFlagsTab) + + var tabIconsSelected:[CGImage] = [] + tabIconsSelected.append(theme.icons.emojiRecentTabActive) + tabIconsSelected.append(theme.icons.emojiSmileTabActive) + tabIconsSelected.append(theme.icons.emojiNatureTabActive) + tabIconsSelected.append(theme.icons.emojiFoodTabActive) + tabIconsSelected.append(theme.icons.emojiSportTabActive) + tabIconsSelected.append(theme.icons.emojiCarTabActive) + tabIconsSelected.append(theme.icons.emojiObjectsTabActive) + tabIconsSelected.append(theme.icons.emojiSymbolsTabActive) + tabIconsSelected.append(theme.icons.emojiFlagsTabActive) + for key in seglist { + if key != .Recent { + let _ = genericView.tableView.addItem(item: EStickItem(initialSize, segment:key, segmentName:key.localizedString)) + } + let _ = genericView.tableView.addItem(item: EBlockItem(initialSize, attrLines: seg[key]!, segment: key, account: context.account, selectHandler: { [weak self] emoji in + self?.interactions?.sendEmoji(emoji) + })) + let _ = genericView.tabs.addItem(item: ETabRowItem(initialSize, icon: tabIcons[key.hashValue], iconSelected:tabIconsSelected[key.hashValue], stableId:key.rawValue, width:w, clickHandler:{[weak self] (stableId) in + self?.scrollTo(stableId: stableId) + })) + } + } + genericView.tableView.endTableUpdates() + genericView.updateVisibility(genericView.tableView.isEmpty, isSearch: search != nil) + + // self.genericView.tableView.scroll(to: .up(true)) + } + func update(with interactions: EntertainmentInteractions) { self.interactions = interactions } + override var supportSwipes: Bool { + return !genericView.tabs._mouseInside() + } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -317,8 +447,10 @@ class EmojiViewController: TelegramGenericViewController, T func scrollTo(stableId:AnyHashable) -> Void { genericView.tabs.changeSelection(stableId: stableId) - genericView.tableView.scroll(to: .top(id: stableId, animated: true, focus: false, inset: 0), inset:NSEdgeInsets(top:3)) + genericView.tableView.scroll(to: .top(id: stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), inset:NSEdgeInsets(top:3)) + } + override func scrollup(force: Bool = false) { + self.genericView.tableView.scroll(to: .up(true)) } - } diff --git a/Telegram-Mac/EmptyChatViewController.swift b/Telegram-Mac/EmptyChatViewController.swift index c0f968e2ff..fd9a49f6a1 100644 --- a/Telegram-Mac/EmptyChatViewController.swift +++ b/Telegram-Mac/EmptyChatViewController.swift @@ -8,7 +8,8 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore class EmptyChatView : View { private let containerView: View = View() @@ -16,20 +17,39 @@ class EmptyChatView : View { private let imageView:ImageView = ImageView() required init(frame frameRect: NSRect) { super.init(frame: frameRect) + self.layer = CAGradientLayer() + self.layer?.disableActions() + addSubview(containerView) containerView.addSubview(imageView) containerView.addSubview(label) - updateLocalizationAndTheme() + label.userInteractionEnabled = false + label.isSelectable = false + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) containerView.backgroundColor = theme.colors.background - self.background = theme.colors.background + let theme = (theme as! TelegramPresentationTheme) + //theme.chatServiceItemColor + + self.background = .clear imageView.image = theme.icons.chatEmpty + switch theme.controllerBackgroundMode { + case .plain: + imageView.isHidden = false + default: + imageView.isHidden = true + } + + containerView.backgroundColor = imageView.isHidden ? .clear : theme.chatBackground + + imageView.sizeToFit() - label.backgroundColor = theme.colors.background - label.update(TextViewLayout(.initialize(string: tr(.emptyPeerDescription), color: theme.colors.grayText, font: .normal(.header)), maximumNumberOfLines: 1)) + label.disableBackgroundDrawing = true + label.backgroundColor = imageView.isHidden ? theme.chatServiceItemColor : theme.chatBackground + label.update(TextViewLayout(.initialize(string: L10n.emptyPeerDescription, color: imageView.isHidden ? theme.chatServiceItemTextColor : theme.colors.grayText, font: .medium(imageView.isHidden ? .text : .header)), maximumNumberOfLines: 1, alignment: .center)) needsLayout = true } @@ -41,20 +61,55 @@ class EmptyChatView : View { super.layout() label.layout?.measure(width: frame.size.width - 20) label.update(label.layout) - containerView.setFrameSize(frame.size.width - 20, imageView.frame.size.height + label.frame.size.height + 30) - imageView.centerX() - containerView.center() - label.centerX(y: imageView.frame.maxY + 30) + + if imageView.isHidden { + + label.setFrameSize(label.frame.width + 16, label.frame.height + 6) + + containerView.setFrameSize(label.frame.width + 20, 24) + containerView.center() + label.center() + label.layer?.cornerRadius = label.frame.height / 2 + containerView.layer?.cornerRadius = containerView.frame.height / 2 + } else { + containerView.setFrameSize(max(imageView.frame.width, label.frame.width) + 40, imageView.frame.size.height + label.frame.size.height + 70) + imageView.centerX(y: 20) + containerView.center() + label.centerX(y: imageView.frame.maxY + 30) + containerView.layer?.cornerRadius = 0 + } + + + } } class EmptyChatViewController: TelegramGenericViewController { - override init(_ account: Account) { - super.init(account) + + override init(_ context: AccountContext) { + super.init(context) self.bar = NavigationBarStyle(height:0) } + private var temporaryTouchBar: Any? + + @available(OSX 10.12.2, *) + override func makeTouchBar() -> NSTouchBar? { + if temporaryTouchBar == nil { + temporaryTouchBar = ChatListTouchBar(context: self.context, search: { [weak self] in + self?.context.sharedContext.bindings.globalSearch("") + }, newGroup: { [weak self] in + self?.context.composeCreateGroup() + }, newSecretChat: { [weak self] in + self?.context.composeCreateSecretChat() + }, newChannel: { [weak self] in + self?.context.composeCreateChannel() + }) + } + return temporaryTouchBar as? NSTouchBar + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) (navigationController as? MajorNavigationController)?.closeSidebar() @@ -63,10 +118,23 @@ class EmptyChatViewController: TelegramGenericViewController { override func escapeKeyAction() -> KeyHandlerResult { return .rejected } + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + updateBackgroundColor(theme.controllerBackgroundMode) + } + + override public var isOpaque: Bool { + return false + } + + override var responderPriority: HandlerPriority { + return .medium + } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - globalPeerHandler.set(.single(nil)) + context.globalPeerHandler.set(.single(nil)) } override func viewDidLoad() { diff --git a/Telegram-Mac/EmptyComposeController.swift b/Telegram-Mac/EmptyComposeController.swift index cae9abd175..a786f20908 100644 --- a/Telegram-Mac/EmptyComposeController.swift +++ b/Telegram-Mac/EmptyComposeController.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore class ComposeState { let result:T diff --git a/Telegram-Mac/EmptyGroupstickerSearchRowItem.swift b/Telegram-Mac/EmptyGroupstickerSearchRowItem.swift index 9779feae42..7841fe076c 100644 --- a/Telegram-Mac/EmptyGroupstickerSearchRowItem.swift +++ b/Telegram-Mac/EmptyGroupstickerSearchRowItem.swift @@ -11,8 +11,8 @@ import TGUIKit class EmptyGroupstickerSearchRowItem: GeneralRowItem { - init(_ initialSize: NSSize, height: CGFloat, stableId: AnyHashable, inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0)) { - super.init(initialSize, height: height, stableId: stableId, inset: inset) + init(_ initialSize: NSSize, height: CGFloat, stableId: AnyHashable, viewType: GeneralViewType = .legacy) { + super.init(initialSize, height: height, stableId: stableId, viewType: viewType) } override func viewClass() -> AnyClass { @@ -22,43 +22,75 @@ class EmptyGroupstickerSearchRowItem: GeneralRowItem { private class EmptyGroupstickerSearchRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) private let headerView: TextView = TextView() private let descView: TextView = TextView() private let imageView: ImageView = ImageView() required init(frame frameRect: NSRect) { super.init(frame: frameRect) - addSubview(imageView) - addSubview(headerView) - addSubview(descView) + containerView.addSubview(imageView) + containerView.addSubview(headerView) + containerView.addSubview(descView) + addSubview(containerView) } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) + + if let item = item as? GeneralRowItem { + let contentRect: NSRect + switch item.viewType { + case .legacy: + contentRect = bounds + case .modern: + contentRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + } + self.containerView.change(size: contentRect.size, animated: animated, corners: item.viewType.corners) + self.containerView.change(pos: contentRect.origin, animated: animated) + } + imageView.image = theme.icons.groupStickerNotFound imageView.sizeToFit() needsLayout = true } + override var backdorColor: NSColor { + return theme.colors.background + } + override func updateColors() { - descView.backgroundColor = backdorColor - headerView.backgroundColor = backdorColor + if let item = item as? GeneralRowItem { + descView.backgroundColor = backdorColor + headerView.backgroundColor = backdorColor + containerView.backgroundColor = backdorColor + backgroundColor = item.viewType.rowBackground + } } override func layout() { super.layout() if let item = item as? EmptyGroupstickerSearchRowItem { - let headerLayout = TextViewLayout(.initialize(string: tr(.groupStickersEmptyHeader), color: theme.colors.redUI, font: .medium(.text)), maximumNumberOfLines: 1) - let descLayout = TextViewLayout(.initialize(string: tr(.groupStickersEmptyDesc), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) - headerLayout.measure(width: frame.width - item.inset.left - item.inset.right - 40) - descLayout.measure(width: frame.width - item.inset.left - item.inset.right - 40) - descView.update(descLayout) - headerView.update(headerLayout) - - headerView.setFrameOrigin(item.inset.left + 40, 8) - descView.setFrameOrigin(item.inset.left + 40, frame.height - descView.frame.height - 8) - - imageView.centerY(x: item.inset.left) + switch item.viewType { + case .legacy: + containerView.frame = bounds + case let .modern(_, insets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + + let headerLayout = TextViewLayout(.initialize(string: L10n.groupStickersEmptyHeader, color: theme.colors.redUI, font: .medium(.text)), maximumNumberOfLines: 1) + let descLayout = TextViewLayout(.initialize(string: L10n.groupStickersEmptyDesc, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + headerLayout.measure(width: item.blockWidth - insets.left - insets.right - 40) + descLayout.measure(width: item.blockWidth - insets.left - insets.right - 40) + descView.update(descLayout) + headerView.update(headerLayout) + + headerView.setFrameOrigin(insets.left + 40, 8) + descView.setFrameOrigin(insets.left + 40, containerView.frame.height - descView.frame.height - 8) + + imageView.centerY(x: insets.left) + + } + self.containerView.setCorners(item.viewType.corners) } } diff --git a/Telegram-Mac/EntertainmentViewController.swift b/Telegram-Mac/EntertainmentViewController.swift index d2fa23459d..07d2a821ac 100644 --- a/Telegram-Mac/EntertainmentViewController.swift +++ b/Telegram-Mac/EntertainmentViewController.swift @@ -8,38 +8,613 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + + +enum ESearchCommand { + case loading + case normal + case close + case clearText + case apply(String) +} + +open class EntertainmentSearchView: OverlayControl, NSTextViewDelegate { + + public private(set) var state:SearchFieldState = .None + private(set) public var input:NSTextView = SearchTextField() + + private var lock:Bool = false + + private let clear:ImageButton = ImageButton() + private let search:ImageView = ImageView() + private let progressIndicator:ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 18, 18)) + private let placeholder:TextViewLabel = TextViewLabel() + + + public let inset:CGFloat = 6 + public let leftInset:CGFloat = 20.0 + + public var searchInteractions:SearchInteractions? + + private let _searchValue:ValuePromise = ValuePromise(SearchState(state: .None, request: nil), ignoreRepeated: true) + + public var searchValue: Signal { + return _searchValue.get() + } + public var shouldUpdateTouchBarItemIdentifiers: (()->[Any])? + + + private let inputContainer = View() + + public var isLoading:Bool = false { + didSet { + if oldValue != isLoading { + self.updateLoading() + needsLayout = true + } + } + } + + override open func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + let theme = (theme as! TelegramPresentationTheme) + inputContainer.backgroundColor = .clear + input.textColor = presentation.search.textColor + input.backgroundColor = presentation.colors.background + placeholder.attributedString = .initialize(string: presentation.search.placeholder(), color: presentation.search.placeholderColor, font: .normal(.title)) + placeholder.backgroundColor = presentation.colors.background + self.backgroundColor = presentation.colors.background + placeholder.sizeToFit() + search.image = theme.icons.entertainment_Search + search.sizeToFit() + clear.set(image: theme.icons.entertainment_SearchCancel, for: .Normal) + _ = clear.sizeToFit() + input.insertionPointColor = presentation.search.textColor + progressIndicator.progressColor = theme.colors.grayIcon + needsLayout = true + + } + + open var startTextInset: CGFloat { + return leftInset + } + + open var placeholderTextInset: CGFloat { + return startTextInset + } + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.backgroundColor = .grayBackground + if #available(OSX 10.12.2, *) { + input.allowsCharacterPickerTouchBarItem = false + } + progressIndicator.isHidden = true + input.focusRingType = .none + input.autoresizingMask = [.width, .height] + input.backgroundColor = NSColor.clear + input.delegate = self + input.isRichText = false + + input.textContainer?.widthTracksTextView = true + input.textContainer?.heightTracksTextView = false + + input.isHorizontallyResizable = false + input.isVerticallyResizable = false + + input.font = .normal(.title) + input.textColor = .text + input.isHidden = true + input.drawsBackground = false + + input.setFrameSize(20, 18) + + placeholder.sizeToFit() + self.border = [.Bottom] + + //self.addSubview(search) + self.addSubview(placeholder) + inputContainer.addSubview(input) + addSubview(inputContainer) + inputContainer.backgroundColor = .clear + clear.backgroundColor = .clear + + + clear.set(handler: { [weak self] _ in + self?.cancelSearch() + }, for: .Click) + + addSubview(clear) + + clear.isHidden = true + + + self.set(handler: {[weak self] (event) in + if let strongSelf = self { + strongSelf.change(state: .Focus , true) + } + }, for: .Click) + + updateLocalizationAndTheme(theme: theme) + + + + progressIndicator.set(handler: { [weak self] _ in + self?.cancelSearch() + }, for: .Click) + + } + + @available(OSX 10.12.2, *) + public func textView(_ textView: NSTextView, shouldUpdateTouchBarItemIdentifiers identifiers: [NSTouchBarItem.Identifier]) -> [NSTouchBarItem.Identifier] { + return self.shouldUpdateTouchBarItemIdentifiers?() as? [NSTouchBarItem.Identifier] ?? identifiers + } + + open func cancelSearch() { + if self.query.isEmpty { + change(state: .None, true) + } else { + setString("") + } + } + + open func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { + if let trimmed = replacementString?.trimmed, trimmed.isEmpty, affectedCharRange.min == 0 && affectedCharRange.max == 0, textView.string.isEmpty { + return false + } + if replacementString == "\n" { + return false + } + return true + } + + + + open func textDidChange(_ notification: Notification) { + + let trimmed = input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")) + if trimmed != input.string { + self.setString(trimmed) + return + } + + let value = SearchState(state: state, request: trimmed, responder: self.input == window?.firstResponder) + searchInteractions?.textModified(value) + _searchValue.set(value) + + + let pHidden = !input.string.isEmpty + if placeholder.isHidden != pHidden { + placeholder.isHidden = pHidden + } + + needsLayout = true + + let iHidden = !(state == .Focus && !input.string.isEmpty) + if input.isHidden != iHidden { + // input.isHidden = iHidden + window?.makeFirstResponder(input) + } + } + + open override func mouseUp(with event: NSEvent) { + if isLoading { + let point = convert(event.locationInWindow, from: nil) + if NSPointInRect(point, progressIndicator.frame) { + setString("") + } else { + super.mouseUp(with: event) + } + } else { + super.mouseUp(with: event) + } + } + + + public func textViewDidChangeSelection(_ notification: Notification) { + if let storage = input.textStorage { + let size = storage.size() + + let inputInset = placeholderTextInset + + let defWidth = frame.width - inputInset - inset - clear.frame.width - 10 + // input.sizeToFit() + input.setFrameSize(max(size.width + 10, defWidth), size.height) + // inputContainer.setFrameSize(inputContainer.frame.width, input.frame.height) + if let layout = input.layoutManager, !input.string.isEmpty { + let index = max(0, input.selectedRange().max - 1) + let point = layout.location(forGlyphAt: layout.glyphIndexForCharacter(at: index)) + + let additionalInset: CGFloat + if index + 2 < input.string.length { + let nextPoint = layout.location(forGlyphAt: layout.glyphIndexForCharacter(at: index + 2)) + additionalInset = nextPoint.x - point.x + } else { + additionalInset = 8 + } + + if defWidth < size.width && point.x > defWidth { + input.setFrameOrigin(floorToScreenPixels(backingScaleFactor, defWidth - point.x - additionalInset), input.frame.minY) + if input.frame.maxX < inputContainer.frame.width { + input.setFrameOrigin(inputContainer.frame.width - input.frame.width + 4, input.frame.minY) + } + } else { + input.setFrameOrigin(0, input.frame.minY) + } + } else { + input.setFrameOrigin(0, input.frame.minY) + } + needsLayout = true + } + } + + open func textDidEndEditing(_ notification: Notification) { + didResignResponder() + } + + open func textDidBeginEditing(_ notification: Notification) { + didBecomeResponder() + } + + open var isEmpty: Bool { + return query.isEmpty + } + + open func didResignResponder() { + let value = SearchState(state: state, request: self.query, responder: false) + searchInteractions?.responderModified(value) + _searchValue.set(value) + if isEmpty { + change(state: .None, true) + } + + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + + open func didBecomeResponder() { + let value = SearchState(state: state, request: self.query, responder: true) + searchInteractions?.responderModified(SearchState(state: state, request: self.query, responder: true)) + _searchValue.set(value) + + change(state: .Focus, true) + + self.kitWindow?.set(escape: {[weak self] () -> KeyHandlerResult in + if let strongSelf = self { + return strongSelf.changeResponder() ? .invoked : .rejected + } + return .rejected + + }, with: self, priority: .modal) + + self.kitWindow?.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.state == .Focus { + return .invokeNext + } + return .rejected + }, with: self, for: .RightArrow, priority: .modal) + + self.kitWindow?.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.state == .Focus { + return .invokeNext + } + return .rejected + }, with: self, for: .LeftArrow, priority: .modal) + + self.kitWindow?.set(responder: {[weak self] () -> NSResponder? in + return self?.input + }, with: self, priority: .modal) + } + + + open override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + } + + + + open func change(state:SearchFieldState, _ animated:Bool) -> Void { + + if state != self.state && !lock { + self.state = state + + let text = input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")) + let value = SearchState(state: state, request: state == .None ? nil : text, responder: self.input == window?.firstResponder) + searchInteractions?.stateModified(value, animated) + + _searchValue.set(value) + + lock = true + + if state == .Focus { + + window?.makeFirstResponder(input) + + let inputInset = placeholderTextInset + 8 + + inputContainer.setFrameSize(frame.width - inputInset - inset - clear.frame.width - 6, input.frame.height) + inputContainer.centerY(x: inputInset) + input.frame = inputContainer.bounds + + input.isHidden = false + + self.input.isHidden = false + self.window?.makeFirstResponder(self.input) + self.lock = false + + clear.isHidden = false + clear.layer?.opacity = 1.0 + + } + + if state == .None { + + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + + self.input.isHidden = true + self.input.string = "" + self.window?.makeFirstResponder(nil) + self.placeholder.isHidden = false + + if animated { + + clear.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion:true, additive:false, completion: {[weak self] (complete) in + self?.clear.isHidden = true + self?.lock = false + }) + } else { + clear.isHidden = true + lock = false + } + + clear.layer?.opacity = 0.0 + } + updateLoading() + self.needsLayout = true + } + + } + + open override func viewWillMove(toWindow newWindow: NSWindow?) { + if newWindow == nil { + if isEmpty { + change(state: .None, false) + } + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + } + + + func updateLoading() { + if isLoading && state == .Focus { + if progressIndicator.superview == nil { + addSubview(progressIndicator) + } + clear.isHidden = false + progressIndicator.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + clear.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] completed in + if completed { + self?.clear.isHidden = true + } + }) + progressIndicator.isHidden = false + progressIndicator.animates = true + } else { + progressIndicator.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] completed in + if completed { + self?.progressIndicator.isHidden = true + } + }) + clear.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + progressIndicator.animates = false + progressIndicator.removeFromSuperview() + clear.isHidden = self.state == .None + } + if window?.firstResponder == input { + window?.makeFirstResponder(input) + } + } + + + + + open override func layout() { + super.layout() + search.centerY(x: leftInset) + placeholder.centerY(x: placeholderTextInset + 2) + clear.centerY(x: frame.width - leftInset - clear.frame.width) + progressIndicator.frame = NSMakeRect(clear.frame.minX + 2, clear.frame.minY + 2, self.clear.frame.width - 4, self.clear.frame.height - 4) + inputContainer.centerY(x: placeholderTextInset, addition: 1) + } + + public func changeResponder(_ animated:Bool = true) -> Bool { + if state == .Focus { + cancelSearch() + } else { + change(state: .Focus, animated) + } + return true + } + + deinit { + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + + public var query:String { + return self.input.string + } + + open override func change(size: NSSize, animated: Bool = true, _ save: Bool = true, removeOnCompletion: Bool = false, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + super.change(size: size, animated: animated, save, duration: duration, timingFunction: timingFunction) + clear.change(pos: NSMakePoint(frame.width - inset - clear.frame.width, clear.frame.minY), animated: animated) + } + + + public func setString(_ string:String) { + self.input.string = string + textDidChange(Notification(name: NSText.didChangeNotification)) + needsLayout = true + } + + public func cancel(_ animated:Bool) -> Void { + change(state: .None, animated) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + public final class EntertainmentInteractions { var current:EntertainmentState = .emoji var sendEmoji:(String) ->Void = {_ in} - var sendSticker:(TelegramMediaFile) ->Void = {_ in} - var sendGIF:(TelegramMediaFile) ->Void = {_ in} + var sendSticker:(TelegramMediaFile, Bool) ->Void = { _, _ in} + var sendGIF:(TelegramMediaFile, Bool) ->Void = { _, _ in} - var showEntertainment:(EntertainmentState,Bool)->Void = { _,_ in} + var showEntertainment:(EntertainmentState, Bool)->Void = { _,_ in} var close:()->Void = {} + var toggleSearch:()->Void = { } + let peerId:PeerId init(_ defaultState: EntertainmentState, peerId:PeerId) { current = defaultState self.peerId = peerId } - } +final class EntertainmentView : View { + fileprivate var sectionView: NSView + private let bottomView = View() + private let borderView = View() + fileprivate let emoji: ImageButton = ImageButton() + fileprivate let stickers: ImageButton = ImageButton() + fileprivate let gifs: ImageButton = ImageButton() + + + + private let sectionTabs: View = View() + init(sectionView: NSView, frame: NSRect) { + self.sectionView = sectionView + super.init(frame: frame) + self.bottomView.border = [.Top] + self.addSubview(self.sectionView) + addSubview(self.bottomView) + self.bottomView.addSubview(sectionTabs) + + self.sectionTabs.addSubview(self.emoji) + self.sectionTabs.addSubview(self.stickers) + self.sectionTabs.addSubview(self.gifs) + + self.bottomView.addSubview(self.borderView) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + self.borderView.background = theme.colors.border + self.emoji.set(image: theme.icons.entertainment_Emoji, for: .Normal) + self.stickers.set(image: theme.icons.entertainment_Stickers, for: .Normal) + self.gifs.set(image: theme.icons.entertainment_Gifs, for: .Normal) + _ = self.emoji.sizeToFit() + _ = self.stickers.sizeToFit() + _ = self.gifs.sizeToFit() + + } + + func toggleSearch(_ signal:ValuePromise) { +// if let searchView = self.searchView { +// self.searchView = nil +// searchView.searchInteractions = nil +// signal.set(.init(state: .None, request: nil)) +// searchView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak searchView] _ in +// searchView?.removeFromSuperview() +// }) +// self.search.isSelected = false +// } else { +// self.searchView = EntertainmentSearchView(frame: NSMakeRect(0, 0, frame.width, 50)) +// self.addSubview(self.searchView!) +// self.searchView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) +// self.searchView?.searchInteractions = SearchInteractions({ [weak self] state, _ in +// signal.set(state) +// switch state.state { +// case .Focus: +// break +// case .None: +// self?.toggleSearch(signal) +// } +// }, { [weak self] state in +// signal.set(state) +// switch state.state { +// case .Focus: +// break +// case .None: +// self?.toggleSearch(signal) +// } +// }) +// self.search.isSelected = true +// self.searchView?.change(state: .Focus, false) +// } + } + + func updateSelected(_ state: EntertainmentState) { + self.emoji.isSelected = false + self.stickers.isSelected = false + self.gifs.isSelected = false + + switch state { + case .emoji: + self.emoji.isSelected = true + case .stickers: + self.stickers.isSelected = true + case .gifs: + self.gifs.isSelected = true + } + } + + + override func layout() { + super.layout() + self.sectionView.frame = NSMakeRect(0, 0, self.frame.width, self.frame.height - 50) + self.bottomView.frame = NSMakeRect(0, self.frame.height - 50, self.frame.width, 50) + self.borderView.frame = NSMakeRect(0, 0, self.bottomView.frame.width, .borderSize) + self.sectionTabs.setFrameSize(NSMakeSize(self.sectionTabs.subviewsSize.width + 40, 40)) + self.sectionTabs.center() + + self.emoji.centerY(x: 0) + self.stickers.centerY(x: self.emoji.frame.maxX + 20) + self.gifs.centerY(x: self.stickers.frame.maxX + 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} -class EntertainmentViewController: NavigationViewController { +class EntertainmentViewController: TelegramGenericViewController { private let languageDisposable:MetaDisposable = MetaDisposable() - private var account:Account - private var chatInteraction:ChatInteraction? - private var interactions:EntertainmentInteractions? + private(set) weak var chatInteraction:ChatInteraction? + private(set) var interactions:EntertainmentInteractions? private let cap:SidebarCapViewController private let section: SectionViewController @@ -49,9 +624,24 @@ class EntertainmentViewController: NavigationViewController { private let emoji:EmojiViewController - private let stickers:StickersViewController + private let stickers:NStickersViewController private let gifs:GIFViewController + private let searchState = ValuePromise(.init(state: .None, request: nil)) + + private var effectiveSearchView: SearchView? { + if self.gifs.view.superview != nil { + return self.gifs.genericView.searchView + } + if self.emoji.view.superview != nil { + return self.emoji.genericView.searchView + } + if self.stickers.view.superview != nil { + return self.stickers.genericView.searchView + } + return nil + } + func update(with chatInteraction:ChatInteraction) -> Void { self.chatInteraction = chatInteraction @@ -60,23 +650,35 @@ class EntertainmentViewController: NavigationViewController { interactions.close = { [weak self] in self?.closePopover() } - interactions.sendSticker = { [weak self] file in - self?.chatInteraction?.sendAppFile(file) + interactions.sendSticker = { [weak self] file, silent in + self?.chatInteraction?.sendAppFile(file, silent) self?.closePopover() } - interactions.sendGIF = { [weak self] file in - self?.chatInteraction?.sendAppFile(file) + interactions.sendGIF = { [weak self] file, silent in + self?.chatInteraction?.sendAppFile(file, silent) self?.closePopover() } interactions.sendEmoji = { [weak self] emoji in _ = self?.chatInteraction?.appendText(emoji) + guard let `self` = self else { + return + } + +// if self.genericView.searchView != nil { +// self.genericView.toggleSearch(self.searchState) +// } + } + interactions.toggleSearch = { [weak self] in + guard let `self` = self else { + return + } + self.toggleSearch() } - self.interactions = interactions emoji.update(with: interactions) stickers.update(with: interactions, chatInteraction: chatInteraction) - gifs.update(with: interactions) + gifs.update(with: interactions, chatInteraction: chatInteraction) } @@ -84,31 +686,36 @@ class EntertainmentViewController: NavigationViewController { self.viewWillDisappear(false) } - init(size:NSSize, account:Account) { + init(size:NSSize, context:AccountContext) { - self.account = account - self.cap = SidebarCapViewController(account: account) - self.emoji = EmojiViewController(account) - self.stickers = StickersViewController(account:account) - self.gifs = GIFViewController(account: account) + self.cap = SidebarCapViewController(context) + self.emoji = EmojiViewController(context) + self.stickers = NStickersViewController(context) + self.gifs = GIFViewController(context) var items:[SectionControllerItem] = [] - items.append(SectionControllerItem(title: tr(.entertainmentEmoji).uppercased(), controller: emoji)) - items.append(SectionControllerItem(title: tr(.entertainmentStickers).uppercased(), controller: stickers)) - items.append(SectionControllerItem(title: tr(.entertainmentGIF ).uppercased(), controller: gifs)) - self.section = SectionViewController(sections: items, selected: Int(FastSettings.entertainmentState.rawValue)) - super.init(section) + items.append(SectionControllerItem(title:{L10n.entertainmentEmoji.uppercased()}, controller: emoji)) + items.append(SectionControllerItem(title: {L10n.entertainmentStickers.uppercased()}, controller: stickers)) + items.append(SectionControllerItem(title: {L10n.entertainmentGIF.uppercased()}, controller: gifs)) + self.section = SectionViewController(sections: items, selected: Int(FastSettings.entertainmentState.rawValue), hasHeaderView: false) + super.init(context) bar = .init(height: 0) } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.section.updateLocalizationAndTheme(theme: theme) self.view.background = theme.colors.background - emoji.view.background = theme.colors.background - stickers.view.background = theme.colors.background - gifs.view.background = theme.colors.background + if emoji.isLoaded() { + emoji.view.background = theme.colors.background + } + if stickers.isLoaded() { + stickers.view.background = theme.colors.background + } + if gifs.isLoaded() { + gifs.view.background = theme.colors.background + } } deinit { @@ -120,52 +727,144 @@ class EntertainmentViewController: NavigationViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) section.viewWillAppear(animated) + updateLocalizationAndTheme(theme: theme) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) section.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + private func toggleSearch() { + if let searchView = self.effectiveSearchView { + if searchView.state == .Focus { + searchView.setString("") + searchView.cancel(true) + } else { + searchView.change(state: .Focus, true) + } + } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) section.viewDidAppear(animated) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else { + return .rejected + } + if self.context.sharedContext.bindings.rootNavigation().genericView.state != .single { + return .rejected + } + self.toggleSearch() + return .invoked + }, with: self, for: .F, priority: .modal, modifierFlags: .command) + } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) section.viewDidDisappear(animated) } + override func initializer() -> EntertainmentView { + let rect = NSMakeRect(self._frameRect.minX, self._frameRect.minY, self._frameRect.width, self._frameRect.height - self.bar.height) + self.section._frameRect = NSMakeRect(rect.minX, rect.minY, rect.width, rect.height - 50) + return EntertainmentView(sectionView: self.section.view, frame: rect) + } + override func firstResponder() -> NSResponder? { + if popover == nil { + return nil + } + return effectiveSearchView//genericView.searchView?.input + } override func viewDidLoad() { super.viewDidLoad() cap.loadViewIfNeeded() + self.genericView.updateSelected(FastSettings.entertainmentState) + + + let callSearchCmd:(ESearchCommand, SearchView)->Void = { command, view in + switch command { + case .clearText: + view.setString("") + case .loading: + view.isLoading = true + case .normal: + view.isLoading = false + case .close: + view.cancel(true) + case let .apply(value): + view.setString(value) + } + } + + self.stickers.makeSearchCommand = { [weak self] command in + if self?.stickers.view.superview != nil, let view = self?.stickers.genericView.searchView { + callSearchCmd(command, view) + } + } + self.gifs.makeSearchCommand = { [weak self] command in + if self?.gifs.view.superview != nil, let view = self?.gifs.genericView.searchView { + callSearchCmd(command, view) + } + } + self.emoji.makeSearchCommand = { [weak self] command in + if self?.emoji.view.superview != nil, let view = self?.emoji.genericView.searchView { + callSearchCmd(command, view) + } + } - let contentRect = NSMakeRect(0, 0, frame.width, frame.height) - emoji._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) - stickers._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) + self.genericView.emoji.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + if self.genericView.emoji.isSelected { + self.emoji.scrollup() + } + self.section.select(0, true, notifyApper: true) + + }, for: .Click) + + self.genericView.stickers.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + if self.genericView.stickers.isSelected { + self.stickers.scrollup() + } + self.section.select(1, true, notifyApper: true) + }, for: .Click) + + self.genericView.gifs.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + if self.genericView.gifs.isSelected { + self.gifs.scrollup() + } + self.section.select(2, true, notifyApper: true) + }, for: .Click) section.selectionUpdateHandler = { [weak self] index in - FastSettings.changeEntertainmentState(EntertainmentState(rawValue: Int32(index))!) + let state = EntertainmentState(rawValue: Int32(index))! + FastSettings.changeEntertainmentState(state) self?.chatInteraction?.update({$0.withUpdatedIsEmojiSection(index == 0)}) + self?.genericView.updateSelected(state) } - section._frameRect = contentRect - addSubview(section.view) - self.ready.set(section.ready.get()) languageDisposable.set((combineLatest(appearanceSignal, ready.get() |> filter {$0} |> take(1))).start(next: { [weak self] _ in - self?.updateLocalizationAndTheme() + self?.updateLocalizationAndTheme(theme: theme) })) } - - - } diff --git a/Telegram-Mac/ExMajorNavigationController.swift b/Telegram-Mac/ExMajorNavigationController.swift index 8bcd8fe9a5..06b034c890 100644 --- a/Telegram-Mac/ExMajorNavigationController.swift +++ b/Telegram-Mac/ExMajorNavigationController.swift @@ -8,18 +8,36 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore class ExMajorNavigationController: MajorNavigationController { - private let account:Account + private let context:AccountContext override var sidebar: ViewController? { - return account.context.entertainment + return context.sharedContext.bindings.entertainment() } - public init(_ account: Account, _ majorClass:AnyClass, _ empty:ViewController) { - self.account = account - super.init(majorClass, empty) + override var window: Window? { + return context.window + } + + open override var responderPriority: HandlerPriority { + return .medium + } + + public init(_ context: AccountContext, _ majorClass:AnyClass, _ empty:ViewController) { + self.context = context + super.init(majorClass, empty, context.window) + } + + override func push(_ controller: ViewController, _ animated: Bool, style: ViewControllerStyle?) { + super.push(controller, animated, style: style) + } + + @available(OSX 10.12.2, *) + override func makeTouchBar() -> NSTouchBar? { + return controller.makeTouchBar()//globalAudio?.makeTouchBar()// } } diff --git a/Telegram-Mac/ExportProxyModalController.swift b/Telegram-Mac/ExportProxyModalController.swift index 2c31beafd3..8dab0aa48a 100644 --- a/Telegram-Mac/ExportProxyModalController.swift +++ b/Telegram-Mac/ExportProxyModalController.swift @@ -8,7 +8,8 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit diff --git a/Telegram-Mac/Extensions.swift b/Telegram-Mac/Extensions.swift index 91aef881a9..321b73a72a 100644 --- a/Telegram-Mac/Extensions.swift +++ b/Telegram-Mac/Extensions.swift @@ -8,10 +8,11 @@ import Foundation import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac - +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox +import LocalAuthentication extension Message { var chatStableId:ChatHistoryEntryId { @@ -19,17 +20,10 @@ extension Message { } } -extension MessageHistoryHole { - - var chatStableId:ChatHistoryEntryId { - return ChatHistoryEntryId.hole(self) - } -} - extension NSMutableAttributedString { - func detectLinks(type:ParsingType, account:Account? = nil, color:NSColor = .link, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil, command:((String)->Void)? = nil, applyProxy:((ProxySettings)->Void)? = nil) -> Void { - let things = ObjcUtils.textCheckingResults(forText: self.string, highlightMentionsAndTags: type.contains(.Mentions) || type.contains(.Hashtags), highlightCommands: type.contains(.Commands)) + func detectLinks(type:ParsingType, context:AccountContext? = nil, color:NSColor = theme.colors.link, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil, command:((String)->Void)? = nil, applyProxy:((ProxyServerSettings)->Void)? = nil, dotInMention: Bool = false) -> Void { + let things = ObjcUtils.textCheckingResults(forText: self.string, highlightMentions: type.contains(.Mentions), highlightTags: type.contains(.Hashtags), highlightCommands: type.contains(.Commands), dotInMention: dotInMention) self.beginEditing() @@ -40,12 +34,12 @@ extension NSMutableAttributedString { if range.location != NSNotFound { let sublink = (self.string as NSString).substring(with: range) - if let account = account { - self.addAttribute(NSAttributedStringKey.link, value: inApp(for: sublink as NSString, account: account, openInfo: openInfo, hashtag: hashtag, command: command, applyProxy: applyProxy), range: range) + if let context = context { + self.addAttribute(NSAttributedString.Key.link, value: inApp(for: sublink as NSString, context: context, openInfo: openInfo, hashtag: hashtag, command: command, applyProxy: applyProxy), range: range) } else { - self.addAttribute(NSAttributedStringKey.link, value: inAppLink.external(link: sublink, false), range: range) + self.addAttribute(NSAttributedString.Key.link, value: inAppLink.external(link: sublink, false), range: range) } - self.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: range) + self.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range) self.addAttribute(.cursor, value: NSCursor.pointingHand, range: range) } @@ -64,8 +58,8 @@ extension NSMutableAttributedString { } return false } - - let symbols:[(from: String, to: String)] = [(from: "✌", to: "✌️"), (from: "☺", to: "☺️"), (from: "☝", to: "☝️"), (from: "1⃣", to: "1️⃣"), (from: "2⃣", to: "2️⃣"), (from: "3⃣", to: "3️⃣"), (from: "4⃣", to: "4️⃣"), (from: "5⃣", to: "5️⃣"), (from: "6⃣", to: "6️⃣"), (from: "7⃣", to: "7️⃣"), (from: "8⃣", to: "8️⃣"), (from: "9⃣", to: "9️⃣"), (from: "0⃣", to: "0️⃣"), (from: "❤", to: "❤️"), (from: "☁", to: "☁️"), (from: "ℹ", to: "ℹ️"), (from: "✍", to: "✍️")] + + let symbols:[(from: String, to: String)] = [(from: "✌", to: "✌️"), (from: "☺", to: "☺️"), (from: "☝", to: "☝️"), (from: "1⃣", to: "1️⃣"), (from: "2⃣", to: "2️⃣"), (from: "3⃣", to: "3️⃣"), (from: "4⃣", to: "4️⃣"), (from: "5⃣", to: "5️⃣"), (from: "6⃣", to: "6️⃣"), (from: "7⃣", to: "7️⃣"), (from: "8⃣", to: "8️⃣"), (from: "9⃣", to: "9️⃣"), (from: "0⃣", to: "0️⃣"), (from: "❤", to: "❤️"), (from: "☁", to: "☁️"), (from: "ℹ", to: "ℹ️"), (from: "✍", to: "✍️"), (from: "♥", to: "❤️"), (from: "⁉", to: "⁉️"), (from: "❣", to: "❣️"), (from: "⬅", to: "⬅️"), (from: "◻", to: "◻️"), (from: "➡", to: "➡️"), (from: "◼", to: "◼️")] for symbol in symbols { while changeSymbol(symbol.from, to: symbol.to) { @@ -94,73 +88,35 @@ public extension String { str = str.replacingOccurrences(of: "8⃣", with: "8️⃣") str = str.replacingOccurrences(of: "9⃣", with: "9️⃣") str = str.replacingOccurrences(of: "0⃣", with: "0️⃣") + str = str.replacingOccurrences(of: "#⃣", with: "#️⃣") str = str.replacingOccurrences(of: "❤", with: "❤️") + str = str.replacingOccurrences(of: "♥", with: "❤️") str = str.replacingOccurrences(of: "☁", with: "☁️") str = str.replacingOccurrences(of: "✍", with: "✍️") + str = str.replacingOccurrences(of: "⁉", with: "⁉️") + str = str.replacingOccurrences(of: "❣", with: "❣️") + str = str.replacingOccurrences(of: "⬅", with: "⬅️") + str = str.replacingOccurrences(of: "◻", with: "◻️") + str = str.replacingOccurrences(of: "◼", with: "◼️") + str = str.replacingOccurrences(of: "➡", with: "➡️") + str = str.replacingOccurrences(of: "⚰", with: "⚰️") + + return str } static func stringForShortCallDurationSeconds(for seconds: Int32) -> String { if seconds < 60 { - return tr(.callShortSecondsCountable(Int(seconds))) + return tr(L10n.callShortSecondsCountable(Int(seconds))) } else { let number = Int(seconds) / 60 - return tr(.callShortMinutesCountable(number)) + return tr(L10n.callShortMinutesCountable(number)) } } - var trimmed:String { - - var string:String = self - while !string.isEmpty, let index = string.rangeOfCharacter(from: NSCharacterSet.whitespacesAndNewlines), index.lowerBound == string.startIndex { - string = String(string[index.upperBound.. 2 { - copy = String(copy[.."] = "😆" dictionary[":->"] = "😆" dictionary["XD"] = "😆" + dictionary["xD"] = "😂" dictionary["O:)"] = "😇" dictionary["3-)"] = "😌" dictionary[":P"] = "😛" @@ -1678,7 +1662,7 @@ func ==(lhs: EmojiClue, rhs: EmojiClue) -> Bool { return lhs.emoji == rhs.emoji && lhs.label == rhs.label && lhs.replacement == rhs.replacement } -func searchEmojiClue(query: String, postbox: Postbox) -> Signal<[EmojiClue], Void> { +func searchEmojiClue(query: String, postbox: Postbox) -> Signal<[EmojiClue], NoError> { return recentUsedEmoji(postbox: postbox) |> deliverOn(resourcesQueue) |> map { recent in @@ -1700,6 +1684,11 @@ func searchEmojiClue(query: String, postbox: Postbox) -> Signal<[EmojiClue], Voi } } +func randomInt32() -> Int32 { + let uRandom = arc4random() + return Int32(bitPattern: uRandom) +} + func + (left: Dictionary, right: Dictionary) -> Dictionary @@ -1714,11 +1703,67 @@ func + (left: Dictionary, right: Dictionary) return map } +extension Array { + func subarray(with range: NSRange) -> Array { + return Array(self[range.min ..< range.max]) + } + mutating func move(at oldIndex: Int, to newIndex: Int) { + self.insert(self.remove(at: oldIndex), at: newIndex) + } +} +extension Array { + func chunks(_ chunkSize: Int) -> [[Element]] { + return stride(from: 0, to: self.count, by: chunkSize).map { + Array(self[$0..(with selectKey: (Element) -> Key) -> [Key:Element] { + var dict = [Key:Element]() + for element in self { + dict[selectKey(element)] = element + } + return dict + } +} + func copyToClipboard(_ string:String) { + NSPasteboard.general.clearContents() NSPasteboard.general.declareTypes([.string], owner: nil) NSPasteboard.general.setString(string, forType: .string) } +extension LAPolicy { + static var applicationPolicy: LAPolicy { + if #available(OSX 10.12.2, *) { + #if DEBUG + return .deviceOwnerAuthentication + #endif + return .deviceOwnerAuthenticationWithBiometrics + } else { + return .deviceOwnerAuthentication + } + } +} + +extension LAContext { + var canUseBiometric: Bool { + if #available(OSX 10.12.2, *) { + #if DEBUG + return true + #endif + if canEvaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, error: nil) { + return true + } else { + return false + } + } else { + #if DEBUG + return true + #endif + return false + } + } +} extension CVImageBuffer { @@ -1778,14 +1823,30 @@ extension CGImage { let thumbnailImage: CGImage = self - let thumbnailContextSize = thumbnailImage.size - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + let thumbnailContextSize = thumbnailImage.size.multipliedByScreenScale() + + let thumbnailContextSmallSize = thumbnailContextSize.aspectFitted(NSMakeSize(50, 50)) + + let thumbnailContext = DrawingContext(size: thumbnailContextSmallSize, scale: 1.0) + + + thumbnailContext.withContext { ctx in ctx.interpolationQuality = .none - ctx.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + telegramFastBlurMore(Int32(thumbnailContextSmallSize.width), Int32(thumbnailContextSmallSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + let thumb = DrawingContext(size: thumbnailContextSize, scale: 1.0) + + + thumb.withContext { ctx in + ctx.interpolationQuality = .none + ctx.draw(thumbnailContext.generateImage()!, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + // telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumb.bytesPerRow), thumb.bytes) + return thumbnailContext.generateImage()! } @@ -1808,3 +1869,567 @@ func synced(_ lock: Any, closure: ()->Void) { closure() objc_sync_exit(lock) } + + +extension NSData { + + var hexString: String { + let buf = bytes.assumingMemoryBound(to: UInt8.self) + let charA = UInt8(UnicodeScalar("a").value) + let char0 = UInt8(UnicodeScalar("0").value) + + func itoh(_ value: UInt8) -> UInt8 { + return (value > 9) ? (charA + value - 10) : (char0 + value) + } + + let hexLen = length * 2 + let ptr = UnsafeMutablePointer.allocate(capacity: hexLen) + + for i in 0 ..< length { + ptr[i*2] = itoh((buf[i] >> 4) & 0xF) + ptr[i*2+1] = itoh(buf[i] & 0xF) + } + + return String(bytesNoCopy: ptr, length: hexLen, encoding: .utf8, freeWhenDone: true)! + } +} + +extension NSTextView { + + var selectedRangeRect: NSRect { + + var rect: NSRect = firstRect(forCharacterRange: selectedRange(), actualRange: nil) + + + + + if let window = window { + //rect = window.convertFromScreen(rect) + + var textViewBounds: NSRect = convert(bounds, to: nil) + textViewBounds = window.convertToScreen(textViewBounds) + + rect.origin.x -= textViewBounds.origin.x; + rect.origin.y -= (textViewBounds.origin.y ); + } + +// if let superview = superview { +// rect = superview.convert(rect, from: nil) +// } + // rect.origin.y += 10 + return rect + } + + + +} + +extension CGContext { + func round(_ size:NSSize,_ cornerRadius:CGFloat = .cornerRadius, positionFlags: LayoutPositionFlags? = nil) { + let minx:CGFloat = 0, midx = size.width/2.0, maxx = size.width + let miny:CGFloat = 0, midy = size.height/2.0, maxy = size.height + + self.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = cornerRadius + var bottomLeftRadius: CGFloat = cornerRadius + var topRightRadius: CGFloat = cornerRadius + var bottomRightRadius: CGFloat = cornerRadius + + + if let positionFlags = positionFlags { + if positionFlags.contains(.top) && positionFlags.contains(.left) { + topLeftRadius = topLeftRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + topRightRadius = topRightRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + bottomLeftRadius = bottomLeftRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + bottomRightRadius = bottomRightRadius * 3 + 2 + } + } + + self.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: topLeftRadius) + self.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: topRightRadius) + self.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: bottomRightRadius) + self.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: bottomLeftRadius) + + self.closePath() + self.clip() + + } +} + + + + + + +extension NSControl.ImagePosition { + + var touchBarDefaultSize: CGFloat { + switch self { + case .imageOnly: + return 56.0 + case .imageLeading: + return 175.0 + default: + return 150.0 + } + } + +} + +extension NSButton { + + func addTouchBarButtonWidthConstraint() { + switch self.imagePosition { + case .imageLeading: + addWidthConstraint(relation: .lessThanOrEqual, + size: self.imagePosition.touchBarDefaultSize) + default: + addWidthConstraint(relation: .equal, + size: self.imagePosition.touchBarDefaultSize) + } + } + +} + + +@available(OSX 10.12.2, *) +extension NSImage { + + // MARK: Project drawables + + + static let defaultBg = NSImage(named: "DefaultBackground")! + + static let shuffling = #imageLiteral(resourceName: "Icon_DFRShuffle").forUI() + static let repeating = #imageLiteral(resourceName: "Icon_DFRRepeat").forUI() + + static let previous = NSImage(named: NSImage.touchBarRewindTemplateName)! + static let next = NSImage(named: NSImage.touchBarFastForwardTemplateName)! + static let play = NSImage(named: NSImage.touchBarPlayTemplateName)! + static let pause = NSImage(named: NSImage.touchBarPauseTemplateName)! + + static let volumeLow = NSImage(named: NSImage.touchBarAudioOutputVolumeLowTemplateName) + static let volumeMedium = NSImage(named: NSImage.touchBarAudioOutputVolumeMediumTemplateName) + static let volumeHigh = NSImage(named: NSImage.touchBarAudioOutputVolumeHighTemplateName) + + static let playhead = NSImage(named: NSImage.touchBarPlayheadTemplateName) + static let playbar = #imageLiteral(resourceName: "Icon_Playbar") + +} + +extension NSImage { + + // MARK: Extended functions + + /** + Returns the NSImage with 'isTemplate' enabled + This lets the UI draw the appropriate color + according to background (e.g. dark in TouchBar) + */ + func forUI() -> NSImage { + self.isTemplate = true + + return self + } + + func edit(size: NSSize? = nil, + editCommand: @escaping (NSImage, NSSize) -> ()) -> NSImage { + let temp = NSImage(size: size ?? self.size) + + guard temp.size.width > 0, temp.size.height > 0 else { return self } + + temp.lockFocus() + editCommand(self, temp.size) + temp.unlockFocus() + + return temp + } + + /** + Resizes NSImage + - parameter newSize: the requested image size + - parameter squareCrop: if true + - returns: self scaled to requested size + */ + func resized(to newSize: CGSize, squareCrop: Bool = true) -> NSImage { + return self.edit(size: newSize) { + image, size in + + var fromRect = NSZeroRect + let inRect = NSMakeRect(0, 0, size.width, size.height) + + if squareCrop, image.size.width != image.size.height { + let minSize = min(image.size.width, image.size.height) + let maxSize = max(image.size.width, image.size.height) + let start = ( maxSize - minSize ) / 2 + + fromRect = NSMakeRect( + image.size.width != minSize ? start : 0, + image.size.height != minSize ? start : 0, + minSize, + minSize + ) + } + + image.draw(in: inRect, + from: fromRect, + operation: .sourceOver, + fraction: 1.0) + } + } + + /** + Changes NSImage alpha + - parameter alpha: the requested alpha value + - returns: self with requested alpha value + */ + func withAlpha(_ alpha: CGFloat) -> NSImage { + return self.edit { + image, size in + image.draw(in: NSMakeRect(0, 0, size.width, size.height), + from: NSZeroRect, + operation: .sourceOver, + fraction: alpha) + } + } + +} + + +extension Window { + var titleView: NSView? { + if let windowView = contentView?.superview { + return ObjcUtils.findElements(byClass: "NSTitlebarContainerView", in: windowView).first + } + return nil + } +} + +func quadraticEaseOut (_ x: T) -> T { + return -x * (x - 2) +} + +extension Date { + var startOfDay: Date { + var calendar = NSCalendar.current + return calendar.startOfDay(for: self) + } + + var startOfDayUTC: Date { + var calendar = NSCalendar.current + calendar.timeZone = TimeZone(abbreviation: "UTC")! + return calendar.startOfDay(for: self) + } + + var endOfDay: Date { + var components = DateComponents() + components.day = 1 + components.second = -1 + var calendar = NSCalendar.current + return calendar.date(byAdding: components, to: startOfDay)! + } + + var startOfMonth: Date { + let components = Calendar.current.dateComponents([.year, .month], from: startOfDay) + return Calendar.current.date(from: components)! + } + + var endOfMonth: Date { + var components = DateComponents() + components.month = 1 + components.second = -1 + return Calendar.current.date(byAdding: components, to: startOfMonth)! + } +} + + + + +public extension NSAttributedString { + + func applyRtf() -> (NSAttributedString, [NSTextAttachment]) { + let string = self.mutableCopy() as! NSMutableAttributedString + + let modified: NSMutableAttributedString = string.mutableCopy() as! NSMutableAttributedString + + + var index: Int = 1 + while true { + let range = modified.string.nsstring.range(of: "\t0") + if range.location != NSNotFound { + modified.replaceCharacters(in: range, with: "\t\(index)") + index += 1 + } else { + break + } + } + + var attachments:[NSTextAttachment] = [] + + string.enumerateAttributes(in: string.range, options: [], using: { attr, range, _ in + if let url = attr[.link] { + var string: String? + if let url = url as? NSURL, let link = url.absoluteString { + string = link + } else if let link = url as? String { + string = link + } + if let string = string { + let tag = TGInputTextTag(uniqueId: arc4random64(), attachment: string, attribute: TGInputTextAttribute(name: NSAttributedString.Key.foregroundColor.rawValue, value: theme.colors.link)) + if let tag = tag { + modified.addAttribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), value: tag, range: range) + } + } + } else if let font = attr[.font] as? NSFont { + let newFont: NSFont + if font.fontDescriptor.symbolicTraits.contains(.bold) && font.fontDescriptor.symbolicTraits.contains(.italic) { + newFont = .boldItalic(theme.fontSize) + } else if font.fontDescriptor.symbolicTraits.contains(.bold) { + newFont = .bold(theme.fontSize) + } else if font.fontDescriptor.symbolicTraits.contains(.italic) { + newFont = .italic(theme.fontSize) + } else if font.fontDescriptor.symbolicTraits.contains(NSFontDescriptor.SymbolicTraits.monoSpace) { + newFont = .code(theme.fontSize) + } else { + newFont = .normal(theme.fontSize) + } + modified.addAttribute(.font, value: newFont, range: range) + } + for key in attr.keys { + switch key { + case .font, .link: + break + case .attachment: + modified.removeAttribute(key, range: range) + attachments.append(attr[key] as! NSTextAttachment) + default: + modified.removeAttribute(key, range: range) + } + } + }) + + return (modified.trimmed, attachments) + } + + func appendAttributedString(_ string: NSAttributedString, selectedRange: NSRange = NSMakeRange(0, 0)) -> (NSAttributedString, NSRange) { + let inputText = self.mutableCopy() as! NSMutableAttributedString + + + var range: NSRange = NSMakeRange(selectedRange.location + string.string.length, 0); + if selectedRange.upperBound - selectedRange.lowerBound > 0 { + inputText.replaceCharacters(in: NSMakeRange(selectedRange.lowerBound, selectedRange.upperBound - selectedRange.lowerBound), with: string) + } else { + inputText.insert(string, at: selectedRange.lowerBound) + } + return (inputText, range) + } +} + + +extension Date { + + static var kernelBootTimeSecs:Int32 { + var mib = [ CTL_KERN, KERN_BOOTTIME ] + var bootTime = timeval() + var bootTimeSize = MemoryLayout.size + + if 0 != sysctl(&mib, UInt32(mib.count), &bootTime, &bootTimeSize, nil, 0) { + fatalError("Could not get boot time, errno: \(errno)") + } + + return Int32(bootTime.tv_sec) + } +} + + + + + +private let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]") + +func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data { + if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) { + let color1: NSColor + let color2: NSColor + let color3: NSColor + let color4: NSColor + + var colors: [NSColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { NSColor(rgb: $0) } + let replacementColors: [NSColor] + switch fitzModifier { + case .type12: + replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { NSColor(rgb: $0) } + case .type3: + replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { NSColor(rgb: $0) } + case .type4: + replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { NSColor(rgb: $0) } + case .type5: + replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { NSColor(rgb: $0) } + case .type6: + replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { NSColor(rgb: $0) } + } + + func colorToString(_ color: NSColor) -> String { + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + color.getRed(&r, green: &g, blue: &b, alpha: nil) + return "\"k\":[\(r),\(g),\(b),1]" + } + + func match(_ a: Double, _ b: Double, eps: Double) -> Bool { + return abs(a - b) < eps + } + + var replacements: [(NSTextCheckingResult, String)] = [] + + if let colorKeyRegex = colorKeyRegex { + let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string)) + for result in results.reversed() { + if let range = Range(result.range, in: string) { + let substring = String(string[range]) + let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)] + let components = color.split(separator: ",") + if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) { + if match(a, 1.0, eps: 0.01) { + for i in 0 ..< colors.count { + let color = colors[i] + var cr: CGFloat = 0.0 + var cg: CGFloat = 0.0 + var cb: CGFloat = 0.0 + color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) + if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) { + replacements.append((result, colorToString(replacementColors[i]))) + } + } + } + } + } + } + } + + for (result, text) in replacements { + if let range = Range(result.range, in: string) { + string = string.replacingCharacters(in: range, with: text) + } + } + + return string.data(using: .utf8) ?? data + } else { + return data + } +} + + +extension Double { + + func toString(decimal: Int = 9) -> String { + let value = decimal < 0 ? 0 : decimal + var string = String(format: "%.\(value)f", self) + + while string.last == "0" || string.last == "." { + if string.last == "." { string = String(string.dropLast()); break} + string = String(string.dropLast()) + } + return string + } +} + + +public extension String { + func rightJustified(width: Int, pad: String = " ", truncate: Bool = false) -> String { + guard width > count else { + return truncate ? String(suffix(width)) : self + } + return String(repeating: pad, count: width - count) + self + } + + func leftJustified(width: Int, pad: String = " ", truncate: Bool = false) -> String { + guard width > count else { + return truncate ? String(prefix(width)) : self + } + return self + String(repeating: pad, count: width - count) + } +} + + + +extension CGImage { + var data: Data? { + guard let mutableData = CFDataCreateMutable(nil, 0), + let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) else { return nil } + CGImageDestinationAddImage(destination, self, nil) + guard CGImageDestinationFinalize(destination) else { return nil } + return mutableData as Data + } +} + +func localizedPsa(_ key: String, type: String, args: [CVarArg] = []) -> String { + let fullKey = key + "." + type + let cloud = translate(key: fullKey, args) + if cloud == fullKey { + return translate(key: key, args) + } else { + return cloud + } +} + +func + (left: CGPoint, right: CGPoint) -> CGPoint { + return CGPoint(x: left.x + right.x, y: left.y + right.y) +} +func - (left: CGPoint, right: CGPoint) -> CGPoint { + return CGPoint(x: left.x - right.x, y: left.y - right.y) +} +func + (left: CGSize, right: CGSize) -> CGSize { + return CGSize(width: left.width + right.width, height: left.height + right.height) +} +func - (left: CGSize, right: CGSize) -> CGSize { + return CGSize(width: left.width - right.width, height: left.height - right.height) +} + + +func freeSystemGygabytes() -> UInt64? { + let attrs = try? FileManager.default.attributesOfFileSystem(forPath: "/") + + if let freeBytes = attrs?[FileAttributeKey.systemFreeSize] as? UInt64 { + return freeBytes / 1073741824 + } + return nil +} + +func showOutOfMemoryWarning(_ window: Window, freeSpace: UInt64, context: AccountContext) { + let alert: NSAlert = NSAlert() + alert.addButton(withTitle: L10n.systemMemoryWarningOK) + alert.addButton(withTitle: L10n.systemMemoryWarningDataAndStorage) + // alert.addButton(withTitle: L10n.systemMemoryWarningManageSystemStorage) + + alert.messageText = L10n.systemMemoryWarningHeader + alert.informativeText = L10n.systemMemoryWarningText(freeSpace == 0 ? L10n.systemMemoryWarningLessThen1GB : L10n.systemMemoryWarningFreeSpace(Int(freeSpace))) + alert.alertStyle = .critical + + alert.beginSheetModal(for: window, completionHandler: { response in + switch response.rawValue { + case 1000: + break + case 1001: + context.sharedContext.bindings.rootNavigation().push(StorageUsageController(context)) + case 1002: + openSystemSettings(.storage) + default: + break + } + }) +} + +extension NSImage { + var _cgImage: CGImage? { + return self.cgImage(forProposedRect: nil, context: nil, hints: nil) + } +} diff --git a/Telegram-Mac/ExternalMusicAlbumArtResources.swift b/Telegram-Mac/ExternalMusicAlbumArtResources.swift new file mode 100644 index 0000000000..7da36d427c --- /dev/null +++ b/Telegram-Mac/ExternalMusicAlbumArtResources.swift @@ -0,0 +1,113 @@ +// +// ExternalMusicAlbumArtResources.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +private func urlEncodedStringFromString(_ string: String) -> String { + var nsString: NSString = string as NSString + if let value = nsString.replacingPercentEscapes(using: String.Encoding.utf8.rawValue) { + nsString = value as NSString + } + + let result = CFURLCreateStringByAddingPercentEscapes(nil, nsString as CFString, nil, "?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ " as CFString, CFStringConvertNSStringEncodingToEncoding(String.Encoding.utf8.rawValue))! + return result as String +} + +func fetchExternalMusicAlbumArtResource(account: Account, resource: ExternalMusicAlbumArtResource) -> Signal { + return Signal { subscriber in + subscriber.putNext(.reset) + + if resource.performer.isEmpty || resource.performer.lowercased().trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "unknown artist" || resource.title.isEmpty { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return EmptyDisposable + } else { + let excludeWords: [String] = [ + " vs. ", + " vs ", + " versus ", + " ft. ", + " ft ", + " featuring ", + " feat. ", + " feat ", + " presents ", + " pres. ", + " pres ", + " and ", + " & ", + " . " + ] + + var performer = resource.performer + + for word in excludeWords { + performer = performer.replacingOccurrences(of: word, with: " ") + } + + let metaUrl = "https://itunes.apple.com/search?term=\(urlEncodedStringFromString("\(performer) \(resource.title)"))&entity=song&limit=4" + + let fetchDisposable = MetaDisposable() + + let disposable = fetchHttpResource(url: metaUrl).start(next: { result in + if case let .dataPart(_, data, _, complete) = result, complete { + guard let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return + } + + guard let results = dict["results"] as? [Any] else { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return + } + + guard let result = results.first as? [String: Any] else { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return + } + + guard var artworkUrl = result["artworkUrl100"] as? String else { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return + } + + if !resource.isThumbnail { + artworkUrl = artworkUrl.replacingOccurrences(of: "100x100", with: "600x600") + } + + if artworkUrl.isEmpty { + subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) + subscriber.putCompletion() + return + } else { + fetchDisposable.set(fetchHttpResource(url: artworkUrl).start(next: { next in + subscriber.putNext(next) + }, completed: { + subscriber.putCompletion() + })) + } + } + }) + + return ActionDisposable { + disposable.dispose() + fetchDisposable.dispose() + } + } + } +} diff --git a/Telegram-Mac/ExternalVideoLoader.swift b/Telegram-Mac/ExternalVideoLoader.swift index b4c3de4fbc..633114243c 100644 --- a/Telegram-Mac/ExternalVideoLoader.swift +++ b/Telegram-Mac/ExternalVideoLoader.swift @@ -7,9 +7,10 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit let sharedVideoLoader:ExternalVideoLoader = { let shared = ExternalVideoLoader() @@ -17,20 +18,27 @@ let sharedVideoLoader:ExternalVideoLoader = { }() +enum ExternalVideoServiceType { + case youtube + case vimeo + case none +} final class ExternalVideo : Equatable { let dimensions:NSSize let quality:NSSize let stream:String - fileprivate init(dimensions:NSSize, stream:String, quality:NSSize) { + let date: TimeInterval + fileprivate init(dimensions:NSSize, stream:String, quality:NSSize, date: TimeInterval) { self.dimensions = dimensions self.stream = stream self.quality = quality + self.date = date } } func ==(lhs:ExternalVideo, rhs:ExternalVideo) -> Bool { - return lhs.stream == rhs.stream + return lhs.stream == rhs.stream && lhs.date == rhs.date } enum ExternalVideoStatus { @@ -63,6 +71,7 @@ private final class ExternalVideoStatusContext { private let youtubeName = "YouTube" private let vimeoName = "Vimeo" +fileprivate let twitterIcon = #imageLiteral(resourceName: "icons8-circled-play-48").precomposed() fileprivate let youtubeIcon = #imageLiteral(resourceName: "icon_YouTubePlay").precomposed() fileprivate let vimeoIcon = #imageLiteral(resourceName: "Icon_VimeoPlay").precomposed() @@ -78,10 +87,16 @@ class ExternalVideoLoader { private var cancelTokensVimeo:[WrappedExternalVideoId: Any] = [:] static func isPlayable(_ content:TelegramMediaWebpageLoadedContent) -> Bool { + return (content.websiteName == youtubeName || content.websiteName == vimeoName) && content.image != nil } static func playIcon(_ content:TelegramMediaWebpageLoadedContent) -> CGImage? { + if let embedUrl = content.embedUrl { + if embedUrl.contains("twitter.com/i/videos") && content.image != nil { + return twitterIcon + } + } if content.websiteName == vimeoName { return vimeoIcon } else if content.websiteName == youtubeName { @@ -91,6 +106,15 @@ class ExternalVideoLoader { } + static func serviceType(_ content:TelegramMediaWebpageLoadedContent) -> ExternalVideoServiceType { + if content.websiteName == vimeoName { + return .vimeo + } else if content.websiteName == youtubeName { + return .youtube + } + return .none + } + func status(for content:TelegramMediaWebpageLoadedContent) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() @@ -127,10 +151,10 @@ class ExternalVideoLoader { } func fetch(for content:TelegramMediaWebpageLoadedContent) -> Signal { - if content.websiteName == youtubeName { + if content.displayUrl.lowercased().contains(youtubeName.lowercased()) { return fetchYoutubeContent(for: content.displayUrl) } - if content.websiteName == vimeoName { + if content.displayUrl.lowercased().contains(vimeoName.lowercased()) { return fetchVimeoContent(for: content.displayUrl) } return .fail(Void()) @@ -142,7 +166,7 @@ class ExternalVideoLoader { let disposable:MetaDisposable = MetaDisposable() self.statusQueue.async { - if let video = self.dataContexts[WrappedExternalVideoId(embed)] { + if let video = self.dataContexts[WrappedExternalVideoId(embed)], Date().timeIntervalSince1970 - 30 * 60 < video.date { subscriber.putNext(video) subscriber.putCompletion() } else if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)], statusContext.status != nil { @@ -180,7 +204,7 @@ class ExternalVideoLoader { stream = url.absoluteString } if let quality = quality, let stream = stream { - externalVideo = ExternalVideo(dimensions: NSMakeSize(1280, 720), stream: stream, quality: quality) + externalVideo = ExternalVideo(dimensions: NSMakeSize(1280, 720), stream: stream, quality: quality, date: Date().timeIntervalSince1970) self.dataContexts[WrappedExternalVideoId(embed)] = externalVideo! status = .loaded(externalVideo!) } else { @@ -190,6 +214,10 @@ class ExternalVideoLoader { status = .fail } + if self.statusContexts[WrappedExternalVideoId(embed)] == nil { + self.statusContexts[WrappedExternalVideoId(embed)] = ExternalVideoStatusContext() + } + if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)] { statusContext.status = status @@ -205,15 +233,17 @@ class ExternalVideoLoader { }) } disposable.set(ActionDisposable { - if let operation = self.cancelTokensYT[WrappedExternalVideoId(embed)] { - operation.cancel() - self.cancelTokensYT.removeValue(forKey: WrappedExternalVideoId(embed)) - - if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)], let status = statusContext.status, case .fetching = status { - statusContext.status = nil + self.statusQueue.async { + if let operation = self.cancelTokensYT[WrappedExternalVideoId(embed)] { + operation.cancel() + self.cancelTokensYT.removeValue(forKey: WrappedExternalVideoId(embed)) - for subscriber in statusContext.subscribers.copyItems() { - subscriber(statusContext.status) + if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)], let status = statusContext.status, case .fetching = status { + statusContext.status = nil + + for subscriber in statusContext.subscribers.copyItems() { + subscriber(statusContext.status) + } } } } @@ -234,7 +264,7 @@ class ExternalVideoLoader { var canceled:Bool = false - if let video = self.dataContexts[WrappedExternalVideoId(embed)] { + if let video = self.dataContexts[WrappedExternalVideoId(embed)], Date().timeIntervalSince1970 - 30 * 60 < video.date { subscriber.putNext(video) subscriber.putCompletion() } else if self.cancelTokensVimeo[WrappedExternalVideoId(embed)] == nil { @@ -257,11 +287,15 @@ class ExternalVideoLoader { if let video = video { let quality:NSSize = NSMakeSize(1280, 720) - let stream:String = video.highestQualityStreamURL().absoluteString + let stream:String? = video.highestQualityStreamURL()?.absoluteString + if let stream = stream { + externalVideo = ExternalVideo(dimensions: NSMakeSize(1280, 720), stream: stream, quality: quality, date: Date().timeIntervalSince1970) + self.dataContexts[WrappedExternalVideoId(embed)] = externalVideo! + status = .loaded(externalVideo!) + } else { + status = .fail + } - externalVideo = ExternalVideo(dimensions: NSMakeSize(1280, 720), stream: stream, quality: quality) - self.dataContexts[WrappedExternalVideoId(embed)] = externalVideo! - status = .loaded(externalVideo!) } else { status = .fail } @@ -283,14 +317,16 @@ class ExternalVideoLoader { } disposable.set(ActionDisposable { - if let _ = self.cancelTokensVimeo[WrappedExternalVideoId(embed)] { - self.cancelTokensVimeo.removeValue(forKey: WrappedExternalVideoId(embed)) - canceled = true - if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)], let status = statusContext.status, case .fetching = status { - statusContext.status = nil - - for subscriber in statusContext.subscribers.copyItems() { - subscriber(statusContext.status) + self.statusQueue.async { + if let _ = self.cancelTokensVimeo[WrappedExternalVideoId(embed)] { + self.cancelTokensVimeo.removeValue(forKey: WrappedExternalVideoId(embed)) + canceled = true + if let statusContext = self.statusContexts[WrappedExternalVideoId(embed)], let status = statusContext.status, case .fetching = status { + statusContext.status = nil + + for subscriber in statusContext.subscribers.copyItems() { + subscriber(statusContext.status) + } } } } diff --git a/Telegram-Mac/FFMpegAVCodec.h b/Telegram-Mac/FFMpegAVCodec.h new file mode 100644 index 0000000000..19a0413a22 --- /dev/null +++ b/Telegram-Mac/FFMpegAVCodec.h @@ -0,0 +1,21 @@ +// +// FFMpegAVCodec.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FFMpegAVCodec : NSObject + ++ (FFMpegAVCodec * _Nullable)findForId:(int)codecId; + +- (void *)impl; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegAVCodec.m b/Telegram-Mac/FFMpegAVCodec.m new file mode 100644 index 0000000000..271efcfdcc --- /dev/null +++ b/Telegram-Mac/FFMpegAVCodec.m @@ -0,0 +1,42 @@ +// +// FFMpegAVCodec.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegAVCodec.h" + +#import "libavcodec/avcodec.h" + +@interface FFMpegAVCodec () { + AVCodec *_impl; +} + +@end + +@implementation FFMpegAVCodec + +- (instancetype)initWithImpl:(AVCodec *)impl { + self = [super init]; + if (self != nil) { + _impl = impl; + } + return self; +} + ++ (FFMpegAVCodec * _Nullable)findForId:(int)codecId { + AVCodec *codec = avcodec_find_decoder(codecId); + if (codec) { + return [[FFMpegAVCodec alloc] initWithImpl:codec]; + } else { + return nil; + } +} + +- (void *)impl { + return _impl; +} + +@end diff --git a/Telegram-Mac/FFMpegAVCodecContext.h b/Telegram-Mac/FFMpegAVCodecContext.h new file mode 100644 index 0000000000..d8e1cd29d4 --- /dev/null +++ b/Telegram-Mac/FFMpegAVCodecContext.h @@ -0,0 +1,40 @@ +// +// FFMpegAVCodecContext.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +#import "FFMpegAVSampleFormat.h" +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, FFMpegAVCodecContextReceiveResult) +{ + FFMpegAVCodecContextReceiveResultError, + FFMpegAVCodecContextReceiveResultNotEnoughData, + FFMpegAVCodecContextReceiveResultSuccess, +}; + +@class FFMpegAVCodec; +@class FFMpegAVFrame; + +@interface FFMpegAVCodecContext : NSObject + +- (instancetype)initWithCodec:(FFMpegAVCodec *)codec; + +- (void *)impl; +- (int32_t)channels; +- (int32_t)sampleRate; +- (FFMpegAVSampleFormat)sampleFormat; + +- (bool)open; +- (bool)sendEnd; +- (FFMpegAVCodecContextReceiveResult)receiveIntoFrame:(FFMpegAVFrame *)frame; +- (void)flushBuffers; + + @end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegAVCodecContext.m b/Telegram-Mac/FFMpegAVCodecContext.m new file mode 100644 index 0000000000..f373ae5a99 --- /dev/null +++ b/Telegram-Mac/FFMpegAVCodecContext.m @@ -0,0 +1,81 @@ +// +// FFMpegAVCodecContext.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegAVCodecContext.h" + +#import "FFMpegAVFrame.h" +#import "FFMpegAVCodec.h" + +#import "libavcodec/avcodec.h" + +@interface FFMpegAVCodecContext () { + FFMpegAVCodec *_codec; + AVCodecContext *_impl; +} + + @end + +@implementation FFMpegAVCodecContext + +- (instancetype)initWithCodec:(FFMpegAVCodec *)codec { + self = [super init]; + if (self != nil) { + _codec = codec; + _impl = avcodec_alloc_context3((AVCodec *)[codec impl]); + } + return self; +} + +- (void)dealloc { + if (_impl) { + avcodec_free_context(&_impl); + } +} + +- (void *)impl { + return _impl; +} + +- (int32_t)channels { + return (int32_t)_impl->channels; +} + +- (int32_t)sampleRate { + return (int32_t)_impl->sample_rate; +} + +- (FFMpegAVSampleFormat)sampleFormat { + return (FFMpegAVSampleFormat)_impl->sample_fmt; +} + +- (bool)open { + int result = avcodec_open2(_impl, (AVCodec *)[_codec impl], nil); + return result >= 0; +} + +- (bool)sendEnd { + int status = avcodec_send_packet(_impl, nil); + return status == 0; +} + +- (FFMpegAVCodecContextReceiveResult)receiveIntoFrame:(FFMpegAVFrame *)frame { + int status = avcodec_receive_frame(_impl, (AVFrame *)[frame impl]); + if (status == 0) { + return FFMpegAVCodecContextReceiveResultSuccess; + } else if (status == -35) { + return FFMpegAVCodecContextReceiveResultNotEnoughData; + } else { + return FFMpegAVCodecContextReceiveResultError; + } +} + +- (void)flushBuffers { + avcodec_flush_buffers(_impl); +} + + @end diff --git a/Telegram-Mac/FFMpegAVFormatContext.h b/Telegram-Mac/FFMpegAVFormatContext.h new file mode 100644 index 0000000000..33db0a7308 --- /dev/null +++ b/Telegram-Mac/FFMpegAVFormatContext.h @@ -0,0 +1,52 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FFMpegAVIOContext; +@class FFMpegPacket; + +typedef enum FFMpegAVFormatStreamType { + FFMpegAVFormatStreamTypeVideo, + FFMpegAVFormatStreamTypeAudio +} FFMpegAVFormatStreamType; + +typedef struct FFMpegFpsAndTimebase { + CMTime fps; + CMTime timebase; +} FFMpegFpsAndTimebase; + +typedef struct FFMpegStreamMetrics { + int32_t width; + int32_t height; + double rotationAngle; + uint8_t *extradata; + int32_t extradataSize; +} FFMpegStreamMetrics; + +extern int FFMpegCodecIdH264; +extern int FFMpegCodecIdHEVC; +extern int FFMpegCodecIdMPEG4; + +@class FFMpegAVCodecContext; + +@interface FFMpegAVFormatContext : NSObject + +- (instancetype)init; + +- (void)setIOContext:(FFMpegAVIOContext *)ioContext; +- (bool)openInput; +- (bool)findStreamInfo; +- (void)seekFrameForStreamIndex:(int32_t)streamIndex pts:(int64_t)pts positionOnKeyframe:(bool)positionOnKeyframe; +- (bool)readFrameIntoPacket:(FFMpegPacket *)packet; +- (NSArray *)streamIndicesForType:(FFMpegAVFormatStreamType)type; +- (bool)isAttachedPicAtStreamIndex:(int32_t)streamIndex; +- (int)codecIdAtStreamIndex:(int32_t)streamIndex; +- (int64_t)durationAtStreamIndex:(int32_t)streamIndex; +- (bool)codecParamsAtStreamIndex:(int32_t)streamIndex toContext:(FFMpegAVCodecContext *)context; +- (FFMpegFpsAndTimebase)fpsAndTimebaseForStreamIndex:(int32_t)streamIndex defaultTimeBase:(CMTime)defaultTimeBase; +- (FFMpegStreamMetrics)metricsForStreamAtIndex:(int32_t)streamIndex; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegAVFormatContext.m b/Telegram-Mac/FFMpegAVFormatContext.m new file mode 100644 index 0000000000..b1119a5ec3 --- /dev/null +++ b/Telegram-Mac/FFMpegAVFormatContext.m @@ -0,0 +1,155 @@ +// +// FFMpegAVFormatContext.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegAVFormatContext.h" + +#import "FFMpegAVIOContext.h" +#import "FFMpegPacket.h" +#import "FFMpegAVCodecContext.h" + +#import "libavformat/avformat.h" + +int FFMpegCodecIdH264 = AV_CODEC_ID_H264; +int FFMpegCodecIdHEVC = AV_CODEC_ID_HEVC; +int FFMpegCodecIdMPEG4 = AV_CODEC_ID_MPEG4; + +@interface FFMpegAVFormatContext () { + AVFormatContext *_impl; +} + +@end + +@implementation FFMpegAVFormatContext + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _impl = avformat_alloc_context(); + } + return self; +} + +- (void)dealloc { + if (_impl != nil) { + avformat_close_input(&_impl); + } +} + +- (void)setIOContext:(FFMpegAVIOContext *)ioContext { + _impl->pb = [ioContext impl]; +} + +- (bool)openInput { + AVDictionary *options = nil; + av_dict_set(&options, "usetoc", "1", 0); + int result = avformat_open_input(&_impl, "http://localhost/file", nil, &options); + av_dict_free(&options); + if (_impl != nil) { + _impl->flags |= AVFMT_FLAG_FAST_SEEK; + } + + return result >= 0; +} + +- (bool)findStreamInfo { + int result = avformat_find_stream_info(_impl, nil); + return result >= 0; +} + +- (void)seekFrameForStreamIndex:(int32_t)streamIndex pts:(int64_t)pts positionOnKeyframe:(bool)positionOnKeyframe { + int options = AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD; + if (!positionOnKeyframe) { + options |= AVSEEK_FLAG_ANY; + } + av_seek_frame(_impl, streamIndex, pts, options); +} + + +- (bool)readFrameIntoPacket:(FFMpegPacket *)packet { + int result = av_read_frame(_impl, (AVPacket *)[packet impl]); + return result >= 0; +} + +- (NSArray *)streamIndicesForType:(FFMpegAVFormatStreamType)type { + NSMutableArray *indices = [[NSMutableArray alloc] init]; + enum AVMediaType mediaType; + switch(type) { + case FFMpegAVFormatStreamTypeAudio: + mediaType = AVMEDIA_TYPE_AUDIO; + break; + case FFMpegAVFormatStreamTypeVideo: + mediaType = AVMEDIA_TYPE_VIDEO; + break; + default: + mediaType = AVMEDIA_TYPE_VIDEO; + break; + } + for (unsigned int i = 0; i < _impl->nb_streams; i++) { + if (mediaType == _impl->streams[i]->codecpar->codec_type) { + [indices addObject:@(i)]; + } + } + return indices; +} + +- (bool)isAttachedPicAtStreamIndex:(int32_t)streamIndex { + return ((_impl->streams[streamIndex]->disposition) & AV_DISPOSITION_ATTACHED_PIC) != 0; +} + +- (int)codecIdAtStreamIndex:(int32_t)streamIndex { + return _impl->streams[streamIndex]->codecpar->codec_id; +} + +- (int64_t)durationAtStreamIndex:(int32_t)streamIndex { + return _impl->streams[streamIndex]->duration; +} + +- (bool)codecParamsAtStreamIndex:(int32_t)streamIndex toContext:(FFMpegAVCodecContext *)context { + int result = avcodec_parameters_to_context((AVCodecContext *)[context impl], _impl->streams[streamIndex]->codecpar); + return result >= 0; +} + +- (FFMpegFpsAndTimebase)fpsAndTimebaseForStreamIndex:(int32_t)streamIndex defaultTimeBase:(CMTime)defaultTimeBase { + CMTime timebase; + CMTime fps; + + AVStream *stream = _impl->streams[streamIndex]; + + if (stream->time_base.den != 0 && stream->time_base.num != 0) { + timebase = CMTimeMake((int64_t)stream->time_base.num, stream->time_base.den); + } else if (stream->codec->time_base.den != 0 && stream->codec->time_base.num != 0) { + timebase = CMTimeMake((int64_t)stream->codec->time_base.num, stream->codec->time_base.den); + } else { + timebase = defaultTimeBase; + } + + if (stream->avg_frame_rate.den != 0 && stream->avg_frame_rate.num != 0) { + fps = CMTimeMake((int64_t)stream->avg_frame_rate.num, stream->avg_frame_rate.den); + } else if (stream->r_frame_rate.den != 0 && stream->r_frame_rate.num != 0) { + fps = CMTimeMake((int64_t)stream->r_frame_rate.num, stream->r_frame_rate.den); + } else { + fps = CMTimeMake(1, 24); + } + + return (FFMpegFpsAndTimebase){ .fps = fps, .timebase = timebase }; +} + +- (FFMpegStreamMetrics)metricsForStreamAtIndex:(int32_t)streamIndex { + double rotationAngle = 0.0; + AVDictionaryEntry *entry = av_dict_get(_impl->streams[streamIndex]->metadata, "rotate", nil, 0); + if (entry && entry->value) { + if (strcmp(entry->value, "0") != 0) { + double angle = [[[NSString alloc] initWithCString:entry->value encoding:NSUTF8StringEncoding] doubleValue]; + rotationAngle = angle * M_PI / 180.0; + } + } + + return (FFMpegStreamMetrics){ .width = _impl->streams[streamIndex]->codecpar->width, .height = _impl->streams[streamIndex]->codecpar->height, .rotationAngle = rotationAngle, .extradata = _impl->streams[streamIndex]->codecpar->extradata, .extradataSize = _impl->streams[streamIndex]->codecpar->extradata_size }; +} + +@end diff --git a/Telegram-Mac/FFMpegAVFrame.h b/Telegram-Mac/FFMpegAVFrame.h new file mode 100644 index 0000000000..83a22c8445 --- /dev/null +++ b/Telegram-Mac/FFMpegAVFrame.h @@ -0,0 +1,34 @@ +// +// FFMpegAVFrame.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// +#import + + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, FFMpegAVFrameColorRange) { + FFMpegAVFrameColorRangeRestricted, + FFMpegAVFrameColorRangeFull +}; + +@interface FFMpegAVFrame : NSObject + + @property (nonatomic, readonly) int32_t width; + @property (nonatomic, readonly) int32_t height; + @property (nonatomic, readonly) uint8_t **data; + @property (nonatomic, readonly) int *lineSize; + @property (nonatomic, readonly) int64_t pts; + @property (nonatomic, readonly) int64_t duration; + @property (nonatomic, readonly) FFMpegAVFrameColorRange colorRange; + +- (instancetype)init; + +- (void *)impl; + + @end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegAVFrame.m b/Telegram-Mac/FFMpegAVFrame.m new file mode 100644 index 0000000000..2cd1554483 --- /dev/null +++ b/Telegram-Mac/FFMpegAVFrame.m @@ -0,0 +1,73 @@ +// +// FFMpegAVFrame.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegAVFrame.h" + +#import "libavformat/avformat.h" + +@interface FFMpegAVFrame () { + AVFrame *_impl; +} + + @end + +@implementation FFMpegAVFrame + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _impl = av_frame_alloc(); + } + return self; +} + +- (void)dealloc { + if (_impl) { + av_frame_unref(_impl); + } +} + +- (int32_t)width { + return _impl->width; +} + +- (int32_t)height { + return _impl->height; +} + +- (uint8_t **)data { + return _impl->data; +} + +- (int *)lineSize { + return _impl->linesize; +} + +- (int64_t)pts { + return _impl->pts; +} + +- (int64_t)duration { + return _impl->pkt_duration; +} + +- (FFMpegAVFrameColorRange)colorRange { + switch (_impl->color_range) { + case AVCOL_RANGE_MPEG: + case AVCOL_RANGE_UNSPECIFIED: + return FFMpegAVFrameColorRangeRestricted; + default: + return FFMpegAVFrameColorRangeFull; + } +} + +- (void *)impl { + return _impl; +} + +@end diff --git a/Telegram-Mac/FFMpegAVIOContext.h b/Telegram-Mac/FFMpegAVIOContext.h new file mode 100644 index 0000000000..f11729f83d --- /dev/null +++ b/Telegram-Mac/FFMpegAVIOContext.h @@ -0,0 +1,24 @@ +// +// FFMpegAVIOContext.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#define FFMPEG_AVSEEK_SIZE 0x10000 + +@interface FFMpegAVIOContext : NSObject + +- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek; + +- (void *)impl; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Telegram-Mac/FFMpegAVIOContext.m b/Telegram-Mac/FFMpegAVIOContext.m new file mode 100644 index 0000000000..c8e5870d3f --- /dev/null +++ b/Telegram-Mac/FFMpegAVIOContext.m @@ -0,0 +1,48 @@ +// +// FFMpegAVIOContext.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegAVIOContext.h" + +#import "libavformat/avformat.h" + +@interface FFMpegAVIOContext () { + AVIOContext *_impl; +} + + @end + +@implementation FFMpegAVIOContext + +- (instancetype _Nullable)initWithBufferSize:(int32_t)bufferSize opaqueContext:(void * const)opaqueContext readPacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))readPacket writePacket:(int (* _Nullable)(void * _Nullable opaque, uint8_t * _Nullable buf, int buf_size))writePacket seek:(int64_t (*)(void * _Nullable opaque, int64_t offset, int whence))seek { + self = [super init]; + if (self != nil) { + void *avIoBuffer = av_malloc(bufferSize); + _impl = avio_alloc_context(avIoBuffer, bufferSize, 0, opaqueContext, readPacket, writePacket, seek); + _impl->direct = 1; + if (_impl == nil) { + av_free(avIoBuffer); + return nil; + } + } + return self; +} + +- (void)dealloc { + if (_impl != nil) { + if (_impl->buffer != nil) { + av_free(_impl->buffer); + } + av_free(_impl); + } +} + +- (void *)impl { + return _impl; +} + +@end diff --git a/Telegram-Mac/FFMpegAVSampleFormat.h b/Telegram-Mac/FFMpegAVSampleFormat.h new file mode 100644 index 0000000000..07780dbce1 --- /dev/null +++ b/Telegram-Mac/FFMpegAVSampleFormat.h @@ -0,0 +1,33 @@ +// +// FFMpegAVSampleFormat.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef enum FFMpegAVSampleFormat { + FFMPEG_AV_SAMPLE_FMT_NONE = -1, + FFMPEG_AV_SAMPLE_FMT_U8, ///< unsigned 8 bits + FFMPEG_AV_SAMPLE_FMT_S16, ///< signed 16 bits + FFMPEG_AV_SAMPLE_FMT_S32, ///< signed 32 bits + FFMPEG_AV_SAMPLE_FMT_FLT, ///< float + FFMPEG_AV_SAMPLE_FMT_DBL, ///< double + + FFMPEG_AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar + FFMPEG_AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar + FFMPEG_AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar + FFMPEG_AV_SAMPLE_FMT_FLTP, ///< float, planar + FFMPEG_AV_SAMPLE_FMT_DBLP, ///< double, planar + FFMPEG_AV_SAMPLE_FMT_S64, ///< signed 64 bits + FFMPEG_AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar + + FFMPEG_AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically +} FFMpegAVSampleFormat; + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegAudioFrameDecoder.swift b/Telegram-Mac/FFMpegAudioFrameDecoder.swift new file mode 100644 index 0000000000..a8dcbf255e --- /dev/null +++ b/Telegram-Mac/FFMpegAudioFrameDecoder.swift @@ -0,0 +1,113 @@ +import Foundation +import CoreMedia + +final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { + private let codecContext: FFMpegAVCodecContext + private let swrContext: FFMpegSWResample + + private let audioFrame: FFMpegAVFrame + private var resetDecoderOnNextFrame = true + + private var delayedFrames: [MediaTrackFrame] = [] + + init(codecContext: FFMpegAVCodecContext) { + self.codecContext = codecContext + self.audioFrame = FFMpegAVFrame() + + self.swrContext = FFMpegSWResample(sourceChannelCount: Int(codecContext.channels()), sourceSampleRate: Int(codecContext.sampleRate()), sourceSampleFormat: codecContext.sampleFormat(), destinationChannelCount: 2, destinationSampleRate: 44100, destinationSampleFormat: FFMPEG_AV_SAMPLE_FMT_S16) + } + + func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { + let status = frame.packet.send(toDecoder: self.codecContext) + if status == 0 { + while true { + let result = self.codecContext.receive(into: self.audioFrame) + if case .success = result { + if let convertedFrame = convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration) { + self.delayedFrames.append(convertedFrame) + } + } else { + break + } + } + + if self.delayedFrames.count >= 1 { + var minFrameIndex = 0 + var minPosition = self.delayedFrames[0].position + for i in 1 ..< self.delayedFrames.count { + if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 { + minFrameIndex = i + minPosition = self.delayedFrames[i].position + } + } + return self.delayedFrames.remove(at: minFrameIndex) + } + } + + return nil + } + + func takeQueuedFrame() -> MediaTrackFrame? { + if self.delayedFrames.count >= 1 { + var minFrameIndex = 0 + var minPosition = self.delayedFrames[0].position + for i in 1 ..< self.delayedFrames.count { + if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 { + minFrameIndex = i + minPosition = self.delayedFrames[i].position + } + } + return self.delayedFrames.remove(at: minFrameIndex) + } else { + return nil + } + } + + func takeRemainingFrame() -> MediaTrackFrame? { + if !self.delayedFrames.isEmpty { + var minFrameIndex = 0 + var minPosition = self.delayedFrames[0].position + for i in 1 ..< self.delayedFrames.count { + if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 { + minFrameIndex = i + minPosition = self.delayedFrames[i].position + } + } + return self.delayedFrames.remove(at: minFrameIndex) + } else { + return nil + } + } + + private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? { + guard let data = self.swrContext.resample(frame) else { + return nil + } + + var blockBuffer: CMBlockBuffer? + + let bytes = malloc(data.count)! + data.copyBytes(to: bytes.assumingMemoryBound(to: UInt8.self), count: data.count) + let status = CMBlockBufferCreateWithMemoryBlock(allocator: nil, memoryBlock: bytes, blockLength: data.count, blockAllocator: nil, customBlockSource: nil, offsetToData: 0, dataLength: data.count, flags: 0, blockBufferOut: &blockBuffer) + if status != noErr { + return nil + } + + var timingInfo = CMSampleTimingInfo(duration: duration, presentationTimeStamp: pts, decodeTimeStamp: pts) + var sampleBuffer: CMSampleBuffer? + var sampleSize = data.count + guard CMSampleBufferCreate(allocator: nil, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: nil, sampleCount: 1, sampleTimingEntryCount: 1, sampleTimingArray: &timingInfo, sampleSizeEntryCount: 1, sampleSizeArray: &sampleSize, sampleBufferOut: &sampleBuffer) == noErr else { + return nil + } + + let resetDecoder = self.resetDecoderOnNextFrame + self.resetDecoderOnNextFrame = false + + return MediaTrackFrame(type: .audio, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: true) + } + + func reset() { + self.codecContext.flushBuffers() + self.resetDecoderOnNextFrame = true + } +} diff --git a/Telegram-Mac/FFMpegMediaFrameSource.swift b/Telegram-Mac/FFMpegMediaFrameSource.swift new file mode 100644 index 0000000000..f097072a8e --- /dev/null +++ b/Telegram-Mac/FFMpegMediaFrameSource.swift @@ -0,0 +1,303 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private final class ThreadTaskQueue: NSObject { + private var mutex: pthread_mutex_t + private var condition: pthread_cond_t + private var tasks: [() -> Void] = [] + private var shouldExit = false + + override init() { + self.mutex = pthread_mutex_t() + self.condition = pthread_cond_t() + pthread_mutex_init(&self.mutex, nil) + pthread_cond_init(&self.condition, nil) + + super.init() + } + + deinit { + pthread_mutex_destroy(&self.mutex) + pthread_cond_destroy(&self.condition) + } + + func loop() { + while !self.shouldExit { + pthread_mutex_lock(&self.mutex) + + if tasks.isEmpty { + pthread_cond_wait(&self.condition, &self.mutex) + } + + var task: (() -> Void)? + if !self.tasks.isEmpty { + task = self.tasks.removeFirst() + } + + pthread_mutex_unlock(&self.mutex) + + if let task = task { + autoreleasepool { + task() + } + } + } + } + + func enqueue(_ task: @escaping () -> Void) { + pthread_mutex_lock(&self.mutex) + self.tasks.append(task) + pthread_cond_broadcast(&self.condition) + pthread_mutex_unlock(&self.mutex) + } + + func terminate() { + pthread_mutex_lock(&self.mutex) + self.shouldExit = true + pthread_cond_broadcast(&self.condition) + pthread_mutex_unlock(&self.mutex) + } +} + +private func contextForCurrentThread() -> FFMpegMediaFrameSourceContext? { + return Thread.current.threadDictionary["FFMpegMediaFrameSourceContext"] as? FFMpegMediaFrameSourceContext +} + +final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { + private let queue: Queue + private let postbox: Postbox + private let resourceReference: MediaResourceReference + private let tempFilePath: String? + private let streamable: Bool + private let stallDuration: Double + private let lowWaterDuration: Double + private let highWaterDuration: Double + private let video: Bool + private let preferSoftwareDecoding: Bool + private let fetchAutomatically: Bool + private let maximumFetchSize: Int? + + private let taskQueue: ThreadTaskQueue + private let thread: Thread + + private let eventSinkBag = Bag<(MediaTrackEvent) -> Void>() + private var generatingFrames = false + private var requestedFrameGenerationTimestamp: Double? + + @objc private static func threadEntry(_ taskQueue: ThreadTaskQueue) { + autoreleasepool { + let context = FFMpegMediaFrameSourceContext(thread: Thread.current) + let localStorage = Thread.current.threadDictionary + localStorage["FFMpegMediaFrameSourceContext"] = context + + taskQueue.loop() + + Thread.current.threadDictionary.removeObject(forKey: "FFMpegMediaFrameSourceContext") + } + } + + private var copyOfSemaphore: Atomic? + + init(queue: Queue, postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool, maximumFetchSize: Int? = nil, stallDuration: Double = 1.0, lowWaterDuration: Double = 2.0, highWaterDuration: Double = 3.0) { + self.queue = queue + self.postbox = postbox + self.resourceReference = resourceReference + self.tempFilePath = tempFilePath + self.streamable = streamable + self.video = video + self.preferSoftwareDecoding = preferSoftwareDecoding + self.fetchAutomatically = fetchAutomatically + self.maximumFetchSize = maximumFetchSize + self.stallDuration = stallDuration + self.lowWaterDuration = lowWaterDuration + self.highWaterDuration = highWaterDuration + + self.taskQueue = ThreadTaskQueue() + + self.thread = Thread(target: FFMpegMediaFrameSource.self, selector: #selector(FFMpegMediaFrameSource.threadEntry(_:)), object: taskQueue) + self.thread.name = "FFMpegMediaFrameSourceContext" + self.thread.start() + + super.init() + + performWithContext { [weak self] context in + let semaphore = context.currentSemaphore + queue.async { [weak self] in + self?.copyOfSemaphore = semaphore + } + } + } + + deinit { + self.copyOfSemaphore?.swap(nil)?.signal() + self.taskQueue.terminate() + } + + func addEventSink(_ f: @escaping (MediaTrackEvent) -> Void) -> Int { + //assert(self.queue.isCurrent()) + + return self.eventSinkBag.add(f) + } + + func removeEventSink(_ index: Int) { + // assert(self.queue.isCurrent()) + + self.eventSinkBag.remove(index) + } + + func generateFrames(until timestamp: Double) { + assert(self.queue.isCurrent()) + + if self.requestedFrameGenerationTimestamp == nil || !self.requestedFrameGenerationTimestamp!.isEqual(to: timestamp) { + self.requestedFrameGenerationTimestamp = timestamp + + self.internalGenerateFrames(until: timestamp) + } + } + + func ensureHasFrames(until timestamp: Double) -> Signal { + assert(self.queue.isCurrent()) + + return Signal { subscriber in + let disposable = MetaDisposable() + let currentSemaphore = Atomic?>(value: nil) + + disposable.set(ActionDisposable { + currentSemaphore.with({ $0 })?.with({ $0 })?.signal() + }) + self.performWithContext({ context in + let _ = currentSemaphore.swap(context.currentSemaphore) + let _ = context.takeFrames(until: timestamp) + subscriber.putCompletion() + }) + return disposable + } + |> runOn(self.queue) + } + + private func internalGenerateFrames(until timestamp: Double) { + if self.generatingFrames { + return + } + + self.generatingFrames = true + + let postbox = self.postbox + let resourceReference = self.resourceReference + let tempFilePath = self.tempFilePath + let queue = self.queue + let streamable = self.streamable + let video = self.video + let preferSoftwareDecoding = self.preferSoftwareDecoding + let fetchAutomatically = self.fetchAutomatically + let maximumFetchSize = self.maximumFetchSize + + self.performWithContext { [weak self] context in + context.initializeState(postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically, maximumFetchSize: maximumFetchSize) + + let (frames, endOfStream) = context.takeFrames(until: timestamp) + + queue.async { [weak self] in + if let strongSelf = self { + strongSelf.generatingFrames = false + + for sink in strongSelf.eventSinkBag.copyItems() { + sink(.frames(frames)) + if endOfStream { + sink(.endOfStream) + } + } + + if strongSelf.requestedFrameGenerationTimestamp != nil && !strongSelf.requestedFrameGenerationTimestamp!.isEqual(to: timestamp) { + strongSelf.internalGenerateFrames(until: strongSelf.requestedFrameGenerationTimestamp!) + } + } + } + } + } + + func performWithContext(_ f: @escaping (FFMpegMediaFrameSourceContext) -> Void) { + assert(self.queue.isCurrent()) + + taskQueue.enqueue { + if let context = contextForCurrentThread() { + f(context) + } + } + } + + func seek(timestamp: Double) -> Signal, MediaFrameSourceSeekError> { + assert(self.queue.isCurrent()) + + return Signal { subscriber in + let disposable = MetaDisposable() + + let queue = self.queue + let postbox = self.postbox + let resourceReference = self.resourceReference + let tempFilePath = self.tempFilePath + let streamable = self.streamable + let video = self.video + let preferSoftwareDecoding = self.preferSoftwareDecoding + let fetchAutomatically = self.fetchAutomatically + let maximumFetchSize = self.maximumFetchSize + + let currentSemaphore = Atomic?>(value: nil) + + disposable.set(ActionDisposable { + currentSemaphore.with({ $0 })?.with({ $0 })?.signal() + }) + + self.performWithContext { [weak self] context in + let _ = currentSemaphore.swap(context.currentSemaphore) + + context.initializeState(postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically, maximumFetchSize: maximumFetchSize) + + context.seek(timestamp: timestamp, completed: { streamDescriptionsAndTimestamp in + queue.async { + if let strongSelf = self { + if let (streamDescriptions, timestamp) = streamDescriptionsAndTimestamp { + strongSelf.requestedFrameGenerationTimestamp = nil + subscriber.putNext(QueueLocalObject(queue: queue, generate: { + if let strongSelf = self { + var audioBuffer: MediaTrackFrameBuffer? + var videoBuffer: MediaTrackFrameBuffer? + + if let audio = streamDescriptions.audio { + audioBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: audio.decoder, type: .audio, duration: audio.duration, rotationAngle: 0.0, aspect: 1.0, stallDuration: strongSelf.stallDuration, lowWaterDuration: strongSelf.lowWaterDuration, highWaterDuration: strongSelf.highWaterDuration) + } + + var extraDecodedVideoFrames: [MediaTrackFrame] = [] + if let video = streamDescriptions.video { + videoBuffer = MediaTrackFrameBuffer(frameSource: strongSelf, decoder: video.decoder, type: .video, duration: video.duration, rotationAngle: video.rotationAngle, aspect: video.aspect, stallDuration: strongSelf.stallDuration, lowWaterDuration: strongSelf.lowWaterDuration, highWaterDuration: strongSelf.highWaterDuration) + for videoFrame in streamDescriptions.extraVideoFrames { + if let decodedFrame = video.decoder.decode(frame: videoFrame) { + extraDecodedVideoFrames.append(decodedFrame) + } + } + } + + return MediaFrameSourceSeekResult(buffers: MediaPlaybackBuffers(audioBuffer: audioBuffer, videoBuffer: videoBuffer), extraDecodedVideoFrames: extraDecodedVideoFrames, timestamp: timestamp) + } else { + return MediaFrameSourceSeekResult(buffers: MediaPlaybackBuffers(audioBuffer: nil, videoBuffer: nil), extraDecodedVideoFrames: [], timestamp: timestamp) + } + })) + let _ = currentSemaphore.swap(nil) + subscriber.putCompletion() + } else { + let _ = currentSemaphore.swap(nil) + subscriber.putError(.generic) + } + } + } + }) + } + + return disposable + } + } +} diff --git a/Telegram-Mac/FFMpegMediaFrameSourceContext.swift b/Telegram-Mac/FFMpegMediaFrameSourceContext.swift new file mode 100644 index 0000000000..95c3ad1930 --- /dev/null +++ b/Telegram-Mac/FFMpegMediaFrameSourceContext.swift @@ -0,0 +1,685 @@ +import Foundation +import SwiftSignalKit +import Postbox +import CoreMedia +import TelegramCore +import SyncCore + + +private struct StreamContext { + let index: Int + let codecContext: FFMpegAVCodecContext? + let fps: CMTime + let timebase: CMTime + let duration: CMTime + let decoder: MediaTrackFrameDecoder + let rotationAngle: Double + let aspect: Double +} + +struct FFMpegMediaFrameSourceDescription { + let duration: CMTime + let decoder: MediaTrackFrameDecoder + let rotationAngle: Double + let aspect: Double +} + +struct FFMpegMediaFrameSourceDescriptionSet { + let audio: FFMpegMediaFrameSourceDescription? + let video: FFMpegMediaFrameSourceDescription? + let extraVideoFrames: [MediaTrackDecodableFrame] +} + +private final class InitializedState { + fileprivate let avIoContext: FFMpegAVIOContext + fileprivate let avFormatContext: FFMpegAVFormatContext + + fileprivate let audioStream: StreamContext? + fileprivate let videoStream: StreamContext? + + init(avIoContext: FFMpegAVIOContext, avFormatContext: FFMpegAVFormatContext, audioStream: StreamContext?, videoStream: StreamContext?) { + self.avIoContext = avIoContext + self.avFormatContext = avFormatContext + self.audioStream = audioStream + self.videoStream = videoStream + } +} + +struct FFMpegMediaFrameSourceStreamContextInfo { + let duration: CMTime + let decoder: MediaTrackFrameDecoder +} + +struct FFMpegMediaFrameSourceContextInfo { + let audioStream: FFMpegMediaFrameSourceStreamContextInfo? + let videoStream: FFMpegMediaFrameSourceStreamContextInfo? +} + +private var maxOffset: Int = 0 + +private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + guard let postbox = context.postbox, let resourceReference = context.resourceReference, let streamable = context.streamable else { + return 0 + } + + var fetchedCount: Int32 = 0 + + var fetchedData: Data? + + /*#if DEBUG + maxOffset = max(maxOffset, context.readingOffset + Int(bufferSize)) + print("maxOffset \(maxOffset)") + #endif*/ + + let resourceSize: Int = resourceReference.resource.size ?? Int(Int32.max - 1) + let readCount = max(0, min(resourceSize - context.readingOffset, Int(bufferSize))) + let requestRange: Range = context.readingOffset ..< (context.readingOffset + readCount) + + assert(readCount < 16 * 1024 * 1024) + + if let maximumFetchSize = context.maximumFetchSize { + context.touchedRanges.insert(integersIn: requestRange) + var totalCount = 0 + for range in context.touchedRanges.rangeView { + totalCount += range.count + } + if totalCount > maximumFetchSize { + context.readingError = true + return 0 + } + } + + if streamable { + let data: Signal<(Data, Bool), NoError> + data = postbox.mediaBox.resourceData(resourceReference.resource, size: resourceSize, in: requestRange, mode: .complete) + if readCount == 0 { + fetchedData = Data() + } else { + if let tempFilePath = context.tempFilePath, let fileData = (try? Data(contentsOf: URL(fileURLWithPath: tempFilePath), options: .mappedRead))?.subdata(in: requestRange) { + fetchedData = fileData + } else { + let semaphore = DispatchSemaphore(value: 0) + let _ = context.currentSemaphore.swap(semaphore) + var completedRequest = false + let disposable = data.start(next: { result in + let (data, isComplete) = result + if data.count == readCount || isComplete { + precondition(data.count <= readCount) + fetchedData = data + completedRequest = true + semaphore.signal() + } + }) + semaphore.wait() + let _ = context.currentSemaphore.swap(nil) + disposable.dispose() + if !completedRequest { + context.readingError = true + return 0 + } + } + } + } else { + if let tempFilePath = context.tempFilePath, let fileSize = fileSize(tempFilePath) { + let fd = open(tempFilePath, O_RDONLY, S_IRUSR) + if fd >= 0 { + let readingOffset = context.readingOffset + let readCount = max(0, min(fileSize - readingOffset, Int(bufferSize))) + let range = readingOffset ..< (readingOffset + readCount) + assert(readCount < 16 * 1024 * 1024) + + lseek(fd, off_t(range.lowerBound), SEEK_SET) + var data = Data(count: readCount) + data.withUnsafeMutableBytes { bytes -> Void in + precondition(bytes.baseAddress != nil) + let readBytes = read(fd, bytes.baseAddress, readCount) + precondition(readBytes <= readCount) + } + fetchedData = data + close(fd) + } + } else { + let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) + let semaphore = DispatchSemaphore(value: 0) + let _ = context.currentSemaphore.swap(semaphore) + let readingOffset = context.readingOffset + var completedRequest = false + let disposable = data.start(next: { next in + if next.complete { + let readCount = max(0, min(next.size - readingOffset, Int(bufferSize))) + let range = readingOffset ..< (readingOffset + readCount) + + assert(readCount < 16 * 1024 * 1024) + + let fd = open(next.path, O_RDONLY, S_IRUSR) + if fd >= 0 { + lseek(fd, off_t(range.lowerBound), SEEK_SET) + var data = Data(count: readCount) + data.withUnsafeMutableBytes { bytes -> Void in + precondition(bytes.baseAddress != nil) + let readBytes = read(fd, bytes.baseAddress, readCount) + assert(readBytes <= readCount) + precondition(readBytes <= readCount) + } + fetchedData = data + close(fd) + } + completedRequest = true + semaphore.signal() + } + }) + semaphore.wait() + let _ = context.currentSemaphore.swap(nil) + disposable.dispose() + if !completedRequest { + context.readingError = true + return 0 + } + } + } + if let fetchedData = fetchedData { + precondition(fetchedData.count <= readCount) + fetchedData.withUnsafeBytes { bytes -> Void in + precondition(bytes.baseAddress != nil) + memcpy(buffer, bytes.baseAddress, fetchedData.count) + } + fetchedCount = Int32(fetchedData.count) + context.readingOffset += Int(fetchedCount) + } + + if context.closed { + context.readingError = true + return 0 + } + return fetchedCount +} + +private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + guard let postbox = context.postbox, let resourceReference = context.resourceReference, let streamable = context.streamable, let statsCategory = context.statsCategory else { + return 0 + } + + var result: Int64 = offset + + let resourceSize: Int + if let size = resourceReference.resource.size { + resourceSize = size + } else { + if !streamable { + if let tempFilePath = context.tempFilePath, let fileSize = fileSize(tempFilePath) { + resourceSize = fileSize + } else { + var resultSize: Int = Int(Int32.max - 1) + let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false)) + let semaphore = DispatchSemaphore(value: 0) + let _ = context.currentSemaphore.swap(semaphore) + var completedRequest = false + let disposable = data.start(next: { next in + if next.complete { + resultSize = Int(next.size) + completedRequest = true + semaphore.signal() + } + }) + semaphore.wait() + let _ = context.currentSemaphore.swap(nil) + disposable.dispose() + if !completedRequest { + context.readingError = true + return 0 + } + resourceSize = resultSize + } + } else { + resourceSize = Int(Int32.max - 1) + } + } + + if (whence & FFMPEG_AVSEEK_SIZE) != 0 { + result = Int64(resourceSize == Int(Int32.max - 1) ? 0 : resourceSize) + } else { + context.readingOffset = Int(min(Int64(resourceSize), offset)) + + if context.readingOffset != context.requestedDataOffset { + context.requestedDataOffset = context.readingOffset + + if context.readingOffset >= resourceSize { + context.fetchedDataDisposable.set(nil) + } else { + if streamable { + if context.tempFilePath == nil { + let fetchRange: Range = context.readingOffset ..< Int(Int32.max) + context.fetchedDataDisposable.set(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: resourceReference, range: (fetchRange, .elevated), statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + } + } else if !context.requestedCompleteFetch && context.fetchAutomatically { + context.requestedCompleteFetch = true + if context.tempFilePath == nil { + context.fetchedDataDisposable.set(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: resourceReference, statsCategory: statsCategory, preferBackgroundReferenceRevalidation: streamable).start()) + } + } + } + } + } + + if context.closed { + context.readingError = true + return 0 + } + + return result +} + +final class FFMpegMediaFrameSourceContext: NSObject { + private let thread: Thread + + var closed = false + + fileprivate var postbox: Postbox? + fileprivate var resourceReference: MediaResourceReference? + fileprivate var tempFilePath: String? + fileprivate var streamable: Bool? + fileprivate var statsCategory: MediaResourceStatsCategory? + + private let ioBufferSize = 1 * 1024 + fileprivate var readingOffset = 0 + + fileprivate var requestedDataOffset: Int? + fileprivate let fetchedDataDisposable = MetaDisposable() + fileprivate let fetchedFullDataDisposable = MetaDisposable() + fileprivate var requestedCompleteFetch = false + + fileprivate var readingError = false { + didSet { + self.fetchedDataDisposable.dispose() + self.fetchedFullDataDisposable.dispose() + } + } + + private var initializedState: InitializedState? + private var packetQueue: [FFMpegPacket] = [] + + private var preferSoftwareDecoding: Bool = false + fileprivate var fetchAutomatically: Bool = true + fileprivate var maximumFetchSize: Int? = nil + fileprivate var touchedRanges = IndexSet() + + let currentSemaphore = Atomic(value: nil) + + init(thread: Thread) { + self.thread = thread + } + + deinit { + assert(Thread.current === self.thread) + + self.fetchedDataDisposable.dispose() + self.fetchedFullDataDisposable.dispose() + } + + func initializeState(postbox: Postbox, resourceReference: MediaResourceReference, tempFilePath: String?, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, fetchAutomatically: Bool, maximumFetchSize: Int?) { + if self.readingError || self.initializedState != nil { + return + } + + let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals + + self.postbox = postbox + self.resourceReference = resourceReference + self.tempFilePath = tempFilePath + self.streamable = streamable + self.statsCategory = video ? .video : .audio + self.preferSoftwareDecoding = preferSoftwareDecoding + self.fetchAutomatically = fetchAutomatically + self.maximumFetchSize = maximumFetchSize + + var preferSoftwareAudioDecoding = false + if case let .media(media, _) = resourceReference, let file = media.media as? TelegramMediaFile { + if file.isInstantVideo { + preferSoftwareAudioDecoding = true + } + } + + if streamable { + if self.tempFilePath == nil { + self.fetchedDataDisposable.set(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: resourceReference, range: (0 ..< Int(Int32.max), .elevated), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } + } else if !self.requestedCompleteFetch && self.fetchAutomatically { + self.requestedCompleteFetch = true + if self.tempFilePath == nil { + self.fetchedFullDataDisposable.set(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: resourceReference, statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } + } + + let avFormatContext = FFMpegAVFormatContext() + + guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(self.ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback) else { + self.readingError = true + return + } + + avFormatContext.setIO(avIoContext) + + if !avFormatContext.openInput() { + self.readingError = true + return + } + + if !avFormatContext.findStreamInfo() { + self.readingError = true; + return + } + + var videoStream: StreamContext? + var audioStream: StreamContext? + + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeVideo) { + let streamIndex = streamIndexNumber.int32Value + if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) { + continue + } + + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale) + + let metrics = avFormatContext.metricsForStream(at: streamIndex) + + let rotationAngle: Double = metrics.rotationAngle + let aspect = Double(metrics.width) / Double(metrics.height) + + if self.preferSoftwareDecoding { + if let codec = FFMpegAVCodec.find(forId: codecId) { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + videoStream = StreamContext(index: Int(streamIndex), codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + break + } + } + } + } else if codecId == FFMpegCodecIdMPEG4 { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromMpeg4CodecData(UInt32(kCMVideoCodecType_MPEG4Video), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break + } + } else if codecId == FFMpegCodecIdH264 { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromAVCCodecData(UInt32(kCMVideoCodecType_H264), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break + } + } else if codecId == FFMpegCodecIdHEVC { + if let videoFormat = FFMpegMediaFrameSourceContextHelpers.createFormatDescriptionFromHEVCCodecData(UInt32(kCMVideoCodecType_HEVC), metrics.width, metrics.height, metrics.extradata, metrics.extradataSize) { + videoStream = StreamContext(index: Int(streamIndex), codecContext: nil, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaPassthroughVideoFrameDecoder(videoFormat: videoFormat, rotationAngle: rotationAngle), rotationAngle: rotationAngle, aspect: aspect) + break + } + } + } + + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeAudio) { + let streamIndex = streamIndexNumber.int32Value + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + var codec: FFMpegAVCodec? + + if codec == nil { + codec = FFMpegAVCodec.find(forId: codecId) + } + + if let codec = codec { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale) + + audioStream = StreamContext(index: Int(streamIndex), codecContext: codecContext, fps: fps, timebase: timebase, duration: duration, decoder: FFMpegAudioFrameDecoder(codecContext: codecContext), rotationAngle: 0.0, aspect: 1.0) + break + } + } + } + } + + self.initializedState = InitializedState(avIoContext: avIoContext, avFormatContext: avFormatContext, audioStream: audioStream, videoStream: videoStream) + + if streamable { + if self.tempFilePath == nil { + self.fetchedFullDataDisposable.set(fetchedMediaResource(mediaBox: postbox.mediaBox, reference: resourceReference, range: (0 ..< Int(Int32.max), .default), statsCategory: self.statsCategory ?? .generic, preferBackgroundReferenceRevalidation: streamable).start()) + } + self.requestedCompleteFetch = true + } + } + + private func readPacket() -> FFMpegPacket? { + if !self.packetQueue.isEmpty { + return self.packetQueue.remove(at: 0) + } else { + return self.readPacketInternal() + } + } + + private func readPacketInternal() -> FFMpegPacket? { + guard let initializedState = self.initializedState else { + return nil + } + + let packet = FFMpegPacket() + if initializedState.avFormatContext.readFrame(into: packet) { + return packet + } else { + return nil + } + } + + func takeFrames(until: Double) -> (frames: [MediaTrackDecodableFrame], endOfStream: Bool) { + if self.readingError { + return ([], true) + } + + guard let initializedState = self.initializedState else { + return ([], true) + } + + var videoTimestamp: Double? + if initializedState.videoStream == nil { + videoTimestamp = Double.infinity + } + + var audioTimestamp: Double? + if initializedState.audioStream == nil { + audioTimestamp = Double.infinity + } + + var frames: [MediaTrackDecodableFrame] = [] + var endOfStream = false + + while !self.readingError && ((videoTimestamp == nil || videoTimestamp!.isLess(than: until)) || (audioTimestamp == nil || audioTimestamp!.isLess(than: until))) { + if let packet = self.readPacket() { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { + let frame = videoFrameFromPacket(packet, videoStream: videoStream) + frames.append(frame) + + if videoTimestamp == nil || videoTimestamp! < CMTimeGetSeconds(frame.pts) { + videoTimestamp = CMTimeGetSeconds(frame.pts) + } + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { + let packetPts = packet.pts + + let pts = CMTimeMake(value: packetPts, timescale: audioStream.timebase.timescale) + let dts = CMTimeMake(value: packet.dts, timescale: audioStream.timebase.timescale) + + let duration: CMTime + + let frameDuration = packet.duration + if frameDuration != 0 { + duration = CMTimeMake(value: frameDuration * audioStream.timebase.value, timescale: audioStream.timebase.timescale) + } else { + duration = audioStream.fps + } + + let frame = MediaTrackDecodableFrame(type: .audio, packet: packet, pts: pts, dts: dts, duration: duration) + frames.append(frame) + + if audioTimestamp == nil || audioTimestamp! < CMTimeGetSeconds(pts) { + audioTimestamp = CMTimeGetSeconds(pts) + } + } + } else { + endOfStream = true + break + } + } + + return (frames, endOfStream) + } + + func contextInfo() -> FFMpegMediaFrameSourceContextInfo? { + if let initializedState = self.initializedState { + var audioStreamContext: FFMpegMediaFrameSourceStreamContextInfo? + var videoStreamContext: FFMpegMediaFrameSourceStreamContextInfo? + + if let audioStream = initializedState.audioStream { + audioStreamContext = FFMpegMediaFrameSourceStreamContextInfo(duration: audioStream.duration, decoder: audioStream.decoder) + } + + if let videoStream = initializedState.videoStream { + videoStreamContext = FFMpegMediaFrameSourceStreamContextInfo(duration: videoStream.duration, decoder: videoStream.decoder) + } + + return FFMpegMediaFrameSourceContextInfo(audioStream: audioStreamContext, videoStream: videoStreamContext) + } + return nil + } + + func seek(timestamp: Double, completed: ((FFMpegMediaFrameSourceDescriptionSet, CMTime)?) -> Void) { + if let initializedState = self.initializedState { + self.packetQueue.removeAll() + + for stream in [initializedState.videoStream, initializedState.audioStream] { + if let stream = stream { + let pts = CMTimeMakeWithSeconds(timestamp, preferredTimescale: stream.timebase.timescale) + initializedState.avFormatContext.seekFrame(forStreamIndex: Int32(stream.index), pts: pts.value, positionOnKeyframe: true) + break + } + } + + var audioDescription: FFMpegMediaFrameSourceDescription? + var videoDescription: FFMpegMediaFrameSourceDescription? + + if let audioStream = initializedState.audioStream { + audioDescription = FFMpegMediaFrameSourceDescription(duration: audioStream.duration, decoder: audioStream.decoder, rotationAngle: 0.0, aspect: 1.0) + } + + if let videoStream = initializedState.videoStream { + videoDescription = FFMpegMediaFrameSourceDescription(duration: videoStream.duration, decoder: videoStream.decoder, rotationAngle: videoStream.rotationAngle, aspect: videoStream.aspect) + } + + var actualPts: CMTime = CMTimeMake(value: 0, timescale: 1) + var extraVideoFrames: [MediaTrackDecodableFrame] = [] + if timestamp.isZero || initializedState.videoStream == nil { + for _ in 0 ..< 24 { + if let packet = self.readPacketInternal() { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { + self.packetQueue.append(packet) + let pts = CMTimeMake(value: packet.pts, timescale: videoStream.timebase.timescale) + actualPts = pts + break + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { + self.packetQueue.append(packet) + let pts = CMTimeMake(value: packet.pts, timescale: audioStream.timebase.timescale) + actualPts = pts + break + } + } else { + break + } + } + } else if let videoStream = initializedState.videoStream { + let targetPts = CMTimeMakeWithSeconds(Float64(timestamp), preferredTimescale: videoStream.timebase.timescale) + let limitPts = CMTimeMakeWithSeconds(Float64(timestamp + 0.5), preferredTimescale: videoStream.timebase.timescale) + var audioPackets: [FFMpegPacket] = [] + while !self.readingError { + if let packet = self.readPacket() { + if let videoStream = initializedState.videoStream, Int(packet.streamIndex) == videoStream.index { + let frame = videoFrameFromPacket(packet, videoStream: videoStream) + extraVideoFrames.append(frame) + + if CMTimeCompare(frame.dts, limitPts) >= 0 && CMTimeCompare(frame.pts, limitPts) >= 0 { + break + } + } else if let audioStream = initializedState.audioStream, Int(packet.streamIndex) == audioStream.index { + audioPackets.append(packet) + } + } else { + break + } + } + if !extraVideoFrames.isEmpty { + var closestFrame: MediaTrackDecodableFrame? + for frame in extraVideoFrames { + if CMTimeCompare(frame.pts, targetPts) >= 0 { + if let closestFrameValue = closestFrame { + if CMTimeCompare(frame.pts, closestFrameValue.pts) < 0 { + closestFrame = frame + } + } else { + closestFrame = frame + } + } + } + if let closestFrame = closestFrame { + actualPts = closestFrame.pts + } else { + if let videoStream = initializedState.videoStream { + actualPts = videoStream.duration + } else { + actualPts = extraVideoFrames.last!.pts + } + } + } + if let audioStream = initializedState.audioStream { + self.packetQueue.append(contentsOf: audioPackets.filter({ packet in + let pts = CMTimeMake(value: packet.pts, timescale: audioStream.timebase.timescale) + if CMTimeCompare(pts, actualPts) >= 0 { + return true + } else { + return false + } + })) + } + } + + completed((FFMpegMediaFrameSourceDescriptionSet(audio: audioDescription, video: videoDescription, extraVideoFrames: extraVideoFrames), actualPts)) + } else { + completed(nil) + } + } + + func close() { + self.closed = true + } +} + +private func videoFrameFromPacket(_ packet: FFMpegPacket, videoStream: StreamContext) -> MediaTrackDecodableFrame { + let packetPts = packet.pts + + let pts = CMTimeMake(value: packetPts, timescale: videoStream.timebase.timescale) + let dts = CMTimeMake(value: packet.dts, timescale: videoStream.timebase.timescale) + + let duration: CMTime + + let frameDuration = packet.duration + if frameDuration != 0 { + duration = CMTimeMake(value: frameDuration * videoStream.timebase.value, timescale: videoStream.timebase.timescale) + } else { + duration = videoStream.fps + } + + return MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration) +} diff --git a/Telegram-Mac/FFMpegMediaFrameSourceContextHelpers.swift b/Telegram-Mac/FFMpegMediaFrameSourceContextHelpers.swift new file mode 100644 index 0000000000..45373b99ca --- /dev/null +++ b/Telegram-Mac/FFMpegMediaFrameSourceContextHelpers.swift @@ -0,0 +1,130 @@ +import Foundation +import CoreMedia + +final class FFMpegMediaFrameSourceContextHelpers { + static let registerFFMpegGlobals: Void = { + #if DEBUG + av_log_set_level(AV_LOG_ERROR) + #else + av_log_set_level(AV_LOG_QUIET) + #endif + av_register_all() + return + }() + + static func createFormatDescriptionFromAVCCodecData(_ formatId: UInt32, _ width: Int32, _ height: Int32, _ extradata: UnsafePointer, _ extradata_size: Int32) -> CMFormatDescription? { + let par = NSMutableDictionary() + par.setObject(1 as NSNumber, forKey: "HorizontalSpacing" as NSString) + par.setObject(1 as NSNumber, forKey: "VerticalSpacing" as NSString) + + let atoms = NSMutableDictionary() + atoms.setObject(NSData(bytes: extradata, length: Int(extradata_size)), forKey: "avcC" as NSString) + + let extensions = NSMutableDictionary() + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationBottomField" as NSString) + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationTopField" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(par, forKey: "CVPixelAspectRatio" as NSString) + extensions.setObject(atoms, forKey: "SampleDescriptionExtensionAtoms" as NSString) + extensions.setObject("avc1" as NSString, forKey: "FormatName" as NSString) + extensions.setObject(0 as NSNumber, forKey: "SpatialQuality" as NSString) + extensions.setObject(0 as NSNumber, forKey: "Version" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(1 as NSNumber, forKey: "CVFieldCount" as NSString) + extensions.setObject(24 as NSNumber, forKey: "Depth" as NSString) + + var formatDescription: CMFormatDescription? + CMVideoFormatDescriptionCreate(allocator: nil, codecType: CMVideoCodecType(formatId), width: width, height: height, extensions: extensions, formatDescriptionOut: &formatDescription) + + return formatDescription + } + + static func createFormatDescriptionFromMpeg4CodecData(_ formatId: UInt32, _ width: Int32, _ height: Int32, _ extradata: UnsafePointer, _ extradata_size: Int32) -> CMFormatDescription? { + let par = NSMutableDictionary() + par.setObject(1 as NSNumber, forKey: "HorizontalSpacing" as NSString) + par.setObject(1 as NSNumber, forKey: "VerticalSpacing" as NSString) + + let atoms = NSMutableDictionary() + atoms.setObject(NSData(bytes: extradata, length: Int(extradata_size)), forKey: "esds" as NSString) + + let extensions = NSMutableDictionary() + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationBottomField" as NSString) + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationTopField" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(par, forKey: "CVPixelAspectRatio" as NSString) + extensions.setObject(atoms, forKey: "SampleDescriptionExtensionAtoms" as NSString) + extensions.setObject("mp4v" as NSString, forKey: "FormatName" as NSString) + extensions.setObject(0 as NSNumber, forKey: "SpatialQuality" as NSString) + //extensions.setObject(0 as NSNumber, forKey: "Version" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(1 as NSNumber, forKey: "CVFieldCount" as NSString) + extensions.setObject(24 as NSNumber, forKey: "Depth" as NSString) + + var formatDescription: CMFormatDescription? + guard CMVideoFormatDescriptionCreate(allocator: nil, codecType: kCMVideoCodecType_MPEG4Video, width: width, height: height, extensions: extensions, formatDescriptionOut: &formatDescription) == noErr else { + return nil + } + + return formatDescription + } + + static func createFormatDescriptionFromHEVCCodecData(_ formatId: UInt32, _ width: Int32, _ height: Int32, _ extradata: UnsafePointer, _ extradata_size: Int32) -> CMFormatDescription? { + let par = NSMutableDictionary() + par.setObject(1 as NSNumber, forKey: "HorizontalSpacing" as NSString) + par.setObject(1 as NSNumber, forKey: "VerticalSpacing" as NSString) + + let atoms = NSMutableDictionary() + atoms.setObject(NSData(bytes: extradata, length: Int(extradata_size)), forKey: "hvcC" as NSString) + + let extensions = NSMutableDictionary() + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationBottomField" as NSString) + extensions.setObject("left" as NSString, forKey: "CVImageBufferChromaLocationTopField" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(par, forKey: "CVPixelAspectRatio" as NSString) + extensions.setObject(atoms, forKey: "SampleDescriptionExtensionAtoms" as NSString) + extensions.setObject("hevc" as NSString, forKey: "FormatName" as NSString) + extensions.setObject(0 as NSNumber, forKey: "SpatialQuality" as NSString) + extensions.setObject(0 as NSNumber, forKey: "Version" as NSString) + extensions.setObject(0 as NSNumber, forKey: "FullRangeVideo" as NSString) + extensions.setObject(1 as NSNumber, forKey: "CVFieldCount" as NSString) + extensions.setObject(24 as NSNumber, forKey: "Depth" as NSString) + + var formatDescription: CMFormatDescription? + CMVideoFormatDescriptionCreate(allocator: nil, codecType: CMVideoCodecType(formatId), width: width, height: height, extensions: extensions, formatDescriptionOut: &formatDescription) + + return formatDescription + } + + static func streamIndices(formatContext: UnsafeMutablePointer, codecType: AVMediaType) -> [Int] { + var indices: [Int] = [] + for i in 0 ..< Int(formatContext.pointee.nb_streams) { + if codecType == formatContext.pointee.streams.advanced(by: i).pointee!.pointee.codecpar!.pointee.codec_type { + indices.append(i) + } + } + return indices + } + + static func streamFpsAndTimeBase(stream: UnsafePointer, defaultTimeBase: CMTime) -> (fps: CMTime, timebase: CMTime) { + let timebase: CMTime + var fps: CMTime + + if stream.pointee.time_base.den != 0 && stream.pointee.time_base.num != 0 { + timebase = CMTimeMake(value: Int64(stream.pointee.time_base.num), timescale: stream.pointee.time_base.den) + } else if stream.pointee.codec.pointee.time_base.den != 0 && stream.pointee.codec.pointee.time_base.num != 0 { + timebase = CMTimeMake(value: Int64(stream.pointee.codec.pointee.time_base.num), timescale: stream.pointee.codec.pointee.time_base.den) + } else { + timebase = defaultTimeBase + } + + if stream.pointee.avg_frame_rate.den != 0 && stream.pointee.avg_frame_rate.num != 0 { + fps = CMTimeMake(value: Int64(stream.pointee.avg_frame_rate.num), timescale: stream.pointee.avg_frame_rate.den) + } else if stream.pointee.r_frame_rate.den != 0 && stream.pointee.r_frame_rate.num != 0 { + fps = CMTimeMake(value: Int64(stream.pointee.r_frame_rate.num), timescale: stream.pointee.r_frame_rate.den) + } else { + fps = CMTimeMake(value: 1, timescale: 24) + } + + return (fps, timebase) + } +} diff --git a/Telegram-Mac/FFMpegMediaPassthroughVideoFrameDecoder.swift b/Telegram-Mac/FFMpegMediaPassthroughVideoFrameDecoder.swift new file mode 100644 index 0000000000..f417d3d40a --- /dev/null +++ b/Telegram-Mac/FFMpegMediaPassthroughVideoFrameDecoder.swift @@ -0,0 +1,49 @@ +import CoreMedia + +final class FFMpegMediaPassthroughVideoFrameDecoder: MediaTrackFrameDecoder { + private let videoFormat: CMVideoFormatDescription + private let rotationAngle: Double + private var resetDecoderOnNextFrame = true + + init(videoFormat: CMVideoFormatDescription, rotationAngle: Double) { + self.videoFormat = videoFormat + self.rotationAngle = rotationAngle + } + + func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { + var blockBuffer: CMBlockBuffer? + + let bytes = malloc(Int(frame.packet.size))! + memcpy(bytes, frame.packet.data, Int(frame.packet.size)) + guard CMBlockBufferCreateWithMemoryBlock(allocator: nil, memoryBlock: bytes, blockLength: Int(frame.packet.size), blockAllocator: nil, customBlockSource: nil, offsetToData: 0, dataLength: Int(frame.packet.size), flags: 0, blockBufferOut: &blockBuffer) == noErr else { + free(bytes) + return nil + } + + var timingInfo = CMSampleTimingInfo(duration: frame.duration, presentationTimeStamp: frame.pts, decodeTimeStamp: frame.dts) + var sampleBuffer: CMSampleBuffer? + var sampleSize = Int(frame.packet.size) + guard CMSampleBufferCreate(allocator: nil, dataBuffer: blockBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: self.videoFormat, sampleCount: 1, sampleTimingEntryCount: 1, sampleTimingArray: &timingInfo, sampleSizeEntryCount: 1, sampleSizeArray: &sampleSize, sampleBufferOut: &sampleBuffer) == noErr else { + return nil + } + + let resetDecoder = self.resetDecoderOnNextFrame + if self.resetDecoderOnNextFrame { + self.resetDecoderOnNextFrame = false + let attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer!, createIfNecessary: true)! as NSArray + let dict = attachments[0] as! NSMutableDictionary + + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding as NSString as String) + } + + return MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: false, rotationAngle: self.rotationAngle) + } + + func takeRemainingFrame() -> MediaTrackFrame? { + return nil + } + + func reset() { + self.resetDecoderOnNextFrame = true + } +} diff --git a/Telegram-Mac/FFMpegMediaVideoFrameDecoder.swift b/Telegram-Mac/FFMpegMediaVideoFrameDecoder.swift new file mode 100644 index 0000000000..95843bef60 --- /dev/null +++ b/Telegram-Mac/FFMpegMediaVideoFrameDecoder.swift @@ -0,0 +1,372 @@ +import CoreMedia +import Accelerate + +private let bufferCount = 32 + +private let deviceColorSpace: CGColorSpace = { + if #available(OSX 10.11.2, *) { + if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) { + return colorSpace + } else { + return CGColorSpaceCreateDeviceRGB() + } + } else { + return CGColorSpaceCreateDeviceRGB() + } +}() + +final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { + enum ReceiveResult { + case error + case moreDataNeeded + case result(MediaTrackFrame) + } + + private let codecContext: FFMpegAVCodecContext + + private let videoFrame: FFMpegAVFrame + private var resetDecoderOnNextFrame = true + + private var defaultDuration: CMTime? + private var defaultTimescale: CMTimeScale? + + private var pixelBufferPool: CVPixelBufferPool? + + private var delayedFrames: [MediaTrackFrame] = [] + + init(codecContext: FFMpegAVCodecContext) { + self.codecContext = codecContext + self.videoFrame = FFMpegAVFrame() + + /*var sourcePixelBufferOptions: [String: Any] = [:] + sourcePixelBufferOptions[kCVPixelBufferPixelFormatTypeKey as String] = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange as NSNumber + + sourcePixelBufferOptions[kCVPixelBufferWidthKey as String] = codecContext.pointee.width as NSNumber + sourcePixelBufferOptions[kCVPixelBufferHeightKey as String] = codecContext.pointee.height as NSNumber + sourcePixelBufferOptions[kCVPixelBufferBytesPerRowAlignmentKey as String] = 128 as NSNumber + sourcePixelBufferOptions[kCVPixelBufferPlaneAlignmentKey as String] = 128 as NSNumber + + let ioSurfaceProperties = NSMutableDictionary() + ioSurfaceProperties["IOSurfaceIsGlobal"] = true as NSNumber + + sourcePixelBufferOptions[kCVPixelBufferIOSurfacePropertiesKey as String] = ioSurfaceProperties + + var pixelBufferPoolOptions: [String: Any] = [:] + pixelBufferPoolOptions[kCVPixelBufferPoolMinimumBufferCountKey as String] = bufferCount as NSNumber + + var pixelBufferPool: CVPixelBufferPool? + CVPixelBufferPoolCreate(kCFAllocatorDefault, pixelBufferPoolOptions as CFDictionary, sourcePixelBufferOptions as CFDictionary, &pixelBufferPool) + + self.pixelBufferPool = pixelBufferPool*/ + } + + func decodeInternal(frame: MediaTrackDecodableFrame) { + + } + + func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? { + return self.decode(frame: frame, ptsOffset: nil) + } + + func sendToDecoder(frame: MediaTrackDecodableFrame) -> Bool { + self.defaultDuration = frame.duration + self.defaultTimescale = frame.pts.timescale + + let status = frame.packet.send(toDecoder: self.codecContext) + return status == 0 + } + + func sendEndToDecoder() -> Bool { + return self.codecContext.sendEnd() + } + + func receiveFromDecoder(ptsOffset: CMTime?) -> ReceiveResult { + guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { + return .error + } + + let receiveResult = self.codecContext.receive(into: self.videoFrame) + switch receiveResult { + case .success: + var pts = CMTimeMake(value: self.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) { + return .result(convertedFrame) + } else { + return .error + } + case .notEnoughData: + return .moreDataNeeded + case .error: + return .error + @unknown default: + return .error + } + } + + func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?) -> MediaTrackFrame? { + let status = frame.packet.send(toDecoder: self.codecContext) + if status == 0 { + self.defaultDuration = frame.duration + self.defaultTimescale = frame.pts.timescale + + if self.codecContext.receive(into: self.videoFrame) == .success { + var pts = CMTimeMake(value: self.videoFrame.pts, timescale: frame.pts.timescale) + if let ptsOffset = ptsOffset { + pts = CMTimeAdd(pts, ptsOffset) + } + return convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: frame.duration) + } + } + + return nil + } + + func receiveRemainingFrames(ptsOffset: CMTime?) -> [MediaTrackFrame] { + guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { + return [] + } + + var result: [MediaTrackFrame] = [] + result.append(contentsOf: self.delayedFrames) + self.delayedFrames.removeAll() + + while true { + if case .success = self.codecContext.receive(into: self.videoFrame) { + var pts = CMTimeMake(value: self.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) { + result.append(convertedFrame) + } + } else { + break + } + } + return result + } + + func render(frame: MediaTrackDecodableFrame) -> CGImage? { + let status = frame.packet.send(toDecoder: self.codecContext) + if status == 0 { + if case .success = self.codecContext.receive(into: self.videoFrame) { + return convertVideoFrameToImage(self.videoFrame) + } + } + + return nil + } + + func takeQueuedFrame() -> MediaTrackFrame? { + return nil + } + + func takeRemainingFrame() -> MediaTrackFrame? { + if !self.delayedFrames.isEmpty { + var minFrameIndex = 0 + var minPosition = self.delayedFrames[0].position + for i in 1 ..< self.delayedFrames.count { + if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 { + minFrameIndex = i + minPosition = self.delayedFrames[i].position + } + } + return self.delayedFrames.remove(at: minFrameIndex) + } else { + return nil + } + } + + private func convertVideoFrameToImage(_ frame: FFMpegAVFrame) -> CGImage? { + var info = vImage_YpCbCrToARGB() + + var pixelRange: vImage_YpCbCrPixelRange + switch frame.colorRange { + case .full: + pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 0, CbCr_bias: 128, YpRangeMax: 255, CbCrRangeMax: 255, YpMax: 255, YpMin: 0, CbCrMax: 255, CbCrMin: 0) + default: + pixelRange = vImage_YpCbCrPixelRange(Yp_bias: 16, CbCr_bias: 128, YpRangeMax: 235, CbCrRangeMax: 240, YpMax: 255, YpMin: 0, CbCrMax: 255, CbCrMin: 0) + } + var result = kvImageNoError + result = vImageConvert_YpCbCrToARGB_GenerateConversion(kvImage_YpCbCrToARGBMatrix_ITU_R_709_2, &pixelRange, &info, kvImage420Yp8_Cb8_Cr8, kvImageARGB8888, 0) + if result != kvImageNoError { + return nil + } + + var srcYp = vImage_Buffer(data: frame.data[0], height: vImagePixelCount(frame.height), width: vImagePixelCount(frame.width), rowBytes: Int(frame.lineSize[0])) + var srcCb = vImage_Buffer(data: frame.data[1], height: vImagePixelCount(frame.height), width: vImagePixelCount(frame.width / 2), rowBytes: Int(frame.lineSize[1])) + var srcCr = vImage_Buffer(data: frame.data[2], height: vImagePixelCount(frame.height), width: vImagePixelCount(frame.width / 2), rowBytes: Int(frame.lineSize[2])) + + let argbBytesPerRow = (4 * Int(frame.width) + 15) & (~15) + let argbLength = argbBytesPerRow * Int(frame.height) + let argb = malloc(argbLength)! + guard let provider = CGDataProvider(dataInfo: argb, data: argb, size: argbLength, releaseData: { bytes, _, _ in + free(bytes) + }) else { + return nil + } + + var dst = vImage_Buffer(data: argb, height: vImagePixelCount(frame.height), width: vImagePixelCount(frame.width), rowBytes: argbBytesPerRow) + + var permuteMap: [UInt8] = [3, 2, 1, 0] + + result = vImageConvert_420Yp8_Cb8_Cr8ToARGB8888(&srcYp, &srcCb, &srcCr, &dst, &info, &permuteMap, 0x00, 0) + if result != kvImageNoError { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue) + + guard let image = CGImage(width: Int(frame.width), height: Int(frame.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: argbBytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) else { + return nil + } + + return image + } + + private func convertVideoFrame(_ frame: FFMpegAVFrame, pts: CMTime, dts: CMTime, duration: CMTime) -> MediaTrackFrame? { + if frame.data[0] == nil { + return nil + } + if frame.lineSize[1] != frame.lineSize[2] { + return nil + } + + var pixelBufferRef: CVPixelBuffer? + if let pixelBufferPool = self.pixelBufferPool { + let auxAttributes: [String: Any] = [kCVPixelBufferPoolAllocationThresholdKey as String: bufferCount as NSNumber]; + let err = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(kCFAllocatorDefault, pixelBufferPool, auxAttributes as CFDictionary, &pixelBufferRef) + if err == kCVReturnWouldExceedAllocationThreshold { + print("kCVReturnWouldExceedAllocationThreshold, dropping frame") + return nil + } + } else { + let ioSurfaceProperties = NSMutableDictionary() + ioSurfaceProperties["IOSurfaceIsGlobal"] = true as NSNumber + + var options: [String: Any] = [kCVPixelBufferBytesPerRowAlignmentKey as String: frame.lineSize[0] as NSNumber] + options[kCVPixelBufferIOSurfacePropertiesKey as String] = ioSurfaceProperties + + CVPixelBufferCreate(kCFAllocatorDefault, + Int(frame.width), + Int(frame.height), + kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, + options as CFDictionary, + &pixelBufferRef) + } + + guard let pixelBuffer = pixelBufferRef else { + return nil + } + + let srcPlaneSize = Int(frame.lineSize[1]) * Int(frame.height / 2) + let dstPlaneSize = srcPlaneSize * 2 + + let dstPlane = malloc(dstPlaneSize)!.assumingMemoryBound(to: UInt8.self) + defer { + free(dstPlane) + } + + for i in 0 ..< srcPlaneSize { + dstPlane[2 * i] = frame.data[1]![i] + dstPlane[2 * i + 1] = frame.data[2]![i] + } + + let status = CVPixelBufferLockBaseAddress(pixelBuffer, []) + if status != kCVReturnSuccess { + return nil + } + + let bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0) + + let bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1) + + var base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)! + if bytePerRowY == frame.lineSize[0] { + memcpy(base, frame.data[0]!, bytePerRowY * Int(frame.height)) + } else { + var dest = base + var src = frame.data[0]! + let linesize = Int(frame.lineSize[0]) + for _ in 0 ..< Int(frame.height) { + memcpy(dest, src, linesize) + dest = dest.advanced(by: bytePerRowY) + src = src.advanced(by: linesize) + } + } + + base = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)! + if bytesPerRowUV == frame.lineSize[1] * 2 { + memcpy(base, dstPlane, Int(frame.height / 2) * bytesPerRowUV) + } else { + var dest = base + var src = dstPlane + let linesize = Int(frame.lineSize[1]) * 2 + for _ in 0 ..< Int(frame.height / 2) { + memcpy(dest, src, linesize) + dest = dest.advanced(by: bytesPerRowUV) + src = src.advanced(by: linesize) + } + } + + CVPixelBufferUnlockBaseAddress(pixelBuffer, []) + + var formatRef: CMVideoFormatDescription? + let formatStatus = CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescriptionOut: &formatRef) + + guard let format = formatRef, formatStatus == 0 else { + return nil + } + + var timingInfo = CMSampleTimingInfo(duration: duration, presentationTimeStamp: pts, decodeTimeStamp: pts) + var sampleBuffer: CMSampleBuffer? + + guard CMSampleBufferCreateReadyWithImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescription: format, sampleTiming: &timingInfo, sampleBufferOut: &sampleBuffer) == noErr else { + return nil + } + + let attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer!, createIfNecessary: true)! as NSArray + let dict = attachments[0] as! NSMutableDictionary + + let resetDecoder = self.resetDecoderOnNextFrame + if self.resetDecoderOnNextFrame { + self.resetDecoderOnNextFrame = false + //dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding as NSString as String) + } + + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String) + + let decodedFrame = MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: true) + + self.delayedFrames.append(decodedFrame) + + if self.delayedFrames.count >= 1 { + var minFrameIndex = 0 + var minPosition = self.delayedFrames[0].position + for i in 1 ..< self.delayedFrames.count { + if CMTimeCompare(self.delayedFrames[i].position, minPosition) < 0 { + minFrameIndex = i + minPosition = self.delayedFrames[i].position + } + } + if minFrameIndex != 0 { + assert(true) + } + return self.delayedFrames.remove(at: minFrameIndex) + } else { + return nil + } + } + + func decodeImage() { + + } + + func reset() { + self.codecContext.flushBuffers() + self.resetDecoderOnNextFrame = true + } +} diff --git a/Telegram-Mac/FFMpegPacket.h b/Telegram-Mac/FFMpegPacket.h new file mode 100644 index 0000000000..28d843567c --- /dev/null +++ b/Telegram-Mac/FFMpegPacket.h @@ -0,0 +1,29 @@ +// +// FFMpegPacket.h +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class FFMpegAVCodecContext; + +@interface FFMpegPacket : NSObject + +@property (nonatomic, readonly) int64_t pts; +@property (nonatomic, readonly) int64_t dts; +@property (nonatomic, readonly) int64_t duration; +@property (nonatomic, readonly) int32_t streamIndex; +@property (nonatomic, readonly) int32_t size; +@property (nonatomic, readonly) uint8_t *data; + +- (void *)impl; +- (int32_t)sendToDecoder:(FFMpegAVCodecContext *)codecContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegPacket.m b/Telegram-Mac/FFMpegPacket.m new file mode 100644 index 0000000000..cabd863395 --- /dev/null +++ b/Telegram-Mac/FFMpegPacket.m @@ -0,0 +1,68 @@ +// +// FFMpegPacket.m +// Telegram +// +// Created by Mikhail Filimonov on 05/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// +#import "FFMpegPacket.h" +#import "FFMpegAVCodecContext.h" +#import "libavformat/avformat.h" + +@interface FFMpegPacket () { + AVPacket _impl; +} + +@end + +@implementation FFMpegPacket + +- (instancetype)init { + self = [super init]; + if (self != nil) { + av_init_packet(&_impl); + } + return self; +} + +- (void)dealloc { + av_packet_unref(&_impl); +} + +- (void *)impl { + return &_impl; +} + +- (int64_t)pts { + if (_impl.pts == 0x8000000000000000) { + return _impl.dts; + } else { + return _impl.pts; + } +} + +- (int64_t)dts { + return _impl.dts; +} + +- (int64_t)duration { + return _impl.duration; +} + +- (int32_t)streamIndex { + return (int32_t)_impl.stream_index; +} + +- (int32_t)size { + return (int32_t)_impl.size; +} + +- (uint8_t *)data { + return _impl.data; +} + +- (int32_t)sendToDecoder:(FFMpegAVCodecContext *)codecContext { + return avcodec_send_packet((AVCodecContext *)[codecContext impl], &_impl); +} + +@end diff --git a/Telegram-Mac/FFMpegSwResample.h b/Telegram-Mac/FFMpegSwResample.h new file mode 100755 index 0000000000..54b283395a --- /dev/null +++ b/Telegram-Mac/FFMpegSwResample.h @@ -0,0 +1,17 @@ +#import + + +#import "FFMpegAVSampleFormat.h" + +NS_ASSUME_NONNULL_BEGIN + +@class FFMpegAVFrame; + +@interface FFMpegSWResample : NSObject + +- (instancetype)initWithSourceChannelCount:(NSInteger)sourceChannelCount sourceSampleRate:(NSInteger)sourceSampleRate sourceSampleFormat:(enum FFMpegAVSampleFormat)sourceSampleFormat destinationChannelCount:(NSInteger)destinationChannelCount destinationSampleRate:(NSInteger)destinationSampleRate destinationSampleFormat:(enum FFMpegAVSampleFormat)destinationSampleFormat; +- (NSData * _Nullable)resample:(FFMpegAVFrame *)frame; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Telegram-Mac/FFMpegSwResample.m b/Telegram-Mac/FFMpegSwResample.m new file mode 100755 index 0000000000..06ec830f1a --- /dev/null +++ b/Telegram-Mac/FFMpegSwResample.m @@ -0,0 +1,74 @@ +#import "FFMpegSWResample.h" +#import "FFMpegAVFrame.h" + +#import "libavcodec/avcodec.h" +#import "libswresample/swresample.h" + +@interface FFMpegSWResample () { + SwrContext *_context; + NSUInteger _ratio; + NSInteger _destinationChannelCount; + enum FFMpegAVSampleFormat _destinationSampleFormat; + void *_buffer; + int _bufferSize; +} + +@end + +@implementation FFMpegSWResample + +- (instancetype)initWithSourceChannelCount:(NSInteger)sourceChannelCount sourceSampleRate:(NSInteger)sourceSampleRate sourceSampleFormat:(enum FFMpegAVSampleFormat)sourceSampleFormat destinationChannelCount:(NSInteger)destinationChannelCount destinationSampleRate:(NSInteger)destinationSampleRate destinationSampleFormat:(enum FFMpegAVSampleFormat)destinationSampleFormat { + self = [super init]; + if (self != nil) { + _destinationChannelCount = destinationChannelCount; + _destinationSampleFormat = destinationSampleFormat; + _context = swr_alloc_set_opts(NULL, + av_get_default_channel_layout((int)destinationChannelCount), + (enum AVSampleFormat)destinationSampleFormat, + (int)destinationSampleRate, + av_get_default_channel_layout((int)sourceChannelCount), + (enum AVSampleFormat)sourceSampleFormat, + (int)sourceSampleRate, + 0, + NULL); + _ratio = MAX(1, destinationSampleRate / sourceSampleRate) * MAX(1, destinationChannelCount / sourceChannelCount) * 2; + swr_init(_context); + } + return self; +} + +- (void)dealloc { + swr_free(&_context); + if (_buffer) { + free(_buffer); + } +} + +- (NSData * _Nullable)resample:(FFMpegAVFrame *)frame { + AVFrame *frameImpl = (AVFrame *)[frame impl]; + int bufSize = av_samples_get_buffer_size(NULL, + (int)_destinationChannelCount, + frameImpl->nb_samples * (int)_ratio, + (enum AVSampleFormat)_destinationSampleFormat, + 1); + + if (!_buffer || _bufferSize < bufSize) { + _bufferSize = bufSize; + _buffer = realloc(_buffer, _bufferSize); + } + + Byte *outbuf[2] = { _buffer, 0 }; + + int numFrames = swr_convert(_context, + outbuf, + frameImpl->nb_samples * (int)_ratio, + (const uint8_t **)frameImpl->data, + frameImpl->nb_samples); + if (numFrames <= 0) { + return nil; + } + + return [[NSData alloc] initWithBytes:_buffer length:numFrames * _destinationChannelCount * 2]; +} + +@end diff --git a/Telegram-Mac/FWDNavigationAction.swift b/Telegram-Mac/FWDNavigationAction.swift index f0e176ae69..dbf940f3c4 100644 --- a/Telegram-Mac/FWDNavigationAction.swift +++ b/Telegram-Mac/FWDNavigationAction.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class FWDNavigationAction: NavigationModalAction { let messages:[Message] @@ -20,23 +21,19 @@ class FWDNavigationAction: NavigationModalAction { init(messages:[Message], displayName:String) { self.messages = messages - super.init(reason: tr(.forwardModalActionTitleCountable(messages.count)), desc: tr(.forwardModalActionDescriptionCountable(messages.count, displayName))) + super.init(reason: L10n.forwardModalActionTitleCountable(messages.count), desc: L10n.forwardModalActionDescriptionCountable(messages.count, displayName)) } override func isInvokable(for value:Any) -> Bool { if let value = value as? Peer { - for message in messages { - if !message.possibilityForwardTo(value) { - return false - } - } + return value.canSendMessage } return true } override func alertError(for value:Any, with window:Window) -> Void { if let _ = value as? Peer { - alert(for: window, header: appName, info: tr(.alertForwardError)) + alert(for: window, info: tr(L10n.alertForwardError)) } } diff --git a/Telegram-Mac/FastBlur.h b/Telegram-Mac/FastBlur.h index 6a0448eef5..9cd7646dd7 100644 --- a/Telegram-Mac/FastBlur.h +++ b/Telegram-Mac/FastBlur.h @@ -12,5 +12,6 @@ #import void telegramFastBlur(int imageWidth, int imageHeight, int imageStride, void *pixels); - +void telegramFastBlurMore(int imageWidth, int imageHeight, int imageStride, void *pixels); +void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void *pixels); #endif diff --git a/Telegram-Mac/FastBlur.m b/Telegram-Mac/FastBlur.m index e125ce260d..f4faf4f63c 100644 --- a/Telegram-Mac/FastBlur.m +++ b/Telegram-Mac/FastBlur.m @@ -7,6 +7,8 @@ // #import "FastBlur.h" +#import + @@ -110,3 +112,128 @@ void telegramFastBlur(int imageWidth, int imageHeight, int imageStride, void *pi free(rgb); } + + + + + +void telegramFastBlurMore(int imageWidth, int imageHeight, int imageStride, void *pixels) +{ + uint8_t *pix = (uint8_t *)pixels; + const int w = imageWidth; + const int h = imageHeight; + const int stride = imageStride; + const int radius = 7; + const int r1 = radius + 1; + const int div = radius * 2 + 1; + + if (radius > 15 || div >= w || div >= h) + { + return; + } + + uint64_t *rgb = malloc(imageStride * imageHeight * sizeof(uint64_t)); + + int x, y, i; + + int yw = 0; + const int we = w - r1; + for (y = 0; y < h; y++) { + uint64_t cur = get_colors (&pix[yw]); + uint64_t rgballsum = -radius * cur; + uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1); + + for (i = 1; i <= radius; i++) { + uint64_t cur = get_colors (&pix[yw + i * 4]); + rgbsum += cur * (r1 - i); + rgballsum += cur; + } + + x = 0; + +#define update(start, middle, end) \ +rgb[y * w + x] = (rgbsum >> 6) & 0x00FF00FF00FF00FF; \ +\ +rgballsum += get_colors (&pix[yw + (start) * 4]) - \ +2 * get_colors (&pix[yw + (middle) * 4]) + \ +get_colors (&pix[yw + (end) * 4]); \ +rgbsum += rgballsum; \ +x++; \ + + while (x < r1) { + update (0, x, x + r1); + } + while (x < we) { + update (x - r1, x, x + r1); + } + while (x < w) { + update (x - r1, x, w - 1); + } +#undef update + + yw += stride; + } + + const int he = h - r1; + for (x = 0; x < w; x++) { + uint64_t rgballsum = -radius * rgb[x]; + uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1); + for (i = 1; i <= radius; i++) { + rgbsum += rgb[i * w + x] * (r1 - i); + rgballsum += rgb[i * w + x]; + } + + y = 0; + int yi = x * 4; + +#define update(start, middle, end) \ +int64_t res = rgbsum >> 6; \ +pix[yi] = (uint8_t)res; \ +pix[yi + 1] = (uint8_t)(res >> 16); \ +pix[yi + 2] = (uint8_t)(res >> 32); \ +\ +rgballsum += rgb[x + (start) * w] - \ +2 * rgb[x + (middle) * w] + \ +rgb[x + (end) * w]; \ +rgbsum += rgballsum; \ +y++; \ +yi += stride; + + while (y < r1) { + update (0, y, y + r1); + } + while (y < he) { + update (y - r1, y, y + r1); + } + while (y < h) { + update (y - r1, y, h - 1); + } +#undef update + } + + free(rgb); +} + + + +void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void *pixels) { + vImage_Buffer srcBuffer; + srcBuffer.width = imageWidth; + srcBuffer.height = imageHeight; + srcBuffer.rowBytes = imageStride; + srcBuffer.data = pixels; + + { + vImage_Buffer dstBuffer; + dstBuffer.width = imageWidth; + dstBuffer.height = imageHeight; + dstBuffer.rowBytes = imageStride; + dstBuffer.data = pixels; + + int boxSize = 2; + boxSize = boxSize - (boxSize % 2) + 1; + + vImageBoxConvolve_ARGB8888(&srcBuffer, &dstBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend); + } + +} diff --git a/Telegram-Mac/FastSettings.swift b/Telegram-Mac/FastSettings.swift index a5f7b60e20..6842167569 100644 --- a/Telegram-Mac/FastSettings.swift +++ b/Telegram-Mac/FastSettings.swift @@ -7,9 +7,10 @@ // import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox enum SendingType :String { case enter = "enter" @@ -31,6 +32,86 @@ enum ForceTouchAction: Int32 { case edit case reply case forward + case previewMedia +} + +enum ContextTextTooltip : Int32 { + case reply + case edit +} + +enum BotPemissionKey: String { + case contact = "PermissionInlineBotContact" +} + + +enum AppTooltip { + case voiceRecording + case videoRecording + case mediaPreview_archive + case mediaPreview_collage + case mediaPreview_media + case mediaPreview_file + fileprivate var localizedString: String { + switch self { + case .voiceRecording: + return L10n.appTooltipVoiceRecord + case .videoRecording: + return L10n.appTooltipVideoRecord + case .mediaPreview_archive: + return L10n.previewSenderArchiveTooltip + case .mediaPreview_collage: + return L10n.previewSenderCollageTooltip + case .mediaPreview_media: + return L10n.previewSenderMediaTooltip + case .mediaPreview_file: + return L10n.previewSenderFileTooltip + } + } + + private var version:Int { + return 1 + } + + fileprivate var key: String { + switch self { + case .voiceRecording: + return "app_tooltip_voice_recording_" + "\(version)" + case .videoRecording: + return "app_tooltip_video_recording_" + "\(version)" + case .mediaPreview_archive: + return "app_tooltip_mediaPreview_archive_" + "\(version)" + case .mediaPreview_collage: + return "app_tooltip_mediaPreview_collage_" + "\(version)" + case .mediaPreview_media: + return "app_tooltip_mediaPreview_media_" + "\(version)" + case .mediaPreview_file: + return "app_tooltip_mediaPreview_file_" + "\(version)" + } + } + + fileprivate var showCount: Int { + return 4 + } + +} + +func getAppTooltip(for value: AppTooltip, callback: (String) -> Void) { + let shownCount: Int = UserDefaults.standard.integer(forKey: value.key) + + var success: Bool = false + + defer { + if success { + UserDefaults.standard.set(shownCount + 1, forKey: value.key) + UserDefaults.standard.synchronize() + } + } + //shownCount == 0 || (shownCount < value.showCount && arc4random_uniform(100) > 100 / 3) + if true { + success = true + callback(value.localizedString) + } } class FastSettings { @@ -44,7 +125,24 @@ class FastSettings { private static let kIsMinimisizeType = "kIsMinimisizeType" private static let kAutomaticConvertEmojiesType = "kAutomaticConvertEmojiesType2" private static let kForceTouchAction = "kForceTouchAction" + private static let kNeedCollage = "kNeedCollage" + private static let kInstantViewScrollBySpace = "kInstantViewScrollBySpace" + private static let kAutomaticallyPlayGifs = "kAutomaticallyPlayGifs" + private static let kArchiveIsHidden = "kArchiveIsHidden" + private static let kRTFEnable = "kRTFEnable"; + private static let kNeedShowChannelIntro = "kNeedShowChannelIntro" + + private static let kNoticeAdChannel = "kNoticeAdChannel" + private static let kPlayingRate = "kPlayingRate" + + + private static let kVolumeRate = "kVolumeRate" + private static let kArchiveAutohidden = "kArchiveAutohidden" + private static let kAutohideArchiveFeature = "kAutohideArchiveFeature" + + private static let kLeftColumnWidth = "kLeftColumnWidth" + static var sendingType:SendingType { let type = UserDefaults.standard.value(forKey: kSendingType) as? String if let type = type { @@ -79,6 +177,26 @@ class FastSettings { UserDefaults.standard.set(!isChannelMessagesMuted(peerId), forKey: "\(peerId)_m_muted") } + static var playingRate: Double { + return min(max(UserDefaults.standard.double(forKey: kPlayingRate), 1), 1.7) + } + + static func setPlayingRate(_ rate: Double) { + UserDefaults.standard.set(rate, forKey: kPlayingRate) + } + + static var volumeRate: Float { + if UserDefaults.standard.value(forKey: kVolumeRate) != nil { + return min(max(UserDefaults.standard.float(forKey: kVolumeRate), 0), 1) + } else { + return 0.8 + } + } + + static func setVolumeRate(_ rate: Float) { + UserDefaults.standard.set(rate, forKey: kVolumeRate) + } + static var isMinimisize: Bool { get { return UserDefaults.standard.bool(forKey: kIsMinimisizeType) @@ -100,41 +218,156 @@ class FastSettings { return RecordingStateSettings(rawValue: Int32(UserDefaults.standard.integer(forKey: kRecordingStateType))) ?? .voice } + static var isNeedCollage: Bool { + return UserDefaults.standard.bool(forKey: kNeedCollage) + } + + static var enableRTF: Bool { + set { + UserDefaults.standard.set(!newValue, forKey: kRTFEnable) + UserDefaults.standard.synchronize() + } + get { + return !UserDefaults.standard.bool(forKey: kRTFEnable) + } + } + + static func toggleIsNeedCollage(_ enable: Bool) -> Void { + UserDefaults.standard.set(enable, forKey: kNeedCollage) + UserDefaults.standard.synchronize() + } + static func toggleRecordingState() { UserDefaults.standard.set((recordingState == .voice ? RecordingStateSettings.video : RecordingStateSettings.voice).rawValue, forKey: kRecordingStateType) } + static var needShowChannelIntro: Bool { + return !UserDefaults.standard.bool(forKey: kNeedShowChannelIntro) + } + + static func markChannelIntroHasSeen() { + UserDefaults.standard.set(true, forKey: kNeedShowChannelIntro) + } + static var forceTouchAction: ForceTouchAction { return ForceTouchAction(rawValue: Int32(UserDefaults.standard.integer(forKey: kForceTouchAction))) ?? .edit } static func toggleForceTouchAction(_ action: ForceTouchAction) { UserDefaults.standard.set(action.rawValue, forKey: kForceTouchAction) + UserDefaults.standard.synchronize() + } + + static func tooltipAbility(for tooltip: ContextTextTooltip) -> Bool { + let value = UserDefaults.standard.integer(forKey: "tooltip:\(tooltip.rawValue)") + UserDefaults.standard.set(value + 1, forKey: "tooltip:\(tooltip.rawValue)") + return value < 12 + } + + static func archivedTooltipCountAndIncrement() -> Int { + let value = UserDefaults.standard.integer(forKey: "archivation_tooltips") + UserDefaults.standard.set(value + 1, forKey: "archivation_tooltips") + return value + } + + static var showAdAlert: Bool { + return !UserDefaults.standard.bool(forKey: kNoticeAdChannel) + } + + static func adAlertViewed() { + UserDefaults.standard.set(true, forKey: kNoticeAdChannel) + UserDefaults.standard.synchronize() + } + + static func openInQuickLook(_ ext: String) -> Bool { + return !UserDefaults.standard.bool(forKey: "open_in_quick_look_\(ext)") + } + static func toggleOpenInQuickLook(_ ext: String) -> Void { + UserDefaults.standard.set(openInQuickLook(ext), forKey: "open_in_quick_look_\(ext)") + UserDefaults.standard.synchronize() } static func toggleSidebarShown(_ enable: Bool) { UserDefaults.standard.set(!enable, forKey: kSidebarShownType) + UserDefaults.standard.synchronize() } static func toggleSidebar(_ enable: Bool) { UserDefaults.standard.set(enable, forKey: kSidebarType) + UserDefaults.standard.synchronize() } static func toggleInAppSouds(_ enable: Bool) { UserDefaults.standard.set(!enable, forKey: kInAppSoundsType) + UserDefaults.standard.synchronize() } static var inAppSounds: Bool { return !UserDefaults.standard.bool(forKey: kInAppSoundsType) } + + static func toggleInstantViewScrollBySpace(_ enable: Bool) { + UserDefaults.standard.set(enable, forKey: kInstantViewScrollBySpace) + UserDefaults.standard.synchronize() + } static func toggleAutomaticReplaceEmojies(_ enable: Bool) { UserDefaults.standard.set(!enable, forKey: kAutomaticConvertEmojiesType) + UserDefaults.standard.synchronize() } static var isPossibleReplaceEmojies: Bool { return !UserDefaults.standard.bool(forKey: kAutomaticConvertEmojiesType) } + + static var instantViewScrollBySpace: Bool { + return UserDefaults.standard.bool(forKey: kInstantViewScrollBySpace) + } + + static func toggleAutoPlayGifs(_ enable: Bool) { + UserDefaults.standard.set(!enable, forKey: kAutomaticallyPlayGifs) + UserDefaults.standard.synchronize() + } + + static var gifsAutoPlay:Bool { + return !UserDefaults.standard.bool(forKey: kAutomaticallyPlayGifs) + } + + static var archiveStatus: HiddenArchiveStatus { + get { + let value = UserDefaults.standard.integer(forKey: kArchiveIsHidden) + return HiddenArchiveStatus(rawValue: min(value, 3))! + } + set { + UserDefaults.standard.set(newValue.rawValue, forKey: kArchiveIsHidden) + UserDefaults.standard.synchronize() + } + } + + static func showPromoTitle(for peerId: PeerId) -> Bool { + return UserDefaults.standard.value(forKey: "promo_\(peerId)_1") as? Bool ?? true + } + static func removePromoTitle(for peerId: PeerId) { + UserDefaults.standard.set(false, forKey: "promo_\(peerId)_1") + UserDefaults.standard.synchronize() + } + + static func isTestLiked(_ messageId: MessageId) -> Bool { + return UserDefaults.standard.value(forKey: "isTestLiked_\(messageId)") as? Bool ?? false + } + static func toggleTestLike(_ messageId: MessageId) { + UserDefaults.standard.set(!isTestLiked(messageId), forKey: "isTestLiked_\(messageId)") + UserDefaults.standard.synchronize() + } + + static func isSecretChatWebPreviewAvailable(for accountId: Int64) -> Bool? { + return UserDefaults.standard.value(forKey: "IsSecretChatWebPreviewAvailable_\(accountId)") as? Bool + } + + static func setSecretChatWebPreviewAvailable(for accountId: Int64, value: Bool) -> Void { + UserDefaults.standard.set(value, forKey: "IsSecretChatWebPreviewAvailable_\(accountId)") + UserDefaults.standard.synchronize() + } static var downloadsFolder:String? { let paths = NSSearchPathForDirectoriesInDomains(.downloadsDirectory, .userDomainMask, true) @@ -142,18 +375,101 @@ class FastSettings { return path } + + static func requstPermission(with permission: BotPemissionKey, for peerId: PeerId, success: @escaping()->Void) { + + let localizedHeader = _NSLocalizedString("Confirm.Header.\(permission.rawValue)") + let localizedDesc = _NSLocalizedString("Confirm.Desc.\(permission.rawValue)") + confirm(for: mainWindow, header: localizedHeader, information: localizedDesc, successHandler: { _ in + success() + }) + } + + static func diceHasAlreadyPlayed(_ message: Message) -> Bool { + return UserDefaults.standard.bool(forKey: "dice_\(message.id.id)_\(message.id.namespace)_\(message.stableId)") + } + static func markDiceAsPlayed(_ message: Message) { + UserDefaults.standard.set(true, forKey: "dice_\(message.id.id)_\(message.id.namespace)_\(message.stableId)") + UserDefaults.standard.synchronize() + } + + static func updateLeftColumnWidth(_ width: CGFloat) { + UserDefaults.standard.set(round(width), forKey: kLeftColumnWidth) + UserDefaults.standard.synchronize() + } + static var leftColumnWidth: CGFloat { + return round(UserDefaults.standard.value(forKey: kLeftColumnWidth) as? CGFloat ?? 300) + } + + /* + + +(void)requestPermissionWithKey:(NSString *)permissionKey peer_id:(int)peer_id handler:(void (^)(bool success))handler { + + static NSMutableDictionary *denied; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + denied = [NSMutableDictionary dictionary]; + }); + + + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + NSString *key = [NSString stringWithFormat:@"%@:%d",permissionKey,peer_id]; + + BOOL access = [defaults boolForKey:key]; + + + if(access) { + if(handler) + handler(access); + } else { + + if([denied[key] boolValue]) { + if(handler) + handler(NO); + return; + } + + NSString *localizeHeaderKey = [NSString stringWithFormat:@"Confirm.Header.%@",permissionKey]; + NSString *localizeDescKey = [NSString stringWithFormat:@"Confirm.Desc.%@",permissionKey]; + confirm(NSLocalizedString(localizeHeaderKey, nil), NSLocalizedString(localizeDescKey, nil), ^{ + if(handler) + handler(YES); + + [defaults setBool:YES forKey:key]; + [defaults synchronize]; + }, ^{ + if(handler) + handler(NO); + + [denied setValue:@(YES) forKey:key]; + + [defaults setBool:NO forKey:key]; + [defaults synchronize]; + }); + } + + } + + */ + } fileprivate let TelegramFileMediaBoxPath:String = "TelegramFileMediaBoxPathAttributeKey" func saveAs(_ file:TelegramMediaFile, account:Account) { - let name = account.postbox.mediaBox.resourceData(file.resource) |> mapToSignal { data -> Signal< (String, String), Void> in + let name = account.postbox.mediaBox.resourceData(file.resource) |> mapToSignal { data -> Signal< (String, String), NoError> in if data.complete { var ext:String = "" let fileName = file.fileName ?? data.path.nsstring.lastPathComponent + if let ext = file.fileName?.nsstring.pathExtension { + return .single((data.path, ext)) + } ext = fileName.nsstring.pathExtension - return resourceType(mimeType: file.mimeType) |> mapToSignal { _type -> Signal<(String, String), Void> in + return resourceType(mimeType: file.mimeType) |> mapToSignal { _type -> Signal<(String, String), NoError> in let ext = _type == "*" || _type == nil ? (ext.length == 0 ? "file" : ext) : _type! return .single((data.path, ext)) @@ -164,90 +480,194 @@ func saveAs(_ file:TelegramMediaFile, account:Account) { } |> deliverOnMainQueue _ = name.start(next: { path, ext in - savePanel(file: path, ext: ext, for: mainWindow) + savePanel(file: path, ext: ext, for: mainWindow, defaultName: file.fileName) }) } -func copyToDownloads(_ file: TelegramMediaFile, account:Account) -> Signal { - return downloadFilePath(file, account) |> deliverOn(resourcesQueue) |> map { (boxPath, adopted) in - var adopted = adopted - var i:Int = 1 - let deletedPathExt = adopted.nsstring.deletingPathExtension - while FileManager.default.fileExists(atPath: adopted, isDirectory: nil) { - let ext = adopted.nsstring.pathExtension - let box = FileManager.xattrStringValue(forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) - if box == boxPath { - return +func copyToDownloads(_ file: TelegramMediaFile, postbox: Postbox, saveAnyway: Bool = false) -> Signal { + let path = downloadFilePath(file, postbox) + return combineLatest(queue: resourcesQueue, path, downloadedFilePaths(postbox)) |> map { (expanded, paths) in + guard let (boxPath, adopted) = expanded else { + return nil + } + if let id = file.id { + if let path = paths.path(for: id), !saveAnyway { + let lastModified = Int32(FileManager.default.modificationDateForFileAtPath(path: path.downloadedPath)?.timeIntervalSince1970 ?? 0) + if fileSize(path.downloadedPath) == Int(path.size), lastModified == path.lastModified { + return path.downloadedPath + } + + } + + var adopted = adopted + var i:Int = 1 + let deletedPathExt = adopted.nsstring.deletingPathExtension + while FileManager.default.fileExists(atPath: adopted, isDirectory: nil) { + let ext = adopted.nsstring.pathExtension + adopted = "\(deletedPathExt) (\(i)).\(ext)" + i += 1 } - adopted = "\(deletedPathExt) (\(i)).\(ext)" - i += 1 + try? FileManager.default.copyItem(atPath: boxPath, toPath: adopted) + +// let quarantineData = "doesn't matter".data(using: .utf8)! +// +// +// URL(fileURLWithPath: adopted).withUnsafeFileSystemRepresentation { fileSystemPath in +// _ = quarantineData.withUnsafeBytes { +// setxattr(fileSystemPath, "com.apple.quarantine", $0.baseAddress, quarantineData.count, 0, 0) +// } +// } + + let lastModified = FileManager.default.modificationDateForFileAtPath(path: adopted)?.timeIntervalSince1970 ?? FileManager.default.creationDateForFileAtPath(path: adopted)?.timeIntervalSince1970 ?? Date().timeIntervalSince1970 + + let fs = fileSize(boxPath) + let path = DownloadedPath(id: id, downloadedPath: adopted, size: fs != nil ? Int32(fs!) : nil ?? Int32(file.size ?? 0), lastModified: Int32(lastModified)) + + _ = updateDownloadedFilePaths(postbox, { + $0.withAddedPath(path) + }).start() + + return adopted + } else { + return adopted } - - try? FileManager.default.copyItem(atPath: boxPath, toPath: adopted) - FileManager.setXAttrStringValue(boxPath, forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) } +// return downloadFilePath(file, postbox) |> deliverOn(resourcesQueue) |> map { (boxPath, adopted) in +// var adopted = adopted +// var i:Int = 1 +// let deletedPathExt = adopted.nsstring.deletingPathExtension +// while FileManager.default.fileExists(atPath: adopted, isDirectory: nil) { +// let ext = adopted.nsstring.pathExtension +// let box = FileManager.xattrStringValue(forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) +// if box == boxPath { +// return +// } +// +// adopted = "\(deletedPathExt) (\(i)).\(ext)" +// i += 1 +// } +// +// try? FileManager.default.copyItem(atPath: boxPath, toPath: adopted) +// FileManager.setXAttrStringValue(boxPath, forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) +// } +// } -private func downloadFilePath(_ file: TelegramMediaFile, _ account:Account) -> Signal<(String, String), Void> { - return account.postbox.mediaBox.resourceData(file.resource) |> mapToSignal { data -> Signal< (String, String), Void> in +func downloadFilePath(_ file: TelegramMediaFile, _ postbox: Postbox) -> Signal<(String, String)?, NoError> { + return combineLatest(postbox.mediaBox.resourceData(file.resource) |> take(1), automaticDownloadSettings(postbox: postbox) |> take(1)) |> mapToSignal { data, settings -> Signal< (String, String)?, NoError> in if data.complete { var ext:String = "" let fileName = file.fileName ?? data.path.nsstring.lastPathComponent ext = fileName.nsstring.pathExtension if !ext.isEmpty { - if let folder = FastSettings.downloadsFolder { - return .single((data.path, "\(folder)/\(fileName.nsstring.deletingPathExtension).\(ext)")) - } - return .complete() + return .single((data.path, "\(settings.downloadFolder)/\(fileName.nsstring.deletingPathExtension).\(ext)")) } else { - return resourceType(mimeType: file.mimeType) |> mapToSignal { (ext) -> Signal<(String, String), Void> in + return resourceType(mimeType: file.mimeType) |> mapToSignal { (ext) -> Signal<(String, String)?, NoError> in if let folder = FastSettings.downloadsFolder { let ext = ext == "*" || ext == nil ? "file" : ext! return .single((data.path, "\(folder)/\(fileName).\( ext )")) } - return .complete() + return .single(nil) } } } else { - return .complete() + return .single(nil) + } + } +} + +func fileFinderPath(_ file: TelegramMediaFile, _ postbox: Postbox) -> Signal { + return combineLatest(downloadFilePath(file, postbox), downloadedFilePaths(postbox)) |> map { (expanded, paths) in + guard let (boxPath, adopted) = expanded else { + return nil + } + if let id = file.id { + do { + + if let path = paths.path(for: id) { + let lastModified = Int32(FileManager.default.modificationDateForFileAtPath(path: path.downloadedPath)?.timeIntervalSince1970 ?? 0) + if fileSize(path.downloadedPath) == Int(path.size), lastModified == path.lastModified { + return path.downloadedPath + } + } + + return adopted + } + } else { + return nil } } } func showInFinder(_ file:TelegramMediaFile, account:Account) { - let path = downloadFilePath(file, account) |> deliverOnMainQueue + let path = downloadFilePath(file, account.postbox) |> deliverOnMainQueue - _ = path.start(next: { (boxPath, adopted) in - do { - var adopted = adopted - - var i:Int = 1 - let deletedPathExt = adopted.nsstring.deletingPathExtension - while FileManager.default.fileExists(atPath: adopted, isDirectory: nil) { - let ext = adopted.nsstring.pathExtension - let box = FileManager.xattrStringValue(forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) - if box == boxPath { - NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: adopted)]) - return + _ = combineLatest(path, downloadedFilePaths(account.postbox)).start(next: { (expanded, paths) in + + guard let (boxPath, adopted) = expanded else { + return + } + if let id = file.id { + do { + + if let path = paths.path(for: id) { + let lastModified = Int32(FileManager.default.modificationDateForFileAtPath(path: path.downloadedPath)?.timeIntervalSince1970 ?? 0) + if fileSize(path.downloadedPath) == Int(path.size), lastModified == path.lastModified { + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: path.downloadedPath)]) + return + } + } - adopted = "\(deletedPathExt) (\(i)).\(ext)" - i += 1 + var adopted = adopted + var i:Int = 1 + let deletedPathExt = adopted.nsstring.deletingPathExtension + while FileManager.default.fileExists(atPath: adopted, isDirectory: nil) { + let ext = adopted.nsstring.pathExtension + adopted = "\(deletedPathExt) (\(i)).\(ext)" + i += 1 + } + + try? FileManager.default.copyItem(atPath: boxPath, toPath: adopted) + +// let quarantineData = "doesn't matter".data(using: .utf8)! +// +// URL(fileURLWithPath: adopted).withUnsafeFileSystemRepresentation { fileSystemPath in +// _ = quarantineData.withUnsafeBytes { +// setxattr(fileSystemPath, "com.apple.quarantine", $0.baseAddress, quarantineData.count, 0, 0) +// } +// } + + //setxattr(<#T##path: UnsafePointer!##UnsafePointer!#>, <#T##name: UnsafePointer!##UnsafePointer!#>, <#T##value: UnsafeRawPointer!##UnsafeRawPointer!#>, <#T##size: Int##Int#>, <#T##position: UInt32##UInt32#>, <#T##options: Int32##Int32#>) + + // setxattr(ordinaryFileURL.path, SUAppleQuarantineIdentifier, quarantineData, quarantineDataLength, 0, XATTR_CREATE) + + let lastModified = FileManager.default.modificationDateForFileAtPath(path: adopted)?.timeIntervalSince1970 ?? FileManager.default.creationDateForFileAtPath(path: adopted)?.timeIntervalSince1970 ?? Date().timeIntervalSince1970 + + let fs = fileSize(boxPath) + let path = DownloadedPath(id: id, downloadedPath: adopted, size: fs != nil ? Int32(fs!) : nil ?? Int32(file.size ?? 0), lastModified: Int32(lastModified)) + + + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: adopted)]) + _ = updateDownloadedFilePaths(account.postbox, { + $0.withAddedPath(path) + }).start() + } - - try? FileManager.default.copyItem(atPath: boxPath, toPath: adopted) - FileManager.setXAttrStringValue(boxPath, forKey: TelegramFileMediaBoxPath, at: URL(fileURLWithPath: adopted)) - NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: adopted)]) } }) } -func putFileToTemp(from:String, named:String) -> Signal { + + + +func putFileToTemp(from:String, named:String) -> Signal { return Signal { subscriber in let new = NSTemporaryDirectory() + named + try? FileManager.default.removeItem(atPath: new) try? FileManager.default.copyItem(atPath: from, toPath: new) subscriber.putNext(new) @@ -255,3 +675,5 @@ func putFileToTemp(from:String, named:String) -> Signal { return EmptyDisposable } } + + diff --git a/Telegram-Mac/FeaturedStickerPacksController.swift b/Telegram-Mac/FeaturedStickerPacksController.swift index 9593ccb0b4..22720be713 100644 --- a/Telegram-Mac/FeaturedStickerPacksController.swift +++ b/Telegram-Mac/FeaturedStickerPacksController.swift @@ -8,18 +8,19 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore private final class FeaturedStickerPacksControllerArguments { - let account: Account + let context: AccountContext let openStickerPack: (StickerPackCollectionInfo) -> Void let addPack: (StickerPackCollectionInfo) -> Void - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) { - self.account = account + init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) { + self.context = context self.openStickerPack = openStickerPack self.addPack = addPack } @@ -40,81 +41,25 @@ private enum FeaturedStickerPacksEntryId: Hashable { return id.hashValue } } - - static func ==(lhs: FeaturedStickerPacksEntryId, rhs: FeaturedStickerPacksEntryId) -> Bool { - switch lhs { - case let .pack(id): - if case .pack(id) = rhs { - return true - } else { - return false - } - case let .section(id): - if case .section(id) = rhs { - return true - } else { - return false - } - } - } } private enum FeaturedStickerPacksEntry: TableItemListNodeEntry { case section(sectionId:Int32) - case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, Bool, StickerPackItem?, Int32, Bool) + case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, Bool, StickerPackItem?, Int32, Bool, GeneralViewType) var stableId: FeaturedStickerPacksEntryId { switch self { - case let .pack(_, _, info, _, _, _, _): + case let .pack(_, _, info, _, _, _, _, _): return .pack(info.id) case let .section(id): return .section(id) } } - static func ==(lhs: FeaturedStickerPacksEntry, rhs: FeaturedStickerPacksEntry) -> Bool { - switch lhs { - case let .pack(lhsSectionId, lhsIndex, lhsInfo, lhsUnread, lhsTopItem, lhsCount, lhsInstalled): - if case let .pack(rhsSectionId, rhsIndex, rhsInfo, rhsUnread, rhsTopItem, rhsCount, rhsInstalled) = rhs { - - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if lhsInfo != rhsInfo { - return false - } - if lhsUnread != rhsUnread { - return false - } - if lhsTopItem != rhsTopItem { - return false - } - if lhsCount != rhsCount { - return false - } - if lhsInstalled != rhsInstalled { - return false - } - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } - var stableIndex:Int32 { switch self { - case let .pack(sectionId: sectionId, index, _, _, _, _, _): + case let .pack(sectionId: sectionId, index, _, _, _, _, _, _): fatalError() case let .section(sectionId: sectionId): return (sectionId + 1) * 1000 - sectionId @@ -123,7 +68,7 @@ private enum FeaturedStickerPacksEntry: TableItemListNodeEntry { var index:Int32 { switch self { - case let .pack(sectionId: sectionId, index, _, _, _, _, _): + case let .pack(sectionId: sectionId, index, _, _, _, _, _, _): return (sectionId * 1000) + 100 + index case let .section(sectionId: sectionId): return (sectionId + 1) * 1000 - sectionId @@ -136,25 +81,16 @@ private enum FeaturedStickerPacksEntry: TableItemListNodeEntry { func item(_ arguments: FeaturedStickerPacksControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .pack(_, _, info, unread, topItem, count, installed): - return StickerSetTableRowItem(initialSize, account: arguments.account, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: .installation(installed: installed), action: { + case let .pack(_, _, info, unread, topItem, count, installed, viewType): + return StickerSetTableRowItem(initialSize, context: arguments.context, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: .installation(installed: installed), viewType: viewType, action: { arguments.openStickerPack(info) }, addPack: { arguments.addPack(info) }, removePack: { }) - - /* - return ItemListStickerPackItem(account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false), enabled: true, sectionId: self.section, action: { _ in - arguments.openStickerPack(info) - }, addPack: { - arguments.addPack(info) - }, removePack: { _ in - }) - */ case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } @@ -187,11 +123,13 @@ private func featuredStickerPacksControllerEntries(state: FeaturedStickerPacksCo if let value = unreadPacks[item.info.id] { unread = value } - entries.append(.pack(sectionId: sectionId, index, item.info, unread, item.topItems.first, item.info.count, installedPacks.contains(item.info.id))) + entries.append(.pack(sectionId: sectionId, index, item.info, unread, item.topItems.first, item.info.count, installedPacks.contains(item.info.id), bestGeneralViewType(featured, for: item))) index += 1 } } } + entries.append(.section(sectionId: sectionId)) + sectionId += 1 return entries } @@ -205,7 +143,12 @@ private func prepareTransition(left:[AppearanceWrapperEntry() - stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) let featured = Promise<[FeaturedStickerPackItem]>() - featured.set(account.viewTracker.featuredStickerPacks()) + featured.set(context.account.viewTracker.featuredStickerPacks()) var initialUnreadPacks: [ItemCollectionId: Bool] = [:] let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = self.atomicSize - genericView.merge(with: combineLatest(statePromise.get(), stickerPacks.get(), featured.get(), appearanceSignal) |> deliverOnMainQueue + + let signal = combineLatest(queue: prepareQueue,statePromise.get(), stickerPacks.get(), featured.get(), appearanceSignal) |> map { state, view, featured, appearance -> TableUpdateTransition in - for item in featured { if initialUnreadPacks[item.info.id] == nil { initialUnreadPacks[item.info.id] = item.unread @@ -250,32 +193,22 @@ class FeaturedStickerPacksController: TableViewController { let entries = featuredStickerPacksControllerEntries(state: state, view: view, featured: featured, unreadPacks: initialUnreadPacks).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) - } |> afterDisposed { + } |> afterDisposed { actionsDisposable.dispose() - } ) + } |> deliverOnMainQueue + + self.disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) + var alreadyReadIds = Set() genericView.addScroll(listener: TableScrollListener ({ scroll in - /* - var unreadIds: [ItemCollectionId] = [] - for entry in entries { - switch entry { - case let .pack(_, info, unread, _, _, _): - if unread && !alreadyReadIds.contains(info.id) { - unreadIds.append(info.id) - } - } - } - if !unreadIds.isEmpty { - alreadyReadIds.formUnion(Set(unreadIds)) - - let _ = markFeaturedStickerPacksAsSeenInteractively(postbox: account.postbox, ids: unreadIds).start() - } - */ + })) - readyOnce() } } diff --git a/Telegram-Mac/FetchCachedRepresentations.swift b/Telegram-Mac/FetchCachedRepresentations.swift index 88476072a7..c0c203c404 100644 --- a/Telegram-Mac/FetchCachedRepresentations.swift +++ b/Telegram-Mac/FetchCachedRepresentations.swift @@ -7,136 +7,612 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit +import RLottie -public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedMediaResourceRepresentation) -> Signal { +private let cacheThreadPool = ThreadPool(threadCount: 1, threadPriority: 0.1) + + +public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { if let representation = representation as? CachedStickerAJpegRepresentation { - return fetchCachedStickerAJpegRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) + return fetchCachedStickerAJpegRepresentation(account: account, resource: resource, representation: representation) + } else if let representation = representation as? CachedAnimatedStickerRepresentation { + return fetchCachedAnimatedStickerRepresentation(account: account, resource: resource, representation: representation) } else if let representation = representation as? CachedScaledImageRepresentation { - return fetchCachedScaledImageRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) - } else if let representation = representation as? CachedVideoFirstFrameRepresentation { - return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) + return fetchCachedScaledImageRepresentation(account: account, resource: resource, representation: representation) + } else if representation is CachedVideoFirstFrameRepresentation { + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> mapToSignal { data -> Signal in + if data.complete { + return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data) + |> `catch` { _ -> Signal in + return .complete() + } + } else if let size = resource.size { + return videoFirstFrameData(account: account, resource: resource, chunkSize: min(size, 192 * 1024)) + } else { + return .complete() + } + } + } else if let representation = representation as? CachedScaledVideoFirstFrameRepresentation { + return fetchCachedScaledVideoFirstFrameRepresentation(account: account, resource: resource, representation: representation) + } else if let representation = representation as? CachedDiceRepresentation { + if let diceCache = account.diceCache { + return diceCache.interactiveSymbolData(baseSymbol: representation.emoji, side: representation.value, synchronous: false) |> filter { $0.0 != nil } |> mapToSignal { data in + return fetchCachedDiceRepresentation(account: account, data: data.0!, representation: representation) + } + } else { + return .complete() + } + } else if let representation = representation as? CachedBlurredWallpaperRepresentation { + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> mapToSignal { data -> Signal in + if !data.complete { + return .complete() + } + return fetchCachedBlurredWallpaperRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + } + } else if let representation = representation as? CachedPatternWallpaperMaskRepresentation { + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> mapToSignal { data -> Signal in + if !data.complete { + return .complete() + } + return fetchCachedPatternWallpaperMaskRepresentation(resource: resource, resourceData: data, representation: representation) + } } + + + return .never() } -private func accountRecordIdPathName(_ id: AccountRecordId) -> String { - return "account-\(UInt64(bitPattern: id.int64))" + +public func fetchCachedSharedResourceRepresentation(accountManager: AccountManager, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { + fatalError() } -private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal { + +private func fetchCachedPatternWallpaperMaskRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedPatternWallpaperMaskRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { - let image = convertFromWebP(data)?.takeRetainedValue() - let appGroupName = "6N38VWS5BX.ru.keepcoder.Telegram" - if let image = image, let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) { - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - // + + var svgPath: String? + + let path = NSTemporaryDirectory() + "\(arc4random64())" + let url = URL(fileURLWithPath: path) + + if let data = TGGUnzipData(data, 8 * 1024 * 1024), data.count > 5, let string = String(data: data.subdata(in: 0 ..< 5), encoding: .utf8), string == " runOn(Queue.concurrentDefaultQueue()) +} + + +private func accountRecordIdPathName(_ id: AccountRecordId) -> String { + return "account-\(UInt64(bitPattern: id.int64))" +} + + +public enum FetchVideoFirstFrameError { + case generic +} + + +private func videoFirstFrameData(account: Account, resource: MediaResource, chunkSize: Int) -> Signal { + if let size = resource.size { + return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, chunkSize)) + |> 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) + |> `catch` { _ -> Signal in + if chunkSize > size { + return .complete() + } else { + return videoFirstFrameData(account: account, resource: resource, chunkSize: chunkSize + chunkSize) + } + } + } + } + } else { + return .complete() + } +} + + + +private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData) -> Signal { + return Signal { subscriber in + let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" + do { + let _ = try? FileManager.default.removeItem(atPath: tempFilePath) + try FileManager.default.linkItem(atPath: resourceData.path, toPath: tempFilePath) + + let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) + imageGenerator.appliesPreferredTrackTransform = true + + let fullSizeImage = try imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) + + + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + let url = URL(fileURLWithPath: path) + + if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + + let colorQuality: Float = 0.6 - let size:CGSize - if let s = representation.size { - size = s + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, fullSizeImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + subscriber.putNext(.temporaryPath(path)) + subscriber.putCompletion() + } + } + } catch { + let _ = try? FileManager.default.removeItem(atPath: tempFilePath) + subscriber.putError(.generic) + subscriber.putCompletion() + } + return EmptyDisposable + } |> runOn(cacheThreadPool) +} + + +private func fetchCachedAnimatedStickerRepresentation(account: Account, resource: MediaResource, representation: CachedAnimatedStickerRepresentation) -> Signal { + + let data: Signal + if let resource = resource as? LocalBundleResource { + data = Signal { subscriber in + if let path = Bundle.main.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true)) + subscriber.putCompletion() + } + return EmptyDisposable + } + } else { + data = account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + } + + return data |> deliverOn(lottieThreadPool) |> map { resourceData -> (CGImage?, Data?, MediaResourceData) in + if resourceData.complete { + if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + if !representation.thumb { + var dataValue: Data! = TGGUnzipData(data, 8 * 1024 * 1024) + if dataValue == nil { + dataValue = data + } + if let json = String(data: transformedWithFitzModifier(data: dataValue, fitzModifier: representation.fitzModifier), encoding: .utf8), json.length > 0 { + let rlottie = RLottieBridge(json: json, key: resourceData.path) + + let unmanaged = rlottie?.renderFrame(0, width: Int(representation.size.width * 2), height: Int(representation.size.height * 2)) + let colorImage = unmanaged?.takeRetainedValue() + return (colorImage, nil, resourceData) + } } else { - size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale) + return (nil, data, resourceData) } + } + } + return (nil, nil, resourceData) + } |> runOn(cacheThreadPool) |> mapToSignal { frame, data, resourceData in + if resourceData.complete { + if !representation.thumb { + let path = NSTemporaryDirectory() + "\(arc4random64())" + let url = URL(fileURLWithPath: path) - let colorImage: CGImage - if let _ = representation.size { - colorImage = generateImage(size, contextGenerator: { size, context in - context.setBlendMode(.copy) - context.draw(image, in: CGRect(origin: CGPoint(), size: size)) - })! + let colorData = NSMutableData() + if let colorImage = frame, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypePNG, 1, nil){ + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + + let colorQuality: Float + colorQuality = 0.4 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + try? colorData.write(to: url, options: .atomic) + return .single(.temporaryPath(path)) + } } else { - colorImage = image + return .complete() } + } else if let data = data { - let alphaImage = generateImage(size, contextGenerator: { size, context in - context.setFillColor(NSColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - context.clip(to: CGRect(origin: CGPoint(), size: size), mask: colorImage) - context.setFillColor(NSColor.black.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - }) + let path = NSTemporaryDirectory() + "\(arc4random64())" + let url = URL(fileURLWithPath: path) + + let colorData = NSMutableData() + let umnanaged = convertFromWebP(data) + var image = umnanaged?.takeUnretainedValue() ?? NSImage(data: data)?.cgImage(forProposedRect: nil, context: nil, hints: nil) + umnanaged?.release() - if let alphaImage = alphaImage, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { + if image == nil, let data = TGGUnzipData(data, 8 * 1024 * 1024) { + if let json = String(data: transformedWithFitzModifier(data: data, fitzModifier: representation.fitzModifier), encoding: .utf8), json.length > 0 { + let rlottie = RLottieBridge(json: json, key: resourceData.path) + let unmanaged = rlottie?.renderFrame(0, width: Int(representation.size.width * 2), height: Int(representation.size.height * 2)) + image = unmanaged?.takeRetainedValue() + } + } + + if let image = image, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypePNG, 1, nil) { CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) - CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) let colorQuality: Float - let alphaQuality: Float - if representation.size == nil { - colorQuality = 0.6 - alphaQuality = 0.6 - } else { - colorQuality = 0.5 - alphaQuality = 0.4 - } + colorQuality = 0.4 let options = NSMutableDictionary() options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + try? colorData.write(to: url, options: .atomic) + return .single(.temporaryPath(path)) + } + } else { + return .complete() + } + } + } + return .never() + } +} + +private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, representation: CachedStickerAJpegRepresentation) -> Signal { + return account.postbox.mediaBox.resourceData(resource) |> mapToSignal { resourceData in + return Signal { subscriber in + if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + let unmanaged = convertFromWebP(data) + let image = unmanaged?.takeUnretainedValue() + unmanaged?.release() + let appGroupName = ApiEnvironment.group + if let image = image, let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let directory = "\(containerUrl.path)/\(accountRecordIdPathName(account.id))/cached/" + try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: true, attributes: nil) + let path: String = directory + "\(randomId)" + let url = URL(fileURLWithPath: path) - let optionsAlpha = NSMutableDictionary() - optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + let colorData = NSMutableData() - CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) - CGImageDestinationAddImage(alphaDestination, alphaImage, optionsAlpha as CFDictionary) - if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) { - let finalData = NSMutableData() - var colorSize: Int32 = Int32(colorData.length) - finalData.append(&colorSize, length: 4) - finalData.append(colorData as Data) - var alphaSize: Int32 = Int32(alphaData.length) - finalData.append(&alphaSize, length: 4) - finalData.append(alphaData as Data) - - let _ = try? finalData.write(to: url, options: [.atomic]) - - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + + let size:CGSize + if let s = representation.size { + size = s + } else { + size = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale) + } + + let colorImage: CGImage + if let _ = representation.size { + colorImage = generateImage(size, contextGenerator: { size, context in + context.setBlendMode(.copy) + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! + } else { + colorImage = image + } + if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypePNG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + + let colorQuality: Float + if representation.size == nil { + colorQuality = 0.6 + } else { + colorQuality = 0.3 + } + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let _ = try? colorData.write(to: url, options: [.atomic]) + subscriber.putNext(.temporaryPath(path)) + subscriber.putCompletion() + } + } else { subscriber.putCompletion() } - } else { - subscriber.putCompletion() } - + } + return EmptyDisposable + } |> runOn(cacheThreadPool) + } +} + +private func fetchCachedScaledImageRepresentation(account: Account, resource: MediaResource, representation: CachedScaledImageRepresentation) -> Signal { + return account.postbox.mediaBox.resourceData(resource) |> mapToSignal { resourceData in + return Signal { subscriber in + if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + if let image = NSImage(data: data) { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + let url = URL(fileURLWithPath: path) + + let size = representation.size + + let colorImage = generateImage(size, contextGenerator: { size, context in + context.setBlendMode(.copy) + if let image = image.cgImage(forProposedRect: nil, context: nil, hints: nil) { + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + } + })! + + if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, nil) + + let colorQuality: Float = 0.5 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + subscriber.putNext(.temporaryPath(path)) + subscriber.putCompletion() + } + } + } + } + return EmptyDisposable + } |> runOn(cacheThreadPool) + } +} + +private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource: MediaResource, representation: CachedVideoFirstFrameRepresentation) -> Signal { + return account.postbox.mediaBox.resourceRangesStatus(resource) |> mapToSignal { _ in + return account.postbox.mediaBox.resourceData(resource) |> take(1) + } |> runOn(cacheThreadPool) |> mapToSignal { resourceData in + let tempFilePath = NSTemporaryDirectory() + "\(resourceData.path.nsstring.lastPathComponent).mp4" + let _ = try? FileManager.default.removeItem(atPath: tempFilePath) + try? FileManager.default.linkItem(atPath: resourceData.path, toPath: tempFilePath) + + let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) + imageGenerator.appliesPreferredTrackTransform = true + let fullSizeImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) + + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + let url = URL(fileURLWithPath: path) + + if let fullSizeImage = fullSizeImage, let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, nil) + + let colorQuality: Float = 0.6 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, fullSizeImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + return .single(.temporaryPath(path)) } } - return EmptyDisposable - }) |> runOn(account.graphicsThreadPool) + return .never() + } } -private func fetchCachedScaledImageRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedScaledImageRepresentation) -> Signal { + +private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, resource: MediaResource, representation: CachedScaledVideoFirstFrameRepresentation) -> Signal { + return account.postbox.mediaBox.resourceData(resource) |> mapToSignal { resourceData in + return account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), complete: true) |> mapToSignal { firstFrame -> Signal in + return Signal({ subscriber in + if let data = try? Data(contentsOf: URL(fileURLWithPath: firstFrame.path), options: [.mappedIfSafe]) { + if let image = NSImage(data: data)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + let url = URL(fileURLWithPath: path) + + let size = representation.size + + let colorImage = generateImage(size, contextGenerator: { size, context in + context.setBlendMode(.copy) + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + })! + + if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, nil) + + let colorQuality: Float = 0.5 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + subscriber.putNext(.temporaryPath(path)) + subscriber.putCompletion() + } + } + } + } + return EmptyDisposable + }) |> runOn(cacheThreadPool) + } + } + +} + + + +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 = NSImage(data: data) { + if let image = NSImage(data: data)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { var randomId: Int64 = 0 arc4random_buf(&randomId, 8) let path = NSTemporaryDirectory() + "\(randomId)" let url = URL(fileURLWithPath: path) - let size = representation.size - - let colorImage = generateImage(size, contextGenerator: { size, context in - context.setBlendMode(.copy) - context.draw(image.precomposed(), in: CGRect(origin: CGPoint(), size: size)) - })! - - if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) + if let colorImage = blurredImage(image, radius: 70), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 @@ -145,57 +621,81 @@ private func fetchCachedScaledImageRepresentation(account: Account, resource: Me CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } } } return EmptyDisposable - }) |> runOn(account.graphicsThreadPool) + }) |> runOn(cacheThreadPool) } -private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedVideoFirstFrameRepresentation) -> Signal { + +private func fetchCachedDiceRepresentation(account: Account, data: Data, representation: CachedDiceRepresentation) -> Signal { return Signal { subscriber in - if resourceData.complete { - let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" - - do { - let _ = try? FileManager.default.removeItem(atPath: tempFilePath) - try FileManager.default.linkItem(atPath: resourceData.path, toPath: tempFilePath) - - let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) - let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) - imageGenerator.appliesPreferredTrackTransform = true - let fullSizeImage = try imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) + + var dataValue: Data! = TGGUnzipData(data, 8 * 1024 * 1024) + if dataValue == nil { + dataValue = data + } + if let json = String(data: dataValue, encoding: .utf8) { + let rlottie = RLottieBridge(json: json, key: representation.emoji + representation.value) + if let rlottie = rlottie { + let unmanaged = rlottie.renderFrame(representation.value == diceIdle ? 0 : rlottie.endFrame() - 1, width: Int(representation.size.width * 2), height: Int(representation.size.height * 2)) + let colorImage = unmanaged.takeRetainedValue() - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let path = NSTemporaryDirectory() + "\(randomId)" + let path = NSTemporaryDirectory() + "\(arc4random64())" let url = URL(fileURLWithPath: path) - if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) - - let colorQuality: Float = 0.6 - + let colorData = NSMutableData() + if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypePNG, 1, nil){ + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + let colorQuality: Float + colorQuality = 0.4 let options = NSMutableDictionary() options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) - - CGImageDestinationAddImage(colorDestination, fullSizeImage, options as CFDictionary) - if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + CGImageDestinationAddImage(colorDestination, colorImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + try? colorData.write(to: url, options: .atomic) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } + } else { + subscriber.putCompletion() } - - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + } else { subscriber.putCompletion() - } catch (let e) { - print("\(e)") } + } - return EmptyDisposable - } |> runOn(account.graphicsThreadPool) + + return ActionDisposable { + + } + } |> runOn(lottieThreadPool) +} + +func getAnimatedStickerThumb(data: Data, size: NSSize = NSMakeSize(512, 512)) -> Signal { + + return .single(data) |> deliverOn(lottieThreadPool) |> map { data -> String? in + var dataValue: Data! = TGGUnzipData(data, 8 * 1024 * 1024) + if dataValue == nil { + dataValue = data + } + if let json = String(data: transformedWithFitzModifier(data: dataValue, fitzModifier: nil), encoding: .utf8), json.length > 0 { + let rlottie = RLottieBridge(json: json, key: "\(arc4random())") + let unmanaged = rlottie?.renderFrame(0, width: Int(size.width), height: Int(size.height)) + let colorImage = unmanaged?.takeRetainedValue() + + if let image = colorImage { + let rep = NSBitmapImageRep(cgImage: image) + let data = rep.representation(using: .png, properties: [:]) + let path = NSTemporaryDirectory() + "temp_as_\(arc4random64()).png" + try? data?.write(to: URL(fileURLWithPath: path)) + return path + } + } + return nil + } |> deliverOnMainQueue } diff --git a/Telegram-Mac/FetchManager.swift b/Telegram-Mac/FetchManager.swift new file mode 100644 index 0000000000..1672f22099 --- /dev/null +++ b/Telegram-Mac/FetchManager.swift @@ -0,0 +1,487 @@ +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +private struct FetchManagerLocationEntryId: Hashable { + let location: FetchManagerLocation + let resourceId: MediaResourceId + let locationKey: FetchManagerLocationKey + + static func ==(lhs: FetchManagerLocationEntryId, rhs: FetchManagerLocationEntryId) -> Bool { + if lhs.location != rhs.location { + return false + } + if !lhs.resourceId.isEqual(to: rhs.resourceId) { + return false + } + if lhs.locationKey != rhs.locationKey { + return false + } + return true + } + + var hashValue: Int { + return self.resourceId.hashValue &* 31 &+ self.locationKey.hashValue + } +} + +private final class FetchManagerLocationEntry { + let id: FetchManagerLocationEntryId + let episode: Int32 + let reference: MediaResourceReference + let fetchTag: MediaResourceStatsCategory + + var referenceCount: Int32 = 0 + var elevatedPriorityReferenceCount: Int32 = 0 + var userInitiatedPriorityIndices: [Int32] = [] + let downloadRange: Range? + var priorityKey: FetchManagerPriorityKey? { + if self.referenceCount > 0 { + return FetchManagerPriorityKey(locationKey: self.id.locationKey, hasElevatedPriority: self.elevatedPriorityReferenceCount > 0, userInitiatedPriority: userInitiatedPriorityIndices.last) + } else { + return nil + } + } + + init(id: FetchManagerLocationEntryId, episode: Int32, reference: MediaResourceReference, fetchTag: MediaResourceStatsCategory?, downloadRange: Range? = nil) { + self.id = id + self.episode = episode + self.reference = reference + self.fetchTag = fetchTag ?? .generic + self.downloadRange = downloadRange + } +} + +private final class FetchManagerActiveContext { + var disposable: Disposable? +} + +private final class FetchManagerStatusContext { + var disposable: Disposable? + var originalStatus: MediaResourceStatus? + var subscribers = Bag<(MediaResourceStatus) -> Void>() + + var hasEntry = false + + var isEmpty: Bool { + return !self.hasEntry && self.subscribers.isEmpty + } + + var combinedStatus: MediaResourceStatus? { + if let originalStatus = self.originalStatus { + if originalStatus == .Remote && self.hasEntry { + return .Fetching(isActive: false, progress: 0.0) + } else { + return originalStatus + } + } else { + return nil + } + } +} + +private final class FetchManagerCategoryContext { + private let postbox: Postbox + private let entryCompleted: (FetchManagerLocationEntryId, FetchResourceSourceType) -> Void + + private var topEntryIdAndPriority: (FetchManagerLocationEntryId, FetchManagerPriorityKey)? + private var entries: [FetchManagerLocationEntryId: FetchManagerLocationEntry] = [:] + + private var activeContexts: [FetchManagerLocationEntryId: FetchManagerActiveContext] = [:] + private var statusContexts: [FetchManagerLocationEntryId: FetchManagerStatusContext] = [:] + + init(postbox: Postbox, entryCompleted: @escaping (FetchManagerLocationEntryId, FetchResourceSourceType) -> Void) { + self.postbox = postbox + self.entryCompleted = entryCompleted + } + + func withEntry(id: FetchManagerLocationEntryId, downloadRange: Range? = nil, takeNew: (() -> (MediaResourceReference, MediaResourceStatsCategory?, Int32))?, _ f: (FetchManagerLocationEntry) -> Void) { + let entry: FetchManagerLocationEntry + let previousPriorityKey: FetchManagerPriorityKey? + + if let current = self.entries[id] { + entry = current + previousPriorityKey = entry.priorityKey + } else if let takeNew = takeNew { + previousPriorityKey = nil + let (reference, fetchTag, episode) = takeNew() + entry = FetchManagerLocationEntry(id: id, episode: episode, reference: reference, fetchTag: fetchTag, downloadRange: downloadRange) + self.entries[id] = entry + } else { + return + } + + f(entry) + + var removedEntries = false + + let updatedPriorityKey = entry.priorityKey + if previousPriorityKey != updatedPriorityKey { + if let updatedPriorityKey = updatedPriorityKey { + if let (topId, topPriority) = self.topEntryIdAndPriority { + if updatedPriorityKey < topPriority { + self.topEntryIdAndPriority = (entry.id, updatedPriorityKey) + } else if updatedPriorityKey > topPriority && topId == id { + self.topEntryIdAndPriority = nil + } + } else { + self.topEntryIdAndPriority = (entry.id, updatedPriorityKey) + } + } else { + if self.topEntryIdAndPriority?.0 == id { + self.topEntryIdAndPriority = nil + } + self.entries.removeValue(forKey: id) + removedEntries = true + } + } + + self.maybeFindAndActivateNewTopEntry() + + if removedEntries { + var removedIds: [FetchManagerLocationEntryId] = [] + for (entryId, activeContext) in self.activeContexts { + if self.entries[entryId] == nil { + removedIds.append(entryId) + activeContext.disposable?.dispose() + } + } + for entryId in removedIds { + self.activeContexts.removeValue(forKey: entryId) + } + } + + if let activeContext = self.activeContexts[id] { + if activeContext.disposable == nil { + if let entry = self.entries[id] { + let entryCompleted = self.entryCompleted + let range: (Range, MediaBoxFetchPriority)? + if let downloadRange = entry.downloadRange { + range = (downloadRange, .default) + } else { + range = nil + } + activeContext.disposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: entry.reference, range: range, statsCategory: entry.fetchTag, reportResultStatus: true).start(next: { value in + entryCompleted(id, value) + }) + } else { + assertionFailure() + } + } + } + + if (previousPriorityKey != nil) != (updatedPriorityKey != nil) { + if let statusContext = self.statusContexts[id] { + if updatedPriorityKey != nil { + if !statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = true + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } else { + if statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = false + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } + } + } + } + + func maybeFindAndActivateNewTopEntry() { + if !self.entries.isEmpty { + for (id, entry) in self.entries { + if activeContexts[id] == nil { + let activeContext = FetchManagerActiveContext() + self.activeContexts[id] = activeContext + let entryCompleted = self.entryCompleted + let range: (Range, MediaBoxFetchPriority)? + if let downloadRange = entry.downloadRange { + range = (downloadRange, .default) + } else { + range = nil + } + activeContext.disposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: entry.reference, range: range, statsCategory: entry.fetchTag, reportResultStatus: true).start(next: { value in + entryCompleted(id, value) + }) + } + } + } + + } + + func cancelEntry(_ entryId: FetchManagerLocationEntryId) { + var id: FetchManagerLocationEntryId = entryId + if self.entries[id] == nil { + for (key, _) in self.entries { + if key.resourceId.isEqual(to: entryId.resourceId) { + id = key + break + } + } + } + + if let _ = self.entries[id] { + self.entries.removeValue(forKey: id) + + if let statusContext = self.statusContexts[id] { + if statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = false + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } + } + + if let activeContext = self.activeContexts[id] { + activeContext.disposable?.dispose() + activeContext.disposable = nil + self.activeContexts.removeValue(forKey: id) + } + + if self.topEntryIdAndPriority?.0 == id { + self.topEntryIdAndPriority = nil + } + + self.maybeFindAndActivateNewTopEntry() + } + + func withFetchStatusContext(_ id: FetchManagerLocationEntryId, _ f: (FetchManagerStatusContext) -> Void) { + let statusContext: FetchManagerStatusContext + if let current = self.statusContexts[id] { + statusContext = current + } else { + statusContext = FetchManagerStatusContext() + self.statusContexts[id] = statusContext + if self.entries[id] != nil { + statusContext.hasEntry = true + } + } + + f(statusContext) + + if statusContext.isEmpty { + statusContext.disposable?.dispose() + self.statusContexts.removeValue(forKey: id) + } + } + + var isEmpty: Bool { + return self.entries.isEmpty && self.activeContexts.isEmpty && self.statusContexts.isEmpty + } +} + +final class FetchManager { + private let queue = Queue() + private let postbox: Postbox + private var nextEpisodeId: Int32 = 0 + private var nextUserInitiatedIndex: Int32 = 0 + + private var categoryContexts: [FetchManagerCategory: FetchManagerCategoryContext] = [:] + + init(postbox: Postbox) { + self.postbox = postbox + } + + private func takeNextEpisodeId() -> Int32 { + let value = self.nextEpisodeId + self.nextEpisodeId += 1 + return value + } + + private func takeNextUserInitiatedIndex() -> Int32 { + let value = self.nextUserInitiatedIndex + self.nextUserInitiatedIndex += 1 + return value + } + + private func withCategoryContext(_ key: FetchManagerCategory, _ f: (FetchManagerCategoryContext) -> Void) { + assert(self.queue.isCurrent()) + let context: FetchManagerCategoryContext + if let current = self.categoryContexts[key] { + context = current + } else { + let queue = self.queue + context = FetchManagerCategoryContext(postbox: self.postbox, entryCompleted: { [weak self] id, source in + queue.async { + if let strongSelf = self { + let postbox = strongSelf.postbox + switch source { + case .remote: + switch id.locationKey { + case let .messageId(messageId): + _ = (strongSelf.postbox.messageAtId(messageId) |> map { $0?.media.first as? TelegramMediaFile} |> filter {$0 != nil} |> map {$0!} |> mapToSignal { file -> Signal in + if !file.isMusic && !file.isAnimated && !file.isVideo && !file.isVoice && !file.isInstantVideo && !file.isAnimatedSticker && !file.isStaticSticker { + return copyToDownloads(file, postbox: postbox) |> map { _ in } + } + return .single(Void()) + }).start() + default: + break + } + default: + break + } + strongSelf.withCategoryContext(key, { context in + context.cancelEntry(id) + }) + } + } + }) + self.categoryContexts[key] = context + } + + f(context) + + if context.isEmpty { + self.categoryContexts.removeValue(forKey: key) + } + } + + func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, downloadRange: Range? = nil, reference: MediaResourceReference, fetchTag: MediaResourceStatsCategory?, elevatedPriority: Bool, userInitiated: Bool) -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + if let strongSelf = self { + var assignedEpisode: Int32? + var assignedUserInitiatedIndex: Int32? + + strongSelf.withCategoryContext(category, { context in + context.withEntry(id: FetchManagerLocationEntryId(location: location, resourceId: reference.resource.id, locationKey: locationKey), downloadRange: downloadRange, takeNew: { return (reference, fetchTag, strongSelf.takeNextEpisodeId()) }, { entry in + assignedEpisode = entry.episode + entry.referenceCount += 1 + if elevatedPriority { + entry.elevatedPriorityReferenceCount += 1 + } + if userInitiated { + let userInitiatedIndex = strongSelf.takeNextUserInitiatedIndex() + assignedUserInitiatedIndex = userInitiatedIndex + entry.userInitiatedPriorityIndices.append(userInitiatedIndex) + entry.userInitiatedPriorityIndices.sort() + } + }) + }) + + return ActionDisposable { + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(category, { context in + context.withEntry(id: FetchManagerLocationEntryId(location: location, resourceId: reference.resource.id, locationKey: locationKey), downloadRange: downloadRange, takeNew: nil, { entry in + if entry.episode == assignedEpisode { + entry.referenceCount -= 1 + assert(entry.referenceCount >= 0) + if elevatedPriority { + entry.elevatedPriorityReferenceCount -= 1 + assert(entry.elevatedPriorityReferenceCount >= 0) + } + if let userInitiatedIndex = assignedUserInitiatedIndex { + if let index = entry.userInitiatedPriorityIndices.index(of: userInitiatedIndex) { + entry.userInitiatedPriorityIndices.remove(at: index) + } else { + assertionFailure() + } + } + } + }) + }) + } + } + } + } else { + return EmptyDisposable + } + } |> runOn(self.queue) + } + + func cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, reference: MediaResourceReference) { + self.queue.async { + self.withCategoryContext(category, { context in + context.cancelEntry(FetchManagerLocationEntryId(location: location, resourceId: reference.resource.id, locationKey: locationKey)) + }) + } + } + + func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, reference: MediaResourceReference) -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + if let strongSelf = self { + var assignedIndex: Int? + + let entryId = FetchManagerLocationEntryId(location: location, resourceId: reference.resource.id, locationKey: locationKey) + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + assignedIndex = statusContext.subscribers.add({ status in + subscriber.putNext(status) + if case .Local = status { + subscriber.putCompletion() + } + }) + if let status = statusContext.combinedStatus { + subscriber.putNext(status) + if case .Local = status { + subscriber.putCompletion() + } + } + if statusContext.disposable == nil { + statusContext.disposable = strongSelf.postbox.mediaBox.resourceStatus(reference.resource).start(next: { status in + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + statusContext.originalStatus = status + + + + if let combinedStatus = statusContext.combinedStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + }) + }) + } + } + }) + } + }) + }) + + return ActionDisposable { + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + if let assignedIndex = assignedIndex { + statusContext.subscribers.remove(assignedIndex) + } + }) + }) + } + } + } + } else { + return EmptyDisposable + } + } |> runOn(self.queue) + } +} diff --git a/Telegram-Mac/FetchManagerLocation.swift b/Telegram-Mac/FetchManagerLocation.swift new file mode 100644 index 0000000000..4481192496 --- /dev/null +++ b/Telegram-Mac/FetchManagerLocation.swift @@ -0,0 +1,126 @@ + +import Foundation +import Postbox + +enum FetchManagerCategory: Int32 { + case image + case file +} + +enum FetchManagerLocationKey: Comparable, Hashable { + case messageId(MessageId) + case free + + static func ==(lhs: FetchManagerLocationKey, rhs: FetchManagerLocationKey) -> Bool { + switch lhs { + case let .messageId(id): + if case .messageId(id) = rhs { + return true + } else { + return false + } + case .free: + if case .free = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: FetchManagerLocationKey, rhs: FetchManagerLocationKey) -> Bool { + switch lhs { + case let .messageId(lhsId): + if case let .messageId(rhsId) = rhs { + return lhsId < rhsId + } else { + return true + } + case .free: + if case .free = rhs { + return false + } else { + return false + } + } + } + + var hashValue: Int { + switch self { + case let .messageId(id): + return id.hashValue + case .free: + return 1 + } + } +} + +struct FetchManagerPriorityKey: Comparable { + let locationKey: FetchManagerLocationKey + let hasElevatedPriority: Bool + let userInitiatedPriority: Int32? + + static func ==(lhs: FetchManagerPriorityKey, rhs: FetchManagerPriorityKey) -> Bool { + if lhs.locationKey != rhs.locationKey { + return false + } + if lhs.hasElevatedPriority != rhs.hasElevatedPriority { + return false + } + if lhs.userInitiatedPriority != rhs.userInitiatedPriority { + return false + } + return true + } + + static func <(lhs: FetchManagerPriorityKey, rhs: FetchManagerPriorityKey) -> Bool { + if let lhsUserInitiatedPriority = lhs.userInitiatedPriority, let rhsUserInitiatedPriority = rhs.userInitiatedPriority { + if lhsUserInitiatedPriority != rhsUserInitiatedPriority { + if lhsUserInitiatedPriority > rhsUserInitiatedPriority { + return false + } else { + return true + } + } + } else if (lhs.userInitiatedPriority != nil) != (rhs.userInitiatedPriority != nil) { + if lhs.userInitiatedPriority != nil { + return false + } else { + return true + } + } + + if lhs.hasElevatedPriority != rhs.hasElevatedPriority { + if lhs.hasElevatedPriority { + return false + } else { + return true + } + } + + return lhs.locationKey < rhs.locationKey + } +} + +enum FetchManagerLocation: Hashable { + case chat(PeerId) + + static func ==(lhs: FetchManagerLocation, rhs: FetchManagerLocation) -> Bool { + switch lhs { + case let .chat(peerId): + if case .chat(peerId) = rhs { + return true + } else { + return false + } + } + } + + var hashValue: Int { + switch self { + case let .chat(peerId): + return peerId.hashValue + } + } +} + diff --git a/Telegram-Mac/FetchMediaUtils.swift b/Telegram-Mac/FetchMediaUtils.swift new file mode 100644 index 0000000000..6d3952154d --- /dev/null +++ b/Telegram-Mac/FetchMediaUtils.swift @@ -0,0 +1,28 @@ + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +func freeMediaFileInteractiveFetched(context: AccountContext, fileReference: FileMediaReference, range: Range? = nil) -> Signal { + return fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource), range: range != nil ? (range!, .default) : nil, statsCategory: fileReference.media.isVideo ? .video : .file) |> `catch` { _ in return .complete() } +} + +func cancelFreeMediaFileInteractiveFetch(context: AccountContext, resource: MediaResource) { + context.account.postbox.mediaBox.cancelInteractiveResourceFetch(resource) +} + +func messageMediaFileInteractiveFetched(context: AccountContext, messageId: MessageId, fileReference: FileMediaReference, range: Range? = nil) -> Signal { + return context.fetchManager.interactivelyFetched(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), downloadRange: range, reference: fileReference.resourceReference(fileReference.media.resource), fetchTag: fileReference.media.isVideo ? .video : .file, elevatedPriority: false, userInitiated: true) +} + + +func messageMediaFileCancelInteractiveFetch(context: AccountContext, messageId: MessageId, fileReference: FileMediaReference) { + context.fetchManager.cancelInteractiveFetches(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), reference: fileReference.resourceReference(fileReference.media.resource)) + +} + +func messageMediaFileStatus(context: AccountContext, messageId: MessageId, fileReference: FileMediaReference) -> Signal { + return context.fetchManager.fetchStatus(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), reference: fileReference.resourceReference(fileReference.media.resource)) +} diff --git a/Telegram-Mac/FetchVideoMediaResource.swift b/Telegram-Mac/FetchVideoMediaResource.swift index 2f8ea9deac..51c8efa4ba 100644 --- a/Telegram-Mac/FetchVideoMediaResource.swift +++ b/Telegram-Mac/FetchVideoMediaResource.swift @@ -7,24 +7,31 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit -func fetchGifMediaResource(resource: LocalFileGifMediaResource) -> Signal { +func fetchGifMediaResource(resource: LocalFileGifMediaResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) + let queue: Queue = Queue() var cancelled: Bool = false let exportPath = NSTemporaryDirectory() + "\(resource.randomId).mp4" if let data = try? Data(contentsOf: URL(fileURLWithPath: resource.path)) { - resourcesQueue.async { + queue.async { TGGifConverter.convertGif(toMp4: data, exportPath: exportPath, completionHandler: { path in + let remuxedPath = NSTemporaryDirectory() + "\(arc4random()).mp4" +// let remuxed = FFMpegRemuxer.remux(path, to: remuxedPath) +// if remuxed { +// try? FileManager.default.removeItem(atPath: path) +// try? FileManager.default.moveItem(atPath: remuxedPath, toPath: path) +// } subscriber.putNext(.moveLocalFile(path: path)) subscriber.putCompletion() }, errorHandler: { - subscriber.putError(Void()) - subscriber.putCompletion() + subscriber.putError(.generic) }, cancelHandler: { () -> Bool in return cancelled }) @@ -36,3 +43,135 @@ func fetchGifMediaResource(resource: LocalFileGifMediaResource) -> Signal Signal { + return Signal { subscriber in + subscriber.putNext(.reset) + + + +// var cancelled: Bool = false +// let queue: Queue = Queue() +// +// let avAsset = AVURLAsset(url: URL(fileURLWithPath: resource.path)) + + let exportPath = NSTemporaryDirectory() + "\(resource.randomId).mp4" + + + if FileManager.default.fileExists(atPath: exportPath) { + removeFile(at: exportPath) + } + + try? FileManager.default.copyItem(atPath: resource.path, toPath: exportPath) + + subscriber.putNext(.moveLocalFile(path: exportPath)) + + subscriber.putCompletion() + +// var timer: SwiftSignalKit.Timer? +// +// let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPreset1280x720) +// if let exportSession = exportSession { +// +// exportSession.outputURL = URL(fileURLWithPath: exportPath) +// exportSession.outputFileType = .mp4 +// exportSession.canPerformMultiplePassesOverSourceMediaData = true +// exportSession.shouldOptimizeForNetworkUse = true +// let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0) +// let range = CMTimeRangeMake(start: start, duration: avAsset.duration) +// exportSession.timeRange = range +// +// exportSession.exportAsynchronously { +// if cancelled { +// subscriber.putCompletion() +// exportSession.cancelExport() +// return +// } +// switch exportSession.status { +// case .failed: +// timer?.invalidate() +// subscriber.putError(.generic) +// case .cancelled: +// timer?.invalidate() +// subscriber.putCompletion() +// case .completed: +// //let remuxedPath = NSTemporaryDirectory() + "\(arc4random()).mp4" +//// let remuxed = FFMpegRemuxer.remux(exportPath, to: remuxedPath) +//// if remuxed { +//// try? FileManager.default.removeItem(atPath: exportPath) +//// try? FileManager.default.moveItem(atPath: remuxedPath, toPath: exportPath) +//// } +// subscriber.putNext(.moveLocalFile(path: exportPath)) +// timer?.invalidate() +// subscriber.putCompletion() +// case .exporting: +// break +// default: +// break +// } +// } +// +// timer = SwiftSignalKit.Timer(timeout: 0.05, repeat: true, completion: { +// subscriber.putNext(.progressUpdated(exportSession.progress)) +// }, queue: queue) +// +// timer?.start() +// } + + + + return ActionDisposable { +// cancelled = true +// exportSession?.cancelExport() +// timer?.invalidate() + } + } +} + +func fetchArchiveMediaResource(account: Account, resource: LocalFileArchiveMediaResource) -> Signal { + return Signal { subscriber in + subscriber.putNext(.reset) + let source: ArchiveSource = .resource(resource) + let disposable = archiver.archive(source, startIfNeeded: true).start(next: { status in + switch status { + case let .done(url): + if resource.path.contains("tg_temp_archive_") { + try? FileManager.default.removeItem(atPath: resource.path) + } + subscriber.putNext(.moveLocalFile(path: url.path)) + subscriber.putCompletion() + archiver.remove(source) + case .fail: + subscriber.putError(.generic) + subscriber.putCompletion() + default: + break + } + }, error: { error in + subscriber.putError(.generic) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + disposable.dispose() + } + } +} + + +func fetchLottieSoundData(resource: LottieSoundMediaResource) -> Signal { + return Signal { subscriber in + subscriber.putNext(.reset) + let remuxedPath = NSTemporaryDirectory() + "\(arc4random()).mp4" + try? resource.data.write(to: URL(fileURLWithPath: remuxedPath)) + subscriber.putNext(.moveLocalFile(path: remuxedPath)) + subscriber.putCompletion() + + + return ActionDisposable { + + } + } +} diff --git a/Telegram-Mac/FolderIcons.swift b/Telegram-Mac/FolderIcons.swift new file mode 100644 index 0000000000..e81527ccf9 --- /dev/null +++ b/Telegram-Mac/FolderIcons.swift @@ -0,0 +1,194 @@ +// +// FolderIcons.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa + + +enum FolderIconState { + case sidebar + case sidebarActive + case preview + case settings + var color: NSColor { + switch self { + case .sidebar: + return NSColor.white.withAlphaComponent(0.5) + case .sidebarActive: + return .white + case .preview: + return theme.colors.grayIcon + case .settings: + return theme.colors.grayIcon + } + } +} + +let allSidebarFolderIcons: [FolderIcon] = [FolderIcon(emoticon: .emoji("🐱")), + FolderIcon(emoticon: .emoji("📕")), + FolderIcon(emoticon: .emoji("💰")), + FolderIcon(emoticon: .emoji("📸")), + FolderIcon(emoticon: .emoji("🎮")), + FolderIcon(emoticon: .emoji("🏡")), + FolderIcon(emoticon: .emoji("💡")), + FolderIcon(emoticon: .emoji("👍")), + FolderIcon(emoticon: .emoji("🔒")), + FolderIcon(emoticon: .emoji("❤️")), + FolderIcon(emoticon: .emoji("➕")), + FolderIcon(emoticon: .emoji("🎵")), + FolderIcon(emoticon: .emoji("🎨")), + FolderIcon(emoticon: .emoji("✈️")), + FolderIcon(emoticon: .emoji("⚽️")), + FolderIcon(emoticon: .emoji("⭐")), + FolderIcon(emoticon: .emoji("🎓")), + FolderIcon(emoticon: .emoji("🛫")), + FolderIcon(emoticon: .emoji("👑")), + FolderIcon(emoticon: .emoji("👨‍💼")), + FolderIcon(emoticon: .emoji("👤")), + FolderIcon(emoticon: .emoji("👥")), + FolderIcon(emoticon: .emoji("📢")), + FolderIcon(emoticon: .emoji("💬")), + FolderIcon(emoticon: .emoji("✅")), + FolderIcon(emoticon: .emoji("☑️")), + FolderIcon(emoticon: .emoji("🤖")), + FolderIcon(emoticon: .emoji("🗂"))] + + + +enum FolderEmoticon { + case emoji(String) + case allChats + case groups + case read + case personal + case unmuted + case unread + case channels + case bots + case folder + + var emoji: String? { + switch self { + case let .emoji(emoji): + return emoji + case .allChats: return "💬" + case .personal: return "👤" + case .groups: return "👥" + case .read: return "✅" + case .unmuted: return "🔔" + case .unread: return "☑️" + case .channels: return "📢" + case .bots: return "🤖" + case .folder: return "🗂" + } + } + + var iconName: String { + switch self { + case .allChats: + return "Icon_Sidebar_AllChats" + case .groups: + return "Icon_Sidebar_Group" + case .read: + return "Icon_Sidebar_Read" + case .unread: + return "Icon_Sidebar_Unread" + case .personal: + return "Icon_Sidebar_Personal" + case .unmuted: + return "Icon_Sidebar_Unmuted" + case .channels: + return "Icon_Sidebar_Channel" + case .bots: + return "Icon_Sidebar_Bot" + case .folder: + return "Icon_Sidebar_Folder" + case let .emoji(emoji): + switch emoji { + case "👤": + return "Icon_Sidebar_Personal" + case "👥": + return "Icon_Sidebar_Group" + case "📢": + return "Icon_Sidebar_Channel" + case "💬": + return "Icon_Sidebar_AllChats" + case "✅": + return "Icon_Sidebar_Read" + case "☑️": + return "Icon_Sidebar_Unread" + case "🔔": + return "Icon_Sidebar_Unmuted" + case "🗂": + return "Icon_Sidebar_Folder" + case "🤖": + return "Icon_Sidebar_Bot" + case "🐶", "🐱": + return "Icon_Sidebar_Animal" + case "📕": + return "Icon_Sidebar_Book" + case "💰": + return "Icon_Sidebar_Coin" + case "📸": + return "Icon_Sidebar_Flash" + case "🎮": + return "Icon_Sidebar_Game" + case "🏡": + return "Icon_Sidebar_Home" + case "💡": + return "Icon_Sidebar_Lamp" + case "👍": + return "Icon_Sidebar_Like" + case "🔒": + return "Icon_Sidebar_Lock" + case "❤️": + return "Icon_Sidebar_Love" + case "➕": + return "Icon_Sidebar_Math" + case "🎵": + return "Icon_Sidebar_Music" + case "🎨": + return "Icon_Sidebar_Paint" + case "✈️": + return "Icon_Sidebar_Plane" + case "⚽️": + return "Icon_Sidebar_Sport" + case "⭐": + return "Icon_Sidebar_Star" + case "🎓": + return "Icon_Sidebar_Student" + case "🛫": + return "Icon_Sidebar_Telegram" + case "👑": + return "Icon_Sidebar_Virus" + case "👨‍💼": + return "Icon_Sidebar_Work" + case "🍷": + return "Icon_Sidebar_Wine" + case "🎭": + return "Icon_Sidebar_Mask" + default: + return "Icon_Sidebar_Folder" + } + } + } +} + +final class FolderIcon { + let emoticon: FolderEmoticon + + init(emoticon: FolderEmoticon) { + self.emoticon = emoticon + } + + func icon(for state: FolderIconState) -> CGImage { + return NSImage(named: self.emoticon.iconName)!.precomposed(state.color, flipVertical: state == .preview) + } + +} + + diff --git a/Telegram-Mac/ForgotPasswordController.swift b/Telegram-Mac/ForgotPasswordController.swift new file mode 100644 index 0000000000..f657ea9180 --- /dev/null +++ b/Telegram-Mac/ForgotPasswordController.swift @@ -0,0 +1,178 @@ +// +// ForgotPasswordController.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + + +private let _id_input_code = InputDataIdentifier("_id_input_code") + +private struct ForgotPasswordState : Equatable { + let code: String + let error: InputDataValueError? + let checking: Bool + init(code: String, error: InputDataValueError?, checking: Bool) { + self.code = code + self.error = error + self.checking = checking + } + func withUpdatedCode(_ code: String) -> ForgotPasswordState { + return ForgotPasswordState(code: code, error: self.error, checking: self.checking) + } + func withUpdatedError(_ error: InputDataValueError?) -> ForgotPasswordState { + return ForgotPasswordState(code: self.code, error: error, checking: self.checking) + } + func withUpdatedChecking(_ checking: Bool) -> ForgotPasswordState { + return ForgotPasswordState(code: self.code, error: self.error, checking: checking) + } +} + +private func forgotPasswordEntries(state: ForgotPasswordState, pattern: String, unavailable: @escaping()->Void) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.code), error: state.error, identifier: _id_input_code, mode: .plain, data: InputDataRowData(), placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: 6)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: GeneralRowTextType.markdown(L10n.twoStepAuthRecoveryCodeHelp + "\n\n" + L10n.twoStepAuthRecoveryEmailUnavailableNew(pattern), linkHandler: { _ in + unavailable() + }), data: InputDataGeneralTextData(detectBold: false))) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func ForgotUnauthorizedPasswordController(accountManager: AccountManager, account: UnauthorizedAccount, emailPattern: String) -> InputDataModalController { + + + let initialState = ForgotPasswordState(code: "", error: nil, checking: false) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((ForgotPasswordState) -> ForgotPasswordState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + + + let disposable = MetaDisposable() + + var close: (() -> Void)? = nil + + let checkCode: (String) -> InputDataValidation = { code in + return .fail(.doSomething { f in + let checking: Bool = stateValue.with { $0.checking } + if !checking { + updateState { state in + return state.withUpdatedChecking(true) + } + + if code.length == 6 { + disposable.set(showModalProgress(signal: performPasswordRecovery(accountManager: accountManager, account: account, code: code, syncContacts: false) |> deliverOnMainQueue, for: mainWindow).start(next: { + + updateState { state in + return state.withUpdatedChecking(false) + } + + close?() + + }, error: { error in + + updateState { state in + return state.withUpdatedChecking(false) + } + + let text: String + switch error { + case .invalidCode: + text = L10n.twoStepAuthEmailCodeInvalid + case .expired: + text = L10n.twoStepAuthEmailCodeExpired + case .limitExceeded: + text = L10n.loginFloodWait + } + + updateState { current in + return current.withUpdatedError(InputDataValueError(description: text, target: .data)) + } + + f(.fail(.fields([_id_input_code : .shake]))) + })) + } else { + updateState { current in + return current.withUpdatedError(InputDataValueError(description: L10n.twoStepAuthEmailCodeInvalid, target: .data)) + } + + f(.fail(.fields([_id_input_code : .shake]))) + } + + + } + + }) + } + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: forgotPasswordEntries(state: state, pattern: emailPattern, unavailable: { + alert(for: mainWindow, info: L10n.twoStepAuthRecoveryFailed) + })) + } + + let controller = InputDataController(dataSignal: signal, title: L10n.twoStepAuthRecoveryTitle, validateData: { data in + + return checkCode(stateValue.with { $0.code }) + }, updateDatas: { data in + updateState { current in + return current.withUpdatedCode(data[_id_input_code]?.stringValue ?? current.code).withUpdatedError(nil) + } + let code = stateValue.with { $0.code } + if code.length == 6 { + return checkCode(code) + } + return .none + }, afterDisappear: { + disposable.dispose() + }, updateDoneValue: { data in + return { f in + let checking = stateValue.with { $0.checking } + f(checking ? .loading : .invisible) + } + }, hasDone: true) + + controller.getBackgroundColor = { + theme.colors.background + } + + let modalInteractions = ModalInteractions(acceptTitle: L10n.modalSend, accept: { [weak controller] in + _ = controller?.returnKeyAction() + }, drawBorder: true, height: 50, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + close = { [weak modalController] in + modalController?.modal?.close() + } + + return modalController + +} diff --git a/Telegram-Mac/ForwardChatListController.swift b/Telegram-Mac/ForwardChatListController.swift index 378919aa70..6b536cc141 100644 --- a/Telegram-Mac/ForwardChatListController.swift +++ b/Telegram-Mac/ForwardChatListController.swift @@ -8,23 +8,30 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore class ForwardChatListController: ChatListController { override func getLeftBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.chatCancel)) + let button = TextButtonBarView(controller: self, text: tr(L10n.chatCancel)) - button.button.set(handler: { [weak self] _ in + button.set(handler: { [weak self] _ in + self?.navigationController?.removeModalAction() self?.navigationController?.back() - }, for: .Click) + }, for: .Click) return button } - init(_ account: Account) { - super.init(account, modal:true) + override func getRightBarViewOnce() -> BarView { + return BarView(controller: self) + } + + init(_ context: AccountContext) { + super.init(context, modal:true) } override func escapeKeyAction() -> KeyHandlerResult { + navigationController?.removeModalAction() return .rejected } diff --git a/Telegram-Mac/ForwardPanelModel.swift b/Telegram-Mac/ForwardPanelModel.swift index db5e4a89dc..032e9bfe89 100644 --- a/Telegram-Mac/ForwardPanelModel.swift +++ b/Telegram-Mac/ForwardPanelModel.swift @@ -8,37 +8,25 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore class ForwardPanelModel: ChatAccessoryModel { private var account:Account - private var forwardIds:[MessageId] private var forwardMessages:[Message] = [] - private var disposable:MetaDisposable = MetaDisposable() - - init(forwardIds:[MessageId], account:Account) { + init(forwardMessages:[Message], account:Account) { self.account = account - self.forwardIds = forwardIds + self.forwardMessages = forwardMessages super.init() - - - disposable.set((account.postbox.messagesAtIds(forwardIds) - |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - strongSelf.forwardMessages = result - strongSelf.make() - } - })) + self.make() } - deinit { - disposable.dispose() } @@ -48,22 +36,35 @@ class ForwardPanelModel: ChatAccessoryModel { var used:Set = Set() + var keys:[Int64:Int64] = [:] + var forwardMessages:[Message] = [] + for message in self.forwardMessages { + if let groupingKey = message.groupingKey { + if keys[groupingKey] == nil { + keys[groupingKey] = groupingKey + forwardMessages.append(message) + } + } else { + forwardMessages.append(message) + } + } + + for message in forwardMessages { - if let peer = messageMainPeer(message), let author = message.author { + if let author = message.chatPeer(account.peerId) { if !used.contains(author.id) { used.insert(author.id) - if peer.isChannel { - names.append(peer.displayTitle) + if author.isChannel { + names.append(author.displayTitle) } else { names.append(author.displayTitle) } } } - } - self.headerAttr = NSAttributedString.initialize(string: names.joined(separator: ", "), color: theme.colors.blueUI, font: .medium(.text)) - self.messageAttr = NSAttributedString.initialize(string: tr(.messageAccessoryPanelForwardedCountable(forwardMessages.count)), color: theme.colors.text, font: .normal(.text)) + self.headerAttr = NSAttributedString.initialize(string: names.joined(separator: ", "), color: theme.colors.accent, font: .medium(.text)) + self.messageAttr = NSAttributedString.initialize(string: tr(L10n.messageAccessoryPanelForwardedCountable(forwardMessages.count)), color: theme.colors.text, font: .normal(.text)) nodeReady.set(.single(true)) self.setNeedDisplay() diff --git a/Telegram-Mac/GIFContainerView.swift b/Telegram-Mac/GIFContainerView.swift index 435159846a..4e424201b0 100644 --- a/Telegram-Mac/GIFContainerView.swift +++ b/Telegram-Mac/GIFContainerView.swift @@ -7,13 +7,19 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit -import PostboxMac -import SwiftSignalKitMac -class GIFContainerView: View { +import Postbox +import SwiftSignalKit - private(set) var player:GIFPlayerView = GIFPlayerView() + + +class GIFContainerView: Control { + + let player:GifPlayerBufferView = GifPlayerBufferView() + + private var progressView:RadialProgressView? var playerInset:NSEdgeInsets = NSEdgeInsets() { didSet { @@ -24,30 +30,35 @@ class GIFContainerView: View { private let fetchDisposable = MetaDisposable() private let playerDisposable = MetaDisposable() - private var resource:TelegramMediaResource? - private var account:Account? + private var context: AccountContext? private var size:NSSize = NSZeroSize + private var ignoreWindowKey: Bool = false private weak var tableView:TableView? - var timebase:CMTimebase? { - didSet { - player.reset(with: timebase, false) - } - } - private var path:String? { - didSet { - updatePlayerIfNeeded() - } - } + + var associatedMessageId: MessageId? = nil + private var fileReference: FileMediaReference? + override init() { + + super.init() addSubview(player) self.backgroundColor = .clear - self.layer?.borderWidth = 1.5 - //self.layer?.cornerRadius = 4.0 + + + player.background = .clear + player.setVideoLayerGravity(.resizeAspectFill) + set(handler: { [weak self] control in + if let `self` = self, let window = self.window as? Window, let table = self.tableView, let context = self.context { + _ = startModalPreviewHandle(table, window: window, context: context) + } + }, for: .LongMouseDown) + } + required convenience init(frame frameRect: NSRect) { self.init() } @@ -69,60 +80,49 @@ class GIFContainerView: View { } - func fetch() { - if let account = account, let resource = resource { - fetchDisposable.set(account.postbox.mediaBox.fetchedResource(resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)).start()) + func cancelFetching() { + if let reference = fileReference { + context?.account.postbox.mediaBox.cancelInteractiveResourceFetch(reference.media.resource) } } - + + func fetch() { + if let context = context, let reference = fileReference { + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(reference.media.resource), statsCategory: .file).start()) + } + } func removeNotificationListeners() { NotificationCenter.default.removeObserver(self) } + var accept: Bool { + let wAccept = window != nil && (window!.isKeyWindow || self.ignoreWindowKey) && !NSIsEmptyRect(visibleRect) + let accept:Bool = wAccept + return accept + } + + @objc func updatePlayerIfNeeded() { + let accept = self.accept - - let wAccept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) - var accept:Bool = false - - if let window = window { - var points:[NSPoint] = [] - - points.append(convert(focus(NSMakeSize(1, 1)).origin, to: window.contentView)) - points.append(convert(NSMakePoint(1, 1), to: window.contentView)) - points.append(convert(NSMakePoint(frame.width - 1, frame.height - 1), to: window.contentView)) - - - for point in points { - if let hit = window.contentView?.hitTest(point) { - accept = wAccept && (hit == self.player || hit == self) - if !accept && wAccept, let hit = hit as? Control { - accept = !hit.userInteractionEnabled - } - } - if accept { - break - } + if !ignoreWindowKey { + var s:Signal = .single(Void()) + if accept { + s = s |> delay(0.05, queue: Queue.mainQueue()) } - - - + playerDisposable.set(s.start(next: {[weak self] (next) in + if let strongSelf = self { + strongSelf.player.ticking = accept + } + })) + } else { + playerDisposable.set(nil) + self.player.ticking = accept } - player.set(path: accept ? path : nil, timebase: timebase) - - - /*var s:Signal = .single() - s = s |> delay(0.01, queue: Queue.mainQueue()) - playerDisposable.set(s.start(next: {[weak self] (next) in - if let strongSelf = self { - let accept = strongSelf.window != nil && strongSelf.window!.isKeyWindow && !NSIsEmptyRect(strongSelf.visibleRect) - strongSelf.player.set(path: accept ? strongSelf.path : nil) - } - })) */ } @@ -132,12 +132,14 @@ class GIFContainerView: View { NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: tableView?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: tableView?.view) } else { removeNotificationListeners() } } deinit { playerDisposable.dispose() + removeNotificationListeners() } override func viewDidMoveToWindow() { @@ -145,65 +147,54 @@ class GIFContainerView: View { updatePlayerIfNeeded() } - func update(with resource: TelegramMediaResource, size: NSSize, viewSize:NSSize, account: Account, table: TableView?, iconSignal:Signal<(TransformImageArguments)->DrawingContext?,Void>) { + + func update(with fileReference: FileMediaReference, size: NSSize, viewSize:NSSize, context: AccountContext, table: TableView?, ignoreWindowKey: Bool = false, isPreview: Bool = false, iconSignal:Signal) { + + + let updated = self.fileReference == nil || !fileReference.media.isEqual(to: self.fileReference!.media) self.tableView = table - self.account = account - self.resource = resource + self.fileReference = fileReference + self.context = context self.size = size self.setFrameSize(size) - + self.ignoreWindowKey = ignoreWindowKey self.layer?.borderColor = theme.colors.background.cgColor - updateListeners() player.setFrameSize(viewSize) - player.center() progressView?.center() - player.setSignal(account: account, signal: iconSignal) - let imageSize = viewSize.aspectFitted(NSMakeSize(size.width, size.height - 8)) - - let arguments = TransformImageArguments(corners: ImageCorners(radius:2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) + player.update(fileReference, context: context) + + + let imageSize = viewSize.aspectFitted(NSMakeSize(size.width, size.height)) + let size = (fileReference.media.dimensions?.size ?? imageSize).aspectFilled(viewSize) + let arguments = TransformImageArguments(corners: ImageCorners(radius:2.0), imageSize: size, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) + + player.setSignal(signal: cachedMedia(media: fileReference.media, arguments: arguments, scale: backingScaleFactor), clearInstantly: updated) + + if !player.isFullyLoaded { + player.setSignal(iconSignal, cacheImage: { result in + cacheMedia(result, media: fileReference.media, arguments: arguments, scale: System.backingScale) + }) + } + player.set(arguments: arguments) - let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(resource) - - self.statusDisposable.set((combineLatest(updatedStatusSignal, account.postbox.mediaBox.resourceData(resource)) |> deliverOnMainQueue).start(next: { [weak self] (status,resource) in - if let strongSelf = self { - if case .Local = status { - if let progressView = strongSelf.progressView { - progressView.removeFromSuperview() - strongSelf.progressView = nil - } - strongSelf.path = resource.path - - } else { - if strongSelf.progressView == nil { - let progressView = RadialProgressView() - progressView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0)) - strongSelf.progressView = progressView - strongSelf.addSubview(progressView) - strongSelf.progressView?.center() - } - } - - switch status { - case let .Fetching(_, progress): - strongSelf.progressView?.state = .Fetching(progress: progress, force: false) - case .Local: - strongSelf.progressView?.state = .Play - case .Remote: - strongSelf.progressView?.state = .Remote - } - } - })) - + updatePlayerIfNeeded() fetch() + needsLayout = true } + override func layout() { + super.layout() + progressView?.center() + updatePlayerIfNeeded() + } + override func copy() -> Any { let view = View() view.backgroundColor = .clear diff --git a/Telegram-Mac/GIFPlayerView.swift b/Telegram-Mac/GIFPlayerView.swift index 0390fa350d..201b8b1baf 100644 --- a/Telegram-Mac/GIFPlayerView.swift +++ b/Telegram-Mac/GIFPlayerView.swift @@ -9,57 +9,172 @@ import Cocoa import TGUIKit import AVFoundation -import SwiftSignalKitMac +import SwiftSignalKit -let sampleBufferQueue = DispatchQueue(label: "samplebuffer") + + +final class CIStickerContext : CIContext { + deinit { + var bp:Int = 0 + bp += 1 + } +} + +private class AlphaFrameFilter: CIFilter { + static var kernel: CIColorKernel? = { + return CIColorKernel(source: """ +kernel vec4 alphaFrame(__sample s, __sample m) { + return vec4( s.rgb, m.r ); +} +""") + }() + + var inputImage: CIImage? + var maskImage: CIImage? + + override var outputImage: CIImage? { + let kernel = AlphaFrameFilter.kernel! + guard let inputImage = inputImage, let maskImage = maskImage else { + return nil + } + let args = [inputImage as AnyObject, maskImage as AnyObject] + return kernel.apply(extent: inputImage.extent, arguments: args) + } +} + +let sampleBufferQueue = DispatchQueue.init(label: "sampleBufferQueue", qos: DispatchQoS.background, attributes: []) private let veryLongTimeInterval = CFTimeInterval(256.0 * 365.0 * 24.0 * 60.0 * 60.0) +struct AVGifData : Equatable { + let asset: AVURLAsset + let track: AVAssetTrack + let animatedSticker: Bool + let swapOnComplete: Bool + private init(asset: AVURLAsset, track: AVAssetTrack, animatedSticker: Bool, swapOnComplete: Bool) { + self.asset = asset + self.track = track + self.swapOnComplete = swapOnComplete + self.animatedSticker = animatedSticker + } + + static func dataFrom(_ path: String?, animatedSticker: Bool = false, swapOnComplete: Bool = false) -> AVGifData? { + let new = link(path: path, ext: "mp4") + if let new = new { + let avAsset = AVURLAsset(url: URL(fileURLWithPath: new)) + let t = avAsset.tracks(withMediaType: .video).first + if let t = t { + return AVGifData(asset: avAsset, track: t, animatedSticker: animatedSticker, swapOnComplete: swapOnComplete) + } + } + return nil + } + static func ==(lhs: AVGifData, rhs: AVGifData) -> Bool { + return lhs.asset.url == rhs.asset.url && lhs.animatedSticker == rhs.animatedSticker + } + +} + +private final class TAVSampleBufferDisplayLayer : AVSampleBufferDisplayLayer { + deinit { + + } +} class GIFPlayerView: TransformImageView { - private var sampleLayer:AVSampleBufferDisplayLayer = AVSampleBufferDisplayLayer() + enum LoopActionResult { + case pause + } - private var _reader:Atomic = Atomic(value:nil) - + private let sampleLayer:TAVSampleBufferDisplayLayer = TAVSampleBufferDisplayLayer() + private var _reader:Atomic = Atomic(value:nil) private var _asset:Atomic = Atomic(value:nil) - - private let _output:Atomic = Atomic(value:nil) - - private let _track:Atomic = Atomic(value:nil) - - private let _needReset:Atomic = Atomic(value:false) - - private let _timer:Atomic = Atomic(value:nil) - - + private let _loopAction:Atomic<(()->LoopActionResult)?> = Atomic(value:nil) private let _timebase:Atomic = Atomic(value:nil) - private let _stopRequesting:Atomic = Atomic(value:false) private let _swapNext:Atomic = Atomic(value:true) - private let _path:Atomic = Atomic(value:nil) + private let _data:Atomic = Atomic(value:nil) + + func setLoopAction(_ action:(()->LoopActionResult)?) { + _ = _loopAction.swap(action) + } + - public var followWindow:Bool = true + private let maskLayer = CAShapeLayer() + var positionFlags: LayoutPositionFlags? { + didSet { + if let positionFlags = positionFlags { + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius + + + if positionFlags.contains(.top) && positionFlags.contains(.left) { + bottomLeftRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + bottomRightRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + topLeftRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + topRightRadius = .cornerRadius * 3 + 2 + } + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topRightRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topLeftRadius) + + maskLayer.path = path + layer?.mask = maskLayer + } else { + layer?.mask = nil + } + } + } override init() { super.init() sampleLayer.actions = ["onOrderIn":NSNull(),"sublayers":NSNull(),"bounds":NSNull(),"frame":NSNull(),"position":NSNull(),"contents":NSNull(),"opacity":NSNull(), "transform": NSNull() ] - sampleLayer.videoGravity = .resizeAspectFill + sampleLayer.videoGravity = .resizeAspect sampleLayer.backgroundColor = NSColor.clear.cgColor + + layer?.addSublayer(sampleLayer) + + } + + func setVideoLayerGravity(_ gravity: AVLayerVideoGravity) { + sampleLayer.videoGravity = gravity } - var isHasPath: Bool { - return _path.modify({$0}) != nil + + var controlTimebase: CMTimebase? { + return sampleLayer.controlTimebase + } + + var isHasData: Bool { + return _data.modify({$0}) != nil } required init?(coder: NSCoder) { @@ -76,17 +191,12 @@ class GIFPlayerView: TransformImageView { sampleLayer.frame = bounds } - func set(path:String?, timebase:CMTimebase? = nil) -> Void { + func set(data: AVGifData?, timebase:CMTimebase? = nil) -> Void { assertOnMainThread() - - - let realPath:String? = link(path:path, ext:"mov") - - - if realPath != self._path.modify({$0}) { - _ = _path.swap(realPath) + + if data != _data.swap(data) { _ = _timebase.swap(timebase) - let path = self._path + let _data = self._data let layer:AVSampleBufferDisplayLayer = self.sampleLayer let reader = self._reader let output = self._output @@ -97,18 +207,16 @@ class GIFPlayerView: TransformImageView { let asset = self._asset let timer = self._timer let timebase = self._timebase - - if let path = realPath { - let avAsset = AVURLAsset(url: URL(fileURLWithPath: path)) - let _ = asset.swap(avAsset) - let t = avAsset.tracks(withMediaType: .video).first - if let track = t { - layer.setAffineTransform(track.preferredTransform.inverted()) + let loopAction = self._loopAction + if let data = data { + let _ = track.swap(data.track) + let _ = asset.swap(data.asset) + + _ = stopRequesting.swap(false) + if data.swapOnComplete { + _ = timebase.swap(self.controlTimebase) } - - let _ = track.swap(t) - _ = stopRequesting.swap(t == nil) - _ = swapNext.swap(t != nil) + _ = swapNext.swap(true) } else { _ = asset.swap(nil) _ = track.swap(nil) @@ -118,31 +226,49 @@ class GIFPlayerView: TransformImageView { } layer.requestMediaDataWhenReady(on: sampleBufferQueue, using: { - if stopRequesting.modify({$0}) { + if stopRequesting.swap(false) { + + if let controlTimebase = layer.controlTimebase, let current = timer.swap(nil) { + CMTimebaseRemoveTimer(controlTimebase, timer: current) + _ = timebase.swap(nil) + } + layer.stopRequestingMediaData() layer.flushAndRemoveImage() - reader.modify({$0})?.cancelReading() - _ = reader.swap(nil) - _ = stopRequesting.swap(false) + var reader = reader.swap(nil) + Queue.concurrentBackgroundQueue().async { + reader?.cancelReading() + reader = nil + } + return } if swapNext.swap(false) { - _ = reader.modify({$0})?.cancelReading() - _ = reader.swap(nil) _ = output.swap(nil) + var reader = reader.swap(nil) + Queue.concurrentBackgroundQueue().async { + reader?.cancelReading() + reader = nil + } } - if let readerValue = reader.modify({$0}), let outputValue = output.modify({$0}) { + if let readerValue = reader.with({ $0 }), let outputValue = output.with({ $0 }) { + + let affineTransform = track.with { $0?.preferredTransform.inverted() } + if let affineTransform = affineTransform { + layer.setAffineTransform(affineTransform) + } + while layer.isReadyForMoreMediaData { - if !stopRequesting.modify({$0}) { - if let sampleVideo = outputValue.copyNextSampleBuffer() { - layer.enqueue(sampleVideo) - + if !stopRequesting.with({ $0 }) { + + if readerValue.status == .reading, let sampleBuffer = outputValue.copyNextSampleBuffer() { + layer.enqueue(sampleBuffer) continue } - _ = stopRequesting.modify({_ in path.modify({$0}) == nil}) + _ = stopRequesting.modify { _ in _data.with { $0 } == nil } break } else { break @@ -150,9 +276,15 @@ class GIFPlayerView: TransformImageView { } if readerValue.status == .completed || readerValue.status == .cancelled { - if reset.modify({$0}) { - _ = reset.swap(false) - + if reset.swap(false) { + let loopActionResult = loopAction.with({ $0?() }) + + if let loopActionResult = loopActionResult { + switch loopActionResult { + case .pause: + return + } + } let result = restartReading(_reader: reader, _asset: asset, _track: track, _output: output, _needReset: reset, _timer: timer, layer: layer, _timebase: timebase) if result { layer.flush() @@ -172,13 +304,12 @@ class GIFPlayerView: TransformImageView { } func reset(with timebase:CMTimebase? = nil, _ resetImage: Bool = true) { - if resetImage { + // if resetImage { sampleLayer.flushAndRemoveImage() - } else { - sampleLayer.flush() - } + // } else { + // sampleLayer.flush() + // } - clear(false) _ = _swapNext.swap(true) _ = _timebase.swap(timebase) } @@ -186,22 +317,11 @@ class GIFPlayerView: TransformImageView { deinit { - clear(true) - _ = _path.swap(nil) - sampleLayer.flushAndRemoveImage() + _ = _stopRequesting.swap(true) } - private func clear(_ stopRequesting:Bool = false) { - _ = _stopRequesting.swap(stopRequesting) - - if let timebase = sampleLayer.controlTimebase, let timer = _timer.modify({$0}) { - _ = _timer.swap(nil) - CMTimebaseRemoveTimer(timebase, timer) - } - - } - + required convenience init(frame frameRect: NSRect) { self.init() @@ -213,7 +333,7 @@ fileprivate func restartReading(_reader:Atomic, _asset:Atomic, _asset:Atomic, _asset:Atomic, _asset:Atomic Bool { - let sum:CGFloat = sizes.reduce(0, { (acc, size) -> CGFloat in - return acc + size.width - }) - if sum >= width { - return true - } else { - return false - } +private final class GifTabsArguments { + let select:(GifTabEntryId)->Void + let context: AccountContext + init(context: AccountContext, select: @escaping(GifTabEntryId)->Void) { + self.context = context + self.select = select } } -func ==(lhs:RecentGifRow, rhs:RecentGifRow) -> Bool { - return lhs.entries == rhs.entries && lhs.results == rhs.results && lhs.sizes == rhs.sizes + +enum GifTabEntryId : Hashable { + case recent + case trending + case recommended(String) } -func makeRecentGifEnties(_ results:[TelegramMediaFile], initialSize:NSSize) -> [RecentGifRowEntry] { - var entries:[RecentGifEntry] = [] - var rows:[RecentGifRow] = [] +private enum GifTabEntry : TableItemListNodeEntry { + typealias ItemGenerationArguments = GifTabsArguments - var dimensions:[NSSize] = [] - var results = results - var index:Int = 0 - for result in results { - entries.append(.gif(index: index, file: result)) - dimensions.append(result.dimensions ?? NSZeroSize) - index += 1 - } + case recent(selected: Bool) + case trending(selected: Bool) + case recommended(selected: Bool, index: Int, value: String) - var fitted:[[NSSize]] = [] - let f:Int = Int(round(initialSize.width / initialSize.height)) - while !dimensions.isEmpty { - let row = fitPrettyDimensions(dimensions, isLastRow: f > dimensions.count, fitToHeight: false, perSize:initialSize) - fitted.append(row) - dimensions.removeSubrange(0 ..< row.count) - } - for row in fitted { - let subentries = Array(entries.prefix(row.count)) - let subresult = Array(results.prefix(row.count)) - rows.append(RecentGifRow(entries: subentries, results: subresult, sizes: row)) - - entries.removeSubrange(0 ..< row.count) - results.removeSubrange(0 ..< row.count) - - } - var idx:Int = 0 - return rows.map { row in - let entry = RecentGifRowEntry.gif(index: idx, row: row) - idx += 1 - return entry - } -} - - -enum RecentGifEntry : Equatable { - case gif(index:Int, file:TelegramMediaFile) - var index:Int { + var index: Int { switch self { - case let .gif(index, _): + case .recent: + return -2 + case .trending: + return -1 + case let .recommended(_, index, _): return index } } - var mediaId:MediaId { + var stableId: GifTabEntryId { switch self { - case let .gif(_, file): - return file.id ?? MediaId(namespace: 0, id: 0) + case .recent: + return .recent + case .trending: + return .trending + case let .recommended(_, _, value): + return .recommended(value) } } -} -func ==(lhs:RecentGifEntry, rhs: RecentGifEntry) -> Bool { - switch lhs { - case let .gif(lhsIndex, lhsFile): - if case let .gif(rhsIndex, rhsFile) = rhs { - return lhsIndex == rhsIndex && lhsFile.isEqual(rhsFile) - } else { - return false - } + + static func < (lhs: GifTabEntry, rhs: GifTabEntry) -> Bool { + return lhs.index < rhs.index } -} - -enum RecentGifRowEntry : Comparable, Identifiable { - case gif(index:Int, row: RecentGifRow) - var index:Int { + var selected: Bool { switch self { - case let .gif(index, _): - return index + case let .recent(selected): + return selected + case let .trending(selected): + return selected + case let .recommended(selected, _, _): + return selected } } - var stableId: AnyHashable { - switch self { - case let .gif(index: _, row: row): - return row.entries.reduce("", { (current, row) -> String in - return current + "index:\(row.index), id:\(row.mediaId)" - }).hashValue - } + func item(_ arguments: GifTabsArguments, initialSize: NSSize) -> TableRowItem { + return GifPanelTabRowItem(initialSize, selected: self.selected, entry: stableId, select: arguments.select) } } -func ==(lhs:RecentGifRowEntry, rhs: RecentGifRowEntry) -> Bool { - switch lhs { - case let .gif(index, row): - if case .gif(index, row) = rhs { - return true + + + +struct GIFKeyboardConfiguration : Equatable { + static var defaultValue: GIFKeyboardConfiguration { + return GIFKeyboardConfiguration(emojis: []) + } + + let emojis: [String] + + fileprivate init(emojis: [String]) { + self.emojis = emojis.map { $0.fixed } + } + + static func with(appConfiguration: AppConfiguration) -> GIFKeyboardConfiguration { + if let data = appConfiguration.data, let value = data["gif_search_emojies"] as? [String] { + return GIFKeyboardConfiguration(emojis: value.map { $0.fixed }) } else { - return false + return .defaultValue } } + } -func <(lhs:RecentGifRowEntry, rhs: RecentGifRowEntry) -> Bool { - return lhs.index < rhs.index +private func prepareEntries(left:[InputContextEntry], right:[InputContextEntry], context: AccountContext, initialSize:NSSize, arguments: RecentGifsArguments?) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in + switch entry { + case let .contextMediaResult(collection, row, index): + return ContextMediaRowItem(initialSize, row, index, context, ContextMediaArguments(sendResult: { result, view in + if let collection = collection { + arguments?.sendInlineResult(collection, result, view) + } else { + switch result { + case let .internalReference(values): + if let file = values.file { + arguments?.sendAppFile(file, view, false) + } + default: + break + } + } + }, menuItems: { file, view in + return context.account.postbox.transaction { transaction -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + if let mediaId = file.id { + let gifItems = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs).compactMap {$0.contents as? RecentMediaItem} + if let _ = gifItems.firstIndex(where: {$0.media.id == mediaId}) { + items.append(ContextMenuItem(L10n.messageContextRemoveGif, handler: { + let _ = removeSavedGif(postbox: context.account.postbox, mediaId: mediaId).start() + })) + } else { + items.append(ContextMenuItem(L10n.messageContextSaveGif, handler: { + let _ = addSavedGif(postbox: context.account.postbox, fileReference: FileMediaReference.savedGif(media: file)).start() + })) + } + items.append(ContextMenuItem(L10n.chatSendWithoutSound, handler: { + arguments?.sendAppFile(file, view, true) + })) + } + return items + } + })) + case let .separator(string, _, _): + return SeparatorRowItem(initialSize, entry.stableId, string: string) + case let .emoji(clues, selected, _, _): + return ContextClueRowItem(initialSize, stableId: entry.stableId, context: context, clues: clues, selected: selected, canDisablePrediction: false, callback: { emoji in + arguments?.searchBySuggestion(emoji) + }) + default: + fatalError() + } + }) + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } -private func prepareEntries(left:[RecentGifRowEntry], right:[RecentGifRowEntry], account:Account, initialSize:NSSize, arguments: RecentGifsArguments) -> TableUpdateTransition { - +private func prepareTabTransition(left:[GifTabEntry], right:[GifTabEntry], initialSize:NSSize, arguments: GifTabsArguments) -> TableUpdateTransition { let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in - switch entry { - case .gif: - return RecentGIFRowItem(initialSize, account: account, entry: entry, arguments: arguments) - } + return entry.item(arguments, initialSize: initialSize) }) - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated) + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) + } -private func recentEntries(for view:OrderedItemListView?, initialSize:NSSize) -> [RecentGifRowEntry] { +private func recentEntries(for view:OrderedItemListView?, initialSize:NSSize) -> [InputContextEntry] { if let view = view { - return makeRecentGifEnties(view.items.prefix(70).flatMap({($0.contents as? RecentMediaItem)?.media as? TelegramMediaFile}), initialSize: NSMakeSize(initialSize.width, 100)) + let result: [ChatContextResult] = view.items.compactMap({($0.contents as? RecentMediaItem)?.media as? TelegramMediaFile}).map { file in + let reference = ChatContextResult.InternalReference(queryId: 0, id: "gif-panel", type: "gif", title: nil, description: nil, image: nil, file: file, message: .auto(caption: "", entities: nil, replyMarkup: nil)) + return .internalReference(reference) + } + let values = makeMediaEnties(result, isSavedGifs: true, initialSize: NSMakeSize(initialSize.width, 100)) + var wrapped:[InputContextEntry] = [] + for value in values { + wrapped.append(InputContextEntry.contextMediaResult(nil, value, Int64(arc4random()) | ((Int64(wrapped.count) << 40)))) + } + + return wrapped } return [] } -struct RecentGifsArguments { - let sendGif:(TelegramMediaFile)->Void +private func tabsEntries(_ emojis: [String], selected: GifTabEntryId) -> [GifTabEntry] { + var entries:[GifTabEntry] = [] + + entries.append(.recent(selected: selected == .recent)) + entries.append(.trending(selected: selected == .trending)) + + for (i, emoji) in emojis.enumerated() { + entries.append(.recommended(selected: selected == .recommended(emoji), index: i, value: emoji)) + } + + return entries +} + +private func gifEntries(for collection: ChatContextResultCollection?, results: [ChatContextResult], initialSize: NSSize) -> [InputContextEntry] { + var result: [InputContextEntry] = [] + if let collection = collection { + result = makeMediaEnties(results, isSavedGifs: true, initialSize: NSMakeSize(initialSize.width, 100)).map({InputContextEntry.contextMediaResult(collection, $0, arc4random64())}) + } + + return result +} + +final class RecentGifsArguments { + var sendInlineResult:(ChatContextResultCollection,ChatContextResult, NSView) -> Void = { _,_,_ in} + var sendAppFile:(TelegramMediaFile, NSView, Bool) -> Void = { _,_,_ in} + var searchBySuggestion:(String)->Void = { _ in } } final class TableContainer : View { fileprivate var tableView: TableView? + fileprivate var restrictedView:RestrictionWrappedView? + fileprivate let progressView: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 30, 30)) + fileprivate let emptyResults: ImageView = ImageView() + + + let searchView = SearchView(frame: .zero) + private let searchContainer = View() + fileprivate let packsView:HorizontalTableView = HorizontalTableView(frame: NSZeroRect) + private let separator:View = View() + fileprivate let tabsContainer: View = View() + required init(frame frameRect: NSRect) { super.init(frame: frameRect) + emptyResults.contentGravity = .center + updateLocalizationAndTheme(theme: theme) + + + searchContainer.addSubview(searchView) + addSubview(searchContainer) + + tabsContainer.addSubview(packsView) + tabsContainer.addSubview(separator) + addSubview(tabsContainer) + + reinstall() + } + + func updateRestricion(_ peer: Peer?) { + if let peer = peer, let text = permissionText(from: peer, for: .banSendGifs) { + restrictedView?.removeFromSuperview() + restrictedView = RestrictionWrappedView(text) + addSubview(restrictedView!) + } else { + restrictedView?.removeFromSuperview() + restrictedView = nil + } + setFrameSize(frame.size) + needsLayout = true + } + + private var searchState: SearchState? = nil + + func updateSearchState(_ searchState: SearchState, animated: Bool) { + self.searchState = searchState + switch searchState.state { + case .Focus: + tabsContainer.change(pos: NSMakePoint(0, -tabsContainer.frame.height), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + case .None: + tabsContainer.change(pos: NSMakePoint(0, 0), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + } + if let tableView = tableView { + tableView.change(size: NSMakeSize(frame.width, frame.height - searchContainer.frame.maxY), animated: animated) + tableView.change(pos: NSMakePoint(0, searchContainer.frame.maxY), animated: animated) + } } func reinstall() { + self.packsView.removeAll() tableView?.removeFromSuperview() tableView = TableView(frame: bounds) - addSubview(tableView!) + var subviews:[NSView] = [tabsContainer, searchContainer,tableView!, emptyResults] + + restrictedView?.removeFromSuperview() + if let restrictedView = restrictedView { + subviews.append(restrictedView) + } + self.subviews = subviews } + fileprivate func merge(with transition: TableUpdateTransition, tabTransition: TableUpdateTransition, animated: Bool) { + self.tableView?.merge(with: transition) + self.packsView.merge(with: tabTransition) + if let tableView = tableView { + let emptySearchHidden: Bool = !tableView.isEmpty + + if !emptySearchHidden { + emptyResults.isHidden = false + } + emptyResults.change(opacity: emptySearchHidden ? 0 : 1, animated: animated, completion: { [weak self] completed in + if completed { + self?.emptyResults.isHidden = emptySearchHidden + } + }) + + } else { + emptyResults.isHidden = true + } + } func deinstall() { tableView?.removeFromSuperview() tableView = nil } - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - tableView?.setFrameSize(newSize) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + self.restrictedView?.updateLocalizationAndTheme(theme: theme) + emptyResults.background = theme.colors.background + emptyResults.image = theme.icons.stickersEmptySearch + searchView.updateLocalizationAndTheme(theme: theme) + separator.backgroundColor = theme.colors.border + } + + override func layout() { + super.layout() + + let initial: CGFloat = searchState?.state == .Focus ? -50 : 0 + + tabsContainer.frame = NSMakeRect(0, initial, frame.width, 50) + separator.frame = NSMakeRect(0, tabsContainer.frame.height - .borderSize, tabsContainer.frame.width, .borderSize) + packsView.frame = tabsContainer.focus(NSMakeSize(frame.width, 40)) + + + searchContainer.frame = NSMakeRect(0, tabsContainer.frame.maxY, frame.width, 50) + searchView.setFrameSize(NSMakeSize(frame.width - 20, 30)) + searchView.center() + + restrictedView?.setFrameSize(frame.size) + + if let tableView = tableView { + tableView.frame = NSMakeRect(0, searchContainer.frame.maxY, frame.width, frame.height - searchContainer.frame.maxY) + emptyResults.sizeToFit() + emptyResults.center() + } + progressView.center() } required init?(coder: NSCoder) { @@ -185,16 +346,69 @@ final class TableContainer : View { } } -class GIFViewController: TelegramGenericViewController { +class GIFViewController: TelegramGenericViewController, Notifable { + + + private var tabsState: ValuePromise = ValuePromise(.recent, ignoreRepeated: true) + + private let searchValue = ValuePromise(.init(state: .None, request: nil)) + private var searchState: SearchState = .init(state: .None, request: nil) { + didSet { + let value = searchState + if value.request.isEmpty { + self.searchValue.set(value) + } else { + self.searchValue.set(value) + } + + } + } + private var interactions:EntertainmentInteractions? + private weak var chatInteraction: ChatInteraction? private let disposable = MetaDisposable() - init(account:Account) { - super.init(account) + private let searchStateDisposable = MetaDisposable() + private let preloadDisposable = MetaDisposable() + var makeSearchCommand:((ESearchCommand)->Void)? + + override init(_ context: AccountContext) { + super.init(context) bar = .init(height: 0) } - func update(with interactions:EntertainmentInteractions?) { + private func updateSearchState(_ state: SearchState) { + self.searchState = state + if !state.request.isEmpty { + self.makeSearchCommand?(.loading) + } + if self.isLoaded() == true { + self.genericView.updateSearchState(state, animated: true) + self.genericView.tableView?.scroll(to: .up(true)) + } + } + + func update(with interactions:EntertainmentInteractions?, chatInteraction: ChatInteraction) { self.interactions = interactions + self.chatInteraction?.remove(observer: self) + self.chatInteraction = chatInteraction + chatInteraction.add(observer: self) + if isLoaded() { + genericView.updateRestricion(chatInteraction.presentation.peer) + } + } + + func notify(with value: Any, oldValue: Any, animated: Bool) { + if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState, let peer = value.peer, let oldPeer = oldValue.peer { + if permissionText(from: peer, for: .banSendGifs) != permissionText(from: oldPeer, for: .banSendGifs) { + genericView.updateRestricion(peer) + } + } + } + + + + func isEqual(to other: Notifable) -> Bool { + return other === self } override func viewWillDisappear(_ animated: Bool) { @@ -204,42 +418,192 @@ class GIFViewController: TelegramGenericViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - genericView.deinstall() + genericView.tableView?.removeAll() + genericView.tableView?.removeFromSuperview() + genericView.tableView = nil ready.set(.single(false)) } + + override var responderPriority: HandlerPriority { + return .modal + } + + override var canBecomeResponder: Bool { + if let view = context.sharedContext.bindings.rootNavigation().view as? SplitView { + return view.state == .single + } + return false + } + + override func becomeFirstResponder() -> Bool? { + return false + } + override func viewWillAppear(_ animated: Bool) { - super.viewDidAppear(animated) + super.viewWillAppear(animated) + + let value = GIFKeyboardConfiguration.with(appConfiguration: context.appConfiguration) genericView.reinstall() + genericView.updateRestricion(chatInteraction?.presentation.peer) + + + let searchInteractions = SearchInteractions({ [weak self] state, _ in + self?.updateSearchState(state) + }, { [weak self] state in + self?.updateSearchState(state) + }) + genericView.searchView.searchInteractions = searchInteractions _ = atomicSize.swap(_frameRect.size) - let arguments = RecentGifsArguments(sendGif: { [weak self] file in - self?.interactions?.sendGIF(file) + let arguments = RecentGifsArguments() + + arguments.sendAppFile = { [weak self] file, view, silent in + if let slowMode = self?.chatInteraction?.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else { + self?.chatInteraction?.sendAppFile(file, silent) + self?.makeSearchCommand?(.close) + self?.context.sharedContext.bindings.entertainment().closePopover() + } + } + + arguments.sendInlineResult = { [weak self] results, result, view in + if let slowMode = self?.chatInteraction?.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else { + self?.chatInteraction?.sendInlineResult(results, result) + self?.makeSearchCommand?(.close) + self?.context.sharedContext.bindings.entertainment().closePopover() + } + } + + arguments.searchBySuggestion = { [weak self] value in + self?.makeSearchCommand?(.apply(value)) + } + + let tabsArguments = GifTabsArguments(context: context, select: { [weak self] id in + self?.makeSearchCommand?(.close) + self?.tabsState.set(id) + self?.scrollup() }) - let previous:Atomic<[RecentGifRowEntry]> = Atomic(value: []) + let previous:Atomic<[InputContextEntry]> = Atomic(value: []) let initialSize = self.atomicSize - let account = self.account + let context = self.context + + struct SearchGifsState { + var request: String + var state: SearchFieldState + var values:[ChatContextResult] + var nextOffset: String + var tab: GifTabEntryId + } + + let loadNext: ValuePromise = ValuePromise(true, ignoreRepeated: false) - let signal = account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)]) |> deliverOn(prepareQueue) |> map { view -> TableUpdateTransition in - let postboxView = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)] as! OrderedItemListView - let entries = recentEntries(for: postboxView, initialSize: initialSize.modify({$0})) - return prepareEntries(left: previous.swap(entries), right: entries, account: account, initialSize: initialSize.modify({$0}), arguments: arguments) + let searchState:Atomic = Atomic(value: SearchGifsState(request: "", state: .None, values: [], nextOffset: "", tab: .recent)) + + let signal = combineLatest(queue: prepareQueue, context.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)]), self.searchValue.get(), tabsState.get(), loadNext.get()) |> mapToSignal { view, search, selectedTab, _ -> Signal<(TableUpdateTransition, GifTabEntryId), NoError> in + + _ = searchState.modify { current -> SearchGifsState in + var current = current + if current.request != search.request || current.state != search.state || current.tab != selectedTab { + current.values = [] + current.nextOffset = "" + } + current.request = search.request + current.state = search.state + current.tab = selectedTab + return current + } + + switch search.state { + case .Focus: + let searchSignal = searchGifs(account: context.account, query: search.request, nextOffset: searchState.with { $0.nextOffset }) + return searchSignal |> map { result in + _ = searchState.modify { current -> SearchGifsState in + var current = current + current.values += (result?.results ?? []) + current.nextOffset = result?.nextOffset ?? "" + return current + } + let entries = gifEntries(for: result, results: searchState.with { $0.values }, initialSize: initialSize.with { $0 }) + return (prepareEntries(left: previous.swap(entries), right: entries, context: context, initialSize: initialSize.with { $0 }, arguments: arguments), selectedTab) + } + default: + var request: String? = nil + + switch selectedTab { + case .recent: + break + case .trending: + request = "" + case let .recommended(value): + request = value + } + if let request = request { + let searchSignal = searchGifs(account: context.account, query: request, nextOffset: searchState.with { $0.nextOffset }) + return searchSignal |> map { result in + _ = searchState.modify { current -> SearchGifsState in + var current = current + current.values += (result?.results ?? []) + current.nextOffset = result?.nextOffset ?? "" + return current + } + let entries = gifEntries(for: result, results: searchState.with { $0.values }, initialSize: initialSize.with { $0 }) + return (prepareEntries(left: previous.swap(entries), right: entries, context: context, initialSize: initialSize.with { $0 }, arguments: arguments), selectedTab) + } + } else { + let postboxView = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)] as! OrderedItemListView + let entries = recentEntries(for: postboxView, initialSize: initialSize.with { $0 }).sorted(by: <) + return .single((prepareEntries(left: previous.swap(entries), right: entries, context: context, initialSize: initialSize.with { $0 }, arguments: arguments), selectedTab)) + } + + } + } |> deliverOnMainQueue + + var firstTime: Bool = true + + let previvousTabs: Atomic<[GifTabEntry]> = Atomic(value: []) + + let transitions: Signal<(TableUpdateTransition, TableUpdateTransition), NoError> = signal |> map { transition, id in + let entries = tabsEntries(value.emojis, selected: id) + return (transition, prepareTabTransition(left: previvousTabs.swap(entries), right: entries, initialSize: initialSize.with { $0 }, arguments: tabsArguments)) } |> deliverOnMainQueue - disposable.set(signal.start(next: { [weak self] transition in - self?.genericView.tableView?.merge(with: transition) + disposable.set(transitions.start(next: { [weak self] transition, tabTransition in + self?.genericView.merge(with: transition, tabTransition: tabTransition, animated: !firstTime) + self?.makeSearchCommand?(.normal) + firstTime = false self?.ready.set(.single(true)) })) + + + genericView.tableView?.setScrollHandler { position in + if !searchState.with({ $0.values.isEmpty && !$0.nextOffset.isEmpty }) { + switch position.direction { + case .bottom: + loadNext.set(true) + default: + break + } + } + } } + override func scrollup(force: Bool = false) { + self.genericView.tableView?.scroll(to: .up(true)) + } deinit { disposable.dispose() - NSLog("deinit gifs controller") + searchStateDisposable.dispose() + chatInteraction?.remove(observer: self) + preloadDisposable.dispose() } } diff --git a/Telegram-Mac/GalleryControls.swift b/Telegram-Mac/GalleryControls.swift deleted file mode 100644 index 4720386cb4..0000000000 --- a/Telegram-Mac/GalleryControls.swift +++ /dev/null @@ -1,209 +0,0 @@ -// -// GalleryControls.swift -// Telegram-Mac -// -// Created by keepcoder on 07/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac - -private let prevImage = #imageLiteral(resourceName: "Icon_GalleryLeft").precomposed() -private let nextImage = #imageLiteral(resourceName: "Icon_GalleryRight").precomposed() -private let moreImage = #imageLiteral(resourceName: "Icon_GalleryMore").precomposed() -private let dismissImage = #imageLiteral(resourceName: "Icon_GalleryDismiss").precomposed() - - - - - -class GalleryControls: Node { - - let index:Promise<(Int,Int)> = Promise() - private let interactions:GalleryInteractions - - - override var backgroundColor: NSColor? { - return .blackTransparent - } - - init(_ view: View? = nil, interactions:GalleryInteractions) { - self.interactions = interactions - super.init(view) - view?.layer?.opacity = 0.0 - } - - func animateIn() -> Void { - self.setNeedDisplay() - - if let view = view { - view.centerX(y: 10.0) - view.layer?.opacity = 1.0 - view.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - view.layer?.animatePosition(from: NSMakePoint(view.frame.minX, -view.frame.height), to: NSMakePoint(view.frame.minX, 10), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - } - - } - - func animateOut() -> Void { - self.setNeedDisplay() - - if let view = view { - - - view.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - view.layer?.animatePosition(from: view.frame.origin, to: NSMakePoint(view.frame.minX, -view.frame.height), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion:false) - } - - } - - override func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.round(layer.frame.size,.cornerRadius) - super.draw(layer, in: ctx) - } - - - -} - -class GalleryGeneralControls : GalleryControls { - - private let previous:ImageButton = ImageButton() - private let next:ImageButton = ImageButton() - private let more:ImageButton = ImageButton() - private let dismiss:ImageButton = ImageButton() - private let counter:TitleButton = TitleButton() - - - private let disposable:MetaDisposable = MetaDisposable() - - override var backgroundColor: NSColor? { - return .blackTransparent - } - - override init(_ view: View? = nil, interactions:GalleryInteractions) { - - - super.init(view, interactions: interactions) - - counter.style = galleryButtonStyle - previous.style = galleryButtonStyle - next.style = galleryButtonStyle - more.style = galleryButtonStyle - dismiss.style = galleryButtonStyle - - previous.set(image: prevImage, for: .Normal) - next.set(image: nextImage, for: .Normal) - more.set(image: moreImage, for: .Normal) - dismiss.set(image: dismissImage, for: .Normal) - - previous.set(handler: {_ in _ = interactions.previous()}, for: .Click) - next.set(handler: {_ in _ = interactions.next()}, for: .Click) - more.set(handler: { control in _ = interactions.showActions(control)}, for: .Click) - dismiss.set(handler: {_ in _ = interactions.dismiss()}, for: .Click) - - if let view = view { - - counter.sizeToFit(NSZeroSize, NSMakeSize(150, view.frame.height)) - counter.center(view) - - let bwidth = (view.frame.width - counter.frame.width) / 4.0 - - previous.frame = NSMakeRect(0, 0, bwidth, view.frame.height) - next.frame = NSMakeRect(previous.frame.maxX, 0, bwidth, view.frame.height) - more.frame = NSMakeRect(counter.frame.maxX, 0, bwidth, view.frame.height) - dismiss.frame = NSMakeRect(more.frame.maxX, 0, bwidth, view.frame.height) - - view.addSubview(previous) - view.addSubview(next) - view.addSubview(counter) - view.addSubview(more) - view.addSubview(dismiss) - - } - - disposable.set(index.get().start(next: {[weak self] (current, total) in - self?.counter.set(text: tr(.galleryCounter(current, total)), for: .Normal) - })) - - - } - - deinit { - disposable.dispose() - } -} - - -class GallerySecretControls : GalleryControls { - private let progress:TimableProgressView = TimableProgressView(TimableProgressTheme(seconds: 20)) - private let duration: TitleButton = TitleButton() - private let dismiss:ImageButton = ImageButton() - private var timer:SwiftSignalKitMac.Timer? = nil - override init(_ view: View?, interactions: GalleryInteractions) { - super.init(view, interactions: interactions) - if let view = view { - duration.set(font: .bold(.header), for: .Normal) - duration.set(color: .white, for: .Normal) - duration.set(text: "20 sec", for: .Normal) - dismiss.set(image: dismissImage, for: .Normal) - dismiss.sizeToFit() - dismiss.set(handler: {_ in _ = interactions.dismiss()}, for: .Click) - view.addSubview(progress) - view.addSubview(duration) - view.addSubview(dismiss) - - } - } - - func update(with attribute: AutoremoveTimeoutMessageAttribute, outgoing: Bool) { - timer?.invalidate() - if !outgoing { - progress.isHidden = false - duration.isHidden = false - dismiss.centerY(x: frame.width - dismiss.frame.width - 20) - progress.centerY(x: 20) - duration.center() - if let countdownBeginTime = attribute.countdownBeginTime { - - let difference:()->TimeInterval = { - return TimeInterval((countdownBeginTime + attribute.timeout)) - (CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - } - let start = difference() / Double(attribute.timeout) * 100.0 - progress.theme = TimableProgressTheme(seconds: difference(), start: start) - - let updateTitle:()->Void = { [weak self] in - self?.duration.set(text: String.stringForShortCallDurationSeconds(for: Int32(difference())), for: .Normal) - self?.duration.center() - } - updateTitle() - timer = SwiftSignalKitMac.Timer(timeout: 1, repeat: true, completion: updateTitle, queue: Queue.mainQueue()) - timer?.start() - } else { - progress.theme = TimableProgressTheme(seconds: TimeInterval(attribute.timeout)) - duration.set(text: String.stringForShortCallDurationSeconds(for: attribute.timeout), for: .Normal) - duration.center() - } - progress.progress = 0 - progress.startAnimation() - } else { - progress.isHidden = true - duration.isHidden = true - dismiss.center() - } - - } - - override func animateIn() { - super.animateIn() - } - - override func animateOut() { - super.animateOut() - progress.stopAnimation() - } - -} diff --git a/Telegram-Mac/GalleryMessageEntry.swift b/Telegram-Mac/GalleryMessageEntry.swift deleted file mode 100644 index ef66958456..0000000000 --- a/Telegram-Mac/GalleryMessageEntry.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// GalleryMessageEntry.swift -// Telegram -// -// Created by keepcoder on 22/09/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa - - diff --git a/Telegram-Mac/GalleryModernControls.swift b/Telegram-Mac/GalleryModernControls.swift new file mode 100644 index 0000000000..12a57416f3 --- /dev/null +++ b/Telegram-Mac/GalleryModernControls.swift @@ -0,0 +1,357 @@ +// +// GalleryModernControls.swift +// Telegram +// +// Created by Mikhail Filimonov on 28/08/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class GalleryModernControlsView: View { + + fileprivate let photoView: AvatarControl = AvatarControl(font: .avatar(18)) + private var nameNode: (TextNodeLayout, TextNode)? = nil + private var dateNode: (TextNodeLayout, TextNode)? = nil + private let shareControl: ImageButton = ImageButton() + private let moreControl: ImageButton = ImageButton() + + private let zoomInControl: ImageButton = ImageButton() + private let zoomOutControl: ImageButton = ImageButton() + private let rotateControl: ImageButton = ImageButton() + private let fastSaveControl: ImageButton = ImageButton() + + fileprivate var interactions: GalleryInteractions? + fileprivate var thumbs: GalleryThumbsControlView? { + didSet { + oldValue?.removeFromSuperview() + if let thumbs = thumbs { + addSubview(thumbs, positioned: .below, relativeTo: self.subviews.first) + thumbs.setFrameOrigin(NSMakePoint((self.frame.width - thumbs.frame.width) / 2 + (thumbs.frame.width - thumbs.documentSize.width) / 2, (self.frame.height - thumbs.frame.height) / 2)) + } + needsLayout = true + } + } + private var currentState:(peer: Peer?, timestamp: TimeInterval, account: Account)? { + didSet { + updateInterface() + } + } + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + // backgroundColor = .blackTransparent + photoView.setFrameSize(60, 60) + addSubview(photoView) + addSubview(shareControl) + addSubview(moreControl) + photoView.userInteractionEnabled = false + shareControl.autohighlight = false + moreControl.autohighlight = false + + let shareIcon = NSImage(cgImage: theme.icons.galleryShare, size: theme.icons.galleryShare.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + let moreIcon = NSImage(cgImage: theme.icons.galleryMore, size: theme.icons.galleryMore.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + let fastSaveIcon = NSImage(cgImage: theme.icons.galleryFastSave, size: theme.icons.galleryFastSave.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + + + shareControl.set(image: shareIcon, for: .Normal) + moreControl.set(image: moreIcon, for: .Normal) + fastSaveControl.set(image: fastSaveIcon, for: .Normal) + + + shareControl.set(image: theme.icons.galleryShare, for: .Hover) + moreControl.set(image: theme.icons.galleryMore, for: .Hover) + shareControl.set(image: theme.icons.galleryShare, for: .Highlight) + moreControl.set(image: theme.icons.galleryMore, for: .Highlight) + + fastSaveControl.set(image: theme.icons.galleryFastSave, for: .Hover) + fastSaveControl.set(image: theme.icons.galleryFastSave, for: .Highlight) + + _ = moreControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + _ = shareControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + + addSubview(fastSaveControl) + addSubview(zoomInControl) + addSubview(zoomOutControl) + addSubview(rotateControl) + + let zoomIn = NSImage(cgImage: theme.icons.galleryZoomIn, size: theme.icons.galleryZoomIn.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + let zoomOut = NSImage(cgImage: theme.icons.galleryZoomOut, size: theme.icons.galleryZoomOut.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + let rotate = NSImage(cgImage: theme.icons.galleryRotate, size: theme.icons.galleryRotate.backingSize).precomposed(NSColor.white.withAlphaComponent(0.7)) + + + zoomInControl.set(image: zoomIn, for: .Normal) + zoomOutControl.set(image: zoomOut, for: .Normal) + rotateControl.set(image: rotate, for: .Normal) + + zoomInControl.set(image: theme.icons.galleryZoomIn, for: .Hover) + zoomOutControl.set(image: theme.icons.galleryZoomOut, for: .Hover) + rotateControl.set(image: theme.icons.galleryRotate, for: .Hover) + + zoomInControl.set(image: theme.icons.galleryZoomIn, for: .Highlight) + zoomOutControl.set(image: theme.icons.galleryZoomOut, for: .Highlight) + rotateControl.set(image: theme.icons.galleryRotate, for: .Highlight) + + + _ = zoomInControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + _ = zoomOutControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + _ = rotateControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + _ = fastSaveControl.sizeToFit(NSZeroSize, NSMakeSize(60, 60), thatFit: true) + + shareControl.set(handler: { [weak self] control in + _ = self?.interactions?.share(control) + }, for: .Click) + + moreControl.set(handler: { [weak self] control in + _ = self?.interactions?.showActions(control) + }, for: .Click) + + rotateControl.set(handler: { [weak self] _ in + self?.interactions?.rotateLeft() + }, for: .Click) + + zoomInControl.set(handler: { [weak self] _ in + self?.interactions?.zoomIn() + }, for: .Click) + + zoomOutControl.set(handler: { [weak self] _ in + self?.interactions?.zoomOut() + }, for: .Click) + + fastSaveControl.set(handler: { [weak self] _ in + self?.interactions?.fastSave() + }, for: .Click) + } + + override func mouseUp(with event: NSEvent) { + let point = self.convert(event.locationInWindow, from: nil) + if let currentState = currentState { + if NSPointInRect(point, photoView.frame) || NSPointInRect(point, nameRect), let peerId = currentState.peer?.id { + interactions?.openInfo(peerId) + } else if NSPointInRect(point, dateRect) { + interactions?.openMessage() + } else if let thumbs = thumbs, !NSPointInRect(point, thumbs.frame) { + _ = interactions?.dismiss() + } + } + + } + + private var nameRect: NSRect { + if let nameNode = nameNode { + return NSMakeRect(photoView.frame.maxX + 10, photoView.frame.midY - nameNode.0.size.height - 2, nameNode.0.size.width, nameNode.0.size.height) + } + return NSZeroRect + } + private var dateRect: NSRect { + if let dateNode = dateNode { + return NSMakeRect(photoView.frame.maxX + 10, photoView.frame.midY + 2, dateNode.0.size.width, dateNode.0.size.height) + } + return NSZeroRect + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + if let nameNode = nameNode { + var point = NSMakePoint(photoView.frame.maxX + 10, photoView.frame.midY - nameNode.0.size.height - 2) + if dateNode == nil { + point.y = photoView.frame.midY - floorToScreenPixels(backingScaleFactor, (nameNode.0.size.height / 2)) + } + nameNode.1.draw(NSMakeRect(point.x, point.y, nameNode.0.size.width, nameNode.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: .clear) + } + if let dateNode = dateNode { + dateNode.1.draw(NSMakeRect(photoView.frame.maxX + 10, photoView.frame.midY + 2, dateNode.0.size.width, dateNode.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: .clear) + } + } + + func updateControlsVisible(_ entry: GalleryEntry) { + switch entry { + + case let .instantMedia(media, _): + if media.media is TelegramMediaImage { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = false + fastSaveControl.isHidden = false + } else if let file = media.media as? TelegramMediaFile { + if file.isVideo { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = true + fastSaveControl.isHidden = false + } + } + case let .message(message): + if let message = message.message, message.containsSecretMedia { + + } + if message.message?.media.first is TelegramMediaImage { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = false + fastSaveControl.isHidden = message.message?.containsSecretMedia == true + } else if let file = message.message?.media.first as? TelegramMediaFile { + if file.isVideo { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = true + fastSaveControl.isHidden = message.message?.containsSecretMedia == true + } else if !file.isGraphicFile { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = true + fastSaveControl.isHidden = message.message?.containsSecretMedia == true + } + } else if let webpage = message.message?.media.first as? TelegramMediaWebpage { + if case let .Loaded(content) = webpage.content { + if ExternalVideoLoader.isPlayable(content) { + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = true + fastSaveControl.isHidden = true + } + } + } else { + zoomInControl.isHidden = true + zoomOutControl.isHidden = true + rotateControl.isHidden = true + fastSaveControl.isHidden = true + } + default: + zoomInControl.isHidden = false + zoomOutControl.isHidden = false + rotateControl.isHidden = false + fastSaveControl.isHidden = false + } + } + + func updatePeer(_ peer: Peer?, timestamp: TimeInterval, account: Account, canShare: Bool) { + currentState = (peer, timestamp, account) + shareControl.isHidden = !canShare + needsLayout = true + } + + override func viewWillMove(toWindow newWindow: NSWindow?) { + super.viewWillMove(toWindow: newWindow) + + if let window = newWindow as? Window { + window.set(mouseHandler: { [weak self] _ -> KeyHandlerResult in + self?.updateVisibility() + return .rejected + }, with: self, for: .mouseMoved) + } else { + (self.window as? Window)?.remove(object: self, for: .mouseMoved) + } + } + + private func updateInterface() { + guard let window = window else {return} + + let point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let currentState = currentState { + photoView.setPeer(account: currentState.account, peer: currentState.peer) + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + formatter.doesRelativeDateFormatting = true + formatter.timeZone = NSTimeZone.local + nameNode = TextNode.layoutText(.initialize(string: currentState.peer?.displayTitle.prefixWithDots(30) ?? L10n.peerDeletedUser, color: NSPointInRect(point, nameRect) ? .white : .grayText, font: .medium(.huge)), nil, 1, .end, NSMakeSize(frame.width, 20), nil, false, .left) + dateNode = currentState.timestamp == 0 ? nil : TextNode.layoutText(.initialize(string: formatter.string(from: Date(timeIntervalSince1970: currentState.timestamp)), color: NSPointInRect(point, dateRect) ? .white : .grayText, font: .normal(.title)), nil, 1, .end, NSMakeSize(frame.width, 20), nil, false, .left) + } + + photoView._change(opacity: NSPointInRect(point, photoView.frame) ? 1 : 0.7, animated: false) + + needsDisplay = true + } + + fileprivate var isInside: Bool = false { + didSet { + updateInterface() + } + } + + func updateVisibility() { + if frame.minY >= 0 { + isInside = mouseInside() + } + } + + override func layout() { + super.layout() + photoView.centerY(x: 80) + + moreControl.centerY(x: frame.width - moreControl.frame.width - 80) + shareControl.centerY(x: moreControl.frame.minX - shareControl.frame.width) + + let alignControl = shareControl.isHidden ? moreControl : shareControl + fastSaveControl.centerY(x: alignControl.frame.minX - fastSaveControl.frame.width) + rotateControl.centerY(x: (fastSaveControl.isHidden ? alignControl.frame.minX : fastSaveControl.frame.minX) - rotateControl.frame.width - 60) + zoomInControl.centerY(x: (rotateControl.isHidden ? alignControl.frame.minX - 60 : rotateControl.frame.minX) - zoomInControl.frame.width) + zoomOutControl.centerY(x: zoomInControl.frame.minX - zoomOutControl.frame.width) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} + + +class GalleryModernControls: GenericViewController { + private let context: AccountContext + private let interactions: GalleryInteractions + private let thumbs: GalleryThumbsControl + private let peerDisposable = MetaDisposable() + init(_ context: AccountContext, interactions: GalleryInteractions, frame: NSRect, thumbsControl: GalleryThumbsControl) { + self.context = context + self.interactions = interactions + thumbs = thumbsControl + super.init(frame: frame) + } + + override func viewDidLoad() { + super.viewDidLoad() + genericView.thumbs = thumbs.genericView + genericView.interactions = interactions + + thumbs.afterLayoutTransition = { [weak self] animated in + guard let `self` = self else { return } + self.thumbs.genericView.change(pos: NSMakePoint((self.frame.width - self.thumbs.frame.width) / 2 + (self.thumbs.frame.width - self.thumbs.genericView.documentSize.width) / 2, (self.frame.height - self.thumbs.frame.height) / 2), animated: animated) + } + } + + deinit { + peerDisposable.dispose() + } + + + func update(_ entry: GalleryEntry?) { + if let entry = entry { + if let interfaceState = entry.interfaceState { + self.genericView.updateControlsVisible(entry) + peerDisposable.set((context.account.postbox.loadedPeerWithId(interfaceState.0) |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let `self` = self else {return} + self.genericView.updatePeer(peer, timestamp: interfaceState.1 == 0 ? 0 : interfaceState.1 - self.context.timeDifference, account: self.context.account, canShare: entry.canShare) + })) + } + } + } + + + + + func animateIn() { + genericView.change(pos: NSMakePoint(0, 0), animated: true, timingFunction: CAMediaTimingFunctionName.spring) + } + + func animateOut() { + genericView.change(pos: NSMakePoint(0, -frame.height), animated: true, timingFunction: CAMediaTimingFunctionName.spring) + } +} diff --git a/Telegram-Mac/GalleryPageController.swift b/Telegram-Mac/GalleryPageController.swift index 230f983587..7131b0ad1f 100644 --- a/Telegram-Mac/GalleryPageController.swift +++ b/Telegram-Mac/GalleryPageController.swift @@ -8,23 +8,151 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit import AVFoundation +import AVKit +import TelegramCore +import SyncCore +import Postbox +fileprivate class GMagnifyView : MagnifyView { + private var progressView: RadialProgressView? -fileprivate extension MagnifyView { + fileprivate let statusDisposable = MetaDisposable() + var minX:CGFloat { if contentView.frame.minX > 0 { return frame.minX + contentView.frame.minX } return frame.minX } + + override var isOpaque: Bool { + return true + } + + func updateStatus(_ status: Signal) { + statusDisposable.set((status |> deliverOnMainQueue).start(next: { [weak self] status in + self?.updateProgress(status) + })) + } + private func updateProgress(_ status: MediaResourceStatus) { + + switch status { + case let .Fetching(_, progress): + if self.progressView == nil { + self.progressView = RadialProgressView() + self.addSubview(self.progressView!) + self.progressView!.center() + } + progressView?.state = .ImpossibleFetching(progress: progress, force: false) + case .Local: + + if let progressView = self.progressView { + progressView.state = .ImpossibleFetching(progress: 1, force: false) + self.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] complete in + if complete { + progressView?.removeFromSuperview() + } + }) + } + case .Remote: + if self.progressView == nil { + self.progressView = RadialProgressView() + self.addSubview(self.progressView!) + self.progressView!.center() + } + progressView?.state = .Remote + } + + progressView?.userInteractionEnabled = status != .Local + } + + override func mouseInside() -> Bool { + return super.mouseInside() + } + + deinit { + statusDisposable.dispose() + } + + private let fillFrame:(GMagnifyView)->NSRect + private let prevAction:()->Void + private let nextAction:()->Void + private let hasPrev:()->Bool + private let hasNext:()->Bool + private let dismiss:()->Void + private let prev: Control + private let next: Control + init(_ contentView: NSView, contentSize: NSSize, prev: Control, next: Control, fillFrame:@escaping(GMagnifyView)->NSRect, prevAction: @escaping()->Void, nextAction:@escaping()->Void, hasPrev: @escaping()->Bool, hasNext:@escaping()->Bool, dismiss:@escaping()->Void) { + self.fillFrame = fillFrame + self.prevAction = prevAction + self.nextAction = nextAction + self.prev = prev + self.next = next + self.hasPrev = hasPrev + self.hasNext = hasNext + self.dismiss = dismiss + super.init(contentView, contentSize: contentSize) + prev.alphaValue = 0 + next.alphaValue = 0 + + } + + override var contentSize: NSSize { + didSet { + if frame.origin != NSZeroPoint { + self.frame = fillFrame(self) + } + } + } + + + override func mouseUp(with theEvent: NSEvent) { + let point = convert(theEvent.locationInWindow, from: nil) + + if point.x > frame.width - 80 && self.hasNext() { + nextAction() + } else if point.x < 80 && self.hasPrev() { + prevAction() + } else { + dismiss() + } + } + + func hideOrShowControls(hasPrev: Bool, hasNext: Bool, animated: Bool) { + guard let window = window as? Window else {return} + + let point = convert(window.mouseLocationOutsideOfEventStream, from: nil) + (animated ? prev.animator() : prev).alphaValue = point.x > 80 || !hasPrev ? 0 : 1 + (animated ? next.animator() : next).alphaValue = point.x < frame.width - 80 || !hasNext ? 0 : 1 + } + + override func add(magnify: CGFloat, for location: NSPoint, animated: Bool) { + super.add(magnify: magnify, for: location, animated: animated) + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + progressView?.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } class GalleryPageView : NSView { init() { super.init(frame:NSZeroRect) - //self.wantsLayer = true + self.wantsLayer = true + self.canDrawSubviewsIntoLayer = true + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) } @@ -34,10 +162,20 @@ class GalleryPageView : NSView { } +private final class PageController : NSPageController { + + + deinit { + var bp:Int = 0 + bp += 1 + } +} + class GalleryPageController : NSObject, NSPageControllerDelegate { - private let controller:NSPageController = NSPageController() + private let controller:PageController = PageController() private let ioDisposabe:MetaDisposable = MetaDisposable() + private let smartUpdaterDisposable = MetaDisposable() private var identifiers:[NSPageController.ObjectIdentifier:MGalleryItem] = [:] private var cache:NSCache = NSCache() private var queuedTransitions:[UpdateTransition] = [] @@ -52,38 +190,145 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { private var startIndex:Int = -1 let view:GalleryPageView = GalleryPageView() private let captionView: TextView = TextView() + private let captionContainer = View() + private let captionScrollView = ScrollView() private let window:Window + private let autohideCaptionDisposable = MetaDisposable() + private let magnifyDisposable = MetaDisposable() let selectedIndex:ValuePromise = ValuePromise(ignoreRepeated: false) - init(frame:NSRect, contentInset:NSEdgeInsets, interactions:GalleryInteractions, window:Window) { + let thumbsControl: GalleryThumbsControl + private let indexDisposable = MetaDisposable() + fileprivate let reversed: Bool + private let navigationDisposable = MetaDisposable() + private let _prev: ImageButton = ImageButton() + private let _next: ImageButton = ImageButton() + + private var hasInited: Bool = false + + var selectedItemChanged: ((@escaping(MGalleryItem) -> Void)->Void)! + var transition: ((@escaping(UpdateTransition, MGalleryItem?) -> Void) -> Void)! + + private var transitionCallFunc: ((UpdateTransition, MGalleryItem?) -> Void)? + private var selectedItemCallFunc: ((MGalleryItem) -> Void)? + private let interactions: GalleryInteractions + init(frame:NSRect, contentInset:NSEdgeInsets, interactions:GalleryInteractions, window:Window, reversed: Bool) { self.contentInset = contentInset self.window = window - + self.reversed = reversed + thumbsControl = GalleryThumbsControl(interactions: interactions) + self.interactions = interactions super.init() + + self.selectedItemChanged = { [weak self] selectedItemCallFunc in + self?.selectedItemCallFunc = selectedItemCallFunc + } + + self.transition = { [weak self] transitionCallFunc in + self?.transitionCallFunc = transitionCallFunc + } + + _prev.animates = true + _next.animates = true + + _prev.autohighlight = false + _next.autohighlight = false + _prev.set(image: theme.icons.galleryPrev, for: .Normal) + _next.set(image: theme.icons.galleryNext, for: .Normal) + + _prev.set(background: .clear, for: .Normal) + _next.set(background: .clear, for: .Normal) + + + + _prev.frame = NSMakeRect(0, 0, 60, frame.height) + _next.frame = NSMakeRect(frame.width - 60, 0, 60, frame.height) + + _next.userInteractionEnabled = false + _prev.userInteractionEnabled = false + + captionContainer.addSubview(captionView) + captionScrollView.documentView = captionContainer + + indexDisposable.set((selectedIndex.get()).start(next: { [weak self] index in + guard let `self` = self else {return} + + let transition = self.thumbsControl.layoutItems(with: self.items, selectedIndex: index, animated: true) + self.transitionCallFunc?(transition, self.selectedItem) + })) + cache.countLimit = 10 + captionView.isSelectable = false + captionView.userInteractionEnabled = true - window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - if let view = self?.controller.selectedViewController?.view as? MagnifyView, let window = view.window { + var dragged: NSPoint? = nil + + window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self, !hasModals(self.window) else {return .rejected} + + if let view = self.controller.selectedViewController?.view as? GMagnifyView { + if let view = view.contentView as? SVideoView, view.insideControls { + dragged = nil + return .invokeNext + } - let point = window.mouseLocationOutsideOfEventStream - if point.x < view.minX && !view.mouseInContent && view.magnify == 1.0 { - _ = interactions.previous() - } else if view.mouseInContent && view.magnify == 1.0 { - _ = interactions.next() - } else { - let hitTestView = window.contentView?.hitTest(point) - if hitTestView is GalleryBackgroundView || view.contentView == hitTestView?.subviews.first { - _ = interactions.dismiss() - - } else { - return .invokeNext - } + } + + if let _dragged = dragged { + let difference = NSMakePoint(abs(_dragged.x - event.locationInWindow.x), abs(_dragged.y - event.locationInWindow.y)) + if difference.x >= 10 || difference.y >= 10 { + dragged = nil + return .invoked + } + } + dragged = nil + + let view = self.window.contentView?.hitTest(event.locationInWindow) + let point = self.controller.view.convert(event.locationInWindow, from: nil) + + if NSPointInRect(point, self.captionScrollView.frame), self.captionScrollView.layer?.opacity != 0, let captionLayout = self.captionView.layout, captionLayout.link(at: self.captionView.convert(event.locationInWindow, from: nil)) != nil { + self.captionView.mouseUp(with: event) + return .invoked + } else if self.captionView.mouseInside() { + return .invoked + } + if let view = self.controller.selectedViewController?.view as? GMagnifyView, let window = view.window as? Window, self.controller.view._mouseInside() { + guard event.locationInWindow.x > 80 && event.locationInWindow.x < window.frame.width - 80 else { + view.mouseUp(with: event) + return .invoked } + if let view = view.contentView as? SVideoView, view.insideControls { + return .rejected + } + if hasPictureInPicture { + return .rejected + } + + _ = interactions.dismiss() + return .invoked + } else if view is GalleryModernControlsView { + _ = interactions.dismiss() + return .invoked } - return .invoked + return .invokeNext }, with: self, for: .leftMouseUp) + window.set(mouseHandler: { event -> KeyHandlerResult in + dragged = nil + return .rejected + }, with: self, for: .leftMouseDown) + + window.set(mouseHandler: { event -> KeyHandlerResult in + guard dragged == nil else {return .rejected} + dragged = event.locationInWindow + return .rejected + }, with: self, for: .leftMouseDragged) + + window.set(responder: { [weak self] () -> NSResponder? in + return self?.controller.selectedViewController?.view + }, with: self, priority: .high) + window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in if let strongSelf = self { if strongSelf.lockedTransition { @@ -95,9 +340,20 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { return .invoked }, with: self, for: .scrollWheel) + window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + self.autohideCaptionDisposable.set(nil) + if self.lockedTransition == false { + self.captionScrollView.change(opacity: 1.0) + self.configureCaptionAutohide() + } + (self.controller.selectedViewController?.view as? GMagnifyView)?.hideOrShowControls(hasPrev: self.hasPrev, hasNext: self.hasNext, animated: true) + return .rejected + }, with: self, for: .mouseMoved) + window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - if let view = self?.controller.selectedViewController?.view as? MagnifyView, let window = view.window { + if let view = self?.controller.selectedViewController?.view as? GMagnifyView, let window = view.window { let point = window.mouseLocationOutsideOfEventStream let hitTestView = window.contentView?.hitTest(point) @@ -113,27 +369,51 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { return .invoked }, with: self, for: .rightMouseUp) - // view.background = .blackTransparent controller.view = view controller.view.frame = frame controller.delegate = self controller.transitionStyle = .horizontalStrip } - func merge(with transition:UpdateTransition) -> Bool { + private func configureCaptionAutohide() { + let view = controller.selectedViewController?.view as? MagnifyView + if captionScrollView.superview != nil { + captionScrollView.removeFromSuperview() + controller.view.addSubview(captionScrollView) + } + autohideCaptionDisposable.set((Signal.single(Void()) |> delay(view?.mouseInContent == true ? 5.0 : 1.5, queue: Queue.mainQueue())).start(next: { [weak self] in + guard let `self` = self else { + return + } + self.captionScrollView.change(opacity: self.captionScrollView._mouseInside() ? 1 : 0) + })) + } + + var items: [MGalleryItem] { + return controller.arrangedObjects.map {$0 as! MGalleryItem} + } + + private var afterTransaction:(()->Void)? = nil + + func merge(with transition:UpdateTransition, afterTransaction:(()->Void)? = nil) -> Bool { queuedTransitions.append(transition) + self.afterTransaction = afterTransaction return enqueueTransitions() } var isFullScreen: Bool { if let view = controller.selectedViewController?.view as? MagnifyView { - if view.contentView.frame.size == window.frame.size { + if view.contentView.window !== window { return true } } return false } + func exitFullScreen() { + self.selectedItem?.toggleFullScreen() + } + var itemView:NSView? { return (controller.selectedViewController?.view as? MagnifyView)?.contentView } @@ -143,13 +423,14 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { let wasInited = !controller.arrangedObjects.isEmpty let item: MGalleryItem? = !controller.arrangedObjects.isEmpty ? self.item(at: controller.selectedIndex) : nil - + let animated = !self.items.isEmpty var items:[MGalleryItem] = controller.arrangedObjects as! [MGalleryItem] + items = reversed ? items.reversed() : items while !queuedTransitions.isEmpty { let transition = queuedTransitions[0] - let searchItem:(AnyHashable)->MGalleryItem? = { stableId in + let searchItem:(AnyHashable, [MGalleryItem])->MGalleryItem? = { stableId, items in for item in items { if item.stableId == stableId { return item @@ -159,58 +440,109 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { } for rdx in transition.deleted.reversed() { - let item = items[rdx] - identifiers.removeValue(forKey: item.identifier) - items.remove(at: rdx) + if items.count > rdx { + let item = items[rdx] + identifiers.removeValue(forKey: item.identifier) + cache.removeObject(forKey: item.identifier as AnyObject) + items.remove(at: rdx) + } } for (idx,item) in transition.inserted { - let item = searchItem(item.stableId) ?? item + let item = searchItem(item.stableId, items) ?? item identifiers[item.identifier] = item - items.insert(item, at: idx) + items.insert(item, at: min(idx, items.count)) } for (idx,item) in transition.updated { - let item = searchItem(item.stableId) ?? item + let item = searchItem(item.stableId, items) ?? item identifiers[item.identifier] = item - items[idx] = item + cache.removeObject(forKey: item.identifier as AnyObject) + if idx < items.count { + items[idx] = item + } } - queuedTransitions.removeFirst() } - if items.count > 0 { - controller.arrangedObjects = items - - if let item = item { - for i in 0 ..< items.count { - if item.identifier == items[i].identifier { - if controller.selectedIndex != i { - controller.selectedIndex = i + items = reversed ? items.reversed() : items + + if self.items != items { + + if items.count > 0 { + + let selectedItem = self.selectedItem + + controller.arrangedObjects = items + controller.completeTransition() + + if let item = item { + for i in 0 ..< items.count { + if item.identifier == items[i].identifier { + if controller.selectedIndex != i { + controller.selectedIndex = i + } + break } - break + } + } + if wasInited { + items[controller.selectedIndex].request(immediately: false) + if selectedItem != self.selectedItem { + self.selectedItem?.appear(for: controller.selectedViewController?.view) } } } if wasInited { - items[controller.selectedIndex].request(immediately: false) + transitionCallFunc?(self.thumbsControl.layoutItems(with: self.items, selectedIndex: controller.selectedIndex, animated: animated), selectedItem) } } + if let item = selectedItem { + (controller.selectedViewController?.view as? GMagnifyView)?.updateStatus(item.status) + } + afterTransaction?() + return items.isEmpty } return false } + var hasNext: Bool { + return controller.selectedIndex < controller.arrangedObjects.count - 1 + } + var hasPrev: Bool { + return controller.selectedIndex > 0 + } + func next() { if !lockedTransition { - set(index: min(controller.selectedIndex + 1, controller.arrangedObjects.count - 1), animated: false) + if let item = self.selectedItem as? MGalleryVideoItem, item.isFullscreen { + return + } + let item = self.item(at: min(controller.selectedIndex + 1, controller.arrangedObjects.count - 1)) + item.request() + + if let index = self.items.firstIndex(of: item) { + self.set(index: index, animated: false) + } } } func prev() { if !lockedTransition { - set(index: max(controller.selectedIndex - 1, 0), animated: false) + if let item = self.selectedItem as? MGalleryVideoItem, item.isFullscreen { + return + } + let item = self.item(at: max(controller.selectedIndex - 1, 0)) + item.request() + if let index = self.items.firstIndex(of: item) { + self.set(index: index, animated: false) + } } } + var currentIndex: Int { + return controller.selectedIndex + } + func zoomIn() { if let magnigy = controller.selectedViewController?.view as? MagnifyView { magnigy.zoomIn() @@ -223,6 +555,35 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { } } + func decreaseSpeed() { + + } + + func increaseSpeed() { + + } + + + func rotateLeft() { + guard let item = self.selectedItem else {return} + item.disableAnimations = true + + _ = (item.rotate.get() |> take(1)).start(next: { [weak item] orientation in + if let orientation = orientation { + switch orientation { + case .right: + item?.rotate.set(.down) + case .down: + item?.rotate.set(.left) + default: + item?.rotate.set(nil) + } + } else { + item?.rotate.set(.right) + } + }) + } + func set(index:Int, animated:Bool) { _ = enqueueTransitions() @@ -240,9 +601,17 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { } else { if controller.selectedIndex != index { controller.selectedIndex = index + pageControllerDidEndLiveTransition(controller, force:true) + } else if currentController == nil { + selectedIndex.set(index) } - pageControllerDidEndLiveTransition(controller, force:true) currentController = controller.selectedViewController + + } + + if items.count > 1, hasInited { + items[min(max(self.controller.selectedIndex - 1, 0), items.count - 1)].request() + items[min(max(self.controller.selectedIndex + 1, 0), items.count - 1)].request() } } } @@ -255,46 +624,88 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { view.minMagnify = item.minMagnify view.maxMagnify = item.maxMagnify + + item.size.set(.single(item.sizeValue)) item.view.set(.single(view.contentView)) - item.size.set(view.smartUpdater.get()) + +// smartUpdaterDisposable.set(view.smartUpdaterValue.start(next: { size in +// item.size.set(.single(size)) +// })) + } } func pageControllerWillStartLiveTransition(_ pageController: NSPageController) { lockedTransition = true + captionScrollView.change(opacity: 0) startIndex = pageController.selectedIndex + + if items.count > 1 { + items[min(max(pageController.selectedIndex - 1, 0), items.count - 1)].request() + items[min(max(pageController.selectedIndex + 1, 0), items.count - 1)].request() + } } func pageControllerDidEndLiveTransition(_ pageController: NSPageController, force:Bool) { let previousView = currentController?.view as? MagnifyView - if startIndex != pageController.selectedIndex { - if startIndex > 0 && startIndex < pageController.arrangedObjects.count { + + captionScrollView.change(opacity: 0, animated: captionScrollView.superview != nil, completion: { [weak captionScrollView] completed in + if completed { + captionScrollView?.removeFromSuperview() + } + }) + + + if startIndex != pageController.selectedIndex || force { + if startIndex != -1, startIndex <= pageController.arrangedObjects.count - 1 { self.item(at: startIndex).disappear(for: previousView?.contentView) } startIndex = pageController.selectedIndex - -// if let caption = item.caption { -// caption.measure(width: item.sizeValue.width) -// captionView.update(caption) -// captionView.setFrameSize(captionView.frame.size.width + 10, captionView.frame.size.height + 8) -// (pageController.selectedViewController?.view as? MagnifyView)?.contentView.addSubview(captionView) -// captionView.centerX(y: 10) -// } else { -// captionView.removeFromSuperview() -// } -// + + pageController.completeTransition() - if let controllerView = pageController.selectedViewController?.view as? MagnifyView, previousView != controllerView || force { + if let controllerView = pageController.selectedViewController?.view as? GMagnifyView, previousView != controllerView || force { + controllerView.hideOrShowControls(hasPrev: hasPrev, hasNext: hasNext, animated: !force) let item = self.item(at: startIndex) - item.appear(for: controllerView.contentView) + if hasInited { + item.appear(for: controllerView.contentView) + + } controllerView.frame = view.focus(contentFrame.size, inset:contentInset) + magnifyDisposable.set(controllerView.magnifyUpdaterValue.start(next: { [weak self] value in + self?.captionScrollView.isHidden = value > 1.0 + })) + } + } + + let item = self.item(at: pageController.selectedIndex) + if let caption = item.caption { + caption.measure(width: min(item.sizeValue.width + 240, min(item.pagerSize.width - 200, 600))) + captionView.update(caption) + captionView.backgroundColor = .clear + captionView.disableBackgroundDrawing = true + view.addSubview(captionScrollView) + captionScrollView.change(opacity: 1.0) + captionScrollView.setFrameSize(captionView.frame.size.width + 10, min(90, captionView.frame.height)) + captionScrollView.centerX(y: 100) + captionScrollView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.9).cgColor + captionScrollView.layer?.cornerRadius = .cornerRadius + + captionContainer.frame = NSMakeRect(0, 0, captionScrollView.frame.width, captionView.frame.height) + captionView.center() + + } else { + captionView.update(nil) + captionScrollView.removeFromSuperview() } + + configureCaptionAutohide() } private var currentController:NSViewController? @@ -302,9 +713,9 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { func pageControllerDidEndLiveTransition(_ pageController: NSPageController) { pageControllerDidEndLiveTransition(pageController, force:false) currentController = pageController.selectedViewController - if let view = pageController.view as? MagnifyView { - window.makeFirstResponder(view.contentView) - } + // if let view = pageController.view as? MagnifyView { + // window.makeFirstResponder(view) + // } lockedTransition = false } @@ -322,15 +733,17 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { var contentFrame:NSRect { - return NSMakeRect(frame.minX + contentInset.left, frame.minY + contentInset.top, frame.width - contentInset.left - contentInset.right, frame.height - contentInset.top - contentInset.bottom) + let rect = NSMakeRect(frame.minX + contentInset.left, frame.minY + contentInset.top, frame.width - contentInset.left - contentInset.right, frame.height - contentInset.top - contentInset.bottom) + + return rect } func pageController(_ pageController: NSPageController, frameFor object: Any?) -> NSRect { if let object = object { let item = self.item(for: object) let size = item.sizeValue - - return view.focus(size.fitted(contentFrame.size), inset:contentInset) + + return view.focus(size.fitted(contentFrame.size), inset:self.contentInset) } return view.bounds } @@ -343,10 +756,33 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { let item = identifiers[identifier]! let view = item.singleView() view.wantsLayer = true - view.background = theme.colors.background - controller.view = MagnifyView(view, contentSize:item.sizeValue) + let magnify = GMagnifyView(view, contentSize:item.sizeValue, prev: _prev, next: _next, fillFrame: { [weak self] view in + guard let `self` = self else {return NSZeroRect} + + return self.view.focus(self.contentFrame.size, inset: self.contentInset) + }, prevAction: { [weak self] in + self?.prev() + }, nextAction: { [weak self] in + self?.next() + }, hasPrev: { [weak self] in + return self?.hasPrev ?? false + }, hasNext: { [weak self] in + return self?.hasNext ?? false + }, dismiss: { [weak self] in + _ = self?.interactions.dismiss() + }) + item.magnify.set(magnify.magnifyUpdaterValue |> deliverOnPrepareQueue) + controller.view = magnify + if hasInited { + item.request() + } + magnify.updateStatus(item.status) cache.setObject(controller, forKey: identifier as AnyObject) - item.request() + + if selectedItem == item, hasInited { + // item.view.set(.single(magnify.contentView)) + item.appear(for: magnify.contentView) + } return controller } } @@ -364,6 +800,12 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { return object as! MGalleryItem } + func select(by item: MGalleryItem) -> Void { + if let index = index(for: item), !lockedTransition { + set(index: index, animated: false) + } + } + func index(for item:MGalleryItem) -> Int? { for i in 0 ..< controller.arrangedObjects.count { if let _item = controller.arrangedObjects[i] as? MGalleryItem { @@ -386,128 +828,187 @@ class GalleryPageController : NSObject, NSPageControllerDelegate { return nil } - func animateIn( from:@escaping(AnyHashable)->NSView?, completion:(()->Void)? = nil) ->Void { + func hideVideoControls() -> Bool { + if let item = self.selectedItem as? MGalleryVideoItem { + return item.hideControls() + } + return false + } + + func animateIn( from:@escaping(AnyHashable)->NSView?, completion:(()->Void)? = nil, addAccesoryOnCopiedView:(((AnyHashable?, NSView))->Void)? = nil, addVideoTimebase:(((AnyHashable, NSView))->Void)? = nil, showBackground:(()->Void)? = nil) ->Void { + + window.contentView?.addSubview(_prev) + window.contentView?.addSubview(_next) + captionScrollView.change(opacity: 0, animated: false) if let selectedView = controller.selectedViewController?.view as? MagnifyView, let item = self.selectedItem { + item.request() lockedTransition = true - if let oldView = from(item.stableId), let oldWindow = oldView.window { + if let oldView = from(item.stableId), let oldWindow = oldView.window, let oldScreen = oldWindow.screen { selectedView.isHidden = true - ioDisposabe.set((item.image.get() |> take(1)).start(next: { [weak self, weak selectedView] image in + ioDisposabe.set((item.image.get() |> map { $0.value } |> take(1) |> timeout(0.7, queue: Queue.mainQueue(), alternate: .single(.image(nil, nil)))).start(next: { [weak self, weak oldView, weak selectedView] value in + - if let view = self?.view, let contentInset = self?.contentInset, let contentFrame = self?.contentFrame { - let newRect = view.focus(item.sizeValue.fitted(contentFrame.size), inset:contentInset) - let oldRect = oldWindow.convertToScreen(oldView.convert(oldView.bounds, to: nil)) - + if let view = self?.view, let contentInset = self?.contentInset, let contentFrame = self?.contentFrame, let oldView = oldView { + let newRect = view.focus(item.sizeValue.fitted(contentFrame.size), inset: contentInset) + var oldRect = oldWindow.convertToScreen(oldView.convert(oldView.bounds, to: nil)) + oldRect.origin = oldRect.origin.offsetBy(dx: -oldScreen.frame.minX, dy: -oldScreen.frame.minY) selectedView?.contentSize = item.sizeValue.fitted(contentFrame.size) - - if let _ = image, let strongSelf = self { - self?.animate(oldRect: oldRect, newRect: newRect, newAlphaFrom: 0, newAlphaTo:1, oldAlphaFrom: 1, oldAlphaTo:0, contents: image, oldView: oldView, completion: { [weak strongSelf] in + if value.hasValue, let strongSelf = self { + self?.animate(oldRect: oldRect, newRect: newRect, newAlphaFrom: 0, newAlphaTo:1, oldAlphaFrom: 1, oldAlphaTo:0, contents: value, oldView: oldView, completion: { [weak strongSelf, weak selectedView] in selectedView?.isHidden = false + strongSelf?.captionScrollView.change(opacity: 1.0) + strongSelf?.hasInited = true + strongSelf?.selectedItem?.appear(for: selectedView?.contentView) strongSelf?.lockedTransition = false - }) + }, stableId: item.stableId, addAccesoryOnCopiedView: addAccesoryOnCopiedView) } else { selectedView?.isHidden = false + self?.hasInited = true + self?.selectedItem?.appear(for: selectedView?.contentView) self?.lockedTransition = false } + if let selectedView = selectedView { + addVideoTimebase?((item.stableId, selectedView.contentView)) + } } - + showBackground?() + completion?() })) } else { - ioDisposabe.set((item.image.get() |> take(1)).start(next: { [weak self] image in - //selectedView?.isHidden = false - self?.lockedTransition = false - if let completion = completion { - completion() + ioDisposabe.set((item.image.get() |> take(1)).start(next: { [weak self, weak selectedView] image in + if let selectedView = selectedView { + selectedView.isHidden = false + selectedView.swapView(selectedView.contentView) + self?.lockedTransition = false + self?.hasInited = true + self?.selectedItem?.appear(for: selectedView.contentView) + if let completion = completion { + completion() + self?.window.applyResponderIfNeeded() + } } + })) } } } - func animate(oldRect:NSRect, newRect:NSRect, newAlphaFrom:CGFloat, newAlphaTo:CGFloat, oldAlphaFrom:CGFloat, oldAlphaTo:CGFloat, contents:CGImage?, oldView:NSView, completion:@escaping ()->Void) { + func animate(oldRect:NSRect, newRect:NSRect, newAlphaFrom:CGFloat, newAlphaTo:CGFloat, oldAlphaFrom:CGFloat, oldAlphaTo:CGFloat, contents:GPreviewValue, oldView:NSView, completion:@escaping ()->Void, stableId: AnyHashable, addAccesoryOnCopiedView:(((AnyHashable?, NSView))->Void)? = nil) { lockedTransition = true let view = self.view - let newView:NSView = NSView(frame: oldRect) - newView.wantsLayer = true - newView.layer?.opacity = Float(newAlphaFrom) - newView.layer?.contents = contents - newView.layer?.backgroundColor = theme.colors.background.cgColor + + let newView:NSView // + + switch contents { + case let .image(contents, _): + newView = NSView(frame: newRect) + newView.wantsLayer = true + newView.layer?.contents = contents + case let .view(view): + newView = view ?? NSView(frame: newRect) + newView.frame = newRect + } + let copyView = oldView.copy() as! NSView - copyView.layer?.backgroundColor = theme.colors.background.cgColor - copyView.frame = oldRect + addAccesoryOnCopiedView?((stableId, copyView)) + + copyView.frame = NSMakeRect(oldRect.minX, oldRect.minY, oldAlphaFrom == 0 ? newRect.width : oldRect.width, oldAlphaFrom == 0 ? newRect.height : oldRect.height) copyView.wantsLayer = true - copyView.layer?.opacity = Float(oldAlphaFrom) view.addSubview(newView) view.addSubview(copyView) + + CATransaction.begin() - let duration:Double = 0.2 + let duration:Double = 0.25 + + let timingFunction: CAMediaTimingFunctionName = .spring + - newView._change(pos: newRect.origin, animated: true, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - newView._change(size: newRect.size, animated: true, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - newView._change(opacity: newAlphaTo, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - copyView._change(pos: newRect.origin, animated: true, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - copyView._change(size: newRect.size, animated: true, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) - copyView._change(opacity: oldAlphaTo, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) { [weak self] _ in + + newView.layer?.animatePosition(from: oldRect.origin, to: newRect.origin, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + newView.layer?.animateAlpha(from: newAlphaFrom, to: newAlphaTo, duration: duration / 2, timingFunction: timingFunction, removeOnCompletion: false) + + + newView.layer?.animateScaleX(from: oldRect.width / newRect.width, to: 1, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + newView.layer?.animateScaleY(from: oldRect.height / newRect.height, to: 1, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + + + copyView.layer?.animatePosition(from: oldRect.origin, to: newRect.origin, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + copyView.layer?.animateScaleX(from: oldAlphaFrom == 0 ? oldRect.width / newRect.width : 1, to: oldAlphaFrom != 0 ? newRect.width / oldRect.width : 1, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + copyView.layer?.animateScaleY(from: oldAlphaFrom == 0 ? oldRect.height / newRect.height : 1, to: oldAlphaFrom != 0 ? newRect.height / oldRect.height : 1, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + + copyView.layer?.animateAlpha(from: oldAlphaFrom , to: oldAlphaTo, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak self, weak copyView, weak newView] _ in completion() self?.lockedTransition = false - if let strongSelf = self { - newView.removeFromSuperview() - copyView.removeFromSuperview() - Queue.mainQueue().after(0.1, { [weak strongSelf] in - if let view = strongSelf?.controller.selectedViewController?.view as? MagnifyView { - strongSelf?.window.makeFirstResponder(view) - } - }) - } - } + newView?.removeFromSuperview() + copyView?.removeFromSuperview() + }) CATransaction.commit() } - func animateOut( to:@escaping(AnyHashable)->NSView?, completion:(()->Void)? = nil) ->Void { + func animateOut( to:@escaping(AnyHashable)->NSView?, completion:(((Bool, AnyHashable?))->Void)? = nil, addAccesoryOnCopiedView:(((AnyHashable?, NSView))->Void)? = nil, addVideoTimebase:(((AnyHashable, NSView))->Void)? = nil) ->Void { lockedTransition = true + + + captionScrollView.change(opacity: 0, animated: true) + if let selectedView = controller.selectedViewController?.view as? MagnifyView, let item = selectedItem { selectedView.isHidden = true item.disappear(for: selectedView.contentView) - if let oldView = to(item.stableId), let window = oldView.window { - let newRect = window.convertToScreen(oldView.convert(oldView.bounds, to: nil)) + if let oldView = to(item.stableId), let window = oldView.window, let screen = window.screen { + var newRect = window.convertToScreen(oldView.convert(oldView.bounds, to: nil)) + newRect.origin = newRect.origin.offsetBy(dx: -screen.frame.minX, dy: -screen.frame.minY) let oldRect = view.focus(item.sizeValue.fitted(contentFrame.size), inset:contentInset) - ioDisposabe.set((item.image.get() |> take(1)).start(next: { [weak self] (image) in - self?.animate(oldRect: oldRect, newRect: newRect, newAlphaFrom: 1, newAlphaTo:0, oldAlphaFrom: 0, oldAlphaTo: 1, contents: image, oldView: oldView, completion: { - completion?() - }) - + ioDisposabe.set((item.image.get() |> map { $0.value } |> take(1) |> timeout(0.1, queue: Queue.mainQueue(), alternate: .single(.image(nil, nil)))).start(next: { [weak self, weak item] value in + if let item = item { + self?.animate(oldRect: oldRect, newRect: newRect, newAlphaFrom: 1, newAlphaTo:0, oldAlphaFrom: 0, oldAlphaTo: 1, contents: value, oldView: oldView, completion: { [weak item] in + if let item = item { + completion?((true, item.stableId)) + } + }, stableId: item.stableId, addAccesoryOnCopiedView: addAccesoryOnCopiedView) + } })) + + addVideoTimebase?((item.stableId, selectedView.contentView)) + } else { view._change(opacity: 0, completion: { (_) in - completion?() + completion?((false, item.stableId)) }) } } else { view._change(opacity: 0, completion: { (_) in - completion?() + completion?((false, nil)) }) } } deinit { - window.remove(object: self, for: .leftMouseUp) + window.removeAllHandlers(for: self) ioDisposabe.dispose() + navigationDisposable.dispose() + autohideCaptionDisposable.dispose() + magnifyDisposable.dispose() + indexDisposable.dispose() + smartUpdaterDisposable.dispose() + cache.removeAllObjects() } } diff --git a/Telegram-Mac/GalleryThumbsControl.swift b/Telegram-Mac/GalleryThumbsControl.swift new file mode 100644 index 0000000000..ca8c3035de --- /dev/null +++ b/Telegram-Mac/GalleryThumbsControl.swift @@ -0,0 +1,151 @@ +// +// GalleryThumbsControl.swift +// Telegram +// +// Created by keepcoder on 10/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore + +class GalleryThumbsControl: ViewController { + private let interactions: GalleryInteractions + + var afterLayoutTransition:((Bool)->Void)? = nil + + init(interactions: GalleryInteractions) { + self.interactions = interactions + super.init(frame: NSMakeRect(0, 0, 400, 80)) + } + + private(set) var items:[MGalleryItem] = [] + + + func layoutItems(with items: [MGalleryItem], selectedIndex selected: Int, animated: Bool) -> UpdateTransition { + + let current: MGalleryItem? = selected > items.count - 1 || selected < 0 ? nil : items[selected] + + var newItems:[MGalleryItem] = [] + + var isForceInstant: Bool = false + for item in items { + if case .instantMedia = item.entry { + isForceInstant = true + newItems = items + break + } + } + + if !isForceInstant, let current = current { + switch current.entry { + case .message(let entry): + + if let message = entry.message { + if let groupInfo = message.groupInfo { + + newItems.append(current) + + var next: Int = selected + 1 + var prev: Int = selected - 1 + + var prevFilled: Bool = prev < 0 + var nextFilled: Bool = next >= items.count + + while !prevFilled || !nextFilled { + if !prevFilled { + prevFilled = items[prev].entry.message?.groupInfo != groupInfo + if !prevFilled { + newItems.insert(items[prev], at: 0) + } + prev -= 1 + } + if !nextFilled { + nextFilled = items[next].entry.message?.groupInfo != groupInfo + if !nextFilled { + newItems.append(items[next]) + } + next += 1 + } + + prevFilled = prevFilled || prev < 0 + nextFilled = nextFilled || next >= items.count + + } + } + } + + case .instantMedia: + newItems = items + case .photo: + newItems = items + case .secureIdDocument: + newItems = items + } + } + + if newItems.count == 1 { + newItems = [] + } + + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: self.items, rightList: newItems) + + + + for rdx in deleteIndices.reversed() { + genericView.removeItem(at: rdx, animated: animated) + self.items.remove(at: rdx) + } + + + for (idx, item, _) in indicesAndItems { + genericView.insertItem(item, at: idx, isSelected: current?.stableId == item.stableId, animated: animated, callback: { [weak self] item in + self?.interactions.select(item) + }) + self.items.insert(item, at: idx) + } + for (idx, item, _) in updateIndices { + let item = item + genericView.updateItem(item, at: idx) + self.items[idx] = item + } + + for i in 0 ..< self.items.count { + if current?.stableId == self.items[i].stableId { + genericView.layoutItems(selectedIndex: i, animated: animated) + break + } + } + + if self.items.count <= 1 { + genericView.change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.genericView.isHidden = true + } + }) + } else { + genericView.isHidden = false + genericView.change(opacity: 1, animated: animated) + } + + afterLayoutTransition?(animated) + + return UpdateTransition(deleted: deleteIndices, inserted: indicesAndItems.map {($0.0, $0.1)}, updated: updateIndices.map {($0.0, $0.1)}) + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + var genericView:GalleryThumbsControlView { + return view as! GalleryThumbsControlView + } + + override func viewClass() -> AnyClass { + return GalleryThumbsControlView.self + } +} diff --git a/Telegram-Mac/GalleryThumbsControlView.swift b/Telegram-Mac/GalleryThumbsControlView.swift new file mode 100644 index 0000000000..2d231aabc2 --- /dev/null +++ b/Telegram-Mac/GalleryThumbsControlView.swift @@ -0,0 +1,300 @@ +// +// GalleryThumsControllerView.swift +// Telegram +// +// Created by keepcoder on 10/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +private class GalleryThumb { + var signal:Signal? { + var signal:Signal? + if let item = item as? MGalleryPhotoItem { + signal = chatWebpageSnippetPhoto(account: item.context.account, imageReference: item.entry.imageReference(item.media), scale: System.backingScale, small: true, secureIdAccessContext: item.secureIdAccessContext) + } else if let item = item as? MGalleryGIFItem { + signal = chatMessageVideo(postbox: item.context.account.postbox, fileReference: item.entry.fileReference(item.media), scale: System.backingScale) + } else if let item = item as? MGalleryVideoItem { + signal = chatMessageVideo(postbox: item.context.account.postbox, fileReference: item.entry.fileReference(item.media), scale: System.backingScale) + } else if let item = item as? MGalleryPeerPhotoItem { + signal = chatMessagePhoto(account: item.context.account, imageReference: item.entry.imageReference(item.media), scale: System.backingScale) + } + return signal + } + let size: NSSize? + private weak var _view: GalleryThumbContainer? = nil + var selected: Bool = false + var isEnabled: Bool = true + private let callback:(MGalleryItem)->Void + private let item: MGalleryItem + + var frame: NSRect = .zero + + init(_ item: MGalleryItem, callback:@escaping(MGalleryItem)->Void) { + self.callback = callback + self.item = item + + if let item = item as? MGalleryPhotoItem { + item.fetch() + } else if let item = item as? MGalleryPeerPhotoItem { + item.fetch() + } + + var size: NSSize? + if let item = item as? MGalleryPhotoItem { + size = item.media.representations.first?.dimensions.size + } else if let item = item as? MGalleryGIFItem { + size = item.media.videoSize + } else if let item = item as? MGalleryVideoItem { + size = item.media.videoSize + } else if let item = item as? MGalleryPeerPhotoItem { + size = item.media.representations.first?.dimensions.size + } + + self.size = size + } + + var viewSize: NSSize { + if selected { + return NSMakeSize(80, 80) + } else { + return NSMakeSize(40, 80) + } + } + + var view: GalleryThumbContainer { + if _view == nil { + let view = GalleryThumbContainer(self) + view.frame = frame + view.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + self.callback(self.item) + }, for: .Click) + _view = view + return view + } + return _view! + } + + var opt:GalleryThumbContainer? { + return _view + } + + func cleanup() { + _view?.removeFromSuperview() + _view = nil + } + +} + +class GalleryThumbContainer : Control { + + fileprivate let imageView: TransformImageView = TransformImageView() + fileprivate let overlay: View = View() + fileprivate init(_ item: GalleryThumb) { + super.init(frame: NSZeroRect) + backgroundColor = .clear + if let signal = item.signal, let size = item.size { + imageView.setSignal(signal) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize:size.aspectFilled(NSMakeSize(80, 80)), boundingSize: NSMakeSize(80, 80), intrinsicInsets: NSEdgeInsets()) + imageView.set(arguments: arguments) + } + overlay.layer?.opacity = 0.35 + overlay.setFrameSize(80, 80) + imageView.setFrameSize(80, 80) + addSubview(imageView) + addSubview(overlay) + overlay.backgroundColor = .black + layer?.cornerRadius = .cornerRadius + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + override func layout() { + imageView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} + + + +class GalleryThumbsControlView: View { + + private let scrollView: HorizontalScrollView = HorizontalScrollView() + private let documentView: View = View() + private var selectedView: View? + + private var items: [GalleryThumb] = [] + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + backgroundColor = .clear + addSubview(scrollView) + scrollView.documentView = documentView + scrollView.backgroundColor = .clear + scrollView.background = .clear + + documentView.backgroundColor = .clear + + + NotificationCenter.default.addObserver(self, selector: #selector(scrollDidUpdated), name: NSView.boundsDidChangeNotification, object: scrollView.contentView) + NotificationCenter.default.addObserver(self, selector: #selector(scrollDidUpdated), name: NSView.frameDidChangeNotification, object: scrollView) + + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + private var previousRange: NSRange? = nil + + @objc private func scrollDidUpdated() { + + + var range: NSRange = NSMakeRange(NSNotFound, 0) + + let distance:(min: CGFloat, max: CGFloat) = (min: scrollView.documentOffset.x - 80, max: scrollView.documentOffset.x + scrollView.frame.width + 80) + + for (i, item) in items.enumerated() { + if item.frame.minX >= distance.min && item.frame.maxX <= distance.max { + range.length += 1 + if range.location == NSNotFound { + range.location = i + } + } else if range.location != NSNotFound { + break + } + } + + if previousRange == range { + return + } + + previousRange = range + + documentView.subviews = range.location == NSNotFound ? [] : items.subarray(with: range).map { $0.view } + } + + override func layout() { + super.layout() + scrollView.frame = bounds + } + + required init?(coder: NSCoder) { + fatalError() + } + + func insertItem(_ item: MGalleryItem, at: Int, isSelected: Bool, animated: Bool, callback:@escaping(MGalleryItem)->Void) { + let view = GalleryThumb(item, callback: callback) + view.selected = isSelected + + let idx = idsExcludeDisabled(at) + + items.insert(view, at: idx) + + } + + func idsExcludeDisabled(_ at: Int) -> Int { + var idx = at + for i in 0 ..< documentView.subviews.count { + let subview = documentView.subviews[i] as! GalleryThumbContainer + if !subview.isEnabled { + if i <= at { + idx += 1 + } + } + } + + return idx + } + + func removeItem(at: Int, animated: Bool) { + + let idx = idsExcludeDisabled(at) + + let subview = items[idx] + subview.isEnabled = false + subview.opt?.isEnabled = false + subview.opt?.change(opacity: 0, animated: animated, completion: { [weak subview, weak self] completed in + subview?.cleanup() + self?.items.remove(at: idx) + }) + } + + func updateItem(_ item: MGalleryItem, at: Int) { + + } + + var documentSize: NSSize { + return NSMakeSize(min(documentView.frame.width, frame.width), documentView.frame.height) + } + + func layoutItems(selectedIndex: Int? = nil, animated: Bool) { + + let idx = idsExcludeDisabled(selectedIndex ?? 0) + + let minWidth: CGFloat = frame.height / 2 + let difSize = NSMakeSize(frame.height, frame.height) + + var x:CGFloat = 0 + + let duration: Double = 0.4 + var selectedView: GalleryThumbContainer? + for i in 0 ..< items.count { + let thumb = items[i] + var size = idx == i ? difSize : NSMakeSize(minWidth, frame.height) + let view = thumb.opt + + let rect = CGRect(origin: NSMakePoint(x, 0), size: size) + thumb.frame = rect + + view?.overlay.change(opacity: 0.35) + if thumb.isEnabled { + if let view = view { + view._change(size: rect.size, animated: animated, duration: duration, timingFunction: CAMediaTimingFunctionName.spring) + + let f = view.focus(view.imageView.frame.size) + view.imageView._change(pos: f.origin, animated: animated, duration: duration, timingFunction: CAMediaTimingFunctionName.spring) + + view._change(pos: rect.origin, animated: animated, duration: duration, timingFunction: CAMediaTimingFunctionName.spring) + } + } else { + size.width -= minWidth + } + x += size.width + 4 + + if idx == i { + selectedView = view + view?.overlay.change(opacity: 0.0) + } + } + + self.selectedView = selectedView + + documentView.setFrameSize(x, frame.height) + + + if let selectedView = selectedView { + scrollView.clipView.scroll(to: NSMakePoint(min(max(selectedView.frame.midX - frame.width / 2, 0), max(documentView.frame.width - frame.width, 0)), 0), animated: animated && documentView.subviews.count > 0) + } + previousRange = nil + scrollDidUpdated() + } + +} diff --git a/Telegram-Mac/GalleryTouchBar.swift b/Telegram-Mac/GalleryTouchBar.swift new file mode 100644 index 0000000000..832a7a221f --- /dev/null +++ b/Telegram-Mac/GalleryTouchBar.swift @@ -0,0 +1,327 @@ +// +// GalleryTouchBar.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import TGUIKit + + +@available(OSX 10.12.2, *) +private extension NSTouchBarItem.Identifier { + static let zoomControls = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.gallery.zoom") + static let rotate = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.gallery.rotate") + + static let videoPlayControl = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.gallery.videoPlayControl") + static let videoTimeControls = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.gallery.videoTimeControls") + static let scrubber = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.gallery.scrubber") +} + +@available(OSX 10.12.2, *) +private class GalleryThumbsTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate { + + private static let itemViewIdentifier = "GalleryTouchBarThumbItemView" + + private var entries: [MGalleryItem] = [] + private var selected:(MGalleryItem)->Void + init(identifier: NSTouchBarItem.Identifier, selected:@escaping(MGalleryItem)->Void) { + self.selected = selected + super.init(identifier: identifier) + + let scrubber = TGScrubber() + scrubber.register(GalleryTouchBarThumbItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: GalleryThumbsTouchBarItem.itemViewIdentifier)) + + scrubber.mode = .free + scrubber.selectionBackgroundStyle = .outlineOverlay + scrubber.selectionOverlayStyle = .outlineOverlay + scrubber.delegate = self + scrubber.dataSource = self + scrubber.isContinuous = true + scrubber.floatsSelectionViews = false + scrubber.itemAlignment = .center + self.view = scrubber + } + + var scrubber: NSScrubber { + return view as! NSScrubber + } + + func insertItem(_ item: MGalleryItem, at: Int) { +// let index = min(at, entries.count) +// entries.insert(item, at: index) +// scrubber.insertItems(at: IndexSet(integer: index)) + } + func removeItem(at: Int) { +// if at < entries.count { +// entries.remove(at: at) +// scrubber.removeItems(at: IndexSet(integer: at)) +// } + } + func updateItem(_ item: MGalleryItem, at: Int) { +// if at < entries.count { +// entries[at] = item +// scrubber.reloadItems(at: IndexSet(integer: at)) +// } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var lockNotify: Bool = false + func selectAndScroll(to item: MGalleryItem?) { + Queue.mainQueue().justDispatch { + if let first = self.entries.firstIndex(where: {$0.entry.stableId == item?.stableId}) { + self.lockNotify = true + self.scrubber.selectedIndex = first + } else { + self.scrubber.selectedIndex = -1 + } + } + } + + func numberOfItems(for scrubber: NSScrubber) -> Int { + return entries.count + } + + + func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: GalleryThumbsTouchBarItem.itemViewIdentifier), owner: nil) as! GalleryTouchBarThumbItemView + view.update(entries[index]) + return view + } + + func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize { + return NSMakeSize(40, 30) + } + func scrubber(_ scrubber: NSScrubber, didHighlightItemAt highlightedIndex: Int) { + + } + + + func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) { + if let current = NSApp.currentEvent, current.window == nil { + selected(entries[index]) + } + } + +} + +@available(OSX 10.12.2, *) +class GalleryTouchBar: NSTouchBar, NSTouchBarDelegate { + private let interactions: GalleryInteractions + + private var selectedItem: MGalleryItem? + private let videoStatusDisposable = MetaDisposable() + init(interactions: GalleryInteractions, selectedItemChanged: @escaping(@escaping(MGalleryItem) -> Void) ->Void, transition:@escaping(@escaping(UpdateTransition, MGalleryItem?) -> Void) ->Void) { + self.interactions = interactions + super.init() + self.customizationIdentifier = .windowBar + self.delegate = self + + self.defaultItemIdentifiers = [.scrubber] + + + transition { [weak self] transition, selectedItem in + self?.applyTransition(transition, selectedItem) + } + selectedItemChanged { [weak self] selectedItem in + self?.updateSelectedItem(selectedItem) + } + + + } + + private func applyTransition(_ transition: UpdateTransition, _ selectedItem: MGalleryItem?) { + guard let item = self.item(forIdentifier: .scrubber) as? GalleryThumbsTouchBarItem else {return} + item.lockNotify = true + //item.scrubber.performSequentialBatchUpdates { [weak item] in + for rdx in transition.deleted.reversed() { + item.removeItem(at: rdx) + } + for (idx, insertItem) in transition.inserted { + item.insertItem(insertItem, at: idx) + } + for (idx, updateItem) in transition.updated { + item.updateItem(updateItem, at: idx) + } + // } + if !transition.isEmpty { + item.scrubber.reloadData() + } + updateSelectedItem(selectedItem) + } + + private func updateSelectedItem(_ item: MGalleryItem?) { + if self.selectedItem?.entry != item?.entry { + self.selectedItem = item + // + var items: [NSTouchBarItem.Identifier] = [] + if !(item is MGalleryGIFItem) && !(item is MGalleryVideoItem) { + items.append(.zoomControls) + items.append(.rotate) + } + if let item = item as? MGalleryVideoItem { + items.append(.videoPlayControl) + items.append(.videoTimeControls) + videoStatusDisposable.set((item.playerState |> deliverOnMainQueue).start(next: { [weak self] state in + self?.updateVideoControls(state) + })) + } else { + videoStatusDisposable.set(nil) + } + items.append(.fixedSpaceLarge) + items.append(.scrubber) + items.append(.fixedSpaceLarge) + + self.defaultItemIdentifiers = items + + (self.item(forIdentifier: .scrubber) as? GalleryThumbsTouchBarItem)?.selectAndScroll(to: item) + } + } + + private func updateVideoControls(_ state: AVPlayerState) { + guard let button = (self.item(forIdentifier: .videoPlayControl) as? NSCustomTouchBarItem)?.view as? NSButton else {return} + guard let segment = (self.item(forIdentifier: .videoTimeControls) as? NSCustomTouchBarItem)?.view as? NSSegmentedControl else {return} + + if let item = selectedItem as? MGalleryExternalVideoItem { + switch ExternalVideoLoader.serviceType(item.content) { + case .youtube: + button.bezelColor = .redUI + case .vimeo: + button.bezelColor = .accent + case .none: + button.bezelColor = nil + } + } else { + button.bezelColor = nil + } + switch state { + case let .playing(duration): + button.isEnabled = true + button.image = NSImage(named: NSImage.touchBarPauseTemplateName)! + segment.setEnabled(duration >= 30, forSegment: 0) + segment.setEnabled(duration >= 30, forSegment: 1) + case let .paused(duration): + button.isEnabled = true + button.image = NSImage(named: NSImage.touchBarPlayTemplateName)! + + segment.setEnabled(duration >= 30, forSegment: 0) + segment.setEnabled(duration >= 30, forSegment: 1) + default: + button.isEnabled = false + button.image = NSImage(named: NSImage.touchBarPlayTemplateName)! + segment.setEnabled(false, forSegment: 0) + segment.setEnabled(false, forSegment: 1) + } + } + + deinit { + videoStatusDisposable.dispose() + } + + + @objc private func zoom(_ sender: Any?) { + guard let segment = sender as? NSSegmentedControl else {return} + switch segment.selectedSegment { + case 0: + interactions.zoomOut() + case 1: + interactions.zoomIn() + default: + break + } + } + + @objc private func rotate() { + interactions.rotateLeft() + } + @objc private func videoTimeControlsActions(_ sender: Any?) { + guard let segment = sender as? NSSegmentedControl else {return} + if let item = selectedItem { + switch segment.selectedSegment { + case 0: + item.rewindBack() + case 1: + item.rewindForward() + default: + break + } + } + } + + @objc private func playOrPauseAction() { + if let item = selectedItem { + item.togglePlayerOrPause() + } + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .zoomControls: + let item = NSCustomTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 2 + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_ZoomOut"))!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.Name("Icon_TouchBar_ZoomIn"))!, forSegment: 1) + + segment.setWidth(93, forSegment: 0) + segment.setWidth(93, forSegment: 1) + + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(zoom(_:)) + item.view = segment + return item + case .rotate: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(image: NSImage(named: NSImage.touchBarRotateLeftTemplateName)!, target: self, action: #selector(rotate)) + button.addWidthConstraint(size: 93) + item.view = button + item.customizationLabel = button.title + return item + + case .videoTimeControls: + let item = NSCustomTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .separated + segment.segmentCount = 2 + segment.setImage(NSImage(named: NSImage.touchBarSkipBack15SecondsTemplateName)!, forSegment: 0) + segment.setImage(NSImage(named: NSImage.touchBarSkipAhead15SecondsTemplateName)!, forSegment: 1) + + segment.setWidth(93, forSegment: 0) + segment.setWidth(93, forSegment: 1) + + segment.trackingMode = .momentary + segment.target = self + segment.action = #selector(videoTimeControlsActions(_:)) + item.view = segment + return item + case .videoPlayControl: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(image:NSImage(named: NSImage.touchBarPlayTemplateName)!, target: self, action: #selector(playOrPauseAction)) + button.addWidthConstraint(size: 93) + item.view = button + item.customizationLabel = button.title + return item + case .scrubber: + return GalleryThumbsTouchBarItem(identifier: identifier, selected: { [weak self] item in + self?.interactions.select(item) + }) + + default: + return nil + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/GalleryTouchBarThumbItemView.swift b/Telegram-Mac/GalleryTouchBarThumbItemView.swift new file mode 100644 index 0000000000..36fcc5cbe8 --- /dev/null +++ b/Telegram-Mac/GalleryTouchBarThumbItemView.swift @@ -0,0 +1,63 @@ +// +// GalleryTouchBarThumbItemView.swift +// Telegram +// +// Created by Mikhail Filimonov on 20/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +@available(OSX 10.12.2, *) +class GalleryTouchBarThumbItemView: NSScrubberItemView { + fileprivate let imageView: TransformImageView = TransformImageView() + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + } + + func update(_ item: MGalleryItem) { + var signal:Signal? + var size: NSSize? + if let item = item as? MGalleryPhotoItem { + signal = chatWebpageSnippetPhoto(account: item.context.account, imageReference: item.entry.imageReference(item.media), scale: backingScaleFactor, small: true, secureIdAccessContext: item.secureIdAccessContext) + size = item.media.representations.first?.dimensions.size + } else if let item = item as? MGalleryGIFItem { + signal = chatMessageImageFile(account: item.context.account, fileReference: item.entry.fileReference(item.media), scale: backingScaleFactor) + size = item.media.videoSize + } else if let item = item as? MGalleryExternalVideoItem { + signal = chatWebpageSnippetPhoto(account: item.context.account, imageReference: item.entry.imageReference(item.mediaImage), scale: backingScaleFactor, small: true, secureIdAccessContext: nil) + size = item.mediaImage.representations.first?.dimensions.size + } else if let item = item as? MGalleryVideoItem { + signal = chatMessageImageFile(account: item.context.account, fileReference: item.entry.fileReference(item.media), scale: backingScaleFactor) + size = item.media.videoSize + } else if let item = item as? MGalleryPeerPhotoItem { + signal = chatMessagePhoto(account: item.context.account, imageReference: item.entry.imageReference(item.media), scale: backingScaleFactor) + + size = item.media.representations.first?.dimensions.size + } + item.fetch() + + if let signal = signal, let size = size { + imageView.setSignal(signal) + let arguments = TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize:size.aspectFilled(NSMakeSize(36, 30)), boundingSize: NSMakeSize(36, 30), intrinsicInsets: NSEdgeInsets()) + imageView.set(arguments: arguments) + } + imageView.setFrameSize(36, 30) + } + + override func layout() { + super.layout() + imageView.center() + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/GalleryViewer.swift b/Telegram-Mac/GalleryViewer.swift index b19558bf38..0c2b9b2210 100644 --- a/Telegram-Mac/GalleryViewer.swift +++ b/Telegram-Mac/GalleryViewer.swift @@ -7,27 +7,34 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import AVFoundation + final class GalleryInteractions { var dismiss:()->KeyHandlerResult = { return .rejected} var next:()->KeyHandlerResult = { return .rejected} + var select:(MGalleryItem)->Void = { _ in} var previous:()->KeyHandlerResult = { return .rejected} var showActions:(Control)->KeyHandlerResult = {_ in return .rejected} + var share:(Control)->Void = { _ in } var contextMenu:()->NSMenu? = {return nil} + var openInfo:(PeerId)->Void = {_ in} + var openMessage:()->Void = {} + var showThumbsControl:(View, Bool)->Void = {_, _ in} + var hideThumbsControl:(View, Bool)->Void = {_, _ in} + + var zoomIn:()->Void = {} + var zoomOut:()->Void = {} + var rotateLeft:()->Void = {} + + var fastSave:()->Void = {} } private(set) var viewer:GalleryViewer? -func cacheGalleryView(_ view: NSView, for item: MGalleryItem) { - viewer?.viewCache[item.stableId] = view -} - -func cachedGalleryView(for item: MGalleryItem) -> NSView? { - return viewer?.viewCache[item.stableId] -} let galleryButtonStyle = ControlStyle(font:.medium(.huge), foregroundColor:.white, backgroundColor:.clear, highlightColor:.grayIcon) @@ -38,11 +45,13 @@ private func tagsForMessage(_ message: Message) -> MessageTags? { case _ as TelegramMediaImage: return .photoOrVideo case let file as TelegramMediaFile: - if file.isVideo && !file.isAnimated { + if file.isVideo && file.isAnimated { + return nil + } else if file.isVideo && !file.isAnimated { return .photoOrVideo } else if file.isVoice { return .voiceOrInstantVideo - } else if file.isSticker || (file.isVideo && file.isAnimated) { + } else if file.isStaticSticker || (file.isVideo && file.isAnimated) { return nil } else { return .file @@ -62,12 +71,14 @@ enum GalleryAppearType { case secret } -private func mediaForMessage(message: Message) -> Media? { +private func mediaForMessage(message: Message, postbox: Postbox) -> Media? { for media in message.media { if let media = media as? TelegramMediaImage { return media } else if let file = media as? TelegramMediaFile { - if file.mimeType.hasPrefix("image/") || file.isVideo { + if file.isGraphicFile || file.isVideo || file.isAnimated { + return file + } else if file.isVideoFile, FileManager.default.fileExists(atPath: postbox.mediaBox.resourcePath(file.resource)) { return file } } else if let webpage = media as? TelegramMediaWebpage { @@ -84,45 +95,51 @@ private func mediaForMessage(message: Message) -> Media? { return nil } +fileprivate func itemFor(entry: ChatHistoryEntry, context: AccountContext, pagerSize: NSSize) -> MGalleryItem { + switch entry { + case let .MessageEntry(message, _, _, _, _, _, _): + if let media = mediaForMessage(message: message, postbox: context.account.postbox) { + if let _ = media as? TelegramMediaImage { + return MGalleryPhotoItem(context, .message(entry), pagerSize) + } else if let file = media as? TelegramMediaFile { + if (file.isVideo && !file.isAnimated) { + return MGalleryVideoItem(context, .message(entry), pagerSize) + } else { + if file.mimeType.hasPrefix("image/") { + return MGalleryPhotoItem(context, .message(entry), pagerSize) + } else if file.isVideo && file.isAnimated { + return MGalleryGIFItem(context, .message(entry), pagerSize) + } else if file.isVideoFile { + return MGalleryVideoItem(context, .message(entry), pagerSize) + } + } + } + } else if !message.media.isEmpty, let webpage = message.media[0] as? TelegramMediaWebpage { + if case let .Loaded(content) = webpage.content { + if ExternalVideoLoader.isPlayable(content) { + return MGalleryExternalVideoItem(context, .message(entry), pagerSize) + } + } + } + default: + break + } + + return MGalleryItem(context, .message(entry), pagerSize) +} -fileprivate func prepareEntries(from:[ChatHistoryEntry]?, to:[ChatHistoryEntry], account:Account, pagerSize:NSSize) -> Signal, Void> { +fileprivate func prepareEntries(from:[ChatHistoryEntry]?, to:[ChatHistoryEntry], context: AccountContext, pagerSize:NSSize) -> Signal, NoError> { return Signal { subscriber in let (removed, inserted, updated) = proccessEntriesWithoutReverse(from, right: to, { (entry) -> MGalleryItem in - switch entry { - case let .MessageEntry(message, _, _, _, _): - if let media = mediaForMessage(message: message) { - if let _ = media as? TelegramMediaImage { - return MGalleryPhotoItem(account, .message(entry), pagerSize) - } else if let file = media as? TelegramMediaFile { - if file.isVideo && !file.isAnimated { - return MGalleryVideoItem(account, .message(entry), pagerSize) - } else { - if file.mimeType.hasPrefix("image/") { - return MGalleryPhotoItem(account, .message(entry), pagerSize) - } else if file.isVideo && file.isAnimated { - return MGalleryGIFItem(account, .message(entry), pagerSize) - } - } - } - } else if !message.media.isEmpty, let webpage = message.media[0] as? TelegramMediaWebpage { - if case let .Loaded(content) = webpage.content { - if ExternalVideoLoader.isPlayable(content) { - return MGalleryExternalVideoItem(account, .message(entry), pagerSize) - } - } - } - default: - break - } - return MGalleryItem(account, .message(entry), pagerSize) + return itemFor(entry: entry, context: context, pagerSize: pagerSize) }) subscriber.putNext(UpdateTransition(deleted: removed, inserted: inserted, updated: updated)) subscriber.putCompletion() return EmptyDisposable - } |> runOn(prepareQueue) + } |> runOn(Queue.mainQueue()) } @@ -143,17 +160,51 @@ class GalleryMessagesBehavior { } } -final class GalleryBackgroundView : View { +final class GalleryBackgroundView : NSView { + + deinit { + var bp:Int = 0 + bp += 1 + } +} + + + +private final class GalleryTouchBarController : ViewController { + + private let interactions: GalleryInteractions + private let selectedItemChanged: (@escaping(MGalleryItem) -> Void)->Void + private let transition: (@escaping(UpdateTransition, MGalleryItem?) -> Void) -> Void + init(interactions: GalleryInteractions, selectedItemChanged: @escaping(@escaping(MGalleryItem) -> Void) ->Void, transition: @escaping(@escaping(UpdateTransition, MGalleryItem?) -> Void) ->Void) { + self.interactions = interactions + self.selectedItemChanged = selectedItemChanged + self.transition = transition + super.init() + } + private var temporaryTouchBar: Any? + + @available(OSX 10.12.2, *) + override func makeTouchBar() -> NSTouchBar? { + if temporaryTouchBar == nil { + temporaryTouchBar = GalleryTouchBar(interactions: interactions, selectedItemChanged: selectedItemChanged, transition: transition) + } + return temporaryTouchBar as? NSTouchBar + } + deinit { + var bp:Int = 0 + bp += 1 + } } + class GalleryViewer: NSResponder { - fileprivate var viewCache:[AnyHashable: NSView] = [:] - private var window:Window - private var controls:GalleryControls! - private let pager:GalleryPageController + let window:Window + // private var controls:GalleryControls! + private var controls: GalleryModernControls! + let pager:GalleryPageController private let backgroundView: GalleryBackgroundView = GalleryBackgroundView() private let ready = Promise() private var didSetReady = false @@ -162,49 +213,61 @@ class GalleryViewer: NSResponder { private let readyDispose = MetaDisposable() private let operationDisposable = MetaDisposable() private(set) weak var delegate:InteractionContentViewProtocol? - private let account:Account - + private let context:AccountContext + private let touchbarController: GalleryTouchBarController private let indexDisposable:MetaDisposable = MetaDisposable() + private let messagesActionDisposable = MetaDisposable() let interactions:GalleryInteractions = GalleryInteractions() - let contentInteractions:ChatMediaGalleryParameters? + let contentInteractions:ChatMediaLayoutParameters? + fileprivate var firstStableId: AnyHashable? = nil required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } let type:GalleryAppearType - - private init(account:Account, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType) { - self.account = account + private let reversed: Bool + private init(context: AccountContext, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType, reversed:Bool = false) { + self.context = context self.delegate = delegate self.type = type + self.reversed = reversed self.contentInteractions = contentInteractions if let screen = NSScreen.main { let bounds = NSMakeRect(0, 0, screen.frame.width, screen.frame.height) self.window = Window(contentRect: bounds, styleMask: [.borderless], backing: .buffered, defer: false, screen: screen) self.window.contentView?.wantsLayer = true - - self.window.level = .screenSaver + self.window.contentView?.canDrawSubviewsIntoLayer = true + + self.window.level = .popUpMenu self.window.isOpaque = false self.window.backgroundColor = .clear - - backgroundView.backgroundColor = .blackTransparent + // self.window.appearance = theme.appearance + backgroundView.wantsLayer = true + backgroundView.background = NSColor.black.withAlphaComponent(0.9) backgroundView.frame = bounds - self.pager = GalleryPageController(frame: bounds, contentInset:NSEdgeInsets(left: 0, right: 0, top: 0, bottom: 95), interactions:interactions, window:window) + self.pager = GalleryPageController(frame: bounds, contentInset:NSEdgeInsets(left: 0, right: 0, top: 0, bottom: 95), interactions:interactions, window:window, reversed: reversed) + //, selectedItemChanged: selectedItemChanged, transition: transition + self.touchbarController = GalleryTouchBarController(interactions: interactions, selectedItemChanged: pager.selectedItemChanged, transition: pager.transition) + self.window.rootViewController = touchbarController + } else { fatalError("main screen not found for MediaViewer") } + super.init() - window.set(responder: { [weak self] () -> NSResponder? in - return self - }, with: self, priority: .high) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window) + interactions.dismiss = { [weak self] () -> KeyHandlerResult in if let pager = self?.pager { + if pager.isFullScreen { - return .invokeNext + pager.exitFullScreen() + return .invoked } if !pager.lockedTransition { self?.close(true) @@ -223,20 +286,57 @@ class GalleryViewer: NSResponder { return .invoked } + interactions.select = { [weak self] item in + self?.pager.select(by: item) + } + interactions.showActions = { [weak self] control -> KeyHandlerResult in self?.showControlsPopover(control) return .invoked } + interactions.share = { [weak self] control in + self?.share(control) + } + + interactions.openInfo = { [weak self] peerId in + self?.openInfo(peerId) + } + + interactions.openMessage = { [weak self] in + self?.showMessage() + } interactions.contextMenu = {[weak self] in return self?.contextMenu } - window.set(handler: interactions.dismiss, with:self, for: .Space) + interactions.zoomIn = { [weak self] in + self?.pager.zoomIn() + } + interactions.zoomOut = { [weak self] in + self?.pager.zoomOut() + } + interactions.rotateLeft = { [weak self] in + self?.pager.rotateLeft() + } + interactions.fastSave = { [weak self] in + self?.saveAs(true) + } + window.set(handler: { [weak self] in + guard let `self` = self else {return .rejected} + if self.pager.selectedItem is MGalleryVideoItem || self.pager.selectedItem is MGalleryExternalVideoItem { + self.pager.selectedItem?.togglePlayerOrPause() + return .invoked + } else { + return self.interactions.dismiss() + } + }, with:self, for: .Space) + window.set(handler: interactions.dismiss, with:self, for: .Escape) window.closeInterceptor = { [weak self] in _ = self?.interactions.dismiss() + return true } window.set(handler: interactions.next, with:self, for: .RightArrow) @@ -252,6 +352,21 @@ class GalleryViewer: NSResponder { return .invoked }, with: self, for: .Equal) + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.pager.decreaseSpeed() + return .invoked + }, with: self, for: .Minus, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.pager.increaseSpeed() + return .invoked + }, with: self, for: .Equal, modifierFlags: [.command, .option]) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.pager.rotateLeft() + return .invoked + }, with: self, for: .R, modifierFlags: [.command]) + window.set(handler: { [weak self] () -> KeyHandlerResult in self?.saveAs() return .invoked @@ -261,124 +376,157 @@ class GalleryViewer: NSResponder { self?.copy(nil) } - switch type { - case .secret: - self.controls = GallerySecretControls(View(frame:NSMakeRect(0, 10, 200, 75)), interactions:interactions) - default: - self.controls = GalleryGeneralControls(View(frame:NSMakeRect(0, 10, 400, 75)), interactions:interactions) + window.firstResponderFilter = { responder in + return responder } + + self.controls = GalleryModernControls(context, interactions: interactions, frame: NSMakeRect(0, -150, window.frame.width, 150), thumbsControl: pager.thumbsControl) + self.pager.view.addSubview(self.backgroundView) self.window.contentView?.addSubview(self.pager.view) - self.window.contentView?.addSubview(self.controls.view!) + self.window.contentView?.addSubview(self.controls.view) + + if #available(OSX 10.12.2, *) { + window.touchBar = window.makeTouchBar() + } + } + + @objc open func windowDidBecomeKey() { + + } + + + @objc open func windowDidResignKey() { + self.window.makeKeyAndOrderFront(self) + // window.makeFirstResponder(self) } var pagerSize: NSSize { return NSMakeSize(pager.frame.size.width - pager.contentInset.right - pager.contentInset.left, pager.frame.size.height - pager.contentInset.bottom - pager.contentInset.top) } - fileprivate convenience init(account:Account, peerId:PeerId, firstStableId:AnyHashable, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil) { - self.init(account: account, delegate, contentInteractions, type: .profile(peerId)) + fileprivate convenience init(context: AccountContext, peerId:PeerId, firstStableId:AnyHashable, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil, reversed:Bool = false) { + self.init(context: context, delegate, contentInteractions, type: .profile(peerId), reversed: reversed) let pagerSize = self.pagerSize - ready.set(account.postbox.modify { modifier -> Peer? in - return modifier.getPeer(peerId) - } |> deliverOnMainQueue |> map { [weak self] peer -> Bool in - if let peer = peer { - var representations:[TelegramMediaImageRepresentation] = [] - if let representation = peer.smallProfileImage { - representations.append(representation) - } - if let representation = peer.largeProfileImage { - representations.append(representation) - } - let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: 0), representations: representations) - - _ = self?.pager.merge(with: UpdateTransition(deleted: [], inserted: [(0,MGalleryPeerPhotoItem(account, .photo(index: 0, stableId: firstStableId, photo: image, reference: .none), pagerSize))], updated: [])) - - self?.controls.index.set(.single((1,1))) - self?.pager.set(index: 0, animated: false) - - return true - } - return false - }) - - var totalCount:Int = 1 + let previous: Atomic<[GalleryEntry]> = Atomic(value: []) - self.disposable.set((requestPeerPhotos(account: account, peerId: peerId) |> map { photos -> (UpdateTransition, Int) in - - var inserted:[(Int, MGalleryItem)] = [] - var updated:[(Int, MGalleryItem)] = [] - let deleted:[Int] = [] - if !photos.isEmpty { - updated.append((0, MGalleryPeerPhotoItem(account, .photo(index: photos[0].index, stableId: firstStableId, photo: photos[0].image, reference: photos[0].reference), pagerSize))) - for i in 1 ..< photos.count { - inserted.append((i, MGalleryPeerPhotoItem(account, .photo(index: photos[i].index, stableId: photos[i].image.imageId, photo: photos[i].image, reference: photos[i].reference), pagerSize))) + let transaction: Signal<(UpdateTransition, Int), NoError> = peerPhotosGalleryEntries(account: context.account, peerId: peerId, firstStableId: firstStableId) |> map { (entries, selected) in + let (deleted, inserted, updated) = proccessEntriesWithoutReverse(previous.swap(entries), right: entries, { entry -> MGalleryItem in + switch entry { + case let .photo(_, _, photo, _, _, _, _): + if !photo.videoRepresentations.isEmpty { + return MGalleryGIFItem(context, entry, pagerSize) + } else { + return MGalleryPeerPhotoItem(context, entry, pagerSize) + } + default: + preconditionFailure() } - } - return (UpdateTransition(deleted: deleted, inserted: inserted, updated: updated), max(0, photos.count)) - - } |> deliverOnMainQueue).start(next: { [weak self] transition, total in - totalCount = total - self?.controls.index.set(.single((1,max(totalCount, 1)))) - _ = self?.pager.merge(with: transition) - - })) + }) + return (UpdateTransition(deleted: deleted, inserted: inserted, updated: updated), selected) + } |> deliverOnMainQueue - self.indexDisposable.set((pager.selectedIndex.get() |> deliverOnMainQueue).start(next: { [weak self] (selectedIndex) in - if let strongSelf = self { - self?.controls.index.set(.single((selectedIndex + 1, strongSelf.pager.count))) - } + + disposable.set(transaction.start(next: { [weak self] transaction, selected in + _ = self?.pager.merge(with: transaction, afterTransaction: { + self?.controls.update(self?.pager.selectedItem?.entry) + }) + self?.pager.selectedIndex.set(selected) + self?.pager.set(index: selected, animated: false) + self?.ready.set(.single(true)) + + })) + + self.indexDisposable.set((pager.selectedIndex.get() |> deliverOnMainQueue).start(next: { [weak self] selectedIndex in + guard let `self` = self else {return} + self.controls.update(self.pager.selectedItem?.entry) })) } - fileprivate convenience init(account:Account, instantMedias:[InstantPageMedia], firstIndex:Int, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil) { - self.init(account: account, delegate, contentInteractions, type: .history) - + fileprivate convenience init(context: AccountContext, instantMedias:[InstantPageMedia], firstIndex:Int, firstStableId: AnyHashable? = nil, parent: Message? = nil, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil, reversed:Bool = false) { + self.init(context: context, delegate, contentInteractions, type: .history, reversed: reversed) + self.firstStableId = firstStableId let pagerSize = self.pagerSize - let totalCount:Int = instantMedias.count ready.set(.single(true) |> map { [weak self] _ -> Bool in + guard let `self` = self else {return false} + var inserted: [(Int, MGalleryItem)] = [] for i in 0 ..< instantMedias.count { let media = instantMedias[i] if media.media is TelegramMediaImage { - inserted.append((media.index, MGalleryPhotoItem(account, .instantMedia(media), pagerSize))) + inserted.append((media.index, MGalleryPhotoItem(context, .instantMedia(media, parent), pagerSize))) } else if let file = media.media as? TelegramMediaFile { if file.isVideo && file.isAnimated { - inserted.append((media.index, MGalleryGIFItem(account, .instantMedia(media), pagerSize))) + inserted.append((media.index, MGalleryGIFItem(context, .instantMedia(media, parent), pagerSize))) } else if file.isVideo { - inserted.append((media.index, MGalleryVideoItem(account, .instantMedia(media), pagerSize))) + inserted.append((media.index, MGalleryVideoItem(context, .instantMedia(media, parent), pagerSize))) } + } else if media.media is TelegramMediaWebpage { + inserted.append((media.index, MGalleryExternalVideoItem(context, .instantMedia(media, parent), pagerSize))) } } - _ = self?.pager.merge(with: UpdateTransition(deleted: [], inserted: inserted, updated: [])) - - self?.controls.index.set(.single((firstIndex + 1, totalCount))) - self?.pager.set(index: firstIndex, animated: false) + _ = self.pager.merge(with: UpdateTransition(deleted: [], inserted: inserted, updated: [])) + //self?.controls.index.set(.single((firstIndex + 1, totalCount))) + self.pager.set(index: firstIndex, animated: false) + self.controls.update(self.pager.selectedItem?.entry) return true }) - self.indexDisposable.set((pager.selectedIndex.get() |> deliverOnMainQueue).start(next: { [weak self] (selectedIndex) in - self?.controls.index.set(.single((selectedIndex + 1,totalCount))) + self.indexDisposable.set((pager.selectedIndex.get() |> deliverOnMainQueue).start(next: { [weak self] selectedIndex in + guard let `self` = self else {return} + self.controls.update(self.pager.selectedItem?.entry) })) } + + fileprivate convenience init(context: AccountContext, secureIdMedias:[SecureIdDocumentValue], firstIndex:Int, _ delegate:InteractionContentViewProtocol? = nil, reversed:Bool = false) { + self.init(context: context, delegate, nil, type: .history, reversed: reversed) + + let pagerSize = self.pagerSize + + + + ready.set(.single(true) |> map { [weak self] _ -> Bool in + guard let `self` = self else {return false} + var inserted: [(Int, MGalleryItem)] = [] + for i in 0 ..< secureIdMedias.count { + let media = secureIdMedias[i] + inserted.append((i, MGalleryPhotoItem(context, .secureIdDocument(media, i), pagerSize))) + + } + + _ = self.pager.merge(with: UpdateTransition(deleted: [], inserted: inserted, updated: [])) + + self.pager.set(index: firstIndex, animated: false) + self.controls.update(self.pager.selectedItem?.entry) + return true + + }) + + self.indexDisposable.set((pager.selectedIndex.get() |> deliverOnMainQueue).start(next: { [weak self] selectedIndex in + guard let `self` = self else {return} + self.controls.update(self.pager.selectedItem?.entry) + })) + + + } - fileprivate convenience init(account:Account, message:Message, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType = .history, item: MGalleryItem? = nil) { + fileprivate convenience init(context: AccountContext, message:Message, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType = .history, item: MGalleryItem? = nil, reversed: Bool = false) { - self.init(account: account, delegate, contentInteractions, type: type) + self.init(context: context, delegate, contentInteractions, type: type, reversed: reversed) let previous:Atomic<[ChatHistoryEntry]> = Atomic(value:[]) @@ -397,76 +545,136 @@ class GalleryViewer: NSResponder { ready.set(.single(true)) } - - let signal = request.get() |> distinctUntilChanged - |> mapToSignal { index -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), Void> in + |> mapToSignal { index -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), NoError> in + var type = type let tags = tagsForMessage(message) - let pullCount = tags != nil ? 50 : 1 - let view = account.viewTracker.aroundIdMessageHistoryViewForPeerId(message.id.peerId, count: pullCount, messageId: index.id, tagMask: tags, orderStatistics: [.combinedLocation]) + if tags == nil { + type = .alone + } + + let mode: ChatMode + if message.isScheduledMessage { + mode = .scheduled + } else { + mode = .history + } + + let signal:Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> + switch mode { + case .history: + signal = context.account.viewTracker.aroundIdMessageHistoryViewForLocation(.peer(message.id.peerId), count: 50, messageId: index.id, tagMask: tags, orderStatistics: [.combinedLocation], additionalData: []) + case .scheduled: + signal = context.account.viewTracker.scheduledMessagesViewForLocation(.peer(message.id.peerId)) + } + switch type { case .alone: - let entries:[ChatHistoryEntry] = [.MessageEntry(message, false, .Full(isAdmin: false), nil, nil)] + let entries:[ChatHistoryEntry] = [.MessageEntry(message, MessageIndex(message), false, .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings))] let previous = previous.swap(entries) - return prepareEntries(from: previous, to: entries, account: account, pagerSize: pagerSize) |> map { transition in - return (transition,previous, entries) - } |> deliverOnMainQueue + + var inserted: [(Int, MGalleryItem)] = [] + + inserted.insert((0, itemFor(entry: entries[0], context: context, pagerSize: pagerSize)), at: 0) + + if let webpage = message.media.first as? TelegramMediaWebpage { + let instantMedias = instantPageMedias(for: webpage) + if instantMedias.count > 1 { + for i in 1 ..< instantMedias.count { + let media = instantMedias[i] + if media.media is TelegramMediaImage { + inserted.append((i, MGalleryPhotoItem(context, .instantMedia(media, message), pagerSize))) + } else if let file = media.media as? TelegramMediaFile { + if file.isVideo && file.isAnimated { + inserted.append((i, MGalleryGIFItem(context, .instantMedia(media, message), pagerSize))) + } else if file.isVideo || file.isVideoFile { + inserted.append((i, MGalleryVideoItem(context, .instantMedia(media, message), pagerSize))) + } + } else if media.media is TelegramMediaWebpage { + inserted.append((i, MGalleryExternalVideoItem(context, .instantMedia(media, message), pagerSize))) + } + } + } + + } + + return .single((UpdateTransition(deleted: [], inserted: inserted, updated: []), previous, entries)) |> deliverOnMainQueue + case .history: - return view |> mapToQueue { view, _, _ -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), Void> in - let entries:[ChatHistoryEntry] = messageEntries(view.entries, includeHoles : false) - let previous = previous.swap(entries) - return prepareEntries(from: previous, to: entries, account: account, pagerSize: pagerSize) |> deliverOnMainQueue |> map { transition in + return signal |> mapToSignal { view, _, _ -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), NoError> in + let entries:[ChatHistoryEntry] = messageEntries(view.entries, includeHoles : false).filter { entry -> Bool in + switch entry { + case let .MessageEntry(message, _, _, _, _, _, _): + return message.id.peerId.namespace == Namespaces.Peer.SecretChat || !message.containsSecretMedia && mediaForMessage(message: message, postbox: context.account.postbox) != nil + default: + return true + } + } + let previous = previous.with {$0} + return prepareEntries(from: previous, to: entries, context: context, pagerSize: pagerSize) |> deliverOnMainQueue |> map { transition in _ = indexes.swap((view.earlierId, view.laterId)) return (transition,previous, entries) } } case .secret: - return account.postbox.messageView(index.id) |> mapToQueue { view -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), Void> in + return context.account.postbox.messageView(index.id) |> mapToSignal { view -> Signal<(UpdateTransition, [ChatHistoryEntry], [ChatHistoryEntry]), NoError> in var entries:[ChatHistoryEntry] = [] if let message = view.message, !(message.media.first is TelegramMediaExpiredContent) { - entries.append(.MessageEntry(message, false, .Full(isAdmin: false), nil, nil)) + entries.append(.MessageEntry(message, MessageIndex(message), false, .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings))) } - let previous = previous.swap(entries) - return prepareEntries(from: previous, to: entries, account: account, pagerSize: pagerSize) |> map { transition in + let previous = previous.with {$0} + return prepareEntries(from: previous, to: entries, context: context, pagerSize: pagerSize) |> map { transition in return (transition,previous, entries) - } |> deliverOnMainQueue + } } case .profile: return .complete() } - } - |> map { [weak self] transition, previous, new in + } |> deliverOnMainQueue + |> map { [weak self] transition, prev, new in if let strongSelf = self { + + _ = previous.swap(new) + + let new = reversed ? new.reversed() : new + _ = current.swap(new) var id:MessageId = message.id let index = currentIndex.modify({$0}) - if let index = index { - id = previous[index].message!.id + if let index = index, prev.count > index { + id = prev[index].message!.id } - for i in 0 ..< new.count { - if let message = new[i].message { - if message.id == id { - _ = currentIndex.swap(i) + var current:Int? = currentIndex.modify({$0}) + if current == nil || !reversed { + for i in 0 ..< new.count { + if let message = new[i].message { + if message.id == id { + current = i + } } } } +// let isEmpty = strongSelf.pager.merge(with: transition) + if !isEmpty { - if let newIndex = currentIndex.modify({$0}) { + + + if let newIndex = current { strongSelf.pager.selectedIndex.set(newIndex) strongSelf.pager.set(index: newIndex, animated: false) - if let attribute = new[newIndex].message?.autoremoveAttribute { - (self?.controls as? GallerySecretControls)?.update(with: attribute, outgoing: !new[newIndex].message!.flags.contains(.Incoming)) + if !new.isEmpty && newIndex < new.count && newIndex >= 0, let attribute = new[newIndex].message?.autoremoveAttribute { + // (self?.controls as? GallerySecretControls)?.update(with: attribute, outgoing: !new[newIndex].message!.flags.contains(.Incoming)) } } } else { @@ -474,7 +682,6 @@ class GalleryViewer: NSResponder { } strongSelf.ready.set(.single(true)) } - } @@ -483,27 +690,38 @@ class GalleryViewer: NSResponder { self.indexDisposable.set(pager.selectedIndex.get().start(next: { [weak self] (selectedIndex) in let entries = current.modify({$0}) - let selectedIndex = min(entries.count - 1, selectedIndex) + guard let `self` = self else {return} + let current = entries[selectedIndex] if let location = current.location { - self?.controls.index.set(.single((location.index + 1, location.count))) - } else { - self?.controls.index.set(.single((1,1))) + let total = location.count + let current = reversed ? total - location.index : location.index + self.controls.update(self.pager.selectedItem?.entry) + } else { + self.controls.update(self.pager.selectedItem?.entry) } if let message = entries[selectedIndex].message, message.containsSecretMedia { - _ = (markMessageContentAsConsumedInteractively(postbox: account.postbox, messageId: message.id) |> delay(0.5, queue: Queue.concurrentDefaultQueue())).start() + _ = (markMessageContentAsConsumedInteractively(postbox: context.account.postbox, messageId: message.id) |> delay(0.5, queue: Queue.concurrentDefaultQueue())).start() } let indexes = indexes.modify({$0}) if let pagerIndex = currentIndex.modify({$0}) { - if selectedIndex < pagerIndex && pagerIndex < reqlimit, let earlier = indexes.earlierId { - request.set(.single(earlier)) - } else if selectedIndex > pagerIndex && pagerIndex > entries.count - reqlimit, let later = indexes.laterId { - request.set(.single(later)) + if selectedIndex < pagerIndex && pagerIndex < reqlimit { + if !reversed, let earlier = indexes.earlierId { + request.set(.single(earlier)) + } else if reversed, let later = indexes.laterId { + request.set(.single(later)) + } + } else if selectedIndex > pagerIndex && pagerIndex > entries.count - reqlimit { + if !reversed, let later = indexes.laterId { + request.set(.single(later)) + } else if reversed, let earlier = indexes.earlierId { + request.set(.single(earlier)) + } } } _ = currentIndex.swap(selectedIndex) @@ -515,34 +733,215 @@ class GalleryViewer: NSResponder { } - + func showControlsPopover(_ control:Control) { + + if let popover = control.popover { + popover.hide() + return + } + var items:[SPopoverItem] = [] - items.append(SPopoverItem(tr(.galleryContextSaveAs), {[weak self] in - self?.saveAs() - })) - if let _ = self.contentInteractions, case .history = type { - items.append(SPopoverItem(tr(.galleryContextShowMessage), {[weak self] in - self?.showMessage() + + if pager.selectedItem?.entry.message?.containsSecretMedia == true { + } else { + items.append(SPopoverItem(L10n.galleryContextSaveAs, {[weak self] in + self?.saveAs() })) } - items.append(SPopoverItem(tr(.galleryContextCopyToClipboard), {[weak self] in - self?.copy(nil) - })) + + + let context = self.context + + var chatMode: ChatMode = .history + if let message = pager.selectedItem?.entry.message, message.isScheduledMessage { + chatMode = .scheduled + } + + if let item = pager.selectedItem as? MGalleryGIFItem, chatMode == .history { + let file = item.media + if file.isAnimated && file.isVideo { + let reference = item.entry.fileReference(file) + items.append(SPopoverItem(L10n.gallerySaveGif, { + let _ = addSavedGif(postbox: context.account.postbox, fileReference: reference).start() + })) + } + } + + + + if let _ = self.contentInteractions, chatMode == .history { + if let message = pager.selectedItem?.entry.message { + items.append(SPopoverItem(L10n.galleryContextShowMessage, {[weak self] in + self?.showMessage() + })) + items.append(SPopoverItem(L10n.galleryContextShowGallery, {[weak self] in + self?.showSharedMedia() + })) + if canDeleteMessage(message, account: context.account) { + items.append(SPopoverItem(L10n.galleryContextDeletePhoto, {[weak self] in + self?.deleteMessage(control) + })) + } + } + } + + if pager.selectedItem?.entry.message?.containsSecretMedia == true { + } else { + items.append(SPopoverItem(L10n.galleryContextCopyToClipboard, {[weak self] in + self?.copy(nil) + })) + } + switch type { case .profile(let peerId): - if peerId == account.peerId { - items.append(SPopoverItem(tr(.galleryContextDeletePhoto), {[weak self] in + if peerId == context.peerId { + items.append(SPopoverItem(L10n.galleryContextDeletePhoto, {[weak self] in self?.deletePhoto() })) + if pager.currentIndex != 0 { + items.append(SPopoverItem(L10n.galleryContextMainPhoto, { [weak self] in + self?.updateMainPhoto() + })) + } } default: break } - showPopover(for: control, with: SPopoverViewController(items: items), inset:NSMakePoint((-125 + 14),0)) + items.append(SPopoverItem(L10n.navigationClose, { [weak self] in + _ = self?.interactions.dismiss() + })) + + showPopover(for: control, with: SPopoverViewController(items: items, visibility: 6), inset:NSMakePoint((-105 + 14), 0), static: true) + } + + private func deleteMessages(_ messages:[Message]) { + if !messages.isEmpty, let peer = messageMainPeer(messages[0]) { + + let peerId = messages[0].id.peerId + let messageIds = messages.map {$0.id} + + let channelAdmin:Signal<[ChannelParticipant]?, NoError> = peer.isSupergroup ? channelAdmins(account: context.account, peerId: peerId) + |> `catch` {_ in return .complete()} |> map { admins -> [ChannelParticipant]? in + return admins.map({$0.participant}) + } : .single(nil) + + + messagesActionDisposable.set((channelAdmin |> deliverOnMainQueue).start( next:{ [weak self] admins in + guard let `self` = self else {return} + + var canDelete:Bool = true + var canDeleteForEveryone = true + var otherCounter:Int32 = 0 + var _mustDeleteForEveryoneMessage: Bool = true + for message in messages { + if !canDeleteMessage(message, account: self.context.account) { + canDelete = false + } + if !mustDeleteForEveryoneMessage(message) { + _mustDeleteForEveryoneMessage = false + } + if !canDeleteForEveryoneMessage(message, context: self.context) { + canDeleteForEveryone = false + } else { + if message.effectiveAuthor?.id != self.context.peerId && !(self.context.limitConfiguration.canRemoveIncomingMessagesInPrivateChats && message.peers[message.id.peerId] is TelegramUser) { + if let peer = message.peers[message.id.peerId] as? TelegramGroup { + inner: switch peer.role { + case .member: + otherCounter += 1 + default: + break inner + } + } else { + otherCounter += 1 + } + } + } + } + + if otherCounter == messages.count { + canDeleteForEveryone = false + } + + if canDelete { + let thrid:String? = (canDeleteForEveryone ? peer.isUser ? L10n.chatMessageDeleteForMeAndPerson(peer.compactDisplayTitle) : L10n.chatConfirmDeleteMessagesForEveryone : nil) + + if let thrid = thrid { + modernConfirm(for: self.window, account: self.context.account, peerId: nil, header: L10n.chatConfirmDeleteMessagesCountable(messages.count), information: nil, okTitle: L10n.confirmDelete, thridTitle: thrid, successHandler: { [weak self] result in + guard let `self` = self else {return} + + let type:InteractiveMessagesDeletionType + switch result { + case .basic: + type = .forLocalPeer + case .thrid: + type = .forEveryone + } + + _ = deleteMessagesInteractively(account: self.context.account, messageIds: messageIds, type: type).start() + }) + } else { + _ = deleteMessagesInteractively(account: self.context.account, messageIds: messageIds, type: .forLocalPeer).start() + } + } + })) + } + } + + private func deleteMessage(_ control: Control) { + if let message = self.pager.selectedItem?.entry.message { + let messages = pager.thumbsControl.items.compactMap({$0.entry.message}) + + if messages.count > 1 { + + var items:[SPopoverItem] = [] + + let thisTitle: String + if message.media.first is TelegramMediaImage { + thisTitle = L10n.galleryContextShareThisPhoto + } else { + thisTitle = L10n.galleryContextShareThisVideo + } + items.append(SPopoverItem(thisTitle, { [weak self] in + self?.deleteMessages([message]) + })) + + + let allTitle: String + if messages.filter({$0.media.first is TelegramMediaImage}).count == messages.count { + allTitle = L10n.galleryContextShareAllPhotosCountable(messages.count) + } else if messages.filter({$0.media.first is TelegramMediaFile}).count == messages.count { + allTitle = L10n.galleryContextShareAllVideosCountable(messages.count) + } else { + allTitle = L10n.galleryContextShareAllItemsCountable(messages.count) + } + + items.append(SPopoverItem(allTitle, { [weak self] in + self?.deleteMessages(messages) + })) + showPopover(for: control, with: SPopoverViewController(items: items), inset:NSMakePoint((-90 + 14),0), static: true) + } else { + deleteMessages([message]) + } + } + } + + private func updateMainPhoto() { + if let item = self.pager.selectedItem { + if let index = self.pager.index(for: item) { + if case let .photo(_, _, _, reference, _, _, _) = item.entry { + if let reference = reference { + _ = updatePeerPhotoExisting(network: context.account.network, reference: reference).start() + _ = pager.merge(with: UpdateTransition(deleted: [index], inserted: [(0, item)], updated: [])) + pager.selectedIndex.set(0) + } + } + } + + } } private func deletePhoto() { @@ -554,10 +953,10 @@ class GalleryViewer: NSResponder { close() } - pager.selectedIndex.set(index - 1) + pager.selectedIndex.set(index) - if case let .photo(_, _, _, reference) = item.entry { - _ = removeUserPhoto(account: account, reference: index == 0 ? .none : reference).start() + if case let .photo(_, _, _, reference, _, _, _) = item.entry { + _ = removeAccountPhoto(network: context.account.network, reference: index == 0 ? nil : reference).start() } } @@ -570,17 +969,17 @@ class GalleryViewer: NSResponder { if let item = self.pager.selectedItem { if !(item is MGalleryExternalVideoItem) { - menu.addItem(ContextMenuItem(tr(.galleryContextSaveAs), handler: { [weak self] in + menu.addItem(ContextMenuItem(tr(L10n.galleryContextSaveAs), handler: { [weak self] in self?.saveAs() })) } if let _ = self.contentInteractions { - menu.addItem(ContextMenuItem(tr(.galleryContextShowMessage), handler: { [weak self] in + menu.addItem(ContextMenuItem(tr(L10n.galleryContextShowMessage), handler: { [weak self] in self?.showMessage() })) } - menu.addItem(ContextMenuItem(tr(.galleryContextCopyToClipboard), handler: { [weak self] in + menu.addItem(ContextMenuItem(tr(L10n.galleryContextCopyToClipboard), handler: { [weak self] in self?.copy(nil) })) } @@ -590,14 +989,77 @@ class GalleryViewer: NSResponder { } - func saveAs() -> Void { + func saveAs(_ fast: Bool = false) -> Void { if let item = self.pager.selectedItem { if !(item is MGalleryExternalVideoItem) { - operationDisposable.set((item.path.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] path in - if let strongSelf = self { - savePanel(file: path.nsstring.deletingPathExtension, ext: path.nsstring.pathExtension, for: strongSelf.window) + let isPhoto = item is MGalleryPhotoItem || item is MGalleryPeerPhotoItem + operationDisposable.set((item.realStatus |> take(1) |> deliverOnMainQueue).start(next: { [weak self] status in + guard let `self` = self else {return} + switch status { + case .Local: + self.operationDisposable.set((item.path.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] path in + if let strongSelf = self { + if fast { + // let attr = NSMutableAttributedString() + // attr.append(string: "File saved to your download folder", color: .white, font: .bold(18)) + + let text: String + if item is MGalleryVideoItem { + text = L10n.galleryViewFastSaveVideo1 + } else if item is MGalleryGIFItem { + text = L10n.galleryViewFastSaveGif1 + } else { + text = L10n.galleryViewFastSaveImage1 + } + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss" + + + let file: TelegramMediaFile? + if let item = item as? MGalleryVideoItem { + file = item.media + } else if let item = item as? MGalleryGIFItem { + file = item.media + } else if let photo = item as? MGalleryPhotoItem { + file = photo.entry.file ?? TelegramMediaFile(fileId: MediaId(namespace: 0, id: arc4random64()), partialReference: nil, resource: photo.media.representations.last!.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/jpeg", size: nil, attributes: [.FileName(fileName: "photo_\(dateFormatter.string(from: Date())).jpeg")]) + } else if let photo = item as? MGalleryPeerPhotoItem { + file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: arc4random64()), partialReference: nil, resource: photo.media.representations.last!.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/jpeg", size: nil, attributes: [.FileName(fileName: "photo_\(dateFormatter.string(from: Date())).jpeg")]) + } else { + file = nil + } + + let context = strongSelf.context + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .bold(15), textColor: .white), bold: MarkdownAttributeSet(font: .bold(15), textColor: .white), link: MarkdownAttributeSet(font: .bold(15), textColor: .link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { _ in })) + })).mutableCopy() as! NSMutableAttributedString + + let layout = TextViewLayout(attributedText, alignment: .center, lineSpacing: 5.0, alwaysStaticItems: true) + layout.interactions = TextViewInteractions(processURL: { [weak strongSelf] url in + if let file = file { + showInFinder(file, account: context.account) + strongSelf?.close(false) + } + }) + layout.measure(width: 160) + + if let file = file { + + _ = (copyToDownloads(file, postbox: context.account.postbox, saveAnyway: true) |> map { _ in } |> deliverOnMainQueue |> take(1) |> then (showSaveModal(for: strongSelf.window, context: context, animation: LocalAnimatedSticker.success_saved, text: layout, delay: 3.0))).start() + } else { + savePanel(file: path.nsstring.deletingPathExtension, ext: path.nsstring.pathExtension, for: strongSelf.window) + } + } else { + savePanel(file: path.nsstring.deletingPathExtension, ext: path.nsstring.pathExtension, for: strongSelf.window) + } + } + })) + default: + alert(for: self.window, info: isPhoto ? L10n.galleryWaitDownloadPhoto : L10n.galleryWaitDownloadVideo) } + })) + } } @@ -610,9 +1072,62 @@ class GalleryViewer: NSResponder { } } + func showSharedMedia() { + close() + if let message = self.pager.selectedItem?.entry.message { + context.sharedContext.bindings.rootNavigation().push(PeerMediaController(context: context, peerId: message.id.peerId)) + } + } + + func openInfo(_ peerId: PeerId) { + close() + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + } + + func share(_ control: Control) -> Void { + if let message = self.pager.selectedItem?.entry.message { + if message.groupInfo != nil { + let messages = pager.thumbsControl.items.compactMap({$0.entry.message}) + var items:[SPopoverItem] = [] + + let thisTitle: String + if message.media.first is TelegramMediaImage { + thisTitle = L10n.galleryContextShareThisPhoto + } else { + thisTitle = L10n.galleryContextShareThisVideo + } + + items.append(SPopoverItem(thisTitle, { [weak self] in + guard let `self` = self else {return} + showModal(with: ShareModalController(ShareMessageObject(self.context, message)), for: self.window) + + })) + + let allTitle: String + if messages.filter({$0.media.first is TelegramMediaImage}).count == messages.count { + allTitle = L10n.galleryContextShareAllPhotosCountable(messages.count) + } else if messages.filter({$0.media.first is TelegramMediaFile}).count == messages.count { + allTitle = L10n.galleryContextShareAllVideosCountable(messages.count) + } else { + allTitle = L10n.galleryContextShareAllItemsCountable(messages.count) + } + + items.append(SPopoverItem(allTitle, { [weak self] in + guard let `self` = self else {return} + showModal(with: ShareModalController(ShareMessageObject(self.context, message, messages)), for: self.window) + })) + + + showPopover(for: control, with: SPopoverViewController(items: items), inset:NSMakePoint((-125 + 14),0), static: true) + } else { + showModal(with: ShareModalController(ShareMessageObject(self.context, message)), for: self.window) + } + } + } + @objc func copy(_ sender:Any? = nil) -> Void { if let item = self.pager.selectedItem { - if !(item is MGalleryExternalVideoItem) { + if !(item is MGalleryExternalVideoItem), item.entry.message?.containsSecretMedia != true { operationDisposable.set((item.path.get() |> take(1) |> deliverOnMainQueue).start(next: { path in let pb = NSPasteboard.general pb.clearContents() @@ -632,21 +1147,47 @@ class GalleryViewer: NSResponder { fileprivate func show(_ animated: Bool = true, _ ignoreStableId:AnyHashable? = nil) -> Void { viewer = self - window.makeFirstResponder(self) + mainWindow.resignFirstResponder() + self.window.makeKeyAndOrderFront(nil) + //window.makeFirstResponder(self) //closePipVideo() - backgroundView.change(opacity: 0, animated: false) + // backgroundView.alphaValue = 0 + backgroundView._change(opacity: 0, animated: false) self.readyDispose.set((self.ready.get() |> take(1) |> deliverOnMainQueue).start { [weak self] in if let strongSelf = self { - strongSelf.backgroundView.change(opacity: 1, animated: animated) + + if let startTime = strongSelf.contentInteractions?.timeCodeInitializer { + if let item = strongSelf.pager.selectedItem as? MGalleryVideoItem { + item.startTime = startTime + } + } + strongSelf.pager.animateIn(from: { [weak strongSelf] stableId -> NSView? in + + + if let firstStableId = strongSelf?.firstStableId, let innerIndex = stableId.base as? Int { + if let ignore = ignoreStableId?.base as? Int, ignore == innerIndex { + return nil + } + let view = strongSelf?.delegate?.contentInteractionView(for: firstStableId, animateIn: false) + return view?.subviews[innerIndex] + } if ignoreStableId != stableId { - return strongSelf?.delegate?.contentInteractionView(for: stableId) + return strongSelf?.delegate?.contentInteractionView(for: stableId, animateIn: false) } + return nil }, completion:{ [weak strongSelf] in + //strongSelf?.backgroundView.alphaValue = 1.0 strongSelf?.controls.animateIn() + strongSelf?.backgroundView._change(opacity: 1, animated: false) + }, addAccesoryOnCopiedView: { stableId, view in + if let stableId = stableId { + //self?.delegate?.addAccesoryOnCopiedView(for: stableId, view: view) + } + }, addVideoTimebase: { stableId, view in + }) - strongSelf.window.makeKeyAndOrderFront(nil) } }); @@ -656,16 +1197,30 @@ class GalleryViewer: NSResponder { disposable.dispose() readyDispose.dispose() didSetReady = false - + NotificationCenter.default.removeObserver(self) if animated { - backgroundView.change(opacity: 0, duration: 0.15, timingFunction: kCAMediaTimingFunctionSpring) + backgroundView._change(opacity: 0, animated: false) controls.animateOut() - self.pager.animateOut(to: {[weak self] (stableId) -> NSView? in - return self?.delegate?.contentInteractionView(for: stableId) - }, completion: { [weak self] in + self.pager.animateOut(to: { [weak self] stableId in + if let firstStableId = self?.firstStableId, let innerIndex = stableId.base as? Int { + let view = self?.delegate?.contentInteractionView(for: firstStableId, animateIn: false) + return view?.subviews[innerIndex] + } + return self?.delegate?.contentInteractionView(for: stableId, animateIn: true) + }, completion: { [weak self] interactive, stableId in + + if let stableId = stableId { + self?.delegate?.interactionControllerDidFinishAnimation(interactive: interactive, for: stableId) + } self?.window.orderOut(nil) viewer = nil playPipIfNeeded() + }, addAccesoryOnCopiedView: { [weak self] stableId, view in + if let stableId = stableId { + self?.delegate?.addAccesoryOnCopiedView(for: stableId, view: view) + } + }, addVideoTimebase: { stableId, view in + }) } else { window.orderOut(nil) @@ -676,43 +1231,64 @@ class GalleryViewer: NSResponder { } deinit { + clean() + } + + func clean() { indexDisposable.dispose() disposable.dispose() operationDisposable.dispose() window.removeAllHandlers(for: self) readyDispose.dispose() + messagesActionDisposable.dispose() + self.contentInteractions?.remove_timeCodeInitializer() } + } + func closeGalleryViewer(_ animated: Bool) { viewer?.close(animated) } -func showChatGallery(account:Account, message:Message, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType = .history) { +func showChatGallery(context: AccountContext, message:Message, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType = .history, reversed: Bool = false) { if viewer == nil { - let gallery = GalleryViewer(account: account, message: message, delegate, contentInteractions, type: type) + viewer?.clean() + let gallery = GalleryViewer(context: context, message: message, delegate, contentInteractions, type: type, reversed: reversed) gallery.show() } } -func showGalleryFromPip(item: MGalleryItem, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType = .history) { - if viewer == nil, let message = item.entry.message { - let gallery = GalleryViewer(account: item.account, message: message, delegate, contentInteractions, type: type, item: item) +func showGalleryFromPip(item: MGalleryItem, gallery: GalleryViewer, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType = .history) { + if viewer == nil { + viewer?.clean() gallery.show(true, item.stableId) } } -func showPhotosGallery(account:Account, peerId:PeerId, firstStableId:AnyHashable, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil) { +func showPhotosGallery(context: AccountContext, peerId:PeerId, firstStableId:AnyHashable, _ delegate:InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil) { if viewer == nil { - let gallery = GalleryViewer(account: account, peerId: peerId, firstStableId: firstStableId, delegate, contentInteractions) + viewer?.clean() + let gallery = GalleryViewer(context: context, peerId: peerId, firstStableId: firstStableId, delegate, contentInteractions) gallery.show() } } -func showInstantViewGallery(account: Account, medias:[InstantPageMedia], firstIndex: Int, _ delegate: InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaGalleryParameters? = nil) { +func showInstantViewGallery(context: AccountContext, medias:[InstantPageMedia], firstIndex: Int, firstStableId:AnyHashable? = nil, parent: Message? = nil, _ delegate: InteractionContentViewProtocol? = nil, _ contentInteractions:ChatMediaLayoutParameters? = nil) { if viewer == nil { - let gallery = GalleryViewer(account: account, instantMedias: medias, firstIndex: firstIndex, delegate, contentInteractions) + viewer?.clean() + let gallery = GalleryViewer(context: context, instantMedias: medias, firstIndex: firstIndex, firstStableId: firstStableId, parent: parent, delegate, contentInteractions) gallery.show() } } + +func showSecureIdDocumentsGallery(context: AccountContext, medias:[SecureIdDocumentValue], firstIndex: Int, _ delegate: InteractionContentViewProtocol? = nil) { + if viewer == nil { + viewer?.clean() + let gallery = GalleryViewer(context: context, secureIdMedias: medias, firstIndex: firstIndex, delegate) + gallery.show() + } + +} + diff --git a/Telegram-Mac/GeneralBlockTextRowItem.swift b/Telegram-Mac/GeneralBlockTextRowItem.swift new file mode 100644 index 0000000000..fa895a4004 --- /dev/null +++ b/Telegram-Mac/GeneralBlockTextRowItem.swift @@ -0,0 +1,152 @@ +// +// GeneralBlockTextRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +struct GeneralBlockTextHeader { + let text: String + let icon: CGImage? + init(text: String, icon: CGImage?) { + self.text = text + self.icon = icon + } +} + +class GeneralBlockTextRowItem: GeneralRowItem { + fileprivate let textLayout: TextViewLayout + fileprivate let header: GeneralBlockTextHeader? + fileprivate let headerLayout: TextViewLayout? + init(_ initialSize: NSSize, stableId: AnyHashable, viewType: GeneralViewType, text: String, font: NSFont, header: GeneralBlockTextHeader? = nil) { + self.textLayout = TextViewLayout(.initialize(string: text, color: theme.colors.text, font: font), alwaysStaticItems: false) + self.header = header + if let header = header { + self.headerLayout = TextViewLayout(.initialize(string: header.text, color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 3) + } else { + self.headerLayout = nil + } + super.init(initialSize, stableId: stableId, viewType: viewType) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + self.textLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) + self.headerLayout?.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) + return true + } + + override func viewClass() -> AnyClass { + return GeneralBlockTextRowView.self + } + + override var height: CGFloat { + var height: CGFloat = textLayout.layoutSize.height + viewType.innerInset.bottom + viewType.innerInset.top + + if let headerLayout = self.headerLayout { + height += (headerLayout.layoutSize.height + 4) + } + + return height + } +} + + +private final class GeneralBlockTextRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let textView = TextView() + private var headerView: TextView? + private var headerImageView : ImageView? + private let separator: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(containerView) + containerView.addSubview(textView) + containerView.addSubview(separator) + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? GeneralBlockTextRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + self.textView.backgroundColor = backdorColor + self.separator.backgroundColor = theme.colors.border + } + + override func layout() { + super.layout() + guard let item = item as? GeneralBlockTextRowItem else { + return + } + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + if let headerView = headerView { + var inset: CGFloat = 0 + if let headerImageView = headerImageView { + headerImageView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, item.viewType.innerInset.top + 2)) + inset += headerImageView.frame.width + 4 + } + headerView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left + inset, item.viewType.innerInset.top)) + textView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, headerView.frame.maxY + 4)) + } else { + textView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, item.viewType.innerInset.top)) + } + + + separator.frame = NSMakeRect(item.viewType.innerInset.left, containerView.frame.height - .borderSize, containerView.frame.width - item.viewType.innerInset.left - item.viewType.innerInset.right, .borderSize) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? GeneralBlockTextRowItem else { + return + } + + if let headerLayout = item.headerLayout { + if headerView == nil { + self.headerView = TextView() + self.headerView?.userInteractionEnabled = false + self.headerView?.isSelectable = false + containerView.addSubview(self.headerView!) + } + headerView?.update(headerLayout) + + if let image = item.header?.icon { + if headerImageView == nil { + self.headerImageView = ImageView() + containerView.addSubview(self.headerImageView!) + } + headerImageView?.image = image + headerImageView?.sizeToFit() + } + } else { + self.headerView?.removeFromSuperview() + self.headerView = nil + } + + textView.update(item.textLayout) + self.separator.isHidden = !item.viewType.hasBorder + needsLayout = true + } + + override var firstResponder: NSResponder? { + return nil + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/GeneralInputRow.swift b/Telegram-Mac/GeneralInputRow.swift index bbc0f23665..3446b72b79 100644 --- a/Telegram-Mac/GeneralInputRow.swift +++ b/Telegram-Mac/GeneralInputRow.swift @@ -8,7 +8,7 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit enum GeneralInputRowType { case plain @@ -39,32 +39,37 @@ class GeneralInputRowItem: TableRowItem { let placeholder:NSAttributedString let limit:Int32 let holdText:Bool + let automaticallyBecomeResponder: Bool fileprivate let canFastClean: Bool let _stableId:AnyHashable override var stableId: AnyHashable { return _stableId } + fileprivate let font: NSFont - - init(_ initialSize:NSSize, stableId:AnyHashable = arc4random(), placeholder:String, text:String = "", limit:Int32 = 140, insets: NSEdgeInsets = NSEdgeInsets(left:25,right:25,top:2,bottom:3), textChangeHandler:@escaping(String)->Void = {_ in}, textFilter:@escaping(String)->String = {value in return value}, holdText:Bool = false, inputType: GeneralInputRowType = .plain, pasteFilter:((String)->(Bool, String))? = nil, canFastClean: Bool = false) { + init(_ initialSize:NSSize, stableId:AnyHashable = arc4random(), placeholder:String, text:String = "", limit:Int32 = 140, insets: NSEdgeInsets = NSEdgeInsets(left:25,right:25,top:2,bottom:3), textChangeHandler:@escaping(String)->Void = {_ in}, textFilter:@escaping(String)->String = {value in return value}, holdText:Bool = false, font: NSFont = .normal(.text), inputType: GeneralInputRowType = .plain, pasteFilter:((String)->(Bool, String))? = nil, canFastClean: Bool = false, automaticallyBecomeResponder: Bool = true) { _stableId = stableId self.insets = insets + self.automaticallyBecomeResponder = automaticallyBecomeResponder self.pasteFilter = pasteFilter self.holdText = holdText self.canFastClean = canFastClean self.textChangeHandler = textChangeHandler self.limit = limit + self.font = font self.text = text self.inputType = inputType self.textFilter = textFilter self.placeholder = .initialize(string: placeholder, color: theme.colors.grayText, font: .normal(.text), coreText: false) - let textStorage = NSTextStorage(attributedString: .initialize(string: text)) - let textContainer = NSTextContainer(containerSize: NSMakeSize(initialSize.width - insets.left - insets.right, .greatestFiniteMagnitude)) + let textStorage = NSTextStorage(attributedString: .initialize(string: text, font: font, coreText: false)) + let textContainer = NSTextContainer(size: NSMakeSize(initialSize.width - insets.left - insets.right, .greatestFiniteMagnitude)) let layoutManager = NSLayoutManager(); + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) layoutManager.ensureLayout(for: textContainer) @@ -78,9 +83,11 @@ class GeneralInputRowItem: TableRowItem { var _height:CGFloat = 24 override var height: CGFloat { - return _height + insets.top + insets.bottom + return _height + insets.top + insets.bottom + 5 } + + override func viewClass() -> AnyClass { return GeneralInputRowView.self } @@ -92,18 +99,19 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele let textView:TGModernGrowingTextView - private let secureField: NSSecureTextField = NSSecureTextField(frame: NSMakeRect(0, 0, 100, 16)) private let cleanImage: ImageButton = ImageButton() + let separator: View = View() + required init(frame frameRect: NSRect) { - textView = TGModernGrowingTextView(frame: frameRect) + textView = TGModernGrowingTextView(frame: NSMakeRect(25, 0, frameRect.width - 50, frameRect.height)) super.init(frame: frameRect) textView.delegate = self textView.textFont = .normal(.text) - textView.min_height = 16 - + //textView.min_height = 16 + textView.max_height = 1500 secureField.isBordered = false secureField.isBezeled = false secureField.focusRingType = .none @@ -111,14 +119,15 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele secureField.drawsBackground = true secureField.isEditable = true secureField.isSelectable = true - // secureField.wantsLayer = true + secureField.font = .normal(.text) secureField.textView?.insertionPointColor = theme.colors.text secureField.sizeToFit() addSubview(cleanImage) - + addSubview(separator) + cleanImage.set(handler: { [weak self] _ in self?.textView.setString("") self?.secureField.stringValue = "" @@ -126,7 +135,16 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele } - override func controlTextDidChange(_ obj: Notification) { + override func shakeView() { + if !secureField.isHidden { + secureField.shake() + } + if !textView.isHidden { + textView.shake() + } + } + + func controlTextDidChange(_ obj: Notification) { if let item = item as? GeneralInputRowItem { let string = secureField.stringValue let updated = item.textFilter(string) @@ -147,19 +165,14 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele super.layout() if let item = item as? GeneralInputRowItem { textView.frame = NSMakeRect(item.insets.left, item.insets.top, frame.width - item.insets.left - item.insets.right,textView.frame.height) - secureField.frame = NSMakeRect(item.insets.left, item.insets.top, frame.width - item.insets.left - item.insets.right, secureField.frame.height) - cleanImage.centerY(x: frame.width - item.insets.right - cleanImage.frame.width) + separator.frame = NSMakeRect(item.insets.left + 2, frame.height - .borderSize, frame.width - item.insets.left - item.insets.right, .borderSize) } } override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - if let item = item as? GeneralInputRowItem { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.insets.left, frame.height - .borderSize, frame.width - item.insets.left - item.insets.right, .borderSize)) - } + } override func viewDidMoveToWindow() { @@ -167,19 +180,22 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele _ = becomeFirstResponder() } + override func updateColors() { + super.updateColors() + textView.setBackgroundColor(theme.colors.background) + separator.backgroundColor = theme.colors.border + } + override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated:animated) - textView.textColor = theme.colors.text - textView.linkColor = theme.colors.link - - secureField.textColor = theme.colors.text - secureField.backgroundColor = backdorColor + textView.animates = false if let item = item as? GeneralInputRowItem { + cleanImage.set(image: theme.icons.recentDismiss, for: .Normal) - cleanImage.sizeToFit() + _ = cleanImage.sizeToFit() cleanImage.isHidden = (!item.canFastClean || (item.holdText && item.text.isEmpty)) @@ -188,16 +204,18 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele secureField.removeFromSuperview() - addSubview(textView, positioned: .below, relativeTo: cleanImage) + if textView.superview == nil { + addSubview(textView, positioned: .below, relativeTo: cleanImage) + } // secureField.isHidden = true // textView.isHidden = false if item.holdText { - textView.defaultText = item.placeholder.string - // if item.text != textView.string() { - textView.setString(item.text, animated: false) + //if item.text != textView.string().replacingOccurrences(of: item.placeholder.string, with: "") { + // textView.defaultText = item.placeholder.string + textView.setString(item.text, animated: false) // } } else { if textView.placeholderAttributedString == nil || !textView.placeholderAttributedString!.isEqual(to: item.placeholder) { @@ -221,9 +239,14 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele } + textView.textFont = item.font + textView.textColor = theme.colors.text + textView.linkColor = theme.colors.link + secureField.textColor = theme.colors.text + secureField.backgroundColor = backdorColor - } + textView.animates = true needsLayout = true } @@ -232,7 +255,7 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele window?.makeFirstResponder(firstResponder) } - public func maxCharactersLimit() -> Int32 { + public func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { if let item = item as? GeneralInputRowItem { return item.limit } @@ -245,11 +268,14 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele item._height = height table.noteHeightOfRow(item.index,animated) + + separator.change(pos: NSMakePoint(separator.frame.minX, frame.height - .borderSize), animated: animated) + } } - func textViewSize() -> NSSize { + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { return textView.frame.size } @@ -284,6 +310,10 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele } + func textViewDidReachedLimit(_ textView: Any) { + self.textView.shake() + } + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { if let item = item as? GeneralInputRowItem, let pasteFilter = item.pasteFilter { if let string = pasteboard.string(forType: .string) { @@ -319,7 +349,9 @@ class GeneralInputRowView: TableRowView,TGModernGrowingDelegate, NSTextFieldDele } override func becomeFirstResponder() -> Bool { - window?.makeFirstResponder(firstResponder) + if let item = item as? GeneralInputRowItem, item.automaticallyBecomeResponder { + window?.makeFirstResponder(firstResponder) + } return true } diff --git a/Telegram-Mac/GeneralInteractedRowItem.swift b/Telegram-Mac/GeneralInteractedRowItem.swift index 2753a45238..85041e4e90 100644 --- a/Telegram-Mac/GeneralInteractedRowItem.swift +++ b/Telegram-Mac/GeneralInteractedRowItem.swift @@ -8,12 +8,18 @@ import Cocoa import TGUIKit - +import SwiftSignalKit struct GeneralThumbAdditional { let thumb:CGImage let textInset:CGFloat? + let thumbInset: CGFloat? + init(thumb: CGImage, textInset: CGFloat? = nil, thumbInset: CGFloat? = nil) { + self.thumb = thumb + self.textInset = textInset + self.thumbInset = thumbInset + } } @@ -25,28 +31,57 @@ class GeneralInteractedRowItem: GeneralRowItem { var descLayout:TextViewLayout? var nameStyle:ControlStyle let thumb:GeneralThumbAdditional? + let activeThumb:GeneralThumbAdditional? let switchAppearance: SwitchViewAppearance + let autoswitch: Bool + + let disabledAction:()->Void + var nameWidth:CGFloat { - var width = self.size.width - (inset.left + inset.right) - switch type { - case .switchable: - width -= 40 - case .context: - width -= 40 - default: - break + switch self.viewType { + case .legacy: + var width = self.size.width - (inset.left + inset.right) + switch type { + case .switchable: + width -= 40 + case .context: + width -= 40 + case .selectable: + width -= 40 + default: + break + } + if let thumb = thumb { + width -= thumb.thumb.backingSize.width + 20 + } + return width + case let .modern(_, insets): + var width = self.blockWidth - (insets.left + insets.right) + switch type { + case .switchable: + width -= 40 + case .context: + width -= 40 + case .selectable: + width -= 40 + default: + break + } + if let thumb = thumb { + width -= thumb.thumb.backingSize.width + 20 + } + return width } - if let thumb = thumb { - width -= thumb.thumb.backingSize.width + 20 - } - return width } + private let menuItems:(()->[ContextMenuItem])? + - init(_ initialSize:NSSize, stableId:AnyHashable = arc4random(), name:String, icon: CGImage? = nil, nameStyle:ControlStyle = ControlStyle(font: NSFont.normal(.title), foregroundColor: theme.colors.text), description: String? = nil, type:GeneralInteractedType = .none, action:@escaping ()->Void = {}, drawCustomSeparator:Bool = true, thumb:GeneralThumbAdditional? = nil, border:BorderType = [], inset: NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), enabled: Bool = true, switchAppearance: SwitchViewAppearance = switchViewAppearance) { + init(_ initialSize:NSSize, stableId:AnyHashable = arc4random(), name:String, icon: CGImage? = nil, activeIcon: CGImage? = nil, nameStyle:ControlStyle = ControlStyle(font: .normal(.title), foregroundColor: theme.colors.text), description: String? = nil, descTextColor: NSColor = theme.colors.grayText, type:GeneralInteractedType = .none, viewType: GeneralViewType = .legacy, action:@escaping ()->Void = {}, drawCustomSeparator:Bool = true, thumb:GeneralThumbAdditional? = nil, border:BorderType = [], inset: NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), enabled: Bool = true, switchAppearance: SwitchViewAppearance = switchViewAppearance, error: InputDataValueError? = nil, autoswitch: Bool = true, disabledAction: @escaping()-> Void = {}, menuItems:(()->[ContextMenuItem])? = nil) { self.name = name + self.menuItems = menuItems if let description = description { - descLayout = TextViewLayout(.initialize(string: description, color: theme.colors.grayText, font: .normal(.text))) + descLayout = TextViewLayout(.initialize(string: description, color: descTextColor, font: .normal(.text))) } else { descLayout = nil } @@ -56,24 +91,41 @@ class GeneralInteractedRowItem: GeneralRowItem { } else { self.thumb = thumb } + self.disabledAction = disabledAction + self.autoswitch = autoswitch + self.activeThumb = activeIcon != nil ? GeneralThumbAdditional(thumb: activeIcon!, textInset: nil) : self.thumb self.switchAppearance = switchAppearance - super.init(initialSize, stableId:stableId, type:type, action:action, drawCustomSeparator:drawCustomSeparator, border:border, inset:inset, enabled: enabled) + super.init(initialSize, height: 0, stableId:stableId, type:type, viewType: viewType, action:action, drawCustomSeparator:drawCustomSeparator, border:border, inset:inset, enabled: enabled, error: error) + _ = makeSize(initialSize.width, oldWidth: 0) } override var height: CGFloat { - if let descLayout = descLayout { - return super.height + descLayout.layoutSize.height + 5 + + switch viewType { + case .legacy: + let height: CGFloat = super.height + 40 + if let descLayout = descLayout { + return height + descLayout.layoutSize.height + } + return height + case let .modern(_, insets): + let height: CGFloat = super.height + insets.top + insets.bottom + nameLayout!.0.size.height + if let descLayout = self.descLayout { + return height + descLayout.layoutSize.height + 2 + } + return height } - return super.height } + + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - - nameLayout = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: name, color: nameStyle.foregroundColor, font: nameStyle.font), nil, 1, .end, NSMakeSize(nameWidth, self.size.height), nil, isSelected, .left) - nameLayoutSelected = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: name, color: .white, font: nameStyle.font), nil, 1, .end, NSMakeSize(nameWidth, self.size.height), nil, isSelected, .left) + let result = super.makeSize(width, oldWidth: oldWidth) + nameLayout = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: name, color: enabled ? nameStyle.foregroundColor : theme.colors.grayText, font: nameStyle.font), nil, 1, .end, NSMakeSize(nameWidth, .greatestFiniteMagnitude), nil, isSelected, .left) + nameLayoutSelected = TextNode.layoutText(maybeNode: nil, NSAttributedString.initialize(string: name, color: theme.colors.underSelectedColor, font: nameStyle.font), nil, 1, .end, NSMakeSize(nameWidth, .greatestFiniteMagnitude), nil, isSelected, .left) descLayout?.measure(width: nameWidth) - return super.makeSize(width, oldWidth: oldWidth) + return result } override func prepare(_ selected: Bool) { @@ -84,4 +136,13 @@ class GeneralInteractedRowItem: GeneralRowItem { return GeneralInteractedRowView.self } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + + if let menuItems = self.menuItems { + return .single(menuItems()) + } else { + return super.menuItems(in: location) + } + } } diff --git a/Telegram-Mac/GeneralInteractedRowView.swift b/Telegram-Mac/GeneralInteractedRowView.swift index 219d91f990..479700ef61 100644 --- a/Telegram-Mac/GeneralInteractedRowView.swift +++ b/Telegram-Mac/GeneralInteractedRowView.swift @@ -11,37 +11,31 @@ import TGUIKit class GeneralInteractedRowView: GeneralRowView { - + + private let containerView: GeneralRowContainerView = GeneralRowContainerView(frame: NSZeroRect) private(set) var switchView:SwitchView? + private(set) var progressView: ProgressIndicator? private(set) var textView:TextView? - private(set) var overlay:OverlayControl = OverlayControl() private(set) var descriptionView: TextView? private var nextView:ImageView = ImageView() + + + override func set(item:TableRowItem, animated:Bool = false) { - overlay.removeAllHandlers() nextView.image = theme.icons.generalNext - overlay.animates = false - // overlay.set(handler: { [weak self] control in - // if let strongSelf = self { - // self?.textView?.backgroundColor = strongSelf.isSelect ? strongSelf.backdorColor : .clear - // } - // }, for: .Highlight) - - // overlay.set(handler: { [weak self] control in - // if let strongSelf = self { - // self?.textView?.backgroundColor = strongSelf.backdorColor - // } - // }, for: .Normal) - // + if let item = item as? GeneralInteractedRowItem { if let descLayout = item.descLayout { if descriptionView == nil { descriptionView = TextView() - addSubview(descriptionView!) + descriptionView?.userInteractionEnabled = false + descriptionView?.isSelectable = false + descriptionView?.isEventLess = true + containerView.addSubview(descriptionView!) } descriptionView?.update(descLayout) } else { @@ -53,176 +47,359 @@ class GeneralInteractedRowView: GeneralRowView { if case let .switchable(stateback) = item.type { if switchView == nil { switchView = SwitchView(frame: NSMakeRect(0, 0, 32, 20)) - addSubview(switchView!) + containerView.addSubview(switchView!) } + switchView?.autoswitch = item.autoswitch switchView?.presentation = item.switchAppearance - switchView?.setIsOn(stateback(),animated:animated) + switchView?.setIsOn(stateback,animated:animated) switchView?.stateChanged = item.action switchView?.userInteractionEnabled = item.enabled + switchView?.isEnabled = item.enabled } else { switchView?.removeFromSuperview() switchView = nil } if case let .image(stateback) = item.type { - nextView.image = stateback() + nextView.image = stateback nextView.sizeToFit() nextView.isHidden = false } - if case let .context(value) = item.type { + switch item.type { + case let .context(value), let .nextContext(value), let .contextSelector(value, _): if textView == nil { textView = TextView() textView?.animates = false textView?.userInteractionEnabled = false - addSubview(textView!) + textView?.isEventLess = true + containerView.addSubview(textView!) } - let layout = TextViewLayout(.initialize(string: value(), color: isSelect ? .white : theme.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1) + let layout = item.isSelected ? nil : TextViewLayout(.initialize(string: value, color: isSelect ? theme.colors.underSelectedColor : theme.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1) textView?.set(layout: layout) nextView.isHidden = false - } else { + default: textView?.removeFromSuperview() textView = nil } - - textView?.backgroundColor = theme.colors.background - - if item.enabled { - overlay.set(handler:{ _ in - item.action() - }, for: .SingleClick) - } - + textView?.backgroundColor = backdorColor if case let .selectable(value) = item.type { - nextView.isHidden = !value() + nextView.isHidden = !value nextView.image = theme.icons.generalCheck nextView.sizeToFit() } + var needNextImage: Bool = false + if case .colorSelector = item.type { + needNextImage = true + } if case .next = item.type { + needNextImage = true + } + if case .nextContext = item.type { + needNextImage = true + } + if case .contextSelector = item.type { + needNextImage = true + } + if needNextImage { nextView.isHidden = false - nextView.image = theme.icons.generalNext + nextView.image = item.isSelected ? nil : theme.icons.generalNext nextView.sizeToFit() - + } + switch item.viewType { + case .legacy: + containerView.setCorners([], animated: false) + case .modern: + containerView.setCorners(self.isResorting ? GeneralViewItemCorners.all : item.viewType.corners, animated: animated) } - + switch item.type { + case .loading: + if progressView == nil { + self.progressView = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) + containerView.addSubview(self.progressView!) + } + default: + self.progressView?.removeFromSuperview() + self.progressView = nil + } + + } super.set(item: item, animated: animated) - self.needsLayout = true + + + containerView.needsLayout = true + containerView.needsDisplay = true } - override var backdorColor: NSColor { - return isSelect ? theme.colors.blueSelect : theme.colors.background + override func updateIsResorting() { + if let item = self.item { + self.set(item: item, animated: true) + } } - override func mouseDown(with event: NSEvent) { - + override var backdorColor: NSColor { + return isSelect ? theme.colors.accentSelect : theme.colors.background } override func updateColors() { - super.updateColors() - descriptionView?.backgroundColor = backdorColor - textView?.backgroundColor = backdorColor - - if isSelect { - // overlay.set(background: .clear, for: .Highlight) - } else { - //overlay.set(background: theme.colors.grayTransparent, for: .Highlight) - + if let item = item as? GeneralInteractedRowItem { + self.background = item.viewType.rowBackground + let highlighted = isSelect ? self.backdorColor : theme.colors.grayHighlight + descriptionView?.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + textView?.backgroundColor = containerView.controlState == .Highlight && !isSelect ? .clear : self.backdorColor + containerView.set(background: self.backdorColor, for: .Normal) + containerView.set(background: highlighted, for: .Highlight) + progressView?.progressColor = theme.colors.grayIcon } - // overlay.set(background: backdorColor, for: .Hover) - + containerView.needsDisplay = true + } + + override func shakeView() { + self.shake() + } + + private var textXAdditional: CGFloat { + var textXAdditional:CGFloat = 0 + guard let item = item as? GeneralInteractedRowItem else {return 0} + let t = item.isSelected ? item.activeThumb : item.thumb + if let thumb = t { + if let textInset = thumb.textInset { + textXAdditional = textInset + } else { + textXAdditional = thumb.thumb.backingSize.width + 10 + } + } + return textXAdditional } override func draw(_ layer: CALayer, in ctx: CGContext) { - - super.draw(layer, in: ctx) - - if let item = item as? GeneralInteractedRowItem { - + + if let item = item as? GeneralInteractedRowItem, layer == containerView.layer { - var textXAdditional:CGFloat = 0 - if let thumb = item.thumb { - let f = focus(thumb.thumb.backingSize) - let icon = isSelect ? ControlStyle(highlightColor: .white).highlight(image: thumb.thumb) : thumb.thumb - ctx.draw(icon, in: NSMakeRect(item.inset.left, f.minY, f.width, f.height)) - if let textInset = thumb.textInset { - textXAdditional = textInset - } else { - textXAdditional = thumb.thumb.backingSize.width + 10 + switch item.viewType { + case .legacy: + super.draw(layer, in: ctx) + let t = item.isSelected ? item.activeThumb : item.thumb + if let thumb = t { + var f = focus(thumb.thumb.backingSize) + if item.descLayout != nil { + f.origin.y = 11 + } + let icon = thumb.thumb //isSelect ? ControlStyle(highlightColor: .white).highlight(image: thumb.thumb) : + ctx.draw(icon, in: NSMakeRect(item.inset.left, f.minY, f.width, f.height)) } - } - - if item.drawCustomSeparator, !isSelect { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(textXAdditional + item.inset.left, frame.height - .borderSize, frame.width - (item.inset.left + item.inset.right + textXAdditional), .borderSize)) - } - - if let nameLayout = (item.isSelected ? item.nameLayoutSelected : item.nameLayout) { - var textRect = focus(NSMakeSize(nameLayout.0.size.width,nameLayout.0.size.height)) - textRect.origin.x = item.inset.left + textXAdditional - textRect.origin.y -= 1 - if item.descLayout != nil { - textRect.origin.y = floorToScreenPixels(frame.height/2) - nameLayout.0.size.height - 2 + + if item.drawCustomSeparator, !isSelect && !self.isResorting { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(textXAdditional + item.inset.left, frame.height - .borderSize, frame.width - (item.inset.left + item.inset.right + textXAdditional), .borderSize)) + } + + if let nameLayout = (item.isSelected ? item.nameLayoutSelected : item.nameLayout) { + var textRect = focus(NSMakeSize(nameLayout.0.size.width,nameLayout.0.size.height)) + textRect.origin.x = item.inset.left + textXAdditional + textRect.origin.y -= 2 + if item.descLayout != nil { + textRect.origin.y = 10 + } + nameLayout.1.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + if case let .colorSelector(stateback) = item.type { + ctx.setFillColor(stateback.cgColor) + ctx.fillEllipse(in: NSMakeRect(frame.width - 14 - item.inset.right - 16, floorToScreenPixels(backingScaleFactor, (frame.height - 14) / 2), 14, 14)) + } + case let .modern(position, insets): + let t = item.isSelected ? item.activeThumb : item.thumb + if let thumb = t { + var f = focus(thumb.thumb.backingSize) + if item.descLayout != nil { + f.origin.y = insets.top + } + let icon = thumb.thumb + ctx.draw(icon, in: NSMakeRect(insets.left + (thumb.thumbInset ?? 0), f.minY, f.width, f.height)) + } + + if position.border, !isSelect && !self.isResorting { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(textXAdditional + insets.left, containerView.frame.height - .borderSize, containerView.frame.width - (insets.left + insets.right + textXAdditional), .borderSize)) } - nameLayout.1.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor) + if let nameLayout = (item.isSelected ? item.nameLayoutSelected : item.nameLayout) { + var textRect = focus(NSMakeSize(nameLayout.0.size.width,nameLayout.0.size.height)) + textRect.origin.x = insets.left + textXAdditional + textRect.origin.y = insets.top - 1 + + nameLayout.1.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + if case let .colorSelector(stateback) = item.type { + ctx.setFillColor(stateback.cgColor) + ctx.fillEllipse(in: NSMakeRect(containerView.frame.width - 14 - insets.right, floorToScreenPixels(backingScaleFactor, (containerView.frame.height - 14) / 2), 14, 14)) + } } + + } } + + required init(frame frameRect: NSRect) { super.init(frame: frameRect) - addSubview(overlay) nextView.sizeToFit() - addSubview(nextView) + containerView.addSubview(nextView) + self.containerView.displayDelegate = self + self.addSubview(self.containerView) + + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Highlight) + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Normal) + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Hover) + + containerView.set(handler: { [weak self] _ in + self?.invokeIfNeededDown() + }, for: .Down) + containerView.set(handler: { [weak self] _ in + if let event = NSApp.currentEvent { + self?.mouseDragged(with: event) + } + }, for: .MouseDragging) + + containerView.set(handler: { [weak self] _ in + self?.invokeIfNeededUp() + }, for: .Up) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + func invokeAction(_ item: GeneralInteractedRowItem) { + if item.enabled { + if let textView = self.textView { + switch item.type { + case let .contextSelector(_, items): + showPopover(for: textView, with: SPopoverViewController(items: items), edge: .minX, inset: NSMakePoint(0,-30)) + return + default: + break + } + } + + switch item.type { + case let .switchable(enabled): + if item.autoswitch { + item.type = .switchable(!enabled) + self.switchView?.send(event: .Click) + return + } + default: + break + } + item.action() + } else { + item.disabledAction() + } + } + private func invokeIfNeededUp() { + if let event = NSApp.currentEvent { + if let item = item as? GeneralInteractedRowItem, let table = item.table, table.alwaysOpenRowsOnMouseUp, containerView.mouseInside() { + invokeAction(item) + } else { + super.mouseUp(with: event) + } + } + + } + private func invokeIfNeededDown() { + if let event = NSApp.currentEvent { + if let item = item as? GeneralInteractedRowItem, let table = item.table, !table.alwaysOpenRowsOnMouseUp, containerView.mouseInside() { + invokeAction(item) + } else { + super.mouseDown(with: event) + } + } + } override func layout() { super.layout() if let item = item as? GeneralInteractedRowItem { - let inset = general?.inset ?? NSEdgeInsetsZero - self.overlay.frame = NSMakeRect(inset.left, 0, frame.width - inset.left - inset.right, frame.height) - - if let descriptionView = descriptionView { - descriptionView.setFrameOrigin(inset.left, floorToScreenPixels(frame.height / 2) + 2) - } + let insets = item.inset - let nextInset = nextView.isHidden ? 0 : nextView.frame.width + 6 + (inset.right == 0 ? 10 : 0) - - if let switchView = switchView { - switchView.centerY(x:frame.width - inset.right - switchView.frame.width - nextInset) - } - if let textView = textView { - var width:CGFloat = 100 - if let name = item.nameLayout { - width = frame.width - name.0.size.width - nextInset - inset.right - inset.left - 10 + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + self.containerView.setCorners([]) + if let descriptionView = descriptionView { + descriptionView.setFrameOrigin(insets.left + textXAdditional, floorToScreenPixels(backingScaleFactor, frame.height - descriptionView.frame.height - 6)) } + let nextInset = nextView.isHidden ? 0 : nextView.frame.width + 6 + (insets.right == 0 ? 10 : 0) + if let switchView = switchView { + switchView.centerY(x:frame.width - insets.right - switchView.frame.width - nextInset, addition: -1) + } + if let textView = textView { + var width:CGFloat = 100 + if let name = item.nameLayout { + width = frame.width - name.0.size.width - nextInset - insets.right - insets.left - 10 + } + textView.layout?.measure(width: width) + textView.update(textView.layout) + textView.centerY(x:frame.width - insets.right - textView.frame.width - nextInset, addition: -1) + if !nextView.isHidden { + textView.setFrameOrigin(textView.frame.minX,textView.frame.minY - 1) + } + } + nextView.centerY(x: frame.width - (insets.right == 0 ? 10 : insets.right) - nextView.frame.width) + if let progressView = progressView { + progressView.centerY(x: frame.width - (insets.right == 0 ? 10 : insets.right) - progressView.frame.width, addition: -1) + } + case let .modern(_, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), insets.top, item.blockWidth, frame.height - insets.bottom - insets.top) + self.containerView.setCorners(self.isResorting ? GeneralViewItemCorners.all : item.viewType.corners) + if let descriptionView = self.descriptionView { + descriptionView.setFrameOrigin(innerInsets.left + textXAdditional, containerView.frame.height - descriptionView.frame.height - innerInsets.bottom) + } + let nextInset = nextView.isHidden ? 0 : nextView.frame.width + 6 - textView.layout?.measure(width: width) - textView.update(textView.layout) - textView.centerY(x:frame.width - inset.right - textView.frame.width - nextInset) - if !nextView.isHidden { - textView.setFrameOrigin(textView.frame.minX,textView.frame.minY - 1) + if let switchView = switchView { + switchView.centerY(x: containerView.frame.width - innerInsets.right - switchView.frame.width - nextInset, addition: -1) + } + if let textView = textView { + var width:CGFloat = 100 + if let name = item.nameLayout { + width = containerView.frame.width - name.0.size.width - innerInsets.right - insets.left - 10 + } + textView.layout?.measure(width: width) + textView.update(textView.layout) + textView.centerY(x: containerView.frame.width - innerInsets.right - textView.frame.width - nextInset) + if !nextView.isHidden { + textView.setFrameOrigin(textView.frame.minX, textView.frame.minY - 1) + } + } + nextView.centerY(x: containerView.frame.width - innerInsets.right - nextView.frame.width, addition: -1) + if let progressView = progressView { + progressView.centerY(x: containerView.frame.width - innerInsets.right - progressView.frame.width, addition: -1) } } - nextView.centerY(x: frame.width - (inset.right == 0 ? 10 : inset.right) - nextView.frame.width) } diff --git a/Telegram-Mac/GeneralLineSeparatorRowItem.swift b/Telegram-Mac/GeneralLineSeparatorRowItem.swift new file mode 100644 index 0000000000..3973c1bec6 --- /dev/null +++ b/Telegram-Mac/GeneralLineSeparatorRowItem.swift @@ -0,0 +1,27 @@ +// +// GeneralLineSeparatorRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 10/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +class GeneralLineSeparatorRowItem: GeneralRowItem { + init(initialSize: NSSize, stableId: AnyHashable, height: CGFloat = .borderSize) { + super.init(initialSize, height: height, stableId: stableId) + } + + override func viewClass() -> AnyClass { + return GeneralLineSeparatorRowView.self + } +} + +private final class GeneralLineSeparatorRowView : TableRowView { + override var backdorColor: NSColor { + return theme.colors.border + } +} diff --git a/Telegram-Mac/GeneralRowItem.swift b/Telegram-Mac/GeneralRowItem.swift index b641a239e6..8daa2792f2 100644 --- a/Telegram-Mac/GeneralRowItem.swift +++ b/Telegram-Mac/GeneralRowItem.swift @@ -9,17 +9,220 @@ import Cocoa import TGUIKit +enum InputDataValueErrorTarget : Equatable { + case data + case files +} + +struct InputDataValueError : Equatable { + let description: String + let target: InputDataValueErrorTarget + init(description: String, target: InputDataValueErrorTarget) { + self.description = description + self.target = target + } +} + +func bestGeneralViewType(_ array:[T], for item: T) -> GeneralViewType where T: AnyObject { + for _ in array { + if item === array.first && item === array.last { + return .singleItem + } else if item === array.first { + return .firstItem + } else if item === array.last { + return .lastItem + } else { + return .innerItem + } + } + return .singleItem +} +func bestGeneralViewType(_ array:[T], for item: T) -> GeneralViewType where T: Equatable { + for _ in array { + if item == array.first && item == array.last { + return .singleItem + } else if item == array.first { + return .firstItem + } else if item == array.last { + return .lastItem + } else { + return .innerItem + } + } + return .singleItem +} -enum GeneralInteractedType { +func bestGeneralViewType(_ array:[T], for i: Int) -> GeneralViewType { + if array.count <= 1 { + return .singleItem + } else if i == 0 { + return .firstItem + } else if i == array.count - 1 { + return .lastItem + } else { + return .innerItem + } +} + +enum GeneralInteractedType : Equatable { case none case next - case selectable(stateback:()->Bool) - case switchable(stateback:()->Bool) - case context(stateback:()->String) - case image(stateback:()->CGImage) - case button(stateback:()->String) - case search(stateback:(String)->Bool) + case nextContext(String) + case selectable(Bool) + case switchable(Bool) + case context(String) + case loading + case image(CGImage) + case button(String) + case search(Bool) + case colorSelector(NSColor) + #if !SHARE + case contextSelector(String, [SPopoverItem]) + #endif +} + + +final class GeneralViewItemCorners : OptionSet { + public var rawValue: Int32 + + public init() { + self.rawValue = 0 + } + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let topLeft = GeneralViewItemCorners(rawValue: (1 << 0)) + public static let topRight = GeneralViewItemCorners(rawValue: (1 << 1)) + public static let bottomLeft = GeneralViewItemCorners(rawValue: (1 << 2)) + public static let bottomRight = GeneralViewItemCorners(rawValue: (1 << 3)) + + static var all: GeneralViewItemCorners { + return [.topLeft, .topRight, .bottomLeft, .bottomRight] + } + +} + +enum GeneralViewItemPosition : Equatable { + case first + case last + case inner + case single + var corners: GeneralViewItemCorners { + switch self { + case .first: + return [.topLeft, .topRight] + case .inner: + return [] + case .last: + return [.bottomLeft, .bottomRight] + case .single: + return [.topLeft, .topRight, .bottomRight, .bottomLeft] + } + } + + + + var border: Bool { + guard theme.colors.listBackground != theme.colors.background else { + return true + } + switch self { + case .first, .inner: + return true + default: + return false + } + } + +} + + +enum GeneralViewType : Equatable { + case legacy + case modern(position: GeneralViewItemPosition, insets: NSEdgeInsets) + + var isPlainMode: Bool { + return theme.colors.listBackground == theme.colors.background || self == .legacy + } + var innerInset: NSEdgeInsets { + switch self { + case .legacy: + return NSEdgeInsetsMake(0, 0, 0, 0) + case let .modern(_, insets): + return insets + } + } + + var rowBackground: NSColor { + switch self { + case .legacy: + return theme.colors.background + case .modern: + return .clear + } + } + + var corners:GeneralViewItemCorners { + switch self { + case .legacy: + return [] + case let .modern(position, _): + return isPlainMode ? [] : position.corners + } + } + var hasBorder: Bool { + switch self { + case .legacy: + return false + case let .modern(position, _): + return position.border + } + } + var position: GeneralViewItemPosition { + switch self { + case .legacy: + return .single + case let .modern(position, _): + return position + } + } + + func withUpdatedInsets(_ insets: NSEdgeInsets) -> GeneralViewType { + switch self { + case .legacy: + return self + case let .modern(position, _): + return .modern(position: position, insets: insets) + } + } + + static var firstItem: GeneralViewType { + return .modern(position: .first, insets: NSEdgeInsetsMake(12, 16, 12, 16)) + } + static var innerItem: GeneralViewType { + return .modern(position: .inner, insets: NSEdgeInsetsMake(12, 16, 12, 16)) + } + static var lastItem: GeneralViewType { + return .modern(position: .last, insets: NSEdgeInsetsMake(12, 16, 12, 16)) + } + static var singleItem: GeneralViewType { + return .modern(position: .single, insets: NSEdgeInsetsMake(12, 16, 12, 16)) + } + static var textTopItem: GeneralViewType { + return .modern(position: .single, insets: NSEdgeInsetsMake(0, 16, 5, 0)) + } + static var textBottomItem: GeneralViewType { + return .modern(position: .single, insets: NSEdgeInsetsMake(5, 16, 0, 0)) + } + static var separator: GeneralViewType { + return .modern(position: .single, insets: NSEdgeInsetsMake(0, 0, 0, 0)) + } + static func plain(_ position: GeneralViewItemPosition) -> GeneralViewType { + return .modern(position: position, insets: NSEdgeInsetsMake(0, 0, 0, 0)) + } } class GeneralRowItem: TableRowItem { @@ -28,7 +231,11 @@ class GeneralRowItem: TableRowItem { let enabled: Bool let _height:CGFloat override var height: CGFloat { - return _height + var height = _height + if let errorLayout = errorLayout { + height += errorLayout.layoutSize.height + } + return height } private let _stableId:AnyHashable @@ -44,29 +251,86 @@ class GeneralRowItem: TableRowItem { } } - let inset:NSEdgeInsets + private let _inset: NSEdgeInsets + var inset:NSEdgeInsets { + return _inset + } + private(set) var action:()->Void - private(set) var type:GeneralInteractedType + var type:GeneralInteractedType + + let backgroundColor: NSColor + + let error: InputDataValueError? + let errorLayout: TextViewLayout? - init(_ initialSize: NSSize, height:CGFloat = 40.0, stableId:AnyHashable = arc4random(),type:GeneralInteractedType = .none, action:@escaping()->Void = {}, drawCustomSeparator:Bool = true, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), enabled: Bool = true) { + private(set) var viewType: GeneralViewType + + + func updateViewType(_ viewType: GeneralViewType) { + self.viewType = viewType + } + + init(_ initialSize: NSSize, height:CGFloat = 40.0, stableId:AnyHashable = arc4random(),type:GeneralInteractedType = .none, viewType: GeneralViewType = .legacy, action:@escaping()->Void = {}, drawCustomSeparator:Bool = true, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), enabled: Bool = true, backgroundColor: NSColor? = nil, error: InputDataValueError? = nil) { self.type = type _height = height _stableId = stableId self.border = border - self.inset = inset + self._inset = inset + + if let backgroundColor = backgroundColor { + self.backgroundColor = backgroundColor + } else { + switch viewType { + case .modern: + self.backgroundColor = theme.colors.listBackground + default: + self.backgroundColor = viewType.rowBackground + } + } + self.drawCustomSeparator = drawCustomSeparator self.action = action self.enabled = enabled + self.error = error + self.viewType = viewType + if let error = error { + errorLayout = TextViewLayout(.initialize(string: error.description, color: theme.colors.redUI, font: .normal(.text))) + } else { + errorLayout = nil + } + super.init(initialSize) let _ = self.makeSize(initialSize.width) } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + errorLayout?.measure(width: width - inset.left - inset.right) + return success + } + override var instantlyResize: Bool { return true } + override var canBeAnchor: Bool { + return false + } + + + var blockWidth: CGFloat { + switch self.viewType { + case .legacy: + return self.width - self.inset.left - self.inset.right + case .modern: + return min(600, self.width - self.inset.left - self.inset.right) + } + } + override func viewClass() -> AnyClass { return GeneralRowView.self } diff --git a/Telegram-Mac/GeneralRowView.swift b/Telegram-Mac/GeneralRowView.swift index 5a57dd87f6..970f4fb81d 100644 --- a/Telegram-Mac/GeneralRowView.swift +++ b/Telegram-Mac/GeneralRowView.swift @@ -8,30 +8,235 @@ import Cocoa import TGUIKit -class GeneralRowView: TableRowView,ViewDisplayDelegate { + + +class GeneralContainableRowView : TableRowView { + let containerView = GeneralRowContainerView(frame: NSZeroRect) + let borderView: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + super.addSubview(self.containerView) + containerView.addSubview(borderView) + } + + deinit { + self.containerView.removeAllSubviews() + } + + override func addSubview(_ view: NSView) { + self.containerView.addSubview(view) + } + + override func addSubview(_ view: NSView, positioned place: NSWindow.OrderingMode, relativeTo otherView: NSView?) { + self.containerView.addSubview(view, positioned: place, relativeTo: otherView) + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? GeneralRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + self.borderView.backgroundColor = theme.colors.border + } + + override func layout() { + super.layout() + guard let item = item as? GeneralRowItem else { + return + } + let blockWidth = min(600, frame.width - item.inset.left - item.inset.right) + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - blockWidth) / 2), item.inset.top, blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + borderView.frame = NSMakeRect(item.viewType.innerInset.left, containerView.frame.height - .borderSize, containerView.frame.width - item.viewType.innerInset.left - item.viewType.innerInset.right, .borderSize) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? GeneralRowItem else { + return + } + + borderView.isHidden = !item.viewType.hasBorder + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class GeneralRowContainerView : Control { + private let maskLayer = CAShapeLayer() + private var newPath: CGPath? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layer?.mask = maskLayer + self.maskLayer.disableActions() + } + + private var corners: GeneralViewItemCorners? = nil + func setCorners(_ corners: GeneralViewItemCorners, animated: Bool = false, frame: NSRect? = nil) { + if animated && self.corners != nil { + let newPath = self.createMask(for: corners, frame: frame ?? self.frame) + + var oldPath: CGPath = self.maskLayer.path ?? CGMutablePath() + + if let presentation = self.maskLayer.presentation(), let _ = self.maskLayer.animation(forKey:"path") { + oldPath = presentation.path ?? oldPath + if newPath == self.newPath { + self.corners = corners + return + } + } + self.newPath = newPath + + self.maskLayer.animate(from: oldPath, to: newPath, keyPath: "path", timingFunction: .easeOut, duration: 0.18, removeOnCompletion: false, additive: false, completion: { [weak self] completed in + //if completed { + self?.maskLayer.removeAllAnimations() + // } + self?.maskLayer.path = newPath + }) + + } else { + self.maskLayer.path = createMask(for: corners, frame: frame ?? self.bounds) + } + self.corners = corners + } + private func createMask(for corners: GeneralViewItemCorners, frame: NSRect) -> CGPath { + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = 0 + var bottomLeftRadius: CGFloat = 0 + var topRightRadius: CGFloat = 0 + var bottomRightRadius: CGFloat = 0 + + + if corners.contains(.topLeft) { + bottomLeftRadius = 10 + } + if corners.contains(.topRight) { + bottomRightRadius = 10 + } + if corners.contains(.bottomLeft) { + topLeftRadius = 10 + } + if corners.contains(.bottomRight) { + topRightRadius = 10 + } + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topRightRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topLeftRadius) + + + return path + } + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + func change(size: NSSize, animated: Bool, corners: GeneralViewItemCorners) { + super._change(size: size, animated: animated, animated, duration: 0.18) + setCorners(corners, animated: animated, frame: NSMakeRect(0, 0, size.width, size.height)) + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class GeneralRowView: TableRowView,ViewDisplayDelegate { + + private var errorTextView: TextView? = nil var general:GeneralRowItem? { return self.item as? GeneralRowItem } + + required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay } + override var firstResponder: NSResponder? { + return nil + } + + + override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) if let item = item as? GeneralRowItem { self.border = item.border + + let minX = (frame.width - item.blockWidth) / 2 + + + if let errorLayout = item.errorLayout { + let alphaAnimated = animated && errorTextView == nil + let posAnimated = animated && errorTextView != nil + if errorTextView == nil { + errorTextView = TextView() + errorTextView?.isSelectable = false + addSubview(errorTextView!) + } + errorTextView!.update(errorLayout) + switch item.viewType { + case .legacy: + errorTextView!.change(pos: NSMakePoint(item.inset.left, frame.height - 6 - errorLayout.layoutSize.height), animated: posAnimated) + case let .modern(_, insets): + errorTextView!.change(pos: NSMakePoint(minX + insets.left, frame.height - 2 - errorLayout.layoutSize.height), animated: posAnimated) + } + if alphaAnimated { + errorTextView!.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } else { + if let errorTextView = self.errorTextView { + if animated { + self.errorTextView = nil + errorTextView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, completion: { [weak errorTextView] _ in + errorTextView?.removeFromSuperview() + }) + } else { + errorTextView.removeFromSuperview() + self.errorTextView = nil + } + } + + } } self.needsDisplay = true + self.needsLayout = true } override func draw(_ layer: CALayer, in ctx: CGContext) { + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(layer.bounds) + } super.draw(layer, in: ctx) } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -44,8 +249,29 @@ class GeneralRowView: TableRowView,ViewDisplayDelegate { // overlay.frame = NSMakeRect(inset.left, 0, newSize.width - (inset.left + inset.right), newSize.height) } + override func layout() { + + guard let item = item as? GeneralRowItem else {return} + + let minX = (frame.width - item.blockWidth) / 2 + + if let errorTextView = errorTextView { + switch item.viewType { + case .legacy: + errorTextView.setFrameOrigin(item.inset.left, frame.height - 6 - errorTextView.frame.height) + case let .modern(_, insets): + errorTextView.setFrameOrigin(minX + insets.left, frame.height - 2 + - errorTextView.frame.height) + } + } + } + override var backdorColor: NSColor { - return theme.colors.background + + guard let item = self.item as? GeneralRowItem else { + return .clear + } + return item.backgroundColor } } diff --git a/Telegram-Mac/GeneralSettingsViewController.swift b/Telegram-Mac/GeneralSettingsViewController.swift index 91adcac9c5..31b0130ed6 100644 --- a/Telegram-Mac/GeneralSettingsViewController.swift +++ b/Telegram-Mac/GeneralSettingsViewController.swift @@ -8,51 +8,70 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox private enum GeneralSettingsEntry : Comparable, Identifiable { case section(sectionId:Int) case header(sectionId: Int, uniqueId:Int, text:String) - case handleInAppKeys(sectionId:Int, enabled:Bool) - case darkMode(sectionId:Int, enabled: Bool) - case fontSize(sectionId:Int, enabled: Bool) - case sidebar(sectionId:Int, enabled: Bool) - case inAppSounds(sectionId:Int, enabled: Bool) - case enterBehavior(sectionId:Int, enabled: Bool) - case cmdEnterBehavior(sectionId:Int, enabled: Bool) - case emojiReplacements(sectionId:Int, enabled: Bool) - case forceTouchReply(sectionId:Int, enabled: Bool) - case forceTouchEdit(sectionId:Int, enabled: Bool) - case forceTouchForward(sectionId:Int, enabled: Bool) + case sidebar(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case inAppSounds(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case shortcuts(sectionId: Int, viewType: GeneralViewType) + case enterBehavior(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case cmdEnterBehavior(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case emojiReplacements(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case predictEmoji(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case bigEmoji(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case statusBar(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case showCallsTab(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case enableRFTCopy(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case openChatAtLaunch(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case acceptSecretChats(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case forceTouchReply(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case forceTouchEdit(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case forceTouchForward(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case forceTouchPreviewMedia(sectionId:Int, enabled: Bool, viewType: GeneralViewType) var stableId: Int { switch self { case let .header(_, uniqueId, _): return uniqueId - case .darkMode: - return 0 - case .fontSize: + case .sidebar: return 1 - case .enterBehavior: + case .emojiReplacements: return 2 - case .cmdEnterBehavior: + case .predictEmoji: return 3 - case .handleInAppKeys: + case .bigEmoji: return 4 - case .sidebar: + case .showCallsTab: return 5 - case .inAppSounds: + case .statusBar: return 6 - case .emojiReplacements: + case .inAppSounds: return 7 - case .forceTouchReply: + case .shortcuts: return 8 - case .forceTouchEdit: + case .enableRFTCopy: return 9 - case .forceTouchForward: + case .openChatAtLaunch: return 10 + case .acceptSecretChats: + return 11 + case .forceTouchReply: + return 12 + case .forceTouchEdit: + return 13 + case .forceTouchForward: + return 14 + case .forceTouchPreviewMedia: + return 15 + case .enterBehavior: + return 16 + case .cmdEnterBehavior: + return 17 case let .section(id): return (id + 1) * 1000 - id } @@ -62,27 +81,39 @@ private enum GeneralSettingsEntry : Comparable, Identifiable { switch self { case let .header(sectionId, _, _): return (sectionId * 1000) + stableId - case let .fontSize(sectionId, _): + case let .showCallsTab(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .enableRFTCopy(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .openChatAtLaunch(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .acceptSecretChats(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .sidebar(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .inAppSounds(sectionId, _, _): return (sectionId * 1000) + stableId - case let .darkMode(sectionId, _): + case let .shortcuts(sectionId, _): return (sectionId * 1000) + stableId - case let .sidebar(sectionId, _): + case let .emojiReplacements(sectionId, _, _): return (sectionId * 1000) + stableId - case let .inAppSounds(sectionId, _): + case let .predictEmoji(sectionId, _, _): return (sectionId * 1000) + stableId - case let .emojiReplacements(sectionId, _): + case let .bigEmoji(sectionId, _, _): return (sectionId * 1000) + stableId - case let .handleInAppKeys(sectionId, _): + case let .statusBar(sectionId, _, _): return (sectionId * 1000) + stableId - case let .enterBehavior(sectionId, _): + case let .enterBehavior(sectionId, _, _): return (sectionId * 1000) + stableId - case let .cmdEnterBehavior(sectionId, _): + case let .cmdEnterBehavior(sectionId, _, _): return (sectionId * 1000) + stableId - case let .forceTouchReply(sectionId, _): + case let .forceTouchReply(sectionId, _, _): return (sectionId * 1000) + stableId - case let .forceTouchEdit(sectionId, _): + case let .forceTouchEdit(sectionId, _, _): return (sectionId * 1000) + stableId - case let .forceTouchForward(sectionId, _): + case let .forceTouchForward(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .forceTouchPreviewMedia(sectionId, _, _): return (sectionId * 1000) + stableId case let .section(id): return (id + 1) * 1000 - id @@ -92,245 +123,195 @@ private enum GeneralSettingsEntry : Comparable, Identifiable { func item(_ arguments:GeneralSettingsArguments, initialSize:NSSize) -> TableRowItem { switch self { case .section: - return GeneralRowItem(initialSize, height: 30, stableId: stableId) - case let .fontSize(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsLargeFonts), description: tr(.generalSettingsFontDescription), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { - arguments.toggleFonts(!enabled) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .header(sectionId: _, uniqueId: _, text: text): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0), viewType: .textTopItem) + case let .showCallsTab(sectionId: _, enabled: enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsShowCallsTab, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleCallsTab(!enabled) }) - case let .darkMode(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsDarkMode), description: tr(.generalSettingsDarkModeDescription), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { - _ = updateThemeSettings(postbox: arguments.account.postbox, pallete: !enabled ? darkPallete : whitePallete, dark: !enabled).start() - + case let .enableRFTCopy(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsCopyRTF, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleRTFEnabled(!enabled) }) - case let .handleInAppKeys(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsMediaKeysForInAppPlayer), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { - arguments.toggleInAppKeys(!enabled) + case let .openChatAtLaunch(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsOpenLatestChatOnLaunch, type: .switchable(enabled), viewType: viewType, action: { + arguments.openChatAtLaunch(!enabled) }) - case let .sidebar(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsEnableSidebar), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { + case let .acceptSecretChats(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsAcceptSecretChats, type: .switchable(enabled), viewType: viewType, action: { + arguments.acceptSecretChats(!enabled) + }) + case let .sidebar(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsEnableSidebar, type: .switchable(enabled), viewType: viewType, action: { arguments.toggleSidebar(!enabled) }) - case let .inAppSounds(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsInAppSounds), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { + case let .inAppSounds(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsInAppSounds, type: .switchable(enabled), viewType: viewType, action: { arguments.toggleInAppSounds(!enabled) }) - case let .emojiReplacements(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.generalSettingsEmojiReplacements), type: .switchable(stateback: { () -> Bool in - return enabled - }), action: { + case let .shortcuts(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsShortcuts, type: .nextContext("⌘ + ?"), viewType: viewType, action: { + arguments.openShortcuts() + }) + case let .emojiReplacements(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsEmojiReplacements, type: .switchable(enabled), viewType: viewType, action: { arguments.toggleEmojiReplacements(!enabled) }) - case let .header(sectionId: _, uniqueId: _, text: text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text, drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case let .enterBehavior(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, name: tr(.generalSettingsSendByEnter), type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + case let .predictEmoji(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsEmojiPrediction, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleEmojiPrediction(!enabled) + }) + case let .bigEmoji(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsBigEmoji, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleBigEmoji(!enabled) + }) + case let .statusBar(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.generalSettingsStatusBarItem, type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleStatusBar(!enabled) + }) + case let .enterBehavior(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsSendByEnter, type: .selectable(enabled), viewType: viewType, action: { arguments.toggleInput(.enter) }) - case let .cmdEnterBehavior(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, name: tr(.generalSettingsSendByCmdEnter), type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + case let .cmdEnterBehavior(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsSendByCmdEnter, type: .selectable(enabled), viewType: viewType, action: { arguments.toggleInput(.cmdEnter) }) - case let .forceTouchEdit(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, name: tr(.generalSettingsForceTouchEdit), type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + case let .forceTouchEdit(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsForceTouchEdit, type: .selectable(enabled), viewType: viewType, action: { arguments.toggleForceTouchAction(.edit) }) - case let .forceTouchReply(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, name: tr(.generalSettingsForceTouchReply), type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + case let .forceTouchReply(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsForceTouchReply, type: .selectable(enabled), viewType: viewType, action: { arguments.toggleForceTouchAction(.reply) }) - case let .forceTouchForward(sectionId: _, enabled: enabled): - return GeneralInteractedRowItem(initialSize, name: tr(.generalSettingsForceTouchForward), type: .selectable(stateback: { () -> Bool in - return enabled - }), action: { + case let .forceTouchForward(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsForceTouchForward, type: .selectable(enabled), viewType: viewType, action: { arguments.toggleForceTouchAction(.forward) }) - } - } - -} - -private func ==(lhs: GeneralSettingsEntry, rhs: GeneralSettingsEntry) -> Bool { - switch lhs { - case let .header(sectionId, uniqueId, text): - if case .header(sectionId, uniqueId, text) = rhs { - return true - } else { - return false - } - case let .fontSize(sectionId, enabled): - if case .fontSize(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .darkMode(sectionId, enabled): - if case .darkMode(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .handleInAppKeys(sectionId, enabled): - if case .handleInAppKeys(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .sidebar(sectionId, enabled): - if case .sidebar(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .inAppSounds(sectionId, enabled): - if case .inAppSounds(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .emojiReplacements(sectionId, enabled): - if case .emojiReplacements(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .enterBehavior(sectionId, enabled): - if case .enterBehavior(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .cmdEnterBehavior(sectionId, enabled): - if case .cmdEnterBehavior(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .forceTouchReply(sectionId, enabled): - if case .forceTouchReply(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .forceTouchEdit(sectionId, enabled): - if case .forceTouchEdit(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .forceTouchForward(sectionId, enabled): - if case .forceTouchForward(sectionId, enabled) = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false + case let .forceTouchPreviewMedia(sectionId: _, enabled, viewType): + return GeneralInteractedRowItem(initialSize, name: L10n.generalSettingsForceTouchPreviewMedia, type: .selectable(enabled), viewType: viewType, action: { + arguments.toggleForceTouchAction(.previewMedia) + }) } } } - private func <(lhs: GeneralSettingsEntry, rhs: GeneralSettingsEntry) -> Bool { return lhs.sortIndex < rhs.sortIndex } private final class GeneralSettingsArguments { - let account:Account - let toggleFonts:(Bool) -> Void + let context:AccountContext + let toggleCallsTab:(Bool) -> Void let toggleInAppKeys:(Bool) -> Void let toggleInput:(SendingType)-> Void let toggleSidebar:(Bool) -> Void let toggleInAppSounds:(Bool) -> Void let toggleEmojiReplacements:(Bool) -> Void let toggleForceTouchAction:(ForceTouchAction) -> Void - init(account:Account, toggleFonts:@escaping(Bool)-> Void, toggleInAppKeys: @escaping(Bool) -> Void, toggleInput: @escaping(SendingType)-> Void, toggleSidebar: @escaping (Bool) -> Void, toggleInAppSounds: @escaping (Bool) -> Void, toggleEmojiReplacements:@escaping(Bool) -> Void, toggleForceTouchAction: @escaping(ForceTouchAction)->Void) { - self.account = account - self.toggleFonts = toggleFonts + let toggleInstantViewScrollBySpace:(Bool) -> Void + let toggleAutoplayGifs:(Bool) -> Void + let toggleEmojiPrediction: (Bool)->Void + let toggleBigEmoji: (Bool) -> Void + let toggleStatusBar: (Bool) -> Void + let toggleRTFEnabled: (Bool) -> Void + let openChatAtLaunch:(Bool)->Void + let acceptSecretChats:(Bool)->Void + let toggleWorkMode:(Bool)->Void + let openShortcuts: ()->Void + init(context:AccountContext, toggleCallsTab:@escaping(Bool)-> Void, toggleInAppKeys: @escaping(Bool) -> Void, toggleInput: @escaping(SendingType)-> Void, toggleSidebar: @escaping (Bool) -> Void, toggleInAppSounds: @escaping (Bool) -> Void, toggleEmojiReplacements:@escaping(Bool) -> Void, toggleForceTouchAction: @escaping(ForceTouchAction)->Void, toggleInstantViewScrollBySpace: @escaping(Bool)->Void, toggleAutoplayGifs:@escaping(Bool) -> Void, toggleEmojiPrediction: @escaping(Bool) -> Void, toggleBigEmoji: @escaping(Bool) -> Void, toggleStatusBar: @escaping(Bool) -> Void, toggleRTFEnabled: @escaping(Bool)->Void, openChatAtLaunch:@escaping(Bool)->Void, acceptSecretChats: @escaping(Bool)->Void, toggleWorkMode:@escaping(Bool)->Void, openShortcuts: @escaping()->Void) { + self.context = context + self.toggleCallsTab = toggleCallsTab self.toggleInAppKeys = toggleInAppKeys self.toggleInput = toggleInput self.toggleSidebar = toggleSidebar self.toggleInAppSounds = toggleInAppSounds self.toggleEmojiReplacements = toggleEmojiReplacements self.toggleForceTouchAction = toggleForceTouchAction + self.toggleInstantViewScrollBySpace = toggleInstantViewScrollBySpace + self.toggleAutoplayGifs = toggleAutoplayGifs + self.toggleEmojiPrediction = toggleEmojiPrediction + self.toggleBigEmoji = toggleBigEmoji + self.toggleStatusBar = toggleStatusBar + self.toggleRTFEnabled = toggleRTFEnabled + self.openChatAtLaunch = openChatAtLaunch + self.acceptSecretChats = acceptSecretChats + self.toggleWorkMode = toggleWorkMode + self.openShortcuts = openShortcuts } } -private func generalSettingsEntries(arguments:GeneralSettingsArguments, baseSettings: BaseApplicationSettings, appearance: Appearance) -> [GeneralSettingsEntry] { +private func generalSettingsEntries(arguments:GeneralSettingsArguments, baseSettings: BaseApplicationSettings, appearance: Appearance, launchSettings: LaunchSettings, secretChatSettings: SecretChatSettings) -> [GeneralSettingsEntry] { var sectionId:Int = 1 var entries:[GeneralSettingsEntry] = [] + var headerUnique:Int = -1 + entries.append(.section(sectionId: sectionId)) sectionId += 1 - var headerUnique:Int = -1 - - entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: tr(.generalSettingsAppearanceSettings))) + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsEmojiAndStickers)) headerUnique -= 1 - entries.append(.darkMode(sectionId: sectionId, enabled: appearance.presentation.dark)) - entries.append(.fontSize(sectionId: sectionId, enabled: appearance.presentation.fontSize > 13.0)) + entries.append(.sidebar(sectionId: sectionId, enabled: FastSettings.sidebarEnabled, viewType: .firstItem)) + entries.append(.emojiReplacements(sectionId: sectionId, enabled: FastSettings.isPossibleReplaceEmojies, viewType: .innerItem)) + if !baseSettings.predictEmoji { + entries.append(.predictEmoji(sectionId: sectionId, enabled: baseSettings.predictEmoji, viewType: .innerItem)) + } + entries.append(.bigEmoji(sectionId: sectionId, enabled: baseSettings.bigEmoji, viewType: .lastItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: tr(.generalSettingsInputSettings))) + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsInterfaceHeader)) headerUnique -= 1 - - entries.append(.enterBehavior(sectionId: sectionId, enabled: FastSettings.sendingType == .enter)) - entries.append(.cmdEnterBehavior(sectionId: sectionId, enabled: FastSettings.sendingType == .cmdEnter)) + entries.append(.showCallsTab(sectionId: sectionId, enabled: baseSettings.showCallsTab, viewType: .firstItem)) + entries.append(.statusBar(sectionId: sectionId, enabled: baseSettings.statusBar, viewType: .innerItem)) + entries.append(.inAppSounds(sectionId: sectionId, enabled: FastSettings.inAppSounds, viewType: .lastItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsShortcutsHeader)) + headerUnique -= 1 + entries.append(.shortcuts(sectionId: sectionId, viewType: .singleItem)) - entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: tr(.generalSettingsGeneralSettings))) + + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsAdvancedHeader)) headerUnique -= 1 + entries.append(.enableRFTCopy(sectionId: sectionId, enabled: FastSettings.enableRTF, viewType: .firstItem)) + // entries.append(.openChatAtLaunch(sectionId: sectionId, enabled: launchSettings.openAtLaunch, viewType: .innerItem)) + entries.append(.acceptSecretChats(sectionId: sectionId, enabled: secretChatSettings.acceptOnThisDevice, viewType: .lastItem)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsForceTouchHeader)) + headerUnique -= 1 - //entries.append(.largeFonts(sectionId: sectionId, enabled: baseSettings.fontSize > 13)) - #if !APP_STORE - entries.append(.handleInAppKeys(sectionId: sectionId, enabled: baseSettings.handleInAppKeys)) - #endif - entries.append(.sidebar(sectionId: sectionId, enabled: FastSettings.sidebarEnabled)) - entries.append(.inAppSounds(sectionId: sectionId, enabled: FastSettings.inAppSounds)) - entries.append(.emojiReplacements(sectionId: sectionId, enabled: FastSettings.isPossibleReplaceEmojies)) + entries.append(.forceTouchReply(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .reply, viewType: .firstItem)) + entries.append(.forceTouchEdit(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .edit, viewType: .innerItem)) + entries.append(.forceTouchForward(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .forward, viewType: .lastItem)) - entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: tr(.generalSettingsForceTouchHeader))) + entries.append(.header(sectionId: sectionId, uniqueId: headerUnique, text: L10n.generalSettingsInputSettings)) headerUnique -= 1 - - entries.append(.forceTouchReply(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .reply)) - entries.append(.forceTouchEdit(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .edit)) - entries.append(.forceTouchForward(sectionId: sectionId, enabled: FastSettings.forceTouchAction == .forward)) + entries.append(.enterBehavior(sectionId: sectionId, enabled: FastSettings.sendingType == .enter, viewType: .firstItem)) + entries.append(.cmdEnterBehavior(sectionId: sectionId, enabled: FastSettings.sendingType == .cmdEnter, viewType: .lastItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 + return entries } @@ -345,24 +326,26 @@ private func prepareEntries(left: [AppearanceWrapperEntry] class GeneralSettingsViewController: TableViewController { - + private let disposable = MetaDisposable() override var removeAfterDisapper:Bool { return true } override func viewDidLoad() { super.viewDidLoad() - readyOnce() - let postbox = account.postbox + + let context = self.context let inputPromise:ValuePromise = ValuePromise(FastSettings.sendingType, ignoreRepeated: true) let forceTouchPromise:ValuePromise = ValuePromise(FastSettings.forceTouchAction, ignoreRepeated: true) - let arguments = GeneralSettingsArguments(account: account, toggleFonts: { enable in - _ = updateApplicationFontSize(postbox: postbox, fontSize: enable ? 15.0 : 13.0).start() + let arguments = GeneralSettingsArguments(context: context, toggleCallsTab: { enable in + _ = updateBaseAppSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings -> BaseApplicationSettings in + return settings.withUpdatedShowCallsTab(enable) + }).start() }, toggleInAppKeys: { enable in - _ = updateBaseAppSettingsInteractively(postbox: postbox, { settings -> BaseApplicationSettings in + _ = updateBaseAppSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings -> BaseApplicationSettings in return settings.withUpdatedInAppKeyHandle(enable) }).start() }, toggleInput: { input in @@ -377,44 +360,80 @@ class GeneralSettingsViewController: TableViewController { }, toggleForceTouchAction: { action in FastSettings.toggleForceTouchAction(action) forceTouchPromise.set(action) + }, toggleInstantViewScrollBySpace: { enable in + FastSettings.toggleInstantViewScrollBySpace(enable) + }, toggleAutoplayGifs: { enable in + FastSettings.toggleAutoPlayGifs(enable) + }, toggleEmojiPrediction: { enable in + _ = updateBaseAppSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings -> BaseApplicationSettings in + return settings.withUpdatedPredictEmoji(enable) + }).start() + }, toggleBigEmoji: { enable in + _ = updateBaseAppSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings -> BaseApplicationSettings in + return settings.withUpdatedBigEmoji(enable) + }).start() + }, toggleStatusBar: { enable in + _ = updateBaseAppSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings -> BaseApplicationSettings in + return settings.withUpdatedStatusBar(enable) + }).start() + }, toggleRTFEnabled: { enable in + FastSettings.enableRTF = enable + }, openChatAtLaunch: { enable in + _ = updateLaunchSettings(context.account.postbox, { + $0.withUpdatedOpenAtLaunch(enable) + }).start() + }, acceptSecretChats: { enable in + _ = context.account.postbox.transaction({ transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.secretChatSettings, { _ in + return SecretChatSettings(acceptOnThisDevice: enable) + }) + }).start() + }, toggleWorkMode: { value in + + }, openShortcuts: { + context.sharedContext.bindings.rootNavigation().push(ShortcutListController(context: context)) }) let initialSize = atomicSize let previos:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - genericView.merge(with: combineLatest(account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.baseAppSettings]) |> deliverOnMainQueue, inputPromise.get() |> deliverOnMainQueue, forceTouchPromise.get() |> deliverOnMainQueue, appearanceSignal) |> map { settings, _, _, appearance -> TableUpdateTransition in + let baseSettingsSignal: Signal = .single(context.sharedContext.baseSettings) |> then(baseAppSettings(accountManager: context.sharedContext.accountManager)) + + let signal = combineLatest(queue: prepareQueue, baseSettingsSignal, inputPromise.get(), forceTouchPromise.get(), appearanceSignal, appLaunchSettings(postbox: context.account.postbox), context.account.postbox.preferencesView(keys: [PreferencesKeys.secretChatSettings])) |> map { settings, _, _, appearance, launchSettings, preferencesView -> TableUpdateTransition in - let baseSettings: BaseApplicationSettings - if let settings = settings.values[ApplicationSpecificPreferencesKeys.baseAppSettings] as? BaseApplicationSettings { - baseSettings = settings - } else { - baseSettings = BaseApplicationSettings.defaultSettings - } + let baseSettings: BaseApplicationSettings = settings + + let secretChatSettings = preferencesView.values[PreferencesKeys.secretChatSettings] as? SecretChatSettings ?? SecretChatSettings.defaultSettings - let entries = generalSettingsEntries(arguments: arguments, baseSettings: baseSettings, appearance: appearance).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) + let entries = generalSettingsEntries(arguments: arguments, baseSettings: baseSettings, appearance: appearance, launchSettings: launchSettings, secretChatSettings: secretChatSettings).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) let previous = previos.swap(entries) return prepareEntries(left: previous, right: entries, arguments: arguments, initialSize: initialSize.modify({$0})) - - } |> deliverOnMainQueue ) + + } |> deliverOnMainQueue + disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) + } private var loggerClickCount = 0 private func incrementLogClick() { loggerClickCount += 1 - let account = self.account + let context = self.context if loggerClickCount == 5 { UserDefaults.standard.set(!UserDefaults.standard.bool(forKey: "enablelogs"), forKey: "enablelogs") - let logs = Logger.shared.collectLogs() |> deliverOnMainQueue |> mapToSignal { logs -> Signal in - return selectModalPeers(account: account, title: "Send Logs", limit: 1, confirmation: {_ in return confirmSignal(for: mainWindow, header: appName, information: "Are you sure you want send logs?")}) |> filter {!$0.isEmpty} |> map {$0.first!} |> mapToSignal { peerId -> Signal in + let logs = Logger.shared.collectLogs() |> deliverOnMainQueue |> mapToSignal { logs -> Signal in + return selectModalPeers(context: context, title: "Send Logs", limit: 1, confirmation: {_ in return confirmSignal(for: mainWindow, information: "Are you sure you want send logs?")}) |> filter {!$0.isEmpty} |> map {$0.first!} |> mapToSignal { peerId -> Signal in let messages = logs.map { (name, path) -> EnqueueMessage in let id = arc4random64() - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)]) - return .message(text: "", attributes: [], media: file, replyToMessageId: nil) + 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)]) + return .message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) } - return enqueueMessages(account: account, peerId: peerId, messages: messages) |> map {_ in} + return enqueueMessages(context: context, peerId: peerId, messages: messages) |> map {_ in} } } _ = logs.start() @@ -425,6 +444,9 @@ class GeneralSettingsViewController: TableViewController { } + deinit { + disposable.dispose() + } override func viewDidAppear(_ animated: Bool) { diff --git a/Telegram-Mac/GeneralTextRowItem.swift b/Telegram-Mac/GeneralTextRowItem.swift index 912613170d..815116f7b7 100644 --- a/Telegram-Mac/GeneralTextRowItem.swift +++ b/Telegram-Mac/GeneralTextRowItem.swift @@ -9,69 +9,129 @@ import Cocoa import TGUIKit -enum GeneralRowTextType { +enum GeneralRowTextType : Equatable { case plain(String) case markdown(String, linkHandler: (String)->Void) + + static func ==(lhs: GeneralRowTextType, rhs: GeneralRowTextType) -> Bool { + switch lhs { + case let .plain(text): + if case .plain(text) = rhs { + return true + } else { + return false + } + case let .markdown(text, _): + if case .markdown(text, _) = rhs { + return true + } else { + return false + } + } + } } -class GeneralTextRowItem: GeneralRowItem { + +class GeneralTextRowItem: GeneralRowItem { + fileprivate let textColor: NSColor fileprivate var layout:TextViewLayout private let text:NSAttributedString private let alignment:NSTextAlignment fileprivate let centerViewAlignment: Bool - init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text:NSAttributedString, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:2), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false) { - self.text = text + fileprivate let additionLoading: Bool + fileprivate let isTextSelectable: Bool + fileprivate let rightItem: InputDataGeneralTextRightData + init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text:NSAttributedString, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0, top:4, bottom:2), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false, additionLoading: Bool = false, additionRightText: String? = nil, linkExecutor: TextViewInteractions = globalLinkExecutor, isTextSelectable: Bool = false, detectLinks: Bool = true, viewType: GeneralViewType = .legacy, rightItem: InputDataGeneralTextRightData = InputDataGeneralTextRightData(isLoading: false, text: nil)) { + self.textColor = theme.colors.listGrayText + self.isTextSelectable = isTextSelectable + let mutable = text.mutableCopy() as! NSMutableAttributedString + if detectLinks { + mutable.detectLinks(type: [.Links], context: nil, openInfo: {_, _, _, _ in }, hashtag: nil, command: nil, applyProxy: nil, dotInMention: false) + } + self.rightItem = rightItem + self.text = mutable + self.additionLoading = additionLoading self.alignment = alignment self.centerViewAlignment = centerViewAlignment - layout = TextViewLayout(text, truncationType: .end, alignment: alignment) - layout.interactions = globalLinkExecutor - super.init(initialSize, height: height, stableId: stableId, type: .none, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) + layout = TextViewLayout(mutable, truncationType: .end, alignment: alignment) + layout.interactions = linkExecutor + super.init(initialSize, height: height, stableId: stableId, type: .none, viewType: viewType, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) } - init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text: GeneralRowTextType, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:2), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false) { + init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text: GeneralRowTextType, detectBold: Bool = true, textColor: NSColor = theme.colors.listGrayText, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0, top:4, bottom:2), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false, additionLoading: Bool = false, isTextSelectable: Bool = false, viewType: GeneralViewType = .legacy, rightItem: InputDataGeneralTextRightData = InputDataGeneralTextRightData(isLoading: false, text: nil), fontSize: CGFloat? = nil) { - let attributedText: NSAttributedString - + let attributedText: NSMutableAttributedString + self.textColor = textColor switch text { case let .plain(text): - attributedText = .initialize(string: text, color: theme.colors.grayText, font: .normal(.custom(11.5))) + attributedText = NSAttributedString.initialize(string: text, color: textColor, font: .normal(fontSize ?? 11.5)).mutableCopy() as! NSMutableAttributedString case let .markdown(text, handler): - attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.custom(11.5)), textColor: theme.colors.grayText), bold: MarkdownAttributeSet(font: .bold(.custom(11.5)), textColor: theme.colors.grayText), link: MarkdownAttributeSet(font: .normal(.custom(11.5)), textColor: theme.colors.link), linkAttribute: { contents in - return (NSAttributedStringKey.link.rawValue, inAppLink.callback(contents, handler)) - })) + attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(fontSize ?? 11.5), textColor: textColor), bold: MarkdownAttributeSet(font: .bold(fontSize ?? 11.5), textColor: textColor), link: MarkdownAttributeSet(font: .normal(fontSize ?? 11.5), textColor: theme.colors.link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, handler)) + })).mutableCopy() as! NSMutableAttributedString + } + if detectBold { + attributedText.detectBoldColorInString(with: .bold(fontSize ?? 11.5)) } + self.rightItem = rightItem self.text = attributedText self.alignment = alignment + self.isTextSelectable = isTextSelectable + self.additionLoading = additionLoading self.centerViewAlignment = centerViewAlignment layout = TextViewLayout(attributedText, truncationType: .end, alignment: alignment) layout.interactions = globalLinkExecutor - super.init(initialSize, height: height, stableId: stableId, type: .none, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) + super.init(initialSize, height: height, stableId: stableId, type: .none, viewType: viewType, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) } - init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text:String, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:2), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false) { - let attr = NSAttributedString.initialize(string: text, color: theme.colors.grayText, font: .normal(.custom(11.5))).mutableCopy() as! NSMutableAttributedString - attr.detectBoldColorInString(with: .medium(.text)) + init(_ initialSize: NSSize, stableId: AnyHashable = arc4random(), height: CGFloat = 0, text:String, detectBold: Bool = true, textColor: NSColor = theme.colors.listGrayText, alignment:NSTextAlignment = .left, drawCustomSeparator:Bool = false, border:BorderType = [], inset:NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), action: @escaping ()->Void = {}, centerViewAlignment: Bool = false, additionLoading: Bool = false, fontSize: CGFloat = 11.5, isTextSelectable: Bool = false, viewType: GeneralViewType = .legacy, rightItem: InputDataGeneralTextRightData = InputDataGeneralTextRightData(isLoading: false, text: nil)) { + let attr = NSAttributedString.initialize(string: text, color: textColor, font: .normal(fontSize)).mutableCopy() as! NSMutableAttributedString + if detectBold { + attr.detectBoldColorInString(with: .medium(fontSize)) + } + self.textColor = textColor self.text = attr self.alignment = alignment + self.isTextSelectable = isTextSelectable + self.additionLoading = additionLoading self.centerViewAlignment = centerViewAlignment layout = TextViewLayout(self.text, truncationType: .end, alignment: alignment) layout.interactions = globalLinkExecutor - super.init(initialSize, height: height, stableId: stableId, type: .none, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) + self.rightItem = rightItem + super.init(initialSize, height: height, stableId: stableId, type: .none, viewType: viewType, action: action, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) } + + override var height: CGFloat { if _height > 0 { return _height } - return layout.layoutSize.height + inset.top + inset.bottom + switch viewType { + case .legacy: + return layout.layoutSize.height + inset.top + inset.bottom + (additionLoading ? 30 : 0) + case let .modern(_, insets): + return layout.layoutSize.height + inset.top + inset.bottom + insets.top + insets.bottom + (additionLoading ? 30 : 0) + } } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - - layout.measure(width: width - inset.left - inset.right) + let success = super.makeSize(width, oldWidth: oldWidth) + switch viewType { + case .legacy: + layout.measure(width: width - inset.left - inset.right) + case let .modern(_, insets): + var addition: CGFloat = 0 + if let text = rightItem.text { + let layout = TextViewLayout(text) + layout.measure(width: .greatestFiniteMagnitude) + addition += layout.layoutSize.width + 20 + } + layout.measure(width: self.blockWidth - insets.left - insets.right - addition) + } - return super.makeSize(width, oldWidth: oldWidth) + return success } override func viewClass() -> AnyClass { @@ -83,29 +143,105 @@ class GeneralTextRowItem: GeneralRowItem { class GeneralTextRowView : GeneralRowView { private let textView:TextView = TextView() - + private var progressView: ProgressIndicator? + private var rightTextView: TextView? + private var animatedView: MediaAnimatedStickerView? required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(textView) - textView.isSelectable = false + } + + override var firstResponder: NSResponder? { + return nil } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - if let item = item as? GeneralTextRowItem, item.drawCustomSeparator { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + if let item = item as? GeneralTextRowItem { + switch item.viewType { + case .legacy: + if item.drawCustomSeparator { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + } + case .modern: + break + } } } + override var backdorColor: NSColor { + if let item = item as? GeneralTextRowItem { + return item.viewType.rowBackground + } + return theme.colors.background + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) - textView.backgroundColor = theme.colors.background + textView.backgroundColor = self.backdorColor + + guard let item = item as? GeneralTextRowItem else {return} + textView.isSelectable = item.isTextSelectable + + if item.additionLoading || item.rightItem.isLoading { + let size = item.rightItem.isLoading ? NSMakeSize(15, 15) : NSMakeSize(20, 20) + if progressView == nil { + progressView = ProgressIndicator(frame: NSMakeRect(0, 0, size.width, size.height)) + } + if progressView!.superview == nil { + addSubview(progressView!) + } + } else { + progressView?.removeFromSuperview() + progressView = nil + } + if let text = item.rightItem.text { + if self.rightTextView == nil { + self.rightTextView = TextView() + addSubview(self.rightTextView!) + } + + + let textLayout = TextViewLayout(text) + textLayout.measure(width: .greatestFiniteMagnitude) + + var animatedData:InputDataTextInsertAnimatedViewData? + text.enumerateAttributes(in: text.range, options: [], using: { data, range, stop in + + if let attr = data[InputDataTextInsertAnimatedViewData.attributeKey] { + animatedData = attr as? InputDataTextInsertAnimatedViewData + } + }) + + if let attr = animatedData { + if self.animatedView == nil { + self.animatedView = MediaAnimatedStickerView(frame: NSZeroRect) + self.addSubview(self.animatedView!) + } + self.animatedView?.update(with: attr.file, size: NSMakeSize(16, 16), context: attr.context, parent: nil, table: nil, parameters: ChatAnimatedStickerMediaLayoutParameters(playPolicy: .loop, media: attr.file), animated: animated, positionFlags: nil, approximateSynchronousValue: true) + + } else { + self.animatedView?.removeFromSuperview() + self.animatedView = nil + } + + self.rightTextView?.update(textLayout) + self.rightTextView?.isSelectable = false + self.rightTextView?.userInteractionEnabled = false + } else { + self.rightTextView?.removeFromSuperview() + self.rightTextView = nil + self.animatedView?.removeFromSuperview() + self.animatedView = nil + } + + needsDisplay = true needsLayout = true } @@ -117,12 +253,51 @@ class GeneralTextRowView : GeneralRowView { } } + override func shakeView() { + textView.shake() + } + override func layout() { super.layout() if let item = item as? GeneralTextRowItem { - textView.update(item.layout, origin:NSMakePoint(item.inset.left, item.inset.top)) + if item.additionLoading, let progressView = progressView { + progressView.centerX(y: 0) + textView.update(item.layout) + textView.centerX(y: progressView.frame.maxY + 10) + } else { + switch item.viewType { + case .legacy: + textView.update(item.layout, origin: NSMakePoint(item.inset.left, item.inset.top)) + case let .modern(_, insets): + let mid = max(0, floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2)) + textView.update(item.layout, origin: NSMakePoint(mid + insets.left, item.inset.top + insets.top)) + + if item.rightItem.isLoading, let progressView = self.progressView { + progressView.setFrameOrigin(NSMakePoint(frame.width - progressView.frame.width - mid - insets.left - insets.right, item.inset.top + insets.top)) + progressView.progressColor = item.textColor + } + if let rightTextView = self.rightTextView { + rightTextView.setFrameOrigin(NSMakePoint(frame.width - rightTextView.frame.width - mid - insets.left - insets.right, frame.height - insets.bottom - rightTextView.frame.height)) + + if let layout = rightTextView.layout { + var animatedRange: NSRange? = nil + layout.attributedString.enumerateAttributes(in: layout.attributedString.range, options: [], using: { data, range, stop in + if let _ = data[InputDataTextInsertAnimatedViewData.attributeKey] { + animatedRange = range + } + }) + if let range = animatedRange, let view = self.animatedView, let offset = layout.offset(for: range.location) { + view.setFrameOrigin(NSMakePoint(rightTextView.frame.minX + offset, rightTextView.frame.minY - 1)) + } + } + } + + } + + + } if item.centerViewAlignment { - textView.center() + textView.center() } } } diff --git a/Telegram-Mac/GeneratedMediaStoreSettings.swift b/Telegram-Mac/GeneratedMediaStoreSettings.swift index d0d7333c29..a0e7724f42 100644 --- a/Telegram-Mac/GeneratedMediaStoreSettings.swift +++ b/Telegram-Mac/GeneratedMediaStoreSettings.swift @@ -7,8 +7,8 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { public let storeEditedPhotos: Bool @@ -47,8 +47,8 @@ public struct GeneratedMediaStoreSettings: PreferencesEntry, Equatable { } func updateGeneratedMediaStoreSettingsInteractively(postbox: Postbox, _ f: @escaping (GeneratedMediaStoreSettings) -> GeneratedMediaStoreSettings) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings, { entry in + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings, { entry in let currentSettings: GeneratedMediaStoreSettings if let entry = entry as? GeneratedMediaStoreSettings { currentSettings = entry diff --git a/Telegram-Mac/Geocoding.swift b/Telegram-Mac/Geocoding.swift new file mode 100644 index 0000000000..9a39958f09 --- /dev/null +++ b/Telegram-Mac/Geocoding.swift @@ -0,0 +1,60 @@ +import Foundation +import CoreLocation +import SwiftSignalKit + +func geocodeLocation(dictionary: [String: String]) -> Signal<(Double, Double)?, NoError> { + return Signal { subscriber in + let geocoder = CLGeocoder() + geocoder.geocodeAddressDictionary(dictionary, completionHandler: { placemarks, _ in + if let location = placemarks?.first?.location { + subscriber.putNext((location.coordinate.latitude, location.coordinate.longitude)) + } else { + subscriber.putNext(nil) + } + subscriber.putCompletion() + }) + return ActionDisposable { + geocoder.cancelGeocode() + } + } +} + +struct ReverseGeocodedPlacemark { + let street: String? + let city: String? + let country: String? + + var fullAddress: String { + var components: [String] = [] + if let street = self.street { + components.append(street) + } + if let city = self.city { + components.append(city) + } + if let country = self.country { + components.append(country) + } + + return components.joined(separator: ", ") + } +} + +func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signal { + return Signal { subscriber in + let geocoder = CLGeocoder() + geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: { placemarks, _ in + if let placemarks = placemarks, let placemark = placemarks.first { + let result = ReverseGeocodedPlacemark(street: placemark.thoroughfare, city: placemark.locality, country: placemark.country) + subscriber.putNext(result) + subscriber.putCompletion() + } else { + subscriber.putNext(nil) + subscriber.putCompletion() + } + }) + return ActionDisposable { + geocoder.cancelGeocode() + } + } +} diff --git a/Telegram-Mac/GifPanelTabRowItem.swift b/Telegram-Mac/GifPanelTabRowItem.swift new file mode 100644 index 0000000000..8c9e6dc761 --- /dev/null +++ b/Telegram-Mac/GifPanelTabRowItem.swift @@ -0,0 +1,110 @@ +// +// GifPanelTabRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 05/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class GifPanelTabRowItem: TableRowItem { + override var stableId: AnyHashable { + return entry + } + + let entry: GifTabEntryId + private let selected: Bool + + let select:(GifTabEntryId)->Void + + fileprivate let icon: CGImage + + init(_ initialSize: NSSize, selected: Bool, entry: GifTabEntryId, select: @escaping(GifTabEntryId)->Void) { + self.selected = selected + self.entry = entry + self.select = select + var icon: CGImage + switch entry { + case .recent: + icon = theme.icons.stickersTabRecent + case .trending: + icon = theme.icons.gif_trending + case let .recommended(value): + icon = generateTextIcon(.initialize(string: value, color: .white, font: .normal(18))) + } + + self.icon = generateImage(NSMakeSize(35, 35), contextGenerator: { size, ctx in + let rect = CGRect(origin: CGPoint(), size: size) + ctx.interpolationQuality = .high + ctx.clear(rect) + if selected { + ctx.round(size, .cornerRadius) + ctx.setFillColor(theme.colors.grayForeground.cgColor) + ctx.fill(rect) + } + ctx.draw(icon, in: rect.focus(icon.backingSize)) + })! + + + super.init(initialSize) + } + + override var height:CGFloat { + return 40.0 + } + override var width: CGFloat { + return 40.0 + } + + override func viewClass() -> AnyClass { + return GifPanelTabRowView.self + } +} + + +private final class GifPanelTabRowView: HorizontalRowView { + + private let control: ImageButton = ImageButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + control.set(handler: { [weak self] control in + if let item = self?.item as? GifPanelTabRowItem { + item.select(item.entry) + } + }, for: .Click) + + + control.autohighlight = false + control.animates = false + control.frame = NSMakeRect(0, 0, 40, 40) + + addSubview(control) + + } + + + override var backdorColor: NSColor { + return .clear + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + if let item = item as? GifPanelTabRowItem { + control.set(image: item.icon, for: .Normal) + control.set(image: item.icon, for: .Highlight) + control.set(image: item.icon, for: .Hover) + + } + control.frame = bounds + control.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/GifPlayerBufferView.swift b/Telegram-Mac/GifPlayerBufferView.swift new file mode 100644 index 0000000000..c4a5db9a52 --- /dev/null +++ b/Telegram-Mac/GifPlayerBufferView.swift @@ -0,0 +1,179 @@ +// +// GifPlayerBufferView.swift +// Telegram +// +// Created by Mikhail Filimonov on 28/05/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import Postbox +import SwiftSignalKit + + +final class GifPlayerBufferView : TransformImageView { + var timebase: CMTimebase + + private var videoLayer: (SoftwareVideoLayerFrameManager, SampleBufferLayer)? + + override init() { + var timebase: CMTimebase? + CMTimebaseCreateWithMasterClock(allocator: nil, masterClock: CMClockGetHostTimeClock(), timebaseOut: &timebase) + CMTimebaseSetRate(timebase!, rate: 0.0) + self.timebase = timebase! + + super.init() + self.layerContentsRedrawPolicy = .duringViewResize + } + + private var fileReference: FileMediaReference? + private var resizeInChat: Bool = false + func update(_ fileReference: FileMediaReference, context: AccountContext, resizeInChat: Bool = false) -> Void { + + let updated = self.fileReference == nil || !fileReference.media.isEqual(to: self.fileReference!.media) + self.fileReference = fileReference + self.resizeInChat = resizeInChat + if updated { + self.videoLayer?.1.layer.removeFromSuperlayer() + + let layerHolder = takeSampleBufferLayer() + if let gravity = gravity { + layerHolder.layer.videoGravity = gravity + } else { + layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill + } + layerHolder.layer.backgroundColor = NSColor.clear.cgColor + self.layer?.addSublayer(layerHolder.layer) + let manager = SoftwareVideoLayerFrameManager(account: context.account, fileReference: fileReference, layerHolder: layerHolder) + self.videoLayer = (manager, layerHolder) + manager.start() + } + + + needsLayout = true + } + + override func layout() { + super.layout() + + if let file = fileReference?.media, resizeInChat { + let dimensions = file.dimensions?.size ?? frame.size + let size = dimensions.aspectFitted(frame.size) + let rect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - size.width) / 2), floorToScreenPixels(backingScaleFactor, (frame.height - size.height) / 2), size.width, size.height) + videoLayer?.1.layer.frame = rect + } else { + videoLayer?.1.layer.frame = bounds + } + + } + + private var gravity: AVLayerVideoGravity? + + func setVideoLayerGravity(_ gravity: AVLayerVideoGravity) { + self.gravity = gravity + videoLayer?.1.layer.videoGravity = gravity + } + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + private var displayLink: ConstantDisplayLinkAnimator? + var ticking: Bool = false { + didSet { + if self.ticking != oldValue { + if self.ticking { + let displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.displayLinkEvent() + }, fps: 25) + self.displayLink = displayLink + displayLink.isPaused = false + CMTimebaseSetRate(self.timebase, rate: 1.0) + } else if let displayLink = self.displayLink { + self.displayLink = nil + displayLink.isPaused = true + displayLink.invalidate() + CMTimebaseSetRate(self.timebase, rate: 0.0) + } + } + } + } + +// private var displayLink: DisplayLink? +// var ticking: Bool = false { +// didSet { +// if self.ticking != oldValue { +// if self.ticking { +// let displayLink = DisplayLink(onQueue: .main) +// self.displayLink = displayLink +// displayLink?.start() +// displayLink?.callback = { [weak self] in +// self?.displayLinkEvent() +// } +// CMTimebaseSetRate(self.timebase, rate: 1.0) +// } else if let displayLink = self.displayLink { +// self.displayLink = nil +// displayLink.cancel() +// CMTimebaseSetRate(self.timebase, rate: 0.0) +// } +// } +// } +// } + + private func displayLinkEvent() { + let timestamp = CMTimebaseGetTime(self.timebase).seconds + self.videoLayer?.0.tick(timestamp: timestamp) + } + + + private let maskLayer = CAShapeLayer() + + var positionFlags: LayoutPositionFlags? { + didSet { + if let positionFlags = positionFlags { + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius + + + if positionFlags.contains(.top) && positionFlags.contains(.left) { + bottomLeftRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + bottomRightRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + topLeftRadius = .cornerRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + topRightRadius = .cornerRadius * 3 + 2 + } + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topRightRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topLeftRadius) + + maskLayer.path = path + layer?.mask = maskLayer + } else { + layer?.mask = nil + } + } + } + +} diff --git a/Telegram-Mac/GlobalBadgeNode.swift b/Telegram-Mac/GlobalBadgeNode.swift index 195b6fad58..95f8f007a9 100644 --- a/Telegram-Mac/GlobalBadgeNode.swift +++ b/Telegram-Mac/GlobalBadgeNode.swift @@ -8,16 +8,21 @@ import Cocoa import TGUIKit -import PostboxMac -import SwiftSignalKitMac -import TelegramCoreMac +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + + class GlobalBadgeNode: Node { private let account:Account + private let sharedContext: SharedAccountContext private let layoutChanged:(()->Void)? private let excludePeerId:PeerId? private let disposable:MetaDisposable = MetaDisposable() private var textLayout:(TextNodeLayout, TextNode)? + var customLayout: Bool = false var xInset:CGFloat = 0 private var attributedString:NSAttributedString? { didSet { @@ -25,69 +30,175 @@ class GlobalBadgeNode: Node { textLayout = TextNode.layoutText(maybeNode: nil, attributedString, nil, 1, .middle, NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude), nil, false, .left) size = NSMakeSize(textLayout!.0.size.width + 8, textLayout!.0.size.height + 7) size = NSMakeSize(max(size.height,size.width), size.height) + + } else { textLayout = nil size = NSZeroSize } - if let superview = view?.superview as? View { + setNeedDisplay() + if let superview = view?.superview as? View, !self.customLayout { superview.customHandler.layout = { [weak self] view in if let strongSelf = self { if strongSelf.layoutChanged == nil { var origin:NSPoint = NSZeroPoint let center = view.focus(strongSelf.size) - origin = NSMakePoint(floorToScreenPixels(center.midX) + 4 + strongSelf.xInset, 4) + origin = NSMakePoint(floorToScreenPixels(System.backingScale, center.midX) + strongSelf.xInset, 4) + origin.x = min(view.frame.width - strongSelf.size.width - 4, origin.x) strongSelf.frame = NSMakeRect(origin.x,origin.y,strongSelf.size.width,strongSelf.size.height) } else { strongSelf.view?.setFrameSize(strongSelf.size) + strongSelf.layoutChanged?() } } } - setNeedDisplay() - superview.needsLayout = true } - + view?.superview?.needsLayout = true } } + override func update() { + let attributedString = self.attributedString + self.attributedString = attributedString + } + + override func setNeedDisplay() { + super.setNeedDisplay() + } + + var isSelected: Bool = false { + didSet { + if oldValue != self.isSelected { + self.view?.needsDisplay = true + let copy = self.attributedString?.mutableCopy() as? NSMutableAttributedString + guard let attr = copy else { + return + } + attr.addAttribute(.foregroundColor, value: getColor(!isSelected), range: attr.range) + self.attributedString = copy + } + } + } + private let getColor: (Bool) -> NSColor - init(_ account:Account, excludePeerId:PeerId? = nil, layoutChanged:(()->Void)? = nil) { + init(_ account: Account, sharedContext: SharedAccountContext, dockTile: Bool = false, collectAllAccounts: Bool = false, excludePeerId:PeerId? = nil, excludeGroupId: PeerGroupId? = nil, view: View? = nil, layoutChanged:(()->Void)? = nil, getColor: @escaping(Bool) -> NSColor = { _ in return theme.colors.redUI }, fontSize: CGFloat = .small, applyFilter: Bool = true, filter: ChatListFilter? = nil, removeWhenSidebar: Bool = false) { self.account = account self.excludePeerId = excludePeerId self.layoutChanged = layoutChanged - super.init(View()) + self.sharedContext = sharedContext + self.getColor = getColor + super.init(view) + + struct Result : Equatable { + let dockText: String? + let total:Int32 + init(dockText: String?, total: Int32) { + self.dockText = dockText + self.total = max(total, 0) + } + } + + var items:[UnreadMessageCountsItem] = [] + let peerSignal: Signal<(Peer, Bool)?, NoError> + + + - var items:[UnreadMessageCountsItem] = [.total] if let peerId = excludePeerId { items.append(.peer(peerId)) + let notificationKeyView: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) + peerSignal = combineLatest(account.postbox.loadedPeerWithId(peerId), account.postbox.combinedView(keys: [notificationKeyView]) |> map { view in + return ((view.views[notificationKeyView] as? PeerNotificationSettingsView)?.notificationSettings[peerId])?.isRemovedFromTotalUnreadCount(default: false) ?? false + }) |> map {Optional($0)} + } else { + peerSignal = .single(nil) } - self.disposable.set((account.postbox.unreadMessageCountsView(items: items) |> deliverOnMainQueue).start(next: { [weak self] view in - if let strongSelf = self { - var count: Int32 = 0 - var totalCount:Int32 = 0 - if let total = view.count(for: .total) { - count = total - totalCount = total - } - if let excludePeerId = excludePeerId, let peerCount = view.count(for: .peer(excludePeerId)) { - count -= peerCount + + let signal: Signal<[(Int32, RenderedTotalUnreadCountType)], NoError> + if collectAllAccounts { + signal = sharedContext.activeAccountsWithInfo |> mapToSignal { primaryId, accounts in + return combineLatest(accounts.filter { $0.account.id != account.id }.map { renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: $0.account.postbox) }) + } + } else { + signal = renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: account.postbox) |> map { [$0] } + } + + var unreadCountItems: [UnreadMessageCountsItem] = [] + unreadCountItems.append(.total(nil)) + var keys: [PostboxViewKey] = [] + let unreadKey: PostboxViewKey + unreadKey = .unreadCounts(items: []) + + var s:Signal + + if let filter = filter { + s = chatListFilterItems(account: account, accountManager: sharedContext.accountManager) |> map { value in + if let unread = value.count(for: filter) { + return Result(dockText: nil, total: Int32(unread.count)) + } else { + return Result(dockText: nil, total: 0) } + } |> deliverOnMainQueue + } else { + s = combineLatest(signal, account.postbox.unreadMessageCountsView(items: items), account.postbox.combinedView(keys: keys), appNotificationSettings(accountManager: sharedContext.accountManager), peerSignal) |> map { (counts, view, keysView, inAppSettings, peerSettings) in - count = max(0, count) - totalCount = max(0, totalCount) - - var dockTile:String? = nil - if totalCount > 0 { - dockTile = "\(totalCount)" + if !applyFilter || filter == nil { + var excludeTotal: Int32 = 0 + + var dockText: String? + let totalValue = !inAppSettings.badgeEnabled ? 0 : (collectAllAccounts && !inAppSettings.notifyAllAccounts ? 0 : max(0, counts.reduce(0, { $0 + $1.0 }))) + if totalValue > 0 { + dockText = "\(max(0, totalValue))" + } + + excludeTotal = totalValue + + if items.count == 1, let peerSettings = peerSettings { + if let count = view.count(for: items[0]), count > 0 { + var removable = false + switch inAppSettings.totalUnreadCountDisplayStyle { + case .raw: + removable = true + case .filtered: + if !peerSettings.1 { + removable = true + } + } + if removable { + switch inAppSettings.totalUnreadCountDisplayCategory { + case .chats: + excludeTotal -= 1 + case .messages: + excludeTotal -= count + } + } + } + } + return Result(dockText: dockText, total: excludeTotal) } - if count == 0 { + return Result(dockText: nil, total: 0) + } |> deliverOnMainQueue + } + + s = combineLatest(s, chatListFolderSettings(account.postbox)) |> map { + return Result(dockText: $0.dockText, total: $1.sidebar && removeWhenSidebar ? 0 : $0.total) + } |> deliverOnMainQueue + + self.disposable.set(s.start(next: { [weak self] result in + if let strongSelf = self { + + if result.total == 0 { strongSelf.attributedString = nil } else { - strongSelf.attributedString = .initialize(string: Int(count).prettyNumber, color: .white, font: .bold(.small)) + strongSelf.attributedString = .initialize(string: Int(result.total).prettyNumber, color: getColor(strongSelf.isSelected) != theme.colors.redUI ? theme.colors.underSelectedColor : .white, font: .bold(fontSize)) } strongSelf.layoutChanged?() - NSApplication.shared.dockTile.badgeLabel = dockTile + if dockTile { + NSApplication.shared.dockTile.badgeLabel = result.dockText + forceUpdateStatusBarIconByDockTile(sharedContext: sharedContext) + } } })) } @@ -95,14 +206,14 @@ class GlobalBadgeNode: Node { override public func draw(_ layer: CALayer, in ctx: CGContext) { if let view = view { - ctx.setFillColor(theme.colors.redUI.cgColor) + ctx.setFillColor(getColor(isSelected).cgColor) ctx.round(self.size, self.size.height/2.0) ctx.fill(layer.bounds) if let textLayout = textLayout { let focus = view.focus(textLayout.0.size) - textLayout.1.draw(focus, in: ctx, backingScaleFactor: view.backingScaleFactor) + textLayout.1.draw(focus, in: ctx, backingScaleFactor: view.backingScaleFactor, backgroundColor: view.backgroundColor) } } } @@ -112,3 +223,69 @@ class GlobalBadgeNode: Node { } } + +func forceUpdateStatusBarIconByDockTile(sharedContext: SharedAccountContext) { + if let count = Int(NSApplication.shared.dockTile.badgeLabel ?? "0") { + var color: NSColor = .black + if #available(OSX 10.14, *) { + color = .labelColor + } + resourcesQueue.async { + let icon = generateStatusBarIcon(count, color: color) + Queue.mainQueue().async { + sharedContext.updateStatusBarImage(icon) + } + } + + } +} + +private func generateStatusBarIcon(_ unreadCount: Int, color: NSColor) -> NSImage { + let icon = NSImage(named: "StatusIcon")! +// if unreadCount > 0 { +// return NSImage(cgImage: icon.precomposed(whitePalette.redUI), size: icon.size) +// } else { +// return icon +// } + + var string = "\(unreadCount)" + if string.count > 3 { + string = ".." + string.nsstring.substring(from: string.length - 2) + } + let attributedString = NSAttributedString.initialize(string: string, color: .white, font: .medium(8), coreText: true) + + let textLayout = TextNode.layoutText(maybeNode: nil, attributedString, nil, 1, .start, NSMakeSize(18, CGFloat.greatestFiniteMagnitude), nil, false, .center) + + let generated: CGImage? + if unreadCount > 0 { + generated = generateImage(NSMakeSize(max((textLayout.0.size.width + 4), (textLayout.0.size.height + 4)), (textLayout.0.size.height + 2)), scale: nil, rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + ctx.setFillColor(NSColor.red.cgColor) + + + ctx.round(size, size.height/2.0) + ctx.fill(rect) + + let focus = NSMakePoint((rect.width - textLayout.0.size.width) / 2, (rect.height - textLayout.0.size.height) / 2) + textLayout.1.draw(NSMakeRect(focus.x, 2, textLayout.0.size.width, textLayout.0.size.height), in: ctx, backingScaleFactor: 2.0, backgroundColor: .white) + + })! + } else { + generated = nil + } + + let full = generateImage(NSMakeSize(24, 20), contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + + ctx.draw(icon.precomposed(color), in: NSMakeRect((size.width - icon.size.width) / 2, 2, icon.size.width, icon.size.height)) + if let generated = generated { + ctx.draw(generated, in: NSMakeRect(rect.width - generated.size.width / System.backingScale, 0, generated.size.width / System.backingScale, generated.size.height / System.backingScale)) + } + })! + let image = NSImage(cgImage: full, size: full.backingSize) + return image +} diff --git a/Telegram-Mac/GlobalHandlers.swift b/Telegram-Mac/GlobalHandlers.swift index c4e98390e5..867a153249 100644 --- a/Telegram-Mac/GlobalHandlers.swift +++ b/Telegram-Mac/GlobalHandlers.swift @@ -7,8 +7,9 @@ // import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore -public let globalPeerHandler:Promise = Promise() +let archiver: ArchiverContext = ArchiverContext() diff --git a/Telegram-Mac/GlobalSearchModalController.swift b/Telegram-Mac/GlobalSearchModalController.swift new file mode 100644 index 0000000000..a0b3c11ca8 --- /dev/null +++ b/Telegram-Mac/GlobalSearchModalController.swift @@ -0,0 +1,206 @@ +// +// GlobalSearchModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 16.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + +private final class GlobalSearchArguments { + let context: AccountContext + let openInfo: (Peer, MessageId?) -> Void + init(context: AccountContext, openInfo: @escaping(Peer, MessageId?) -> Void) { + self.context = context + self.openInfo = openInfo + } +} + +private struct GlobalSearchState : Equatable { + static func == (lhs: GlobalSearchState, rhs: GlobalSearchState) -> Bool { + if let lhsSearchResult = lhs.searchResult, let rhsSearchResult = rhs.searchResult { + if lhsSearchResult.messages.count != rhsSearchResult.messages.count { + return false + } else { + for (i, lhsMessage) in rhsSearchResult.messages.enumerated() { + if !isEqualMessages(lhsMessage, rhsSearchResult.messages[i]) { + return false + } + } + if lhsSearchResult.completed != rhsSearchResult.completed { + return false + } + if lhsSearchResult.readStates != rhsSearchResult.readStates { + return false + } + } + } else if (lhs.searchResult != nil) != (rhs.searchResult != nil) { + return false + } + + return lhs.isLoading == rhs.isLoading && lhs.searchState == rhs.searchState + } + + var isLoading: Bool = false + var searchState: SearchMessagesState? + var searchResult: SearchMessagesResult? + var query: String = "" +} + +private func _id_message(_ id: MessageIndex) -> InputDataIdentifier { + return InputDataIdentifier("_id_message_\(id)") +} + +private func globalSearchEntries(state: GlobalSearchState, arguments: GlobalSearchArguments) -> [InputDataEntry] { + + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .customModern(50))) + sectionId += 1 + + if let result = state.searchResult { + if !result.messages.isEmpty { + + let mapped = result.messages.map { message -> MessageHistoryEntry in + let randomId = arc4random() + return MessageHistoryEntry(message: message.withUpdatedStableId(randomId), isRead: result.readStates[message.id.peerId]?.isOutgoingMessageIndexRead(MessageIndex(message)) ?? false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) + } + let _messageEntries = messageEntries(mapped, dayGrouping: true, renderType: theme.bubbled ? .bubble : .list, searchState: SearchMessagesResultState(state.query, result.messages)) + + let interactions = ChatInteraction(chatLocation: .peer(PeerId(0)), context: arguments.context, mode: .history, isLogInteraction: true, disableSelectAbility: true, isGlobalSearchMessage: true) + + + interactions.openInfo = { peerId, _, postId, _ in + let message = mapped.first(where: { $0.message.id == postId}) + if let peer = message?.message.peers[peerId] { + arguments.openInfo(peer, postId) + } + } + + for entry in _messageEntries { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_message(entry.index), equatable: nil, item: { initialSize, stableId in + let item:TableRowItem + switch entry { + case .DateEntry: + item = ChatDateStickItem(initialSize, entry, interaction: interactions, theme: theme) + default: + item = ChatRowItem.item(initialSize, from: entry, interaction: interactions, theme: theme) + } + _ = item.makeSize(initialSize.width, oldWidth: 0) + return item + })) + index += 1 + } + } + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func GlobalSearchModalController(context: AccountContext) -> ViewController { + + var close: (()->Void)? = nil + + + let arguments = GlobalSearchArguments(context: context, openInfo: { peer, messageId in + close?() + let signal = context.account.postbox.transaction { transaction -> Void in + updatePeers(transaction: transaction, peers: [peer], update: { _, _ in + return peer + }) + } |> deliverOnMainQueue + _ = signal.start(completed: { + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peer.id), messageId: messageId)) + }) + }) + + let initialState = GlobalSearchState() + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((GlobalSearchState) -> GlobalSearchState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let searchDisposable = MetaDisposable() + + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: globalSearchEntries(state: state, arguments: arguments), searchState: .visible(.init(cancelImage: nil, cancel: { + + }, updateState: { searchState in + switch searchState.state { + case .None: + break + case .Focus: + break + } + if !searchState.request.isEmpty { + updateState { state in + var state = state + state.isLoading = true + state.query = searchState.request + state.searchState = nil + return state + } + let signal = searchMessages(account: context.account, location: .general, query: "#g c:ru minviews:100 \(searchState.request)", state: stateValue.with { $0.searchState }, limit: 100) |> deliverOnMainQueue + + searchDisposable.set(signal.start(next: { searchResult, searchState in + updateState { state in + var state = state + state.searchState = searchState + state.searchResult = searchResult + state.isLoading = false + return state + } + })) + } else { + updateState { state in + var state = state + state.isLoading = false + state.query = searchState.request + return state + } + searchDisposable.set(nil) + } + + }))) + } + + let controller = InputDataController(dataSignal: signal, title: "Global Search", hasDone: false) + + controller.onDeinit = { + searchDisposable.dispose() + } + + + controller.getBackgroundColor = { + return .clear + } + + controller.didLoaded = { controller, _ in + controller.genericView.backgroundMode = theme.backgroundMode + controller.genericView.tableView.setIsFlipped(true) + } + + controller.updateDatas = { data in + updateState { state in + return state + } + return .none + } + + return controller +} diff --git a/Telegram-Mac/GridHoleItem.swift b/Telegram-Mac/GridHoleItem.swift deleted file mode 100644 index 63aeddbf31..0000000000 --- a/Telegram-Mac/GridHoleItem.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// GridHoleItem.swift -// Telegram-Mac -// -// Created by keepcoder on 26/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -final class GridHoleItem: GridItem { - func update(node: GridItemNode) { - - } - - let section: GridSection? = nil - - func node(layout: GridNodeLayout, gridNode:GridNode) -> GridItemNode { - return GridHoleItemNode(gridNode) - } -} - -class GridHoleItemNode: GridItemNode { -// private let activityIndicatorView: UIActivityIndicatorView -// -// override init() { -// self.activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) -// -// super.init() -// -// self.view.addSubview(self.activityIndicatorView) -// self.activityIndicatorView.startAnimating() -// } -// -// override func layout() { -// super.layout() -// -// let size = self.bounds.size -// let activityIndicatorSize = self.activityIndicatorView.bounds.size -// self.activityIndicatorView.frame = CGRect(origin: CGPoint(x: floor((size.width - activityIndicatorSize.width) / 2.0), y: floor((size.height - activityIndicatorSize.height) / 2.0)), size: activityIndicatorSize) -// } -} diff --git a/Telegram-Mac/GridMessageItem.swift b/Telegram-Mac/GridMessageItem.swift deleted file mode 100644 index 6d3c3ae2b9..0000000000 --- a/Telegram-Mac/GridMessageItem.swift +++ /dev/null @@ -1,261 +0,0 @@ -// -// GridMessageItem.swift -// Telegram-Mac -// -// Created by keepcoder on 26/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - - -private func mediaForMessage(_ message: Message) -> Media? { - for media in message.media { - if let media = media as? TelegramMediaImage { - return media - } else if let file = media as? TelegramMediaFile { - if file.mimeType.hasPrefix("audio/") { - return nil - } else if !file.isVideo && file.mimeType.hasPrefix("video/") { - return file - } else { - return file - } - } - } - return nil -} - -final class GridMessageItem: GridItem { - private let account: Account - private let message: Message - private let chatInteraction: ChatInteraction - - let section: GridSection? = nil - fileprivate weak var grid:GridNode? - init(account: Account, message: Message, chatInteraction: ChatInteraction) { - self.account = account - self.message = message - self.chatInteraction = chatInteraction - } - - func update(node: GridItemNode) { - guard let node = node as? GridMessageItemNode else { - assertionFailure() - return - } - if let media = mediaForMessage(self.message) { - node.setup(account: self.account, media: media, message: self.message, chatInteraction: self.chatInteraction) - } - } - - func node(layout: GridNodeLayout, gridNode:GridNode) -> GridItemNode { - let node = GridMessageItemNode(gridNode) - if let media = mediaForMessage(self.message) { - node.setup(account: self.account, media: media, message: self.message, chatInteraction: self.chatInteraction) - } - return node - } -} - -final class GridMessageItemNode: GridItemNode { - private var currentState: (Account, Media, CGSize)? - private let imageView: TransformImageView - private var message: Message? - private var chatInteraction: ChatInteraction? - private var selectionView:SelectingControl? - private var progressView:RadialProgressView? - private var _status:MediaResourceStatus? - private let statusDisposable = MetaDisposable() - private let fetchingDisposable = MetaDisposable() - override var stableId: AnyHashable { - return (message?.chatStableId ?? ChatHistoryEntryId.undefined) - } - - - open override func menu(for event: NSEvent) -> NSMenu? { - if let message = message, let account = currentState?.0 { - let menu = ContextMenu() - - - if canForwardMessage(message, account: account) { - menu.addItem(ContextMenuItem(tr(.messageContextForward), handler: { [weak self] in - self?.chatInteraction?.forwardMessages([message.id]) - })) - } - - if canDeleteMessage(message, account: account) { - menu.addItem(ContextMenuItem(tr(.messageContextDelete), handler: { [weak self] in - self?.chatInteraction?.deleteMessages([message.id]) - })) - } - - menu.addItem(ContextMenuItem(tr(.messageContextGoto), handler: { [weak self] in - self?.chatInteraction?.focusMessageId(nil, message.id, .center(id: 0, animated: false, focus: true, inset: 0)) - })) - return menu - } - return nil - } - - override init(_ grid:GridNode) { - self.imageView = TransformImageView() - - super.init(grid) - - self.addSubview(self.imageView) - - self.set(handler: { [weak self] (control) in - if let strongSelf = self, let interactions = strongSelf.chatInteraction, let currentState = strongSelf.currentState, let message = strongSelf.message { - if interactions.presentation.state == .selecting { - interactions.update({$0.withToggledSelectedMessage(message.id)}) - strongSelf.updateSelectionState(animated: true) - } else { - if strongSelf._status == nil || strongSelf._status == .Local { - showChatGallery(account: currentState.0, message: message, strongSelf.grid, ChatMediaGalleryParameters(showMedia: {}, showMessage: { [weak interactions] message in - interactions?.focusMessageId(nil, message.id, .center(id: 0, animated: false, focus: true, inset: 0)) - }, isWebpage: false)) - } else if let file = message.media.first as? TelegramMediaFile { - if let status = strongSelf._status { - switch status { - case .Remote: - strongSelf.fetchingDisposable.set(chatMessageFileInteractiveFetched(account: currentState.0, file: file).start()) - case .Fetching: - fileCancelInteractiveFetch(account: currentState.0, file: file) - default: - break - } - } - } - - } - } - - - }, for: .Click) - } - - - required init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func copy() -> Any { - return imageView.copy() - } - - - func setup(account: Account, media: Media, message: Message, chatInteraction: ChatInteraction) { - if self.currentState == nil || self.currentState!.0 !== account || !self.currentState!.1.isEqual(media) { - var mediaDimensions: CGSize? - backgroundColor = theme.colors.background - statusDisposable.set(nil) - fetchingDisposable.set(nil) - - if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { - mediaDimensions = largestSize - self.imageView.setSignal(account: account, signal: mediaGridMessagePhoto(account: account, photo: image, scale: backingScaleFactor)) - progressView?.removeFromSuperview() - progressView = nil - } else if let file = media as? TelegramMediaFile { - mediaDimensions = file.previewRepresentations.last?.dimensions - self.imageView.setSignal(account: account, signal: mediaGridMessageVideo(account: account, file: file, scale: backingScaleFactor)) - - - statusDisposable.set((chatMessageFileStatus(account: account, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in - - if let strongSelf = self { - if strongSelf.progressView == nil { - strongSelf.progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) - strongSelf.progressView?.userInteractionEnabled = false - strongSelf.addSubview(strongSelf.progressView!) - strongSelf.progressView?.center() - } - strongSelf._status = status - - switch status { - case let .Fetching(_, progress): - strongSelf.progressView?.state = .Fetching(progress: progress, force: false) - case .Remote: - strongSelf.progressView?.state = .Remote - case .Local: - strongSelf.progressView?.state = .Play - } - - } - })) - - } - - - self.currentState = (account, media, mediaDimensions ?? NSMakeSize(100, 100)) - } - - self.message = message - self.chatInteraction = chatInteraction - - self.updateSelectionState(animated: false) - - self.needsLayout = true - } - - override func layout() { - super.layout() - - let imageFrame = NSMakeRect(1, 1, bounds.width - 4, bounds.height - 4) - self.imageView.frame = imageFrame - - if let (_, _, mediaDimensions) = self.currentState { - let imageSize = mediaDimensions.aspectFilled(imageFrame.size) - self.imageView.set(arguments:TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: NSEdgeInsets())) - } - if let selectionView = selectionView { - selectionView.setFrameOrigin(frame.width - selectionView.frame.width - 5, 5) - } - progressView?.center() - } - - func updateSelectionState(animated: Bool) { - if let messageId = self.message?.id, let interactions = self.chatInteraction { - if let selectionState = interactions.presentation.selectionState { - let selected = selectionState.selectedIds.contains(messageId) - - if let selectionView = self.selectionView { - selectionView.set(selected: selected, animated: animated) - } else { - selectionView = SelectingControl(unselectedImage: theme.icons.chatToggleUnselected, selectedImage: theme.icons.chatToggleSelected) - - addSubview(selectionView!) - selectionView?.set(selected: selected, animated: animated) - - } - } else { - if let selectionView = selectionView { - self.selectionView = nil - if animated { - selectionView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak selectionView] completion in - selectionView?.removeFromSuperview() - }) - } else { - selectionView.removeFromSuperview() - } - } - } - needsLayout = true - } - } - - deinit { - statusDisposable.dispose() - fetchingDisposable.dispose() - } - -} diff --git a/Telegram-Mac/GroupAdminsController.swift b/Telegram-Mac/GroupAdminsController.swift deleted file mode 100644 index bf80e79d8f..0000000000 --- a/Telegram-Mac/GroupAdminsController.swift +++ /dev/null @@ -1,420 +0,0 @@ -// -// GroupAdminsController.swift -// Telegram -// -// Created by keepcoder on 13/03/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac - - -private final class GroupAdminsControllerArguments { - let account: Account - - let updateAllAreAdmins: (Bool) -> Void - let updatePeerIsAdmin: (PeerId, Bool) -> Void - - init(account: Account, updateAllAreAdmins: @escaping (Bool) -> Void, updatePeerIsAdmin: @escaping (PeerId, Bool) -> Void) { - self.account = account - self.updateAllAreAdmins = updateAllAreAdmins - self.updatePeerIsAdmin = updatePeerIsAdmin - } -} - -private enum GroupAdminsSection: Int32 { - case allAdmins - case peers -} - -private enum GroupAdminsEntryStableId: Hashable { - case index(Int32) - case section(Int) - case peer(PeerId) - - var hashValue: Int { - switch self { - case let .section(index): - return index.hashValue - case let .index(index): - return index.hashValue - case let .peer(peerId): - return peerId.hashValue - } - } - - static func ==(lhs: GroupAdminsEntryStableId, rhs: GroupAdminsEntryStableId) -> Bool { - switch lhs { - case let .index(index): - if case .index(index) = rhs { - return true - } else { - return false - } - case let .section(index): - if case .section(index) = rhs { - return true - } else { - return false - } - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - } - } -} - -private enum GroupAdminsEntry: Comparable, Identifiable { - case allAdmins(sectionId:Int, Bool) - case allAdminsInfo(sectionId:Int, String) - case peerItem(sectionId:Int, Int32, Peer, PeerPresence?, Bool, Bool) - case section(sectionId:Int) - var stableId: GroupAdminsEntryStableId { - switch self { - case .allAdmins: - return .index(0) - case .allAdminsInfo: - return .index(1) - case let .section(sectionId): - return .section(sectionId) - case let .peerItem(_, _, peer, _, _, _): - return .peer(peer.id) - } - } - - var stableIndex: Int { - switch self { - case .allAdmins: - return 0 - case .allAdminsInfo: - return 1 - case let .section(sectionId): - return (sectionId + 1) * 1000 - sectionId - case .peerItem: - fatalError() - } - } - - static func ==(lhs: GroupAdminsEntry, rhs: GroupAdminsEntry) -> Bool { - switch lhs { - case let .allAdmins(sectionId, value): - if case .allAdmins(sectionId, value) = rhs { - return true - } else { - return false - } - case let .allAdminsInfo(sectionId, text): - if case .allAdminsInfo(sectionId, text) = rhs { - return true - } else { - return false - } - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - case let .peerItem(lhsSectionId, lhsIndex, lhsPeer, lhsPresence, lhsToggled, lhsEnabled): - if case let .peerItem(rhsSectionId, rhsIndex, rhsPeer, rhsPresence, rhsToggled, rhsEnabled) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsSectionId != rhsSectionId { - return false - } - if !lhsPeer.isEqual(rhsPeer) { - return false - } - if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { - if !lhsPresence.isEqual(to: rhsPresence) { - return false - } - } else if (lhsPresence != nil) != (rhsPresence != nil) { - return false - } - if lhsToggled != rhsToggled { - return false - } - if lhsEnabled != rhsEnabled { - return false - } - return true - } else { - return false - } - } - } - - var sortIndex:Int { - switch self { - case let .allAdmins(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .allAdminsInfo(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .peerItem(sectionId, index, _, _, _, _): - return (sectionId * 1000) + Int(index) + 100 - case let .section(sectionId): - return (sectionId + 1) * 1000 - sectionId - } - } - - static func <(lhs: GroupAdminsEntry, rhs: GroupAdminsEntry) -> Bool { - return lhs.sortIndex < rhs.sortIndex - } - - func item(_ arguments: GroupAdminsControllerArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case let .allAdmins(_, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.groupAdminsAllMembersAdmins), type: .switchable(stateback: { () -> Bool in - return value - }), action: { - arguments.updateAllAreAdmins(!value) - }) - - case let .allAdminsInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case let .peerItem(_, _, peer, presence, toggled, enabled): - - var string:String = tr(.peerStatusRecently) - var color:NSColor = theme.colors.grayText - - if let presence = presence as? TelegramUserPresence { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) - } else if let peer = peer as? TelegramUser, let botInfo = peer.botInfo { - string = botInfo.flags.contains(.hasAccessToChatHistory) ? tr(.peerInfoBotStatusHasAccess) : tr(.peerInfoBotStatusHasNoAccess) - } - - - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: enabled, height: 46, photoSize: NSMakeSize(36, 36), titleStyle: ControlStyle(font: .medium(.custom(12.5))), statusStyle: ControlStyle(font: NSFont.normal(.custom(12.5)), foregroundColor:color), status: string, drawLastSeparator: true, inset:NSEdgeInsets(left:30.0,right:30.0), generalType: .switchable(stateback: { () -> Bool in - return toggled - }), action: { - arguments.updatePeerIsAdmin(peer.id, !toggled) - }) - - } - } -} - -private struct GroupAdminsControllerState: Equatable { - let updatingAllAdminsValue: Bool? - let updatedAllAdminsValue: Bool? - - let updatingAdminValue: [PeerId: Bool] - - init() { - self.updatingAllAdminsValue = nil - self.updatedAllAdminsValue = nil - self.updatingAdminValue = [:] - } - - init(updatingAllAdminsValue: Bool?, updatedAllAdminsValue: Bool?, updatingAdminValue: [PeerId: Bool]) { - self.updatingAllAdminsValue = updatingAllAdminsValue - self.updatedAllAdminsValue = updatedAllAdminsValue - self.updatingAdminValue = updatingAdminValue - } - - static func ==(lhs: GroupAdminsControllerState, rhs: GroupAdminsControllerState) -> Bool { - if lhs.updatingAllAdminsValue != rhs.updatingAllAdminsValue { - return false - } - if lhs.updatedAllAdminsValue != rhs.updatedAllAdminsValue { - return false - } - if lhs.updatingAdminValue != rhs.updatingAdminValue { - return false - } - - return true - } - - func withUpdatedUpdatingAllAdminsValue(_ updatingAllAdminsValue: Bool?) -> GroupAdminsControllerState { - return GroupAdminsControllerState(updatingAllAdminsValue: updatingAllAdminsValue, updatedAllAdminsValue: self.updatedAllAdminsValue, updatingAdminValue: self.updatingAdminValue) - } - - func withUpdatedUpdatedAllAdminsValue(_ updatedAllAdminsValue: Bool?) -> GroupAdminsControllerState { - return GroupAdminsControllerState(updatingAllAdminsValue: self.updatingAllAdminsValue, updatedAllAdminsValue: updatedAllAdminsValue, updatingAdminValue: self.updatingAdminValue) - } - - func withUpdatedUpdatingAdminValue(_ updatingAdminValue: [PeerId: Bool]) -> GroupAdminsControllerState { - return GroupAdminsControllerState(updatingAllAdminsValue: self.updatingAllAdminsValue, updatedAllAdminsValue: self.updatedAllAdminsValue, updatingAdminValue: updatingAdminValue) - } -} - -private func groupAdminsControllerEntries(account: Account, view: PeerView, state: GroupAdminsControllerState) -> [GroupAdminsEntry] { - var entries: [GroupAdminsEntry] = [] - - if let peer = view.peers[view.peerId] as? TelegramGroup, let cachedData = view.cachedData as? CachedGroupData, let participants = cachedData.participants { - - var sectionId:Int = 1 - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - - let effectiveAdminsEnabled: Bool - if let updatingAllAdminsValue = state.updatingAllAdminsValue { - effectiveAdminsEnabled = updatingAllAdminsValue - } else { - effectiveAdminsEnabled = peer.flags.contains(.adminsEnabled) - } - - entries.append(.allAdmins(sectionId: sectionId, !effectiveAdminsEnabled)) - if effectiveAdminsEnabled { - entries.append(.allAdminsInfo(sectionId: sectionId, tr(.groupAdminsDescAdminInvites))) - } else { - entries.append(.allAdminsInfo(sectionId: sectionId, tr(.groupAdminsDescAllInvites))) - } - - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - - - var index: Int32 = 0 - for participant in participants.participants.sorted(by: <) { - if let peer = view.peers[participant.peerId] { - var isAdmin = false - var isEnabled = true - if !effectiveAdminsEnabled { - isAdmin = true - isEnabled = false - } else { - switch participant { - case .creator: - isAdmin = true - isEnabled = false - case .admin: - if let value = state.updatingAdminValue[peer.id] { - isAdmin = value - } else { - isAdmin = true - } - case .member: - if let value = state.updatingAdminValue[peer.id] { - isAdmin = value - } else { - isAdmin = false - } - } - } - entries.append(.peerItem(sectionId: sectionId, index, peer, view.peerPresences[participant.peerId], isAdmin, isEnabled)) - index += 1 - } - } - } - - return entries -} - -private func prepareTransition(left:[AppearanceWrapperEntry], right:[AppearanceWrapperEntry], arguments: GroupAdminsControllerArguments, initialSize: NSSize) -> TableUpdateTransition { - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - }) - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - - -class GroupAdminsController : TableViewController { - private let peerId:PeerId - init(account:Account, peerId:PeerId) { - self.peerId = peerId - super.init(account) - } - - override func viewDidLoad() { - super.viewDidLoad() - - let account = self.account - let peerId = self.peerId - - let statePromise = ValuePromise(GroupAdminsControllerState(), ignoreRepeated: true) - let stateValue = Atomic(value: GroupAdminsControllerState()) - let updateState: ((GroupAdminsControllerState) -> GroupAdminsControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - let actionsDisposable = DisposableSet() - - let toggleAllAdminsDisposable = MetaDisposable() - actionsDisposable.add(toggleAllAdminsDisposable) - - let toggleAdminsDisposables = DisposableDict() - actionsDisposable.add(toggleAdminsDisposables) - - let arguments = GroupAdminsControllerArguments(account: account, updateAllAreAdmins: { value in - updateState { state in - return state.withUpdatedUpdatingAllAdminsValue(value) - } - toggleAllAdminsDisposable.set((updateGroupManagementType(account: account, peerId: peerId, type: value ? .unrestricted : .restrictedToAdmins) |> deliverOnMainQueue).start(error: { - updateState { state in - return state.withUpdatedUpdatingAllAdminsValue(nil) - } - }, completed: { - updateState { state in - return state.withUpdatedUpdatingAllAdminsValue(nil).withUpdatedUpdatedAllAdminsValue(value) - } - })) - }, updatePeerIsAdmin: { memberId, value in - updateState { state in - var updatingAdminValue = state.updatingAdminValue - updatingAdminValue[memberId] = value - return state.withUpdatedUpdatingAdminValue(updatingAdminValue) - } - - if value { - toggleAdminsDisposables.set((addPeerAdmin(account: account, peerId: peerId, adminId: memberId) |> deliverOnMainQueue).start(error: { _ in - updateState { state in - var updatingAdminValue = state.updatingAdminValue - updatingAdminValue.removeValue(forKey: memberId) - return state.withUpdatedUpdatingAdminValue(updatingAdminValue) - } - }, completed: { - updateState { state in - var updatingAdminValue = state.updatingAdminValue - updatingAdminValue.removeValue(forKey: memberId) - return state.withUpdatedUpdatingAdminValue(updatingAdminValue) - } - }), forKey: memberId) - } else { - toggleAdminsDisposables.set((removePeerAdmin(account: account, peerId: peerId, adminId: memberId) |> deliverOnMainQueue).start(error: { _ in - updateState { state in - var updatingAdminValue = state.updatingAdminValue - updatingAdminValue.removeValue(forKey: memberId) - return state.withUpdatedUpdatingAdminValue(updatingAdminValue) - } - }, completed: { - updateState { state in - var updatingAdminValue = state.updatingAdminValue - updatingAdminValue.removeValue(forKey: memberId) - return state.withUpdatedUpdatingAdminValue(updatingAdminValue) - } - }), forKey: memberId) - } - }) - - let peerView = account.viewTracker.peerView(peerId) - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - - genericView.merge(with: combineLatest(statePromise.get(), peerView, appearanceSignal) |> deliverOnMainQueue - |> map { state, view, appearance -> TableUpdateTransition in - let entries = groupAdminsControllerEntries(account: account, view: view, state: state).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareTransition(left: previous.swap(entries), right: entries, arguments: arguments, initialSize: initialSize.modify({$0})) - - } |> afterDisposed { - actionsDisposable.dispose() - }) - readyOnce() - - } -} - diff --git a/Telegram-Mac/GroupInfoEntries.swift b/Telegram-Mac/GroupInfoEntries.swift index 75ec9407a2..25d244888b 100644 --- a/Telegram-Mac/GroupInfoEntries.swift +++ b/Telegram-Mac/GroupInfoEntries.swift @@ -7,21 +7,35 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit import TGUIKit +let minumimUsersBlock: Int = 5 private func valuesRequiringUpdate(state: GroupInfoState, view: PeerView) -> (title: String?, description: String?) { if let peer = view.peers[view.peerId] as? TelegramGroup { + var titleValue: String? + var descriptionValue: String? if let editingState = state.editingState { if let title = editingState.editingName, title != peer.title { - return (title, nil) + titleValue = title + } + if let cachedData = view.cachedData as? CachedGroupData { + if let about = cachedData.about { + if about != editingState.editingDescriptionText { + descriptionValue = editingState.editingDescriptionText + } + } else if !editingState.editingDescriptionText.isEmpty { + descriptionValue = editingState.editingDescriptionText + } } } - return (nil, nil) + + return (titleValue, descriptionValue) } else if let peer = view.peers[view.peerId] as? TelegramChannel { var titleValue: String? var descriptionValue: String? @@ -58,16 +72,6 @@ struct GroupInfoEditingState: Equatable { func withUpdatedEditingDescriptionText(_ editingDescriptionText: String) -> GroupInfoEditingState { return GroupInfoEditingState(editingName: self.editingName, editingDescriptionText: editingDescriptionText) } - - static func ==(lhs: GroupInfoEditingState, rhs: GroupInfoEditingState) -> Bool { - if lhs.editingName != rhs.editingName { - return false - } - if lhs.editingDescriptionText != rhs.editingDescriptionText { - return false - } - return true - } } final class GroupInfoArguments : PeerInfoArguments { @@ -76,35 +80,51 @@ final class GroupInfoArguments : PeerInfoArguments { private let removeMemberDisposable = MetaDisposable() private let updatePeerNameDisposable = MetaDisposable() private let updatePhotoDisposable = MetaDisposable() + private let reportPeerDisposable = MetaDisposable() func updateState(_ f: (GroupInfoState) -> GroupInfoState) -> Void { updateInfoState { state -> PeerInfoState in - return f(state as! GroupInfoState) + let result = f(state as! GroupInfoState) + return result } } - override func updateEditable(_ editable:Bool, peerView:PeerView) { + var loadMore: (()->Void)? = nil + + override func updateEditable(_ editable:Bool, peerView:PeerView, controller: PeerInfoController) -> Bool { - let account = self.account + let context = self.context let peerId = self.peerId let updateState:((GroupInfoState)->GroupInfoState)->Void = { [weak self] f in self?.updateState(f) } if editable { if let peer = peerViewMainPeer(peerView) { - if peer.isGroup { + if peer.isSupergroup, let cachedData = peerView.cachedData as? CachedChannelData { updateState { state -> GroupInfoState in - return state.withUpdatedEditingState(GroupInfoEditingState(editingName: peer.displayTitle)) + return state.withUpdatedEditingState(GroupInfoEditingState(editingName: peer.displayTitle, editingDescriptionText: cachedData.about ?? "")) } - } else if peer.isSupergroup, let cachedData = peerView.cachedData as? CachedChannelData { + } else if let cachedData = peerView.cachedData as? CachedGroupData { updateState { state -> GroupInfoState in return state.withUpdatedEditingState(GroupInfoEditingState(editingName: peer.displayTitle, editingDescriptionText: cachedData.about ?? "")) } } } } else { + + + var updateValues: (title: String?, description: String?) = (nil, nil) updateState { state in updateValues = valuesRequiringUpdate(state: state, view: peerView) + return state + } + + if let titleValue = updateValues.title, titleValue.isEmpty { + controller.genericView.item(stableId: IntPeerInfoEntryStableId(value: 1).hashValue)?.view?.shakeView() + return false + } + + updateState { state in if updateValues.0 != nil || updateValues.1 != nil { return state.withUpdatedSavingData(true) } else { @@ -112,25 +132,26 @@ final class GroupInfoArguments : PeerInfoArguments { } } - let updateTitle: Signal + + let updateTitle: Signal if let titleValue = updateValues.title { - updateTitle = updatePeerTitle(account: account, peerId: peerId, title: titleValue) - |> mapError { _ in return Void() } + updateTitle = updatePeerTitle(account: context.account, peerId: peerId, title: titleValue) + |> `catch` {_ in return .complete()} } else { updateTitle = .complete() } - let updateDescription: Signal + let updateDescription: Signal if let descriptionValue = updateValues.description { - updateDescription = updatePeerDescription(account: account, peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) - |> mapError { _ in return Void() } + updateDescription = updatePeerDescription(account: context.account, peerId: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue) + |> `catch` {_ in return .complete()} } else { updateDescription = .complete() } let signal = combineLatest(updateTitle, updateDescription) - updatePeerNameDisposable.set(showModalProgress(signal: (signal |> deliverOnMainQueue), for: mainWindow).start(error: { _ in + updatePeerNameDisposable.set(showModalProgress(signal: (signal |> deliverOnMainQueue), for: context.window).start(error: { _ in updateState { state in return state.withUpdatedSavingData(false) } @@ -140,6 +161,8 @@ final class GroupInfoArguments : PeerInfoArguments { } })) } + + return true } override func dismissEdition() { @@ -147,7 +170,7 @@ final class GroupInfoArguments : PeerInfoArguments { return state.withUpdatedSavingData(false).withUpdatedEditingState(nil) } } - + func updateEditingName(_ name:String) -> Void { updateState { state in if let editingState = state.editingState { @@ -167,38 +190,101 @@ final class GroupInfoArguments : PeerInfoArguments { } func visibilitySetup() { - let setup = ChannelVisibilityController(account: account, peerId: peerId) - _ = (setup.onComplete.get() |> deliverOnMainQueue).start(next: { [weak self] _ in + let setup = ChannelVisibilityController(context, peerId: peerId) + _ = (setup.onComplete.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in + self?.changeControllers(peerId) self?.pullNavigation()?.back() }) pushViewController(setup) } + private func changeControllers(_ peerId: PeerId?) { + guard let navigationController = self.pullNavigation() else { + return + } + if let peerId = peerId { + var chatController: ChatController? = ChatController(context: context, chatLocation: .peer(peerId)) + + navigationController.removeAll() + + chatController!.navigationController = navigationController + chatController!.loadViewIfNeeded(navigationController.bounds) + + var signal = chatController!.ready.get() |> filter {$0} |> take(1) |> ignoreValues + + var controller: PeerInfoController? = PeerInfoController(context: context, peerId: peerId) + + + controller!.navigationController = navigationController + controller!.loadViewIfNeeded(navigationController.bounds) + + let mainSignal = controller!.ready.get() |> filter {$0} |> take(1) |> ignoreValues + + signal = combineLatest(queue: .mainQueue(), signal, mainSignal) |> ignoreValues + + _ = signal.start(completed: { [weak navigationController] in + guard let navigationController = navigationController else { return } + + + navigationController.stackInsert(chatController!, at: 0) + navigationController.stackInsert(controller!, at: 1) + navigationController.stackInsert(navigationController.controller, at: 2) + navigationController.back() + chatController = nil + controller = nil + }) + } else { + navigationController.back() + } + + + } + + func report() -> Void { + let context = self.context + let peerId = self.peerId + + let report = reportReasonSelector(context: context) |> mapToSignal { reason -> Signal in + return showModalProgress(signal: reportPeer(account: context.account, peerId: peerId, reason: reason), for: context.window) + } |> deliverOnMainQueue + + reportPeerDisposable.set(report.start(next: { [weak self] in + self?.pullNavigation()?.controller.show(toaster: ControllerToaster(text: L10n.peerInfoChannelReported)) + })) + } + func preHistorySetup() { - let setup = PreHistorySettingsController(account, peerId: peerId) - _ = (setup.onComplete.get() |> deliverOnMainQueue).start(next: { [weak self] enabled in - if let strongSelf = self { - _ = showModalProgress(signal: updateChannelHistoryAvailabilitySettingsInteractively(postbox: strongSelf.account.postbox, network: strongSelf.account.network, peerId: strongSelf.peerId, historyAvailableForNewMembers: enabled), for: mainWindow).start() - - } + let setup = PreHistorySettingsController(context, peerId: peerId) + _ = (setup.onComplete.get() |> deliverOnMainQueue).start(next: { [weak self] peerId in + self?.changeControllers(peerId) }) pushViewController(setup) } func blacklist() { - pushViewController(ChannelBlacklistViewController(account: account, peerId: peerId)) + pushViewController(ChannelPermissionsController(context, peerId: peerId)) } - func convert() { - pushViewController(ConvertGroupViewController(account: account, peerId: peerId)) - } func admins() { - pushViewController(ChannelAdminsViewController(account: account, peerId: peerId)) + pushViewController(ChannelAdminsViewController(context, peerId: peerId)) } func invation() { - pushViewController(LinkInvationController(account: account, peerId: peerId)) + pushViewController(LinkInvationController(context, peerId: peerId)) + } + + func stats(_ datacenterId: Int32) { + self.pushViewController(GroupStatsViewController(context, peerId: peerId, datacenterId: datacenterId)) + } + + func showMore() { + let updateState:((GroupInfoState)->GroupInfoState)->Void = { [weak self] f in + self?.updateState(f) + } + updateState { + return $0.withUpdatedHasShowMoreButton(nil) + } } func updatePhoto(_ path:String) -> Void { @@ -208,42 +294,29 @@ final class GroupInfoArguments : PeerInfoArguments { } let cancel = { [weak self] in - self?.updatePhotoDisposable.dispose() + self?.updatePhotoDisposable.set(nil) updateState { state -> GroupInfoState in return state.withoutUpdatingPhotoState() } } - let account = self.account + let context = self.context let peerId = self.peerId - - -// let updateSignal = filethumb(with: URL(fileURLWithPath: path), account: account, scale: System.backingScale) |> mapToSignal { res -> Signal in -// guard let image = NSImage(contentsOf: URL(fileURLWithPath: path)) else { -// return .complete() -// } -// let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: image.size, boundingSize: NSMakeSize(640, 640), intrinsicInsets: NSEdgeInsets()) -// if let image = res(arguments)?.generateImage() { -// return putToTemp(image: NSImage(cgImage: image, size: image.backingSize)) -// } -// return .complete() -// } |> map { path -> TelegramMediaResource in -// return LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) -// } - - let updateSignal = Signal.single(path) |> map { path -> TelegramMediaResource in + var updateSignal = Signal.single(path) |> map { path -> TelegramMediaResource in return LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) - } |> beforeNext { resource in + } |> beforeNext { resource in updateState { (state) -> GroupInfoState in return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in - return PeerInfoUpdatingPhotoState(progress: 0, cancel: cancel) + return PeerInfoUpdatingPhotoState(progress: 0, image: NSImage(contentsOfFile: path)?.cgImage(forProposedRect: nil, context: nil, hints: nil), cancel: cancel) } } } |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { resource -> Signal in - return updatePeerPhoto(account: account, peerId: peerId, resource: resource) + return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) } @@ -271,22 +344,143 @@ final class GroupInfoArguments : PeerInfoArguments { } - func addMember() -> Void { - let account = self.account + func updateVideo(_ signal:Signal) -> Void { + + let updateState:((GroupInfoState)->GroupInfoState)->Void = { [weak self] f in + self?.updateState(f) + } + + let cancel = { [weak self] in + self?.updatePhotoDisposable.set(nil) + updateState { state -> GroupInfoState in + return state.withoutUpdatingPhotoState() + } + } + + let context = self.context + let peerId = self.peerId + + + let updateSignal: Signal = signal + |> mapError { _ in return UploadPeerPhotoError.generic } + |> mapToSignal { state in + switch state { + case .error: + return .fail(.generic) + case let .start(path): + updateState { (state) -> GroupInfoState in + return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in + return PeerInfoUpdatingPhotoState(progress: 0, image: NSImage(contentsOfFile: path)?._cgImage, cancel: cancel) + } + } + return .next(.progress(0)) + case let .progress(value): + return .next(.progress(value * 0.2)) + case let .complete(thumb, video, keyFrame): + let (thumbResource, videoResource) = (LocalFileReferenceMediaResource(localFilePath: thumb, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true), + LocalFileReferenceMediaResource(localFilePath: video, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true)) + + return updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: thumbResource), video: uploadedPeerVideo(postbox: context.account.postbox, network: context.account.network, messageMediaPreuploadManager: nil, resource: videoResource) |> map(Optional.init), videoStartTimestamp: keyFrame, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + }) |> map { result in + switch result { + case let .progress(current): + return .progress(0.2 + (current * 0.8)) + default: + return result + } + } + } + } + + updatePhotoDisposable.set((updateSignal |> deliverOnMainQueue).start(next: { status in + updateState { state -> GroupInfoState in + switch status { + case .complete: + return state.withoutUpdatingPhotoState() + case let .progress(progress): + return state.withUpdatedUpdatingPhotoState { previous -> PeerInfoUpdatingPhotoState? in + return previous?.withUpdatedProgress(progress) + } + } + } + }, error: { error in + updateState { (state) -> GroupInfoState in + return state.withoutUpdatingPhotoState() + } + }, completed: { + updateState { (state) -> GroupInfoState in + return state.withoutUpdatingPhotoState() + } + })) + + + } + + func setupDiscussion() { + _ = (self.context.account.postbox.loadedPeerWithId(self.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let `self` = self { + self.pushViewController(ChannelDiscussionSetupController(context: self.context, peer: peer)) + } + }) + } + + private func upgradeToSupergroup() -> (PeerId, @escaping () -> Void) -> Void { + return { [weak self] upgradedPeerId, f in + guard let `self` = self, let navigationController = self.pullNavigation() else { + return + } + let context = self.context + + var chatController: ChatController? = ChatController(context: context, chatLocation: .peer(upgradedPeerId)) + + + chatController!.navigationController = navigationController + chatController!.loadViewIfNeeded(navigationController.bounds) + + var signal = chatController!.ready.get() |> filter {$0} |> take(1) |> ignoreValues + + var controller: PeerInfoController? = PeerInfoController(context: context, peerId: upgradedPeerId) + + controller!.navigationController = navigationController + controller!.loadViewIfNeeded(navigationController.bounds) + + let mainSignal = combineLatest(controller!.ready.get(), controller!.ready.get()) |> map { $0 && $1 } |> filter {$0} |> take(1) |> ignoreValues + + signal = combineLatest(queue: .mainQueue(), signal, mainSignal) |> ignoreValues + + _ = signal.start(completed: { [weak navigationController] in + navigationController?.removeAll() + navigationController?.push(chatController!, false, style: .none) + navigationController?.push(controller!, false, style: .none) + + chatController = nil + controller = nil + }) + + } + } + + func addMember(_ canInviteByLink: Bool) -> Void { + + let upgradeToSupergroup = self.upgradeToSupergroup() + + let context = self.context let peerId = self.peerId let updateState:((GroupInfoState)->GroupInfoState)->Void = { [weak self] f in self?.updateState(f) } - let confirmationImpl:([PeerId])->Signal = { peerIds in + let confirmationImpl:([PeerId])->Signal = { peerIds in if let first = peerIds.first, peerIds.count == 1 { - return account.postbox.loadedPeerWithId(first) |> deliverOnMainQueue |> mapToSignal { peer in - return confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmAddMember(peer.displayTitle))) + return context.account.postbox.loadedPeerWithId(first) |> deliverOnMainQueue |> mapToSignal { peer in + return confirmSignal(for: context.window, information: L10n.peerInfoConfirmAddMember(peer.displayTitle), okTitle: L10n.peerInfoConfirmAdd) } } - return confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmAddMembers(peerIds.count))) + return confirmSignal(for: context.window, information: L10n.peerInfoConfirmAddMembers1Countable(peerIds.count), okTitle: L10n.peerInfoConfirmAdd) } - let addMember = account.viewTracker.peerView( peerId) |> take(1) |> deliverOnMainQueue |> mapToSignal{ view -> Signal in + + let addMember = context.account.viewTracker.peerView(peerId) |> take(1) |> deliverOnMainQueue |> mapToSignal{ view -> Signal in var excludePeerIds:[PeerId] = [] if let cachedData = view.cachedData as? CachedChannelData { @@ -295,10 +489,18 @@ final class GroupInfoArguments : PeerInfoArguments { excludePeerIds = Array(cachedData.peerIds) } - return selectModalPeers(account: account, title: tr(.peerInfoAddMember), settings: [.contacts, .remote], excludePeerIds:excludePeerIds, limit: peerId.namespace == Namespaces.Peer.CloudGroup ? 1 : 100, confirmation: confirmationImpl) + var linkInvation: (()->Void)? = nil + if canInviteByLink { + linkInvation = { [weak self] in + self?.invation() + } + } + + + return selectModalPeers(context: context, title: L10n.peerInfoAddMember, settings: [.contacts, .remote], excludePeerIds:excludePeerIds, limit: peerId.namespace == Namespaces.Peer.CloudGroup ? 1 : 100, confirmation: confirmationImpl, linkInvation: linkInvation) |> deliverOnMainQueue |> mapToSignal { memberIds -> Signal in - return account.postbox.multiplePeersView(memberIds + [peerId]) + return context.account.postbox.multiplePeersView(memberIds + [peerId]) |> take(1) |> deliverOnMainQueue |> mapToSignal { view -> Signal in @@ -326,7 +528,7 @@ final class GroupInfoArguments : PeerInfoArguments { if let peer = view.peers[peerId] { if peer.isGroup, let memberId = memberIds.first { - return addPeerMember(account: account, peerId: peerId, memberId: memberId) + return addGroupMember(account: context.account, peerId: peerId, memberId: memberId) |> deliverOnMainQueue |> afterCompleted { updateState { state in @@ -335,7 +537,7 @@ final class GroupInfoArguments : PeerInfoArguments { return state.withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) } - } |> `catch` { _ -> Signal in + } |> `catch` { error -> Signal in updateState { state in var temporaryParticipants = state.temporaryParticipants for i in 0 ..< temporaryParticipants.count { @@ -349,12 +551,50 @@ final class GroupInfoArguments : PeerInfoArguments { return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) } - return .complete() } } else if peer.isSupergroup { - return addChannelMembers(account: account, peerId: peerId, memberIds: memberIds) + return context.peerChannelMemberCategoriesContextsManager.addMembers(account: context.account, peerId: peerId, memberIds: memberIds) |> deliverOnMainQueue |> `catch` { error in + let text: String + switch error { + case .notMutualContact: + text = L10n.groupInfoAddUserLeftError + case .limitExceeded: + text = L10n.channelErrorAddTooMuch + case .botDoesntSupportGroups: + text = L10n.channelBotDoesntSupportGroups + case .tooMuchBots: + text = L10n.channelTooMuchBots + case .tooMuchJoined: + text = L10n.inviteChannelsTooMuch + case .generic: + text = L10n.unknownError + case let .bot(memberId): + let _ = (context.account.postbox.transaction { transaction in + return transaction.getPeer(peerId) + } + |> deliverOnMainQueue).start(next: { peer in + guard let peer = peer as? TelegramChannel else { + alert(for: context.window, info: L10n.unknownError) + return + } + if peer.hasPermission(.addAdmins) { + confirm(for: context.window, information: L10n.channelAddBotErrorHaveRights, okTitle: L10n.channelAddBotAsAdmin, successHandler: { _ in + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: memberId, initialParticipant: nil, updated: { _ in }, upgradedToSupergroup: upgradeToSupergroup), for: context.window) + }) + } else { + alert(for: context.window, info: L10n.channelAddBotErrorHaveRights) + } + }) + return .complete() + case .restricted: + text = L10n.channelErrorAddBlocked + } + alert(for: context.window, info: text) + + return .complete() + } } } @@ -366,18 +606,38 @@ final class GroupInfoArguments : PeerInfoArguments { addMemberDisposable.set(addMember.start()) } + + func restrict(_ participant: ChannelParticipant) -> Void { + + let context = self.context + let peerId = self.peerId + + showModal(with: RestrictedModalViewController(context, peerId: peerId, memberId: participant.peerId, initialParticipant: participant, updated: { updatedRights in + _ = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: participant.peerId, bannedRights: updatedRights).start() + }), for: context.window) + } + + func promote(_ participant: ChannelParticipant) -> Void { + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: self.upgradeToSupergroup()), for: context.window) + } + func removePeer(_ memberId:PeerId) -> Void { - let account = self.account + let context = self.context let peerId = self.peerId let updateState:((GroupInfoState)->GroupInfoState)->Void = { [weak self] f in self?.updateState(f) } - let signal = account.postbox.loadedPeerWithId(memberId) + + + + let signal = context.account.postbox.loadedPeerWithId(memberId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in - return confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmRemovePeer(peer.displayTitle))) + let result = ValuePromise() + result.set(true) + return result.get() } |> mapToSignal { value -> Signal in if value { @@ -398,7 +658,21 @@ final class GroupInfoArguments : PeerInfoArguments { return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds).withUpdatedRemovingParticipantIds(removingParticipantIds) } - return (peerId.namespace == Namespaces.Peer.CloudChannel ? updateChannelMemberBannedRights(account: account, peerId: peerId, memberId: memberId, rights: TelegramChannelBannedRights(flags: [.banReadMessages], untilDate: 0)) : removePeerMember(account: account, peerId: peerId, memberId: memberId)) + if peerId.namespace == Namespaces.Peer.CloudChannel { + return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)) + |> afterDisposed { + Queue.mainQueue().async { + updateState { state in + var removingParticipantIds = state.removingParticipantIds + removingParticipantIds.remove(memberId) + + return state.withUpdatedRemovingParticipantIds(removingParticipantIds) + } + } + } + } + + return removePeerMember(account: context.account, peerId: peerId, memberId: memberId) |> deliverOnMainQueue |> afterDisposed { updateState { state in @@ -414,18 +688,50 @@ final class GroupInfoArguments : PeerInfoArguments { } removeMemberDisposable.set(signal.start()) } - - func setGroupAdmins() { - pullNavigation()?.push(GroupAdminsController(account: account, peerId: peerId)) - } + func setGroupStickerset() { - pullNavigation()?.push(GroupStickerSetController(account: account, peerId: peerId)) + pullNavigation()?.push(GroupStickerSetController(context, peerId: peerId)) } + func updateGroupPhoto(_ custom: NSImage?) { + let invoke:(NSImage) -> Void = { image in + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: mainWindow, animationType: .scaleCenter) + _ = controller.result.start(next: { [weak self] url, _ in + self?.updatePhoto(url.path) + }) + controller.onClose = { + removeFile(at: path) + } + }) + } + if let image = custom { + invoke(image) + } else { + let context = self.context + + filePanel(with: photoExts + videoExts, allowMultiple: false, canChooseDirectories: false, for: context.window, completion: { [weak self] paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + invoke(image) + } else if let path = paths?.first { + selectVideoAvatar(context: context, path: path, localize: L10n.videoAvatarChooseDescGroup, signal: { [weak self] signal in + self?.updateVideo(signal) + }) + } + }) + } + } + func eventLog() { - pullNavigation()?.push(ChannelEventLogController(account, peerId: peerId)) + pullNavigation()?.push(ChannelEventLogController(context, peerId: peerId)) + } + + func peerMenuItems(for peer: Peer) -> [ContextMenuItem] { + + return [] } deinit { @@ -433,6 +739,7 @@ final class GroupInfoArguments : PeerInfoArguments { addMemberDisposable.dispose() updatePeerNameDisposable.dispose() updatePhotoDisposable.dispose() + reportPeerDisposable.dispose() } } @@ -445,10 +752,12 @@ class GroupInfoState: PeerInfoState { let successfullyAddedParticipantIds: Set let removingParticipantIds: Set let updatingPhotoState:PeerInfoUpdatingPhotoState? - + let savingData: Bool - init(editingState: GroupInfoEditingState?, updatingName:String?, temporaryParticipants:[TemporaryParticipant], successfullyAddedParticipantIds:Set, removingParticipantIds:Set, savingData: Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?) { + let hasShowMoreButton: Bool? + + init(editingState: GroupInfoEditingState?, updatingName:String?, temporaryParticipants:[TemporaryParticipant], successfullyAddedParticipantIds:Set, removingParticipantIds:Set, savingData: Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?, hasShowMoreButton: Bool?) { self.editingState = editingState self.updatingName = updatingName self.temporaryParticipants = temporaryParticipants @@ -456,6 +765,7 @@ class GroupInfoState: PeerInfoState { self.removingParticipantIds = removingParticipantIds self.savingData = savingData self.updatingPhotoState = updatingPhotoState + self.hasShowMoreButton = hasShowMoreButton } override init() { @@ -466,6 +776,7 @@ class GroupInfoState: PeerInfoState { self.removingParticipantIds = Set() self.savingData = false self.updatingPhotoState = nil + self.hasShowMoreButton = true } func isEqual(to: PeerInfoState) -> Bool { @@ -475,71 +786,47 @@ class GroupInfoState: PeerInfoState { return false } - static func ==(lhs: GroupInfoState, rhs: GroupInfoState) -> Bool { - if lhs.editingState != rhs.editingState { - return false - } - if lhs.updatingName != rhs.updatingName { - return false - } - if lhs.temporaryParticipants != rhs.temporaryParticipants { - return false - } - if lhs.successfullyAddedParticipantIds != rhs.successfullyAddedParticipantIds { - return false - } - if lhs.removingParticipantIds != rhs.removingParticipantIds { - return false - } - if lhs.savingData != rhs.savingData { - return false - } - - if lhs.updatingPhotoState != rhs.updatingPhotoState { - return false - } - - return true - } - func withUpdatedEditingState(_ editingState: GroupInfoEditingState?) -> GroupInfoState { - return GroupInfoState(editingState: editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedUpdatingName(_ updatingName: String?) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: self.editingState, updatingName: updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedTemporaryParticipants(_ temporaryParticipants: [TemporaryParticipant]) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedSuccessfullyAddedParticipantIds(_ successfullyAddedParticipantIds: Set) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedRemovingParticipantIds(_ removingParticipantIds: Set) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedSavingData(_ savingData: Bool) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: savingData, updatingPhotoState: self.updatingPhotoState) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: self.hasShowMoreButton) } func withUpdatedUpdatingPhotoState(_ f: (PeerInfoUpdatingPhotoState?) -> PeerInfoUpdatingPhotoState?) -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: f(self.updatingPhotoState)) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: f(self.updatingPhotoState), hasShowMoreButton: self.hasShowMoreButton) } func withoutUpdatingPhotoState() -> GroupInfoState { - return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: nil) + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: nil, hasShowMoreButton: self.hasShowMoreButton) + } + func withUpdatedHasShowMoreButton(_ hasShowMoreButton: Bool?) -> GroupInfoState { + return GroupInfoState(editingState: self.editingState, updatingName: self.updatingName, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, updatingPhotoState: self.updatingPhotoState, hasShowMoreButton: hasShowMoreButton) } } -enum GroupInfoMemberStatus { +enum GroupInfoMemberStatus : Equatable { case member - case admin + case admin(rank: String) } private struct GroupPeerEntryStableId: PeerInfoEntryStableId { @@ -561,53 +848,89 @@ private struct GroupPeerEntryStableId: PeerInfoEntryStableId { enum GroupInfoEntry: PeerInfoEntry { - case info(section:Int, view: PeerView, editable:Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?) - case setGroupPhoto(section:Int) - case about(section:Int, text: String) - case addressName(section:Int, name:String) - case sharedMedia(section:Int) - case notifications(section:Int, settings: PeerNotificationSettings?) - case usersHeader(section:Int, count:Int) - case addMember(section:Int) - case inviteLink(section:Int) - case convertToSuperGroup(section:Int) - case groupTypeSetup(section:Int, isPublic: Bool) - case groupDescriptionSetup(section:Int, text: String) - case groupAboutDescription(section:Int) - case groupStickerset(section:Int, packName: String) - case preHistory(section:Int, enabled: Bool) - case groupManagementInfoLabel(section:Int, text: String) - case setAdmins(section:Int) - case membersAdmins(section:Int, count: Int) - case membersBlacklist(section:Int, count: Int) - - case member(section:Int, index: Int, peerId: PeerId, peer: Peer?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ShortPeerDeleting?, enabled:Bool) - case leave(section:Int) + case info(section:Int, view: PeerView, editingState: Bool, updatingPhotoState:PeerInfoUpdatingPhotoState?, viewType: GeneralViewType) + case setTitle(section:Int, text: String, viewType: GeneralViewType) + case scam(section:Int, text: String, viewType: GeneralViewType) + case about(section:Int, text: String, viewType: GeneralViewType) + case addressName(section:Int, name:String, viewType: GeneralViewType) + case sharedMedia(section:Int, viewType: GeneralViewType) + case notifications(section:Int, settings: PeerNotificationSettings?, viewType: GeneralViewType) + case usersHeader(section:Int, count:Int, viewType: GeneralViewType) + case addMember(section:Int, inviteViaLink: Bool, viewType: GeneralViewType) + case groupTypeSetup(section:Int, isPublic: Bool, viewType: GeneralViewType) + case linkedChannel(section:Int, channel: Peer, subscribers: Int32?, viewType: GeneralViewType) + case groupDescriptionSetup(section:Int, text: String, viewType: GeneralViewType) + case groupAboutDescription(section:Int, viewType: GeneralViewType) + case groupStickerset(section:Int, packName: String, viewType: GeneralViewType) + case preHistory(section:Int, enabled: Bool, viewType: GeneralViewType) + case groupManagementInfoLabel(section:Int, text: String, viewType: GeneralViewType) + case administrators(section:Int, count: String, viewType: GeneralViewType) + case permissions(section:Int, count: String, viewType: GeneralViewType) + case member(section:Int, index: Int, peerId: PeerId, peer: Peer?, presence: PeerPresence?, activity: PeerInputActivity?, memberStatus: GroupInfoMemberStatus, editing: ShortPeerDeleting?, menuItems: [ContextMenuItem], enabled:Bool, viewType: GeneralViewType) + case showMore(section:Int, index: Int, viewType: GeneralViewType) + case leave(section:Int, text: String, viewType: GeneralViewType) + case media(section:Int, controller: PeerMediaController, isVisible: Bool, viewType: GeneralViewType) case section(Int) + func withUpdatedViewType(_ viewType: GeneralViewType) -> GroupInfoEntry { + switch self { + case let .info(section, view, editingState, updatingPhotoState, _): return .info(section: section, view: view, editingState: editingState, updatingPhotoState: updatingPhotoState, viewType: viewType) + case let .setTitle(section, text, _): return .setTitle(section: section, text: text, viewType: viewType) + case let .scam(section, text, _): return .scam(section: section, text: text, viewType: viewType) + case let .about(section, text, _): return .about(section: section, text: text, viewType: viewType) + case let .addressName(section, name, _): return .addressName(section: section, name: name, viewType: viewType) + case let .sharedMedia(section, _): return .sharedMedia(section: section, viewType: viewType) + case let .notifications(section, settings, _): return .notifications(section: section, settings: settings, viewType: viewType) + case let .usersHeader(section, count, _): return .usersHeader(section: section, count: count, viewType: viewType) + case let .addMember(section, inviteViaLink, _): return .addMember(section: section, inviteViaLink: inviteViaLink, viewType: viewType) + case let .groupTypeSetup(section, isPublic, _): return .groupTypeSetup(section: section, isPublic: isPublic, viewType: viewType) + case let .linkedChannel(section, channel, subscriber, _): return .linkedChannel(section: section, channel: channel, subscribers: subscriber, viewType: viewType) + case let .groupDescriptionSetup(section, text, _): return .groupDescriptionSetup(section: section, text: text, viewType: viewType) + case let .groupAboutDescription(section, _): return .groupAboutDescription(section: section, viewType: viewType) + case let .groupStickerset(section, packName, _): return .groupStickerset(section: section, packName: packName, viewType: viewType) + case let .preHistory(section, enabled, _): return .preHistory(section: section, enabled: enabled, viewType: viewType) + case let .groupManagementInfoLabel(section, text, _): return .groupManagementInfoLabel(section: section, text: text, viewType: viewType) + case let .administrators(section, count, _): return .administrators(section: section, count: count, viewType: viewType) + case let .permissions(section, count, _): return .permissions(section: section, count: count, viewType: viewType) + case let .member(section, index, peerId, peer, presence, activity, memberStatus, editing, menuItems, enabled, _): return .member(section: section, index: index, peerId: peerId, peer: peer, presence: presence, activity: activity, memberStatus: memberStatus, editing: editing, menuItems: menuItems, enabled: enabled, viewType: viewType) + case let .showMore(section, index, _): return .showMore(section: section, index: index, viewType: viewType) + case let .leave(section, text, _): return .leave(section: section, text: text, viewType: viewType) + case let .media(section, controller, isVisible, _): return .media(section: section, controller: controller, isVisible: isVisible, viewType: viewType) + case .section: return self + } + } + func isEqual(to: PeerInfoEntry) -> Bool { guard let entry = to as? GroupInfoEntry else { return false } switch self { - case let .info(_, lhsPeerView, lhsEditable, lhsUpdatingPhotoState): + case let .info(lhsSection, lhsPeerView, lhsEditingState, lhsUpdatingPhotoState, lhsViewType): switch entry { - case let .info(_, rhsPeerView, rhsEditable, rhsUpdatingPhotoState): + case let .info(rhsSection, rhsPeerView, rhsEditingState, rhsUpdatingPhotoState, rhsViewType): - if lhsEditable != rhsEditable { + if lhsUpdatingPhotoState != rhsUpdatingPhotoState { return false } - if lhsUpdatingPhotoState != rhsUpdatingPhotoState { + if lhsSection != rhsSection { + return false + } + if lhsViewType != rhsViewType { + return false + } + + if lhsEditingState != rhsEditingState { return false } let lhsPeer = peerViewMainPeer(lhsPeerView) let lhsCachedData = lhsPeerView.cachedData + let lhsNotificationSettings = lhsPeerView.notificationSettings let rhsPeer = peerViewMainPeer(rhsPeerView) let rhsCachedData = rhsPeerView.cachedData - + let rhsNotificationSettings = rhsPeerView.notificationSettings if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { if !lhsPeer.isEqual(rhsPeer) { return false @@ -615,6 +938,14 @@ enum GroupInfoEntry: PeerInfoEntry { } else if (lhsPeer == nil) != (rhsPeer != nil) { return false } + + if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings { + if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) { + return false + } + } else if (lhsNotificationSettings == nil) != (rhsNotificationSettings == nil) { + return false + } if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData { if !lhsCachedData.isEqual(to: rhsCachedData) { return false @@ -626,65 +957,64 @@ enum GroupInfoEntry: PeerInfoEntry { default: return false } - case .setGroupPhoto: - if case .setGroupPhoto = entry { + case let .setTitle(section, text, viewType): + if case .setTitle(section, text, viewType) = entry { return true } else { return false } - case let .addressName(_, addressName): - if case .addressName(_, addressName) = entry { + case let .addressName(section, addressName, viewType): + if case .addressName(section, addressName, viewType) = entry { return true } else { return false } - case let .about(_, text): - if case .about(_, text) = entry { + case let .scam(section, text, viewType): + if case .scam(section, text, viewType) = entry { return true } else { return false } - case .sharedMedia: - if case .sharedMedia = entry { + case let .about(section, text, viewType): + if case .about(section, text, viewType) = entry { return true } else { return false } - case let .preHistory(sectionId, enabled): - if case .preHistory(sectionId, enabled) = entry { + case let .sharedMedia(sectionId, viewType): + if case .sharedMedia(sectionId, viewType) = entry { return true } else { return false } - case .setAdmins: - if case .setAdmins = entry { + case let .preHistory(sectionId, enabled, viewType): + if case .preHistory(sectionId, enabled, viewType) = entry { return true } else { return false } - - case .inviteLink: - if case .inviteLink = entry { + case let .administrators(section, count, viewType): + if case .administrators(section, count, viewType) = entry { return true } else { return false } - case let .groupStickerset(sectionId, packName): - if case .groupStickerset(sectionId, packName) = entry { + case let .permissions(section, count, viewType): + if case .permissions(section, count, viewType) = entry { return true } else { return false } - case .convertToSuperGroup: - if case .convertToSuperGroup = entry { + case let .groupStickerset(sectionId, packName, viewType): + if case .groupStickerset(sectionId, packName, viewType) = entry { return true } else { return false } - case let .notifications(_, lhsSettings): + case let .notifications(section, lhsSettings, viewType): switch entry { - case let .notifications(_, rhsSettings): - + case .notifications(section, let rhsSettings, viewType): + if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings { return lhsSettings.isEqual(to: rhsSettings) } else if (lhsSettings != nil) != (rhsSettings != nil) { @@ -694,59 +1024,56 @@ enum GroupInfoEntry: PeerInfoEntry { default: return false } - case let .groupTypeSetup(_, isPublic): - if case .groupTypeSetup(_, isPublic) = entry { + case let .groupTypeSetup(sectionId, isPublic, viewType): + if case .groupTypeSetup(sectionId, isPublic, viewType) = entry { return true } else { return false } - case .groupDescriptionSetup: - if case .groupDescriptionSetup = entry { - return true + case let .linkedChannel(sectionId, lhsChannel, subscribers, viewType): + if case .linkedChannel(sectionId, let rhsChannel, subscribers, viewType) = entry { + return lhsChannel.isEqual(rhsChannel) } else { return false } - case .groupAboutDescription: - if case .groupAboutDescription = entry { - return true - } else { - return false - } - case let .groupManagementInfoLabel(_, text): - if case .groupManagementInfoLabel(_, text) = entry { + case let .groupDescriptionSetup(section, text, viewType): + if case .groupDescriptionSetup(section, text, viewType) = entry { return true } else { return false } - case let .membersAdmins(_, count): - if case .membersAdmins(_, count) = entry { + case let .groupAboutDescription(section, viewType): + if case .groupAboutDescription(section, viewType) = entry { return true } else { return false } - case let .membersBlacklist(_, count): - if case .membersBlacklist(_, count) = entry { + case let .groupManagementInfoLabel(section, text, viewType): + if case .groupManagementInfoLabel(section, text, viewType) = entry { return true } else { return false } - case let .usersHeader(_, count): - if case .usersHeader(_, count) = entry { + case let .usersHeader(section, count, viewType): + if case .usersHeader(section, count, viewType) = entry { return true } else { return false } - case .addMember: - if case .addMember = entry { + case let .addMember(section, inviteViaLink, viewType): + if case .addMember(section, inviteViaLink, viewType) = entry { return true } else { return false } - case let .member(_, lhsIndex, lhsPeerId, lhsPeer, lhsPresence, lhsMemberStatus, lhsEditing, lhsEnabled): - if case let .member(_, rhsIndex, rhsPeerId, rhsPeer, rhsPresence, rhsMemberStatus, rhsEditing, rhsEnabled) = entry { + case let .member(lhsSection, lhsIndex, lhsPeerId, lhsPeer, lhsPresence, lhsActivity, lhsMemberStatus, lhsEditing, lhsMenuItems, lhsEnabled, lhsViewType): + if case let .member(rhsSection, rhsIndex, rhsPeerId, rhsPeer, rhsPresence, rhsActivity, rhsMemberStatus, rhsEditing, rhsMenuItems, rhsEnabled, rhsViewType) = entry { if lhsIndex != rhsIndex { return false } + if lhsSection != rhsSection { + return false + } if lhsMemberStatus != rhsMemberStatus { return false } @@ -756,6 +1083,9 @@ enum GroupInfoEntry: PeerInfoEntry { if lhsEnabled != rhsEnabled { return false } + if lhsViewType != rhsViewType { + return false + } if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { if !lhsPeer.isEqual(rhsPeer) { return false @@ -763,6 +1093,9 @@ enum GroupInfoEntry: PeerInfoEntry { } else if (lhsPeer != nil) != (rhsPeer != nil) { return false } + if lhsActivity != rhsActivity { + return false + } if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { if !lhsPresence.isEqual(to: rhsPresence) { return false @@ -773,13 +1106,27 @@ enum GroupInfoEntry: PeerInfoEntry { if lhsEditing != rhsEditing { return false } - + if lhsMenuItems != rhsMenuItems { + return false + } return true } else { return false } - case .leave: - if case .leave = entry { + case let .showMore(sectionId, index, viewType): + if case .showMore(sectionId, index, viewType) = entry { + return true + } else { + return false + } + case let .leave(sectionId, text, viewType): + if case .leave(sectionId, text, viewType) = entry { + return true + } else { + return false + } + case let .media(sectionId, _, isVisible, viewType): + if case .media(sectionId, _, isVisible, viewType) = entry { return true } else { return false @@ -796,7 +1143,7 @@ enum GroupInfoEntry: PeerInfoEntry { var stableId: PeerInfoEntryStableId { switch self { - case let .member(_, _, peerId, _, _, _, _, _): + case let .member(_, _, peerId, _, _, _, _, _, _, _, _): return GroupPeerEntryStableId(peerId: peerId) default: return IntPeerInfoEntryStableId(value: stableIndex) @@ -807,35 +1154,35 @@ enum GroupInfoEntry: PeerInfoEntry { switch self { case .info: return 0 - case .about: + case .setTitle: return 1 - case .addressName: + case .scam: return 2 - case .setGroupPhoto: + case .about: return 3 - case .inviteLink: + case .addressName: return 4 - case .notifications: + case .groupDescriptionSetup: return 5 - case .sharedMedia: + case .groupAboutDescription: return 6 - case .groupTypeSetup: + case .notifications: return 7 - case .preHistory: + case .sharedMedia: return 8 - case .setAdmins: + case .groupTypeSetup: return 9 - case .groupDescriptionSetup: + case .linkedChannel: return 10 - case .groupAboutDescription: + case .preHistory: return 11 case .groupStickerset: return 12 case .groupManagementInfoLabel: return 13 - case .membersAdmins: + case .permissions: return 14 - case .membersBlacklist: + case .administrators: return 15 case .usersHeader: return 16 @@ -843,63 +1190,116 @@ enum GroupInfoEntry: PeerInfoEntry { return 17 case .member: fatalError("no stableIndex") - case .convertToSuperGroup: - return 18 - case .leave: + case .showMore: return 19 + case .leave: + return 20 + case .media: + return 21 case let .section(id): - return (id + 1) * 1000 - id + return (id + 1) * 100000 - id } } - + var sectionId: Int { + switch self { + case let .info(sectionId, _, _, _, _): + return sectionId + case let .scam(sectionId, _, _): + return sectionId + case let .about(sectionId, _, _): + return sectionId + case let .addressName(sectionId, _, _): + return sectionId + case let .setTitle(sectionId, _, _): + return sectionId + case let .addMember(sectionId, _, _): + return sectionId + case let .notifications(sectionId, _, _): + return sectionId + case let .sharedMedia(sectionId, _): + return sectionId + case let .groupTypeSetup(sectionId, _, _): + return sectionId + case let .linkedChannel(sectionId, _, _, _): + return sectionId + case let .preHistory(sectionId, _, _): + return sectionId + case let .groupStickerset(sectionId, _, _): + return sectionId + case let .administrators(sectionId, _, _): + return sectionId + case let .permissions(sectionId, _, _): + return sectionId + case let .groupDescriptionSetup(sectionId, _, _): + return sectionId + case let .groupAboutDescription(sectionId, _): + return sectionId + case let .groupManagementInfoLabel(sectionId, _, _): + return sectionId + case let .usersHeader(sectionId, _, _): + return sectionId + case let .member(sectionId, _, _, _, _, _, _, _, _, _, _): + return sectionId + case let .showMore(sectionId, _, _): + return sectionId + case let .leave(sectionId, _, _): + return sectionId + case let .media(sectionId, _, _, _): + return sectionId + case let .section(sectionId): + return sectionId + } + } var sortIndex: Int { switch self { - case let .info(sectionId, _, _, _): - return (sectionId * 1000) + stableIndex - case let .about(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .addressName(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .setGroupPhoto(sectionId): - return (sectionId * 1000) + stableIndex - case let .inviteLink(sectionId): - return (sectionId * 1000) + stableIndex - case let .addMember(sectionId): - return (sectionId * 1000) + stableIndex - case let .notifications(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .sharedMedia(sectionId): - return (sectionId * 1000) + stableIndex - case let .groupTypeSetup(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .preHistory(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .groupStickerset(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .setAdmins(sectionId): - return (sectionId * 1000) + stableIndex - case let .groupDescriptionSetup(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .groupAboutDescription(sectionId): - return (sectionId * 1000) + stableIndex - case let .groupManagementInfoLabel(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .membersAdmins(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .membersBlacklist(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .usersHeader(sectionId, _): - return (sectionId * 1000) + stableIndex - case let .member(sectionId, index, _, _, _, _, _, _): - return (sectionId * 1000) + index + 200 - case let .leave(sectionId): - return (sectionId * 1000) + stableIndex - case let .convertToSuperGroup(sectionId): - return (sectionId * 1000) + stableIndex + case let .info(sectionId, _, _, _, _): + return (sectionId * 100000) + stableIndex + case let .scam(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .about(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .addressName(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .setTitle(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .addMember(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .notifications(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .sharedMedia(sectionId, _): + return (sectionId * 100000) + stableIndex + case let .groupTypeSetup(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .linkedChannel(sectionId, _, _, _): + return (sectionId * 100000) + stableIndex + case let .preHistory(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .groupStickerset(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .administrators(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .permissions(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .groupDescriptionSetup(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .groupAboutDescription(sectionId, _): + return (sectionId * 100000) + stableIndex + case let .groupManagementInfoLabel(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .usersHeader(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .member(sectionId, index, _, _, _, _, _, _, _, _, _): + return (sectionId * 100000) + index + 200 + case let .showMore(sectionId, index, _): + return (sectionId * 100000) + index + 200 + case let .leave(sectionId, _, _): + return (sectionId * 100000) + stableIndex + case let .media(sectionId, _, _, _): + return (sectionId * 100000) + stableIndex case let .section(sectionId): - return (sectionId + 1) * 1000 - sectionId + return (sectionId + 1) * 100000 - sectionId } } @@ -908,131 +1308,100 @@ enum GroupInfoEntry: PeerInfoEntry { return false } - return self.sortIndex > other.sortIndex + return self.sortIndex < other.sortIndex } func item(initialSize:NSSize, arguments:PeerInfoArguments) -> TableRowItem { let arguments = arguments as! GroupInfoArguments - let state = arguments.state as! GroupInfoState - switch self { - case let .info(_, peerView, editable, updatingPhotoState): - return PeerInfoHeaderItem(initialSize, stableId:stableId.hashValue, account: arguments.account, peerView:peerView, editable: editable, updatingPhotoState: updatingPhotoState, firstNameEditableText: state.editingState?.editingName, textChangeHandler: { name, _ in - arguments.updateEditingName(name) - }) - case let .about(_, text): - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:tr(.peerInfoInfo), text:text, account: arguments.account, detectLinks:true, openInfo: { peerId, toChat, _, _ in + case let .info(_, peerView, editable, updatingPhotoState, viewType): + return PeerInfoHeadItem(initialSize, stableId: stableId.hashValue, context: arguments.context, arguments: arguments, peerView: peerView, viewType: viewType, editing: editable, updatingPhotoState: updatingPhotoState, updatePhoto: arguments.updateGroupPhoto) + case let .scam(_, text, viewType): + return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label: L10n.peerInfoScam, labelColor: theme.colors.redUI, text: text, context: arguments.context, viewType: viewType, detectLinks:false) + case let .about(_, text, viewType): + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label: L10n.peerInfoInfo, text: text, context: arguments.context, viewType: viewType, detectLinks:true, openInfo: { peerId, toChat, postId, _ in if toChat { - arguments.peerChat(peerId) + arguments.peerChat(peerId, postId: postId) } else { arguments.peerInfo(peerId) } - }, hashtag: arguments.account.context.globalSearch) - case let .addressName(_, value): + }, hashtag: arguments.context.sharedContext.bindings.globalSearch) + case let .addressName(_, value, viewType): let link = "https://t.me/\(value)" - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:tr(.peerInfoSharelink), text: link, account: arguments.account, isTextSelectable:false, callback:{ - showModal(with: ShareModalController(ShareLinkObject(arguments.account, link: link)), for: mainWindow) - }) - case .setGroupPhoto: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSetGroupPhoto), nameStyle: blueActionButton, type: .none, action: { - - pickImage(for: mainWindow, completion: { image in - if let image = image { - _ = (putToTemp(image: image) |> deliverOnMainQueue).start(next: { path in - arguments.updatePhoto(path) - }) - } - }) - - }) - case let .notifications(_, settings): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoNotifications), type: .switchable(stateback: { () -> Bool in - - if let settings = settings as? TelegramPeerNotificationSettings, case .muted = settings.muteState { - return false - } else { - return true - } - - }), action: { - arguments.toggleNotifications() + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label: L10n.peerInfoSharelink, text: link, context: arguments.context, viewType: viewType, isTextSelectable:false, callback:{ + showModal(with: ShareModalController(ShareLinkObject(arguments.context, link: link)), for: mainWindow) + }, selectFullWord: true) + case let .setTitle(_, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoGroupTitlePleceholder, filter: { $0 }, updated: arguments.updateEditingName, limit: 255) + case let .notifications(_, settings, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoNotifications, type: .switchable(!((settings as? TelegramPeerNotificationSettings)?.isMuted ?? true)), viewType: viewType, action: { + arguments.toggleNotifications() }) - - case .sharedMedia: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSharedMedia), type: .none, action: { () in + + case let .sharedMedia(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSharedMedia, type: .next, viewType: viewType, action: { () in arguments.sharedMedia() }) - case let .groupDescriptionSetup(section: _, text: text): - return GeneralInputRowItem(initialSize, stableId: stableId.hashValue, placeholder: tr(.peerInfoAboutPlaceholder), text: text, limit: 255, insets: NSEdgeInsets(left:25,right:25,top:8,bottom:3), textChangeHandler: { updatedText in - arguments.updateEditingDescriptionText(updatedText) - }) - case let .preHistory(_, enabled): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoPreHistory), type: .context(stateback: { () -> String in - return enabled ? tr(.peerInfoPreHistoryVisible) : tr(.peerInfoPreHistoryHidden) - }), action: { () in + case let .groupDescriptionSetup(section: _, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoAboutPlaceholder, filter: { $0 }, updated: arguments.updateEditingDescriptionText, limit: 255) + case let .preHistory(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoPreHistory, type: .context(enabled ? L10n.peerInfoPreHistoryVisible : L10n.peerInfoPreHistoryHidden), viewType: viewType, action: { arguments.preHistorySetup() }) - case .groupAboutDescription: - return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: tr(.peerInfoSetAboutDescription)) - - case let .groupTypeSetup(section: _, isPublic: isPublic): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoGroupType), type: .context(stateback: { () -> String in - return isPublic ? tr(.peerInfoGroupTypePublic) : tr(.peerInfoGroupTypePrivate) - }), action: { () in + case let .groupAboutDescription(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: L10n.peerInfoSetAboutDescription, viewType: viewType) + + case let .groupTypeSetup(section: _, isPublic: isPublic, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoGroupType, type: .nextContext(isPublic ? L10n.peerInfoGroupTypePublic : L10n.peerInfoGroupTypePrivate), viewType: viewType, action: { () in arguments.visibilitySetup() }) - case .setAdmins: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSetAdmins), type: .none, action: { () in - arguments.setGroupAdmins() + case let .linkedChannel(_, channel, _, viewType): + let title: String + if let address = channel.addressName { + title = "@\(address)" + } else { + title = channel.displayTitle + } + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoLinkedChannel, type: .nextContext(title), viewType: viewType, action: { () in + arguments.setupDiscussion() }) - case .groupStickerset(_, let name): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSetGroupStickersSet), type: .context(stateback: { - return name - }), action: { () in + case let .groupStickerset(_, name, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSetGroupStickersSet, type: .nextContext(name), viewType: viewType, action: { () in arguments.setGroupStickerset() }) - case let .membersBlacklist(section: _, count: count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoBlackList), type: .context(stateback: { () -> String in - return "\(count)" - }), action: { () in + case let .permissions(section: _, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoPermissions, icon: theme.icons.peerInfoPermissions, type: .nextContext(count), viewType: viewType, action: { () in arguments.blacklist() }) - case let .membersAdmins(section: _, count: count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoAdmins), type: .context(stateback: { () -> String in - return "\(count)" - }), action: { () in + case let .administrators(section: _, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoAdministrators, icon: theme.icons.peerInfoAdmins, type: .nextContext(count), viewType: viewType, action: { () in arguments.admins() }) - case let .usersHeader(section: _, count: count): - return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: tr(.peerInfoMembersHeaderCountable(count))) - case .addMember: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoAddMember), nameStyle: blueActionButton, type: .none, action: { () in - - arguments.addMember() - - }, thumb: GeneralThumbAdditional(thumb: theme.icons.peerInfoAddMember, textInset: 36), inset:NSEdgeInsets(left: 40, right: 30)) - case .inviteLink: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoInviteLink), nameStyle: blueActionButton, type: .none, action: { () in - arguments.invation() - }) - - case let .member(_, _, _, peer, presence, memberStatus, editing, enabled): + case let .usersHeader(section: _, count, viewType): + var countValue = L10n.peerInfoMembersHeaderCountable(count) + countValue = countValue.replacingOccurrences(of: "\(count)", with: count.separatedNumber) + return GeneralTextRowItem(initialSize, stableId: stableId.hashValue, text: countValue, viewType: viewType) + case let .addMember(_, inviteViaLink, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoAddMember, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { () in + arguments.addMember(inviteViaLink) + }, thumb: GeneralThumbAdditional(thumb: theme.icons.peerInfoAddMember, textInset: 52, thumbInset: 5)) + case let .member(_, _, _, peer, presence, inputActivity, memberStatus, editing, menuItems, enabled, viewType): let label: String switch memberStatus { - case .admin: - label = tr(.peerInfoAdminLabel) + case let .admin(rank): + label = rank case .member: label = "" } - - var string:String = tr(.peerStatusRecently) + + var string:String = L10n.peerStatusRecently var color:NSColor = theme.colors.grayText - if let presence = presence as? TelegramUserPresence { + if let peer = peer as? TelegramUser, let botInfo = peer.botInfo { + string = botInfo.flags.contains(.hasAccessToChatHistory) ? L10n.peerInfoBotStatusHasAccess : L10n.peerInfoBotStatusHasNoAccess + } else if let presence = presence as? TelegramUserPresence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) - } else if let peer = peer as? TelegramUser, let botInfo = peer.botInfo { - string = botInfo.flags.contains(.hasAccessToChatHistory) ? tr(.peerInfoBotStatusHasAccess) : tr(.peerInfoBotStatusHasNoAccess) + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) } let interactionType:ShortPeerItemInteractionType @@ -1045,137 +1414,201 @@ enum GroupInfoEntry: PeerInfoEntry { interactionType = .plain } - return ShortPeerRowItem(initialSize, peer: peer!, account: arguments.account, stableId: stableId.hashValue, enabled: enabled, height: 46, photoSize: NSMakeSize(36, 36), titleStyle: ControlStyle(font: .medium(.custom(12.5)), foregroundColor: theme.colors.text), statusStyle: ControlStyle(font: NSFont.normal(.custom(12.5)), foregroundColor:color), status: string, inset:NSEdgeInsets(left:30.0,right:30.0), interactionType: interactionType, generalType:.context( stateback: { - return label - }), action:{ + return ShortPeerRowItem(initialSize, peer: peer!, account: arguments.context.account, stableId: stableId.hashValue, enabled: enabled, height: 50, photoSize: NSMakeSize(36, 36), titleStyle: ControlStyle(font: .medium(12.5), foregroundColor: theme.colors.text), statusStyle: ControlStyle(font: NSFont.normal(12.5), foregroundColor:color), status: string, inset: NSEdgeInsets(left:30.0,right:30.0), interactionType: interactionType, generalType: .context(label), viewType: viewType, action: { arguments.peerInfo(peer!.id) - }) - - case .leave: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoDeleteAndExit), nameStyle: redActionButton, type: .none, action: { + }, contextMenuItems: { + return .single(menuItems) + }, inputActivity: inputActivity) + case let .showMore(_, _, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoShowMore, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.showMore() + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + case let .leave(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: text, nameStyle: redActionButton, type: .none, viewType: viewType, action: { arguments.delete() }) - case .convertToSuperGroup: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoConvertToSupergroup), nameStyle: blueActionButton, type: .none, action: { () in - arguments.convert() - }) - case .section(_): - return GeneralRowItem(initialSize, height:20, stableId: stableId.hashValue) + case let .media(_, controller, isVisible, viewType): + return PeerMediaBlockRowItem(initialSize, stableId: stableId.hashValue, controller: controller, isVisible: isVisible, viewType: viewType) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId.hashValue, viewType: .separator) default: preconditionFailure() } } } +enum GroupInfoSection : Int { + case header = 1 + case info = 2 + case desc = 3 + case action = 4 + case addition = 5 + case type = 6 + case admin = 7 + case members = 8 + case destruct = 9 + case media = 10 +} - -func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfoEntry] { - var entries: [PeerInfoEntry] = [] +func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments, inputActivities: [PeerId: PeerInputActivity], channelMembers: [RenderedChannelParticipant] = [], mediaTabsData: PeerMediaTabsData) -> [PeerInfoEntry] { + var entries: [GroupInfoEntry] = [] if let group = peerViewMainPeer(view), let arguments = arguments as? GroupInfoArguments, let state = arguments.state as? GroupInfoState { - var sectionId:Int = 0 - let access = group.groupAccess - var canInviteByLink = access.isCreator + let access = group.groupAccess - var canEditInfo = state.editingState != nil - if let group = group as? TelegramChannel { - canEditInfo = state.editingState != nil && group.hasAdminRights(.canChangeInfo) - canInviteByLink = group.hasAdminRights(.canChangeInviteLink) - } - entries.append(GroupInfoEntry.info(section: sectionId, view: view, editable: canEditInfo, updatingPhotoState: state.updatingPhotoState)) + var infoBlock: [GroupInfoEntry] = [] + func applyBlock(_ block:[GroupInfoEntry]) { + var block = block + for (i, item) in block.enumerated() { + block[i] = item.withUpdatedViewType(bestGeneralViewType(block, for: i)) + } + entries.append(contentsOf: block) + } - + infoBlock.append(.info(section: GroupInfoSection.header.rawValue, view: view, editingState: state.editingState != nil, updatingPhotoState: state.updatingPhotoState, viewType: .singleItem)) if let editingState = state.editingState { - if canEditInfo { - entries.append(GroupInfoEntry.setGroupPhoto(section: sectionId)) - } - if canInviteByLink { - entries.append(GroupInfoEntry.inviteLink(section: sectionId)) + if access.canEditGroupInfo { + infoBlock.append(GroupInfoEntry.setTitle(section: GroupInfoSection.header.rawValue, text: editingState.editingName ?? group.displayTitle, viewType: .singleItem)) + + infoBlock.append(GroupInfoEntry.groupDescriptionSetup(section: GroupInfoSection.header.rawValue, text: editingState.editingDescriptionText, viewType: .singleItem)) + applyBlock(infoBlock) + + entries.append(GroupInfoEntry.groupAboutDescription(section: GroupInfoSection.header.rawValue, viewType: .textBottomItem)) + + + } else { + applyBlock(infoBlock) } - entries.append(GroupInfoEntry.section(sectionId)) - sectionId += 1 - if let cachedChannelData = view.cachedData as? CachedChannelData { + if let group = view.peers[view.peerId] as? TelegramGroup, let cachedGroupData = view.cachedData as? CachedGroupData { + if case .creator = group.role { + if cachedGroupData.flags.contains(.canChangeUsername) { + entries.append(GroupInfoEntry.groupTypeSetup(section: GroupInfoSection.type.rawValue, isPublic: group.addressName != nil, viewType: .firstItem)) + entries.append(GroupInfoEntry.preHistory(section: GroupInfoSection.type.rawValue, enabled: false, viewType: .lastItem)) + } + + var activePermissionCount: Int? + if let defaultBannedRights = group.defaultBannedRights { + var count = 0 + for right in allGroupPermissionList { + if !defaultBannedRights.flags.contains(right) { + count += 1 + } + } + activePermissionCount = count + } + + entries.append(GroupInfoEntry.permissions(section: GroupInfoSection.admin.rawValue, count: activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? "", viewType: .firstItem)) + entries.append(GroupInfoEntry.administrators(section: GroupInfoSection.admin.rawValue, count: "", viewType: .lastItem)) + } + } else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData { + + var actionBlock:[GroupInfoEntry] = [] if access.isCreator { - entries.append(GroupInfoEntry.groupTypeSetup(section: sectionId, isPublic: group.addressName != nil)) - if group.addressName == nil { - entries.append(GroupInfoEntry.preHistory(section: sectionId, enabled: cachedChannelData.flags.contains(.preHistoryEnabled))) + actionBlock.append(.groupTypeSetup(section: GroupInfoSection.type.rawValue, isPublic: group.addressName != nil, viewType: .singleItem)) + } + if (channel.adminRights != nil || channel.flags.contains(.isCreator)), let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + actionBlock.append(.linkedChannel(section: GroupInfoSection.type.rawValue, channel: peer, subscribers: cachedChannelData.participantsSummary.memberCount, viewType: .singleItem)) + } else if channel.hasPermission(.banMembers) { + if !access.isPublic { + actionBlock.append(.preHistory(section: GroupInfoSection.type.rawValue, enabled: cachedChannelData.flags.contains(.preHistoryEnabled), viewType: .singleItem)) } } - if canEditInfo { - entries.append(GroupInfoEntry.groupDescriptionSetup(section: sectionId, text: editingState.editingDescriptionText)) - entries.append(GroupInfoEntry.groupAboutDescription(section: sectionId)) - - entries.append(GroupInfoEntry.section(sectionId)) - sectionId += 1 - - if cachedChannelData.flags.contains(.canSetStickerSet) { - entries.append(GroupInfoEntry.groupStickerset(section: sectionId, packName: cachedChannelData.stickerPack?.title ?? "")) - - entries.append(GroupInfoEntry.section(sectionId)) - sectionId += 1 - } - - + if cachedChannelData.flags.contains(.canSetStickerSet) && access.canEditGroupInfo { + actionBlock.append(.groupStickerset(section: GroupInfoSection.type.rawValue, packName: cachedChannelData.stickerPack?.title ?? "", viewType: .singleItem)) } + applyBlock(actionBlock) - if access.canManageGroup { - let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0 - entries.append(GroupInfoEntry.membersAdmins(section: sectionId, count: Int(adminCount))) - let bannedCount = (cachedChannelData.participantsSummary.bannedCount ?? 0) + (cachedChannelData.participantsSummary.kickedCount ?? 0) - entries.append(GroupInfoEntry.membersBlacklist(section: sectionId, count: Int(bannedCount))) - + var canViewAdminsAndBanned = false + if let channel = view.peers[view.peerId] as? TelegramChannel { + if let adminRights = channel.adminRights, !adminRights.isEmpty { + canViewAdminsAndBanned = true + } else if channel.flags.contains(.isCreator) { + canViewAdminsAndBanned = true + } } - } else if group.isGroup { - if access.isCreator { - entries.append(GroupInfoEntry.setAdmins(section: sectionId)) + if canViewAdminsAndBanned { + var activePermissionCount: Int? + if let defaultBannedRights = channel.defaultBannedRights { + var count = 0 + for right in allGroupPermissionList { + if !defaultBannedRights.flags.contains(right) { + count += 1 + } + } + activePermissionCount = count + } + + + entries.append(GroupInfoEntry.permissions(section: GroupInfoSection.admin.rawValue, count: activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? "", viewType: .firstItem)) + entries.append(GroupInfoEntry.administrators(section: GroupInfoSection.admin.rawValue, count: cachedChannelData.participantsSummary.adminCount.flatMap { "\($0)" } ?? "", viewType: .lastItem)) + } } + } else { + applyBlock(infoBlock) + + + + var aboutBlock:[GroupInfoEntry] = [] + + if group.isScam { + aboutBlock.append(GroupInfoEntry.scam(section: GroupInfoSection.desc.rawValue, text: L10n.groupInfoScamWarning, viewType: .singleItem)) + } + if let cachedChannelData = view.cachedData as? CachedChannelData { - if let about = cachedChannelData.about, !about.isEmpty { - entries.append(GroupInfoEntry.about(section: sectionId, text: about)) + if let about = cachedChannelData.about, !about.isEmpty, !group.isScam { + aboutBlock.append(GroupInfoEntry.about(section: GroupInfoSection.desc.rawValue, text: about, viewType: .singleItem)) } } - if let addressName = group.addressName { - entries.append(GroupInfoEntry.addressName(section: sectionId, name: addressName)) + + if let cachedGroupData = view.cachedData as? CachedGroupData { + if let about = cachedGroupData.about, !about.isEmpty, !group.isScam { + aboutBlock.append(GroupInfoEntry.about(section: GroupInfoSection.desc.rawValue, text: about, viewType: .singleItem)) + } } - if entries.count > 1 { - entries.append(GroupInfoEntry.section(sectionId)) - sectionId += 1 + if let addressName = group.addressName { + aboutBlock.append(GroupInfoEntry.addressName(section: GroupInfoSection.desc.rawValue, name: addressName, viewType: .singleItem)) } - entries.append(GroupInfoEntry.sharedMedia(section: sectionId)) - } - - entries.append(GroupInfoEntry.notifications(section: sectionId, settings: view.notificationSettings)) + applyBlock(aboutBlock) + +// +// entries.append(GroupInfoEntry.notifications(section: GroupInfoSection.addition.rawValue, settings: view.notificationSettings, viewType: .firstItem)) +// entries.append(GroupInfoEntry.sharedMedia(section: GroupInfoSection.addition.rawValue, viewType: .lastItem)) + + } + + - if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants { + if let participants = (view.cachedData as? CachedGroupData)?.participants, participants.participants.count <= minumimUsersBlock, let group = peerViewMainPeer(view) as? TelegramGroup { - entries.append(GroupInfoEntry.section(sectionId)) - sectionId = 10 + // entries.append(GroupInfoEntry.usersHeader(section: GroupInfoSection.members.rawValue, count: participants.participants.count, viewType: .textTopItem)) - entries.append(GroupInfoEntry.usersHeader(section: sectionId, count: participants.participants.count)) + var usersBlock:[GroupInfoEntry] = [] - if access.canManageMembers { - entries.append(GroupInfoEntry.addMember(section: sectionId)) - } +// if access.canAddMembers { +// usersBlock.append(.addMember(section: GroupInfoSection.members.rawValue, inviteViaLink: access.canCreateInviteLink, viewType: .singleItem)) +// } var updatedParticipants = participants.participants let existingParticipantIds = Set(updatedParticipants.map { $0.peerId }) + + var peerPresences: [PeerId: PeerPresence] = view.peerPresences var peers: [PeerId: Peer] = view.peers var disabledPeerIds = state.removingParticipantIds @@ -1183,7 +1616,7 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo if !state.temporaryParticipants.isEmpty { for participant in state.temporaryParticipants { if !existingParticipantIds.contains(participant.peer.id) { - updatedParticipants.append(.member(id: participant.peer.id, invitedBy: arguments.account.peerId, invitedAt: participant.timestamp)) + updatedParticipants.append(.member(id: participant.peer.id, invitedBy: arguments.context.account.peerId, invitedAt: participant.timestamp)) if let presence = participant.presence, peerPresences[participant.peer.id] == nil { peerPresences[participant.peer.id] = presence } @@ -1195,9 +1628,19 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo } } - let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in + let sortedParticipants = participants.participants.filter({peers[$0.peerId]?.displayTitle != nil}).sorted(by: { lhs, rhs in let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence let rhsPresence = view.peerPresences[rhs.peerId] as? TelegramUserPresence + + let lhsActivity = inputActivities[lhs.peerId] + let rhsActivity = inputActivities[rhs.peerId] + + if lhsActivity != nil && rhsActivity == nil { + return true + } else if rhsActivity != nil && lhsActivity == nil { + return false + } + if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { return lhsPresence.status > rhsPresence.status } else if let _ = lhsPresence { @@ -1214,8 +1657,10 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo let memberStatus: GroupInfoMemberStatus if access.highlightAdmins { switch sortedParticipants[i] { - case .admin, .creator: - memberStatus = .admin + case .admin: + memberStatus = .admin(rank: L10n.chatAdminBadge) + case .creator: + memberStatus = .admin(rank: L10n.chatOwnerBadge) case .member: memberStatus = .member } @@ -1223,36 +1668,85 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo memberStatus = .member } + var canRestrict: Bool + if sortedParticipants[i].peerId == arguments.context.peerId { + canRestrict = false + } else { + switch group.role { + case .creator: + canRestrict = true + case .member: + switch sortedParticipants[i] { + case .creator, .admin: + canRestrict = false + case let .member(member): + if member.invitedBy == arguments.context.peerId { + canRestrict = true + } else { + canRestrict = false + } + } + case .admin: + switch sortedParticipants[i] { + case .creator, .admin: + canRestrict = false + case .member: + canRestrict = true + } + } + } + + + let editing:ShortPeerDeleting? - if state.editingState != nil, let group = group as? TelegramGroup { - let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i]) + if state.editingState != nil { + let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i]) || (sortedParticipants[i].invitedBy == arguments.context.peerId && sortedParticipants[i].peerId != arguments.context.peerId) editing = ShortPeerDeleting(editable: deletable) } else { editing = nil } - entries.append(GroupInfoEntry.member(section: sectionId, index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], memberStatus: memberStatus, editing: editing, enabled: !disabledPeerIds.contains(peer.id))) + var menuItems:[ContextMenuItem] = [] + + + if canRestrict { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuDelete, handler: { + arguments.removePeer(sortedParticipants[i].peerId) + })) + } + + + usersBlock.append(.member(section: GroupInfoSection.members.rawValue, index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], activity: inputActivities[peer.id], memberStatus: memberStatus, editing: editing, menuItems: menuItems, enabled: !disabledPeerIds.contains(peer.id), viewType: .singleItem)) } } + + if usersBlock.count <= minumimUsersBlock { + applyBlock(usersBlock) + } } - if let cachedGroupData = view.cachedData as? CachedChannelData, let participants = cachedGroupData.topParticipants, let channel = group as? TelegramChannel { + if channelMembers.count <= minumimUsersBlock, let channel = peerViewMainPeer(view) as? TelegramChannel { - var updatedParticipants = participants.participants - let existingParticipantIds = Set(updatedParticipants.map { $0.peerId }) + let participants = channelMembers + + var updatedParticipants = participants + let existingParticipantIds = Set(updatedParticipants.map { $0.peer.id }) var peerPresences: [PeerId: PeerPresence] = view.peerPresences var peers: [PeerId: Peer] = view.peers var disabledPeerIds = state.removingParticipantIds + if !state.temporaryParticipants.isEmpty { for participant in state.temporaryParticipants { if !existingParticipantIds.contains(participant.peer.id) { - //member(id: participant.peer.id, invitedAt: participant.timestamp) - updatedParticipants.append(.member(id: participant.peer.id, invitedAt: participant.timestamp, adminInfo: nil, banInfo: nil)) + updatedParticipants.append(RenderedChannelParticipant(participant: .member(id: participant.peer.id, invitedAt: participant.timestamp, adminInfo: nil, banInfo: nil, rank: nil), peer: participant.peer)) if let presence = participant.presence, peerPresences[participant.peer.id] == nil { peerPresences[participant.peer.id] = presence } + if participant.peer.id == arguments.context.account.peerId { + peerPresences[participant.peer.id] = TelegramUserPresence(status: .present(until: Int32.max), lastActivity: Int32.max) + } if peers[participant.peer.id] == nil { peers[participant.peer.id] = participant.peer } @@ -1261,20 +1755,22 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo } } - entries.append(GroupInfoEntry.section(sectionId)) - sectionId = 10 - - if let membersCount = cachedGroupData.participantsSummary.memberCount { - entries.append(GroupInfoEntry.usersHeader(section: sectionId, count: Int(membersCount))) - } - - if channel.hasAdminRights(.canInviteUsers) { - entries.append(GroupInfoEntry.addMember(section: sectionId)) - } + var usersBlock:[GroupInfoEntry] = [] + - let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in - let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence - let rhsPresence = view.peerPresences[rhs.peerId] as? TelegramUserPresence + var sortedParticipants = participants.filter({!$0.peer.rawDisplayTitle.isEmpty}).sorted(by: { lhs, rhs in + let lhsPresence = lhs.presences[lhs.peer.id] as? TelegramUserPresence + let rhsPresence = rhs.presences[rhs.peer.id] as? TelegramUserPresence + + let lhsActivity = inputActivities[lhs.peer.id] + let rhsActivity = inputActivities[rhs.peer.id] + + if lhsActivity != nil && rhsActivity == nil { + return true + } else if rhsActivity != nil && lhsActivity == nil { + return false + } + if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { return lhsPresence.status > rhsPresence.status } else if let _ = lhsPresence { @@ -1287,49 +1783,123 @@ func groupInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfo }) for i in 0 ..< sortedParticipants.count { - if let peer = view.peers[sortedParticipants[i].peerId] { - let memberStatus: GroupInfoMemberStatus - if access.highlightAdmins { - switch sortedParticipants[i] { - case .creator: - memberStatus = .admin - case .member(_, _, let adminRights, _): - memberStatus = adminRights != nil ? .admin : .member - } - } else { - memberStatus = .member + let memberStatus: GroupInfoMemberStatus + if access.highlightAdmins { + switch sortedParticipants[i].participant { + case let .creator(_, rank): + memberStatus = .admin(rank: rank ?? L10n.chatOwnerBadge) + case let .member(_, _, adminRights, _, rank): + memberStatus = adminRights != nil ? .admin(rank: rank ?? L10n.chatAdminBadge) : .member } - - let editing:ShortPeerDeleting? - - if state.editingState != nil, let group = group as? TelegramChannel { - let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i], accountId: arguments.account.peerId) - editing = ShortPeerDeleting(editable: deletable) - } else { - editing = nil + } else { + memberStatus = .member + } + + var canPromote: Bool + var canRestrict: Bool + if sortedParticipants[i].peer.id == arguments.context.peerId { + canPromote = false + canRestrict = false + } else { + switch sortedParticipants[i].participant { + case .creator: + canPromote = false + canRestrict = false + case let .member(_, _, adminRights, bannedRights, _): + if channel.hasPermission(.addAdmins) { + canPromote = true + } else { + canPromote = false + } + if channel.hasPermission(.banMembers) { + canRestrict = true + } else { + canRestrict = false + } + if canPromote { + if let bannedRights = bannedRights { + if bannedRights.restrictedBy != arguments.context.peerId && !channel.flags.contains(.isCreator) { + canPromote = false + } + } + } + if canRestrict { + if let adminRights = adminRights { + if adminRights.promotedBy != arguments.context.peerId && !channel.flags.contains(.isCreator) { + canRestrict = false + } + } + } } - - entries.append(GroupInfoEntry.member(section: sectionId, index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], memberStatus: memberStatus, editing: editing, enabled: !disabledPeerIds.contains(peer.id))) } + + var menuItems:[ContextMenuItem] = [] + + + if canPromote { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuPromote, handler: { + arguments.promote(sortedParticipants[i].participant) + })) + } + if canRestrict { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuRestrict, handler: { + arguments.restrict(sortedParticipants[i].participant) + })) + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuDelete, handler: { + arguments.removePeer(sortedParticipants[i].peer.id) + })) + } + + + let editing:ShortPeerDeleting? + + if state.editingState != nil, let group = group as? TelegramChannel { + let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i].participant, accountId: arguments.context.account.peerId) + editing = ShortPeerDeleting(editable: deletable) + } else { + editing = nil + } + + usersBlock.append(GroupInfoEntry.member(section: GroupInfoSection.members.rawValue, index: i, peerId: sortedParticipants[i].peer.id, peer: sortedParticipants[i].peer, presence: sortedParticipants[i].presences[sortedParticipants[i].peer.id], activity: inputActivities[sortedParticipants[i].peer.id], memberStatus: memberStatus, editing: editing, menuItems: menuItems, enabled: !disabledPeerIds.contains(sortedParticipants[i].peer.id), viewType: .singleItem)) + } + if usersBlock.count <= minumimUsersBlock { + applyBlock(usersBlock) } } - entries.append(GroupInfoEntry.section(sectionId)) - sectionId += 1 - if let group = peerViewMainPeer(view) as? TelegramGroup { - if case .Member = group.membership { - if state.editingState != nil && access.isCreator { - entries.append(GroupInfoEntry.convertToSuperGroup(section: sectionId)) - } - entries.append(GroupInfoEntry.leave(section: sectionId)) - } - } else if let channel = peerViewMainPeer(view) as? TelegramChannel { + var destructBlock:[GroupInfoEntry] = [] + + if let channel = peerViewMainPeer(view) as? TelegramChannel { if case .member = channel.participationStatus { - entries.append(GroupInfoEntry.leave(section: sectionId)) + if state.editingState != nil, access.isCreator { + destructBlock.append(GroupInfoEntry.leave(section: GroupInfoSection.destruct.rawValue, text: L10n.peerInfoDeleteGroup, viewType: .singleItem)) + } } } + applyBlock(destructBlock) + + if mediaTabsData.loaded && !mediaTabsData.collections.isEmpty, let controller = arguments.mediaController() { + entries.append(.media(section: GroupInfoSection.media.rawValue, controller: controller, isVisible: state.editingState == nil, viewType: .singleItem)) + } + + var items:[GroupInfoEntry] = [] + var sectionId:Int = 0 + for entry in entries { + if entry.sectionId == GroupInfoSection.media.rawValue { + sectionId = entry.sectionId + } else if entry.sectionId != sectionId { + items.append(.section(sectionId)) + sectionId = entry.sectionId + } + items.append(entry) + } + sectionId += 1 + items.append(.section(sectionId)) + + entries = items + } diff --git a/Telegram-Mac/GroupNameRowItem.swift b/Telegram-Mac/GroupNameRowItem.swift index de6c85b9f2..d1ab07c450 100644 --- a/Telegram-Mac/GroupNameRowItem.swift +++ b/Telegram-Mac/GroupNameRowItem.swift @@ -8,48 +8,126 @@ import Cocoa import TGUIKit -class GroupNameRowItem: GeneralInputRowItem { +import TelegramCore +import SyncCore +import Postbox + +class GroupNameRowItem: InputDataRowItem { + + var photo:String? + fileprivate let pickPicture: ((Bool)->Void)? + fileprivate let account: Account + init(_ initialSize: NSSize, stableId: AnyHashable, account: Account, placeholder: String, photo: String? = nil, viewType: GeneralViewType = .legacy, text:String = "", limit: Int32 = 140, textChangeHandler:@escaping(String)->Void = {_ in}, pickPicture: ((Bool)->Void)? = nil) { + self.photo = photo + self.account = account + self.pickPicture = pickPicture + super.init(initialSize, stableId: stableId, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: placeholder, filter: { $0 }, updated: textChangeHandler, limit: limit) - init(_ initialSize: NSSize, stableId: AnyHashable, placeholder: String, text:String = "", limit: Int32 = 140, textChangeHandler:@escaping(String)->Void = {_ in}) { - super.init(initialSize, stableId: stableId, placeholder: placeholder, limit: limit, textChangeHandler:textChangeHandler) } override func viewClass() -> AnyClass { return GroupNameRowView.self } + override var textFieldLeftInset: CGFloat { + return 60 + } + override var height: CGFloat { - return 80 + switch viewType { + case .legacy: + return max(80, super.height) + case let .modern(_, insets): + return max(insets.bottom + insets.top + 50, super.height) + } } } - - -class GroupNameRowView : GeneralInputRowView { +class GroupNameRowView : InputDataRowView { private let imageView:ImageView = ImageView() - private let sepator:View = View() + private let photoView: TransformImageView = TransformImageView() + private let tranparentView: View = View() + private let circleView = View(frame: NSMakeRect(0, 0, 50, 50)) required init(frame frameRect: NSRect) { super.init(frame: frameRect) - textView.isSingleLine = true - addSubview(sepator) - addSubview(imageView) + containerView.addSubview(photoView) + containerView.addSubview(tranparentView) + containerView.addSubview(imageView) + containerView.addSubview(circleView) + photoView.setFrameSize(50, 50) + tranparentView.setFrameSize(50, 50) + photoView.animatesAlphaOnFirstTransition = true + tranparentView.layer?.cornerRadius = 25 + circleView.layer?.cornerRadius = 25 + circleView.layer?.borderWidth = .borderSize } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) - sepator.backgroundColor = theme.colors.border + + guard let item = item as? GroupNameRowItem else {return} + + photoView.isHidden = item.photo == nil + imageView.isHidden = item.photo != nil + if let path = item.photo, let image = NSImage(contentsOf: URL(fileURLWithPath: path)) { + + let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) + let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + photoView.setSignal(chatMessagePhoto(account: item.account, imageReference: ImageMediaReference.standalone(media: image), scale: backingScaleFactor), clearInstantly: false, animate: true) + + let arguments = TransformImageArguments(corners: ImageCorners(radius: photoView.frame.width / 2), imageSize: photoView.frame.size, boundingSize: photoView.frame.size, intrinsicInsets: NSEdgeInsets()) + photoView.set(arguments: arguments) + + } + circleView.isHidden = item.photo != nil + tranparentView.backgroundColor = NSColor.clear imageView.image = theme.icons.newChatCamera imageView.sizeToFit() } + override func updateColors() { + super.updateColors() + circleView.layer?.borderColor = theme.colors.grayIcon.cgColor + } + + override func mouseUp(with event: NSEvent) { + let point = containerView.convert(event.locationInWindow, from: nil) + if NSPointInRect(point, photoView.frame) { + if let item = item as? GroupNameRowItem { + if item.photo == nil { + item.pickPicture?(true) + } else { + ContextMenu.show(items: [ContextMenuItem(L10n.peerCreatePeerContextUpdatePhoto, handler: { + item.pickPicture?(true) + }), ContextMenuItem(L10n.peerCreatePeerContextRemovePhoto, handler: { + item.pickPicture?(false) + })], view: photoView, event: event) + } + } + } + } + override func layout() { super.layout() - textView.frame = NSMakeRect(100, 0, frame.width - 140 ,textView.frame.height) - textView.centerY() - imageView.setFrameOrigin(30 + floorToScreenPixels((50 - imageView.frame.width)/2.0), 17 + floorToScreenPixels((50 - imageView.frame.height)/2.0)) - sepator.frame = NSMakeRect(105, textView.frame.maxY - .borderSize, frame.width - 140, .borderSize) + + guard let item = item as? GroupNameRowItem else {return} + + switch item.viewType { + case .legacy: + imageView.setFrameOrigin(30 + floorToScreenPixels(backingScaleFactor, (50 - imageView.frame.width)/2.0), 17 + floorToScreenPixels(backingScaleFactor, (50 - imageView.frame.height)/2.0)) + photoView.setFrameOrigin(30 + floorToScreenPixels(backingScaleFactor, (50 - photoView.frame.width)/2.0), 17 + floorToScreenPixels(backingScaleFactor, (50 - photoView.frame.height)/2.0)) + tranparentView.setFrameOrigin(30 + floorToScreenPixels(backingScaleFactor, (50 - tranparentView.frame.width)/2.0), 17 + floorToScreenPixels(backingScaleFactor, (50 - tranparentView.frame.height)/2.0)) + case let .modern(_, insets): + circleView.setFrameOrigin(insets.left, insets.top) + imageView.setFrameOrigin(insets.left + floorToScreenPixels(backingScaleFactor, (50 - imageView.frame.width) / 2), insets.top + floorToScreenPixels(backingScaleFactor, (50 - imageView.frame.height) / 2)) + photoView.setFrameOrigin(insets.left, insets.top) + tranparentView.setFrameOrigin(insets.left, insets.top) + textView.centerY(x: insets.left + item.textFieldLeftInset - 3) + + } + } override func textViewTextDidChange(_ string: String) { @@ -57,17 +135,11 @@ class GroupNameRowView : GeneralInputRowView { } override func textViewHeightChanged(_ height: CGFloat, animated: Bool) { - textView._change(pos: NSMakePoint(100, floorToScreenPixels((frame.height - height)/2.0)), animated: animated) super.textViewHeightChanged(height, animated: animated) - sepator._change(pos: NSMakePoint(105, textView.frame.maxY - .borderSize), animated: animated) } override func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(theme.colors.background.cgColor) - ctx.fill(bounds) - ctx.setStrokeColor(theme.colors.grayIcon.cgColor) - ctx.setLineWidth(1.0) - ctx.strokeEllipse(in: NSMakeRect(30, 17, 50, 50)) + super.draw(layer, in: ctx) } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/GroupStickerSetController.swift b/Telegram-Mac/GroupStickerSetController.swift index c76dbaf4f7..eec2fdc41d 100644 --- a/Telegram-Mac/GroupStickerSetController.swift +++ b/Telegram-Mac/GroupStickerSetController.swift @@ -8,17 +8,18 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private final class GroupStickersArguments { - let account: Account + let context: AccountContext let textChanged:(String, String)->Void let installStickerset:((StickerPackCollectionInfo, [ItemCollectionItem], Int32))->Void let openChat:(PeerId)->Void - init(account: Account, textChanged:@escaping(String, String)->Void, installStickerset:@escaping((StickerPackCollectionInfo, [ItemCollectionItem], Int32))->Void, openChat:@escaping(PeerId)->Void) { - self.account = account + init(context: AccountContext, textChanged:@escaping(String, String)->Void, installStickerset:@escaping((StickerPackCollectionInfo, [ItemCollectionItem], Int32))->Void, openChat:@escaping(PeerId)->Void) { + self.context = context self.textChanged = textChanged self.installStickerset = installStickerset self.openChat = openChat @@ -35,41 +36,6 @@ private enum GroupStickersetEntryId: Hashable { var hashValue: Int { return 0 } - - static func ==(lhs: GroupStickersetEntryId, rhs: GroupStickersetEntryId) -> Bool { - switch lhs { - case let .section(index): - if case .section(index) = rhs { - return true - } else { - return false - } - case let .pack(id): - if case .pack(id) = rhs { - return true - } else { - return false - } - case .status: - if case .status = rhs { - return true - } else { - return false - } - case .input: - if case .input = rhs { - return true - } else { - return false - } - case let .description(index): - if case .description(index) = rhs { - return true - } else { - return false - } - } - } } private enum GroupStickersetLoadingStatus : Equatable { @@ -78,44 +44,22 @@ private enum GroupStickersetLoadingStatus : Equatable { case failed } -private func ==(lhs: GroupStickersetLoadingStatus, rhs: GroupStickersetLoadingStatus) -> Bool { - switch lhs { - case .loading: - if case .loading = rhs { - return true - } else { - return false - } - case .failed: - if case .failed = rhs { - return true - } else { - return false - } - case let .loaded(lhsInfo, lhsPackItem, lhsCount): - if case let .loaded(rhsInfo, rhsPackItem, rhsCount) = rhs { - if lhsInfo != rhsInfo { - return false - } - if lhsPackItem != rhsPackItem { - return false - } - if lhsCount != rhsCount { - return false - } - return true - } else { - return false - } - } -} - private enum GroupStickersetEntry : TableItemListNodeEntry { case section(Int32) - case input(Int32, value: String) - case status(Int32, status:GroupStickersetLoadingStatus) - case description(Int32, Int32, text: String) - case pack(Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool) + case input(Int32, value: String, viewType: GeneralViewType) + case status(Int32, status:GroupStickersetLoadingStatus, viewType: GeneralViewType) + case description(Int32, Int32, text: String, viewType: GeneralViewType) + case pack(Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool, GeneralViewType) + + func withUpdatedViewType(_ viewType: GeneralViewType) -> GroupStickersetEntry { + switch self { + case .section: return self + case let .input(sectionId, value: value, _): return .input(sectionId, value: value, viewType: viewType) + case let .status(sectionId, status: status, _): return .status(sectionId, status: status, viewType: viewType) + case let .description(sectionId, index, text: text, _): return .description(sectionId, index, text: text, viewType: viewType) + case let .pack(sectionId, index, info, item, count, selected, _): return .pack(sectionId, index, info, item, count, selected, viewType) + } + } var stableId: GroupStickersetEntryId { switch self { @@ -125,9 +69,9 @@ private enum GroupStickersetEntry : TableItemListNodeEntry { return .input case .status: return .status - case .description(_, let id, _): + case .description(_, let id, _, _): return .description(id) - case .pack(_, _, let info, _, _, _): + case .pack(_, _, let info, _, _, _, _): return .pack(info.id) } } @@ -138,7 +82,7 @@ private enum GroupStickersetEntry : TableItemListNodeEntry { return 0 case .status: return 1 - case .description(_, let index, _): + case .description(_, let index, _, _): return 2 + index case .pack: fatalError("") @@ -149,13 +93,13 @@ private enum GroupStickersetEntry : TableItemListNodeEntry { var index:Int32 { switch self { - case let .input(sectionId, _): + case let .input(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .status(sectionId, _): + case let .status(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .description(sectionId, _, _): + case let .description(sectionId, _, _, _): return (sectionId * 1000) + stableIndex - case let .pack( sectionId, index, _, _, _, _): + case let .pack( sectionId, index, _, _, _, _, _): return (sectionId * 1000) + 100 + index case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -168,39 +112,40 @@ private enum GroupStickersetEntry : TableItemListNodeEntry { func item(_ arguments: GroupStickersArguments, initialSize: NSSize) -> TableRowItem { switch self { case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case let .input(_, value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: "t.me/addstickers/", text: value, limit: 30, insets: NSEdgeInsets(left: 30, right: 30, top: 2, bottom: 3), textChangeHandler: { updatedText in - arguments.textChanged(value, updatedText) - }, textFilter: { text -> String in + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .input(_, value, viewType): + return InputDataRowItem(initialSize, stableId: stableId, mode: .plain, error: nil, viewType: viewType, currentText: value, placeholder: nil, inputPlaceholder: "https://t.me/addstickers/", defaultText: "https://t.me/addstickers/", rightItem: .action(theme.icons.recentDismiss, .clearText), filter: { text in var filter = NSCharacterSet.alphanumerics filter.insert(charactersIn: "_") return text.trimmingCharacters(in: filter.inverted) - }, holdText:true, pasteFilter: { value in + }, updated: { updatedText in + arguments.textChanged(value, updatedText) + }, pasteFilter: { value in if let index = value.range(of: "t.me/addstickers/") { - return (true, value.substring(from: index.upperBound)) + return (true, String(value[index.upperBound...])) } return (false, value) - }, canFastClean: true) - case let .description(_, _, text): + }, limit: 25 + 30) + + case let .description(_, _, text, viewType): let attr = NSMutableAttributedString() _ = attr.append(string: text, color: theme.colors.grayText, font: .normal(.text)) - attr.detectLinks(type: [.Mentions, .Hashtags], account: arguments.account, color: theme.colors.link, openInfo: { peerId, _, _, _ in + attr.detectLinks(type: [.Mentions, .Hashtags], context: arguments.context, color: theme.colors.link, openInfo: { peerId, _, _, _ in arguments.openChat(peerId) }) - return GeneralTextRowItem(initialSize, stableId: stableId, text: attr) - case let .status(_, status): + return GeneralTextRowItem(initialSize, stableId: stableId, text: attr, viewType: viewType) + case let .status(_, status, viewType): switch status { case let .loaded(info, topItem, count): - return StickerSetTableRowItem(initialSize, account: arguments.account, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: .empty, action: {}) + return StickerSetTableRowItem(initialSize, context: arguments.context, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: .empty, viewType: viewType, action: {}) case .loading: - return LoadingTableItem(initialSize, height: 50, stableId: stableId) + return LoadingTableItem(initialSize, height: 50, stableId: stableId, viewType: viewType) case .failed: - return EmptyGroupstickerSearchRowItem(initialSize, height: 50, stableId: stableId) + return EmptyGroupstickerSearchRowItem(initialSize, height: 50, stableId: stableId, viewType: viewType) } - case let .pack(_, _, info, topItem, count, selected): - return StickerSetTableRowItem(initialSize, account: arguments.account, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: selected ? .selected : .empty, action: { + case let .pack(_, _, info, topItem, count, selected, viewType): + return StickerSetTableRowItem(initialSize, context: arguments.context, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: ItemListStickerPackItemEditing(editable: false, editing: false), enabled: true, control: selected ? .selected : .empty, viewType: viewType, action: { if let topItem = topItem { arguments.installStickerset((info, [topItem], count)) } @@ -210,62 +155,6 @@ private enum GroupStickersetEntry : TableItemListNodeEntry { } -private func ==(lhs: GroupStickersetEntry, rhs: GroupStickersetEntry) -> Bool { - switch lhs { - case .section(let id): - if case .section(id) = rhs { - return true - } else { - return false - } - case let .input(index, text): - if case .input(index, text) = rhs { - return true - } else { - return false - } - case let .status(index, status): - if case .status(index, status: status) = rhs { - return true - } else { - return false - } - - case let .description(section, id, text): - if case .description(section, id, text: text) = rhs { - return true - } else { - return false - } - case let .pack(lhsSectionId, lhsIndex, lhsInfo, lhsTopItem, lhsCount, lhsSelected): - if case let .pack(rhsSectionId, rhsIndex, rhsInfo, rhsTopItem, rhsCount, rhsSelected) = rhs { - - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if lhsInfo != rhsInfo { - return false - } - if lhsTopItem != rhsTopItem { - return false - } - if lhsCount != rhsCount { - return false - } - if lhsSelected != rhsSelected { - return false - } - return true - } else { - return false - } - } - -} - private func groupStickersEntries(state: GroupStickerSetControllerState, view: CombinedView, peerId: PeerId, specificPack: (StickerPackCollectionInfo, [ItemCollectionItem])?) -> [GroupStickersetEntry] { var entries: [GroupStickersetEntry] = [] @@ -287,25 +176,35 @@ private func groupStickersEntries(state: GroupStickerSetControllerState, view: C } } - entries.append(.input(sectionId, value: value)) + var inputBlock: [GroupStickersetEntry] = [] + func applyBlock(_ block:[GroupStickersetEntry]) { + var block = block + for (i, item) in block.enumerated() { + block[i] = item.withUpdatedViewType(bestGeneralViewType(block, for: i)) + } + entries.append(contentsOf: block) + } + inputBlock.append(.input(sectionId, value: value, viewType: .singleItem)) if state.loading { - entries.append(.status(sectionId, status: .loading)) + inputBlock.append(.status(sectionId, status: .loading, viewType: .singleItem)) } else { if state.failed { - entries.append(.status(sectionId, status: .failed)) + inputBlock.append(.status(sectionId, status: .failed, viewType: .singleItem)) } else if let loadedPack = state.loadedPack { - entries.append(.status(sectionId, status: .loaded(loadedPack.0, loadedPack.1.first as? StickerPackItem, loadedPack.2))) + inputBlock.append(.status(sectionId, status: .loaded(loadedPack.0, loadedPack.1.first as? StickerPackItem, loadedPack.2), viewType: .singleItem)) } else { if let specificPack = specificPack, !value.isEmpty { - entries.append(.status(sectionId, status: .loaded(specificPack.0, specificPack.1.first as? StickerPackItem, Int32(specificPack.1.count)))) + inputBlock.append(.status(sectionId, status: .loaded(specificPack.0, specificPack.1.first as? StickerPackItem, Int32(specificPack.1.count)), viewType: .singleItem)) } } } - entries.append(.description(sectionId, descriptionId, text: tr(.groupStickersCreateDescription))) + applyBlock(inputBlock) + + entries.append(.description(sectionId, descriptionId, text: L10n.groupStickersCreateDescription, viewType: .textBottomItem)) descriptionId += 1 @@ -314,12 +213,12 @@ private func groupStickersEntries(state: GroupStickerSetControllerState, view: C - entries.append(.description(sectionId, descriptionId, text: tr(.groupStickersChooseHeader))) + entries.append(.description(sectionId, descriptionId, text: L10n.groupStickersChooseHeader, viewType: .textTopItem)) descriptionId += 1 if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { var index: Int32 = 0 - for entry in packsEntries { + for (i, entry) in packsEntries.enumerated() { if let info = entry.info as? StickerPackCollectionInfo { var selected: Bool if let loadedPack = state.loadedPack { @@ -327,7 +226,7 @@ private func groupStickersEntries(state: GroupStickerSetControllerState, view: C } else { selected = info == specificPack?.0 } - entries.append(.pack(sectionId, index, info, entry.firstItem as? StickerPackItem, info.count == 0 ? entry.count : info.count, selected)) + entries.append(.pack(sectionId, index, info, entry.firstItem as? StickerPackItem, info.count == 0 ? entry.count : info.count, selected, bestGeneralViewType(packsEntries, for: i))) index += 1 } } @@ -379,16 +278,37 @@ private struct GroupStickerSetControllerState: Equatable { class GroupStickerSetController: TableViewController { private let peerId: PeerId + private let disposable = MetaDisposable() private var saveGroupStickerSet:(()->Void)? = nil - init( account: Account, peerId:PeerId) { + init(_ context: AccountContext, peerId:PeerId) { self.peerId = peerId - super.init(account) + super.init(context) + } + + deinit { + disposable.dispose() + } + + override func becomeFirstResponder() -> Bool? { + return true + } + + override func firstResponder() -> NSResponder? { + var responder: NSResponder? + genericView.enumerateViews { view -> Bool in + if responder == nil, let firstResponder = view.firstResponder { + responder = firstResponder + return false + } + return true + } + return responder } override func viewDidLoad() { super.viewDidLoad() - let account = self.account + let context = self.context let peerId = self.peerId let statePromise = ValuePromise(GroupStickerSetControllerState(), ignoreRepeated: true) @@ -404,12 +324,12 @@ class GroupStickerSetController: TableViewController { let resolveDisposable = MetaDisposable() actionsDisposable.add(resolveDisposable) - let arguments = GroupStickersArguments(account: account, textChanged: { current, updated in + let arguments = GroupStickersArguments(context: context, textChanged: { current, updated in updateState({$0.withUpdatedLoadedPack(nil).withUpdatedFailed(false).withUpdatedText(updated)}) if updated.isEmpty { resolveDisposable.set(nil) } else { - resolveDisposable.set((loadedStickerPack(postbox: account.postbox, network: account.network, reference: .name(updated)) |> deliverOnMainQueue).start(next: { result in + resolveDisposable.set((loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .name(updated), forceActualized: false) |> deliverOnMainQueue).start(next: { result in switch result { case .fetching: updateState({$0.withUpdatedLoadedPack(nil).withUpdatedLoading(true)}) @@ -423,14 +343,14 @@ class GroupStickerSetController: TableViewController { }, installStickerset: { info in updateState({$0.withUpdatedLoadedPack(info).withUpdatedText(info.0.shortName)}) }, openChat: { [weak self] peerId in - self?.navigationController?.push(ChatController(account: account, peerId: peerId)) + self?.navigationController?.push(ChatController(context: context, chatLocation: .peer(peerId))) }) saveGroupStickerSet = { [weak self] in if let strongSelf = self { - actionsDisposable.add(showModalProgress(signal: updateGroupSpecificStickerset(postbox: account.postbox, network: account.network, peerId: peerId, info: stateValue.modify{$0}.loadedPack?.0), for: mainWindow).start(next: { [weak strongSelf] in + actionsDisposable.add(showModalProgress(signal: updateGroupSpecificStickerset(postbox: context.account.postbox, network: context.account.network, peerId: peerId, info: stateValue.modify{$0}.loadedPack?.0), for: mainWindow).start(next: { [weak strongSelf] _ in strongSelf?.navigationController?.back() - }, error: { [weak strongSelf] in + }, error: { [weak strongSelf] _ in strongSelf?.navigationController?.back() })) self?.doneButton?.isEnabled = false @@ -438,21 +358,28 @@ class GroupStickerSetController: TableViewController { } let stickerPacks = Promise() - stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) let featured = Promise<[FeaturedStickerPackItem]>() - featured.set(account.viewTracker.featuredStickerPacks()) + featured.set(context.account.viewTracker.featuredStickerPacks()) let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = self.atomicSize - genericView.merge(with: combineLatest(statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, peerSpecificStickerPack(postbox: account.postbox, network: account.network, peerId: peerId) |> deliverOnMainQueue, appearanceSignal) + + let signal = combineLatest(queue: prepareQueue,statePromise.get(), stickerPacks.get(), peerSpecificStickerPack(postbox: context.account.postbox, network: context.account.network, peerId: peerId), appearanceSignal) |> map { state, view, specificPack, appearance -> TableUpdateTransition in - let entries = groupStickersEntries(state: state, view: view, peerId: peerId, specificPack: specificPack).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let entries = groupStickersEntries(state: state, view: view, peerId: peerId, specificPack: specificPack.packInfo).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) } |> afterDisposed { actionsDisposable.dispose() - } ) - readyOnce() + } |> deliverOnMainQueue + + self.disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) + + actionsDisposable.add((statePromise.get() |> deliverOnMainQueue).start(next: { [weak self] state in var enabled = !state.failed @@ -467,17 +394,14 @@ class GroupStickerSetController: TableViewController { })) } - var doneButton:Button? { - if let button = rightBarView as? TextButtonBarView { - return button.button - } - return nil + var doneButton:Control? { + return rightBarView } override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.navigationDone)) + let button = TextButtonBarView(controller: self, text: tr(L10n.navigationDone)) - button.button.set(handler: { [weak self] _ in + button.set(handler: { [weak self] _ in self?.saveGroupStickerSet?() }, for: .Click) diff --git a/Telegram-Mac/GroupedLayout.swift b/Telegram-Mac/GroupedLayout.swift new file mode 100644 index 0000000000..59e46a73e9 --- /dev/null +++ b/Telegram-Mac/GroupedLayout.swift @@ -0,0 +1,456 @@ +// +// GroupedLayout.swift +// Telegram +// +// Created by keepcoder on 31/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import Postbox + +private final class MessagePhotoInfo { + let mid:MessageId + let imageSize: NSSize + let aspectRatio: CGFloat + fileprivate(set) var layoutFrame: NSRect = NSZeroRect + fileprivate(set) var positionFlags: LayoutPositionFlags = .none + + init(_ message: Message) { + self.mid = message.id + + self.imageSize = ChatLayoutUtils.contentSize(for: message.media[0], with: 320) + self.aspectRatio = self.imageSize.width / self.imageSize.height + +// if let image = message.media.first as? TelegramMediaImage { +// if let representation = image.representationForDisplayAtSize(NSMakeSize(320, 320)) { +// self.imageSize = representation.dimensions +// } else if let represenatation = imageRepresentationLargerThan(image.representations, size: NSMakeSize(320, 320)) { +// self.imageSize = represenatation.dimensions +// self.aspectRatio = self.imageSize.width / self.imageSize.height +// } else { +// = NSZeroSize +// self.aspectRatio = 1.0 +// } +// } else { +// self.imageSize = NSZeroSize +// self.aspectRatio = 1.0 +// } + } +} + +private final class GroupedLayoutAttempt { + let lineCounts:[Int] + let heights:[CGFloat] + init(lineCounts:[Int], heights: [CGFloat]) { + self.lineCounts = lineCounts + self.heights = heights + } +} + + + +class GroupedLayout { + + private(set) var dimensions: NSSize = NSZeroSize + private var layouts:[MessageId: MessagePhotoInfo] = [:] + private(set) var messages:[Message] + + init(_ messages: [Message]) { + self.messages = messages + } + + func contentNode(for index: Int) -> ChatMediaContentView.Type { + return ChatLayoutUtils.contentNode(for: messages[index].media[0]) + } + + var count: Int { + return messages.count + } + + func message(at point: NSPoint) -> Message? { + for i in 0 ..< messages.count { + if NSPointInRect(point, frame(at: i)) { + return messages[i] + } + } + return nil + } + + func measure(_ maxSize: NSSize, spacing: CGFloat = 4.0) { + + var photos: [MessagePhotoInfo] = [] + + if messages.count == 1 { + let photo = MessagePhotoInfo(messages[0]) + photos.append(photo) + photos[0].layoutFrame = NSMakeRect(0, 0, photos[0].imageSize.width, photos[0].imageSize.height) + photos[0].positionFlags = .none + } else { + var proportions: String = "" + var averageAspectRatio: CGFloat = 1.0 + var forceCalc: Bool = false + for message in messages { + let photo = MessagePhotoInfo(message) + photos.append(photo) + + if photo.aspectRatio > 1.2 { + proportions += "w" + } else if photo.aspectRatio < 0.8 { + proportions += "n" + } else { + proportions += "q" + } + averageAspectRatio += photo.aspectRatio + + if photo.aspectRatio > 2.0 { + forceCalc = true + } + } + + let minWidth: CGFloat = 70 + let maxAspectRatio = maxSize.width / maxSize.height + if (photos.count > 0) { + averageAspectRatio = averageAspectRatio / CGFloat(photos.count) + } + + if !forceCalc { + if photos.count == 2 { + if proportions == "ww" && averageAspectRatio > 1.4 * maxAspectRatio && photos[1].aspectRatio - photos[0].aspectRatio < 0.2 { + let width: CGFloat = maxSize.width + let height:CGFloat = min(width / photos[0].aspectRatio, min(width / photos[1].aspectRatio, (maxSize.height - spacing) / 2.0)) + + photos[0].layoutFrame = NSMakeRect(0.0, 0.0, width, height) + photos[0].positionFlags = [.top, .left, .right] + + photos[1].layoutFrame = NSMakeRect(0.0, height + spacing, width, height) + photos[1].positionFlags = [.bottom, .left, .right] + } else if proportions == "ww" || proportions == "qq" { + let width: CGFloat = (maxSize.width - spacing) / 2.0 + let height: CGFloat = min(width / photos[0].aspectRatio, min(width / photos[1].aspectRatio, maxSize.height)) + + photos[0].layoutFrame = NSMakeRect(0.0, 0.0, width, height) + photos[0].positionFlags = [.top, .left, .bottom] + + photos[1].layoutFrame = NSMakeRect(width + spacing, 0.0, width, height) + photos[1].positionFlags = [.top, .right, .bottom] + } else { + let firstWidth: CGFloat = (maxSize.width - spacing) / photos[1].aspectRatio / (1.0 / photos[0].aspectRatio + 1.0 / photos[1].aspectRatio) + let secondWidth: CGFloat = maxSize.width - firstWidth - spacing + let height: CGFloat = min(maxSize.height, min(firstWidth / photos[0].aspectRatio, secondWidth / photos[1].aspectRatio)) + + photos[0].layoutFrame = NSMakeRect(0.0, 0.0, firstWidth, height) + photos[0].positionFlags = [.top, .left, .bottom] + + photos[1].layoutFrame = NSMakeRect(firstWidth + spacing, 0.0, secondWidth, height) + photos[1].positionFlags = [.top, .right, .bottom] + } + } else if photos.count == 3 { + if proportions.hasPrefix("n") { + let firstHeight = maxSize.height + + let thirdHeight = min((maxSize.height - spacing) * 0.5, round(photos[1].aspectRatio * (maxSize.width - spacing) / (photos[2].aspectRatio + photos[1].aspectRatio))) + let secondHeight = maxSize.height - thirdHeight - spacing + let rightWidth = max(minWidth, min((maxSize.width - spacing) * 0.5, round(min(thirdHeight * photos[2].aspectRatio, secondHeight * photos[1].aspectRatio)))) + + let leftWidth = round(min(firstHeight * photos[0].aspectRatio, (maxSize.width - spacing - rightWidth))) + photos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: leftWidth, height: firstHeight) + photos[0].positionFlags = [.top, .left, .bottom] + + photos[1].layoutFrame = CGRect(x: leftWidth + spacing, y: 0.0, width: rightWidth, height: secondHeight) + photos[1].positionFlags = [.right, .top] + + photos[2].layoutFrame = CGRect(x: leftWidth + spacing, y: secondHeight + spacing, width: rightWidth, height: thirdHeight) + photos[2].positionFlags = [.right, .bottom] + } else { + var width = maxSize.width + let firstHeight = floor(min(width / photos[0].aspectRatio, (maxSize.height - spacing) * 0.66)) + photos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: width, height: firstHeight) + photos[0].positionFlags = [.top, .left, .right] + + width = (maxSize.width - spacing) / 2.0 + let secondHeight = min(maxSize.height - firstHeight - spacing, round(min(width / photos[1].aspectRatio, width / photos[2].aspectRatio))) + photos[1].layoutFrame = CGRect(x: 0.0, y: firstHeight + spacing, width: width, height: secondHeight) + photos[1].positionFlags = [.left, .bottom] + + photos[2].layoutFrame = CGRect(x: width + spacing, y: firstHeight + spacing, width: width, height: secondHeight) + photos[2].positionFlags = [.right, .bottom] + } + + } else if photos.count == 4 { + if proportions == "www" || proportions.hasPrefix("w") { + let w: CGFloat = maxSize.width + let h0: CGFloat = min(w / photos[0].aspectRatio, (maxSize.height - spacing) * 0.66) + photos[0].layoutFrame = NSMakeRect(0.0, 0.0, w, h0) + photos[0].positionFlags = [.top, .left, .right] + + var h: CGFloat = (maxSize.width - 2 * spacing) / (photos[1].aspectRatio + photos[2].aspectRatio + photos[3].aspectRatio) + let w0: CGFloat = max((maxSize.width - 2 * spacing) * 0.33, h * photos[1].aspectRatio) + var w2: CGFloat = max((maxSize.width - 2 * spacing) * 0.33, h * photos[3].aspectRatio) + var w1: CGFloat = w - w0 - w2 - 2 * spacing + + if w1 < minWidth { + w2 -= minWidth - w1 + w1 = minWidth + } + + h = min(maxSize.height - h0 - spacing, h) + photos[1].layoutFrame = NSMakeRect(0.0, h0 + spacing, w0, h) + photos[1].positionFlags = [.left, .bottom] + + photos[2].layoutFrame = NSMakeRect(w0 + spacing, h0 + spacing, w1, h) + photos[2].positionFlags = [.bottom] + + photos[3].layoutFrame = NSMakeRect(w0 + w1 + 2 * spacing, h0 + spacing, w2, h) + photos[3].positionFlags = [.right, .bottom] + } else { + let h: CGFloat = maxSize.height + let w0: CGFloat = min(h * photos[0].aspectRatio, (maxSize.width - spacing) * 0.6) + photos[0].layoutFrame = NSMakeRect(0.0, 0.0, w0, h) + photos[0].positionFlags = [.top, .left, .bottom] + + var w: CGFloat = (maxSize.height - 2 * spacing) / (1.0 / photos[1].aspectRatio + 1.0 / photos[2].aspectRatio + 1.0 / photos[3].aspectRatio) + let h0: CGFloat = w / photos[1].aspectRatio + let h1: CGFloat = w / photos[2].aspectRatio + let h2: CGFloat = w / photos[3].aspectRatio + w = min(maxSize.width - w0 - spacing, w) + photos[1].layoutFrame = NSMakeRect(w0 + spacing, 0.0, w, h0) + photos[1].positionFlags = [.right, .top] + + photos[2].layoutFrame = NSMakeRect(w0 + spacing, h0 + spacing, w, h1) + photos[2].positionFlags = [.right] + + photos[3].layoutFrame = NSMakeRect(w0 + spacing, h0 + h1 + 2 * spacing, w, h2) + photos[3].positionFlags = [.right, .bottom] + } + } + } + + if forceCalc || photos.count >= 5 { + var croppedRatios:[CGFloat] = [] + for photo in photos { + if averageAspectRatio > 1.1 { + croppedRatios.append(max(1.0, photo.aspectRatio)) + } else { + croppedRatios.append(min(1.0, photo.aspectRatio)) + } + } + + func multiHeight(_ ratios: [CGFloat]) -> CGFloat { + var ratioSum: CGFloat = 0 + for ratio in ratios { + ratioSum += ratio + } + return (maxSize.width - (CGFloat(ratios.count) - 1) * spacing) / ratioSum + } + + var attempts: [GroupedLayoutAttempt] = [] + + func addAttempt(_ lineCounts:[Int], _ heights:[CGFloat]) { + attempts.append(GroupedLayoutAttempt(lineCounts: lineCounts, heights: heights)) + } + + + addAttempt([croppedRatios.count], [multiHeight(croppedRatios)]) + + + var secondLine:Int = 0 + var thirdLine:Int = 0 + var fourthLine:Int = 0 + + for firstLine in 1 ..< croppedRatios.count { + secondLine = croppedRatios.count - firstLine + if firstLine > 3 || secondLine > 3 { + continue + } + + addAttempt([firstLine, croppedRatios.count - firstLine], [multiHeight(croppedRatios.subarray(with: NSMakeRange(0, firstLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine, croppedRatios.count - firstLine)))]) + } + + for firstLine in 1 ..< croppedRatios.count - 1 { + for secondLine in 1.. 3 || secondLine > (averageAspectRatio < 0.85 ? 4 : 3) || thirdLine > 3 { + continue + } + addAttempt([firstLine, secondLine, thirdLine], [multiHeight(croppedRatios.subarray(with: NSMakeRange(0, firstLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine, croppedRatios.count - firstLine - thirdLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine + secondLine, croppedRatios.count - firstLine - secondLine)))]) + + } + } + if croppedRatios.count > 2 { + for firstLine in 1 ..< croppedRatios.count - 2 { + for secondLine in 1 ..< croppedRatios.count - firstLine { + for thirdLine in 1 ..< croppedRatios.count - firstLine - secondLine { + fourthLine = croppedRatios.count - firstLine - secondLine - thirdLine; + if firstLine > 3 || secondLine > 3 || thirdLine > 3 || fourthLine > 3 { + continue + } + + addAttempt([firstLine, secondLine, thirdLine, fourthLine], [multiHeight(croppedRatios.subarray(with: NSMakeRange(0, firstLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine, croppedRatios.count - firstLine - thirdLine - fourthLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine + secondLine, croppedRatios.count - firstLine - secondLine - fourthLine))), multiHeight(croppedRatios.subarray(with: NSMakeRange(firstLine + secondLine + thirdLine, croppedRatios.count - firstLine - secondLine - thirdLine)))]) + } + } + } + } + + + let maxHeight: CGFloat = maxSize.height / 3 * 4 + var optimal: GroupedLayoutAttempt? = nil + var optimalDiff: CGFloat = 0.0 + for attempt in attempts { + var totalHeight: CGFloat = spacing * (CGFloat(attempt.heights.count) - 1); + var minLineHeight: CGFloat = .greatestFiniteMagnitude; + var maxLineHeight: CGFloat = 0.0 + for lineHeight in attempt.heights { + totalHeight += lineHeight + if lineHeight < minLineHeight { + minLineHeight = lineHeight + } + if lineHeight > maxLineHeight { + maxLineHeight = lineHeight; + } + } + + var diff: CGFloat = fabs(totalHeight - maxHeight); + + if (attempt.lineCounts.count > 1) { + if (attempt.lineCounts[0] > attempt.lineCounts[1]) + || (attempt.lineCounts.count > 2 && attempt.lineCounts[1] > attempt.lineCounts[2]) + || (attempt.lineCounts.count > 3 && attempt.lineCounts[2] > attempt.lineCounts[3]) { + diff *= 1.5 + } + } + + if minLineHeight < minWidth { + diff *= 1.5 + } + + if (optimal == nil || diff < optimalDiff) + { + optimal = attempt; + optimalDiff = diff; + } + } + + var index: Int = 0 + var y: CGFloat = 0.0 + if let optimal = optimal { + for i in 0 ..< optimal.lineCounts.count { + let count: Int = optimal.lineCounts[i] + let lineHeight: CGFloat = optimal.heights[i] + var x: CGFloat = 0.0 + + var positionFlags: LayoutPositionFlags = [.none] + if i == 0 { + positionFlags.insert(.top) + } + if i == optimal.lineCounts.count - 1 { + positionFlags.insert(.bottom) + } + + for k in 0 ..< count + { + var innerPositionFlags:LayoutPositionFlags = positionFlags; + + if k == 0 { + innerPositionFlags.insert(.left); + } + if k == count - 1 { + innerPositionFlags.insert(.right) + } + + if positionFlags == .none { + innerPositionFlags.insert(.inside) + } + + let ratio: CGFloat = croppedRatios[index]; + let width: CGFloat = ratio * lineHeight; + photos[index].layoutFrame = NSMakeRect(x, y, width, lineHeight); + photos[index].positionFlags = innerPositionFlags; + + x += width + spacing; + index += 1 + } + + y += lineHeight + spacing; + } + } + } + } + + + var dimensions: CGSize = NSZeroSize + var layouts: [MessageId: MessagePhotoInfo] = [:] + for photo in photos { + layouts[photo.mid] = photo + + if photo.layoutFrame.maxX > dimensions.width { + dimensions.width = photo.layoutFrame.maxX + } + if photo.layoutFrame.maxY > dimensions.height { + dimensions.height = photo.layoutFrame.maxY + } + } + + + for (_, layout) in layouts { + layout.layoutFrame = NSMakeRect(floorToScreenPixels(System.backingScale, layout.layoutFrame.minX), floorToScreenPixels(System.backingScale, layout.layoutFrame.minY), floorToScreenPixels(System.backingScale, layout.layoutFrame.width), floorToScreenPixels(System.backingScale, layout.layoutFrame.height)) + } + + self.layouts = layouts + self.dimensions = NSMakeSize(floorToScreenPixels(System.backingScale, dimensions.width), floorToScreenPixels(System.backingScale, dimensions.height)) + } + + func frame(for messageId: MessageId) -> NSRect { + guard let photo = layouts[messageId] else { + return NSZeroRect + } + return photo.layoutFrame + } + + func frame(at index: Int) -> NSRect { + return frame(for: messages[index].id) + } + + func position(for messageId: MessageId) -> LayoutPositionFlags { + guard let photo = layouts[messageId] else { + return .none + } + return photo.positionFlags + } + + + func moveItemIfNeeded(at index:Int, point: NSPoint) -> Int? { + + for i in 0 ..< count { + let frame = self.frame(at: i) + if NSPointInRect(point, frame) && i != index { + let current = messages[index] + messages.remove(at: index) + messages.insert(current, at: i) + return i + } + } + + return nil + } + + func isNeedMoveItem(at index:Int, point: NSPoint) -> Bool { + + for i in 0 ..< count { + let frame = self.frame(at: i) + if NSPointInRect(point, frame) && i != index { + return true + } + } + + return false + } + + func position(at index: Int) -> LayoutPositionFlags { + return position(for: messages[index].id) + } + +} diff --git a/Telegram-Mac/GroupsInCommonViewController.swift b/Telegram-Mac/GroupsInCommonViewController.swift index 42b0d4617f..96f6f06f84 100644 --- a/Telegram-Mac/GroupsInCommonViewController.swift +++ b/Telegram-Mac/GroupsInCommonViewController.swift @@ -8,148 +8,109 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit final class GroupsInCommonArguments { - let account:Account + let context: AccountContext let open:(PeerId)->Void - init(account: Account, open: @escaping(PeerId) -> Void) { + init(context: AccountContext, open: @escaping(PeerId) -> Void) { self.open = open - self.account = account + self.context = context } } -private enum GroupsInCommonEntry : Comparable, Identifiable { - case empty(Bool) - case peer(Int, Peer) - case section - - var stableId: AnyHashable { - switch self { - case .empty: - return -1 - case .section: - return 0 - case let .peer(_, peer): - return peer.id.hashValue - } - } + +private func _id_peer_id(_ id: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_peer_id_\(id)") +} + +private func commonGroupsEntries(state: GroupsInCommonState, arguments: GroupsInCommonArguments) -> [InputDataEntry] { - var index:Int { - switch self { - case .empty: - return -1 - case .section: - return 0 - case let .peer(index, _): - return index + 10 - } - } - func item(arguments: GroupsInCommonArguments, initialSize:NSSize) -> TableRowItem { - switch self { - case let .empty(loading): - return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: loading, text: tr(.groupsInCommonEmpty)) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId, type: .none) - case let .peer(_, peer): - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, inset:NSEdgeInsets(left:30.0,right:30.0), action: { + var entries:[InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + + let peers = state.peers.compactMap { $0.chatMainPeer } + + for (i, peer) in peers.enumerated() { + var viewType: GeneralViewType = bestGeneralViewType(peers, for: i) + if i == 0 { + if peers.count == 1 { + viewType = .lastItem + } else { + viewType = .innerItem + } + } + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_peer_id(peer.id), equatable: InputDataEquatable(PeerEquatable(peer)), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 46, photoSize: NSMakeSize(32, 32), inset: NSEdgeInsetsZero, viewType: viewType, action: { arguments.open(peer.id) }) - } + })) + index += 1 } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries + } -private func ==(lhs:GroupsInCommonEntry, rhs: GroupsInCommonEntry) -> Bool { - switch lhs { - case let .empty(loading): - if case .empty(loading) = rhs { - return true - } else { - return false - } - case .section: - if case .section = rhs { - return true - } else { - return false - } - case let .peer(lhsIndex, lhsPeer): - if case let .peer(rhsIndex, rhsPeer) = rhs { - return lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) - } else { - return false - } - } -} +func GroupsInCommonViewController(context: AccountContext, peerId: PeerId) -> ViewController { + -private func groupsInCommonEntries(_ peers:[Peer], loading:Bool) -> [GroupsInCommonEntry] { - if peers.isEmpty { - return [.empty(loading)] - } else { - var entries:[GroupsInCommonEntry] = [] - entries.append(.section) - var index:Int = 0 - for peer in peers { - entries.append(.peer(index, peer)) - index += 1 - } - return entries + let actionsDisposable = DisposableSet() + + let arguments = GroupsInCommonArguments(context: context, open: { peerId in + context.sharedContext.bindings.rootNavigation().push(ChatAdditionController(context: context, chatLocation: .peer(peerId))) + }) + + let contextValue: Promise = Promise() + let peerId = context.account.postbox.peerView(id: peerId) |> take(1) |> map { view in + return peerViewMainPeer(view)?.id ?? peerId } -} - -private func prepareTransition(left:[AppearanceWrapperEntry], right:[AppearanceWrapperEntry], arguments: GroupsInCommonArguments, initialSize: NSSize) -> TableUpdateTransition { - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in - return entry.entry.item(arguments: arguments, initialSize: initialSize) + contextValue.set(peerId |> map { + GroupsInCommonContext(account: context.account, peerId: $0) }) + let state = contextValue.get() |> mapToSignal { + $0.state + } + let dataSignal = state |> map { + return InputDataSignalValue(entries: commonGroupsEntries(state: $0, arguments: arguments)) + } - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -private func <(lhs:GroupsInCommonEntry, rhs: GroupsInCommonEntry) -> Bool { - return lhs.index < rhs.index -} - -class GroupsInCommonViewController: TableViewController { - private let peerId:PeerId - init(account:Account, peerId:PeerId) { - self.peerId = peerId - super.init(account) + let controller = InputDataController(dataSignal: dataSignal, title: "") + controller.bar = .init(height: 0) + + controller.contextOject = contextValue + + controller.onDeinit = { + actionsDisposable.dispose() } - override func viewDidLoad() { - super.viewDidLoad() - let account = self.account - - let arguments = GroupsInCommonArguments(account: account, open: { [weak self] peerId in - if let strongSelf = self { - strongSelf.navigationController?.push(ChatController(account: strongSelf.account, peerId: peerId)) - } - }) - - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = atomicSize - let signal = combineLatest(Signal<([Peer], Bool), Void>.single(([], true)), appearanceSignal |> take(1)) |> then(combineLatest(groupsInCommon(account: account, peerId: peerId) |> mapToSignal { peerIds -> Signal<([Peer], Bool), Void> in - return account.postbox.modify { modififer -> ([Peer], Bool) in - var peers:[Peer] = [] - for peerId in peerIds { - if let peer = modififer.getPeer(peerId) { - peers.append(peer) - } - } - return (peers, false) + controller.getBackgroundColor = { + theme.colors.listBackground + } + + controller.didLoaded = { controller, _ in + controller.tableView.setScrollHandler { position in + switch position.direction { + case .bottom: + _ = contextValue.get().start(next: { ctx in + ctx.loadMore() + }) + //commonContext.loadMore() + default: + break } - }, appearanceSignal)) |> map { result -> TableUpdateTransition in - let entries = groupsInCommonEntries(result.0.0, loading: result.0.1).map {AppearanceWrapperEntry(entry: $0, appearance: result.1)} - - return prepareTransition(left: previous.swap(entries), right: entries, arguments: arguments, initialSize: initialSize.modify {$0} ) - } |> deliverOnMainQueue - - self.genericView.merge(with: signal) - - readyOnce() + } } + return controller } diff --git a/Telegram-Mac/GroupsStatsController.swift b/Telegram-Mac/GroupsStatsController.swift new file mode 100644 index 0000000000..d1ff2087c6 --- /dev/null +++ b/Telegram-Mac/GroupsStatsController.swift @@ -0,0 +1,447 @@ +// +// GroupsStatsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import Postbox +import SyncCore +import GraphCore + +private func statsEntries(_ state: GroupStatsContextState, uiState: UIStatsState, peers: [PeerId : Peer]?, updateIsLoading: @escaping(InputDataIdentifier, Bool)->Void, revealSection: @escaping(UIStatsState.RevealSection)->Void, context: GroupStatsContext, accountContext: AccountContext, openPeerInfo: @escaping(PeerId)->Void, detailedDisposable: DisposableDict) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + + if state.stats == nil { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("loading"), equatable: nil, item: { initialSize, stableId in + return StatisticsLoadingRowItem(initialSize, stableId: stableId, context: accountContext, text: L10n.channelStatsLoading) + })) + } else if let stats = state.stats { + + let dates = "\(dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(stats.period.minDate)))) – \(dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(stats.period.maxDate))))" + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.statsGroupOverview), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem, rightItem: InputDataGeneralTextRightData(isLoading: false, text: .initialize(string: dates, color: theme.colors.listGrayText, font: .normal(12)))))) + index += 1 + + var overviewItems:[ChannelOverviewItem] = [] + + if stats.members.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.statsGroupMembers, value: stats.members.attributedString)) + } + if stats.messages.current != 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.statsGroupMessages, value: stats.messages.attributedString)) + } + if stats.viewers.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.statsGroupViewers, value: stats.viewers.attributedString)) + } + if stats.posters.current > 0 { + overviewItems.append(ChannelOverviewItem(title: L10n.statsGroupPosters, value: stats.posters.attributedString)) + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("overview"), equatable: InputDataEquatable(overviewItems), item: { initialSize, stableId in + return ChannelOverviewStatsRowItem(initialSize, stableId: stableId, items: overviewItems, viewType: .singleItem) + })) + index += 1 + + + struct Graph { + let graph: StatsGraph + let title: String + let identifier: InputDataIdentifier + let type: ChartItemType + let load:(InputDataIdentifier)->Void + } + + var graphs: [Graph] = [] + + if !stats.growthGraph.isEmpty { + graphs.append(Graph(graph: stats.growthGraph, title: L10n.statsGroupGrowthTitle, identifier: InputDataIdentifier("growthGraph"), type: .lines, load: { identifier in + context.loadGrowthGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.membersGraph.isEmpty { + graphs.append(Graph(graph: stats.membersGraph, title: L10n.statsGroupMembersTitle, identifier: InputDataIdentifier("membersGraph"), type: .lines, load: { identifier in + context.loadMembersGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.newMembersBySourceGraph.isEmpty { + graphs.append(Graph(graph: stats.newMembersBySourceGraph, title: L10n.statsGroupNewMembersBySourceTitle, identifier: InputDataIdentifier("newMembersBySourceGraph"), type: .bars, load: { identifier in + context.loadNewMembersBySourceGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.languagesGraph.isEmpty { + graphs.append(Graph(graph: stats.languagesGraph, title: L10n.statsGroupLanguagesTitle, identifier: InputDataIdentifier("languagesGraph"), type: .pie, load: { identifier in + context.loadLanguagesGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.messagesGraph.isEmpty { + graphs.append(Graph(graph: stats.messagesGraph, title: L10n.statsGroupMessagesTitle, identifier: InputDataIdentifier("messagesGraph"), type: .bars, load: { identifier in + context.loadMessagesGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.actionsGraph.isEmpty { + graphs.append(Graph(graph: stats.actionsGraph, title: L10n.statsGroupActionsTitle, identifier: InputDataIdentifier("actionsGraph"), type: .lines, load: { identifier in + context.loadActionsGraph() + updateIsLoading(identifier, true) + })) + } + + + if !stats.topHoursGraph.isEmpty { + graphs.append(Graph(graph: stats.topHoursGraph, title: L10n.statsGroupTopHoursTitle, identifier: InputDataIdentifier("topHoursGraph"), type: .hourlyStep, load: { identifier in + context.loadTopHoursGraph() + updateIsLoading(identifier, true) + })) + } + + if !stats.topWeekdaysGraph.isEmpty { + graphs.append(Graph(graph: stats.topWeekdaysGraph, title: L10n.statsGroupTopWeekdaysTitle, identifier: InputDataIdentifier("topWeekdaysGraph"), type: .area, load: { identifier in + context.loadTopWeekdaysGraph() + updateIsLoading(identifier, true) + })) + } + + + + for graph in graphs { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(graph.title), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + switch graph.graph { + case let .Loaded(_, string): + ChartsDataManager.readChart(data: string.data(using: .utf8)!, sync: true, success: { collection in + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticRowItem(initialSize, stableId: stableId, context: accountContext, collection: collection, viewType: .singleItem, type: graph.type, getDetailsData: { date, completion in + detailedDisposable.set(context.loadDetailedGraph(graph.graph, x: Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in + if let graph = graph, case let .Loaded(_, data) = graph { + completion(data) + } + }), forKey: graph.identifier) + }) + })) + }, failure: { error in + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: error.localizedDescription) + })) + }) + + updateIsLoading(graph.identifier, false) + + index += 1 + case .OnDemand: + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: nil) + })) + index += 1 + if !uiState.loading.contains(graph.identifier) { + graph.load(graph.identifier) + } + case let .Failed(error): + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: graph.identifier, equatable: InputDataEquatable(graph.graph), item: { initialSize, stableId in + return StatisticLoadingRowItem(initialSize, stableId: stableId, error: error) + })) + index += 1 + updateIsLoading(graph.identifier, false) + case .Empty: + break + } + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + var addNextSection: Bool = false + + + + if let peers = peers { + var topPosters = stats.topPosters.filter { $0.messageCount > 0 && peers[$0.peerId] != nil && !peers[$0.peerId]!.rawDisplayTitle.isEmpty } + if !topPosters.isEmpty { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.statsGroupTopPostersTitle), data: .init(color: theme.colors.listGrayText, detectBold: false, viewType: .textTopItem, rightItem: InputDataGeneralTextRightData(isLoading: false, text: .initialize(string: dates, color: theme.colors.listGrayText, font: .normal(12)))))) + index += 1 + + let needReveal = !uiState.revealed.contains(.topPosters) && topPosters.count > 10 + let toRevealCount = topPosters.count - 10 + if needReveal { + topPosters = Array(topPosters.prefix(10)) + } + + for (i, topPoster) in topPosters.enumerated() { + if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 { + var textComponents: [String] = [] + if topPoster.messageCount > 0 { + textComponents.append(L10n.statsGroupTopPosterMessagesCountable(Int(topPoster.messageCount))) + if topPoster.averageChars > 0 { + textComponents.append(L10n.statsGroupTopPosterCharsCountable(Int(topPoster.averageChars))) + } + } + + var viewType = bestGeneralViewType(topPosters, for: i) + + if topPoster == topPosters.last, needReveal { + viewType = .innerItem + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("top_posters_\(peer.id)"), equatable: nil, item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: accountContext.account, stableId: stableId, enabled: true, height: 56, photoSize: NSMakeSize(36, 36), status: textComponents.joined(separator: ", "), inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: { + openPeerInfo(peer.id) + }) + })) + index += 1 + } + } + + if needReveal { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: UIStatsState.RevealSection.topPosters.id, equatable: nil, item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.statsShowMoreCountable(toRevealCount), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: { + + revealSection(UIStatsState.RevealSection.topPosters) + + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + + addNextSection = true + } + + var topAdmins = stats.topAdmins.filter { + return peers[$0.peerId] != nil && ($0.deletedCount + $0.kickedCount + $0.bannedCount) > 0 && !peers[$0.peerId]!.rawDisplayTitle.isEmpty + } + + if !topAdmins.isEmpty { + if addNextSection { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.statsGroupTopAdminsTitle), data: .init(color: theme.colors.listGrayText, detectBold: false, viewType: .textTopItem, rightItem: InputDataGeneralTextRightData(isLoading: false, text: .initialize(string: dates, color: theme.colors.listGrayText, font: .normal(12)))))) + index += 1 + + let needReveal = !uiState.revealed.contains(.topPosters) && topAdmins.count > 10 + let toRevealCount = topAdmins.count - 10 + if needReveal { + topAdmins = Array(topAdmins.prefix(10)) + } + + for (i, topAdmin) in topAdmins.enumerated() { + if let peer = peers[topAdmin.peerId] { + + var textComponents: [String] = [] + if topAdmin.deletedCount > 0 { + textComponents.append(L10n.statsGroupTopAdminDeletionsCountable(Int(topAdmin.deletedCount))) + } + if topAdmin.kickedCount > 0 { + textComponents.append(L10n.statsGroupTopAdminKicksCountable(Int(topAdmin.kickedCount))) + } + if topAdmin.bannedCount > 0 { + textComponents.append(L10n.statsGroupTopAdminBansCountable(Int(topAdmin.bannedCount))) + } + + var viewType = bestGeneralViewType(topPosters, for: i) + + if topAdmin == topAdmins.last, needReveal { + viewType = .innerItem + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier.init("top_admin_\(peer.id)"), equatable: nil, item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: accountContext.account, stableId: stableId, enabled: true, height: 56, photoSize: NSMakeSize(36, 36), status: textComponents.joined(separator: ", "), inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: { + openPeerInfo(peer.id) + }) + })) + index += 1 + } + } + + + if needReveal { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: UIStatsState.RevealSection.topAdmins.id, equatable: nil, item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.statsShowMoreCountable(toRevealCount), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: { + + revealSection(UIStatsState.RevealSection.topAdmins) + + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + + + } else { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + addNextSection = false + } + + + var topInviters = stats.topInviters.filter { + return peers[$0.peerId] != nil && $0.inviteCount > 0 && !peers[$0.peerId]!.rawDisplayTitle.isEmpty + } + + if !topInviters.isEmpty { + if addNextSection { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.statsGroupTopInvitersTitle), data: .init(color: theme.colors.listGrayText, detectBold: false, viewType: .textTopItem, rightItem: InputDataGeneralTextRightData(isLoading: false, text: .initialize(string: dates, color: theme.colors.listGrayText, font: .normal(12)))))) + index += 1 + + + let needReveal = !uiState.revealed.contains(.topPosters) && topInviters.count > 10 + let toRevealCount = topInviters.count - 10 + if needReveal { + topInviters = Array(topInviters.prefix(10)) + } + + + for (i, topInviter) in topInviters.enumerated() { + if let peer = peers[topInviter.peerId] { + + var textComponents: [String] = [] + textComponents.append(L10n.statsGroupTopInviterInvitesCountable(Int(topInviter.inviteCount))) + + var viewType = bestGeneralViewType(topPosters, for: i) + + if topInviter == topInviters.last, needReveal { + viewType = .innerItem + } + + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("top_inviter_\(peer.id)"), equatable: nil, item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: accountContext.account, stableId: stableId, enabled: true, height: 56, photoSize: NSMakeSize(36, 36), status: textComponents.joined(separator: ", "), inset: NSEdgeInsets(left: 30, right: 30), viewType: viewType, action: { + openPeerInfo(peer.id) + }) + })) + index += 1 + } + } + + if needReveal { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: UIStatsState.RevealSection.topInviters.id, equatable: nil, item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.statsShowMoreCountable(toRevealCount), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: { + + revealSection(UIStatsState.RevealSection.topInviters) + + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + + } else { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + addNextSection = false + } + } + if addNextSection { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + } + + + return entries +} + + +func GroupStatsViewController(_ context: AccountContext, peerId: PeerId, datacenterId: Int32) -> ViewController { + + let initialState = UIStatsState(loading: []) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((UIStatsState) -> UIStatsState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + let openPeerInfo:(PeerId)->Void = { peerId in + return context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + } + + let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId) + + let peersPromise = Promise<[PeerId: Peer]?>(nil) + + peersPromise.set(.single(nil) |> then(statsContext.state |> map(Optional.init) + |> map { stats -> [PeerId]? in + guard let stats = stats?.stats else { + return nil + } + var peerIds = Set() + peerIds.formUnion(stats.topPosters.map { $0.peerId }) + peerIds.formUnion(stats.topAdmins.map { $0.peerId }) + peerIds.formUnion(stats.topInviters.map { $0.peerId }) + return Array(peerIds) + } + |> mapToSignal { peerIds -> Signal<[PeerId: Peer]?, NoError> in + return context.account.postbox.transaction { transaction -> [PeerId: Peer]? in + var peers: [PeerId: Peer] = [:] + if let peerIds = peerIds { + for peerId in peerIds { + if let peer = transaction.getPeer(peerId) { + peers[peerId] = peer + } + } + } + return peers + } + })) + + let detailedDisposable = DisposableDict() + + let signal = combineLatest(queue: prepareQueue, statePromise.get(), statsContext.state, peersPromise.get()) |> map { uiState, state, peers in + return statsEntries(state, uiState: uiState, peers: peers, updateIsLoading: { identifier, isLoading in + updateState { state in + if isLoading { + return state.withAddedLoading(identifier) + } else { + return state.withRemovedLoading(identifier) + } + } + }, revealSection: { section in + updateState { + $0.withRevealedSection(section) + } + }, context: statsContext, accountContext: context, openPeerInfo: openPeerInfo, detailedDisposable: detailedDisposable) + } |> map { + return InputDataSignalValue(entries: $0) + } + + + let controller = InputDataController(dataSignal: signal, title: L10n.groupStatsTitle, removeAfterDisappear: false, hasDone: false) + + controller.contextOject = statsContext + controller.didLoaded = { controller, _ in + controller.tableView.alwaysOpenRowsOnMouseUp = true + controller.tableView.needUpdateVisibleAfterScroll = true + } + + controller.onDeinit = { + detailedDisposable.dispose() + } + + return controller +} diff --git a/Telegram-Mac/HapticEngine.swift b/Telegram-Mac/HapticEngine.swift new file mode 100644 index 0000000000..6ef1509ef6 --- /dev/null +++ b/Telegram-Mac/HapticEngine.swift @@ -0,0 +1,15 @@ +// +// HapticEngine.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +class HapticEngine { + + + +} diff --git a/Telegram-Mac/ImageCompression.swift b/Telegram-Mac/ImageCompression.swift new file mode 100644 index 0000000000..6f422fa4fb --- /dev/null +++ b/Telegram-Mac/ImageCompression.swift @@ -0,0 +1,147 @@ +// +// ImageCompression.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +import TelegramCore +import SyncCore +import Postbox +import TGUIKit +import MurMurHash32 + +public struct TinyThumbnailData: Equatable { + let tablesDataHash: Int32 + let data: Data +} + +private let fixedTablesData = dataWithHexString("ffd8ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffd9") + +private let fixedTablesDataHash: Int32 = murMurHash32Data(fixedTablesData) + +private struct my_error_mgr { + var pub = jpeg_error_mgr() +} + +func decompressTinyThumbnail(data: TinyThumbnailData) -> CGImage? { + if data.tablesDataHash != fixedTablesDataHash { + return nil + } + + var cinfo = jpeg_decompress_struct() + var jerr = my_error_mgr() + + cinfo.err = jpeg_std_error(&jerr.pub) + //jerr.pub.error_exit = my_error_exit + + /* Establish the setjmp return context for my_error_exit to use. */ + /*if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + fclose(infile); + return 0; + }*/ + + /* Now we can initialize the JPEG decompression object. */ + jpeg_CreateDecompress(&cinfo, JPEG_LIB_VERSION, MemoryLayout.size(ofValue: cinfo)) + + /* Step 2: specify data source (eg, a file) */ + + let fixedTablesDataLength = UInt(fixedTablesData.count) + fixedTablesData.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + jpeg_mem_src(&cinfo, bytes, fixedTablesDataLength) + jpeg_read_header(&cinfo, 0) + } + + let result = data.data.withUnsafeBytes { (bytes: UnsafePointer) -> CGImage? in + jpeg_mem_src(&cinfo, bytes, fixedTablesDataLength) + jpeg_read_header(&cinfo, 1) + jpeg_start_decompress(&cinfo) + let rowStride = Int(cinfo.output_width) * 3 + var tempBuffer = malloc(rowStride)!.assumingMemoryBound(to: UInt8.self) + defer { + free(tempBuffer) + } + let context = DrawingContext(size: CGSize(width: CGFloat(cinfo.output_width), height: CGFloat(cinfo.output_height)), scale: 1.0, clear: false) + while cinfo.output_scanline < cinfo.output_height { + let rowPointer = context.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: Int(cinfo.output_scanline) * context.bytesPerRow) + var row: JSAMPROW? = UnsafeMutablePointer(tempBuffer) + jpeg_read_scanlines(&cinfo, &row, 1) + for x in 0 ..< Int(cinfo.output_width) { + rowPointer[x * 4 + 3] = 255 + for i in 0 ..< 3 { + rowPointer[x * 4 + i] = tempBuffer[x * 3 + i] + } + } + } + return context.generateImage() + } + + jpeg_finish_decompress(&cinfo) + jpeg_destroy_decompress(&cinfo) + + return result +} + +private let tinyThumbnailHeaderPattern = Data(base64Encoded: "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=") +private let tinyThumbnailFooterPattern = Data(base64Encoded: "/9k=") + +func decodeTinyThumbnail(data: Data) -> Data? { + if data.count < 3 { + return nil + } + guard let tinyThumbnailHeaderPattern = tinyThumbnailHeaderPattern, let tinyThumbnailFooterPattern = tinyThumbnailFooterPattern else { + return nil + } + var version: UInt8 = 0 + data.copyBytes(to: &version, count: 1) + if version != 1 { + return nil + } + var width: UInt8 = 0 + var height: UInt8 = 0 + data.copyBytes(to: &width, from: 1 ..< 2) + data.copyBytes(to: &height, from: 2 ..< 3) + + var resultData = Data() + resultData.append(tinyThumbnailHeaderPattern) + resultData.append(data.subdata(in: 3 ..< data.count)) + resultData.append(tinyThumbnailFooterPattern) + resultData.withUnsafeMutableBytes({ (resultBytes: UnsafeMutablePointer) -> Void in + resultBytes[164] = width + resultBytes[166] = height + }) + return resultData +} + +func serializeTinyThumbnail(_ data: TinyThumbnailData) -> String { + var result = "TTh1 \(data.data.count) bytes\n" + result.append(String(data.tablesDataHash, radix: 16)) + result.append(data.data.base64EncodedString()) + let parsed = parseTinyThumbnail(result) + assert(parsed == data) + return result +} + +func parseTinyThumbnail(_ text: String) -> TinyThumbnailData? { + if text.hasPrefix("TTh1") && text.count > 20 { + guard let startIndex = text.range(of: "\n")?.upperBound else { + return nil + } + let start = startIndex.encodedOffset + guard let hash = Int32(String(text[text.index(text.startIndex, offsetBy: start) ..< text.index(text.startIndex, offsetBy: start + 8)]), radix: 16) else { + return nil + } + guard let data = Data(base64Encoded: String(text[text.index(text.startIndex, offsetBy: start + 8)...])) else { + return nil + } + return TinyThumbnailData(tablesDataHash: hash, data: data) + } + return nil +} diff --git a/Telegram-Mac/ImageTransparency.swift b/Telegram-Mac/ImageTransparency.swift new file mode 100644 index 0000000000..5d85f1b97c --- /dev/null +++ b/Telegram-Mac/ImageTransparency.swift @@ -0,0 +1,188 @@ +// +// ImageTransparency.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Accelerate +import TGUIKit + + +private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? { + var sourceBuffer = vImage_Buffer() + defer { + free(sourceBuffer.data) + } + + var cgImageFormat = vImage_CGImageFormat( + bitsPerComponent: UInt32(cgImage.bitsPerComponent), + bitsPerPixel: UInt32(cgImage.bitsPerPixel), + colorSpace: Unmanaged.passUnretained(cgImage.colorSpace!), + bitmapInfo: cgImage.bitmapInfo, + version: 0, + decode: nil, + renderingIntent: .defaultIntent + ) + + let noFlags = vImage_Flags(kvImageNoFlags) + var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &cgImageFormat, nil, cgImage, noFlags) + assert(error == kvImageNoError) + + if cgImage.alphaInfo == .premultipliedLast { + error = vImageUnpremultiplyData_RGBA8888(&sourceBuffer, &sourceBuffer, noFlags) + } else if cgImage.alphaInfo == .premultipliedFirst { + error = vImageUnpremultiplyData_ARGB8888(&sourceBuffer, &sourceBuffer, noFlags) + } + assert(error == kvImageNoError) + + let histogramBins = (0...3).map { _ in + return [vImagePixelCount](repeating: 0, count: 256) + } + var mutableHistogram: [UnsafeMutablePointer?] = histogramBins.map { + return UnsafeMutablePointer(mutating: $0) + } + error = vImageHistogramCalculation_ARGB8888(&sourceBuffer, &mutableHistogram, noFlags) + assert(error == kvImageNoError) + + let alphaBinIndex = [.last, .premultipliedLast].contains(cgImage.alphaInfo) ? 3 : 0 + return (histogramBins, alphaBinIndex) +} + +func imageHasTransparency(_ cgImage: CGImage) -> Bool { + guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else { + return false + } + guard [.first, .last, .premultipliedFirst, .premultipliedLast].contains(cgImage.alphaInfo) else { + return false + } + if let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) { + for i in 0 ..< 255 { + if histogramBins[alphaBinIndex][i] > 0 { + return true + } + } + } + return false +} + +private func scaledDrawingContext(_ cgImage: CGImage, maxSize: CGSize) -> DrawingContext { + var size = CGSize(width: cgImage.width, height: cgImage.height) + if (size.width > maxSize.width && size.height > maxSize.height) { + size = size.aspectFilled(maxSize) + } + let context = DrawingContext(size: size, scale: 1.0, clear: true) + context.withFlippedContext { context in + context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) + } + return context +} + +func imageRequiresInversion(_ cgImage: CGImage) -> Bool { + guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else { + return false + } + guard [.first, .last, .premultipliedFirst, .premultipliedLast].contains(cgImage.alphaInfo) else { + return false + } + + let context = scaledDrawingContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0)) + if let cgImage = context.generateImage(), let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) { + var hasAlpha = false + for i in 0 ..< 255 { + if histogramBins[alphaBinIndex][i] > 0 { + hasAlpha = true + break + } + } + + if hasAlpha { + var matching: Int = 0 + var total: Int = 0 + for y in 0 ..< Int(context.size.height) { + for x in 0 ..< Int(context.size.width) { + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + context.colorAt(CGPoint(x: x, y: y)).getHue(nil, saturation: &saturation, brightness: &brightness, alpha: &alpha) + if alpha < 1.0 { + hasAlpha = true + } + + if alpha > 0.0 { + total += 1 + if saturation < 0.1 && brightness < 0.25 { + matching += 1 + } + } + } + } + return CGFloat(matching) / CGFloat(total) > 0.85 + } + } + return false +} + + +func generateTintedImage(image: CGImage?, color: NSColor, backgroundColor: NSColor? = nil, flipVertical: Bool = true) -> CGImage? { + guard let image = image else { + return nil + } + let imageSize = image.size + return generateImage(imageSize, contextGenerator: { size, context in + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: imageSize)) + } + + let imageRect = CGRect(origin: CGPoint(), size: imageSize) + context.saveGState() + if flipVertical { + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + } + context.clip(to: imageRect, mask: image) + context.setFillColor(color.cgColor) + context.fill(imageRect) + context.restoreGState() + }) +} + + + +private func orientationFromExif(orientation: Int) -> ImageOrientation { + switch orientation { + case 1: + return .up; + case 3: + return .down; + case 8: + return .left; + case 6: + return .right; + case 2: + return .upMirrored; + case 4: + return .downMirrored; + case 5: + return .leftMirrored; + case 7: + return .rightMirrored; + default: + return .up + } +} + +func imageOrientationFromSource(_ source: CGImageSource) -> ImageOrientation { + if let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) { + let dict = properties as NSDictionary + if let value = dict.object(forKey: kCGImagePropertyOrientation) as? NSNumber { + return orientationFromExif(orientation: value.intValue) + } + } + + return .up +} diff --git a/Telegram-Mac/ImageUtils.swift b/Telegram-Mac/ImageUtils.swift index 96e6273585..94cd0192c5 100644 --- a/Telegram-Mac/ImageUtils.swift +++ b/Telegram-Mac/ImageUtils.swift @@ -7,87 +7,109 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit import TGUIKit +import SyncCore -/* - @[TGColorWithHex(0xff516a), TGColorWithHex(0xff885e)], - @[TGColorWithHex(0xffa85c), TGColorWithHex(0xffcd6a)], - @[TGColorWithHex(0x54cb68), TGColorWithHex(0xa0de7e)], - @[TGColorWithHex(0x2a9ef1), TGColorWithHex(0x72d5fd)], - @[TGColorWithHex(0x665fff), TGColorWithHex(0x82b1ff)], - @[TGColorWithHex(0xd669ed), TGColorWithHex(0xe0a2f3)], - */ +let graphicsThreadPool = ThreadPool(threadCount: 5, threadPriority: 1) -private let colors: [(top: NSColor, bottom: NSColor)] = [ - (NSColor(0xff516a), NSColor(0xff885e)), - (NSColor(0xffa85c), NSColor(0xffcd6a)), - (NSColor(0x54cb68), NSColor(0xa0de7e)), - (NSColor(0x2a9ef1), NSColor(0x72d5fd)), - (NSColor(0x665fff), NSColor(0x82b1ff)), - (NSColor(0xd669ed), NSColor(0xe0a2f3)) -] +enum PeerPhoto { + case peer(Peer, TelegramMediaImageRepresentation?, [String], Message?) +} -public func peerAvatarImage(account: Account, peer: Peer, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), scale:CGFloat = 1.0, font:NSFont = .medium(.title)) -> Signal? { - if let smallProfileImage = peer.smallProfileImage { - - return cachedPeerPhoto(peer.id, representation: smallProfileImage, size: displayDimensions, scale: scale) |> mapToSignal { cached -> Signal in - if let cached = cached { - return .single(cached) - } else { - let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource) - let imageData = resourceData - |> take(1) - |> mapToSignal { maybeData -> Signal in - if maybeData.complete { - return .single(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path))) - } else { - return Signal { subscriber in - let resourceDataDisposable = resourceData.start(next: { data in - if data.complete { - subscriber.putNext(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path))) - subscriber.putCompletion() - } - }, error: { error in - subscriber.putError(error) - }, completed: { - subscriber.putCompletion() - }) - let fetchedDataDisposable = account.postbox.mediaBox.fetchedResource(smallProfileImage.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)).start() - return ActionDisposable { - resourceDataDisposable.dispose() - fetchedDataDisposable.dispose() - } +private var capHolder:[String : CGImage] = [:] + +private func peerImage(account: Account, peer: Peer, displayDimensions: NSSize, representation: TelegramMediaImageRepresentation?, message: Message? = nil, displayLetters: [String], font: NSFont, scale: CGFloat, genCap: Bool, synchronousLoad: Bool) -> Signal<(CGImage?, Bool), NoError> { + if let representation = representation { + return cachedPeerPhoto(peer.id, representation: representation, size: displayDimensions, scale: scale) |> mapToSignal { cached -> Signal<(CGImage?, Bool), NoError> in + return autoreleasepool { + if let cached = cached { + return cachePeerPhoto(image: cached, peerId: peer.id, representation: representation, size: displayDimensions, scale: scale) |> map { + return (cached, false) + } + } else { + let resourceData = account.postbox.mediaBox.resourceData(representation.resource, attemptSynchronously: synchronousLoad) + let imageData = resourceData + |> take(1) + |> mapToSignal { maybeData -> Signal<(Data?, Bool), NoError> in + return autoreleasepool { + if maybeData.complete { + return .single((try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)), false)) + } else { + return Signal { subscriber in + let resourceDataDisposable = resourceData.start(next: { data in + if data.complete { + subscriber.putNext((try? Data(contentsOf: URL(fileURLWithPath: data.path)), true)) + subscriber.putCompletion() + } + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + let fetchedDataDisposable: Disposable + if let reference = PeerReference(peer) { + fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.avatar(peer: reference, resource: representation.resource), statsCategory: .image).start() + } else if let message = message { + fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.messageAuthorAvatar(message: MessageReference(message), resource: representation.resource), statsCategory: .image).start() + } else { + fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.standalone(resource: representation.resource), statsCategory: .image).start() + } + return ActionDisposable { + resourceDataDisposable.dispose() + fetchedDataDisposable.dispose() + } + } + } } - } - } - return imageData - |> deliverOn(account.graphicsThreadPool) - |> mapToSignal { data -> Signal in - - let image:CGImage? - if let data = data { - image = roundImage(data, displayDimensions, scale:scale) + } + + let def = deferred({ () -> Signal<(CGImage?, Bool), NoError> in + let key = NSStringFromSize(displayDimensions) + if let image = capHolder[key] { + return .single((image, false)) } else { - image = nil + let size = NSMakeSize(max(30, displayDimensions.width), max(30, displayDimensions.height)) + capHolder[key] = generateAvatarPlaceholder(foregroundColor: theme.colors.grayBackground, size: size) + return .single((capHolder[key]!, false)) } - if let image = image { - return cachePeerPhoto(image: image, peerId: peer.id, representation: smallProfileImage, size: displayDimensions, scale: scale) |> map { - return image + }) |> deliverOnMainQueue + + let loadDataSignal = synchronousLoad ? imageData : imageData |> deliverOn(graphicsThreadPool) + + let img = loadDataSignal |> mapToSignal { data, animated -> Signal<(CGImage?, Bool), NoError> in + + let image:CGImage? + if let data = data { + image = roundImage(data, displayDimensions, scale:scale) + } else { + image = nil } - } else { - return .single(image) - } - + if let image = image { + return cachePeerPhoto(image: image, peerId: peer.id, representation: representation, size: displayDimensions, scale: scale) |> map { + return (image, animated) + } + } else { + return .single((image, animated)) + } + + } + if genCap { + return def |> then(img) + } else { + return img + } } } } } else { - var letters = peer.displayLetters + var letters = displayLetters if letters.count < 2 { while letters.count != 2 { letters.append("") @@ -95,74 +117,24 @@ public func peerAvatarImage(account: Account, peer: Peer, displayDimensions: CGS } - - - - - let colorIndex: Int32 - if peer.id.namespace == Namespaces.Peer.CloudUser { - colorIndex = colorIndexForUid(peer.id.id, account.peerId.id) - } else if peer.id.namespace == Namespaces.Peer.CloudChannel { - colorIndex = colorIndexForGroupId(TGPeerIdFromChannelId(peer.id.id)) - } else { - colorIndex = colorIndexForGroupId(-Int64(peer.id.id)) - } - let color = colors[abs(Int(colorIndex % 6))] + let color = theme.colors.peerColors(Int(abs(peer.id.id % 7))) let symbol = letters.reduce("", { (current, letter) -> String in return current + letter }) - return cachedEmptyPeerPhoto(peer.id, symbol: symbol, color: color.top, size: displayDimensions, scale: scale) |> mapToSignal { cached -> Signal in + return cachedEmptyPeerPhoto(peer.id, symbol: symbol, color: color.top, size: displayDimensions, scale: scale) |> mapToSignal { cached -> Signal<(CGImage?, Bool), NoError> in if let cached = cached { - return .single(cached) + return .single((cached, false)) } else { - return Signal { subscriber in - let image = generateImage(displayDimensions, contextGenerator: { (size, ctx) in - ctx.clear(NSMakeRect(0, 0, size.width, size.height)) - ctx.beginPath() - ctx.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: - size.height)) - ctx.clip() - - - var locations: [CGFloat] = [1.0, 0.2]; - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [color.bottom.cgColor, color.top.cgColor]), locations: &locations)! - - ctx.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) - - ctx.setBlendMode(.normal) - - let letters = letters - let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) - let attributedString = NSAttributedString(string: string, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: NSColor.white]) - - let line = CTLineCreateWithAttributedString(attributedString) - let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) - - let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) , y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0)) - - ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) - ctx.scaleBy(x: 1.0, y: 1.0) - ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - // - ctx.translateBy(x: lineOrigin.x, y: lineOrigin.y) - CTLineDraw(line, ctx) - ctx.translateBy(x: -lineOrigin.x, y: -lineOrigin.y) - }) - - subscriber.putNext(image) - subscriber.putCompletion() - return EmptyDisposable - } |> runOn(account.graphicsThreadPool) |> mapToSignal { image -> Signal in + return generateEmptyPhoto(displayDimensions, type: .peer(colors: color, letter: letters, font: font)) |> runOn(graphicsThreadPool) |> mapToSignal { image -> Signal<(CGImage?, Bool), NoError> in if let image = image { return cacheEmptyPeerPhoto(image: image, peerId: peer.id, symbol: symbol, color: color.top, size: displayDimensions, scale: scale) |> map { - return image + return (image, false) } } else { - return .single(image) + return .single((image, false)) } } } @@ -171,26 +143,149 @@ public func peerAvatarImage(account: Account, peer: Peer, displayDimensions: CGS } } -func generateEmptyRoundAvatar(_ displayDimensions:NSSize, font: NSFont, account:Account, peer:Peer) -> Signal { +func peerAvatarImage(account: Account, photo: PeerPhoto, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), scale:CGFloat = 1.0, font:NSFont = .medium(.title), genCap: Bool = true, synchronousLoad: Bool = false) -> Signal<(CGImage?, Bool), NoError> { + + switch photo { + case let .peer(peer, representation, displayLetters, message): + return peerImage(account: account, peer: peer, displayDimensions: displayDimensions, representation: representation, message: message, displayLetters: displayLetters, font: font, scale: scale, genCap: genCap, synchronousLoad: synchronousLoad) + } +} + +/* + case let .group(peerIds, representations, displayLetters): + var combine:[Signal<(CGImage?, Bool), NoError>] = [] + let inGroupSize = NSMakeSize(displayDimensions.width / 2 - 2, displayDimensions.height / 2 - 2) + for peerId in peerIds { + let representation: TelegramMediaImageRepresentation? = representations[peerId] + let letters = displayLetters[peerId] ?? ["", ""] + combine.append(peerImage(account: account, peerId: peerId, displayDimensions: inGroupSize, representation: representation, displayLetters: letters, font: font, scale: scale, genCap: genCap, synchronousLoad: synchronousLoad)) + } + return combineLatest(combine) |> deliverOn(graphicsThreadPool) |> map { images -> (CGImage?, Bool) in + var animated: Bool = false + for image in images { + if image.1 { + animated = true + break + } + } + return (generateImage(displayDimensions, rotatedContext: { size, ctx in + var x: CGFloat = 0 + var y: CGFloat = 0 + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + for i in 0 ..< images.count { + let image = images[i].0 + if let image = image { + let img = generateImage(image.size, rotatedContext: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(image, in: NSMakeRect(0, 0, image.size.width, image.size.height)) + })! + ctx.draw(img, in: NSMakeRect(x, y, inGroupSize.width, inGroupSize.height)) + } + x += inGroupSize.width + 4 + if (i + 1) % 2 == 0 { + x = 0 + y += inGroupSize.height + 4 + } + } + + }), animated) + + } + */ + +enum EmptyAvatartType { + case peer(colors:(top:NSColor, bottom: NSColor), letter: [String], font: NSFont) + case icon(colors:(top:NSColor, bottom: NSColor), icon: CGImage, iconSize: NSSize, cornerRadius: CGFloat?) +} + +func generateEmptyPhoto(_ displayDimensions:NSSize, type: EmptyAvatartType) -> Signal { return Signal { subscriber in - let letters = peer.displayLetters - let colorIndex: Int32 - if peer.id.namespace == Namespaces.Peer.CloudUser { - colorIndex = colorIndexForUid(peer.id.id, account.peerId.id) - } else if peer.id.namespace == Namespaces.Peer.CloudChannel { - colorIndex = colorIndexForGroupId(TGPeerIdFromChannelId(peer.id.id)) - } else { - colorIndex = colorIndexForGroupId(-Int64(peer.id.id)) + let color:(top: NSColor, bottom: NSColor) + let letters: [String]? + let icon: CGImage? + let iconSize: NSSize? + let font: NSFont? + let cornerRadius: CGFloat? + switch type { + case let .icon(colors, _icon, _iconSize, _cornerRadius): + color = colors + icon = _icon + letters = nil + font = nil + iconSize = _iconSize + cornerRadius = _cornerRadius + case let .peer(colors, _letters, _font): + color = colors + icon = nil + font = _font + letters = _letters + iconSize = nil + cornerRadius = nil } - let color = colors[abs(Int(colorIndex % 6))] + + let image = generateImage(displayDimensions, contextGenerator: { (size, ctx) in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + + if let cornerRadius = cornerRadius { + ctx.round(size, cornerRadius) + } else { + ctx.round(size, size.height / 2) + } + //ctx.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: + // size.height)) + // ctx.clip() + + var locations: [CGFloat] = [1.0, 0.2]; + let colorSpace = deviceColorSpace + let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [color.top.cgColor, color.bottom.cgColor]), locations: &locations)! + + ctx.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + + ctx.setBlendMode(.normal) + + if let letters = letters, let font = font { + let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) + let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: NSColor.white]) + + let line = CTLineCreateWithAttributedString(attributedString) + let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) + + let lineOrigin = CGPoint(x: floorToScreenPixels(System.backingScale, -lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) , y: floorToScreenPixels(System.backingScale, -lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0)) + + ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) + ctx.scaleBy(x: 1.0, y: 1.0) + ctx.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + // + ctx.translateBy(x: lineOrigin.x, y: lineOrigin.y) + CTLineDraw(line, ctx) + ctx.translateBy(x: -lineOrigin.x, y: -lineOrigin.y) + } + + if let icon = icon, let iconSize = iconSize { + let rect = NSMakeRect((displayDimensions.width - iconSize.width)/2, (displayDimensions.height - iconSize.height)/2, iconSize.width, iconSize.height) + ctx.draw(icon, in: rect) + } + + }) + subscriber.putNext(image) + subscriber.putCompletion() + return EmptyDisposable + } +} + +func generateEmptyRoundAvatar(_ displayDimensions:NSSize, font: NSFont, account:Account, peer:Peer) -> Signal { + return Signal { subscriber in + let letters = peer.displayLetters + + let color = theme.colors.peerColors(Int(abs(peer.id.id % 7))) let image = generateImage(displayDimensions, contextGenerator: { (size, ctx) in ctx.clear(NSMakeRect(0, 0, size.width, size.height)) var locations: [CGFloat] = [1.0, 0.2]; - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [color.bottom.cgColor, color.top.cgColor]), locations: &locations)! + let colorSpace = deviceColorSpace + let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [color.top.cgColor, color.bottom.cgColor]), locations: &locations)! ctx.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) @@ -198,12 +293,12 @@ func generateEmptyRoundAvatar(_ displayDimensions:NSSize, font: NSFont, account: let letters = letters let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) - let attributedString = NSAttributedString(string: string, attributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: NSColor.white]) + let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: NSColor.white]) let line = CTLineCreateWithAttributedString(attributedString) let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) - let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) , y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0)) + let lineOrigin = CGPoint(x: floorToScreenPixels(System.backingScale, -lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) , y: floorToScreenPixels(System.backingScale, -lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0)) ctx.translateBy(x: size.width / 2.0, y: size.height / 2.0) ctx.scaleBy(x: 1.0, y: 1.0) diff --git a/Telegram-Mac/InAppLinks.swift b/Telegram-Mac/InAppLinks.swift index 6ff1004241..437bf1b98f 100644 --- a/Telegram-Mac/InAppLinks.swift +++ b/Telegram-Mac/InAppLinks.swift @@ -7,14 +7,66 @@ // import Cocoa +import Foundation import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import MtProtoKit +//import WalletCore + private let inapp:String = "chat://" private let tgme:String = "tg://" + +func resolveUsername(username: String, context: AccountContext) -> Signal { + if username.hasPrefix("_private_"), let range = username.range(of: "_private_") { + if let channelId = Int32(username[range.upperBound...]) { + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + + let peerSignal: Signal = context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } |> mapToSignal { peer in + if let peer = peer { + return .single(peer) + } else { + return findChannelById(postbox: context.account.postbox, network: context.account.network, channelId: peerId.id) + } + } + + return peerSignal |> deliverOnMainQueue |> map { peer in + if let peer = peer { + if let peer = peer as? TelegramChannel { + if peer.participationStatus != .member { + return nil + } + } + } + return peer + } + } else { + return .single(nil) + } + } else { + return resolvePeerByName(account: context.account, name: username) |> mapToSignal { peerId -> Signal in + if let peerId = peerId { + return context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + } + return .single(nil) + } |> deliverOnMainQueue + } + +} + +enum InAppSettingsSection : String { + case themes + case devices + case folders + case privacy +} + enum ChatInitialActionBehavior : Equatable { case none case automatic @@ -24,60 +76,209 @@ enum ChatInitialAction : Equatable { case start(parameter: String, behavior: ChatInitialActionBehavior) case inputText(text: String, behavior: ChatInitialActionBehavior) case files(list: [String], behavior: ChatInitialActionBehavior) + case forward(messageIds: [MessageId], text: String?, behavior: ChatInitialActionBehavior) + case ad(PromoChatListItem.Kind) + case source(MessageId) + case closeAfter(Int32) } -func ==(lhs:ChatInitialAction, rhs:ChatInitialAction) -> Bool { - switch lhs { - case let .start(lhsText, lhsBehavior): - if case let .start(rhsText, rhsBehavior) = rhs, lhsText == rhsText, lhsBehavior == rhsBehavior { - return true - } else { - return false - } - case let .inputText(lhsText, lhsBehavior): - if case let .inputText(rhsText, rhsBehavior) = rhs, lhsText == rhsText, lhsBehavior == rhsBehavior { - return true - } else { + +var globalLinkExecutor:TextViewInteractions { + get { + return TextViewInteractions(processURL:{(link) in + if let link = link as? inAppLink { + switch link { + case .requestSecureId: + break // never execute from inapp + default: + execute(inapp:link) + } + } + }, isDomainLink: { value, origin in + if let value = value as? inAppLink { + switch value { + case .external: + return true + default: + if let origin = origin { + if origin != value.link, !origin.isEmpty && origin != "‌" { + return true + } + } + return false + } + } return false - } - case let .files(lhsList, lhsBehavior): - if case let .files(rhsList, rhsBehavior) = rhs, lhsList == rhsList, lhsBehavior == rhsBehavior { - return true - } else { + }, makeLinkType: { link, url in + if let link = link as? inAppLink { + switch link { + case .botCommand: + return .command + case .hashtag: + return .hashtag + case .code: + return .code + case .followResolvedName: + if url.hasPrefix("@") { + return .username + } else { + return .plain + } + case let .external(link, _): + if isValidEmail(link) { + return .email + } else if telegram_me.first(where: {link.contains($0 + "joinchat/")}) != nil { + return .inviteLink + } else { + return .plain + } + case .stickerPack: + return .stickerPack + case .joinchat: + return .inviteLink + default: + return .plain + } + } + return .plain + }, localizeLinkCopy: { type in + return copyContextText(from: type) + }, resolveLink: { link in + return (link as? inAppLink)?.link + }, copyAttributedString: { string in + let pb = NSPasteboard.general + + if !FastSettings.enableRTF { + pb.clearContents() + pb.declareTypes([.string], owner: nil) + pb.setString(string.string, forType: .string) + return true + } + + let modified: NSMutableAttributedString = string.mutableCopy() as! NSMutableAttributedString + + string.enumerateAttributes(in: string.range, options: [], using: { attr, range, _ in + if let appLink = attr[NSAttributedString.Key.link] as? inAppLink { + switch appLink { + case .code, .hashtag, .callback: + break + default: + if appLink.link != modified.string.nsstring.substring(with: range) { + modified.addAttribute(NSAttributedString.Key.link, value: appLink.link, range: range) + } + } + + } else if let appLink = attr[NSAttributedString.Key(TGCustomLinkAttributeName)] as? TGInputTextTag { + if (appLink.attachment as? String) != modified.string.nsstring.substring(with: range) { + modified.addAttribute(NSAttributedString.Key.link, value: appLink.attachment, range: range) + } + } else if attr[.foregroundColor] != nil { + modified.removeAttribute(.foregroundColor, range: range) + } else if let font = attr[.font] as? NSFont { + if let newFont = NSFont(name: font.fontName, size: 0) { + modified.setFont(font: newFont, range: range) + } + } else if attr[.paragraphStyle] != nil { + modified.removeAttribute(.paragraphStyle, range: range) + } + }) + + + if !modified.string.isEmpty { + pb.clearContents() + + let rtf = try? modified.data(from: modified.range, documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType : NSAttributedString.DocumentType.rtf]) + if let rtf = rtf { + pb.declareTypes([.rtf], owner: nil) + pb.setData(rtf, forType: .rtf) + pb.setString(modified.string, forType: .string) + return true + } + } + return false - } + }) } } -let globalLinkExecutor:TextViewInteractions = TextViewInteractions(processURL:{(link) in - if let link = link as? inAppLink { - execute(inapp:link) +func copyContextText(from type: LinkType) -> String { + switch type { + case .username: + return L10n.textContextCopyUsername + case .command: + return L10n.textContextCopyCommand + case .hashtag: + return L10n.textContextCopyHashtag + case .email: + return L10n.textContextCopyEmail + case .plain: + return L10n.textContextCopyLink + case .inviteLink: + return L10n.textContextCopyInviteLink + case .stickerPack: + return L10n.textContextCopyStickerPack + case .code: + return L10n.textContextCopyCode } -}) +} -func execute(inapp:inAppLink) { +func execute(inapp:inAppLink, afterComplete: @escaping(Bool)->Void = { _ in }) { switch inapp { - case let .external(link,needConfirm): + case let .external(link, needConfirm): var url:String = link.trimmed - if !url.hasPrefix("http") && !url.hasPrefix("ftp") { + + var reversedUrl = String(url.reversed()) + while reversedUrl.components(separatedBy: "#").count > 2 { + if let index = reversedUrl.range(of: "#") { + reversedUrl.replaceSubrange(index, with: "32%") + } + } + url = String(reversedUrl.reversed()) + + if !url.hasPrefix("http") && !url.hasPrefix("ftp"), url.range(of: "://") == nil { if isValidEmail(link) { - url = "mailto:" + url + // url = "mailto:" + url } else { url = "http://" + url } } - if let url = URL(string: escape(with:url)) { + let urlValue = url + let escaped = escape(with:url) + if let urlQueryAllowed = Optional(escaped), let url = URL(string: urlQueryAllowed) { + let needConfirm = needConfirm || url.host != URL(string: urlValue)?.host + let removePecentEncoding = url.host == URL(string: urlValue)?.host let success:()->Void = { + + var path = url.absoluteString + let supportSchemes:[String] = ["itunes.apple.com"] + for scheme in supportSchemes { + var url:URL? = nil + if path.contains(scheme) { + switch scheme { + case supportSchemes[0]: // itunes + path = "itms://" + path.nsstring.substring(from: path.nsstring.range(of: scheme).location) + url = URL(string: path) + default: + continue + } + } + if let url = url { + NSWorkspace.shared.open(url) + afterComplete(true) + return + } + } + afterComplete(true) NSWorkspace.shared.open(url) } if needConfirm { - confirm(for: mainWindow, with: appName, and: tr(.inAppLinksConfirmOpenExternal(url.absoluteString)), successHandler: {_ in success()}) + confirm(for: mainWindow, header: L10n.inAppLinksConfirmOpenExternalHeader, information: L10n.inAppLinksConfirmOpenExternalNew(removePecentEncoding ? (url.absoluteString.removingPercentEncoding ?? url.absoluteString) : escaped), okTitle: L10n.inAppLinksConfirmOpenExternalOK, successHandler: {_ in success()}, cancelHandler: { afterComplete(false) }) } else { success() } } - case let .peerInfo(peerId, action, openChat, postId, callback): + case let .peerInfo(_, peerId, action, openChat, postId, callback): let messageId:MessageId? if let postId = postId { messageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: MessageId.Id(postId)) @@ -85,107 +286,371 @@ func execute(inapp:inAppLink) { messageId = nil } callback(peerId, openChat, messageId, action) - case let .followResolvedName(username, postId, account, action, callback): - let _ = showModalProgress(signal: resolvePeerByName(account: account, name: username) |> mapToSignal { peerId -> Signal in - if let peerId = peerId { - return account.postbox.loadedPeerWithId(peerId) |> map {Optional($0)} - } - return .single(nil) - } |> deliverOnMainQueue, for: mainWindow).start(next: { peer in - if let peer = peer { - let messageId:MessageId? - if let postId = postId { - messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: postId) - } else { - messageId = nil + afterComplete(true) + case let .followResolvedName(_, username, postId, context, action, callback): + + if username.hasPrefix("_private_"), let range = username.range(of: "_private_") { + if let channelId = Int32(username[range.upperBound...]) { + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + + let peerSignal: Signal = context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } |> mapToSignal { peer in + if let peer = peer { + return .single(peer) + } else { + return findChannelById(postbox: context.account.postbox, network: context.account.network, channelId: peerId.id) + } } - callback(peer.id, peer.isChannel || peer.isSupergroup || peer.isBot, messageId, action) + + _ = showModalProgress(signal: peerSignal |> deliverOnMainQueue, for: context.window).start(next: { peer in + if let peer = peer { + let messageId:MessageId? + if let postId = postId { + messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: postId) + } else { + messageId = nil + } + if let peer = peer as? TelegramChannel { + if peer.participationStatus == .kicked { + alert(for: context.window, info: L10n.alertPrivateChannelAccessError) + return + } + } + callback(peer.id, peer.isChannel || peer.isSupergroup || peer.isBot, messageId, action) + } else { + alert(for: context.window, info: L10n.alertPrivateChannelAccessError) + } + }) } else { - alert(for: mainWindow, header: appName, info: tr(.alertUserDoesntExists)) + alert(for: context.window, info: L10n.alertPrivateChannelAccessError) } + } else { + let _ = showModalProgress(signal: resolvePeerByName(account: context.account, name: username) |> mapToSignal { peerId -> Signal in + if let peerId = peerId { + return context.account.postbox.loadedPeerWithId(peerId) |> map {Optional($0)} + } + return .single(nil) + } |> deliverOnMainQueue, for: context.window).start(next: { peer in + if let peer = peer { + let messageId:MessageId? + if let postId = postId { + messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: postId) + } else { + messageId = nil + } + callback(peer.id, peer.isChannel || peer.isSupergroup || peer.isBot, messageId, action) + } else { + alert(for: context.window, info: tr(L10n.alertUserDoesntExists)) + } + + }) + } + afterComplete(true) + case let .inviteBotToGroup(_, username, context, action, callback): + let _ = showModalProgress(signal: resolvePeerByName(account: context.account, name: username) |> filter {$0 != nil} |> map{$0!} |> deliverOnMainQueue, for: context.window).start(next: { botPeerId in - }) - case let .inviteBotToGroup(username, account, action, callback): - - let _ = (showModalProgress(signal: resolvePeerByName(account: account, name: username) |> filter {$0 != nil} |> map{$0!} |> deliverOnMainQueue, for: mainWindow) |> mapToSignal { memberId -> Signal in - - return selectModalPeers(account: account, title: "", behavior: SelectChatsBehavior(limit: 1), confirmation: { peerIds -> Signal in + let selectedPeer = selectModalPeers(context: context, title: L10n.selectPeersTitleSelectChat, behavior: SelectChatsBehavior(limit: 1), confirmation: { peerIds -> Signal in if let peerId = peerIds.first { - return account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in - return confirmSignal(for: mainWindow, header: appName, information: tr(.confirmAddBotToGroup(peer.displayTitle))) + return context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in + return confirmSignal(for: context.window, information: L10n.confirmAddBotToGroup(peer.displayTitle)) } } return .single(false) - }) |> deliverOnMainQueue |> filter {$0.first != nil} |> map {$0.first!} |> mapToSignal { peerId in - return showModalProgress(signal: addPeerMember(account: account, peerId: peerId, memberId: memberId), for: mainWindow) |> mapError {_ in} |> map {peerId} - } - }).start(next: { peerId in - callback(peerId, true, nil, action) - }, error: { + }) |> deliverOnMainQueue |> filter { $0.first != nil } |> map { $0.first! } + let signal:Signal<(StartBotInGroupResult, PeerId), NoError> = selectedPeer |> mapToSignal { peerId in + var payload: String = "" + if let action = action { + switch action { + case let .start(data, _): + payload = data + default: + break + } + } + if payload.isEmpty { + if peerId.namespace == Namespaces.Peer.CloudGroup { + return showModalProgress(signal: addGroupMember(account: context.account, peerId: peerId, memberId: botPeerId), for: context.window) + |> map { (.none, peerId) } + |> `catch` { _ -> Signal<(StartBotInGroupResult, PeerId), NoError> in return .single((.none, peerId)) } + } else { + return showModalProgress(signal: context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerId, memberId: botPeerId), for: context.window) + |> map { _ in (.none, peerId) } + |> then(.single((.none, peerId))) + } + } else { + return showModalProgress(signal: requestStartBotInGroup(account: context.account, botPeerId: botPeerId, groupPeerId: peerId, payload: payload), for: context.window) + |> map { + ($0, peerId) + } + |> `catch` { _ -> Signal<(StartBotInGroupResult, PeerId), NoError> in return .single((.none, peerId)) } + + } + } |> deliverOnMainQueue + + _ = signal.start(next: { result, peerId in + switch result { + case let .channelParticipant(participant): + context.peerChannelMemberCategoriesContextsManager.externallyAdded(peerId: peerId, participant: participant) + case .none: + break + } + callback(peerId, true, nil, nil) + }) }) + afterComplete(true) case let .botCommand(command, interaction): interaction(command) + afterComplete(true) case let .hashtag(hashtag, interaction): interaction(hashtag) - case let .joinchat(hash, account, interaction): - _ = showModalProgress(signal: joinLinkInformation(hash, account: account), for: mainWindow).start(next: { (result) in + afterComplete(true) + case let .joinchat(_, hash, context, interaction): + _ = showModalProgress(signal: joinLinkInformation(hash, account: context.account), for: context.window).start(next: { (result) in switch result { case let .alreadyJoined(peerId): interaction(peerId, true, nil, nil) case .invite: - showModal(with: JoinLinkPreviewModalController(account, hash: hash, join: result, interaction: { peerId in + showModal(with: JoinLinkPreviewModalController(context, hash: hash, join: result, interaction: { peerId in if let peerId = peerId { interaction(peerId, true, nil, nil) } - }), for: mainWindow) + }), for: context.window) + case let .peek(peerId, peek): + interaction(peerId, true, nil, .closeAfter(peek)) case .invalidHash: - alert(for: mainWindow, info: tr(.groupUnavailable)) + alert(for: context.window, info: tr(L10n.groupUnavailable)) } }) + afterComplete(true) case let .callback(param, interaction): interaction(param) + afterComplete(true) + case let .code(param, interaction): + interaction(param) + afterComplete(true) case let .logout(interaction): interaction() - case let .shareUrl(account, url): + afterComplete(true) + case let .shareUrl(_, context, url): if !url.hasPrefix("@") { - showModal(with: ShareModalController(ShareLinkObject(account, link: url)), for: mainWindow) + showModal(with: ShareModalController(ShareLinkObject(context, link: url)), for: context.window) } - case let .stickerPack(reference, account, peerId): - showModal(with: StickersPackPreviewModalController(account, peerId: peerId, reference: reference), for: mainWindow) - case let .socks(settings, applyProxy): + afterComplete(true) + case let .wallpaper(_, context, preview): + switch preview { + case let .gradient(top, bottom, rotation): + let wallpaper: TelegramWallpaper = .gradient(top.argb, bottom.rgb, WallpaperSettings(rotation: rotation)) + showModal(with: WallpaperPreviewController(context, wallpaper: Wallpaper(wallpaper), source: .link(wallpaper)), for: context.window) + case let .color(color): + let wallpaper: TelegramWallpaper = .color(color.argb) + showModal(with: WallpaperPreviewController(context, wallpaper: Wallpaper(wallpaper), source: .link(wallpaper)), for: context.window) + case let .slug(slug, settings): + _ = showModalProgress(signal: getWallpaper(network: context.account.network, slug: slug) |> deliverOnMainQueue, for: context.window).start(next: { wallpaper in + showModal(with: WallpaperPreviewController(context, wallpaper: Wallpaper(wallpaper).withUpdatedSettings(settings), source: .link(wallpaper)), for: context.window) + }, error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.wallpaperPreviewDoesntExists) + } + }) + } + afterComplete(true) + case let .stickerPack(_, reference, context, peerId): + showModal(with: StickerPackPreviewModalController(context, peerId: peerId, reference: reference), for: context.window) + afterComplete(true) + case let .confirmPhone(_, context, phone, hash): + _ = showModalProgress(signal: requestCancelAccountResetData(network: context.account.network, hash: hash) |> deliverOnMainQueue, for: context.window).start(next: { data in + showModal(with: cancelResetAccountController(account: context.account, phone: phone, data: data), for: context.window) + }, error: { error in + switch error { + case .limitExceeded: + alert(for: context.window, info: L10n.loginFloodWait) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }) + afterComplete(true) + case let .socks(_, settings, applyProxy): applyProxy(settings) + afterComplete(true) case .nothing: - break + afterComplete(true) + case let .requestSecureId(_, context, value): + if value.nonce.isEmpty { + alert(for: context.window, info: value.isModern ? "nonce is empty" : "payload is empty") + return + } + _ = showModalProgress(signal: (requestSecureIdForm(postbox: context.account.postbox, network: context.account.network, peerId: value.peerId, scope: value.scope, publicKey: value.publicKey) |> mapToSignal { form in + return context.account.postbox.loadedPeerWithId(context.peerId) |> mapError {_ in return .generic} |> map { peer in + return (form, peer) + } + } |> deliverOnMainQueue), for: context.window).start(next: { form, peer in + let passport = PassportWindowController(context: context, peer: peer, request: value, form: form) + passport.show() + }, error: { error in + switch error { + case .serverError(let text): + alert(for: context.window, info: text) + case .generic: + alert(for: context.window, info: "An error occured") + case .versionOutdated: + updateAppAsYouWish(text: L10n.secureIdAppVersionOutdated, updateApp: true) + } + }) + afterComplete(true) + case let .applyLocalization(_, context, value): + _ = showModalProgress(signal: requestLocalizationPreview(network: context.account.network, identifier: value) |> deliverOnMainQueue, for: context.window).start(next: { info in + if appAppearance.language.primaryLanguage.languageCode == info.languageCode { + alert(for: context.window, info: L10n.applyLanguageChangeLanguageAlreadyActive(info.title)) + } else if info.totalStringCount == 0 { + confirm(for: context.window, header: L10n.applyLanguageUnsufficientDataTitle, information: L10n.applyLanguageUnsufficientDataText(info.title), cancelTitle: "", thridTitle: L10n.applyLanguageUnsufficientDataOpenPlatform, successHandler: { result in + switch result { + case .basic: + break + case .thrid: + execute(inapp: inAppLink.external(link: info.platformUrl, false)) + } + }) + } else { + showModal(with: LocalizationPreviewModalController(context, info: info), for: context.window) + } + + }, error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.localizationPreviewErrorGeneric) + } + }) + afterComplete(true) + case let .theme(_, context, name): + _ = showModalProgress(signal: getTheme(account: context.account, slug: name), for: context.window).start(next: { value in + if value.file == nil, let _ = value.settings { + showModal(with: ThemePreviewModalController(context: context, source: .cloudTheme(value)), for: context.window) + } else if value.file == nil { + showEditThemeModalController(context: context, theme: value) + } else { + showModal(with: ThemePreviewModalController(context: context, source: .cloudTheme(value)), for: context.window) + } + }, error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.themeGetThemeError) + case .unsupported: + alert(for: context.window, info: L10n.themeGetThemeError) + case .slugInvalid: + alert(for: context.window, info: L10n.themeGetThemeError) + } + }) + afterComplete(true) + case let .unsupportedScheme(_, context, path): + _ = (getDeepLinkInfo(network: context.account.network, path: path) |> deliverOnMainQueue).start(next: { info in + if let info = info { + updateAppAsYouWish(text: info.message, updateApp: info.updateApp) + } + }) + afterComplete(true) + case let .tonTransfer(_, context, data: data): + if #available(OSX 10.12, *) { +// let _ = combineLatest(queue: .mainQueue(), walletConfiguration(postbox: context.account.postbox), TONKeychain.hasKeys(for: context.account)).start(next: { configuration, hasKeys in +// if let config = configuration.config, let blockchainName = configuration.blockchainName { +// let tonContext = context.tonContext.context(config: config, blockchainName: blockchainName, enableProxy: !configuration.disableProxy) +// if hasKeys { +// let signal = tonContext.storage.getWalletRecords() |> deliverOnMainQueue +// _ = signal.start(next: { wallets in +// if !wallets.isEmpty { +// let amount = data.amount ?? 0 +// let formattedAmount: String +// if amount > 0 { +// formattedAmount = formatBalanceText(amount) +// } else { +// formattedAmount = "" +// } +// let controller = WalletSendController(context: context, tonContext: tonContext, walletInfo: wallets[0].info, recipient: data.address, comment: data.comment ?? "", amount: formattedAmount) +// showModal(with: controller, for: context.window) +// } else { +// confirm(for: context.window, header: L10n.walletTonLinkEmptyTitle, information: L10n.walletTonLinkEmptyText, okTitle: L10n.walletTonLinkEmptyThrid, successHandler: { result in +// switch result { +// case .basic: +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .intro)) +// default: +// break +// } +// }) +// } +// }) +// } else { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .unavailable)) +// } +// } +// }) + } + case .instantView: + afterComplete(true) + case let .settings(_, context, section): + let controller: ViewController + switch section { + case .themes: + controller = AppAppearanceViewController(context: context) + case .devices: + controller = RecentSessionsController(context) + case .folders: + controller = ChatListFiltersListController(context: context) + case .privacy: + controller = PrivacyAndSecurityViewController(context, initialSettings: (nil, nil), focusOnItemTag: .autoArchive) + } + context.sharedContext.bindings.rootNavigation().push(controller) + afterComplete(true) } } -private func escape(with link:String) -> String { - var escaped = link.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) - escaped = escaped?.replacingOccurrences(of: "%24", with: "$") - escaped = escaped?.replacingOccurrences(of: "%26", with: "&") - escaped = escaped?.replacingOccurrences(of: "%2B", with: "+") - escaped = escaped?.replacingOccurrences(of: "%2C", with: ",") - escaped = escaped?.replacingOccurrences(of: "%2F", with: "/") - escaped = escaped?.replacingOccurrences(of: "%3A", with: ":") - escaped = escaped?.replacingOccurrences(of: "%3B", with: ";") - escaped = escaped?.replacingOccurrences(of: "%3D", with: "=") - escaped = escaped?.replacingOccurrences(of: "%3F", with: "?") - escaped = escaped?.replacingOccurrences(of: "%40", with: "@") - escaped = escaped?.replacingOccurrences(of: "%20", with: " ") - escaped = escaped?.replacingOccurrences(of: "%09", with: "\t") - escaped = escaped?.replacingOccurrences(of: "%23", with: "#") - escaped = escaped?.replacingOccurrences(of: "%3C", with: "<") - escaped = escaped?.replacingOccurrences(of: "%3E", with: ">") - escaped = escaped?.replacingOccurrences(of: "%22", with: "\"") - escaped = escaped?.replacingOccurrences(of: "%0A", with: "\n") - escaped = escaped?.replacingOccurrences(of: "%25", with: "%") - escaped = escaped?.replacingOccurrences(of: "%2E", with: ".") - return escaped ?? link +private func updateAppAsYouWish(text: String, updateApp: Bool) { + // + confirm(for: mainWindow, header: appName, information: text, okTitle: updateApp ? L10n.alertButtonOKUpdateApp : L10n.modalOK, cancelTitle: updateApp ? L10n.modalCancel : "", thridTitle: nil, successHandler: { _ in + if updateApp { + #if APP_STORE + execute(inapp: inAppLink.external(link: "https://apps.apple.com/us/app/telegram/id747648890", false)) + #else + (NSApp.delegate as? AppDelegate)?.checkForUpdates(updateApp) + #endif + } + }) } -private func urlVars(with url:String) -> [String:String] { +func escape(with link:String, addPercent: Bool = true) -> String { + var escaped = addPercent ? link.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? link : link + escaped = escaped.replacingOccurrences(of: "%21", with: "!") + escaped = escaped.replacingOccurrences(of: "%24", with: "$") + escaped = escaped.replacingOccurrences(of: "%26", with: "&") + escaped = escaped.replacingOccurrences(of: "%2B", with: "+") + escaped = escaped.replacingOccurrences(of: "%2C", with: ",") + escaped = escaped.replacingOccurrences(of: "%2F", with: "/") + escaped = escaped.replacingOccurrences(of: "%3A", with: ":") + escaped = escaped.replacingOccurrences(of: "%3B", with: ";") + escaped = escaped.replacingOccurrences(of: "%3D", with: "=") + escaped = escaped.replacingOccurrences(of: "%3F", with: "?") + escaped = escaped.replacingOccurrences(of: "%40", with: "@") + escaped = escaped.replacingOccurrences(of: "%20", with: " ") + escaped = escaped.replacingOccurrences(of: "%09", with: "\t") + escaped = escaped.replacingOccurrences(of: "%23", with: "#") + escaped = escaped.replacingOccurrences(of: "%3C", with: "<") + escaped = escaped.replacingOccurrences(of: "%3E", with: ">") + escaped = escaped.replacingOccurrences(of: "%22", with: "\"") + escaped = escaped.replacingOccurrences(of: "%0A", with: "\n") + escaped = escaped.replacingOccurrences(of: "%25", with: "%") + escaped = escaped.replacingOccurrences(of: "%2E", with: ".") + escaped = escaped.replacingOccurrences(of: "%2C", with: ",") + escaped = escaped.replacingOccurrences(of: "%7D", with: "}") + escaped = escaped.replacingOccurrences(of: "%7B", with: "{") + escaped = escaped.replacingOccurrences(of: "%5B", with: "[") + escaped = escaped.replacingOccurrences(of: "%5D", with: "]") + return escaped +} + + +func urlVars(with url:String) -> [String:String] { var vars:[String:String] = [:] let range = url.nsstring.range(of: "?") let ns:NSString = range.location != NSNotFound ? url.nsstring.substring(from: range.location + 1).nsstring : url.nsstring @@ -201,34 +666,111 @@ private func urlVars(with url:String) -> [String:String] { return vars } -private func isValidEmail(_ checkString:String) -> Bool { - let emailRegex = ".+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2}[A-Za-z]*" - let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegex) - return emailTest.evaluate(with: checkString) + +enum SecureIdPermission : String { + case identity = "identity" + case address = "address" + case email = "email" + case phone = "phone" +} + +struct inAppSecureIdRequest { + let peerId: PeerId + let scope: String + let callback: String? + let publicKey: String + let nonce: Data + let isModern: Bool } + +enum WallpaperPreview { + case color(NSColor) + case slug(String, WallpaperSettings) + case gradient(NSColor, NSColor, Int32?) +} + enum inAppLink { case external(link:String, Bool) // link, confirm - case peerInfo(peerId:PeerId, action:ChatInitialAction?, openChat:Bool, postId:Int32?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) - case followResolvedName(username:String, postId:Int32?, account:Account, action:ChatInitialAction?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) - case inviteBotToGroup(username:String, account:Account, action:ChatInitialAction?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) + case peerInfo(link: String, peerId:PeerId, action:ChatInitialAction?, openChat:Bool, postId:Int32?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) + case followResolvedName(link: String, username:String, postId:Int32?, context: AccountContext, action:ChatInitialAction?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) + case inviteBotToGroup(link: String, username:String, context: AccountContext, action:ChatInitialAction?, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) case botCommand(String, (String)->Void) case callback(String, (String)->Void) + case code(String, (String)->Void) case hashtag(String, (String)->Void) - case shareUrl(Account, String) - case joinchat(String, account:Account, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) + case shareUrl(link: String, AccountContext, String) + case joinchat(link: String, String, context: AccountContext, callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void) case logout(()->Void) - case stickerPack(StickerPackReference, account:Account, peerId:PeerId?) + case stickerPack(link: String, StickerPackReference, context: AccountContext, peerId:PeerId?) + case confirmPhone(link: String, context: AccountContext, phone: String, hash: String) case nothing - case socks(ProxySettings, applyProxy:(ProxySettings)->Void) + case socks(link: String, ProxyServerSettings, applyProxy:(ProxyServerSettings)->Void) + case requestSecureId(link: String, context: AccountContext, value: inAppSecureIdRequest) + case unsupportedScheme(link: String, context: AccountContext, path: String) + case applyLocalization(link: String, context: AccountContext, value: String) + case wallpaper(link: String, context: AccountContext, preview: WallpaperPreview) + case theme(link: String, context: AccountContext, name: String) + case tonTransfer(link: String, context: AccountContext, data: ParsedWalletUrl) + case instantView(link: String, webpage: TelegramMediaWebpage, anchor: String?) + case settings(link: String, context: AccountContext, section: InAppSettingsSection) + var link: String { + switch self { + case let .external(link,_): + if link.hasPrefix("mailto:") { + return link.replacingOccurrences(of: "mailto:", with: "") + } + return link + case let .peerInfo(values): + return values.link + case let .followResolvedName(values): + return values.link + case let .inviteBotToGroup(values): + return values.link + case let .botCommand(link, _), let .callback(link, _), let .code(link, _), let .hashtag(link, _): + return link + case let .shareUrl(values): + return values.link + case let .joinchat(values): + return values.link + case let .stickerPack(values): + return values.link + case let .confirmPhone(values): + return values.link + case let .socks(values): + return values.link + case let .requestSecureId(values): + return values.link + case let .unsupportedScheme(values): + return values.link + case let .applyLocalization(values): + return values.link + case let .wallpaper(values): + return values.link + case let .theme(values): + return values.link + case let .tonTransfer(link, _, _): + return link + case let .instantView(link, _, _): + return link + case let .settings(link, _, _): + return link + case .nothing: + return "" + case .logout: + return "" + } + } } let telegram_me:[String] = ["telegram.me/","telegram.dog/","t.me/"] -let actions_me:[String] = ["joinchat/","addstickers/","confirmphone?","socks?"] +let actions_me:[String] = ["joinchat/","addstickers/","confirmphone","socks", "proxy", "setlanguage", "bg", "addtheme/"] let telegram_scheme:String = "tg://" -let known_scheme:[String] = ["resolve?domain=","msg_url?url=","join?invite=","addstickers?set=","confirmphone", "socks?"] +let known_scheme:[String] = ["resolve","msg_url","join","addstickers","confirmphone", "socks", "proxy", "passport", "setlanguage", "bg", "privatepost", "addtheme", "settings"] + +let ton_scheme:String = "ton://" private let keyURLUsername = "domain"; private let keyURLPostId = "post"; @@ -238,36 +780,137 @@ private let keyURLSet = "set"; private let keyURLText = "text"; private let keyURLStart = "start"; private let keyURLStartGroup = "startgroup"; +private let keyURLSecret = "secret"; +private let keyURLPhone = "phone"; +private let keyURLHash = "hash"; private let keyURLHost = "server"; private let keyURLPort = "port"; private let keyURLUser = "user"; private let keyURLPass = "pass"; -func inApp(for url:NSString, account:Account, peerId:PeerId? = nil, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil, command:((String)->Void)? = nil, applyProxy:((ProxySettings) -> Void)? = nil, confirm: Bool = false) -> inAppLink { +let legacyPassportUsername = "telegrampassport" + +func inApp(for url:NSString, context: AccountContext? = nil, peerId:PeerId? = nil, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil, command:((String)->Void)? = nil, applyProxy:((ProxyServerSettings) -> Void)? = nil, confirm: Bool = false) -> inAppLink { let external = url + let urlString = external as String let url = url.lowercased.nsstring + + + for domain in telegram_me { let range = url.range(of: domain) - if range.location != NSNotFound && (range.location == 0 || url.substring(from: range.location - 1).hasPrefix("/")) { + if range.location != NSNotFound && (range.location == 0 || (range.location <= 8 && url.substring(from: range.location - 1).hasPrefix("/"))) { let string = external.substring(from: range.location + range.length) for action in actions_me { if string.hasPrefix(action) { - let value = string.substring(from: string.index(string.startIndex, offsetBy: action.length)) + let value = String(string[string.index(string.startIndex, offsetBy: action.length) ..< string.endIndex]) switch action { case actions_me[0]: - if let openInfo = openInfo { - return .joinchat(value, account: account, callback: openInfo) + if let openInfo = openInfo, let context = context { + return .joinchat(link: urlString, value, context: context, callback: openInfo) } case actions_me[1]: - return .stickerPack(StickerPackReference.name(value), account: account, peerId: peerId) + if let context = context { + return .stickerPack(link: urlString, StickerPackReference.name(value), context: context, peerId: peerId) + } + case actions_me[2]: + let vars = urlVars(with: string) + if let context = context, let phone = vars[keyURLPhone], let hash = vars[keyURLHash] { + return .confirmPhone(link: urlString, context: context, phone: phone, hash: hash) + } case actions_me[3]: let vars = urlVars(with: string) if let applyProxy = applyProxy, let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort) { let server = escape(with: server) - return .socks(ProxySettings(host: server, port: port, username: vars[keyURLUser], password: vars[keyURLPass]), applyProxy: applyProxy) + let username = vars[keyURLUser] != nil ? escape(with: vars[keyURLUser]!) : nil + let pass = vars[keyURLPass] != nil ? escape(with: vars[keyURLPass]!) : nil + return .socks(link: urlString, ProxyServerSettings(host: server, port: port, connection: .socks5(username: username, password: pass)), applyProxy: applyProxy) } + case actions_me[4]: + let vars = urlVars(with: string) + if let applyProxy = applyProxy, let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort), let rawSecret = vars[keyURLSecret] { + let server = escape(with: server) + if let secret = MTProxySecret.parse(rawSecret)?.serialize() { + return .socks(link: urlString, ProxyServerSettings(host: server, port: port, connection: .mtp(secret: secret)), applyProxy: applyProxy) + } + } + case actions_me[5]: + if let context = context, !value.isEmpty { + return .applyLocalization(link: urlString, context: context, value: String(value[value.index(after: value.startIndex) ..< value.endIndex])) + } else { + + } + case actions_me[6]: + if !value.isEmpty { + let component = String(value[value.index(after: value.startIndex) ..< value.endIndex]) + if let context = context { + if component.count == 6, component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil, let color = NSColor(hexString: "#\(component)") { + return .wallpaper(link: urlString, context: context, preview: .color(color)) + } else { + + let vars = urlVars(with: value) + + var rotation:Int32? = vars["rotation"] != nil ? Int32(vars["rotation"]!) : nil + + if let r = rotation { + let available:[Int32] = [0, 45, 90, 135, 180, 225, 270, 310] + if !available.contains(r) { + rotation = nil + } + } + + let components = component.components(separatedBy: "?").first?.components(separatedBy: "-") ?? [] + if components.count == 2, let topColor = NSColor(hexString: "#\(components[0])"), let bottomColor = NSColor(hexString: "#\(components[1])") { + return .wallpaper(link: urlString, context: context, preview: .gradient(topColor, bottomColor, rotation)) + } + + var blur: Bool = false + var intensity: Int32? = 80 + var color: UInt32? = nil + var bottomColor: UInt32? = nil + + if let bgcolor = vars["bg_color"], !bgcolor.isEmpty { + let components = bgcolor.components(separatedBy: "-") + if components.count == 2 { + if let rgb = NSColor(hexString: "#\(components[0])")?.argb { + color = rgb + } + if let rgb = NSColor(hexString: "#\(components[1])")?.argb { + bottomColor = rgb + } + } else if components.count == 1 { + if let rgb = NSColor(hexString: "#\(components[0])")?.argb { + color = rgb + } + } + } + if let intensityString = vars["intensity"] { + intensity = Int32(intensityString) + } + if let mode = vars["mode"] { + blur = mode.contains("blur") + } + + let settings: WallpaperSettings = WallpaperSettings(blur: blur, motion: false, color: color, bottomColor: bottomColor, intensity: intensity, rotation: rotation) + + var slug = component + if let index = component.range(of: "?") { + slug = String(component[component.startIndex ..< index.lowerBound]) + } + + return .wallpaper(link: urlString, context: context, preview: .slug(slug, settings)) + } + } + } + return .external(link: url as String, false) + case actions_me[7]: + let userAndPost = string.components(separatedBy: "/") + if userAndPost.count == 2, let context = context { + return .theme(link: urlString, context: context, name: userAndPost[1]) + } + return .external(link: url as String, false) default: break } @@ -285,8 +928,8 @@ func inApp(for url:NSString, account:Account, peerId:PeerId? = nil, openInfo:((P action = .start(parameter: value, behavior: .none) break loop; case keyURLStartGroup: - if let openInfo = openInfo { - return .inviteBotToGroup(username: username, account: account, action: .start(parameter: value, behavior: .none), callback: openInfo) + if let openInfo = openInfo, let context = context { + return .inviteBotToGroup(link: urlString, username: username, context: context, action: .start(parameter: value, behavior: .automatic), callback: openInfo) } break loop; default: @@ -296,29 +939,49 @@ func inApp(for url:NSString, account:Account, peerId:PeerId? = nil, openInfo:((P } if let openInfo = openInfo { - if username == "iv" { - return .external(link: url as String, false) - } else { - return .followResolvedName(username: username, postId: nil, account: account, action: action, callback: openInfo) + if username == "iv" || username.isEmpty { + return .external(link: url as String, username.isEmpty) + } else if let context = context { + return .followResolvedName(link: urlString, username: username, postId: nil, context: context, action: action, callback: openInfo) } } } else if let openInfo = openInfo { let userAndPost = string.components(separatedBy: "/") if userAndPost.count >= 2 { let name = userAndPost[0] - let post = userAndPost[1].isEmpty ? nil : userAndPost[1].nsstring.intValue - if name.hasPrefix("iv?") { + + if name == "c" { + if let context = context { + let post = userAndPost.count >= 3 ? (userAndPost[2].isEmpty ? nil : Int32(userAndPost[2])) : nil + return .followResolvedName(link: urlString, username: "_private_\(userAndPost[1])", postId: post, context: context, action:nil, callback: openInfo) + } + } else if name == "s" { return .external(link: url as String, false) + } else if name == "addtheme" { + if let context = context { + return .theme(link: url as String, context: context, name: userAndPost[1]) + } } else { - return .followResolvedName(username: name, postId: post, account: account, action:nil, callback: openInfo) + let post = userAndPost[1].isEmpty ? nil : Int32(userAndPost[1])//.intValue + if name.hasPrefix("iv?") { + return .external(link: url as String, false) + } else if name.hasPrefix("share?") || name == "share" { + let params = urlVars(with: url as String) + if let url = params["url"], let context = context { + return .shareUrl(link: urlString, context, url) + } + return .external(link: url as String, false) + } else if let context = context { + return .followResolvedName(link: urlString, username: name, postId: post, context: context, action:nil, callback: openInfo) + } } } } } } - if url.hasPrefix("@"), let openInfo = openInfo { - return .followResolvedName(username: url.substring(from: 1), postId: nil, account: account, action:nil, callback: openInfo) + if url.hasPrefix("@"), let openInfo = openInfo, let context = context { + return .followResolvedName(link: urlString, username: url.substring(from: 1), postId: nil, context: context, action:nil, callback: openInfo) } if url.hasPrefix("/"), let command = command { @@ -347,50 +1010,211 @@ func inApp(for url:NSString, account:Account, peerId:PeerId? = nil, openInfo:((P case keyURLStart: action = .start(parameter: value, behavior: .none) break loop; + case keyURLStartGroup: + if let context = context { + return .inviteBotToGroup(link: urlString, username: username, context: context, action: .start(parameter: value, behavior: .none), callback: openInfo) + } default: break } } - return .followResolvedName(username: username, postId: post, account: account, action: action, callback:openInfo) + if username == legacyPassportUsername { + return inApp(for: external.replacingOccurrences(of: "tg://resolve", with: "tg://passport").nsstring, context: context, peerId: peerId, openInfo: openInfo, hashtag: hashtag, command: command, applyProxy: applyProxy, confirm: confirm) + //return inapp + } else if username == "addtheme", let context = context { + return .theme(link: urlString, context: context, name:"") + } else if let context = context { + return .followResolvedName(link: urlString, username: username, postId: post, context: context, action: action, callback:openInfo) + } } case known_scheme[1]: if let url = vars[keyURLUrl] { let url = url.nsstring.replacingOccurrences(of: "+", with: " ").removingPercentEncoding let text = vars[keyURLText]?.replacingOccurrences(of: "+", with: " ").removingPercentEncoding - if let url = url { + if let url = url, let context = context { var applied = url if let text = text { applied += "\n" + text } - return .shareUrl(account, applied) + return .shareUrl(link: urlString, context, applied) } } case known_scheme[2]: - if let invite = vars[keyURLInvite], let openInfo = openInfo { - return .joinchat(invite, account: account, callback: openInfo) + if let invite = vars[keyURLInvite], let openInfo = openInfo, let context = context { + return .joinchat(link: urlString, invite, context: context, callback: openInfo) } case known_scheme[3]: - if let set = vars[keyURLSet] { - return .stickerPack(.name(set), account:account, peerId:nil) + if let set = vars[keyURLSet], let context = context { + return .stickerPack(link: urlString, .name(set), context: context, peerId:nil) + } + case known_scheme[4]: + if let context = context, let phone = vars[keyURLPhone], let hash = vars[keyURLHash] { + return .confirmPhone(link: urlString, context: context, phone: phone, hash: hash) } case known_scheme[5]: if let applyProxy = applyProxy, let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort) { let server = escape(with: server) - return .socks(ProxySettings(host: server, port: port, username: vars[keyURLUser], password: vars[keyURLPass]), applyProxy: applyProxy) + return .socks(link: urlString, ProxyServerSettings(host: server, port: port, connection: .socks5(username: vars[keyURLUser], password: vars[keyURLPass])), applyProxy: applyProxy) + } + case known_scheme[6]: + if let applyProxy = applyProxy, let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort), let rawSecret = vars[keyURLSecret] { + let server = escape(with: server) + if let secret = MTProxySecret.parse(rawSecret)?.serialize() { + return .socks(link: urlString, ProxyServerSettings(host: server, port: port, connection: .mtp(secret: secret)), applyProxy: applyProxy) + } + } + case known_scheme[7]: + if let scope = vars["scope"], let publicKey = vars["public_key"], let rawBotId = vars["bot_id"], let botId = Int32(rawBotId), let context = context { + + + let scope = escape(with: scope, addPercent: false) + + + let isModern: Bool = scope.hasPrefix("{") + + let nonceString = (isModern ? vars["nonce"] : vars["payload"]) ?? "" + + let nonce = escape(with: nonceString, addPercent: false).data(using: .utf8) ?? Data() + + let callbackUrl = vars["callback_url"] != nil ? escape(with: vars["callback_url"]!, addPercent: false) : nil + return .requestSecureId(link: urlString, context: context, value: inAppSecureIdRequest(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, callback: callbackUrl, publicKey: escape(with: publicKey, addPercent: false), nonce: nonce, isModern: isModern)) + } + case known_scheme[8]: + if let context = context, let value = vars["lang"] { + return .applyLocalization(link: urlString, context: context, value: value) + } + case known_scheme[9]: + if let context = context, let value = vars["slug"] { + + var blur: Bool = false + var intensity: Int32? = 80 + var color: UInt32? = nil + var bottomColor: UInt32? = nil + + var rotation:Int32? = vars["rotation"] != nil ? Int32(vars["rotation"]!) : nil + + if let r = rotation { + let available:[Int32] = [0, 45, 90, 135, 180, 225, 270, 310] + if !available.contains(r) { + rotation = nil + } + } + + + if let bgcolor = vars["bg_color"], !bgcolor.isEmpty { + let components = bgcolor.components(separatedBy: "-") + if components.count == 2 { + if let rgb = NSColor(hexString: "#\(components[0])")?.argb { + color = rgb + } + if let rgb = NSColor(hexString: "#\(components[1])")?.argb { + bottomColor = rgb + } + } else if components.count == 1 { + if let rgb = NSColor(hexString: "#\(components[0])")?.argb { + color = rgb + } + } + } + if let mode = vars["mode"] { + blur = mode.contains("blur") + } + if let intensityString = vars["intensity"] { + intensity = Int32(intensityString) + } + + let settings: WallpaperSettings = WallpaperSettings(blur: blur, motion: false, color: color, bottomColor: bottomColor, intensity: intensity, rotation: rotation) + + return .wallpaper(link: urlString, context: context, preview: .slug(value, settings)) + } + if let context = context, let value = vars["color"] { + return .wallpaper(link: urlString, context: context, preview: .slug(value, WallpaperSettings())) + } else if let context = context, let component = vars["gradient"] { + + var rotation:Int32? = vars["rotation"] != nil ? Int32(vars["rotation"]!) : nil + + if let r = rotation { + let available:[Int32] = [0, 45, 90, 135, 180, 225, 270, 310] + if !available.contains(r) { + rotation = nil + } + } + + let components = component.components(separatedBy: "?").first?.components(separatedBy: "-") ?? [] + if components.count == 2, let topColor = NSColor(hexString: "#\(components[0])"), let bottomColor = NSColor(hexString: "#\(components[1])") { + return .wallpaper(link: urlString, context: context, preview: .gradient(topColor, bottomColor, rotation)) + } + } + case known_scheme[10]: + if let username = vars["channel"], let openInfo = openInfo { + let post = vars[keyURLPostId]?.nsstring.intValue + if let context = context { + return .followResolvedName(link: urlString, username: "_private_\(username)", postId: post, context: context, action:nil, callback: openInfo) + } + } + case known_scheme[11]: + if let context = context, let value = vars["slug"] { + return .theme(link: urlString, context: context, name: value) + } + case known_scheme[12]: + if let context = context, let range = action.range(of: known_scheme[12] + "/") { + let section = String(action[range.upperBound...]) + if let section = InAppSettingsSection(rawValue: section) { + return .settings(link: urlString, context: context, section: section) + } } default: break } - + return .nothing } } + if let context = context { + var path = url.substring(from: telegram_scheme.length) + let qLocation = path.nsstring.range(of: "?").location + path = path.nsstring.substring(to: qLocation != NSNotFound ? qLocation : path.length) + return .unsupportedScheme(link: urlString, context: context, path: path) + } + } else if url.hasPrefix(ton_scheme), let context = context { + return .external(link: url as String, false) +// let action = url.substring(from: ton_scheme.length) +// if action.hasPrefix("transfer/") { +// let vars = urlVars(with: url as String) +// let preAddressLength = ton_scheme.length + "transfer/".length + walletAddressLength +// let address = urlString.prefix(preAddressLength) +// if address.length == preAddressLength { +// let address = String(address.suffix(walletAddressLength)) +// var amount: Int64? = nil +// var comment: String? = nil +// if let varAmount = vars["amount"], !varAmount.isEmpty, let intAmount = Int64(varAmount) { +// amount = intAmount +// } +// if let varComment = vars["text"], !varComment.isEmpty { +// comment = escape(with: varComment, addPercent: false) +// } +// return .tonTransfer(link: urlString, context: context, data: ParsedWalletUrl(address: address, amount: amount, comment: comment)) +// } +// } + return .nothing } - return .external(link: external as String, confirm) + return .external(link: urlString as String, confirm) +} + +func addUrlParameter(value: String, to url: String) -> String { + if let _ = url.range(of: "?") { + return url + "&" + value + } else { + if url.hasSuffix("/") { + return url + value + } else { + return url + "/" + value + } + } } func makeInAppLink(with action:String, params:[String:Any]) -> String { @@ -407,20 +1231,96 @@ func makeInAppLink(with action:String, params:[String:Any]) -> String { return link } -func proxySettings(from url:String) -> (ProxySettings?, Bool) { +func proxySettings(from url:String) -> (ProxyServerSettings?, Bool) { let url = url.nsstring - if url.hasPrefix(telegram_scheme) { + if url.hasPrefix(telegram_scheme), let _ = URL(string: url as String) { let action = url.substring(from: telegram_scheme.length) let vars = urlVars(with: url as String) if action.hasPrefix("socks") { if let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort) { let server = escape(with: server) - return (ProxySettings(host: server, port: port, username: vars[keyURLUser], password: vars[keyURLPass]), true) + return (ProxyServerSettings(host: server, port: port, connection: .socks5(username: vars[keyURLUser], password: vars[keyURLPass])), true) } return (nil , true) + } else if action.hasPrefix("proxy") { + if let server = vars[keyURLHost], let maybePort = vars[keyURLPort], let port = Int32(maybePort), let rawSecret = vars[keyURLSecret] { + let server = escape(with: server) + if let secret = MTProxySecret.parse(rawSecret)?.serialize() { + return (ProxyServerSettings(host: server, port: port, connection: .mtp(secret: secret)), true) + } + } } + } else if let _ = URL(string: url as String) { + let link = inApp(for: url, applyProxy: {_ in}) + switch link { + case let .socks(_, settings, _): + return (settings, true) + default: + break + } } return (nil, false) } + +public struct ParsedWalletUrl { + public let address: String + public let amount: Int64? + public let comment: String? +} + +// +//public func parseWalletUrl(_ url: URL) -> ParsedWalletUrl? { +// guard url.scheme == "ton" && url.host == "transfer" else { +// return nil +// } +// var address: String? +// let path = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) +// if isValidAddress(path) { +// address = path +// } +// var amount: Int64? +// var comment: String? +// if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems { +// for queryItem in queryItems { +// if let value = queryItem.value { +// if queryItem.name == "amount", !value.isEmpty, let amountValue = Int64(value) { +// amount = amountValue +// } else if queryItem.name == "text", !value.isEmpty { +// comment = value +// } +// } +// } +// } +// return address.flatMap { ParsedWalletUrl(address: $0, amount: amount, comment: comment) } +//} + + + +func resolveInstantViewUrl(account: Account, url: String) -> Signal { + return webpagePreview(account: account, url: url) + |> mapToSignal { webpage -> Signal in + if let webpage = webpage { + + if case let .Loaded(content) = webpage.content { + if content.instantPage != nil { + var anchorValue: String? + if let anchorRange = url.range(of: "#") { + let anchor = url[anchorRange.upperBound...] + if !anchor.isEmpty { + anchorValue = String(anchor) + } + } + return .single(.instantView(link: url, webpage: webpage, anchor: anchorValue)) + } else { + return .single(.external(link: url, false)) + } + } else { + return .complete() + } + } else { + return .single(.external(link: url, false)) + } + } +} diff --git a/Telegram-Mac/InAppNotificationSettings.swift b/Telegram-Mac/InAppNotificationSettings.swift index 5585cb2f40..9dee401d42 100644 --- a/Telegram-Mac/InAppNotificationSettings.swift +++ b/Telegram-Mac/InAppNotificationSettings.swift @@ -6,25 +6,71 @@ // Copyright © 2017 Telegram. All rights reserved. // -import Cocoa -import PostboxMac -import SwiftSignalKitMac + +public enum TotalUnreadCountDisplayStyle: Int32 { + case filtered = 0 + case raw = 1 + + var category: ChatListTotalUnreadStateCategory { + switch self { + case .filtered: + return .filtered + case .raw: + return .raw + } + } +} + +public enum TotalUnreadCountDisplayCategory: Int32 { + case chats = 0 + case messages = 1 + + var statsType: ChatListTotalUnreadStateStats { + switch self { + case .chats: + return .chats + case .messages: + return .messages + } + } +} + +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore +import SyncCore + struct InAppNotificationSettings: PreferencesEntry, Equatable { let enabled: Bool let playSounds: Bool let tone: String let displayPreviews: Bool let muteUntil: Int32 + let notifyAllAccounts: Bool + let totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle + let totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory + let totalUnreadCountIncludeTags: PeerSummaryCounterTags + let showNotificationsOutOfFocus: Bool + let badgeEnabled: Bool + let requestUserAttention: Bool static var defaultSettings: InAppNotificationSettings { - return InAppNotificationSettings(enabled: true, playSounds: true, tone: "Default", displayPreviews: true, muteUntil: 0) + return InAppNotificationSettings(enabled: true, playSounds: true, tone: "Default", displayPreviews: true, muteUntil: 0, totalUnreadCountDisplayStyle: .filtered, totalUnreadCountDisplayCategory: .chats, totalUnreadCountIncludeTags: .all, notifyAllAccounts: true, showNotificationsOutOfFocus: true, badgeEnabled: true, requestUserAttention: false) } - init(enabled:Bool, playSounds: Bool, tone: String, displayPreviews: Bool, muteUntil: Int32) { + init(enabled:Bool, playSounds: Bool, tone: String, displayPreviews: Bool, muteUntil: Int32, totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: PeerSummaryCounterTags, notifyAllAccounts: Bool, showNotificationsOutOfFocus: Bool, badgeEnabled: Bool, requestUserAttention: Bool) { self.enabled = enabled self.playSounds = playSounds self.tone = tone self.displayPreviews = displayPreviews self.muteUntil = muteUntil + self.notifyAllAccounts = notifyAllAccounts + self.totalUnreadCountDisplayStyle = totalUnreadCountDisplayStyle + self.totalUnreadCountDisplayCategory = totalUnreadCountDisplayCategory + self.totalUnreadCountIncludeTags = totalUnreadCountIncludeTags + self.showNotificationsOutOfFocus = showNotificationsOutOfFocus + self.badgeEnabled = badgeEnabled + self.requestUserAttention = requestUserAttention } init(decoder: PostboxDecoder) { @@ -33,6 +79,33 @@ struct InAppNotificationSettings: PreferencesEntry, Equatable { self.tone = decoder.decodeStringForKey("t", orElse: "") self.displayPreviews = decoder.decodeInt32ForKey("p", orElse: 0) != 0 self.muteUntil = decoder.decodeInt32ForKey("m2", orElse: 0) + self.notifyAllAccounts = decoder.decodeBoolForKey("naa", orElse: true) + self.totalUnreadCountDisplayStyle = TotalUnreadCountDisplayStyle(rawValue: decoder.decodeInt32ForKey("tds", orElse: 1)) ?? .filtered + self.totalUnreadCountDisplayCategory = TotalUnreadCountDisplayCategory(rawValue: decoder.decodeInt32ForKey("totalUnreadCountDisplayCategory", orElse: 1)) ?? .chats + if let value = decoder.decodeOptionalInt32ForKey("totalUnreadCountIncludeTags_2") { + self.totalUnreadCountIncludeTags = PeerSummaryCounterTags(rawValue: value) + } else if let value = decoder.decodeOptionalInt32ForKey("totalUnreadCountIncludeTags") { + var resultTags: PeerSummaryCounterTags = [] + for legacyTag in LegacyPeerSummaryCounterTags(rawValue: value) { + if legacyTag == .regularChatsAndPrivateGroups { + resultTags.insert(.contact) + resultTags.insert(.nonContact) + resultTags.insert(.bot) + resultTags.insert(.group) + } else if legacyTag == .publicGroups { + resultTags.insert(.group) + } else if legacyTag == .channels { + resultTags.insert(.channel) + } + } + self.totalUnreadCountIncludeTags = resultTags + } else { + self.totalUnreadCountIncludeTags = .all + } + + self.showNotificationsOutOfFocus = decoder.decodeInt32ForKey("snoof", orElse: 1) != 0 + self.badgeEnabled = decoder.decodeBoolForKey("badge", orElse: true) + self.requestUserAttention = decoder.decodeBoolForKey("requestUserAttention", orElse: false) } func encode(_ encoder: PostboxEncoder) { @@ -41,28 +114,61 @@ struct InAppNotificationSettings: PreferencesEntry, Equatable { encoder.encodeString(self.tone, forKey: "t") encoder.encodeInt32(self.displayPreviews ? 1 : 0, forKey: "p") encoder.encodeInt32(self.muteUntil, forKey: "m2") + encoder.encodeBool(self.notifyAllAccounts, forKey: "naa") + encoder.encodeInt32(self.totalUnreadCountDisplayStyle.rawValue, forKey: "tds") + encoder.encodeInt32(self.totalUnreadCountDisplayCategory.rawValue, forKey: "totalUnreadCountDisplayCategory") + encoder.encodeInt32(self.totalUnreadCountIncludeTags.rawValue, forKey: "totalUnreadCountIncludeTags_2") + encoder.encodeInt32(self.showNotificationsOutOfFocus ? 1 : 0, forKey: "snoof") + encoder.encodeBool(self.badgeEnabled, forKey: "badge") + encoder.encodeBool(self.requestUserAttention, forKey: "requestUserAttention") } func withUpdatedEnables(_ enabled: Bool) -> InAppNotificationSettings { - return InAppNotificationSettings(enabled: enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil) + return InAppNotificationSettings(enabled: enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) } func withUpdatedPlaySounds(_ playSounds: Bool) -> InAppNotificationSettings { - return InAppNotificationSettings(enabled: self.enabled, playSounds: playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil) + return InAppNotificationSettings(enabled: self.enabled, playSounds: playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) } func withUpdatedTone(_ tone: String) -> InAppNotificationSettings { - return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil) + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) } func withUpdatedDisplayPreviews(_ displayPreviews: Bool) -> InAppNotificationSettings { - return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: displayPreviews, muteUntil: self.muteUntil) + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: displayPreviews, muteUntil: self.muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) } func withUpdatedMuteUntil(_ muteUntil: Int32) -> InAppNotificationSettings { - return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil) + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) + } + + func withUpdatedTotalUnreadCountDisplayCategory(_ totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) } + func withUpdatedTotalUnreadCountDisplayStyle(_ totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) + } + + func withUpdatedNotifyAllAccounts(_ notifyAllAccounts: Bool) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: self.totalUnreadCountIncludeTags, notifyAllAccounts: notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) + } + + func withUpdatedTotalUnreadCountIncludeTags(_ totalUnreadCountIncludeTags: PeerSummaryCounterTags) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) + } + + func withUpdatedSnoof(_ showNotificationsOutOfFocus: Bool) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: self.requestUserAttention) + } + + func withUpdatedBadgeEnabled(_ badgeEnabled: Bool) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: badgeEnabled, requestUserAttention: self.requestUserAttention) + } + func withUpdatedRequestUserAttention(_ requestUserAttention: Bool) -> InAppNotificationSettings { + return InAppNotificationSettings(enabled: self.enabled, playSounds: self.playSounds, tone: self.tone, displayPreviews: self.displayPreviews, muteUntil: self.muteUntil, totalUnreadCountDisplayStyle: self.totalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: self.totalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: totalUnreadCountIncludeTags, notifyAllAccounts: self.notifyAllAccounts, showNotificationsOutOfFocus: self.showNotificationsOutOfFocus, badgeEnabled: self.badgeEnabled, requestUserAttention: requestUserAttention) + } func isEqual(to: PreferencesEntry) -> Bool { if let to = to as? InAppNotificationSettings { return self == to @@ -70,30 +176,12 @@ struct InAppNotificationSettings: PreferencesEntry, Equatable { return false } } - - static func ==(lhs: InAppNotificationSettings, rhs: InAppNotificationSettings) -> Bool { - if lhs.enabled != rhs.enabled { - return false - } - if lhs.playSounds != rhs.playSounds { - return false - } - if lhs.tone != rhs.tone { - return false - } - if lhs.displayPreviews != rhs.displayPreviews { - return false - } - if lhs.muteUntil != rhs.muteUntil { - return false - } - return true - } } -func updateInAppNotificationSettingsInteractively(postbox: Postbox, _ f: @escaping (InAppNotificationSettings) -> InAppNotificationSettings) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.inAppNotificationSettings, { entry in +func updateInAppNotificationSettingsInteractively(accountManager: AccountManager, _ f: @escaping (InAppNotificationSettings) -> InAppNotificationSettings) -> Signal { + + return accountManager.transaction { transaction in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.inAppNotificationSettings, { entry in let currentSettings: InAppNotificationSettings if let entry = entry as? InAppNotificationSettings { currentSettings = entry @@ -105,8 +193,19 @@ func updateInAppNotificationSettingsInteractively(postbox: Postbox, _ f: @escapi } } -func appNotificationSettings(postbox: Postbox) -> Signal { - return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.inAppNotificationSettings]) |> map { preferences in - return (preferences.values[ApplicationSpecificPreferencesKeys.inAppNotificationSettings] as? InAppNotificationSettings) ?? InAppNotificationSettings.defaultSettings +func appNotificationSettings(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.inAppNotificationSettings]) |> map { view in + return view.entries[ApplicationSharedPreferencesKeys.inAppNotificationSettings] as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings + } +} +func globalNotificationSettings(postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) |> map { view in + let viewSettings: GlobalNotificationSettingsSet + if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { + viewSettings = settings.effective + } else { + viewSettings = GlobalNotificationSettingsSet.defaultSettings + } + return viewSettings } } diff --git a/Telegram-Mac/InactiveChannelsController.swift b/Telegram-Mac/InactiveChannelsController.swift new file mode 100644 index 0000000000..204c1b68c1 --- /dev/null +++ b/Telegram-Mac/InactiveChannelsController.swift @@ -0,0 +1,238 @@ +// +// InactiveChannelsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/12/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +private func localizedInactiveDate(_ timestamp: Int32) -> String { + + let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + var t: time_t = time_t(TimeInterval(timestamp)) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + var now: time_t = time_t(nowTimestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + + let string: String + + if timeinfoNow.tm_year == timeinfo.tm_year && timeinfoNow.tm_mon == timeinfo.tm_mon { + //weeks + let dif = Int(roundf(Float(timeinfoNow.tm_mday - timeinfo.tm_mday) / 7)) + string = L10n.inactiveChannelsInactiveWeekCountable(dif) + + } else if timeinfoNow.tm_year == timeinfo.tm_year { + //month + let dif = Int(timeinfoNow.tm_mon - timeinfo.tm_mon) + string = L10n.inactiveChannelsInactiveMonthCountable(dif) + } else { + //year + var dif = Int(timeinfoNow.tm_year - timeinfo.tm_year) + + if Int(timeinfoNow.tm_mon - timeinfo.tm_mon) > 6 { + dif += 1 + } + string = L10n.inactiveChannelsInactiveYearCountable(dif) + } + return string +} + +private final class InactiveChannelsArguments { + let context: AccountContext + let select: SelectPeerInteraction + init(context: AccountContext, select: SelectPeerInteraction) { + self.context = context + self.select = select + } +} + +private struct InactiveChannelsState : Equatable { + let channels:[InactiveChannel]? + init(channels: [InactiveChannel]?) { + self.channels = channels + } + func withUpdatedChannels(_ channels: [InactiveChannel]) -> InactiveChannelsState { + return InactiveChannelsState(channels: channels) + } +} + + +private func inactiveEntries(state: InactiveChannelsState, arguments: InactiveChannelsArguments, source: InactiveSource) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("_id_text"), equatable: nil, item: { initialSize, stableId in + return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .singleItem, text: source.localizedString, font: .normal(.text), header: GeneralBlockTextHeader(text: source.header, icon: theme.icons.sentFailed)) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + +// + if let channels = state.channels { + if !channels.isEmpty { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.inactiveChannelsHeader), data: .init(color: theme.colors.grayText, viewType: .textTopItem))) + index += 1 + } + for channel in channels { + let viewType = bestGeneralViewType(channels, for: channel) + struct _Equatable : Equatable { + let channel: InactiveChannel + let viewType: GeneralViewType + } + let equatable = _Equatable(channel: channel, viewType: viewType) + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("_id_peer_\(channel.peer.id.toInt64())"), equatable: InputDataEquatable(equatable), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: channel.peer, account: arguments.context.account, stableId: stableId, enabled: true, height: 50, photoSize: NSMakeSize(36, 36), status: localizedInactiveDate(channel.lastActivityDate), inset: NSEdgeInsets(left: 30, right: 30), interactionType: .selectable(arguments.select), viewType: viewType) + })) + index += 1 + } + if !channels.isEmpty { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + } else { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.inactiveChannelsHeader), data: .init(color: theme.colors.grayText, viewType: .textTopItem))) + index += 1 + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("_id_loading"), equatable: nil, item: { initialSize, stableId in + return LoadingTableItem(initialSize, height: 42, stableId: stableId, viewType: .singleItem) + })) + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + return entries +} + +func InactiveChannelsController(context: AccountContext, source: InactiveSource) -> InputDataModalController { + let initialState = InactiveChannelsState(channels: nil) + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((InactiveChannelsState) -> InactiveChannelsState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + + let disposable = MetaDisposable() + + disposable.set((inactiveChannelList(network: context.account.network) |> delay(0.5, queue: .mainQueue())).start(next: { channels in + updateState { + $0.withUpdatedChannels(channels) + } + })) + + let arguments = InactiveChannelsArguments(context: context, select: SelectPeerInteraction()) + + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: inactiveEntries(state: state, arguments: arguments, source: source)) + } + + let controller = InputDataController(dataSignal: signal, title: L10n.inactiveChannelsTitle) + + var close: (()->Void)? = nil + + let modalInteractions = ModalInteractions(acceptTitle: L10n.inactiveChannelsOK, accept: { + close?() + + if !arguments.select.presentation.selected.isEmpty { + let removeSignal = combineLatest(arguments.select.presentation.selected.map { removePeerChat(account: context.account, peerId: $0, reportChatSpam: false)}) + let peers = arguments.select.presentation.peers.map { $0.value } + let signal = context.account.postbox.transaction { transaction in + updatePeers(transaction: transaction, peers: peers, update: { _, updated in + return updated + }) + } |> mapToSignal { _ in + return removeSignal + } + + _ = showModalProgress(signal: signal, for: context.window).start() + } + + }, drawBorder: true, height: 50, singleButton: true) + + + arguments.select.singleUpdater = { [weak modalInteractions] presentation in + modalInteractions?.updateDone { button in + button.isEnabled = !presentation.selected.isEmpty + } + } + + controller.afterTransaction = { [weak modalInteractions] _ in + modalInteractions?.updateDone { button in + let state = stateValue.with { $0 } + if let channels = state.channels { + button.isEnabled = channels.isEmpty || !arguments.select.presentation.selected.isEmpty + button.set(text: channels.isEmpty ? L10n.modalOK : L10n.inactiveChannelsOK, for: .Normal) + } else { + button.isEnabled = false + } + } + } + + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { + close?() + }) + + controller.updateDatas = { data in + return .none + } + controller.onDeinit = { + disposable.dispose() + } + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, closeHandler: { f in f() }, size: NSMakeSize(400, 300)) + + close = { [weak modalController] in + modalController?.close() + } + + return modalController + +} + +enum InactiveSource { + case join + case create + case upgrade + case invite + var localizedString: String { + switch self { + case .join: + return L10n.joinChannelsTooMuch + case .create: + return L10n.createChannelsTooMuch + case .upgrade: + return L10n.upgradeChannelsTooMuch + case .invite: + return L10n.inviteChannelsTooMuch + } + } + var header: String { + return L10n.inactiveChannelsBlockHeader + } +} + +func showInactiveChannels(context: AccountContext, source: InactiveSource) { + showModal(with: InactiveChannelsController(context: context, source: source), for: context.window) +} diff --git a/Telegram-Mac/Info.plist b/Telegram-Mac/Info.plist index e32892c1f2..3f61d71199 100644 --- a/Telegram-Mac/Info.plist +++ b/Telegram-Mac/Info.plist @@ -2,6 +2,8 @@ + APPCENTER_SECRET + ${APPCENTER_SECRET} ATSApplicationFontsPath fonts CFBundleDevelopmentRegion @@ -19,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.5.2 + 6.3 CFBundleURLTypes @@ -29,13 +31,16 @@ tg telegram + ton CFBundleVersion - 107977 + 203623 LSApplicationCategoryType public.app-category.social-networking + LSFileQuarantineEnabled + LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSAppTransportSecurity @@ -43,15 +48,25 @@ NSAllowsArbitraryLoads + NSCameraUsageDescription + We need access to your camera so that you can record video messages. NSHumanReadableCopyright Copyright © 2016 Telegram. All rights reserved. + NSLocationAlwaysUsageDescription + We need access to your location so that you can send your current locations and setup Auto-Night theme NSMainNibFile MainMenu + NSMicrophoneUsageDescription + We need access to your microphone so that you can record voice messages and make calls. NSPrincipalClass NSApplication SUFeedURL ${SFEED_URL} SUPublicDSAKeyFile ${DSA_PEM_FILE} + UIAppFonts + + SFCompactRounded-Semibold.otf + diff --git a/Telegram-Mac/InlineAudioPlayerView.swift b/Telegram-Mac/InlineAudioPlayerView.swift index 6e066f7f53..78ee229d10 100644 --- a/Telegram-Mac/InlineAudioPlayerView.swift +++ b/Telegram-Mac/InlineAudioPlayerView.swift @@ -8,35 +8,59 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac - +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class InlineAudioPlayerView: NavigationHeaderView, APDelegate { - let previous:ImageButton = ImageButton() - let next:ImageButton = ImageButton() - let playOrPause:ImageButton = ImageButton() - let dismiss:ImageButton = ImageButton() - let repeatControl:ImageButton = ImageButton() - let progressView:LinearProgressControl = LinearProgressControl(progressHeight: .borderSize) - let textView:TextView = TextView() - let containerView:View = View() - let separator:View = View() - private var controller:APController? + private let previous:ImageButton = ImageButton() + private let next:ImageButton = ImageButton() + private let playOrPause:ImageButton = ImageButton() + private let dismiss:ImageButton = ImageButton() + private let repeatControl:ImageButton = ImageButton() + private let progressView:LinearProgressControl = LinearProgressControl(progressHeight: .borderSize) + private let textView:TextView = TextView() + private let containerView:Control + private let separator:View = View() + private let playingSpeed: ImageButton = ImageButton() + private var controller:APController? { + didSet { + if let controller = controller { + self.bufferingStatusDisposable.set((controller.bufferingStatus + |> deliverOnMainQueue).start(next: { [weak self] status in + if let status = status { + self?.updateStatus(status.0, status.1) + } + })) + controller.baseRate = (controller is APChatVoiceController) ? FastSettings.playingRate : 1.0 + } else { + self.bufferingStatusDisposable.set(nil) + } + self.playingSpeed.isHidden = !(controller is APChatVoiceController) + } + } + private var context: AccountContext? private var message:Message? private(set) var instantVideoPip:InstantVideoPIP? + private var ranges: (IndexSet, Int)? + + private var bufferingStatusDisposable: MetaDisposable = MetaDisposable() + + override init(_ header: NavigationHeader) { separator.backgroundColor = .border - + dismiss.disableActions() + repeatControl.disableActions() + repeatControl.autohighlight = false textView.isSelectable = false - - + containerView = Control(frame: NSMakeRect(0, 0, 0, header.height)) super.init(header) @@ -62,49 +86,117 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { controller.toggleRepeat() control.set(image: controller.needRepeat ? theme.icons.audioPlayerRepeatActive : theme.icons.audioPlayerRepeat, for: .Normal) } - }, for: .Click) + progressView.onUserChanged = { [weak self] progress in self?.controller?.set(trackProgress: progress) + self?.progressView.set(progress: CGFloat(progress), animated: false) + } + + var paused: Bool = false + + progressView.startScrobbling = { [weak self] in + _ = self?.controller?.pause() + paused = true + } + + progressView.endScrobbling = { [weak self] in + if paused { + _ = self?.controller?.play() + } } progressView.set(handler: { [weak self] control in let control = control as! LinearProgressControl if let strongSelf = self { strongSelf.controller?.set(trackProgress: control.interactiveValue) + strongSelf.progressView.set(progress: CGFloat(control.interactiveValue), animated: false) } }, for: .Click) + playingSpeed.autohighlight = false + containerView.addSubview(previous) containerView.addSubview(next) containerView.addSubview(playOrPause) containerView.addSubview(dismiss) containerView.addSubview(repeatControl) containerView.addSubview(textView) + containerView.addSubview(playingSpeed) addSubview(containerView) addSubview(separator) addSubview(progressView) textView.userInteractionEnabled = false + textView.isEventLess = true + + updateLocalizationAndTheme(theme: theme) + + containerView.set(handler: { [weak self] _ in + self?.showAudioPlayerList() + }, for: .LongOver) + + containerView.set(handler: { [weak self] _ in + self?.gotoMessage() + }, for: .SingleClick) - updateLocalizationAndTheme() + playingSpeed.set(handler: { [weak self] control in + FastSettings.setPlayingRate(FastSettings.playingRate == 1.7 ? 1.0 : 1.7) + self?.controller?.baseRate = FastSettings.playingRate + (control as! ImageButton).set(image: FastSettings.playingRate == 1.7 ? theme.icons.playingVoice2x : theme.icons.playingVoice1x, for: .Normal) + + }, for: .Click) + } + + private func showAudioPlayerList() { + guard let window = kitWindow, let context = context else {return} + let point = containerView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if NSPointInRect(point, textView.frame) { + if let song = controller?.currentSong, controller is APChatMusicController { + switch song.stableId { + case let .message(message): + showPopover(for: textView, with: PlayerListController(audioPlayer: self, context: context, messageIndex: MessageIndex(message)), edge: .minX, inset: NSMakePoint((300 - textView.frame.width) / 2, -60)) + default: + break + } + } + } } + func updateStatus(_ ranges: IndexSet, _ size: Int) { + self.ranges = (ranges, size) + + if let ranges = self.ranges, !ranges.0.isEmpty, ranges.1 != 0 { + for range in ranges.0.rangeView { + var progress = (CGFloat(range.count) / CGFloat(ranges.1)) + progress = progress == 1.0 ? 0 : progress + progressView.set(fetchingProgress: progress, animated: progress > 0) + + break + } + } + } + + private var playProgressStyle:ControlStyle { - return ControlStyle(foregroundColor: theme.colors.blueUI, backgroundColor: .clear) + return ControlStyle(foregroundColor: theme.colors.accent, backgroundColor: .clear) } private var fetchProgressStyle:ControlStyle { return ControlStyle(foregroundColor: theme.colors.grayTransparent, backgroundColor: .clear) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + playingSpeed.set(image: FastSettings.playingRate != 1.0 ? theme.icons.playingVoice2x : theme.icons.playingVoice1x, for: .Normal) previous.set(image: theme.icons.audioPlayerPrev, for: .Normal) next.set(image: theme.icons.audioPlayerNext, for: .Normal) playOrPause.set(image: theme.icons.audioPlayerPause, for: .Normal) dismiss.set(image: theme.icons.auduiPlayerDismiss, for: .Normal) + + progressView.fetchingColor = theme.colors.accent.withAlphaComponent(0.5) + if let controller = controller { repeatControl.set(image: controller.needRepeat ? theme.icons.audioPlayerRepeatActive : theme.icons.audioPlayerRepeat, for: .Normal) if let song = controller.currentSong { @@ -115,11 +207,17 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { repeatControl.set(image: theme.icons.audioPlayerRepeat, for: .Normal) } - previous.sizeToFit() - next.sizeToFit() - playOrPause.sizeToFit() - dismiss.sizeToFit() - repeatControl.sizeToFit() + _ = previous.sizeToFit() + _ = next.sizeToFit() + _ = playOrPause.sizeToFit() + _ = dismiss.sizeToFit() + _ = repeatControl.sizeToFit() + _ = playingSpeed.sizeToFit() + + + previous.centerY(x: 20) + playOrPause.centerY(x: previous.frame.maxX + 5) + next.centerY(x: playOrPause.frame.maxX + 5) backgroundColor = theme.colors.background containerView.backgroundColor = theme.colors.background @@ -127,31 +225,67 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { separator.backgroundColor = theme.colors.border } - override func mouseUp(with event: NSEvent) { - super.mouseUp(with: event) - if let message = message, let controller = controller, let navigation = controller.account.context.mainNavigation { - if let controller = navigation.controller as? ChatController, controller.chatInteraction.peerId == message.id.peerId { - controller.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, animated: true, focus: false, inset: 0)) + private func gotoMessage() { + if let message = message, let context = context { + if let controller = context.sharedContext.bindings.rootNavigation().controller as? ChatController, controller.chatInteraction.peerId == message.id.peerId { + controller.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: false), inset: 0)) } else { - navigation.push(ChatController(account: controller.account, peerId: message.id.peerId, messageId: message.id)) + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(message.id.peerId), messageId: message.id)) } } } - func update(with controller:APController, tableView:TableView) { + func update(with controller:APController, context: AccountContext, tableView:TableView?, supportTableView: TableView? = nil) { self.controller?.remove(listener: self) self.controller = controller + self.context = context self.controller?.add(listener: self) self.ready.set(controller.ready.get()) repeatControl.isHidden = !(controller is APChatMusicController) - self.instantVideoPip = InstantVideoPIP(controller, window: mainWindow) - self.instantVideoPip?.updateTableView(tableView) + if let tableView = tableView { + if self.instantVideoPip == nil { + self.instantVideoPip = InstantVideoPIP(controller, context: context, window: mainWindow) + } + self.instantVideoPip?.updateTableView(tableView, context: context, controller: controller) + addGlobalAudioToVisible(tableView: tableView) + } + if let supportTableView = supportTableView { + addGlobalAudioToVisible(tableView: supportTableView) + } + } + + private func addGlobalAudioToVisible(tableView: TableView) { + if let controller = controller { + tableView.enumerateViews(with: { (view) in + var contentView: NSView? = (view as? ChatRowView)?.contentView.subviews.last ?? (view as? PeerMediaMusicRowView) + if let view = ((view as? ChatMessageView)?.webpageContent as? WPMediaContentView)?.contentNode { + contentView = view + } + + if let view = contentView as? ChatAudioContentView { + controller.add(listener: view) + } else if let view = contentView as? ChatVideoMessageContentView { + controller.add(listener: view) + } else if let view = contentView as? WPMediaContentView { + if let contentNode = view.contentNode as? ChatAudioContentView { + controller.add(listener: contentNode) + } + } else if let view = view as? PeerMediaMusicRowView { + controller.add(listener: view) + } else if let view = view as? PeerMediaVoiceRowView { + controller.add(listener: view) + } + return true + }) + controller.notifyGlobalStateChanged() + } } deinit { controller?.remove(listener: self) controller?.stop() + bufferingStatusDisposable.dispose() } func attributedTitle(for song:APSongItem) -> NSAttributedString { @@ -168,7 +302,7 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { func songDidChanged(song:APSongItem, for controller:APController) { next.set(image: controller.nextEnabled ? theme.icons.audioPlayerNext : theme.icons.audioPlayerLockedNext, for: .Normal) previous.set(image: controller.prevEnabled ? theme.icons.audioPlayerPrev : theme.icons.audioPlayerLockedPrev, for: .Normal) - let layout = TextViewLayout(attributedTitle(for: song), maximumNumberOfLines:2, alignment: .center) + let layout = TextViewLayout(attributedTitle(for: song), maximumNumberOfLines:2, alignment: .left) self.textView.update(layout) self.needsLayout = true @@ -190,7 +324,7 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { progressView.set(progress: 0, animated:true) case let .playing(data): progressView.style = playProgressStyle - progressView.set(progress: CGFloat(data.progress), animated:data.animated) + progressView.set(progress: CGFloat(data.progress == .nan ? 0 : data.progress), animated: data.animated, duration: 0.2) playOrPause.set(image: theme.icons.audioPlayerPause, for: .Normal) break case let .fetching(progress, animated): @@ -215,27 +349,24 @@ class InlineAudioPlayerView: NavigationHeaderView, APDelegate { stopAndHide(true) } - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - separator.setFrameSize(newSize.width, .borderSize) - } - override func layout() { super.layout() - containerView.frame = NSMakeRect(0, 0, frame.width, frame.height) - previous.centerY(x: 20) - playOrPause.centerY(x: previous.frame.maxX + 20) - next.centerY(x: playOrPause.frame.maxX + 20) + containerView.frame = bounds + dismiss.centerY(x: frame.width - 20 - dismiss.frame.width) - repeatControl.centerY(x: frame.width - dismiss.frame.width - 40 - repeatControl.frame.width) - progressView.frame = NSMakeRect(0, frame.height - 10, frame.width, 10) - textView.layout?.measure(width: frame.width - (next.frame.maxX + dismiss.frame.width + repeatControl.frame.width + 20)) + repeatControl.centerY(x: dismiss.frame.minX - 10 - repeatControl.frame.width) + progressView.frame = NSMakeRect(0, frame.height - 6, frame.width, 6) + textView.layout?.measure(width: frame.width - (next.frame.maxX + dismiss.frame.width + repeatControl.frame.width + (playingSpeed.isHidden ? 0 : playingSpeed.frame.width + 10))) textView.update(textView.layout) - let w = dismiss.frame.minX - next.frame.maxX + playingSpeed.centerY(x: dismiss.frame.minX - playingSpeed.frame.width - 20) + + let w = (repeatControl.isHidden ? dismiss.frame.minX : repeatControl.frame.minX) - next.frame.maxX + + textView.centerY(x: next.frame.maxX + 10) + - textView.centerY(x: next.frame.maxX + floorToScreenPixels((w - textView.frame.width)/2)) - separator.setFrameOrigin(0, frame.height - .borderSize) + separator.frame = NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize) } func stopAndHide(_ animated:Bool) -> Void { diff --git a/Telegram-Mac/InlineAuthOptionRowItem.swift b/Telegram-Mac/InlineAuthOptionRowItem.swift new file mode 100644 index 0000000000..aa08863f37 --- /dev/null +++ b/Telegram-Mac/InlineAuthOptionRowItem.swift @@ -0,0 +1,91 @@ +// +// InlineAuthOptionRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 22/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +class InlineAuthOptionRowItem: GeneralRowItem { + + fileprivate let selected: Bool + fileprivate let textLayout: TextViewLayout + + init(_ initialSize: NSSize, stableId: AnyHashable, attributedString: NSAttributedString, selected: Bool, action: @escaping()->Void) { + self.selected = selected + self.textLayout = TextViewLayout(attributedString, maximumNumberOfLines: 3, alwaysStaticItems: true) + super.init(initialSize, stableId: stableId, action: action, inset: NSEdgeInsetsMake(10, 30, 10, 30)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + textLayout.measure(width: width - inset.left - inset.right - 50) + + return super.makeSize(width, oldWidth: oldWidth) + } + + override func viewClass() -> AnyClass { + return InlineAuthOptionRowView.self + } + + override var height: CGFloat { + return max(textLayout.layoutSize.height + inset.top + inset.bottom, 30) + } +} + + +private final class InlineAuthOptionRowView : TableRowView { + private let textView = TextView() + private let selectView: SelectingControl = SelectingControl(unselectedImage: theme.icons.chatGroupToggleUnselected, selectedImage: theme.icons.chatGroupToggleSelected) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + addSubview(selectView) + selectView.userInteractionEnabled = false + textView.userInteractionEnabled = false + textView.isSelectable = false + } + + override func mouseUp(with event: NSEvent) { + guard let item = item as? InlineAuthOptionRowItem else { + return + } + item.action() + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? InlineAuthOptionRowItem else { + return + } + + selectView.set(selected: item.selected, animated: animated) + } + + override func layout() { + super.layout() + + guard let item = item as? InlineAuthOptionRowItem else { + return + } + + textView.update(item.textLayout) + + if item.textLayout.layoutSize.height < selectView.frame.height { + selectView.centerY(x: item.inset.left) + textView.centerY(x: selectView.frame.maxX + 10) + } else { + selectView.setFrameOrigin(NSMakePoint(item.inset.left, item.inset.top)) + textView.setFrameOrigin(NSMakePoint(selectView.frame.maxX + 10, selectView.frame.minY)) + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/InlineLoginController.swift b/Telegram-Mac/InlineLoginController.swift new file mode 100644 index 0000000000..6b5a371de8 --- /dev/null +++ b/Telegram-Mac/InlineLoginController.swift @@ -0,0 +1,183 @@ +// +// InlineLoginController.swift +// Telegram +// +// Created by Mikhail Filimonov on 22/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +public struct InlineLoginOption: OptionSet { + public var rawValue: Int32 + + public init() { + self.rawValue = 0 + } + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let login = InlineLoginOption(rawValue: 1 << 0) + public static let allowMessages = InlineLoginOption(rawValue: 1 << 1) + +} + + + +private struct InlineLoginState : Equatable { + let options:InlineLoginOption + init(options: InlineLoginOption) { + self.options = options + } + + func withUpdatedOption(_ option: InlineLoginOption) -> InlineLoginState { + var options = self.options + if options.contains(option) { + options.remove(option) + } else { + options.insert(option) + } + return InlineLoginState(options: options) + } + + func withRemovedOption(_ option: InlineLoginOption, _ dependsOn: InlineLoginOption) -> InlineLoginState { + var options = self.options + if !options.contains(dependsOn) { + options.remove(option) + } + return InlineLoginState(options: options) + } +} + +private let _id_option_login = InputDataIdentifier("_id_option_login") +private let _id_option_allow_send_messages = InputDataIdentifier("_id_option_allow_send_messages") + +private func inlineLoginEntries(_ state: InlineLoginState, url: String, accountPeer: Peer, botPeer: Peer, writeAllowed: Bool, toggleOption:@escaping(InlineLoginOption)->Void, removeOption:@escaping(InlineLoginOption, InlineLoginOption)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + let host = URL(string: url)?.host ?? url + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("title"), equatable: nil, item: { initialSize, stableId in + + let attributedString = NSMutableAttributedString() + let string = L10n.botInlineAuthTitle(url) + let _ = attributedString.append(string: string, color: theme.colors.text, font: .normal(.text)) + let range = string.nsstring.range(of: url) + attributedString.addAttribute(.font, value: NSFont.medium(.text), range: range) + + return GeneralTextRowItem(initialSize, stableId: stableId, text: attributedString, alignment: .center, drawCustomSeparator: false, centerViewAlignment: true, isTextSelectable: false, detectLinks: false) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let loginEnabled = state.options.contains(.login) + let allowMessagesEnabled = state.options.contains(.allowMessages) + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option_login, equatable: InputDataEquatable(loginEnabled), item: { initialSize, stableId in + + let attributeString = NSMutableAttributedString() + let string = L10n.botInlineAuthOptionLogin(host, accountPeer.displayTitle) + let hostRange = string.nsstring.range(of: host) + _ = attributeString.append(string: string, color: theme.colors.text, font: .normal(.text)) + attributeString.addAttribute(.font, value: NSFont.medium(.text), range: hostRange) + + return InlineAuthOptionRowItem(initialSize, stableId: stableId, attributedString: attributeString, selected: loginEnabled, action: { + toggleOption(.login) + removeOption(.allowMessages, .login) + }) + })) + index += 1 + + + if writeAllowed { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option_allow_send_messages, equatable: InputDataEquatable(allowMessagesEnabled), item: { initialSize, stableId in + + let attributeString = NSMutableAttributedString() + let string = L10n.botInlineAuthOptionAllowSendMessages(botPeer.displayTitle) + let titleRange = string.nsstring.range(of: botPeer.displayTitle) + _ = attributeString.append(string: string, color: theme.colors.text, font: .normal(.text)) + attributeString.addAttribute(.font, value: NSFont.medium(.text), range: titleRange) + + return InlineAuthOptionRowItem(initialSize, stableId: stableId, attributedString: attributeString, selected: allowMessagesEnabled, action: { + toggleOption(.allowMessages) + }) + })) + index += 1 + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func InlineLoginController(context: AccountContext, url: String, originalURL: String, writeAllowed: Bool, botPeer: Peer, authorize: @escaping(Bool)->Void) -> InputDataModalController { + + + let initialState = writeAllowed ? InlineLoginState(options: [.login, .allowMessages]) : InlineLoginState(options: [.login]) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((InlineLoginState) -> InlineLoginState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let signal = combineLatest(statePromise.get(), context.account.postbox.loadedPeerWithId(context.peerId)) |> map { state, accountPeer in + return inlineLoginEntries(state, url: url, accountPeer: accountPeer, botPeer: botPeer, writeAllowed: writeAllowed, toggleOption: { option in + updateState { current in + return current.withUpdatedOption(option) + } + }, removeOption: { option, dependsOn in + updateState { current in + return current.withRemovedOption(option, dependsOn) + } + }) + } |> map { InputDataSignalValue(entries: $0) } + + var close:(()->Void)? + + let interactions = ModalInteractions(acceptTitle: L10n.botInlineAuthOpen, accept: { + let state = stateValue.with { $0 } + + if state.options.isEmpty { + execute(inapp: inAppLink.external(link: originalURL, false)) + } else { + authorize(state.options.contains(.allowMessages)) + } + close?() + }, drawBorder: true, height: 50, singleButton: true) + + let controller = InputDataController(dataSignal: signal, title: L10n.botInlineAuthHeader) + + controller.getBackgroundColor = { + theme.colors.background + } + + let modalController = InputDataModalController(controller, modalInteractions: interactions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + close = { [weak modalController] in + modalController?.close() + } + + return modalController +} diff --git a/Telegram-Mac/InputContextHelper.swift b/Telegram-Mac/InputContextHelper.swift index 82a27efb46..cf55b7c4b7 100644 --- a/Telegram-Mac/InputContextHelper.swift +++ b/Telegram-Mac/InputContextHelper.swift @@ -7,28 +7,35 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore enum InputContextEntry : Comparable, Identifiable { case switchPeer(PeerId, ChatContextResultSwitchPeer) + case message(Int64, Message, String) case peer(Peer, Int, Int64) case contextResult(ChatContextResultCollection,ChatContextResult,Int64) - case contextMediaResult(ChatContextResultCollection, InputMediaContextRow, Int64) + case contextMediaResult(ChatContextResultCollection?, InputMediaContextRow, Int64) case command(PeerCommand, Int64, Int64) case sticker(InputMediaStickersRow, Int64) - case emoji(EmojiClue, Int32) - case inlineRestricted(String?) + case showPeers(Int, Int64) + case emoji([String], String?, Bool, Int32) + case hashtag(String, Int64) + case inlineRestricted(String) + case separator(String, Int64, Int64) var stableId: Int64 { switch self { case .switchPeer: return -1 - case let .peer(peer,_, stableId): + case let .message(_, message, _): + return message.id.toInt64() + case let .peer(_,_, stableId): return stableId case let .contextResult(_,_,index): return index @@ -36,12 +43,18 @@ enum InputContextEntry : Comparable, Identifiable { return index case let .command( _, _, stableId): return stableId - case let .sticker( _, stableId): + case let .sticker(_, stableId): return stableId - case let .emoji(clue, _): - return clue.hashValue + case let .showPeers(_, stableId): + return stableId + case let .hashtag(hashtag, _): + return Int64(hashtag.hashValue) + case let .emoji(clue, _, _, _): + return Int64(clue.joined().hashValue) case .inlineRestricted: return -1000 + case let .separator(_, _, stableId): + return stableId } } @@ -59,10 +72,18 @@ enum InputContextEntry : Comparable, Identifiable { return index //result.maybeId | ((Int64(index) << 40)) case let .sticker(_, index): return index //result.maybeId | ((Int64(index) << 40)) - case let .emoji(_, index): + case let .showPeers(index, _): + return Int64(index) //result.maybeId | ((Int64(index) << 40)) + case let .hashtag(_, index): + return index + case let .emoji(_, _, _, index): return Int64(index) //result.maybeId | ((Int64(index) << 40)) case .inlineRestricted: return 0 + case let .message(index, _, _): + return index + case let .separator(_, index, _): + return index } } } @@ -104,9 +125,19 @@ func ==(lhs:InputContextEntry, rhs:InputContextEntry) -> Bool { return lhsSticker == rhsSticker && lhsIndex == rhsIndex } return false - case let .emoji(lhsClue, lhsIndex): - if case let .emoji(rhsClue, rhsIndex) = rhs { - return lhsClue == rhsClue && lhsIndex == rhsIndex + case let .showPeers(index, stableId): + if case .showPeers(index, stableId) = rhs { + return true + } + return false + case let .hashtag(lhsHashtag, lhsIndex): + if case let .hashtag(rhsHashtag, rhsIndex) = rhs { + return lhsHashtag == rhsHashtag && lhsIndex == rhsIndex + } + return false + case let .emoji(lhsClue, lhsCurrent, lhsFirstWord, lhsIndex): + if case let .emoji(rhsClue, rhsCurrent, rhsFirstWord, rhsIndex) = rhs { + return lhsClue == rhsClue && lhsIndex == rhsIndex && lhsFirstWord == rhsFirstWord && lhsCurrent == rhsCurrent } return false case let .inlineRestricted(lhsText): @@ -115,41 +146,71 @@ func ==(lhs:InputContextEntry, rhs:InputContextEntry) -> Bool { } else { return false } + case let .message(index, lhsMessage, searchText): + if case .message(index, let rhsMessage, searchText) = rhs { + return isEqualMessages(lhsMessage, rhsMessage) + } else { + return false + } + case let .separator(value1, value2, value3): + if case .separator(value1, value2, value3) = rhs { + return true + } + return false } } -fileprivate func prepareEntries(left:[AppearanceWrapperEntry]?, right:[AppearanceWrapperEntry], account:Account,initialSize:NSSize, chatInteraction:ChatInteraction) -> TableUpdateTransition { +fileprivate func prepareEntries(left:[AppearanceWrapperEntry]?, right:[AppearanceWrapperEntry], context: AccountContext, initialSize:NSSize, chatInteraction:ChatInteraction) -> TableUpdateTransition { let (removed,inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in switch entry.entry { case let .switchPeer(peerId, switchPeer): - return ContextSwitchPeerRowItem(initialSize, peerId:peerId, switchPeer:switchPeer, account:account, callback: { + return ContextSwitchPeerRowItem(initialSize, peerId:peerId, switchPeer:switchPeer, account: context.account, callback: { chatInteraction.switchInlinePeer(peerId, .start(parameter: switchPeer.startParam, behavior: .automatic)) }) case let .peer(peer, _, _): var status:String? - if let user = peer as? TelegramUser { - status = user.username + if let user = peer as? TelegramUser, let address = user.addressName { + status = "@\(address)" } let titleStyle:ControlStyle = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.text, backgroundColor: theme.colors.background, highlightColor:.white) let statusStyle:ControlStyle = ControlStyle(font: .normal(.text), foregroundColor: theme.colors.grayText, backgroundColor: theme.colors.background, highlightColor:.white) - return ShortPeerRowItem(initialSize, peer: peer, account: account, height: 40, photoSize: NSMakeSize(30, 30), titleStyle: titleStyle, statusStyle: statusStyle, status: status, borderType: [], drawCustomSeparator: true, inset: NSEdgeInsets(left:20)) + return ShortPeerRowItem(initialSize, peer: peer, account: context.account, height: 40, photoSize: NSMakeSize(30, 30), titleStyle: titleStyle, statusStyle: statusStyle, status: status, borderType: [], drawCustomSeparator: true, inset: NSEdgeInsets(left:20)) case let .contextResult(results,result,index): - return ContextListRowItem(initialSize, results, result, index, account, chatInteraction) + return ContextListRowItem(initialSize, results, result, index, context, chatInteraction) case let .contextMediaResult(results,result,index): - return ContextMediaRowItem(initialSize, results, result, index, account, chatInteraction) + return ContextMediaRowItem(initialSize, result, index, context, ContextMediaArguments(sendResult: { result, view in + if let results = results { + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else { + chatInteraction.sendInlineResult(results, result) + } + } + })) case let .command(command,_, stableId): - return ContextCommandRowItem(initialSize, account, command, stableId) - case let .emoji(clue, _): - return ContextClueRowItem(initialSize, stableId: entry.stableId, clue: clue) + return ContextCommandRowItem(initialSize, context.account, command, stableId) + case let .emoji(clues, selected, firstWord, _): + return ContextClueRowItem(initialSize, stableId: entry.stableId, context: context, clues: clues, selected: selected, canDisablePrediction: firstWord) + case let .hashtag(hashtag, _): + return ContextHashtagRowItem(initialSize, hashtag: "#\(hashtag)") case let .sticker(result, stableId): - return ContextStickerRowItem(initialSize, account, result, stableId, chatInteraction) - case .inlineRestricted(let until): - let text = until != nil ? tr(.channelPersmissionDeniedSendInlineUntil(until!)) : tr(.channelPersmissionDeniedSendInlineForever) + return ContextStickerRowItem(initialSize, context, result, stableId, chatInteraction) + case .showPeers: + return ContextShowPeersHolderItem(initialSize, stableId: entry.stableId, action: { + + }) + case let .inlineRestricted(text): return GeneralTextRowItem(initialSize, stableId: entry.stableId, height: 40, text: text, alignment: .center, centerViewAlignment: true) + case let .message(_, message, searchText): + return ContextSearchMessageItem(initialSize, context: context, message: message, searchText: searchText, action: { + + }) + case let .separator(string, _, _): + return SeparatorRowItem(initialSize, entry.stableId, string: string) } }) @@ -159,7 +220,7 @@ fileprivate func prepareEntries(left:[AppearanceWrapperEntry] } -class InputContextView : TableView, AppearanceViewProtocol { +class InputContextView : TableView { //let tableView:TableView let separatorView:View weak var relativeView: NSView? @@ -170,21 +231,21 @@ class InputContextView : TableView, AppearanceViewProtocol { } } - public required init(frame frameRect: NSRect, isFlipped: Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { + public override init(frame frameRect: NSRect) { // tableView = TableView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) separatorView = View(frame: NSMakeRect(0, 0, frameRect.width, .borderSize)) super.init(frame: frameRect) // addSubview(tableView) addSubview(separatorView) separatorView.autoresizingMask = [.width, .maxYMargin] - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - func updateLocalizationAndTheme() { + override func updateLocalizationAndTheme(theme: PresentationTheme) { separatorView.backgroundColor = theme.colors.border - //backgroundColor = theme.colors.background + // backgroundColor = theme.colors.background } required init?(coder: NSCoder) { @@ -196,7 +257,7 @@ class InputContextView : TableView, AppearanceViewProtocol { case .above: separatorView.setFrameOrigin(0, 0) case .below: - separatorView.setFrameOrigin(NSMakePoint(0, frame.height - separatorView.frame.height)) + separatorView.frame = NSMakeRect(0, frame.height - separatorView.frame.height, frame.width, .borderSize) } } @@ -206,112 +267,203 @@ class InputContextView : TableView, AppearanceViewProtocol { } } +private enum OverscrollState { + case small + case intermediate + case full +} + +private final class OverscrollData { + var state:OverscrollState + init(state: OverscrollState) { + self.state = state + } +} + class InputContextViewController : GenericViewController, TableViewDelegate { - private let account:Account + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + + private let overscrollData: OverscrollData = OverscrollData(state: .small) + + fileprivate var markAsNeedShown: Bool = false + + private let context:AccountContext private let chatInteraction:ChatInteraction + private let highlightInsteadOfSelect: Bool + + private var escapeTextMarked: String? + + fileprivate var result:ChatPresentationInputQueryResult? + + fileprivate weak var superview: NSView? override func loadView() { super.loadView() genericView.delegate = self view.layer?.opacity = 0 } - init(account:Account,chatInteraction:ChatInteraction) { - self.account = account + init(context: AccountContext, chatInteraction: ChatInteraction, highlightInsteadOfSelect: Bool) { self.chatInteraction = chatInteraction + self.context = context + self.highlightInsteadOfSelect = highlightInsteadOfSelect super.init() } + + override func viewDidLoad() { + super.viewDidLoad() + } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - let prev = self?.deselectSelectedSticker() - self?.genericView.selectNext(true,true) - self?.selectFirstInRowIfCan(prev) + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + let prev = self.deselectSelectedHorizontalItem() + self.highlightInsteadOfSelect ? self.genericView.highlightNext(true,true) : self.genericView.selectNext(true,true) + self.selectFirstInRowIfCan(prev, false) return .invoked - }, with: self, for: .DownArrow, priority: .high) + }, with: self, for: .DownArrow, priority: .modal) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - let prev = self?.deselectSelectedSticker() - self?.genericView.selectPrev(true,true) - self?.selectFirstInRowIfCan(prev) + context.window.set(handler: {[weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + let prev = self.deselectSelectedHorizontalItem() + self.highlightInsteadOfSelect ? self.genericView.highlightPrev(true,true) : self.genericView.selectPrev(true,true) + self.selectFirstInRowIfCan(prev, true) return .invoked - }, with: self, for: .UpArrow, priority: .high) + }, with: self, for: .UpArrow, priority: .modal) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + context.window.set(handler: { [weak self] () -> KeyHandlerResult in if let strongSelf = self { if case .stickers = strongSelf.chatInteraction.presentation.inputContext { strongSelf.selectPreviousSticker() - return .invoked + return strongSelf.genericView.selectedItem() != nil ? .invoked : .invokeNext + } else if case .emoji = strongSelf.chatInteraction.presentation.inputContext { + return strongSelf.selectPrevEmojiClue() } } return .invokeNext - }, with: self, for: .LeftArrow, priority: .high) + }, with: self, for: .LeftArrow, priority: .modal) - self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .RightArrow, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .UpArrow, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .DownArrow, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .LeftArrow, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in if let strongSelf = self { if case .stickers = strongSelf.chatInteraction.presentation.inputContext { strongSelf.selectNextSticker() - return .invoked + return strongSelf.genericView.selectedItem() != nil ? .invoked : .invokeNext + } else if case .emoji = strongSelf.chatInteraction.presentation.inputContext { + return strongSelf.selectNextEmojiClue() } } return .invokeNext - }, with: self, for: .RightArrow, priority: .high) + }, with: self, for: .RightArrow, priority: .modal) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in + context.window.set(handler: {[weak self] () -> KeyHandlerResult in if let strongSelf = self { + if strongSelf.context.isInGlobalSearch { + return .rejected + } return strongSelf.invoke() } return .invokeNext - }, with: self, for: .Return, priority: .high) + }, with: self, for: .Return, priority: .modal) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in + context.window.set(handler: {[weak self] () -> KeyHandlerResult in if let strongSelf = self { return strongSelf.invokeTab() } return .invokeNext - }, with: self, for: .Tab, priority: .high) + }, with: self, for: .Tab, priority: .modal) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in + context.window.set(handler: {[weak self] () -> KeyHandlerResult in if self?.genericView.selectedItem() != nil { - _ = self?.deselectSelectedSticker() + _ = self?.deselectSelectedHorizontalItem() self?.genericView.cancelSelection() + self?.escapeTextMarked = self?.chatInteraction.presentation.effectiveInput.inputText return .invoked } return .rejected }, with: self, for: .Escape, priority: .modal) + + } func invoke() -> KeyHandlerResult { - if let selectedItem = genericView.selectedItem() { + if let selectedItem = genericView.highlightedItem() ?? genericView.selectedItem() { if let selectedItem = selectedItem as? ShortPeerRowItem { chatInteraction.movePeerToInput(selectedItem.peer) } else if let selectedItem = selectedItem as? ContextListRowItem { - chatInteraction.sendInlineResult(selectedItem.results,selectedItem.result) + + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + if let view = selectedItem.view { + showSlowModeTimeoutTooltip(slowMode, for: view) + self.genericView.cancelSelection() + } + } else { + chatInteraction.sendInlineResult(selectedItem.results,selectedItem.result) + } } else if let selectedItem = selectedItem as? ContextCommandRowItem { - chatInteraction.sendCommand(selectedItem.command) + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + if let view = selectedItem.view { + showSlowModeTimeoutTooltip(slowMode, for: view) + self.genericView.cancelSelection() + } + } else { + chatInteraction.sendCommand(selectedItem.command) + } } else if let selectedItem = selectedItem as? ContextClueRowItem { - let clue = selectedItem.clue - + if let clue = selectedItem.selectedIndex != nil ? selectedItem.clues[selectedItem.selectedIndex!] : nil { + let textInputState = chatInteraction.presentation.effectiveInput + if let (range, _, _) = textInputStateContextQueryRangeAndType(textInputState, includeContext: false) { + let inputText = textInputState.inputText + + let distance = inputText.distance(from: range.lowerBound, to: range.upperBound) + let replacementText = clue + + let atLength = range.lowerBound > inputText.startIndex && inputText[inputText.index(before: range.lowerBound)] == ":" ? 1 : 0 + _ = chatInteraction.appendText(replacementText, selectedRange: textInputState.selectionRange.lowerBound - distance - atLength ..< textInputState.selectionRange.upperBound) + } + } else { + return .rejected + } + } else if let selectedItem = selectedItem as? ContextHashtagRowItem { let textInputState = chatInteraction.presentation.effectiveInput if let (range, _, _) = textInputStateContextQueryRangeAndType(textInputState, includeContext: false) { let inputText = textInputState.inputText let distance = inputText.distance(from: range.lowerBound, to: range.upperBound) - let replacementText = clue.emoji + let replacementText = selectedItem.hashtag + " " let atLength = 1 _ = chatInteraction.appendText(replacementText, selectedRange: textInputState.selectionRange.lowerBound - distance - atLength ..< textInputState.selectionRange.upperBound) } } else if let selectedItem = selectedItem as? ContextStickerRowItem, let index = selectedItem.selectedIndex { - chatInteraction.sendAppFile(selectedItem.result.results[index].file) + chatInteraction.sendAppFile(selectedItem.result.results[index].file, false) chatInteraction.clearInput() + } else if let selectedItem = selectedItem as? ContextSearchMessageItem { + chatInteraction.focusMessageId(nil, selectedItem.message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) } return .invoked } - return .invokeNext + return .rejected } func invokeTab() -> KeyHandlerResult { @@ -322,22 +474,85 @@ class InputContextViewController : GenericViewController, Tabl let commandText = "/" + selectedItem.command.command.text + " " chatInteraction.updateInput(with: commandText) + } else if let selectedItem = selectedItem as? ContextClueRowItem { + let clue: String? + if let index = selectedItem.selectedIndex { + clue = selectedItem.clues[index] + } else { + clue = selectedItem.clues.first + } + if let clue = clue { + let textInputState = chatInteraction.presentation.effectiveInput + if let (range, _, _) = textInputStateContextQueryRangeAndType(textInputState, includeContext: false) { + let inputText = textInputState.inputText + + let distance = inputText.distance(from: range.lowerBound, to: range.upperBound) + let replacementText = clue + + let atLength = range.lowerBound > inputText.startIndex && inputText[inputText.index(before: range.lowerBound)] == ":" ? 1 : 0 + _ = chatInteraction.appendText(replacementText, selectedRange: textInputState.selectionRange.lowerBound - distance - atLength ..< textInputState.selectionRange.upperBound) + } + } + return .invoked + } else if let selectedItem = selectedItem as? ContextHashtagRowItem { + let textInputState = chatInteraction.presentation.effectiveInput + if let (range, _, _) = textInputStateContextQueryRangeAndType(textInputState, includeContext: false) { + let inputText = textInputState.inputText + + let distance = inputText.distance(from: range.lowerBound, to: range.upperBound) + let replacementText = selectedItem.hashtag + " " + + let atLength = 1 + _ = chatInteraction.appendText(replacementText, selectedRange: textInputState.selectionRange.lowerBound - distance - atLength ..< textInputState.selectionRange.upperBound) + } } return .invoked } return .invokeNext } - func deselectSelectedSticker() -> Int? { + func deselectSelectedHorizontalItem() -> Int? { var prev:Int? = nil if let selectedItem = genericView.selectedItem() as? ContextStickerRowItem { prev = selectedItem.selectedIndex selectedItem.selectedIndex = nil - selectedItem.redraw() + selectedItem.redraw(animated: true) + } + if let selectedItem = genericView.selectedItem() as? ContextClueRowItem { + prev = selectedItem.selectedIndex + selectedItem.selectedIndex = nil + selectedItem.redraw(animated: true) } return prev } + func selectNextEmojiClue() -> KeyHandlerResult { + if let selectedItem = genericView.selectedItem() as? ContextClueRowItem { + if let selectedIndex = selectedItem.selectedIndex { + var index = selectedIndex + index += 1 + selectedItem.selectedIndex = max(min(index, selectedItem.clues.count - 1), 0) + selectedItem.redraw(animated: true) + } + + return selectedItem.selectedIndex != nil ? .invoked : .rejected + } + return .rejected + + } + func selectPrevEmojiClue() -> KeyHandlerResult { + if let selectedItem = genericView.selectedItem() as? ContextClueRowItem { + if let selectedIndex = selectedItem.selectedIndex { + var index = selectedIndex + index -= 1 + selectedItem.selectedIndex = max(min(index, selectedItem.clues.count - 1), 0) + selectedItem.redraw(animated: true) + } + return selectedItem.selectedIndex != nil ? .invoked : .rejected + } + return .rejected + } + func selectPreviousSticker() { if let selectedItem = genericView.selectedItem() as? ContextStickerRowItem { if selectedItem.selectedIndex != nil { @@ -348,7 +563,7 @@ class InputContextViewController : GenericViewController, Tabl } if selectedItem.selectedIndex! < 0 { - _ = deselectSelectedSticker() + _ = deselectSelectedHorizontalItem() genericView.selectPrev(true,true) selectLastInRowIfCan() } else { @@ -367,7 +582,7 @@ class InputContextViewController : GenericViewController, Tabl } if selectedItem.selectedIndex! > selectedItem.result.entries.count - 1 { - _ = deselectSelectedSticker() + _ = deselectSelectedHorizontalItem() genericView.selectNext(true,true) selectFirstInRowIfCan() } else { @@ -376,13 +591,24 @@ class InputContextViewController : GenericViewController, Tabl } } - func selectFirstInRowIfCan(_ start:Int? = nil) { + func selectFirstInRowIfCan(_ start:Int? = nil, _ bottom: Bool = false) { if let selectedItem = genericView.selectedItem() as? ContextStickerRowItem { var index = start ?? 0 index = max(index, 0) selectedItem.selectedIndex = index selectedItem.redraw() } + if let selectedItem = genericView.selectedItem() as? ContextClueRowItem { + var index: Int + if let start = start { + index = start + (bottom ? -1 : 1) + } else { + index = bottom ? selectedItem.clues.count - 1 : 0 + } + index = min(max(index, 0), selectedItem.clues.count - 1) + selectedItem.selectedIndex = index + selectedItem.redraw(animated: true) + } } func selectLastInRowIfCan(_ start:Int? = nil) { @@ -390,7 +616,7 @@ class InputContextViewController : GenericViewController, Tabl var index = start ?? selectedItem.result.entries.count - 1 index = min(index, selectedItem.result.entries.count - 1) selectedItem.selectedIndex = index - selectedItem.redraw() + selectedItem.redraw(animated: true) } } @@ -400,7 +626,7 @@ class InputContextViewController : GenericViewController, Tabl } func cleanup() { - self.window?.removeAllHandlers(for: self) + mainWindow.removeAllHandlers(for: self) } deinit { @@ -417,41 +643,77 @@ class InputContextViewController : GenericViewController, Tabl _ = invoke() } } - func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { return true } func isSelectable(row:Int, item:TableRowItem) -> Bool { return !(item is ContextMediaRowItem) } - func make(with transition:TableUpdateTransition, animated:Bool, result: ChatPresentationInputQueryResult? = nil) { + func make(with transition:TableUpdateTransition, animated:Bool, selectIndex: Int?, result: ChatPresentationInputQueryResult? = nil) { assertOnMainThread() genericView.cancelSelection() + genericView.cancelHighlight() genericView.merge(with: transition) layout(animated) - if !genericView.isEmpty, let result = result, case .mentions = result { - _ = genericView.select(item: genericView.item(at: 0)) + if !genericView.isEmpty, let result = result { + + if let escapeTextMarked = escapeTextMarked, escapeTextMarked == self.chatInteraction.presentation.effectiveInput.inputText { + return + } else { + escapeTextMarked = nil + } + switch result { + case .mentions, .searchMessages, .commands, .hashtags: + if !highlightInsteadOfSelect { + _ = genericView.select(item: genericView.item(at: selectIndex ?? 0)) + } else { + _ = genericView.highlight(item: genericView.item(at: selectIndex ?? 0)) + } + case let .emoji(_, firstWord): + if !highlightInsteadOfSelect { + _ = genericView.select(item: genericView.item(at: selectIndex ?? 0)) + } else { + _ = genericView.highlight(item: genericView.item(at: selectIndex ?? 0)) + } + if !firstWord { + _ = selectFirstInRowIfCan() + } + default: + break + } + if let selectIndex = selectIndex { + _ = genericView.select(item: genericView.item(at: selectIndex)) + genericView.scroll(to: .center(id: genericView.item(at: selectIndex).stableId, innerId: nil, animated: false, focus: .init(focus: false), inset: 0)) + } } } func layout(_ animated:Bool) { - let future = NSMakeSize(frame.width, min(genericView.listHeight,140)) - // genericView.change(size: future, animated: animated) - // genericView.change(pos: NSMakePoint(0, 0), animated: animated) - - genericView.change(size: future, animated: animated) - - switch genericView.position { - case .above: - genericView.separatorView.change(pos: NSZeroPoint, animated: true) - case .below: - genericView.separatorView.change(pos: NSMakePoint(0, frame.height - genericView.separatorView.frame.height), animated: true) + if let superview = superview, let relativeView = genericView.relativeView { + var height = min(superview.frame.height - 50 - relativeView.frame.height, floor(superview.frame.height / 3)) + if genericView.firstItem is ContextClueRowItem { + height = min(height, 120) + } + let future = NSMakeSize(frame.width, min(genericView.listHeight, height)) + // genericView.change(size: future, animated: animated) + // genericView.change(pos: NSMakePoint(0, 0), animated: animated) + CATransaction.begin() + genericView.change(size: future, animated: animated, duration: future.height > frame.height || genericView.position == .below ? 0 : 0.5) + + switch genericView.position { + case .above: + genericView.separatorView.change(pos: NSZeroPoint, animated: animated) + case .below: + genericView.separatorView.change(pos: NSMakePoint(0, frame.height - genericView.separatorView.frame.height), animated: animated) + } + +// let y = genericView.position == .above ? relativeView.frame.minY - frame.height : relativeView.frame.maxY +// genericView.change(pos: NSMakePoint(0, y), animated: animated) + + CATransaction.commit() } - if let relativeView = genericView.relativeView { - let y = genericView.position == .above ? relativeView.frame.minY - frame.height : relativeView.frame.maxY - genericView.change(pos: NSMakePoint(0, y), animated: animated) - } } @@ -464,17 +726,17 @@ enum InputContextPosition { class InputContextHelper: NSObject { - var disposable:MetaDisposable = MetaDisposable() + private let disposable:MetaDisposable = MetaDisposable() - private var controller:InputContextViewController - private let account:Account + let controller:InputContextViewController + private let context:AccountContext private let chatInteraction:ChatInteraction private let entries:Atomic<[AppearanceWrapperEntry]?> = Atomic(value:nil) - - init(account:Account, chatInteraction:ChatInteraction) { - self.account = account + private let loadMoreDisposable = MetaDisposable() + init(chatInteraction:ChatInteraction, highlightInsteadOfSelect: Bool = false) { self.chatInteraction = chatInteraction - controller = InputContextViewController(account:account,chatInteraction:chatInteraction) + self.context = chatInteraction.context + controller = InputContextViewController(context: chatInteraction.context, chatInteraction: chatInteraction, highlightInsteadOfSelect: highlightInsteadOfSelect) } public var accessoryView:NSView? { @@ -485,34 +747,71 @@ class InputContextHelper: NSObject { self.controller.viewWillDisappear(false) } - func context(with result:ChatPresentationInputQueryResult?, for view: NSView, relativeView: NSView, position: InputContextPosition = .above, animated:Bool) { - - controller._frameRect = NSMakeRect(0, 0, view.frame.width, 140) + func context(with result:ChatPresentationInputQueryResult?, for view: NSView, relativeView: NSView, position: InputContextPosition = .above, selectIndex:Int? = nil, animated:Bool) { + controller._frameRect = NSMakeRect(0, 0, view.frame.width, floor(view.frame.height / 3)) controller.loadViewIfNeeded() - + controller.superview = view controller.genericView.relativeView = relativeView controller.genericView.position = position + controller.updateLocalizationAndTheme(theme: theme) + var currentResult = result + let initialSize = controller.atomicSize + let previosEntries = self.entries + let context = self.chatInteraction.context + let chatInteraction = self.chatInteraction - let makeSignal = combineLatest(entries(for: result, initialSize:initialSize.modify {$0}, chatInteraction: chatInteraction), appearanceSignal) |> map { [weak self] entries, appearance -> (TableUpdateTransition,Bool, Bool) in - if let strongSelf = self { - let entries = entries.map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - let previous = strongSelf.entries.swap(entries) - let previousIsEmpty:Bool = previous?.isEmpty ?? true - return (prepareEntries(left: previous, right: entries, account: strongSelf.account, initialSize: initialSize.modify({$0}), chatInteraction:strongSelf.chatInteraction),!entries.isEmpty, previousIsEmpty) + let entriesValue: Promise<[InputContextEntry]> = Promise() + + self.loadMoreDisposable.set(nil) + + controller.genericView.setScrollHandler { [weak self] position in + guard let `self` = self, let result = currentResult else {return} + switch position.direction { + case .bottom: + switch result { + case let .searchMessages(messages, _, _): + messages.2(messages.1) + case let .contextRequestResult(peer, oldCollection): + if let oldCollection = oldCollection, let nextOffset = oldCollection.nextOffset { + self.loadMoreDisposable.set((requestChatContextResults(account: context.account, botId: oldCollection.botId, peerId: self.chatInteraction.peerId, query: oldCollection.query, offset: nextOffset) |> delay(0.5, queue: Queue.mainQueue())).start(next: { [weak self] collection in + guard let `self` = self else {return} + + if let collection = collection { + let newResult = ChatPresentationInputQueryResult.contextRequestResult(peer, oldCollection.withAdditionalCollection(collection.results)) + currentResult = newResult + entriesValue.set(self.entries(for: newResult, initialSize: initialSize.modify {$0}, chatInteraction: chatInteraction)) + } + })) + } + default: + break + } + default: + break } - return (TableUpdateTransition(deleted: [], inserted: [], updated: []), false, false) + } + + entriesValue.set(entries(for: result, initialSize: initialSize.modify {$0}, chatInteraction: chatInteraction)) + + let makeSignal = combineLatest(queue: prepareQueue, entriesValue.get(), appearanceSignal) |> map { entries, appearance -> (TableUpdateTransition,Bool, Bool) in + + let entries = entries.map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let previous = previosEntries.swap(entries) + let previousIsEmpty:Bool = previous?.isEmpty ?? true + return (prepareEntries(left: previous, right: entries, context: context, initialSize: initialSize.modify({$0}), chatInteraction:chatInteraction),!entries.isEmpty, previousIsEmpty) } |> deliverOnMainQueue - disposable.set(makeSignal.start(next: { [weak self, weak view] transition, show, previousIsEmpty in + disposable.set((makeSignal |> map { [weak self, weak view, weak relativeView] transition, show, previousIsEmpty in - if show, let controller = self?.controller { + if show, let controller = self?.controller, let relativeView = relativeView { if previousIsEmpty { controller.genericView.removeAll() } - controller.make(with: transition, animated:animated, result: result) + controller.make(with: transition, animated:animated, selectIndex: selectIndex, result: result) if let view = view { + controller.markAsNeedShown = true controller.viewWillAppear(animated) if controller.view.superview == nil { view.addSubview(controller.view, positioned: .below, relativeTo: relativeView) @@ -520,52 +819,59 @@ class InputContextHelper: NSObject { controller.view.setFrameOrigin(0, relativeView.frame.minY) } controller.viewDidAppear(animated) - + controller.genericView.isHidden = false controller.genericView.change(opacity: 1, animated: animated) let y = position == .above ? relativeView.frame.minY - controller.frame.height : relativeView.frame.maxY - controller.genericView.change(pos: NSMakePoint(0, y), animated: animated, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) - + if y != controller.genericView.frame.minY { + controller.genericView._change(pos: NSMakePoint(0, y), animated: animated, duration: 0.4, timingFunction: CAMediaTimingFunctionName.spring, forceAnimateIfHasAnimation: true) + } } - } else if let controller = self?.controller { - controller.viewWillDisappear(animated) - + } else if let controller = self?.controller, let relativeView = relativeView { + var controller:InputContextViewController? = controller + controller?.viewWillDisappear(animated) + controller?.markAsNeedShown = false if animated { - controller.genericView.change(pos: NSMakePoint(0, relativeView.frame.minY), animated: animated, removeOnCompletion: false, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak controller] completed in - - if completed { + controller?.genericView._change(pos: NSMakePoint(0, relativeView.frame.minY), animated: animated, removeOnCompletion: false, duration: 0.4, timingFunction: CAMediaTimingFunctionName.spring, forceAnimateIfHasAnimation: true, completion: { completed in + if controller?.markAsNeedShown == false { controller?.removeFromSuperview() controller?.genericView.removeAll() controller?.viewDidDisappear(animated) + controller?.genericView.cancelSelection() } - + controller = nil }) - controller.genericView.change(opacity: 0, animated: true) + controller?.genericView.change(opacity: 0, animated: true) } else { - controller.removeFromSuperview() - controller.viewDidDisappear(animated) + controller?.removeFromSuperview() + controller?.viewDidDisappear(animated) + controller?.genericView.cancelSelection() } } - })) + }).start()) } - func entries(for result:ChatPresentationInputQueryResult?, initialSize:NSSize, chatInteraction: ChatInteraction) -> Signal<[InputContextEntry],Void> { + func entries(for result:ChatPresentationInputQueryResult?, initialSize:NSSize, chatInteraction: ChatInteraction) -> Signal<[InputContextEntry], NoError> { if let result = result { - return Signal {(subscriber) in + return Signal { subscriber in var entries:[InputContextEntry] = [] switch result { case let .mentions(peers): + var mention:[PeerId: PeerId] = [:] for i in 0 ..< peers.count { - entries.append(.peer(peers[i],entries.count, Int64(arc4random()))) + if mention[peers[i].id] == nil { + entries.append(.peer(peers[i],entries.count, Int64(arc4random()))) + mention[peers[i].id] = peers[i].id + } } case let .contextRequestResult(_, result): - if let peer = chatInteraction.presentation.peer as? TelegramChannel { - if peer.inlineRestricted, let bannedRights = peer.bannedRights { - entries.append(.inlineRestricted(bannedRights.untilDate == .max ? nil : bannedRights.formattedUntilDate)) + if let peer = chatInteraction.presentation.peer { + if let text = permissionText(from: peer, for: .banSendInline) { + entries.append(.inlineRestricted(text)) break } } @@ -583,10 +889,12 @@ class InputContextHelper: NSObject { } case .media: - let mediaRows = makeMediaEnties(result.results, initialSize:NSMakeSize(initialSize.width, 100)) + let mediaRows = makeMediaEnties(result.results, isSavedGifs: false, initialSize:NSMakeSize(initialSize.width, 100)) for i in 0 ..< mediaRows.count { - entries.append(.contextMediaResult(result, mediaRows[i], Int64(arc4random()) | ((Int64(entries.count) << 40)))) + if !mediaRows[i].results.isEmpty { + entries.append(.contextMediaResult(result, mediaRows[i], Int64(arc4random()) | ((Int64(entries.count) << 40)))) + } } } @@ -597,10 +905,17 @@ class InputContextHelper: NSObject { entries.append(.command(commands[i], index, Int64(arc4random()) | ((Int64(commands.count) << 40)))) index += 1 } + case .hashtags(let hashtags): + var index:Int64 = 2000 + for i in 0 ..< hashtags.count { + entries.append(.hashtag(hashtags[i], index)) + index += 1 + } case let .stickers(stickers): - if let peer = chatInteraction.presentation.peer as? TelegramChannel { - if peer.stickersRestricted { + if let peer = chatInteraction.presentation.peer { + if let text = permissionText(from: peer, for: .banSendStickers) { + entries.append(.inlineRestricted(text)) break } } @@ -611,15 +926,34 @@ class InputContextHelper: NSObject { entries.append(.sticker(mediaRows[i], Int64(arc4random()) | ((Int64(entries.count) << 40)))) } - break - case let .emoji(clues): + case let .emoji(clues, firstWord): var index:Int32 = 0 - for clue in clues { - entries.append(.emoji(clue, index)) + if !clues.isEmpty { + entries.append(.emoji(clues, nil, firstWord, index)) + index += 1 + } + + case let .searchMessages(messages, suggestPeers, searchText): + var index: Int64 = 0 + + + let count:Int = min(max(6 - messages.0.count, 1), suggestPeers.count) + for i in 0 ..< count { + let peer = suggestPeers[i] + entries.append(.peer(peer, Int(index), arc4random64())) + index += 1 + } + + +// if suggestPeers.count > 1, messages.0.count > 0 { +// entries.append(.showPeers(Int(index), arc4random64())) +// index += 1 +// } + + for message in messages.0 { + entries.append(.message(index, message, searchText)) index += 1 } - default: - break } entries.sort(by: <) subscriber.putNext(entries) @@ -633,6 +967,7 @@ class InputContextHelper: NSObject { deinit { disposable.dispose() + loadMoreDisposable.dispose() } } diff --git a/Telegram-Mac/InputDataController.swift b/Telegram-Mac/InputDataController.swift new file mode 100644 index 0000000000..e3263ab00f --- /dev/null +++ b/Telegram-Mac/InputDataController.swift @@ -0,0 +1,878 @@ +// +// InputDataController.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + + +public class InputDataModalController : ModalViewController { + private let controller: InputDataController + private let _modalInteractions: ModalInteractions? + private let closeHandler: (@escaping()-> Void) -> Void + private let themeDisposable = MetaDisposable() + init(_ controller: InputDataController, modalInteractions: ModalInteractions? = nil, closeHandler: @escaping(@escaping()-> Void) -> Void = { $0() }, size: NSSize = NSMakeSize(350, 300)) { + self.controller = controller + self._modalInteractions = modalInteractions + self.controller._frameRect = NSMakeRect(0, 0, max(size.width, 350), size.height) + self.controller.prepareAllItems = true + self.closeHandler = closeHandler + super.init(frame: controller._frameRect) + } + + var isFullScreenImpl: (()->Bool)? = nil + + public override var isFullScreen: Bool { + return self.isFullScreenImpl?() ?? super.isFullScreen + } + var closableImpl: (()->Bool)? = nil + + public override var closable: Bool { + return closableImpl?() ?? super.closable + } + + public override func close(animationType: ModalAnimationCloseBehaviour = .common) { + closeHandler({ [weak self] in + self?.closeModal() + }) + } + public override var shouldCloseAllTheSameModals: Bool { + return false + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + } + + public override var containerBackground: NSColor { + return controller.getBackgroundColor() + } + + private func closeModal() { + super.close() + } + + public override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + if controller.defaultBarTitle.isEmpty { + return nil + } + return (left: self.controller.leftModalHeader, center: self.controller.centerModalHeader ?? ModalHeaderData(title: controller.defaultBarTitle), right: self.controller.rightModalHeader) + } + + + public override var modalInteractions: ModalInteractions? { + return _modalInteractions + } + + public override var handleEvents: Bool { + return true + } + + public override func becomeFirstResponder() -> Bool? { + return controller.becomeFirstResponder() + } + + public override func firstResponder() -> NSResponder? { + return controller.firstResponder() + } + + public override func returnKeyAction() -> KeyHandlerResult { + return controller.returnKeyAction() + } + + public override var haveNextResponder: Bool { + return controller.haveNextResponder + } + + public override func nextResponder() -> NSResponder? { + return controller.nextResponder() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + controller.viewWillAppear(animated) + } + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + controller.viewDidAppear(animated) + } + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + controller.viewWillDisappear(animated) + } + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + controller.viewDidDisappear(animated) + controller.didRemovedFromStack() + } + + + @objc private func rootControllerFrameChanged(_ notification:Notification) { + viewDidResized(frame.size) + } + + public override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + + updateSize(true) + } + + var dynamicSizeImpl:(()->Bool)? = nil + + override open var dynamicSize: Bool { + return self.dynamicSizeImpl?() ?? true + } + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(max(340, min(self.controller._frameRect.width, max(size.width, 350))), min(size.height - 150, controller.tableView.listHeight)), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(max(340, min(self.controller._frameRect.width, max(contentSize.width, 350))), min(contentSize.height - 150, controller.tableView.listHeight)), animated: animated) + } + } + + public override func viewClass() -> AnyClass { + fatalError() + } + + public override func loadView() { + viewDidLoad() + } + + public override var view: NSView { + if !controller.isLoaded() { + loadView() + } + return controller.view + } + + + public override func viewDidLoad() { + controller.loadView() + super.viewDidLoad() + ready.set(controller.ready.get()) + + themeDisposable.set(appearanceSignal.start(next: { [weak self] appearance in + self?.modal?.updateLocalizationAndTheme(theme: appearance.presentation) + self?.controller.updateLocalizationAndTheme(theme: appearance.presentation) + })) + + controller.modalTransitionHandler = { [weak self] animated in + if self?.dynamicSize == true { + self?.updateSize(animated) + } + } + } + deinit { + themeDisposable.dispose() + } +} + + + + +final class InputDataArguments { + let select:((InputDataIdentifier, InputDataValue))->Void + let dataUpdated:()->Void + init(select: @escaping((InputDataIdentifier, InputDataValue))->Void, dataUpdated:@escaping()->Void) { + self.select = select + self.dataUpdated = dataUpdated + } +} + +private let queue: Queue = Queue(name: "InputDataItemsQueue", qos: DispatchQoS.background) + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], animated: Bool, searchState: TableSearchViewState?, initialSize:NSSize, arguments: InputDataArguments, onMainQueue: Bool) -> Signal { + return Signal { subscriber in + + func makeItem(_ entry: InputDataEntry) -> TableRowItem { + return entry.item(arguments: arguments, initialSize: initialSize) + } + + let applyQueue = onMainQueue ? .mainQueue() : prepareQueue + + let cancelled: Atomic = Atomic(value: false) + + if Thread.isMainThread { + var initialIndex:Int = 0 + var height:CGFloat = 0 + var firstInsertion:[(Int, TableRowItem)] = [] + let entries = Array(right) + + let index:Int = 0 + + for i in index ..< entries.count { + let item = makeItem(entries[i].entry) + height += item.height + firstInsertion.append((i, item)) + if initialSize.height < height { + break + } + } + initialIndex = firstInsertion.count + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state: .none(nil), searchState: searchState)) + + queue.async { + if !cancelled.with({ $0 }) { + + var insertions:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + + for i in initialIndex ..< entries.count { + let item:TableRowItem + item = makeItem(entries[i].entry) + insertions.append((i, item)) + if cancelled.with({ $0 }) { + break + } + } + if !cancelled.with({ $0 }) { + applyQueue.async { + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: insertions, updated: updates, state: .none(nil), searchState: searchState)) + subscriber.putCompletion() + } + } + } + } + } else { + queue.async { + let (deleted,inserted,updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in + if !cancelled.with({ $0 }) { + return makeItem(entry.entry) + } else { + return TableRowItem(.zero) + } + }) + if !cancelled.with({ $0 }) { + applyQueue.async { + subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated, state: .none(nil), searchState: searchState)) + subscriber.putCompletion() + } + } + } + + } + + return ActionDisposable { + _ = cancelled.swap(true) + } + } |> runOn(onMainQueue ? .mainQueue() : prepareQueue) + + + +// +// let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in +// return entry.entry.item(arguments: arguments, initialSize: initialSize) +// } +// return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: animated, searchState: searchState) +} + + +enum InputDataReturnResult { + case `default` + case nextResponder + case invokeEvent + case nothing +} + +enum InputDataDeleteResult { + case `default` + case invoked +} + +struct InputDataSignalValue { + let entries: [InputDataEntry] + let animated: Bool + let searchState: TableSearchViewState? + init(entries: [InputDataEntry], animated: Bool = true, searchState: TableSearchViewState? = nil) { + self.entries = entries + self.animated = animated + self.searchState = searchState + } +} + +final class InputDataView : BackgroundView, AppearanceViewProtocol { + let tableView = TableView() + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tableView) + tableView.frame = bounds + } + + func updateLocalizationAndTheme(theme: PresentationTheme) { + tableView.updateLocalizationAndTheme(theme: theme) + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func layout() { + super.layout() + tableView.frame = bounds + } +} + +class InputDataController: GenericViewController { + + fileprivate var modalTransitionHandler:((Bool)->Void)? = nil + fileprivate var prepareAllItems: Bool = false + + private let values: Promise = Promise() + private let disposable = MetaDisposable() + private let appearanceDisposablet = MetaDisposable() + private let title: String + var validateData:([InputDataIdentifier : InputDataValue]) -> InputDataValidation + var afterDisappear: ()->Void + var updateDatas:([InputDataIdentifier : InputDataValue]) -> InputDataValidation + var didLoaded:(InputDataController, [InputDataIdentifier : InputDataValue]) -> Void + private let _removeAfterDisappear: Bool + private let hasDone: Bool + var updateDoneValue:([InputDataIdentifier : InputDataValue])->((InputDoneValue)->Void)->Void + var customRightButton:((ViewController)->BarView?)? + var updateRightBarView:((BarView)->Void)? + var afterTransaction: (InputDataController)->Void + var backInvocation: ([InputDataIdentifier : InputDataValue], @escaping(Bool)->Void)->Void + var returnKeyInvocation:(InputDataIdentifier?, NSEvent) -> InputDataReturnResult + var deleteKeyInvocation:(InputDataIdentifier?) -> InputDataDeleteResult + var tabKeyInvocation:(InputDataIdentifier?) -> InputDataDeleteResult + var rightModalHeader: ModalHeaderData? = nil + var leftModalHeader: ModalHeaderData? = nil + var centerModalHeader: ModalHeaderData? = nil + var keyWindowUpdate:(Bool, InputDataController) -> Void = { _, _ in } + var hasBackSwipe:()->Bool = { return true } + private let searchKeyInvocation:() -> InputDataDeleteResult + var getBackgroundColor: ()->NSColor + let identifier: String + var onDeinit:(()->Void)? + var ignoreRightBarHandler: Bool = false + + var contextOject: Any? + + var _abolishWhenNavigationSame: Bool = false + + init(dataSignal:Signal, title: String, validateData:@escaping([InputDataIdentifier : InputDataValue]) -> InputDataValidation = {_ in return .fail(.none)}, updateDatas: @escaping([InputDataIdentifier : InputDataValue]) -> InputDataValidation = {_ in return .fail(.none)}, afterDisappear: @escaping() -> Void = {}, didLoaded: @escaping(InputDataController, [InputDataIdentifier : InputDataValue]) -> Void = { _, _ in}, updateDoneValue:@escaping([InputDataIdentifier : InputDataValue])->((InputDoneValue)->Void)->Void = { _ in return {_ in}}, removeAfterDisappear: Bool = true, hasDone: Bool = true, identifier: String = "", customRightButton: ((ViewController)->BarView?)? = nil, afterTransaction: @escaping(InputDataController)->Void = { _ in }, backInvocation: @escaping([InputDataIdentifier : InputDataValue], @escaping(Bool)->Void)->Void = { $1(true) }, returnKeyInvocation: @escaping(InputDataIdentifier?, NSEvent) -> InputDataReturnResult = {_, _ in return .default }, deleteKeyInvocation: @escaping(InputDataIdentifier?) -> InputDataDeleteResult = {_ in return .default }, tabKeyInvocation: @escaping(InputDataIdentifier?) -> InputDataDeleteResult = {_ in return .default }, searchKeyInvocation: @escaping() -> InputDataDeleteResult = { return .default }, getBackgroundColor: @escaping()->NSColor = { theme.colors.listBackground }) { + self.title = title + self.validateData = validateData + self.afterDisappear = afterDisappear + self.updateDatas = updateDatas + self.didLoaded = didLoaded + self.identifier = identifier + self._removeAfterDisappear = removeAfterDisappear + self.hasDone = hasDone + self.updateDoneValue = updateDoneValue + self.customRightButton = customRightButton + self.afterTransaction = afterTransaction + self.backInvocation = backInvocation + self.returnKeyInvocation = returnKeyInvocation + self.deleteKeyInvocation = deleteKeyInvocation + self.tabKeyInvocation = tabKeyInvocation + self.searchKeyInvocation = searchKeyInvocation + self.getBackgroundColor = getBackgroundColor + super.init() + values.set(dataSignal) + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + self.genericView.updateLocalizationAndTheme(theme: theme) + requestUpdateBackBar() + requestUpdateCenterBar() + requestUpdateRightBar() + } + + override func requestUpdateRightBar() { + super.requestUpdateRightBar() + self.updateRightBarView?(self.rightBarView) + } + + + + override var defaultBarTitle: String { + return title + } + + override func getRightBarViewOnce() -> BarView { + return customRightButton?(self) ?? (hasDone ? TextButtonBarView(controller: self, text: L10n.navigationDone, style: navigationButtonStyle, alignment:.Right) : super.getRightBarViewOnce()) + } + + private var doneView: TextButtonBarView { + return rightBarView as! TextButtonBarView + } + + override var responderPriority: HandlerPriority { + return .modal + } + var tableView: TableView { + return self.genericView.tableView + } + + func fetchData() -> [InputDataIdentifier : InputDataValue] { + var values:[InputDataIdentifier : InputDataValue] = [:] + tableView.enumerateItems { item -> Bool in + if let identifier = (item.stableId.base as? InputDataEntryId)?.identifier { + if let item = item as? InputDataRowDataValue { + values[identifier] = item.value + } + } + return true + } + return values + } + + private func findItem(for identifier: InputDataIdentifier) -> TableRowItem? { + var item: TableRowItem? + tableView.enumerateItems { current -> Bool in + if let stableId = current.stableId.base as? InputDataEntryId { + if stableId.identifier == identifier { + item = current + } + } + return item == nil + } + return item + } + + func makeFirstResponderIfPossible(for identifier: InputDataIdentifier, focusIdentifier: InputDataIdentifier? = nil, scrollDown: Bool = false, scrollIfNeeded: Bool = true) { + if let item = findItem(for: identifier) { + _ = window?.makeFirstResponder(findItem(for: identifier)?.view?.firstResponder) + + if let focusIdentifier = focusIdentifier { + if let item = findItem(for: focusIdentifier) { + tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), inset: NSEdgeInsets(), true) + } + } else if scrollIfNeeded { + if !scrollDown { + tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), inset: NSEdgeInsets(), true) + } else { + tableView.scroll(to: .down(true)) + } + } + } + } + + func proccessValidation(_ validation: InputDataValidation) { + var scrollFirstItem: TableRowItem? = nil + switch validation { + case let .fail(fail): + switch fail { + case let .alert(text): + alert(for: mainWindow, info: text) + case let .fields(fields): + for (identifier, action) in fields { + switch action { + case .shake: + let item = findItem(for: identifier) + item?.view?.shakeView() + if scrollFirstItem == nil { + var invoked: Bool = false + scrollFirstItem = item + if let item = item, !invoked { + tableView.scroll(to: .top(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), inset: NSEdgeInsets(), timingFunction: .linear, true) + invoked = true + } + } + case let .shakeWithData(data): + let item = findItem(for: identifier) + item?.view?.shakeViewWithData(data) + } + } + case let .doSomething(next): + next { [weak self] validation in + self?.proccessValidation(validation) + } + default: + //TODO IF NEEDED + break + } + case let .success(behaviour): + switch behaviour { + case .navigationBack: + navigationController?.back() + case .navigationBackWithPushAnimation: + navigationController?.back(animationStyle: .push) + case let .custom(action): + action() + } + case .none: + break + } + } + + func validateInputValues() { + self.proccessValidation(self.validateData(self.fetchData())) + } + + private func validateInput(data: [InputDataIdentifier : InputDataValue]) { + proccessValidation(self.validateData(data)) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.tableView.getBackgroundColor = self.getBackgroundColor + + + appearanceDisposablet.set(appearanceSignal.start(next: { [weak self] _ in + self?.updateLocalizationAndTheme(theme: theme) + })) + + let arguments = InputDataArguments(select: { [weak self] (identifier, value) in + guard let `self` = self else {return} + self.validateInput(data: [identifier : value]) + }, dataUpdated: { [weak self] in + guard let `self` = self else {return} + self.proccessValidation(self.updateDatas(self.fetchData())) + }) + + self.rightBarView.set(handler:{ [weak self] _ in + guard let `self` = self else {return} + if !self.ignoreRightBarHandler { + self.validateInput(data: self.fetchData()) + } + }, for: .Click) + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + let initialSize = self.atomicSize + + let onMainQueue: Atomic = Atomic(value: !prepareAllItems) + + let signal: Signal = combineLatest(queue: .mainQueue(), appearanceSignal, values.get()) |> mapToQueue { appearance, state in + let entries = state.entries.map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) + return prepareTransition(left: previous.swap(entries), right: entries, animated: state.animated, searchState: state.searchState, initialSize: initialSize.modify{$0}, arguments: arguments, onMainQueue: onMainQueue.swap(false)) + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] transition in + guard let `self` = self else {return} + self.tableView.merge(with: transition) + + + let result = self.updateDoneValue(self.fetchData()) + result { [weak self] value in + guard let `self` = self else {return} + switch value { + case let .disabled(text): + self.doneView.isHidden = false + self.doneView.isLoading = false + self.doneView.isEnabled = false + self.doneView.set(text: text, for: .Normal) + case let .enabled(text): + self.doneView.isHidden = false + self.doneView.isLoading = false + self.doneView.isEnabled = true + self.doneView.set(text: text, for: .Normal) + case .loading: + self.doneView.isHidden = false + self.doneView.isLoading = true + case .invisible: + self.doneView.isHidden = true + } + + } + + self.afterTransaction(self) + self.modalTransitionHandler?(transition.animated) + + let wasReady: Bool = self.didSetReady + self.readyOnce() + if !wasReady { + self.didLoaded(self, self.fetchData()) + } + })) + } + + override func returnKeyAction() -> KeyHandlerResult { + if let event = NSApp.currentEvent { + switch returnKeyInvocation(self.currentFirstResponderIdentifier, event) { + case .default: + self.validateInput(data: self.fetchData()) + return .invoked + case .nextResponder: + _ = window?.makeFirstResponder(self.nextResponder()) + return .invoked + case .nothing: + return .invoked + case .invokeEvent: + return .invokeNext + } + } + return .invokeNext + } + + override func becomeFirstResponder() -> Bool? { + return true + } + + override var canBecomeResponder: Bool { + return true + } + + override func didRemovedFromStack() { + super.didRemovedFromStack() + afterDisappear() + } + private var firstTake: Bool = true + + override func firstResponder() -> NSResponder? { + if self.window?.firstResponder == self.window || self.window?.firstResponder == tableView.documentView { + var first: NSResponder? = nil + tableView.enumerateViews { view -> Bool in + first = view.firstResponder + if first != nil, self.firstTake { + if let item = view.item as? InputDataRowDataValue { + switch item.value { + case let .string(value): + let value = value ?? "" + if !value.isEmpty { + return true + } + default: + break + } + } + } + return first == nil + } + self.firstTake = false + return first + } + return window?.firstResponder + } + + override func backSettings() -> (String, CGImage?) { + + return super.backSettings() + } + + override var enableBack: Bool { + return true + } + // private var canInvokeBack: Bool = false + override func invokeNavigationBack() -> Bool { + return true + } + + override func executeReturn() { + backInvocation(fetchData(), { [weak self] result in + if result { + self?.navigationController?.back() + } + }) + } + + override func getLeftBarViewOnce() -> BarView { + if let navigation = navigationController { + return navigation.empty === self ? BarView(controller: self) : super.getLeftBarViewOnce() + } + return BarView(controller: self) + } + + override var haveNextResponder: Bool { + return true + } + + override var abolishWhenNavigationSame: Bool { + return _abolishWhenNavigationSame + } + + override func backKeyAction() -> KeyHandlerResult { + return .invokeNext + } + + override func viewDidAppear(_ animated: Bool) { + _ = self.window?.makeFirstResponder(nil) + super.viewDidAppear(animated) + + window?.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + let index = self.tableView.row(at: self.tableView.documentView!.convert(event.locationInWindow, from: nil)) + + if index > -1, let view = self.tableView.item(at: index).view { + if view.mouseInsideField { + if self.window?.firstResponder != view.firstResponder { + _ = self.window?.makeFirstResponder(view.firstResponder) + return .invoked + } + } + } + + return .invokeNext + }, with: self, for: .leftMouseUp, priority: self.responderPriority) + + + window?.set(handler: { [weak self] in + guard let `self` = self else {return .rejected} + + switch self.deleteKeyInvocation(self.currentFirstResponderIdentifier) { + case .default: + return .rejected + case .invoked: + return .invoked + } + + }, with: self, for: .Delete, priority: self.responderPriority, modifierFlags: nil) + + + window?.set(handler: { [weak self] in + guard let `self` = self else {return .rejected} + + switch self.searchKeyInvocation() { + case .default: + return .rejected + case .invoked: + return .invoked + } + + }, with: self, for: .F, priority: self.responderPriority, modifierFlags: nil) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + let view = self?.findReponsderView as? InputDataRowView + + view?.makeBold() + return .invoked + }, with: self, for: .B, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + let view = self?.findReponsderView as? InputDataRowView + view?.makeUrl() + return .invoked + }, with: self, for: .U, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + let view = self?.findReponsderView as? InputDataRowView + view?.makeItalic() + return .invoked + }, with: self, for: .I, priority: self.responderPriority, modifierFlags: [.command]) + + + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + let view = self?.findReponsderView as? InputDataRowView + view?.makeMonospace() + return .invoked + }, with: self, for: .K, priority: responderPriority, modifierFlags: [.command, .shift]) + + } + + + + var findReponsderView: TableRowView? { + if let view = self.firstResponder() as? NSView { + var superview: NSView? = view + while superview != nil { + if let current = superview as? TableRowView { + return current + } else { + superview = superview?.superview + } + } + } + return nil + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + + var currentFirstResponderIdentifier: InputDataIdentifier? { + var identifier: InputDataIdentifier? = nil + + tableView.enumerateViews { view -> Bool in + if view.hasFirstResponder() { + if view.firstResponder == view.window?.firstResponder { + identifier = (view.item?.stableId.base as? InputDataEntryId)?.identifier + } + + } + return identifier == nil + } + return identifier + } + + override var supportSwipes: Bool { + let horizontal = HackUtils.findElements(byClass: "TGUIKit.HorizontalTableView", in: genericView.tableView)?.first as? HorizontalTableView + if let horizontal = horizontal { + return !horizontal._mouseInside() + } + return self.hasBackSwipe() + } + + override func nextResponder() -> NSResponder? { + var next: NSResponder? + let current = self.window?.firstResponder + + + + var selectNext: Bool = false + + var first: NSResponder? = nil + + + tableView.enumerateViews { view -> Bool in + if view.hasFirstResponder() { + first = view.firstResponder + } + return first == nil + } + + tableView.enumerateViews { view -> Bool in + if view.hasFirstResponder() { + if selectNext { + next = view.firstResponder + } else if view.firstResponder == current || view.firstResponder == (current as? NSView)?.superview?.superview { + if let nextInner = view.nextResponder() { + next = nextInner + return false + } + selectNext = true + return true + } + } + return next == nil + } + + + return next ?? first + } + + override var removeAfterDisapper: Bool { + return _removeAfterDisappear + } + + override func windowDidBecomeKey() { + self.keyWindowUpdate(true, self) + } + + override func windowDidResignKey() { + self.keyWindowUpdate(false, self) + } + + override func escapeKeyAction() -> KeyHandlerResult { + self.executeReturn() + return .invoked + } + + deinit { + disposable.dispose() + appearanceDisposablet.dispose() + onDeinit?() + } + +} diff --git a/Telegram-Mac/InputDataControllerEntries.swift b/Telegram-Mac/InputDataControllerEntries.swift new file mode 100644 index 0000000000..36780e3898 --- /dev/null +++ b/Telegram-Mac/InputDataControllerEntries.swift @@ -0,0 +1,668 @@ +// +// InputDataControllerEntries.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + +enum InputDataEntryId : Hashable { + case desc(Int32) + case input(InputDataIdentifier) + case general(InputDataIdentifier) + case selector(InputDataIdentifier) + case dataSelector(InputDataIdentifier) + case dateSelector(InputDataIdentifier) + case custom(InputDataIdentifier) + case search(InputDataIdentifier) + case loading + case sectionId(Int32) + var hashValue: Int { + return 0 + } + + var identifier: InputDataIdentifier? { + switch self { + case let .input(identifier), let .selector(identifier), let .dataSelector(identifier), let .dateSelector(identifier), let .general(identifier), let .custom(identifier), let .search(identifier): + return identifier + default: + return nil + } + } +} + + +enum InputDataInputMode : Equatable { + case plain + case secure +} + + + +public protocol _HasCustomInputDataEquatableRepresentation { + func _toCustomInputDataEquatable() -> InputDataEquatable? +} + +internal protocol _InputDataEquatableBox { + var _typeID: ObjectIdentifier { get } + func _unbox() -> T? + + func _isEqual(to: _InputDataEquatableBox) -> Bool? + + var _base: Any { get } + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool +} + +internal struct _ConcreteEquatableBox : _InputDataEquatableBox { + internal var _baseEquatable: Base + + internal init(_ base: Base) { + self._baseEquatable = base + } + + + internal var _typeID: ObjectIdentifier { + return ObjectIdentifier(type(of: self)) + } + + internal func _unbox() -> T? { + return (self as _InputDataEquatableBox as? _ConcreteEquatableBox)?._baseEquatable + } + + internal func _isEqual(to rhs: _InputDataEquatableBox) -> Bool? { + if let rhs: Base = rhs._unbox() { + return _baseEquatable == rhs + } + return nil + } + + internal var _base: Any { + return _baseEquatable + } + + internal + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool { + guard let value = _baseEquatable as? T else { return false } + result.initialize(to: value) + return true + } +} + + +public struct InputDataEquatable { + internal var _box: _InputDataEquatableBox + internal var _usedCustomRepresentation: Bool + + + public init(_ base: H) { + if let customRepresentation = + (base as? _HasCustomInputDataEquatableRepresentation)?._toCustomInputDataEquatable() { + self = customRepresentation + self._usedCustomRepresentation = true + return + } + + self._box = _ConcreteEquatableBox(base) + self._usedCustomRepresentation = false + } + + internal init(_usingDefaultRepresentationOf base: H) { + self._box = _ConcreteEquatableBox(base) + self._usedCustomRepresentation = false + } + + public var base: Any { + return _box._base + } + internal + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool { + // Attempt the downcast. + if _box._downCastConditional(into: result) { return true } + + + + return false + } +} + +extension InputDataEquatable : Equatable { + public static func == (lhs: InputDataEquatable, rhs: InputDataEquatable) -> Bool { + if let result = lhs._box._isEqual(to: rhs._box) { return result } + + return false + } +} + +extension InputDataEquatable : CustomStringConvertible { + public var description: String { + return String(describing: base) + } +} + +extension InputDataEquatable : CustomDebugStringConvertible { + public var debugDescription: String { + return "InputDataEquatable(" + String(reflecting: base) + ")" + } +} + +extension InputDataEquatable : CustomReflectable { + public var customMirror: Mirror { + return Mirror( + self, + children: ["value": base]) + } +} + + + + +public // COMPILER_INTRINSIC +func _convertToInputDataEquatable(_ value: H) -> InputDataEquatable { + return InputDataEquatable(value) +} + +internal func _convertToInputDataEquatableIndirect( + _ value: H, + _ target: UnsafeMutablePointer + ) { + target.initialize(to: InputDataEquatable(value)) +} + +internal func _InputDataEquatableDownCastConditionalIndirect( + _ value: UnsafePointer, + _ target: UnsafeMutablePointer + ) -> Bool { + return value.pointee._downCastConditional(into: target) +} + + +struct InputDataInputPlaceholder : Equatable { + let placeholder: String? + let drawBorderAfterPlaceholder: Bool + let icon: CGImage? + let action: (()-> Void)? + let hasLimitationText: Bool + let insets: NSEdgeInsets + init(_ placeholder: String? = nil, icon: CGImage? = nil, drawBorderAfterPlaceholder: Bool = false, hasLimitationText: Bool = false, insets: NSEdgeInsets = NSEdgeInsets(), action: (()-> Void)? = nil) { + self.drawBorderAfterPlaceholder = drawBorderAfterPlaceholder + self.hasLimitationText = hasLimitationText + self.placeholder = placeholder + self.icon = icon + self.action = action + self.insets = insets + } + + static func ==(lhs: InputDataInputPlaceholder, rhs: InputDataInputPlaceholder) -> Bool { + return lhs.placeholder == rhs.placeholder && lhs.icon === rhs.icon && lhs.drawBorderAfterPlaceholder == rhs.drawBorderAfterPlaceholder && lhs.insets == rhs.insets + } +} + + +final class InputDataGeneralData : Equatable { + let name: String + let color: NSColor + let icon: CGImage? + let type: GeneralInteractedType + let viewType: GeneralViewType + let description: String? + let action: (()->Void)? + let disabledAction:(()->Void)? + let enabled: Bool + let justUpdate: Int64? + let menuItems:(()->[ContextMenuItem])? + init(name: String, color: NSColor, icon: CGImage? = nil, type: GeneralInteractedType = .none, viewType: GeneralViewType = .legacy, enabled: Bool = true, description: String? = nil, justUpdate: Int64? = nil, action: (()->Void)? = nil, disabledAction: (()->Void)? = nil, menuItems:(()->[ContextMenuItem])? = nil) { + self.name = name + self.color = color + self.icon = icon + self.type = type + self.viewType = viewType + self.description = description + self.action = action + self.enabled = enabled + self.justUpdate = justUpdate + self.disabledAction = disabledAction + self.menuItems = menuItems + } + + static func ==(lhs: InputDataGeneralData, rhs: InputDataGeneralData) -> Bool { + return lhs.name == rhs.name && lhs.icon === rhs.icon && lhs.color.hexString == rhs.color.hexString && lhs.type == rhs.type && lhs.description == rhs.description && lhs.viewType == rhs.viewType && lhs.enabled == rhs.enabled && lhs.justUpdate == rhs.justUpdate + } +} + +final class InputDataTextInsertAnimatedViewData : NSObject { + let context: AccountContext + let file: TelegramMediaFile + init(context: AccountContext, file: TelegramMediaFile) { + self.context = context + self.file = file + } + static func == (lhs: InputDataTextInsertAnimatedViewData, rhs: InputDataTextInsertAnimatedViewData) -> Bool { + return lhs.file == rhs.file + } + static var attributeKey: NSAttributedString.Key { + return NSAttributedString.Key("InputDataTextInsertAnimatedDataKey") + } +} + +struct InputDataGeneralTextRightData : Equatable { + let isLoading: Bool + let text: NSAttributedString? + init(isLoading: Bool, text: NSAttributedString?) { + self.isLoading = isLoading + self.text = text + } +} + +final class InputDataGeneralTextData : Equatable { + let color: NSColor + let detectBold: Bool + let viewType: GeneralViewType + let rightItem: InputDataGeneralTextRightData + let fontSize: CGFloat? + init(color: NSColor = theme.colors.listGrayText, detectBold: Bool = true, viewType: GeneralViewType = .legacy, rightItem: InputDataGeneralTextRightData = InputDataGeneralTextRightData(isLoading: false, text: nil), fontSize: CGFloat? = nil) { + self.color = color + self.detectBold = detectBold + self.viewType = viewType + self.rightItem = rightItem + self.fontSize = fontSize + } + static func ==(lhs: InputDataGeneralTextData, rhs: InputDataGeneralTextData) -> Bool { + return lhs.color == rhs.color && lhs.detectBold == rhs.detectBold && lhs.viewType == rhs.viewType && lhs.rightItem == rhs.rightItem && lhs.fontSize == rhs.fontSize + } +} + +final class InputDataRowData : Equatable { + let viewType: GeneralViewType + let rightItem: InputDataRightItem? + let defaultText: String? + let pasteFilter:((String)->(Bool, String))? + let maxBlockWidth: CGFloat? + let canMakeTransformations: Bool + init(viewType: GeneralViewType = .legacy, rightItem: InputDataRightItem? = nil, defaultText: String? = nil, maxBlockWidth: CGFloat? = nil, canMakeTransformations: Bool = false, pasteFilter:((String)->(Bool, String))? = nil) { + self.viewType = viewType + self.rightItem = rightItem + self.defaultText = defaultText + self.pasteFilter = pasteFilter + self.maxBlockWidth = maxBlockWidth + self.canMakeTransformations = canMakeTransformations + } + static func ==(lhs: InputDataRowData, rhs: InputDataRowData) -> Bool { + return lhs.viewType == rhs.viewType && lhs.rightItem == rhs.rightItem && lhs.defaultText == rhs.defaultText && lhs.maxBlockWidth == rhs.maxBlockWidth && lhs.canMakeTransformations == rhs.canMakeTransformations + } +} + +enum InputDataSectionType : Equatable { + case normal + case legacy + case custom(CGFloat) + case customModern(CGFloat) + var height: CGFloat { + switch self { + case .normal: + return 30 + case .legacy: + return 20 + case let .custom(height): + return height + case let .customModern(height): + return height + } + } +} + +enum InputDataEntry : Identifiable, Comparable { + case desc(sectionId: Int32, index: Int32, text: GeneralRowTextType, data: InputDataGeneralTextData) + case input(sectionId: Int32, index: Int32, value: InputDataValue, error: InputDataValueError?, identifier: InputDataIdentifier, mode: InputDataInputMode, data: InputDataRowData, placeholder: InputDataInputPlaceholder?, inputPlaceholder: String, filter:(String)->String, limit: Int32) + case general(sectionId: Int32, index: Int32, value: InputDataValue, error: InputDataValueError?, identifier: InputDataIdentifier, data: InputDataGeneralData) + case dateSelector(sectionId: Int32, index: Int32, value: InputDataValue, error: InputDataValueError?, identifier: InputDataIdentifier, placeholder: String) + case selector(sectionId: Int32, index: Int32, value: InputDataValue, error: InputDataValueError?, identifier: InputDataIdentifier, placeholder: String, values:[ValuesSelectorValue]) + case dataSelector(sectionId: Int32, index: Int32, value: InputDataValue, error: InputDataValueError?, identifier: InputDataIdentifier, placeholder: String, description: String?, icon: CGImage?, action:()->Void) + case custom(sectionId: Int32, index: Int32, value: InputDataValue, identifier: InputDataIdentifier, equatable: InputDataEquatable?, item:(NSSize, InputDataEntryId)->TableRowItem) + case search(sectionId: Int32, index: Int32, value: InputDataValue, identifier: InputDataIdentifier, update:(SearchState)->Void) + case loading + case sectionId(Int32, type: InputDataSectionType) + + var stableId: InputDataEntryId { + switch self { + case let .desc(_, index, _, _): + return .desc(index) + case let .input(_, _, _, _, identifier, _, _, _, _, _, _): + return .input(identifier) + case let .general(_, _, _, _, identifier, _): + return .general(identifier) + case let .selector(_, _, _, _, identifier, _, _): + return .selector(identifier) + case let .dataSelector(_, _, _, _, identifier, _, _, _, _): + return .dataSelector(identifier) + case let .dateSelector(_, _, _, _, identifier, _): + return .dateSelector(identifier) + case let .custom(_, _, _, identifier, _, _): + return .custom(identifier) + case let .search(_, _, _, identifier, _): + return .custom(identifier) + case let .sectionId(index, _): + return .sectionId(index) + case .loading: + return .loading + } + } + + var stableIndex: Int32 { + switch self { + case let .desc(_, index, _, _): + return index + case let .input(_, index, _, _, _, _, _, _, _, _, _): + return index + case let .general(_, index, _, _, _, _): + return index + case let .selector(_, index, _, _, _, _, _): + return index + case let .dateSelector(_, index, _, _, _, _): + return index + case let .dataSelector(_, index, _, _, _, _, _, _, _): + return index + case let .custom(_, index, _, _, _, _): + return index + case let .search(_, index, _, _, _): + return index + case .loading: + return 0 + case .sectionId: + fatalError() + } + } + + var sectionIndex: Int32 { + switch self { + case let .desc(index, _, _, _): + return index + case let .input(index, _, _, _, _, _, _, _, _, _, _): + return index + case let .selector(index, _, _, _, _, _, _): + return index + case let .general(index, _, _, _, _, _): + return index + case let .dateSelector(index, _, _, _, _, _): + return index + case let .dataSelector(index, _, _, _, _, _, _, _, _): + return index + case let .custom(index, _, _, _, _, _): + return index + case let .search(index, _, _, _, _): + return index + case .loading: + return 0 + case .sectionId: + fatalError() + } + } + + var index: Int32 { + switch self { + case let .sectionId(sectionId, _): + return (sectionId + 1) * 1000 - sectionId + default: + return (sectionIndex * 1000) + stableIndex + } + } + + func item(arguments: InputDataArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .sectionId(_, type): + let viewType: GeneralViewType + switch type { + case .legacy, .custom: + viewType = .legacy + default: + viewType = .separator + } + return GeneralRowItem(initialSize, height: type.height, stableId: stableId, viewType: viewType) + case let .desc(_, _, text, data): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, detectBold: data.detectBold, textColor: data.color, viewType: data.viewType, rightItem: data.rightItem, fontSize: data.fontSize) + case let .custom(_, _, _, _, _, item): + return item(initialSize, stableId) + case let .selector(_, _, value, error, _, placeholder, values): + return InputDataDataSelectorRowItem(initialSize, stableId: stableId, value: value, error: error, placeholder: placeholder, updated: arguments.dataUpdated, values: values) + case let .dataSelector(_, _, _, error, _, placeholder, description, icon, action): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: placeholder, icon: icon, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.accent), description: description, type: .none, action: action, error: error) + case let .general(_, _, value, error, identifier, data): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: data.name, icon: data.icon, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: data.color), description: data.description, type: data.type, viewType: data.viewType, action: { + data.action != nil ? data.action?() : arguments.select((identifier, value)) + }, enabled: data.enabled, error: error, disabledAction: data.disabledAction ?? {}, menuItems: data.menuItems) + case let .dateSelector(_, _, value, error, _, placeholder): + return InputDataDateRowItem(initialSize, stableId: stableId, value: value, error: error, updated: arguments.dataUpdated, placeholder: placeholder) + case let .input(_, _, value, error, _, mode, data, placeholder, inputPlaceholder, filter, limit: limit): + return InputDataRowItem(initialSize, stableId: stableId, mode: mode, error: error, viewType: data.viewType, currentText: value.stringValue ?? "", currentAttributedText: value.attributedString, placeholder: placeholder, inputPlaceholder: inputPlaceholder, defaultText: data.defaultText, rightItem: data.rightItem, canMakeTransformations: data.canMakeTransformations, maxBlockWidth: data.maxBlockWidth, filter: filter, updated: { _ in + arguments.dataUpdated() + }, pasteFilter: data.pasteFilter, limit: limit) + case .loading: + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) + case let .search(_, _, value, _, update): + return SearchRowItem(initialSize, stableId: stableId, searchInteractions: SearchInteractions({ state, _ in + update(state) + }, { state in + update(state) + }), inset: NSEdgeInsets(left: 10,right: 10, top: 10, bottom: 10)) + } + } +} + +func <(lhs: InputDataEntry, rhs: InputDataEntry) -> Bool { + return lhs.index < rhs.index +} + +func ==(lhs: InputDataEntry, rhs: InputDataEntry) -> Bool { + switch lhs { + case let .desc(sectionId, index, text, data): + if case .desc(sectionId, index, text, data) = rhs { + return true + } else { + return false + } + case let .input(sectionId, index, lhsValue, lhsError, identifier, mode, data, placeholder, inputPlaceholder, _, limit): + if case .input(sectionId, index, let rhsValue, let rhsError, identifier, mode, data, placeholder, inputPlaceholder, _, limit) = rhs { + return lhsValue == rhsValue && lhsError == rhsError + } else { + return false + } + case let .general(sectionId, index, lhsValue, lhsError, identifier, data): + if case .general(sectionId, index, let rhsValue, let rhsError, identifier, data) = rhs { + return lhsValue == rhsValue && lhsError == rhsError + } else { + return false + } + case let .selector(sectionId, index, lhsValue, lhsError, identifier, placeholder, lhsValues): + if case .selector(sectionId, index, let rhsValue, let rhsError, identifier, placeholder, let rhsValues) = rhs { + return lhsValues == rhsValues && lhsValue == rhsValue && lhsError == rhsError + } else { + return false + } + case let .dateSelector(sectionId, index, lhsValue, lhsError, identifier, placeholder): + if case .dateSelector(sectionId, index, let rhsValue, let rhsError, identifier, placeholder) = rhs { + return lhsValue == rhsValue && lhsError == rhsError + } else { + return false + } + case let .dataSelector(sectionId, index, lhsValue, lhsError, identifier, placeholder, description, lhsIcon, _): + if case .dataSelector(sectionId, index, let rhsValue, let rhsError, identifier, placeholder, description, let rhsIcon, _) = rhs { + return lhsValue == rhsValue && lhsError == rhsError && lhsIcon == rhsIcon + } else { + return false + } + case let .custom(sectionId, index, value, identifier, lhsEquatable, _): + if case .custom(sectionId, index, value, identifier, let rhsEquatable, _) = rhs { + return lhsEquatable == rhsEquatable + } else { + return false + } + case let .search(sectionId, index, value, identifier, _): + if case .search(sectionId, index, value, identifier, _) = rhs { + return true + } else { + return false + } + case let .sectionId(id, type): + if case .sectionId(id, type) = rhs { + return true + } else { + return false + } + case .loading: + if case .loading = rhs { + return true + } else { + return false + } + } +} + +let InputDataEmptyIdentifier = InputDataIdentifier("") + +struct InputDataIdentifier : Hashable { + let identifier: String + init(_ identifier: String) { + self.identifier = identifier + } + var hashValue: Int { + return identifier.hashValue + } +} + +func ==(lhs: InputDataIdentifier, rhs: InputDataIdentifier) -> Bool { + return lhs.identifier == rhs.identifier +} + +enum InputDataValue : Equatable { + case string(String?) + case attributedString(NSAttributedString?) + case date(Int32?, Int32?, Int32?) + case gender(SecureIdGender?) + case secureIdDocument(SecureIdVerificationDocument) + case none + var stringValue: String? { + switch self { + case let .string(value): + return value + default: + return nil + } + } + + var attributedString:NSAttributedString? { + switch self { + case let .attributedString(value): + return value + default: + return nil + } + } + + var secureIdDocument: SecureIdVerificationDocument? { + switch self { + case let .secureIdDocument(document): + return document + default: + return nil + } + } + + var gender: SecureIdGender? { + switch self { + case let .gender(gender): + return gender + default: + return nil + } + } +} + +func ==(lhs: InputDataValue, rhs: InputDataValue) -> Bool { + switch lhs { + case let .string(lhsValue): + if case let .string(rhsValue) = rhs { + return lhsValue == rhsValue + } else { + return false + } + case let .attributedString(lhsValue): + if case let .attributedString(rhsValue) = rhs { + return lhsValue == rhsValue + } else { + return false + } + case let .gender(lhsValue): + if case let .gender(rhsValue) = rhs { + return lhsValue == rhsValue + } else { + return false + } + case let .secureIdDocument(lhsValue): + if case let .secureIdDocument(rhsValue) = rhs { + return lhsValue.isEqual(to: rhsValue) + } else { + return false + } + case let .date(lhsDay, lhsMonth, lhsYear): + if case let .date(rhsDay, rhsMonth,rhsYear) = rhs { + return lhsDay == rhsDay && lhsMonth == rhsMonth && lhsYear == rhsYear + } else { + return false + } + case .none: + if case .none = rhs { + return true + } else { + return false + } + } +} + +enum InputDataValidationFailAction { + case shake + case shakeWithData(Any) +} + +enum InputDataValidationBehaviour { + case navigationBack + case navigationBackWithPushAnimation + case custom(()->Void) +} + +enum InputDataFailResult { + case alert(String) + case doSomething(next: (@escaping(InputDataValidation)->Void) -> Void) + case textAfter(String, InputDataIdentifier) + case fields([InputDataIdentifier: InputDataValidationFailAction]) + case none +} + +enum InputDataValidation { + case success(InputDataValidationBehaviour) + case fail(InputDataFailResult) + case none + var isSuccess: Bool { + switch self { + case .success: + return true + case .fail, .none: + return false + + } + } +} + + + + +enum InputDoneValue : Equatable { + case enabled(String) + case disabled(String) + case invisible + case loading +} diff --git a/Telegram-Mac/InputDataDataSelectorRowItem.swift b/Telegram-Mac/InputDataDataSelectorRowItem.swift new file mode 100644 index 0000000000..3ea8bf3c94 --- /dev/null +++ b/Telegram-Mac/InputDataDataSelectorRowItem.swift @@ -0,0 +1,124 @@ +// +// InputDataDataSelectorRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class InputDataDataSelectorRowItem: GeneralRowItem, InputDataRowDataValue { + + private let updated:()->Void + fileprivate var _value: InputDataValue { + didSet { + if _value != oldValue { + updated() + } + } + } + fileprivate let placeholderLayout: TextViewLayout + + var value: InputDataValue { + return _value + } + + fileprivate let values: [ValuesSelectorValue] + init(_ initialSize: NSSize, stableId: AnyHashable, value: InputDataValue, error: InputDataValueError?, placeholder: String, updated: @escaping()->Void, values: [ValuesSelectorValue]) { + self._value = value + self.updated = updated + self.placeholderLayout = TextViewLayout(.initialize(string: placeholder, color: theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) + self.values = values + super.init(initialSize, height: 42, stableId: stableId, error: error) + _ = makeSize(initialSize.width, oldWidth: oldWidth) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + placeholderLayout.measure(width: 100) + return success + } + + override func viewClass() -> AnyClass { + return InputDataDataSelectorRowView.self + } + +} + + +final class InputDataDataSelectorRowView : GeneralRowView { + private let placeholderTextView = TextView() + private let dataTextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(placeholderTextView) + addSubview(dataTextView) + placeholderTextView.userInteractionEnabled = false + placeholderTextView.isSelectable = false + dataTextView.userInteractionEnabled = false + dataTextView.isSelectable = false + } + + override func mouseDown(with event: NSEvent) { + if event.clickCount == 1 { + guard let item = item as? InputDataDataSelectorRowItem else {return} + showModal(with: ValuesSelectorModalController(values: item.values, selected: item.values.first(where: {$0.value == item.value}), title: item.placeholderLayout.attributedString.string, onComplete: { [weak item] newValue in + item?._value = newValue.value + item?.redraw() + }), for: mainWindow) + } + } + + override func shakeView() { + dataTextView.shake() + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + guard let item = item as? InputDataDataSelectorRowItem else {return} + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + } + + override func layout() { + super.layout() + guard let item = item as? InputDataDataSelectorRowItem else {return} + placeholderTextView.setFrameOrigin(item.inset.left, 14) + + dataTextView.layout?.measure(width: frame.width - item.inset.left - item.inset.right - 106) + dataTextView.update(dataTextView.layout) + dataTextView.setFrameOrigin(item.inset.left + 106, 14) + } + + override func updateColors() { + placeholderTextView.backgroundColor = theme.colors.background + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? InputDataDataSelectorRowItem else {return} + placeholderTextView.update(item.placeholderLayout) + + var selected: ValuesSelectorValue? + let index:Int? = item.values.index(where: { entry -> Bool in + return entry.value == item.value + }) + if let index = index { + selected = item.values[index] + } + let layout = TextViewLayout(.initialize(string: selected?.localized ?? item.placeholderLayout.attributedString.string, color: selected == nil ? theme.colors.grayText : theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) + dataTextView.update(layout) + + needsLayout = true + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/InputDataDateRowItem.swift b/Telegram-Mac/InputDataDateRowItem.swift new file mode 100644 index 0000000000..de06187e86 --- /dev/null +++ b/Telegram-Mac/InputDataDateRowItem.swift @@ -0,0 +1,422 @@ +// +// InputDataDateRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class InputDataDateRowItem: GeneralRowItem, InputDataRowDataValue { + fileprivate let placeholderLayout: TextViewLayout + fileprivate let dayHolderLayout: TextViewLayout + fileprivate let monthHolderLayout: TextViewLayout + fileprivate let yearHolderLayout: TextViewLayout + + private let updated:()->Void + fileprivate var _value: InputDataValue { + didSet { + if _value != oldValue { + updated() + } + } + } + + var value: InputDataValue { + return _value + } + + init(_ initialSize: NSSize, stableId: AnyHashable, value: InputDataValue, error: InputDataValueError?, updated:@escaping()->Void, placeholder: String) { + self._value = value + self.updated = updated + placeholderLayout = TextViewLayout(.initialize(string: placeholder, color: theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) + + dayHolderLayout = TextViewLayout(.initialize(string: L10n.inputDataDateDayPlaceholder1, color: theme.colors.text, font: .normal(.text))) + monthHolderLayout = TextViewLayout(.initialize(string: L10n.inputDataDateMonthPlaceholder1, color: theme.colors.text, font: .normal(.text))) + yearHolderLayout = TextViewLayout(.initialize(string: L10n.inputDataDateYearPlaceholder1, color: theme.colors.text, font: .normal(.text))) + + dayHolderLayout.measure(width: .greatestFiniteMagnitude) + monthHolderLayout.measure(width: .greatestFiniteMagnitude) + yearHolderLayout.measure(width: .greatestFiniteMagnitude) + + super.init(initialSize, height: 44, stableId: stableId, error: error) + _ = makeSize(initialSize.width, oldWidth: oldWidth) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + placeholderLayout.measure(width: 100) + return success + } + + override func viewClass() -> AnyClass { + return InputDataDateRowView.self + } + +} + + +final class InputDataDateRowView : GeneralRowView, TGModernGrowingDelegate { + private let placeholderTextView = TextView() + private let dayInput = TGModernGrowingTextView(frame: NSZeroRect) + private let monthInput = TGModernGrowingTextView(frame: NSZeroRect) + private let yearInput = TGModernGrowingTextView(frame: NSZeroRect) + + private let firstSeparator = TextViewLabel() + private let secondSeparator = TextViewLabel() + private var ignoreChanges: Bool = false + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(placeholderTextView) + + placeholderTextView.userInteractionEnabled = false + placeholderTextView.isSelectable = false + + dayInput.delegate = self + monthInput.delegate = self + yearInput.delegate = self + + + + dayInput.textFont = .normal(.text) + monthInput.textFont = .normal(.text) + yearInput.textFont = .normal(.text) + + + + addSubview(dayInput) + addSubview(monthInput) + addSubview(yearInput) + + firstSeparator.autosize = true + secondSeparator.autosize = true + + addSubview(firstSeparator) + addSubview(secondSeparator) + + } + + public func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + switch true { + case textView === dayInput: + return 2 + case textView === monthInput: + return 2 + case textView === yearInput: + return 4 + default: + return 0 + } + } + + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + var bp:Int = 0 + bp += 1 + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return textView.frame.size + } + + func textViewEnterPressed(_ event:NSEvent) -> Bool { + if FastSettings.checkSendingAbility(for: event) { + return true + } + return false + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func textViewNeedClose(_ textView: Any) { + + } + + func textViewTextDidChange(_ string: String) { + + guard let item = item as? InputDataDateRowItem else {return} + guard !ignoreChanges else {return} + + var day = String(dayInput.string().unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)}) + var month = String(monthInput.string().unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)}) + var year = String(yearInput.string().unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)}) + + var _month:String? + var _year: String? + var _day: String? + if year.length == 4 { + year = "\(Int(year)!)" + _year = year + } + if month.length > 0 { + let _m = min(Int(month)!, 12) + if _m == 0 { + month = "0" + } else { + month = "\(month.length == 2 && _m < 10 ? "0\(_m)" : "\(_m)")" + } + _month = month == "0" ? nil : month + } + + if day.length > 0 { + var _max:Int = 31 + if let year = _year, let month = _month { + if let date = dateFormatter.date(from: "02.\(month).\(year)") { + _max = CalendarUtils.lastDay(ofTheMonth: date) + } + } + let _d = min(Int(day)!, _max) + if _d == 0 { + day = "0" + } else { + day = "\(day.length == 2 && _d < 10 ? "0\(_d)" : "\(_d)")" + } + _day = day == "0" ? nil : day + } + + item._value = .date(_day != nil ? Int32(_day!) : nil, _month != nil ? Int32(_month!) : nil, _year != nil ? Int32(_year!) : nil) + + dayInput.setString(day) + monthInput.setString(month) + yearInput.setString(year) + + if month.length == 2, month.prefix(1) == "0" { + textViewDidReachedLimit(monthInput) + } + + if day.length == 2, window?.firstResponder == dayInput.inputView { + textViewDidReachedLimit(dayInput) + } + if month.length == 2, window?.firstResponder == monthInput.inputView { + textViewDidReachedLimit(monthInput) + } + } + + func textViewDidReachedLimit(_ textView: Any) { + if let responder = nextResponder() { + window?.makeFirstResponder(responder) + } + } + + func controlTextDidChange(_ obj: Notification) { + + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + return false + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + guard let item = item as? InputDataDateRowItem else {return} + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + } + + override var mouseInsideField: Bool { + return yearInput._mouseInside() || dayInput._mouseInside() || monthInput._mouseInside() + } + + override func hitTest(_ point: NSPoint) -> NSView? { + switch true { + case NSPointInRect(point, yearInput.frame): + return yearInput + case NSPointInRect(point, dayInput.frame): + return dayInput + case NSPointInRect(point, monthInput.frame): + return monthInput + default: + return super.hitTest(point) + } + } + + override func hasFirstResponder() -> Bool { + return true + } + + override var firstResponder: NSResponder? { + let isKeyDown = NSApp.currentEvent?.type == NSEvent.EventType.keyDown && NSApp.currentEvent?.keyCode == KeyboardKey.Tab.rawValue + switch true { + case yearInput._mouseInside() && !isKeyDown: + return yearInput.inputView + case dayInput._mouseInside() && !isKeyDown: + return dayInput.inputView + case monthInput._mouseInside() && !isKeyDown: + return monthInput.inputView + default: + switch true { + case yearInput.inputView == window?.firstResponder: + return yearInput.inputView + case dayInput.inputView == window?.firstResponder: + return dayInput.inputView + case monthInput.inputView == window?.firstResponder: + return monthInput.inputView + default: + return dayInput.inputView + } + } + } + + override func nextResponder() -> NSResponder? { + if window?.firstResponder == dayInput.inputView { + return monthInput.inputView + } + if window?.firstResponder == monthInput.inputView { + return yearInput.inputView + } + return nil + } + + + override func layout() { + super.layout() + guard let item = item as? InputDataDateRowItem else {return} + + placeholderTextView.setFrameOrigin(item.inset.left, 14) + + let defaultLeftInset = item.inset.left + 100 + + dayInput.setFrameOrigin(defaultLeftInset, 15) + monthInput.setFrameOrigin(dayInput.frame.maxX + 8, 15) + yearInput.setFrameOrigin(monthInput.frame.maxX + 8, 15) + + firstSeparator.setFrameOrigin(dayInput.frame.maxX - 7, 14) + secondSeparator.setFrameOrigin(monthInput.frame.maxX - 7, 14) + + +// +// dayPlaceholder.setFrameOrigin(defaultLeftInset, 14) + +// +// monthPlaceholder.setFrameOrigin(defaultLeftInset, dayPlaceholder.frame.maxY + 5) +// monthSelector.setFrameOrigin(monthPlaceholder.frame.maxX + 3, monthPlaceholder.frame.minY - 3) +// +// yearPlaceholder.setFrameOrigin(defaultLeftInset, monthPlaceholder.frame.maxY + 5) +// yearSelector.setFrameOrigin(yearPlaceholder.frame.maxX + 3, yearPlaceholder.frame.minY - 3) + } + + override func shakeView() { + guard let item = item as? InputDataDateRowItem else {return} + + switch item.value { + case let .date(day, month, year): + if day == nil { + dayInput.shake() + } + if month == nil { + monthInput.shake() + } + if year == nil { + yearInput.shake() + } + if year != nil && month != nil && day != nil { + dayInput.shake() + monthInput.shake() + yearInput.shake() + } + default: + break + } + + + } + + override func updateColors() { + placeholderTextView.backgroundColor = theme.colors.background + firstSeparator.backgroundColor = theme.colors.background + secondSeparator.backgroundColor = theme.colors.background + + dayInput.textColor = theme.colors.text + monthInput.textColor = theme.colors.text + yearInput.textColor = theme.colors.text + + dayInput.setBackgroundColor(backdorColor) + monthInput.setBackgroundColor(backdorColor) + yearInput.setBackgroundColor(backdorColor) + } + + override func set(item: TableRowItem, animated: Bool) { + + + + guard let item = item as? InputDataDateRowItem else {return} + placeholderTextView.update(item.placeholderLayout) + + + let dayLayout = TextViewLayout(.initialize(string: L10n.inputDataDateDayPlaceholder1, color: theme.colors.grayText, font: .normal(.text))) + dayLayout.measure(width: .greatestFiniteMagnitude) + + let monthLayout = TextViewLayout(.initialize(string: L10n.inputDataDateMonthPlaceholder1, color: theme.colors.grayText, font: .normal(.text))) + monthLayout.measure(width: .greatestFiniteMagnitude) + + let yearLayout = TextViewLayout(.initialize(string: L10n.inputDataDateYearPlaceholder1, color: theme.colors.grayText, font: .normal(.text))) + yearLayout.measure(width: .greatestFiniteMagnitude) + + + + dayInput.min_height = Int32(dayLayout.layoutSize.height) + dayInput.max_height = Int32(dayLayout.layoutSize.height) + dayInput.setFrameSize(NSMakeSize(dayLayout.layoutSize.width + 20, dayLayout.layoutSize.height)) + + monthInput.min_height = Int32(monthLayout.layoutSize.height) + monthInput.max_height = Int32(monthLayout.layoutSize.height) + monthInput.setFrameSize(NSMakeSize(monthLayout.layoutSize.width + 20, monthLayout.layoutSize.height)) + + yearInput.min_height = Int32(yearLayout.layoutSize.height) + yearInput.max_height = Int32(yearLayout.layoutSize.height) + yearInput.setFrameSize(NSMakeSize(yearLayout.layoutSize.width + 20, yearLayout.layoutSize.height)) + + firstSeparator.attributedString = .initialize(string: "/", color: theme.colors.text, font: .medium(.text)) + secondSeparator.attributedString = .initialize(string: "/", color: theme.colors.text, font: .medium(.text)) + firstSeparator.sizeToFit() + secondSeparator.sizeToFit() + + ignoreChanges = true + + switch item.value { + case let .date(day, month, year): + if let day = day { + dayInput.setString("\( day < 10 && day > 0 ? "\(day)" : "\(day)")", animated: false) + } else { + dayInput.setString("", animated: false) + } + if let month = month { + monthInput.setString("\( month < 10 && month > 0 ? "\(month)" : "\(month)")", animated: false) + } else { + monthInput.setString("", animated: false) + } + if let year = year { + yearInput.setString("\(year)", animated: false) + } else { + yearInput.setString("", animated: false) + } + default: + dayInput.setString("", animated: false) + monthInput.setString("", animated: false) + yearInput.setString("", animated: false) + } + ignoreChanges = false + + dayInput.placeholderAttributedString = dayLayout.attributedString + monthInput.placeholderAttributedString = monthLayout.attributedString + yearInput.placeholderAttributedString = yearLayout.attributedString + + + super.set(item: item, animated: animated) + + layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/InputDataRowItem.swift b/Telegram-Mac/InputDataRowItem.swift new file mode 100644 index 0000000000..aa22137334 --- /dev/null +++ b/Telegram-Mac/InputDataRowItem.swift @@ -0,0 +1,929 @@ +// +// InputDataRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +protocol InputDataRowDataValue { + var value: InputDataValue { get } +} + +enum InputDataRightItemAction : Equatable { + static func == (lhs: InputDataRightItemAction, rhs: InputDataRightItemAction) -> Bool { + switch lhs { + case .clearText: + if case .clearText = rhs { + return true + } else { + return false + } + case .resort: + if case .resort = rhs { + return true + } else { + return false + } + case .none: + if case .none = rhs { + return true + } else { + return false + } + case .custom: + if case .custom = rhs { + return true + } else { + return false + } + } + } + + case clearText + case resort + case custom((TableRowItem, Control)->Void) + case none +} + +enum InputDataRightItem : Equatable { + case action(CGImage, InputDataRightItemAction) + case loading +} + +class InputDataRowItem: GeneralRowItem, InputDataRowDataValue { + + fileprivate let placeholderLayout: TextViewLayout? + fileprivate let placeholder: InputDataInputPlaceholder? + + + fileprivate let inputPlaceholder: NSAttributedString + fileprivate let filter:(String)->String + let limit:Int32 + private let updated:(String)->Void + private var _currentText: NSAttributedString = NSAttributedString() { + didSet { + updated(currentText.string) + } + } + fileprivate(set) var currentText: NSAttributedString { + set { + let copy = newValue.mutableCopy() as! NSMutableAttributedString + if let defaultText = self.defaultText { + copy.replaceCharacters(in: NSMakeRange(0, min(defaultText.length, copy.length)), with: "") + } + _currentText = copy + + if copy != newValue { + self.redraw() + } + } + get { + return _currentText + } + } + + + + var currentAttributed: NSAttributedString { + let attr = NSMutableAttributedString() + + if let defaultText = self.defaultText { + attr.append(.initialize(string: defaultText, color: theme.colors.text, font: .normal(.text))) + } + attr.append(currentText) + return attr + } + + var value: InputDataValue { + if canMakeTransformations { + return .attributedString(currentText) + } else { + return .string(currentText.string) + } + } + + fileprivate var inputHeight: CGFloat = 21 + fileprivate var realInputHeight: CGFloat = 21 + + override var inset: NSEdgeInsets { + var inset = super.inset + switch viewType { + case .legacy: + break + case .modern: + if let errorLayout = errorLayout { + inset.bottom += errorLayout.layoutSize.height + 4 + } + } + return inset + } + + override var height: CGFloat { + switch viewType { + case .legacy: + var height = inputHeight + 8 + if let errorLayout = errorLayout { + height += (height == 42 ? errorLayout.layoutSize.height : errorLayout.layoutSize.height / 2) + } + return height + case let .modern(_, insets): + var inputHeight = realInputHeight + switch self.mode { + case .plain: + break + case .secure: + inputHeight -= 6 + } + let height = inputHeight + insets.top + insets.bottom + inset.top + inset.bottom +// if let errorLayout = errorLayout { +// height += errorLayout.layoutSize.height + 4 +// } + return height + } + + } + fileprivate let defaultText: String? + fileprivate let mode: InputDataInputMode + fileprivate let rightItem: InputDataRightItem? + fileprivate let canMakeTransformations: Bool + fileprivate let pasteFilter:((String)->(Bool, String))? + private let maxBlockWidth: CGFloat? + init(_ initialSize: NSSize, stableId: AnyHashable, mode: InputDataInputMode, error: InputDataValueError?, viewType: GeneralViewType = .legacy, currentText: String, currentAttributedText: NSAttributedString? = nil, placeholder: InputDataInputPlaceholder?, inputPlaceholder: String, defaultText: String? = nil, rightItem: InputDataRightItem? = nil, canMakeTransformations: Bool = false, insets: NSEdgeInsets = NSEdgeInsets(left: 30.0, right: 30.0), maxBlockWidth: CGFloat? = nil, filter:@escaping(String)->String, updated:@escaping(String)->Void, pasteFilter:((String)->(Bool, String))? = nil, limit: Int32) { + self.filter = filter + self.limit = limit + self.updated = updated + self.placeholder = placeholder + self.pasteFilter = pasteFilter + self.defaultText = defaultText + self.maxBlockWidth = maxBlockWidth + self.canMakeTransformations = canMakeTransformations + self.rightItem = rightItem + let holder = NSMutableAttributedString() + switch mode { + case .secure: + holder.append(.initialize(string: inputPlaceholder, color: theme.colors.grayText, font: .light(.text))) + case .plain: + holder.append(.initialize(string: inputPlaceholder, color: theme.colors.grayText, font: .normal(.text))) + } + self.inputPlaceholder = holder + placeholderLayout = placeholder?.placeholder != nil ? TextViewLayout(.initialize(string: placeholder!.placeholder!, color: theme.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) : nil + + _currentText = currentAttributedText ?? NSAttributedString.initialize(string: currentText, color: theme.colors.text, font: .normal(.text), coreText: false) + self.mode = mode + + super.init(initialSize, stableId: stableId, viewType: viewType, inset: insets, error: error) + + + _ = makeSize(initialSize.width, oldWidth: oldWidth) + } + + override var blockWidth: CGFloat { + if let maxBlockWidth = maxBlockWidth { + return min(maxBlockWidth, super.blockWidth) + } else { + return super.blockWidth + } + } + + var textFieldLeftInset: CGFloat { + if let placeholder = placeholder { + if let _ = placeholder.placeholder { + return 102 + } else { + if let icon = placeholder.icon { + return icon.backingSize.width + 6 + placeholder.insets.left + } else { + return -2 + } + } + } else { + return -2 + } + } + + override var instantlyResize: Bool { + return true + } + + func calculateHeight() { + _ = self.makeSize(self.width, oldWidth: self.width) + } + + private(set) fileprivate var additionRightInset: CGFloat = 0 + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let currentAttributed: NSMutableAttributedString = NSMutableAttributedString() + _ = currentAttributed.append(string: (defaultText ?? ""), font: .normal(.text)) + currentAttributed.append(currentText) + + if mode == .secure { + currentAttributed.setAttributedString(.init(string: String(currentText.string.map { _ in return "•" }))) + currentAttributed.addAttribute(.font, value: NSFont.normal(15.0 + 3.22), range: currentAttributed.range) + } + + let textStorage = NSTextStorage(attributedString: currentAttributed) + + if let rightItem = self.rightItem { + switch rightItem { + case .loading: + self.additionRightInset = 20 + case let .action(icon, _): + self.additionRightInset = icon.backingSize.width + 2 + } + } else { + self.additionRightInset = 0 + } + + switch viewType { + case .legacy: + let textContainer = NSTextContainer(size: NSMakeSize(initialSize.width - inset.left - inset.right - textFieldLeftInset - additionRightInset, .greatestFiniteMagnitude)) + let layoutManager = NSLayoutManager() + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + layoutManager.ensureLayout(for: textContainer) + self.realInputHeight = max(34, layoutManager.usedRect(for: textContainer).height + 6) + inputHeight = max(34, layoutManager.usedRect(for: textContainer).height + 6) + case let .modern(_, insets): + let textContainer = NSTextContainer(size: NSMakeSize(self.blockWidth - insets.left - insets.right - textFieldLeftInset - additionRightInset, .greatestFiniteMagnitude)) + let layoutManager = NSLayoutManager() + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + layoutManager.ensureLayout(for: textContainer) + switch self.mode { + case .plain: + self.realInputHeight = max(16, layoutManager.usedRect(for: textContainer).height) + case .secure: + self.realInputHeight = max(22, layoutManager.usedRect(for: textContainer).height) + } + inputHeight = max(34, layoutManager.usedRect(for: textContainer).height + 1) + } + + let success = super.makeSize(width, oldWidth: oldWidth) + placeholderLayout?.measure(width: 100) + return success + } + + override func viewClass() -> AnyClass { + return InputDataRowView.self + } + +} + +private final class InputDataSecureField : NSSecureTextField { + override func becomeFirstResponder() -> Bool { + + let success = super.becomeFirstResponder() + if success { + let tetView = self.currentEditor() as? NSTextView + tetView?.insertionPointColor = theme.colors.indicatorColor + } + //NSTextView* textField = (NSTextView*) [self currentEditor]; + + return success + } +} + + +class InputDataRowView : GeneralRowView, TGModernGrowingDelegate, NSTextFieldDelegate { + internal let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let placeholderTextView = TextView() + private let rightActionView: ImageButton = ImageButton() + private var loadingView: ProgressIndicator? = nil + private var placeholderAction: ImageButton? + internal let textView: TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect, unscrollable: true) + private let secureField: InputDataSecureField = InputDataSecureField(frame: NSMakeRect(0, 0, 100, 16)) + private let textLimitation: TextViewLabel = TextViewLabel(frame: NSMakeRect(0, 0, 16, 14)) + private let separator: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + containerView.addSubview(placeholderTextView) + containerView.addSubview(textView) + containerView.addSubview(secureField) + containerView.addSubview(separator) + containerView.addSubview(textLimitation) + containerView.addSubview(rightActionView) + addSubview(containerView) + rightActionView.autohighlight = false + + containerView.userInteractionEnabled = false + + textLimitation.alignment = .right + + + + // textView.max_height = 34 + // .isSingleLine = true + textView.delegate = self + placeholderTextView.userInteractionEnabled = false + placeholderTextView.isSelectable = false + + secureField.isBordered = false + secureField.isBezeled = false + secureField.focusRingType = .none + secureField.delegate = self + secureField.drawsBackground = true + secureField.isEditable = true + secureField.isSelectable = true + + textView.max_height = 10000 + + secureField.font = .normal(.text) + secureField.textView?.insertionPointColor = theme.colors.text + secureField.sizeToFit() + + } + + override func shakeView() { + if !secureField.isHidden { + secureField.shake() + secureField.setSelectionRange(NSMakeRange(0, secureField.stringValue.length)) + } + if !textView.isHidden { + textView.shake() + textView.setSelectedRange(NSMakeRange(0, textView.string().length)) + } + self.separator.shake() + } + + override func hasFirstResponder() -> Bool { + return true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + } + + func canTransformInputText() -> Bool { + guard let item = item as? InputDataRowItem else { return false } + return item.canMakeTransformations + } + + func makeBold() { + self.textView.boldWord() + } + func makeUrl() { + self.makeUrl(of: textView.selectedRange()) + } + func makeItalic() { + self.textView.italicWord() + } + func makeMonospace() { + self.textView.codeWord() + } + + func makeUrl(of range: NSRange) { + guard range.min != range.max, let window = window as? Window else { + return + } + var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) + let defaultTag: TGInputTextTag? = self.textView.attributedString().attribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), at: range.location, effectiveRange: &effectiveRange) as? TGInputTextTag + + + let defaultUrl = defaultTag?.attachment as? String + + if effectiveRange.location == NSNotFound || defaultTag == nil { + effectiveRange = range + } + + showModal(with: InputURLFormatterModalController(string: self.textView.string().nsstring.substring(with: effectiveRange), defaultUrl: defaultUrl, completion: { [weak self] url in + self?.textView.addLink(url, range: effectiveRange) + }), for: window) + + } + + + private var separatorFrame: NSRect { + + guard let item = item as? InputDataRowItem else { return NSZeroRect } + + switch item.viewType { + case .legacy: + if let placeholder = item.placeholder { + if placeholder.drawBorderAfterPlaceholder { + return NSMakeRect(item.inset.left + item.textFieldLeftInset + 4, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.left - item.inset.right - item.textFieldLeftInset - 4, .borderSize) + } else { + return NSMakeRect(item.inset.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.left - item.inset.right, .borderSize) + } + } else { + return NSMakeRect(item.inset.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.left - item.inset.right, .borderSize) + } + case let .modern(_, innerInsets): + if let placeholder = item.placeholder { + if placeholder.drawBorderAfterPlaceholder { + return NSMakeRect(innerInsets.left + item.textFieldLeftInset + 4, self.containerView.frame.height - .borderSize, self.containerView.frame.width - innerInsets.left - innerInsets.right - item.textFieldLeftInset - 4, .borderSize) + } else { + return NSMakeRect(innerInsets.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.inset.left - innerInsets.right, .borderSize) + } + } else { + return NSMakeRect(innerInsets.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - innerInsets.left - innerInsets.right, .borderSize) + } + } + } + + override func layout() { + super.layout() + guard let item = item as? InputDataRowItem else {return} + + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + placeholderTextView.setFrameOrigin(item.inset.left, 14) + placeholderAction?.setFrameOrigin(item.inset.left, 12) + + separator.frame = separatorFrame + + if let rightItem = item.rightItem { + switch rightItem { + case .action: + rightActionView.setFrameOrigin(NSMakePoint(self.containerView.frame.width - rightActionView.frame.width - item.inset.right + 4, 14)) + default: + break + } + } + + secureField.setFrameSize(NSMakeSize(self.containerView.frame.width - item.inset.left - item.inset.right - item.textFieldLeftInset - item.additionRightInset, item.inputHeight)) + secureField.setFrameOrigin(item.inset.left + item.textFieldLeftInset, 14) + + textView.setFrameSize(NSMakeSize(self.containerView.frame.width - item.inset.left - item.inset.right - item.textFieldLeftInset - item.additionRightInset, item.inputHeight)) + textView.setFrameOrigin(item.inset.left + item.textFieldLeftInset - 3, 6) + + textLimitation.setFrameOrigin(NSMakePoint(self.containerView.frame.width - item.inset.right - textLimitation.frame.width + 4, self.containerView.frame.height - textLimitation.frame.height - 4)) + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + + self.separator.isHidden = !position.border + + let textX = innerInsets.left + item.textFieldLeftInset - 3 + + placeholderTextView.setFrameOrigin(NSMakePoint(innerInsets.left, innerInsets.top)) + placeholderAction?.setFrameOrigin(NSMakePoint(innerInsets.left, innerInsets.top - 1)) + + separator.frame = separatorFrame + + + if let rightItem = item.rightItem { + switch rightItem { + case .action: + if item.realInputHeight <= 22 { + rightActionView.centerY(x: self.containerView.frame.width - rightActionView.frame.width - innerInsets.right) + } else { + rightActionView.setFrameOrigin(NSMakePoint(self.containerView.frame.width - rightActionView.frame.width - innerInsets.right, innerInsets.top)) + } + case .loading: + if let loadingView = loadingView { + if item.realInputHeight <= 16 { + loadingView.centerY(x: self.containerView.frame.width - loadingView.frame.width - innerInsets.right) + } else { + loadingView.setFrameOrigin(NSMakePoint(self.containerView.frame.width - loadingView.frame.width - innerInsets.right, innerInsets.top)) + } + } + } + } + + + secureField.setFrameSize(NSMakeSize(item.blockWidth - innerInsets.left - innerInsets.right - item.textFieldLeftInset - item.additionRightInset, item.inputHeight)) + secureField.setFrameOrigin(innerInsets.left + item.textFieldLeftInset + 1, innerInsets.top) + + textView.setFrameSize(NSMakeSize(item.blockWidth - innerInsets.left - innerInsets.right - item.textFieldLeftInset - item.additionRightInset, item.inputHeight)) + + if item.realInputHeight <= 16 { + textView.setFrameOrigin(textX, innerInsets.top - 8) + } else { + textView.setFrameOrigin(textX, innerInsets.top ) + } + + textLimitation.setFrameOrigin(NSMakePoint(item.blockWidth - innerInsets.right - textLimitation.frame.width, self.containerView.frame.height - innerInsets.bottom - textLimitation.frame.height)) + + + } + } + + public func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + if let item = item as? InputDataRowItem { + return item.limit + } + return 100000 + } + + func textViewDidReachedLimit(_ textView: Any) { + if let item = item as? InputDataRowItem { + switch item.mode { + case .plain: + if self.textView.selectedRange().max == self.textView.string().length { + self.textView.shake() + } + case .secure: + self.secureField.shake() + } + } + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + if let item = item as? InputDataRowItem, let pasteFilter = item.pasteFilter { + if let string = pasteboard.string(forType: .string)?.trimmed { + let value = pasteFilter(string) + let updatedText = item.filter(value.1) + if value.0 { + switch item.mode { + case .plain: + textView.setString(updatedText) + case .secure: + secureField.stringValue = updatedText + } + } else { + switch item.mode { + case .plain: + textView.appendText(updatedText) + case .secure: + secureField.stringValue = secureField.stringValue + updatedText + } + } + return true + } + + } + return false + } + + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + + if let item = item as? InputDataRowItem, let table = item.table { + item.inputHeight = height + + + switch item.viewType { + case .legacy: + textLimitation.change(pos: NSMakePoint(containerView.frame.width - item.inset.right - textLimitation.frame.width + 4, item.height - textLimitation.frame.height), animated: animated) + case let .modern(_, insets): + textLimitation.change(pos: NSMakePoint(item.blockWidth - insets.right - textLimitation.frame.width , item.height - textLimitation.frame.height - insets.bottom), animated: animated) + } + + item.calculateHeight() + + change(size: NSMakeSize(item.width, item.height), animated: animated) + + let containerRect: NSRect + switch item.viewType { + case .legacy: + containerRect = self.bounds + case .modern: + containerRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, item.height - item.inset.bottom - item.inset.top) + } + containerView.change(size: containerRect.size, animated: animated, corners: item.viewType.corners) + containerView.change(pos: containerRect.origin, animated: animated) + + if let placeholder = item.placeholder { + if placeholder.drawBorderAfterPlaceholder { + separator.change(pos: NSMakePoint(separator.frame.minX, self.containerView.frame.height - .borderSize), animated: animated) + } else { + separator.change(pos: NSMakePoint(separator.frame.minX, self.containerView.frame.height - .borderSize), animated: animated) + } + } else { + separator.change(pos: NSMakePoint(separator.frame.minX, self.containerView.frame.height - .borderSize), animated: animated) + } + + table.noteHeightOfRow(item.index, animated) + } + + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return textView.frame.size + } + + func textViewEnterPressed(_ event:NSEvent) -> Bool { + if FastSettings.checkSendingAbility(for: event) { + return true + } + return false + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func textViewNeedClose(_ textView: Any) { + + } + + func textViewTextDidChange(_ string: String) { + if let item = item as? InputDataRowItem { + let updated = item.filter(string) + if updated != string { + + textView.setString(updated, animated: true) + NSSound.beep() + } else { + item.currentText = textView.attributedString() + } + let prevInputHeight = item.realInputHeight + let prevHeight = item.inputHeight + item.calculateHeight() + if prevInputHeight != item.realInputHeight && prevHeight == item.inputHeight { + textViewHeightChanged(item.inputHeight, animated: true) + } + containerView.needsDisplay = true + } + } + + + + func controlTextDidChange(_ obj: Notification) { + if let item = item as? InputDataRowItem { + let string = secureField.stringValue + let updated = item.filter(string) + if updated != string { + secureField.stringValue = updated + } else { + item.currentText = .initialize(string: updated, color: theme.colors.text, font: .normal(.text)) + } + let prevInputHeight = item.realInputHeight + item.calculateHeight() + if prevInputHeight != item.realInputHeight { + textViewHeightChanged(item.inputHeight, animated: true) + } + containerView.needsDisplay = true + } + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + if let item = item as? InputDataRowItem { + if item.currentText != textView.attributedString(), !textView.inputView.hasMarkedText() { + item.currentText = textView.attributedString() + } + } + } + +// func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { +// if let item = item as? InputDataRowItem, let string = pasteboard.string(forType: .string) { +// let updated = item.filter(string) +// if updated == string { +// return false +// } else { +// NSSound.beep() +// shakeView() +// } +// } +// return true +// } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + placeholderTextView.backgroundColor = backdorColor + textView.cursorColor = theme.colors.indicatorColor + textView.textFont = .normal(.text) + textView.textColor = theme.colors.text + textView.linkColor = theme.colors.link + + textView.setBackgroundColor(backdorColor) + secureField.font = .normal(13) + secureField.backgroundColor = backdorColor + secureField.textColor = theme.colors.text + separator.backgroundColor = theme.colors.border + containerView.backgroundColor = backdorColor + loadingView?.progressColor = theme.colors.grayText + guard let item = item as? InputDataRowItem else { + return + } + self.background = item.viewType.rowBackground + } + + + override var mouseInsideField: Bool { + return secureField._mouseInside() || textView._mouseInside() + } + + override func hitTest(_ point: NSPoint) -> NSView? { +// switch true { +// case NSPointInRect(convert(point, from: superview), secureField.frame): +// return secureField +// case NSPointInRect(convert(point, from: superview), textView.frame): +// return textView +// default: + return super.hitTest(point) + //} + } + + override var firstResponder: NSResponder? { + if let item = item as? InputDataRowItem { + switch item.mode { + case .plain: + return textView.inputView + case .secure: + return secureField + } + } + return super.firstResponder + } + + func showPlaceholderActionTooltip(_ text: String) -> Void { + if let placeholderAction = placeholderAction { + tooltip(for: placeholderAction, text: text) + } + } + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? InputDataRowItem else {return} + + self.textView.animates = false + super.set(item: item, animated: animated) + self.textView.animates = true + + placeholderTextView.isHidden = item.placeholderLayout == nil + placeholderTextView.update(item.placeholderLayout) + + let containerRect: NSRect + switch item.viewType { + case .legacy: + containerRect = self.bounds + case .modern: + containerRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, item.height - item.inset.bottom - item.inset.top) + } + containerView.change(size: containerRect.size, animated: animated, corners: item.viewType.corners) + containerView.change(pos: containerRect.origin, animated: animated) + + if let rightItem = item.rightItem { + switch rightItem { + case let .action(image, action): + rightActionView.set(image: image, for: .Normal) + _ = rightActionView.sizeToFit() + rightActionView.isHidden = false + loadingView?.removeFromSuperview() + loadingView = nil + rightActionView.removeAllHandlers() + switch action { + case .none: + rightActionView.userInteractionEnabled = false + rightActionView.autohighlight = false + case .resort: + rightActionView.userInteractionEnabled = false + rightActionView.autohighlight = false + case .clearText: + rightActionView.userInteractionEnabled = true + rightActionView.autohighlight = true + rightActionView.set(handler: { [weak self] _ in + self?.secureField.stringValue = "" + self?.textView.setString("") + }, for: .Click) + case let .custom(action): + rightActionView.userInteractionEnabled = true + rightActionView.autohighlight = true + rightActionView.set(handler: { control in + action(item, control) + }, for: .Click) + } + case .loading: + if loadingView == nil { + loadingView = ProgressIndicator(frame: NSMakeRect(0, 0, 18, 18)) + loadingView?.progressColor = theme.colors.grayText + containerView.addSubview(loadingView!) + } + rightActionView.isHidden = true + } + } else { + rightActionView.isHidden = true + loadingView?.removeFromSuperview() + loadingView = nil + } + + if let placeholder = item.placeholder { + if let icon = placeholder.icon { + if placeholderAction == nil { + self.placeholderAction = ImageButton() + containerView.addSubview(self.placeholderAction!) + if animated { + placeholderAction!.layer?.animateAlpha(from: 0, to: 2, duration: 0.2) + separator.change(size: separatorFrame.size, animated: animated) + separator.change(pos: separatorFrame.origin, animated: animated) + } + + switch item.viewType { + case .legacy: + textView._change(pos: NSMakePoint(item.inset.left + item.textFieldLeftInset - 3, 6), animated: animated) + case let .modern(_, innerInsets): + let textX = innerInsets.left + item.textFieldLeftInset - 3 + if item.realInputHeight <= 16 { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } else { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } + } + + } + guard let placeholderAction = self.placeholderAction else { + return + } + placeholderAction.set(image: icon, for: .Normal) + placeholderAction.set(image: icon, for: .Highlight) + placeholderAction.set(image: icon, for: .Hover) + _ = placeholderAction.sizeToFit() + placeholderAction.removeAllHandlers() + placeholderAction.set(handler: { _ in + placeholder.action?() + }, for: .SingleClick) + } else { + if animated { + if let placeholderAction = placeholderAction { + self.placeholderAction = nil + placeholderAction.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak placeholderAction] _ in + placeholderAction?.removeFromSuperview() + }) + separator.change(size: separatorFrame.size, animated: animated) + separator.change(pos: separatorFrame.origin, animated: animated) + } + } else { + placeholderAction?.removeFromSuperview() + placeholderAction = nil + } + + switch item.viewType { + case .legacy: + textView._change(pos: NSMakePoint(item.inset.left + item.textFieldLeftInset - 3, 6), animated: animated) + case let .modern(_, innerInsets): + let textX = innerInsets.left + item.textFieldLeftInset - 3 + if item.realInputHeight <= 16 { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } else { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } + } + } + + if placeholder.hasLimitationText { + textLimitation.isHidden = item.currentText.length < item.limit / 3 * 2 + textLimitation.attributedString = .initialize(string: "\(item.limit - Int32(item.currentText.length))", color: theme.colors.grayText, font: .normal(.small)) + } else { + textLimitation.isHidden = true + } + } else { + if animated { + if let placeholderAction = placeholderAction { + self.placeholderAction = nil + placeholderAction.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak placeholderAction] _ in + placeholderAction?.removeFromSuperview() + }) + separator.change(size: separatorFrame.size, animated: animated) + separator.change(pos: separatorFrame.origin, animated: animated) + } + } else { + placeholderAction?.removeFromSuperview() + placeholderAction = nil + } + + switch item.viewType { + case .legacy: + textView._change(pos: NSMakePoint(item.inset.left + item.textFieldLeftInset - 3, 6), animated: animated) + case let .modern(_, innerInsets): + let textX = innerInsets.left + item.textFieldLeftInset - 3 + if item.realInputHeight <= 16 { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } else { + textView._change(pos: NSMakePoint(textX, textView.frame.minY), animated: animated) + } + } + } + + switch item.mode { + case .plain: + secureField.isHidden = true + textView.isHidden = false + textView.animates = false + textView.setPlaceholderAttributedString(item.inputPlaceholder, update: false) + if item.currentAttributed != textView.attributedString() { + textView.setAttributedString(item.currentAttributed, animated: false) + } + textView.update(false) + textView.needsDisplay = true + textView.animates = true + case .secure: + secureField.placeholderAttributedString = item.inputPlaceholder + secureField.isHidden = false + + textView.isHidden = true + if item.currentText.string != secureField.stringValue { + secureField.stringValue = item.currentText.string + } + } + + containerView.needsDisplay = true + self.needsLayout = true + + } +} diff --git a/Telegram-Mac/InputFormatterPopover.swift b/Telegram-Mac/InputFormatterPopover.swift new file mode 100644 index 0000000000..f983035b33 --- /dev/null +++ b/Telegram-Mac/InputFormatterPopover.swift @@ -0,0 +1,214 @@ +// +// InputFormatterPopover.swift +// Telegram +// +// Created by keepcoder on 27/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +private enum InputFormatterViewState { + case normal + case link +} +private class InputFormatterView : NSView { + let link: TitleButton = TitleButton() + + let linkField: NSTextField = NSTextField(frame: NSMakeRect(0, 0, 30, 18)) + let dismissLink:ImageButton = ImageButton() + + fileprivate var state: InputFormatterViewState = .link + + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(linkField) + addSubview(link) + addSubview(dismissLink) + + dismissLink.set(image: theme.icons.recentDismiss, for: .Normal) + _ = dismissLink.sizeToFit() + + // linkField.placeholderAttributedString = NSAttributedString.initialize(string: L10n.inputFormatterSetLink, color: theme.colors.grayText, font: .normal(.text)) + linkField.font = .normal(.text) + linkField.wantsLayer = true + linkField.isEditable = true + linkField.isSelectable = true + linkField.maximumNumberOfLines = 1 + linkField.backgroundColor = .clear + linkField.drawsBackground = false + linkField.isBezeled = false + linkField.isBordered = false + linkField.focusRingType = .none + + + // linkField.delegate = self + + link.set(handler: { [weak self] _ in + self?.change(state: .link, animated: true) + }, for: .Click) + + + + + dismissLink.centerY(x: frame.width - 10 - dismissLink.frame.width) + dismissLink.isHidden = true + + change(state: .link, animated: false) + } + + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + + if commandSelector == #selector(insertNewline(_:)) { + + return true + } + + return false + } + + func change(state: InputFormatterViewState, animated: Bool) { + self.state = state + switch state { + case .normal: + link.isHidden = false + link._change(opacity: 1.0, animated: animated) + dismissLink._change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.dismissLink.isHidden = true + } + }) + linkField._change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.linkField.isHidden = true + } + }) + case .link: + linkField.isHidden = false + dismissLink.isHidden = false + linkField._change(opacity: 1.0, animated: animated) + dismissLink._change(opacity: 1.0, animated: animated) + + link._change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.link.isHidden = true + } + }) + + } + } + + override func layout() { + super.layout() + + linkField.setFrameSize(frame.width - link.frame.width - 10, 18) + linkField.centerY(x: 10) + linkField.textView?.frame = linkField.bounds + switch state { + case .normal: + link.centerY(x: 0) + case .link: + link.centerY(x: frame.width - link.frame.width) + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +final class InputFormatterArguments { + let link:(String)->Void + init(link:@escaping(String)->Void) { + self.link = link + } +} + +private final class FormatterViewController : NSViewController { + + override var acceptsFirstResponder: Bool { + return true + } +} + +class InputFormatterPopover: NSPopover { + + + + private let window: Window + init(_ arguments: InputFormatterArguments, window: Window) { + self.window = window + super.init() + let controller = FormatterViewController() + let view = InputFormatterView(frame: NSMakeRect(0, 0, 240, 40)) + + + + + controller.view = view + + view.dismissLink.set(handler: { [weak self] _ in + self?.close() + }, for: .Click) + + self.contentViewController = controller + + window.set(handler: { [weak view] () -> KeyHandlerResult in + if let view = view { + if view.state == .link { + let attr = view.linkField.attributedStringValue.mutableCopy() as! NSMutableAttributedString + + attr.detectLinks(type: [.Links]) + + var url:String? = nil + + attr.enumerateAttribute(NSAttributedString.Key.link, in: attr.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in + + if let value = value as? inAppLink { + switch value { + case let .external(link, _): + url = link + break + default: + break + } + } + + let s: ObjCBool = (url != nil) ? true : false + stop.pointee = s + + }) + + if let url = url { + arguments.link(url) + } else { + view.shake() + } + + } + return .invoked + } + return .rejected + }, with: self, for: .Return, priority: .modal) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.close() + return .invoked + }, with: self, for: .Escape, priority: .modal) + + + } + + deinit { + window.removeAllHandlers(for: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/InputPasswordController.swift b/Telegram-Mac/InputPasswordController.swift new file mode 100644 index 0000000000..7a5c0fb511 --- /dev/null +++ b/Telegram-Mac/InputPasswordController.swift @@ -0,0 +1,141 @@ +// +// InputPasswordController.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import TelegramCore +import SyncCore +import TGUIKit + +enum InputPasswordValueError { + case generic + case wrong +} + + +private struct InputPasswordState : Equatable { + let error: InputDataValueError? + let value: InputDataValue + let isLoading: Bool + init(value: InputDataValue, error: InputDataValueError?, isLoading: Bool) { + self.value = value + self.error = error + self.isLoading = isLoading + } + + func withUpdatedError(_ error: InputDataValueError?) -> InputPasswordState { + return InputPasswordState(value: self.value, error: error, isLoading: self.isLoading) + } + func withUpdatedValue(_ value: InputDataValue) -> InputPasswordState { + return InputPasswordState(value: value, error: self.error, isLoading: self.isLoading) + } + func withUpdatedLoading(_ isLoading: Bool) -> InputPasswordState { + return InputPasswordState(value: self.value, error: self.error, isLoading: isLoading) + } +} + +private let _id_input_pwd:InputDataIdentifier = InputDataIdentifier("_id_input_pwd") + +private func inputPasswordEntries(state: InputPasswordState, desc:String) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: state.value, error: state.error, identifier: _id_input_pwd, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.inputPasswordControllerPlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(desc), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func InputPasswordController(context: AccountContext, title: String, desc: String, checker:@escaping(String)->Signal) -> InputDataModalController { + + let initialState: InputPasswordState = InputPasswordState(value: .string(nil), error: nil, isLoading: false) + let stateValue: Atomic = Atomic(value: initialState) + let statePromise:ValuePromise = ValuePromise(initialState, ignoreRepeated: true) + + let updateState:(_ f:(InputPasswordState)->InputPasswordState) -> Void = { f in + statePromise.set(stateValue.modify(f)) + } + + let dataSignal = statePromise.get() |> map { state in + return inputPasswordEntries(state: state, desc: desc) + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + let checkPassword = MetaDisposable() + + var dismiss:(()->Void)? + + let controller = InputDataController(dataSignal: dataSignal, title: title, validateData: { data in + return .fail(.doSomething { f in + if let pwd = data[_id_input_pwd]?.stringValue, !stateValue.with({$0.isLoading}) { + updateState { + return $0.withUpdatedLoading(true) + } + checkPassword.set(showModalProgress(signal: checker(pwd), for: context.window).start(error: { error in + let text: String + switch error { + case .wrong: + text = L10n.inputPasswordControllerErrorWrongPassword + case .generic: + text = L10n.unknownError + } + updateState { + return $0.withUpdatedLoading(false).withUpdatedError(InputDataValueError(description: text, target: .data)) + } + f(.fail(.fields([_id_input_pwd : .shake]))) + }, completed: { + updateState { + return $0.withUpdatedLoading(false) + } + dismiss?() + })) + } + }) + }, updateDatas: { data in + updateState { + return $0.withUpdatedValue(data[_id_input_pwd]!).withUpdatedError(nil) + } + return .fail(.none) + }, afterDisappear: { + checkPassword.dispose() + }, hasDone: true) + + let interactions = ModalInteractions(acceptTitle: L10n.navigationDone, accept: { [weak controller] in + + controller?.validateInputValues() + + }, drawBorder: true, height: 50, singleButton: true) + + controller.getBackgroundColor = { + theme.colors.listBackground + } + + let modalController = InputDataModalController(controller, modalInteractions: interactions, size: NSMakeSize(300, 300)) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + dismiss = { [weak modalController] in + modalController?.close() + } + + return modalController +} diff --git a/Telegram-Mac/InputPasteboardParser.swift b/Telegram-Mac/InputPasteboardParser.swift index 37859547df..bf70b82de9 100644 --- a/Telegram-Mac/InputPasteboardParser.swift +++ b/Telegram-Mac/InputPasteboardParser.swift @@ -7,15 +7,58 @@ // import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox import TGUIKit class InputPasteboardParser: NSObject { + public class func getPasteboardUrls(_ pasteboard: NSPasteboard) -> Signal<[URL], NoError> { + let items = pasteboard.pasteboardItems + + if let items = items, !items.isEmpty { + var files:[URL] = [] + + for item in items { + let path = item.string(forType: NSPasteboard.PasteboardType(rawValue: "public.file-url")) + if let path = path, let url = URL(string: path) { + files.append(url) + } + + } + + var image:NSImage? = nil + + if files.isEmpty { + if let images = pasteboard.readObjects(forClasses: [NSImage.self], options: nil) as? [NSImage], !images.isEmpty { + image = images[0] + } + } + + + files = files.filter { path -> Bool in + if let size = fs(path.path) { + return size <= 2000 * 1024 * 1024 + } + + return false + } + + + if !files.isEmpty { + return .single(files) + } else if let image = image { + return putToTemp(image: image, compress: false) |> map {[URL(fileURLWithPath: $0)]} |> deliverOnMainQueue + } + + } + + return .single([]) + } - public class func proccess(pasteboard:NSPasteboard, account:Account, chatInteraction:ChatInteraction, window:Window) -> Bool { + public class func canProccessPasteboard(_ pasteboard:NSPasteboard) -> Bool { let items = pasteboard.pasteboardItems if let items = items, !items.isEmpty { @@ -38,7 +81,7 @@ class InputPasteboardParser: NSObject { } - if let _ = items[0].types.index(of: NSPasteboard.PasteboardType(rawValue: "com.apple.traditional-mac-plain-text")) { + if let _ = items[0].types.firstIndex(of: NSPasteboard.PasteboardType(rawValue: "com.apple.traditional-mac-plain-text")) { return true } @@ -46,7 +89,7 @@ class InputPasteboardParser: NSObject { files = files.filter { path -> Bool in if let size = fileSize(path.path) { - return size <= 1500000000 + return size <= 2000 * 1024 * 1024 } return false @@ -55,28 +98,107 @@ class InputPasteboardParser: NSObject { let afterSizeCheck = files.count if afterSizeCheck == 0 && previous != afterSizeCheck { - alert(for: mainWindow, header: appName, info: tr(.appMaxFileSize)) return false } - if let peer = chatInteraction.presentation.peer, peer.mediaRestricted { - alertForMediaRestriction(peer) + if !files.isEmpty { + + return false + } else if let _ = image { return false } - if !files.isEmpty { - showModal(with:PreviewSenderController(urls: files, account:account, chatInteraction:chatInteraction), for:window) + } + + return true + } + + public class func proccess(pasteboard:NSPasteboard, chatInteraction:ChatInteraction, window:Window) -> Bool { + let items = pasteboard.pasteboardItems + + + if let items = items, !items.isEmpty { + var files:[URL] = [] + + for item in items { + let path = item.string(forType: NSPasteboard.PasteboardType(rawValue: "public.file-url")) + if let path = path, let url = URL(string: path) { + files.append(url) + } + } + + var image:NSImage? = nil + + if files.isEmpty { + if let images = pasteboard.readObjects(forClasses: [NSImage.self], options: nil) as? [NSImage], !images.isEmpty { + + if let representation = images[0].representations.first as? NSPDFImageRep { + let url = URL(fileURLWithPath: NSTemporaryDirectory() + "ios_scan_\(arc4random()).pdf") + + try? representation.pdfRepresentation.write(to: url) + + files.append(url) + image = nil + } else { + image = images[0] + } + } + } + + + if let _ = items[0].types.firstIndex(of: NSPasteboard.PasteboardType(rawValue: "com.microsoft.appbundleid")) { + return true + } + + let previous = files.count + + files = files.filter { path -> Bool in + if let size = fileSize(path.path) { + return size <= 2000 * 1024 * 1024 + } + + return false + } + + let afterSizeCheck = files.count + + if afterSizeCheck == 0 && previous != afterSizeCheck { + alert(for: mainWindow, info: L10n.appMaxFileSize1) + return false + } + if let peer = chatInteraction.presentation.peer, let permissionText = permissionText(from: peer, for: .banSendMedia) { + if !files.isEmpty || image != nil { + alert(for: mainWindow, info: permissionText) + return false + } + } + + if files.count == 1, let editState = chatInteraction.presentation.interfaceState.editState, editState.canEditMedia { + _ = (Sender.generateMedia(for: MediaSenderContainer(path: files[0].path, isFile: false), account: chatInteraction.context.account, isSecretRelated: chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat) |> deliverOnMainQueue).start(next: { [weak chatInteraction] media, _ in + chatInteraction?.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + }) + return false + } else if let image = image, let editState = chatInteraction.presentation.interfaceState.editState, editState.canEditMedia { + _ = (putToTemp(image: image) |> mapToSignal {Sender.generateMedia(for: MediaSenderContainer(path: $0, isFile: false), account: chatInteraction.context.account, isSecretRelated: chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat) } |> deliverOnMainQueue).start(next: { [weak chatInteraction] media, _ in + chatInteraction?.update({$0.updatedInterfaceState({$0.updatedEditState({$0?.withUpdatedMedia(media)})})}) + }) + return false + } + + if !files.isEmpty { + chatInteraction.showPreviewSender(files, true, nil) return false } else if let image = image { _ = (putToTemp(image: image, compress: false) |> deliverOnMainQueue).start(next: { (path) in - showModal(with:PreviewSenderController(urls: [URL(fileURLWithPath: path)], account:account, chatInteraction:chatInteraction), for:window) + chatInteraction.showPreviewSender([URL(fileURLWithPath: path)], true, nil) }) return false } } + return true } diff --git a/Telegram-Mac/InputSources.swift b/Telegram-Mac/InputSources.swift new file mode 100644 index 0000000000..f8ffabfbe1 --- /dev/null +++ b/Telegram-Mac/InputSources.swift @@ -0,0 +1,60 @@ +// +// InputSources.swift +// Telegram +// +// Created by Mikhail Filimonov on 18/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import SwiftSignalKit +import Foundation +import TelegramCore +import SyncCore +import Postbox + + +final class InputSources: NSObject { + + private let _inputSource: ValuePromise<[String]> = ValuePromise(["en"], ignoreRepeated: true) + + var value: Signal<[String], NoError> { + _inputSource.set(currentAppInputSource().uniqueElements) + return _inputSource.get() |> distinctUntilChanged(isEqual: { $0 == $1 }) + } + + func searchEmoji(postbox: Postbox, sharedContext: SharedAccountContext, query: String, completeMatch: Bool, checkPrediction: Bool) -> Signal<[String], NoError> { + return combineLatest(value, baseAppSettings(accountManager: sharedContext.accountManager)) |> mapToSignal { sources, settings in + if settings.predictEmoji || !checkPrediction { + return combineLatest(sources.map({ searchEmojiKeywords(postbox: postbox, inputLanguageCode: $0, query: query.lowercased(), completeMatch: completeMatch) })) |> map { results in + return results.reduce([], { $0 + $1 }).reduce([], { current, value -> [String] in + if completeMatch { + if query.lowercased() == value.keyword.lowercased() { + return current + value.emoticons + } else { + return current + } + } else { + return current + value.emoticons + } + }).uniqueElements.map { $0.fixed } + } |> distinctUntilChanged + } else { + return .single([]) + } + } + } + + + override init() { + super.init() + NotificationCenter.default.addObserver(self, selector: #selector(inputSourceChanged), name: NSTextInputContext.keyboardSelectionDidChangeNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc private func inputSourceChanged() { + _inputSource.set(currentAppInputSource().uniqueElements) + } +} diff --git a/Telegram-Mac/InputURLFormatterModalController.swift b/Telegram-Mac/InputURLFormatterModalController.swift new file mode 100644 index 0000000000..0fca36d876 --- /dev/null +++ b/Telegram-Mac/InputURLFormatterModalController.swift @@ -0,0 +1,146 @@ +// +// InputURLFormatterModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +private struct InputURLFormatterState : Equatable { + let text: String + let url: String? + init(text: String, url: String?) { + self.text = text + self.url = url + } + + func withUpdatedUrl(_ url: String?) -> InputURLFormatterState { + return InputURLFormatterState(text: self.text, url: url) + } +} + +private let _id_input_url = InputDataIdentifier("_id_input_url") + +private func inputURLFormatterEntries(state: InputURLFormatterState) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.inputFormatterTextHeader), data: InputDataGeneralTextData(color: theme.colors.text, viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("_id_text"), equatable: nil, item: { initialSize, stableId in + return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .singleItem, text: state.text, font: .normal(.text)) + })) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.inputFormatterURLHeader), data: InputDataGeneralTextData(color: theme.colors.text, viewType: .textTopItem))) + index += 1 + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.url), error: nil, identifier: _id_input_url, mode: .plain, data: InputDataRowData( viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.inputFormatterURLHeader, filter: { $0 }, limit: 10000)) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func InputURLFormatterModalController(string: String, defaultUrl: String? = nil, completion: @escaping(String?) -> Void) -> InputDataModalController { + + + let initialState = InputURLFormatterState(text: string, url: defaultUrl?.removingPercentEncoding) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((InputURLFormatterState) -> InputURLFormatterState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let dataSignal = statePromise.get() |> map { state in + return inputURLFormatterEntries(state: state) + } + + var close: (() -> Void)? = nil + + let controller = InputDataController(dataSignal: dataSignal |> map { InputDataSignalValue(entries: $0) }, title: L10n.inputFormatterURLHeader, validateData: { data in + + if let string = data[_id_input_url]?.stringValue { + + let attr = NSMutableAttributedString(string: string) + + attr.detectLinks(type: [.Links]) + + var url:String? = nil + + + attr.enumerateAttribute(NSAttributedString.Key.link, in: attr.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in + + if let value = value as? inAppLink { + switch value { + case let .external(link, _): + url = link + break + default: + break + } + } + + let s: ObjCBool = (url != nil) ? true : false + stop.pointee = s + + }) + + completion(url) + close?() + return .none + + + } + + return .fail(.fields([_id_input_url: .shake])) + + }, updateDatas: { data in + + updateState { + $0.withUpdatedUrl(data[_id_input_url]?.stringValue) + } + + return .none + + }) + + + let modalInteractions = ModalInteractions(acceptTitle: L10n.modalOK, accept: { [weak controller] in + controller?.validateInputValues() + }, drawBorder: true, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + close = { [weak modalController] in + modalController?.modal?.close() + } + + + + return modalController + +} diff --git a/Telegram-Mac/InstalledStickerPacksController.swift b/Telegram-Mac/InstalledStickerPacksController.swift index 1e96086a6b..af6766fe93 100644 --- a/Telegram-Mac/InstalledStickerPacksController.swift +++ b/Telegram-Mac/InstalledStickerPacksController.swift @@ -8,26 +8,43 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +enum InstalledStickerPacksEntryTag: ItemListItemTag { + case suggestOptions + case loopAnimatedStickers + + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? InstalledStickerPacksEntryTag, self == other { + return true + } else { + return false + } + } +} private final class InstalledStickerPacksControllerArguments { - let account: Account + let context: AccountContext let openStickerPack: (StickerPackCollectionInfo) -> Void let removePack: (ItemCollectionId) -> Void let openStickersBot: () -> Void let openFeatured: () -> Void - let openArchived: () -> Void - - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping () -> Void) { - self.account = account + let openArchived: ([ArchivedStickerPackItem]?) -> Void + let openSuggestionOptions: () -> Void + let toggleLoopAnimated: (Bool)->Void + init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestionOptions: @escaping() -> Void, toggleLoopAnimated: @escaping(Bool)->Void) { + self.context = context self.openStickerPack = openStickerPack self.removePack = removePack self.openStickersBot = openStickersBot self.openFeatured = openFeatured self.openArchived = openArchived + self.openSuggestionOptions = openSuggestionOptions + self.toggleLoopAnimated = toggleLoopAnimated } } @@ -78,107 +95,80 @@ private enum InstalledStickerPacksEntryId: Hashable { } } +private struct ArchivedListContainer : Equatable { + let archived: [ArchivedStickerPackItem]? + static func ==(lhs: ArchivedListContainer, rhs: ArchivedListContainer) -> Bool { + if let lhsItem = lhs.archived, let rhsItem = rhs.archived { + if lhsItem.count != rhsItem.count { + return false + } else { + for i in 0 ..< lhsItem.count { + let lhs = lhsItem[i] + let rhs = rhsItem[i] + if lhs.info != rhs.info { + return false + } + if lhs.topItems != rhs.topItems { + return false + } + } + } + } else if (lhs.archived != nil) != (rhs.archived != nil) { + return false + } + return true + } +} + private enum InstalledStickerPacksEntry: TableItemListNodeEntry { case section(sectionId:Int32) - case trending(sectionId:Int32, Int32) - case archived(sectionId:Int32) - case packsTitle(sectionId:Int32, String) - case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool, ItemListStickerPackItemEditing) - case packsInfo(sectionId:Int32, String) + case suggestOptions(sectionId: Int32, String, GeneralViewType) + case trending(sectionId:Int32, Int32, GeneralViewType) + case archived(sectionId:Int32, ArchivedListContainer, GeneralViewType) + case loopAnimated(sectionId: Int32, Bool, GeneralViewType) + case packsTitle(sectionId:Int32, String, GeneralViewType) + case pack(sectionId:Int32, Int32, StickerPackCollectionInfo, StickerPackItem?, Int32, Bool, Bool, ItemListStickerPackItemEditing, GeneralViewType) + case packsInfo(sectionId:Int32, String, GeneralViewType) var stableId: InstalledStickerPacksEntryId { switch self { - case .trending: + case .suggestOptions: return .index(0) - case .archived: + case .trending: return .index(1) + case .archived: + return .index(2) + case .loopAnimated: + return .index(4) case .packsTitle: - return .index(3) - case let .pack(_, _, info, _, _, _, _): + return .index(5) + case let .pack(_, _, info, _, _, _, _, _, _): return .pack(info.id) case .packsInfo: - return .index(4) + return .index(6) case let .section(sectionId): return .index((sectionId + 1) * 1000 - sectionId) } } - static func ==(lhs: InstalledStickerPacksEntry, rhs: InstalledStickerPacksEntry) -> Bool { - switch lhs { - case let .trending(sectionId, count): - if case .trending(sectionId, count) = rhs { - return true - } else { - return false - } - case let .archived(sectionId): - if case .archived(sectionId) = rhs { - return true - } else { - return false - } - case let .packsTitle(sectionId, text): - if case .packsTitle(sectionId, text) = rhs { - return true - } else { - return false - } - case let .pack(lhsSectionId, lhsIndex, lhsInfo, lhsTopItem, lhsCount, lhsEnabled, lhsEditing): - if case let .pack(rhsSectionId, rhsIndex, rhsInfo, rhsTopItem, rhsCount, rhsEnabled, rhsEditing) = rhs { - - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if lhsInfo != rhsInfo { - return false - } - if lhsTopItem != rhsTopItem { - return false - } - if lhsCount != rhsCount { - return false - } - if lhsEnabled != rhsEnabled { - return false - } - if lhsEditing != rhsEditing { - return false - } - return true - } else { - return false - } - case let .packsInfo(sectionId, text): - if case .packsInfo(sectionId, text) = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } var stableIndex:Int32 { switch self { - case .trending: + case .suggestOptions: return 0 - case .archived: + case .trending: return 1 - case .packsTitle: + case .archived: return 2 + case .loopAnimated: + return 3 + case .packsTitle: + return 4 case .pack: fatalError("") case .packsInfo: - return 4 + return 5 case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } @@ -186,15 +176,19 @@ private enum InstalledStickerPacksEntry: TableItemListNodeEntry { var index:Int32 { switch self { - case let .trending(sectionId, _): + case let .suggestOptions(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .archived(sectionId): + case let .trending(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .packsTitle(sectionId, _): + case let .archived(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .pack( sectionId, index, _, _, _, _, _): + case let .loopAnimated(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .packsTitle(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .pack( sectionId, index, _, _, _, _, _, _, _): return (sectionId * 1000) + 100 + index - case let .packsInfo(sectionId, _): + case let .packsInfo(sectionId, _, _): return (sectionId * 1000) + stableIndex case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -207,21 +201,26 @@ private enum InstalledStickerPacksEntry: TableItemListNodeEntry { func item(_ arguments: InstalledStickerPacksControllerArguments, initialSize:NSSize) -> TableRowItem { switch self { - case let .trending(_, count): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.installedStickersTranding), type: .context(stateback: { () -> String in - return count > 0 ? "\(count)" : "" - }), action: { + case let .suggestOptions(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.stickersSuggestStickers, type: .context(value), viewType: viewType, action: { + arguments.openSuggestionOptions() + }) + case let .trending(_, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.installedStickersTranding, type: .context(count > 0 ? "\(count)" : ""), viewType: viewType, action: { arguments.openFeatured() }) - - case .archived: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.installedStickersArchived), type: .next, action: { - arguments.openArchived() + case let .archived(_, archived, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.installedStickersArchived, type: .next, viewType: viewType, action: { + arguments.openArchived(archived.archived) + }) + case let .loopAnimated(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.installedStickersLoopAnimated, type: .switchable(value), viewType: viewType, action: { + arguments.toggleLoopAnimated(!value) }) - case let .packsTitle(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .pack(_, _, info, topItem, count, enabled, editing): - return StickerSetTableRowItem(initialSize, account: arguments.account, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: editing, enabled: enabled, control: .none, action: { + case let .packsTitle(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .pack(_, _, info, topItem, count, enabled, _, editing, viewType): + return StickerSetTableRowItem(initialSize, context: arguments.context, stableId: stableId, info: info, topItem: topItem, itemCount: count, unread: false, editing: editing, enabled: enabled, control: .none, viewType: viewType, action: { arguments.openStickerPack(info) }, addPack: { @@ -229,10 +228,10 @@ private enum InstalledStickerPacksEntry: TableItemListNodeEntry { arguments.removePack(info.id) }) - case let .packsInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, height: 20, text: text) + case let .packsInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } @@ -267,7 +266,7 @@ private struct InstalledStickerPacksControllerState: Equatable { } -private func installedStickerPacksControllerEntries(state: InstalledStickerPacksControllerState, view: CombinedView, featured: [FeaturedStickerPackItem]) -> [InstalledStickerPacksEntry] { +private func installedStickerPacksControllerEntries(state: InstalledStickerPacksControllerState, autoplayMedia: AutoplayMediaPreferences, stickerSettings: StickerSettings, view: CombinedView, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?) -> [InstalledStickerPacksEntry] { var entries: [InstalledStickerPacksEntry] = [] var sectionId:Int32 = 1 @@ -275,6 +274,17 @@ private func installedStickerPacksControllerEntries(state: InstalledStickerPacks entries.append(.section(sectionId: sectionId)) sectionId += 1 + let suggestString: String + switch stickerSettings.emojiStickerSuggestionMode { + case .none: + suggestString = L10n.stickersSuggestNone + case .all: + suggestString = L10n.stickersSuggestAll + case .installed: + suggestString = L10n.stickersSuggestAdded + } + entries.append(.suggestOptions(sectionId: sectionId, suggestString, .firstItem)) + if featured.count != 0 { var unreadCount: Int32 = 0 for item in featured { @@ -282,29 +292,30 @@ private func installedStickerPacksControllerEntries(state: InstalledStickerPacks unreadCount += 1 } } - entries.append(.trending(sectionId: sectionId, unreadCount)) + entries.append(.trending(sectionId: sectionId, unreadCount, .innerItem)) } - entries.append(.archived(sectionId: sectionId)) + entries.append(.archived(sectionId: sectionId, ArchivedListContainer(archived: archived), .innerItem)) + entries.append(.loopAnimated(sectionId: sectionId, autoplayMedia.loopAnimatedStickers, .lastItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.packsTitle(sectionId: sectionId, tr(.installedStickersPacksTitle))) + entries.append(.packsTitle(sectionId: sectionId, L10n.installedStickersPacksTitle, .textTopItem)) if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { var index: Int32 = 0 for entry in packsEntries { if let info = entry.info as? StickerPackCollectionInfo { - entries.append(.pack(sectionId: sectionId, index, info, entry.firstItem as? StickerPackItem, info.count == 0 ? entry.count : info.count, true, ItemListStickerPackItemEditing(editable: true, editing: state.editing))) + let viewType: GeneralViewType = bestGeneralViewType(packsEntries, for: entry) + + entries.append(.pack(sectionId: sectionId, index, info, entry.firstItem as? StickerPackItem, info.count == 0 ? entry.count : info.count, true, autoplayMedia.loopAnimatedStickers, ItemListStickerPackItemEditing(editable: true, editing: state.editing), viewType)) index += 1 } } } } - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - entries.append(.packsInfo(sectionId: sectionId, tr(.installedStickersDescrpiption))) + entries.append(.packsInfo(sectionId: sectionId, L10n.installedStickersDescrpiption, .textBottomItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 return entries @@ -319,63 +330,223 @@ private func prepareTransition(left:[AppearanceWrapperEntry InstalledStickerPacksControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } + let archivedPromise = Promise<[ArchivedStickerPackItem]?>() + archivedPromise.set(.single(nil) |> then(archivedStickerPacks(account: context.account) |> map(Optional.init))) + + let actionsDisposable = DisposableSet() let resolveDisposable = MetaDisposable() actionsDisposable.add(resolveDisposable) - let arguments = InstalledStickerPacksControllerArguments(account: account, openStickerPack: { info in - showModal(with: StickersPackPreviewModalController(account, peerId: nil, reference: .name(info.shortName)), for: mainWindow) + let arguments = InstalledStickerPacksControllerArguments(context: context, openStickerPack: { info in + showModal(with: StickerPackPreviewModalController(context, peerId: nil, reference: .name(info.shortName)), for: context.window) }, removePack: { id in - confirm(for: mainWindow, with: appName, and: tr(.installedStickersRemoveDescription), okTitle: tr(.installedStickersRemoveDelete), successHandler: { result in + confirm(for: context.window, information: tr(L10n.installedStickersRemoveDescription), okTitle: tr(L10n.installedStickersRemoveDelete), successHandler: { result in switch result { case .basic: - _ = removeStickerPackInteractively(postbox: account.postbox, id: id).start() + _ = removeStickerPackInteractively(postbox: context.account.postbox, id: id, option: RemoveStickerPackOption.archive).start() case .thrid: break } }) }, openStickersBot: { - resolveDisposable.set((resolvePeerByName(account: account, name: "stickers") |> deliverOnMainQueue).start(next: { peerId in + resolveDisposable.set((resolvePeerByName(account: context.account, name: "stickers") |> deliverOnMainQueue).start(next: { peerId in if let peerId = peerId { // navigateToChatControllerImpl?(peerId) } })) }, openFeatured: { [weak self] in - self?.navigationController?.push(FeaturedStickerPacksController(account)) - }, openArchived: { [weak self] in - self?.navigationController?.push(ArchivedStickerPacksController(account)) + self?.navigationController?.push(FeaturedStickerPacksController(context)) + }, openArchived: { [weak self] archived in + self?.navigationController?.push(ArchivedStickerPacksController(context, archived: archived, updatedPacks: { packs in + archivedPromise.set(.single(packs)) + })) + }, openSuggestionOptions: { [weak self] in + self?.openSuggestionOptions() + }, toggleLoopAnimated: { value in + _ = updateAutoplayMediaSettingsInteractively(postbox: context.account.postbox, { + $0.withUpdatedLoopAnimatedStickers(value) + }).start() }) - let stickerPacks = Promise() - stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + let stickerPacks = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]) + + let featured = context.account.viewTracker.featuredStickerPacks() - let featured = Promise<[FeaturedStickerPackItem]>() - featured.set(account.viewTracker.featuredStickerPacks()) + + let stickerSettingsKey = ApplicationSpecificPreferencesKeys.stickerSettings + let autoplayKey = ApplicationSpecificPreferencesKeys.autoplayMedia + let preferencesKey: PostboxViewKey = .preferences(keys: Set([stickerSettingsKey, autoplayKey])) + let preferencesView = context.account.postbox.combinedView(keys: [preferencesKey]) let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = self.atomicSize - genericView.merge(with: combineLatest(statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue, appearanceSignal) - |> map { state, view, featured, appearance -> TableUpdateTransition in - let entries = installedStickerPacksControllerEntries(state: state, view: view, featured: featured).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + + let signal = combineLatest(queue: prepareQueue, statePromise.get(), stickerPacks, featured, archivedPromise.get(), appearanceSignal, preferencesView) + |> map { state, view, featured, archived, appearance, preferencesView -> TableUpdateTransition in + + var stickerSettings = StickerSettings.defaultSettings + if let view = preferencesView.views[preferencesKey] as? PreferencesView { + if let value = view.values[stickerSettingsKey] as? StickerSettings { + stickerSettings = value + } + } + + var autoplayMedia = AutoplayMediaPreferences.defaultSettings + if let view = preferencesView.views[preferencesKey] as? PreferencesView { + if let value = view.values[autoplayKey] as? AutoplayMediaPreferences { + autoplayMedia = value + } + } + + let entries = installedStickerPacksControllerEntries(state: state, autoplayMedia: autoplayMedia, stickerSettings: stickerSettings, view: view, featured: featured, archived: archived).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previousEntries.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) - } |> afterDisposed { - actionsDisposable.dispose() - } ) - readyOnce() + } |> afterDisposed { + actionsDisposable.dispose() + } |> deliverOnMainQueue + + + disposbale.set(signal.start(next: { [weak self] transition in + guard let `self` = self else {return} + + self.genericView.merge(with: transition) + + self.readyOnce() + + if !transition.isEmpty { + var start: Int? = nil + var length: Int = 0 + self.genericView.enumerateItems(with: { item -> Bool in + if item is StickerSetTableRowItem { + if start == nil { + start = item.index + } + length += 1 + } else if start != nil { + return false + } + return true + }) + if let start = start { + self.genericView.resortController = TableResortController(resortRange: NSMakeRange(start, length), startTimeout: 0.2, start: { _ in }, resort: { _ in }, complete: { fromIndex, toIndex in + + + if fromIndex == toIndex { + return + } + + let entries = previousEntries.with {$0}.map( {$0.entry }) + + + let fromEntry = entries[fromIndex] + guard case let .pack(_, _, fromPackInfo, _, _, _, _, _, _) = fromEntry else { + return + } + + var referenceId: ItemCollectionId? + var beforeAll = false + var afterAll = false + if toIndex < entries.count { + switch entries[toIndex] { + case let .pack(_, _, toPackInfo, _, _, _, _, _, _): + referenceId = toPackInfo.id + default: + if entries[toIndex] < fromEntry { + beforeAll = true + } else { + afterAll = true + } + } + } else { + afterAll = true + } + + + let _ = (context.account.postbox.transaction { transaction -> Void in + var infos = transaction.getItemCollectionsInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks) + var reorderInfo: ItemCollectionInfo? + for i in 0 ..< infos.count { + if infos[i].0 == fromPackInfo.id { + reorderInfo = infos[i].1 + infos.remove(at: i) + break + } + } + if let reorderInfo = reorderInfo { + if let referenceId = referenceId { + var inserted = false + for i in 0 ..< infos.count { + if infos[i].0 == referenceId { + if fromIndex < toIndex { + infos.insert((fromPackInfo.id, reorderInfo), at: i + 1) + } else { + infos.insert((fromPackInfo.id, reorderInfo), at: i) + } + inserted = true + break + } + } + if !inserted { + infos.append((fromPackInfo.id, reorderInfo)) + } + } else if beforeAll { + infos.insert((fromPackInfo.id, reorderInfo), at: 0) + } else if afterAll { + infos.append((fromPackInfo.id, reorderInfo)) + } + addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: Namespaces.ItemCollection.CloudStickerPacks, content: .sync, noDelay: false) + transaction.replaceItemCollectionInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks, itemCollectionInfos: infos) + } + }).start() + + }) + } else { + self.genericView.resortController = nil + } + } + + + + })) + + } } diff --git a/Telegram-Mac/InstantPageAnchorItem.swift b/Telegram-Mac/InstantPageAnchorItem.swift index a21b615aeb..1220818f79 100644 --- a/Telegram-Mac/InstantPageAnchorItem.swift +++ b/Telegram-Mac/InstantPageAnchorItem.swift @@ -7,17 +7,19 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore final class InstantPageAnchorItem: InstantPageItem { var frame: CGRect let medias: [InstantPageMedia] = [] - let wantsNode: Bool = false + let wantsView: Bool = false let hasLinks: Bool = false let isInteractive: Bool = false - - private let anchor: String + let separatesTiles: Bool = false + + let anchor: String init(frame: CGRect, anchor: String) { self.anchor = anchor @@ -32,11 +34,11 @@ final class InstantPageAnchorItem: InstantPageItem { return self.anchor == anchor } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { return false } - func node(account: Account) -> InstantPageView? { + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { return nil } diff --git a/Telegram-Mac/InstantPageArticleItem.swift b/Telegram-Mac/InstantPageArticleItem.swift new file mode 100644 index 0000000000..1ef7cacd6a --- /dev/null +++ b/Telegram-Mac/InstantPageArticleItem.swift @@ -0,0 +1,251 @@ +// +// InstantPageArticleItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import Postbox +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit + + +final class InstantPageArticleItem: InstantPageItem { + + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + let hasLinks: Bool = false + + + let isInteractive: Bool = false + + + + var frame: CGRect + let wantsView: Bool = true + let separatesTiles: Bool = false + let medias: [InstantPageMedia] = [] + let webPage: TelegramMediaWebpage + + let contentItems: [InstantPageItem] + let contentSize: CGSize + let cover: TelegramMediaImage? + let url: String + let webpageId: MediaId + let rtl: Bool + + init(frame: CGRect, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool) { + self.frame = frame + self.webPage = webPage + self.contentItems = contentItems + self.contentSize = contentSize + self.cover = cover + self.url = url + self.webpageId = webpageId + self.rtl = rtl + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return InstantPageArticleView(context: arguments.context, item: self, webPage: self.webPage, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, rtl: self.rtl, openUrl: arguments.openUrl) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesView(_ view: InstantPageView) -> Bool { + if let view = view as? InstantPageArticleView { + return self === view.item + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 7 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 1000.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func drawInTile(context: CGContext) { + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } +} + +func layoutArticleItem(theme: InstantPageTheme, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: MediaId, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem { + let inset: CGFloat = 17.0 + let imageSpacing: CGFloat = 10.0 + var sideInset = inset + let imageSize = CGSize(width: 44.0, height: 44.0) + if cover != nil { + sideInset += imageSize.width + imageSpacing + } + + var availableLines: Int = 3 + var contentHeight: CGFloat = 15.0 * 2.0 + + var hasRTL = false + var contentItems: [InstantPageItem] = [] + let (titleTextItem, titleItems, titleSize) = layoutTextItemWithString(title, boundingWidth: boundingWidth - inset - sideInset, offset: CGPoint(x: inset, y: 15.0), maxNumberOfLines: availableLines) + contentItems.append(contentsOf: titleItems) + contentHeight += titleSize.height + + if let textItem = titleTextItem { + availableLines -= textItem.lines.count + if textItem.containsRTL { + hasRTL = true + } + } + var descriptionInset = inset + if hasRTL && cover != nil { + descriptionInset += imageSize.width + imageSpacing + for var item in titleItems { + item.frame = item.frame.offsetBy(dx: imageSize.width + imageSpacing, dy: 0.0) + } + } + + if availableLines > 0 { + let (descriptionTextItem, descriptionItems, descriptionSize) = layoutTextItemWithString(description, boundingWidth: boundingWidth - inset - sideInset, alignment: hasRTL ? .right : .natural, offset: CGPoint(x: descriptionInset, y: 15.0 + titleSize.height + 14.0), maxNumberOfLines: availableLines) + contentItems.append(contentsOf: descriptionItems) + + if let textItem = descriptionTextItem { + if textItem.containsRTL || hasRTL { + hasRTL = true + } + } + contentHeight += descriptionSize.height + 14.0 + } + + let contentSize = CGSize(width: boundingWidth, height: contentHeight) + return InstantPageArticleItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height)), webPage: webPage, contentItems: contentItems, contentSize: contentSize, cover: cover, url: url, webpageId: webpageId, rtl: rtl || hasRTL) +} + + + + + + + + +final class InstantPageArticleView: Button, InstantPageView { + let item: InstantPageArticleItem + + + private let contentTile: InstantPageTile + private let contentTileView: InstantPageTileView + private var imageView: TransformImageView? + + let url: String + let webpageId: MediaId + let cover: TelegramMediaImage? + let rtl: Bool + + private let openUrl: (InstantPageUrlItem) -> Void + + private var fetchedDisposable = MetaDisposable() + + init(context: AccountContext, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void) { + self.item = item + self.url = url + self.webpageId = webpageId + self.cover = cover + self.rtl = rtl + self.openUrl = openUrl + + self.contentTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height)) + self.contentTile.items.append(contentsOf: contentItems) + self.contentTileView = InstantPageTileView(tile: self.contentTile, backgroundColor: .clear) + + super.init() + + self.addSubview(self.contentTileView) + + if let image = cover { + let imageView = TransformImageView() + + let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) + imageView.setSignal(chatMessagePhoto(account: context.account, imageReference: imageReference, scale: backingScaleFactor)) + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: imageReference).start()) + + self.imageView = imageView + self.addSubview(imageView) + } + + set(handler: { [weak self] _ in + self?.click() + }, for: .Up) + + set(background: theme.colors.grayBackground, for: .Highlight) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + deinit { + self.fetchedDisposable.dispose() + } + + private func click() { + self.openUrl(InstantPageUrlItem(url: self.url, webpageId: self.webpageId)) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + let inset: CGFloat = 17.0 + let imageSize = CGSize(width: 44.0, height: 44.0) + + self.contentTileView.frame = self.bounds + + if let imageView = self.imageView, let image = self.cover, let largest = largestImageRepresentation(image.representations) { + let size = largest.dimensions.size.aspectFilled(imageSize) + let boundingSize = imageSize + + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: 5.0), imageSize: size, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets())) + } + + if let imageView = self.imageView { + if self.rtl { + imageView.frame = CGRect(origin: CGPoint(x: inset, y: 11.0), size: imageSize) + } else { + imageView.frame = CGRect(origin: CGPoint(x: size.width - inset - imageSize.width, y: 11.0), size: imageSize) + } + } + } + + func updateIsVisible(_ isVisible: Bool) { + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + } + + + + func updateHiddenMedia(media: InstantPageMedia?) { + } + +} diff --git a/Telegram-Mac/InstantPageAudioItem.swift b/Telegram-Mac/InstantPageAudioItem.swift new file mode 100644 index 0000000000..bcccb82000 --- /dev/null +++ b/Telegram-Mac/InstantPageAudioItem.swift @@ -0,0 +1,71 @@ +// +// InstantPageAudioItem.swift +// Telegram +// +// Created by keepcoder on 11/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore + +final class InstantPageAudioItem: InstantPageItem { + let wantsView: Bool = true + let hasLinks: Bool = false + var isInteractive: Bool { + return true + } + let separatesTiles: Bool = false + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + var frame: CGRect + let medias: [InstantPageMedia] + + let media: InstantPageMedia + + init(frame: CGRect, media: InstantPageMedia) { + self.frame = frame + self.media = media + self.medias = [media] + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return InstantPageAudioView(context: arguments.context, media: media) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesView(_ node: InstantPageView) -> Bool { + if let node = node as? InstantPageAudioView { + return self.media == node.media + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 4 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 1000.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func drawInTile(context: CGContext) { + } +} diff --git a/Telegram-Mac/InstantPageAudioView.swift b/Telegram-Mac/InstantPageAudioView.swift new file mode 100644 index 0000000000..c0a9b61604 --- /dev/null +++ b/Telegram-Mac/InstantPageAudioView.swift @@ -0,0 +1,186 @@ +// +// InstantPageAudioItem.swift +// Telegram +// +// Created by keepcoder on 11/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +final class InstantPageAudioView: View, InstantPageView, APDelegate { + + + private let context: AccountContext + let media: InstantPageMedia + private var nameView: TextView? + private let statusView: RadialProgressView = RadialProgressView() + private let linearProgress: LinearProgressControl = LinearProgressControl(progressHeight: 3) + private var bufferingStatusDisposable: MetaDisposable = MetaDisposable() + private var ranges: (IndexSet, Int)? + + weak var controller:APController? { + didSet { + if let controller = controller { + self.bufferingStatusDisposable.set((controller.bufferingStatus + |> deliverOnMainQueue).start(next: { [weak self] status in + if let status = status { + self?.updateStatus(status.0, status.1) + } + })) + controller.add(listener: self) + } + } + } + + private func updateStatus(_ ranges: IndexSet, _ size: Int) { + self.ranges = (ranges, size) + + if let ranges = self.ranges, !ranges.0.isEmpty, ranges.1 != 0 { + for range in ranges.0.rangeView { + var progress = (CGFloat(range.count) / CGFloat(ranges.1)) + progress = progress == 1.0 ? 0 : progress + linearProgress.set(fetchingProgress: progress, animated: progress > 0) + + break + } + } + } + + deinit { + bufferingStatusDisposable.dispose() + } + + init(context: AccountContext, media: InstantPageMedia) { + self.context = context + self.media = media + super.init() + addSubview(statusView) + addSubview(linearProgress) + linearProgress.style = ControlStyle(foregroundColor: theme.colors.text, backgroundColor: theme.colors.border, highlightColor: theme.colors.text) + linearProgress.set(background: theme.colors.border, for: .Normal) + + let file = media.media as! TelegramMediaFile + + if file.isMusic { + nameView = TextView() + let attr = NSMutableAttributedString() + _ = attr.append(string: file.musicText.1, color: theme.colors.text, font: .medium(.title)) + _ = attr.append(string: " - ", color: theme.colors.grayText, font: .normal(.title)) + _ = attr.append(string: file.musicText.0, color: theme.colors.grayText, font: .normal(.title)) + let nameLayout = TextViewLayout(attr, maximumNumberOfLines: 1) + nameView?.update(nameLayout) + addSubview(nameView!) + } + + linearProgress.fetchingColor = theme.colors.grayText + + if let current = globalAudio?.currentSong { + if current.entry.isEqual(to: self.wrapper) { + globalAudio?.add(listener: self) + } + } + statusView.state = .Icon(image: theme.icons.ivAudioPlay, mode: .copy) + linearProgress.set(progress: 0, animated:true) + + } + + var wrapper: APSingleWrapper { + let file = self.media.media as! TelegramMediaFile + return APSingleWrapper(resource: file.resource, name: nil, performer: nil, id: file.id ?? MediaId(namespace: 0, id: 0)) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + + + + let insets = NSEdgeInsets(top: 18.0, left: 17.0, bottom: 18.0, right: 17.0) + + let leftInset: CGFloat = 46.0 + 10.0 + let rightInset: CGFloat = 0.0 + + let maxTitleWidth = max(1.0, size.width - insets.left - leftInset - rightInset - insets.right) + + statusView.centerY(x: insets.left) + + let leftScrubberInset: CGFloat = insets.left + 46.0 + 10.0 + let rightScrubberInset: CGFloat = insets.right + + + if let nameView = nameView { + nameView.layout?.measure(width: maxTitleWidth) + nameView.update(nameView.layout, origin: CGPoint(x: insets.left + leftInset, y: 5)) + } + + var topOffset: CGFloat = 0.0 + if nameView == nil { + topOffset = -10.0 + } + + linearProgress.frame = CGRect(origin: CGPoint(x: leftScrubberInset, y: 26.0 + topOffset + 5), size: CGSize(width: size.width - leftScrubberInset - rightScrubberInset, height: 5)) + + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + func songDidChanged(song: APSongItem, for controller: APController) { + linearProgress.onUserChanged = { [weak controller, weak self] progress in + controller?.set(trackProgress: progress) + self?.linearProgress.set(progress: CGFloat(progress), animated: false) + } + } + + func songDidChangedState(song: APSongItem, for controller: APController) { + switch song.state { + case .waiting, .paused: + statusView.state = .Icon(image: theme.icons.ivAudioPlay, mode: .copy) + case .stoped: + statusView.state = .Icon(image: theme.icons.ivAudioPlay, mode: .copy) + linearProgress.set(progress: 0, animated:true) + case let .playing(data): + linearProgress.set(progress: CGFloat(data.progress), animated: data.animated) + statusView.state = .Icon(image: theme.icons.ivAudioPause, mode: .copy) + break + case .fetching: + break + } + } + + func songDidStartPlaying(song: APSongItem, for controller: APController) { + + } + + func songDidStopPlaying(song: APSongItem, for controller: APController) { + self.bufferingStatusDisposable.set(nil) + statusView.state = .Icon(image: theme.icons.ivAudioPlay, mode: .copy) + linearProgress.set(progress: 0) + linearProgress.set(fetchingProgress: 0) + linearProgress.onUserChanged = nil + } + + func playerDidChangedTimebase(song: APSongItem, for controller: APController) { + + } + + func audioDidCompleteQueue(for controller: APController) { + + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/InstantPageChannelItem.swift b/Telegram-Mac/InstantPageChannelItem.swift index d872b737a6..069e3b3380 100644 --- a/Telegram-Mac/InstantPageChannelItem.swift +++ b/Telegram-Mac/InstantPageChannelItem.swift @@ -7,17 +7,20 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit class InstantPageChannelItem: InstantPageItem { var frame: CGRect let medias: [InstantPageMedia] = [] - let wantsNode: Bool = true + let wantsView: Bool = true let hasLinks: Bool = false let isInteractive: Bool = false + let separatesTiles: Bool = false + let channel: TelegramChannel let overlay: Bool private let joinChannel:(TelegramChannel)->Void @@ -39,11 +42,11 @@ class InstantPageChannelItem: InstantPageItem { return false } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { return node is InstantPageChannelView } - func node(account: Account) -> InstantPageView? { + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { return InstantPageChannelView(frameRect: frame, channel: channel, overlay: overlay, openChannel: openChannel, joinChannel: joinChannel) } diff --git a/Telegram-Mac/InstantPageChannelView.swift b/Telegram-Mac/InstantPageChannelView.swift index d2d261ac88..6dd3877080 100644 --- a/Telegram-Mac/InstantPageChannelView.swift +++ b/Telegram-Mac/InstantPageChannelView.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox class InstantPageChannelView : View, InstantPageView { private let channel: TelegramChannel @@ -27,7 +28,7 @@ class InstantPageChannelView : View, InstantPageView { self.openChannel = openChannel checkView.image = theme.icons.ivChannelJoined checkView.sizeToFit() - joinLayout = TextNode.layoutText(.initialize(string: tr(.ivChannelJoin), color: .white, font: .normal(.huge)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + joinLayout = TextNode.layoutText(.initialize(string: tr(L10n.ivChannelJoin), color: .white, font: .normal(.huge)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) nameLayout = TextNode.layoutText(.initialize(string: channel.displayTitle, color: .white, font: .normal(.huge)), nil, 1, .end, NSMakeSize(frameRect.width - 40 - joinLayout.0.size.width, .greatestFiniteMagnitude), nil, false, .left) super.init(frame: frameRect) @@ -37,7 +38,7 @@ class InstantPageChannelView : View, InstantPageView { override func layout() { super.layout() - joinLayout = TextNode.layoutText(.initialize(string: tr(.ivChannelJoin), color: overlay ? .white : theme.colors.text, font: .normal(.huge)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + joinLayout = TextNode.layoutText(.initialize(string: tr(L10n.ivChannelJoin), color: overlay ? .white : theme.colors.text, font: .normal(.huge)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) nameLayout = TextNode.layoutText(.initialize(string: channel.displayTitle, color: overlay ? .white : theme.colors.text, font: .normal(.huge)), nil, 1, .end, NSMakeSize(frame.width - 40 - joinLayout.0.size.width, .greatestFiniteMagnitude), nil, false, .left) checkView.centerY(x: frame.width - checkView.frame.width - 20) @@ -66,7 +67,7 @@ class InstantPageChannelView : View, InstantPageView { ctx.fill(bounds) let f = focus(nameLayout.0.size) - nameLayout.1.draw(NSMakeRect(40, f.minY, f.width, f.height), in: ctx, backingScaleFactor: backingScaleFactor) + nameLayout.1.draw(NSMakeRect(40, f.minY, f.width, f.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) switch channel.participationStatus { case .member: @@ -74,7 +75,7 @@ class InstantPageChannelView : View, InstantPageView { default: checkView.isHidden = true let f = focus(joinLayout.0.size) - joinLayout.1.draw(NSMakeRect(frame.width - f.width - 40, f.minY, f.width, f.height), in: ctx, backingScaleFactor: backingScaleFactor) + joinLayout.1.draw(NSMakeRect(frame.width - f.width - 40, f.minY, f.width, f.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } } diff --git a/Telegram-Mac/InstantPageContentView.swift b/Telegram-Mac/InstantPageContentView.swift new file mode 100644 index 0000000000..9108eb7da9 --- /dev/null +++ b/Telegram-Mac/InstantPageContentView.swift @@ -0,0 +1,409 @@ +// +// InstantPageContentView.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import TGUIKit + + +final class InstantPageContentView : View { + private let arguments: InstantPageItemArguments + + + var currentLayoutTiles: [InstantPageTile] = [] + var currentLayoutItemsWithViews: [InstantPageItem] = [] + var distanceThresholdGroupCount: [Int: Int] = [:] + + var visibleTiles: [Int: InstantPageTileView] = [:] + var visibleItemsWithViews: [Int: InstantPageView] = [:] + + var currentWebEmbedHeights: [Int : CGFloat] = [:] + var currentExpandedDetails: [Int : Bool]? + var currentDetailsItems: [InstantPageDetailsItem] = [] + + var requestLayoutUpdate: ((Bool) -> Void)? + + var currentLayout: InstantPageLayout + let contentSize: CGSize + let inOverlayPanel: Bool + + private var previousVisibleBounds: CGRect? + + init(arguments: InstantPageItemArguments, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false) { + self.arguments = arguments + + + self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + self.contentSize = contentSize + self.inOverlayPanel = inOverlayPanel + + super.init() + + self.updateLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + func updateLayout() { + for (_, tileView) in self.visibleTiles { + tileView.removeFromSuperview() + } + self.visibleTiles.removeAll() + + let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width) + + var currentDetailsItems: [InstantPageDetailsItem] = [] + var currentLayoutItemsWithViews: [InstantPageItem] = [] + var distanceThresholdGroupCount: [Int : Int] = [:] + + var expandedDetails: [Int : Bool] = [:] + + var detailsIndex = -1 + for item in self.currentLayout.items { + if item.wantsView { + currentLayoutItemsWithViews.append(item) + if let group = item.distanceThresholdGroup() { + let count: Int + if let currentCount = distanceThresholdGroupCount[Int(group)] { + count = currentCount + } else { + count = 0 + } + distanceThresholdGroupCount[Int(group)] = count + 1 + } + if let detailsItem = item as? InstantPageDetailsItem { + detailsIndex += 1 + expandedDetails[detailsIndex] = detailsItem.initiallyExpanded + currentDetailsItems.append(detailsItem) + } + } + } + + if self.currentExpandedDetails == nil { + self.currentExpandedDetails = expandedDetails + } + + self.currentLayoutTiles = currentLayoutTiles + self.currentLayoutItemsWithViews = currentLayoutItemsWithViews + self.currentDetailsItems = currentDetailsItems + self.distanceThresholdGroupCount = distanceThresholdGroupCount + } + + var effectiveContentSize: CGSize { + var contentSize = self.contentSize + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + contentSize.height += -item.frame.height + (expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight) + } + return contentSize + } + + func isExpandedItem(_ item: InstantPageDetailsItem) -> Bool { + if let index = self.currentDetailsItems.firstIndex(where: {$0 === item}) { + return self.currentExpandedDetails?[index] ?? item.initiallyExpanded + } else { + return false + } + } + + func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) { + var visibleTileIndices = Set() + var visibleItemIndices = Set() + + self.previousVisibleBounds = visibleBounds + + CATransaction.begin() + defer { + CATransaction.commit() + } + + var topView: View? + let topTileView = topView + for view in self.subviews.reversed() { + if let view = view as? InstantPageTileView { + topView = view + break + } + } + + var collapseOffset: CGFloat = 0.0 + + var itemIndex = -1 + var embedIndex = -1 + var detailsIndex = -1 + + + for item in self.currentLayoutItemsWithViews { + itemIndex += 1 + if item is InstantPageWebEmbedItem { + embedIndex += 1 + } + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + + var itemThreshold: CGFloat = 0.0 + if let group = item.distanceThresholdGroup() { + var count: Int = 0 + if let currentCount = self.distanceThresholdGroupCount[group] { + count = currentCount + } + itemThreshold = item.distanceThresholdWithGroupCount(count) + } + + var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset) + var thresholdedItemFrame = itemFrame + thresholdedItemFrame.origin.y -= itemThreshold + thresholdedItemFrame.size.height += itemThreshold * 2.0 + + if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] { + let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight + collapseOffset += itemFrame.height - height + itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height)) + } + + if visibleBounds.intersects(thresholdedItemFrame) { + visibleItemIndices.insert(itemIndex) + + var itemView = self.visibleItemsWithViews[itemIndex] + if let currentItemView = itemView { + if !item.matchesView(currentItemView) { + (currentItemView as! NSView).removeFromSuperview() + self.visibleItemsWithViews.removeValue(forKey: itemIndex) + itemView = nil + } + } + + if itemView == nil { + let itemIndex = itemIndex + let detailsIndex = detailsIndex + + let arguments = InstantPageItemArguments(context: self.arguments.context, theme: self.arguments.theme, openMedia: self.arguments.openMedia, openPeer: self.arguments.openPeer, openUrl: self.arguments.openUrl, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { [weak self] expanded in + self?.updateDetailsExpanded(detailsIndex, expanded) + }, isExpandedItem: { [weak self] item in + return self?.isExpandedItem(item) ?? false + }, effectiveRectForItem: { [weak self] item in + return self?.effectiveFrameForItem(item) ?? item.frame + }) + + if let newView = item.view(arguments: arguments, currentExpandedDetails: self.currentExpandedDetails) { + newView.frame = itemFrame + self.addSubview(newView) + topView = newView as? View + self.visibleItemsWithViews[itemIndex] = newView + itemView = newView + + if let itemView = itemView as? InstantPageDetailsView { + itemView.requestLayoutUpdate = { [weak self] animated in + self?.requestLayoutUpdate?(animated) + } + } + } + } else { + if (itemView as! NSView).frame != itemFrame { + (itemView as! NSView)._change(size: itemFrame.size, animated: animated) + (itemView as! NSView)._change(pos: itemFrame.origin, animated: animated) + } else { + (itemView as! NSView).needsDisplay = true + } + } + + if let itemView = itemView as? InstantPageDetailsView { + itemView.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemView.frame.minX, dy: -itemView.frame.minY), animated: animated) + } + } + } + + topView = topTileView + + var tileIndex = -1 + for tile in self.currentLayoutTiles { + tileIndex += 1 + + let tileFrame = effectiveFrameForTile(tile) + var tileVisibleFrame = tileFrame + tileVisibleFrame.origin.y -= 400.0 + tileVisibleFrame.size.height += 400.0 * 2.0 + if tileVisibleFrame.intersects(visibleBounds) { + visibleTileIndices.insert(tileIndex) + + if self.visibleTiles[tileIndex] == nil { + let tileView = InstantPageTileView(tile: tile, backgroundColor: .clear) + tileView.frame = tileFrame + self.addSubview(tileView) + topView = tileView + self.visibleTiles[tileIndex] = tileView + } else { + if visibleTiles[tileIndex]!.frame != tileFrame { + + let view = self.visibleTiles[tileIndex]! + view._change(pos: tileFrame.origin, animated: animated) + view._change(size: tileFrame.size, animated: animated) + } + } + } + } + + var removeTileIndices: [Int] = [] + for (index, tileView) in self.visibleTiles { + if !visibleTileIndices.contains(index) { + removeTileIndices.append(index) + tileView.removeFromSuperview() + } + } + for index in removeTileIndices { + self.visibleTiles.removeValue(forKey: index) + } + + var removeItemIndices: [Int] = [] + for (index, itemView) in self.visibleItemsWithViews { + if !visibleItemIndices.contains(index) { + removeItemIndices.append(index) + (itemView as! NSView).removeFromSuperview() + } else { + var itemFrame = (itemView as! NSView).frame + let itemThreshold: CGFloat = 200.0 + itemFrame.origin.y -= itemThreshold + itemFrame.size.height += itemThreshold * 2.0 + itemView.updateIsVisible(visibleBounds.intersects(itemFrame)) + } + } + for index in removeItemIndices { + self.visibleItemsWithViews.removeValue(forKey: index) + } + + let subviews = self.subviews.sorted(by: {$0.frame.minY < $1.frame.minY}) + self.subviews = subviews + self.needsLayout = true + } + + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { + // let currentHeight = self.currentWebEmbedHeights[index] + // if height != currentHeight { + // if let currentHeight = currentHeight, currentHeight > height { + // return + // } + // self.currentWebEmbedHeights[index] = height + // + // let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + // self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in + // if let strongSelf = self { + // strongSelf.updateLayout() + // strongSelf.updateVisibleItems() + // } + // })) + // } + } + + func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) { + if var currentExpandedDetails = self.currentExpandedDetails { + currentExpandedDetails[index] = expanded + self.currentExpandedDetails = currentExpandedDetails + } + self.requestLayoutUpdate?(animated) + } + + + + func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint { + var contentOffset = CGPoint() + for (_, itemView) in self.visibleItemsWithViews { + if let itemView = itemView as? InstantPageScrollableView, itemView.item === item { + contentOffset = itemView.contentOffset + break + } + } + return contentOffset + } + + func viewForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsView? { + for (_, itemView) in self.visibleItemsWithViews { + if let detailsView = itemView as? InstantPageDetailsView, detailsView.item === item { + return detailsView + } + } + return nil + } + + private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize { + if let view = viewForDetailsItem(item) { + return CGSize(width: item.frame.width, height: view.effectiveContentSize.height + item.titleHeight) + } else { + return item.frame.size + } + } + + private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect { + let layoutOrigin = tile.frame.origin + var origin = layoutOrigin + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + if layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + return CGRect(origin: origin, size: tile.frame.size) + } + + func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect { + let layoutOrigin = item.frame.origin + var origin = layoutOrigin + + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + if layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + + if let item = item as? InstantPageDetailsItem { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height)) + } else { + return CGRect(origin: origin, size: item.frame.size) + } + } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { + for item in self.currentLayout.items { + let itemFrame = self.effectiveFrameForItem(item) + if itemFrame.contains(location) { + if let item = item as? InstantPageTextItem, item.selectable { + return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY)) + } else if let item = item as? InstantPageScrollableItem { + let contentOffset = scrollableContentOffset(item: item) + if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) { + return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y)) + } + } else if let item = item as? InstantPageDetailsItem { + for (_, itemView) in self.visibleItemsWithViews { + if let itemView = itemView as? InstantPageDetailsView, itemView.item === item { + if let (textItem, parentOffset) = itemView.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) { + return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y)) + } + } + } + } + } + } + return nil + } + +} diff --git a/Telegram-Mac/InstantPageDetailsItem.swift b/Telegram-Mac/InstantPageDetailsItem.swift new file mode 100644 index 0000000000..877c1bcb01 --- /dev/null +++ b/Telegram-Mac/InstantPageDetailsItem.swift @@ -0,0 +1,435 @@ +// +// InstantPageDetailsItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit + +final class InstantPageDetailsItem: InstantPageItem { + var hasLinks: Bool = false + + var isInteractive: Bool = false + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + var frame: CGRect + let wantsView: Bool = true + let separatesTiles: Bool = true + let medias: [InstantPageMedia] = [] + + let titleItems: [InstantPageItem] + let titleHeight: CGFloat + let items: [InstantPageItem] + let safeInset: CGFloat + let rtl: Bool + let initiallyExpanded: Bool + let index: Int + + var isExpanded: Bool { + return self.arguments?.isExpandedItem(self) ?? initiallyExpanded + } + + var effectiveRect: NSRect { + return self.arguments?.effectiveRectForItem(self) ?? frame + } + + private var arguments: InstantPageItemArguments? + + init(frame: CGRect, titleItems: [InstantPageItem], titleHeight: CGFloat, items: [InstantPageItem], safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) { + self.frame = frame + self.titleItems = titleItems + self.titleHeight = titleHeight + self.items = items + self.safeInset = safeInset + self.rtl = rtl + self.initiallyExpanded = initiallyExpanded + self.index = index + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + var expanded: Bool? + self.arguments = arguments + if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] { + expanded = currentlyExpanded + } + return InstantPageDetailsView(arguments: arguments, item: self, currentlyExpanded: expanded) + } + + private func itemsIn( _ rect: NSRect, items: [InstantPageItem] = []) -> [InstantPageItem] { + var items: [InstantPageItem] = items + for (_, item) in self.items.enumerated() { + if item.frame.intersects(rect) { + if let item = item as? InstantPageTableItem { + return item.itemsIn(rect, items: items) + } else if let item = item as? InstantPageDetailsItem { + var rect = rect + rect.origin.y = rect.minY - item.effectiveRect.minY - titleHeight + return item.itemsIn(rect, items: items) + } else { + items.append(item) + } + } + + } + return items + } + func itemsIn( _ rect: NSRect) -> [InstantPageItem] { + return self.itemsIn(rect.offsetBy(dx: 0, dy: -titleHeight), items: []) + } + + func deepRect(_ rect: NSRect) -> NSRect { + for (_, item) in self.items.enumerated() { + if item.frame.intersects(rect) { + if let item = item as? InstantPageDetailsItem { + var rect = rect + let result = rect.minY - item.effectiveRect.minY - titleHeight + rect.origin.y = result + if result > 0 { + + return item.deepRect(rect) + + } else { + var bp:Int = 0 + bp += 1 + } + } + } + + } + return rect + } + + func deepItemsInRect(_ rect: NSRect, items: [InstantPageItem] = []) -> [InstantPageItem] { + for (_, item) in self.items.enumerated() { + if item.frame.intersects(rect) { + if let item = item as? InstantPageDetailsItem { + var rect = rect + rect.origin.y = rect.minY - item.effectiveRect.minY - titleHeight + return item.deepItemsInRect(rect, items: item.items) + } + } + + } + return items + } + + func deepItemsInRect(_ rect: NSRect) -> [InstantPageItem] { + return deepItemsInRect(rect, items: self.items) + } + + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesView(_ node: InstantPageView) -> Bool { + if let node = node as? InstantPageDetailsView { + return self === node.item + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 8 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return CGFloat.greatestFiniteMagnitude + } + + func drawInTile(context: CGContext) { + } + + +} + +func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) -> InstantPageDetailsItem { + let detailsInset: CGFloat = 17.0 + safeInset + let titleInset: CGFloat = 22.0 + + let (_, titleItems, titleSize) = layoutTextItemWithString(title, boundingWidth: boundingWidth - detailsInset * 2.0 - titleInset, offset: CGPoint(x: detailsInset + titleInset, y: 0.0)) + let titleHeight = max(44.0, titleSize.height + 26.0) + var offset: CGFloat? + for var item in titleItems { + var itemOffset = floorToScreenPixels(System.backingScale, (titleHeight - item.frame.height) / 2.0) + if item is InstantPageTextItem { + offset = itemOffset + } else if let offset = offset { + itemOffset = offset + } + item.frame = item.frame.offsetBy(dx: 0.0, dy: itemOffset) + } + + return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + titleHeight), titleItems: titleItems, titleHeight: titleHeight, items: items, safeInset: safeInset, rtl: rtl, initiallyExpanded: initiallyExpanded, index: index) +} + + + + + + + + + + +private let detailsInset: CGFloat = 17.0 +private let titleInset: CGFloat = 22.0 + +final class InstantPageDetailsView: Control, InstantPageView { + private let arguments: InstantPageItemArguments + let item: InstantPageDetailsItem + + private let titleTile: InstantPageTile + private let titleTileView: InstantPageTileView + + private let highlightedBackgroundView: View + private let buttonView: Control + private let arrowView: InstantPageDetailsArrowView + let separatorView: View + let contentView: InstantPageContentView + + var expanded: Bool + + var previousView: InstantPageDetailsView? + + var requestLayoutUpdate: ((Bool) -> Void)? + + init(arguments: InstantPageItemArguments, item: InstantPageDetailsItem, currentlyExpanded: Bool?) { + self.arguments = arguments + + self.item = item + + + let frame = item.frame + + self.highlightedBackgroundView = View() + self.highlightedBackgroundView.layer?.opacity = 0.0 + + + self.titleTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: frame.width, height: item.titleHeight)) + self.titleTile.items.append(contentsOf: item.titleItems) + self.titleTileView = InstantPageTileView(tile: self.titleTile, backgroundColor: .clear) + + if let expanded = currentlyExpanded { + self.expanded = expanded + } else { + self.expanded = item.initiallyExpanded + } + + self.arrowView = InstantPageDetailsArrowView(color: theme.colors.grayText, open: self.expanded) + self.separatorView = View() + separatorView.backgroundColor = theme.colors.border + self.buttonView = Control() + + self.contentView = InstantPageContentView(arguments: arguments, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight)) + + + + super.init() + + + self.addSubview(self.contentView) + self.addSubview(self.highlightedBackgroundView) + self.addSubview(self.titleTileView) + self.addSubview(self.arrowView) + self.addSubview(self.separatorView) + self.addSubview(self.buttonView) + + + + buttonView.set(handler: { [weak self] _ in + guard let `self` = self else {return} + arguments.updateDetailsExpanded(!self.expanded) + self.setExpanded(!self.expanded, animated: true) + }, for: .Click) + + self.contentView.requestLayoutUpdate = { [weak self] animated in + self?.requestLayoutUpdate?(animated) + } + self.setExpanded(self.expanded, animated: false) + + } + + + override var needsDisplay: Bool { + didSet { + for subview in self.contentView.subviews { + subview.needsDisplay = true + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + + func setExpanded(_ expanded: Bool, animated: Bool) { + self.expanded = expanded + self.arrowView.setOpen(expanded, animated: animated) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + } + + override func layout() { + super.layout() + + let size = self.bounds.size + let inset = detailsInset + self.item.safeInset + + self.titleTileView.frame = self.titleTile.frame + self.highlightedBackgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: self.item.titleHeight + .borderSize)) + self.buttonView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: self.item.titleHeight)) + self.arrowView.frame = CGRect(x: inset, y: floorToScreenPixels(backingScaleFactor, (self.item.titleHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0) + self.contentView.frame = CGRect(x: 0.0, y: self.item.titleHeight, width: size.width, height: self.item.frame.height - self.item.titleHeight) + + let lineSize = CGSize(width: self.frame.width - inset, height: .borderSize) + self.separatorView.frame = CGRect(origin: CGPoint(x: self.item.rtl ? 0.0 : inset, y: self.item.titleHeight - lineSize.height), size: lineSize) + } + + func updateIsVisible(_ isVisible: Bool) { + + } + + + func updateVisibleItems(visibleBounds: CGRect, animated: Bool) { + if self.bounds.height > self.item.titleHeight { + self.contentView.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -self.contentView.frame.minX, dy: -self.contentView.frame.minY), animated: animated) + } + } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { + if self.titleTileView.frame.contains(location) { + for case let item as InstantPageTextItem in self.item.titleItems { + if item.frame.contains(location) { + return (item, self.titleTileView.frame.origin) + } + } + } + else if let (textItem, parentOffset) = self.contentView.textItemAtLocation(location.offsetBy(dx: -self.contentView.frame.minX, dy: -self.contentView.frame.minY)) { + return (textItem, self.contentView.frame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y)) + } + return nil + } + + + var effectiveContentSize: CGSize { + return self.contentView.effectiveContentSize + } + + func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect { + return self.contentView.effectiveFrameForItem(item).offsetBy(dx: 0.0, dy: self.item.titleHeight) + } +} + + +final class InstantPageDetailsArrowView : View { + var color: NSColor { + didSet { + self.setNeedsDisplay() + } + } + private (set) var open: Bool + + private var progress: CGFloat = 0.0 + private var targetProgress: CGFloat? + private var timer: SwiftSignalKit.Timer? + + init(color: NSColor, open: Bool) { + self.color = color + self.open = open + self.progress = open ? 1.0 : 0.0 + + super.init() + + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + deinit { + timer?.invalidate() + } + + private func stopTimer() { + timer?.invalidate() + timer = nil + } + private func startTimer() { + if timer == nil { + timer = SwiftSignalKit.Timer(timeout: 0.016, repeat: true, completion: { [weak self] in + self?.displayLinkEvent() + }, queue: Queue.mainQueue()) + timer?.start() + } + } + + func setOpen(_ open: Bool, animated: Bool) { + self.open = open + let openProgress: CGFloat = open ? 1.0 : 0.0 + if animated { + self.targetProgress = openProgress + startTimer() + } else { + self.progress = openProgress + self.targetProgress = nil + stopTimer() + } + } + + + private func displayLinkEvent() { + if let targetProgress = self.targetProgress { + let sign = CGFloat(targetProgress - self.progress > 0 ? 1 : -1) + self.progress += 0.14 * sign + if sign > 0 && self.progress > targetProgress { + self.progress = 1.0 + self.targetProgress = nil + stopTimer() + //self.displayLink?.isPaused = true + } else if sign < 0 && self.progress < targetProgress { + self.progress = 0.0 + self.targetProgress = nil + stopTimer() + } + } + + self.setNeedsDisplay() + } + + + override func draw(_ layer: CALayer, in ctx: CGContext) { + ctx.setStrokeColor(color.cgColor) + ctx.setLineCap(.round) + ctx.setLineWidth(2.0) + + ctx.move(to: CGPoint(x: 1.0, y: 1.0 + 5.0 * progress)) + ctx.addLine(to: CGPoint(x: 6.0, y: 6.0 - 5.0 * progress)) + ctx.addLine(to: CGPoint(x: 11.0, y: 1.0 + 5.0 * progress)) + ctx.strokePath() + } + +} diff --git a/Telegram-Mac/InstantPageImageItem.swift b/Telegram-Mac/InstantPageImageItem.swift new file mode 100644 index 0000000000..4e078814af --- /dev/null +++ b/Telegram-Mac/InstantPageImageItem.swift @@ -0,0 +1,103 @@ +// +// InstantPageImageItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore + +protocol InstantPageImageAttribute { +} + +struct InstantPageMapAttribute: InstantPageImageAttribute { + let zoom: Int32 + let dimensions: CGSize +} + +final class InstantPageImageItem: InstantPageItem { + let hasLinks: Bool = false + let isInteractive: Bool + let separatesTiles: Bool = false + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + var frame: CGRect + + let webPage: TelegramMediaWebpage + + let media: InstantPageMedia + let attributes: [InstantPageImageAttribute] + + var medias: [InstantPageMedia] { + return [self.media] + } + + let roundCorners: Bool + let fit: Bool + + let wantsView: Bool = true + + init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], interactive: Bool, roundCorners: Bool, fit: Bool) { + self.frame = frame + self.webPage = webPage + self.media = media + self.isInteractive = interactive + self.attributes = attributes + self.roundCorners = roundCorners + self.fit = fit + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + + let viewArguments: InstantPageMediaArguments + if let _ = media.media as? TelegramMediaMap, let attribute = attributes.first as? InstantPageMapAttribute { + viewArguments = .map(attribute) + } else { + viewArguments = .image(interactive: self.isInteractive, roundCorners: self.roundCorners, fit: self.fit) + } + + return InstantPageMediaView(context: arguments.context, media: media, arguments: viewArguments) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func matchesView(_ node: InstantPageView) -> Bool { + if let node = node as? InstantPageMediaView { + return node.media == self.media + } else { + return false + } + } + + func distanceThresholdGroup() -> Int? { + return 1 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 400.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func drawInTile(context: CGContext) { + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } +} + + + + diff --git a/Telegram-Mac/InstantPageItem.swift b/Telegram-Mac/InstantPageItem.swift index 5035d63de8..1bc0c0f655 100644 --- a/Telegram-Mac/InstantPageItem.swift +++ b/Telegram-Mac/InstantPageItem.swift @@ -7,22 +7,50 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore +import Postbox + + +final class InstantPageItemArguments { + let context: AccountContext + let theme: InstantPageTheme + let openMedia:(InstantPageMedia)->Void + let openPeer:(PeerId) -> Void + let openUrl:(InstantPageUrlItem) -> Void + let updateWebEmbedHeight:(CGFloat) -> Void + let updateDetailsExpanded: (Bool) -> Void + let isExpandedItem: (InstantPageDetailsItem) -> Bool + let effectiveRectForItem: (InstantPageItem) -> NSRect + init(context: AccountContext, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, isExpandedItem: @escaping(InstantPageDetailsItem) -> Bool, effectiveRectForItem: @escaping(InstantPageItem) -> NSRect) { + self.context = context + self.theme = theme + self.openMedia = openMedia + self.openPeer = openPeer + self.openUrl = openUrl + self.updateWebEmbedHeight = updateWebEmbedHeight + self.updateDetailsExpanded = updateDetailsExpanded + self.isExpandedItem = isExpandedItem + self.effectiveRectForItem = effectiveRectForItem + } +} protocol InstantPageItem { var frame: CGRect { get set } var hasLinks: Bool { get } - var wantsNode: Bool { get } + var wantsView: Bool { get } var medias: [InstantPageMedia] { get } - + var separatesTiles: Bool { get } + var isInteractive: Bool { get } func matchesAnchor(_ anchor: String) -> Bool func drawInTile(context: CGContext) - func node(account: Account) -> InstantPageView? - func matchesNode(_ node: InstantPageView) -> Bool + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? + func matchesView(_ node: InstantPageView) -> Bool func linkSelectionViews() -> [InstantPageLinkSelectionView] func distanceThresholdGroup() -> Int? func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat } + diff --git a/Telegram-Mac/InstantPageLayout.swift b/Telegram-Mac/InstantPageLayout.swift index a4edbc4d7b..2778a70e68 100644 --- a/Telegram-Mac/InstantPageLayout.swift +++ b/Telegram-Mac/InstantPageLayout.swift @@ -7,9 +7,11 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit +import SyncCore final class InstantPageLayout { let origin: CGPoint @@ -28,273 +30,500 @@ final class InstantPageLayout { func flattenedItemsWithOrigin(_ origin: CGPoint) -> [InstantPageItem] { return self.items.map({ item in - var item = item - item.frame = item.frame.offsetBy(dx: origin.x, dy: origin.y) - return item + var _item = item + _item.frame = item.frame.offsetBy(dx: origin.x, dy: origin.y) + return _item }) } + + var deepMedias: [InstantPageMedia] { + var media:[InstantPageMedia] = [] + for item in items { + media.append(contentsOf: self.deepMedia(item, medias: [])) + } + return media + } + + private func deepMedia(_ item: InstantPageItem, medias: [InstantPageMedia]) -> [InstantPageMedia] { + var medias = medias + if item.isInteractive { + return medias + item.medias//.filter({ $0.media is TelegramMediaImage || $0.media is TelegramMediaFile }) + } else if let item = item as? InstantPageDetailsItem { + var mediaDetails:[InstantPageMedia] = medias + for item in item.items { + mediaDetails.append(contentsOf: deepMedia(item, medias: [])) + } + medias += mediaDetails + } + return medias + } } -func layoutInstantPageBlock(_ block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, isCover: Bool, fillToWidthAndHeight: Bool, horizontalInsetBetweenMaxWidth: CGFloat, presentation: InstantViewAppearance, media: [MediaId: Media], mediaIndexCounter: inout Int, overlay: Bool, openChannel:@escaping(TelegramChannel)->Void, joinChannel:@escaping(TelegramChannel)->Void) -> InstantPageLayout { +private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, category: InstantPageTextCategoryType, link: Bool) { + let attributes = theme.textCategories.attributes(type: category, link: link) + 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)) + } + stack.push(.fontSize(attributes.font.size)) + stack.push(.lineSpacingFactor(attributes.font.lineSpacingFactor)) + if attributes.underline { + stack.push(.underline) + } +} +func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout { + + let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in + var items: [InstantPageItem] = [] + var offset = contentSize.height + var contentSize = CGSize() + var rtl = rtl + if case .empty = caption.text { + } else { + contentSize.height += 14.0 + 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) + contentSize.height += captionContentSize.height + offset += captionContentSize.height + items.append(contentsOf: captionItems) + + rtl = textItem?.containsRTL ?? rtl + } + + if case .empty = caption.credit { + } else { + if case .empty = caption.text { + contentSize.height += 14.0 + offset += 14.0 + } else { + contentSize.height += 10.0 + offset += 10.0 + } + 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) + contentSize.height += captionContentSize.height + offset += captionContentSize.height + items.append(contentsOf: captionItems) + } + if contentSize.height > 0.0 && isCover { + contentSize.height += 14.0 + } + return (items, contentSize) + } + + let stringForDate: (Int32) -> String = { date in + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: appAppearance.language.languageCode) + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .none + return dateFormatter.string(from: Date(timeIntervalSince1970: Double(date))) + } switch block { case let .cover(block): - return layoutInstantPageBlock(block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, isCover: true, fillToWidthAndHeight: fillToWidthAndHeight, horizontalInsetBetweenMaxWidth: horizontalInsetBetweenMaxWidth, presentation: presentation, media: media, mediaIndexCounter: &mediaIndexCounter, overlay: overlay, openChannel: openChannel, joinChannel: joinChannel) + return layoutInstantPageBlock(webpage: webpage, 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, webEmbedHeights: webEmbedHeights) case let .title(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(28.0)) - styleStack.push(.fontSerif(true)) - styleStack.push(.lineSpacingFactor(0.685)) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subtitle(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17.0)) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .authorDate(author: author, date: date): + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + 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 = _NSLocalizedString("InstantPage.AuthorAndDateTitle") + let authorRange = formatString.range(of: "%1$@") + let dateRange = formatString.range(of: "%2$@") + if let authorRange = authorRange, let dateRange = dateRange { + 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 + } + } + if let text = text { + var previousItemHasRTL = false + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + } else { + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) + } + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .header(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(24.0)) - styleStack.push(.fontSerif(true)) - styleStack.push(.lineSpacingFactor(0.685)) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subheader(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(19.0)) - styleStack.push(.fontSerif(true)) - styleStack.push(.lineSpacingFactor(0.685)) - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .paragraph(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .preformatted(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(16.0)) - styleStack.push(.fontFixed(true)) - - styleStack.push(.lineSpacingFactor(0.685)) + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) let backgroundInset: CGFloat = 14.0 - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: backgroundInset) - let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: item.frame.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: item.frame.height + backgroundInset * 2.0)), shape: .rect, color: theme.colors.grayBackground) - return InstantPageLayout(origin: CGPoint(), contentSize: backgroundItem.frame.size, items: [backgroundItem, item]) - case let .authorDate(author: author, date: date): + 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) + 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) + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0), items: allItems) + case let .footer(text): let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) + setupStyleStack(styleStack, theme: theme, category: .caption, 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) + 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]) + 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 hasNums = false + if ordered { + for item in contentItems { + if let num = item.num, !num.isEmpty { + hasNums = true + break + } + } } - styleStack.push(.textColor(theme.colors.grayText)) - var text: RichText? - if case .empty = author { - if date != 0 { - let dateStringPlain = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(date)), dateStyle: .long, timeStyle: .none) - text = RichText.plain(dateStringPlain) + for i in 0 ..< contentItems.count { + let item = contentItems[i] + if ordered { + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + let value: String + if hasNums { + if let num = item.num { + value = "\(num)." + } else { + value = " " + } + } else { + value = "\(i + 1)." + } + let (textItem, _, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint()) + if let textItem = textItem, let line = textItem.lines.first { + textItem.selectable = false + maxIndexWidth = max(maxIndexWidth, line.frame.width) + indexItems.append(textItem) + } + } else { + let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 6.0, height: 12.0)), shapeFrame: CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: CGSize(width: 6.0, height: 6.0)), shape: .ellipse, color: theme.textCategories.paragraph.color) + indexItems.append(shapeItem) } - } else { - let dateStringPlain = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(date)), dateStyle: .long, timeStyle: .none) - let dateText = RichText.plain(dateStringPlain) + } + let indexSpacing: CGFloat = ordered ? 12.0 : 20.0 + for (i, item) in contentItems.enumerated() { + if (i != 0) { + contentSize.height += 18.0 + } + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - if date != 0 { - let formatString = _NSLocalizedString("InstantPage.AuthorAndDateTitle") - let authorRange = formatString.range(of: "%1$@")! - let dateRange = formatString.range(of: "%2$@")! + var effectiveItem = item + if case let .blocks(blocks, num) = effectiveItem, blocks.isEmpty { + effectiveItem = .text(.plain(" "), num) + } + 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) - if authorRange.lowerBound < dateRange.lowerBound { - let byPart = formatString.substring(to: authorRange.lowerBound) - let middlePart = formatString.substring(with: authorRange.upperBound ..< dateRange.lowerBound) - let endPart = formatString.substring(from: dateRange.upperBound) - - text = .concat([.plain(byPart), author, .plain(middlePart), dateText, .plain(endPart)]) + contentSize.height += textItemSize.height + var indexItem = indexItems[i] + var itemFrame = indexItem.frame + + var lineMidY: CGFloat = 0.0 + if let textItem = textItem { + if let line = textItem.lines.first { + lineMidY = textItem.frame.minY + line.frame.midY + } else { + lineMidY = textItem.frame.midY + } + } + + if let textIndexItem = indexItem as? InstantPageTextItem, let line = textIndexItem.lines.first { + itemFrame = itemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - line.frame.width, dy: floorToScreenPixels(System.backingScale, lineMidY - (itemFrame.height / 2.0))) } else { - let beforePart = formatString.substring(to: dateRange.lowerBound) - let middlePart = formatString.substring(with: dateRange.upperBound ..< authorRange.lowerBound) - let endPart = formatString.substring(from: authorRange.upperBound) + itemFrame = itemFrame.offsetBy(dx: horizontalInset, dy: floorToScreenPixels(System.backingScale, lineMidY - itemFrame.height / 2.0)) + } + indexItems[i].frame = itemFrame + listItems.append(indexItems[i]) + listItems.append(contentsOf: textItems) + case let .blocks(blocks, _): + var previousBlock: InstantPageBlock? + var originY: CGFloat = contentSize.height + for subBlock in blocks { + let subLayout = layoutInstantPageBlock(webpage: webpage, 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, webEmbedHeights: webEmbedHeights) - text = .concat([.plain(beforePart), dateText, .plain(middlePart), author, .plain(endPart)]) + let spacing: CGFloat = previousBlock != nil && subLayout.contentSize.height > 0.0 ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0 + let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing)) + if previousBlock == nil { + originY += spacing + } + listItems.append(contentsOf: blockItems) + contentSize.height += subLayout.contentSize.height + spacing + previousBlock = subBlock } - } else { - text = author + var indexItem = indexItems[i] + 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 { + indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset, dy: originY) + } + indexItems[i].frame = indexItemFrame + listItems.append(indexItems[i]) + break + + default: + break } } - if let text = text { - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: listItems) + case let .blockQuote(text, 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) + + if case .empty = caption { } else { - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) + 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) + + 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 + 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 (textItem, 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) + if let textItem = textItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: contentSize.height) } - case let .image(id, caption): + + contentSize.height += textContentSize.height + items.append(contentsOf: textItems) + + if case .empty = caption { + } else { + contentSize.height += 14.0 + + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + + let (textItem, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: .center, offset: CGPoint(x: 0.0, y: contentSize.height), media: media, webpage: webpage) + if let textItem = textItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: contentSize.height) + } + + contentSize.height += captionContentSize.height + items.append(contentsOf: captionItems) + } + contentSize.height += verticalInset + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .image(id, caption, url, webpageId): if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { - let imageSize = largest.dimensions - var filledSize = imageSize.fit(CGSize(width: boundingWidth, height: 600.0)) + let imageSize = largest.dimensions.size + var filledSize = imageSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) - if fillToWidthAndHeight { - filledSize = CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: boundingWidth - horizontalInsetBetweenMaxWidth * 2); + if let size = fillToSize { + filledSize = size } else if isCover { - filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: 1.0)) - - if filledSize.height > .ulpOfOne { - let maxSize = CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: floor((boundingWidth - horizontalInsetBetweenMaxWidth * 2) * 3.0 / 5.0)) - filledSize = CGSize(width: min(filledSize.width, maxSize.width), height: min(filledSize.height, maxSize.height)) + filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) + if !filledSize.height.isZero { + filledSize = filledSize.cropped(CGSize(width: boundingWidth - safeInset * 2.0, height: floor((boundingWidth - safeInset * 2.0) * 3.0 / 5.0))) } } let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 - var items:[InstantPageItem] = [] - - var contentSize: NSSize = NSMakeSize(boundingWidth, 0.0) + var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) + var items: [InstantPageItem] = [] - contentSize.height += filledSize.height + var mediaUrl: InstantPageUrlItem? + if let url = url { + mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) + } - let mediaItem = InstantPageMediaItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), media: InstantPageMedia(index: mediaIndex, media: image, caption: richPlainText(caption)), arguments: InstantPageMediaArguments.image(interactive: true, roundCorners: false, fit: false)) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, webpage: webpage, url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) + contentSize.height += filledSize.height - var hasCaption: Bool = true - if case .empty = caption { - hasCaption = false - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height - if hasCaption { - contentSize.height += 10 - - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(15.0)) - styleStack.push(.textColor(theme.colors.grayText)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.alignment = .center - if filledSize.width > boundingWidth - .ulpOfOne { - captionItem.frame = captionItem.frame.offsetBy(dx: horizontalInset, dy: contentSize.height) - } else { - captionItem.frame = captionItem.frame.offsetBy(dx: floorToScreenPixels((boundingWidth - captionItem.frame.size.width) / 2.0), dy: contentSize.height) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + } else { + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) + } + case let .video(id, caption, autoplay, loop): + if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions?.size { + let imageSize = dimensions + var filledSize = imageSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) + + if let size = fillToSize { + filledSize = size + } else if isCover { + filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) + if !filledSize.height.isZero { + filledSize = filledSize.cropped(CGSize(width: boundingWidth - safeInset * 2.0, height: floor((boundingWidth - safeInset * 2.0) * 3.0 / 5.0))) } - contentSize.height += captionItem.frame.size.height; - items.append(captionItem) } + + let mediaIndex = mediaIndexCounter + mediaIndexCounter += 1 + + var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) + var items: [InstantPageItem] = [] + + let mediaItem = InstantPageMediaItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), media: InstantPageMedia(index: mediaIndex, media: file, webpage: webpage, url: nil, caption: caption.text, credit: caption.credit), arguments: InstantPageMediaArguments.video(interactive: true, autoplay: autoplay)) + items.append(mediaItem) - + contentSize.height += filledSize.height + + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } else { return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } - case let .video(id, caption, autoplay, loop): + case let .collage(blocks, caption): - if let video = media[id] as? TelegramMediaFile { - let imageSize = video.dimensions ?? CGSize() - if imageSize.width > .ulpOfOne && imageSize.height > .ulpOfOne { - var filledSize = imageSize.fit(CGSize(width: boundingWidth, height: 600.0)) - if fillToWidthAndHeight { - filledSize = CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: boundingWidth - horizontalInsetBetweenMaxWidth * 2) - } else if isCover { - filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: 1)) - if filledSize.height > .ulpOfOne { - let maxSize = CGSize(width: boundingWidth - horizontalInsetBetweenMaxWidth * 2, height: floor((boundingWidth - horizontalInsetBetweenMaxWidth * 2) * 3.0 / 5.0)) - filledSize = CGSize(width: min(filledSize.width, maxSize.width), height: min(filledSize.height, maxSize.height)) - } - } - - var items:[InstantPageItem] = [] - - let mediaIndex = mediaIndexCounter - mediaIndexCounter += 1 - - var contentSize = CGSize(width: boundingWidth, height: 0) - - contentSize.height += filledSize.height - - let mediaItem = InstantPageMediaItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), media: InstantPageMedia(index: mediaIndex, media: video, caption: richPlainText(caption)), arguments: InstantPageMediaArguments.video(interactive: true, autoplay: autoplay)) - - items.append(mediaItem) - - var hasCaption: Bool = true - if case .empty = caption { - hasCaption = false - } - - if hasCaption { - contentSize.height += 10 - - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - styleStack.push(.textColor(theme.colors.grayText)) - - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.alignment = .center - if filledSize.width > boundingWidth - .ulpOfOne { - captionItem.frame = captionItem.frame.offsetBy(dx: horizontalInset, dy: contentSize.height) - } else { - captionItem.frame = captionItem.frame.offsetBy(dx: floorToScreenPixels((boundingWidth - captionItem.frame.size.width) / 2.0), dy: contentSize.height) - } - contentSize.height += captionItem.frame.size.height; - items.append(captionItem) - } - - - return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - + let spacing: CGFloat = 2 + let itemsPerRow: CGFloat = min(round(boundingWidth / 150), CGFloat(blocks.count)) + let itemSize: CGFloat = floorToScreenPixels(System.backingScale, (boundingWidth - spacing * max(0, itemsPerRow - 1)) / itemsPerRow) + + var items:[InstantPageItem] = [] + var nextItemOrigin: CGPoint = CGPoint() + + var i = 0 + for subItem in blocks { + if nextItemOrigin.x + itemSize > boundingWidth { + nextItemOrigin.x = 0.0 + nextItemOrigin.y += itemSize + spacing } + let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subItem, boundingWidth: itemSize, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: NSMakeSize(itemSize, itemSize), media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, webEmbedHeights: webEmbedHeights) + items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin)) + nextItemOrigin.x += itemSize + spacing; + + i += 1 } - case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId): - var embedBoundingWidth = boundingWidth - horizontalInset * 2.0 - if stretchToWidth { - embedBoundingWidth = boundingWidth - } - let size: CGSize - if dimensions.width.isLessThanOrEqualTo(0.0) { - size = CGSize(width: embedBoundingWidth, height: dimensions.height) - } else { - size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth)) - } - let item = InstantPageWebEmbedItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size), url: url, html: html, enableScrolling: allowScrolling) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) - case let .postEmbed(_, _, avatarId, author, date, blocks, caption): - var contentSize = NSMakeSize(boundingWidth, 0.0) + + var contentSize = CGSize(width: boundingWidth, height: nextItemOrigin.y + itemSize) + + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height + + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .postEmbed(url, webpageId, avatarId, author, date, blocks, caption): + var contentSize = CGSize(width: boundingWidth, height: 0.0) let lineInset: CGFloat = 20.0 - let verticalInset:CGFloat = 4.0 - let itemSpacing:CGFloat = 10.0 - var avatarInset:CGFloat = 0.0 - var avatarVerticalInset:CGFloat = 0.0 + let verticalInset: CGFloat = 4.0 + let itemSpacing: CGFloat = 10.0 + var avatarInset: CGFloat = 0.0 + var avatarVerticalInset: CGFloat = 0.0 contentSize.height += verticalInset - var items:[InstantPageItem] = [] - - if author.length > 0 { - let avatar: TelegramMediaImage? - if let avatarId = avatarId { - avatar = media[avatarId] as? TelegramMediaImage - } else { - avatar = nil - } + var items: [InstantPageItem] = [] + + if !author.isEmpty { + let avatar: TelegramMediaImage? = avatarId.flatMap { media[$0] as? TelegramMediaImage } if let avatar = avatar { - let avatarItem = InstantPageMediaItem(frame: NSMakeRect(horizontalInset + lineInset + 1.0, contentSize.height - 2.0, 50.0, 50.0), media: InstantPageMedia.init(index: -1, media: avatar, caption: richPlainText(caption)), arguments: .image(interactive: false, roundCorners: true, fit: false)) - + let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: avatar, webpage: webpage, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) items.append(avatarItem) + avatarInset += 62.0 avatarVerticalInset += 6.0 if date == 0 { @@ -303,313 +532,405 @@ func layoutInstantPageBlock(_ block: InstantPageBlock, boundingWidth: CGFloat, h } let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.bold) - styleStack.push(.textColor(theme.colors.text)) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain(author), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset + avatarInset, dy: contentSize.height + avatarVerticalInset) + 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) + items.append(contentsOf: textItems) - contentSize.height += textItem.frame.size.height + avatarVerticalInset - items.append(textItem) + contentSize.height += textContentSize.height + avatarVerticalInset } - if date != 0 { - if !items.isEmpty { + if items.count != 0 { contentSize.height += itemSpacing } - let dateString = DateFormatter.localizedString(from: Date(timeIntervalSince1970: TimeInterval(date)), dateStyle: .long, timeStyle: .none) - + + let dateString = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(date)), dateStyle: .long, timeStyle: .none) + let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain(dateString), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset + avatarInset, dy: contentSize.height) - items.append(textItem) + let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(dateString), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset, offset: CGPoint(x: horizontalInset + lineInset + avatarInset, y: contentSize.height), media: media, webpage: webpage) + items.append(contentsOf: textItems) + + contentSize.height += textContentSize.height } - if !items.isEmpty { - contentSize.height += itemSpacing; + if items.count != 0 { + contentSize.height += itemSpacing } - var previous: InstantPageBlock? = nil - for sub in blocks { - let subLayout = layoutInstantPageBlock(sub, boundingWidth: boundingWidth - horizontalInset * 2 - lineInset, horizontalInset: 0, isCover: false, fillToWidthAndHeight: false, horizontalInsetBetweenMaxWidth: horizontalInsetBetweenMaxWidth, presentation: presentation, media: media, mediaIndexCounter: &mediaIndexCounter, overlay: overlay, openChannel: openChannel, joinChannel: joinChannel) - let spacing = spacingBetweenBlocks(upper: previous, lower: sub) - let subItems = subLayout.flattenedItemsWithOrigin(NSMakePoint(horizontalInset + lineInset, contentSize.height + spacing)) - items.append(contentsOf: subItems) + var previousBlock: InstantPageBlock? + for subBlock in blocks { + let subLayout = layoutInstantPageBlock(webpage: webpage, 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, webEmbedHeights: webEmbedHeights) + + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) + let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + lineInset, y: contentSize.height + spacing)) + items.append(contentsOf: blockItems) contentSize.height += subLayout.contentSize.height + spacing - previous = sub + previousBlock = subBlock } - contentSize.height += verticalInset; + contentSize.height += verticalInset - items.append(InstantPageShapeItem(frame: NSMakeRect(horizontalInset, 0.0, 3.0, contentSize.height), shapeFrame: NSMakeRect(0.0, 0.0, 3.0, contentSize.height), shape: .roundLine, color: theme.colors.text)) + items.append(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)) - var hasCaption: Bool = true - if case .empty = caption { - hasCaption = false - } + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height - if hasCaption { - contentSize.height += 14.0 - - let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: horizontalInset, dy: contentSize.height) - contentSize.height += captionItem.frame.size.height - items.append(captionItem) - } return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case let .collage(blocks, _): + case let .slideshow(items: subItems, caption: caption): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] - let spacing: CGFloat = 2 - let itemsPerRow: CGFloat = min(round(boundingWidth / 150), CGFloat(blocks.count)) - let itemSize: CGFloat = floorToScreenPixels((boundingWidth - spacing * max(0, itemsPerRow - 1)) / itemsPerRow) + var itemMedias: [InstantPageMedia] = [] - var items:[InstantPageItem] = [] - var nextItemOrigin: CGPoint = CGPoint() - for subBlock in blocks { - if nextItemOrigin.x + itemSize > boundingWidth { - nextItemOrigin.x = 0.0 - nextItemOrigin.y += itemSize + spacing + for subBlock in subItems { + switch subBlock { + case let .image(id, caption, url, webpageId): + if let image = media[id] as? TelegramMediaImage, let imageSize = largestImageRepresentation(image.representations)?.dimensions.size { + let mediaIndex = mediaIndexCounter + mediaIndexCounter += 1 + + let filledSize = imageSize.fitted(CGSize(width: boundingWidth, height: 1200.0)) + contentSize.height = max(contentSize.height, filledSize.height) + + var mediaUrl: InstantPageUrlItem? + if let url = url { + mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) + } + itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, webpage: webpage, url: mediaUrl, caption: caption.text, credit: caption.credit)) + } + break + default: + break } - let subLayout = layoutInstantPageBlock(subBlock, boundingWidth: itemSize, horizontalInset: 0, isCover: false, fillToWidthAndHeight: true, horizontalInsetBetweenMaxWidth: horizontalInsetBetweenMaxWidth, presentation: presentation, media: media, mediaIndexCounter: &mediaIndexCounter, overlay: overlay, openChannel: openChannel, joinChannel: joinChannel) - items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin)) - nextItemOrigin.x += itemSize + spacing; } + items.append(InstantPageSlideshowItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height)), webPage: webpage, medias: itemMedias)) - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: nextItemOrigin.y + itemSize), items: items) - case .anchor(let anchor): - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: [InstantPageAnchorItem(frame: CGRect(), anchor: anchor)]) - case .channelBanner(let channel): - if let channel = channel { - let width = boundingWidth - horizontalInsetBetweenMaxWidth * 2 - let item = InstantPageChannelItem(frame: CGRect(x: floorToScreenPixels((boundingWidth - width)/2), y: 0, width: width , height: InstantPageChannelView.height), channel: channel, overlay: overlay, openChannel: openChannel, joinChannel: joinChannel) - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: item.frame.height), items: [item]) - } - case .footer(let text): - let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - let item = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) - return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) - case .divider: - let lineWidth = floorToScreenPixels(boundingWidth / 2) - let shapeItem = InstantPageShapeItem(frame: CGRect(x: floorToScreenPixels((boundingWidth - lineWidth) / 2.0), y: 0.0, width: lineWidth, height: 1.0), shapeFrame: CGRect(x: 0, y: 0, width: lineWidth, height: 1.0), shape: .rect, color: theme.colors.grayText) - return InstantPageLayout(origin: CGPoint(), contentSize: shapeItem.frame.size, items: [shapeItem]) - case let .list(items, ordered): + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height - var contentSize = CGSize(width: boundingWidth, height: 0) - var maxIndexWidth:CGFloat = 0.0 - var listItems:[InstantPageItem] = [] - var indexItems:[InstantPageItem] = [] - for i in 0 ..< items.count { - if ordered { - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - styleStack.push(.textColor(theme.colors.text)) - let textItem = layoutTextItemWithString(attributedStringForRichText(.plain("\(i + 1)."), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2) - textItem.frame.size.width = textItem.lines.first!.frame.width - maxIndexWidth = max(textItem.frame.width, maxIndexWidth); - indexItems.append(textItem) - } else { - let shapeItem = InstantPageShapeItem(frame: CGRect(x: 0.0, y: 0.0, width: 6.0, height: 12.0), shapeFrame: CGRect(x: 0.0, y: 3.0, width: 6.0, height: 6.0), shape: .ellipse, color: theme.colors.text) - indexItems.append(shapeItem) - } - } - var index: Int = -1 - let indexSpacing: CGFloat = ordered ? 7.0 : 20.0 - for text in items { - index += 1 - if index != 0 { - contentSize.height += 20.0 - } - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - styleStack.push(.textColor(theme.colors.text)) - - let textItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2 - indexSpacing - maxIndexWidth) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + indexSpacing + maxIndexWidth, dy: contentSize.height) - contentSize.height += textItem.frame.size.height - - var indexItem = indexItems[index] - indexItem.frame = indexItem.frame.offsetBy(dx: horizontalInset, dy: textItem.frame.minY) - - listItems.append(indexItem) - listItems.append(textItem) - + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .table(title, rows, bordered, striped): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] + + var styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + let backgroundInset: CGFloat = 0.0 + let (textItem, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, alignment: .center, offset: CGPoint(), media: media, webpage: webpage) + if let textItem = textItem { + textItem.frame = textItem.frame.offsetBy(dx: floor(boundingWidth - textItem.frame.width) / 2.0, dy: 0.0) } - return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: listItems) - case let .blockQuote(text, caption): - let lineInset: CGFloat = 20.0 - let verticalInset: CGFloat = 4 - var contentSize = CGSize(width: boundingWidth, height: verticalInset) - var items:[InstantPageItem] = [] + items.append(contentsOf: textItems) + contentSize.height += textContentSize.height + 10.0 - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17)) - styleStack.push(.fontSerif(true)) - styleStack.push(.italic) - styleStack.push(.textColor(theme.colors.text)) + styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .table, link: false) + let tableBoundingWidth = boundingWidth - horizontalInset * 2.0 + let tableItem = layoutTableItem(rtl: rtl, rows: rows, styleStack: styleStack, theme: theme, bordered: bordered, striped: striped, boundingWidth: tableBoundingWidth, horizontalInset: horizontalInset, media: media, webpage: webpage) + tableItem.frame = tableItem.frame.offsetBy(dx: 0.0, dy: contentSize.height) - let textItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2 - lineInset) - textItem.frame = textItem.frame.offsetBy(dx: horizontalInset + lineInset, dy: contentSize.height) + contentSize.height += tableItem.frame.height + items.append(tableItem) - contentSize.height += textItem.frame.size.height - items.append(textItem) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .details(title, blocks, expanded): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var subitems: [InstantPageItem] = [] + let detailsIndex = detailsIndexCounter + detailsIndexCounter += 1 - var hasCaption: Bool = true - if case .empty = caption { - hasCaption = false + var subDetailsIndex = 0 + + var previousBlock: InstantPageBlock? + for subBlock in blocks { + + let subLayout = layoutInstantPageBlock(webpage: webpage, 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, webEmbedHeights: webEmbedHeights) + + + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) + let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) + subitems.append(contentsOf: blockItems) + contentSize.height += subLayout.contentSize.height + spacing + previousBlock = subBlock } - if hasCaption { - contentSize.height += 14.0 + if !blocks.isEmpty { + let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) + contentSize.height += closingSpacing + } + + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + styleStack.push(.lineSpacingFactor(0.685)) + let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, safeInset: safeInset, rtl: rtl, initiallyExpanded: expanded, index: detailsIndex) + return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem]) + case let .relatedArticles(title, articles): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] + + let styleStack = InstantPageTextStyleStack() + 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) + 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: appAppearance.presentation.colors.grayBackground) + items.append(backgroundItem) + items.append(contentsOf: textItems) + contentSize.height += backgroundItem.frame.height + + for (i, article) in articles.enumerated() { + var cover: TelegramMediaImage? + if let coverId = article.photoId { + cover = media[coverId] as? TelegramMediaImage + } - let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) + var styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .article, link: false) + let title = attributedStringForRichText(.plain(article.title ?? ""), styleStack: styleStack) + + styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .caption, link: false) + + var subtext: String? + if article.author != nil || article.date != nil { + if let author = article.author { + if let date = article.date { + subtext = L10n.instantPageRelatedArticleAuthorAndDateTitle(author, stringForDate(date)) + } else { + subtext = author + } + } else if let date = article.date { + subtext = stringForDate(date) + } + } else { + subtext = article.description + } + let description = attributedStringForRichText(.plain(subtext ?? ""), styleStack: styleStack) + + let item = layoutArticleItem(theme: theme, webPage: webpage, title: title, description: description, cover: cover, url: article.url, webpageId: article.webpageId, boundingWidth: boundingWidth, rtl: rtl) + item.frame = item.frame.offsetBy(dx: 0.0, dy: contentSize.height) + 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: .borderSize) + 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): + let imageSize = dimensions.size + var filledSize = imageSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) + + if let size = fillToSize { + filledSize = size + } else if isCover { + filledSize = imageSize.aspectFilled(CGSize(width: boundingWidth - safeInset * 2.0, height: 1.0)) + if !filledSize.height.isZero { + filledSize = filledSize.cropped(CGSize(width: boundingWidth - safeInset * 2.0, height: floor((boundingWidth - safeInset * 2.0) * 3.0 / 5.0))) } - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: horizontalInset + lineInset, dy: contentSize.height) - contentSize.height += captionItem.frame.size.height - items.append(captionItem) } - contentSize.height += verticalInset - items.append(InstantPageShapeItem(frame: CGRect(x: horizontalInset, y: 0, width: 3.0, height: contentSize.height), shapeFrame: CGRect(x: 0.0, y: 0.0, width: 3.0, height: contentSize.height), shape: .roundLine, color: theme.colors.text)) + let map = TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil) + let attributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: dimensions.size)] + var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) + var items: [InstantPageItem] = [] + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: map, webpage: webpage, url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) - return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case let .pullQuote(text, caption): + items.append(mediaItem) + contentSize.height += filledSize.height - let verticalInset: CGFloat = 4.0 - var contentSize = CGSize(width: boundingWidth, height: verticalInset) - var items:[InstantPageItem] = [] + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height - let styleStack = InstantPageTextStyleStack() - styleStack.push(.fontSize(17)) - styleStack.push(.fontSerif(true)) - styleStack.push(.italic) - styleStack.push(.textColor(theme.colors.text)) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId): + var embedBoundingWidth = boundingWidth - horizontalInset * 2.0 + if stretchToWidth { + embedBoundingWidth = boundingWidth + } - let textItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2) - textItem.frame = textItem.frame.offsetBy(dx: floorToScreenPixels((boundingWidth - textItem.frame.size.width) / 2.0), dy: contentSize.height) - textItem.alignment = .center + let embedIndex = embedIndexCounter + embedIndexCounter += 1 - contentSize.height += textItem.frame.size.height - items.append(textItem) + let size: CGSize + if let dimensions = dimensions?.size { + if dimensions.width.isLessThanOrEqualTo(0.0) { + size = CGSize(width: embedBoundingWidth, height: dimensions.height) + } else { + size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth)) + } + } else { + if let height = webEmbedHeights[embedIndex] { + size = CGSize(width: embedBoundingWidth, height: CGFloat(height)) + } else { + size = CGSize(width: embedBoundingWidth, height: 44.0) + } + } - if true { - contentSize.height += 14.0 - - let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) + var items: [InstantPageItem] = [] + var contentSize: CGSize + let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) + let item: InstantPageItem + if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage { + var url = url + if url.lowercased().contains("youtube"), url.lowercased().contains("embed/") { + url = url.replacingOccurrences(of: "embed/", with: "watch?v=") } - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floorToScreenPixels((boundingWidth - captionItem.frame.size.width) / 2.0), dy: contentSize.height) - captionItem.alignment = .center - contentSize.height += captionItem.frame.size.height - items.append(captionItem) + let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, attributes: [], instantPage: nil) + let content = TelegramMediaWebpageContent.Loaded(loadedContent) + + item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), webpage: webpage, url: nil, caption: nil, credit: nil), attributes: [], interactive: false, roundCorners: false, fit: false) + + } else { + item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling) } + items.append(item) + contentSize = item.frame.size - contentSize.height += verticalInset - return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case let .audio(id, caption): - break - case let .slideshow(blocks, caption): + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height - var medias:[InstantPageMedia] = [] + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .channelBanner(peer): var contentSize = CGSize(width: boundingWidth, height: 0.0) - var items:[InstantPageItem] = [] + var items: [InstantPageItem] = [] - for subBlock in blocks { - switch subBlock { - case let .image(id, _): - if let photo = media[id] as? TelegramMediaImage, let imageSize = largestImageRepresentation(photo.representations)?.dimensions { - let mediaIndex = mediaIndexCounter - mediaIndexCounter += 1 - let filledSize = imageSize.fit(CGSize(width: boundingWidth, height: 600)) - contentSize.height = min(max(contentSize.height, filledSize.height), boundingWidth) - medias.append(InstantPageMedia(index: mediaIndex, media: photo, caption: richPlainText(caption))) - } - default: - break + var offset: CGFloat = 0.0 + + var previousItemHasRTL = false + if let previousItem = previousItems.last as? InstantPageTextItem { + if previousItem.containsRTL { + previousItemHasRTL = true + } + var minY = previousItem.frame.minY + if let firstItem = previousItems.first { + minY = firstItem.frame.maxY } + offset = minY - previousItem.frame.maxY + } + if !offset.isZero { + offset -= 40.0 + 14.0 } - items.append(InstantPageSlideshowItem(frame: CGRect(x: 0, y: 0, width: boundingWidth, height: contentSize.height), medias: medias)) - - var hasCaption: Bool = true - if case .empty = caption { - hasCaption = false + if let peer = peer { + let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL) + items.append(item) + if offset.isZero { + contentSize.height += 40.0 + } } + return InstantPageLayout(origin: CGPoint(x: 0.0, y: offset), contentSize: contentSize, items: items) + case let .anchor(name): + let item = InstantPageAnchorItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 0.0)), anchor: name) + return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item]) + case let .audio(audioId, caption): + var contentSize = CGSize(width: boundingWidth, height: 0.0) + var items: [InstantPageItem] = [] - if hasCaption { - contentSize.height += 14.0 + if let file = media[audioId] as? TelegramMediaFile { + let mediaIndex = mediaIndexCounter + mediaIndexCounter += 1 + let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: file, webpage: webpage, url: nil, caption: nil, credit: nil)) - let styleStack = InstantPageTextStyleStack() - styleStack.push(.textColor(theme.colors.grayText)) - styleStack.push(.fontSize(15.0)) - if presentation.fontSerif { - styleStack.push(.fontSerif(true)) - } - let captionItem = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0) - captionItem.frame = captionItem.frame.offsetBy(dx: floorToScreenPixels((boundingWidth - captionItem.frame.size.width) / 2.0), dy: contentSize.height) - captionItem.alignment = .center - contentSize.height += captionItem.frame.size.height - items.append(captionItem) + contentSize.height += item.frame.height + items.append(item) + let (captionItems, captionSize) = layoutCaption(caption, contentSize) + items.append(contentsOf: captionItems) + contentSize.height += captionSize.height } - return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case .unsupported: - break + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + default: + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } - return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } -func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, presentation: InstantViewAppearance, openChannel:@escaping(TelegramChannel)->Void, joinChannel:@escaping(TelegramChannel)->Void) -> InstantPageLayout { + +func instantPageMedias(for webpage: TelegramMediaWebpage) -> [InstantPageMedia] { + + switch webpage.content { + case let .Loaded(content): + var index: Int = 0 + var detailsIndex: Int = 0 + if let instantPage = content.instantPage { + return instantPageMedias(for: instantPage.blocks, webpage: webpage, medias: instantPage.media, mediaIndexCounter: &index, detailsIndexCounter: &detailsIndex) + } else { + return [] + } + default: + return [] + } +} + +private func instantPageMedias(for blocks: [InstantPageBlock], webpage: TelegramMediaWebpage, medias: [MediaId : Media], mediaIndexCounter: inout Int, detailsIndexCounter: inout Int) -> [InstantPageMedia] { + var current: [InstantPageMedia] = [] + for block in blocks { + switch block { + case let .audio(id, _): + if let media = medias[id] { + current.append(InstantPageMedia(index: mediaIndexCounter, media: media, webpage: webpage, url: nil, caption: nil, credit: nil)) + mediaIndexCounter += 1 + } + case let .collage(blocks, _), let .slideshow(blocks, _): + current.append(contentsOf: instantPageMedias(for: blocks, webpage: webpage, medias: medias, mediaIndexCounter: &mediaIndexCounter, detailsIndexCounter: &detailsIndexCounter)) + case .details(_, blocks, _): + current.append(contentsOf: instantPageMedias(for: blocks, webpage: webpage, medias: medias, mediaIndexCounter: &mediaIndexCounter, detailsIndexCounter: &detailsIndexCounter)) + detailsIndexCounter += 1 + case let .image(id, _, _, _): + if let media = medias[id] { + current.append(InstantPageMedia(index: mediaIndexCounter, media: media, webpage: webpage, url: nil, caption: nil, credit: nil)) + mediaIndexCounter += 1 + } + case let .video(id, _, _, _): + if let media = medias[id] { + current.append(InstantPageMedia(index: mediaIndexCounter, media: media, webpage: webpage, url: nil, caption: nil, credit: nil)) + mediaIndexCounter += 1 + } +// case let .webEmbed(url, _, size, _, _, _, coverId): +// if let url = url, let coverId = coverId, let image = medias[coverId] as? TelegramMediaImage { +// var url = url +// if url.lowercased().contains("youtube"), url.lowercased().contains("embed/") { +// url = url.replacingOccurrences(of: "embed/", with: "watch?v=") +// } +// 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, image: image, file: nil, instantPage: nil) +// let content = TelegramMediaWebpageContent.Loaded(loadedContent) +// +// current.append(InstantPageMedia(index: mediaIndexCounter, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), webpage: webpage, url: nil, caption: nil, credit: nil)) +// mediaIndexCounter += 1 +// } + default: + break + } + } + return current +} + + +func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, safeInset: CGFloat, theme: InstantPageTheme, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout { var maybeLoadedContent: TelegramMediaWebpageLoadedContent? if case let .Loaded(content) = webPage.content { maybeLoadedContent = content } + guard let loadedContent = maybeLoadedContent, let instantPage = loadedContent.instantPage else { return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } + let rtl = instantPage.rtl let pageBlocks = instantPage.blocks var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] @@ -620,33 +941,27 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: } var mediaIndexCounter: Int = 0 + var embedIndexCounter: Int = 0 + var detailsIndexCounter: Int = 0 var previousBlock: InstantPageBlock? - var previousLayout:InstantPageLayout? for block in pageBlocks { - var spacingBetween = spacingBetweenBlocks(upper: previousBlock, lower: block) - - if (spacingBetween < -.ulpOfOne) { - spacingBetween -= (previousLayout?.contentSize.height ?? 0) - (previousLayout?.items.first?.frame.height ?? 0); - } - - let horizontalInsetBetweenMaxWidth = max(0, (boundingWidth - 720)/2) - - let blockLayout = layoutInstantPageBlock(block, boundingWidth: boundingWidth, horizontalInset: 40 + horizontalInsetBetweenMaxWidth, isCover: false, fillToWidthAndHeight: false, horizontalInsetBetweenMaxWidth: horizontalInsetBetweenMaxWidth, presentation: presentation, media: media, mediaIndexCounter: &mediaIndexCounter, overlay: spacingBetween < -.ulpOfOne, openChannel: openChannel, joinChannel: joinChannel) - - let spacing = blockLayout.contentSize.height > .ulpOfOne ? spacingBetween : 0.0 - + let blockLayout = layoutInstantPageBlock(webpage: webPage, 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, webEmbedHeights: webEmbedHeights) + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block) 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) { - contentSize.height += spacing > -.ulpOfOne ? blockLayout.contentSize.height + spacing : 0.0 + contentSize.height += blockLayout.contentSize.height + spacing previousBlock = block - previousLayout = blockLayout } } let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) contentSize.height += closingSpacing +// 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) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } diff --git a/Telegram-Mac/InstantPageLayoutSpacings.swift b/Telegram-Mac/InstantPageLayoutSpacings.swift index a26e8a71d1..d531d24e9a 100644 --- a/Telegram-Mac/InstantPageLayoutSpacings.swift +++ b/Telegram-Mac/InstantPageLayoutSpacings.swift @@ -7,46 +7,24 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat { if let upper = upper, let lower = lower { switch (upper, lower) { - case (_, .cover): + case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil), (_, .anchor): return 0.0 - case (.cover(let block), .channelBanner): - var hasCaption: Bool = true - switch block { - case let .image(_, caption: caption): - if case .empty = caption { - hasCaption = false - } - case let .video(_, caption, _, _): - if case .empty = caption { - hasCaption = false - } - case let .slideshow(_, caption): - if case .empty = caption { - hasCaption = false - } - default: - hasCaption = false - } - - return hasCaption ? -40 : 0 - case (.divider, _), (_, .divider): return 25.0 case (_, .blockQuote), (.blockQuote, _), (_, .pullQuote), (.pullQuote, _): return 27.0 + case (.kicker, .title), (.cover, .title): + return 16.0 case (_, .title): return 20.0 - case (.title, .subtitle): - return 20.0 - case (.title, .authorDate): + case (.title, .authorDate), (.subtitle, .authorDate): return 18.0 - case (.subtitle, .authorDate): - return 20 case (_, .authorDate): return 20.0 case (.title, .paragraph), (.authorDate, .paragraph): @@ -67,29 +45,29 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> return 31.0 case (.preformatted, .list): return 19.0 - case (.paragraph, .list), (.list, .list): - return 31 case (_, .list): - return 20.0 + return 25.0 case (.paragraph, .preformatted): return 19.0 case (_, .preformatted): return 20.0 - case (_, .header): - return 32.0 - case (_, .subheader): + case (_, .header), (_, .subheader): return 32.0 default: return 20.0 } } else if let lower = lower { switch lower { - case .cover, .channelBanner: + case .cover, .channelBanner, .details, .anchor: return 0.0 default: - return 24.0 + return 25.0 } } else { - return 24.0 + if let upper = upper, case .relatedArticles = upper { + return 0.0 + } else { + return 25.0 + } } } diff --git a/Telegram-Mac/InstantPageMedia.swift b/Telegram-Mac/InstantPageMedia.swift index 39ad86a2ef..4a4418fc25 100644 --- a/Telegram-Mac/InstantPageMedia.swift +++ b/Telegram-Mac/InstantPageMedia.swift @@ -7,19 +7,31 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + + struct InstantPageMedia: Equatable, Identifiable { let index: Int let media: Media - let caption: String? - + let webpage:TelegramMediaWebpage + let url: InstantPageUrlItem? + let caption: RichText? + let credit: RichText? + var stableId: Int { return index } + func withUpdatedIndex(_ index: Int) -> InstantPageMedia { + return InstantPageMedia(index: index, media: self.media, webpage: webpage, url: self.url, caption: self.caption, credit: self.credit) + } + + static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { - return lhs.index == rhs.index && lhs.media.isEqual(rhs.media) && lhs.caption == rhs.caption + return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit } } diff --git a/Telegram-Mac/InstantPageMediaItem.swift b/Telegram-Mac/InstantPageMediaItem.swift index 4abedc6dff..8edcacead0 100644 --- a/Telegram-Mac/InstantPageMediaItem.swift +++ b/Telegram-Mac/InstantPageMediaItem.swift @@ -8,18 +8,21 @@ import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore enum InstantPageMediaArguments { case image(interactive: Bool, roundCorners: Bool, fit: Bool) case video(interactive: Bool, autoplay: Bool) - + case map(InstantPageMapAttribute) var isInteractive: Bool { switch self { case .image(let interactive, _, _): return interactive case .video(let interactive, _): return interactive + case .map: + return false } } } @@ -38,24 +41,25 @@ final class InstantPageMediaItem: InstantPageItem { let arguments: InstantPageMediaArguments - let wantsNode: Bool = true + let wantsView: Bool = true let hasLinks: Bool = false - + let separatesTiles: Bool = false + init(frame: CGRect, media: InstantPageMedia, arguments: InstantPageMediaArguments) { self.frame = frame self.media = media self.arguments = arguments } - func node(account: Account) -> InstantPageView? { - return InstantPageMediaView(account: account, media: self.media, arguments: self.arguments) + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return InstantPageMediaView(context: arguments.context, media: self.media, arguments: self.arguments) } func matchesAnchor(_ anchor: String) -> Bool { return false } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { if let node = node as? InstantPageMediaView { return node.media == self.media } else { diff --git a/Telegram-Mac/InstantPageMediaView.swift b/Telegram-Mac/InstantPageMediaView.swift index 5ae955e9c8..2e43754522 100644 --- a/Telegram-Mac/InstantPageMediaView.swift +++ b/Telegram-Mac/InstantPageMediaView.swift @@ -8,24 +8,26 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import SyncCore final class InstantPageMediaView: View, InstantPageView { - private let account: Account + private let context: AccountContext let media: InstantPageMedia private let arguments: InstantPageMediaArguments - + private var iconView:ImageView? + private let imageView: TransformImageView - private let progressView = RadialProgressView() + private let progressView = RadialProgressView(theme:RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) private var currentSize: CGSize? private let fetchedDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() private let playerDisposable = MetaDisposable() private let videoDataDisposable = MetaDisposable() - private var videoPath:String? { + private var videoData:AVGifData? { didSet { updatePlayerIfNeeded() } @@ -33,12 +35,12 @@ final class InstantPageMediaView: View, InstantPageView { @objc private func updatePlayerIfNeeded() { - var s:Signal = .single(Void()) + var s:Signal = .single(Void()) s = s |> delay(0.01, queue: Queue.mainQueue()) playerDisposable.set(s.start(next: { [weak self] in if let strongSelf = self { let accept = strongSelf.window != nil && strongSelf.window!.isKeyWindow - (strongSelf.imageView as? GIFPlayerView)?.set(path: accept ? strongSelf.videoPath : nil) + (strongSelf.imageView as? GIFPlayerView)?.set(data: accept ? strongSelf.videoData : nil) } })) } @@ -59,8 +61,8 @@ final class InstantPageMediaView: View, InstantPageView { updatePlayerListenters() } - init(account: Account, media: InstantPageMedia, arguments: InstantPageMediaArguments) { - self.account = account + init(context: AccountContext, media: InstantPageMedia, arguments: InstantPageMediaArguments) { + self.context = context self.media = media self.arguments = arguments @@ -73,6 +75,8 @@ final class InstantPageMediaView: View, InstantPageView { } else { self.imageView = TransformImageView() } + case .map: + self.imageView = TransformImageView() } @@ -80,59 +84,111 @@ final class InstantPageMediaView: View, InstantPageView { progressView.isHidden = true - self.imageView.alphaTransitionOnFirstUpdate = true + self.imageView.animatesAlphaOnFirstTransition = true self.addSubview(self.imageView) addSubview(progressView) + + let updateProgressState:(MediaResourceStatus)->Void = { [weak self] status in + guard let `self` = self else {return} + + self.progressView.fetchControls = FetchControls(fetch: { [weak self] in + guard let `self` = self else {return} + + switch status { + case .Remote: + if let image = media.media as? TelegramMediaImage { + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(media.webpage), media: image)).start()) + } else if let file = media.media as? TelegramMediaFile { + self.fetchedDisposable.set(freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.webPage(webPage: WebpageReference(media.webpage), media: file)).start()) + } + case .Fetching: + if let image = media.media as? TelegramMediaImage { + chatMessagePhotoCancelInteractiveFetch(account: context.account, photo: image) + } else if let file = media.media as? TelegramMediaFile { + cancelFreeMediaFileInteractiveFetch(context: context, resource: file.resource) + } + default: + break + } + }) + switch status { case let .Fetching(_, progress): - self?.progressView.isHidden = false - self?.progressView.state = .Fetching(progress: progress, force: false) + self.progressView.isHidden = false + self.progressView.state = .Fetching(progress: progress, force: false) case .Local: - self?.progressView.isHidden = true - self?.progressView.state = .None + self.progressView.isHidden = media.media is TelegramMediaImage || self.imageView is GIFPlayerView + self.progressView.state = media.media is TelegramMediaImage || self.imageView is GIFPlayerView ? .None : .Play case .Remote: - self?.progressView.state = .Remote + self.progressView.state = .Remote } } if let image = media.media as? TelegramMediaImage { - let imageSize = largestImageRepresentation(image.representations)?.dimensions ?? NSZeroSize - imageView.setSignal(signal: cachedMedia(media: image, size: imageSize, scale: backingScaleFactor)) - self.imageView.setSignal(account: account, signal: chatMessagePhoto(account: account, photo: image, scale: backingScaleFactor), cacheImage: { [weak self] img in - if let strongSelf = self { - return cacheMedia(signal: img, media: image, size: imageSize, scale: strongSelf.backingScaleFactor) - } else { - return .complete() - } - }) - self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photo: image).start()) + self.imageView.setSignal( chatMessagePhoto(account: context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(media.webpage), media: image), scale: backingScaleFactor)) + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(media.webpage), media: image)).start()) if let largest = largestImageRepresentation(image.representations) { - statusDisposable.set((account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start()) + if arguments.isInteractive { + statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: updateProgressState)) + } } } else if let file = media.media as? TelegramMediaFile { - statusDisposable.set((account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: updateProgressState)) - self.fetchedDisposable.set(account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .video)).start()) + if arguments.isInteractive { + statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: updateProgressState)) + } + self.fetchedDisposable.set(freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.webPage(webPage: WebpageReference(media.webpage), media: file)).start()) - self.imageView.setSignal(account: account, signal: chatMessageVideo(account: account, video: file, scale: backingScaleFactor)) + if file.mimeType.hasPrefix("image/") && !file.mimeType.hasSuffix("gif") { + self.imageView.setSignal(instantPageImageFile(account: context.account, fileReference: .webPage(webPage: WebpageReference(media.webpage), media: file), scale: backingScaleFactor, fetched: true)) + } else { + self.imageView.setSignal(chatMessageVideo(postbox: context.account.postbox, fileReference: .webPage(webPage: WebpageReference(media.webpage), media: file), scale: backingScaleFactor)) + } switch arguments { case let .video(_, autoplay): if autoplay { - videoDataDisposable.set((account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue).start(next: { [weak self] data in - if data.complete { - self?.videoPath = data.path - } else { - self?.videoPath = nil - } + videoDataDisposable.set((context.account.postbox.mediaBox.resourceData(file.resource) |> deliverOnResourceQueue |> map { data in return data.complete ? AVGifData.dataFrom(data.path) : nil} |> deliverOnMainQueue).start(next: { [weak self] data in + self?.videoData = data })) } default: break } + } else if let map = media.media as? TelegramMediaMap { + + let iconView = ImageView() + iconView.image = theme.icons.chatMapPin + iconView.sizeToFit() + addSubview(iconView) + + self.iconView = iconView + + var zoom: Int32 = 12 + var dimensions = CGSize(width: 200.0, height: 100.0) + switch arguments { + case let .map(attribute): + zoom = attribute.zoom + dimensions = attribute.dimensions + default: + break + } + + let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height), zoom: zoom) + + let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimensions), resource: resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(media.webpage), media: image) + let signal = chatWebpageSnippetPhoto(account: context.account, imageReference: imageReference, scale: backingScaleFactor, small: false) + self.imageView.setSignal(signal) + } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image { + let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) + let signal = chatWebpageSnippetPhoto(account: context.account, imageReference: imageReference, scale: backingScaleFactor, small: false) + self.imageView.setSignal(signal) + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: imageReference).start()) + statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(image.representations.last!.resource) |> deliverOnMainQueue).start(next: updateProgressState)) } } @@ -166,13 +222,15 @@ final class InstantPageMediaView: View, InstantPageView { let size = self.bounds.size + iconView?.center() + if self.currentSize != size { self.currentSize = size self.imageView.frame = CGRect(origin: CGPoint(), size: size) if let image = self.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { - var imageSize = largest.dimensions.aspectFilled(size) + var imageSize = largest.dimensions.size.aspectFilled(size) var boundingSize = size var radius: CGFloat = 0.0 @@ -181,10 +239,12 @@ final class InstantPageMediaView: View, InstantPageView { radius = roundCorners ? floor(min(size.width, size.height) / 2.0) : 0.0 if fit { - imageSize = largest.dimensions.fitted(size) + imageSize = largest.dimensions.size.fitted(size) boundingSize = imageSize; } + default: + break } @@ -192,11 +252,45 @@ final class InstantPageMediaView: View, InstantPageView { imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets())) } else if let file = self.media.media as? TelegramMediaFile { - let imageSize = file.dimensions?.aspectFilled(size) ?? size + let imageSize = file.dimensions?.size.aspectFilled(size) ?? size let boundingSize = size imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets())) + } else if let _ = self.media.media as? TelegramMediaMap { + var imageSize = size + + var boundingSize = size + switch arguments { + case let .map(attribute): + boundingSize = attribute.dimensions + imageSize = attribute.dimensions.aspectFilled(size) + default: + break + } + + + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets())) + } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) { + var imageSize = largest.dimensions.size.aspectFilled(size) + var boundingSize = size + var radius: CGFloat = 0.0 + + switch arguments { + case let .image(_, roundCorners, fit): + radius = roundCorners ? floor(min(size.width, size.height) / 2.0) : 0.0 + + if fit { + imageSize = largest.dimensions.size.fitted(size) + boundingSize = imageSize; + } + + default: + + break + } + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets())) } + } progressView.center() } diff --git a/Telegram-Mac/InstantPagePeerReferenceItem.swift b/Telegram-Mac/InstantPagePeerReferenceItem.swift new file mode 100644 index 0000000000..96cdde7955 --- /dev/null +++ b/Telegram-Mac/InstantPagePeerReferenceItem.swift @@ -0,0 +1,72 @@ +// +// InstantPagePeerReferenceItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore + +final class InstantPagePeerReferenceItem: InstantPageItem { + + + let hasLinks: Bool = false + let isInteractive: Bool = false + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + var frame: CGRect + let wantsView: Bool = true + let separatesTiles: Bool = false + let medias: [InstantPageMedia] = [] + + let initialPeer: Peer + let safeInset: CGFloat + let transparent: Bool + let rtl: Bool + + init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool) { + self.frame = frame + self.initialPeer = initialPeer + self.safeInset = safeInset + self.transparent = transparent + self.rtl = rtl + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return nil + } + + func matchesView(_ node: InstantPageView) -> Bool { + return false + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func distanceThresholdGroup() -> Int? { + return 5 + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + if count > 3 { + return 1000.0 + } else { + return CGFloat.greatestFiniteMagnitude + } + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func drawInTile(context: CGContext) { + } +} diff --git a/Telegram-Mac/InstantPageScrollableItem.swift b/Telegram-Mac/InstantPageScrollableItem.swift new file mode 100644 index 0000000000..16f8b93d90 --- /dev/null +++ b/Telegram-Mac/InstantPageScrollableItem.swift @@ -0,0 +1,147 @@ + +import Foundation +import TelegramCore +import SyncCore +import Postbox +import TGUIKit + +protocol InstantPageScrollableItem: class, InstantPageItem { + var contentSize: CGSize { get } + var horizontalInset: CGFloat { get } + var isRTL: Bool { get } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? +} + +private final class InstantPageScrollableContentViewParameters: NSObject { + let item: InstantPageScrollableItem + + init(item: InstantPageScrollableItem) { + self.item = item + super.init() + } +} + +final class InstantPageScrollableContentView: View { + let item: InstantPageScrollableItem + + init(item: InstantPageScrollableItem, additionalViews: [InstantPageView]) { + self.item = item + super.init() + for case let view as NSView in additionalViews { + self.addSubview(view) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + + override func draw(_ layer: CALayer, in ctx: CGContext) { + item.drawInTile(context: ctx) + } + +} + + + + +final class InstantPageScrollableView: ScrollView, InstantPageView { + let item: InstantPageScrollableItem + let contentNode: InstantPageScrollableContentView + let containerView: View = View() + + + override var hasVerticalScroller: Bool { + get { + return false + } + set { + super.hasVerticalScroller = newValue + } + } + + + override func scrollWheel(with event: NSEvent) { + + var scrollPoint = contentView.bounds.origin + let isInverted: Bool = System.isScrollInverted + + if event.scrollingDeltaX != 0 { + if !isInverted { + scrollPoint.x += -event.scrollingDeltaX + } else { + scrollPoint.x -= event.scrollingDeltaX + } + + scrollPoint.x = max(0, min(scrollPoint.x, (documentView!.frame.width) - contentSize.width)) + + clipView.scroll(to: scrollPoint) + + + } else { + superview?.enclosingScrollView?.scrollWheel(with: event) + } + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + // clipView.scroll(to: NSMakePoint(0, 0)) + } + + +// @objc var contentOffset: NSPoint { +// get { +// return NSZeroPoint +// } +// } + + init(item: InstantPageScrollableItem, arguments: InstantPageItemArguments, additionalViews: [InstantPageView]) { + self.item = item + self.contentNode = InstantPageScrollableContentView(item: item, additionalViews: additionalViews) + super.init(frame: NSZeroRect) + // wantsLayer = true + + containerView.frame = CGRect(origin: CGPoint(x: 0, y: 0.0), size: NSMakeSize(item.contentSize.width + item.horizontalInset * 2, item.contentSize.height)) + containerView.backgroundColor = .clear + self.contentNode.frame = CGRect(origin: CGPoint(x: item.horizontalInset, y: 0.0), size: item.contentSize) + + + containerView.addSubview(contentNode) + self.documentView = containerView + + if item.isRTL { + self.contentView.scroll(to: CGPoint(x: containerView.frame.width - item.frame.width, y: 0.0)) + // self.contentOffset = CGPoint(x: self.contentSize.width - item.frame.width, y: 0.0) + } + + } + + override var needsDisplay: Bool { + didSet { + contentNode.needsDisplay = true + } + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateIsVisible(_ isVisible: Bool) { + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + } + + + func updateHiddenMedia(media: InstantPageMedia?) { + } + + +} diff --git a/Telegram-Mac/InstantPageSelectText.swift b/Telegram-Mac/InstantPageSelectText.swift index 032fd50f00..a8e6b8e841 100644 --- a/Telegram-Mac/InstantPageSelectText.swift +++ b/Telegram-Mac/InstantPageSelectText.swift @@ -16,9 +16,11 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import AVFoundation struct InstantPageSelectContainer { let attributedString: NSAttributedString @@ -88,13 +90,28 @@ private let instantSelectManager:InstantPageSelectManager = { }() private class InstantViewContentInteractive : InteractionContentViewProtocol { + + private let callback:(AnyHashable)->NSView? init(_ callback:@escaping(AnyHashable)->NSView?) { self.callback = callback } - func contentInteractionView(for stableId: AnyHashable) -> NSView? { + func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { return callback(stableId) } + func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + + } + + public func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + + } + func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + return nil + } + public func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + + } } class InstantPageSelectText : NSObject { @@ -111,10 +128,45 @@ class InstantPageSelectText : NSObject { self.scroll = scroll } + private func deepItemsInRect(_ rect: NSRect, itemsInRect:@escaping(NSRect)->[InstantPageItem], effectiveRectForItem:@escaping(InstantPageItem)-> NSRect) -> [(InstantPageItem, InstantPageTableItem?, InstantPageDetailsItem?)] { + return itemsInRect(rect).reduce([], { (current, item) -> [(InstantPageItem, InstantPageTableItem?, InstantPageDetailsItem?)] in + var current = current + if let item = item as? InstantPageTableItem { + var itemRect = effectiveRectForItem(item) + let view = findView(item, self.scroll.documentView) as? InstantPageScrollableView + if let view = view { + itemRect.origin.x -= view.documentOffset.x - item.horizontalInset + } + current += item.itemsIn(NSMakeRect(rect.minX - itemRect.minX, rect.minY - itemRect.minY, 1, 1)).map {($0, Optional(item), nil)} + } else if let item = item as? InstantPageDetailsItem, item.isExpanded { + let itemRect = effectiveRectForItem(item) + current += item.itemsIn(NSMakeRect(rect.minX - itemRect.minX, rect.minY - itemRect.minY, 1, 1)).map {($0, nil, Optional(item))} + } else { + current += [(item, nil, nil)] + } + return current + }) + } - func initializeHandlers(for window:Window, instantLayout: InstantPageLayout, instantPage: InstantPage, account: Account, updateLayout: @escaping()->Void, openInfo:@escaping(PeerId, Bool, MessageId?, ChatInitialAction?)->Void, openNewTab:@escaping (MediaId, String)->Void) { + private func findView(_ item: InstantPageItem, _ currentView: NSView?) -> NSView? { + if let currentView = currentView { + for (_, subview) in currentView.subviews.enumerated() { + if let subview = subview as? InstantPageView, item.matchesView(subview) { + return subview as? NSView + } else if let subview = subview as? InstantPageDetailsView { + if let view = findView(item, subview.contentView) as? InstantPageView, item.matchesView(view) { + return view as? NSView + } + } + } + } + return nil + } + + func initializeHandlers(for window:Window, instantLayout: InstantPageLayout, instantPage: InstantPage, context: AccountContext, updateLayout: @escaping()->Void, openUrl:@escaping(InstantPageUrlItem) -> Void, itemsInRect:@escaping(NSRect) -> [InstantPageItem], effectiveRectForItem:@escaping(InstantPageItem)-> NSRect) { window.removeAllHandlers(for: self) + window.set(mouseHandler: { [weak self, weak window] event -> KeyHandlerResult in @@ -122,7 +174,7 @@ class InstantPageSelectText : NSObject { let isInDocument = self?.scroll.documentView?.isInnerView(window?.contentView?.hitTest(event.locationInWindow)) ?? false self?.started = false - window?.makeFirstResponder(nil) + _ = window?.makeFirstResponder(nil) if isInDocument { if let scroll = self?.scroll, let superview = scroll.superview, let documentView = scroll.documentView, let window = window { let point = superview.convert(window.mouseLocationOutsideOfEventStream, from: nil) @@ -167,118 +219,96 @@ class InstantPageSelectText : NSObject { let isInDocument = self?.scroll.documentView?.isInnerView(window?.contentView?.hitTest(event.locationInWindow)) ?? false - guard isInDocument else { + guard isInDocument, let `self` = self else { return .rejected } let result: KeyHandlerResult - self?.beginInnerLocation = NSZeroPoint + self.beginInnerLocation = NSZeroPoint - let point = self?.scroll.documentView?.convert(event.locationInWindow, from: nil) ?? NSZeroPoint + let point = self.scroll.documentView?.convert(event.locationInWindow, from: nil) ?? NSZeroPoint + _ = window?.makeFirstResponder(instantSelectManager) - let textItem = instantLayout.items(in: NSMakeRect(point.x, point.y, 1, 1)).filter({$0 is InstantPageTextItem}).map({$0 as! InstantPageTextItem}).first - - let item = instantLayout.items(in: NSMakeRect(point.x, point.y, 1, 1)).first - - window?.makeFirstResponder(instantSelectManager) - if event.clickCount == 2, let item = textItem { - - let itemsRect = NSMakeRect(max(point.x, 0), max(point.y, 0), 1, 1) - - - instantSelectManager.removeAll() - - for line in item.lines { - - var minX:CGFloat = item.frame.minX - switch item.alignment { - case .center: - minX += floorToScreenPixels((item.frame.width - line.frame.width) / 2) - default: - break - } - - let rect = NSMakeRect(item.frame.minX, itemsRect.minY < item.frame.minY ? 0 : itemsRect.minY - item.frame.minY, itemsRect.width ,itemsRect.minY < item.frame.minY ? min(itemsRect.maxY - item.frame.minY, item.frame.height) : itemsRect.minY < item.frame.minY ? min(item.frame.maxY - itemsRect.minY, item.frame.height) : itemsRect.height) - - - let beginX = point.x - minX - - if rect.intersects(line.frame) { - instantSelectManager.add(line: line, attributedString: line.selectWord(in: NSMakePoint(beginX, 0), boundingWidth: 1, alignment: item.alignment)) + let rect = NSMakeRect(point.x, point.y, 1.0, 1.0) + + let item = self.deepItemsInRect(rect, itemsInRect: itemsInRect, effectiveRectForItem: effectiveRectForItem).last + + if let item = item { + if let textItem = item.0 as? InstantPageTextItem { + let itemRect: NSRect + if let tableItem = item.1 { + let effectiveRect = effectiveRectForItem(item.1!) + let skipCells = tableItem.itemFrameSkipCells(textItem, effectiveRect: effectiveRect) + itemRect = rect.offsetBy(dx: -skipCells.minX, dy: -skipCells.minY) + } else if let detailsItem = item.2 { + let effectiveRect = detailsItem.effectiveRect + let r = NSMakeRect(rect.minX - effectiveRect.minX, rect.minY - effectiveRect.minY, 1, 1).offsetBy(dx: 0, dy: -detailsItem.titleHeight) + itemRect = detailsItem.deepRect(r).offsetBy(dx: -item.0.frame.minX, dy: -item.0.frame.minY) + } else { + let effectiveRect = effectiveRectForItem(textItem) + itemRect = rect.offsetBy(dx: -effectiveRect.minX, dy: -effectiveRect.minY) } - } - result = .rejected - } else if event.clickCount == 3, let textItem = textItem { - instantSelectManager.removeAll() - for line in textItem.lines { - instantSelectManager.add(line: line, attributedString: line.selectText(in: NSMakeRect(0, 0, line.frame.width, 1), boundingWidth: line.frame.width, alignment: textItem.alignment)) - } - result = .rejected - } else if event.clickCount == 1 { - if let item = textItem, instantSelectManager.isEmpty { - let p = NSMakePoint(point.x - item.frame.minX, point.y - item.frame.minY) - if let link = item.linkAt(point: p) { - - switch link { - case .email(_, let email): - execute(inapp: inAppLink.external(link: email, false)) - case let .url(_ , url, webpageId): + if event.clickCount == 1, instantSelectManager.isEmpty { + if let link = textItem.linkAt(point: itemRect.origin) { + openUrl(link) + result = .rejected + } else { + result = .invokeNext + } + } else if event.clickCount == 2, item.1 == nil { + instantSelectManager.removeAll() + for line in textItem.lines { - let url = url.nsstring - let anchorRange = url.range(of: "#") - var foundAnchor = false - if anchorRange.location != NSNotFound { - let anchor = url.substring(from: anchorRange.location + anchorRange.length) - if !anchor.isEmpty { - for item in instantLayout.items { - if item.matchesAnchor(anchor) { - self?.scroll.clipView.scroll(to: item.frame.origin, animated: true) - foundAnchor = true - break - } - } - } + var minX:CGFloat = item.0.frame.minX + switch textItem.alignment { + case .center: + minX += floorToScreenPixels(System.backingScale, (item.0.frame.width - line.frame.width) / 2) + default: + break } - if !foundAnchor { - if let mediaId = webpageId { - openNewTab(mediaId, url as String) - } else { - execute(inapp: inApp(for: url, account: account, openInfo: openInfo)) - } + let beginX = point.x - minX + if line.frame.intersects(itemRect.offsetBy(dx: -itemRect.minX, dy: 0)) { + instantSelectManager.add(line: line, attributedString: line.selectWord(in: NSMakePoint(beginX, 0), boundingWidth: textItem.frame.width, alignment: textItem.alignment, rect: itemRect)) } - - break - default: - break } + result = .rejected + } else if (event.clickCount == 3 && item.1 == nil) || (event.clickCount == 2 && item.1 != nil) { + instantSelectManager.removeAll() + for line in textItem.lines { + instantSelectManager.add(line: line, attributedString: line.selectText(in: NSMakeRect(0, 0, line.frame.width, 1), boundingWidth: textItem.frame.width, alignment: textItem.alignment)) + } + result = .rejected + } else { + result = .invokeNext } - result = .rejected - - } else if let item = item, instantSelectManager.isEmpty { - let items = instantLayout.items - if item.isInteractive { - let medias = items.filter({$0.isInteractive}).reduce([], { current, item -> [InstantPageMedia] in - var current = current - current.append(contentsOf: item.medias) - return current - }) + } else { + + + if item.0.isInteractive { + + let items:[InstantPageItem] + if let details = item.2 { + let itemRect = effectiveRectForItem(details) + items = details.deepItemsInRect(NSMakeRect(rect.minX - itemRect.minX, rect.minY - itemRect.minY, 1, 1).offsetBy(dx: 0, dy: -details.titleHeight)).filter({$0.isInteractive}) + } else { + items = instantLayout.items.filter({$0.isInteractive}) + } - self?.interactive = InstantViewContentInteractive({ stableId in - if let index = stableId.base as? Int { - for item in items { - if let _ = item.medias.filter({$0.index == index}).first { - if let subviews = self?.scroll.documentView?.subviews { - for subview in subviews { - if !NSIsEmptyRect(subview.visibleRect), let subview = subview as? InstantPageView, item.matchesNode(subview) { - return subview as? NSView - } - } - } + let medias:[InstantPageMedia] = instantLayout.deepMedias + let item = item.0 + + + + self.interactive = InstantViewContentInteractive({ [weak self] stableId in + if let index = stableId.base as? Int, let `self` = self { + if let media = medias.first(where: {$0.index == index}) { + if let item = items.first(where: {$0.medias.contains(media)}) { + return self.findView(item, self.scroll.documentView) } } } @@ -287,24 +317,73 @@ class InstantPageSelectText : NSObject { var index = medias.index(of: item.medias.first!)! - let view = self?.interactive?.contentInteractionView(for: AnyHashable(index)) + let view = self.interactive?.contentInteractionView(for: AnyHashable(index), animateIn: false) if let view = view as? InstantPageSlideshowView { index += view.indexOfDisplayedSlide } - showInstantViewGallery(account: account, medias: medias, firstIndex: index, self?.interactive) + if let file = medias[index].media as? TelegramMediaFile, file.isMusic || file.isVoice { + + if view?.hitTest(point) is RadialProgressView, let view = view as? InstantPageAudioView { + if view.controller != nil { + view.controller?.playOrPause() + } else { + let audio = APSingleResourceController(account: context.account, wrapper: view.wrapper, streamable: true) + view.controller = audio + audio.start() + } + } + return .invokeNext + } else if let map = medias[index].media as? TelegramMediaMap { + execute(inapp: inAppLink.external(link: "https://maps.google.com/maps?q=\(String(format:"%f", map.latitude)),\(String(format:"%f", map.longitude))", false)) + return .rejected + } + + if let v = view?.hitTest(point) as? RadialProgressView { + switch v.state { + case .Fetching: + return .invokeNext + case .Remote: + return .invokeNext + default: + break + } + } + + + showInstantViewGallery(context: context, medias: medias, firstIndex: index, self.interactive) + result = .rejected + } else { - result = .invokeNext + if let media = item.0.medias.first { + if let webpage = media.media as? TelegramMediaWebpage { + switch webpage.content { + case let .Loaded(content): + execute(inapp: inAppLink.external(link: content.url, false)) + result = .rejected + default: + result = .invokeNext + } + } else { + result = .invokeNext + } + } else { + result = .invokeNext + } } - } else { - result = .invokeNext } } else { result = .invokeNext } + + + + + + if result == .invokeNext { Queue.mainQueue().justDispatch(updateLayout) } else { @@ -319,28 +398,44 @@ class InstantPageSelectText : NSObject { let isInDocument = self?.scroll.documentView?.isInnerView(window?.contentView?.hitTest(event.locationInWindow)) ?? false - guard isInDocument else { + guard isInDocument, let `self` = self else { return .rejected } - let point = self?.scroll.documentView?.convert(event.locationInWindow, from: nil) ?? NSZeroPoint + let point = self.scroll.documentView?.convert(event.locationInWindow, from: nil) ?? NSZeroPoint + let rect = NSMakeRect(point.x, point.y, 1.0, 1.0) - let items = instantLayout.items(in: NSMakeRect(point.x, point.y, 1, 1)).filter({$0 is InstantPageTextItem}).map({$0 as! InstantPageTextItem}) - - if items.isEmpty { - NSCursor.arrow.set() - } else { - if let item = items.first { - let p = NSMakePoint(point.x - item.frame.minX, point.y - item.frame.minY) - if let _ = item.linkAt(point: p) { - NSCursor.pointingHand.set() + for item in self.deepItemsInRect(rect, itemsInRect: itemsInRect, effectiveRectForItem: effectiveRectForItem).reversed() { + if let textItem = item.0 as? InstantPageTextItem { + let itemRect: NSRect + if let tableItem = item.1 { + let effectiveRect = effectiveRectForItem(item.1!) + let skipCells = tableItem.itemFrameSkipCells(textItem, effectiveRect: effectiveRect) + itemRect = rect.offsetBy(dx: -skipCells.minX, dy: -skipCells.minY) + } else if let detailsItem = item.2 { + let effectiveRect = detailsItem.effectiveRect + let r = NSMakeRect(rect.minX - effectiveRect.minX, rect.minY - effectiveRect.minY, 1, 1).offsetBy(dx: 0, dy: -detailsItem.titleHeight) + itemRect = detailsItem.deepRect(r).offsetBy(dx: -item.0.frame.minX, dy: -item.0.frame.minY) } else { + let effectiveRect = effectiveRectForItem(textItem) + itemRect = rect.offsetBy(dx: -effectiveRect.minX, dy: -effectiveRect.minY) + } + if let _ = textItem.linkAt(point: itemRect.origin) { + NSCursor.pointingHand.set() + break + } else if item.1 == nil { NSCursor.iBeam.set() + break + } else { + NSCursor.arrow.set() } + } else { + NSCursor.arrow.set() } - } + + return .invokeNext }, with: self, for: .mouseMoved, priority:.modal) @@ -353,100 +448,213 @@ class InstantPageSelectText : NSObject { self?.scroll.contentView.autoscroll(with: event) if window?.firstResponder != instantSelectManager { - window?.makeFirstResponder(instantSelectManager) + _ = window?.makeFirstResponder(instantSelectManager) } - self?.runSelector(instantLayout, updateLayout: updateLayout) + self?.runSelector(instantLayout, updateLayout: updateLayout, itemsInRect: itemsInRect, effectiveRectForItem: effectiveRectForItem) return .invoked } - return .invoked + return .invokeNext }, with: self, for: .leftMouseDragged, priority:.modal) } - private func runSelector(_ instantPage: InstantPageLayout, updateLayout: @escaping()->Void) { + private func runSelector(_ instantPage: InstantPageLayout, updateLayout: @escaping()->Void, itemsInRect:@escaping(NSRect) -> [InstantPageItem], effectiveRectForItem:@escaping(InstantPageItem)-> NSRect) { instantSelectManager.removeAll() let itemsRect = NSMakeRect(max(min(endInnerLocation.x, beginInnerLocation.x), 0), max(min(endInnerLocation.y, beginInnerLocation.y), 0), abs(endInnerLocation.x - beginInnerLocation.x), abs(endInnerLocation.y - beginInnerLocation.y)) - let items = instantPage.items(in: itemsRect).filter({$0 is InstantPageTextItem}).map({$0 as! InstantPageTextItem}) + guard itemsRect.size != NSZeroSize else { + return + } + + // let items = itemsInRect(itemsRect).compactMap { $0 as? InstantPageTextItem } let reversed = endInnerLocation.y < beginInnerLocation.y + + +// let lines = items.reduce([]) { (current, item) -> [InstantPageTextLine] in +// let itemRect = effectiveRectForItem(item) +// +// let rect = NSMakeRect(itemRect.minX, itemsRect.minY < itemRect.minY ? 0 : itemsRect.minY - itemRect.minY, itemsRect.width ,itemsRect.minY < itemRect.minY ? min(itemsRect.maxY - itemRect.minY, itemRect.height) : itemsRect.minY < itemRect.minY ? min(itemRect.maxY - itemsRect.minY, itemRect.height) : itemsRect.height) +// +// let lines = item.lines.filter { line in +// return line.frame.intersects(rect) +// } +// +// return current + lines +// } +// +// + let items = itemsInRect(itemsRect).reduce([], { (current, item) -> [(InstantPageItem, InstantPageTableItem?, InstantPageDetailsItem?)] in + var current = current - let multiple = items.count > 1 + let itemRect = effectiveRectForItem(item) + let rect = NSMakeRect(itemsRect.minX - itemRect.minX, itemsRect.minY - itemRect.minY, itemsRect.width, itemsRect.height) + if let item = item as? InstantPageTextItem { + current += [(item, nil, nil)] + } else if let item = item as? InstantPageDetailsItem, item.isExpanded { + current += item.itemsIn(rect).map {($0, nil, Optional(item))} + } + return current + }) - for i in 0 ..< items.count { - let item = items[i] - - let initiatedItem = (!multiple || (reversed ? i == items.count - 1 : i == 0)) - - for line in item.lines { - - var minX:CGFloat = item.frame.minX - switch item.alignment { - case .center: - minX += floorToScreenPixels((item.frame.width - line.frame.width) / 2) - default: - break + // let items = deepItemsInRect(itemsRect, itemsInRect: itemsInRect, effectiveRectForItem: effectiveRectForItem) + + var lines:[(InstantPageTextLine, InstantPageTextItem)] = [] + + for item in items { + if let textItem = item.0 as? InstantPageTextItem { + var itemRect: NSRect + if let detailsItem = item.2 { + let effectiveRect = detailsItem.effectiveRect + let r = NSMakeRect(itemsRect.minX - effectiveRect.minX, itemsRect.minY - effectiveRect.minY, itemsRect.width, itemsRect.height).offsetBy(dx: 0, dy: -detailsItem.titleHeight) + itemRect = detailsItem.deepRect(r).offsetBy(dx: -item.0.frame.minX, dy: -item.0.frame.minY) + } else { + let effectiveRect = effectiveRectForItem(textItem) + itemRect = itemsRect.offsetBy(dx: -effectiveRect.minX, dy: -effectiveRect.minY) } - let rect = NSMakeRect(item.frame.minX, itemsRect.minY < item.frame.minY ? 0 : itemsRect.minY - item.frame.minY, itemsRect.width ,itemsRect.minY < item.frame.minY ? min(itemsRect.maxY - item.frame.minY, item.frame.height) : itemsRect.minY < item.frame.minY ? min(item.frame.maxY - itemsRect.minY, item.frame.height) : itemsRect.height) - - let z = NSMakeRect(rect.minX, rect.minY, rect.width, 1) - let n = NSMakeRect(rect.maxX, rect.maxY, rect.width, 1) - - let start = reversed ? n : z - let end = reversed ? z : n - - let beginX = beginInnerLocation.x - minX - let endX = endInnerLocation.x - minX - - if rect.intersects(line.frame) { - - let selectedText:NSAttributedString - - if line.frame.intersects(start) && line.frame.intersects(end) { - if !initiatedItem { - selectedText = line.selectText(in: NSMakeRect(0, 0, endX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } else { - selectedText = line.selectText(in: NSMakeRect(beginX, 0, endX - beginX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } - - } else if line.frame.intersects(start) { - - if !initiatedItem { - selectedText = line.selectText(in: NSMakeRect(0, 0, line.frame.width, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } else { - selectedText = line.selectText(in: NSMakeRect(reversed ? 0 : beginX, 0, reversed ? beginX : line.frame.width - beginX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } - - } else if line.frame.intersects(end) { - if !initiatedItem { - if reversed { - selectedText = line.selectText(in: NSMakeRect(endX, 0, line.frame.width - endX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } else { - selectedText = line.selectText(in: NSMakeRect(0, 0, endX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } - } else { - if multiple { - selectedText = line.selectText(in: NSMakeRect(0, 0, line.frame.width, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } else { - selectedText = line.selectText(in: NSMakeRect(reversed ? endX : 0, 0, reversed ? line.frame.width - endX : endX, 0), boundingWidth: item.frame.width, alignment: item.alignment) - } - } - + for line in textItem.lines { + switch textItem.alignment { + case .center: + itemRect.origin.x -= floorToScreenPixels(System.backingScale, (textItem.frame.width - line.frame.width) / 2) + case .right: + itemRect.origin.x = textItem.frame.width - itemRect.origin.x + default: + break + } + if line.frame.intersects(itemRect.insetBy(dx: -itemRect.minX, dy: 0)) { + lines.append((line, textItem)) + } + } + } + } + + for i in 0 ..< lines.count { + let line = lines[i].0 + let item = lines[i].1 + let alignment = item.alignment + + var minX:CGFloat = item.frame.minX + switch alignment { + case .center: + minX += floorToScreenPixels(System.backingScale, (item.frame.width - line.frame.width) / 2) + case .right: + minX = item.frame.width - minX + default: + break + } + + let selectedText:NSAttributedString + + let beginX = beginInnerLocation.x - minX + let endX = endInnerLocation.x - minX + + let firstLine: InstantPageTextLine = reversed ? lines.last!.0 : lines.first!.0 + let endLine: InstantPageTextLine = !reversed ? lines.last!.0 : lines.first!.0 + let multiple: Bool = lines.count > 1 + + if firstLine === line { + if !reversed { + if multiple { + selectedText = line.selectText(in: NSMakeRect(beginX, 0, item.frame.width - beginX, 0), boundingWidth: item.frame.width, alignment: alignment) } else { - selectedText = line.selectText(in: NSMakeRect(0, 0, line.frame.width, 0), boundingWidth: item.frame.width, alignment: item.alignment) + selectedText = line.selectText(in: NSMakeRect(beginX, 0, endX - beginX, 0), boundingWidth: item.frame.width, alignment: alignment) + } + } else { + if multiple { + selectedText = line.selectText(in: NSMakeRect(0, 0, beginX, 0), boundingWidth: item.frame.width, alignment: alignment) + } else { + selectedText = line.selectText(in: NSMakeRect(endX, 0, beginX - endX, 0), boundingWidth: item.frame.width, alignment: alignment) } - - - instantSelectManager.add(line: line, attributedString: selectedText) } + + } else if endLine === line { + if !reversed { + selectedText = line.selectText(in: NSMakeRect(0, 0, endX, 0), boundingWidth: item.frame.width, alignment: alignment) + } else { + selectedText = line.selectText(in: NSMakeRect(endX, 0, item.frame.maxX - endX, 0), boundingWidth: item.frame.width, alignment: alignment) + } + } else { + selectedText = line.selectText(in: NSMakeRect(0, 0, item.frame.width, 0), boundingWidth: item.frame.width, alignment: alignment) } + + instantSelectManager.add(line: line, attributedString: selectedText) } + +// if let item = item, let textItem = item.0 as? InstantPageTextItem { +// let itemRect: NSRect +// if let tableItem = item.1 { +// let effectiveRect = effectiveRectForItem(item.1!) +// let skipCells = tableItem.itemFrameSkipCells(textItem, effectiveRect: effectiveRect) +// itemRect = rect.offsetBy(dx: -skipCells.minX, dy: -skipCells.minY) +// } else if let detailsItem = item.2 { +// let effectiveRect = detailsItem.effectiveRect +// let r = NSMakeRect(rect.minX - effectiveRect.minX, rect.minY - effectiveRect.minY, 1, 1).offsetBy(dx: 0, dy: -detailsItem.titleHeight) +// itemRect = detailsItem.deepRect(r).offsetBy(dx: -item.0.frame.minX, dy: -item.0.frame.minY) +// } else { +// let effectiveRect = effectiveRectForItem(textItem) +// itemRect = rect.offsetBy(dx: -effectiveRect.minX, dy: -effectiveRect.minY) +// } +// +// } + +// for i in 0 ..< lines.count { +// let line = lines[i] +// +// let item = items.first(where: {$0.lines.contains(where: {$0 === line})})! +// +// let itemRect = effectiveRectForItem(item) +// +// var minX:CGFloat = itemRect.minX +// switch item.alignment { +// case .center: +// minX += floorToScreenPixels(System.backingScale, (itemRect.width - line.frame.width) / 2) +// default: +// break +// } +// +// let selectedText:NSAttributedString +// +// let beginX = beginInnerLocation.x - minX +// let endX = endInnerLocation.x - minX +// +// let firstLine: InstantPageTextLine = reversed ? lines.last! : lines.first! +// let endLine: InstantPageTextLine = !reversed ? lines.last! : lines.first! +// let multiple: Bool = lines.count > 1 +// +// if firstLine === line { +// if !reversed { +// if multiple { +// selectedText = line.selectText(in: NSMakeRect(beginX, 0, itemRect.width - beginX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } else { +// selectedText = line.selectText(in: NSMakeRect(beginX, 0, endX - beginX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } +// } else { +// if multiple { +// selectedText = line.selectText(in: NSMakeRect(0, 0, beginX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } else { +// selectedText = line.selectText(in: NSMakeRect(endX, 0, beginX - endX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } +// } +// +// } else if endLine === line { +// if !reversed { +// selectedText = line.selectText(in: NSMakeRect(0, 0, endX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } else { +// selectedText = line.selectText(in: NSMakeRect(endX, 0, itemRect.maxX - endX, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } +// } else { +// selectedText = line.selectText(in: NSMakeRect(0, 0, itemRect.width, 0), boundingWidth: itemRect.width, alignment: item.alignment) +// } +// +// instantSelectManager.add(line: line, attributedString: selectedText) +// } + updateLayout() } diff --git a/Telegram-Mac/InstantPageShapeItem.swift b/Telegram-Mac/InstantPageShapeItem.swift index 969e64e291..26ea0b7317 100644 --- a/Telegram-Mac/InstantPageShapeItem.swift +++ b/Telegram-Mac/InstantPageShapeItem.swift @@ -8,7 +8,8 @@ import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore enum InstantPageShape { case rect @@ -23,9 +24,10 @@ final class InstantPageShapeItem: InstantPageItem { let color: NSColor let medias: [InstantPageMedia] = [] - let wantsNode: Bool = false + let wantsView: Bool = false let hasLinks: Bool = false - + let separatesTiles: Bool = false + let isInteractive: Bool = false init(frame: CGRect, shapeFrame: CGRect, shape: InstantPageShape, color: NSColor) { @@ -62,11 +64,11 @@ final class InstantPageShapeItem: InstantPageItem { return false } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { return false } - func node(account: Account) -> InstantPageView? { + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { return nil } diff --git a/Telegram-Mac/InstantPageSlideshowItem.swift b/Telegram-Mac/InstantPageSlideshowItem.swift index 854d88c018..b3b79e3aec 100644 --- a/Telegram-Mac/InstantPageSlideshowItem.swift +++ b/Telegram-Mac/InstantPageSlideshowItem.swift @@ -7,19 +7,24 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit class InstantPageSlideshowItem: InstantPageItem { var frame: CGRect let medias: [InstantPageMedia] - let wantsNode: Bool = true + let wantsView: Bool = true let hasLinks: Bool = false let isInteractive: Bool = true + let separatesTiles: Bool = false + + let webPage: TelegramMediaWebpage - init(frame: CGRect, medias:[InstantPageMedia]) { + init(frame: CGRect, webPage: TelegramMediaWebpage, medias: [InstantPageMedia]) { self.frame = frame + self.webPage = webPage self.medias = medias } @@ -31,7 +36,7 @@ class InstantPageSlideshowItem: InstantPageItem { return false } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { if let view = node as? InstantPageSlideshowView { return self.medias == view.medias } @@ -39,8 +44,8 @@ class InstantPageSlideshowItem: InstantPageItem { } - func node(account: Account) -> InstantPageView? { - return InstantPageSlideshowView(frameRect: frame, medias: medias, account: account) + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return InstantPageSlideshowView(frameRect: frame, medias: medias, context: arguments.context) } func linkSelectionViews() -> [InstantPageLinkSelectionView] { @@ -59,27 +64,38 @@ class InstantPageSlideshowItem: InstantPageItem { class InstantPageSlideshowView : View, InstantPageView { fileprivate let medias: [InstantPageMedia] - private let slideView: MIHSliderView - init(frameRect: NSRect, medias: [InstantPageMedia], account: Account) { + private let slideView: SliderView + init(frameRect: NSRect, medias: [InstantPageMedia], context: AccountContext) { self.medias = medias - slideView = MIHSliderView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + slideView = SliderView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) super.init(frame: frameRect) addSubview(slideView) for media in medias { - let view = InstantPageMediaView(account: account, media: media, arguments: .image(interactive: true, roundCorners: false, fit: false)) + var arguments: InstantPageMediaArguments = .image(interactive: true, roundCorners: false, fit: false) + if let media = media.media as? TelegramMediaFile { + if media.isVideo { + arguments = .video(interactive: true, autoplay: media.isAnimated) + } + } + let view = InstantPageMediaView(context: context, media: media, arguments: arguments) slideView.addSlide(view) } } + override func layout() { + super.layout() + slideView.center() + } + var indexOfDisplayedSlide: Int { return Int(slideView.indexOfDisplayedSlide) } override func copy() -> Any { - return slideView.displayedSlide.copy() + return slideView.displayedSlide?.copy() ?? super.copy() } func updateIsVisible(_ isVisible: Bool) { diff --git a/Telegram-Mac/InstantPageStoredState.swift b/Telegram-Mac/InstantPageStoredState.swift new file mode 100644 index 0000000000..b842504179 --- /dev/null +++ b/Telegram-Mac/InstantPageStoredState.swift @@ -0,0 +1,86 @@ +// +// InstantPageStoredState.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +final class InstantPageStoredDetailsState: PostboxCoding { + let index: Int32 + let expanded: Bool + let details: [InstantPageStoredDetailsState] + + init(index: Int32, expanded: Bool, details: [InstantPageStoredDetailsState]) { + self.index = index + self.expanded = expanded + self.details = details + } + + init(decoder: PostboxDecoder) { + self.index = decoder.decodeInt32ForKey("index", orElse: 0) + self.expanded = decoder.decodeBoolForKey("expanded", orElse: false) + self.details = decoder.decodeObjectArrayForKey("details") + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.index, forKey: "index") + encoder.encodeBool(self.expanded, forKey: "expanded") + encoder.encodeObjectArray(self.details, forKey: "details") + } +} + +final class InstantPageStoredState: PostboxCoding { + let contentOffset: Double + let details: [InstantPageStoredDetailsState] + + init(contentOffset: Double, details: [InstantPageStoredDetailsState]) { + self.contentOffset = contentOffset + self.details = details + } + + init(decoder: PostboxDecoder) { + self.contentOffset = decoder.decodeDoubleForKey("offset", orElse: 0.0) + self.details = decoder.decodeObjectArrayForKey("details") + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeDouble(self.contentOffset, forKey: "offset") + encoder.encodeObjectArray(self.details, forKey: "details") + } +} + +func instantPageStoredState(postbox: Postbox, webPage: TelegramMediaWebpage) -> Signal { + return postbox.transaction { transaction -> InstantPageStoredState? in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: webPage.webpageId.id) + if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, key: key)) as? InstantPageStoredState { + return entry + } else { + return nil + } + } +} + +private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 100, highWaterItemCount: 200) + +func updateInstantPageStoredStateInteractively(postbox: Postbox, webPage: TelegramMediaWebpage, state: InstantPageStoredState?) -> Signal { + return postbox.transaction { transaction -> Void in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: webPage.webpageId.id) + let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, key: key) + if let state = state { + transaction.putItemCacheEntry(id: id, entry: state, collectionSpec: collectionSpec) + } else { + transaction.removeItemCacheEntry(id: id) + } + } +} diff --git a/Telegram-Mac/InstantPageTableItem.swift b/Telegram-Mac/InstantPageTableItem.swift new file mode 100644 index 0000000000..6c69b7017a --- /dev/null +++ b/Telegram-Mac/InstantPageTableItem.swift @@ -0,0 +1,677 @@ + +import Foundation +import TelegramCore +import SyncCore +import Postbox +import TGUIKit + + + + + +private struct TableSide: OptionSet { + var rawValue: Int32 = 0 + + static let top = TableSide(rawValue: 1 << 0) + static let left = TableSide(rawValue: 1 << 1) + static let right = TableSide(rawValue: 1 << 2) + static let bottom = TableSide(rawValue: 1 << 3) + + var uiRectCorner: NSRectCorner { + var corners: NSRectCorner = [] + if self.contains(.top) && self.contains(.left) { + corners.insert(.topLeft) + } + if self.contains(.top) && self.contains(.right) { + corners.insert(.topRight) + } + if self.contains(.bottom) && self.contains(.left) { + corners.insert(.bottomLeft) + } + if self.contains(.bottom) && self.contains(.right) { + corners.insert(.bottomRight) + } + return corners + } +} + +private extension TableHorizontalAlignment { + var textAlignment: NSTextAlignment { + switch self { + case .left: + return .left + case .center: + return .center + case .right: + return .right + } + } +} + +private struct TableCellPosition { + let row: Int + let column: Int +} + +private struct InstantPageTableCellItem { + let position: TableCellPosition + let cell: InstantPageTableCell + let frame: CGRect + let filled: Bool + let textItem: InstantPageTextItem? + let additionalItems: [InstantPageItem] + let adjacentSides: TableSide + + func withRowHeight(_ height: CGFloat) -> InstantPageTableCellItem { + var frame = self.frame + frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: height) + return InstantPageTableCellItem(position: position, cell: self.cell, frame: frame, filled: self.filled, textItem: self.textItem, additionalItems: self.additionalItems, adjacentSides: self.adjacentSides) + } + + func withRTL(_ totalWidth: CGFloat) -> InstantPageTableCellItem { + var frame = self.frame + frame = CGRect(x: totalWidth - frame.minX - frame.width, y: frame.minY, width: frame.width, height: frame.height) + var adjacentSides = self.adjacentSides + if adjacentSides.contains(.left) && !adjacentSides.contains(.right) { + adjacentSides.remove(.left) + adjacentSides.insert(.right) + } + else if adjacentSides.contains(.right) && !adjacentSides.contains(.left) { + adjacentSides.remove(.right) + adjacentSides.insert(.left) + } + return InstantPageTableCellItem(position: position, cell: self.cell, frame: frame, filled: self.filled, textItem: self.textItem, additionalItems: self.additionalItems, adjacentSides: adjacentSides) + } + + var verticalAlignment: TableVerticalAlignment { + return self.cell.verticalAlignment + } + + var colspan: Int { + return self.cell.colspan > 1 ? Int(clamping: self.cell.colspan) : 1 + } + + var rowspan: Int { + return self.cell.rowspan > 1 ? Int(clamping: self.cell.rowspan) : 1 + } +} + +private let tableCellInsets = NSEdgeInsetsMake(14.0, 12.0, 14.0, 12.0) +private let tableBorderWidth: CGFloat = 1.0 +private let tableCornerRadius: CGFloat = 5.0 + +final class InstantPageTableItem: InstantPageScrollableItem { + + var hasLinks: Bool = false + var isInteractive: Bool = false + + func linkSelectionViews() -> [InstantPageLinkSelectionView] { + return [] + } + + var frame: CGRect + let totalWidth: CGFloat + let horizontalInset: CGFloat + let medias: [InstantPageMedia] = [] + let wantsView: Bool = true + let separatesTiles: Bool = false + + let theme: InstantPageTheme + + let isRTL: Bool + fileprivate let cells: [InstantPageTableCellItem] + private let borderWidth: CGFloat + + let anchors: [String: (CGFloat, Bool)] + + fileprivate init(frame: CGRect, totalWidth: CGFloat, horizontalInset: CGFloat, borderWidth: CGFloat, theme: InstantPageTheme, cells: [InstantPageTableCellItem], rtl: Bool) { + self.frame = frame + self.totalWidth = totalWidth + self.horizontalInset = horizontalInset + self.borderWidth = borderWidth + self.theme = theme + self.cells = cells + self.isRTL = rtl + + var anchors: [String: (CGFloat, Bool)] = [:] + for cell in cells { + if let textItem = cell.textItem { + for (anchor, (lineIndex, empty)) in textItem.anchors { + if anchors[anchor] == nil { + let textItemFrame = textItem.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY) + let offset = textItemFrame.minY + textItem.lines[lineIndex].frame.minY + anchors[anchor] = (offset, empty) + } + } + } + } + self.anchors = anchors + } + + func itemsIn( _ rect: NSRect, items: [InstantPageItem] = []) -> [InstantPageItem] { + var items: [InstantPageItem] = items + for cell in cells { + if let textItem = cell.textItem, cell.frame.intersects(rect) { + items.append(textItem) + } + } + return items + } + + func itemFrameSkipCells(_ item: InstantPageTextItem, effectiveRect: NSRect) -> NSRect { + for cell in cells { + if let textItem = cell.textItem, textItem === item { + return item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY).offsetBy(dx: effectiveRect.minX + horizontalInset, dy: effectiveRect.minY) + } + } + return item.frame + } + + var contentSize: CGSize { + return CGSize(width: self.totalWidth, height: self.frame.height) + } + + func drawInTile(context: CGContext) { + for cell in self.cells { + if cell.textItem == nil && cell.additionalItems.isEmpty { + 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: CGPath? + if !cell.adjacentSides.isEmpty { + + path = CGContext.round(frame: bounds, cornerRadius: tableCornerRadius, rectCorner: cell.adjacentSides.uiRectCorner) + + // path = NSBezierPath(roundedRect: bounds, byRoundingCorners: cell.adjacentSides.uiRectCorner, cornerRadius: 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) + 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 { + context.fill(bounds) + } + if hasBorder { + context.stroke(bounds) + } + } + + if let textItem = cell.textItem { + textItem.drawInTile(context: context) + } + context.restoreGState() + } + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + var additionalViews: [InstantPageView] = [] + for cell in self.cells { + for item in cell.additionalItems { + if item.wantsView { + if let view = item.view(arguments: arguments, currentExpandedDetails: nil) { + view.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY) + additionalViews.append(view) + } + } + } + } + return InstantPageScrollableView(item: self, arguments: arguments, additionalViews: additionalViews) + } + + + func matchesView(_ node: InstantPageView) -> Bool { + if let node = node as? InstantPageScrollableView { + return node.item === self + } + return false + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } + +// func linkSelectionRects(at point: CGPoint) -> [CGRect] { +// for cell in self.cells { +// if let item = cell.textItem, item.selectable, item.frame.insetBy(dx: -tableCellInsets.left, dy: -tableCellInsets.top).contains(point.offsetBy(dx: -cell.frame.minX - self.horizontalInset, dy: -cell.frame.minY)) { +// let rects = item.linkSelectionRects(at: point.offsetBy(dx: -cell.frame.minX - self.horizontalInset - item.frame.minX, dy: -cell.frame.minY - item.frame.minY)) +// return rects.map { $0.offsetBy(dx: cell.frame.minX + item.frame.minX + self.horizontalInset, dy: cell.frame.minY + item.frame.minY) } +// } +// } +// return [] +// } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { + for cell in self.cells { + if let item = cell.textItem, item.selectable, item.frame.insetBy(dx: -tableCellInsets.left, dy: -tableCellInsets.top).contains(location.offsetBy(dx: -cell.frame.minX - self.horizontalInset, dy: -cell.frame.minY)) { + return (item, cell.frame.origin.offsetBy(dx: self.horizontalInset, dy: 0.0)) + } + } + return nil + } +} + +private struct TableRow { + var minColumnWidths: [Int : CGFloat] + var maxColumnWidths: [Int : CGFloat] +} + +private func offsetForHorizontalAlignment(_ alignment: TableHorizontalAlignment, width: CGFloat, boundingWidth: CGFloat, insets: NSEdgeInsets) -> CGFloat { + switch alignment { + case .left: + return insets.left + case .center: + return (boundingWidth - width) / 2.0 + case .right: + return boundingWidth - width - insets.right + } +} + +private func offestForVerticalAlignment(_ verticalAlignment: TableVerticalAlignment, height: CGFloat, boundingHeight: CGFloat, insets: NSEdgeInsets) -> CGFloat { + switch verticalAlignment { + case .top: + return insets.top + case .middle: + return (boundingHeight - height) / 2.0 + case .bottom: + return boundingHeight - height - insets.bottom + } +} + +func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [MediaId: Media], webpage: TelegramMediaWebpage) -> InstantPageTableItem { + if rows.count == 0 { + return InstantPageTableItem(frame: CGRect(), totalWidth: 0.0, horizontalInset: 0.0, borderWidth: 0.0, theme: theme, cells: [], rtl: rtl) + } + + let borderWidth = bordered ? tableBorderWidth : 0.0 + let totalCellPadding = tableCellInsets.left + tableCellInsets.right + let cellWidthLimit = boundingWidth - totalCellPadding + var tableRows: [TableRow] = [] + var columnCount: Int = 0 + + var columnSpans: [Range : (CGFloat, CGFloat)] = [:] + var rowSpans: [Int : [(Int, Int)]] = [:] + + 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 { + 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 { + if let shortestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage, minimizeWidth: true).0 { + minCellWidth = shortestTextItem.effectiveWidth() + totalCellPadding + } + + if let longestTextItem = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding), boundingWidth: cellWidthLimit, offset: CGPoint(), media: media, webpage: webpage).0 { + maxCellWidth = max(minCellWidth, longestTextItem.effectiveWidth() + totalCellPadding) + } + } + if cell.colspan > 1 { + minColumnWidths[i] = 1.0 + maxColumnWidths[i] = 1.0 + + let spanRange = i ..< i + Int(cell.colspan) + if let (minSpanWidth, maxSpanWidth) = columnSpans[spanRange] { + columnSpans[spanRange] = (max(minSpanWidth, minCellWidth), max(maxSpanWidth, 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(TableRow(minColumnWidths: minColumnWidths, maxColumnWidths: maxColumnWidths)) + columnCount = max(columnCount, row.cells.count) + r += 1 + } + + let maxContentWidth = boundingWidth - 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 columnWidth = row.minColumnWidths[i] { + minWidth = max(minWidth, columnWidth) + } + if let columnWidth = row.maxColumnWidths[i] { + maxWidth = max(maxWidth, columnWidth) + } + } + minColumnWidths[i] = minWidth + maxColumnWidths[i] = maxWidth + availableWidth -= minWidth + maxTotalWidth += maxWidth + } + + 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 columnWidth = minColumnWidths[i] { + minWidth += columnWidth + } + if let columnWidth = maxColumnWidths[i] { + maxWidth += columnWidth + } + } + + if minWidth < minSpanWidth { + let delta = minSpanWidth - minWidth + for i in range { + if let columnWidth = minColumnWidths[i] { + let growth = floor(delta / CGFloat(range.count)) + minColumnWidths[i] = columnWidth + growth + availableWidth -= growth + } + } + } + + if maxWidth < maxSpanWidth { + let delta = maxSpanWidth - maxWidth + for i in range { + if let columnWidth = maxColumnWidths[i] { + let growth = round(delta / CGFloat(range.count)) + maxColumnWidths[i] = columnWidth + growth + maxTotalWidth += growth + } + } + } + } + + 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 = boundingWidth + } else { + totalWidth += borderWidth + } + + var finalizedCells: [InstantPageTableCellItem] = [] + var origin: CGPoint = CGPoint(x: borderWidth / 2.0, y: borderWidth / 2.0) + var totalHeight: CGFloat = 0.0 + var rowHeights: [Int : CGFloat] = [:] + + var awaitingSpanCells: [Int : [(Int, InstantPageTableCellItem)]] = [:] + + 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: [InstantPageTableCellItem] = [] + + for cell in row.cells { + if let cells = awaitingSpanCells[i] { + isEmptyRow = false + for colAndCell in cells { + let cell = colAndCell.1 + if cell.position.column == k { + for j in 0 ..< cell.colspan { + if let width = finalColumnWidths[k + j] { + origin.x += width + } + } + k += cell.colspan + } else { + break + } + } + } + + var cellWidth: CGFloat = 0.0 + let colspan: Int = cell.colspan > 1 ? Int(clamping: cell.colspan) : 1 + let rowspan: Int = cell.rowspan > 1 ? Int(clamping: cell.rowspan) : 1 + for j in 0 ..< colspan { + if let width = finalColumnWidths[k + j] { + cellWidth += width + } + } + + var item: InstantPageTextItem? + var additionalItems: [InstantPageItem] = [] + var cellHeight: CGFloat? + if let text = cell.text { + let (textItem, items, _) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidth - totalCellPadding), boundingWidth: cellWidth - totalCellPadding, alignment: cell.alignment.textAlignment, offset: CGPoint(), media: media, webpage: webpage) + if let textItem = textItem { + isEmptyRow = false + textItem.frame = textItem.frame.offsetBy(dx: tableCellInsets.left, dy: 0.0) + cellHeight = ceil(textItem.frame.height) + tableCellInsets.top + tableCellInsets.bottom + + item = textItem + } + for var item in items where !(item is InstantPageTextItem) { + isEmptyRow = false + if textItem == nil { + let offset = offsetForHorizontalAlignment(cell.alignment, width: item.frame.width, boundingWidth: cellWidth, insets: tableCellInsets) + item.frame = item.frame.offsetBy(dx: offset, dy: 0.0) + } else { + item.frame = item.frame.offsetBy(dx: tableCellInsets.left, dy: 0.0) + } + let height = ceil(item.frame.height) + tableCellInsets.top + tableCellInsets.bottom - 10.0 + if let currentCellHeight = cellHeight { + cellHeight = max(currentCellHeight, height) + } else { + cellHeight = height + } + + additionalItems.append(item) + } + } + var filled = cell.header + if !filled && striped { + filled = i % 2 == 0 + } + var adjacentSides: TableSide = [] + if i == 0 { + adjacentSides.insert(.top) + } + if i == rows.count - 1 { + adjacentSides.insert(.bottom) + } + if k == 0 { + adjacentSides.insert(.left) + } + if k + colspan == columnCount { + adjacentSides.insert(.right) + } + let rowCell = InstantPageTableCellItem(position: TableCellPosition(row: i, column: k), cell: cell, frame: CGRect(x: origin.x, y: origin.y, width: cellWidth, height: cellHeight ?? 20.0), filled: filled, textItem: item, additionalItems: additionalItems, adjacentSides: adjacentSides) + if rowspan == 1 { + rowCells.append(rowCell) + if let cellHeight = cellHeight { + maxRowHeight = max(maxRowHeight, cellHeight) + } + } else { + for j in i ..< i + rowspan { + if awaitingSpanCells[j] == nil { + awaitingSpanCells[j] = [(k, rowCell)] + } else { + awaitingSpanCells[j]!.append((k, rowCell)) + } + } + } + + k += colspan + origin.x += cellWidth + } + + let finalizeCell: (InstantPageTableCellItem, inout [InstantPageTableCellItem], CGFloat) -> Void = { cell, cells, height in + let updatedCell = cell.withRowHeight(height) + if let textItem = updatedCell.textItem { + let offset = offestForVerticalAlignment(cell.verticalAlignment, height: textItem.frame.height, boundingHeight: height, insets: tableCellInsets) + updatedCell.textItem!.frame = textItem.frame.offsetBy(dx: 0.0, dy: offset) + + for var item in updatedCell.additionalItems { + item.frame = item.frame.offsetBy(dx: 0.0, dy: offset) + } + } else { + for var item in updatedCell.additionalItems { + let offset = offestForVerticalAlignment(cell.verticalAlignment, height: item.frame.height, boundingHeight: height, insets: tableCellInsets) + item.frame = item.frame.offsetBy(dx: 0.0, dy: offset) + } + } + cells.append(updatedCell) + } + + if !isEmptyRow { + rowHeights[i] = maxRowHeight + } else { + rowHeights[i] = 0.0 + maxRowHeight = 0.0 + } + + var completedSpans = [Int : Set]() + if let cells = awaitingSpanCells[i] { + for colAndCell in cells { + let cell = colAndCell.1 + let utmostRow = cell.position.row + cell.rowspan - 1 + if rowHeights[utmostRow] == nil { + continue + } + + var cellHeight: CGFloat = 0.0 + for k in cell.position.row ..< utmostRow + 1 { + if let height = rowHeights[k] { + cellHeight += height + } + + if completedSpans[k] == nil { + var set = Set() + set.insert(colAndCell.0) + completedSpans[k] = set + } else { + completedSpans[k]!.insert(colAndCell.0) + } + } + + if cell.frame.height > cellHeight { + let delta = cell.frame.height - cellHeight + cellHeight = cell.frame.height + maxRowHeight += delta + rowHeights[i] = maxRowHeight + } + + finalizeCell(cell, &finalizedCells, cellHeight) + } + } + + for cell in rowCells { + finalizeCell(cell, &finalizedCells, maxRowHeight) + } + + if !completedSpans.isEmpty { + awaitingSpanCells = awaitingSpanCells.reduce([Int : [(Int, InstantPageTableCellItem)]]()) { (current, rowAndValue) in + var result = current + let cells = rowAndValue.value.filter({ column, cell in + if let completedSpansInRow = completedSpans[rowAndValue.key] { + return !completedSpansInRow.contains(column) + } else { + return true + } + }) + if !cells.isEmpty { + result[rowAndValue.key] = cells + } + return result + } + } + + if !isEmptyRow { + totalHeight += maxRowHeight + origin.y += maxRowHeight + } + } + totalHeight += borderWidth + + if rtl { + finalizedCells = finalizedCells.map { $0.withRTL(totalWidth) } + } + + return InstantPageTableItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: totalHeight), totalWidth: totalWidth, horizontalInset: horizontalInset, borderWidth: borderWidth, theme: theme, cells: finalizedCells, rtl: rtl) +} diff --git a/Telegram-Mac/InstantPageTextItem.swift b/Telegram-Mac/InstantPageTextItem.swift index 721deea51b..62fec9ef43 100644 --- a/Telegram-Mac/InstantPageTextItem.swift +++ b/Telegram-Mac/InstantPageTextItem.swift @@ -8,8 +8,33 @@ import Cocoa import TGUIKit +import TelegramCore +import SyncCore +import Postbox -import TelegramCoreMac + +extension NSAttributedString.Key { + static let URL: NSAttributedString.Key = NSAttributedString.Key("TelegramURL") +} + +final class InstantPageUrlItem: Equatable { + let url: String + let webpageId: MediaId? + + init(url: String, webpageId: MediaId?) { + self.url = url + self.webpageId = webpageId + } + + public static func ==(lhs: InstantPageUrlItem, rhs: InstantPageUrlItem) -> Bool { + return lhs.url == rhs.url && lhs.webpageId == rhs.webpageId + } +} + +struct InstantPageTextMarkedItem { + let frame: CGRect + let color: NSColor +} struct InstantPageTextUrlItem { let frame: CGRect @@ -20,53 +45,100 @@ struct InstantPageTextStrikethroughItem { let frame: CGRect } +struct InstantPageTextImageItem { + let frame: CGRect + let range: NSRange + let id: MediaId +} + +struct InstantPageTextAnchorItem { + let name: String + let empty: Bool +} + final class InstantPageTextLine { let line: CTLine + let range: NSRange let frame: CGRect - let urlItems: [InstantPageTextUrlItem] let strikethroughItems: [InstantPageTextStrikethroughItem] + let markedItems: [InstantPageTextMarkedItem] + let imageItems: [InstantPageTextImageItem] + let anchorItems: [InstantPageTextAnchorItem] + let isRTL: Bool let attributedString: NSAttributedString + + let separatesTiles: Bool = false + + var selectRect: NSRect = NSZeroRect - init(line: CTLine, attributedString: NSAttributedString, frame: CGRect, urlItems: [InstantPageTextUrlItem], strikethroughItems: [InstantPageTextStrikethroughItem]) { + + init(line: CTLine, attributedString: NSAttributedString, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], markedItems: [InstantPageTextMarkedItem], imageItems: [InstantPageTextImageItem], anchorItems: [InstantPageTextAnchorItem], isRTL: Bool) { self.line = line - self.frame = frame self.attributedString = attributedString - self.urlItems = urlItems + self.range = range + self.frame = frame self.strikethroughItems = strikethroughItems + self.markedItems = markedItems + self.imageItems = imageItems + self.anchorItems = anchorItems + self.isRTL = isRTL + } - func linkAt(point: NSPoint) -> RichText? { - let index: CFIndex = CTLineGetStringIndexForPosition(line, point) - if index >= 0 && index < attributedString.length { - return attributedString.attribute(NSAttributedStringKey.link, at: index, effectiveRange: nil) as? RichText + func linkAt(point: NSPoint) -> InstantPageUrlItem? { + if point.x >= 0 && point.x <= frame.width { + let index: CFIndex = CTLineGetStringIndexForPosition(line, point) + if index >= 0 && index < attributedString.length { + return attributedString.attribute(.URL, at: index, effectiveRange: nil) as? InstantPageUrlItem + } } + return nil } func selectText(in rect: NSRect, boundingWidth: CGFloat, alignment: NSTextAlignment) -> NSAttributedString { + var rect = rect + if isRTL { + rect.origin.x -= (boundingWidth - frame.width) + } let startIndex: CFIndex = CTLineGetStringIndexForPosition(line, NSMakePoint(rect.minX, 0)) let endIndex: CFIndex = CTLineGetStringIndexForPosition(line, NSMakePoint(rect.maxX, 0)) + + var startOffset = CTLineGetOffsetForStringIndex(line, startIndex, nil) var endOffset = CTLineGetOffsetForStringIndex(line, endIndex, nil) - + switch alignment { case .center: - let additional = floorToScreenPixels((boundingWidth - frame.width) / 2) + let additional = floorToScreenPixels(System.backingScale, (boundingWidth - frame.width) / 2) startOffset += additional endOffset += additional + case .right: + startOffset = boundingWidth - startOffset + endOffset = boundingWidth - endOffset default: break } - selectRect = NSMakeRect(startOffset, frame.minY - 2, endOffset - startOffset, frame.height + 6) - return attributedString.attributedSubstring(from: NSMakeRange(startIndex, endIndex - startIndex)) + var selectRect = NSMakeRect(startOffset, frame.minY - 2, endOffset - startOffset, frame.height + 6) + + if isRTL { + selectRect.origin.x += (boundingWidth - frame.width) + } + + self.selectRect = selectRect + return attributedString.attributedSubstring(from: NSMakeRange(min(startIndex, endIndex), abs(endIndex - startIndex))) } - func selectWord(in point: NSPoint, boundingWidth: CGFloat, alignment: NSTextAlignment) -> NSAttributedString { + func selectWord(in point: NSPoint, boundingWidth: CGFloat, alignment: NSTextAlignment, rect: NSRect) -> NSAttributedString { + var point = point + if isRTL { + point.x -= (boundingWidth - frame.width) + } let startIndex: CFIndex = CTLineGetStringIndexForPosition(line, point) @@ -77,7 +149,7 @@ final class InstantPageTextLine { var range = NSMakeRange(startIndex, 1) let char:NSString = attributedString.string.nsstring.substring(with: range) as NSString var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) - let check = attributedString.attribute(NSAttributedStringKey.link, at: range.location, effectiveRange: &effectiveRange) + let check = attributedString.attribute(NSAttributedString.Key.link, at: range.location, effectiveRange: &effectiveRange) if check != nil && effectiveRange.location != NSNotFound { return attributedString.attributedSubstring(from: effectiveRange) } @@ -120,14 +192,25 @@ final class InstantPageTextLine { switch alignment { case .center: - let additional = floorToScreenPixels((boundingWidth - frame.width) / 2) + let additional = floorToScreenPixels(System.backingScale, (boundingWidth - frame.width) / 2) startOffset += additional endOffset += additional + case .right: + startOffset = boundingWidth - startOffset + endOffset = boundingWidth - endOffset default: break } + selectRect = NSMakeRect(startOffset, frame.minY - 2, endOffset - startOffset, frame.height + 6) + + + + if isRTL { + selectRect.origin.x += (boundingWidth - frame.width) + } + return attributedString.attributedSubstring(from: range) } @@ -136,39 +219,71 @@ final class InstantPageTextLine { } } + +private func frameForLine(_ 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 +} + final class InstantPageTextItem: InstantPageItem { + var hasLinks: Bool = false + + let isInteractive: Bool = false + + let attributedString: NSAttributedString let lines: [InstantPageTextLine] - let hasLinks: Bool + let rtlLineIndices: Set var frame: CGRect - var alignment: NSTextAlignment = .left + let alignment: NSTextAlignment let medias: [InstantPageMedia] = [] - let wantsNode: Bool = false + let anchors: [String: (Int, Bool)] + let wantsView: Bool = false + let separatesTiles: Bool = false + var selectable: Bool = true - let isInteractive: Bool = false + var containsRTL: Bool { + return !self.rtlLineIndices.isEmpty + } - init(frame: CGRect, lines: [InstantPageTextLine]) { + init(frame: CGRect, attributedString: NSAttributedString, alignment: NSTextAlignment, lines: [InstantPageTextLine]) { + self.attributedString = attributedString + self.alignment = alignment self.frame = frame self.lines = lines - var hasLinks = false + var index = 0 + var rtlLineIndices = Set() + var anchors: [String: (Int, Bool)] = [:] for line in lines { - if !line.urlItems.isEmpty { - hasLinks = true + if line.isRTL { + rtlLineIndices.insert(index) } + for anchor in line.anchorItems { + anchors[anchor.name] = (index, anchor.empty) + } + index += 1 } - self.hasLinks = hasLinks + self.rtlLineIndices = rtlLineIndices + self.anchors = anchors } - func linkAt(point: NSPoint) -> RichText? { + func linkAt(point: NSPoint) -> InstantPageUrlItem? { for line in lines { - var point = NSMakePoint(min(max(point.x, 0), frame.width), point.y) + var point = NSMakePoint(point.x, point.y) switch alignment { case .center: - point.x -= floorToScreenPixels((frame.width - line.frame.width) / 2) + point.x -= floorToScreenPixels(System.backingScale, (frame.width - line.frame.width) / 2) + case .right: + point.x = frame.width - point.x default: break } - if line.frame.minY < point.y && line.frame.maxY > point.y { + if NSPointInRect(point, line.frame) { return line.linkAt(point: NSMakePoint(point.x, 0)) } } @@ -182,35 +297,52 @@ final class InstantPageTextItem: InstantPageItem { let clipRect = context.boundingBoxOfClipPath - - let upperOriginBound = clipRect.minY - 10.0 let lowerOriginBound = clipRect.maxY + 10.0 let boundsWidth = self.frame.size.width - for line in self.lines { - let lineFrame = line.frame + for i in 0 ..< self.lines.count { + let line = self.lines[i] + let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) if lineFrame.maxY < upperOriginBound || lineFrame.minY > lowerOriginBound { continue } - var lineOrigin = lineFrame.origin - if self.alignment == .center { - lineOrigin.x = floor((boundsWidth - lineFrame.size.width) / 2.0) - } + let lineOrigin = lineFrame.origin - context.setFillColor(theme.colors.selectText.cgColor) - context.fill(line.selectRect) + 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 rect = CGRect(x: itemFrame.minX - 2.0, y: floor(itemFrame.minY + (itemFrame.height - height) / 2.0), width: itemFrame.width + 4.0, height: height) + let path = CGPath(roundedRect: rect, cornerWidth: 3, cornerHeight: 3, transform: nil) //NSBezierPath(roundedRect: rect, xRadius: 3.0, yRadius: 3.0) + context.addPath(path) + context.fillPath() + } + context.restoreGState() + } + context.setFillColor(theme.colors.selectText.cgColor) + context.fill(line.selectRect) + CTLineDraw(line.line, context) if !line.strikethroughItems.isEmpty { for item in line.strikethroughItems { - context.fill(CGRect(x: item.frame.minX, y: item.frame.minY + floor((lineFrame.size.height / 2.0) + 1.0), width: item.frame.size.width, height: 1.0)) + let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0) + context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + floor((lineFrame.size.height / 2.0) + 1.0), width: itemFrame.size.width, height: 1.0)) } } + + } context.restoreGState() @@ -224,11 +356,11 @@ final class InstantPageTextItem: InstantPageItem { return false } - func node(account: Account) -> InstantPageView? { + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { return nil } - func matchesNode(_ node: InstantPageView) -> Bool { + func matchesView(_ node: InstantPageView) -> Bool { return false } @@ -239,84 +371,232 @@ final class InstantPageTextItem: InstantPageItem { func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { return 0.0 } + + func lineRects() -> [CGRect] { + let boundsWidth = self.frame.width + var rects: [CGRect] = [] + var topLeft = CGPoint(x: CGFloat.greatestFiniteMagnitude, y: 0.0) + var bottomRight = CGPoint() + + var lastLineFrame: CGRect? + 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 + } + + if lineFrame.minX < topLeft.x { + topLeft = CGPoint(x: lineFrame.minX, y: topLeft.y) + } + if lineFrame.maxX > bottomRight.x { + bottomRight = CGPoint(x: lineFrame.maxX, y: bottomRight.y) + } + + if self.lines.count > 1 && i == self.lines.count - 1 { + lastLineFrame = lineFrame + } else { + if lineFrame.minY < topLeft.y { + topLeft = CGPoint(x: topLeft.x, y: lineFrame.minY) + } + if lineFrame.maxY > bottomRight.y { + bottomRight = CGPoint(x: bottomRight.x, y: lineFrame.maxY) + } + } + } + rects.append(CGRect(x: topLeft.x, y: topLeft.y, width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y)) + if self.lines.count > 1, var lastLineFrame = lastLineFrame { + let delta = lastLineFrame.minY - bottomRight.y + lastLineFrame = CGRect(x: lastLineFrame.minX, y: bottomRight.y, width: lastLineFrame.width, height: lastLineFrame.height + delta) + rects.append(lastLineFrame) + } + + return rects + } + + func effectiveWidth() -> CGFloat { + var width: CGFloat = 0.0 + for line in self.lines { + width = max(width, line.frame.width) + } + return ceil(width) + } + + func plainText() -> String { + if let first = self.lines.first, let last = self.lines.last { + return self.attributedString.attributedSubstring(from: NSMakeRange(first.range.location, last.range.location + last.range.length - first.range.location)).string + } + return "" + } + } -func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack) -> NSAttributedString { + + + +func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil, boundingWidth: CGFloat? = nil) -> NSAttributedString { switch text { case .empty: return NSAttributedString(string: "", attributes: styleStack.textAttributes()) case let .plain(string): - return NSAttributedString(string: string, attributes: styleStack.textAttributes()) + var attributes = styleStack.textAttributes() + if let url = url { + attributes[.URL] = url + } + return NSAttributedString(string: string, attributes: attributes) case let .bold(text): styleStack.push(.bold) - let result = attributedStringForRichText(text, styleStack: styleStack) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) styleStack.pop() return result case let .italic(text): styleStack.push(.italic) - let result = attributedStringForRichText(text, styleStack: styleStack) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) styleStack.pop() return result case let .underline(text): styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) styleStack.pop() return result case let .strikethrough(text): styleStack.push(.strikethrough) - let result = attributedStringForRichText(text, styleStack: styleStack) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) styleStack.pop() return result case let .fixed(text): styleStack.push(.fontFixed(true)) - let result = attributedStringForRichText(text, styleStack: styleStack) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) styleStack.pop() return result case let .url(text, url, webpageId): - styleStack.push(.link(.url(text: text, url: url, webpageId: webpageId))) - styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack) - styleStack.pop() + styleStack.push(.link(webpageId != nil)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: url, webpageId: webpageId)) styleStack.pop() return result case let .email(text, email): styleStack.push(.bold) - styleStack.push(.link(.url(text: text, url: email, webpageId: nil))) styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack) - styleStack.pop() + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "mailto:\(email)", webpageId: nil)) styleStack.pop() styleStack.pop() return result case let .concat(texts): let string = NSMutableAttributedString() for text in texts { - let substring = attributedStringForRichText(text, styleStack: styleStack) + let substring = attributedStringForRichText(text, styleStack: styleStack, url: url, boundingWidth: boundingWidth) string.append(substring) } return string + case let .subscript(text): + styleStack.push(.subscript) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result + case let .superscript(text): + styleStack.push(.superscript) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result + case let .marked(text): + styleStack.push(.marker) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + 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)) + styleStack.pop() + styleStack.pop() + return result + case let .image(id, dimensions): + struct RunStruct { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + } + + var dimensions = dimensions.size + if let boundingWidth = boundingWidth { + dimensions = dimensions.fittedToWidthOrSmaller(boundingWidth) + } + let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) + extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.width)) + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in + }, 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 attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedString.Key): (delegate as Any), .instantPageMediaIdAttribute : id.id, .instantPageMediaDimensionsAttribute: dimensions] + return NSAttributedString(string: " ", attributes: attrDictionaryDelegate) + case let .anchor(text, name): + var empty = false + var text = text + if case .empty = text { + empty = true + text = .plain("\u{200b}") + } + styleStack.push(.anchor(name, empty)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + styleStack.pop() + return result } } -func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat) -> InstantPageTextItem { +func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { if string.length == 0 { - return InstantPageTextItem(frame: CGRect(), lines: []) + return (nil, [], CGSize()) } var lines: [InstantPageTextLine] = [] - guard let font = string.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as? NSFont else { - return InstantPageTextItem(frame: CGRect(), lines: []) + var imageItems: [InstantPageTextImageItem] = [] + var font = string.attribute(.font, at: 0, effectiveRange: nil) as? NSFont + if font == nil { + let range = NSMakeRange(0, string.length) + string.enumerateAttributes(in: range, options: []) { attributes, range, _ in + if font == nil, let furtherFont = attributes[.font] as? NSFont { + font = furtherFont + } + } + } + let image = string.attribute(.instantPageMediaIdAttribute, at: 0, effectiveRange: nil) + guard font != nil || image != nil else { + return (nil, [], CGSize()) } var lineSpacingFactor: CGFloat = 1.12 - if let lineSpacingFactorAttribute = string.attribute(.instantPageLineSpacingFactor, at: 0, effectiveRange: nil) { + if let lineSpacingFactorAttribute = string.attribute(.instantPageLineSpacingFactorAttribute, at: 0, effectiveRange: nil) { lineSpacingFactor = CGFloat((lineSpacingFactorAttribute as! NSNumber).floatValue) } let typesetter = CTTypesetterCreateWithAttributedString(string) - let fontAscent = font.ascender - let fontDescent = font.descender + let fontAscent = font?.ascender ?? 0.0 + let fontDescent = font?.descender ?? 0.0 let fontLineHeight = floor(fontAscent + fontDescent) let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) @@ -324,58 +604,221 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo var lastIndex: CFIndex = 0 var currentLineOrigin = CGPoint() + 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? while true { - let currentMaxWidth = boundingWidth - currentLineOrigin.x - let currentLineInset: CGFloat = 0.0 - let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth)) + var workingLineOrigin = currentLineOrigin + let currentMaxWidth = boundingWidth - workingLineOrigin.x + let 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 + indexOffset = nil + hadIndexOffset = true + } else { + lineCharacterCount = suggestedLineBreak + } + } if lineCharacterCount > 0 { - let line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0) + var line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0) + var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil)) + let lineRange = NSMakeRange(lastIndex, lineCharacterCount) + let substring = string.attributedSubstring(from: lineRange).string + + 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 trailingWhitespace = CGFloat(CTLineGetTrailingWhitespaceWidth(line)) - let lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil) + Double(currentLineInset)) + let hadExtraDescent = extraDescent > 0.0 + extraDescent = 0.0 + var lineImageItems: [InstantPageTextImageItem] = [] + var isRTL = false + if let glyphRuns = CTLineGetGlyphRuns(line) as? [CTRun], !glyphRuns.isEmpty { + if let run = glyphRuns.first, CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + + var appliedLineOffset: CGFloat = 0.0 + 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[.instantPageMediaIdAttribute] as? Int64, let dimensions = attributes[.instantPageMediaDimensionsAttribute] as? CGSize { + var imageFrame = CGRect(origin: CGPoint(), size: dimensions) + + let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil) + let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels(System.backingScale, (fontLineHeight - imageFrame.size.height) / 2.0) + imageFrame.origin = imageFrame.origin.offsetBy(dx: workingLineOrigin.x + xOffset, dy: workingLineOrigin.y + yOffset) + + let minSpacing = fontLineSpacing - 4.0 + let delta = workingLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset + if !fontAscent.isZero && delta > 0.0 { + workingLineOrigin.y += delta + appliedLineOffset += delta + imageFrame.origin = imageFrame.origin.offsetBy(dx: 0.0, dy: delta) + } + if !fontLineHeight.isZero { + extraDescent = max(extraDescent, imageFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing)) + } + maxImageHeight = max(maxImageHeight, imageFrame.height) + lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id))) + } + } + } + } + + if substring.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && lineImageItems.count > 0 { + 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 + } - var urlItems: [InstantPageTextUrlItem] = [] var strikethroughItems: [InstantPageTextStrikethroughItem] = [] + var markedItems: [InstantPageTextMarkedItem] = [] + var anchorItems: [InstantPageTextAnchorItem] = [] - string.enumerateAttribute(.strikethroughStyle, in: NSMakeRange(lastIndex, lineCharacterCount), options: [], using: { item, range, _ in - if let _ = item { + string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in + if let _ = attributes[.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, width: abs(upperX - lowerX), height: fontLineHeight))) + } + if let color = attributes[.instantPageMarkerColorAttribute] as? NSColor { + var lineHeight = fontLineHeight + var delta: CGFloat = 0.0 - strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: currentLineOrigin.x + lowerX, y: currentLineOrigin.y, width: upperX - lowerX, height: fontLineHeight))) + if let offset = attributes[.baselineOffset] as? CGFloat { + lineHeight = floorToScreenPixels(System.backingScale, 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 + delta, width: abs(upperX - lowerX), height: lineHeight), color: color)) + } + if let item = attributes[.instantPageAnchorAttribute] as? Dictionary, let name = item["name"] as? String, let empty = item["empty"] as? Bool { + anchorItems.append(InstantPageTextAnchorItem(name: name, empty: empty)) } - }) + } - let textLine = InstantPageTextLine(line: line, attributedString: string, frame: CGRect(x: currentLineOrigin.x, y: currentLineOrigin.y, width: lineWidth, height: fontLineHeight), urlItems: urlItems, strikethroughItems: strikethroughItems) + if !anchorItems.isEmpty { + hasAnchors = true + } - lines.append(textLine) + if hadExtraDescent && extraDescent > 0 { + workingLineOrigin.y += fontLineSpacing + } - var rightAligned = false + let height = !fontLineHeight.isZero ? fontLineHeight : maxImageHeight + let textLine = InstantPageTextLine(line: line, attributedString: string, 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 glyphRuns = CTLineGetGlyphRuns(line) - if CFArrayGetCount(glyphRuns) != 0 { - if (CTRunGetStatus(CFArrayGetValueAtIndex(glyphRuns, 0) as! CTRun).rawValue & CTRunStatus.rightToLeft.rawValue) != 0 { - rightAligned = true - } - }*/ + lines.append(textLine) + imageItems.append(contentsOf: lineImageItems) - //hadRTL |= rightAligned; + if lineWidth > maxLineWidth { + maxLineWidth = lineWidth + } - currentLineOrigin.x = 0.0; - currentLineOrigin.y += fontLineHeight + fontLineSpacing + workingLineOrigin.x = 0.0 + workingLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent + currentLineOrigin = workingLineOrigin lastIndex += lineCharacterCount + if stop { + break + } } else { - break; + break } } var height: CGFloat = 0.0 - if !lines.isEmpty { - height = lines.last!.frame.maxY + if !lines.isEmpty && !(string.string == "\u{200b}" && hasAnchors) { + height = lines.last!.frame.maxY + extraDescent + } + + var textWidth = boundingWidth + var requiresScroll = false + if !imageItems.isEmpty && maxLineWidth > boundingWidth + 10.0 { + textWidth = maxLineWidth + requiresScroll = true + } + + let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: textWidth, height: height), attributedString: string, alignment: alignment, lines: lines) + if !requiresScroll { + textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y) + } + var items: [InstantPageItem] = [] + if !requiresScroll && (imageItems.isEmpty || string.length > 1) { + items.append(textItem) + } + + 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) + for imageItem in line.imageItems { + if let image = media[imageItem.id] as? TelegramMediaFile { + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, webpage: webpage, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + additionalItems.append(item) + + if item.frame.minY < topInset { + topInset = item.frame.minY + } + if item.frame.maxY > height { + bottomInset = max(bottomInset, item.frame.maxY - height) + } + } + } + } } - return InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: height), lines: lines) +// if requiresScroll { +// textItem.frame = textItem.frame.offsetBy(dx: 0.0, dy: fabs(topInset)) +// for var item in additionalItems { +// item.frame = item.frame.offsetBy(dx: 0.0, dy: fabs(topInset)) +// } +// +// let scrollableItem = InstantPageScrollableTextItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth + horizontalInset * 2.0, height: height + fabs(topInset) + bottomInset), item: textItem, additionalItems: additionalItems, totalWidth: textWidth, horizontalInset: horizontalInset, rtl: textItem.containsRTL) +// items.append(scrollableItem) +// } else { + items.append(contentsOf: additionalItems) +// } + + return (requiresScroll ? nil : textItem, items, textItem.frame.size) } + diff --git a/Telegram-Mac/InstantPageTextStyleStack.swift b/Telegram-Mac/InstantPageTextStyleStack.swift index 9a8cebe5f3..9c751e2ef7 100644 --- a/Telegram-Mac/InstantPageTextStyleStack.swift +++ b/Telegram-Mac/InstantPageTextStyleStack.swift @@ -8,31 +8,10 @@ import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit - -func richPlainText(_ text:RichText) -> String { - switch text { - case .plain(let plain): - return plain - case .bold(let rich), .italic(let rich), .fixed(let rich), .strikethrough(let rich), .underline(let rich): - return richPlainText(rich) - case .email(let rich, _): - return richPlainText(rich) - case .url(let rich, _, _): - return richPlainText(rich) - case .concat(let richs): - var string:String = "" - for rich in richs { - string += richPlainText(rich) - } - return string - case .empty: - return"" - } -} - enum InstantPageTextStyle { case fontSize(CGFloat) case lineSpacingFactor(CGFloat) @@ -43,15 +22,25 @@ enum InstantPageTextStyle { case underline case strikethrough case textColor(NSColor) - case link(RichText) + case `subscript` + case superscript + case markerColor(NSColor) + case marker + case anchor(String, Bool) + case linkColor(NSColor) + case linkMarkerColor(NSColor) + case link(Bool) } -extension NSAttributedStringKey { - static var instantPageLineSpacingFactor: NSAttributedStringKey { - return NSAttributedStringKey.init(rawValue: "LineSpacingFactorAttribute") - } +extension NSAttributedString.Key { + static let instantPageLineSpacingFactorAttribute = NSAttributedString.Key("InstantPageLineSpacingFactorAttribute") + static let instantPageMarkerColorAttribute = NSAttributedString.Key("InstantPageMarkerColorAttribute") + static let instantPageMediaIdAttribute = NSAttributedString.Key("InstantPageMediaIdAttribute") + static let instantPageMediaDimensionsAttribute = NSAttributedString.Key("InstantPageMediaDimensionsAttribute") + static let instantPageAnchorAttribute = NSAttributedString.Key("InstantPageAnchorAttribute") } + final class InstantPageTextStyleStack { private var items: [InstantPageTextStyle] = [] @@ -65,7 +54,7 @@ final class InstantPageTextStyleStack { } } - func textAttributes() -> [NSAttributedStringKey: Any] { + func textAttributes() -> [NSAttributedString.Key: Any] { var fontSize: CGFloat? var fontSerif: Bool? var fontFixed: Bool? @@ -75,7 +64,14 @@ final class InstantPageTextStyleStack { var underline: Bool? var color: NSColor? var lineSpacingFactor: CGFloat? - var link:RichText? + var baselineOffset: CGFloat? + var markerColor: NSColor? + var marker: Bool? + var anchor: Dictionary? + var linkColor: NSColor? + var linkMarkerColor: NSColor? + var link: Bool? + for item in self.items.reversed() { switch item { case let .fontSize(value): @@ -114,12 +110,43 @@ final class InstantPageTextStyleStack { if lineSpacingFactor == nil { lineSpacingFactor = value } - case .link(let value): - link = value + case .subscript: + if baselineOffset == nil { + baselineOffset = 0.35 + underline = false + } + case .superscript: + if baselineOffset == nil { + baselineOffset = -0.35 + } + case let .markerColor(color): + if markerColor == nil { + markerColor = color + } + case .marker: + if marker == nil { + marker = true + } + case let .anchor(name, empty): + if anchor == nil { + anchor = ["name": name, "empty": empty] + } + case let .linkColor(color): + if linkColor == nil { + linkColor = color + } + case let .linkMarkerColor(color): + if linkMarkerColor == nil { + linkMarkerColor = color + } + case let .link(instant): + if link == nil { + link = instant + } } } - var attributes: [NSAttributedStringKey: Any] = [:] + var attributes: [NSAttributedString.Key: Any] = [:] var parsedFontSize: CGFloat if let fontSize = fontSize { @@ -128,61 +155,76 @@ final class InstantPageTextStyleStack { parsedFontSize = 16.0 } + if let baselineOffset = baselineOffset { + attributes[.baselineOffset] = round(parsedFontSize * baselineOffset); + parsedFontSize = round(parsedFontSize * 0.85) + } + if (bold != nil && bold!) && (italic != nil && italic!) { if fontSerif != nil && fontSerif! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Georgia-BoldItalic", size: parsedFontSize) + attributes[.font] = NSFont(name: "Georgia-BoldItalic", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Menlo-BoldItalic", size: parsedFontSize) + attributes[.font] = NSFont(name: "Menlo-BoldItalic", size: parsedFontSize) } else { - attributes[NSAttributedStringKey.font] = NSFont.bold(.custom(parsedFontSize)) + attributes[.font] = systemMediumFont(parsedFontSize) } } else if bold != nil && bold! { if fontSerif != nil && fontSerif! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Georgia-Bold", size: parsedFontSize) + attributes[.font] = NSFont(name: "Georgia-Bold", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Menlo-Bold", size: parsedFontSize) + attributes[.font] = NSFont(name: "Menlo-Bold", size: parsedFontSize) } else { - attributes[NSAttributedStringKey.font] = NSFont.bold(.custom(parsedFontSize)) + attributes[.font] = NSFont.bold(parsedFontSize) } } else if italic != nil && italic! { if fontSerif != nil && fontSerif! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Georgia-Italic", size: parsedFontSize) + attributes[.font] = NSFont(name: "Georgia-Italic", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Menlo-Italic", size: parsedFontSize) + attributes[.font] = NSFont(name: "Menlo-Italic", size: parsedFontSize) } else { - attributes[NSAttributedStringKey.font] = NSFont.italic(.custom(parsedFontSize)) + attributes[.font] = NSFont.italic(parsedFontSize) } } else { if fontSerif != nil && fontSerif! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Georgia", size: parsedFontSize) + attributes[.font] = NSFont(name: "Georgia", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSAttributedStringKey.font] = NSFont(name: "Menlo", size: parsedFontSize) + attributes[.font] = NSFont(name: "Menlo", size: parsedFontSize) } else { - attributes[NSAttributedStringKey.font] = NSFont.normal(.custom(parsedFontSize)) + attributes[.font] = NSFont.normal(parsedFontSize) } } if strikethrough != nil && strikethrough! { - attributes[NSAttributedStringKey.strikethroughStyle] = (NSUnderlineStyle.styleSingle.rawValue | NSUnderlineStyle.patternSolid.rawValue) as NSNumber + attributes[.strikethroughStyle] = (NSUnderlineStyle.single.rawValue | NSUnderlineStyle.patternDash.rawValue) as NSNumber } if underline != nil && underline! { - attributes[NSAttributedStringKey.underlineStyle] = NSUnderlineStyle.styleSingle.rawValue as NSNumber + attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue as NSNumber } - if let color = color { - attributes[NSAttributedStringKey.foregroundColor] = color + if let link = link, let linkColor = linkColor { + attributes[.foregroundColor] = linkColor + if link, let linkMarkerColor = linkMarkerColor { + attributes[.instantPageMarkerColorAttribute] = linkMarkerColor + } } else { - attributes[NSAttributedStringKey.foregroundColor] = theme.colors.text + if let color = color { + attributes[.foregroundColor] = color + } else { + attributes[.foregroundColor] = NSColor.black + } } + if let lineSpacingFactor = lineSpacingFactor { + attributes[.instantPageLineSpacingFactorAttribute] = lineSpacingFactor as NSNumber + } - if let link = link { - attributes[NSAttributedStringKey.link] = link + if marker != nil && marker!, let markerColor = markerColor { + attributes[.instantPageMarkerColorAttribute] = markerColor } - if let lineSpacingFactor = lineSpacingFactor { - attributes[.instantPageLineSpacingFactor] = lineSpacingFactor as NSNumber + if let anchor = anchor { + attributes[.instantPageAnchorAttribute] = anchor } return attributes diff --git a/Telegram-Mac/InstantPageTheme.swift b/Telegram-Mac/InstantPageTheme.swift new file mode 100644 index 0000000000..09ecc9df36 --- /dev/null +++ b/Telegram-Mac/InstantPageTheme.swift @@ -0,0 +1,339 @@ +// +// InstantPageTheme.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import Postbox + +enum InstantPageFontStyle { + case sans + case serif +} + +struct InstantPageFont { + let style: InstantPageFontStyle + let size: CGFloat + let lineSpacingFactor: CGFloat +} + +struct InstantPageTextAttributes { + let font: InstantPageFont + let color: NSColor + let underline: Bool + + init(font: InstantPageFont, color: NSColor, underline: Bool = false) { + self.font = font + self.color = color + self.underline = underline + } + + func withUnderline(_ underline: Bool) -> 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) + } +} + +enum InstantPageTextCategoryType { + case kicker + case header + case subheader + case paragraph + case caption + case credit + case table + case article +} + +struct InstantPageTextCategories { + let kicker: InstantPageTextAttributes + let header: InstantPageTextAttributes + let subheader: InstantPageTextAttributes + let paragraph: InstantPageTextAttributes + let caption: InstantPageTextAttributes + let credit: InstantPageTextAttributes + let table: InstantPageTextAttributes + let article: InstantPageTextAttributes + + 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) + } + } + + 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)) + } +} + +final class InstantPageTheme { + let type: InstantPageThemeType + let pageBackgroundColor: NSColor + + let textCategories: InstantPageTextCategories + let serif: Bool + + let codeBlockBackgroundColor: NSColor + + let linkColor: NSColor + let textHighlightColor: NSColor + let linkHighlightColor: NSColor + let markerColor: NSColor + + let panelBackgroundColor: NSColor + let panelHighlightedBackgroundColor: NSColor + let panelPrimaryColor: NSColor + let panelSecondaryColor: NSColor + let panelAccentColor: NSColor + + let tableBorderColor: NSColor + let tableHeaderColor: NSColor + let controlColor: NSColor + + let imageTintColor: NSColor? + + let overlayPanelColor: NSColor + + init(type: InstantPageThemeType, pageBackgroundColor: NSColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: NSColor, linkColor: NSColor, textHighlightColor: NSColor, linkHighlightColor: NSColor, markerColor: NSColor, panelBackgroundColor: NSColor, panelHighlightedBackgroundColor: NSColor, panelPrimaryColor: NSColor, panelSecondaryColor: NSColor, panelAccentColor: NSColor, tableBorderColor: NSColor, tableHeaderColor: NSColor, controlColor: NSColor, imageTintColor: NSColor?, overlayPanelColor: NSColor) { + self.type = type + self.pageBackgroundColor = pageBackgroundColor + self.textCategories = textCategories + self.serif = serif + self.codeBlockBackgroundColor = codeBlockBackgroundColor + self.linkColor = linkColor + self.textHighlightColor = textHighlightColor + self.linkHighlightColor = linkHighlightColor + self.markerColor = markerColor + self.panelBackgroundColor = panelBackgroundColor + self.panelHighlightedBackgroundColor = panelHighlightedBackgroundColor + self.panelPrimaryColor = panelPrimaryColor + self.panelSecondaryColor = panelSecondaryColor + self.panelAccentColor = panelAccentColor + self.tableBorderColor = tableBorderColor + self.tableHeaderColor = tableHeaderColor + self.controlColor = controlColor + self.imageTintColor = imageTintColor + self.overlayPanelColor = overlayPanelColor + } + + 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) + } +} + +private let lightTheme = InstantPageTheme( + type: .light, + pageBackgroundColor: .white, + textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: .black), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: .black), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: .black), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: .black), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x79828b)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: NSColor(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) + ), + serif: false, + codeBlockBackgroundColor: NSColor(rgb: 0xf5f8fc), + linkColor: NSColor(rgb: 0x007ee5), + textHighlightColor: NSColor(rgb: 0, alpha: 0.12), + linkHighlightColor: NSColor(rgb: 0x007ee5, alpha: 0.07), + markerColor: NSColor(rgb: 0xfef3bc), + panelBackgroundColor: NSColor(rgb: 0xf3f4f5), + panelHighlightedBackgroundColor: NSColor(rgb: 0xe7e7e7), + panelPrimaryColor: .black, + panelSecondaryColor: NSColor(rgb: 0x79828b), + panelAccentColor: NSColor(rgb: 0x007ee5), + tableBorderColor: NSColor(rgb: 0xe2e2e2), + tableHeaderColor: NSColor(rgb: 0xf4f4f4), + controlColor: NSColor(rgb: 0xc7c7cd), + imageTintColor: nil, + overlayPanelColor: .white +) + +private let sepiaTheme = InstantPageTheme( + type: .sepia, + pageBackgroundColor: NSColor(rgb: 0xf8f1e2), + textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0x4f321d)), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0x4f321d)), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0x4f321d)), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x4f321d)), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x927e6b)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x927e6b)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x4f321d)), + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x4f321d)) + ), + serif: false, + codeBlockBackgroundColor: NSColor(rgb: 0xefe7d6), + linkColor: NSColor(rgb: 0xd19600), + textHighlightColor: NSColor(rgb: 0, alpha: 0.1), + linkHighlightColor: NSColor(rgb: 0xd19600, alpha: 0.1), + markerColor: NSColor(rgb: 0xe5ddcd), + panelBackgroundColor: NSColor(rgb: 0xefe7d6), + panelHighlightedBackgroundColor: NSColor(rgb: 0xe3dccb), + panelPrimaryColor: .black, + panelSecondaryColor: NSColor(rgb: 0x927e6b), + panelAccentColor: NSColor(rgb: 0xd19601), + tableBorderColor: NSColor(rgb: 0xddd1b8), + tableHeaderColor: NSColor(rgb: 0xf0e7d4), + controlColor: NSColor(rgb: 0xddd1b8), + imageTintColor: nil, + overlayPanelColor: NSColor(rgb: 0xf8f1e2) +) + +private let grayTheme = InstantPageTheme( + type: .gray, + pageBackgroundColor: NSColor(rgb: 0x5a5a5c), + textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xcecece)), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xcecece)), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xcecece)), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xcecece)), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xa0a0a0)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xa0a0a0)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xcecece)), + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xcecece)) + ), + serif: false, + codeBlockBackgroundColor: NSColor(rgb: 0x555556), + linkColor: NSColor(rgb: 0x5ac8fa), + textHighlightColor: NSColor(rgb: 0, alpha: 0.16), + linkHighlightColor: NSColor(rgb: 0x5ac8fa, alpha: 0.13), + markerColor: NSColor(rgb: 0x4b4b4b), + panelBackgroundColor: NSColor(rgb: 0x555556), + panelHighlightedBackgroundColor: NSColor(rgb: 0x505051), + panelPrimaryColor: NSColor(rgb: 0xcecece), + panelSecondaryColor: NSColor(rgb: 0xa0a0a0), + panelAccentColor: NSColor(rgb: 0x54b9f8), + tableBorderColor: NSColor(rgb: 0x484848), + tableHeaderColor: NSColor(rgb: 0x555556), + controlColor: NSColor(rgb: 0x484848), + imageTintColor: NSColor(rgb: 0xcecece), + overlayPanelColor: NSColor(rgb: 0x5a5a5c) +) + +private let darkTheme = InstantPageTheme( + type: .dark, + pageBackgroundColor: NSColor(rgb: 0x000000), + textCategories: InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xb0b0b0)), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xb0b0b0)), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: NSColor(rgb: 0xb0b0b0)), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xb0b0b0)), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x6a6a6a)), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0x6a6a6a)), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xb0b0b0)), + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: NSColor(rgb: 0xb0b0b0)) + ), + serif: false, + codeBlockBackgroundColor: NSColor(rgb: 0x131313), + linkColor: NSColor(rgb: 0x5ac8fa), + textHighlightColor: NSColor(rgb: 0xffffff, alpha: 0.1), + linkHighlightColor: NSColor(rgb: 0x5ac8fa, alpha: 0.2), + markerColor: NSColor(rgb: 0x313131), + panelBackgroundColor: NSColor(rgb: 0x131313), + panelHighlightedBackgroundColor: NSColor(rgb: 0x1f1f1f), + panelPrimaryColor: NSColor(rgb: 0xb0b0b0), + panelSecondaryColor: NSColor(rgb: 0x6a6a6a), + panelAccentColor: NSColor(rgb: 0x50b6f3), + tableBorderColor: NSColor(rgb: 0x303030), + tableHeaderColor: NSColor(rgb: 0x131313), + controlColor: NSColor(rgb: 0x303030), + imageTintColor: NSColor(rgb: 0xb0b0b0), + overlayPanelColor: NSColor(rgb: 0x232323) +) + +private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat { + switch variant { + case .small: + return 0.85 + case .standard: + return 1.0 + case .large: + return 1.15 + case .xlarge: + return 1.3 + case .xxlarge: + return 1.5 + } +} + +func instantPageThemeTypeForSettingsAndTime(settings: InstantViewAppearance, time: Date?) -> InstantPageThemeType { + + return .dark + +// switch theme.colors.name { +// +// } + +// if settings.autoNightMode { +// switch settings.themeType { +// case .light, .sepia, .gray: +// var useDarkTheme = false +// /*switch presentationTheme.name { +// case let .builtin(name): +// switch name { +// case .nightAccent, .nightGrayscale: +// useDarkTheme = true +// default: +// break +// } +// default: +// break +// }*/ +// if let time = time { +// let calendar = Calendar.current +// let hour = calendar.component(.hour, from: time) +// if hour <= 8 || hour >= 22 { +// useDarkTheme = true +// } +// } +// if useDarkTheme { +// return .dark +// } +// case .dark: +// break +// } +// } +// +// return settings.themeType +} + +func instantPageThemeForType(_ type: InstantPageThemeType, settings: InstantViewAppearance) -> InstantPageTheme { + switch type { + case .light: + return lightTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(.standard), forceSerif: settings.fontSerif) + case .sepia: + return sepiaTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(.standard), forceSerif: settings.fontSerif) + case .gray: + return grayTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(.standard), forceSerif: settings.fontSerif) + case .dark: + return darkTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(.standard), forceSerif: settings.fontSerif) + } +} + diff --git a/Telegram-Mac/InstantPageTile.swift b/Telegram-Mac/InstantPageTile.swift index 3fb408b587..262660a982 100644 --- a/Telegram-Mac/InstantPageTile.swift +++ b/Telegram-Mac/InstantPageTile.swift @@ -34,11 +34,12 @@ final class InstantPageTile { } func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFloat) -> [InstantPageTile] { - var tileByOrigin: [Int: InstantPageTile] = [:] + var tileByOrigin: [Int : InstantPageTile] = [:] let tileHeight: CGFloat = 256.0 + var tileHoles: [CGRect] = [] for item in layout.items { - if !item.wantsNode { + if !item.wantsView { let topTileIndex = max(0, Int(floor(item.frame.minY - 10.0) / tileHeight)) let bottomTileIndex = max(topTileIndex, Int(floor(item.frame.maxY + 10.0) / tileHeight)) for i in topTileIndex ... bottomTileIndex { @@ -51,10 +52,45 @@ func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFl } tile.items.append(item) } + } else if item.separatesTiles { + tileHoles.append(item.frame) } } - return tileByOrigin.values.sorted(by: { lhs, rhs in + var finalTiles: [InstantPageTile] = [] + var usedTiles = Set() + + for hole in tileHoles { + let topTileIndex = max(0, Int(floor(hole.minY - 10.0) / tileHeight)) + let bottomTileIndex = max(topTileIndex, Int(floor(hole.maxY + 10.0) / tileHeight)) + for i in topTileIndex ... bottomTileIndex { + if let tile = tileByOrigin[i] { + if tile.frame.minY > hole.minY && tile.frame.minY < hole.maxY { + let delta = hole.maxY - tile.frame.minY + let updatedTile = InstantPageTile(frame: CGRect(origin: tile.frame.origin.offsetBy(dx: 0.0, dy: delta), size: CGSize(width: tile.frame.width, height: tile.frame.height - delta))) + updatedTile.items.append(contentsOf: tile.items) + finalTiles.append(updatedTile) + usedTiles.insert(i) + } else if tile.frame.maxY > hole.minY && tile.frame.minY < hole.minY { + let delta = tile.frame.maxY - hole.minY + let updatedTile = InstantPageTile(frame: CGRect(origin: tile.frame.origin, size: CGSize(width: tile.frame.width, height: tile.frame.height - delta))) + updatedTile.items.append(contentsOf: tile.items) + finalTiles.append(updatedTile) + usedTiles.insert(i) + } + } + } + //let holeTile = InstantPageTile(frame: hole) + //finalTiles.append(holeTile) + } + + for (index, tile) in tileByOrigin { + if !usedTiles.contains(index) { + finalTiles.append(tile) + } + } + + return finalTiles.sorted(by: { lhs, rhs in return lhs.frame.minY < rhs.frame.minY }) } diff --git a/Telegram-Mac/InstantPageTileView.swift b/Telegram-Mac/InstantPageTileView.swift index 3ef7a54860..fdc4ca6c6b 100644 --- a/Telegram-Mac/InstantPageTileView.swift +++ b/Telegram-Mac/InstantPageTileView.swift @@ -14,12 +14,18 @@ import TGUIKit final class InstantPageTileView: View { private let tile: InstantPageTile - init(tile: InstantPageTile) { + init(tile: InstantPageTile, backgroundColor: NSColor) { self.tile = tile super.init() - super.backgroundColor = theme.colors.background + super.backgroundColor = backgroundColor + // layerContentsRedrawPolicy = .never } + func redrawTile() { +// layerContentsRedrawPolicy = .onSetNeedsDisplay +// display() +// layerContentsRedrawPolicy = .never + } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/InstantPageViewController.swift b/Telegram-Mac/InstantPageViewController.swift index 0151359855..c7a5b2cbf1 100644 --- a/Telegram-Mac/InstantPageViewController.swift +++ b/Telegram-Mac/InstantPageViewController.swift @@ -8,64 +8,15 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - -class InstantPageModalBrowser : ModalViewController { - - private let navigation: NavigationViewController - - init(_ page: InstantPageViewController) { - self.navigation = NavigationViewController(page) - page._frameRect = NSMakeRect(0, 0, 400, 365) - navigation._frameRect = NSMakeRect(0, 0, 400, 400) - super.init(frame: page._frameRect) - - } - - override func escapeKeyAction() -> KeyHandlerResult { - if navigation.controller == navigation.empty { - return .rejected - } - navigation.back() - return .invoked - } - - var currentInstantController:InstantPageViewController { - return navigation.controller as! InstantPageViewController - } - - override func viewDidLoad() { - super.viewDidLoad() - self.ready.set(currentInstantController.ready.get()) - } - - - override var handleEvents: Bool { - return true - } - - override var dynamicSize: Bool { - return true - } - - override func measure(size: NSSize) { - updateSize(size) - } - - private func updateSize(_ size: NSSize) { - self.modal?.resize(with:NSMakeSize(min(size.width - 120, 600), min(size.height - 40, currentInstantController.genericView.documentSize.height)), animated: false) - } - - override func initializer() -> NSView { - return navigation.view - } -} +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class InstantPageViewController: TelegramGenericViewController { + var pageDidScrolled:(((documentSize: NSSize, position: ScrollPosition))->Void)? var currentLayout: InstantPageLayout? @@ -85,11 +36,27 @@ class InstantPageViewController: TelegramGenericViewController { private var selectManager: InstantPageSelectText? private let joinDisposable = MetaDisposable() - private let openPeerInfoDisposable = MetaDisposable() private let mediaDisposable = MetaDisposable() - private var appearance: InstantViewAppearance = InstantViewAppearance.defaultSettings private let actualizeDisposable = MetaDisposable() + private let loadStoredStateDisposable = MetaDisposable() + private let saveProgressDisposable = MetaDisposable() + private let loadWebpageDisposable = MetaDisposable() + private let updateLayoutDisposable = MetaDisposable() + private let appearanceDisposable = MetaDisposable() + private var initialAnchor: String? + private var pendingAnchor: String? + private var initialState: InstantPageStoredState? + + private let loadProgress = ValuePromise(0.00, ignoreRepeated: true) + var progressSignal: Signal { + return loadProgress.get() + } + + + var currentWebEmbedHeights: [Int : CGFloat] = [:] + var currentExpandedDetails: [Int : Bool]? + var currentDetailsItems: [InstantPageDetailsItem] = [] var webPage: TelegramMediaWebpage { didSet { @@ -102,16 +69,17 @@ class InstantPageViewController: TelegramGenericViewController { } } let message: String? - init(_ account: Account, webPage: TelegramMediaWebpage, message: String?) { + init(_ context: AccountContext, webPage: TelegramMediaWebpage, message: String?, messageId: MessageId? = nil, anchor: String? = nil, saveToRecent: Bool = true) { self.webPage = webPage self.message = message + self.pendingAnchor = anchor switch webPage.content { case .Loaded(let content): self.instantPage = content.instantPage default: break } - super.init(account) + super.init(context) bar = .init(height: 0) noticeResizeWhenLoaded = false } @@ -119,36 +87,68 @@ class InstantPageViewController: TelegramGenericViewController { override var defaultBarTitle: String { switch webPage.content { case .Loaded(let content): - return content.title ?? super.defaultBarTitle + return content.websiteName ?? super.defaultBarTitle default: return super.defaultBarTitle } } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - reloadData() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) } - private func updateLayout() { - - let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: frame.width, presentation: appearance, openChannel: { [weak self] channel in - if let account = self?.account { - self?.account.context.mainNavigation?.push(ChatController(account: account, peerId: channel.id)) - self?.closeModal() + var currentState: InstantPageStoredState { + var details: [InstantPageStoredDetailsState] = [] + if let currentExpandedDetails = self.currentExpandedDetails { + for (index, expanded) in currentExpandedDetails { + details.append(InstantPageStoredDetailsState(index: Int32(clamping: index), expanded: expanded, details: [])) } - }, joinChannel: { [weak self] channel in - if let strongSelf = self, let window = self?.window { - strongSelf.joinDisposable.set(showModalProgress(signal: joinChannel(account: strongSelf.account, peerId: channel.id), for: window).start(next: { [weak strongSelf] in - strongSelf?.updateLayout() - strongSelf?.containerLayoutUpdated(transition: .immediate) - })) + } + return InstantPageStoredState(contentOffset: Double(self.genericView.contentOffset.y), details: details) + } + + + func updateWebPage(_ webPage: TelegramMediaWebpage, anchor: String?, state: InstantPageStoredState? = nil) { + if self.webPage != webPage { + + self.webPage = webPage + if let anchor = anchor { + self.initialAnchor = anchor.removingPercentEncoding + } else if let state = state { + self.initialState = state + if !state.details.isEmpty { + var storedExpandedDetails: [Int: Bool] = [:] + for state in state.details { + storedExpandedDetails[Int(clamping: state.index)] = state.expanded + } + self.currentExpandedDetails = storedExpandedDetails + } } - }) + self.currentLayout = nil + self.updateLayout() + + if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, instantPage.isComplete { + self.loadProgress.set(1.0) + + if let anchor = self.pendingAnchor { + self.pendingAnchor = nil + self.scrollToAnchor(anchor) + } + } + reloadData() + } + self.readyOnce() + } + + + private func updateLayout() { + let currentLayout = instantPageLayoutForWebPage(webPage, boundingWidth: max(500, frame.width), safeInset: 0, theme: instantPageThemeForType(theme.insantPageThemeType, settings: appearance), webEmbedHeights: self.currentWebEmbedHeights) + + updateInteractions() - for (_, tileNode) in self.visibleTiles { - tileNode.removeFromSuperview() + for (_, tileView) in self.visibleTiles { + tileView.removeFromSuperview() } self.visibleTiles.removeAll() @@ -159,12 +159,18 @@ class InstantPageViewController: TelegramGenericViewController { let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: frame.width) + + var currentDetailsItems: [InstantPageDetailsItem] = [] var currentLayoutItemsWithViews: [InstantPageItem] = [] var currentLayoutItemsWithLinks: [InstantPageItem] = [] var distanceThresholdGroupCount: [Int: Int] = [:] + var expandedDetails: [Int : Bool] = [:] + var detailsIndex = -1 + + for item in currentLayout.items { - if item.wantsNode { + if item.wantsView { currentLayoutItemsWithViews.append(item) if let group = item.distanceThresholdGroup() { let count: Int @@ -175,14 +181,32 @@ class InstantPageViewController: TelegramGenericViewController { } distanceThresholdGroupCount[Int(group)] = count + 1 } + if item.hasLinks { + currentLayoutItemsWithLinks.append(item) + } + if let detailsItem = item as? InstantPageDetailsItem { + detailsIndex += 1 + expandedDetails[detailsIndex] = detailsItem.initiallyExpanded + currentDetailsItems.append(detailsItem) + } } - if item.hasLinks { - currentLayoutItemsWithLinks.append(item) + + } + + if var currentExpandedDetails = self.currentExpandedDetails { + for (index, expanded) in expandedDetails { + if currentExpandedDetails[index] == nil { + currentExpandedDetails[index] = expanded + } } + self.currentExpandedDetails = currentExpandedDetails + } else { + self.currentExpandedDetails = expandedDetails } self.currentLayout = currentLayout self.currentLayoutTiles = currentLayoutTiles + self.currentDetailsItems = currentDetailsItems self.currentLayoutItemsWithViews = currentLayoutItemsWithViews self.currentLayoutItemsWithLinks = currentLayoutItemsWithLinks self.distanceThresholdGroupCount = distanceThresholdGroupCount @@ -193,28 +217,260 @@ class InstantPageViewController: TelegramGenericViewController { override func viewDidResized(_ size: NSSize) { super.viewDidResized(size) + reloadData() } private func reloadData() { updateLayout() - self.containerLayoutUpdated(transition: .immediate) + self.containerLayoutUpdated(animated: false) updateInteractions() } + func isExpandedItem(_ item: InstantPageDetailsItem) -> Bool { + if let index = self.currentDetailsItems.firstIndex(where: {$0 === item}) { + return self.currentExpandedDetails?[index] ?? item.initiallyExpanded + } else { + return false + } + } + + override var supportSwipes: Bool { + return false + } + + override var window: Window? { + if isLoaded() { + return self.view.window as? Window + } else { + return nil + } + } + func updateInteractions() { if let window = window, let layout = currentLayout, let instantPage = instantPage { - selectManager?.initializeHandlers(for: window, instantLayout: layout, instantPage: instantPage, account: account, updateLayout: { [weak self] in - self?.updateVisibleItems() - }, openInfo: { [weak self] peerId, openChat, postId, action in - self?.openInfo(peerId, openChat, postId, action) - self?.modal?.close() - }, openNewTab: { [weak self] mediaId, url in - self?.openNewTab(mediaId, url) + selectManager?.initializeHandlers(for: window, instantLayout: layout, instantPage: instantPage, context: context, updateLayout: { [weak self] in + guard let `self` = self else {return} + + self.updateVisibleItems(visibleBounds: self.genericView.contentView.bounds, animated: false) + }, openUrl: { [weak self] url in + self?.openUrl(url) + }, itemsInRect: { [weak self] rect in + guard let `self` = self, let currentLayout = self.currentLayout else { return [] } + return currentLayout.items.filter{rect.intersects(self.effectiveFrameForItem($0))} + }, effectiveRectForItem: { [weak self] item in + guard let `self` = self else { return NSZeroRect } + return self.effectiveFrameForItem(item) }) } } + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { + + + let currentHeight = self.currentWebEmbedHeights[index] + if height != currentHeight { + if let currentHeight = currentHeight, currentHeight > height { + return + } + self.currentWebEmbedHeights[index] = height + + let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in + if let strongSelf = self { + NSLog("\(strongSelf.currentWebEmbedHeights)") + + strongSelf.reloadData() + strongSelf.updateVisibleItems(visibleBounds: strongSelf.genericView.contentView.bounds, animated: false) + } + })) + } + } + + private func openUrl(_ url: InstantPageUrlItem) { + var baseUrl = url.url + var anchor: String? + if let anchorRange = url.url.range(of: "#") { + anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding + baseUrl = String(baseUrl[.. deliverOnMainQueue).start(next: { [weak self] result in + guard let `self` = self else {return} + + switch result { + case let .result(webpage): + if let webpage = webpage, case .Loaded = webpage.content { + self.loadProgress.set(1.0) + showInstantPage(InstantPageViewController(self.context, webPage: webpage, message: nil, anchor: anchor)) + } + break + case let .progress(progress): + self.loadProgress.set(CGFloat(0.07 + progress * (1.0 - 0.07))) + } + })) + } else { + loadProgress.set(1.0) + execute(inapp: result) + } + default: + self.loadProgress.set(1.0) + execute(inapp: result) + } + } + + private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect { + let layoutOrigin = tile.frame.origin + var origin = layoutOrigin + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + if layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + return CGRect(origin: origin, size: tile.frame.size) + } + + private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect { + let layoutOrigin = item.frame.origin + var origin = layoutOrigin + + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + if layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + + if let item = item as? InstantPageDetailsItem { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height)) + } else { + return CGRect(origin: origin, size: item.frame.size) + } + } + + private func viewForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsView? { + for (_, itemView) in self.visibleItemsWithViews { + if let detailsView = itemView as? InstantPageDetailsView, detailsView.item === item { + return detailsView + } + } + return nil + } + + private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize { + if let view = viewForDetailsItem(item) { + return CGSize(width: item.frame.width, height: view.effectiveContentSize.height + item.titleHeight) + } else { + return item.frame.size + } + } + + private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, Bool, [InstantPageDetailsItem])? { + for item in items { + if let item = item as? InstantPageAnchorItem, item.anchor == anchor { + return (item, -10.0, false, []) + } else if let item = item as? InstantPageTextItem { + if let (lineIndex, empty) = item.anchors[anchor] { + return (item, item.lines[lineIndex].frame.minY - 10.0, !empty, []) + } + } + else if let item = item as? InstantPageTableItem { + if let (offset, empty) = item.anchors[anchor] { + return (item, offset - 10.0, !empty, []) + } + } + else if let item = item as? InstantPageDetailsItem { + if let (foundItem, offset, reference, detailsItems) = self.findAnchorItem(anchor, items: item.items) { + var detailsItems = detailsItems + detailsItems.insert(item, at: 0) + return (foundItem, offset, reference, detailsItems) + } + } + } + return nil + } + + + private func scrollToAnchor(_ anchor: String, animated: Bool = true) { + guard let items = self.currentLayout?.items else { + return + } + + if !anchor.isEmpty { + if let (item, lineOffset, reference, detailsItems) = findAnchorItem(String(anchor), items: items) { + var previousDetailsView: InstantPageDetailsView? + var containerOffset: CGFloat = 0.0 + for detailsItem in detailsItems { + if let previousView = previousDetailsView { + previousView.contentView.updateDetailsExpanded(detailsItem.index, true, animated: false) + let frame = previousView.effectiveFrameForItem(detailsItem) + containerOffset += frame.minY + + previousDetailsView = previousView.contentView.viewForDetailsItem(detailsItem) + previousDetailsView?.setExpanded(true, animated: false) + } else { + self.updateDetailsExpanded(detailsItem.index, true, animated: false) + let frame = self.effectiveFrameForItem(detailsItem) + containerOffset += frame.minY + + previousDetailsView = self.viewForDetailsItem(detailsItem) + previousDetailsView?.setExpanded(true, animated: false) + } + } + + let frame: CGRect + if let previousDetailsView = previousDetailsView { + frame = previousDetailsView.effectiveFrameForItem(item) + } else { + frame = self.effectiveFrameForItem(item) + } + + let targetY = min(containerOffset + frame.minY + (reference ? -5 : lineOffset), self.documentView.frame.height - self.genericView.frame.height) + genericView.clipView.scroll(to: CGPoint(x: 0.0, y: targetY), animated: animated) + } else if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, !instantPage.isComplete { + // self.loadProgress.set(0.5) + self.pendingAnchor = anchor + } + } else { + genericView.clipView.scroll(to: NSZeroPoint, animated: animated) + } + + + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) updateInteractions() @@ -226,43 +482,61 @@ class InstantPageViewController: TelegramGenericViewController { func openInfo(_ peerId:PeerId, _ openChat: Bool, _ postId:MessageId?, _ action:ChatInitialAction?) { if openChat { - account.context.mainNavigation?.push(ChatController(account: account, peerId: peerId, messageId: postId, initialAction: action)) + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId), messageId: postId, initialAction: action)) closeModal() } else { - openPeerInfoDisposable.set((account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.account.context.mainNavigation?.push(PeerInfoController(account: strongSelf.account, peer: peer)) - strongSelf.closeModal() - } - })) + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + closeModal() } } - func openNewTab(_ mediaId: MediaId, _ url: String) { - let getMedia = account.postbox.modify { modifier -> Media? in - return modifier.getMedia(mediaId) - } |> deliverOnMainQueue - mediaDisposable.set(getMedia.start(next: { [weak self] media in - if let media = media as? TelegramMediaWebpage, let strongSelf = self { - strongSelf.navigationController?.push(InstantPageViewController(strongSelf.account, webPage: media, message: nil)) - } else { - execute(inapp: .external(link: url, false)) - } - })) - } - +// func openNewTab(_) { +// let getMedia = account.postbox.transaction { transaction -> Media? in +// return transaction.getMedia(mediaId) +// } |> deliverOnMainQueue +// mediaDisposable.set(getMedia.start(next: { [weak self] media in +// if let media = media as? TelegramMediaWebpage, let strongSelf = self, case let .Loaded(content) = media.content, let page = content.instantPage { +// strongSelf.navigationController?.push(InstantPageViewController(strongSelf.account, webPage: media, message: nil)) +// } else if let window = self?.window, let account = self?.account { +// self.loadProgress.set(0.02) +// _ = (webpagePreviewWithProgress(account: account, url: url, webpageId: mediaId) |> deliverOnMainQueue).start(next: { result in +// +// if let page = page { +// switch page.content { +// case let .Loaded(content): +// if let _ = content.instantPage { +// showInstantPage(InstantPageViewController(account, webPage: page, message: nil)) +// } else { +// execute(inapp: .external(link: url, false)) +// } +// default: +// execute(inapp: .external(link: url, false)) +// } +// } else { +// execute(inapp: .external(link: url, false)) +// } +// }) +// } else { +// execute(inapp: .external(link: url, false)) +// } +// })) +// } +// + override func viewDidLoad() { - view.background = theme.colors.background + + + genericView.deltaCorner = -1 genericView.documentView = View(frame: genericView.bounds) NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: genericView.contentView, queue: nil, using: { [weak self] _ in - if let strongSelf = self { - strongSelf.updateVisibleItems() - strongSelf.pageDidScrolled?((documentSize: strongSelf.genericView.frame.size, position: strongSelf.genericView.scrollPosition().current)) - } + guard let `self` = self else { return } + self.updateVisibleItems(visibleBounds: self.genericView.contentView.bounds, animated: false) + self.pageDidScrolled?((documentSize: self.genericView.frame.size, position: self.genericView.scrollPosition().current)) + self.saveArticleProgress() }) genericView.hasVerticalScroller = true selectManager = InstantPageSelectText(genericView) @@ -270,16 +544,12 @@ class InstantPageViewController: TelegramGenericViewController { var firstLoad: Bool = true - ready.set((ivAppearance(postbox: account.postbox) |> deliverOnMainQueue |> map { [weak self] appearance -> Bool in + appearanceDisposable.set((ivAppearance(postbox: context.account.postbox) |> deliverOnMainQueue).start(next: { [weak self] appearance in self?.appearance = appearance self?.reloadData() - - if firstLoad, let currentLayout = self?.currentLayout, let webPage = self?.webPage, let message = self?.message, let scrollView = self?.genericView { + if firstLoad, let currentLayout = self?.currentLayout, let webPage = self?.webPage, let scrollView = self?.genericView { firstLoad = false - - if let mediaId = webPage.id, let state = appearance.state[mediaId] { - self?.applyScrollState(state) - } else { + if let message = self?.message { switch webPage.content { case .Loaded(let content): @@ -289,7 +559,7 @@ class InstantPageViewController: TelegramGenericViewController { let range = message.nsstring.range(of: content.url) if range.location != NSNotFound { - if let link = attr.attribute(NSAttributedStringKey.link, at: range.location, effectiveRange: nil) as? inAppLink { + if let link = attr.attribute(NSAttributedString.Key.link, at: range.location, effectiveRange: nil) as? inAppLink { switch link { case let .external(url, _): let anchorRange = url.nsstring.range(of: "#") @@ -300,7 +570,6 @@ class InstantPageViewController: TelegramGenericViewController { if item.matchesAnchor(anchor) { scrollView.clipView.scroll(to: item.frame.origin, animated: false) scrollView.reflectScrolledClipView(scrollView.clipView) - break } } } @@ -315,30 +584,41 @@ class InstantPageViewController: TelegramGenericViewController { break } } + if let mediaId = webPage.id, let state = appearance.state[mediaId] { + self?.applyScrollState(state) + } } - - return true })) - actualizeDisposable.set((actualizedWebpage(postbox: account.postbox, network: account.network, webpage: webPage) |> delay(1.0, queue: Queue.mainQueue()) |> deliverOnMainQueue).start(next: { [weak self] webpage in - switch webpage.content { - case .Loaded(let content): - if content.instantPage != nil { - self?.webPage = webpage - self?.reloadData() - } - default: - break + + loadStoredStateDisposable.set((instantPageStoredState(postbox: context.account.postbox, webPage: webPage) |> deliverOnMainQueue).start(next: { [weak self] state in + guard let `self` = self else { + return } - + self.updateWebPage(self.webPage, anchor: self.pendingAnchor, state: state) + if let anchor = self.pendingAnchor { + self.pendingAnchor = nil + self.scrollToAnchor(anchor, animated: false) + } + })) + + actualizeDisposable.set((actualizedWebpage(postbox: context.account.postbox, network: context.account.network, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] webpage in + self?.updateWebPage(webpage, anchor: self?.pendingAnchor) })) } + + override func readyOnce() { + if !didSetReady { + loadProgress.set(1.0) + } + super.readyOnce() + } - func containerLayoutUpdated(transition: ContainedViewLayoutTransition) { + func containerLayoutUpdated(animated: Bool) { if visibleItemsWithViews.isEmpty && visibleTiles.isEmpty { genericView.contentView.bounds = NSZeroRect } - self.updateVisibleItems() + self.updateVisibleItems(visibleBounds: self.genericView.contentView.bounds, animated: animated) } override var enableBack: Bool { @@ -348,40 +628,65 @@ class InstantPageViewController: TelegramGenericViewController { return false } - private var documentView: View { - return genericView.documentView as! View + private var documentView: NSView { + return genericView.documentView! + } + + + private func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true) { + if var currentExpandedDetails = self.currentExpandedDetails { + currentExpandedDetails[index] = expanded + self.currentExpandedDetails = currentExpandedDetails + } + self.updateVisibleItems(visibleBounds: self.genericView.contentView.bounds, animated: animated) } - func updateVisibleItems() { + func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) { + + + CATransaction.begin() + + defer { + CATransaction.commit() + } + var visibleTileIndices = Set() var visibleItemIndices = Set() - let visibleBounds = genericView.contentView.bounds - var tileIndex = -1 - for tile in self.currentLayoutTiles { - tileIndex += 1 - var tileVisibleFrame = tile.frame - tileVisibleFrame.origin.y -= 400.0 - tileVisibleFrame.size.height += 400.0 * 2.0 - if tileVisibleFrame.intersects(visibleBounds) { - visibleTileIndices.insert(tileIndex) - - if let tile = visibleTiles[tileIndex] { - tile.needsDisplay = true - } else { - let tileNode = InstantPageTileView(tile: tile) - tileNode.frame = tile.frame - documentView.addSubview(tileNode) - self.visibleTiles[tileIndex] = tileNode - } - + var topView: NSView? + let topTileView = topView + for view in documentView.subviews.reversed() { + if let view = view as? InstantPageTileView { + topView = view + break } } + let visibleBounds = genericView.contentView.bounds + + + + var collapseOffset: CGFloat = 0.0 + + var itemIndex = -1 + var embedIndex = -1 + var detailsIndex = -1 + + var previousDetailsView: InstantPageDetailsView? + + for item in self.currentLayoutItemsWithViews { itemIndex += 1 + + if item is InstantPageWebEmbedItem { + embedIndex += 1 + } + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + var itemThreshold: CGFloat = 0.0 if let group = item.distanceThresholdGroup() { var count: Int = 0 @@ -390,42 +695,135 @@ class InstantPageViewController: TelegramGenericViewController { } itemThreshold = item.distanceThresholdWithGroupCount(count) } - var itemFrame = item.frame - itemFrame.origin.y -= itemThreshold - itemFrame.size.height += itemThreshold * 2.0 - if visibleBounds.intersects(itemFrame) { + + + var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset) + var thresholdedItemFrame = itemFrame + thresholdedItemFrame.origin.y -= itemThreshold + thresholdedItemFrame.size.height += itemThreshold * 2.0 + + if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] { + let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight + collapseOffset += itemFrame.height - height + itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height)) + } + + if visibleBounds.intersects(thresholdedItemFrame) { visibleItemIndices.insert(itemIndex) - var itemNode = self.visibleItemsWithViews[itemIndex] - if let currentItemNode = itemNode { - if !item.matchesNode(currentItemNode) { - (currentItemNode as! View).removeFromSuperview() + var itemView = self.visibleItemsWithViews[itemIndex] + if let currentItemView = itemView { + if !item.matchesView(currentItemView) { + (currentItemView as! NSView).removeFromSuperview() self.visibleItemsWithViews.removeValue(forKey: itemIndex) - itemNode = nil + itemView = nil } } - if itemNode == nil { - if let itemNode = item.node(account: self.account) { - (itemNode as! View).frame = item.frame - documentView.addSubview(itemNode as! View) - self.visibleItemsWithViews[itemIndex] = itemNode + if itemView == nil { + let embedIndex = embedIndex + let detailsIndex = detailsIndex + + let arguments = InstantPageItemArguments(context: context, theme: instantPageThemeForType(theme.insantPageThemeType, settings: appearance), openMedia: { media in + + }, openPeer: { peerId in + + }, openUrl: { [weak self] url in + self?.openUrl(url) + }, updateWebEmbedHeight: { [weak self] height in + self?.updateWebEmbedHeight(embedIndex, height) + }, updateDetailsExpanded: { [weak self] expanded in + self?.updateDetailsExpanded(detailsIndex, expanded) + }, isExpandedItem: { [weak self] item in + return self?.isExpandedItem(item) ?? false + }, effectiveRectForItem: { [weak self] item in + return self?.effectiveFrameForItem(item) ?? item.frame + }) + + if let newView = item.view(arguments: arguments, currentExpandedDetails: self.currentExpandedDetails) { + newView.frame = itemFrame + documentView.addSubview(newView) + topView = newView + self.visibleItemsWithViews[itemIndex] = newView + itemView = newView + + if let itemView = itemView as? InstantPageDetailsView { + itemView.requestLayoutUpdate = { [weak self] animated in + if let strongSelf = self { + strongSelf.updateVisibleItems(visibleBounds: strongSelf.genericView.contentView.bounds, animated: animated) + } + } + + if let previousDetailsView = previousDetailsView { + if itemView.frame.minY - previousDetailsView.frame.maxY < 1.0 { + itemView.previousView = previousDetailsView + } + } + previousDetailsView = itemView + } + } } else { - (itemNode as! View).removeFromSuperview() - documentView.addSubview((itemNode as! View)) - if (itemNode as! View).frame != item.frame { - (itemNode as! View).frame = item.frame + if (itemView as! NSView).frame != itemFrame { + (itemView as! NSView)._change(size: itemFrame.size, animated: animated) + (itemView as! NSView)._change(pos: itemFrame.origin, animated: animated) + } else { + (itemView as! NSView).needsDisplay = true } } + + if let itemView = itemView as? InstantPageDetailsView { + itemView.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemView.frame.minX, dy: -itemView.frame.minY), animated: animated) + } } } + topView = topTileView + + var tileIndex = -1 + for tile in self.currentLayoutTiles { + tileIndex += 1 + + let tileFrame = effectiveFrameForTile(tile) + var tileVisibleFrame = tileFrame + tileVisibleFrame.origin.y -= 800.0 + tileVisibleFrame.size.height += 800.0 * 2.0 + if tileVisibleFrame.intersects(visibleBounds) { + visibleTileIndices.insert(tileIndex) + + if self.visibleTiles[tileIndex] == nil { + let tileView = InstantPageTileView(tile: tile, backgroundColor: .clear) + tileView.frame = tileFrame + documentView.addSubview(tileView) + topView = tileView + self.visibleTiles[tileIndex] = tileView + } else { + if visibleTiles[tileIndex]!.frame != tileFrame { + let view = self.visibleTiles[tileIndex]! + view._change(pos: tileFrame.origin, animated: animated) + view._change(size: tileFrame.size, animated: animated) + } else { + visibleTiles[tileIndex]!.needsDisplay = true + } + } + } else { + var bp:Int = 0 + bp += 1 + } + } + + if let currentLayout = self.currentLayout { + let effectiveContentHeight = currentLayout.contentSize.height - collapseOffset + if effectiveContentHeight != self.genericView.contentSize.height { + documentView.setFrameSize(CGSize(width: currentLayout.contentSize.width, height: effectiveContentHeight)) + } + } + var removeTileIndices: [Int] = [] - for (index, tileNode) in self.visibleTiles { + for (index, tileView) in self.visibleTiles { if !visibleTileIndices.contains(index) { removeTileIndices.append(index) - tileNode.removeFromSuperview() + tileView.removeFromSuperview() } } for index in removeTileIndices { @@ -433,24 +831,29 @@ class InstantPageViewController: TelegramGenericViewController { } var removeItemIndices: [Int] = [] - for (index, itemNode) in self.visibleItemsWithViews { + for (index, itemView) in self.visibleItemsWithViews { if !visibleItemIndices.contains(index) { removeItemIndices.append(index) - (itemNode as! View).removeFromSuperview() + (itemView as! NSView).removeFromSuperview() } else { - var itemFrame = (itemNode as! View).frame + var itemFrame = (itemView as! NSView).frame let itemThreshold: CGFloat = 200.0 itemFrame.origin.y -= itemThreshold itemFrame.size.height += itemThreshold * 2.0 - itemNode.updateIsVisible(visibleBounds.intersects(itemFrame)) + itemView.updateIsVisible(visibleBounds.intersects(itemFrame)) } } + let subviews = documentView.subviews.sorted(by: {$0.frame.minY < $1.frame.minY}) + documentView.subviews = subviews + documentView.needsLayout = true + for index in removeItemIndices { self.visibleItemsWithViews.removeValue(forKey: index) } } + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if let window = window { @@ -491,27 +894,68 @@ class InstantPageViewController: TelegramGenericViewController { private func applyScrollState(_ state: IVReadState) { if let currentLayout = currentLayout, Int32(currentLayout.items.count) > state.blockId, let scrollState = scrollState { let item = currentLayout.items[Int(state.blockId)] - let offset = CGPoint(x: 0, y: genericView.contentInsets.top + item.frame.origin.y + CGFloat(scrollState.blockOffset) - 8) + let offset = CGPoint(x: 0, y: genericView.contentInsets.top + item.frame.origin.y + CGFloat(scrollState.blockOffset)) genericView.clipView.scroll(to: offset, animated: false) genericView.reflectScrolledClipView(genericView.clipView) } } - + // Called when space button is enabled to trigger scrolling + enum Direction { + case up + case down + } + func scrollPage(direction: Direction) { + self.updateVisibleItems(visibleBounds: self.genericView.contentView.bounds, animated: false) + + var newOrigin = genericView.clipView.bounds.origin + switch direction { + case .up: + // without *2 we will stay at current position + newOrigin.y -= 50 + if newOrigin.y < 0 { + newOrigin.y = 0 + } + case .down: + newOrigin.y += 50 + let maxY = genericView.documentSize.height - genericView.clipView.frame.height + if newOrigin.y > maxY { + newOrigin.y = maxY + } + } + + genericView.clipView.scroll(to: newOrigin, animated: true) + genericView.reflectScrolledClipView(genericView.clipView) + pageDidScrolled?((documentSize: genericView.frame.size, position: genericView.scrollPosition().current)) + } + + private func saveArticleProgress() { + let point = CGPoint(x: genericView.frame.size.width / 2.0, y: genericView.contentOffset.y + genericView.contentInsets.top) + + let id = self.webPage.webpageId + + let percent = Int32((point.y + frame.height) / genericView.documentSize.height * 100.0) + } + deinit { NotificationCenter.default.removeObserver(self) selectManager?.removeHandlers(for: mainWindow) joinDisposable.dispose() actualizeDisposable.dispose() - openPeerInfoDisposable.dispose() + saveProgressDisposable.dispose() mediaDisposable.dispose() + updateLayoutDisposable.dispose() + loadWebpageDisposable.dispose() + appearanceDisposable.dispose() + loadStoredStateDisposable.dispose() if let window = window { selectManager?.removeHandlers(for: window) } if let state = scrollState, let mediaId = webPage.id { - _ = updateInstantViewAppearanceSettingsInteractively(postbox: account.postbox, {$0.withUpdatedIVState(state, for: mediaId)}).start() + // _ = updateInstantViewAppearanceSettingsInteractively(postbox: account.postbox, {$0.withUpdatedIVState(state, for: mediaId)}).start() } + } } diff --git a/Telegram-Mac/InstantPageWebEmbedItem.swift b/Telegram-Mac/InstantPageWebEmbedItem.swift index 4cf21bd544..6bf81bf130 100644 --- a/Telegram-Mac/InstantPageWebEmbedItem.swift +++ b/Telegram-Mac/InstantPageWebEmbedItem.swift @@ -7,18 +7,20 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore final class InstantPageWebEmbedItem: InstantPageItem { var frame: CGRect let hasLinks: Bool = false - let wantsNode: Bool = true + let wantsView: Bool = true let medias: [InstantPageMedia] = [] let url: String? let html: String? let enableScrolling: Bool - + let separatesTiles: Bool = false + let isInteractive: Bool = false init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool) { @@ -28,16 +30,19 @@ final class InstantPageWebEmbedItem: InstantPageItem { self.enableScrolling = enableScrolling } - func node(account: Account) -> InstantPageView? { - return instantPageWebEmbedView(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling) + func view(arguments: InstantPageItemArguments, currentExpandedDetails: [Int : Bool]?) -> (InstantPageView & NSView)? { + return InstantPageWebEmbedView(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: { height in + arguments.updateWebEmbedHeight(height) + + }) } func matchesAnchor(_ anchor: String) -> Bool { return false } - func matchesNode(_ node: InstantPageView) -> Bool { - if let node = node as? instantPageWebEmbedView { + func matchesView(_ node: InstantPageView) -> Bool { + if let node = node as? InstantPageWebEmbedView { return self.url == node.url && self.html == node.html } else { return false diff --git a/Telegram-Mac/InstantVideoPIP.swift b/Telegram-Mac/InstantVideoPIP.swift index 86f6ae7aa9..f6236ce9c5 100644 --- a/Telegram-Mac/InstantVideoPIP.swift +++ b/Telegram-Mac/InstantVideoPIP.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit enum InstantVideoPIPCornerAlignment { case topLeft @@ -19,7 +20,7 @@ enum InstantVideoPIPCornerAlignment { case bottomRight } -class InstantVideoPIPView : GIFContainerView { +class InstantVideoPIPView : GIFPlayerView { let playingProgressView: RadialProgressView = RadialProgressView(theme:RadialProgressTheme(backgroundColor: .clear, foregroundColor: NSColor.white.withAlphaComponent(0.8), lineWidth: 3), twist: false) override init() { @@ -28,6 +29,7 @@ class InstantVideoPIPView : GIFContainerView { required init(frame frameRect: NSRect) { super.init() + setFrameSize(NSMakeSize(200, 200)) playingProgressView.userInteractionEnabled = false } @@ -51,23 +53,34 @@ class InstantVideoPIPView : GIFContainerView { } class InstantVideoPIP: GenericViewController, APDelegate { - private let controller:APController + private var controller:APController + private var context: AccountContext private weak var tableView:TableView? private var listener:TableScrollListener! + private let dataDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private var currentMessage:Message? = nil private var scrollTime: TimeInterval = CFAbsoluteTimeGetCurrent() private var alignment:InstantVideoPIPCornerAlignment = .topRight private var isShown:Bool = false - init(_ controller:APController, window:Window) { + + private var timebase: CMTimebase? = nil { + didSet { + genericView.reset(with: timebase) + } + } + + init(_ controller:APController, context: AccountContext, window:Window) { self.controller = controller - + self.context = context super.init() listener = TableScrollListener({ [weak self] _ in self?.updateScrolled() }) controller.add(listener: self) - (controller.account.context.mainNavigation as? MajorNavigationController)?.add(listener: WeakReference(value: self)) + context.sharedContext.bindings.rootNavigation().add(listener: WeakReference(value: self)) } override var window:Window? { @@ -82,10 +95,10 @@ class InstantVideoPIP: GenericViewController, APDelegate { } override func navigationWillChangeController() { - if let controller = controller.account.context.mainNavigation?.controller as? ChatController { - updateTableView(controller.genericView.tableView) + if let controller = context.sharedContext.bindings.rootNavigation().controller as? ChatController { + updateTableView(controller.genericView.tableView, context: context, controller: self.controller) } else { - updateTableView(nil) + updateTableView(nil, context: context, controller: self.controller) } } @@ -128,15 +141,35 @@ class InstantVideoPIP: GenericViewController, APDelegate { if isShown { hide() } + dataDisposable.dispose() + fetchDisposable.dispose() } func showIfNeeded(animated: Bool = true) { loadViewIfNeeded() isShown = true - if let media = currentMessage?.media.first as? TelegramMediaFile { - let signal:Signal<(TransformImageArguments) -> DrawingContext?, NoError> - signal = chatWebpageSnippetPhoto(account: controller.account, photo: TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: media.previewRepresentations), scale: view.backingScaleFactor, small:true) - genericView.update(with: media.resource, size: NSMakeSize(150, 150), viewSize: NSMakeSize(150, 150), account: controller.account, table: nil, iconSignal: signal) + genericView.animatesAlphaOnFirstTransition = false + if let message = currentMessage, let media = message.media.first as? TelegramMediaFile { + let signal:Signal = chatMessageVideo(postbox: context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(message), media: media), scale: view.backingScaleFactor) + + let resource = FileMediaReference.message(message: MessageReference(message), media: media) + + let data: Signal = context.account.postbox.mediaBox.resourceData(resource.media.resource) |> map { resource in + if resource.complete { + return AVGifData.dataFrom(resource.path) + } else if let resource = media.resource as? LocalFileReferenceMediaResource { + return AVGifData.dataFrom(resource.localFilePath) + } else { + return nil + } + } |> deliverOnMainQueue + + genericView.setSignal(signal) + + dataDisposable.set(data.start(next: { [weak self] data in + self?.genericView.set(data: data, timebase: self?.timebase) + })) + } if let contentView = window?.contentView, genericView.superview == nil { @@ -230,7 +263,7 @@ class InstantVideoPIP: GenericViewController, APDelegate { point = NSMakePoint(-view.frame.width, 100) } - genericView.change(pos: point, animated: animated, completion: { [weak view] completed in + genericView._change(pos: point, animated: animated, completion: { [weak view] completed in view?.removeFromSuperview() }) } @@ -242,13 +275,13 @@ class InstantVideoPIP: GenericViewController, APDelegate { if let contentView = window?.contentView { switch corner { case .topRight: - genericView.change(pos: NSMakePoint(contentView.frame.width - view.frame.width - 20, contentView.frame.height - view.frame.height - 130), animated: animated) + genericView._change(pos: NSMakePoint(contentView.frame.width - view.frame.width - 20, contentView.frame.height - view.frame.height - 130), animated: animated) case .topLeft: - genericView.change(pos: NSMakePoint(20, contentView.frame.height - view.frame.height - 130), animated: animated) + genericView._change(pos: NSMakePoint(20, contentView.frame.height - view.frame.height - 130), animated: animated) case .bottomRight: - genericView.change(pos: NSMakePoint(contentView.frame.width - view.frame.width - 20, 100), animated: animated) + genericView._change(pos: NSMakePoint(contentView.frame.width - view.frame.width - 20, 100), animated: animated) case .bottomLeft: - genericView.change(pos: NSMakePoint(20, 100), animated: animated) + genericView._change(pos: NSMakePoint(20, 100), animated: animated) } } @@ -275,10 +308,16 @@ class InstantVideoPIP: GenericViewController, APDelegate { } } - func updateTableView(_ tableView:TableView?) { + func updateTableView(_ tableView:TableView?, context: AccountContext, controller: APController) { self.tableView?.removeScroll(listener: listener) self.tableView = tableView + self.context = context self.tableView?.addScroll(listener: listener) + if controller != self.controller { + self.controller = controller + controller.add(listener: self) + } + updateScrolled() } @@ -298,16 +337,16 @@ class InstantVideoPIP: GenericViewController, APDelegate { if let msg = msg { if let currentMessage = currentMessage, !isShown && currentMessage.id != msg.id, CFAbsoluteTimeGetCurrent() - scrollTime > 1.0 { if let item = tableView?.item(stableId: msg.chatStableId) { - tableView?.scroll(to: .center(id: item.stableId, animated: true, focus: false, inset: 0)) + tableView?.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0)) } } currentMessage = msg - genericView.timebase = controller.timebase +// genericView.reset(with: controller.timebase, false) } else { currentMessage = nil - genericView.player.reset(with: nil) + self.timebase = nil } updateScrolled() } @@ -326,12 +365,12 @@ class InstantVideoPIP: GenericViewController, APDelegate { } func songDidStopPlaying(song:APSongItem, for controller:APController) { if song.stableId == currentMessage?.chatStableId { - genericView.timebase = nil + self.timebase = nil } } func playerDidChangedTimebase(song:APSongItem, for controller:APController) { if song.stableId == currentMessage?.chatStableId { - genericView.timebase = controller.timebase + self.timebase = controller.timebase } } diff --git a/Telegram-Mac/InstantViewAppearance.swift b/Telegram-Mac/InstantViewAppearance.swift index 2d1f9aabf3..68a63a6e7d 100644 --- a/Telegram-Mac/InstantViewAppearance.swift +++ b/Telegram-Mac/InstantViewAppearance.swift @@ -8,10 +8,28 @@ import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit -public struct IVReadState : PostboxCoding { +public enum InstantPageThemeType: Int32 { + case light = 0 + case dark = 1 + case sepia = 2 + case gray = 3 +} + +public enum InstantPagePresentationFontSize: Int32 { + case small = 0 + case standard = 1 + case large = 2 + case xlarge = 3 + case xxlarge = 4 +} + + + + +public struct IVReadState : PostboxCoding, Equatable { let blockId:Int32 let blockOffset: Int32 public init(blockId:Int32, blockOffset: Int32) { @@ -27,6 +45,7 @@ public struct IVReadState : PostboxCoding { encoder.encodeInt32(blockId, forKey: "bi") encoder.encodeInt32(blockOffset, forKey: "bo") } + } @@ -61,7 +80,7 @@ public struct InstantViewAppearance: PreferencesEntry, Equatable { } public static func ==(lhs: InstantViewAppearance, rhs: InstantViewAppearance) -> Bool { - return lhs.fontSerif == rhs.fontSerif + return lhs.fontSerif == rhs.fontSerif && lhs.state == rhs.state } func withUpdatedFontSerif(_ fontSerif: Bool) -> InstantViewAppearance { @@ -76,8 +95,8 @@ public struct InstantViewAppearance: PreferencesEntry, Equatable { } func updateInstantViewAppearanceSettingsInteractively(postbox: Postbox, _ f: @escaping (InstantViewAppearance) -> InstantViewAppearance) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.instantViewAppearance, { entry in + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.instantViewAppearance, { entry in let currentSettings: InstantViewAppearance if let entry = entry as? InstantViewAppearance { currentSettings = entry @@ -89,7 +108,7 @@ func updateInstantViewAppearanceSettingsInteractively(postbox: Postbox, _ f: @es } } -func ivAppearance(postbox: Postbox) -> Signal { +func ivAppearance(postbox: Postbox) -> Signal { return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.instantViewAppearance]) |> map { preferences in return (preferences.values[ApplicationSpecificPreferencesKeys.instantViewAppearance] as? InstantViewAppearance) ?? InstantViewAppearance.defaultSettings } diff --git a/Telegram-Mac/InstantViewWindow.swift b/Telegram-Mac/InstantViewWindow.swift index cdff00701e..2157afc349 100644 --- a/Telegram-Mac/InstantViewWindow.swift +++ b/Telegram-Mac/InstantViewWindow.swift @@ -8,17 +8,18 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private final class InstantViewArguments { - let account: Account + let context: AccountContext let share:()->Void let back:()->Void let openInSafari:()->Void let enableSansSerif:(Bool)->Void - init(account: Account, share: @escaping()->Void, back: @escaping()->Void, openInSafari: @escaping()->Void, enableSansSerif:@escaping(Bool)->Void) { - self.account = account + init(context: AccountContext, share: @escaping()->Void, back: @escaping()->Void, openInSafari: @escaping()->Void, enableSansSerif:@escaping(Bool)->Void) { + self.context = context self.share = share self.back = back self.enableSansSerif = enableSansSerif @@ -60,7 +61,7 @@ private class HeaderView : View { } private func initialize() { - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) addSubview(borderView) addSubview(share) addSubview(actions) @@ -103,13 +104,13 @@ private class HeaderView : View { view.setFrameOrigin(view.frame.minX, view.frame.minY - 1) if animated { addSubview(view) - view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self, weak view] completed in + view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring, completion: { [weak self, weak view] completed in if completed { self?.titleView?.removeFromSuperview() self?.titleView = view } }) - titleView?.change(opacity: 0, timingFunction: kCAMediaTimingFunctionSpring) + titleView?.change(opacity: 0, timingFunction: CAMediaTimingFunctionName.spring) } else { titleView?.removeFromSuperview() titleView = view @@ -143,8 +144,9 @@ private class HeaderView : View { back.centerY(x: 86) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) borderView.backgroundColor = theme.colors.border backgroundColor = theme.colors.background titleView?.backgroundColor = theme.colors.background @@ -157,10 +159,10 @@ private class HeaderView : View { safari.set(image: theme.icons.instantViewSafari, for: .Normal) back.set(image: theme.icons.instantViewBack, for: .Normal) - share.sizeToFit() - actions.sizeToFit() - safari.sizeToFit() - back.sizeToFit() + _ = share.sizeToFit() + _ = actions.sizeToFit() + _ = safari.sizeToFit() + _ = back.sizeToFit() } required init?(coder: NSCoder) { @@ -171,6 +173,8 @@ private class HeaderView : View { class InstantWindowContentView : View { private let headerView: HeaderView = HeaderView(frame: NSZeroRect) private let contentView: View = View() + fileprivate let loadingIndicatorView: LinearProgressControl = LinearProgressControl(progressHeight: 2) + fileprivate var arguments: InstantViewArguments? { didSet { headerView.arguments = arguments @@ -186,14 +190,18 @@ class InstantWindowContentView : View { super.init(frame: frameRect) addSubview(headerView) addSubview(contentView) + addSubview(loadingIndicatorView) + loadingIndicatorView.layer?.opacity = 0 + flip = false contentView.autoresizesSubviews = false layout() } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) contentView.backgroundColor = theme.colors.background backgroundColor = theme.colors.background + loadingIndicatorView.style = ControlStyle(foregroundColor: theme.colors.text, backgroundColor: backgroundColor) } func updateTitle(_ title:String, animated: Bool) { @@ -219,9 +227,11 @@ class InstantWindowContentView : View { override func layout() { super.layout() - headerView.frame = NSMakeRect(0, 0, frame.width, barHeight) - contentView.frame = NSMakeRect(0, headerView.frame.maxY, frame.width, frame.height - headerView.frame.height) + headerView.frame = NSMakeRect(0, frame.height - barHeight, frame.width, barHeight) + contentView.frame = NSMakeRect(0, 0, min(frame.width, 720), frame.height - headerView.frame.height) + contentView.centerX() contentView.subviews.first?.frame = contentView.bounds + loadingIndicatorView.frame = NSMakeRect(0, frame.height - barHeight, frame.width, loadingIndicatorView.frame.height) } } @@ -237,33 +247,33 @@ class InstantViewController : TelegramGenericViewController 0, animated: true) } - let arguments = InstantViewArguments(account: account, share: { [weak self] in + let arguments = InstantViewArguments(context: context, share: { [weak self] in self?.share() }, back: { [weak self] in self?.navigation.back() @@ -281,18 +291,27 @@ class InstantViewController : TelegramGenericViewController deliverOnMainQueue).start(next: { [weak self] appearance in + appearanceDisposable.set((ivAppearance(postbox: context.account.postbox) |> deliverOnMainQueue).start(next: { [weak self] appearance in self?.genericView.presenation = appearance })) _window.closeInterceptor = { [weak self] in - self?._window.orderOut(nil) - instantController = nil + if let window = self?._window, !window.styleMask.contains(.fullScreen) { + self?._window.orderOut(nil) + instantController = nil + } else { + self?._window.toggleFullScreen(nil) + delay(0.8, closure: { + self?._window.orderOut(self) + instantController = nil + }) + } + return true } let closeKeyboardHandler:()->KeyHandlerResult = { [weak self] in @@ -300,14 +319,54 @@ class InstantViewController : TelegramGenericViewControllerKeyHandlerResult = { [weak self, weak page] in + if let window = self?._window { + if !window.styleMask.contains(.fullScreen) { + page?.scrollPage(direction: .down) + } + } + + return .invoked + } + _window.set(handler: spaceScrollDownKeyboardHandler, with: self, for: .Space, priority: .low) + + let spaceScrollUpKeyboardHandler:()->KeyHandlerResult = { [weak self, weak page] in + if let window = self?._window { + if !window.styleMask.contains(.fullScreen) { + page?.scrollPage(direction: .up) + } + } + + return .invoked + } + + _window.set(handler: spaceScrollUpKeyboardHandler, with: self, for: .Space, priority: .medium, modifierFlags: [.shift]) + } else { + _window.set(handler: closeKeyboardHandler, with: self, for: .Space) + } if let titleView = titleView { @@ -321,6 +380,22 @@ class InstantViewController : TelegramGenericViewController, animated: Bool = true) { + loadProgressDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] value in + guard let `self` = self else {return} + self.genericView.loadingIndicatorView.set(progress: value, animated: animated, duration: 0.2) + if value == 1 || value == 0 { + self.genericView.loadingIndicatorView.change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.genericView.loadingIndicatorView.set(progress: 0, animated: false) + } + }) + } else if value > 0 { + self.genericView.loadingIndicatorView.change(opacity: 1, animated: animated) + } + })) + } private func openInSafari() { @@ -350,7 +425,7 @@ class InstantViewController : TelegramGenericViewController take(1) |> filter {$0} |> deliverOnMainQueue |> map { [weak self] value in - self?._window.makeKeyAndOrderFront(nil) - + guard let `self` = self else {return value} + + // let toRect = self._window.frame + // let fromRect = NSMakeRect(toRect.minX + (toRect.width - 100) / 2, toRect.minY + (toRect.height - 100) / 2, 100, 100) + //self._window.setFrame(fromRect, display: true) + self._window.makeKeyAndOrderFront(nil) + + // NSLog("\(self._window.contentView?.superview?.layer)") + // self._window.contentView?.superview?.layer?.animateScaleCenter(from: 0, to: 1, duration: 0.4) + + //self._window.setFrame(toRect, display: false, animate: true) + // self._window.contentView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + return value }) } @@ -422,12 +511,19 @@ class InstantViewController : TelegramGenericViewController Void { + func update(with peer:TelegramGroup, account:Account, participants:[Peer]? = nil, groupUserCount: Int32 = 0) -> Void { imageView.setPeer(account: account, peer: peer) let attr = NSMutableAttributedString() _ = attr.append(string: peer.displayTitle, color: theme.colors.text, font: .normal(.title)) _ = attr.append(string: "\n") - _ = attr.append(string: tr(.peerStatusMemberCountable(peer.participantCount)), color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: L10n.peerStatusMemberCountable(peer.participantCount).replacingOccurrences(of: "\(peer.participantCount)", with: peer.participantCount.formattedWithSeparator), color: theme.colors.grayText, font: .normal(.text)) let titleLayout = TextViewLayout(attr, alignment: .center) titleLayout.measure(width: frame.width - 40) titleView.update(titleLayout) basicContainer.setFrameSize(frame.width, imageView.frame.height + titleView.frame.height + 10) + usersContainer.removeAllSubviews() + + if let participants = participants { + for participant in participants { + if usersContainer.subviews.count < 3 { + let avatar = AvatarControl(font: .avatar(20)) + avatar.setFrameSize(50, 50) + avatar.setPeer(account: account, peer: participant) + usersContainer.addSubview(avatar) + } else { + let additionCount = Int(groupUserCount) - usersContainer.subviews.count + if additionCount > 0 { + let avatar = AvatarControl(font: .avatar(20)) + avatar.setFrameSize(50, 50) + avatar.setState(account: account, state: .Empty) + let icon = generateImage(NSMakeSize(46, 46), contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + var fontSize: CGFloat = 13 + + if additionCount.prettyNumber.length == 1 { + fontSize = 18 + } else if additionCount.prettyNumber.length == 2 { + fontSize = 15 + } + let layout = TextViewLayout(.initialize(string: "+\(additionCount.prettyNumber)", color: .white, font: .medium(fontSize)), maximumNumberOfLines: 1, truncationType: .middle) + layout.measure(width: size.width - 4) + if !layout.lines.isEmpty { + let line = layout.lines[0] + // ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + ctx.textPosition = NSMakePoint(floorToScreenPixels(System.backingScale, (size.width - line.frame.width)/2.0) - 1, floorToScreenPixels(System.backingScale, (size.height - line.frame.height)/2.0) + 4) + + CTLineDraw(line.line, ctx) + } + })! + avatar.setSignal(generateEmptyPhoto(avatar.frame.size, type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize, cornerRadius: nil)) |> map {($0, false)}) + usersContainer.addSubview(avatar) + } + break + } + + } + } + needsLayout = true + + } override func layout() { super.layout() - basicContainer.center() imageView.centerX(y: 0) titleView.centerX(y: 80) + + if !usersContainer.subviews.isEmpty { + basicContainer.centerX(y: 20) + var x:CGFloat = 0 + for avatar in usersContainer.subviews { + avatar.setFrameOrigin(NSMakePoint(x, 0)) + x += avatar.frame.width + 10 + } + usersContainer.setFrameSize(x - 10, usersContainer.subviews[0].frame.height) + } else { + basicContainer.center() + } + + usersContainer.centerX(y: basicContainer.frame.maxY + 20) } required init?(coder: NSCoder) { @@ -57,17 +118,16 @@ private class JoinLinkPreviewView : View { class JoinLinkPreviewModalController: ModalViewController { - private let account:Account + private let context:AccountContext private let join:ExternalJoiningChatState private let joinhash:String private let interaction:(PeerId?)->Void override func viewDidLoad() { super.viewDidLoad() switch join { - case let .invite(data): - let peer = TelegramGroup(id: PeerId(namespace: 0, id: 0), title: data.title, photo: data.photoRepresentation != nil ? [data.photoRepresentation!] : [], participantCount: Int(data.participantsCount), role: .member, membership: .Left, flags: [], migrationReference: nil, creationDate: 0, version: 0) - - genericView.update(with: peer, account: account) + case let .invite(title: title, image, memberCount, participants): + let peer = TelegramGroup(id: PeerId(namespace: 0, id: 0), title: title, photo: image.flatMap { [$0] } ?? [], participantCount: Int(memberCount), role: .member, membership: .Left, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0) + genericView.update(with: peer, account: context.account, participants: participants, groupUserCount: memberCount) default: break } @@ -82,23 +142,45 @@ class JoinLinkPreviewModalController: ModalViewController { return JoinLinkPreviewView.self } - init(_ account:Account, hash:String, join:ExternalJoiningChatState, interaction:@escaping(PeerId?)->Void) { - self.account = account + init(_ context: AccountContext, hash:String, join:ExternalJoiningChatState, interaction:@escaping(PeerId?)->Void) { + self.context = context self.join = join self.joinhash = hash self.interaction = interaction - super.init(frame: NSMakeRect(0, 0, 250, 200)) + + var rect = NSMakeRect(0, 0, 270, 180) + switch join { + case let .invite(_, _, _, participants): + if let participants = participants, participants.count > 0 { + rect.size.height = 230 + } + default: + break + } + super.init(frame: rect) + bar = .init(height: 0) } override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.joinLinkJoin), accept: { [weak self] in + let context = self.context + return ModalInteractions(acceptTitle: L10n.joinLinkJoin, accept: { [weak self] in if let strongSelf = self, let window = strongSelf.window { - _ = showModalProgress(signal: joinChatInteractively(with: strongSelf.joinhash, account: strongSelf.account), for: window).start(next: { [weak strongSelf] (peerId) in + _ = showModalProgress(signal: joinChatInteractively(with: strongSelf.joinhash, account: strongSelf.context.account), for: window).start(next: { [weak strongSelf] peerId in strongSelf?.interaction(peerId) self?.close() + }, error: { error in + let text: String + switch error { + case .generic: + text = L10n.unknownError + case .tooMuchJoined: + showInactiveChannels(context: context, source: .join) + return + } + alert(for: context.window, info: text) }) } - }, cancelTitle: tr(.modalCancel)) + }, cancelTitle: tr(L10n.modalCancel)) } diff --git a/Telegram-Mac/Keychain.swift b/Telegram-Mac/Keychain.swift new file mode 100644 index 0000000000..b0ef6e9489 --- /dev/null +++ b/Telegram-Mac/Keychain.swift @@ -0,0 +1,225 @@ +//// +//// Keychain.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 03/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import Security +// +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import MtProtoKit +//import WalletCore +// +//private let salt = "string with no sense".data(using: .utf8)! +// +//@available(OSX 10.12, *) +//enum TKKeychainName : String { +// case `public` +// case `private` +//} +//@available(OSX 10.12, *) +//struct TKKey : Equatable { +// static func == (lhs: TKKey, rhs: TKKey) -> Bool { +// return lhs.publicKey.key == rhs.publicKey.key && lhs.privateKey.key == rhs.privateKey.key +// } +// let publicKey: TKPublicKey +// let privateKey: TKPrivateKey +//} +// +//@available(OSX 10.12, *) +//struct TKPublicKey : Codable, Equatable { +// let key: Data +// +// func encrypt(data: Data) -> Data? { +// let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, +// kSecAttrKeyClass as String: kSecAttrKeyClassPublic, +// kSecAttrKeySizeInBits as String : 256] +// var error: Unmanaged? +// guard let publicKey = SecKeyCreateWithData(self.key as CFData, +// options as CFDictionary, +// &error) else { +// return nil +// } +// +// if data.count % 16 != 0 { +// return nil +// } +// return SecKeyCreateEncryptedData(publicKey, SecKeyAlgorithm.eciesEncryptionCofactorX963SHA256AESGCM, data as CFData, nil) as Data? +// } +// +// +// +// static func get(for account: Account) -> TKPublicKey? { +// if let publicTonKey = SSKeychain.passwordData(forService: serviceName(for: account), account: TKKeychainName.public.rawValue) { +// return TKPublicKey(key: publicTonKey) +// } +// return nil +// } +// func set(for account: Account) -> Bool { +// return SSKeychain.setPasswordData(self.key, forService: serviceName(for: account), account: TKKeychainName.public.rawValue) +// } +// static func delete(for account: Account) -> Void { +// SSKeychain.deletePassword(forService: serviceName(for: account), account: TKKeychainName.public.rawValue) +// } +//} +// +//@available(OSX 10.12, *) +//struct TKPrivateKey : Codable { +// let key: Data +// +// static func get(for account: Account) -> TKPrivateKey? { +// if let privateTonKey = SSKeychain.passwordData(forService: serviceName(for: account), account: TKKeychainName.private.rawValue) { +// return TKPrivateKey(key: privateTonKey) +// } +// return nil +// } +// func set(for account: Account) -> Bool { +// return SSKeychain.setPasswordData(self.key, forService: serviceName(for: account), account: TKKeychainName.private.rawValue) +// } +// static func delete(for account: Account) -> Void { +// SSKeychain.deletePassword(forService: serviceName(for: account), account: TKKeychainName.private.rawValue) +// } +// func decrypt(data: Data) -> Data? { +// let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, +// kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, +// kSecAttrKeySizeInBits as String : 256] +// var error: Unmanaged? +// guard let privateKey = SecKeyCreateWithData(self.key as CFData, +// options as CFDictionary, +// &error) else { +// return nil +// } +// return SecKeyCreateDecryptedData(privateKey, SecKeyAlgorithm.eciesEncryptionCofactorX963SHA256AESGCM, data as CFData, nil) as Data? +// } +//} +// +//private func serviceName(for account: Account) -> String { +// return "TON-\(account.id.int64)" +//} +// +//@available(OSX 10.12, *) +//final class TONKeychain { +// +// private static let queue = Queue(name: "TONKeychain", qos: DispatchQoS.default) +// +// static func createLocalPrivateKey() -> TKKey? { +// let attributes: [String: Any] = [kSecAttrKeyType as String : kSecAttrKeyTypeECSECPrimeRandom, +// kSecAttrKeySizeInBits as String: 256, +// kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent: true]] +// +// var error: Unmanaged? +// guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { +// return nil +// } +// +// guard let publicKey = SecKeyCopyPublicKey(privateKey) else { +// return nil +// } +// +// guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else { +// return nil +// } +// guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else { +// return nil +// } +// +// return TKKey(publicKey: TKPublicKey(key: publicKeyData), privateKey: TKPrivateKey(key: privateKeyData)) +// } +// +// static func initializePairAndSavePublic(for account: Account) -> Signal { +// return Signal { subscriber in +// if let keys = createLocalPrivateKey() { +// let success = keys.publicKey.set(for: account) +// if success { +// subscriber.putNext(keys) +// subscriber.putCompletion() +// } else { +// subscriber.putNext(nil) +// subscriber.putCompletion() +// } +// } else { +// subscriber.putNext(nil) +// subscriber.putCompletion() +// } +// return EmptyDisposable +// } |> runOn(queue) +// } +// +// static func applyKeys(_ keys: TKKey, account: Account, tonInstance: TonInstance, password: String) -> Signal { +// return Signal { subscriber in +// let pbkdf = MTPBKDF2(password.data(using: .utf8)!, salt, 100000)! +// +// return tonlibEncrypt(tonInstance: tonInstance, decryptedData: keys.privateKey.key, secret: pbkdf).start(next: { data in +// let success1 = keys.publicKey.set(for: account) +// let success2 = TKPrivateKey(key: data).set(for: account) +// subscriber.putNext(success1 && success2) +// subscriber.putCompletion() +// }, completed: { +// subscriber.putCompletion() +// }) +// } |> runOn(queue) +// } +// +// static func hasKeys(for account: Account) -> Signal { +// return Signal { subscriber in +// subscriber.putNext(TKPrivateKey.get(for: account) != nil && TKPublicKey.get(for: account) != nil) +// subscriber.putCompletion() +// return EmptyDisposable +// } |> runOn(queue) +// } +// +// static func delete(account: Account) -> Signal { +// return Signal { subscriber in +// TKPrivateKey.delete(for: account) +// TKPublicKey.delete(for: account) +// subscriber.putNext(Void()) +// subscriber.putCompletion() +// return EmptyDisposable +// } +// } +// +// static func decryptedSecretKey(_ encryptedKey: TonKeychainEncryptedData, account: Account, tonInstance: TonInstance, by password: String) -> Signal { +// return Signal { subscriber in +// let privateKey = TKPrivateKey.get(for: account)?.key +// +// var disposable: Disposable? +// if let privateEncrypted = privateKey, let publicKey = TKPublicKey.get(for: account) { +// let pbkdf = MTPBKDF2(password.data(using: .utf8)!, salt, 100000)! +// +// disposable = tonlibDecrypt(tonInstance: tonInstance, encryptedData: privateEncrypted, secret: pbkdf).start(next: { data in +// if let privateKey = data { +// let priv = TKPrivateKey(key: privateKey) +// if publicKey.key == encryptedKey.publicKey { +// subscriber.putNext(priv.decrypt(data: encryptedKey.data)) +// subscriber.putCompletion() +// } else { +// subscriber.putNext(nil) +// subscriber.putCompletion() +// } +// } else { +// subscriber.putNext(nil) +// subscriber.putCompletion() +// } +// }, completed: { +// +// }) +// +// +// } else { +// subscriber.putNext(nil) +// subscriber.putCompletion() +// } +// return ActionDisposable { +// disposable?.dispose() +// } +// } |> runOn(queue) +// } +// +//} +// +// diff --git a/Telegram-Mac/LAnimationButton.swift b/Telegram-Mac/LAnimationButton.swift new file mode 100644 index 0000000000..799da65fe5 --- /dev/null +++ b/Telegram-Mac/LAnimationButton.swift @@ -0,0 +1,129 @@ +// +// LAnimationButton.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import TGUIKit + + +enum LButtonAutoplaySide { + case left + case right +} + +class LAnimationButton: Button { + private let animationView: LottiePlayerView = LottiePlayerView(frame: NSZeroRect) + var speed: CGFloat = 1.0 { + didSet { + //animationView.animationSpeed = speed + } + } + private let offset: NSSize + + var played = false + var completion: (() -> Void)? + + var autoplayOnVisibleSide: LButtonAutoplaySide? = nil + private var animation: LottieAnimation? + private var firstFrame: LottieAnimation? + init(animation: String, size: NSSize, keysToColor: [String]? = nil, color: NSColor = .black, offset: NSSize = NSMakeSize(0, 0), autoplaySide: LButtonAutoplaySide? = nil, rotated: Bool = false) { + self.offset = offset + self.autoplayOnVisibleSide = autoplaySide + if let file = Bundle.main.path(forResource: animation, ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: file)) { + self.animation = LottieAnimation(compressed: data, key: .init(key: .bundle(animation), size: size), cachePurpose: .none, playPolicy: .once, maximumFps: 60) + self.firstFrame = LottieAnimation(compressed: data, key: .init(key: .bundle(animation), size: size), cachePurpose: .none, playPolicy: .framesCount(1), maximumFps: 60) + } else { + self.animation = nil + self.firstFrame = nil + } + animationView.setFrameSize(size) + super.init(frame: NSMakeRect(0, 0, size.width, size.height)) + addSubview(animationView) + if keysToColor != nil { + self.set(keysToColor: keysToColor, color: color) + } + + if rotated { + // animationView.rotate(byDegrees: 180) + } + } + + func set(keysToColor: [String]? = nil, color: NSColor = .black) { + let newColor = color.usingColorSpace(.deviceRGB)! + + var colors: [LottieColor] = [] + if let keysToColor = keysToColor { + for keyToColor in keysToColor { + colors.append(LottieColor(keyPath: keyToColor, color: newColor)) + } + } + + self.animation = self.animation?.withUpdatedColors(colors) + self.firstFrame = self.firstFrame?.withUpdatedColors(colors) + animationView.set(self.firstFrame) + + } + + + override func viewDidMoveToWindow() { + if window == nil { + animationView.set(nil) + } else { + animationView.set(self.firstFrame) + } + } + + private var prevVisibleRect: NSRect = NSZeroRect + + override func updateTrackingAreas() { + super.updateTrackingAreas() + if let audoplaySide = self.autoplayOnVisibleSide { + let point: NSPoint + switch audoplaySide { + case .right: + point = NSMakePoint(frame.width - frame.width / 3, frame.height / 2) + case .left: + point = NSMakePoint(frame.width / 3, frame.height / 2) + } + let locationInWindow = self.convert(point, to: nil) + if window?.contentView?.hitTest(locationInWindow) == self.animationView { + if !self.played { + self.played = true + animationView.set(self.animation) + } + } else if self.played { + self.played = false + //animationView.set(self.firstFrame) + } + } + + + self.prevVisibleRect = visibleRect + } + + + + func loop() { + self.animationView.set(self.animation, reset: true) + } + + + + + override func layout() { + super.layout() + animationView.center() + animationView.setFrameOrigin(animationView.frame.minX - offset.width, animationView.frame.minY - offset.height) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} diff --git a/Telegram-Mac/LanguageRowItem.swift b/Telegram-Mac/LanguageRowItem.swift index 14422afd9f..b4435234dd 100644 --- a/Telegram-Mac/LanguageRowItem.swift +++ b/Telegram-Mac/LanguageRowItem.swift @@ -8,24 +8,28 @@ import Cocoa import TGUIKit -import TelegramCoreMac -class LanguageRowItem: TableRowItem { +import TelegramCore +import SyncCore +class LanguageRowItem: GeneralRowItem { fileprivate let selected:Bool fileprivate let locale:TextViewLayout fileprivate let title:TextViewLayout - fileprivate let action:()->Void + fileprivate let deleteAction: ()->Void + fileprivate let deletable: Bool + fileprivate let _stableId:AnyHashable override var stableId: AnyHashable { return _stableId } - init(initialSize: NSSize, stableId: AnyHashable, selected: Bool, value:LocalizationInfo, action:@escaping()->Void, reversed: Bool = false) { + init(initialSize: NSSize, stableId: AnyHashable, selected: Bool, deletable: Bool, value:LocalizationInfo, viewType: GeneralViewType = .legacy, action:@escaping()->Void, deleteAction: @escaping()->Void = {}, reversed: Bool = false) { self._stableId = stableId self.selected = selected self.title = TextViewLayout(.initialize(string: reversed ? value.localizedTitle : value.title, color: theme.colors.text, font: .normal(.title)), maximumNumberOfLines: 1) self.locale = TextViewLayout(.initialize(string: reversed ? value.title : value.localizedTitle, color: reversed ? theme.colors.grayText : theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) - self.action = action - super.init(initialSize) + self.deletable = deletable + self.deleteAction = deleteAction + super.init(initialSize, viewType: viewType, action: action) _ = makeSize(initialSize.width, oldWidth: initialSize.width) } @@ -51,22 +55,30 @@ class LanguageRowView : TableRowView { private let titleTextView:TextView = TextView() private let selectedImage:ImageView = ImageView() private let overalay:OverlayControl = OverlayControl() + private let deleteButton = ImageButton() required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(titleTextView) addSubview(localeTextView) addSubview(selectedImage) + selectedImage.sizeToFit() localeTextView.isSelectable = false titleTextView.isSelectable = false addSubview(overalay) overalay.set(background: .grayTransparent, for: .Highlight) - + addSubview(deleteButton) overalay.set(handler: { [weak self] _ in if let item = self?.item as? LanguageRowItem { item.action() } }, for: .Click) + + deleteButton.set(handler: { [weak self] _ in + if let item = self?.item as? LanguageRowItem { + item.deleteAction() + } + }, for: .Click) } override var backdorColor: NSColor { @@ -85,11 +97,18 @@ class LanguageRowView : TableRowView { titleTextView.update(item.title) localeTextView.update(item.locale) + deleteButton.set(image: theme.icons.customLocalizationDelete, for: .Normal) + _ = deleteButton.sizeToFit() + selectedImage.image = theme.icons.generalSelect selectedImage.sizeToFit() titleTextView.backgroundColor = theme.colors.background localeTextView.backgroundColor = theme.colors.background selectedImage.isHidden = !item.selected + + deleteButton.isHidden = !item.deletable || item.selected + + needsLayout = true } } @@ -97,6 +116,7 @@ class LanguageRowView : TableRowView { override func layout() { super.layout() selectedImage.centerY(x: frame.width - 25 - selectedImage.frame.width) + deleteButton.centerY(x: frame.width - 18 - deleteButton.frame.width) if let item = item as? LanguageRowItem { titleTextView.update(item.title, origin: NSMakePoint(25, 5)) localeTextView.update(item.locale, origin: NSMakePoint(25, frame.height - titleTextView.frame.height - 5)) diff --git a/Telegram-Mac/LanguageViewController.swift b/Telegram-Mac/LanguageViewController.swift index 7e1f5aef18..f9c0879a66 100644 --- a/Telegram-Mac/LanguageViewController.swift +++ b/Telegram-Mac/LanguageViewController.swift @@ -8,24 +8,20 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit + -extension LocalizationInfo : Equatable { - -} -public func ==(lhs:LocalizationInfo, rhs:LocalizationInfo) -> Bool { - return lhs.title == rhs.title && lhs.languageCode == rhs.languageCode && lhs.localizedTitle == rhs.localizedTitle -} final class LanguageControllerArguments { - let account:Account + let context:AccountContext let change:(LocalizationInfo)->Void - let searchInteractions:SearchInteractions - init(account:Account, change:@escaping(LocalizationInfo)->Void, searchInteractions: SearchInteractions) { - self.account = account + let delete:(LocalizationInfo)->Void + init(context: AccountContext, change:@escaping(LocalizationInfo)->Void, delete:@escaping(LocalizationInfo)->Void) { + self.context = context self.change = change - self.searchInteractions = searchInteractions + self.delete = delete } } @@ -33,6 +29,8 @@ enum LanguageTableEntryId : Hashable { case search case language(String) case loading + case sectionId(Int32) + case headerId(Int32) var hashValue: Int { switch self { case .search: @@ -41,54 +39,41 @@ enum LanguageTableEntryId : Hashable { return id.hashValue case .loading: return 1 + case .sectionId: + return 2 + case .headerId: + return 3 } } - static func ==(lhs:LanguageTableEntryId, rhs:LanguageTableEntryId) -> Bool { - switch lhs { - case .search: - if case .search = rhs { - return true - } else { - return false - } - case .loading: - if case .loading = rhs { - return true - } else { - return false - } - case .language(let id): - if case .language(id) = rhs { - return true - } else { - return false - } - } - } - } enum LanguageTableEntry : TableItemListNodeEntry { - case search - case language(index:Int32, selected: Bool, value: LocalizationInfo) + case language(sectionId: Int32, index:Int32, selected: Bool, deletable: Bool, value: LocalizationInfo, viewType: GeneralViewType) + case section(Int32, Bool) + case header(sectionId: Int32, index:Int32, descId: Int32, value: String, viewType: GeneralViewType) case loading var stableId: LanguageTableEntryId { switch self { - case .search: - return .search - case .language(_, _, let value): + case .language(_, _, _, _, let value, _): return .language(value.languageCode) + case let .section(sectionId, _): + return .sectionId(sectionId) + case let .header(_, _, id, _, _): + return .headerId(id) case .loading: return .loading } } + var index:Int32 { switch self { - case .search: - return 0 - case .language(let index, _, _): - return 1 + index + case let .language(sectionId, index, _, _, _, _): + return (sectionId * 1000) + index + case let .header(sectionId, index, _, _, _): + return (sectionId * 1000) + index + case let .section(sectionId, _): + return (sectionId + 1) * 1000 - sectionId case .loading: return -1 } @@ -96,65 +81,123 @@ enum LanguageTableEntry : TableItemListNodeEntry { func item(_ arguments: LanguageControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case .search: - return SearchRowItem(initialSize, stableId: stableId, searchInteractions: arguments.searchInteractions, inset: NSEdgeInsets(left: 25, right: 25, top: 10, bottom: 10)) - case let .language(_, selected, value): - return LanguageRowItem(initialSize: initialSize, stableId: stableId, selected: selected, value: value, action: { + case let .language(_, _, selected, deletable, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: value.title, description: value.localizedTitle, descTextColor: theme.colors.grayText, type: .selectable(selected), viewType: viewType, action: { arguments.change(value) + }, menuItems: { + if deletable { + return [ContextMenuItem(L10n.messageContextDelete, handler: { + arguments.delete(value) + })] + } + return [] }) + case let .section(_, hasSearch): + return GeneralRowItem(initialSize, height: hasSearch ? 80 : 30, stableId: stableId, viewType: .separator) + case let .header(_, _, _, value, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: value, viewType: viewType) case .loading: return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) } } } -func ==(lhs:LanguageTableEntry, rhs:LanguageTableEntry) -> Bool { - switch lhs { - case .search: - if case .search = rhs { - return true - } else { - return false - } - case .loading: - if case .loading = rhs { - return true - } else { - return false - } - case let .language(index, selected, value): - if case .language(index, selected, value) = rhs { - return true - } else { - return false - } - } -} - func <(lhs:LanguageTableEntry, rhs:LanguageTableEntry) -> Bool { return lhs.index < rhs.index } -private func languageControllerEntries(infos: [LocalizationInfo]?, language: Language, state:SearchState) -> [LanguageTableEntry] { +private func languageControllerEntries(listState: LocalizationListState?, language: TelegramLocalization, state:SearchState, searchViewState: TableSearchViewState) -> [LanguageTableEntry] { + var sectionId: Int32 = 0 + var index: Int32 = 0 + var entries: [LanguageTableEntry] = [] - if let infos = infos { - entries.append(.search) - var index:Int32 = 1 + if let listState = listState, !listState.availableSavedLocalizations.isEmpty || !listState.availableOfficialLocalizations.isEmpty { + - for value in infos { + switch searchViewState { + case .visible: + entries.append(.section(sectionId, true)) + sectionId += 1 + default: + entries.append(.section(sectionId, false)) + sectionId += 1 + } + + let availableSavedLocalizations = listState.availableSavedLocalizations.filter({ info in !listState.availableOfficialLocalizations.contains(where: { $0.languageCode == info.languageCode }) }).filter { value in + if state.request.isEmpty { + return true + } else { + return (value.title.lowercased().range(of: state.request.lowercased()) != nil) || (value.localizedTitle.lowercased().range(of: state.request.lowercased()) != nil) + } + } + + let availableOfficialLocalizations = listState.availableOfficialLocalizations.filter { value in + if state.request.isEmpty { + return true + } else { + return (value.title.lowercased().range(of: state.request.lowercased()) != nil) || (value.localizedTitle.lowercased().range(of: state.request.lowercased()) != nil) + } + } + + var existingIds:Set = Set() + + + let saved = availableSavedLocalizations.filter { value in + + if existingIds.contains(value.languageCode) { + return false + } + var accept: Bool = true if !state.request.isEmpty { accept = (value.title.lowercased().range(of: state.request.lowercased()) != nil) || (value.localizedTitle.lowercased().range(of: state.request.lowercased()) != nil) } - if accept { - entries.append(.language(index: index, selected: value.languageCode == language.languageCode, value: value)) - index += 1 + return accept + } + + for value in saved { + let viewType: GeneralViewType = bestGeneralViewType(saved, for: value) + + existingIds.insert(value.languageCode) + entries.append(.language(sectionId: sectionId, index: index, selected: value.languageCode == language.primaryLanguage.languageCode, deletable: true, value: value, viewType: viewType)) + index += 1 + } + + + + if !availableOfficialLocalizations.isEmpty { + + if !availableSavedLocalizations.isEmpty { + entries.append(.section(sectionId, false)) + sectionId += 1 } + + let list = listState.availableOfficialLocalizations.filter { value in + if existingIds.contains(value.languageCode) { + return false + } + var accept: Bool = true + if !state.request.isEmpty { + accept = (value.title.lowercased().range(of: state.request.lowercased()) != nil) || (value.localizedTitle.lowercased().range(of: state.request.lowercased()) != nil) + } + return accept + } + + for value in list { + let viewType: GeneralViewType = bestGeneralViewType(list, for: value) + existingIds.insert(value.languageCode) + entries.append(.language(sectionId: sectionId, index: index, selected: value.languageCode == language.primaryLanguage.languageCode, deletable: false, value: value, viewType: viewType)) + index += 1 + } } + + entries.append(.section(sectionId, false)) + sectionId += 1 + } else { entries.append(.loading) } @@ -163,13 +206,63 @@ private func languageControllerEntries(infos: [LocalizationInfo]?, language: Lan return entries } -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:LanguageControllerArguments) -> TableUpdateTransition { +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, animated: Bool, arguments:LanguageControllerArguments, searchViewState: TableSearchViewState) -> Signal { - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) + + return Signal { subscriber in + var cancelled = false + + if Thread.isMainThread { + var initialIndex:Int = 0 + var height:CGFloat = 0 + var firstInsertion:[(Int, TableRowItem)] = [] + let entries = Array(right) + + let index:Int = 0 + + for i in index ..< entries.count { + let item = entries[i].entry.item(arguments, initialSize: initialSize) + height += item.height + firstInsertion.append((i, item)) + if initialSize.height < height { + break + } + } + + initialIndex = firstInsertion.count + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state: .none(nil), searchState: searchViewState)) + + prepareQueue.async { + if !cancelled { + + var insertions:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + + for i in initialIndex ..< entries.count { + let item:TableRowItem + item = entries[i].entry.item(arguments, initialSize: initialSize) + insertions.append((i, item)) + } + + + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: insertions, updated: updates, state: .none(nil), searchState: searchViewState)) + subscriber.putCompletion() + } + } + } else { + let (deleted,inserted,updated) = proccessEntriesWithoutReverse(left, right: right, { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + }) + + subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated: animated, state: .none(nil), searchState: searchViewState)) + subscriber.putCompletion() + } + + return ActionDisposable { + cancelled = true + } } - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } @@ -177,61 +270,136 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntryVoid)? = nil override var enableBack: Bool { return true } + override init(_ context: AccountContext) { + super.init(context) + } + deinit { applyDisposable.dispose() languageDisposable.dispose() + disposable.dispose() + } + + override func getRightBarViewOnce() -> BarView { + let view = ImageBarView(controller: self, theme.icons.chatSearch) + + view.button.set(handler: { [weak self] _ in + self?.toggleSearch?() + }, for: .Click) + view.set(image: theme.icons.chatSearch, highlightImage: nil) + return view + } + + + override func requestUpdateRightBar() { + super.requestUpdateRightBar() + (self.rightBarView as? ImageBarView)?.set(image: theme.icons.chatSearch, highlightImage: nil) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.toggleSearch?() + return .invoked + }, with: self, for: .F, modifierFlags: [.command]) } override func viewDidLoad() { super.viewDidLoad() - let searchPromise = ValuePromise() + let context = self.context + + + let stateValue: Atomic = Atomic(value: SearchState(state: .None, request: nil)) + let statePromise:ValuePromise = ValuePromise(SearchState(state: .None, request: nil), ignoreRepeated: true) + + let updateState:((SearchState)->SearchState)->Void = { f in + statePromise.set(stateValue.modify(f)) + } + + + let searchValue:Atomic = Atomic(value: .none) + let searchState: ValuePromise = ValuePromise(.none, ignoreRepeated: true) + let updateSearchValue:((TableSearchViewState)->TableSearchViewState)->Void = { f in + searchState.set(searchValue.modify(f)) + } - let searchInteractions = SearchInteractions({ state in - searchPromise.set(state) - }, { state in - searchPromise.set(state) + let searchData = TableSearchVisibleData(cancelImage: theme.icons.chatSearchCancel, cancel: { + updateSearchValue { _ in + return .none + } + }, updateState: { searchState in + updateState { _ in + return searchState + } }) - searchPromise.set(SearchState(state: .None, request: nil)) - let account = self.account - let arguments = LanguageControllerArguments(account: account, change: { [weak self] value in - if value.languageCode != appCurrentLanguage.languageCode { - self?.applyDisposable.set(showModalProgress(signal: downoadAndApplyLocalization(postbox: account.postbox, network: account.network, languageCode: value.languageCode), for: mainWindow).start()) + self.toggleSearch = { + updateSearchValue { current in + switch current { + case .none: + return .visible(searchData) + case .visible: + return .none + } + } + } + + let arguments = LanguageControllerArguments(context: context, change: { [weak self] value in + if value.languageCode != appCurrentLanguage.primaryLanguage.languageCode { + self?.applyDisposable.set(showModalProgress(signal: downloadAndApplyLocalization(accountManager:context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, languageCode: value.languageCode), for: context.window).start()) } - }, searchInteractions: searchInteractions) + }, delete: { info in + confirm(for: context.window, information: L10n.languageRemovePack, successHandler: { _ in + let _ = (context.account.postbox.transaction { transaction in + removeSavedLocalization(transaction: transaction, languageCode: info.languageCode) + }).start() + }) + }) let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = atomicSize + let signal = context.account.postbox.preferencesView(keys: [PreferencesKeys.localizationListState]) |> map { value -> LocalizationListState? in + return value.values[PreferencesKeys.localizationListState] as? LocalizationListState + } |> deliverOnPrepareQueue - genericView.merge(with: combineLatest(Signal<[LocalizationInfo]?, Void>.single(nil) |> then(availableLocalizations(network: account.network) |> map {Optional($0)} |> deliverOnMainQueue), appearanceSignal) - |> mapToSignal { infos, appearance in - return searchPromise.get() |> map { state in - return (infos, appearance, state) - } - } |> map { infos, appearance, state in - let entries = languageControllerEntries(infos: infos, language: appearance.language - , state: state).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify({$0}), arguments: arguments) - }) - + let first: Atomic = Atomic(value: true) + let prevSearch: Atomic = Atomic(value: nil) + + let transition: Signal = combineLatest(signal, appearanceSignal, statePromise.get(), searchState.get()) |> mapToSignal { listState, appearance, state, searchViewState in + let entries = languageControllerEntries(listState: listState, language: appearance.language, state: state, searchViewState: searchViewState) + .map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.with { $0 }, animated: prevSearch.swap(state.request) == state.request, arguments: arguments, searchViewState: searchViewState) + |> runOn(first.swap(false) ? .mainQueue() : prepareQueue) + } |> deliverOnMainQueue - readyOnce() + disposable.set(transition.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + })) } - - + } diff --git a/Telegram-Mac/LeftSidebarController.swift b/Telegram-Mac/LeftSidebarController.swift new file mode 100644 index 0000000000..9f1daa21f8 --- /dev/null +++ b/Telegram-Mac/LeftSidebarController.swift @@ -0,0 +1,246 @@ +// +// FoldersSidebarController.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore + +func filterContextMenuItems(_ filter: ChatListFilter?, context: AccountContext) -> [ContextMenuItem] { + var items:[ContextMenuItem] = [] + if var filter = filter { + items.append(.init(L10n.chatListFilterEdit, handler: { + context.sharedContext.bindings.rootNavigation().push(ChatListFilterController(context: context, filter: filter)) + })) + items.append(.init(L10n.chatListFilterAddChats, handler: { + showModal(with: ShareModalController(SelectCallbackObject(context, defaultSelectedIds: Set(filter.data.includePeers.peers), additionTopItems: nil, limit: 100, limitReachedText: L10n.chatListFilterIncludeLimitReached, callback: { peerIds in + return updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + filter.data.includePeers.setPeers(Array(peerIds.uniqueElements.prefix(100))) + if let index = filters.firstIndex(where: {$0.id == filter.id }) { + filters[index] = filter + } + return filters + }) |> ignoreValues + + })), for: context.window) + })) + items.append(.init(L10n.chatListFilterDelete, handler: { + confirm(for: context.window, header: L10n.chatListFilterConfirmRemoveHeader, information: L10n.chatListFilterConfirmRemoveText, okTitle: L10n.chatListFilterConfirmRemoveOK, successHandler: { _ in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + filters.removeAll(where: { $0.id == filter.id }) + return filters + }).start() + }) + + })) + } else { + items.append(.init(L10n.chatListFilterEditFilters, handler: { + context.sharedContext.bindings.rootNavigation().push(ChatListFiltersListController(context: context)) + })) + } + + return items +} + +private final class LeftSidebarArguments { + let context: AccountContext + let callback:(ChatListFilter?)->Void + let menuItems:(ChatListFilter?)->[ContextMenuItem] + init(context: AccountContext, callback: @escaping(ChatListFilter?)->Void, menuItems: @escaping(ChatListFilter?)->[ContextMenuItem]) { + self.context = context + self.callback = callback + self.menuItems = menuItems + } +} + + +final class LeftSidebarView: View { + fileprivate let tableView = TableView() + private let visualEffectView: NSVisualEffectView + private let borderView = View() + required init(frame frameRect: NSRect) { + self.visualEffectView = NSVisualEffectView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + + addSubview(self.visualEffectView) + addSubview(self.borderView) + + addSubview(self.tableView) + tableView.getBackgroundColor = { + return .clear + } + + visualEffectView.blendingMode = .behindWindow + visualEffectView.material = .ultraDark + + updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + borderView.backgroundColor = theme.colors.border + self.backgroundColor = theme.colors.listBackground + self.borderView.isHidden = !theme.colors.isDark + self.visualEffectView.isHidden = theme.colors.isDark + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + self.visualEffectView.frame = bounds + self.tableView.frame = bounds + self.borderView.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + } +} + +private enum LeftSibarBarEntry : Comparable, Identifiable { + static func < (lhs: LeftSibarBarEntry, rhs: LeftSibarBarEntry) -> Bool { + return lhs.index < rhs.index + } + case topOffset + case allChats(selected: Bool, unreadCount: Int, hasUnmutedUnread: Bool) + case folder(index: Int, selected: Bool, filter: ChatListFilter, unreadCount: Int, hasUnmutedUnread: Bool) + + var stableId: Int32 { + switch self { + case .topOffset: + return -2 + case .allChats: + return -1 + case let .folder(_, _, filter, _, _): + return filter.id + } + } + + var index: Int { + switch self { + case .topOffset: + return -1 + case .allChats: + return 0 + case let .folder(index, _, _, _, _): + return index + } + } + + func item(_ arguments: LeftSidebarArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .allChats(selected, unreadCount, hasUnmutedUnread): + return LeftSidebarFolderItem(initialSize, folder: nil, selected: selected, unreadCount: unreadCount, hasUnmutedUnread: hasUnmutedUnread, callback: arguments.callback, menuItems: arguments.menuItems) + case let .folder(_, selected, filter, unreadCount, hasUnmutedUnread): + return LeftSidebarFolderItem(initialSize, folder: filter, selected: selected, unreadCount: unreadCount, hasUnmutedUnread: hasUnmutedUnread, callback: arguments.callback, menuItems: arguments.menuItems) + case .topOffset: + return GeneralRowItem(initialSize, height: 16, stableId: stableId, backgroundColor: .clear) + } + } +} + +private func leftSidebarEntries(_ filterData: FilterData, _ badges: ChatListFilterBadges) -> [LeftSibarBarEntry] { + var index: Int = 1 + + var entries:[LeftSibarBarEntry] = [] + entries.append(.topOffset) + + entries.append(.allChats(selected: filterData.filter == nil, unreadCount: badges.total, hasUnmutedUnread: true)) + + for filter in filterData.tabs { + let badge = badges.count(for: filter) + entries.append(.folder(index: index, selected: filter.id == filterData.filter?.id, filter: filter, unreadCount: badge?.count ?? 0, hasUnmutedUnread: badge?.hasUnmutedUnread ?? false)) + index += 1 + } + + return entries +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:LeftSidebarArguments) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + +class LeftSidebarController: TelegramGenericViewController { + + let filterData: Signal + let updateFilter: (_ f:(FilterData)->FilterData)->Void + + private let disposable = MetaDisposable() + + init(_ context: AccountContext, filterData: Signal, updateFilter: @escaping(_ f:(FilterData)->FilterData)->Void) { + self.filterData = filterData + self.updateFilter = updateFilter + super.init(context) + self.bar = .init(height: 0) + } + + deinit { + disposable.dispose() + } + + override func viewDidLoad() { + super.viewDidLoad() + let context = self.context + + let arguments = LeftSidebarArguments(context: context, callback: { [weak self] filter in + self?.updateFilter { state in + return state.withUpdatedFilter(filter) + } + + let rootNavigation = context.sharedContext.bindings.rootNavigation() + + let leftController = context.sharedContext.bindings.mainController() + leftController.chatListNavigation.close(animated: context.sharedContext.layout != .single || rootNavigation.stackCount == 1) + + if context.sharedContext.layout == .single { + rootNavigation.close(animated: true) + } + leftController.showChatList() + + }, menuItems: { filter in + return filterContextMenuItems(filter, context: context) + }) + let initialSize = self.atomicSize + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let signal: Signal = combineLatest(queue: prepareQueue, filterData, chatListFilterItems(account: context.account, accountManager: context.sharedContext.accountManager), appearanceSignal) |> map { filterData, badges, appearance in + let entries = leftSidebarEntries(filterData, badges).map { AppearanceWrapperEntry.init(entry: $0, appearance: appearance) } + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.with { $0 }, arguments: arguments) + } |> deliverOnMainQueue + + self.genericView.tableView.alwaysOpenRowsOnMouseUp = true + + disposable.set(signal.start(next: { [weak self] transition in + + guard let `self` = self else { + return + } + self.genericView.tableView.merge(with: transition) + self.readyOnce() + + let range = NSMakeRange(2, self.genericView.tableView.count - 2) + + self.genericView.tableView.resortController = TableResortController(resortRange: range, start: { _ in }, resort: { _ in }, complete: { from, to in + _ = updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in + var filters = filters + filters.move(at: from - range.location, to: to - range.location) + return filters + }).start() + }) + })) + } +} diff --git a/Telegram-Mac/LeftSidebarFolderItem.swift b/Telegram-Mac/LeftSidebarFolderItem.swift new file mode 100644 index 0000000000..4a8d2de1f8 --- /dev/null +++ b/Telegram-Mac/LeftSidebarFolderItem.swift @@ -0,0 +1,249 @@ +// +// LeftSidebarFolderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SwiftSignalKit + +extension FolderIcon { + convenience init(_ filter: ChatListFilter?) { + if let filter = filter { + if let emoticon = filter.emoticon { + self.init(emoticon: .emoji(emoticon)) + } else { + switch chatListFilterType(filter) { + case .bots: + self.init(emoticon: .bots) + case .channels: + self.init(emoticon: .channels) + case .contacts: + self.init(emoticon: .personal) + case .groups: + self.init(emoticon: .groups) + case .nonContacts: + self.init(emoticon: .personal) + case .unmuted: + self.init(emoticon: .unmuted) + case .unread: + self.init(emoticon: .unread) + case .generic: + self.init(emoticon: .folder) + } + } + } else { + self.init(emoticon: .allChats) + } + } +} + +class LeftSidebarFolderItem: TableRowItem { + + fileprivate let folder: ChatListFilter? + fileprivate let selected: Bool + fileprivate let callback: (ChatListFilter?)->Void + fileprivate let menuItems: (ChatListFilter?)-> [ContextMenuItem] + + let icon: CGImage + let badge: CGImage? + let nameLayout: TextViewLayout + + + init(_ initialSize: NSSize, folder: ChatListFilter?, selected: Bool, unreadCount: Int, hasUnmutedUnread: Bool, callback: @escaping(ChatListFilter?)->Void, menuItems: @escaping(ChatListFilter?) -> [ContextMenuItem]) { + self.folder = folder + self.selected = selected + self.callback = callback + self.menuItems = menuItems + var folderIcon = FolderIcon(folder).icon(for: selected ? .sidebarActive : .sidebar) + nameLayout = TextViewLayout(.initialize(string: folder != nil ? folder!.title : L10n.chatListFilterAllChats, color: !selected ? NSColor.white.withAlphaComponent(0.5) : .white, font: .medium(10)), alignment: .center) + nameLayout.measure(width: initialSize.width - 10) + + + + let generateIcon:()->CGImage? = { + let icon: CGImage? + if unreadCount > 0 { + + let textColor: NSColor + if selected { + textColor = .black + } else { + textColor = .white + } + + let attributedString = NSAttributedString.initialize(string: "\(unreadCount.prettyNumber)", color: textColor, font: .medium(.short), coreText: true) + let textLayout = TextNode.layoutText(maybeNode: nil, attributedString, nil, 1, .start, NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude), nil, false, .center) + var size = NSMakeSize(textLayout.0.size.width + 8, textLayout.0.size.height + 5) + size = NSMakeSize(max(size.height,size.width), size.height) + + let badge = generateImage(size, rotatedContext: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + if selected { + ctx.setFillColor(.white) + } else if hasUnmutedUnread { + ctx.setFillColor(NSColor.accentIcon.cgColor) + } else { + ctx.setFillColor(NSColor.grayIcon.cgColor) + } + ctx.round(size, size.height/2.0) + ctx.fill(rect) + +// ctx.setBlendMode(.clear) + + let focus = rect.focus(textLayout.0.size) + textLayout.1.draw(focus.offsetBy(dx: 0, dy: -1), in: ctx, backingScaleFactor: 2.0, backgroundColor: .white) + + })! + + folderIcon = generateImage(folderIcon.systemSize, contextGenerator: { size, ctx in + let rect = NSMakeRect(0, 0, size.width, size.height) + ctx.clear(rect) + + ctx.draw(folderIcon, in: rect.focus(folderIcon.systemSize)) + + ctx.clip(to: NSMakeRect(rect.width - badge.systemSize.width / 2 - 11 / System.backingScale, rect.height - badge.systemSize.height + 5 / System.backingScale, badge.systemSize.width + 4, badge.systemSize.height + 4), mask: badge) + + ctx.clear(rect) + + // ctx.draw(badge, in: rect) + })! + + icon = badge + + } else { + icon = nil + } + return icon + } + self.badge = generateIcon() + self.icon = folderIcon + super.init(initialSize) + } + + override var stableId: AnyHashable { + return folder?.id ?? -1 + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + return .single(self.menuItems(folder)) + } + + override var height: CGFloat { + return 32 + 8 + 8 + nameLayout.layoutSize.height + 4 + } + + override func viewClass() -> AnyClass { + return LeftSidebarFolderView.self + } + +} + + +private final class LeftSidebarFolderView : TableRowView { + private let imageView = ImageView(frame: NSMakeRect(0, 0, 32, 32)) + private let badgeView = ImageView() + private let textView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + addSubview(badgeView) + textView.userInteractionEnabled = false + textView.isSelectable = false + textView.isEventLess = true + badgeView.isEventLess = true + imageView.isEventLess = true + } + + override func updateIsResorting() { + updateHighlight(animated: true) + } + + func updateHighlight(animated: Bool = true) { + guard let item = item as? LeftSidebarFolderItem else { + return + } + if !item.selected, mouseInside(), (NSEvent.pressedMouseButtons & (1 << 0)) != 0 { + self.imageView.change(opacity: 0.8, animated: animated) + self.textView.change(opacity: 0.8, animated: animated) + } else { + self.imageView.change(opacity: 1.0, animated: animated) + self.textView.change(opacity: 1.0, animated: animated) + } + } + + + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateHighlight() + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateHighlight() + } + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateHighlight() + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + updateHighlight() + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + updateHighlight() + + if mouseInside() { + guard let item = item as? LeftSidebarFolderItem else { + return + } + item.callback(item.folder) + } + } + + override var backdorColor: NSColor { + return .clear + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? LeftSidebarFolderItem else { + return + } + // imageView.animates = animated + imageView.image = item.icon + + textView.update(item.nameLayout) + + + // badgeView.animates = animated + badgeView.image = item.badge + badgeView.sizeToFit() + + updateHighlight(animated: animated) + + needsLayout = true + } + + override func layout() { + super.layout() + + imageView.centerX(y: 8) + textView.centerX(y: imageView.frame.maxY + 4) + badgeView.setFrameOrigin(NSMakePoint(imageView.frame.maxX - badgeView.frame.width / 2 - 4, imageView.frame.minY - 4)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/LegacyImportAuthorization.swift b/Telegram-Mac/LegacyImportAuthorization.swift deleted file mode 100644 index 535384211e..0000000000 --- a/Telegram-Mac/LegacyImportAuthorization.swift +++ /dev/null @@ -1,411 +0,0 @@ -// -// LegacyImportAuthorization.swift -// Telegram -// -// Created by keepcoder on 05/03/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - - -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac -import MtProtoKitMac - -enum AuthorizationLegacyData { - case data(masterDatacenterId: Int32, userId:Int32, groups:[String: [String: Data]], peers:[Peer], secretMessages:[StoreMessage], resources: [(MediaResource, Data)], secretState:[PeerId: SecretChatStateBridge], passcode:String?) - case passcodeRequired - case none -} - -private enum LegacyGroupResult { - case data(NSDictionary) - case passcodeRequired - case empty -} - -private func keychainData(from dictionary: NSDictionary) -> [String : Data] { - var data:[String: Data] = [:] - for (key, value) in dictionary { - data[key as! String] = NSKeyedArchiver.archivedData(withRootObject: value) - } - return data -} - -private let keychainGroups:NSDictionary? = { - if let keychainData = SSKeychain.passwordData(forService: "Telegram", account: "authkeys") { - return NSKeyedUnarchiver.unarchiveObject(with: keychainData) as? NSDictionary - } else { - return nil - } -} () - -private func legacyGroupData (_ groupName: String, passcode:Data) -> LegacyGroupResult { - assert(passcode.count == 64) - - let groupData:NSData? - - #if APP_STORE - if let data = keychainGroups?[groupName] as? NSData { - groupData = data - } else { - groupData = nil - } - #else - let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] - let applicationName = "Telegram" - let dataDirectory = applicationSupportPath + "/\(applicationName)/encrypt-mtkeychain" - let path = dataDirectory + "/ru.keepcoder.Telegram_\(groupName).bin" - NSLog("\(path)") - - groupData = NSData(contentsOf: URL(fileURLWithPath: path)) - #endif - - - - if let groupData = groupData, groupData.length >= 8 { - let decrypt = NSMutableData(data: groupData.subdata(with: NSMakeRange(4, groupData.length - 8))) - let modifiedIv = NSMutableData(data: passcode.subdata(in: 32 ..< 64)) - MTAesDecryptInplaceAndModifyIv(decrypt, passcode.subdata(in: 0 ..< 32), modifiedIv) - - var hash:Int32 = 0 - var length:Int32 = 0 - groupData.getBytes(&hash, range: NSMakeRange(groupData.length - 4, 4)) - groupData.getBytes(&length, range: NSMakeRange(0, 4)) - - let decryptedHash = MTMurMurHash32(decrypt.bytes, length) - - if hash != decryptedHash { - return .passcodeRequired - } - - let object = NSKeyedUnarchiver.unarchiveObject(with: decrypt.subdata(with: NSMakeRange(0, Int(length)))) - - if let object = object as? NSDictionary { - return .data(object) - } - - } - return .empty -} - -func legacyAuthData(passcode:Data, textPasscode:String? = nil) -> AuthorizationLegacyData { - - var groupsData:[String : [String: Data]] = [:] - - let groups:[String] = ["persistent", "primes", "temp"] - var masterDatacenterId:Int32? - var userId:Int32? - var sqlKey:String? - for group in groups { - switch legacyGroupData(group, passcode: passcode) { - case .passcodeRequired: - return .passcodeRequired - case let .data(data): - if let dcId = data["dc_id"] as? NSNumber, let id = data["user_id"] as? NSNumber { - masterDatacenterId = dcId.int32Value - userId = id.int32Value - } - if let ekey = data["e_key"] as? String { - sqlKey = ekey - } - groupsData[group] = keychainData(from : data) - - default: - break - } - } - - - - if let masterDatacenterId = masterDatacenterId, let userId = userId { - - var peers:[Peer] = [] - var messages:[StoreMessage] = [] - var participantPeerIds:Set = Set() - var resources: [(MediaResource, Data)] = [] - var secretState:[PeerId: SecretChatStateBridge] = [:] - - - if let sSize = fileSize(legacySqlPath), sSize > 0, let ySize = fileSize(legacyYapPath), ySize > 0, let sqlKey = sqlKey, let keyData = sqlKey.data(using: .utf8), FileManager.default.fileExists(atPath: legacySqlPath), FileManager.default.fileExists(atPath: legacyYapPath), let sql = SqliteInterface(databasePath: legacySqlPath), let yap = SqliteInterface(databasePath: legacyYapPath) { - - - if sql.unlock(password: keyData) { - - sql.select("select serialized from encrypted_chats", { value -> Bool in - if let chat = MacosLegacy.parse(Buffer(data: value.getData(at: 0))) as? MacosLegacy.EncryptedChat { - switch chat { - case let .encryptedChat(chat): - let secret = TelegramSecretChat(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: chat.id), creationDate: chat.date, regularPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: chat.adminId == userId ? chat.participantId : chat.adminId), accessHash: chat.accessHash, role: chat.adminId == userId ? .creator : .participant, embeddedState: .terminated, messageAutoremoveTimeout: nil) - peers.append(secret) - participantPeerIds.insert(secret.regularPeerId) - secretState[secret.id] = SecretChatStateBridge(role: chat.adminId == userId ? .creator : .participant) - } - } - - return true - }) - - var secretKeys:[Int64: SecretFileEncryptionKey] = [:] - - yap.select("select data, key from database2 where collection = \"encrypted_image_collection\"", { keyIvValue -> Bool in - if let dict = NSKeyedUnarchiver.unarchiveObject(with: keyIvValue.getData(at: 0)) as? NSDictionary { - if let aesKey = dict["key"] as? Data, let aesIv = dict["iv"] as? Data { - secretKeys[keyIvValue.getInt64(at: 1)] = SecretFileEncryptionKey(aesKey: aesKey, aesIv: aesIv) - } - } - return true - }) - - - - for peer in peers { - sql.select("select serialized, message_text from messages where peer_id in (\(peer.id.id))", { value -> Bool in - - var text = value.getString(at: 1) - - let message = MacosLegacy.parse(Buffer(data: value.getData(at: 0))) - if let message = message as? MacosLegacy.Message { - switch message { - case let .destructMessage(message): - - guard message.dstate == 2 else { - break - } - - var media:[Media] = [] - switch message.media { - case let .messageMediaGeo(geo: geo): - switch geo { - case let .geoPoint(long, lat): - media.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)) - default: - media.append(TelegramMediaMap(latitude: 0, longitude: 0, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)) - } - case let .messageMediaContact(phoneNumber, firstName, lastName, userId): - let peerId:PeerId? = userId != 0 ? PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) : nil - media.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: peerId)) - case let .messageMediaPhoto(photo, caption): - switch photo { - case let .photo(_, id, accessHash, _, sizes): - - if let key = secretKeys[id] { - text = caption - var representations: [TelegramMediaImageRepresentation] = [] - - for size in sizes { - switch size { - case let .photoCachedSize(_, _, w, h, bytes): - let resource = LocalFileMediaResource(fileId: arc4random64()) - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) - resources.append((resource, bytes.makeData())) - - - case let .photoSize(_, location, w, h, size): - switch location { - case let .fileLocation(dcId, _, _, _): - let resource = SecretFileMediaResource(fileId: id, accessHash: accessHash, size: nil, decryptedSize: size, datacenterId: Int(dcId), key: key) - - representations.append(TelegramMediaImageRepresentation(dimensions: NSMakeSize(CGFloat(w), CGFloat(h)), resource: resource)) - - let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: id), representations: representations) - - media.append(image) - default: - break - } - - case .photoSizeEmpty: - break - } - } - - } - - - break - default: - break - } - break - case let .messageMediaDocument(document, caption): - - switch document { - case let .document(id, accessHash, _, mimeType, size, thumb, dcId, _, attributes): - if let key = secretKeys[id] { - text = caption - var parsedAttributes: [TelegramMediaFileAttribute] = [] - for attribute in attributes { - switch attribute { - case let .documentAttributeFilename(fileName): - parsedAttributes.append(.FileName(fileName: fileName)) - case let .documentAttributeSticker(_, alt, _, _): - parsedAttributes.append(.Sticker(displayText: alt, packReference: nil, maskData: nil)) - case .documentAttributeHasStickers: - parsedAttributes.append(.HasLinkedStickers) - case let .documentAttributeImageSize(w, h): - parsedAttributes.append(.ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h)))) - case .documentAttributeAnimated: - parsedAttributes.append(.Animated) - case let .documentAttributeVideo(duration, w, h): - parsedAttributes.append(.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: [])) - case let .documentAttributeAudio(flags, duration, title, performer, waveform): - let isVoice = (flags & (1 << 10)) != 0 - var waveformBuffer: MemoryBuffer? - if let waveform = waveform { - waveformBuffer = MemoryBuffer(waveform) - } - parsedAttributes.append(.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: performer, waveform: waveformBuffer)) - default: - break - } - } - - var previewRepresentations: [TelegramMediaImageRepresentation] = [] - - switch thumb { - case let .photoCachedSize(_, _, w, h, bytes): - - let resource = LocalFileMediaResource(fileId: arc4random64()) - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: NSMakeSize(CGFloat(w), CGFloat(h)), resource: resource)) - resources.append((resource, bytes.makeData())) - default: - break - } - - let resource = SecretFileMediaResource(fileId: id, accessHash: accessHash, size: nil, decryptedSize: size, datacenterId: Int(dcId), key: key) - - - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: id), resource: resource, previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) - media.append(fileMedia) - - } - default: - break - } - default: - break - } - - var attributes:[MessageAttribute] = [] - - if message.ttlSeconds > 0 { - var countdownBeginTime:Int32? = nil - if message.destructionTime > 0 { - countdownBeginTime = message.destructionTime - message.ttlSeconds - } - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: message.ttlSeconds, countdownBeginTime: countdownBeginTime)) - } - - var flags:StoreMessageFlags = StoreMessageFlags() - - if message.fromId != userId { - flags.insert(.Incoming) - } - let tags = tagsForStoreMessage(incoming: message.fromId == peer.id.id, attributes: [], media: media, textEntities: nil) - messages.append(StoreMessage(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.SecretIncoming, id: message.id), globallyUniqueId: message.random, timestamp: message.date, flags: flags, tags: tags.0, globalTags: tags.1, forwardInfo: nil, authorId: PeerId(namespace: Namespaces.Peer.CloudUser, id: message.fromId), text: text, attributes: attributes, media: media)) - } - } - return true - }) - } - - let participantIds = participantPeerIds.map({String($0.id)}).joined(separator: ",") - sql.select("select serialized from users where n_id in (\(participantIds))", { value -> Bool in - - if let user = MacosLegacy.parse(Buffer(data: value.getData(at: 0))) as? MacosLegacy.User { - switch user { - case let .user(user): - var representations:[TelegramMediaImageRepresentation] = [] - if let photo = user.photo { - switch photo { - case let .userProfilePhoto(photo): - if let small = mediaResourceFromApiFileLocation(photo.photoSmall, size: nil) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: 100, height: 100), resource: small)) - } - if let big = mediaResourceFromApiFileLocation(photo.photoBig, size: nil) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: 100, height: 100), resource: big)) - } - default: - break - } - } - peers.append(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: user.id), accessHash: user.accessHash, firstName: user.firstName, lastName: user.lastName, username: user.username, phone: user.phone, photo: representations, botInfo: nil, flags: [])) - default: - break - } - } - return true - }) - - - } - - } - - return .data(masterDatacenterId: masterDatacenterId, userId: userId, groups: groupsData, peers: peers, secretMessages: messages, resources: resources, secretState: secretState, passcode: textPasscode) - } - - return .none -} - -private func legacyMediaImageRepresentationsFromApiSizes(_ sizes: [MacosLegacy.PhotoSize]) -> [TelegramMediaImageRepresentation] { - var representations: [TelegramMediaImageRepresentation] = [] - for size in sizes { - switch size { - case let .photoCachedSize(_, location, w, h, bytes): - if let resource = mediaResourceFromApiFileLocation(location, size: bytes.size) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) - } - case let .photoSize(_, location, w, h, size): - if let resource = mediaResourceFromApiFileLocation(location, size: Int(size)) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) - } - case .photoSizeEmpty: - break - } - } - return representations -} - -private func mediaResourceFromApiFileLocation(_ fileLocation: MacosLegacy.FileLocation, size: Int?) -> TelegramMediaResource? { - switch fileLocation { - case let .fileLocation(dcId, volumeId, localId, secret): - return CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size) - case .fileLocationUnavailable: - return nil - } -} - -private var legacySqlPath: String { - - let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] - let path = applicationSupportPath + "/" + "Telegram" + "/database/" - #if APP_STORE - return path + "encrypted.sqlite" - #else - return path + "encrypted6.sqlite" - #endif -} - -private var legacyYapPath: String { - let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] - return applicationSupportPath + "/" + "Telegram" + "/database/" + "yap_store-t143.sqlite" -} - -func clearLegacyData() { - let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] - let applicationName = "Telegram" - let dataDirectory = applicationSupportPath + "/\(applicationName)/encrypt-mtkeychain" - try? FileManager.default.removeItem(atPath: dataDirectory) -} - -func emptyPasscodeData() -> Data { - - var data:Data = Data() - var zero:UInt8 = 0 - for _ in 0 ..< 64 { - data.append(&zero, count: 1) - } - return data -} diff --git a/Telegram-Mac/LinkInvationController.swift b/Telegram-Mac/LinkInvationController.swift index 0f5df59748..b200a9888a 100644 --- a/Telegram-Mac/LinkInvationController.swift +++ b/Telegram-Mac/LinkInvationController.swift @@ -9,9 +9,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private enum GroupLinkInvationEntryStableId : Hashable { case section(Int) @@ -28,29 +29,6 @@ private enum GroupLinkInvationEntryStableId : Hashable { return 1000000 } } - - static func ==(lhs:GroupLinkInvationEntryStableId, rhs:GroupLinkInvationEntryStableId) -> Bool { - switch lhs { - case let .section(id): - if case .section(id) = rhs { - return true - } else { - return false - } - case let .index(id): - if case .index(id) = rhs { - return true - } else { - return false - } - case .loading: - if case .loading = rhs { - return true - } else { - return false - } - } - } } private enum GroupLinkInvationEntry : Identifiable, Comparable { @@ -136,13 +114,13 @@ private func <(lhs:GroupLinkInvationEntry, rhs:GroupLinkInvationEntry) -> Bool { } final class GroupLinkInvationArguments { - let account:Account + let context: AccountContext let copy:()->Void let share:()->Void let revoke:()->Void - init(account:Account, copy:@escaping()->Void, share:@escaping()->Void, revoke:@escaping()->Void) { - self.account = account + init(context: AccountContext, copy:@escaping()->Void, share:@escaping()->Void, revoke:@escaping()->Void) { + self.context = context self.copy = copy self.share = share self.revoke = revoke @@ -178,21 +156,21 @@ private func groupInvationEntries(view:PeerView, arguments:GroupLinkInvationArgu entries.append(.link(sectionId: sectionId, uniqueIdx: uniqueId, text: link)) uniqueId += 1 - entries.append(.text(sectionId: sectionId, uniqueIdx: uniqueId, text: isGroup ? tr(.groupInvationGroupDescription) : tr(.groupInvationChannelDescription))) + entries.append(.text(sectionId: sectionId, uniqueIdx: uniqueId, text: isGroup ? L10n.groupInvationGroupDescription : L10n.groupInvationChannelDescription)) uniqueId += 1 entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: tr(.groupInvationCopyLink), callback: { + entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: L10n.groupInvationCopyLink, callback: { arguments.copy() })) uniqueId += 1 - entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: tr(.groupInvationRevoke), callback: { + entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: L10n.groupInvationRevoke, callback: { arguments.revoke() })) uniqueId += 1 - entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: tr(.groupInvationShare), callback: { + entries.append(.action(sectionId: sectionId, uniqueIdx: uniqueId, text: L10n.groupInvationShare, callback: { arguments.share() })) uniqueId += 1 @@ -231,36 +209,36 @@ class LinkInvationController: TableViewController { private let revokeLinkDisposable = MetaDisposable() private let disposable:MetaDisposable = MetaDisposable() - init(account:Account, peerId:PeerId) { + init(_ context: AccountContext, peerId:PeerId) { self.peerId = peerId - super.init(account) + super.init(context) } override func viewDidLoad() { super.viewDidLoad() - let account = self.account + let context = self.context let peerId = self.peerId let link:Atomic = Atomic(value: nil) let peer:Atomic = Atomic(value: nil) - let arguments = GroupLinkInvationArguments(account: account, copy: { [weak self] in + let arguments = GroupLinkInvationArguments(context: context, copy: { [weak self] in if let link = link.modify({$0}) { copyToClipboard(link) - self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied))) + self?.show(toaster: ControllerToaster(text: tr(L10n.shareLinkCopied))) } }, share: { if let link = link.modify({$0}) { - showModal(with: ShareModalController(ShareLinkObject(account, link: link)), for: mainWindow) + showModal(with: ShareModalController(ShareLinkObject(context, link: link)), for: mainWindow) } }, revoke: { [weak self] in - if let peer = peer.modify({$0}), let account = self?.account { - let info = peer.isChannel ? tr(.linkInvationChannelConfirmRevoke) : tr(.linkInvationGroupConfirmRevoke) - let signal = confirmSignal(for: mainWindow, header: appName, information: info, okTitle: tr(.linkInvationConfirmOk)) + if let peer = peer.modify({$0}), let context = self?.context { + let info = peer.isChannel ? L10n.linkInvationChannelConfirmRevoke : L10n.linkInvationGroupConfirmRevoke + let signal = confirmSignal(for: mainWindow, information: info, okTitle: L10n.linkInvationConfirmOk) |> filter {$0} - |> mapToSignal { _ -> Signal in - return ensuredExistingPeerExportedInvitation(account: account, peerId: peer.id, revokeExisted: true) + |> mapToSignal { _ -> Signal in + return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peer.id, revokeExisted: true) } self?.revokeLinkDisposable.set(signal.start()) } @@ -268,7 +246,7 @@ class LinkInvationController: TableViewController { let previous:Atomic<[GroupLinkInvationEntry]> = Atomic(value: []) let atomicSize = self.atomicSize - let apply = account.viewTracker.peerView( peerId) |> deliverOn(prepareQueue) |> map { view -> TableUpdateTransition in + let apply = context.account.viewTracker.peerView( peerId) |> deliverOn(prepareQueue) |> map { view -> TableUpdateTransition in let exportLink:String? if let cachedData = view.cachedData as? CachedChannelData { @@ -291,7 +269,7 @@ class LinkInvationController: TableViewController { self?.readyOnce() })) - revokeLinkDisposable.set(ensuredExistingPeerExportedInvitation(account: account, peerId: peerId).start()) + revokeLinkDisposable.set(ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId).start()) } deinit { diff --git a/Telegram-Mac/ListViewIntermediateState.swift b/Telegram-Mac/ListViewIntermediateState.swift index e7d1a2d357..10c1978591 100644 --- a/Telegram-Mac/ListViewIntermediateState.swift +++ b/Telegram-Mac/ListViewIntermediateState.swift @@ -8,7 +8,7 @@ import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit public enum ListViewCenterScrollPositionOverflow { case Top diff --git a/Telegram-Mac/LoadingTableItem.swift b/Telegram-Mac/LoadingTableItem.swift index 9a802cc59a..78af24e790 100644 --- a/Telegram-Mac/LoadingTableItem.swift +++ b/Telegram-Mac/LoadingTableItem.swift @@ -11,11 +11,10 @@ import TGUIKit class LoadingTableItem: GeneralRowItem { - init(_ initialSize: NSSize, height: CGFloat, stableId: AnyHashable) { - super.init(initialSize, height: height, stableId: stableId) + init(_ initialSize: NSSize, height: CGFloat, stableId: AnyHashable, viewType: GeneralViewType = .legacy) { + super.init(initialSize, height: height, stableId: stableId, viewType: viewType) } - override func viewClass() -> AnyClass { return LoadingTableRowView.self } @@ -23,11 +22,13 @@ class LoadingTableItem: GeneralRowItem { class LoadingTableRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) private let progress: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) required init(frame frameRect: NSRect) { super.init(frame: frameRect) - addSubview(progress) + containerView.addSubview(progress) + addSubview(containerView) } override func viewDidMoveToWindow() { @@ -38,9 +39,30 @@ class LoadingTableRowView : TableRowView { } } + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + if let item = item as? GeneralRowItem { + containerView.background = backdorColor + backgroundColor = item.viewType.rowBackground + } + } + override func layout() { super.layout() - progress.center() + + if let item = item as? GeneralRowItem { + switch item.viewType { + case .legacy: + containerView.frame = bounds + case .modern: + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + } + self.containerView.setCorners(item.viewType.corners) + progress.center() + } } deinit { @@ -49,6 +71,19 @@ class LoadingTableRowView : TableRowView { override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) + + if let item = item as? GeneralRowItem { + let contentRect: NSRect + switch item.viewType { + case .legacy: + contentRect = bounds + case .modern: + contentRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + } + self.containerView.change(size: contentRect.size, animated: animated, corners: item.viewType.corners) + self.containerView.change(pos: contentRect.origin, animated: animated) + } + } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/Localizable.swift b/Telegram-Mac/Localizable.swift index f93dfb2807..a6fdc863ba 100644 --- a/Telegram-Mac/Localizable.swift +++ b/Telegram-Mac/Localizable.swift @@ -1,4246 +1,7461 @@ -// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen +// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen import Foundation +// swiftlint:disable superfluous_disable_command // swiftlint:disable file_length -// swiftlint:disable line_length -// swiftlint:disable type_body_length -enum L10n { +// swiftlint:disable identifier_name line_length type_body_length +internal final class L10n { /// Default - case defaultSoundName + internal static var defaultSoundName: String { return L10n.tr("Localizable", "DefaultSoundName") } /// None - case notificationSettingsToneNone - /// incorrect password - case passwordHashInvalid - /// code expired - case phoneCodeExpired - /// phone code invalid - case phoneCodeInvalid - /// invalid phone number - case phoneNumberInvalid + internal static var notificationSettingsToneNone: String { return L10n.tr("Localizable", "NotificationSettingsToneNone") } + /// Incorrect password + internal static var passwordHashInvalid: String { return L10n.tr("Localizable", "PASSWORD_HASH_INVALID") } + /// Code expired + internal static var phoneCodeExpired: String { return L10n.tr("Localizable", "PHONE_CODE_EXPIRED") } + /// Invalid code + internal static var phoneCodeInvalid: String { return L10n.tr("Localizable", "PHONE_CODE_INVALID") } + /// Invalid phone number + internal static var phoneNumberInvalid: String { return L10n.tr("Localizable", "PHONE_NUMBER_INVALID") } + /// An error occurred. Please try again later + internal static var unknownError: String { return L10n.tr("Localizable", "UnknownError") } /// You - case you + internal static var you: String { return L10n.tr("Localizable", "You") } /// Check for Updates - case _1000Title + internal static var _1000Title: String { return L10n.tr("Localizable", "1000.title") } /// Telegram - case _1XtHYUBwTitle + internal static var _1XtHYUBwTitle: String { return L10n.tr("Localizable", "1Xt-HY-uBw.title") } /// Transformations - case _2oIRnZJCTitle + internal static var _2oIRnZJCTitle: String { return L10n.tr("Localizable", "2oI-Rn-ZJC.title") } /// Enter Full Screen - case _4J7DPTxaTitle + internal static var _4J7DPTxaTitle: String { return L10n.tr("Localizable", "4J7-dP-txa.title") } /// Quit Telegram - case _4sb4sVLiTitle - /// About Telegram - case _5kVVbQxSTitle + internal static var _4sb4sVLiTitle: String { return L10n.tr("Localizable", "4sb-4s-VLi.title") } /// Edit - case _5QFOaP0TTitle + internal static var _5QFOaP0TTitle: String { return L10n.tr("Localizable", "5QF-Oa-p0T.title") } + /// About Telegram + internal static var _5kVVbQxSTitle: String { return L10n.tr("Localizable", "5kV-Vb-QxS.title") } /// Redo - case _6dhZSVamTitle + internal static var _6dhZSVamTitle: String { return L10n.tr("Localizable", "6dh-zS-Vam.title") } /// Correct Spelling Automatically - case _78YHA62vTitle + internal static var _78YHA62vTitle: String { return L10n.tr("Localizable", "78Y-hA-62v.title") } /// Substitutions - case _9icFLObxTitle + internal static var _9icFLObxTitle: String { return L10n.tr("Localizable", "9ic-FL-obx.title") } /// Smart Copy/Paste - case _9yt4BNSMTitle + internal static var _9yt4BNSMTitle: String { return L10n.tr("Localizable", "9yt-4B-nSM.title") } /// Free messaging app for macOS based on MTProto for speed and security. - case aboutDescription - /// Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while. \n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions. - case accountConfirmAskQuestion + internal static var aboutDescription: String { return L10n.tr("Localizable", "About.Description") } + /// Tinted + internal static var accentColorsTinted: String { return L10n.tr("Localizable", "AccentColors.Tinted") } + /// Please note that Telegram Support is run by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions. + internal static var accountConfirmAskQuestion: String { return L10n.tr("Localizable", "Account.Confirm.AskQuestion") } /// Open FAQ - case accountConfirmGoToFaq + internal static var accountConfirmGoToFaq: String { return L10n.tr("Localizable", "Account.Confirm.GoToFaq") } /// Log out? - case accountConfirmLogout + internal static var accountConfirmLogout: String { return L10n.tr("Localizable", "Account.Confirm.Logout") } /// Remember, logging out cancels all your Secret Chats. - case accountConfirmLogoutText - /// New Account - case accountsControllerNewAccount + internal static var accountConfirmLogoutText: String { return L10n.tr("Localizable", "Account.Confirm.LogoutText") } /// About - case accountSettingsAbout - /// Appearance - case accountSettingsAppearance + internal static var accountSettingsAbout: String { return L10n.tr("Localizable", "AccountSettings.About") } + /// Add Account + internal static var accountSettingsAddAccount: String { return L10n.tr("Localizable", "AccountSettings.AddAccount") } /// Ask a Question - case accountSettingsAskQuestion + internal static var accountSettingsAskQuestion: String { return L10n.tr("Localizable", "AccountSettings.AskQuestion") } /// Bio - case accountSettingsBio + internal static var accountSettingsBio: String { return L10n.tr("Localizable", "AccountSettings.Bio") } /// English - case accountSettingsCurrentLanguage + internal static var accountSettingsCurrentLanguage: String { return L10n.tr("Localizable", "AccountSettings.CurrentLanguage") } + /// Data and Storage + internal static var accountSettingsDataAndStorage: String { return L10n.tr("Localizable", "AccountSettings.DataAndStorage") } + /// Delete Account + internal static var accountSettingsDeleteAccount: String { return L10n.tr("Localizable", "AccountSettings.DeleteAccount") } /// Telegram FAQ - case accountSettingsFAQ + internal static var accountSettingsFAQ: String { return L10n.tr("Localizable", "AccountSettings.FAQ") } + /// Chat Folders + internal static var accountSettingsFilters: String { return L10n.tr("Localizable", "AccountSettings.Filters") } /// General - case accountSettingsGeneral + internal static var accountSettingsGeneral: String { return L10n.tr("Localizable", "AccountSettings.General") } /// Language - case accountSettingsLanguage + internal static var accountSettingsLanguage: String { return L10n.tr("Localizable", "AccountSettings.Language") } /// Logout - case accountSettingsLogout + internal static var accountSettingsLogout: String { return L10n.tr("Localizable", "AccountSettings.Logout") } /// Notifications - case accountSettingsNotifications + internal static var accountSettingsNotifications: String { return L10n.tr("Localizable", "AccountSettings.Notifications") } + /// Telegram Passport + internal static var accountSettingsPassport: String { return L10n.tr("Localizable", "AccountSettings.Passport") } /// Privacy and Security - case accountSettingsPrivacyAndSecurity + internal static var accountSettingsPrivacyAndSecurity: String { return L10n.tr("Localizable", "AccountSettings.PrivacyAndSecurity") } + /// Proxy + internal static var accountSettingsProxy: String { return L10n.tr("Localizable", "AccountSettings.Proxy") } + /// Read Articles + internal static var accountSettingsReadArticles: String { return L10n.tr("Localizable", "AccountSettings.ReadArticles") } /// Set a Bio - case accountSettingsSetBio + internal static var accountSettingsSetBio: String { return L10n.tr("Localizable", "AccountSettings.SetBio") } /// Set Profile Photo - case accountSettingsSetProfilePhoto + internal static var accountSettingsSetProfilePhoto: String { return L10n.tr("Localizable", "AccountSettings.SetProfilePhoto") } /// Set a Username - case accountSettingsSetUsername + internal static var accountSettingsSetUsername: String { return L10n.tr("Localizable", "AccountSettings.SetUsername") } /// Stickers - case accountSettingsStickers - /// Storage - case accountSettingsStorage + internal static var accountSettingsStickers: String { return L10n.tr("Localizable", "AccountSettings.Stickers") } + /// Appearance + internal static var accountSettingsTheme: String { return L10n.tr("Localizable", "AccountSettings.Theme") } /// Username - case accountSettingsUsername + internal static var accountSettingsUsername: String { return L10n.tr("Localizable", "AccountSettings.Username") } + /// Gram Wallet + internal static var accountSettingsWallet: String { return L10n.tr("Localizable", "AccountSettings.Wallet") } + /// Connected + internal static var accountSettingsProxyConnected: String { return L10n.tr("Localizable", "AccountSettings.Proxy.Connected") } + /// Connecting + internal static var accountSettingsProxyConnecting: String { return L10n.tr("Localizable", "AccountSettings.Proxy.Connecting") } + /// Disabled + internal static var accountSettingsProxyDisabled: String { return L10n.tr("Localizable", "AccountSettings.Proxy.Disabled") } + /// Update + internal static var accountViewControllerUpdate: String { return L10n.tr("Localizable", "AccountViewController.Update") } + /// failed + internal static var accountViewControllerDescFailed: String { return L10n.tr("Localizable", "AccountViewController.Desc.Failed") } + /// updated + internal static var accountViewControllerDescUpdated: String { return L10n.tr("Localizable", "AccountViewController.Desc.Updated") } + /// New Account + internal static var accountsControllerNewAccount: String { return L10n.tr("Localizable", "AccountsController.NewAccount") } /// Add Admin - case adminsAddAdmin + internal static var adminsAddAdmin: String { return L10n.tr("Localizable", "Admins.AddAdmin") } /// Admin - case adminsAdmin + internal static var adminsAdmin: String { return L10n.tr("Localizable", "Admins.Admin") } /// CHANNEL ADMINS - case adminsChannelAdmins + internal static var adminsChannelAdmins: String { return L10n.tr("Localizable", "Admins.ChannelAdmins") } /// You can add admins to help you manage your channel - case adminsChannelDescription - /// Creator - case adminsCreator - /// Everybody can add new members - case adminsEverbodyCanAddMembers + internal static var adminsChannelDescription: String { return L10n.tr("Localizable", "Admins.ChannelDescription") } + /// Any member can add new members + internal static var adminsEverbodyCanAddMembers: String { return L10n.tr("Localizable", "Admins.EverbodyCanAddMembers") } /// GROUP ADMINS - case adminsGroupAdmins - /// You can add admins to help you manage your group - case adminsGroupDescription - /// Only Admins can add new members - case adminsOnlyAdminsCanAddMembers + internal static var adminsGroupAdmins: String { return L10n.tr("Localizable", "Admins.GroupAdmins") } + /// You can add admins to help you manage your group. + internal static var adminsGroupDescription: String { return L10n.tr("Localizable", "Admins.GroupDescription") } + /// Only admins can add new members. + internal static var adminsOnlyAdminsCanAddMembers: String { return L10n.tr("Localizable", "Admins.OnlyAdminsCanAddMembers") } + /// Owner + internal static var adminsOwner: String { return L10n.tr("Localizable", "Admins.Owner") } /// Contacts - case adminsSelectNewAdminTitle + internal static var adminsSelectNewAdminTitle: String { return L10n.tr("Localizable", "Admins.SelectNewAdminTitle") } /// Only Admins - case adminsWhoCanInviteAdmins + internal static var adminsWhoCanInviteAdmins: String { return L10n.tr("Localizable", "Admins.WhoCanInvite.Admins") } /// All Members - case adminsWhoCanInviteEveryone + internal static var adminsWhoCanInviteEveryone: String { return L10n.tr("Localizable", "Admins.WhoCanInvite.Everyone") } /// Who can add members - case adminsWhoCanInviteText + internal static var adminsWhoCanInviteText: String { return L10n.tr("Localizable", "Admins.WhoCanInvite.Text") } /// Cancel - case alertCancel + internal static var alertCancel: String { return L10n.tr("Localizable", "Alert.Cancel") } + /// Discard + internal static var alertDiscard: String { return L10n.tr("Localizable", "Alert.Discard") } + /// No + internal static var alertNO: String { return L10n.tr("Localizable", "Alert.NO") } /// OK - case alertOK + internal static var alertOK: String { return L10n.tr("Localizable", "Alert.OK") } /// Sorry, this user doesn't seem to exist. - case alertUserDoesntExists - /// Can't forward messages to this conversation. - case alertForwardError + internal static var alertUserDoesntExists: String { return L10n.tr("Localizable", "Alert.UserDoesntExists") } + /// Update App + internal static var alertButtonOKUpdateApp: String { return L10n.tr("Localizable", "Alert.ButtonOK.UpdateApp") } + /// Discard + internal static var alertConfirmDiscard: String { return L10n.tr("Localizable", "Alert.Confirm.Discard") } + /// Stop + internal static var alertConfirmStop: String { return L10n.tr("Localizable", "Alert.Confirm.Stop") } + /// Sorry, you can't forward messages to this conversation. + internal static var alertForwardError: String { return L10n.tr("Localizable", "Alert.Forward.Error") } + /// Cancel + internal static var alertHideNewChatsCancel: String { return L10n.tr("Localizable", "Alert.HideNewChats.Cancel") } + /// Hide new chats? + internal static var alertHideNewChatsHeader: String { return L10n.tr("Localizable", "Alert.HideNewChats.Header") } + /// Go to Settings + internal static var alertHideNewChatsOK: String { return L10n.tr("Localizable", "Alert.HideNewChats.OK") } + /// You are receiving lots of new chats from users who are not in your Contact List. Do you want to have such chats automatically muted and archived? + internal static var alertHideNewChatsText: String { return L10n.tr("Localizable", "Alert.HideNewChats.Text") } + /// Unfortunately, you can't access this message. You are not a member of the chat where it was posted. + internal static var alertPrivateChannelAccessError: String { return L10n.tr("Localizable", "Alert.PrivateChannel.AccessError") } /// Delete - case alertSendErrorDelete + internal static var alertSendErrorDelete: String { return L10n.tr("Localizable", "Alert.SendError.Delete") } /// Your message could not be sent - case alertSendErrorHeader + internal static var alertSendErrorHeader: String { return L10n.tr("Localizable", "Alert.SendError.Header") } /// Ignore - case alertSendErrorIgnore + internal static var alertSendErrorIgnore: String { return L10n.tr("Localizable", "Alert.SendError.Ignore") } /// Resend - case alertSendErrorResend - /// An error occurred while sending the previous message. Would you like to resend it ? - case alertSendErrorText - /// Maximum file size is 1.5 GB - case appMaxFileSize + internal static var alertSendErrorResend: String { return L10n.tr("Localizable", "Alert.SendError.Resend") } + /// %d + internal static func alertSendErrorResendItemsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_countable", p1) + } + /// Resend %d messages + internal static func alertSendErrorResendItemsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_few", p1) + } + /// Resend %d messages + internal static func alertSendErrorResendItemsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_many", p1) + } + /// Resend %d message + internal static func alertSendErrorResendItemsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_one", p1) + } + /// Resend %d messages + internal static func alertSendErrorResendItemsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_other", p1) + } + /// Resend %d messages + internal static func alertSendErrorResendItemsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_two", p1) + } + /// Resend %d messages + internal static func alertSendErrorResendItemsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Alert.SendError.ResendItems_zero", p1) + } + /// An error occurred while sending the previous message. Would you like to try resending it? + internal static var alertSendErrorText: String { return L10n.tr("Localizable", "Alert.SendError.Text") } + /// Maximum file size is 2.0 GB + internal static var appMaxFileSize1: String { return L10n.tr("Localizable", "App.MaxFileSize1") } + /// Hold to record video. Click to switch to audio + internal static var appTooltipVideoRecord: String { return L10n.tr("Localizable", "App.Tooltip.VideoRecord") } + /// Hold to record audio. Click to switch to video + internal static var appTooltipVoiceRecord: String { return L10n.tr("Localizable", "App.Tooltip.VoiceRecord") } + /// Check for Updates + internal static var appUpdateCheckForUpdates: String { return L10n.tr("Localizable", "AppUpdate.CheckForUpdates") } + /// Downloading... + internal static var appUpdateDownloading: String { return L10n.tr("Localizable", "AppUpdate.Downloading") } + /// Download Update + internal static var appUpdateDownloadUpdate: String { return L10n.tr("Localizable", "AppUpdate.DownloadUpdate") } + /// Please update the app to get the latest features and improvements. + internal static var appUpdateNewestAvailable: String { return L10n.tr("Localizable", "AppUpdate.NewestAvailable") } + /// Retrieving Information... + internal static var appUpdateRetrievingInfo: String { return L10n.tr("Localizable", "AppUpdate.RetrievingInfo") } + /// Updates + internal static var appUpdateTitle: String { return L10n.tr("Localizable", "AppUpdate.Title") } + /// Unarchiving... + internal static var appUpdateUnarchiving: String { return L10n.tr("Localizable", "AppUpdate.Unarchiving") } + /// You have the latest version of Telegram. + internal static var appUpdateUptodate: String { return L10n.tr("Localizable", "AppUpdate.Uptodate") } + /// NEW VERSION (your version: %@) + internal static func appUpdateTitleNew(_ p1: String) -> String { + return L10n.tr("Localizable", "AppUpdate.Title.New", p1) + } + /// PREVIOUS VERSIONS + internal static var appUpdateTitlePrevious: String { return L10n.tr("Localizable", "AppUpdate.Title.Previous") } + /// CLOUD THEMES + internal static var appearanceCloudThemes: String { return L10n.tr("Localizable", "Appearance.CloudThemes") } + /// Custom Background + internal static var appearanceCustomBackground: String { return L10n.tr("Localizable", "Appearance.CustomBackground") } + /// Export Theme + internal static var appearanceExportTheme: String { return L10n.tr("Localizable", "Appearance.ExportTheme") } + /// New Theme + internal static var appearanceNewTheme: String { return L10n.tr("Localizable", "Appearance.NewTheme") } + /// Reset to Defaults + internal static var appearanceReset: String { return L10n.tr("Localizable", "Appearance.Reset") } + /// Incompatible with macOS, click to edit + internal static var appearanceCloudThemeUnsupported: String { return L10n.tr("Localizable", "Appearance.CloudTheme.Unsupported") } + /// Remove + internal static var appearanceConfirmRemoveOK: String { return L10n.tr("Localizable", "Appearance.Confirm.RemoveOK") } + /// Are you sure you want to delete this theme? + internal static var appearanceConfirmRemoveText: String { return L10n.tr("Localizable", "Appearance.Confirm.RemoveText") } + /// Theme + internal static var appearanceConfirmRemoveTitle: String { return L10n.tr("Localizable", "Appearance.Confirm.RemoveTitle") } + /// The file size must not exceed 2MB and the image dimensions must not exceed 500x500px. + internal static var appearanceCustomBackgroundFileError: String { return L10n.tr("Localizable", "Appearance.CustomBackground.FileError") } + /// Auto-Night Mode + internal static var appearanceSettingsAutoNight: String { return L10n.tr("Localizable", "Appearance.Settings.AutoNight") } + /// AUTO-NIGHT MODE + internal static var appearanceSettingsAutoNightHeader: String { return L10n.tr("Localizable", "Appearance.Settings.AutoNightHeader") } + /// Bubbles Mode + internal static var appearanceSettingsBubblesMode: String { return L10n.tr("Localizable", "Appearance.Settings.BubblesMode") } + /// Edit + internal static var appearanceThemeEdit: String { return L10n.tr("Localizable", "Appearance.Theme.Edit") } + /// Remove + internal static var appearanceThemeRemove: String { return L10n.tr("Localizable", "Appearance.Theme.Remove") } + /// Share + internal static var appearanceThemeShare: String { return L10n.tr("Localizable", "Appearance.Theme.Share") } + /// Follow System Appearance + internal static var appearanceSettingsFollowSystemAppearance: String { return L10n.tr("Localizable", "AppearanceSettings.FollowSystemAppearance") } + /// Good morning! 👋 + internal static var appearanceSettingsChatPreview1: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.1") } + /// Do you know what time it is? + internal static var appearanceSettingsChatPreview2: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.2") } + /// It's morning in Tokyo 😎 + internal static var appearanceSettingsChatPreview3: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.3") } + /// Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff! + internal static var appearanceSettingsChatPreviewFirstText: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.FirstText") } + /// CHAT PREVIEW + internal static var appearanceSettingsChatPreviewHeader: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.Header") } + /// I can't even take you seriously right now. + internal static var appearanceSettingsChatPreviewSecondText: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.SecondText") } + /// Lucio + internal static var appearanceSettingsChatPreviewUserName1: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.UserName1") } + /// Reinhardt + internal static var appearanceSettingsChatPreviewUserName2: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.UserName2") } + /// Reinhardt, we need to find you some new tunes 🎶. + internal static var appearanceSettingsChatPreviewZeroText: String { return L10n.tr("Localizable", "AppearanceSettings.ChatPreview.ZeroText") } + /// Bubbles + internal static var appearanceSettingsChatViewBubbles: String { return L10n.tr("Localizable", "AppearanceSettings.ChatView.Bubbles") } + /// Minimalist + internal static var appearanceSettingsChatViewClassic: String { return L10n.tr("Localizable", "AppearanceSettings.ChatView.Classic") } + /// CHAT VIEW + internal static var appearanceSettingsChatViewHeader: String { return L10n.tr("Localizable", "AppearanceSettings.ChatView.Header") } + /// Day + internal static var appearanceSettingsColorThemeDay: String { return L10n.tr("Localizable", "AppearanceSettings.ColorTheme.day") } + /// Day Classic + internal static var appearanceSettingsColorThemeDayClassic: String { return L10n.tr("Localizable", "AppearanceSettings.ColorTheme.dayClassic") } + /// COLOR THEME + internal static var appearanceSettingsColorThemeHeader: String { return L10n.tr("Localizable", "AppearanceSettings.ColorTheme.Header") } + /// Night Accent + internal static var appearanceSettingsColorThemeNightAccent: String { return L10n.tr("Localizable", "AppearanceSettings.ColorTheme.nightAccent") } + /// System + internal static var appearanceSettingsColorThemeSystem: String { return L10n.tr("Localizable", "AppearanceSettings.ColorTheme.system") } + /// Select default dark palette which one will be used in dark system appearance mode. + internal static var appearanceSettingsFollowSystemAppearanceDefaultDark: String { return L10n.tr("Localizable", "AppearanceSettings.FollowSystemAppearance.DefaultDark") } + /// Select default day palette which one will be used in light system appearance mode. + internal static var appearanceSettingsFollowSystemAppearanceDefaultDay: String { return L10n.tr("Localizable", "AppearanceSettings.FollowSystemAppearance.DefaultDay") } + /// DEFAULT PALETTES + internal static var appearanceSettingsFollowSystemAppearanceDefaultHeader: String { return L10n.tr("Localizable", "AppearanceSettings.FollowSystemAppearance.DefaultHeader") } + /// TEXT SIZE + internal static var appearanceSettingsTextSizeHeader: String { return L10n.tr("Localizable", "AppearanceSettings.TextSize.Header") } + /// Change + internal static var applyLanguageApplyLanguageAction: String { return L10n.tr("Localizable", "ApplyLanguage.ApplyLanguageAction") } + /// Change + internal static var applyLanguageChangeLanguageAction: String { return L10n.tr("Localizable", "ApplyLanguage.ChangeLanguageAction") } + /// The language %@ is already active. + internal static func applyLanguageChangeLanguageAlreadyActive(_ p1: String) -> String { + return L10n.tr("Localizable", "ApplyLanguage.ChangeLanguageAlreadyActive", p1) + } + /// You are about to apply a language pack **%@**.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings. + internal static func applyLanguageChangeLanguageOfficialText(_ p1: String) -> String { + return L10n.tr("Localizable", "ApplyLanguage.ChangeLanguageOfficialText", p1) + } + /// Change Language? + internal static var applyLanguageChangeLanguageTitle: String { return L10n.tr("Localizable", "ApplyLanguage.ChangeLanguageTitle") } + /// You are about to apply a custom language pack **%@** that is %@%% complete.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings. + internal static func applyLanguageChangeLanguageUnofficialText(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "ApplyLanguage.ChangeLanguageUnofficialText", p1, p2) + } + /// Translation Platform + internal static var applyLanguageUnsufficientDataOpenPlatform: String { return L10n.tr("Localizable", "ApplyLanguage.UnsufficientDataOpenPlatform") } + /// Unfortunately, this custom language pack %@ doesn't contain data for Telegram macos. You can contribute to this language pack using the translations platform. + internal static func applyLanguageUnsufficientDataText(_ p1: String) -> String { + return L10n.tr("Localizable", "ApplyLanguage.UnsufficientDataText", p1) + } + /// Insufficient Data + internal static var applyLanguageUnsufficientDataTitle: String { return L10n.tr("Localizable", "ApplyLanguage.UnsufficientDataTitle") } + /// unmuted chats will get unarchived if new messages arrive. + internal static var archiveTooltipFirstText: String { return L10n.tr("Localizable", "Archive.Tooltip.First.Text") } + /// Chat Archived + internal static var archiveTooltipFirstTitle: String { return L10n.tr("Localizable", "Archive.Tooltip.First.Title") } + /// Chat Archived + internal static var archiveTooltipJustArchiveTitle: String { return L10n.tr("Localizable", "Archive.Tooltip.JustArchive.Title") } + /// muted chats will stay archivated after new messages arrive. + internal static var archiveTooltipSecondText: String { return L10n.tr("Localizable", "Archive.Tooltip.Second.Text") } + /// Chat Archived + internal static var archiveTooltipSecondTitle: String { return L10n.tr("Localizable", "Archive.Tooltip.Second.Title") } + /// you can pin an unlimited number of archived chats on the top. + internal static var archiveTooltipThirdText: String { return L10n.tr("Localizable", "Archive.Tooltip.Third.Text") } + /// Chat Archived + internal static var archiveTooltipThirdTitle: String { return L10n.tr("Localizable", "Archive.Tooltip.Third.Title") } /// You can have up to 200 sticker sets installed. Unused stickers are archived when you add more. - case archivedStickersDescription + internal static var archivedStickersDescription: String { return L10n.tr("Localizable", "ArchivedStickers.Description") } + /// Your archived sticker packs will appear here + internal static var archivedStickersEmpty: String { return L10n.tr("Localizable", "ArchivedStickers.Empty") } + /// Mark As Read + internal static var articleMarkAsRead: String { return L10n.tr("Localizable", "Article.MarkAsRead") } + /// Mark As Unread + internal static var articleMarkAsUnread: String { return L10n.tr("Localizable", "Article.MarkAsUnread") } + /// READ + internal static var articleRead: String { return L10n.tr("Localizable", "Article.Read") } + /// Read All + internal static var articleReadAll: String { return L10n.tr("Localizable", "Article.ReadAll") } + /// Remove + internal static var articleRemove: String { return L10n.tr("Localizable", "Article.Remove") } + /// Remove All + internal static var articleRemoveAll: String { return L10n.tr("Localizable", "Article.RemoveAll") } /// Unknown Artist - case audioUnknownArtist + internal static var audioUnknownArtist: String { return L10n.tr("Localizable", "Audio.UnknownArtist") } /// Untitled - case audioUntitledSong + internal static var audioUntitledSong: String { return L10n.tr("Localizable", "Audio.UntitledSong") } /// video message - case audioControllerVideoMessage + internal static var audioControllerVideoMessage: String { return L10n.tr("Localizable", "AudioController.videoMessage") } /// voice message - case audioControllerVoiceMessage - /// Release outside of this field to cancel - case audioRecordReleaseOut - /// Window - case aufd15bRTitle - /// Any details such as age, occupation of city. Example: 23 y.o. designer from San Francisco - case bioDescription + internal static var audioControllerVoiceMessage: String { return L10n.tr("Localizable", "AudioController.voiceMessage") } + /// Click outside of circle to cancel + internal static var audioRecordHelpFixed: String { return L10n.tr("Localizable", "AudioRecord.Help.Fixed") } + /// Release outside of circle to cancel + internal static var audioRecordHelpPlain: String { return L10n.tr("Localizable", "AudioRecord.Help.Plain") } + /// , + internal static var autoDownloadSettingsDelimeter: String { return L10n.tr("Localizable", "AutoDownloadSettings.Delimeter") } + /// and + internal static var autoDownloadSettingsLastDelimeter: String { return L10n.tr("Localizable", "AutoDownloadSettings.LastDelimeter") } + /// Off for all chats + internal static var autoDownloadSettingsOffForAll: String { return L10n.tr("Localizable", "AutoDownloadSettings.OffForAll") } + /// On for %@ + internal static func autoDownloadSettingsOnFor(_ p1: String) -> String { + return L10n.tr("Localizable", "AutoDownloadSettings.OnFor", p1) + } + /// On for all chats + internal static var autoDownloadSettingsOnForAll: String { return L10n.tr("Localizable", "AutoDownloadSettings.OnForAll") } + /// Channels + internal static var autoDownloadSettingsTypeChannels: String { return L10n.tr("Localizable", "AutoDownloadSettings.TypeChannels") } + /// Groups + internal static var autoDownloadSettingsTypeGroupChats: String { return L10n.tr("Localizable", "AutoDownloadSettings.TypeGroupChats") } + /// Private Chats + internal static var autoDownloadSettingsTypePrivateChats: String { return L10n.tr("Localizable", "AutoDownloadSettings.TypePrivateChats") } + /// Up to %@ for %@ + internal static func autoDownloadSettingsUpToFor(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "AutoDownloadSettings.UpToFor", p1, p2) + } + /// Up to %@ for all chats + internal static func autoDownloadSettingsUpToForAll(_ p1: String) -> String { + return L10n.tr("Localizable", "AutoDownloadSettings.UpToForAll", p1) + } + /// Disabled + internal static var autoNightSettingsDisabled: String { return L10n.tr("Localizable", "AutoNight.Settings.Disabled") } + /// From + internal static var autoNightSettingsFrom: String { return L10n.tr("Localizable", "AutoNight.Settings.From") } + /// PREFERRED NIGHT THEME + internal static var autoNightSettingsPreferredTheme: String { return L10n.tr("Localizable", "AutoNight.Settings.PreferredTheme") } + /// Scheduled + internal static var autoNightSettingsScheduled: String { return L10n.tr("Localizable", "AutoNight.Settings.Scheduled") } + /// Use Local Sunset & Sunrise + internal static var autoNightSettingsSunsetAndSunrise: String { return L10n.tr("Localizable", "AutoNight.Settings.SunsetAndSunrise") } + /// System + internal static var autoNightSettingsSystemBased: String { return L10n.tr("Localizable", "AutoNight.Settings.SystemBased") } + /// App interfaces will match the system appearance settings. + internal static var autoNightSettingsSystemBasedDesc: String { return L10n.tr("Localizable", "AutoNight.Settings.SystemBasedDesc") } + /// Auto-Night Theme + internal static var autoNightSettingsTitle: String { return L10n.tr("Localizable", "AutoNight.Settings.Title") } + /// To + internal static var autoNightSettingsTo: String { return L10n.tr("Localizable", "AutoNight.Settings.To") } + /// Update Location + internal static var autoNightSettingsUpdateLocation: String { return L10n.tr("Localizable", "AutoNight.Settings.UpdateLocation") } + /// Calculating sunset & sunrise times requires a one-time check of your approximate location. Note that this location is only stored locally on your device.\n\nSunset: %@\nSunrise: %@ + internal static func autoNightSettingsSunriseDesc(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "AutoNight.Settings.Sunrise.Desc", p1, p2) + } + /// Calculating sunset & sunrise times requires a one-time check of your approximate location. Note that this location is only stored locally on your device.\n\nSunset: N/A\nSunrise: N/A + internal static var autoNightSettingsSunriseDescNA: String { return L10n.tr("Localizable", "AutoNight.Settings.Sunrise.Desc.NA") } + /// Can't determine your location. Please check your system settings and try again. + internal static var autoNightSettingsUpdateLocationError: String { return L10n.tr("Localizable", "AutoNight.Settings.UpdateLocation.Error") } + /// Preferences… + internal static var bofnm1cWTitle: String { return L10n.tr("Localizable", "BOF-NM-1cW.title") } + /// Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco + internal static var bioDescription: String { return L10n.tr("Localizable", "Bio.Description") } + /// BIO + internal static var bioHeader: String { return L10n.tr("Localizable", "Bio.Header") } /// A few words about you - case bioPlaceholder + internal static var bioPlaceholder: String { return L10n.tr("Localizable", "Bio.Placeholder") } /// Save - case bioSave + internal static var bioSave: String { return L10n.tr("Localizable", "Bio.Save") } + /// Do you want to block %@ from messaging and calling you on Telegram? + internal static func blockContactTitle(_ p1: String) -> String { + return L10n.tr("Localizable", "BlockContact.Title", p1) + } + /// Block %@ + internal static func blockContactOptionsAction(_ p1: String) -> String { + return L10n.tr("Localizable", "BlockContact.Options.Action", p1) + } + /// Delete this Chat + internal static var blockContactOptionsDeleteChat: String { return L10n.tr("Localizable", "BlockContact.Options.DeleteChat") } + /// Report Spam + internal static var blockContactOptionsReport: String { return L10n.tr("Localizable", "BlockContact.Options.Report") } + /// Manage User + internal static var blockContactOptionsTitle: String { return L10n.tr("Localizable", "BlockContact.Options.Title") } /// Blocked users can't send you messages or add you to groups. They will not see your profile pictures, online and last seen status. - case blockedPeersEmptyDescrpition - /// Preferences… - case bofnm1cWTitle - /// Transformations - case c8aY6VQdTitle + internal static var blockedPeersEmptyDescrpition: String { return L10n.tr("Localizable", "BlockedPeers.EmptyDescrpition") } + /// Open Link + internal static var botInlineAuthHeader: String { return L10n.tr("Localizable", "Bot.InlineAuth.Header") } + /// Open + internal static var botInlineAuthOpen: String { return L10n.tr("Localizable", "Bot.InlineAuth.Open") } + /// Do you want to open \n%@? + internal static func botInlineAuthTitle(_ p1: String) -> String { + return L10n.tr("Localizable", "Bot.InlineAuth.Title", p1) + } + /// Allow %@ to send me messages + internal static func botInlineAuthOptionAllowSendMessages(_ p1: String) -> String { + return L10n.tr("Localizable", "Bot.InlineAuth.Option.AllowSendMessages", p1) + } + /// Log in to %@ as %@ + internal static func botInlineAuthOptionLogin(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Bot.InlineAuth.Option.Login", p1, p2) + } /// Hide Telegram - case cagYXWT6Title + internal static var cagYXWT6Title: String { return L10n.tr("Localizable", "Cag-YX-WT6.title") } /// %@'s app does not support calls. They need to update their app before you can call them. - case callParticipantVersionOutdatedError(String) + internal static func callParticipantVersionOutdatedError(_ p1: String) -> String { + return L10n.tr("Localizable", "Call.ParticipantVersionOutdatedError", p1) + } /// Sorry, %@ doesn't accept calls. - case callPrivacyErrorMessage(String) + internal static func callPrivacyErrorMessage(_ p1: String) -> String { + return L10n.tr("Localizable", "Call.PrivacyErrorMessage", p1) + } /// %d - case callShortMinutesCountable(Int) + internal static func callShortMinutesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_countable", p1) + } /// %d min - case callShortMinutesFew(Int) + internal static func callShortMinutesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_few", p1) + } /// %d min - case callShortMinutesMany(Int) + internal static func callShortMinutesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_many", p1) + } /// %d min - case callShortMinutesOne(Int) + internal static func callShortMinutesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_one", p1) + } /// %d min - case callShortMinutesOther(Int) + internal static func callShortMinutesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_other", p1) + } /// %d min - case callShortMinutesTwo(Int) + internal static func callShortMinutesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_two", p1) + } /// %d min - case callShortMinutesZero(Int) + internal static func callShortMinutesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortMinutes_zero", p1) + } /// %d - case callShortSecondsCountable(Int) + internal static func callShortSecondsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_countable", p1) + } /// %d sec - case callShortSecondsFew(Int) + internal static func callShortSecondsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_few", p1) + } /// %d sec - case callShortSecondsMany(Int) + internal static func callShortSecondsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_many", p1) + } /// %d sec - case callShortSecondsOne(Int) + internal static func callShortSecondsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_one", p1) + } /// %d sec - case callShortSecondsOther(Int) + internal static func callShortSecondsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_other", p1) + } /// %d sec - case callShortSecondsTwo(Int) + internal static func callShortSecondsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_two", p1) + } /// %d sec - case callShortSecondsZero(Int) + internal static func callShortSecondsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Call.ShortSeconds_zero", p1) + } /// Busy - case callStatusBusy + internal static var callStatusBusy: String { return L10n.tr("Localizable", "Call.StatusBusy") } /// is calling you... - case callStatusCalling + internal static var callStatusCalling: String { return L10n.tr("Localizable", "Call.StatusCalling") } + /// is calling → %@... + internal static func callStatusCallingAccount(_ p1: String) -> String { + return L10n.tr("Localizable", "Call.StatusCallingAccount", p1) + } /// Connecting... - case callStatusConnecting + internal static var callStatusConnecting: String { return L10n.tr("Localizable", "Call.StatusConnecting") } /// Call Ended - case callStatusEnded + internal static var callStatusEnded: String { return L10n.tr("Localizable", "Call.StatusEnded") } /// Call Failed - case callStatusFailed + internal static var callStatusFailed: String { return L10n.tr("Localizable", "Call.StatusFailed") } /// Contacting... - case callStatusRequesting + internal static var callStatusRequesting: String { return L10n.tr("Localizable", "Call.StatusRequesting") } /// Ringing... - case callStatusRinging - /// Undefined Error, please try later. - case callUndefinedError - /// Finish call with %@ and start a new one with %@? - case callConfirmDiscardCurrentDescription(String, String) + internal static var callStatusRinging: String { return L10n.tr("Localizable", "Call.StatusRinging") } + /// Undefined error, please try later. + internal static var callUndefinedError: String { return L10n.tr("Localizable", "Call.UndefinedError") } + /// Finish the call with %@ and start a new one with %@? + internal static func callConfirmDiscardCurrentDescription(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Call.Confirm.DiscardCurrent.Description", p1, p2) + } /// Call in Progress - case callConfirmDiscardCurrentHeader + internal static var callConfirmDiscardCurrentHeader: String { return L10n.tr("Localizable", "Call.Confirm.DiscardCurrent.Header") } /// Leave comment... - case callRatingModalPlaceholder + internal static var callRatingModalPlaceholder: String { return L10n.tr("Localizable", "Call.RatingModal.Placeholder") } /// Incoming - case callRecentIncoming + internal static var callRecentIncoming: String { return L10n.tr("Localizable", "Call.Recent.Incoming") } /// Missed - case callRecentMissed + internal static var callRecentMissed: String { return L10n.tr("Localizable", "Call.Recent.Missed") } /// Outgoing - case callRecentOutgoing + internal static var callRecentOutgoing: String { return L10n.tr("Localizable", "Call.Recent.Outgoing") } /// End Call - case callHeaderEndCall + internal static var callHeaderEndCall: String { return L10n.tr("Localizable", "CallHeader.EndCall") } + /// Mute other sounds during calls + internal static var callSettingsMuteSound: String { return L10n.tr("Localizable", "CallSettings.MuteSound") } + /// Open system preferences + internal static var callSettingsOpenSystemPreferences: String { return L10n.tr("Localizable", "CallSettings.OpenSystemPreferences") } + /// Call Settings + internal static var callSettingsTitle: String { return L10n.tr("Localizable", "CallSettings.Title") } + /// Input device + internal static var callSettingsInputText: String { return L10n.tr("Localizable", "CallSettings.Input.Text") } + /// MICROPHONE + internal static var callSettingsInputTitle: String { return L10n.tr("Localizable", "CallSettings.Input.Title") } + /// OTHER SETTINGS + internal static var callSettingsOtherSettingsTitle: String { return L10n.tr("Localizable", "CallSettings.OtherSettings.Title") } + /// Output device + internal static var callSettingsOutputText: String { return L10n.tr("Localizable", "CallSettings.Output.Text") } + /// SOUND + internal static var callSettingsOutputTitle: String { return L10n.tr("Localizable", "CallSettings.Output.Title") } + /// The deletion process was cancelled for your account %@. + internal static func cancelResetAccountSuccess(_ p1: String) -> String { + return L10n.tr("Localizable", "CancelResetAccount.Success", p1) + } + /// Somebody with access to your phone number **%@** has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf it wasn't you, please enter the code we've just sent you via SMS to your number. + internal static func cancelResetAccountTextSMS(_ p1: String) -> String { + return L10n.tr("Localizable", "CancelResetAccount.TextSMS", p1) + } + /// Cancel Account Reset + internal static var cancelResetAccountTitle: String { return L10n.tr("Localizable", "CancelResetAccount.Title") } + /// All Admins + internal static var chanelEventFilterAllAdmins: String { return L10n.tr("Localizable", "Chanel.EventFilter.AllAdmins") } + /// All Events + internal static var chanelEventFilterAllEvents: String { return L10n.tr("Localizable", "Chanel.EventFilter.AllEvents") } /// You have changed your phone number to %@. - case changeNumberConfirmCodeSuccess(String) + internal static func changeNumberConfirmCodeSuccess(_ p1: String) -> String { + return L10n.tr("Localizable", "ChangeNumber.ConfirmCode.Success", p1) + } /// Code expired. - case changeNumberConfirmCodeErrorCodeExpired + internal static var changeNumberConfirmCodeErrorCodeExpired: String { return L10n.tr("Localizable", "ChangeNumber.ConfirmCode.Error.codeExpired") } /// An error occurred. - case changeNumberConfirmCodeErrorGeneric + internal static var changeNumberConfirmCodeErrorGeneric: String { return L10n.tr("Localizable", "ChangeNumber.ConfirmCode.Error.Generic") } /// Invalid code. Please try again. - case changeNumberConfirmCodeErrorInvalidCode + internal static var changeNumberConfirmCodeErrorInvalidCode: String { return L10n.tr("Localizable", "ChangeNumber.ConfirmCode.Error.invalidCode") } /// You have entered invalid code too many times. Please try again later. - case changeNumberConfirmCodeErrorLimitExceeded + internal static var changeNumberConfirmCodeErrorLimitExceeded: String { return L10n.tr("Localizable", "ChangeNumber.ConfirmCode.Error.limitExceeded") } /// An error occurred. Please try again later. - case changeNumberSendDataErrorGeneric + internal static var changeNumberSendDataErrorGeneric: String { return L10n.tr("Localizable", "ChangeNumber.SendData.Error.Generic") } /// The phone number you entered is not valid. Please enter the correct number along with your area code. - case changeNumberSendDataErrorInvalidPhoneNumber - /// You have requested authorization code too many times. Please try again later. - case changeNumberSendDataErrorLimitExceeded + internal static var changeNumberSendDataErrorInvalidPhoneNumber: String { return L10n.tr("Localizable", "ChangeNumber.SendData.Error.InvalidPhoneNumber") } + /// You have requested for an authorization code too many times. Please try again later. + internal static var changeNumberSendDataErrorLimitExceeded: String { return L10n.tr("Localizable", "ChangeNumber.SendData.Error.LimitExceeded") } /// The number %@ is already connected to a Telegram account. Please delete that account before migrating to the new number. - case changeNumberSendDataErrorPhoneNumberOccupied(String) + internal static func changeNumberSendDataErrorPhoneNumberOccupied(_ p1: String) -> String { + return L10n.tr("Localizable", "ChangeNumber.SendData.Error.PhoneNumberOccupied", p1) + } /// All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven't blocked them in Telegram. - case changePhoneNumberIntroAlert + internal static var changePhoneNumberIntroAlert: String { return L10n.tr("Localizable", "ChangePhoneNumber.Intro.Alert") } /// You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\n**Important**: all your Telegram contacts will get your **new number** added to their address book, provided they had your old number and you haven't blocked them in Telegram. - case changePhoneNumberIntroDescription + internal static var changePhoneNumberIntroDescription: String { return L10n.tr("Localizable", "ChangePhoneNumber.Intro.Description") } + /// Make Admin + internal static var channelAddBotAsAdmin: String { return L10n.tr("Localizable", "Channel.AddBotAsAdmin") } + /// Bots can only be added as administrators. + internal static var channelAddBotErrorHaveRights: String { return L10n.tr("Localizable", "Channel.AddBotErrorHaveRights") } + /// Sorry, bots can only be added to channels as administrators. + internal static var channelAddBotErrorNoRights: String { return L10n.tr("Localizable", "Channel.AddBotErrorNoRights") } /// Forever - case channelBanForever + internal static var channelBanForever: String { return L10n.tr("Localizable", "Channel.BanForever") } + /// Sorry, this bot is telling us it doesn't want to be added to groups. You can't add this bot unless its developers change their mind. + internal static var channelBotDoesntSupportGroups: String { return L10n.tr("Localizable", "Channel.BotDoesntSupportGroups") } /// Channel Name - case channelChannelNameHolder + internal static var channelChannelNameHolder: String { return L10n.tr("Localizable", "Channel.ChannelNameHolder") } /// Create - case channelCreate + internal static var channelCreate: String { return L10n.tr("Localizable", "Channel.Create") } + /// DESCRIPTION + internal static var channelDescHeader: String { return L10n.tr("Localizable", "Channel.DescHeader") } /// Description - case channelDescriptionHolder + internal static var channelDescriptionHolder: String { return L10n.tr("Localizable", "Channel.DescriptionHolder") } /// You can provide an optional description for your channel. - case channelDescriptionHolderDescrpiton + internal static var channelDescriptionHolderDescrpiton: String { return L10n.tr("Localizable", "Channel.DescriptionHolderDescrpiton") } + /// Sorry, you can't add this user to channels. + internal static var channelErrorAddBlocked: String { return L10n.tr("Localizable", "Channel.ErrorAddBlocked") } + /// Sorry, you can only add the first 200 members to a channel. Note that an unlimited number of people may join via the channel's link. + internal static var channelErrorAddTooMuch: String { return L10n.tr("Localizable", "Channel.ErrorAddTooMuch") } /// People can join your channel by following this link. You can revoke the link at any time. - case channelExportLinkAboutChannel + internal static var channelExportLinkAboutChannel: String { return L10n.tr("Localizable", "Channel.ExportLinkAboutChannel") } /// People can join your group by following this link. You can revoke the link at any time. - case channelExportLinkAboutGroup - /// Channels are a tool for broadcasting your messages to large audiences. - case channelIntroDescription + internal static var channelExportLinkAboutGroup: String { return L10n.tr("Localizable", "Channel.ExportLinkAboutGroup") } + /// Channels are a tool for broadcasting your messages to unlimited audiences. + internal static var channelIntroDescription: String { return L10n.tr("Localizable", "Channel.IntroDescription") } /// What is a Channel? - case channelIntroDescriptionHeader + internal static var channelIntroDescriptionHeader: String { return L10n.tr("Localizable", "Channel.IntroDescriptionHeader") } + /// CHANNEL NAME + internal static var channelNameHeader: String { return L10n.tr("Localizable", "Channel.NameHeader") } /// New Channel - case channelNewChannel + internal static var channelNewChannel: String { return L10n.tr("Localizable", "Channel.NewChannel") } /// Private - case channelPrivate + internal static var channelPrivate: String { return L10n.tr("Localizable", "Channel.Private") } /// Private channels can only be joined via an invite link. - case channelPrivateAboutChannel - /// Private groups can only be joined if you were invited or by invite link. - case channelPrivateAboutGroup + internal static var channelPrivateAboutChannel: String { return L10n.tr("Localizable", "Channel.PrivateAboutChannel") } + /// Private groups can only be joined if you were invited or have an invite link. + internal static var channelPrivateAboutGroup: String { return L10n.tr("Localizable", "Channel.PrivateAboutGroup") } /// Public - case channelPublic - /// Public channels can be found via search, anyone can join them. - case channelPublicAboutChannel - /// Public groups can be found via search, chat history is available to everyone and anyone can join. - case channelPublicAboutGroup + internal static var channelPublic: String { return L10n.tr("Localizable", "Channel.Public") } + /// Public channels can be found in search, anyone can join them. + internal static var channelPublicAboutChannel: String { return L10n.tr("Localizable", "Channel.PublicAboutChannel") } + /// Public groups can be found in search, their chat history is available to everyone and anyone can join. + internal static var channelPublicAboutGroup: String { return L10n.tr("Localizable", "Channel.PublicAboutGroup") } /// Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead - case channelPublicNamesLimitError + internal static var channelPublicNamesLimitError: String { return L10n.tr("Localizable", "Channel.PublicNamesLimitError") } + /// Sorry, there are already too many bots in this group. Please remove some of the bots you're not using first. + internal static var channelTooMuchBots: String { return L10n.tr("Localizable", "Channel.TooMuchBots") } /// CHANNEL TYPE - case channelTypeHeaderChannel + internal static var channelTypeHeaderChannel: String { return L10n.tr("Localizable", "Channel.TypeHeaderChannel") } /// GROUP TYPE - case channelTypeHeaderGroup - /// People can share this link with others and find your channel using Telegram search. - case channelUsernameAboutChannel + internal static var channelTypeHeaderGroup: String { return L10n.tr("Localizable", "Channel.TypeHeaderGroup") } + /// People can share this link with others and can find your channel using Telegram search. + internal static var channelUsernameAboutChannel: String { return L10n.tr("Localizable", "Channel.UsernameAboutChannel") } /// People can share this link with others and find your group using Telegram search. - case channelUsernameAboutGroup + internal static var channelUsernameAboutGroup: String { return L10n.tr("Localizable", "Channel.UsernameAboutGroup") } /// USER RESTRICTIONS - case channelUserRestriction + internal static var channelUserRestriction: String { return L10n.tr("Localizable", "Channel.UserRestriction") } /// This Admin will be able to add new admins with the same (or more limited) permissions than he/she has. - case channelAdminAdminAccess + internal static var channelAdminAdminAccess: String { return L10n.tr("Localizable", "Channel.Admin.AdminAccess") } /// This admin will not be able to add new admins. - case channelAdminAdminRestricted + internal static var channelAdminAdminRestricted: String { return L10n.tr("Localizable", "Channel.Admin.AdminRestricted") } + /// You are not allowed to edit the rights of this admin. + internal static var channelAdminCantEdit: String { return L10n.tr("Localizable", "Channel.Admin.CantEdit") } /// You cannot edit the rights of this admin. - case channelAdminCantEditRights + internal static var channelAdminCantEditRights: String { return L10n.tr("Localizable", "Channel.Admin.CantEditRights") } /// Dismiss Admin - case channelAdminDismiss + internal static var channelAdminDismiss: String { return L10n.tr("Localizable", "Channel.Admin.Dismiss") } /// WHAT CAN THIS ADMIN DO? - case channelAdminWhatCanAdminDo - /// Sorry you can't promote this user to admin - case channelAdminsAddAdminError + internal static var channelAdminWhatCanAdminDo: String { return L10n.tr("Localizable", "Channel.Admin.WhatCanAdminDo") } + /// CUSTOM TITLE + internal static var channelAdminRoleHeader: String { return L10n.tr("Localizable", "Channel.Admin.Role.Header") } + /// A title that will be shown instead of 'admin'. + internal static var channelAdminRoleAdminDesc: String { return L10n.tr("Localizable", "Channel.Admin.Role.Admin.Desc") } + /// A title that will be shown instead of 'owner'. + internal static var channelAdminRoleOwnerDesc: String { return L10n.tr("Localizable", "Channel.Admin.Role.Owner.Desc") } + /// admin + internal static var channelAdminRolePlaceholderAdmin: String { return L10n.tr("Localizable", "Channel.Admin.Role.Placeholder.Admin") } + /// owner + internal static var channelAdminRolePlaceholderOwner: String { return L10n.tr("Localizable", "Channel.Admin.Role.Placeholder.Owner") } + /// Transfer Channel Ownership + internal static var channelAdminTransferOwnershipChannel: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Channel") } + /// Transfer Group Ownership + internal static var channelAdminTransferOwnershipGroup: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Group") } + /// Change Owner + internal static var channelAdminTransferOwnershipConfirmOK: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Confirm.OK") } + /// This will transfer the full owner rights for %@ to %@.\n\nYou will no longer be considered the creator of the channel. The new owner will be free to remove any of your admin privileges or even ban you. + internal static func channelAdminTransferOwnershipConfirmChannelText(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Confirm.Channel.Text", p1, p2) + } + /// Transfer Channel Ownership + internal static var channelAdminTransferOwnershipConfirmChannelTitle: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Confirm.Channel.Title") } + /// This will transfer the full owner rights for %@ to %@.\n\nYou will no longer be considered the creator of the group. The new owner will be free to remove any of your admin privileges or even ban you. + internal static func channelAdminTransferOwnershipConfirmGroupText(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Confirm.Group.Text", p1, p2) + } + /// Transfer Group Ownership + internal static var channelAdminTransferOwnershipConfirmGroupTitle: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Confirm.Group.Title") } + /// Please enter your 2-Step verification password to complete the transfer. + internal static var channelAdminTransferOwnershipPasswordDesc: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Password.Desc") } + /// Two-Step Verification + internal static var channelAdminTransferOwnershipPasswordTitle: String { return L10n.tr("Localizable", "Channel.Admin.TransferOwnership.Password.Title") } + /// Sorry, you're not allowed to promote this user to become an admin. + internal static var channelAdminsAddAdminError: String { return L10n.tr("Localizable", "Channel.Admins.AddAdminError") } /// promoted by %@ - case channelAdminsPromotedBy(String) + internal static func channelAdminsPromotedBy(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Admins.PromotedBy", p1) + } /// Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them. - case channelAdminsPromoteBannedAdminError + internal static var channelAdminsPromoteBannedAdminError: String { return L10n.tr("Localizable", "Channel.Admins.Promote.BannedAdminError") } /// Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them. - case channelAdminsPromoteUnmemberAdminError + internal static var channelAdminsPromoteUnmemberAdminError: String { return L10n.tr("Localizable", "Channel.Admins.Promote.UnmemberAdminError") } + /// Add Members + internal static var channelBanUserPermissionAddMembers: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionAddMembers") } + /// Change Group Info + internal static var channelBanUserPermissionChangeGroupInfo: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionChangeGroupInfo") } + /// Can Embed Links + internal static var channelBanUserPermissionEmbedLinks: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionEmbedLinks") } + /// Can Read Messages + internal static var channelBanUserPermissionReadMessages: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionReadMessages") } + /// Can Send Media + internal static var channelBanUserPermissionSendMedia: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionSendMedia") } + /// Can Send Messages + internal static var channelBanUserPermissionSendMessages: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionSendMessages") } + /// Send Polls + internal static var channelBanUserPermissionSendPolls: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionSendPolls") } + /// Can Send Stickers & GIFs + internal static var channelBanUserPermissionSendStickersAndGifs: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionSendStickersAndGifs") } + /// User Restrictions + internal static var channelBanUserPermissionsHeader: String { return L10n.tr("Localizable", "Channel.BanUser.PermissionsHeader") } + /// Ban User + internal static var channelBanUserTitle: String { return L10n.tr("Localizable", "Channel.BanUser.Title") } + /// Unban + internal static var channelBanUserUnban: String { return L10n.tr("Localizable", "Channel.BanUser.Unban") } /// blocked by %@ - case channelBlacklistBlockedBy(String) - /// Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them. - case channelBlacklistDemoteAdminError + internal static func channelBlacklistBlockedBy(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Blacklist.BlockedBy", p1) + } + /// Sorry, you can't ban this user because they are an admin of this group and you are not allowed to demote them. + internal static var channelBlacklistDemoteAdminError: String { return L10n.tr("Localizable", "Channel.Blacklist.DemoteAdminError") } + /// Users removed from the channel by admins cannot rejoin via invite links. + internal static var channelBlacklistDescChannel: String { return L10n.tr("Localizable", "Channel.Blacklist.DescChannel") } + /// Users removed from the group by admins cannot rejoin via invite links. + internal static var channelBlacklistDescGroup: String { return L10n.tr("Localizable", "Channel.Blacklist.DescGroup") } + /// Remove User + internal static var channelBlacklistRemoveUser: String { return L10n.tr("Localizable", "Channel.Blacklist.RemoveUser") } /// restricted by %@ - case channelBlacklistRestrictedBy(String) + internal static func channelBlacklistRestrictedBy(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Blacklist.RestrictedBy", p1) + } /// Members - case channelBlacklistSelectNewUserTitle + internal static var channelBlacklistSelectNewUserTitle: String { return L10n.tr("Localizable", "Channel.Blacklist.SelectNewUserTitle") } /// Unban - case channelBlacklistUnban + internal static var channelBlacklistUnban: String { return L10n.tr("Localizable", "Channel.Blacklist.Unban") } + /// Add To Group + internal static var channelBlacklistContextAddToGroup: String { return L10n.tr("Localizable", "Channel.Blacklist.Context.AddToGroup") } + /// Remove + internal static var channelBlacklistContextRemove: String { return L10n.tr("Localizable", "Channel.Blacklist.Context.Remove") } /// Block For - case channelBlockUserBlockFor + internal static var channelBlockUserBlockFor: String { return L10n.tr("Localizable", "Channel.BlockUser.BlockFor") } /// Can Embed Links - case channelBlockUserCanEmbedLinks + internal static var channelBlockUserCanEmbedLinks: String { return L10n.tr("Localizable", "Channel.BlockUser.CanEmbedLinks") } /// Can Read Messages - case channelBlockUserCanReadMessages + internal static var channelBlockUserCanReadMessages: String { return L10n.tr("Localizable", "Channel.BlockUser.CanReadMessages") } /// Can Send Media - case channelBlockUserCanSendMedia + internal static var channelBlockUserCanSendMedia: String { return L10n.tr("Localizable", "Channel.BlockUser.CanSendMedia") } /// Can Send Messages - case channelBlockUserCanSendMessages + internal static var channelBlockUserCanSendMessages: String { return L10n.tr("Localizable", "Channel.BlockUser.CanSendMessages") } /// Can Send Stickers & GIFs - case channelBlockUserCanSendStickers + internal static var channelBlockUserCanSendStickers: String { return L10n.tr("Localizable", "Channel.BlockUser.CanSendStickers") } + /// Add Members + internal static var channelEditAdminPermissionInviteMembers: String { return L10n.tr("Localizable", "Channel.EditAdmin.PermissionInviteMembers") } + /// Add Subscribers + internal static var channelEditAdminPermissionInviteSubscribers: String { return L10n.tr("Localizable", "Channel.EditAdmin.PermissionInviteSubscribers") } + /// Invite Users via Link + internal static var channelEditAdminPermissionInviteViaLink: String { return L10n.tr("Localizable", "Channel.EditAdmin.PermissionInviteViaLink") } /// Add New Admins - case channelEditAdminPermissionAddNewAdmins + internal static var channelEditAdminPermissionAddNewAdmins: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.AddNewAdmins") } /// Ban Users - case channelEditAdminPermissionBanUsers + internal static var channelEditAdminPermissionBanUsers: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.BanUsers") } /// Change Channel Info - case channelEditAdminPermissionChangeInfo + internal static var channelEditAdminPermissionChangeInfo: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.ChangeInfo") } /// Delete Messages - case channelEditAdminPermissionDeleteMessages + internal static var channelEditAdminPermissionDeleteMessages: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.DeleteMessages") } /// Edit Messages - case channelEditAdminPermissionEditMessages - /// Invite Users - case channelEditAdminPermissionInviteUsers + internal static var channelEditAdminPermissionEditMessages: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.EditMessages") } /// Pin Messages - case channelEditAdminPermissionPinMessages + internal static var channelEditAdminPermissionPinMessages: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.PinMessages") } /// Post Messages - case channelEditAdminPermissionPostMessages + internal static var channelEditAdminPermissionPostMessages: String { return L10n.tr("Localizable", "Channel.EditAdmin.Permission.PostMessages") } + /// Sorry, you don't have the necessary permissions for this action. + internal static var channelErrorDontHavePermissions: String { return L10n.tr("Localizable", "Channel.Error.DontHavePermissions") } /// ADMINS - case channelEventFilterAdminsHeader + internal static var channelEventFilterAdminsHeader: String { return L10n.tr("Localizable", "Channel.EventFilter.AdminsHeader") } /// EVENTS - case channelEventFilterEventsHeader + internal static var channelEventFilterEventsHeader: String { return L10n.tr("Localizable", "Channel.EventFilter.EventsHeader") } /// Empty - case channelEventLogEmpty + internal static var channelEventLogEmpty: String { return L10n.tr("Localizable", "Channel.EventLog.Empty") } /// ** No events found**\n\nNo recent events that match your query have been found. - case channelEventLogEmptySearch + internal static var channelEventLogEmptySearch: String { return L10n.tr("Localizable", "Channel.EventLog.EmptySearch") } /// **No events here yet**\n\nThere were no service actions taken by the channel's members and admins for the last 48 hours. - case channelEventLogEmptyText + internal static var channelEventLogEmptyText: String { return L10n.tr("Localizable", "Channel.EventLog.EmptyText") } + /// %@ linked this group to %@ + internal static func channelEventLogMessageChangedLinkedChannel(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageChangedLinkedChannel", p1, p2) + } + /// %@ linked %@ as the discussion group + internal static func channelEventLogMessageChangedLinkedGroup(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageChangedLinkedGroup", p1, p2) + } + /// %@ unlinked this group from %@ + internal static func channelEventLogMessageChangedUnlinkedChannel(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageChangedUnlinkedChannel", p1, p2) + } + /// %@ removed discussion group + internal static func channelEventLogMessageChangedUnlinkedGroup(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageChangedUnlinkedGroup", p1) + } + /// changed custom title for %@: %@ + internal static func channelEventLogMessageRankName(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageRankName", p1, p2) + } + /// transferred ownership + internal static var channelEventLogMessageTransfered: String { return L10n.tr("Localizable", "Channel.EventLog.MessageTransfered") } + /// transferred ownership to %@ %@ + internal static func channelEventLogMessageTransferedName(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.MessageTransferedName", p1, p2) + } /// Original message - case channelEventLogOriginalMessage + internal static var channelEventLogOriginalMessage: String { return L10n.tr("Localizable", "Channel.EventLog.OriginalMessage") } /// What Is This? - case channelEventLogWhat - /// What is an event log? - case channelEventLogAlertHeader + internal static var channelEventLogWhat: String { return L10n.tr("Localizable", "Channel.EventLog.What") } + /// What is the event log? + internal static var channelEventLogAlertHeader: String { return L10n.tr("Localizable", "Channel.EventLog.Alert.Header") } /// This is a list of all service actions taken by the group's members and admins in the last 48 hours. - case channelEventLogAlertInfo - /// %@ removed channel description: - case channelEventLogServiceAboutRemoved(String) - /// %@ edited channel description: - case channelEventLogServiceAboutUpdated(String) + internal static var channelEventLogAlertInfo: String { return L10n.tr("Localizable", "Channel.EventLog.Alert.Info") } + /// %@ removed this channel's description: + internal static func channelEventLogServiceAboutRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.AboutRemoved", p1) + } + /// %@ edited this channel's description: + internal static func channelEventLogServiceAboutUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.AboutUpdated", p1) + } + /// %@ disabled slowmode + internal static func channelEventLogServiceDisabledSlowMode(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.DisabledSlowMode", p1) + } /// %@ disabled channel signatures - case channelEventLogServiceDisableSignatures(String) + internal static func channelEventLogServiceDisableSignatures(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.DisableSignatures", p1) + } /// %@ enabled channel signatures - case channelEventLogServiceEnableSignatures(String) + internal static func channelEventLogServiceEnableSignatures(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.EnableSignatures", p1) + } /// %@ removed channel link: - case channelEventLogServiceLinkRemoved(String) - /// %@ edited channel link: - case channelEventLogServiceLinkUpdated(String) + internal static func channelEventLogServiceLinkRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.LinkRemoved", p1) + } + /// %@ edited this channel's link: + internal static func channelEventLogServiceLinkUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.LinkUpdated", p1) + } + /// - Title + internal static var channelEventLogServiceMinusTitle: String { return L10n.tr("Localizable", "Channel.EventLog.Service.MinusTitle") } /// %@ removed channel photo - case channelEventLogServicePhotoRemoved(String) - /// %@ updated channel photo - case channelEventLogServicePhotoUpdated(String) - /// %@ edited channel title: - case channelEventLogServiceTitleUpdated(String) + internal static func channelEventLogServicePhotoRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.PhotoRemoved", p1) + } + /// %@ updated this channel's photo + internal static func channelEventLogServicePhotoUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.PhotoUpdated", p1) + } + /// + Title: %@ + internal static func channelEventLogServicePlusTitle(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.PlusTitle", p1) + } + /// %@ set slowmode to %@ + internal static func channelEventLogServiceSetSlowMode(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.SetSlowMode", p1, p2) + } + /// %@ edited this channel's title: + internal static func channelEventLogServiceTitleUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.TitleUpdated", p1) + } /// %@ joined the channel - case channelEventLogServiceUpdateJoin(String) + internal static func channelEventLogServiceUpdateJoin(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.UpdateJoin", p1) + } /// %@ left the channel - case channelEventLogServiceUpdateLeft(String) - /// The admins of this group have restricted you from posting inline content here - case channelPersmissionDeniedSendInlineForever - /// The admins of this group have restricted you from posting inline content here until %@ - case channelPersmissionDeniedSendInlineUntil(String) - /// The admins of this group have restricted you from sending media here - case channelPersmissionDeniedSendMediaForever - /// The admins of this group have restricted you from sending media here until %@ - case channelPersmissionDeniedSendMediaUntil(String) + internal static func channelEventLogServiceUpdateLeft(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.EventLog.Service.UpdateLeft", p1) + } + /// This option is disabled in channel Permissions for all members. + internal static var channelExceptionDisabledOptionChannel: String { return L10n.tr("Localizable", "Channel.Exception.DisabledOption.Channel") } + /// This option is disabled in group's Permissions for all members. + internal static var channelExceptionDisabledOptionGroup: String { return L10n.tr("Localizable", "Channel.Exception.DisabledOption.Group") } + /// Create Channel + internal static var channelIntroCreateChannel: String { return L10n.tr("Localizable", "Channel.Intro.CreateChannel") } + /// SLOW MODE + internal static var channelPermissionsSlowModeHeader: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Header") } + /// Members will be able to send only one message per this interval. + internal static var channelPermissionsSlowModeTextOff: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Text.Off") } + /// Members will be able to send only one message every %@ + internal static func channelPermissionsSlowModeTextSelected(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Text.Selected", p1) + } + /// 10s + internal static var channelPermissionsSlowModeTimeout10s: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.10s") } + /// 15m + internal static var channelPermissionsSlowModeTimeout15m: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.15m") } + /// 1h + internal static var channelPermissionsSlowModeTimeout1h: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.1h") } + /// 1m + internal static var channelPermissionsSlowModeTimeout1m: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.1m") } + /// 30s + internal static var channelPermissionsSlowModeTimeout30s: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.30s") } + /// 5m + internal static var channelPermissionsSlowModeTimeout5m: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.5m") } + /// Off + internal static var channelPermissionsSlowModeTimeoutOff: String { return L10n.tr("Localizable", "Channel.Permissions.SlowMode.Timeout.Off") } + /// Sending GIFs isn't allowed in this group. + internal static var channelPersmissionDeniedSendGifsDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendGifs.DefaultRestrictedText") } + /// The admins of this group have restricted you from sending GIFs here. + internal static var channelPersmissionDeniedSendGifsForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendGifs.Forever") } + /// The admins of this group have restricted you from sending GIFs here until %@. + internal static func channelPersmissionDeniedSendGifsUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendGifs.Until", p1) + } + /// Posting inline content isn't allowed in this group. + internal static var channelPersmissionDeniedSendInlineDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendInline.DefaultRestrictedText") } + /// The admins of this group have restricted you from posting inline content here. + internal static var channelPersmissionDeniedSendInlineForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendInline.Forever") } + /// The admins of this group have restricted you from posting inline content here until %@. + internal static func channelPersmissionDeniedSendInlineUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendInline.Until", p1) + } + /// Sending media isn't allowed in this group. + internal static var channelPersmissionDeniedSendMediaDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMedia.DefaultRestrictedText") } + /// The admins of this group have restricted you from sending media here. + internal static var channelPersmissionDeniedSendMediaForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMedia.Forever") } + /// The admins of this group have restricted you from sending media here until %@. + internal static func channelPersmissionDeniedSendMediaUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMedia.Until", p1) + } + /// Writing messages isn’t allowed in this group. + internal static var channelPersmissionDeniedSendMessagesDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMessages.DefaultRestrictedText") } /// The admins of this group have restricted you from writing here - case channelPersmissionDeniedSendMessagesForever - /// The admins of this group have restricted you from writing here until %@ - case channelPersmissionDeniedSendMessagesUntil(String) - /// The admins of this group have restricted you from sending stickers here - case channelPersmissionDeniedSendStickersForever - /// The admins of this group have restricted you from sending stickers here until %@ - case channelPersmissionDeniedSendStickersUntil(String) + internal static var channelPersmissionDeniedSendMessagesForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMessages.Forever") } + /// The admins of this group have restricted you from writing here until %@. + internal static func channelPersmissionDeniedSendMessagesUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendMessages.Until", p1) + } + /// Posting polls isn't allowed in this group. + internal static var channelPersmissionDeniedSendPollDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendPoll.DefaultRestrictedText") } + /// The admins of this group have restricted you from posting polls here. + internal static var channelPersmissionDeniedSendPollForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendPoll.Forever") } + /// The admins of this group have restricted you from posting polls here until %@. + internal static func channelPersmissionDeniedSendPollUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendPoll.Until", p1) + } + /// Sending stickers isn't allowed in this group. + internal static var channelPersmissionDeniedSendStickersDefaultRestrictedText: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendStickers.DefaultRestrictedText") } + /// The admins of this group have restricted you from sending stickers here. + internal static var channelPersmissionDeniedSendStickersForever: String { return L10n.tr("Localizable", "Channel.Persmission.Denied.SendStickers.Forever") } + /// The admins of this group have restricted you from sending stickers here until %@. + internal static func channelPersmissionDeniedSendStickersUntil(_ p1: String) -> String { + return L10n.tr("Localizable", "Channel.Persmission.Denied.SendStickers.Until", p1) + } /// contacts - case channelSelectPeersContacts + internal static var channelSelectPeersContacts: String { return L10n.tr("Localizable", "Channel.SelectPeers.Contacts") } /// global - case channelSelectPeersGlobal + internal static var channelSelectPeersGlobal: String { return L10n.tr("Localizable", "Channel.SelectPeers.Global") } + /// Off + internal static var channelSlowModeOff: String { return L10n.tr("Localizable", "Channel.SlowMode.Off") } + /// Slowmode is enabled.\nYou can send your next message in %@:%@ + internal static func channelSlowModeToolTip(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Channel.SlowMode.ToolTip", p1, p2) + } + /// **Preparing stats**\nPlease wait a few moments while we generate your stats + internal static var channelStatsLoading: String { return L10n.tr("Localizable", "Channel.Stats.Loading") } + /// Sorry, this channel has too many admins and the new owner can't be added. Please remove one of the existing admins first. + internal static var channelTransferOwnerErrorAdminsTooMuch: String { return L10n.tr("Localizable", "Channel.TransferOwner.ErrorAdminsTooMuch") } + /// Sorry, this user is not a member of this channel and their privacy settings prevent you from adding them manually. + internal static var channelTransferOwnerErrorPrivacyRestricted: String { return L10n.tr("Localizable", "Channel.TransferOwner.ErrorPrivacyRestricted") } + /// Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first. + internal static var channelTransferOwnerErrorPublicChannelsTooMuch: String { return L10n.tr("Localizable", "Channel.TransferOwner.ErrorPublicChannelsTooMuch") } + /// Enable 2-Step Verification. + internal static var channelTransferOwnerErrorEnable2FA: String { return L10n.tr("Localizable", "Channel.TransferOwner.Error.Enable2FA") } + /// Ownership transfers are only available if:\n\n• 2-Step Verification was enabled for your account more than 7 days ago.\n\n• You have logged in on this device more than 24 hours ago.\n\nPlease come back later. + internal static var channelTransferOwnerErrorText: String { return L10n.tr("Localizable", "Channel.TransferOwner.Error.Text") } + /// Security Check + internal static var channelTransferOwnerErrorTitle: String { return L10n.tr("Localizable", "Channel.TransferOwner.Error.Title") } /// Recent Actions - case channelAdminsRecentActions - /// Add Member - case channelBlacklistAddMember + internal static var channelAdminsRecentActions: String { return L10n.tr("Localizable", "ChannelAdmins.RecentActions") } /// BLOCKED - case channelBlacklistBlocked - /// Blacklisted users are removed from the group and can only come back if invited by an admin. Invite links don't work for them. - case channelBlacklistEmptyDescrpition + internal static var channelBlacklistBlocked: String { return L10n.tr("Localizable", "ChannelBlacklist.Blocked") } + /// Blacklisted users are removed from the group and can only come back if they are invited back by an admin. Invite links won't work for blacklisted users. + internal static var channelBlacklistEmptyDescrpition: String { return L10n.tr("Localizable", "ChannelBlacklist.EmptyDescrpition") } /// RESTRICTED - case channelBlacklistRestricted + internal static var channelBlacklistRestricted: String { return L10n.tr("Localizable", "ChannelBlacklist.Restricted") } /// Channel Info - case channelEventFilterChannelInfo + internal static var channelEventFilterChannelInfo: String { return L10n.tr("Localizable", "ChannelEventFilter.ChannelInfo") } /// Deleted Messages - case channelEventFilterDeletedMessages + internal static var channelEventFilterDeletedMessages: String { return L10n.tr("Localizable", "ChannelEventFilter.DeletedMessages") } /// Edited Messages - case channelEventFilterEditedMessages + internal static var channelEventFilterEditedMessages: String { return L10n.tr("Localizable", "ChannelEventFilter.EditedMessages") } /// Group Info - case channelEventFilterGroupInfo + internal static var channelEventFilterGroupInfo: String { return L10n.tr("Localizable", "ChannelEventFilter.GroupInfo") } /// Members Removed - case channelEventFilterLeavingMembers + internal static var channelEventFilterLeavingMembers: String { return L10n.tr("Localizable", "ChannelEventFilter.LeavingMembers") } /// New Admins - case channelEventFilterNewAdmins + internal static var channelEventFilterNewAdmins: String { return L10n.tr("Localizable", "ChannelEventFilter.NewAdmins") } /// New Members - case channelEventFilterNewMembers + internal static var channelEventFilterNewMembers: String { return L10n.tr("Localizable", "ChannelEventFilter.NewMembers") } /// New Restrictions - case channelEventFilterNewRestrictions + internal static var channelEventFilterNewRestrictions: String { return L10n.tr("Localizable", "ChannelEventFilter.NewRestrictions") } /// Pinned Messages - case channelEventFilterPinnedMessages + internal static var channelEventFilterPinnedMessages: String { return L10n.tr("Localizable", "ChannelEventFilter.PinnedMessages") } + /// Sorry, if a person left a channel, only a mutual contact can bring them back (they need to have your phone number, and you need theirs). + internal static var channelInfoAddUserLeftError: String { return L10n.tr("Localizable", "ChannelInfo.AddUserLeftError") } + /// ⚠️ Warning: Many users reported this channel as a scam. Please be careful, especially if it asks you for money. + internal static var channelInfoScamWarning: String { return L10n.tr("Localizable", "ChannelInfo.ScamWarning") } /// Add Members - case channelMembersAddMembers + internal static var channelMembersAddMembers: String { return L10n.tr("Localizable", "ChannelMembers.AddMembers") } + /// Add Subscribers + internal static var channelMembersAddSubscribers: String { return L10n.tr("Localizable", "ChannelMembers.AddSubscribers") } /// Invite via Link - case channelMembersInviteLink + internal static var channelMembersInviteLink: String { return L10n.tr("Localizable", "ChannelMembers.InviteLink") } /// Only channel admins can see this list. - case channelMembersMembersListDesc + internal static var channelMembersMembersListDesc: String { return L10n.tr("Localizable", "ChannelMembers.MembersListDesc") } /// Add Members - case channelMembersSelectTitle - /// Checking - case channelVisibilityChecking + internal static var channelMembersSelectTitle: String { return L10n.tr("Localizable", "ChannelMembers.Select.Title") } + /// OVERVIEW + internal static var channelStatsOverview: String { return L10n.tr("Localizable", "ChannelStats.Overview") } + /// %d + internal static func channelStatsSharesCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_countable", p1) + } + /// %d shares + internal static func channelStatsSharesCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_few", p1) + } + /// %d shares + internal static func channelStatsSharesCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_many", p1) + } + /// %d shares + internal static func channelStatsSharesCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_one", p1) + } + /// %d shares + internal static func channelStatsSharesCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_other", p1) + } + /// %d shares + internal static func channelStatsSharesCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.SharesCount_two", p1) + } + /// No shares + internal static var channelStatsSharesCountZero: String { return L10n.tr("Localizable", "ChannelStats.SharesCount_zero") } + /// Channel Statistics + internal static var channelStatsTitle: String { return L10n.tr("Localizable", "ChannelStats.Title") } + /// %d + internal static func channelStatsViewsCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_countable", p1) + } + /// %d views + internal static func channelStatsViewsCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_few", p1) + } + /// %d views + internal static func channelStatsViewsCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_many", p1) + } + /// %d views + internal static func channelStatsViewsCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_one", p1) + } + /// %d views + internal static func channelStatsViewsCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_other", p1) + } + /// %d views + internal static func channelStatsViewsCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChannelStats.ViewsCount_two", p1) + } + /// No views + internal static var channelStatsViewsCountZero: String { return L10n.tr("Localizable", "ChannelStats.ViewsCount_zero") } + /// FOLLOWERS + internal static var channelStatsGraphFollowers: String { return L10n.tr("Localizable", "ChannelStats.Graph.Followers") } + /// GROWTH + internal static var channelStatsGraphGrowth: String { return L10n.tr("Localizable", "ChannelStats.Graph.Growth") } + /// INTERACTIONS + internal static var channelStatsGraphInteractions: String { return L10n.tr("Localizable", "ChannelStats.Graph.Interactions") } + /// LANGUAGE + internal static var channelStatsGraphLanguage: String { return L10n.tr("Localizable", "ChannelStats.Graph.Language") } + /// FOLLOWERS BY SOURCE + internal static var channelStatsGraphNewFollowersBySource: String { return L10n.tr("Localizable", "ChannelStats.Graph.NewFollowersBySource") } + /// NOTIFICATIONS + internal static var channelStatsGraphNotifications: String { return L10n.tr("Localizable", "ChannelStats.Graph.Notifications") } + /// VIEWS BY HOURS (UTC) + internal static var channelStatsGraphViewsByHours: String { return L10n.tr("Localizable", "ChannelStats.Graph.ViewsByHours") } + /// VIEWS BY SOURCE + internal static var channelStatsGraphViewsBySource: String { return L10n.tr("Localizable", "ChannelStats.Graph.ViewsBySource") } + /// Enabled Notifications + internal static var channelStatsOverviewEnabledNotifications: String { return L10n.tr("Localizable", "ChannelStats.Overview.EnabledNotifications") } + /// Followers + internal static var channelStatsOverviewFollowers: String { return L10n.tr("Localizable", "ChannelStats.Overview.Followers") } + /// Shares Per Post + internal static var channelStatsOverviewSharesPerPost: String { return L10n.tr("Localizable", "ChannelStats.Overview.SharesPerPost") } + /// Views Per Post + internal static var channelStatsOverviewViewsPerPost: String { return L10n.tr("Localizable", "ChannelStats.Overview.ViewsPerPost") } + /// RECENT POSTS + internal static var channelStatsRecentHeader: String { return L10n.tr("Localizable", "ChannelStats.Recent.Header") } + /// Checking... + internal static var channelVisibilityChecking: String { return L10n.tr("Localizable", "ChannelVisibility.Checking") } /// Loading... - case channelVisibilityLoading + internal static var channelVisibilityLoading: String { return L10n.tr("Localizable", "ChannelVisibility.Loading") } + /// Are you sure you want to make this channel private and remove its username? + internal static var channelVisibilityConfirmRevoke: String { return L10n.tr("Localizable", "ChannelVisibility.Confirm.Revoke") } + /// If you make this channel private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels. + internal static func channelVisibilityConfirmMakePrivateChannel(_ p1: String) -> String { + return L10n.tr("Localizable", "ChannelVisibility.Confirm.MakePrivate.Channel", p1) + } + /// If you make this group private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels. + internal static func channelVisibilityConfirmMakePrivateGroup(_ p1: String) -> String { + return L10n.tr("Localizable", "ChannelVisibility.Confirm.MakePrivate.Group", p1) + } /// admin - case chatAdminBadge + internal static var chatAdminBadge: String { return L10n.tr("Localizable", "Chat.AdminBadge") } + /// ADD PROXY + internal static var chatApplyProxy: String { return L10n.tr("Localizable", "Chat.ApplyProxy") } /// Cancel - case chatCancel + internal static var chatCancel: String { return L10n.tr("Localizable", "Chat.Cancel") } + /// channel + internal static var chatChannelBadge: String { return L10n.tr("Localizable", "Chat.ChannelBadge") } + /// Copy Selected Text + internal static var chatCopySelectedText: String { return L10n.tr("Localizable", "Chat.CopySelectedText") } /// without compression - case chatDropAsFilesDesc + internal static var chatDropAsFilesDesc: String { return L10n.tr("Localizable", "Chat.DropAsFilesDesc") } + /// Edit Media + internal static var chatDropEditDesc: String { return L10n.tr("Localizable", "Chat.DropEditDesc") } + /// Drop file there to edit media + internal static var chatDropEditTitle: String { return L10n.tr("Localizable", "Chat.DropEditTitle") } /// in a quick way - case chatDropQuickDesc + internal static var chatDropQuickDesc: String { return L10n.tr("Localizable", "Chat.DropQuickDesc") } /// Drop files here to send them - case chatDropTitle + internal static var chatDropTitle: String { return L10n.tr("Localizable", "Chat.DropTitle") } /// No messages here yet - case chatEmptyChat + internal static var chatEmptyChat: String { return L10n.tr("Localizable", "Chat.EmptyChat") } /// Forward Messages - case chatForwardActionHeader + internal static var chatForwardActionHeader: String { return L10n.tr("Localizable", "Chat.ForwardActionHeader") } /// INSTANT VIEW - case chatInstantView + internal static var chatInstantView: String { return L10n.tr("Localizable", "Chat.InstantView") } + /// Live Location + internal static var chatLiveLocation: String { return L10n.tr("Localizable", "Chat.LiveLocation") } + /// owner + internal static var chatOwnerBadge: String { return L10n.tr("Localizable", "Chat.OwnerBadge") } /// %d of %d - case chatSearchCount(Int, Int) + internal static func chatSearchCount(_ p1: Int, _ p2: Int) -> String { + return L10n.tr("Localizable", "Chat.SearchCount", p1, p2) + } /// from: - case chatSearchFrom + internal static var chatSearchFrom: String { return L10n.tr("Localizable", "Chat.SearchFrom") } + /// Sorry, you can only send messages to mutual contacts at the moment. + internal static var chatSendMessageErrorFlood: String { return L10n.tr("Localizable", "Chat.SendMessageErrorFlood") } + /// Sorry, you are currently restricted from posting to public groups. + internal static var chatSendMessageErrorGroupRestricted: String { return L10n.tr("Localizable", "Chat.SendMessageErrorGroupRestricted") } + /// Slowmode is enabled. + internal static var chatSendMessageSlowmodeError: String { return L10n.tr("Localizable", "Chat.SendMessageSlowmodeError") } /// Share - case chatShareInlineResultActionHeader + internal static var chatShareInlineResultActionHeader: String { return L10n.tr("Localizable", "Chat.ShareInlineResultActionHeader") } + /// Feed + internal static var chatTitleFeed: String { return L10n.tr("Localizable", "Chat.TitleFeed") } + /// VIEW BACKGROUND + internal static var chatViewBackground: String { return L10n.tr("Localizable", "Chat.ViewBackground") } + /// VIEW CONTACT + internal static var chatViewContact: String { return L10n.tr("Localizable", "Chat.ViewContact") } + /// VIEW THEME + internal static var chatActionViewTheme: String { return L10n.tr("Localizable", "Chat.Action.ViewTheme") } + /// Forwarded from: [%@]() + internal static func chatBubblesForwardedFrom(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Bubbles.ForwardedFrom", p1) + } /// Incoming Call - case chatCallIncoming + internal static var chatCallIncoming: String { return L10n.tr("Localizable", "Chat.Call.Incoming") } /// Outgoing Call - case chatCallOutgoing + internal static var chatCallOutgoing: String { return L10n.tr("Localizable", "Chat.Call.Outgoing") } + /// Sorry, this channel is not accessible. + internal static var chatChannelUnaccessible: String { return L10n.tr("Localizable", "Chat.Channel.Unaccessible") } /// This action can't be undone - case chatConfirmActionUndonable + internal static var chatConfirmActionUndonable: String { return L10n.tr("Localizable", "Chat.Confirm.ActionUndonable") } + /// %d + internal static func chatConfirmDeleteForEveryoneCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_countable", p1) + } + /// Are you sure you want to delete this messages for everyone? + internal static var chatConfirmDeleteForEveryoneFew: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_few") } + /// Are you sure you want to delete this messages for everyone? + internal static var chatConfirmDeleteForEveryoneMany: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_many") } + /// Are you sure you want to delete this message for everyone? + internal static var chatConfirmDeleteForEveryoneOne: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_one") } + /// Are you sure you want to delete this messages for everyone? + internal static var chatConfirmDeleteForEveryoneOther: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_other") } + /// Are you sure you want to delete this messages for everyone? + internal static var chatConfirmDeleteForEveryoneTwo: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_two") } + /// Are you sure you want to delete this messages for everyone? + internal static var chatConfirmDeleteForEveryoneZero: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteForEveryone_zero") } + /// %d + internal static func chatConfirmDeleteMessagesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_countable", p1) + } + /// Delete selected messages? + internal static var chatConfirmDeleteMessagesFew: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_few") } + /// Delete selected messages? + internal static var chatConfirmDeleteMessagesMany: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_many") } + /// Delete selected message? + internal static var chatConfirmDeleteMessagesOne: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_one") } /// Delete selected messages? - case chatConfirmDeleteMessages - /// Delete for All - case chatConfirmDeleteMessagesForEveryone + internal static var chatConfirmDeleteMessagesOther: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_other") } + /// Delete selected messages? + internal static var chatConfirmDeleteMessagesTwo: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_two") } + /// Delete selected messages? + internal static var chatConfirmDeleteMessagesZero: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessages_zero") } + /// Delete for Everyone + internal static var chatConfirmDeleteMessagesForEveryone: String { return L10n.tr("Localizable", "Chat.Confirm.DeleteMessagesForEveryone") } + /// Report Spam? + internal static var chatConfirmReportSpam: String { return L10n.tr("Localizable", "Chat.Confirm.ReportSpam") } + /// Are you sure you want to report spam from this user? + internal static var chatConfirmReportSpamUser: String { return L10n.tr("Localizable", "Chat.Confirm.ReportSpamUser") } /// Would you like to unpin this message? - case chatConfirmUnpin + internal static var chatConfirmUnpin: String { return L10n.tr("Localizable", "Chat.Confirm.Unpin") } + /// Report Spam and leave channel? + internal static var chatConfirmReportSpamChannel: String { return L10n.tr("Localizable", "Chat.Confirm.ReportSpam.Channel") } + /// Report Spam and leave group? + internal static var chatConfirmReportSpamGroup: String { return L10n.tr("Localizable", "Chat.Confirm.ReportSpam.Group") } + /// Report Spam + internal static var chatConfirmReportSpamHeader: String { return L10n.tr("Localizable", "Chat.Confirm.ReportSpam.Header") } + /// Unpin message + internal static var chatConfirmUnpinHeader: String { return L10n.tr("Localizable", "Chat.Confirm.Unpin.Header") } + /// Unpin + internal static var chatConfirmUnpinOK: String { return L10n.tr("Localizable", "Chat.Confirm.Unpin.OK") } /// Connecting - case chatConnectingStatusConnecting + internal static var chatConnectingStatusConnecting: String { return L10n.tr("Localizable", "Chat.ConnectingStatus.connecting") } /// Connecting to proxy - case chatConnectingStatusConnectingToProxy + internal static var chatConnectingStatusConnectingToProxy: String { return L10n.tr("Localizable", "Chat.ConnectingStatus.connectingToProxy") } /// Updating - case chatConnectingStatusUpdating + internal static var chatConnectingStatusUpdating: String { return L10n.tr("Localizable", "Chat.ConnectingStatus.updating") } /// Waiting for network - case chatConnectingStatusWaitingNetwork + internal static var chatConnectingStatusWaitingNetwork: String { return L10n.tr("Localizable", "Chat.ConnectingStatus.waitingNetwork") } /// Add to Favorites - case chatContextAddFavoriteSticker - /// Clear History - case chatContextClearHistory + internal static var chatContextAddFavoriteSticker: String { return L10n.tr("Localizable", "Chat.Context.AddFavoriteSticker") } + /// Archive + internal static var chatContextArchive: String { return L10n.tr("Localizable", "Chat.Context.Archive") } + /// Clear Chat History + internal static var chatContextClearHistory: String { return L10n.tr("Localizable", "Chat.Context.ClearHistory") } + /// Clear All + internal static var chatContextClearScheduled: String { return L10n.tr("Localizable", "Chat.Context.ClearScheduled") } /// Copy Preformatted Block - case chatContextCopyBlock + internal static var chatContextCopyBlock: String { return L10n.tr("Localizable", "Chat.Context.CopyBlock") } /// Unmute - case chatContextDisableNotifications - /// Edit (click on date) - case chatContextEdit + internal static var chatContextDisableNotifications: String { return L10n.tr("Localizable", "Chat.Context.DisableNotifications") } + /// Edit + internal static var chatContextEdit1: String { return L10n.tr("Localizable", "Chat.Context.Edit1") } + /// click on date + internal static var chatContextEditHelp: String { return L10n.tr("Localizable", "Chat.Context.EditHelp") } /// Mute - case chatContextEnableNotifications + internal static var chatContextEnableNotifications: String { return L10n.tr("Localizable", "Chat.Context.EnableNotifications") } + /// Channels Info + internal static var chatContextFeedInfo: String { return L10n.tr("Localizable", "Chat.Context.FeedInfo") } /// Info - case chatContextInfo + internal static var chatContextInfo: String { return L10n.tr("Localizable", "Chat.Context.Info") } /// Remove from Favorites - case chatContextRemoveFavoriteSticker + internal static var chatContextRemoveFavoriteSticker: String { return L10n.tr("Localizable", "Chat.Context.RemoveFavoriteSticker") } + /// Restrict + internal static var chatContextRestrict: String { return L10n.tr("Localizable", "Chat.Context.Restrict") } + /// Shared Media + internal static var chatContextSharedMedia: String { return L10n.tr("Localizable", "Chat.Context.SharedMedia") } + /// Unarchive + internal static var chatContextUnarchive: String { return L10n.tr("Localizable", "Chat.Context.Unarchive") } + /// Scheduled Messages + internal static var chatContextClearScheduledConfirmHeader: String { return L10n.tr("Localizable", "Chat.Context.ClearScheduled.Confirm.Header") } + /// Are you sure you want to delete all scheduled messages? + internal static var chatContextClearScheduledConfirmInfo: String { return L10n.tr("Localizable", "Chat.Context.ClearScheduled.Confirm.Info") } + /// Clear All + internal static var chatContextClearScheduledConfirmOK: String { return L10n.tr("Localizable", "Chat.Context.ClearScheduled.Confirm.OK") } + /// Reschedule + internal static var chatContextScheduledReschedule: String { return L10n.tr("Localizable", "Chat.Context.Scheduled.Reschedule") } + /// Send Now + internal static var chatContextScheduledSendNow: String { return L10n.tr("Localizable", "Chat.Context.Scheduled.SendNow") } + /// Copy Link to Proxy + internal static var chatCopyProxyConfiguration: String { return L10n.tr("Localizable", "Chat.Copy.ProxyConfiguration") } + /// Scheduled for %@ + internal static func chatDateScheduledFor(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Date.ScheduledFor", p1) + } + /// Scheduled for today + internal static var chatDateScheduledForToday: String { return L10n.tr("Localizable", "Chat.Date.ScheduledForToday") } + /// Scheduled until online + internal static var chatDateScheduledUntilOnline: String { return L10n.tr("Localizable", "Chat.Date.ScheduledUntilOnline") } + /// as archive + internal static var chatDropFolderDesc: String { return L10n.tr("Localizable", "Chat.DropFolder.Desc") } + /// Drop the folder here to send + internal static var chatDropFolderTitle: String { return L10n.tr("Localizable", "Chat.DropFolder.Title") } + /// Are you sure you want to discard all changes? + internal static var chatEditCancelText: String { return L10n.tr("Localizable", "Chat.Edit.Cancel.Text") } + /// Click to edit Media + internal static var chatEditMessageMedia: String { return L10n.tr("Localizable", "Chat.EditMessage.Media") } + /// Send + internal static var chatEmojiSend: String { return L10n.tr("Localizable", "Chat.Emoji.Send") } + /// Send a dart emoji to try your luck. + internal static var chatEmojiDartResultNew: String { return L10n.tr("Localizable", "Chat.Emoji.Dart.ResultNew") } + /// Send a %@ emoji to try your luck. + internal static func chatEmojiDefResultNew(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Emoji.Def.ResultNew", p1) + } + /// Send a dice emoji to roll a die. + internal static var chatEmojiDiceResultNew: String { return L10n.tr("Localizable", "Chat.Emoji.Dice.ResultNew") } + /// Link Preview + internal static var chatEmptyLinkPreview: String { return L10n.tr("Localizable", "Chat.Empty.LinkPreview") } + /// Sorry, this group is not accessible. + internal static var chatGroupUnaccessible: String { return L10n.tr("Localizable", "Chat.Group.Unaccessible") } /// Pinned message - case chatHeaderPinnedMessage + internal static var chatHeaderPinnedMessage: String { return L10n.tr("Localizable", "Chat.Header.PinnedMessage") } /// Report Spam - case chatHeaderReportSpam + internal static var chatHeaderReportSpam: String { return L10n.tr("Localizable", "Chat.Header.ReportSpam") } + /// Loading... + internal static var chatInlineRequestLoading: String { return L10n.tr("Localizable", "Chat.InlineRequest.Loading") } + /// Close + internal static var chatInputClose: String { return L10n.tr("Localizable", "Chat.Input.Close") } /// Delete and exit - case chatInputDelete + internal static var chatInputDelete: String { return L10n.tr("Localizable", "Chat.Input.Delete") } + /// Discuss + internal static var chatInputDiscuss: String { return L10n.tr("Localizable", "Chat.Input.Discuss") } /// Join - case chatInputJoin + internal static var chatInputJoin: String { return L10n.tr("Localizable", "Chat.Input.Join") } /// Mute - case chatInputMute - /// Return to group - case chatInputReturn + internal static var chatInputMute: String { return L10n.tr("Localizable", "Chat.Input.Mute") } + /// Restart + internal static var chatInputRestart: String { return L10n.tr("Localizable", "Chat.Input.Restart") } + /// Return to the group + internal static var chatInputReturn: String { return L10n.tr("Localizable", "Chat.Input.Return") } /// Start - case chatInputStartBot + internal static var chatInputStartBot: String { return L10n.tr("Localizable", "Chat.Input.StartBot") } /// Unblock - case chatInputUnblock + internal static var chatInputUnblock: String { return L10n.tr("Localizable", "Chat.Input.Unblock") } /// Unmute - case chatInputUnmute + internal static var chatInputUnmute: String { return L10n.tr("Localizable", "Chat.Input.Unmute") } /// Edit Message - case chatInputAccessoryEditMessage - /// Waiting for the user to come online... - case chatInputSecretChatWaitingToOnline + internal static var chatInputAccessoryEditMessage: String { return L10n.tr("Localizable", "Chat.Input.Accessory.EditMessage") } + /// %d + internal static func chatInputErrorMessageTooLongCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_countable", p1) + } + /// Your message is too long to be saved. Please remove %d characters. + internal static func chatInputErrorMessageTooLongFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_few", p1) + } + /// Your message is too long to be saved. Please remove %d characters. + internal static func chatInputErrorMessageTooLongMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_many", p1) + } + /// Your message is too long to be saved. Please remove %d character. + internal static func chatInputErrorMessageTooLongOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_one", p1) + } + /// Your message is too long to be saved. Please remove %d characters. + internal static func chatInputErrorMessageTooLongOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_other", p1) + } + /// Your message is too long to be saved. Please remove %d characters. + internal static func chatInputErrorMessageTooLongTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_two", p1) + } + /// Your message is too long to be saved. Please remove %d characters. + internal static func chatInputErrorMessageTooLongZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Input.Error.MessageTooLong_zero", p1) + } + /// Waiting for the %@ to get online... + internal static func chatInputSecretChatWaitingToUserOnline(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Input.SecretChat.WaitingToUserOnline", p1) + } /// Contact - case chatListContact + internal static var chatListContact: String { return L10n.tr("Localizable", "Chat.List.Contact") } /// GIF - case chatListGIF + internal static var chatListGIF: String { return L10n.tr("Localizable", "Chat.List.GIF") } /// Video message - case chatListInstantVideo + internal static var chatListInstantVideo: String { return L10n.tr("Localizable", "Chat.List.InstantVideo") } /// Location - case chatListMap + internal static var chatListMap: String { return L10n.tr("Localizable", "Chat.List.Map") } + /// Photo + internal static var chatListPhoto: String { return L10n.tr("Localizable", "Chat.List.Photo") } + /// %d + internal static func chatListPhotoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_countable", p1) + } + /// %d Photos + internal static func chatListPhotoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_few", p1) + } + /// %d Photos + internal static func chatListPhotoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_many", p1) + } /// Photo - case chatListPhoto + internal static var chatListPhotoOne: String { return L10n.tr("Localizable", "Chat.List.Photo_one") } + /// %d Photos + internal static func chatListPhotoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_other", p1) + } + /// %d Photos + internal static func chatListPhotoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_two", p1) + } + /// %d Photos + internal static func chatListPhotoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Photo_zero", p1) + } /// %@ Sticker - case chatListSticker(String) + internal static func chatListSticker(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.List.Sticker", p1) + } + /// Video + internal static var chatListVideo: String { return L10n.tr("Localizable", "Chat.List.Video") } + /// %d + internal static func chatListVideoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_countable", p1) + } + /// %d Videos + internal static func chatListVideoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_few", p1) + } + /// %d Videos + internal static func chatListVideoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_many", p1) + } /// Video - case chatListVideo + internal static var chatListVideoOne: String { return L10n.tr("Localizable", "Chat.List.Video_one") } + /// %d Videos + internal static func chatListVideoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_other", p1) + } + /// %d Videos + internal static func chatListVideoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_two", p1) + } + /// %d Videos + internal static func chatListVideoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.List.Video_zero", p1) + } /// Voice message - case chatListVoice + internal static var chatListVoice: String { return L10n.tr("Localizable", "Chat.List.Voice") } /// Payment: %@ - case chatListServicePaymentSent(String) + internal static func chatListServicePaymentSent(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.List.Service.PaymentSent", p1) + } + /// %d + internal static func chatLiveLocationUpdatedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_countable", p1) + } + /// Updated %d minutes ago + internal static func chatLiveLocationUpdatedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_few", p1) + } + /// Updated %d minutes ago + internal static func chatLiveLocationUpdatedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_many", p1) + } + /// Updated %d minute ago + internal static func chatLiveLocationUpdatedOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_one", p1) + } + /// Updated %d minutes ago + internal static func chatLiveLocationUpdatedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_other", p1) + } + /// Updated %d minutes ago + internal static func chatLiveLocationUpdatedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_two", p1) + } + /// Updated %d minutes ago + internal static func chatLiveLocationUpdatedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.LiveLocation.Updated_zero", p1) + } + /// Updated just now + internal static var chatLiveLocationUpdatedNow: String { return L10n.tr("Localizable", "Chat.LiveLocation.UpdatedNow") } + /// Delete for everyone + internal static var chatMessageDeleteForEveryone: String { return L10n.tr("Localizable", "Chat.Message.DeleteForEveryone") } + /// Delete for me + internal static var chatMessageDeleteForMe: String { return L10n.tr("Localizable", "Chat.Message.DeleteForMe") } + /// Delete for me and %@ + internal static func chatMessageDeleteForMeAndPerson(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Message.DeleteForMeAndPerson", p1) + } /// edited - case chatMessageEdited + internal static var chatMessageEdited: String { return L10n.tr("Localizable", "Chat.Message.edited") } + /// %d + internal static func chatMessageUnsendMessagesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Message.UnsendMessages_countable", p1) + } + /// Unsend my messages + internal static var chatMessageUnsendMessagesFew: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_few") } + /// Unsend my messages + internal static var chatMessageUnsendMessagesMany: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_many") } + /// Unsend my message + internal static var chatMessageUnsendMessagesOne: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_one") } + /// Unsend my messages + internal static var chatMessageUnsendMessagesOther: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_other") } + /// Unsend my messages + internal static var chatMessageUnsendMessagesTwo: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_two") } + /// Unsend my messages + internal static var chatMessageUnsendMessagesZero: String { return L10n.tr("Localizable", "Chat.Message.UnsendMessages_zero") } /// This message is not supported by your version of Telegram. Please update to the latest version from the AppStore or install it from https://macos.telegram.org - case chatMessageUnsupported + internal static var chatMessageUnsupported: String { return L10n.tr("Localizable", "Chat.Message.Unsupported") } + /// This message is not supported by your version Telegram. Please update to the latest version. + internal static var chatMessageUnsupportedNew: String { return L10n.tr("Localizable", "Chat.Message.UnsupportedNew") } /// via - case chatMessageVia - /// - Use end-to-end encryption - case chatSecretChat1Feature - /// - Leave no trace on our servers - case chatSecretChat2Feature - /// - Have a self-destruct timer - case chatSecretChat3Feature - /// - Do not allow forwarding - case chatSecretChat4Feature + internal static var chatMessageVia: String { return L10n.tr("Localizable", "Chat.Message.Via") } + /// MTProxy Configuration + internal static var chatMessageMTProxyConfig: String { return L10n.tr("Localizable", "Chat.Message.MTProxy.Config") } + /// SOCKS5 Configuration + internal static var chatMessageSocks5Config: String { return L10n.tr("Localizable", "Chat.Message.Socks5.Config") } + /// SHOW MESSAGE + internal static var chatMessageActionShowMessage: String { return L10n.tr("Localizable", "Chat.MessageAction.ShowMessage") } + /// %@%% + internal static func chatPollResult(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Poll.Result", p1) + } + /// Stop Poll + internal static var chatPollStop: String { return L10n.tr("Localizable", "Chat.Poll.Stop") } + /// Vote + internal static var chatPollSubmitVote: String { return L10n.tr("Localizable", "Chat.Poll.SubmitVote") } + /// %d + internal static func chatPollTotalVotes1Countable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_countable", p1) + } + /// %d votes + internal static func chatPollTotalVotes1Few(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_few", p1) + } + /// %d votes + internal static func chatPollTotalVotes1Many(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_many", p1) + } + /// %d vote + internal static func chatPollTotalVotes1One(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_one", p1) + } + /// %d votes + internal static func chatPollTotalVotes1Other(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_other", p1) + } + /// %d votes + internal static func chatPollTotalVotes1Two(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_two", p1) + } + /// %d vote + internal static func chatPollTotalVotes1Zero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.TotalVotes1_zero", p1) + } + /// No votes yet + internal static var chatPollTotalVotesEmpty: String { return L10n.tr("Localizable", "Chat.Poll.TotalVotesEmpty") } + /// No votes + internal static var chatPollTotalVotesResultEmpty: String { return L10n.tr("Localizable", "Chat.Poll.TotalVotesResultEmpty") } + /// Retract Vote + internal static var chatPollUnvote: String { return L10n.tr("Localizable", "Chat.Poll.Unvote") } + /// View Results + internal static var chatPollViewResults: String { return L10n.tr("Localizable", "Chat.Poll.ViewResults") } + /// Stop Poll? + internal static var chatPollStopConfirmHeader: String { return L10n.tr("Localizable", "Chat.Poll.Stop.Confirm.Header") } + /// If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone. + internal static var chatPollStopConfirmText: String { return L10n.tr("Localizable", "Chat.Poll.Stop.Confirm.Text") } + /// no votes + internal static var chatPollTooltipNoVotes: String { return L10n.tr("Localizable", "Chat.Poll.Tooltip.NoVotes") } + /// %d + internal static func chatPollTooltipVotesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_countable", p1) + } + /// %d votes + internal static func chatPollTooltipVotesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_few", p1) + } + /// %d votes + internal static func chatPollTooltipVotesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_many", p1) + } + /// %d vote + internal static func chatPollTooltipVotesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_one", p1) + } + /// %d votes + internal static func chatPollTooltipVotesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_other", p1) + } + /// %d votes + internal static func chatPollTooltipVotesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_two", p1) + } + /// %d votes + internal static func chatPollTooltipVotesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Poll.Tooltip.Votes_zero", p1) + } + /// Anonymous Poll + internal static var chatPollTypeAnonymous: String { return L10n.tr("Localizable", "Chat.Poll.Type.Anonymous") } + /// Anonymous Quiz + internal static var chatPollTypeAnonymousQuiz: String { return L10n.tr("Localizable", "Chat.Poll.Type.AnonymousQuiz") } + /// Final Results + internal static var chatPollTypeClosed: String { return L10n.tr("Localizable", "Chat.Poll.Type.Closed") } + /// Poll + internal static var chatPollTypePublic: String { return L10n.tr("Localizable", "Chat.Poll.Type.Public") } + /// Quiz + internal static var chatPollTypeQuiz: String { return L10n.tr("Localizable", "Chat.Poll.Type.Quiz") } + /// Proxy Sponsor + internal static var chatProxySponsoredAlertHeader: String { return L10n.tr("Localizable", "Chat.ProxySponsored.AlertHeader") } + /// Settings + internal static var chatProxySponsoredAlertSettings: String { return L10n.tr("Localizable", "Chat.ProxySponsored.AlertSettings") } + /// This channel is shown by your proxy server. To remove this channel from your chats list, disable the proxy in Telegram Settings. + internal static var chatProxySponsoredAlertText: String { return L10n.tr("Localizable", "Chat.ProxySponsored.AlertText") } + /// This channel is shown by your proxy server + internal static var chatProxySponsoredCapDesc: String { return L10n.tr("Localizable", "Chat.ProxySponsored.CapDesc") } + /// Proxy Sponsor + internal static var chatProxySponsoredCapTitle: String { return L10n.tr("Localizable", "Chat.ProxySponsored.CapTitle") } + /// Stop Quiz + internal static var chatQuizStop: String { return L10n.tr("Localizable", "Chat.Quiz.Stop") } + /// Quiz + internal static var chatQuizTextType: String { return L10n.tr("Localizable", "Chat.Quiz.TextType") } + /// %d + internal static func chatQuizTotalVotesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_countable", p1) + } + /// %d answers + internal static func chatQuizTotalVotesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_few", p1) + } + /// %d answers + internal static func chatQuizTotalVotesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_many", p1) + } + /// %d answer + internal static func chatQuizTotalVotesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_one", p1) + } + /// %d answers + internal static func chatQuizTotalVotesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_other", p1) + } + /// %d answers + internal static func chatQuizTotalVotesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_two", p1) + } + /// %d answer + internal static func chatQuizTotalVotesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.TotalVotes_zero", p1) + } + /// No answers yet + internal static var chatQuizTotalVotesEmpty: String { return L10n.tr("Localizable", "Chat.Quiz.TotalVotesEmpty") } + /// No answers + internal static var chatQuizTotalVotesResultEmpty: String { return L10n.tr("Localizable", "Chat.Quiz.TotalVotesResultEmpty") } + /// Stop Quiz? + internal static var chatQuizStopConfirmHeader: String { return L10n.tr("Localizable", "Chat.Quiz.Stop.Confirm.Header") } + /// If you stop this quiz now, nobody will be able to answer in it anymore. This action cannot be undone. + internal static var chatQuizStopConfirmText: String { return L10n.tr("Localizable", "Chat.Quiz.Stop.Confirm.Text") } + /// no answers + internal static var chatQuizTooltipNoVotes: String { return L10n.tr("Localizable", "Chat.Quiz.Tooltip.NoVotes") } + /// %d + internal static func chatQuizTooltipVotesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_countable", p1) + } + /// %d answers + internal static func chatQuizTooltipVotesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_few", p1) + } + /// %d answers + internal static func chatQuizTooltipVotesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_many", p1) + } + /// %d answer + internal static func chatQuizTooltipVotesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_one", p1) + } + /// %d answers + internal static func chatQuizTooltipVotesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_other", p1) + } + /// %d answers + internal static func chatQuizTooltipVotesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_two", p1) + } + /// %d answers + internal static func chatQuizTooltipVotesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.Quiz.Tooltip.Votes_zero", p1) + } + /// Are you sure you want to cancel recording? + internal static var chatRecordingCancel: String { return L10n.tr("Localizable", "Chat.Recording.Cancel") } + /// Reminder + internal static var chatRightContextReminder: String { return L10n.tr("Localizable", "Chat.Right.Context.Reminder") } + /// Scheduled Messages + internal static var chatRightContextScheduledMessages: String { return L10n.tr("Localizable", "Chat.Right.Context.ScheduledMessages") } + /// The buttons will become active as soon as the message is sent. + internal static var chatScheduledInlineButtonError: String { return L10n.tr("Localizable", "Chat.Scheduled.InlineButton.Error") } + /// • Use end-to-end encryption + internal static var chatSecretChat1Feature: String { return L10n.tr("Localizable", "Chat.SecretChat.1Feature") } + /// • Leave no trace on our servers + internal static var chatSecretChat2Feature: String { return L10n.tr("Localizable", "Chat.SecretChat.2Feature") } + /// • Have a self-destruct timer + internal static var chatSecretChat3Feature: String { return L10n.tr("Localizable", "Chat.SecretChat.3Feature") } + /// • Do not allow forwarding + internal static var chatSecretChat4Feature: String { return L10n.tr("Localizable", "Chat.SecretChat.4Feature") } /// Secret chats: - case chatSecretChatEmptyHeader - /// You have just successfully transferred **%@** to **%@** for **%@** - case chatServicePaymentSent(String, String, String) + internal static var chatSecretChatEmptyHeader: String { return L10n.tr("Localizable", "Chat.SecretChat.EmptyHeader") } + /// Secret Chat + internal static var chatSecretChatPreviewHeader: String { return L10n.tr("Localizable", "Chat.SecretChat.Preview.Header") } + /// NO + internal static var chatSecretChatPreviewNO: String { return L10n.tr("Localizable", "Chat.SecretChat.Preview.NO") } + /// YES + internal static var chatSecretChatPreviewOK: String { return L10n.tr("Localizable", "Chat.SecretChat.Preview.OK") } + /// Would you like to enable extended link previews in Secret Chat? Note that link previews are generated on Telegram Servers. + internal static var chatSecretChatPreviewText: String { return L10n.tr("Localizable", "Chat.SecretChat.Preview.Text") } + /// Schedule a Message + internal static var chatSendScheduledMessage: String { return L10n.tr("Localizable", "Chat.Send.ScheduledMessage") } + /// Set a Reminder + internal static var chatSendSetReminder: String { return L10n.tr("Localizable", "Chat.Send.SetReminder") } + /// Send Without Sound + internal static var chatSendWithoutSound: String { return L10n.tr("Localizable", "Chat.Send.WithoutSound") } + /// Sorry, you can only send only 100 scheduled messages. + internal static var chatSendMessageErrorTooMuchScheduled: String { return L10n.tr("Localizable", "Chat.SendMessageError.TooMuchScheduled") } + /// You allowed this bot to message you when you logged in on %@ + internal static func chatServiceBotPermissionAllowed(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.BotPermissionAllowed", p1) + } + /// You have successfully transferred **%@** to **%@** for **%@** + internal static func chatServicePaymentSent(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Chat.Service.PaymentSent", p1, p2, p3) + } + /// %@ joined Telegram + internal static func chatServicePeerJoinedTelegram(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.PeerJoinedTelegram", p1) + } /// pinned message - case chatServicePinnedMessage + internal static var chatServicePinnedMessage: String { return L10n.tr("Localizable", "Chat.Service.PinnedMessage") } + /// Search messages by %@ + internal static func chatServiceSearchAllMessages(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.SearchAllMessages", p1) + } /// You - case chatServiceYou + internal static var chatServiceYou: String { return L10n.tr("Localizable", "Chat.Service.You") } + /// Cancelled + internal static var chatServiceCallCancelled: String { return L10n.tr("Localizable", "Chat.Service.Call.Cancelled") } + /// Missed + internal static var chatServiceCallMissed: String { return L10n.tr("Localizable", "Chat.Service.Call.Missed") } /// channel photo removed - case chatServiceChannelRemovedPhoto + internal static var chatServiceChannelRemovedPhoto: String { return L10n.tr("Localizable", "Chat.Service.Channel.RemovedPhoto") } /// channel photo updated - case chatServiceChannelUpdatedPhoto + internal static var chatServiceChannelUpdatedPhoto: String { return L10n.tr("Localizable", "Chat.Service.Channel.UpdatedPhoto") } /// channel renamed to "%@" - case chatServiceChannelUpdatedTitle(String) + internal static func chatServiceChannelUpdatedTitle(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Channel.UpdatedTitle", p1) + } + /// channel video updated + internal static var chatServiceChannelUpdatedVideo: String { return L10n.tr("Localizable", "Chat.Service.Channel.UpdatedVideo") } /// %@ invited %@ - case chatServiceGroupAddedMembers(String, String) - /// %@ joined group - case chatServiceGroupAddedSelf(String) + internal static func chatServiceGroupAddedMembers(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.AddedMembers", p1, p2) + } + /// %@ joined the group + internal static func chatServiceGroupAddedSelf(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.AddedSelf", p1) + } /// %@ created the group "%@" - case chatServiceGroupCreated(String, String) + internal static func chatServiceGroupCreated(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.Created", p1, p2) + } /// %@ joined group via invite link - case chatServiceGroupJoinedByLink(String) + internal static func chatServiceGroupJoinedByLink(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.JoinedByLink", p1) + } /// This group was upgraded to a supergroup - case chatServiceGroupMigratedToSupergroup - /// %@ kicked %@ - case chatServiceGroupRemovedMembers(String, String) + internal static var chatServiceGroupMigratedToSupergroup: String { return L10n.tr("Localizable", "Chat.Service.Group.MigratedToSupergroup") } + /// %@ removed %@ + internal static func chatServiceGroupRemovedMembers(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.RemovedMembers", p1, p2) + } /// %@ removed group photo - case chatServiceGroupRemovedPhoto(String) - /// %@ left group - case chatServiceGroupRemovedSelf(String) + internal static func chatServiceGroupRemovedPhoto(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.RemovedPhoto", p1) + } + /// %@ left the group + internal static func chatServiceGroupRemovedSelf(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.RemovedSelf", p1) + } /// %@ took a screenshot - case chatServiceGroupTookScreenshot(String) + internal static func chatServiceGroupTookScreenshot(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.TookScreenshot", p1) + } /// %@ updated group photo - case chatServiceGroupUpdatedPhoto(String) + internal static func chatServiceGroupUpdatedPhoto(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.UpdatedPhoto", p1) + } /// %@ pinned "%@" - case chatServiceGroupUpdatedPinnedMessage(String, String) - /// %@ changed group name to "%@" - case chatServiceGroupUpdatedTitle(String, String) + internal static func chatServiceGroupUpdatedPinnedMessage(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.UpdatedPinnedMessage", p1, p2) + } + /// %@ changed the group name to "%@" + internal static func chatServiceGroupUpdatedTitle(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.UpdatedTitle", p1, p2) + } + /// %@ updated group video + internal static func chatServiceGroupUpdatedVideo(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.Group.UpdatedVideo", p1) + } /// %@ disabled the self-destruct timer - case chatServiceSecretChatDisabledTimer(String) + internal static func chatServiceSecretChatDisabledTimer(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.SecretChat.DisabledTimer", p1) + } /// %@ set the self-destruct timer to %@ - case chatServiceSecretChatSetTimer(String, String) + internal static func chatServiceSecretChatSetTimer(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.SecretChat.SetTimer", p1, p2) + } /// You disabled the self-destruct timer - case chatServiceSecretChatDisabledTimerSelf + internal static var chatServiceSecretChatDisabledTimerSelf: String { return L10n.tr("Localizable", "Chat.Service.SecretChat.DisabledTimer.Self") } /// You set the self-destruct timer to %@ - case chatServiceSecretChatSetTimerSelf(String) - /// Your Cloud storage - case chatTitleSelf - /// Draft: - case chatListDraft - /// Message is not supported - case chatListUnsupportedMessage - /// You - case chatListYou - /// Call - case chatListContextCall - /// Clear History - case chatListContextClearHistory - /// Delete And Exit - case chatListContextDeleteAndExit - /// Delete Chat - case chatListContextDeleteChat - /// Leave Channel - case chatListContextLeaveChannel - /// Leave Group - case chatListContextLeaveGroup + internal static func chatServiceSecretChatSetTimerSelf(_ p1: String) -> String { + return L10n.tr("Localizable", "Chat.Service.SecretChat.SetTimer.Self", p1) + } + /// %@ received the following documents: %@ + internal static func chatServiceSecureIdAccessGranted(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Chat.Service.SecureId.AccessGranted", p1, p2) + } + /// Reminder + internal static var chatTitleReminder: String { return L10n.tr("Localizable", "Chat.Title.Reminder") } + /// Scheduled Messages + internal static var chatTitleScheduledMessages: String { return L10n.tr("Localizable", "Chat.Title.ScheduledMessages") } + /// Your cloud storage + internal static var chatTitleSelf: String { return L10n.tr("Localizable", "Chat.Title.self") } + /// The account was hidden by the user + internal static var chatTooltipHiddenForwardName: String { return L10n.tr("Localizable", "Chat.Tooltip.HiddenForwardName") } + /// %d + internal static func chatUndoManagerChannelDeletedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_countable", p1) + } + /// %d Channels Deleted + internal static func chatUndoManagerChannelDeletedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_few", p1) + } + /// %d Channels Deleted + internal static func chatUndoManagerChannelDeletedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_many", p1) + } + /// Channel Deleted + internal static var chatUndoManagerChannelDeletedOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_one") } + /// %d Channels Deleted + internal static func chatUndoManagerChannelDeletedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_other", p1) + } + /// %d Channels Deleted + internal static func chatUndoManagerChannelDeletedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_two", p1) + } + /// %d Channels Deleted + internal static func chatUndoManagerChannelDeletedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelDeleted_zero", p1) + } + /// %d + internal static func chatUndoManagerChannelLeftCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_countable", p1) + } + /// %d Channels Left + internal static func chatUndoManagerChannelLeftFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_few", p1) + } + /// %d Channels Left + internal static func chatUndoManagerChannelLeftMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_many", p1) + } + /// Channel Left + internal static var chatUndoManagerChannelLeftOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_one") } + /// %d Channels Left + internal static func chatUndoManagerChannelLeftOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_other", p1) + } + /// %d Channels Left + internal static func chatUndoManagerChannelLeftTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_two", p1) + } + /// %d Channels Left + internal static func chatUndoManagerChannelLeftZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChannelLeft_zero", p1) + } + /// %d + internal static func chatUndoManagerChatLeftCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_countable", p1) + } + /// %d Chats Left + internal static func chatUndoManagerChatLeftFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_few", p1) + } + /// %d Chats Left + internal static func chatUndoManagerChatLeftMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_many", p1) + } + /// Chat Left + internal static var chatUndoManagerChatLeftOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_one") } + /// %d Chats Left + internal static func chatUndoManagerChatLeftOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_other", p1) + } + /// %d Chats Left + internal static func chatUndoManagerChatLeftTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_two", p1) + } + /// %d Chats Left + internal static func chatUndoManagerChatLeftZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatLeft_zero", p1) + } + /// %d + internal static func chatUndoManagerChatsArchivedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_countable", p1) + } + /// %d Chats Archived + internal static func chatUndoManagerChatsArchivedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_few", p1) + } + /// %d Chats Archived + internal static func chatUndoManagerChatsArchivedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_many", p1) + } + /// Chat Archived + internal static var chatUndoManagerChatsArchivedOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_one") } + /// %d Chats Archived + internal static func chatUndoManagerChatsArchivedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_other", p1) + } + /// %d Chats Archived + internal static func chatUndoManagerChatsArchivedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_two", p1) + } + /// %d Chat Archived + internal static func chatUndoManagerChatsArchivedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsArchived_zero", p1) + } + /// %d + internal static func chatUndoManagerChatsDeletedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_countable", p1) + } + /// %d Chats Deleted + internal static func chatUndoManagerChatsDeletedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_few", p1) + } + /// %d Chats Deleted + internal static func chatUndoManagerChatsDeletedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_many", p1) + } + /// Chat Deleted + internal static var chatUndoManagerChatsDeletedOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_one") } + /// %d Chats Deleted + internal static func chatUndoManagerChatsDeletedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_other", p1) + } + /// %d Chats Deleted + internal static func chatUndoManagerChatsDeletedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_two", p1) + } + /// %d Chat Deleted + internal static func chatUndoManagerChatsDeletedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsDeleted_zero", p1) + } + /// %d + internal static func chatUndoManagerChatsHistoryClearedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_countable", p1) + } + /// %d Chat History Cleared + internal static func chatUndoManagerChatsHistoryClearedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_few", p1) + } + /// %d Chat History Cleared + internal static func chatUndoManagerChatsHistoryClearedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_many", p1) + } + /// Chat History Cleared + internal static var chatUndoManagerChatsHistoryClearedOne: String { return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_one") } + /// %d Chat History Cleared + internal static func chatUndoManagerChatsHistoryClearedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_other", p1) + } + /// %d Chat History Cleared + internal static func chatUndoManagerChatsHistoryClearedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_two", p1) + } + /// %d Chat History Cleared + internal static func chatUndoManagerChatsHistoryClearedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Chat.UndoManager.ChatsHistoryCleared_zero", p1) + } + /// Undo + internal static var chatUndoManagerUndo: String { return L10n.tr("Localizable", "Chat.UndoManager.Undo") } + /// UPDATE + internal static var chatUnsupportedUpdatedApp: String { return L10n.tr("Localizable", "Chat.Unsupported.UpdatedApp") } + /// processing... + internal static var chatVideoProcessing: String { return L10n.tr("Localizable", "Chat.Video.Processing") } + /// Chat Background + internal static var chatWPBackgroundTitle: String { return L10n.tr("Localizable", "Chat.WP.BackgroundTitle") } + /// Color + internal static var chatWPColor: String { return L10n.tr("Localizable", "Chat.WP.Color") } + /// Pinch, swipe or double tap to select a custom area for the background. + internal static var chatWPFirstMessage: String { return L10n.tr("Localizable", "Chat.WP.FirstMessage") } + /// Pattern Intensity + internal static var chatWPIntensity: String { return L10n.tr("Localizable", "Chat.WP.Intensity") } + /// Pattern + internal static var chatWPPattern: String { return L10n.tr("Localizable", "Chat.WP.Pattern") } + /// Pinch me, I'm dreaming! + internal static var chatWPSecondMessage: String { return L10n.tr("Localizable", "Chat.WP.SecondMessage") } + /// Select From File + internal static var chatWPSelectFromFile: String { return L10n.tr("Localizable", "Chat.WP.SelectFromFile") } + /// Press Apply to set the background + internal static var chatWPColorFirstMessage: String { return L10n.tr("Localizable", "Chat.WP.Color.FirstMessage") } + /// Enjoy the view + internal static var chatWPColorSecondMessage: String { return L10n.tr("Localizable", "Chat.WP.Color.SecondMessage") } + /// None + internal static var chatWPPatternNone: String { return L10n.tr("Localizable", "Chat.WP.Pattern.None") } + /// %d of %d + internal static func chatWebpageMediaCount(_ p1: Int, _ p2: Int) -> String { + return L10n.tr("Localizable", "Chat.Webpage.MediaCount", p1, p2) + } + /// Show Next + internal static var chatInputShowNext: String { return L10n.tr("Localizable", "ChatInput.ShowNext") } + /// Archived Chats + internal static var chatListArchivedChats: String { return L10n.tr("Localizable", "ChatList.ArchivedChats") } + /// Show All + internal static var chatListCloseFilter: String { return L10n.tr("Localizable", "ChatList.CloseFilter") } + /// All + internal static var chatListCloseFilterShort: String { return L10n.tr("Localizable", "ChatList.CloseFilterShort") } + /// Draft: + internal static var chatListDraft: String { return L10n.tr("Localizable", "ChatList.Draft") } + /// **You have no conversations yet**\nStart messaging by tapping the pencil button in the top right corner or got to the Contacts section. + internal static var chatListEmptyText: String { return L10n.tr("Localizable", "ChatList.EmptyText") } + /// Channels + internal static var chatListFeeds: String { return L10n.tr("Localizable", "ChatList.Feeds") } + /// Group Channel + internal static var chatListGroupChannel: String { return L10n.tr("Localizable", "ChatList.GroupChannel") } + /// Hide Muted + internal static var chatListHideMuted: String { return L10n.tr("Localizable", "ChatList.HideMuted") } + /// Proxy Sponsor + internal static var chatListSponsoredChannel: String { return L10n.tr("Localizable", "ChatList.SponsoredChannel") } + /// Feed + internal static var chatListTitleFeed: String { return L10n.tr("Localizable", "ChatList.TitleFeed") } + /// Unhide Muted + internal static var chatListUnhideMuted: String { return L10n.tr("Localizable", "ChatList.UnhideMuted") } + /// Message is not supported + internal static var chatListUnsupportedMessage: String { return L10n.tr("Localizable", "ChatList.UnsupportedMessage") } + /// You + internal static var chatListYou: String { return L10n.tr("Localizable", "ChatList.You") } + /// CHATS + internal static var chatListAddBottomSeparator: String { return L10n.tr("Localizable", "ChatList.Add.BottomSeparator") } + /// Select chats... + internal static var chatListAddPlaceholder: String { return L10n.tr("Localizable", "ChatList.Add.Placeholder") } + /// Add + internal static var chatListAddSave: String { return L10n.tr("Localizable", "ChatList.Add.Save") } + /// CHAT TYPES + internal static var chatListAddTopSeparator: String { return L10n.tr("Localizable", "ChatList.Add.TopSeparator") } + /// Chats + internal static var chatListArchiveBack: String { return L10n.tr("Localizable", "ChatList.Archive.Back") } + /// Call + internal static var chatListContextCall: String { return L10n.tr("Localizable", "ChatList.Context.Call") } + /// Clear History + internal static var chatListContextClearHistory: String { return L10n.tr("Localizable", "ChatList.Context.ClearHistory") } + /// Delete And Exit + internal static var chatListContextDeleteAndExit: String { return L10n.tr("Localizable", "ChatList.Context.DeleteAndExit") } + /// Delete Chat + internal static var chatListContextDeleteChat: String { return L10n.tr("Localizable", "ChatList.Context.DeleteChat") } + /// Hide + internal static var chatListContextHidePromo: String { return L10n.tr("Localizable", "ChatList.Context.HidePromo") } + /// Leave Channel + internal static var chatListContextLeaveChannel: String { return L10n.tr("Localizable", "ChatList.Context.LeaveChannel") } + /// Leave Group + internal static var chatListContextLeaveGroup: String { return L10n.tr("Localizable", "ChatList.Context.LeaveGroup") } + /// Mark As Read + internal static var chatListContextMaskAsRead: String { return L10n.tr("Localizable", "ChatList.Context.MaskAsRead") } + /// Mark As Unread + internal static var chatListContextMaskAsUnread: String { return L10n.tr("Localizable", "ChatList.Context.MaskAsUnread") } /// Mute - case chatListContextMute + internal static var chatListContextMute: String { return L10n.tr("Localizable", "ChatList.Context.Mute") } /// Pin - case chatListContextPin - /// Return Group - case chatListContextReturnGroup + internal static var chatListContextPin: String { return L10n.tr("Localizable", "ChatList.Context.Pin") } + /// Sorry, you can pin no more than 5 chats to the top. + internal static var chatListContextPinError: String { return L10n.tr("Localizable", "ChatList.Context.PinError") } + /// Sorry, you can only pin 5 chats to the top in the main list. More chats can be pinned in Chat Folders and your Archive. + internal static var chatListContextPinErrorNew2: String { return L10n.tr("Localizable", "ChatList.Context.PinErrorNew2") } + /// Return to Group + internal static var chatListContextReturnGroup: String { return L10n.tr("Localizable", "ChatList.Context.ReturnGroup") } /// Unmute - case chatListContextUnmute + internal static var chatListContextUnmute: String { return L10n.tr("Localizable", "ChatList.Context.Unmute") } /// Unpin - case chatListContextUnpin + internal static var chatListContextUnpin: String { return L10n.tr("Localizable", "ChatList.Context.Unpin") } + /// Set Up Folders + internal static var chatListContextPinErrorNewSetupFolders: String { return L10n.tr("Localizable", "ChatList.Context.PinErrorNew.SetupFolders") } + /// Add Chats + internal static var chatListFilterAddChats: String { return L10n.tr("Localizable", "ChatList.Filter.AddChats") } + /// Add to folder... + internal static var chatListFilterAddToFolder: String { return L10n.tr("Localizable", "ChatList.Filter.AddToFolder") } + /// All + internal static var chatListFilterAll: String { return L10n.tr("Localizable", "ChatList.Filter.All") } + /// All Chats + internal static var chatListFilterAllChats: String { return L10n.tr("Localizable", "ChatList.Filter.AllChats") } + /// Archive + internal static var chatListFilterArchive: String { return L10n.tr("Localizable", "ChatList.Filter.Archive") } + /// Chats + internal static var chatListFilterBack: String { return L10n.tr("Localizable", "ChatList.Filter.Back") } + /// Bots + internal static var chatListFilterBots: String { return L10n.tr("Localizable", "ChatList.Filter.Bots") } + /// Channels + internal static var chatListFilterChannels: String { return L10n.tr("Localizable", "ChatList.Filter.Channels") } + /// Contacts + internal static var chatListFilterContacts: String { return L10n.tr("Localizable", "ChatList.Filter.Contacts") } + /// Delete + internal static var chatListFilterDelete: String { return L10n.tr("Localizable", "ChatList.Filter.Delete") } + /// Create + internal static var chatListFilterDone: String { return L10n.tr("Localizable", "ChatList.Filter.Done") } + /// Edit + internal static var chatListFilterEdit: String { return L10n.tr("Localizable", "ChatList.Filter.Edit") } + /// Edit Folders + internal static var chatListFilterEditFilters: String { return L10n.tr("Localizable", "ChatList.Filter.EditFilters") } + /// **No chats currently match this folder.**\n\n[Edit Folder](filter) + internal static var chatListFilterEmpty: String { return L10n.tr("Localizable", "ChatList.Filter.Empty") } + /// Exclude Muted + internal static var chatListFilterExcludeMuted: String { return L10n.tr("Localizable", "ChatList.Filter.ExcludeMuted") } + /// Exclude Read + internal static var chatListFilterExcludeRead: String { return L10n.tr("Localizable", "ChatList.Filter.ExcludeRead") } + /// Groups + internal static var chatListFilterGroups: String { return L10n.tr("Localizable", "ChatList.Filter.Groups") } + /// Create folders for different groups of chats and quickly switch between them. + internal static var chatListFilterHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Header") } + /// %d + internal static func chatListFilterHideCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_countable", p1) + } + /// Hide %d Chats + internal static func chatListFilterHideFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_few", p1) + } + /// Hide %d Chats + internal static func chatListFilterHideMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_many", p1) + } + /// Hide %d Chat + internal static func chatListFilterHideOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_one", p1) + } + /// Hide %d Chats + internal static func chatListFilterHideOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_other", p1) + } + /// Hide %d Chats + internal static func chatListFilterHideTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_two", p1) + } + /// Hide %d Chats + internal static func chatListFilterHideZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.Hide_zero", p1) + } + /// **Adding Chats**\nPlease wait a few moments while we fill this folder for you... + internal static var chatListFilterLoading: String { return L10n.tr("Localizable", "ChatList.Filter.Loading") } + /// Muted + internal static var chatListFilterMutedChats: String { return L10n.tr("Localizable", "ChatList.Filter.MutedChats") } + /// Create Folder + internal static var chatListFilterNewTitle: String { return L10n.tr("Localizable", "ChatList.Filter.NewTitle") } + /// Non-Contacts + internal static var chatListFilterNonContacts: String { return L10n.tr("Localizable", "ChatList.Filter.NonContacts") } + /// Read + internal static var chatListFilterReadChats: String { return L10n.tr("Localizable", "ChatList.Filter.ReadChats") } + /// Remove From Folder + internal static var chatListFilterRemoveFromFolder: String { return L10n.tr("Localizable", "ChatList.Filter.RemoveFromFolder") } + /// Secret Chats + internal static var chatListFilterSecretChat: String { return L10n.tr("Localizable", "ChatList.Filter.SecretChat") } + /// Edit Folders + internal static var chatListFilterSetup: String { return L10n.tr("Localizable", "ChatList.Filter.Setup") } + /// Add Folder + internal static var chatListFilterSetupEmpty: String { return L10n.tr("Localizable", "ChatList.Filter.SetupEmpty") } + /// %d + internal static func chatListFilterShowMoreCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_countable", p1) + } + /// Show %d More Chats + internal static func chatListFilterShowMoreFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_few", p1) + } + /// Show %d More Chats + internal static func chatListFilterShowMoreMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_many", p1) + } + /// Show %d More Chat + internal static func chatListFilterShowMoreOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_one", p1) + } + /// Show %d More Chats + internal static func chatListFilterShowMoreOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_other", p1) + } + /// Show %d More Chats + internal static func chatListFilterShowMoreTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_two", p1) + } + /// Show %d More Chats + internal static func chatListFilterShowMoreZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "ChatList.Filter.ShowMore_zero", p1) + } + /// Small Groups + internal static var chatListFilterSmallGroups: String { return L10n.tr("Localizable", "ChatList.Filter.SmallGroups") } + /// Folder + internal static var chatListFilterTitle: String { return L10n.tr("Localizable", "ChatList.Filter.Title") } + /// You can organize your chats by right click. + internal static var chatListFilterTooltip: String { return L10n.tr("Localizable", "ChatList.Filter.Tooltip") } + /// Done + internal static var chatListFilterAddDone: String { return L10n.tr("Localizable", "ChatList.Filter.Add.Done") } + /// INCLUDE CHAT TYPES + internal static var chatListFilterCategoriesHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Categories.Header") } + /// Delete Folder + internal static var chatListFilterConfirmRemoveHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Confirm.Remove.Header") } + /// Delete + internal static var chatListFilterConfirmRemoveOK: String { return L10n.tr("Localizable", "ChatList.Filter.Confirm.Remove.OK") } + /// Are you sure you want to delete folder? + internal static var chatListFilterConfirmRemoveText: String { return L10n.tr("Localizable", "ChatList.Filter.Confirm.Remove.Text") } + /// Cancel + internal static var chatListFilterDiscardCancel: String { return L10n.tr("Localizable", "ChatList.Filter.Discard.Cancel") } + /// Discard Changes + internal static var chatListFilterDiscardHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Discard.Header") } + /// Discard + internal static var chatListFilterDiscardOK: String { return L10n.tr("Localizable", "ChatList.Filter.Discard.OK") } + /// Are you sure you want to discard all changes? + internal static var chatListFilterDiscardText: String { return L10n.tr("Localizable", "ChatList.Filter.Discard.Text") } + /// Please add some chats or chat types to the folder. + internal static var chatListFilterErrorEmpty: String { return L10n.tr("Localizable", "ChatList.Filter.Error.Empty") } + /// Can’t create a folder that includes all your chats. + internal static var chatListFilterErrorLikeChats: String { return L10n.tr("Localizable", "ChatList.Filter.Error.LikeChats") } + /// Add Chats + internal static var chatListFilterExcludeAddChat: String { return L10n.tr("Localizable", "ChatList.Filter.Exclude.AddChat") } + /// Choose chats and types of chats that will never appear in this folder + internal static var chatListFilterExcludeDesc: String { return L10n.tr("Localizable", "ChatList.Filter.Exclude.Desc") } + /// EXCLUDED CHATS + internal static var chatListFilterExcludeHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Exclude.Header") } + /// Sorry, you can only add up to 100 chats. + internal static var chatListFilterExcludeLimitReached: String { return L10n.tr("Localizable", "ChatList.Filter.Exclude.LimitReached") } + /// Remove + internal static var chatListFilterExcludeRemoveChat: String { return L10n.tr("Localizable", "ChatList.Filter.Exclude.RemoveChat") } + /// Add Chats + internal static var chatListFilterIncludeAddChat: String { return L10n.tr("Localizable", "ChatList.Filter.Include.AddChat") } + /// Choose chats and types of chats that will appear in this folder + internal static var chatListFilterIncludeDesc: String { return L10n.tr("Localizable", "ChatList.Filter.Include.Desc") } + /// INCLUDED CHATS + internal static var chatListFilterIncludeHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Include.Header") } + /// Sorry, you can only add up to 100 chats. + internal static var chatListFilterIncludeLimitReached: String { return L10n.tr("Localizable", "ChatList.Filter.Include.LimitReached") } + /// Remove + internal static var chatListFilterIncludeRemoveChat: String { return L10n.tr("Localizable", "ChatList.Filter.Include.RemoveChat") } + /// Add a Custom Folder + internal static var chatListFilterListAddNew: String { return L10n.tr("Localizable", "ChatList.Filter.List.AddNew") } + /// Drag and drop folders to change order. Right click to remove. + internal static var chatListFilterListDesc: String { return L10n.tr("Localizable", "ChatList.Filter.List.Desc") } + /// FOLDERS + internal static var chatListFilterListHeader: String { return L10n.tr("Localizable", "ChatList.Filter.List.Header") } + /// Remove + internal static var chatListFilterListRemove: String { return L10n.tr("Localizable", "ChatList.Filter.List.Remove") } + /// Chat Folders + internal static var chatListFilterListTitle: String { return L10n.tr("Localizable", "ChatList.Filter.List.Title") } + /// FOLDER NAME + internal static var chatListFilterNameHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Name.Header") } + /// Folder Name + internal static var chatListFilterNamePlaceholder: String { return L10n.tr("Localizable", "ChatList.Filter.Name.Placeholder") } + /// Add + internal static var chatListFilterRecommendedAdd: String { return L10n.tr("Localizable", "ChatList.Filter.Recommended.Add") } + /// RECOMMENDED + internal static var chatListFilterRecommendedHeader: String { return L10n.tr("Localizable", "ChatList.Filter.Recommended.Header") } + /// If you have many folders, try moving tabs to the left. + internal static var chatListFilterTabBarDesc: String { return L10n.tr("Localizable", "ChatList.Filter.TabBar.Desc") } + /// TABS VIEW + internal static var chatListFilterTabBarHeader: String { return L10n.tr("Localizable", "ChatList.Filter.TabBar.Header") } + /// Tabs on the left + internal static var chatListFilterTabBarOnTheLeft: String { return L10n.tr("Localizable", "ChatList.Filter.TabBar.OnTheLeft") } + /// Tabs at the top + internal static var chatListFilterTabBarOnTheTop: String { return L10n.tr("Localizable", "ChatList.Filter.TabBar.OnTheTop") } + /// Bots + internal static var chatListFilterTilteDefaultBots: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Bots") } + /// Channels + internal static var chatListFilterTilteDefaultChannels: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Channels") } + /// Contacts + internal static var chatListFilterTilteDefaultContacts: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Contacts") } + /// Groups + internal static var chatListFilterTilteDefaultGroups: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Groups") } + /// Non-Contacts + internal static var chatListFilterTilteDefaultNonContacts: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.NonContacts") } + /// Unmuted + internal static var chatListFilterTilteDefaultUnmuted: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Unmuted") } + /// Unread + internal static var chatListFilterTilteDefaultUnread: String { return L10n.tr("Localizable", "ChatList.Filter.Tilte.Default.Unread") } + /// For 1 Day + internal static var chatListMute1Day: String { return L10n.tr("Localizable", "ChatList.Mute.1Day") } + /// For 1 Hour + internal static var chatListMute1Hour: String { return L10n.tr("Localizable", "ChatList.Mute.1Hour") } + /// For 3 Days + internal static var chatListMute3Days: String { return L10n.tr("Localizable", "ChatList.Mute.3Days") } + /// For 4 Hours + internal static var chatListMute4Hours: String { return L10n.tr("Localizable", "ChatList.Mute.4Hours") } + /// For 8 Hours + internal static var chatListMute8Hours: String { return L10n.tr("Localizable", "ChatList.Mute.8Hours") } + /// Forever + internal static var chatListMuteForever: String { return L10n.tr("Localizable", "ChatList.Mute.Forever") } + /// Are you sure you want to read all chats? + internal static var chatListPopoverConfirm: String { return L10n.tr("Localizable", "ChatList.Popover.Confirm") } + /// Read All + internal static var chatListPopoverReadAll: String { return L10n.tr("Localizable", "ChatList.Popover.ReadAll") } + /// Collapse + internal static var chatListRevealActionCollapse: String { return L10n.tr("Localizable", "ChatList.RevealAction.Collapse") } + /// Expand + internal static var chatListRevealActionExpand: String { return L10n.tr("Localizable", "ChatList.RevealAction.Expand") } + /// Hide + internal static var chatListRevealActionHide: String { return L10n.tr("Localizable", "ChatList.RevealAction.Hide") } + /// Pin + internal static var chatListRevealActionPin: String { return L10n.tr("Localizable", "ChatList.RevealAction.Pin") } /// %@ created a secret chat. - case chatListSecretChatCreated(String) + internal static func chatListSecretChatCreated(_ p1: String) -> String { + return L10n.tr("Localizable", "ChatList.SecretChat.Created", p1) + } /// Waiting to come online - case chatListSecretChatExKeys + internal static var chatListSecretChatExKeys: String { return L10n.tr("Localizable", "ChatList.SecretChat.ExKeys") } /// %@ joined your secret chat. - case chatListSecretChatJoined(String) + internal static func chatListSecretChatJoined(_ p1: String) -> String { + return L10n.tr("Localizable", "ChatList.SecretChat.Joined", p1) + } /// Secret chat cancelled - case chatListSecretChatTerminated + internal static var chatListSecretChatTerminated: String { return L10n.tr("Localizable", "ChatList.SecretChat.Terminated") } /// self-destructing photo - case chatListServiceDestructingPhoto + internal static var chatListServiceDestructingPhoto: String { return L10n.tr("Localizable", "ChatList.Service.DestructingPhoto") } /// self-destructing video - case chatListServiceDestructingVideo + internal static var chatListServiceDestructingVideo: String { return L10n.tr("Localizable", "ChatList.Service.DestructingVideo") } + /// %d %@ + internal static func chatListServiceGameScored1Countable(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_countable", p1, p2) + } + /// scored %d in %@ + internal static func chatListServiceGameScored1Few(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_few", p1, p2) + } /// scored %d in %@ - case chatListServiceGameScored(Int, String) + internal static func chatListServiceGameScored1Many(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_many", p1, p2) + } + /// scored %d in %@ + internal static func chatListServiceGameScored1One(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_one", p1, p2) + } + /// scored %d in %@ + internal static func chatListServiceGameScored1Other(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_other", p1, p2) + } + /// scored %d in %@ + internal static func chatListServiceGameScored1Two(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_two", p1, p2) + } + /// scored %d in %@ + internal static func chatListServiceGameScored1Zero(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.GameScored1_zero", p1, p2) + } /// Cancelled Call - case chatListServiceCallCancelled - /// Disconnected - case chatListServiceCallDisconnected + internal static var chatListServiceCallCancelled: String { return L10n.tr("Localizable", "ChatList.Service.Call.Cancelled") } /// Incoming Call (%@) - case chatListServiceCallIncoming(String) + internal static func chatListServiceCallIncoming(_ p1: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.Call.incoming", p1) + } /// Missed Call - case chatListServiceCallMissed + internal static var chatListServiceCallMissed: String { return L10n.tr("Localizable", "ChatList.Service.Call.Missed") } /// Outgoing Call (%@) - case chatListServiceCallOutgoing(String) + internal static func chatListServiceCallOutgoing(_ p1: String) -> String { + return L10n.tr("Localizable", "ChatList.Service.Call.outgoing", p1) + } + /// Archive + internal static var chatListSwipingArchive: String { return L10n.tr("Localizable", "ChatList.Swiping.Archive") } + /// Delete + internal static var chatListSwipingDelete: String { return L10n.tr("Localizable", "ChatList.Swiping.Delete") } + /// Mute + internal static var chatListSwipingMute: String { return L10n.tr("Localizable", "ChatList.Swiping.Mute") } + /// Pin + internal static var chatListSwipingPin: String { return L10n.tr("Localizable", "ChatList.Swiping.Pin") } + /// Read + internal static var chatListSwipingRead: String { return L10n.tr("Localizable", "ChatList.Swiping.Read") } + /// Unarchive + internal static var chatListSwipingUnarchive: String { return L10n.tr("Localizable", "ChatList.Swiping.Unarchive") } + /// Unmute + internal static var chatListSwipingUnmute: String { return L10n.tr("Localizable", "ChatList.Swiping.Unmute") } + /// Unpin + internal static var chatListSwipingUnpin: String { return L10n.tr("Localizable", "ChatList.Swiping.Unpin") } + /// Unread + internal static var chatListSwipingUnread: String { return L10n.tr("Localizable", "ChatList.Swiping.Unread") } /// views - case chatMessageTooltipViews + internal static var chatMessageTooltipViews: String { return L10n.tr("Localizable", "ChatMessage.Tooltip.Views") } /// channel created - case chatServiceChannelCreated + internal static var chatServiceChannelCreated: String { return L10n.tr("Localizable", "ChatService.ChannelCreated") } + /// Default + internal static var chatWallpaperEmpty: String { return L10n.tr("Localizable", "ChatWallpaper.Empty") } /// Create - case composeCreate + internal static var composeCreate: String { return L10n.tr("Localizable", "Compose.Create") } /// Next - case composeNext + internal static var composeNext: String { return L10n.tr("Localizable", "Compose.Next") } /// Select users - case composeSelectUsers - /// Create secret chat with "%@" - case composeConfirmStartSecretChat(String) + internal static var composeSelectUsers: String { return L10n.tr("Localizable", "Compose.SelectUsers") } + /// Start a secret chat with "%@" + internal static func composeConfirmStartSecretChat(_ p1: String) -> String { + return L10n.tr("Localizable", "Compose.Confirm.StartSecretChat", p1) + } + /// You will be able to add more users after you finish creating the group and convert it to supergroup. + internal static var composeCreateGroupLimitError: String { return L10n.tr("Localizable", "Compose.CreateGroup.LimitError") } /// New Channel - case composePopoverNewChannel + internal static var composePopoverNewChannel: String { return L10n.tr("Localizable", "Compose.Popover.NewChannel") } /// New Group - case composePopoverNewGroup + internal static var composePopoverNewGroup: String { return L10n.tr("Localizable", "Compose.Popover.NewGroup") } /// New Secret Chat - case composePopoverNewSecretChat + internal static var composePopoverNewSecretChat: String { return L10n.tr("Localizable", "Compose.Popover.NewSecretChat") } /// Secret Chat - case composeSelectSecretChat + internal static var composeSelectSecretChat: String { return L10n.tr("Localizable", "Compose.Select.SecretChat") } /// Whom would you like to message? - case composeSelectGroupUsersPlaceholder + internal static var composeSelectGroupUsersPlaceholder: String { return L10n.tr("Localizable", "Compose.SelectGroupUsers.Placeholder") } /// Add the bot to "%@"? - case confirmAddBotToGroup(String) - /// Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway? - case confirmDeleteAdminedChannel - /// Are you sure you want to delete all message history?\n\nThis action cannot be undone. - case confirmDeleteChatUser + internal static func confirmAddBotToGroup(_ p1: String) -> String { + return L10n.tr("Localizable", "Confirm.AddBotToGroup", p1) + } + /// Delete + internal static var confirmDelete: String { return L10n.tr("Localizable", "Confirm.Delete") } + /// Wait! Deleting this channel will remove all of its members and all of its messages will be lost forever.\n\nAre you sure you want to continue? + internal static var confirmDeleteAdminedChannel: String { return L10n.tr("Localizable", "Confirm.DeleteAdminedChannel") } + /// Are you sure you want to delete all message history? + internal static var confirmDeleteChatUser: String { return L10n.tr("Localizable", "Confirm.DeleteChatUser") } /// Are you sure you want to leave this group? - case confirmLeaveGroup + internal static var confirmLeaveGroup: String { return L10n.tr("Localizable", "Confirm.LeaveGroup") } + /// The bot will know your phone number. This can be useful for integration with other services. + internal static var confirmDescPermissionInlineBotContact: String { return L10n.tr("Localizable", "Confirm.Desc.PermissionInlineBotContact") } + /// Share Your Phone Number? + internal static var confirmHeaderPermissionInlineBotContact: String { return L10n.tr("Localizable", "Confirm.Header.PermissionInlineBotContact") } /// connecting - case connectingStatusConnecting + internal static var connectingStatusConnecting: String { return L10n.tr("Localizable", "ConnectingStatus.connecting") } /// connecting to proxy - case connectingStatusConnectingToProxy + internal static var connectingStatusConnectingToProxy: String { return L10n.tr("Localizable", "ConnectingStatus.connectingToProxy") } /// click here to disable proxy - case connectingStatusDisableProxy + internal static var connectingStatusDisableProxy: String { return L10n.tr("Localizable", "ConnectingStatus.DisableProxy") } /// online - case connectingStatusOnline + internal static var connectingStatusOnline: String { return L10n.tr("Localizable", "ConnectingStatus.online") } /// updating - case connectingStatusUpdating + internal static var connectingStatusUpdating: String { return L10n.tr("Localizable", "ConnectingStatus.updating") } /// waiting for network - case connectingStatusWaitingNetwork + internal static var connectingStatusWaitingNetwork: String { return L10n.tr("Localizable", "ConnectingStatus.waitingNetwork") } + /// birthday + internal static var contactInfoBirthdayLabel: String { return L10n.tr("Localizable", "ContactInfo.BirthdayLabel") } + /// Contact Info + internal static var contactInfoContactInfo: String { return L10n.tr("Localizable", "ContactInfo.ContactInfo") } + /// job + internal static var contactInfoJob: String { return L10n.tr("Localizable", "ContactInfo.Job") } + /// home + internal static var contactInfoPhoneLabelHome: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelHome") } + /// home fax + internal static var contactInfoPhoneLabelHomeFax: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelHomeFax") } + /// main + internal static var contactInfoPhoneLabelMain: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelMain") } + /// mobile + internal static var contactInfoPhoneLabelMobile: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelMobile") } + /// other + internal static var contactInfoPhoneLabelOther: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelOther") } + /// pager + internal static var contactInfoPhoneLabelPager: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelPager") } + /// work + internal static var contactInfoPhoneLabelWork: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelWork") } + /// work fax + internal static var contactInfoPhoneLabelWorkFax: String { return L10n.tr("Localizable", "ContactInfo.PhoneLabelWorkFax") } + /// homepage + internal static var contactInfoURLLabelHomepage: String { return L10n.tr("Localizable", "ContactInfo.URLLabelHomepage") } /// Add Contact - case contactsAddContact + internal static var contactsAddContact: String { return L10n.tr("Localizable", "Contacts.AddContact") } /// Contacts - case contactsContacsSeparator - /// This contact is not registered in Telegram yet. You will be able to send them a Telegram message as soon as they sign up. - case contactsNotRegistredDescription - /// Not Telegram Contact - case contactsNotRegistredTitle + internal static var contactsContacsSeparator: String { return L10n.tr("Localizable", "Contacts.ContacsSeparator") } + /// This person is not registered on Telegram yet.\n\nYou will be able to send them a Telegram message as soon as they sign up. + internal static var contactsNotRegistredDescription: String { return L10n.tr("Localizable", "Contacts.NotRegistredDescription") } + /// Not a Telegram User + internal static var contactsNotRegistredTitle: String { return L10n.tr("Localizable", "Contacts.NotRegistredTitle") } /// First Name - case contactsFirstNamePlaceholder + internal static var contactsFirstNamePlaceholder: String { return L10n.tr("Localizable", "Contacts.FirstName.Placeholder") } /// Last Name - case contactsLastNamePlaceholder + internal static var contactsLastNamePlaceholder: String { return L10n.tr("Localizable", "Contacts.LastName.Placeholder") } + /// phone number can't be empty + internal static var contactsPhoneNumberInvalid: String { return L10n.tr("Localizable", "Contacts.PhoneNumber.Invalid") } + /// the person with this phone number is not registered on Telegram yet. + internal static var contactsPhoneNumberNotRegistred: String { return L10n.tr("Localizable", "Contacts.PhoneNumber.NotRegistred") } /// Phone Number - case contactsPhoneNumberPlaceholder + internal static var contactsPhoneNumberPlaceholder: String { return L10n.tr("Localizable", "Contacts.PhoneNumber.Placeholder") } /// Save as... - case contextCopyMedia + internal static var contextCopyMedia: String { return L10n.tr("Localizable", "Context.CopyMedia") } + /// Open in Quick Look + internal static var contextOpenInQuickLook: String { return L10n.tr("Localizable", "Context.OpenInQuickLook") } /// Remove - case contextRecentGifRemove + internal static var contextRecentGifRemove: String { return L10n.tr("Localizable", "Context.RecentGifRemove") } /// Remove - case contextRemoveFaveSticker + internal static var contextRemoveFaveSticker: String { return L10n.tr("Localizable", "Context.RemoveFaveSticker") } /// Show In Finder - case contextShowInFinder + internal static var contextShowInFinder: String { return L10n.tr("Localizable", "Context.ShowInFinder") } /// View Sticker Set - case contextViewStickerSet + internal static var contextViewStickerSet: String { return L10n.tr("Localizable", "Context.ViewStickerSet") } /// Are you sure? This action cannot be undone. - case convertToSuperGroupConfirm - /// Something is wrong, please try again later. - case convertToSupergroupAlertError + internal static var convertToSuperGroupConfirm: String { return L10n.tr("Localizable", "ConvertToSuperGroup.Confirm") } + /// Something went wrong, sorry. Please try again later. + internal static var convertToSupergroupAlertError: String { return L10n.tr("Localizable", "ConvertToSupergroup.Alert.Error") } + /// Cancel + internal static var crashOnLaunchCancel: String { return L10n.tr("Localizable", "CrashOnLaunch.Cancel") } + /// If Telegram keeps crashing immediately after you open it, click OK to log out of the app. This should solve this issue. + internal static var crashOnLaunchInformation: String { return L10n.tr("Localizable", "CrashOnLaunch.Information") } + /// Something’s not right. + internal static var crashOnLaunchMessage: String { return L10n.tr("Localizable", "CrashOnLaunch.Message") } + /// Log out + internal static var crashOnLaunchOK: String { return L10n.tr("Localizable", "CrashOnLaunch.OK") } + /// Sorry, you are a member of too many groups and channels. Please leave some before creating a new one. + internal static var createChannelsTooMuch: String { return L10n.tr("Localizable", "Create.ChannelsTooMuch") } /// Group Name - case createGroupNameHolder - /// Smart Links - case cwLP1JidTitle - /// Make Lower Case - case d9MCDAMdTitle + internal static var createGroupNameHolder: String { return L10n.tr("Localizable", "CreateGroup.NameHolder") } + /// Night Mode + internal static var darkModeConfirmNightModeHeader: String { return L10n.tr("Localizable", "DarkMode.Confirm.NightMode.Header") } + /// Disable + internal static var darkModeConfirmNightModeOK: String { return L10n.tr("Localizable", "DarkMode.Confirm.NightMode.OK") } + /// You have enabled auto night mode. If you want to change dark mode you have to disable it. + internal static var darkModeConfirmNightModeText: String { return L10n.tr("Localizable", "DarkMode.Confirm.NightMode.Text") } + /// Auto-Download Media + internal static var dataAndStorageAutomaticDownload: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload") } + /// Download Folder + internal static var dataAndStorageDownloadFolder: String { return L10n.tr("Localizable", "DataAndStorage.DownloadFolder") } /// Network Usage - case dataAndStorageNetworkUsage + internal static var dataAndStorageNetworkUsage: String { return L10n.tr("Localizable", "DataAndStorage.NetworkUsage") } /// Storage Usage - case dataAndStorageStorageUsage + internal static var dataAndStorageStorageUsage: String { return L10n.tr("Localizable", "DataAndStorage.StorageUsage") } /// AUTOMATIC AUDIO DOWNLOAD - case dataAndStorageAutomaticAudioDownloadHeader + internal static var dataAndStorageAutomaticAudioDownloadHeader: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticAudioDownload.Header") } + /// Files + internal static var dataAndStorageAutomaticDownloadFiles: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Files") } + /// GIFs + internal static var dataAndStorageAutomaticDownloadGIFs: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.GIFs") } /// Groups and Channels - case dataAndStorageAutomaticDownloadGroupsChannels + internal static var dataAndStorageAutomaticDownloadGroupsChannels: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.GroupsChannels") } + /// AUTOMATIC MEDIA DOWNLOAD + internal static var dataAndStorageAutomaticDownloadHeader: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Header") } + /// Video Messages + internal static var dataAndStorageAutomaticDownloadInstantVideo: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.InstantVideo") } + /// Photos + internal static var dataAndStorageAutomaticDownloadPhoto: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Photo") } + /// Reset Auto-Download Settings + internal static var dataAndStorageAutomaticDownloadReset: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Reset") } + /// Videos + internal static var dataAndStorageAutomaticDownloadVideo: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Video") } + /// Voice Messages + internal static var dataAndStorageAutomaticDownloadVoice: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticDownload.Voice") } /// AUTOMATIC PHOTO DOWNLOAD - case dataAndStorageAutomaticPhotoDownloadHeader + internal static var dataAndStorageAutomaticPhotoDownloadHeader: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticPhotoDownload.Header") } /// AUTOMATIC VIDEO DOWNLOAD - case dataAndStorageAutomaticVideoDownloadHeader + internal static var dataAndStorageAutomaticVideoDownloadHeader: String { return L10n.tr("Localizable", "DataAndStorage.AutomaticVideoDownload.Header") } + /// GIFs + internal static var dataAndStorageAutoplayGIFs: String { return L10n.tr("Localizable", "DataAndStorage.Autoplay.GIFs") } + /// AUTO-PLAY MEDIA + internal static var dataAndStorageAutoplayHeader: String { return L10n.tr("Localizable", "DataAndStorage.Autoplay.Header") } + /// Sound on Hover + internal static var dataAndStorageAutoplaySoundOnHover: String { return L10n.tr("Localizable", "DataAndStorage.Autoplay.SoundOnHover") } + /// Videos + internal static var dataAndStorageAutoplayVideos: String { return L10n.tr("Localizable", "DataAndStorage.Autoplay.Videos") } + /// Sound will start playing when you move your cursor over a video. + internal static var dataAndStorageAutoplaySoundOnHoverDesc: String { return L10n.tr("Localizable", "DataAndStorage.Autoplay.SoundOnHover.Desc") } + /// Preload Larger Videos + internal static var dataAndStorageCategoryPreloadLargeVideos: String { return L10n.tr("Localizable", "DataAndStorage.Category.PreloadLargeVideos") } + /// Preload first few seconds (1-2 MB) of videos large than %@ MB for instant playback. + internal static func dataAndStorageCategoryPreloadLargeVideosDesc(_ p1: String) -> String { + return L10n.tr("Localizable", "DataAndStorage.Category.PreloadLargeVideosDesc", p1) + } + /// Channels + internal static var dataAndStorageCategorySettingsChannels: String { return L10n.tr("Localizable", "DataAndStorage.CategorySettings.Channels") } + /// Group Chats + internal static var dataAndStorageCategorySettingsGroupChats: String { return L10n.tr("Localizable", "DataAndStorage.CategorySettings.GroupChats") } + /// Private Chats + internal static var dataAndStorageCategorySettingsPrivateChats: String { return L10n.tr("Localizable", "DataAndStorage.CategorySettings.PrivateChats") } + /// Unlimited + internal static var dataAndStorageCateroryFileSizeUnlimited: String { return L10n.tr("Localizable", "DataAndStorage.CateroryFileSize.Unlimited") } + /// LIMIT BY SIZE + internal static var dataAndStorageCateroryFileSizeLimitHeader: String { return L10n.tr("Localizable", "DataAndStorage.CateroryFileSizeLimit.Header") } + /// Undo all custom auto-download settings. + internal static var dataAndStorageConfirmResetSettings: String { return L10n.tr("Localizable", "DataAndStorage.Confirm.ResetSettings") } /// Today - case dateToday - /// Undo - case drj4nYzgTitle + internal static var dateToday: String { return L10n.tr("Localizable", "Date.Today") } + /// Link Group + internal static var discussionSetModalOK: String { return L10n.tr("Localizable", "Discussion.Set.Modal.OK") } + /// Do you want make **%@** the discussion board for **%@**?\n\nAny member of this group will be able to see messages in the channel. + internal static func discussionSetModalTextChannelPrivateGroup(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Discussion.Set.Modal.Text.ChannelPrivateGroup", p1, p2) + } + /// Do you want make **%@** the discussion board for **%@**?\n\nAny member of this group will able to see all messages in the channel. + internal static func discussionSetModalTextPrivateChannelPublicGroup(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Discussion.Set.Modal.Text.PrivateChannelPublicGroup", p1, p2) + } + /// Do you want make **%@** the discussion board for **%@**? + internal static func discussionSetModalTextPublicChannelPublicGroup(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Discussion.Set.Modal.Text.PublicChannelPublicGroup", p1, p2) + } + /// Discuss + internal static var discussionControllerIconText: String { return L10n.tr("Localizable", "DiscussionController.IconText") } + /// private channel + internal static var discussionControllerPrivateChannel: String { return L10n.tr("Localizable", "DiscussionController.PrivateChannel") } + /// private group + internal static var discussionControllerPrivateGroup: String { return L10n.tr("Localizable", "DiscussionController.PrivateGroup") } + /// Discussion Group + internal static var discussionControllerChannelTitle: String { return L10n.tr("Localizable", "DiscussionController.Channel.Title") } + /// Create a New Group + internal static var discussionControllerChannelEmptyCreateGroup: String { return L10n.tr("Localizable", "DiscussionController.Channel.Empty.CreateGroup") } + /// Everything you post in channel will be forwarded to this group. + internal static var discussionControllerChannelEmptyDescription: String { return L10n.tr("Localizable", "DiscussionController.Channel.Empty.Description") } + /// Select a group chat for discussion that will be displayed in your channel. + internal static var discussionControllerChannelEmptyHeader: String { return L10n.tr("Localizable", "DiscussionController.Channel.Empty.Header") } + /// Everything you post in channel is forwarded to this group. + internal static var discussionControllerChannelSetDescription: String { return L10n.tr("Localizable", "DiscussionController.Channel.Set.Description") } + /// a link to **%@** is shown to all subscribers in the bottom pannel. + internal static func discussionControllerChannelSetHeader(_ p1: String) -> String { + return L10n.tr("Localizable", "DiscussionController.Channel.Set.Header", p1) + } + /// Unlink Group + internal static var discussionControllerChannelSetUnlinkGroup: String { return L10n.tr("Localizable", "DiscussionController.Channel.Set.UnlinkGroup") } + /// Are you sure you want to unlink channel from this group? + internal static var discussionControllerConfrimUnlinkChannel: String { return L10n.tr("Localizable", "DiscussionController.Confrim.UnlinkChannel") } + /// Are you sure you want to unlink group from this channel? + internal static var discussionControllerConfrimUnlinkGroup: String { return L10n.tr("Localizable", "DiscussionController.Confrim.UnlinkGroup") } + /// Proceed + internal static var discussionControllerErrorOK: String { return L10n.tr("Localizable", "DiscussionController.Error.OK") } + /// Warning: If you set this private group as the disccussion group for your channel, all channel subscribers will be able to access the group. "Chat history for new members" will be switched to Visible + internal static var discussionControllerErrorPreHistory: String { return L10n.tr("Localizable", "DiscussionController.Error.PreHistory") } + /// Linked Channel + internal static var discussionControllerGroupTitle: String { return L10n.tr("Localizable", "DiscussionController.Group.Title") } + /// All new messages posted in this channel are forwarded to this group. + internal static var discussionControllerGroupSetDescription: String { return L10n.tr("Localizable", "DiscussionController.Group.Set.Description") } + /// **%@** is linking the group as its discussion board. + internal static func discussionControllerGroupSetHeader(_ p1: String) -> String { + return L10n.tr("Localizable", "DiscussionController.Group.Set.Header", p1) + } + /// Unlink Channel + internal static var discussionControllerGroupSetUnlinkChannel: String { return L10n.tr("Localizable", "DiscussionController.Group.Set.UnlinkChannel") } + /// The channel successfully unlinked. + internal static var discussionControllerGroupUnsetDescription: String { return L10n.tr("Localizable", "DiscussionController.Group.Unset.Description") } /// Spelling and Grammar - case dv1IoYv7Title + internal static var dv1IoYv7Title: String { return L10n.tr("Localizable", "Dv1-io-Yv7.title") } + /// Edit + internal static var editMessageEditCurrentPhoto: String { return L10n.tr("Localizable", "Edit.Message.EditCurrentPhoto") } + /// Add Account + internal static var editAccountAddAccount: String { return L10n.tr("Localizable", "EditAccount.AddAccount") } + /// Change Number + internal static var editAccountChangeNumber: String { return L10n.tr("Localizable", "EditAccount.ChangeNumber") } + /// Log Out + internal static var editAccountLogout: String { return L10n.tr("Localizable", "EditAccount.Logout") } + /// Enter your name and add a profile photo. + internal static var editAccountNameDesc: String { return L10n.tr("Localizable", "EditAccount.NameDesc") } + /// Edit Profile + internal static var editAccountTitle: String { return L10n.tr("Localizable", "EditAccount.Title") } + /// Username + internal static var editAccountUsername: String { return L10n.tr("Localizable", "EditAccount.Username") } + /// RESET + internal static var editImageControlReset: String { return L10n.tr("Localizable", "EditImageControl.Reset") } + /// Are you sure you want to close and discard all changes? + internal static var editImageControlConfirmDiscard: String { return L10n.tr("Localizable", "EditImageControl.Confirm.Discard") } + /// This name is already taken. + internal static var editThameNameAlreadyTaken: String { return L10n.tr("Localizable", "EditThame.Name.AlreadyTaken") } + /// Save + internal static var editThemeEdit: String { return L10n.tr("Localizable", "EditTheme.Edit") } + /// Theme Name + internal static var editThemeNamePlaceholder: String { return L10n.tr("Localizable", "EditTheme.NamePlaceholder") } + /// Create from File... + internal static var editThemeSelectFile: String { return L10n.tr("Localizable", "EditTheme.SelectFile") } + /// This theme will be based on your current theme and wallpaper. Otherwise, you can use a custom theme file if you already have one. + internal static var editThemeSelectFileDesc: String { return L10n.tr("Localizable", "EditTheme.SelectFileDesc") } + /// Update from File... + internal static var editThemeSelectUpdatedFile: String { return L10n.tr("Localizable", "EditTheme.SelectUpdatedFile") } + /// You can update your theme for all users by uploading manual changes from a file. + internal static var editThemeSelectUpdatedFileDesc: String { return L10n.tr("Localizable", "EditTheme.SelectUpdatedFileDesc") } + /// Your theme will be updated for all users each time you change it. Anyone can install it using this link.\n\nTheme links must be longer than 5 characters and can use a-z, 0-9 and underscores. + internal static var editThemeSlugDesc: String { return L10n.tr("Localizable", "EditTheme.SlugDesc") } + /// short link + internal static var editThemeSlugPlaceholder: String { return L10n.tr("Localizable", "EditTheme.SlugPlaceholder") } + /// Edit Theme + internal static var editThemeTitle: String { return L10n.tr("Localizable", "EditTheme.Title") } + /// This link is already taken. Please try a different one. + internal static var editThemeSlugErrorAlreadyExists: String { return L10n.tr("Localizable", "EditTheme.SlugError.AlreadyExists") } + /// invalid format. + internal static var editThemeSlugErrorFormat: String { return L10n.tr("Localizable", "EditTheme.SlugError.Format") } /// Activity & Sport - case emojiActivityAndSport + internal static var emojiActivityAndSport: String { return L10n.tr("Localizable", "Emoji.ActivityAndSport") } /// Animals & Nature - case emojiAnimalsAndNature + internal static var emojiAnimalsAndNature: String { return L10n.tr("Localizable", "Emoji.AnimalsAndNature") } /// Flags - case emojiFlags + internal static var emojiFlags: String { return L10n.tr("Localizable", "Emoji.Flags") } /// Food & Drink - case emojiFoodAndDrink + internal static var emojiFoodAndDrink: String { return L10n.tr("Localizable", "Emoji.FoodAndDrink") } /// Objects - case emojiObjects + internal static var emojiObjects: String { return L10n.tr("Localizable", "Emoji.Objects") } /// Frequently Used - case emojiRecent + internal static var emojiRecent: String { return L10n.tr("Localizable", "Emoji.Recent") } /// Smileys & People - case emojiSmilesAndPeople + internal static var emojiSmilesAndPeople: String { return L10n.tr("Localizable", "Emoji.SmilesAndPeople") } /// Symbols - case emojiSymbols + internal static var emojiSymbols: String { return L10n.tr("Localizable", "Emoji.Symbols") } /// Travel & Places - case emojiTravelAndPlaces + internal static var emojiTravelAndPlaces: String { return L10n.tr("Localizable", "Emoji.TravelAndPlaces") } + /// • Up to %@ members + internal static func emptyGroupInfoLine1(_ p1: String) -> String { + return L10n.tr("Localizable", "EmptyGroupInfo.Line1", p1) + } + /// • Persistent chat history + internal static var emptyGroupInfoLine2: String { return L10n.tr("Localizable", "EmptyGroupInfo.Line2") } + /// • Public links such as t.me/title + internal static var emptyGroupInfoLine3: String { return L10n.tr("Localizable", "EmptyGroupInfo.Line3") } + /// • Admins with different rights + internal static var emptyGroupInfoLine4: String { return L10n.tr("Localizable", "EmptyGroupInfo.Line4") } + /// Groups can have: + internal static var emptyGroupInfoSubtitle: String { return L10n.tr("Localizable", "EmptyGroupInfo.Subtitle") } + /// You have created a group + internal static var emptyGroupInfoTitle: String { return L10n.tr("Localizable", "EmptyGroupInfo.Title") } /// Select a chat to start messaging - case emptyPeerDescription + internal static var emptyPeerDescription: String { return L10n.tr("Localizable", "EmptyPeer.Description") } /// This image and text were derived from the encryption key for this secret chat with **%@**.\n\nIf they look the same on **%@**'s device, end-to-end encryption is guaranteed. - case encryptionKeyDescription(String, String) + internal static func encryptionKeyDescription(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EncryptionKey.Description", p1, p2) + } /// EMOJI - case entertainmentEmoji + internal static var entertainmentEmoji: String { return L10n.tr("Localizable", "Entertainment.Emoji") } /// GIFs - case entertainmentGIF + internal static var entertainmentGIF: String { return L10n.tr("Localizable", "Entertainment.GIF") } /// STICKERS - case entertainmentStickers + internal static var entertainmentStickers: String { return L10n.tr("Localizable", "Entertainment.Stickers") } /// Emoji - case entertainmentSwitchEmoji + internal static var entertainmentSwitchEmoji: String { return L10n.tr("Localizable", "Entertainment.Switch.Emoji") } /// Stickers & GIFs - case entertainmentSwitchGifAndStickers + internal static var entertainmentSwitchGifAndStickers: String { return L10n.tr("Localizable", "Entertainment.Switch.GifAndStickers") } + /// An error occured. Please try again later. + internal static var errorAnError: String { return L10n.tr("Localizable", "Error.AnError") } /// This username is already taken. - case errorUsernameAlreadyTaken + internal static var errorUsernameAlreadyTaken: String { return L10n.tr("Localizable", "Error.Username.AlreadyTaken") } /// This username is invalid. - case errorUsernameInvalid + internal static var errorUsernameInvalid: String { return L10n.tr("Localizable", "Error.Username.Invalid") } /// A username must have at least 5 characters. - case errorUsernameMinimumLength + internal static var errorUsernameMinimumLength: String { return L10n.tr("Localizable", "Error.Username.MinimumLength") } /// A username can't start with a number. - case errorUsernameNumberStart + internal static var errorUsernameNumberStart: String { return L10n.tr("Localizable", "Error.Username.NumberStart") } /// A username can't end with an underscore. - case errorUsernameUnderscopeEnd + internal static var errorUsernameUnderscopeEnd: String { return L10n.tr("Localizable", "Error.Username.UnderscopeEnd") } /// A username can't start with an underscore. - case errorUsernameUnderscopeStart + internal static var errorUsernameUnderscopeStart: String { return L10n.tr("Localizable", "Error.Username.UnderscopeStart") } /// Banned %@ %@ - case eventLogServiceBanned(String, String) + internal static func eventLogServiceBanned(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Banned", p1, p2) + } + /// changed defaults rights + internal static var eventLogServiceChangedDefaultsRights: String { return L10n.tr("Localizable", "EventLog.Service.ChangedDefaultsRights") } /// %@ changed group sticker set - case eventLogServiceChangedStickerSet(String) + internal static func eventLogServiceChangedStickerSet(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.ChangedStickerSet", p1) + } /// %@ deleted message: - case eventLogServiceDeletedMessage(String) + internal static func eventLogServiceDeletedMessage(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.DeletedMessage", p1) + } /// restricted %@ %@ indefinitely - case eventLogServiceDemoted(String, String) + internal static func eventLogServiceDemoted(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Demoted", p1, p2) + } + /// %@ edited caption: + internal static func eventLogServiceEditedCaption(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.EditedCaption", p1) + } + /// %@ edited media: + internal static func eventLogServiceEditedMedia(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.EditedMedia", p1) + } /// %@ edited message: - case eventLogServiceEditedMessage(String) + internal static func eventLogServiceEditedMessage(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.EditedMessage", p1) + } /// Previous Description - case eventLogServicePreviousDesc + internal static var eventLogServicePreviousDesc: String { return L10n.tr("Localizable", "EventLog.Service.PreviousDesc") } /// Previous Link - case eventLogServicePreviousLink + internal static var eventLogServicePreviousLink: String { return L10n.tr("Localizable", "EventLog.Service.PreviousLink") } /// Previous Title - case eventLogServicePreviousTitle + internal static var eventLogServicePreviousTitle: String { return L10n.tr("Localizable", "EventLog.Service.PreviousTitle") } /// promoted %@ %@: - case eventLogServicePromoted(String, String) + internal static func eventLogServicePromoted(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Promoted", p1, p2) + } /// %@ removed group sticker set - case eventLogServiceRemovedStickerSet(String) + internal static func eventLogServiceRemovedStickerSet(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.RemovedStickerSet", p1) + } /// %@ unpinned message - case eventLogServiceRemovePinned(String) + internal static func eventLogServiceRemovePinned(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.RemovePinned", p1) + } /// %@ pinned message: - case eventLogServiceUpdatePinned(String) + internal static func eventLogServiceUpdatePinned(_ p1: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.UpdatePinned", p1) + } + /// Add Members + internal static var eventLogServiceDemoteAddMembers: String { return L10n.tr("Localizable", "EventLog.Service.Demote.AddMembers") } + /// Change Info + internal static var eventLogServiceDemoteChangeInfo: String { return L10n.tr("Localizable", "EventLog.Service.Demote.ChangeInfo") } /// Embed Links - case eventLogServiceDemoteEmbedLinks + internal static var eventLogServiceDemoteEmbedLinks: String { return L10n.tr("Localizable", "EventLog.Service.Demote.EmbedLinks") } + /// Pin Messages + internal static var eventLogServiceDemotePinMessages: String { return L10n.tr("Localizable", "EventLog.Service.Demote.PinMessages") } + /// Post Polls + internal static var eventLogServiceDemotePostPolls: String { return L10n.tr("Localizable", "EventLog.Service.Demote.PostPolls") } + /// Send GIFs + internal static var eventLogServiceDemoteSendGifs: String { return L10n.tr("Localizable", "EventLog.Service.Demote.SendGifs") } /// Send Inline - case eventLogServiceDemoteSendInline + internal static var eventLogServiceDemoteSendInline: String { return L10n.tr("Localizable", "EventLog.Service.Demote.SendInline") } /// Send Media - case eventLogServiceDemoteSendMedia + internal static var eventLogServiceDemoteSendMedia: String { return L10n.tr("Localizable", "EventLog.Service.Demote.SendMedia") } /// Send Messages - case eventLogServiceDemoteSendMessages + internal static var eventLogServiceDemoteSendMessages: String { return L10n.tr("Localizable", "EventLog.Service.Demote.SendMessages") } /// Send Stickers - case eventLogServiceDemoteSendStickers - /// changed restrictions for %@ %@ indefinitely - case eventLogServiceDemotedChanged(String, String) + internal static var eventLogServiceDemoteSendStickers: String { return L10n.tr("Localizable", "EventLog.Service.Demote.SendStickers") } + /// changed the restrictions for %@ %@ indefinitely + internal static func eventLogServiceDemotedChanged(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Demoted.Changed", p1, p2) + } /// restricted %@ %@ until %@ - case eventLogServiceDemotedUntil(String, String, String) + internal static func eventLogServiceDemotedUntil(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Demoted.Until", p1, p2, p3) + } /// changed restrictions for %@ %@ until %@ - case eventLogServiceDemotedChangedUntil(String, String, String) + internal static func eventLogServiceDemotedChangedUntil(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Demoted.Changed.Until", p1, p2, p3) + } /// Add New Admins - case eventLogServicePromoteAddNewAdmins + internal static var eventLogServicePromoteAddNewAdmins: String { return L10n.tr("Localizable", "EventLog.Service.Promote.AddNewAdmins") } /// Add Users - case eventLogServicePromoteAddUsers + internal static var eventLogServicePromoteAddUsers: String { return L10n.tr("Localizable", "EventLog.Service.Promote.AddUsers") } /// Ban Users - case eventLogServicePromoteBanUsers + internal static var eventLogServicePromoteBanUsers: String { return L10n.tr("Localizable", "EventLog.Service.Promote.BanUsers") } /// Change Info - case eventLogServicePromoteChangeInfo + internal static var eventLogServicePromoteChangeInfo: String { return L10n.tr("Localizable", "EventLog.Service.Promote.ChangeInfo") } /// Delete Messages - case eventLogServicePromoteDeleteMessages + internal static var eventLogServicePromoteDeleteMessages: String { return L10n.tr("Localizable", "EventLog.Service.Promote.DeleteMessages") } /// Edit Messages - case eventLogServicePromoteEditMessages + internal static var eventLogServicePromoteEditMessages: String { return L10n.tr("Localizable", "EventLog.Service.Promote.EditMessages") } /// Invite Users Via Link - case eventLogServicePromoteInviteViaLink + internal static var eventLogServicePromoteInviteViaLink: String { return L10n.tr("Localizable", "EventLog.Service.Promote.InviteViaLink") } /// Pin Messages - case eventLogServicePromotePinMessages + internal static var eventLogServicePromotePinMessages: String { return L10n.tr("Localizable", "EventLog.Service.Promote.PinMessages") } /// Post Messages - case eventLogServicePromotePostMessages + internal static var eventLogServicePromotePostMessages: String { return L10n.tr("Localizable", "EventLog.Service.Promote.PostMessages") } /// changed privileges for %@ %@: - case eventLogServicePromotedChanged(String, String) + internal static func eventLogServicePromotedChanged(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "EventLog.Service.Promoted.Changed", p1, p2) + } /// Disable Dark Mode - case fastSettingsDisableDarkMode + internal static var fastSettingsDisableDarkMode: String { return L10n.tr("Localizable", "FastSettings.DisableDarkMode") } /// Enable Dark Mode - case fastSettingsEnableDarkMode + internal static var fastSettingsEnableDarkMode: String { return L10n.tr("Localizable", "FastSettings.EnableDarkMode") } /// Lock Telegram - case fastSettingsLockTelegram + internal static var fastSettingsLockTelegram: String { return L10n.tr("Localizable", "FastSettings.LockTelegram") } /// Mute For 2 Hours - case fastSettingsMute2Hours + internal static var fastSettingsMute2Hours: String { return L10n.tr("Localizable", "FastSettings.Mute2Hours") } /// Set a Passcode - case fastSettingsSetPasscode + internal static var fastSettingsSetPasscode: String { return L10n.tr("Localizable", "FastSettings.SetPasscode") } /// Unmute - case fastSettingsUnmute - /// Substitutions - case feMD8WVrTitle + internal static var fastSettingsUnmute: String { return L10n.tr("Localizable", "FastSettings.Unmute") } + /// forward messages here for quick access + internal static var forwardToSavedMessages: String { return L10n.tr("Localizable", "Forward.ToSavedMessages") } /// %d %@ - case forwardModalActionDescriptionCountable(Int, String) + internal static func forwardModalActionDescriptionCountable(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_countable", p1, p2) + } /// Select a user or chat to forward messages from %@ - case forwardModalActionDescriptionFew(String) + internal static func forwardModalActionDescriptionFew(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_few", p1) + } /// Select a user or chat to forward messages from %@ - case forwardModalActionDescriptionMany(String) + internal static func forwardModalActionDescriptionMany(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_many", p1) + } /// Select a user or chat to forward message from %@ - case forwardModalActionDescriptionOne(String) + internal static func forwardModalActionDescriptionOne(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_one", p1) + } /// Select a user or chat to forward messages from %@ - case forwardModalActionDescriptionOther(String) + internal static func forwardModalActionDescriptionOther(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_other", p1) + } /// Select a user or chat to forward messages from %@ - case forwardModalActionDescriptionTwo(String) + internal static func forwardModalActionDescriptionTwo(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_two", p1) + } /// Select a user or chat to forward messages from %@ - case forwardModalActionDescriptionZero(String) + internal static func forwardModalActionDescriptionZero(_ p1: String) -> String { + return L10n.tr("Localizable", "ForwardModalAction.description_zero", p1) + } /// %d - case forwardModalActionTitleCountable(Int) + internal static func forwardModalActionTitleCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ForwardModalAction.Title_countable", p1) + } /// Forwarding messages - case forwardModalActionTitleFew + internal static var forwardModalActionTitleFew: String { return L10n.tr("Localizable", "ForwardModalAction.Title_few") } /// Forwarding messages - case forwardModalActionTitleMany + internal static var forwardModalActionTitleMany: String { return L10n.tr("Localizable", "ForwardModalAction.Title_many") } /// Forwarding message - case forwardModalActionTitleOne + internal static var forwardModalActionTitleOne: String { return L10n.tr("Localizable", "ForwardModalAction.Title_one") } /// Forwarding messages - case forwardModalActionTitleOther + internal static var forwardModalActionTitleOther: String { return L10n.tr("Localizable", "ForwardModalAction.Title_other") } /// Forwarding messages - case forwardModalActionTitleTwo + internal static var forwardModalActionTitleTwo: String { return L10n.tr("Localizable", "ForwardModalAction.Title_two") } /// Forwarding messages - case forwardModalActionTitleZero + internal static var forwardModalActionTitleZero: String { return L10n.tr("Localizable", "ForwardModalAction.Title_zero") } /// Delete - case galleryContextDeletePhoto - /// %d of %d - case galleryCounter(Int, Int) + internal static var galleryContextDeletePhoto: String { return L10n.tr("Localizable", "Gallery.ContextDeletePhoto") } + /// Save GIF + internal static var gallerySaveGif: String { return L10n.tr("Localizable", "Gallery.SaveGif") } /// Copy to Clipboard - case galleryContextCopyToClipboard + internal static var galleryContextCopyToClipboard: String { return L10n.tr("Localizable", "Gallery.Context.CopyToClipboard") } + /// Set As Main Photo + internal static var galleryContextMainPhoto: String { return L10n.tr("Localizable", "Gallery.Context.MainPhoto") } /// Save As... - case galleryContextSaveAs + internal static var galleryContextSaveAs: String { return L10n.tr("Localizable", "Gallery.Context.SaveAs") } + /// Shared Media + internal static var galleryContextShowGallery: String { return L10n.tr("Localizable", "Gallery.Context.ShowGallery") } /// Show Message - case galleryContextShowMessage + internal static var galleryContextShowMessage: String { return L10n.tr("Localizable", "Gallery.Context.ShowMessage") } + /// %d + internal static func galleryContextShareAllItemsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_countable", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_few", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_many", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_one", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_other", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_two", p1) + } + /// All %d Items + internal static func galleryContextShareAllItemsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllItems_zero", p1) + } + /// %d + internal static func galleryContextShareAllPhotosCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_countable", p1) + } + /// All %d Photos + internal static func galleryContextShareAllPhotosFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_few", p1) + } + /// All %d Photos + internal static func galleryContextShareAllPhotosMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_many", p1) + } + /// All %d Photo + internal static func galleryContextShareAllPhotosOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_one", p1) + } + /// All %d Photos + internal static func galleryContextShareAllPhotosOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_other", p1) + } + /// All %d Photos + internal static func galleryContextShareAllPhotosTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_two", p1) + } + /// All %d Photos + internal static func galleryContextShareAllPhotosZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllPhotos_zero", p1) + } + /// %d + internal static func galleryContextShareAllVideosCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_countable", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_few", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_many", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_one", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_other", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_two", p1) + } + /// All %d Videos + internal static func galleryContextShareAllVideosZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Gallery.Context.Share.AllVideos_zero", p1) + } + /// This Photo + internal static var galleryContextShareThisPhoto: String { return L10n.tr("Localizable", "Gallery.Context.Share.ThisPhoto") } + /// This Video + internal static var galleryContextShareThisVideo: String { return L10n.tr("Localizable", "Gallery.Context.Share.ThisVideo") } + /// Please wait for the photo to be fully downloaded. + internal static var galleryWaitDownloadPhoto: String { return L10n.tr("Localizable", "Gallery.WaitDownload.Photo") } + /// Please wait for the video to be fully downloaded. + internal static var galleryWaitDownloadVideo: String { return L10n.tr("Localizable", "Gallery.WaitDownload.Video") } + /// GIF saved to\n[Downloads]() + internal static var galleryViewFastSaveGif1: String { return L10n.tr("Localizable", "GalleryView.FastSave.Gif1") } + /// Image saved to\n[Downloads]() + internal static var galleryViewFastSaveImage1: String { return L10n.tr("Localizable", "GalleryView.FastSave.Image1") } + /// Video saved to\n[Downloads]() + internal static var galleryViewFastSaveVideo1: String { return L10n.tr("Localizable", "GalleryView.FastSave.Video1") } + /// Accent Color + internal static var generalSettingsAccentColor: String { return L10n.tr("Localizable", "GeneralSettings.AccentColor") } + /// Accept Secret Chats + internal static var generalSettingsAcceptSecretChats: String { return L10n.tr("Localizable", "GeneralSettings.AcceptSecretChats") } + /// ADVANCED + internal static var generalSettingsAdvancedHeader: String { return L10n.tr("Localizable", "GeneralSettings.AdvancedHeader") } /// APPEARANCE SETTINGS - case generalSettingsAppearanceSettings + internal static var generalSettingsAppearanceSettings: String { return L10n.tr("Localizable", "GeneralSettings.AppearanceSettings") } + /// Autoplay GIFs + internal static var generalSettingsAutoplayGifs: String { return L10n.tr("Localizable", "GeneralSettings.AutoplayGifs") } + /// Big Emoji + internal static var generalSettingsBigEmoji: String { return L10n.tr("Localizable", "GeneralSettings.BigEmoji") } + /// Chat Background + internal static var generalSettingsChatBackground: String { return L10n.tr("Localizable", "GeneralSettings.ChatBackground") } + /// Copy Text Formatting + internal static var generalSettingsCopyRTF: String { return L10n.tr("Localizable", "GeneralSettings.CopyRTF") } /// Dark Mode - case generalSettingsDarkMode - /// Automatic replace emojis - case generalSettingsEmojiReplacements + internal static var generalSettingsDarkMode: String { return L10n.tr("Localizable", "GeneralSettings.DarkMode") } + /// EMOJI & STICKERS + internal static var generalSettingsEmojiAndStickers: String { return L10n.tr("Localizable", "GeneralSettings.EmojiAndStickers") } + /// Suggest Emoji + internal static var generalSettingsEmojiPrediction: String { return L10n.tr("Localizable", "GeneralSettings.EmojiPrediction") } + /// Automatically replace emojis + internal static var generalSettingsEmojiReplacements: String { return L10n.tr("Localizable", "GeneralSettings.EmojiReplacements") } /// Sidebar - case generalSettingsEnableSidebar + internal static var generalSettingsEnableSidebar: String { return L10n.tr("Localizable", "GeneralSettings.EnableSidebar") } /// FORCE TOUCH ACTION - case generalSettingsForceTouchHeader + internal static var generalSettingsForceTouchHeader: String { return L10n.tr("Localizable", "GeneralSettings.ForceTouchHeader") } /// GENERAL SETTINGS - case generalSettingsGeneralSettings + internal static var generalSettingsGeneralSettings: String { return L10n.tr("Localizable", "GeneralSettings.GeneralSettings") } /// In-App Sounds - case generalSettingsInAppSounds + internal static var generalSettingsInAppSounds: String { return L10n.tr("Localizable", "GeneralSettings.InAppSounds") } /// INPUT SETTINGS - case generalSettingsInputSettings - /// Large Message Font - case generalSettingsLargeFonts + internal static var generalSettingsInputSettings: String { return L10n.tr("Localizable", "GeneralSettings.InputSettings") } + /// INSTANT VIEW + internal static var generalSettingsInstantViewHeader: String { return L10n.tr("Localizable", "GeneralSettings.InstantViewHeader") } + /// INTERFACE + internal static var generalSettingsInterfaceHeader: String { return L10n.tr("Localizable", "GeneralSettings.InterfaceHeader") } /// Handle media keys for in-app player - case generalSettingsMediaKeysForInAppPlayer + internal static var generalSettingsMediaKeysForInAppPlayer: String { return L10n.tr("Localizable", "GeneralSettings.MediaKeysForInAppPlayer") } + /// Reopen Last Chat On Launch + internal static var generalSettingsOpenLatestChatOnLaunch: String { return L10n.tr("Localizable", "GeneralSettings.OpenLatestChatOnLaunch") } /// Use ⌘ + Enter to send - case generalSettingsSendByCmdEnter + internal static var generalSettingsSendByCmdEnter: String { return L10n.tr("Localizable", "GeneralSettings.SendByCmdEnter") } /// Use Enter to send - case generalSettingsSendByEnter + internal static var generalSettingsSendByEnter: String { return L10n.tr("Localizable", "GeneralSettings.SendByEnter") } + /// Keyboard Shortcuts + internal static var generalSettingsShortcuts: String { return L10n.tr("Localizable", "GeneralSettings.Shortcuts") } + /// SHORTCUTS + internal static var generalSettingsShortcutsHeader: String { return L10n.tr("Localizable", "GeneralSettings.ShortcutsHeader") } + /// Suggest Articles in Search + internal static var generalSettingsShowArticlesInSearch: String { return L10n.tr("Localizable", "GeneralSettings.ShowArticlesInSearch") } + /// Show Calls Tab + internal static var generalSettingsShowCallsTab: String { return L10n.tr("Localizable", "GeneralSettings.ShowCallsTab") } + /// Menu Bar Item + internal static var generalSettingsStatusBarItem: String { return L10n.tr("Localizable", "GeneralSettings.StatusBarItem") } /// A color scheme for nighttime and dark desktops - case generalSettingsDarkModeDescription + internal static var generalSettingsDarkModeDescription: String { return L10n.tr("Localizable", "GeneralSettings.DarkMode.Description") } + /// Disable + internal static var generalSettingsEmojiPredictionDisable: String { return L10n.tr("Localizable", "GeneralSettings.EmojiPrediction.Disable") } + /// Disable emoji suggestions? You can re-enable them in Settings at any time. + internal static var generalSettingsEmojiPredictionDisableText: String { return L10n.tr("Localizable", "GeneralSettings.EmojiPrediction.DisableText") } /// Use large font for messages - case generalSettingsFontDescription + internal static var generalSettingsFontDescription: String { return L10n.tr("Localizable", "GeneralSettings.Font.Description") } /// Edit Message - case generalSettingsForceTouchEdit + internal static var generalSettingsForceTouchEdit: String { return L10n.tr("Localizable", "GeneralSettings.ForceTouch.Edit") } /// Forward Message - case generalSettingsForceTouchForward + internal static var generalSettingsForceTouchForward: String { return L10n.tr("Localizable", "GeneralSettings.ForceTouch.Forward") } + /// Preview Media + internal static var generalSettingsForceTouchPreviewMedia: String { return L10n.tr("Localizable", "GeneralSettings.ForceTouch.PreviewMedia") } /// Reply to Message - case generalSettingsForceTouchReply + internal static var generalSettingsForceTouchReply: String { return L10n.tr("Localizable", "GeneralSettings.ForceTouch.Reply") } + /// Scroll With Spacebar + internal static var generalSettingsInstantViewScrollBySpace: String { return L10n.tr("Localizable", "GeneralSettings.InstantView.ScrollBySpace") } + /// More Info + internal static var genericErrorMoreInfo: String { return L10n.tr("Localizable", "Generic.ErrorMoreInfo") } + /// REACTIONS + internal static var gifsPaneReactions: String { return L10n.tr("Localizable", "GifsPane.Reactions") } + /// TRENDING GIFS + internal static var gifsPaneTrending: String { return L10n.tr("Localizable", "GifsPane.Trending") } + /// Total + internal static var graphTotal: String { return L10n.tr("Localizable", "Graph.Total") } + /// Zoom Out + internal static var graphZoomOut: String { return L10n.tr("Localizable", "Graph.ZoomOut") } /// New Group - case groupCreateGroup + internal static var groupCreateGroup: String { return L10n.tr("Localizable", "Group.CreateGroup") } /// New Group - case groupNewGroup - /// Sorry, this group does not seem to exist. - case groupUnavailable + internal static var groupNewGroup: String { return L10n.tr("Localizable", "Group.NewGroup") } + /// Sorry, this group doesn't seem to exist. + internal static var groupUnavailable: String { return L10n.tr("Localizable", "Group.Unavailable") } /// Change Group Info - case groupEditAdminPermissionChangeInfo + internal static var groupEditAdminPermissionChangeInfo: String { return L10n.tr("Localizable", "Group.EditAdmin.Permission.ChangeInfo") } /// **No events here yet**\n\nThere were no service actions taken by the group's members and admins for the last 48 hours. - case groupEventLogEmptyText - /// %@ removed group description: - case groupEventLogServiceAboutRemoved(String) - /// %@ edited group description: - case groupEventLogServiceAboutUpdated(String) + internal static var groupEventLogEmptyText: String { return L10n.tr("Localizable", "Group.EventLog.EmptyText") } + /// %@ removed the group's description: + internal static func groupEventLogServiceAboutRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.AboutRemoved", p1) + } + /// %@ edited the group's description: + internal static func groupEventLogServiceAboutUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.AboutUpdated", p1) + } /// %@ disabled group invites - case groupEventLogServiceDisableInvites(String) + internal static func groupEventLogServiceDisableInvites(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.DisableInvites", p1) + } /// %@ enabled group invites - case groupEventLogServiceEnableInvites(String) - /// %@ removed group link: - case groupEventLogServiceLinkRemoved(String) - /// %@ edited group link: - case groupEventLogServiceLinkUpdated(String) + internal static func groupEventLogServiceEnableInvites(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.EnableInvites", p1) + } + /// %@ removed the group's link: + internal static func groupEventLogServiceLinkRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.LinkRemoved", p1) + } + /// %@ edited the group's link: + internal static func groupEventLogServiceLinkUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.LinkUpdated", p1) + } /// %@ removed group photo - case groupEventLogServicePhotoRemoved(String) - /// %@ updated group photo - case groupEventLogServicePhotoUpdated(String) - /// %@ edited group title: - case groupEventLogServiceTitleUpdated(String) + internal static func groupEventLogServicePhotoRemoved(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.PhotoRemoved", p1) + } + /// %@ updated the group's photo + internal static func groupEventLogServicePhotoUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.PhotoUpdated", p1) + } + /// %@ edited the group's title: + internal static func groupEventLogServiceTitleUpdated(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.TitleUpdated", p1) + } /// %@ joined the group - case groupEventLogServiceUpdateJoin(String) + internal static func groupEventLogServiceUpdateJoin(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.UpdateJoin", p1) + } /// %@ left the group - case groupEventLogServiceUpdateLeft(String) + internal static func groupEventLogServiceUpdateLeft(_ p1: String) -> String { + return L10n.tr("Localizable", "Group.EventLog.Service.UpdateLeft", p1) + } + /// Sorry, the target user has too many location-based groups already. Please ask them to delete or transfer one of their existing ones first. + internal static var groupOwnershipTransferErrorLocatedGroupsTooMuch: String { return L10n.tr("Localizable", "Group.OwnershipTransfer.ErrorLocatedGroupsTooMuch") } + /// Sorry, this group has too many admins and the new owner can't be added. Please remove one of the existing admins first. + internal static var groupTransferOwnerErrorAdminsTooMuch: String { return L10n.tr("Localizable", "Group.TransferOwner.ErrorAdminsTooMuch") } + /// Sorry, this user is not a member of this group and their privacy settings prevent you from adding them manually. + internal static var groupTransferOwnerErrorPrivacyRestricted: String { return L10n.tr("Localizable", "Group.TransferOwner.ErrorPrivacyRestricted") } /// All Members Are Admins - case groupAdminsAllMembersAdmins - /// Only admins can add and remove members, edit name and photo of this group. - case groupAdminsDescAdminInvites - /// Group members can add new members, edit name and photo of this group. - case groupAdminsDescAllInvites + internal static var groupAdminsAllMembersAdmins: String { return L10n.tr("Localizable", "GroupAdmins.AllMembersAdmins") } + /// Only admins can add and remove members, and can edit the group's name and photo. + internal static var groupAdminsDescAdminInvites: String { return L10n.tr("Localizable", "GroupAdmins.Desc.AdminInvites") } + /// Group members can add new members, and can edit the name or photo of the group. + internal static var groupAdminsDescAllInvites: String { return L10n.tr("Localizable", "GroupAdmins.Desc.AllInvites") } + /// Sorry, if a person left a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs). + internal static var groupInfoAddUserLeftError: String { return L10n.tr("Localizable", "GroupInfo.AddUserLeftError") } + /// Administrators + internal static var groupInfoAdministrators: String { return L10n.tr("Localizable", "GroupInfo.Administrators") } + /// ⚠️ Warning: Many users reported this group as a scam. Please be careful, especially if it asks you for money. + internal static var groupInfoScamWarning: String { return L10n.tr("Localizable", "GroupInfo.ScamWarning") } + /// Administrators + internal static var groupInfoAdministratorsTitle: String { return L10n.tr("Localizable", "GroupInfo.Administrators.Title") } + /// Add Exception + internal static var groupInfoPermissionsAddException: String { return L10n.tr("Localizable", "GroupInfo.Permissions.AddException") } + /// EXCEPTIONS + internal static var groupInfoPermissionsExceptions: String { return L10n.tr("Localizable", "GroupInfo.Permissions.Exceptions") } + /// Removed Users + internal static var groupInfoPermissionsRemoved: String { return L10n.tr("Localizable", "GroupInfo.Permissions.Removed") } + /// Search Exceptions + internal static var groupInfoPermissionsSearchPlaceholder: String { return L10n.tr("Localizable", "GroupInfo.Permissions.SearchPlaceholder") } + /// WHAT CAN MEMBERS OF THIS GROUP DO? + internal static var groupInfoPermissionsSectionTitle: String { return L10n.tr("Localizable", "GroupInfo.Permissions.SectionTitle") } /// Anyone who has Telegram installed will be able to join your channel by following this link - case groupInvationChannelDescription + internal static var groupInvationChannelDescription: String { return L10n.tr("Localizable", "GroupInvation.ChannelDescription") } /// Copy Link - case groupInvationCopyLink - /// Anyone who has Telegram installed will be able to join your group by following this link - case groupInvationGroupDescription + internal static var groupInvationCopyLink: String { return L10n.tr("Localizable", "GroupInvation.CopyLink") } + /// Anyone who has Telegram installed will be able to join your group by opening this link. + internal static var groupInvationGroupDescription: String { return L10n.tr("Localizable", "GroupInvation.GroupDescription") } /// Revoke - case groupInvationRevoke + internal static var groupInvationRevoke: String { return L10n.tr("Localizable", "GroupInvation.Revoke") } /// Share Link - case groupInvationShare - /// No groups in common - case groupsInCommonEmpty + internal static var groupInvationShare: String { return L10n.tr("Localizable", "GroupInvation.Share") } + /// Exception added by %@ %@ + internal static func groupPermissionAddedInfo(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "GroupPermission.AddedInfo", p1, p2) + } + /// Exception Added + internal static var groupPermissionAddSuccess: String { return L10n.tr("Localizable", "GroupPermission.AddSuccess") } + /// Apply + internal static var groupPermissionApplyAlertAction: String { return L10n.tr("Localizable", "GroupPermission.ApplyAlertAction") } + /// You have changed this user's rights in %@.\nApply Changes? + internal static func groupPermissionApplyAlertText(_ p1: String) -> String { + return L10n.tr("Localizable", "GroupPermission.ApplyAlertText", p1) + } + /// Delete Exception + internal static var groupPermissionDelete: String { return L10n.tr("Localizable", "GroupPermission.Delete") } + /// Duration + internal static var groupPermissionDuration: String { return L10n.tr("Localizable", "GroupPermission.Duration") } + /// New Exception + internal static var groupPermissionNewTitle: String { return L10n.tr("Localizable", "GroupPermission.NewTitle") } + /// no add + internal static var groupPermissionNoAddMembers: String { return L10n.tr("Localizable", "GroupPermission.NoAddMembers") } + /// no info + internal static var groupPermissionNoChangeInfo: String { return L10n.tr("Localizable", "GroupPermission.NoChangeInfo") } + /// no pin + internal static var groupPermissionNoPinMessages: String { return L10n.tr("Localizable", "GroupPermission.NoPinMessages") } + /// no GIFs + internal static var groupPermissionNoSendGifs: String { return L10n.tr("Localizable", "GroupPermission.NoSendGifs") } + /// no links + internal static var groupPermissionNoSendLinks: String { return L10n.tr("Localizable", "GroupPermission.NoSendLinks") } + /// no media + internal static var groupPermissionNoSendMedia: String { return L10n.tr("Localizable", "GroupPermission.NoSendMedia") } + /// no messages + internal static var groupPermissionNoSendMessages: String { return L10n.tr("Localizable", "GroupPermission.NoSendMessages") } + /// no polls + internal static var groupPermissionNoSendPolls: String { return L10n.tr("Localizable", "GroupPermission.NoSendPolls") } + /// This permission is not available in public groups. + internal static var groupPermissionNotAvailableInPublicGroups: String { return L10n.tr("Localizable", "GroupPermission.NotAvailableInPublicGroups") } + /// WHAT CAN THIS MEMBER DO? + internal static var groupPermissionSectionTitle: String { return L10n.tr("Localizable", "GroupPermission.SectionTitle") } + /// Exception + internal static var groupPermissionTitle: String { return L10n.tr("Localizable", "GroupPermission.Title") } + /// Group Statistics + internal static var groupStatsTitle: String { return L10n.tr("Localizable", "GroupStats.Title") } /// CHOOSE FROM YOUR STICKERS - case groupStickersChooseHeader - /// You can create your own custom sticker set using @stickers bot. - case groupStickersCreateDescription - /// Try again or choose from list below - case groupStickersEmptyDesc + internal static var groupStickersChooseHeader: String { return L10n.tr("Localizable", "GroupStickers.ChooseHeader") } + /// You can create your own custom sticker set using the @stickers bot. + internal static var groupStickersCreateDescription: String { return L10n.tr("Localizable", "GroupStickers.CreateDescription") } + /// Try again or choose from the list below + internal static var groupStickersEmptyDesc: String { return L10n.tr("Localizable", "GroupStickers.EmptyDesc") } /// No such sticker set found - case groupStickersEmptyHeader - /// Paste - case gvau4SdLTitle + internal static var groupStickersEmptyHeader: String { return L10n.tr("Localizable", "GroupStickers.EmptyHeader") } + /// No groups in common + internal static var groupsInCommonEmpty: String { return L10n.tr("Localizable", "GroupsInCommon.Empty") } /// View - case h8h7bM4vTitle - /// Show Spelling and Grammar - case hFoCyZxITitle + internal static var h8h7bM4vTitle: String { return L10n.tr("Localizable", "H8h-7b-M4v.title") } /// Text Replacement - case hfqgknfaTitle - /// Smart Quotes - case hQb2vFYvTitle + internal static var hfqgknfaTitle: String { return L10n.tr("Localizable", "HFQ-gK-NFA.title") } + /// Show Spelling and Grammar + internal static var hFoCyZxITitle: String { return L10n.tr("Localizable", "HFo-cy-zxI.title") } /// View - case hyVFhRgOTitle - /// Check Document Now - case hz2CUCR7Title - /// open %@? - case inAppLinksConfirmOpenExternal(String) + internal static var hyVFhRgOTitle: String { return L10n.tr("Localizable", "HyV-fh-RgO.title") } + /// Join + internal static var ivChannelJoin: String { return L10n.tr("Localizable", "IV.Channel.Join") } + /// Do you want to open "%@"? + internal static func inAppLinksConfirmOpenExternalNew(_ p1: String) -> String { + return L10n.tr("Localizable", "InAppLinks.Confirm.OpenExternalNew", p1) + } + /// Open Link + internal static var inAppLinksConfirmOpenExternalHeader: String { return L10n.tr("Localizable", "InAppLinks.Confirm.OpenExternal.Header") } + /// Open + internal static var inAppLinksConfirmOpenExternalOK: String { return L10n.tr("Localizable", "InAppLinks.Confirm.OpenExternal.OK") } + /// Too many groups and channels + internal static var inactiveChannelsBlockHeader: String { return L10n.tr("Localizable", "InactiveChannels.BlockHeader") } + /// LEAST ACTIVE + internal static var inactiveChannelsHeader: String { return L10n.tr("Localizable", "InactiveChannels.Header") } + /// %d + internal static func inactiveChannelsInactiveMonthCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_countable", p1) + } + /// inactive %d months + internal static func inactiveChannelsInactiveMonthFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_few", p1) + } + /// inactive %d months + internal static func inactiveChannelsInactiveMonthMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_many", p1) + } + /// inactive %d month + internal static func inactiveChannelsInactiveMonthOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_one", p1) + } + /// inactive %d months + internal static func inactiveChannelsInactiveMonthOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_other", p1) + } + /// inactive %d months + internal static func inactiveChannelsInactiveMonthTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_two", p1) + } + /// inactive %d month + internal static func inactiveChannelsInactiveMonthZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveMonth_zero", p1) + } + /// %d + internal static func inactiveChannelsInactiveWeekCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_countable", p1) + } + /// inactive %d weeks + internal static func inactiveChannelsInactiveWeekFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_few", p1) + } + /// inactive %d weeks + internal static func inactiveChannelsInactiveWeekMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_many", p1) + } + /// inactive %d week + internal static func inactiveChannelsInactiveWeekOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_one", p1) + } + /// inactive %d weeks + internal static func inactiveChannelsInactiveWeekOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_other", p1) + } + /// inactive %d weeks + internal static func inactiveChannelsInactiveWeekTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_two", p1) + } + /// inactive %d week + internal static func inactiveChannelsInactiveWeekZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveWeek_zero", p1) + } + /// %d + internal static func inactiveChannelsInactiveYearCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_countable", p1) + } + /// inactive %d years + internal static func inactiveChannelsInactiveYearFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_few", p1) + } + /// inactive %d years + internal static func inactiveChannelsInactiveYearMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_many", p1) + } + /// inactive %d year + internal static func inactiveChannelsInactiveYearOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_one", p1) + } + /// inactive %d years + internal static func inactiveChannelsInactiveYearOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_other", p1) + } + /// inactive %d years + internal static func inactiveChannelsInactiveYearTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_two", p1) + } + /// inactive %d year + internal static func inactiveChannelsInactiveYearZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "InactiveChannels.InactiveYear_zero", p1) + } + /// Leave + internal static var inactiveChannelsOK: String { return L10n.tr("Localizable", "InactiveChannels.OK") } + /// Limit Reached + internal static var inactiveChannelsTitle: String { return L10n.tr("Localizable", "InactiveChannels.Title") } /// Select a user or chat to share content via %@ - case inlineModalActionDesc(String) + internal static func inlineModalActionDesc(_ p1: String) -> String { + return L10n.tr("Localizable", "InlineModalAction.Desc", p1) + } /// Share bot content - case inlineModalActionTitle + internal static var inlineModalActionTitle: String { return L10n.tr("Localizable", "InlineModalAction.Title") } /// File - case inputAttachPopoverFile + internal static var inputAttachPopoverFile: String { return L10n.tr("Localizable", "InputAttach.Popover.File") } + /// Location + internal static var inputAttachPopoverLocation: String { return L10n.tr("Localizable", "InputAttach.Popover.Location") } /// Photo Or Video - case inputAttachPopoverPhotoOrVideo + internal static var inputAttachPopoverPhotoOrVideo: String { return L10n.tr("Localizable", "InputAttach.Popover.PhotoOrVideo") } /// Camera - case inputAttachPopoverPicture + internal static var inputAttachPopoverPicture: String { return L10n.tr("Localizable", "InputAttach.Popover.Picture") } + /// Poll + internal static var inputAttachPopoverPoll: String { return L10n.tr("Localizable", "InputAttach.Popover.Poll") } + /// Day: + internal static var inputDataDateDayPlaceholder: String { return L10n.tr("Localizable", "InputData.Date.Day.Placeholder") } + /// Day + internal static var inputDataDateDayPlaceholder1: String { return L10n.tr("Localizable", "InputData.Date.Day.Placeholder1") } + /// Month: + internal static var inputDataDateMonthPlaceholder: String { return L10n.tr("Localizable", "InputData.Date.Month.Placeholder") } + /// Month + internal static var inputDataDateMonthPlaceholder1: String { return L10n.tr("Localizable", "InputData.Date.Month.Placeholder1") } + /// Year: + internal static var inputDataDateYearPlaceholder: String { return L10n.tr("Localizable", "InputData.Date.Year.Placeholder") } + /// Year + internal static var inputDataDateYearPlaceholder1: String { return L10n.tr("Localizable", "InputData.Date.Year.Placeholder1") } + /// TEXT + internal static var inputFormatterTextHeader: String { return L10n.tr("Localizable", "InputFormatter.Text.Header") } + /// URL + internal static var inputFormatterURLHeader: String { return L10n.tr("Localizable", "InputFormatter.URL.Header") } + /// URL + internal static var inputFormatterURLPlaceholder: String { return L10n.tr("Localizable", "InputFormatter.URL.Placeholder") } + /// Password + internal static var inputPasswordControllerPlaceholder: String { return L10n.tr("Localizable", "InputPasswordController.Placeholder") } + /// Invalid password. Please try again + internal static var inputPasswordControllerErrorWrongPassword: String { return L10n.tr("Localizable", "InputPasswordController.Error.WrongPassword") } /// Archived Stickers - case installedStickersArchived + internal static var installedStickersArchived: String { return L10n.tr("Localizable", "InstalledStickers.Archived") } /// Artists are welcome to add their own sticker sets using our @stickers bot.\n\nTap on a sticker to view and add the whole set. - case installedStickersDescrpiption + internal static var installedStickersDescrpiption: String { return L10n.tr("Localizable", "InstalledStickers.Descrpiption") } + /// Loop Animated Stickers + internal static var installedStickersLoopAnimated: String { return L10n.tr("Localizable", "InstalledStickers.LoopAnimated") } /// STICKER SETS - case installedStickersPacksTitle + internal static var installedStickersPacksTitle: String { return L10n.tr("Localizable", "InstalledStickers.PacksTitle") } /// Trending Stickers - case installedStickersTranding + internal static var installedStickersTranding: String { return L10n.tr("Localizable", "InstalledStickers.Tranding") } /// Delete - case installedStickersRemoveDelete + internal static var installedStickersRemoveDelete: String { return L10n.tr("Localizable", "InstalledStickers.Remove.Delete") } /// Stickers will be archived, you can quickly restore it later from the Archived Stickers section. - case installedStickersRemoveDescription + internal static var installedStickersRemoveDescription: String { return L10n.tr("Localizable", "InstalledStickers.Remove.Description") } /// By %1$@ • %2$@ - case instantPageAuthorAndDateTitle(String, String) - /// Join - case ivChannelJoin + internal static func instantPageAuthorAndDateTitle(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "InstantPage.AuthorAndDateTitle", p1, p2) + } + /// %@ • %@ + internal static func instantPageRelatedArticleAuthorAndDateTitle(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "InstantPage.RelatedArticleAuthorAndDateTitle", p1, p2) + } + /// Sorry, the target user is a member of too many groups and channels. Please ask them to leave some first. + internal static var inviteChannelsTooMuch: String { return L10n.tr("Localizable", "Invite.ChannelsTooMuch") } + /// Sorry, you are a member of too many groups and channels. Please leave some before joining one. + internal static var joinChannelsTooMuch: String { return L10n.tr("Localizable", "Join.ChannelsTooMuch") } + /// Inactive Chats + internal static var joinInactiveChannels: String { return L10n.tr("Localizable", "Join.InactiveChannels") } /// Join - case joinLinkJoin + internal static var joinLinkJoin: String { return L10n.tr("Localizable", "JoinLink.Join") } /// Show All - case kd2MpPUSTitle + internal static var kd2MpPUSTitle: String { return L10n.tr("Localizable", "Kd2-mp-pUS.title") } /// Bring All to Front - case le2AR0XJTitle + internal static var le2AR0XJTitle: String { return L10n.tr("Localizable", "LE2-aR-0XJ.title") } + /// OFFICIAL TRANSLATIONS + internal static var languageOfficialTransationsHeader: String { return L10n.tr("Localizable", "Language.OfficialTransationsHeader") } + /// Are you sure you want to remove this lang-pack? + internal static var languageRemovePack: String { return L10n.tr("Localizable", "Language.RemovePack") } + /// %d + internal static func lastSeenHoursAgoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_countable", p1) + } + /// last seen %d hours ago + internal static func lastSeenHoursAgoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_few", p1) + } + /// last seen %d hours ago + internal static func lastSeenHoursAgoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_many", p1) + } + /// last seen %d hour ago + internal static func lastSeenHoursAgoOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_one", p1) + } + /// last seen %d hours ago + internal static func lastSeenHoursAgoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_other", p1) + } + /// last seen %d hours ago + internal static func lastSeenHoursAgoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_two", p1) + } + /// last seen %d hour ago + internal static func lastSeenHoursAgoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "LastSeen.HoursAgo_zero", p1) + } /// Welcome to the new super-fast and stable Telegram for macOS, fully rewritten in Swift 3.0. - case legacyIntroDescription1 + internal static var legacyIntroDescription1: String { return L10n.tr("Localizable", "Legacy.Intro.Description1") } /// Please note that your existing secret chats will be available in read-only mode. You can of course create new ones to continue chatting. - case legacyIntroDescription2 + internal static var legacyIntroDescription2: String { return L10n.tr("Localizable", "Legacy.Intro.Description2") } /// Start Messaging - case legacyIntroNext + internal static var legacyIntroNext: String { return L10n.tr("Localizable", "Legacy.Intro.Next") } /// Are you sure you want to revoke this link? Once you do, no one will be able to join the channel using it. - case linkInvationChannelConfirmRevoke + internal static var linkInvationChannelConfirmRevoke: String { return L10n.tr("Localizable", "LinkInvation.Channel.Confirm.Revoke") } /// Revoke - case linkInvationConfirmOk + internal static var linkInvationConfirmOk: String { return L10n.tr("Localizable", "LinkInvation.Confirm.Ok") } /// Are you sure you want to revoke this link? Once you do, no one will be able to join the group using it. - case linkInvationGroupConfirmRevoke + internal static var linkInvationGroupConfirmRevoke: String { return L10n.tr("Localizable", "LinkInvation.Group.Confirm.Revoke") } + /// Sorry, this language doesn't seem to exist. + internal static var localizationPreviewErrorGeneric: String { return L10n.tr("Localizable", "Localization.Preview.Error.Generic") } + /// Accurate to %@ + internal static func locationSendAccurateTo(_ p1: String) -> String { + return L10n.tr("Localizable", "Location.Send.AccurateTo", p1) + } + /// Hide nearby places + internal static var locationSendHideNearby: String { return L10n.tr("Localizable", "Location.Send.HideNearby") } + /// Locating... + internal static var locationSendLocating: String { return L10n.tr("Localizable", "Location.Send.Locating") } + /// Send My Current Location + internal static var locationSendMyLocation: String { return L10n.tr("Localizable", "Location.Send.MyLocation") } + /// Show nearby places + internal static var locationSendShowNearby: String { return L10n.tr("Localizable", "Location.Send.ShowNearby") } + /// Send This Location + internal static var locationSendThisLocation: String { return L10n.tr("Localizable", "Location.Send.ThisLocation") } + /// Location + internal static var locationSendTitle: String { return L10n.tr("Localizable", "Location.Send.Title") } + /// Unknown Location + internal static var locationSendThisLocationUnknown: String { return L10n.tr("Localizable", "Location.Send.ThisLocation.Unknown") } /// code - case loginCodePlaceholder - /// Continue on English - case loginContinueOnLanguage + internal static var loginCodePlaceholder: String { return L10n.tr("Localizable", "Login.codePlaceholder") } + /// Continue in English + internal static var loginContinueOnLanguage: String { return L10n.tr("Localizable", "Login.ContinueOnLanguage") } /// country - case loginCountryLabel + internal static var loginCountryLabel: String { return L10n.tr("Localizable", "Login.countryLabel") } /// Please enter the code you've just received in Telegram on your other device. - case loginEnterCodeFromApp + internal static var loginEnterCodeFromApp: String { return L10n.tr("Localizable", "Login.EnterCodeFromApp") } /// You have enabled Two-Step Verification, your account is now protected with an additional password. - case loginEnterPasswordDescription - /// too many attempts, please try later. - case loginFloodWait + internal static var loginEnterPasswordDescription: String { return L10n.tr("Localizable", "Login.EnterPasswordDescription") } + /// Too many attempts, please try again later. + internal static var loginFloodWait: String { return L10n.tr("Localizable", "Login.FloodWait") } /// Invalid Country Code - case loginInvalidCountryCode + internal static var loginInvalidCountryCode: String { return L10n.tr("Localizable", "Login.InvalidCountryCode") } + /// Invalid first name. Please try again. + internal static var loginInvalidFirstNameError: String { return L10n.tr("Localizable", "Login.InvalidFirstNameError") } + /// Invalid last name. Please try again. + internal static var loginInvalidLastNameError: String { return L10n.tr("Localizable", "Login.InvalidLastNameError") } /// We have sent you a code via SMS. Please enter it above. - case loginJustSentSms + internal static var loginJustSentSms: String { return L10n.tr("Localizable", "Login.JustSentSms") } /// Next - case loginNext + internal static var loginNext: String { return L10n.tr("Localizable", "Login.Next") } + /// Forgot password? + internal static var loginPasswordForgot: String { return L10n.tr("Localizable", "Login.PasswordForgot") } /// password - case loginPasswordPlaceholder + internal static var loginPasswordPlaceholder: String { return L10n.tr("Localizable", "Login.passwordPlaceholder") } /// We’ve just called your number. Please enter the code above. - case loginPhoneCalledCode + internal static var loginPhoneCalledCode: String { return L10n.tr("Localizable", "Login.PhoneCalledCode") } /// Telegram dialed your number - case loginPhoneDialed + internal static var loginPhoneDialed: String { return L10n.tr("Localizable", "Login.PhoneDialed") } /// phone number - case loginPhoneFieldPlaceholder - /// Phone number not registered. If you don't have a Telegram account yet, please sign up with your mobile device. - case loginPhoneNumberNotRegistred - /// Since you haven't provided a recovery e-mail during the setup of your password, your remaining options are either to remember your password or to reset your account. - case loginRecoveryMailFailed - /// RESET MY ACCOUNT - case loginResetAccount - /// All your chats and messages, along with any media and files you shared will be lost if you proceed with resetting your account. - case loginResetAccountDescription + internal static var loginPhoneFieldPlaceholder: String { return L10n.tr("Localizable", "Login.phoneFieldPlaceholder") } + /// This account is already logged in from this app. + internal static var loginPhoneNumberAlreadyAuthorized: String { return L10n.tr("Localizable", "Login.PhoneNumberAlreadyAuthorized") } + /// This phone number isn't registered. If you don't have a Telegram account yet, please sign up with your mobile device. + internal static var loginPhoneNumberNotRegistred: String { return L10n.tr("Localizable", "Login.PhoneNumberNotRegistred") } + /// Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account. + internal static var loginRecoveryMailFailed: String { return L10n.tr("Localizable", "Login.RecoveryMailFailed") } + /// RESET + internal static var loginResetAccount: String { return L10n.tr("Localizable", "Login.ResetAccount") } + /// If you proceed with resetting your account, all of your chats and messages along with any media and files you shared, will be lost. + internal static var loginResetAccountDescription: String { return L10n.tr("Localizable", "Login.ResetAccountDescription") } + /// Reset Account + internal static var loginResetAccountText: String { return L10n.tr("Localizable", "Login.ResetAccountText") } /// Haven't received the code? - case loginSendSmsIfNotReceivedAppCode + internal static var loginSendSmsIfNotReceivedAppCode: String { return L10n.tr("Localizable", "Login.SendSmsIfNotReceivedAppCode") } /// Welcome to the macOS application - case loginWelcomeDescription + internal static var loginWelcomeDescription: String { return L10n.tr("Localizable", "Login.WelcomeDescription") } /// Telegram will call you in %d:%@ - case loginWillCall(Int, String) + internal static func loginWillCall(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "Login.willCall", p1, p2) + } /// Telegram will send you an SMS in %d:%@ - case loginWillSendSms(Int, String) + internal static func loginWillSendSms(_ p1: Int, _ p2: String) -> String { + return L10n.tr("Localizable", "Login.willSendSms", p1, p2) + } /// your code - case loginYourCodeLabel + internal static var loginYourCodeLabel: String { return L10n.tr("Localizable", "Login.YourCodeLabel") } /// your password - case loginYourPasswordLabel + internal static var loginYourPasswordLabel: String { return L10n.tr("Localizable", "Login.YourPasswordLabel") } /// your phone - case loginYourPhoneLabel + internal static var loginYourPhoneLabel: String { return L10n.tr("Localizable", "Login.YourPhoneLabel") } + /// Can't reach server + internal static var loginConnectionErrorHeader: String { return L10n.tr("Localizable", "Login.ConnectionError.Header") } + /// Please check your internet connection and try again. + internal static var loginConnectionErrorInfo: String { return L10n.tr("Localizable", "Login.ConnectionError.Info") } + /// Try Again + internal static var loginConnectionErrorTryAgain: String { return L10n.tr("Localizable", "Login.ConnectionError.TryAgain") } + /// Use Proxy + internal static var loginConnectionErrorUseProxy: String { return L10n.tr("Localizable", "Login.ConnectionError.UseProxy") } /// Enter Code - case loginHeaderCode + internal static var loginHeaderCode: String { return L10n.tr("Localizable", "Login.Header.Code") } /// Enter Password - case loginHeaderPassword + internal static var loginHeaderPassword: String { return L10n.tr("Localizable", "Login.Header.Password") } /// Sign Up - case loginHeaderSignUp + internal static var loginHeaderSignUp: String { return L10n.tr("Localizable", "Login.Header.SignUp") } + /// Switch + internal static var loginPhoneNumberAlreadyAuthorizedSwitch: String { return L10n.tr("Localizable", "Login.PhoneNumberAlreadyAuthorized.Switch") } + /// Log in by phone Number + internal static var loginQRCancel: String { return L10n.tr("Localizable", "Login.QR.Cancel") } + /// Open Telegram on your phone + internal static var loginQRHelp1: String { return L10n.tr("Localizable", "Login.QR.Help1") } + /// Go to **Settings** > **Devices** > **Scan QR** + internal static var loginQRHelp2: String { return L10n.tr("Localizable", "Login.QR.Help2") } + /// Point your phone at this screen to confirm login + internal static var loginQRHelp3: String { return L10n.tr("Localizable", "Login.QR.Help3") } + /// Log in by QR Code + internal static var loginQRLogin: String { return L10n.tr("Localizable", "Login.QR.Login") } + /// Log in to Telegram by QR Code + internal static var loginQRTitle: String { return L10n.tr("Localizable", "Login.QR.Title") } + /// Enter your name and add a profile picture. + internal static var loginRegisterDesc: String { return L10n.tr("Localizable", "Login.Register.Desc") } + /// add\nphoto + internal static var loginRegisterAddPhotoPlaceholder: String { return L10n.tr("Localizable", "Login.Register.AddPhoto.Placeholder") } + /// First Name + internal static var loginRegisterFirstNamePlaceholder: String { return L10n.tr("Localizable", "Login.Register.FirstName.Placeholder") } + /// Last Name + internal static var loginRegisterLastNamePlaceholder: String { return L10n.tr("Localizable", "Login.Register.LastName.Placeholder") } + /// Set up multiple phone numbers and easily switch between them. + internal static var logoutOptionsAddAccountText: String { return L10n.tr("Localizable", "LogoutOptions.AddAccountText") } + /// Add another account + internal static var logoutOptionsAddAccountTitle: String { return L10n.tr("Localizable", "LogoutOptions.AddAccountTitle") } + /// ALTERNATIVE OPTIONS + internal static var logoutOptionsAlternativeOptionsSection: String { return L10n.tr("Localizable", "LogoutOptions.AlternativeOptionsSection") } + /// Move your contacts, groups, messages and media to a new number. + internal static var logoutOptionsChangePhoneNumberText: String { return L10n.tr("Localizable", "LogoutOptions.ChangePhoneNumberText") } + /// Change Phone Number + internal static var logoutOptionsChangePhoneNumberTitle: String { return L10n.tr("Localizable", "LogoutOptions.ChangePhoneNumberTitle") } + /// Free up disk space on your device; your media will stay in the cloud. + internal static var logoutOptionsClearCacheText: String { return L10n.tr("Localizable", "LogoutOptions.ClearCacheText") } + /// Clear Cache + internal static var logoutOptionsClearCacheTitle: String { return L10n.tr("Localizable", "LogoutOptions.ClearCacheTitle") } + /// Tell us about any issues; logging out doesn't usually help. + internal static var logoutOptionsContactSupportText: String { return L10n.tr("Localizable", "LogoutOptions.ContactSupportText") } + /// Contact Support + internal static var logoutOptionsContactSupportTitle: String { return L10n.tr("Localizable", "LogoutOptions.ContactSupportTitle") } + /// Log Out + internal static var logoutOptionsLogOut: String { return L10n.tr("Localizable", "LogoutOptions.LogOut") } + /// Remember, logging out kills all your Secret Chats. + internal static var logoutOptionsLogOutInfo: String { return L10n.tr("Localizable", "LogoutOptions.LogOutInfo") } + /// Lock the app with a passcode so that others can't open it. + internal static var logoutOptionsSetPasscodeText: String { return L10n.tr("Localizable", "LogoutOptions.SetPasscodeText") } + /// Set a Passcode + internal static var logoutOptionsSetPasscodeTitle: String { return L10n.tr("Localizable", "LogoutOptions.SetPasscodeTitle") } + /// Log out + internal static var logoutOptionsTitle: String { return L10n.tr("Localizable", "LogoutOptions.Title") } + /// SCAM + internal static var markScam: String { return L10n.tr("Localizable", "Mark.Scam") } + /// Discard Changes + internal static var mediaSenderDiscardChangesHeader: String { return L10n.tr("Localizable", "MediaSender.DiscardChanges.Header") } + /// Discard + internal static var mediaSenderDiscardChangesOK: String { return L10n.tr("Localizable", "MediaSender.DiscardChanges.OK") } + /// Are you sure you want to discard all changes? + internal static var mediaSenderDiscardChangesText: String { return L10n.tr("Localizable", "MediaSender.DiscardChanges.Text") } /// %d - case messageAccessoryPanelForwardedCountable(Int) + internal static func messageAccessoryPanelForwardedCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_countable", p1) + } /// %d forwarded messages - case messageAccessoryPanelForwardedFew(Int) + internal static func messageAccessoryPanelForwardedFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_few", p1) + } /// %d forwarded messages - case messageAccessoryPanelForwardedMany(Int) + internal static func messageAccessoryPanelForwardedMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_many", p1) + } /// %d forwarded message - case messageAccessoryPanelForwardedOne(Int) + internal static func messageAccessoryPanelForwardedOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_one", p1) + } /// %d forwarded messages - case messageAccessoryPanelForwardedOther(Int) + internal static func messageAccessoryPanelForwardedOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_other", p1) + } /// %d forwarded messages - case messageAccessoryPanelForwardedTwo(Int) + internal static func messageAccessoryPanelForwardedTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_two", p1) + } /// %d forwarded messages - case messageAccessoryPanelForwardedZero(Int) + internal static func messageAccessoryPanelForwardedZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.AccessoryPanel.Forwarded_zero", p1) + } /// Delete - case messageActionsPanelDelete + internal static var messageActionsPanelDelete: String { return L10n.tr("Localizable", "Message.ActionsPanel.Delete") } /// Select messages - case messageActionsPanelEmptySelected + internal static var messageActionsPanelEmptySelected: String { return L10n.tr("Localizable", "Message.ActionsPanel.EmptySelected") } /// Forward - case messageActionsPanelForward + internal static var messageActionsPanelForward: String { return L10n.tr("Localizable", "Message.ActionsPanel.Forward") } /// %d - case messageActionsPanelSelectedCountCountable(Int) + internal static func messageActionsPanelSelectedCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_countable", p1) + } /// %d messages selected - case messageActionsPanelSelectedCountFew(Int) + internal static func messageActionsPanelSelectedCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_few", p1) + } /// %d messages selected - case messageActionsPanelSelectedCountMany(Int) + internal static func messageActionsPanelSelectedCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_many", p1) + } /// %d message selected - case messageActionsPanelSelectedCountOne(Int) + internal static func messageActionsPanelSelectedCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_one", p1) + } /// %d messages selected - case messageActionsPanelSelectedCountOther(Int) + internal static func messageActionsPanelSelectedCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_other", p1) + } /// %d messages selected - case messageActionsPanelSelectedCountTwo(Int) + internal static func messageActionsPanelSelectedCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_two", p1) + } /// %d messages selected - case messageActionsPanelSelectedCountZero(Int) + internal static func messageActionsPanelSelectedCountZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.ActionsPanel.SelectedCount_zero", p1) + } /// Delete - case messageContextDelete + internal static var messageContextDelete: String { return L10n.tr("Localizable", "Message.Context.Delete") } /// Edit - case messageContextEdit + internal static var messageContextEdit: String { return L10n.tr("Localizable", "Message.Context.Edit") } /// Forward - case messageContextForward - /// Save to Cloud Storage - case messageContextForwardToCloud + internal static var messageContextForward: String { return L10n.tr("Localizable", "Message.Context.Forward") } + /// Forward to Saved Messages + internal static var messageContextForwardToCloud: String { return L10n.tr("Localizable", "Message.Context.ForwardToCloud") } /// Show Message - case messageContextGoto + internal static var messageContextGoto: String { return L10n.tr("Localizable", "Message.Context.Goto") } + /// Open With... + internal static var messageContextOpenWith: String { return L10n.tr("Localizable", "Message.Context.OpenWith") } /// Pin - case messageContextPin - /// Reply (double click) - case messageContextReply + internal static var messageContextPin: String { return L10n.tr("Localizable", "Message.Context.Pin") } + /// Remove GIF + internal static var messageContextRemoveGif: String { return L10n.tr("Localizable", "Message.Context.RemoveGif") } + /// Reply + internal static var messageContextReply1: String { return L10n.tr("Localizable", "Message.Context.Reply1") } + /// double click + internal static var messageContextReplyHelp: String { return L10n.tr("Localizable", "Message.Context.ReplyHelp") } + /// Report + internal static var messageContextReport: String { return L10n.tr("Localizable", "Message.Context.Report") } /// Add GIF - case messageContextSaveGif + internal static var messageContextSaveGif: String { return L10n.tr("Localizable", "Message.Context.SaveGif") } /// Select - case messageContextSelect - /// Pin only - case messageContextConfirmOnlyPin - /// Pin this message and notify all members of the group? - case messageContextConfirmPin - /// Copy Link - case messageContextCopyMessageLink + internal static var messageContextSelect: String { return L10n.tr("Localizable", "Message.Context.Select") } + /// Share + internal static var messageContextShare: String { return L10n.tr("Localizable", "Message.Context.Share") } + /// Unpin + internal static var messageContextUnpin: String { return L10n.tr("Localizable", "Message.Context.Unpin") } + /// Notify all members + internal static var messageContextConfirmNotifyPin: String { return L10n.tr("Localizable", "Message.Context.Confirm.NotifyPin") } + /// Would you like to pin this message? + internal static var messageContextConfirmPin1: String { return L10n.tr("Localizable", "Message.Context.Confirm.Pin1") } + /// Thank you! Your report will be reviewed by our team very soon. + internal static var messageContextReportAlertOK: String { return L10n.tr("Localizable", "Message.Context.Report.AlertOK") } + /// archived folder + internal static var messageStatusArchived: String { return L10n.tr("Localizable", "Message.Status.Archived") } + /// preparing archive + internal static var messageStatusArchivePreparing: String { return L10n.tr("Localizable", "Message.Status.ArchivePreparing") } + /// %d%% archiving + internal static func messageStatusArchiving(_ p1: Int) -> String { + return L10n.tr("Localizable", "Message.Status.Archiving", p1) + } + /// archivation failed + internal static var messageStatusArchiveFailed: String { return L10n.tr("Localizable", "Message.Status.Archive.Failed") } + /// file size limit exceeded + internal static var messageStatusArchiveFailedSizeLimit: String { return L10n.tr("Localizable", "Message.Status.Archive.FailedSizeLimit") } + /// Copy Message Link + internal static var messageContextCopyMessageLink1: String { return L10n.tr("Localizable", "MessageContext.CopyMessageLink1") } + /// %@d + internal static func messageTimerShortDays(_ p1: String) -> String { + return L10n.tr("Localizable", "MessageTimer.ShortDays", p1) + } + /// %@h + internal static func messageTimerShortHours(_ p1: String) -> String { + return L10n.tr("Localizable", "MessageTimer.ShortHours", p1) + } + /// %@m + internal static func messageTimerShortMinutes(_ p1: String) -> String { + return L10n.tr("Localizable", "MessageTimer.ShortMinutes", p1) + } + /// %@s + internal static func messageTimerShortSeconds(_ p1: String) -> String { + return L10n.tr("Localizable", "MessageTimer.ShortSeconds", p1) + } + /// %@w + internal static func messageTimerShortWeeks(_ p1: String) -> String { + return L10n.tr("Localizable", "MessageTimer.ShortWeeks", p1) + } /// Deleted message - case messagesDeletedMessage + internal static var messagesDeletedMessage: String { return L10n.tr("Localizable", "Messages.DeletedMessage") } /// Forwarded messages - case messagesForwardHeader + internal static var messagesForwardHeader: String { return L10n.tr("Localizable", "Messages.ForwardHeader") } /// Unread messages - case messagesUnreadMark - /// %d% downloaded - case messagesFileStateFetchingIn1(Int) - /// %d% uploaded - case messagesFileStateFetchingOut1(Int) + internal static var messagesUnreadMark: String { return L10n.tr("Localizable", "Messages.UnreadMark") } + /// %d%% downloaded + internal static func messagesFileStateFetchingIn1(_ p1: Int) -> String { + return L10n.tr("Localizable", "Messages.File.State.FetchingIn_1", p1) + } + /// %d%% uploaded + internal static func messagesFileStateFetchingOut1(_ p1: Int) -> String { + return L10n.tr("Localizable", "Messages.File.State.FetchingOut_1", p1) + } /// Show in Finder - case messagesFileStateLocal + internal static var messagesFileStateLocal: String { return L10n.tr("Localizable", "Messages.File.State.Local") } /// Download - case messagesFileStateRemote + internal static var messagesFileStateRemote: String { return L10n.tr("Localizable", "Messages.File.State.Remote") } /// Broadcast... - case messagesPlaceholderBroadcast + internal static var messagesPlaceholderBroadcast: String { return L10n.tr("Localizable", "Messages.Placeholder.Broadcast") } /// Write a message... - case messagesPlaceholderSentMessage + internal static var messagesPlaceholderSentMessage: String { return L10n.tr("Localizable", "Messages.Placeholder.SentMessage") } + /// Silent Broadcast... + internal static var messagesPlaceholderSilentBroadcast: String { return L10n.tr("Localizable", "Messages.Placeholder.SilentBroadcast") } + /// Broadcast... + internal static var messagesPlaceholderBroadcastSmall: String { return L10n.tr("Localizable", "Messages.Placeholder.Broadcast.Small") } + /// Message... + internal static var messagesPlaceholderSentMessageSmall: String { return L10n.tr("Localizable", "Messages.Placeholder.SentMessage.Small") } /// Reply - case messagesReplyLoadingHeader + internal static var messagesReplyLoadingHeader: String { return L10n.tr("Localizable", "Messages.ReplyLoading.Header") } /// Loading... - case messagesReplyLoadingLoading - /// Check Grammar With Spelling - case mk62p4JGTitle + internal static var messagesReplyLoadingLoading: String { return L10n.tr("Localizable", "Messages.ReplyLoading.Loading") } + /// Apply + internal static var modalApply: String { return L10n.tr("Localizable", "Modal.Apply") } /// Cancel - case modalCancel + internal static var modalCancel: String { return L10n.tr("Localizable", "Modal.Cancel") } /// Copy Link - case modalCopyLink + internal static var modalCopyLink: String { return L10n.tr("Localizable", "Modal.CopyLink") } /// OK - case modalOK + internal static var modalOK: String { return L10n.tr("Localizable", "Modal.OK") } /// Send - case modalSend + internal static var modalSend: String { return L10n.tr("Localizable", "Modal.Send") } + /// Set + internal static var modalSet: String { return L10n.tr("Localizable", "Modal.Set") } /// Share - case modalShare + internal static var modalShare: String { return L10n.tr("Localizable", "Modal.Share") } + /// Add + internal static var navigationAdd: String { return L10n.tr("Localizable", "Navigation.Add") } /// Back - case navigationBack + internal static var navigationBack: String { return L10n.tr("Localizable", "Navigation.back") } /// Cancel - case navigationCancel + internal static var navigationCancel: String { return L10n.tr("Localizable", "Navigation.Cancel") } /// Close - case navigationClose + internal static var navigationClose: String { return L10n.tr("Localizable", "Navigation.Close") } /// Done - case navigationDone + internal static var navigationDone: String { return L10n.tr("Localizable", "Navigation.Done") } /// Edit - case navigationEdit - /// You have new message - case notificationLockedPreview + internal static var navigationEdit: String { return L10n.tr("Localizable", "Navigation.Edit") } + /// Next + internal static var navigationNext: String { return L10n.tr("Localizable", "Navigation.Next") } + /// Bytes Received + internal static var networkUsageBytesReceived: String { return L10n.tr("Localizable", "NetworkUsage.BytesReceived") } + /// Bytes Sent + internal static var networkUsageBytesSent: String { return L10n.tr("Localizable", "NetworkUsage.BytesSent") } + /// Network Usage + internal static var networkUsageNetworkUsage: String { return L10n.tr("Localizable", "NetworkUsage.NetworkUsage") } + /// Network usage since %@ + internal static func networkUsageNetworkUsageSince(_ p1: String) -> String { + return L10n.tr("Localizable", "NetworkUsage.NetworkUsageSince", p1) + } + /// Reset Statistics + internal static var networkUsageReset: String { return L10n.tr("Localizable", "NetworkUsage.Reset") } + /// AUDIO + internal static var networkUsageHeaderAudio: String { return L10n.tr("Localizable", "NetworkUsage.Header.Audio") } + /// FILES + internal static var networkUsageHeaderFiles: String { return L10n.tr("Localizable", "NetworkUsage.Header.Files") } + /// MESSAGES + internal static var networkUsageHeaderGeneric: String { return L10n.tr("Localizable", "NetworkUsage.Header.Generic") } + /// PHOTOS + internal static var networkUsageHeaderImages: String { return L10n.tr("Localizable", "NetworkUsage.Header.Images") } + /// VIDEOS + internal static var networkUsageHeaderVideos: String { return L10n.tr("Localizable", "NetworkUsage.Header.Videos") } + /// phone number + internal static var newContactPhone: String { return L10n.tr("Localizable", "NewContact.Phone") } + /// New Contact + internal static var newContactTitle: String { return L10n.tr("Localizable", "NewContact.Title") } + /// Share My Phone Number + internal static var newContactExceptionShareMyPhoneNumber: String { return L10n.tr("Localizable", "NewContact.Exception.ShareMyPhoneNumber") } + /// You can make your phone visible to %@. + internal static func newContactExceptionShareMyPhoneNumberDesc(_ p1: String) -> String { + return L10n.tr("Localizable", "NewContact.Exception.ShareMyPhoneNumber.Desc", p1) + } + /// Hidden + internal static var newContactPhoneHidden: String { return L10n.tr("Localizable", "NewContact.Phone.Hidden") } + /// Phone number will be **visible** once %@ adds you as a contact. + internal static func newContactPhoneHiddenText(_ p1: String) -> String { + return L10n.tr("Localizable", "NewContact.Phone.Hidden.Text", p1) + } + /// Anonymous Voting + internal static var newPollAnonymous: String { return L10n.tr("Localizable", "NewPoll.Anonymous") } + /// Are you sure you want to discard this poll? + internal static var newPollDisacardConfirm: String { return L10n.tr("Localizable", "NewPoll.DisacardConfirm") } + /// Poll + internal static var newPollDisacardConfirmHeader: String { return L10n.tr("Localizable", "NewPoll.DisacardConfirmHeader") } + /// Multiple Choice + internal static var newPollMultipleChoice: String { return L10n.tr("Localizable", "NewPoll.MultipleChoice") } + /// Add an Option + internal static var newPollOptionsAddOption: String { return L10n.tr("Localizable", "NewPoll.OptionsAddOption") } + /// %d + internal static func newPollOptionsDescriptionCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_countable", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_few", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_many", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_one", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_other", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_two", p1) + } + /// You can add %d more options + internal static func newPollOptionsDescriptionZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescription_zero", p1) + } + /// You have added the maximum number of options. + internal static var newPollOptionsDescriptionLimitReached: String { return L10n.tr("Localizable", "NewPoll.OptionsDescriptionLimitReached") } + /// %d + internal static func newPollOptionsDescriptionMinimumCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_countable", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_few", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_many", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_one", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_other", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_two", p1) + } + /// Minimum %d options + internal static func newPollOptionsDescriptionMinimumZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.OptionsDescriptionMinimum_zero", p1) + } + /// POLL OPTIONS + internal static var newPollOptionsHeader: String { return L10n.tr("Localizable", "NewPoll.OptionsHeader") } + /// Option + internal static var newPollOptionsPlaceholder: String { return L10n.tr("Localizable", "NewPoll.OptionsPlaceholder") } + /// QUESTION + internal static var newPollQuestionHeader: String { return L10n.tr("Localizable", "NewPoll.QuestionHeader") } + /// QUESTION (%d) + internal static func newPollQuestionHeaderLimit(_ p1: Int) -> String { + return L10n.tr("Localizable", "NewPoll.QuestionHeaderLimit", p1) + } + /// Ask a question + internal static var newPollQuestionPlaceholder: String { return L10n.tr("Localizable", "NewPoll.QuestionPlaceholder") } + /// Quiz Mode + internal static var newPollQuiz: String { return L10n.tr("Localizable", "NewPoll.Quiz") } + /// Quiz has only one right answer. You can't revoke their votes. + internal static var newPollQuizDesc: String { return L10n.tr("Localizable", "NewPoll.QuizDesc") } + /// Select the correct option + internal static var newPollQuizTooltip: String { return L10n.tr("Localizable", "NewPoll.QuizTooltip") } + /// New Poll + internal static var newPollTitle: String { return L10n.tr("Localizable", "NewPoll.Title") } + /// No + internal static var newPollDisacardConfirmNo: String { return L10n.tr("Localizable", "NewPoll.DisacardConfirm.No") } + /// Discard + internal static var newPollDisacardConfirmYes: String { return L10n.tr("Localizable", "NewPoll.DisacardConfirm.Yes") } + /// Users will see this comment after choosing a wrong answer, good for educational purposes. + internal static var newPollExplanationDesc: String { return L10n.tr("Localizable", "NewPoll.Explanation.Desc") } + /// EXPLANATION + internal static var newPollExplanationHeader: String { return L10n.tr("Localizable", "NewPoll.Explanation.Header") } + /// Add a Comment (Optional) + internal static var newPollExplanationPlaceholder: String { return L10n.tr("Localizable", "NewPoll.Explanation.Placeholder") } + /// A quiz has one correct answer. + internal static var newPollQuizMultipleError: String { return L10n.tr("Localizable", "NewPoll.QuizMultiple.Error") } + /// New Quiz + internal static var newPollTitleQuiz: String { return L10n.tr("Localizable", "NewPoll.Title.Quiz") } + /// Create + internal static var newThemeCreate: String { return L10n.tr("Localizable", "NewTheme.Create") } + /// This theme will be based on your current theme. + internal static var newThemeDesc: String { return L10n.tr("Localizable", "NewTheme.Desc") } + /// name can't be empty. + internal static var newThemeEmptyTextError: String { return L10n.tr("Localizable", "NewTheme.EmptyTextError") } + /// Theme name + internal static var newThemePlaceholder: String { return L10n.tr("Localizable", "NewTheme.Placeholder") } + /// New Theme + internal static var newThemeTitle: String { return L10n.tr("Localizable", "NewTheme.Title") } + /// You have a new message + internal static var notificationLockedPreview: String { return L10n.tr("Localizable", "Notification.LockedPreview") } + /// Mark as Read + internal static var notificationMarkAsRead: String { return L10n.tr("Localizable", "Notification.MarkAsRead") } + /// 📆 Reminder + internal static var notificationReminder: String { return L10n.tr("Localizable", "Notification.Reminder") } + /// All Accounts + internal static var notificationSettingsAllAccounts: String { return L10n.tr("Localizable", "NotificationSettings.AllAccounts") } + /// Switch off to show the number of unread chats instead of messages. + internal static var notificationSettingsBadgeDesc: String { return L10n.tr("Localizable", "NotificationSettings.BadgeDesc") } + /// Enabled + internal static var notificationSettingsBadgeEnabled: String { return L10n.tr("Localizable", "NotificationSettings.BadgeEnabled") } + /// BADGE COUNTER + internal static var notificationSettingsBadgeHeader: String { return L10n.tr("Localizable", "NotificationSettings.BadgeHeader") } + /// Bounce Dock Icon + internal static var notificationSettingsBounceDockIcon: String { return L10n.tr("Localizable", "NotificationSettings.BounceDockIcon") } + /// New Contacts + internal static var notificationSettingsContactJoined: String { return L10n.tr("Localizable", "NotificationSettings.ContactJoined") } + /// Receive notifications when one of your contacts becomes available on Telegram. + internal static var notificationSettingsContactJoinedInfo: String { return L10n.tr("Localizable", "NotificationSettings.ContactJoinedInfo") } + /// Count Unread Messages + internal static var notificationSettingsCountUnreadMessages: String { return L10n.tr("Localizable", "NotificationSettings.CountUnreadMessages") } + /// Include Channels + internal static var notificationSettingsIncludeChannels: String { return L10n.tr("Localizable", "NotificationSettings.IncludeChannels") } + /// Include Groups + internal static var notificationSettingsIncludeGroups: String { return L10n.tr("Localizable", "NotificationSettings.IncludeGroups") } + /// Include Muted Chats + internal static var notificationSettingsIncludeMutedChats: String { return L10n.tr("Localizable", "NotificationSettings.IncludeMutedChats") } /// Message Preview - case notificationSettingsMessagesPreview + internal static var notificationSettingsMessagesPreview: String { return L10n.tr("Localizable", "NotificationSettings.MessagesPreview") } /// Notification Tone - case notificationSettingsNotificationTone + internal static var notificationSettingsNotificationTone: String { return L10n.tr("Localizable", "NotificationSettings.NotificationTone") } /// Reset Notifications - case notificationSettingsResetNotifications + internal static var notificationSettingsResetNotifications: String { return L10n.tr("Localizable", "NotificationSettings.ResetNotifications") } /// You can set custom notifications for specific chats below. - case notificationSettingsResetNotificationsText + internal static var notificationSettingsResetNotificationsText: String { return L10n.tr("Localizable", "NotificationSettings.ResetNotificationsText") } + /// Sent Message + internal static var notificationSettingsSendMessageEffect: String { return L10n.tr("Localizable", "NotificationSettings.SendMessageEffect") } + /// SHOW NOTIFICATIONS FROM + internal static var notificationSettingsShowNotificationsFrom: String { return L10n.tr("Localizable", "NotificationSettings.ShowNotificationsFrom") } + /// App is in Focus + internal static var notificationSettingsSnoof: String { return L10n.tr("Localizable", "NotificationSettings.Snoof") } + /// SHOW NOTIFICATIONS WHEN + internal static var notificationSettingsSnoofHeader: String { return L10n.tr("Localizable", "NotificationSettings.SnoofHeader") } + /// SOUND EFFECTS + internal static var notificationSettingsSoundEffects: String { return L10n.tr("Localizable", "NotificationSettings.SoundEffects") } /// Notifications - case notificationSettingsToggleNotifications + internal static var notificationSettingsToggleNotifications: String { return L10n.tr("Localizable", "NotificationSettings.ToggleNotifications") } /// Reset notifications - case notificationSettingsConfirmReset + internal static var notificationSettingsConfirmReset: String { return L10n.tr("Localizable", "NotificationSettings.Confirm.Reset") } + /// Turn this on if you want to receive notifications from all your accounts. + internal static var notificationSettingsShowNotificationsFromOff: String { return L10n.tr("Localizable", "NotificationSettings.ShowNotificationsFrom.Off") } + /// Turn this off if you want to receive notifications only from your active account. + internal static var notificationSettingsShowNotificationsFromOn: String { return L10n.tr("Localizable", "NotificationSettings.ShowNotificationsFrom.On") } + /// Turn this on if you want to always receive notifications. + internal static var notificationSettingsSnoofOff: String { return L10n.tr("Localizable", "NotificationSettings.Snoof.Off") } + /// Turn this off if you want to receive notifications only when application is not in focus. + internal static var notificationSettingsSnoofOn: String { return L10n.tr("Localizable", "NotificationSettings.Snoof.On") } + /// NOTIFICATIONS + internal static var notificationSettingsToggleNotificationsHeader: String { return L10n.tr("Localizable", "NotificationSettings.ToggleNotifications.Header") } /// Default - case notificationSettingsToneDefault - /// Hide - case olwNPBQNTitle + internal static var notificationSettingsToneDefault: String { return L10n.tr("Localizable", "NotificationSettings.Tone.Default") } + /// Mute + internal static var notificationsSnooze: String { return L10n.tr("Localizable", "Notifications.Snooze") } /// Minimize - case oy7WFPoVTitle - /// Delete - case pa3QIU2kTitle + internal static var oy7WFPoVTitle: String { return L10n.tr("Localizable", "OY7-WF-poV.title") } + /// Hide + internal static var olwNPBQNTitle: String { return L10n.tr("Localizable", "Olw-nP-bQN.title") } /// Auto-Lock - case passcodeAutolock + internal static var passcodeAutolock: String { return L10n.tr("Localizable", "Passcode.Autolock") } /// Change passcode - case passcodeChange - /// Enter Current Passcode - case passcodeEnterCurrentPlaceholder - /// Enter New Passcode - case passcodeEnterNewPlaceholder - /// Enter a passcode - case passcodeEnterPasscodePlaceholder - /// If you don't remember your passcode, you can - case passcodeLogoutDescription - /// logout - case passcodeLogoutLinkText + internal static var passcodeChange: String { return L10n.tr("Localizable", "Passcode.Change") } + /// Enter your current passcode + internal static var passcodeEnterCurrentPlaceholder: String { return L10n.tr("Localizable", "Passcode.EnterCurrentPlaceholder") } + /// Enter the new passcode + internal static var passcodeEnterNewPlaceholder: String { return L10n.tr("Localizable", "Passcode.EnterNewPlaceholder") } + /// Enter your passcode + internal static var passcodeEnterPasscodePlaceholder: String { return L10n.tr("Localizable", "Passcode.EnterPasscodePlaceholder") } /// Next - case passcodeNext - /// Re-enter a passcode - case passcodeReEnterPlaceholder + internal static var passcodeNext: String { return L10n.tr("Localizable", "Passcode.Next") } + /// or + internal static var passcodeOr: String { return L10n.tr("Localizable", "Passcode.Or") } + /// Re-enter the passcode + internal static var passcodeReEnterPlaceholder: String { return L10n.tr("Localizable", "Passcode.ReEnterPlaceholder") } /// Turn Passcode Off - case passcodeTurnOff + internal static var passcodeTurnOff: String { return L10n.tr("Localizable", "Passcode.TurnOff") } /// Turn Passcode On - case passcodeTurnOn + internal static var passcodeTurnOn: String { return L10n.tr("Localizable", "Passcode.TurnOn") } /// When you set up an additional passcode, you can use ⌘ + L for lock.\n\nNote: if you forget the passcode, you'll need to delete and reinstall the app. All secret chats will be lost. - case passcodeTurnOnDescription + internal static var passcodeTurnOnDescription: String { return L10n.tr("Localizable", "Passcode.TurnOnDescription") } + /// unlock itself + internal static var passcodeUnlockTouchIdReason: String { return L10n.tr("Localizable", "Passcode.UnlockTouchIdReason") } + /// Unlock with Touch ID + internal static var passcodeUseTouchId: String { return L10n.tr("Localizable", "Passcode.UseTouchId") } /// Disabled - case passcodeAutoLockDisabled + internal static var passcodeAutoLockDisabled: String { return L10n.tr("Localizable", "Passcode.AutoLock.Disabled") } /// If away for %@ - case passcodeAutoLockIfAway(String) - /// Sorry, Telegram Mac doesn't support payments yet. Please use one of our mobile apps to do this. - case paymentsUnsupported - /// Deleted User - case peerDeletedUser + internal static func passcodeAutoLockIfAway(_ p1: String) -> String { + return L10n.tr("Localizable", "Passcode.AutoLock.IfAway", p1) + } + /// If you don't remember your passcode, you can [log out]() + internal static var passcodeLostDescription: String { return L10n.tr("Localizable", "Passcode.Lost.Description") } + /// When a local passcode is set, a lock button is appears in quick settings menu. Just hover settings icon in tab bar or use ⌘ + L.\n\nNote: if you forget your local passcode you'll need to log out of Telegram Macos and log in again. + internal static var passcodeControllerText: String { return L10n.tr("Localizable", "PasscodeController.Text") } + /// Change Passcode + internal static var passcodeControllerChangeTitle: String { return L10n.tr("Localizable", "PasscodeController.Change.Title") } + /// Enter current passcode + internal static var passcodeControllerCurrentPlaceholder: String { return L10n.tr("Localizable", "PasscodeController.Current.Placeholder") } + /// Disable Passcode + internal static var passcodeControllerDisableTitle: String { return L10n.tr("Localizable", "PasscodeController.Disable.Title") } + /// Enter a passcode + internal static var passcodeControllerEnterPasscodePlaceholder: String { return L10n.tr("Localizable", "PasscodeController.EnterPasscode.Placeholder") } + /// invalid passcode + internal static var passcodeControllerErrorCurrent: String { return L10n.tr("Localizable", "PasscodeController.Error.Current") } + /// passcodes are different + internal static var passcodeControllerErrorDifferent: String { return L10n.tr("Localizable", "PasscodeController.Error.Different") } + /// CURRENT PASSCODE + internal static var passcodeControllerHeaderCurrent: String { return L10n.tr("Localizable", "PasscodeController.Header.Current") } + /// NEW PASSCODE + internal static var passcodeControllerHeaderNew: String { return L10n.tr("Localizable", "PasscodeController.Header.New") } + /// Passcode + internal static var passcodeControllerInstallTitle: String { return L10n.tr("Localizable", "PasscodeController.Install.Title") } + /// Re-enter new passcode + internal static var passcodeControllerReEnterPasscodePlaceholder: String { return L10n.tr("Localizable", "PasscodeController.ReEnterPasscode.Placeholder") } + /// Enter Your Passcode + internal static var passlockEnterYourPasscode: String { return L10n.tr("Localizable", "Passlock.EnterYourPasscode") } + /// Arabic + internal static var passportLanguageAr: String { return L10n.tr("Localizable", "Passport.Language.ar") } + /// Azerbaijani + internal static var passportLanguageAz: String { return L10n.tr("Localizable", "Passport.Language.az") } + /// Bulgarian + internal static var passportLanguageBg: String { return L10n.tr("Localizable", "Passport.Language.bg") } + /// Bangla + internal static var passportLanguageBn: String { return L10n.tr("Localizable", "Passport.Language.bn") } + /// Czech + internal static var passportLanguageCs: String { return L10n.tr("Localizable", "Passport.Language.cs") } + /// Danish + internal static var passportLanguageDa: String { return L10n.tr("Localizable", "Passport.Language.da") } + /// German + internal static var passportLanguageDe: String { return L10n.tr("Localizable", "Passport.Language.de") } + /// Divehi + internal static var passportLanguageDv: String { return L10n.tr("Localizable", "Passport.Language.dv") } + /// Dzongkha + internal static var passportLanguageDz: String { return L10n.tr("Localizable", "Passport.Language.dz") } + /// Greek + internal static var passportLanguageEl: String { return L10n.tr("Localizable", "Passport.Language.el") } + /// English + internal static var passportLanguageEn: String { return L10n.tr("Localizable", "Passport.Language.en") } + /// Spanish + internal static var passportLanguageEs: String { return L10n.tr("Localizable", "Passport.Language.es") } + /// Estonian + internal static var passportLanguageEt: String { return L10n.tr("Localizable", "Passport.Language.et") } + /// Persian + internal static var passportLanguageFa: String { return L10n.tr("Localizable", "Passport.Language.fa") } + /// French + internal static var passportLanguageFr: String { return L10n.tr("Localizable", "Passport.Language.fr") } + /// Hebrew + internal static var passportLanguageHe: String { return L10n.tr("Localizable", "Passport.Language.he") } + /// Croatian + internal static var passportLanguageHr: String { return L10n.tr("Localizable", "Passport.Language.hr") } + /// Hungarian + internal static var passportLanguageHu: String { return L10n.tr("Localizable", "Passport.Language.hu") } + /// Armenian + internal static var passportLanguageHy: String { return L10n.tr("Localizable", "Passport.Language.hy") } + /// Indonesian + internal static var passportLanguageId: String { return L10n.tr("Localizable", "Passport.Language.id") } + /// Icelandic + internal static var passportLanguageIs: String { return L10n.tr("Localizable", "Passport.Language.is") } + /// Italian + internal static var passportLanguageIt: String { return L10n.tr("Localizable", "Passport.Language.it") } + /// Japanese + internal static var passportLanguageJa: String { return L10n.tr("Localizable", "Passport.Language.ja") } + /// Georgian + internal static var passportLanguageKa: String { return L10n.tr("Localizable", "Passport.Language.ka") } + /// Khmer + internal static var passportLanguageKm: String { return L10n.tr("Localizable", "Passport.Language.km") } + /// Korean + internal static var passportLanguageKo: String { return L10n.tr("Localizable", "Passport.Language.ko") } + /// Lao + internal static var passportLanguageLo: String { return L10n.tr("Localizable", "Passport.Language.lo") } + /// Lithuanian + internal static var passportLanguageLt: String { return L10n.tr("Localizable", "Passport.Language.lt") } + /// Latvian + internal static var passportLanguageLv: String { return L10n.tr("Localizable", "Passport.Language.lv") } + /// Macedonian + internal static var passportLanguageMk: String { return L10n.tr("Localizable", "Passport.Language.mk") } + /// Mongolian + internal static var passportLanguageMn: String { return L10n.tr("Localizable", "Passport.Language.mn") } + /// Malay + internal static var passportLanguageMs: String { return L10n.tr("Localizable", "Passport.Language.ms") } + /// Burmese + internal static var passportLanguageMy: String { return L10n.tr("Localizable", "Passport.Language.my") } + /// Nepali + internal static var passportLanguageNe: String { return L10n.tr("Localizable", "Passport.Language.ne") } + /// Dutch + internal static var passportLanguageNl: String { return L10n.tr("Localizable", "Passport.Language.nl") } + /// Polish + internal static var passportLanguagePl: String { return L10n.tr("Localizable", "Passport.Language.pl") } + /// Portuguese + internal static var passportLanguagePt: String { return L10n.tr("Localizable", "Passport.Language.pt") } + /// Romanian + internal static var passportLanguageRo: String { return L10n.tr("Localizable", "Passport.Language.ro") } + /// Russian + internal static var passportLanguageRu: String { return L10n.tr("Localizable", "Passport.Language.ru") } + /// Slovak + internal static var passportLanguageSk: String { return L10n.tr("Localizable", "Passport.Language.sk") } + /// Slovenian + internal static var passportLanguageSl: String { return L10n.tr("Localizable", "Passport.Language.sl") } + /// Thai + internal static var passportLanguageTh: String { return L10n.tr("Localizable", "Passport.Language.th") } + /// Turkmen + internal static var passportLanguageTk: String { return L10n.tr("Localizable", "Passport.Language.tk") } + /// Turkish + internal static var passportLanguageTr: String { return L10n.tr("Localizable", "Passport.Language.tr") } + /// Ukrainian + internal static var passportLanguageUk: String { return L10n.tr("Localizable", "Passport.Language.uk") } + /// Uzbek + internal static var passportLanguageUz: String { return L10n.tr("Localizable", "Passport.Language.uz") } + /// Vietnamese + internal static var passportLanguageVi: String { return L10n.tr("Localizable", "Passport.Language.vi") } + /// Forgotten Password + internal static var passportResetPasswordConfirmHeader: String { return L10n.tr("Localizable", "Passport.ResetPassword.Confirm.Header") } + /// Reset + internal static var passportResetPasswordConfirmOK: String { return L10n.tr("Localizable", "Passport.ResetPassword.Confirm.OK") } + /// All documents uploaded to your Telegram Passport will be lost. You will be able to upload new documents. + internal static var passportResetPasswordConfirmText: String { return L10n.tr("Localizable", "Passport.ResetPassword.Confirm.Text") } + /// Sorry, Telegram for macOS doesn't support payments yet. Please use one of our mobile apps to do this. + internal static var paymentsUnsupported: String { return L10n.tr("Localizable", "Payments.Unsupported") } + /// Deleted Account + internal static var peerDeletedUser: String { return L10n.tr("Localizable", "Peer.DeletedUser") } + /// Saved Messages + internal static var peerSavedMessages: String { return L10n.tr("Localizable", "Peer.SavedMessages") } /// Service Notifications - case peerServiceNotifications - /// %d are recording voice - case peerActivityChatMultiRecordingAudio(Int) - /// %d are recording video - case peerActivityChatMultiRecordingVideo(Int) - /// %d are sending audio - case peerActivityChatMultiSendingAudio(Int) - /// %d are sending file - case peerActivityChatMultiSendingFile(Int) - /// %d are sending photo - case peerActivityChatMultiSendingPhoto(Int) - /// %d are sending video - case peerActivityChatMultiSendingVideo(Int) - /// %d are typing - case peerActivityChatMultiTypingText(Int) + internal static var peerServiceNotifications: String { return L10n.tr("Localizable", "Peer.ServiceNotifications") } + /// %@ is playing a game + internal static func peerActivityChatPlayingGame(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.PlayingGame", p1) + } + /// %@ is recording voice + internal static func peerActivityChatRecordingAudio(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.RecordingAudio", p1) + } + /// %@ is recording video + internal static func peerActivityChatRecordingVideo(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.RecordingVideo", p1) + } + /// %@ is sending file + internal static func peerActivityChatSendingFile(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.SendingFile", p1) + } + /// %@ is sending photo + internal static func peerActivityChatSendingPhoto(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.SendingPhoto", p1) + } + /// %@ is sending video + internal static func peerActivityChatSendingVideo(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.SendingVideo", p1) + } + /// %@ is typing + internal static func peerActivityChatTypingText(_ p1: String) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.TypingText", p1) + } + /// %@ and %d others are playing a games + internal static func peerActivityChatMultiPlayingGame1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.PlayingGame1", p1, p2) + } + /// %@ and %d others are recording voice + internal static func peerActivityChatMultiRecordingAudio1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.RecordingAudio1", p1, p2) + } + /// %@ and %d others are recording video + internal static func peerActivityChatMultiRecordingVideo1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.RecordingVideo1", p1, p2) + } + /// %@ and %d others are sending audio + internal static func peerActivityChatMultiSendingAudio1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.SendingAudio1", p1, p2) + } + /// %@ and %d others are sending files + internal static func peerActivityChatMultiSendingFile1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.SendingFile1", p1, p2) + } + /// %@ and %d others are sending photos + internal static func peerActivityChatMultiSendingPhoto1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.SendingPhoto1", p1, p2) + } + /// %@ and %d others are sending videos + internal static func peerActivityChatMultiSendingVideo1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.SendingVideo1", p1, p2) + } + /// %@ and %d others are typing + internal static func peerActivityChatMultiTypingText1(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Peer.Activity.Chat.Multi.TypingText1", p1, p2) + } + /// playing a game + internal static var peerActivityUserPlayingGame: String { return L10n.tr("Localizable", "Peer.Activity.User.PlayingGame") } /// recording voice - case peerActivityUserRecordingAudio + internal static var peerActivityUserRecordingAudio: String { return L10n.tr("Localizable", "Peer.Activity.User.RecordingAudio") } /// recording video - case peerActivityUserRecordingVideo + internal static var peerActivityUserRecordingVideo: String { return L10n.tr("Localizable", "Peer.Activity.User.RecordingVideo") } /// sending file - case peerActivityUserSendingFile - /// sending photo - case peerActivityUserSendingPhoto - /// sending video - case peerActivityUserSendingVideo + internal static var peerActivityUserSendingFile: String { return L10n.tr("Localizable", "Peer.Activity.User.SendingFile") } + /// sending a photo + internal static var peerActivityUserSendingPhoto: String { return L10n.tr("Localizable", "Peer.Activity.User.SendingPhoto") } + /// sending a video + internal static var peerActivityUserSendingVideo: String { return L10n.tr("Localizable", "Peer.Activity.User.SendingVideo") } /// typing - case peerActivityUserTypingText - /// You can send and receive files of any type up to 1.5 GB each and access them anywhere. - case peerMediaSharedFilesEmptyList + internal static var peerActivityUserTypingText: String { return L10n.tr("Localizable", "Peer.Activity.User.TypingText") } + /// Remove photo + internal static var peerCreatePeerContextRemovePhoto: String { return L10n.tr("Localizable", "Peer.CreatePeer.Context.RemovePhoto") } + /// Update photo + internal static var peerCreatePeerContextUpdatePhoto: String { return L10n.tr("Localizable", "Peer.CreatePeer.Context.UpdatePhoto") } + /// You can send and receive files of any type up to 2.0 GB each and access them anywhere. + internal static var peerMediaSharedFilesEmptyList1: String { return L10n.tr("Localizable", "Peer.Media.SharedFilesEmptyList1") } /// All links shared in this chat will appear here. - case peerMediaSharedLinksEmptyList + internal static var peerMediaSharedLinksEmptyList: String { return L10n.tr("Localizable", "Peer.Media.SharedLinksEmptyList") } /// Share photos and videos in this chat - or this paperclip stays unhappy. - case peerMediaSharedMediaEmptyList + internal static var peerMediaSharedMediaEmptyList: String { return L10n.tr("Localizable", "Peer.Media.SharedMediaEmptyList") } /// All music shared in this chat will appear here. - case peerMediaSharedMusicEmptyList + internal static var peerMediaSharedMusicEmptyList: String { return L10n.tr("Localizable", "Peer.Media.SharedMusicEmptyList") } + /// All voice and video messages shared in this chat will appear here. + internal static var peerMediaSharedVoiceEmptyList: String { return L10n.tr("Localizable", "Peer.Media.SharedVoiceEmptyList") } /// channel - case peerStatusChannel + internal static var peerStatusChannel: String { return L10n.tr("Localizable", "Peer.Status.channel") } /// group - case peerStatusGroup + internal static var peerStatusGroup: String { return L10n.tr("Localizable", "Peer.Status.group") } /// last seen just now - case peerStatusJustNow + internal static var peerStatusJustNow: String { return L10n.tr("Localizable", "Peer.Status.justNow") } /// last seen within a month - case peerStatusLastMonth + internal static var peerStatusLastMonth: String { return L10n.tr("Localizable", "Peer.Status.lastMonth") } /// last seen %@ at %@ - case peerStatusLastSeenAt(String, String) + internal static func peerStatusLastSeenAt(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Peer.Status.LastSeenAt", p1, p2) + } /// last seen within a week - case peerStatusLastWeek + internal static var peerStatusLastWeek: String { return L10n.tr("Localizable", "Peer.Status.lastWeek") } + /// last seen a long time ago + internal static var peerStatusLongTimeAgo: String { return L10n.tr("Localizable", "Peer.Status.longTimeAgo") } /// %d - case peerStatusMemberCountable(Int) + internal static func peerStatusMemberCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_countable", p1) + } /// %d members - case peerStatusMemberFew(Int) + internal static func peerStatusMemberFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_few", p1) + } /// %d members - case peerStatusMemberMany(Int) + internal static func peerStatusMemberMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_many", p1) + } /// %d member - case peerStatusMemberOne(Int) + internal static func peerStatusMemberOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_one", p1) + } /// %d members - case peerStatusMemberOther(Int) + internal static func peerStatusMemberOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_other", p1) + } /// %d members - case peerStatusMemberTwo(Int) + internal static func peerStatusMemberTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_two", p1) + } /// %d members - case peerStatusMemberZero(Int) + internal static func peerStatusMemberZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member_zero", p1) + } /// %d - case peerStatusMinAgoCountable(Int) + internal static func peerStatusMinAgoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_countable", p1) + } /// last seen %d minutes ago - case peerStatusMinAgoFew(Int) + internal static func peerStatusMinAgoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_few", p1) + } /// last seen %d minutes ago - case peerStatusMinAgoMany(Int) + internal static func peerStatusMinAgoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_many", p1) + } /// last seen %d minute ago - case peerStatusMinAgoOne(Int) + internal static func peerStatusMinAgoOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_one", p1) + } /// last seen %d minutes ago - case peerStatusMinAgoOther(Int) + internal static func peerStatusMinAgoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_other", p1) + } /// last seen %d minutes ago - case peerStatusMinAgoTwo(Int) + internal static func peerStatusMinAgoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_two", p1) + } /// last seen %d minutes ago - case peerStatusMinAgoZero(Int) + internal static func peerStatusMinAgoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.minAgo_zero", p1) + } /// online - case peerStatusOnline + internal static var peerStatusOnline: String { return L10n.tr("Localizable", "Peer.Status.online") } /// last seen recently - case peerStatusRecently + internal static var peerStatusRecently: String { return L10n.tr("Localizable", "Peer.Status.recently") } + /// %d + internal static func peerStatusSubscribersCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_countable", p1) + } + /// %d subscribers + internal static func peerStatusSubscribersFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_few", p1) + } + /// %d subscribers + internal static func peerStatusSubscribersMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_many", p1) + } + /// %d subscriber + internal static func peerStatusSubscribersOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_one", p1) + } + /// %d subscribers + internal static func peerStatusSubscribersOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_other", p1) + } + /// %d subscribers + internal static func peerStatusSubscribersTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_two", p1) + } + /// %d subscribers + internal static func peerStatusSubscribersZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Subscribers_zero", p1) + } /// today - case peerStatusToday + internal static var peerStatusToday: String { return L10n.tr("Localizable", "Peer.Status.Today") } /// yesterday - case peerStatusYesterday + internal static var peerStatusYesterday: String { return L10n.tr("Localizable", "Peer.Status.Yesterday") } /// %d - case peerStatusMemberOnlineCountable(Int) + internal static func peerStatusMemberOnlineCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_countable", p1) + } /// %d online - case peerStatusMemberOnlineFew(Int) + internal static func peerStatusMemberOnlineFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_few", p1) + } /// %d online - case peerStatusMemberOnlineMany(Int) + internal static func peerStatusMemberOnlineMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_many", p1) + } /// %d online - case peerStatusMemberOnlineOne(Int) + internal static func peerStatusMemberOnlineOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_one", p1) + } /// %d online - case peerStatusMemberOnlineOther(Int) + internal static func peerStatusMemberOnlineOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_other", p1) + } /// %d online - case peerStatusMemberOnlineTwo(Int) + internal static func peerStatusMemberOnlineTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_two", p1) + } /// %d online - case peerStatusMemberOnlineZero(Int) + internal static func peerStatusMemberOnlineZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Peer.Status.Member.Online_zero", p1) + } /// about - case peerInfoAbout + internal static var peerInfoAbout: String { return L10n.tr("Localizable", "PeerInfo.about") } /// Add Contact - case peerInfoAddContact - /// Add member - case peerInfoAddMember + internal static var peerInfoAddContact: String { return L10n.tr("Localizable", "PeerInfo.AddContact") } + /// Add Members + internal static var peerInfoAddMember: String { return L10n.tr("Localizable", "PeerInfo.AddMember") } + /// Add %@ to Contacts + internal static func peerInfoAddUserToContact(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.AddUserToContact", p1) + } + /// Administrators + internal static var peerInfoAdministrators: String { return L10n.tr("Localizable", "PeerInfo.Administrators") } /// admin - case peerInfoAdminLabel - /// Admins - case peerInfoAdmins + internal static var peerInfoAdminLabel: String { return L10n.tr("Localizable", "PeerInfo.AdminLabel") } /// bio - case peerInfoBio - /// Blacklist - case peerInfoBlackList + internal static var peerInfoBio: String { return L10n.tr("Localizable", "PeerInfo.bio") } + /// Removed Users + internal static var peerInfoBlackList: String { return L10n.tr("Localizable", "PeerInfo.BlackList") } /// Block User - case peerInfoBlockUser - /// Thank You! Your report will be reviewed by our team very soon. - case peerInfoChannelReported + internal static var peerInfoBlockUser: String { return L10n.tr("Localizable", "PeerInfo.BlockUser") } + /// Thank you! Your report will be reviewed by our team soon. + internal static var peerInfoChannelReported: String { return L10n.tr("Localizable", "PeerInfo.ChannelReported") } /// Channel Type - case peerInfoChannelType + internal static var peerInfoChannelType: String { return L10n.tr("Localizable", "PeerInfo.ChannelType") } /// Convert To Supergroup - case peerInfoConvertToSupergroup + internal static var peerInfoConvertToSupergroup: String { return L10n.tr("Localizable", "PeerInfo.ConvertToSupergroup") } /// Delete and Exit - case peerInfoDeleteAndExit + internal static var peerInfoDeleteAndExit: String { return L10n.tr("Localizable", "PeerInfo.DeleteAndExit") } /// Delete Channel - case peerInfoDeleteChannel + internal static var peerInfoDeleteChannel: String { return L10n.tr("Localizable", "PeerInfo.DeleteChannel") } /// Delete Contact - case peerInfoDeleteContact + internal static var peerInfoDeleteContact: String { return L10n.tr("Localizable", "PeerInfo.DeleteContact") } + /// Delete Group + internal static var peerInfoDeleteGroup: String { return L10n.tr("Localizable", "PeerInfo.DeleteGroup") } /// Delete Secret Chat - case peerInfoDeleteSecretChat + internal static var peerInfoDeleteSecretChat: String { return L10n.tr("Localizable", "PeerInfo.DeleteSecretChat") } + /// Discussion + internal static var peerInfoDiscussion: String { return L10n.tr("Localizable", "PeerInfo.Discussion") } /// Encryption Key - case peerInfoEncryptionKey + internal static var peerInfoEncryptionKey: String { return L10n.tr("Localizable", "PeerInfo.EncryptionKey") } /// Groups In Common - case peerInfoGroupsInCommon + internal static var peerInfoGroupsInCommon: String { return L10n.tr("Localizable", "PeerInfo.GroupsInCommon") } /// Group Type - case peerInfoGroupType + internal static var peerInfoGroupType: String { return L10n.tr("Localizable", "PeerInfo.GroupType") } /// info - case peerInfoInfo + internal static var peerInfoInfo: String { return L10n.tr("Localizable", "PeerInfo.info") } /// Invite Link - case peerInfoInviteLink + internal static var peerInfoInviteLink: String { return L10n.tr("Localizable", "PeerInfo.InviteLink") } /// Leave Channel - case peerInfoLeaveChannel + internal static var peerInfoLeaveChannel: String { return L10n.tr("Localizable", "PeerInfo.LeaveChannel") } + /// Leave Group + internal static var peerInfoLeaveGroup: String { return L10n.tr("Localizable", "PeerInfo.LeaveGroup") } + /// Linked Channel + internal static var peerInfoLinkedChannel: String { return L10n.tr("Localizable", "PeerInfo.LinkedChannel") } /// Members - case peerInfoMembers + internal static var peerInfoMembers: String { return L10n.tr("Localizable", "PeerInfo.Members") } /// %d - case peerInfoMembersHeaderCountable(Int) + internal static func peerInfoMembersHeaderCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_countable", p1) + } /// %d MEMBERS - case peerInfoMembersHeaderFew(Int) + internal static func peerInfoMembersHeaderFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_few", p1) + } /// %d MEMBERS - case peerInfoMembersHeaderMany(Int) + internal static func peerInfoMembersHeaderMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_many", p1) + } /// %d MEMBER - case peerInfoMembersHeaderOne(Int) + internal static func peerInfoMembersHeaderOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_one", p1) + } /// %d MEMBERS - case peerInfoMembersHeaderOther(Int) + internal static func peerInfoMembersHeaderOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_other", p1) + } /// %d MEMBERS - case peerInfoMembersHeaderTwo(Int) + internal static func peerInfoMembersHeaderTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_two", p1) + } /// %d MEMBERS - case peerInfoMembersHeaderZero(Int) + internal static func peerInfoMembersHeaderZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.MembersHeader_zero", p1) + } /// Notifications - case peerInfoNotifications + internal static var peerInfoNotifications: String { return L10n.tr("Localizable", "PeerInfo.Notifications") } + /// Permissions + internal static var peerInfoPermissions: String { return L10n.tr("Localizable", "PeerInfo.Permissions") } /// phone - case peerInfoPhone + internal static var peerInfoPhone: String { return L10n.tr("Localizable", "PeerInfo.Phone") } /// Chat History For New Members - case peerInfoPreHistory + internal static var peerInfoPreHistory: String { return L10n.tr("Localizable", "PeerInfo.PreHistory") } + /// Removed Users + internal static var peerInfoRemovedUsers: String { return L10n.tr("Localizable", "PeerInfo.RemovedUsers") } /// Report - case peerInfoReport + internal static var peerInfoReport: String { return L10n.tr("Localizable", "PeerInfo.Report") } + /// Restart Bot + internal static var peerInfoRestartBot: String { return L10n.tr("Localizable", "PeerInfo.RestartBot") } + /// scam + internal static var peerInfoScam: String { return L10n.tr("Localizable", "PeerInfo.scam") } + /// ⚠️ Warning: Many users reported this account as a scam. Please be careful, especially if it asks you for money. + internal static var peerInfoScamWarning: String { return L10n.tr("Localizable", "PeerInfo.ScamWarning") } /// Send Message - case peerInfoSendMessage + internal static var peerInfoSendMessage: String { return L10n.tr("Localizable", "PeerInfo.SendMessage") } /// You can provide an optional description for your group. - case peerInfoSetAboutDescription - /// Set Admins - case peerInfoSetAdmins + internal static var peerInfoSetAboutDescription: String { return L10n.tr("Localizable", "PeerInfo.SetAboutDescription") } /// Set Channel Photo - case peerInfoSetChannelPhoto + internal static var peerInfoSetChannelPhoto: String { return L10n.tr("Localizable", "PeerInfo.SetChannelPhoto") } /// Set Group Photo - case peerInfoSetGroupPhoto + internal static var peerInfoSetGroupPhoto: String { return L10n.tr("Localizable", "PeerInfo.SetGroupPhoto") } /// Group Sticker Set - case peerInfoSetGroupStickersSet + internal static var peerInfoSetGroupStickersSet: String { return L10n.tr("Localizable", "PeerInfo.SetGroupStickersSet") } /// Share Contact - case peerInfoShareContact + internal static var peerInfoShareContact: String { return L10n.tr("Localizable", "PeerInfo.ShareContact") } /// Shared Media - case peerInfoSharedMedia + internal static var peerInfoSharedMedia: String { return L10n.tr("Localizable", "PeerInfo.SharedMedia") } /// share link - case peerInfoSharelink + internal static var peerInfoSharelink: String { return L10n.tr("Localizable", "PeerInfo.sharelink") } + /// Share My Contact Info + internal static var peerInfoShareMyInfo: String { return L10n.tr("Localizable", "PeerInfo.ShareMyInfo") } + /// Show More + internal static var peerInfoShowMore: String { return L10n.tr("Localizable", "PeerInfo.ShowMore") } + /// [more]() + internal static var peerInfoShowMoreText: String { return L10n.tr("Localizable", "PeerInfo.ShowMoreText") } /// Sign Messages - case peerInfoSignMessages + internal static var peerInfoSignMessages: String { return L10n.tr("Localizable", "PeerInfo.SignMessages") } /// Start Secret Chat - case peerInfoStartSecretChat + internal static var peerInfoStartSecretChat: String { return L10n.tr("Localizable", "PeerInfo.StartSecretChat") } + /// Stop Bot + internal static var peerInfoStopBot: String { return L10n.tr("Localizable", "PeerInfo.StopBot") } + /// Subscribers + internal static var peerInfoSubscribers: String { return L10n.tr("Localizable", "PeerInfo.Subscribers") } + /// Unarchive + internal static var peerInfoUnarchive: String { return L10n.tr("Localizable", "PeerInfo.Unarchive") } /// Unblock User - case peerInfoUnblockUser + internal static var peerInfoUnblockUser: String { return L10n.tr("Localizable", "PeerInfo.UnblockUser") } /// username - case peerInfoUsername + internal static var peerInfoUsername: String { return L10n.tr("Localizable", "PeerInfo.username") } /// Description - case peerInfoAboutPlaceholder + internal static var peerInfoAboutPlaceholder: String { return L10n.tr("Localizable", "PeerInfo.About.Placeholder") } + /// Add + internal static var peerInfoActionAddMembers: String { return L10n.tr("Localizable", "PeerInfo.Action.AddMembers") } + /// Call + internal static var peerInfoActionCall: String { return L10n.tr("Localizable", "PeerInfo.Action.Call") } + /// Leave + internal static var peerInfoActionLeave: String { return L10n.tr("Localizable", "PeerInfo.Action.Leave") } + /// Message + internal static var peerInfoActionMessage: String { return L10n.tr("Localizable", "PeerInfo.Action.Message") } + /// More + internal static var peerInfoActionMore: String { return L10n.tr("Localizable", "PeerInfo.Action.More") } + /// Mute + internal static var peerInfoActionMute: String { return L10n.tr("Localizable", "PeerInfo.Action.Mute") } + /// Report + internal static var peerInfoActionReport: String { return L10n.tr("Localizable", "PeerInfo.Action.Report") } + /// Secret + internal static var peerInfoActionSecretChat: String { return L10n.tr("Localizable", "PeerInfo.Action.SecretChat") } + /// Share + internal static var peerInfoActionShare: String { return L10n.tr("Localizable", "PeerInfo.Action.Share") } + /// Statistics + internal static var peerInfoActionStatistics: String { return L10n.tr("Localizable", "PeerInfo.Action.Statistics") } + /// Unmute + internal static var peerInfoActionUnmute: String { return L10n.tr("Localizable", "PeerInfo.Action.Unmute") } + /// Add To Group + internal static var peerInfoBotAddToGroup: String { return L10n.tr("Localizable", "PeerInfo.Bot.AddToGroup") } + /// Help + internal static var peerInfoBotHelp: String { return L10n.tr("Localizable", "PeerInfo.Bot.Help") } + /// Privacy + internal static var peerInfoBotPrivacy: String { return L10n.tr("Localizable", "PeerInfo.Bot.Privacy") } + /// Settings + internal static var peerInfoBotSettings: String { return L10n.tr("Localizable", "PeerInfo.Bot.Settings") } + /// Share + internal static var peerInfoBotShare: String { return L10n.tr("Localizable", "PeerInfo.Bot.Share") } /// has access to messages - case peerInfoBotStatusHasAccess + internal static var peerInfoBotStatusHasAccess: String { return L10n.tr("Localizable", "PeerInfo.BotStatus.HasAccess") } /// has no access to messages - case peerInfoBotStatusHasNoAccess + internal static var peerInfoBotStatusHasNoAccess: String { return L10n.tr("Localizable", "PeerInfo.BotStatus.HasNoAccess") } /// Channel Name - case peerInfoChannelNamePlaceholder - /// Add "%@" to group? - case peerInfoConfirmAddMember(String) - /// Add %d users to group? - case peerInfoConfirmAddMembers(Int) - /// Are you sure you want to delete all message history and leave "%@"?\n\nThis action cannot be undone. - case peerInfoConfirmDeleteChat(String) - /// Delete Contact? - case peerInfoConfirmDeleteContact + internal static var peerInfoChannelNamePlaceholder: String { return L10n.tr("Localizable", "PeerInfo.ChannelName.Placeholder") } + /// Channel Name + internal static var peerInfoChannelTitlePleceholder: String { return L10n.tr("Localizable", "PeerInfo.ChannelTitle.Pleceholder") } + /// Add + internal static var peerInfoConfirmAdd: String { return L10n.tr("Localizable", "PeerInfo.Confirm.Add") } + /// Add "%@" to the group? + internal static func peerInfoConfirmAddMember(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMember", p1) + } + /// %d + internal static func peerInfoConfirmAddMembers1Countable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_countable", p1) + } + /// Add %d users to the group? + internal static func peerInfoConfirmAddMembers1Few(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_few", p1) + } + /// Add %d users to the group? + internal static func peerInfoConfirmAddMembers1Many(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_many", p1) + } + /// Add %d user to the group? + internal static func peerInfoConfirmAddMembers1One(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_one", p1) + } + /// Add %d users to the group? + internal static func peerInfoConfirmAddMembers1Other(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_other", p1) + } + /// Add %d users to the group? + internal static func peerInfoConfirmAddMembers1Two(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_two", p1) + } + /// Add %d users to the group? + internal static func peerInfoConfirmAddMembers1Zero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.AddMembers1_zero", p1) + } + /// Clear + internal static var peerInfoConfirmClear: String { return L10n.tr("Localizable", "PeerInfo.Confirm.Clear") } + /// Are you sure you want to delete all message history and leave "%@"? + internal static func peerInfoConfirmDeleteChat(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.DeleteChat", p1) + } + /// Are you sure you want to delete this contact? + internal static var peerInfoConfirmDeleteContact: String { return L10n.tr("Localizable", "PeerInfo.Confirm.DeleteContact") } + /// Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway? + internal static var peerInfoConfirmDeleteGroupConfirmation: String { return L10n.tr("Localizable", "PeerInfo.Confirm.DeleteGroupConfirmation") } + /// Are you sure you want to delete chat? + internal static var peerInfoConfirmDeleteUserChat: String { return L10n.tr("Localizable", "PeerInfo.Confirm.DeleteUserChat") } /// Are you sure you want to leave this channel? - case peerInfoConfirmLeaveChannel - /// Are you sure you want to leave this group?\n\nThis action cannot be undone. - case peerInfoConfirmLeaveGroup + internal static var peerInfoConfirmLeaveChannel: String { return L10n.tr("Localizable", "PeerInfo.Confirm.LeaveChannel") } + /// Are you sure you want to leave this group? + internal static var peerInfoConfirmLeaveGroup: String { return L10n.tr("Localizable", "PeerInfo.Confirm.LeaveGroup") } /// Remove "%@" from group? - case peerInfoConfirmRemovePeer(String) + internal static func peerInfoConfirmRemovePeer(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.RemovePeer", p1) + } + /// Are you sure you want to share your phone number with "%@"? + internal static func peerInfoConfirmShareInfo(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.ShareInfo", p1) + } /// Are you sure you want to start a secret chat with "%@"? - case peerInfoConfirmStartSecretChat(String) + internal static func peerInfoConfirmStartSecretChat(_ p1: String) -> String { + return L10n.tr("Localizable", "PeerInfo.Confirm.StartSecretChat", p1) + } + /// Secret Chat + internal static var peerInfoConfirmSecretChatHeader: String { return L10n.tr("Localizable", "PeerInfo.Confirm.SecretChat.Header") } + /// Start + internal static var peerInfoConfirmSecretChatOK: String { return L10n.tr("Localizable", "PeerInfo.Confirm.SecretChat.OK") } + /// Add + internal static var peerInfoDiscussionAdd: String { return L10n.tr("Localizable", "PeerInfo.Discussion.Add") } + /// Add group chat for comments. + internal static var peerInfoDiscussionDesc: String { return L10n.tr("Localizable", "PeerInfo.Discussion.Desc") } /// First Name - case peerInfoFirstNamePlaceholder + internal static var peerInfoFirstNamePlaceholder: String { return L10n.tr("Localizable", "PeerInfo.FirstName.Placeholder") } + /// Delete + internal static var peerInfoGroupMenuDelete: String { return L10n.tr("Localizable", "PeerInfo.Group.Menu.Delete") } + /// Promote + internal static var peerInfoGroupMenuPromote: String { return L10n.tr("Localizable", "PeerInfo.Group.Menu.Promote") } + /// Restrict + internal static var peerInfoGroupMenuRestrict: String { return L10n.tr("Localizable", "PeerInfo.Group.Menu.Restrict") } /// Group Name - case peerInfoGroupNamePlaceholder + internal static var peerInfoGroupNamePlaceholder: String { return L10n.tr("Localizable", "PeerInfo.GroupName.Placeholder") } + /// Group Name + internal static var peerInfoGroupTitlePleceholder: String { return L10n.tr("Localizable", "PeerInfo.GroupTitle.Pleceholder") } /// Private - case peerInfoGroupTypePrivate + internal static var peerInfoGroupTypePrivate: String { return L10n.tr("Localizable", "PeerInfo.GroupType.Private") } /// Public - case peerInfoGroupTypePublic + internal static var peerInfoGroupTypePublic: String { return L10n.tr("Localizable", "PeerInfo.GroupType.Public") } + /// Sorry, you must be in this user's Telegram contacts to add them to this group.\n\nThey can also join on their own if you send them an invite link. + internal static var peerInfoInviteErrorContactNeeded: String { return L10n.tr("Localizable", "PeerInfo.InviteError.ContactNeeded") } /// Last Name - case peerInfoLastNamePlaceholder + internal static var peerInfoLastNamePlaceholder: String { return L10n.tr("Localizable", "PeerInfo.LastName.Placeholder") } /// Hidden - case peerInfoPreHistoryHidden + internal static var peerInfoPreHistoryHidden: String { return L10n.tr("Localizable", "PeerInfo.PreHistory.Hidden") } /// Visible - case peerInfoPreHistoryVisible - /// Add names of the admins to the messages they post. - case peerInfoSignMessagesDesc + internal static var peerInfoPreHistoryVisible: String { return L10n.tr("Localizable", "PeerInfo.PreHistory.Visible") } + /// Append names of the admins to the messages they post. + internal static var peerInfoSignMessagesDesc: String { return L10n.tr("Localizable", "PeerInfo.SignMessages.Desc") } + /// Audio + internal static var peerMediaAudio: String { return L10n.tr("Localizable", "PeerMedia.Audio") } + /// Groups + internal static var peerMediaCommonGroups: String { return L10n.tr("Localizable", "PeerMedia.CommonGroups") } + /// Docs + internal static var peerMediaFiles: String { return L10n.tr("Localizable", "PeerMedia.Files") } + /// GIFs + internal static var peerMediaGifs: String { return L10n.tr("Localizable", "PeerMedia.Gifs") } + /// Links + internal static var peerMediaLinks: String { return L10n.tr("Localizable", "PeerMedia.Links") } + /// Media + internal static var peerMediaMedia: String { return L10n.tr("Localizable", "PeerMedia.Media") } + /// Members + internal static var peerMediaMembers: String { return L10n.tr("Localizable", "PeerMedia.Members") } /// Shared Media - case peerMediaSharedMedia + internal static var peerMediaSharedMedia: String { return L10n.tr("Localizable", "PeerMedia.SharedMedia") } + /// Voicemessages + internal static var peerMediaVoice: String { return L10n.tr("Localizable", "PeerMedia.Voice") } /// Shared Audio - case peerMediaPopoverSharedAudio + internal static var peerMediaPopoverSharedAudio: String { return L10n.tr("Localizable", "PeerMedia.Popover.SharedAudio") } /// Shared Files - case peerMediaPopoverSharedFiles + internal static var peerMediaPopoverSharedFiles: String { return L10n.tr("Localizable", "PeerMedia.Popover.SharedFiles") } /// Shared Links - case peerMediaPopoverSharedLinks + internal static var peerMediaPopoverSharedLinks: String { return L10n.tr("Localizable", "PeerMedia.Popover.SharedLinks") } /// Shared Media - case peerMediaPopoverSharedMedia + internal static var peerMediaPopoverSharedMedia: String { return L10n.tr("Localizable", "PeerMedia.Popover.SharedMedia") } + /// Invite to Group via Link + internal static var peerSelectInviteViaLink: String { return L10n.tr("Localizable", "PeerSelect.InviteViaLink") } + /// Sorry, public polls can’t be forwarded to channels. + internal static var pollForwardError: String { return L10n.tr("Localizable", "Poll.Forward.Error") } + /// [Collapse]() + internal static var pollResultsCollapse: String { return L10n.tr("Localizable", "PollResults.Collapse") } + /// %d + internal static func pollResultsLoadMoreCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_countable", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_few", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_many", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_one", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_other", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_two", p1) + } + /// Show More (%d) + internal static func pollResultsLoadMoreZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PollResults.LoadMore_zero", p1) + } + /// Poll Results + internal static var pollResultsTitlePoll: String { return L10n.tr("Localizable", "PollResults.Title.Poll") } + /// Quiz Results + internal static var pollResultsTitleQuiz: String { return L10n.tr("Localizable", "PollResults.Title.Quiz") } + /// Warning, this will unlink the group from "%@" + internal static func preHistoryConfirmUnlink(_ p1: String) -> String { + return L10n.tr("Localizable", "PreHistory.Confirm.Unlink", p1) + } /// CHAT HISTORY FOR NEW MEMBERS - case preHistorySettingsHeader + internal static var preHistorySettingsHeader: String { return L10n.tr("Localizable", "PreHistorySettings.Header") } /// New members won't see earlier messages. - case preHistorySettingsDescriptionHidden + internal static var preHistorySettingsDescriptionHidden: String { return L10n.tr("Localizable", "PreHistorySettings.Description.Hidden") } /// New Members will see messages that were sent before they joined. - case preHistorySettingsDescriptionVisible + internal static var preHistorySettingsDescriptionVisible: String { return L10n.tr("Localizable", "PreHistorySettings.Description.Visible") } + /// New members won't see more than 100 previous messages. + internal static var preHistorySettingsDescriptionGroupHidden: String { return L10n.tr("Localizable", "PreHistorySettings.Description.Group.Hidden") } /// bot - case presenceBot - /// Caption... - case previderSenderCaptionPlaceholder - /// Send as compressed - case previewSenderCompressFile + internal static var presenceBot: String { return L10n.tr("Localizable", "Presence.bot") } + /// support + internal static var presenceSupport: String { return L10n.tr("Localizable", "Presence.Support") } + /// %d + internal static func previewDraggingAddItemsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Preview.Dragging.AddItems_countable", p1) + } + /// Add Items + internal static var previewDraggingAddItemsFew: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_few") } + /// Add Items + internal static var previewDraggingAddItemsMany: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_many") } + /// Add Item + internal static var previewDraggingAddItemsOne: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_one") } + /// Add Items + internal static var previewDraggingAddItemsOther: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_other") } + /// Add Items + internal static var previewDraggingAddItemsTwo: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_two") } + /// Add Items + internal static var previewDraggingAddItemsZero: String { return L10n.tr("Localizable", "Preview.Dragging.AddItems_zero") } + /// Archive all media in one zip file + internal static var previewSenderArchiveTooltip: String { return L10n.tr("Localizable", "PreviewSender.ArchiveTooltip") } + /// Add a caption... + internal static var previewSenderCaptionPlaceholder: String { return L10n.tr("Localizable", "PreviewSender.CaptionPlaceholder") } + /// Group all media into one message + internal static var previewSenderCollageTooltip: String { return L10n.tr("Localizable", "PreviewSender.CollageTooltip") } + /// Add a comment... + internal static var previewSenderCommentPlaceholder: String { return L10n.tr("Localizable", "PreviewSender.CommentPlaceholder") } + /// Send compressed + internal static var previewSenderCompressFile: String { return L10n.tr("Localizable", "PreviewSender.CompressFile") } + /// Send without compression + internal static var previewSenderFileTooltip: String { return L10n.tr("Localizable", "PreviewSender.FileTooltip") } + /// Send in a quick way + internal static var previewSenderMediaTooltip: String { return L10n.tr("Localizable", "PreviewSender.MediaTooltip") } + /// %d + internal static func previewSenderSendAudioCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_countable", p1) + } + /// Send %d Audios + internal static func previewSenderSendAudioFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_few", p1) + } + /// Send %d Audios + internal static func previewSenderSendAudioMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_many", p1) + } + /// Send Audio + internal static var previewSenderSendAudioOne: String { return L10n.tr("Localizable", "PreviewSender.SendAudio_one") } + /// Send %d Audios + internal static func previewSenderSendAudioOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_other", p1) + } + /// Send %d Audios + internal static func previewSenderSendAudioTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_two", p1) + } + /// Send %d Audios + internal static func previewSenderSendAudioZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendAudio_zero", p1) + } + /// %d + internal static func previewSenderSendFileCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_countable", p1) + } + /// Send %d Files + internal static func previewSenderSendFileFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_few", p1) + } + /// Send %d Files + internal static func previewSenderSendFileMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_many", p1) + } + /// Send File + internal static var previewSenderSendFileOne: String { return L10n.tr("Localizable", "PreviewSender.SendFile_one") } + /// Send %d Files + internal static func previewSenderSendFileOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_other", p1) + } + /// Send %d Files + internal static func previewSenderSendFileTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_two", p1) + } + /// Send %d Files + internal static func previewSenderSendFileZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendFile_zero", p1) + } + /// %d + internal static func previewSenderSendGifCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_countable", p1) + } + /// Send %d GIFs + internal static func previewSenderSendGifFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_few", p1) + } + /// Send %d GIFs + internal static func previewSenderSendGifMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_many", p1) + } + /// Send GIF + internal static var previewSenderSendGifOne: String { return L10n.tr("Localizable", "PreviewSender.SendGif_one") } + /// Send %d GIFs + internal static func previewSenderSendGifOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_other", p1) + } + /// Send %d GIFs + internal static func previewSenderSendGifTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_two", p1) + } + /// Send %d GIFs + internal static func previewSenderSendGifZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendGif_zero", p1) + } + /// %d + internal static func previewSenderSendMediaCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_countable", p1) + } + /// Send %d Media + internal static func previewSenderSendMediaFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_few", p1) + } + /// Send %d Media + internal static func previewSenderSendMediaMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_many", p1) + } + /// Send Media + internal static var previewSenderSendMediaOne: String { return L10n.tr("Localizable", "PreviewSender.SendMedia_one") } + /// Send %d Media + internal static func previewSenderSendMediaOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_other", p1) + } + /// Send %d Media + internal static func previewSenderSendMediaTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_two", p1) + } + /// Send %d Media + internal static func previewSenderSendMediaZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendMedia_zero", p1) + } + /// %d + internal static func previewSenderSendPhotoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_countable", p1) + } + /// Send %d Photos + internal static func previewSenderSendPhotoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_few", p1) + } + /// Send %d Photos + internal static func previewSenderSendPhotoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_many", p1) + } + /// Send Photo + internal static var previewSenderSendPhotoOne: String { return L10n.tr("Localizable", "PreviewSender.SendPhoto_one") } + /// Send %d Photos + internal static func previewSenderSendPhotoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_other", p1) + } + /// Send %d Photos + internal static func previewSenderSendPhotoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_two", p1) + } + /// Send %d Photos + internal static func previewSenderSendPhotoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendPhoto_zero", p1) + } + /// %d + internal static func previewSenderSendVideoCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_countable", p1) + } + /// Send %d Videos + internal static func previewSenderSendVideoFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_few", p1) + } + /// Send %d Videos + internal static func previewSenderSendVideoMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_many", p1) + } + /// Send Video + internal static var previewSenderSendVideoOne: String { return L10n.tr("Localizable", "PreviewSender.SendVideo_one") } + /// Send %d Videos + internal static func previewSenderSendVideoOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_other", p1) + } + /// Send %d Videos + internal static func previewSenderSendVideoTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_two", p1) + } + /// Send %d Videos + internal static func previewSenderSendVideoZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PreviewSender.SendVideo_zero", p1) + } + /// Sorry, you can't create a group with these users due to their privacy settings. + internal static var privacyGroupsAndChannelsInviteToChannelMultipleError: String { return L10n.tr("Localizable", "Privacy.GroupsAndChannels.InviteToChannelMultipleError") } + /// Automatically archive and mute new chats, groups and channels from non-contacts. + internal static var privacyAndSecurityAutoArchiveDesc: String { return L10n.tr("Localizable", "PrivacyAndSecurity.AutoArchiveDesc") } + /// NEW CHATS FROM UNKNOWN USERS + internal static var privacyAndSecurityAutoArchiveHeader: String { return L10n.tr("Localizable", "PrivacyAndSecurity.AutoArchiveHeader") } + /// Archive and Mute + internal static var privacyAndSecurityAutoArchiveText: String { return L10n.tr("Localizable", "PrivacyAndSecurity.AutoArchiveText") } + /// %@ users + internal static func privacyAndSecurityBlockedUsers(_ p1: String) -> String { + return L10n.tr("Localizable", "PrivacyAndSecurity.BlockedUsers", p1) + } + /// Clear Cloud Drafts + internal static var privacyAndSecurityClearCloudDrafts: String { return L10n.tr("Localizable", "PrivacyAndSecurity.ClearCloudDrafts") } + /// CHATS + internal static var privacyAndSecurityClearCloudDraftsHeader: String { return L10n.tr("Localizable", "PrivacyAndSecurity.ClearCloudDraftsHeader") } + /// Display sensitive media in public channels on all your Telegram devices. + internal static var privacyAndSecuritySensitiveDesc: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SensitiveDesc") } + /// SENSITIVE CONTENT + internal static var privacyAndSecuritySensitiveHeader: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SensitiveHeader") } + /// Disable filtering + internal static var privacyAndSecuritySensitiveText: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SensitiveText") } + /// CONNECTED WEBSITES + internal static var privacyAndSecurityWebAuthorizationHeader: String { return L10n.tr("Localizable", "PrivacyAndSecurity.WebAuthorizationHeader") } + /// Are you sure you want to clear all cloud drafts? + internal static var privacyAndSecurityConfirmClearCloudDrafts: String { return L10n.tr("Localizable", "PrivacyAndSecurity.Confirm.ClearCloudDrafts") } + /// Off + internal static var privacyAndSecurityItemOff: String { return L10n.tr("Localizable", "PrivacyAndSecurity.Item.Off") } + /// On + internal static var privacyAndSecurityItemOn: String { return L10n.tr("Localizable", "PrivacyAndSecurity.Item.On") } + /// Link previews will be generated on Telegram servers. We do not store data about the links you send. + internal static var privacyAndSecuritySecretChatWebPreviewDesc: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SecretChatWebPreview.Desc") } + /// SECRET CHAT + internal static var privacyAndSecuritySecretChatWebPreviewHeader: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SecretChatWebPreview.Header") } + /// Link Previews + internal static var privacyAndSecuritySecretChatWebPreviewText: String { return L10n.tr("Localizable", "PrivacyAndSecurity.SecretChatWebPreview.Text") } + /// Users who add your number to their contacts will see it on Telegram only if they are your contacts. + internal static var privacyPhoneNumberSettingsCustomDisabledHelp: String { return L10n.tr("Localizable", "PrivacyPhoneNumberSettings.CustomDisabledHelp") } + /// WHO CAN FIND ME BY MY NUMBER + internal static var privacyPhoneNumberSettingsDiscoveryHeader: String { return L10n.tr("Localizable", "PrivacyPhoneNumberSettings.DiscoveryHeader") } /// Active Sessions - case privacySettingsActiveSessions + internal static var privacySettingsActiveSessions: String { return L10n.tr("Localizable", "PrivacySettings.ActiveSessions") } /// Blocked Users - case privacySettingsBlockedUsers - /// Groups - case privacySettingsGroups + internal static var privacySettingsBlockedUsers: String { return L10n.tr("Localizable", "PrivacySettings.BlockedUsers") } + /// If Away For + internal static var privacySettingsDeleteAccount: String { return L10n.tr("Localizable", "PrivacySettings.DeleteAccount") } + /// If you do not come online at least once within this period, your account will be deleted along with all messages and contacts. + internal static var privacySettingsDeleteAccountDescription: String { return L10n.tr("Localizable", "PrivacySettings.DeleteAccountDescription") } + /// DELETE MY ACCOUNT + internal static var privacySettingsDeleteAccountHeader: String { return L10n.tr("Localizable", "PrivacySettings.DeleteAccountHeader") } + /// Forwarded Messages + internal static var privacySettingsForwards: String { return L10n.tr("Localizable", "PrivacySettings.Forwards") } + /// %d + internal static func privacySettingsGroupMembersCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_countable", p1) + } + /// %d members + internal static func privacySettingsGroupMembersCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_few", p1) + } + /// %d members + internal static func privacySettingsGroupMembersCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_many", p1) + } + /// %d member + internal static func privacySettingsGroupMembersCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_one", p1) + } + /// %d members + internal static func privacySettingsGroupMembersCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_other", p1) + } + /// %d members + internal static func privacySettingsGroupMembersCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_two", p1) + } + /// %d members + internal static func privacySettingsGroupMembersCountZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettings.GroupMembersCount_zero", p1) + } + /// Groups and Channels + internal static var privacySettingsGroups: String { return L10n.tr("Localizable", "PrivacySettings.Groups") } /// Last Seen - case privacySettingsLastSeen + internal static var privacySettingsLastSeen: String { return L10n.tr("Localizable", "PrivacySettings.LastSeen") } + /// My Contacts (-%@) + internal static func privacySettingsLastSeenContactsMinus(_ p1: String) -> String { + return L10n.tr("Localizable", "PrivacySettings.LastSeenContactsMinus", p1) + } + /// My Contacts (-%@, +%@) + internal static func privacySettingsLastSeenContactsMinusPlus(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "PrivacySettings.LastSeenContactsMinusPlus", p1, p2) + } + /// My Contacts (+%@) + internal static func privacySettingsLastSeenContactsPlus(_ p1: String) -> String { + return L10n.tr("Localizable", "PrivacySettings.LastSeenContactsPlus", p1) + } + /// Everybody (-%@) + internal static func privacySettingsLastSeenEverybodyMinus(_ p1: String) -> String { + return L10n.tr("Localizable", "PrivacySettings.LastSeenEverybodyMinus", p1) + } + /// Nobody (+%@) + internal static func privacySettingsLastSeenNobodyPlus(_ p1: String) -> String { + return L10n.tr("Localizable", "PrivacySettings.LastSeenNobodyPlus", p1) + } /// Passcode - case privacySettingsPasscode + internal static var privacySettingsPasscode: String { return L10n.tr("Localizable", "PrivacySettings.Passcode") } + /// Phone Number + internal static var privacySettingsPhoneNumber: String { return L10n.tr("Localizable", "PrivacySettings.PhoneNumber") } /// PRIVACY - case privacySettingsPrivacyHeader + internal static var privacySettingsPrivacyHeader: String { return L10n.tr("Localizable", "PrivacySettings.PrivacyHeader") } + /// Profile Photo + internal static var privacySettingsProfilePhoto: String { return L10n.tr("Localizable", "PrivacySettings.ProfilePhoto") } /// CONNECTION TYPE - case privacySettingsProxyHeader + internal static var privacySettingsProxyHeader: String { return L10n.tr("Localizable", "PrivacySettings.ProxyHeader") } /// SECURITY - case privacySettingsSecurityHeader + internal static var privacySettingsSecurityHeader: String { return L10n.tr("Localizable", "PrivacySettings.SecurityHeader") } /// Two-Step Verification - case privacySettingsTwoStepVerification + internal static var privacySettingsTwoStepVerification: String { return L10n.tr("Localizable", "PrivacySettings.TwoStepVerification") } /// Use Proxy - case privacySettingsUseProxy + internal static var privacySettingsUseProxy: String { return L10n.tr("Localizable", "PrivacySettings.UseProxy") } /// Voice Calls - case privacySettingsVoiceCalls + internal static var privacySettingsVoiceCalls: String { return L10n.tr("Localizable", "PrivacySettings.VoiceCalls") } /// Add New - case privacySettingsPeerSelectAddNew + internal static var privacySettingsPeerSelectAddNew: String { return L10n.tr("Localizable", "PrivacySettings.PeerSelect.AddNew") } + /// Add Users or Groups + internal static var privacySettingsPeerSelectAddUserOrGroup: String { return L10n.tr("Localizable", "PrivacySettings.PeerSelect.AddUserOrGroup") } /// Add Users - case privacySettingsControllerAddUsers + internal static var privacySettingsControllerAddUsers: String { return L10n.tr("Localizable", "PrivacySettingsController.AddUsers") } /// Always Allow - case privacySettingsControllerAlwaysAllow + internal static var privacySettingsControllerAlwaysAllow: String { return L10n.tr("Localizable", "PrivacySettingsController.AlwaysAllow") } /// Always Share - case privacySettingsControllerAlwaysShare + internal static var privacySettingsControllerAlwaysShare: String { return L10n.tr("Localizable", "PrivacySettingsController.AlwaysShare") } /// Always Share With - case privacySettingsControllerAlwaysShareWith + internal static var privacySettingsControllerAlwaysShareWith: String { return L10n.tr("Localizable", "PrivacySettingsController.AlwaysShareWith") } /// Everybody - case privacySettingsControllerEverbody + internal static var privacySettingsControllerEverbody: String { return L10n.tr("Localizable", "PrivacySettingsController.Everbody") } /// You can restrict who can add you to groups and channels with granular precision. - case privacySettingsControllerGroupDescription + internal static var privacySettingsControllerGroupDescription: String { return L10n.tr("Localizable", "PrivacySettingsController.GroupDescription") } /// WHO CAN ADD ME TO GROUP CHATS - case privacySettingsControllerGroupHeader + internal static var privacySettingsControllerGroupHeader: String { return L10n.tr("Localizable", "PrivacySettingsController.GroupHeader") } /// Last Seen - case privacySettingsControllerHeader + internal static var privacySettingsControllerHeader: String { return L10n.tr("Localizable", "PrivacySettingsController.Header") } /// Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month). - case privacySettingsControllerLastSeenDescription + internal static var privacySettingsControllerLastSeenDescription: String { return L10n.tr("Localizable", "PrivacySettingsController.LastSeenDescription") } /// WHO CAN SEE MY TIMESTAMP - case privacySettingsControllerLastSeenHeader + internal static var privacySettingsControllerLastSeenHeader: String { return L10n.tr("Localizable", "PrivacySettingsController.LastSeenHeader") } /// My Contacts - case privacySettingsControllerMyContacts + internal static var privacySettingsControllerMyContacts: String { return L10n.tr("Localizable", "PrivacySettingsController.MyContacts") } /// Never Allow - case privacySettingsControllerNeverAllow + internal static var privacySettingsControllerNeverAllow: String { return L10n.tr("Localizable", "PrivacySettingsController.NeverAllow") } /// Never Share - case privacySettingsControllerNeverShare + internal static var privacySettingsControllerNeverShare: String { return L10n.tr("Localizable", "PrivacySettingsController.NeverShare") } /// Never Share With - case privacySettingsControllerNeverShareWith + internal static var privacySettingsControllerNeverShareWith: String { return L10n.tr("Localizable", "PrivacySettingsController.NeverShareWith") } /// Nobody - case privacySettingsControllerNobody + internal static var privacySettingsControllerNobody: String { return L10n.tr("Localizable", "PrivacySettingsController.Nobody") } /// These settings will override the values above. - case privacySettingsControllerPeerInfo + internal static var privacySettingsControllerPeerInfo: String { return L10n.tr("Localizable", "PrivacySettingsController.PeerInfo") } /// You can restrict who can call you with granular precision. - case privacySettingsControllerPhoneCallDescription + internal static var privacySettingsControllerPhoneCallDescription: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneCallDescription") } /// WHO CAN CALL ME - case privacySettingsControllerPhoneCallHeader + internal static var privacySettingsControllerPhoneCallHeader: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneCallHeader") } /// %d - case privacySettingsControllerUserCountCountable(Int) + internal static func privacySettingsControllerUserCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_countable", p1) + } /// %d users - case privacySettingsControllerUserCountFew(Int) + internal static func privacySettingsControllerUserCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_few", p1) + } /// %d users - case privacySettingsControllerUserCountMany(Int) + internal static func privacySettingsControllerUserCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_many", p1) + } /// %d user - case privacySettingsControllerUserCountOne(Int) + internal static func privacySettingsControllerUserCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_one", p1) + } /// %d users - case privacySettingsControllerUserCountOther(Int) + internal static func privacySettingsControllerUserCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_other", p1) + } /// %d users - case privacySettingsControllerUserCountTwo(Int) - /// %d user - case privacySettingsControllerUserCountZero(Int) + internal static func privacySettingsControllerUserCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_two", p1) + } + /// %d users + internal static func privacySettingsControllerUserCountZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "PrivacySettingsController.UserCount_zero", p1) + } + /// When forwarded to other chats, messages you send will not link back to your account. + internal static var privacySettingsControllerForwardsCustomHelp: String { return L10n.tr("Localizable", "PrivacySettingsController.Forwards.CustomHelp") } + /// WHO CAN FORWARD MY MESSAGES + internal static var privacySettingsControllerForwardsWhoCanForward: String { return L10n.tr("Localizable", "PrivacySettingsController.Forwards.WhoCanForward") } + /// Always Allow + internal static var privacySettingsControllerForwardsAlwaysAllowTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.Forwards.AlwaysAllow.Title") } + /// Never Allow + internal static var privacySettingsControllerForwardsNeverAllowTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.Forwards.NeverAllow.Title") } + /// Always + internal static var privacySettingsControllerP2pAlways: String { return L10n.tr("Localizable", "PrivacySettingsController.P2p.Always") } + /// My Contacts + internal static var privacySettingsControllerP2pContacts: String { return L10n.tr("Localizable", "PrivacySettingsController.P2p.Contacts") } + /// Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but will slighly decrease audio quality + internal static var privacySettingsControllerP2pDesc: String { return L10n.tr("Localizable", "PrivacySettingsController.P2p.Desc") } + /// PEER TO PEER + internal static var privacySettingsControllerP2pHeader: String { return L10n.tr("Localizable", "PrivacySettingsController.P2p.Header") } + /// Never + internal static var privacySettingsControllerP2pNever: String { return L10n.tr("Localizable", "PrivacySettingsController.P2p.Never") } + /// Users who already have your number saved in the contacts will also see it on Telegram. + internal static var privacySettingsControllerPhoneNumberCustomHelp: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneNumber.CustomHelp") } + /// WHO CAN SEE MY PHONE NUMBER + internal static var privacySettingsControllerPhoneNumberWhoCanSeePhoneNumber: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneNumber.WhoCanSeePhoneNumber") } + /// Always Share With + internal static var privacySettingsControllerPhoneNumberAlwaysAllowTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneNumber.AlwaysAllow.Title") } + /// Never Share + internal static var privacySettingsControllerPhoneNumberNeverAllowTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.PhoneNumber.NeverAllow.Title") } + /// You can restrict who can see your profile photo with granular precision. + internal static var privacySettingsControllerProfilePhotoCustomHelp: String { return L10n.tr("Localizable", "PrivacySettingsController.ProfilePhoto.CustomHelp") } + /// WHO CAN SEE MY PROFILE PHOTO + internal static var privacySettingsControllerProfilePhotoWhoCanSeeMyPhoto: String { return L10n.tr("Localizable", "PrivacySettingsController.ProfilePhoto.WhoCanSeeMyPhoto") } + /// Always Share With + internal static var privacySettingsControllerProfilePhotoAlwaysShareWithTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.ProfilePhoto.AlwaysShareWith.Title") } + /// Never Share With + internal static var privacySettingsControllerProfilePhotoNeverShareWithTitle: String { return L10n.tr("Localizable", "PrivacySettingsController.ProfilePhoto.NeverShareWith.Title") } + /// Cancel + internal static var privateChannelPeekCancel: String { return L10n.tr("Localizable", "PrivateChannel.Peek.Cancel") } + /// Join Channel + internal static var privateChannelPeekHeader: String { return L10n.tr("Localizable", "PrivateChannel.Peek.Header") } + /// Join Channel + internal static var privateChannelPeekOK: String { return L10n.tr("Localizable", "PrivateChannel.Peek.OK") } + /// This channel is private. Please join it to continue viewing its content. + internal static var privateChannelPeekText: String { return L10n.tr("Localizable", "PrivateChannel.Peek.Text") } /// Are you sure you want to disable proxy server %@? - case proxyForceDisable(String) - /// Are you sure you want to enable this proxy? - case proxyForceEnableHeader - /// You can change your proxy server later in the Settings (Privacy and Security). - case proxyForceEnableText + internal static func proxyForceDisable(_ p1: String) -> String { + return L10n.tr("Localizable", "Proxy.ForceDisable", p1) + } + /// Connect + internal static var proxyForceEnableConnect: String { return L10n.tr("Localizable", "Proxy.ForceEnable.Connect") } + /// Enable Proxy + internal static var proxyForceEnableEnable: String { return L10n.tr("Localizable", "Proxy.ForceEnable.Enable") } + /// Do you want to add this proxy? + internal static var proxyForceEnableHeader1: String { return L10n.tr("Localizable", "Proxy.ForceEnable.Header1") } + /// This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic. + internal static var proxyForceEnableMTPDesc: String { return L10n.tr("Localizable", "Proxy.ForceEnable.MTPDesc") } + /// Add Proxy + internal static var proxyForceEnableOK: String { return L10n.tr("Localizable", "Proxy.ForceEnable.OK") } + /// You can change your proxy server later in Settings > Privacy and Security. + internal static var proxyForceEnableText: String { return L10n.tr("Localizable", "Proxy.ForceEnable.Text") } /// Server: %@ - case proxyForceEnableTextIP(String) + internal static func proxyForceEnableTextIP(_ p1: String) -> String { + return L10n.tr("Localizable", "Proxy.ForceEnable.Text.IP", p1) + } /// Password: %@ - case proxyForceEnableTextPassword(String) + internal static func proxyForceEnableTextPassword(_ p1: String) -> String { + return L10n.tr("Localizable", "Proxy.ForceEnable.Text.Password", p1) + } /// Port: %d - case proxyForceEnableTextPort(Int) + internal static func proxyForceEnableTextPort(_ p1: Int) -> String { + return L10n.tr("Localizable", "Proxy.ForceEnable.Text.Port", p1) + } + /// Secret: %@ + internal static func proxyForceEnableTextSecret(_ p1: String) -> String { + return L10n.tr("Localizable", "Proxy.ForceEnable.Text.Secret", p1) + } /// Username: %@ - case proxyForceEnableTextUsername(String) + internal static func proxyForceEnableTextUsername(_ p1: String) -> String { + return L10n.tr("Localizable", "Proxy.ForceEnable.Text.Username", p1) + } + /// Add Proxy + internal static var proxySettingsAddProxy: String { return L10n.tr("Localizable", "ProxySettings.AddProxy") } /// Connection - case proxySettingsConnectionHeader + internal static var proxySettingsConnectionHeader: String { return L10n.tr("Localizable", "ProxySettings.ConnectionHeader") } + /// Share proxy link + internal static var proxySettingsCopyLink: String { return L10n.tr("Localizable", "ProxySettings.CopyLink") } /// CREDENTIALS (OPTIONAL) - case proxySettingsCredentialsHeader + internal static var proxySettingsCredentialsHeader: String { return L10n.tr("Localizable", "ProxySettings.CredentialsHeader") } /// Disabled - case proxySettingsDisabled + internal static var proxySettingsDisabled: String { return L10n.tr("Localizable", "ProxySettings.Disabled") } + /// Proxy + internal static var proxySettingsEnable: String { return L10n.tr("Localizable", "ProxySettings.Enable") } /// If your clipboard contains socks5-link (**t.me/socks?server=127.0.0.1&port=80**) it will apply immediately - case proxySettingsExportDescription + internal static var proxySettingsExportDescription: String { return L10n.tr("Localizable", "ProxySettings.ExportDescription") } /// Export link from clipboard - case proxySettingsExportLink + internal static var proxySettingsExportLink: String { return L10n.tr("Localizable", "ProxySettings.ExportLink") } + /// Incorrect secret. Please try again. + internal static var proxySettingsIncorrectSecret: String { return L10n.tr("Localizable", "ProxySettings.IncorrectSecret") } + /// MTPROTO + internal static var proxySettingsMTP: String { return L10n.tr("Localizable", "ProxySettings.MTP") } /// Password - case proxySettingsPassword + internal static var proxySettingsPassword: String { return L10n.tr("Localizable", "ProxySettings.Password") } /// Port - case proxySettingsPort + internal static var proxySettingsPort: String { return L10n.tr("Localizable", "ProxySettings.Port") } /// Proxy settings not found in clipboard. - case proxySettingsProxyNotFound + internal static var proxySettingsProxyNotFound: String { return L10n.tr("Localizable", "ProxySettings.ProxyNotFound") } /// Save - case proxySettingsSave + internal static var proxySettingsSave: String { return L10n.tr("Localizable", "ProxySettings.Save") } + /// Secret + internal static var proxySettingsSecret: String { return L10n.tr("Localizable", "ProxySettings.Secret") } /// Server - case proxySettingsServer + internal static var proxySettingsServer: String { return L10n.tr("Localizable", "ProxySettings.Server") } /// Share this link with friends to circumvent censorship in your country - case proxySettingsShare + internal static var proxySettingsShare: String { return L10n.tr("Localizable", "ProxySettings.Share") } + /// Share Proxy List + internal static var proxySettingsShareProxyList: String { return L10n.tr("Localizable", "ProxySettings.ShareProxyList") } /// SOCKS5 - case proxySettingsSocks5 + internal static var proxySettingsSocks5: String { return L10n.tr("Localizable", "ProxySettings.Socks5") } + /// Proxy Settings + internal static var proxySettingsTitle: String { return L10n.tr("Localizable", "ProxySettings.Title") } + /// Proxy Type + internal static var proxySettingsType: String { return L10n.tr("Localizable", "ProxySettings.Type") } + /// Use for Calls + internal static var proxySettingsUseForCalls: String { return L10n.tr("Localizable", "ProxySettings.UseForCalls") } /// Username - case proxySettingsUsername + internal static var proxySettingsUsername: String { return L10n.tr("Localizable", "ProxySettings.Username") } + /// available (ping: %@ ms) + internal static func proxySettingsItemAvailable(_ p1: String) -> String { + return L10n.tr("Localizable", "ProxySettings.Item.Available", p1) + } + /// checking + internal static var proxySettingsItemChecking: String { return L10n.tr("Localizable", "ProxySettings.Item.Checking") } + /// connected + internal static var proxySettingsItemConnected: String { return L10n.tr("Localizable", "ProxySettings.Item.Connected") } + /// connected (ping: %@ ms) + internal static func proxySettingsItemConnectedPing(_ p1: String) -> String { + return L10n.tr("Localizable", "ProxySettings.Item.ConnectedPing", p1) + } + /// last connection %@ + internal static func proxySettingsItemLastConnection(_ p1: String) -> String { + return L10n.tr("Localizable", "ProxySettings.Item.LastConnection", p1) + } + /// unavailable + internal static var proxySettingsItemNeverConnected: String { return L10n.tr("Localizable", "ProxySettings.Item.NeverConnected") } + /// The proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic. + internal static var proxySettingsMtpSponsor: String { return L10n.tr("Localizable", "ProxySettings.Mtp.Sponsor") } + /// You or your friends can add this proxy by scanning this code with phone or in-app camera. + internal static var proxySettingsQRText: String { return L10n.tr("Localizable", "ProxySettings.QR.Text") } /// Preview - case quickLookPreview - /// **tab** or **↑ ↓** to navigate, **⮐** to select, **esc** to dismiss - case quickSwitcherDescription + internal static var quickLookPreview: String { return L10n.tr("Localizable", "QuickLook.Preview") } + /// **TAB** or **↑ ↓** to navigate, **⮐** to select, **ESC** to dismiss + internal static var quickSwitcherDescription: String { return L10n.tr("Localizable", "QuickSwitcher.Description") } /// Popular - case quickSwitcherPopular + internal static var quickSwitcherPopular: String { return L10n.tr("Localizable", "QuickSwitcher.Popular") } /// Recent - case quickSwitcherRecently - /// Telegram - case qvCM9Y7gTitle + internal static var quickSwitcherRecently: String { return L10n.tr("Localizable", "QuickSwitcher.Recently") } /// Zoom - case r4oN2Eq4Title - /// Check Spelling While Typing - case rbDRhWINTitle + internal static var r4oN2Eq4Title: String { return L10n.tr("Localizable", "R4o-n2-Eq4.title") } /// Your recent calls will appear here - case recentCallsEmpty + internal static var recentCallsEmpty: String { return L10n.tr("Localizable", "RecentCalls.Empty") } + /// These devices have no access to your account. The code was entered correctly, but no correct password was given. + internal static var recentSessionsIncompleteAttemptDesc: String { return L10n.tr("Localizable", "RecentSessions.IncompleteAttemptDesc") } + /// INCOMPLETE LOGIN ATTEMPTS + internal static var recentSessionsIncompleteAttemptHeader: String { return L10n.tr("Localizable", "RecentSessions.IncompleteAttemptHeader") } /// Revoke - case recentSessionsRevoke + internal static var recentSessionsRevoke: String { return L10n.tr("Localizable", "RecentSessions.Revoke") } /// Do you want to terminate this session? - case recentSessionsConfirmRevoke + internal static var recentSessionsConfirmRevoke: String { return L10n.tr("Localizable", "RecentSessions.Confirm.Revoke") } /// Are you sure you want to terminate all other sessions? - case recentSessionsConfirmTerminateOthers + internal static var recentSessionsConfirmTerminateOthers: String { return L10n.tr("Localizable", "RecentSessions.Confirm.TerminateOthers") } + /// For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours. + internal static var recentSessionsErrorFreshReset: String { return L10n.tr("Localizable", "RecentSessions.Error.FreshReset") } + /// Child Abuse + internal static var reportReasonChildAbuse: String { return L10n.tr("Localizable", "ReportReason.ChildAbuse") } + /// Copyright + internal static var reportReasonCopyright: String { return L10n.tr("Localizable", "ReportReason.Copyright") } + /// Other + internal static var reportReasonOther: String { return L10n.tr("Localizable", "ReportReason.Other") } /// Pornography - case reportReasonPorno + internal static var reportReasonPorno: String { return L10n.tr("Localizable", "ReportReason.Porno") } + /// Report + internal static var reportReasonReport: String { return L10n.tr("Localizable", "ReportReason.Report") } /// Spam - case reportReasonSpam + internal static var reportReasonSpam: String { return L10n.tr("Localizable", "ReportReason.Spam") } /// Violence - case reportReasonViolence - /// Smart Dashes - case rgMF4YcnTitle + internal static var reportReasonViolence: String { return L10n.tr("Localizable", "ReportReason.Violence") } + /// Description + internal static var reportReasonOtherPlaceholder: String { return L10n.tr("Localizable", "ReportReason.Other.Placeholder") } + /// Settings + internal static var requestAccesErrorConirmSettings: String { return L10n.tr("Localizable", "RequestAcces.Error.Conirm.Settings") } + /// Telegram needs access to your microphone to make calls + internal static var requestAccesErrorHaveNotAccessCall: String { return L10n.tr("Localizable", "RequestAcces.Error.HaveNotAccess.Call") } + /// Telegram needs access to your microphone and camera to record video messages. + internal static var requestAccesErrorHaveNotAccessVideoMessages: String { return L10n.tr("Localizable", "RequestAcces.Error.HaveNotAccess.VideoMessages") } + /// Telegram needs access to your microphone to record voice messages. + internal static var requestAccesErrorHaveNotAccessVoiceMessages: String { return L10n.tr("Localizable", "RequestAcces.Error.HaveNotAccess.VoiceMessages") } /// Select All - case ruw6mB2mTitle + internal static var ruw6mB2mTitle: String { return L10n.tr("Localizable", "Ruw-6m-B2m.title") } + /// Send on %@ at %@ + internal static func scheduleSendDate(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "Schedule.SendDate", p1, p2) + } + /// Send today at %@ + internal static func scheduleSendToday(_ p1: String) -> String { + return L10n.tr("Localizable", "Schedule.SendToday", p1) + } + /// Send When Online + internal static var scheduleSendWhenOnline: String { return L10n.tr("Localizable", "Schedule.SendWhenOnline") } + /// at + internal static var scheduleControllerAt: String { return L10n.tr("Localizable", "ScheduleController.at") } + /// Schedule Message + internal static var scheduleControllerTitle: String { return L10n.tr("Localizable", "ScheduleController.Title") } + /// Are you sure you want to clear your search history? + internal static var searchConfirmClearHistory: String { return L10n.tr("Localizable", "Search.Confirm.ClearHistory") } + /// %@ %d + internal static func searchGlobalChannel1Countable(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_countable", p1, p2) + } + /// %@, %d subscribers + internal static func searchGlobalChannel1Few(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_few", p1, p2) + } + /// %@, %d subscribers + internal static func searchGlobalChannel1Many(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_many", p1, p2) + } + /// %@, %d subscriber + internal static func searchGlobalChannel1One(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_one", p1, p2) + } + /// %@, %d subscribers + internal static func searchGlobalChannel1Other(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_other", p1, p2) + } + /// %@, %d subscribers + internal static func searchGlobalChannel1Two(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_two", p1, p2) + } + /// %@, %d subscribers + internal static func searchGlobalChannel1Zero(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Channel1_zero", p1, p2) + } + /// %@ %d + internal static func searchGlobalGroup1Countable(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_countable", p1, p2) + } + /// %@, %d members + internal static func searchGlobalGroup1Few(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_few", p1, p2) + } + /// %@, %d members + internal static func searchGlobalGroup1Many(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_many", p1, p2) + } + /// %@, %d member + internal static func searchGlobalGroup1One(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_one", p1, p2) + } + /// %@, %d members + internal static func searchGlobalGroup1Other(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_other", p1, p2) + } + /// %@, %d members + internal static func searchGlobalGroup1Two(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_two", p1, p2) + } + /// %@, %d members + internal static func searchGlobalGroup1Zero(_ p1: String, _ p2: Int) -> String { + return L10n.tr("Localizable", "Search.Global.Group1_zero", p1, p2) + } + /// Articles + internal static var searchPopularArticles: String { return L10n.tr("Localizable", "Search.Popular.Articles") } + /// Delete + internal static var searchPopularDelete: String { return L10n.tr("Localizable", "Search.Popular.Delete") } + /// Saved + internal static var searchPopularSavedMessages: String { return L10n.tr("Localizable", "Search.Popular.SavedMessages") } /// contacts and chats - case searchSeparatorChatsAndContacts + internal static var searchSeparatorChatsAndContacts: String { return L10n.tr("Localizable", "Search.Separator.ChatsAndContacts") } /// global search - case searchSeparatorGlobalPeers + internal static var searchSeparatorGlobalPeers: String { return L10n.tr("Localizable", "Search.Separator.GlobalPeers") } /// messages - case searchSeparatorMessages + internal static var searchSeparatorMessages: String { return L10n.tr("Localizable", "Search.Separator.Messages") } /// People - case searchSeparatorPopular + internal static var searchSeparatorPopular: String { return L10n.tr("Localizable", "Search.Separator.Popular") } /// Recent - case searchSeparatorRecent + internal static var searchSeparatorRecent: String { return L10n.tr("Localizable", "Search.Separator.Recent") } /// Search - case searchFieldSearch + internal static var searchFieldSearch: String { return L10n.tr("Localizable", "SearchField.Search") } /// Off - case secretTimerOff + internal static var secretTimerOff: String { return L10n.tr("Localizable", "SecretTimer.Off") } + /// Sorry, your Telegram app is out of date and can’t handle this request. Please update Telegram. + internal static var secureIdAppVersionOutdated: String { return L10n.tr("Localizable", "SecureId.AppVersionOutdated") } + /// Please correct errors + internal static var secureIdCorrectErrors: String { return L10n.tr("Localizable", "SecureId.CorrectErrors") } + /// Delete Address + internal static var secureIdDeleteAddress: String { return L10n.tr("Localizable", "SecureId.DeleteAddress") } + /// Delete Document + internal static var secureIdDeleteIdentity: String { return L10n.tr("Localizable", "SecureId.DeleteIdentity") } + /// Delete Telegram Passport + internal static var secureIdDeletePassport: String { return L10n.tr("Localizable", "SecureId.DeletePassport") } + /// Email Address + internal static var secureIdEmail: String { return L10n.tr("Localizable", "SecureId.Email") } + /// Identity Document + internal static var secureIdIdentityDocument: String { return L10n.tr("Localizable", "SecureId.IdentityDocument") } + /// With Telegram Passport you can easily sign up for websites and services that require identity veritification.\n\nYour information, personal data, and documents are protected by end-to-end encryption. Nobody including Telegram, can access them without your permission. + internal static var secureIdInfo: String { return L10n.tr("Localizable", "SecureId.Info") } + /// Please log in to your account to use Telegram Passport + internal static var secureIdLoginText: String { return L10n.tr("Localizable", "SecureId.LoginText") } + /// Phone Number + internal static var secureIdPhoneNumber: String { return L10n.tr("Localizable", "SecureId.PhoneNumber") } + /// Password Recovery + internal static var secureIdRecoverPassword: String { return L10n.tr("Localizable", "SecureId.RecoverPassword") } + /// Delete Email Address? + internal static var secureIdRemoveEmail: String { return L10n.tr("Localizable", "SecureId.RemoveEmail") } + /// Delete Phone Number? + internal static var secureIdRemovePhoneNumber: String { return L10n.tr("Localizable", "SecureId.RemovePhoneNumber") } + /// Residential Address + internal static var secureIdResidentialAddress: String { return L10n.tr("Localizable", "SecureId.ResidentialAddress") } + /// Scan %d + internal static func secureIdScanNumber(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.ScanNumber", p1) + } + /// Upload Additional Scan + internal static var secureIdUploadAdditionalScan: String { return L10n.tr("Localizable", "SecureId.UploadAdditionalScan") } + /// Upload Scan + internal static var secureIdUploadScan: String { return L10n.tr("Localizable", "SecureId.UploadScan") } + /// You are sending your documents directly to **%@** and allowing their **%@** to send you messages. + internal static func secureIdAcceptHelp(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "SecureId.Accept.Help", p1, p2) + } + /// You accept the [Login Widget Example Privacy Policy](_applyPolicy_) and allow their **%@** to send you messages. + internal static func secureIdAcceptPolicy(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.Accept.Policy", p1) + } + /// Add Bank Statement + internal static var secureIdAddBankStatement: String { return L10n.tr("Localizable", "SecureId.Add.BankStatement") } + /// Add Driver's License + internal static var secureIdAddDriverLicense: String { return L10n.tr("Localizable", "SecureId.Add.DriverLicense") } + /// Add Identity Card + internal static var secureIdAddID: String { return L10n.tr("Localizable", "SecureId.Add.ID") } + /// Add Internal Passport + internal static var secureIdAddInternalPassport: String { return L10n.tr("Localizable", "SecureId.Add.InternalPassport") } + /// Add Passport + internal static var secureIdAddPassport: String { return L10n.tr("Localizable", "SecureId.Add.Passport") } + /// Add Passport Registration + internal static var secureIdAddPassportRegistration: String { return L10n.tr("Localizable", "SecureId.Add.PassportRegistration") } + /// Add Personal Details + internal static var secureIdAddPersonalDetails: String { return L10n.tr("Localizable", "SecureId.Add.PersonalDetails") } + /// Add Rental Agreement + internal static var secureIdAddRentalAgreement: String { return L10n.tr("Localizable", "SecureId.Add.RentalAgreement") } + /// Add Residential Address + internal static var secureIdAddResidentialAddress: String { return L10n.tr("Localizable", "SecureId.Add.ResidentialAddress") } + /// Add Temporary Registration + internal static var secureIdAddTemporaryRegistration: String { return L10n.tr("Localizable", "SecureId.Add.TemporaryRegistration") } + /// Add Tenancy Agreement + internal static var secureIdAddTenancyAgreement: String { return L10n.tr("Localizable", "SecureId.Add.TenancyAgreement") } + /// Add Utility Bill + internal static var secureIdAddUtilityBill: String { return L10n.tr("Localizable", "SecureId.Add.UtilityBill") } + /// ADDRESS + internal static var secureIdAddressHeader: String { return L10n.tr("Localizable", "SecureId.Address.Header") } + /// %d + internal static func secureIdAddressScansCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_countable", p1) + } + /// %d scans + internal static func secureIdAddressScansFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_few", p1) + } + /// %d scans + internal static func secureIdAddressScansMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_many", p1) + } + /// %d scan + internal static func secureIdAddressScansOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_one", p1) + } + /// %d scans + internal static func secureIdAddressScansOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_other", p1) + } + /// %d scans + internal static func secureIdAddressScansTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_two", p1) + } + /// %d scans + internal static func secureIdAddressScansZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "SecureId.Address.Scans_zero", p1) + } + /// City + internal static var secureIdAddressCityInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.City.InputPlaceholder") } + /// City + internal static var secureIdAddressCityPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.City.Placeholder") } + /// Country + internal static var secureIdAddressCountryPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Country.Placeholder") } + /// Postcode + internal static var secureIdAddressPostcodeInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Postcode.InputPlaceholder") } + /// Postcode + internal static var secureIdAddressPostcodePlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Postcode.Placeholder") } + /// State/Republic/Region + internal static var secureIdAddressRegionInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Region.InputPlaceholder") } + /// Region + internal static var secureIdAddressRegionPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Region.Placeholder") } + /// Street and Number, PO Box + internal static var secureIdAddressStreetInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Street.InputPlaceholder") } + /// Street + internal static var secureIdAddressStreetPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Street.Placeholder") } + /// Apt, suite, unit, building, floor + internal static var secureIdAddressStreet1InputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Address.Street1.InputPlaceholder") } + /// Are you sure you want to stop the authorization process? + internal static var secureIdConfirmCancel: String { return L10n.tr("Localizable", "SecureId.Confirm.Cancel") } + /// Delete Address + internal static var secureIdConfirmDeleteAddress: String { return L10n.tr("Localizable", "SecureId.Confirm.DeleteAddress") } + /// Are you sure you want to delete this document? + internal static var secureIdConfirmDeleteDocument: String { return L10n.tr("Localizable", "SecureId.Confirm.DeleteDocument") } + /// Please create a password to protect your passport info. You will also be asked to enter it when you log in to Telegram. + internal static var secureIdCreatePasswordDescription: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Description") } + /// PASSWORD + internal static var secureIdCreatePasswordHeader: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Header") } + /// Please create a password which will be used to encrypt your personal data.\n\nThis password will also be required whenever you log in to Telegram on a new device. + internal static var secureIdCreatePasswordIntro: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Intro") } + /// Enter your password + internal static var secureIdCreatePasswordPasswordInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.PasswordInputPlaceholder") } + /// Password + internal static var secureIdCreatePasswordPasswordPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.PasswordPlaceholder") } + /// Re-Enter your password + internal static var secureIdCreatePasswordRePasswordInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.RePasswordInputPlaceholder") } + /// Password & E-Mail + internal static var secureIdCreatePasswordTitle: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Title") } + /// Please add your valid e-mail. It is the only way to recover a forgotten password. + internal static var secureIdCreatePasswordEmailDescription: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Email.Description") } + /// RECOVERY E-MAIL + internal static var secureIdCreatePasswordEmailHeader: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Email.Header") } + /// Your E-Mail + internal static var secureIdCreatePasswordEmailInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Email.InputPlaceholder") } + /// E-Mail + internal static var secureIdCreatePasswordEmailPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Email.Placeholder") } + /// HINT + internal static var secureIdCreatePasswordHintHeader: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Hint.Header") } + /// Hint for your password + internal static var secureIdCreatePasswordHintInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Hint.InputPlaceholder") } + /// Hint + internal static var secureIdCreatePasswordHintPlaceholder: String { return L10n.tr("Localizable", "SecureId.CreatePassword.Hint.Placeholder") } + /// **%@ requests access to your personal data**\nto sign you up for their services + internal static func secureIdCreatePasswordIntroHeader(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.CreatePassword.Intro.Header", p1) + } + /// Delete Personal Details + internal static var secureIdDeletePersonalDetails: String { return L10n.tr("Localizable", "SecureId.Delete.PersonalDetails") } + /// Are you sure you want to delete personal details? + internal static var secureIdDeleteConfirmPersonalDetails: String { return L10n.tr("Localizable", "SecureId.Delete.Confirm.PersonalDetails") } + /// Discard Changes + internal static var secureIdDiscardChangesHeader: String { return L10n.tr("Localizable", "SecureId.DiscardChanges.Header") } + /// Are you sure you want to discard all changes? + internal static var secureIdDiscardChangesText: String { return L10n.tr("Localizable", "SecureId.DiscardChanges.Text") } + /// Edit Bank Statement + internal static var secureIdEditBankStatement: String { return L10n.tr("Localizable", "SecureId.Edit.BankStatement") } + /// Edit Driver's License + internal static var secureIdEditDriverLicense: String { return L10n.tr("Localizable", "SecureId.Edit.DriverLicense") } + /// Edit Identity Card + internal static var secureIdEditID: String { return L10n.tr("Localizable", "SecureId.Edit.ID") } + /// Edit Internal Passport + internal static var secureIdEditInternalPassport: String { return L10n.tr("Localizable", "SecureId.Edit.InternalPassport") } + /// Edit Passport + internal static var secureIdEditPassport: String { return L10n.tr("Localizable", "SecureId.Edit.Passport") } + /// Edit Passport Registration + internal static var secureIdEditPassportRegistration: String { return L10n.tr("Localizable", "SecureId.Edit.PassportRegistration") } + /// Edit Personal Details + internal static var secureIdEditPersonalDetails: String { return L10n.tr("Localizable", "SecureId.Edit.PersonalDetails") } + /// Edit Rental Agreement + internal static var secureIdEditRentalAgreement: String { return L10n.tr("Localizable", "SecureId.Edit.RentalAgreement") } + /// Edit Residential Address + internal static var secureIdEditResidentialAddress: String { return L10n.tr("Localizable", "SecureId.Edit.ResidentialAddress") } + /// Edit Temporary Registration + internal static var secureIdEditTemporaryRegistration: String { return L10n.tr("Localizable", "SecureId.Edit.TemporaryRegistration") } + /// Edit Tenancy Agreement + internal static var secureIdEditTenancyAgreement: String { return L10n.tr("Localizable", "SecureId.Edit.TenancyAgreement") } + /// Edit Utility Bill + internal static var secureIdEditUtilityBill: String { return L10n.tr("Localizable", "SecureId.Edit.UtilityBill") } + /// Use %@ + internal static func secureIdEmailUseSame(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.Email.UseSame", p1) + } + /// Enter your e-mail + internal static var secureIdEmailEmailInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Email.Email.InputPlaceholder") } + /// E-Mail + internal static var secureIdEmailEmailPlaceholder: String { return L10n.tr("Localizable", "SecureId.Email.Email.Placeholder") } + /// Note: You will receive a confirmation code to the e-mail address you provide. + internal static var secureIdEmailUseSameDesc: String { return L10n.tr("Localizable", "SecureId.Email.UseSame.Desc") } + /// Please enter the confirmation code we've just sent to %@. + internal static func secureIdEmailActivateDescription(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.EmailActivate.Description", p1) + } + /// Enter code + internal static var secureIdEmailActivateCodeInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.EmailActivate.Code.InputPlaceholder") } + /// Code + internal static var secureIdEmailActivateCodePlaceholder: String { return L10n.tr("Localizable", "SecureId.EmailActivate.Code.Placeholder") } + /// Provide your address + internal static var secureIdEmptyDescriptionAddress: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.Address") } + /// Upload a scan of your bank statement + internal static var secureIdEmptyDescriptionBankStatement: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.BankStatement") } + /// Upload a scan of your driver's license + internal static var secureIdEmptyDescriptionDriversLicense: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.DriversLicense") } + /// Upload a scan of your identity card + internal static var secureIdEmptyDescriptionIdentityCard: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.IdentityCard") } + /// Upload a scan of your internal passport + internal static var secureIdEmptyDescriptionInternalPassport: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.InternalPassport") } + /// Upload a scan of your passport + internal static var secureIdEmptyDescriptionPassport: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.Passport") } + /// Upload a scan of your passport registration + internal static var secureIdEmptyDescriptionPassportRegistration: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.PassportRegistration") } + /// Fill in your personal details + internal static var secureIdEmptyDescriptionPersonalDetails: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.PersonalDetails") } + /// Upload a scan of your temporary registration + internal static var secureIdEmptyDescriptionTemporaryRegistration: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.TemporaryRegistration") } + /// Upload a scan of your tenancy agreement + internal static var secureIdEmptyDescriptionTenancyAgreement: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.TenancyAgreement") } + /// Upload a scan of your utility bill + internal static var secureIdEmptyDescriptionUtilityBill: String { return L10n.tr("Localizable", "SecureId.EmptyDescription.UtilityBill") } + /// You can't upload more than 20 files + internal static var secureIdErrorScansLimit: String { return L10n.tr("Localizable", "SecureId.Error.ScansLimit") } + /// %@%% Uploaded + internal static func secureIdFileUploadProgress(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.FileUpload.Progress", p1) + } + /// Female + internal static var secureIdGenderFemale: String { return L10n.tr("Localizable", "SecureId.Gender.Female") } + /// Male + internal static var secureIdGenderMale: String { return L10n.tr("Localizable", "SecureId.Gender.Male") } + /// Bank Statement + internal static var secureIdIdentityBankStatement: String { return L10n.tr("Localizable", "SecureId.Identity.BankStatement") } + /// DOCUMENT DETAILS + internal static var secureIdIdentityDocumentDetailsHeader: String { return L10n.tr("Localizable", "SecureId.Identity.DocumentDetailsHeader") } + /// Driver's License + internal static var secureIdIdentityDriverLicense: String { return L10n.tr("Localizable", "SecureId.Identity.DriverLicense") } + /// Identity Card + internal static var secureIdIdentityId: String { return L10n.tr("Localizable", "SecureId.Identity.Id") } + /// Enter your name using the Latin alphabet + internal static var secureIdIdentityNameInLatine: String { return L10n.tr("Localizable", "SecureId.Identity.NameInLatine") } + /// Passport + internal static var secureIdIdentityPassport: String { return L10n.tr("Localizable", "SecureId.Identity.Passport") } + /// Passport Registration + internal static var secureIdIdentityPassportRegistration: String { return L10n.tr("Localizable", "SecureId.Identity.PassportRegistration") } + /// Selfie + internal static var secureIdIdentitySelfie: String { return L10n.tr("Localizable", "SecureId.Identity.Selfie") } + /// Upload a photo of yourself holding your document. Make sure the ID and your face are clearly visible. + internal static var secureIdIdentitySelfieHelp: String { return L10n.tr("Localizable", "SecureId.Identity.SelfieHelp") } + /// SELFIE VERIFICATION + internal static var secureIdIdentitySelfieTitle: String { return L10n.tr("Localizable", "SecureId.Identity.SelfieTitle") } + /// Add Selfie + internal static var secureIdIdentitySelfieUpload: String { return L10n.tr("Localizable", "SecureId.Identity.SelfieUpload") } + /// Retake Selfie + internal static var secureIdIdentitySelfieUploadNew: String { return L10n.tr("Localizable", "SecureId.Identity.SelfieUploadNew") } + /// Tenancy Agreement + internal static var secureIdIdentityTenancyAgreement: String { return L10n.tr("Localizable", "SecureId.Identity.TenancyAgreement") } + /// Utility Bill + internal static var secureIdIdentityUtilityBill: String { return L10n.tr("Localizable", "SecureId.Identity.UtilityBill") } + /// Card ID + internal static var secureIdIdentityCardIdInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.CardId.InputPlaceholder") } + /// Card ID + internal static var secureIdIdentityCardIdPlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.CardId.Placeholder") } + /// Name + internal static var secureIdIdentityInputPlaceholderFirstName: String { return L10n.tr("Localizable", "SecureId.Identity.InputPlaceholder.FirstName") } + /// Surname + internal static var secureIdIdentityInputPlaceholderLastName: String { return L10n.tr("Localizable", "SecureId.Identity.InputPlaceholder.LastName") } + /// Middle Name + internal static var secureIdIdentityInputPlaceholderMiddleName: String { return L10n.tr("Localizable", "SecureId.Identity.InputPlaceholder.MiddleName") } + /// License ID + internal static var secureIdIdentityLicenseInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.License.InputPlaceholder") } + /// License ID + internal static var secureIdIdentityLicensePlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.License.Placeholder") } + /// Document № + internal static var secureIdIdentityPassportInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.Passport.InputPlaceholder") } + /// Document № + internal static var secureIdIdentityPassportPlaceholder: String { return L10n.tr("Localizable", "SecureId.Identity.Passport.Placeholder") } + /// Birthday + internal static var secureIdIdentityPlaceholderBirthday: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.Birthday") } + /// Citizenship + internal static var secureIdIdentityPlaceholderCitizenship: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.Citizenship") } + /// Country + internal static var secureIdIdentityPlaceholderCountry: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.Country") } + /// Expiry Date + internal static var secureIdIdentityPlaceholderExpiryDate: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.ExpiryDate") } + /// Name + internal static var secureIdIdentityPlaceholderFirstName: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.FirstName") } + /// Gender + internal static var secureIdIdentityPlaceholderGender: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.Gender") } + /// Issue Date + internal static var secureIdIdentityPlaceholderIssuedDate: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.IssuedDate") } + /// Surname + internal static var secureIdIdentityPlaceholderLastName: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.LastName") } + /// Middle Name + internal static var secureIdIdentityPlaceholderMiddleName: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.MiddleName") } + /// Residence + internal static var secureIdIdentityPlaceholderResidence: String { return L10n.tr("Localizable", "SecureId.Identity.Placeholder.Residence") } + /// The document must contain your first and last name, your residential address, a stamp / barcode / QR code / logo, and issue date, no more than 3 month ago. + internal static var secureIdIdentityScanDescription: String { return L10n.tr("Localizable", "SecureId.IdentityScan.Description") } + /// Are you sure you want to delete your Telegram Passport? All details will be lost. + internal static var secureIdInfoDeletePassport: String { return L10n.tr("Localizable", "SecureId.Info.DeletePassport") } + /// More Info + internal static var secureIdInfoMore: String { return L10n.tr("Localizable", "SecureId.Info.More") } + /// What is Telegram Passport? + internal static var secureIdInfoTitle: String { return L10n.tr("Localizable", "SecureId.Info.Title") } + /// Please use latin characters only + internal static var secureIdInputErrorLatinOnly: String { return L10n.tr("Localizable", "SecureId.InputError.LatinOnly") } + /// Please enter your password to access your personal data + internal static var secureIdInsertPasswordDescription: String { return L10n.tr("Localizable", "SecureId.InsertPassword.Description") } + /// Next + internal static var secureIdInsertPasswordNext: String { return L10n.tr("Localizable", "SecureId.InsertPassword.Next") } + /// Enter your password + internal static var secureIdInsertPasswordPassword: String { return L10n.tr("Localizable", "SecureId.InsertPassword.Password") } + /// Please enter your Telegram password to decrypt your data + internal static var secureIdInsertPasswordSettingsDescription: String { return L10n.tr("Localizable", "SecureId.InsertPassword.Settings.Description") } + /// E-Mail + internal static var secureIdInstallEmailTitle: String { return L10n.tr("Localizable", "SecureId.InstallEmail.Title") } + /// Phone Number + internal static var secureIdInstallPhoneTitle: String { return L10n.tr("Localizable", "SecureId.InstallPhone.Title") } + /// YOUR NAME IN %@ + internal static func secureIdNameNativeHeader(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.NameNative.Header", p1) + } + /// NAME IN COUNTRY OF RESIDENCE + internal static var secureIdNameNativeHeaderEmpty: String { return L10n.tr("Localizable", "SecureId.NameNative.HeaderEmpty") } + /// Your name in the language of your country of residence + internal static var secureIdNameNativeDescEmpty: String { return L10n.tr("Localizable", "SecureId.NameNative.Desc.Empty") } + /// Your name in the language of your country of residence (%@). + internal static func secureIdNameNativeDescLanguage(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.NameNative.Desc.Language", p1) + } + /// Invalid password. Please try again + internal static var secureIdPasswordErrorInvalid: String { return L10n.tr("Localizable", "SecureId.Password.Error.Invalid") } + /// Limit exceeded. Please try again later + internal static var secureIdPasswordErrorLimit: String { return L10n.tr("Localizable", "SecureId.Password.Error.Limit") } + /// OR ENTER ANOTHER PHONE NUMBER + internal static var secureIdPhoneNumberHeader: String { return L10n.tr("Localizable", "SecureId.PhoneNumber.Header") } + /// Note: You will receive a confirmation code on the phone number you provide. + internal static var secureIdPhoneNumberNote: String { return L10n.tr("Localizable", "SecureId.PhoneNumber.Note") } + /// Use %@ + internal static func secureIdPhoneNumberUseSame(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.PhoneNumber.UseSame", p1) + } + /// Please enter the confirmation code we've just sent to %@ via SMS + internal static func secureIdPhoneNumberConfirmCodeDesc(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.PhoneNumber.ConfirmCode.Desc", p1) + } + /// Enter the code + internal static var secureIdPhoneNumberConfirmCodeInputPlaceholder: String { return L10n.tr("Localizable", "SecureId.PhoneNumber.ConfirmCode.InputPlaceholder") } + /// Code + internal static var secureIdPhoneNumberConfirmCodePlaceholder: String { return L10n.tr("Localizable", "SecureId.PhoneNumber.ConfirmCode.Placeholder") } + /// Use the phone number you use for Telegram + internal static var secureIdPhoneNumberUseSameDesc: String { return L10n.tr("Localizable", "SecureId.PhoneNumber.UseSame.Desc") } + /// Code was sent to %@ + internal static func secureIdRecoverPasswordSentEmailCode(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.RecoverPassword.SentEmailCode", p1) + } + /// Authorize + internal static var secureIdRequestAccept: String { return L10n.tr("Localizable", "SecureId.Request.Accept") } + /// Create a Password + internal static var secureIdRequestCreatePassword: String { return L10n.tr("Localizable", "SecureId.Request.CreatePassword") } + /// **%@** requests access to your personal data to sign you up for their services. + internal static func secureIdRequestHeader1(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.Request.Header1", p1) + } + /// Bank Statement + internal static var secureIdRequestPermissionBankStatement: String { return L10n.tr("Localizable", "SecureId.Request.Permission.BankStatement") } + /// Driver's License + internal static var secureIdRequestPermissionDriversLicense: String { return L10n.tr("Localizable", "SecureId.Request.Permission.DriversLicense") } + /// E-Mail + internal static var secureIdRequestPermissionEmail: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Email") } + /// Identity Card + internal static var secureIdRequestPermissionIDCard: String { return L10n.tr("Localizable", "SecureId.Request.Permission.IDCard") } + /// Identity Document + internal static var secureIdRequestPermissionIdentityDocument: String { return L10n.tr("Localizable", "SecureId.Request.Permission.IdentityDocument") } + /// Internal Passport + internal static var secureIdRequestPermissionInternalPassport: String { return L10n.tr("Localizable", "SecureId.Request.Permission.InternalPassport") } + /// Passport + internal static var secureIdRequestPermissionPassport: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Passport") } + /// Passport Registration + internal static var secureIdRequestPermissionPassportRegistration: String { return L10n.tr("Localizable", "SecureId.Request.Permission.PassportRegistration") } + /// Personal Details + internal static var secureIdRequestPermissionPersonalDetails: String { return L10n.tr("Localizable", "SecureId.Request.Permission.PersonalDetails") } + /// Phone Number + internal static var secureIdRequestPermissionPhone: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Phone") } + /// Residential Address + internal static var secureIdRequestPermissionResidentialAddress: String { return L10n.tr("Localizable", "SecureId.Request.Permission.ResidentialAddress") } + /// Temporary Registration + internal static var secureIdRequestPermissionTemporaryRegistration: String { return L10n.tr("Localizable", "SecureId.Request.Permission.TemporaryRegistration") } + /// Tenancy Agreement + internal static var secureIdRequestPermissionTenancyAgreement: String { return L10n.tr("Localizable", "SecureId.Request.Permission.TenancyAgreement") } + /// Utility Bill + internal static var secureIdRequestPermissionUtilityBill: String { return L10n.tr("Localizable", "SecureId.Request.Permission.UtilityBill") } + /// Upload proof of your address + internal static var secureIdRequestPermissionAddressEmpty: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Address.Empty") } + /// Provide your contact email address + internal static var secureIdRequestPermissionEmailEmpty: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Email.Empty") } + /// Upload a scan of your passport or other ID + internal static var secureIdRequestPermissionIdentityEmpty: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Identity.Empty") } + /// Provide your contact phone number + internal static var secureIdRequestPermissionPhoneEmpty: String { return L10n.tr("Localizable", "SecureId.Request.Permission.Phone.Empty") } + /// %@ or %@ + internal static func secureIdRequestTwoDocumentsTitle(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "SecureId.Request.TwoDocuments.Title", p1, p2) + } + /// Upload a selfie with your document + internal static var secureIdRequestUploadSelfie: String { return L10n.tr("Localizable", "SecureId.Request.Upload.Selfie") } + /// Upload a translation of your document + internal static var secureIdRequestUploadTranslation: String { return L10n.tr("Localizable", "SecureId.Request.Upload.Translation") } + /// REQUESTED INFORMATION + internal static var secureIdRequestedInformationHeader: String { return L10n.tr("Localizable", "SecureId.RequestedInformation.Header") } + /// SCANS + internal static var secureIdScansHeader: String { return L10n.tr("Localizable", "SecureId.Scans.Header") } + /// Upload scans of a certified English translation of the document. + internal static var secureIdTranslationDesc: String { return L10n.tr("Localizable", "SecureId.Translation.Desc") } + /// TRANSLATION + internal static var secureIdTranslationHeader: String { return L10n.tr("Localizable", "SecureId.Translation.Header") } + /// Upload a photo of the front side of the document + internal static var secureIdUploadFront: String { return L10n.tr("Localizable", "SecureId.Upload.Front") } + /// Upload the main page of the document + internal static var secureIdUploadMain: String { return L10n.tr("Localizable", "SecureId.Upload.Main") } + /// Upload a photo of the reverse side of the document + internal static var secureIdUploadReverse: String { return L10n.tr("Localizable", "SecureId.Upload.Reverse") } + /// Upload a selfie of yourself holding the document + internal static var secureIdUploadSelfie: String { return L10n.tr("Localizable", "SecureId.Upload.Selfie") } + /// Front Side + internal static var secureIdUploadTitleFrontSide: String { return L10n.tr("Localizable", "SecureId.Upload.Title.FrontSide") } + /// Main Page + internal static var secureIdUploadTitleMainPage: String { return L10n.tr("Localizable", "SecureId.Upload.Title.MainPage") } + /// Reverse Side + internal static var secureIdUploadTitleReverseSide: String { return L10n.tr("Localizable", "SecureId.Upload.Title.ReverseSide") } + /// Upload a scan of %@ or %@ + internal static func secureIdUploadScanMulti(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "SecureId.UploadScan.Multi", p1, p2) + } + /// Upload a scan of %@ + internal static func secureIdUploadScanSingle(_ p1: String) -> String { + return L10n.tr("Localizable", "SecureId.UploadScan.Single", p1) + } + /// Warning! All data saved in your Telegram passport will be lost! + internal static var secureIdWarningDataLost: String { return L10n.tr("Localizable", "SecureId.Warning.DataLost") } + /// Since you didn't provide a recovery email when setting up your password, your remaining options are either to remember your password or to reset your account. + internal static var secureIdForgotPasswordNoEmail: String { return L10n.tr("Localizable", "SecureId.forgotPassword.NoEmail") } + /// None + internal static var selectAreaControlDimensionNone: String { return L10n.tr("Localizable", "SelectAreaControl.Dimension.None") } + /// Original + internal static var selectAreaControlDimensionOriginal: String { return L10n.tr("Localizable", "SelectAreaControl.Dimension.Original") } + /// Square + internal static var selectAreaControlDimensionSquare: String { return L10n.tr("Localizable", "SelectAreaControl.Dimension.Square") } + /// Search Members + internal static var selectPeersTitleSearchMembers: String { return L10n.tr("Localizable", "SelectPeers.Title.SearchMembers") } + /// Select Chat + internal static var selectPeersTitleSelectChat: String { return L10n.tr("Localizable", "SelectPeers.Title.SelectChat") } /// clear - case separatorClear + internal static var separatorClear: String { return L10n.tr("Localizable", "Separator.Clear") } /// show less - case separatorShowLess + internal static var separatorShowLess: String { return L10n.tr("Localizable", "Separator.ShowLess") } /// show more - case separatorShowMore + internal static var separatorShowMore: String { return L10n.tr("Localizable", "Separator.ShowMore") } /// %@ sent you a self-destructing photo. Please view it on your mobile. - case serviceMessageDesturctingPhoto(String) - /// %@ sent you a self-destructing video. Please view it on your mobile. - case serviceMessageDesturctingVideo(String) + internal static func serviceMessageDesturctingPhoto(_ p1: String) -> String { + return L10n.tr("Localizable", "ServiceMessage.DesturctingPhoto", p1) + } + /// %@ sent you a self-destructing video. Please view it on your mobile device. + internal static func serviceMessageDesturctingVideo(_ p1: String) -> String { + return L10n.tr("Localizable", "ServiceMessage.DesturctingVideo", p1) + } /// file has expired - case serviceMessageExpiredFile + internal static var serviceMessageExpiredFile: String { return L10n.tr("Localizable", "ServiceMessage.ExpiredFile") } /// photo has expired - case serviceMessageExpiredPhoto + internal static var serviceMessageExpiredPhoto: String { return L10n.tr("Localizable", "ServiceMessage.ExpiredPhoto") } /// video has expired - case serviceMessageExpiredVideo + internal static var serviceMessageExpiredVideo: String { return L10n.tr("Localizable", "ServiceMessage.ExpiredVideo") } /// %@ sent a self-destructing photo. - case serviceMessageDesturctingPhotoYou(String) + internal static func serviceMessageDesturctingPhotoYou(_ p1: String) -> String { + return L10n.tr("Localizable", "ServiceMessage.DesturctingPhoto.You", p1) + } /// %@ sent a self-destructing video. - case serviceMessageDesturctingVideoYou(String) + internal static func serviceMessageDesturctingVideoYou(_ p1: String) -> String { + return L10n.tr("Localizable", "ServiceMessage.DesturctingVideo.You", p1) + } /// ACTIVE SESSIONS - case sessionsActiveSessionsHeader + internal static var sessionsActiveSessionsHeader: String { return L10n.tr("Localizable", "Sessions.ActiveSessionsHeader") } /// CURRENT SESSION - case sessionsCurrentSessionHeader + internal static var sessionsCurrentSessionHeader: String { return L10n.tr("Localizable", "Sessions.CurrentSessionHeader") } /// Logs out all devices except for this one. - case sessionsTerminateDescription + internal static var sessionsTerminateDescription: String { return L10n.tr("Localizable", "Sessions.TerminateDescription") } /// Terminate all other sessions - case sessionsTerminateOthers + internal static var sessionsTerminateOthers: String { return L10n.tr("Localizable", "Sessions.TerminateOthers") } + /// Search results from Settings and the Telegram FAQ will appear here. + internal static var settingsSearchEmptyItem: String { return L10n.tr("Localizable", "SettingsSearch.EmptyItem") } + /// RECENT + internal static var settingsSearchRecent: String { return L10n.tr("Localizable", "SettingsSearch.Recent") } + /// clear + internal static var settingsSearchRecentClear: String { return L10n.tr("Localizable", "SettingsSearch.Recent.Clear") } + /// + internal static var settingsSearchSynonymsAppLanguage: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.AppLanguage") } + /// + internal static var settingsSearchSynonymsFAQ: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.FAQ") } + /// + internal static var settingsSearchSynonymsPassport: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Passport") } + /// + internal static var settingsSearchSynonymsSavedMessages: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.SavedMessages") } + /// Support + internal static var settingsSearchSynonymsSupport: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Support") } + /// Apple Watch + internal static var settingsSearchSynonymsWatch: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Watch") } + /// + internal static var settingsSearchSynonymsAppearanceAutoNightTheme: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.AutoNightTheme") } + /// Wallpaper + internal static var settingsSearchSynonymsAppearanceChatBackground: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.ChatBackground") } + /// bubbles + internal static var settingsSearchSynonymsAppearanceChatMode: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.ChatMode") } + /// + internal static var settingsSearchSynonymsAppearanceColorTheme: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.ColorTheme") } + /// font + internal static var settingsSearchSynonymsAppearanceTextSize: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.TextSize") } + /// + internal static var settingsSearchSynonymsAppearanceTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.Title") } + /// + internal static var settingsSearchSynonymsAppearanceChatBackgroundCustom: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.ChatBackground.Custom") } + /// + internal static var settingsSearchSynonymsAppearanceChatBackgroundSetColor: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Appearance.ChatBackground.SetColor") } + /// + internal static var settingsSearchSynonymsCallsCallTab: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Calls.CallTab") } + /// + internal static var settingsSearchSynonymsCallsTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Calls.Title") } + /// + internal static var settingsSearchSynonymsDataAutoDownloadReset: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.AutoDownloadReset") } + /// + internal static var settingsSearchSynonymsDataAutoDownloadUsingCellular: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.AutoDownloadUsingCellular") } + /// + internal static var settingsSearchSynonymsDataAutoDownloadUsingWifi: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.AutoDownloadUsingWifi") } + /// + internal static var settingsSearchSynonymsDataAutoplayGifs: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.AutoplayGifs") } + /// + internal static var settingsSearchSynonymsDataAutoplayVideos: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.AutoplayVideos") } + /// + internal static var settingsSearchSynonymsDataCallsUseLessData: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.CallsUseLessData") } + /// + internal static var settingsSearchSynonymsDataDownloadInBackground: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.DownloadInBackground") } + /// + internal static var settingsSearchSynonymsDataNetworkUsage: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.NetworkUsage") } + /// + internal static var settingsSearchSynonymsDataSaveEditedPhotos: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.SaveEditedPhotos") } + /// + internal static var settingsSearchSynonymsDataSaveIncomingPhotos: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.SaveIncomingPhotos") } + /// + internal static var settingsSearchSynonymsDataTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.Title") } + /// + internal static var settingsSearchSynonymsDataStorageClearCache: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.Storage.ClearCache") } + /// + internal static var settingsSearchSynonymsDataStorageKeepMedia: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.Storage.KeepMedia") } + /// Cache + internal static var settingsSearchSynonymsDataStorageTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Data.Storage.Title") } + /// + internal static var settingsSearchSynonymsEditProfileAddAccount: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.AddAccount") } + /// + internal static var settingsSearchSynonymsEditProfileBio: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.Bio") } + /// + internal static var settingsSearchSynonymsEditProfileLogout: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.Logout") } + /// + internal static var settingsSearchSynonymsEditProfilePhoneNumber: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.PhoneNumber") } + /// + internal static var settingsSearchSynonymsEditProfileTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.Title") } + /// nickname + internal static var settingsSearchSynonymsEditProfileUsername: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.EditProfile.Username") } + /// + internal static var settingsSearchSynonymsNotificationsBadgeCountUnreadMessages: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.BadgeCountUnreadMessages") } + /// + internal static var settingsSearchSynonymsNotificationsBadgeIncludeMutedChannels: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChannels") } + /// + internal static var settingsSearchSynonymsNotificationsBadgeIncludeMutedChats: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChats") } + /// + internal static var settingsSearchSynonymsNotificationsBadgeIncludeMutedPublicGroups: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedPublicGroups") } + /// + internal static var settingsSearchSynonymsNotificationsChannelNotificationsAlert: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ChannelNotificationsAlert") } + /// + internal static var settingsSearchSynonymsNotificationsChannelNotificationsExceptions: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ChannelNotificationsExceptions") } + /// + internal static var settingsSearchSynonymsNotificationsChannelNotificationsPreview: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ChannelNotificationsPreview") } + /// + internal static var settingsSearchSynonymsNotificationsChannelNotificationsSound: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ChannelNotificationsSound") } + /// + internal static var settingsSearchSynonymsNotificationsContactJoined: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ContactJoined") } + /// + internal static var settingsSearchSynonymsNotificationsDisplayNamesOnLockScreen: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.DisplayNamesOnLockScreen") } + /// + internal static var settingsSearchSynonymsNotificationsGroupNotificationsAlert: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.GroupNotificationsAlert") } + /// + internal static var settingsSearchSynonymsNotificationsGroupNotificationsExceptions: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.GroupNotificationsExceptions") } + /// + internal static var settingsSearchSynonymsNotificationsGroupNotificationsPreview: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.GroupNotificationsPreview") } + /// + internal static var settingsSearchSynonymsNotificationsGroupNotificationsSound: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.GroupNotificationsSound") } + /// + internal static var settingsSearchSynonymsNotificationsInAppNotificationsPreview: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.InAppNotificationsPreview") } + /// + internal static var settingsSearchSynonymsNotificationsInAppNotificationsSound: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.InAppNotificationsSound") } + /// + internal static var settingsSearchSynonymsNotificationsInAppNotificationsVibrate: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.InAppNotificationsVibrate") } + /// + internal static var settingsSearchSynonymsNotificationsMessageNotificationsAlert: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.MessageNotificationsAlert") } + /// + internal static var settingsSearchSynonymsNotificationsMessageNotificationsExceptions: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.MessageNotificationsExceptions") } + /// + internal static var settingsSearchSynonymsNotificationsMessageNotificationsPreview: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.MessageNotificationsPreview") } + /// + internal static var settingsSearchSynonymsNotificationsMessageNotificationsSound: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.MessageNotificationsSound") } + /// + internal static var settingsSearchSynonymsNotificationsResetAllNotifications: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.ResetAllNotifications") } + /// + internal static var settingsSearchSynonymsNotificationsTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Notifications.Title") } + /// + internal static var settingsSearchSynonymsPrivacyAuthSessions: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.AuthSessions") } + /// + internal static var settingsSearchSynonymsPrivacyBlockedUsers: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.BlockedUsers") } + /// + internal static var settingsSearchSynonymsPrivacyCalls: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Calls") } + /// + internal static var settingsSearchSynonymsPrivacyDeleteAccountIfAwayFor: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.DeleteAccountIfAwayFor") } + /// + internal static var settingsSearchSynonymsPrivacyForwards: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Forwards") } + /// + internal static var settingsSearchSynonymsPrivacyGroupsAndChannels: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.GroupsAndChannels") } + /// + internal static var settingsSearchSynonymsPrivacyLastSeen: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.LastSeen") } + /// + internal static var settingsSearchSynonymsPrivacyPasscode: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Passcode") } + /// + internal static var settingsSearchSynonymsPrivacyPasscodeAndFaceId: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.PasscodeAndFaceId") } + /// + internal static var settingsSearchSynonymsPrivacyPasscodeAndTouchId: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.PasscodeAndTouchId") } + /// + internal static var settingsSearchSynonymsPrivacyProfilePhoto: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.ProfilePhoto") } + /// + internal static var settingsSearchSynonymsPrivacyTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Title") } + /// Password + internal static var settingsSearchSynonymsPrivacyTwoStepAuth: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.TwoStepAuth") } + /// + internal static var settingsSearchSynonymsPrivacyDataClearPaymentsInfo: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.ClearPaymentsInfo") } + /// + internal static var settingsSearchSynonymsPrivacyDataContactsReset: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.ContactsReset") } + /// + internal static var settingsSearchSynonymsPrivacyDataContactsSync: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.ContactsSync") } + /// + internal static var settingsSearchSynonymsPrivacyDataDeleteDrafts: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.DeleteDrafts") } + /// + internal static var settingsSearchSynonymsPrivacyDataSecretChatLinkPreview: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.SecretChatLinkPreview") } + /// + internal static var settingsSearchSynonymsPrivacyDataTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.Title") } + /// + internal static var settingsSearchSynonymsPrivacyDataTopPeers: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Privacy.Data.TopPeers") } + /// + internal static var settingsSearchSynonymsProxyAddProxy: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Proxy.AddProxy") } + /// SOCKS5\nMTProto + internal static var settingsSearchSynonymsProxyTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Proxy.Title") } + /// + internal static var settingsSearchSynonymsProxyUseForCalls: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Proxy.UseForCalls") } + /// + internal static var settingsSearchSynonymsStickersArchivedPacks: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Stickers.ArchivedPacks") } + /// + internal static var settingsSearchSynonymsStickersFeaturedPacks: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Stickers.FeaturedPacks") } + /// + internal static var settingsSearchSynonymsStickersSuggestStickers: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Stickers.SuggestStickers") } + /// + internal static var settingsSearchSynonymsStickersTitle: String { return L10n.tr("Localizable", "SettingsSearch.Synonyms.Stickers.Title") } /// Copied to Clipboard - case shareLinkCopied + internal static var shareLinkCopied: String { return L10n.tr("Localizable", "Share.Link.Copied") } /// Cancel - case shareExtensionCancel + internal static var shareExtensionCancel: String { return L10n.tr("Localizable", "ShareExtension.Cancel") } /// Search - case shareExtensionSearch + internal static var shareExtensionSearch: String { return L10n.tr("Localizable", "ShareExtension.Search") } /// Share - case shareExtensionShare + internal static var shareExtensionShare: String { return L10n.tr("Localizable", "ShareExtension.Share") } /// Next - case shareExtensionPasscodeNext + internal static var shareExtensionPasscodeNext: String { return L10n.tr("Localizable", "ShareExtension.Passcode.Next") } /// passcode - case shareExtensionPasscodePlaceholder + internal static var shareExtensionPasscodePlaceholder: String { return L10n.tr("Localizable", "ShareExtension.Passcode.Placeholder") } /// To share via Telegram, please open the Telegam app and log in. - case shareExtensionUnauthorizedDescription + internal static var shareExtensionUnauthorizedDescription: String { return L10n.tr("Localizable", "ShareExtension.Unauthorized.Description") } /// OK - case shareExtensionUnauthorizedOK + internal static var shareExtensionUnauthorizedOK: String { return L10n.tr("Localizable", "ShareExtension.Unauthorized.OK") } + /// Forward to... + internal static var shareModalSearchForwardPlaceholder: String { return L10n.tr("Localizable", "ShareModal.Search.ForwardPlaceholder") } /// Share to... - case shareModalSearchPlaceholder - /// Sidebar available in chat - case sidebarAvalability + internal static var shareModalSearchPlaceholder: String { return L10n.tr("Localizable", "ShareModal.Search.Placeholder") } + /// CHAT + internal static var shortcutsControllerChat: String { return L10n.tr("Localizable", "ShortcutsController.Chat") } + /// GESTURES + internal static var shortcutsControllerGestures: String { return L10n.tr("Localizable", "ShortcutsController.Gestures") } + /// MARKDOWN + internal static var shortcutsControllerMarkdown: String { return L10n.tr("Localizable", "ShortcutsController.Markdown") } + /// MOUSE + internal static var shortcutsControllerMouse: String { return L10n.tr("Localizable", "ShortcutsController.Mouse") } + /// OTHERS + internal static var shortcutsControllerOthers: String { return L10n.tr("Localizable", "ShortcutsController.Others") } + /// SEARCH + internal static var shortcutsControllerSearch: String { return L10n.tr("Localizable", "ShortcutsController.Search") } + /// Shortcuts + internal static var shortcutsControllerTitle: String { return L10n.tr("Localizable", "ShortcutsController.Title") } + /// Edit Last Message + internal static var shortcutsControllerChatEditLastMessage: String { return L10n.tr("Localizable", "ShortcutsController.Chat.EditLastMessage") } + /// Open Info + internal static var shortcutsControllerChatOpenInfo: String { return L10n.tr("Localizable", "ShortcutsController.Chat.OpenInfo") } + /// Record Voice/Video Message + internal static var shortcutsControllerChatRecordVoiceMessage: String { return L10n.tr("Localizable", "ShortcutsController.Chat.RecordVoiceMessage") } + /// Search Messages + internal static var shortcutsControllerChatSearchMessages: String { return L10n.tr("Localizable", "ShortcutsController.Chat.SearchMessages") } + /// Select Message To Reply + internal static var shortcutsControllerChatSelectMessageToReply: String { return L10n.tr("Localizable", "ShortcutsController.Chat.SelectMessageToReply") } + /// Chat Actions + internal static var shortcutsControllerGesturesChatAction: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.ChatAction") } + /// Navigation Back + internal static var shortcutsControllerGesturesNavigation: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Navigation") } + /// Reply + internal static var shortcutsControllerGesturesReply: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Reply") } + /// Stickers/Emoji/GIFs Panel + internal static var shortcutsControllerGesturesStickers: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Stickers") } + /// Swipe both sides + internal static var shortcutsControllerGesturesChatActionValue: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.ChatAction.Value") } + /// Swipe From Left To Right + internal static var shortcutsControllerGesturesNavigationsValue: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Navigations.Value") } + /// Swipe From Right To Left + internal static var shortcutsControllerGesturesReplyValue: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Reply.Value") } + /// Swipe both sides + internal static var shortcutsControllerGesturesStickersValue: String { return L10n.tr("Localizable", "ShortcutsController.Gestures.Stickers.Value") } + /// Bold + internal static var shortcutsControllerMarkdownBold: String { return L10n.tr("Localizable", "ShortcutsController.Markdown.Bold") } + /// Hyperlink + internal static var shortcutsControllerMarkdownHyperlink: String { return L10n.tr("Localizable", "ShortcutsController.Markdown.Hyperlink") } + /// Italic + internal static var shortcutsControllerMarkdownItalic: String { return L10n.tr("Localizable", "ShortcutsController.Markdown.Italic") } + /// Monospace + internal static var shortcutsControllerMarkdownMonospace: String { return L10n.tr("Localizable", "ShortcutsController.Markdown.Monospace") } + /// Strikethrough + internal static var shortcutsControllerMarkdownStrikethrough: String { return L10n.tr("Localizable", "ShortcutsController.Markdown.Strikethrough") } + /// Fast Reply + internal static var shortcutsControllerMouseFastReply: String { return L10n.tr("Localizable", "ShortcutsController.Mouse.FastReply") } + /// Schedule a message + internal static var shortcutsControllerMouseScheduleMessage: String { return L10n.tr("Localizable", "ShortcutsController.Mouse.ScheduleMessage") } + /// Double Click + internal static var shortcutsControllerMouseFastReplyValue: String { return L10n.tr("Localizable", "ShortcutsController.Mouse.FastReply.Value") } + /// Option click on 'Send Message' + internal static var shortcutsControllerMouseScheduleMessageValue: String { return L10n.tr("Localizable", "ShortcutsController.Mouse.ScheduleMessage.Value") } + /// Lock by Passcode + internal static var shortcutsControllerOthersLockByPasscode: String { return L10n.tr("Localizable", "ShortcutsController.Others.LockByPasscode") } + /// Global Search + internal static var shortcutsControllerSearchGlobalSearch: String { return L10n.tr("Localizable", "ShortcutsController.Search.GlobalSearch") } + /// Quick Search + internal static var shortcutsControllerSearchQuickSearch: String { return L10n.tr("Localizable", "ShortcutsController.Search.QuickSearch") } + /// The sidebar is only available while chatting + internal static var sidebarAvalability: String { return L10n.tr("Localizable", "Sidebar.Avalability") } + /// Hide Panel + internal static var sidebarHide: String { return L10n.tr("Localizable", "Sidebar.Hide") } + /// Sidebar is not available in this chat + internal static var sidebarPeerRestricted: String { return L10n.tr("Localizable", "Sidebar.Peer.Restricted") } + /// Slow mode is enabled. You can't forward a message with a comment + internal static var slowModeForwardCommentError: String { return L10n.tr("Localizable", "SlowMode.ForwardComment.Error") } + /// Slow mode is enabled. You can't send more than one message at a time. + internal static var slowModeMultipleError: String { return L10n.tr("Localizable", "SlowMode.Multiple.Error") } + /// Slowmode is Enabled.\nYou can't add comment as addition message. + internal static var slowModePreviewSenderComment: String { return L10n.tr("Localizable", "SlowMode.PreviewSender.Comment") } + /// Slowmode is Enabled.\nThere is no way to send multiple files at once. + internal static var slowModePreviewSenderFileTooltip: String { return L10n.tr("Localizable", "SlowMode.PreviewSender.FileTooltip") } + /// Slowmode is Enabled.\nThere is no way to send multiple media at once. + internal static var slowModePreviewSenderMediaTooltip: String { return L10n.tr("Localizable", "SlowMode.PreviewSender.MediaTooltip") } + /// Slow mode is enabled. This text is too long to send as one message. + internal static var slowModeTooLongError: String { return L10n.tr("Localizable", "SlowMode.TooLong.Error") } + /// ACTIONS + internal static var statsGroupActionsTitle: String { return L10n.tr("Localizable", "Stats.GroupActionsTitle") } + /// GROWTH + internal static var statsGroupGrowthTitle: String { return L10n.tr("Localizable", "Stats.GroupGrowthTitle") } + /// MEMBERS' PRIMARY LANGUAGE + internal static var statsGroupLanguagesTitle: String { return L10n.tr("Localizable", "Stats.GroupLanguagesTitle") } + /// Members + internal static var statsGroupMembers: String { return L10n.tr("Localizable", "Stats.GroupMembers") } + /// GROUP MEMBERS + internal static var statsGroupMembersTitle: String { return L10n.tr("Localizable", "Stats.GroupMembersTitle") } + /// Messages + internal static var statsGroupMessages: String { return L10n.tr("Localizable", "Stats.GroupMessages") } + /// MESSAGES + internal static var statsGroupMessagesTitle: String { return L10n.tr("Localizable", "Stats.GroupMessagesTitle") } + /// NEW MEMBERS BY SOURCE + internal static var statsGroupNewMembersBySourceTitle: String { return L10n.tr("Localizable", "Stats.GroupNewMembersBySourceTitle") } + /// OVERVIEW + internal static var statsGroupOverview: String { return L10n.tr("Localizable", "Stats.GroupOverview") } + /// Posting Members + internal static var statsGroupPosters: String { return L10n.tr("Localizable", "Stats.GroupPosters") } + /// %d + internal static func statsGroupTopAdminBansCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_countable", p1) + } + /// %d bans + internal static func statsGroupTopAdminBansFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_few", p1) + } + /// %d bans + internal static func statsGroupTopAdminBansMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_many", p1) + } + /// %d ban + internal static func statsGroupTopAdminBansOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_one", p1) + } + /// %d bans + internal static func statsGroupTopAdminBansOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_other", p1) + } + /// %d bans + internal static func statsGroupTopAdminBansTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_two", p1) + } + /// %d bans + internal static func statsGroupTopAdminBansZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminBans_zero", p1) + } + /// %d + internal static func statsGroupTopAdminDeletionsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_countable", p1) + } + /// %d deletions + internal static func statsGroupTopAdminDeletionsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_few", p1) + } + /// %d deletions + internal static func statsGroupTopAdminDeletionsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_many", p1) + } + /// %d deletion + internal static func statsGroupTopAdminDeletionsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_one", p1) + } + /// %d deletions + internal static func statsGroupTopAdminDeletionsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_other", p1) + } + /// %d deletions + internal static func statsGroupTopAdminDeletionsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_two", p1) + } + /// %d deletions + internal static func statsGroupTopAdminDeletionsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminDeletions_zero", p1) + } + /// %d + internal static func statsGroupTopAdminKicksCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_countable", p1) + } + /// %d kicks + internal static func statsGroupTopAdminKicksFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_few", p1) + } + /// %d kicks + internal static func statsGroupTopAdminKicksMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_many", p1) + } + /// %d kick + internal static func statsGroupTopAdminKicksOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_one", p1) + } + /// %d kicks + internal static func statsGroupTopAdminKicksOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_other", p1) + } + /// %d kicks + internal static func statsGroupTopAdminKicksTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_two", p1) + } + /// %d kicks + internal static func statsGroupTopAdminKicksZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopAdminKicks_zero", p1) + } + /// TOP ADMINS + internal static var statsGroupTopAdminsTitle: String { return L10n.tr("Localizable", "Stats.GroupTopAdminsTitle") } + /// TOP HOURS + internal static var statsGroupTopHoursTitle: String { return L10n.tr("Localizable", "Stats.GroupTopHoursTitle") } + /// %d + internal static func statsGroupTopInviterInvitesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_countable", p1) + } + /// %d invitations + internal static func statsGroupTopInviterInvitesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_few", p1) + } + /// %d invitations + internal static func statsGroupTopInviterInvitesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_many", p1) + } + /// %d invitation + internal static func statsGroupTopInviterInvitesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_one", p1) + } + /// %d invitations + internal static func statsGroupTopInviterInvitesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_other", p1) + } + /// %d invitations + internal static func statsGroupTopInviterInvitesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_two", p1) + } + /// %d invitations + internal static func statsGroupTopInviterInvitesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopInviterInvites_zero", p1) + } + /// TOP INVITERS + internal static var statsGroupTopInvitersTitle: String { return L10n.tr("Localizable", "Stats.GroupTopInvitersTitle") } + /// %d + internal static func statsGroupTopPosterCharsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_countable", p1) + } + /// %d symbols per message + internal static func statsGroupTopPosterCharsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_few", p1) + } + /// %d symbols per message + internal static func statsGroupTopPosterCharsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_many", p1) + } + /// %d symbol per message + internal static func statsGroupTopPosterCharsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_one", p1) + } + /// %d symbols per message + internal static func statsGroupTopPosterCharsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_other", p1) + } + /// %d symbols per message + internal static func statsGroupTopPosterCharsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_two", p1) + } + /// %d symbols per message + internal static func statsGroupTopPosterCharsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterChars_zero", p1) + } + /// %d + internal static func statsGroupTopPosterMessagesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_countable", p1) + } + /// %d messages + internal static func statsGroupTopPosterMessagesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_few", p1) + } + /// %d messages + internal static func statsGroupTopPosterMessagesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_many", p1) + } + /// %d message + internal static func statsGroupTopPosterMessagesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_one", p1) + } + /// %d messages + internal static func statsGroupTopPosterMessagesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_other", p1) + } + /// %d messages + internal static func statsGroupTopPosterMessagesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_two", p1) + } + /// %d messages + internal static func statsGroupTopPosterMessagesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.GroupTopPosterMessages_zero", p1) + } + /// TOP MEMBERS + internal static var statsGroupTopPostersTitle: String { return L10n.tr("Localizable", "Stats.GroupTopPostersTitle") } + /// TOP DAYS OF WEEK + internal static var statsGroupTopWeekdaysTitle: String { return L10n.tr("Localizable", "Stats.GroupTopWeekdaysTitle") } + /// Viewing Members + internal static var statsGroupViewers: String { return L10n.tr("Localizable", "Stats.GroupViewers") } + /// %d + internal static func statsShowMoreCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_countable", p1) + } + /// Show %d More + internal static func statsShowMoreFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_few", p1) + } + /// Show %d More + internal static func statsShowMoreMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_many", p1) + } + /// Show %d More + internal static func statsShowMoreOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_one", p1) + } + /// Show %d More + internal static func statsShowMoreOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_other", p1) + } + /// Show %d More + internal static func statsShowMoreTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_two", p1) + } + /// Show %d More + internal static func statsShowMoreZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stats.ShowMore_zero", p1) + } + /// Actions + internal static var statsGroupTopAdminActions: String { return L10n.tr("Localizable", "Stats.GroupTopAdmin.Actions") } + /// Promote + internal static var statsGroupTopAdminPromote: String { return L10n.tr("Localizable", "Stats.GroupTopAdmin.Promote") } + /// History + internal static var statsGroupTopInviterHistory: String { return L10n.tr("Localizable", "Stats.GroupTopInviter.History") } + /// Promote + internal static var statsGroupTopInviterPromote: String { return L10n.tr("Localizable", "Stats.GroupTopInviter.Promote") } + /// History + internal static var statsGroupTopPosterHistory: String { return L10n.tr("Localizable", "Stats.GroupTopPoster.History") } + /// Promote + internal static var statsGroupTopPosterPromote: String { return L10n.tr("Localizable", "Stats.GroupTopPoster.Promote") } + /// Activate + internal static var statusBarActivate: String { return L10n.tr("Localizable", "StatusBar.Activate") } + /// Hide + internal static var statusBarHide: String { return L10n.tr("Localizable", "StatusBar.Hide") } + /// Quit + internal static var statusBarQuit: String { return L10n.tr("Localizable", "StatusBar.Quit") } + /// %d + internal static func stickerPackAdd1Countable(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_countable", p1) + } + /// Add %d Stickers + internal static func stickerPackAdd1Few(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_few", p1) + } + /// Add %d Stickers + internal static func stickerPackAdd1Many(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_many", p1) + } + /// Add %d Sticker + internal static func stickerPackAdd1One(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_one", p1) + } + /// Add %d Stickers + internal static func stickerPackAdd1Other(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_other", p1) + } + /// Add %d Stickers + internal static func stickerPackAdd1Two(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_two", p1) + } /// Add %d Stickers - case stickerPackAdd(Int) - /// Remove %d stickers - case stickerPackRemove(Int) + internal static func stickerPackAdd1Zero(_ p1: Int) -> String { + return L10n.tr("Localizable", "StickerPack.Add1_zero", p1) + } + /// Sorry, this sticker set doesn't seem to exist. + internal static var stickerSetDontExist: String { return L10n.tr("Localizable", "StickerSet.DontExist") } + /// Remove + internal static var stickerSetRemove: String { return L10n.tr("Localizable", "StickerSet.Remove") } + /// %d + internal static func stickersCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_countable", p1) + } + /// %d stickers + internal static func stickersCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_few", p1) + } + /// %d stickers + internal static func stickersCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_many", p1) + } + /// %d sticker + internal static func stickersCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_one", p1) + } + /// %d stickers + internal static func stickersCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_other", p1) + } + /// %d stickers + internal static func stickersCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_two", p1) + } + /// %d stickers + internal static func stickersCountZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Count_zero", p1) + } + /// Favorite + internal static var stickersFavorite: String { return L10n.tr("Localizable", "Stickers.Favorite") } /// GROUP STICKERS - case stickersGroupStickers + internal static var stickersGroupStickers: String { return L10n.tr("Localizable", "Stickers.GroupStickers") } /// Recent - case stickersRecent + internal static var stickersRecent: String { return L10n.tr("Localizable", "Stickers.Recent") } + /// Add + internal static var stickersSearchAdd: String { return L10n.tr("Localizable", "Stickers.SearchAdd") } + /// Added + internal static var stickersSearchAdded: String { return L10n.tr("Localizable", "Stickers.SearchAdded") } + /// My Sets + internal static var stickersSuggestAdded: String { return L10n.tr("Localizable", "Stickers.SuggestAdded") } + /// All Sets + internal static var stickersSuggestAll: String { return L10n.tr("Localizable", "Stickers.SuggestAll") } + /// None + internal static var stickersSuggestNone: String { return L10n.tr("Localizable", "Stickers.SuggestNone") } + /// Suggest Stickers by Emoji + internal static var stickersSuggestStickers: String { return L10n.tr("Localizable", "Stickers.SuggestStickers") } + /// Clear Recent Stickers + internal static var stickersConfirmClearRecentHeader: String { return L10n.tr("Localizable", "Stickers.Confirm.ClearRecentHeader") } + /// Clear + internal static var stickersConfirmClearRecentOK: String { return L10n.tr("Localizable", "Stickers.Confirm.ClearRecentOK") } + /// Are you sure you want to clear recent stickers? + internal static var stickersConfirmClearRecentText: String { return L10n.tr("Localizable", "Stickers.Confirm.ClearRecentText") } + /// Archive + internal static var stickersContextArchive: String { return L10n.tr("Localizable", "Stickers.Context.Archive") } + /// %d + internal static func stickersSetCount1Countable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_countable", p1) + } /// %d stickers - case stickersSetCount(Int) - /// Remove - case stickerSetRemove + internal static func stickersSetCount1Few(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_few", p1) + } + /// %d stickers + internal static func stickersSetCount1Many(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_many", p1) + } + /// %d sticker + internal static func stickersSetCount1One(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_one", p1) + } + /// %d stickers + internal static func stickersSetCount1Other(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_other", p1) + } + /// %d stickers + internal static func stickersSetCount1Two(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_two", p1) + } + /// %d stickers + internal static func stickersSetCount1Zero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Stickers.Set.Count1_zero", p1) + } /// Clear %@ - case storageClear(String) + internal static func storageClear(_ p1: String) -> String { + return L10n.tr("Localizable", "Storage.Clear", p1) + } + /// Clear All + internal static var storageClearAll: String { return L10n.tr("Localizable", "Storage.ClearAll") } /// Audio - case storageClearAudio + internal static var storageClearAudio: String { return L10n.tr("Localizable", "Storage.Clear.Audio") } /// Documents - case storageClearDocuments + internal static var storageClearDocuments: String { return L10n.tr("Localizable", "Storage.Clear.Documents") } /// Photos - case storageClearPhotos + internal static var storageClearPhotos: String { return L10n.tr("Localizable", "Storage.Clear.Photos") } /// Videos - case storageClearVideos - /// Calculating current cache size... - case storageUsageCalculating + internal static var storageClearVideos: String { return L10n.tr("Localizable", "Storage.Clear.Videos") } + /// Are you sure you want to clear all cached data? + internal static var storageClearAllConfirmDescription: String { return L10n.tr("Localizable", "Storage.ClearAll.Confirm.Description") } + /// Telegram is calculating the current cache size.\nThis can take a few minutes. + internal static var storageUsageCalculating: String { return L10n.tr("Localizable", "StorageUsage.Calculating") } /// CHATS - case storageUsageChatsHeader + internal static var storageUsageChatsHeader: String { return L10n.tr("Localizable", "StorageUsage.ChatsHeader") } + /// Clear + internal static var storageUsageClear: String { return L10n.tr("Localizable", "StorageUsage.Clear") } /// Keep Media - case storageUsageKeepMedia - /// Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again. - case storageUsageKeepMediaDescription + internal static var storageUsageKeepMedia: String { return L10n.tr("Localizable", "StorageUsage.KeepMedia") } + /// Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space. + internal static var storageUsageKeepMediaDescription1: String { return L10n.tr("Localizable", "StorageUsage.KeepMedia.Description1") } + /// If your cache size exceeds this limit, the oldest media will be deleted.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again. + internal static var storageUsageLimitDesc: String { return L10n.tr("Localizable", "StorageUsage.Limit.Desc") } + /// MAXIMUM CACHE SIZE + internal static var storageUsageLimitHeader: String { return L10n.tr("Localizable", "StorageUsage.Limit.Header") } + /// No Limit + internal static var storageUsageLimitNoLimit: String { return L10n.tr("Localizable", "StorageUsage.Limit.NoLimit") } + /// Suggest Frequent Contacts + internal static var suggestFrequentContacts: String { return L10n.tr("Localizable", "Suggest.Frequent.Contacts") } + /// This will delete all data about the people you message frequently as well as the inline bots you are likely to use. + internal static var suggestFrequentContactsAlert: String { return L10n.tr("Localizable", "Suggest.Frequent.Contacts.Alert") } + /// Display people you message frequently at the top of the search section for quick access. + internal static var suggestFrequentContactsDesc: String { return L10n.tr("Localizable", "Suggest.Frequent.Contacts.Desc") } /// Choose your language - case suggestLocalizationHeader + internal static var suggestLocalizationHeader: String { return L10n.tr("Localizable", "Suggest.Localization.Header") } /// Other - case suggestLocalizationOther + internal static var suggestLocalizationOther: String { return L10n.tr("Localizable", "Suggest.Localization.Other") } /// Convert to Supergroup - case supergroupConvertButton + internal static var supergroupConvertButton: String { return L10n.tr("Localizable", "Supergroup.Convert.Button") } /// **In supergroups:**\n\n• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group - case supergroupConvertDescription + internal static var supergroupConvertDescription: String { return L10n.tr("Localizable", "Supergroup.Convert.Description") } /// **Note**: This action cannot be undone. - case supergroupConvertUndone + internal static var supergroupConvertUndone: String { return L10n.tr("Localizable", "Supergroup.Convert.Undone") } /// Ban User - case supergroupDeleteRestrictionBanUser + internal static var supergroupDeleteRestrictionBanUser: String { return L10n.tr("Localizable", "Supergroup.DeleteRestriction.BanUser") } /// Delete All Messages - case supergroupDeleteRestrictionDeleteAllMessages + internal static var supergroupDeleteRestrictionDeleteAllMessages: String { return L10n.tr("Localizable", "Supergroup.DeleteRestriction.DeleteAllMessages") } /// Delete Message - case supergroupDeleteRestrictionDeleteMessage + internal static var supergroupDeleteRestrictionDeleteMessage: String { return L10n.tr("Localizable", "Supergroup.DeleteRestriction.DeleteMessage") } /// Report Spam - case supergroupDeleteRestrictionReportSpam - /// Quick Search - case sZhCtGQSTitle + internal static var supergroupDeleteRestrictionReportSpam: String { return L10n.tr("Localizable", "Supergroup.DeleteRestriction.ReportSpam") } + /// Manage Messages + internal static var supergroupDeleteRestrictionTitle: String { return L10n.tr("Localizable", "Supergroup.DeleteRestriction.Title") } + /// App Data Storage + internal static var systemMemoryWarningDataAndStorage: String { return L10n.tr("Localizable", "System.MemoryWarning.DataAndStorage") } + /// %d GB + internal static func systemMemoryWarningFreeSpace(_ p1: Int) -> String { + return L10n.tr("Localizable", "System.MemoryWarning.FreeSpace", p1) + } + /// Warning! + internal static var systemMemoryWarningHeader: String { return L10n.tr("Localizable", "System.MemoryWarning.Header") } + /// Less then 1GB + internal static var systemMemoryWarningLessThen1GB: String { return L10n.tr("Localizable", "System.MemoryWarning.LessThen1GB") } + /// OK + internal static var systemMemoryWarningOK: String { return L10n.tr("Localizable", "System.MemoryWarning.OK") } + /// Your Mac is running low on disk space. Please free up some space by removing unnecessary files or changing your cache settings.\n\nFree space available: ~%@ + internal static func systemMemoryWarningText(_ p1: String) -> String { + return L10n.tr("Localizable", "System.MemoryWarning.Text", p1) + } /// Window - case td7AD5loTitle + internal static var td7AD5loTitle: String { return L10n.tr("Localizable", "Td7-aD-5lo.title") } /// Appearance - case telegramAppearanceViewController + internal static var telegramAppearanceViewController: String { return L10n.tr("Localizable", "Telegram.AppearanceViewController") } /// Archived Stickers - case telegramArchivedStickerPacksController + internal static var telegramArchivedStickerPacksController: String { return L10n.tr("Localizable", "Telegram.ArchivedStickerPacksController") } /// Bio - case telegramBioViewController + internal static var telegramBioViewController: String { return L10n.tr("Localizable", "Telegram.BioViewController") } /// Blocked Users - case telegramBlockedPeersViewController + internal static var telegramBlockedPeersViewController: String { return L10n.tr("Localizable", "Telegram.BlockedPeersViewController") } /// Admins - case telegramChannelAdminsViewController - /// Blacklist - case telegramChannelBlacklistViewController + internal static var telegramChannelAdminsViewController: String { return L10n.tr("Localizable", "Telegram.ChannelAdminsViewController") } + /// Removed Users + internal static var telegramChannelBlacklistViewController: String { return L10n.tr("Localizable", "Telegram.ChannelBlacklistViewController") } /// All Actions - case telegramChannelEventLogController + internal static var telegramChannelEventLogController: String { return L10n.tr("Localizable", "Telegram.ChannelEventLogController") } /// Channel - case telegramChannelIntroViewController + internal static var telegramChannelIntroViewController: String { return L10n.tr("Localizable", "Telegram.ChannelIntroViewController") } /// Channel Members - case telegramChannelMembersViewController + internal static var telegramChannelMembersViewController: String { return L10n.tr("Localizable", "Telegram.ChannelMembersViewController") } + /// Permissions + internal static var telegramChannelPermissionsController: String { return L10n.tr("Localizable", "Telegram.ChannelPermissionsController") } + /// Channel Stats + internal static var telegramChannelStatisticsController: String { return L10n.tr("Localizable", "Telegram.ChannelStatisticsController") } /// Group - case telegramChannelVisibilityController + internal static var telegramChannelVisibilityController: String { return L10n.tr("Localizable", "Telegram.ChannelVisibilityController") } /// Supergroup - case telegramConvertGroupViewController - /// - case telegramEmptyChatViewController + internal static var telegramConvertGroupViewController: String { return L10n.tr("Localizable", "Telegram.ConvertGroupViewController") } + /// Data and Storage + internal static var telegramDataAndStorageViewController: String { return L10n.tr("Localizable", "Telegram.DataAndStorageViewController") } /// Trending Stickers - case telegramFeaturedStickerPacksController + internal static var telegramFeaturedStickerPacksController: String { return L10n.tr("Localizable", "Telegram.FeaturedStickerPacksController") } + /// Forward Messages + internal static var telegramForwardChatListController: String { return L10n.tr("Localizable", "Telegram.ForwardChatListController") } /// General Settings - case telegramGeneralSettingsViewController + internal static var telegramGeneralSettingsViewController: String { return L10n.tr("Localizable", "Telegram.GeneralSettingsViewController") } /// Admins - case telegramGroupAdminsController + internal static var telegramGroupAdminsController: String { return L10n.tr("Localizable", "Telegram.GroupAdminsController") } /// Groups In Common - case telegramGroupsInCommonViewController + internal static var telegramGroupsInCommonViewController: String { return L10n.tr("Localizable", "Telegram.GroupsInCommonViewController") } /// Group Sticker Set - case telegramGroupStickerSetController + internal static var telegramGroupStickerSetController: String { return L10n.tr("Localizable", "Telegram.GroupStickerSetController") } /// Stickers - case telegramInstalledStickerPacksController + internal static var telegramInstalledStickerPacksController: String { return L10n.tr("Localizable", "Telegram.InstalledStickerPacksController") } /// Language - case telegramLanguageViewController + internal static var telegramLanguageViewController: String { return L10n.tr("Localizable", "Telegram.LanguageViewController") } /// Settings - case telegramLayoutAccountController + internal static var telegramLayoutAccountController: String { return L10n.tr("Localizable", "Telegram.LayoutAccountController") } /// Recent Calls - case telegramLayoutRecentCallsViewController + internal static var telegramLayoutRecentCallsViewController: String { return L10n.tr("Localizable", "Telegram.LayoutRecentCallsViewController") } /// Invite Link - case telegramLinkInvationController - /// - case telegramMainViewController + internal static var telegramLinkInvationController: String { return L10n.tr("Localizable", "Telegram.LinkInvationController") } /// Notifications - case telegramNotificationSettingsViewController + internal static var telegramNotificationSettingsViewController: String { return L10n.tr("Localizable", "Telegram.NotificationSettingsViewController") } /// Passcode - case telegramPasscodeSettingsViewController + internal static var telegramPasscodeSettingsViewController: String { return L10n.tr("Localizable", "Telegram.PasscodeSettingsViewController") } + /// Passport + internal static var telegramPassportController: String { return L10n.tr("Localizable", "Telegram.PassportController") } /// Info - case telegramPeerInfoController + internal static var telegramPeerInfoController: String { return L10n.tr("Localizable", "Telegram.PeerInfoController") } + /// Shared Media + internal static var telegramPeerMediaController: String { return L10n.tr("Localizable", "Telegram.PeerMediaController") } /// Change Number - case telegramPhoneNumberConfirmController - /// Change Phone Number - case telegramPhoneNumberIntroController + internal static var telegramPhoneNumberConfirmController: String { return L10n.tr("Localizable", "Telegram.PhoneNumberConfirmController") } /// Chat History Settings - case telegramPreHistorySettingsController + internal static var telegramPreHistorySettingsController: String { return L10n.tr("Localizable", "Telegram.PreHistorySettingsController") } /// Privacy and Security - case telegramPrivacyAndSecurityViewController + internal static var telegramPrivacyAndSecurityViewController: String { return L10n.tr("Localizable", "Telegram.PrivacyAndSecurityViewController") } /// Proxy - case telegramProxySettingsViewController + internal static var telegramProxySettingsViewController: String { return L10n.tr("Localizable", "Telegram.ProxySettingsViewController") } /// Active Sessions - case telegramRecentSessionsController + internal static var telegramRecentSessionsController: String { return L10n.tr("Localizable", "Telegram.RecentSessionsController") } /// Encryption Key - case telegramSecretChatKeyViewController + internal static var telegramSecretChatKeyViewController: String { return L10n.tr("Localizable", "Telegram.SecretChatKeyViewController") } /// Select Users - case telegramSelectPeersController + internal static var telegramSelectPeersController: String { return L10n.tr("Localizable", "Telegram.SelectPeersController") } /// Storage Usage - case telegramStorageUsageController + internal static var telegramStorageUsageController: String { return L10n.tr("Localizable", "Telegram.StorageUsageController") } /// Two-Step Verification - case telegramTwoStepVerificationUnlockController + internal static var telegramTwoStepVerificationUnlockController: String { return L10n.tr("Localizable", "Telegram.TwoStepVerificationUnlockController") } /// Username - case telegramUsernameSettingsViewController - /// Copy - case textCopy - /// Make Bold - case textViewTransformBold - /// Make Monospace - case textViewTransformCode - /// Make Italic - case textViewTransformItalic + internal static var telegramUsernameSettingsViewController: String { return L10n.tr("Localizable", "Telegram.UsernameSettingsViewController") } + /// Logged in with Telegram + internal static var telegramWebSessionsController: String { return L10n.tr("Localizable", "Telegram.WebSessionsController") } + /// Telegram needs to optimize its database after this update. This may take a few minutes, sorry for the inconvenience. + internal static var telegramUpgradeDatabaseText: String { return L10n.tr("Localizable", "Telegram.UpgradeDatabase.Text") } + /// Optimizing Database + internal static var telegramUpgradeDatabaseTitle: String { return L10n.tr("Localizable", "Telegram.UpgradeDatabase.Title") } + /// Agree & Continue + internal static var termsOfServiceAccept: String { return L10n.tr("Localizable", "TermsOfService.Accept") } + /// I confirm that I am %@ or over. + internal static func termsOfServiceConfirmAge(_ p1: String) -> String { + return L10n.tr("Localizable", "TermsOfService.ConfirmAge", p1) + } + /// Decline + internal static var termsOfServiceDisagree: String { return L10n.tr("Localizable", "TermsOfService.Disagree") } + /// Please agree and proceed to %@. + internal static func termsOfServiceProceedBot(_ p1: String) -> String { + return L10n.tr("Localizable", "TermsOfService.ProceedBot", p1) + } + /// Terms of Service + internal static var termsOfServiceTitle: String { return L10n.tr("Localizable", "TermsOfService.Title") } + /// Confirm + internal static var termsOfServiceAcceptConfirmAge: String { return L10n.tr("Localizable", "TermsOfService.Accept.ConfirmAge") } + /// Decline & Deactivate + internal static var termsOfServiceDisagreeOK: String { return L10n.tr("Localizable", "TermsOfService.Disagree.OK") } + /// We're very sorry, but this means we must part ways here. Unlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data (e.g., delete synced contacts) in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest requirements, it won't be possible for us to provide you with this service. + internal static var termsOfServiceDisagreeText: String { return L10n.tr("Localizable", "TermsOfService.Disagree.Text") } + /// Warning, this will irreversibly delete your Telegram account along with all the data you store in the Telegram cloud.\n\nImportant: You can Cancel now and export your data before deleting your account instead of losing it all. (To do this, open the latest version of Telegram Desktop and go to Settings > Export Telegram Data.) + internal static var termsOfServiceDisagreeTextLast: String { return L10n.tr("Localizable", "TermsOfService.Disagree.Text.Last") } + /// Delete Now + internal static var termsOfServiceDisagreeTextLastOK: String { return L10n.tr("Localizable", "TermsOfService.Disagree.Text.Last.OK") } + /// Copy Selected Text + internal static var textCopy: String { return L10n.tr("Localizable", "Text.Copy") } + /// Copy %@ + internal static func textCopyLabel(_ p1: String) -> String { + return L10n.tr("Localizable", "Text.CopyLabel", p1) + } + /// Copy Text + internal static var textCopyText: String { return L10n.tr("Localizable", "Text.CopyText") } + /// Copy Code + internal static var textContextCopyCode: String { return L10n.tr("Localizable", "Text.Context.Copy.Code") } + /// Copy Command + internal static var textContextCopyCommand: String { return L10n.tr("Localizable", "Text.Context.Copy.Command") } + /// Copy Email + internal static var textContextCopyEmail: String { return L10n.tr("Localizable", "Text.Context.Copy.Email") } + /// Copy Hashtag + internal static var textContextCopyHashtag: String { return L10n.tr("Localizable", "Text.Context.Copy.Hashtag") } + /// Copy Invite Link + internal static var textContextCopyInviteLink: String { return L10n.tr("Localizable", "Text.Context.Copy.InviteLink") } + /// Copy Link + internal static var textContextCopyLink: String { return L10n.tr("Localizable", "Text.Context.Copy.Link") } + /// Copy Sticker Pack + internal static var textContextCopyStickerPack: String { return L10n.tr("Localizable", "Text.Context.Copy.StickerPack") } + /// Copy Username + internal static var textContextCopyUsername: String { return L10n.tr("Localizable", "Text.Context.Copy.Username") } + /// Transformations + internal static var textViewTransformations: String { return L10n.tr("Localizable", "Text.View.Transformations") } + /// Bold + internal static var textViewTransformBold: String { return L10n.tr("Localizable", "TextView.Transform.Bold") } + /// Monospace + internal static var textViewTransformCode: String { return L10n.tr("Localizable", "TextView.Transform.Code") } + /// Italic + internal static var textViewTransformItalic: String { return L10n.tr("Localizable", "TextView.Transform.Italic") } + /// Clear Transformations + internal static var textViewTransformRemoveAll: String { return L10n.tr("Localizable", "TextView.Transform.RemoveAll") } + /// Make URL + internal static var textViewTransformURL: String { return L10n.tr("Localizable", "TextView.Transform.URL") } + /// Sorry, this theme doesn't seem to exist for macOS. + internal static var themeGetThemeError: String { return L10n.tr("Localizable", "Theme.GetTheme.Error") } + /// Theme Preview + internal static var themePreviewTitle: String { return L10n.tr("Localizable", "ThemePreview.Title") } + /// %d + internal static func themePreviewUsesCountCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_countable", p1) + } + /// %d people are using this theme + internal static func themePreviewUsesCountFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_few", p1) + } + /// %d people are using this theme + internal static func themePreviewUsesCountMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_many", p1) + } + /// %d person is using this theme + internal static func themePreviewUsesCountOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_one", p1) + } + /// %d people are using this theme + internal static func themePreviewUsesCountOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_other", p1) + } + /// %d people are using this theme + internal static func themePreviewUsesCountTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_two", p1) + } + /// %d person is using this theme + internal static func themePreviewUsesCountZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "ThemePreview.UsesCount_zero", p1) + } /// at - case timeAt + internal static var timeAt: String { return L10n.tr("Localizable", "Time.at") } /// last seen - case timeLastSeen + internal static var timeLastSeen: String { return L10n.tr("Localizable", "Time.last_seen") } + /// Jan %@, %@ at %@ + internal static func timePreciseDateM1(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m1", p1, p2, p3) + } + /// Oct %@, %@ at %@ + internal static func timePreciseDateM10(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m10", p1, p2, p3) + } + /// Nov %@, %@ at %@ + internal static func timePreciseDateM11(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m11", p1, p2, p3) + } + /// Dec %@, %@ at %@ + internal static func timePreciseDateM12(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m12", p1, p2, p3) + } + /// Feb %@, %@ at %@ + internal static func timePreciseDateM2(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m2", p1, p2, p3) + } + /// Mar %@, %@ at %@ + internal static func timePreciseDateM3(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m3", p1, p2, p3) + } + /// Apr %@, %@ at %@ + internal static func timePreciseDateM4(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m4", p1, p2, p3) + } + /// May %@, %@ at %@ + internal static func timePreciseDateM5(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m5", p1, p2, p3) + } + /// Jun %@, %@ at %@ + internal static func timePreciseDateM6(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m6", p1, p2, p3) + } + /// Jul %@, %@ at %@ + internal static func timePreciseDateM7(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m7", p1, p2, p3) + } + /// Aug %@, %@ at %@ + internal static func timePreciseDateM8(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m8", p1, p2, p3) + } + /// Sep %@, %@ at %@ + internal static func timePreciseDateM9(_ p1: String, _ p2: String, _ p3: String) -> String { + return L10n.tr("Localizable", "Time.PreciseDate_m9", p1, p2, p3) + } /// today - case timeToday + internal static var timeToday: String { return L10n.tr("Localizable", "Time.today") } + /// today at %@ + internal static func timeTodayAt(_ p1: String) -> String { + return L10n.tr("Localizable", "Time.TodayAt", p1) + } /// yesterday - case timeYesterday + internal static var timeYesterday: String { return L10n.tr("Localizable", "Time.yesterday") } + /// yesterday at %@ + internal static func timeYesterdayAt(_ p1: String) -> String { + return L10n.tr("Localizable", "Time.YesterdayAt", p1) + } /// %d - case timerDaysCountable(Int) + internal static func timerDaysCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_countable", p1) + } /// %d days - case timerDaysFew(Int) + internal static func timerDaysFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_few", p1) + } /// %d days - case timerDaysMany(Int) + internal static func timerDaysMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_many", p1) + } /// %d day - case timerDaysOne(Int) + internal static func timerDaysOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_one", p1) + } /// %d days - case timerDaysOther(Int) + internal static func timerDaysOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_other", p1) + } /// %d days - case timerDaysTwo(Int) + internal static func timerDaysTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_two", p1) + } /// %d days - case timerDaysZero(Int) + internal static func timerDaysZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Days_zero", p1) + } /// Forever - case timerForever + internal static var timerForever: String { return L10n.tr("Localizable", "Timer.Forever") } /// %d - case timerHoursCountable(Int) + internal static func timerHoursCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_countable", p1) + } /// %d hours - case timerHoursFew(Int) + internal static func timerHoursFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_few", p1) + } /// %d hours - case timerHoursMany(Int) + internal static func timerHoursMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_many", p1) + } /// %d hour - case timerHoursOne(Int) + internal static func timerHoursOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_one", p1) + } /// %d hours - case timerHoursOther(Int) + internal static func timerHoursOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_other", p1) + } /// %d hours - case timerHoursTwo(Int) + internal static func timerHoursTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_two", p1) + } /// %d hours - case timerHoursZero(Int) + internal static func timerHoursZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Hours_zero", p1) + } /// %d - case timerMinutesCountable(Int) + internal static func timerMinutesCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_countable", p1) + } /// %d minutes - case timerMinutesFew(Int) + internal static func timerMinutesFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_few", p1) + } /// %d minutes - case timerMinutesMany(Int) + internal static func timerMinutesMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_many", p1) + } /// %d minute - case timerMinutesOne(Int) + internal static func timerMinutesOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_one", p1) + } /// %d minutes - case timerMinutesOther(Int) + internal static func timerMinutesOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_other", p1) + } /// %d minutes - case timerMinutesTwo(Int) + internal static func timerMinutesTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_two", p1) + } /// %d minutes - case timerMinutesZero(Int) + internal static func timerMinutesZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Minutes_zero", p1) + } /// %d - case timerMonthsCountable(Int) + internal static func timerMonthsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_countable", p1) + } /// %d months - case timerMonthsFew(Int) + internal static func timerMonthsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_few", p1) + } /// %d months - case timerMonthsMany(Int) + internal static func timerMonthsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_many", p1) + } /// %d month - case timerMonthsOne(Int) + internal static func timerMonthsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_one", p1) + } /// %d months - case timerMonthsOther(Int) + internal static func timerMonthsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_other", p1) + } /// %d months - case timerMonthsTwo(Int) + internal static func timerMonthsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_two", p1) + } /// %d months - case timerMonthsZero(Int) + internal static func timerMonthsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Months_zero", p1) + } /// %d - case timerSecondsCountable(Int) + internal static func timerSecondsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_countable", p1) + } /// %d seconds - case timerSecondsFew(Int) + internal static func timerSecondsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_few", p1) + } /// %d seconds - case timerSecondsMany(Int) + internal static func timerSecondsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_many", p1) + } /// %d second - case timerSecondsOne(Int) + internal static func timerSecondsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_one", p1) + } /// %d seconds - case timerSecondsOther(Int) + internal static func timerSecondsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_other", p1) + } /// %d seconds - case timerSecondsTwo(Int) + internal static func timerSecondsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_two", p1) + } /// %d seconds - case timerSecondsZero(Int) + internal static func timerSecondsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Seconds_zero", p1) + } /// %d - case timerWeeksCountable(Int) + internal static func timerWeeksCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_countable", p1) + } /// %d weeks - case timerWeeksFew(Int) + internal static func timerWeeksFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_few", p1) + } /// %d weeks - case timerWeeksMany(Int) + internal static func timerWeeksMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_many", p1) + } /// %d week - case timerWeeksOne(Int) + internal static func timerWeeksOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_one", p1) + } /// %d weeks - case timerWeeksOther(Int) + internal static func timerWeeksOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_other", p1) + } /// %d weeks - case timerWeeksTwo(Int) + internal static func timerWeeksTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_two", p1) + } /// %d weeks - case timerWeeksZero(Int) + internal static func timerWeeksZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Weeks_zero", p1) + } /// %d - case timerYearsCountable(Int) + internal static func timerYearsCountable(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_countable", p1) + } /// %d years - case timerYearsFew(Int) + internal static func timerYearsFew(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_few", p1) + } /// %d years - case timerYearsMany(Int) + internal static func timerYearsMany(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_many", p1) + } /// %d year - case timerYearsOne(Int) + internal static func timerYearsOne(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_one", p1) + } /// %d years - case timerYearsOther(Int) + internal static func timerYearsOther(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_other", p1) + } /// %d years - case timerYearsTwo(Int) + internal static func timerYearsTwo(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_two", p1) + } /// %d years - case timerYearsZero(Int) - /// Data Detectors - case tRrPd1PSTitle + internal static func timerYearsZero(_ p1: Int) -> String { + return L10n.tr("Localizable", "Timer.Years_zero", p1) + } + /// Attach + internal static var touchBarAttach: String { return L10n.tr("Localizable", "TouchBar.Attach") } + /// Call + internal static var touchBarCall: String { return L10n.tr("Localizable", "TouchBar.Call") } + /// Favorite + internal static var touchBarFavorite: String { return L10n.tr("Localizable", "TouchBar.Favorite") } + /// Recent + internal static var touchBarRecent: String { return L10n.tr("Localizable", "TouchBar.Recent") } + /// Recently Used + internal static var touchBarRecentlyUsed: String { return L10n.tr("Localizable", "TouchBar.RecentlyUsed") } + /// Search for messages or users + internal static var touchBarSearchUsersOrMessages: String { return L10n.tr("Localizable", "TouchBar.SearchUsersOrMessages") } + /// Start Secret Chat + internal static var touchBarStartSecretChat: String { return L10n.tr("Localizable", "TouchBar.StartSecretChat") } + /// Replace with File + internal static var touchBarEditMessageReplaceWithFile: String { return L10n.tr("Localizable", "TouchBar.EditMessage.ReplaceWithFile") } + /// Replace with Media + internal static var touchBarEditMessageReplaceWithMedia: String { return L10n.tr("Localizable", "TouchBar.EditMessage.ReplaceWithMedia") } + /// Chat Actions + internal static var touchBarLabelChatActions: String { return L10n.tr("Localizable", "TouchBarLabel.ChatActions") } + /// Emoji & Stickers + internal static var touchBarLabelEmojiAndStickers: String { return L10n.tr("Localizable", "TouchBarLabel.EmojiAndStickers") } + /// New Chat + internal static var touchBarLabelNewChat: String { return L10n.tr("Localizable", "TouchBarLabel.NewChat") } /// Skip - case twoStepAuthEmailSkip + internal static var twoStepAuthEmailSkip: String { return L10n.tr("Localizable", "TwoStep.AuthEmailSkip") } /// An error occured. Please try again later. - case twoStepAuthAnError + internal static var twoStepAuthAnError: String { return L10n.tr("Localizable", "TwoStepAuth.AnError") } /// Change Recovery E-Mail - case twoStepAuthChangeEmail + internal static var twoStepAuthChangeEmail: String { return L10n.tr("Localizable", "TwoStepAuth.ChangeEmail") } /// Change Password - case twoStepAuthChangePassword + internal static var twoStepAuthChangePassword: String { return L10n.tr("Localizable", "TwoStepAuth.ChangePassword") } + /// Please enter a new password which will be used to protect your data. + internal static var twoStepAuthChangePasswordDesc: String { return L10n.tr("Localizable", "TwoStepAuth.ChangePasswordDesc") } /// Abort Two-Step Verification Setup - case twoStepAuthConfirmationAbort - /// Please check your e-mail and click on the validation link to complete Two-Step Verification setup. Be sure to check the spam folder as well. - case twoStepAuthConfirmationText + internal static var twoStepAuthConfirmationAbort: String { return L10n.tr("Localizable", "TwoStepAuth.ConfirmationAbort") } + /// Please check your e-mail and enter the confirmation code to complete Two-Step Verification setup. Be sure to check the spam folder as well. + internal static var twoStepAuthConfirmationTextNew: String { return L10n.tr("Localizable", "TwoStepAuth.ConfirmationTextNew") } + /// Please enter the code we've just emailed at %@. + internal static func twoStepAuthConfirmEmailCodeDesc(_ p1: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.ConfirmEmailCodeDesc", p1) + } /// E-Mail - case twoStepAuthEmail + internal static var twoStepAuthEmail: String { return L10n.tr("Localizable", "TwoStepAuth.Email") } + /// This confirmation code has expired. Please try again. + internal static var twoStepAuthEmailCodeExpired: String { return L10n.tr("Localizable", "TwoStepAuth.EmailCodeExpired") } + /// You have entered an invalid code. Please try again. + internal static var twoStepAuthEmailCodeInvalid: String { return L10n.tr("Localizable", "TwoStepAuth.EmailCodeInvalid") } /// Please add your valid e-mail. It is the only way to recover a forgotten password. - case twoStepAuthEmailHelp + internal static var twoStepAuthEmailHelp: String { return L10n.tr("Localizable", "TwoStepAuth.EmailHelp") } + /// Please enter your new recovery email. It is the only way to recover a forgotten password. + internal static var twoStepAuthEmailHelpChange: String { return L10n.tr("Localizable", "TwoStepAuth.EmailHelpChange") } /// Invalid e-mail address. Please try again. - case twoStepAuthEmailInvalid + internal static var twoStepAuthEmailInvalid: String { return L10n.tr("Localizable", "TwoStepAuth.EmailInvalid") } /// We have sent you an e-mail to confirm your address. - case twoStepAuthEmailSent + internal static var twoStepAuthEmailSent: String { return L10n.tr("Localizable", "TwoStepAuth.EmailSent") } /// No, seriously.\n\nIf you forget your password, you will lose access to your Telegram account. There will be no way to restore it. - case twoStepAuthEmailSkipAlert + internal static var twoStepAuthEmailSkipAlert: String { return L10n.tr("Localizable", "TwoStepAuth.EmailSkipAlert") } + /// Enter Code + internal static var twoStepAuthEnterEmailCode: String { return L10n.tr("Localizable", "TwoStepAuth.EnterEmailCode") } /// Forgot password? - case twoStepAuthEnterPasswordForgot + internal static var twoStepAuthEnterPasswordForgot: String { return L10n.tr("Localizable", "TwoStepAuth.EnterPasswordForgot") } /// You have enabled Two-Step Verification, so your account is protected with an additional password. - case twoStepAuthEnterPasswordHelp + internal static var twoStepAuthEnterPasswordHelp: String { return L10n.tr("Localizable", "TwoStepAuth.EnterPasswordHelp") } /// Hint: %@ - case twoStepAuthEnterPasswordHint(String) + internal static func twoStepAuthEnterPasswordHint(_ p1: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.EnterPasswordHint", p1) + } /// Password - case twoStepAuthEnterPasswordPassword + internal static var twoStepAuthEnterPasswordPassword: String { return L10n.tr("Localizable", "TwoStepAuth.EnterPasswordPassword") } /// Limit exceeded. Please try again later. - case twoStepAuthFloodError + internal static var twoStepAuthFloodError: String { return L10n.tr("Localizable", "TwoStepAuth.FloodError") } /// An error occurred. Please try again later. - case twoStepAuthGenericError + internal static var twoStepAuthGenericError: String { return L10n.tr("Localizable", "TwoStepAuth.GenericError") } /// You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account. - case twoStepAuthGenericHelp + internal static var twoStepAuthGenericHelp: String { return L10n.tr("Localizable", "TwoStepAuth.GenericHelp") } + /// Invalid password. Please try again. + internal static var twoStepAuthInvalidPasswordError: String { return L10n.tr("Localizable", "TwoStepAuth.InvalidPasswordError") } /// Password - case twoStepAuthPasswordTitle + internal static var twoStepAuthPasswordTitle: String { return L10n.tr("Localizable", "TwoStepAuth.PasswordTitle") } /// Your recovery e-mail %@ is not yet active and pending confirmation. - case twoStepAuthPendingEmailHelp(String) + internal static func twoStepAuthPendingEmailHelp(_ p1: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.PendingEmailHelp", p1) + } /// Code - case twoStepAuthRecoveryCode + internal static var twoStepAuthRecoveryCode: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryCode") } /// Code Expired - case twoStepAuthRecoveryCodeExpired + internal static var twoStepAuthRecoveryCodeExpired: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryCodeExpired") } /// Please check your e-mail and enter the 6-digit code we've sent there to deactivate your cloud password. - case twoStepAuthRecoveryCodeHelp + internal static var twoStepAuthRecoveryCodeHelp: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryCodeHelp") } /// Invalid code. Please try again. - case twoStepAuthRecoveryCodeInvalid - /// Having trouble accessing your e-mail %@? - case twoStepAuthRecoveryEmailUnavailable(String) + internal static var twoStepAuthRecoveryCodeInvalid: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryCodeInvalid") } + /// Having trouble accessing your e-mail\n[%@]()? + internal static func twoStepAuthRecoveryEmailUnavailableNew(_ p1: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.RecoveryEmailUnavailableNew", p1) + } /// Your remaining options are either to remember your password or to reset your account. - case twoStepAuthRecoveryFailed + internal static var twoStepAuthRecoveryFailed: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryFailed") } /// We have sent a recovery code to the e-mail you provided:\n\n%@ - case twoStepAuthRecoverySent(String) + internal static func twoStepAuthRecoverySent(_ p1: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.RecoverySent", p1) + } /// E-Mail Code - case twoStepAuthRecoveryTitle + internal static var twoStepAuthRecoveryTitle: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryTitle") } /// Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account. - case twoStepAuthRecoveryUnavailable + internal static var twoStepAuthRecoveryUnavailable: String { return L10n.tr("Localizable", "TwoStepAuth.RecoveryUnavailable") } /// Turn Password Off - case twoStepAuthRemovePassword + internal static var twoStepAuthRemovePassword: String { return L10n.tr("Localizable", "TwoStepAuth.RemovePassword") } + /// Since the account **%@** is active and protected by a password, we will delete it in 1 week for security purposes.\n\nYou can cancel this process at any time.\n\nYou'll be able to reset your account in:\n%@ + internal static func twoStepAuthResetDescription(_ p1: String, _ p2: String) -> String { + return L10n.tr("Localizable", "TwoStepAuth.ResetDescription", p1, p2) + } /// Set Additional Password - case twoStepAuthSetPassword + internal static var twoStepAuthSetPassword: String { return L10n.tr("Localizable", "TwoStepAuth.SetPassword") } /// You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS. - case twoStepAuthSetPasswordHelp + internal static var twoStepAuthSetPasswordHelp: String { return L10n.tr("Localizable", "TwoStepAuth.SetPasswordHelp") } /// Set Recovery E-Mail - case twoStepAuthSetupEmail + internal static var twoStepAuthSetupEmail: String { return L10n.tr("Localizable", "TwoStepAuth.SetupEmail") } /// Recovery E-Mail - case twoStepAuthSetupEmailTitle - /// Please create a hint for your password - case twoStepAuthSetupHint + internal static var twoStepAuthSetupEmailTitle: String { return L10n.tr("Localizable", "TwoStepAuth.SetupEmailTitle") } + /// You can create an optional hint for your password. + internal static var twoStepAuthSetupHintDesc: String { return L10n.tr("Localizable", "TwoStepAuth.SetupHintDesc") } + /// Hint + internal static var twoStepAuthSetupHintPlaceholder: String { return L10n.tr("Localizable", "TwoStepAuth.SetupHintPlaceholder") } /// Password Hint - case twoStepAuthSetupHintTitle + internal static var twoStepAuthSetupHintTitle: String { return L10n.tr("Localizable", "TwoStepAuth.SetupHintTitle") } /// Passwords don't match. Please try again. - case twoStepAuthSetupPasswordConfirmFailed - /// Please re-enter your password - case twoStepAuthSetupPasswordConfirmPassword - /// Enter a password - case twoStepAuthSetupPasswordEnterPassword - /// Please enter your new password - case twoStepAuthSetupPasswordEnterPasswordNew + internal static var twoStepAuthSetupPasswordConfirmFailed: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordConfirmFailed") } + /// Re-enter your password + internal static var twoStepAuthSetupPasswordConfirmPassword: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordConfirmPassword") } + /// Please confirm your password. + internal static var twoStepAuthSetupPasswordConfirmPasswordDesc: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordConfirmPasswordDesc") } + /// Please create a password which will be used to protect your data. + internal static var twoStepAuthSetupPasswordDesc: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordDesc") } + /// Enter your cloud password + internal static var twoStepAuthSetupPasswordEnterPassword: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordEnterPassword") } + /// Enter your new password + internal static var twoStepAuthSetupPasswordEnterPasswordNew: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordEnterPasswordNew") } /// Your Password - case twoStepAuthSetupPasswordTitle + internal static var twoStepAuthSetupPasswordTitle: String { return L10n.tr("Localizable", "TwoStepAuth.SetupPasswordTitle") } /// Are you sure you want to disable your password? - case twoStepAuthConfirmDisablePassword + internal static var twoStepAuthConfirmDisablePassword: String { return L10n.tr("Localizable", "TwoStepAuth.Confirm.DisablePassword") } /// An error occured. Please try again later. - case twoStepAuthErrorGeneric + internal static var twoStepAuthErrorGeneric: String { return L10n.tr("Localizable", "TwoStepAuth.Error.Generic") } /// Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account. - case twoStepAuthErrorHaventEmail + internal static var twoStepAuthErrorHaventEmail: String { return L10n.tr("Localizable", "TwoStepAuth.Error.HaventEmail") } /// Please enter valid e-mail address. - case twoStepAuthErrorInvalidEmail - /// You have entered invalid password too many times. Please try again later. - case twoStepAuthErrorLimitExceeded + internal static var twoStepAuthErrorInvalidEmail: String { return L10n.tr("Localizable", "TwoStepAuth.Error.InvalidEmail") } + /// You have entered an invalid password too many times. Please try again later. + internal static var twoStepAuthErrorLimitExceeded: String { return L10n.tr("Localizable", "TwoStepAuth.Error.LimitExceeded") } /// Passwords don't match.\nPlease try again. - case twoStepAuthErrorPasswordsDontMatch + internal static var twoStepAuthErrorPasswordsDontMatch: String { return L10n.tr("Localizable", "TwoStepAuth.Error.PasswordsDontMatch") } /// Capitalize - case uezBsLqGTitle - /// Telegram - case uQyDDJDrTitle - /// Cut - case uRlIYUnGTitle + internal static var uezBsLqGTitle: String { return L10n.tr("Localizable", "UEZ-Bs-lqG.title") } + /// Update Telegram + internal static var updateUpdateTelegram: String { return L10n.tr("Localizable", "Update.UpdateTelegram") } + /// Telegram Update + internal static var updateAppTelegramUpdate: String { return L10n.tr("Localizable", "UpdateApp.TelegramUpdate") } + /// Update Telegram + internal static var updateAppUpdateTelegram: String { return L10n.tr("Localizable", "UpdateApp.UpdateTelegram") } + /// Sorry, you are a member of too many groups and channels. For technical reasons, you need to leave some first before changing this setting in your groups. + internal static var upgradeChannelsTooMuch: String { return L10n.tr("Localizable", "Upgrade.ChannelsTooMuch") } /// %@ is available - case usernameSettingsAvailable(String) + internal static func usernameSettingsAvailable(_ p1: String) -> String { + return L10n.tr("Localizable", "UsernameSettings.available", p1) + } /// You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters. - case usernameSettingsChangeDescription + internal static var usernameSettingsChangeDescription: String { return L10n.tr("Localizable", "UsernameSettings.ChangeDescription") } /// Done - case usernameSettingsDone + internal static var usernameSettingsDone: String { return L10n.tr("Localizable", "UsernameSettings.Done") } /// Enter your username - case usernameSettingsInputPlaceholder + internal static var usernameSettingsInputPlaceholder: String { return L10n.tr("Localizable", "UsernameSettings.InputPlaceholder") } /// Hide Others - case vdrFpXzOTitle - /// Make Upper Case - case vmV6d7jITitle + internal static var vdrFpXzOTitle: String { return L10n.tr("Localizable", "Vdr-fp-XzO.title") } + /// Cancel + internal static var videoAvatarButtonCancel: String { return L10n.tr("Localizable", "VideoAvatar.Button.Cancel") } + /// Set + internal static var videoAvatarButtonSet: String { return L10n.tr("Localizable", "VideoAvatar.Button.Set") } + /// Choose a cover for channel video + internal static var videoAvatarChooseDescChannel: String { return L10n.tr("Localizable", "VideoAvatar.ChooseDesc.Channel") } + /// Choose a cover for group video + internal static var videoAvatarChooseDescGroup: String { return L10n.tr("Localizable", "VideoAvatar.ChooseDesc.Group") } + /// Choose a cover for your profile video + internal static var videoAvatarChooseDescProfile: String { return L10n.tr("Localizable", "VideoAvatar.ChooseDesc.Profile") } /// Edit - case w486f4DlTitle + internal static var w486f4DlTitle: String { return L10n.tr("Localizable", "W48-6f-4Dl.title") } + /// Apply + internal static var wallpaperPreviewApply: String { return L10n.tr("Localizable", "WallpaperPreview.Apply") } + /// Blurred + internal static var wallpaperPreviewBlurred: String { return L10n.tr("Localizable", "WallpaperPreview.Blurred") } + /// Sorry, this background doesn't seem to exist. + internal static var wallpaperPreviewDoesntExists: String { return L10n.tr("Localizable", "WallpaperPreview.DoesntExists") } + /// Background Preview + internal static var wallpaperPreviewHeader: String { return L10n.tr("Localizable", "WallpaperPreview.Header") } + /// Paste and Match Style + internal static var weT3VZwkTitle: String { return L10n.tr("Localizable", "WeT-3V-zwk.title") } + /// Disconnect + internal static var webAuthorizationsLogout: String { return L10n.tr("Localizable", "WebAuthorizations.Logout") } + /// Disconnect All Websites + internal static var webAuthorizationsLogoutAll: String { return L10n.tr("Localizable", "WebAuthorizations.LogoutAll") } + /// Do you want to disconnect this website? + internal static var webAuthorizationsConfirmRevoke: String { return L10n.tr("Localizable", "WebAuthorizations.Confirm.Revoke") } + /// Are you sure you want to disconnect all websites? + internal static var webAuthorizationsConfirmRevokeAll: String { return L10n.tr("Localizable", "WebAuthorizations.Confirm.RevokeAll") } + /// CONNECTED WEBSITES + internal static var webAuthorizationsLoggedInDescrpiption: String { return L10n.tr("Localizable", "WebAuthorizations.LoggedIn.Descrpiption") } + /// You can log in on websites that support signing in with Telegram. + internal static var webAuthorizationsLogoutAllDescription: String { return L10n.tr("Localizable", "WebAuthorizations.LogoutAll.Description") } /// Fri - case weekdayShortFriday + internal static var weekdayShortFriday: String { return L10n.tr("Localizable", "Weekday.ShortFriday") } /// Mon - case weekdayShortMonday + internal static var weekdayShortMonday: String { return L10n.tr("Localizable", "Weekday.ShortMonday") } /// Sat - case weekdayShortSaturday + internal static var weekdayShortSaturday: String { return L10n.tr("Localizable", "Weekday.ShortSaturday") } /// Sun - case weekdayShortSunday + internal static var weekdayShortSunday: String { return L10n.tr("Localizable", "Weekday.ShortSunday") } /// Thu - case weekdayShortThursday + internal static var weekdayShortThursday: String { return L10n.tr("Localizable", "Weekday.ShortThursday") } /// Tue - case weekdayShortTuesday + internal static var weekdayShortTuesday: String { return L10n.tr("Localizable", "Weekday.ShortTuesday") } /// Wed - case weekdayShortWednesday - /// Paste and Match Style - case weT3VZwkTitle - /// Copy - case x3vGGIWUTitle - /// Show Substitutions - case z6FFW3nzTitle + internal static var weekdayShortWednesday: String { return L10n.tr("Localizable", "Weekday.ShortWednesday") } + /// Edit + internal static var ns103Title: String { return L10n.tr("Localizable", "_NS103.title") } /// Window - case _NS138Title + internal static var ns167Title: String { return L10n.tr("Localizable", "_NS167.title") } /// View - case _NS70Title + internal static var ns70Title: String { return L10n.tr("Localizable", "_NS70.title") } + /// View + internal static var ns81Title: String { return L10n.tr("Localizable", "_NS81.title") } /// Edit - case _NS88Title -} -// swiftlint:enable type_body_length - -extension L10n: CustomStringConvertible { - var description: String { return self.string } - - var string: String { - switch self { - case .defaultSoundName: - return L10n.tr(key: "DefaultSoundName") - case .notificationSettingsToneNone: - return L10n.tr(key: "NotificationSettingsToneNone") - case .passwordHashInvalid: - return L10n.tr(key: "PASSWORD_HASH_INVALID") - case .phoneCodeExpired: - return L10n.tr(key: "PHONE_CODE_EXPIRED") - case .phoneCodeInvalid: - return L10n.tr(key: "PHONE_CODE_INVALID") - case .phoneNumberInvalid: - return L10n.tr(key: "PHONE_NUMBER_INVALID") - case .you: - return L10n.tr(key: "You") - case ._1000Title: - return L10n.tr(key: "1000.title") - case ._1XtHYUBwTitle: - return L10n.tr(key: "1Xt-HY-uBw.title") - case ._2oIRnZJCTitle: - return L10n.tr(key: "2oI-Rn-ZJC.title") - case ._4J7DPTxaTitle: - return L10n.tr(key: "4J7-dP-txa.title") - case ._4sb4sVLiTitle: - return L10n.tr(key: "4sb-4s-VLi.title") - case ._5kVVbQxSTitle: - return L10n.tr(key: "5kV-Vb-QxS.title") - case ._5QFOaP0TTitle: - return L10n.tr(key: "5QF-Oa-p0T.title") - case ._6dhZSVamTitle: - return L10n.tr(key: "6dh-zS-Vam.title") - case ._78YHA62vTitle: - return L10n.tr(key: "78Y-hA-62v.title") - case ._9icFLObxTitle: - return L10n.tr(key: "9ic-FL-obx.title") - case ._9yt4BNSMTitle: - return L10n.tr(key: "9yt-4B-nSM.title") - case .aboutDescription: - return L10n.tr(key: "About.Description") - case .accountConfirmAskQuestion: - return L10n.tr(key: "Account.Confirm.AskQuestion") - case .accountConfirmGoToFaq: - return L10n.tr(key: "Account.Confirm.GoToFaq") - case .accountConfirmLogout: - return L10n.tr(key: "Account.Confirm.Logout") - case .accountConfirmLogoutText: - return L10n.tr(key: "Account.Confirm.LogoutText") - case .accountsControllerNewAccount: - return L10n.tr(key: "AccountsController.NewAccount") - case .accountSettingsAbout: - return L10n.tr(key: "AccountSettings.About") - case .accountSettingsAppearance: - return L10n.tr(key: "AccountSettings.Appearance") - case .accountSettingsAskQuestion: - return L10n.tr(key: "AccountSettings.AskQuestion") - case .accountSettingsBio: - return L10n.tr(key: "AccountSettings.Bio") - case .accountSettingsCurrentLanguage: - return L10n.tr(key: "AccountSettings.CurrentLanguage") - case .accountSettingsFAQ: - return L10n.tr(key: "AccountSettings.FAQ") - case .accountSettingsGeneral: - return L10n.tr(key: "AccountSettings.General") - case .accountSettingsLanguage: - return L10n.tr(key: "AccountSettings.Language") - case .accountSettingsLogout: - return L10n.tr(key: "AccountSettings.Logout") - case .accountSettingsNotifications: - return L10n.tr(key: "AccountSettings.Notifications") - case .accountSettingsPrivacyAndSecurity: - return L10n.tr(key: "AccountSettings.PrivacyAndSecurity") - case .accountSettingsSetBio: - return L10n.tr(key: "AccountSettings.SetBio") - case .accountSettingsSetProfilePhoto: - return L10n.tr(key: "AccountSettings.SetProfilePhoto") - case .accountSettingsSetUsername: - return L10n.tr(key: "AccountSettings.SetUsername") - case .accountSettingsStickers: - return L10n.tr(key: "AccountSettings.Stickers") - case .accountSettingsStorage: - return L10n.tr(key: "AccountSettings.Storage") - case .accountSettingsUsername: - return L10n.tr(key: "AccountSettings.Username") - case .adminsAddAdmin: - return L10n.tr(key: "Admins.AddAdmin") - case .adminsAdmin: - return L10n.tr(key: "Admins.Admin") - case .adminsChannelAdmins: - return L10n.tr(key: "Admins.ChannelAdmins") - case .adminsChannelDescription: - return L10n.tr(key: "Admins.ChannelDescription") - case .adminsCreator: - return L10n.tr(key: "Admins.Creator") - case .adminsEverbodyCanAddMembers: - return L10n.tr(key: "Admins.EverbodyCanAddMembers") - case .adminsGroupAdmins: - return L10n.tr(key: "Admins.GroupAdmins") - case .adminsGroupDescription: - return L10n.tr(key: "Admins.GroupDescription") - case .adminsOnlyAdminsCanAddMembers: - return L10n.tr(key: "Admins.OnlyAdminsCanAddMembers") - case .adminsSelectNewAdminTitle: - return L10n.tr(key: "Admins.SelectNewAdminTitle") - case .adminsWhoCanInviteAdmins: - return L10n.tr(key: "Admins.WhoCanInvite.Admins") - case .adminsWhoCanInviteEveryone: - return L10n.tr(key: "Admins.WhoCanInvite.Everyone") - case .adminsWhoCanInviteText: - return L10n.tr(key: "Admins.WhoCanInvite.Text") - case .alertCancel: - return L10n.tr(key: "Alert.Cancel") - case .alertOK: - return L10n.tr(key: "Alert.OK") - case .alertUserDoesntExists: - return L10n.tr(key: "Alert.UserDoesntExists") - case .alertForwardError: - return L10n.tr(key: "Alert.Forward.Error") - case .alertSendErrorDelete: - return L10n.tr(key: "Alert.SendError.Delete") - case .alertSendErrorHeader: - return L10n.tr(key: "Alert.SendError.Header") - case .alertSendErrorIgnore: - return L10n.tr(key: "Alert.SendError.Ignore") - case .alertSendErrorResend: - return L10n.tr(key: "Alert.SendError.Resend") - case .alertSendErrorText: - return L10n.tr(key: "Alert.SendError.Text") - case .appMaxFileSize: - return L10n.tr(key: "App.MaxFileSize") - case .archivedStickersDescription: - return L10n.tr(key: "ArchivedStickers.Description") - case .audioUnknownArtist: - return L10n.tr(key: "Audio.UnknownArtist") - case .audioUntitledSong: - return L10n.tr(key: "Audio.UntitledSong") - case .audioControllerVideoMessage: - return L10n.tr(key: "AudioController.videoMessage") - case .audioControllerVoiceMessage: - return L10n.tr(key: "AudioController.voiceMessage") - case .audioRecordReleaseOut: - return L10n.tr(key: "AudioRecord.ReleaseOut") - case .aufd15bRTitle: - return L10n.tr(key: "aUF-d1-5bR.title") - case .bioDescription: - return L10n.tr(key: "Bio.Description") - case .bioPlaceholder: - return L10n.tr(key: "Bio.Placeholder") - case .bioSave: - return L10n.tr(key: "Bio.Save") - case .blockedPeersEmptyDescrpition: - return L10n.tr(key: "BlockedPeers.EmptyDescrpition") - case .bofnm1cWTitle: - return L10n.tr(key: "BOF-NM-1cW.title") - case .c8aY6VQdTitle: - return L10n.tr(key: "c8a-y6-VQd.title") - case .cagYXWT6Title: - return L10n.tr(key: "Cag-YX-WT6.title") - case .callParticipantVersionOutdatedError(let p1): - return L10n.tr(key: "Call.ParticipantVersionOutdatedError", p1) - case .callPrivacyErrorMessage(let p1): - return L10n.tr(key: "Call.PrivacyErrorMessage", p1) - case .callShortMinutesCountable(let p1): - return L10n.tr(key: "Call.ShortMinutes_countable", p1) - case .callShortMinutesFew(let p1): - return L10n.tr(key: "Call.ShortMinutes_few", p1) - case .callShortMinutesMany(let p1): - return L10n.tr(key: "Call.ShortMinutes_many", p1) - case .callShortMinutesOne(let p1): - return L10n.tr(key: "Call.ShortMinutes_one", p1) - case .callShortMinutesOther(let p1): - return L10n.tr(key: "Call.ShortMinutes_other", p1) - case .callShortMinutesTwo(let p1): - return L10n.tr(key: "Call.ShortMinutes_two", p1) - case .callShortMinutesZero(let p1): - return L10n.tr(key: "Call.ShortMinutes_zero", p1) - case .callShortSecondsCountable(let p1): - return L10n.tr(key: "Call.ShortSeconds_countable", p1) - case .callShortSecondsFew(let p1): - return L10n.tr(key: "Call.ShortSeconds_few", p1) - case .callShortSecondsMany(let p1): - return L10n.tr(key: "Call.ShortSeconds_many", p1) - case .callShortSecondsOne(let p1): - return L10n.tr(key: "Call.ShortSeconds_one", p1) - case .callShortSecondsOther(let p1): - return L10n.tr(key: "Call.ShortSeconds_other", p1) - case .callShortSecondsTwo(let p1): - return L10n.tr(key: "Call.ShortSeconds_two", p1) - case .callShortSecondsZero(let p1): - return L10n.tr(key: "Call.ShortSeconds_zero", p1) - case .callStatusBusy: - return L10n.tr(key: "Call.StatusBusy") - case .callStatusCalling: - return L10n.tr(key: "Call.StatusCalling") - case .callStatusConnecting: - return L10n.tr(key: "Call.StatusConnecting") - case .callStatusEnded: - return L10n.tr(key: "Call.StatusEnded") - case .callStatusFailed: - return L10n.tr(key: "Call.StatusFailed") - case .callStatusRequesting: - return L10n.tr(key: "Call.StatusRequesting") - case .callStatusRinging: - return L10n.tr(key: "Call.StatusRinging") - case .callUndefinedError: - return L10n.tr(key: "Call.UndefinedError") - case .callConfirmDiscardCurrentDescription(let p1, let p2): - return L10n.tr(key: "Call.Confirm.DiscardCurrent.Description", p1, p2) - case .callConfirmDiscardCurrentHeader: - return L10n.tr(key: "Call.Confirm.DiscardCurrent.Header") - case .callRatingModalPlaceholder: - return L10n.tr(key: "Call.RatingModal.Placeholder") - case .callRecentIncoming: - return L10n.tr(key: "Call.Recent.Incoming") - case .callRecentMissed: - return L10n.tr(key: "Call.Recent.Missed") - case .callRecentOutgoing: - return L10n.tr(key: "Call.Recent.Outgoing") - case .callHeaderEndCall: - return L10n.tr(key: "CallHeader.EndCall") - case .changeNumberConfirmCodeSuccess(let p1): - return L10n.tr(key: "ChangeNumber.ConfirmCode.Success", p1) - case .changeNumberConfirmCodeErrorCodeExpired: - return L10n.tr(key: "ChangeNumber.ConfirmCode.Error.codeExpired") - case .changeNumberConfirmCodeErrorGeneric: - return L10n.tr(key: "ChangeNumber.ConfirmCode.Error.Generic") - case .changeNumberConfirmCodeErrorInvalidCode: - return L10n.tr(key: "ChangeNumber.ConfirmCode.Error.invalidCode") - case .changeNumberConfirmCodeErrorLimitExceeded: - return L10n.tr(key: "ChangeNumber.ConfirmCode.Error.limitExceeded") - case .changeNumberSendDataErrorGeneric: - return L10n.tr(key: "ChangeNumber.SendData.Error.Generic") - case .changeNumberSendDataErrorInvalidPhoneNumber: - return L10n.tr(key: "ChangeNumber.SendData.Error.InvalidPhoneNumber") - case .changeNumberSendDataErrorLimitExceeded: - return L10n.tr(key: "ChangeNumber.SendData.Error.LimitExceeded") - case .changeNumberSendDataErrorPhoneNumberOccupied(let p1): - return L10n.tr(key: "ChangeNumber.SendData.Error.PhoneNumberOccupied", p1) - case .changePhoneNumberIntroAlert: - return L10n.tr(key: "ChangePhoneNumber.Intro.Alert") - case .changePhoneNumberIntroDescription: - return L10n.tr(key: "ChangePhoneNumber.Intro.Description") - case .channelBanForever: - return L10n.tr(key: "Channel.BanForever") - case .channelChannelNameHolder: - return L10n.tr(key: "Channel.ChannelNameHolder") - case .channelCreate: - return L10n.tr(key: "Channel.Create") - case .channelDescriptionHolder: - return L10n.tr(key: "Channel.DescriptionHolder") - case .channelDescriptionHolderDescrpiton: - return L10n.tr(key: "Channel.DescriptionHolderDescrpiton") - case .channelExportLinkAboutChannel: - return L10n.tr(key: "Channel.ExportLinkAboutChannel") - case .channelExportLinkAboutGroup: - return L10n.tr(key: "Channel.ExportLinkAboutGroup") - case .channelIntroDescription: - return L10n.tr(key: "Channel.IntroDescription") - case .channelIntroDescriptionHeader: - return L10n.tr(key: "Channel.IntroDescriptionHeader") - case .channelNewChannel: - return L10n.tr(key: "Channel.NewChannel") - case .channelPrivate: - return L10n.tr(key: "Channel.Private") - case .channelPrivateAboutChannel: - return L10n.tr(key: "Channel.PrivateAboutChannel") - case .channelPrivateAboutGroup: - return L10n.tr(key: "Channel.PrivateAboutGroup") - case .channelPublic: - return L10n.tr(key: "Channel.Public") - case .channelPublicAboutChannel: - return L10n.tr(key: "Channel.PublicAboutChannel") - case .channelPublicAboutGroup: - return L10n.tr(key: "Channel.PublicAboutGroup") - case .channelPublicNamesLimitError: - return L10n.tr(key: "Channel.PublicNamesLimitError") - case .channelTypeHeaderChannel: - return L10n.tr(key: "Channel.TypeHeaderChannel") - case .channelTypeHeaderGroup: - return L10n.tr(key: "Channel.TypeHeaderGroup") - case .channelUsernameAboutChannel: - return L10n.tr(key: "Channel.UsernameAboutChannel") - case .channelUsernameAboutGroup: - return L10n.tr(key: "Channel.UsernameAboutGroup") - case .channelUserRestriction: - return L10n.tr(key: "Channel.UserRestriction") - case .channelAdminAdminAccess: - return L10n.tr(key: "Channel.Admin.AdminAccess") - case .channelAdminAdminRestricted: - return L10n.tr(key: "Channel.Admin.AdminRestricted") - case .channelAdminCantEditRights: - return L10n.tr(key: "Channel.Admin.CantEditRights") - case .channelAdminDismiss: - return L10n.tr(key: "Channel.Admin.Dismiss") - case .channelAdminWhatCanAdminDo: - return L10n.tr(key: "Channel.Admin.WhatCanAdminDo") - case .channelAdminsAddAdminError: - return L10n.tr(key: "Channel.Admins.AddAdminError") - case .channelAdminsPromotedBy(let p1): - return L10n.tr(key: "Channel.Admins.PromotedBy", p1) - case .channelAdminsPromoteBannedAdminError: - return L10n.tr(key: "Channel.Admins.Promote.BannedAdminError") - case .channelAdminsPromoteUnmemberAdminError: - return L10n.tr(key: "Channel.Admins.Promote.UnmemberAdminError") - case .channelBlacklistBlockedBy(let p1): - return L10n.tr(key: "Channel.Blacklist.BlockedBy", p1) - case .channelBlacklistDemoteAdminError: - return L10n.tr(key: "Channel.Blacklist.DemoteAdminError") - case .channelBlacklistRestrictedBy(let p1): - return L10n.tr(key: "Channel.Blacklist.RestrictedBy", p1) - case .channelBlacklistSelectNewUserTitle: - return L10n.tr(key: "Channel.Blacklist.SelectNewUserTitle") - case .channelBlacklistUnban: - return L10n.tr(key: "Channel.Blacklist.Unban") - case .channelBlockUserBlockFor: - return L10n.tr(key: "Channel.BlockUser.BlockFor") - case .channelBlockUserCanEmbedLinks: - return L10n.tr(key: "Channel.BlockUser.CanEmbedLinks") - case .channelBlockUserCanReadMessages: - return L10n.tr(key: "Channel.BlockUser.CanReadMessages") - case .channelBlockUserCanSendMedia: - return L10n.tr(key: "Channel.BlockUser.CanSendMedia") - case .channelBlockUserCanSendMessages: - return L10n.tr(key: "Channel.BlockUser.CanSendMessages") - case .channelBlockUserCanSendStickers: - return L10n.tr(key: "Channel.BlockUser.CanSendStickers") - case .channelEditAdminPermissionAddNewAdmins: - return L10n.tr(key: "Channel.EditAdmin.Permission.AddNewAdmins") - case .channelEditAdminPermissionBanUsers: - return L10n.tr(key: "Channel.EditAdmin.Permission.BanUsers") - case .channelEditAdminPermissionChangeInfo: - return L10n.tr(key: "Channel.EditAdmin.Permission.ChangeInfo") - case .channelEditAdminPermissionDeleteMessages: - return L10n.tr(key: "Channel.EditAdmin.Permission.DeleteMessages") - case .channelEditAdminPermissionEditMessages: - return L10n.tr(key: "Channel.EditAdmin.Permission.EditMessages") - case .channelEditAdminPermissionInviteUsers: - return L10n.tr(key: "Channel.EditAdmin.Permission.InviteUsers") - case .channelEditAdminPermissionPinMessages: - return L10n.tr(key: "Channel.EditAdmin.Permission.PinMessages") - case .channelEditAdminPermissionPostMessages: - return L10n.tr(key: "Channel.EditAdmin.Permission.PostMessages") - case .channelEventFilterAdminsHeader: - return L10n.tr(key: "Channel.EventFilter.AdminsHeader") - case .channelEventFilterEventsHeader: - return L10n.tr(key: "Channel.EventFilter.EventsHeader") - case .channelEventLogEmpty: - return L10n.tr(key: "Channel.EventLog.Empty") - case .channelEventLogEmptySearch: - return L10n.tr(key: "Channel.EventLog.EmptySearch") - case .channelEventLogEmptyText: - return L10n.tr(key: "Channel.EventLog.EmptyText") - case .channelEventLogOriginalMessage: - return L10n.tr(key: "Channel.EventLog.OriginalMessage") - case .channelEventLogWhat: - return L10n.tr(key: "Channel.EventLog.What") - case .channelEventLogAlertHeader: - return L10n.tr(key: "Channel.EventLog.Alert.Header") - case .channelEventLogAlertInfo: - return L10n.tr(key: "Channel.EventLog.Alert.Info") - case .channelEventLogServiceAboutRemoved(let p1): - return L10n.tr(key: "Channel.EventLog.Service.AboutRemoved", p1) - case .channelEventLogServiceAboutUpdated(let p1): - return L10n.tr(key: "Channel.EventLog.Service.AboutUpdated", p1) - case .channelEventLogServiceDisableSignatures(let p1): - return L10n.tr(key: "Channel.EventLog.Service.DisableSignatures", p1) - case .channelEventLogServiceEnableSignatures(let p1): - return L10n.tr(key: "Channel.EventLog.Service.EnableSignatures", p1) - case .channelEventLogServiceLinkRemoved(let p1): - return L10n.tr(key: "Channel.EventLog.Service.LinkRemoved", p1) - case .channelEventLogServiceLinkUpdated(let p1): - return L10n.tr(key: "Channel.EventLog.Service.LinkUpdated", p1) - case .channelEventLogServicePhotoRemoved(let p1): - return L10n.tr(key: "Channel.EventLog.Service.PhotoRemoved", p1) - case .channelEventLogServicePhotoUpdated(let p1): - return L10n.tr(key: "Channel.EventLog.Service.PhotoUpdated", p1) - case .channelEventLogServiceTitleUpdated(let p1): - return L10n.tr(key: "Channel.EventLog.Service.TitleUpdated", p1) - case .channelEventLogServiceUpdateJoin(let p1): - return L10n.tr(key: "Channel.EventLog.Service.UpdateJoin", p1) - case .channelEventLogServiceUpdateLeft(let p1): - return L10n.tr(key: "Channel.EventLog.Service.UpdateLeft", p1) - case .channelPersmissionDeniedSendInlineForever: - return L10n.tr(key: "Channel.Persmission.Denied.SendInline.Forever") - case .channelPersmissionDeniedSendInlineUntil(let p1): - return L10n.tr(key: "Channel.Persmission.Denied.SendInline.Until", p1) - case .channelPersmissionDeniedSendMediaForever: - return L10n.tr(key: "Channel.Persmission.Denied.SendMedia.Forever") - case .channelPersmissionDeniedSendMediaUntil(let p1): - return L10n.tr(key: "Channel.Persmission.Denied.SendMedia.Until", p1) - case .channelPersmissionDeniedSendMessagesForever: - return L10n.tr(key: "Channel.Persmission.Denied.SendMessages.Forever") - case .channelPersmissionDeniedSendMessagesUntil(let p1): - return L10n.tr(key: "Channel.Persmission.Denied.SendMessages.Until", p1) - case .channelPersmissionDeniedSendStickersForever: - return L10n.tr(key: "Channel.Persmission.Denied.SendStickers.Forever") - case .channelPersmissionDeniedSendStickersUntil(let p1): - return L10n.tr(key: "Channel.Persmission.Denied.SendStickers.Until", p1) - case .channelSelectPeersContacts: - return L10n.tr(key: "Channel.SelectPeers.Contacts") - case .channelSelectPeersGlobal: - return L10n.tr(key: "Channel.SelectPeers.Global") - case .channelAdminsRecentActions: - return L10n.tr(key: "ChannelAdmins.RecentActions") - case .channelBlacklistAddMember: - return L10n.tr(key: "ChannelBlacklist.AddMember") - case .channelBlacklistBlocked: - return L10n.tr(key: "ChannelBlacklist.Blocked") - case .channelBlacklistEmptyDescrpition: - return L10n.tr(key: "ChannelBlacklist.EmptyDescrpition") - case .channelBlacklistRestricted: - return L10n.tr(key: "ChannelBlacklist.Restricted") - case .channelEventFilterChannelInfo: - return L10n.tr(key: "ChannelEventFilter.ChannelInfo") - case .channelEventFilterDeletedMessages: - return L10n.tr(key: "ChannelEventFilter.DeletedMessages") - case .channelEventFilterEditedMessages: - return L10n.tr(key: "ChannelEventFilter.EditedMessages") - case .channelEventFilterGroupInfo: - return L10n.tr(key: "ChannelEventFilter.GroupInfo") - case .channelEventFilterLeavingMembers: - return L10n.tr(key: "ChannelEventFilter.LeavingMembers") - case .channelEventFilterNewAdmins: - return L10n.tr(key: "ChannelEventFilter.NewAdmins") - case .channelEventFilterNewMembers: - return L10n.tr(key: "ChannelEventFilter.NewMembers") - case .channelEventFilterNewRestrictions: - return L10n.tr(key: "ChannelEventFilter.NewRestrictions") - case .channelEventFilterPinnedMessages: - return L10n.tr(key: "ChannelEventFilter.PinnedMessages") - case .channelMembersAddMembers: - return L10n.tr(key: "ChannelMembers.AddMembers") - case .channelMembersInviteLink: - return L10n.tr(key: "ChannelMembers.InviteLink") - case .channelMembersMembersListDesc: - return L10n.tr(key: "ChannelMembers.MembersListDesc") - case .channelMembersSelectTitle: - return L10n.tr(key: "ChannelMembers.Select.Title") - case .channelVisibilityChecking: - return L10n.tr(key: "ChannelVisibility.Checking") - case .channelVisibilityLoading: - return L10n.tr(key: "ChannelVisibility.Loading") - case .chatAdminBadge: - return L10n.tr(key: "Chat.AdminBadge") - case .chatCancel: - return L10n.tr(key: "Chat.Cancel") - case .chatDropAsFilesDesc: - return L10n.tr(key: "Chat.DropAsFilesDesc") - case .chatDropQuickDesc: - return L10n.tr(key: "Chat.DropQuickDesc") - case .chatDropTitle: - return L10n.tr(key: "Chat.DropTitle") - case .chatEmptyChat: - return L10n.tr(key: "Chat.EmptyChat") - case .chatForwardActionHeader: - return L10n.tr(key: "Chat.ForwardActionHeader") - case .chatInstantView: - return L10n.tr(key: "Chat.InstantView") - case .chatSearchCount(let p1, let p2): - return L10n.tr(key: "Chat.SearchCount", p1, p2) - case .chatSearchFrom: - return L10n.tr(key: "Chat.SearchFrom") - case .chatShareInlineResultActionHeader: - return L10n.tr(key: "Chat.ShareInlineResultActionHeader") - case .chatCallIncoming: - return L10n.tr(key: "Chat.Call.Incoming") - case .chatCallOutgoing: - return L10n.tr(key: "Chat.Call.Outgoing") - case .chatConfirmActionUndonable: - return L10n.tr(key: "Chat.Confirm.ActionUndonable") - case .chatConfirmDeleteMessages: - return L10n.tr(key: "Chat.Confirm.DeleteMessages") - case .chatConfirmDeleteMessagesForEveryone: - return L10n.tr(key: "Chat.Confirm.DeleteMessagesForEveryone") - case .chatConfirmUnpin: - return L10n.tr(key: "Chat.Confirm.Unpin") - case .chatConnectingStatusConnecting: - return L10n.tr(key: "Chat.ConnectingStatus.connecting") - case .chatConnectingStatusConnectingToProxy: - return L10n.tr(key: "Chat.ConnectingStatus.connectingToProxy") - case .chatConnectingStatusUpdating: - return L10n.tr(key: "Chat.ConnectingStatus.updating") - case .chatConnectingStatusWaitingNetwork: - return L10n.tr(key: "Chat.ConnectingStatus.waitingNetwork") - case .chatContextAddFavoriteSticker: - return L10n.tr(key: "Chat.Context.AddFavoriteSticker") - case .chatContextClearHistory: - return L10n.tr(key: "Chat.Context.ClearHistory") - case .chatContextCopyBlock: - return L10n.tr(key: "Chat.Context.CopyBlock") - case .chatContextDisableNotifications: - return L10n.tr(key: "Chat.Context.DisableNotifications") - case .chatContextEdit: - return L10n.tr(key: "Chat.Context.Edit") - case .chatContextEnableNotifications: - return L10n.tr(key: "Chat.Context.EnableNotifications") - case .chatContextInfo: - return L10n.tr(key: "Chat.Context.Info") - case .chatContextRemoveFavoriteSticker: - return L10n.tr(key: "Chat.Context.RemoveFavoriteSticker") - case .chatHeaderPinnedMessage: - return L10n.tr(key: "Chat.Header.PinnedMessage") - case .chatHeaderReportSpam: - return L10n.tr(key: "Chat.Header.ReportSpam") - case .chatInputDelete: - return L10n.tr(key: "Chat.Input.Delete") - case .chatInputJoin: - return L10n.tr(key: "Chat.Input.Join") - case .chatInputMute: - return L10n.tr(key: "Chat.Input.Mute") - case .chatInputReturn: - return L10n.tr(key: "Chat.Input.Return") - case .chatInputStartBot: - return L10n.tr(key: "Chat.Input.StartBot") - case .chatInputUnblock: - return L10n.tr(key: "Chat.Input.Unblock") - case .chatInputUnmute: - return L10n.tr(key: "Chat.Input.Unmute") - case .chatInputAccessoryEditMessage: - return L10n.tr(key: "Chat.Input.Accessory.EditMessage") - case .chatInputSecretChatWaitingToOnline: - return L10n.tr(key: "Chat.Input.SecretChat.WaitingToOnline") - case .chatListContact: - return L10n.tr(key: "Chat.List.Contact") - case .chatListGIF: - return L10n.tr(key: "Chat.List.GIF") - case .chatListInstantVideo: - return L10n.tr(key: "Chat.List.InstantVideo") - case .chatListMap: - return L10n.tr(key: "Chat.List.Map") - case .chatListPhoto: - return L10n.tr(key: "Chat.List.Photo") - case .chatListSticker(let p1): - return L10n.tr(key: "Chat.List.Sticker", p1) - case .chatListVideo: - return L10n.tr(key: "Chat.List.Video") - case .chatListVoice: - return L10n.tr(key: "Chat.List.Voice") - case .chatListServicePaymentSent(let p1): - return L10n.tr(key: "Chat.List.Service.PaymentSent", p1) - case .chatMessageEdited: - return L10n.tr(key: "Chat.Message.edited") - case .chatMessageUnsupported: - return L10n.tr(key: "Chat.Message.Unsupported") - case .chatMessageVia: - return L10n.tr(key: "Chat.Message.Via") - case .chatSecretChat1Feature: - return L10n.tr(key: "Chat.SecretChat.1Feature") - case .chatSecretChat2Feature: - return L10n.tr(key: "Chat.SecretChat.2Feature") - case .chatSecretChat3Feature: - return L10n.tr(key: "Chat.SecretChat.3Feature") - case .chatSecretChat4Feature: - return L10n.tr(key: "Chat.SecretChat.4Feature") - case .chatSecretChatEmptyHeader: - return L10n.tr(key: "Chat.SecretChat.EmptyHeader") - case .chatServicePaymentSent(let p1, let p2, let p3): - return L10n.tr(key: "Chat.Service.PaymentSent", p1, p2, p3) - case .chatServicePinnedMessage: - return L10n.tr(key: "Chat.Service.PinnedMessage") - case .chatServiceYou: - return L10n.tr(key: "Chat.Service.You") - case .chatServiceChannelRemovedPhoto: - return L10n.tr(key: "Chat.Service.Channel.RemovedPhoto") - case .chatServiceChannelUpdatedPhoto: - return L10n.tr(key: "Chat.Service.Channel.UpdatedPhoto") - case .chatServiceChannelUpdatedTitle(let p1): - return L10n.tr(key: "Chat.Service.Channel.UpdatedTitle", p1) - case .chatServiceGroupAddedMembers(let p1, let p2): - return L10n.tr(key: "Chat.Service.Group.AddedMembers", p1, p2) - case .chatServiceGroupAddedSelf(let p1): - return L10n.tr(key: "Chat.Service.Group.AddedSelf", p1) - case .chatServiceGroupCreated(let p1, let p2): - return L10n.tr(key: "Chat.Service.Group.Created", p1, p2) - case .chatServiceGroupJoinedByLink(let p1): - return L10n.tr(key: "Chat.Service.Group.JoinedByLink", p1) - case .chatServiceGroupMigratedToSupergroup: - return L10n.tr(key: "Chat.Service.Group.MigratedToSupergroup") - case .chatServiceGroupRemovedMembers(let p1, let p2): - return L10n.tr(key: "Chat.Service.Group.RemovedMembers", p1, p2) - case .chatServiceGroupRemovedPhoto(let p1): - return L10n.tr(key: "Chat.Service.Group.RemovedPhoto", p1) - case .chatServiceGroupRemovedSelf(let p1): - return L10n.tr(key: "Chat.Service.Group.RemovedSelf", p1) - case .chatServiceGroupTookScreenshot(let p1): - return L10n.tr(key: "Chat.Service.Group.TookScreenshot", p1) - case .chatServiceGroupUpdatedPhoto(let p1): - return L10n.tr(key: "Chat.Service.Group.UpdatedPhoto", p1) - case .chatServiceGroupUpdatedPinnedMessage(let p1, let p2): - return L10n.tr(key: "Chat.Service.Group.UpdatedPinnedMessage", p1, p2) - case .chatServiceGroupUpdatedTitle(let p1, let p2): - return L10n.tr(key: "Chat.Service.Group.UpdatedTitle", p1, p2) - case .chatServiceSecretChatDisabledTimer(let p1): - return L10n.tr(key: "Chat.Service.SecretChat.DisabledTimer", p1) - case .chatServiceSecretChatSetTimer(let p1, let p2): - return L10n.tr(key: "Chat.Service.SecretChat.SetTimer", p1, p2) - case .chatServiceSecretChatDisabledTimerSelf: - return L10n.tr(key: "Chat.Service.SecretChat.DisabledTimer.Self") - case .chatServiceSecretChatSetTimerSelf(let p1): - return L10n.tr(key: "Chat.Service.SecretChat.SetTimer.Self", p1) - case .chatTitleSelf: - return L10n.tr(key: "Chat.Title.self") - case .chatListDraft: - return L10n.tr(key: "ChatList.Draft") - case .chatListUnsupportedMessage: - return L10n.tr(key: "ChatList.UnsupportedMessage") - case .chatListYou: - return L10n.tr(key: "ChatList.You") - case .chatListContextCall: - return L10n.tr(key: "ChatList.Context.Call") - case .chatListContextClearHistory: - return L10n.tr(key: "ChatList.Context.ClearHistory") - case .chatListContextDeleteAndExit: - return L10n.tr(key: "ChatList.Context.DeleteAndExit") - case .chatListContextDeleteChat: - return L10n.tr(key: "ChatList.Context.DeleteChat") - case .chatListContextLeaveChannel: - return L10n.tr(key: "ChatList.Context.LeaveChannel") - case .chatListContextLeaveGroup: - return L10n.tr(key: "ChatList.Context.LeaveGroup") - case .chatListContextMute: - return L10n.tr(key: "ChatList.Context.Mute") - case .chatListContextPin: - return L10n.tr(key: "ChatList.Context.Pin") - case .chatListContextReturnGroup: - return L10n.tr(key: "ChatList.Context.ReturnGroup") - case .chatListContextUnmute: - return L10n.tr(key: "ChatList.Context.Unmute") - case .chatListContextUnpin: - return L10n.tr(key: "ChatList.Context.Unpin") - case .chatListSecretChatCreated(let p1): - return L10n.tr(key: "ChatList.SecretChat.Created", p1) - case .chatListSecretChatExKeys: - return L10n.tr(key: "ChatList.SecretChat.ExKeys") - case .chatListSecretChatJoined(let p1): - return L10n.tr(key: "ChatList.SecretChat.Joined", p1) - case .chatListSecretChatTerminated: - return L10n.tr(key: "ChatList.SecretChat.Terminated") - case .chatListServiceDestructingPhoto: - return L10n.tr(key: "ChatList.Service.DestructingPhoto") - case .chatListServiceDestructingVideo: - return L10n.tr(key: "ChatList.Service.DestructingVideo") - case .chatListServiceGameScored(let p1, let p2): - return L10n.tr(key: "ChatList.Service.GameScored", p1, p2) - case .chatListServiceCallCancelled: - return L10n.tr(key: "ChatList.Service.Call.Cancelled") - case .chatListServiceCallDisconnected: - return L10n.tr(key: "ChatList.Service.Call.Disconnected") - case .chatListServiceCallIncoming(let p1): - return L10n.tr(key: "ChatList.Service.Call.incoming", p1) - case .chatListServiceCallMissed: - return L10n.tr(key: "ChatList.Service.Call.Missed") - case .chatListServiceCallOutgoing(let p1): - return L10n.tr(key: "ChatList.Service.Call.outgoing", p1) - case .chatMessageTooltipViews: - return L10n.tr(key: "ChatMessage.Tooltip.Views") - case .chatServiceChannelCreated: - return L10n.tr(key: "ChatService.ChannelCreated") - case .composeCreate: - return L10n.tr(key: "Compose.Create") - case .composeNext: - return L10n.tr(key: "Compose.Next") - case .composeSelectUsers: - return L10n.tr(key: "Compose.SelectUsers") - case .composeConfirmStartSecretChat(let p1): - return L10n.tr(key: "Compose.Confirm.StartSecretChat", p1) - case .composePopoverNewChannel: - return L10n.tr(key: "Compose.Popover.NewChannel") - case .composePopoverNewGroup: - return L10n.tr(key: "Compose.Popover.NewGroup") - case .composePopoverNewSecretChat: - return L10n.tr(key: "Compose.Popover.NewSecretChat") - case .composeSelectSecretChat: - return L10n.tr(key: "Compose.Select.SecretChat") - case .composeSelectGroupUsersPlaceholder: - return L10n.tr(key: "Compose.SelectGroupUsers.Placeholder") - case .confirmAddBotToGroup(let p1): - return L10n.tr(key: "Confirm.AddBotToGroup", p1) - case .confirmDeleteAdminedChannel: - return L10n.tr(key: "Confirm.DeleteAdminedChannel") - case .confirmDeleteChatUser: - return L10n.tr(key: "Confirm.DeleteChatUser") - case .confirmLeaveGroup: - return L10n.tr(key: "Confirm.LeaveGroup") - case .connectingStatusConnecting: - return L10n.tr(key: "ConnectingStatus.connecting") - case .connectingStatusConnectingToProxy: - return L10n.tr(key: "ConnectingStatus.connectingToProxy") - case .connectingStatusDisableProxy: - return L10n.tr(key: "ConnectingStatus.DisableProxy") - case .connectingStatusOnline: - return L10n.tr(key: "ConnectingStatus.online") - case .connectingStatusUpdating: - return L10n.tr(key: "ConnectingStatus.updating") - case .connectingStatusWaitingNetwork: - return L10n.tr(key: "ConnectingStatus.waitingNetwork") - case .contactsAddContact: - return L10n.tr(key: "Contacts.AddContact") - case .contactsContacsSeparator: - return L10n.tr(key: "Contacts.ContacsSeparator") - case .contactsNotRegistredDescription: - return L10n.tr(key: "Contacts.NotRegistredDescription") - case .contactsNotRegistredTitle: - return L10n.tr(key: "Contacts.NotRegistredTitle") - case .contactsFirstNamePlaceholder: - return L10n.tr(key: "Contacts.FirstName.Placeholder") - case .contactsLastNamePlaceholder: - return L10n.tr(key: "Contacts.LastName.Placeholder") - case .contactsPhoneNumberPlaceholder: - return L10n.tr(key: "Contacts.PhoneNumber.Placeholder") - case .contextCopyMedia: - return L10n.tr(key: "Context.CopyMedia") - case .contextRecentGifRemove: - return L10n.tr(key: "Context.RecentGifRemove") - case .contextRemoveFaveSticker: - return L10n.tr(key: "Context.RemoveFaveSticker") - case .contextShowInFinder: - return L10n.tr(key: "Context.ShowInFinder") - case .contextViewStickerSet: - return L10n.tr(key: "Context.ViewStickerSet") - case .convertToSuperGroupConfirm: - return L10n.tr(key: "ConvertToSuperGroup.Confirm") - case .convertToSupergroupAlertError: - return L10n.tr(key: "ConvertToSupergroup.Alert.Error") - case .createGroupNameHolder: - return L10n.tr(key: "CreateGroup.NameHolder") - case .cwLP1JidTitle: - return L10n.tr(key: "cwL-P1-jid.title") - case .d9MCDAMdTitle: - return L10n.tr(key: "d9M-CD-aMd.title") - case .dataAndStorageNetworkUsage: - return L10n.tr(key: "DataAndStorage.NetworkUsage") - case .dataAndStorageStorageUsage: - return L10n.tr(key: "DataAndStorage.StorageUsage") - case .dataAndStorageAutomaticAudioDownloadHeader: - return L10n.tr(key: "DataAndStorage.AutomaticAudioDownload.Header") - case .dataAndStorageAutomaticDownloadGroupsChannels: - return L10n.tr(key: "DataAndStorage.AutomaticDownload.GroupsChannels") - case .dataAndStorageAutomaticPhotoDownloadHeader: - return L10n.tr(key: "DataAndStorage.AutomaticPhotoDownload.Header") - case .dataAndStorageAutomaticVideoDownloadHeader: - return L10n.tr(key: "DataAndStorage.AutomaticVideoDownload.Header") - case .dateToday: - return L10n.tr(key: "Date.Today") - case .drj4nYzgTitle: - return L10n.tr(key: "dRJ-4n-Yzg.title") - case .dv1IoYv7Title: - return L10n.tr(key: "Dv1-io-Yv7.title") - case .emojiActivityAndSport: - return L10n.tr(key: "Emoji.ActivityAndSport") - case .emojiAnimalsAndNature: - return L10n.tr(key: "Emoji.AnimalsAndNature") - case .emojiFlags: - return L10n.tr(key: "Emoji.Flags") - case .emojiFoodAndDrink: - return L10n.tr(key: "Emoji.FoodAndDrink") - case .emojiObjects: - return L10n.tr(key: "Emoji.Objects") - case .emojiRecent: - return L10n.tr(key: "Emoji.Recent") - case .emojiSmilesAndPeople: - return L10n.tr(key: "Emoji.SmilesAndPeople") - case .emojiSymbols: - return L10n.tr(key: "Emoji.Symbols") - case .emojiTravelAndPlaces: - return L10n.tr(key: "Emoji.TravelAndPlaces") - case .emptyPeerDescription: - return L10n.tr(key: "EmptyPeer.Description") - case .encryptionKeyDescription(let p1, let p2): - return L10n.tr(key: "EncryptionKey.Description", p1, p2) - case .entertainmentEmoji: - return L10n.tr(key: "Entertainment.Emoji") - case .entertainmentGIF: - return L10n.tr(key: "Entertainment.GIF") - case .entertainmentStickers: - return L10n.tr(key: "Entertainment.Stickers") - case .entertainmentSwitchEmoji: - return L10n.tr(key: "Entertainment.Switch.Emoji") - case .entertainmentSwitchGifAndStickers: - return L10n.tr(key: "Entertainment.Switch.GifAndStickers") - case .errorUsernameAlreadyTaken: - return L10n.tr(key: "Error.Username.AlreadyTaken") - case .errorUsernameInvalid: - return L10n.tr(key: "Error.Username.Invalid") - case .errorUsernameMinimumLength: - return L10n.tr(key: "Error.Username.MinimumLength") - case .errorUsernameNumberStart: - return L10n.tr(key: "Error.Username.NumberStart") - case .errorUsernameUnderscopeEnd: - return L10n.tr(key: "Error.Username.UnderscopeEnd") - case .errorUsernameUnderscopeStart: - return L10n.tr(key: "Error.Username.UnderscopeStart") - case .eventLogServiceBanned(let p1, let p2): - return L10n.tr(key: "EventLog.Service.Banned", p1, p2) - case .eventLogServiceChangedStickerSet(let p1): - return L10n.tr(key: "EventLog.Service.ChangedStickerSet", p1) - case .eventLogServiceDeletedMessage(let p1): - return L10n.tr(key: "EventLog.Service.DeletedMessage", p1) - case .eventLogServiceDemoted(let p1, let p2): - return L10n.tr(key: "EventLog.Service.Demoted", p1, p2) - case .eventLogServiceEditedMessage(let p1): - return L10n.tr(key: "EventLog.Service.EditedMessage", p1) - case .eventLogServicePreviousDesc: - return L10n.tr(key: "EventLog.Service.PreviousDesc") - case .eventLogServicePreviousLink: - return L10n.tr(key: "EventLog.Service.PreviousLink") - case .eventLogServicePreviousTitle: - return L10n.tr(key: "EventLog.Service.PreviousTitle") - case .eventLogServicePromoted(let p1, let p2): - return L10n.tr(key: "EventLog.Service.Promoted", p1, p2) - case .eventLogServiceRemovedStickerSet(let p1): - return L10n.tr(key: "EventLog.Service.RemovedStickerSet", p1) - case .eventLogServiceRemovePinned(let p1): - return L10n.tr(key: "EventLog.Service.RemovePinned", p1) - case .eventLogServiceUpdatePinned(let p1): - return L10n.tr(key: "EventLog.Service.UpdatePinned", p1) - case .eventLogServiceDemoteEmbedLinks: - return L10n.tr(key: "EventLog.Service.Demote.EmbedLinks") - case .eventLogServiceDemoteSendInline: - return L10n.tr(key: "EventLog.Service.Demote.SendInline") - case .eventLogServiceDemoteSendMedia: - return L10n.tr(key: "EventLog.Service.Demote.SendMedia") - case .eventLogServiceDemoteSendMessages: - return L10n.tr(key: "EventLog.Service.Demote.SendMessages") - case .eventLogServiceDemoteSendStickers: - return L10n.tr(key: "EventLog.Service.Demote.SendStickers") - case .eventLogServiceDemotedChanged(let p1, let p2): - return L10n.tr(key: "EventLog.Service.Demoted.Changed", p1, p2) - case .eventLogServiceDemotedUntil(let p1, let p2, let p3): - return L10n.tr(key: "EventLog.Service.Demoted.Until", p1, p2, p3) - case .eventLogServiceDemotedChangedUntil(let p1, let p2, let p3): - return L10n.tr(key: "EventLog.Service.Demoted.Changed.Until", p1, p2, p3) - case .eventLogServicePromoteAddNewAdmins: - return L10n.tr(key: "EventLog.Service.Promote.AddNewAdmins") - case .eventLogServicePromoteAddUsers: - return L10n.tr(key: "EventLog.Service.Promote.AddUsers") - case .eventLogServicePromoteBanUsers: - return L10n.tr(key: "EventLog.Service.Promote.BanUsers") - case .eventLogServicePromoteChangeInfo: - return L10n.tr(key: "EventLog.Service.Promote.ChangeInfo") - case .eventLogServicePromoteDeleteMessages: - return L10n.tr(key: "EventLog.Service.Promote.DeleteMessages") - case .eventLogServicePromoteEditMessages: - return L10n.tr(key: "EventLog.Service.Promote.EditMessages") - case .eventLogServicePromoteInviteViaLink: - return L10n.tr(key: "EventLog.Service.Promote.InviteViaLink") - case .eventLogServicePromotePinMessages: - return L10n.tr(key: "EventLog.Service.Promote.PinMessages") - case .eventLogServicePromotePostMessages: - return L10n.tr(key: "EventLog.Service.Promote.PostMessages") - case .eventLogServicePromotedChanged(let p1, let p2): - return L10n.tr(key: "EventLog.Service.Promoted.Changed", p1, p2) - case .fastSettingsDisableDarkMode: - return L10n.tr(key: "FastSettings.DisableDarkMode") - case .fastSettingsEnableDarkMode: - return L10n.tr(key: "FastSettings.EnableDarkMode") - case .fastSettingsLockTelegram: - return L10n.tr(key: "FastSettings.LockTelegram") - case .fastSettingsMute2Hours: - return L10n.tr(key: "FastSettings.Mute2Hours") - case .fastSettingsSetPasscode: - return L10n.tr(key: "FastSettings.SetPasscode") - case .fastSettingsUnmute: - return L10n.tr(key: "FastSettings.Unmute") - case .feMD8WVrTitle: - return L10n.tr(key: "FeM-D8-WVr.title") - case .forwardModalActionDescriptionCountable(let p1, let p2): - return L10n.tr(key: "ForwardModalAction.description_countable", p1, p2) - case .forwardModalActionDescriptionFew(let p1): - return L10n.tr(key: "ForwardModalAction.description_few", p1) - case .forwardModalActionDescriptionMany(let p1): - return L10n.tr(key: "ForwardModalAction.description_many", p1) - case .forwardModalActionDescriptionOne(let p1): - return L10n.tr(key: "ForwardModalAction.description_one", p1) - case .forwardModalActionDescriptionOther(let p1): - return L10n.tr(key: "ForwardModalAction.description_other", p1) - case .forwardModalActionDescriptionTwo(let p1): - return L10n.tr(key: "ForwardModalAction.description_two", p1) - case .forwardModalActionDescriptionZero(let p1): - return L10n.tr(key: "ForwardModalAction.description_zero", p1) - case .forwardModalActionTitleCountable(let p1): - return L10n.tr(key: "ForwardModalAction.Title_countable", p1) - case .forwardModalActionTitleFew: - return L10n.tr(key: "ForwardModalAction.Title_few") - case .forwardModalActionTitleMany: - return L10n.tr(key: "ForwardModalAction.Title_many") - case .forwardModalActionTitleOne: - return L10n.tr(key: "ForwardModalAction.Title_one") - case .forwardModalActionTitleOther: - return L10n.tr(key: "ForwardModalAction.Title_other") - case .forwardModalActionTitleTwo: - return L10n.tr(key: "ForwardModalAction.Title_two") - case .forwardModalActionTitleZero: - return L10n.tr(key: "ForwardModalAction.Title_zero") - case .galleryContextDeletePhoto: - return L10n.tr(key: "Gallery.ContextDeletePhoto") - case .galleryCounter(let p1, let p2): - return L10n.tr(key: "Gallery.Counter", p1, p2) - case .galleryContextCopyToClipboard: - return L10n.tr(key: "Gallery.Context.CopyToClipboard") - case .galleryContextSaveAs: - return L10n.tr(key: "Gallery.Context.SaveAs") - case .galleryContextShowMessage: - return L10n.tr(key: "Gallery.Context.ShowMessage") - case .generalSettingsAppearanceSettings: - return L10n.tr(key: "GeneralSettings.AppearanceSettings") - case .generalSettingsDarkMode: - return L10n.tr(key: "GeneralSettings.DarkMode") - case .generalSettingsEmojiReplacements: - return L10n.tr(key: "GeneralSettings.EmojiReplacements") - case .generalSettingsEnableSidebar: - return L10n.tr(key: "GeneralSettings.EnableSidebar") - case .generalSettingsForceTouchHeader: - return L10n.tr(key: "GeneralSettings.ForceTouchHeader") - case .generalSettingsGeneralSettings: - return L10n.tr(key: "GeneralSettings.GeneralSettings") - case .generalSettingsInAppSounds: - return L10n.tr(key: "GeneralSettings.InAppSounds") - case .generalSettingsInputSettings: - return L10n.tr(key: "GeneralSettings.InputSettings") - case .generalSettingsLargeFonts: - return L10n.tr(key: "GeneralSettings.LargeFonts") - case .generalSettingsMediaKeysForInAppPlayer: - return L10n.tr(key: "GeneralSettings.MediaKeysForInAppPlayer") - case .generalSettingsSendByCmdEnter: - return L10n.tr(key: "GeneralSettings.SendByCmdEnter") - case .generalSettingsSendByEnter: - return L10n.tr(key: "GeneralSettings.SendByEnter") - case .generalSettingsDarkModeDescription: - return L10n.tr(key: "GeneralSettings.DarkMode.Description") - case .generalSettingsFontDescription: - return L10n.tr(key: "GeneralSettings.Font.Description") - case .generalSettingsForceTouchEdit: - return L10n.tr(key: "GeneralSettings.ForceTouch.Edit") - case .generalSettingsForceTouchForward: - return L10n.tr(key: "GeneralSettings.ForceTouch.Forward") - case .generalSettingsForceTouchReply: - return L10n.tr(key: "GeneralSettings.ForceTouch.Reply") - case .groupCreateGroup: - return L10n.tr(key: "Group.CreateGroup") - case .groupNewGroup: - return L10n.tr(key: "Group.NewGroup") - case .groupUnavailable: - return L10n.tr(key: "Group.Unavailable") - case .groupEditAdminPermissionChangeInfo: - return L10n.tr(key: "Group.EditAdmin.Permission.ChangeInfo") - case .groupEventLogEmptyText: - return L10n.tr(key: "Group.EventLog.EmptyText") - case .groupEventLogServiceAboutRemoved(let p1): - return L10n.tr(key: "Group.EventLog.Service.AboutRemoved", p1) - case .groupEventLogServiceAboutUpdated(let p1): - return L10n.tr(key: "Group.EventLog.Service.AboutUpdated", p1) - case .groupEventLogServiceDisableInvites(let p1): - return L10n.tr(key: "Group.EventLog.Service.DisableInvites", p1) - case .groupEventLogServiceEnableInvites(let p1): - return L10n.tr(key: "Group.EventLog.Service.EnableInvites", p1) - case .groupEventLogServiceLinkRemoved(let p1): - return L10n.tr(key: "Group.EventLog.Service.LinkRemoved", p1) - case .groupEventLogServiceLinkUpdated(let p1): - return L10n.tr(key: "Group.EventLog.Service.LinkUpdated", p1) - case .groupEventLogServicePhotoRemoved(let p1): - return L10n.tr(key: "Group.EventLog.Service.PhotoRemoved", p1) - case .groupEventLogServicePhotoUpdated(let p1): - return L10n.tr(key: "Group.EventLog.Service.PhotoUpdated", p1) - case .groupEventLogServiceTitleUpdated(let p1): - return L10n.tr(key: "Group.EventLog.Service.TitleUpdated", p1) - case .groupEventLogServiceUpdateJoin(let p1): - return L10n.tr(key: "Group.EventLog.Service.UpdateJoin", p1) - case .groupEventLogServiceUpdateLeft(let p1): - return L10n.tr(key: "Group.EventLog.Service.UpdateLeft", p1) - case .groupAdminsAllMembersAdmins: - return L10n.tr(key: "GroupAdmins.AllMembersAdmins") - case .groupAdminsDescAdminInvites: - return L10n.tr(key: "GroupAdmins.Desc.AdminInvites") - case .groupAdminsDescAllInvites: - return L10n.tr(key: "GroupAdmins.Desc.AllInvites") - case .groupInvationChannelDescription: - return L10n.tr(key: "GroupInvation.ChannelDescription") - case .groupInvationCopyLink: - return L10n.tr(key: "GroupInvation.CopyLink") - case .groupInvationGroupDescription: - return L10n.tr(key: "GroupInvation.GroupDescription") - case .groupInvationRevoke: - return L10n.tr(key: "GroupInvation.Revoke") - case .groupInvationShare: - return L10n.tr(key: "GroupInvation.Share") - case .groupsInCommonEmpty: - return L10n.tr(key: "GroupsInCommon.Empty") - case .groupStickersChooseHeader: - return L10n.tr(key: "GroupStickers.ChooseHeader") - case .groupStickersCreateDescription: - return L10n.tr(key: "GroupStickers.CreateDescription") - case .groupStickersEmptyDesc: - return L10n.tr(key: "GroupStickers.EmptyDesc") - case .groupStickersEmptyHeader: - return L10n.tr(key: "GroupStickers.EmptyHeader") - case .gvau4SdLTitle: - return L10n.tr(key: "gVA-U4-sdL.title") - case .h8h7bM4vTitle: - return L10n.tr(key: "H8h-7b-M4v.title") - case .hFoCyZxITitle: - return L10n.tr(key: "HFo-cy-zxI.title") - case .hfqgknfaTitle: - return L10n.tr(key: "HFQ-gK-NFA.title") - case .hQb2vFYvTitle: - return L10n.tr(key: "hQb-2v-fYv.title") - case .hyVFhRgOTitle: - return L10n.tr(key: "HyV-fh-RgO.title") - case .hz2CUCR7Title: - return L10n.tr(key: "hz2-CU-CR7.title") - case .inAppLinksConfirmOpenExternal(let p1): - return L10n.tr(key: "InAppLinks.Confirm.OpenExternal", p1) - case .inlineModalActionDesc(let p1): - return L10n.tr(key: "InlineModalAction.Desc", p1) - case .inlineModalActionTitle: - return L10n.tr(key: "InlineModalAction.Title") - case .inputAttachPopoverFile: - return L10n.tr(key: "InputAttach.Popover.File") - case .inputAttachPopoverPhotoOrVideo: - return L10n.tr(key: "InputAttach.Popover.PhotoOrVideo") - case .inputAttachPopoverPicture: - return L10n.tr(key: "InputAttach.Popover.Picture") - case .installedStickersArchived: - return L10n.tr(key: "InstalledStickers.Archived") - case .installedStickersDescrpiption: - return L10n.tr(key: "InstalledStickers.Descrpiption") - case .installedStickersPacksTitle: - return L10n.tr(key: "InstalledStickers.PacksTitle") - case .installedStickersTranding: - return L10n.tr(key: "InstalledStickers.Tranding") - case .installedStickersRemoveDelete: - return L10n.tr(key: "InstalledStickers.Remove.Delete") - case .installedStickersRemoveDescription: - return L10n.tr(key: "InstalledStickers.Remove.Description") - case .instantPageAuthorAndDateTitle(let p1, let p2): - return L10n.tr(key: "InstantPage.AuthorAndDateTitle", p1, p2) - case .ivChannelJoin: - return L10n.tr(key: "IV.Channel.Join") - case .joinLinkJoin: - return L10n.tr(key: "JoinLink.Join") - case .kd2MpPUSTitle: - return L10n.tr(key: "Kd2-mp-pUS.title") - case .le2AR0XJTitle: - return L10n.tr(key: "LE2-aR-0XJ.title") - case .legacyIntroDescription1: - return L10n.tr(key: "Legacy.Intro.Description1") - case .legacyIntroDescription2: - return L10n.tr(key: "Legacy.Intro.Description2") - case .legacyIntroNext: - return L10n.tr(key: "Legacy.Intro.Next") - case .linkInvationChannelConfirmRevoke: - return L10n.tr(key: "LinkInvation.Channel.Confirm.Revoke") - case .linkInvationConfirmOk: - return L10n.tr(key: "LinkInvation.Confirm.Ok") - case .linkInvationGroupConfirmRevoke: - return L10n.tr(key: "LinkInvation.Group.Confirm.Revoke") - case .loginCodePlaceholder: - return L10n.tr(key: "Login.codePlaceholder") - case .loginContinueOnLanguage: - return L10n.tr(key: "Login.ContinueOnLanguage") - case .loginCountryLabel: - return L10n.tr(key: "Login.countryLabel") - case .loginEnterCodeFromApp: - return L10n.tr(key: "Login.EnterCodeFromApp") - case .loginEnterPasswordDescription: - return L10n.tr(key: "Login.EnterPasswordDescription") - case .loginFloodWait: - return L10n.tr(key: "Login.FloodWait") - case .loginInvalidCountryCode: - return L10n.tr(key: "Login.InvalidCountryCode") - case .loginJustSentSms: - return L10n.tr(key: "Login.JustSentSms") - case .loginNext: - return L10n.tr(key: "Login.Next") - case .loginPasswordPlaceholder: - return L10n.tr(key: "Login.passwordPlaceholder") - case .loginPhoneCalledCode: - return L10n.tr(key: "Login.PhoneCalledCode") - case .loginPhoneDialed: - return L10n.tr(key: "Login.PhoneDialed") - case .loginPhoneFieldPlaceholder: - return L10n.tr(key: "Login.phoneFieldPlaceholder") - case .loginPhoneNumberNotRegistred: - return L10n.tr(key: "Login.PhoneNumberNotRegistred") - case .loginRecoveryMailFailed: - return L10n.tr(key: "Login.RecoveryMailFailed") - case .loginResetAccount: - return L10n.tr(key: "Login.ResetAccount") - case .loginResetAccountDescription: - return L10n.tr(key: "Login.ResetAccountDescription") - case .loginSendSmsIfNotReceivedAppCode: - return L10n.tr(key: "Login.SendSmsIfNotReceivedAppCode") - case .loginWelcomeDescription: - return L10n.tr(key: "Login.WelcomeDescription") - case .loginWillCall(let p1, let p2): - return L10n.tr(key: "Login.willCall", p1, p2) - case .loginWillSendSms(let p1, let p2): - return L10n.tr(key: "Login.willSendSms", p1, p2) - case .loginYourCodeLabel: - return L10n.tr(key: "Login.YourCodeLabel") - case .loginYourPasswordLabel: - return L10n.tr(key: "Login.YourPasswordLabel") - case .loginYourPhoneLabel: - return L10n.tr(key: "Login.YourPhoneLabel") - case .loginHeaderCode: - return L10n.tr(key: "Login.Header.Code") - case .loginHeaderPassword: - return L10n.tr(key: "Login.Header.Password") - case .loginHeaderSignUp: - return L10n.tr(key: "Login.Header.SignUp") - case .messageAccessoryPanelForwardedCountable(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_countable", p1) - case .messageAccessoryPanelForwardedFew(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_few", p1) - case .messageAccessoryPanelForwardedMany(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_many", p1) - case .messageAccessoryPanelForwardedOne(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_one", p1) - case .messageAccessoryPanelForwardedOther(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_other", p1) - case .messageAccessoryPanelForwardedTwo(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_two", p1) - case .messageAccessoryPanelForwardedZero(let p1): - return L10n.tr(key: "Message.AccessoryPanel.Forwarded_zero", p1) - case .messageActionsPanelDelete: - return L10n.tr(key: "Message.ActionsPanel.Delete") - case .messageActionsPanelEmptySelected: - return L10n.tr(key: "Message.ActionsPanel.EmptySelected") - case .messageActionsPanelForward: - return L10n.tr(key: "Message.ActionsPanel.Forward") - case .messageActionsPanelSelectedCountCountable(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_countable", p1) - case .messageActionsPanelSelectedCountFew(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_few", p1) - case .messageActionsPanelSelectedCountMany(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_many", p1) - case .messageActionsPanelSelectedCountOne(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_one", p1) - case .messageActionsPanelSelectedCountOther(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_other", p1) - case .messageActionsPanelSelectedCountTwo(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_two", p1) - case .messageActionsPanelSelectedCountZero(let p1): - return L10n.tr(key: "Message.ActionsPanel.SelectedCount_zero", p1) - case .messageContextDelete: - return L10n.tr(key: "Message.Context.Delete") - case .messageContextEdit: - return L10n.tr(key: "Message.Context.Edit") - case .messageContextForward: - return L10n.tr(key: "Message.Context.Forward") - case .messageContextForwardToCloud: - return L10n.tr(key: "Message.Context.ForwardToCloud") - case .messageContextGoto: - return L10n.tr(key: "Message.Context.Goto") - case .messageContextPin: - return L10n.tr(key: "Message.Context.Pin") - case .messageContextReply: - return L10n.tr(key: "Message.Context.Reply") - case .messageContextSaveGif: - return L10n.tr(key: "Message.Context.SaveGif") - case .messageContextSelect: - return L10n.tr(key: "Message.Context.Select") - case .messageContextConfirmOnlyPin: - return L10n.tr(key: "Message.Context.Confirm.OnlyPin") - case .messageContextConfirmPin: - return L10n.tr(key: "Message.Context.Confirm.Pin") - case .messageContextCopyMessageLink: - return L10n.tr(key: "MessageContext.CopyMessageLink") - case .messagesDeletedMessage: - return L10n.tr(key: "Messages.DeletedMessage") - case .messagesForwardHeader: - return L10n.tr(key: "Messages.ForwardHeader") - case .messagesUnreadMark: - return L10n.tr(key: "Messages.UnreadMark") - case .messagesFileStateFetchingIn1(let p1): - return L10n.tr(key: "Messages.File.State.FetchingIn_1", p1) - case .messagesFileStateFetchingOut1(let p1): - return L10n.tr(key: "Messages.File.State.FetchingOut_1", p1) - case .messagesFileStateLocal: - return L10n.tr(key: "Messages.File.State.Local") - case .messagesFileStateRemote: - return L10n.tr(key: "Messages.File.State.Remote") - case .messagesPlaceholderBroadcast: - return L10n.tr(key: "Messages.Placeholder.Broadcast") - case .messagesPlaceholderSentMessage: - return L10n.tr(key: "Messages.Placeholder.SentMessage") - case .messagesReplyLoadingHeader: - return L10n.tr(key: "Messages.ReplyLoading.Header") - case .messagesReplyLoadingLoading: - return L10n.tr(key: "Messages.ReplyLoading.Loading") - case .mk62p4JGTitle: - return L10n.tr(key: "mK6-2p-4JG.title") - case .modalCancel: - return L10n.tr(key: "Modal.Cancel") - case .modalCopyLink: - return L10n.tr(key: "Modal.CopyLink") - case .modalOK: - return L10n.tr(key: "Modal.OK") - case .modalSend: - return L10n.tr(key: "Modal.Send") - case .modalShare: - return L10n.tr(key: "Modal.Share") - case .navigationBack: - return L10n.tr(key: "Navigation.back") - case .navigationCancel: - return L10n.tr(key: "Navigation.Cancel") - case .navigationClose: - return L10n.tr(key: "Navigation.Close") - case .navigationDone: - return L10n.tr(key: "Navigation.Done") - case .navigationEdit: - return L10n.tr(key: "Navigation.Edit") - case .notificationLockedPreview: - return L10n.tr(key: "Notification.LockedPreview") - case .notificationSettingsMessagesPreview: - return L10n.tr(key: "NotificationSettings.MessagesPreview") - case .notificationSettingsNotificationTone: - return L10n.tr(key: "NotificationSettings.NotificationTone") - case .notificationSettingsResetNotifications: - return L10n.tr(key: "NotificationSettings.ResetNotifications") - case .notificationSettingsResetNotificationsText: - return L10n.tr(key: "NotificationSettings.ResetNotificationsText") - case .notificationSettingsToggleNotifications: - return L10n.tr(key: "NotificationSettings.ToggleNotifications") - case .notificationSettingsConfirmReset: - return L10n.tr(key: "NotificationSettings.Confirm.Reset") - case .notificationSettingsToneDefault: - return L10n.tr(key: "NotificationSettings.Tone.Default") - case .olwNPBQNTitle: - return L10n.tr(key: "Olw-nP-bQN.title") - case .oy7WFPoVTitle: - return L10n.tr(key: "OY7-WF-poV.title") - case .pa3QIU2kTitle: - return L10n.tr(key: "pa3-QI-u2k.title") - case .passcodeAutolock: - return L10n.tr(key: "Passcode.Autolock") - case .passcodeChange: - return L10n.tr(key: "Passcode.Change") - case .passcodeEnterCurrentPlaceholder: - return L10n.tr(key: "Passcode.EnterCurrentPlaceholder") - case .passcodeEnterNewPlaceholder: - return L10n.tr(key: "Passcode.EnterNewPlaceholder") - case .passcodeEnterPasscodePlaceholder: - return L10n.tr(key: "Passcode.EnterPasscodePlaceholder") - case .passcodeLogoutDescription: - return L10n.tr(key: "Passcode.LogoutDescription") - case .passcodeLogoutLinkText: - return L10n.tr(key: "Passcode.LogoutLinkText") - case .passcodeNext: - return L10n.tr(key: "Passcode.Next") - case .passcodeReEnterPlaceholder: - return L10n.tr(key: "Passcode.ReEnterPlaceholder") - case .passcodeTurnOff: - return L10n.tr(key: "Passcode.TurnOff") - case .passcodeTurnOn: - return L10n.tr(key: "Passcode.TurnOn") - case .passcodeTurnOnDescription: - return L10n.tr(key: "Passcode.TurnOnDescription") - case .passcodeAutoLockDisabled: - return L10n.tr(key: "Passcode.AutoLock.Disabled") - case .passcodeAutoLockIfAway(let p1): - return L10n.tr(key: "Passcode.AutoLock.IfAway", p1) - case .paymentsUnsupported: - return L10n.tr(key: "Payments.Unsupported") - case .peerDeletedUser: - return L10n.tr(key: "Peer.DeletedUser") - case .peerServiceNotifications: - return L10n.tr(key: "Peer.ServiceNotifications") - case .peerActivityChatMultiRecordingAudio(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.RecordingAudio", p1) - case .peerActivityChatMultiRecordingVideo(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.RecordingVideo", p1) - case .peerActivityChatMultiSendingAudio(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.SendingAudio", p1) - case .peerActivityChatMultiSendingFile(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.SendingFile", p1) - case .peerActivityChatMultiSendingPhoto(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.SendingPhoto", p1) - case .peerActivityChatMultiSendingVideo(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.SendingVideo", p1) - case .peerActivityChatMultiTypingText(let p1): - return L10n.tr(key: "Peer.Activity.Chat.Multi.TypingText", p1) - case .peerActivityUserRecordingAudio: - return L10n.tr(key: "Peer.Activity.User.RecordingAudio") - case .peerActivityUserRecordingVideo: - return L10n.tr(key: "Peer.Activity.User.RecordingVideo") - case .peerActivityUserSendingFile: - return L10n.tr(key: "Peer.Activity.User.SendingFile") - case .peerActivityUserSendingPhoto: - return L10n.tr(key: "Peer.Activity.User.SendingPhoto") - case .peerActivityUserSendingVideo: - return L10n.tr(key: "Peer.Activity.User.SendingVideo") - case .peerActivityUserTypingText: - return L10n.tr(key: "Peer.Activity.User.TypingText") - case .peerMediaSharedFilesEmptyList: - return L10n.tr(key: "Peer.Media.SharedFilesEmptyList") - case .peerMediaSharedLinksEmptyList: - return L10n.tr(key: "Peer.Media.SharedLinksEmptyList") - case .peerMediaSharedMediaEmptyList: - return L10n.tr(key: "Peer.Media.SharedMediaEmptyList") - case .peerMediaSharedMusicEmptyList: - return L10n.tr(key: "Peer.Media.SharedMusicEmptyList") - case .peerStatusChannel: - return L10n.tr(key: "Peer.Status.channel") - case .peerStatusGroup: - return L10n.tr(key: "Peer.Status.group") - case .peerStatusJustNow: - return L10n.tr(key: "Peer.Status.justNow") - case .peerStatusLastMonth: - return L10n.tr(key: "Peer.Status.lastMonth") - case .peerStatusLastSeenAt(let p1, let p2): - return L10n.tr(key: "Peer.Status.LastSeenAt", p1, p2) - case .peerStatusLastWeek: - return L10n.tr(key: "Peer.Status.lastWeek") - case .peerStatusMemberCountable(let p1): - return L10n.tr(key: "Peer.Status.Member_countable", p1) - case .peerStatusMemberFew(let p1): - return L10n.tr(key: "Peer.Status.Member_few", p1) - case .peerStatusMemberMany(let p1): - return L10n.tr(key: "Peer.Status.Member_many", p1) - case .peerStatusMemberOne(let p1): - return L10n.tr(key: "Peer.Status.Member_one", p1) - case .peerStatusMemberOther(let p1): - return L10n.tr(key: "Peer.Status.Member_other", p1) - case .peerStatusMemberTwo(let p1): - return L10n.tr(key: "Peer.Status.Member_two", p1) - case .peerStatusMemberZero(let p1): - return L10n.tr(key: "Peer.Status.Member_zero", p1) - case .peerStatusMinAgoCountable(let p1): - return L10n.tr(key: "Peer.Status.minAgo_countable", p1) - case .peerStatusMinAgoFew(let p1): - return L10n.tr(key: "Peer.Status.minAgo_few", p1) - case .peerStatusMinAgoMany(let p1): - return L10n.tr(key: "Peer.Status.minAgo_many", p1) - case .peerStatusMinAgoOne(let p1): - return L10n.tr(key: "Peer.Status.minAgo_one", p1) - case .peerStatusMinAgoOther(let p1): - return L10n.tr(key: "Peer.Status.minAgo_other", p1) - case .peerStatusMinAgoTwo(let p1): - return L10n.tr(key: "Peer.Status.minAgo_two", p1) - case .peerStatusMinAgoZero(let p1): - return L10n.tr(key: "Peer.Status.minAgo_zero", p1) - case .peerStatusOnline: - return L10n.tr(key: "Peer.Status.online") - case .peerStatusRecently: - return L10n.tr(key: "Peer.Status.recently") - case .peerStatusToday: - return L10n.tr(key: "Peer.Status.Today") - case .peerStatusYesterday: - return L10n.tr(key: "Peer.Status.Yesterday") - case .peerStatusMemberOnlineCountable(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_countable", p1) - case .peerStatusMemberOnlineFew(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_few", p1) - case .peerStatusMemberOnlineMany(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_many", p1) - case .peerStatusMemberOnlineOne(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_one", p1) - case .peerStatusMemberOnlineOther(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_other", p1) - case .peerStatusMemberOnlineTwo(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_two", p1) - case .peerStatusMemberOnlineZero(let p1): - return L10n.tr(key: "Peer.Status.Member.Online_zero", p1) - case .peerInfoAbout: - return L10n.tr(key: "PeerInfo.about") - case .peerInfoAddContact: - return L10n.tr(key: "PeerInfo.AddContact") - case .peerInfoAddMember: - return L10n.tr(key: "PeerInfo.AddMember") - case .peerInfoAdminLabel: - return L10n.tr(key: "PeerInfo.AdminLabel") - case .peerInfoAdmins: - return L10n.tr(key: "PeerInfo.Admins") - case .peerInfoBio: - return L10n.tr(key: "PeerInfo.bio") - case .peerInfoBlackList: - return L10n.tr(key: "PeerInfo.BlackList") - case .peerInfoBlockUser: - return L10n.tr(key: "PeerInfo.BlockUser") - case .peerInfoChannelReported: - return L10n.tr(key: "PeerInfo.ChannelReported") - case .peerInfoChannelType: - return L10n.tr(key: "PeerInfo.ChannelType") - case .peerInfoConvertToSupergroup: - return L10n.tr(key: "PeerInfo.ConvertToSupergroup") - case .peerInfoDeleteAndExit: - return L10n.tr(key: "PeerInfo.DeleteAndExit") - case .peerInfoDeleteChannel: - return L10n.tr(key: "PeerInfo.DeleteChannel") - case .peerInfoDeleteContact: - return L10n.tr(key: "PeerInfo.DeleteContact") - case .peerInfoDeleteSecretChat: - return L10n.tr(key: "PeerInfo.DeleteSecretChat") - case .peerInfoEncryptionKey: - return L10n.tr(key: "PeerInfo.EncryptionKey") - case .peerInfoGroupsInCommon: - return L10n.tr(key: "PeerInfo.GroupsInCommon") - case .peerInfoGroupType: - return L10n.tr(key: "PeerInfo.GroupType") - case .peerInfoInfo: - return L10n.tr(key: "PeerInfo.info") - case .peerInfoInviteLink: - return L10n.tr(key: "PeerInfo.InviteLink") - case .peerInfoLeaveChannel: - return L10n.tr(key: "PeerInfo.LeaveChannel") - case .peerInfoMembers: - return L10n.tr(key: "PeerInfo.Members") - case .peerInfoMembersHeaderCountable(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_countable", p1) - case .peerInfoMembersHeaderFew(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_few", p1) - case .peerInfoMembersHeaderMany(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_many", p1) - case .peerInfoMembersHeaderOne(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_one", p1) - case .peerInfoMembersHeaderOther(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_other", p1) - case .peerInfoMembersHeaderTwo(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_two", p1) - case .peerInfoMembersHeaderZero(let p1): - return L10n.tr(key: "PeerInfo.MembersHeader_zero", p1) - case .peerInfoNotifications: - return L10n.tr(key: "PeerInfo.Notifications") - case .peerInfoPhone: - return L10n.tr(key: "PeerInfo.Phone") - case .peerInfoPreHistory: - return L10n.tr(key: "PeerInfo.PreHistory") - case .peerInfoReport: - return L10n.tr(key: "PeerInfo.Report") - case .peerInfoSendMessage: - return L10n.tr(key: "PeerInfo.SendMessage") - case .peerInfoSetAboutDescription: - return L10n.tr(key: "PeerInfo.SetAboutDescription") - case .peerInfoSetAdmins: - return L10n.tr(key: "PeerInfo.SetAdmins") - case .peerInfoSetChannelPhoto: - return L10n.tr(key: "PeerInfo.SetChannelPhoto") - case .peerInfoSetGroupPhoto: - return L10n.tr(key: "PeerInfo.SetGroupPhoto") - case .peerInfoSetGroupStickersSet: - return L10n.tr(key: "PeerInfo.SetGroupStickersSet") - case .peerInfoShareContact: - return L10n.tr(key: "PeerInfo.ShareContact") - case .peerInfoSharedMedia: - return L10n.tr(key: "PeerInfo.SharedMedia") - case .peerInfoSharelink: - return L10n.tr(key: "PeerInfo.sharelink") - case .peerInfoSignMessages: - return L10n.tr(key: "PeerInfo.SignMessages") - case .peerInfoStartSecretChat: - return L10n.tr(key: "PeerInfo.StartSecretChat") - case .peerInfoUnblockUser: - return L10n.tr(key: "PeerInfo.UnblockUser") - case .peerInfoUsername: - return L10n.tr(key: "PeerInfo.username") - case .peerInfoAboutPlaceholder: - return L10n.tr(key: "PeerInfo.About.Placeholder") - case .peerInfoBotStatusHasAccess: - return L10n.tr(key: "PeerInfo.BotStatus.HasAccess") - case .peerInfoBotStatusHasNoAccess: - return L10n.tr(key: "PeerInfo.BotStatus.HasNoAccess") - case .peerInfoChannelNamePlaceholder: - return L10n.tr(key: "PeerInfo.ChannelName.Placeholder") - case .peerInfoConfirmAddMember(let p1): - return L10n.tr(key: "PeerInfo.Confirm.AddMember", p1) - case .peerInfoConfirmAddMembers(let p1): - return L10n.tr(key: "PeerInfo.Confirm.AddMembers", p1) - case .peerInfoConfirmDeleteChat(let p1): - return L10n.tr(key: "PeerInfo.Confirm.DeleteChat", p1) - case .peerInfoConfirmDeleteContact: - return L10n.tr(key: "PeerInfo.Confirm.DeleteContact") - case .peerInfoConfirmLeaveChannel: - return L10n.tr(key: "PeerInfo.Confirm.LeaveChannel") - case .peerInfoConfirmLeaveGroup: - return L10n.tr(key: "PeerInfo.Confirm.LeaveGroup") - case .peerInfoConfirmRemovePeer(let p1): - return L10n.tr(key: "PeerInfo.Confirm.RemovePeer", p1) - case .peerInfoConfirmStartSecretChat(let p1): - return L10n.tr(key: "PeerInfo.Confirm.StartSecretChat", p1) - case .peerInfoFirstNamePlaceholder: - return L10n.tr(key: "PeerInfo.FirstName.Placeholder") - case .peerInfoGroupNamePlaceholder: - return L10n.tr(key: "PeerInfo.GroupName.Placeholder") - case .peerInfoGroupTypePrivate: - return L10n.tr(key: "PeerInfo.GroupType.Private") - case .peerInfoGroupTypePublic: - return L10n.tr(key: "PeerInfo.GroupType.Public") - case .peerInfoLastNamePlaceholder: - return L10n.tr(key: "PeerInfo.LastName.Placeholder") - case .peerInfoPreHistoryHidden: - return L10n.tr(key: "PeerInfo.PreHistory.Hidden") - case .peerInfoPreHistoryVisible: - return L10n.tr(key: "PeerInfo.PreHistory.Visible") - case .peerInfoSignMessagesDesc: - return L10n.tr(key: "PeerInfo.SignMessages.Desc") - case .peerMediaSharedMedia: - return L10n.tr(key: "PeerMedia.SharedMedia") - case .peerMediaPopoverSharedAudio: - return L10n.tr(key: "PeerMedia.Popover.SharedAudio") - case .peerMediaPopoverSharedFiles: - return L10n.tr(key: "PeerMedia.Popover.SharedFiles") - case .peerMediaPopoverSharedLinks: - return L10n.tr(key: "PeerMedia.Popover.SharedLinks") - case .peerMediaPopoverSharedMedia: - return L10n.tr(key: "PeerMedia.Popover.SharedMedia") - case .preHistorySettingsHeader: - return L10n.tr(key: "PreHistorySettings.Header") - case .preHistorySettingsDescriptionHidden: - return L10n.tr(key: "PreHistorySettings.Description.Hidden") - case .preHistorySettingsDescriptionVisible: - return L10n.tr(key: "PreHistorySettings.Description.Visible") - case .presenceBot: - return L10n.tr(key: "Presence.bot") - case .previderSenderCaptionPlaceholder: - return L10n.tr(key: "PreviderSender.CaptionPlaceholder") - case .previewSenderCompressFile: - return L10n.tr(key: "PreviewSender.CompressFile") - case .privacySettingsActiveSessions: - return L10n.tr(key: "PrivacySettings.ActiveSessions") - case .privacySettingsBlockedUsers: - return L10n.tr(key: "PrivacySettings.BlockedUsers") - case .privacySettingsGroups: - return L10n.tr(key: "PrivacySettings.Groups") - case .privacySettingsLastSeen: - return L10n.tr(key: "PrivacySettings.LastSeen") - case .privacySettingsPasscode: - return L10n.tr(key: "PrivacySettings.Passcode") - case .privacySettingsPrivacyHeader: - return L10n.tr(key: "PrivacySettings.PrivacyHeader") - case .privacySettingsProxyHeader: - return L10n.tr(key: "PrivacySettings.ProxyHeader") - case .privacySettingsSecurityHeader: - return L10n.tr(key: "PrivacySettings.SecurityHeader") - case .privacySettingsTwoStepVerification: - return L10n.tr(key: "PrivacySettings.TwoStepVerification") - case .privacySettingsUseProxy: - return L10n.tr(key: "PrivacySettings.UseProxy") - case .privacySettingsVoiceCalls: - return L10n.tr(key: "PrivacySettings.VoiceCalls") - case .privacySettingsPeerSelectAddNew: - return L10n.tr(key: "PrivacySettings.PeerSelect.AddNew") - case .privacySettingsControllerAddUsers: - return L10n.tr(key: "PrivacySettingsController.AddUsers") - case .privacySettingsControllerAlwaysAllow: - return L10n.tr(key: "PrivacySettingsController.AlwaysAllow") - case .privacySettingsControllerAlwaysShare: - return L10n.tr(key: "PrivacySettingsController.AlwaysShare") - case .privacySettingsControllerAlwaysShareWith: - return L10n.tr(key: "PrivacySettingsController.AlwaysShareWith") - case .privacySettingsControllerEverbody: - return L10n.tr(key: "PrivacySettingsController.Everbody") - case .privacySettingsControllerGroupDescription: - return L10n.tr(key: "PrivacySettingsController.GroupDescription") - case .privacySettingsControllerGroupHeader: - return L10n.tr(key: "PrivacySettingsController.GroupHeader") - case .privacySettingsControllerHeader: - return L10n.tr(key: "PrivacySettingsController.Header") - case .privacySettingsControllerLastSeenDescription: - return L10n.tr(key: "PrivacySettingsController.LastSeenDescription") - case .privacySettingsControllerLastSeenHeader: - return L10n.tr(key: "PrivacySettingsController.LastSeenHeader") - case .privacySettingsControllerMyContacts: - return L10n.tr(key: "PrivacySettingsController.MyContacts") - case .privacySettingsControllerNeverAllow: - return L10n.tr(key: "PrivacySettingsController.NeverAllow") - case .privacySettingsControllerNeverShare: - return L10n.tr(key: "PrivacySettingsController.NeverShare") - case .privacySettingsControllerNeverShareWith: - return L10n.tr(key: "PrivacySettingsController.NeverShareWith") - case .privacySettingsControllerNobody: - return L10n.tr(key: "PrivacySettingsController.Nobody") - case .privacySettingsControllerPeerInfo: - return L10n.tr(key: "PrivacySettingsController.PeerInfo") - case .privacySettingsControllerPhoneCallDescription: - return L10n.tr(key: "PrivacySettingsController.PhoneCallDescription") - case .privacySettingsControllerPhoneCallHeader: - return L10n.tr(key: "PrivacySettingsController.PhoneCallHeader") - case .privacySettingsControllerUserCountCountable(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_countable", p1) - case .privacySettingsControllerUserCountFew(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_few", p1) - case .privacySettingsControllerUserCountMany(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_many", p1) - case .privacySettingsControllerUserCountOne(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_one", p1) - case .privacySettingsControllerUserCountOther(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_other", p1) - case .privacySettingsControllerUserCountTwo(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_two", p1) - case .privacySettingsControllerUserCountZero(let p1): - return L10n.tr(key: "PrivacySettingsController.UserCount_zero", p1) - case .proxyForceDisable(let p1): - return L10n.tr(key: "Proxy.ForceDisable", p1) - case .proxyForceEnableHeader: - return L10n.tr(key: "Proxy.ForceEnable.Header") - case .proxyForceEnableText: - return L10n.tr(key: "Proxy.ForceEnable.Text") - case .proxyForceEnableTextIP(let p1): - return L10n.tr(key: "Proxy.ForceEnable.Text.IP", p1) - case .proxyForceEnableTextPassword(let p1): - return L10n.tr(key: "Proxy.ForceEnable.Text.Password", p1) - case .proxyForceEnableTextPort(let p1): - return L10n.tr(key: "Proxy.ForceEnable.Text.Port", p1) - case .proxyForceEnableTextUsername(let p1): - return L10n.tr(key: "Proxy.ForceEnable.Text.Username", p1) - case .proxySettingsConnectionHeader: - return L10n.tr(key: "ProxySettings.ConnectionHeader") - case .proxySettingsCredentialsHeader: - return L10n.tr(key: "ProxySettings.CredentialsHeader") - case .proxySettingsDisabled: - return L10n.tr(key: "ProxySettings.Disabled") - case .proxySettingsExportDescription: - return L10n.tr(key: "ProxySettings.ExportDescription") - case .proxySettingsExportLink: - return L10n.tr(key: "ProxySettings.ExportLink") - case .proxySettingsPassword: - return L10n.tr(key: "ProxySettings.Password") - case .proxySettingsPort: - return L10n.tr(key: "ProxySettings.Port") - case .proxySettingsProxyNotFound: - return L10n.tr(key: "ProxySettings.ProxyNotFound") - case .proxySettingsSave: - return L10n.tr(key: "ProxySettings.Save") - case .proxySettingsServer: - return L10n.tr(key: "ProxySettings.Server") - case .proxySettingsShare: - return L10n.tr(key: "ProxySettings.Share") - case .proxySettingsSocks5: - return L10n.tr(key: "ProxySettings.Socks5") - case .proxySettingsUsername: - return L10n.tr(key: "ProxySettings.Username") - case .quickLookPreview: - return L10n.tr(key: "QuickLook.Preview") - case .quickSwitcherDescription: - return L10n.tr(key: "QuickSwitcher.Description") - case .quickSwitcherPopular: - return L10n.tr(key: "QuickSwitcher.Popular") - case .quickSwitcherRecently: - return L10n.tr(key: "QuickSwitcher.Recently") - case .qvCM9Y7gTitle: - return L10n.tr(key: "QvC-M9-y7g.title") - case .r4oN2Eq4Title: - return L10n.tr(key: "R4o-n2-Eq4.title") - case .rbDRhWINTitle: - return L10n.tr(key: "rbD-Rh-wIN.title") - case .recentCallsEmpty: - return L10n.tr(key: "RecentCalls.Empty") - case .recentSessionsRevoke: - return L10n.tr(key: "RecentSessions.Revoke") - case .recentSessionsConfirmRevoke: - return L10n.tr(key: "RecentSessions.Confirm.Revoke") - case .recentSessionsConfirmTerminateOthers: - return L10n.tr(key: "RecentSessions.Confirm.TerminateOthers") - case .reportReasonPorno: - return L10n.tr(key: "ReportReason.Porno") - case .reportReasonSpam: - return L10n.tr(key: "ReportReason.Spam") - case .reportReasonViolence: - return L10n.tr(key: "ReportReason.Violence") - case .rgMF4YcnTitle: - return L10n.tr(key: "rgM-f4-ycn.title") - case .ruw6mB2mTitle: - return L10n.tr(key: "Ruw-6m-B2m.title") - case .searchSeparatorChatsAndContacts: - return L10n.tr(key: "Search.Separator.ChatsAndContacts") - case .searchSeparatorGlobalPeers: - return L10n.tr(key: "Search.Separator.GlobalPeers") - case .searchSeparatorMessages: - return L10n.tr(key: "Search.Separator.Messages") - case .searchSeparatorPopular: - return L10n.tr(key: "Search.Separator.Popular") - case .searchSeparatorRecent: - return L10n.tr(key: "Search.Separator.Recent") - case .searchFieldSearch: - return L10n.tr(key: "SearchField.Search") - case .secretTimerOff: - return L10n.tr(key: "SecretTimer.Off") - case .separatorClear: - return L10n.tr(key: "Separator.Clear") - case .separatorShowLess: - return L10n.tr(key: "Separator.ShowLess") - case .separatorShowMore: - return L10n.tr(key: "Separator.ShowMore") - case .serviceMessageDesturctingPhoto(let p1): - return L10n.tr(key: "ServiceMessage.DesturctingPhoto", p1) - case .serviceMessageDesturctingVideo(let p1): - return L10n.tr(key: "ServiceMessage.DesturctingVideo", p1) - case .serviceMessageExpiredFile: - return L10n.tr(key: "ServiceMessage.ExpiredFile") - case .serviceMessageExpiredPhoto: - return L10n.tr(key: "ServiceMessage.ExpiredPhoto") - case .serviceMessageExpiredVideo: - return L10n.tr(key: "ServiceMessage.ExpiredVideo") - case .serviceMessageDesturctingPhotoYou(let p1): - return L10n.tr(key: "ServiceMessage.DesturctingPhoto.You", p1) - case .serviceMessageDesturctingVideoYou(let p1): - return L10n.tr(key: "ServiceMessage.DesturctingVideo.You", p1) - case .sessionsActiveSessionsHeader: - return L10n.tr(key: "Sessions.ActiveSessionsHeader") - case .sessionsCurrentSessionHeader: - return L10n.tr(key: "Sessions.CurrentSessionHeader") - case .sessionsTerminateDescription: - return L10n.tr(key: "Sessions.TerminateDescription") - case .sessionsTerminateOthers: - return L10n.tr(key: "Sessions.TerminateOthers") - case .shareLinkCopied: - return L10n.tr(key: "Share.Link.Copied") - case .shareExtensionCancel: - return L10n.tr(key: "ShareExtension.Cancel") - case .shareExtensionSearch: - return L10n.tr(key: "ShareExtension.Search") - case .shareExtensionShare: - return L10n.tr(key: "ShareExtension.Share") - case .shareExtensionPasscodeNext: - return L10n.tr(key: "ShareExtension.Passcode.Next") - case .shareExtensionPasscodePlaceholder: - return L10n.tr(key: "ShareExtension.Passcode.Placeholder") - case .shareExtensionUnauthorizedDescription: - return L10n.tr(key: "ShareExtension.Unauthorized.Description") - case .shareExtensionUnauthorizedOK: - return L10n.tr(key: "ShareExtension.Unauthorized.OK") - case .shareModalSearchPlaceholder: - return L10n.tr(key: "ShareModal.Search.Placeholder") - case .sidebarAvalability: - return L10n.tr(key: "Sidebar.Avalability") - case .stickerPackAdd(let p1): - return L10n.tr(key: "StickerPack.Add", p1) - case .stickerPackRemove(let p1): - return L10n.tr(key: "StickerPack.Remove", p1) - case .stickersGroupStickers: - return L10n.tr(key: "Stickers.GroupStickers") - case .stickersRecent: - return L10n.tr(key: "Stickers.Recent") - case .stickersSetCount(let p1): - return L10n.tr(key: "Stickers.Set.Count", p1) - case .stickerSetRemove: - return L10n.tr(key: "StickerSet.Remove") - case .storageClear(let p1): - return L10n.tr(key: "Storage.Clear", p1) - case .storageClearAudio: - return L10n.tr(key: "Storage.Clear.Audio") - case .storageClearDocuments: - return L10n.tr(key: "Storage.Clear.Documents") - case .storageClearPhotos: - return L10n.tr(key: "Storage.Clear.Photos") - case .storageClearVideos: - return L10n.tr(key: "Storage.Clear.Videos") - case .storageUsageCalculating: - return L10n.tr(key: "StorageUsage.Calculating") - case .storageUsageChatsHeader: - return L10n.tr(key: "StorageUsage.ChatsHeader") - case .storageUsageKeepMedia: - return L10n.tr(key: "StorageUsage.KeepMedia") - case .storageUsageKeepMediaDescription: - return L10n.tr(key: "StorageUsage.KeepMedia.Description") - case .suggestLocalizationHeader: - return L10n.tr(key: "Suggest.Localization.Header") - case .suggestLocalizationOther: - return L10n.tr(key: "Suggest.Localization.Other") - case .supergroupConvertButton: - return L10n.tr(key: "Supergroup.Convert.Button") - case .supergroupConvertDescription: - return L10n.tr(key: "Supergroup.Convert.Description") - case .supergroupConvertUndone: - return L10n.tr(key: "Supergroup.Convert.Undone") - case .supergroupDeleteRestrictionBanUser: - return L10n.tr(key: "Supergroup.DeleteRestriction.BanUser") - case .supergroupDeleteRestrictionDeleteAllMessages: - return L10n.tr(key: "Supergroup.DeleteRestriction.DeleteAllMessages") - case .supergroupDeleteRestrictionDeleteMessage: - return L10n.tr(key: "Supergroup.DeleteRestriction.DeleteMessage") - case .supergroupDeleteRestrictionReportSpam: - return L10n.tr(key: "Supergroup.DeleteRestriction.ReportSpam") - case .sZhCtGQSTitle: - return L10n.tr(key: "sZh-ct-GQS.title") - case .td7AD5loTitle: - return L10n.tr(key: "Td7-aD-5lo.title") - case .telegramAppearanceViewController: - return L10n.tr(key: "Telegram.AppearanceViewController") - case .telegramArchivedStickerPacksController: - return L10n.tr(key: "Telegram.ArchivedStickerPacksController") - case .telegramBioViewController: - return L10n.tr(key: "Telegram.BioViewController") - case .telegramBlockedPeersViewController: - return L10n.tr(key: "Telegram.BlockedPeersViewController") - case .telegramChannelAdminsViewController: - return L10n.tr(key: "Telegram.ChannelAdminsViewController") - case .telegramChannelBlacklistViewController: - return L10n.tr(key: "Telegram.ChannelBlacklistViewController") - case .telegramChannelEventLogController: - return L10n.tr(key: "Telegram.ChannelEventLogController") - case .telegramChannelIntroViewController: - return L10n.tr(key: "Telegram.ChannelIntroViewController") - case .telegramChannelMembersViewController: - return L10n.tr(key: "Telegram.ChannelMembersViewController") - case .telegramChannelVisibilityController: - return L10n.tr(key: "Telegram.ChannelVisibilityController") - case .telegramConvertGroupViewController: - return L10n.tr(key: "Telegram.ConvertGroupViewController") - case .telegramEmptyChatViewController: - return L10n.tr(key: "Telegram.EmptyChatViewController") - case .telegramFeaturedStickerPacksController: - return L10n.tr(key: "Telegram.FeaturedStickerPacksController") - case .telegramGeneralSettingsViewController: - return L10n.tr(key: "Telegram.GeneralSettingsViewController") - case .telegramGroupAdminsController: - return L10n.tr(key: "Telegram.GroupAdminsController") - case .telegramGroupsInCommonViewController: - return L10n.tr(key: "Telegram.GroupsInCommonViewController") - case .telegramGroupStickerSetController: - return L10n.tr(key: "Telegram.GroupStickerSetController") - case .telegramInstalledStickerPacksController: - return L10n.tr(key: "Telegram.InstalledStickerPacksController") - case .telegramLanguageViewController: - return L10n.tr(key: "Telegram.LanguageViewController") - case .telegramLayoutAccountController: - return L10n.tr(key: "Telegram.LayoutAccountController") - case .telegramLayoutRecentCallsViewController: - return L10n.tr(key: "Telegram.LayoutRecentCallsViewController") - case .telegramLinkInvationController: - return L10n.tr(key: "Telegram.LinkInvationController") - case .telegramMainViewController: - return L10n.tr(key: "Telegram.MainViewController") - case .telegramNotificationSettingsViewController: - return L10n.tr(key: "Telegram.NotificationSettingsViewController") - case .telegramPasscodeSettingsViewController: - return L10n.tr(key: "Telegram.PasscodeSettingsViewController") - case .telegramPeerInfoController: - return L10n.tr(key: "Telegram.PeerInfoController") - case .telegramPhoneNumberConfirmController: - return L10n.tr(key: "Telegram.PhoneNumberConfirmController") - case .telegramPhoneNumberIntroController: - return L10n.tr(key: "Telegram.PhoneNumberIntroController") - case .telegramPreHistorySettingsController: - return L10n.tr(key: "Telegram.PreHistorySettingsController") - case .telegramPrivacyAndSecurityViewController: - return L10n.tr(key: "Telegram.PrivacyAndSecurityViewController") - case .telegramProxySettingsViewController: - return L10n.tr(key: "Telegram.ProxySettingsViewController") - case .telegramRecentSessionsController: - return L10n.tr(key: "Telegram.RecentSessionsController") - case .telegramSecretChatKeyViewController: - return L10n.tr(key: "Telegram.SecretChatKeyViewController") - case .telegramSelectPeersController: - return L10n.tr(key: "Telegram.SelectPeersController") - case .telegramStorageUsageController: - return L10n.tr(key: "Telegram.StorageUsageController") - case .telegramTwoStepVerificationUnlockController: - return L10n.tr(key: "Telegram.TwoStepVerificationUnlockController") - case .telegramUsernameSettingsViewController: - return L10n.tr(key: "Telegram.UsernameSettingsViewController") - case .textCopy: - return L10n.tr(key: "Text.Copy") - case .textViewTransformBold: - return L10n.tr(key: "TextView.Transform.Bold") - case .textViewTransformCode: - return L10n.tr(key: "TextView.Transform.Code") - case .textViewTransformItalic: - return L10n.tr(key: "TextView.Transform.Italic") - case .timeAt: - return L10n.tr(key: "Time.at") - case .timeLastSeen: - return L10n.tr(key: "Time.last_seen") - case .timeToday: - return L10n.tr(key: "Time.today") - case .timeYesterday: - return L10n.tr(key: "Time.yesterday") - case .timerDaysCountable(let p1): - return L10n.tr(key: "Timer.Days_countable", p1) - case .timerDaysFew(let p1): - return L10n.tr(key: "Timer.Days_few", p1) - case .timerDaysMany(let p1): - return L10n.tr(key: "Timer.Days_many", p1) - case .timerDaysOne(let p1): - return L10n.tr(key: "Timer.Days_one", p1) - case .timerDaysOther(let p1): - return L10n.tr(key: "Timer.Days_other", p1) - case .timerDaysTwo(let p1): - return L10n.tr(key: "Timer.Days_two", p1) - case .timerDaysZero(let p1): - return L10n.tr(key: "Timer.Days_zero", p1) - case .timerForever: - return L10n.tr(key: "Timer.Forever") - case .timerHoursCountable(let p1): - return L10n.tr(key: "Timer.Hours_countable", p1) - case .timerHoursFew(let p1): - return L10n.tr(key: "Timer.Hours_few", p1) - case .timerHoursMany(let p1): - return L10n.tr(key: "Timer.Hours_many", p1) - case .timerHoursOne(let p1): - return L10n.tr(key: "Timer.Hours_one", p1) - case .timerHoursOther(let p1): - return L10n.tr(key: "Timer.Hours_other", p1) - case .timerHoursTwo(let p1): - return L10n.tr(key: "Timer.Hours_two", p1) - case .timerHoursZero(let p1): - return L10n.tr(key: "Timer.Hours_zero", p1) - case .timerMinutesCountable(let p1): - return L10n.tr(key: "Timer.Minutes_countable", p1) - case .timerMinutesFew(let p1): - return L10n.tr(key: "Timer.Minutes_few", p1) - case .timerMinutesMany(let p1): - return L10n.tr(key: "Timer.Minutes_many", p1) - case .timerMinutesOne(let p1): - return L10n.tr(key: "Timer.Minutes_one", p1) - case .timerMinutesOther(let p1): - return L10n.tr(key: "Timer.Minutes_other", p1) - case .timerMinutesTwo(let p1): - return L10n.tr(key: "Timer.Minutes_two", p1) - case .timerMinutesZero(let p1): - return L10n.tr(key: "Timer.Minutes_zero", p1) - case .timerMonthsCountable(let p1): - return L10n.tr(key: "Timer.Months_countable", p1) - case .timerMonthsFew(let p1): - return L10n.tr(key: "Timer.Months_few", p1) - case .timerMonthsMany(let p1): - return L10n.tr(key: "Timer.Months_many", p1) - case .timerMonthsOne(let p1): - return L10n.tr(key: "Timer.Months_one", p1) - case .timerMonthsOther(let p1): - return L10n.tr(key: "Timer.Months_other", p1) - case .timerMonthsTwo(let p1): - return L10n.tr(key: "Timer.Months_two", p1) - case .timerMonthsZero(let p1): - return L10n.tr(key: "Timer.Months_zero", p1) - case .timerSecondsCountable(let p1): - return L10n.tr(key: "Timer.Seconds_countable", p1) - case .timerSecondsFew(let p1): - return L10n.tr(key: "Timer.Seconds_few", p1) - case .timerSecondsMany(let p1): - return L10n.tr(key: "Timer.Seconds_many", p1) - case .timerSecondsOne(let p1): - return L10n.tr(key: "Timer.Seconds_one", p1) - case .timerSecondsOther(let p1): - return L10n.tr(key: "Timer.Seconds_other", p1) - case .timerSecondsTwo(let p1): - return L10n.tr(key: "Timer.Seconds_two", p1) - case .timerSecondsZero(let p1): - return L10n.tr(key: "Timer.Seconds_zero", p1) - case .timerWeeksCountable(let p1): - return L10n.tr(key: "Timer.Weeks_countable", p1) - case .timerWeeksFew(let p1): - return L10n.tr(key: "Timer.Weeks_few", p1) - case .timerWeeksMany(let p1): - return L10n.tr(key: "Timer.Weeks_many", p1) - case .timerWeeksOne(let p1): - return L10n.tr(key: "Timer.Weeks_one", p1) - case .timerWeeksOther(let p1): - return L10n.tr(key: "Timer.Weeks_other", p1) - case .timerWeeksTwo(let p1): - return L10n.tr(key: "Timer.Weeks_two", p1) - case .timerWeeksZero(let p1): - return L10n.tr(key: "Timer.Weeks_zero", p1) - case .timerYearsCountable(let p1): - return L10n.tr(key: "Timer.Years_countable", p1) - case .timerYearsFew(let p1): - return L10n.tr(key: "Timer.Years_few", p1) - case .timerYearsMany(let p1): - return L10n.tr(key: "Timer.Years_many", p1) - case .timerYearsOne(let p1): - return L10n.tr(key: "Timer.Years_one", p1) - case .timerYearsOther(let p1): - return L10n.tr(key: "Timer.Years_other", p1) - case .timerYearsTwo(let p1): - return L10n.tr(key: "Timer.Years_two", p1) - case .timerYearsZero(let p1): - return L10n.tr(key: "Timer.Years_zero", p1) - case .tRrPd1PSTitle: - return L10n.tr(key: "tRr-pd-1PS.title") - case .twoStepAuthEmailSkip: - return L10n.tr(key: "TwoStep.AuthEmailSkip") - case .twoStepAuthAnError: - return L10n.tr(key: "TwoStepAuth.AnError") - case .twoStepAuthChangeEmail: - return L10n.tr(key: "TwoStepAuth.ChangeEmail") - case .twoStepAuthChangePassword: - return L10n.tr(key: "TwoStepAuth.ChangePassword") - case .twoStepAuthConfirmationAbort: - return L10n.tr(key: "TwoStepAuth.ConfirmationAbort") - case .twoStepAuthConfirmationText: - return L10n.tr(key: "TwoStepAuth.ConfirmationText") - case .twoStepAuthEmail: - return L10n.tr(key: "TwoStepAuth.Email") - case .twoStepAuthEmailHelp: - return L10n.tr(key: "TwoStepAuth.EmailHelp") - case .twoStepAuthEmailInvalid: - return L10n.tr(key: "TwoStepAuth.EmailInvalid") - case .twoStepAuthEmailSent: - return L10n.tr(key: "TwoStepAuth.EmailSent") - case .twoStepAuthEmailSkipAlert: - return L10n.tr(key: "TwoStepAuth.EmailSkipAlert") - case .twoStepAuthEnterPasswordForgot: - return L10n.tr(key: "TwoStepAuth.EnterPasswordForgot") - case .twoStepAuthEnterPasswordHelp: - return L10n.tr(key: "TwoStepAuth.EnterPasswordHelp") - case .twoStepAuthEnterPasswordHint(let p1): - return L10n.tr(key: "TwoStepAuth.EnterPasswordHint", p1) - case .twoStepAuthEnterPasswordPassword: - return L10n.tr(key: "TwoStepAuth.EnterPasswordPassword") - case .twoStepAuthFloodError: - return L10n.tr(key: "TwoStepAuth.FloodError") - case .twoStepAuthGenericError: - return L10n.tr(key: "TwoStepAuth.GenericError") - case .twoStepAuthGenericHelp: - return L10n.tr(key: "TwoStepAuth.GenericHelp") - case .twoStepAuthPasswordTitle: - return L10n.tr(key: "TwoStepAuth.PasswordTitle") - case .twoStepAuthPendingEmailHelp(let p1): - return L10n.tr(key: "TwoStepAuth.PendingEmailHelp", p1) - case .twoStepAuthRecoveryCode: - return L10n.tr(key: "TwoStepAuth.RecoveryCode") - case .twoStepAuthRecoveryCodeExpired: - return L10n.tr(key: "TwoStepAuth.RecoveryCodeExpired") - case .twoStepAuthRecoveryCodeHelp: - return L10n.tr(key: "TwoStepAuth.RecoveryCodeHelp") - case .twoStepAuthRecoveryCodeInvalid: - return L10n.tr(key: "TwoStepAuth.RecoveryCodeInvalid") - case .twoStepAuthRecoveryEmailUnavailable(let p1): - return L10n.tr(key: "TwoStepAuth.RecoveryEmailUnavailable", p1) - case .twoStepAuthRecoveryFailed: - return L10n.tr(key: "TwoStepAuth.RecoveryFailed") - case .twoStepAuthRecoverySent(let p1): - return L10n.tr(key: "TwoStepAuth.RecoverySent", p1) - case .twoStepAuthRecoveryTitle: - return L10n.tr(key: "TwoStepAuth.RecoveryTitle") - case .twoStepAuthRecoveryUnavailable: - return L10n.tr(key: "TwoStepAuth.RecoveryUnavailable") - case .twoStepAuthRemovePassword: - return L10n.tr(key: "TwoStepAuth.RemovePassword") - case .twoStepAuthSetPassword: - return L10n.tr(key: "TwoStepAuth.SetPassword") - case .twoStepAuthSetPasswordHelp: - return L10n.tr(key: "TwoStepAuth.SetPasswordHelp") - case .twoStepAuthSetupEmail: - return L10n.tr(key: "TwoStepAuth.SetupEmail") - case .twoStepAuthSetupEmailTitle: - return L10n.tr(key: "TwoStepAuth.SetupEmailTitle") - case .twoStepAuthSetupHint: - return L10n.tr(key: "TwoStepAuth.SetupHint") - case .twoStepAuthSetupHintTitle: - return L10n.tr(key: "TwoStepAuth.SetupHintTitle") - case .twoStepAuthSetupPasswordConfirmFailed: - return L10n.tr(key: "TwoStepAuth.SetupPasswordConfirmFailed") - case .twoStepAuthSetupPasswordConfirmPassword: - return L10n.tr(key: "TwoStepAuth.SetupPasswordConfirmPassword") - case .twoStepAuthSetupPasswordEnterPassword: - return L10n.tr(key: "TwoStepAuth.SetupPasswordEnterPassword") - case .twoStepAuthSetupPasswordEnterPasswordNew: - return L10n.tr(key: "TwoStepAuth.SetupPasswordEnterPasswordNew") - case .twoStepAuthSetupPasswordTitle: - return L10n.tr(key: "TwoStepAuth.SetupPasswordTitle") - case .twoStepAuthConfirmDisablePassword: - return L10n.tr(key: "TwoStepAuth.Confirm.DisablePassword") - case .twoStepAuthErrorGeneric: - return L10n.tr(key: "TwoStepAuth.Error.Generic") - case .twoStepAuthErrorHaventEmail: - return L10n.tr(key: "TwoStepAuth.Error.HaventEmail") - case .twoStepAuthErrorInvalidEmail: - return L10n.tr(key: "TwoStepAuth.Error.InvalidEmail") - case .twoStepAuthErrorLimitExceeded: - return L10n.tr(key: "TwoStepAuth.Error.LimitExceeded") - case .twoStepAuthErrorPasswordsDontMatch: - return L10n.tr(key: "TwoStepAuth.Error.PasswordsDontMatch") - case .uezBsLqGTitle: - return L10n.tr(key: "UEZ-Bs-lqG.title") - case .uQyDDJDrTitle: - return L10n.tr(key: "uQy-DD-JDr.title") - case .uRlIYUnGTitle: - return L10n.tr(key: "uRl-iY-unG.title") - case .usernameSettingsAvailable(let p1): - return L10n.tr(key: "UsernameSettings.available", p1) - case .usernameSettingsChangeDescription: - return L10n.tr(key: "UsernameSettings.ChangeDescription") - case .usernameSettingsDone: - return L10n.tr(key: "UsernameSettings.Done") - case .usernameSettingsInputPlaceholder: - return L10n.tr(key: "UsernameSettings.InputPlaceholder") - case .vdrFpXzOTitle: - return L10n.tr(key: "Vdr-fp-XzO.title") - case .vmV6d7jITitle: - return L10n.tr(key: "vmV-6d-7jI.title") - case .w486f4DlTitle: - return L10n.tr(key: "W48-6f-4Dl.title") - case .weekdayShortFriday: - return L10n.tr(key: "Weekday.ShortFriday") - case .weekdayShortMonday: - return L10n.tr(key: "Weekday.ShortMonday") - case .weekdayShortSaturday: - return L10n.tr(key: "Weekday.ShortSaturday") - case .weekdayShortSunday: - return L10n.tr(key: "Weekday.ShortSunday") - case .weekdayShortThursday: - return L10n.tr(key: "Weekday.ShortThursday") - case .weekdayShortTuesday: - return L10n.tr(key: "Weekday.ShortTuesday") - case .weekdayShortWednesday: - return L10n.tr(key: "Weekday.ShortWednesday") - case .weT3VZwkTitle: - return L10n.tr(key: "WeT-3V-zwk.title") - case .x3vGGIWUTitle: - return L10n.tr(key: "x3v-GG-iWU.title") - case .z6FFW3nzTitle: - return L10n.tr(key: "z6F-FW-3nz.title") - case ._NS138Title: - return L10n.tr(key: "_NS:138.title") - case ._NS70Title: - return L10n.tr(key: "_NS:70.title") - case ._NS88Title: - return L10n.tr(key: "_NS:88.title") - } + internal static var ns88Title: String { return L10n.tr("Localizable", "_NS88.title") } + /// Window + internal static var ns163Title: String { return L10n.tr("Localizable", "_NS:163.title") } + /// View + internal static var ns77Title: String { return L10n.tr("Localizable", "_NS:77.title") } + /// Edit + internal static var ns99Title: String { return L10n.tr("Localizable", "_NS:99.title") } + /// Global Search + internal static var aMaRbKjVTitle: String { return L10n.tr("Localizable", "aMa-rb-kjV.title") } + /// Window + internal static var aufd15bRTitle: String { return L10n.tr("Localizable", "aUF-d1-5bR.title") } + /// Transformations + internal static var c8aY6VQdTitle: String { return L10n.tr("Localizable", "c8a-y6-VQd.title") } + /// Smart Links + internal static var cwLP1JidTitle: String { return L10n.tr("Localizable", "cwL-P1-jid.title") } + /// Make Lower Case + internal static var d9MCDAMdTitle: String { return L10n.tr("Localizable", "d9M-CD-aMd.title") } + /// Undo + internal static var drj4nYzgTitle: String { return L10n.tr("Localizable", "dRJ-4n-Yzg.title") } + /// Paste + internal static var gvau4SdLTitle: String { return L10n.tr("Localizable", "gVA-U4-sdL.title") } + /// Smart Quotes + internal static var hQb2vFYvTitle: String { return L10n.tr("Localizable", "hQb-2v-fYv.title") } + /// Check Document Now + internal static var hz2CUCR7Title: String { return L10n.tr("Localizable", "hz2-CU-CR7.title") } + /// Check Grammar With Spelling + internal static var mk62p4JGTitle: String { return L10n.tr("Localizable", "mK6-2p-4JG.title") } + /// Delete + internal static var pa3QIU2kTitle: String { return L10n.tr("Localizable", "pa3-QI-u2k.title") } + /// Leave + internal static var peerInfoConfirmLeave: String { return L10n.tr("Localizable", "peerInfo.Confirm.Leave") } + /// This will delete all messages and media in this chat from your Telegram cloud. Other members of the group will still have them. + internal static var peerInfoConfirmClearHistoryGroup: String { return L10n.tr("Localizable", "peerInfo.Confirm.ClearHistory.Group") } + /// This will delete all messages and media in this chat from your Telegram cloud. + internal static var peerInfoConfirmClearHistorySavedMesssages: String { return L10n.tr("Localizable", "peerInfo.Confirm.ClearHistory.SavedMesssages") } + /// This will delete all messages and media in this chat from your Telegram cloud. Your chat partner will still have them. + internal static var peerInfoConfirmClearHistoryUser: String { return L10n.tr("Localizable", "peerInfo.Confirm.ClearHistory.User") } + /// Are you sure you want to delete all messages in the chat? + internal static var peerInfoConfirmClearHistoryUserBothSides: String { return L10n.tr("Localizable", "peerInfo.Confirm.ClearHistory.UserBothSides") } + /// PSA + internal static var psaChatlist: String { return L10n.tr("Localizable", "psa.chatlist") } + /// This message provides you with a public service announcement in your chat list + internal static var psaText: String { return L10n.tr("Localizable", "psa.text") } + /// PSA Notification + internal static var psaTitle: String { return L10n.tr("Localizable", "psa.title") } + /// Public Service Announcement + internal static var psaChatTitle: String { return L10n.tr("Localizable", "psa.chat.title") } + /// This message provides you with a public service announcement. + internal static var psaChatTextCovid: String { return L10n.tr("Localizable", "psa.chat.text.covid") } + /// PSA Notification\nfrom: [%@]() + internal static func psaTitleBubbles(_ p1: String) -> String { + return L10n.tr("Localizable", "psa.title.bubbles", p1) } - - private static func tr(key: String, _ args: CVarArg...) -> String { - return translate(key: key, args) - } + /// Check Spelling While Typing + internal static var rbDRhWINTitle: String { return L10n.tr("Localizable", "rbD-Rh-wIN.title") } + /// Smart Dashes + internal static var rgMF4YcnTitle: String { return L10n.tr("Localizable", "rgM-f4-ycn.title") } + /// Quick Search + internal static var sZhCtGQSTitle: String { return L10n.tr("Localizable", "sZh-ct-GQS.title") } + /// Data Detectors + internal static var tRrPd1PSTitle: String { return L10n.tr("Localizable", "tRr-pd-1PS.title") } + /// Telegram + internal static var uQyDDJDrTitle: String { return L10n.tr("Localizable", "uQy-DD-JDr.title") } + /// Cut + internal static var uRlIYUnGTitle: String { return L10n.tr("Localizable", "uRl-iY-unG.title") } + /// Make Upper Case + internal static var vmV6d7jITitle: String { return L10n.tr("Localizable", "vmV-6d-7jI.title") } + /// Copy + internal static var x3vGGIWUTitle: String { return L10n.tr("Localizable", "x3v-GG-iWU.title") } + /// Show Substitutions + internal static var z6FFW3nzTitle: String { return L10n.tr("Localizable", "z6F-FW-3nz.title") } } +// swiftlint:enable identifier_name line_length type_body_length -func tr(_ key: L10n) -> String { - return key.string +extension L10n { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + return translate(key: key, args) + } } private final class BundleToken {} diff --git a/Telegram-Mac/LocalizableExtension.swift b/Telegram-Mac/LocalizableExtension.swift index dc554a2ed2..37472888d6 100644 --- a/Telegram-Mac/LocalizableExtension.swift +++ b/Telegram-Mac/LocalizableExtension.swift @@ -5,10 +5,11 @@ // Created by keepcoder on 25/05/2017. // Copyright © 2017 Telegram. All rights reserved. // -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore import TGUIKit - +import SyncCore #if !APP_STORE import Sparkle #endif @@ -36,6 +37,10 @@ extension Double { } + func tr(_ string: String) -> String { + return string +} + private func dictFromLocalization(_ value: Localization) -> [String: String] { var dict: [String: String] = [:] for entry in value.entries { @@ -65,31 +70,32 @@ private func dictFromLocalization(_ value: Localization) -> [String: String] { } func applyUILocalization(_ settings: LocalizationSettings) { - let language = Language(languageCode: settings.languageCode, strings: dictFromLocalization(settings.localization)) - #if !APP_STORE - // SULocalizationWrapper.setLanguageCode(settings.languageCode) - #endif + let primaryLanguage = Language(languageCode: settings.primaryComponent.languageCode, customPluralizationCode: settings.primaryComponent.customPluralizationCode, strings: dictFromLocalization(settings.primaryComponent.localization)) + let secondaryLanguage = settings.secondaryComponent != nil ? Language.init(languageCode: settings.secondaryComponent!.languageCode, customPluralizationCode: settings.secondaryComponent!.customPluralizationCode, strings: dictFromLocalization(settings.secondaryComponent!.localization)) : nil + + let language = TelegramLocalization(primaryLanguage: primaryLanguage, secondaryLanguage: secondaryLanguage, localizedName: settings.primaryComponent.localizedName) _ = _appCurrentLanguage.swap(language) languagePromise.set(.single(language)) applyMainMenuLocalization(mainWindow) } func applyShareUILocalization(_ settings: LocalizationSettings) { - let language = Language(languageCode: settings.languageCode, strings: dictFromLocalization(settings.localization)) + let primaryLanguage = Language(languageCode: settings.primaryComponent.languageCode, customPluralizationCode: settings.primaryComponent.customPluralizationCode, strings: dictFromLocalization(settings.primaryComponent.localization)) + let secondaryLanguage = settings.secondaryComponent != nil ? Language.init(languageCode: settings.secondaryComponent!.languageCode, customPluralizationCode: settings.secondaryComponent!.customPluralizationCode, strings: dictFromLocalization(settings.secondaryComponent!.localization)) : nil + + let language = TelegramLocalization(primaryLanguage: primaryLanguage, secondaryLanguage: secondaryLanguage, localizedName: settings.primaryComponent.localizedName) _ = _appCurrentLanguage.swap(language) languagePromise.set(.single(language)) } func dropShareLocalization() { - let language = Language(languageCode: "en", strings: [:]) + let language = TelegramLocalization(primaryLanguage: Language(languageCode: "en", customPluralizationCode: nil, strings: [:]), secondaryLanguage: nil, localizedName: "English") _ = _appCurrentLanguage.swap(language) languagePromise.set(.single(language)) } func dropLocalization() { - #if !APP_STORE - // SULocalizationWrapper.setLanguageCode("en") - #endif - let language = Language(languageCode: "en", strings: [:]) + + let language = TelegramLocalization(primaryLanguage: Language(languageCode: "en", customPluralizationCode: nil, strings: [:]), secondaryLanguage: nil, localizedName: "English") _ = _appCurrentLanguage.swap(language) languagePromise.set(.single(language)) applyMainMenuLocalization(mainWindow) @@ -117,9 +123,11 @@ private func localizeMainMenuItem(_ item:NSMenuItem) { class Language : Equatable { let languageCode:String + let customPluralizationCode: String? let strings:[String: String] - init (languageCode:String, strings:[String: String]) { + init (languageCode:String, customPluralizationCode: String?, strings:[String: String]) { self.languageCode = languageCode + self.customPluralizationCode = customPluralizationCode self.strings = strings } } @@ -129,44 +137,53 @@ func ==(lhs:Language, rhs:Language) -> Bool { } func translate(key: String, _ args: [CVarArg]) -> String { - let format:String + var format:String? var args = args if key.hasSuffix("_countable") { - if let count = args.first as? Int { - - let code = languageCodehash(appCurrentLanguage.languageCode) - - if let index = key.range(of: "_")?.lowerBound { - var string = String(key[.. 1 { - args.removeFirst() - } + + for i in 0 ..< args.count { + if let count = args[i] as? Int { + let code = languageCodehash(appCurrentLanguage.pluralizationCode) - } else { - format = _NSLocalizedString(key) + if let index = key.range(of: "_")?.lowerBound { + var string = String(key[.. 1 { + //args.remove(at: i) + //} + } else { + format = _NSLocalizedString(key) + } + break } - } else { + } + if format == nil { format = _NSLocalizedString(key) } + } else { format = _NSLocalizedString(key) } - let ranges = extractArgumentRanges(format) - var formatted = format - for range in ranges.reversed() { - if range.0 < args.count { - let value = "\(args[range.0])" - formatted = formatted.nsstring.replacingCharacters(in: range.1, with: value) - } else { - formatted = formatted.nsstring.replacingCharacters(in: range.1, with: "") + if let format = format { + let ranges = extractArgumentRanges(format) + var formatted = format + while ranges.count != args.count { + args.removeFirst() } - + for range in ranges.reversed() { + if range.0 < args.count { + let value = "\(args[range.0])" + formatted = formatted.nsstring.replacingCharacters(in: range.1, with: value) + } else { + formatted = formatted.nsstring.replacingCharacters(in: range.1, with: "") + } + } + return formatted } - return formatted + return "UndefinedKey" } private let argumentRegex = try! NSRegularExpression(pattern: "%(((\\\\d+)\\\\$)?)([@df])", options: []) @@ -187,26 +204,50 @@ func extractArgumentRanges(_ value: String) -> [(Int, NSRange)] { return result } -func t(_ key: L10n) -> String { - return key.string +final class TelegramLocalization : Equatable { + + + let primaryLanguage: Language + let secondaryLanguage: Language? + let baseLanguageCode: String + let localizedName: String + init(primaryLanguage: Language, secondaryLanguage: Language?, localizedName: String) { + self.primaryLanguage = primaryLanguage + self.secondaryLanguage = secondaryLanguage + self.localizedName = localizedName + self.baseLanguageCode = secondaryLanguage?.languageCode ?? primaryLanguage.languageCode + } + + var languageCode: String { + return baseLanguageCode + } + + static func == (lhs: TelegramLocalization, rhs: TelegramLocalization) -> Bool { + return lhs.primaryLanguage == rhs.primaryLanguage && lhs.secondaryLanguage == rhs.secondaryLanguage && lhs.baseLanguageCode == rhs.baseLanguageCode + } + + var pluralizationCode: String { + return primaryLanguage.customPluralizationCode ?? secondaryLanguage?.customPluralizationCode ?? secondaryLanguage?.languageCode ?? primaryLanguage.languageCode + } + } - -let _appCurrentLanguage:Atomic = Atomic(value: Language(languageCode: "en", strings: [:])) -var appCurrentLanguage:Language { - return _appCurrentLanguage.modify({$0}) +let _appCurrentLanguage:Atomic = Atomic(value: TelegramLocalization(primaryLanguage: Language(languageCode: "en", customPluralizationCode: nil, strings: [:]), secondaryLanguage: nil, localizedName: "English")) +var appCurrentLanguage:TelegramLocalization { + return _appCurrentLanguage.modify {$0} } -let languagePromise:Promise = Promise(Language(languageCode: "en", strings: [:])) +private let languagePromise:Promise = Promise(appCurrentLanguage) -var languageSignal:Signal { +var languageSignal:Signal { return languagePromise.get() |> distinctUntilChanged |> deliverOnMainQueue } public func _NSLocalizedString(_ key: String) -> String { - let language = appCurrentLanguage - - if let value = language.strings[key], !value.isEmpty { + let primary = appCurrentLanguage.primaryLanguage + let secondary = appCurrentLanguage.secondaryLanguage + + if let value = (primary.strings[key] ?? secondary?.strings[key]), !value.isEmpty { return value } else { let path = Bundle.main.path(forResource: "en", ofType: "lproj") diff --git a/Telegram-Mac/LocalizationPreviewModalController.swift b/Telegram-Mac/LocalizationPreviewModalController.swift new file mode 100644 index 0000000000..83563877ac --- /dev/null +++ b/Telegram-Mac/LocalizationPreviewModalController.swift @@ -0,0 +1,113 @@ +// +// LocalizationPreviewModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore + +private final class LocalizationPreviewView : Control { + private let titleView: TextView = TextView() + private let titleContainer: View = View() + + private let textView: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + titleView.isSelectable = false + titleView.userInteractionEnabled = false + + textView.isSelectable = false + + titleContainer.addSubview(titleView) + addSubview(titleContainer) + addSubview(textView) + titleContainer.border = [.Bottom] + } + + func update(with info: LocalizationInfo, width: CGFloat) -> CGFloat { + let titleLayout = TextViewLayout(.initialize(string: L10n.applyLanguageChangeLanguageTitle, color: theme.colors.text, font: .medium(.title)), alwaysStaticItems: true) + titleLayout.measure(width: width) + titleView.update(titleLayout) + + + let text: String + if info.isOfficial { + text = L10n.applyLanguageChangeLanguageOfficialText(info.title) + } else { + text = L10n.applyLanguageChangeLanguageUnofficialText(info.title, "\(Int(Float(info.translatedStringCount) / Float(info.totalStringCount) * 100.0))") + } + + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.text), bold: MarkdownAttributeSet(font: .bold(.text), textColor: theme.colors.text), link: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { _ in + execute(inapp: .external(link: info.platformUrl, false)) + })) + })).mutableCopy() as! NSMutableAttributedString + attributedText.detectBoldColorInString(with: .bold(.text)) + + let textLayout = TextViewLayout(attributedText, alignment: .center, alwaysStaticItems: true) + textLayout.measure(width: width - 40) + + textLayout.interactions = globalLinkExecutor + + textView.update(textLayout) + + return 50 + 40 + textLayout.layoutSize.height + } + + override func layout() { + super.layout() + titleContainer.frame = NSMakeRect(0, 0, frame.width, 50) + titleView.center() + + textView.centerX(y: titleContainer.frame.maxY + 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class LocalizationPreviewModalController: ModalViewController { + private let context: AccountContext + private let info: LocalizationInfo + init(_ context: AccountContext, info: LocalizationInfo) { + self.info = info + self.context = context + super.init(frame: NSMakeRect(0, 0, 320, 200)) + bar = .init(height: 0) + } + private var genericView:LocalizationPreviewView { + return self.view as! LocalizationPreviewView + } + + private func applyLocalization() { + close() + _ = showModalProgress(signal: downloadAndApplyLocalization(accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, languageCode: info.languageCode), for: mainWindow).start() + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: L10n.applyLanguageApplyLanguageAction, accept: { [weak self] in + self?.applyLocalization() + }, cancelTitle: L10n.modalCancel, height: 50) + } + + override func viewClass() -> AnyClass { + return LocalizationPreviewView.self + } + + override func viewDidLoad() { + super.viewDidLoad() + + let value = genericView.update(with: info, width: frame.width) + self.modal?.resize(with:NSMakeSize(genericView.frame.width, value), animated: false) + + readyOnce() + + } +} diff --git a/Telegram-Mac/LocationModalController.swift b/Telegram-Mac/LocationModalController.swift new file mode 100644 index 0000000000..249dbbb2ee --- /dev/null +++ b/Telegram-Mac/LocationModalController.swift @@ -0,0 +1,678 @@ +// +// LocationModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import MapKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + + + +private enum PickLocationState : Equatable { + case user(CLLocation?) + case custom(CLLocation?, named: String?) + var location: CLLocation? { + switch self { + case let .user(location): + return location + case let .custom(location, _): + return location + } + } +} + +private enum LocationViewState : Equatable { + case normal(PickLocationState) + case expanded(CLLocation?) +} + + +private final class LocationPinView : View { + private let locationPin: ImageView = ImageView() + private let dotView: View = View(frame: NSMakeRect(0, 0, 4, 4)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(locationPin) + addSubview(dotView) + dotView.layer?.cornerRadius = 2 + updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + locationPin.image = theme.icons.locationMapPin + locationPin.sizeToFit() + dotView.backgroundColor = theme.colors.accentIcon + } + + override func layout() { + super.layout() + dotView.centerX(y: frame.height - dotView.frame.height) + } + + func updateState(_ state: PickLocationState, animated: Bool) -> Void { + + switch state { + case .user: + dotView.change(opacity: 0, animated: animated) + locationPin.change(pos: NSMakePoint(locationPin.frame.minX, frame.height - dotView.frame.height - locationPin.frame.height - 6), animated: animated, duration: 0.3, timingFunction: CAMediaTimingFunctionName.spring) + case let .custom(location, _): + dotView.change(opacity: 1, animated: animated) + locationPin.change(pos: NSMakePoint(locationPin.frame.minX, location == nil ? 0 : frame.height - dotView.frame.height - locationPin.frame.height - 6), animated: animated, duration: 0.3, timingFunction: CAMediaTimingFunctionName.spring) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class LocationMapView : View { + fileprivate let mapView: MKMapView = MKMapView() + private let headerTextView: TextView = TextView() + private let header: View = View() + private let expandContainer: Control = Control(frame: NSMakeRect(0, 0, 0, 50)) + private let expandButton: TitleButton = TitleButton() + private var state: LocationViewState = .normal(.user(nil)) + private var hasExpand: Bool = true + private let loadingView: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) + fileprivate let dismiss: ImageButton = ImageButton() + fileprivate let tableView: TableView = TableView(frame: NSZeroRect) + fileprivate let locateButton: ImageButton = ImageButton() + + private let locationPinView = LocationPinView(frame: NSMakeRect(0, 0, 40, 70)) + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(mapView) + + mapView.mapType = .standard + mapView.isZoomEnabled = true + mapView.isScrollEnabled = true + mapView.showsUserLocation = true + mapView.showsZoomControls = true + mapView.wantsLayer = true + header.addSubview(headerTextView) + header.addSubview(dismiss) + header.addSubview(locateButton) + addSubview(header) + addSubview(tableView) + updateLocalizationAndTheme(theme: theme) + + expandButton.isEventLess = true + expandButton.userInteractionEnabled = false + + expandContainer.addSubview(loadingView) + expandContainer.addSubview(expandButton) + addSubview(expandContainer) + locateButton.autohighlight = false + mapView.addSubview(locationPinView) + } + + fileprivate func getSelectedLocation() -> CLLocation? { + let windowLocation = locationPinView.convert(NSMakePoint(locationPinView.frame.width / 2, locationPinView.frame.height - 2), to: nil) + let coordinate = mapView.convert(windowLocation, toCoordinateFrom: nil) + return CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + locationPinView.updateLocalizationAndTheme(theme: theme) + expandButton.set(font: .medium(.title), for: .Normal) + expandButton.set(color: theme.colors.accent, for: .Normal) + dismiss.set(image: theme.icons.modalClose, for: .Normal) + _ = locateButton.sizeToFit() + _ = dismiss.sizeToFit() + header.backgroundColor = theme.colors.background + header.border = [.Bottom] + loadingView.progressColor = theme.colors.accent + expandContainer.border = [.Top] + expandContainer.backgroundColor = theme.colors.background + let title = TextViewLayout(.initialize(string: L10n.locationSendTitle, color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1) + title.measure(width: frame.width - 20) + + headerTextView.update(title) + headerTextView.center() + } + + fileprivate func updateExpandState(_ state: LocationViewState, loading: Bool, hasVenues: Bool, animated: Bool, toggleExpand:@escaping(LocationViewState)->Void) { + loadingView.isHidden = !loading && hasVenues + expandButton.isHidden = loading || !hasVenues + hasExpand = (loading || hasVenues) + self.state = state + + let duration: Double = 0.3 + let timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.spring + + CATransaction.begin() + let mapY: CGFloat + switch state { + case let .normal(pickState): + switch pickState { + case .custom: + hasExpand = false + locateButton.set(image: theme.icons.locationMapLocate, for: .Normal) + case let .user(location): + locateButton.set(image: theme.icons.locationMapLocated, for: .Normal) + locateButton.isHidden = location == nil + } + locationPinView.change(opacity: loading ? 0 : 1, animated: animated) + locationPinView.updateState(pickState, animated: animated) + expandButton.set(text: L10n.locationSendShowNearby, for: .Normal) + tableView.change(size: NSMakeSize(frame.width, 60), animated: animated, timingFunction: CAMediaTimingFunctionName.spring) + tableView.change(pos: NSMakePoint(0, frame.height - 60 - (hasExpand ? expandContainer.frame.height : 0)), animated: animated, duration: duration, timingFunction: timingFunction) + mapY = header.frame.height + locateButton.userInteractionEnabled = true + case .expanded: + locateButton.userInteractionEnabled = false + locateButton.set(image: theme.icons.locationMapLocate, for: .Normal) + locationPinView.change(opacity: 0, animated: animated) + expandButton.set(text: L10n.locationSendHideNearby, for: .Normal) + let tableHeight = min(tableView.listHeight, frame.height - (hasExpand ? expandContainer.frame.height : 0) - header.frame.height - 50) + tableView.change(size: NSMakeSize(frame.width, tableHeight), animated: animated, duration: duration, timingFunction: timingFunction) + tableView.change(pos: NSMakePoint(0, frame.height - (hasExpand ? expandContainer.frame.height : 0) - tableHeight), animated: animated, duration: duration, timingFunction: timingFunction) + mapY = -(mapView.frame.height / 2) + header.frame.height + 50 / 2 + } + expandContainer.change(pos: NSMakePoint(0, hasExpand ? frame.height - expandContainer.frame.height : frame.height), animated: animated, duration: duration, timingFunction: timingFunction) + _ = locateButton.sizeToFit() + + + mapView._change(pos: NSMakePoint(0, mapY), animated: animated, duration: duration, timingFunction: timingFunction) + let pinPoint = mapView.focus(NSMakeSize(locationPinView.frame.width, 4)) + locationPinView.change(pos: NSMakePoint(pinPoint.minX, pinPoint.midY - locationPinView.frame.height), animated: animated, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear) + + + CATransaction.commit() + + expandContainer.removeAllHandlers() + if !loading { + expandContainer.set(handler: { [weak self] _ in + guard let `self` = self else {return} + switch state { + case .normal: + toggleExpand(.expanded(self.mapView.userLocation.location)) + case .expanded: + toggleExpand(.normal(.user(self.mapView.userLocation.location))) + } + }, for: .Click) + } + needsLayout = true + } + + override func layout() { + super.layout() + header.frame = NSMakeRect(0, 0, frame.width, 50) + headerTextView.center() + dismiss.centerY(x: 10) + locateButton.centerY(x: frame.width - 10 - locateButton.frame.width) + expandContainer.frame = NSMakeRect(0, hasExpand ? frame.height - expandContainer.frame.height : frame.height, frame.width, expandContainer.frame.height) + let mapY: CGFloat + let mapHeight: CGFloat = frame.height - 50 - header.frame.height + switch state { + case .normal: + tableView.frame = NSMakeRect(0, frame.height - 60 - (hasExpand ? expandContainer.frame.height : 0), frame.width, 60) + mapY = header.frame.height + case .expanded: + let tableHeight = min(tableView.listHeight, frame.height - (hasExpand ? expandContainer.frame.height : 0) - header.frame.height - 50) + tableView.frame = NSMakeRect(0, frame.height - (hasExpand ? expandContainer.frame.height : 0) - tableHeight, frame.width, tableHeight) + mapY = -(mapHeight / 2) + header.frame.height + 50 / 2 + } + let delegate = mapView.delegate + mapView.delegate = nil + mapView.frame = NSMakeRect(0, mapY, frame.width, mapHeight) + mapView.delegate = delegate + + let pinPoint = mapView.focus(NSMakeSize(4, 4)) + locationPinView.centerX(y: pinPoint.midY - locationPinView.frame.height) + + + expandButton.center() + loadingView.center() + + let zoomControls = HackUtils.findElements(byClass: "MKZoomSegmentedControl", in: mapView).first as? NSView + if let zoomControls = zoomControls { + zoomControls.setFrameOrigin(20, 20) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +private final class MapItemsArguments { + let context: AccountContext + let sendCurrent:()->Void + let sendVenue:(TelegramMediaMap)->Void + let searchVenues:(String)->Void + init(context: AccountContext, sendCurrent:@escaping()->Void, sendVenue:@escaping(TelegramMediaMap)->Void, searchVenues: @escaping(String)->Void) { + self.context = context + self.sendCurrent = sendCurrent + self.sendVenue = sendVenue + self.searchVenues = searchVenues + } +} + +private enum MapItemEntryId : Hashable { + case currentLocation + case expandNearby + case nearby(Int32) + case search + case searchEmptyId + var hashValue: Int { + return 0 + } +} + +private enum MapItemEntry : TableItemListNodeEntry { + case currentLocation(index:Int32, state: LocationSelectCurrentState) + case expandNearby(index: Int32, expand: Bool, loading: Bool) + case nearby(index: Int32, result: ChatContextResult) + case search(index: Int32) + case searchEmpty(index: Int32, loading: Bool) + var index: Int32 { + switch self { + case let .currentLocation(index, _): + return index + case let .expandNearby(index, _, _): + return index + case let .nearby(index, _): + return index + case let .search(index): + return index + case let .searchEmpty(index, _): + return index + } + } + + var stableId: MapItemEntryId { + switch self { + case .currentLocation: + return .currentLocation + case .expandNearby: + return .expandNearby + case .search: + return .search + case .searchEmpty: + return .searchEmptyId + case let .nearby(index, _): + return .nearby(index) + } + } + + func item(_ arguments: MapItemsArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .nearby(_, result): + return LocationPlaceSuggestionRowItem(initialSize, stableId: stableId, account: arguments.context.account, result: result, action: { + switch result.message { + case let .mapLocation(media, _): + arguments.sendVenue(media) + default: + break + } + }) + case .search: + return SearchRowItem(initialSize, stableId: stableId, searchInteractions: SearchInteractions({ state, _ in + arguments.searchVenues(state.request) + }, { state in + arguments.searchVenues(state.request) + }), inset: NSEdgeInsets(left: 10,right: 10, top: 10, bottom: 10)) + case let .currentLocation(_, state): + return LocationSendCurrentItem(initialSize, stableId: stableId, state: state, action: { + arguments.sendCurrent() + }) + case let .searchEmpty(_, loading): + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: loading) + default: + return GeneralRowItem(initialSize, height: 20, stableId: stableId) + } + } +} + +private func < (lhs: MapItemEntry, rhs: MapItemEntry) -> Bool { + return lhs.index < rhs.index +} + +private func mapEntries(result: [ChatContextResult], loading: Bool, location: CLLocation?, state: LocationViewState) -> [MapItemEntry] { + var entries: [MapItemEntry] = [] + + + + var index: Int32 = 0 + + let selectState: LocationSelectCurrentState + switch state { + case .expanded: + selectState = .accurate(location: location, expanded: true) + case let .normal(pickState): + switch pickState { + case .user: + selectState = .accurate(location: location, expanded: false) + case let .custom(_, name): + let text: String + if let name = name { + text = name.isEmpty ? L10n.locationSendThisLocationUnknown : name + } else { + text = L10n.locationSendLocating + } + selectState = .selected(location: text) + } + } + + entries.append(.currentLocation(index: index, state: selectState)) + index += 1 + switch state { + case .expanded: + entries.append(.search(index: index)) + index += 1 + if !result.isEmpty { + for value in result { + entries.append(.nearby(index: index, result: value)) + index += 1 + } + } else { + entries.append(MapItemEntry.searchEmpty(index: index, loading: false)) + index += 1 + } + + case .normal: + break + } + + return entries +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:MapItemsArguments) -> TableUpdateTransition { + + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + +private class MapDelegate : NSObject, MKMapViewDelegate { + + fileprivate var isPinRaised: Bool = false + private var animated: Bool = false + let location:Promise = Promise() + fileprivate var willChangeRegion:()->Void = {} + fileprivate var didChangeRegion:()->Void = {} + + func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) { + self.location.set(.single(userLocation)) + + guard !isPinRaised else {return} + focusUserLocation(mapView) + } + + func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { + willChangeRegion() + + } + + func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + didChangeRegion() + } + + func mapView(_ mapView: MKMapView, didFailToLocateUserWithError error: Error) { + if "\(error)".contains("Code=1") { + self.location.set(.single(nil)) + isPinRaised = true + didChangeRegion() + mapView.showsUserLocation = false + } + } + + + fileprivate func focusUserLocation(_ mapView: MKMapView) { + let userLocation = mapView.userLocation + + var region = MKCoordinateRegion() + var span = MKCoordinateSpan() + span.latitudeDelta = CLLocationDegrees(0.005) + span.longitudeDelta = CLLocationDegrees(0.005) + var location = CLLocationCoordinate2D() + location.latitude = userLocation.coordinate.latitude + location.longitude = userLocation.coordinate.longitude + region.span = span + region.center = location + mapView.setRegion(region, animated: animated) + animated = true + } +} + +class LocationModalController: ModalViewController { + + private let chatInteraction: ChatInteraction + private let delegate: MapDelegate = MapDelegate() + private let disposable = MetaDisposable() + private let sendDisposable = MetaDisposable() + private let statePromise:Promise = Promise() + init(_ chatInteraction: ChatInteraction) { + self.chatInteraction = chatInteraction + super.init(frame: NSMakeRect(0, 0, 360, 380)) + } + + override var dynamicSize: Bool { + return true + } + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(360, size.height - 70), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(360, contentSize.height - 70), animated: animated) + } + } + + override func viewClass() -> AnyClass { + return LocationMapView.self + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + genericView.mapView.showsUserLocation = false + window?.removeAllHandlers(for: self) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(mouseHandler: { [weak self] _ -> KeyHandlerResult in + self?.delegate.isPinRaised = true + return .rejected + }, with: self, for: .leftMouseDragged, priority: .modal) + } + + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + super.close(animationType: animationType) + } + + private func sendLocation(_ media: TelegramMediaMap? = nil) { + sendDisposable.set((statePromise.get() |> deliverOnMainQueue).start(next: { [weak self] state in + switch state { + case let .normal(picked): + if let location = picked.location { + self?.chatInteraction.sendLocation(location.coordinate, nil) + self?.close() + } + case let .expanded(location): + if let media = media { + let coordinate = CLLocationCoordinate2D(latitude: media.latitude, longitude: media.longitude) + self?.chatInteraction.sendLocation(coordinate, media.venue) + self?.close() + } else if let location = location { + self?.chatInteraction.sendLocation(location.coordinate, nil) + self?.close() + } + } + })) + } + + override func returnKeyAction() -> KeyHandlerResult { + sendLocation() + return .invoked + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.mapView.delegate = delegate + genericView.dismiss.set(handler: { [weak self] _ in + self?.close() + }, for: .Click) + + let state: ValuePromise = ValuePromise(.normal(.user(genericView.mapView.userLocation.location)), ignoreRepeated: true) + statePromise.set(state.get()) + + genericView.locateButton.set(handler: { [weak self] _ in + guard let `self` = self else {return} + self.delegate.focusUserLocation(self.genericView.mapView) + self.delegate.isPinRaised = false + }, for: .Click) + + if let _ = genericView.mapView.userLocation.location { + delegate.location.set(.single(genericView.mapView.userLocation)) + delegate.focusUserLocation(genericView.mapView) + } + + var handleRegion: Bool = true + + delegate.willChangeRegion = { [weak self] in + guard let `self` = self, handleRegion else {return} + if self.delegate.isPinRaised { + state.set(.normal(.custom(nil, named: nil))) + } else { + state.set(.normal(.user(self.genericView.mapView.userLocation.location))) + } + } + delegate.didChangeRegion = { [weak self] in + guard let `self` = self, handleRegion else {return} + if self.delegate.isPinRaised { + state.set(.normal(.custom(self.genericView.getSelectedLocation(), named: nil))) + } else { + state.set(.normal(.user(self.genericView.mapView.userLocation.location))) + } + } + + let peerId = chatInteraction.peerId + let context = self.chatInteraction.context + + let search:Promise = Promise("") + + var cachedData:[String : ChatContextResultCollection] = [:] + let previousResult:Atomic = Atomic(value: nil) + let peerSignal: Signal = .single(nil) |> then(resolvePeerByName(account: context.account, name: "foursquare") ) + let requestSignal = combineLatest(peerSignal |> deliverOnPrepareQueue, delegate.location.get() |> take(1) |> deliverOnPrepareQueue, search.get() |> distinctUntilChanged |> deliverOnPrepareQueue) + |> mapToSignal { botId, location, query -> Signal<(ChatContextResultCollection?, CLLocation?, Bool, Bool), NoError> in + if let botId = botId, let location = location { + let first = Signal<(ChatContextResultCollection?, CLLocation?, Bool, Bool), NoError>.single((cachedData[query] ?? previousResult.modify {$0}, location.location, cachedData[query] == nil, !query.isEmpty)) + if cachedData[query] == nil { + return first |> then(requestChatContextResults(account: context.account, botId: botId, peerId: peerId, query: query, location: .single((location.coordinate.latitude, location.coordinate.longitude)), offset: "") + |> `catch` { _ in return .complete() } + |> deliverOnPrepareQueue |> map { result in + var value = result?.results + if let result = result { + cachedData[query] = result.results + } + value = previousResult.modify { _ in result?.results } + + return (value, location.location, false, !query.isEmpty) + }) + } else { + return first + } + + } else { + return .single((nil, location?.location, botId == nil, false)) + } + } + + let signal: Signal<(ChatContextResultCollection?, CLLocation?, Bool, Bool), NoError> = .single((nil, nil, true, false)) |> then(requestSignal) + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let initialSize = self.atomicSize + let arguments = MapItemsArguments(context: context, sendCurrent: { [weak self] in + self?.sendLocation() + }, sendVenue: { [weak self] venue in + self?.sendLocation(venue) + }, searchVenues: { query in + prepareQueue.async { + if cachedData[query] != nil { + search.set(.single(query)) + } else { + search.set(.single(query) |> delay(0.2, queue: prepareQueue)) + } + } + }) + + let stateModified = state.get() |> mapToSignal { state -> Signal in + switch state { + case let .normal(pick): + switch pick { + case let .custom(location, _): + if let location = location { + return .single(state) |> then(reverseGeocodeLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) |> map { value in + return .normal(.custom(location, named: value?.fullAddress)) + }) + } + default: + break + } + default: + break + } + return .single(state) + } |> distinctUntilChanged + + let transition:Signal<(TableUpdateTransition, Bool, LocationViewState, Bool), NoError> = combineLatest(signal |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue, stateModified |> deliverOnPrepareQueue) |> map { data, appearance, state in + let results:[ChatContextResult] = data.0?.results ?? [] + let entries = mapEntries(result: results, loading: data.2, location: data.1, state: state).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), data.2, state, !results.isEmpty || data.3) + } |> deliverOnMainQueue + + let animated:Atomic = Atomic(value: false) + + disposable.set(transition.start(next: { [weak self] transition, loading, expanded, hasVenues in + guard let `self` = self else {return} + self.genericView.tableView.merge(with: transition) + switch expanded { + case .expanded: + handleRegion = false + default: + handleRegion = true + } + self.genericView.updateExpandState(expanded, loading: loading, hasVenues: hasVenues, animated: animated.swap(true), toggleExpand: { [weak self] viewState in + self?.genericView.tableView.clipView.scroll(to: NSMakePoint(0, 0), animated: false) + search.set(.single("")) + state.set(viewState) + }) + self.readyOnce() + })) + + } + + deinit { + disposable.dispose() + sendDisposable.dispose() + } + + private var genericView: LocationMapView { + return view as! LocationMapView + } +} diff --git a/Telegram-Mac/LocationPlaceSuggestionRowItem.swift b/Telegram-Mac/LocationPlaceSuggestionRowItem.swift new file mode 100644 index 0000000000..70cd38934d --- /dev/null +++ b/Telegram-Mac/LocationPlaceSuggestionRowItem.swift @@ -0,0 +1,171 @@ +// +// LocationPlaceSuggestionRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + +private let randomColors = [NSColor(rgb: 0xe56cd5), NSColor(rgb: 0xf89440), NSColor(rgb: 0x9986ff), NSColor(rgb: 0x44b3f5), NSColor(rgb: 0x6dc139), NSColor(rgb: 0xff5d5a), NSColor(rgb: 0xf87aad), NSColor(rgb: 0x6e82b3), NSColor(rgb: 0xf5ba21)] + +private let venueColors: [String: NSColor] = [ + "building/medical": NSColor(rgb: 0x43b3f4), + "building/gym": NSColor(rgb: 0x43b3f4), + "arts_entertainment": NSColor(rgb: 0xe56dd6), + "travel/bedandbreakfast": NSColor(rgb: 0x9987ff), + "travel/hotel": NSColor(rgb: 0x9987ff), + "travel/hostel": NSColor(rgb: 0x9987ff), + "travel/resort": NSColor(rgb: 0x9987ff), + "building": NSColor(rgb: 0x6e81b2), + "education": NSColor(rgb: 0xa57348), + "event": NSColor(rgb: 0x959595), + "food": NSColor(rgb: 0xf7943f), + "education/cafeteria": NSColor(rgb: 0xf7943f), + "nightlife": NSColor(rgb: 0xe56dd6), + "travel/hotel_bar": NSColor(rgb: 0xe56dd6), + "parks_outdoors": NSColor(rgb: 0x6cc039), + "shops": NSColor(rgb: 0xffb300), + "travel": NSColor(rgb: 0x1c9fff), + "work": NSColor(rgb: 0xad7854), + "home": NSColor(rgb: 0x00aeef) +] + +func venueIconColor(type: String) -> NSColor { + if type.isEmpty { + return NSColor(rgb: 0x008df2) + } + if let color = venueColors[type] { + return color + } + let generalType = type.components(separatedBy: "/").first ?? type + if let color = venueColors[generalType] { + return color + } + + let index = Int(abs(persistentHash32(type)) % Int32(randomColors.count)) + return randomColors[index] +} + + + +class LocationPlaceSuggestionRowItem: GeneralRowItem { + private let result: ChatContextResult + fileprivate let textLayout: TextViewLayout + fileprivate let image: TelegramMediaImage? + fileprivate let account: Account + fileprivate let color: NSColor + init(_ initialSize: NSSize, stableId: AnyHashable, account: Account, result: ChatContextResult, action: @escaping()->Void) { + self.result = result + self.account = account + let attr = NSMutableAttributedString() + var image: TelegramMediaImage? = nil + switch result { + case let .externalReference(values): + + switch values.message { + case let .mapLocation(media, _): + if let venue = media.venue { + _ = attr.append(string: venue.title, color: theme.colors.text, font: .medium(.text)) + _ = attr.append(string: "\n") + _ = attr.append(string: venue.address, color: theme.colors.grayText, font: .normal(.text)) + if let type = venue.type { + let resource = HttpReferenceMediaResource(url: "https://ss3.4sqi.net/img/categories_v2/\(type)_88.png", size: nil) + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 60, height: 60), resource: resource) + image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [representation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } + } + self.color = venueIconColor(type: media.venue?.type ?? "") + default: + self.color = venueIconColor(type: "") + } + textLayout = TextViewLayout(attr, maximumNumberOfLines: 2) + default: + fatalError() + } + + self.image = image + + super.init(initialSize, height: 60, stableId: stableId, action: action, inset: NSEdgeInsetsMake(0, 10, 0, 10)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - inset.left - inset.right - 50 - 10) + return success + } + + override func viewClass() -> AnyClass { + return LocationPlaceSuggestionRowView.self + } + +} + +private final class LocationPlaceSuggestionRowView : TableRowView { + private let thumbHolder: View = View() + private let textView: TextView = TextView() + private let imageView: TransformImageView = TransformImageView(frame: NSMakeRect(0, 0, 50, 50)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(thumbHolder) + addSubview(textView) + textView.userInteractionEnabled = false + textView.isSelectable = false + thumbHolder.setFrameSize(50, 50) + thumbHolder.layer?.cornerRadius = 25 + thumbHolder.addSubview(imageView) + textView.isEventLess = true + } + + override func mouseUp(with event: NSEvent) { + if mouseInside() { + guard let item = item as? GeneralRowItem else {return} + item.action() + } else { + super.mouseUp(with: event) + } + } + + override func updateColors() { + super.updateColors() + + guard let item = item as? LocationPlaceSuggestionRowItem else {return} + + + textView.backgroundColor = theme.colors.background + thumbHolder.backgroundColor = item.color + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? LocationPlaceSuggestionRowItem else {return} + + textView.update(item.textLayout) + imageView.isHidden = item.image == nil + if let image = item.image { + imageView.setSignal(chatWebpageSnippetPhoto(account: item.account, imageReference: ImageMediaReference.standalone(media: image), scale: backingScaleFactor, small: true)) + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: 25), imageSize: NSMakeSize(50, 50), boundingSize: NSMakeSize(50, 50), intrinsicInsets: NSEdgeInsetsZero)) + } + } + + override func layout() { + super.layout() + guard let item = item as? GeneralRowItem else {return} + + thumbHolder.centerY(x: item.inset.left) + textView.centerY(x: thumbHolder.frame.maxX + 10) + imageView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/LocationRequest.swift b/Telegram-Mac/LocationRequest.swift new file mode 100644 index 0000000000..3e9a4ea673 --- /dev/null +++ b/Telegram-Mac/LocationRequest.swift @@ -0,0 +1,117 @@ +// +// LocationRequest.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/08/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import CoreLocation +import SwiftSignalKit +import AVKit +enum UserLocationResult : Equatable { + case success(CLLocation) +} +enum UserLocationError : Equatable { + case restricted + case notDetermined + case denied + case wifiRequired + case disabled +} +private let manager: CLLocationManager = CLLocationManager() + +private class UserLocationRequest : NSObject, CLLocationManagerDelegate { + fileprivate let result: ValuePromise = ValuePromise(ignoreRepeated: true) + fileprivate let error: ValuePromise = ValuePromise(ignoreRepeated: true) + + override init() { + super.init() + + manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers + + manager.delegate = self + + if CLLocationManager.locationServicesEnabled() { + switch CLLocationManager.authorizationStatus() { + case .authorizedAlways: + manager.startUpdatingLocation() + case .denied: + error.set(.denied) + case .restricted: + error.set(.restricted) + case .notDetermined: + manager.startUpdatingLocation() + } + } else { + error.set(.disabled) + } + } + + @objc func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + switch status { + case .authorizedAlways: + manager.startUpdatingLocation() + case .denied: + error.set(.denied) + case .notDetermined: + manager.startUpdatingLocation() + case .restricted: + error.set(.restricted) + case .authorizedWhenInUse: + manager.startUpdatingLocation() + } + } + + @objc func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + var bp:Int = 0 + bp += 1 + self.error.set(.wifiRequired) + } + + @objc func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + + if let location = locations.last { + manager.stopUpdatingLocation() + manager.delegate = nil; + result.set(.success(location)) + } + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + + func stop() { + manager.stopUpdatingLocation() + manager.delegate = nil; + } +} + +func requestUserLocation() -> Signal { + + return Signal { subscriber -> Disposable in + let disposable = DisposableSet() + var manager: UserLocationRequest! + Queue.mainQueue().async { + manager = UserLocationRequest() + + disposable.add(manager.result.get().start(next: { result in + subscriber.putNext(result) + })) + disposable.add(manager.error.get().start(next: { result in + subscriber.putError(result) + })) + } +// + return ActionDisposable { + disposable.dispose() + Queue.mainQueue().async { + manager.stop() + } + } + } +} diff --git a/Telegram-Mac/LocationSendCurrentItem.swift b/Telegram-Mac/LocationSendCurrentItem.swift new file mode 100644 index 0000000000..b5a5a5ca5d --- /dev/null +++ b/Telegram-Mac/LocationSendCurrentItem.swift @@ -0,0 +1,134 @@ +// +// LocationSendCurrent.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import MapKit +enum LocationSelectCurrentState : Equatable { + case accurate(location: CLLocation?, expanded: Bool) + case selected(location: String) +} + +class LocationSendCurrentItem: GeneralRowItem { + fileprivate let statusLayout: TextViewLayout + fileprivate let state: LocationSelectCurrentState + init(_ initialSize: NSSize, stableId: AnyHashable, state: LocationSelectCurrentState, action:@escaping()->Void) { + self.state = state + let text: String + switch state { + case let .accurate(location, _): + if let location = location { + let formatter = MKDistanceFormatter() + formatter.unitStyle = .full + formatter.locale = Locale(identifier: appAppearance.language.languageCode) + let formatted = formatter.string(fromDistance: location.horizontalAccuracy) + text = L10n.locationSendAccurateTo("\(formatted)") + } else { + text = L10n.locationSendLocating + } + + case let .selected(location): + text = location + } + statusLayout = TextViewLayout.init(.initialize(string: text, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + super.init(initialSize, height: 60, stableId: stableId, action: action, inset: NSEdgeInsetsMake(0, 10, 0, 10)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + statusLayout.measure(width: width - inset.left - inset.right - theme.icons.locationPin.backingSize.width - 10) + return success + } + + override func viewClass() -> AnyClass { + return LocationSendCurrentView.self + } +} + + +private final class LocationSendCurrentView : TableRowView { + private let iconView: ImageView = ImageView() + private let statusView = TextView() + private let button: TitleButton = TitleButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(button) + addSubview(iconView) + addSubview(statusView) + statusView.userInteractionEnabled = false + statusView.isSelectable = false + button.userInteractionEnabled = false + button.isEventLess = true + statusView.isEventLess = true + } + + override func mouseUp(with event: NSEvent) { + if mouseInside() { + guard let item = item as? GeneralRowItem else {return} + item.action() + } else { + super.mouseUp(with: event) + } + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? LocationSendCurrentItem else {return} + + + button.set(font: .medium(.title), for: .Normal) + button.set(color: theme.colors.accent, for: .Normal) + let text: String + switch item.state { + case .accurate: + text = L10n.locationSendMyLocation + case .selected: + text = L10n.locationSendThisLocation + } + button.set(text: text, for: .Normal) + _ = button.sizeToFit() + + statusView.update(item.statusLayout) + + iconView.image = theme.icons.locationPin + _ = iconView.sizeToFit() + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + guard let item = item as? LocationSendCurrentItem else {return} + + switch item.state { + case let .accurate(_, expanded): + if expanded { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(statusView.frame.minX, frame.height - .borderSize, frame.width - statusView.frame.minX, .borderSize)) + } + default: + break + } + + } + + override func layout() { + guard let item = item as? GeneralRowItem else {return} + + iconView.centerY(x: item.inset.left) + + button.setFrameOrigin(iconView.frame.maxX + 3, frame.height / 2 - button.frame.height) + statusView.setFrameOrigin(iconView.frame.maxX + 10, frame.height / 2) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/Telegram-Mac/LoginErrorStateView.swift b/Telegram-Mac/LoginErrorStateView.swift index a354f6c1ed..419fd6b02d 100644 --- a/Telegram-Mac/LoginErrorStateView.swift +++ b/Telegram-Mac/LoginErrorStateView.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import TGUIKit enum LoginAuthErrorState : Equatable { case normal diff --git a/Telegram-Mac/LoginViewController.swift b/Telegram-Mac/LoginViewController.swift index eb8bd112c0..f084511d04 100644 --- a/Telegram-Mac/LoginViewController.swift +++ b/Telegram-Mac/LoginViewController.swift @@ -8,57 +8,193 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac -import MtProtoKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + private let manager = CountryManager() final class LoginAuthViewArguments { let sendCode:(String)->Void + let updatePhoneNumberField:(String)->Void let resendCode:()->Void let editPhone:()->Void let checkCode:(String)->Void let checkPassword:(String)->Void - init(sendCode:@escaping(String)->Void, resendCode:@escaping()->Void, editPhone:@escaping()->Void, checkCode:@escaping(String)->Void, checkPassword:@escaping(String)->Void) { + let requestPasswordRecovery: (@escaping(PasswordRecoveryOption)-> Void)->Void + let resetAccount: ()->Void + let signUp:(String, String, URL?) -> Void + let cancelQrAuth:()->Void + init(sendCode:@escaping(String)->Void, resendCode:@escaping()->Void, editPhone:@escaping()->Void, checkCode:@escaping(String)->Void, checkPassword:@escaping(String)->Void, requestPasswordRecovery: @escaping(@escaping(PasswordRecoveryOption)-> Void)->Void, resetAccount: @escaping()->Void, signUp:@escaping(String, String, URL?) -> Void, cancelQrAuth: @escaping()->Void, updatePhoneNumberField:@escaping(String)->Void) { self.sendCode = sendCode self.resendCode = resendCode self.editPhone = editPhone self.checkCode = checkCode self.checkPassword = checkPassword + self.requestPasswordRecovery = requestPasswordRecovery + self.resetAccount = resetAccount + self.signUp = signUp + self.cancelQrAuth = cancelQrAuth + self.updatePhoneNumberField = updatePhoneNumberField } } -private class SignupView : View { - let textView:TextView = TextView() - let button:TitleButton = TitleButton() +private class SignupView : View, NSTextFieldDelegate { + + let firstName:NSTextField = NSTextField() + let lastName:NSTextField = NSTextField() + + private var photoUrl: URL? + + private let firstNameSeparator: View = View() + private let lastNameSeparator: View = View() + + private let photoView = ImageView() + private let descView: TextView = TextView() + private let addPhotoView: TextView = TextView() var arguments: LoginAuthViewArguments? required init(frame frameRect: NSRect) { super.init(frame: frameRect) - addSubview(textView) - addSubview(button) + addSubview(firstName) + addSubview(lastName) + addSubview(firstNameSeparator) + addSubview(lastNameSeparator) + addSubview(addPhotoView) + addSubview(photoView) + addSubview(descView) + descView.userInteractionEnabled = false + descView.isSelectable = false + addPhotoView.userInteractionEnabled = false + addPhotoView.isSelectable = false + - button.set(font: .medium(.title), for: .Normal) - button.set(color: .blueUI, for: .Normal) - button.set(text: tr(.alertOK), for: .Normal) + firstName.isBordered = false + firstName.usesSingleLineMode = true + firstName.isBezeled = false + firstName.focusRingType = .none + firstName.drawsBackground = false - button.sizeToFit() + firstName.delegate = self + lastName.delegate = self - button.set(handler: { [weak self] _ in - self?.arguments?.editPhone() - }, for: .Click) - let layout = TextViewLayout(.initialize(string: tr(.loginPhoneNumberNotRegistred), color: .text, font: .normal(.title)), alignment: .center) - layout.measure(width: frameRect.width - 20) - textView.update(layout) + lastName.isBordered = false + lastName.usesSingleLineMode = true + lastName.isBezeled = false + lastName.focusRingType = .none + lastName.drawsBackground = false + + firstName.cell?.wraps = false + firstName.cell?.isScrollable = true + + firstName.nextKeyView = lastName + firstName.nextResponder = lastName.textView + // lastName.nextKeyView = firstName + + lastName.cell?.wraps = false + lastName.cell?.isScrollable = true + + lastName.font = .medium(14) + firstName.font = .medium(14) + + photoView.layer?.cornerRadius = 50 + updateLocalizationAndTheme(theme: theme) + } + + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + + if commandSelector == #selector(insertNewline(_:)) { + trySignUp() + return true + } + + return false + } + + func trySignUp() { + if firstName.stringValue.isEmpty { + firstName.shake() + + if firstName.textView != window?.firstResponder { + window?.makeFirstResponder(firstName.textView) + } + } else { + arguments?.signUp(firstName.stringValue, lastName.stringValue, self.photoUrl) + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + firstNameSeparator.backgroundColor = theme.colors.border + lastNameSeparator.backgroundColor = theme.colors.border + + lastName.placeholderAttributedString = .initialize(string: L10n.loginRegisterLastNamePlaceholder, color: theme.colors.grayText, font: .medium(14)) + firstName.placeholderAttributedString = .initialize(string: L10n.loginRegisterFirstNamePlaceholder, color: theme.colors.grayText, font: .medium(14)) + + lastName.textColor = theme.colors.text + firstName.textColor = theme.colors.text + + let descLayout = TextViewLayout(.initialize(string: L10n.loginRegisterDesc, color: theme.colors.grayText, font: .normal(.text))) + descLayout.measure(width: frame.width) + descView.update(descLayout) + + let addPhotoLayout = TextViewLayout(.initialize(string: L10n.loginRegisterAddPhotoPlaceholder, color: theme.colors.grayText, font: .normal(.text)), alignment: .center) + addPhotoLayout.measure(width: 90) + addPhotoView.update(addPhotoLayout) + + photoView.layer?.borderColor = theme.colors.border.cgColor + photoView.layer?.borderWidth = 1.0 + + + needsLayout = true + } + + override func mouseDown(with event: NSEvent) { + if photoView._mouseInside() { + + let updatePhoto:(URL) -> Void = { [weak self] url in + self?.photoView.image = NSImage.init(contentsOf: url)?.cgImage(forProposedRect: nil, context: nil, hints: nil) + self?.photoUrl = url + } + + filePanel(with: photoExts, allowMultiple: false, canChooseDirectories: false, for: mainWindow, completion: { paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + _ = (putToTemp(image: image, compress: true) |> deliverOnMainQueue).start(next: { path in + let controller = EditImageModalController(URL(fileURLWithPath: path), settings: .disableSizes(dimensions: .square)) + showModal(with: controller, for: mainWindow, animationType: .scaleCenter) + _ = (controller.result |> deliverOnMainQueue).start(next: { url, _ in + updatePhoto(url) + //arguments.updatePhoto(url.path) + }) + + controller.onClose = { + removeFile(at: path) + } + }) + } + }) + } else { + super.mouseDown(with: event) + } } override func layout() { super.layout() - textView.layout?.measure(width: frame.width - 20) - textView.update(textView.layout) - textView.centerX(y: 30) - button.centerX(y: textView.frame.maxY + 35) + + photoView.frame = NSMakeRect(0, 0, 100, 100) + + addPhotoView.setFrameOrigin(NSMakePoint( floorToScreenPixels(backingScaleFactor, (photoView.frame.width - addPhotoView.frame.width) / 2), floorToScreenPixels(backingScaleFactor, (photoView.frame.height - addPhotoView.frame.height) / 2))) + + firstName.frame = NSMakeRect(photoView.frame.maxX + 10, 20, frame.width - (photoView.frame.maxX + 10), 20) + lastName.frame = NSMakeRect(photoView.frame.maxX + 10, 70, frame.width - (photoView.frame.maxX + 10), 20) + + + firstNameSeparator.frame = NSMakeRect(photoView.frame.maxX + 10, 50, frame.width, .borderSize) + lastNameSeparator.frame = NSMakeRect(photoView.frame.maxX + 10, 100, frame.width, .borderSize) + + descView.centerX(y: photoView.frame.maxY + 50) } required init?(coder: NSCoder) { @@ -74,12 +210,12 @@ private class InputPasswordContainerView : View { required init(frame frameRect: NSRect) { super.init(frame: frameRect) - + input.stringValue = "" input.isBordered = false input.isBezeled = false input.focusRingType = .none - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) addSubview(input) @@ -88,20 +224,23 @@ private class InputPasswordContainerView : View { input.action = #selector(action) input.target = self - addSubview(passwordLabel) + // addSubview(passwordLabel) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.loginPasswordPlaceholder), color: .grayText, font: .normal(.title)) + _ = attr.append(string: L10n.loginPasswordPlaceholder, color: theme.colors.grayText, font: .normal(.title)) + input.backgroundColor = theme.colors.background input.placeholderAttributedString = attr - input.font = NSFont.normal(FontSize.text) - input.textColor = .text + input.font = .normal(.text) + input.textColor = theme.colors.text input.sizeToFit() - passwordLabel.attributedString = .initialize(string: tr(.loginYourPasswordLabel), color: .grayText, font: .normal(FontSize.title)) - passwordLabel.sizeToFit() + + needsDisplay = true + + } @objc func action() { @@ -114,7 +253,7 @@ private class InputPasswordContainerView : View { override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(passwordLabel.frame.maxX + 20, frame.height - .borderSize, frame.width, .borderSize)) + ctx.fill(NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize)) } required init?(coder: NSCoder) { @@ -140,6 +279,9 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { let textView:TextView = TextView() let delayView:TextView = TextView() + private let forgotPasswordView = TitleButton() + private let resetAccountView = TitleButton() + fileprivate var selectedItem:CountryItem? fileprivate var undo:[String] = [] @@ -161,9 +303,9 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { delayView.isSelectable = false editControl.set(font: .medium(.title), for: .Normal) - editControl.set(color: .blueUI, for: .Normal) - editControl.set(text: tr(.navigationEdit), for: .Normal) - editControl.sizeToFit() + editControl.set(color: theme.colors.accent, for: .Normal) + editControl.set(text: tr(L10n.navigationEdit), for: .Normal) + _ = editControl.sizeToFit() editControl.set(handler: { [weak self] _ in self?.arguments?.editPhone() @@ -172,21 +314,22 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { - addSubview(yourPhoneLabel) - addSubview(codeLabel) + // addSubview(yourPhoneLabel) + // addSubview(codeLabel) addSubview(editControl) - + + - codeText.textColor = .text codeText.font = NSFont.normal(.title) + codeText.textColor = theme.colors.text + numberText.textColor = theme.colors.grayText - numberText.textColor = .grayText numberText.font = NSFont.normal(.title) numberText.isSelectable = false numberText.isEditable = false @@ -214,26 +357,82 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { addSubview(delayView) addSubview(errorLabel) + + addSubview(forgotPasswordView) + addSubview(resetAccountView) + + forgotPasswordView.isHidden = true + resetAccountView.isHidden = true + + forgotPasswordView.set(font: .normal(.title), for: .Normal) + resetAccountView.set(font: .normal(.title), for: .Normal) + + + forgotPasswordView.set(handler: { [weak self] _ in + self?.arguments?.requestPasswordRecovery({ [weak self] option in + switch option { + case .email: + self?.resetAccountView.isHidden = false + case .none: + alert(for: mainWindow, info: L10n.loginRecoveryMailFailed) + self?.resetAccountView.isHidden = false + } + }) + }, for: .Click) + + resetAccountView.set(handler: { [weak self] _ in + self?.arguments?.resetAccount() + }, for: .Click) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + editControl.set(color: theme.colors.accent, for: .Normal) + + + codeText.textColor = theme.colors.text + numberText.textColor = theme.colors.grayText + + yourPhoneLabel.backgroundColor = theme.colors.background + numberText.backgroundColor = theme.colors.background + codeText.backgroundColor = theme.colors.background + - yourPhoneLabel.attributedString = .initialize(string: tr(.loginYourPhoneLabel), color: .grayText, font: NSFont.normal(FontSize.title)) + errorLabel.backgroundColor = theme.colors.background + delayView.backgroundColor = theme.colors.background + textView.backgroundColor = theme.colors.background + + yourPhoneLabel.attributedString = .initialize(string: L10n.loginYourPhoneLabel, color: theme.colors.grayText, font: .normal(.title)) yourPhoneLabel.sizeToFit() - codeLabel.attributedString = .initialize(string: tr(.loginYourCodeLabel), color: .grayText, font: NSFont.normal(FontSize.title)) + codeLabel.attributedString = .initialize(string: L10n.loginYourCodeLabel, color: theme.colors.grayText, font: .normal(.title)) codeLabel.sizeToFit() - numberText.placeholderAttributedString = NSAttributedString.initialize(string: tr(.loginPhoneFieldPlaceholder), color: .grayText, font: NSFont.normal(.header), coreText: false) - codeText.placeholderAttributedString = NSAttributedString.initialize(string: tr(.loginCodePlaceholder), color: .grayText, font: NSFont.normal(.header), coreText: false) + numberText.placeholderAttributedString = .initialize(string: L10n.loginPhoneFieldPlaceholder, color: theme.colors.grayText, font: .normal(.header), coreText: false) + codeText.placeholderAttributedString = .initialize(string: L10n.loginCodePlaceholder, color: theme.colors.grayText, font: .normal(.header), coreText: false) + + + forgotPasswordView.set(color: theme.colors.accent, for: .Normal) + resetAccountView.set(color: theme.colors.redUI, for: .Normal) + + forgotPasswordView.set(text: L10n.loginPasswordForgot, for: .Normal) + resetAccountView.set(text: L10n.loginResetAccountText, for: .Normal) + + + _ = forgotPasswordView.sizeToFit() + _ = resetAccountView.sizeToFit() + + + + needsLayout = true + needsDisplay = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - let defaultInset:CGFloat = 30 fileprivate override func layout() { @@ -242,28 +441,38 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { numberText.sizeToFit() - let maxInset = max(yourPhoneLabel.frame.width, codeLabel.frame.width) - let contentInset = maxInset + 20 + 5 + defaultInset - yourPhoneLabel.setFrameOrigin(maxInset - yourPhoneLabel.frame.width + defaultInset, floorToScreenPixels(25 - yourPhoneLabel.frame.height/2)) - codeLabel.setFrameOrigin(maxInset - codeLabel.frame.width + defaultInset, floorToScreenPixels(75 - codeLabel.frame.height/2)) - codeText.setFrameOrigin(contentInset, floorToScreenPixels(75 - codeText.frame.height/2)) + codeText.setFrameOrigin(0, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + + numberText.setFrameOrigin(0, floorToScreenPixels(backingScaleFactor, 25 - yourPhoneLabel.frame.height/2)) + editControl.setFrameOrigin(frame.width - editControl.frame.width, floorToScreenPixels(backingScaleFactor, 25 - yourPhoneLabel.frame.height/2)) + + + var topOffset: CGFloat = codeText.frame.minY - numberText.setFrameOrigin(contentInset, floorToScreenPixels(25 - yourPhoneLabel.frame.height/2)) - editControl.setFrameOrigin(frame.width - editControl.frame.width, floorToScreenPixels(25 - yourPhoneLabel.frame.height/2)) + if !codeText.isHidden { + topOffset += 50 + } + if numberText.isHidden { + topOffset -= 50 + } + - textView.centerX(y: codeText.frame.maxY + 60 + (passwordEnabled ? inputPassword.frame.height : 0)) + textView.centerX(y: topOffset + 20 + (passwordEnabled ? inputPassword.frame.height : 0)) delayView.centerX(y: textView.frame.maxY + 20) - errorLabel.centerX(y: codeText.frame.maxY + 30 + (passwordEnabled ? inputPassword.frame.height : 0)) + errorLabel.centerX(y: codeText.frame.maxY + 25 + (passwordEnabled ? inputPassword.frame.height : 0)) + + forgotPasswordView.centerX(y: textView.frame.maxY + 10) + resetAccountView.centerX(y: forgotPasswordView.frame.maxY + 5) - inputPassword.passwordLabel.centerY() - inputPassword.passwordLabel.setFrameOrigin(contentInset - inputPassword.passwordLabel.frame.width - 25, inputPassword.passwordLabel.frame.minY) inputPassword.input.setFrameSize(inputPassword.frame.width - inputPassword.passwordLabel.frame.minX, inputPassword.input.frame.height) - inputPassword.input.centerY(x:inputPassword.passwordLabel.frame.maxX + 25) + inputPassword.input.centerY() - inputPassword.setFrameOrigin(0, 101) + + + inputPassword.setFrameOrigin(0, topOffset) } fileprivate func update(with type:SentAuthorizationCodeType, nextType:AuthorizationCodeNextType? = nil, timeout:Int32?) { @@ -304,14 +513,14 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { switch type { case let .otherSession(length: length): codeLength = Int(length) - basic = tr(.loginEnterCodeFromApp) - nextText = tr(.loginSendSmsIfNotReceivedAppCode) + basic = L10n.loginEnterCodeFromApp + nextText = L10n.loginSendSmsIfNotReceivedAppCode case let .sms(length: length): codeLength = Int(length) - basic = tr(.loginJustSentSms) + basic = L10n.loginJustSentSms case let .call(length: length): codeLength = Int(length) - basic = tr(.loginPhoneCalledCode) + basic = L10n.loginPhoneCalledCode default: break } @@ -327,10 +536,10 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { if timeout > 0 { switch nextType { case .call: - nextText = tr(.loginWillCall(minutes, secValue)) + nextText = L10n.loginWillCall(minutes, secValue) break case .sms: - nextText = tr(.loginWillSendSms(minutes, secValue)) + nextText = L10n.loginWillSendSms(minutes, secValue) break default: break @@ -338,8 +547,8 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { } else { switch nextType { case .call: - basic = tr(.loginPhoneCalledCode) - nextText = tr(.loginPhoneDialed) + basic = L10n.loginPhoneCalledCode + nextText = L10n.loginPhoneDialed break default: break @@ -347,11 +556,11 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { } } else { - nextText = tr(.loginSendSmsIfNotReceivedAppCode) + nextText = tr(L10n.loginSendSmsIfNotReceivedAppCode) } } - _ = attr.append(string: basic, color: .grayText, font: .normal(.title)) + _ = attr.append(string: basic, color: theme.colors.grayText, font: .normal(.title)) let textLayout = TextViewLayout(attr, alignment: .center) textLayout.measure(width: 300) textView.update(textLayout) @@ -361,17 +570,17 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { let attr = NSMutableAttributedString() if case .otherSession = type { - _ = attr.append(string: nextText, color: .link , font: .normal(.title)) + _ = attr.append(string: nextText, color: theme.colors.link , font: .normal(.title)) attr.add(link: inAppLink.callback("resend", { [weak self] link in self?.arguments?.resendCode() }), for: attr.range) if timeout == nil { - attr.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.colors.link, range: attr.range) + attr.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.colors.link, range: attr.range) } else if let timeout = timeout { - attr.addAttribute(NSAttributedStringKey.foregroundColor, value: timeout <= 0 ? theme.colors.link : theme.colors.grayText, range: attr.range) + attr.addAttribute(NSAttributedString.Key.foregroundColor, value: timeout <= 0 ? theme.colors.link : theme.colors.grayText, range: attr.range) } } else { - _ = attr.append(string: nextText, color: .grayText, font: .normal(.title)) + _ = attr.append(string: nextText, color: theme.colors.grayText, font: .normal(.title)) } let layout = TextViewLayout(attr) layout.interactions = globalLinkExecutor @@ -385,12 +594,14 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { func update(number: String, type: SentAuthorizationCodeType, hash: String, timeout: Int32?, nextType: AuthorizationCodeNextType?, animated: Bool) { self.passwordEnabled = false - self.numberText.stringValue = number - self.codeText.textColor = .text + self.numberText.stringValue = formatPhoneNumber(number) self.codeText.stringValue = "" self.codeText.isEditable = true self.codeText.isSelectable = true inputPassword.isHidden = true + forgotPasswordView.isHidden = true + resetAccountView.isHidden = true + clearError() self.update(with: type, nextType: nextType, timeout: timeout) } @@ -398,11 +609,13 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { let textError:String switch error { case .limitExceeded: - textError = tr(.loginFloodWait) + textError = L10n.loginFloodWait case .invalidCode: - textError = tr(.phoneCodeInvalid) + textError = L10n.phoneCodeInvalid case .generic: - textError = tr(.phoneCodeExpired) + textError = L10n.phoneCodeExpired + case .codeExpired: + textError = L10n.phoneCodeExpired } errorLabel.state.set(.single(.error(textError))) codeText.shake() @@ -412,9 +625,9 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { let text:String switch error { case .invalidPassword: - text = tr(.passwordHashInvalid) + text = L10n.passwordHashInvalid case .limitExceeded: - text = tr(.loginFloodWait) + text = L10n.loginFloodWait case .generic: text = "undefined error" } @@ -431,22 +644,29 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { func showPasswordInput(_ hint:String, _ number:String, _ code:String, animated: Bool) { errorLabel.state.set(.single(.normal)) self.passwordEnabled = true + + self.codeText.isHidden = code.isEmpty + self.numberText.isHidden = number.isEmpty + self.editControl.isHidden = number.isEmpty + self.numberText.stringValue = number self.codeText.stringValue = code if !hint.isEmpty { - self.inputPassword.input.placeholderAttributedString = NSAttributedString.initialize(string: hint, color: .grayText, font: .normal(.title)) + self.inputPassword.input.placeholderAttributedString = .initialize(string: hint, color: theme.colors.grayText, font: .normal(.title)) } else { - self.inputPassword.input.placeholderAttributedString = NSAttributedString.initialize(string: tr(.loginPasswordPlaceholder), color: .grayText, font: .normal(.title)) + self.inputPassword.input.placeholderAttributedString = .initialize(string: L10n.loginPasswordPlaceholder, color: theme.colors.grayText, font: .normal(.title)) } self.inputPassword.isHidden = false - self.codeText.textColor = .grayText + self.codeText.textColor = theme.colors.grayText self.codeText.isEditable = false self.codeText.isSelectable = false - let textLayout = TextViewLayout(.initialize(string: tr(.loginEnterPasswordDescription), color: .grayText, font: .normal(.title)), alignment: .center) + let textLayout = TextViewLayout(.initialize(string: L10n.loginEnterPasswordDescription, color: theme.colors.grayText, font: .normal(.title)), alignment: .center) textLayout.measure(width: 300) textView.update(textLayout) + + disposable.set(nil) delayView.isHidden = true @@ -456,10 +676,13 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { textView.centerX() textView.change(pos: NSMakePoint(textView.frame.minX, textView.frame.minY + inputPassword.frame.height), animated: animated) + forgotPasswordView.isHidden = false + needsLayout = true + needsDisplay = true } - override func controlTextDidChange(_ obj: Notification) { + func controlTextDidChange(_ obj: Notification) { let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined().prefix(codeLength) codeText.stringValue = code codeText.sizeToFit() @@ -481,10 +704,13 @@ private class InputCodeContainerView : View, NSTextFieldDelegate { super.draw(layer, in: ctx) - let maxInset = max(yourPhoneLabel.frame.width, codeLabel.frame.width) + 20 ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(defaultInset + maxInset, 50, frame.width - maxInset, .borderSize)) - ctx.fill(NSMakeRect(defaultInset + maxInset, 100, frame.width - maxInset, .borderSize)) + if !self.numberText.isHidden { + ctx.fill(NSMakeRect(0, 50, frame.width, .borderSize)) + } + if !codeText.isHidden { + ctx.fill(NSMakeRect(0, 100, frame.width, .borderSize)) + } } override func setFrameSize(_ newSize: NSSize) { @@ -518,30 +744,29 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { super.init(frame: frameRect) - countrySelector.style = ControlStyle(font: NSFont.medium(.title), foregroundColor: NSColor(0x007ee5), backgroundColor:.white) + countrySelector.style = ControlStyle(font: .medium(.title), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) countrySelector.set(text: "France", for: .Normal) - countrySelector.sizeToFit() + _ = countrySelector.sizeToFit() addSubview(countrySelector) - - addSubview(countryLabel) - addSubview(numberLabel) + // addSubview(countryLabel) + // addSubview(numberLabel) countrySelector.set(handler: { [weak self] _ in self?.showCountrySelector() }, for: .Click) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) codeText.stringValue = "+" - codeText.textColor = .text - codeText.font = NSFont.normal(.title) + - numberText.textColor = .text - numberText.font = NSFont.normal(.title) + codeText.font = .normal(.title) + + numberText.font = .normal(.title) numberText.isBordered = false numberText.isBezeled = false @@ -569,29 +794,42 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) - countryLabel.attributedString = .initialize(string: tr(.loginCountryLabel), color: .grayText, font: NSFont.normal(FontSize.title)) + countrySelector.style = ControlStyle(font: .medium(.title), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) + + + codeText.backgroundColor = theme.colors.background + numberText.backgroundColor = theme.colors.background + + countryLabel.attributedString = .initialize(string: L10n.loginCountryLabel, color: theme.colors.grayText, font: .normal(.title)) countryLabel.sizeToFit() - numberLabel.attributedString = .initialize(string: tr(.loginYourPhoneLabel), color: .grayText, font: NSFont.normal(FontSize.title)) + numberLabel.attributedString = .initialize(string: L10n.loginYourPhoneLabel, color: theme.colors.grayText, font: .normal(.title)) numberLabel.sizeToFit() - numberText.placeholderAttributedString = NSAttributedString.initialize(string: tr(.loginPhoneFieldPlaceholder), color: .grayText, font: NSFont.normal(.header), coreText: false) + numberText.placeholderAttributedString = .initialize(string: L10n.loginPhoneFieldPlaceholder, color: theme.colors.grayText, font: .normal(.header), coreText: false) needsLayout = true + needsDisplay = true } func setPhoneError(_ error: AuthorizationCodeRequestError) { let text:String switch error { case .invalidPhoneNumber: - text = tr(.phoneNumberInvalid) + text = tr(L10n.phoneNumberInvalid) case .limitExceeded: - text = tr(.loginFloodWait) + text = tr(L10n.loginFloodWait) case .generic: text = "undefined error" + case .phoneLimitExceeded: + text = "undefined error" + case .phoneBanned: + text = "PHONE BANNED" + case .timeout: + text = "timeout" } errorLabel.state.set(.single(.error(text))) } @@ -610,26 +848,26 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { codeText.sizeToFit() numberText.sizeToFit() - let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) - let contentInset = maxInset + 20 + 5 - countrySelector.setFrameOrigin(contentInset, floorToScreenPixels(25 - countrySelector.frame.height/2)) + // let maxInset: CGFloat = max(countryLabel.frame.width,numberLabel.frame.width) + // let contentInset = maxInset + 20 + 5 + countrySelector.setFrameOrigin(0, floorToScreenPixels(backingScaleFactor, 25 - countrySelector.frame.height/2)) - countryLabel.setFrameOrigin(maxInset - countryLabel.frame.width, floorToScreenPixels(25 - countryLabel.frame.height/2)) - numberLabel.setFrameOrigin(maxInset - numberLabel.frame.width, floorToScreenPixels(75 - numberLabel.frame.height/2)) + // countryLabel.setFrameOrigin(maxInset - countryLabel.frame.width, floorToScreenPixels(backingScaleFactor, 25 - countryLabel.frame.height/2)) + // numberLabel.setFrameOrigin(maxInset - numberLabel.frame.width, floorToScreenPixels(backingScaleFactor, 75 - numberLabel.frame.height/2)) - codeText.setFrameOrigin(contentInset, floorToScreenPixels(75 - codeText.frame.height/2)) - numberText.setFrameOrigin(contentInset + separatorInset, floorToScreenPixels(75 - codeText.frame.height/2)) - errorLabel.centerX(y: 120) + codeText.setFrameOrigin(0, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + numberText.setFrameOrigin(separatorInset, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + errorLabel.centerX(y: 110) } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) + 20 + // let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) + 20 ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(maxInset, 50, frame.width - maxInset, .borderSize)) - ctx.fill(NSMakeRect(maxInset, 100, frame.width - maxInset, .borderSize)) + ctx.fill(NSMakeRect(0, 50, frame.width, .borderSize)) + ctx.fill(NSMakeRect(0, 100, frame.width, .borderSize)) // ctx.fill(NSMakeRect(maxInset + separatorInset, 50, .borderSize, 50)) } @@ -651,9 +889,10 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { } - override func controlTextDidChange(_ obj: Notification) { - + func controlTextDidChange(_ obj: Notification) { if let field = obj.object as? NSTextField { + hasChanges = true + let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() let dec = code.prefix(4) @@ -661,7 +900,7 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { if code.length > 4 { - let list = Array(code.characters).map {String($0)} + let list = code.map {String($0)} let reduced = list.reduce([], { current, value -> [String] in var current = current current.append((current.last ?? "") + value) @@ -704,12 +943,18 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { } else if field == numberText { - var formated = formatPhoneNumber(dec + numberText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) + let current = dec + numberText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() + var formated: String = current + if !current.hasPrefix("99288") { + formated = formatPhoneNumber(current) + } if formated.hasPrefix("+") { formated = formated.fromSuffix(2) } formated = formated.substring(from: dec.endIndex).prefix(17) numberText.stringValue = formated + + self.arguments?.updatePhoneNumberField(formated) } } @@ -744,10 +989,12 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { return false } + fileprivate var hasChanges: Bool = false + func update(selectedItem:CountryItem?, update:Bool, updateCode:Bool = true) -> Void { self.selectedItem = selectedItem if update { - countrySelector.set(text: selectedItem?.shortName ?? tr(.loginInvalidCountryCode), for: .Normal) + countrySelector.set(text: selectedItem?.shortName ?? tr(L10n.loginInvalidCountryCode), for: .Normal) countrySelector.sizeToFit() if updateCode { codeText.stringValue = selectedItem != nil ? "+\(selectedItem!.code)" : "+" @@ -766,11 +1013,132 @@ private class PhoneNumberContainerView : View, NSTextFieldDelegate { } + +private func timerValueString(days: Int32, hours: Int32, minutes: Int32) -> String { + var string = NSMutableAttributedString() + + var daysString = "" + if days > 0 { + daysString = "**" + L10n.timerDaysCountable(Int(days)) + "** " + } + + var hoursString = "" + if hours > 0 || days > 0 { + hoursString = "**" + L10n.timerHoursCountable(Int(hours)) + "** " + } + + let minutesString = "**" + L10n.timerMinutesCountable(Int(minutes)) + "**" + + return daysString + hoursString + minutesString +} + +private final class AwaitingResetConfirmationView : View { + private let textView: TextView = TextView() + private let reset: TitleButton = TitleButton() + private var phoneNumber: String = "" + private var protectedUntil: Int32 = 0 + private var timer: SwiftSignalKit.Timer? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + textView.isSelectable = false + addSubview(textView) + addSubview(reset) + + reset.set(font: .bold(.title), for: .Normal) + } + + func update(with phoneNumber: String, until:Int32, reset: @escaping()-> Void) -> Void { + self.phoneNumber = phoneNumber + self.protectedUntil = until + updateLocalizationAndTheme(theme: theme) + + self.reset.removeAllHandlers() + self.reset.set(handler: { _ in + reset() + }, for: .Click) + + if self.timer == nil { + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in + self?.updateTimerValue() + }, queue: Queue.mainQueue()) + self.timer = timer + timer.start() + } + } + + deinit { + timer?.invalidate() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + reset.set(color: theme.colors.redUI, for: .Normal) + reset.set(text: L10n.loginResetAccount, for: .Normal) + _ = reset.sizeToFit() + updateTimerValue() + } + + private func updateTimerValue() { + let timerSeconds = max(0, self.protectedUntil - Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + + let secondsInAMinute: Int32 = 60 + let secondsInAnHour: Int32 = 60 * secondsInAMinute + let secondsInADay: Int32 = 24 * secondsInAnHour + + let days = timerSeconds / secondsInADay + + let hourSeconds = timerSeconds % secondsInADay + let hours = hourSeconds / secondsInAnHour + + let minuteSeconds = hourSeconds % secondsInAnHour + var minutes = minuteSeconds / secondsInAMinute + + if days == 0 && hours == 0 && minutes == 0 && timerSeconds > 0 { + minutes = 1 + } + + + let attr = NSMutableAttributedString() + + + _ = attr.append(string: L10n.twoStepAuthResetDescription(self.phoneNumber, timerValueString(days: days, hours: hours, minutes: minutes)), color: theme.colors.grayText, font: .normal(.text)) + attr.detectBoldColorInString(with: .bold(.text)) + + let layout = TextViewLayout(attr, alignment: .left, alwaysStaticItems: true) + layout.measure(width: frame.width) + + textView.update(layout) + needsLayout = true + + self.reset.isEnabled = timerSeconds <= 0 + + if timerSeconds <= 0 { + timer?.invalidate() + timer = nil + } + + } + + override func layout() { + super.layout() + textView.centerX() + reset.setFrameOrigin(0, textView.frame.maxY + 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + class LoginAuthInfoView : View { fileprivate var state: UnauthorizedAccountStateContents = .empty private let phoneNumberContainer:PhoneNumberContainerView + private let resetAccountContainer:AwaitingResetConfirmationView + private let codeInputContainer:InputCodeContainerView private let signupView:SignupView var arguments:LoginAuthViewArguments? { @@ -785,6 +1153,9 @@ class LoginAuthInfoView : View { var phoneNumber:String { return phoneNumberContainer.codeText.stringValue + phoneNumberContainer.numberText.stringValue } + func trySignUp() { + signupView.trySignUp() + } var code:String { return codeInputContainer.codeText.stringValue @@ -809,12 +1180,20 @@ class LoginAuthInfoView : View { required init(frame frameRect: NSRect) { codeInputContainer = InputCodeContainerView(frame: frameRect) phoneNumberContainer = PhoneNumberContainerView(frame: frameRect) + resetAccountContainer = AwaitingResetConfirmationView(frame: frameRect) signupView = SignupView(frame: frameRect) super.init(frame:frameRect) addSubview(codeInputContainer) addSubview(phoneNumberContainer) addSubview(signupView) + addSubview(resetAccountContainer) + } + + func updateCountryCode(_ code: String) { + if !phoneNumberContainer.hasChanges { + phoneNumberContainer.update(selectedItem: manager.item(bySmallCountryName: code), update: true) + } } @@ -836,6 +1215,11 @@ class LoginAuthInfoView : View { return codeInputContainer.inputPassword.input } return window?.firstResponder + case .signUp: + if window?.firstResponder != signupView.firstName.textView || window?.firstResponder != signupView.lastName.textView { + return signupView.firstName + } + return window?.firstResponder default: return nil } @@ -846,7 +1230,7 @@ class LoginAuthInfoView : View { switch state { case let .phoneEntry(countryCode, phoneNumber): - phoneNumberContainer.updateLocalizationAndTheme() + phoneNumberContainer.updateLocalizationAndTheme(theme: theme) phoneNumberContainer.errorLabel.state.set(.single(.normal)) phoneNumberContainer.update(countryCode: countryCode, number: phoneNumber) phoneNumberContainer.isHidden = false @@ -855,58 +1239,93 @@ class LoginAuthInfoView : View { if completed { self?.codeInputContainer.isHidden = true self?.signupView.isHidden = true + self?.resetAccountContainer.isHidden = true } }) codeInputContainer.change(opacity: 0, animated: animated) + resetAccountContainer.change(opacity: 0, animated: animated) + signupView.change(opacity: 0, animated: animated) case .empty: - phoneNumberContainer.updateLocalizationAndTheme() + phoneNumberContainer.updateLocalizationAndTheme(theme: theme) phoneNumberContainer.isHidden = false phoneNumberContainer.change(opacity: 1, animated: animated, completion: { [weak self] completed in if completed { self?.codeInputContainer.isHidden = true self?.signupView.isHidden = true + self?.resetAccountContainer.isHidden = true } }) - case let .confirmationCodeEntry(number, type, hash, timeout, nextType): - codeInputContainer.updateLocalizationAndTheme() + codeInputContainer.change(opacity: 0, animated: animated) + resetAccountContainer.change(opacity: 0, animated: animated) + signupView.change(opacity: 0, animated: animated) + + case let .confirmationCodeEntry(number, type, hash, timeout, nextType, _): + codeInputContainer.updateLocalizationAndTheme(theme: theme) codeInputContainer.isHidden = false codeInputContainer.undo = [] codeInputContainer.update(number: number, type: type, hash: hash, timeout: timeout, nextType: nextType, animated: animated) phoneNumberContainer.change(opacity: 0, animated: animated) signupView.change(opacity: 0, animated: animated) + resetAccountContainer.change(opacity: 0, animated: animated) + + codeInputContainer.change(opacity: 1, animated: animated, completion: { [weak self] completed in if completed { self?.phoneNumberContainer.isHidden = true self?.signupView.isHidden = true + self?.resetAccountContainer.isHidden = true } }) - case let .passwordEntry(hint, number, code): - codeInputContainer.updateLocalizationAndTheme() + case let .passwordEntry(hint, number, code, _, _): + codeInputContainer.updateLocalizationAndTheme(theme: theme) codeInputContainer.isHidden = false codeInputContainer.showPasswordInput(hint, number ?? "", code ?? "", animated: animated) phoneNumberContainer.change(opacity: 0, animated: animated) signupView.change(opacity: 0, animated: animated) + resetAccountContainer.change(opacity: 0, animated: animated) codeInputContainer.change(opacity: 1, animated: animated, completion: { [weak self] completed in if completed { self?.phoneNumberContainer.isHidden = true self?.signupView.isHidden = true + self?.resetAccountContainer.isHidden = true } }) case .signUp: - signupView.updateLocalizationAndTheme() + signupView.updateLocalizationAndTheme(theme: theme) signupView.isHidden = false phoneNumberContainer.change(opacity: 0, animated: animated) codeInputContainer.change(opacity: 0, animated: animated) - + resetAccountContainer.change(opacity: 0, animated: animated) + signupView.change(opacity: 1, animated: animated, completion: { [weak self] completed in if completed { self?.phoneNumberContainer.isHidden = true self?.codeInputContainer.isHidden = true + self?.resetAccountContainer.isHidden = true } }) + case .passwordRecovery: + //TODO + break + case .awaitingAccountReset(let protectedUntil, let number, _): + resetAccountContainer.isHidden = false + + resetAccountContainer.update(with: number ?? "", until: protectedUntil, reset: { [weak self] in + self?.arguments?.resetAccount() + }) + resetAccountContainer.change(opacity: 1, animated: animated, completion: { [weak self] completed in + if completed { + self?.signupView.isHidden = true + self?.phoneNumberContainer.isHidden = true + self?.codeInputContainer.isHidden = true + } + }) + phoneNumberContainer.change(opacity: 0, animated: animated) + codeInputContainer.change(opacity: 0, animated: animated) + signupView.change(opacity: 0, animated: animated) } window?.makeFirstResponder(firstResponder()) } @@ -917,6 +1336,12 @@ class LoginAuthInfoView : View { phoneNumberContainer.setFrameSize(newSize) codeInputContainer.setFrameSize(newSize) signupView.setFrameSize(newSize) + resetAccountContainer.setFrameSize(newSize) + + phoneNumberContainer.centerX() + codeInputContainer.centerX() + signupView.centerX() + resetAccountContainer.centerX() } diff --git a/Telegram-Mac/LogoutViewController.swift b/Telegram-Mac/LogoutViewController.swift new file mode 100644 index 0000000000..0ecffaa475 --- /dev/null +++ b/Telegram-Mac/LogoutViewController.swift @@ -0,0 +1,159 @@ +// +// LogoutViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private struct LogoutControllerState : Equatable { + +} + +private final class LogoutControllerArguments { + let addAccount: ()->Void + let setPasscode: ()->Void + let clearCache: ()->Void + let changePhoneNumber: ()->Void + let contactSupport: ()->Void + let logout: ()->Void + init(addAccount: @escaping()-> Void, setPasscode: @escaping()->Void, clearCache: @escaping()->Void, changePhoneNumber: @escaping()->Void, contactSupport: @escaping()->Void, logout: @escaping()->Void) { + self.addAccount = addAccount + self.setPasscode = setPasscode + self.clearCache = clearCache + self.changePhoneNumber = changePhoneNumber + self.contactSupport = contactSupport + self.logout = logout + } +} + +private let _id_add_account: InputDataIdentifier = InputDataIdentifier("_id_add_account") +private let _id_set_a_passcode: InputDataIdentifier = InputDataIdentifier("_id_set_a_passcode") +private let _id_clear_cache: InputDataIdentifier = InputDataIdentifier("_id_clear_cache") +private let _id_change_phone_number: InputDataIdentifier = InputDataIdentifier("_id_change_phone_number") +private let _id_contact_support: InputDataIdentifier = InputDataIdentifier("_id_contact_support") + +private let _id_log_out: InputDataIdentifier = InputDataIdentifier("_id_log_out") + + +private func logoutEntries(state: LogoutControllerState, activeAccounts: [AccountWithInfo], arguments: LogoutControllerArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.logoutOptionsAlternativeOptionsSection), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + if activeAccounts.count < 3 { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_add_account, data: InputDataGeneralData(name: L10n.logoutOptionsAddAccountTitle, color: theme.colors.text, icon: theme.icons.logoutOptionAddAccount, type: .next, viewType: .firstItem, description: L10n.logoutOptionsAddAccountText, action: arguments.addAccount))) + index += 1 + } + + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_set_a_passcode, data: InputDataGeneralData(name: L10n.logoutOptionsSetPasscodeTitle, color: theme.colors.text, icon: theme.icons.logoutOptionSetPasscode, type: .next, viewType: .innerItem, description: L10n.logoutOptionsSetPasscodeText, action: arguments.setPasscode))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_clear_cache, data: InputDataGeneralData(name: L10n.logoutOptionsClearCacheTitle, color: theme.colors.text, icon: theme.icons.logoutOptionClearCache, type: .next, viewType: .innerItem, description: L10n.logoutOptionsClearCacheText, action: arguments.clearCache))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_change_phone_number, data: InputDataGeneralData(name: L10n.logoutOptionsChangePhoneNumberTitle, color: theme.colors.text, icon: theme.icons.logoutOptionChangePhoneNumber, type: .next, viewType: .innerItem, description: L10n.logoutOptionsChangePhoneNumberText, action: arguments.changePhoneNumber))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_contact_support, data: InputDataGeneralData(name: L10n.logoutOptionsContactSupportTitle, color: theme.colors.text, icon: theme.icons.logoutOptionContactSupport, type: .next, viewType: .lastItem, description: L10n.logoutOptionsContactSupportText, action: arguments.contactSupport))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + +// entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_log_out, data: InputDataGeneralData(name: L10n.logoutOptionsLogOut, color: theme.colors.redUI, viewType: .singleItem, action: arguments.logout))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.logoutOptionsLogOutInfo), data: InputDataGeneralTextData(viewType: .textBottomItem))) +// index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func LogoutViewController(context: AccountContext, f: @escaping((ViewController)) -> Void) -> InputDataModalController { + + let state: ValuePromise = ValuePromise(LogoutControllerState()) + let stateValue: Atomic = Atomic(value: LogoutControllerState()) + + let updateState:((LogoutControllerState)->LogoutControllerState) -> Void = { f in + state.set(stateValue.modify(f)) + } + + + + let arguments = LogoutControllerArguments(addAccount: { + let testingEnvironment = NSApp.currentEvent?.modifierFlags.contains(.command) == true + context.sharedContext.beginNewAuth(testingEnvironment: testingEnvironment) + }, setPasscode: { + closeAllModals() + f(PasscodeSettingsViewController(context)) + }, clearCache: { + closeAllModals() + f(StorageUsageController(context)) + }, changePhoneNumber: { + closeAllModals() + f(PhoneNumberIntroController(context)) + }, contactSupport: { + confirm(for: mainWindow, information: L10n.accountConfirmAskQuestion, thridTitle: L10n.accountConfirmGoToFaq, successHandler: { result in + closeAllModals() + switch result { + case .basic: + _ = showModalProgress(signal: supportPeerId(account: context.account), for: mainWindow).start(next: { peerId in + if let peerId = peerId { + f(ChatController(context: context, chatLocation: .peer(peerId))) + } + }) + case .thrid: + openFaq(context: context) + } + }) + }, logout: { + confirm(for: mainWindow, header: L10n.accountConfirmLogout, information: L10n.accountConfirmLogoutText, successHandler: { _ in + closeAllModals() + _ = logoutFromAccount(id: context.account.id, accountManager: context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).start() + }) + }) + + let signal = combineLatest(state.get() |> distinctUntilChanged, context.sharedContext.activeAccountsWithInfo |> map {$0.accounts}) |> map { state, activeAccounts in + return logoutEntries(state: state, activeAccounts: activeAccounts, arguments: arguments) + } + + let controller = InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0) }, title: L10n.logoutOptionsTitle, hasDone: false) + + + let modalController = InputDataModalController(controller, modalInteractions: ModalInteractions(acceptTitle: L10n.logoutOptionsLogOut, accept: { + arguments.logout() + }, drawBorder: true, height: 50, singleButton: true)) + + controller.afterTransaction = { [weak modalController] controller in + modalController?.modalInteractions?.updateDone { button in + button.set(color: theme.colors.redUI, for: .Normal) + } + } + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + return modalController +} diff --git a/Telegram-Mac/LottieBufferCompressor.swift b/Telegram-Mac/LottieBufferCompressor.swift new file mode 100644 index 0000000000..15503ad18d --- /dev/null +++ b/Telegram-Mac/LottieBufferCompressor.swift @@ -0,0 +1,367 @@ +// +// BufferCompressor.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Compression +import Accelerate +import Postbox +import SwiftSignalKit +import TGUIKit + +private enum WriteResult { + case success + case failed +} +private enum ReadResult { + case success(Data) + case cached(Data, ()->Void) + case failed +} + +private struct FrameDst : Codable { + let offset: Int + let length: Int + init(offset: Int, length: Int) { + self.offset = offset + self.length = length + } +} + +private var sharedData:Atomic<[LottieAnimationEntryKey:WeakReference]> = Atomic(value: [:]) + + +final class TRLotData { + + + fileprivate var map:[Int : FrameDst] + fileprivate let bufferSize: Int + + private let mapPath: String + private let dataPath: String + + private var readHandle: FileHandle? + private var writeHandle: FileHandle? + private let key: LottieAnimationEntryKey + fileprivate let queue: Queue + + fileprivate func hasAlreadyFrame(_ frame: Int) -> Bool { + assert(queue.isCurrent()) + return self.map[frame] != nil + } + fileprivate func readFrame(frame: Int) -> ReadResult { + + self.writeHandle?.closeFile() + self.writeHandle = nil + assert(queue.isCurrent()) + + if let dest = map[frame] { + let readHande: FileHandle? + if let handle = self.readHandle { + readHande = handle + } else { + readHande = FileHandle(forReadingAtPath: self.dataPath) + self.readHandle = readHande + } + + guard let dataHandle = readHande else { + self.map.removeAll() + return .failed + } + + dataHandle.seek(toFileOffset: UInt64(dest.offset)) + let data = dataHandle.readData(ofLength: dest.length) + if data.count == dest.length { + return .success(data) + } else { + self.map.removeValue(forKey: frame) + return .failed + } + } + + return .failed + } + + deinit { + queue.sync { + self.readHandle?.closeFile() + self.writeHandle?.closeFile() + let data = try? PropertyListEncoder().encode(self.map) + if let data = data { + _ = NSKeyedArchiver.archiveRootObject(data, toFile: self.mapPath) + } + } + _ = sharedData.modify { value in + var value = value + value.removeValue(forKey: self.key) + return value + } + } + + fileprivate func writeFrame(frame: Int, data:Data, endFrame: Int) -> WriteResult { + self.readHandle?.closeFile() + self.readHandle = nil + assert(queue.isCurrent()) + if map[frame] == nil { + let writeHandle: FileHandle? + if let handle = self.writeHandle { + writeHandle = handle + } else { + writeHandle = FileHandle(forWritingAtPath: self.dataPath) + self.writeHandle = writeHandle + } + + guard let dataHandle = writeHandle else { + return .failed + } + let length = dataHandle.seekToEndOfFile() + dataHandle.write(data) + self.map[frame] = FrameDst(offset: Int(length), length: data.count) + } + + return .success + } + + + fileprivate static var directory: String { + let appGroupName = ApiEnvironment.group + let groupPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)!.path + + let path = groupPath + "/trlottie-animations/" + try? FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + return path + } + + + static func mapPath(_ animation: LottieAnimation, bufferSize: Int) -> String { + + let path = TRLotData.directory + animation.cacheKey + + return path + "-v4-lzfse-bs\(bufferSize)-lt\(animation.liveTime)-map" + } + + static func dataPath(_ animation: LottieAnimation, bufferSize: Int) -> String { + let path = TRLotData.directory + animation.cacheKey + + return path + "-v4-lzfse-bs\(bufferSize)-lt\(animation.liveTime)-data" + } + + init(_ animation: LottieAnimation, endFrame: Int, bufferSize: Int, queue: Queue) { + self.queue = queue + self.mapPath = TRLotData.mapPath(animation, bufferSize: bufferSize) + self.dataPath = TRLotData.dataPath(animation, bufferSize: bufferSize) + self.key = animation.key + var mapHandle:FileHandle? + + let deferr:(TRLotData)->Void = { data in + if !FileManager.default.fileExists(atPath: data.mapPath) { + FileManager.default.createFile(atPath: data.mapPath, contents: nil, attributes: nil) + } + if !FileManager.default.fileExists(atPath: data.dataPath) { + FileManager.default.createFile(atPath: data.dataPath, contents: nil, attributes: nil) + } + try? FileManager.default.setAttributes([.modificationDate : Date()], ofItemAtPath: data.mapPath) + try? FileManager.default.setAttributes([.modificationDate : Date()], ofItemAtPath: data.dataPath) + + _ = sharedData.modify { value in + var value = value + value[data.key] = WeakReference(value: data) + return value + } + mapHandle?.closeFile() + } + + guard let handle = FileHandle(forReadingAtPath: self.mapPath) else { + self.map = [:] + self.bufferSize = bufferSize + deferr(self) + return + } + mapHandle = handle + + guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: self.mapPath) as? Data else { + self.map = [:] + self.bufferSize = bufferSize + deferr(self) + return + } + do { + self.map = try PropertyListDecoder().decode([Int: FrameDst].self, from: data) + self.bufferSize = bufferSize + deferr(self) + } catch { + self.map = [:] + self.bufferSize = bufferSize + deferr(self) + } + + + } + +} + +private let lzfseQueue = Queue(name: "LZFSE BUFFER Queue", qos: DispatchQoS.default) + + +final class TRLotFileSupplyment { + fileprivate let bufferSize: Int + fileprivate let data:TRLotData + fileprivate let queue: Queue + fileprivate var shouldWaitToRead: [Int:Int] = [:] + + init(_ animation:LottieAnimation, bufferSize: Int, frames: Int, queue: Queue) { + let cached = sharedData.with { $0[animation.key]?.value } + let queue = cached?.queue ?? queue + self.data = cached ?? TRLotData(animation, endFrame: frames, bufferSize: bufferSize, queue: queue) + self.queue = queue + for value in self.data.map { + shouldWaitToRead[value.key] = value.key + } + self.bufferSize = bufferSize + } + func addFrame(_ previous: RenderedFrame?, _ current: RenderedFrame, endFrame: Int) { + if shouldWaitToRead[Int(current.frame)] == nil { + shouldWaitToRead[Int(current.frame)] = Int(current.frame) + queue.async { + if !self.data.hasAlreadyFrame(Int(current.frame)) { + let address = current.data.assumingMemoryBound(to: UInt8.self) + + let dst: UnsafeMutablePointer = malloc(self.bufferSize)!.assumingMemoryBound(to: UInt8.self) + var length:Int = self.bufferSize + if let previous = previous { + let uint64Bs = self.bufferSize / 8 + let dstDelta: UnsafeMutablePointer = malloc(self.bufferSize)!.assumingMemoryBound(to: UInt8.self) + memcpy(dstDelta, previous.data.assumingMemoryBound(to: UInt8.self), self.bufferSize) + + + let ui64Dst = dstDelta.withMemoryRebound(to: UInt64.self, capacity: uint64Bs, { previousBytes in + return previousBytes + }) + + let ui64Address = address.withMemoryRebound(to: UInt64.self, capacity: uint64Bs, { address in + return address + }) + + + var i: Int = 0 + while i < uint64Bs { + ui64Dst[i] = ui64Dst[i] ^ ui64Address[i] + i &+= 1 + } + + let ui8 = ui64Dst.withMemoryRebound(to: UInt8.self, capacity: self.bufferSize, { body in + return body + }) + + length = compression_encode_buffer(dst, self.bufferSize, ui8, self.bufferSize, nil, COMPRESSION_LZ4) + dstDelta.deallocate() + } else { + length = compression_encode_buffer(dst, self.bufferSize, address, self.bufferSize, nil, COMPRESSION_LZ4) + } + let _ = self.data.writeFrame(frame: Int(current.frame), data: Data(bytesNoCopy: dst, count: length, deallocator: .none), endFrame: endFrame) + dst.deallocate() + } + } + } + } + + func readFrame(previous: RenderedFrame?, frame: Int) -> UnsafeRawPointer? { + var rendered: UnsafeRawPointer? = nil + if shouldWaitToRead[frame] != nil { + queue.sync { + switch self.data.readFrame(frame: frame) { + case let .success(data): + let address = malloc(bufferSize)!.assumingMemoryBound(to: UInt8.self) + + + rendered = data.withUnsafeBytes { dataBytes -> UnsafeRawPointer in + + let unsafeBufferPointer = dataBytes.bindMemory(to: UInt8.self) + let unsafePointer = unsafeBufferPointer.baseAddress! + + let _ = compression_decode_buffer(address, bufferSize, unsafePointer, data.count, nil, COMPRESSION_LZ4) + + if let previous = previous { + + let previousBytes = previous.data.assumingMemoryBound(to: UInt64.self) + + let uint64Bs = self.bufferSize / 8 + + address.withMemoryRebound(to: UInt64.self, capacity: uint64Bs, { address in + var i = 0 + while i < uint64Bs { + address[i] = previousBytes[i] ^ address[i] + i &+= 1 + } + }) + + } + return UnsafeRawPointer(address) + } + + default: + rendered = nil + } + } + } + + return rendered + } + + +} + + + + +private final class CacheRemovable { + init() { + + } + + fileprivate func start() { + let signal = Signal.single(Void()) |> deliverOn(lzfseQueue) |> then (Signal.single(Void()) |> delay(30 * 60, queue: lzfseQueue) |> restart) + + + _ = signal.start(next: { + self.clean() + }) + } + + private func clean() { + + let fileURLs = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: TRLotData.directory), includingPropertiesForKeys: nil, options: .skipsHiddenFiles ) + if let fileURLs = fileURLs { + for url in fileURLs { + let path = url.path + let name = path.nsstring.lastPathComponent + if let index = name.range(of: "lt") { + let tail = String(name[index.upperBound...]) + if let until = tail.range(of: "-") { + if let liveTime = TimeInterval(tail[.. { + private var lock: pthread_mutex_t + private var value: T + + public init(value: T) { + self.lock = pthread_mutex_t() + self.value = value + pthread_mutex_init(&self.lock, nil) + } + + deinit { + pthread_mutex_destroy(&self.lock) + } + + public func with(_ f: (T) -> R) -> R { + pthread_mutex_lock(&self.lock) + let result = f(self.value) + pthread_mutex_unlock(&self.lock) + + return result + } + + public func modify(_ f: (T) -> T) -> T { + pthread_mutex_lock(&self.lock) + let result = f(self.value) + self.value = result + pthread_mutex_unlock(&self.lock) + + return result + } + + public func swap(_ value: T) -> T { + pthread_mutex_lock(&self.lock) + let previous = self.value + self.value = value + pthread_mutex_unlock(&self.lock) + + return previous + } +} + + +let lottieThreadPool: ThreadPool = ThreadPool(threadCount: 1, threadPriority: 0.1) +private let stateQueue = Queue() + + + +enum LottiePlayerState : Equatable { + case initializing + case failed + case playing + case stoped +} + + +final class RenderedFrame : Equatable { + let frame: Int32 + let data: UnsafeRawPointer + let size: NSSize + let backingScale: Int + let key: LottieAnimationEntryKey + init(key: LottieAnimationEntryKey, frame: Int32, size: NSSize, data: UnsafeRawPointer, backingScale: Int) { + self.key = key + self.frame = frame + self.size = size + self.data = data + self.backingScale = backingScale + } + static func ==(lhs: RenderedFrame, rhs: RenderedFrame) -> Bool { + return lhs.frame == rhs.frame + } + + var bufferSize: Int { + return Int(size.width * CGFloat(backingScale) * size.height * CGFloat(backingScale) * 4) + } + + deinit { + data.deallocate() + + _ = sharedFrames.modify { value in + var value = value + if var shared = value[key] { + shared.removeValue(forKey: frame) + if shared.isEmpty { + value.removeValue(forKey: key) + } else { + value[key] = shared + } + } + return value + } + + } +} + +private var sharedFrames:RenderAtomic<[LottieAnimationEntryKey : [Int32: WeakReference]]> = RenderAtomic(value: [:]) + +private final class RendererState { + fileprivate let animation: LottieAnimation + private(set) var frames: [RenderedFrame] + private(set) var previousFrame:RenderedFrame? + private(set) var cachedFrames:[Int32 : RenderedFrame] + private(set) var currentFrame: Int32 + private(set) var startFrame:Int32 + private(set) var endFrame: Int32 + private(set) var fps: Int32 + private(set) var cancelled: Bool + private(set) weak var layer: RLottieBridge? + private(set) var videoFormat:CMVideoFormatDescription? + private var fileSupplyment: TRLotFileSupplyment? + init(cancelled: Bool, animation: LottieAnimation, layer: RLottieBridge?, fileSupplyment: TRLotFileSupplyment?, frames: [RenderedFrame], cachedFrames: [Int32 : RenderedFrame], currentFrame: Int32, startFrame: Int32, endFrame: Int32, fps: Int32) { + self.fileSupplyment = fileSupplyment + self.animation = animation + self.cancelled = cancelled + self.layer = layer + self.frames = frames + self.cachedFrames = cachedFrames + self.currentFrame = currentFrame + self.startFrame = startFrame + self.endFrame = endFrame + self.fps = fps + } + func withUpdatedFrames(_ frames: [RenderedFrame]) -> RendererState { + self.frames = frames + return self + } + func withAddedFrame(_ frame: RenderedFrame) { + if let fileSupplyment = fileSupplyment { + let prev = frame.frame == 0 ? nil : self.frames.last ?? previousFrame + fileSupplyment.addFrame(prev, frame, endFrame: Int(self.endFrame)) + } + + _ = sharedFrames.modify { value in + var value = value + if value[self.animation.key] == nil { + value[self.animation.key] = [:] + } + value[self.animation.key]?[frame.frame] = WeakReference(value: frame) + return value + } + + + self.frames = self.frames + [frame] + } + + func withUpdatedCurrentFrame(_ currentFrame: Int32) -> RendererState { + self.currentFrame = currentFrame + return self + } + func withUpdatedVideoFormat(_ videoFormat: CMVideoFormatDescription) -> RendererState { + self.videoFormat = videoFormat + return self + } + + func takeFirst() -> RenderedFrame { + var frames = self.frames + if frames.first?.frame == endFrame { + self.previousFrame = nil + } else { + self.previousFrame = frames.last + } + let prev = frames.removeFirst() + self.frames = frames + return prev + } + + func renderFrame(at frame: Int32) -> RenderedFrame? { + if let layer = self.layer { + let s:(w: Int, h: Int) = (w: Int(animation.size.width) * animation.backingScale, h: Int(animation.size.height) * animation.backingScale) + + var data: UnsafeRawPointer? + + let sharedFrame = sharedFrames.with { value -> RenderedFrame? in + return value[animation.key]?[frame]?.value + } + + if let sharedFrame = sharedFrame { + return sharedFrame + } + + if let fileSupplyment = fileSupplyment { + let previous = frame == startFrame ? nil : self.frames.last ?? previousFrame + if let frame = fileSupplyment.readFrame(previous: previous, frame: Int(frame)) { + data = frame + } + } + if data == nil { + let bufferSize = s.w * s.h * 4 + let memoryData = malloc(bufferSize)! + let frameData = memoryData.assumingMemoryBound(to: UInt8.self) + layer.renderFrame(with: frame, into: frameData, width: Int32(s.w), height: Int32(s.h)) + data = UnsafeRawPointer(frameData) + } + + + if let data = data { + return RenderedFrame(key: animation.key, frame: frame, size: animation.size, data: data, backingScale: self.animation.backingScale) + } + + } + return nil + } + + deinit { + + } + + func cancel() -> RendererState { + self.cancelled = true + + return self + } +} + +private final class LottieSoundEffect { + private let player: MediaPlayer + let triggerOn: Int32? + + private(set) var isPlayable: Bool = false + + init(data: Data, animation: LottieAnimation, postbox: Postbox, triggerOn: Int32?) { + + + self.player = MediaPlayer(postbox: postbox, reference: MediaResourceReference.standalone(resource: LottieSoundMediaResource(randomId: Int64(animation.cacheKey.hashValue), data: data)), streamable: false, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: 1.0, fetchAutomatically: true) + self.triggerOn = triggerOn + } + func play() { + if isPlayable { + self.player.play() + isPlayable = false + } + } + + func markAsPlayable() -> Void { + isPlayable = true + } +} + +private let maximum_rendered_frames: Int = 4 +private final class PlayerRenderer { + + private var soundEffect: LottieSoundEffect? + + private(set) var finished: Bool = false + private let animation: LottieAnimation + private var layer: Atomic = Atomic(value: nil) + private let updateState:(LottiePlayerState)->Void + private let displayFrame: (RenderedFrame)->Void + private var timer: SwiftSignalKit.Timer? + private let release:()->Void + init(animation: LottieAnimation, displayFrame: @escaping(RenderedFrame)->Void, release:@escaping()->Void, updateState:@escaping(LottiePlayerState)->Void) { + self.animation = animation + self.displayFrame = displayFrame + self.updateState = updateState + self.release = release + } + + private var onDispose: (()->Void)? + deinit { + self.timer?.invalidate() + self.onDispose?() + _ = self.layer.swap(nil) + self.release() + self.updateState(.stoped) + } + + + func initializeAndPlay() { + self.updateState(.initializing) + assert(stateQueue.isCurrent()) + let decompressed = TGGUnzipData(self.animation.compressed, 8 * 1024 * 1024) + let data: Data? + if let decompressed = decompressed { + data = decompressed + } else { + data = self.animation.compressed + } + if let data = data, !data.isEmpty { + let modified = transformedWithFitzModifier(data: data, fitzModifier: self.animation.key.fitzModifier) + if let json = String(data: modified, encoding: .utf8) { + if let bridge = RLottieBridge(json: json, key: self.animation.cacheKey) { + for color in self.animation.colors { + bridge.setColor(color.color, forKeyPath: color.keyPath) + } + self.play(self.layer.modify({_ in bridge})!) + } else { + self.updateState(.failed) + } + } else { + self.updateState(.failed) + } + } else { + self.updateState(.failed) + } + } + + func playAgain() { + self.layer.with { lottie -> Void in + if let lottie = lottie { + self.play(lottie) + } + } + } + + func playSoundEffect() { + self.soundEffect?.markAsPlayable() + } + private var getCurrentFrame:()->Int32? = { return nil } + var currentFrame: Int32? { + return self.getCurrentFrame() + } + + private func play(_ player: RLottieBridge) { + + self.finished = false + + let fps: Int = max(min(Int(player.fps()), self.animation.maximumFps), 24) + + let bufferSize = Int(self.animation.size.width) * animation.backingScale * Int(self.animation.size.height) * animation.backingScale * 4 + + let fileSupplyment: TRLotFileSupplyment? + switch self.animation.cache { + case .temporaryLZ4: + fileSupplyment = TRLotFileSupplyment(self.animation, bufferSize: bufferSize, frames: Int(player.endFrame()), queue: Queue()) + case .none: + fileSupplyment = nil + } + + let maxFrames:Int32 = 180 + var currentFrame: Int32 = 0 + var startFrame: Int32 = min(min(player.startFrame(), maxFrames), min(player.endFrame(), maxFrames)) + var endFrame: Int32 = min(player.endFrame(), maxFrames) + switch self.animation.playPolicy { + case let .loopAt(firstStart, range): + startFrame = range.lowerBound + endFrame = range.upperBound + if let firstStart = firstStart { + currentFrame = firstStart + } + case let .toEnd(from): + startFrame = max(min(from, endFrame - 1), startFrame) + currentFrame = max(min(from, endFrame - 1), startFrame) + default: + break + } + + let initialState = RendererState(cancelled: false, animation: self.animation, layer: player, fileSupplyment: fileSupplyment, frames: [], cachedFrames: [:], currentFrame: currentFrame, startFrame: startFrame, endFrame: endFrame, fps: max(min(player.fps(), 60), 30)) + + let stateValue:RenderAtomic = RenderAtomic(value: initialState) + let updateState:(_ f:(RendererState?)->RendererState?)->Void = { f in + _ = stateValue.modify(f) + } + + self.getCurrentFrame = { + return stateValue.with { $0?.currentFrame } + } + + var framesTask: ThreadPoolTask? = nil + + let isRendering: Atomic = Atomic(value: false) + + self.onDispose = { + updateState { + $0?.cancel() + } + framesTask?.cancel() + framesTask = nil + _ = stateValue.swap(nil) + } + + let currentState:(_ state: RenderAtomic) -> RendererState? = { state in + return state.with { $0 } + } + + + var add_frames_impl:(()->Void)? = nil + var askedRender: Bool = false + var playedCount: Int32 = 0 + let render:()->Void = { [weak self] in + assert(stateQueue.isCurrent()) + var hungry: Bool = false + var cancelled: Bool = false + if let renderer = self { + var current: RenderedFrame? + updateState { stateValue in + guard let state = stateValue, !state.frames.isEmpty else { + return stateValue + } + current = state.takeFirst() + hungry = state.frames.count < maximum_rendered_frames - 1 + cancelled = state.cancelled + return state + } + + if !cancelled { + if let current = current { + let displayFrame = renderer.displayFrame + let updateState = renderer.updateState + displayFrame(current) + playedCount += 1 + if current.frame > 0 { + updateState(.playing) + } + if let soundEffect = renderer.soundEffect { + if let triggerOn = soundEffect.triggerOn { + let triggers:[Int32] = [triggerOn - 1, triggerOn, triggerOn + 1] + if triggers.contains(current.frame) { + soundEffect.play() + } + } else { + if current.frame == 0 { + soundEffect.play() + } + } + } + if let triggerOn = renderer.animation.triggerOn { + switch triggerOn.0 { + case .first: + if startFrame == current.frame { + DispatchQueue.main.async(execute: triggerOn.1) + } + case .last: + if endFrame - 1 == current.frame { + DispatchQueue.main.async(execute: triggerOn.1) + } + case let .custom(index): + if index == current.frame { + DispatchQueue.main.async(execute: triggerOn.1) + } + } + + } + + switch renderer.animation.playPolicy { + case .loop, .loopAt: + break + case .once: + if current.frame + 1 == currentState(stateValue)?.endFrame { + renderer.finished = true + cancelled = true + updateState(.stoped) + renderer.timer?.invalidate() + framesTask?.cancel() + let onFinish = renderer.animation.onFinish ?? {} + DispatchQueue.main.async(execute: onFinish) + } + case .onceEnd, .toEnd: + if let state = currentState(stateValue), state.endFrame - current.frame <= 1 { + renderer.finished = true + cancelled = true + updateState(.stoped) + renderer.timer?.invalidate() + framesTask?.cancel() + let onFinish = renderer.animation.onFinish ?? {} + DispatchQueue.main.async(execute: onFinish) + } + case let .framesCount(limit): + if limit <= playedCount { + renderer.finished = true + cancelled = true + updateState(.stoped) + renderer.timer?.invalidate() + framesTask?.cancel() + let onFinish = renderer.animation.onFinish ?? {} + DispatchQueue.main.async(execute: onFinish) + } + case let .onceToFrame(frame): + if frame <= current.frame { + renderer.finished = true + cancelled = true + updateState(.stoped) + renderer.timer?.invalidate() + framesTask?.cancel() + let onFinish = renderer.animation.onFinish ?? {} + DispatchQueue.main.async(execute: onFinish) + } + } + + } + } + } + isRendering.with { isRendering in + if hungry && !isRendering && !cancelled && !askedRender { + askedRender = true + add_frames_impl?() + } + } + } + + let maximum = Int(initialState.startFrame + initialState.endFrame) + framesTask = ThreadPoolTask { state in + _ = isRendering.swap(true) + while !state.cancelled.with({$0}) && (currentState(stateValue)?.frames.count ?? Int.max) < min(maximum_rendered_frames, maximum) { + + let currentFrame = stateValue.with { $0?.currentFrame ?? 0 } + + let frame: RenderedFrame? = stateValue.with { $0?.renderFrame(at: currentFrame) } + + _ = stateValue.modify { stateValue -> RendererState? in + guard let state = stateValue else { + return stateValue + } + var currentFrame = state.currentFrame + + if currentFrame % Int32(round(Float(state.fps) / Float(fps))) != 0 { + currentFrame += 1 + } + if currentFrame >= state.endFrame - 1 { + currentFrame = state.startFrame - 1 + } + if let frame = frame { + state.withAddedFrame(frame) + } + return state.withUpdatedCurrentFrame(currentFrame + 1) + } + if frame == nil { + break + } + } + _ = isRendering.swap(false) + stateQueue.async { + askedRender = false + } + } + + let add_frames:()->Void = { + if let framesTask = framesTask { + lottieThreadPool.addTask(framesTask) + } + } + + add_frames_impl = { + add_frames() + } + add_frames() + + self.timer = SwiftSignalKit.Timer(timeout: (1.0 / TimeInterval(fps)), repeat: true, completion: { + render() + }, queue: stateQueue) + + self.timer?.start() + + } + +} + +private final class PlayerContext { + private let rendererRef: QueueLocalObject + fileprivate let animation: LottieAnimation + init(_ animation: LottieAnimation, displayFrame: @escaping(RenderedFrame)->Void, release:@escaping()->Void, updateState: @escaping(LottiePlayerState)->Void) { + self.animation = animation + self.rendererRef = QueueLocalObject.init(queue: stateQueue, generate: { + return PlayerRenderer(animation: animation, displayFrame: displayFrame, release: release, updateState: { state in + Queue.mainQueue().async { + updateState(state) + } + }) + }) + + self.rendererRef.with { renderer in + renderer.initializeAndPlay() + } + } + + func playAgain() { + self.rendererRef.with { renderer in + if renderer.finished { + renderer.playAgain() + } + } + } + func playSoundEffect() { + self.rendererRef.with { renderer in + renderer.playSoundEffect() + } + } + var currentFrame:Int32? { + var currentFrame:Int32? = nil + self.rendererRef.syncWith { renderer in + currentFrame = renderer.currentFrame + } + return currentFrame + } +} + + +enum ASLiveTime : Int { + case chat = 3_600 + case thumb = 259200 +} + +enum ASCachePurpose { + case none + case temporaryLZ4(ASLiveTime) +} + +struct LottieAnimationEntryKey : Hashable { + let size: CGSize + let backingScale: Int + let key:LottieAnimationKey + let fitzModifier: EmojiFitzModifier? + init(key: LottieAnimationKey, size: CGSize, backingScale: Int = Int(System.backingScale), fitzModifier: EmojiFitzModifier? = nil) { + self.key = key + self.size = size + self.backingScale = backingScale + self.fitzModifier = fitzModifier + } + + func withUpdatedBackingScale(_ backingScale: Int) -> LottieAnimationEntryKey { + return LottieAnimationEntryKey(key: key, size: size, backingScale: backingScale, fitzModifier: fitzModifier) + } + + func hash(into hasher: inout Hasher) { + + } +} + +enum LottieAnimationKey : Equatable { + case media(MediaId?) + case bundle(String) +} + +enum LottiePlayPolicy : Equatable { + case loop + case loopAt(firstStart:Int32?, range: ClosedRange) + case once + case onceEnd + case toEnd(from: Int32) + case framesCount(Int32) + case onceToFrame(Int32) +} + +struct LottieColor : Equatable { + let keyPath: String + let color: NSColor +} + +enum LottiePlayerTriggerFrame : Equatable { + case first + case last + case custom(Int32) +} + +final class LottieAnimation : Equatable { + static func == (lhs: LottieAnimation, rhs: LottieAnimation) -> Bool { + return lhs.key == rhs.key && lhs.playPolicy == rhs.playPolicy && lhs.colors == rhs.colors + } + + var liveTime: Int { + switch cache { + case .none: + return 0 + case let .temporaryLZ4(liveTime): + return liveTime.rawValue + } + } + + let compressed: Data + let key: LottieAnimationEntryKey + let cache: ASCachePurpose + let maximumFps: Int + let playPolicy: LottiePlayPolicy + let colors:[LottieColor] + + let postbox: Postbox? + + var onFinish:(()->Void)? + + var triggerOn:(LottiePlayerTriggerFrame, ()->Void, ()->Void)? + + + init(compressed: Data, key: LottieAnimationEntryKey, cachePurpose: ASCachePurpose = .temporaryLZ4(.thumb), playPolicy: LottiePlayPolicy = .loop, maximumFps: Int = 60, colors: [LottieColor] = [], postbox: Postbox? = nil) { + self.compressed = compressed + self.key = key + self.cache = cachePurpose + self.maximumFps = maximumFps + self.playPolicy = playPolicy + self.colors = colors + self.postbox = postbox + } + + var size: NSSize { + var size = key.size +// while (size.width / 16) != round(size.width / 16) { +// size.width += 1 +// size.height += 1 +// } + return size + } + var viewSize: NSSize { + return key.size + } + var backingScale: Int { + return key.backingScale + } + + func withUpdatedBackingScale(_ scale: Int) -> LottieAnimation { + return LottieAnimation(compressed: self.compressed, key: self.key.withUpdatedBackingScale(scale), cachePurpose: self.cache, playPolicy: self.playPolicy, maximumFps: self.maximumFps, colors: self.colors, postbox: self.postbox) + } + func withUpdatedColors(_ colors: [LottieColor]) -> LottieAnimation { + return LottieAnimation(compressed: self.compressed, key: self.key, cachePurpose: self.cache, playPolicy: self.playPolicy, maximumFps: self.maximumFps, colors: colors, postbox: self.postbox) + } + + var cacheKey: String { + switch key.key { + case let .media(id): + if let id = id { + if let fitzModifier = key.fitzModifier { + return "animation-\(id.namespace)-\(id.id)-fitz\(fitzModifier.rawValue)" + } else { + return "animation-\(id.namespace)-\(id.id)" + } + } else { + return "\(arc4random())" + } + case let .bundle(string): + return string + } + } +} +private final class PlayerViewLayer: AVSampleBufferDisplayLayer { + override func action(forKey event: String) -> CAAction? { + return NSNull() + } + + deinit { + if !Thread.isMainThread { + var bp: Int = 0 + bp += 1 + } + // assertOnMainThread() + } +} + + +final class MetalContext { + let device: MTLDevice + let pipelineState: MTLRenderPipelineState + let vertexBuffer: MTLBuffer + let sampler: MTLSamplerState + + init?() { + if let device = CGDirectDisplayCopyCurrentMetalDevice(CGMainDisplayID()) { + self.device = device + } else { + return nil + } + do { + let library = try device.makeLibrary(source: + """ +using namespace metal; + +struct VertexIn { + packed_float3 position; + packed_float2 texCoord; +}; + +struct VertexOut { + float4 position [[position]]; + float2 texCoord; +}; + +vertex VertexOut basic_vertex( + const device VertexIn* vertex_array [[ buffer(0) ]], + unsigned int vid [[ vertex_id ]] +) { + VertexIn VertexIn = vertex_array[vid]; + + VertexOut VertexOut; + VertexOut.position = float4(VertexIn.position, 1.0); + VertexOut.texCoord = VertexIn.texCoord; + + return VertexOut; +} + +fragment float4 basic_fragment( + VertexOut interpolated [[stage_in]], + texture2d tex2D [[ texture(0) ]], + sampler sampler2D [[ sampler(0) ]] +) { + float4 color = tex2D.sample(sampler2D, interpolated.texCoord); + return float4(color.b, color.g, color.r, color.a); +} +""", options: nil) + + let fragmentProgram = library.makeFunction(name: "basic_fragment") + let vertexProgram = library.makeFunction(name: "basic_vertex") + + let pipelineStateDescriptor = MTLRenderPipelineDescriptor() + pipelineStateDescriptor.vertexFunction = vertexProgram + pipelineStateDescriptor.fragmentFunction = fragmentProgram + pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm + + self.pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) + + + let vertexData: [Float] = [ + -1.0, -1.0, 0.0, 0.0, 1.0, + -1.0, 1.0, 0.0, 0.0, 0.0, + 1.0, -1.0, 0.0, 1.0, 1.0, + 1.0, -1.0, 0.0, 1.0, 1.0, + -1.0, 1.0, 0.0, 0.0, 0.0, + 1.0, 1.0, 0.0, 1.0, 0.0 + ] + + let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) + self.vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])! + + let sampler = MTLSamplerDescriptor() + sampler.minFilter = MTLSamplerMinMagFilter.nearest + sampler.magFilter = MTLSamplerMinMagFilter.nearest + sampler.mipFilter = MTLSamplerMipFilter.nearest + sampler.maxAnisotropy = 1 + sampler.sAddressMode = MTLSamplerAddressMode.clampToEdge + sampler.tAddressMode = MTLSamplerAddressMode.clampToEdge + sampler.rAddressMode = MTLSamplerAddressMode.clampToEdge + sampler.normalizedCoordinates = true + sampler.lodMinClamp = 0.0 + sampler.lodMaxClamp = .greatestFiniteMagnitude + self.sampler = device.makeSamplerState(descriptor: sampler)! + + } catch { + return nil + } + } +} + +private final class ContextHolder { + private var useCount: Int = 0 + + let context: MetalContext + init?() { + guard let context = MetalContext() else { + return nil + } + self.context = context + } + func incrementUseCount() { + assert(Queue.mainQueue().isCurrent()) + useCount += 1 + } + func decrementUseCount() { + assert(Queue.mainQueue().isCurrent()) + useCount -= 1 + assert(useCount >= 0) + + if shouldRelease() { + holder = nil + } + } + func shouldRelease() -> Bool { + return useCount == 0 + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + } +} + +private var holder: ContextHolder? + + + +private final class MetalRenderer: View { + private let texture: MTLTexture + private let commandQueue: MTLCommandQueue? + private let metalLayer: CAMetalLayer = CAMetalLayer() + private let context: MetalContext + init(animation: LottieAnimation, context: MetalContext) { + self.context = context + self.commandQueue = context.device.makeCommandQueue() + let textureDesc: MTLTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: Int(animation.size.width) * animation.backingScale, height: Int(animation.size.height) * animation.backingScale, mipmapped: false) + textureDesc.sampleCount = 1 + textureDesc.textureType = .type2D + + self.texture = context.device.makeTexture(descriptor: textureDesc)! + + super.init(frame: NSMakeRect(0, 0, animation.viewSize.width, animation.viewSize.height)) + + self.metalLayer.device = context.device + self.metalLayer.framebufferOnly = true + self.metalLayer.isOpaque = false + self.metalLayer.contentsScale = backingScaleFactor + self.wantsLayer = true + self.layer?.addSublayer(metalLayer) + metalLayer.frame = CGRect(origin: CGPoint(), size: animation.viewSize) + holder?.incrementUseCount() + } + + override func layout() { + super.layout() + metalLayer.frame = self.bounds + } + + override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } + + deinit { + holder?.decrementUseCount() + } + + override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + self.metalLayer.contentsScale = backingScaleFactor + } + + override func removeFromSuperview() { + super.removeFromSuperview() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + func render(bytes: UnsafeRawPointer, size: NSSize, backingScale: Int) { + assertNotOnMainThread() + let region = MTLRegionMake2D(0, 0, Int(size.width) * backingScale, Int(size.height) * backingScale) + + self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: Int(size.width) * backingScale * 4) + + guard let drawable = metalLayer.nextDrawable(), let commandQueue = self.commandQueue, let commandBuffer = commandQueue.makeCommandBuffer() else { + return + } + + let renderPassDescriptor = MTLRenderPassDescriptor() + renderPassDescriptor.colorAttachments[0].texture = drawable.texture + renderPassDescriptor.colorAttachments[0].loadAction = .clear + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) + + + + + let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! + + renderEncoder.setRenderPipelineState(self.context.pipelineState) + renderEncoder.setVertexBuffer(self.context.vertexBuffer, offset: 0, index: 0) + renderEncoder.setFragmentTexture(self.texture, index: 0) + renderEncoder.setFragmentSamplerState(self.context.sampler, index: 0) + renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1) + renderEncoder.endEncoding() + + commandBuffer.present(drawable) + commandBuffer.commit() + } + +} + +private final class LottieFallbackView: NSView { + override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } +} + +class LottiePlayerView : NSView { + private var context: PlayerContext? + + private let _currentState: Atomic = Atomic(value: .initializing) + var currentState: LottiePlayerState { + return _currentState.with { $0 } + } + + private let stateValue: ValuePromise = ValuePromise(.initializing, ignoreRepeated: true) + var state: Signal { + return stateValue.get() + } + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + } + + var animation: LottieAnimation? { + return context?.animation + } + + override var isFlipped: Bool { + return true + } + + override func layout() { + super.layout() + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidChangeBackingProperties() { + if let context = context { + self.set(context.animation.withUpdatedBackingScale(Int(backingScaleFactor))) + } + } + + func playIfNeeded() { + if let context = self.context, context.animation.playPolicy == .once { + context.playAgain() + } else { + context?.playSoundEffect() + } + } + + var currentFrame: Int32? { + if let context = self.context { + return context.currentFrame + } else { + return nil + } + } + + func set(_ animation: LottieAnimation?, reset: Bool = false, saveContext: Bool = false) { + + if let animation = animation { + self.stateValue.set(self._currentState.modify { _ in .initializing }) + if self.context?.animation != animation || reset { + if holder == nil { + holder = ContextHolder() + } + if let holder = holder { + let metal = MetalRenderer(animation: animation, context: holder.context) + self.addSubview(metal) + let layer = Unmanaged.passRetained(metal) + + + var cachedContext:Unmanaged? + if let context = self.context, saveContext { + cachedContext = Unmanaged.passRetained(context) + } else { + cachedContext = nil + } + + self.context = PlayerContext(animation, displayFrame: { frame in + layer.takeUnretainedValue().render(bytes: frame.data, size: frame.size, backingScale: frame.backingScale) + }, release: { + Queue.mainQueue().async { + layer.takeRetainedValue().removeFromSuperview() + _ = cachedContext?.takeRetainedValue() + cachedContext = nil + } + + }, updateState: { [weak self] state in + guard let `self` = self else { + return + } + switch state { + case .playing, .failed, .stoped: + _ = cachedContext?.takeRetainedValue() + cachedContext = nil + default: + break + } + self.stateValue.set(self._currentState.modify { _ in state } ) + }) + } else { + let fallback = LottieFallbackView() + fallback.wantsLayer = true + fallback.frame = CGRect(origin: CGPoint(), size: animation.viewSize) + fallback.layer?.contentsGravity = .resize + self.addSubview(fallback) + let layer = Unmanaged.passRetained(fallback) + + self.context = PlayerContext(animation, displayFrame: { frame in + + let image = generateImagePixel(frame.size, scale: CGFloat(frame.backingScale), pixelGenerator: { (_, pixelData) in + memcpy(pixelData, frame.data, frame.bufferSize) + }) + Queue.mainQueue().async { + layer.takeUnretainedValue().layer?.contents = image + } + }, release: { + Queue.mainQueue().async { + layer.takeRetainedValue().removeFromSuperview() + } + }, updateState: { [weak self] state in + guard let `self` = self else { + return + } + self.stateValue.set(self._currentState.modify { _ in state } ) + }) + } + } + } else { + self.context = nil + self.stateValue.set(self._currentState.modify { _ in .stoped }) + } + } +} + diff --git a/Telegram-Mac/MGalleryExternalVideoItem.swift b/Telegram-Mac/MGalleryExternalVideoItem.swift index 66d77d5356..2cf6089f61 100644 --- a/Telegram-Mac/MGalleryExternalVideoItem.swift +++ b/Telegram-Mac/MGalleryExternalVideoItem.swift @@ -8,28 +8,364 @@ import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit import AVFoundation import AVKit +enum AVPlayerState : Equatable { + case playing(duration: Float64) + case paused(duration: Float64) + case waiting + + @available(OSX 10.12, *) + init(_ player: AVPlayer) { + let duration: Float64 + if let item = player.currentItem { + duration = CMTimeGetSeconds(item.duration) + } else { + duration = 0 + } + switch player.timeControlStatus { + case .playing: + self = .playing(duration: duration) + case .paused: + self = .paused(duration: duration) + case .waitingToPlayAtSpecifiedRate: + self = .waiting + @unknown default: + self = .waiting + } + } +} + +private final class GAVPlayer : AVPlayer { + private var playerStatusContext = 0 + private let _playerState: ValuePromise = ValuePromise(.waiting, ignoreRepeated: true) + var playerState: Signal { + return _playerState.get() |> deliverOnMainQueue + } + + var bufferingValue: ValuePromise = ValuePromise(true, ignoreRepeated: true) + + override func pause() { + super.pause() + } + override init(url: URL) { + super.init(url: url) + } + override init(playerItem item: AVPlayerItem?) { + super.init(playerItem: item) + if #available(OSX 10.12, *) { + addObserver(self, forKeyPath: "timeControlStatus", options: [.new, .initial], context: &playerStatusContext) + } + NotificationCenter.default.addObserver(self, selector: #selector(playerDidEnd(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item) + + item?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: [.new], context: nil) + item?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.new], context: nil) + item?.addObserver(self, forKeyPath: "playbackBufferFull", options: [.new], context: nil) + } + + override init() { + super.init() + } + + @objc private func playerDidEnd(_ notification: Notification) { + seek(to: CMTime(seconds: 0, preferredTimescale: 1000000000)); + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) + { + // Check status + if keyPath == "timeControlStatus" && context == &playerStatusContext && change != nil + { + if #available(OSX 10.12, *) { + _playerState.set(AVPlayerState(self)) + } + } + + if let item = object as? AVPlayerItem { + bufferingValue.set(!item.isPlaybackLikelyToKeepUp) + } + } + + deinit { + if #available(OSX 10.12, *) { + removeObserver(self, forKeyPath: "timeControlStatus") + } + self.currentItem?.removeObserver(self, forKeyPath: "playbackBufferEmpty") + self.currentItem?.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + self.currentItem?.removeObserver(self, forKeyPath: "playbackBufferFull") + + + NotificationCenter.default.removeObserver(self) + } +} -class MGalleryExternalVideoItem: MGalleryVideoItem { +class VideoPlayerView : AVPlayerView { + + override func enterFullScreenMode(_ screen: NSScreen, withOptions options: [NSView.FullScreenModeOptionKey : Any]? = nil) -> Bool { + return super.enterFullScreenMode(screen, withOptions: options) + } + + var isPip: Bool = false + + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateLayout() + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateLayout() + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateLayout() + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + updateLayout() + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + updateLayout() + } + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateLayout() + } + + + func rewindForward(_ seekDuration: Float64 = 15) { + guard let player = player, let duration = player.currentItem?.duration else { return } + let playerCurrentTime = CMTimeGetSeconds(player.currentTime()) + let newTime = min(playerCurrentTime + seekDuration, CMTimeGetSeconds(duration)) + + let time2: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000) + player.seek(to: time2, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) + } + func rewindBack(_ seekDuration: Float64 = 15) { + guard let player = player else { return } + + let playerCurrentTime = CMTimeGetSeconds(player.currentTime()) + var newTime = playerCurrentTime - seekDuration + + if newTime < 0 { + newTime = 0 + } + let time2: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000) + player.seek(to: time2, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) + + } + + private func updateLayout() { + let controls = HackUtils.findElements(byClass: "AVMovableView", in: self)?.first as? NSView + if let controls = controls { + if let pip = controls.subviews.last as? ImageButton { + pip.setFrameOrigin(controls.frame.width - pip.frame.width - 80, controls.frame.height - pip.frame.height - 16) + } + controls._change(opacity: _mouseInside() ? 1 : 0, animated: true) + + } + + } + + override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + updateLayout() + } + + override func layout() { + super.layout() + updateLayout() + } +} + + +class MGalleryExternalVideoItem: MGalleryItem { let content:TelegramMediaWebpageLoadedContent - private let media:TelegramMediaImage - override init(_ account: Account, _ entry:GalleryEntry, _ pagerSize: NSSize) { - let webpage = entry.message!.media[0] as! TelegramMediaWebpage + private let _media:TelegramMediaImage + + var mediaImage: TelegramMediaImage { + return _media + } + + private(set) var startTime: TimeInterval = 0 + private var playAfter:Bool = true + private let _playerItem: Promise = Promise() + + var playerState: Signal { + return _playerItem.get() |> mapToSignal { $0.playerState } + } + override init(_ context: AccountContext, _ entry: GalleryEntry, _ pagerSize: NSSize) { + + + + let webpage = entry.webpage! + + var startTime:TimeInterval = 0 if case let .Loaded(content) = webpage.content { self.content = content - self.media = content.image! + + + _ = ObjcUtils._youtubeVideoId(fromText: content.embedUrl, originalUrl: content.url, startTime: &startTime) + + self._media = content.image! } else { fatalError("content for external video not found") } - super.init(account, entry, pagerSize) + super.init(context, entry, pagerSize) + self.startTime = startTime + + _playerItem.set((path.get() |> distinctUntilChanged |> deliverOnMainQueue) |> map { path -> GAVPlayer in + let url = URL(string: path) ?? URL(fileURLWithPath: path) + return GAVPlayer(url: url) + }) + + disposable.set(combineLatest(_playerItem.get() |> deliverOnMainQueue, view.get() |> distinctUntilChanged |> deliverOnMainQueue |> map { $0 as! AVPlayerView }).start(next: { [weak self] player, view in + if let strongSelf = self { + view.player = player + if strongSelf.playAfter { + strongSelf.playAfter = false + + player.play() + if strongSelf.startTime > 0 { + player.seek(to: CMTimeMake(value: Int64(strongSelf.startTime * 1000.0), timescale: 1000)) + } + } + let controls = HackUtils.findElements(byClass: "AVMovableView", in: view)?.first as? NSView + if let controls = controls, let pip = strongSelf.pipButton { + controls.addSubview(pip) + view.needsLayout = true + } + } + })) + + } + + private var _cachedView: VideoPlayerView? + private var pipButton: ImageButton? + + override func toggleFullScreen() { + if let view = _cachedView { + let controls = HackUtils.findElements(byClass: "AVMovableView", in: view)?.first as? NSView + if let controls = controls { + if let view = controls.subviews.first?.subviews.first?.subviews.first?.subviews.last?.subviews.first?.subviews.last { + if let view = view as? NSButton { + view.performClick(self) + } + } + } + } + } + + override func togglePlayerOrPause() { + if let view = _cachedView, let player = view.player { + switch player.rate { + case 0: + player.play() + default: + player.pause() + } + } + } + + override func rewindBack() { + _cachedView?.rewindBack() + } + override func rewindForward() { + _cachedView?.rewindForward() + } + + override func singleView() -> NSView { + let view: VideoPlayerView + if let _cachedView = _cachedView { + view = _cachedView + } else { + view = VideoPlayerView() + } + view.showsFullScreenToggleButton = true + view.showsFrameSteppingButtons = true + view.controlsStyle = .floating + view.autoresizingMask = [] + view.autoresizesSubviews = false + + let pip:ImageButton = ImageButton() + pip.style = ControlStyle(highlightColor: .grayIcon) + pip.set(image: #imageLiteral(resourceName: "Icon_PIPVideoEnable").precomposed(NSColor.white.withAlphaComponent(0.9)), for: .Normal) + + pip.set(handler: { [weak view, weak self] _ in + if let view = view, let strongSelf = self, let viewer = viewer { + let frame = view.window!.convertToScreen(view.convert(view.bounds, to: nil)) + if !viewer.pager.isFullScreen { + closeGalleryViewer(false) + showLegacyPipVideo(view, viewer: viewer, item: strongSelf, origin: frame.origin, delegate: viewer.delegate, contentInteractions: viewer.contentInteractions, type: viewer.type) + } + } + }, for: .Down) + + _ = pip.sizeToFit() + + pipButton = pip + + + + _cachedView = view + return view + + } + private var isPausedGlobalPlayer: Bool = false + override func appear(for view: NSView?) { + super.appear(for: view) + + pausepip() + + if let pauseMusic = globalAudio?.pause() { + isPausedGlobalPlayer = pauseMusic + } + + if let view = view as? AVPlayerView { + if let player = view.player { + player.play() + playAfter = false + } else { + playAfter = true + } + } else { + playAfter = true + } + } + + override var maxMagnify: CGFloat { + return min(pagerSize.width / sizeValue.width, pagerSize.height / sizeValue.height) + } + + override func disappear(for view: NSView?) { + super.disappear(for: view) + if isPausedGlobalPlayer { + _ = globalAudio?.play() + } + if let view = view as? VideoPlayerView, !view.isPip { + view.player?.pause() + } + playAfter = false + } + + override var status: Signal { + return _playerItem.get() |> mapToSignal { value in + return value.bufferingValue.get() |> map { buffering in + return buffering ? .Fetching(isActive: true, progress: 0.8) : .Local + } + } } override var sizeValue: NSSize { @@ -37,21 +373,31 @@ class MGalleryExternalVideoItem: MGalleryVideoItem { } override func request(immediately: Bool) { + super.request(immediately: immediately) + let webpage = entry.webpage! + - let signal:Signal<(TransformImageArguments) -> DrawingContext?,NoError> = chatMessagePhoto(account: account, photo: media, scale: System.backingScale) + let signal:Signal = chatMessagePhoto(account: context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(webpage), media: _media), scale: System.backingScale) let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: sizeValue, boundingSize: sizeValue, intrinsicInsets: NSEdgeInsets()) - let result = signal |> deliverOn(account.graphicsThreadPool) |> mapToThrottled { transform -> Signal in - return .single(transform(arguments)?.generateImage()) + let result = signal |> deliverOn(graphicsThreadPool) |> mapToThrottled { data -> Signal in + return .single(data.execute(arguments, data.data)?.generateImage()) + } + + switch webpage.content { + case let .Loaded(content): + _ = sharedVideoLoader.fetch(for: content).start() + default: + break } - self.path.set(sharedVideoLoader.status(for: content) |> mapToSignal { (status) -> Signal in + self.path.set(sharedVideoLoader.status(for: content) |> `catch` {_ in return .complete()} |> mapToSignal { (status) -> Signal in if let status = status, case let .loaded(video) = status { return .single(video.stream) } return .complete() } |> deliverOnMainQueue) - self.image.set(result |> deliverOnMainQueue) + self.image.set(result |> map { GPreviewValueClass(.image($0 != nil ? NSImage(cgImage: $0!, size: $0!.backingSize) : nil, nil)) } |> deliverOnMainQueue) fetch() } diff --git a/Telegram-Mac/MGalleryGIFItem.swift b/Telegram-Mac/MGalleryGIFItem.swift index 17d3878c1a..6d9b0bcae3 100644 --- a/Telegram-Mac/MGalleryGIFItem.swift +++ b/Telegram-Mac/MGalleryGIFItem.swift @@ -7,28 +7,50 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit class MGalleryGIFItem: MGalleryItem { - override init(_ account: Account, _ entry: GalleryEntry, _ pagerSize: NSSize) { - super.init(account, entry, pagerSize) + private var mediaPlayer: MediaPlayer! + + override init(_ context: AccountContext, _ entry: GalleryEntry, _ pagerSize: NSSize) { + super.init(context, entry, pagerSize) let view = self.view - let pathSignal = path.get() |> distinctUntilChanged |> deliverOnMainQueue |> mapToSignal { path -> Signal<(String?,GIFPlayerView), Void> in - return view.get() |> distinctUntilChanged |> map { view in - return (path,view as! GIFPlayerView) + + let fileReference = entry.fileReference(media) + + self.mediaPlayer = MediaPlayer(postbox: context.account.postbox, reference: fileReference.resourceReference(media.resource), streamable: media.isStreamable, video: true, preferSoftwareDecoding: false, enableSound: false, fetchAutomatically: false) + mediaPlayer.actionAtEnd = .loop(nil) + + + disposable.set(view.get().start(next: { [weak self] view in + if let view = (view as? MediaPlayerView) { + self?.mediaPlayer.attachPlayerView(view) } - } - disposable.set(pathSignal.start(next: { (path, view) in - view.set(path: path) })) } - private var media:TelegramMediaFile { + override func appear(for view: NSView?) { + super.appear(for: view) + self.mediaPlayer.play() + } + + override func disappear(for view: NSView?) { + super.disappear(for: view) + + self.mediaPlayer.pause() + } + + override var status:Signal { + return chatMessageFileStatus(account: context.account, file: media) + } + + var media:TelegramMediaFile { switch entry { case .message(let entry): if let media = entry.message!.media[0] as? TelegramMediaFile { @@ -41,8 +63,13 @@ class MGalleryGIFItem: MGalleryItem { fatalError("") } } - case .instantMedia(let media): + case .instantMedia(let media, _): return media.media as! TelegramMediaFile + case let .photo(_, _, photo, _, _, _, _): + let video = photo.videoRepresentations.last! + let file = TelegramMediaFile(fileId: photo.imageId, partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: video.resource.size, attributes: [.Video(duration:0, size: PixelDimensions(640, 640), flags: [])]) + + return file default: fatalError() } @@ -50,41 +77,45 @@ class MGalleryGIFItem: MGalleryItem { fatalError("") } - override var maxMagnify:CGFloat { - return 1.0 - } +// override var maxMagnify:CGFloat { +// return 1.0 +// } override func singleView() -> NSView { - let player = GIFPlayerView() - player.followWindow = false + let player = MediaPlayerView(backgroundThread: true) return player } override var sizeValue: NSSize { - if let size = media.dimensions { + if let size = media.dimensions?.size { + + var size = size + + return size.fitted(pagerSize) } return pagerSize } override func request(immediately: Bool) { - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: media.previewRepresentations) + super.request(immediately: immediately) + let size = media.dimensions?.size.fitted(pagerSize) ?? sizeValue - let signal:Signal<(TransformImageArguments) -> DrawingContext?,NoError> = chatMessagePhoto(account: account, photo: image, scale: System.backingScale) - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: sizeValue, boundingSize: sizeValue, intrinsicInsets: NSEdgeInsets()) - let result = signal |> deliverOn(account.graphicsThreadPool) |> mapToThrottled { transform -> Signal in - return .single(transform(arguments)?.generateImage()) + let signal:Signal = chatMessageVideo(postbox: context.account.postbox, fileReference: entry.fileReference(media), scale: System.backingScale) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + let result = signal |> deliverOn(graphicsThreadPool) |> mapToThrottled { generator -> Signal in + return .single(generator.execute(arguments, generator.data)?.generateImage()) } - path.set(account.postbox.mediaBox.resourceData(media.resource) |> mapToSignal { (resource) -> Signal in + path.set(context.account.postbox.mediaBox.resourceData(media.resource) |> mapToSignal { (resource) -> Signal in if resource.complete { return .single(link(path:resource.path, ext:kMediaGifExt)!) } return .never() }) - self.image.set(result |> deliverOnMainQueue) + self.image.set(result |> map { GPreviewValueClass(.image($0 != nil ? NSImage(cgImage: $0!, size: $0!.backingSize) : nil, nil)) } |> deliverOnMainQueue) fetch() } @@ -96,7 +127,7 @@ class MGalleryGIFItem: MGalleryItem { } override func fetch() -> Void { - fetching.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + fetching.set(chatMessageFileInteractiveFetched(account: context.account, fileReference: entry.fileReference(media)).start()) } diff --git a/Telegram-Mac/MGalleryItem.swift b/Telegram-Mac/MGalleryItem.swift index b86d519ce5..aa9258b724 100644 --- a/Telegram-Mac/MGalleryItem.swift +++ b/Telegram-Mac/MGalleryItem.swift @@ -7,35 +7,257 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit import TGUIKit +final class GPreviewValueClass { + let value: GPreviewValue + init(_ value: GPreviewValue) { + self.value = value + } +} + -enum GalleryEntry : Identifiable { +enum GPreviewValue { + case image(NSImage?, ImageOrientation?) + case view(NSView?) + + var hasValue: Bool { + switch self { + case let .image(img, _): + return img != nil + case let .view(view): + return view != nil + } + } + var size: NSSize? { + switch self { + case let .image(img, _): + return img?.size + case let .view(view): + return view?.frame.size + } + } + + var rotation: ImageOrientation? { + switch self { + case let .image(_, rotation): + return rotation + case .view: + return nil + } + } + + var image: NSImage? { + switch self { + case let .image(img, _): + return img + case .view: + return nil + } + } + +} + +func <(lhs: GalleryEntry, rhs: GalleryEntry) -> Bool { + switch lhs { + case .message(let lhsEntry): + if case let .message(rhsEntry) = rhs { + return lhsEntry < rhsEntry + } else { + return false + } + case let .secureIdDocument(_, lhsIndex): + if case let .secureIdDocument(_, rhsIndex) = rhs { + return lhsIndex < rhsIndex + } else { + return false + } + case let .photo(lhsIndex, _, _, _, _, _, _): + if case let .photo(rhsIndex, _, _, _, _, _, _) = rhs { + return lhsIndex < rhsIndex + } else { + return false + } + case let .instantMedia(lhsMedia, _): + if case let .instantMedia(rhsMedia, _) = rhs { + return lhsMedia.index < rhsMedia.index + } else { + return false + } + } +} + +func ==(lhs: GalleryEntry, rhs: GalleryEntry) -> Bool { + switch lhs { + case .message(let lhsEntry): + if case let .message(rhsEntry) = rhs { + return lhsEntry.stableId == rhsEntry.stableId + } else { + return false + } + case let .secureIdDocument(lhsEntry, lhsIndex): + if case let .secureIdDocument(rhsEntry, rhsIndex) = rhs { + return lhsEntry.document.isEqual(to: rhsEntry.document) && lhsIndex == rhsIndex + } else { + return false + } + case let .photo(lhsIndex, lhsStableId, lhsPhoto, lhsReference, lhsPeer, _, lhsDate): + if case let .photo(rhsIndex, rhsStableId, rhsPhoto, rhsReference, rhsPeer, _, rhsDate) = rhs { + return lhsIndex == rhsIndex && lhsStableId == rhsStableId && lhsPhoto.isEqual(to: rhsPhoto) && lhsReference == rhsReference && lhsPeer.isEqual(rhsPeer) && lhsDate == rhsDate + } else { + return false + } + case let .instantMedia(lhsMedia, _): + if case let .instantMedia(rhsMedia, _) = rhs { + return lhsMedia == rhsMedia + } else { + return false + } + } +} +enum GalleryEntry : Comparable, Identifiable { case message(ChatHistoryEntry) - case photo(index:Int, stableId:AnyHashable, photo:TelegramMediaImage, reference: TelegramMediaRemoteImageReference) - case instantMedia(InstantPageMedia) + case photo(index:Int, stableId:AnyHashable, photo:TelegramMediaImage, reference: TelegramMediaImageReference?, peer: Peer, message: Message?, date: TimeInterval) + case instantMedia(InstantPageMedia, Message?) + case secureIdDocument(SecureIdDocumentValue, Int) var stableId: AnyHashable { switch self { case let .message(entry): return entry.stableId - case let .photo(_, stableId, _, _): + case let .photo(_, stableId, _, _, _, _, _): return stableId - case let .instantMedia(media): + case let .instantMedia(media, _): return media.index + case let .secureIdDocument(document, _): + return document.stableId } } + var canShare: Bool { + return message != nil && !message!.isScheduledMessage && !message!.containsSecretMedia + } + + var interfaceState:(PeerId, TimeInterval)? { + switch self { + case let .message(entry): + if let peerId = entry.message!.effectiveAuthor?.id { + return (peerId, TimeInterval(entry.message!.timestamp)) + } + case let .instantMedia(_, message): + if let message = message, let peerId = message.effectiveAuthor?.id { + return (peerId, TimeInterval(message.timestamp)) + } + case let .photo(_, _, _, _, peer, _, date): + return (peer.id, date) + default: + return nil + } + return nil + } + + var file:TelegramMediaFile? { + switch self { + case .message(let entry): + if let media = entry.message!.media[0] as? TelegramMediaFile { + return media + } else if let media = entry.message!.media[0] as? TelegramMediaWebpage { + switch media.content { + case let .Loaded(content): + return content.file + default: + return nil + } + } + case .instantMedia(let media, _): + return media.media as? TelegramMediaFile + default: + return nil + } + + return nil + } + + var webpage: TelegramMediaWebpage? { + switch self { + case let .message(entry): + return entry.message!.media[0] as? TelegramMediaWebpage + case let .instantMedia(media, _): + return media.media as? TelegramMediaWebpage + default: + return nil + } + } + + func imageReference( _ image: TelegramMediaImage) -> ImageMediaReference { + switch self { + case let .message(entry): + return ImageMediaReference.message(message: MessageReference(entry.message!), media: image) + case let .instantMedia(media, _): + return ImageMediaReference.webPage(webPage: WebpageReference(media.webpage), media: image) + case .secureIdDocument: + return ImageMediaReference.standalone(media: image) + case .photo: + return ImageMediaReference.standalone(media: image) + } + } + + var peer: Peer? { + switch self { + case let .photo(_, _, _, _, peer, _, _): + return peer + default: + return nil + } + } + + func peerPhotoResource() -> MediaResourceReference { + switch self { + case let .photo(_, _, image, _, peer, message, _): + if let representation = image.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + if let message = message { + return .media(media: .message(message: MessageReference(message), media: image), resource: representation.resource) + } + if let peerReference = PeerReference(peer) { + return .avatar(peer: peerReference, resource: representation.resource) + } else { + return .standalone(resource: representation.resource) + } + } else { + preconditionFailure() + } + default: + preconditionFailure() + } + } + + func fileReference( _ file: TelegramMediaFile) -> FileMediaReference { + switch self { + case let .message(entry): + return FileMediaReference.message(message: MessageReference(entry.message!), media: file) + case let .instantMedia(media, _): + return FileMediaReference.webPage(webPage: WebpageReference(media.webpage), media: file) + case .secureIdDocument: + return FileMediaReference.standalone(media: file) + case .photo: + return FileMediaReference.standalone(media: file) + } + } + + var identifier: String { switch self { case let .message(entry): return "\(entry.message?.stableId ?? 0)" - case .photo(_, let stableId, _, _): + case .photo(_, let stableId, _, _, _, _, _): return "\(stableId)" case .instantMedia: return "\(stableId)" + case let .secureIdDocument(document, _): + return "secureId: \(document.document.id.hashValue)" } } @@ -52,6 +274,8 @@ enum GalleryEntry : Identifiable { switch self { case let .message(entry): return entry.message + case let .instantMedia(_, message): + return message default: return nil } @@ -60,18 +284,18 @@ enum GalleryEntry : Identifiable { switch self { case .message: return nil - case let .photo(_, _, photo, _): + case let .photo(_, _, photo, _, _, _, _): return photo default: return nil } } - var photoReference:TelegramMediaRemoteImageReference? { + var photoReference:TelegramMediaImageReference? { switch self { case .message: return nil - case let .photo(_, _, _, reference): + case let .photo(_, _, _, reference, _, _, _): return reference default: return nil @@ -79,12 +303,39 @@ enum GalleryEntry : Identifiable { } } +func ==(lhs: MGalleryItem, rhs: MGalleryItem) -> Bool { + return lhs.entry == rhs.entry +} +func <(lhs: MGalleryItem, rhs: MGalleryItem) -> Bool { + return lhs.entry < rhs.entry +} + +private final class MGalleryItemView : NSView { + init() { + super.init(frame: NSZeroRect) + self.wantsLayer = true + self.layerContentsRedrawPolicy = .never + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override var isOpaque: Bool { + return true + } + + deinit { + var bp:Int = 0 + bp += 1 + } +} -class MGalleryItem: NSObject { - let image:Promise = Promise() +class MGalleryItem: NSObject, Comparable, Identifiable { + let image:Promise = Promise() let view:Promise = Promise() let size:Promise = Promise() - let magnify:Promise = Promise() + let magnify:Promise = Promise(1) + let rotate: ValuePromise = ValuePromise(nil, ignoreRepeated: true) let disposable:MetaDisposable = MetaDisposable() let fetching:MetaDisposable = MetaDisposable() @@ -92,11 +343,25 @@ class MGalleryItem: NSObject { private let viewDisposable:MetaDisposable = MetaDisposable() let path:Promise = Promise() let entry:GalleryEntry - let account:Account - let pagerSize:NSSize + let context: AccountContext + private var _pagerSize: NSSize + private var captionSeized: Bool = false + var pagerSize:NSSize { + var pagerSize = _pagerSize + if let caption = caption, !captionSeized { + caption.measure(width: pagerSize.width - 300) + captionSeized = true + } + if let caption = caption { + pagerSize.height -= min(140, caption.layoutSize.height + 60) + } + return pagerSize + } let caption: TextViewLayout? - private(set) var modifiedSize: NSSize? = nil + var disableAnimations: Bool = false + + var modifiedSize: NSSize? = nil private(set) var magnifyValue:CGFloat = 1.0 var stableId: AnyHashable { @@ -104,7 +369,7 @@ class MGalleryItem: NSObject { } var identifier:NSPageController.ObjectIdentifier { - return NSPageController.ObjectIdentifier(rawValue: entry.identifier) + return entry.identifier + self.className } var sizeValue:NSSize { @@ -119,17 +384,67 @@ class MGalleryItem: NSObject { return 8.0 } - func smallestValue(for size: NSSize) -> Signal { - return .single(pagerSize) + func smallestValue(for size: NSSize) -> NSSize { + return pagerSize + } + + var status:Signal { + return .single(.Local) + } + + var realStatus:Signal { + return self.status + } + + func toggleFullScreen() { + + } + func togglePlayerOrPause() { + + } + func rewindBack() { + + } + func rewindForward() { + } - init(_ account:Account, _ entry:GalleryEntry, _ pagerSize:NSSize) { + init(_ context: AccountContext, _ entry:GalleryEntry, _ pagerSize:NSSize) { self.entry = entry - self.account = account - self.pagerSize = pagerSize - if let caption = entry.message?.text { - self.caption = TextViewLayout(.initialize(string: caption, color: .white, font: .normal(.text)), alignment: .center) - self.caption?.measure(width: .greatestFiniteMagnitude) + self.context = context + self._pagerSize = pagerSize + if let caption = entry.message?.text, !caption.isEmpty, !(entry.message?.media.first is TelegramMediaWebpage) { + let attr = NSMutableAttributedString() + _ = attr.append(string: caption.trimmed.fullTrimmed, color: .white, font: .normal(.text)) + + attr.detectLinks(type: [.Links, .Mentions], context: context, color: .linkColor, openInfo: { peerId, toChat, postId, action in + let navigation = context.sharedContext.bindings.rootNavigation() + let controller = navigation.controller + if toChat { + if peerId == (controller as? ChatController)?.chatInteraction.peerId { + if let postId = postId { + (controller as? ChatController)?.chatInteraction.focusMessageId(nil, postId, TableScrollState.center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + } + } else { + navigation.push(ChatAdditionController(context: context, chatLocation: .peer(peerId), messageId: postId, initialAction: action)) + } + } else { + navigation.push(PeerInfoController(context: context, peerId: peerId)) + } + viewer?.close() + }, hashtag: { _ in }, command: {_ in }, applyProxy: { _ in }) + + self.caption = TextViewLayout(attr, alignment: .left) + self.caption?.interactions = TextViewInteractions(processURL: { link in + if let link = link as? inAppLink { + execute(inapp: link, afterComplete: { value in + if value { + viewer?.close() + } + }) + + } + }) } else { self.caption = nil } @@ -138,20 +453,27 @@ class MGalleryItem: NSObject { var first:Bool = true - let image = combineLatest(self.image.get(), view.get()) |> map { [weak self] image, view in - view.layer?.contents = image - view.layer?.backgroundColor = theme.colors.background.cgColor - - if first, let slf = self, let magnify = view.superview?.superview as? MagnifyView { - self?.modifiedSize = image?.size - if magnify.contentSize != slf.sizeValue { - magnify.contentSize = slf.sizeValue - } - } + let image = combineLatest(self.image.get() |> map { $0.value }, view.get()) |> map { [weak self] value, view in + guard let `self` = self else {return} + view.layer?.contents = value.image - if !first { + if !first && !self.disableAnimations { view.layer?.animateContents() } + self.disableAnimations = false + view.layer?.backgroundColor = self.backgroundColor.cgColor + + if let magnify = view.superview?.superview as? MagnifyView { + var size = magnify.contentSize + if self is MGalleryPhotoItem || self is MGalleryPeerPhotoItem { + if value.rotation == nil { + size = value.size?.aspectFitted(size) ?? size + } else { + size = value.size ?? size + } + } + magnify.contentSize = size + } first = false } viewDisposable.set(image.start()) @@ -161,18 +483,34 @@ class MGalleryItem: NSObject { })) } + var backgroundColor: NSColor { + return .black + } + + var isGraphicFile: Bool { + if self.entry.message?.media.first is TelegramMediaFile { + return true + } else { + return false + } + } + func singleView() -> NSView { - return NSView() + return MGalleryItemView() } func request(immediately:Bool = true) { - + // self.caption?.measure(width: sizeValue.width) } func fetch() { } + var notFittedSize: NSSize { + return sizeValue + } + func cancel() { fetching.set(nil) } @@ -194,7 +532,5 @@ class MGalleryItem: NSObject { } -func ==(lhs:MGalleryItem, rhs:MGalleryItem) -> Bool { - return lhs.stableId == rhs.stableId -} + diff --git a/Telegram-Mac/MGalleryPeerPhotoItem.swift b/Telegram-Mac/MGalleryPeerPhotoItem.swift index 9274cee528..671094e076 100644 --- a/Telegram-Mac/MGalleryPeerPhotoItem.swift +++ b/Telegram-Mac/MGalleryPeerPhotoItem.swift @@ -7,76 +7,113 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit class MGalleryPeerPhotoItem: MGalleryItem { - private let media:TelegramMediaImage - override init(_ account: Account, _ entry: GalleryEntry, _ pagerSize: NSSize) { + let media:TelegramMediaImage + override init(_ context: AccountContext, _ entry: GalleryEntry, _ pagerSize: NSSize) { self.media = entry.photo! - super.init(account, entry, pagerSize) + super.init(context, entry, pagerSize) } override var sizeValue: NSSize { - if let largest = media.representations.last { - return largest.dimensions.fitted(pagerSize) + if let largest = media.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + return largest.dimensions.size.fitted(pagerSize) } return NSZeroSize } - override func smallestValue(for size: NSSize) -> Signal { - if let largest = media.representations.last { - return .single(largest.dimensions.fitted(size)) + override func smallestValue(for size: NSSize) -> NSSize { + if let largest = media.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + return largest.dimensions.size.fitted(size) + } + return pagerSize + } + + override var status:Signal { + if let largestRepresentation = media.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + return context.account.postbox.mediaBox.resourceStatus(largestRepresentation.resource) + } else { + return .never() } - return .single(pagerSize) } override func request(immediately: Bool) { + super.request(immediately: immediately) - - let account = self.account + let context = self.context let media = self.media + let entry = self.entry + + let magnify = self.magnify.get() + + let sizeValue: Signal = size.get() |> mapToSignal { size in + return magnify |> take(1) |> map { magnify in + return NSMakeSize(floorToScreenPixels(System.backingScale, size.width / magnify), floorToScreenPixels(System.backingScale, size.height / magnify)) + } + } |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs == rhs + }) + - let result = size.get() |> mapToSignal { [weak self] size -> Signal in - if let strongSelf = self { - return strongSelf.smallestValue(for: size) + let result = combineLatest(sizeValue, rotate.get()) |> mapToSignal { [weak self] size, orientation -> Signal<(NSSize, ImageOrientation?), NoError> in + guard let `self` = self else {return .complete()} + var newSize = self.smallestValue(for: size) + if let orientation = orientation { + if orientation == .right || orientation == .left { + newSize = NSMakeSize(newSize.height, newSize.width) + } } - return .complete() - } |> distinctUntilChanged |> mapToSignal { size -> Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), Void> in - return chatMessagePhoto(account: account, photo: media, scale: System.backingScale) |> deliverOn(account.graphicsThreadPool) |> map { transform in - return (transform, TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) + return .single((newSize, orientation)) + } |> mapToSignal { size, orientation -> Signal<(NSImage?, ImageOrientation?), NoError> in + return chatGalleryPhoto(account: context.account, imageReference: entry.imageReference(media), toRepresentationSize: NSMakeSize(1280, 1280), peer: entry.peer, scale: System.backingScale, synchronousLoad: true) + |> map { transform in + let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) + if let orientation = orientation { + let transformed = image?.createMatchingBackingDataWithImage(orienation: orientation) + if let transformed = transformed { + return (NSImage(cgImage: transformed, size: transformed.size), orientation) + } + } + if let image = image { + return (NSImage(cgImage: image, size: image.size), orientation) + } else { + return (nil, nil) + } } - } |> mapToThrottled { (transform, arguments) -> Signal in - return .single(transform(arguments)?.generateImage()) } - - if let representation = media.representations.last { - path.set(account.postbox.mediaBox.resourceData(representation.resource) |> mapToSignal { (resource) -> Signal in + if let representation = media.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + path.set(context.account.postbox.mediaBox.resourceData(representation.resource) |> mapToSignal { (resource) -> Signal in if resource.complete { return .single(link(path:resource.path, ext:kMediaImageExt)!) } return .never() }) - } + } - self.image.set(result |> deliverOnMainQueue) + self.image.set(result |> map { GPreviewValueClass(.image($0.0, $0.1)) } |> deliverOnMainQueue) fetch() } override func fetch() -> Void { - fetching.set(chatMessagePhotoInteractiveFetched(account: account, photo: media).start()) + fetching.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: self.entry.peerPhotoResource()).start()) } override func cancel() -> Void { super.cancel() - chatMessagePhotoCancelInteractiveFetch(account: account, photo: media) + if let representation = media.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + cancelFreeMediaFileInteractiveFetch(context: context, resource: representation.resource) + } + fetching.set(nil) } } diff --git a/Telegram-Mac/MGalleryPhotoItem.swift b/Telegram-Mac/MGalleryPhotoItem.swift index b88b73ac4e..3ca2429ee6 100644 --- a/Telegram-Mac/MGalleryPhotoItem.swift +++ b/Telegram-Mac/MGalleryPhotoItem.swift @@ -7,113 +7,203 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit class MGalleryPhotoItem: MGalleryItem { - private let media:TelegramMediaImage + let media:TelegramMediaImage + let secureIdAccessContext: SecureIdAccessContext? private let representation:TelegramMediaImageRepresentation - override init(_ account: Account, _ entry: GalleryEntry, _ pagerSize: NSSize) { + override init(_ context: AccountContext, _ entry: GalleryEntry, _ pagerSize: NSSize) { switch entry { case .message(let entry): if let webpage = entry.message!.media[0] as? TelegramMediaWebpage { if case let .Loaded(content) = webpage.content, let image = content.image { self.media = image + } else if case let .Loaded(content) = webpage.content, let media = content.file { + let represenatation = TelegramMediaImageRepresentation(dimensions: media.dimensions ?? PixelDimensions(0, 0), resource: media.resource) + var representations = media.previewRepresentations + representations.append(represenatation) + self.media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } else { fatalError("image for webpage not found") } } else { if let media = entry.message!.media[0] as? TelegramMediaFile { - let represenatation = TelegramMediaImageRepresentation(dimensions: media.dimensions ?? NSZeroSize, resource: media.resource) + let represenatation = TelegramMediaImageRepresentation(dimensions: media.dimensions ?? PixelDimensions(0, 0), resource: media.resource) var representations = media.previewRepresentations representations.append(represenatation) - self.media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations) + self.media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) } else { self.media = entry.message!.media[0] as! TelegramMediaImage } } - case .instantMedia(let media): + secureIdAccessContext = nil + case .instantMedia(let media, _): self.media = media.media as! TelegramMediaImage + secureIdAccessContext = nil + case let .secureIdDocument(document, _): + self.media = document.image + self.secureIdAccessContext = document.context default: fatalError("photo item not supported entry type") } self.representation = media.representations.last! - super.init(account, entry, pagerSize) + super.init(context, entry, pagerSize) } override var sizeValue: NSSize { if let largest = media.representations.last { - if let modifiedSize = modifiedSize, largest.dimensions.width == 0 { - let lhsProportion = modifiedSize.width/modifiedSize.height - let rhsProportion = largest.dimensions.width/largest.dimensions.height - - if lhsProportion != rhsProportion { - return modifiedSize.fitted(pagerSize) - } - + if let modifiedSize = modifiedSize { + return modifiedSize.fitted(pagerSize) } - return largest.dimensions.fitted(pagerSize) + return largest.dimensions.size.fitted(pagerSize) } return NSZeroSize } - override func smallestValue(for size: NSSize) -> Signal { + override func smallestValue(for size: NSSize) -> NSSize { if let largest = media.representations.last { if let modifiedSize = modifiedSize { let lhsProportion = modifiedSize.width/modifiedSize.height - let rhsProportion = largest.dimensions.width/largest.dimensions.height + let rhsProportion = largest.dimensions.size.width/largest.dimensions.size.height if lhsProportion != rhsProportion { - return .single(modifiedSize.fitted(size)) + return modifiedSize.fitted(size) } } - return .single(largest.dimensions.fitted(size)) + return largest.dimensions.size.fitted(size) } - return .single(pagerSize) + return pagerSize } + override var status:Signal { + return chatMessagePhotoStatus(account: context.account, photo: media) + } + + private var hasRequested: Bool = false + override func request(immediately: Bool) { + super.request(immediately: immediately) - let account = self.account - let media = self.media - - let result = size.get() |> mapToSignal { [weak self] size -> Signal in - if let strongSelf = self { - return strongSelf.smallestValue(for: size) + if !hasRequested { + let context = self.context + let entry = self.entry + let media = self.media + let secureIdAccessContext = self.secureIdAccessContext + + let sizeValue: Signal = size.get() |> map { size in + return NSMakeSize(floorToScreenPixels(System.backingScale, size.width), floorToScreenPixels(System.backingScale, size.height)) + } |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs == rhs + }) + + let rotateValue = rotate.get() |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs == rhs + }) + + + let signal = combineLatest(sizeValue, rotateValue) |> mapToSignal { [weak self] size, orientation -> Signal<(NSSize, ImageOrientation?), NoError> in + guard let `self` = self else {return .complete()} + + var size = size + if self.sizeValue.width > self.sizeValue.height && size.width < size.height + || self.sizeValue.width < self.sizeValue.height && size.width > size.height { + size = NSMakeSize(size.height, size.width) + } + + var newSize = self.smallestValue(for: size) + if let orientation = orientation { + if orientation == .right || orientation == .left { + newSize = NSMakeSize(newSize.height, newSize.width) + } + } + return .single((newSize, orientation)) + } - return .complete() - } |> distinctUntilChanged |> mapToSignal { size -> Signal in - return chatGalleryPhoto(account: account, photo: media, scale: System.backingScale) |> deliverOn(account.graphicsThreadPool) |> map { transform in - return transform(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) + + class Data { + let image: NSImage? + let orientation: ImageOrientation? + init(_ image: NSImage?, _ orientation: ImageOrientation?) { + self.image = image + self.orientation = orientation + } } - } - - path.set(account.postbox.mediaBox.resourceData(representation.resource) |> mapToSignal { resource -> Signal in - if resource.complete { - return .single(link(path:resource.path, ext:kMediaImageExt)!) + + let result = combineLatest(signal, self.magnify.get() |> distinctUntilChanged) |> mapToSignal { [weak self] data, magnify -> Signal in + + let (size, orientation) = data + return chatGalleryPhoto(account: context.account, imageReference: entry.imageReference(media), scale: System.backingScale, secureIdAccessContext: secureIdAccessContext, synchronousLoad: true) + |> map { [weak self] transform in + + var size = NSMakeSize(ceil(size.width * magnify), ceil(size.height * magnify)) + let image = transform(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) + + if let image = image, orientation == nil { + let newSize = image.size.aspectFitted(size) + if newSize != size { + size = newSize + self?.modifiedSize = image.size + } + } + + if let orientation = orientation { + let transformed = image?.createMatchingBackingDataWithImage(orienation: orientation) + if let transformed = transformed { + return Data(NSImage(cgImage: transformed, size: size), orientation) + } + } + if let image = image { + return Data(NSImage(cgImage: image, size: size), orientation) + } else { + return Data(nil, orientation) + } + } + } - return .never() - }) - - self.image.set(result |> deliverOnMainQueue) - + + path.set(context.account.postbox.mediaBox.resourceData(representation.resource) |> mapToSignal { resource -> Signal in + if resource.complete { + return .single(link(path:resource.path, ext:kMediaImageExt)!) + } + return .never() + }) + + self.image.set(result |> map { GPreviewValueClass(.image($0.image, $0.orientation)) } |> deliverOnMainQueue) + + + fetch() + + hasRequested = true + } - fetch() + } + + override var backgroundColor: NSColor { + return theme.colors.transparentBackground } override func fetch() -> Void { - fetching.set(chatMessagePhotoInteractiveFetched(account: account, photo: media).start()) + fetching.set(chatMessagePhotoInteractiveFetched(account: context.account, imageReference: entry.imageReference(media)).start()) } override func cancel() -> Void { super.cancel() - chatMessagePhotoCancelInteractiveFetch(account: account, photo: media) + chatMessagePhotoCancelInteractiveFetch(account: context.account, photo: media) } + deinit { + var bp:Int = 0 + bp += 1 + } + } diff --git a/Telegram-Mac/MGalleryVideoItem.swift b/Telegram-Mac/MGalleryVideoItem.swift index ebcf51f382..e931d5facd 100644 --- a/Telegram-Mac/MGalleryVideoItem.swift +++ b/Telegram-Mac/MGalleryVideoItem.swift @@ -8,102 +8,59 @@ import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit import AVFoundation import AVKit -private class VideoPlayerView : AVPlayerView { - - deinit { - var bp:Int = 0 - bp += 1 - } - - override func layout() { - super.layout() - let controls = subviews.last?.subviews.last - if let controls = controls, let pip = controls.subviews.last as? ImageButton { - pip.setFrameOrigin(controls.frame.width - pip.frame.width - 80, 34) - controls.centerX(y: 50) - } - } -} - class MGalleryVideoItem: MGalleryItem { - + var startTime: TimeInterval = 0 private var playAfter:Bool = false - override init(_ account: Account, _ entry: GalleryEntry, _ pagerSize: NSSize) { - super.init(account, entry, pagerSize) - - let pathSignal = combineLatest(path.get() |> distinctUntilChanged |> deliverOnMainQueue, view.get() |> distinctUntilChanged) |> map { path, view -> (AVPlayer?,AVPlayerView) in - let url = URL(string: path) ?? URL(fileURLWithPath: path) - let player = AVPlayer(url: url) - player.seek(to: CMTime()) - return (player, view as! AVPlayerView) - } - disposable.set(pathSignal.start(next: { [weak self] player, view in - if let strongSelf = self { - view.player = player - if strongSelf.playAfter { - strongSelf.playAfter = false - player?.play() - - let controls = view.subviews.last?.subviews.last - if let controls = controls, let pip = strongSelf.pipButton { - controls.addSubview(pip) - view.needsLayout = true - } - + private let controller: SVideoController + var playerState: Signal { + return controller.status |> map { value in + switch value.status { + case .playing: + return .playing(duration: value.duration) + case .paused: + return .paused(duration: value.duration) + case let .buffering(initial, whilePlaying): + if whilePlaying { + return .playing(duration: value.duration) + } else if !whilePlaying && !initial { + return .paused(duration: value.duration) + } else { + return .waiting } } - })) - + } |> deliverOnMainQueue + } + override init(_ context: AccountContext, _ entry: GalleryEntry, _ pagerSize: NSSize) { + controller = SVideoController(postbox: context.account.postbox, reference: entry.fileReference(entry.file!)) + super.init(context, entry, pagerSize) + + controller.togglePictureInPictureImpl = { [weak self] enter, control in + guard let `self` = self else {return} + let frame = control.view.window!.convertToScreen(control.view.convert(control.view.bounds, to: nil)) + if enter, let viewer = viewer { + closeGalleryViewer(false) + showPipVideo(control: control, viewer: viewer, item: self, origin: frame.origin, delegate: viewer.delegate, contentInteractions: viewer.contentInteractions, type: viewer.type) + } else if !enter { + exitPictureInPicture() + } + } } deinit { var bp:Int = 0 bp += 1 } - - private var _cachedView: VideoPlayerView? - private var pipButton: ImageButton? - - override func singleView() -> NSView { - let view: VideoPlayerView - if let _cachedView = _cachedView { - view = _cachedView - } else { - view = VideoPlayerView() - } - view.showsFullScreenToggleButton = true - view.showsFrameSteppingButtons = true - view.controlsStyle = .floating - view.autoresizingMask = [] - view.autoresizesSubviews = false - - let pip:ImageButton = ImageButton() - pip.style = ControlStyle(highlightColor: .grayIcon) - pip.set(image: #imageLiteral(resourceName: "Icon_PIPVideoEnable").precomposed(NSColor.white.withAlphaComponent(0.9)), for: .Normal) - - pip.set(handler: { [weak view, weak self] _ in - if let view = view, let strongSelf = self, let viewer = viewer { - let frame = view.window!.convertToScreen(view.convert(view.bounds, to: nil)) - closeGalleryViewer(false) - showPipVideo(view, item: strongSelf, origin: frame.origin, delegate: viewer.delegate, contentInteractions: viewer.contentInteractions, type: viewer.type) - } - }, for: .Down) - - pip.sizeToFit() - - pipButton = pip - - - _cachedView = view - return view + override func singleView() -> NSView { + return controller.genericView } private var isPausedGlobalPlayer: Bool = false @@ -117,15 +74,9 @@ class MGalleryVideoItem: MGalleryItem { isPausedGlobalPlayer = pauseMusic } - if let view = view as? AVPlayerView { - if let player = view.player { - player.play() - } else { - playAfter = true - } - } else { - playAfter = true - } + controller.play(startTime) + controller.viewDidAppear(false) + self.startTime = 0 } override var maxMagnify: CGFloat { @@ -137,73 +88,139 @@ class MGalleryVideoItem: MGalleryItem { if isPausedGlobalPlayer { _ = globalAudio?.play() } - if let view = view as? AVPlayerView { - view.player?.pause() + if controller.style != .pictureInPicture { + controller.pause() } + controller.viewDidDisappear(false) playAfter = false } - private var media:TelegramMediaFile { - switch entry { - case .message(let entry): - if let media = entry.message!.media[0] as? TelegramMediaFile { - return media - } else if let media = entry.message!.media[0] as? TelegramMediaWebpage { - switch media.content { - case let .Loaded(content): - return content.file! - default: - fatalError("") - } - } - case .instantMedia(let media): - return media.media as! TelegramMediaFile - default: - fatalError() + override var status:Signal { + if media.isStreamable { + return .single(.Local) + } else { + return chatMessageFileStatus(account: context.account, file: media) + } + } + + override var realStatus:Signal { + return chatMessageFileStatus(account: context.account, file: media) + } + + var media:TelegramMediaFile { + return entry.file! + } + private var examinatedSize: CGSize? + var dimensions: CGSize? { + + if let examinatedSize = examinatedSize { + return examinatedSize + } + if let dimensions = media.dimensions { + return dimensions.size + } + let linked = link(path: context.account.postbox.mediaBox.resourcePath(media.resource), ext: "mp4") + guard let path = linked else { + return media.dimensions?.size } - fatalError("") + let url = URL(fileURLWithPath: path) + guard let track = AVURLAsset(url: url).tracks(withMediaType: .video).first else { + return media.dimensions?.size + } + try? FileManager.default.removeItem(at: url) + self.examinatedSize = track.naturalSize.applying(track.preferredTransform) + return examinatedSize + + } + + override var notFittedSize: NSSize { + if let size = dimensions { + return size.fitted(pagerSize) + } + return pagerSize } override var sizeValue: NSSize { - if let size = media.dimensions { - let size = size.fitted(pagerSize) - return NSMakeSize(max(size.width, 320), max(size.height, 240)) + if let size = dimensions { + + var pagerSize = self.pagerSize + + + var size = size + + +// let addition = max(400 - size.width, 400 - size.height) +// if addition > 0 { +// size.width += addition +// size.height += addition +// } + + size = size.fitted(pagerSize) + + return size } return pagerSize } + func hideControls() -> Bool { + return controller.hideControlsIfNeeded() + } + + override func toggleFullScreen() { + controller.toggleFullScreen() + } + + override func togglePlayerOrPause() { + controller.togglePlayerOrPause() + } + + override func rewindBack() { + controller.rewindBackward() + } + override func rewindForward() { + controller.rewindForward() + } + + var isFullscreen: Bool { + return controller.isFullscreen + } + + override func request(immediately: Bool) { - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: media.previewRepresentations) + super.request(immediately: immediately) - let signal:Signal<(TransformImageArguments) -> DrawingContext?,NoError> = chatMessagePhoto(account: account, photo: image, scale: System.backingScale) + let signal:Signal = chatMessageVideo(postbox: context.account.postbox, fileReference: entry.fileReference(media), scale: System.backingScale, synchronousLoad: true) + let size = sizeValue - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: sizeValue, boundingSize: sizeValue, intrinsicInsets: NSEdgeInsets()) - let result = signal |> deliverOn(account.graphicsThreadPool) |> mapToThrottled { transform -> Signal in - return .single(transform(arguments)?.generateImage()) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets(), resizeMode: .none) + let result = signal |> mapToThrottled { data -> Signal in + return .single(data.execute(arguments, data.data)?.generateImage()) } - - path.set(account.postbox.mediaBox.resourceData(media.resource) |> mapToSignal { (resource) -> Signal in + path.set(context.account.postbox.mediaBox.resourceData(media.resource) |> mapToSignal { (resource) -> Signal in if resource.complete { return .single(link(path:resource.path, ext:kMediaVideoExt)!) } return .never() }) - self.image.set(media.previewRepresentations.isEmpty ? .single(nil) |> deliverOnMainQueue : result |> deliverOnMainQueue) + self.image.set(media.previewRepresentations.isEmpty ? .single(GPreviewValueClass(.image(nil, nil))) |> deliverOnMainQueue : result |> map { GPreviewValueClass(.image($0 != nil ? NSImage(cgImage: $0!, size: $0!.backingSize) : nil, nil)) } |> deliverOnMainQueue) fetch() } - - override func fetch() -> Void { - - fetching.set(chatMessageFileInteractiveFetched(account: account, file: media).start()) + if !media.isStreamable { + if let parent = entry.message { + _ = messageMediaFileInteractiveFetched(context: context, messageId: parent.id, fileReference: FileMediaReference.message(message: MessageReference(parent), media: media)).start() + } else { + _ = freeMediaFileInteractiveFetched(context: context, fileReference: FileMediaReference.standalone(media: media)).start() + } + } } } diff --git a/Telegram-Mac/MainViewController.swift b/Telegram-Mac/MainViewController.swift index 3bc8f888e7..42056f5f14 100644 --- a/Telegram-Mac/MainViewController.swift +++ b/Telegram-Mac/MainViewController.swift @@ -8,20 +8,253 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +#if !APP_STORE +import Sparkle +enum UpdateButtonState { + case common + case important + case critical +} + +final class UpdateTabView : Control { + let textView: TextView = TextView() + let imageView: ImageView = ImageView() + let progressView: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 24, 24)) + + var isInstalling: Bool = false { + didSet { + textView.isHidden = isInstalling || layoutState == .minimisize + progressView.isHidden = !isInstalling + imageView.isHidden = isInstalling || layoutState != .minimisize + + if layoutState != .minimisize, isInstalling, let superview = self.superview { + change(size: NSMakeSize(60, frame.height), animated: true, timingFunction: .spring) + change(pos: NSMakePoint(superview.bounds.focus(self.frame.size).minX, self.frame.minY), animated: true, timingFunction: .spring) + progressView.change(pos: self.bounds.focus(progressView.frame.size).origin, animated: true, timingFunction: .spring) + } + + } + } + + var layoutState: SplitViewState = .dual { + didSet { + let installing = self.isInstalling + self.isInstalling = installing + } + } + + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + textView.userInteractionEnabled = false + textView.isSelectable = false + addSubview(textView) + addSubview(progressView) + addSubview(imageView) + progressView.progressColor = .white + isInstalling = false + + let layout = TextViewLayout(.initialize(string: L10n.updateUpdateTelegram, color: theme.colors.underSelectedColor, font: .medium(.title))) + layout.measure(width: max(280, frame.width)) + textView.update(layout) + + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.3) + shadow.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow + + } + + override func cursorUpdate(with event: NSEvent) { + NSCursor.pointingHand.set() + } + + override var backgroundColor: NSColor { + didSet { + textView.backgroundColor = backgroundColor + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + imageView.image = (theme as! TelegramPresentationTheme).icons.appUpdate + imageView.sizeToFit() + needsLayout = true + } + + + + override func layout() { + super.layout() + + + + + textView.center() + progressView.center() + imageView.center() + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +final class UpdateTabController: GenericViewController { + private let disposable = MetaDisposable() + private let shakeDisposable = MetaDisposable() + private let context: SharedAccountContext + private var state: UpdateButtonState = .common { + didSet { + switch state { + case .common: + genericView.backgroundColor = theme.colors.accent + case .important: + genericView.backgroundColor = theme.colors.greenUI + case .critical: + genericView.backgroundColor = theme.colors.redUI + } + } + } + private let stateDisposable = MetaDisposable() + private var appcastItem: SUAppcastItem? { + didSet { + + genericView.isHidden = appcastItem == nil + + + var state = self.state + + if appcastItem != oldValue { + if let appcastItem = appcastItem { + state = appcastItem.isCritical ? .critical : .common + + if state != .critical { + + let importantDelay: Double = 60 * 60 * 24 + let criticalDelay: Double = 60 * 60 * 24 + let updateSignal = Signal.single(.important) |> delay(importantDelay, queue: .mainQueue()) |> then(.single(.critical) |> delay(criticalDelay, queue: .mainQueue())) + + stateDisposable.set(updateSignal.start(next: { [weak self] newState in + self?.state = newState + })) + + } + + } else { + stateDisposable.set(nil) + } + } + self.state = state + } + } + + init(_ context: SharedAccountContext) { + self.context = context + super.init() + self.bar = NavigationBarStyle(height: 0) + } + + override func viewDidLoad() { + super.viewDidLoad() + let context = self.context + + + genericView.set(background: theme.colors.grayForeground, for: .Normal) + genericView.isHidden = true + genericView.hideAnimated = true + + disposable.set((appUpdateStateSignal |> deliverOnMainQueue).start(next: { [weak self] state in + switch state.loadingState { + case let .readyToInstall(item): + self?.appcastItem = item + self?.genericView.isInstalling = false + case .installing: + self?.genericView.isInstalling = true + default: + self?.appcastItem = nil + self?.genericView.isInstalling = false + } + })) + + genericView.set(handler: { _ in + let authrorized = (NSApp.delegate as? AppDelegate)?.hasAuthorized ?? false + if authrorized, let controller = context.bindings.rootNavigation().controller as? ChatController { + controller.chatInteraction.saveState(true) + } + updateApplication(sharedContext: context) + }, for: .Click) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let item = self.appcastItem + self.appcastItem = item + } + + func updateLayout(_ layout: SplitViewState, parentSize: NSSize, isChatList: Bool) { + genericView.layoutState = layout + + let bottom = parentSize.height - genericView.frame.height + + if isChatList && layout != .minimisize { + genericView.setFrameSize(NSMakeSize(genericView.textView.frame.width + 40, 40)) + genericView.layer?.cornerRadius = genericView.frame.height / 2 + genericView.centerX(y: layout == .minimisize ? bottom - 10 : bottom - 60) + + var shakeDelay: Double = 60 * 60 + + + let signal = Signal.single(Void()) |> delay(shakeDelay, queue: .mainQueue()) |> then(.single(Void()) |> delay(shakeDelay, queue: .mainQueue()) |> restart) + self.shakeDisposable.set(signal.start(next: { [weak self] in + self?.genericView.shake(beep: false) + })) + } else { + genericView.setFrameSize(NSMakeSize(parentSize.width, 50)) + genericView.setFrameOrigin(NSMakePoint(0, layout == .minimisize ? bottom : bottom - 50)) + genericView.layer?.cornerRadius = 0 + shakeDisposable.set(nil) + } + } + + deinit { + disposable.dispose() + stateDisposable.dispose() + shakeDisposable.dispose() + } +} + +#endif class MainViewController: TelegramViewController { - private var tabController:TabBarController = TabBarController() - private let accountManager:AccountManager - private var contacts:ContactsController - private var chatList:ChatListController - private var settings:AccountViewController + let tabController:TabBarController = TabBarController() + let contacts:ContactsController + let chatListNavigation:NavigationViewController + let settings:AccountViewController private let phoneCalls:RecentCallsViewController private let layoutDisposable:MetaDisposable = MetaDisposable() + private let badgeCountDisposable: MetaDisposable = MetaDisposable() + private let tooltipDisposable = MetaDisposable() + #if !APP_STORE + private let updateController: UpdateTabController + #endif + var isUpChatList: Bool = false { + didSet { + if isUpChatList != oldValue { + tabController.replace(tab: tabController.tab(at: chatIndex).withUpdatedImages(theme.icons.tab_chats, isUpChatList ? theme.icons.tab_chats_active_filters : theme.icons.tab_chats_active), at: chatIndex) + } + } + } + override var navigationController: NavigationViewController? { didSet { @@ -32,33 +265,66 @@ class MainViewController: TelegramViewController { override func viewDidResized(_ size: NSSize) { super.viewDidResized(size) tabController.view.frame = bounds + #if !APP_STORE + updateController.updateLayout(context.sharedContext.layout, parentSize: size, isChatList: true) + #endif } override func loadView() { super.loadView() + + let context = self.context + tabController._frameRect = self._frameRect self.bar = NavigationBarStyle(height: 0) backgroundColor = theme.colors.background addSubview(tabController.view) + #if !APP_STORE + addSubview(updateController.view) + #endif + - tabController.add(tab: TabItem(image: theme.tabBar.icon(key: 0, image: #imageLiteral(resourceName: "Icon_TabContacts"), selected: false), selectedImage: theme.tabBar.icon(key: 0, image: #imageLiteral(resourceName: "Icon_TabContacts_Highlighted"), selected: true), controller: contacts)) + tabController.add(tab: TabItem(image: theme.icons.tab_contacts, selectedImage: theme.icons.tab_contacts_active, controller: contacts)) - tabController.add(tab: TabItem(image: theme.tabBar.icon(key: 1, image: #imageLiteral(resourceName: "Icon_TabRecentCalls"), selected: false), selectedImage: theme.tabBar.icon(key: 1, image: #imageLiteral(resourceName: "Icon_TabRecentCallsHighlighted"), selected: true), controller: phoneCalls)) + tabController.add(tab: TabItem(image: theme.icons.tab_calls, selectedImage: theme.icons.tab_calls_active, controller: phoneCalls)) - tabController.add(tab: TabBadgeItem(account, controller: chatList, image: theme.tabBar.icon(key: 2, image: #imageLiteral(resourceName: "Icon_TabChatList"), selected: false), selectedImage: theme.tabBar.icon(key: 2, image: #imageLiteral(resourceName: "Icon_TabChatList_Highlighted"), selected: true))) + tabController.add(tab: TabBadgeItem(context, controller: chatListNavigation, image: theme.icons.tab_chats, selectedImage: isUpChatList ? theme.icons.tab_chats_active_filters : theme.icons.tab_chats_active, longHoverHandler: { [weak self] control in + self?.showFastChatSettings(control) + })) - tabController.add(tab: TabItem(image: theme.tabBar.icon(key: 3, image: #imageLiteral(resourceName: "Icon_TabSettings"), selected: false), selectedImage: theme.tabBar.icon(key: 3, image: #imageLiteral(resourceName: "Icon_TabSettings_Highlighted"), selected: true), controller: settings, longHoverHandler: { [weak self] control in + tabController.add(tab: TabAllBadgeItem(context, image: theme.icons.tab_settings, selectedImage: theme.icons.tab_settings_active, controller: settings, longHoverHandler: { [weak self] control in self?.showFastSettings(control) - })) - tabController.updateLocalizationAndTheme() + tabController.updateLocalizationAndTheme(theme: theme) +// let s:Signal = Signal.single(arc4random() % 2 == 5) |> then(deferred { +// return Signal.single(arc4random() % 2 == 5) +// } |> delay(10 * 10, queue: .mainQueue()) |> restart) +// |> filter { $0 } +// |> deliverOnMainQueue +// +// tooltipDisposable.set(s.start(next: { [weak self] show in +// +// self?.showFilterTooltip() +// +// })) + +// account.postbox.transaction ({ transaction -> Void in +// +// +// }).start() - self.ready.set(.single(true)) + self.ready.set(combineLatest(queue: prepareQueue, self.chatList.ready.get(), self.settings.ready.get()) |> map { $0 && $1 }) - layoutDisposable.set(account.context.layoutHandler.get().start(next: { [weak self] state in - self?.tabController.hideTabView(state == .minimisize) + layoutDisposable.set(context.sharedContext.layoutHandler.get().start(next: { [weak self] state in + guard let `self` = self else { + return + } + self.tabController.hideTabView(state == .minimisize) + #if !APP_STORE + self.updateController.updateLayout(state, parentSize: self.frame.size, isChatList: true) + #endif })) tabController.didChangedIndex = { [weak self] index in @@ -66,109 +332,285 @@ class MainViewController: TelegramViewController { } } - private let settingsDisposable = MetaDisposable() + func prepareControllers() { + chatList.loadViewIfNeeded(bounds) + settings.loadViewIfNeeded(bounds) + } - private func showFastSettings(_ control:Control) { + private func showCallsTab() { + tabController.insert(tab: TabItem(image: theme.icons.tab_calls, selectedImage: theme.icons.tab_calls_active, controller: phoneCalls), at: 1) + } + private func hideCallsTab() { + tabController.remove(at: 1) + } + + private func showFilterTooltip() { + tabController.showTooltip(text: L10n.chatListFilterTooltip, for: showCallTabs ? 2 : 1) + } + + private var showCallTabs: Bool = true + + override func viewDidLoad() { + super.viewDidLoad() - let passcodeData = account.postbox.modify { modifier -> PostboxAccessChallengeData in - return modifier.getAccessChallengeData() - } |> deliverOnMainQueue - let applicationSettings = appNotificationSettings(postbox: account.postbox) |> take(1) |> deliverOnMainQueue + chatListNavigation.hasBarRightBorder = true - - settingsDisposable.set(combineLatest(passcodeData, applicationSettings).start(next: { [weak self] passcode, notifications in - self?._showFast(control: control, passcodeData: passcode, notifications: notifications) + prefDisposable.set((baseAppSettings(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] settings in + guard let `self` = self else {return} + if settings.showCallsTab != self.showCallTabs { + self.showCallTabs = settings.showCallsTab + if self.showCallTabs { + self.showCallsTab() + } else { + self.hideCallsTab() + } + } + })) + } + + private func _showFastChatSettings(_ control: Control, unreadCount: Int32) { + var items: [SPopoverItem] = [] + let context = self.context + + if unreadCount > 0 { + items.append(SPopoverItem(L10n.chatListPopoverReadAll, { + confirm(for: context.window, information: L10n.chatListPopoverConfirm, successHandler: { _ in + _ = context.account.postbox.transaction ({ transaction -> Void in + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: .root, filterPredicate: nil) + markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: Namespaces.PeerGroup.archive, filterPredicate: nil) + }).start() + }) + })) + } + + if self.tabController.current == chatListNavigation, !items.isEmpty { + showPopover(for: control, with: SPopoverViewController(items: items), edge: .maxX, inset: NSMakePoint(control.frame.width + 12, 0)) + } + } + + func showFastChatSettings() { + self.showFastChatSettings(tabController.control(for: self.chatIndex)) + } + + private func showFastChatSettings(_ control: Control) { + + let context = self.context + let unreadCountsKey = PostboxViewKey.unreadCounts(items: [.total(nil)]) + + _ = (context.account.postbox.combinedView(keys: [unreadCountsKey]) |> take(1) |> deliverOnMainQueue).start(next: { [weak self, weak control] view in + let totalUnreadState: ChatListTotalUnreadState + if let value = view.views[unreadCountsKey] as? UnreadMessageCountsView, let (_, total) = value.total() { + totalUnreadState = total + } else { + totalUnreadState = ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]) + } + let total = totalUnreadState.absoluteCounters.reduce(0, { current, value in + return current + value.value.messageCount + }) + if let control = control { + self?._showFastChatSettings(control, unreadCount: total) + } + }) + } + private let filterMenuDisposable = MetaDisposable() + private let settingsDisposable = MetaDisposable() + private let prefDisposable = MetaDisposable() + private weak var quickController: ViewController? + private func showFastSettings(_ control:Control) { + + let passcodeData = context.sharedContext.accountManager.transaction { transaction -> PostboxAccessChallengeData in + return transaction.getAccessChallengeData() + } |> deliverOnMainQueue + + let applicationSettings = appNotificationSettings(accountManager: context.sharedContext.accountManager) |> take(1) |> deliverOnMainQueue + + + settingsDisposable.set(combineLatest(passcodeData, applicationSettings, context.sharedContext.activeAccountsWithInfo |> take(1) |> map {$0.accounts} |> deliverOnMainQueue).start(next: { [weak self] passcode, notifications, accounts in + self?._showFast(control: control, accounts: accounts, passcodeData: passcode, notifications: notifications) })) } - private func _showFast( control: Control, passcodeData: PostboxAccessChallengeData, notifications: InAppNotificationSettings) { + private func _showFast( control: Control, accounts: [AccountWithInfo], passcodeData: PostboxAccessChallengeData, notifications: InAppNotificationSettings) { + + if let popover = control.popover { + popover.hide() + return + } + var items:[SPopoverItem] = [] - + let context = self.context + var headerItems: [TableRowItem] = [] + for account in accounts { + if account.account.id != context.account.id { + + let item = ShortPeerRowItem(NSZeroSize, peer: account.peer, account: account.account, height: 40, photoSize: NSMakeSize(25, 25), titleStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.text, highlightColor: .white), drawCustomSeparator: false, inset: NSEdgeInsets(left: 10), action: { + context.sharedContext.switchToAccount(id: account.account.id, action: nil) + }, highlightOnHover: true, badgeNode: GlobalBadgeNode(account.account, sharedContext: context.sharedContext, getColor: { selected in + if selected { + return theme.colors.underSelectedColor + } else { + return theme.colors.accent + } + }), compactText: true) + + headerItems.append(item) +// items.append(SPopoverItem(account.peer.displayTitle, { +// context.sharedContext.switchToAccount(id: account.account.id) +// })) + } + + } + switch passcodeData { case .none: - items.append(SPopoverItem(tr(.fastSettingsSetPasscode), { [weak self] in - if let account = self?.account { - self?.tabController.select(index: 3) - account.context.mainNavigation?.push(PasscodeSettingsViewController(account)) - } + items.append(SPopoverItem(tr(L10n.fastSettingsSetPasscode), { [weak self] in + guard let `self` = self else {return} + self.tabController.select(index: self.tabController.count - 1) + self.context.sharedContext.bindings.rootNavigation().push(PasscodeSettingsViewController(self.context)) }, theme.icons.fastSettingsLock)) default: - items.append(SPopoverItem(tr(.fastSettingsLockTelegram), { - if let event = NSEvent.keyEvent(with: .keyDown, location: NSZeroPoint, modifierFlags: [.command], timestamp: Date().timeIntervalSince1970, windowNumber: mainWindow.windowNumber, context: nil, characters: "", charactersIgnoringModifiers: "", isARepeat: false, keyCode: KeyboardKey.L.rawValue) { - mainWindow.sendEvent(event) - } + items.append(SPopoverItem(tr(L10n.fastSettingsLockTelegram), { + context.window.sendKeyEvent(KeyboardKey.L, modifierFlags: [.command]) }, theme.icons.fastSettingsLock)) } - - items.append(SPopoverItem(theme.dark ? tr(.fastSettingsDisableDarkMode) : tr(.fastSettingsEnableDarkMode), { [weak self] in - if let strongSelf = self { - _ = updateThemeSettings(postbox: strongSelf.account.postbox, pallete: !theme.dark ? darkPallete : whitePallete, dark: !theme.dark).start() - } - }, theme.dark ? theme.icons.fastSettingsSunny : theme.icons.fastSettingsDark)) + items.append(SPopoverItem(theme.colors.isDark ? L10n.fastSettingsDisableDarkMode : L10n.fastSettingsEnableDarkMode, { + let nightSettings = autoNightSettings(accountManager: context.sharedContext.accountManager) |> take(1) |> deliverOnMainQueue + + _ = nightSettings.start(next: { settings in + if settings.systemBased || settings.schedule != nil { + confirm(for: context.window, header: L10n.darkModeConfirmNightModeHeader, information: L10n.darkModeConfirmNightModeText, okTitle: L10n.darkModeConfirmNightModeOK, successHandler: { _ in + + _ = context.sharedContext.accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.autoNight, { entry in + let settings: AutoNightThemePreferences = entry as? AutoNightThemePreferences ?? AutoNightThemePreferences.defaultSettings + return settings.withUpdatedSystemBased(false).withUpdatedSchedule(nil) + }) + transaction.updateSharedData(ApplicationSharedPreferencesKeys.themeSettings, { entry in + let settings = entry as? ThemePaletteSettings ?? ThemePaletteSettings.defaultTheme + return settings.withUpdatedToDefault(dark: !theme.colors.isDark).withUpdatedDefaultIsDark(!theme.colors.isDark) + }) + }.start() + }) + } else { + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings -> ThemePaletteSettings in + return settings.withUpdatedToDefault(dark: !theme.colors.isDark).withUpdatedDefaultIsDark(!theme.colors.isDark) + }).start() + } + }) + }, theme.colors.isDark ? theme.icons.fastSettingsSunny : theme.icons.fastSettingsDark)) + let time = Int32(Date().timeIntervalSince1970) let unmuted = notifications.muteUntil < time - items.append(SPopoverItem(unmuted ? tr(.fastSettingsMute2Hours) : tr(.fastSettingsUnmute), { [weak self] in - if let account = self?.account { + items.append(SPopoverItem(unmuted ? tr(L10n.fastSettingsMute2Hours) : tr(L10n.fastSettingsUnmute), { [weak self] in + if let context = self?.context { let time = Int32(Date().timeIntervalSince1970 + 2 * 60 * 60) - _ = updateInAppNotificationSettingsInteractively(postbox: account.postbox, {$0.withUpdatedMuteUntil(unmuted ? time : 0)}).start() + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedMuteUntil(unmuted ? time : 0)}).start() } }, notifications.muteUntil < time ? theme.icons.fastSettingsMute : theme.icons.fastSettingsUnmute)) - let controller = SPopoverViewController(items: items) - if self.tabController.current != settings { - showPopover(for: control, with: controller, edge: .maxX, inset: NSMakePoint(control.frame.width - 12, 0)) - } + let controller = SPopoverViewController(items: items, visibility: 10, headerItems: headerItems) + showPopover(for: control, with: controller, edge: .maxX, inset: NSMakePoint(control.frame.width - 12, 0)) + self.quickController = controller } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - tabController.updateLocalizationAndTheme() - + private var previousTheme:TelegramPresentationTheme? + private var previousIconColor:NSColor? + private var previousIsUpChatList: Bool? + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + tabController.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + #if !APP_STORE + updateController.updateLocalizationAndTheme(theme: theme) + #endif - if !tabController.isEmpty { - tabController.replace(tab: tabController.tab(at: 0).withUpdatedImages(theme.tabBar.icon(key: 0, image: #imageLiteral(resourceName: "Icon_TabContacts"), selected: false), theme.tabBar.icon(key: 0, image: #imageLiteral(resourceName: "Icon_TabContacts_Highlighted"), selected: true)), at: 0) - - tabController.replace(tab: tabController.tab(at: 1).withUpdatedImages(theme.tabBar.icon(key: 1, image: #imageLiteral(resourceName: "Icon_TabRecentCalls"), selected: false), theme.tabBar.icon(key: 1, image: #imageLiteral(resourceName: "Icon_TabRecentCallsHighlighted"), selected: true)), at: 1) - - tabController.replace(tab: tabController.tab(at: 2).withUpdatedImages(theme.tabBar.icon(key: 2, image: #imageLiteral(resourceName: "Icon_TabChatList"), selected: false), theme.tabBar.icon(key: 2, image: #imageLiteral(resourceName: "Icon_TabChatList_Highlighted"), selected: true)), at: 2) + updateTabsIfNeeded() + self.tabController.view.needsLayout = true + } + + private func updateTabsIfNeeded() { + if !tabController.isEmpty && (previousTheme?.colors != theme.colors || previousIconColor != theme.colors.accentIcon || self.isUpChatList != self.previousIsUpChatList) { + var index: Int = 0 + tabController.replace(tab: tabController.tab(at: index).withUpdatedImages(theme.icons.tab_contacts, theme.icons.tab_contacts_active), at: index) + index += 1 + if showCallTabs { + tabController.replace(tab: tabController.tab(at: index).withUpdatedImages(theme.icons.tab_calls, theme.icons.tab_calls_active), at: index) + index += 1 + } - tabController.replace(tab: tabController.tab(at: 3).withUpdatedImages(theme.tabBar.icon(key: 3, image: #imageLiteral(resourceName: "Icon_TabSettings"), selected: false), theme.tabBar.icon(key: 3, image: #imageLiteral(resourceName: "Icon_TabSettings_Highlighted"), selected: true)), at: 3) + tabController.replace(tab: tabController.tab(at: index).withUpdatedImages(theme.icons.tab_chats, isUpChatList ? theme.icons.tab_chats_active_filters : theme.icons.tab_chats_active), at: index) + index += 1 + tabController.replace(tab: tabController.tab(at: index).withUpdatedImages(theme.icons.tab_settings, theme.icons.tab_settings_active), at: index) } + self.previousTheme = theme + self.previousIconColor = theme.colors.accentIcon + self.previousIsUpChatList = self.isUpChatList } + private var previousIndex: Int? = nil + func checkSettings(_ index:Int) { + let isSettings = tabController.tab(at: index).controller is AccountViewController - if index == 3 && account.context.layout != .single { - account.context.mainNavigation?.push(GeneralSettingsViewController(account), false) + let navigation = context.sharedContext.bindings.rootNavigation() + + if let controller = navigation.controller as? InputDataController, controller.identifier == "wallet-create" { + self.previousIndex = index + quickController?.popover?.hide() } else { - - account.context.mainNavigation?.enumerateControllers( { controller, index in - if (controller is ChatController) || (controller is PeerInfoController) || (controller is GroupAdminsController) || (controller is GroupAdminsController) || (controller is ChannelAdminsViewController) || (controller is ChannelAdminsViewController) || (controller is EmptyChatViewController) { - self.backFromSettings(index) - return true + if previousIndex == tabController.count - 1 || isSettings { + if isSettings && context.sharedContext.layout != .single { + navigation.push(GeneralSettingsViewController(context), false) + } else { + navigation.enumerateControllers( { controller, index in + if (controller is ChatController) || (controller is PeerInfoController) || (controller is ChannelAdminsViewController) || (controller is ChannelAdminsViewController) || (controller is EmptyChatViewController) { + self.backFromSettings(index) + return true + } + return false + }) } - return false - }) + } + self.previousIndex = index + quickController?.popover?.hide() } } private func backFromSettings(_ index:Int) { - account.context.mainNavigation?.to(index: index) + context.sharedContext.bindings.rootNavigation().to(index: index) + } + + override func focusSearch(animated: Bool) { + if context.sharedContext.layout == .minimisize { + return + } + let animated = animated && (context.sharedContext.layout != .single || context.sharedContext.bindings.rootNavigation().stackCount == 1) + if context.sharedContext.layout == .single { + context.sharedContext.bindings.rootNavigation().close() + } + if let current = tabController.current { + if current is AccountViewController { + tabController.select(index: chatIndex) + } + tabController.current?.focusSearch(animated: animated) + } } override func getCenterBarViewOnce() -> TitledBarView { return TitledBarView(controller: self) } + private var firstTime: Bool = true override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if !animated { - self.tabController.select(index:2) + if firstTime { + // self.tabController.select(index: chatIndex) + firstTime = false } + self.tabController.current?.viewDidAppear(animated) } override func viewWillAppear(_ animated: Bool) { @@ -182,28 +624,81 @@ class MainViewController: TelegramViewController { self.tabController.current?.viewWillDisappear(animated) } + var chatIndex: Int { + if showCallTabs { + return 2 + } else { + return 1 + } + } + + var settingsIndex: Int { + if showCallTabs { + return 3 + } else { + return 2 + } + } + + override func navigationUndoHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + if let controller = self.tabController.current { + return controller.navigationUndoHeaderDidNoticeAnimation(current, previous, animated) + } + return {} + } + + func openChat(_ index: Int, force: Bool = false) { + if self.tabController.current == chatListNavigation { + chatList.openChat(index, force: force) + } + } + + var chatList: ChatListController { + return chatListNavigation.controller as! ChatListController + } + func showPreferences() { - if self.account.context.layout != .minimisize { - self.tabController.select(index:3) + context.sharedContext.bindings.switchSplitLayout(.dual) + if self.context.sharedContext.layout != .minimisize { + self.tabController.select(index:settingsIndex) } } + func showChatList() { + self.tabController.select(index: self.chatIndex) + } + + override var responderPriority: HandlerPriority { + return context.sharedContext.layout == .single ? .medium : .low + } + func isCanMinimisize() -> Bool{ - return self.tabController.current == chatList + return self.tabController.current == chatListNavigation || self.tabController.current == contacts || self.tabController.current == self.phoneCalls + } + + override func updateFrame(_ frame: NSRect, animated: Bool) { + super.updateFrame(frame, animated: animated) + self.tabController.updateFrame(frame, animated: animated) } - init(_ account:Account, accountManager:AccountManager) { + override init(_ context: AccountContext) { - self.accountManager = accountManager - chatList = ChatListController(account) - contacts = ContactsController(account) - settings = AccountViewController(account, accountManager: accountManager) - phoneCalls = RecentCallsViewController(account) - super.init(account) + chatListNavigation = NavigationViewController(ChatListController(context), context.window) + contacts = ContactsController(context) + settings = AccountViewController(context) + phoneCalls = RecentCallsViewController(context) + #if !APP_STORE + updateController = UpdateTabController(context.sharedContext) + #endif + super.init(context) bar = NavigationBarStyle(height: 0) + // chatListNavigation.alwaysAnimate = true } deinit { layoutDisposable.dispose() + prefDisposable.dispose() + settingsDisposable.dispose() + filterMenuDisposable.dispose() } } diff --git a/Telegram-Mac/MajorBackNavigationBar.swift b/Telegram-Mac/MajorBackNavigationBar.swift index 33044e41a0..b50a6f9ce9 100644 --- a/Telegram-Mac/MajorBackNavigationBar.swift +++ b/Telegram-Mac/MajorBackNavigationBar.swift @@ -8,33 +8,42 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox class MajorBackNavigationBar: BackNavigationBar { private let disposable:MetaDisposable = MetaDisposable() - private let account:Account + private let context:AccountContext private let peerId:PeerId private let badgeNode:GlobalBadgeNode - init(_ controller: ViewController, account:Account, excludePeerId:PeerId) { - self.account = account + init(_ controller: ViewController, context: AccountContext, excludePeerId:PeerId) { + self.context = context self.peerId = excludePeerId - badgeNode = GlobalBadgeNode(account, excludePeerId: excludePeerId) - badgeNode.xInset = -22 + + var layoutChanged:(()->Void)? = nil + badgeNode = GlobalBadgeNode(context.account, sharedContext: context.sharedContext, excludeGroupId: Namespaces.PeerGroup.archive, view: View(), layoutChanged: { + layoutChanged?() + }) + badgeNode.xInset = 0 + + super.init(controller) - disposable.set((account.applicationContext as? TelegramApplicationContext)?.layoutHandler.get().start(next: { [weak self] state in - if let strongSelf = self { - switch state { - case .single: - strongSelf.badgeNode.view?.isHidden = false - default: - strongSelf.badgeNode.view?.isHidden = true - } - } - })) addSubview(badgeNode.view!) + + layoutChanged = { [weak self] in + self?.needsLayout = true + } + + } + + override func layout() { + super.layout() + + self.badgeNode.view!.setFrameOrigin(NSMakePoint(min(frame.width == minWidth ? 30 : 22, frame.width - self.badgeNode.view!.frame.width - 4), 4)) + } deinit { diff --git a/Telegram-Mac/ManageSharedAccountInfo.swift b/Telegram-Mac/ManageSharedAccountInfo.swift new file mode 100644 index 0000000000..dfeabacd69 --- /dev/null +++ b/Telegram-Mac/ManageSharedAccountInfo.swift @@ -0,0 +1,86 @@ +// +// ManageSharedAccountInfo.swift +// Telegram +// +// Created by Mikhail Filimonov on 28/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Foundation +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox +import SyncCore +private func accountInfo(account: Account) -> Signal { + let peerName = account.postbox.transaction { transaction -> String in + guard let peer = transaction.getPeer(account.peerId) else { + return "" + } + if let addressName = peer.addressName { + return "\(addressName)" + } + return peer.displayTitle + } + + let primaryDatacenterId = Int32(account.network.datacenterId) + let context = account.network.context + + var datacenters: [Int32: AccountDatacenterInfo] = [:] + for nId in context.knownDatacenterIds() { + if let id = nId as? Int { + if let authInfo = context.authInfoForDatacenter(withId: id), let authKey = authInfo.authKey { + let transportScheme = context.chooseTransportSchemeForConnection(toDatacenterId: id, schemes: context.transportSchemesForDatacenter(withId: id, media: true, enforceMedia: false, isProxy: false)) + var addressList: [AccountDatacenterAddress] = [] + if let transportScheme = transportScheme, let address = transportScheme.address, let host = address.host { + let secret: Data? = address.secret + addressList.append(AccountDatacenterAddress(host: host, port: Int32(address.port), isMedia: address.preferForMedia, secret: secret)) + } + datacenters[Int32(id)] = AccountDatacenterInfo(masterKey: AccountDatacenterKey(id: authInfo.authKeyId, data: authKey), addressList: addressList) + } + } + } + + let notificationKey = masterNotificationsKey(account: account, ignoreDisabled: false) + + return combineLatest(peerName, notificationKey) + |> map { peerName, notificationKey -> StoredAccountInfo in + return StoredAccountInfo(id: account.id.int64, primaryId: primaryDatacenterId, isTestingEnvironment: account.testingEnvironment, peerName: peerName, datacenters: datacenters, notificationKey: AccountNotificationKey(id: notificationKey.id, data: notificationKey.data)) + } +} + +func sharedAccountInfos(accountManager: AccountManager, accounts: Signal<[Account], NoError>) -> Signal { + return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.proxySettings]), accounts) + |> mapToSignal { sharedData, accounts -> Signal in + let proxySettings = sharedData.entries[SharedDataKeys.proxySettings] as? ProxySettings + let proxy = proxySettings?.effectiveActiveServer.flatMap { proxyServer -> AccountProxyConnection? in + var username: String? + var password: String? + var secret: Data? + switch proxyServer.connection { + case let .socks5(usernameValue, passwordValue): + username = usernameValue + password = passwordValue + case let .mtp(secretValue): + secret = secretValue + } + return AccountProxyConnection(host: proxyServer.host, port: proxyServer.port, username: username, password: password, secret: secret) + } + + return combineLatest(accounts.map(accountInfo)) + |> map { infos -> StoredAccountInfos in + return StoredAccountInfos(proxy: proxy, accounts: infos) + } + } +} + +func storeAccountsData(rootPath: String, accounts: StoredAccountInfos) { + guard let data = try? JSONEncoder().encode(accounts) else { + Logger.shared.log("storeAccountsData", "Error encoding data") + return + } + guard let _ = try? data.write(to: URL(fileURLWithPath: rootPath + "/accounts-shared-data")) else { + Logger.shared.log("storeAccountsData", "Error saving data") + return + } +} diff --git a/Telegram-Mac/ManagedAudioSession.swift b/Telegram-Mac/ManagedAudioSession.swift new file mode 100755 index 0000000000..07792f52a3 --- /dev/null +++ b/Telegram-Mac/ManagedAudioSession.swift @@ -0,0 +1,487 @@ +import Foundation +import SwiftSignalKit +import AVFoundation + +enum ManagedAudioSessionType { + case play + case playAndRecord + case voiceCall +} + +private func nativeCategoryForType(_ type: ManagedAudioSessionType) -> String { + switch type { + case .play: + return "AVAudioSessionCategoryPlayback" + case .playAndRecord, .voiceCall: + return "AVAudioSessionCategoryPlayAndRecord" + } +} + +private func allowBluetoothForType(_ type: ManagedAudioSessionType) -> Bool { + switch type { + case .play: + return false + case .playAndRecord, .voiceCall: + return true + } +} + +public enum AudioSessionOutput { + case speaker +} + +public enum AudioSessionOutputMode: Equatable { + case system + case speakerIfNoHeadphones + case custom(AudioSessionOutput) + + public static func ==(lhs: AudioSessionOutputMode, rhs: AudioSessionOutputMode) -> Bool { + switch lhs { + case .system: + if case .system = rhs { + return true + } else { + return false + } + case .speakerIfNoHeadphones: + if case .speakerIfNoHeadphones = rhs { + return true + } else { + return false + } + case let .custom(output): + if case .custom(output) = rhs { + return true + } else { + return false + } + } + } +} + +private final class HolderRecord { + let id: Int32 + let audioSessionType: ManagedAudioSessionType + let control: ManagedAudioSessionControl + let activate: (ManagedAudioSessionControl) -> Void + let deactivate: () -> Signal + let headsetConnectionStatusChanged: (Bool) -> Void + let once: Bool + var outputMode: AudioSessionOutputMode + var active: Bool = false + var deactivatingDisposable: Disposable? = nil + + init(id: Int32, audioSessionType: ManagedAudioSessionType, control: ManagedAudioSessionControl, activate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void, once: Bool, outputMode: AudioSessionOutputMode) { + self.id = id + self.audioSessionType = audioSessionType + self.control = control + self.activate = activate + self.deactivate = deactivate + self.headsetConnectionStatusChanged = headsetConnectionStatusChanged + self.once = once + self.outputMode = outputMode + } +} + +private final class ManagedAudioSessionControlActivate { + let f: (AudioSessionActivationState) -> Void + + init(_ f: @escaping (AudioSessionActivationState) -> Void) { + self.f = f + } +} + +public struct AudioSessionActivationState { + public let isHeadsetConnected: Bool +} + +public class ManagedAudioSessionControl { + private let setupImpl: (Bool) -> Void + private let activateImpl: (ManagedAudioSessionControlActivate) -> Void + private let setOutputModeImpl: (AudioSessionOutputMode) -> Void + + fileprivate init(setupImpl: @escaping (Bool) -> Void, activateImpl: @escaping (ManagedAudioSessionControlActivate) -> Void, setOutputModeImpl: @escaping (AudioSessionOutputMode) -> Void) { + self.setupImpl = setupImpl + self.activateImpl = activateImpl + self.setOutputModeImpl = setOutputModeImpl + } + + public func setup(synchronous: Bool = false) { + self.setupImpl(synchronous) + } + + public func activate(_ completion: @escaping (AudioSessionActivationState) -> Void) { + self.activateImpl(ManagedAudioSessionControlActivate(completion)) + } + + public func setOutputMode(_ mode: AudioSessionOutputMode) { + self.setOutputModeImpl(mode) + } +} + +public final class ManagedAudioSession { + private var nextId: Int32 = 0 + private let queue = Queue() + private var holders: [HolderRecord] = [] + private var currentTypeAndOutputMode: (ManagedAudioSessionType, AudioSessionOutputMode)? + private var deactivateTimer: SwiftSignalKit.Timer? + + private var isHeadsetPluggedInValue = false + private let outputsToHeadphonesSubscribers = Bag<(Bool) -> Void>() + private let isActiveSubscribers = Bag<(Bool) -> Void>() + + init() { + let queue = self.queue + + + queue.async { + self.isHeadsetPluggedInValue = self.isHeadsetPluggedIn() + } + } + + deinit { + self.deactivateTimer?.invalidate() + } + + func headsetConnected() -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + if let strongSelf = self { + subscriber.putNext(strongSelf.isHeadsetPluggedInValue) + + let index = strongSelf.outputsToHeadphonesSubscribers.add({ value in + subscriber.putNext(value) + }) + + return ActionDisposable { + queue.async { + if let strongSelf = self { + strongSelf.outputsToHeadphonesSubscribers.remove(index) + } + } + } + } else { + return EmptyDisposable + } + } |> runOn(queue) + } + + public func isActive() -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + if let strongSelf = self { + subscriber.putNext(strongSelf.currentTypeAndOutputMode != nil) + + let index = strongSelf.isActiveSubscribers.add({ value in + subscriber.putNext(value) + }) + + return ActionDisposable { + queue.async { + if let strongSelf = self { + strongSelf.isActiveSubscribers.remove(index) + } + } + } + } else { + return EmptyDisposable + } + } |> runOn(queue) + } + + func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, activate: @escaping (AudioSessionActivationState) -> Void, deactivate: @escaping () -> Signal) -> Disposable { + return self.push(audioSessionType: audioSessionType, once: once, manualActivate: { control in + control.setup() + control.activate({ state in + activate(state) + }) + }, deactivate: deactivate) + } + + func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, manualActivate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void = { _ in }) -> Disposable { + let id = OSAtomicIncrement32(&self.nextId) + self.queue.async { + self.holders.append(HolderRecord(id: id, audioSessionType: audioSessionType, control: ManagedAudioSessionControl(setupImpl: { [weak self] synchronous in + if let strongSelf = self { + let f: () -> Void = { + for holder in strongSelf.holders { + if holder.id == id && holder.active { + strongSelf.setup(type: audioSessionType, outputMode: holder.outputMode) + break + } + } + } + + if synchronous { + strongSelf.queue.sync(f) + } else { + strongSelf.queue.async(f) + } + } + }, activateImpl: { [weak self] completion in + if let strongSelf = self { + strongSelf.queue.async { + for holder in strongSelf.holders { + if holder.id == id && holder.active { + strongSelf.activate() + completion.f(AudioSessionActivationState(isHeadsetConnected: strongSelf.isHeadsetPluggedInValue)) + break + } + } + } + } + }, setOutputModeImpl: { [weak self] value in + if let strongSelf = self { + strongSelf.queue.async { + for holder in strongSelf.holders { + if holder.id == id { + if holder.outputMode != value { + holder.outputMode = value + } + + if holder.active { + strongSelf.updateOutputMode(value) + } + } + } + } + } + }), activate: manualActivate, deactivate: deactivate, headsetConnectionStatusChanged: headsetConnectionStatusChanged, once: once, outputMode: outputMode)) + self.updateHolders() + } + return ActionDisposable { [weak self] in + if let strongSelf = self { + strongSelf.queue.async { + strongSelf.removeDeactivatedHolder(id: id) + } + } + } + } + + func dropAll() { + self.queue.async { + self.updateHolders(interruption: true) + } + } + + private func removeDeactivatedHolder(id: Int32) { + assert(self.queue.isCurrent()) + + for i in 0 ..< self.holders.count { + if self.holders[i].id == id { + self.holders[i].deactivatingDisposable?.dispose() + self.holders.remove(at: i) + self.updateHolders() + break + } + } + } + + private func updateHolders(interruption: Bool = false) { + assert(self.queue.isCurrent()) + + print("holder count \(self.holders.count)") + + if !self.holders.isEmpty { + var activeIndex: Int? + var deactivating = false + var index = 0 + for record in self.holders { + if record.active { + activeIndex = index + break + } + else if record.deactivatingDisposable != nil { + deactivating = true + } + index += 1 + } + if !deactivating { + if let activeIndex = activeIndex { + var deactivate = false + + if interruption { + if self.holders[activeIndex].audioSessionType != .voiceCall { + deactivate = true + } + } else { + if activeIndex != self.holders.count - 1 { + if self.holders[activeIndex].audioSessionType == .voiceCall { + deactivate = false + } else { + deactivate = true + } + } + } + + if deactivate { + self.holders[activeIndex].active = false + let id = self.holders[activeIndex].id + self.holders[activeIndex].deactivatingDisposable = (self.holders[activeIndex].deactivate() |> deliverOn(self.queue)).start(completed: { [weak self] in + if let strongSelf = self { + var index = 0 + for currentRecord in strongSelf.holders { + if currentRecord.id == id { + currentRecord.deactivatingDisposable = nil + if currentRecord.once { + strongSelf.holders.remove(at: index) + } + break + } + index += 1 + } + strongSelf.updateHolders() + } + }) + } + } else if activeIndex == nil { + let lastIndex = self.holders.count - 1 + + self.deactivateTimer?.invalidate() + self.deactivateTimer = nil + + self.holders[lastIndex].active = true + self.holders[lastIndex].activate(self.holders[lastIndex].control) + } + } + } else { + self.applyNoneDelayed() + } + } + + private func applyNoneDelayed() { + self.deactivateTimer?.invalidate() + + if self.currentTypeAndOutputMode?.0 == .voiceCall { + self.applyNone() + } else { + let deactivateTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + if let strongSelf = self { + strongSelf.applyNone() + } + }, queue: self.queue) + self.deactivateTimer = deactivateTimer + deactivateTimer.start() + } + } + + private func isHeadsetPluggedIn() -> Bool { + assert(self.queue.isCurrent()) + +// let route = AVAudioSession.sharedInstance().currentRoute +// //print("\(route)") +// for desc in route.outputs { +// if desc.portType == AVAudioSessionPortHeadphones || desc.portType == AVAudioSessionPortBluetoothA2DP || desc.portType == AVAudioSessionPortBluetoothHFP { +// return true +// } +// } + + return false + } + + private func applyNone() { + self.deactivateTimer?.invalidate() + self.deactivateTimer = nil + + let wasActive = self.currentTypeAndOutputMode != nil + self.currentTypeAndOutputMode = nil + +// print("ManagedAudioSession setting active false") +// do { +// try AVAudioSession.sharedInstance().setActive(false, with: [.notifyOthersOnDeactivation]) +// } catch let error { +// print("ManagedAudioSession applyNone error \(error)") +// } + + if wasActive { + for subscriber in self.isActiveSubscribers.copyItems() { + subscriber(false) + } + } + } + + private func setup(type: ManagedAudioSessionType, outputMode: AudioSessionOutputMode) { + self.deactivateTimer?.invalidate() + self.deactivateTimer = nil + + let wasActive = self.currentTypeAndOutputMode != nil + + if self.currentTypeAndOutputMode == nil || self.currentTypeAndOutputMode! != (type, outputMode) { + self.currentTypeAndOutputMode = (type, outputMode) +// +// do { +// print("ManagedAudioSession setting category for \(type)") +// try AVAudioSession.sharedInstance().setCategory(nativeCategoryForType(type), with: AVAudioSessionCategoryOptions(rawValue: allowBluetoothForType(type) ? AVAudioSessionCategoryOptions.allowBluetooth.rawValue : 0)) +// print("ManagedAudioSession setting active \(type != .none)") +// try AVAudioSession.sharedInstance().setMode(type == .voiceCall ? AVAudioSessionModeVoiceChat : AVAudioSessionModeDefault) +// } catch let error { +// print("ManagedAudioSession setup error \(error)") +// } + } + + if !wasActive { + for subscriber in self.isActiveSubscribers.copyItems() { + subscriber(true) + } + } + } + + private func setupOutputMode(_ outputMode: AudioSessionOutputMode) throws { +// switch outputMode { +// case .system: +// try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) +// case let .custom(output): +// switch output { +// case .speaker: +// try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) +// } +// case .speakerIfNoHeadphones: +// if !self.isHeadsetPluggedInValue { +// try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) +// } +// } + } + + private func activate() { + if let (type, outputMode) = self.currentTypeAndOutputMode { +// do { +// try AVAudioSession.sharedInstance().setActive(true) +// +// try self.setupOutputMode(outputMode) +// +// if case .voiceCall = type { +// try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.005) +// } +// } catch let error { +// print("ManagedAudioSession activate error \(error)") +// } + } + } + + private func updateOutputMode(_ outputMode: AudioSessionOutputMode) { + if let (type, currentOutputMode) = self.currentTypeAndOutputMode, currentOutputMode != outputMode { + self.currentTypeAndOutputMode = (type, outputMode) + do { + try self.setupOutputMode(outputMode) + } catch let error { + print("ManagedAudioSession overrideOutputAudioPort error \(error)") + } + } + } + + func callKitActivatedAudioSession() { + /*self.queue.async { + print("ManagedAudioSession callKitDeactivatedAudioSession") + self.callKitAudioSessionIsActive = true + self.updateHolders() + }*/ + } + + func callKitDeactivatedAudioSession() { + /*self.queue.async { + print("ManagedAudioSession callKitDeactivatedAudioSession") + self.callKitAudioSessionIsActive = false + self.updateHolders() + }*/ + } +} diff --git a/Telegram-Mac/MapResources.swift b/Telegram-Mac/MapResources.swift new file mode 100644 index 0000000000..1c217b2c99 --- /dev/null +++ b/Telegram-Mac/MapResources.swift @@ -0,0 +1,123 @@ +import Foundation +import Postbox +import TelegramCore +import SyncCore +import MapKit +import SwiftSignalKit + +public struct MapSnapshotMediaResourceId: MediaResourceId { + public let latitude: Double + public let longitude: Double + public let width: Int32 + public let height: Int32 + public let zoom: Int32 + public var uniqueId: String { + return "map-\(latitude)-\(longitude)-\(width)x\(height)-\(zoom)" + } + + public var hashValue: Int { + return self.uniqueId.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? MapSnapshotMediaResourceId { + return self.latitude == to.latitude && self.longitude == to.longitude && self.width == to.width && self.height == to.height && self.zoom == to.zoom + } else { + return false + } + } +} + +public class MapSnapshotMediaResource: TelegramMediaResource { + public let latitude: Double + public let longitude: Double + public let width: Int32 + public let height: Int32 + public let zoom: Int32 + public init(latitude: Double, longitude: Double, width: Int32, height: Int32, zoom: Int32) { + self.latitude = latitude + self.longitude = longitude + self.width = width + self.height = height + self.zoom = zoom + } + + public required init(decoder: PostboxDecoder) { + self.latitude = decoder.decodeDoubleForKey("lt", orElse: 0.0) + self.longitude = decoder.decodeDoubleForKey("ln", orElse: 0.0) + self.width = decoder.decodeInt32ForKey("w", orElse: 0) + self.height = decoder.decodeInt32ForKey("h", orElse: 0) + self.zoom = decoder.decodeInt32ForKey("z", orElse: 15) + + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeDouble(self.latitude, forKey: "lt") + encoder.encodeDouble(self.longitude, forKey: "ln") + encoder.encodeInt32(self.width, forKey: "w") + encoder.encodeInt32(self.height, forKey: "h") + encoder.encodeInt32(self.zoom, forKey: "z") + } + + public var id: MediaResourceId { + return MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height, zoom: self.zoom) + } + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? MapSnapshotMediaResource { + return self.latitude == to.latitude && self.longitude == to.longitude && self.width == to.width && self.height == to.height && self.zoom == to.zoom + } else { + return false + } + } +} + +let TGGoogleMapsOffset: Int = 268435456 +let TGGoogleMapsRadius = Double(TGGoogleMapsOffset) / Double.pi + +private func yToLatitude(_ y: Int) -> Double { + return ((Double.pi / 2.0) - 2 * atan(exp((Double(y - TGGoogleMapsOffset)) / TGGoogleMapsRadius))) * 180.0 / Double.pi; +} + +private func latitudeToY(_ latitude: Double) -> Int { + return Int(round(Double(TGGoogleMapsOffset) - TGGoogleMapsRadius * log((1.0 + sin(latitude * Double.pi / 180.0)) / (1.0 - sin(latitude * Double.pi / 180.0))) / 2.0)) +} + +private func adjustGMapLatitude(_ latitude: Double, offset: Int, zoom: Int) -> Double { + let t: Int = (offset << (21 - zoom)) + return yToLatitude(latitudeToY(latitude) + t) +} + +func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + Queue.concurrentDefaultQueue().async { + let options = MKMapSnapshotter.Options() + let latitude = adjustGMapLatitude(resource.latitude, offset: -10, zoom: Int(resource.zoom)) + options.region = MKCoordinateRegion(center: CLLocationCoordinate2DMake(latitude, resource.longitude), span: MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)) + options.mapType = .standard + options.showsPointsOfInterest = false + options.showsBuildings = true + options.size = CGSize(width: CGFloat(resource.width + 1), height: CGFloat(resource.height + 24)) + // options.scale = 2.0 + let snapshotter = MKMapSnapshotter(options: options) + snapshotter.start(with: DispatchQueue.global(), completionHandler: { result, error in + if let image = result?.image, let data = image.tiffRepresentation(using: .jpeg, factor: 0.6) { + let imageRep = NSBitmapImageRep(data: data) + let compressedData: Data? = imageRep?.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:]) + if let data = compressedData { + subscriber.putNext(MediaResourceDataFetchResult.dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true)) + subscriber.putCompletion() + } + } + }) + disposable.set(ActionDisposable { + snapshotter.cancel() + }) + } + + return disposable + } +} + diff --git a/Telegram-Mac/Markdown.swift b/Telegram-Mac/Markdown.swift index 4ddb354228..dc3799f8cf 100644 --- a/Telegram-Mac/Markdown.swift +++ b/Telegram-Mac/Markdown.swift @@ -21,7 +21,7 @@ final class MarkdownAttributes { let link: MarkdownAttributeSet let linkAttribute: (String) -> (String, Any)? - init(body: MarkdownAttributeSet, bold: MarkdownAttributeSet, link: MarkdownAttributeSet, linkAttribute: @escaping (String) -> (String, Any)?) { + init(body: MarkdownAttributeSet, bold: MarkdownAttributeSet = MarkdownAttributeSet(font: .bold(.text), textColor: theme.colors.grayText), link: MarkdownAttributeSet, linkAttribute: @escaping (String) -> (String, Any)?) { self.body = body self.link = link self.bold = bold @@ -58,10 +58,10 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt let result = NSMutableAttributedString() var remainingRange = NSMakeRange(0, nsString.length) - var bodyAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.body.font, NSAttributedStringKey.foregroundColor: attributes.body.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] + var bodyAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.body.font, NSAttributedString.Key.foregroundColor: attributes.body.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.body.additionalAttributes { - bodyAttributes[NSAttributedStringKey(rawValue: key)] = value + bodyAttributes[NSAttributedString.Key(rawValue: key)] = value } } @@ -77,14 +77,14 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt if character == UInt16(("[" as UnicodeScalar).value) { remainingRange = NSMakeRange(range.location + range.length, remainingRange.location + remainingRange.length - (range.location + range.length)) if let (parsedLinkText, parsedLinkContents) = parseLink(string: nsString, remainingRange: &remainingRange) { - var linkAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.link.font, NSAttributedStringKey.foregroundColor: attributes.link.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] + var linkAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.link.font, NSAttributedString.Key.foregroundColor: attributes.link.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.link.additionalAttributes { - linkAttributes[NSAttributedStringKey(rawValue: key)] = value + linkAttributes[NSAttributedString.Key(rawValue: key)] = value } } if let (attributeName, attributeValue) = attributes.linkAttribute(parsedLinkContents) { - linkAttributes[NSAttributedStringKey(rawValue: attributeName)] = attributeValue + linkAttributes[NSAttributedString.Key(rawValue: attributeName)] = attributeValue } result.append(NSAttributedString(string: parsedLinkText, attributes: linkAttributes)) } @@ -95,10 +95,10 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt remainingRange = NSMakeRange(range.location + range.length + 1, remainingRange.location + remainingRange.length - (range.location + range.length + 1)) if let bold = parseBold(string: nsString, remainingRange: &remainingRange) { - var boldAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.bold.font, NSAttributedStringKey.foregroundColor: attributes.bold.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] + var boldAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.bold.font, NSAttributedString.Key.foregroundColor: attributes.bold.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.bold.additionalAttributes { - boldAttributes[NSAttributedStringKey(rawValue: key)] = value + boldAttributes[NSAttributedString.Key(rawValue: key)] = value } } result.append(NSAttributedString(string: bold, attributes: boldAttributes)) diff --git a/Telegram-Mac/MediaAnimatedStickerView.swift b/Telegram-Mac/MediaAnimatedStickerView.swift new file mode 100644 index 0000000000..482cd95470 --- /dev/null +++ b/Telegram-Mac/MediaAnimatedStickerView.swift @@ -0,0 +1,285 @@ +// +// ChatMediaAnimatedSticker.swift +// Telegram +// +// Created by Mikhail Filimonov on 13/05/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit + + + +class MediaAnimatedStickerView: ChatMediaContentView { + + private let loadResourceDisposable = MetaDisposable() + private let stateDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private let playThrottleDisposable = MetaDisposable() + private let playerView: LottiePlayerView = LottiePlayerView(frame: NSMakeRect(0, 0, 240, 240)) + private let thumbView = TransformImageView() + private var sticker:LottieAnimation? = nil { + didSet { + if oldValue != sticker { + self.previousAccept = false + } + updatePlayerIfNeeded() + } + } + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.thumbView) + addSubview(self.playerView) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func clean() { + stateDisposable.set(nil) + loadResourceDisposable.set(nil) + playThrottleDisposable.set(nil) + fetchDisposable.set(nil) + } + + deinit { + loadResourceDisposable.dispose() + stateDisposable.dispose() + playThrottleDisposable.dispose() + fetchDisposable.dispose() + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + private var previousAccept: Bool = false + + + @objc func updatePlayerIfNeeded() { + var accept = ((self.window != nil && self.window!.isKeyWindow) || (self.window != nil && !(self.window is Window))) && !NSIsEmptyRect(self.visibleRect) && !self.isDynamicContentLocked && self.sticker != nil + + let parameters = self.parameters as? ChatAnimatedStickerMediaLayoutParameters + + accept = parameters?.alwaysAccept ?? accept + + var signal = Signal.single(Void()) + if accept && !nextForceAccept { + signal = signal |> delay(accept ? 0.1 : 0, queue: .mainQueue()) + } + if accept && self.sticker != nil { + nextForceAccept = false + } + + if let sticker = self.sticker, previousAccept { + switch sticker.playPolicy { + case .once: + return + default: + break + } + } + + if previousAccept != accept { + self.playThrottleDisposable.set(signal.start(next: { [weak self] in + guard let `self` = self else { + return + } + self.playerView.set(accept ? self.sticker : nil) + self.previousAccept = accept + })) + } + previousAccept = accept + + + } + + private var nextForceAccept: Bool = false + + + override func previewMediaIfPossible() -> Bool { + if let table = table, let context = context, let window = window as? Window { + _ = startModalPreviewHandle(table, window: window, context: context) + } + return true + } + override func executeInteraction(_ isControl: Bool) { + if let window = window as? Window { + if let context = context, let peerId = parent?.id.peerId, let media = media as? TelegramMediaFile, !media.isEmojiAnimatedSticker, let reference = media.stickerReference { + showModal(with:StickerPackPreviewModalController(context, peerId: peerId, reference: reference), for:window) + } else { + self.playerView.playIfNeeded() + } + } + } + + var chatLoopAnimated: Bool { + if let context = self.context { + return context.autoplayMedia.loopAnimatedStickers + } + return true + } + + func updateListeners() { + if let window = window { + + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self.enclosingScrollView?.contentView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: self.enclosingScrollView?.documentView) + + } else { + removeNotificationListeners() + } + } + + override func viewWillDraw() { + super.viewWillDraw() + updatePlayerIfNeeded() + } + + override func willRemove() { + super.willRemove() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToSuperview() { + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToWindow() { + updateListeners() + updatePlayerIfNeeded() + } + + override func update(with media: Media, size: NSSize, context: AccountContext, parent: Message?, table: TableView?, parameters: ChatMediaLayoutParameters?, animated: Bool, positionFlags: LayoutPositionFlags?, approximateSynchronousValue: Bool) { + + + guard let file = media as? TelegramMediaFile else { return } + + let updated = self.media != nil ? !file.isSemanticallyEqual(to: self.media!) : true + + if parent?.stableId != self.parent?.stableId { + self.sticker = nil + } else if parent == nil, updated { + self.sticker = nil + } + self.nextForceAccept = approximateSynchronousValue || parent?.id.namespace == Namespaces.Message.Local + + + super.update(with: media, size: size, context: context, parent: parent, table: table, parameters: parameters, animated: animated, positionFlags: positionFlags, approximateSynchronousValue: approximateSynchronousValue) + + + let reference: MediaResourceReference + + if let message = parent { + reference = FileMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource) + } else if let stickerReference = file.stickerReference { + if file.resource is CloudStickerPackThumbnailMediaResource { + reference = MediaResourceReference.stickerPackThumbnail(stickerPack: stickerReference, resource: file.resource) + } else { + reference = FileMediaReference.stickerPack(stickerPack: stickerReference, media: file).resourceReference(file.resource) + } + } else { + reference = FileMediaReference.standalone(media: file).resourceReference(file.resource) + } + + let data: Signal + if let resource = file.resource as? LocalBundleResource { + data = Signal { subscriber in + if let path = Bundle.main.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true)) + subscriber.putCompletion() + } + return EmptyDisposable + } |> runOn(resourcesQueue) + } else { + data = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: approximateSynchronousValue) + } + + self.loadResourceDisposable.set((data |> map { resourceData -> Data? in + + if resourceData.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + return data + } + return nil + } |> deliverOnMainQueue).start(next: { [weak file, weak self] data in + if let data = data, let file = file, let `self` = self { + let parameters = parameters as? ChatAnimatedStickerMediaLayoutParameters + let playPolicy: LottiePlayPolicy = parameters?.playPolicy ?? (file.isEmojiAnimatedSticker || !self.chatLoopAnimated ? (self.parameters == nil ? .framesCount(1) : .once) : .loop) + + let maximumFps: Int = size.width < 200 && !file.isEmojiAnimatedSticker ? size.width <= 30 ? 24 : 30 : 60 + let cache: ASCachePurpose = parameters?.cache ?? (size.width < 200 && size.width > 30 ? .temporaryLZ4(.thumb) : self.parent != nil ? .temporaryLZ4(.chat) : .none) + let fitzModifier = file.animatedEmojiFitzModifier + self.sticker = LottieAnimation(compressed: data, key: LottieAnimationEntryKey(key: .media(file.id), size: size, fitzModifier: fitzModifier), cachePurpose: cache, playPolicy: playPolicy, maximumFps: maximumFps, postbox: self.context?.account.postbox) + self.fetchStatus = .Local + } else { + self?.sticker = nil + self?.fetchStatus = .Remote + } + })) + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + + + self.thumbView.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor), clearInstantly: updated) + if !self.thumbView.isFullyLoaded { + self.thumbView.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, scale: backingScaleFactor, size: size, fetched: false), cacheImage: { [weak file] result in + if let file = file { + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + } + }) + self.thumbView.set(arguments: arguments) + } + + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference).start()) + stateDisposable.set((self.playerView.state |> deliverOnMainQueue).start(next: { [weak self] state in + guard let `self` = self else { return } + + + switch state { + case .playing: + self.playerView.isHidden = false + self.thumbView.isHidden = true + case .stoped: + self.playerView.isHidden = true + self.thumbView.isHidden = false + if let parameters = parameters as? ChatAnimatedStickerMediaLayoutParameters { + if parameters.hidePlayer { + self.playerView.isHidden = false + self.thumbView.isHidden = true + } + } + default: + self.playerView.isHidden = false + self.thumbView.isHidden = false + } + })) + } + + override var contents: Any? { + return self.thumbView.image + } + + override func layout() { + super.layout() + self.playerView.frame = bounds + self.thumbView.frame = bounds + } + +} diff --git a/Telegram-Mac/MediaFrameSource.swift b/Telegram-Mac/MediaFrameSource.swift new file mode 100755 index 0000000000..c7ade4438e --- /dev/null +++ b/Telegram-Mac/MediaFrameSource.swift @@ -0,0 +1,32 @@ +import Foundation +import SwiftSignalKit +import CoreMedia + + +enum MediaTrackEvent { + case frames([MediaTrackDecodableFrame]) + case endOfStream +} + +final class MediaFrameSourceSeekResult { + let buffers: MediaPlaybackBuffers + let extraDecodedVideoFrames: [MediaTrackFrame] + let timestamp: CMTime + + init(buffers: MediaPlaybackBuffers, extraDecodedVideoFrames: [MediaTrackFrame], timestamp: CMTime) { + self.buffers = buffers + self.extraDecodedVideoFrames = extraDecodedVideoFrames + self.timestamp = timestamp + } +} + +enum MediaFrameSourceSeekError { + case generic +} + +protocol MediaFrameSource { + func addEventSink(_ f: @escaping (MediaTrackEvent) -> Void) -> Int + func removeEventSink(_ index: Int) + func generateFrames(until timestamp: Double) + func seek(timestamp: Double) -> Signal, MediaFrameSourceSeekError> +} diff --git a/Telegram-Mac/MediaGroupPreviewRowItem.swift b/Telegram-Mac/MediaGroupPreviewRowItem.swift new file mode 100644 index 0000000000..47793f971a --- /dev/null +++ b/Telegram-Mac/MediaGroupPreviewRowItem.swift @@ -0,0 +1,358 @@ +// +// MediaGroupPreviewRowItem.swift +// Telegram +// +// Created by keepcoder on 02/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class MediaGroupPreviewRowItem: TableRowItem { + fileprivate let context: AccountContext + private let _stableId: UInt32 = arc4random() + fileprivate let layout: GroupedLayout + fileprivate let reorder:(Int, Int)->Void + fileprivate let urls: [URL] + fileprivate let hasEditedData: [URL: EditedImageData] + fileprivate let edit:(URL)->Void + fileprivate let delete:(URL)->Void + init(_ initialSize: NSSize, messages: [Message], urls: [URL], editedData: [URL : EditedImageData], edit: @escaping(URL)->Void, delete:@escaping(URL)->Void, context: AccountContext, reorder:@escaping(Int, Int)->Void) { + layout = GroupedLayout(messages) + self.hasEditedData = editedData + self.edit = edit + self.delete = delete + self.urls = urls + self.reorder = reorder + self.context = context + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + layout.measure(NSMakeSize(width - 20, width - 20)) + return success + } + + override var height: CGFloat { + return layout.dimensions.height + 12 + } + + override var stableId: AnyHashable { + return _stableId + } + + + + override func viewClass() -> AnyClass { + return MediaGroupPreviewRowView.self + } + +} + +class MediaGroupPreviewRowView : TableRowView, ModalPreviewRowViewProtocol { + private var contents: [ChatMediaContentView] = [] + private var startPoint: NSPoint = NSZeroPoint + private(set) var draggingIndex: Int? = nil + + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + + guard let item = item as? MediaGroupPreviewRowItem else { return nil } + + for i in 0 ..< item.layout.count { + if NSPointInRect(point, item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y)) { + let contentNode = contents[i] + if contentNode is ChatGIFContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, GifPreviewModalView.self), contentNode) + } + } else if contentNode is ChatInteractiveContentView { + if let image = contentNode.media as? TelegramMediaImage { + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } + } + } + return nil + } + + override func draw(_ dirtyRect: NSRect) { + + } + + override func updateColors() { + super.updateColors() + for content in contents { + content.backgroundColor = .clear + } + } + + private var offset: NSPoint { + guard let item = item as? MediaGroupPreviewRowItem else { return NSZeroPoint } + return NSMakePoint((frame.width - item.layout.dimensions.width) / 2, 6) + } + + override func set(item: TableRowItem, animated: Bool) { + + guard let item = item as? MediaGroupPreviewRowItem else {return} + + if contents.count > item.layout.count { + let contentCount = contents.count + let layoutCount = item.layout.count + + for i in layoutCount ..< contentCount { + contents[i].removeFromSuperview() + } + contents = contents.subarray(with: NSMakeRange(0, layoutCount)) + } else if contents.count < item.layout.count { + let contentCount = contents.count + for _ in contentCount ..< item.layout.count { + contents.append(ChatInteractiveContentView(frame: NSZeroRect)) + contents.last?.userInteractionEnabled = false + } + } + + + + for i in 0 ..< contents.count { + let content = contents[i] + addSubview(content) + let control: MediaPreviewEditControl + if let editControl = content.subviews.last as? MediaPreviewEditControl { + control = editControl + } else { + let editControl = MediaPreviewEditControl() + content.addSubview(editControl) + control = editControl + } + control.canEdit = item.layout.messages[i].media[0] is TelegramMediaImage + control.set(edit: { [weak item] in + guard let item = item else {return} + item.edit(item.urls[i]) + }, delete: { [weak item] in + guard let item = item else {return} + item.delete(item.urls[i]) + }, hasEditedData: item.hasEditedData[item.urls[i]] != nil) + + } + + assert(contents.count == item.layout.count) + + for i in 0 ..< item.layout.count { + contents[i].update(with: item.layout.messages[i].media[0], size: item.layout.frame(at: i).size, context: item.context, parent: nil, table: item.table, positionFlags: item.layout.position(at: i)) + } + super.set(item: item, animated: animated) + + needsLayout = true + + updateMouse() + } + + override func forceClick(in location: NSPoint) { + guard let item = item as? MediaGroupPreviewRowItem else {return} + + for i in 0 ..< item.layout.count { + if NSPointInRect(location, item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y)) { + _ = contents[i].previewMediaIfPossible() + break + } + } + } + + override func updateMouse() { + guard let window = window, let table = item?.table else { + for node in self.contents { + if let control = node.subviews.last as? MediaPreviewEditControl { + control.isHidden = true + } + } + return + } + + let row = table.row(at: table.documentView!.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + + if row == item?.index { + let point = convert(window.mouseLocationOutsideOfEventStream, from: nil) + for node in self.contents { + if let control = node.subviews.last as? MediaPreviewEditControl { + if NSPointInRect(point, node.frame) { + control.isHidden = false + } else { + control.isHidden = true + } + } + } + } else { + for node in self.contents { + if let control = node.subviews.last as? MediaPreviewEditControl { + control.isHidden = true + } + } + } + } + + override func mouseDown(with event: NSEvent) { + guard let item = item as? MediaGroupPreviewRowItem else {return} + let point = convert(event.locationInWindow, from: nil) + draggingIndex = nil + previous = point + for i in 0 ..< item.layout.count { + if NSPointInRect(point, item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y)) { + self.startPoint = point + self.draggingIndex = i + //contents[i].removeFromSuperview() + addSubview(contents[i]) + break + } + } + super.mouseDown(with: event) + } + + override func mouseUp(with event: NSEvent) { + + guard let item = item as? MediaGroupPreviewRowItem else {return} + + var point = convert(event.locationInWindow, from: nil) + point = NSMakePoint(min(max(0, point.x), frame.width - 10), min(max(0, point.y), frame.height - 10)) + if let index = draggingIndex, let newIndex = item.layout.moveItemIfNeeded(at: index, point: point) { + + let current = contents[index] + contents.remove(at: index) + contents.insert(current, at: newIndex) + + _ = item.makeSize(frame.width, oldWidth: 0) + item.table?.noteHeightOfRow(item.index, true) + + item.reorder(index, newIndex) + + set(item: item, animated: true) + for i in 0 ..< item.layout.count { + let rect = item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y) + contents[i].change(pos: rect.origin, animated: true) + contents[i].change(size: rect.size, animated: true) + } + } else { + for i in 0 ..< item.layout.count { + let rect = item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y) + contents[i].change(pos: rect.origin, animated: true) + contents[i].change(size: rect.size, animated: true) + } + } + + draggingIndex = nil + startPoint = NSZeroPoint + } + private var previous: NSPoint = NSZeroPoint + override func mouseDragged(with event: NSEvent) { + guard let item = item as? MediaGroupPreviewRowItem else {return} + + let point = convert(event.locationInWindow, from: nil) + + if let index = draggingIndex { + let past = contents[index].frame + + var current = contents[index].frame.origin + current.x += (point.x - previous.x) + current.y += (point.y - previous.y) + + + let size = contents[index].frame.size.fitted(NSMakeSize(100, 100)) + current.x -= (size.width - past.width) * ((point.x - past.minX) / past.width) + current.y -= (size.height - past.height) * ((point.y - past.minY) / past.height) + + + contents[index].change(pos: current, animated: false) + + if size != contents[index].frame.size { + contents[index].change(size: size, animated: true) + } + previous = point + + + + + let layout = GroupedLayout(item.layout.messages) + layout.measure(NSMakeSize(frame.width - 20, frame.width - 20)) + + if let new = layout.moveItemIfNeeded(at: index, point: point) { + + for i in 0 ..< layout.count { + let current = item.layout.frame(at: i).offsetBy(dx: offset.x, dy: offset.y).origin + + if i != index { + contents[i].setFrameOrigin(current) + } + + } + } else { + for i in 0 ..< item.layout.count { + if i != index { + contents[i].setFrameOrigin(item.layout.frame(at: i).origin.offsetBy(dx: offset.x, dy: offset.y)) + } + } + } + } + } + + override var needsDisplay: Bool { + get { + return super.needsDisplay + } + set { + super.needsDisplay = newValue + for content in contents { + content.needsDisplay = newValue + } + } + } + override var backgroundColor: NSColor { + didSet { + for content in contents { + content.backgroundColor = backdorColor + } + } + } + + + override func viewWillMove(toSuperview newSuperview: NSView?) { + if newSuperview == nil { + for content in contents { + content.willRemove() + } + } + } + + + override var backdorColor: NSColor { + return theme.colors.background + } + + + override func layout() { + super.layout() + guard let item = item as? MediaGroupPreviewRowItem else {return} + + assert(contents.count == item.layout.count) + + if let _ = draggingIndex { + return + } + + for i in 0 ..< item.layout.count { + contents[i].setFrameOrigin(item.layout.frame(at: i).origin.offsetBy(dx: offset.x, dy: offset.y)) + if let control = contents[i].subviews.first(where: { $0 is MediaPreviewEditControl }) { + control.setFrameOrigin(NSMakePoint(contents[i].frame.width - control.frame.width - 10, contents[i].frame.height - control.frame.height - 10)) + } + } + + } +} diff --git a/Telegram-Mac/MediaPlaybackData.swift b/Telegram-Mac/MediaPlaybackData.swift new file mode 100755 index 0000000000..687daac556 --- /dev/null +++ b/Telegram-Mac/MediaPlaybackData.swift @@ -0,0 +1,12 @@ +import Foundation +import SwiftSignalKit + +final class MediaPlaybackBuffers { + let audioBuffer: MediaTrackFrameBuffer? + let videoBuffer: MediaTrackFrameBuffer? + + init(audioBuffer: MediaTrackFrameBuffer?, videoBuffer: MediaTrackFrameBuffer?) { + self.audioBuffer = audioBuffer + self.videoBuffer = videoBuffer + } +} diff --git a/Telegram-Mac/MediaPlayer.swift b/Telegram-Mac/MediaPlayer.swift new file mode 100755 index 0000000000..ff8d891ee8 --- /dev/null +++ b/Telegram-Mac/MediaPlayer.swift @@ -0,0 +1,1018 @@ +import Foundation +import SwiftSignalKit +import Postbox +import CoreMedia +import TelegramCore +import SyncCore +import Postbox + +private let traceEvents = false + +private struct MediaPlayerControlTimebase { + let timebase: CMTimebase + let isAudio: Bool + init(timebase: CMTimebase, isAudio: Bool) { + self.timebase = timebase + self.isAudio = isAudio + } +} + +private enum MediaPlayerPlaybackAction { + case play + case pause +} + +private final class MediaPlayerLoadedState { + let frameSource: MediaFrameSource + let mediaBuffers: MediaPlaybackBuffers + let controlTimebase: MediaPlayerControlTimebase + var lostAudioSession: Bool = false + var extraVideoFrames: ([MediaTrackFrame], CMTime)? + init(frameSource: MediaFrameSource, mediaBuffers: MediaPlaybackBuffers, controlTimebase: MediaPlayerControlTimebase) { + self.frameSource = frameSource + self.mediaBuffers = mediaBuffers + self.controlTimebase = controlTimebase + } +} + +private enum MediaPlayerState { + case empty + case seeking(frameSource: MediaFrameSource, timestamp: Double, disposable: Disposable, action: MediaPlayerPlaybackAction, enableSound: Bool) + case paused(MediaPlayerLoadedState) + case playing(MediaPlayerLoadedState) +} + +enum MediaPlayerActionAtEnd { + case loop((() -> Void)?) + case action(() -> Void) + case loopDisablingSound(() -> Void) + case stop +} + +private final class MediaPlayerAudioRendererContext { + let renderer: MediaPlayerAudioRenderer + var requestedFrames = false + + init(renderer: MediaPlayerAudioRenderer) { + self.renderer = renderer + } +} + +private final class MediaPlayerContext { + private let queue: Queue + + private let postbox: Postbox + private let resourceReference: MediaResourceReference + private let streamable: Bool + private let video: Bool + private let preferSoftwareDecoding: Bool + private var enableSound: Bool + private var baseRate: Double + private var volume: Float + private let fetchAutomatically: Bool + private var playAndRecord: Bool + private var keepAudioSessionWhilePaused: Bool + private var initialTimebase: CMTimebase? + private var seekId: Int = 0 + + private var state: MediaPlayerState = .empty + private var audioRenderer: MediaPlayerAudioRendererContext? + private var forceAudioToSpeaker = false + fileprivate let videoRenderer: VideoPlayerProxy + + private var tickTimer: SwiftSignalKit.Timer? + + private var lastStatusUpdateTimestamp: Double? + private let playerStatus: ValuePromise + + fileprivate var actionAtEnd: MediaPlayerActionAtEnd = .stop + + fileprivate var timebasePromise: Promise = Promise() + + private var stoppedAtEnd = false + + init(queue: Queue, playerStatus: ValuePromise, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, volume: Float, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool, initialTimebase: CMTimebase?) { + assert(queue.isCurrent()) + + self.queue = queue + self.initialTimebase = initialTimebase + self.playerStatus = playerStatus + self.postbox = postbox + self.resourceReference = resourceReference + self.streamable = streamable + self.video = video + self.preferSoftwareDecoding = preferSoftwareDecoding + self.enableSound = enableSound + self.baseRate = baseRate + self.volume = volume + self.fetchAutomatically = fetchAutomatically + self.playAndRecord = playAndRecord + self.keepAudioSessionWhilePaused = keepAudioSessionWhilePaused + + self.videoRenderer = VideoPlayerProxy(queue: queue) + + self.videoRenderer.visibilityUpdated = { [weak self] value in + assert(queue.isCurrent()) + + if let strongSelf = self, !strongSelf.enableSound { + switch strongSelf.state { + case .empty: + if value && playAutomatically { + strongSelf.play() + } + case .paused: + if value { + strongSelf.play() + } + case .playing: + if !value { + strongSelf.pause(lostAudioSession: false) + } + case let .seeking(_, _, _, action, _): + switch action { + case .pause: + if value { + strongSelf.play() + } + case .play: + if !value { + strongSelf.pause(lostAudioSession: false) + } + } + } + } + } + + self.videoRenderer.takeFrameAndQueue = (queue, { [weak self] in + assert(queue.isCurrent()) + + if let strongSelf = self { + var maybeLoadedState: MediaPlayerLoadedState? + + switch strongSelf.state { + case .empty: + return .noFrames + case let .paused(state): + maybeLoadedState = state + case let .playing(state): + maybeLoadedState = state + case .seeking: + return .noFrames + } + + if let loadedState = maybeLoadedState, let videoBuffer = loadedState.mediaBuffers.videoBuffer { + if let (extraVideoFrames, atTime) = loadedState.extraVideoFrames { + loadedState.extraVideoFrames = nil + return .restoreState(extraVideoFrames, atTime) + } else { + return videoBuffer.takeFrame() + } + } else { + return .noFrames + } + } else { + return .noFrames + } + + }) + } + + deinit { + assert(self.queue.isCurrent()) + + self.tickTimer?.invalidate() + + if case let .seeking(_, _, disposable, _, _) = self.state { + disposable.dispose() + } + } + + fileprivate func seek(timestamp: Double) { + assert(self.queue.isCurrent()) + + let action: MediaPlayerPlaybackAction + switch self.state { + case .empty, .paused: + action = .pause + case .playing: + action = .play + case let .seeking(_, _, _, currentAction, _): + action = currentAction + } + self.seek(timestamp: timestamp, action: action) + } + + fileprivate func seek(timestamp: Double, action: MediaPlayerPlaybackAction) { + assert(self.queue.isCurrent()) + + var loadedState: MediaPlayerLoadedState? + switch self.state { + case .empty: + break + case let .playing(currentLoadedState): + loadedState = currentLoadedState + case let .paused(currentLoadedState): + loadedState = currentLoadedState + case let .seeking(previousFrameSource, previousTimestamp, previousDisposable, _, previousEnableSound): + if previousTimestamp.isEqual(to: timestamp) && self.enableSound == previousEnableSound { + self.state = .seeking(frameSource: previousFrameSource, timestamp: previousTimestamp, disposable: previousDisposable, action: action, enableSound: self.enableSound) + } else { + previousDisposable.dispose() + } + } + + self.tickTimer?.invalidate() + if let loadedState = loadedState { + self.seekId += 1 + + if loadedState.controlTimebase.isAudio { + self.audioRenderer?.renderer.setRate(0.0) + } else { + if !CMTimebaseGetRate(loadedState.controlTimebase.timebase).isEqual(to: 0.0) { + CMTimebaseSetRate(loadedState.controlTimebase.timebase, rate: 0.0) + } + } + let currentTimestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + var duration: Double = 0.0 + var videoStatus: MediaTrackFrameBufferStatus? + if let videoTrackFrameBuffer = loadedState.mediaBuffers.videoBuffer { + videoStatus = videoTrackFrameBuffer.status(at: currentTimestamp) + duration = max(duration, CMTimeGetSeconds(videoTrackFrameBuffer.duration)) + } + + var audioStatus: MediaTrackFrameBufferStatus? + if let audioTrackFrameBuffer = loadedState.mediaBuffers.audioBuffer { + audioStatus = audioTrackFrameBuffer.status(at: currentTimestamp) + duration = max(duration, CMTimeGetSeconds(audioTrackFrameBuffer.duration)) + } + let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: duration, dimensions: CGSize(), timestamp: min(max(timestamp, 0.0), duration), baseRate: self.baseRate, volume: self.volume, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) + self.playerStatus.set(status) + } else { + let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, dimensions: CGSize(), timestamp: timestamp, baseRate: self.baseRate, volume: self.volume, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) + self.playerStatus.set(status) + } + + let frameSource = FFMpegMediaFrameSource(queue: self.queue, postbox: self.postbox, resourceReference: self.resourceReference, tempFilePath: nil, streamable: self.streamable, video: self.video, preferSoftwareDecoding: self.preferSoftwareDecoding, fetchAutomatically: self.fetchAutomatically) + let disposable = MetaDisposable() + self.state = .seeking(frameSource: frameSource, timestamp: timestamp, disposable: disposable, action: action, enableSound: self.enableSound) + + self.lastStatusUpdateTimestamp = nil + + let seekResult = frameSource.seek(timestamp: timestamp) |> deliverOn(self.queue) + + disposable.set(seekResult.start(next: { [weak self] seekResult in + if let strongSelf = self { + var result: MediaFrameSourceSeekResult? + seekResult.with { object in + assert(strongSelf.queue.isCurrent()) + result = object + } + if let result = result { + strongSelf.seekingCompleted(seekResult: result) + } else { + assertionFailure() + } + } + }, error: { _ in + })) + } + + fileprivate func seekingCompleted(seekResult: MediaFrameSourceSeekResult) { + if traceEvents { + print("seekingCompleted at \(CMTimeGetSeconds(seekResult.timestamp))") + } + + assert(self.queue.isCurrent()) + + guard case let .seeking(frameSource, _, _, action, _) = self.state else { + assertionFailure() + return + } + + var buffers = seekResult.buffers + if !self.enableSound { + buffers = MediaPlaybackBuffers(audioBuffer: nil, videoBuffer: buffers.videoBuffer) + } + + buffers.audioBuffer?.statusUpdated = { [weak self] in + self?.tick() + } + buffers.videoBuffer?.statusUpdated = { [weak self] in + self?.tick() + } + let controlTimebase: MediaPlayerControlTimebase + + if let _ = buffers.audioBuffer { + let renderer: MediaPlayerAudioRenderer + if let currentRenderer = self.audioRenderer, !currentRenderer.requestedFrames { + renderer = currentRenderer.renderer + } else { + self.audioRenderer?.renderer.stop() + self.audioRenderer = nil + + let queue = self.queue + renderer = MediaPlayerAudioRenderer(playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, volume: self.volume, updatedRate: { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.tick() + } + } + }, audioPaused: { [weak self] in + queue.async { + if let strongSelf = self { + if strongSelf.enableSound { + strongSelf.pause(lostAudioSession: true) + } else { + strongSelf.seek(timestamp: 0.0, action: .play) + } + } + } + }) + self.audioRenderer = MediaPlayerAudioRendererContext(renderer: renderer) + renderer.start() + } + + controlTimebase = MediaPlayerControlTimebase(timebase: renderer.audioTimebase, isAudio: true) + } else { + self.audioRenderer?.renderer.stop() + self.audioRenderer = nil + + var timebase: CMTimebase? + CMTimebaseCreateWithMasterClock(allocator: kCFAllocatorDefault, masterClock: CMClockGetHostTimeClock(), timebaseOut: &timebase) + CMTimebaseSetRate(timebase!, rate: self.baseRate) + CMTimebaseSetTime(timebase!, time: seekResult.timestamp) + controlTimebase = MediaPlayerControlTimebase(timebase: timebase!, isAudio: false) + } + + let loadedState = MediaPlayerLoadedState(frameSource: frameSource, mediaBuffers: buffers, controlTimebase: controlTimebase) + loadedState.extraVideoFrames = (seekResult.extraDecodedVideoFrames, seekResult.timestamp) + self.timebasePromise.set(.single(loadedState.controlTimebase.timebase)) + + + if let audioRenderer = self.audioRenderer?.renderer { + let queue = self.queue + audioRenderer.flushBuffers(at: seekResult.timestamp, completion: { [weak self] in + queue.async { [weak self] in + if let strongSelf = self { + switch action { + case .play: + strongSelf.state = .playing(loadedState) + strongSelf.audioRenderer?.renderer.start() + case .pause: + strongSelf.state = .paused(loadedState) + } + + strongSelf.lastStatusUpdateTimestamp = nil + strongSelf.tick() + } + } + }) + } else { + switch action { + case .play: + self.state = .playing(loadedState) + case .pause: + self.state = .paused(loadedState) + } + + self.lastStatusUpdateTimestamp = nil + self.tick() + } + } + + fileprivate func play() { + assert(self.queue.isCurrent()) + + switch self.state { + case .empty: + self.lastStatusUpdateTimestamp = nil + if self.enableSound { + let queue = self.queue + let renderer = MediaPlayerAudioRenderer( playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, volume: self.volume, updatedRate: { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.tick() + } + } + }, audioPaused: { [weak self] in + queue.async { + if let strongSelf = self { + if strongSelf.enableSound { + strongSelf.pause(lostAudioSession: true) + } else { + strongSelf.seek(timestamp: 0.0, action: .play) + } + } + } + }) + self.audioRenderer = MediaPlayerAudioRendererContext(renderer: renderer) + renderer.start() + } + self.seek(timestamp: 0.0, action: .play) + case let .seeking(frameSource, timestamp, disposable, _, enableSound): + self.state = .seeking(frameSource: frameSource, timestamp: timestamp, disposable: disposable, action: .play, enableSound: enableSound) + self.lastStatusUpdateTimestamp = nil + case let .paused(loadedState): + if loadedState.lostAudioSession { + let timestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + self.seek(timestamp: timestamp, action: .play) + } else { + self.lastStatusUpdateTimestamp = nil + if self.stoppedAtEnd { + self.seek(timestamp: 0.0, action: .play) + } else { + self.state = .playing(loadedState) + self.tick() + } + } + case .playing: + break + } + } + + fileprivate func playOnceWithSound(playAndRecord: Bool) { + assert(self.queue.isCurrent()) + + if !self.enableSound { + self.lastStatusUpdateTimestamp = nil + self.enableSound = true + self.playAndRecord = playAndRecord + self.seek(timestamp: 0.0, action: .play) + } + } + + fileprivate func toggleSoundEnabled() { + assert(self.queue.isCurrent()) + + var loadedState: MediaPlayerLoadedState? + switch self.state { + case .empty: + break + case let .playing(currentLoadedState): + loadedState = currentLoadedState + case let .paused(currentLoadedState): + loadedState = currentLoadedState + case let .seeking(_, timestamp, disposable, action, _): + self.state = .empty + disposable.dispose() + self.enableSound = !self.enableSound + self.seek(timestamp: timestamp, action: action) + } + + if let loadedState = loadedState { + self.enableSound = !self.enableSound + let timestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + self.lastStatusUpdateTimestamp = timestamp + self.seek(timestamp: timestamp) + } + + } + + fileprivate func continuePlayingWithoutSound() { + if self.enableSound { + self.lastStatusUpdateTimestamp = nil + + var loadedState: MediaPlayerLoadedState? + switch self.state { + case .empty: + break + case let .playing(currentLoadedState): + loadedState = currentLoadedState + case let .paused(currentLoadedState): + loadedState = currentLoadedState + case let .seeking(_, timestamp, disposable, action, _): + if self.enableSound { + self.state = .empty + disposable.dispose() + self.enableSound = false + self.seek(timestamp: timestamp, action: action) + } + } + + if let loadedState = loadedState { + self.enableSound = false + self.playAndRecord = false + let timestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + self.seek(timestamp: timestamp, action: .play) + } + } + } + + fileprivate func setBaseRate(_ baseRate: Double) { + self.baseRate = baseRate + self.lastStatusUpdateTimestamp = nil + self.tick() + self.audioRenderer?.renderer.setBaseRate(baseRate) + } + + fileprivate func setForceAudioToSpeaker(_ value: Bool) { + if self.forceAudioToSpeaker != value { + self.forceAudioToSpeaker = value + + self.audioRenderer?.renderer.setForceAudioToSpeaker(value) + } + } + + fileprivate func setKeepAudioSessionWhilePaused(_ value: Bool) { + if self.keepAudioSessionWhilePaused != value { + self.keepAudioSessionWhilePaused = value + + var isPlaying = false + switch self.state { + case .playing: + isPlaying = true + case let .seeking(_, _, _, action, _): + switch action { + case .play: + isPlaying = true + default: + break + } + default: + break + } + if value && !isPlaying { + self.audioRenderer?.renderer.stop() + } else { + self.audioRenderer?.renderer.start() + } + } + } + + fileprivate func pause(lostAudioSession: Bool) { + assert(self.queue.isCurrent()) + + switch self.state { + case .empty: + break + case let .seeking(frameSource, timestamp, disposable, _, enableSound): + self.state = .seeking(frameSource: frameSource, timestamp: timestamp, disposable: disposable, action: .pause, enableSound: enableSound) + self.lastStatusUpdateTimestamp = nil + case let .paused(loadedState): + if lostAudioSession { + loadedState.lostAudioSession = true + } + case let .playing(loadedState): + if lostAudioSession { + loadedState.lostAudioSession = true + } + self.state = .paused(loadedState) + self.lastStatusUpdateTimestamp = nil + self.tick() + } + } + + + fileprivate func togglePlayPause() { + assert(self.queue.isCurrent()) + + switch self.state { + case .empty: + self.play() + case let .seeking(_, _, _, action, _): + switch action { + case .play: + self.pause(lostAudioSession: false) + case .pause: + self.play() + } + case .paused: + self.play() + case .playing: + self.pause(lostAudioSession: false) + } + } + + fileprivate func setVolume(_ volume: Float) { + assert(self.queue.isCurrent()) + self.volume = volume + audioRenderer?.renderer.setVolume(volume) + } + fileprivate func toggleVolumeOnOff() { + assert(self.queue.isCurrent()) + if self.volume > 0 { + self.volume = 0 + } else { + self.volume = 1.0 + } + audioRenderer?.renderer.setVolume(volume) + } + + + fileprivate func getVolume(_ completion: @escaping(Float) -> Void) { + assert(self.queue.isCurrent()) + completion(volume) + } + + + private func tick() { + self.tickTimer?.invalidate() + + var maybeLoadedState: MediaPlayerLoadedState? + + switch self.state { + case .empty: + return + case let .paused(state): + maybeLoadedState = state + case let .playing(state): + maybeLoadedState = state + case .seeking: + return + } + + guard let loadedState = maybeLoadedState else { + return + } + + let timestamp = CMTimeGetSeconds(CMTimebaseGetTime(loadedState.controlTimebase.timebase)) + if traceEvents { + print("tick at \(timestamp)") + } + + var duration: Double = 0.0 + var videoStatus: MediaTrackFrameBufferStatus? + if let videoTrackFrameBuffer = loadedState.mediaBuffers.videoBuffer { + videoStatus = videoTrackFrameBuffer.status(at: timestamp) + duration = max(duration, CMTimeGetSeconds(videoTrackFrameBuffer.duration)) + } + + var audioStatus: MediaTrackFrameBufferStatus? + if let audioTrackFrameBuffer = loadedState.mediaBuffers.audioBuffer { + audioStatus = audioTrackFrameBuffer.status(at: timestamp) + duration = max(duration, CMTimeGetSeconds(audioTrackFrameBuffer.duration)) + } + + var performActionAtEndNow = false + + var worstStatus: MediaTrackFrameBufferStatus? + for status in [videoStatus, audioStatus] { + if let status = status { + if let worst = worstStatus { + switch status { + case .buffering: + worstStatus = status + case let .full(currentFullUntil): + switch worst { + case .buffering: + worstStatus = worst + case let .full(worstFullUntil): + if currentFullUntil < worstFullUntil { + worstStatus = status + } else { + worstStatus = worst + } + case .finished: + worstStatus = status + } + case let .finished(currentFinishedAt): + switch worst { + case .buffering, .full: + worstStatus = worst + case let .finished(worstFinishedAt): + if currentFinishedAt < worstFinishedAt { + worstStatus = worst + } else { + worstStatus = status + } + } + } + } else { + worstStatus = status + } + } + } + + var rate: Double + var buffering = false + + if let worstStatus = worstStatus, case let .full(fullUntil) = worstStatus, fullUntil.isFinite { + if case .playing = self.state { + rate = self.baseRate + + let nextTickDelay = max(0.0, fullUntil - timestamp) / self.baseRate + let tickTimer = SwiftSignalKit.Timer(timeout: nextTickDelay, repeat: false, completion: { [weak self] in + self?.tick() + }, queue: self.queue) + self.tickTimer = tickTimer + tickTimer.start() + } else { + rate = 0.0 + } + } else if let worstStatus = worstStatus, case let .finished(finishedAt) = worstStatus, finishedAt.isFinite { + let nextTickDelay = max(0.0, finishedAt - timestamp) / self.baseRate + if nextTickDelay.isLessThanOrEqualTo(0.0) { + rate = 0.0 + performActionAtEndNow = true + } else { + if case .playing = self.state { + rate = self.baseRate + + let tickTimer = SwiftSignalKit.Timer(timeout: nextTickDelay, repeat: false, completion: { [weak self] in + self?.tick() + }, queue: self.queue) + self.tickTimer = tickTimer + tickTimer.start() + } else { + rate = 0.0 + } + } + } else { + buffering = true + rate = 0.0 + } + + var reportRate = rate + + if loadedState.controlTimebase.isAudio { + if rate.isEqual(to: 1.0) { + self.audioRenderer?.renderer.start() + } + self.audioRenderer?.renderer.setRate(rate) + if rate.isEqual(to: 1.0), let audioRenderer = self.audioRenderer { + let timebaseRate = CMTimebaseGetRate(audioRenderer.renderer.audioTimebase) + if !timebaseRate.isEqual(to: rate) { + reportRate = timebaseRate + } + } + } else { + if !CMTimebaseGetRate(loadedState.controlTimebase.timebase).isEqual(to: rate) { + CMTimebaseSetRate(loadedState.controlTimebase.timebase, rate: rate) + } + } + + if let videoTrackFrameBuffer = loadedState.mediaBuffers.videoBuffer, videoTrackFrameBuffer.hasFrames { + self.videoRenderer.state = (loadedState.controlTimebase.timebase, true, videoTrackFrameBuffer.rotationAngle, videoTrackFrameBuffer.aspect) + } + + if let audioRenderer = self.audioRenderer, let audioTrackFrameBuffer = loadedState.mediaBuffers.audioBuffer, audioTrackFrameBuffer.hasFrames { + let queue = self.queue + audioRenderer.requestedFrames = true + audioRenderer.renderer.beginRequestingFrames(queue: queue.queue, takeFrame: { [weak audioTrackFrameBuffer] in + assert(queue.isCurrent()) + if let audioTrackFrameBuffer = audioTrackFrameBuffer { + return audioTrackFrameBuffer.takeFrame() + } else { + return .noFrames + } + }) + } + + var statusTimestamp = CACurrentMediaTime() + let playbackStatus: MediaPlayerPlaybackStatus + if buffering { + var whilePlaying = false + if case .playing = self.state { + whilePlaying = true + } + playbackStatus = .buffering(initial: false, whilePlaying: whilePlaying) + } else if !rate.isZero { + if reportRate.isZero { + //playbackStatus = .buffering(initial: false, whilePlaying: true) + playbackStatus = .playing + statusTimestamp = 0.0 + } else { + playbackStatus = .playing + } + } else { + playbackStatus = .paused + } + if self.lastStatusUpdateTimestamp == nil || self.lastStatusUpdateTimestamp! < statusTimestamp + 500 { + lastStatusUpdateTimestamp = statusTimestamp + var reportTimestamp = timestamp + if case .seeking(_, timestamp, _, _, _) = self.state { + reportTimestamp = timestamp + } + let status = MediaPlayerStatus(generationTimestamp: statusTimestamp, duration: duration, dimensions: CGSize(), timestamp: min(max(reportTimestamp, 0.0), duration), baseRate: self.baseRate, volume: self.volume, seekId: self.seekId, status: playbackStatus) + self.playerStatus.set(status) + } + + + if performActionAtEndNow { + switch self.actionAtEnd { + case let .loop(f): + self.stoppedAtEnd = false + self.seek(timestamp: 0.0, action: .play) + f?() + case .stop: + self.stoppedAtEnd = true + self.pause(lostAudioSession: false) + case let .action(f): + self.stoppedAtEnd = true + // self.pause(lostAudioSession: false) + f() + case let .loopDisablingSound(f): + self.stoppedAtEnd = false + self.enableSound = false + self.seek(timestamp: 0.0, action: .play) + f() + } + } else { + self.stoppedAtEnd = false + } + } +} + +enum MediaPlayerPlaybackStatus: Equatable { + case playing + case paused + case buffering(initial: Bool, whilePlaying: Bool) + + static func ==(lhs: MediaPlayerPlaybackStatus, rhs: MediaPlayerPlaybackStatus) -> Bool { + switch lhs { + case .playing: + if case .playing = rhs { + return true + } else { + return false + } + case .paused: + if case .paused = rhs { + return true + } else { + return false + } + case let .buffering(initial, whilePlaying): + if case .buffering(initial, whilePlaying) = rhs { + return true + } else { + return false + } + } + } +} + +struct MediaPlayerStatus: Equatable { + let generationTimestamp: Double + let duration: Double + let dimensions: CGSize + let timestamp: Double + let baseRate: Double + let volume: Float + let seekId: Int + let status: MediaPlayerPlaybackStatus +} + +final class MediaPlayer { + private let queue = Queue() + private var contextRef: Unmanaged? + + private let timebasePromise:Promise = Promise() + + var timebase: Signal { + return timebasePromise.get() + } + + private let statusValue = ValuePromise(ignoreRepeated: true) + + var status: Signal { + return self.statusValue.get() + } + + var actionAtEnd: MediaPlayerActionAtEnd = .stop { + didSet { + let value = self.actionAtEnd + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.actionAtEnd = value + } + } + } + } + + init(postbox: Postbox, reference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, volume: Float = 0.8, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true, initialTimebase: CMTimebase? = nil) { + self.queue.async { + self.statusValue.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: baseRate, volume: volume, seekId: 0, status: .paused)) + let context = MediaPlayerContext(queue: self.queue, playerStatus: self.statusValue, postbox: postbox, resourceReference: reference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, volume: volume, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused, initialTimebase: initialTimebase) + self.contextRef = Unmanaged.passRetained(context) + self.timebasePromise.set(context.timebasePromise.get()) + } + } + + + + deinit { + let contextRef = self.contextRef + self.queue.async { + contextRef?.release() + } + } + + func play() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.play() + } + } + } + + func playOnceWithSound(playAndRecord: Bool) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.playOnceWithSound(playAndRecord: playAndRecord) + } + } + } + + func toggleSoundEnabled() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.toggleSoundEnabled() + } + } + } + + func continuePlayingWithoutSound() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.continuePlayingWithoutSound() + } + } + } + + func setForceAudioToSpeaker(_ value: Bool) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.setForceAudioToSpeaker(value) + } + } + } + + func setKeepAudioSessionWhilePaused(_ value: Bool) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.setKeepAudioSessionWhilePaused(value) + } + } + } + + func pause() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.pause(lostAudioSession: false) + } + } + } + + func togglePlayPause() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.togglePlayPause() + } + } + } + + func setVolume(_ volume: Float) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.setVolume(volume) + } + } + } + + func toggleVolumeOnOff() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.toggleVolumeOnOff() + } + } + } + + func getVolume(_ completion: @escaping(Float) -> Void) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.getVolume(completion) + } + } + } + + func seek(timestamp: Double) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.seek(timestamp: timestamp) + } + } + } + + + func setBaseRate(_ baseRate: Double) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.setBaseRate(baseRate) + } + } + } + + func attachPlayerView(_ node: MediaPlayerView) { + let nodeRef: Unmanaged = Unmanaged.passRetained(node) + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.videoRenderer.attachNodeAndRelease(nodeRef) + } else { + Queue.mainQueue().async { + nodeRef.release() + } + } + } + } +} diff --git a/Telegram-Mac/MediaPlayerAudioRenderer.swift b/Telegram-Mac/MediaPlayerAudioRenderer.swift new file mode 100755 index 0000000000..de71edd4a8 --- /dev/null +++ b/Telegram-Mac/MediaPlayerAudioRenderer.swift @@ -0,0 +1,824 @@ +import Foundation +import SwiftSignalKit +import CoreMedia +import AVFoundation +import TelegramCore +import SyncCore + +private enum AudioPlayerRendererState { + case paused + case playing(rate: Double, didSetRate: Bool) +} + +private final class AudioPlayerRendererBufferContext { + var state: AudioPlayerRendererState = .paused + let timebase: CMTimebase + let buffer: RingByteBuffer + var bufferMaxChannelSampleIndex: Int64 = 0 + var lowWaterSize: Int + var notifyLowWater: () -> Void + var updatedRate: () -> Void + var notifiedLowWater = false + var overflowData = Data() + var overflowDataMaxChannelSampleIndex: Int64 = 0 + var renderTimestampTick: Int64 = 0 + + init(timebase: CMTimebase, buffer: RingByteBuffer, lowWaterSize: Int, notifyLowWater: @escaping () -> Void, updatedRate: @escaping () -> Void) { + self.timebase = timebase + self.buffer = buffer + self.lowWaterSize = lowWaterSize + self.notifyLowWater = notifyLowWater + self.updatedRate = updatedRate + } +} + +private let audioPlayerRendererBufferContextMap = Atomic<[Int32: Atomic]>(value: [:]) +private let audioPlayerRendererQueue = Queue() + +private var _nextPlayerRendererBufferContextId: Int32 = 1 +private func registerPlayerRendererBufferContext(_ context: Atomic) -> Int32 { + var id: Int32 = 0 + + let _ = audioPlayerRendererBufferContextMap.modify { contextMap in + id = _nextPlayerRendererBufferContextId + _nextPlayerRendererBufferContextId += 1 + + var contextMap = contextMap + contextMap[id] = context + return contextMap + } + return id +} + +private func unregisterPlayerRendererBufferContext(_ id: Int32) { + let _ = audioPlayerRendererBufferContextMap.modify { contextMap in + var contextMap = contextMap + let _ = contextMap.removeValue(forKey: id) + return contextMap + } +} + +private func withPlayerRendererBuffer(_ id: Int32, _ f: (Atomic) -> Void) { + audioPlayerRendererBufferContextMap.with { contextMap in + if let context = contextMap[id] { + f(context) + } + } +} + +private let kOutputBus: UInt32 = 0 +private let kInputBus: UInt32 = 1 + +private func rendererInputProc(refCon: UnsafeMutableRawPointer, ioActionFlags: UnsafeMutablePointer, inTimeStamp: UnsafePointer, inBusNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer?) -> OSStatus { + guard let ioData = ioData else { + return noErr + } + + let bufferList = UnsafeMutableAudioBufferListPointer(ioData) + + var rendererFillOffset = (0, 0) + var notifyLowWater: (() -> Void)? + var updatedRate: (() -> Void)? + + withPlayerRendererBuffer(Int32(intptr_t(bitPattern: refCon)), { context in + context.with { context in + switch context.state { + case let .playing(rate, didSetRate): + if context.buffer.availableBytes != 0 { + let sampleIndex = context.bufferMaxChannelSampleIndex - Int64(context.buffer.availableBytes / (2 * + 2)) + + if !didSetRate { + context.state = .playing(rate: rate, didSetRate: true) + let masterClock: CMClockOrTimebase = CMTimebaseGetMaster(context.timebase)! + CMTimebaseSetRateAndAnchorTime(context.timebase, rate: rate, anchorTime: CMTimeMake(value: sampleIndex, timescale: 44100), immediateMasterTime: CMSyncGetTime(masterClock)) + updatedRate = context.updatedRate + } else { + context.renderTimestampTick += 1 + if context.renderTimestampTick % 1000 == 0 { + let delta = (Double(sampleIndex) / 44100.0) - CMTimeGetSeconds(CMTimebaseGetTime(context.timebase)) + if delta > 0.01 { + CMTimebaseSetTime(context.timebase, time: CMTimeMake(value: sampleIndex, timescale: 44100)) + updatedRate = context.updatedRate + } + } + } + + let rendererBuffer = context.buffer + + while rendererFillOffset.0 < bufferList.count { + if let bufferData = bufferList[rendererFillOffset.0].mData { + let bufferDataSize = Int(bufferList[rendererFillOffset.0].mDataByteSize) + + let dataOffset = rendererFillOffset.1 + if dataOffset == bufferDataSize { + rendererFillOffset = (rendererFillOffset.0 + 1, 0) + continue + } + + let consumeCount = bufferDataSize - dataOffset + + let actualConsumedCount = rendererBuffer.dequeue(bufferData.advanced(by: dataOffset), count: consumeCount) + rendererFillOffset.1 += actualConsumedCount + + if actualConsumedCount == 0 { + break + } + } else { + break + } + } + } + + if !context.notifiedLowWater { + let availableBytes = context.buffer.availableBytes + if availableBytes <= context.lowWaterSize { + context.notifiedLowWater = true + notifyLowWater = context.notifyLowWater + } + } + case .paused: + break + } + } + }) + + for i in rendererFillOffset.0 ..< bufferList.count { + var dataOffset = 0 + if i == rendererFillOffset.0 { + dataOffset = rendererFillOffset.1 + } + if let data = bufferList[i].mData { + memset(data.advanced(by: dataOffset), 0, Int(bufferList[i].mDataByteSize) - dataOffset) + } + } + + if let notifyLowWater = notifyLowWater { + notifyLowWater() + } + + if let updatedRate = updatedRate { + updatedRate() + } + + return noErr +} + +private struct RequestingFramesContext { + let queue: DispatchQueue + let takeFrame: () -> MediaTrackFrameResult +} + +private final class AudioPlayerRendererContext { + let audioStreamDescription: AudioStreamBasicDescription + let bufferSizeInSeconds: Int = 5 + let lowWaterSizeInSeconds: Int = 2 + + let controlTimebase: CMTimebase + let updatedRate: () -> Void + let audioPaused: () -> Void + + var paused = true + var baseRate: Double + var volume: Float + + var audioGraph: AUGraph? + var timePitchAudioUnit: AudioComponentInstance? + var outputAudioUnit: AudioComponentInstance? + + var bufferContextId: Int32! + let bufferContext: Atomic + + var requestingFramesContext: RequestingFramesContext? + + let audioSessionDisposable = MetaDisposable() + var audioSessionControl: ManagedAudioSessionControl? + let playAndRecord: Bool + var forceAudioToSpeaker: Bool { + didSet { + if self.forceAudioToSpeaker != oldValue { + if let audioSessionControl = self.audioSessionControl { + audioSessionControl.setOutputMode(self.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system) + } + } + } + } + + init(controlTimebase: CMTimebase, playAndRecord: Bool, forceAudioToSpeaker: Bool, baseRate: Double, volume: Float, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + assert(audioPlayerRendererQueue.isCurrent()) + + self.forceAudioToSpeaker = forceAudioToSpeaker + self.baseRate = baseRate + + self.controlTimebase = controlTimebase + self.updatedRate = updatedRate + self.audioPaused = audioPaused + self.volume = volume + self.playAndRecord = playAndRecord + + self.audioStreamDescription = audioRendererNativeStreamDescription() + + let bufferSize = Int(self.audioStreamDescription.mSampleRate) * self.bufferSizeInSeconds * Int(self.audioStreamDescription.mBytesPerFrame) + let lowWaterSize = Int(self.audioStreamDescription.mSampleRate) * self.lowWaterSizeInSeconds * Int(self.audioStreamDescription.mBytesPerFrame) + + var notifyLowWater: () -> Void = { } + + self.bufferContext = Atomic(value: AudioPlayerRendererBufferContext(timebase: controlTimebase, buffer: RingByteBuffer(size: bufferSize), lowWaterSize: lowWaterSize, notifyLowWater: { + notifyLowWater() + }, updatedRate: { + updatedRate() + })) + self.bufferContextId = registerPlayerRendererBufferContext(self.bufferContext) + + notifyLowWater = { [weak self] in + audioPlayerRendererQueue.async { + if let strongSelf = self { + strongSelf.checkBuffer() + } + } + } + } + + deinit { + // assert(audioPlayerRendererQueue.isCurrent()) + + self.audioSessionDisposable.dispose() + + unregisterPlayerRendererBufferContext(self.bufferContextId) + + self.closeAudioUnit() + } + + fileprivate func setBaseRate(_ baseRate: Double) { + if let timePitchAudioUnit = self.timePitchAudioUnit, !self.baseRate.isEqual(to: baseRate) { + self.baseRate = baseRate + AudioUnitSetParameter(timePitchAudioUnit, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, Float32(baseRate), 0) + self.bufferContext.with { context in + if case .playing = context.state { + context.state = .playing(rate: baseRate, didSetRate: false) + } + } + } + } + + fileprivate func setRate(_ rate: Double) { + assert(audioPlayerRendererQueue.isCurrent()) + + if !rate.isZero && self.paused { + self.start() + } + + let baseRate = self.baseRate + + self.bufferContext.with { context in + if !rate.isZero { + if case .playing = context.state { + } else { + context.state = .playing(rate: baseRate, didSetRate: false) + } + } else { + context.state = .paused + CMTimebaseSetRate(context.timebase, rate: 0.0) + } + } + } + + fileprivate func flushBuffers(at timestamp: CMTime, completion: () -> Void) { + assert(audioPlayerRendererQueue.isCurrent()) + + self.bufferContext.with { context in + context.buffer.clear() + context.bufferMaxChannelSampleIndex = 0 + context.notifiedLowWater = false + context.overflowData = Data() + context.overflowDataMaxChannelSampleIndex = 0 + CMTimebaseSetTime(context.timebase, time: timestamp) + + switch context.state { + case let .playing(rate, _): + context.state = .playing(rate: rate, didSetRate: false) + case .paused: + break + } + } + + completion() + } + + fileprivate func start() { + assert(audioPlayerRendererQueue.isCurrent()) + + if self.paused { + self.paused = false + self.startAudioUnit() + } + } + + fileprivate func stop() { + assert(audioPlayerRendererQueue.isCurrent()) + + if !self.paused { + self.paused = true + self.setRate(0.0) + self.closeAudioUnit() + } + } + + private func startAudioUnit() { + assert(audioPlayerRendererQueue.isCurrent()) + + if self.audioGraph == nil { + var maybeAudioGraph: AUGraph? + guard NewAUGraph(&maybeAudioGraph) == noErr, let audioGraph = maybeAudioGraph else { + return + } + + var converterNode: AUNode = 0 + var converterDescription = AudioComponentDescription() + converterDescription.componentType = kAudioUnitType_FormatConverter + converterDescription.componentSubType = kAudioUnitSubType_AUConverter + converterDescription.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &converterDescription, &converterNode) == noErr else { + return + } + + var timePitchNode: AUNode = 0 + var timePitchDescription = AudioComponentDescription() + timePitchDescription.componentType = kAudioUnitType_FormatConverter + timePitchDescription.componentSubType = kAudioUnitSubType_AUiPodTimeOther + timePitchDescription.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &timePitchDescription, &timePitchNode) == noErr else { + return + } + + var outputNode: AUNode = 0 + var outputDesc = AudioComponentDescription() + outputDesc.componentType = kAudioUnitType_Output + outputDesc.componentSubType = kAudioUnitSubType_HALOutput + outputDesc.componentFlags = 0 + outputDesc.componentFlagsMask = 0 + outputDesc.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &outputDesc, &outputNode) == noErr else { + return + } + + guard AUGraphOpen(audioGraph) == noErr else { + return + } + + guard AUGraphConnectNodeInput(audioGraph, converterNode, 0, timePitchNode, 0) == noErr else { + return + } + + guard AUGraphConnectNodeInput(audioGraph, timePitchNode, 0, outputNode, 0) == noErr else { + return + } + + var maybeConverterAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, converterNode, &converterDescription, &maybeConverterAudioUnit) == noErr, let converterAudioUnit = maybeConverterAudioUnit else { + return + } + + var maybeTimePitchAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, timePitchNode, &timePitchDescription, &maybeTimePitchAudioUnit) == noErr, let timePitchAudioUnit = maybeTimePitchAudioUnit else { + return + } + AudioUnitSetParameter(timePitchAudioUnit, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, Float32(self.baseRate), 0) + + var maybeOutputAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, outputNode, &outputDesc, &maybeOutputAudioUnit) == noErr, let outputAudioUnit = maybeOutputAudioUnit else { + return + } + + var outputAudioFormat = audioRendererNativeStreamDescription() + + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputAudioFormat, UInt32(MemoryLayout.size)) + + var streamFormat = AudioStreamBasicDescription() + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, UInt32(MemoryLayout.size)) + AudioUnitSetProperty(timePitchAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, UInt32(MemoryLayout.size)) + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, UInt32(MemoryLayout.size)) + + var callbackStruct = AURenderCallbackStruct() + callbackStruct.inputProc = rendererInputProc + callbackStruct.inputProcRefCon = UnsafeMutableRawPointer(bitPattern: intptr_t(self.bufferContextId)) + + guard AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct) == noErr else { + return + } + + var one: UInt32 = 1 + guard AudioUnitSetProperty(outputAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &one, 4) == noErr else { + return + } + + AudioUnitSetParameter(outputAudioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, kOutputBus, max(min(1, volume), 0), 0) + +// guard AudioUnitSetParameter(outputAudioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, kOutputBus, 0.1, 0) == noErr else { +// return +// } + + var maximumFramesPerSlice: UInt32 = 4096 + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4) + AudioUnitSetProperty(timePitchAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4) + AudioUnitSetProperty(outputAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, 4) + + guard AUGraphInitialize(audioGraph) == noErr else { + return + } + + self.audioGraph = audioGraph + self.timePitchAudioUnit = timePitchAudioUnit + self.outputAudioUnit = outputAudioUnit + } + audioSessionAcquired() + } + + + + func setVolume(_ volume: Float) { + assert(audioPlayerRendererQueue.isCurrent()) + self.volume = max(min(1, volume), 0) + if let outputAudioUnit = outputAudioUnit { + AudioUnitSetParameter(outputAudioUnit, kHALOutputParam_Volume, kAudioUnitScope_Output, kOutputBus, self.volume, 0) + + } + } + + private func audioSessionAcquired() { + assert(audioPlayerRendererQueue.isCurrent()) + + if let audioGraph = self.audioGraph { + guard AUGraphStart(audioGraph) == noErr else { + self.closeAudioUnit() + return + } + } + } + + private func closeAudioUnit() { + assert(audioPlayerRendererQueue.isCurrent()) + + if let audioGraph = self.audioGraph { + var status = noErr + + self.bufferContext.with { context in + context.buffer.clear() + } + + status = AUGraphStop(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "AUGraphStop error \(status)") + } + + status = AUGraphUninitialize(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "AUGraphUninitialize error \(status)") + } + + status = AUGraphClose(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "AUGraphClose error \(status)") + } + + status = DisposeAUGraph(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "DisposeAUGraph error \(status)") + } + + self.audioGraph = nil + self.outputAudioUnit = nil + self.timePitchAudioUnit = nil + } + } + + func checkBuffer() { + assert(audioPlayerRendererQueue.isCurrent()) + + while true { + let bytesToRequest = self.bufferContext.with { context -> Int in + let availableBytes = context.buffer.availableBytes + if availableBytes <= context.lowWaterSize { + return context.buffer.size - availableBytes + } else { + return 0 + } + } + + if bytesToRequest == 0 { + self.bufferContext.with { context in + context.notifiedLowWater = false + } + break + } + + let overflowTakenLength = self.bufferContext.with { context -> Int in + let takeLength = min(context.overflowData.count, bytesToRequest) + if takeLength != 0 { + if takeLength == context.overflowData.count { + let data = context.overflowData + context.overflowData = Data() + self.enqueueSamples(data, sampleIndex: context.overflowDataMaxChannelSampleIndex - Int64(data.count / (2 * 2))) + } else { + let data = context.overflowData.subdata(in: 0 ..< takeLength) + self.enqueueSamples(data, sampleIndex: context.overflowDataMaxChannelSampleIndex - Int64(context.overflowData.count / (2 * 2))) + context.overflowData.replaceSubrange(0 ..< takeLength, with: Data()) + } + } + return takeLength + } + + if overflowTakenLength != 0 { + continue + } + + if let requestingFramesContext = self.requestingFramesContext { + requestingFramesContext.queue.async { + let takenFrame = requestingFramesContext.takeFrame() + audioPlayerRendererQueue.async { + switch takenFrame { + case let .frame(frame): + if let dataBuffer = CMSampleBufferGetDataBuffer(frame.sampleBuffer) { + let dataLength = CMBlockBufferGetDataLength(dataBuffer) + let takeLength = min(dataLength, bytesToRequest) + + let pts = CMSampleBufferGetPresentationTimeStamp(frame.sampleBuffer) + let bufferSampleIndex = CMTimeConvertScale(pts, timescale: 44100, method: .roundAwayFromZero).value + + let bytes = malloc(takeLength)! + CMBlockBufferCopyDataBytes(dataBuffer, atOffset: 0, dataLength: takeLength, destination: bytes) + self.enqueueSamples(Data(bytesNoCopy: bytes.assumingMemoryBound(to: UInt8.self), count: takeLength, deallocator: .free), sampleIndex: bufferSampleIndex) + + if takeLength < dataLength { + self.bufferContext.with { context in + let copyOffset = context.overflowData.count + context.overflowData.count += dataLength - takeLength + context.overflowData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + CMBlockBufferCopyDataBytes(dataBuffer, atOffset: takeLength, dataLength: dataLength - takeLength, destination: bytes.advanced(by: copyOffset)) + } + } + } + + self.checkBuffer() + } else { + assertionFailure() + } + case .restoreState: + assertionFailure() + self.checkBuffer() + break + case .skipFrame: + self.checkBuffer() + break + case .noFrames, .finished: + self.requestingFramesContext = nil + } + } + } + } else { + self.bufferContext.with { context in + context.notifiedLowWater = false + } + } + + break + } + } + + private func enqueueSamples(_ data: Data, sampleIndex: Int64) { + assert(audioPlayerRendererQueue.isCurrent()) + + self.bufferContext.with { context in + let bytesToCopy = min(context.buffer.size - context.buffer.availableBytes, data.count) + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + let _ = context.buffer.enqueue(UnsafeRawPointer(bytes), count: bytesToCopy) + context.bufferMaxChannelSampleIndex = sampleIndex + Int64(data.count / (2 * 2)) + } + } + } + + fileprivate func beginRequestingFrames(queue: DispatchQueue, takeFrame: @escaping () -> MediaTrackFrameResult) { + assert(audioPlayerRendererQueue.isCurrent()) + + if let _ = self.requestingFramesContext { + return + } + + self.requestingFramesContext = RequestingFramesContext(queue: queue, takeFrame: takeFrame) + + self.checkBuffer() + } + + func endRequestingFrames() { + assert(audioPlayerRendererQueue.isCurrent()) + + self.requestingFramesContext = nil + } +} + +private func audioRendererNativeStreamDescription() -> AudioStreamBasicDescription { + var canonicalBasicStreamDescription = AudioStreamBasicDescription() + canonicalBasicStreamDescription.mSampleRate = 44100.00 + canonicalBasicStreamDescription.mFormatID = kAudioFormatLinearPCM + canonicalBasicStreamDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked + canonicalBasicStreamDescription.mFramesPerPacket = 1 + canonicalBasicStreamDescription.mChannelsPerFrame = 2 + canonicalBasicStreamDescription.mBytesPerFrame = 2 * 2 + canonicalBasicStreamDescription.mBitsPerChannel = 8 * 2 + canonicalBasicStreamDescription.mBytesPerPacket = 2 * 2 + return canonicalBasicStreamDescription +} + +final class MediaPlayerAudioSessionCustomControl { + let activate: () -> Void + let deactivate: () -> Void + + init(activate: @escaping () -> Void, deactivate: @escaping () -> Void) { + self.activate = activate + self.deactivate = deactivate + } +} + +enum MediaPlayerAudioSessionControl { + case manager(ManagedAudioSession) + case custom((MediaPlayerAudioSessionCustomControl) -> Disposable) +} + +struct AudioAddress { + static var outputDevice = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultOutputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster) +} + +enum AudioNotification: String { + case audioDevicesDidChange + case audioInputDeviceDidChange + case audioOutputDeviceDidChange + + var stringValue: String { + return "Audio" + rawValue + } + + var notificationName: NSNotification.Name { + return NSNotification.Name(stringValue) + } +} + +struct AudioListener { + static var output: AudioObjectPropertyListenerProc = { _, _, _, _ in + NotificationCenter.default.post(name: AudioNotification.audioOutputDeviceDidChange.notificationName, object: nil) + return 0 + } +} + + +final class MediaPlayerAudioRenderer { + private var contextRef: Unmanaged? + + let audioTimebase: CMTimebase + + init(playAndRecord: Bool, forceAudioToSpeaker: Bool, baseRate: Double, volume: Float, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + var audioClock: CMClock? + + var deviceId:AudioDeviceID = AudioDeviceID() + var deviceIdRequest:AudioObjectPropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster) + var deviceIdSize:UInt32 = UInt32(MemoryLayout.size) + + _ = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &deviceIdRequest, 0, nil, &deviceIdSize, &deviceId) + + CMAudioDeviceClockCreateFromAudioDeviceID(allocator: kCFAllocatorDefault, deviceID: deviceId, clockOut: &audioClock) + + if audioClock == nil { + CMAudioDeviceClockCreate(allocator: nil, deviceUID: nil, clockOut: &audioClock) + } + + + var audioTimebase: CMTimebase? + if let audioClock = audioClock { + CMTimebaseCreateWithMasterClock(allocator: nil, masterClock: audioClock, timebaseOut: &audioTimebase) + } + + + if audioTimebase == nil { + CMTimebaseCreateWithMasterClock(allocator: nil, masterClock: CMClockGetHostTimeClock(), timebaseOut: &audioTimebase) + } + + let timebase = audioTimebase! + + + self.audioTimebase = timebase + CMTimebaseSetRate(self.audioTimebase, rate: baseRate) + audioPlayerRendererQueue.async { + let context = AudioPlayerRendererContext(controlTimebase: timebase, playAndRecord: playAndRecord, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, volume: volume, updatedRate: updatedRate, audioPaused: audioPaused) + self.contextRef = Unmanaged.passRetained(context) + context.setVolume(volume) + } + + AudioObjectAddPropertyListener(AudioObjectID(kAudioObjectSystemObject), &AudioAddress.outputDevice, AudioListener.output, nil) + + + NotificationCenter.default.addObserver(self, selector: #selector(handleNotification(_:)), name: AudioNotification.audioOutputDeviceDidChange.notificationName, object: nil) + } + + @objc private func handleNotification(_ notification: Notification) { + + audioPlayerRendererQueue.async { + + let context = self.contextRef!.takeRetainedValue() + + let newContext = AudioPlayerRendererContext(controlTimebase: context.controlTimebase, playAndRecord: false, forceAudioToSpeaker: false, baseRate: context.baseRate, volume: context.volume, updatedRate: context.updatedRate, audioPaused: context.audioPaused) + + self.contextRef = Unmanaged.passRetained(newContext) + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + AudioObjectRemovePropertyListener(AudioObjectID(kAudioObjectSystemObject), &AudioAddress.outputDevice, AudioListener.output, nil) + let contextRef = self.contextRef + audioPlayerRendererQueue.async { + contextRef?.release() + } + } + + func start() { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.start() + } + } + } + + func stop() { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.stop() + } + } + } + + func volume(_ completion: @escaping (Float) -> Void) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + completion(context.volume) + } + } + } + + func setVolume(_ volume: Float) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.setVolume(volume) + } + } + } + + func setRate(_ rate: Double) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.setRate(rate) + } + } + } + + func setBaseRate(_ baseRate: Double) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.setBaseRate(baseRate) + } + } + } + + func beginRequestingFrames(queue: DispatchQueue, takeFrame: @escaping () -> MediaTrackFrameResult) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.beginRequestingFrames(queue: queue, takeFrame: takeFrame) + } + } + } + + func flushBuffers(at timestamp: CMTime, completion: @escaping () -> Void) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.flushBuffers(at: timestamp, completion: completion) + } + } + } + + func setForceAudioToSpeaker(_ value: Bool) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.forceAudioToSpeaker = value + } + } + } +} diff --git a/Telegram-Mac/MediaPlayerFramePreview.swift b/Telegram-Mac/MediaPlayerFramePreview.swift new file mode 100644 index 0000000000..b8ef2bb9e1 --- /dev/null +++ b/Telegram-Mac/MediaPlayerFramePreview.swift @@ -0,0 +1,144 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private final class FramePreviewContext { + let source: UniversalSoftwareVideoSource + + init(source: UniversalSoftwareVideoSource) { + self.source = source + } +} + +private func initializedPreviewContext(queue: Queue, postbox: Postbox, fileReference: FileMediaReference) -> Signal, NoError> { + return Signal { subscriber in + let source = UniversalSoftwareVideoSource(mediaBox: postbox.mediaBox, fileReference: fileReference) + let readyDisposable = (source.ready + |> filter { $0 }).start(next: { _ in + subscriber.putNext(QueueLocalObject(queue: queue, generate: { + return FramePreviewContext(source: source) + })) + }) + + return ActionDisposable { + readyDisposable.dispose() + } + } +} + +public enum MediaPlayerFramePreviewResult { + case image(CGImage) + case waitingForData +} + +private final class MediaPlayerFramePreviewImpl { + private let queue: Queue + private let context: Promise> + private let currentFrameDisposable = MetaDisposable() + private var currentFrameTimestamp: Double? + private var nextFrameTimestamp: Double? + fileprivate let framePipe = ValuePipe() + + init(queue: Queue, postbox: Postbox, fileReference: FileMediaReference) { + self.queue = queue + self.context = Promise() + self.context.set(initializedPreviewContext(queue: queue, postbox: postbox, fileReference: fileReference)) + } + + deinit { + assert(self.queue.isCurrent()) + self.currentFrameDisposable.dispose() + } + + func generateFrame(at timestamp: Double) { + if self.currentFrameTimestamp != nil { + self.nextFrameTimestamp = timestamp + return + } + self.currentFrameTimestamp = timestamp + let queue = self.queue + let takeDisposable = MetaDisposable() + let disposable = (self.context.get() + |> take(1)).start(next: { [weak self] context in + queue.justDispatch { + guard context.queue === queue else { + return + } + context.with { context in + let disposable = (context.source.takeFrame(at: timestamp)).start(next: { result in + queue.async { + guard let strongSelf = self else { + return + } + switch result { + case .waitingForData: + strongSelf.framePipe.putNext(.waitingForData) + case let .image(image): + if let image = image { + strongSelf.framePipe.putNext(.image(image)) + } + strongSelf.currentFrameTimestamp = nil + if let nextFrameTimestamp = strongSelf.nextFrameTimestamp { + strongSelf.nextFrameTimestamp = nil + strongSelf.generateFrame(at: nextFrameTimestamp) + } + } + } + + }) + takeDisposable.set(disposable) + } + } + }) + self.currentFrameDisposable.set(ActionDisposable { + takeDisposable.dispose() + disposable.dispose() + }) + } + + func cancelPendingFrames() { + self.nextFrameTimestamp = nil + self.currentFrameTimestamp = nil + self.currentFrameDisposable.set(nil) + } +} + +public final class MediaPlayerFramePreview { + private let queue: Queue + private let impl: QueueLocalObject + + public var generatedFrames: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.framePipe.signal().start(next: { result in + subscriber.putNext(result) + })) + } + return disposable + } + } + + public init(postbox: Postbox, fileReference: FileMediaReference) { + let queue = Queue() + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return MediaPlayerFramePreviewImpl(queue: queue, postbox: postbox, fileReference: fileReference) + }) + } + + public func generateFrame(at timestamp: Double) { + self.impl.with { impl in + impl.generateFrame(at: timestamp) + } + } + + public func cancelPendingFrames() { + self.impl.with { impl in + impl.cancelPendingFrames() + } + } +} diff --git a/Telegram-Mac/MediaPlayerView.swift b/Telegram-Mac/MediaPlayerView.swift new file mode 100644 index 0000000000..dbd17fd804 --- /dev/null +++ b/Telegram-Mac/MediaPlayerView.swift @@ -0,0 +1,369 @@ +// +// MediaPlayerView.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/11/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Foundation +import TGUIKit +import SwiftSignalKit +import AVFoundation + +private final class MediaPlayerViewLayer: AVSampleBufferDisplayLayer { + override func action(forKey event: String) -> CAAction? { + return NSNull() + } +} + +private final class MediaPlayerViewDisplayView: View { + var updateInHierarchy: ((Bool) -> Void)? + + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + self.updateInHierarchy?(superview != nil) + } +} + +private enum PollStatus: CustomStringConvertible { + case delay(Double) + case finished + + var description: String { + switch self { + case let .delay(value): + return "delay(\(value))" + case .finished: + return "finished" + } + } +} + +final class MediaPlayerView: View { + var videoInHierarchy: Bool = false + var updateVideoInHierarchy: ((Bool) -> Void)? + + private var videoNode: MediaPlayerViewDisplayView + + private var videoLayer: AVSampleBufferDisplayLayer? + + private let videoQueue: Queue + + + func setVideoLayerGravity(_ gravity: AVLayerVideoGravity) { + videoLayer?.videoGravity = gravity + } + + var takeFrameAndQueue: (Queue, () -> MediaTrackFrameResult)? + var timer: SwiftSignalKit.Timer? + var polling = false + + var currentRotationAngle = 0.0 + var currentAspect = 1.0 + + var state: (timebase: CMTimebase, requestFrames: Bool, rotationAngle: Double, aspect: Double)? { + didSet { + self.updateState() + } + } + + private let maskLayer = CAShapeLayer() + + var positionFlags: LayoutPositionFlags? { + didSet { + if let positionFlags = positionFlags { + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = .cornerRadius + var bottomLeftRadius: CGFloat = .cornerRadius + var topRightRadius: CGFloat = .cornerRadius + var bottomRightRadius: CGFloat = .cornerRadius + + + if positionFlags.contains(.bottom) && positionFlags.contains(.left) { + topLeftRadius = topLeftRadius * 3 + 2 + } + if positionFlags.contains(.bottom) && positionFlags.contains(.right) { + topRightRadius = topRightRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.left) { + bottomLeftRadius = bottomLeftRadius * 3 + 2 + } + if positionFlags.contains(.top) && positionFlags.contains(.right) { + bottomRightRadius = bottomRightRadius * 3 + 2 + } + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topRightRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topLeftRadius) + + maskLayer.path = path + layer?.mask = maskLayer + } else { + layer?.mask = nil + } + } + } + + private func updateState() { + if let (timebase, requestFrames, rotationAngle, aspect) = self.state { + if let videoLayer = self.videoLayer { + videoQueue.async { + if videoLayer.controlTimebase !== timebase || videoLayer.status == .failed { + videoLayer.flush() + videoLayer.controlTimebase = timebase + } + } + + if !self.currentRotationAngle.isEqual(to: rotationAngle) || !self.currentAspect.isEqual(to: aspect) { + self.currentRotationAngle = rotationAngle + self.currentAspect = aspect + var transform = CGAffineTransform(rotationAngle: CGFloat(rotationAngle)) + if !rotationAngle.isZero { + transform = transform.scaledBy(x: CGFloat(aspect), y: CGFloat(1.0 / aspect)) + } + videoLayer.setAffineTransform(transform) + } + + if self.videoInHierarchy { + if requestFrames { + self.startPolling() + } + } + } + } + } + + private func startPolling() { + if !self.polling { + self.polling = true + self.poll(completion: { [weak self] status in + self?.polling = false + + if let strongSelf = self, let (_, requestFrames, _, _) = strongSelf.state, requestFrames { + strongSelf.timer?.invalidate() + switch status { + case let .delay(delay): + strongSelf.timer = SwiftSignalKit.Timer(timeout: delay, repeat: true, completion: { + if let strongSelf = self, let videoLayer = strongSelf.videoLayer, let (_, requestFrames, _, _) = strongSelf.state, requestFrames, strongSelf.videoInHierarchy { + if videoLayer.isReadyForMoreMediaData { + strongSelf.timer?.invalidate() + strongSelf.timer = nil + strongSelf.startPolling() + } + } + }, queue: Queue.mainQueue()) + strongSelf.timer?.start() + case .finished: + break + } + } + }) + } + } + + private func poll(completion: @escaping (PollStatus) -> Void) { + if let (takeFrameQueue, takeFrame) = self.takeFrameAndQueue, let videoLayer = self.videoLayer, let (timebase, _, _, _) = self.state { + let layerRef = Unmanaged.passRetained(videoLayer) + takeFrameQueue.async { + let status: PollStatus + do { + var numFrames = 0 + let layer = layerRef.takeUnretainedValue() + let layerTime = CMTimeGetSeconds(CMTimebaseGetTime(timebase)) + var maxTakenTime = layerTime + 0.1 + var finised = false + loop: while true { + let isReady = layer.isReadyForMoreMediaData + + if isReady { + switch takeFrame() { + case let .restoreState(frames, atTime): + layer.flush() + for i in 0 ..< frames.count { + let frame = frames[i] + let frameTime = CMTimeGetSeconds(frame.position) + maxTakenTime = frameTime + let attachments = CMSampleBufferGetSampleAttachmentsArray(frame.sampleBuffer, createIfNecessary: true)! as NSArray + let dict = attachments[0] as! NSMutableDictionary + if i == 0 { + CMSetAttachment(frame.sampleBuffer, key: kCMSampleBufferAttachmentKey_ResetDecoderBeforeDecoding as NSString, value: kCFBooleanTrue as AnyObject, attachmentMode: kCMAttachmentMode_ShouldPropagate) + CMSetAttachment(frame.sampleBuffer, key: kCMSampleBufferAttachmentKey_EndsPreviousSampleDuration as NSString, value: kCFBooleanTrue as AnyObject, attachmentMode: kCMAttachmentMode_ShouldPropagate) + } + if CMTimeCompare(frame.position, atTime) < 0 { + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DoNotDisplay as NSString as String) + } else if CMTimeCompare(frame.position, atTime) == 0 { + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String) + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleBufferAttachmentKey_EndsPreviousSampleDuration as NSString as String) + //print("restore state to \(frame.position) -> \(frameTime) at \(layerTime) (\(i + 1) of \(frames.count))") + } + layer.enqueue(frame.sampleBuffer) + } + + case let .frame(frame): + numFrames += 1 + let frameTime = CMTimeGetSeconds(frame.position) + if frame.resetDecoder { + layer.flush() + } + + if frame.decoded && frameTime < layerTime { + continue loop + } + + //print("took frame at \(frameTime) current \(layerTime)") + maxTakenTime = frameTime + layer.enqueue(frame.sampleBuffer) + + + case .skipFrame: + break + case .noFrames: + finised = true + break loop + case .finished: + finised = true + break loop + } + } else { + break loop + } + } + if finised { + status = .finished + } else { + status = .delay(max(1.0 / 30.0, maxTakenTime - layerTime)) + } + //print("took \(numFrames) frames, status \(status)") + } + DispatchQueue.main.async { + layerRef.release() + + completion(status) + } + } + } + } + + var transformArguments: TransformImageArguments? { + didSet { + self.updateLayout() + } + } + + init(backgroundThread: Bool = false) { + self.videoNode = MediaPlayerViewDisplayView() + + if false && backgroundThread { + self.videoQueue = Queue() + } else { + self.videoQueue = Queue.mainQueue() + } + + super.init() + + self.videoNode.updateInHierarchy = { [weak self] value in + if let strongSelf = self { + if strongSelf.videoInHierarchy != value { + strongSelf.videoInHierarchy = value + if value { + strongSelf.updateState() + } + } + strongSelf.updateVideoInHierarchy?(value) + } + } + self.addSubview(self.videoNode) + + self.videoQueue.async { [weak self] in + let videoLayer = MediaPlayerViewLayer() + videoLayer.videoGravity = .resize + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.videoLayer = videoLayer + strongSelf.updateLayout() + strongSelf.layer?.addSublayer(videoLayer) + strongSelf.updateState() + } + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + self.videoLayer?.removeFromSuperlayer() + + if let (takeFrameQueue, _) = self.takeFrameAndQueue { + if let videoLayer = self.videoLayer { + takeFrameQueue.async { + videoLayer.flushAndRemoveImage() + + takeFrameQueue.after(1.0, { + videoLayer.flushAndRemoveImage() + }) + } + } + } + } + + override var frame: CGRect { + didSet { + if !oldValue.size.equalTo(self.frame.size) { + self.updateLayout() + } + } + } + + override func setFrameSize(_ newSize: NSSize) { + let oldValue = self.frame + super.setFrameSize(newSize) + if !oldValue.size.equalTo(self.frame.size) { + self.updateLayout() + } + } + + func updateLayout() { + let bounds = self.bounds + + let fittedRect: CGRect + if let arguments = self.transformArguments { + let drawingRect = bounds + var fittedSize = arguments.imageSize + if abs(fittedSize.width - bounds.size.width).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.width = bounds.size.width + } + if abs(fittedSize.height - bounds.size.height).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.height = bounds.size.height + } + + 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) + } else { + fittedRect = bounds + } + + if let videoLayer = self.videoLayer { + videoLayer.position = CGPoint(x: fittedRect.midX, y: fittedRect.midY) + videoLayer.bounds = CGRect(origin: CGPoint(), size: fittedRect.size) + } + } + + func reset() { + self.videoLayer?.flush() + } +} diff --git a/Telegram-Mac/MediaPreviewEditControl.swift b/Telegram-Mac/MediaPreviewEditControl.swift new file mode 100644 index 0000000000..99a024a5e4 --- /dev/null +++ b/Telegram-Mac/MediaPreviewEditControl.swift @@ -0,0 +1,81 @@ +// +// MediaPreviewEditControl.swift +// Telegram +// +// Created by Mikhail Filimonov on 09/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + +class MediaPreviewEditControl: Control { + private let crop = ImageButton() + private let delete = ImageButton() + override init() { + super.init(frame: NSMakeRect(0, 0, 60, 30)) + addSubview(crop) + addSubview(delete) + crop.set(image: theme.icons.previewSenderCrop, for: .Normal) + delete.set(image: theme.icons.previewSenderDelete, for: .Normal) + _ = crop.sizeToFit(NSZeroSize, NSMakeSize(27, frame.height), thatFit: true) + _ = delete.sizeToFit(NSZeroSize, NSMakeSize(27, frame.height), thatFit: true) + + backgroundColor = .blackTransparent + layer?.cornerRadius = frame.height / 2 + } + + func set(edit:@escaping()->Void, delete:@escaping()->Void, hasEditedData: Bool) { + self.crop.removeAllHandlers() + self.delete.removeAllHandlers() + + self.crop.isSelected = hasEditedData + + self.crop.set(handler: { _ in + edit() + }, for: .Up) + + self.delete.set(handler: { _ in + delete() + }, for: .Up) + } + + var canEdit: Bool = true { + didSet { + crop.isHidden = !canEdit + self.setFrameSize(canEdit ? 60 : 30, frame.height) + } + } + var canDelete: Bool = true { + didSet { + delete.isHidden = !canDelete + } + } + + var isInteractiveMedia: Bool = true { + didSet { + backgroundColor = isInteractiveMedia ? .blackTransparent : .clear + delete.set(image: isInteractiveMedia ? theme.icons.previewSenderDelete : theme.icons.previewSenderDeleteFile, for: .Normal) + } + } + + override func layout() { + super.layout() + if canEdit { + crop.centerY(x: 3) + delete.centerY(x: crop.frame.maxX) + } else { + delete.center() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} diff --git a/Telegram-Mac/MediaPreviewRowItem.swift b/Telegram-Mac/MediaPreviewRowItem.swift new file mode 100644 index 0000000000..28e1fb386a --- /dev/null +++ b/Telegram-Mac/MediaPreviewRowItem.swift @@ -0,0 +1,229 @@ +// +// MediaPreviewRowItem.swift +// Telegram +// +// Created by keepcoder on 19/10/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import SwiftSignalKit +import Postbox + +class MediaPreviewRowItem: TableRowItem { + + + + + let media: Media + fileprivate let context: AccountContext + private let _stableId = arc4random() + fileprivate let parameters: ChatMediaLayoutParameters? + fileprivate let chatInteraction: ChatInteraction + fileprivate let edit:()->Void + fileprivate let delete: (()->Void)? + fileprivate let hasEditedData: Bool + init(_ initialSize: NSSize, media: Media, context: AccountContext, hasEditedData: Bool = false, edit:@escaping()->Void = {}, delete: (()->Void)? = nil) { + self.edit = edit + self.delete = delete + self.media = media + self.context = context + self.hasEditedData = hasEditedData + self.chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context) + if let media = media as? TelegramMediaFile { + parameters = ChatMediaLayoutParameters.layout(for: media, isWebpage: false, chatInteraction: chatInteraction, presentation: .Empty, automaticDownload: true, isIncoming: false, autoplayMedia: AutoplayMediaPreferences.defaultSettings) + } else { + parameters = nil + } + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + + private var overSize: CGFloat? = nil + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let result = super.makeSize(width, oldWidth: oldWidth) + parameters?.makeLabelsForWidth(width - (media.isInteractiveMedia ? 20 : 120)) + + if let table = table, table.count == 1 { + if contentSize.height > table.frame.height && table.frame.height > 0 { + overSize = table.frame.height - 12 + } else { + overSize = nil + } + } + + return result + } + + override var stableId: AnyHashable { + return _stableId + } + + override var identifier: String { + return "\(ChatLayoutUtils.contentNode(for: media))" + } + + override var height: CGFloat { + return contentSize.height + 12 + } + + var contentSize: NSSize { + let contentSize = layoutSize + return NSMakeSize(width - (media.isInteractiveMedia ? 20 : 40), overSize ?? contentSize.height) + } + + override var layoutSize: NSSize { + return ChatLayoutUtils.contentSize(for: media, with: initialSize.width - (media.isInteractiveMedia ? 20 : 80)) + } + + public func contentNode() -> ChatMediaContentView.Type { + return ChatLayoutUtils.contentNode(for: media) + } + + override func viewClass() -> AnyClass { + return MediaPreviewRowView.self + } +} + +fileprivate class MediaPreviewRowView : TableRowView, ModalPreviewRowViewProtocol { + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let contentNode = contentNode { + if contentNode is ChatGIFContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, GifPreviewModalView.self), contentNode) + } + } else if contentNode is ChatInteractiveContentView { + if let image = contentNode.media as? TelegramMediaImage { + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } else if contentNode is MediaAnimatedStickerView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, AnimatedStickerPreviewModalView.self), contentNode) + } + } else if contentNode is ChatFileContentView { + if let file = contentNode.media as? TelegramMediaFile, file.isGraphicFile, let mediaId = file.id, let dimension = file.dimensions { + var representations: [TelegramMediaImageRepresentation] = [] + representations.append(contentsOf: file.previewRepresentations) + representations.append(TelegramMediaImageRepresentation(dimensions: dimension, resource: file.resource)) + let image = TelegramMediaImage(imageId: mediaId, representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: file.partialReference, flags: []) + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } + } + + return nil + } + + var contentNode:ChatMediaContentView? + let editControl: MediaPreviewEditControl = MediaPreviewEditControl() + override var needsDisplay: Bool { + get { + return super.needsDisplay + } + set { + super.needsDisplay = true + contentNode?.needsDisplay = true + } + } + + override func forceClick(in location: NSPoint) { + _ = contentNode?.previewMediaIfPossible() + } + + override func draw(_ dirtyRect: NSRect) { + + } + + override func updateMouse() { + guard let window = window, let table = item?.table else { + editControl.isHidden = true + return + } + + let row = table.row(at: table.documentView!.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + + if row == item?.index { + editControl.isHidden = false + } else { + editControl.isHidden = true + } + } + + override func shakeView() { + contentNode?.shake() + } + + override func set(item:TableRowItem, animated:Bool = false) { + super.set(item: item, animated: animated) + guard let item = item as? MediaPreviewRowItem else { return } + + if contentNode == nil || !contentNode!.isKind(of: item.contentNode()) { + self.contentNode?.removeFromSuperview() + let node = item.contentNode() + self.contentNode = node.init(frame:NSZeroRect) + self.addSubview(self.contentNode!) + addSubview(editControl) + updateMouse() + } + + + editControl.canEdit = (item.media is TelegramMediaImage) + editControl.isInteractiveMedia = item.media.isInteractiveMedia + editControl.canDelete = item.delete != nil + editControl.set(edit: { [weak item] in + item?.edit() + }, delete: { [weak item] in + item?.delete?() + }, hasEditedData: item.hasEditedData) + + self.contentNode?.update(with: item.media, size: item.contentSize, context: item.context, parent: nil, table: item.table, parameters: item.parameters, animated: animated) + + } + + override func layout() { + super.layout() + guard let contentNode = contentNode else {return} + contentNode.setFrameOrigin(12, 6) + if editControl.isInteractiveMedia { + editControl.setFrameOrigin(NSMakePoint(frame.width - editControl.frame.width - 20, frame.height - editControl.frame.height - 20)) + } else { + editControl.centerY(x: frame.width - editControl.frame.width - 10) + } + } + + open override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + if let content = self.contentNode?.interactionContentView(for: innerId, animateIn: animateIn) { + return content + } + return self + } + + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + } + + override var backgroundColor: NSColor { + didSet { + contentNode?.backgroundColor = backdorColor + } + } + + override func viewWillMove(toSuperview newSuperview: NSView?) { + if newSuperview == nil { + self.contentNode?.willRemove() + } + } + +} + + diff --git a/Telegram-Mac/MediaResources.swift b/Telegram-Mac/MediaResources.swift index 59eb958d5a..609378d242 100644 --- a/Telegram-Mac/MediaResources.swift +++ b/Telegram-Mac/MediaResources.swift @@ -8,8 +8,9 @@ import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore public final class VideoMediaResourceAdjustments: PostboxCoding, Equatable { let data: MemoryBuffer @@ -91,7 +92,7 @@ public final class VideoLibraryMediaResource: TelegramMediaResource { return VideoLibraryMediaResourceId(localIdentifier: self.localIdentifier, adjustmentsDigest: self.adjustments?.digest) } - public func isEqual(to: TelegramMediaResource) -> Bool { + public func isEqual(to: MediaResource) -> Bool { if let to = to as? VideoLibraryMediaResource { return self.localIdentifier == to.localIdentifier && self.adjustments == to.adjustments } else { @@ -120,7 +121,18 @@ public struct LocalFileGifMediaResourceId: MediaResourceId { } } + + public final class LocalFileGifMediaResource: TelegramMediaResource { + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? LocalFileGifMediaResource { + return self.randomId == to.randomId && self.path == to.path + } else { + return false + } + } + public let randomId: Int64 public let path: String @@ -143,11 +155,284 @@ public final class LocalFileGifMediaResource: TelegramMediaResource { return LocalFileGifMediaResourceId(randomId: self.randomId) } - public func isEqual(to: TelegramMediaResource) -> Bool { - if let to = to as? LocalFileGifMediaResource { +} + +public struct LocalFileVideoMediaResourceId: MediaResourceId { + public let randomId: Int64 + + public var uniqueId: String { + return "lmov-\(self.randomId)" + } + + public var hashValue: Int { + return self.randomId.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? LocalFileVideoMediaResourceId { + return self.randomId == to.randomId + } else { + return false + } + } +} + +public final class LocalFileVideoMediaResource: TelegramMediaResource { + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? LocalFileVideoMediaResource { return self.randomId == to.randomId && self.path == to.path } else { return false } } + + public let randomId: Int64 + public let path: String + + public init(randomId: Int64, path: String) { + self.randomId = randomId + self.path = path + } + + public required init(decoder: PostboxDecoder) { + self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) + self.path = decoder.decodeStringForKey("p", orElse: "") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.randomId, forKey: "i") + encoder.encodeString(self.path, forKey: "p") + } + + public var id: MediaResourceId { + return LocalFileVideoMediaResourceId(randomId: self.randomId) + } + +} + +public struct LottieSoundMediaResourceId: MediaResourceId { + public let randomId: Int64 + + public var uniqueId: String { + return "lottie-sound-\(self.randomId)" + } + + public var hashValue: Int { + return self.randomId.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? LottieSoundMediaResourceId { + return self.randomId == to.randomId + } else { + return false + } + } +} + +public final class LottieSoundMediaResource: TelegramMediaResource { + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? LottieSoundMediaResource { + return self.randomId == to.randomId && self.data == to.data + } else { + return false + } + } + + public let randomId: Int64 + public let data: Data + + public init(randomId: Int64, data: Data) { + self.randomId = randomId + self.data = data + } + + public required init(decoder: PostboxDecoder) { + self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) + self.data = decoder.decodeDataForKey("d") ?? Data() + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.randomId, forKey: "i") + encoder.encodeData(self.data, forKey: "d") + } + + public var id: MediaResourceId { + return LottieSoundMediaResourceId(randomId: self.randomId) + } + +} + + + +public struct LocalFileArchiveMediaResourceId: MediaResourceId { + public let randomId: Int64 + + public var uniqueId: String { + return "larchive-\(self.randomId)" + } + + public var hashValue: Int { + return self.randomId.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? LocalFileArchiveMediaResourceId { + return self.randomId == to.randomId + } else { + return false + } + } +} + +public final class LocalFileArchiveMediaResource: TelegramMediaResource { + public let randomId: Int64 + public let path: String + + public var headerSize: Int32 { + return 32 * 1024 + } + + public init(randomId: Int64, path: String) { + self.randomId = randomId + self.path = path + } + + public required init(decoder: PostboxDecoder) { + self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) + self.path = decoder.decodeStringForKey("p", orElse: "") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.randomId, forKey: "i") + encoder.encodeString(self.path, forKey: "p") + } + + public var id: MediaResourceId { + return LocalFileArchiveMediaResourceId(randomId: self.randomId) + } + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? LocalFileArchiveMediaResource { + return self.randomId == to.randomId && self.path == to.path + } else { + return false + } + } +} + + +public struct ExternalMusicAlbumArtResourceId: MediaResourceId { + public let title: String + public let performer: String + public let isThumbnail: Bool + + public var uniqueId: String { + return "ext-album-art-\(isThumbnail ? "thump" : "full")-\(self.title.replacingOccurrences(of: "/", with: "_"))-\(self.performer.replacingOccurrences(of: "/", with: "_"))" + } + + public var hashValue: Int { + return self.title.hashValue &* 31 &+ self.performer.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? ExternalMusicAlbumArtResourceId { + return self.title == to.title && self.performer == to.performer && self.isThumbnail == to.isThumbnail + } else { + return false + } + } +} + + +public class ExternalMusicAlbumArtResource: TelegramMediaResource { + public let title: String + public let performer: String + public let isThumbnail: Bool + + public init(title: String, performer: String, isThumbnail: Bool) { + self.title = title + self.performer = performer + self.isThumbnail = isThumbnail + } + + public required init(decoder: PostboxDecoder) { + self.title = decoder.decodeStringForKey("t", orElse: "") + self.performer = decoder.decodeStringForKey("p", orElse: "") + self.isThumbnail = decoder.decodeInt32ForKey("th", orElse: 1) != 0 + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.title, forKey: "t") + encoder.encodeString(self.performer, forKey: "p") + encoder.encodeInt32(self.isThumbnail ? 1 : 0, forKey: "th") + } + + public var id: MediaResourceId { + return ExternalMusicAlbumArtResourceId(title: self.title, performer: self.performer, isThumbnail: self.isThumbnail) + } + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? ExternalMusicAlbumArtResource { + return self.title == to.title && self.performer == to.performer && self.isThumbnail == to.isThumbnail + } else { + return false + } + } +} + + +public struct LocalBundleResourceId: MediaResourceId { + public let name: String + public let ext: String + + public var uniqueId: String { + return "local-bundle-\(self.name)-\(self.ext)" + } + + public var hashValue: Int { + return self.name.hashValue + } + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? LocalBundleResourceId { + return self.name == to.name && self.ext == to.ext + } else { + return false + } + } +} + +public class LocalBundleResource: TelegramMediaResource { + public let name: String + public let ext: String + + public init(name: String, ext: String) { + self.name = name + self.ext = ext + } + + public required init(decoder: PostboxDecoder) { + self.name = decoder.decodeStringForKey("n", orElse: "") + self.ext = decoder.decodeStringForKey("e", orElse: "") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.name, forKey: "n") + encoder.encodeString(self.ext, forKey: "e") + } + + public var id: MediaResourceId { + return LocalBundleResourceId(name: self.name, ext: self.ext) + } + + public func isEqual(to: MediaResource) -> Bool { + if let to = to as? LocalBundleResource { + return self.name == to.name && self.ext == to.ext + } else { + return false + } + } } diff --git a/Telegram-Mac/MediaTitleBarView.swift b/Telegram-Mac/MediaTitleBarView.swift deleted file mode 100644 index 2a8c969567..0000000000 --- a/Telegram-Mac/MediaTitleBarView.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// MediaTitleBarView.swift -// Telegram-Mac -// -// Created by keepcoder on 14/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit - - -final class PeerMediaTypeInteraction { - var media:() -> Void = {} - var files:() -> Void = {} - var links:() -> Void = {} - var audio:() -> Void = {} - - init(media:@escaping() -> Void, files:@escaping() -> Void, links:@escaping() -> Void, audio:@escaping() -> Void) { - self.media = media - self.files = files - self.links = links - self.audio = audio - } -} - - - -class MediaTitleBarView: TitledBarView { - - private let button:TitleButton = TitleButton() - private let dropdownImage = ImageButton() - - public init(controller: ViewController, interactions:PeerMediaTypeInteraction) { - super.init(controller: controller) - - button.set(font: .medium(.title), for: .Normal) - - - button.highlightHovered = true - dropdownImage.highlightHovered = true - let showDropDown:(Control) -> Void = { [weak self] _ in - - if let strongSelf = self, !hasPopover(mainWindow) { - var items:[SPopoverItem] = [] - items.append(SPopoverItem(tr(.peerMediaPopoverSharedMedia), { [weak strongSelf] in - interactions.media() - strongSelf?.button.set(text: tr(.peerMediaSharedMedia), for: .Normal) - })) - items.append(SPopoverItem(tr(.peerMediaPopoverSharedFiles), { [weak strongSelf] in - interactions.files() - strongSelf?.button.set(text: tr(.peerMediaPopoverSharedFiles), for: .Normal) - })) - items.append(SPopoverItem(tr(.peerMediaPopoverSharedLinks), { [weak strongSelf] in - interactions.links() - strongSelf?.button.set(text: tr(.peerMediaPopoverSharedLinks), for: .Normal) - })) - items.append(SPopoverItem(tr(.peerMediaPopoverSharedAudio), { [weak strongSelf] in - interactions.audio() - strongSelf?.button.set(text: tr(.peerMediaPopoverSharedAudio), for: .Normal) - })) - - let controller = SPopoverViewController(items: items) - showPopover(for: strongSelf, with: controller, edge: .maxY, inset: NSMakePoint( floorToScreenPixels(strongSelf.frame.width / 2) - floorToScreenPixels(controller.frame.width/2),-50)) - - } - } - - self.set(handler: showDropDown, for: .Click) - - dropdownImage.userInteractionEnabled = false - button.userInteractionEnabled = false - - //dropdownImage.set(handler: showDropDown, for: .Click) - - set(handler: { [weak self] control in - - //self?.dropdownImage.layer?.animateRotateCenter(from: 0, to: 180, duration: 0.2, removeOnCompletion: false) - - }, for: .Highlight) - - button.sizeToFit() - addSubview(button) - - - addSubview(dropdownImage) - } - - override func updateLocalizationAndTheme() { - button.set(text: tr(.peerMediaSharedMedia), for: .Normal) - button.set(color: theme.colors.blueUI, for: .Normal) - dropdownImage.set(image: theme.icons.mediaDropdown, for: .Normal) - dropdownImage.sizeToFit() - needsLayout = true - } - - - override func layout() { - super.layout() - button.center() - dropdownImage.centerY(x: button.frame.maxX + 4) - dropdownImage.setFrameOrigin(dropdownImage.frame.minX, dropdownImage.frame.minY + 1) - - } - - required init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} diff --git a/Telegram-Mac/MediaTrackDecodableFrame.swift b/Telegram-Mac/MediaTrackDecodableFrame.swift new file mode 100755 index 0000000000..6c01603a79 --- /dev/null +++ b/Telegram-Mac/MediaTrackDecodableFrame.swift @@ -0,0 +1,25 @@ +import Foundation +import CoreMedia + +enum MediaTrackFrameType { + case video + case audio +} + +final class MediaTrackDecodableFrame { + let type: MediaTrackFrameType + let packet: FFMpegPacket + let pts: CMTime + let dts: CMTime + let duration: CMTime + + init(type: MediaTrackFrameType, packet: FFMpegPacket, pts: CMTime, dts: CMTime, duration: CMTime) { + self.type = type + + self.pts = pts + self.dts = dts + self.duration = duration + + self.packet = packet + } +} diff --git a/Telegram-Mac/MediaTrackFrame.swift b/Telegram-Mac/MediaTrackFrame.swift new file mode 100755 index 0000000000..925d7c9477 --- /dev/null +++ b/Telegram-Mac/MediaTrackFrame.swift @@ -0,0 +1,28 @@ +import Foundation +import CoreMedia + +final class MediaTrackFrame { + let type: MediaTrackFrameType + let sampleBuffer: CMSampleBuffer + let resetDecoder: Bool + let decoded: Bool + let rotationAngle: Double + + init(type: MediaTrackFrameType, sampleBuffer: CMSampleBuffer, resetDecoder: Bool, decoded: Bool, rotationAngle: Double = 0.0) { + self.type = type + self.sampleBuffer = sampleBuffer + self.resetDecoder = resetDecoder + self.decoded = decoded + self.rotationAngle = rotationAngle + + + } + + var position: CMTime { + return CMSampleBufferGetPresentationTimeStamp(self.sampleBuffer) + } + + var duration: CMTime { + return CMSampleBufferGetDuration(self.sampleBuffer) + } +} diff --git a/Telegram-Mac/MediaTrackFrameBuffer.swift b/Telegram-Mac/MediaTrackFrameBuffer.swift new file mode 100755 index 0000000000..0fb68f3bf8 --- /dev/null +++ b/Telegram-Mac/MediaTrackFrameBuffer.swift @@ -0,0 +1,168 @@ +import Foundation +import SwiftSignalKit +import CoreMedia + +enum MediaTrackFrameBufferStatus { + case buffering + case full(until: Double) + case finished(at: Double) +} + +enum MediaTrackFrameResult { + case noFrames + case skipFrame + case restoreState([MediaTrackFrame], CMTime) + case frame(MediaTrackFrame) + case finished +} + +private let traceEvents = false + +final class MediaTrackFrameBuffer { + private let stallDuration: Double + private let lowWaterDuration: Double + private let highWaterDuration: Double + + private let frameSource: MediaFrameSource + private let decoder: MediaTrackFrameDecoder + private let type: MediaTrackFrameType + let duration: CMTime + let rotationAngle: Double + let aspect: Double + + var statusUpdated: () -> Void = { } + + private var frameSourceSinkIndex: Int? + + private var frames: [MediaTrackDecodableFrame] = [] + private var endOfStream = false + private var bufferedUntilTime: CMTime? + + init(frameSource: MediaFrameSource, decoder: MediaTrackFrameDecoder, type: MediaTrackFrameType, duration: CMTime, rotationAngle: Double, aspect: Double, stallDuration: Double = 1.0, lowWaterDuration: Double = 2.0, highWaterDuration: Double = 3.0) { + self.frameSource = frameSource + self.type = type + self.decoder = decoder + self.duration = duration + self.rotationAngle = rotationAngle + self.aspect = aspect + self.stallDuration = stallDuration + self.lowWaterDuration = lowWaterDuration + self.highWaterDuration = highWaterDuration + + self.frameSourceSinkIndex = self.frameSource.addEventSink { [weak self] event in + if let strongSelf = self { + switch event { + case let .frames(frames): + var filteredFrames: [MediaTrackDecodableFrame] = [] + for frame in frames { + if frame.type == type { + filteredFrames.append(frame) + } + } + if !filteredFrames.isEmpty { + strongSelf.addFrames(filteredFrames) + } + case .endOfStream: + strongSelf.endOfStreamReached() + } + } + } + } + + deinit { + if let frameSourceSinkIndex = self.frameSourceSinkIndex { + self.frameSource.removeEventSink(frameSourceSinkIndex) + } + } + + private func addFrames(_ frames: [MediaTrackDecodableFrame]) { + self.frames.append(contentsOf: frames) + var maxUntilTime: CMTime? + for frame in frames { + let frameEndTime = CMTimeAdd(frame.pts, frame.duration) + if self.bufferedUntilTime == nil || CMTimeCompare(self.bufferedUntilTime!, frameEndTime) < 0 { + self.bufferedUntilTime = frameEndTime + maxUntilTime = frameEndTime + } + } + + if let maxUntilTime = maxUntilTime { + if traceEvents { + print("added \(frames.count) frames until \(CMTimeGetSeconds(maxUntilTime)), \(self.frames.count) total") + } + } + + self.statusUpdated() + } + + private func endOfStreamReached() { + self.endOfStream = true + self.bufferedUntilTime = duration + self.statusUpdated() + } + + func status(at timestamp: Double) -> MediaTrackFrameBufferStatus { + var bufferedDuration = 0.0 + if let bufferedUntilTime = self.bufferedUntilTime { + if CMTimeCompare(bufferedUntilTime, self.duration) >= 0 || self.endOfStream { + return .finished(at: CMTimeGetSeconds(bufferedUntilTime)) + } + + bufferedDuration = CMTimeGetSeconds(bufferedUntilTime) - timestamp + } + + let minTimestamp = timestamp - 1.0 + for i in (0 ..< self.frames.count).reversed() { + if CMTimeGetSeconds(self.frames[i].pts) < minTimestamp { + self.frames.remove(at: i) + } + } + + if bufferedDuration < self.lowWaterDuration { + if traceEvents { + print("buffered duration: \(bufferedDuration), requesting until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") + } + self.frameSource.generateFrames(until: timestamp + self.highWaterDuration) + + if bufferedDuration > self.stallDuration { + if traceEvents { + print("buffered1 duration: \(bufferedDuration), wait until \(timestamp) + \(self.highWaterDuration - bufferedDuration)") + } + return .full(until: timestamp + self.highWaterDuration) + } else { + return .buffering + } + } else { + if traceEvents { + print("buffered2 duration: \(bufferedDuration), wait until \(timestamp) + \(bufferedDuration - self.lowWaterDuration)") + } + return .full(until: timestamp + max(0.0, bufferedDuration - self.lowWaterDuration)) + } + } + + var hasFrames: Bool { + return !self.frames.isEmpty + } + + func takeFrame() -> MediaTrackFrameResult { + if !self.frames.isEmpty { + let frame = self.frames.removeFirst() + if let decodedFrame = self.decoder.decode(frame: frame) { + return .frame(decodedFrame) + } else { + return .skipFrame + } + } else { + if self.endOfStream, let decodedFrame = self.decoder.takeRemainingFrame() { + return .frame(decodedFrame) + } else { + if let bufferedUntilTime = bufferedUntilTime { + if CMTimeCompare(bufferedUntilTime, self.duration) >= 0 || self.endOfStream { + return .finished + } + } + } + } + return .noFrames + } +} diff --git a/Telegram-Mac/MediaTrackFrameDecoder.swift b/Telegram-Mac/MediaTrackFrameDecoder.swift new file mode 100755 index 0000000000..9e4c8266ec --- /dev/null +++ b/Telegram-Mac/MediaTrackFrameDecoder.swift @@ -0,0 +1,6 @@ + +protocol MediaTrackFrameDecoder { + func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? + func takeRemainingFrame() -> MediaTrackFrame? + func reset() +} diff --git a/Telegram-Mac/MediaUtils.swift b/Telegram-Mac/MediaUtils.swift index f9f1331c99..07c6866b05 100644 --- a/Telegram-Mac/MediaUtils.swift +++ b/Telegram-Mac/MediaUtils.swift @@ -7,19 +7,44 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox import TGUIKit import AVFoundation +import Accelerate + +public final class ImageDataTransformation { + let data: ImageRenderData + let execute:(TransformImageArguments, ImageRenderData)->DrawingContext? + init(data: ImageRenderData = ImageRenderData(nil, nil, false), execute:@escaping(TransformImageArguments, ImageRenderData)->DrawingContext? = { _, _ in return nil}) { + self.data = data + self.execute = execute + } +} + +final class ImageRenderData { + let thumbnailData: Data? + let fullSizeData:Data? + let fullSizeComplete:Bool + init(_ thumbnailData: Data?, _ fullSizeData: Data?, _ fullSizeComplete: Bool) { + self.thumbnailData = thumbnailData + self.fullSizeData = fullSizeData + self.fullSizeComplete = fullSizeComplete + } +} -func chatMessageFileStatus(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.resourceStatus(file.resource) +func chatMessageFileStatus(account: Account, file: TelegramMediaFile, approximateSynchronousValue: Bool = false) -> Signal { + if let _ = file.resource as? LocalFileReferenceMediaResource { + return .single(.Local) + } + return account.postbox.mediaBox.resourceStatus(file.resource, approximateSynchronousValue: approximateSynchronousValue) } -func chatMessageFileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file), implNext: true) +func chatMessageFileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal { + return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource), statsCategory: .file, reportResultStatus: true) |> `catch` { _ in return .complete() } //account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file), implNext: true) } func chatMessageFileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { @@ -27,7 +52,7 @@ func chatMessageFileCancelInteractiveFetch(account: Account, file: TelegramMedia } func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> TelegramMediaImageRepresentation? { - return photo.representationForDisplayAtSize(NSMakeSize(1280.0, 1280.0)) + return photo.representationForDisplayAtSize(PixelDimensions(1280, 1280)) } func smallestImageRepresentation(_ representation:[TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? { @@ -35,111 +60,182 @@ func smallestImageRepresentation(_ representation:[TelegramMediaImageRepresentat } -private func chatMessagePhotoDatas(account: Account, photo: TelegramMediaImage, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { - if let smallestRepresentation = smallestImageRepresentation(photo.representations), let largestRepresentation = photo.representationForDisplayAtSize(fullRepresentationSize) { - let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource) +// +func chatMessagePhotoDatas(postbox: Postbox, imageReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil, peer: Peer? = nil) -> Signal { + if let smallestRepresentation = smallestImageRepresentation(imageReference.media.representations), let largestRepresentation = imageReference.media.representationForDisplayAtSize(PixelDimensions(fullRepresentationSize)) { - let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in - if maybeData.complete { - let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - - return .single((nil, loadedData, true)) - } else { - let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(smallestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) - let fetchedFullSize = account.postbox.mediaBox.fetchedResource(largestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) - - let thumbnail = Signal { subscriber in - let fetchedDisposable = fetchedThumbnail.start() - let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() + + let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) + + let signal = maybeFullSize + |> take(1) + |> mapToSignal { maybeData -> Signal in + if maybeData.complete { + let loadedData: Data? + if largestRepresentation.resource is EncryptedMediaResource, let secureIdAccessContext = secureIdAccessContext { + loadedData = decryptedResourceData(data: maybeData, resource: largestRepresentation.resource, params: secureIdAccessContext) + } else { + loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) } - } - - let fullSizeData: Signal<(Data?, Bool), NoError> - - if autoFetchFullSize { - fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in - let fetchedFullSizeDisposable = fetchedFullSize.start() - let fullSizeDisposable = account.postbox.mediaBox.resourceData(largestRepresentation.resource).start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + return .single(ImageRenderData(nil, loadedData, true)) + } else { + + + let decodedThumbnailData = imageReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) + let fetchedThumbnail: Signal + if let _ = decodedThumbnailData { + fetchedThumbnail = .complete() + } else { + let reference: MediaResourceReference + if let peer = peer, let peerReference = PeerReference(peer) { + reference = MediaResourceReference.avatar(peer: peerReference, resource: smallestRepresentation.resource) + } else { + reference = imageReference.resourceReference(smallestRepresentation.resource) + } + fetchedThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: reference, statsCategory: .image) |> `catch` { _ in return .complete() } + } + let reference: MediaResourceReference + if let peer = peer, let peerReference = PeerReference(peer) { + reference = MediaResourceReference.avatar(peer: peerReference, resource: largestRepresentation.resource) + } else { + reference = imageReference.resourceReference(largestRepresentation.resource) + } + let fetchedFullSize = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: reference, statsCategory: .image) + + let anyThumbnail: [Signal] + if tryAdditionalRepresentations { + anyThumbnail = imageReference.media.representations.filter({ representation in + return representation != largestRepresentation + }).map({ representation -> Signal in + return postbox.mediaBox.resourceData(representation.resource) + |> take(1) + }) + } else { + anyThumbnail = [] + } + + let mainThumbnail = Signal { subscriber in + if let decodedThumbnailData = decodedThumbnailData { + subscriber.putNext(decodedThumbnailData) + subscriber.putCompletion() + return EmptyDisposable + } else { + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedFullSizeDisposable.dispose() - fullSizeDisposable.dispose() + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } } } - } else { - fullSizeData = account.postbox.mediaBox.resourceData(largestRepresentation.resource) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + + let thumbnail = combineLatest(anyThumbnail) + |> mapToSignal { thumbnails -> Signal in + for thumbnail in thumbnails { + if thumbnail.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: thumbnail.path), options: []) { + return .single(data) + } + } + return mainThumbnail } - } - - - return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + + let fullSizeData: Signal + + if autoFetchFullSize { + fullSizeData = Signal { subscriber in + let fetchedFullSizeDisposable = fetchedFullSize.start() + let fullSizeDisposable = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + } else { + fullSizeData = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) + |> map { next -> ImageRenderData in + return ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + } } + + return thumbnail + |> mapToSignal { thumbnailData in + if let _ = thumbnailData { + return fullSizeData + |> map { fullSizeData in + return ImageRenderData(fullSizeData.fullSizeComplete ? nil : thumbnailData, fullSizeData.fullSizeData, fullSizeData.fullSizeComplete) + } + } else { + return .single(ImageRenderData(nil, nil, false)) + } + } +// +// return combineLatest(thumbnail, fullSizeData) +// |> map { thumbnailData, fullSizeData -> ImageRenderData in +// return ImageRenderData(fullSizeData.fullSizeComplete ? nil : thumbnailData, fullSizeData.fullSizeData, fullSizeData.fullSizeComplete) +// } } } - } - + |> distinctUntilChanged(isEqual: { lhs, rhs in + if (lhs.thumbnailData == nil && lhs.fullSizeData == nil) && (rhs.thumbnailData == nil && rhs.fullSizeData == nil) { + return true + } else { + return false + } + }) + return signal } else { return .never() } } -private func chatMessageWebFilePhotoDatas(account: Account, photo: TelegramMediaWebFile) -> Signal<(Data?, Bool), NoError> { - let maybeFullSize = account.postbox.mediaBox.resourceData(photo.resource) + + +private func chatMessageWebFilePhotoDatas(account: Account, photo: TelegramMediaWebFile, synchronousLoad: Bool = false) -> Signal { + let maybeFullSize = account.postbox.mediaBox.resourceData(photo.resource, attemptSynchronously: synchronousLoad) - let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Bool), NoError> in + let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((loadedData, true)) + return .single(ImageRenderData(nil, loadedData, true)) } else { - let fullSizeData: Signal<(Data?, Bool), NoError> - - fullSizeData = account.postbox.mediaBox.resourceData(photo.resource) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) - } - - return fullSizeData |> map { resource in - return (resource.0, resource.1) + return account.postbox.mediaBox.resourceData(photo.resource, attemptSynchronously: synchronousLoad) + |> map { next -> ImageRenderData in + return ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } - - } |> filter({ $0.0 != nil }) + } |> filter({ $0.fullSizeData != nil }) return signal } -private func chatMessageFileDatas(account: Account, file: TelegramMediaFile, pathExtension: String? = nil, progressive: Bool = false, justThumbail: Bool = false) -> Signal<(Data?, String?, Bool), NoError> { - if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) { +private func chatMessageFileDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, justThumbail: Bool = false, synchronousLoad: Bool = false) -> Signal { + if let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { let thumbnailResource = smallestRepresentation.resource - let fullSizeResource = file.resource + let fullSizeResource = largestImageRepresentation(fileReference.media.previewRepresentations)?.resource ?? smallestRepresentation.resource - let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension) + let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource, pathExtension: pathExtension, attemptSynchronously: synchronousLoad) - let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, String?, Bool), NoError> in - if maybeData.complete && !justThumbail { - return .single((nil, maybeData.path, true)) - } else { - let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(thumbnailResource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)) + let signal = maybeFullSize |> mapToSignal { maybeData -> Signal in + + if maybeData.complete && !justThumbail { + let fullData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)) + return .single(ImageRenderData(nil, fullData, fullData != nil)) + } else { + let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(thumbnailResource)) let thumbnail = Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() - let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in + let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension, attemptSynchronously: synchronousLoad).start(next: { next in subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) }, error: subscriber.putError, completed: subscriber.putCompletion) @@ -150,17 +246,26 @@ private func chatMessageFileDatas(account: Account, file: TelegramMediaFile, pat } - let fullSizeDataAndPath = account.postbox.mediaBox.resourceData(fullSizeResource, option: !progressive ? .complete(waitUntilFetchStatus: false) : .incremental(waitUntilFetchStatus: false)) |> map { next -> (String?, Bool) in - return (next.size == 0 ? nil : next.path, next.complete) + let fullSizeDataAndPath = account.postbox.mediaBox.resourceData(fullSizeResource, option: !progressive ? .complete(waitUntilFetchStatus: false) : .incremental(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) |> map { next -> Data? in + return next.size == 0 || !next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path)) } - + + //TODO return thumbnail |> mapToSignal { thumbnailData in - return fullSizeDataAndPath |> map { (dataPath, complete) in - return (thumbnailData, dataPath, complete) + return fullSizeDataAndPath |> take(1) |> map { fullSizeData in + return ImageRenderData(thumbnailData, fullSizeData, fullSizeData != nil) } - } + } |> then(Signal({ subscriber -> Disposable in + if !maybeData.complete, let fullSizeResource = fullSizeResource as? LocalFileReferenceMediaResource { + let thumbnailData = justThumbail ? try? Data(contentsOf: URL(fileURLWithPath: fullSizeResource.localFilePath)) : nil + let fulleSizeData = try? Data(contentsOf: URL(fileURLWithPath: fullSizeResource.localFilePath)) + subscriber.putNext(ImageRenderData(thumbnailData, fulleSizeData, true)) + } + subscriber.putCompletion() + return EmptyDisposable + })) } - } |> filter({ $0.0 != nil || $0.1 != nil }) + } |> filter({ $0.thumbnailData != nil || $0.fullSizeData != nil }) return signal } else { @@ -169,41 +274,45 @@ private func chatMessageFileDatas(account: Account, file: TelegramMediaFile, pat } -func chatGalleryPhoto(account: Account, photo: TelegramMediaImage, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), scale:CGFloat) -> Signal<(TransformImageArguments) -> CGImage?, NoError> { - let signal = chatMessagePhotoDatas(account: account, photo: photo, fullRepresentationSize:toRepresentationSize) +func chatGalleryPhoto(account: Account, imageReference: ImageMediaReference, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), peer: Peer? = nil, scale:CGFloat, secureIdAccessContext: SecureIdAccessContext? = nil, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> CGImage?, NoError> { + let signal = chatMessagePhotoDatas(postbox: account.postbox, imageReference: imageReference, fullRepresentationSize:toRepresentationSize, synchronousLoad: synchronousLoad, secureIdAccessContext: secureIdAccessContext, peer: peer) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal |> map { data in return { arguments in - assertNotOnMainThread() let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + let fullSizeData = data.fullSizeData + let thumbnailData = data.thumbnailData + + let options = NSMutableDictionary() + + options.setValue(max(fittedSize.width * scale, fittedSize.height * scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCache as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCacheImmediately as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + + if let fullSizeData = fullSizeData { - if fullSizeComplete { - let options = NSMutableDictionary() - - // options.setValue(max(fittedSize.width * scale, fittedSize.height * scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + if data.fullSizeComplete { if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { - return image + return generateImage(image.size, contextGenerator: { (size, ctx) in + ctx.setFillColor(theme.colors.transparentBackground.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(image, in: NSMakeRect(0, 0, size.width, size.height)) + }) + } - } else { - let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) - - let options = NSMutableDictionary() - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { - return image - } } } + options.setValue(150 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { thumbnailImage = image } @@ -215,12 +324,15 @@ func chatGalleryPhoto(account: Account, photo: TelegramMediaImage, toRepresentat c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + return generateImage(fittedSize, contextGenerator: { size, ctx in + ctx.draw(thumbnailContext.generateImage()!, in: NSMakeRect(0, 0, size.width, size.height)) + }) - return thumbnailContext.generateImage() } return generateImage(fittedSize, contextGenerator: { (size, ctx) in - ctx.setFillColor(theme.colors.background.cgColor) + ctx.setFillColor(theme.colors.transparentBackground.cgColor) ctx.fill(NSMakeRect(0, 0, size.width, size.height)) }) @@ -228,31 +340,41 @@ func chatGalleryPhoto(account: Account, photo: TelegramMediaImage, toRepresentat } } -func chatMessagePhoto(account: Account, photo: TelegramMediaImage, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessagePhotoDatas(account: account, photo: photo, fullRepresentationSize:toRepresentationSize) +func chatMessagePhoto(account: Account, imageReference: ImageMediaReference, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), peer: Peer? = nil, scale:CGFloat, synchronousLoad: Bool = false, autoFetchFullSize: Bool = false) -> Signal { + let signal = chatMessagePhotoDatas(postbox: account.postbox, imageReference: imageReference, fullRepresentationSize: toRepresentationSize, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad, peer: peer) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) + let fullSizeData = data.fullSizeData + let thumbnailData = data.thumbnailData + 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 fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize) + switch arguments.resizeMode { + case .none: + break + default: + fittedSize = fittedSize.fitted(arguments.imageSize) + } + let fittedRect = CGRect(origin: CGPoint(x: floorToScreenPixels(System.backingScale, drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0), y: floorToScreenPixels(System.backingScale, drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0)), size: fittedSize) var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { - if fullSizeComplete { + if data.fullSizeComplete { let options = NSMutableDictionary() options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCache as String) + + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, data.fullSizeComplete) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber @@ -262,32 +384,92 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage, toRepresentat } } + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { thumbnailImage = image } var blurredThumbnailImage: CGImage? if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + + let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 90.0, height: 90.0)) + + let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in - c.interpolationQuality = .none + thumbnailContext.withFlippedContext { c in c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - blurredThumbnailImage = thumbnailContext.generateImage() + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage() { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + telegramFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + blurredThumbnailImage = additionalBlurContext.generateImage() + } else { + blurredThumbnailImage = thumbnailContext.generateImage() + } } - context.withContext { c in + + context.withContext(isHighQuality: data.fullSizeData != nil, { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { - c.fill(arguments.drawingRect) + switch arguments.resizeMode { + case .blurBackground: + let blurSourceImage = thumbnailImage ?? fullSizeImage + + if let fullSizeImage = blurSourceImage { + let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + // telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredImage = thumbnailContext.generateImage() { + let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .medium + c.draw(blurredImage, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) + c.setBlendMode(.normal) + c.setFillColor(theme.colors.background.withAlphaComponent(0.5).cgColor) + c.fill(arguments.drawingRect) + c.setBlendMode(.copy) + } + } else { + c.fill(arguments.drawingRect) + } + case let .fill(color): + c.setFillColor(color.cgColor) + c.fill(arguments.drawingRect) + case .fillTransparent: + c.setFillColor(theme.colors.transparentBackground.cgColor) + c.fill(arguments.drawingRect) + case .none: + break + case .imageColor: + break + } } - c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage { c.interpolationQuality = .low c.draw(blurredThumbnailImage, in: fittedRect) @@ -299,23 +481,22 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage, toRepresentat c.interpolationQuality = .medium c.draw(fullSizeImage, in: fittedRect) } - - } + + }) addCorners(context, arguments: arguments, scale:scale) return context - } + }) } } -func chatMessageWebFilePhoto(account: Account, photo: TelegramMediaWebFile, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageWebFilePhotoDatas(account: account, photo: photo) +func chatMessageWebFilePhoto(account: Account, photo: TelegramMediaWebFile, toRepresentationSize:NSSize = NSMakeSize(1280, 1280), scale:CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessageWebFilePhotoDatas(account: account, photo: photo, synchronousLoad: synchronousLoad) - return signal |> map { (fullSizeData, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) let drawingRect = arguments.drawingRect @@ -323,18 +504,20 @@ func chatMessageWebFilePhoto(account: Account, photo: TelegramMediaWebFile, toRe 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 fullSizeImage: CGImage? - if let fullSizeData = fullSizeData { - if fullSizeComplete { + if let fullSizeData = data.fullSizeData { + if data.fullSizeComplete { let options = NSMutableDictionary() options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, data.fullSizeComplete) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber @@ -344,11 +527,12 @@ func chatMessageWebFilePhoto(account: Account, photo: TelegramMediaWebFile, toRe } } - - context.withContext { c in + + context.withContext(isHighQuality: fullSizeImage != nil, { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { + c.setFillColor(theme.colors.grayBackground.cgColor) c.fill(arguments.drawingRect) } @@ -358,13 +542,13 @@ func chatMessageWebFilePhoto(account: Account, photo: TelegramMediaWebFile, toRe c.draw(fullSizeImage, in: fittedRect) } - } + }) addCorners(context, arguments: arguments, scale:scale) return context - } + }) } } @@ -376,486 +560,1098 @@ enum StickerDatasType { case full } +func chatMessageStickerResource(file: TelegramMediaFile, small: Bool) -> MediaResource { + let resource: MediaResource + if small, let smallest = largestImageRepresentation(file.previewRepresentations) { + resource = smallest.resource + } else { + resource = file.resource + } + return resource +} + + + -private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, type: StickerDatasType) -> Signal<(Data?, Data?, Bool), NoError> { - // let maybeFetched = account.postbox.mediaBox.resourceData(file.resource, complete: true) + +private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal { - let dimensions:NSSize? - switch type { - case .thumb: - dimensions = CGSize(width: 30, height: 30) - case .small: - dimensions = CGSize(width: 120, height: 120) - case .chatMessage: - dimensions = CGSize(width: 300, height: 300) - case .full: - dimensions = nil - } + let thumbnailResource = chatMessageStickerResource(file: file, small: true) + let resource = chatMessageStickerResource(file: file, small: small) + + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 120.0, height: 120.0) : nil), complete: false, fetch: false, attemptSynchronously: synchronousLoad) |> runOn(synchronousLoad ? .mainQueue() : resourcesQueue) - let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: dimensions), complete: true) - return maybeFetched |> take(1) |> mapToSignal { maybeData in - var size:Int = 0 - if let s = file.size { - size = s - } - if maybeData.size >= size { - let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - - return .single((nil, loadedData, true)) - } else { - //let fullSizeData = account.postbox.mediaBox.resourceData(file.resource, complete: true) - - let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: dimensions), complete: true) |> map { next in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) - } - - return fullSizeData |> map { (data, complete) -> (Data?, Data?, Bool) in - return (nil, data, complete) + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + + return .single(ImageRenderData(nil, loadedData, true)) + } else { + let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: true) + let fullSizeData:Signal = .single(ImageRenderData(nil, nil, false)) |> then(postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 120.0, height: 120.0) : nil), complete: true) + |> map { next in + return ImageRenderData(nil, !next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) + }) + + + return Signal { subscriber in + var fetch: Disposable? + if fetched { + fetch = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(resource)).start() + } + + var fetchThumbnail: Disposable? + if !thumbnailResource.id.isEqual(to: resource.id) { + fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + } + let disposable = (combineLatest(thumbnailData, fullSizeData) + |> map { thumbnailData, fullSizeData -> ImageRenderData in + return ImageRenderData(thumbnailData.complete && !fullSizeData.fullSizeComplete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.fullSizeData, fullSizeData.fullSizeComplete) + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetch?.dispose() + fetchThumbnail?.dispose() + disposable.dispose() + } + } } - } } } -func chatMessageSticker(account: Account, file: TelegramMediaFile, type: StickerDatasType, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + + +private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMediaFile, synchronousLoad: Bool) -> Signal { + let thumbnailResource = chatMessageStickerResource(file: file, small: true) - let signal = chatMessageStickerDatas(account: account, file: file, type: type) + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false, fetch: false, attemptSynchronously: synchronousLoad) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(loadedData) + } else { + let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: true) + + return Signal { subscriber in + let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + + let disposable = (thumbnailData + |> map { thumbnailData -> Data? in + return thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetchThumbnail.dispose() + disposable.dispose() + } + } + } + } +} + + +public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, scale: CGFloat, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal { + let signal: Signal + signal = chatMessageStickerDatas(postbox: postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad) + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: arguments.emptyColor == nil) - let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) + let drawingRect = arguments.drawingRect + let fittedSize = 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) + //let fittedRect = arguments.drawingRect - var fullSizeImage: (CGImage, CGImage)? - if let fullSizeData = fullSizeData, fullSizeComplete { - if let image = imageFromAJpeg(data: fullSizeData) { + var fullSizeImage: CGImage? + if let fullSizeData = data.fullSizeData, data.fullSizeComplete { + if let image = NSImage(data: fullSizeData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { fullSizeImage = image } } - let thumbnailImage: CGImage? = nil + var thumbnailImage: CGImage? + if fullSizeImage == nil, let thumbnailData = data.thumbnailData { + if let image = NSImage(data: thumbnailData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + thumbnailImage = image + } + } var blurredThumbnailImage: CGImage? + let thumbnailInset: CGFloat = 10.0 if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + let thumbnailSize = thumbnailImage.size + var thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailDrawingSize = thumbnailContextSize + thumbnailContextSize.width += thumbnailInset * 2.0 + thumbnailContextSize.height += thumbnailInset * 2.0 + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) + thumbnailContext.withFlippedContext(isHighQuality: false, { c in + let cgImage = thumbnailImage + c.setBlendMode(.normal) + c.interpolationQuality = .medium + c.draw(cgImage, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + }) + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } - context.withContext { c in - c.setBlendMode(.copy) + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in + if let color = arguments.emptyColor { + c.setBlendMode(.normal) + switch color { + case let .color(color): + c.setFillColor(color.cgColor) + default: + break + } + c.fill(drawingRect) + } else { + c.setBlendMode(.copy) + } + if let blurredThumbnailImage = blurredThumbnailImage { c.interpolationQuality = .low - c.draw(blurredThumbnailImage, in: arguments.drawingRect) + let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) + c.draw(blurredThumbnailImage, in: fittedRect.insetBy(dx: -thumbnailScaledInset, dy: -thumbnailScaledInset)) } if let fullSizeImage = fullSizeImage { - let cgImage = fullSizeImage.0 - let cgImageAlpha = fullSizeImage.1 - + let cgImage = fullSizeImage 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) + c.draw(cgImage, in: fittedRect) } - } + }) return context - } + }) } } -func chatWebpageSnippetPhotoData(account: Account, photo: TelegramMediaImage, small:Bool) -> Signal { - if let closestRepresentation = (small ? photo.representationForDisplayAtSize(CGSize(width: 120.0, height: 120.0)) : largestImageRepresentation(photo.representations)) { - let resourceData = account.postbox.mediaBox.resourceData(closestRepresentation.resource) |> map { next in - return next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) - } - - return Signal { subscriber in - let disposable = DisposableSet() - disposable.add(resourceData.start(next: { data in - subscriber.putNext(data) - }, error: { error in - subscriber.putError(error) - }, completed: { - subscriber.putCompletion() - })) - disposable.add(account.postbox.mediaBox.fetchedResource(closestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)).start()) - return disposable - } - } else { - return .never() - } -} -func chatWebpageSnippetPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat, small:Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatWebpageSnippetPhotoData(account: account, photo: photo, small:small) + + + + +private func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool, size: NSSize) -> Signal { + let thumbnailResource = chatMessageStickerResource(file: file, small: true) + let resource = chatMessageStickerResource(file: file, small: small) - return signal |> map { fullSizeData in - return { arguments in - var fullSizeImage: CGImage? - if let fullSizeData = fullSizeData { - let options = NSMutableDictionary() - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { - fullSizeImage = image - } - } - - if let fullSizeImage = fullSizeImage { - let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(thumb: small, size: size, fitzModifier: file.animatedEmojiFitzModifier), complete: false, fetch: false, attemptSynchronously: synchronousLoad) |> runOn(synchronousLoad ? .mainQueue() : resourcesQueue) + + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize) - let drawingRect = arguments.drawingRect + return .single(ImageRenderData(nil, loadedData, true)) + } else { + let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedAnimatedStickerRepresentation(thumb: true, size: size.aspectFitted(NSMakeSize(60, 60)), fitzModifier: file.animatedEmojiFitzModifier), complete: true) - 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.withContext { c in - c.setBlendMode(.copy) - if arguments.boundingSize.width > arguments.imageSize.width || arguments.boundingSize.height > arguments.imageSize.height { - c.fill(arguments.drawingRect) + let fullSizeData:Signal = .single(ImageRenderData(nil, nil, false)) |> then(postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(thumb: false, size: size, fitzModifier: file.animatedEmojiFitzModifier), complete: true) + |> map { next in + return ImageRenderData(nil, !next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) + }) + + return Signal { subscriber in + var fetch: Disposable? + if fetched { + fetch = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(resource)).start() } - c.interpolationQuality = .medium - c.draw(fullSizeImage, in: fittedRect) + var fetchThumbnail: Disposable? + if !thumbnailResource.id.isEqual(to: resource.id) { + fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + } + let disposable = (combineLatest(thumbnailData, fullSizeData) + |> map { thumbnailData, fullSizeData -> ImageRenderData in + return ImageRenderData(thumbnailData.complete && !fullSizeData.fullSizeComplete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.fullSizeData, fullSizeData.fullSizeComplete) + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetch?.dispose() + fetchThumbnail?.dispose() + disposable.dispose() + } } - - addCorners(context, arguments: arguments, scale:scale) - - return context - } else { - return nil } - } - } -} - - - -func chatMessagePhotoStatus(account: Account, photo: TelegramMediaImage) -> Signal { - if let largestRepresentation = largestRepresentationForPhoto(photo) { - return account.postbox.mediaBox.resourceStatus(largestRepresentation.resource) - } else { - return .never() - } -} - -func chatMessagePhotoInteractiveFetched(account: Account, photo: TelegramMediaImage) -> Signal { - if let largestRepresentation = largestRepresentationForPhoto(photo) { - return account.postbox.mediaBox.fetchedResource(largestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) |> map {_ in} - } else { - return .never() } } -func chatMessagePhotoCancelInteractiveFetch(account: Account, photo: TelegramMediaImage) { - if let largestRepresentation = largestRepresentationForPhoto(photo) { - return account.postbox.mediaBox.cancelInteractiveResourceFetch(largestRepresentation.resource) - } -} -func fileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)) |> map {_ in} -} -func fileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { - account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) -} -public func blurImage(_ data:Data?, _ s:NSSize, cornerRadius:CGFloat = 0) -> CGImage? { +private func chatMessageAnimatedStickerThumbnailData(postbox: Postbox, file: TelegramMediaFile, synchronousLoad: Bool) -> Signal { + let thumbnailResource = chatMessageStickerResource(file: file, small: true) + var size: NSSize = NSMakeSize(60, 60) + size = file.dimensions?.size.aspectFitted(size) ?? size + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedAnimatedStickerRepresentation(thumb: true, size: size), complete: false, fetch: false, attemptSynchronously: synchronousLoad) - var thumbnailImage: CGImage? - if let idata = data, let imageSource = CGImageSourceCreateWithData(idata as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { - thumbnailImage = image - } - var blurredThumbnailImage: CGImage? - - if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 300.0, height: 300.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { ctx in - ctx.interpolationQuality = .none - - ctx.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() - - if cornerRadius > 0 { - - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 2.0) - - thumbnailContext.withContext({ (ctx) in - let minx:CGFloat = 0, midx = thumbnailContextSize.width/2.0, maxx = thumbnailContextSize.width - let miny:CGFloat = 0, midy = thumbnailContextSize.height/2.0, maxy = thumbnailContextSize.height - - ctx.move(to: NSMakePoint(minx, midy)) - ctx.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: cornerRadius) - ctx.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: cornerRadius) - - ctx.closePath() - ctx.clip() - - ctx.draw(blurredThumbnailImage!, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(loadedData) + } else { + let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedAnimatedStickerRepresentation(thumb: true, size: size), complete: false) - }) - - blurredThumbnailImage = thumbnailContext.generateImage() - } + return Signal { subscriber in + let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + + let disposable = (thumbnailData + |> map { thumbnailData -> Data? in + return thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetchThumbnail.dispose() + disposable.dispose() + } + } + } } - - return blurredThumbnailImage } -func chatMessageVideo(account: Account, video: TelegramMediaFile, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageFileDatas(account: account, file: video) - - return signal |> map { (thumbnailData, fullSizeDataAndPath, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() - let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) - if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { - return context - } +public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, scale: CGFloat, size: NSSize, fetched: Bool = false, onlyFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal { + let signal: Signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad, size: size) + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: arguments.emptyColor == nil) let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + let fittedSize = 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) + //let fittedRect = arguments.drawingRect var fullSizeImage: CGImage? - if let fullSizeDataAndPath = fullSizeDataAndPath { - if fullSizeComplete { - if video.mimeType.hasPrefix("video/") { - let tempFilePath = NSTemporaryDirectory() + "\(fullSizeDataAndPath.nsstring.lastPathComponent).mov" - - _ = try? FileManager.default.removeItem(atPath: tempFilePath) - _ = try? FileManager.default.linkItem(atPath: fullSizeDataAndPath, toPath: tempFilePath) - - let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) - let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) - imageGenerator.appliesPreferredTrackTransform = true - - - if let image = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) { - fullSizeImage = image - } - - } - /*let options: [NSString: NSObject] = [ - kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), - kCGImageSourceCreateThumbnailFromImageAlways: true - ] - if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { - fullSizeImage = image - }*/ - } else { - /*let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFDataRef, fullSizeData.length >= fullTotalSize) - - var options: [NSString : NSObject!] = [:] - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionaryRef) { - fullSizeImage = image - }*/ + if let fullSizeData = data.fullSizeData, data.fullSizeComplete { + if let image = NSImage(data: fullSizeData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + fullSizeImage = image } } var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { - thumbnailImage = image + if fullSizeImage == nil, let thumbnailData = data.thumbnailData { + if let image = NSImage(data: thumbnailData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + thumbnailImage = image + } } var blurredThumbnailImage: CGImage? + let thumbnailInset: CGFloat = 10.0 if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + let thumbnailSize = thumbnailImage.size + var thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailDrawingSize = thumbnailContextSize + thumbnailContextSize.width += thumbnailInset * 2.0 + thumbnailContextSize.height += thumbnailInset * 2.0 + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) + thumbnailContext.withFlippedContext(isHighQuality: false, { c in + let cgImage = thumbnailImage + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + c.draw(cgImage, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + }) + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } - context.withContext { c in - c.setBlendMode(.copy) - if arguments.boundingSize != arguments.imageSize { - c.fill(arguments.drawingRect) + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in + if let color = arguments.emptyColor { + c.setBlendMode(.normal) + switch color { + case let .color(color): + c.setFillColor(color.cgColor) + default: + break + } + c.fill(drawingRect) + } else { + c.setBlendMode(.copy) } - c.setBlendMode(.copy) - if let blurredThumbnailImage = blurredThumbnailImage { + if let blurredThumbnailImage = blurredThumbnailImage, fullSizeImage == nil { c.interpolationQuality = .low - c.draw(blurredThumbnailImage, in: fittedRect) + let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) + c.draw(blurredThumbnailImage, in: fittedRect.insetBy(dx: -thumbnailScaledInset, dy: -thumbnailScaledInset)) } if let fullSizeImage = fullSizeImage { + let cgImage = fullSizeImage c.setBlendMode(.normal) c.interpolationQuality = .medium - c.draw(fullSizeImage, in: fittedRect) + + c.draw(cgImage, in: fittedRect) } - } - - addCorners(context, arguments: arguments, scale:scale) + }) return context - } + }) } } -private func chatSecretMessageVideoData(account: Account, file: TelegramMediaFile) -> Signal { - if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) { - let thumbnailResource = smallestRepresentation.resource - - let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(thumbnailResource, tag: TelegramMediaResourceFetchTag(statsCategory: .video)) - - let thumbnail = Signal { subscriber in - let fetchedDisposable = fetchedThumbnail.start() - let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() - } - } - return thumbnail - } else { - return .single(nil) - } -} -func chatSecretMessageVideo(account: Account, video: TelegramMediaFile, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatSecretMessageVideoData(account: account, file: video) +public func chatMessageDiceSticker(postbox: Postbox, file: TelegramMediaFile, emoji: String, value: String, scale: CGFloat, size: NSSize, synchronousLoad: Bool = false) -> Signal { - return signal |> map { thumbnailData in - return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) - if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { - return context - } + + let signal: Signal = postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedDiceRepresentation(emoji: emoji, value: value, size: size), complete: true, fetch: true, attemptSynchronously: synchronousLoad) |> map { data in + if data.complete { + return ImageRenderData(nil, try? Data(contentsOf: URL(fileURLWithPath: data.path)), true) + } else { + return ImageRenderData(nil, nil, false) + } + } |> runOn(synchronousLoad ? .mainQueue() : resourcesQueue) + + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: arguments.emptyColor == nil) let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + let fittedSize = 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) + //let fittedRect = arguments.drawingRect - /*var fullSizeImage: CGImage? - if let fullSizeDataAndPath = fullSizeDataAndPath { - if fullSizeComplete { - if video.mimeType.hasPrefix("video/") { - let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" - - _ = try? FileManager.default.removeItem(atPath: tempFilePath) - _ = try? FileManager.default.linkItem(atPath: fullSizeDataAndPath.1, toPath: tempFilePath) - - let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) - let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) - imageGenerator.appliesPreferredTrackTransform = true - if let image = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) { - fullSizeImage = image - } - } - /*let options: [NSString: NSObject] = [ - kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), - kCGImageSourceCreateThumbnailFromImageAlways: true - ] - if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { - fullSizeImage = image - }*/ - } else { - /*let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFDataRef, fullSizeData.length >= fullTotalSize) - - var options: [NSString : NSObject!] = [:] - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionaryRef) { - fullSizeImage = image - }*/ - } - }*/ - var blurredImage: CGImage? + var fullSizeImage: CGImage? + if let fullSizeData = data.fullSizeData, data.fullSizeComplete { + if let image = NSImage(data: fullSizeData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + fullSizeImage = image + } + } - if blurredImage == nil { - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { - let thumbnailSize = CGSize(width: image.width, height: image.height) - let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in - c.interpolationQuality = .none - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) - let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) - thumbnailContext2.withContext { c in - c.interpolationQuality = .none - if let image = thumbnailContext.generateImage() { - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) - } - } - telegramFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) - - blurredImage = thumbnailContext2.generateImage() + var thumbnailImage: CGImage? + if fullSizeImage == nil, let thumbnailData = data.thumbnailData { + if let image = NSImage(data: thumbnailData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + thumbnailImage = image } } - context.withContext { c in - c.setBlendMode(.copy) - if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) - c.fill(arguments.drawingRect) + var blurredThumbnailImage: CGImage? + let thumbnailInset: CGFloat = 10.0 + if let thumbnailImage = thumbnailImage { + let thumbnailSize = thumbnailImage.size + var thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailDrawingSize = thumbnailContextSize + thumbnailContextSize.width += thumbnailInset * 2.0 + thumbnailContextSize.height += thumbnailInset * 2.0 + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) + thumbnailContext.withFlippedContext(isHighQuality: false, { c in + let cgImage = thumbnailImage + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + c.draw(cgImage, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + }) + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in + if let color = arguments.emptyColor { + c.setBlendMode(.normal) + switch color { + case let .color(color): + c.setFillColor(color.cgColor) + default: + break + } + c.fill(drawingRect) + } else { + c.setBlendMode(.copy) } - c.setBlendMode(.copy) - if let cgImage = blurredImage { + if let blurredThumbnailImage = blurredThumbnailImage, fullSizeImage == nil { c.interpolationQuality = .low - c.draw(cgImage, in: fittedRect) + let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) + c.draw(blurredThumbnailImage, in: fittedRect.insetBy(dx: -thumbnailScaledInset, dy: -thumbnailScaledInset)) } - if !arguments.insets.left.isEqual(to: 0.0) { - c.clear(CGRect(origin: CGPoint(), size: CGSize(width: arguments.insets.left, height: context.size.height))) - } - if !arguments.insets.right.isEqual(to: 0.0) { - c.clear(CGRect(origin: CGPoint(x: context.size.width - arguments.insets.right, y: 0.0), size: CGSize(width: arguments.insets.right, height: context.size.height))) + if let fullSizeImage = fullSizeImage { + let cgImage = fullSizeImage + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + c.draw(cgImage, in: fittedRect) } - } - - addCorners(context, arguments: arguments, scale: scale) + }) return context - } + }) } } - +private func chatMessageStickerPackThumbnailData(postbox: Postbox, representation: TelegramMediaImageRepresentation, synchronousLoad: Bool) -> Signal { + let resource = representation.resource + + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: CGSize(width: 120.0, height: 120.0)), complete: false, fetch: false, attemptSynchronously: synchronousLoad) + + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(loadedData) + } else { + let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: CGSize(width: 120.0, height: 120.0)), complete: false) + |> map { next in + return (!next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) + } + + return Signal { subscriber in + let fetch: Disposable? = nil + let disposable = fullSizeData.start(next: { next in + subscriber.putNext(next.0) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetch?.dispose() + disposable.dispose() + } + } + } + } +} + + + + +public func chatMessageStickerPackThumbnail(postbox: Postbox, representation: TelegramMediaImageRepresentation, scale: CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessageStickerPackThumbnailData(postbox: postbox, representation: representation, synchronousLoad: synchronousLoad) + + return signal + |> map { fullSizeData in + return ImageDataTransformation(data: ImageRenderData.init(nil, fullSizeData, fullSizeData != nil), execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: arguments.emptyColor == nil) + + let drawingRect = arguments.drawingRect + let fittedSize = 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 fullSizeImage: CGImage? + if let fullSizeData = fullSizeData { + if let image = NSImage(data: fullSizeData)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + fullSizeImage = image + } + } + + context.withFlippedContext(isHighQuality: fullSizeImage != nil, { c in + if let color = arguments.emptyColor { + c.setBlendMode(.normal) + switch color { + case let .color(color): + c.setFillColor(color.cgColor) + default: + break + } + c.fill(drawingRect) + } else { + c.setBlendMode(.copy) + } + + if let fullSizeImage = fullSizeImage { + let cgImage = fullSizeImage + c.setBlendMode(.normal) + c.interpolationQuality = .medium + c.draw(cgImage, in: fittedRect) + } + }) + + return context + }) + } +} + + + +func chatWebpageSnippetPhotoData(account: Account, imageRefence: ImageMediaReference, small:Bool) -> Signal { + if let closestRepresentation = (small ? imageRefence.media.representationForDisplayAtSize(PixelDimensions(120, 120)) : largestImageRepresentation(imageRefence.media.representations)) { + let resourceData = account.postbox.mediaBox.resourceData(closestRepresentation.resource) |> map { next in + return !next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) + } + + return Signal { subscriber in + let disposable = DisposableSet() + disposable.add(resourceData.start(next: { data in + subscriber.putNext(data) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + })) + //account.postbox.mediaBox.fetchedResource(closestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file) + + disposable.add(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: imageRefence.resourceReference(closestRepresentation.resource), statsCategory: .file).start()) + return disposable + } + } else { + return .never() + } +} + +func chatWebpageSnippetPhoto(account: Account, imageReference: ImageMediaReference, scale:CGFloat, small:Bool, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil) -> Signal { + let signal = chatMessagePhotoDatas(postbox: account.postbox, imageReference: imageReference, synchronousLoad: synchronousLoad, secureIdAccessContext: secureIdAccessContext) + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) + + let fullSizeData = data.fullSizeData + let thumbnailData = data.thumbnailData + + let drawingRect = arguments.drawingRect + var fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize) + switch arguments.resizeMode { + case .none: + break + default: + fittedSize = fittedSize.fitted(arguments.imageSize) + } + var fittedRect = CGRect(origin: CGPoint(x: floorToScreenPixels(System.backingScale, drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0), y: floorToScreenPixels(System.backingScale, drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0)), size: fittedSize) + // + // let drawingRect = arguments.drawingRect + // var fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + // var 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 fullSizeImage: CGImage? + if let fullSizeData = fullSizeData { + if data.fullSizeComplete { + let options = NSMutableDictionary() + // options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + switch arguments.resizeMode { + case .none: + fittedSize = image.backingSize.aspectFilled(arguments.boundingSize)//.fitted(image.backingSize) + 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) + default: + break + } + } + } else { + let imageSource = CGImageSourceCreateIncremental(nil) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, data.fullSizeComplete) + + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + switch arguments.resizeMode { + case .none: + fittedSize = image.backingSize.aspectFilled(arguments.boundingSize)//.fitted(image.backingSize) + 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) + default: + break + } + } + } + } + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + + var thumbnailImage: CGImage? + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { + thumbnailImage = image + } + + var blurredThumbnailImage: CGImage? + if let thumbnailImage = thumbnailImage { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in + c.setBlendMode(.copy) + + if arguments.boundingSize != arguments.imageSize { + switch arguments.resizeMode { + case .blurBackground: + let blurSourceImage = thumbnailImage ?? fullSizeImage + + if let fullSizeImage = blurSourceImage { + let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredImage = thumbnailContext.generateImage() { + let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .low + c.draw(blurredImage, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) + c.setBlendMode(.normal) + c.setFillColor(theme.colors.background.withAlphaComponent(0.5).cgColor) + c.fill(arguments.drawingRect) + c.setBlendMode(.copy) + } + } else { + c.setBlendMode(.normal) + c.setFillColor(theme.colors.grayForeground.cgColor) + c.fill(arguments.drawingRect) + } + case let .fill(color): + c.setBlendMode(.normal) + c.setFillColor(color.cgColor) + c.fill(arguments.drawingRect) + case .fillTransparent: + c.setBlendMode(.normal) + c.setFillColor(theme.colors.transparentBackground.cgColor) + c.fill(arguments.drawingRect) + case .none: + c.setBlendMode(.normal) + c.setFillColor(theme.colors.grayForeground.cgColor) + c.fill(arguments.drawingRect) + case .imageColor: + break + } + } + + c.setBlendMode(.copy) + if let cgImage = blurredThumbnailImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + + if blurredThumbnailImage == nil && fullSizeImage == nil && arguments.boundingSize == arguments.imageSize { + c.setBlendMode(.normal) + c.setFillColor(theme.colors.grayForeground.cgColor) + c.fill(arguments.drawingRect) + } + }) + + addCorners(context, arguments: arguments, scale:scale) + + return context + }) + } +} + + + +func chatMessagePhotoStatus(account: Account, photo: TelegramMediaImage, approximateSynchronousValue: Bool = false) -> Signal { + if let largestRepresentation = photo.representationForDisplayAtSize(PixelDimensions(1280, 1280)) { + if largestRepresentation.resource is LocalFileReferenceMediaResource { + return .single(.Local) + } else { + return account.postbox.mediaBox.resourceStatus(largestRepresentation.resource, approximateSynchronousValue: approximateSynchronousValue) + } + } else { + return .never() + } +} + +func chatMessagePhotoInteractiveFetched(account: Account, imageReference: ImageMediaReference, toRepresentationSize: NSSize = NSMakeSize(1280, 1280)) -> Signal { + if let largestRepresentation = imageReference.media.representationForDisplayAtSize(PixelDimensions(toRepresentationSize)) { + return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: imageReference.resourceReference(largestRepresentation.resource), statsCategory: .image) |> `catch` { _ in return .complete() } |> map {_ in} + } else { + return .never() + } +} + +func chatMessagePhotoCancelInteractiveFetch(account: Account, photo: TelegramMediaImage) { + if let largestRepresentation = largestRepresentationForPhoto(photo) { + return account.postbox.mediaBox.cancelInteractiveResourceFetch(largestRepresentation.resource) + } +} + +func fileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal { + return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource), statsCategory: .file) |> `catch` { _ in return .complete() } |> map {_ in} //account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)) |> map {_ in} +} + +func fileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { + account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) +} + + +public func blurImage(_ data:Data?, _ s:NSSize, cornerRadius:CGFloat = 0) -> CGImage? { + + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + + var thumbnailImage: CGImage? + if let idata = data, let imageSource = CGImageSourceCreateWithData(idata as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { + thumbnailImage = image + } + var blurredThumbnailImage: CGImage? + + if let thumbnailImage = thumbnailImage { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 300.0, height: 300.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withContext { ctx in + ctx.interpolationQuality = .none + + ctx.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + + if cornerRadius > 0 { + + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 2.0) + + thumbnailContext.withContext({ (ctx) in + let minx:CGFloat = 0, midx = thumbnailContextSize.width/2.0, maxx = thumbnailContextSize.width + let miny:CGFloat = 0, midy = thumbnailContextSize.height/2.0, maxy = thumbnailContextSize.height + + ctx.move(to: NSMakePoint(minx, midy)) + ctx.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: cornerRadius) + + ctx.closePath() + ctx.clip() + + ctx.draw(blurredThumbnailImage!, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + + }) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + } + + return blurredThumbnailImage +} + + + +private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaReference, thumbnailSize: Bool = false, onlyFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal { + + let fetchedFullSize = postbox.mediaBox.cachedResourceRepresentation(fileReference.media.resource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: true, attemptSynchronously: synchronousLoad) + + let maybeFullSize = postbox.mediaBox.cachedResourceRepresentation(fileReference.media.resource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: false, attemptSynchronously: synchronousLoad) + + let signal = maybeFullSize + |> take(1) + |> mapToSignal { maybeData -> Signal in + if maybeData.complete { + let loadedData: Data? + loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(ImageRenderData(nil, loadedData, true)) + } else { + + + let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) + let fetchedThumbnail: Signal + if let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + fetchedThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: fileReference.resourceReference(smallestRepresentation.resource), statsCategory: .image) |> `catch` { _ in return .complete() } + } else { + fetchedThumbnail = .complete() + } + + + + let mainThumbnail = Signal { subscriber in + if let decodedThumbnailData = decodedThumbnailData { + subscriber.putNext(ImageRenderData(decodedThumbnailData, nil, true)) + } + + let fetchedDisposable = fetchedThumbnail.start() + var thumbnailDisposable: Disposable? = nil + if let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + thumbnailDisposable = postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(ImageRenderData(!next.complete ? decodedThumbnailData : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), nil, !next.complete)) + }, error: subscriber.putError, completed: subscriber.putCompletion) + } else if decodedThumbnailData == nil { + subscriber.putNext(ImageRenderData(nil, nil, true)) + } + + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable?.dispose() + } + } + + let thumbnail = mainThumbnail + + let fullSizeData: Signal + + fullSizeData = fetchedFullSize + |> map { next -> ImageRenderData in + return ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + } + + return thumbnail + |> mapToSignal { thumbnailData in + let isThumb = thumbnailData.fullSizeComplete + return fullSizeData + |> map { fullSizeData in + if !isThumb && !fullSizeData.fullSizeComplete { + return ImageRenderData(nil, thumbnailData.thumbnailData, false) + } else { + return ImageRenderData(fullSizeData.fullSizeComplete ? nil : thumbnailData.thumbnailData, fullSizeData.fullSizeData, fullSizeData.fullSizeComplete) + } + } + } + } + } + return signal + + +// let image = TelegramMediaImage(imageId: fileReference.media.id ?? MediaId(namespace: 0, id: 0), representations: fileReference.media.previewRepresentations, immediateThumbnailData: fileReference.media.immediateThumbnailData, reference: nil, partialReference: fileReference.media.partialReference) +// let imageReference: ImageMediaReference +// switch fileReference { +// case let .message(message, _): +// imageReference = ImageMediaReference.message(message: message, media: image) +// case .savedGif: +// imageReference = ImageMediaReference.savedGif(media: image) +// case .standalone: +// imageReference = ImageMediaReference.standalone(media: image) +// case let .stickerPack(stickerPack, _): +// imageReference = ImageMediaReference.stickerPack(stickerPack: stickerPack, media: image) +// case let .webPage(webPage, _): +// imageReference = ImageMediaReference.webPage(webPage: webPage, media: image) +// } +// +// return chatMessagePhotoDatas(postbox: postbox, imageReference: imageReference, autoFetchFullSize: true, synchronousLoad: synchronousLoad) +// +// let fullSizeResource = fileReference.media.resource +// +// let thumbnailResource = smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource +// +// let maybeFullSize = postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: false, attemptSynchronously: synchronousLoad) +// let fetchedFullSize = postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: true, attemptSynchronously: synchronousLoad) +// +// let signal = maybeFullSize +// |> take(1) +// |> mapToSignal { maybeData -> Signal<(Atomic, (Atomic, String)?, Bool), NoError> in +// if maybeData.complete { +// let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) +// +// return .single((Atomic(value: nil), loadedData == nil ? nil : (Atomic(value: loadedData!), maybeData.path), true)) +// } else { +// let thumbnail: Signal, NoError> +// if onlyFullSize { +// thumbnail = .single(Atomic(value: nil)) +// } else if let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) { +// thumbnail = .single(Atomic(value: decodedThumbnailData)) +// } else if let thumbnailResource = thumbnailResource { +// thumbnail = Signal { subscriber in +// let fetchedDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: fileReference.resourceReference(thumbnailResource), statsCategory: .video).start() +// let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: synchronousLoad).start(next: { next in +// subscriber.putNext(Atomic(value: next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))) +// }, error: subscriber.putError, completed: subscriber.putCompletion) +// +// return ActionDisposable { +// fetchedDisposable.dispose() +// thumbnailDisposable.dispose() +// } +// } +// } else { +// thumbnail = .single(Atomic(value: nil)) +// } +// +// let fullSizeDataAndPath = Signal { subscriber in +// let dataDisposable = fetchedFullSize.start(next: { next in +// subscriber.putNext(next) +// }, completed: { +// subscriber.putCompletion() +// }) +// //let fetchedDisposable = fetchedPartialVideoThumbnailData(postbox: postbox, fileReference: fileReference).start() +// return ActionDisposable { +// dataDisposable.dispose() +// //fetchedDisposable.dispose() +// } +// } |> map { next -> ((Atomic, String)?, Bool) in +// let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) +// return (data == nil ? nil : (Atomic(value: data!), next.path), next.complete) +// } +// +// return thumbnail +// |> mapToSignal { thumbnailData in +// return fullSizeDataAndPath +// |> map { (dataAndPath, complete) in +// return (thumbnailData, dataAndPath, complete) +// } +// } +// } +// } |> filter({ +// if onlyFullSize { +// return $0.1 != nil || $0.2 +// } else { +// return true//$0.0 != nil || $0.1 != nil || $0.2 +// } +// }) +// +// return signal +} + + + +func chatMessageVideo(postbox: Postbox, fileReference: FileMediaReference, scale: CGFloat, synchronousLoad: Bool = false) -> Signal { + return mediaGridMessageVideo(postbox: postbox, fileReference: fileReference, scale: scale, synchronousLoad: synchronousLoad) +} + + +private func chatSecretMessageVideoData(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal { + if let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + let thumbnailResource = smallestRepresentation.resource + + let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(thumbnailResource), statsCategory: .video) + + let thumbnail = Signal { subscriber in + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(ImageRenderData(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path)), nil, true)) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } + } + return thumbnail + } else { + return .single(ImageRenderData(nil, nil, false)) + } +} + +func chatSecretMessageVideo(account: Account, fileReference: FileMediaReference, scale:CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatSecretMessageVideoData(account: account, fileReference: fileReference, synchronousLoad: synchronousLoad) + + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { + return context + } + + let thumbnailData = data.thumbnailData + + 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 blurredImage: CGImage? + + if blurredImage == nil { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { + let thumbnailSize = CGSize(width: image.width, height: image.height) + let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) + let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) + thumbnailContext2.withContext { c in + c.interpolationQuality = .none + if let image = thumbnailContext.generateImage() { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + } + } + telegramFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) + + blurredImage = thumbnailContext2.generateImage() + } + } + + context.withContext({ c in + c.setBlendMode(.copy) + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + //c.setFillColor(NSColor(white: 0.0, alpha: 0.4).cgColor) + c.fill(arguments.drawingRect) + } + + c.setBlendMode(.copy) + if let cgImage = blurredImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + } + + if !arguments.insets.left.isEqual(to: 0.0) { + c.clear(CGRect(origin: CGPoint(), size: CGSize(width: arguments.insets.left, height: context.size.height))) + } + if !arguments.insets.right.isEqual(to: 0.0) { + c.clear(CGRect(origin: CGPoint(x: context.size.width - arguments.insets.right, y: 0.0), size: CGSize(width: arguments.insets.right, height: context.size.height))) + } + }) + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + + + private enum Corner: Hashable { case TopLeft(Int), TopRight(Int), BottomLeft(Int), BottomRight(Int) @@ -886,38 +1682,7 @@ private enum Corner: Hashable { } } -private func ==(lhs: Corner, rhs: Corner) -> Bool { - switch lhs { - case let .TopLeft(lhsRadius): - switch rhs { - case let .TopLeft(rhsRadius) where rhsRadius == lhsRadius: - return true - default: - return false - } - case let .TopRight(lhsRadius): - switch rhs { - case let .TopRight(rhsRadius) where rhsRadius == lhsRadius: - return true - default: - return false - } - case let .BottomLeft(lhsRadius): - switch rhs { - case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius: - return true - default: - return false - } - case let .BottomRight(lhsRadius): - switch rhs { - case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius: - return true - default: - return false - } - } -} + private enum Tail: Hashable { case BottomLeft(Int) @@ -961,55 +1726,35 @@ private func ==(lhs: Tail, rhs: Tail) -> Bool { } } -private var cachedCorners: [CGFloat: [Corner: DrawingContext]] = [:] -private let cachedCornersLock = SwiftSignalKitMac.Lock() -private var cachedTails: [Tail: DrawingContext] = [:] -private let cachedTailsLock = SwiftSignalKitMac.Lock() - private func cornerContext(_ corner: Corner, scale:CGFloat) -> DrawingContext { var cached: DrawingContext? - cachedCornersLock.locked { - cached = cachedCorners[scale]?[corner] - } + - if let cached = cached { - return cached - } else { - let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), scale: scale, clear: true) - - context.withContext { c in - c.setBlendMode(.copy) - c.setFillColor(NSColor.black.cgColor) - let rect: CGRect - switch corner { - case let .TopLeft(radius): - rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) - case let .TopRight(radius): - rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) - case let .BottomLeft(radius): - rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) - case let .BottomRight(radius): - rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) - } - c.fillEllipse(in: rect) - } - - cachedCornersLock.locked { - if cachedCorners[scale] == nil { - cachedCorners[scale] = [:] - } - cachedCorners[scale]?[corner] = context + let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), scale: scale, clear: true) + + context.withContext { c in + c.setBlendMode(.copy) + c.setFillColor(NSColor.black.cgColor) + let rect: CGRect + switch corner { + case let .TopLeft(radius): + rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) + case let .TopRight(radius): + rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) + case let .BottomLeft(radius): + rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) + case let .BottomRight(radius): + rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) } - return context + c.fillEllipse(in: rect) } + return context } private func tailContext(_ tail: Tail, scale:CGFloat) -> DrawingContext { var cached: DrawingContext? - cachedTailsLock.locked { - cached = cachedTails[tail] - } + if let cached = cached { return cached @@ -1053,9 +1798,6 @@ private func tailContext(_ tail: Tail, scale:CGFloat) -> DrawingContext { c.fillEllipse(in: rect) } - cachedCornersLock.locked { - cachedTails[tail] = context - } return context } } @@ -1066,24 +1808,24 @@ private func addCorners(_ context: DrawingContext, arguments: TransformImageArgu let corners = arguments.corners let drawingRect = arguments.drawingRect - if case let .Corner(radius) = corners.topLeft, radius > CGFloat(FLT_EPSILON) { + if case let .Corner(radius) = corners.topLeft, radius > CGFloat.ulpOfOne { let corner = cornerContext(.TopLeft(Int(radius)), scale:scale) context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY)) } - if case let .Corner(radius) = corners.topRight, radius > CGFloat(FLT_EPSILON) { + if case let .Corner(radius) = corners.topRight, radius > CGFloat.ulpOfOne { let corner = cornerContext(.TopRight(Int(radius)), scale:scale) context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY)) } switch corners.bottomLeft { case let .Corner(radius): - if radius > CGFloat(FLT_EPSILON) { + if radius > CGFloat.ulpOfOne { let corner = cornerContext(.BottomLeft(Int(radius)), scale:scale) context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius)) } case let .Tail(radius): - if radius > CGFloat(FLT_EPSILON) { + if radius > CGFloat.ulpOfOne { let tail = tailContext(.BottomLeft(Int(radius)), scale:scale) let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0)) context.withContext { c in @@ -1097,12 +1839,12 @@ private func addCorners(_ context: DrawingContext, arguments: TransformImageArgu switch corners.bottomRight { case let .Corner(radius): - if radius > CGFloat(FLT_EPSILON) { + if radius > CGFloat.ulpOfOne { let corner = cornerContext(.BottomRight(Int(radius)), scale:scale) context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius)) } case let .Tail(radius): - if radius > CGFloat(FLT_EPSILON) { + if radius > CGFloat.ulpOfOne { let tail = tailContext(.BottomRight(Int(radius)), scale:scale) context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius - 3.0, y: drawingRect.maxY - radius)) } @@ -1110,14 +1852,18 @@ private func addCorners(_ context: DrawingContext, arguments: TransformImageArgu } -func mediaGridMessagePhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessagePhotoDatas(account: account, photo: photo, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true) +func mediaGridMessagePhoto(account: Account, imageReference: ImageMediaReference, scale:CGFloat) -> Signal { + let signal = chatMessagePhotoDatas(postbox: account.postbox, imageReference: imageReference, fullRepresentationSize: CGSize(width: 240, height: 240), autoFetchFullSize: true) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - return { arguments in + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) + let thumbnailData = data.thumbnailData + let fullSizeData = data.fullSizeData + let fullSizeComplete = data.fullSizeComplete + 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) @@ -1128,7 +1874,9 @@ func mediaGridMessagePhoto(account: Account, photo: TelegramMediaImage, scale:CG let options = NSMutableDictionary() options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } else { @@ -1142,9 +1890,11 @@ func mediaGridMessagePhoto(account: Account, photo: TelegramMediaImage, scale:CG } } } - + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { thumbnailImage = image } @@ -1157,13 +1907,14 @@ func mediaGridMessagePhoto(account: Account, photo: TelegramMediaImage, scale:CG c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } - context.withContext { c in + context.withContext(isHighQuality: fullSizeImage != nil, { c in c.setBlendMode(.copy) + c.setFillColor(theme.colors.grayBackground.cgColor) if arguments.boundingSize != arguments.imageSize { c.fill(arguments.drawingRect) } @@ -1179,70 +1930,69 @@ func mediaGridMessagePhoto(account: Account, photo: TelegramMediaImage, scale:CG c.interpolationQuality = .medium c.draw(fullSizeImage, in: fittedRect) } - } + + if arguments.boundingSize == arguments.imageSize && fullSizeImage == nil && blurredThumbnailImage == nil { + c.setBlendMode(.normal) + c.fill(arguments.drawingRect) + } + }) addCorners(context, arguments: arguments, scale:scale) return context - } + }) } } -func mediaGridMessageVideo(account: Account, file: TelegramMediaFile, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageFileDatas(account: account, file: file) + + +func chatMessageVideoThumbnail(account: Account, fileReference: FileMediaReference, scale: CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessageVideoDatas(postbox: account.postbox, fileReference: fileReference, thumbnailSize: true, synchronousLoad: synchronousLoad) - return signal |> map { (thumbnailData, fullSizeDataAndPath, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() - let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) - if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { - return context - } + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + + let thumbnailData = data.thumbnailData + let fullSizeData = data.fullSizeData + let fullSizeComplete = data.fullSizeComplete + let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + var fittedSize = arguments.imageSize + if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.width = arguments.boundingSize.width + } + if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.height = arguments.boundingSize.height + } + 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 fullSizeImage: CGImage? - if let fullSizeDataAndPath = fullSizeDataAndPath { + if let fullSizeData = fullSizeData { if fullSizeComplete { - if file.mimeType.hasPrefix("video/") { - let tempFilePath = NSTemporaryDirectory() + "\(fullSizeDataAndPath.nsstring.lastPathComponent).mov" - - _ = try? FileManager.default.removeItem(atPath: tempFilePath) - _ = try? FileManager.default.linkItem(atPath: fullSizeDataAndPath, toPath: tempFilePath) - - let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) - let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 200, height: 200) - imageGenerator.appliesPreferredTrackTransform = true - - - if let image = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) { - fullSizeImage = image - } + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image } - /*let options: [NSString: NSObject] = [ - kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), - kCGImageSourceCreateThumbnailFromImageAlways: true - ] - if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { - fullSizeImage = image - }*/ } else { - /*let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFDataRef, fullSizeData.length >= fullTotalSize) - - var options: [NSString : NSObject!] = [:] - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionaryRef) { - fullSizeImage = image - }*/ + let imageSource = CGImageSourceCreateIncremental(nil) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) + + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } } } - + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { thumbnailImage = image } @@ -1251,7 +2001,7 @@ func mediaGridMessageVideo(account: Account, file: TelegramMediaFile, scale:CGFl let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in + thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } @@ -1260,33 +2010,178 @@ func mediaGridMessageVideo(account: Account, file: TelegramMediaFile, scale:CGFl blurredThumbnailImage = thumbnailContext.generateImage() } - context.withContext { c in + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in c.setBlendMode(.copy) - if arguments.boundingSize != arguments.imageSize { + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + //c.setFillColor(NSColor(white: 0.0, alpha: 0.4).cgColor) c.fill(arguments.drawingRect) } c.setBlendMode(.copy) - if let blurredThumbnailImage = blurredThumbnailImage { + if let cgImage = blurredThumbnailImage { c.interpolationQuality = .low - c.draw(blurredThumbnailImage, in: fittedRect) + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) } if let fullSizeImage = fullSizeImage { - c.setBlendMode(.normal) c.interpolationQuality = .medium c.draw(fullSizeImage, in: fittedRect) } - } + }) - addCorners(context, arguments: arguments, scale:scale) + addCorners(context, arguments: arguments, scale: scale) return context - } + }) } } +func mediaGridMessageVideo(postbox: Postbox, fileReference: FileMediaReference, scale: CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessageVideoDatas(postbox: postbox, fileReference: fileReference, synchronousLoad: synchronousLoad) + + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + + let thumbnailData = data.thumbnailData + let fullSizeData = data.fullSizeData + let fullSizeComplete = data.fullSizeComplete + + let drawingRect = arguments.drawingRect + var fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + if fittedSize.width < drawingRect.size.width && fittedSize.width >= drawingRect.size.width - 2.0 { + fittedSize.width = drawingRect.size.width + } + if fittedSize.height < drawingRect.size.height && fittedSize.height >= drawingRect.size.height - 2.0 { + fittedSize.height = drawingRect.size.height + } + 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 fullSizeImage: CGImage? + if let fullSizeData = fullSizeData { + if fullSizeComplete { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } else { + let imageSource = CGImageSourceCreateIncremental(nil) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) + + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } + } + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + var thumbnailImage: CGImage? + if fullSizeImage == nil, let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { + thumbnailImage = image + } + + var blurredThumbnailImage: CGImage? + if let thumbnailImage = thumbnailImage { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext(isHighQuality: fullSizeImage != nil, { c in + c.setBlendMode(.copy) + if arguments.boundingSize != arguments.imageSize { + switch arguments.resizeMode { + case .blurBackground: + let blurSourceImage = thumbnailImage ?? fullSizeImage + + if let image = blurSourceImage { + let thumbnailSize = CGSize(width: image.width, height: image.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + // telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + if let blurredImage = thumbnailContext.generateImage() { + let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .medium + c.draw(blurredImage, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) + c.setBlendMode(.normal) + c.setFillColor(theme.colors.background.withAlphaComponent(0.5).cgColor) + c.fill(arguments.drawingRect) + c.setBlendMode(.copy) + } + + let value = arguments.imageSize.aspectFitted(arguments.boundingSize) + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: NSMakeRect((arguments.boundingSize.width - value.width) / 2, (arguments.boundingSize.height - value.height) / 2, value.width, value.height)) + } + + + } else { + c.fill(arguments.drawingRect) + } + case let .fill(color): + c.setFillColor(color.cgColor) + c.fill(arguments.drawingRect) + case .fillTransparent: + c.setFillColor(theme.colors.transparentBackground.cgColor) + c.fill(arguments.drawingRect) + case .none: + c.setBlendMode(.copy) + if let cgImage = blurredThumbnailImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + case .imageColor: + break + } + } else { + c.setBlendMode(.copy) + if let cgImage = blurredThumbnailImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + } + }) + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + private func imageFromAJpeg(data: Data) -> (CGImage, CGImage)? { if let (colorData, alphaData) = data.withUnsafeBytes({ (bytes: UnsafePointer) -> (Data, Data)? in @@ -1306,14 +2201,16 @@ private func imageFromAJpeg(data: Data) -> (CGImage, CGImage)? { let alphaData = data.subdata(in: (4 + Int(colorSize) + 4) ..< (4 + Int(colorSize) + 4 + Int(alphaSize))) return (colorData, alphaData) }) { - - let sourceColor:CGImageSource? = CGImageSourceCreateWithData(colorData as CFData, nil); - let sourceAlpha:CGImageSource? = CGImageSourceCreateWithData(alphaData as CFData, nil); + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + let sourceColor:CGImageSource? = CGImageSourceCreateWithData(colorData as CFData, options); + let sourceAlpha:CGImageSource? = CGImageSourceCreateWithData(alphaData as CFData, options); if let sourceColor = sourceColor, let sourceAlpha = sourceAlpha { - let colorImage = CGImageSourceCreateImageAtIndex(sourceColor, 0, nil); - let alphaImage = CGImageSourceCreateImageAtIndex(sourceAlpha, 0, nil); + let colorImage = CGImageSourceCreateImageAtIndex(sourceColor, 0, options); + let alphaImage = CGImageSourceCreateImageAtIndex(sourceAlpha, 0, options); if let colorImage = colorImage, let alphaImage = alphaImage { return (colorImage, alphaImage) } @@ -1323,16 +2220,36 @@ private func imageFromAJpeg(data: Data) -> (CGImage, CGImage)? { } -public func putToTemp(image:NSImage, compress: Bool = true) -> Signal { +public func putToTemp(image:NSImage, compress: Bool = true) -> Signal { return Signal { (subscriber) in - let data:Data? = image.tiffRepresentation(using: .jpeg, factor: compress ? 0.83 : 1) - if let data = data { - let imageRep = NSBitmapImageRep(data: data) - let repData = imageRep?.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: compress ? 0.83 : 1]) + // let data:Data? = image.tiffRepresentation(using: .jpeg, factor: compress ? 0.83 : 1) + if let data = image.tiffRepresentation(using: compress ? .jpeg : .none, factor: compress ? 0.83 : 1.0) { let path = NSTemporaryDirectory() + "tg_image_\(arc4random()).jpeg" - try? repData?.write(to: URL(fileURLWithPath: path)) + if compress { + let imageRep = NSBitmapImageRep(data: data) + try? imageRep?.representation(using: .jpeg, properties: [:])?.write(to: URL(fileURLWithPath: path)) + } else { + // try? data.write(to: URL(fileURLWithPath: path)) + + + let options = NSMutableDictionary() + + let mutableData: CFMutableData = NSMutableData() as CFMutableData + + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) { + CGImageDestinationAddImage(colorDestination, image.cgImage(forProposedRect: nil, context: nil, hints: nil)!, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + try? (mutableData as Data).write(to: URL(fileURLWithPath: path)) + } + } + + } + + + + subscriber.putNext(path) } @@ -1346,31 +2263,30 @@ public func putToTemp(image:NSImage, compress: Bool = true) -> Signal Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return Signal { (subscriber) in +public func filethumb(with url:URL, account:Account, scale:CGFloat) -> Signal { + return Signal { (subscriber) in let data = try? Data(contentsOf: url) - subscriber.putNext(data) subscriber.putCompletion() return EmptyDisposable - } |> map({ (data) in - - return { arguments in - + } |> map { data in + return ImageDataTransformation(data: ImageRenderData(data, nil, true), execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale:scale, clear: true) 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 thumb: CGImage? - if let data = data { + if let thumbnailData = data.thumbnailData { let options = NSMutableDictionary() options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { thumb = image } } @@ -1385,20 +2301,24 @@ public func filethumb(with url:URL, account:Account, scale:CGFloat) -> Signal<(T addCorners(context, arguments: arguments, scale:scale) return context - } - }) - |> runOn(account.graphicsThreadPool) + }) + } + } -func chatSecretPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessagePhotoDatas(account: account, photo: photo) +func chatSecretPhoto(account: Account, imageReference: ImageMediaReference, scale:CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessagePhotoDatas(postbox: account.postbox, imageReference: imageReference, synchronousLoad: synchronousLoad) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - return { arguments in + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + let fullSizeData = data.fullSizeData + let thumbnailData = data.thumbnailData + let fullSizeComplete = data.fullSizeComplete + let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { @@ -1416,7 +2336,7 @@ func chatSecretPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) if fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { let thumbnailSize = CGSize(width: image.width, height: image.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) @@ -1451,7 +2371,10 @@ func chatSecretPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) } if blurredImage == nil { - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { let thumbnailSize = CGSize(width: image.width, height: image.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) @@ -1478,7 +2401,7 @@ func chatSecretPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) context.withContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) + //c.setFillColor(NSColor(white: 0.0, alpha: 0.4).cgColor) c.fill(arguments.drawingRect) } @@ -1499,26 +2422,213 @@ func chatSecretPhoto(account: Account, photo: TelegramMediaImage, scale:CGFloat) addCorners(context, arguments: arguments, scale:scale) return context - } + }) } } -func chatMessageImageFile(account: Account, file: TelegramMediaFile, progressive: Bool = false, scale: CGFloat) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageFileDatas(account: account, file: file, progressive: progressive, justThumbail: true) +func chatMessageImageFile(account: Account, fileReference: FileMediaReference, progressive: Bool = false, scale: CGFloat, synchronousLoad: Bool = false) -> Signal { + let signal = chatMessageFileDatas(account: account, fileReference: fileReference, progressive: progressive, justThumbail: true, synchronousLoad: synchronousLoad) - return signal |> map { (thumbnailData, fullSizeDataAndPath, fullSizeComplete) in - return { arguments in - assertNotOnMainThread() + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize)//.fitted(arguments.imageSize) + let fittedRect = CGRect(origin: CGPoint(x: floorToScreenPixels(System.backingScale, drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0), y: floorToScreenPixels(System.backingScale, drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0)), size: fittedSize) + + + + var fullSizeImage: CGImage? + + if let fullSizeData = data.fullSizeData { + let options = NSMutableDictionary() + // options.setValue(max(fittedSize.width * 3, fittedSize.height * 3) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCache as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCacheImmediately as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options) { + if let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } else if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { + fullSizeImage = image + } + } + } + + let options = NSMutableDictionary() + // options.setValue(max(fittedSize.width * 3, fittedSize.height * 3) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCache as String) + options.setValue(false as NSNumber, forKey: kCGImageSourceShouldCacheImmediately as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + + var thumbnailImage: CGImage? + if let thumbnailData = data.thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { + thumbnailImage = image + } + + var blurredThumbnailImage: CGImage? + if let thumbnailImage = thumbnailImage, fullSizeImage == nil { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext(isHighQuality: data.fullSizeData != nil, { c in + //c.setBlendMode(.copy) + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + c.setFillColor(theme.colors.transparentBackground.cgColor) + c.fill(arguments.drawingRect) + } + + // c.setBlendMode(.copy) + if let cgImage = blurredThumbnailImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.setFillColor(theme.colors.transparentBackground.cgColor) + c.fill(fittedRect) + c.draw(fullSizeImage, in: fittedRect) + } + }) + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + + +private func chatMessagePhotoThumbnailDatas(account: Account, imageReference: ImageMediaReference, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil) -> Signal { + let fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0) + if let smallestRepresentation = smallestImageRepresentation(imageReference.media.representations), let largestRepresentation = imageReference.media.representationForDisplayAtSize(PixelDimensions(fullRepresentationSize)) { + + let size = CGSize(width: 160.0, height: 160.0) + let maybeFullSize: Signal + + if largestRepresentation.resource is EncryptedMediaResource { + maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: synchronousLoad) + } else { + maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: size), complete: false, attemptSynchronously: synchronousLoad) + } + + let signal = maybeFullSize + |> take(1) + |> mapToSignal { maybeData -> Signal in + if maybeData.complete { + if largestRepresentation.resource is EncryptedMediaResource, let secureIdAccessContext = secureIdAccessContext { + let loadedData: Data? = decryptedResourceData(data: maybeData, resource: largestRepresentation.resource, params: secureIdAccessContext) + return .single(ImageRenderData(nil, loadedData, true)) + } else { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(ImageRenderData(nil, loadedData, true)) + } + + } else { + + let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: imageReference.resourceReference(smallestRepresentation.resource), statsCategory: .image)//account.postbox.mediaBox.fetchedResource(smallestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) + + let thumbnail = Signal { subscriber in + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(!next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } + } + + let maybeFullData: Signal + + if largestRepresentation.resource is EncryptedMediaResource { + maybeFullData = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: synchronousLoad) + } else { + maybeFullData = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: size), complete: false, attemptSynchronously: synchronousLoad) + } + + let fullSizeData: Signal = maybeFullData + |> map { next -> ImageRenderData in + return ImageRenderData(nil, !next.complete ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + } + + return thumbnail |> mapToSignal { thumbnailData in + return fullSizeData |> map { fullData in + return ImageRenderData(thumbnailData, fullData.fullSizeData, fullData.fullSizeComplete) + } + } + } + } |> filter({ $0.thumbnailData != nil || $0.fullSizeData != nil }) + + return signal + } else { + return .never() + } +} + +func chatMessagePhotoThumbnail(account: Account, imageReference: ImageMediaReference, scale: CGFloat = System.backingScale, synchronousLoad: Bool = false, secureIdAccessContext: SecureIdAccessContext? = nil) -> Signal { + let signal = chatMessagePhotoThumbnailDatas(account: account, imageReference: imageReference, synchronousLoad: synchronousLoad, secureIdAccessContext: secureIdAccessContext) + + return signal |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + + let fullSizeData = data.fullSizeData + let thumbnailData = data.thumbnailData + let fullSizeComplete = data.fullSizeComplete + + let drawingRect = arguments.drawingRect + var fittedSize = arguments.imageSize + if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.width = arguments.boundingSize.width + } + if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.height = arguments.boundingSize.height + } + 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 fullSizeImage: CGImage? + if let fullSizeData = fullSizeData { + if fullSizeComplete { + /*let options = NSMutableDictionary() + options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + }*/ + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } + } var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { thumbnailImage = image } @@ -1527,74 +2637,257 @@ func chatMessageImageFile(account: Account, file: TelegramMediaFile, progressive let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withContext { c in + thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } - context.withContext { c in + context.withFlippedContext(isHighQuality: fullSizeImage != nil, { c in c.setBlendMode(.copy) - if arguments.boundingSize != arguments.imageSize { + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + //c.setFillColor(NSColor(white: 0.0, alpha: 0.4).cgColor) c.fill(arguments.drawingRect) } + c.setBlendMode(.copy) - if let blurredThumbnailImage = blurredThumbnailImage { + if let cgImage = blurredThumbnailImage { c.interpolationQuality = .low - c.draw(blurredThumbnailImage, in: fittedRect) + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + }) + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + +private func builtinWallpaperData() -> Signal { + return Signal { subscriber in + if let filePath = Bundle.main.path(forResource: "builtin-wallpaper-0", ofType: "jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) { + subscriber.putNext(ImageRenderData(nil, data, true)) + } + subscriber.putCompletion() + + return EmptyDisposable + } |> runOn(Queue.concurrentDefaultQueue()) +} + +func settingsBuiltinWallpaperImage(account: Account, scale: CGFloat = 2.0) -> Signal { + return builtinWallpaperData() |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + + var fullSizeImage: CGImage? + if let fullSizeData = data.fullSizeData { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } + if let fullSizeImage = fullSizeImage { + let drawingRect = arguments.drawingRect + var fittedSize = fullSizeImage.size.aspectFilled(drawingRect.size) + if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.width = arguments.boundingSize.width + } + if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.height = arguments.boundingSize.height + } + + 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) + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + } + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + +private func chatWallpaperDatas(account: Account, representations: [TelegramMediaImageRepresentation], file: TelegramMediaFile? = nil, webpage: TelegramMediaWebpage? = nil, slug: String? = nil, autoFetchFullSize: Bool = false, isBlurred: Bool = false, synchronousLoad: Bool = false) -> Signal { + if let smallestRepresentation = smallestImageRepresentation(representations), let largestRepresentation = largestImageRepresentation(representations) { + let maybeFullSize: Signal + if isBlurred { + maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedBlurredWallpaperRepresentation(), complete: false, attemptSynchronously: synchronousLoad) + } else { + maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: synchronousLoad) + } + + let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(ImageRenderData(nil, loadedData, true)) + } else { + let smallReference: MediaResourceReference + let fullReference: MediaResourceReference + if let webpage = webpage, let file = file { + smallReference = MediaResourceReference.media(media: AnyMediaReference.webPage(webPage: WebpageReference(webpage), media: file), resource: smallestRepresentation.resource) + fullReference = MediaResourceReference.media(media: AnyMediaReference.webPage(webPage: WebpageReference(webpage), media: file), resource: largestRepresentation.resource) + } else { + smallReference = MediaResourceReference.wallpaper(wallpaper: slug != nil ? .slug(slug!) : nil, resource: smallestRepresentation.resource) + fullReference = MediaResourceReference.wallpaper(wallpaper: slug != nil ? .slug(slug!) : nil, resource: largestRepresentation.resource) + } + + let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: smallReference, statsCategory: .image) + let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fullReference, statsCategory: .image) + + let thumbnail = Signal { subscriber in + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } + } + + let fullSizeData: Signal + + if autoFetchFullSize { + fullSizeData = Signal { subscriber in + let fetchedFullSizeDisposable = fetchedFullSize.start() + + let fetchData: Signal + if isBlurred { + fetchData = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedBlurredWallpaperRepresentation(), complete: false, attemptSynchronously: synchronousLoad) + } else { + fetchData = account.postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: synchronousLoad) + } + + let fullSizeDisposable = fetchData.start(next: { next in + subscriber.putNext(ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + } else { + fullSizeData = account.postbox.mediaBox.resourceData(largestRepresentation.resource) + |> map { next -> ImageRenderData in + return ImageRenderData(nil, next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + } } + + return thumbnail |> mapToSignal { thumbnailData in + return fullSizeData |> map { fullData in + return ImageRenderData(thumbnailData, fullData.fullSizeData, fullData.fullSizeComplete) + } + } + } + } |> filter({ $0.thumbnailData != nil || $0.fullSizeData != nil }) + + return signal + } else { + return .never() + } +} + +enum PatternWallpaperDrawMode { + case thumbnail + case fastScreen + case screen +} + + +func crossplatformPreview(account: Account, palette: ColorPalette, wallpaper: Wallpaper, webpage: TelegramMediaWebpage? = nil, mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false, scale: CGFloat = 2.0, isBlurred: Bool = false, synchronousLoad: Bool = false) -> Signal { + let signal: Signal = moveWallpaperToCache(postbox: account.postbox, wallpaper: wallpaper) + + return signal |> map { wallpaper in + return ImageDataTransformation(data: ImageRenderData(nil, nil, false), execute: { arguments, data in + + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + let preview = generateThemePreview(for: palette, wallpaper: wallpaper, backgroundMode: generateBackgroundMode(wallpaper, palette: palette, maxSize: WallpaperDimensions.aspectFilled(NSMakeSize(600, 600)))) + + context.withContext { ctx in + ctx.draw(preview, in: arguments.drawingRect) } - addCorners(context, arguments: arguments, scale: scale) + addCorners(context, arguments: arguments, scale: arguments.scale) return context - } + }) } } -private func chatMessagePhotoThumbnailDatas(account: Account, photo: TelegramMediaImage) -> Signal<(Data?, Data?, Bool), NoError> { - let fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0) - if let smallestRepresentation = smallestImageRepresentation(photo.representations), let largestRepresentation = photo.representationForDisplayAtSize(fullRepresentationSize) { +private func patternWallpaperDatas(account: Account, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal { + 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 = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 160.0, height: 160.0)), complete: false) + let size: CGSize? + switch mode { + case .thumbnail: + size = largestRepresentation.dimensions.size.fitted(CGSize(width: 640.0, height: 640.0)) + default: + size = nil + } + let maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: false) - let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in - if maybeData.complete { - let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) - } else { - let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(smallestRepresentation.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) - - let thumbnail = Signal { subscriber in - let fetchedDisposable = fetchedThumbnail.start() - let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) + let signal = maybeFullSize + |> take(1) + |> mapToSignal { maybeData -> Signal in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + return .single(ImageRenderData(nil, loadedData, true)) + } else { + let fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[smallestIndex].reference) + let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[largestIndex].reference) - return ActionDisposable { - fetchedDisposable.dispose() - thumbnailDisposable.dispose() + let thumbnailData = Signal { subscriber in + let fetchedDisposable = fetchedThumbnail.start() + let thumbnailDisposable = account.postbox.mediaBox.cachedResourceRepresentation(representations[smallestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start(next: { next in + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedDisposable.dispose() + thumbnailDisposable.dispose() + } } - } - - let fullSizeData: Signal<(Data?, Bool), NoError> = maybeFullSize - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) - } - - - return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + + let fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + let fetchedFullSizeDisposable = fetchedFullSize.start() + let fullSizeDisposable = account.postbox.mediaBox.cachedResourceRepresentation(representations[largestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start(next: { next in + subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + + return thumbnailData |> mapToSignal { thumbnailData in + return fullSizeData |> map { (fullSizeData, complete) in + return ImageRenderData(thumbnailData, fullSizeData, complete) + } } } - } - } |> filter({ $0.0 != nil || $0.1 != nil }) + } return signal } else { @@ -1602,13 +2895,38 @@ private func chatMessagePhotoThumbnailDatas(account: Account, photo: TelegramMed } } -func chatMessagePhotoThumbnail(account: Account, photo: TelegramMediaImage, scale: CGFloat = System.backingScale) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessagePhotoThumbnailDatas(account: account, photo: photo) - - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in - return { arguments in + + + +private func chatWallpaperInternal(_ signal: Signal, prominent: Bool, scale: CGFloat) -> Signal { + + return signal |> map { data in + + var fullSizeImage: CGImage? = nil + if let fullSizeData = data.fullSizeData, data.fullSizeComplete { + + if prominent { + let options = NSMutableDictionary() + options.setValue(960 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { + fullSizeImage = image + } + } else { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } + } + + + return ImageDataTransformation(data: data, execute: { arguments, data in let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + let thumbnailData = data.thumbnailData + let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { @@ -1619,35 +2937,13 @@ func chatMessagePhotoThumbnail(account: Account, photo: TelegramMediaImage, scal } 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 fullSizeImage: CGImage? - if let fullSizeData = fullSizeData { - if fullSizeComplete { - /*let options = NSMutableDictionary() - options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { - fullSizeImage = image - }*/ - let options = NSMutableDictionary() - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { - fullSizeImage = image - } - } else { - let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) - - let options = NSMutableDictionary() - options[kCGImageSourceShouldCache as NSString] = false as NSNumber - if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { - fullSizeImage = image - } - } - } + var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, options), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) { thumbnailImage = image } @@ -1665,29 +2961,423 @@ func chatMessagePhotoThumbnail(account: Account, photo: TelegramMediaImage, scal blurredThumbnailImage = thumbnailContext.generateImage() } - context.withFlippedContext { c in - c.setBlendMode(.copy) - if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { - //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) - c.fill(arguments.drawingRect) + + if let combinedColor = arguments.emptyColor { + + let colors:[NSColor] + let color: NSColor + let intensity: CGFloat + let rotation: Int32? + switch combinedColor { + case let .color(combinedColor): + color = combinedColor.withAlphaComponent(1.0) + intensity = combinedColor.alpha + colors = [color] + rotation = nil + case let .gradient(top, bottom, r): + color = top.withAlphaComponent(1.0) + intensity = top.alpha + colors = [top, bottom].reversed().map { $0.withAlphaComponent(1.0) } + rotation = r } - c.setBlendMode(.copy) - if let cgImage = blurredThumbnailImage { - c.interpolationQuality = .low - c.draw(cgImage, in: fittedRect) - c.setBlendMode(.normal) + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + context.withFlippedContext { c in + c.setBlendMode(.copy) + if colors.count == 1 { + c.setFillColor(color.cgColor) + c.fill(arguments.drawingRect) + } else { + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + c.saveGState() + c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0) + c.rotate(by: CGFloat(rotation ?? 0) * CGFloat.pi / -180.0) + c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0) + + c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + c.restoreGState() + } + + if let fullSizeImage = fullSizeImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + c.clip(to: fittedRect, mask: fullSizeImage) + + + if colors.count == 1 { + c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor) + c.fill(arguments.drawingRect) + } else { + let gradientColors = colors.map { patternColor(for: $0, intensity: intensity, prominent: prominent).cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + c.saveGState() + c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0) + c.rotate(by: CGFloat(rotation ?? 0) * CGFloat.pi / -180.0) + c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0) + + c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + c.restoreGState() + } + } + } + addCorners(context, arguments: arguments, scale: scale) + return context + + } else { + context.withFlippedContext(isHighQuality: fullSizeImage != nil, { c in + c.setBlendMode(.copy) + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + //c.setFillColor(NSColor(white: 0.0, alpha: 0.4).cgColor) + c.fill(arguments.drawingRect) + } + + c.setBlendMode(.copy) + if let blurredThumbnailImage = blurredThumbnailImage { + c.interpolationQuality = .low + c.draw(blurredThumbnailImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + }) - if let fullSizeImage = fullSizeImage { - c.interpolationQuality = .medium - c.draw(fullSizeImage, in: fittedRect) + addCorners(context, arguments: arguments, scale: scale) + + return context + } + }) + } +} + + +func patternWallpaperImage(account: Account, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, scale: CGFloat = 2.0, autoFetchFullSize: Bool = false) -> Signal { + var prominent = false + if case .thumbnail = mode { + prominent = false + } + return chatWallpaperInternal(patternWallpaperDatas(account: account, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize), prominent: prominent, scale: scale) +} + + +func chatWallpaper(account: Account, representations: [TelegramMediaImageRepresentation], file: TelegramMediaFile? = nil, webpage: TelegramMediaWebpage? = nil, slug: String? = nil, mode: PatternWallpaperDrawMode, isPattern: Bool, autoFetchFullSize: Bool = false, scale: CGFloat = 2.0, isBlurred: Bool = false, synchronousLoad: Bool = false) -> Signal { + var prominent = false + if case .thumbnail = mode { + prominent = false + } + let signal: Signal + if isPattern { + let reps = representations.map { rep -> ImageRepresentationWithReference in + if let webpage = webpage, let file = file { + return ImageRepresentationWithReference(representation: rep, reference: MediaResourceReference.media(media: AnyMediaReference.webPage(webPage: WebpageReference(webpage), media: file), resource: rep.resource)) + } else { + return ImageRepresentationWithReference(representation: rep, reference: MediaResourceReference.wallpaper(wallpaper: slug != nil ? .slug(slug!) : nil, resource: rep.resource)) + } + } + signal = patternWallpaperDatas(account: account, representations: reps, mode: mode, autoFetchFullSize: autoFetchFullSize) + } else { + signal = chatWallpaperDatas(account: account, representations: representations, file: file, webpage: webpage, autoFetchFullSize: autoFetchFullSize, isBlurred: isBlurred, synchronousLoad: synchronousLoad) + } + return chatWallpaperInternal(signal, prominent: prominent, scale: scale) +} + + +func instantPageImageFile(account: Account, fileReference: FileMediaReference, scale: CGFloat, fetched: Bool = false) -> Signal { + return chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false) + |> map { data in + return ImageDataTransformation(data: data, execute: { arguments, data in + assertNotOnMainThread() + let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) + + let drawingRect = arguments.drawingRect + let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) + + var fullSizeImage: CGImage? + var imageOrientation: ImageOrientation = .up + if let fullSizeData = data.fullSizeData { + let options = NSMutableDictionary() + options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, options), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { + imageOrientation = imageOrientationFromSource(imageSource) + fullSizeImage = image + } } + + 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 + if var fullSizeImage = fullSizeImage { + if let color = arguments.emptyColor { + switch color { + case let .color(color): + if imageRequiresInversion(fullSizeImage), let tintedImage = generateTintedImage(image: fullSizeImage, color: color) { + fullSizeImage = tintedImage + } + default: + break + } + } + + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect) + } + } + + addCorners(context, arguments: arguments, scale: scale) + + return context + }) + } +} + +private func rotationFor(_ orientation: ImageOrientation) -> CGFloat { + switch orientation { + case .left: + return CGFloat.pi / 2.0 + case .right: + return -CGFloat.pi / 2.0 + case .down: + return -CGFloat.pi + default: + return 0.0 + } +} + +func drawImage(context: CGContext, image: CGImage, orientation: ImageOrientation, in rect: CGRect) { + var restore = true + var drawRect = rect + switch orientation { + case .left: + fallthrough + case .right: + fallthrough + case .down: + let angle = rotationFor(orientation) + context.saveGState() + context.translateBy(x: rect.midX, y: rect.midY) + context.rotate(by: angle) + context.translateBy(x: -rect.midX, y: -rect.midY) + var t = CGAffineTransform(translationX: rect.midX, y: rect.midY) + t = t.rotated(by: angle) + t = t.translatedBy(x: -rect.midX, y: -rect.midY) + + drawRect = rect.applying(t) + case .leftMirrored: + context.saveGState() + context.translateBy(x: rect.midX, y: rect.midY) + context.rotate(by: -CGFloat.pi / 2.0) + context.translateBy(x: -rect.midX, y: -rect.midY) + var t = CGAffineTransform(translationX: rect.midX, y: rect.midY) + t = t.rotated(by: -CGFloat.pi / 2.0) + t = t.translatedBy(x: -rect.midX, y: -rect.midY) + + drawRect = rect.applying(t) + default: + restore = false + } + context.draw(image, in: drawRect) + if restore { + context.restoreGState() + } +} + + + + + +func mapResourceToAvatarSizes(postbox: Postbox, resource: MediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> { + return postbox.mediaBox.resourceData(resource) + |> take(1) + |> map { data -> [Int: Data] in + guard data.complete, let image = NSImage(contentsOfFile: data.path)?.cgImage(forProposedRect: nil, context: nil, hints: nil) else { + return [:] } - addCorners(context, arguments: arguments, scale: scale) + let options = NSMutableDictionary() + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - return context - } + let colorQuality: Float = 0.6 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + var result: [Int: Data] = [:] + for i in 0 ..< representations.count { + if let scaledImage = generateScaledImage(image: image, size: representations[i].dimensions.size, scale: 1.0) { + + let mutableData: CFMutableData = NSMutableData() as CFMutableData + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, options) { + CGImageDestinationSetProperties(colorDestination, nil) + + CGImageDestinationAddImage(colorDestination, scaledImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + + } + } + + result[i] = mutableData as Data + } + } + return result + } +} + + +public func generateScaledImage(image: CGImage?, size: CGSize, scale: CGFloat? = nil) -> CGImage? { + guard let image = image else { + return nil + } + + return generateImage(size, contextGenerator: { size, context in + context.draw(image, in: CGRect(origin: CGPoint(), size: size)) + }, opaque: true) +} + + +private func imageBuffer(from data: UnsafeMutableRawPointer!, width: vImagePixelCount, height: vImagePixelCount, rowBytes: Int) -> vImage_Buffer { + return vImage_Buffer(data: data, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: rowBytes) +} + +func blurredImage(_ image: CGImage, radius: CGFloat, iterations: Int = 3) -> CGImage? { + guard let providerData = image.dataProvider?.data else { + return nil + } + + if image.size.width <= 0.0 || image.size.height <= 0 || radius <= 0 { + return image + } + + var boxSize = UInt32(radius) + if boxSize % 2 == 0 { + boxSize += 1 + } + + let bytes = image.bytesPerRow * image.height + let inData = malloc(bytes) + var inBuffer = imageBuffer(from: inData, width: vImagePixelCount(image.width), height: vImagePixelCount(image.height), rowBytes: image.bytesPerRow) + + let outData = malloc(bytes) + var outBuffer = imageBuffer(from: outData, width: vImagePixelCount(image.width), height: vImagePixelCount(image.height), rowBytes: image.bytesPerRow) + + let tempSize = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, nil, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend + kvImageGetTempBufferSize)) + let tempData = malloc(tempSize) + + defer { + free(inData) + free(outData) + free(tempData) + } + + let source = CFDataGetBytePtr(providerData) + memcpy(inBuffer.data, source, bytes) + + for _ in 0 ..< iterations { + vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempData, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend)) + + let temp = inBuffer.data + inBuffer.data = outBuffer.data + outBuffer.data = temp } + + let context = image.colorSpace.flatMap { + CGContext(data: inBuffer.data, width: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bytesPerRow: image.bytesPerRow, space: $0, bitmapInfo: image.bitmapInfo.rawValue) + } + + let blurredCGImage = context?.makeImage() + if let blurredCGImage = blurredCGImage { + return blurredCGImage + } else { + return nil + } +} + + + + + +func patternColor(for color: NSColor, intensity: CGFloat, prominent: Bool = false) -> NSColor { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + if brightness > 0.5 { + brightness = max(0.0, brightness * 0.65) + } else { + brightness = max(0.0, min(1.0, 1.0 - brightness * 0.65)) + } + saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation)) + alpha = (prominent ? 0.5 : 0.4) * intensity + return NSColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) +} + + + + +func prepareTextAttachments(_ attachments: [NSTextAttachment]) -> Signal<[URL], NoError> { + return Signal { subscriber in + + var cancelled: Bool = false + + resourcesQueue.async { + var urls:[URL] = [] + + for attachment in attachments { + if cancelled { + for url in urls { + try? FileManager.default.removeItem(at: url) + } + subscriber.putCompletion() + return + } + if let fileWrapper = attachment.fileWrapper { + if let data = fileWrapper.regularFileContents { + if let fileName = fileWrapper.filename { + let path = NSTemporaryDirectory() + fileName + var newPath = path + var i:Int = 0 + if FileManager.default.fileExists(atPath: newPath) { + newPath = path.nsstring.deletingPathExtension + "\(i)." + path.nsstring.pathExtension + i += 1 + } + let url = URL(fileURLWithPath: newPath) + do { + try data.write(to: url) + urls.append(url) + } catch {} + } + } + } + } + subscriber.putNext(urls) + subscriber.putCompletion() + } + + return ActionDisposable { + cancelled = true + } + } |> runOn(prepareQueue) } diff --git a/Telegram-Mac/MessageActionsPanelView.swift b/Telegram-Mac/MessageActionsPanelView.swift index 3a9bd8bf61..792313b3b9 100644 --- a/Telegram-Mac/MessageActionsPanelView.swift +++ b/Telegram-Mac/MessageActionsPanelView.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox @@ -19,9 +20,7 @@ class MessageActionsPanelView: Control, Notifable { private var deleteButton:TitleButton = TitleButton() private var forwardButton:TitleButton = TitleButton() private var countTitle:TitleButton = TitleButton() - - private let loadMessagesDisposable:MetaDisposable = MetaDisposable() - + required init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -39,11 +38,11 @@ class MessageActionsPanelView: Control, Notifable { addSubview(forwardButton) addSubview(countTitle) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } private var buttonActiveStyle:ControlStyle { - return ControlStyle(font:.normal(.header), foregroundColor: theme.colors.grayText, backgroundColor: theme.colors.background, highlightColor: theme.colors.blueIcon) + return ControlStyle(font:.normal(.header), foregroundColor: theme.colors.grayText, backgroundColor: theme.colors.background, highlightColor: theme.colors.accentIcon) } private var deleteButtonActiveStyle:ControlStyle { return ControlStyle(font:.normal(.header), foregroundColor: theme.colors.grayText, backgroundColor: theme.colors.background, highlightColor: theme.colors.redUI) @@ -82,40 +81,24 @@ class MessageActionsPanelView: Control, Notifable { forwardButton.userInteractionEnabled = canForward deleteButton.set(color: !canDelete ? theme.colors.grayText : theme.colors.redUI, for: .Normal) - forwardButton.set(color: !canForward ? theme.colors.grayText : theme.colors.blueUI, for: .Normal) + forwardButton.set(color: !canForward ? theme.colors.grayText : theme.colors.accent, for: .Normal) deleteButton.set(image: !deleteButton.userInteractionEnabled ? theme.icons.chatDeleteMessagesInactive : theme.icons.chatDeleteMessagesActive, for: .Normal) forwardButton.set(image: !forwardButton.userInteractionEnabled ? theme.icons.chatForwardMessagesInactive : theme.icons.chatForwardMessagesActive, for: .Normal) - countTitle.set(text: count == 0 ? tr(.messageActionsPanelEmptySelected) : tr(.messageActionsPanelSelectedCountCountable(count)), for: .Normal) + countTitle.set(text: count == 0 ? tr(L10n.messageActionsPanelEmptySelected) : tr(L10n.messageActionsPanelSelectedCountCountable(count)), for: .Normal) countTitle.set(color: (!canForward && !canDelete) || count == 0 ? theme.colors.grayText : theme.colors.text, for: .Normal) countTitle.sizeToFit(NSZeroSize, NSMakeSize(frame.width - deleteButton.frame.width - forwardButton.frame.width - 80, frame.height)) countTitle.center() } func notify(with value: Any, oldValue: Any, animated:Bool) { - if let selectingState = (value as? ChatPresentationInterfaceState)?.selectionState, let account = chatInteraction?.account { - let ids = Array(selectingState.selectedIds) - loadMessagesDisposable.set((account.postbox.messagesAtIds(ids) |> deliverOnMainQueue).start( next:{ [weak self] messages in - var canDelete:Bool = !ids.isEmpty - var canForward:Bool = !ids.isEmpty - for message in messages { - if !canDeleteMessage(message, account: account) { - canDelete = false - } - if !canForwardMessage(message, account: account) { - canForward = false - } - } - self?.updateUI(canDelete, canForward, ids.count) - - })) - + if let value = value as? ChatPresentationInterfaceState, let selectionState = value.selectionState { + updateUI(value.canInvokeBasicActions.delete, value.canInvokeBasicActions.forward, selectionState.selectedIds.count) } } deinit { - loadMessagesDisposable.dispose() } func isEqual(to other: Notifable) -> Bool { @@ -144,20 +127,20 @@ class MessageActionsPanelView: Control, Notifable { } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - - deleteButton.set(text: tr(.messageActionsPanelDelete), for: .Normal) - forwardButton.set(text: tr(.messageActionsPanelForward), for: .Normal) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + deleteButton.set(text: tr(L10n.messageActionsPanelDelete), for: .Normal) + forwardButton.set(text: tr(L10n.messageActionsPanelForward), for: .Normal) deleteButton.set(image: !deleteButton.userInteractionEnabled ? theme.icons.chatDeleteMessagesInactive : theme.icons.chatDeleteMessagesActive, for: .Normal) forwardButton.set(image: !forwardButton.userInteractionEnabled ? theme.icons.chatForwardMessagesInactive : theme.icons.chatForwardMessagesActive, for: .Normal) deleteButton.set(color: !deleteButton.userInteractionEnabled ? theme.colors.grayText : theme.colors.redUI, for: .Normal) - forwardButton.set(color: !forwardButton.userInteractionEnabled ? theme.colors.grayText : theme.colors.blueUI, for: .Normal) + forwardButton.set(color: !forwardButton.userInteractionEnabled ? theme.colors.grayText : theme.colors.accent, for: .Normal) - deleteButton.sizeToFit(NSZeroSize, NSMakeSize(0, frame.height)) - forwardButton.sizeToFit(NSZeroSize, NSMakeSize(0, frame.height)) + _ = deleteButton.sizeToFit(NSZeroSize, NSMakeSize(0, frame.height)) + _ = forwardButton.sizeToFit(NSZeroSize, NSMakeSize(0, frame.height)) deleteButton.style = deleteButtonActiveStyle forwardButton.style = buttonActiveStyle diff --git a/Telegram-Mac/MessageTimecode.swift b/Telegram-Mac/MessageTimecode.swift new file mode 100644 index 0000000000..88563fbfce --- /dev/null +++ b/Telegram-Mac/MessageTimecode.swift @@ -0,0 +1,355 @@ + +import Foundation +import Cocoa +import TelegramCore +import SyncCore + + + +private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) +private let dataAndPhoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link, .phoneNumber]).rawValue) +private let phoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.phoneNumber]).rawValue) +private let validHashtagSet: CharacterSet = { + var set = CharacterSet.alphanumerics + set.insert("_") + return set +}() +private let validIdentifierSet: CharacterSet = { + var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!) + set.insert(charactersIn: "A".unicodeScalars.first! ... "Z".unicodeScalars.first!) + set.insert(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!) + set.insert("_") + return set +}() +private let identifierDelimiterSet: CharacterSet = { + var set = CharacterSet.punctuationCharacters + set.formUnion(CharacterSet.whitespacesAndNewlines) + return set +}() +private let externalIdentifierDelimiterSet: CharacterSet = { + var set = CharacterSet.punctuationCharacters + set.formUnion(CharacterSet.whitespacesAndNewlines) + set.remove(".") + return set +}() +private let timecodeDelimiterSet: CharacterSet = { + var set = CharacterSet.punctuationCharacters + set.formUnion(CharacterSet.whitespacesAndNewlines) + set.remove(":") + return set +}() +private let validTimecodeSet: CharacterSet = { + var set = CharacterSet(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!) + set.insert(":") + return set +}() + +public struct ApplicationSpecificEntityType { + public static let Timecode: Int32 = 1 +} + +private enum CurrentEntityType { + case command + case mention + case hashtag + case phoneNumber + case timecode + + var type: EnabledEntityTypes { + switch self { + case .command: + return .command + case .mention: + return .mention + case .hashtag: + return .hashtag + case .phoneNumber: + return .phoneNumber + case .timecode: + return .timecode + } + } +} + +public struct EnabledEntityTypes: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let command = EnabledEntityTypes(rawValue: 1 << 0) + public static let mention = EnabledEntityTypes(rawValue: 1 << 1) + public static let hashtag = EnabledEntityTypes(rawValue: 1 << 2) + public static let url = EnabledEntityTypes(rawValue: 1 << 3) + public static let phoneNumber = EnabledEntityTypes(rawValue: 1 << 4) + public static let timecode = EnabledEntityTypes(rawValue: 1 << 5) + public static let external = EnabledEntityTypes(rawValue: 1 << 6) + + public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .url, .phoneNumber] +} + +private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, _ range: Range, _ enabledTypes: EnabledEntityTypes, _ entities: inout [MessageTextEntity], mediaDuration: Double? = nil) { + if !enabledTypes.contains(type.type) { + return + } + let indexRange: Range = utf16.distance(from: utf16.startIndex, to: range.lowerBound) ..< utf16.distance(from: utf16.startIndex, to: range.upperBound) + var overlaps = false + for entity in entities { + if entity.range.overlaps(indexRange) { + overlaps = true + break + } + } + if !overlaps { + let entityType: MessageTextEntityType + switch type { + case .command: + entityType = .BotCommand + case .mention: + entityType = .Mention + case .hashtag: + entityType = .Hashtag + case .phoneNumber: + entityType = .PhoneNumber + case .timecode: + entityType = .Custom(type: ApplicationSpecificEntityType.Timecode) + } + + if case .timecode = type { + if let mediaDuration = mediaDuration, let timecode = parseTimecodeString(String(utf16[range])), timecode <= mediaDuration { + entities.append(MessageTextEntity(range: indexRange, type: entityType)) + } + } else { + entities.append(MessageTextEntity(range: indexRange, type: entityType)) + } + } +} + + +public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityTypes, currentEntities: [MessageTextEntity] = []) -> [MessageTextEntity] { + var entities: [MessageTextEntity] = currentEntities + + let utf16 = text.utf16 + + var detector: NSDataDetector? + if enabledTypes.contains(.phoneNumber) && enabledTypes.contains(.url) { + detector = dataAndPhoneNumberDetector + } else if enabledTypes.contains(.phoneNumber) { + detector = phoneNumberDetector + } else if enabledTypes.contains(.url) { + detector = dataDetector + } + + let delimiterSet = enabledTypes.contains(.external) ? externalIdentifierDelimiterSet : identifierDelimiterSet + + if let detector = detector { + detector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in + if let result = result { + if result.resultType == NSTextCheckingResult.CheckingType.link || result.resultType == NSTextCheckingResult.CheckingType.phoneNumber { + let lowerBound = utf16.index(utf16.startIndex, offsetBy: result.range.location).samePosition(in: text) + let upperBound = utf16.index(utf16.startIndex, offsetBy: result.range.location + result.range.length).samePosition(in: text) + if let lowerBound = lowerBound, let upperBound = upperBound { + let type: MessageTextEntityType + if result.resultType == NSTextCheckingResult.CheckingType.link { + type = .Url + } else { + type = .PhoneNumber + } + entities.append(MessageTextEntity(range: utf16.distance(from: text.startIndex, to: lowerBound) ..< utf16.distance(from: text.startIndex, to: upperBound), type: type)) + } + } + } + }) + } + + var index = utf16.startIndex + var currentEntity: (CurrentEntityType, Range)? + + var previousScalar: UnicodeScalar? + while index != utf16.endIndex { + let c = utf16[index] + let scalar = UnicodeScalar(c) + var notFound = true + if let scalar = scalar { + if scalar == "/" { + notFound = false + if previousScalar != nil && !delimiterSet.contains(previousScalar!) { + currentEntity = nil + } else { + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &entities) + } + currentEntity = (.command, index ..< index) + } + } else if scalar == "@" { + notFound = false + if let (type, range) = currentEntity { + if case .command = type { + currentEntity = (type, range.lowerBound ..< utf16.index(after: index)) + } else { + commitEntity(utf16, type, range, enabledTypes, &entities) + currentEntity = (.mention, index ..< index) + } + } else { + currentEntity = (.mention, index ..< index) + } + } else if scalar == "#" { + notFound = false + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &entities) + } + currentEntity = (.hashtag, index ..< index) + } + + if notFound { + if let (type, range) = currentEntity { + switch type { + case .command, .mention: + if validIdentifierSet.contains(scalar) { + currentEntity = (type, range.lowerBound ..< utf16.index(after: index)) + } else if delimiterSet.contains(scalar) { + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &entities) + } + currentEntity = nil + } + case .hashtag: + if validHashtagSet.contains(scalar) { + currentEntity = (type, range.lowerBound ..< utf16.index(after: index)) + } else if delimiterSet.contains(scalar) { + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &entities) + } + currentEntity = nil + } + default: + break + } + } + } + } + index = utf16.index(after: index) + previousScalar = scalar + } + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &entities) + } + + return entities +} + +public func addLocallyGeneratedEntities(_ text: String, enabledTypes: EnabledEntityTypes, entities: [MessageTextEntity], mediaDuration: Double? = nil) -> [MessageTextEntity]? { + var resultEntities = entities + + var hasDigits = false + var hasColons = false + + let detectPhoneNumbers = enabledTypes.contains(.phoneNumber) + let detectTimecodes = enabledTypes.contains(.timecode) + if detectPhoneNumbers || detectTimecodes { + loop: for c in text.utf16 { + if let scalar = UnicodeScalar(c) { + if scalar >= "0" && scalar <= "9" { + hasDigits = true + if !detectTimecodes || hasColons { + break loop + } + } else if scalar == ":" { + hasColons = true + if hasDigits { + break loop + } + } + } + } + } + + if hasDigits { + if let phoneNumberDetector = phoneNumberDetector, detectPhoneNumbers { + let utf16 = text.utf16 + phoneNumberDetector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in + if let result = result { + if result.resultType == NSTextCheckingResult.CheckingType.phoneNumber { + let lowerBound = utf16.index(utf16.startIndex, offsetBy: result.range.location).samePosition(in: text) + let upperBound = utf16.index(utf16.startIndex, offsetBy: result.range.location + result.range.length).samePosition(in: text) + if let lowerBound = lowerBound, let upperBound = upperBound { + commitEntity(utf16, .phoneNumber, lowerBound ..< upperBound, enabledTypes, &resultEntities) + } + } + } + }) + } + if hasColons && detectTimecodes { + let utf16 = text.utf16 + let delimiterSet = timecodeDelimiterSet + + var index = utf16.startIndex + var currentEntity: (CurrentEntityType, Range)? + + var previousScalar: UnicodeScalar? + while index != utf16.endIndex { + let c = utf16[index] + let scalar = UnicodeScalar(c) + var notFound = true + if let scalar = scalar { + if validTimecodeSet.contains(scalar) { + notFound = false + if let (type, range) = currentEntity, type == .timecode { + currentEntity = (.timecode, range.lowerBound ..< utf16.index(after: index)) + } else if previousScalar == nil || CharacterSet.whitespacesAndNewlines.contains(previousScalar!) { + currentEntity = (.timecode, index ..< index) + } + } + + if notFound { + if let (type, range) = currentEntity { + switch type { + case .timecode: + if delimiterSet.contains(scalar) { + commitEntity(utf16, type, range, enabledTypes, &resultEntities, mediaDuration: mediaDuration) + currentEntity = nil + } + default: + break + } + } + } + } + index = utf16.index(after: index) + previousScalar = scalar + } + if let (type, range) = currentEntity { + commitEntity(utf16, type, range, enabledTypes, &resultEntities, mediaDuration: mediaDuration) + } + } + } + + if resultEntities.count != entities.count { + return resultEntities + } else { + return nil + } +} + +public func parseTimecodeString(_ string: String?) -> Double? { + if let string = string, string.rangeOfCharacter(from: validTimecodeSet.inverted) == nil { + let components = string.components(separatedBy: ":") + if components.count > 1 && components.count <= 3 { + if components.count == 3 { + if let hours = Int(components[0]), let minutes = Int(components[1]), let seconds = Int(components[2]) { + if hours >= 0 && hours < 48 && minutes >= 0 && minutes < 60 && seconds >= 0 && seconds < 60 { + return Double(seconds) + Double(minutes) * 60.0 + Double(hours) * 60.0 * 60.0 + } + } + } else if components.count == 2 { + if let minutes = Int(components[0]), let seconds = Int(components[1]) { + if minutes >= 0 && minutes < 60 && seconds >= 0 && seconds < 60 { + return Double(seconds) + Double(minutes) * 60.0 + } + } + } + } + } + return nil +} diff --git a/Telegram-Mac/MimeTypes.swift b/Telegram-Mac/MimeTypes.swift index 81cd4aa0b1..df8f2dd840 100644 --- a/Telegram-Mac/MimeTypes.swift +++ b/Telegram-Mac/MimeTypes.swift @@ -7,40 +7,40 @@ // import Cocoa -import SwiftSignalKitMac - +import SwiftSignalKit +import TGUIKit fileprivate var mimestore:[String:String] = [:] fileprivate var extensionstore:[String:String] = [:] -private func initializeMimeStore() { - do { - if mimestore.isEmpty && extensionstore.isEmpty { - let path = Bundle.main.path(forResource: "mime-types", ofType: "txt") - let content = try? String(contentsOfFile: path ?? "") - let mimes = content?.components(separatedBy: CharacterSet.newlines) - - if let mimes = mimes { - for mime in mimes { - let single = mime.components(separatedBy: ":") - if single.count == 2 { - extensionstore[single[0]] = single[1] - mimestore[single[1]] = single[0] - } +func initializeMimeStore() { + assertOnMainThread() + if mimestore.isEmpty && extensionstore.isEmpty { + let path = Bundle.main.path(forResource: "mime-types", ofType: "txt") + let content = try? String(contentsOfFile: path ?? "") + let mimes = content?.components(separatedBy: CharacterSet.newlines) + + if let mimes = mimes { + for mime in mimes { + let single = mime.components(separatedBy: ":") + if single.count == 2 { + extensionstore[single[0]] = single[1] + mimestore[single[1]] = single[0] } } } } } -func resourceType(mimeType:String? = nil, orExt:String? = nil) -> Signal { +func resourceType(mimeType:String? = nil, orExt:String? = nil) -> Signal { - initializeMimeStore() assert(mimeType != nil || orExt != nil) assert((mimeType != nil && orExt == nil) || (mimeType == nil && orExt != nil)) - return Signal { (subscriber) -> Disposable in + return Signal { (subscriber) -> Disposable in + + initializeMimeStore() var result:String? @@ -55,33 +55,41 @@ func resourceType(mimeType:String? = nil, orExt:String? = nil) -> Signal runOn(resourcesQueue) + } |> runOn(Queue.mainQueue()) } -func MIMEType(_ fileExtension: String) -> String { - - initializeMimeStore() - - if let ext = extensionstore[fileExtension] { - return ext - } else { - if !fileExtension.isEmpty { - let UTIRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil) - let UTI = UTIRef?.takeRetainedValue() - if let UTI = UTI { - let MIMETypeRef = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType) - if MIMETypeRef != nil - { - let MIMEType = MIMETypeRef?.takeRetainedValue() - return MIMEType as String? ?? "application/octet-stream" +func MIMEType(_ path: String, isExt: Bool = false) -> String { + let fileExtension = isExt ? path.lowercased() : path.nsstring.pathExtension.lowercased() + if !fileExtension.isEmpty { + if let ext = extensionstore[fileExtension] { + return ext + } else { + if !fileExtension.isEmpty { + let UTIRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil) + let UTI = UTIRef?.takeRetainedValue() + if let UTI = UTI { + let MIMETypeRef = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType) + if MIMETypeRef != nil + { + let MIMEType = MIMETypeRef?.takeRetainedValue() + return MIMEType as String? ?? "application/octet-stream" + } } + } - + return "application/octet-stream" } - return "application/octet-stream" + } else { + return !isExt && path.isDirectory ? "application/zip" : "application/octet-stream" } - +} + +func fileExt(_ mimeType: String) -> String? { + if let ext = mimestore[mimeType] { + return ext + } + return nil } let voiceMime = "audio/ogg" diff --git a/Telegram-Mac/ModalOptionSetController.swift b/Telegram-Mac/ModalOptionSetController.swift new file mode 100644 index 0000000000..abc2179df5 --- /dev/null +++ b/Telegram-Mac/ModalOptionSetController.swift @@ -0,0 +1,174 @@ +// +// ModalOptionSetController.swift +// Telegram +// +// Created by Mikhail Filimonov on 10/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import TGUIKit + +private final class ModalOptionsArguments { + let context: AccountContext + let toggleOption: (Int)->Void + init(context: AccountContext, toggleOption:@escaping(Int)->Void) { + self.context = context + self.toggleOption = toggleOption + } +} + +struct ModalOptionSet : Equatable { + let title: String + let selected: Bool + let editable: Bool + init(title: String, selected: Bool, editable: Bool) { + self.title = title + self.selected = selected + self.editable = editable + } + func withUpdatedSelected(_ selected: Bool) -> ModalOptionSet { + return ModalOptionSet(title: self.title, selected: selected, editable: self.editable) + } +} +enum ModalOptionSetResult { + case selected + case none +} + +private struct ModalOptionsState: Equatable { + let options: [ModalOptionSet] + let selectOne: Bool + init(options:[ModalOptionSet], selectOne: Bool) { + self.options = options + self.selectOne = selectOne + } + + func withToggledOptionAt(_ index: Int) -> ModalOptionsState { + var options = self.options + options[index] = options[index].withUpdatedSelected(!options[index].selected) + if selectOne { + for i in 0 ..< options.count { + options[i] = options[i].withUpdatedSelected(false) + } + options[index] = options[index].withUpdatedSelected(true) + } + + return ModalOptionsState(options: options, selectOne: self.selectOne) + } +} + +private let _id_title: InputDataIdentifier = InputDataIdentifier("_id_title") +private let _id_border: InputDataIdentifier = InputDataIdentifier("_id_border") +private func _id_option(_ index: Int)->InputDataIdentifier { + return InputDataIdentifier("_id_option_\(index)") +} +private func modalOptionsSetEntries(state: ModalOptionsState, desc: String?, arguments: ModalOptionsArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + var sectionId: Int32 = 0 + var index: Int32 = 0 + + if let desc = desc { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_title, equatable: InputDataEquatable(desc), item: { initialSize, stableId in + return GeneralTextRowItem(initialSize, stableId: stableId, text: .plain(desc), textColor: theme.colors.grayText, alignment: .center, drawCustomSeparator: false, inset: NSEdgeInsets(left: 30.0, right: 30.0, top: 10, bottom: 10)) + })) + index += 1 + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_border, equatable: nil, item: { initialSize, stableId in + return GeneralLineSeparatorRowItem.init(initialSize: initialSize, stableId: stableId) + })) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + for (i, option) in state.options.enumerated() { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option(i), equatable: InputDataEquatable(option), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: option.title, type: .selectable(option.selected), viewType: bestGeneralViewType(state.options, for: i), action: { + arguments.toggleOption(i) + }, enabled: option.editable, disabledAction: { + + }) + })) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func ModalOptionSetController(context: AccountContext, options: [ModalOptionSet], selectOne: Bool = false, actionText: (String, NSColor), desc: String? = nil, title: String, result: @escaping ([ModalOptionSetResult])->Void) -> InputDataModalController { + + let initialState: ModalOptionsState = ModalOptionsState(options: options, selectOne: selectOne) + let stateValue: Atomic = Atomic(value: initialState) + let statePromise: ValuePromise = ValuePromise(initialState, ignoreRepeated: true) + + let updateState: (_ f:(ModalOptionsState)->ModalOptionsState)->Void = { f in + statePromise.set(stateValue.modify(f)) + } + + let arguments = ModalOptionsArguments(context: context, toggleOption: { index in + updateState { + $0.withToggledOptionAt(index) + } + }) + + let actionsDisposable = DisposableSet() + + let dataSignal = statePromise.get() |> mapToSignal { state in + return .single(modalOptionsSetEntries(state: state, desc: desc, arguments: arguments)) + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + + var dismiss:(()->Void)? + + + let controller = InputDataController(dataSignal: dataSignal, title: title, validateData: { data in + + result(stateValue.with { state in + return state.options.map { option in + if option.selected { + return .selected + } else { + return .none + } + } + }) + + dismiss?() + + return .fail(.none) + }, afterDisappear: { + actionsDisposable.dispose() + }) + + let modalInteractions: ModalInteractions = ModalInteractions(acceptTitle: actionText.0, accept: { [weak controller] in + controller?.validateInputValues() + }, drawBorder: true, height: 50, singleButton: true) + + + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, size: NSMakeSize(300, 300)) + + dismiss = { [weak modalController] in + modalController?.close() + } + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: dismiss) + + Queue.mainQueue().justDispatch { + modalInteractions.updateDone { title in + title.set(color: actionText.1, for: .Normal) + } + } + + + return modalController +} diff --git a/Telegram-Mac/ModalPreviewViews.swift b/Telegram-Mac/ModalPreviewViews.swift new file mode 100644 index 0000000000..946e085563 --- /dev/null +++ b/Telegram-Mac/ModalPreviewViews.swift @@ -0,0 +1,279 @@ +// +// StickerPreviewModalController.swift +// Telegram +// +// Created by keepcoder on 02/02/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class StickerPreviewModalView : View, ModalPreviewControllerView { + fileprivate let imageView:TransformImageView = TransformImageView() + fileprivate let textView:TextView = TextView() + private let fetchDisposable = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + textView.backgroundColor = .clear + imageView.setFrameSize(100,100) + self.background = .clear + } + + deinit { + fetchDisposable.dispose() + } + + override func layout() { + super.layout() + imageView.center() + } + + func update(with reference: QuickPreviewMedia, context: AccountContext, animated: Bool) -> Void { + if let reference = reference.fileReference { + + let size = reference.media.dimensions?.size.aspectFitted(NSMakeSize(min(300, frame.size.width), min(300, frame.size.height))) ?? frame.size + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) + imageView.frame = NSMakeRect(0, frame.height - size.height, size.width, size.height) + if animated { + imageView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) + } + + imageView.setSignal(chatMessageSticker(postbox: context.account.postbox, file: reference.media, small: false, scale: backingScaleFactor, fetched: true), clearInstantly: true, animate:true) + + let layout = TextViewLayout(.initialize(string: reference.media.stickerText?.fixed, color: nil, font: .normal(30.0))) + layout.measure(width: .greatestFiniteMagnitude) + textView.update(layout) + textView.centerX() + if animated { + textView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) + } + + needsLayout = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +class GifPreviewModalView : View, ModalPreviewControllerView { + fileprivate var player:GIFContainerView = GIFContainerView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(player) + player.setFrameSize(100,100) + self.background = .clear + } + + override func layout() { + super.layout() + player.center() + + } + + func update(with reference: QuickPreviewMedia, context: AccountContext, animated: Bool) -> Void { + if let reference = reference.fileReference { + if animated { + let current = self.player + current.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak current] completed in + if completed { + current?.removeFromSuperview() + } + }) + } else { + self.player.removeFromSuperview() + } + + self.player = GIFContainerView() + self.player.layer?.borderWidth = 0 + self.player.layer?.cornerRadius = .cornerRadius + addSubview(self.player) + let size = reference.media.dimensions?.size.aspectFitted(NSMakeSize(frame.size.width, frame.size.height - 40)) ?? frame.size + + + let iconSignal: Signal + iconSignal = chatMessageVideo(postbox: context.account.postbox, fileReference: reference, scale: backingScaleFactor) + +// if let value = reference.media.videoThumbnails.first { +// let file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: arc4random64()), partialReference: nil, resource: value.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: []) +// iconSignal = chatMessageVideo(postbox: context.account.postbox, fileReference: FileMediaReference.standalone(media: file), scale: backingScaleFactor) +// } else { +// iconSignal = chatMessageVideo(postbox: context.account.postbox, fileReference: reference, scale: backingScaleFactor) +// } + + player.update(with: reference, size: size, viewSize: size, context: context, table: nil, iconSignal: iconSignal) + player.frame = NSMakeRect(0, frame.height - size.height, size.width, size.height) + if animated { + player.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + needsLayout = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class ImagePreviewModalView : View, ModalPreviewControllerView { + fileprivate var imageView:TransformImageView = TransformImageView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + self.background = .clear + } + + override func layout() { + super.layout() + imageView.center() + } + + func update(with reference: QuickPreviewMedia, context: AccountContext, animated: Bool) -> Void { + if let reference = reference.imageReference { + let current = self.imageView + if animated { + current.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak current] completed in + if completed { + current?.removeFromSuperview() + } + }) + } else { + current.removeFromSuperview() + } + + self.imageView = TransformImageView() + self.imageView.layer?.borderWidth = 0 + addSubview(self.imageView) + + let size = frame.size + + let dimensions = largestImageRepresentation(reference.media.representations)?.dimensions.size ?? size + + let arguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: dimensions.fitted(size), boundingSize: dimensions.fitted(size), intrinsicInsets: NSEdgeInsets(), resizeMode: .none) + + self.imageView.setSignal(signal: cachedMedia(media: reference.media, arguments: arguments, scale: backingScaleFactor, positionFlags: nil), clearInstantly: false) + + let updateImageSignal = chatMessagePhoto(account: context.account, imageReference: reference, scale: backingScaleFactor, synchronousLoad: true) + self.imageView.setSignal(updateImageSignal, animate: false) + self.imageView.set(arguments: arguments) + + imageView.setFrameSize(arguments.imageSize) + if animated { + imageView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) + } + needsLayout = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +class AnimatedStickerPreviewModalView : View, ModalPreviewControllerView { + private let loadResourceDisposable = MetaDisposable() + fileprivate let textView:TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.background = .clear + + addSubview(textView) + textView.backgroundColor = .clear + } + private var player: LottiePlayerView? + + override func layout() { + super.layout() + //player.center() + } + + override func viewDidMoveToWindow() { + if window == nil { + player?.set(nil) + player = nil + } + } + + deinit { + self.loadResourceDisposable.dispose() + var bp:Int = 0 + bp += 1 + } + + func update(with reference: QuickPreviewMedia, context: AccountContext, animated: Bool) -> Void { + + if let reference = reference.fileReference { + self.player?.removeFromSuperview() + self.player = nil + + let size = NSMakeSize(frame.width - 80, frame.height - 80) + + + self.player = LottiePlayerView(frame: NSMakeRect(0, 0, size.width, size.height)) + addSubview(self.player!) + self.player?.center() + + let mediaId = reference.media.id + + let data: Signal + if let resource = reference.media.resource as? LocalBundleResource { + data = Signal { subscriber in + if let path = Bundle.main.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true)) + subscriber.putCompletion() + } + return EmptyDisposable + } + } else { + data = context.account.postbox.mediaBox.resourceData(reference.media.resource, attemptSynchronously: true) + } + + self.loadResourceDisposable.set((data |> map { resourceData -> Data? in + + if resourceData.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { + return data + } + return nil + } |> deliverOnMainQueue).start(next: { [weak self] data in + if let data = data { + self?.player?.set(LottieAnimation(compressed: data, key: LottieAnimationEntryKey(key: .media(mediaId), size: size), cachePurpose: .none)) + } else { + self?.player?.set(nil) + } + })) + + if animated { + player!.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + + let layout = TextViewLayout(.initialize(string: reference.media.stickerText?.fixed, color: nil, font: .normal(30.0))) + layout.measure(width: .greatestFiniteMagnitude) + textView.update(layout) + textView.centerX() + if animated { + textView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) + } + + } else { + var bp:Int = 0 + bp += 1 + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/NativeAudioPlayer.swift b/Telegram-Mac/NativeAudioPlayer.swift index 651d560885..16593afc74 100644 --- a/Telegram-Mac/NativeAudioPlayer.swift +++ b/Telegram-Mac/NativeAudioPlayer.swift @@ -31,7 +31,7 @@ class NativeAudioPlayer: AudioPlayer { if AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &deviceIdRequest, 0, nil, &deviceIdSize, &deviceId) == noErr { var masterClock:CMClock? - CMAudioDeviceClockCreateFromAudioDeviceID(kCFAllocatorDefault, deviceId, &masterClock) + CMAudioDeviceClockCreateFromAudioDeviceID(allocator: kCFAllocatorDefault, deviceID: deviceId, clockOut: &masterClock) _player.masterClock = masterClock } @@ -86,7 +86,7 @@ class NativeAudioPlayer: AudioPlayer { override func playFrom(position: TimeInterval) { queue.async { if position > 0 { - self._player.seek(to: CMTimeMakeWithSeconds(position, 10000), toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) + self._player.seek(to: CMTimeMakeWithSeconds(position, preferredTimescale: 10000), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) } self._player.play() self.delegate?.audioPlayerDidChangedTimebase(self) @@ -96,7 +96,7 @@ class NativeAudioPlayer: AudioPlayer { override func set(position:TimeInterval) { queue.async { - self._player.seek(to: CMTimeMakeWithSeconds(position, 10000), toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) + self._player.seek(to: CMTimeMakeWithSeconds(position, preferredTimescale: 10000), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) self.delegate?.audioPlayerDidChangedTimebase(self) } } @@ -120,7 +120,7 @@ class NativeAudioPlayer: AudioPlayer { override func stop() { queue.async { - self._player.seek(to: CMTimeMake(0, self._player.currentTime().timescale)) + self._player.seek(to: CMTimeMake(value: 0, timescale: self._player.currentTime().timescale)) self._player.pause() } } diff --git a/Telegram-Mac/NativeCallSettingsViewController.swift b/Telegram-Mac/NativeCallSettingsViewController.swift index 78418e32d9..7f6727636a 100644 --- a/Telegram-Mac/NativeCallSettingsViewController.swift +++ b/Telegram-Mac/NativeCallSettingsViewController.swift @@ -43,7 +43,7 @@ class NativeCallSettingsViewController: NSViewController { self.currentOutputDeviceId = currentOutputDeviceId self.onSave = onSave self.onCancel = onCancel - super.init(nibName: NSNib.Name(rawValue: "NativeCallSettingsViewController"), bundle: nil) + super.init(nibName: "NativeCallSettingsViewController", bundle: nil) } required init?(coder: NSCoder) { @@ -55,24 +55,24 @@ class NativeCallSettingsViewController: NSViewController { // for device in inputDevices { // let index = inputDeviceButton.itemArray.count // -// inputDeviceButton.addItem(withTitle: device.deviceId == "default" ? tr(.callDeviceSettingsDefault) : device.deviceName) +// inputDeviceButton.addItem(withTitle: device.deviceId == "default" ? tr(L10n.callDeviceSettingsDefault) : device.deviceName) // if inputDevices[index].deviceId == currentInputDeviceId { // inputDeviceButton.select(inputDeviceButton.lastItem) // } // } // for device in outputDevices { // let index = outputDeviceButton.itemArray.count -// outputDeviceButton.addItem(withTitle: device.deviceId == "default" ? tr(.callDeviceSettingsDefault) : device.deviceName) +// outputDeviceButton.addItem(withTitle: device.deviceId == "default" ? tr(L10n.callDeviceSettingsDefault) : device.deviceName) // if outputDevices[index].deviceId == currentOutputDeviceId { // outputDeviceButton.select(outputDeviceButton.lastItem) // } // } // -// inputDeviceTitle.stringValue = tr(.callDeviceSettingsInputLabel) -// outputDeviceTitle.stringValue = tr(.callDeviceSettingsOutputLabel) +// inputDeviceTitle.stringValue = tr(L10n.callDeviceSettingsInputLabel) +// outputDeviceTitle.stringValue = tr(L10n.callDeviceSettingsOutputLabel) - okButton.title = tr(.modalOK) - cancelButton.title = tr(.modalCancel) + okButton.title = tr(L10n.modalOK) + cancelButton.title = tr(L10n.modalCancel) } } diff --git a/Telegram-Mac/NetworkUsageStatsController.swift b/Telegram-Mac/NetworkUsageStatsController.swift new file mode 100644 index 0000000000..42a87eea2d --- /dev/null +++ b/Telegram-Mac/NetworkUsageStatsController.swift @@ -0,0 +1,106 @@ +// +// NetworkUsageStatsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import TGUIKit + + +private func networkUsageStatsControllerEntries(stats: NetworkUsageStats) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageHeaderGeneric), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("messagesSent"), data: InputDataGeneralData(name: L10n.networkUsageBytesSent, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.generic.wifi.outgoing + stats.generic.cellular.outgoing))), viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("messagesReceived"), data: InputDataGeneralData(name: L10n.networkUsageBytesReceived, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.generic.wifi.incoming + stats.generic.cellular.incoming))), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageHeaderImages), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("imagesSent"), data: InputDataGeneralData(name: L10n.networkUsageBytesSent, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.image.wifi.outgoing + stats.image.cellular.outgoing))), viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("imagesReceived"), data: InputDataGeneralData(name: L10n.networkUsageBytesReceived, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.image.wifi.incoming + stats.image.cellular.incoming))), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageHeaderVideos), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("videosSent"), data: InputDataGeneralData(name: L10n.networkUsageBytesSent, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.video.wifi.outgoing + stats.video.cellular.outgoing))), viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("videosReceived"), data: InputDataGeneralData(name: L10n.networkUsageBytesReceived, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.video.wifi.incoming + stats.video.cellular.incoming))), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageHeaderAudio), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("audioSent"), data: InputDataGeneralData(name: L10n.networkUsageBytesSent, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.audio.wifi.outgoing + stats.audio.cellular.outgoing))), viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("audioReceived"), data: InputDataGeneralData(name: L10n.networkUsageBytesReceived, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.audio.wifi.incoming + stats.audio.cellular.incoming))), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageHeaderFiles), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("filesSent"), data: InputDataGeneralData(name: L10n.networkUsageBytesSent, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.file.wifi.outgoing + stats.file.cellular.outgoing))), viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("filesReceived"), data: InputDataGeneralData(name: L10n.networkUsageBytesReceived, color: theme.colors.text, icon: nil, type: .context(.prettySized(with: Int(stats.file.wifi.incoming + stats.file.cellular.incoming))), viewType: .lastItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: .init("reset"), data: InputDataGeneralData(name: L10n.networkUsageReset, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + if stats.resetWifiTimestamp != 0 { + let formatter = DateFormatter() + formatter.dateFormat = "E, d MMM yyyy HH:mm" + let dateStringPlain = formatter.string(from: Date(timeIntervalSince1970: Double(stats.resetWifiTimestamp))) + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.networkUsageNetworkUsageSince(dateStringPlain)), data: InputDataGeneralTextData(viewType: .textTopItem))) + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func networkUsageStatsController(context: AccountContext) -> ViewController { + + let promise: Promise = Promise() + promise.set(combineLatest(accountNetworkUsageStats(account: context.account, reset: []) |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map {$0.0}) + + return InputDataController(dataSignal: promise.get() |> deliverOnPrepareQueue |> map {networkUsageStatsControllerEntries(stats: $0)} |> map { InputDataSignalValue(entries: $0) }, title: L10n.networkUsageNetworkUsage, validateData: { data in + if data[.init("reset")] != nil { + let reset: ResetNetworkUsageStats = [.wifi, .cellular] + promise.set(accountNetworkUsageStats(account: context.account, reset: reset)) + } + return .fail(.none) + }, removeAfterDisappear: true, hasDone: false, identifier: "networkUsage") +} diff --git a/Telegram-Mac/NewContactController.swift b/Telegram-Mac/NewContactController.swift new file mode 100644 index 0000000000..cf34d77bac --- /dev/null +++ b/Telegram-Mac/NewContactController.swift @@ -0,0 +1,161 @@ +// +// AddContactController.swift +// Telegram +// +// Created by Mikhail Filimonov on 07/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit + +private final class NewContactArguments { + let context: AccountContext + let updateText:(String, String)->Void + let toggleAddToException:(Bool)->Void + init(context: AccountContext, updateText:@escaping(String, String)->Void, toggleAddToException:@escaping(Bool)->Void) { + self.context = context + self.updateText = updateText + self.toggleAddToException = toggleAddToException + } +} + +private struct NewContactState : Equatable { + +} +private let _id_contact_info = InputDataIdentifier("_id_contact_info") +private let _id_phone_number = InputDataIdentifier("_id_phone_number") +private let _id_add_exception = InputDataIdentifier("_id_add_exception") + +private func newContactEntries(state: EditInfoState, arguments: NewContactArguments) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_contact_info, equatable: InputDataEquatable(state), item: { initialSize, stableId in + return EditAccountInfoItem(initialSize, stableId: stableId, account: arguments.context.account, state: state, viewType: .singleItem, updateText: arguments.updateText) + })) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_phone_number, equatable: InputDataEquatable(state.phone), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.newContactPhone, description: state.phone == nil ? L10n.newContactPhoneHidden : formatPhoneNumber(state.phone!), descTextColor: theme.colors.accent, type: .none, viewType: .singleItem, action: { + + }) + })) + index += 1 + + if state.phone == nil { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.newContactPhoneHiddenText(state.firstName)), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + } + + + if let peerStatusSettings = state.peerStatusSettings, peerStatusSettings.contains(.addExceptionWhenAddingContact) { + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_add_exception, data: InputDataGeneralData(name: L10n.newContactExceptionShareMyPhoneNumber, color: theme.colors.text, type: .switchable(state.addToException), viewType: .singleItem, action: { + arguments.toggleAddToException(!state.addToException) + }))) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.newContactExceptionShareMyPhoneNumberDesc(state.firstName)), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func NewContactController(context: AccountContext, peerId: PeerId) -> InputDataModalController { + + let initialState: EditInfoState = EditInfoState() + let stateValue: Atomic = Atomic(value: initialState) + let statePromise: ValuePromise = ValuePromise(initialState, ignoreRepeated: true) + + let updateState: (_ f:(EditInfoState)->EditInfoState)->Void = { f in + statePromise.set(stateValue.modify(f)) + } + + let arguments = NewContactArguments(context: context, updateText: { firstName, lastName in + updateState { + return $0.withUpdatedFirstName(firstName).withUpdatedLastName(lastName) + } + }, toggleAddToException: { value in + updateState { + return $0.withUpdatedAddToException(value) + } + }) + + let actionsDisposable = DisposableSet() + + let dataSignal = statePromise.get() |> mapToSignal { state in + if state.peer == nil { + return .never() + } else { + return .single(newContactEntries(state: state, arguments: arguments)) + } + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + actionsDisposable.add((context.account.postbox.peerView(id: peerId) |> deliverOnMainQueue).start(next: { pv in + updateState { current in + return current.withUpdatedPeerView(pv) + } + })) + + var dismiss:(()->Void)? + + let addContact:()->Void = { + let state = stateValue.with { $0 } + _ = showModalProgress(signal: addContactInteractively(account: context.account, peerId: peerId, firstName: state.firstName, lastName: state.lastName, phoneNumber: state.phone ?? "", addToPrivacyExceptions: state.addToException), for: context.window).start(completed: { + _ = showModalSuccess(for: context.window, icon: theme.icons.successModalProgress, delay: 2.0).start() + }) + dismiss?() + } + + let controller = InputDataController(dataSignal: dataSignal, title: L10n.newContactTitle, validateData: { data in + + let firstName = stateValue.with { $0.firstName } + if firstName.isEmpty { + return .fail(.fields([_id_contact_info : .shake])) + } + addContact() + + return .fail(.none) + }, afterDisappear: { + actionsDisposable.dispose() + }) + + let modalInteractions: ModalInteractions = ModalInteractions(acceptTitle: L10n.navigationDone, accept: { [weak controller] in + controller?.validateInputValues() + }, drawBorder: true, height: 50, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, size: NSMakeSize(300, 300)) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + dismiss = { [weak modalController] in + modalController?.close() + } + + return modalController +} diff --git a/Telegram-Mac/NewPollController.swift b/Telegram-Mac/NewPollController.swift new file mode 100644 index 0000000000..49762bdd5d --- /dev/null +++ b/Telegram-Mac/NewPollController.swift @@ -0,0 +1,786 @@ +// +// NewPollController.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit + +private let optionsLimit: Int = 10 +private let maxTextLength:Int32 = 255 +private let maxOptionLength: Int32 = 100 + + +private func _id_input_option() -> InputDataIdentifier { + return InputDataIdentifier("_id_input_option_\(arc4random())") +} +private let _id_input_title = InputDataIdentifier("_id_input_title") +private let _id_input_add_option = InputDataIdentifier("_id_input_add_option") + +private let _id_anonymous = InputDataIdentifier("_id_anonymous") +private let _id_multiple_choice = InputDataIdentifier("_id_multiple_choice") +private let _id_quiz = InputDataIdentifier("_id_quiz") +private let _id_explanation = InputDataIdentifier("_id_explanation") + + +private struct NewPollOption : Equatable { + let identifier: InputDataIdentifier + let text: String + let selected: Bool + init(identifier: InputDataIdentifier, text: String, selected: Bool) { + self.identifier = identifier + self.text = text + self.selected = selected + } + func withUpdatedText(_ text: String) -> NewPollOption { + return NewPollOption(identifier: self.identifier, text: text, selected: self.selected) + } + func withUpdatedSelected(_ selected: Bool) -> NewPollOption { + return NewPollOption(identifier: self.identifier, text: self.text, selected: selected) + } +} + +private enum NewPollMode : Equatable { + case normal(anonymous: Bool) + case quiz(anonymous: Bool) + case multiple(anonymous: Bool) + + var isAnonymous: Bool { + switch self { + case let .normal(anonymous): + return anonymous + case let .quiz(anonymous): + return anonymous + case let .multiple(anonymous): + return anonymous + } + } + func withUpdatedIsAnonymous(_ anonymous: Bool) -> NewPollMode { + switch self { + case .normal: + return .normal(anonymous: anonymous) + case .quiz: + return .quiz(anonymous: anonymous) + case .multiple: + return .multiple(anonymous: anonymous) + } + } + + var isQuiz: Bool { + switch self { + case .quiz: + return true + default: + return false + } + } + var isMultiple: Bool { + switch self { + case .multiple: + return true + default: + return false + } + } + + func isModeEqual(to mode: NewPollMode) -> Bool { + switch self { + case .normal: + return !mode.isQuiz && !mode.isMultiple + case .quiz: + return mode.isQuiz + case .multiple: + return mode.isMultiple + } + } + + var publicity: TelegramMediaPollPublicity { + if isAnonymous { + return .anonymous + } else { + return .public + } + } + var kind: TelegramMediaPollKind { + switch self { + case .normal: + return .poll(multipleAnswers: false) + case .multiple: + return .poll(multipleAnswers: true) + case .quiz: + return .quiz + } + } +} + +private struct NewPollState : Equatable { + let title: String + let options: [NewPollOption] + private let random: UInt32 + let mode: NewPollMode + let isQuiz: Bool? + let quizExplanation: NSAttributedString + init(title: String, options: [NewPollOption], random: UInt32, mode: NewPollMode, isQuiz: Bool?, quizExplanation: NSAttributedString) { + self.title = title + self.options = options + self.random = random + self.mode = mode + self.isQuiz = isQuiz + self.quizExplanation = quizExplanation + } + + func withUpdatedTitle(_ title: String) -> NewPollState { + return NewPollState(title: title, options: self.options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + func withDeleteOption(_ identifier: InputDataIdentifier) -> NewPollState { + var options = self.options + options.removeAll(where: {$0.identifier == identifier}) + return NewPollState(title: title, options: options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + + func withUnselectItems() -> NewPollState { + return NewPollState(title: self.title, options: self.options.map { $0.withUpdatedSelected(false) }, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + + func withUpdatedOption(_ f:(NewPollOption) -> NewPollOption, forKey identifier: InputDataIdentifier) -> NewPollState { + var options = self.options + if let index = options.firstIndex(where: {$0.identifier == identifier}) { + options[index] = f(options[index]) + } + return NewPollState(title: self.title, options: options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + + func withUpdatedOptions(_ data:[InputDataIdentifier : InputDataValue]) -> NewPollState { + var options = self.options + for (key, value) in data { + if let index = self.indexOf(key) { + options[index] = options[index].withUpdatedText(value.stringValue ?? options[index].text) + } + } + return NewPollState(title: self.title, options: options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + + func withAddedOption(_ option: NewPollOption) -> NewPollState { + var options = self.options + options.append(option) + return NewPollState(title: self.title, options: options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + func withUpdatedPos(_ previous: Int, _ current: Int) -> NewPollState { + var options = self.options + options.move(at: previous, to: current) + return NewPollState(title: self.title, options: options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + + func indexOf(_ identifier: InputDataIdentifier) -> Int? { + return options.firstIndex(where: { $0.identifier == identifier }) + } + + func withUpdatedState() -> NewPollState { + return NewPollState(title: self.title, options: self.options, random: arc4random(), mode: self.mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + func withUpdatedMode(_ mode: NewPollMode) -> NewPollState { + return NewPollState(title: self.title, options: self.options, random: self.random, mode: mode, isQuiz: self.isQuiz, quizExplanation: self.quizExplanation) + } + func withUpdatedQuizExplanation(_ quizExplanation: NSAttributedString) -> NewPollState { + return NewPollState(title: self.title, options: self.options, random: self.random, mode: self.mode, isQuiz: self.isQuiz, quizExplanation: quizExplanation) + } + + var isEnabled: Bool { + let isEnabled = !title.trimmed.isEmpty && options.filter({!$0.text.trimmed.isEmpty}).count >= 2 + switch self.mode { + case .quiz: + if let option = self.options.first(where: {$0.selected }) { + if option.text.trimmed.isEmpty { + return false + } + } + return isEnabled + default: + return isEnabled + } + } + + var shouldShowTooltipForQuiz: Bool { + return self.mode.isQuiz && !self.options.contains(where: { $0.selected }) + } + + var media: TelegramMediaPoll { + var options: [TelegramMediaPollOption] = [] + var answers: [Data]? + for (i, option) in self.options.enumerated() { + if !option.text.trimmed.isEmpty { + options.append(TelegramMediaPollOption(text: option.text.trimmed, opaqueIdentifier: "\(i)".data(using: .utf8)!)) + if option.selected { + answers = [options.last!.opaqueIdentifier] + } + } + } + + let solution: TelegramMediaPollResults.Solution? + if !self.quizExplanation.string.isEmpty { + let entities = ChatTextInputState(inputText: self.quizExplanation.string, selectionRange: 0..<0, attributes: chatTextAttributes(from: self.quizExplanation)) + solution = TelegramMediaPollResults.Solution(text: self.quizExplanation.string, entities: entities.messageTextEntities()) + } else { + solution = nil + } + + return TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: arc4random64()), publicity: mode.publicity, kind: mode.kind, text: title.trimmed, options: options, correctAnswers: answers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: solution), isClosed: false, deadlineTimeout: nil) + } +} + +private func newPollEntries(_ state: NewPollState, context: AccountContext, canBePublic: Bool, deleteOption:@escaping(InputDataIdentifier) -> Void, updateQuizSelected:@escaping(InputDataIdentifier) -> Void, updateMode: @escaping(NewPollMode)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(state.title.length > maxTextLength / 3 * 2 ? L10n.newPollQuestionHeaderLimit(Int(maxTextLength) - state.title.length) : L10n.newPollQuestionHeader), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) + index += 1 + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.title), error: nil, identifier: _id_input_title, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.newPollQuestionPlaceholder, filter: { text in + + var text = text + while text.contains("\n\n\n") { + text = text.replacingOccurrences(of: "\n\n\n", with: "\n\n") + } + + if !text.isEmpty { + while text.range(of: "\n")?.lowerBound == text.startIndex { + text = String(text[text.index(after: text.startIndex)...]) + } + } + + return text + + }, limit: maxTextLength)) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.newPollOptionsHeader), data: InputDataGeneralTextData(detectBold: false, viewType: .textTopItem))) + index += 1 + + let sorted = state.options + + + + for (i, option) in sorted.enumerated() { + + var viewType: GeneralViewType = bestGeneralViewType(sorted, for: i) + if i == sorted.count - 1, state.options.count < optionsLimit { + if i == 0 { + viewType = .firstItem + } else { + viewType = .innerItem + } + } + let placeholder: InputDataInputPlaceholder? + switch state.mode { + case .multiple: + placeholder = InputDataInputPlaceholder(hasLimitationText: true) + case .normal: + placeholder = InputDataInputPlaceholder(hasLimitationText: true) + case .quiz: + + placeholder = InputDataInputPlaceholder(nil, icon: option.selected ? theme.icons.chatToggleSelected : theme.icons.poll_quiz_unselected, drawBorderAfterPlaceholder: true, hasLimitationText: true, action: { + updateQuizSelected(option.identifier) + //deleteOption(option.identifier) + }) + } + + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(option.text), error: nil, identifier: option.identifier, mode: .plain, data: InputDataRowData(viewType: viewType, rightItem: .action(theme.icons.recentDismiss, .custom({ _, _ in + deleteOption(option.identifier) + }))), placeholder: placeholder, inputPlaceholder: L10n.newPollOptionsPlaceholder, filter: { text in + return text.trimmingCharacters(in: CharacterSet.newlines) + }, limit: maxOptionLength)) + index += 1 + } + if state.options.count < optionsLimit { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_input_add_option, data: InputDataGeneralData(name: L10n.newPollOptionsAddOption, color: theme.colors.accent, icon: theme.icons.pollAddOption, type: .none, viewType: state.options.isEmpty ? .singleItem : .lastItem, action: nil))) + index += 1 + } + + + index = 50 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(state.options.count < 2 ? L10n.newPollOptionsDescriptionMinimumCountable(2) : optionsLimit == state.options.count ? L10n.newPollOptionsDescriptionLimitReached : L10n.newPollOptionsDescriptionCountable(optionsLimit - state.options.count)), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + var hideMultiple: Bool = false + var hideQuiz: Bool = false + if let isQuiz = state.isQuiz { + hideMultiple = isQuiz + hideQuiz = true + } + + if canBePublic { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_anonymous, data: InputDataGeneralData(name: L10n.newPollAnonymous, color: theme.colors.text, type: .switchable(state.mode.isAnonymous), viewType: hideQuiz && hideMultiple ? .singleItem : .firstItem, justUpdate: arc4random64(), action: { + updateMode(state.mode.withUpdatedIsAnonymous(!state.mode.isAnonymous)) + }))) + index += 1 + } + + + + if !hideMultiple { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_multiple_choice, data: InputDataGeneralData(name: L10n.newPollMultipleChoice, color: theme.colors.text, type: .switchable(state.mode.isMultiple), viewType: canBePublic ? hideQuiz ? .lastItem : .innerItem : hideQuiz ? .lastItem : .firstItem, enabled: !state.mode.isQuiz, justUpdate: arc4random64(), action: { + if state.mode.isMultiple { + updateMode(.normal(anonymous: state.mode.isAnonymous)) + } else { + updateMode(.multiple(anonymous: state.mode.isAnonymous)) + } + }, disabledAction: { + alert(for: context.window, info: L10n.newPollQuizMultipleError) + }))) + index += 1 + } + + + + if !hideQuiz { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_quiz, data: InputDataGeneralData(name: L10n.newPollQuiz, color: theme.colors.text, type: .switchable(state.mode.isQuiz), viewType: .lastItem, enabled: !state.mode.isMultiple, justUpdate: arc4random64(), action: { + if state.mode.isQuiz { + updateMode(.normal(anonymous: state.mode.isAnonymous)) + } else { + updateMode(.quiz(anonymous: state.mode.isAnonymous)) + } + }))) + index += 1 + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.newPollQuizDesc), data: InputDataGeneralTextData(color: theme.colors.listGrayText, viewType: .textBottomItem))) + index += 1 + + + + } else if state.isQuiz == true { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.newPollQuizDesc), data: InputDataGeneralTextData(color: theme.colors.listGrayText, viewType: .textBottomItem))) + index += 1 + } + + switch state.mode { + case .quiz: + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.newPollExplanationHeader), data: InputDataGeneralTextData(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .attributedString(state.quizExplanation), error: nil, identifier: _id_explanation, mode: .plain, data: InputDataRowData(viewType: .singleItem, canMakeTransformations: true), placeholder: nil, inputPlaceholder: L10n.newPollExplanationPlaceholder, filter: { text in + return text.trimmingCharacters(in: CharacterSet.newlines) + }, limit: 200)) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.newPollExplanationDesc), data: InputDataGeneralTextData(color: theme.colors.listGrayText, viewType: .textBottomItem))) + index += 1 + default: + break + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func NewPollController(chatInteraction: ChatInteraction, isQuiz: Bool? = nil) -> InputDataModalController { + + + let mode: NewPollMode + if let isQuiz = isQuiz, isQuiz { + mode = .quiz(anonymous: true) + } else { + mode = .normal(anonymous: true) + } + + let initialState = NewPollState(title: "", options: [NewPollOption(identifier: _id_input_option(), text: "", selected: false), NewPollOption(identifier: _id_input_option(), text: "", selected: false)], random: arc4random(), mode: mode, isQuiz: isQuiz, quizExplanation: NSAttributedString()) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((NewPollState) -> NewPollState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + var shouldMakeNextResponderAfterTransition: (InputDataIdentifier, Bool, InputDataIdentifier?, Bool)? = nil + var shouldMakeNearResponderAfterTransition: (InputDataIdentifier, Int)? = nil + + let animated: Atomic = Atomic(value: false) + + let deleteOption:(InputDataIdentifier)-> Void = { identifier in + let state = stateValue.with { $0 } + updateState { state in + return state.withDeleteOption(identifier) + } + shouldMakeNearResponderAfterTransition = (identifier, state.indexOf(identifier)!) + } + let updateQuizSelected:(InputDataIdentifier)-> Void = { identifier in + updateState { state in + return state.withUnselectItems().withUpdatedOption({ option -> NewPollOption in + return option.withUpdatedSelected(true) + }, forKey: identifier) + } + } + + + var showTooltipForQuiz:(()->Void)? = nil + + + let updateMode:(NewPollMode)->Void = { mode in + let oldMode = stateValue.with { $0.mode } + + updateState { state in + if state.mode.isModeEqual(to: mode) { + return state.withUpdatedMode(mode) + } else { + return state.withUnselectItems().withUpdatedMode(mode) + } + } + if mode.isQuiz && !oldMode.isQuiz { + showTooltipForQuiz?() + } + } + + let addOption:(Bool)-> InputDataValidation = { byClick in + let option = NewPollOption(identifier: _id_input_option(), text: "", selected: false) + updateState { state in + if state.options.count < optionsLimit { + return state.withAddedOption(option) + } else { + return state + } + } + shouldMakeNextResponderAfterTransition = (option.identifier, false, byClick ? _id_input_add_option : nil, true) + + return .none + } + + var close: (() -> Void)? = nil + + + + let checkAndSend:() -> Void = { + let state = stateValue.with { $0 } + + if state.isEnabled && !state.shouldShowTooltipForQuiz { + chatInteraction.sendMedias([state.media], ChatTextInputState(), false, nil, false, nil) + close?() + } else if state.shouldShowTooltipForQuiz { + showTooltipForQuiz?() + } + } + + + let interactions = ModalInteractions(acceptTitle: L10n.modalSend, accept: { + checkAndSend() + }, drawBorder: true, height: 50, singleButton: true) + + + let canBePublic: Bool + if let peer = chatInteraction.presentation.mainPeer { + canBePublic = !peer.isChannel + } else { + canBePublic = true + } + + let context = chatInteraction.context + + let signal: Signal = statePromise.get() |> deliverOnPrepareQueue |> map { value in + return InputDataSignalValue(entries: newPollEntries(value, context: context, canBePublic: canBePublic, deleteOption: deleteOption, updateQuizSelected: updateQuizSelected, updateMode: updateMode), animated: animated.swap(true)) + } + + + let controller = InputDataController(dataSignal: signal, title: isQuiz == true ? L10n.newPollTitleQuiz : L10n.newPollTitle, validateData: { data -> InputDataValidation in + + if let _ = data[_id_input_add_option] { + return addOption(true) + } + + var fails: [InputDataIdentifier : InputDataValidationFailAction] = [:] + for (key, value) in data { + if let string = value.stringValue, string.trimmed.isEmpty { + fails[key] = .shake + } + } + if !fails.isEmpty { + + let state = stateValue.with { $0 } + + if fails.contains(where: {$0.key == _id_input_title}) { + shouldMakeNextResponderAfterTransition = (_id_input_title, true, nil, true) + } else { + for option in state.options { + if fails.contains(where: {$0.key == option.identifier}) { + shouldMakeNextResponderAfterTransition = (option.identifier, true, nil, true) + break + } + } + } + } + + return .fail(.doSomething { f in + + f(.fail(.fields(fails))) + + var addedOptions: Int? = nil + updateState { state in + var state = state + if fails.isEmpty { + if state.options.count < 2 { + state = state.withAddedOption(NewPollOption(identifier: _id_input_option(), text: "", selected: false)) + if addedOptions == nil { + addedOptions = state.options.count - 1 + } + } + } + return state.withUpdatedState() + } + + if let addedOptions = addedOptions { + let state = stateValue.with { $0 } + shouldMakeNextResponderAfterTransition = (state.options[addedOptions].identifier, false, nil, true) + } + }) + + }, updateDatas: { data in + updateState { state in + return state.withUpdatedTitle(data[_id_input_title]?.stringValue ?? state.title) + .withUpdatedOptions(data) + .withUpdatedQuizExplanation(data[_id_explanation]?.attributedString ?? state.quizExplanation) + } + return .none + }, afterDisappear: { + + }, updateDoneValue: { data in + return { f in + f(.disabled(L10n.navigationDone)) + } + }, removeAfterDisappear: true, hasDone: true, identifier: "new-poll", afterTransaction: { controller in + + if let (identifier, checkEmptyCurrent, focusIdentifier, scrollIfNeeded) = shouldMakeNextResponderAfterTransition { + var markResponder: Bool = true + let state = stateValue.with { $0 } + if let current = controller.currentFirstResponderIdentifier, checkEmptyCurrent { + if current == _id_input_title, state.title.trimmed.isEmpty { + markResponder = false + } + if let option = state.options.first(where: {$0.identifier == current}), option.text.trimmed.isEmpty { + markResponder = false + } + } + if markResponder { + controller.makeFirstResponderIfPossible(for: identifier, focusIdentifier: focusIdentifier, scrollIfNeeded: scrollIfNeeded) + } + shouldMakeNextResponderAfterTransition = nil + } + + if let (_, index) = shouldMakeNearResponderAfterTransition { + + let state = stateValue.with { $0 } + if !state.options.isEmpty { + if controller.currentFirstResponderIdentifier == nil { + if index == 0 { + if !state.options.isEmpty { + controller.makeFirstResponderIfPossible(for: state.options[0].identifier) + } + } else { + controller.makeFirstResponderIfPossible(for: state.options[index - 1].identifier) + } + } + } else { + controller.makeFirstResponderIfPossible(for: _id_input_title) + } + + + + shouldMakeNearResponderAfterTransition = nil + } + + var range: NSRange = NSMakeRange(NSNotFound, 0) + let state = stateValue.with { $0 } + + controller.tableView.enumerateItems(with: { item -> Bool in + if let identifier = (item.stableId.base as? InputDataEntryId)?.identifier { + if let _ = state.indexOf(identifier) { + if range.location == NSNotFound { + range.location = item.index + } + range.length += 1 + } + } + + return true + }) + +// +// let resort = TableResortController(resortRange: range, startTimeout: 0.1, start: { _ in +// +// }, resort: { _ in +// +// }, complete: { [weak controller] previous, current in +// if previous != current { +// _ = animated.swap(false) +// +// updateState { state in +// return state.withUpdatedPos(previous - range.location, current - range.location) +// } +// } else { +// updateState { state in +// return state.withUpdatedState() +// } +// } +// if let identifier = controller?.currentFirstResponderIdentifier { +// shouldMakeNextResponderAfterTransition = (identifier, false, nil, false) +// } +// +// }, updateItems: { currentView, items in +// +//// let items = items.compactMap { $0 as? GeneralRowItem }.filter({ $0.view?.identifier != NSUserInterfaceItemIdentifier("-1")}) +//// for (i, item) in items.enumerated() { +//// NSLog("\(item.view)") +//// item.updateViewType(bestGeneralViewType(items, for: i)) +//// item.view?.set(item: item, animated: true) +//// } +//// if let item = currentView?.item as? GeneralRowItem { +//// //item.updateViewType(.singleItem) +//// currentView?.set(item: item, animated: true) +//// } +// +// }) +// controller.tableView.resortController = resort +// + interactions.updateDone { done in + done.isEnabled = state.isEnabled + } + + }, returnKeyInvocation: { identifier, event in + + if FastSettings.checkSendingAbility(for: event) { + checkAndSend() + return .default + } else { + let state = stateValue.with { $0 } + + + if identifier == _id_input_title { + if state.options.isEmpty { + _ = addOption(false) + return .nothing + } + return .invokeEvent + } + + if let identifier = identifier { + + let index: Int? = state.indexOf(identifier) + let isLast = index == state.options.count - 1 + + if isLast { + if state.options.count == optionsLimit { + return .nothing + } else { + _ = addOption(false) + return .nothing + } + } else { + return .nextResponder + } + } + } + + + return .nothing + }, deleteKeyInvocation: { identifier in + + let state = stateValue.with { $0 } + if let index = state.options.firstIndex(where: { $0.identifier == identifier}) { + if state.options[index].text.isEmpty { + deleteOption(state.options[index].identifier) + return .invoked + } + } + + return .default + }) + + + let modalController = InputDataModalController(controller, modalInteractions: interactions, closeHandler: { f in + let state = stateValue.with { $0 } + + if !state.title.isEmpty || !state.options.filter({!$0.text.isEmpty}).isEmpty { + confirm(for: mainWindow, header: L10n.newPollDisacardConfirmHeader, information: L10n.newPollDisacardConfirm, okTitle: L10n.newPollDisacardConfirmYes, cancelTitle: L10n.newPollDisacardConfirmNo, successHandler: { _ in + f() + }) + } else { + f() + } + }) + + close = { [weak modalController] in + modalController?.modal?.close() + } + + showTooltipForQuiz = { [weak controller] in + let options = stateValue.with({ $0.options }) + for option in options { + let view = controller?.tableView.item(stableId: InputDataEntryId.input(option.identifier))?.view as? InputDataRowView + if view?.visibleRect.height == view?.frame.height { + delay(0.2, closure: { [weak view] in + view?.showPlaceholderActionTooltip(L10n.newPollQuizTooltip) + }) + break + } + + } + + } + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: close) + + + chatInteraction.context.window.set(handler: { [weak controller] () -> KeyHandlerResult in + if let controller = controller { + let state = stateValue.with {$0} + + let id = controller.currentFirstResponderIdentifier + + if let id = id, let index = state.indexOf(id) { + let option = state.options[index] + if state.options.count - 1 == index, state.options.count < 10 { + if !option.text.isEmpty { + _ = addOption(false) + return .invoked + } + } + } + + } + return .rejected + }, with: controller, for: .Tab, priority: .supreme) + + return modalController + +} diff --git a/Telegram-Mac/NewThemeController.swift b/Telegram-Mac/NewThemeController.swift new file mode 100644 index 0000000000..5c2ab845e7 --- /dev/null +++ b/Telegram-Mac/NewThemeController.swift @@ -0,0 +1,186 @@ +// +// NewThemeController.swift +// Telegram +// +// Created by Mikhail Filimonov on 28/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore + + + +private let _id_input_name = InputDataIdentifier("_id_input_name") + +private struct NewThemeState : Equatable { + let name: String + let error: InputDataValueError? + init(name: String, error: InputDataValueError?) { + self.name = name + self.error = error + } + func withUpdatedCode(_ name: String) -> NewThemeState { + return NewThemeState(name: name, error: self.error) + } + func withUpdatedError(_ error: InputDataValueError?) -> NewThemeState { + return NewThemeState(name: self.name, error: error) + } +} + +private func newThemeEntries(state: NewThemeState) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.name), error: state.error, identifier: _id_input_name, mode: .plain, data: InputDataRowData(), placeholder: nil, inputPlaceholder: L10n.newThemePlaceholder, filter: { $0 }, limit: 100)) + index += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.newThemeDesc), data: InputDataGeneralTextData())) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func NewThemeController(context: AccountContext, palette: ColorPalette) -> InputDataModalController { + var palette = palette + let initialState = NewThemeState(name: findBestNameForPalette(palette), error: nil) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((NewThemeState) -> NewThemeState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let disposable = MetaDisposable() + + var close: (() -> Void)? = nil + + let signal = statePromise.get() |> map { state in + return InputDataSignalValue(entries: newThemeEntries(state: state)) + } + + func create() -> InputDataValidation { + return .fail(.doSomething(next: { f in + + let name = stateValue.with { $0.name } + + if name.isEmpty { + f(.fail(.fields([_id_input_name : .shake]))) + return + } + + let temp = NSTemporaryDirectory() + "\(arc4random()).palette" + try? palette.toString.write(to: URL(fileURLWithPath: temp), atomically: true, encoding: .utf8) + let resource = LocalFileReferenceMediaResource(localFilePath: temp, randomId: arc4random64(), isUniquelyReferencedTemporaryFile: true, size: fs(temp)) + var thumbnailData: Data? = nil + let preview = generateThemePreview(for: palette, wallpaper: theme.wallpaper.wallpaper, backgroundMode: theme.backgroundMode) + if let mutableData = CFDataCreateMutable(nil, 0), let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) { + CGImageDestinationAddImage(destination, preview, nil) + if CGImageDestinationFinalize(destination) { + let data = mutableData as Data + thumbnailData = data + } + } +// let baseTheme: TelegramBaseTheme? +// switch palette.parent { +// case .day: +// baseTheme = .day +// case .dayClassic: +// baseTheme = .classic +// case .nightAccent: +// baseTheme = .night +// default: +// baseTheme = nil +// } +// let settings: TelegramThemeSettings? +// if let baseTheme = baseTheme { +// settings = .init(baseTheme: baseTheme, accentColor: Int32(palette.accent.rgb), messageColors: (top: Int32(palette.bubbleBackground_outgoing.rgb), Int32(palette.bubbleBackground_outgoing.rgb)), wallpaper: nil) +// } else { +// settings = nil +// } +// + disposable.set(showModalProgress(signal: createTheme(account: context.account, title: name, resource: resource, thumbnailData: thumbnailData, settings: nil) + |> filter { value in + switch value { + case .result: + return true + default: + return false + } + } |> take(1), for: context.window).start(next: { result in + switch result { + case let .result(theme): + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { + $0.withUpdatedCloudTheme(theme) + }).start() + default: + break + } + exportPalette(palette: palette.withUpdatedName(name), completion: { result in + if let result = result { + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: result)]) + } + }) + f(.success(.custom { + delay(0.2, closure: { + close?() + }) + })) + + }, error: { _ in + alert(for: context.window, info: L10n.unknownError) + })) + })) + } + + let controller = InputDataController(dataSignal: signal, title: L10n.newThemeTitle, validateData: { data in + + let name = stateValue.with { $0.name } + + if name.isEmpty { + updateState { current in + return current.withUpdatedError(.init(description: L10n.newThemeEmptyTextError, target: .data)) + } + return .fail(.fields([_id_input_name: .shake])) + } else { + return create() + } + + }, updateDatas: { data in + updateState { current in + return current.withUpdatedCode(data[_id_input_name]?.stringValue ?? current.name).withUpdatedError(nil) + } + return .none + }, afterDisappear: { + disposable.dispose() + }, getBackgroundColor: { + theme.colors.background + }) + + let modalInteractions = ModalInteractions(acceptTitle: L10n.newThemeCreate, accept: { [weak controller] in + _ = controller?.returnKeyAction() + }, drawBorder: true, height: 50, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + close = { [weak modalController] in + modalController?.modal?.close() + } + + return modalController + +} diff --git a/Telegram-Mac/NotificationPreferencesController.swift b/Telegram-Mac/NotificationPreferencesController.swift new file mode 100644 index 0000000000..a71db97470 --- /dev/null +++ b/Telegram-Mac/NotificationPreferencesController.swift @@ -0,0 +1,321 @@ +// +// NotificationPreferencesController.swift +// Telegram +// +// Created by Mikhail Filimonov on 03/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + +enum NotificationsAndSoundsEntryTag: ItemListItemTag { + case allAccounts + case messagePreviews + case includeChannels + case unreadCountCategory + case joinedNotifications + case reset + + var stableId: InputDataEntryId { + switch self { + case .allAccounts: + return .general(_id_all_accounts) + case .messagePreviews: + return .general(_id_message_preview) + case .includeChannels: + return .general(_id_include_channels) + case .unreadCountCategory: + return .general(_id_count_unred_messages) + case .joinedNotifications: + return .general(_id_new_contacts) + case .reset: + return .general(_id_reset) + } + } + + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? NotificationsAndSoundsEntryTag, self == other { + return true + } else { + return false + } + } +} + +private final class NotificationArguments { + let resetAllNotifications:() -> Void + let toggleMessagesPreview:() -> Void + let toggleNotifications:() -> Void + let notificationTone:(String) -> Void + let toggleIncludeUnreadChats:(Bool) -> Void + let toggleCountUnreadMessages:(Bool) -> Void + let toggleIncludeGroups:(Bool) -> Void + let toggleIncludeChannels:(Bool) -> Void + let allAcounts: ()-> Void + let snoof: ()-> Void + let updateJoinedNotifications: (Bool) -> Void + let toggleBadge: (Bool)->Void + let toggleRequestUserAttention: ()->Void + let toggleInAppSounds:(Bool)->Void + init(resetAllNotifications: @escaping() -> Void, toggleMessagesPreview:@escaping() -> Void, toggleNotifications:@escaping() -> Void, notificationTone:@escaping(String) -> Void, toggleIncludeUnreadChats:@escaping(Bool) -> Void, toggleCountUnreadMessages:@escaping(Bool) -> Void, toggleIncludeGroups:@escaping(Bool) -> Void, toggleIncludeChannels:@escaping(Bool) -> Void, allAcounts: @escaping()-> Void, snoof: @escaping()-> Void, updateJoinedNotifications: @escaping(Bool) -> Void, toggleBadge: @escaping(Bool)->Void, toggleRequestUserAttention: @escaping ()->Void, toggleInAppSounds: @escaping(Bool)->Void) { + self.resetAllNotifications = resetAllNotifications + self.toggleMessagesPreview = toggleMessagesPreview + self.toggleNotifications = toggleNotifications + self.notificationTone = notificationTone + self.toggleIncludeUnreadChats = toggleIncludeUnreadChats + self.toggleCountUnreadMessages = toggleCountUnreadMessages + self.toggleIncludeGroups = toggleIncludeGroups + self.toggleIncludeChannels = toggleIncludeChannels + self.allAcounts = allAcounts + self.snoof = snoof + self.updateJoinedNotifications = updateJoinedNotifications + self.toggleBadge = toggleBadge + self.toggleRequestUserAttention = toggleRequestUserAttention + self.toggleInAppSounds = toggleInAppSounds + } +} + +private let _id_all_accounts = InputDataIdentifier("_id_all_accounts") +private let _id_notifications = InputDataIdentifier("_id_notifications") +private let _id_message_preview = InputDataIdentifier("_id_message_preview") +private let _id_reset = InputDataIdentifier("_id_reset") + +private let _id_badge_enabled = InputDataIdentifier("_badge_enabled") +private let _id_include_muted_chats = InputDataIdentifier("_id_include_muted_chats") +private let _id_include_public_group = InputDataIdentifier("_id_include_public_group") +private let _id_include_channels = InputDataIdentifier("_id_include_channels") +private let _id_count_unred_messages = InputDataIdentifier("_id_count_unred_messages") +private let _id_new_contacts = InputDataIdentifier("_id_new_contacts") +private let _id_snoof = InputDataIdentifier("_id_snoof") +private let _id_tone = InputDataIdentifier("_id_tone") +private let _id_bounce = InputDataIdentifier("_id_bounce") + +private let _id_message_effect = InputDataIdentifier("_id_message_effect") + +private func notificationEntries(settings:InAppNotificationSettings, globalSettings: GlobalNotificationSettingsSet, accounts: [AccountWithInfo], arguments: NotificationArguments) -> [InputDataEntry] { + + var entries:[InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + if accounts.count > 1 { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsShowNotificationsFrom), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_all_accounts, data: InputDataGeneralData(name: L10n.notificationSettingsAllAccounts, color: theme.colors.text, type: .switchable(settings.notifyAllAccounts), viewType: .singleItem, action: { + arguments.allAcounts() + }))) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(settings.notifyAllAccounts ? L10n.notificationSettingsShowNotificationsFromOn : L10n.notificationSettingsShowNotificationsFromOff), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + } + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsToggleNotificationsHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_notifications, data: InputDataGeneralData(name: L10n.notificationSettingsToggleNotifications, color: theme.colors.text, type: .switchable(settings.enabled), viewType: .firstItem, action: { + arguments.toggleNotifications() + }))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_message_preview, data: InputDataGeneralData(name: L10n.notificationSettingsMessagesPreview, color: theme.colors.text, type: .switchable(settings.displayPreviews), viewType: .innerItem, action: { + arguments.toggleMessagesPreview() + }))) + index += 1 + + + let tones = ObjcUtils.notificationTones("Default") + var tonesItems:[SPopoverItem] = [] + for tone in tones { + tonesItems.append(SPopoverItem(localizedString(tone), { + arguments.notificationTone(tone) + })) + } + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_tone, data: InputDataGeneralData(name: L10n.notificationSettingsNotificationTone, color: theme.colors.text, type: .contextSelector(settings.tone.isEmpty ? L10n.notificationSettingsToneDefault : localizedString(settings.tone), tonesItems), viewType: .innerItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_bounce, data: InputDataGeneralData(name: L10n.notificationSettingsBounceDockIcon, color: theme.colors.text, type: .switchable(settings.requestUserAttention), viewType: .innerItem, action: { + arguments.toggleRequestUserAttention() + }))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_reset, data: InputDataGeneralData(name: L10n.notificationSettingsResetNotifications, color: theme.colors.text, type: .none, viewType: .lastItem, action: { + arguments.resetAllNotifications() + }))) + index += 1 + + + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsSoundEffects), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_message_effect, data: InputDataGeneralData(name: L10n.notificationSettingsSendMessageEffect, color: theme.colors.text, type: .switchable(FastSettings.inAppSounds), viewType: .singleItem, action: { + arguments.toggleInAppSounds(!FastSettings.inAppSounds) + }))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsBadgeHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_badge_enabled, data: InputDataGeneralData(name: L10n.notificationSettingsBadgeEnabled, color: theme.colors.text, type: .switchable(settings.badgeEnabled), viewType: .firstItem, action: { + arguments.toggleBadge(!settings.badgeEnabled) + }))) + index += 1 + + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_include_public_group, data: InputDataGeneralData(name: L10n.notificationSettingsIncludeGroups, color: theme.colors.text, type: .switchable(settings.totalUnreadCountIncludeTags.contains(.group)), viewType: .innerItem, enabled: settings.badgeEnabled, action: { + arguments.toggleIncludeGroups(!settings.totalUnreadCountIncludeTags.contains(.group)) + }))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_include_channels, data: InputDataGeneralData(name: L10n.notificationSettingsIncludeChannels, color: theme.colors.text, type: .switchable(settings.totalUnreadCountIncludeTags.contains(.channel)), viewType: .innerItem, enabled: settings.badgeEnabled, action: { + arguments.toggleIncludeChannels(!settings.totalUnreadCountIncludeTags.contains(.channel)) + }))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_count_unred_messages, data: InputDataGeneralData(name: L10n.notificationSettingsCountUnreadMessages, color: theme.colors.text, type: .switchable(settings.totalUnreadCountDisplayCategory == .messages), viewType: .lastItem, enabled: settings.badgeEnabled, action: { + arguments.toggleCountUnreadMessages(settings.totalUnreadCountDisplayCategory != .messages) + }))) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsBadgeDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_new_contacts, data: InputDataGeneralData(name: L10n.notificationSettingsContactJoined, color: theme.colors.text, type: .switchable(globalSettings.contactsJoined), viewType: .singleItem, action: { + arguments.updateJoinedNotifications(!globalSettings.contactsJoined) + }))) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsContactJoinedInfo), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.notificationSettingsSnoofHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_snoof, data: InputDataGeneralData(name: L10n.notificationSettingsSnoof, color: theme.colors.text, type: .switchable(!settings.showNotificationsOutOfFocus), viewType: .singleItem, action: { + arguments.snoof() + }))) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(!settings.showNotificationsOutOfFocus ? L10n.notificationSettingsSnoofOn : L10n.notificationSettingsSnoofOff), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func NotificationPreferencesController(_ context: AccountContext, focusOnItemTag: NotificationsAndSoundsEntryTag? = nil) -> ViewController { + let arguments = NotificationArguments(resetAllNotifications: { + confirm(for: context.window, header: L10n.notificationSettingsConfirmReset, information: tr(L10n.chatConfirmActionUndonable), successHandler: { _ in + _ = resetPeerNotificationSettings(network: context.account.network).start() + }) + }, toggleMessagesPreview: { + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedDisplayPreviews(!$0.displayPreviews)}).start() + }, toggleNotifications: { + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedEnables(!$0.enabled)}).start() + }, notificationTone: { tone in + _ = NSSound(named: tone)?.play() + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedTone(tone)}).start() + }, toggleIncludeUnreadChats: { enable in + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedTotalUnreadCountDisplayStyle(enable ? .raw : .filtered)}).start() + }, toggleCountUnreadMessages: { enable in + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, {$0.withUpdatedTotalUnreadCountDisplayCategory(enable ? .messages : .chats)}).start() + }, toggleIncludeGroups: { enable in + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + var tags: PeerSummaryCounterTags = value.totalUnreadCountIncludeTags + if enable { + tags.insert(.group) + } else { + tags.remove(.group) + } + return value.withUpdatedTotalUnreadCountIncludeTags(tags) + }).start() + }, toggleIncludeChannels: { enable in + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + var tags: PeerSummaryCounterTags = value.totalUnreadCountIncludeTags + if enable { + tags.insert(.channel) + } else { + tags.remove(.channel) + } + return value.withUpdatedTotalUnreadCountIncludeTags(tags) + }).start() + }, allAcounts: { + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + return value.withUpdatedNotifyAllAccounts(!value.notifyAllAccounts) + }).start() + }, snoof: { + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + return value.withUpdatedSnoof(!value.showNotificationsOutOfFocus) + }).start() + }, updateJoinedNotifications: { value in + _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in + var settings = settings + settings.contactsJoined = value + return settings + }).start() + }, toggleBadge: { enabled in + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + return value.withUpdatedBadgeEnabled(enabled) + }).start() + }, toggleRequestUserAttention: { + _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { value in + return value.withUpdatedRequestUserAttention(!value.requestUserAttention) + }).start() + }, toggleInAppSounds: { value in + FastSettings.toggleInAppSouds(value) + }) + + let entriesSignal = combineLatest(queue: prepareQueue, appNotificationSettings(accountManager: context.sharedContext.accountManager), globalNotificationSettings(postbox: context.account.postbox), context.sharedContext.activeAccountsWithInfo |> map { $0.accounts }) |> map { inAppSettings, globalSettings, accounts -> [InputDataEntry] in + return notificationEntries(settings: inAppSettings, globalSettings: globalSettings, accounts: accounts, arguments: arguments) + } + + + let controller = InputDataController(dataSignal: entriesSignal |> map { InputDataSignalValue(entries: $0) }, title: L10n.telegramNotificationSettingsViewController, hasDone: false, identifier: "notification-settings") + + + controller.didLoaded = { controller, _ in + if let focusOnItemTag = focusOnItemTag { + controller.genericView.tableView.scroll(to: .center(id: focusOnItemTag.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets()) + } + } + + return controller +} diff --git a/Telegram-Mac/NotificationSettingsViewController.swift b/Telegram-Mac/NotificationSettingsViewController.swift deleted file mode 100644 index d32714fbbe..0000000000 --- a/Telegram-Mac/NotificationSettingsViewController.swift +++ /dev/null @@ -1,409 +0,0 @@ -// -// NotificationSettingsViewController.swift -// TelegramMac -// -// Created by keepcoder on 15/11/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac - -fileprivate enum NotificationSettingsEntry : Comparable, Identifiable { - case notifications - case messagePreview - case notificationTone(String) - case resetNotifications - case resetText - case whiteSpace(Int32,CGFloat) - case searchField - case peer(ChatListEntry) - case searchPeer(Peer, Int32, PeerNotificationSettings?) - var index:Int32 { - switch self { - case .notifications: - return 1000 - case .messagePreview: - return 2000 - case .notificationTone: - return 3000 - case .resetNotifications: - return 4000 - case .resetText: - return 5000 - case let .whiteSpace(index,_): - return index - case .searchField: - return 6000 - case let .peer(entry): - return INT32_MAX - entry.index.messageIndex.timestamp - case let .searchPeer(_, index, _): - return 7000 + index - } - } - - var stableId:AnyHashable { - switch self { - case let .peer(entry): - switch entry { - case let .HoleEntry(hole): - return hole - default: - return entry.index - } - case let .searchPeer(peer,_,_): - return peer.id - default: - return Int64(index) - } - } -} - -fileprivate func <(lhs:NotificationSettingsEntry, rhs:NotificationSettingsEntry) -> Bool { - return lhs.index < rhs.index -} - -fileprivate func ==(lhs:NotificationSettingsEntry, rhs:NotificationSettingsEntry) -> Bool { - switch lhs { - case let .peer(lhsEntry): - if case let .peer(rhsEntry) = rhs { - return lhsEntry == rhsEntry - } - case let .searchPeer(lhsPeer, lhsIndex, lhsNotificationSettings): - if case let .searchPeer(rhsPeer, rhsIndex, rhsNotificationSettings) = rhs { - - if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings { - if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) { - return false - } - } else if (lhsNotificationSettings != nil) != (rhsNotificationSettings != nil) { - return false - } - - return lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex - } - case let .notificationTone(lhsTone): - if case let .notificationTone(rhsTone) = rhs { - return lhsTone == rhsTone - } - return false - default: - return lhs.stableId == rhs.stableId - } - return lhs.stableId == rhs.stableId -} - -private func simpleEntries(_ settings:InAppNotificationSettings) -> [NotificationSettingsEntry] { - var simpleEntries:[NotificationSettingsEntry] = [] - simpleEntries.append(.whiteSpace(1,15)) - simpleEntries.append(.notifications) - simpleEntries.append(.messagePreview) - simpleEntries.append(.notificationTone(settings.tone)) - simpleEntries.append(.resetNotifications) - simpleEntries.append(.resetText) - simpleEntries.append(.whiteSpace(5001,40)) - simpleEntries.append(.searchField) - return simpleEntries.reversed() -} - -fileprivate struct NotificationsSettingsList { - let list:[AppearanceWrapperEntry] - let settings:InAppNotificationSettings -} - -struct NotificationSettingsInteractions { - let resetAllNotifications:() -> Void - let toggleMessagesPreview:() -> Void - let toggleNotifications:() -> Void - let notificationTone:(String) -> Void -} - - -class NotificationSettingsViewController: TableViewController { - private let request = Promise() - private let disposable:MetaDisposable = MetaDisposable() - private let notificationsDisposable:MetaDisposable = MetaDisposable() - private var tones:[SPopoverItem] = [] - - private let search:ValuePromise = ValuePromise(ignoreRepeated: true) - - override var removeAfterDisapper:Bool { - return true - } - - - - override func viewDidLoad() { - super.viewDidLoad() - - let previous:Atomic = Atomic(value: nil) - let initialSize = self.atomicSize - let account = self.account - - - search.set(SearchState(state: .None, request: nil)) - - - let searchInteractions = SearchInteractions({ [weak self] state in - self?.search.set(state) - }, { [weak self] state in - self?.search.set(state) - }) - - let interactions = NotificationSettingsInteractions(resetAllNotifications: { [weak self] in - if let window = self?.window , let account = self?.account { - confirm(for: window, with: tr(.notificationSettingsConfirmReset), and: tr(.chatConfirmActionUndonable), successHandler: { _ in - _ = resetPeerNotificationSettings(network: account.network).start() - }) - } - }, toggleMessagesPreview: { - _ = updateInAppNotificationSettingsInteractively(postbox: account.postbox, {$0.withUpdatedDisplayPreviews(!$0.displayPreviews)}).start() - }, toggleNotifications: { - _ = updateInAppNotificationSettingsInteractively(postbox: account.postbox, {$0.withUpdatedEnables(!$0.enabled)}).start() - }, notificationTone: { (tone) in - - }); - - let tones = ObjcUtils.notificationTones("Default") - for tone in tones { - self.tones.append(SPopoverItem(localizedString(tone), { - _ = NSSound(named: NSSound.Name(rawValue: tone))?.play() - _ = updateInAppNotificationSettingsInteractively(postbox: account.postbox, {$0.withUpdatedTone(tone)}).start() - })) - } - - let first = Atomic(value:true) - - let list:Signal,Void> = (combineLatest(request.get() |> distinctUntilChanged, search.get() |> distinctUntilChanged) |> mapToSignal { [weak self] (location, search) -> Signal,Void> in - - var signal:Signal - - - let mappedEntries:Signal<[NotificationSettingsEntry],Void> - - if search.request.isEmpty || search.state == .None { - - switch(location) { - case let .Initial(count, _): - signal = account.viewTracker.tailChatListView(count: count) |> map {$0.0} - case let .Index(index): - signal = account.viewTracker.aroundChatListView(index: index, count: 100) |> map {$0.0} - } - - mappedEntries = signal |> map { value -> [NotificationSettingsEntry] in - var ids:[PeerId:PeerId] = [:] - - return value.entries.filter({ index -> Bool in - switch index { - case .HoleEntry: - return false - case let .MessageEntry(_, _, _, _,_, renderedPeer, _): - let first = ids[renderedPeer.peerId] == nil && renderedPeer.peerId.namespace != Namespaces.Peer.SecretChat - ids[renderedPeer.peerId] = renderedPeer.peerId - return first - } - - }).map { peer -> NotificationSettingsEntry in - return .peer(peer) - } - } |> mapToQueue { list in return .single(list)} - - - } else { - - - var ids:[PeerId:Peer] = [:] - let foundLocalPeers = combineLatest(account.postbox.searchPeers(query: search.request.lowercased()) |> map {$0.flatMap({$0.chatMainPeer}).filter({!($0 is TelegramSecretChat)})},account.postbox.searchContacts(query: search.request.lowercased())) - |> map { (peers, contacts) -> [Peer] in - return (peers + contacts).filter({ (peer) -> Bool in - let first = ids[peer.id] == nil - ids[peer.id] = peer - return first - }) - } - - mappedEntries = foundLocalPeers |> mapToSignal { peers -> Signal<[NotificationSettingsEntry], Void> in - - return combineLatest(peers.map { peer -> Signal in - - return account.postbox.modify { (modifier) -> TelegramPeerNotificationSettings? in - return modifier.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings - } - - }) |> map { (settings) -> [NotificationSettingsEntry] in - var entries:[NotificationSettingsEntry] = [] - for i in 0 ..< peers.count { - entries.append(.searchPeer(peers[i], Int32(i) + 1, settings[i])) - } - return entries - } - } - } - - return combineLatest(mappedEntries, account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.inAppNotificationSettings]), appearanceSignal) |> map { value, settings, appearance -> NotificationsSettingsList in - - let inAppSettings: InAppNotificationSettings - if let settings = settings.values[ApplicationSpecificPreferencesKeys.inAppNotificationSettings] as? InAppNotificationSettings { - inAppSettings = settings - } else { - inAppSettings = InAppNotificationSettings.defaultSettings - } - return NotificationsSettingsList(list: (simpleEntries(inAppSettings) + value).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)}.sorted(by: <), settings: inAppSettings) - } |> mapToQueue { [weak self] value -> Signal, Void> in - if let strongSelf = self { - return strongSelf.prepareEntries(from: previous.modify {$0}, to: value, account: account, interactions:interactions, searchInteractions: searchInteractions, initialSize: initialSize.modify({$0}), animated: !first.swap(false)) - } - return .never() - } - - }) - |> deliverOnMainQueue - - - - - let apply = list |> mapToSignal { [weak self] transition -> Signal in - - self?.readyOnce() - - self?.genericView.resetScrollNotifies() - _ = previous.swap(transition.entries) - self?.genericView.merge(with: transition) - self?.searchView?.searchInteractions = searchInteractions - return .complete() - - } - - disposable.set(apply.start()) - - request.set(.single(.Initial(100, nil))) - - } - - fileprivate func prepareEntries(from:NotificationsSettingsList?, to:NotificationsSettingsList, account:Account, interactions:NotificationSettingsInteractions, searchInteractions:SearchInteractions, initialSize:NSSize, animated:Bool) -> Signal,Void> { - - return Signal { [weak self] subscriber in - - let (deleted,inserted, updated) = proccessEntriesWithoutReverse(from?.list, right: to.list, { (entry) -> TableRowItem in - - switch entry.entry { - case .notifications: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.notificationSettingsToggleNotifications), type: .switchable(stateback: { () -> Bool in - return to.settings.enabled - }), action: { - interactions.toggleNotifications() - }) - case .messagePreview: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.notificationSettingsMessagesPreview), type: .switchable(stateback: { () -> Bool in - return to.settings.displayPreviews - }), action: { - interactions.toggleMessagesPreview() - }) - case let .whiteSpace(_, height): - return GeneralRowItem(initialSize, height: height, stableId: entry.stableId) - case .notificationTone: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.notificationSettingsNotificationTone), type: .context(stateback: { () -> String in - return to.settings.tone.isEmpty ? tr(.notificationSettingsToneDefault) : localizedString(to.settings.tone) - }), action: { [weak self] in - self?.showToneOptions() - }) - case .resetNotifications: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.notificationSettingsResetNotifications), type: .next, action: { - interactions.resetAllNotifications() - }) - case .resetText: - return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: tr(.notificationSettingsResetNotificationsText)) - case .searchField: - return SearchRowItem(initialSize, stableId: entry.stableId, searchInteractions:searchInteractions) - case let .peer(peerEntry): - switch peerEntry { - case .HoleEntry: - return GeneralRowItem(initialSize, stableId:entry.stableId) - case let .MessageEntry(_, _, _, notifySettings,_, renderedPeer, _): - if let peer = renderedPeer.chatMainPeer { - return ShortPeerRowItem(initialSize, peer: peer, account: account, height: 40, photoSize: NSMakeSize(30,30), inset: NSEdgeInsets(left:30,right:30), generalType:.switchable(stateback: { - if let notifySettings = notifySettings as? TelegramPeerNotificationSettings { - if case .muted(_) = notifySettings.muteState { - return false - } - } - return true - }), action:{ [weak self] in - self?.notificationsDisposable.set(togglePeerMuted(account: account, peerId: peer.id).start()) - }) - } - return GeneralRowItem(initialSize, stableId:entry.stableId) - } - case let .searchPeer(peer, _, notifySettings): - return ShortPeerRowItem(initialSize, peer: peer, account: account, height: 40, photoSize: NSMakeSize(30,30), inset: NSEdgeInsets(left:30,right:30), generalType:.switchable(stateback: { - if let notifySettings = notifySettings as? TelegramPeerNotificationSettings { - if case .muted(_) = notifySettings.muteState { - return false - } - } - return true - }), action:{ [weak self] in - self?.notificationsDisposable.set(togglePeerMuted(account: account, peerId: peer.id).start()) - }) - - } - - }) - - let transition = TableEntriesTransition(deleted: deleted, inserted: inserted, updated:updated, entries: to, animated:animated, state: animated ? .none(nil) : .saveVisible(.lower)) - subscriber.putNext(transition) - subscriber.putCompletion() - - return EmptyDisposable - } |> runOn(prepareQueue) - - } - - func showToneOptions() { - if let view = (genericView.viewNecessary(at: 3) as? GeneralInteractedRowView)?.textView { - showPopover(for: view, with: SPopoverViewController(items: tones), edge: .minX, inset: NSMakePoint(0,-30)) - } - - } - - override var canBecomeResponder: Bool { - return true - } - - override func becomeFirstResponder() -> Bool? { - return false - } - - override func firstResponder() -> NSResponder? { - if let item = genericView.item(stableId: NotificationSettingsEntry.searchField.stableId), let view = genericView.viewNecessary(at: item.index) as? SearchRowView { - return view.searchView.input - } - return nil - } - - - var searchView:SearchView? { - if let item = genericView.item(stableId: NotificationSettingsEntry.searchField.stableId), let view = genericView.viewNecessary(at: item.index) as? SearchRowView { - return view.searchView - } - return nil - } - - override func escapeKeyAction() -> KeyHandlerResult { - if let item = genericView.item(stableId: NotificationSettingsEntry.searchField.stableId), let view = genericView.viewNecessary(at: item.index) as? SearchRowView { - if view.searchView.state == .Focus { - return view.searchView.changeResponder() ? .invoked : .rejected - } - } - return .rejected - } - - deinit { - disposable.dispose() - notificationsDisposable.dispose() - } - -} diff --git a/Telegram-Mac/ObjcUtils.h b/Telegram-Mac/ObjcUtils.h index 965a2508ad..ddcb04f906 100644 --- a/Telegram-Mac/ObjcUtils.h +++ b/Telegram-Mac/ObjcUtils.h @@ -9,39 +9,56 @@ #import #import #import + + + +@interface OpenWithObject : NSObject +@property (nonatomic, strong,readonly) NSString *fullname; +@property (nonatomic, strong,readonly) NSURL *app; +@property (nonatomic, strong,readonly) NSImage *icon; + + +-(id)initWithFullname:(NSString *)fullname app:(NSURL *)app icon:(NSImage *)icon; + +@end + + + @interface ObjcUtils : NSObject -+ (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTags:(bool)highlightMentionsAndTags highlightCommands:(bool)highlightCommands; -+(NSString *) md5:(NSString *)string; -+ (NSArray *)findElementsByClass:(NSString *)className inView:(NSView *)view; -+ (NSString *)stringForEmojiHashOfData:(NSData *)data count:(NSInteger)count positionExtractor:(int32_t (^)(uint8_t *, int32_t, int32_t))positionExtractor; ++ (NSData *)dataFromHexString:(NSString *)string; ++ (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentions:(bool)highlightMentions highlightTags:(bool)highlightTags highlightCommands:(bool)highlightCommands dotInMention:(bool)dotInMention; ++(NSString * __nonnull) md5:(NSString *__nonnull)string; ++(NSArray *__nonnull)findElementsByClass:(NSString *__nonnull)className inView:(NSView *__nonnull)view; ++(NSString * __nonnull)stringForEmojiHashOfData:(NSData *__nonnull)data count:(NSInteger)count positionExtractor:(int32_t (^__nonnull)(uint8_t *__nonnull, int32_t, int32_t))positionExtractor; +(NSArray *)bufferList:(CMSampleBufferRef)sampleBuffer; -+(NSString *)callEmojies:(NSData *)keySha256; -+ (NSArray *)getEmojiFromString:(NSString *)string; -+(NSOpenPanel *)openPanel; -+(NSSavePanel *)savePanel; -+(NSEvent *)scrollEvent:(NSEvent *)from; -+(NSSize)gifDimensionSize:(NSString *)path; ++(NSString * __nonnull)callEmojies:(NSData *__nonnull)keySha256; ++ (NSArray * __nonnull)getEmojiFromString:(NSString * __nonnull)string; ++(NSOpenPanel * __nonnull)openPanel; ++(NSSavePanel * __nonnull)savePanel; ++(NSEvent * __nonnull)scrollEvent:(NSEvent *__nonnull)from; ++(NSSize)gifDimensionSize:(NSString * __nonnull)path; +(int)colorMask:(int)idValue mainId:(int)mainId; -+(NSArray *)notificationTones:(NSString *)def; -+(NSString *)youtubeIdentifier:(NSString *)url; -@end ++(NSArray * __nonnull)notificationTones:(NSString * __nonnull)def; ++(NSString * __nullable)youtubeIdentifier:(NSString * __nonnull)url;; ++ (NSString * __nullable)_youtubeVideoIdFromText:(NSString * __nullable)text originalUrl:(NSString * __nullable)originalUrl startTime:(NSTimeInterval *)startTime; ++(NSArray *)appsForFileUrl:(NSString *)fileUrl; -@interface NSFileManager (Extension) -+ (NSString *)xattrStringValueForKey:(NSString *)key atURL:(NSURL *)URL; -+ (BOOL)setXAttrStringValue:(NSString *)value forKey:(NSString *)key atURL:(NSURL *)URL; @end + + + @interface NSMutableAttributedString(Extension) -(void)detectBoldColorInStringWithFont:(NSFont *)font; @end -NSArray *cut_long_message(NSString *message, int max_length); +NSArray * __nonnull cut_long_message(NSString *message, int max_length); int64_t SystemIdleTime(void); -NSDictionary *audioTags(AVURLAsset *asset); -NSImage *TGIdenticonImage(NSData *data, NSData *additionalData, CGSize size); +NSDictionary * __nonnull audioTags(AVURLAsset *asset); +NSImage * __nonnull TGIdenticonImage(NSData *data, NSData *additionalData, CGSize size); @interface NSData (TG) -- (NSString *)stringByEncodingInHex; +- (NSString * __nonnull)stringByEncodingInHex; @end BOOL isEnterAccessObjc(NSEvent *theEvent, BOOL byCmdEnter); @@ -50,3 +67,7 @@ BOOL isEnterEventObjc(NSEvent *theEvent); int colorIndexForGroupId(int64_t groupId); int64_t TGPeerIdFromChannelId(int32_t channelId); int colorIndexForUid(int32_t uid, int32_t myUserId); + + +NSArray * __nonnull currentAppInputSource(); +NSEvent * __nullable createScrollWheelEvent(); diff --git a/Telegram-Mac/ObjcUtils.m b/Telegram-Mac/ObjcUtils.m index 7b55267ae4..3546e23178 100644 --- a/Telegram-Mac/ObjcUtils.m +++ b/Telegram-Mac/ObjcUtils.m @@ -9,10 +9,47 @@ #import "ObjcUtils.h" #import #import +#import +@implementation OpenWithObject + +-(id)initWithFullname:(NSString *)fullname app:(NSURL *)app icon:(NSImage *)icon { + if(self = [super init]) { + _fullname = fullname; + _app = app; + _icon = icon; + } + + return self; +} + + +@end @implementation ObjcUtils -+ (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTags:(bool)highlightMentionsAndTags highlightCommands:(bool)highlightCommands + + ++ (NSData *)dataFromHexString:(NSString *)string +{ + string = [string lowercaseString]; + NSMutableData *data= [NSMutableData new]; + unsigned char whole_byte; + char byte_chars[3] = {'\0','\0','\0'}; + int i = 0; + int length = string.length; + while (i < length-1) { + char c = [string characterAtIndex:i++]; + if (c < '0' || (c > '9' && c < 'a') || c > 'f') + continue; + byte_chars[0] = c; + byte_chars[1] = [string characterAtIndex:i++]; + whole_byte = strtol(byte_chars, NULL, 16); + [data appendBytes:&whole_byte length:1]; + } + return data; +} + ++ (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentions:(bool)highlightMentions highlightTags:(bool)highlightTags highlightCommands:(bool)highlightCommands dotInMention:(bool)dotInMention { bool containsSomething = false; @@ -31,7 +68,12 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag { unichar c = characterAtIndexImp(text, sel, i); - if (highlightMentionsAndTags && (c == '@' || c == '#')) + if (highlightMentions && (c == '@')) + { + containsSomething = true; + break; + } + if (highlightTags && (c == '#')) { containsSomething = true; break; @@ -111,7 +153,8 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag { @try { NSTextCheckingType type = [match resultType]; - if (type == NSTextCheckingTypeLink || type == NSTextCheckingTypePhoneNumber) + NSString *scheme = [[[match URL] scheme] lowercaseString]; + if ((type == NSTextCheckingTypeLink || type == NSTextCheckingTypePhoneNumber) && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"] || [scheme isEqualToString:@"tg"] || [scheme isEqualToString:@"ton"] || scheme == nil)) { [results addObject:[NSValue valueWithRange:match.range]]; } @@ -132,7 +175,7 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag characterSet = [NSCharacterSet alphanumericCharacterSet]; }); - if (containsSomething && (highlightMentionsAndTags || highlightCommands)) + if (containsSomething && (highlightMentions || highlightTags || highlightCommands)) { int mentionStart = -1; int hashtagStart = -1; @@ -142,11 +185,11 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag for (int i = 0; i < length; i++) { unichar c = characterAtIndexImp(text, sel, i); - if (highlightMentionsAndTags && commandStart == -1) + if ((highlightMentions || highlightTags) && commandStart == -1) { if (mentionStart != -1) { - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_' || (dotInMention && c == '.')))) { if (i > mentionStart + 1) { @@ -238,6 +281,218 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag return nil; } ++ (NSString *)_youtubeVideoIdFromText:(NSString *)text originalUrl:(NSString *)originalUrl startTime:(NSTimeInterval *)startTime { + if ([text hasPrefix:@"http://www.youtube.com/watch?v="] || [text hasPrefix:@"https://www.youtube.com/watch?v="] || [text hasPrefix:@"http://m.youtube.com/watch?v="] || [text hasPrefix:@"https://m.youtube.com/watch?v="]) + { + NSRange range1 = [text rangeOfString:@"?v="]; + bool match = true; + for (NSInteger i = range1.location + range1.length; i < (NSInteger)text.length; i++) + { + unichar c = [text characterAtIndex:i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#')) + { + match = false; + break; + } + } + + if (match) + { + NSString *videoId = nil; + NSRange ampRange = [text rangeOfString:@"&"]; + NSRange hashRange = [text rangeOfString:@"#"]; + if (ampRange.location != NSNotFound || hashRange.location != NSNotFound) + { + NSInteger location = MIN(ampRange.location, hashRange.location); + videoId = [text substringWithRange:NSMakeRange(range1.location + range1.length, location - range1.location - range1.length)]; + } + else + videoId = [text substringFromIndex:range1.location + range1.length]; + + if (videoId.length != 0) + return videoId; + } + } + else if ([text hasPrefix:@"http://youtu.be/"] || [text hasPrefix:@"https://youtu.be/"] || [text hasPrefix:@"http://www.youtube.com/embed/"] || [text hasPrefix:@"https://www.youtube.com/embed/"]) + { + NSString *suffix = @""; + + NSMutableArray *prefixes = [NSMutableArray arrayWithArray:@ + [ + @"http://youtu.be/", + @"https://youtu.be/", + @"http://www.youtube.com/embed/", + @"https://www.youtube.com/embed/" + ]]; + + while (suffix.length == 0 && prefixes.count > 0) + { + NSString *prefix = prefixes.firstObject; + if ([text hasPrefix:prefix]) + { + suffix = [text substringFromIndex:prefix.length]; + break; + } + else + { + [prefixes removeObjectAtIndex:0]; + } + } + + NSString *queryString = nil; + for (int i = 0; i < (int)suffix.length; i++) + { + unichar c = [suffix characterAtIndex:i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '=' || c == '&' || c == '#')) + { + if (c == '?') + { + queryString = [suffix substringFromIndex:i + 1]; + suffix = [suffix substringToIndex:i]; + break; + } + else + { + return nil; + } + } + } + + if (startTime != NULL) + { + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + NSString *queryString = [NSURL URLWithString:originalUrl].query; + for (NSString *param in [queryString componentsSeparatedByString:@"&"]) + { + NSArray *components = [param componentsSeparatedByString:@"="]; + if (components.count < 2) + continue; + [params setObject:components.lastObject forKey:components.firstObject]; + } + + NSString *timeParam = params[@"t"]; + if (timeParam != nil) + { + NSTimeInterval position = 0.0; + if ([timeParam rangeOfString:@"s"].location != NSNotFound) + { + NSString *value; + NSUInteger location = 0; + for (NSUInteger i = 0; i < timeParam.length; i++) + { + unichar c = [timeParam characterAtIndex:i]; + if ((c < '0' || c > '9')) + { + value = [timeParam substringWithRange:NSMakeRange(location, i - location)]; + location = i + 1; + switch (c) + { + case 's': + position += value.doubleValue; + break; + + case 'm': + position += value.doubleValue * 60.0; + break; + + case 'h': + position += value.doubleValue * 3600.0; + break; + + default: + break; + } + } + } + } + else + { + position = timeParam.doubleValue; + } + + *startTime = position; + } + } + + return suffix; + } + + return nil; +} + ++ (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon { + NSBundle *b = [NSBundle bundleWithURL:url]; + if (b) { + NSString *path = [url path]; + *name = [[NSFileManager defaultManager] displayNameAtPath: path]; + if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"]; + if (*name) { + *bundle = [b bundleIdentifier]; + if (bundle) { + *version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + *icon = [[NSWorkspace sharedWorkspace] iconForFile: path]; + if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)]; + return; + } + } + } + *bundle = *name = *version = nil; + *icon = nil; +} + ++(NSArray *)appsForFileUrl:(NSString *)fileUrl { + + NSArray *appsList = (__bridge NSArray*)LSCopyApplicationURLsForURL((__bridge CFURLRef)[NSURL fileURLWithPath:fileUrl], kLSRolesAll); + NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16]; + int fullcount = 0; + for (id app in appsList) { + if (fullcount > 15) break; + + NSString *bundle = nil, *name = nil, *version = nil; + NSImage *icon = nil; + [ObjcUtils fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon]; + if (bundle && name) { + NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"]; + if (!version) version = @""; + + NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key]; + if (!versions) { + versions = [NSMutableDictionary dictionaryWithCapacity:2]; + [data setValue:versions forKey:key]; + } + if (![versions objectForKey:version]) { + [versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version]; + ++fullcount; + } + } + } + + + NSMutableArray *apps = [NSMutableArray arrayWithCapacity:fullcount]; + for (id key in data) { + NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key]; + for (id ver in val) { + NSArray *app = (NSArray*)[val objectForKey:ver]; + + NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver; + BOOL showVersion = ([val count] > 1); + if (!showVersion) { + NSError *error = NULL; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error]; + showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0,[version length])]; + } + if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""]; + OpenWithObject *a = [[OpenWithObject alloc] initWithFullname:fullname app:app[2] icon:app[1]]; + + [apps addObject:a]; + } + } + + + return apps; +} + + (NSArray *)getEmojiFromString:(NSString *)string { __block NSMutableDictionary *temp = [NSMutableDictionary dictionary]; @@ -343,6 +598,8 @@ + (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTag } return array; } + + +(NSString *) md5:(NSString *)string { @@ -693,7 +950,8 @@ static int32_t get_bits(uint8_t const *bytes, unsigned int bitOffset, unsigned i [list addObject:@"NotificationSettingsToneNone"]; - NSArray *dirContents = [fm contentsOfDirectoryAtPath:@"~/Library/Sounds" error:nil]; + NSString *homeSoundsPath = [NSHomeDirectory() stringByAppendingString:@"/Library/Sounds"]; + NSArray *dirContents = [fm contentsOfDirectoryAtPath:homeSoundsPath error:nil]; [list addObjectsFromArray:[dirContents filteredArrayUsingPredicate:fltr]]; dirContents = [fm contentsOfDirectoryAtPath:@"/Library/Sounds" error:nil]; @@ -947,7 +1205,6 @@ int64_t SystemIdleTime(void) { NSArray *metadata = [asset metadataForFormat:obj]; for (AVMutableMetadataItem *metaItem in metadata) { - NSLog(@"%@ : %@", metaItem.identifier, (NSString *)metaItem.value); if([metaItem.identifier isEqualToString:AVMetadataIdentifierID3MetadataLeadPerformer]) { artistName = (NSString *) metaItem.value; } else if([metaItem.identifier isEqualToString:AVMetadataIdentifierID3MetadataTitleDescription]) { @@ -1015,7 +1272,7 @@ BOOL isEnterEventObjc(NSEvent *theEvent) { BOOL isEnterAccessObjc(NSEvent *theEvent, BOOL byCmdEnter) { if(isEnterEventObjc(theEvent)) { NSUInteger flags = (theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask); - return !byCmdEnter ? flags == 0 || flags == 65536 : (theEvent.modifierFlags & NSCommandKeyMask) > 0; + return !byCmdEnter ? flags == 0 || flags == 65536 || flags == 2097152 : (theEvent.modifierFlags & NSCommandKeyMask) > 0; } return NO; } @@ -1054,3 +1311,36 @@ inline int colorIndexForGroupId(int64_t groupId) return colorIndex; } +/* + Sorry guys there was a code which caused a crash on text input + */ + +NSArray * __nonnull currentAppInputSource() +{ + + CFArrayRef inputSourcesList = TISCreateInputSourceList(NULL, false); + + CFIndex inputSourcesCount = CFArrayGetCount(inputSourcesList); + + NSMutableArray *inputs = [[NSMutableArray alloc] init]; + + for (int i = 0; i < inputSourcesCount; i++) { + NSArray* list = (__bridge NSArray *)(TISGetInputSourceProperty(CFArrayGetValueAtIndex(inputSourcesList, i), kTISPropertyInputSourceLanguages)); + if ([list count] > 0 && list[0] != nil) { + [inputs addObject:list[0]]; + } + + } + + return inputs; +} + +NSEvent * __nullable createScrollWheelEvent() { + CGEventRef upEvent = CGEventCreateScrollWheelEvent( + NULL, + kCGScrollEventUnitPixel, + 1, 0, 0 ); + NSEvent *event = [NSEvent eventWithCGEvent:upEvent]; + CFRelease(upEvent); + return event; +} diff --git a/Telegram-Mac/OngoingCallConnectionDescription.h b/Telegram-Mac/OngoingCallConnectionDescription.h new file mode 100644 index 0000000000..e44f7eedb7 --- /dev/null +++ b/Telegram-Mac/OngoingCallConnectionDescription.h @@ -0,0 +1,13 @@ +// +// OngoingCallConnectionDescription.h +// Telegram +// +// Created by keepcoder on 03/05/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +#import + + + + diff --git a/Telegram-Mac/OngoingCallConnectionDescription.m b/Telegram-Mac/OngoingCallConnectionDescription.m new file mode 100644 index 0000000000..2df95a874e --- /dev/null +++ b/Telegram-Mac/OngoingCallConnectionDescription.m @@ -0,0 +1,10 @@ +// +// OngoingCallConnectionDescription.m +// Telegram +// +// Created by keepcoder on 03/05/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +#import "OngoingCallConnectionDescription.h" + diff --git a/Telegram-Mac/OngoingCallContext.swift b/Telegram-Mac/OngoingCallContext.swift new file mode 100644 index 0000000000..5628f16928 --- /dev/null +++ b/Telegram-Mac/OngoingCallContext.swift @@ -0,0 +1,689 @@ +// +// OngoingCallContext.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + + + +private func callConnectionDescription(_ connection: CallSessionConnection) -> OngoingCallConnectionDescription { + return OngoingCallConnectionDescription(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) +} + +private func callConnectionDescriptionWebrtc(_ connection: CallSessionConnection) -> OngoingCallConnectionDescriptionWebrtc { + return OngoingCallConnectionDescriptionWebrtc(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) +} + +/*private func callConnectionDescriptionWebrtcCustom(_ connection: CallSessionConnection) -> OngoingCallConnectionDescriptionWebrtcCustom { + return OngoingCallConnectionDescriptionWebrtcCustom(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) + }*/ + +private let callLogsLimit = 20 + +public func callLogNameForId(id: Int64, account: Account) -> String? { + let path = callLogsPath(account: account) + let namePrefix = "\(id)_" + + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { + for url in enumerator { + if let url = url as? URL { + if url.lastPathComponent.hasPrefix(namePrefix) { + return url.lastPathComponent + } + } + } + } + return nil +} + +public func callLogsPath(account: Account) -> String { + return account.basePath + "/calls" +} + +private func cleanupCallLogs(account: Account) { + let path = callLogsPath(account: account) + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: path, isDirectory: nil) { + try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + } + + var oldest: (URL, Date)? = nil + var count = 0 + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { + for url in enumerator { + if let url = url as? URL { + if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate { + if let currentOldest = oldest { + if date < currentOldest.1 { + oldest = (url, date) + } + } else { + oldest = (url, date) + } + count += 1 + } + } + } + } + if count > callLogsLimit, let oldest = oldest { + try? fileManager.removeItem(atPath: oldest.0.path) + } +} + +private let setupLogs: Bool = { + OngoingCallThreadLocalContext.setupLoggingFunction({ value in + if let value = value { + Logger.shared.log("TGVOIP", value) + } + }) + OngoingCallThreadLocalContextWebrtc.setupLoggingFunction({ value in + if let value = value { + Logger.shared.log("TGVOIP", value) + } + }) + /*OngoingCallThreadLocalContextWebrtcCustom.setupLoggingFunction({ value in + if let value = value { + Logger.shared.log("TGVOIP", value) + } + })*/ + return true +}() + +public struct OngoingCallContextState: Equatable { + public enum State { + case initializing + case connected + case reconnecting + case failed + } + + public enum VideoState: Equatable { + case notAvailable + case available(Bool) + case active + case activeOutgoing + } + + public enum RemoteVideoState: Equatable { + case inactive + case active + } + + public let state: State + public let videoState: VideoState + public let remoteVideoState: RemoteVideoState +} + +private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ { + private let queue: Queue + + init(queue: Queue) { + self.queue = queue + + super.init() + } + + func dispatch(_ f: @escaping () -> Void) { + self.queue.async { + f() + } + } + + func dispatch(after seconds: Double, block f: @escaping () -> Void) { + self.queue.after(seconds, f) + } + + func isCurrent() -> Bool { + return self.queue.isCurrent() + } +} + +private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType { + switch type { + case .none: + return .wifi + case .wifi: + return .wifi + } +} + +private func ongoingNetworkTypeForTypeWebrtc(_ type: NetworkType) -> OngoingCallNetworkTypeWebrtc { + switch type { + case .none: + return .wifiWebrtc + case .wifi: + return .wifiWebrtc + } +} + +/*private func ongoingNetworkTypeForTypeWebrtcCustom(_ type: NetworkType) -> OngoingCallNetworkTypeWebrtcCustom { + switch type { + case .none: + return .wifi + case .wifi: + return .wifi + case let .cellular(cellular): + switch cellular { + case .edge: + return .cellularEdge + case .gprs: + return .cellularGprs + case .thirdG, .unknown: + return .cellular3g + case .lte: + return .cellularLte + } + } + }*/ + +private func ongoingDataSavingForType(_ type: VoiceCallDataSaving) -> OngoingCallDataSaving { + switch type { + case .never: + return .never + case .cellular: + return .cellular + case .always: + return .always + default: + return .never + } +} + +private func ongoingDataSavingForTypeWebrtc(_ type: VoiceCallDataSaving) -> OngoingCallDataSavingWebrtc { + switch type { + case .never: + return .neverWebrtc + case .cellular: + return .cellularWebrtc + case .always: + return .alwaysWebrtc + default: + return .neverWebrtc + } +} + +/*private func ongoingDataSavingForTypeWebrtcCustom(_ type: VoiceCallDataSaving) -> OngoingCallDataSavingWebrtcCustom { + switch type { + case .never: + return .never + case .cellular: + return .cellular + case .always: + return .always + default: + return .never + } + }*/ + +private protocol OngoingCallThreadLocalContextProtocol: class { + func nativeSetNetworkType(_ type: NetworkType) + func nativeSetIsMuted(_ value: Bool) + func nativeSetVideoEnabled(_ value: Bool) + func nativeSwitchVideoCamera() + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) + func nativeDebugInfo() -> String + func nativeVersion() -> String + func nativeGetDerivedState() -> Data +} + +private final class OngoingCallThreadLocalContextHolder { + let context: OngoingCallThreadLocalContextProtocol + + init(_ context: OngoingCallThreadLocalContextProtocol) { + self.context = context + } +} + +extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol { + func nativeSetNetworkType(_ type: NetworkType) { + self.setNetworkType(ongoingNetworkTypeForType(type)) + } + + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) { + self.stop(completion) + } + + func nativeSetIsMuted(_ value: Bool) { + self.setIsMuted(value) + } + + func nativeSetVideoEnabled(_ value: Bool) { + } + + func nativeSwitchVideoCamera() { + } + + func nativeDebugInfo() -> String { + return self.debugInfo() ?? "" + } + + func nativeVersion() -> String { + return self.version() ?? "" + } + + func nativeGetDerivedState() -> Data { + return self.getDerivedState() + } +} + +extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol { + func nativeSetNetworkType(_ type: NetworkType) { + self.setNetworkType(ongoingNetworkTypeForTypeWebrtc(type)) + } + + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) { + self.stop(completion) + } + + func nativeSetIsMuted(_ value: Bool) { + self.setIsMuted(value) + } + + func nativeSetVideoEnabled(_ value: Bool) { + self.setVideoEnabled(value) + } + + func nativeSwitchVideoCamera() { + self.switchVideoCamera() + } + + func nativeDebugInfo() -> String { + return self.debugInfo() ?? "" + } + + func nativeVersion() -> String { + return self.version() ?? "" + } + + func nativeGetDerivedState() -> Data { + return self.getDerivedState() + } +} + +/*extension OngoingCallThreadLocalContextWebrtcCustom: OngoingCallThreadLocalContextProtocol { + func nativeSetNetworkType(_ type: NetworkType) { + self.setNetworkType(ongoingNetworkTypeForTypeWebrtcCustom(type)) + } + + func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) { + self.stop(completion) + } + + func nativeSetIsMuted(_ value: Bool) { + self.setIsMuted(value) + } + + func nativeDebugInfo() -> String { + return self.debugInfo() ?? "" + } + + func nativeVersion() -> String { + return self.version() ?? "" + } + + func nativeGetDerivedState() -> Data { + return self.getDerivedState() + } + }*/ + +private extension OngoingCallContextState.State { + init(_ state: OngoingCallState) { + switch state { + case .initializing: + self = .initializing + case .connected: + self = .connected + case .failed: + self = .failed + case .reconnecting: + self = .reconnecting + default: + self = .failed + } + } +} + +private extension OngoingCallContextState.State { + init(_ state: OngoingCallStateWebrtc) { + switch state { + case .initializingWebrtc: + self = .initializing + case .connectedWebrtc: + self = .connected + case .failedWebrtc: + self = .failed + case .reconnectingWebrtc: + self = .reconnecting + default: + self = .failed + } + } +} + +/*private extension OngoingCallContextState { + init(_ state: OngoingCallStateWebrtcCustom) { + switch state { + case .initializing: + self = .initializing + case .connected: + self = .connected + case .failed: + self = .failed + case .reconnecting: + self = .reconnecting + default: + self = .failed + } + } + }*/ + +public final class OngoingCallContext { + public struct AuxiliaryServer { + public enum Connection { + case stun + case turn(username: String, password: String) + } + + public let host: String + public let port: Int + public let connection: Connection + + public init( + host: String, + port: Int, + connection: Connection + ) { + self.host = host + self.port = port + self.connection = connection + } + } + + public let internalId: CallSessionInternalId + + private let queue = Queue() + private let account: Account + private let callSessionManager: CallSessionManager + private let logPath: String + + private var contextRef: Unmanaged? + + private let contextState = Promise(nil) + public var state: Signal { + return self.contextState.get() + } + + private var signalingDataDisposable: Disposable? + + private let receptionPromise = Promise(nil) + public var reception: Signal { + return self.receptionPromise.get() + } + + private let audioSessionDisposable = MetaDisposable() + private var networkTypeDisposable: Disposable? + + public static var maxLayer: Int32 { + return OngoingCallThreadLocalContext.maxLayer() + //return max(OngoingCallThreadLocalContext.maxLayer(), OngoingCallThreadLocalContextWebrtc.maxLayer()) + } + + public static func versions(includeExperimental: Bool) -> [String] { + var result: [String] = [OngoingCallThreadLocalContext.version()] + if includeExperimental { + result.append(OngoingCallThreadLocalContextWebrtc.version()) + //result.append(OngoingCallThreadLocalContextWebrtcCustom.version()) + } + return result + } + + public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, auxiliaryServers: [AuxiliaryServer], initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, isVideo: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, logName: String) { + let _ = setupLogs + OngoingCallThreadLocalContext.applyServerConfig(serializedData) + //OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData) + + self.internalId = internalId + self.account = account + self.callSessionManager = callSessionManager + self.logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log" + let logPath = self.logPath + + let queue = self.queue + + cleanupCallLogs(account: account) + queue.sync { + //version == OngoingCallThreadLocalContextWebrtc.version() + if false { + var voipProxyServer: VoipProxyServerWebrtc? + if let proxyServer = proxyServer { + switch proxyServer.connection { + case let .socks5(username, password): + voipProxyServer = VoipProxyServerWebrtc(host: proxyServer.host, port: proxyServer.port, username: username, password: password) + case .mtp: + break + } + } + var rtcServers: [VoipRtcServerWebrtc] = [] + for server in auxiliaryServers { + switch server.connection { + case .stun: + rtcServers.append(VoipRtcServerWebrtc( + host: server.host, + port: Int32(clamping: server.port), + username: "", + password: "", + isTurn: false + )) + case let .turn(username, password): + rtcServers.append(VoipRtcServerWebrtc( + host: server.host, + port: Int32(clamping: server.port), + username: username, + password: password, + isTurn: false + )) + } + } + let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, isVideo: isVideo, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in + callSessionManager?.sendSignalingData(internalId: internalId, data: data) + }) + + self.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) + context.stateChanged = { [weak self] state, videoState, remoteVideoState in + queue.async { + guard let strongSelf = self else { + return + } + let mappedState = OngoingCallContextState.State(state) + let mappedVideoState: OngoingCallContextState.VideoState + switch videoState { + case .inactiveWebrtc: + mappedVideoState = .available(true) + case .activeWebrtc: + mappedVideoState = .active + case .activeOutgoingWebrtc: + mappedVideoState = .activeOutgoing + @unknown default: + mappedVideoState = .available(false) + } + let mappedRemoteVideoState: OngoingCallContextState.RemoteVideoState + switch remoteVideoState { + case .inactiveWebrtc: + mappedRemoteVideoState = .inactive + case .activeWebrtc: + mappedRemoteVideoState = .active + @unknown default: + mappedRemoteVideoState = .inactive + } + strongSelf.contextState.set(.single(OngoingCallContextState(state: mappedState, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState))) + } + } + context.signalBarsChanged = { [weak self] signalBars in + self?.receptionPromise.set(.single(signalBars)) + } + + self.networkTypeDisposable = (updatedNetworkType + |> deliverOn(queue)).start(next: { [weak self] networkType in + self?.withContext { context in + context.nativeSetNetworkType(networkType) + } + }) + } else { + var voipProxyServer: VoipProxyServer? + if let proxyServer = proxyServer { + switch proxyServer.connection { + case let .socks5(username, password): + voipProxyServer = VoipProxyServer(host: proxyServer.host, port: proxyServer.port, username: username, password: password) + case .mtp: + break + } + } + let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType), dataSaving: ongoingDataSavingForType(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath) + + self.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) + context.stateChanged = { [weak self] state in + self?.contextState.set(.single(OngoingCallContextState(state: OngoingCallContextState.State(state), videoState: .notAvailable, remoteVideoState: .inactive))) + } + context.signalBarsChanged = { [weak self] signalBars in + self?.receptionPromise.set(.single(signalBars)) + } + + self.networkTypeDisposable = (updatedNetworkType + |> deliverOn(queue)).start(next: { [weak self] networkType in + self?.withContext { context in + context.nativeSetNetworkType(networkType) + } + }) + } + } + + + self.signalingDataDisposable = (callSessionManager.callSignalingData(internalId: internalId)).start(next: { [weak self] data in + print("data received") + queue.async { + self?.withContext { context in + if let context = context as? OngoingCallThreadLocalContextWebrtc { + context.addSignaling(data) + } + } + } + }) + } + + deinit { + let contextRef = self.contextRef + self.queue.async { + contextRef?.release() + } + + self.audioSessionDisposable.dispose() + self.networkTypeDisposable?.dispose() + } + + private func withContext(_ f: @escaping (OngoingCallThreadLocalContextProtocol) -> Void) { + self.queue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + f(context.context) + } + } + } + + public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise) { + let account = self.account + let logPath = self.logPath + + self.withContext { context in + context.nativeStop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in + debugLogValue.set(.single(debugLog)) + let delta = NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: bytesReceivedMobile, + outgoing: bytesSentMobile), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: bytesReceivedWifi, + outgoing: bytesSentWifi)) + updateAccountNetworkUsageStats(account: self.account, category: .call, delta: delta) + + if !logPath.isEmpty, let debugLog = debugLog { + let logsPath = callLogsPath(account: account) + let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) + if let data = debugLog.data(using: .utf8) { + let _ = try? data.write(to: URL(fileURLWithPath: logPath)) + } + } + + if let callId = callId, let debugLog = debugLog { + if sendDebugLogs { + let _ = saveCallDebugLog(network: self.account.network, callId: callId, log: debugLog).start() + } + } + } + let derivedState = context.nativeGetDerivedState() + let _ = updateVoipDerivedStateInteractively(postbox: self.account.postbox, { _ in + return VoipDerivedState(data: derivedState) + }).start() + } + } + + public func setIsMuted(_ value: Bool) { + self.withContext { context in + context.nativeSetIsMuted(value) + } + } + + public func setEnableVideo(_ value: Bool) { + self.withContext { context in + context.nativeSetVideoEnabled(value) + } + } + + public func switchVideoCamera() { + self.withContext { context in + context.nativeSwitchVideoCamera() + } + } + + public func debugInfo() -> Signal<(String, String), NoError> { + let poll = Signal<(String, String), NoError> { subscriber in + self.withContext { context in + let version = context.nativeVersion() + let debugInfo = context.nativeDebugInfo() + subscriber.putNext((version, debugInfo)) + subscriber.putCompletion() + } + + return EmptyDisposable + } + return (poll |> then(.complete() |> delay(0.5, queue: Queue.concurrentDefaultQueue()))) |> restart + } + + public func makeIncomingVideoView(completion: @escaping (NSView?) -> Void) { + self.withContext { context in + if let context = context as? OngoingCallThreadLocalContextWebrtc { + context.makeIncomingVideoView(completion) + } else { + completion(nil) + } + } + } + + public func makeOutgoingVideoView(completion: @escaping (NSView?) -> Void) { + self.withContext { context in + if let context = context as? OngoingCallThreadLocalContextWebrtc { + context.makeOutgoingVideoView(completion) + } else { + completion(nil) + } + } + } +} diff --git a/Telegram-Mac/OngoingCallThreadLocalContext.h b/Telegram-Mac/OngoingCallThreadLocalContext.h new file mode 100644 index 0000000000..d350c12deb --- /dev/null +++ b/Telegram-Mac/OngoingCallThreadLocalContext.h @@ -0,0 +1,94 @@ +// +// CallsBridge.h +// Telegram +// +// Created by keepcoder on 03/05/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +#import +#import "OngoingCallConnectionDescription.h" + +@protocol OngoingCallThreadLocalContextQueue + +- (void)dispatch:(void (^ _Nonnull)())f; +- (bool)isCurrent; + +@end + + +@interface OngoingCallConnectionDescription : NSObject + +@property (nonatomic, readonly) int64_t connectionId; +@property (nonatomic, strong, readonly) NSString * _Nonnull ip; +@property (nonatomic, strong, readonly) NSString * _Nonnull ipv6; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSData * _Nonnull peerTag; + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag; + +@end + +typedef NS_ENUM(int32_t, OngoingCallState) { + OngoingCallStateInitializing, + OngoingCallStateConnected, + OngoingCallStateFailed, + OngoingCallStateReconnecting +}; + +typedef NS_ENUM(int32_t, OngoingCallNetworkType) { + OngoingCallNetworkTypeWifi, + OngoingCallNetworkTypeCellularGprs, + OngoingCallNetworkTypeCellularEdge, + OngoingCallNetworkTypeCellular3g, + OngoingCallNetworkTypeCellularLte +}; + +typedef NS_ENUM(int32_t, OngoingCallDataSaving) { + OngoingCallDataSavingNever, + OngoingCallDataSavingCellular, + OngoingCallDataSavingAlways +}; + + + + +@interface VoipProxyServer : NSObject +@property(nonatomic, strong, readonly) NSString *host; +@property(nonatomic, assign, readonly) int32_t port; +@property(nonatomic, strong, readonly) NSString *username; +@property(nonatomic, strong, readonly) NSString *password; +-(id)initWithHost:(NSString*)host port:(int32_t)port username:(NSString *)username password:(NSString *)password; +@end + +@interface AudioDevice : NSObject +@property(nonatomic, strong, readonly) NSString * deviceId; +@property(nonatomic, strong, readonly) NSString * deviceName; +-(id)initWithDeviceId:(NSString*)deviceId deviceName:(NSString *)deviceName; +@end + +@interface OngoingCallThreadLocalContext : NSObject + ++ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; ++ (void)applyServerConfig:(NSString * _Nullable)data; ++ (int32_t)maxLayer; ++ (NSString * _Nonnull)version; + +@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState); +@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath; +- (void)stop:(void (^_Nonnull)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; + +- (bool)needRate; + +- (NSString * _Nullable)debugInfo; +- (NSString * _Nullable)version; +- (NSData * _Nonnull)getDerivedState; + +- (void)setIsMuted:(bool)isMuted; +- (void)setNetworkType:(OngoingCallNetworkType)networkType; + + + +@end diff --git a/Telegram-Mac/OngoingCallThreadLocalContext.mm b/Telegram-Mac/OngoingCallThreadLocalContext.mm new file mode 100644 index 0000000000..9d9721afd0 --- /dev/null +++ b/Telegram-Mac/OngoingCallThreadLocalContext.mm @@ -0,0 +1,456 @@ +// +// CallsBridge.m +// Telegram +// +// Created by keepcoder on 03/05/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +#import "OngoingCallThreadLocalContext.h" +#import "VoIPController.h" +#import "VoIPServerConfig.h" +#import "TGCallUtils.h" +#import "OngoingCallConnectionDescription.h" +#import "TgVoip.h" +#define CVoIPController tgvoip::VoIPController + +#import +#import + + +@implementation AudioDevice + +-(id)initWithDeviceId:(NSString*)deviceId deviceName:(NSString *)deviceName { + if (self = [super init]) { + _deviceId = deviceId; + _deviceName = deviceName; + } + return self; +} +@end + + +void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { + MTAesEncryptRaw(inBytes, outBytes, length, key, iv); +} + +void TGCallAesIgeDecrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { + MTAesDecryptRaw(inBytes, outBytes, length, key, iv); +} + +void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output) { + MTRawSha1(msg, length, output); +} + +void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output) { + MTRawSha256(msg, length, output); +} + +void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num) { + uint8_t *outData = (uint8_t *)malloc(length); + MTAesCtr *aesCtr = [[MTAesCtr alloc] initWithKey:key keyLength:32 iv:iv ecount:ecount num:*num]; + [aesCtr encryptIn:inOut out:outData len:length]; + memcpy(inOut, outData, length); + free(outData); + + [aesCtr getIv:iv]; + + memcpy(ecount, [aesCtr ecount], 16); + *num = [aesCtr num]; +} + +void TGCallRandomBytes(uint8_t *buffer, size_t length) { + arc4random_buf(buffer, length); +} + +static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkType type) { + switch (type) { + case OngoingCallNetworkTypeWifi: + return TgVoipNetworkType::WiFi; + case OngoingCallNetworkTypeCellularGprs: + return TgVoipNetworkType::Gprs; + case OngoingCallNetworkTypeCellular3g: + return TgVoipNetworkType::ThirdGeneration; + case OngoingCallNetworkTypeCellularLte: + return TgVoipNetworkType::Lte; + default: + return TgVoipNetworkType::ThirdGeneration; + } +} + +static TgVoipDataSaving callControllerDataSavingForType(OngoingCallDataSaving type) { + switch (type) { + case OngoingCallDataSavingNever: + return TgVoipDataSaving::Never; + case OngoingCallDataSavingCellular: + return TgVoipDataSaving::Mobile; + case OngoingCallDataSavingAlways: + return TgVoipDataSaving::Always; + default: + return TgVoipDataSaving::Never; + } +} + + + +@implementation VoipProxyServer +-(id)initWithHost:(NSString*)host port:(int32_t)port username:(NSString *)username password:(NSString *)password { + self = [super init]; + _host = host; + _port = port; + _username = username; + _password = password; + return self; +} +@end + +@implementation OngoingCallConnectionDescription + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag { + self = [super init]; + if (self != nil) { + _connectionId = connectionId; + _ip = ip; + _ipv6 = ipv6; + _port = port; + _peerTag = peerTag; + } + return self; +} + +@end + +static MTAtomic *callContexts() { + static MTAtomic *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[MTAtomic alloc] initWithValue:[[NSMutableDictionary alloc] init]]; + }); + return instance; +} + +@interface OngoingCallThreadLocalContextReference : NSObject + +@property (nonatomic, weak) OngoingCallThreadLocalContext *context; +@property (nonatomic, strong, readonly) id queue; + +@end + +@implementation OngoingCallThreadLocalContextReference + +- (instancetype)initWithContext:(OngoingCallThreadLocalContext *)context queue:(id)queue { + self = [super init]; + if (self != nil) { + self.context = context; + _queue = queue; + } + return self; +} + +@end + +static int32_t nextId = 1; + +static int32_t addContext(OngoingCallThreadLocalContext *context, id queue) { + int32_t contextId = OSAtomicIncrement32(&nextId); + [callContexts() with:^id(NSMutableDictionary *dict) { + dict[@(contextId)] = [[OngoingCallThreadLocalContextReference alloc] initWithContext:context queue:queue]; + return nil; + }]; + return contextId; +} + +static void removeContext(int32_t contextId) { + [callContexts() with:^id(NSMutableDictionary *dict) { + [dict removeObjectForKey:@(contextId)]; + return nil; + }]; +} + +static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalContext *)) { + __block OngoingCallThreadLocalContextReference *reference = nil; + [callContexts() with:^id(NSMutableDictionary *dict) { + reference = dict[@(contextId)]; + return nil; + }]; + if (reference != nil) { + [reference.queue dispatch:^{ + __strong OngoingCallThreadLocalContext *context = reference.context; + if (context != nil) { + f(context); + } + }]; + } +} + +@interface OngoingCallThreadLocalContext () { + id _queue; + int32_t _contextId; + + OngoingCallNetworkType _networkType; + NSTimeInterval _callReceiveTimeout; + NSTimeInterval _callRingTimeout; + NSTimeInterval _callConnectTimeout; + NSTimeInterval _callPacketTimeout; + + std::unique_ptr _tgVoip; + + + OngoingCallState _state; + int32_t _signalBars; + NSData *_lastDerivedState; +} + +- (void)controllerStateChanged:(TgVoipState)state; +- (void)signalBarsChanged:(int32_t)signalBars; + +@end + +@implementation OngoingCallThreadLocalContext + +static void (*InternalVoipLoggingFunction)(NSString *) = NULL; + ++ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { + InternalVoipLoggingFunction = loggingFunction; + TgVoip::setLoggingFunction([](std::string const &string) { + if (InternalVoipLoggingFunction) { + InternalVoipLoggingFunction([[NSString alloc] initWithUTF8String:string.c_str()]); + } + }); +} + ++ (void)applyServerConfig:(NSString *)string { + if (string.length != 0) { + TgVoip::setGlobalServerConfig(std::string(string.UTF8String)); + } +} + ++ (int32_t)maxLayer { + return 92; +} + ++ (NSString *)version { + return [NSString stringWithUTF8String:TgVoip::getVersion().c_str()]; +} + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType dataSaving:(OngoingCallDataSaving)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath { + self = [super init]; + if (self != nil) { + _queue = queue; + assert([queue isCurrent]); + _contextId = addContext(self, queue); + + _callReceiveTimeout = 20.0; + _callRingTimeout = 90.0; + _callConnectTimeout = 30.0; + _callPacketTimeout = 10.0; + _networkType = networkType; + + std::vector derivedStateValue; + derivedStateValue.resize(derivedState.length); + [derivedState getBytes:derivedStateValue.data() length:derivedState.length]; + + TgVoipProxy* proxyValue = nullptr; + if (proxy != nil) { + TgVoipProxy *proxyObject = new TgVoipProxy(); + proxyObject->host = proxy.host.UTF8String; + proxyObject->port = (uint16_t)proxy.port; + proxyObject->login = proxy.username.UTF8String ?: ""; + proxyObject->password = proxy.password.UTF8String ?: ""; + proxyValue = proxyObject; + } + + TgVoipCrypto crypto; + crypto.sha1 = &TGCallSha1; + crypto.sha256 = &TGCallSha256; + crypto.rand_bytes = &TGCallRandomBytes; + crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; + crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; + crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt; + + std::vector endpoints; + NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; + for (OngoingCallConnectionDescription *connection in connections) { + unsigned char peerTag[16]; + [connection.peerTag getBytes:peerTag length:16]; + + TgVoipEndpoint endpoint; + endpoint.endpointId = connection.connectionId; + endpoint.host = { + .ipv4 = std::string(connection.ip.UTF8String), + .ipv6 = std::string(connection.ipv6.UTF8String) + }; + endpoint.port = (uint16_t)connection.port; + endpoint.type = TgVoipEndpointType::UdpRelay; + memcpy(endpoint.peerTag, peerTag, 16); + endpoints.push_back(endpoint); + } + + TgVoipConfig config = { + .initializationTimeout = _callConnectTimeout, + .receiveTimeout = _callPacketTimeout, + .dataSaving = callControllerDataSavingForType(dataSaving), + .enableP2P = static_cast(allowP2P), + .enableAEC = false, + .enableNS = true, + .enableAGC = true, + .enableCallUpgrade = false, + .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), + .maxApiLayer = [OngoingCallThreadLocalContext maxLayer] + }; + + std::vector encryptionKeyValue; + encryptionKeyValue.resize(key.length); + memcpy(encryptionKeyValue.data(), key.bytes, key.length); + + TgVoipEncryptionKey encryptionKey = { + .value = encryptionKeyValue, + .isOutgoing = isOutgoing, + }; + + /* + TgVoipConfig const &config, + TgVoipPersistentState const &persistentState, + std::vector const &endpoints, + std::unique_ptr const &proxy, + TgVoipNetworkType initialNetworkType, + TgVoipEncryptionKey const &encryptionKey + #ifdef TGVOIP_USE_CUSTOM_CRYPTO + , + TgVoipCrypto const &crypto + */ + + + + _tgVoip = TgVoip::makeInstance( + config, + { derivedStateValue }, + endpoints, + proxyValue, + callControllerNetworkTypeForType(networkType), + encryptionKey, + crypto + ); + + _state = OngoingCallStateInitializing; + _signalBars = -1; + + __weak OngoingCallThreadLocalContext *weakSelf = self; + _tgVoip->setOnStateUpdated([weakSelf](TgVoipState state) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf controllerStateChanged:state]; + } + }); + _tgVoip->setOnSignalBarsUpdated([weakSelf](int signalBars) { + __strong OngoingCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf signalBarsChanged:signalBars]; + } + }); + } + return self; +} + +- (void)dealloc { + assert([_queue isCurrent]); + removeContext(_contextId); + if (_tgVoip != NULL) { + [self stop:nil]; + } +} + +- (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { + if (_tgVoip) { + TgVoipFinalState finalState = _tgVoip->stop(); + + NSString *debugLog = [NSString stringWithUTF8String:finalState.debugLog.c_str()]; + _lastDerivedState = [[NSData alloc] initWithBytes:finalState.persistentState.value.data() length:finalState.persistentState.value.size()]; + + _tgVoip = NULL; + + if (completion) { + completion(debugLog, finalState.trafficStats.bytesSentWifi, finalState.trafficStats.bytesReceivedWifi, finalState.trafficStats.bytesSentMobile, finalState.trafficStats.bytesReceivedMobile); + } + } +} + +- (NSString *)debugInfo { + if (_tgVoip != nil) { + auto rawDebugString = _tgVoip->getDebugInfo(); + return [NSString stringWithUTF8String:rawDebugString.c_str()]; + } else { + return nil; + } +} + +- (NSString *)version { + if (_tgVoip != nil) { + return [NSString stringWithUTF8String:_tgVoip->getVersion().c_str()]; + } else { + return nil; + } +} + +- (NSData * _Nonnull)getDerivedState { + if (_tgVoip) { + auto persistentState = _tgVoip->getPersistentState(); + return [[NSData alloc] initWithBytes:persistentState.value.data() length:persistentState.value.size()]; + } else if (_lastDerivedState != nil) { + return _lastDerivedState; + } else { + return [NSData data]; + } +} + +- (void)controllerStateChanged:(TgVoipState)state { + OngoingCallState callState = OngoingCallStateInitializing; + switch (state) { + case TgVoipState::Established: + callState = OngoingCallStateConnected; + break; + case TgVoipState::Failed: + callState = OngoingCallStateFailed; + break; + case TgVoipState::Reconnecting: + callState = OngoingCallStateReconnecting; + break; + default: + break; + } + + if (callState != _state) { + _state = callState; + + if (_stateChanged) { + _stateChanged(callState); + } + } +} + +- (void)signalBarsChanged:(int32_t)signalBars { + if (signalBars != _signalBars) { + _signalBars = signalBars; + + if (_signalBarsChanged) { + _signalBarsChanged(signalBars); + } + } +} + +- (void)setIsMuted:(bool)isMuted { + if (_tgVoip) { + _tgVoip->setMuteMicrophone(isMuted); + } +} + +- (void)setNetworkType:(OngoingCallNetworkType)networkType { + if (_networkType != networkType) { + _networkType = networkType; + if (_tgVoip) { + _tgVoip->setNetworkType(callControllerNetworkTypeForType(networkType)); + } + } +} + +@end diff --git a/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.h b/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.h new file mode 100644 index 0000000000..8275744415 --- /dev/null +++ b/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.h @@ -0,0 +1,105 @@ +#ifndef OngoingCallContext_h +#define OngoingCallContext_h + +#import + +@interface OngoingCallConnectionDescriptionWebrtc : NSObject + +@property (nonatomic, readonly) int64_t connectionId; +@property (nonatomic, strong, readonly) NSString * _Nonnull ip; +@property (nonatomic, strong, readonly) NSString * _Nonnull ipv6; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSData * _Nonnull peerTag; + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag; + +@end + +typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) { + OngoingCallStateInitializingWebrtc, + OngoingCallStateConnectedWebrtc, + OngoingCallStateFailedWebrtc, + OngoingCallStateReconnectingWebrtc +}; + +typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) { + OngoingCallVideoStateInactiveWebrtc, + OngoingCallVideoStateActiveOutgoingWebrtc, + OngoingCallVideoStateActiveWebrtc +}; + +typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) { + OngoingCallRemoteVideoStateInactiveWebrtc, + OngoingCallRemoteVideoStateActiveWebrtc +}; + +typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) { + OngoingCallNetworkTypeWifiWebrtc, +}; + +typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { + OngoingCallDataSavingNeverWebrtc, + OngoingCallDataSavingCellularWebrtc, + OngoingCallDataSavingAlwaysWebrtc +}; + +@protocol OngoingCallThreadLocalContextQueueWebrtc + +- (void)dispatch:(void (^ _Nonnull)())f; +- (bool)isCurrent; + +@end + +@interface VoipProxyServerWebrtc : NSObject + +@property (nonatomic, strong, readonly) NSString * _Nonnull host; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSString * _Nullable username; +@property (nonatomic, strong, readonly) NSString * _Nullable password; + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password; + +@end + +@interface VoipRtcServerWebrtc : NSObject + +@property (nonatomic, strong, readonly) NSString * _Nonnull host; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong, readonly) NSString * _Nullable username; +@property (nonatomic, strong, readonly) NSString * _Nullable password; +@property (nonatomic, readonly) bool isTurn; + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password isTurn:(bool)isTurn; + +@end + +@interface OngoingCallThreadLocalContextWebrtc : NSObject + ++ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; ++ (void)applyServerConfig:(NSString * _Nullable)data; ++ (int32_t)maxLayer; ++ (NSString * _Nonnull)version; + +@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc); +@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData; +- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; + +- (bool)needRate; + +- (NSString * _Nullable)debugInfo; +- (NSString * _Nullable)version; +- (NSData * _Nonnull)getDerivedState; + +- (void)setIsMuted:(bool)isMuted; +- (void)setVideoEnabled:(bool)videoEnabled; +- (void)switchVideoCamera; +- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType; +- (void)makeIncomingVideoView:(void (^_Nonnull)(NSView * _Nullable))completion; +- (void)makeOutgoingVideoView:(void (^_Nonnull)(NSView * _Nullable))completion; +- (void)addSignalingData:(NSData * _Nonnull)data; + +@end + +#endif diff --git a/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.mm b/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.mm new file mode 100644 index 0000000000..2456e76f22 --- /dev/null +++ b/Telegram-Mac/OngoingCallThreadLocalContextWebrtc.mm @@ -0,0 +1,472 @@ +#import "OngoingCallThreadLocalContextWebrtc.h" + +#import "TgVoip.h" +#import + + +@implementation OngoingCallConnectionDescriptionWebrtc + +- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag { + self = [super init]; + if (self != nil) { + _connectionId = connectionId; + _ip = ip; + _ipv6 = ipv6; + _port = port; + _peerTag = peerTag; + } + return self; +} + +@end + +@interface OngoingCallThreadLocalContextWebrtc () { + id _queue; + int32_t _contextId; + + OngoingCallNetworkTypeWebrtc _networkType; + NSTimeInterval _callReceiveTimeout; + NSTimeInterval _callRingTimeout; + NSTimeInterval _callConnectTimeout; + NSTimeInterval _callPacketTimeout; + + TgVoip *_tgVoip; + + OngoingCallStateWebrtc _state; + OngoingCallVideoStateWebrtc _videoState; + OngoingCallRemoteVideoStateWebrtc _remoteVideoState; + + int32_t _signalBars; + NSData *_lastDerivedState; + + void (^_sendSignalingData)(NSData *); +} + +- (void)controllerStateChanged:(TgVoipState)state; +- (void)signalBarsChanged:(int32_t)signalBars; + +@end + +@implementation VoipProxyServerWebrtc + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password { + self = [super init]; + if (self != nil) { + _host = host; + _port = port; + _username = username; + _password = password; + } + return self; +} + +@end + +@implementation VoipRtcServerWebrtc + +- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password isTurn:(bool)isTurn { + self = [super init]; + if (self != nil) { + _host = host; + _port = port; + _username = username; + _password = password; + _isTurn = isTurn; + } + return self; +} + +@end + +static TgVoipNetworkType callControllerNetworkTypeForType(OngoingCallNetworkTypeWebrtc type) { + switch (type) { + case OngoingCallNetworkTypeWifiWebrtc: + return TgVoipNetworkType::WiFi; + default: + return TgVoipNetworkType::ThirdGeneration; + } +} + +static TgVoipDataSaving callControllerDataSavingForType(OngoingCallDataSavingWebrtc type) { + switch (type) { + case OngoingCallDataSavingNeverWebrtc: + return TgVoipDataSaving::Never; + case OngoingCallDataSavingCellularWebrtc: + return TgVoipDataSaving::Mobile; + case OngoingCallDataSavingAlwaysWebrtc: + return TgVoipDataSaving::Always; + default: + return TgVoipDataSaving::Never; + } +} + +@implementation OngoingCallThreadLocalContextWebrtc + +static void (*InternalVoipLoggingFunction)(NSString *) = NULL; + ++ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction { + InternalVoipLoggingFunction = loggingFunction; + TgVoip::setLoggingFunction([](std::string const &string) { + if (InternalVoipLoggingFunction) { + InternalVoipLoggingFunction([[NSString alloc] initWithUTF8String:string.c_str()]); + } + }); +} + ++ (void)applyServerConfig:(NSString *)string { + if (string.length != 0) { + TgVoip::setGlobalServerConfig(std::string(string.UTF8String)); + } +} + ++ (int32_t)maxLayer { + return 92; +} + ++ (NSString *)version { + return @"2.7.7"; +} + +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData; { + self = [super init]; + if (self != nil) { + _queue = queue; + assert([queue isCurrent]); + + _callReceiveTimeout = 20.0; + _callRingTimeout = 90.0; + _callConnectTimeout = 30.0; + _callPacketTimeout = 10.0; + _networkType = networkType; + _sendSignalingData = [sendSignalingData copy]; + if (isVideo) { + _videoState = OngoingCallVideoStateActiveOutgoingWebrtc; + _remoteVideoState = OngoingCallRemoteVideoStateActiveWebrtc; + } else { + _videoState = OngoingCallVideoStateInactiveWebrtc; + _remoteVideoState = OngoingCallRemoteVideoStateInactiveWebrtc; + } + + std::vector derivedStateValue; + derivedStateValue.resize(derivedState.length); + [derivedState getBytes:derivedStateValue.data() length:derivedState.length]; + + std::unique_ptr proxyValue = nullptr; + if (proxy != nil) { + TgVoipProxy *proxyObject = new TgVoipProxy(); + proxyObject->host = proxy.host.UTF8String; + proxyObject->port = (uint16_t)proxy.port; + proxyObject->login = proxy.username.UTF8String ?: ""; + proxyObject->password = proxy.password.UTF8String ?: ""; + proxyValue = std::unique_ptr(proxyObject); + } + +// std::vector parsedRtcServers; +// for (VoipRtcServerWebrtc *server in rtcServers) { +// parsedRtcServers.push_back((TgVoipRtcServer){ +// .host = server.host.UTF8String, +// .port = (uint16_t)server.port, +// .login = server.username.UTF8String, +// .password = server.password.UTF8String, +// .isTurn = server.isTurn +// }); +// } +// + /*TgVoipCrypto crypto; + crypto.sha1 = &TGCallSha1; + crypto.sha256 = &TGCallSha256; + crypto.rand_bytes = &TGCallRandomBytes; + crypto.aes_ige_encrypt = &TGCallAesIgeEncrypt; + crypto.aes_ige_decrypt = &TGCallAesIgeDecrypt; + crypto.aes_ctr_encrypt = &TGCallAesCtrEncrypt;*/ + + std::vector endpoints; + NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; + for (OngoingCallConnectionDescriptionWebrtc *connection in connections) { + unsigned char peerTag[16]; + [connection.peerTag getBytes:peerTag length:16]; + + TgVoipEndpoint endpoint; + endpoint.endpointId = connection.connectionId; + endpoint.host = { + .ipv4 = std::string(connection.ip.UTF8String), + .ipv6 = std::string(connection.ipv6.UTF8String) + }; + endpoint.port = (uint16_t)connection.port; + endpoint.type = TgVoipEndpointType::UdpRelay; + memcpy(endpoint.peerTag, peerTag, 16); + endpoints.push_back(endpoint); + } + + TgVoipConfig config = { + .initializationTimeout = _callConnectTimeout, + .receiveTimeout = _callPacketTimeout, + .dataSaving = callControllerDataSavingForType(dataSaving), + .enableP2P = (bool)allowP2P, + .enableAEC = false, + .enableNS = true, + .enableAGC = true, + .enableCallUpgrade = false, + .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String), + .maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer] + }; + + std::vector encryptionKeyValue; + encryptionKeyValue.resize(key.length); + memcpy(encryptionKeyValue.data(), key.bytes, key.length); + + TgVoipEncryptionKey encryptionKey = { + .value = encryptionKeyValue, + .isOutgoing = isOutgoing, + }; + + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; +// _tgVoip = TgVoip::makeInstance( +// config, +// { derivedStateValue }, +// endpoints, +// proxyValue, +// parsedRtcServers, +// callControllerNetworkTypeForType(networkType), +// encryptionKey, +// isVideo, +// [weakSelf, queue](TgVoipState state) { +// [queue dispatch:^{ +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// [strongSelf controllerStateChanged:state]; +// } +// }]; +// }, +// [weakSelf, queue](bool isActive) { +// [queue dispatch:^{ +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// OngoingCallVideoStateWebrtc videoState; +// if (isActive) { +// videoState = OngoingCallVideoStateActive; +// } else { +// videoState = OngoingCallVideoStateInactive; +// } +// if (strongSelf->_videoState != videoState) { +// strongSelf->_videoState = videoState; +// if (strongSelf->_stateChanged) { +// strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState); +// } +// } +// } +// }]; +// }, +// [weakSelf, queue](bool isActive) { +// [queue dispatch:^{ +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// OngoingCallRemoteVideoStateWebrtc remoteVideoState; +// if (isActive) { +// remoteVideoState = OngoingCallRemoteVideoStateActive; +// } else { +// remoteVideoState = OngoingCallRemoteVideoStateInactive; +// } +// if (strongSelf->_remoteVideoState != remoteVideoState) { +// strongSelf->_remoteVideoState = remoteVideoState; +// if (strongSelf->_stateChanged) { +// strongSelf->_stateChanged(strongSelf->_state, strongSelf->_videoState, strongSelf->_remoteVideoState); +// } +// } +// } +// }]; +// }, +// [weakSelf, queue](const std::vector &data) { +// NSData *mappedData = [[NSData alloc] initWithBytes:data.data() length:data.size()]; +// [queue dispatch:^{ +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// [strongSelf signalingDataEmitted:mappedData]; +// } +// }]; +// } +// ); +// + _state = OngoingCallStateInitializingWebrtc; + _signalBars = -1; + } + return self; +} + +- (void)dealloc { + assert([_queue isCurrent]); + if (_tgVoip != NULL) { + [self stop:nil]; + } +} + +- (bool)needRate { + return false; +} + +- (void)stop:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { + if (_tgVoip) { + TgVoipFinalState finalState = _tgVoip->stop(); + + NSString *debugLog = [NSString stringWithUTF8String:finalState.debugLog.c_str()]; + _lastDerivedState = [[NSData alloc] initWithBytes:finalState.persistentState.value.data() length:finalState.persistentState.value.size()]; + + delete _tgVoip; + _tgVoip = NULL; + + if (completion) { + completion(debugLog, finalState.trafficStats.bytesSentWifi, finalState.trafficStats.bytesReceivedWifi, finalState.trafficStats.bytesSentMobile, finalState.trafficStats.bytesReceivedMobile); + } + } +} + +- (NSString *)debugInfo { + if (_tgVoip != nil) { + NSString *version = [self version]; + return [NSString stringWithFormat:@"WebRTC, Version: %@", version]; + //auto rawDebugString = _tgVoip->getDebugInfo(); + //return [NSString stringWithUTF8String:rawDebugString.c_str()]; + } else { + return nil; + } +} + +- (NSString *)version { + if (_tgVoip != nil) { + return @"2.7.7";//[NSString stringWithUTF8String:_tgVoip->getVersion().c_str()]; + } else { + return nil; + } +} + +- (NSData * _Nonnull)getDerivedState { + if (_tgVoip) { + auto persistentState = _tgVoip->getPersistentState(); + return [[NSData alloc] initWithBytes:persistentState.value.data() length:persistentState.value.size()]; + } else if (_lastDerivedState != nil) { + return _lastDerivedState; + } else { + return [NSData data]; + } +} + +- (void)controllerStateChanged:(TgVoipState)state { + OngoingCallStateWebrtc callState = OngoingCallStateInitializingWebrtc; +// switch (state) { +// case TgVoipState::Estabilished: +// callState = OngoingCallStateConnected; +// break; +// case TgVoipState::Failed: +// callState = OngoingCallStateFailed; +// break; +// case TgVoipState::Reconnecting: +// callState = OngoingCallStateReconnecting; +// break; +// default: +// break; +// } + + if (callState != _state) { + _state = callState; + + if (_stateChanged) { + if (_videoState == OngoingCallVideoStateActiveOutgoingWebrtc) { + if (_state == OngoingCallStateConnectedWebrtc) { + _videoState = OngoingCallVideoStateActiveWebrtc; + } + } + _stateChanged(_state, _videoState, _remoteVideoState); + } + } +} + +- (void)signalBarsChanged:(int32_t)signalBars { + if (signalBars != _signalBars) { + _signalBars = signalBars; + + if (_signalBarsChanged) { + _signalBarsChanged(signalBars); + } + } +} + +- (void)signalingDataEmitted:(NSData *)data { + if (_sendSignalingData) { + _sendSignalingData(data); + } +} + +- (void)addSignalingData:(NSData *)data { + if (_tgVoip) { + std::vector mappedData; + mappedData.resize(data.length); + [data getBytes:mappedData.data() length:data.length]; + // _tgVoip->receiveSignalingData(mappedData); + } +} + +- (void)setIsMuted:(bool)isMuted { + if (_tgVoip) { + _tgVoip->setMuteMicrophone(isMuted); + } +} + +- (void)setVideoEnabled:(bool)videoEnabled { + if (_tgVoip) { + // _tgVoip->setSendVideo(videoEnabled); + } +} + +- (void)switchVideoCamera { + if (_tgVoip) { + // _tgVoip->switchVideoCamera(); + } +} + +- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType { + if (_networkType != networkType) { + _networkType = networkType; + if (_tgVoip) { + _tgVoip->setNetworkType(callControllerNetworkTypeForType(networkType)); + } + } +} + +- (void)makeIncomingVideoView:(void (^_Nonnull)(NSView * _Nullable))completion { + if (_tgVoip) { + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ +// VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; +// remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; +// +// std::shared_ptr> sink = [remoteRenderer getSink]; +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// strongSelf->_tgVoip->setIncomingVideoOutput(sink); +// } +// +// completion(remoteRenderer); + }); + } +} + +- (void)makeOutgoingVideoView:(void (^_Nonnull)(NSView * _Nullable))completion { + if (_tgVoip) { + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ +// VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; +// remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; +// +// std::shared_ptr> sink = [remoteRenderer getSink]; +// __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; +// if (strongSelf) { +// strongSelf->_tgVoip->setOutgoingVideoOutput(sink); +// } +// +// completion(remoteRenderer); + }); + } +} + +@end + diff --git a/Telegram-Mac/OpmizeDatabaseView.swift b/Telegram-Mac/OpmizeDatabaseView.swift new file mode 100644 index 0000000000..963eafc146 --- /dev/null +++ b/Telegram-Mac/OpmizeDatabaseView.swift @@ -0,0 +1,52 @@ +// +// OpmizeDatabaseView.swift +// Telegram +// +// Created by Mikhail Filimonov on 22/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class OpmizeDatabaseView: Control { + private let textView = TextView() + + private let progressView = RadialProgressView.init(theme: RadialProgressTheme.init(backgroundColor: .clear, foregroundColor: theme.colors.text), twist: true, size: NSMakeSize(40, 40)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + autoresizingMask = [.width, .height] + addSubview(textView) + addSubview(progressView) + self.backgroundColor = theme.colors.background + let attributedString = NSMutableAttributedString() + _ = attributedString.append(string: L10n.telegramUpgradeDatabaseTitle, color: theme.colors.text, font: .medium(20)) + _ = attributedString.append(string: "\n\n", color: theme.colors.text, font: .medium(13)) + _ = attributedString.append(string: L10n.telegramUpgradeDatabaseText, color: theme.colors.text, font: .normal(14)) + + let layout = TextViewLayout(attributedString, alignment: .center, alwaysStaticItems: true) + layout.measure(width: 300) + + textView.update(layout) + + textView.userInteractionEnabled = false + textView.isSelectable = false + + progressView.state = .ImpossibleFetching(progress: 0.2, force: true) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func layout() { + super.layout() + progressView.centerX(y: frame.midY - progressView.frame.height - 10) + textView.centerX(y: frame.midY + 10) + + } + + func setProgress(_ progress: Float) { + progressView.state = .ImpossibleFetching(progress: max(0.2, progress), force: true) + } + +} diff --git a/Telegram-Mac/OpusAudioPlayer.swift b/Telegram-Mac/OpusAudioPlayer.swift index d383202a69..8ef889fad3 100644 --- a/Telegram-Mac/OpusAudioPlayer.swift +++ b/Telegram-Mac/OpusAudioPlayer.swift @@ -8,7 +8,7 @@ import Cocoa import AudioUnit -import SwiftSignalKitMac +import SwiftSignalKit class OpusAudioPlayer: AudioPlayer, OpusBridgeDelegate { let bridge:OpusObjcBridge diff --git a/Telegram-Mac/PCallSession.swift b/Telegram-Mac/PCallSession.swift index 09c7579f70..ac3bd90315 100644 --- a/Telegram-Mac/PCallSession.swift +++ b/Telegram-Mac/PCallSession.swift @@ -7,9 +7,10 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox import TGUIKit enum CallTone { @@ -22,6 +23,27 @@ enum CallTone { } +public struct CallAuxiliaryServer { + public enum Connection { + case stun + case turn(username: String, password: String) + } + + public let host: String + public let port: Int + public let connection: Connection + + public init( + host: String, + port: Int, + connection: Connection + ) { + self.host = host + self.port = port + self.connection = connection + } +} + enum VoIPState : Int { @@ -31,6 +53,32 @@ enum VoIPState : Int { case failed = 4 } + +private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ { + private let queue: Queue + + init(queue: Queue) { + self.queue = queue + + super.init() + } + + func dispatch(_ f: @escaping () -> Void) { + self.queue.async { + f() + } + } + + func dispatch(after seconds: Double, block f: @escaping () -> Void) { + self.queue.after(seconds, f) + } + + func isCurrent() -> Bool { + return self.queue.isCurrent() + } +} + + let callQueue = Queue(name: "VoIPQueue") private var callSession:PCallSession? = nil @@ -42,13 +90,70 @@ func pullCurrentSession(_ f:@escaping (PCallSession?)->Void) { } +private func getAuxiliaryServers(appConfiguration: AppConfiguration) -> [CallAuxiliaryServer] { + guard let data = appConfiguration.data else { + return [] + } + guard let servers = data["rtc_servers"] as? [[String: Any]] else { + return [] + } + var result: [CallAuxiliaryServer] = [] + for server in servers { + guard let host = server["host"] as? String else { + continue + } + guard let portString = server["port"] as? String else { + continue + } + guard let username = server["username"] as? String else { + continue + } + guard let password = server["password"] as? String else { + continue + } + guard let port = Int(portString) else { + continue + } + result.append(CallAuxiliaryServer( + host: host, + port: port, + connection: .stun + )) + result.append(CallAuxiliaryServer( + host: host, + port: port, + connection: .turn( + username: username, + password: password + ) + )) + } + return result +} + + + class PCallSession { let peerId:PeerId - let account:Account + let account: Account + let sharedContext: SharedAccountContext let id:CallSessionInternalId - private var contextRef: Unmanaged? + private(set) var peer: Peer? + private let peerDisposable = MetaDisposable() + private var ongoingContext: OngoingCallContext? + private var ongoingContextStateDisposable: Disposable? + + + private let serializedData: String? + private let dataSaving: VoiceCallDataSaving + private let derivedState: VoipDerivedState + private let proxyServer: ProxyServerSettings? + private let auxiliaryServers: [OngoingCallContext.AuxiliaryServer] + private let currentNetworkType: NetworkType + private let updatedNetworkType: Signal + private let stateDisposable = MetaDisposable() private let timeoutDisposable = MetaDisposable() @@ -65,7 +170,13 @@ class PCallSession { private var completed: Bool = false let durationPromise:Promise = Promise() private var callSessionValue:CallSession? = nil - init(account:Account, peerId:PeerId, id: CallSessionInternalId) { + private let proxyDisposable = MetaDisposable() + private let requestMicroAccessDisposable = MetaDisposable() + + + private let callSessionManager: CallSessionManager + + init(account: Account, sharedContext: SharedAccountContext, peerId:PeerId, id: CallSessionInternalId) { Queue.mainQueue().async { _ = globalAudio?.pause() @@ -74,46 +185,128 @@ class PCallSession { assert(callQueue.isCurrent()) self.account = account + self.sharedContext = sharedContext self.peerId = peerId self.id = id - - let signal = account.callSessionManager.callState(internalId: id) |> deliverOnMainQueue |> beforeNext { [weak self] session in - self?.proccessState(session) - } + self.callSessionManager = account.callSessionManager + self.updatedNetworkType = account.networkType - state.set(signal |> map {$0.state}) + let semaphore = DispatchSemaphore(value: 0) + var data: (PreferencesView, Peer?, ProxyServerSettings?, NetworkType)! + let _ = combineLatest( + account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) + |> take(1), + account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + }, + proxySettings(accountManager: sharedContext.accountManager) |> take(1), + account.networkType |> take(1) + ).start(next: { preferences, peer, proxy, networkType in + data = (preferences, peer, proxy.effectiveActiveServer, networkType) + semaphore.signal() + }) + semaphore.wait() + - callQueue.async { - callSession = self - let bridge = CallBridge() - - if let inputDeviceId = UserDefaults.standard.object(forKey: "call_inputDevice") as? String { - bridge.setCurrentInputDeviceId(inputDeviceId) - } - if let outputDeviceId = UserDefaults.standard.object(forKey: "call_outputDevice") as? String { - bridge.setCurrentOutputDeviceId(outputDeviceId) - } + - - self.contextRef = Unmanaged.passRetained(bridge) - bridge.stateChangeHandler = { value in - callQueue.async { - if let state = VoIPState(rawValue: Int(value)) { - self.voipStateChanged(state) - } - } + let configuration = data.0.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? VoipConfiguration.defaultValue + let appConfiguration = data.0.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue + let derivedState = data.0.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? VoipDerivedState.default + + self.serializedData = configuration.serializedData + self.dataSaving = .never + self.derivedState = derivedState + self.proxyServer = data.2 + self.peer = data.1 + self.currentNetworkType = data.3 + + + self.auxiliaryServers = getAuxiliaryServers(appConfiguration: appConfiguration).map { server -> OngoingCallContext.AuxiliaryServer in + let mappedConnection: OngoingCallContext.AuxiliaryServer.Connection + switch server.connection { + case .stun: + mappedConnection = .stun + case let .turn(username, password): + mappedConnection = .turn(username: username, password: password) } + return OngoingCallContext.AuxiliaryServer( + host: server.host, + port: server.port, + connection: mappedConnection + ) } + let signal = account.callSessionManager.callState(internalId: id) |> deliverOn(callQueue) |> beforeNext { [weak self] session in + self?.proccessState(session, configuration) + } + + state.set(signal |> map { $0.state }) + +// +// +// peerDisposable.set((account.postbox.multiplePeersView([peerId]) |> deliverOnMainQueue).start(next: { [weak self] view in +// self?.peer = view.peers[peerId] +// })) +// +// let signal = account.callSessionManager.callState(internalId: id) |> mapToSignal { session -> Signal<(CallSession, VoipConfiguration), NoError> in +// return account.postbox.transaction { transaction in +// return (session, currentVoipConfiguration(transaction: transaction)) +// } +// } |> deliverOnMainQueue |> beforeNext { [weak self] session, configuration in +// self?.proccessState(session, configuration) +// } +// +// state.set(signal |> map { $0.0.state}) +// +// proxyDisposable.set((combineLatest(queue: callQueue, proxySettings(accountManager: sharedContext.accountManager), voiceCallSettings(sharedContext.accountManager))).start(next: { [weak self] proxySetttings, callSettings in +// guard let `self` = self else {return} +// callSession = self +// let bridge:OngoingCallThreadLocalContext +// if let server = proxySetttings.effectiveActiveServer, proxySetttings.useForCalls { +// switch server.connection { +// case let .socks5(username, password): +// bridge = OngoingCallThreadLocalContext(proxy: VoipProxyServer(host: server.host, port: server.port, user: username != nil ? username : "", pass: password != nil ? password : "")) +// default: +// bridge = OngoingCallThreadLocalContext(proxy: nil) +// } +// } else { +// bridge = OngoingCallThreadLocalContext(proxy: nil) +// } +// +// if let inputDeviceId = callSettings.inputDeviceId { +// bridge.setCurrentInputDeviceId(inputDeviceId) +// } +// if let outputDeviceId = callSettings.outputDeviceId { +// bridge.setCurrentOutputDeviceId(outputDeviceId) +// } +// bridge.setMutedOtherSounds(callSettings.muteSounds) +// +// self.contextRef = Unmanaged.passRetained(bridge) +// bridge.stateChangeHandler = { value in +// callQueue.async { +// if let state = VoIPState(rawValue: Int(value)) { +// self.voipStateChanged(state) +// } +// } +// } +// })) +// +// callQueue.async { +// +// } +// +// +// } - private func voipStateChanged(_ state:VoIPState) { - switch state { - case .established: + private func voipStateChanged(_ state:OngoingCallContextState) { + switch state.state { + case .connected: if (startTime < Double.ulpOfOne) { stopAudio() startTime = CFAbsoluteTimeGetCurrent(); - durationPromise.set((.single(duration) |> deliverOnMainQueue) |> then (Signal<()->TimeInterval, Void>.single({[weak self] in return self?.duration ?? 0}) |> map {$0()} |> delay(1.01, queue: Queue.mainQueue()) |> restart)) + durationPromise.set((.single(duration) |> deliverOnMainQueue) |> then (Signal<()->TimeInterval, NoError>.single({[weak self] in return self?.duration ?? 0}) |> map {$0()} |> delay(1.01, queue: Queue.mainQueue()) |> restart)) } case .failed: playTone(.callToneFailed) @@ -124,84 +317,14 @@ class PCallSession { } private func startTimeout(_ duration:TimeInterval, discardReason: CallSessionTerminationReason) { - timeoutDisposable.set((Signal.complete() |> delay(duration, queue: Queue.mainQueue())).start(completed: { [weak self] in + timeoutDisposable.set((Signal.complete() |> delay(duration, queue: Queue.mainQueue())).start(completed: { [weak self] in self?.discardCurrentCallWithReason(discardReason) })) } - func inputDevices() -> Signal<[AudioDevice], Void> { - return Signal { [weak self] subscriber in - var cancel = false - self?.withContext { context in - if !cancel { - subscriber.putNext(context.inputDevices()) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancel = true - } - } - } - - func currentInputDeviceId()-> Signal { - return Signal { [weak self] subscriber in - var cancel = false - self?.withContext { context in - if !cancel { - subscriber.putNext(context.currentInputDeviceId()) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancel = true - } - } - } - func currentOutputDeviceId()-> Signal { - return Signal { [weak self] subscriber in - var cancel = false - self?.withContext { context in - if !cancel { - subscriber.putNext(context.currentOutputDeviceId()) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancel = true - } - } - } - func setCurrentInputDevice(_ device:AudioDevice) { - UserDefaults.standard.set(device.deviceId, forKey: "call_inputDevice") - UserDefaults.standard.synchronize() - withContext { context in - context.setCurrentInputDeviceId(device.deviceId); - } - } - - func setCurrentOutputDevice(_ device:AudioDevice) { - UserDefaults.standard.set(device.deviceId, forKey: "call_outputDevice") - UserDefaults.standard.synchronize() - withContext { context in - context.setCurrentOutputDeviceId(device.deviceId); - } - } - - func outputDevices() -> Signal<[AudioDevice], Void> { - return Signal { [weak self] subscriber in - var cancel = false - self?.withContext { context in - if !cancel { - subscriber.putNext(context.outputDevices()) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancel = true - } - } - } + + + private func invalidateTimeout() { timeoutDisposable.set(nil) @@ -234,61 +357,70 @@ class PCallSession { return 0.0; } - func stopTransmission() { + func stopTransmission(_ id: CallId?) { durationPromise.set(.complete()) - callQueue.async { - callSession = nil - self.contextRef?.release() - self.contextRef = nil - } + ongoingContext?.stop(callId: id, sendDebugLogs: false, debugLogValue: Promise()) } func drop(_ reason:DropCallReason) { - account.callSessionManager.drop(internalId: id, reason: reason) + account.callSessionManager.drop(internalId: id, reason: reason, debugLog: .single(nil)) } - - func acceptCallSession() { + private func acceptAfterAccess() { callAcceptedTime = CFAbsoluteTimeGetCurrent() account.callSessionManager.accept(internalId: id) } + func acceptCallSession() { + requestMicroAccessDisposable.set((requestAudioPermission() |> deliverOnMainQueue).start(next: { [weak self] access in + if access { + self?.acceptAfterAccess() + } else { + confirm(for: mainWindow, information: L10n.requestAccesErrorHaveNotAccessCall, okTitle: L10n.modalOK, cancelTitle: "", thridTitle: L10n.requestAccesErrorConirmSettings, successHandler: { result in + switch result { + case .thrid: + openSystemSettings(.microphone) + default: + break + } + }) + } + })) + + } + func mute() { isMute = true - withContext { context in - context.mute() - } + ongoingContext?.setIsMuted(true) } func unmute() { isMute = false - withContext { context in - context.unmute() - } + ongoingContext?.setIsMuted(false) } func toggleMute() { self.isMute = !self.isMute - withContext { context in - if context.isMuted() { - context.unmute() - } else { - context.mute() - } - } + ongoingContext?.setIsMuted(self.isMute) } - private func proccessState(_ session: CallSession) { + private func proccessState(_ session: CallSession, _ configuration: VoipConfiguration) { self.callSessionValue = session switch session.state { - case .active(let key, _, let connection): + case let .active(id, key, _, connections, maxLayer, version, allowP2P): playTone(.callToneConnecting) - let cdata = TGCallConnection(key: key, keyHash: key, defaultConnection: TGCallConnectionDescription(identifier: connection.primary.id, ipv4: connection.primary.ip, ipv6: connection.primary.ipv6, port: connection.primary.port, peerTag: connection.primary.peerTag), alternativeConnections: connection.alternatives.map {TGCallConnectionDescription(identifier: $0.id, ipv4: $0.ip, ipv6: $0.ipv6, port: $0.port, peerTag: $0.peerTag)}) + let logName = "\(id.id)_\(id.accessHash)" + + let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.id, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: session.isOutgoing, isVideo: false, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowP2P, logName: logName) + self.ongoingContext = ongoingContext + + stateDisposable.set((ongoingContext.state |> deliverOn(callQueue)).start(next: { [weak self] state in + if let state = state { + self?.voipStateChanged(state) + } + })) - withContext { context in - context.startTransmissionIfNeeded(session.isOutgoing, connection: cdata) - } invalidateTimeout() case .ringing: playRingtone() @@ -302,34 +434,25 @@ class PCallSession { invalidateTimeout() stopAudio() break - case .terminated(let reason, let report): - stopTransmission() + case let .terminated(callId, reason, report): + stopTransmission(callId) invalidateTimeout() switch reason { case .error: playTone(.callToneFailed) default: - stopAudio() + playTone(.callToneEnded) } -// if let report = report { -// let account = self.account -// Queue.mainQueue().async { -// showModal(with: CallRatingModalViewController(account, report: report), for: mainWindow) -// } -// } - default: break } } deinit { + peerDisposable.dispose() stateDisposable.dispose() drop(.disconnect) - let contextRef = self.contextRef - callQueue.async { - contextRef?.release() - } + proxyDisposable.dispose() } private func playRingtone() { @@ -341,14 +464,6 @@ class PCallSession { } - private func withContext(_ f: @escaping (CallBridge) -> Void) { - callQueue.async { - if let contextRef = self.contextRef { - let context = contextRef.takeUnretainedValue() - f(context) - } - } - } func hangUpCurrentCall() { hangUpCurrentCall(false) @@ -459,41 +574,59 @@ enum PCallResult { case samePeer(PCallSession) } -func phoneCall(_ account:Account, peerId:PeerId, ignoreSame:Bool = false) -> Signal { - return Signal { subscriber in - - assert(callQueue.isCurrent()) - - if let session = callSession, session.peerId == peerId, !ignoreSame { - subscriber.putNext(.samePeer(session)) - subscriber.putCompletion() +func phoneCall(account: Account, sharedContext: SharedAccountContext, peerId:PeerId, ignoreSame:Bool = false) -> Signal { + return requestAudioPermission() |> deliverOnMainQueue |> mapToSignal { hasAccess -> Signal in + if hasAccess { + return Signal { subscriber in + + assert(callQueue.isCurrent()) + + if let session = callSession, session.peerId == peerId, !ignoreSame { + subscriber.putNext(.samePeer(session)) + subscriber.putCompletion() + } else { + let confirmation:Signal + if let sessionPeerId = callSession?.peerId { + confirmation = account.postbox.loadedPeerWithId(peerId) |> mapToSignal { peer -> Signal<(new:Peer, previous:Peer), NoError> in + return account.postbox.loadedPeerWithId(sessionPeerId) |> map { (new: peer, previous: $0) } + } |> mapToSignal { value in + return confirmSignal(for: mainWindow, header: L10n.callConfirmDiscardCurrentHeader, information: L10n.callConfirmDiscardCurrentDescription(value.previous.compactDisplayTitle, value.new.displayTitle)) + } + + } else { + confirmation = .single(true) + } + + return (confirmation |> filter {$0} |> map { _ in + callSession?.hangUpCurrentCall() + } |> mapToSignal { _ in + return account.callSessionManager.request(peerId: peerId, isVideo: false) + } |> deliverOn(callQueue) ).start(next: { id in + subscriber.putNext(.success(PCallSession(account: account, sharedContext: sharedContext, peerId: peerId, id: id))) + subscriber.putCompletion() + }) + } + + return EmptyDisposable + + } |> runOn(callQueue) } else { - let confirmation:Signal - if let session = callSession { - confirmation = account.postbox.loadedPeerWithId(peerId) |> mapToSignal { peer -> Signal<(new:Peer, previous:Peer), Void> in - return account.postbox.loadedPeerWithId(session.peerId) |> map {(new:peer, previous:$0)} - } |> mapToSignal { value in - return confirmSignal(for: mainWindow, header: tr(.callConfirmDiscardCurrentHeader), information: tr(.callConfirmDiscardCurrentDescription(value.previous.compactDisplayTitle, value.new.displayTitle))) + confirm(for: mainWindow, information: L10n.requestAccesErrorHaveNotAccessCall, okTitle: L10n.modalOK, cancelTitle: "", thridTitle: L10n.requestAccesErrorConirmSettings, successHandler: { result in + switch result { + case .thrid: + openSystemSettings(.microphone) + default: + break } - - } else { - confirmation = .single(true) - } - - return (confirmation |> filter {$0} |> map { _ in - callSession?.hangUpCurrentCall() - } |> mapToSignal { _ in return account.callSessionManager.request(peerId: peerId) } |> deliverOn(callQueue) ).start(next: { id in - subscriber.putNext(.success(PCallSession(account: account, peerId: peerId, id: id))) - subscriber.putCompletion() + //[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]]; }) + return .complete() } - - return EmptyDisposable - - } |> runOn(callQueue) + } + } -func _callSession() -> Signal { +func _callSession() -> Signal { return Signal { subscriber in var cancel: Bool = false pullCurrentSession({ session in diff --git a/Telegram-Mac/PIPVideoWindow.swift b/Telegram-Mac/PIPVideoWindow.swift index 643b058ba4..7d73ae2a68 100644 --- a/Telegram-Mac/PIPVideoWindow.swift +++ b/Telegram-Mac/PIPVideoWindow.swift @@ -9,28 +9,32 @@ import Cocoa import TGUIKit import AVKit -import SwiftSignalKitMac +import SwiftSignalKit + +private let pipFrameKey: String = "kPipFrameKey3" fileprivate class PIPVideoWindow: NSPanel { - fileprivate let playerView:AVPlayerView + fileprivate let playerView: VideoPlayerView private let rect:NSRect private let close:ImageButton = ImageButton() - private let openGallery:ImageButton = ImageButton() + private let gallery:ImageButton = ImageButton() fileprivate var forcePaused: Bool = false - fileprivate let item: MGalleryVideoItem + fileprivate let item: MGalleryItem fileprivate weak var _delegate: InteractionContentViewProtocol? - fileprivate let _contentInteractions:ChatMediaGalleryParameters? + fileprivate let _contentInteractions:ChatMediaLayoutParameters? fileprivate let _type: GalleryAppearType - init(_ player:AVPlayerView, item: MGalleryVideoItem, origin:NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType) { - + fileprivate let viewer: GalleryViewer + private var hideAnimated: Bool = true + init(_ player: VideoPlayerView, item: MGalleryItem, viewer: GalleryViewer, origin:NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType) { + self.viewer = viewer self._delegate = delegate self._contentInteractions = contentInteractions self._type = type - + player.isPip = true self.playerView = player self.rect = NSMakeRect(origin.x, origin.y, player.frame.width, player.frame.height) self.item = item - super.init(contentRect: rect, styleMask: [.closable, .borderless, .resizable, .nonactivatingPanel], backing: .buffered, defer: true) + super.init(contentRect: rect, styleMask: [.closable, .resizable, .nonactivatingPanel], backing: .buffered, defer: true) close.autohighlight = false @@ -47,60 +51,86 @@ fileprivate class PIPVideoWindow: NSPanel { close.layer?.opacity = 0.8 - openGallery.autohighlight = false - openGallery.set(image: #imageLiteral(resourceName: "Icon_PipOff").precomposed(NSColor.white.withAlphaComponent(0.9)), for: .Normal) + gallery.autohighlight = false + gallery.set(image: #imageLiteral(resourceName: "Icon_PipOff").precomposed(NSColor.white.withAlphaComponent(0.9)), for: .Normal) - openGallery.set(handler: { [weak self] _ in - self?._openGallery() + gallery.set(handler: { [weak self] _ in + self?.openGallery() }, for: .Click) - openGallery.setFrameSize(40,40) - - openGallery.layer?.cornerRadius = 20 - openGallery.style = ControlStyle(backgroundColor: .blackTransparent, highlightColor: .grayIcon) - openGallery.layer?.opacity = 0.8 - + gallery.setFrameSize(40,40) + gallery.layer?.cornerRadius = 20 + gallery.style = ControlStyle(backgroundColor: .blackTransparent, highlightColor: .grayIcon) + gallery.layer?.opacity = 0.8 + + self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]; self.contentView?.wantsLayer = true; self.contentView?.layer?.cornerRadius = 4; - self.contentView?.layer?.backgroundColor = NSColor.clear.cgColor; + self.contentView?.layer?.backgroundColor = NSColor.random.cgColor; self.backgroundColor = .clear; player.autoresizingMask = [.width, .height]; player.setFrameOrigin(0,0) player.controlsStyle = .minimal - player.removeFromSuperview() self.contentView?.addSubview(player) self.contentView?.addSubview(close) - self.contentView?.addSubview(openGallery) + self.contentView?.addSubview(gallery) - + + self.level = .screenSaver self.isMovableByWindowBackground = true + + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResized(_:)), name: NSWindow.didResizeNotification, object: self) + + } - + func hide() { + playerView.isPip = false + + if hideAnimated { + contentView?._change(opacity: 0, animated: true, duration: 0.1, timingFunction: .linear) + setFrame(NSMakeRect(frame.minX + (frame.width - 0) / 2, frame.minY + (frame.height - 0) / 2, 0, 0), display: true, animate: true) + } orderOut(nil) - playerView.player?.pause() window = nil } - func _openGallery() { + override func orderOut(_ sender: Any?) { + super.orderOut(sender) + window = nil + if playerView.controlsStyle != .floating { + playerView.player?.pause() + } + } + + func openGallery() { close.change(opacity: 0, removeOnCompletion: false) { [weak close] completed in close?.removeFromSuperview() } - openGallery.change(opacity: 0, removeOnCompletion: false) { [weak openGallery] completed in - openGallery?.removeFromSuperview() + gallery.change(opacity: 0, removeOnCompletion: false) { [weak gallery] completed in + gallery?.removeFromSuperview() } + playerView.controlsStyle = .floating setFrame(rect, display: true, animate: true) + hideAnimated = false hide() - showGalleryFromPip(item: item, delegate: _delegate, contentInteractions: _contentInteractions, type: _type) + showGalleryFromPip(item: item, gallery: self.viewer, delegate: _delegate, contentInteractions: _contentInteractions, type: _type) + } + + deinit { + if playerView.controlsStyle != .floating { + playerView.player?.pause() + } + NotificationCenter.default.removeObserver(self) } override func animationResizeTime(_ newFrame: NSRect) -> TimeInterval { @@ -108,76 +138,390 @@ fileprivate class PIPVideoWindow: NSPanel { } override func setFrame(_ frameRect: NSRect, display displayFlag: Bool, animate animateFlag: Bool) { - //let closePoint = NSMakePoint(10, frameRect.height - 50) - // let openPoint = NSMakePoint(closePoint.x + close.frame.width + 10, frameRect.height - 50) - - super.setFrame(frameRect, display: displayFlag, animate: animateFlag) } - - override func mouseMoved(with event: NSEvent) { - super.mouseMoved(with: event) - } + override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) close.change(opacity: 1, animated: true) - openGallery.change(opacity: 1, animated: true) + gallery.change(opacity: 1, animated: true) } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) close.change(opacity: 0, animated: true) - openGallery.change(opacity: 0, animated: true) + gallery.change(opacity: 0, animated: true) + } + + @objc func windowDidResized(_ notification: Notification) { + let closePoint = NSMakePoint(10, frame.height - 50) + let openPoint = NSMakePoint(closePoint.x + close.frame.width + 10, frame.height - 50) + self.close.setFrameOrigin(closePoint) + self.gallery.setFrameOrigin(openPoint) + + } + + override var isResizable: Bool { + return true } override func makeKeyAndOrderFront(_ sender: Any?) { super.makeKeyAndOrderFront(sender) + Queue.mainQueue().justDispatch { if let screen = NSScreen.main { - let convert_s = self.playerView.frame.size.fitted(NSMakeSize(300, 300)) - self.minSize = convert_s - self.aspectRatio = convert_s + let savedRect: NSRect = NSMakeRect(0, 0, screen.frame.width * 0.3, screen.frame.width * 0.3) + let convert_s = self.playerView.frame.size.fitted(NSMakeSize(savedRect.width, savedRect.height)) + self.aspectRatio = convert_s + self.minSize = convert_s.aspectFilled(NSMakeSize(100, 100)) let closePoint = NSMakePoint(10, convert_s.height - 50) let openPoint = NSMakePoint(closePoint.x + self.close.frame.width + 10, convert_s.height - 50) self.close.change(pos: closePoint, animated: false) - self.openGallery.change(pos: openPoint, animated: false) - + self.gallery.change(pos: openPoint, animated: false) self.setFrame(NSMakeRect(screen.frame.maxX - convert_s.width - 30, screen.frame.maxY - convert_s.height - 50, convert_s.width, convert_s.height), display: true, animate: true) + } + } + } + + +} + +protocol PictureInPictureControl { + func pause() + func play() + func didEnter() + func didExit() + var view: NSView { get } + var isPictureInPicture: Bool { get } +} + + +private class PictureInpictureView : Control { + private let _window: Window + init(frame: NSRect, window: Window) { + _window = window + super.init(frame: frame) + autoresizesSubviews = true + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + } + + override func mouseExited(with event: NSEvent) { + super.mouseEntered(with: event) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override var window: NSWindow? { + set { + + } + get { + return _window + } + } +} + +fileprivate class ModernPictureInPictureVideoWindow: NSPanel { + fileprivate let _window: Window + fileprivate let control: PictureInPictureControl + private let rect:NSRect + private let restoreRect: NSRect + fileprivate var forcePaused: Bool = false + fileprivate let item: MGalleryItem + fileprivate weak var _delegate: InteractionContentViewProtocol? + fileprivate let _contentInteractions:ChatMediaLayoutParameters? + fileprivate let _type: GalleryAppearType + fileprivate let viewer: GalleryViewer + fileprivate var eventLocalMonitor: Any? + fileprivate var eventGlobalMonitor: Any? + private var hideAnimated: Bool = true + + init(_ control: PictureInPictureControl, item: MGalleryItem, viewer: GalleryViewer, origin:NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType) { + self.viewer = viewer + self._delegate = delegate + self._contentInteractions = contentInteractions + self._type = type + self.control = control + + // let difference = NSMakeSize(item.notFittedSize.width - item.sizeValue.width, item.notFittedSize.height - item.sizeValue.height) + + let newRect = NSMakeRect(origin.x, origin.y, item.notFittedSize.aspectFitted(control.view.frame.size).width, item.notFittedSize.aspectFitted(control.view.frame.size).height) + self.rect = newRect //NSMakeRect(origin.x, origin.y, control.view.frame.width, control.view.frame.height) + self.restoreRect = NSMakeRect(origin.x, origin.y, control.view.frame.width, control.view.frame.height) + self.item = item + _window = Window(contentRect: control.view.bounds, styleMask: [.resizable], backing: .buffered, defer: true) + super.init(contentRect: newRect, styleMask: [.resizable, .nonactivatingPanel], backing: .buffered, defer: true) + + //self.isOpaque = false + self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]; + + + self.contentView = PictureInpictureView.init(frame: bounds, window: _window) + + // self.contentView?.wantsLayer = true; + self.contentView?.layer?.cornerRadius = 4; + self.backgroundColor = .clear; + control.view.frame = NSMakeRect(0, 0, newRect.width, newRect.height) + // control.view.autoresizingMask = [.width, .height]; + + + control.view.setFrameOrigin(0, 0) + // contentView?.autoresizingMask = [.width, .height] + contentView?.addSubview(control.view) + + + _window.set(mouseHandler: { event -> KeyHandlerResult in + + return .invoked + }, with: self, for: .mouseMoved, priority: .low) + + _window.set(mouseHandler: { event -> KeyHandlerResult in + return .invoked + }, with: self, for: .mouseEntered, priority: .low) + + _window.set(mouseHandler: { event -> KeyHandlerResult in + return .invoked + }, with: self, for: .mouseExited, priority: .low) + + + _window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + if event.clickCount == 2, let strongSelf = self { + let inner = strongSelf.control.view.convert(event.locationInWindow, from: nil) + if NSWindow.windowNumber(at: NSEvent.mouseLocation, belowWindowWithWindowNumber: 0) == strongSelf.windowNumber, strongSelf.control.view.hitTest(inner) is MediaPlayerView { + strongSelf.hide() + } } + return .invoked + }, with: self, for: .leftMouseDown, priority: .low) + + + _window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + self?.findAndMoveToCorner() + return .rejected + }, with: self, for: .leftMouseUp, priority: .low) + + + self.level = .modalPanel + self.isMovableByWindowBackground = true + + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResized(_:)), name: NSWindow.didResizeNotification, object: self) + + + + eventLocalMonitor = NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved, .mouseEntered, .mouseExited, .leftMouseDown, .leftMouseUp], handler: { [weak self] event in + guard let `self` = self else {return event} + self._window.sendEvent(event) + return event + }) + + eventGlobalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .mouseEntered, .mouseExited, .leftMouseDown, .leftMouseUp], handler: { [weak self] event in + guard let `self` = self else {return} + self._window.sendEvent(event) + }) + + } + + + + func hide() { + if hideAnimated { + contentView?._change(opacity: 0, animated: true, duration: 0.1, timingFunction: .linear) + setFrame(NSMakeRect(frame.minX + (frame.width - 0) / 2, frame.minY + (frame.height - 0) / 2, 0, 0), display: true, animate: true) + } + + orderOut(nil) + window = nil + _window.removeAllHandlers(for: self) + if let monitor = eventLocalMonitor { + NSEvent.removeMonitor(monitor) + } + if let monitor = eventGlobalMonitor { + NSEvent.removeMonitor(monitor) } } + + override func orderOut(_ sender: Any?) { + super.orderOut(sender) + window = nil + if control.isPictureInPicture { + control.pause() + } + } + + func openGallery() { + setFrame(restoreRect, display: true, animate: true) + hideAnimated = false + hide() + showGalleryFromPip(item: item, gallery: self.viewer, delegate: _delegate, contentInteractions: _contentInteractions, type: _type) + } + + deinit { + if control.isPictureInPicture { + control.pause() + } + NotificationCenter.default.removeObserver(self) + } + + override func animationResizeTime(_ newFrame: NSRect) -> TimeInterval { + return 0.2 + } + + @objc func windowDidResized(_ notification: Notification) { + } + + private func findAndMoveToCorner() { + if let screen = self.screen { + let rect = screen.frame.offsetBy(dx: -screen.visibleFrame.minX, dy: -screen.visibleFrame.minY) + + let point = self.frame.offsetBy(dx: -screen.visibleFrame.minX, dy: -screen.visibleFrame.minY) + + var options:BorderType = [] + + if point.maxX > rect.width && point.minX < rect.width { + options.insert(.Right) + } + + if point.minX < 0 { + options.insert(.Left) + } + + if point.minY < 0 { + options.insert(.Bottom) + } + + + var newFrame = self.frame + + if options.contains(.Right) { + newFrame.origin.x = screen.visibleFrame.maxX - newFrame.width - 30 + } + if options.contains(.Bottom) { + newFrame.origin.y = screen.visibleFrame.minY + 30 + } + if options.contains(.Left) { + newFrame.origin.x = screen.visibleFrame.minX + 30 + } + setFrame(newFrame, display: true, animate: true) + + +// switch alignment { +// case .topLeft: +// setFrame(NSMakeRect(30, 30, self.frame.width, self.frame.height), display: true, animate: true) +// case .topRight: +// setFrame(NSMakeRect(frame.width - self.frame.width - 30, 30, self.frame.width, self.frame.height), display: true, animate: true) +// case .bottomLeft: +// setFrame(NSMakeRect(30, frame.height - self.frame.height - 30, self.frame.width, self.frame.height), display: true, animate: true) +// case .bottomRight: +// setFrame(NSMakeRect(frame.width - self.frame.width - 30, frame.height - self.frame.height - 30, self.frame.width, self.frame.height), display: true, animate: true) +// } + } + } + + + override var isResizable: Bool { + return true + } + override func setFrame(_ frameRect: NSRect, display flag: Bool, animate animateFlag: Bool) { + super.setFrame(frameRect, display: flag, animate: animateFlag) + } + + override func makeKeyAndOrderFront(_ sender: Any?) { + super.makeKeyAndOrderFront(sender) + if let screen = NSScreen.main { + let savedRect: NSRect = NSMakeRect(0, 0, screen.frame.width * 0.3, screen.frame.width * 0.3) + let convert_s = self.rect.size.fitted(NSMakeSize(savedRect.width, savedRect.height)) + self.aspectRatio = self.rect.size.fitted(NSMakeSize(savedRect.width, savedRect.height)) + self.minSize = self.rect.size.fitted(NSMakeSize(savedRect.width, savedRect.height)).aspectFilled(NSMakeSize(250, 250)) + + let frame = NSScreen.main?.frame ?? NSMakeRect(0, 0, 1920, 1080) + + self.maxSize = self.rect.size.fitted(NSMakeSize(savedRect.width, savedRect.height)).aspectFilled(NSMakeSize(frame.width / 3, frame.height / 3)) + + self.setFrame(NSMakeRect(screen.frame.maxX - convert_s.width - 30, screen.frame.maxY - convert_s.height - 50, convert_s.width, convert_s.height), display: true, animate: true) + + } + } + + +} + + + +private var window: NSWindow? + + +var hasPictureInPicture: Bool { + return window != nil } -private var window: PIPVideoWindow? +func showLegacyPipVideo(_ playerView:VideoPlayerView, viewer: GalleryViewer, item: MGalleryItem, origin: NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType) { + closePipVideo() + window = PIPVideoWindow(playerView, item: item, viewer: viewer, origin: origin, delegate: delegate, contentInteractions: contentInteractions, type: type) + window?.makeKeyAndOrderFront(nil) +} -func showPipVideo(_ player:AVPlayerView, item: MGalleryVideoItem, origin: NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaGalleryParameters? = nil, type: GalleryAppearType) { - window = PIPVideoWindow(player, item: item, origin: origin, delegate: delegate, contentInteractions: contentInteractions, type: type) +func showPipVideo(control: PictureInPictureControl, viewer: GalleryViewer, item: MGalleryItem, origin: NSPoint, delegate:InteractionContentViewProtocol? = nil, contentInteractions:ChatMediaLayoutParameters? = nil, type: GalleryAppearType) { + closePipVideo() + window = ModernPictureInPictureVideoWindow(control, item: item, viewer: viewer, origin: origin, delegate: delegate, contentInteractions: contentInteractions, type: type) window?.makeKeyAndOrderFront(nil) } + +func exitPictureInPicture() { + if let window = window as? PIPVideoWindow { + window.openGallery() + } else if let window = window as? ModernPictureInPictureVideoWindow { + window.openGallery() + } +} + func pausepip() { - window?.playerView.player?.pause() - window?.forcePaused = true + if let window = window as? PIPVideoWindow { + window.playerView.player?.pause() + window.forcePaused = true + } else if let window = window as? ModernPictureInPictureVideoWindow { + window.control.pause() + window.forcePaused = true + } + } func playPipIfNeeded() { - if let forcePaused = window?.forcePaused, forcePaused { - window?.playerView.player?.play() + if let window = window as? PIPVideoWindow, window.forcePaused { + window.playerView.player?.play() + } else if let window = window as? ModernPictureInPictureVideoWindow, window.forcePaused { + window.control.play() } } func closePipVideo() { - window?.hide() + if let window = window as? PIPVideoWindow { + window.hide() + window.playerView.player?.pause() + } else if let window = window as? ModernPictureInPictureVideoWindow { + window.hide() + window.control.pause() + } window = nil + } diff --git a/Telegram-Mac/PanelUtils.swift b/Telegram-Mac/PanelUtils.swift index cd7181f23d..fa4d99ffd7 100644 --- a/Telegram-Mac/PanelUtils.swift +++ b/Telegram-Mac/PanelUtils.swift @@ -8,32 +8,33 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit import Foundation -let mediaExts:[String] = ["png","jpg","jpeg","tiff","mp4","mov","avi", "gif"] +import Postbox +import TelegramCore +import SyncCore + +let mediaExts:[String] = ["png","jpg","jpeg","tiff","mp4","mov","avi", "gif", "m4v"] let photoExts:[String] = ["png","jpg","jpeg","tiff"] -let videoExts:[String] = ["mp4","mov","avi"] +let videoExts:[String] = ["mp4","mov","avi", "m4v"] -func filePanel(with exts:[String]? = nil, allowMultiple:Bool = true, for window:Window, completion:@escaping ([String]?)->Void) { +func filePanel(with exts:[String]? = nil, allowMultiple:Bool = true, canChooseDirectories: Bool = false, for window:Window, completion:@escaping ([String]?)->Void) { var result:[String] = [] let panel:NSOpenPanel = NSOpenPanel() panel.canChooseFiles = true - - if let window = NSApp.window(withWindowNumber: panel.windowNumber) { - //window.appearance = theme.appearance - } + panel.canChooseDirectories = canChooseDirectories panel.canCreateDirectories = true panel.allowedFileTypes = exts - panel.allowsMultipleSelection = true + panel.allowsMultipleSelection = allowMultiple panel.beginSheetModal(for: window) { (response) in if response.rawValue == NSFileHandlingPanelOKButton { for url in panel.urls { let path:String = url.path if let exts = exts { let ext:String = path.nsstring.pathExtension.lowercased() - if exts.contains(ext) { + if exts.contains(ext) || (canChooseDirectories && path.isDirectory) { result.append(path) } } else { @@ -47,18 +48,73 @@ func filePanel(with exts:[String]? = nil, allowMultiple:Bool = true, for window: } } -func savePanel(file:String, ext:String, for window:Window) { +func selectFolder(for window:Window, completion:@escaping (String)->Void) { + var result:[String] = [] + let panel:NSOpenPanel = NSOpenPanel() + panel.canChooseFiles = false + panel.canChooseDirectories = true + panel.canCreateDirectories = true + panel.allowsMultipleSelection = false + panel.beginSheetModal(for: window) { (response) in + if response.rawValue == NSFileHandlingPanelOKButton { + for url in panel.urls { + let path:String = url.path + result.append(path) + } + if let first = result.first { + completion(first) + } + } + } +} + +func savePanel(file:String, ext:String, for window:Window, defaultName: String? = nil, completion:((String?)->Void)? = nil) { let savePanel:NSSavePanel = NSSavePanel() let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss" - savePanel.nameFieldStringValue = "\(dateFormatter.string(from: Date())).\(ext)" - savePanel.beginSheetModal(for: window, completionHandler: {(result) in + savePanel.nameFieldStringValue = defaultName ?? "\(dateFormatter.string(from: Date())).\(ext)" + + let wLevel = window.level + // if wLevel == .screenSaver { + window.level = .normal + //} + savePanel.begin { (result) in if result == NSApplication.ModalResponse.OK, let saveUrl = savePanel.url { + try? FileManager.default.removeItem(atPath: saveUrl.path) try? FileManager.default.copyItem(atPath: file, toPath: saveUrl.path) + completion?(saveUrl.path) + } else { + completion?(nil) } - }) + window.level = wLevel + } + +// savePanel.beginSheetModal(for: window, completionHandler: { [weak window] result in +// if result == NSApplication.ModalResponse.OK, let saveUrl = savePanel.url { +// try? FileManager.default.removeItem(atPath: saveUrl.path) +// try? FileManager.default.copyItem(atPath: file, toPath: saveUrl.path) +// completion?(saveUrl.path) +// } else { +// completion?(nil) +// } +// window?.level = wLevel +// }) + +// +// +// DispatchQueue.main.async { +// if let editor = savePanel.fieldEditor(false, for: nil) { +// let exportFilename = savePanel.nameFieldStringValue +// let ext = exportFilename.nsstring.pathExtension +// if !ext.isEmpty { +// let extensionLength = exportFilename.length - ext.length - 1 +// editor.selectedRange = NSMakeRange(0, extensionLength) +// } +// } +// } + } func savePanel(file:String, named:String, for window:Window) { @@ -73,17 +129,50 @@ func savePanel(file:String, named:String, for window:Window) { try? FileManager.default.copyItem(atPath: file, toPath: saveUrl.path) } }) + +// if let editor = savePanel.fieldEditor(false, for: nil) { +// let exportFilename = savePanel.nameFieldStringValue +// let ext = exportFilename.nsstring.pathExtension +// if !ext.isEmpty { +// let extensionLength = exportFilename.length - ext.length - 1 +// editor.selectedRange = NSMakeRange(0, extensionLength) +// } +// } + } -func alert(for window:Window, header:String = appName, info:String?) { +func alert(for window:Window, header:String = appName, info:String?, runModal: Bool = false, completion: (()->Void)? = nil) { +// +// let alert = AlertController(window, header: header, text: info ?? "") +// alert.show(completionHandler: { response in +// completion?() +// }) + let alert:NSAlert = NSAlert() alert.window.appearance = theme.appearance alert.alertStyle = .informational alert.messageText = header alert.informativeText = info ?? "" - alert.beginSheetModal(for: window, completionHandler: { (_) in}) + alert.addButton(withTitle: L10n.alertOK) + + + alert.addButton(withTitle: L10n.alertCancel) + alert.buttons.last?.wantsLayer = true + alert.buttons.last?.layer?.opacity = 0 + alert.buttons.last?.keyEquivalent = "\u{1b}" + alert.buttons.last?.removeAllSubviews() + alert.buttons.last?.focusRingType = .none + if runModal { + alert.runModal() + } else { + alert.beginSheetModal(for: window, completionHandler: { (_) in + completion?() + }) + } + + } func notSupported() { @@ -95,42 +184,188 @@ enum ConfirmResult { case basic } -func confirm(for window:Window, with header:String, and information:String?, okTitle:String? = nil, cancelTitle:String? = nil, thridTitle:String? = nil, successHandler:@escaping(ConfirmResult)->Void) { +func confirm(for window:Window, header: String? = nil, information:String?, okTitle:String? = nil, cancelTitle:String = L10n.alertCancel, thridTitle:String? = nil, fourTitle: String? = nil, successHandler:@escaping (ConfirmResult)->Void, cancelHandler: (()->Void)? = nil) { + + let alert:NSAlert = NSAlert() alert.window.appearance = theme.appearance alert.alertStyle = .informational - alert.messageText = header + alert.messageText = header ?? appName alert.informativeText = information ?? "" - alert.addButton(withTitle: okTitle ?? tr(.alertOK)) - alert.addButton(withTitle: cancelTitle ?? tr(.alertCancel)) + alert.addButton(withTitle: okTitle ?? L10n.alertOK) + if !cancelTitle.isEmpty { + alert.addButton(withTitle: cancelTitle) + alert.buttons.last?.keyEquivalent = "\u{1b}" + } + + if let thridTitle = thridTitle { alert.addButton(withTitle: thridTitle) } + if let fourTitle = fourTitle { + alert.addButton(withTitle: fourTitle) + } + - alert.beginSheetModal(for: window, completionHandler: { (response) in + + alert.beginSheetModal(for: window, completionHandler: { response in + Queue.mainQueue().justDispatch { + if response.rawValue == 1000 { + successHandler(.basic) + } else if response.rawValue == 1002 { + successHandler(.thrid) + } else if response.rawValue == 1001, cancelTitle == "" { + successHandler(.thrid) + } else if response.rawValue == 1001 { + cancelHandler?() + } + } + }) +} + +func modernConfirm(for window:Window, account: Account?, peerId: PeerId?, header: String = appName, information:String? = nil, okTitle:String = L10n.alertOK, cancelTitle:String = L10n.alertCancel, thridTitle:String? = nil, thridAutoOn: Bool = true, successHandler:@escaping(ConfirmResult)->Void) { + // + + let alert:NSAlert = NSAlert() + alert.window.appearance = theme.appearance + alert.alertStyle = .informational + alert.messageText = header + alert.informativeText = information ?? "" + alert.addButton(withTitle: okTitle) + alert.addButton(withTitle: cancelTitle) + + + + if let thridTitle = thridTitle { + alert.showsSuppressionButton = true + alert.suppressionButton?.title = thridTitle + alert.suppressionButton?.state = thridAutoOn ? .on : .off + // alert.addButton(withTitle: thridTitle) + } + + let signal: Signal + if let peerId = peerId, let account = account { + signal = account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) |> deliverOnMainQueue + } else { + signal = .single(nil) + } + + var disposable: Disposable? + + var shown: Bool = false + + let readyToShow:() -> Void = { + if !shown { + shown = true + alert.beginSheetModal(for: window, completionHandler: { [weak alert] response in + disposable?.dispose() + if let alert = alert { + if alert.showsSuppressionButton, let button = alert.suppressionButton, response.rawValue != 1001 { + switch button.state { + case .off: + successHandler(.basic) + case .on: + successHandler(.thrid) + default: + break + } + } else { + if response.rawValue == 1000 { + successHandler(.basic) + } else if response.rawValue == 1002 { + successHandler(.thrid) + } + } + } + }) + } - if response.rawValue == 1000 { - successHandler(.basic) - } else if response.rawValue == 1002 { - successHandler(.thrid) + } + + _ = signal.start(next: { peer in + if let peer = peer, let account = account { + alert.messageText = account.peerId == peer.id ? L10n.peerSavedMessages : peer.displayTitle + alert.icon = nil + if peerId == account.peerId { + let icon = theme.icons.searchSaved + let signal = generateEmptyPhoto(NSMakeSize(70, 70), type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(50, 50)), cornerRadius: nil)) |> deliverOnMainQueue + disposable = signal.start(next: { image in + if let image = image { + alert.icon = NSImage(cgImage: image, size: NSMakeSize(70, 70)) + delay(0.2, closure: { + readyToShow() + }) + } + }) + + } else { + disposable = (peerAvatarImage(account: account, photo: PeerPhoto.peer(peer, peer.smallProfileImage, peer.displayLetters, nil), displayDimensions: NSMakeSize(70, 70), scale: System.backingScale, font: .avatar(30), genCap: true) |> deliverOnMainQueue).start(next: { image, _ in + if let image = image { + alert.icon = NSImage(cgImage: image, size: NSMakeSize(70, 70)) + delay(0.2, closure: { + readyToShow() + }) + } + }) + } + + } else { + readyToShow() } }) + + + +// let alert = AlertController(window, account: account, peerId: peerId, header: header, text: information, okTitle: okTitle, cancelTitle: cancelTitle, thridTitle: thridTitle, accessory: accessory) +// +// alert.show(completionHandler: { response in +// switch response { +// case .OK: +// successHandler(.basic) +// case .alertThirdButtonReturn: +// successHandler(.thrid) +// default: +// break +// } +// }) + +} +func modernConfirmSignal(for window:Window, account: Account?, peerId: PeerId?, header: String = appName, information:String? = nil, okTitle:String = L10n.alertOK, cancelTitle:String = L10n.alertCancel, thridTitle: String? = nil, thridAutoOn: Bool = true) -> Signal { + let value:ValuePromise = ValuePromise(ignoreRepeated: true) + + Queue.mainQueue().async { + modernConfirm(for: window, account: account, peerId: peerId, header: header, information: information, okTitle: okTitle, cancelTitle: cancelTitle, thridTitle: thridTitle, thridAutoOn: thridAutoOn, successHandler: { response in + value.set(response) + }) + } + return value.get() |> take(1) + } -func confirmSignal(for window:Window, header:String, information:String?, okTitle:String? = nil, cancelTitle:String? = nil) -> Signal { +func confirmSignal(for window:Window, header: String? = nil, information:String?, okTitle:String? = nil, cancelTitle:String? = nil) -> Signal { +// let value:ValuePromise = ValuePromise(ignoreRepeated: true) +// +// Queue.mainQueue().async { +// let alert = AlertController(window, header: header ?? appName, text: information ?? "", okTitle: okTitle, cancelTitle: cancelTitle ?? tr(L10n.alertCancel), swapColors: swapColors) +// alert.show(completionHandler: { response in +// value.set(response == .OK) +// }) +// } +// return value.get() |> take(1) + let value:ValuePromise = ValuePromise(ignoreRepeated: true) Queue.mainQueue().async { let alert:NSAlert = NSAlert() alert.alertStyle = .informational - alert.messageText = header + alert.messageText = header ?? appName alert.window.appearance = theme.appearance alert.informativeText = information ?? "" - alert.addButton(withTitle: okTitle ?? tr(.alertOK)) - alert.addButton(withTitle: cancelTitle ?? tr(.alertCancel)) + alert.addButton(withTitle: okTitle ?? tr(L10n.alertOK)) + alert.addButton(withTitle: cancelTitle ?? tr(L10n.alertCancel)) alert.beginSheetModal(for: window, completionHandler: { response in value.set(response.rawValue == 1000) diff --git a/Telegram-Mac/ParseAppearanceColors.swift b/Telegram-Mac/ParseAppearanceColors.swift new file mode 100644 index 0000000000..886c2495cf --- /dev/null +++ b/Telegram-Mac/ParseAppearanceColors.swift @@ -0,0 +1,603 @@ +// +// ParseAppearanceColors.swift +// Telegram +// +// Created by keepcoder on 01/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +private let colors: [UInt32: String] = [ + 0x8e0000: "Berry", + 0xdec196: "Brandy", + 0x800b47: "Cherry", + 0xff7f50: "Coral", + 0xdb5079: "Cranberry", + 0xdc143c: "Crimson", + 0xe0b0ff: "Mauve", + 0xffc0cb: "Pink", + 0xff0000: "Red", + 0xff007f: "Rose", + 0x80461b: "Russet", + 0xff2400: "Scarlet", + 0xf1f1f1: "Seashell", + 0xff3399: "Strawberry", + 0xffbf00: "Amber", + 0xeb9373: "Apricot", + 0xfbe7b2: "Banana", + 0xa1c50a: "Citrus", + 0xb06500: "Ginger", + 0xffd700: "Gold", + 0xfde910: "Lemon", + 0xffa500: "Orange", + 0xffe5b4: "Peach", + 0xff6b53: "Persimmon", + 0xe4d422: "Sunflower", + 0xf28500: "Tangerine", + 0xffc87c: "Topaz", + 0xffff00: "Yellow", + 0x384910: "Clover", + 0x83aa5d: "Cucumber", + 0x50c878: "Emerald", + 0xb5b35c: "Olive", + 0x00ff00: "Green", + 0x00a86b: "Jade", + 0x29ab87: "Jungle", + 0xbfff00: "Lime", + 0x0bda51: "Malachite", + 0x98ff98: "Mint", + 0xaddfad: "Moss", + 0x315ba1: "Azure", + 0x0000ff: "Blue", + 0x0047ab: "Cobalt", + 0x4f69c6: "Indigo", + 0x017987: "Lagoon", + 0x71d9e2: "Aquamarine", + 0x120a8f: "Ultramarine", + 0x000080: "Navy", + 0x2f519e: "Sapphire", + 0x76d7ea: "Sky", + 0x008080: "Teal", + 0x40e0d0: "Turquoise", + 0x9966cc: "Amethyst", + 0x4d0135: "Blackberry", + 0x614051: "Eggplant", + 0xc8a2c8: "Lilac", + 0xb57edc: "Lavender", + 0xccccff: "Periwinkle", + 0x843179: "Plum", + 0x660099: "Purple", + 0xd8bfd8: "Thistle", + 0xda70d6: "Orchid", + 0x240a40: "Violet", + 0x3f2109: "Bronze", + 0x370202: "Chocolate", + 0x7b3f00: "Cinnamon", + 0x301f1e: "Cocoa", + 0x706555: "Coffee", + 0x796989: "Rum", + 0x4e0606: "Mahogany", + 0x782d19: "Mocha", + 0xc2b280: "Sand", + 0x882d17: "Sienna", + 0x780109: "Maple", + 0xf0e68c: "Khaki", + 0xb87333: "Copper", + 0xb94e48: "Chestnut", + 0xeed9c4: "Almond", + 0xfffdd0: "Cream", + 0xb9f2ff: "Diamond", + 0xa98307: "Honey", + 0xfffff0: "Ivory", + 0xeae0c8: "Pearl", + 0xeff2f3: "Porcelain", + 0xd1bea8: "Vanilla", + 0xffffff: "White", + 0x808080: "Gray", + 0x000000: "Black", + 0xe8f1d4: "Chrome", + 0x36454f: "Charcoal", + 0x0c0b1d: "Ebony", + 0xc0c0c0: "Silver", + 0xf5f5f5: "Smoke", + 0x262335: "Steel", + 0x4fa83d: "Apple", + 0x80b3c4: "Glacier", + 0xfebaad: "Melon", + 0xc54b8c: "Mulberry", + 0xa9c6c2: "Opal", + 0x54a5f8: "Blue" +] + +private let adjectives = [ + "Ancient", + "Antique", + "Autumn", + "Baby", + "Barely", + "Baroque", + "Blazing", + "Blushing", + "Bohemian", + "Bubbly", + "Burning", + "Buttered", + "Classic", + "Clear", + "Cool", + "Cosmic", + "Cotton", + "Cozy", + "Crystal", + "Dark", + "Daring", + "Darling", + "Dawn", + "Dazzling", + "Deep", + "Deepest", + "Delicate", + "Delightful", + "Divine", + "Double", + "Downtown", + "Dreamy", + "Dusky", + "Dusty", + "Electric", + "Enchanted", + "Endless", + "Evening", + "Fantastic", + "Flirty", + "Forever", + "Frigid", + "Frosty", + "Frozen", + "Gentle", + "Heavenly", + "Hyper", + "Icy", + "Infinite", + "Innocent", + "Instant", + "Luscious", + "Lunar", + "Lustrous", + "Magic", + "Majestic", + "Mambo", + "Midnight", + "Millenium", + "Morning", + "Mystic", + "Natural", + "Neon", + "Night", + "Opaque", + "Paradise", + "Perfect", + "Perky", + "Polished", + "Powerful", + "Rich", + "Royal", + "Sheer", + "Simply", + "Sizzling", + "Solar", + "Sparkling", + "Splendid", + "Spicy", + "Spring", + "Stellar", + "Sugared", + "Summer", + "Sunny", + "Super", + "Sweet", + "Tender", + "Tenacious", + "Tidal", + "Toasted", + "Totally", + "Tranquil", + "Tropical", + "True", + "Twilight", + "Twinkling", + "Ultimate", + "Ultra", + "Velvety", + "Vibrant", + "Vintage", + "Virtual", + "Warm", + "Warmest", + "Whipped", + "Wild", + "Winsome" +] + +private let subjectives = [ + "Ambrosia", + "Attack", + "Avalanche", + "Blast", + "Bliss", + "Blossom", + "Blush", + "Burst", + "Butter", + "Candy", + "Carnival", + "Charm", + "Chiffon", + "Cloud", + "Comet", + "Delight", + "Dream", + "Dust", + "Fantasy", + "Flame", + "Flash", + "Fire", + "Freeze", + "Frost", + "Glade", + "Glaze", + "Gleam", + "Glimmer", + "Glitter", + "Glow", + "Grande", + "Haze", + "Highlight", + "Ice", + "Illusion", + "Intrigue", + "Jewel", + "Jubilee", + "Kiss", + "Lights", + "Lollypop", + "Love", + "Luster", + "Madness", + "Matte", + "Mirage", + "Mist", + "Moon", + "Muse", + "Myth", + "Nectar", + "Nova", + "Parfait", + "Passion", + "Pop", + "Rain", + "Reflection", + "Rhapsody", + "Romance", + "Satin", + "Sensation", + "Silk", + "Shine", + "Shadow", + "Shimmer", + "Sky", + "Spice", + "Star", + "Sugar", + "Sunrise", + "Sunset", + "Sun", + "Twist", + "Unbound", + "Velvet", + "Vibrant", + "Waters", + "Wine", + "Wink", + "Wonder", + "Zone" +] + +private extension NSColor { + var colorComponents: (r: Int32, g: Int32, b: Int32) { + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + self.getRed(&r, green: &g, blue: &b, alpha: nil) + return (Int32(max(0.0, r) * 255.0), Int32(max(0.0, g) * 255.0), Int32(max(0.0, b) * 255.0)) + } + + func distance(to other: NSColor) -> Int32 { + let e1 = self.colorComponents + let e2 = other.colorComponents + let rMean = (e1.r + e2.r) / 2 + let r = e1.r - e2.r + let g = e1.g - e2.g + let b = e1.b - e2.b + return ((512 + rMean) * r * r) >> 8 + 4 * g * g + ((767 - rMean) * b * b) >> 8 + } +} + + +private func generateThemeName(_ accentColor: NSColor) -> String { + var nearest: (color: UInt32, distance: Int32)? + for (color, _) in colors { + let distance = accentColor.distance(to: NSColor(rgb: color)) + if let currentNearest = nearest { + if distance < currentNearest.distance { + nearest = (color, distance) + } + } else { + nearest = (color, distance) + } + } + + if let color = nearest?.color, let colorName = colors[color]?.capitalized { + return "\(adjectives[Int(arc4random()) % adjectives.count].capitalized) \(colorName)" + } else { + return "" + } +} + + +func importPalette(_ path: String) -> ColorPalette? { + if let fs = fs(path), fs <= 30 * 1014 * 1024, let data = try? String(contentsOf: URL(fileURLWithPath: path)) { + let lines = data.components(separatedBy: "\n").filter({!$0.isEmpty}) + + var isDark: Bool = false + var tinted: Bool = false + var paletteName: String? = nil + var copyright: String = "Telegram" + var wallpaper: PaletteWallpaper? + var parent: TelegramBuiltinTheme = .dayClassic + var accentList:[PaletteAccentColor] = [] + var colors:[String: NSColor] = [:] + + /* + else if name == "accentList" { + accentList = value.components(separatedBy: ",").compactMap { NSColor(hexString: $0) } + } + */ + + for line in lines { + if !line.trimmed.hasPrefix("//") { + var components = line.components(separatedBy: "=") + if components.count > 2 { + components[1] = components[1..Void)? = nil) -> Void { + let string = palette.toString + let temp = NSTemporaryDirectory() + "tmac.palette" + try? string.write(to: URL(fileURLWithPath: temp), atomically: true, encoding: .utf8) + savePanel(file: temp, ext: "palette", for: mainWindow, defaultName: "\(palette.name).palette", completion: completion) +} + + + +func findBestNameForPalette(_ palette: ColorPalette) -> String { + return generateThemeName(palette.accent) +} + + +// +// let readList = Bundle.main.path(forResource: "theme-names-tree", ofType: nil) +// +// if let readList = readList, let string = try? String(contentsOfFile: readList) { +// let lines = string.components(separatedBy: "\n") +// +// var list:[(String, NSColor)] = [] +// +// for line in lines { +// let value = line.components(separatedBy: "=") +// if value.count == 2 { +// if let color = NSColor(hexString: value[1]) { +// let name = value[0].components(separatedBy: " ").map({ $0.capitalizingFirstLetter() }).joined(separator: " ") +// list.append((name, color)) +// } +// } +// } +// +// if list.count > 0 { +// +// let first = pow(palette.accent.hsv.0 - list[0].1.hsv.0, 2) + pow(palette.accent.hsv.1 - list[0].1.hsv.1, 2) + pow(palette.accent.hsv.2 - list[0].1.hsv.2, 2) +// var closest: (Int, CGFloat) = (0, first) +// +// +// for i in 0 ..< list.count { +// let distance = pow(palette.accent.hsv.0 - list[i].1.hsv.0, 2) + pow(palette.accent.hsv.1 - list[i].1.hsv.1, 2) + pow(palette.accent.hsv.2 - list[i].1.hsv.2, 2) +// if distance < closest.1 { +// closest = (i, distance) +// } +// } +// +// if let animalsPath = Bundle.main.path(forResource: "animals", ofType: nil), let string = try? String(contentsOfFile: animalsPath) { +// let animals = string.components(separatedBy: "\n").filter { !$0.isEmpty } +// let animal = animals[Int(arc4random()) % animals.count].capitalizingFirstLetter() +// return list[closest.0].0 + " " + animal +// } +// return list[closest.0].0 +// } +// +// } +// +// return palette.name + diff --git a/Telegram-Mac/PasscodeControllers.swift b/Telegram-Mac/PasscodeControllers.swift new file mode 100644 index 0000000000..572bcf1e27 --- /dev/null +++ b/Telegram-Mac/PasscodeControllers.swift @@ -0,0 +1,287 @@ +// +// PasscodeControllers.swift +// Telegram +// +// Created by Mikhail Filimonov on 06/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + +enum PasscodeMode : Equatable { + case install + case change(String) + case disable(String) +} + +private struct PasscodeState : Equatable { + let mode: PasscodeMode + let data: [InputDataIdentifier : InputDataValue] + let errors: [InputDataIdentifier : InputDataValueError] + init(mode: PasscodeMode, data: [InputDataIdentifier : InputDataValue], errors: [InputDataIdentifier : InputDataValueError]) { + self.mode = mode + self.data = data + self.errors = errors + } + + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> PasscodeState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return PasscodeState(mode: self.mode, data: self.data, errors: errors) + } + + func withUpdatedValue(_ value: InputDataValue, for key: InputDataIdentifier) -> PasscodeState { + var data = self.data + data[key] = value + return PasscodeState(mode: self.mode, data: data, errors: self.errors) + } +} + +private let _id_input_new_passcode = InputDataIdentifier("_id_input_new_passcode") +private let _id_input_re_new_passcode = InputDataIdentifier("_id_input_re_new_passcode") + +private let _id_input_current = InputDataIdentifier("_id_input_current") + +private func passcodeEntries(_ state: PasscodeState) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + switch state.mode { + case .install: + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.passcodeControllerHeaderNew), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_new_passcode]?.stringValue), error: state.errors[_id_input_new_passcode], identifier: _id_input_new_passcode, mode: .secure, data: InputDataRowData(viewType: .firstItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerEnterPasscodePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_re_new_passcode]?.stringValue), error: state.errors[_id_input_re_new_passcode], identifier: _id_input_re_new_passcode, mode: .secure, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerReEnterPasscodePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + + case .change: + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.passcodeControllerHeaderCurrent), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_current]?.stringValue), error: state.errors[_id_input_current], identifier: _id_input_current, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerCurrentPlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.passcodeControllerHeaderNew), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_new_passcode]?.stringValue), error: state.errors[_id_input_new_passcode], identifier: _id_input_new_passcode, mode: .secure, data: InputDataRowData(viewType: .firstItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerEnterPasscodePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_re_new_passcode]?.stringValue), error: state.errors[_id_input_re_new_passcode], identifier: _id_input_re_new_passcode, mode: .secure, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerReEnterPasscodePlaceholder, filter: { $0 }, limit: 255)) + index += 1 + + + + case .disable: + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.passcodeControllerHeaderCurrent), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(state.data[_id_input_current]?.stringValue), error: state.errors[_id_input_current], identifier: _id_input_current, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.passcodeControllerCurrentPlaceholder, filter: { $0 }, limit: 255)) + index += 1 + } + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.passcodeControllerText), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func PasscodeController(sharedContext: SharedAccountContext, mode: PasscodeMode) -> ViewController { + + + let initialState = PasscodeState(mode: mode, data: [:], errors: [:]) + + let statePromise = ValuePromise(initialState, ignoreRepeated: false) + let stateValue = Atomic(value: initialState) + let updateState: ((PasscodeState) -> PasscodeState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let dataSignal = statePromise.get() |> map { + return passcodeEntries($0) + } + + let title: String + switch mode { + case .install: + title = L10n.passcodeControllerInstallTitle + case .change: + title = L10n.passcodeControllerChangeTitle + case .disable: + title = L10n.passcodeControllerDisableTitle + } + + var shouldMakeNextResponderAfterTransition: InputDataIdentifier? = nil + + let actionsDisposable = DisposableSet() + + return InputDataController(dataSignal: dataSignal |> map { InputDataSignalValue(entries: $0) }, title: title, validateData: { data in + + + return .fail(.doSomething { f in + let state = stateValue.with { $0 } + + switch state.mode { + case .install: + let passcode = state.data[_id_input_new_passcode]?.stringValue ?? "" + let confirm = state.data[_id_input_re_new_passcode]?.stringValue ?? "" + + var fields:[InputDataIdentifier : InputDataValidationFailAction] = [:] + + if !passcode.isEmpty, !confirm.isEmpty, passcode != confirm { + fields[_id_input_new_passcode] = .shake + fields[_id_input_re_new_passcode] = .shake + + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.passcodeControllerErrorDifferent, target: .data), for: _id_input_re_new_passcode) + } + } + if passcode.isEmpty { + fields[_id_input_new_passcode] = .shake + shouldMakeNextResponderAfterTransition = _id_input_new_passcode + } + if confirm.isEmpty { + fields[_id_input_re_new_passcode] = .shake + if shouldMakeNextResponderAfterTransition == nil { + shouldMakeNextResponderAfterTransition = _id_input_re_new_passcode + } + } + if !fields.isEmpty { + f(.fail(.fields(fields))) + } else { + actionsDisposable.add((sharedContext.accountManager.transaction { transaction in + transaction.setAccessChallengeData(.plaintextPassword(value: passcode)) + } |> deliverOnMainQueue).start(completed: { + f(.success(.navigationBackWithPushAnimation)) + })) + } + + case let .change(local): + let current = state.data[_id_input_current]?.stringValue ?? "" + + + let passcode = state.data[_id_input_new_passcode]?.stringValue ?? "" + let confirm = state.data[_id_input_re_new_passcode]?.stringValue ?? "" + + var fields:[InputDataIdentifier : InputDataValidationFailAction] = [:] + + if current != local { + fields[_id_input_current] = .shake + if shouldMakeNextResponderAfterTransition == nil { + shouldMakeNextResponderAfterTransition = _id_input_current + } + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.passcodeControllerErrorCurrent, target: .data), for: _id_input_current) + } + f(.fail(.fields(fields))) + return + } + + if !passcode.isEmpty, !confirm.isEmpty, passcode != confirm { + fields[_id_input_new_passcode] = .shake + fields[_id_input_re_new_passcode] = .shake + + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.passcodeControllerErrorDifferent, target: .data), for: _id_input_re_new_passcode) + } + } + if passcode.isEmpty { + fields[_id_input_new_passcode] = .shake + if shouldMakeNextResponderAfterTransition == nil { + shouldMakeNextResponderAfterTransition = _id_input_new_passcode + } + } + if confirm.isEmpty { + fields[_id_input_re_new_passcode] = .shake + if shouldMakeNextResponderAfterTransition == nil { + shouldMakeNextResponderAfterTransition = _id_input_re_new_passcode + } + } + if !fields.isEmpty { + f(.fail(.fields(fields))) + } else { + actionsDisposable.add((sharedContext.accountManager.transaction { transaction in + transaction.setAccessChallengeData(.plaintextPassword(value: passcode)) + } |> deliverOnMainQueue).start(completed: { + f(.success(.navigationBackWithPushAnimation)) + })) + } + case let .disable(local): + let current = state.data[_id_input_current]?.stringValue ?? "" + + if current != local { + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.passcodeControllerErrorCurrent, target: .data), for: _id_input_current) + } + f(.fail(.fields([_id_input_current : .shake]))) + + } else { + actionsDisposable.add((sharedContext.accountManager.transaction { transaction in + transaction.setAccessChallengeData(.none) + } |> deliverOnMainQueue).start(completed: { + f(.success(.navigationBackWithPushAnimation)) + })) + } + + } + + updateState { + $0 + } + }) + }, updateDatas: { data in + updateState { state in + var state = state + if let value = data[_id_input_new_passcode] { + state = state.withUpdatedValue(value, for: _id_input_new_passcode).withUpdatedError(nil, for: _id_input_new_passcode) + } + if let value = data[_id_input_re_new_passcode] { + state = state.withUpdatedValue(value, for: _id_input_re_new_passcode).withUpdatedError(nil, for: _id_input_re_new_passcode) + } + if let value = data[_id_input_current] { + state = state.withUpdatedValue(value, for: _id_input_current).withUpdatedError(nil, for: _id_input_current) + } + return state + } + + + return .none + }, afterDisappear: { + actionsDisposable.dispose() + }, afterTransaction: { controller in + if let identifier = shouldMakeNextResponderAfterTransition { + controller.makeFirstResponderIfPossible(for: identifier) + } + shouldMakeNextResponderAfterTransition = nil + }) + +} diff --git a/Telegram-Mac/PasscodeLockController.swift b/Telegram-Mac/PasscodeLockController.swift index 45e5642ee8..5400bfd2c5 100644 --- a/Telegram-Mac/PasscodeLockController.swift +++ b/Telegram-Mac/PasscodeLockController.swift @@ -8,82 +8,143 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import LocalAuthentication -enum PasscodeInnterState { - case old - case new - case confirm + +private class TouchIdContainerView : View { + fileprivate let button: TitleButton = TitleButton() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(button) + + button.autohighlight = false + button.style = ControlStyle(font: .medium(.title), foregroundColor: .white, backgroundColor: theme.colors.accent, highlightColor: theme.colors.accent) + button.set(font: .medium(.title), for: .Normal) + button.set(color: .white, for: .Normal) + + button.set(text: L10n.passcodeUseTouchId, for: .Normal) + button.set(image: theme.icons.passcodeTouchId, for: .Normal) + button.layer?.cornerRadius = 18 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + _ = button.sizeToFit(NSMakeSize(16, 0), NSMakeSize(0, 36), thatFit: true) + button.centerX(y: frame.height - button.frame.height) + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + let (text, layout) = TextNode.layoutText(NSAttributedString.initialize(string: L10n.passcodeOr, color: theme.colors.grayText, font: .normal(.title)), theme.colors.background, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .center) + + let f = focus(text.size) + layout.draw(NSMakeRect(f.minX, 0, f.width, f.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(0, floorToScreenPixels(backingScaleFactor, f.height / 2), f.minX - 10, .borderSize)) + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(f.maxX + 10, floorToScreenPixels(backingScaleFactor, f.height / 2), f.minX - 10, .borderSize)) + } } -enum PasscodeViewState { - case login - case change(PasscodeInnterState) - case enable(PasscodeInnterState) - case disable(PasscodeInnterState) +private final class PasscodeField : NSSecureTextField { + + override func resignFirstResponder() -> Bool { + (self.delegate as? PasscodeLockView)?.controlTextDidBeginEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.resignFirstResponder() + } + + override func becomeFirstResponder() -> Bool { + (self.delegate as? PasscodeLockView)?.controlTextDidEndEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.becomeFirstResponder() + } } + private class PasscodeLockView : Control, NSTextFieldDelegate { - fileprivate let photoView:AvatarControl = AvatarControl(font: .avatar(.custom(23))) fileprivate let nameView:TextView = TextView() - fileprivate let input:NSSecureTextField - private let nextButton:TitleButton = TitleButton() - private var state:PasscodeViewState? - + fileprivate let input:PasscodeField + private let nextButton:ImageButton = ImageButton() + private var hasTouchId:Bool = false + private let touchIdContainer:TouchIdContainerView = TouchIdContainerView(frame: NSMakeRect(0, 0, 240, 76)) fileprivate let logoutTextView:TextView = TextView() fileprivate let value:ValuePromise = ValuePromise(ignoreRepeated: false) fileprivate var logoutImpl:() -> Void = {} + fileprivate var useTouchIdImpl:() -> Void = {} + private let inputContainer: View = View() + private var fieldState: SearchFieldState = .Focus + required init(frame frameRect: NSRect) { - input = NSSecureTextField(frame: NSZeroRect) + input = PasscodeField(frame: NSZeroRect) + input.stringValue = "" super.init(frame: frameRect) - photoView.setFrameSize(NSMakeSize(80, 80)) - self.backgroundColor = .white - nextButton.set(color: theme.colors.blueUI, for: .Normal) - nextButton.set(font: .medium(.title), for: .Normal) - nextButton.set(text: tr(.passcodeNext), for: .Normal) - nextButton.sizeToFit() + autoresizingMask = [.width, .height] + self.backgroundColor = .clear + nextButton.set(background: theme.colors.accentIcon, for: .Normal) + nextButton.set(image: theme.icons.passcodeLogin, for: .Normal) + nextButton.setFrameSize(26, 26) + nextButton.layer?.cornerRadius = nextButton.frame.height / 2 - addSubview(photoView) + nameView.userInteractionEnabled = false + nameView.isSelectable = false + addSubview(nextButton) + addSubview(nameView) - addSubview(input) + addSubview(inputContainer) addSubview(logoutTextView) - addSubview(nextButton) - + addSubview(touchIdContainer) input.isBordered = false + input.usesSingleLineMode = true input.isBezeled = false input.focusRingType = .none - input.alignment = .center input.delegate = self + input.drawsBackground = false + + input.textView?.insertionPointColor = theme.colors.text + + inputContainer.backgroundColor = theme.colors.grayBackground + inputContainer.layer?.cornerRadius = .cornerRadius + inputContainer.addSubview(input) let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.passcodeEnterPasscodePlaceholder), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) - attr.setAlignment(.center, range: attr.range) + _ = attr.append(string: L10n.passcodeEnterPasscodePlaceholder, color: theme.colors.grayText, font: .medium(17)) + //attr.setAlignment(.center, range: attr.range) input.placeholderAttributedString = attr - input.font = .normal(.text) + input.cell?.usesSingleLineMode = true + input.cell?.wraps = false + input.cell?.isScrollable = true + input.font = .normal(.title) input.textColor = theme.colors.text - input.textView?.insertionPointColor = theme.colors.text + input.textView?.insertionPointColor = theme.colors.grayText + input.sizeToFit() - let logoutAttr = NSMutableAttributedString() - _ = logoutAttr.append(string: tr(.passcodeLogoutDescription), color: theme.colors.grayText, font: .normal(.text)) - _ = logoutAttr.append(string: " ") - let range = logoutAttr.append(string: tr(.passcodeLogoutLinkText), color: theme.colors.link, font: .normal(.text)) + let logoutAttr = parseMarkdownIntoAttributedString(L10n.passcodeLostDescription, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.grayText), bold: MarkdownAttributeSet(font: .bold(.text), textColor: theme.colors.grayText), link: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, {_ in})) + })) logoutTextView.isSelectable = false - logoutAttr.add(link: inAppLink.logout( { [weak self] in + let logoutLayout = TextViewLayout(logoutAttr) + logoutLayout.interactions = TextViewInteractions(processURL:{ [weak self] _ in self?.logoutImpl() - } ), for: range) + }) - let logoutLayout = TextViewLayout(logoutAttr) - logoutLayout.interactions = globalLinkExecutor logoutTextView.set(layout: logoutLayout) - - - + input.target = self input.action = #selector(checkPasscode) @@ -91,14 +152,20 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { self?.checkPasscode() }, for: .SingleClick) - updateLocalizationAndTheme() + touchIdContainer.button.set(handler: { [weak self] _ in + self?.useTouchIdImpl() + }, for: .SingleClick) + + updateLocalizationAndTheme(theme: theme) + layout() } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) backgroundColor = theme.colors.background logoutTextView.backgroundColor = theme.colors.background input.backgroundColor = theme.colors.background + inputContainer.background = theme.colors.grayBackground nameView.backgroundColor = theme.colors.background } @@ -106,77 +173,95 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { } + func controlTextDidChange(_ obj: Notification) { + change(state: fieldState, animated: true) + } + + + func controlTextDidBeginEditing(_ obj: Notification) { + change(state: .Focus, animated: true) + } + + func controlTextDidEndEditing(_ obj: Notification) { + window?.makeFirstResponder(input) + } + + private func change(state: SearchFieldState, animated: Bool) { + self.fieldState = state + switch state { + case .Focus: + input._change(size: NSMakeSize(inputContainer.frame.width - 20, input.frame.height), animated: animated) + input._change(pos: NSMakePoint(10, input.frame.minY), animated: animated) + nextButton.change(opacity: 1, animated: animated) + nextButton._change(pos: NSMakePoint(inputContainer.frame.maxX + 10, nextButton.frame.minY), animated: animated) + case .None: + input.sizeToFit() + let f = inputContainer.focus(input.frame.size) + input._change(pos: NSMakePoint(f.minX, input.frame.minY), animated: animated) + nextButton.change(opacity: 0, animated: animated) + nextButton._change(pos: NSMakePoint(inputContainer.frame.maxX - nextButton.frame.width, nextButton.frame.minY), animated: animated) + } + } @objc func checkPasscode() { value.set(input.stringValue) } - func update(with state:PasscodeViewState, account:Account, peer:Peer) { - self.state = state + func update(hasTouchId: Bool) { + self.hasTouchId = hasTouchId - photoView.setPeer(account: account, peer: peer) - let layout = TextViewLayout(.initialize(string:peer.displayTitle, color: theme.colors.text, font:.normal(.title))) + let layout = TextViewLayout(.initialize(string: L10n.passlockEnterYourPasscode, color: theme.colors.text, font: .medium(17))) layout.measure(width: frame.width - 40) nameView.update(layout) - switch state { - case .login: - self.logoutTextView.isHidden = false - default: - self.logoutTextView.isHidden = true + let text = L10n.passcodeEnterPasscodePlaceholder + if hasTouchId { + showTouchIdUI() + } else { + hideTouchIdUI() } + + input.stringValue = "" + let placeholder = NSMutableAttributedString() + _ = placeholder.append(string: text, color: theme.colors.grayText, font: .normal(.title)) + input.placeholderAttributedString = placeholder + needsLayout = true - changeInput(state) } - fileprivate func changeInput(_ state:PasscodeViewState) { - let placeholder = NSMutableAttributedString() - let text:String - - switch state { - case .login: - text = tr(.passcodeEnterPasscodePlaceholder) - case let .change(inner), let .enable(inner), let .disable(inner): - switch inner { - case .old: - text = tr(.passcodeEnterCurrentPlaceholder) - case .new: - text = tr(.passcodeEnterNewPlaceholder) - case .confirm: - text = tr(.passcodeReEnterPlaceholder) - } - } - input.stringValue = "" - _ = placeholder.append(string: text, color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) - placeholder.setAlignment(.center, range: placeholder.range) - input.placeholderAttributedString = placeholder + private func showTouchIdUI() { + touchIdContainer.isHidden = false } - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(input.frame.minX, input.frame.maxY + 10, input.frame.width, .borderSize)) + private func hideTouchIdUI() { + touchIdContainer.isHidden = true } + override func layout() { super.layout() + inputContainer.setFrameSize(200, 36) + input.setFrameSize(inputContainer.frame.width - 20, input.frame.height) + input.center() + + inputContainer.layer?.cornerRadius = inputContainer.frame.height / 2 logoutTextView.layout?.measure(width: frame.width - 40) logoutTextView.update(logoutTextView.layout) - photoView.center() - photoView.setFrameOrigin(photoView.frame.minX, photoView.frame.minY - floorToScreenPixels((20 + input.frame.height + 60)/2.0) - 20) - input.setFrameSize(200, input.frame.height) - nameView.centerX(y: photoView.frame.maxY + 20) - input.centerX(y: nameView.frame.minY + 30 + 20) - input.setFrameOrigin(input.frame.minX, input.frame.minY) + nameView.center() + nameView.centerX(y: nameView.frame.minY - floorToScreenPixels(backingScaleFactor, (20 + input.frame.height + 60)/2.0) - 20) logoutTextView.centerX(y:frame.height - logoutTextView.frame.height - 20) - setNeedsDisplayLayer() - nextButton.centerX(y: input.frame.maxY + 30) + inputContainer.centerX(y: nameView.frame.maxY + 30) + + touchIdContainer.centerX(y: inputContainer.frame.maxY + 20) + nextButton.setFrameOrigin(inputContainer.frame.maxX + 10, inputContainer.frame.minY + (inputContainer.frame.height - nextButton.frame.height) / 2) + + change(state: fieldState, animated: false) } required init?(coder: NSCoder) { @@ -185,38 +270,44 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { } class PasscodeLockController: ModalViewController { - private let account:Account - private var state: PasscodeViewState { - didSet { - self.genericView.changeInput(state) - } - } + let accountManager: AccountManager + private let useTouchId: Bool + private let appearanceDisposable = MetaDisposable() private let disposable:MetaDisposable = MetaDisposable() private let valueDisposable = MetaDisposable() private let logoutDisposable = MetaDisposable() - private var passcodeValues:[String] = [] private let _doneValue:Promise = Promise() - - var doneValue:Signal { + private let laContext = LAContext() + var doneValue:Signal { return _doneValue.get() } - private let logoutImpl:() -> Void - init(_ account:Account, _ state: PasscodeViewState, logoutImpl:@escaping()->Void = {}) { - self.account = account - self.state = state + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + } + private let updateCurrectController: ()->Void + private let logoutImpl:() -> Signal + init(_ accountManager: AccountManager, useTouchId: Bool, logoutImpl:@escaping()->Signal = { .complete() }, updateCurrectController: @escaping()->Void) { + self.accountManager = accountManager self.logoutImpl = logoutImpl - super.init(frame: NSMakeRect(0, 0, 340, 310)) + self.useTouchId = useTouchId + self.updateCurrectController = updateCurrectController + super.init(frame: NSMakeRect(0, 0, 350, 350)) self.bar = .init(height: 0) } + override var isVisualEffectBackground: Bool { + return false + } + override var isFullScreen: Bool { - switch state { - case .login: - return true - default: - return false - } + return true + } + + + + override var background: NSColor { + return self.containerBackground } private var genericView:PasscodeLockView { @@ -224,102 +315,104 @@ class PasscodeLockController: ModalViewController { } private func checkNextValue(_ passcode: String, _ current:String?) { - switch state { - case .login: - if current == passcode { - _doneValue.set(.single(true)) - close() - } else { - genericView.input.shake() - } - case let .enable(inner): - switch inner { - case .new: - passcodeValues.append(passcode) - self.state = .enable(.confirm) - case .confirm: - if passcodeValues[0] == passcode { - _doneValue.set(account.postbox.modify { modifier -> Bool in - modifier.setAccessChallengeData(.plaintextPassword(value: passcode, timeout: 60*60, attempts: nil)) - return true - }) - close() - } else { - genericView.input.shake() - } - default: - break - } - case let .disable(inner): - switch inner { - case .old: - if current == passcode { - _doneValue.set(account.postbox.modify { modifier -> Bool in - modifier.setAccessChallengeData(.none) - return true - }) - close() - } else { - genericView.input.shake() - } - default: - break - } - case let .change(inner): - switch inner { - case .new: - passcodeValues.append(passcode) - self.state = .change(.confirm) - case .confirm: - if passcodeValues[0] == passcode { - _doneValue.set(account.postbox.modify { modifier -> Bool in - modifier.setAccessChallengeData(.plaintextPassword(value: passcode, timeout: modifier.getAccessChallengeData().timeout, attempts: nil)) - return true - }) - close() - } else { - genericView.input.shake() - } - case .old: - if current != passcode { - genericView.input.shake() - } else { - self.state = .change(.new) + if current == passcode { + self._doneValue.set(.single(true)) + self.close() + + } else { + genericView.input.shake() + } + } + + override func windowDidBecomeKey() { + super.windowDidBecomeKey() + + if NSApp.isActive { + // callTouchId() + } + } + + override func windowDidResignKey() { + super.windowDidResignKey() + if !NSApp.isActive { + // invalidateTouchId() + } + } + + func callTouchId() { + if laContext.canUseBiometric { + laContext.evaluatePolicy(.applicationPolicy, localizedReason: tr(L10n.passcodeUnlockTouchIdReason)) { (success, evaluateError) in + if (success) { + Queue.mainQueue().async { + self._doneValue.set(.single(true)) + self.close() + } } } } } + func invalidateTouchId() { + laContext.invalidate() + } + + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.updateCurrectController() + } + override func viewDidLoad() { super.viewDidLoad() - genericView.logoutImpl = logoutImpl + appearanceDisposable.set(appearanceSignal.start(next: { [weak self] appearance in + self?.updateLocalizationAndTheme(theme: appearance.presentation) + })) - valueDisposable.set((genericView.value.get() |> mapToSignal { [weak self] value in - if let strongSelf = self { - return strongSelf.account.postbox.modify { modifier -> (String, String?) in - switch modifier.getAccessChallengeData() { - case .none: - return (value, nil) - case let .plaintextPassword(passcode, _, _), let .numericalPassword(passcode, _, _): - return (value, passcode) - } + genericView.logoutImpl = { [weak self] in + guard let window = self?.window else { return } + + confirm(for: window, information: L10n.accountConfirmLogoutText, successHandler: { [weak self] _ in + guard let `self` = self else { return } + + _ = showModalProgress(signal: self.logoutImpl(), for: window).start(completed: { [weak self] in + delay(0.2, closure: { [weak self] in + self?.close() + }) + }) + }) + + } + + genericView.useTouchIdImpl = { [weak self] in + self?.callTouchId() + } + + let accountManager = self.accountManager + + valueDisposable.set((genericView.value.get() |> mapToSignal { value in + return accountManager.transaction { transaction -> (String, String?) in + switch transaction.getAccessChallengeData() { + case .none: + return (value, nil) + case let .plaintextPassword(passcode), let .numericalPassword(passcode): + return (value, passcode) } } - return .single(("", nil)) } |> deliverOnMainQueue).start(next: { [weak self] value, current in self?.checkNextValue(value, current) })) - disposable.set((account.postbox.loadedPeerWithId(account.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.genericView.update(with: strongSelf.state, account: strongSelf.account, peer: peer) - strongSelf.readyOnce() - } - })) + genericView.update(hasTouchId: useTouchId) + readyOnce() + } + + override var closable: Bool { + return false + } override func escapeKeyAction() -> KeyHandlerResult { @@ -338,14 +431,27 @@ class PasscodeLockController: ModalViewController { } + override var containerBackground: NSColor { + return theme.colors.background.withAlphaComponent(1.0) + } + override var handleEvents: Bool { + return true + } + + override var handleAllEvents: Bool { + return true + } + + override var responderPriority: HandlerPriority { - return .modal + return .supreme } deinit { disposable.dispose() logoutDisposable.dispose() valueDisposable.dispose() + appearanceDisposable.dispose() self.window?.removeAllHandlers(for: self) } diff --git a/Telegram-Mac/PasscodeSettings.swift b/Telegram-Mac/PasscodeSettings.swift new file mode 100644 index 0000000000..b09b1c8012 --- /dev/null +++ b/Telegram-Mac/PasscodeSettings.swift @@ -0,0 +1,72 @@ +// +// PasscodeSettings.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit + + + +struct PasscodeSettings: PreferencesEntry, Equatable { + + let timeout: Int32? + + static var defaultValue: PasscodeSettings { + return PasscodeSettings(timeout: 60 * 5) + } + + init(timeout: Int32?) { + self.timeout = timeout + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let other = to as? PasscodeSettings { + return other == self + } else { + return false + } + } + + init(decoder: PostboxDecoder) { + self.timeout = decoder.decodeOptionalInt32ForKey("t") + } + + func encode(_ encoder: PostboxEncoder) { + if let timeout = self.timeout { + encoder.encodeInt32(timeout, forKey: "t") + } else { + encoder.encodeNil(forKey: "t") + } + } + + + func withUpdatedTimeout(_ timeout: Int32?) -> PasscodeSettings { + return PasscodeSettings(timeout: timeout) + } +} + + +func passcodeSettings(_ transaction: AccountManagerModifier) -> PasscodeSettings { + return transaction.getSharedData(ApplicationSharedPreferencesKeys.passcodeSettings) as? PasscodeSettings ?? PasscodeSettings.defaultValue +} + +func passcodeSettingsView(_ accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.passcodeSettings]) |> map { view in + return view.entries[ApplicationSharedPreferencesKeys.passcodeSettings] as? PasscodeSettings ?? PasscodeSettings.defaultValue + } +} + +func updatePasscodeSettings(_ accountManager: AccountManager, _ f: @escaping(PasscodeSettings) -> PasscodeSettings) -> Signal { + return accountManager.transaction { transaction in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.passcodeSettings, { entry in + let current = entry as? PasscodeSettings ?? PasscodeSettings.defaultValue + + return f(current) + }) + } |> ignoreValues +} diff --git a/Telegram-Mac/PasscodeSettingsViewController.swift b/Telegram-Mac/PasscodeSettingsViewController.swift index 4ac8c9e77b..61dde13ae3 100644 --- a/Telegram-Mac/PasscodeSettingsViewController.swift +++ b/Telegram-Mac/PasscodeSettingsViewController.swift @@ -8,17 +8,20 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import LocalAuthentication private enum PasscodeEntry : Comparable, Identifiable { - case turnOn(sectionId:Int) - case turnOff(sectionId:Int) - case turnOnDescription(sectionId:Int) - case turnOffDescription(sectionId:Int) - case change(sectionId:Int) - case autoLock(sectionId:Int, time:Int32?) + case turnOn(sectionId:Int, viewType: GeneralViewType) + case turnOff(sectionId:Int, current: String, viewType: GeneralViewType) + case turnOnDescription(sectionId:Int, viewType: GeneralViewType) + case turnOffDescription(sectionId:Int, viewType: GeneralViewType) + case change(sectionId:Int, current: String, viewType: GeneralViewType) + case autoLock(sectionId:Int, time:Int32?, viewType: GeneralViewType) + case turnTouchId(sectionId:Int, enabled: Bool, viewType: GeneralViewType) case section(sectionId:Int) var stableId:Int { @@ -35,6 +38,8 @@ private enum PasscodeEntry : Comparable, Identifiable { return 4 case .autoLock: return 5 + case .turnTouchId: + return 6 case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } @@ -42,17 +47,19 @@ private enum PasscodeEntry : Comparable, Identifiable { var stableIndex:Int { switch self { - case let .turnOn(sectionId): + case let .turnOn(sectionId, _): return (sectionId * 1000) + stableId - case let .turnOff(sectionId): + case let .turnOff(sectionId, _, _): return (sectionId * 1000) + stableId - case let .turnOnDescription(sectionId): + case let .turnOnDescription(sectionId, _): return (sectionId * 1000) + stableId - case let .turnOffDescription(sectionId): + case let .turnOffDescription(sectionId, _): return (sectionId * 1000) + stableId - case let .change(sectionId): + case let .change(sectionId, _, _): return (sectionId * 1000) + stableId - case let .autoLock(sectionId, _): + case let .autoLock(sectionId, _, _): + return (sectionId * 1000) + stableId + case let .turnTouchId(sectionId, _, _): return (sectionId * 1000) + stableId case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -64,54 +71,9 @@ private func <(lhs:PasscodeEntry, rhs:PasscodeEntry) -> Bool { return lhs.stableIndex < rhs.stableIndex } -private func ==(lhs:PasscodeEntry, rhs:PasscodeEntry) -> Bool { - switch lhs { - case let .turnOn(sectionId): - if case .turnOn(sectionId) = rhs { - return true - } else { - return false - } - case let .turnOff(sectionId): - if case .turnOff(sectionId) = rhs { - return true - } else { - return false - } - case let .turnOnDescription(sectionId): - if case .turnOnDescription(sectionId) = rhs { - return true - } else { - return false - } - case let .turnOffDescription(sectionId): - if case .turnOffDescription(sectionId) = rhs { - return true - } else { - return false - } - case let .change(sectionId): - if case .change(sectionId) = rhs { - return true - } else { - return false - } - case let .autoLock(lhsSectionId, lhsTime): - if case let .autoLock(rhsSectionId, rhsTime) = rhs { - return lhsSectionId == rhsSectionId && lhsTime == rhsTime - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } -} -private func passcodeSettinsEntry(_ passcode: PostboxAccessChallengeData) -> [PasscodeEntry] { + +private func passcodeSettinsEntry(_ passcode: PostboxAccessChallengeData, passcodeSettings: PasscodeSettings, _ additional: AdditionalSettings) -> [PasscodeEntry] { var entries:[PasscodeEntry] = [] var sectionId:Int = 1 @@ -120,19 +82,27 @@ private func passcodeSettinsEntry(_ passcode: PostboxAccessChallengeData) -> [Pa switch passcode { case .none: - entries.append(.turnOn(sectionId: sectionId)) - entries.append(.turnOnDescription(sectionId: sectionId)) - case .plaintextPassword, .numericalPassword: - entries.append(.turnOff(sectionId: sectionId)) - entries.append(.change(sectionId: sectionId)) - entries.append(.turnOffDescription(sectionId: sectionId)) + entries.append(.turnOn(sectionId: sectionId, viewType: .singleItem)) + entries.append(.turnOnDescription(sectionId: sectionId, viewType: .textBottomItem)) + case let .plaintextPassword(value), let .numericalPassword(value): + entries.append(.turnOff(sectionId: sectionId, current: value, viewType: .firstItem)) + entries.append(.change(sectionId: sectionId, current: value, viewType: .lastItem)) + entries.append(.turnOffDescription(sectionId: sectionId, viewType: .textBottomItem)) entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.autoLock(sectionId: sectionId, time: passcode.timeout)) + let context = LAContext() + entries.append(.autoLock(sectionId: sectionId, time: passcodeSettings.timeout, viewType: context.canUseBiometric ? .firstItem : .singleItem)) + if context.canUseBiometric { + entries.append(.turnTouchId(sectionId: sectionId, enabled: additional.useTouchId, viewType: .lastItem)) + } + } + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + return entries } @@ -144,41 +114,43 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry], let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in switch entry.entry { case .section: - return GeneralRowItem(initialSize, height: 20, stableId: entry.stableId) - case .turnOn: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.passcodeTurnOn), nameStyle: actionStyle, type: .none, action: { + return GeneralRowItem(initialSize, height: 30, stableId: entry.stableId, viewType: .separator) + case let .turnOn(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(L10n.passcodeTurnOn), nameStyle: actionStyle, type: .none, viewType: viewType, action: { arguments.turnOn() }) - case .turnOff: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.passcodeTurnOff), nameStyle: actionStyle, type: .none, action: { - arguments.turnOff() + case let .turnOff(_, current, viewType): + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(L10n.passcodeTurnOff), nameStyle: actionStyle, type: .none, viewType: viewType, action: { + arguments.turnOff(current) }) - case .change: - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.passcodeChange), nameStyle: actionStyle, type: .none, action: { - arguments.change() + case let .change(_, current, viewType): + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(L10n.passcodeChange), nameStyle: actionStyle, type: .none, viewType: viewType, action: { + arguments.change(current) + }) + case let .turnOnDescription(_, viewType), let .turnOffDescription(_, viewType): + return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: L10n.passcodeControllerText, viewType: viewType) + case let .turnTouchId(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(L10n.passcodeUseTouchId), type: .switchable(enabled), viewType: viewType, action: { + arguments.toggleTouchId(!enabled) }) - case .turnOnDescription, .turnOffDescription: - return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: tr(.passcodeTurnOnDescription)) - case let .autoLock(sectionId: _, time: time): + case let .autoLock(sectionId: _, time, viewType): var text:String if let time = time { if time < 60 { - text = tr(.timerSecondsCountable(Int(time))) - } else if time <= 60 * 60 { - text = tr(.timerMinutesCountable(Int(time / 60))) - } else if time <= 60 * 60 * 24 { - text = tr(.timerHoursCountable(Int(time / 60) / 60)) + text = tr(L10n.timerSecondsCountable(Int(time))) + } else if time < 60 * 60 { + text = tr(L10n.timerMinutesCountable(Int(time / 60))) + } else if time < 60 * 60 * 24 { + text = tr(L10n.timerHoursCountable(Int(time / 60) / 60)) } else { - text = tr(.timerDaysCountable(Int(time / 60) / 60 / 24)) + text = tr(L10n.timerDaysCountable(Int(time / 60) / 60 / 24)) } - text = tr(.passcodeAutoLockIfAway(text)) + text = tr(L10n.passcodeAutoLockIfAway(text)) } else { - text = tr(.passcodeAutoLockDisabled) + text = tr(L10n.passcodeAutoLockDisabled) } - return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: tr(.passcodeAutolock), type: .context { - return text - }, action: { + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: L10n.passcodeAutolock, type: .context(text), viewType: viewType, action: { arguments.ifAway() }) } @@ -189,43 +161,37 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry], private final class PasscodeSettingsArguments { - let account:Account + let context: AccountContext let turnOn:()->Void - let turnOff:()->Void - let change:()->Void + let turnOff:(String)->Void + let change:(String)->Void let ifAway:()->Void - init(_ account:Account, turnOn: @escaping()->Void, turnOff: @escaping()->Void, change:@escaping()->Void, ifAway: @escaping()-> Void) { - self.account = account + let toggleTouchId:(Bool)->Void + init(_ context: AccountContext, turnOn: @escaping()->Void, turnOff: @escaping(String)->Void, change:@escaping(String)->Void, ifAway: @escaping()-> Void, toggleTouchId:@escaping(Bool)->Void) { + self.context = context self.turnOn = turnOn self.turnOff = turnOff self.change = change self.ifAway = ifAway + self.toggleTouchId = toggleTouchId } } class PasscodeSettingsViewController: TableViewController { - private let actionUpdate:Promise = Promise(false) + private let disposable = MetaDisposable() + private func show(mode: PasscodeMode) { + self.navigationController?.push(PasscodeController(sharedContext: context.sharedContext, mode: mode)) + } - private func show(with state: PasscodeViewState) { - let controller = PasscodeLockController(account, state) - actionUpdate.set(controller.doneValue) - showModal(with: controller, for: mainWindow) + deinit { + disposable.dispose() } func updateAwayTimeout(_ timeout:Int32?) { - self.actionUpdate.set(account.postbox.modify { modifier -> Bool in - - switch modifier.getAccessChallengeData() { - case .none: - break - case let .numericalPassword(passcode, _, attempts): - modifier.setAccessChallengeData(.numericalPassword(value: passcode, timeout: timeout, attempts: attempts)) - case let .plaintextPassword(passcode, _, attempts): - modifier.setAccessChallengeData(.plaintextPassword(value: passcode, timeout: timeout, attempts: attempts)) - } - return true - }) + disposable.set(updatePasscodeSettings(context.sharedContext.accountManager, { + $0.withUpdatedTimeout(timeout) + }).start()) } func showIfAwayOptions() { @@ -233,49 +199,46 @@ class PasscodeSettingsViewController: TableViewController { var items:[SPopoverItem] = [] - items.append(SPopoverItem(tr(.passcodeAutoLockDisabled), { [weak self] in + items.append(SPopoverItem(tr(L10n.passcodeAutoLockDisabled), { [weak self] in self?.updateAwayTimeout(nil) })) - if isDebug { - // - items.append(SPopoverItem(tr(.passcodeAutoLockIfAway(tr(.timerSecondsCountable(5)))), { [weak self] in - self?.updateAwayTimeout(5) - })) - } - - items.append(SPopoverItem(tr(.passcodeAutoLockIfAway(tr(.timerMinutesCountable(1)))), { [weak self] in + items.append(SPopoverItem(tr(L10n.passcodeAutoLockIfAway(tr(L10n.timerMinutesCountable(1)))), { [weak self] in self?.updateAwayTimeout(60) })) - items.append(SPopoverItem(tr(.passcodeAutoLockIfAway(tr(.timerMinutesCountable(5)))), { [weak self] in + items.append(SPopoverItem(tr(L10n.passcodeAutoLockIfAway(tr(L10n.timerMinutesCountable(5)))), { [weak self] in self?.updateAwayTimeout(60 * 5) })) - items.append(SPopoverItem(tr(.passcodeAutoLockIfAway(tr(.timerHoursCountable(1)))), { [weak self] in + items.append(SPopoverItem(tr(L10n.passcodeAutoLockIfAway(tr(L10n.timerHoursCountable(1)))), { [weak self] in self?.updateAwayTimeout(60 * 60) })) - items.append(SPopoverItem(tr(.passcodeAutoLockIfAway(tr(.timerHoursCountable(5)))), { [weak self] in + items.append(SPopoverItem(tr(L10n.passcodeAutoLockIfAway(tr(L10n.timerHoursCountable(5)))), { [weak self] in self?.updateAwayTimeout(60 * 60 * 5) })) - showPopover(for: view, with: SPopoverViewController(items: items), edge: .minX, inset: NSMakePoint(0, -25)) + showPopover(for: view, with: SPopoverViewController(items: items, visibility: items.count), edge: .minX, inset: NSMakePoint(0, -25)) } } override func viewDidLoad() { super.viewDidLoad() - let account = self.account - let arguments = PasscodeSettingsArguments(account, turnOn: { [weak self] in - self?.show(with: .enable(.new)) - }, turnOff: { [weak self] in - self?.show(with: .disable(.old)) - }, change: { [weak self] in - self?.show(with: .change(.old)) + let context = self.context + let arguments = PasscodeSettingsArguments(context, turnOn: { [weak self] in + self?.show(mode: .install) + }, turnOff: { [weak self] current in + self?.show(mode: .disable(current)) + }, change: { [weak self] current in + self?.show(mode: .change(current)) }, ifAway: { [weak self] in self?.showIfAwayOptions() + }, toggleTouchId: { enabled in + _ = updateAdditionalSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> AdditionalSettings in + return current.withUpdatedTouchId(enabled) + }).start() }) let initialSize = self.atomicSize.modify({$0}) @@ -283,12 +246,8 @@ class PasscodeSettingsViewController: TableViewController { let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - genericView.merge(with: combineLatest(actionUpdate.get() |> mapToSignal { _ in - return account.postbox.modify { modifier -> PostboxAccessChallengeData in - return modifier.getAccessChallengeData() - } - } |> deliverOn(prepareQueue), appearanceSignal |> deliverOn(prepareQueue)) |> map { passcode, appearance in - let entries = passcodeSettinsEntry(passcode).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + genericView.merge(with: combineLatest(queue: prepareQueue, context.sharedContext.accountManager.accessChallengeData(), passcodeSettingsView(context.sharedContext.accountManager), appearanceSignal, additionalSettings(accountManager: context.sharedContext.accountManager)) |> map { passcode, passcodeSettings, appearance, additional in + let entries = passcodeSettinsEntry(passcode.data, passcodeSettings: passcodeSettings, additional).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize, arguments: arguments) } |> deliverOnMainQueue) diff --git a/Telegram-Mac/PassportAcceptRowItem.swift b/Telegram-Mac/PassportAcceptRowItem.swift new file mode 100644 index 0000000000..f96e4724d0 --- /dev/null +++ b/Telegram-Mac/PassportAcceptRowItem.swift @@ -0,0 +1,67 @@ +// +// PassportAcceptRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class PassportAcceptRowItem: GeneralRowItem { + + init(_ initialSize: NSSize, stableId: AnyHashable, enabled: Bool, action:@escaping()->Void) { + super.init(initialSize, height: 50, stableId: stableId, action: action, enabled: enabled) + } + + + override func viewClass() -> AnyClass { + return PassportAcceptRowView.self + } +} + +final class PassportAcceptRowView : TableRowView { + private let button: TitleButton = TitleButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(button) + button.set(font: .medium(.header), for: .Normal) + + button.set(handler: { [weak self] _ in + guard let item = self?.item as? GeneralRowItem else {return} + item.action() + }, for: .Click) + } + + override func layout() { + super.layout() + guard let item = item as? GeneralRowItem else {return} + + _ = button.sizeToFit(NSZeroSize, NSMakeSize(170, 40), thatFit: true)//.setFrameSize(NSMakeSize(frame.width - item.inset.left - item.inset.right, 40)) + button.center() + button.layer?.cornerRadius = 20 + } + + override func updateColors() { + super.updateColors() + button.set(text: L10n.secureIdRequestAccept, for: .Normal) + button.set(color: .white, for: .Normal) + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? GeneralRowItem else {return} + + // button.userInteractionEnabled = item.enabled + button.autohighlight = false + button.set(background: item.enabled ? theme.colors.accent : theme.colors.grayForeground, for: .Normal) + button.set(image: theme.icons.secureIdAuth, for: .Normal) + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PassportController.swift b/Telegram-Mac/PassportController.swift new file mode 100644 index 0000000000..f332a89dca --- /dev/null +++ b/Telegram-Mac/PassportController.swift @@ -0,0 +1,4088 @@ + // +// PassportController.swift +// Telegram +// +// Created by Mikhail Filimonov on 20/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + + + +private let _id_street1 = InputDataIdentifier("street_line1") +private let _id_street2 = InputDataIdentifier("street_line2") +private let _id_postcode = InputDataIdentifier("post_code") +private let _id_city = InputDataIdentifier("city") +private let _id_state = InputDataIdentifier("state") + +private let _id_delete = InputDataIdentifier("delete") + +private let _id_first_name = InputDataIdentifier("first_name") +private let _id_middle_name = InputDataIdentifier("middle_name") +private let _id_last_name = InputDataIdentifier("last_name") + + private let _id_first_name_native = InputDataIdentifier("first_name_native") + private let _id_middle_name_native = InputDataIdentifier("middle_name_native") + private let _id_last_name_native = InputDataIdentifier("last_name_native") + +private let _id_birthday = InputDataIdentifier("birth_date") +private let _id_issue_date = InputDataIdentifier("issue_date") +private let _id_expire_date = InputDataIdentifier("expiry_date") +private let _id_identifier = InputDataIdentifier("document_no") + +private let _id_country = InputDataIdentifier("country_code") +private let _id_residence = InputDataIdentifier("residence_country_code") +private let _id_gender = InputDataIdentifier("gender") + +private let _id_email_code = InputDataIdentifier("email_code") +private let _id_email_new = InputDataIdentifier("email_new") +private let _id_email_def = InputDataIdentifier("email_default") + +private let _id_phone_code = InputDataIdentifier("_id_phone_code") +private let _id_phone_new = InputDataIdentifier("_id_phone_new") +private let _id_phone_def = InputDataIdentifier("_id_phone_def") + +private let _id_c_password = InputDataIdentifier("create_password") +private let _id_c_repassword = InputDataIdentifier("create_re_password") +private let _id_c_email = InputDataIdentifier("create_email") +private let _id_c_hint = InputDataIdentifier("hint") + +private let _id_selfie = InputDataIdentifier("selfie") +private let _id_selfie_scan = InputDataIdentifier("selfie_scan") + +private let _id_scan = InputDataIdentifier("scan") +private let _id_translation = InputDataIdentifier("translation") + +private let _id_frontside = InputDataIdentifier("front_side") +private let _id_backside = InputDataIdentifier("reverse_side") + +private extension SecureIdVerificationDocument { + var errorIdentifier: InputDataIdentifier { + switch self { + case let .remote(file): + let hash = file.fileHash.base64EncodedString() + return InputDataIdentifier("file_\(hash)") + default: + return InputDataIdentifier("\(arc4random())") + } + } +} + + +private let cManager: CountryManager = CountryManager() + +private struct EditSettingsValues { + let values:[SecureIdValue] + func hasValue(_ relative: SecureIdRequestedFormFieldValue) -> Bool { + return values.contains(where: {$0.key == relative.valueKey}) + } +} + +private func updateFrontMrz(file: String, relative: SecureIdRequestedFormFieldValue, updateState: @escaping ((PassportState)->PassportState)->Void) { + if let image = NSImage(contentsOfFile: file) { + let string = recognizeMRZ(image.precomposed(), nil) + let mrz = TGPassportMRZ.parseLines(string?.components(separatedBy: "\n")) + let localFile:SecureIdVerificationDocument = .local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: file, randomId: arc4random64()), state: .uploading(0))) + + updateState { current in + var current = current + if let mrz = mrz { + if relative.isEqualToMRZ(mrz) { + + let filedata = try! String(contentsOfFile: Bundle.main.path(forResource: "countries", ofType: nil)!).components(separatedBy: "\n") + + var citizenship: InputDataValue? = current.detailsIntermediateState?.citizenship + var residence: InputDataValue? = current.detailsIntermediateState?.residence + + for line in filedata { + let country = line.components(separatedBy: ";") + if let symbols = country.last?.components(separatedBy: ",") { + if symbols.contains(mrz.nationality) && citizenship == nil { + citizenship = .string(country[1]) + } + if symbols.contains(mrz.issuingCountry) && residence == nil { + residence = .string(country[1]) + } + } + } + + + let expiryDate = dateFormatter.string(from: mrz.expiryDate).components(separatedBy: ".").map({Int32($0)}) + let birthDate = dateFormatter.string(from: mrz.birthDate).components(separatedBy: ".").map({Int32($0)}) + let details = DetailsIntermediateState(firstName: .string(mrz.firstName.lowercased().capitalizingFirstLetter()), middleName: nil, lastName: .string(mrz.lastName.lowercased().capitalizingFirstLetter()), firstNameNative: nil, middleNameNative: nil, lastNameNative: nil, birthday: .date(birthDate[0], birthDate[1], birthDate[2]), citizenship: citizenship, residence: residence, gender: .gender(SecureIdGender.gender(from: mrz)), expiryDate: .date(expiryDate[0], expiryDate[1], expiryDate[2]), identifier: .string(mrz.documentNumber)) + current = current.withUpdatedDetailsState(details) + } + } + return current.withUpdatedFrontSide(localFile, for: relative.valueKey) + } + } +} + + private struct FieldRequest : Hashable { + let primary: SecureIdRequestedFormFieldValue + let secondary: SecureIdRequestedFormFieldValue? + let fillPrimary: Bool + init(_ primary: SecureIdRequestedFormFieldValue, _ secondary: SecureIdRequestedFormFieldValue? = nil, _ fillPrimary: Bool = true) { + self.primary = primary + self.secondary = secondary + self.fillPrimary = fillPrimary + } + + var hashValue: Int { + return 0 + } + + static func ==(lhs: FieldRequest, rhs: FieldRequest) -> Bool { + return lhs.primary == rhs.primary && lhs.secondary == rhs.secondary + } + } + +private final class PassportArguments { + let context: AccountContext + let checkPassword:((String, ()->Void))->Void + let requestField:(FieldRequest, SecureIdValue?, SecureIdValue?, [SecureIdRequestedFormFieldValue], EditSettingsValues?)->Void + let createPassword: ()->Void + let abortVerification: ()-> Void + let authorize:(Bool)->Void + let botPrivacy:()->Void + let forgotPassword:()->Void + let deletePassport:()->Void + let updateEmailCode: ()-> Void + init(context: AccountContext, checkPassword:@escaping((String, ()->Void))->Void, requestField:@escaping(FieldRequest, SecureIdValue?, SecureIdValue?, [SecureIdRequestedFormFieldValue], EditSettingsValues?)->Void, createPassword: @escaping()->Void, abortVerification: @escaping()->Void, authorize: @escaping(Bool)->Void, botPrivacy:@escaping()->Void, forgotPassword: @escaping()->Void, deletePassport:@escaping()->Void, updateEmailCode: @escaping()-> Void) { + self.context = context + self.checkPassword = checkPassword + self.requestField = requestField + self.createPassword = createPassword + self.abortVerification = abortVerification + self.botPrivacy = botPrivacy + self.authorize = authorize + self.forgotPassword = forgotPassword + self.deletePassport = deletePassport + self.updateEmailCode = updateEmailCode + } +} + + + +private enum PassportEntryId : Hashable { + case header + case loading + case sectionId(Int32) + case emptyFieldId(FieldRequest) + case filledFieldId(FieldRequest) + case savedFieldId(SecureIdValueKey) + case description(Int32) + case accept + case requestPassword + case createPassword + case deletePassport + case settingsHeader + case inputEmailCode + var hashValue: Int { + return 0 + } +} + +private let scansLimit: Int = 20 + +private struct FieldDescription : Equatable { + let text: String + let isError: Bool + init(text: String, isError: Bool = false) { + self.text = text + self.isError = isError + } +} + +private enum PassportEntry : TableItemListNodeEntry { + case header(sectionId: Int32, index: Int32, requestedFields: [SecureIdRequestedFormField], peer: Peer) + case accept(sectionId: Int32, index: Int32, enabled: Bool) + case emptyField(sectionId: Int32, index: Int32, fieldType: FieldRequest, value: SecureIdValue?, relativeValue: SecureIdValue?, relative: [SecureIdRequestedFormFieldValue], title: String, desc: String, isError: Bool) + case filledField(sectionId: Int32, index: Int32, fieldType: FieldRequest, relative: [SecureIdRequestedFormFieldValue], title: String, description: FieldDescription, value: SecureIdValue?, relativeValue: SecureIdValue?) + case savedField(sectionId: Int32, index: Int32, valueType: SecureIdValueKey, relative: [SecureIdValueKey], relativeValues: [SecureIdValue], title: String, description: String) + case description(sectionId: Int32, index: Int32, text: String, detectBold: Bool) + case inputEmailCode(sectionId: Int32, index: Int32, text: String, limit: Int32?, error: InputDataValueError?) + case settingsHeader(sectionId: Int32, index: Int32) + case requestPassword(sectionId: Int32, index: Int32, hasRecoveryEmail: Bool, isSettings: Bool, error: String?) + case createPassword(sectionId: Int32, index: Int32, peer: Peer) + case loading + case deletePassport(sectionId: Int32, index: Int32) + case sectionId(Int32) + + var stableId: PassportEntryId { + switch self { + case .header: + return .header + case let .emptyField(_, _, fieldType, _, _, _, _, _, _): + return .emptyFieldId(fieldType) + case let .filledField(_, _, fieldType, _, _, _, _, _): + return .filledFieldId(fieldType) + case let .savedField(_, _, type, _, _, _, _): + return .savedFieldId(type) + case let .description(_, index, _, _): + return .description(index) + case .accept: + return .accept + case .loading: + return .loading + case .requestPassword: + return .requestPassword + case .createPassword: + return .createPassword + case .inputEmailCode: + return .inputEmailCode + case .deletePassport: + return .deletePassport + case .settingsHeader: + return .settingsHeader + case .sectionId(let id): + return .sectionId(id) + } + } + + var index: Int32 { + switch self { + case let .header(section, index, _, _): + return (section * 1000) + index + case let .emptyField(section, index, _, _, _, _, _, _, _): + return (section * 1000) + index + case let .accept(section, index, _): + return (section * 1000) + index + case let .filledField(section, index, _, _, _, _, _, _): + return (section * 1000) + index + case let .savedField(section, index, _, _, _, _, _): + return (section * 1000) + index + case let .description(section, index, _, _): + return (section * 1000) + index + case let .requestPassword(section, index, _, _, _): + return (section * 1000) + index + case let .createPassword(section, index, _): + return (section * 1000) + index + case let .inputEmailCode(section, index, _, _, _): + return (section * 1000) + index + case let .deletePassport(section, index): + return (section * 1000) + index + case let .settingsHeader(section, index): + return (section * 1000) + index + case .loading: + return 0 + case let .sectionId(section): + return (section + 1) * 1000 - section + } + } + + func item(_ arguments: PassportArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .header(_, _, requestedFields, peer): + return PassportHeaderItem(initialSize, account: arguments.context.account, stableId: stableId, requestedFields: requestedFields, peer: peer) + case let .emptyField(_, _, fieldType, value, relativeValue, relative, title, desc, isError): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, description: desc, descTextColor: isError ? theme.colors.redUI : theme.colors.grayText, type: .none, action: { + arguments.requestField(fieldType, value, relativeValue, relative, nil) + }) + case let .accept(_, _, enabled): + return PassportAcceptRowItem(initialSize, stableId: stableId, enabled: enabled, action: { + arguments.authorize(enabled) + }) + case let .filledField(_, _, fieldType, relative, title, description, value, relativeValue): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, description: description.text, descTextColor: description.isError ? theme.colors.redUI : theme.colors.grayText, type: .selectable(!description.isError), action: { + arguments.requestField(fieldType, value, relativeValue, relative, nil) + }) + case let .savedField(_, _, fieldType, relative, relativeValues, title, description): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, description: description, type: .next, action: { + arguments.requestField(FieldRequest(fieldType.requestFieldType), relativeValues.first(where: {$0.key == fieldType}), nil, relative.map({$0.requestFieldType}), EditSettingsValues(values: relativeValues)) + }) + case .requestPassword(_, _, let hasRecoveryEmail, let isSettings, let error): + return PassportInsertPasswordItem(initialSize, stableId: stableId, checkPasswordAction: arguments.checkPassword, forgotPassword: arguments.forgotPassword, hasRecoveryEmail: hasRecoveryEmail, isSettings: isSettings, error: error) + case .createPassword(_, _, let peer): + return PassportTwoStepVerificationIntroItem(initialSize, stableId: stableId, peer: peer, action: arguments.createPassword) + case let .inputEmailCode(_, _, text, limit, error): + return InputDataRowItem(initialSize, stableId: stableId, mode: .plain, error: error, currentText: text, placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, updated: { _ in + arguments.updateEmailCode() + }, limit: limit ?? 255) + case let .description(_, _, text, detectBold): + return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: { link in + switch link { + case "abortVerification": + arguments.abortVerification() + case "_applyPolicy_": + arguments.botPrivacy() + default: + break + } + }), detectBold: detectBold, inset: NSEdgeInsets(left: 30.0, right: 30.0, top: 10, bottom:2)) + case .deletePassport: + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.secureIdDeletePassport, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.redUI), type: .none, action: { + arguments.deletePassport() + }) + case .settingsHeader: + return PassportSettingsHeaderItem(initialSize, stableId: stableId) + case .loading: + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) + case .sectionId: + return GeneralRowItem(initialSize, height: 20, stableId: stableId) + } + } +} + +private func <(lhs: PassportEntry, rhs: PassportEntry) -> Bool { + return lhs.index < rhs.index +} + +private func ==(lhs: PassportEntry, rhs: PassportEntry) -> Bool { + switch lhs { + case let .header(lhsSectionId, lhsIndex, lhsRequestedFields, lhsPeer): + if case let .header(rhsSectionId, rhsIndex, rhsRequestedFields, rhsPeer) = rhs { + return lhsSectionId == rhsSectionId && lhsIndex == rhsIndex && lhsRequestedFields == rhsRequestedFields && lhsPeer.isEqual(rhsPeer) + } else { + return false + } + case let .accept(sectionId, index, enabled): + if case .accept(sectionId, index, enabled) = rhs { + return true + } else { + return false + } + case let .emptyField(sectionId, index, fieldType, lhsValue, lhsRelativeValue, lhsRelative, title, desc, isError): + if case .emptyField(sectionId, index, fieldType, let rhsValue, let rhsRelativeValue, let rhsRelative, title, desc, isError) = rhs { + return lhsRelative == rhsRelative && lhsValue == rhsValue && lhsRelativeValue == rhsRelativeValue + } else { + return false + } + case let .filledField(sectionId, index, fieldType, lhsRelative, title, description, lhsValue, lhsRelativeValue): + if case .filledField(sectionId, index, fieldType, let rhsRelative, title, description, let rhsValue, let rhsRelativeValue) = rhs { + return lhsValue == rhsValue && lhsRelative == rhsRelative && lhsRelativeValue == rhsRelativeValue + } else { + return false + } + case let .savedField(sectionId, index, fieldType, relative, relativeValues, title, description): + if case .savedField(sectionId, index, fieldType, relative, relativeValues, title, description) = rhs { + return true + } else { + return false + } + case let .description(sectionId, index, text, detectBold): + if case .description(sectionId, index, text, detectBold) = rhs { + return true + } else { + return false + } + case let .inputEmailCode(sectionId, index, text, limit, error): + if case .inputEmailCode(sectionId, index, text, limit, error) = rhs { + return true + } else { + return false + } + case let .requestPassword(sectionId, index, hasRecoveryEmail, isSettings, lhsError): + if case .requestPassword(sectionId, index, hasRecoveryEmail, isSettings, let rhsError) = rhs { + return lhsError == rhsError + } else { + return false + } + case let .createPassword(sectionId, index, lhsPeer): + if case .createPassword(sectionId, index, let rhsPeer) = rhs { + return lhsPeer.isEqual(rhsPeer) + } else { + return false + } + case let .deletePassport(sectionId, index): + if case .deletePassport(sectionId, index) = rhs { + return true + } else { + return false + } + case let .settingsHeader(sectionId, index): + if case .settingsHeader(sectionId, index) = rhs { + return true + } else { + return false + } + case .loading: + if case .loading = rhs { + return true + } else { + return false + } + case let .sectionId(id): + if case .sectionId(id) = rhs { + return true + } else { + return false + } + } +} + +private func passportEntries(encryptedForm: EncryptedSecureIdForm?, form: SecureIdForm?, peer: Peer?, passwordData: TwoStepVerificationConfiguration?, state: PassportState) -> ([PassportEntry], Bool) { + var entries:[PassportEntry] = [] + + var enabled: Bool = false + + + if let _ = passwordData { + var sectionId: Int32 = 0 + var index: Int32 = 0 + + if state.viewState == .settings { + entries.append(.sectionId(sectionId)) + sectionId += 1 + + let pdKeys:[SecureIdValueKey] = [.personalDetails, .idCard, .passport, .driversLicense, .internalPassport] + let pdValues:[SecureIdValue] = state.values.map{$0.value}.filter{pdKeys.contains($0.key)} + let pdDesc = pdValues.map({$0.key.requestFieldType.rawValue}).joined(separator: ", ") + entries.append(.savedField(sectionId: sectionId, index: index, valueType: .personalDetails, relative: pdKeys, relativeValues: pdValues, title: L10n.secureIdIdentityDocument, description: pdDesc.isEmpty ? L10n.secureIdRequestPermissionIdentityEmpty : pdDesc)) + index += 1 + + let aKeys:[SecureIdValueKey] = [.address, .rentalAgreement, .utilityBill, .bankStatement, .passportRegistration, .temporaryRegistration] + // + let aValues:[SecureIdValue] = state.values.map{$0.value}.filter{aKeys.contains($0.key)} + let aDesc = aValues.map({$0.key.requestFieldType.rawValue}).joined(separator: ", ") + entries.append(.savedField(sectionId: sectionId, index: index, valueType: .address, relative: aKeys, relativeValues: aValues, title: L10n.secureIdResidentialAddress, description: aDesc.isEmpty ? L10n.secureIdRequestPermissionAddressEmpty : aDesc)) + index += 1 + + var pValue: String? = nil + var eValue: String? = nil + + if let value = state.values.filter({$0.value.key == .phone}).first { + switch value.value { + case let .phone(value): + pValue = formatPhoneNumber(value.phone) + default: + break + } + } + if let value = state.values.filter({$0.value.key == .email}).first { + switch value.value { + case let .email(value): + eValue = value.email + default: + break + } + } + + entries.append(.savedField(sectionId: sectionId, index: index, valueType: .phone, relative: [], relativeValues: state.values.filter{$0.value.key == .phone}.map {$0.value}, title: L10n.secureIdPhoneNumber, description: pValue ?? L10n.secureIdRequestPermissionPhoneEmpty)) + index += 1 + + + entries.append(.savedField(sectionId: sectionId, index: index, valueType: .email, relative: [], relativeValues: state.values.filter{$0.value.key == .email}.map {$0.value}, title: L10n.secureIdEmail, description: eValue ?? L10n.secureIdRequestPermissionEmailEmpty)) + index += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + if !state.values.isEmpty { + entries.append(.deletePassport(sectionId: sectionId, index: index)) + index += 1 + } + + } else if state.passwordSettings == nil { + if let passwordData = passwordData { + switch passwordData { + case let .notSet(pendingEmail): + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + if let pendingEmail = pendingEmail { + + entries.append(.inputEmailCode(sectionId: sectionId, index: index, text: state.emailCode, limit: pendingEmail.codeLength, error: state.emailCodeError)) + + + let emailText = L10n.twoStepAuthConfirmationTextNew + "\n\n\(pendingEmail.pattern)\n\n[" + L10n.twoStepAuthConfirmationAbort + "](abortVerification)" + entries.append(.description(sectionId: sectionId, index: index, text: emailText, detectBold: false)) + index += 1 + } else { + if let peer = peer { + entries.append(.createPassword(sectionId: sectionId, index: index, peer: peer)) + index += 1 + } + } + case let .set(_, hasRecoveryEmail, _, _): + + if state.tmpPwd == nil { + if let peer = peer, let form = encryptedForm { + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.header(sectionId: sectionId, index: index, requestedFields: form.requestedFields, peer: peer)) + index += 1 + } + + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + if encryptedForm == nil { + entries.append(.sectionId(sectionId)) + sectionId += 1 + entries.append(.settingsHeader(sectionId: sectionId, index: index)) + index += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + entries.append(.sectionId(sectionId)) + sectionId += 1 + } + + entries.append(.sectionId(sectionId)) + sectionId += 1 + entries.append(.sectionId(sectionId)) + sectionId += 1 + + + + entries.append(.requestPassword(sectionId: sectionId, index: index, hasRecoveryEmail: hasRecoveryEmail, isSettings: encryptedForm == nil, error: state.passwordError)) + index += 1 + } else { + entries.append(.loading) + } + + } + } + + } else if let form = form, let peer = peer { + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.header(sectionId: sectionId, index: index, requestedFields: form.requestedFields, peer: peer)) + index += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + entries.append(.description(sectionId: sectionId, index: index, text: L10n.secureIdRequestedInformationHeader, detectBold: true)) + index += 1 + + var filledCount: Int32 = 0 + + + let requestedFields = form.requestedFields.map { value -> SecureIdRequestedFormField in + switch value { + case let .just(key): + switch key { + case .email, .phone, .personalDetails, .address: + return value + default: + return .oneOf([key]) + } + default: + return value + } + } + + let idCount = requestedFields.filter { $0.isIdentityField }.count + let adCount = requestedFields.filter { $0.isAddressField }.count + + let isDetailsIndepend: Bool = idCount > 1 || idCount == 0 + let isAddressIndepend: Bool = adCount > 1 || adCount == 0 + let hasAddress = requestedFields.filter { value in + switch value { + case let .just(value): + switch value { + case .address: + return true + default: + return false + } + default: + return false + } + }.count == 1 + + let hasDetails = requestedFields.filter { value in + switch value { + case let .just(value): + switch value { + case .personalDetails: + return true + default: + return false + } + default: + return false + } + }.count == 1 + + var hasNativeName = requestedFields.filter { value in + switch value { + case let .just(value): + switch value { + case let .personalDetails(nativeName): + return nativeName + default: + return false + } + default: + return false + } + }.count == 1 + + if hasNativeName, let value = state.searchValue(.personalDetails)?.personalDetails { + if state.configuration?.nativeLanguageByCountry[value.residenceCountryCode] == "en" { + hasNativeName = false + } + } + + for field in requestedFields { + switch field { + case let .just(field): + switch field { + case .address: + if isAddressIndepend { + var isFilled: Bool = false + let desc: FieldDescription + let errors = state.errors[.address] ?? [:] + if let value = state.searchValue(field.valueKey), case let .address(address) = value { + + let errorText = errors[InputDataEmptyIdentifier]?.description ?? (errors.isEmpty ? "" : errors.first!.value.description) + if errorText.isEmpty { + let text = [address.street1, address.city, address.state, cManager.item(bySmallCountryName: address.countryCode)?.shortName ?? address.countryCode].compactMap {$0}.filter {!$0.isEmpty}.joined(separator: ", ") + desc = FieldDescription(text: text) + } else { + desc = FieldDescription(text: errorText, isError: true) + } + isFilled = true + + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), relative: [], title: L10n.secureIdRequestPermissionResidentialAddress, description: desc, value: value, relativeValue: nil)) + filledCount += 1 + + } + if !isFilled { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), value: nil, relativeValue: nil, relative: [], title: L10n.secureIdRequestPermissionResidentialAddress, desc: field.emptyDescription, isError: state.emptyErrors)) + } + index += 1 + } + case .personalDetails: + if isDetailsIndepend { + var isFilled: Bool = false + let desc: FieldDescription + let errors = state.errors[.personalDetails] ?? [:] + let errorText = errors[InputDataEmptyIdentifier]?.description ?? (errors.isEmpty ? "" : errors.first!.value.description) + + if let value = state.searchValue(field.valueKey), case let .personalDetails(details) = value, (!hasNativeName || (hasNativeName && details.nativeName != nil)) { + if errorText.isEmpty { + let text = [details.latinName.firstName + " " + details.latinName.lastName, details.gender.stringValue, details.birthdate.stringValue, cManager.item(bySmallCountryName: details.countryCode)?.shortName ?? details.countryCode].compactMap {$0}.filter {!$0.isEmpty}.joined(separator: ", ") + desc = FieldDescription(text: text) + } else { + desc = FieldDescription(text: errorText, isError: true) + } + + isFilled = true + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), relative: [], title: L10n.secureIdRequestPermissionPersonalDetails, description: desc, value: value, relativeValue: nil)) + filledCount += 1 + + } + if !isFilled { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), value: nil, relativeValue: nil, relative: [], title: L10n.secureIdRequestPermissionPersonalDetails, desc: field.emptyDescription, isError: state.emptyErrors)) + } + index += 1 + } + case .email: + if let value = state.searchValue(field.valueKey), case let .email(email) = value { + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), relative: [], title: field.rawValue, description: FieldDescription(text: email.email), value: value, relativeValue: nil)) + filledCount += 1 + + } else { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), value: nil, relativeValue: nil, relative: [], title: field.rawValue, desc: field.emptyDescription, isError: state.emptyErrors)) + } + index += 1 + case .phone: + if let value = state.searchValue(field.valueKey), case let .phone(phone) = value { + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), relative: [], title: field.rawValue, description: FieldDescription(text: formatPhoneNumber(phone.phone)), value: value, relativeValue: nil)) + filledCount += 1 + } else { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(field), value: nil, relativeValue: nil, relative: [], title: field.rawValue, desc: field.emptyDescription, isError: state.emptyErrors)) + } + index += 1 + default: + fatalError() +// entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(field.primary, field, hasDetails && !isDetailsIndepend), relative: [field], title: field.rawValue, desc: field.emptyDescription, isError: state.emptyErrors)) +// index += 1 + } + case let .oneOf(fields): + + var descText: String + if fields.count == 1 { + descText = L10n.secureIdUploadScanSingle(fields[0].rawValue.lowercased()) + } else { + let all = fields.prefix(fields.count - 1).reduce("", { current, value in + if current.isEmpty { + return value.rawValue.lowercased() + } + return current + ", " + value.rawValue.lowercased() + }) + descText = L10n.secureIdUploadScanMulti(all, fields[fields.count - 1].rawValue.lowercased()) + } + + switch fields[0] { + case .bankStatement, .rentalAgreement, .utilityBill, .passportRegistration, .temporaryRegistration: + + let secondary:SecureIdValue? = fields.compactMap {state.searchValue($0.valueKey)}.first + let address = hasAddress ? state.searchValue(.address) : nil + + let title: String + switch fields.count { + case 1: + title = fields[0].rawValue + case 2: + title = L10n.secureIdRequestTwoDocumentsTitle(fields[0].rawValue, fields[1].rawValue) + default: + title = L10n.secureIdRequestPermissionResidentialAddress + } + + if let secondary = secondary?.verificationDocuments { + descText = L10n.secureIdAddressScansCountable(secondary.count) + } + + if let value = address, case let .address(address) = value, !isAddressIndepend { + descText = (secondary?.verificationDocuments != nil ? descText + ", " : "") + [address.street1, address.city, address.state, cManager.item(bySmallCountryName: address.countryCode)?.shortName ?? address.countryCode].compactMap {$0}.filter {!$0.isEmpty}.joined(separator: ", ") + } + + + if let secondary = secondary?.verificationDocuments, address == nil { + descText = L10n.secureIdAddressScansCountable(secondary.count) + } else if let value = address, case let .address(address) = value, hasAddress && !isAddressIndepend { + descText = [address.street1, address.city, address.state, cManager.item(bySmallCountryName: address.countryCode)?.shortName ?? address.countryCode].compactMap {$0}.filter {!$0.isEmpty}.joined(separator: ", ") + } + + if fields.count > 1, let secondary = secondary { + descText = secondary.requestFieldType.rawValue + ", " + descText + } + + var isUnfilled: Bool = false + + if let secondary = secondary { + if let result = fields.filter({secondary.requestFieldType.valueKey == $0.valueKey}).first { + if result.hasTranslation && (secondary.translations == nil || secondary.translations!.isEmpty) { + isUnfilled = true + descText = L10n.secureIdRequestUploadTranslation + } + } + } + + + + var relative: [SecureIdRequestedFormFieldValue] = fields + if let secondary = secondary { + relative = [fields.filter({secondary.requestFieldType.valueKey == $0.valueKey}).first!] + } + + var errors:[InputDataIdentifier : InputDataValueError] = [:] + + + if hasAddress && !isAddressIndepend { + let primary = (state.errors[.address] ?? [:]) + for (key, value) in primary { + errors[key] = value + } + } + for field in fields { + let secondary = (state.errors[field.valueKey] ?? [:]) + for (key, value) in secondary { + errors[key] = value + } + } + + let errorText = errors[InputDataEmptyIdentifier]?.description ?? (errors.isEmpty ? "" : errors.first!.value.description) + + if !errorText.isEmpty { + descText = errorText + } + let addressField:SecureIdRequestedFormFieldValue = requestedFields.filter({$0.valueKey == .address}).first?.fieldValue ?? .address + + if let secondary = secondary, (!hasAddress || isAddressIndepend || (hasAddress && address != nil)), !isUnfilled { + let desc: FieldDescription = FieldDescription(text: descText, isError: !errorText.isEmpty) + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(addressField, relative[0], hasAddress && !isAddressIndepend), relative: relative, title: title, description: desc, value: address, relativeValue: secondary)) + if !desc.isError { + filledCount += 1 + } + } else { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(addressField, relative[0], hasAddress && !isAddressIndepend), value: address, relativeValue: secondary, relative: relative, title: title, desc: descText, isError: state.emptyErrors)) + } + + index += 1 + case .passport, .idCard, .driversLicense, .internalPassport: + + let secondary:SecureIdValue? = fields.compactMap {state.searchValue($0.valueKey)}.first + let details = hasDetails ? state.searchValue(.personalDetails) : nil + + let title: String + switch fields.count { + case 1: + title = fields[0].rawValue + case 2: + title = L10n.secureIdRequestTwoDocumentsTitle(fields[0].rawValue, fields[1].rawValue) + default: + title = L10n.secureIdRequestPermissionIdentityDocument + } + + + if let secondary = secondary { + descText = secondary.identifier ?? "" + } + + + if let value = details, case let .personalDetails(details) = value, hasDetails && !isDetailsIndepend { + descText = (secondary != nil ? descText + ", " : "") + [details.latinName.firstName + " " + details.latinName.lastName, details.gender.stringValue, details.birthdate.stringValue, cManager.item(bySmallCountryName: details.countryCode)?.shortName ?? details.countryCode].compactMap {$0}.filter {!$0.isEmpty}.joined(separator: ", ") + } + + if fields.count > 1, let secondary = secondary { + descText = secondary.requestFieldType.rawValue + ", " + descText + } + + var isUnfilled: Bool = false + + if let secondary = secondary { + if let result = fields.filter({secondary.requestFieldType.valueKey == $0.valueKey}).first { + if result.hasSelfie && secondary.selfieVerificationDocument == nil { + isUnfilled = true + descText = L10n.secureIdRequestUploadSelfie + } + if result.hasTranslation && (secondary.translations == nil || secondary.translations!.isEmpty) { + isUnfilled = true + descText = L10n.secureIdRequestUploadTranslation + } + } + } + + var relative: [SecureIdRequestedFormFieldValue] = fields + + + if let secondary = secondary { + relative = [fields.filter({secondary.requestFieldType.valueKey == $0.valueKey}).first!] + } + + var errors:[InputDataIdentifier : InputDataValueError] = [:] + + + if hasDetails && !isDetailsIndepend { + let primary = (state.errors[.personalDetails] ?? [:]) + for (key, value) in primary { + errors[key] = value + } + } + for field in fields { + let secondary = (state.errors[field.valueKey] ?? [:]) + for (key, value) in secondary { + errors[key] = value + } + } + + let errorText = errors[InputDataEmptyIdentifier]?.description ?? (errors.isEmpty ? "" : errors.first!.value.description) + + if !errorText.isEmpty { + descText = errorText + } + + let personalField:SecureIdRequestedFormFieldValue = requestedFields.filter({$0.valueKey == .personalDetails}).first?.fieldValue ?? .personalDetails(nativeName: true) + + if let secondary = secondary, (!hasDetails || isDetailsIndepend || (hasDetails && details != nil)), !isUnfilled, (!hasNativeName || (hasNativeName && details?.personalDetails?.nativeName != nil)) { + let desc: FieldDescription = FieldDescription(text: descText, isError: !errorText.isEmpty) + entries.append(.filledField(sectionId: sectionId, index: index, fieldType: FieldRequest(personalField, relative[0], hasDetails && !isDetailsIndepend), relative: relative, title: title, description: desc, value: details, relativeValue: secondary)) + if !desc.isError { + filledCount += 1 + } + + } else { + entries.append(.emptyField(sectionId: sectionId, index: index, fieldType: FieldRequest(personalField, relative[0], hasDetails && !isDetailsIndepend), value: details, relativeValue: secondary, relative: relative, title: title, desc: descText, isError: state.emptyErrors)) + } + index += 1 + default: + break + } + } + + } + let policyText = encryptedForm?.termsUrl != nil ? L10n.secureIdAcceptPolicy("@\(peer.addressName ?? "")") : L10n.secureIdAcceptHelp("@\(peer.addressName ?? "")", "@\(peer.addressName ?? "")") + entries.append(.description(sectionId: sectionId, index: index, text: policyText, detectBold: true)) + index += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + enabled = filledCount == (requestedFields.count - (hasDetails && !isDetailsIndepend ? 1 : 0) - (hasAddress && !isAddressIndepend ? 1 : 0)) + + // entries.append(.accept(sectionId: sectionId, index: index, enabled: filledCount == form.requestedFields.count - relativeAddress.count - relativeIdentity.count)) + // index += 1 + } + + } else { + entries.append(.loading) + } + + return (entries, enabled) +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments: PassportArguments) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + +private final class EmailIntermediateState : Equatable { + let email: String + let length: Int32 + init(email: String, length: Int32) { + self.email = email + self.length = length + } +} +private func ==(lhs: EmailIntermediateState, rhs: EmailIntermediateState) -> Bool { + return lhs.email == rhs.email && lhs.length == rhs.length +} + +private final class DetailsIntermediateState : Equatable { + let firstName: InputDataValue? + let middleName: InputDataValue? + let lastName: InputDataValue? + let firstNameNative: InputDataValue? + let middleNameNative: InputDataValue? + let lastNameNative: InputDataValue? + + let birthday: InputDataValue? + let citizenship: InputDataValue? + let residence: InputDataValue? + let gender: InputDataValue? + let expiryDate: InputDataValue? + let identifier: InputDataValue? + init(firstName: InputDataValue?, middleName: InputDataValue?, lastName: InputDataValue?, firstNameNative: InputDataValue?, middleNameNative: InputDataValue?, lastNameNative: InputDataValue?, birthday: InputDataValue?, citizenship: InputDataValue?, residence: InputDataValue?, gender: InputDataValue?, expiryDate: InputDataValue?, identifier: InputDataValue?) { + self.firstName = firstName + self.middleName = middleName + self.lastName = lastName + self.firstNameNative = firstNameNative + self.lastNameNative = lastNameNative + self.middleNameNative = middleNameNative + self.birthday = birthday + self.citizenship = citizenship + self.residence = residence + self.gender = gender + self.expiryDate = expiryDate + self.identifier = identifier + } + + convenience init(_ data: [InputDataIdentifier : InputDataValue]) { + self.init(firstName: data[_id_first_name], middleName: data[_id_middle_name], lastName: data[_id_last_name], firstNameNative: data[_id_first_name_native], middleNameNative: data[_id_middle_name_native], lastNameNative: data[_id_last_name_native], birthday: data[_id_birthday], citizenship: data[_id_country], residence: data[_id_residence], gender: data[_id_gender], expiryDate: data[_id_expire_date], identifier: data[_id_identifier]) + } + + fileprivate func validateErrors(currentState: DetailsIntermediateState, errors:[InputDataIdentifier : InputDataValueError]?, relativeErrors: [InputDataIdentifier : InputDataValueError]?) -> [InputDataIdentifier : InputDataValidationFailAction] { + var fails:[InputDataIdentifier : InputDataValidationFailAction] = [:] + + if errors?[_id_first_name] != nil, currentState.firstName == firstName { + fails[_id_first_name] = .shake + } + if errors?[_id_middle_name] != nil, currentState.middleName == middleName { + fails[_id_middle_name] = .shake + } + if errors?[_id_last_name] != nil, currentState.lastName == lastName { + fails[_id_last_name] = .shake + } + + if errors?[_id_first_name_native] != nil, currentState.firstNameNative == firstNameNative { + fails[_id_first_name_native] = .shake + } + if errors?[_id_middle_name_native] != nil, currentState.middleNameNative == middleNameNative { + fails[_id_middle_name_native] = .shake + } + if errors?[_id_last_name_native] != nil, currentState.lastNameNative == lastNameNative { + fails[_id_last_name_native] = .shake + } + + if errors?[_id_birthday] != nil, currentState.birthday == birthday { + fails[_id_birthday] = .shake + } + if errors?[_id_country] != nil, currentState.citizenship == citizenship { + fails[_id_country] = .shake + } + if errors?[_id_residence] != nil, currentState.residence == residence { + fails[_id_residence] = .shake + } + if errors?[_id_gender] != nil, currentState.gender == gender { + fails[_id_gender] = .shake + } + + if relativeErrors?[_id_expire_date] != nil, currentState.expiryDate == expiryDate { + fails[_id_expire_date] = .shake + } + if relativeErrors?[_id_identifier] != nil, currentState.identifier == identifier { + fails[_id_identifier] = .shake + } + return fails + } + + fileprivate func removeErrors(currentState: DetailsIntermediateState, errors:[InputDataIdentifier : InputDataValueError]?, relativeErrors: [InputDataIdentifier : InputDataValueError]?) -> (errors: [InputDataIdentifier : InputDataValueError], relativeErrors: [InputDataIdentifier : InputDataValueError]) { + + var errors = errors ?? [:] + var relativeErrors = relativeErrors ?? [:] + + if errors[_id_first_name] != nil, currentState.firstName != firstName { + errors.removeValue(forKey: _id_first_name) + } + if errors[_id_middle_name] != nil, currentState.middleName != middleName { + errors.removeValue(forKey: _id_middle_name) + } + if errors[_id_last_name] != nil, currentState.lastName != lastName { + errors.removeValue(forKey: _id_last_name) + } + + if errors[_id_first_name_native] != nil, currentState.firstNameNative != firstNameNative { + errors.removeValue(forKey: _id_first_name_native) + } + if errors[_id_middle_name_native] != nil, currentState.middleNameNative != middleNameNative { + errors.removeValue(forKey: _id_middle_name_native) + } + if errors[_id_last_name_native] != nil, currentState.lastNameNative != lastNameNative { + errors.removeValue(forKey: _id_last_name_native) + } + + if errors[_id_birthday] != nil, currentState.birthday != birthday { + errors.removeValue(forKey: _id_birthday) + } + if errors[_id_country] != nil, currentState.citizenship != citizenship { + errors.removeValue(forKey: _id_country) + } + if errors[_id_residence] != nil, currentState.residence != residence { + errors.removeValue(forKey: _id_residence) + } + if errors[_id_gender] != nil, currentState.gender != gender { + errors.removeValue(forKey: _id_gender) + } + + if relativeErrors[_id_expire_date] != nil, currentState.expiryDate != expiryDate { + relativeErrors.removeValue(forKey: _id_expire_date) + } + if relativeErrors[_id_identifier] != nil, currentState.identifier != identifier { + relativeErrors.removeValue(forKey: _id_identifier) + } + return (errors: errors, relativeErrors: relativeErrors) + } +} + +private func ==(lhs: DetailsIntermediateState, rhs: DetailsIntermediateState) -> Bool { + return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.birthday == rhs.birthday && lhs.residence == rhs.residence && lhs.citizenship == rhs.citizenship && lhs.gender == rhs.gender && lhs.expiryDate == rhs.expiryDate && lhs.identifier == rhs.identifier +} + +private final class AddressIntermediateState : Equatable { + let street1: InputDataValue? + let street2: InputDataValue? + let city: InputDataValue? + let state: InputDataValue? + let countryCode: InputDataValue? + let postcode: InputDataValue? + + init(street1: InputDataValue?, street2: InputDataValue?, city: InputDataValue?, state: InputDataValue?, countryCode: InputDataValue?, postcode: InputDataValue?) { + self.street1 = street1 + self.street2 = street2 + self.city = city + self.state = state + self.countryCode = countryCode + self.postcode = postcode + } + + convenience init(_ data: [InputDataIdentifier : InputDataValue]) { + self.init(street1: data[_id_street1], street2: data[_id_street2], city: data[_id_city], state: data[_id_state], countryCode: data[_id_country], postcode: data[_id_postcode]) + } + + fileprivate func validateErrors(currentState: AddressIntermediateState, errors:[InputDataIdentifier : InputDataValueError]?) -> [InputDataIdentifier : InputDataValidationFailAction] { + var fails:[InputDataIdentifier : InputDataValidationFailAction] = [:] + + if errors?[_id_street1] != nil, currentState.street1 == street1 { + fails[_id_street1] = .shake + } + if errors?[_id_street2] != nil, currentState.street2 == street2 { + fails[_id_street2] = .shake + } + if errors?[_id_state] != nil, currentState.state == state { + fails[_id_state] = .shake + } + if errors?[_id_city] != nil, currentState.city == city { + fails[_id_city] = .shake + } + if errors?[_id_country] != nil, currentState.countryCode == countryCode { + fails[_id_country] = .shake + } + if errors?[_id_postcode] != nil, currentState.postcode == postcode { + fails[_id_postcode] = .shake + } + return fails + } + + fileprivate func removeErrors(currentState: AddressIntermediateState, errors:[InputDataIdentifier : InputDataValueError]?) -> [InputDataIdentifier : InputDataValueError] { + + var errors = errors ?? [:] + + if errors[_id_street1] != nil, currentState.street1 != street1 { + errors.removeValue(forKey: _id_street1) + } + if errors[_id_street2] != nil, currentState.street2 != street2 { + errors.removeValue(forKey: _id_street2) + } + if errors[_id_state] != nil, currentState.state != state { + errors.removeValue(forKey: _id_state) + } + if errors[_id_city] != nil, currentState.city != city { + errors.removeValue(forKey: _id_city) + } + if errors[_id_country] != nil, currentState.countryCode != countryCode { + errors.removeValue(forKey: _id_country) + } + if errors[_id_postcode] != nil, currentState.postcode != postcode { + errors.removeValue(forKey: _id_postcode) + } + + return errors + } +} + +private func ==(lhs: AddressIntermediateState, rhs: AddressIntermediateState) -> Bool { + return lhs.street1 == rhs.street1 && lhs.street2 == rhs.street2 && lhs.city == rhs.city && lhs.state == rhs.state && lhs.countryCode == rhs.countryCode && lhs.postcode == rhs.postcode +} + +private enum PassportViewState { + case plain + case settings +} + +private final class PassportState : Equatable { + let context: AccountContext + let peer: Peer + let values:[SecureIdValueWithContext] + let accessContext: SecureIdAccessContext? + let password: UpdateTwoStepVerificationPasswordResult? + let passwordSettings: TwoStepVerificationSettings? + let verifyDocumentContext: SecureIdVerificationDocumentsContext? + let files: [SecureIdValueKey : [SecureIdVerificationDocument]] + + let emailIntermediateState: EmailIntermediateState? + + let detailsIntermediateState: DetailsIntermediateState? + let addressIntermediateState: AddressIntermediateState? + + let errors:[SecureIdValueKey: [InputDataIdentifier : InputDataValueError]] + + let selfies:[SecureIdValueKey : SecureIdVerificationDocument] + let translations: [SecureIdValueKey : [SecureIdVerificationDocument]] + let frontSideFile: [SecureIdValueKey : SecureIdVerificationDocument] + let backSideFile: [SecureIdValueKey : SecureIdVerificationDocument] + + let viewState: PassportViewState + + let emptyErrors:Bool + + let tmpPwd: String? + + let configuration: SecureIdConfiguration? + + let passwordError: String? + + let emailCode: String + let emailCodeError: InputDataValueError? + + init(context: AccountContext, peer: Peer, tmpPwd: String?, viewState: PassportViewState, errors: [SecureIdValueKey: [InputDataIdentifier : InputDataValueError]] = [:], passwordSettings:TwoStepVerificationSettings? = nil, password: UpdateTwoStepVerificationPasswordResult? = nil, values: [SecureIdValueWithContext] = [], accessContext: SecureIdAccessContext? = nil, verifyDocumentContext: SecureIdVerificationDocumentsContext? = nil, files: [SecureIdValueKey : [SecureIdVerificationDocument]] = [:], emailIntermediateState: EmailIntermediateState? = nil, detailsIntermediateState: DetailsIntermediateState? = nil, addressIntermediateState: AddressIntermediateState? = nil, selfies: [SecureIdValueKey : SecureIdVerificationDocument] = [:], translations: [SecureIdValueKey : [SecureIdVerificationDocument]] = [:], frontSideFile: [SecureIdValueKey : SecureIdVerificationDocument] = [:], backSideFile: [SecureIdValueKey : SecureIdVerificationDocument] = [:], emptyErrors: Bool = false, configuration: SecureIdConfiguration? = nil, passwordError: String? = nil, emailCode: String = "", emailCodeError: InputDataValueError? = nil) { + self.context = context + self.peer = peer + self.errors = errors + self.passwordSettings = passwordSettings + self.password = password + self.values = values + self.viewState = viewState + self.tmpPwd = tmpPwd + self.accessContext = accessContext + self.verifyDocumentContext = verifyDocumentContext + self.files = files + self.emailIntermediateState = emailIntermediateState + self.detailsIntermediateState = detailsIntermediateState + self.addressIntermediateState = addressIntermediateState + self.selfies = selfies + self.translations = translations + self.frontSideFile = frontSideFile + self.backSideFile = backSideFile + self.emptyErrors = emptyErrors + self.configuration = configuration + self.passwordError = passwordError + self.emailCode = emailCode + self.emailCodeError = emailCodeError + let translations:[SecureIdVerificationDocument] = translations.reduce([], { current, value in + return current + value.value + }) + + self.verifyDocumentContext?.stateUpdated(files.reduce(Array(selfies.values) + Array(frontSideFile.values) + Array(backSideFile.values) + translations, { (current, value) -> [SecureIdVerificationDocument] in + return current + value.value + })) + } + + func searchValue(_ valueKey: SecureIdValueKey) -> SecureIdValue? { + let index = values.index { value -> Bool in + return value.value.isSame(of: valueKey) + } + if let index = index { + return values[index].value + } + return nil + } + + + func withUpdatedTmpPwd(_ tmpPwd: String?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedPassword(_ password: UpdateTwoStepVerificationPasswordResult?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedPasswordSettings(_ settings: TwoStepVerificationSettings?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: settings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + + func withUpdatedValues(_ values:[SecureIdValueWithContext]) -> PassportState { + var current = self + for value in values { + current = current.withUpdatedValue(value) + } + return current + } + + func withUpdatedValue(_ value: SecureIdValueWithContext) -> PassportState { + var values = self.values + let index = values.index { v -> Bool in + return value.value.isSame(of: v.value) + } + if let index = index { + values[index] = value + } else { + values.append(value) + } + + var files = self.files + if let verificationDocuments = value.value.verificationDocuments { + files[value.value.key] = verificationDocuments.compactMap { reference in + switch reference { + case let .remote(file): + return .remote(file) + default: + return nil + } + } + } + + for (key, _) in self.files { + let index = values.index { v -> Bool in + return v.value.key == key + } + if index == nil { + files[key] = [] + } + } + + var selfies = self.selfies + if let selfie = value.value.selfieVerificationDocument { + switch selfie { + case let .remote(file): + selfies[value.value.key] = .remote(file) + default: + selfies[value.value.key] = nil + } + } + + var frontSideFile = self.frontSideFile + if let frontSide = value.value.frontSideVerificationDocument { + switch frontSide { + case let .remote(file): + frontSideFile[value.value.key] = .remote(file) + default: + frontSideFile[value.value.key] = nil + } + } + + var backSideFile = self.backSideFile + if let backSide = value.value.backSideVerificationDocument { + switch backSide { + case let .remote(file): + backSideFile[value.value.key] = .remote(file) + default: + backSideFile[value.value.key] = nil + } + } + + var translations = self.translations + if let translationsDocuments = value.value.translations { + translations[value.value.key] = translationsDocuments.compactMap { reference in + switch reference { + case let .remote(file): + return .remote(file) + default: + return nil + } + } + } + + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: selfies, translations: translations, frontSideFile: frontSideFile, backSideFile: backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedValue(_ key: SecureIdValueKey) -> PassportState { + var values = self.values + let index = values.index { v -> Bool in + return v.value.key == key + } + if let index = index { + values.remove(at: index) + } + var files = self.files + files.removeValue(forKey: key) + + var translations = self.translations + translations.removeValue(forKey: key) + + var selfies = self.selfies + selfies.removeValue(forKey: key) + + var frontSideFile = self.frontSideFile + frontSideFile.removeValue(forKey: key) + + var backSideFile = self.backSideFile + backSideFile.removeValue(forKey: key) + + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: selfies, translations: translations, frontSideFile: frontSideFile, backSideFile: backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedAccessContext(_ accessContext: SecureIdAccessContext) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedVerifyDocumentContext(_ verifyDocumentContext: SecureIdVerificationDocumentsContext) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedFileState(id: Int64, state: SecureIdVerificationLocalDocumentState) -> PassportState { + var files = self.files + for (key, documents) in files { + loop: for i in 0 ..< documents.count { + let file = documents[i] + if file.id.hashValue == id { + switch file { + case var .local(document): + document.state = state + var documents = documents + documents[i] = .local(document) + files[key] = documents + break loop + default: + break + } + } + } + } + var selfies = self.selfies + loop: for (key, value) in self.selfies { + if value.id.hashValue == id { + switch value { + case var .local(document): + document.state = state + selfies[key] = .local(document) + break loop + default: + break + } + } + } + var frontSideFile = self.frontSideFile + loop: for (key, value) in self.frontSideFile { + if value.id.hashValue == id { + switch value { + case var .local(document): + document.state = state + frontSideFile[key] = .local(document) + break loop + default: + break + } + } + } + var backSideFile = self.backSideFile + loop: for (key, value) in self.backSideFile { + if value.id.hashValue == id { + switch value { + case var .local(document): + document.state = state + backSideFile[key] = .local(document) + break loop + default: + break + } + } + } + + var translations = self.translations + + loop: for (key, _values) in self.translations { + var values = _values + for i in 0 ..< _values.count { + let value = values[i] + if value.id.hashValue == id { + switch value { + case var .local(document): + document.state = state + values[i] = .local(document) + translations[key] = values + break loop + default: + break + } + } + } + } + + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: selfies, translations: translations, frontSideFile: frontSideFile, backSideFile: backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withAppendTranslations(_ translations: [SecureIdVerificationDocument], for valueKey: SecureIdValueKey) -> PassportState { + var current = self.translations[valueKey] ?? [] + current.append(contentsOf: translations) + current = Array(current.prefix(scansLimit)) + var dictionary = self.translations + dictionary[valueKey] = current + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: dictionary, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedTranslations(_ translations: [SecureIdVerificationDocument], for valueKey: SecureIdValueKey) -> PassportState { + var dictionary = self.translations + dictionary[valueKey] = translations + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: dictionary, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: dictionary, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedTranslation(_ translation: SecureIdVerificationDocument, for valueKey: SecureIdValueKey) -> PassportState { + var translations = self.translations[valueKey] ?? [] + for i in 0 ..< translations.count { + if translations[i].id == translation.id { + translations.remove(at: i) + break + } + } + var dictionary = self.translations + dictionary[valueKey] = translations + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: dictionary, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + + func withAppendFiles(_ files: [SecureIdVerificationDocument], for valueKey: SecureIdValueKey) -> PassportState { + var current = self.files[valueKey] ?? [] + current.append(contentsOf: files) + current = Array(current.prefix(scansLimit)) + var dictionary = self.files + dictionary[valueKey] = current + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: dictionary, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedFiles(_ files: [SecureIdVerificationDocument], for valueKey: SecureIdValueKey) -> PassportState { + var dictionary = self.files + dictionary[valueKey] = files + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: dictionary, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedFile(_ file: SecureIdVerificationDocument, for valueKey: SecureIdValueKey) -> PassportState { + var files = self.files[valueKey] + if let _ = files { + for i in 0 ..< files!.count { + if files![i].id == file.id { + files!.remove(at: i) + break + } + } + } + var dictionary = self.files + dictionary[valueKey] = files + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: dictionary, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedValues() -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: [], accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: [:], emailIntermediateState: emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: [:], translations: [:], frontSideFile: [:], backSideFile: [:], emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedIntermediateEmailState(_ emailIntermediateState: EmailIntermediateState?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedDetailsState(_ detailsIntermediateState: DetailsIntermediateState?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: detailsIntermediateState, addressIntermediateState: addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + func withUpdatedAddressState(_ addressState: AddressIntermediateState?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: addressState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + func withUpdatedViewState(_ viewState: PassportViewState) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + func withUpdatedSelfie(_ value: SecureIdVerificationDocument?, for key: SecureIdValueKey) -> PassportState { + var selfies = self.selfies + if let value = value { + selfies[key] = value + } else { + selfies.removeValue(forKey: key) + } + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedFrontSide(_ value: SecureIdVerificationDocument?, for key: SecureIdValueKey) -> PassportState { + var frontSideFile = self.frontSideFile + if let value = value { + frontSideFile[key] = value + } else { + frontSideFile.removeValue(forKey: key) + } + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedBackSide(_ value: SecureIdVerificationDocument?, for key: SecureIdValueKey) -> PassportState { + var backSideFile = self.backSideFile + if let value = value { + backSideFile[key] = value + } else { + backSideFile.removeValue(forKey: key) + } + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedEmptyErrors(_ emptyErrors: Bool) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedPasswordError(_ passwordError: String?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedInputErrors() -> PassportState { + var errors = self.errors + for (key, value) in self.errors { + errors[key] = value.filter({$0.value != latinError}) + } + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withRemovedError(for valueKey:SecureIdValueKey, field: InputDataIdentifier) -> PassportState { + var valyeErrors = self.errors[valueKey] ?? [:] + valyeErrors = valyeErrors.filter({$0.key != field}) + var errors = self.errors + errors[valueKey] = valyeErrors + + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func errors(for key: SecureIdValueKey) -> [InputDataIdentifier : InputDataValueError] { + switch key { + case .address: + var aErrors = errors[key] ?? [:] + let rKeys:[SecureIdValueKey] = [.rentalAgreement, .utilityBill, .bankStatement] + for rKey in rKeys { + if let rErrors = errors[rKey] { + for(key, value) in rErrors { + aErrors[key] = value + } + } + } + return aErrors + case .personalDetails: + var aErrors = errors[key] ?? [:] + let rKeys:[SecureIdValueKey] = [.passport, .driversLicense, .idCard] + for rKey in rKeys { + if let rErrors = errors[rKey] { + for(key, value) in rErrors { + aErrors[key] = value + } + } + } + return aErrors + default: + return errors[key] ?? [:] + } + } + + func withRemovedErrors(for key: SecureIdValueKey) -> PassportState { + var errors = self.errors + switch key { + case .address: + errors.removeValue(forKey: key) + let rKeys:[SecureIdValueKey] = [.rentalAgreement, .utilityBill, .bankStatement] + for rKey in rKeys { + errors.removeValue(forKey: rKey) + } + case .personalDetails: + errors.removeValue(forKey: key) + let rKeys:[SecureIdValueKey] = [.passport, .driversLicense, .idCard] + for rKey in rKeys { + errors.removeValue(forKey: rKey) + } + default: + errors.removeValue(forKey: key) + } + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + func withUpdatedErrors(_ errors: [SecureIdValueKey: [InputDataIdentifier : InputDataValueError]]) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedConfiguration(_ configuration: SecureIdConfiguration?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedEmailCode(_ emailCode: String) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: emailCode, emailCodeError: self.emailCodeError) + } + + func withUpdatedEmailCodeError(_ emailCodeError: InputDataValueError?) -> PassportState { + return PassportState(context: self.context, peer: self.peer, tmpPwd: self.tmpPwd, viewState: self.viewState, errors: self.errors, passwordSettings: self.passwordSettings, password: self.password, values: self.values, accessContext: self.accessContext, verifyDocumentContext: self.verifyDocumentContext, files: self.files, emailIntermediateState: self.emailIntermediateState, detailsIntermediateState: self.detailsIntermediateState, addressIntermediateState: self.addressIntermediateState, selfies: self.selfies, translations: self.translations, frontSideFile: self.frontSideFile, backSideFile: self.backSideFile, emptyErrors: self.emptyErrors, configuration: self.configuration, passwordError: self.passwordError, emailCode: self.emailCode, emailCodeError: emailCodeError) + } +} +private func ==(lhs: PassportState, rhs: PassportState) -> Bool { + + if lhs.files.count != rhs.files.count { + return false + } else { + for (lhsKey, lhsValue) in lhs.files { + let rhsValue = rhs.files[lhsKey] + if let rhsValue = rhsValue { + if lhsValue.count != rhsValue.count { + return false + } else { + for i in 0 ..< lhsValue.count { + if !lhsValue[i].isEqual(to: rhsValue[i]) { + return false + } + } + } + + } else { + return false + } + } + } + + if lhs.translations.count != rhs.translations.count { + return false + } else { + for (lhsKey, lhsValue) in lhs.translations { + let rhsValue = rhs.translations[lhsKey] + if let rhsValue = rhsValue { + if lhsValue.count != rhsValue.count { + return false + } else { + for i in 0 ..< lhsValue.count { + if !lhsValue[i].isEqual(to: rhsValue[i]) { + return false + } + } + } + + } else { + return false + } + } + } + + if lhs.selfies.count != rhs.selfies.count { + return false + } else { + for (lhsKey, lhsValue) in lhs.selfies { + let rhsValue = rhs.selfies[lhsKey] + if let rhsValue = rhsValue { + if !lhsValue.isEqual(to: rhsValue) { + return false + } + + } else { + return false + } + } + } + + + return lhs.passwordSettings?.email == rhs.passwordSettings?.email && lhs.password == rhs.password && lhs.values == rhs.values && (lhs.accessContext == nil && rhs.accessContext == nil) && lhs.emailIntermediateState == rhs.emailIntermediateState && lhs.detailsIntermediateState == rhs.detailsIntermediateState && lhs.addressIntermediateState == rhs.addressIntermediateState && lhs.errors == rhs.errors && lhs.viewState == rhs.viewState && lhs.tmpPwd == rhs.tmpPwd && lhs.emptyErrors == rhs.emptyErrors && lhs.passwordError == rhs.passwordError && lhs.emailCode == rhs.emailCode && lhs.emailCodeError == rhs.emailCodeError +} + + + +private func createPasswordEntries( _ state: PassportState) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + let nonFilter:(String)->String = { value in + return value + } + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdCreatePasswordHeader), data: InputDataGeneralTextData())) + index += 1 + + + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_c_password, mode: .secure, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdCreatePasswordPasswordPlaceholder), inputPlaceholder: L10n.secureIdCreatePasswordPasswordInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_c_repassword, mode: .secure, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(""), inputPlaceholder: L10n.secureIdCreatePasswordRePasswordInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdCreatePasswordDescription), data: InputDataGeneralTextData())) + index += 1 + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdCreatePasswordHintHeader), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_c_hint, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdCreatePasswordHintPlaceholder), inputPlaceholder: L10n.secureIdCreatePasswordHintInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdCreatePasswordEmailHeader), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_c_email, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdCreatePasswordEmailPlaceholder), inputPlaceholder: L10n.secureIdCreatePasswordEmailInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdCreatePasswordEmailDescription), data: InputDataGeneralTextData())) + index += 1 + + return entries + +} + + +private func emailEntries( _ state: PassportState, updateState: @escaping ((PassportState)->PassportState)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + var placeholder = state.searchValue(.email)?.emailValue?.email ?? "" + + + if let email = state.emailIntermediateState?.email, !email.isEmpty { + + if placeholder == email { + placeholder = "" + } + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_email_code, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdEmailActivateCodePlaceholder), inputPlaceholder: L10n.secureIdEmailActivateCodeInputPlaceholder, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: Int32(email.length))) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdEmailActivateDescription(email)), data: InputDataGeneralTextData())) + index += 1 + + return entries + + } else if let email = state.passwordSettings?.email, !email.isEmpty { + + if placeholder == email { + placeholder = "" + } + + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(email), error: nil, identifier: _id_email_def, data: InputDataGeneralData(name: L10n.secureIdEmailUseSame(email), color: theme.colors.accent, icon: nil, type: .next, action: nil))) + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + } + + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(placeholder), error: nil, identifier: _id_email_new, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdEmailEmailPlaceholder), inputPlaceholder: L10n.secureIdEmailEmailInputPlaceholder, filter: {$0}, limit: 254)) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdEmailUseSameDesc), data: InputDataGeneralTextData())) + index += 1 + + + return entries +} + + +private func phoneNumberEntries( _ state: PassportState, updateState: @escaping ((PassportState)->PassportState)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + // + if let phone = (state.peer as? TelegramUser)?.phone, !phone.isEmpty { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(phone), error: nil, identifier: _id_phone_def, data: InputDataGeneralData(name: L10n.secureIdPhoneNumberUseSame(formatPhoneNumber(phone)), color: theme.colors.accent, icon: nil, type: .next, action: nil))) + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdPhoneNumberUseSameDesc), data: InputDataGeneralTextData())) + index += 1 + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + } + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdPhoneNumberHeader), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .string(""), identifier: _id_phone_new, equatable: nil, item: { initialSize, stableId -> TableRowItem in + return PassportNewPhoneNumberRowItem(initialSize, stableId: stableId, action: { + + }) + })) + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdPhoneNumberNote), data: InputDataGeneralTextData())) + index += 1 + + + return entries +} + +private func confirmPhoneNumberEntries( _ state: PassportState, phoneNumber: String, updateState: @escaping ((PassportState)->PassportState)->Void) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdPhoneNumberHeader), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_phone_code, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdPhoneNumberConfirmCodePlaceholder), inputPlaceholder: L10n.secureIdPhoneNumberConfirmCodeInputPlaceholder, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: 6)) + + index += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdPhoneNumberConfirmCodeDesc(formatPhoneNumber(phoneNumber))), data: InputDataGeneralTextData())) + index += 1 + + + return entries +} + +private func addressEntries( _ state: PassportState, hasMainField: Bool, relative: SecureIdRequestedFormFieldValue?, updateState: @escaping ((PassportState)->PassportState)->Void)->[InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + let nonFilter:(String)->String = { value in + return value + } + + + let address: SecureIdAddressValue? = hasMainField ? state.searchValue(.address)?.addressValue : nil + let relativeValue: SecureIdValue? = relative == nil ? nil : state.searchValue(relative!.valueKey) + + + let aErrors: [InputDataIdentifier : InputDataValueError]? = state.errors[.address] + + + if hasMainField { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdAddressHeader), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.addressIntermediateState?.street1 ?? .string(address?.street1), error: aErrors?[_id_street1], identifier: _id_street1, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdAddressStreetPlaceholder), inputPlaceholder: L10n.secureIdAddressStreetInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.addressIntermediateState?.street2 ?? .string(address?.street2), error: aErrors?[_id_street2], identifier: _id_street2, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(""), inputPlaceholder: L10n.secureIdAddressStreet1InputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.addressIntermediateState?.city ?? .string(address?.city), error: aErrors?[_id_city], identifier: _id_city, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdAddressCityPlaceholder), inputPlaceholder: L10n.secureIdAddressCityInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.addressIntermediateState?.state ?? .string(address?.state), error: aErrors?[_id_state], identifier: _id_state, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdAddressRegionPlaceholder), inputPlaceholder: L10n.secureIdAddressRegionInputPlaceholder, filter: nonFilter, limit: 255)) + index += 1 + + let filedata = try! String(contentsOfFile: Bundle.main.path(forResource: "countries", ofType: nil)!) + + let countries: [ValuesSelectorValue] = filedata.components(separatedBy: "\n").compactMap { country in + let entry = country.components(separatedBy: ";") + if entry.count >= 3 { + return ValuesSelectorValue(localized: entry[2], value: .string(entry[1])) + } else { + return nil + } + }.sorted(by: { $0.localized < $1.localized}) + + entries.append(InputDataEntry.selector(sectionId: sectionId, index: index, value: state.addressIntermediateState?.countryCode ?? .string(address?.countryCode), error: aErrors?[_id_country], identifier: _id_country, placeholder: L10n.secureIdAddressCountryPlaceholder, values: countries)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.addressIntermediateState?.postcode ?? .string(address?.postcode), error: aErrors?[_id_postcode], identifier: _id_postcode, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdAddressPostcodePlaceholder), inputPlaceholder: L10n.secureIdAddressPostcodeInputPlaceholder, filter: { text in + return latinFilter(text, .address, _id_postcode, true, updateState) + }, limit: 10)) + index += 1 + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + } + + + + if let relative = relative { + let rErrors = state.errors[relative.valueKey] + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdScansHeader), data: InputDataGeneralTextData())) + index += 1 + + if let scanError = rErrors?[_id_scan] { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(scanError.description), data: InputDataGeneralTextData(color: theme.colors.redUI))) + index += 1 + } + + let files = state.files[relative.valueKey] ?? [] + + var fileIndex: Int32 = 0 + + if let accessContext = state.accessContext { + for file in files { + let header = L10n.secureIdScanNumber(Int(fileIndex + 1)) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(file), identifier: InputDataIdentifier("_file_\(fileIndex)"), equatable: InputDataEquatable(file), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: file, context: accessContext, stableId: stableId), error: rErrors?[file.errorIdentifier], header: header, removeAction: { value in + updateState { current in + return current.withRemovedFile(value, for: relative.valueKey) + } + }) + })) + fileIndex += 1 + index += 1 + } + } + + if files.count < scansLimit { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_scan, placeholder: files.count > 0 ? L10n.secureIdUploadAdditionalScan : L10n.secureIdUploadScan, description: nil, icon: nil, action: { + filePanel(with: photoExts, allowMultiple: true, for: mainWindow, completion: { files in + if let files = files { + let localFiles:[SecureIdVerificationDocument] = files.map({.local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: $0, randomId: arc4random64()), state: .uploading(0)))}) + + updateState { current in + if localFiles.count + (current.files[relative.valueKey] ?? []).count > scansLimit { + alert(for: mainWindow, info: L10n.secureIdErrorScansLimit) + } + return current.withAppendFiles(localFiles, for: relative.valueKey).withRemovedError(for: relative.valueKey, field: _id_scan) + } + } + }) + })) + index += 1 + } + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdIdentityScanDescription), data: InputDataGeneralTextData())) + index += 1 + + + if relative.hasTranslation { + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdTranslationHeader), data: InputDataGeneralTextData())) + index += 1 + + if let translationError = rErrors?[_id_translation] { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(translationError.description), data: InputDataGeneralTextData(color: theme.colors.redUI))) + index += 1 + } + + let translations = state.translations[relative.valueKey] ?? [] + + var fileIndex = 0 + + if let accessContext = state.accessContext { + for translation in translations { + let header = L10n.secureIdScanNumber(Int(fileIndex + 1)) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(translation), identifier: InputDataIdentifier("_translation_\(fileIndex)"), equatable: InputDataEquatable(translation), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: translation, context: accessContext, stableId: stableId), error: rErrors?[translation.errorIdentifier], header: header, removeAction: { value in + updateState { current in + return current.withRemovedTranslation(value, for: relative.valueKey) + } + }) + })) + fileIndex += 1 + index += 1 + } + } + + if translations.count < scansLimit { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_translation, placeholder: translations.count > 0 ? L10n.secureIdUploadAdditionalScan : L10n.secureIdUploadScan, description: nil, icon: nil, action: { + filePanel(with: photoExts, allowMultiple: true, for: mainWindow, completion: { files in + if let files = files { + let localFiles:[SecureIdVerificationDocument] = files.map({.local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: $0, randomId: arc4random64()), state: .uploading(0)))}) + + updateState { current in + if localFiles.count + (current.translations[relative.valueKey] ?? []).count > scansLimit { + alert(for: mainWindow, info: L10n.secureIdErrorScansLimit) + } + return current.withAppendTranslations(localFiles, for: relative.valueKey).withRemovedError(for: relative.valueKey, field: _id_translation) + } + } + }) + })) + index += 1 + } + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdTranslationDesc), data: InputDataGeneralTextData())) + index += 1 + } + + } + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + if address != nil || relativeValue != nil { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_delete, data: InputDataGeneralData(name: relativeValue != nil ? L10n.secureIdDeleteIdentity : L10n.secureIdDeleteAddress, color: theme.colors.redUI, icon: nil, type: .none, action: nil))) + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + } + + return entries + +} + fileprivate let latinError = InputDataValueError(description: L10n.secureIdInputErrorLatinOnly, target: .data) + + private func latinFilter(_ text: String, _ valueKey: SecureIdValueKey, _ identifier: InputDataIdentifier, _ includeNumbers: Bool, _ updateState: @escaping((PassportState)->PassportState)->Void) -> String { + + let upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + let lower = "abcdefghijklmnopqrstuvwxyz" + let updated = text.trimmingCharacters(in: CharacterSet(charactersIn: upper + lower + (includeNumbers ? "0987654321-" : "")).inverted) + if updated != text { + updateState { current in + var errors = current.errors + var rErrors = errors[valueKey] ?? [:] + rErrors[identifier] = latinError + errors[valueKey] = rErrors + return current.withUpdatedErrors(errors) + } + } else { + updateState { current in + var errors = current.errors + var rErrors = errors[valueKey] ?? [:] + if rErrors[identifier] == latinError { + rErrors.removeValue(forKey: identifier) + } + errors[valueKey] = rErrors + return current.withUpdatedErrors(errors) + } + } + return updated + } + +private func identityEntries( _ state: PassportState, primary: SecureIdRequestedFormFieldValue?, relative: SecureIdRequestedFormFieldValue?, updateState: @escaping ((PassportState)->PassportState)->Void)->[InputDataEntry] { + var entries:[InputDataEntry] = [] + var sectionId:Int32 = 0 + var index: Int32 = 0 + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + + let personalDetails: SecureIdPersonalDetailsValue? = primary != nil ? state.searchValue(primary!.valueKey)?.personalDetails : nil + let relativeValue: SecureIdValue? = relative == nil ? nil : state.searchValue(relative!.valueKey) + + let pdErrors = state.errors[.personalDetails] + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdIdentityDocumentDetailsHeader), data: InputDataGeneralTextData())) + index += 1 + + + + + let addRelativeIdentifier:()->Void = { + if let relative = relative { + let rErrors = state.errors[relative.valueKey] + + var title: String = "" + var subtitle: String = "" + switch relative { + case .passport: + title = L10n.secureIdIdentityPassportPlaceholder + subtitle = L10n.secureIdIdentityPassportInputPlaceholder + case .internalPassport: + title = L10n.secureIdIdentityPassportPlaceholder + subtitle = L10n.secureIdIdentityPassportInputPlaceholder + case .idCard: + title = L10n.secureIdIdentityCardIdPlaceholder + subtitle = L10n.secureIdIdentityCardIdInputPlaceholder + case .driversLicense: + title = L10n.secureIdIdentityLicensePlaceholder + subtitle = L10n.secureIdIdentityLicenseInputPlaceholder + default: + break + } + + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.identifier ?? .string(relativeValue?.identifier), error: rErrors?[_id_identifier], identifier: _id_identifier, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(title), inputPlaceholder: subtitle, filter: { text in + return latinFilter(text, relative.valueKey, _id_identifier, true, updateState) + }, limit: 20)) + index += 1 + + entries.append(InputDataEntry.dateSelector(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.expiryDate ?? relativeValue?.expiryDate?.inputDataValue ?? .date(nil, nil, nil), error: rErrors?[_id_expire_date], identifier: _id_expire_date, placeholder: L10n.secureIdIdentityPlaceholderExpiryDate)) + index += 1 + } + } + + if let primary = primary { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdIdentityNameInLatine), data: InputDataGeneralTextData())) + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.firstName ?? .string(personalDetails?.latinName.firstName ?? ""), error: pdErrors?[_id_first_name], identifier: _id_first_name, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderFirstName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderFirstName, filter: { text in + return latinFilter(text, primary.valueKey, _id_first_name, false, updateState) + }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.middleName ?? .string(personalDetails?.latinName.middleName ?? ""), error: pdErrors?[_id_middle_name], identifier: _id_middle_name, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderMiddleName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderMiddleName, filter: { text in + return latinFilter(text, primary.valueKey, _id_middle_name, false, updateState) + }, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.lastName ?? .string(personalDetails?.latinName.lastName ?? ""), error: pdErrors?[_id_last_name], identifier: _id_last_name, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderLastName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderLastName, filter: { text in + return latinFilter(text, primary.valueKey, _id_last_name, false, updateState) + }, limit: 255)) + index += 1 + + let genders:[ValuesSelectorValue] = [ValuesSelectorValue(localized: L10n.secureIdGenderMale, value: .gender(.male)), ValuesSelectorValue(localized: L10n.secureIdGenderFemale, value: .gender(.female))] + + entries.append(InputDataEntry.selector(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.gender ?? .gender(personalDetails?.gender), error: pdErrors?[_id_gender], identifier: _id_gender, placeholder: L10n.secureIdIdentityPlaceholderGender, values: genders)) + index += 1 + + entries.append(InputDataEntry.dateSelector(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.birthday ?? personalDetails?.birthdate.inputDataValue ?? .date(nil, nil, nil), error: pdErrors?[_id_birthday], identifier: _id_birthday, placeholder: L10n.secureIdIdentityPlaceholderBirthday)) + index += 1 + + let filedata = try! String(contentsOfFile: Bundle.main.path(forResource: "countries", ofType: nil)!) + + let countries: [ValuesSelectorValue] = filedata.components(separatedBy: "\n").compactMap { country in + let entry = country.components(separatedBy: ";") + if entry.count >= 3 { + return ValuesSelectorValue(localized: entry[2], value: .string(entry[1])) + } else { + return nil + } + }.sorted(by: { $0.localized < $1.localized}) + + entries.append(InputDataEntry.selector(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.citizenship ?? .string(personalDetails?.countryCode), error: pdErrors?[_id_country], identifier: _id_country, placeholder: L10n.secureIdIdentityPlaceholderCitizenship, values: countries)) + index += 1 + + let residence = state.detailsIntermediateState?.residence ?? .string(personalDetails?.residenceCountryCode) + + entries.append(InputDataEntry.selector(sectionId: sectionId, index: index, value: residence, error: pdErrors?[_id_residence], identifier: _id_residence, placeholder: L10n.secureIdIdentityPlaceholderResidence, values: countries)) + index += 1 + + if let _ = relative { + addRelativeIdentifier() + } + + if case let .personalDetails(nativeName) = primary, nativeName { + if let residence = residence.stringValue { + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + if state.configuration?.nativeLanguageByCountry[residence] != "en" { + + let country = countries.filter({$0.value.stringValue == residence}).first?.localized ?? residence + + var localizedDesc: String = "" + var localizedTitle: String = L10n.secureIdNameNativeHeaderEmpty + if let language = state.configuration?.nativeLanguageByCountry[residence] { + let key = "Passport.Language.\(language)" + let localizedKey = localizedString(key) + if localizedKey == key { + localizedDesc = L10n.secureIdNameNativeDescLanguage(country) + } else { + localizedTitle = L10n.secureIdNameNativeHeader(localizedKey.uppercased()) + localizedDesc = L10n.secureIdNameNativeDescEmpty + } + } else { + localizedDesc = L10n.secureIdNameNativeDescLanguage(country) + } + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(localizedTitle), data: InputDataGeneralTextData())) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.firstNameNative ?? .string(personalDetails?.nativeName?.firstName ?? ""), error: pdErrors?[_id_first_name_native], identifier: _id_first_name_native, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderFirstName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderFirstName, filter: {$0}, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.middleNameNative ?? .string(personalDetails?.nativeName?.middleName ?? ""), error: pdErrors?[_id_middle_name_native], identifier: _id_middle_name_native, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderMiddleName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderMiddleName, filter: {$0}, limit: 255)) + index += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: state.detailsIntermediateState?.lastNameNative ?? .string(personalDetails?.nativeName?.lastName ?? ""), error: pdErrors?[_id_last_name_native], identifier: _id_last_name_native, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdIdentityPlaceholderLastName), inputPlaceholder: L10n.secureIdIdentityInputPlaceholderLastName, filter: {$0}, limit: 255)) + index += 1 + + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(localizedDesc), data: InputDataGeneralTextData())) + index += 1 + + } + } + } + + } + + + + + + + + + + if let relative = relative { + + if primary == nil { + addRelativeIdentifier() + } + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + let rErrors = state.errors[relative.valueKey] + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdScansHeader), data: InputDataGeneralTextData())) + index += 1 + + if let scanError = rErrors?[_id_scan] { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(scanError.description), data: InputDataGeneralTextData(color: theme.colors.redUI))) + index += 1 + } + if let accessContext = state.accessContext { + let isMainNotFront: Bool = !relative.hasBacksideDocument + if let file = state.frontSideFile[relative.valueKey] { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(file), identifier: _id_frontside, equatable: InputDataEquatable(file), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: file, context: accessContext, stableId: stableId), error: rErrors?[_id_frontside], header: isMainNotFront ? L10n.secureIdUploadTitleMainPage : L10n.secureIdUploadTitleFrontSide, removeAction: { value in + modernConfirm(for: mainWindow, account: state.context.account, peerId: nil, information: L10n.secureIdConfirmDeleteDocument, successHandler: { _ in + updateState { current in + return current.withUpdatedFrontSide(nil, for: relative.valueKey) + } + }) + }) + })) + index += 1 + } else { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_frontside, placeholder: isMainNotFront ? L10n.secureIdUploadTitleMainPage : L10n.secureIdUploadTitleFrontSide, description: relative.uploadFrontTitleText, icon: isMainNotFront ? theme.icons.passportPassport : (relative.valueKey == .driversLicense ? theme.icons.passportDriverLicense : theme.icons.passportIdCard), action: { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { files in + if let file = files?.first { + updateFrontMrz(file: file, relative: relative, updateState: updateState) + } + }) + })) + index += 1 + } + + if relative.hasBacksideDocument { + if let file = state.backSideFile[relative.valueKey] { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(file), identifier: _id_backside, equatable: InputDataEquatable(file), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: file, context: accessContext, stableId: stableId), error: rErrors?[_id_backside], header: L10n.secureIdUploadTitleReverseSide, removeAction: { value in + updateState { current in + return current.withUpdatedBackSide(nil, for: relative.valueKey) + } + }) + })) + index += 1 + } else { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_backside, placeholder: isMainNotFront ? L10n.secureIdUploadTitleMainPage : L10n.secureIdUploadTitleReverseSide, description: relative.uploadBackTitleText, icon: theme.icons.passportIdCardReverse, action: { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { files in + if let file = files?.first { + if let image = NSImage(contentsOfFile: file) { + let string = recognizeMRZ(image.precomposed(), nil) + let mrz = TGPassportMRZ.parseLines(string?.components(separatedBy: "\n")) + let localFile:SecureIdVerificationDocument = .local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: file, randomId: arc4random64()), state: .uploading(0))) + + updateState { current in + var current = current + if let mrz = mrz { + if relative.isEqualToMRZ(mrz) { + let expiryDate = dateFormatter.string(from: mrz.expiryDate).components(separatedBy: ".").map({Int32($0)}) + let birthDate = dateFormatter.string(from: mrz.birthDate).components(separatedBy: ".").map({Int32($0)}) + let details = DetailsIntermediateState(firstName: .string(mrz.firstName), middleName: nil, lastName: .string(mrz.lastName), firstNameNative: nil, middleNameNative: nil, lastNameNative: nil, birthday: .date(birthDate[0], birthDate[1], birthDate[2]), citizenship: .string(mrz.issuingCountry), residence: current.detailsIntermediateState?.residence, gender: .gender(SecureIdGender.gender(from: mrz)), expiryDate: .date(expiryDate[0], expiryDate[1], expiryDate[2]), identifier: .string(mrz.documentNumber)) + current = current.withUpdatedDetailsState(details) + } + } + return current.withUpdatedBackSide(localFile, for: relative.valueKey) + } + } + } + }) + })) + index += 1 + } + } + } + + + if relative.hasSelfie, let accessContext = state.accessContext { + let rErrors = state.errors[relative.valueKey] + if let selfie = state.selfies[relative.valueKey] { + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(selfie), identifier: _id_selfie, equatable: InputDataEquatable(selfie), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: selfie, context: accessContext, stableId: stableId), error: rErrors?[_id_selfie], header: L10n.secureIdIdentitySelfie, removeAction: { value in + updateState { current in + return current.withUpdatedSelfie(nil, for: relative.valueKey) + } + }) + })) + index += 1 + + } else { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_selfie_scan, placeholder: L10n.secureIdIdentitySelfie, description: L10n.secureIdUploadSelfie, icon: theme.icons.passportSelfie, action: { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { paths in + if let path = paths?.first, let image = NSImage(contentsOfFile: path) { + _ = putToTemp(image: image).start(next: { path in + let localFile:SecureIdVerificationDocument = .local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()), state: .uploading(0))) + + updateState { current in + return current.withUpdatedSelfie(localFile, for: relative.valueKey) + } + }) + } + }) + })) + index += 1 + } + } + + if relative.hasTranslation { + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdTranslationHeader), data: InputDataGeneralTextData())) + index += 1 + + if let translationError = rErrors?[_id_translation] { + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(translationError.description), data: InputDataGeneralTextData(color: theme.colors.redUI))) + index += 1 + } + + let translations = state.translations[relative.valueKey] ?? [] + + var fileIndex: Int32 = 0 + + if let accessContext = state.accessContext { + for translation in translations { + let header = L10n.secureIdScanNumber(Int(fileIndex + 1)) + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .secureIdDocument(translation), identifier: InputDataIdentifier("_translation_\(fileIndex)"), equatable: InputDataEquatable(translation), item: { initialSize, stableId -> TableRowItem in + return PassportDocumentRowItem(initialSize, context: state.context, document: SecureIdDocumentValue(document: translation, context: accessContext, stableId: stableId), error: rErrors?[translation.errorIdentifier], header: header, removeAction: { value in + updateState { current in + return current.withRemovedTranslation(value, for: relative.valueKey) + } + }) + })) + fileIndex += 1 + index += 1 + } + } + + if translations.count < scansLimit { + entries.append(InputDataEntry.dataSelector(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_translation, placeholder: translations.count > 0 ? L10n.secureIdUploadAdditionalScan : L10n.secureIdUploadScan, description: nil, icon: nil, action: { + filePanel(with: photoExts, allowMultiple: true, for: mainWindow, completion: { files in + if let files = files { + let localFiles:[SecureIdVerificationDocument] = files.map({.local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: $0, randomId: arc4random64()), state: .uploading(0)))}) + + updateState { current in + if localFiles.count + (current.translations[relative.valueKey] ?? []).count > scansLimit { + alert(for: mainWindow, info: L10n.secureIdErrorScansLimit) + } + return current.withAppendTranslations(localFiles, for: relative.valueKey).withRemovedError(for: relative.valueKey, field: _id_translation) + } + } + }) + })) + index += 1 + } + + + entries.append(InputDataEntry.desc(sectionId: sectionId, index: index, text: .plain(L10n.secureIdTranslationDesc), data: InputDataGeneralTextData())) + index += 1 + } + + +// entries.append(.desc(sectionId: sectionId, index: index, text: L10n.secureIdIdentityScanDescription, data: InputDataGeneralTextData())) +// index += 1 + + } + + + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + if personalDetails != nil || relativeValue != nil { + entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_delete, data: InputDataGeneralData(name: relativeValue != nil ? L10n.secureIdDeleteIdentity : L10n.secureIdDeletePersonalDetails, color: theme.colors.redUI, icon: nil, type: .none, action: nil))) + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + } + + + return entries +} + + private func recoverEmailEntries(emailPattern: String, unavailable: @escaping() -> Void) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .legacy)) + sectionId += 1 + + entries.append(InputDataEntry.input(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_email_code, mode: .plain, data: InputDataRowData(), placeholder: InputDataInputPlaceholder(L10n.secureIdEmailActivateCodePlaceholder), inputPlaceholder: L10n.secureIdEmailActivateCodeInputPlaceholder, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: 6)) + index += 1 + + + let info = L10n.twoStepAuthRecoveryCodeHelp + "\n\n\(L10n.twoStepAuthRecoveryEmailUnavailableNew(emailPattern))" + + entries.append(.desc(sectionId: sectionId, index: index, text: .markdown(info, linkHandler: { _ in + unavailable() + }), data: InputDataGeneralTextData(detectBold: false))) + + + index += 1 + + return entries +} + +final class PassportControllerView : View { + let tableView: TableView = TableView() + let authorize: PassportAcceptRowView = PassportAcceptRowView(frame: NSZeroRect) + private var item: PassportAcceptRowItem? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tableView) + addSubview(authorize) + updateLocalizationAndTheme(theme: theme) + layout() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + backgroundColor = theme.colors.background + } + + override func layout() { + super.layout() + tableView.frame = NSMakeRect(0, 0, frame.width, frame.height - 80) + authorize.frame = NSMakeRect(0, frame.height - 80, frame.width, 80) + } + + func updateEnabled(_ enabled: Bool, isVisible: Bool, action: @escaping(Bool)->Void) { + self.item = PassportAcceptRowItem(authorize.frame.size, stableId: 0, enabled: enabled, action: { + action(enabled) + }) + authorize.set(item: item!, animated: false) + authorize.isHidden = !isVisible + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class PassportController: TelegramGenericViewController { + + private let form: EncryptedSecureIdForm? + private let disposable = MetaDisposable() + private let secureIdConfigurationDisposable = MetaDisposable() + private let peer: Peer + private let request: inAppSecureIdRequest? + private var pendingEmailConfirm: () -> Bool = { return false } + init(_ context: AccountContext, _ peer: Peer, request: inAppSecureIdRequest?, _ form: EncryptedSecureIdForm?) { + self.form = form + self.peer = peer + self.request = request + super.init(context) + + } + + + override var enableBack: Bool { + return true + } + + override func backSettings() -> (String, CGImage?) { + return (form == nil ? L10n.navigationBack : "", form == nil ? #imageLiteral(resourceName: "Icon_NavigationBack").precomposed(theme.colors.accentIcon) : theme.icons.dismissPinned) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + let index = self.genericView.tableView.row(at: self.genericView.tableView.documentView!.convert(event.locationInWindow, from: nil)) + + if index > 0, let view = self.genericView.tableView.item(at: index).view { + if view.mouseInsideField { + if self.window?.firstResponder != view.firstResponder { + _ = self.window?.makeFirstResponder(view.firstResponder) + return .invoked + } + } + } + + return .rejected + }, with: self, for: .leftMouseDown) + } + + override func requestUpdateRightBar() { + rightView?.set(image: theme.icons.passportInfo, for: .Normal) + } + override func requestUpdateBackBar() { + super.requestUpdateBackBar() + (leftBarView as? TextButtonBarView)?.direction = self.request == nil ? .left : .right + } + + private var rightView: TextButtonBarView? + + override func getRightBarViewOnce() -> BarView { + rightView = TextButtonBarView(controller: self, text:"") + rightView?.direction = .right + rightView?.alignment = .Right + return rightView! + } + + override func viewDidLoad() { + super.viewDidLoad() + + + let inAppRequest = self.request + let encryptedForm = self.form + let formValue: Promise<(EncryptedSecureIdForm?, SecureIdForm?)> = Promise() + + formValue.set(.single((form, nil))) + + let initialSize = self.atomicSize + let context = self.context + + let actionsDisposable = DisposableSet() + let checkPassword = MetaDisposable() + let authorizeDisposable = MetaDisposable() + let emailNewActivationDisposable = MetaDisposable() + let phoneNewActivationDisposable = MetaDisposable() + let recoverPasswordDisposable = MetaDisposable() + + actionsDisposable.add(checkPassword) + actionsDisposable.add(authorizeDisposable) + actionsDisposable.add(emailNewActivationDisposable) + actionsDisposable.add(phoneNewActivationDisposable) + actionsDisposable.add(recoverPasswordDisposable) + + let state:ValuePromise = ValuePromise(PassportState(context: context, peer: peer, tmpPwd: context.temporaryPassword, viewState: .plain), ignoreRepeated: true) + + let stateValue:Atomic = Atomic(value: PassportState(context: context, peer: peer, tmpPwd: context.temporaryPassword, viewState: .plain)) + + var _stateValue: PassportState { + return stateValue.modify({$0}) + } + + let updateState:((PassportState)->PassportState) -> Void = { f in + state.set(stateValue.modify(f)) + } + + let closeAfterSuccessful:()->Void = { [weak self] in + _ = self?.window?.closeInterceptor?() + } + + let closeController:()->Void = { [weak self] in + self?.navigationController?.back() + } + + + let passwordVerificationData: Promise = Promise() + + + + let emailActivation = MetaDisposable() + let saveValueDisposable = MetaDisposable() + actionsDisposable.add(emailActivation) + actionsDisposable.add(saveValueDisposable) + + let updateVerifyDocumentState: (Int64, SecureIdVerificationLocalDocumentState) -> Void = { id, state in + updateState { current in + return current.withUpdatedFileState(id: id, state: state) + } + } + + var checkPwd:((String) -> Void)? + + self.pendingEmailConfirm = { [weak self] in + + let code = stateValue.with { $0.emailCode } + if code.isEmpty, let `self` = self { + self.genericView.tableView.item(stableId: PassportEntryId.inputEmailCode)?.view?.shakeView() + return false + } + + _ = (passwordVerificationData.get() |> take(1)).start(next: { configuration in + if let configuration = configuration { + let pending: TwoStepVerificationPendingEmail? + switch configuration { + case let .set(_, _, pendingEmail, _): + pending = pendingEmail + case let .notSet(pendingEmail): + pending = pendingEmail + } + if let _ = pending { + let code = stateValue.with { $0.emailCode } + let state = _stateValue + if !code.isEmpty { + emailActivation.set(showModalProgress(signal: confirmTwoStepRecoveryEmail(network: context.account.network, code: code) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + + let text: String + switch error { + case .invalidEmail: + text = L10n.twoStepAuthEmailInvalid + case .invalidCode: + text = L10n.twoStepAuthEmailCodeInvalid + case .expired: + text = L10n.twoStepAuthEmailCodeExpired + case .flood: + text = L10n.twoStepAuthFloodError + case .generic: + text = L10n.unknownError + } + updateState { $0.withUpdatedEmailCodeError(InputDataValueError(description: text, target: .data)) } + + }, completed: { + passwordVerificationData.set( showModalProgress(signal: twoStepVerificationConfiguration(account: context.account) |> mapToSignal { config in + if let password = state.password { + switch password { + case let .password(password, _): + switch config { + case .set: + if let encryptedForm = encryptedForm { + return accessSecureId(network: context.account.network, password: password) |> map { values in + return (decryptedSecureIdForm(context: values.context, form: encryptedForm), values.context, values.settings) + } |> deliverOnMainQueue |> map { form, ctx, settings in + + + updateState { current in + var current = current + if let form = form { + current = form.values.reduce(current, { current, value -> PassportState in + return current.withUpdatedValue(value) + }) + } + //return current + return current.withUpdatedAccessContext(ctx).withUpdatedPasswordSettings(settings).withUpdatedVerifyDocumentContext(SecureIdVerificationDocumentsContext(postbox: context.account.postbox, network: context.account.network, context: ctx, update: updateVerifyDocumentState)) + } + formValue.set(.single((nil, form))) + return Optional(config) + } |> `catch` { _ in return .single(nil) } + } else { + let signal = accessSecureId(network: context.account.network, password: password) |> mapToSignal { values in + return getAllSecureIdValues(network: context.account.network) + |> map { encryptedValues in + return decryptedAllSecureIdValues(context: values.context, encryptedValues: encryptedValues) + } + |> mapError {_ in return SecureIdAccessError.generic} + |> map {($0, values.context, values.settings)} + } |> deliverOnMainQueue + + return signal |> map { values, ctx, settings in + updateState { current in + var current = current.withRemovedValues() + current = values.reduce(current, { current, value -> PassportState in + return current.withUpdatedValue(value) + }) + return current.withUpdatedViewState(.settings).withUpdatedPasswordSettings(settings).withUpdatedAccessContext(ctx).withUpdatedVerifyDocumentContext(SecureIdVerificationDocumentsContext(postbox: context.account.postbox, network: context.account.network, context: ctx, update: updateVerifyDocumentState)) + } + return Optional(config) + } |> `catch` { _ in return .single(nil) } + + } + + default: + return .single(Optional(config)) + } + case .none: + return .single(Optional(config)) + } + } + return .single(Optional(config)) + }, for: mainWindow)) + })) + } + } + } + }) + + + + return true + } + + +// emailActivation.set((combineLatest(isKeyWindow.get() |> deliverOnPrepareQueue, Signal.single(Void()) |> delay(3.0, queue: prepareQueue) |> restart) |> mapToSignal { _ in return combineLatest(passwordVerificationData.get() |> take(1) |> deliverOnPrepareQueue, state.get() |> take(1) |> deliverOnPrepareQueue) }).start(next: { config, state in +// if let config = config { +// } +// })) + + + let presentController:(ViewController)->Void = { [weak self] controller in + self?.navigationController?.push(controller) + } + + let executeCallback:(Bool) -> Void = { [weak self] success in + self?.executeCallback(success) + } + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + + + let arguments = PassportArguments(context: context, checkPassword: { value, shake in + if value.isEmpty { + shake() + return + } + if let encryptedForm = encryptedForm { + checkPassword.set((accessSecureId(network: context.account.network, password: value) |> map { data in + return (decryptedSecureIdForm(context: data.context, form: encryptedForm), data.context, data.settings) + } |> deliverOnMainQueue).start(next: { form, ctx, settings in + + context.setTemporaryPwd(value) + + updateState { current in + var current = current.withRemovedValues() + if let form = form { + current = form.values.reduce(current, { current, value -> PassportState in + return current.withUpdatedValue(value) + }) + var errors:[SecureIdValueKey: [InputDataIdentifier : InputDataValueError]] = [:] + + for value in form.values { + var cErrors = errors[value.value.key] ?? [:] + + for(eKey, eValue) in value.errors { + switch eKey { + case let .field(field): + switch field { + case let .address(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + case let .driversLicense(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + case let .idCard(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + case let .passport(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + case let .personalDetails(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + case let .internalPassport(f): + cErrors[InputDataIdentifier(f.rawValue)] = InputDataValueError(description: eValue, target: .data) + } + case .files: + cErrors[_id_scan] = InputDataValueError(description: eValue, target: .files) + case let .file(hash): + cErrors[InputDataIdentifier("file_\(hash.base64EncodedString())")] = InputDataValueError(description: eValue, target: .files) + case .selfie: + cErrors[_id_selfie] = InputDataValueError(description: eValue, target: .files) + case .frontSide: + cErrors[_id_frontside] = InputDataValueError(description: eValue, target: .files) + case .backSide: + cErrors[_id_backside] = InputDataValueError(description: eValue, target: .files) + case let .translationFile(hash): + cErrors[InputDataIdentifier("file_\(hash.base64EncodedString())")] = InputDataValueError(description: eValue, target: .files) + case .translationFiles: + cErrors[_id_translation] = InputDataValueError(description: eValue, target: .files) + case let .value(valueKey): + if valueKey == value.value.key { + cErrors[InputDataEmptyIdentifier] = InputDataValueError(description: eValue, target: .data) + } + //errors[valueKey] = [InputDataEmptyIdentifier : InputDataValueError(description: eValue, target: .data)] + var bp:Int = 0 + bp += 1 + } + } + errors[value.value.key] = cErrors + } + current = current.withUpdatedErrors(errors) + } + return current.withUpdatedAccessContext(ctx).withUpdatedPasswordSettings(settings).withUpdatedVerifyDocumentContext(SecureIdVerificationDocumentsContext(postbox: context.account.postbox, network: context.account.network, context: ctx, update: updateVerifyDocumentState)).withUpdatedPasswordError(nil) + } + formValue.set(.single((nil, form))) + }, error: { error in + switch error { + case .secretPasswordMismatch: + confirm(for: mainWindow, header: L10n.telegramPassportController, information: "Something going wrong", thridTitle: "Delete All Values", successHandler: { result in + switch result { + case .basic: + break + case .thrid: + _ = showModalProgress(signal: updateTwoStepVerificationPassword(network: context.account.network, currentPassword: value, updatedPassword: .none) |> deliverOnMainQueue, for: mainWindow).start(next: {_ in + updateState { current in + return current.withUpdatedPassword(nil) + } + passwordVerificationData.set(.single(.notSet(pendingEmail: nil))) + }, error: { error in + + }) + } + }) + case .passwordError(let error): + updateState { current in + switch error { + case .invalidPassword: + return current.withUpdatedPasswordError(L10n.secureIdPasswordErrorInvalid) + case .limitExceeded: + return current.withUpdatedPasswordError(L10n.secureIdPasswordErrorLimit) + default: + return current.withUpdatedPasswordError(L10n.secureIdPasswordErrorInvalid) + } + } + shake() + + case .generic: + shake() + } + + })) + } else { + let signal = accessSecureId(network: context.account.network, password: value) |> mapToSignal { data in + return getAllSecureIdValues(network: context.account.network) + |> map { encryptedValues in + return decryptedAllSecureIdValues(context: data.context, encryptedValues: encryptedValues) + } + |> mapError {_ in return SecureIdAccessError.generic} + |> map {($0, data.context, data.settings)} + } |> deliverOnMainQueue + + + + checkPassword.set(signal.start(next: { values, ctx, passwordSettings in + + context.setTemporaryPwd(value) + + updateState { current in + var current = current.withRemovedValues() + current = values.reduce(current, { current, value -> PassportState in + return current.withUpdatedValue(value) + }) + return current.withUpdatedViewState(.settings).withUpdatedPasswordSettings(passwordSettings).withUpdatedAccessContext(ctx).withUpdatedVerifyDocumentContext(SecureIdVerificationDocumentsContext(postbox: context.account.postbox, network: context.account.network, context: ctx, update: updateVerifyDocumentState)) + } + }, error: { error in + switch error { + case .secretPasswordMismatch: + confirm(for: mainWindow, header: L10n.telegramPassportController, information: "Something going wrong", thridTitle: "Delete All Values", successHandler: { result in + switch result { + case .basic: + break + case .thrid: + _ = showModalProgress(signal: updateTwoStepVerificationPassword(network: context.account.network, currentPassword: value, updatedPassword: .none) |> deliverOnMainQueue, for: mainWindow).start(next: {_ in + updateState { current in + return current.withUpdatedPassword(nil) + } + passwordVerificationData.set(.single(.notSet(pendingEmail: nil))) + }, error: { error in + + }) + } + }) + case .passwordError: + shake() + case .generic: + shake() + } + })) + } + }, requestField: { request, value, relativeValue, relative, editSettings in + + let valueKey = value?.key + let proccessValue:([SecureIdValue])->InputDataValidation = { values in + return .fail(.doSomething(next: { f in + + let signal: Signal<[SecureIdValueWithContext], SaveSecureIdValueError> = state.get() |> take(1) |> mapError {_ in return SaveSecureIdValueError.generic} |> mapToSignal { state in + if let ctx = state.accessContext { + return combineLatest(values.map({ value in + return saveSecureIdValue(postbox: context.account.postbox, network: context.account.network, context: ctx, value: value, uploadedFiles: [:]) + })) + } else { + return .fail(.generic) + } + } |> deliverOnMainQueue + + saveValueDisposable.set(showModalProgress(signal: signal, for: mainWindow).start(next: { values in + updateState { current in + return values.reduce(current, { current, value in + return current.withUpdatedValue(value).withRemovedErrors(for: value.value.key).withUpdatedEmptyErrors(false) + }) + } + f(.success(.navigationBack)) + }, error: { error in + f(.fail(.alert("\(error)"))) + })) + })) + } + + let removeValue:(SecureIdValueKey) -> Void = { valueKey in + saveValueDisposable.set(showModalProgress(signal: deleteSecureIdValues(network: context.account.network, keys: Set(arrayLiteral: valueKey)), for: mainWindow).start(completed: { + updateState { current in + return current.withRemovedValue(valueKey) + } + })) + } + + let removeValueInteractive:([SecureIdValueKey]) -> InputDataValidation = { valueKeys in + return .fail(.doSomething { f in + saveValueDisposable.set(showModalProgress(signal: deleteSecureIdValues(network: context.account.network, keys: Set(valueKeys)) |> deliverOnMainQueue, for: mainWindow).start(completed: { + updateState { current in + return valueKeys.reduce(current, { (current, key) in + return current.withRemovedValue(key) + }) + } + f(.success(.navigationBack)) + })) + }) + } + + switch request.primary { + case .address: + var loadedData: AddressIntermediateState? + let push:(SecureIdRequestedFormFieldValue, SecureIdRequestedFormFieldValue?, Bool) -> Void = { field, relative, hasMainField in + presentController(InputDataController(dataSignal: combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return addressEntries(state, hasMainField: hasMainField, relative: relative, updateState: updateState) + } |> map { InputDataSignalValue(entries: $0) }, title: relative?.rawValue ?? field.rawValue, validateData: { data in + + if let _ = data[_id_delete] { + return .fail(.doSomething { next in + modernConfirm(for: mainWindow, account: context.account, peerId: nil, header: L10n.telegramPassportController, information: relative == nil ? L10n.secureIdConfirmDeleteAddress : L10n.secureIdConfirmDeleteDocument, thridTitle: hasMainField ? L10n.secureIdConfirmDeleteAddress : nil, successHandler: { result in + var keys: [SecureIdValueKey] = [] + if let relative = relative { + keys.append(relative.valueKey) + } + switch result { + case .basic: + break + case .thrid: + keys.append(field.valueKey) + } + next(removeValueInteractive(keys)) + }) + }) + } + + let current = AddressIntermediateState(data) + + + + let street1 = data[_id_street1]?.stringValue ?? "" + let street2 = data[_id_street2]?.stringValue ?? "" + let city = data[_id_city]?.stringValue ?? "" + let state = data[_id_state]?.stringValue ?? "" + let countryCode = data[_id_country]?.stringValue ?? "" + let postcode = data[_id_postcode]?.stringValue ?? "" + + var fails:[InputDataIdentifier : InputDataValidationFailAction] = [:] + if street1.isEmpty && hasMainField { + fails[_id_street1] = .shake + } + if countryCode.isEmpty && hasMainField { + fails[_id_country] = .shake + } + if city.isEmpty && hasMainField { + fails[_id_city] = .shake + } + if postcode.isEmpty && hasMainField { + fails[_id_postcode] = .shake + } + + var fileIndex: Int = 0 + var verifiedDocuments:[SecureIdVerificationDocumentReference] = [] + while data[InputDataIdentifier("_file_\(fileIndex)")] != nil { + let identifier = InputDataIdentifier("_file_\(fileIndex)") + let value = data[identifier]!.secureIdDocument! + switch value { + case let .remote(reference): + verifiedDocuments.append(.remote(reference)) + case let .local(local): + switch local.state { + case let .uploaded(file): + verifiedDocuments.append(.uploaded(file)) + case .uploading: + fails[identifier] = .shake + } + + } + fileIndex += 1 + } + + var translations:[SecureIdVerificationDocumentReference] = [] + fileIndex = 0 + while data[InputDataIdentifier("_translation_\(fileIndex)")] != nil { + let identifier = InputDataIdentifier("_translation_\(fileIndex)") + let value = data[identifier]!.secureIdDocument! + switch value { + case let .remote(reference): + translations.append(.remote(reference)) + case let .local(local): + switch local.state { + case let .uploaded(file): + translations.append(.uploaded(file)) + case .uploading: + fails[identifier] = .shake + } + + } + fileIndex += 1 + } + + if let relative = relative, relative.hasTranslation, translations.isEmpty, editSettings == nil { + fails[_id_translation] = .shake + } + + if relative != nil, verifiedDocuments.isEmpty { + fails[_id_scan] = .shake + } + + + if !fails.isEmpty { + return .fail(.fields(fails)) + } + + var values:[SecureIdValue] = [] + if let relative = relative { + switch relative { + case .bankStatement: + values.append(SecureIdValue.bankStatement(SecureIdBankStatementValue(verificationDocuments: verifiedDocuments, translations: translations))) + case .rentalAgreement: + values.append(SecureIdValue.rentalAgreement(SecureIdRentalAgreementValue(verificationDocuments: verifiedDocuments, translations: translations))) + case .utilityBill: + values.append(SecureIdValue.utilityBill(SecureIdUtilityBillValue(verificationDocuments: verifiedDocuments, translations: translations))) + case .passportRegistration: + values.append(SecureIdValue.passportRegistration(SecureIdPassportRegistrationValue(verificationDocuments: verifiedDocuments, translations: translations))) + case .temporaryRegistration: + values.append(SecureIdValue.temporaryRegistration(SecureIdTemporaryRegistrationValue(verificationDocuments: verifiedDocuments, translations: translations))) + default: + break + } + } + if hasMainField { + values.append(SecureIdValue.address(SecureIdAddressValue(street1: street1, street2: street2, city: city, state: state, countryCode: countryCode, postcode: postcode))) + } + + if let loadedData = loadedData { + var fails = loadedData.validateErrors(currentState: current, errors: _stateValue.errors(for: .address)) + if let relative = relative { + let errors = _stateValue.errors(for: relative.valueKey) + for error in errors { + var i: Int = 0 + for file in verifiedDocuments { + switch file { + case let .remote(reference): + if error.key == InputDataIdentifier("file_\(reference.fileHash.base64EncodedString())") { + fails[InputDataIdentifier("_file_\(i)")] = .shake + } + i += 1 + default: + break + } + } + for file in translations { + switch file { + case let .remote(reference): + if error.key == InputDataIdentifier("file_\(reference.fileHash.base64EncodedString())") { + fails[InputDataIdentifier("_translation_\(i)")] = .shake + } + i += 1 + default: + break + } + } + } + } + if fails.isEmpty { + if loadedData == current && values.last == value { + return .success(.navigationBack) + } + return proccessValue(values) + } else { + return .fail(.fields(fails)) + } + } + + return .fail(.none) + }, updateDatas: { data in + updateState { current in + var current = current + let address = AddressIntermediateState(data) + var errors = current.errors + + if let loadedData = loadedData { + let updatedErrors = loadedData.removeErrors(currentState: address, errors: errors[request.primary.valueKey]) + errors[request.primary.valueKey] = updatedErrors + current = current.withUpdatedErrors(errors) + } + return current.withUpdatedAddressState(address) + } + + return .fail(.none) + }, afterDisappear: { + updateState { current in + return current.withUpdatedAddressState(nil).withUpdatedValues(current.values).withRemovedInputErrors() + } + }, didLoaded: { _, data in + loadedData = AddressIntermediateState(data) + }, identifier: "passport", backInvocation: { data, f in + if AddressIntermediateState(data) != loadedData { + confirm(for: mainWindow, header: L10n.secureIdDiscardChangesHeader, information: L10n.secureIdDiscardChangesText, okTitle: L10n.alertConfirmDiscard, successHandler: { _ in + f(true) + }) + } else { + f(true) + } + + }, getBackgroundColor: { theme.colors.background })) + } + + if let editSettings = editSettings { + var values:[ValuesSelectorValue] = [] + for relative in relative { + values.append(ValuesSelectorValue(localized: editSettings.hasValue(relative) ? relative.descEdit : relative.descAdd, value: relative)) + } + showModal(with: ValuesSelectorModalController(values: values, selected: values[0], title: L10n.secureIdIdentityDocument, onComplete: { selected in + push(selected.value, selected.value == .address ? nil : selected.value, selected.value == .address) + }), for: mainWindow) + } else if relative.count > 1 { + let values:[ValuesSelectorValue] = relative.map({ValuesSelectorValue(localized: $0.rawValue, value: $0)}) + showModal(with: ValuesSelectorModalController(values: values, selected: values[0], title: L10n.secureIdResidentialAddress, onComplete: { selected in + filePanel(with: photoExts,for: mainWindow, completion: { files in + if let files = files { + push(request.primary, selected.value, request.fillPrimary) + let localFiles:[SecureIdVerificationDocument] = files.map({SecureIdVerificationDocument.local(SecureIdVerificationLocalDocument(id: arc4random64(), resource: LocalFileReferenceMediaResource(localFilePath: $0, randomId: arc4random64()), state: .uploading(0)))}) + updateState { current in + if localFiles.count + (current.files[selected.value.valueKey] ?? []).count > scansLimit { + alert(for: mainWindow, info: L10n.secureIdErrorScansLimit) + } + return current.withAppendFiles(localFiles, for: selected.value.valueKey) + } + } + }) + }), for: mainWindow) + } else if relative.count == 1 { + push(request.primary, relative[0], request.fillPrimary) + } else { + push(request.primary, nil, request.fillPrimary) + } + + + case .personalDetails: + var loadedData:DetailsIntermediateState? + let push:(SecureIdRequestedFormFieldValue, SecureIdRequestedFormFieldValue?, SecureIdRequestedFormFieldValue?) ->Void = { field, relative, primary in + presentController(InputDataController(dataSignal: combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return identityEntries(state, primary: primary, relative: relative, updateState: updateState) + } |> map { InputDataSignalValue(entries: $0) }, title: relative?.rawValue ?? field.rawValue, validateData: { data in + + + if let _ = data[_id_delete] { + return .fail(.doSomething { next in + modernConfirm(for: mainWindow, account: context.account, peerId: nil, header: L10n.telegramPassportController, information: primary != nil && relative != nil ? L10n.secureIdConfirmDeleteDocument : primary != nil ? L10n.secureIdDeleteConfirmPersonalDetails : L10n.secureIdConfirmDeleteDocument, thridTitle: primary != nil && relative != nil ? L10n.secureIdDeletePersonalDetails : nil, successHandler: { result in + var keys: [SecureIdValueKey] = [] + if let relative = relative { + keys.append(relative.valueKey) + } + switch result { + case .basic: + if primary != nil && relative == nil { + keys.append(field.valueKey) + } + case .thrid: + keys.append(field.valueKey) + } + next(removeValueInteractive(keys)) + }) + }) + } + + let firstName = data[_id_first_name]?.stringValue ?? "" + let lastName = data[_id_last_name]?.stringValue ?? "" + let middleName = data[_id_middle_name]?.stringValue ?? "" + let birthday = data[_id_birthday]?.secureIdDate + let countryCode = data[_id_country]?.stringValue ?? "" + let residence = data[_id_residence]?.stringValue ?? "" + let gender = data[_id_gender]?.gender + let identifier = data[_id_identifier]?.stringValue + + + let firstNameNative = data[_id_first_name_native]?.stringValue ?? "" + let middleNameNative = data[_id_middle_name_native]?.stringValue ?? "" + let lastNameNative = data[_id_last_name_native]?.stringValue ?? "" + + + let expiryDate = data[_id_expire_date]?.secureIdDate + + let selfie = data[_id_selfie]?.secureIdDocument + let frontside = data[_id_frontside]?.secureIdDocument + let backside = data[_id_backside]?.secureIdDocument + + var fails:[InputDataIdentifier : InputDataValidationFailAction] = [:] + if firstName.isEmpty, primary != nil { + fails[_id_first_name] = .shake + } + if lastName.isEmpty, primary != nil { + fails[_id_last_name] = .shake + } + if countryCode.isEmpty && primary != nil { + fails[_id_country] = .shake + } + if residence.isEmpty && primary != nil { + fails[_id_birthday] = .shake + } + if gender == nil && primary != nil { + fails[_id_gender] = .shake + } + if birthday == nil && primary != nil { + fails[_id_birthday] = .shake + } + + if let identifier = identifier, identifier.isEmpty { + fails[_id_identifier] = .shake + } + + + + if let relative = relative, relative.hasSelfie, selfie == nil, editSettings == nil { + fails[_id_selfie_scan] = .shake + } + + var fileIndex: Int = 0 + var translations:[SecureIdVerificationDocumentReference] = [] + while data[InputDataIdentifier("_translation_\(fileIndex)")] != nil { + let identifier = InputDataIdentifier("_translation_\(fileIndex)") + let value = data[identifier]!.secureIdDocument! + switch value { + case let .remote(reference): + translations.append(.remote(reference)) + case let .local(local): + switch local.state { + case let .uploaded(file): + translations.append(.uploaded(file)) + case .uploading: + fails[identifier] = .shake + } + + } + fileIndex += 1 + } + + if let relative = relative, relative.hasTranslation, translations.isEmpty, editSettings == nil { + fails[_id_translation] = .shake + } + + var selfieDocument: SecureIdVerificationDocumentReference? = nil + var frontsideDocument: SecureIdVerificationDocumentReference? = nil + var backsideDocument: SecureIdVerificationDocumentReference? = nil + + if let selfie = selfie { + switch selfie { + case let .remote(reference): + selfieDocument = .remote(reference) + case let .local(local): + switch local.state { + case let .uploaded(file): + selfieDocument = .uploaded(file) + case .uploading: + fails[_id_selfie] = .shake + } + } + } + + if let relative = relative { + if frontside == nil { + fails[_id_frontside] = .shake + } + if relative.hasBacksideDocument { + if backside == nil { + fails[_id_backside] = .shake + } + } + + if let frontside = frontside { + switch frontside { + case let .remote(reference): + frontsideDocument = .remote(reference) + case let .local(local): + switch local.state { + case let .uploaded(file): + frontsideDocument = .uploaded(file) + case .uploading: + fails[_id_frontside] = .shake + } + } + } + if let backside = backside { + switch backside { + case let .remote(reference): + backsideDocument = .remote(reference) + case let .local(local): + switch local.state { + case let .uploaded(file): + backsideDocument = .uploaded(file) + case .uploading: + fails[_id_backside] = .shake + } + } + } + } + + var nativeName: SecureIdPersonName? = nil + if let primary = primary, case let .personalDetails(isNativeName) = primary, isNativeName, data[_id_first_name_native] != nil { + if firstNameNative.isEmpty { + fails[_id_first_name_native] = .shake + } + if lastNameNative.isEmpty { + fails[_id_last_name_native] = .shake + } + if fails.isEmpty { + nativeName = SecureIdPersonName(firstName: firstNameNative, lastName: lastNameNative, middleName: middleNameNative) + } + } + + if !fails.isEmpty { + return .fail(.fields(fails)) + } + + + + + var values: [SecureIdValue] = [] + if primary != nil { + let _birthday = birthday! + let _gender = gender! + values.append(SecureIdValue.personalDetails(SecureIdPersonalDetailsValue(latinName: SecureIdPersonName(firstName: firstName, lastName: lastName, middleName: middleName), nativeName: nativeName, birthdate: _birthday, countryCode: countryCode, residenceCountryCode: residence, gender: _gender))) + } + + if let relative = relative { + let _identifier = identifier! + switch relative.valueKey { + case .idCard: + values.append(SecureIdValue.idCard(SecureIdIDCardValue(identifier: _identifier, expiryDate: expiryDate, verificationDocuments: [], translations: translations, selfieDocument: selfieDocument ?? relativeValue?.selfieVerificationDocument, frontSideDocument: frontsideDocument, backSideDocument: backsideDocument))) + case .passport: + values.append(SecureIdValue.passport(SecureIdPassportValue(identifier: _identifier, expiryDate: expiryDate, verificationDocuments: [], translations: translations, selfieDocument: selfieDocument ?? relativeValue?.selfieVerificationDocument, frontSideDocument: frontsideDocument))) + case .driversLicense: + values.append(SecureIdValue.driversLicense(SecureIdDriversLicenseValue(identifier: _identifier, expiryDate: expiryDate, verificationDocuments: [], translations: translations, selfieDocument: selfieDocument ?? relativeValue?.selfieVerificationDocument, frontSideDocument: frontsideDocument, backSideDocument: backsideDocument))) + case .internalPassport: + values.append(SecureIdValue.internalPassport(SecureIdInternalPassportValue(identifier: _identifier, expiryDate: expiryDate, verificationDocuments: [], translations: translations, selfieDocument: selfieDocument ?? relativeValue?.selfieVerificationDocument, frontSideDocument: frontsideDocument))) + + default: + break + } + } + + let current = DetailsIntermediateState(data) + if let loadedData = loadedData { + var fails = loadedData.validateErrors(currentState: current, errors: _stateValue.errors(for: .personalDetails), relativeErrors: relative != nil ? _stateValue.errors(for: relative!.valueKey) : nil) + if let relative = relative { + let errors = _stateValue.errors(for: relative.valueKey) + for error in errors { + switch error.key { + case _id_selfie: + if let selfieDocument = selfieDocument { + switch selfieDocument { + case .remote: + fails[_id_selfie] = .shake + default: + break + } + } + case _id_frontside: + if let frontsideDocument = frontsideDocument { + switch frontsideDocument { + case .remote: + fails[_id_frontside] = .shake + default: + break + } + } + case _id_frontside: + if let backsideDocument = backsideDocument { + switch backsideDocument { + case .remote: + fails[_id_backside] = .shake + default: + break + } + } + default: + break + } + var i: Int = 0 + for file in translations { + switch file { + case let .remote(reference): + if error.key == InputDataIdentifier("file_\(reference.fileHash.base64EncodedString())") { + fails[InputDataIdentifier("_translation_\(i)")] = .shake + } + i += 1 + default: + break + } + } + } + } + if fails.isEmpty { + if loadedData == current && values.last == value { + return .success(.navigationBack) + } + return proccessValue(values) + } else { + return .fail(.fields(fails)) + } + } + + return .fail(.none) + }, updateDatas: { data in + updateState { current in + var current = current + let details = DetailsIntermediateState(data) + var errors = current.errors + + if let loadedData = loadedData { + let updatedErrors = loadedData.removeErrors(currentState: details, errors: primary != nil ? errors[primary!.valueKey] : nil, relativeErrors: relative != nil ? errors[relative!.valueKey] : nil) + if let primary = primary { + errors[primary.valueKey] = updatedErrors.errors + } + if let relative = relative { + errors[relative.valueKey] = updatedErrors.relativeErrors + } + current = current.withUpdatedErrors(errors) + } + return current.withUpdatedDetailsState(details) + } + return .fail(.none) + }, afterDisappear: { + updateState { current in + return current.withUpdatedDetailsState(nil).withUpdatedValues(current.values).withRemovedInputErrors() + } + }, didLoaded: { _, data in + loadedData = DetailsIntermediateState(data) + }, identifier: "passport", backInvocation: { data, f in + if DetailsIntermediateState(data) != loadedData { + confirm(for: mainWindow, header: L10n.secureIdDiscardChangesHeader, information: L10n.secureIdDiscardChangesText, okTitle: L10n.alertConfirmDiscard, successHandler: { _ in + f(true) + }) + } else { + f(true) + } + + }, getBackgroundColor: { theme.colors.background })) + } + + if let editSettings = editSettings { + var values:[ValuesSelectorValue] = [] + for relative in relative { + values.append(ValuesSelectorValue(localized: editSettings.hasValue(relative) ? relative.descEdit : relative.descAdd, value: relative)) + } + showModal(with: ValuesSelectorModalController(values: values, selected: values[0], title: L10n.secureIdIdentityDocument, onComplete: { selected in + push(selected.value, selected.value.valueKey == .personalDetails ? nil : selected.value, selected.value.valueKey == .personalDetails ? .personalDetails(nativeName: true) : nil) + }), for: mainWindow) + } else if relative.count > 1 { + let values:[ValuesSelectorValue] = relative.map({ValuesSelectorValue(localized: $0.rawValue, value: $0)}) + showModal(with: ValuesSelectorModalController(values: values, selected: values[0], title: L10n.secureIdIdentityDocument, onComplete: { selected in + if let relativeValue = relativeValue, relativeValue.frontSideVerificationDocument != nil { + push(request.primary, selected.value, request.fillPrimary ? request.primary : nil) + } else { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { files in + if let file = files?.first { + push(request.primary, selected.value, request.fillPrimary ? request.primary : nil) + updateFrontMrz(file: file, relative: selected.value, updateState: updateState) + } + }) + } + }), for: mainWindow) + } else if relative.count == 1 { + if let relativeValue = relativeValue, relativeValue.frontSideVerificationDocument != nil { + push(request.primary, relative[0], request.fillPrimary ? request.primary : nil) + } else { + filePanel(with: photoExts, allowMultiple: false, for: mainWindow, completion: { files in + if let file = files?.first { + push(request.primary, relative[0], request.fillPrimary ? request.primary : nil) + updateFrontMrz(file: file, relative: relative[0], updateState: updateState) + } + }) + } + + } else { + push(request.primary, nil, request.fillPrimary ? request.primary : nil) + } + + + case .email: + if let valueKey = valueKey { + confirm(for: mainWindow, information: L10n.secureIdRemoveEmail, successHandler: { _ in + _ = removeValue(valueKey) + }) + } else { + let title = L10n.secureIdInstallEmailTitle + var _payload: SecureIdPrepareEmailVerificationPayload? = nil + var _activateEmail: String? = nil + let validate: ([InputDataIdentifier : InputDataValue]) -> InputDataValidation = { data in + let email = data[_id_email_def]?.stringValue ?? data[_id_email_new]?.stringValue + + if let code = data[_id_email_code]?.stringValue, !code.isEmpty, let payload = _payload, let activateEmail = _activateEmail { + return .fail(.doSomething { f in + if let ctx = _stateValue.accessContext { + emailNewActivationDisposable.set(showModalProgress(signal: secureIdCommitEmailVerification(postbox: context.account.postbox, network: context.account.network, context: ctx, payload: payload, code: code) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + f(.fail(.fields([_id_email_new : .shake]))) + }, completed: { + f(proccessValue([SecureIdValue.email(.init(email: activateEmail))])) + })) + } + }) + + } + + if data[_id_email_def] == nil, let email = email, isValidEmail(email) { + return .fail(.doSomething { parent in + emailNewActivationDisposable.set(showModalProgress(signal: secureIdPrepareEmailVerification(network: context.account.network, value: .init(email: email)), for: mainWindow).start(next: { payload in + _payload = payload + _activateEmail = email + updateState { current in + return current.withUpdatedIntermediateEmailState(EmailIntermediateState(email: email, length: payload.length)) + } + }, error: { error in + + })) + + }) + } + + if let email = email, isValidEmail(email) { + return proccessValue([SecureIdValue.email(SecureIdEmailValue(email: email))]) + } else { + if data[_id_email_def] == nil { + return .fail(.fields([_id_email_new : .shake])) + } + } + return .fail(.none) + } + presentController(InputDataController(dataSignal: combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return emailEntries(state, updateState: updateState) + } |> map { InputDataSignalValue(entries: $0) }, title: title, validateData: validate, updateDatas: { data in + if let payload = _payload, let code = data[_id_email_code]?.stringValue, code.length == payload.length { + return validate(data) + } + return .fail(.none) + }, afterDisappear: { + updateState { current in + return current.withUpdatedIntermediateEmailState(nil) + } + }, identifier: "passport", getBackgroundColor: { theme.colors.background })) + } + + case .phone: + + if let valueKey = valueKey { + confirm(for: mainWindow, information: L10n.secureIdRemovePhoneNumber, successHandler: { _ in + _ = removeValue(valueKey) + }) + } else { + let title = L10n.secureIdInstallPhoneTitle + var _payload: SecureIdPreparePhoneVerificationPayload? + let validate: ([InputDataIdentifier : InputDataValue]) -> InputDataValidation = { data in + let phone = data[_id_phone_def]?.stringValue ?? data[_id_phone_new]?.stringValue + if let phone = phone, !phone.isEmpty { + return .fail(.doSomething { parent in + let result = proccessValue([SecureIdValue.phone(SecureIdPhoneValue(phone: phone))]) + switch result { + case let .fail(progress): + switch progress { + case let .doSomething(next: f): + f { result in + switch result { + case .success: + parent(.success(.navigationBack)) + case .none: + break + case .fail: + phoneNewActivationDisposable.set(showModalProgress(signal: secureIdPreparePhoneVerification(network: context.account.network, value: SecureIdPhoneValue(phone: phone)) |> deliverOnMainQueue, for: mainWindow).start(next: { payload in + + _payload = payload + + let validate: ([InputDataIdentifier : InputDataValue])->InputDataValidation = { data in + return .fail(.doSomething { f in + let code = data[_id_phone_code]?.stringValue ?? "" + if code.isEmpty { + f(.fail(.fields([_id_phone_code : .shake]))) + return + } + if let ctx = _stateValue.accessContext { + phoneNewActivationDisposable.set(showModalProgress(signal: secureIdCommitPhoneVerification(postbox: context.account.postbox, network: context.account.network, context: ctx, payload: payload, code: code) |> deliverOnMainQueue, for: mainWindow).start(next: { value in + updateState { current in + return current.withUpdatedValue(value) + } + f(.success(.navigationBack)) + }, error: { error in + f(.fail(.fields([_id_phone_code : .shake]))) + })) + } + }) + } + + presentController(InputDataController(dataSignal: combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return confirmPhoneNumberEntries(state, phoneNumber: phone, updateState: updateState) + } |> map { InputDataSignalValue(entries: $0) }, title: title, validateData: validate, updateDatas: { data in + if let payload = _payload, let code = data[_id_phone_code]?.stringValue { + switch payload.type { + case let .sms(length): + if code.length == length { + return validate(data) + } + default: + break + } + } + return .fail(.none) + }, identifier: "passport", getBackgroundColor: { theme.colors.background })) + }, error: { error in + alert(for: mainWindow, info: "\(error)") + })) + + } + } + default: + break + } + default: + break + } + }) + } else { + if data[_id_phone_def] == nil { + return .fail(.fields([_id_phone_new : .shake])) + } + } + return .fail(.none) + } + presentController(InputDataController(dataSignal: state.get() |> map { state in + return phoneNumberEntries(state, updateState: updateState) + } |> map { InputDataSignalValue(entries: $0) }, title: title, validateData: validate, afterDisappear: { + + }, identifier: "passport", getBackgroundColor: { theme.colors.background })) + } + default: + fatalError() + } + + }, createPassword: { + let promise:Promise<[InputDataEntry]> = Promise() + promise.set(combineLatest(state.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return createPasswordEntries(state) + }) + let controller = InputDataController(dataSignal: promise.get() |> map { InputDataSignalValue(entries: $0) }, title: L10n.secureIdCreatePasswordTitle, validateData: { data in + + let password = data[_id_c_password]!.stringValue! + let repassword = data[_id_c_repassword]!.stringValue! + let hint = data[_id_c_hint]!.stringValue! + + var emptyFields:[InputDataIdentifier : InputDataValidationFailAction] = [:] + if password.isEmpty { + emptyFields[_id_c_password] = .shake + } + if repassword.isEmpty { + emptyFields[_id_c_repassword] = .shake + } + + if !emptyFields.isEmpty { + return .fail(.fields(emptyFields)) + } + + if password != repassword { + return .fail(.fields([_id_c_repassword : .shake])) + } + + + let updatePassword: (String, String?) -> Void = { password, email in + updateState { current in + return current.withUpdatedPassword(.password(password: password, pendingEmail: nil)) + } + + passwordVerificationData.set(.single(nil) |> then(updateTwoStepVerificationPassword(network: context.account.network, currentPassword: nil, updatedPassword: .password(password: password, hint: hint, email: email)) + |> `catch` {_ in return .complete()} + |> mapToSignal { result in + + let configuration: TwoStepVerificationConfiguration + switch result { + case let .password(password, pendingEmail): + if let email = email { + configuration = .notSet(pendingEmail: TwoStepVerificationPendingEmail(pattern: email, codeLength: pendingEmail?.codeLength)) + } else { + configuration = .set(hint: hint, hasRecoveryEmail: false, pendingEmail: pendingEmail, hasSecureValues: false) + } + + updateState { current in + return current.withUpdatedTmpPwd(password) + } + if email == nil { + checkPwd?(password) + } + + default: + configuration = .notSet(pendingEmail: nil) + } + + return .single(Optional(configuration)) + })) + } + + if let email = data[_id_c_email]?.stringValue { + if isValidEmail(email) { + updatePassword(password, email) + return .success(.navigationBackWithPushAnimation) + } else { + + if email.isEmpty { + return .fail(.doSomething(next: { f in + confirm(for: mainWindow, information: L10n.twoStepAuthEmailSkipAlert, okTitle: L10n.twoStepAuthEmailSkip, successHandler: { result in + updatePassword(password, nil) + f(.success(.navigationBackWithPushAnimation)) + }) + })) + } else { + return .fail(.fields([_id_c_email : .shake])) + } + + } + } + + return .fail(.none) + }, updateDoneValue: { data in + return { f in + f(.enabled(L10n.navigationNext)) + } + }, identifier: "passport", getBackgroundColor: { theme.colors.background }) + + presentController(controller) + }, abortVerification: { + emailActivation.set(nil) + + passwordVerificationData.set(showModalProgress(signal: updateTwoStepVerificationPassword(network: context.account.network, currentPassword: nil, updatedPassword: .none) + |> `catch` {_ in .complete()} + |> mapToSignal { _ in + updateState { current in + return current.withUpdatedPasswordSettings(nil) + } + return .single(TwoStepVerificationConfiguration.notSet(pendingEmail: nil)) + }, for: mainWindow)) + }, authorize: { [weak self] enabled in + + if !enabled { + updateState { current in + return current.withUpdatedEmptyErrors(true) + } + + guard let `self` = self else {return} + var scrollItem:TableRowItem? = nil + + self.genericView.tableView.enumerateItems(with: { item -> Bool in + if let stableId = item.stableId.base as? PassportEntryId { + switch stableId { + case .emptyFieldId: + scrollItem = item + default: + break + } + if scrollItem == nil, let item = item as? GeneralInteractedRowItem, let color = item.descLayout?.attributedString.attribute(NSAttributedString.Key.foregroundColor, at: 0, effectiveRange: nil) as? NSColor { + if color.argb == theme.colors.redUI.argb { + scrollItem = item + } + } + } + return scrollItem == nil + }) + + if let scrollItem = scrollItem { + self.genericView.tableView.scroll(to: TableScrollState.top(id: scrollItem.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets(), true) + } + + return + } + + + if let inAppRequest = inAppRequest, let encryptedForm = encryptedForm { + authorizeDisposable.set(showModalProgress(signal: state.get() |> take(1) |> mapError { _ in return GrantSecureIdAccessError.generic} |> mapToSignal { state -> Signal in + + var values:[SecureIdValueWithContext] = [] + + let requestedFields = encryptedForm.requestedFields.map { value -> SecureIdRequestedFormField in + switch value { + case let .just(key): + switch key { + case .email, .phone, .personalDetails, .address: + return value + default: + return .oneOf([key]) + } + default: + return value + } + } + + for field in requestedFields { + switch field { + case let .just(field): + if let value = state.values.filter({$0.value.key == field.valueKey}).first { + values.append(value) + } + case let .oneOf(fields): + if fields.count == 1 { + if let value = state.values.filter({$0.value.key == fields[0].valueKey}).first { + values.append(value) + } + } else { + let field = fields.filter({ field in + return state.searchValue(field.valueKey) != nil + }).first + if let field = field, let value = state.values.filter({$0.value.key == field.valueKey}).first { + values.append(value) + } + } + } + } + + return grantSecureIdAccess(network: context.account.network, peerId: inAppRequest.peerId, publicKey: inAppRequest.publicKey, scope: inAppRequest.scope, opaquePayload: inAppRequest.isModern ? Data() : inAppRequest.nonce, opaqueNonce: inAppRequest.isModern ? inAppRequest.nonce : Data(), values: values, requestedFields: encryptedForm.requestedFields) + } |> deliverOnMainQueue, for: mainWindow).start(error: { error in + alert(for: mainWindow, info: "\(error)") + }, completed: { + executeCallback(true) + closeAfterSuccessful() + })) + } + + }, botPrivacy: { [weak self] in + if let url = self?.form?.termsUrl { + execute(inapp: .external(link: url, false)) + } + }, forgotPassword: { + confirm(for: mainWindow, header: L10n.passportResetPasswordConfirmHeader, information: L10n.passportResetPasswordConfirmText, okTitle: L10n.passportResetPasswordConfirmOK, successHandler: { _ in + recoverPasswordDisposable.set(showModalProgress(signal: requestTwoStepVerificationPasswordRecoveryCode(network: context.account.network) |> deliverOnMainQueue, for: mainWindow).start(next: { emailPattern in + let promise:Promise<[InputDataEntry]> = Promise() + promise.set(combineLatest(Signal<[InputDataEntry], NoError>.single(recoverEmailEntries(emailPattern: emailPattern, unavailable: { + alert(for: mainWindow, info: L10n.twoStepAuthRecoveryFailed) + })) |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map {$0.0}) + presentController(InputDataController(dataSignal: promise.get() |> map { InputDataSignalValue(entries: $0) }, title: L10n.secureIdRecoverPassword, validateData: { data -> InputDataValidation in + + let code = data[_id_email_code]?.stringValue ?? "" + if code.isEmpty { + return .fail(.fields([_id_email_code : .shake])) + } + + return .fail(.doSomething { f in + confirm(for: mainWindow, information: L10n.secureIdWarningDataLost, successHandler: { _ in + recoverPasswordDisposable.set(showModalProgress(signal: recoverTwoStepVerificationPassword(network: context.account.network, code: code) |> deliverOnMainQueue, for: mainWindow).start(error: { error in + f(.fail(.fields([_id_email_code : .shake]))) + }, completed: { + updateState { current in + return current.withUpdatedPassword(nil) + } + passwordVerificationData.set(.single(.notSet(pendingEmail: nil))) + + f(.success(.navigationBack)) + })) + }) + }) + + }, identifier: "passport", getBackgroundColor: { theme.colors.background })) + })) + }) + + // + }, deletePassport: { + confirm(for: mainWindow, header: L10n.secureIdInfoTitle, information: L10n.secureIdInfoDeletePassport, successHandler: { _ in + updateState { current in + let signal = deleteSecureIdValues(network: context.account.network, keys: Set(current.values.map{$0.value.key})) + + _ = (signal |> deliverOnMainQueue).start(next: { + + }, error:{ error in + alert(for: mainWindow, info: "\(error)") + }, completed: { + updateState { current in + return current.withRemovedValues() + } + closeController() + }) + return current + } + }) + }, updateEmailCode: { [weak self] in + guard let `self` = self else {return} + if let item = self.genericView.tableView.item(stableId: PassportEntryId.inputEmailCode) as? InputDataRowItem { + updateState { state in + return state.withUpdatedEmailCode(item.currentText.string).withUpdatedEmailCodeError(nil) + } + if item.limit == item.currentText.string.length { + _ = self.pendingEmailConfirm() + } + } + }) + + + checkPwd = { value in + arguments.checkPassword((value, {})) + } + + + + + + let botPeerSignal = form != nil ? context.account.postbox.loadedPeerWithId(form!.peerId) |> map {Optional($0)} |> deliverOnPrepareQueue : Signal.single(nil) + + let signal: Signal<(TableUpdateTransition, Bool, Bool), NoError> = combineLatest(appearanceSignal |> deliverOnPrepareQueue, formValue.get() |> deliverOnPrepareQueue, passwordVerificationData.get() |> deliverOnPrepareQueue, state.get() |> deliverOnPrepareQueue, botPeerSignal) |> map { appearance, form, passwordData, state, peer in + + let (entries, enabled) = passportEntries(encryptedForm: form.0, form: form.1, peer: peer, passwordData: passwordData, state: state) + + let converted = entries.map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return (prepareTransition(left: previous.swap(converted), right: converted, initialSize: initialSize.modify{$0}, arguments: arguments), enabled, form.1 != nil) + } |> deliverOnMainQueue |> afterDisposed { + actionsDisposable.dispose() + } + + if let pwd = context.temporaryPassword { + actionsDisposable.add((passwordVerificationData.get() |> filter {$0 != nil} |> take(1) |> deliverOnMainQueue).start(next: { _ in + arguments.checkPassword((pwd, { + context.resetTemporaryPwd() + updateState { current in + return current.withUpdatedTmpPwd(nil) + } + })) + })) + } + + passwordVerificationData.set(.single(nil) |> then(twoStepVerificationConfiguration(account: context.account) |> map {Optional($0)})) + + + secureIdConfigurationDisposable.set(secureIdConfiguration(postbox: context.account.postbox, network: context.account.network).start(next: { configuration in + updateState { current in + return current.withUpdatedConfiguration(configuration) + } + })) + + actionsDisposable.add((passwordVerificationData.get() |> deliverOnMainQueue).start(next: { [weak self] configuration in + self?.updateRightView(configuration) + })) + + disposable.set(signal.start(next: { [weak self] transition, enabled, isVisible in + guard let `self` = self else {return} + self.genericView.tableView.merge(with: transition) + self.genericView.updateEnabled(enabled, isVisible: isVisible, action: arguments.authorize) + + self.readyOnce() + if self.window?.firstResponder == nil || self.window?.firstResponder == self.window { + _ = self.window?.makeFirstResponder(self.firstResponder()) + } + })) + + } + + private func updateRightView(_ passwordData: TwoStepVerificationConfiguration?) { + + var hasPendingEmail: Bool = false + + let context = self.context + + if let passwordData = passwordData { + switch passwordData { + case let .notSet(pendingEmail): + hasPendingEmail = pendingEmail != nil + case let .set(_, _, pendingEmail, _): + hasPendingEmail = pendingEmail != nil + } + } + + rightView?.removeAllHandlers() + if hasPendingEmail { + rightView?.removeImage(for: .Normal) + rightView?.set(text: L10n.navigationDone, for: .Normal) + rightView?.set(handler: { [weak self] _ in + _ = self?.pendingEmailConfirm() + }, for: .Click) + } else { + rightView?.set(handler: { _ in + confirm(for: mainWindow, header: L10n.secureIdInfoTitle, information: L10n.secureIdInfo, cancelTitle: "", thridTitle: L10n.secureIdInfoMore, successHandler: { result in + if result == .thrid { + openFaq(context: context) + } + }) + }, for: .Click) + + rightView?.set(image: theme.icons.passportInfo, for: .Normal) + rightView?.set(text: "", for: .Normal) + } + } + + override func returnKeyAction() -> KeyHandlerResult { + _ = pendingEmailConfirm() + return super.returnKeyAction() + } + + override func becomeFirstResponder() -> Bool? { + return true + } + + override func backKeyAction() -> KeyHandlerResult { + return .invokeNext + } + + private var dismissed:Bool = false + override func invokeNavigationBack() -> Bool { + if form == nil { + return true + } + if !dismissed { + confirm(for: mainWindow, information: L10n.secureIdConfirmCancel, okTitle: L10n.alertConfirmStop, successHandler: { [weak self] _ in + guard let `self` = self else {return} + self.dismissed = true + self.executeCallback(false) + _ = self.executeReturn() + }) + } + + return dismissed + } + + private func executeCallback(_ success: Bool) { + if let request = request, let callback = request.callback { + if callback.hasPrefix("tgbot") { + let r = callback.nsstring.range(of: "://") + if r.location != NSNotFound { + let rawBotId = callback.nsstring.substring(with: NSMakeRange(5, r.location - 5)) + if let botId = Int32(rawBotId) { + let sdkCallback = "tgbot\(botId)://passport" + if sdkCallback == callback { + execute(inapp: .external(link: sdkCallback + (success ? "/success" : "/cancel"), false)) + } + } + } + } else { + execute(inapp: .external(link: addUrlParameter(value: "tg_passport=\(success ? "success" : "cancel")", to: callback), false)) + } + } + } + + deinit { + disposable.dispose() + secureIdConfigurationDisposable.dispose() + } + + override func firstResponder() -> NSResponder? { + var responder: NSResponder? = nil + genericView.tableView.enumerateViews { view -> Bool in + if let view = view as? PassportInsertPasswordRowView { + if self.window?.firstResponder == view.input.textView { + responder = view.input.textView + } else { + responder = view.input + } + } else if let view = view as? InputDataRowView { + responder = view.firstResponder + } + return responder == nil + } + return responder + } + +} diff --git a/Telegram-Mac/PassportDocumentRowItem.swift b/Telegram-Mac/PassportDocumentRowItem.swift new file mode 100644 index 0000000000..91b0b8771d --- /dev/null +++ b/Telegram-Mac/PassportDocumentRowItem.swift @@ -0,0 +1,218 @@ +// +// PassportDocumentRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class PassportDocumentRowItem: GeneralRowItem, InputDataRowDataValue { + + fileprivate let title: TextViewLayout + fileprivate let status: TextViewLayout + fileprivate let removeAction:(SecureIdVerificationDocument)->Void + fileprivate let context: AccountContext + + fileprivate let documentValue: SecureIdDocumentValue + + var value: InputDataValue { + return .secureIdDocument(documentValue.document) + } + fileprivate var accessContext: SecureIdAccessContext { + return documentValue.context + } + var image: TelegramMediaImage { + return documentValue.image + } + + fileprivate private(set) var uploadingProgress: Float? + + init(_ initialSize: NSSize, context: AccountContext, document: SecureIdDocumentValue, error: InputDataValueError?, header: String, removeAction:@escaping(SecureIdVerificationDocument)->Void) { + self.documentValue = document + self.context = context + title = TextViewLayout(.initialize(string: header, color: theme.colors.text, font: .normal(.text))) + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.local + formatter.timeStyle = .short + + switch document.document { + case let .remote(file): + if let error = error { + status = TextViewLayout(.initialize(string: error.description, color: theme.colors.redUI, font: .normal(.text)), maximumNumberOfLines: 1) + } else { + status = TextViewLayout(.initialize(string: formatter.string(from: Date(timeIntervalSince1970: TimeInterval(file.timestamp))), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + } + case let .local(file): + switch file.state { + case .uploaded: + status = TextViewLayout(.initialize(string: formatter.string(from: Date()), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + case let .uploading(progress): + uploadingProgress = progress + status = TextViewLayout(.initialize(string: L10n.secureIdFileUploadProgress("\(Int(progress * 100.0))"), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + } + } + + self.removeAction = removeAction + super.init(initialSize, stableId: document.stableId, error: error) + + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func viewClass() -> AnyClass { + return PassportDocumentRowView.self + } + + override var instantlyResize: Bool { + return true + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + title.measure(width: width) + status.measure(width: width) + return success + } + + override var height: CGFloat { + return 60 + } + +} + + +final class PassportDocumentRowView : TableRowView { + private let statusView = TextView() + private let titleView = TextView() + private let imageView = TransformImageView() + private let removeButton = ImageButton() + private let progressView: RadialProgressView = RadialProgressView() + private let downloadingProgress: MetaDisposable = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(titleView) + addSubview(statusView) + addSubview(imageView) + addSubview(removeButton) + imageView.setFrameSize(NSMakeSize(60, 50)) + + removeButton.set(handler: { [weak self] _ in + guard let item = self?.item as? PassportDocumentRowItem else {return} + item.removeAction(item.documentValue.document) + }, for: .Click) + } + + override func shakeView() { + statusView.shake() + } + + deinit { + downloadingProgress.dispose() + } + + override func layout() { + super.layout() + + guard let item = item as? GeneralRowItem else {return} + imageView.centerY(x: item.inset.left) + titleView.setFrameOrigin(imageView.frame.maxX + 10, 10) + statusView.setFrameOrigin(imageView.frame.maxX + 10, frame.height - 10 - statusView.frame.height) + removeButton.centerY(x: frame.width - removeButton.frame.width - item.inset.right) + progressView.center() + } + + override func mouseUp(with event: NSEvent) { + if imageView._mouseInside() { + guard let item = item as? PassportDocumentRowItem, let table = item.table else {return} + var passportItems:[PassportDocumentRowItem] = [] + table.enumerateItems { item -> Bool in + if let item = item as? PassportDocumentRowItem { + passportItems.append(item) + } + return true + } + let index = passportItems.index(of: item)! + showSecureIdDocumentsGallery(context: item.context, medias: passportItems.map({$0.documentValue}), firstIndex: index, item.table) + } else { + super.mouseUp(with: event) + } + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + guard let item = item as? PassportDocumentRowItem, let table = item.table else {return} + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(imageView.frame.maxX + 10, frame.height - .borderSize, frame.width - imageView.frame.maxX - item.inset.right - 10, .borderSize)) + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool) -> NSView { + return imageView + } + + override func updateColors() { + super.updateColors() + statusView.backgroundColor = theme.colors.background + titleView.backgroundColor = theme.colors.background + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? PassportDocumentRowItem else {return} + statusView.update(item.status) + titleView.update(item.title) + + if let progress = item.uploadingProgress { + progressView.state = .Fetching(progress: progress, force: false) + if progressView.superview == nil { + imageView.addSubview(progressView) + } + } else { + progressView.state = .None + progressView.removeFromSuperview() + } + + downloadingProgress.set((chatMessagePhotoStatus(account: item.context.account, photo: item.image) |> deliverOnMainQueue).start(next: { [weak self] status in + guard let `self` = self else {return} + guard let item = self.item as? PassportDocumentRowItem else {return} + switch status { + case let .Fetching(_, progress): + self.progressView.state = .Fetching(progress: progress, force: false) + if self.progressView.superview == nil { + self.imageView.addSubview(self.progressView) + self.progressView.center() + } + + default: + if item.uploadingProgress == nil { + self.progressView.state = .None + self.progressView.removeFromSuperview() + } + } + })) + + self.progressView.fetchControls = FetchControls(fetch: { [weak item] in + guard let item = item else {return} + item.removeAction(item.documentValue.document) + }) + + imageView.setSignal(chatWebpageSnippetPhoto(account: item.context.account, imageReference: ImageMediaReference.standalone(media: item.image), scale: backingScaleFactor, small: true, secureIdAccessContext: item.accessContext)) + _ = chatMessagePhotoInteractiveFetched(account: item.context.account, imageReference: ImageMediaReference.standalone(media: item.image)).start() + imageView.set(arguments: TransformImageArguments(corners: .init(radius: .cornerRadius), imageSize: NSMakeSize(60, 50), boundingSize: NSMakeSize(60, 50), intrinsicInsets: NSEdgeInsets())) + removeButton.set(image: theme.icons.stickerPackDelete, for: .Normal) + _ = removeButton.sizeToFit() + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PassportHeaderItem.swift b/Telegram-Mac/PassportHeaderItem.swift new file mode 100644 index 0000000000..fa3abe659e --- /dev/null +++ b/Telegram-Mac/PassportHeaderItem.swift @@ -0,0 +1,101 @@ +// +// PassportHeaderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 20/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore + +class PassportHeaderItem: TableRowItem { + fileprivate let botPhoto: AvatarNodeState + fileprivate let textLayout: TextViewLayout + fileprivate let account: Account + fileprivate let _stableId: AnyHashable + + override var stableId: AnyHashable { + return _stableId + } + init(_ initialSize: NSSize, account: Account, stableId: AnyHashable, requestedFields: [SecureIdRequestedFormField], peer: Peer) { + self.account = account + self._stableId = stableId + self.botPhoto = .PeerAvatar(peer, peer.displayLetters, peer.smallProfileImage, nil) + + let attributed = NSMutableAttributedString() + + _ = attributed.append(string: L10n.secureIdRequestHeader1(peer.displayTitle), color: theme.colors.grayText, font: .normal(.text)) + attributed.detectBoldColorInString(with: .bold(.text)) + self.textLayout = TextViewLayout(attributed, alignment: .left) + + super.init(initialSize) + + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - 120) + + return success + } + + override var instantlyResize: Bool { + return true + } + + override func viewClass() -> AnyClass { + return PassportHeaderRowView.self + } + + override var height: CGFloat { + return max(50, textLayout.layoutSize.height) + } + +} + + +private final class PassportHeaderRowView : TableRowView { + private let botPhoto: AvatarControl = AvatarControl(font: .avatar(20)) + private let textView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(botPhoto) + addSubview(textView) + textView.userInteractionEnabled = false + textView.isSelectable = false + botPhoto.setFrameSize(50, 50) + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = theme.colors.background + } + + override func layout() { + super.layout() + + botPhoto.centerY(x: 20) + + textView.centerY(x: botPhoto.frame.maxX + 20) + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? PassportHeaderItem else {return} + + textView.update(item.textLayout) + botPhoto.setState(account: item.account, state: item.botPhoto) + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PassportInsertPasswordItem.swift b/Telegram-Mac/PassportInsertPasswordItem.swift new file mode 100644 index 0000000000..34210f4640 --- /dev/null +++ b/Telegram-Mac/PassportInsertPasswordItem.swift @@ -0,0 +1,231 @@ +// +// PassportInsertPasswordItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 20/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +private final class PassportInsertPasswordField : NSSecureTextField { + + override func resignFirstResponder() -> Bool { + (self.delegate as? PassportInsertPasswordRowView)?.controlTextDidBeginEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.resignFirstResponder() + } + + override func becomeFirstResponder() -> Bool { + (self.delegate as? PassportInsertPasswordRowView)?.controlTextDidEndEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.becomeFirstResponder() + } + + override func mouseDown(with event: NSEvent) { + superview?.mouseDown(with: event) + } +} + +class PassportInsertPasswordItem: GeneralRowItem { + private let _stableId: AnyHashable + fileprivate let descLayout: TextViewLayout + fileprivate let checkPasswordAction:((String, ()->Void))->Void + fileprivate let forgotPassword: ()->Void + fileprivate let hasRecoveryEmail: Bool + init(_ initialSize: NSSize, stableId: AnyHashable, checkPasswordAction: @escaping((String, ()->Void))->Void, forgotPassword: @escaping()->Void, hasRecoveryEmail: Bool, isSettings: Bool, error: String?) { + self._stableId = stableId + self.checkPasswordAction = checkPasswordAction + self.forgotPassword = forgotPassword + self.hasRecoveryEmail = hasRecoveryEmail + descLayout = TextViewLayout(.initialize(string: error != nil ? error! : (isSettings ? L10n.secureIdInsertPasswordSettingsDescription : L10n.secureIdInsertPasswordDescription), color: error != nil ? theme.colors.redUI : theme.colors.grayText, font: .normal(.text)), alignment: .center) + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + descLayout.measure(width: width - inset.left - inset.right) + + return success + } + + override var stableId: AnyHashable { + return _stableId + } + + override var instantlyResize: Bool { + return true + } + + override func viewClass() -> AnyClass { + return PassportInsertPasswordRowView.self + } + + override var height: CGFloat { + return 32 + 36 + 20 + 30 + 25 + } +} + + +final class PassportInsertPasswordRowView : GeneralRowView, NSTextFieldDelegate { + let input:NSSecureTextField + private let inputContainer: View = View() + private let descTextView: TextView = TextView() + private let nextButton: TitleButton = TitleButton() + private let forgotPassword: ImageButton = ImageButton() + required init(frame frameRect: NSRect) { + input = PassportInsertPasswordField(frame: NSZeroRect) + super.init(frame: frameRect) + input.stringValue = "" + + + descTextView.userInteractionEnabled = false + descTextView.isSelectable = false + + addSubview(inputContainer) + inputContainer.setFrameSize(250, 36) + + input.wantsLayer = true + input.isBordered = false + input.isBezeled = false + input.focusRingType = .none + input.delegate = self + input.drawsBackground = false + input.isEditable = true + input.isSelectable = true + input.font = .normal(.text) + inputContainer.backgroundColor = theme.colors.grayBackground + inputContainer.layer?.cornerRadius = .cornerRadius + + inputContainer.addSubview(input) + + + input.target = self + input.action = #selector(checkPasscode) + + addSubview(descTextView) + addSubview(nextButton) + inputContainer.addSubview(forgotPassword) + + nextButton.set(handler: { [weak self] _ in + self?.checkPasscode() + }, for: .Click) + + forgotPassword.set(handler: { [weak self] _ in + if let item = self?.item as? PassportInsertPasswordItem { + if item.hasRecoveryEmail { + item.forgotPassword() + } else { + alert(for: mainWindow, info: L10n.secureIdForgotPasswordNoEmail) + } + } + }, for: .Click) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseDown(with event: NSEvent) { + if inputContainer.mouseInside() || input._mouseInside() { + (window as? Window)?.applyResponderIfNeeded() + } else { + super.mouseDown(with: event) + } + } + + func controlTextDidChange(_ obj: Notification) { + + } + + func controlTextDidBeginEditing(_ obj: Notification) { + input.textView?.insertionPointColor = theme.colors.text + } + + func controlTextDidEndEditing(_ obj: Notification) { + + } + + override func layout() { + input.setFrameSize(NSMakeSize(inputContainer.frame.width - 20 - forgotPassword.frame.width - 10, input.frame.height)) + input.centerY(x: 10) + descTextView.centerX() + inputContainer.centerX(y: 32 + 20) + nextButton.centerX(y: inputContainer.frame.maxY + 15) + forgotPassword.centerY(x: inputContainer.frame.width - forgotPassword.frame.width - 10, addition: backingScaleFactor == 2 ? -0.5 : 0) + } + + @objc func checkPasscode() { + guard let item = item as? PassportInsertPasswordItem else {return} + + item.checkPasswordAction((input.stringValue, { [weak self] in + assertOnMainThread() + (self?.window as? Window)?.applyResponderIfNeeded() + self?.input.shake() + self?.input.textView?.selectAllText() + })) + } + + + + override func updateColors() { + super.updateColors() + input.textColor = theme.colors.text + input.backgroundColor = .clear + descTextView.backgroundColor = theme.colors.background + inputContainer.backgroundColor = theme.colors.grayBackground + + let attr = NSMutableAttributedString() + _ = attr.append(string: L10n.secureIdInsertPasswordPassword, color: theme.colors.grayText, font: .normal(.title)) + input.placeholderAttributedString = attr + input.font = .normal(.title) + input.sizeToFit() + nextButton.set(font: .normal(.title), for: .Normal) + nextButton.set(color: .white, for: .Normal) + nextButton.set(background: theme.colors.accent, for: .Normal) + nextButton.set(text: L10n.secureIdInsertPasswordNext, for: .Normal) + _ = nextButton.sizeToFit(NSMakeSize(40, 0), NSMakeSize(.greatestFiniteMagnitude, 40)) + nextButton.layer?.cornerRadius = 20 + + forgotPassword.set(image: theme.icons.passportForgotPassword, for: .Normal) + _ = forgotPassword.sizeToFit() + + var bp:Int = 0 + bp += 1 + } + + override func viewDidMoveToWindow() { + if let window = window as? Window { + window.applyResponderIfNeeded() + } + } + + override var firstResponder: NSResponder? { + return input + } + + override var mouseInsideField: Bool { + return input._mouseInside() + } + + override func hitTest(_ point: NSPoint) -> NSView? { + switch true { + case NSPointInRect(point, inputContainer.frame): + return input + default: + return super.hitTest(point) + } + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + guard let item = item as? PassportInsertPasswordItem else {return} + descTextView.update(item.descLayout) + needsLayout = true + } + +} diff --git a/Telegram-Mac/PassportNewPhoneNumberRowItem.swift b/Telegram-Mac/PassportNewPhoneNumberRowItem.swift new file mode 100644 index 0000000000..bce0b5cd0a --- /dev/null +++ b/Telegram-Mac/PassportNewPhoneNumberRowItem.swift @@ -0,0 +1,452 @@ +// +// SecureIdNewPhoneNumberRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit + +private let manager = CountryManager() + + +private final class PassportPhoneNumberArguments { + let sendCode:(String)->Void + init(sendCode:@escaping(String)->Void) { + self.sendCode = sendCode + } +} + +private final class PassportPhoneTextField : NSTextField { + + override func resignFirstResponder() -> Bool { + (self.delegate as? PassportPhoneContainerView)?.controlTextDidBeginEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.resignFirstResponder() + } + + override func becomeFirstResponder() -> Bool { + (self.delegate as? PassportPhoneContainerView)?.controlTextDidEndEditing(Notification(name: NSControl.textDidChangeNotification)) + return super.becomeFirstResponder() + } + + override func mouseDown(with event: NSEvent) { + superview?.mouseDown(with: event) + } +} + + +private class PassportPhoneContainerView : View, NSTextFieldDelegate { + + var arguments:PassportPhoneNumberArguments? + + + private let countrySelector:TitleButton = TitleButton() + + + fileprivate let errorLabel:LoginErrorStateView = LoginErrorStateView() + + let codeText:PassportPhoneTextField = PassportPhoneTextField() + let numberText:PassportPhoneTextField = PassportPhoneTextField() + + fileprivate var selectedItem:CountryItem? + private let manager: CountryManager + + required init(frame frameRect: NSRect, manager: CountryManager) { + self.manager = manager + super.init(frame: frameRect) + + + countrySelector.style = ControlStyle(font: NSFont.medium(.title), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) + countrySelector.set(text: "France", for: .Normal) + _ = countrySelector.sizeToFit() + addSubview(countrySelector) + + + + countrySelector.set(handler: { [weak self] _ in + self?.showCountrySelector() + }, for: .Click) + + updateLocalizationAndTheme(theme: theme) + + codeText.stringValue = "+" + + codeText.textColor = theme.colors.text + codeText.font = NSFont.normal(.title) + numberText.textColor = theme.colors.text + numberText.font = NSFont.normal(.title) + + numberText.isBordered = false + numberText.isBezeled = false + numberText.drawsBackground = false + numberText.focusRingType = .none + + codeText.drawsBackground = false + codeText.isBordered = false + codeText.isBezeled = false + codeText.focusRingType = .none + + codeText.delegate = self + codeText.nextResponder = numberText + codeText.nextKeyView = numberText + + numberText.delegate = self + numberText.nextResponder = codeText + numberText.nextKeyView = codeText + addSubview(codeText) + addSubview(numberText) + + errorLabel.layer?.opacity = 0 + addSubview(errorLabel) + + let code = NSLocale.current.regionCode ?? "US" + update(selectedItem: manager.item(bySmallCountryName: code), update: true) + + + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.background + + numberText.placeholderAttributedString = NSAttributedString.initialize(string: tr(L10n.loginPhoneFieldPlaceholder), color: theme.colors.grayText, font: NSFont.normal(.header), coreText: false) + codeText.textView?.insertionPointColor = theme.colors.indicatorColor + numberText.textView?.insertionPointColor = theme.colors.indicatorColor + + needsLayout = true + } + + func setPhoneError(_ error: AuthorizationCodeRequestError) { + let text:String + switch error { + case .invalidPhoneNumber: + text = tr(L10n.phoneNumberInvalid) + case .limitExceeded: + text = tr(L10n.loginFloodWait) + case .generic: + text = "undefined error" + case .phoneLimitExceeded: + text = "undefined error" + case .phoneBanned: + text = "PHONE BANNED" + case .timeout: + text = "timeout" + } + errorLabel.state.set(.single(.error(text))) + } + + func update(countryCode: Int32, number: String) { + self.codeText.stringValue = "\(countryCode)" + self.numberText.stringValue = formatPhoneNumber(number) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func layout() { + super.layout() + codeText.sizeToFit() + numberText.sizeToFit() + + let maxInset: CGFloat = 0 + let contentInset = maxInset + countrySelector.setFrameOrigin(contentInset - 2, floorToScreenPixels(backingScaleFactor, 25 - countrySelector.frame.height/2)) + + codeText.setFrameOrigin(contentInset, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + numberText.setFrameOrigin(contentInset + separatorInset, floorToScreenPixels(backingScaleFactor, 75 - codeText.frame.height/2)) + errorLabel.centerX(y: 120) + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + + let maxInset: CGFloat = 0 + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(maxInset, 50, frame.width - maxInset, .borderSize)) + ctx.fill(NSMakeRect(maxInset, 100, frame.width - maxInset, .borderSize)) + // ctx.fill(NSMakeRect(maxInset + separatorInset, 50, .borderSize, 50)) + } + + + func showCountrySelector() { + + var items:[ContextMenuItem] = [] + for country in manager.countries { + let item = ContextMenuItem(country.fullName, handler: { [weak self] in + self?.update(selectedItem: country, update: true) + }) + items.append(item) + } + if let currentEvent = NSApp.currentEvent { + ContextMenu.show(items: items, view: countrySelector, event: currentEvent, onShow: {(menu) in + + }, onClose: {}) + } + + } + + func controlTextDidBeginEditing(_ obj: Notification) { + codeText.textView?.backgroundColor = theme.colors.background + numberText.textView?.backgroundColor = theme.colors.background + codeText.textView?.insertionPointColor = theme.colors.indicatorColor + numberText.textView?.insertionPointColor = theme.colors.indicatorColor + } + + func controlTextDidEndEditing(_ obj: Notification) { + + } + + func controlTextDidChange(_ obj: Notification) { + + + if let field = obj.object as? NSTextField { + + field.textView?.backgroundColor = theme.colors.background + + let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() + let dec = code.prefix(4) + + if field == codeText { + + + if code.length > 4 { + let list = Array(code).map {String($0)} + let reduced = list.reduce([], { current, value -> [String] in + var current = current + current.append((current.last ?? "") + value) + return current + }).map({Int($0)}).filter({$0 != nil}).map({$0!}) + + var found: Bool = false + for _code in reduced { + if let item = manager.item(byCodeNumber: _code) { + codeText.stringValue = "+" + String(_code) + update(selectedItem: item, update: true, updateCode: false) + + let codeString = String(_code) + var formated = formatPhoneNumber(codeString + String(code[codeString.endIndex.. Bool { + if commandSelector == #selector(insertNewline(_:)) { + if control == codeText { + self.window?.makeFirstResponder(self.numberText) + self.numberText.selectText(nil) + } else if !numberText.stringValue.isEmpty { + arguments?.sendCode(number) + } + //Queue.mainQueue().justDispatch { + (control as? NSTextField)?.setCursorToEnd() + //} + return true + } else if commandSelector == #selector(deleteBackward(_:)) { + if control == numberText { + if numberText.stringValue.isEmpty { + Queue.mainQueue().justDispatch { + self.window?.makeFirstResponder(self.codeText) + self.codeText.setCursorToEnd() + } + } + } + return false + + } + return false + } + + func update(selectedItem:CountryItem?, update:Bool, updateCode:Bool = true) -> Void { + self.selectedItem = selectedItem + if update { + countrySelector.set(text: selectedItem?.shortName ?? tr(L10n.loginInvalidCountryCode), for: .Normal) + _ = countrySelector.sizeToFit() + if updateCode { + codeText.stringValue = selectedItem != nil ? "+\(selectedItem!.code)" : "+" + } + needsLayout = true + setNeedsDisplayLayer() + + } + } + + + + var separatorInset:CGFloat { + return codeText.frame.width + 10 + } + +} + + + +class PassportNewPhoneNumberRowItem: GeneralRowItem, InputDataRowDataValue { + + var value: InputDataValue { + return _value + } + + fileprivate var _value: InputDataValue = .string("") + + init(_ initialSize: NSSize, stableId: AnyHashable, action: @escaping()->Void) { + super.init(initialSize, height: 110, stableId: stableId, action: action) + } + + + override func viewClass() -> AnyClass { + return PassportNewPhoneNumberRowView.self + } +} + + +private final class PassportNewPhoneNumberRowView : TableRowView { + fileprivate let container: PassportPhoneContainerView = PassportPhoneContainerView(frame: NSMakeRect(0, 0, 300, 110), manager: manager) + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(container) + } + +// override var firstResponder: NSResponder? { +// if container.numberText._mouseInside() { +// return container.numberText +// } else if container.codeText._mouseInside() { +// return container.codeText +// } +// return container.numberText +// } +// + override var mouseInsideField: Bool { + return container.numberText._mouseInside() || container.codeText._mouseInside() + } + + override func hitTest(_ point: NSPoint) -> NSView? { + switch true { + case NSPointInRect(point, container.numberText.frame): + return container.numberText + case NSPointInRect(point, container.codeText.frame): + return container.codeText + default: + return super.hitTest(point) + } + } + + override func hasFirstResponder() -> Bool { + return true + } + + override var firstResponder: NSResponder? { + let isKeyDown = NSApp.currentEvent?.type == NSEvent.EventType.keyDown && NSApp.currentEvent?.keyCode == KeyboardKey.Tab.rawValue + switch true { + case container.codeText._mouseInside() && !isKeyDown: + return container.codeText + case container.numberText._mouseInside() && !isKeyDown: + return container.numberText + default: + switch true { + case container.codeText.textView == window?.firstResponder: + return container.codeText.textView + case container.numberText.textView == window?.firstResponder: + return container.numberText.textView + default: + return container.numberText + } + } + } + + + override func nextResponder() -> NSResponder? { + if window?.firstResponder == container.codeText.textView { + return container.numberText + } + if window?.firstResponder == container.numberText.textView { + return container.codeText + } + return nil + } + + override func layout() { + super.layout() + + guard let item = item as? GeneralRowItem else { + return + } + container.setFrameSize(frame.width - item.inset.left - item.inset.right, container.frame.height) + container.center() + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + container.arguments = PassportPhoneNumberArguments(sendCode: { [weak self] phone in + guard let item = self?.item as? PassportNewPhoneNumberRowItem else {return} + item._value = .string(phone) + }) + } + + override func updateColors() { + super.updateColors() + container.updateLocalizationAndTheme(theme: theme) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/Telegram-Mac/PassportSettingsHeader.swift b/Telegram-Mac/PassportSettingsHeader.swift new file mode 100644 index 0000000000..6073c9868d --- /dev/null +++ b/Telegram-Mac/PassportSettingsHeader.swift @@ -0,0 +1,13 @@ +// +// PassportSettingsHeader.swift +// Telegram +// +// Created by keepcoder on 12/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +class PassportSettingsHeaderItem: TableRowItem { + +} diff --git a/Telegram-Mac/PassportSettingsHeaderItem.swift b/Telegram-Mac/PassportSettingsHeaderItem.swift new file mode 100644 index 0000000000..80c405f0e1 --- /dev/null +++ b/Telegram-Mac/PassportSettingsHeaderItem.swift @@ -0,0 +1,42 @@ +// +// PassportSettingsHeader.swift +// Telegram +// +// Created by keepcoder on 12/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class PassportSettingsHeaderItem: GeneralRowItem { + + init(_ initialSize: NSSize, stableId: AnyHashable) { + super.init(initialSize, height: theme.icons.passportSettings.backingSize.height, stableId: stableId) + } + + override func viewClass() -> AnyClass { + return PassportSettingsHeaderItemView.self + } +} + +private final class PassportSettingsHeaderItemView : TableRowView { + private let imageView: ImageView = ImageView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + imageView.image = theme.icons.passportSettings + imageView.sizeToFit() + imageView.center() + } + + +} diff --git a/Telegram-Mac/PassportTwoStepVerificationIntroItem.swift b/Telegram-Mac/PassportTwoStepVerificationIntroItem.swift new file mode 100644 index 0000000000..76875a8aa7 --- /dev/null +++ b/Telegram-Mac/PassportTwoStepVerificationIntroItem.swift @@ -0,0 +1,104 @@ +// +// SecureIdTwoStepVerificationIntroItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 22/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore + +class PassportTwoStepVerificationIntroItem: GeneralRowItem { + + fileprivate let headerLayout:TextViewLayout + fileprivate let descLayout:TextViewLayout + init(_ initialSize: NSSize, stableId: AnyHashable, peer: Peer, action: @escaping()->Void) { + let headerAttr = NSMutableAttributedString() + _ = headerAttr.append(string: L10n.secureIdCreatePasswordIntroHeader(peer.displayTitle), color: theme.colors.grayText, font: .normal(.text)) + headerAttr.detectBoldColorInString(with: .medium(.text)) + headerLayout = TextViewLayout(headerAttr, alignment: .center) + + descLayout = TextViewLayout(.initialize(string: L10n.secureIdCreatePasswordIntro, color: theme.colors.grayText, font: .normal(.text)), alignment: .center) + + super.init(initialSize, stableId: stableId, action: action) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override var height: CGFloat { + return headerLayout.layoutSize.height + (theme.icons.twoStepVerificationCreateIntro.backingSize.height + 40) + descLayout.layoutSize.height + 20 + 20 + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + headerLayout.measure(width: width - inset.left - inset.right) + descLayout.measure(width: width - inset.left - inset.right) + return success + } + + override func viewClass() -> AnyClass { + return PassportTwoStepVerificationIntroRowView.self + } + +} + +private final class PassportTwoStepVerificationIntroRowView : TableRowView { + private let headerView: TextView = TextView() + private let imageView: ImageView = ImageView() + private let descView: TextView = TextView() + private let button: TitleButton = TitleButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(headerView) + addSubview(imageView) + addSubview(descView) + addSubview(button) + + descView.isSelectable = false + descView.isSelectable = false + button.set(font: .normal(.title), for: .Normal) + + + button.set(handler: { [weak self] _ in + (self?.item as? GeneralRowItem)?.action() + }, for: .Click) + } + + override func layout() { + super.layout() + headerView.centerX(y: 0) + imageView.centerX(y: headerView.frame.maxY + 20) + descView.centerX(y: imageView.frame.maxY + 20) + button.centerX(y: descView.frame.maxY + 20) + } + + + override func updateColors() { + super.updateColors() + button.set(color: theme.colors.accent, for: .Normal) + headerView.backgroundColor = theme.colors.background + descView.backgroundColor = theme.colors.background + button.set(background: theme.colors.background, for: .Normal) + imageView.image = theme.icons.twoStepVerificationCreateIntro + imageView.sizeToFit() + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? PassportTwoStepVerificationIntroItem else {return} + + button.set(text: L10n.secureIdRequestCreatePassword, for: .Normal) + _ = button.sizeToFit() + headerView.update(item.headerLayout) + descView.update(item.descLayout) + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PassportWindowController.swift b/Telegram-Mac/PassportWindowController.swift new file mode 100644 index 0000000000..2d1a3a732a --- /dev/null +++ b/Telegram-Mac/PassportWindowController.swift @@ -0,0 +1,118 @@ +// +// PasswordWindow.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class PassportWindowArguments { + let back:()->Void + init(back:@escaping()->Void) { + self.back = back + } +} + + +private(set) var passport: PassportWindowController? = nil + + +class PassportWindowController { + let window: Window + let controller: PassportController + let navigationController: NavigationViewController + init(context: AccountContext, peer: Peer, request: inAppSecureIdRequest, form: EncryptedSecureIdForm) { + + + let screen = NSScreen.main! + let size = NSMakeSize(390, 600) + let center = NSMakeRect(floorToScreenPixels(System.backingScale, (screen.frame.width - size.width)/2), floorToScreenPixels(System.backingScale, (screen.frame.height - size.height)/2), size.width, size.height) + + + + window = Window(contentRect: center, styleMask: [.closable, .resizable, .miniaturizable, .fullSizeContentView, .titled, .unifiedTitleAndToolbar, .texturedBackground], backing: .buffered, defer: true) + + + + controller = PassportController(context, peer, request: request, form) + navigationController = NavigationViewController(controller, window) + + + window.isMovableByWindowBackground = true + window.name = "Telegram.PassportWindow" + //window.initSaver() + navigationController._frameRect = NSMakeRect(0, 0, size.width, size.height - 50) + window.titlebarAppearsTransparent = true + window.minSize = size + window.maxSize = size + window.contentView = navigationController.view + + window.closeInterceptor = { [weak self] in + guard let `self` = self else {return true} + self.window.orderOut(nil) + passport = nil + return true + } + (navigationController.view as? View)?.customHandler.layout = { [weak self] _ in + self?.windowDidNeedSaveState(Notification(name: Notification.Name(rawValue: ""))) + } + + windowDidNeedSaveState(Notification(name: Notification.Name(rawValue: ""))) + + if let titleView = window.titleView { + NotificationCenter.default.addObserver(self, selector: #selector(windowDidNeedSaveState(_:)), name: NSView.frameDidChangeNotification, object: titleView) + } + + navigationController.viewDidAppear(false) + + navigationController.doSomethingOnEmptyBack = { [weak self] in + guard let `self` = self else {return} + self.window.orderOut(nil) + passport = nil + } + + controller.viewDidAppear(false) + } + + var barHeight: CGFloat { + return 50 + } + + + @objc func windowDidNeedSaveState(_ notification: Notification) { + if let titleView = window.titleView { + let frame = NSMakeRect(0, window.frame.height - barHeight, titleView.frame.width, barHeight) + if !NSEqualRects(frame, titleView.frame) { + titleView.frame = frame + } + if let controls = (HackUtils.findElements(byClass: "NSTitlebarView", in: titleView)?.first as? NSView)?.subviews { + var xs:[CGFloat] = [18, 58, 38] + for i in 0 ..< min(controls.count, xs.count) { + let view = controls[i] + view.isHidden = true + view.setFrameOrigin(xs[i], floorToScreenPixels(System.backingScale, (barHeight - view.frame.height)/2)) + } + } + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + window.orderOut(nil) + if let titleView = window.titleView { + NotificationCenter.default.removeObserver(titleView) + } + } + + func show() { + passport = self + window.makeKeyAndOrderFront(nil) + } +} diff --git a/Telegram-Mac/PeerChannelMemberCategoriesContextsManager.swift b/Telegram-Mac/PeerChannelMemberCategoriesContextsManager.swift new file mode 100644 index 0000000000..cc1d2aba0b --- /dev/null +++ b/Telegram-Mac/PeerChannelMemberCategoriesContextsManager.swift @@ -0,0 +1,409 @@ +// +// PeerChannelMemberCategoriesContextsManager.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +enum PeerChannelMemberContextKey: Equatable, Hashable { + case recent + case recentSearch(String) + case admins(String?) + case contacts(String?) + case bots(String?) + case restrictedAndBanned(String?) + case restricted(String?) + case banned(String?) + +} + + +private final class PeerChannelMembersOnlineContext { + let subscribers = Bag<(Int32) -> Void>() + let disposable: Disposable + var value: Int32? + var emptyTimer: SwiftSignalKit.Timer? + + init(disposable: Disposable) { + self.disposable = disposable + } +} + + +private final class PeerChannelMemberCategoriesContextsManagerImpl { + fileprivate var contexts: [PeerId: PeerChannelMemberCategoriesContext] = [:] + fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:] + + + func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { + if let current = self.contexts[peerId] { + return current.getContext(key: key, requestUpdate: requestUpdate, updated: updated) + } else { + var becameEmptyImpl: ((Bool) -> Void)? + let context = PeerChannelMemberCategoriesContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, becameEmpty: { value in + becameEmptyImpl?(value) + }) + becameEmptyImpl = { [weak self, weak context] value in + assert(Queue.mainQueue().isCurrent()) + if let strongSelf = self { + if let current = strongSelf.contexts[peerId], current === context { + strongSelf.contexts.removeValue(forKey: peerId) + } + } + } + self.contexts[peerId] = context + return context.getContext(key: key, requestUpdate: requestUpdate, updated: updated) + } + } + + func recentOnline(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, updated: @escaping (Int32) -> Void) -> Disposable { + let context: PeerChannelMembersOnlineContext + if let current = self.onlineContexts[peerId] { + context = current + } else { + let disposable = MetaDisposable() + context = PeerChannelMembersOnlineContext(disposable: disposable) + self.onlineContexts[peerId] = context + + let signal = ( + chatOnlineMembers(postbox: postbox, network: network, peerId: peerId) + |> then( + .complete() + |> delay(30.0, queue: .mainQueue()) + ) + ) |> restart + + disposable.set(signal.start(next: { [weak context] value in + guard let context = context else { + return + } + context.value = value + for f in context.subscribers.copyItems() { + f(value) + } + })) + } + + if let emptyTimer = context.emptyTimer { + emptyTimer.invalidate() + context.emptyTimer = nil + } + + let index = context.subscribers.add({ next in + updated(next) + }) + updated(context.value ?? 0) + + return ActionDisposable { [weak self, weak context] in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + if let current = strongSelf.onlineContexts[peerId], let context = context, current === context { + current.subscribers.remove(index) + if current.subscribers.isEmpty { + if current.emptyTimer == nil { + let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in + if let current = strongSelf.onlineContexts[peerId], let context = context, current === context { + if current.subscribers.isEmpty { + strongSelf.onlineContexts.removeValue(forKey: peerId) + current.disposable.dispose() + } + } + }, queue: Queue.mainQueue()) + current.emptyTimer = timer + timer.start() + } + } + } + } + } + } + + + + func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl) { + if let context = self.contexts[peerId] { + context.loadMore(control) + } + } +} + +final class PeerChannelMemberCategoriesContextsManager { + private let impl: QueueLocalObject + + init() { + self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { + return PeerChannelMemberCategoriesContextsManagerImpl() + }) + } + + func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl?) { + if let control = control { + self.impl.with { impl in + impl.loadMore(peerId: peerId, control: control) + } + } + } + + private func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + assert(Queue.mainQueue().isCurrent()) + if let (disposable, control) = self.impl.syncWith({ impl in + return impl.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) + }) { + return (disposable, control) + } else { + return (EmptyDisposable, nil) + } + } + + func transferOwnership(account: Account, peerId: PeerId, memberId: PeerId, password: String) -> Signal { + return updateChannelOwnership(account: account, accountStateManager: account.stateManager, channelId: peerId, memberId: memberId, password: password) + |> map(Optional.init) + |> deliverOnMainQueue + |> beforeNext { [weak self] results in + if let strongSelf = self, let results = results { + strongSelf.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.replayUpdates(results.map { ($0.0, $0.1, nil) }) + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + + + func externallyAdded(peerId: PeerId, participant: RenderedChannelParticipant) { + self.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if contextPeerId == peerId { + context.replayUpdates([(nil, participant, nil)]) + } + } + } + } + + func recent(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + let key: PeerChannelMemberContextKey + if let searchQuery = searchQuery { + key = .recentSearch(searchQuery) + } else { + key = .recent + } + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) + } + + func recentOnline(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal { + return Signal { [weak self] subscriber in + guard let strongSelf = self else { + subscriber.putNext(0) + subscriber.putCompletion() + return EmptyDisposable + } + let disposable = strongSelf.impl.syncWith({ impl -> Disposable in + return impl.recentOnline(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { value in + subscriber.putNext(value) + }) + }) + return disposable ?? EmptyDisposable + } + |> runOn(Queue.mainQueue()) + } + + + func recentOnlineSmall(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal { + return Signal { [weak self] subscriber in + var previousIds: Set? + let statusesDisposable = MetaDisposable() + let disposableAndControl = self?.recent(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { state in + var idList: [PeerId] = [] + for item in state.list { + idList.append(item.peer.id) + if idList.count >= 200 { + break + } + } + let updatedIds = Set(idList) + if previousIds != updatedIds { + previousIds = updatedIds + let key: PostboxViewKey = .peerPresences(peerIds: updatedIds) + statusesDisposable.set((postbox.combinedView(keys: [key]) + |> map { view -> Int32 in + var count: Int32 = 0 + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + if let presences = (view.views[key] as? PeerPresencesView)?.presences { + for (_, presence) in presences { + if let presence = presence as? TelegramUserPresence { + let relativeStatus = relativeUserPresenceStatus(presence, timeDifference: network.globalTime > 0 ? network.globalTime - timestamp : 0, relativeTo: Int32(timestamp)) + sw: switch relativeStatus { + case let .online(at: until): + if until > Int32(timestamp) { + count += 1 + } else { + var bp:Int = 0 + bp += 1 + } + default: + break sw + } + } + } + } + return count + } + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { count in + subscriber.putNext(count) + })) + } + }) + return ActionDisposable { + disposableAndControl?.0.dispose() + statusesDisposable.dispose() + } + } + |> runOn(Queue.mainQueue()) + + } + + func admins(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) + } + + func restricted(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restricted(searchQuery), requestUpdate: true, updated: updated) + } + + func banned(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .banned(searchQuery), requestUpdate: true, updated: updated) + } + + func restrictedAndBanned(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated) + } + + func updateMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChatBannedRights?) -> Signal { + return updateChannelMemberBannedRights(account: account, peerId: peerId, memberId: memberId, rights: bannedRights) + |> deliverOnMainQueue + |> beforeNext { [weak self] (previous, updated, isMember) in + if let strongSelf = self { + strongSelf.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.replayUpdates([(previous, updated, isMember)]) + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + + func updateMemberAdminRights(account: Account, peerId: PeerId, memberId: PeerId, adminRights: TelegramChatAdminRights, rank: String?) -> Signal { + return updateChannelAdminRights(account: account, peerId: peerId, adminId: memberId, rights: adminRights, rank: rank) + |> map(Optional.init) + |> `catch` { _ -> Signal<(ChannelParticipant?, RenderedChannelParticipant)?, NoError> in + return .single(nil) + } + |> deliverOnMainQueue + |> beforeNext { [weak self] result in + if let strongSelf = self, let (previous, updated) = result { + strongSelf.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.replayUpdates([(previous, updated, nil)]) + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + + func addMember(account: Account, peerId: PeerId, memberId: PeerId) -> Signal { + return addChannelMember(account: account, peerId: peerId, memberId: memberId) + |> map(Optional.init) + |> `catch` { _ -> Signal<(ChannelParticipant?, RenderedChannelParticipant)?, NoError> in + return .single(nil) + } + |> deliverOnMainQueue + |> beforeNext { [weak self] result in + if let strongSelf = self, let (previous, updated) = result { + strongSelf.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.replayUpdates([(previous, updated, nil)]) + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + + func addMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal { + let signals: [Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError>] = memberIds.map({ memberId in + return addChannelMember(account: account, peerId: peerId, memberId: memberId) + |> map(Optional.init) + |> `catch` { error -> Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError> in + return .fail(error) + } + }) + return combineLatest(signals) + |> deliverOnMainQueue + |> beforeNext { [weak self] results in + if let strongSelf = self { + strongSelf.impl.with { impl in + for result in results { + if let (previous, updated) = result { + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.replayUpdates([(previous, updated, nil)]) + } + } + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + + /*return addChannelMembers(account: account, peerId: peerId, memberIds: memberIds) + |> deliverOnMainQueue + |> beforeNext { [weak self] result in + if let strongSelf = self { + strongSelf.impl.with { impl in + for (contextPeerId, context) in impl.contexts { + if peerId == contextPeerId { + context.reset(.recent) + } + } + } + } + } + |> mapToSignal { _ -> Signal in + return .single(Void()) + }*/ + } + +} diff --git a/Telegram-Mac/PeerEmptyHolderItem.swift b/Telegram-Mac/PeerEmptyHolderItem.swift new file mode 100644 index 0000000000..65e0923b46 --- /dev/null +++ b/Telegram-Mac/PeerEmptyHolderItem.swift @@ -0,0 +1,108 @@ +// +// PeerEmptyHolderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 17.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class PeerEmptyHolderItem: GeneralRowItem { + fileprivate let photoSize: NSSize + init(_ initialSize: NSSize, stableId: AnyHashable, height: CGFloat, photoSize: NSSize, viewType: GeneralViewType) { + self.photoSize = photoSize + super.init(initialSize, height: height, stableId: stableId, viewType: viewType) + } + + override func viewClass() -> AnyClass { + return PeerEmptyHolderView.self + } +} + +class PeerEmptyHolderView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let photoView: View = View() + private let firstNameView = View() + private let lastNameView = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + containerView.addSubview(firstNameView) + containerView.addSubview(lastNameView) + containerView.addSubview(photoView) + addSubview(containerView) + + firstNameView.setFrameSize(NSMakeSize(50, 10)) + lastNameView.setFrameSize(NSMakeSize(50, 10)) + + firstNameView.layer?.cornerRadius = 5 + lastNameView.layer?.cornerRadius = 5 + } + + override func viewDidMoveToWindow() { + + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + if let item = item as? GeneralRowItem { + containerView.background = backdorColor + backgroundColor = item.viewType.rowBackground + photoView.backgroundColor = theme.colors.grayBackground + firstNameView.backgroundColor = theme.colors.grayBackground + lastNameView.backgroundColor = theme.colors.grayBackground + } + } + + override func layout() { + super.layout() + + if let item = item as? PeerEmptyHolderItem { + switch item.viewType { + case .legacy: + containerView.frame = bounds + case .modern: + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + } + self.containerView.setCorners(item.viewType.corners) + + + photoView.centerY(x: 10) + + firstNameView.centerY(x: photoView.frame.maxX + 10) + lastNameView.centerY(x: firstNameView.frame.maxX + 10) + } + } + + deinit { + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + if let item = item as? PeerEmptyHolderItem { + let contentRect: NSRect + switch item.viewType { + case .legacy: + contentRect = bounds + case .modern: + contentRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + } + self.containerView.change(size: contentRect.size, animated: animated, corners: item.viewType.corners) + self.containerView.change(pos: contentRect.origin, animated: animated) + + photoView.setFrameSize(item.photoSize) + photoView.layer?.cornerRadius = item.photoSize.height / 2 + needsLayout = true + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PeerInfoController.swift b/Telegram-Mac/PeerInfoController.swift index 45f7b1e684..e48e9ba5ed 100644 --- a/Telegram-Mac/PeerInfoController.swift +++ b/Telegram-Mac/PeerInfoController.swift @@ -8,63 +8,28 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac - -class PeerInfoTitleBarView : TitledBarView { - private var search:ImageButton = ImageButton() - init(controller: ViewController, title:NSAttributedString, handler:@escaping() ->Void) { - super.init(controller: controller, title) - search.set(handler: { _ in - handler() - }, for: .Click) - addSubview(search) - updateLocalizationAndTheme() - } - - func updateSearchVisibility(_ visible: Bool) { - search.isHidden = !visible - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - search.set(image: theme.icons.chatSearch, for: .Normal) - search.sizeToFit() - backgroundColor = theme.colors.background - needsLayout = true - } - - override func layout() { - super.layout() - search.centerY(x: frame.width - search.frame.width) - } - - - required init(frame frameRect: NSRect) { - fatalError("init(frame:) has not been implemented") - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class PeerInfoArguments { let peerId:PeerId - let account:Account + let context: AccountContext + let isAd: Bool let pushViewController:(ViewController) -> Void let pullNavigation:()->NavigationViewController? + let mediaController: ()->PeerMediaController? + - private let peerInfoDisposable = MetaDisposable() private let toggleNotificationsDisposable = MetaDisposable() private let deleteDisposable = MetaDisposable() private let _statePromise = Promise() - var statePromise:Signal { + var statePromise:Signal { return _statePromise.get() } @@ -74,13 +39,12 @@ class PeerInfoArguments { return value.modify {$0} } - func updateInfoState(_ f: (PeerInfoState) -> PeerInfoState) -> Void { - _statePromise.set(.single(value.modify({f($0)}))) + _statePromise.set(.single(value.modify(f))) } - func updateEditable(_ editable:Bool, peerView:PeerView) { - + func updateEditable(_ editable:Bool, peerView:PeerView, controller: PeerInfoController) -> Bool { + return true } func dismissEdition() { @@ -88,49 +52,50 @@ class PeerInfoArguments { } func peerInfo(_ peerId:PeerId) { - peerInfoDisposable.set((account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.pushViewController(PeerInfoController(account: strongSelf.account, peer: peer)) - } - })) + pushViewController(PeerInfoController(context: context, peerId: peerId)) } - func peerChat(_ peerId:PeerId) { - pushViewController(ChatController(account: account, peerId: peerId)) + func peerChat(_ peerId:PeerId, postId: MessageId? = nil) { + pushViewController(ChatController(context: context, chatLocation: .peer(peerId), messageId: postId)) } func toggleNotifications() { - toggleNotificationsDisposable.set(togglePeerMuted(account: account, peerId: peerId).start()) + toggleNotificationsDisposable.set(togglePeerMuted(account: context.account, peerId: peerId).start()) } func delete() { - let account = self.account + let context = self.context let peerId = self.peerId - - deleteDisposable.set((removeChatInteractively(account: account, peerId:peerId) |> deliverOnMainQueue).start(next: { [weak self] success in - if success { - self?.pullNavigation()?.close() - } - })) + let isEditing = (state as? GroupInfoState)?.editingState != nil || (state as? ChannelInfoState)?.editingState != nil + + let signal = context.account.postbox.peerView(id: peerId) |> take(1) |> mapToSignal { view -> Signal in + return removeChatInteractively(context: context, peerId: peerId, userId: peerViewMainPeer(view)?.id, deleteGroup: isEditing && peerViewMainPeer(view)?.groupAccess.isCreator == true) + } + + deleteDisposable.set(signal.start()) } func sharedMedia() { - pushViewController(PeerMediaController(account: account, peerId: peerId, tagMask: .photoOrVideo)) + if let controller = self.mediaController() { + pushViewController(controller) + } } - init(account:Account, peerId:PeerId, state:PeerInfoState, pushViewController:@escaping(ViewController)->Void, pullNavigation:@escaping()->NavigationViewController?) { + init(context: AccountContext, peerId:PeerId, state:PeerInfoState, isAd: Bool, pushViewController:@escaping(ViewController)->Void, pullNavigation:@escaping()->NavigationViewController?, mediaController: @escaping()->PeerMediaController?) { self.value = Atomic(value: state) _statePromise.set(.single(state)) - self.account = account + self.context = context self.peerId = peerId + self.isAd = isAd self.pushViewController = pushViewController self.pullNavigation = pullNavigation + self.mediaController = mediaController } + deinit { toggleNotificationsDisposable.dispose() - peerInfoDisposable.dispose() deleteDisposable.dispose() } } @@ -183,16 +148,66 @@ private struct PeerInfoSortableEntry: Identifiable, Comparable { } } +struct PeerMediaTabsData : Equatable { + let collections:[PeerMediaCollectionMode] + let loaded: Bool +} -fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, peerId:PeerId, arguments: PeerInfoArguments, animated:Bool) -> TableUpdateTransition { - +fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, peerId:PeerId, arguments: PeerInfoArguments, animated:Bool) -> Signal { - let (deleted,inserted, updated) = proccessEntries(from, right: to, { (peerInfoSortableEntry) -> TableRowItem in - return peerInfoSortableEntry.entry.entry.item(initialSize: initialSize, arguments: arguments) - }) + return Signal { subscriber in + + var cancelled = false + + if Thread.isMainThread { + var initialIndex:Int = 0 + var height:CGFloat = 0 + var firstInsertion:[(Int, TableRowItem)] = [] + let entries = Array(to) + + let index:Int = 0 + + for i in index ..< entries.count { + let item = entries[i].entry.entry.item(initialSize: initialSize, arguments: arguments) + height += item.height + firstInsertion.append((i, item)) + if initialSize.height < height { + break + } + } + + + initialIndex = firstInsertion.count + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state: .none(nil))) + + prepareQueue.async { + if !cancelled { + var insertions:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + + for i in initialIndex ..< entries.count { + let item:TableRowItem + item = entries[i].entry.entry.item(initialSize: initialSize, arguments: arguments) + insertions.append((i, item)) + } + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: insertions, updated: updates, state: .none(nil))) + subscriber.putCompletion() + } + } + } else { + let (deleted,inserted, updated) = proccessEntriesWithoutReverse(from, right: to, { (peerInfoSortableEntry) -> TableRowItem in + return peerInfoSortableEntry.entry.entry.item(initialSize: initialSize, arguments: arguments) + }) + subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated: updated, animated: animated, state: animated ? .none(nil) : .saveVisible(.lower), grouping: true, animateVisibleOnly: false)) + subscriber.putCompletion() + } + + return ActionDisposable { + cancelled = true + } + } - return TableUpdateTransition(deleted: deleted, inserted: inserted, updated: updated, animated:animated, state: animated ? .none(nil) : .saveVisible(.lower)) } @@ -201,69 +216,59 @@ class PeerInfoController: EditableViewController { private let updatedChannelParticipants:MetaDisposable = MetaDisposable() let peerId:PeerId - private let peerViewDisposable:MetaDisposable = MetaDisposable() - private let peerAtomic:Atomic - private let peerView:Atomic = Atomic(value: nil) + private let arguments:Promise = Promise() + + private let peerView:Atomic = Atomic(value: nil) private var _groupArguments:GroupInfoArguments! private var _userArguments:UserInfoArguments! private var _channelArguments:ChannelInfoArguments! + + private let peerInputActivitiesDisposable = MetaDisposable() + + private var argumentsAction: DisposableSet = DisposableSet() var disposable:MetaDisposable = MetaDisposable() - init(account:Account, peer:Peer) { - peerAtomic = Atomic(value: peer) - self.peerId = peer.id - super.init(account) + private let mediaController: PeerMediaController + + init(context: AccountContext, peerId:PeerId, isAd: Bool = false) { + self.peerId = peerId + self.mediaController = PeerMediaController(context: context, peerId: peerId, isProfileIntended: true) + super.init(context) let pushViewController:(ViewController) -> Void = { [weak self] controller in self?.navigationController?.push(controller) } - _groupArguments = GroupInfoArguments(account: account, peerId: peerId, state: GroupInfoState(), pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in + _groupArguments = GroupInfoArguments(context: context, peerId: peerId, state: GroupInfoState(), isAd: isAd, pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in return self?.navigationController + }, mediaController: { [weak self] in + return self?.mediaController }) - _userArguments = UserInfoArguments(account: account, peerId: peerId, state: UserInfoState(), pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in - return self?.navigationController + _userArguments = UserInfoArguments(context: context, peerId: peerId, state: UserInfoState(), isAd: isAd, pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in + return self?.navigationController + }, mediaController: { [weak self] in + return self?.mediaController }) - _channelArguments = ChannelInfoArguments(account: account, peerId: peerId, state: ChannelInfoState(), pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in + _channelArguments = ChannelInfoArguments(context: context, peerId: peerId, state: ChannelInfoState(), isAd: isAd, pushViewController: pushViewController, pullNavigation:{ [weak self] () -> NavigationViewController? in return self?.navigationController + }, mediaController: { [weak self] in + return self?.mediaController }) - - } - - override func getCenterBarViewOnce() -> TitledBarView { - return PeerInfoTitleBarView(controller: self, title:.initialize(string: defaultBarTitle, color: theme.colors.text, font: .medium(.title)), handler: { [weak self] in - self?.searchSupergroupUsers() - }) - } - - func searchSupergroupUsers() { - _ = (selectModalPeers(account: account, title: "", behavior: SelectChannelMembersBehavior(peerId: peerId, limit: 1, settings: [])) |> deliverOnMainQueue |> map {$0.first}).start(next: { [weak self] peerId in - if let peerId = peerId { - self?._channelArguments.peerInfo(peerId) - } - }) + } deinit { disposable.dispose() updatedChannelParticipants.dispose() + peerInputActivitiesDisposable.dispose() + argumentsAction.dispose() window?.removeAllHandlers(for: self) } - private var arguments:PeerInfoArguments { - let peer = peerAtomic.modify({$0}) - if peer.isGroup || peer.isSupergroup { - return _groupArguments - } else if peer.isChannel { - return _channelArguments - } else { - return _userArguments - } - } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -280,8 +285,10 @@ class PeerInfoController: EditableViewController { } return .rejected }, with: self, for: .Return, priority: .high, modifierFlags: [.command]) + } + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) window?.removeAllHandlers(for: self) @@ -303,38 +310,91 @@ class PeerInfoController: EditableViewController { return .invokeNext } - + override func viewDidLoad() -> Void { super.viewDidLoad() + self.genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let previousEntries = Atomic<[AppearanceWrapperEntry]?>(value: nil) - let account = self.account + let context = self.context let peerId = self.peerId let initialSize = atomicSize - let arguments = self.arguments + let onMainQueue: Atomic = Atomic(value: true) - let transition = combineLatest(account.viewTracker.peerView(peerId), arguments.statePromise |> distinctUntilChanged, appearanceSignal) |> deliverOn(prepareQueue) - |> map { [weak peerAtomic] view, state, appearance -> (PeerView, TableUpdateTransition) in - - let entries:[AppearanceWrapperEntry] = peerInfoEntries(view: view, arguments: arguments).map({PeerInfoSortableEntry(entry: $0)}).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) - - let previous = previousEntries.swap(entries) - - _ = peerAtomic?.modify{ (previous) -> Peer in - if let peer = peerViewMainPeer(view) { - return peer - } else { - return previous - } - } - - return (view, prepareEntries(from: previous, to: entries, account: account, initialSize: initialSize.modify({$0}), peerId: peerId, arguments:arguments, animated: previous != nil)) - - } |> deliverOnMainQueue + mediaController.navigationController = self.navigationController + mediaController._frameRect = bounds + mediaController.bar = .init(height: 0) + + mediaController.loadViewIfNeeded() + + let inputActivity = context.account.peerInputActivities(peerId: peerId) + |> map { activities -> [PeerId : PeerInputActivity] in + return activities.reduce([:], { (current, activity) -> [PeerId : PeerInputActivity] in + var current = current + current[activity.0] = activity.1 + return current + }) + } + + let inputActivityState: Promise<[PeerId : PeerInputActivity]> = Promise([:]) + + + arguments.set(context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { [weak self] peer in + guard let `self` = self else {return .never()} + + if peer.isGroup || peer.isSupergroup { + inputActivityState.set(inputActivity) + } + + if peer.isGroup || peer.isSupergroup { + return .single(self._groupArguments) + } else if peer.isChannel { + return .single(self._channelArguments) + } else { + return .single(self._userArguments) + } + }) + + let actionsDisposable = DisposableSet() + + var loadMoreControl: PeerChannelMemberCategoryControl? + + let channelMembersPromise = Promise<[RenderedChannelParticipant]>() + if peerId.namespace == Namespaces.Peer.CloudChannel { + let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + channelMembersPromise.set(.single(state.list)) + }) + loadMoreControl = control + actionsDisposable.add(disposable) + } else { + channelMembersPromise.set(.single([])) + } + + let mediaTabsData: Signal = mediaController.tabsValue + let mediaReady = mediaController.ready.get() |> take(1) + + + let transition = arguments.get() |> mapToSignal { arguments in + return combineLatest(queue: prepareQueue, context.account.viewTracker.peerView(peerId, updateData: true), arguments.statePromise, appearanceSignal, inputActivityState.get(), channelMembersPromise.get(), mediaTabsData, mediaReady) + |> mapToQueue { view, state, appearance, inputActivities, channelMembers, mediaTabsData, _ -> Signal<(PeerView, TableUpdateTransition), NoError> in + + let entries:[AppearanceWrapperEntry] = peerInfoEntries(view: view, arguments: arguments, inputActivities: inputActivities, channelMembers: channelMembers, mediaTabsData: mediaTabsData).map({PeerInfoSortableEntry(entry: $0)}).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) + let previous = previousEntries.swap(entries) + return prepareEntries(from: previous, to: entries, account: context.account, initialSize: initialSize.modify({$0}), peerId: peerId, arguments:arguments, animated: previous != nil) |> runOn(onMainQueue.swap(false) ? .mainQueue() : prepareQueue) |> map { (view, $0) } + + } |> deliverOnMainQueue + } |> afterDisposed { + actionsDisposable.dispose() + } + disposable.set(transition.start(next: { [weak self] (peerView, transition) in - + _ = self?.peerView.swap(peerView) let editable:Bool @@ -342,34 +402,45 @@ class PeerInfoController: EditableViewController { if let peer = peer as? TelegramChannel { switch peer.info { case .broadcast: - editable = peer.hasAdminRights(.canChangeInfo) + editable = peer.adminRights != nil || peer.flags.contains(.isCreator) case .group: - editable = true //peer.adminRights != nil || peer.flags.contains(.isCreator) + editable = peer.adminRights != nil || peer.flags.contains(.isCreator) } - - } else if let peer = peer as? TelegramGroup { - editable = peer.role == .creator || peer.role == .admin || !peer.flags.contains(.adminsEnabled) - } else if peer is TelegramUser { - editable = peerView.peerIsContact && account.peerId != peer.id + } else if let group = peer as? TelegramGroup { + switch group.role { + case .admin, .creator: + editable = true + default: + editable = group.groupAccess.canEditGroupInfo || group.groupAccess.canEditMembers + } + } else if peer is TelegramUser, !peer.isBot, peerView.peerIsContact { + editable = context.account.peerId != peer.id } else { editable = false } - (self?.centerBarView as? PeerInfoTitleBarView)?.updateSearchVisibility(peer.isSupergroup) } else { editable = false } self?.set(editable: editable) - self?.readyOnce() self?.genericView.merge(with:transition) + self?.readyOnce() + })) - if peerId.namespace == Namespaces.Peer.CloudChannel { - let fetchParticipants = account.viewTracker.peerView(peerId) |> filter {$0.cachedData != nil} |> take(1) |> deliverOnMainQueue |> mapToSignal {_ -> Signal in - return account.viewTracker.updatedCachedChannelParticipants(peerId, forceImmediateUpdate: true) + + genericView.setScrollHandler { position in + if let loadMoreControl = loadMoreControl { + switch position.direction { + case .bottom: + context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl) + default: + break + } } - updatedChannelParticipants.set(fetchParticipants.start()) + } + } @@ -380,19 +451,38 @@ class PeerInfoController: EditableViewController { return .rejected } - + func updateArguments(_ f:@escaping(PeerInfoArguments) -> Void) { + argumentsAction.add((arguments.get() |> take(1)).start(next: { arguments in + f(arguments) + })) + } override func update(with state: ViewControllerState) { - super.update(with: state) - if let peerView = peerView.modify({$0}) { - self.arguments.updateEditable(state == .Edit, peerView: peerView) + + if let peerView = peerView.with ({$0}) { + updateArguments({ [weak self] arguments in + guard let `self` = self else { + return + } + let updateState = arguments.updateEditable(state == .Edit, peerView: peerView, controller: self) + self.genericView.scroll(to: .up(true)) + + if updateState { + self.applyState(state) + } + }) } } + private func applyState(_ state: ViewControllerState) { + super.update(with: state) + } override func escapeKeyAction() -> KeyHandlerResult { if state == .Edit { - arguments.dismissEdition() + updateArguments({ arguments in + arguments.dismissEdition() + }) state = .Normal return .invoked } diff --git a/Telegram-Mac/PeerInfoEntries.swift b/Telegram-Mac/PeerInfoEntries.swift index a685e1fae5..fc7174255e 100644 --- a/Telegram-Mac/PeerInfoEntries.swift +++ b/Telegram-Mac/PeerInfoEntries.swift @@ -7,9 +7,10 @@ // import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox import TGUIKit @@ -51,18 +52,22 @@ struct IntPeerInfoEntryStableId: PeerInfoEntryStableId { final class PeerInfoUpdatingPhotoState : Equatable { let progress:Float let cancel:()->Void - - init(progress: Float, cancel: @escaping()->Void) { + let image: CGImage? + init(progress: Float, image: CGImage? = nil, cancel: @escaping()->Void) { self.progress = progress self.cancel = cancel + self.image = image + } + func withUpdatedImage(_ image: CGImage) -> PeerInfoUpdatingPhotoState { + return PeerInfoUpdatingPhotoState(progress: progress, image: image, cancel: self.cancel) } func withUpdatedProgress(_ progress: Float) -> PeerInfoUpdatingPhotoState { - return PeerInfoUpdatingPhotoState(progress: progress, cancel: self.cancel) + return PeerInfoUpdatingPhotoState(progress: progress, image: self.image, cancel: self.cancel) } static func ==(lhs:PeerInfoUpdatingPhotoState, rhs: PeerInfoUpdatingPhotoState) -> Bool { - return lhs.progress == rhs.progress + return lhs.progress == rhs.progress && lhs.image == rhs.image } } @@ -75,18 +80,18 @@ protocol PeerInfoEntry { } -func peerInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfoEntry] { +func peerInfoEntries(view: PeerView, arguments: PeerInfoArguments, inputActivities: [PeerId: PeerInputActivity], channelMembers: [RenderedChannelParticipant], mediaTabsData: PeerMediaTabsData) -> [PeerInfoEntry] { if peerViewMainPeer(view) is TelegramUser { - return userInfoEntries(view: view, arguments: arguments) + return userInfoEntries(view: view, arguments: arguments, mediaTabsData: mediaTabsData) } else if let channel = peerViewMainPeer(view) as? TelegramChannel { switch channel.info { case .broadcast: - return channelInfoEntries(view: view, arguments: arguments) + return channelInfoEntries(view: view, arguments: arguments, mediaTabsData: mediaTabsData) case .group: - return groupInfoEntries(view: view, arguments: arguments) + return groupInfoEntries(view: view, arguments: arguments, inputActivities: inputActivities, channelMembers: channelMembers, mediaTabsData: mediaTabsData) } } else if peerViewMainPeer(view) is TelegramGroup { - return groupInfoEntries(view: view, arguments: arguments) + return groupInfoEntries(view: view, arguments: arguments, inputActivities: inputActivities, mediaTabsData: mediaTabsData) } return [] } diff --git a/Telegram-Mac/PeerInfoHeadItem.swift b/Telegram-Mac/PeerInfoHeadItem.swift new file mode 100644 index 0000000000..8d5db710b4 --- /dev/null +++ b/Telegram-Mac/PeerInfoHeadItem.swift @@ -0,0 +1,887 @@ +// +// PeerInfoHeadItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 01/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + + +fileprivate final class ActionButton : Control { + fileprivate let imageView: ImageView = ImageView() + fileprivate let textView: TextView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + + self.imageView.animates = true + imageView.isEventLess = true + textView.isEventLess = true + + set(handler: { control in + control.change(opacity: 0.8, animated: true) + }, for: .Highlight) + + set(handler: { control in + control.change(opacity: 1.0, animated: true) + }, for: .Normal) + + set(handler: { control in + control.change(opacity: 1.0, animated: true) + }, for: .Hover) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateAndLayout(item: ActionItem, theme: PresentationTheme) { + self.imageView.image = item.image + _ = self.imageView.sizeToFit() + self.textView.update(item.textLayout) + + self.removeAllHandlers() + if let subItems = item.subItems { + self.set(handler: { control in + showPopover(for: control, with: SPopoverViewController(items: subItems.map { SPopoverItem($0.text, $0.action, nil, $0.destruct ? theme.colors.redUI : theme.colors.text) }), edge: .maxY, inset: NSMakePoint(-33, -60)) + }, for: .Down) + } else { + self.set(handler: { [weak item] _ in + item?.action() + }, for: .Click) + } + + + needsLayout = true + + } + + override func layout() { + super.layout() + imageView.centerX(y: 0) + + let bottomInset = floorToScreenPixels(backingScaleFactor, ((frame.height - imageView.frame.maxY - 10) - textView.frame.height) / 2) + textView.centerX(y: (imageView.frame.maxY + 10) + bottomInset) + } +} + +fileprivate let photoDimension:CGFloat = 100 +fileprivate let actionItemWidth: CGFloat = 70 +fileprivate let actionItemInsetWidth: CGFloat = 20 + +private struct SubActionItem { + let text: String + let destruct: Bool + let action:()->Void + init(text: String, destruct: Bool = false, action:@escaping()->Void) { + self.text = text + self.action = action + self.destruct = destruct + } +} + +private final class ActionItem { + let text: String + let destruct: Bool + let image: CGImage + let action:()->Void + + let subItems:[SubActionItem]? + + + let textLayout: TextViewLayout + let size: NSSize + + init(text: String, image: CGImage, destruct: Bool = false, action: @escaping()->Void, subItems:[SubActionItem]? = nil) { + self.text = text + self.image = image + self.action = action + self.subItems = subItems + self.destruct = destruct + self.textLayout = TextViewLayout(.initialize(string: text, color: theme.colors.accent, font: .normal(.text)), alignment: .center) + self.textLayout.measure(width: actionItemWidth) + + self.size = NSMakeSize(actionItemWidth, image.backingSize.height + 10 + textLayout.layoutSize.height) + } + +} + +private func actionItems(item: PeerInfoHeadItem, width: CGFloat, theme: TelegramPresentationTheme) -> [ActionItem] { + + var items:[ActionItem] = [] + + var rowItemsCount: Int = 1 + + while width - actionItemWidth * 2 > actionItemWidth * CGFloat(rowItemsCount) + CGFloat(rowItemsCount + 1) * actionItemInsetWidth { + rowItemsCount += 1 + } + rowItemsCount = min(rowItemsCount, 4) + + if let peer = item.peer as? TelegramUser, let arguments = item.arguments as? UserInfoArguments { + if !(item.peerView.peers[item.peerView.peerId] is TelegramSecretChat) { + items.append(ActionItem(text: L10n.peerInfoActionMessage, image: theme.icons.profile_message, action: arguments.sendMessage)) + } + if peer.canCall && peer.id != item.context.peerId, !isServicePeer(peer) && !peer.rawDisplayTitle.isEmpty { + items.append(ActionItem(text: L10n.peerInfoActionCall, image: theme.icons.profile_call, action: arguments.call)) + } + + let value = item.peerView.notificationSettings?.isRemovedFromTotalUnreadCount(default: false) ?? false + items.append(ActionItem(text: value ? L10n.peerInfoActionUnmute : L10n.peerInfoActionMute, image: value ? theme.icons.profile_unmute : theme.icons.profile_mute, action: arguments.toggleNotifications)) + if !peer.isBot { + if !(item.peerView.peers[item.peerView.peerId] is TelegramSecretChat), arguments.context.peerId != peer.id, !isServicePeer(peer) && !peer.rawDisplayTitle.isEmpty { + items.append(ActionItem(text: L10n.peerInfoActionSecretChat, image: theme.icons.profile_secret_chat, action: arguments.startSecretChat)) + } + if peer.id != item.context.peerId, item.peerView.peerIsContact { + items.append(ActionItem(text: L10n.peerInfoActionShare, image: theme.icons.profile_share, action: arguments.shareContact)) + } + if peer.id != item.context.peerId, let cachedData = item.peerView.cachedData as? CachedUserData, item.peerView.peerIsContact { + items.append(ActionItem(text: (!cachedData.isBlocked ? L10n.peerInfoBlockUser : L10n.peerInfoUnblockUser), image: !cachedData.isBlocked ? theme.icons.profile_block : theme.icons.profile_unblock, destruct: true, action: { + arguments.updateBlocked(peer: peer, !cachedData.isBlocked, false) + })) + } + } else if let botInfo = peer.botInfo { + + if let address = peer.addressName, !address.isEmpty { + items.append(ActionItem(text: L10n.peerInfoBotShare, image: theme.icons.profile_share, action: { + arguments.botShare(address) + })) + } + + if botInfo.flags.contains(.worksWithGroups) { + items.append(ActionItem(text: L10n.peerInfoBotAddToGroup, image: theme.icons.profile_more, action: arguments.botAddToGroup)) + } + + if let cachedData = item.peerView.cachedData as? CachedUserData, let botInfo = cachedData.botInfo { + for command in botInfo.commands { + if command.text == "settings" { + items.append(ActionItem(text: L10n.peerInfoBotSettings, image: theme.icons.profile_more, action: arguments.botSettings)) + } + if command.text == "help" { + items.append(ActionItem(text: L10n.peerInfoBotHelp, image: theme.icons.profile_more, action: arguments.botHelp)) + } + if command.text == "privacy" { + items.append(ActionItem(text: L10n.peerInfoBotPrivacy, image: theme.icons.profile_more, action: arguments.botPrivacy)) + } + } + items.append(ActionItem(text: !cachedData.isBlocked ? L10n.peerInfoStopBot : L10n.peerInfoRestartBot, image: theme.icons.profile_more, destruct: true, action: { + arguments.updateBlocked(peer: peer, !cachedData.isBlocked, true) + })) + } + } + + } else if let peer = item.peer, peer.isSupergroup || peer.isGroup, let arguments = item.arguments as? GroupInfoArguments { + let access = peer.groupAccess + + if access.canAddMembers { + items.append(ActionItem(text: L10n.peerInfoActionAddMembers, image: theme.icons.profile_add_member, action: { + arguments.addMember(access.canCreateInviteLink) + })) + } + if let value = item.peerView.notificationSettings?.isRemovedFromTotalUnreadCount(default: false) { + items.append(ActionItem(text: value ? L10n.peerInfoActionUnmute : L10n.peerInfoActionMute, image: value ? theme.icons.profile_unmute : theme.icons.profile_mute, action: arguments.toggleNotifications)) + } + + if let cachedData = item.peerView.cachedData as? CachedChannelData { + if cachedData.statsDatacenterId > 0 { + items.append(ActionItem(text: L10n.peerInfoActionStatistics, image: theme.icons.profile_stats, action: { + arguments.stats(cachedData.statsDatacenterId) + })) + } + } + + if let group = peer as? TelegramGroup { + if case .Member = group.membership { + items.append(ActionItem(text: L10n.peerInfoActionLeave, image: theme.icons.profile_leave, destruct: true, action: arguments.delete)) + } + } else if let group = peer as? TelegramChannel { + if case .member = group.participationStatus { + items.append(ActionItem(text: L10n.peerInfoActionLeave, image: theme.icons.profile_leave, destruct: true, action: arguments.delete)) + } + } + + + if access.canReport { + items.append(ActionItem(text: L10n.peerInfoActionReport, image: theme.icons.profile_report, destruct: true, action: arguments.report)) + } + } else if let peer = item.peer as? TelegramChannel, peer.isChannel, let arguments = item.arguments as? ChannelInfoArguments { + if let value = item.peerView.notificationSettings?.isRemovedFromTotalUnreadCount(default: false) { + items.append(ActionItem(text: value ? L10n.peerInfoActionUnmute : L10n.peerInfoActionMute, image: value ? theme.icons.profile_unmute : theme.icons.profile_mute, action: arguments.toggleNotifications)) + } + + if let cachedData = item.peerView.cachedData as? CachedChannelData { + if cachedData.statsDatacenterId > 0 { + items.append(ActionItem(text: L10n.peerInfoActionStatistics, image: theme.icons.profile_stats, action: { + arguments.stats(cachedData.statsDatacenterId) + })) + } + } + if let address = peer.addressName, !address.isEmpty { + items.append(ActionItem(text: L10n.peerInfoActionShare, image: theme.icons.profile_share, action: arguments.share)) + } + if peer.groupAccess.canReport { + items.append(ActionItem(text: L10n.peerInfoActionReport, image: theme.icons.profile_report, action: arguments.report)) + } + switch peer.participationStatus { + case .member: + items.append(ActionItem(text: L10n.peerInfoActionLeave, image: theme.icons.profile_leave, destruct: true, action: arguments.delete)) + default: + break + } + } + + + if items.count > rowItemsCount { + var subItems:[SubActionItem] = [] + while items.count > rowItemsCount - 1 { + let item = items.removeLast() + subItems.insert(SubActionItem(text: item.text, destruct: item.destruct, action: item.action), at: 0) + } + if !subItems.isEmpty { + items.append(ActionItem(text: L10n.peerInfoActionMore, image: theme.icons.profile_more, action: { }, subItems: subItems)) + } + } + + return items +} + +class PeerInfoHeadItem: GeneralRowItem { + override var height: CGFloat { + let insets = self.viewType.innerInset + var height: CGFloat = 0 + if !editing { + height = photoDimension + insets.top + insets.bottom + nameLayout.layoutSize.height + 4 + statusLayout.layoutSize.height + insets.bottom + + if !items.isEmpty { + let maxActionSize: NSSize = items.max(by: { $0.size.height < $1.size.height })!.size + height += maxActionSize.height + insets.top + } + } else { + height = photoDimension + insets.top + insets.bottom + } + return height + } + + fileprivate var statusLayout: TextViewLayout + fileprivate var nameLayout: TextViewLayout + + + let context: AccountContext + let peer:Peer? + let isVerified: Bool + let isScam: Bool + let peerView:PeerView + var result:PeerStatusStringResult { + didSet { + nameLayout = TextViewLayout(result.title, maximumNumberOfLines: 1) + statusLayout = TextViewLayout(result.status, maximumNumberOfLines: 1, alwaysStaticItems: true) + } + } + + private(set) fileprivate var items: [ActionItem] = [] + + private let fetchPeerAvatar = DisposableSet() + private let onlineMemberCountDisposable = MetaDisposable() + + fileprivate let editing: Bool + fileprivate let updatingPhotoState:PeerInfoUpdatingPhotoState? + fileprivate let updatePhoto:(NSImage?)->Void + fileprivate let arguments: PeerInfoArguments + + let canEditPhoto: Bool + + + let peerPhotosDisposable = MetaDisposable() + + var photos: [TelegramPeerPhoto] = [] + + init(_ initialSize:NSSize, stableId:AnyHashable, context: AccountContext, arguments: PeerInfoArguments, peerView:PeerView, viewType: GeneralViewType, editing: Bool, updatingPhotoState:PeerInfoUpdatingPhotoState? = nil, updatePhoto:@escaping(NSImage?)->Void = { _ in }) { + let peer = peerViewMainPeer(peerView) + self.peer = peer + self.peerView = peerView + self.context = context + self.editing = editing + self.arguments = arguments + self.isVerified = peer?.isVerified ?? false + self.isScam = peer?.isScam ?? false + self.updatingPhotoState = updatingPhotoState + self.updatePhoto = updatePhoto + + + let canEditPhoto: Bool + if let _ = peer as? TelegramUser { + canEditPhoto = false + } else if let _ = peer as? TelegramSecretChat { + canEditPhoto = false + } else if let peer = peer as? TelegramGroup { + canEditPhoto = peer.groupAccess.canEditGroupInfo + } else if let peer = peer as? TelegramChannel { + canEditPhoto = peer.groupAccess.canEditGroupInfo + } else { + canEditPhoto = false + } + + self.canEditPhoto = canEditPhoto && editing + + if let peer = peer { + if let peerReference = PeerReference(peer) { + if let largeProfileImage = peer.largeProfileImage { + fetchPeerAvatar.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: largeProfileImage.resource)).start()) + } + if let smallProfileImage = peer.smallProfileImage { + fetchPeerAvatar.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource)).start()) + } + } + + } + self.result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.huge), highlightIfActivity: false), expanded: true) + nameLayout = TextViewLayout(result.title, maximumNumberOfLines: 1) + statusLayout = TextViewLayout(result.status, maximumNumberOfLines: 1, alwaysStaticItems: true) + + + super.init(initialSize, stableId: stableId, viewType: viewType) + + + if let cachedData = peerView.cachedData as? CachedChannelData { + let onlineMemberCount:Signal + if (cachedData.participantsSummary.memberCount ?? 0) > 200 { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerView.peerId) |> map(Optional.init) |> deliverOnMainQueue + } else { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerView.peerId) |> map(Optional.init) |> deliverOnMainQueue + } + self.onlineMemberCountDisposable.set(onlineMemberCount.start(next: { [weak self] count in + guard let `self` = self else { + return + } + self.result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.huge)), onlineMemberCount: count) + _ = self.makeSize(self.width, oldWidth: 0) + self.redraw() + })) + } + + _ = self.makeSize(initialSize.width, oldWidth: 0) + + + if let peer = peer { + self.photos = syncPeerPhotos(peerId: peer.id) + let signal = peerPhotos(account: context.account, peerId: peer.id, force: true) |> deliverOnMainQueue + peerPhotosDisposable.set(signal.start(next: { [weak self] photos in + self?.photos = photos + self?.redraw() + })) + } + + } + + deinit { + fetchPeerAvatar.dispose() + onlineMemberCountDisposable.dispose() + } + + override func viewClass() -> AnyClass { + return PeerInfoHeadView.self + } + + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + + self.items = actionItems(item: self, width: width, theme: theme) + let textWidth = blockWidth - viewType.innerInset.right - viewType.innerInset.left - (isScam ? theme.icons.chatScam.backingSize.width + 5 : 0) - (isVerified ? theme.icons.peerInfoVerifyProfile.backingSize.width + 5 : 0) + nameLayout.measure(width: textWidth) + statusLayout.measure(width: textWidth) + + return success + } + + fileprivate var nameSize: NSSize { + let stateHeight = max((isScam ? theme.icons.chatScam.backingSize.height : 0), (isVerified ? theme.icons.peerInfoVerifyProfile.backingSize.height : 0)) + let width = nameLayout.layoutSize.width + (isScam ? theme.icons.chatScam.backingSize.width + 5 : 0) + (isVerified ? theme.icons.peerInfoVerifyProfile.backingSize.width + 5 : 0) + return NSMakeSize(width, max(nameLayout.layoutSize.height, stateHeight)) + } + +} + +private final class PeerInfoPhotoEditableView : Control { + private let backgroundView = View() + private let camera: ImageView = ImageView() + private var progressView:RadialProgressContainerView? + private var updatingPhotoState: PeerInfoUpdatingPhotoState? + private var tempImageView: ImageView? + var setup: ((NSImage?)->Void)? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + addSubview(backgroundView) + addSubview(camera) + + camera.image = theme.icons.profile_edit_photo + camera.sizeToFit() + camera.center() + + camera.isEventLess = true + + backgroundView.isEventLess = true + + set(handler: { [weak self] _ in + if self?.updatingPhotoState == nil { + self?.backgroundView.change(opacity: 0.8, animated: true) + self?.camera.change(opacity: 0.8, animated: true) + } + }, for: .Highlight) + + set(handler: { [weak self] _ in + if self?.updatingPhotoState == nil { + self?.backgroundView.change(opacity: 1.0, animated: true) + self?.camera.change(opacity: 1.0, animated: true) + } + }, for: .Normal) + + set(handler: { [weak self] _ in + if self?.updatingPhotoState == nil { + self?.backgroundView.change(opacity: 1.0, animated: true) + self?.camera.change(opacity: 1.0, animated: true) + } + }, for: .Hover) + + backgroundView.backgroundColor = .blackTransparent + backgroundView.frame = bounds + + + set(handler: { [weak self] _ in + if self?.updatingPhotoState == nil { + self?.setup?(nil) + } + }, for: .Click) + } + + func updateState(_ updatingPhotoState: PeerInfoUpdatingPhotoState?, animated: Bool) { + self.updatingPhotoState = updatingPhotoState + + userInteractionEnabled = updatingPhotoState == nil + + self.camera.change(opacity: updatingPhotoState == nil ? 1.0 : 0.0, animated: true) + + if let uploadState = updatingPhotoState { + if self.progressView == nil { + self.progressView = RadialProgressContainerView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, icon: nil)) + self.progressView!.frame = bounds + progressView?.proggressBackground.backgroundColor = .clear + self.addSubview(progressView!) + } + progressView?.progress.fetchControls = FetchControls(fetch: { + updatingPhotoState?.cancel() + }) + progressView?.progress.state = .Fetching(progress: uploadState.progress, force: false) + + if let _ = uploadState.image, self.tempImageView == nil { + self.tempImageView = ImageView() + self.tempImageView?.contentGravity = .resizeAspect + self.tempImageView!.frame = bounds + self.addSubview(tempImageView!, positioned: .below, relativeTo: backgroundView) + if animated { + self.tempImageView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + self.tempImageView?.image = uploadState.image + } else { + if let progressView = self.progressView { + self.progressView = nil + if animated { + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressView] _ in + progressView?.removeFromSuperview() + }) + } else { + progressView.removeFromSuperview() + } + } + if let tempImageView = self.tempImageView { + self.tempImageView = nil + if animated { + tempImageView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempImageView] _ in + tempImageView?.removeFromSuperview() + }) + } else { + tempImageView.removeFromSuperview() + } + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class NameContainer : View { + let nameView = TextView() + var stateImage: ImageView? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(nameView) + } + + func update(_ item: PeerInfoHeadItem) { + self.nameView.update(item.nameLayout) + + if item.isScam || item.isVerified { + if stateImage == nil { + stateImage = ImageView() + addSubview(stateImage!) + } + + stateImage?.image = item.isScam ? theme.icons.chatScam : theme.icons.peerInfoVerifyProfile + _ = stateImage?.sizeToFit() + } else { + stateImage?.removeFromSuperview() + stateImage = nil + } + + needsLayout = true + } + + override func layout() { + super.layout() + + nameView.centerY(x: 0) + stateImage?.centerY(x: nameView.frame.maxX + 5) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +private final class PeerInfoHeadView : GeneralContainableRowView { + private let photoView: AvatarControl = AvatarControl(font: .avatar(30)) + private var photoVideoView: MediaPlayerView? + private var photoVideoPlayer: MediaPlayer? + + + + private let nameView = NameContainer(frame: .zero) + private let statusView = TextView() + private let actionsView = View() + private var photoEditableView: PeerInfoPhotoEditableView? + + + private var activeDragging: Bool = false { + didSet { + self.item?.redraw(animated: true) + } + } + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + photoView.setFrameSize(NSMakeSize(photoDimension, photoDimension)) + + addSubview(photoView) + addSubview(nameView) + addSubview(statusView) + addSubview(actionsView) + + photoView.set(handler: { [weak self] _ in + if let item = self?.item as? PeerInfoHeadItem, let peer = item.peer, let _ = peer.largeProfileImage { + showPhotosGallery(context: item.context, peerId: peer.id, firstStableId: item.stableId, item.table, nil) + } + }, for: .Click) + + registerForDraggedTypes([.tiff, .string, .kUrl, .kFileUrl]) + } + + + override public func performDragOperation(_ sender: NSDraggingInfo) -> Bool { + if activeDragging { + activeDragging = false + if let item = item as? PeerInfoHeadItem { + if let tiff = sender.draggingPasteboard.data(forType: .tiff), let image = NSImage(data: tiff) { + item.updatePhoto(image) + return true + } else { + let list = sender.draggingPasteboard.propertyList(forType: .kFilenames) as? [String] + if let list = list { + if let first = list.first, let image = NSImage(contentsOfFile: first) { + item.updatePhoto(image) + return true + } + } + } + } + } + return false + } + + override public func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + if let item = item as? PeerInfoHeadItem, !item.editing, let peer = item.peer, peer.groupAccess.canEditGroupInfo { + if let tiff = sender.draggingPasteboard.data(forType: .tiff), let _ = NSImage(data: tiff) { + activeDragging = true + } else { + let list = sender.draggingPasteboard.propertyList(forType: .kFilenames) as? [String] + if let list = list { + let list = list.filter { path -> Bool in + if let size = fs(path) { + return size <= 2000 * 1024 * 1024 + } + return false + } + activeDragging = list.count == 1 && NSImage(contentsOfFile: list[0]) != nil + } else { + activeDragging = false + } + } + + } else { + activeDragging = false + } + return .generic + } + override public func draggingExited(_ sender: NSDraggingInfo?) { + activeDragging = false + } + public override func draggingEnded(_ sender: NSDraggingInfo) { + activeDragging = false + } + + @objc func updatePlayerIfNeeded() { + let accept = window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) && !isDynamicContentLocked + if let photoVideoPlayer = photoVideoPlayer { + if accept { + photoVideoPlayer.play() + } else { + photoVideoPlayer.pause() + } + } + } + + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + photoVideoPlayer?.seek(timestamp: 0) + } + + override func viewDidUpdatedDynamicContent() { + super.viewDidUpdatedDynamicContent() + updatePlayerIfNeeded() + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + updateListeners() + updatePlayerIfNeeded() + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateListeners() + updatePlayerIfNeeded() + } + + func updateListeners() { + if let window = window { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: item?.table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: self) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: item?.table?.view) + } else { + removeNotificationListeners() + } + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + deinit { + removeNotificationListeners() + } + + + + override func layout() { + super.layout() + + guard let item = item as? PeerInfoHeadItem else { + return + } + + photoView.centerX(y: item.viewType.innerInset.top) + nameView.centerX(y: photoView.frame.maxY + item.viewType.innerInset.top) + statusView.centerX(y: nameView.frame.maxY + 4) + actionsView.centerX(y: statusView.frame.maxY + item.viewType.innerInset.top) + photoEditableView?.centerX(y: item.viewType.innerInset.top) + + photoVideoView?.frame = photoView.frame + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func layoutActionItems(_ items: [ActionItem], animated: Bool) { + + if !items.isEmpty { + let maxActionSize: NSSize = items.max(by: { $0.size.height < $1.size.height })!.size + + + while actionsView.subviews.count > items.count { + actionsView.subviews.removeLast() + } + while actionsView.subviews.count < items.count { + actionsView.addSubview(ActionButton(frame: .zero)) + } + + let inset: CGFloat = actionItemInsetWidth + + actionsView.change(size: NSMakeSize(actionItemWidth * CGFloat(items.count) + CGFloat(items.count + 1) * inset, maxActionSize.height), animated: animated) + + var x: CGFloat = inset + + for (i, item) in items.enumerated() { + let view = actionsView.subviews[i] as! ActionButton + view.updateAndLayout(item: item, theme: theme) + view.setFrameSize(NSMakeSize(item.size.width, maxActionSize.height)) + view.change(pos: NSMakePoint(x, 0), animated: false) + x += maxActionSize.width + inset + } + + } else { + actionsView.removeAllSubviews() + } + + } + + private var videoRepresentation: TelegramMediaImage.VideoRepresentation? + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? PeerInfoHeadItem else { + return + } + + + photoView.setPeer(account: item.context.account, peer: item.peer) + + if !item.photos.isEmpty { + + if let first = item.photos.first, let video = first.image.videoRepresentations.last, item.updatingPhotoState == nil { + + let equal = videoRepresentation?.resource.id.isEqual(to: video.resource.id) ?? false + + if !equal { + + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + + self.photoVideoView = MediaPlayerView(backgroundThread: true) + self.photoVideoView!.layer?.cornerRadius = self.photoView.frame.height / 2 + if let photoEditableView = self.photoEditableView { + self.addSubview(self.photoVideoView!, positioned: .below, relativeTo: photoEditableView) + } else { + self.addSubview(self.photoVideoView!) + + } + self.photoVideoView!.isEventLess = true + + self.photoVideoView!.frame = self.photoView.frame + + + let file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: first.image.representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: video.resource.size, attributes: []) + + + let mediaPlayer = MediaPlayer(postbox: item.context.account.postbox, reference: MediaResourceReference.standalone(resource: file.resource), streamable: true, video: true, preferSoftwareDecoding: false, enableSound: false, fetchAutomatically: true) + + mediaPlayer.actionAtEnd = .loop(nil) + + self.photoVideoPlayer = mediaPlayer + + if let seekTo = video.startTimestamp { + mediaPlayer.seek(timestamp: seekTo) + } + mediaPlayer.attachPlayerView(self.photoVideoView!) + self.videoRepresentation = video + updatePlayerIfNeeded() + } + + + + } else { + self.photoVideoPlayer = nil + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + } + } else { + self.photoVideoPlayer = nil + self.photoVideoView?.removeFromSuperview() + self.photoVideoView = nil + } + nameView.setFrameSize(item.nameSize) + nameView.update(item) + + statusView.update(item.statusLayout) + + layoutActionItems(item.items, animated: animated) + + + photoView.userInteractionEnabled = !item.editing + + let containerRect: NSRect + switch item.viewType { + case .legacy: + containerRect = self.bounds + case .modern: + containerRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, item.height - item.inset.bottom - item.inset.top) + } + + + if item.canEditPhoto || self.activeDragging || item.updatingPhotoState != nil { + if photoEditableView == nil { + photoEditableView = .init(frame: NSMakeRect(0, 0, photoDimension, photoDimension)) + photoEditableView?.layer?.cornerRadius = photoDimension / 2 + addSubview(photoEditableView!) + if animated { + photoEditableView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + photoEditableView?.updateState(item.updatingPhotoState, animated: animated) + photoEditableView?.setup = item.updatePhoto + } else { + if let photoEditableView = self.photoEditableView { + self.photoEditableView = nil + if animated { + photoEditableView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak photoEditableView] _ in + photoEditableView?.removeFromSuperview() + }) + } else { + photoEditableView.removeFromSuperview() + } + } + } + + containerView.change(size: containerRect.size, animated: animated) + containerView.change(pos: containerRect.origin, animated: animated) + containerView.setCorners(item.viewType.corners, animated: animated) + borderView._change(opacity: item.viewType.hasBorder ? 1.0 : 0.0, animated: animated) + + needsLayout = true + updateListeners() + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + return photoView + } + + override func copy() -> Any { + return photoView.copy() + } + +} diff --git a/Telegram-Mac/PeerInfoHeaderItem.swift b/Telegram-Mac/PeerInfoHeaderItem.swift index d5f593dea0..e938953b1a 100644 --- a/Telegram-Mac/PeerInfoHeaderItem.swift +++ b/Telegram-Mac/PeerInfoHeaderItem.swift @@ -8,59 +8,87 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class PeerInfoHeaderItem: GeneralRowItem { - - let firstTextEdited:String? - let lastTextEdited:String? + fileprivate var firstTextEdited:String? + fileprivate var lastTextEdited:String? override var height: CGFloat { - return 130.0 + switch self.viewType { + case .legacy: + return max(130.0, titleHeight + secondHeight + 60 + 4) + case let .modern(_, insets): + return max(photoDimension + insets.top + insets.bottom, titleHeight + secondHeight + 2 + insets.top + insets.bottom) + } + } + + override var instantlyResize: Bool { + return true } - let photoDimension:CGFloat = 70.0 - let textMargin:CGFloat = 15.0 - var textInset:CGFloat { - return self.inset.left + photoDimension + textMargin + fileprivate let photoDimension:CGFloat = 70.0 + fileprivate var textInset:CGFloat { + switch viewType { + case .legacy: + return self.inset.left + photoDimension + 15.0 + case let .modern(_, insets): + return insets.left + photoDimension + insets.left + } } - var photo:Signal? - var status:(TextNodeLayout, TextNode)? - var name:(TextNodeLayout, TextNode)? + fileprivate var photo:Signal<(CGImage?, Bool), NoError>? + fileprivate let statusLayout: TextViewLayout + fileprivate let nameLayout: TextViewLayout + + fileprivate var titleHeight: CGFloat = 15 + fileprivate var secondHeight: CGFloat = 0 - let account:Account + let context: AccountContext let peer:Peer? let isVerified: Bool + let isScam: Bool let peerView:PeerView let result:PeerStatusStringResult let editable:Bool let updatingPhotoState:PeerInfoUpdatingPhotoState? let textChangeHandler:(String, String?)->Void let canCall:Bool - init(_ initialSize:NSSize, stableId:AnyHashable, account:Account, peerView:PeerView, editable:Bool = false, updatingPhotoState:PeerInfoUpdatingPhotoState? = nil, firstNameEditableText:String? = nil, lastNameEditableText:String? = nil, textChangeHandler:@escaping (String, String?)->Void = {_,_ in}) { + init(_ initialSize:NSSize, stableId:AnyHashable, context: AccountContext, peerView:PeerView, viewType: GeneralViewType = .legacy, editable:Bool = false, updatingPhotoState:PeerInfoUpdatingPhotoState? = nil, firstNameEditableText:String? = nil, lastNameEditableText:String? = nil, textChangeHandler:@escaping (String, String?)->Void = {_,_ in}) { let peer = peerViewMainPeer(peerView) self.peer = peer self.peerView = peerView self.editable = editable - self.account = account + self.context = context self.updatingPhotoState = updatingPhotoState self.textChangeHandler = textChangeHandler self.firstTextEdited = firstNameEditableText self.lastTextEdited = lastNameEditableText - canCall = peer != nil && (peer!.canCall && peer!.id != account.peerId && !editable) - - isVerified = peer?.isVerified ?? false + self.canCall = peer != nil && (peer!.canCall && peer!.id != context.peerId && !editable) + self.isVerified = peer?.isVerified ?? false + self.isScam = peer?.isScam ?? false if let peer = peer { - photo = peerAvatarImage(account: account, peer: peer, displayDimensions:NSMakeSize(photoDimension, photoDimension)) + photo = peerAvatarImage(account: context.account, photo: .peer(peer, peer.smallProfileImage, peer.displayLetters, nil), displayDimensions:NSMakeSize(photoDimension, photoDimension)) } - self.result = stringStatus(for: peerView, theme: PeerStatusStringTheme(titleFont: .medium(.huge), highlightIfActivity: false)) + self.result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.huge), highlightIfActivity: false), expanded: true) + + + nameLayout = TextViewLayout(result.title, maximumNumberOfLines: 1) + statusLayout = TextViewLayout(result.status, maximumNumberOfLines: 1, alwaysStaticItems: true) + super.init(initialSize, stableId: stableId, viewType: viewType) - super.init(initialSize, stableId:stableId) + _ = self.makeSize(initialSize.width, oldWidth: 0) + + } + + + fileprivate func calculateHeight() { + _ = self.makeSize(width, oldWidth: 0) } override func viewClass() -> AnyClass { @@ -68,10 +96,417 @@ class PeerInfoHeaderItem: GeneralRowItem { } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - name = TextNode.layoutText(maybeNode: nil, result.title, nil, 1, .end, NSMakeSize(size.width - textInset - inset.right - (canCall ? 40 : 0), size.height), nil, false, .left) - status = TextNode.layoutText(maybeNode: nil, result.status, nil, 1, .end, NSMakeSize(size.width - textInset - inset.right - (canCall ? 40 : 0), size.height), nil, false, .left) + let success = super.makeSize(width, oldWidth: oldWidth) + + if let firstTextEdited = firstTextEdited { + let textStorage = NSTextStorage(attributedString: .initialize(string: firstTextEdited, font: .normal(.huge), coreText: false)) + let textContainer:NSTextContainer + switch viewType { + case .legacy: + textContainer = NSTextContainer(size: NSMakeSize(width - inset.right - textInset, .greatestFiniteMagnitude)) + case let .modern(_, insets): + textContainer = NSTextContainer(size: NSMakeSize(width - textInset - insets.right - inset.left - inset.right, .greatestFiniteMagnitude)) + } + let layoutManager = NSLayoutManager() + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + layoutManager.ensureLayout(for: textContainer) + titleHeight = max(layoutManager.usedRect(for: textContainer).height, 34) + } else { + titleHeight = 0 + } + + if let lastTextEdited = lastTextEdited { + let textStorage = NSTextStorage(attributedString: .initialize(string: lastTextEdited, font: .normal(.huge), coreText: false)) + let textContainer:NSTextContainer + switch viewType { + case .legacy: + textContainer = NSTextContainer(size: NSMakeSize(width - inset.right - textInset, .greatestFiniteMagnitude)) + case let .modern(_, insets): + textContainer = NSTextContainer(size: NSMakeSize(width - textInset - insets.right - inset.left - inset.right, .greatestFiniteMagnitude)) + } + let layoutManager = NSLayoutManager() + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + layoutManager.ensureLayout(for: textContainer) + secondHeight = max(layoutManager.usedRect(for: textContainer).height, 34) + } else { + secondHeight = 0 + } + + switch viewType { + case .legacy: + break + case let .modern(_, inner): + let textWidth = blockWidth - textInset - inner.right - (canCall ? 40 : 0) - (isScam ? theme.icons.scam.backingSize.width + 5 : 0) + nameLayout.measure(width: textWidth) + statusLayout.measure(width: textWidth) + } + return success + } +} + + +class PeerInfoHeaderView: GeneralRowView, TGModernGrowingDelegate { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let image:AvatarControl = AvatarControl(font: .avatar(26.0)) + private let nameTextView = TextView() + private let statusTextView = TextView() + private let imageView = ImageView() + private let firstNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSMakeRect(0, 0, 0, 34), unscrollable: true) + private let lastNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSMakeRect(0, 0, 0, 34), unscrollable: true) + private let editableContainer:View = View() + private let firstNameSeparator:View = View() + private let separatorView:View = View() + private let progressView:RadialProgressContainerView = RadialProgressContainerView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, icon: nil)) + private let callButton:ImageButton = ImageButton() + private let callDisposable = MetaDisposable() + private let fetchPeerAvatar = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay + image.frame = NSMakeRect(0, 0, 70, 70) + containerView.addSubview(image) + + containerView.addSubview(nameTextView) + containerView.addSubview(statusTextView) + image.set(handler: { [weak self] _ in + if let item = self?.item as? PeerInfoHeaderItem, let peer = item.peer, let _ = peer.largeProfileImage { + showPhotosGallery(context: item.context, peerId: peer.id, firstStableId: item.stableId, item.table, nil) + } + }, for: .Click) + + firstNameTextView.max_height = 10000 + lastNameTextView.max_height = 10000 + + firstNameTextView.delegate = self + firstNameTextView.textFont = .normal(.huge) + + lastNameTextView.delegate = self + lastNameTextView.textFont = .normal(.huge) + + + editableContainer.addSubview(firstNameTextView) + editableContainer.addSubview(lastNameTextView) + + containerView.addSubview(imageView) + + editableContainer.addSubview(firstNameSeparator) + + + progressView.progress.fetchControls = FetchControls(fetch: { [weak self] in + if let item = self?.item as? PeerInfoHeaderItem { + item.updatingPhotoState?.cancel() + } + }) + + callButton.set(handler: { [weak self] _ in + if let item = self?.item as? PeerInfoHeaderItem, let peerId = item.peer?.id { + let context = item.context + self?.callDisposable.set((phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) + })) + } + }, for: .SingleClick) + + containerView.addSubview(callButton) + containerView.addSubview(separatorView) + progressView.frame = image.bounds + + containerView.userInteractionEnabled = false + containerView.displayDelegate = self + containerView.addSubview(editableContainer) + + addSubview(containerView) + } + + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + if let item = item as? PeerInfoHeaderItem, let table = item.table { + + switch item.viewType { + case .legacy: + self.containerView.change(size: NSMakeSize(frame.width, item.height), animated: animated) + case .modern: + self.containerView.change(size: NSMakeSize(item.blockWidth, item.height - item.inset.bottom - item.inset.top), animated: animated, corners: item.viewType.corners) + firstNameSeparator.change(pos: NSMakePoint(4, item.titleHeight + 1), animated: animated) + lastNameTextView._change(pos: NSMakePoint(0, item.titleHeight + 2), animated: animated) + self.separatorView.change(pos: NSMakePoint(self.separatorView.frame.minX, self.containerView.frame.height - .borderSize), animated: animated) + } + + table.noteHeightOfRow(item.index, animated) + change(size: NSMakeSize(frame.width, item.height), animated: animated) + } + } + + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + guard let item = item as? PeerInfoHeaderItem else {return 100} + if item.peer is TelegramUser { + return 128 + } + return 255 + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + if let item = item as? PeerInfoHeaderItem { + return NSMakeSize(frame.width - item.textInset - item.inset.right, textView.frame.height) + } + return NSZeroSize + } + + func textViewEnterPressed(_ event:NSEvent) -> Bool { + if FastSettings.checkSendingAbility(for: event) { + return true + } + return false + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func textViewNeedClose(_ textView: Any) { + + } + + func textViewTextDidChange(_ string: String) { + if let item = item as? PeerInfoHeaderItem { + item.textChangeHandler(firstNameTextView.string(), lastNameTextView.isHidden ? nil : lastNameTextView.string()) + if !firstNameTextView.isHidden { + item.firstTextEdited = firstNameTextView.string() + } + if !lastNameTextView.isHidden { + item.lastTextEdited = lastNameTextView.string() + } - return super.makeSize(width, oldWidth: oldWidth) + let titleHeight = item.titleHeight + let secondHeight = item.secondHeight + let prevHeight = item.height + item.calculateHeight() + if (titleHeight != item.titleHeight || secondHeight != item.secondHeight) && prevHeight == item.height { + textViewHeightChanged(0, animated: true) + } + self.needsLayout = true + } + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + return false + } + + + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + return image + } + + override func copy() -> Any { + return image.copy() } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + if let item = item as? PeerInfoHeaderItem { + self.containerView.background = backdorColor + editableContainer.backgroundColor = backdorColor + firstNameTextView.textColor = theme.colors.text + lastNameTextView.textColor = theme.colors.text + firstNameTextView.setBackgroundColor(backdorColor) + lastNameTextView.setBackgroundColor(backdorColor) + firstNameSeparator.backgroundColor = theme.colors.border + separatorView.backgroundColor = theme.colors.border + self.background = item.viewType.rowBackground + } + } + override func draw(_ layer: CALayer, in ctx: CGContext) { + + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + override func set(item:TableRowItem, animated:Bool = false) { + super.set(item: item, animated: animated) + + if let item = item as? PeerInfoHeaderItem { + + callButton.set(image: theme.icons.peerInfoCall, for: .Normal) + _ = callButton.sizeToFit() + + separatorView.isHidden = !item.viewType.hasBorder || item.viewType.isPlainMode + + switch item.viewType { + case .legacy: + self.containerView.change(size: NSMakeSize(frame.width, item.height), animated: animated, corners: item.viewType.corners) + case .modern: + self.containerView.change(size: NSMakeSize(item.blockWidth, item.height - item.inset.bottom - item.inset.top), animated: animated, corners: item.viewType.corners) + } + + if animated { + if item.editable { + self.editableContainer.isHidden = false + } + self.editableContainer.layer?.animateAlpha(from: !item.editable ? 1 : 0, to: item.editable ? 1 : 0, duration: 0.2, completion: { [weak self] completed in + if completed { + self?.editableContainer.isHidden = !item.editable + } + }) + + } else { + editableContainer.isHidden = !item.editable + self.editableContainer.layer?.removeAllAnimations() + } + self.editableContainer.layer?.opacity = item.editable ? 1 : 0 + + firstNameSeparator.isHidden = item.secondHeight == 0 + + + + + if item.isVerified { + imageView.image = theme.icons.peerInfoVerifyProfile + } else if item.isScam { + imageView.image = theme.icons.chatScam + } else { + imageView.image = nil + } + imageView.sizeToFit() + + imageView.isHidden = imageView.image == nil || item.editable + + let containerRect: NSRect + switch item.viewType { + case .legacy: + containerRect = self.bounds + case .modern: + containerRect = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, item.height - item.inset.bottom - item.inset.top) + } + containerView.change(size: containerRect.size, animated: animated) + containerView.change(pos: containerRect.origin, animated: animated) + containerView.setCorners(item.viewType.corners, animated: animated) + separatorView._change(opacity: item.viewType.hasBorder ? 1.0 : 0.0, animated: animated) + + nameTextView.update(item.nameLayout) + nameTextView.isHidden = item.editable + + statusTextView.update(item.statusLayout) + statusTextView.isHidden = item.editable + + self.needsLayout = true + + if let peer = item.peer { + image.setPeer(account: item.context.account, peer: peer) + + if let largeProfileImage = peer.largeProfileImage { + if let peerReference = PeerReference(peer) { + fetchPeerAvatar.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: largeProfileImage.resource)).start()) + } + } + + if let peer = peer as? TelegramUser { + firstNameTextView.setString(item.firstTextEdited ?? peer.firstName ?? "", animated: false) + lastNameTextView.setString(item.lastTextEdited ?? peer.lastName ?? "", animated: false) + firstNameTextView.setPlaceholderAttributedString(.initialize(string: tr(L10n.peerInfoFirstNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) + lastNameTextView.setPlaceholderAttributedString(.initialize(string: tr(L10n.peerInfoLastNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) + lastNameTextView.isHidden = false + } else { + let titleText = item.firstTextEdited ?? peer.displayTitle + if titleText != firstNameTextView.string() { + firstNameTextView.setString(titleText, animated: false) + } + if peer.isChannel { + firstNameTextView.setPlaceholderAttributedString(.initialize(string: L10n.peerInfoChannelNamePlaceholder, color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) + } else { + firstNameTextView.setPlaceholderAttributedString(.initialize(string: L10n.peerInfoGroupNamePlaceholder, color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) + } + + lastNameTextView.isHidden = true + } + + if let uploadState = item.updatingPhotoState { + if progressView.superview == nil { + image.addSubview(progressView) + progressView.layer?.opacity = 0 + } + progressView.change(opacity: 1, animated: animated) + progressView.progress.state = .Fetching(progress: uploadState.progress, force: false) + } else { + if animated { + progressView.change(opacity: 0, animated: animated, removeOnCompletion: false, completion: { [weak self] complete in + if complete { + self?.progressView.removeFromSuperview() + self?.progressView.layer?.removeAllAnimations() + } + }) + } else { + progressView.removeFromSuperview() + } + } + + callButton.isHidden = !item.canCall + + + } + + needsLayout = true + containerView.needsDisplay = true + } + } + + override func layout() { + super.layout() + if let item = item as? PeerInfoHeaderItem { + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + break + case let .modern(_, innerInset): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + + + image.frame = NSMakeRect(innerInset.left, innerInset.top, image.frame.width, image.frame.height) + + editableContainer.setFrameSize(NSMakeSize(containerView.frame.width - item.textInset - innerInset.right, item.titleHeight + item.secondHeight + 4)) + editableContainer.centerY(x: item.textInset) + + firstNameTextView.setFrameSize(NSMakeSize(editableContainer.frame.width, item.titleHeight)) + lastNameTextView.setFrameSize(NSMakeSize(editableContainer.frame.width, item.secondHeight)) + + + firstNameTextView.setFrameOrigin(0, 0) + firstNameSeparator.frame = NSMakeRect(4, firstNameTextView.frame.maxY + 1, editableContainer.frame.width, .borderSize) + lastNameTextView.setFrameOrigin(0, firstNameTextView.frame.maxY + 2) + + separatorView.frame = NSMakeRect(innerInset.left, containerView.frame.height - .borderSize, containerView.frame.width - innerInset.left - innerInset.right, .borderSize) + + callButton.centerY(x: containerView.frame.width - callButton.frame.width - innerInset.right) + + var nameY:CGFloat = focus(item.nameLayout.layoutSize).minY + let t = item.nameLayout.layoutSize.height + item.statusLayout.layoutSize.height + 4.0 + nameY = (containerView.frame.height - t) / 2.0 + + nameTextView.setFrameOrigin(NSMakePoint(item.textInset, nameY)) + statusTextView.setFrameOrigin(NSMakePoint(item.textInset, nameTextView.frame.maxY + 2)) + imageView.setFrameOrigin(NSMakePoint(item.textInset + item.nameLayout.layoutSize.width + 3, nameY + 3)) + + } + } + } + + deinit { + callDisposable.dispose() + fetchPeerAvatar.dispose() + } } diff --git a/Telegram-Mac/PeerInfoHeaderView.swift b/Telegram-Mac/PeerInfoHeaderView.swift deleted file mode 100644 index 6cc19da9e4..0000000000 --- a/Telegram-Mac/PeerInfoHeaderView.swift +++ /dev/null @@ -1,268 +0,0 @@ -// -// PeerInfoHeaderView.swift -// Telegram-Mac -// -// Created by keepcoder on 12/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac - - - -class PeerInfoHeaderView: TableRowView, TGModernGrowingDelegate { - - private let image:AvatarControl = AvatarControl(font: .avatar(.custom(26))) - - private let firstNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) - private let lastNameTextView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) - private let editableContainer:View = View() - private let firstNameSeparator:View = View() - private let lastNameSeparator:View = View() - private let progressView:RadialProgressContainerView = RadialProgressContainerView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, icon: nil)) - private let callButton:ImageButton = ImageButton() - private let callDisposable = MetaDisposable() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - image.frame = NSMakeRect(0, 0, 70, 70) - addSubview(image) - - image.set(handler: { [weak self] _ in - if let item = self?.item as? PeerInfoHeaderItem, let peer = item.peer, let _ = peer.largeProfileImage { - showPhotosGallery(account: item.account, peerId: peer.id, firstStableId: item.stableId, item.table, nil) - } - }, for: .Click) - - - firstNameTextView.delegate = self - firstNameTextView.textFont = .normal(.huge) - - firstNameTextView.min_height = 22 - firstNameTextView.isSingleLine = true - firstNameTextView.max_height = 22 - - lastNameTextView.delegate = self - lastNameTextView.textFont = .normal(.huge) - - lastNameTextView.min_height = 22 - lastNameTextView.max_height = 22 - lastNameTextView.isSingleLine = true - - editableContainer.addSubview(firstNameTextView) - editableContainer.addSubview(lastNameTextView) - - - editableContainer.addSubview(firstNameSeparator) - editableContainer.addSubview(lastNameSeparator) - - addSubview(editableContainer) - - progressView.progress.fetchControls = FetchControls(fetch: { [weak self] in - if let item = self?.item as? PeerInfoHeaderItem { - item.updatingPhotoState?.cancel() - } - }) - - - - callButton.set(handler: { [weak self] _ in - if let item = self?.item as? PeerInfoHeaderItem, let peerId = item.peer?.id { - let account = item.account - self?.callDisposable.set((phoneCall(account, peerId: peerId) |> deliverOnMainQueue).start(next: { result in - applyUIPCallResult(account, result) - })) - } - }, for: .SingleClick) - - addSubview(callButton) - - progressView.frame = image.bounds - // image.addSubview(progressView) - } - - func textViewHeightChanged(_ height: CGFloat, animated: Bool) { - - } - - func maxCharactersLimit() -> Int32 { - return 30 - } - - func textViewSize() -> NSSize { - if let item = item as? PeerInfoHeaderItem { - return NSMakeSize(frame.width - item.textInset - item.inset.right, 22) - } - return NSZeroSize - } - - func textViewEnterPressed(_ event:NSEvent) -> Bool { - if FastSettings.checkSendingAbility(for: event) { - return true - } - return false - } - - func textViewIsTypingEnabled() -> Bool { - return true - } - - func textViewNeedClose(_ textView: Any) { - - } - - func textViewTextDidChange(_ string: String) { - if let item = item as? PeerInfoHeaderItem { - item.textChangeHandler(firstNameTextView.string(), lastNameTextView.isHidden ? nil : lastNameTextView.string()) - } - } - - func textViewTextDidChangeSelectedRange(_ range: NSRange) { - - } - - func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { - return false - } - - - - override var interactionContentView: NSView { - return image - } - - override func copy() -> Any { - return image.copy() - } - - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var backdorColor: NSColor { - return theme.colors.background - } - - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - if let item = item as? PeerInfoHeaderItem, let name = item.name, !item.editable { - - var nameY:CGFloat = focus(name.0.size).minY - - if let status = item.status { - - let t = name.0.size.height + status.0.size.height + 4.0 - nameY = (frame.height - t) / 2.0 - - let sY = nameY + name.0.size.height + 4.0 - status.1.draw(NSMakeRect(item.textInset, sY, status.0.size.width, status.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - - } - - if item.isVerified { - ctx.draw(theme.icons.peerInfoVerify, in: NSMakeRect(item.textInset + name.0.size.width + 3, nameY + 4, theme.icons.peerInfoVerify.backingSize.width, theme.icons.peerInfoVerify.backingSize.height)) - } - - name.1.draw(NSMakeRect(item.textInset, nameY, name.0.size.width, name.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } - - } - - override func set(item:TableRowItem, animated:Bool = false) { - super.set(item: item, animated: animated) - - if let item = item as? PeerInfoHeaderItem { - image.frame = NSMakeRect(item.inset.left, (frame.height - image.frame.height)/2.0, image.frame.width, image.frame.height) - - callButton.set(image: theme.icons.peerInfoCall, for: .Normal) - callButton.sizeToFit() - - editableContainer.isHidden = !item.editable - editableContainer.backgroundColor = theme.colors.background - - firstNameTextView.textColor = theme.colors.text - lastNameTextView.textColor = theme.colors.text - firstNameTextView.background = theme.colors.background - lastNameTextView.background = theme.colors.background - - firstNameSeparator.backgroundColor = theme.colors.border - lastNameSeparator.backgroundColor = theme.colors.border - if let peer = item.peer { - image.setPeer(account: item.account, peer: peer) - if let peer = peer as? TelegramUser { - firstNameTextView.setString(item.firstTextEdited ?? peer.firstName ?? "", animated: false) - lastNameTextView.setString(item.lastTextEdited ?? peer.lastName ?? "", animated: false) - - firstNameTextView.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(.peerInfoFirstNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) - lastNameTextView.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(.peerInfoLastNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) - - lastNameTextView.isHidden = false - } else { - firstNameTextView.setString(item.firstTextEdited ?? peer.displayTitle, animated: false) - - if peer.isChannel { - firstNameTextView.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(.peerInfoChannelNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) - } else { - firstNameTextView.setPlaceholderAttributedString(NSAttributedString.initialize(string: tr(.peerInfoGroupNamePlaceholder), color: theme.colors.grayText, font: .normal(.header), coreText: false), update: false) - } - lastNameTextView.isHidden = true - } - - if let uploadState = item.updatingPhotoState { - if progressView.superview == nil { - image.addSubview(progressView) - progressView.layer?.opacity = 0 - } - progressView.change(opacity: 1, animated: animated) - progressView.progress.state = .Fetching(progress: uploadState.progress, force: false) - } else { - if animated { - progressView.change(opacity: 0, animated: animated, removeOnCompletion: false, completion: { [weak self] complete in - if complete { - self?.progressView.removeFromSuperview() - self?.progressView.layer?.removeAllAnimations() - } - }) - } else { - progressView.removeFromSuperview() - } - } - - callButton.isHidden = !item.canCall - - lastNameSeparator.isHidden = lastNameTextView.isHidden - needsLayout = true - } - } - } - - override func layout() { - super.layout() - if let item = item as? PeerInfoHeaderItem { - - editableContainer.setFrameSize(NSMakeSize(frame.width - item.textInset - item.inset.right, lastNameTextView.isHidden ? 25 : 56)) - - firstNameTextView.setFrameSize(editableContainer.frame.width, 22) - lastNameTextView.setFrameSize(editableContainer.frame.width, 22) - - firstNameSeparator.frame = NSMakeRect(4, 24, editableContainer.frame.width, .borderSize) - firstNameTextView.setFrameOrigin(0, 0) - lastNameTextView.setFrameOrigin(0, 30) - - lastNameSeparator.frame = NSMakeRect(4, 55, editableContainer.frame.width, .borderSize) - - callButton.centerY(x: frame.width - callButton.frame.width - 30) - editableContainer.centerY(x: item.textInset) - } - } - - deinit { - callDisposable.dispose() - } - - -} diff --git a/Telegram-Mac/PeerInfoUtils.swift b/Telegram-Mac/PeerInfoUtils.swift index 28c95d64f6..cbb61a0427 100644 --- a/Telegram-Mac/PeerInfoUtils.swift +++ b/Telegram-Mac/PeerInfoUtils.swift @@ -7,77 +7,89 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox struct GroupAccess { let highlightAdmins:Bool - let canManageMembers:Bool - let canManageGroup:Bool + let canEditGroupInfo:Bool + let canEditMembers:Bool + let canAddMembers: Bool + let isPublic:Bool let isCreator:Bool + let canCreateInviteLink: Bool + let canReport: Bool } extension Peer { var groupAccess:GroupAccess { var highlightAdmins = false - var canManageGroup = false - var canManageMembers = false + var canEditGroupInfo = false + var canEditMembers = false + var canAddMembers = false + var isPublic = false var isCreator = false + var canReport = true if let group = self as? TelegramGroup { - if group.flags.contains(.adminsEnabled) { - highlightAdmins = true - switch group.role { - case .creator: - canManageGroup = true - canManageMembers = true - isCreator = true - case .admin: - canManageGroup = true - canManageMembers = true - case .member: - break - } - } else { - canManageGroup = group.membership == .Member - canManageMembers = group.membership == .Member - switch group.role { - case .creator: - isCreator = true - default: - break - } + if case .creator = group.role { + isCreator = true + canReport = false + } + highlightAdmins = true + switch group.role { + case .admin, .creator: + canEditGroupInfo = true + canEditMembers = true + canAddMembers = true + canReport = false + case .member: + break + } + if !group.hasBannedPermission(.banChangeInfo) { + canEditGroupInfo = true + } + if !group.hasBannedPermission(.banAddMembers) { + canAddMembers = true } } else if let channel = self as? TelegramChannel { highlightAdmins = true + isPublic = channel.username != nil isCreator = channel.flags.contains(.isCreator) - canManageGroup = channel.adminRights != nil || channel.flags.contains(.isCreator) - canManageMembers = channel.hasAdminRights(.canBanUsers) - + canReport = !channel.flags.contains(.isCreator) && channel.adminRights == nil + if channel.hasPermission(.changeInfo) { + canEditGroupInfo = true + } + if channel.hasPermission(.banMembers) { + canEditMembers = true + } + if channel.hasPermission(.inviteMembers) { + canAddMembers = true + } } - return GroupAccess(highlightAdmins: highlightAdmins, canManageMembers: canManageMembers, canManageGroup: canManageGroup, isCreator: isCreator) + + var canCreateInviteLink = false + if let group = self as? TelegramGroup { + if case .creator = group.role { + canCreateInviteLink = true + } + } else if let channel = self as? TelegramChannel { + if channel.hasPermission(.inviteMembers) && channel.adminRights != nil { + canCreateInviteLink = true + } + } + + + + return GroupAccess(highlightAdmins: highlightAdmins, canEditGroupInfo: canEditGroupInfo, canEditMembers: canEditMembers, canAddMembers: canAddMembers, isPublic: isPublic, isCreator: isCreator, canCreateInviteLink: canCreateInviteLink, canReport: canReport) } var canInviteUsers:Bool { if let peer = self as? TelegramChannel { - switch peer.info { - case .group(let info): - return peer.hasAdminRights(.canInviteUsers) || info.flags.contains(.everyMemberCanInviteMembers) - default: - break - } - return peer.hasAdminRights(.canInviteUsers) + return peer.hasPermission(.inviteMembers) } else if let group = self as? TelegramGroup { - if group.flags.contains(.adminsEnabled) { - switch group.role { - case .creator, .admin: - return true - default: - return false - } - } else { - return true - } + return !group.hasBannedRights(.banAddMembers) } @@ -110,10 +122,12 @@ extension TelegramGroup { extension TelegramChannel { func canRemoveParticipant(_ participant: ChannelParticipant, accountId:PeerId) -> Bool { - let hasRight = hasAdminRights(.canBanUsers) - + let hasRight = hasPermission(.banMembers) + if accountId == participant.peerId { + return false + } switch participant { - case let .member(_, _, adminInfo, _): + case let .member(_, _, adminInfo, _, _): if let adminInfo = adminInfo { return accountId == adminInfo.promotedBy || flags.contains(.isCreator) } else { @@ -166,11 +180,11 @@ func <(lhs:ChannelParticipant, rhs: ChannelParticipant) -> Bool { switch lhs { case .creator: return false - case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo): + case let .member(lhsId, lhsInvitedAt, lhsAdminInfo, lhsBanInfo, lhsRank): switch rhs { case .creator: return true - case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo): + case let .member(rhsId, rhsInvitedAt, rhsAdminInfo, rhsBanInfo, rhsRank): return lhsInvitedAt < rhsInvitedAt } } diff --git a/Telegram-Mac/PeerMediaBlockRowItem.swift b/Telegram-Mac/PeerMediaBlockRowItem.swift new file mode 100644 index 0000000000..8066a0833d --- /dev/null +++ b/Telegram-Mac/PeerMediaBlockRowItem.swift @@ -0,0 +1,258 @@ +// +// PeerMediaBlockRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 19.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SwiftSignalKit +import CoreGraphics + +class PeerMediaBlockRowItem: GeneralRowItem { + + fileprivate var temporaryHeight: CGFloat? + fileprivate let listener: TableScrollListener + fileprivate let controller: PeerMediaController + fileprivate let isMediaVisible: Bool + init(_ initialSize: NSSize, stableId: AnyHashable, controller: PeerMediaController, isVisible: Bool, viewType: GeneralViewType) { + self.controller = controller + self.isMediaVisible = isVisible + self.listener = TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { _ in }) + super.init(initialSize, height: initialSize.height, stableId: stableId, viewType: viewType) + } + + deinit { + if self.controller.isLoaded(), let table = self.table { +// let view = self.controller.genericView +// view.removeFromSuperview() + + if controller.frame.minY == 0 { + table.scroll(to: .up(true)) + if self.controller.genericView.superview != nil { + controller.viewWillDisappear(true) + self.controller.genericView.removeFromSuperview() + controller.viewDidDisappear(true) + } + } + } + + } + + override var instantlyResize: Bool { + return false + } + + override var height: CGFloat { + // return 10000 + + if !isMediaVisible { + return 1 + } else { + if let temporaryHeight = temporaryHeight { + return temporaryHeight + } else { + return table?.frame.height ?? initialSize.height + } + } + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + return true + } + + override func viewClass() -> AnyClass { + return PeerMediaBlockRowView.self + } +} + + +private final class PeerMediaBlockRowView : TableRowView { + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var backdorColor: NSColor { + return theme.colors.listBackground + } + + private func updateOrigin() { + guard let item = item as? PeerMediaBlockRowItem, let table = item.table else { + return + } + item.controller.view.frame = NSMakeRect(0, max(0, self.frame.minY - table.documentOffset.y), self.frame.width, table.frame.height) + } + + override func layout() { + super.layout() + + self.updateOrigin() + } + + override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + self.updateOrigin() + } + + override func removeFromSuperview() { + super.removeFromSuperview() + } + + override func scrollWheel(with event: NSEvent) { + guard let item = item as? PeerMediaBlockRowItem else { + super.scrollWheel(with: event) + return + } + item.controller.view.enclosingScrollView?.scrollWheel(with: event) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? PeerMediaBlockRowItem else { + return + } + item.controller.bar = .init(height: 0) + item.controller._frameRect = bounds + + var scrollInner: Bool = false + + var scrollingInMediaTable: Bool = false + + + if item.isMediaVisible { + item.listener.handler = { [weak self, weak item] _ in + guard let `self` = self, let table = item?.table, let item = item else { + return + } + scrollInner = table.documentOffset.y >= self.frame.minY + let mediaTable = item.controller.genericView.mainTable + if let mediaTable = mediaTable { + + let offset = table.documentOffset.y - self.frame.minY + var updated = max(0, offset) + if mediaTable.documentSize.height <= table.frame.height, updated > 0 { + updated = max(updated - 30, 0) + } + if !scrollingInMediaTable, updated != mediaTable.documentOffset.y { + mediaTable.clipView.scroll(to: NSMakePoint(0, updated)) + mediaTable.reflectScrolledClipView(mediaTable.clipView) + } + if scrollInner { + + } else { + if mediaTable.documentOffset.y > 0 { + scrollInner = true + } + } + NotificationCenter.default.post(name: NSView.boundsDidChangeNotification, object: mediaTable.clipView) + + if item.temporaryHeight != mediaTable.documentSize.height { + item.temporaryHeight = max(mediaTable.documentSize.height, table.frame.height) + table.noteHeightOfRow(item.index, false) + } + + let previousY = item.controller.view.frame.minY + + item.controller.view.frame = NSMakeRect(0, max(0, self.frame.minY - table.documentOffset.y), self.frame.width, table.frame.height) + + let currentY = item.controller.view.frame.minY + if previousY != currentY { + if currentY == 0, previousY != 0 { + item.controller.viewWillAppear(true) + item.controller.viewDidAppear(true) + } else if previousY == 0 { + item.controller.viewWillDisappear(true) + item.controller.viewDidDisappear(true) + } + } + } + } + + item.table?.addScroll(listener: item.listener) + + item.table?.hasVerticalScroller = false + + item.table?._scrollWillStartLiveScrolling = { + scrollingInMediaTable = false + } + } else { + needsLayout = true + } + + if item.controller.view.superview != item.table { + item.controller.view.removeFromSuperview() + item.table?.addSubview(item.controller.view) + } + if let table = item.table { + item.controller.genericView.change(pos: NSMakePoint(0, max(0, table.rectOf(item: item).minY - table.documentOffset.y)), animated: animated) + } + + if item.isMediaVisible { + item.controller.genericView.isHidden = false + } + + item.controller.genericView.change(opacity: item.isMediaVisible ? 1 : 0, animated: animated, completion: { [weak item] _ in + guard let item = item else { + return + } + item.controller.genericView.isHidden = !item.isMediaVisible + }) + + if item.isMediaVisible { + item.controller.currentMainTableView = { [weak item, weak self] mainTable, animated, updated in + if let item = item, animated { + if item.table?.documentOffset.y == self?.frame.minY { + if !updated { + mainTable?.scroll(to: .up(true)) + } + } else if updated { + item.table?.scroll(to: .top(id: item.stableId, innerId: nil, animated: animated, focus: .init(focus: false), inset: 0)) + } + } + + mainTable?.applyExternalScroll = { [weak self, weak item] event in + guard let `self` = self, let item = item else { + return false + } + if scrollInner { + if event.scrollingDeltaY > 0 { + if let tableView = item.controller.genericView.mainTable, tableView.documentOffset.y <= 0 { + if !item.controller.unableToHide { + scrollInner = false + item.table?.clipView.scroll(to: NSMakePoint(0, self.frame.minY)) + item.table?.scrollWheel(with: event) + scrollingInMediaTable = false + return true + } + + } + } + scrollingInMediaTable = true + return false + } else { + scrollingInMediaTable = false + item.table?.scrollWheel(with: event) + return true + } + } + } + } else { + item.controller.currentMainTableView = nil + } + } + + deinit { + } +} diff --git a/Telegram-Mac/PeerMediaCollectionInterfaceState.swift b/Telegram-Mac/PeerMediaCollectionInterfaceState.swift index 0379fffe75..bb09c9a889 100644 --- a/Telegram-Mac/PeerMediaCollectionInterfaceState.swift +++ b/Telegram-Mac/PeerMediaCollectionInterfaceState.swift @@ -7,8 +7,9 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit final class PeerMediaCollectionInteraction : InterfaceObserver { @@ -30,12 +31,15 @@ final class PeerMediaCollectionInteraction : InterfaceObserver { } } -enum PeerMediaCollectionMode { - case photoOrVideo - case file - case music - case webpage - +enum PeerMediaCollectionMode : Int32 { + case members = -2 + case photoOrVideo = -1 + case file = 0 + case webpage = 1 + case music = 2 + case voice = 3 + case commonGroups = 4 + case gifs = 5 var tagsValue:MessageTags { switch self { case .photoOrVideo: @@ -46,22 +50,18 @@ enum PeerMediaCollectionMode { return .music case .webpage: return .webPage + case .voice: + return .voiceOrInstantVideo + case .members: + return [] + case .commonGroups: + return [] + case .gifs: + return .gif } } } -func titleForPeerMediaCollectionMode(_ mode: PeerMediaCollectionMode) -> String { - switch mode { - case .photoOrVideo: - return "Shared Media" - case .file: - return "Shared Files" - case .music: - return "Shared Music" - case .webpage: - return "Shared Links" - } -} struct PeerMediaCollectionInterfaceState: Equatable { let peer: Peer? @@ -122,7 +122,7 @@ struct PeerMediaCollectionInterfaceState: Equatable { selectedIds.formUnion(selectionState.selectedIds) } selectedIds.insert(messageId) - return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), mode: self.mode, selectingMode: self.selectingMode) + return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds, lastSelectedId: nil), mode: self.mode, selectingMode: self.selectingMode) } func withToggledSelectedMessage(_ messageId: MessageId) -> PeerMediaCollectionInterfaceState { @@ -135,11 +135,11 @@ struct PeerMediaCollectionInterfaceState: Equatable { } else { selectedIds.insert(messageId) } - return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), mode: self.mode, selectingMode: self.selectingMode) + return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds, lastSelectedId: nil), mode: self.mode, selectingMode: self.selectingMode) } func withSelectionState() -> PeerMediaCollectionInterfaceState { - return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState ?? ChatInterfaceSelectionState(selectedIds: Set()), mode: self.mode, selectingMode: true) + return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: self.selectionState ?? ChatInterfaceSelectionState(selectedIds: Set(), lastSelectedId: nil), mode: self.mode, selectingMode: true) } func withoutSelectionState() -> PeerMediaCollectionInterfaceState { diff --git a/Telegram-Mac/PeerMediaController.swift b/Telegram-Mac/PeerMediaController.swift index 59b5a933f3..3a31d756e0 100644 --- a/Telegram-Mac/PeerMediaController.swift +++ b/Telegram-Mac/PeerMediaController.swift @@ -1,177 +1,668 @@ // -// PeerMediaController.swift -// Telegram-Mac + // PeerMediaController.swift + // Telegram-Mac + // + // Created by keepcoder on 13/10/2016. + // Copyright © 2016 Telegram. All rights reserved. + // + + import Cocoa + import TGUIKit + import TelegramCore + import SyncCore + import SwiftSignalKit + import Postbox + + // -// Created by keepcoder on 13/10/2016. -// Copyright © 2016 Telegram. All rights reserved. +// private class PeerMediaTitleBarView : TitledBarView { +// private var search:ImageButton = ImageButton() +// fileprivate let context: AccountContext +// fileprivate let peerId: PeerId +// init(controller: ViewController, context: AccountContext, peerId: PeerId, title:NSAttributedString, handler:@escaping() ->Void) { +// super.init(controller: controller, title) +// search.set(handler: { _ in +// handler() +// }, for: .Click) +// addSubview(search) +// self.context = context +// self.peerId = peerId +// updateLocalizationAndTheme(theme: theme) +// } // - -import Cocoa -import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac - - -class PeerMediaControllerView : View { +// func updateSearchVisibility(_ visible: Bool) { +// search.isHidden = !visible +// } +// +// override func updateLocalizationAndTheme(theme: PresentationTheme) { +// super.updateLocalizationAndTheme(theme: theme) +// let theme = (theme as! TelegramPresentationTheme) +// search.set(image: theme.icons.chatSearch, for: .Normal) +// _ = search.sizeToFit() +// backgroundColor = theme.colors.background +// needsLayout = true +// } +// +// override func layout() { +// super.layout() +// search.centerY(x: frame.width - search.frame.width) +// } +// +// +// required init(frame frameRect: NSRect) { +// fatalError("init(frame:) has not been implemented") +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// } + + private final class SearchContainerView : View { + fileprivate let searchView: SearchView = SearchView(frame: NSMakeRect(0, 0, 200, 30)) + fileprivate let close: ImageButton = ImageButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(searchView) + addSubview(close) + updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = theme as! TelegramPresentationTheme + borderColor = theme.colors.border + backgroundColor = .clear + close.set(image: theme.icons.dismissPinned, for: .Normal) + _ = close.sizeToFit() + } + + override func layout() { + super.layout() + searchView.setFrameSize(NSMakeSize(frame.width - close.frame.width - 30, 30)) + searchView.centerY(x: 10) + close.centerY(x: searchView.frame.maxX + 10) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + private final class SegmentContainerView : View { + fileprivate let segmentControl: ScrollableSegmentView + required init(frame frameRect: NSRect) { + self.segmentControl = ScrollableSegmentView(frame: NSMakeRect(0, 0, frameRect.width, 50)) + super.init(frame: frameRect) + addSubview(segmentControl) + updateLocalizationAndTheme(theme: theme) + segmentControl.fitToWidth = true + + } + + override func layout() { + super.layout() + + segmentControl.frame = bounds + segmentControl.center() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + // super.updateLocalizationAndTheme(theme: theme) + segmentControl.theme = ScrollableSegmentTheme(background: .clear, border: .clear, selector: theme.colors.accent, inactiveText: theme.colors.grayText, activeText: theme.colors.text, textFont: .normal(.text)) + backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + private enum PeerMediaAnimationDirection { + case leftToRight + case rightToLeft + } + private let sectionOffset: CGFloat = 30 + + final class PeerMediaContainerView : View { private let actionsPanelView:MessageActionsPanelView = MessageActionsPanelView(frame: NSMakeRect(0,0,0, 50)) - private weak var mainView:NSView? private let separator:View = View() - private var isSelectionState:Bool = false - private var chatInteraction:ChatInteraction? - required init(frame frameRect:NSRect) { + + fileprivate let view: PeerMediaControllerView + required init(frame frameRect: NSRect) { + view = PeerMediaControllerView(frame: NSMakeRect(0, sectionOffset, min(600, frameRect.width - sectionOffset * 2), frameRect.height - sectionOffset)) super.init(frame: frameRect) + addSubview(view) addSubview(actionsPanelView) addSubview(separator) - updateLocalizationAndTheme() + backgroundColor = theme.colors.listBackground + layout() } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.listBackground separator.backgroundColor = theme.colors.border - mainView?.background = theme.colors.background + } + + override func scrollWheel(with event: NSEvent) { + view.scrollWheel(with: event) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + + let blockWidth = min(600, frame.width - sectionOffset * 2) + + + view.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - blockWidth) / 2), sectionOffset, blockWidth, frame.height - sectionOffset) + + let inset:CGFloat = view.isSelectionState ? 50 : 0 + actionsPanelView.frame = NSMakeRect(0, frame.height - inset, frame.width, 50) + separator.frame = NSMakeRect(0, frame.height - inset, frame.width, .borderSize) + + } + + var mainView:NSView? { + return self.view.mainView + } + + var mainTable: TableView? { + if let tableView = self.view.mainView as? TableView { + return tableView + } else if let view = self.view.mainView as? InputDataView { + return view.tableView + } else if let view = self.view.mainView as? PeerMediaGifsView { + return view.tableView + } + return nil } func updateInteraction(_ chatInteraction:ChatInteraction) { - self.chatInteraction = chatInteraction + self.view.updateInteraction(chatInteraction) actionsPanelView.prepare(with: chatInteraction) } - func updateMainView(with view:NSView, animated:Bool) { - mainView?.removeFromSuperview() - mainView?.background = theme.colors.background - self.mainView = view - addSubview(view) + fileprivate func updateMainView(with view:NSView, animated:PeerMediaAnimationDirection?) { + self.view.updateMainView(with: view, animated: animated) + } + + func updateSearchState(_ state: MediaSearchState, updateSearchState:@escaping(SearchState)->Void, toggle:@escaping()->Void) { + self.view.updateSearchState(state, updateSearchState: updateSearchState, toggle: toggle) + } + + func changeState(selectState:Bool, animated:Bool) { + self.view.changeState(selectState: selectState, animated: animated) + let inset:CGFloat = selectState ? 50 : 0 + actionsPanelView.change(pos: NSMakePoint(0, frame.height - inset), animated: animated) + separator.change(pos: NSMakePoint(0, frame.height - inset), animated: animated) + } + + var activePanel: View { + return self.view.activePanel + } + + + fileprivate var segmentPanelView: SegmentContainerView { + return self.view.segmentPanelView + } + fileprivate var searchPanelView: SearchContainerView? { + return self.view.searchPanelView + } + + func updateCorners(_ corners: GeneralViewItemCorners, animated: Bool) { + view.updateCorners(corners, animated: animated) + } + } + + class PeerMediaControllerView : View { + + private let topPanelView = GeneralRowContainerView(frame: .zero) + fileprivate let segmentPanelView: SegmentContainerView + fileprivate var searchPanelView: SearchContainerView? + + private(set) weak var mainView:NSView? + + private let topPanelSeparatorView = View() + + override func scrollWheel(with event: NSEvent) { + mainTable?.scrollWheel(with: event) + } + + var mainTable: TableView? { + if let tableView = self.mainView as? TableView { + return tableView + } else if let view = self.mainView as? InputDataView { + return view.tableView + } + return nil + } + + fileprivate var corners:GeneralViewItemCorners = [.topLeft, .topRight] + + fileprivate var isSelectionState:Bool = false + private var chatInteraction:ChatInteraction? + private var searchState: SearchState? + required init(frame frameRect:NSRect) { + segmentPanelView = SegmentContainerView(frame: NSMakeRect(0, 0, frameRect.width, 50)) + super.init(frame: frameRect) + addSubview(topPanelView) + topPanelView.addSubview(topPanelSeparatorView) + topPanelView.addSubview(segmentPanelView) + updateLocalizationAndTheme(theme: theme) + layout() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + // super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.listBackground + topPanelView.backgroundColor = theme.colors.background + topPanelSeparatorView.backgroundColor = theme.colors.border + } + + func updateInteraction(_ chatInteraction:ChatInteraction) { + self.chatInteraction = chatInteraction + } + + func updateCorners(_ corners: GeneralViewItemCorners, animated: Bool) { + self.corners = corners + self.topPanelView.setCorners(corners, animated: animated) + topPanelSeparatorView.isHidden = corners == .all + } + + fileprivate func updateMainView(with view:NSView, animated:PeerMediaAnimationDirection?) { + addSubview(view, positioned: .below, relativeTo: topPanelView) + + let timingFunction: CAMediaTimingFunctionName = .spring + let duration: TimeInterval = 0.35 + + if let animated = animated { + if let mainView = mainView { + switch animated { + case .leftToRight: + mainView._change(pos: NSMakePoint(-mainView.frame.width, mainView.frame.minY), animated: true, duration: duration, timingFunction: timingFunction, completion: { [weak mainView] completed in + if completed { + mainView?.removeFromSuperview() + } + }) + view.layer?.animatePosition(from: NSMakePoint(view.frame.width, mainView.frame.minY), to: NSMakePoint(0, mainView.frame.minY), duration: duration, timingFunction: timingFunction) + case .rightToLeft: + mainView._change(pos: NSMakePoint(mainView.frame.width, mainView.frame.minY), animated: true, duration: duration, timingFunction: timingFunction, completion: { [weak mainView] completed in + if completed { + mainView?.removeFromSuperview() + } + }) + view.layer?.animatePosition(from: NSMakePoint(-view.frame.width, mainView.frame.minY), to: NSMakePoint(0, mainView.frame.minY), duration: duration, timingFunction: timingFunction) + } + } + self.mainView = view + } else { + mainView?.removeFromSuperview() + self.mainView = view + } needsLayout = true } + func updateSearchState(_ state: MediaSearchState, updateSearchState:@escaping(SearchState)->Void, toggle:@escaping()->Void) { + self.searchState = state.state + switch state.state.state { + case .Focus: + if searchPanelView == nil { + self.searchPanelView = SearchContainerView(frame: NSMakeRect(0, -topPanelView.frame.height, topPanelView.frame.width, 50)) + + guard let searchPanelView = self.searchPanelView else { + fatalError() + } + topPanelView.addSubview(searchPanelView, positioned: .above, relativeTo: topPanelSeparatorView) + searchPanelView.searchView.change(state: .Focus, false) + searchPanelView.searchView.searchInteractions = SearchInteractions({ _, _ in + + }, updateSearchState) + + searchPanelView.close.set(handler: { _ in + toggle() + }, for: .Click) + } + + + guard let searchPanelView = self.searchPanelView else { + fatalError() + } + searchPanelView.searchView.isLoading = state.isLoading + searchPanelView._change(pos: NSZeroPoint, animated: state.animated) + segmentPanelView._change(pos: NSMakePoint(0, topPanelView.frame.height), animated: state.animated) + case .None: + CATransaction.begin() + segmentPanelView.removeFromSuperview() + topPanelView.addSubview(segmentPanelView, positioned: .above, relativeTo: topPanelSeparatorView) + segmentPanelView._change(pos: NSZeroPoint, animated: state.animated) + if let searchPanelView = self.searchPanelView { + self.searchPanelView = nil + searchPanelView._change(pos: NSMakePoint(0, -searchPanelView.frame.height), animated: state.animated, completion: { [weak searchPanelView] completed in + searchPanelView?.removeFromSuperview() + }) + } + CATransaction.commit() + } + } + func changeState(selectState:Bool, animated:Bool) { assert(mainView != nil) + self.isSelectionState = selectState - let inset:CGFloat = selectState ? 50 : 0 - - mainView?.animator().setFrameSize(NSMakeSize(frame.width, frame.height - inset)) - actionsPanelView.change(pos: NSMakePoint(0, frame.height - inset), animated: animated) - separator.change(pos: NSMakePoint(0, frame.height - inset), animated: animated) + } + + var activePanel: View { + if let searchPanel = self.searchPanelView { + return searchPanel + } else { + return segmentPanelView + } } override func layout() { let inset:CGFloat = isSelectionState ? 50 : 0 + topPanelView.frame = NSMakeRect(0, 0, frame.width, 50) + topPanelView.setCorners(self.corners) + topPanelSeparatorView.frame = NSMakeRect(0, topPanelView.frame.height - .borderSize, topPanelView.frame.width, .borderSize) - mainView?.frame = NSMakeRect(0, 0, frame.width, frame.height - inset) - actionsPanelView.frame = NSMakeRect(0, frame.height - inset, frame.width, 50) - separator.frame = NSMakeRect(0, frame.height - inset, frame.width, .borderSize) + if let searchPanelView = self.searchPanelView { + searchPanelView.frame = NSMakeRect(0, 0, frame.width, 50) + segmentPanelView.frame = NSMakeRect(0, topPanelView.frame.height, frame.width, 50) + } else { + segmentPanelView.frame = NSMakeRect(0, 0, topPanelView.frame.width, 50) + } + mainView?.frame = NSMakeRect(0, 50, frame.width, frame.height - inset - 50) + + } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } -} - -class PeerMediaController: EditableViewController, Notifable { - + } + + private extension PeerMediaCollectionMode { + var title: String { + if self == .members { + return L10n.peerMediaMembers + } + if self == .photoOrVideo { + return L10n.peerMediaMedia + } + if self == .file { + return L10n.peerMediaFiles + } + if self == .webpage { + return L10n.peerMediaLinks + } + if self.tagsValue == .music { + return L10n.peerMediaAudio + } + if self == .voice { + return L10n.peerMediaVoice + } + if self == .commonGroups { + return L10n.peerMediaCommonGroups + } + if self == .gifs { + return L10n.peerMediaGifs + } + return "" + } + } + + class PeerMediaController: EditableViewController, Notifable { + private let peerId:PeerId private var peer:Peer? + private var peerView: PeerView? { + didSet { + if isLoaded(), let peerView = peerView, isProfileIntended { + let context = self.context + + if let cachedData = peerView.cachedData as? CachedChannelData { + let onlineMemberCount:Signal + if (cachedData.participantsSummary.memberCount ?? 0) > 200 { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: self.peerId) |> map(Optional.init) |> deliverOnMainQueue + } else { + onlineMemberCount = context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: self.peerId) |> map(Optional.init) |> deliverOnMainQueue + } + + self.onlineMemberCountDisposable.set(onlineMemberCount.start(next: { [weak self] count in + guard let `self` = self else { + return + } + let result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.title)), onlineMemberCount: count) + self.centerBar.status = result.status + self.centerBar.text = result.title + })) + } else { + let result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.title)), onlineMemberCount: 0) + self.centerBar.status = result.status + self.centerBar.text = result.title + } + } + } + } + + private let modeValue: ValuePromise = ValuePromise(nil, ignoreRepeated: true) + private let tabsSignal:Promise<(tabs: [PeerMediaCollectionMode], selected: PeerMediaCollectionMode?, hasLoaded: Bool)> = Promise() - private var tagMask:MessageTags - private var mode:PeerMediaCollectionMode = .photoOrVideo + var tabsValue: Signal { + return tabsSignal.get() |> map { + PeerMediaTabsData(collections: $0.tabs, loaded: $0.hasLoaded) + } |> distinctUntilChanged + } + + private let tabsDisposable = MetaDisposable() + private var mode:PeerMediaCollectionMode? + + private let mediaGrid:PeerMediaPhotosController + private let gifs: PeerMediaPhotosController + private let listControllers:[PeerMediaListController] + private let members: ViewController + private let commonGroups: ViewController - private let mediaGrid:PeerMediaGridController - private let mediaList:PeerMediaListController + private let tagsList:[PeerMediaCollectionMode] = [.members, .photoOrVideo, .file, .webpage, .music, .voice, .gifs, .commonGroups] + + + private var currentTagListIndex: Int { + if let mode = self.mode { + return Int(mode.rawValue) + } else { + return 0 + } + } private var interactions:ChatInteraction - private let openPeerInfoDisposable = MetaDisposable() private let messagesActionDisposable:MetaDisposable = MetaDisposable() private let loadFwdMessagesDisposable = MetaDisposable() + private let loadSelectionMessagesDisposable = MetaDisposable() + private let searchValueDisposable = MetaDisposable() + private let onlineMemberCountDisposable = MetaDisposable() + private var searchController: PeerMediaListController? - override func getCenterBarViewOnce() -> TitledBarView { - return MediaTitleBarView(controller: self, interactions:PeerMediaTypeInteraction(media: { [weak self] in - self?.toggle(with: .photoOrVideo, animated:true) - }, files: { [weak self] in - self?.toggle(with: .file, animated:true) - }, links: { [weak self] in - self?.toggle(with: .webpage, animated:true) - }, audio: { [weak self] in - self?.toggle(with: .music, animated:true) - })) + private let toggleDisposable = MetaDisposable() + + private var currentController: ViewController? + + + + var currentMainTableView:((TableView?, Bool, Bool)->Void)? = nil { + didSet { + if isLoaded() { + currentMainTableView?(genericView.mainTable, false, false) + } + } } + private let isProfileIntended: Bool + + private let editing: ValuePromise = ValuePromise(false, ignoreRepeated: true) + override var state:ViewControllerState { + didSet { + let newValue = state + + genericView.mainTable?.scroll(to: .up(true), completion: { [weak self] _ in + self?.editing.set(newValue == .Edit) + }) + } + } - init(account:Account, peerId:PeerId, tagMask:MessageTags) { + init(context: AccountContext, peerId:PeerId, isProfileIntended:Bool = false) { self.peerId = peerId - self.tagMask = tagMask - - interactions = ChatInteraction(peerId: peerId, account: account) - - - mediaGrid = PeerMediaGridController(account: account, peerId: peerId, messageId: nil, tagMask: tagMask, chatInteraction: interactions) - mediaList = PeerMediaListController(account: account, peerId: peerId, chatInteraction: interactions) + self.isProfileIntended = isProfileIntended + self.interactions = ChatInteraction(chatLocation: .peer(peerId), context: context) + self.mediaGrid = PeerMediaPhotosController(context, chatInteraction: interactions, peerId: peerId, tags: .photoOrVideo) + var listControllers: [PeerMediaListController] = [] + for _ in tagsList.filter ({ !$0.tagsValue.isEmpty }) { + listControllers.append(PeerMediaListController(context: context, chatLocation: .peer(peerId), chatInteraction: interactions)) + } + self.listControllers = listControllers - super.init(account) + self.members = PeerMediaGroupPeersController(context: context, peerId: peerId, editing: editing.get()) + self.commonGroups = GroupsInCommonViewController(context: context, peerId: peerId) + self.gifs = PeerMediaPhotosController(context, chatInteraction: interactions, peerId: peerId, tags: .gif) + super.init(context) + } +// +// private var temporaryTouchBar: Any? +// +// @available(OSX 10.12.2, *) +// override func makeTouchBar() -> NSTouchBar? { +// if temporaryTouchBar == nil { +// temporaryTouchBar = PeerMediaTouchBar(chatInteraction: interactions, currentMode: tabsSignal.get() |> map { $0.selected }, toggleMode: { [weak self] value in +// self?.modeValue.set(value) +// }) +// } +// return temporaryTouchBar as? NSTouchBar +// } + + var unableToHide: Bool { + return self.genericView.activePanel is SearchContainerView || self.state != .Normal } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) interactions.add(observer: self) - if self.mode == .photoOrVideo { - self.mediaGrid.viewDidAppear(animated) - } else { - self.mediaList.viewDidAppear(animated) + + if let mode = self.mode { + self.controller(for: mode).viewDidAppear(animated) } + + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self, self.mode != .photoOrVideo, self.mode != .commonGroups else { + return .rejected + } + if self.mode == .members { + self.searchGroupUsers() + return .invoked + } + self.listControllers[self.currentTagListIndex].toggleSearch() + return .invoked + }, with: self, for: .F, modifierFlags: [.command]) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else { + return .rejected + } + if self.genericView.searchPanelView != nil { + return .rejected + } + // self.genericView.segmentPanelView.segmentControl.selectNext(animated: true) + return .invoked + }, with: self, for: .Tab) + + guard let navigationController = self.navigationController, isProfileIntended else { + return + } + + navigationController.swapNavigationBar(leftView: nil, centerView: self.centerBarView, rightView: nil, animation: .crossfade) + navigationController.swapNavigationBar(leftView: nil, centerView: nil, rightView: self.rightBarView, animation: .none) + } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) interactions.remove(observer: self) - if self.mode == .photoOrVideo { - self.mediaGrid.viewDidDisappear(animated) - } else { - self.mediaList.viewDidDisappear(animated) + + if let mode = mode { + let controller = self.controller(for: mode) + controller.viewDidDisappear(animated) + if let controller = controller as? PeerMediaListController { + controller.searchState.set(.init(state: .None, request: nil)) + } + } + + if let navigationController = navigationController, isProfileIntended { + navigationController.swapNavigationBar(leftView: nil, centerView: navigationController.controller.centerBarView, rightView: nil, animation: .crossfade) + navigationController.swapNavigationBar(leftView: nil, centerView: nil, rightView: navigationController.controller.rightBarView, animation: .none) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if self.mode == .photoOrVideo { - self.mediaGrid.viewWillAppear(animated) - } else { - self.mediaList.viewWillAppear(animated) + + if let mode = mode { + let controller = self.controller(for: mode) + controller.viewWillAppear(animated) } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - if self.mode == .photoOrVideo { - self.mediaGrid.viewWillDisappear(animated) - } else { - self.mediaList.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + + if let mode = mode { + let controller = self.controller(for: mode) + controller.viewWillDisappear(animated) } } func notify(with value: Any, oldValue: Any, animated: Bool) { if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { + + let context = self.context + if value.selectionState != oldValue.selectionState { + if let selectionState = value.selectionState { + let ids = Array(selectionState.selectedIds) + loadSelectionMessagesDisposable.set((context.account.postbox.messagesAtIds(ids) |> deliverOnMainQueue).start( next:{ [weak self] messages in + var canDelete:Bool = !ids.isEmpty + var canForward:Bool = !ids.isEmpty + for message in messages { + if !canDeleteMessage(message, account: context.account) { + canDelete = false + } + if !canForwardMessage(message, account: context.account) { + canForward = false + } + } + self?.interactions.update({$0.withUpdatedBasicActions((canDelete, canForward))}) + })) + } else { + interactions.update({$0.withUpdatedBasicActions((false, false))}) + } + } + if (value.state == .selecting) != (oldValue.state == .selecting) { self.state = value.state == .selecting ? .Edit : .Normal - genericView.changeState(selectState: value.state == .selecting, animated: animated) - if mode == .photoOrVideo { - self.mediaGrid.genericView.grid.forEachItemNode { itemNode in - if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateSelectionState(animated: animated) - } - } - } - + genericView.changeState(selectState: value.state == .selecting && self.mode != .members, animated: animated) } } } - + func isEqual(to other: Notifable) -> Bool { if let other = other as? PeerMediaController { @@ -184,42 +675,174 @@ class PeerMediaController: EditableViewController, Noti super.viewDidLoad() genericView.updateInteraction(interactions) + let tagsList = self.tagsList - interactions.forwardMessages = { [weak self] messageIds in - if let strongSelf = self, let navigation = strongSelf.navigationController { - strongSelf.loadFwdMessagesDisposable.set((strongSelf.account.postbox.messagesAtIds(messageIds) |> deliverOnMainQueue).start(next: { [weak strongSelf] messages in - if let strongSelf = strongSelf { - - let displayName:String = strongSelf.peer?.compactDisplayTitle ?? "Unknown" - let action = FWDNavigationAction(messages: messages, displayName: displayName) - navigation.set(modalAction: action, strongSelf.account.context.layout != .single) - - if strongSelf.account.context.layout == .single { - navigation.push(ForwardChatListController(strongSelf.account)) - } - - action.afterInvoke = { [weak strongSelf] in - strongSelf?.interactions.update(animated: false, {$0.withoutSelectionState()}) - strongSelf?.interactions.saveState() + let context = self.context + let peerId = self.peerId + + + let membersTab:Signal<(tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool), NoError> + let commonGroupsTab:Signal<(tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool), NoError> + + membersTab = context.account.postbox.peerView(id: peerId) |> map { view -> (exist: Bool, loaded: Bool) in + if let cachedData = view.cachedData as? CachedGroupData { + return (exist: Int(cachedData.participants?.participants.count ?? 0 ) > minumimUsersBlock, loaded: true) + } else if let cachedData = view.cachedData as? CachedChannelData { + if let peer = peerViewMainPeer(view), peer.isSupergroup { + return (exist: Int32(cachedData.participantsSummary.memberCount ?? 0) > minumimUsersBlock, loaded: true) + } else { + return (exist: false, loaded: true) + } + } else { + return (exist: false, loaded: true) + } + } |> map { data -> (tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool) in + return (tag: .members, exists: data.exist, hasLoaded: data.loaded) + } + + commonGroupsTab = context.account.postbox.peerView(id: peerId) |> map { view -> (exist: Bool, loaded: Bool) in + if let cachedData = view.cachedData as? CachedUserData { + return (exist: cachedData.commonGroupCount > 0, loaded: true) + } else { + if view.peerId.namespace == Namespaces.Peer.CloudUser || view.peerId.namespace == Namespaces.Peer.SecretChat { + return (exist: false, loaded: false) + } + return (exist: false, loaded: true) + } + } |> map { data -> (tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool) in + return (tag: .commonGroups, exists: data.exist, hasLoaded: data.loaded) + } + + + let tabItems: [Signal<(tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool), NoError>] = self.tagsList.filter { !$0.tagsValue.isEmpty }.map { tags -> Signal<(tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool), NoError> in + return context.account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(.peer(peerId), count: 3, tagMask: tags.tagsValue) + |> map { (view, _, _) -> (tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool) in + let hasLoaded = view.entries.count >= 3 || (!view.isLoading) + return (tag: tags, exists: !view.entries.isEmpty, hasLoaded: hasLoaded) + } + + } + + let mergedTabs = combineLatest(membersTab, combineLatest(tabItems), commonGroupsTab) |> map { members, general, commonGroups -> [(tag: PeerMediaCollectionMode, exists: Bool, hasLoaded: Bool)] in + var general = general + general.insert(members, at: 0) + general.append(commonGroups) + return general + } + + let tabSignal = combineLatest(queue: .mainQueue(), mergedTabs, modeValue.get()) + |> map { tabs, selected -> (tabs: [PeerMediaCollectionMode], selected: PeerMediaCollectionMode?, hasLoaded: Bool) in + var selectedValue = selected + if selected == nil || !tabs.contains(where: { $0.exists && $0.tag == selected }) { + if let selected = selected { + let index = tagsList.firstIndex(of: selected)! + var perhapsBest: PeerMediaCollectionMode? + for i in stride(from: index, to: -1, by: -1) { + if tabs.contains(where: { $0.exists && $0.tag == tagsList[i] }) { + perhapsBest = tagsList[i] + break } - } - })) + selectedValue = perhapsBest ?? tabs.filter { $0.exists }.last?.tag ?? selected + } else { + selectedValue = tabs.filter { $0.exists }.first?.tag + } + + } + return (tabs: tabs.filter { $0.exists }.map { $0.tag }, selected: selectedValue, hasLoaded: tabs.reduce(true, { $0 && $1.hasLoaded })) + } + + tabsSignal.set(tabSignal) + + let data: Signal<(tabs: [PeerMediaCollectionMode], selected: PeerMediaCollectionMode?, hasLoaded: Bool), NoError> = tabsSignal.get() |> deliverOnMainQueue |> mapToSignal { [weak self] data in + guard let `self` = self else { + return .complete() } + if let selected = data.selected { + switch selected { + case .members: + if !self.members.isLoaded() { + self.members.loadViewIfNeeded(self.genericView.view.bounds) + } + return self.members.ready.get() |> map { ready in + return data + } + case .commonGroups: + if !self.commonGroups.isLoaded() { + self.commonGroups.loadViewIfNeeded(self.genericView.view.bounds) + } + return self.commonGroups.ready.get() |> map { ready in + return data + } + case .photoOrVideo: + if !self.mediaGrid.isLoaded() { + self.mediaGrid.loadViewIfNeeded(self.genericView.view.bounds) + } + return self.mediaGrid.ready.get() |> map { _ in + return data + } + case .gifs: + if !self.gifs.isLoaded() { + self.gifs.loadViewIfNeeded(self.genericView.view.bounds) + } + return self.gifs.ready.get() |> map { ready in + return data + } + default: + if !self.listControllers[Int(selected.rawValue)].isLoaded() { + self.listControllers[Int(selected.rawValue)].loadViewIfNeeded(self.genericView.view.bounds) + self.listControllers[Int(selected.rawValue)].load(with: selected.tagsValue) + } + return self.listControllers[Int(selected.rawValue)].ready.get() |> map { _ in + return data + } + } + } else { + return .single(data) + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + if lhs.tabs != rhs.tabs { + return false + } + if lhs.hasLoaded != rhs.hasLoaded { + return false + } + if lhs.selected != rhs.selected { + return false + } + return true + }) + + let ready = data |> map { _ in return true } + + genericView.segmentPanelView.segmentControl.didChangeSelectedItem = { [weak self] item in + let newMode = PeerMediaCollectionMode(rawValue: item.uniqueId)! + + if newMode == self?.mode, let mainTable = self?.genericView.mainTable { + self?.currentMainTableView?(mainTable, true, true) + } + self?.modeValue.set(newMode) + } + + + interactions.forwardMessages = { messageIds in + showModal(with: ShareModalController(ForwardMessagesObject(context, messageIds: messageIds)), for: mainWindow) } interactions.focusMessageId = { [weak self] _, focusMessageId, animated in if let strongSelf = self { - strongSelf.navigationController?.push(ChatController(account: strongSelf.account, peerId: strongSelf.peerId, messageId: focusMessageId)) + strongSelf.navigationController?.push(ChatController(context: context, chatLocation: .peer(strongSelf.peerId), messageId: focusMessageId)) } } interactions.inlineAudioPlayer = { [weak self] controller in - if let navigation = self?.navigationController, let strongSelf = self { + if let navigation = self?.navigationController, let `self` = self { if let header = navigation.header { header.show(true) if let view = header.view as? InlineAudioPlayerView { - view.update(with: controller, tableView: strongSelf.mediaList.genericView) + let tableView = (navigation.first { $0 is ChatController} as? ChatController)?.genericView.tableView + view.update(with: controller, context: context, tableView: tableView, supportTableView: self.currentTable) } } } @@ -228,126 +851,359 @@ class PeerMediaController: EditableViewController, Noti interactions.openInfo = { [weak self] (peerId, toChat, postId, action) in if let strongSelf = self { if toChat { - strongSelf.navigationController?.push(ChatController(account: strongSelf.account, peerId: peerId, messageId: postId, initialAction: action)) + strongSelf.navigationController?.push(ChatController(context: context, chatLocation: .peer(peerId), messageId: postId, initialAction: action)) } else { - strongSelf.openPeerInfoDisposable.set((strongSelf.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak strongSelf] peer in - if let strongSelf = strongSelf { - strongSelf.navigationController?.push(PeerInfoController(account: strongSelf.account, peer: peer)) - } - })) + strongSelf.navigationController?.push(PeerInfoController(context: context, peerId: peerId)) } } } interactions.deleteMessages = { [weak self] messageIds in - if let account = self?.account { - self?.messagesActionDisposable.set((account.postbox.messagesAtIds(messageIds) |> deliverOnMainQueue).start( next:{ [weak self] messages in - - var canDelete:Bool = true - var canDeleteForEveryone = true - - for message in messages { - if !canDeleteMessage(message, account: account) { - canDelete = false + if let strongSelf = self, let peer = strongSelf.peer { + let channelAdmin:Signal<[ChannelParticipant]?, NoError> = peer.isSupergroup ? channelAdmins(account: context.account, peerId: strongSelf.interactions.peerId) + |> `catch` {_ in .complete()} |> map { admins -> [ChannelParticipant]? in + return admins.map({$0.participant}) + } : .single(nil) + + + self?.messagesActionDisposable.set(combineLatest(context.account.postbox.messagesAtIds(messageIds) |> deliverOnMainQueue, channelAdmin |> deliverOnMainQueue).start( next:{ [weak strongSelf] messages, admins in + if let strongSelf = strongSelf { + var canDelete:Bool = true + var canDeleteForEveryone = true + var otherCounter:Int32 = 0 + var _mustDeleteForEveryoneMessage: Bool = true + for message in messages { + if !canDeleteMessage(message, account: context.account) { + canDelete = false + } + if !mustDeleteForEveryoneMessage(message) { + _mustDeleteForEveryoneMessage = false + } + if !canDeleteForEveryoneMessage(message, context: context) { + canDeleteForEveryone = false + } else { + if message.effectiveAuthor?.id != context.peerId && !(context.limitConfiguration.canRemoveIncomingMessagesInPrivateChats && message.peers[message.id.peerId] is TelegramUser) { + if let peer = message.peers[message.id.peerId] as? TelegramGroup { + inner: switch peer.role { + case .member: + otherCounter += 1 + default: + break inner + } + } else { + otherCounter += 1 + } + } + } } - if !canDeleteForEveryoneMessage(message, account: account) { + + if otherCounter > 0 || peer.id == context.peerId { canDeleteForEveryone = false } - } - - if canDelete { - let thrid:String? = canDeleteForEveryone ? tr(.chatConfirmDeleteMessagesForEveryone) : nil + if messages.isEmpty { + strongSelf.interactions.update({$0.withoutSelectionState()}) + return + } - if let window = self?.window { - confirm(for: window, with: tr(.chatConfirmActionUndonable), and: tr(.chatConfirmDeleteMessages), thridTitle:thrid, successHandler: { [weak self] result in - let type:InteractiveMessagesDeletionType - switch result { - case .basic: - type = .forLocalPeer - case .thrid: - type = .forEveryone - } - _ = deleteMessagesInteractively(postbox: account.postbox, messageIds: messageIds, type: type).start() - self?.interactions.update({$0.withoutSelectionState()}) - }) + if canDelete { + let isAdmin = admins?.filter({$0.peerId == messages[0].author?.id}).first != nil + if mustManageDeleteMessages(messages, for: peer, account: strongSelf.context.account), let memberId = messages[0].author?.id, !isAdmin { + + let options:[ModalOptionSet] = [ModalOptionSet(title: L10n.supergroupDeleteRestrictionDeleteMessage, selected: true, editable: true), + ModalOptionSet(title: L10n.supergroupDeleteRestrictionBanUser, selected: false, editable: true), + ModalOptionSet(title: L10n.supergroupDeleteRestrictionReportSpam, selected: false, editable: true), + ModalOptionSet(title: L10n.supergroupDeleteRestrictionDeleteAllMessages, selected: false, editable: true)] + showModal(with: ModalOptionSetController(context: context, options: options, actionText: (L10n.modalOK, theme.colors.accent), title: L10n.supergroupDeleteRestrictionTitle, result: { [weak strongSelf] result in + + var signals:[Signal] = [] + if result[0] == .selected { + signals.append(deleteMessagesInteractively(account: context.account, messageIds: messages.map {$0.id}, type: .forEveryone)) + } + if result[1] == .selected { + signals.append(context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peer.id, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))) + } + if result[2] == .selected { + signals.append(reportSupergroupPeer(account: context.account, peerId: memberId, memberId: memberId, messageIds: messageIds)) + } + if result[3] == .selected { + signals.append(clearAuthorHistory(account: context.account, peerId: peer.id, memberId: memberId)) + } + + _ = showModalProgress(signal: combineLatest(signals), for: context.window).start() + strongSelf?.interactions.update({$0.withoutSelectionState()}) + + }), for: context.window) + } else { + let thrid:String? = (canDeleteForEveryone ? peer.isUser ? L10n.chatMessageDeleteForMeAndPerson(peer.compactDisplayTitle) : L10n.chatConfirmDeleteMessagesForEveryone : nil) + + modernConfirm(for: context.window, account: context.account, peerId: nil, header: thrid == nil ? L10n.chatConfirmActionUndonable : L10n.chatConfirmDeleteMessagesCountable(messages.count), information: thrid == nil ? _mustDeleteForEveryoneMessage ? L10n.chatConfirmDeleteForEveryoneCountable(messages.count) : L10n.chatConfirmDeleteMessagesCountable(messages.count) : nil, okTitle: L10n.confirmDelete, thridTitle: thrid, successHandler: { [weak strongSelf] result in + + guard let `strongSelf` = strongSelf else { + return + } + let type:InteractiveMessagesDeletionType + switch result { + case .basic: + type = .forLocalPeer + case .thrid: + type = .forEveryone + } + _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: messageIds, type: type).start() + strongSelf.interactions.update({$0.withoutSelectionState()}) + }) + } } } - })) } } - let peerSignal = account.viewTracker.peerView(peerId) |> deliverOnMainQueue |> beforeNext({ [weak self] peerView in + let peerSignal = context.account.viewTracker.peerView(peerId) |> deliverOnMainQueue |> beforeNext({ [weak self] peerView in self?.peer = peerView.peers[peerView.peerId] + self?.peerView = peerView }) |> map { view -> Bool in return true } - let combined = combineLatest( [peerSignal |> take(1), mediaGrid.ready.get()] ) |> map { result -> Bool in - return result[0] && result[1] + let combined = combineLatest( [peerSignal |> take(1), ready, self.tabsSignal.get() |> map { $0.hasLoaded }] ) |> map { result -> Bool in + return result[0] && result[1] && result[2] } self.ready.set(combined |> deliverOnMainQueue) - } - - override func loadView() { - super.loadView() - - mediaList.loadViewIfNeeded(bounds) - mediaGrid.loadViewIfNeeded(bounds) - mediaGrid.viewWillAppear(false) - genericView.updateMainView(with: mediaGrid.view, animated: false) - mediaGrid.viewDidAppear(false) + - requestUpdateCenterBar() + var firstTabAppear = true + tabsDisposable.set((data |> deliverOnMainQueue).start(next: { [weak self] tabs, selected, hasLoaded in + var items:[ScrollableSegmentItem] = [] + if hasLoaded, let `self` = self { + let insets = NSEdgeInsets(left: 10, right: 10, bottom: 2) + let segmentTheme = ScrollableSegmentTheme(background: .clear, border: .clear, selector: theme.colors.accent, inactiveText: theme.colors.grayText, activeText: theme.colors.accent, textFont: .normal(.title)) + for (i, tab) in tabs.enumerated() { + items.append(ScrollableSegmentItem(title: tab.title, index: i, uniqueId: tab.rawValue, selected: selected == tab, insets: insets, icon: nil, theme: segmentTheme, equatable: nil)) + } + self.genericView.segmentPanelView.segmentControl.updateItems(items, animated: !firstTabAppear) + if let selected = selected { + self.toggle(with: selected, animated: !firstTabAppear) + } + + firstTabAppear = false + + if tabs.isEmpty, self.isProfileIntended { + if self.genericView.superview != nil { + self.viewWillDisappear(true) + self.genericView.removeFromSuperview() + self.viewDidDisappear(true) + } + } + } + })) } - private func toggle(with mode:PeerMediaCollectionMode, animated:Bool = false) { + + private var currentTable: TableView? { + if self.mode == .photoOrVideo || self.mode == .members { + return nil + } else { + return self.listControllers[currentTagListIndex].genericView + } + } + + private func applyReadyController(mode:PeerMediaCollectionMode, animated:Bool) { + genericView.mainTable?.updatedItems = nil + let oldMode = self.mode + self.mode = mode + let previous = self.currentController + + let controller = self.controller(for: mode) - if self.mode != mode { - self.mode = mode - if mode == .photoOrVideo { - mediaGrid.viewWillAppear(animated) - mediaList.viewWillDisappear(animated) - mediaGrid.view.frame = bounds - genericView.updateMainView(with: mediaGrid.view, animated: animated) - mediaGrid.viewDidAppear(animated) - mediaList.removeFromSuperview() - mediaList.viewDidDisappear(animated) + self.currentController = controller + controller.viewWillAppear(animated) + previous?.viewWillDisappear(animated) + controller.view.frame = self.genericView.view.bounds + let animation: PeerMediaAnimationDirection? + + if animated, let oldMode = oldMode { + if oldMode.rawValue > mode.rawValue { + animation = .rightToLeft } else { - mediaList.viewWillAppear(animated) - mediaGrid.viewWillDisappear(animated) - mediaList.view.frame = bounds - genericView.updateMainView(with: mediaList.view, animated: animated) - mediaList.viewDidAppear(animated) - mediaGrid.removeFromSuperview() - mediaGrid.viewDidDisappear(animated) - } - - if mode != .photoOrVideo { - mediaList.load(with: mode.tagsValue) + animation = .leftToRight } + } else { + animation = nil + } + + genericView.updateMainView(with: controller.view, animated: animation) + controller.viewDidAppear(animated) + previous?.viewDidDisappear(animated) + searchValueDisposable.set(nil) + + + centerBar.updateSearchVisibility(mode != .photoOrVideo && mode != .commonGroups && mode != .gifs && mode != .voice) + + + if let controller = controller as? PeerMediaListController { + searchValueDisposable.set(self.listControllers[currentTagListIndex].mediaSearchValue.start(next: { [weak self, weak controller] state in + self?.genericView.updateSearchState(state, updateSearchState: { searchState in + controller?.searchState.set(searchState) + }, toggle: { + controller?.toggleSearch() + }) + })) } + var firstUpdate: Bool = true + genericView.mainTable?.updatedItems = { [weak self] items in + let filter = items.filter { + !($0 is PeerMediaEmptyRowItem) && !($0.className == "Telegram.GeneralRowItem") && !($0 is SearchEmptyRowItem) + } + self?.genericView.updateCorners(filter.isEmpty ? .all : [.topLeft, .topRight], animated: !firstUpdate) + firstUpdate = false + } + self.currentMainTableView?(genericView.mainTable, animated, previous != controller && genericView.segmentPanelView.segmentControl.contains(oldMode?.rawValue ?? -3)) } - override func requestUpdateCenterBar() { - (self.centerBarView as! MediaTitleBarView).updateLocalizationAndTheme() + func controller(for mode: PeerMediaCollectionMode) -> ViewController { + switch mode { + case .photoOrVideo: + return self.mediaGrid + case .members: + return self.members + case .commonGroups: + return self.commonGroups + case .gifs: + return self.gifs + default: + return self.listControllers[Int(mode.rawValue)] + } + } + + private func toggle(with mode:PeerMediaCollectionMode, animated:Bool = false) { + let isUpdated = self.mode != mode + if isUpdated { + let controller: ViewController = self.controller(for: mode) + + let ready = controller.ready.get() |> take(1) + + toggleDisposable.set(ready.start(next: { [weak self] _ in + self?.applyReadyController(mode: mode, animated: animated) + })) + } else { + self.currentMainTableView?(genericView.mainTable, animated, false) + } + self.modeValue.set(mode) } deinit { messagesActionDisposable.dispose() - openPeerInfoDisposable.dispose() loadFwdMessagesDisposable.dispose() + loadSelectionMessagesDisposable.dispose() + tabsDisposable.dispose() + toggleDisposable.dispose() + onlineMemberCountDisposable.dispose() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + for controller in self.listControllers { + if controller.isLoaded() { + controller.updateLocalizationAndTheme(theme: theme) + } + } + } override public func update(with state:ViewControllerState) -> Void { super.update(with:state) interactions.update({state == .Normal ? $0.withoutSelectionState() : $0.withSelectionState()}) } - -} - - + + override func escapeKeyAction() -> KeyHandlerResult { + if genericView.searchPanelView != nil { + self.listControllers[self.currentTagListIndex].toggleSearch() + return .invoked + } else if interactions.presentation.state == .selecting { + interactions.update { $0.withoutSelectionState() } + return .invoked + } else { + return super.escapeKeyAction() + } + } + + private var centerBar: SearchTitleBarView { + return centerBarView as! SearchTitleBarView + } + + private func searchGroupUsers() { + _ = (selectModalPeers(context: context, title: L10n.selectPeersTitleSearchMembers, behavior: peerId.namespace == Namespaces.Peer.CloudGroup ? SelectGroupMembersBehavior.init(peerId: peerId, limit: 1, settings: []) : SelectChannelMembersBehavior(peerId: peerId, limit: 1, settings: [])) |> deliverOnMainQueue |> map {$0.first}).start(next: { [weak self] peerId in + if let peerId = peerId, let context = self?.context { + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + } + }) + } + + override func getCenterBarViewOnce() -> TitledBarView { + return SearchTitleBarView(controller: self, title:.initialize(string: defaultBarTitle, color: theme.colors.text, font: .medium(.title)), handler: { [weak self] in + guard let `self` = self else { + return + } + if let mode = self.mode { + switch mode { + case .members: + self.searchGroupUsers() + case .photoOrVideo, .commonGroups: + break + default: + (self.controller(for: mode) as? PeerMediaListController)?.toggleSearch() + } + } + }) + } + +// override func getCenterBarViewOnce() -> TitledBarView { +// +// +// +// +// return PeerMediaTitleBarView(controller: self, context: context, peerId: self.peerId, title: .initialize(string: self.defaultBarTitle, color: theme.colors.text, font: .medium(.title)), handler: { [weak self] in +// guard let `self` = self else { +// return +// } +// if let mode = self.mode { +// let controller = self.controller(for: mode) +// if let controller = controller as? PeerMediaListController { +// controller.toggleSearch() +// } else if controller is PeerMediaGroupPeersController { +// +// } +// } +// }) +// } +// + override func becomeFirstResponder() -> Bool? { + return true + } + + override func firstResponder() -> NSResponder? { + return genericView.searchPanelView?.searchView.input + } + + override func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> () -> Void { + for mediaList in listControllers { + if mediaList.view.superview != nil { + return mediaList.navigationHeaderDidNoticeAnimation(current, previous, animated) + } + } + + if mediaGrid.view.superview != nil { + return mediaGrid.navigationHeaderDidNoticeAnimation(current, previous, animated) + } + return {} + } + + } + + + diff --git a/Telegram-Mac/PeerMediaDateItem.swift b/Telegram-Mac/PeerMediaDateItem.swift new file mode 100644 index 0000000000..d0777529fa --- /dev/null +++ b/Telegram-Mac/PeerMediaDateItem.swift @@ -0,0 +1,140 @@ +// +// PeerMediaDateItem.swift +// Telegram +// +// Created by keepcoder on 27/11/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox + +class PeerMediaDateItem: TableStickItem { + + private let _stableId: AnyHashable + private let messageIndex: MessageIndex + fileprivate let textLayout: TextViewLayout + let viewType: GeneralViewType + let inset: NSEdgeInsets + + init(_ initialSize: NSSize, index: MessageIndex, stableId: AnyHashable) { + self.messageIndex = index + self._stableId = stableId + self.viewType = .modern(position: .single, insets: NSEdgeInsetsMake(3, 0, 3, 0)) + self.inset = NSEdgeInsets(left: 0, right: 0) + let timestamp = index.timestamp + + let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + var t: time_t = time_t(timestamp) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + var now: time_t = time_t(nowTimestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + + let text: String + let dateFormatter = DateFormatter() + dateFormatter.timeZone = NSTimeZone.local + dateFormatter.dateFormat = "MMMM yyyy"; + text = dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(timestamp))).uppercased() + + textLayout = TextViewLayout(.initialize(string: text, color: theme.colors.listGrayText, font: .normal(.short))) + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + required init(_ initialSize: NSSize) { + self._stableId = AnyHashable(0) + self.messageIndex = MessageIndex.absoluteLowerBound() + self.textLayout = TextViewLayout(.initialize(string: "")) + self.viewType = .separator + self.inset = NSEdgeInsets(left: 30, right: 30) + super.init(initialSize) + } + + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - 60) + return success + } + + override var stableId: AnyHashable { + return _stableId + } + + override var height: CGFloat { + return textLayout.layoutSize.height + viewType.innerInset.top + viewType.innerInset.bottom + 9 + } + + override func viewClass() -> AnyClass { + return PeerMediaDateView.self + } +} + +fileprivate class PeerMediaDateView : TableStickView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let textView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.containerView) + containerView.addSubview(self.textView) + self.textView.disableBackgroundDrawing = true + self.textView.isSelectable = false + self.textView.userInteractionEnabled = false + } + + override var header: Bool { + didSet { + updateColors() + } + } + override func updateIsVisible(_ visible: Bool, animated: Bool) { + containerView.change(opacity: visible ? 1 : 0, animated: animated) + } + + override var backdorColor: NSColor { + return theme.colors.listBackground.withAlphaComponent(0.8) + } + + override func updateColors() { + guard let item = item as? PeerMediaDateItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + } + + override func layout() { + super.layout() + guard let item = item as? PeerMediaDateItem else { + return + } + let blockWidth = min(600, frame.width - item.inset.left - item.inset.right) + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - blockWidth) / 2), item.inset.top, blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners([]) + + textView.centerY(x: item.viewType.innerInset.left + 12) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? PeerMediaDateItem else { + return + } + self.textView.update(item.textLayout) + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PeerMediaEmptyRowItem.swift b/Telegram-Mac/PeerMediaEmptyRowItem.swift index 53a384f2e5..4ed966a69e 100644 --- a/Telegram-Mac/PeerMediaEmptyRowItem.swift +++ b/Telegram-Mac/PeerMediaEmptyRowItem.swift @@ -8,7 +8,7 @@ import Cocoa import TGUIKit -import PostboxMac +import Postbox @@ -21,16 +21,16 @@ class PeerMediaEmptyRowItem: TableRowItem { let attr:NSAttributedString if tags.contains(.file) { image = theme.icons.mediaEmptyFiles - attr = .initialize(string: tr(.peerMediaSharedFilesEmptyList), color: theme.colors.grayText, font: .normal(.header)) - } else if tags.contains(.music) { + attr = .initialize(string: tr(L10n.peerMediaSharedFilesEmptyList1), color: theme.colors.grayText, font: .normal(.header)) + } else if tags.contains(.music) || tags.contains(.voiceOrInstantVideo) { image = theme.icons.mediaEmptyMusic - attr = .initialize(string: tr(.peerMediaSharedMusicEmptyList), color: theme.colors.grayText, font: .normal(.header)) + attr = .initialize(string: tags.contains(.voiceOrInstantVideo) ? L10n.peerMediaSharedVoiceEmptyList : L10n.peerMediaSharedMusicEmptyList, color: theme.colors.grayText, font: .normal(.header)) } else if tags.contains(.webPage) { image = theme.icons.mediaEmptyLinks - attr = .initialize(string: tr(.peerMediaSharedLinksEmptyList), color: theme.colors.grayText, font: .normal(.header)) + attr = .initialize(string: tr(L10n.peerMediaSharedLinksEmptyList), color: theme.colors.grayText, font: .normal(.header)) } else { image = theme.icons.mediaEmptyShared - attr = .initialize(string: tr(.peerMediaSharedMediaEmptyList), color: theme.colors.grayText, font: .normal(.header)) + attr = .initialize(string: tr(L10n.peerMediaSharedMediaEmptyList), color: theme.colors.grayText, font: .normal(.header)) } textLayout = TextViewLayout(attr, alignment: .center) super.init(initialSize) @@ -63,23 +63,31 @@ class PeerMediaEmptyRowView : TableRowView { addSubview(imageView) } + override var backdorColor: NSColor { + return theme.colors.listBackground + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + override func layout() { super.layout() if let item = item as? PeerMediaEmptyRowItem { - - item.textLayout.measure(width: frame.width - 40) - let f = focus(item.textLayout.layoutSize) - textView.update(item.textLayout, origin:f.origin) - imageView.centerX(y:f.minY - imageView.frame.height - 20) + imageView.centerX(y: bounds.midY - imageView.frame.height - 40) + item.textLayout.measure(width: frame.width - 60) + textView.update(item.textLayout) + textView.centerX(y: imageView.frame.maxY + 16) } } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) if let item = item as? PeerMediaEmptyRowItem { - textView.backgroundColor = theme.colors.background imageView.image = item.image imageView.sizeToFit() + needsLayout = true } } diff --git a/Telegram-Mac/PeerMediaFileRowContent.swift b/Telegram-Mac/PeerMediaFileRowContent.swift index b1f124dd50..0a7b5f0481 100644 --- a/Telegram-Mac/PeerMediaFileRowContent.swift +++ b/Telegram-Mac/PeerMediaFileRowContent.swift @@ -8,18 +8,19 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class PeerMediaFileRowItem: PeerMediaRowItem { - private(set) var nameLayout:TextViewLayout! - private(set) var actionLayout:TextViewLayout! - private(set) var actionLayoutLocal:TextViewLayout! + private(set) var nameLayout:TextViewLayout + private(set) var actionLayout:TextViewLayout + private(set) var actionLayoutLocal:TextViewLayout private(set) var iconArguments:TransformImageArguments? private(set) var icon:TelegramMediaImage? @@ -27,66 +28,66 @@ class PeerMediaFileRowItem: PeerMediaRowItem { private(set) var docIcon:CGImage? private(set) var docTitle:NSAttributedString? - override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ account:Account, _ object: PeerMediaSharedEntry) { - super.init(initialSize,interface,account,object) - iconSize = NSMakeSize(40, 40) + override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ object: PeerMediaSharedEntry, viewType: GeneralViewType = .legacy) { - if let file = message.media.first as? TelegramMediaFile { - - self.file = file - - nameLayout = TextViewLayout(NSAttributedString.initialize(string: file.fileName ?? "Unknown", color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) - - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "MMM d, yyyy 'at' h a" - - let dateString = dateFormatter.string(from: Date(timeIntervalSince1970: Double(TimeInterval(message.timestamp) - account.context.timeDifference))) - - actionLayout = TextViewLayout(NSAttributedString.initialize(string: "\(dataSizeString(file.size ?? 0)) • \(dateString)",color: theme.colors.grayText, font: NSFont.normal(FontSize.text)), maximumNumberOfLines: 1, truncationType: .end) - - let localAction = NSMutableAttributedString() - let range = localAction.append(string: tr(.contextShowInFinder), color: theme.colors.link, font: .normal(.text)) - localAction.add(link: inAppLink.callback("finder", { _ in - showInFinder(file, account: account) - }), for: range) - actionLayoutLocal = TextViewLayout(localAction, maximumNumberOfLines: 1, truncationType: .end) - actionLayoutLocal.interactions = globalLinkExecutor - - let iconImageRepresentation:TelegramMediaImageRepresentation? = smallestImageRepresentation(file.previewRepresentations) + + let message = object.message! + let file = message.media.first as! TelegramMediaFile + self.file = file + + nameLayout = TextViewLayout(NSAttributedString.initialize(string: file.fileName ?? "Unknown", color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MMM d, yyyy 'at' h a" + + let dateString = dateFormatter.string(from: Date(timeIntervalSince1970: Double(TimeInterval(message.timestamp) - interface.context.timeDifference))) + + actionLayout = TextViewLayout(NSAttributedString.initialize(string: "\(dataSizeString(file.size ?? 0)) • \(dateString)",color: theme.colors.grayText, font: NSFont.normal(12.5)), maximumNumberOfLines: 1, truncationType: .end) + + let localAction = NSMutableAttributedString() + let range = localAction.append(string: tr(L10n.contextShowInFinder), color: theme.colors.link, font: .normal(.text)) + localAction.add(link: inAppLink.callback("finder", { _ in + showInFinder(file, account: interface.context.account) + }), for: range) + actionLayoutLocal = TextViewLayout(localAction, maximumNumberOfLines: 1, truncationType: .end) + actionLayoutLocal.interactions = globalLinkExecutor + + let iconImageRepresentation:TelegramMediaImageRepresentation? = smallestImageRepresentation(file.previewRepresentations) + + if let iconImageRepresentation = iconImageRepresentation { + iconArguments = TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: iconImageRepresentation.dimensions.size.aspectFilled(PeerMediaIconSize), boundingSize: PeerMediaIconSize, intrinsicInsets: NSEdgeInsets()) + icon = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } else { + let fileName: String = file.fileName ?? "" - if let iconImageRepresentation = iconImageRepresentation { - iconArguments = TransformImageArguments(corners: ImageCorners( radius: iconSize.width / 2), imageSize: iconImageRepresentation.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: NSEdgeInsets()) - icon = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation]) - } else { - let fileName: String = file.fileName ?? "" - - var fileExtension: String? - if let range = fileName.range(of: ".", options: [.backwards]) { - fileExtension = fileName.substring(from: range.upperBound).lowercased() - } - docIcon = extensionImage(fileExtension: fileExtension ?? "file") - - - if let fileExtension = fileExtension { - docTitle = NSAttributedString.initialize(string: fileExtension, color: theme.colors.text, font: .medium(.text)) - } + var fileExtension: String = "file" + if let range = fileName.range(of: ".", options: [.backwards]) { + fileExtension = fileName[range.upperBound...].lowercased() + } + if fileExtension.length > 5 { + fileExtension = "file" } + docIcon = extensionImage(fileExtension: fileExtension) + + docTitle = NSAttributedString.initialize(string: fileExtension, color: theme.colors.text, font: .medium(.text)) + } + super.init(initialSize,interface,object, viewType: viewType) } - override func menuItems() -> Signal<[ContextMenuItem], Void> { - let signal = super.menuItems() - let account = self.account + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + let signal = super.menuItems(in: location) + let context = self.interface.context if let file = self.file { - return signal |> mapToSignal { items -> Signal<[ContextMenuItem], Void> in + return signal |> mapToSignal { items -> Signal<[ContextMenuItem], NoError> in var items = items - return account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue |> map {data in + return context.account.postbox.mediaBox.resourceData(file.resource) |> deliverOnMainQueue |> map {data in if data.complete { - items.append(ContextMenuItem(tr(.contextCopyMedia), handler: { - saveAs(file, account: account) + items.append(ContextMenuItem(L10n.contextCopyMedia, handler: { + saveAs(file, account: context.account) })) - items.append(ContextMenuItem(tr(.contextShowInFinder), handler: { - showInFinder(file, account: account) + items.append(ContextMenuItem(L10n.contextShowInFinder, handler: { + showInFinder(file, account: context.account) })) } return items @@ -98,13 +99,11 @@ class PeerMediaFileRowItem: PeerMediaRowItem { } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - - nameLayout.measure(width: width - contentInset.left - contentInset.right) - actionLayout.measure(width: width - contentInset.left - contentInset.right) - actionLayoutLocal.measure(width: width - contentInset.left - contentInset.right) - contentSize = NSMakeSize(width, 50) - - return super.makeSize(width, oldWidth: oldWidth) + let success = super.makeSize(width, oldWidth: oldWidth) + nameLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + actionLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + actionLayoutLocal.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + return success } override func viewClass() -> AnyClass { @@ -116,7 +115,7 @@ class PeerMediaFileRowView : PeerMediaRowView { var nameView:TextView = TextView() var actionView:TextView = TextView() - var imageView:TransformImageView = TransformImageView(frame:NSMakeRect(10, 5, 40, 40)) + var imageView:TransformImageView = TransformImageView(frame:NSMakeRect(0, 0, 40, 40)) private var downloadStatusControl:ImageView? private var downloadProgressView:LinearProgressControl? @@ -127,10 +126,9 @@ class PeerMediaFileRowView : PeerMediaRowView { private let fetchDisposable = MetaDisposable() required init(frame frameRect: NSRect) { - nameView.userInteractionEnabled = false nameView.isSelectable = false + nameView.userInteractionEnabled = false actionView.isSelectable = false - super.init(frame: frameRect) addSubview(imageView) addSubview(nameView) @@ -140,43 +138,38 @@ class PeerMediaFileRowView : PeerMediaRowView { func cancel() -> Void { - + cancelFetching() } func delete() -> Void { if let item = item as? PeerMediaFileRowItem { - _ = item.account.postbox.modify({ modifier -> Void in - modifier.deleteMessages([item.message.id]) - }).start() + let mediaBox = item.interface.context.account.postbox.mediaBox + let messageId = item.message.id + _ = item.interface.context.account.postbox.transaction { transaction -> Void in + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: [messageId]) + }.start() } } func cancelFetching() { if let item = item as? PeerMediaFileRowItem, let file = item.file { - chatMessageFileCancelInteractiveFetch(account: item.account, file: file) + messageMediaFileCancelInteractiveFetch(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file)) } } func open() -> Void { if let item = item as? PeerMediaFileRowItem, let file = item.file { if file.isGraphicFile { - showChatGallery(account: item.account, message: item.message, item.table, nil) + showChatGallery(context: item.interface.context, message: item.message, item.table, nil) } else { - QuickLookPreview.current.show(account: item.account, with: file, stableId:item.message.chatStableId, item.table) + QuickLookPreview.current.show(context: item.interface.context, with: file, stableId:item.message.chatStableId, item.table) } } } func fetch() -> Void { if let item = item as? PeerMediaFileRowItem, let file = item.file { - let account = item.account - fetchDisposable.set((chatMessageFileInteractiveFetched(account: item.account, file: file) |> mapToSignal { source -> Signal in - if source == .remote { - return copyToDownloads(file, account: account) - } else { - return .single(Void()) - } - }).start()) + fetchDisposable.set(messageMediaFileInteractiveFetched(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file)).start()) } } @@ -210,6 +203,15 @@ class PeerMediaFileRowView : PeerMediaRowView { if imageView._mouseInside() { executeInteraction(true) return + } else if let status = self.fetchStatus { + switch status { + case .Remote: + executeInteraction(true) + case .Fetching: + executeInteraction(true) + default: + break + } } } super.mouseUp(with: event) @@ -218,19 +220,20 @@ class PeerMediaFileRowView : PeerMediaRowView { override func layout() { super.layout() if let item = item as? PeerMediaRowItem { - - downloadProgressView?.frame = NSMakeRect(item.contentInset.left,frame.height - 4,frame.width - item.contentInset.left - item.contentInset.right,4) - + if let downloadProgressView = downloadProgressView { + downloadProgressView.frame = NSMakeRect(item.separatorOffset, containerView.frame.height - 4, containerView.frame.width - item.separatorOffset - item.viewType.innerInset.right, 4) + } if let downloadStatusControl = downloadStatusControl { + downloadStatusControl.setFrameOrigin(NSMakePoint(item.contentInset.left, contentView.frame.height - 4 - downloadStatusControl.frame.height)) actionView.setFrameOrigin(item.contentInset.left + downloadStatusControl.frame.width + 2.0,actionView.frame.minY) } else { - actionView.setFrameOrigin(item.contentInset.left,actionView.frame.minY) + actionView.setFrameOrigin(item.contentInset.left, actionView.frame.minY) } } } - override var interactionContentView: NSView { + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return imageView } @@ -241,7 +244,19 @@ class PeerMediaFileRowView : PeerMediaRowView { fatalError("init(coder:) has not been implemented") } + override func updateSelectingMode(with selectingMode: Bool, animated: Bool = false) { + super.updateSelectingMode(with: selectingMode, animated: animated) + if let status = fetchStatus { + if case .Local = status { + self.actionView.userInteractionEnabled = !selectingMode + } else { + self.actionView.userInteractionEnabled = false + } + } + } + override func set(item: TableRowItem, animated: Bool) { + let previous = self.item as? PeerMediaFileRowItem super.set(item: item, animated: animated) statusDisposable.set(nil) @@ -251,14 +266,14 @@ class PeerMediaFileRowView : PeerMediaRowView { nameView.update(item.nameLayout, origin: NSMakePoint(item.contentInset.left, item.contentInset.top + 2)) actionView.update(item.actionLayout, origin: NSMakePoint(item.contentInset.left, item.contentSize.height - item.actionLayout.layoutSize.height - item.contentInset.bottom - 2)) - let updateIconImageSignal:Signal<(TransformImageArguments) -> DrawingContext?,NoError> + let updateIconImageSignal:Signal if let icon = item.icon { - updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photo: icon, scale: backingScaleFactor, small:true) + updateIconImageSignal = chatWebpageSnippetPhoto(account: item.interface.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: icon), scale: backingScaleFactor, small:true) } else { updateIconImageSignal = .complete() } - imageView.setSignal(account: item.account, signal: updateIconImageSignal) + imageView.setSignal( updateIconImageSignal) if let arguments = item.iconArguments { imageView.set(arguments: arguments) } else { @@ -269,9 +284,9 @@ class PeerMediaFileRowView : PeerMediaRowView { var updatedStatusSignal: Signal? var updatedFetchControls: FetchControls? - let account = item.account + let context = item.interface.context if let file = item.file { - updatedStatusSignal = chatMessageFileStatus(account: account, file: file) + updatedStatusSignal = chatMessageFileStatus(account: context.account, file: file, approximateSynchronousValue: false) updatedFetchControls = FetchControls(fetch: { [weak self] in self?.executeInteraction(true) }) @@ -283,31 +298,60 @@ class PeerMediaFileRowView : PeerMediaRowView { self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { strongSelf.fetchStatus = status + if case .Local = status { + strongSelf.actionView.userInteractionEnabled = (strongSelf.item as? PeerMediaRowItem)?.interface.presentation.state != .selecting + } else { + strongSelf.actionView.userInteractionEnabled = false + } + + let initStatusControlIfNeeded = { [weak strongSelf] in + if let strongSelf = strongSelf, strongSelf.downloadStatusControl == nil { + strongSelf.downloadStatusControl = ImageView(frame:NSMakeRect(0, 0, theme.icons.peerMediaDownloadFileStart.backingSize.width, theme.icons.peerMediaDownloadFileStart.backingSize.height)) + strongSelf.downloadStatusControl?.animates = true + strongSelf.addSubview(strongSelf.downloadStatusControl!) + strongSelf.needsLayout = true + } + } let initDownloadControlIfNeeded = { [weak strongSelf] in - if let strongSelf = strongSelf, strongSelf.downloadStatusControl == nil { - strongSelf.downloadStatusControl = ImageView(frame:NSMakeRect(item.contentInset.left, strongSelf.frame.height - theme.icons.peerMediaDownloadFileStart.backingSize.height - item.contentInset.bottom - 4.0, theme.icons.peerMediaDownloadFileStart.backingSize.width, theme.icons.peerMediaDownloadFileStart.backingSize.height)) - strongSelf.addSubview(strongSelf.downloadStatusControl!) - + if let strongSelf = strongSelf { + if strongSelf.downloadStatusControl == nil { + strongSelf.downloadStatusControl = ImageView(frame:NSMakeRect(0, 0, theme.icons.peerMediaDownloadFileStart.backingSize.width, theme.icons.peerMediaDownloadFileStart.backingSize.height)) + strongSelf.downloadStatusControl?.animates = true + strongSelf.addSubview(strongSelf.downloadStatusControl!) + } if strongSelf.downloadProgressView == nil { strongSelf.downloadProgressView = LinearProgressControl() - strongSelf.addSubview(strongSelf.downloadProgressView!) + strongSelf.downloadProgressView?.cornerRadius = 2.0 + strongSelf.containerView.addSubview(strongSelf.downloadProgressView!) } - strongSelf.downloadProgressView?.style = ControlStyle(foregroundColor:theme.colors.blueUI) + strongSelf.downloadProgressView?.style = ControlStyle(foregroundColor:theme.colors.accent) strongSelf.needsLayout = true } - } - let deinitDownloadControls = {[weak strongSelf] in + let deinitDownloadControls = { [weak strongSelf] in if let strongSelf = strongSelf { - strongSelf.downloadProgressView?.removeFromSuperview() - strongSelf.downloadProgressView = nil + if let downloadProgressView = strongSelf.downloadProgressView { + strongSelf.downloadProgressView = nil + downloadProgressView.set(progress: 1.0) + downloadProgressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak downloadProgressView] _ in + downloadProgressView?.removeFromSuperview() + }) + } strongSelf.downloadStatusControl?.removeFromSuperview() strongSelf.downloadStatusControl = nil } strongSelf?.needsLayout = true } + + let deinitProgressControl = { [weak strongSelf] in + if let strongSelf = strongSelf { + strongSelf.downloadProgressView?.removeFromSuperview() + strongSelf.downloadProgressView = nil + } + strongSelf?.needsLayout = true + } switch status { @@ -315,7 +359,8 @@ class PeerMediaFileRowView : PeerMediaRowView { deinitDownloadControls() strongSelf.actionView.update(item.actionLayoutLocal) case .Remote: - initDownloadControlIfNeeded() + deinitProgressControl() + initStatusControlIfNeeded() strongSelf.downloadStatusControl?.image = theme.icons.peerMediaDownloadFileStart strongSelf.actionView.update(item.actionLayout) break diff --git a/Telegram-Mac/PeerMediaGifsController.swift b/Telegram-Mac/PeerMediaGifsController.swift new file mode 100644 index 0000000000..104eb7cf2b --- /dev/null +++ b/Telegram-Mac/PeerMediaGifsController.swift @@ -0,0 +1,285 @@ +// +// PeerMediaGifsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/05/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import Postbox +import SwiftSignalKit +import SyncCore + +private final class PeerMediaGifsArguments { + let context: AccountContext + let chatInteraction: ChatInteraction + let gallerySupplyment: InteractionContentViewProtocol + let openMessage: (Message)->Void + let menuItems:(Message, NSView)->Signal<[ContextMenuItem], NoError> + init(context: AccountContext, chatInteraction: ChatInteraction, gallerySupplyment: InteractionContentViewProtocol, openMessage: @escaping(Message)->Void, menuItems: @escaping(Message, NSView)->Signal<[ContextMenuItem], NoError>) { + self.context = context + self.gallerySupplyment = gallerySupplyment + self.chatInteraction = chatInteraction + self.menuItems = menuItems + self.openMessage = openMessage + } +} + + +final class PeerMediaGifsView : View { + let tableView: TableView = TableView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tableView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func layout() { + super.layout() + tableView.frame = bounds + } +} + +private func mediaEntires(state: PeerMediaGifsState, initialSize: NSSize) -> [InputContextEntry] { + + let values = makeChatGridMediaEnties(state.messages, initialSize: NSMakeSize(initialSize.width, 100)) + + var wrapped:[InputContextEntry] = [] + for value in values { + wrapped.append(InputContextEntry.contextMediaResult(nil, value, Int64(arc4random()) | ((Int64(wrapped.count) << 40)))) + } + + return wrapped +} + + +private struct PeerMediaGifsState : Equatable { + let isLoading: Bool + let messages:[Message] + init(isLoading: Bool, messages: [Message]) { + self.isLoading = isLoading + self.messages = messages.reversed() + } + func withAppendMessages(_ collection: [Message]) -> PeerMediaGifsState { + var messages = self.messages + messages.append(contentsOf: collection) + return PeerMediaGifsState(isLoading: self.isLoading, messages: messages) + } + func withUpdatedMessages(_ collection: [Message]) -> PeerMediaGifsState { + return PeerMediaGifsState(isLoading: self.isLoading, messages: collection) + } + func withUpdatedLoading(_ isLoading: Bool) -> PeerMediaGifsState { + return PeerMediaGifsState(isLoading: isLoading, messages: self.messages) + } +} + +private final class PeerMediaGifsSupplyment : InteractionContentViewProtocol { + private weak var tableView: TableView? + init(tableView: TableView) { + self.tableView = tableView + } + + func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { + if let stableId = stableId.base as? ChatHistoryEntryId, let tableView = tableView { + switch stableId { + case let .message(message): + var found: NSView? = nil + tableView.enumerateItems { item -> Bool in + if let item = item as? ContextMediaRowItem { + if item.contains(message.id) { + found = item.view?.interactionContentView(for: message.id, animateIn: animateIn) + } + } + return found == nil + } + return found + default: + break + } + } + return nil + } + func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + + } + func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + if let stableId = stableId.base as? ChatHistoryEntryId, let tableView = tableView { + switch stableId { + case let .message(message): + tableView.enumerateItems { item -> Bool in + if let item = item as? PeerPhotosMonthItem { + if item.contains(message.id) { + item.view?.addAccesoryOnCopiedView(innerId: message.id, view: view) + return false + } + } + return true + } + default: + break + } + } + } + func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + return nil + } + func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + + } +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], animated: Bool, initialSize:NSSize, arguments: PeerMediaGifsArguments) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + switch entry.entry { + case let .contextMediaResult(_, row, index): + return ContextMediaRowItem(initialSize, row, index, arguments.context, ContextMediaArguments(openMessage: arguments.openMessage, messageMenuItems: arguments.menuItems)) + default: + fatalError("not supported") + } + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: animated) +} + +class PeerMediaGifsController: TelegramGenericViewController { + + private let peerId: PeerId + private let historyDisposable = MetaDisposable() + private let disposable = MetaDisposable() + private let chatInteraction: ChatInteraction + private let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + init(_ context: AccountContext, chatInteraction: ChatInteraction, peerId: PeerId) { + self.peerId = peerId + self.chatInteraction = chatInteraction + super.init(context) + } + + deinit { + historyDisposable.dispose() + } + + override func viewDidLoad() { + super.viewDidLoad() + + let context = self.context + let peerId = self.peerId + let initialSize = self.atomicSize + let chatInteraction = self.chatInteraction + + + self.genericView.tableView.emptyItem = PeerMediaEmptyRowItem(NSZeroSize, tags: .gif) + + let perPageCount:()->Int = { + var rowCount:Int = 4 + var perWidth: CGFloat = 0 + let blockWidth = min(600, initialSize.with { $0.width } - 60) + while true { + let maximum = blockWidth - 7 - 7 - CGFloat(rowCount * 2) + perWidth = maximum / CGFloat(rowCount) + if perWidth >= 90 { + break + } else { + rowCount -= 1 + } + } + return Int((initialSize.with { $0.height } / perWidth) * CGFloat(rowCount) + CGFloat(rowCount)) + } + + var requestCount = perPageCount() + 20 + + let location: ValuePromise = ValuePromise(.Initial(count: requestCount), ignoreRepeated: true) + + let initialState = PeerMediaGifsState(isLoading: false, messages: []) + let state: ValuePromise = ValuePromise() + let stateValue: Atomic = Atomic(value: initialState) + let updateState:((PeerMediaGifsState)->PeerMediaGifsState) -> Void = { f in + state.set(stateValue.modify(f)) + } + + let supplyment = PeerMediaGifsSupplyment(tableView: genericView.tableView) + + let arguments = PeerMediaGifsArguments(context: context, chatInteraction: chatInteraction, gallerySupplyment: supplyment, openMessage: { message in + showChatGallery(context: context, message: message, supplyment, nil, type: .history, reversed: true) + }, menuItems: { message, view in + return .single([]) + }) + + + let applyHole:() -> Void = { + location.set(.Initial(count: requestCount)) + } + + let history = location.get() |> mapToSignal { location in + return chatHistoryViewForLocation(location, account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: [.gif]) + } + + self.historyDisposable.set(history.start(next: { update in + + let isLoading: Bool + let view: MessageHistoryView? + let updateType: ChatHistoryViewUpdateType + switch update { + case let .Loading(_, ut): + view = nil + isLoading = true + updateType = ut + case let .HistoryView(values): + view = values.view + isLoading = values.view.isLoading + updateType = values.type + } + + switch updateType { + case let .Generic(type: type): + switch type { + case .FillHole: + DispatchQueue.main.async(execute: applyHole) + default: + break + } + default: + break + } + let messages = view?.entries.map { value in + return value.message + } ?? [] + + updateState { + $0.withUpdatedMessages(messages).withUpdatedLoading(false).withUpdatedLoading(isLoading) + } + })) + + let previous = self.previous + + let transition: Signal = combineLatest(queue: prepareQueue, state.get(), appearanceSignal) |> mapToSignal { state, appearance in + let entries = mediaEntires(state: state, initialSize: initialSize.with { $0 }).map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + return .single(prepareTransition(left: previous.swap(entries), right: entries, animated: true, initialSize: initialSize.with { $0 }, arguments: arguments)) + } |> deliverOnMainQueue + + + + disposable.set(transition.start(next: { [weak self] transition in + guard let `self` = self else { + return + } + self.genericView.tableView.merge(with: transition) + self.readyOnce() + })) + + genericView.tableView.setScrollHandler { position in + switch position.direction { + case .bottom: + requestCount += perPageCount() * 10 + location.set(.Initial(count: requestCount)) + default: + break + } + } + } +} diff --git a/Telegram-Mac/PeerMediaGridController.swift b/Telegram-Mac/PeerMediaGridController.swift deleted file mode 100644 index 0a1b991997..0000000000 --- a/Telegram-Mac/PeerMediaGridController.swift +++ /dev/null @@ -1,441 +0,0 @@ -// -// PeerMediaGridController.swift -// Telegram-Mac -// -// Created by keepcoder on 26/10/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac -import TGUIKit - -public enum ChatHistoryNodeHistoryState: Equatable { - case loading - case loaded(isEmpty: Bool) - - public static func ==(lhs: ChatHistoryNodeHistoryState, rhs: ChatHistoryNodeHistoryState) -> Bool { - switch lhs { - case .loading: - if case .loading = rhs { - return true - } else { - return false - } - case let .loaded(isEmpty): - if case .loaded(isEmpty) = rhs { - return true - } else { - return false - } - } - } -} - -struct ChatHistoryGridViewTransition { - let historyView: ChatHistoryView - let topOffsetWithinMonth: Int - let deleteItems: [Int] - let insertItems: [GridNodeInsertItem] - let updateItems: [GridNodeUpdateItem] - let scrollToItem: GridNodeScrollToItem? - let stationaryItems: GridNodeStationaryItems -} - -struct ChatHistoryViewTransitionInsertEntry { - let index: Int - let previousIndex: Int? - let entry: ChatHistoryEntry - let directionHint: ListViewItemOperationDirectionHint? -} - -struct ChatHistoryViewTransitionUpdateEntry { - let index: Int - let previousIndex: Int - let entry: ChatHistoryEntry - let directionHint: ListViewItemOperationDirectionHint? -} - -private func mappedInsertEntries(account: Account, peerId: PeerId, controllerInteraction: ChatInteraction, entries: [ChatHistoryViewTransitionInsertEntry]) -> [GridNodeInsertItem] { - return entries.map { entry -> GridNodeInsertItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _): - return GridNodeInsertItem(index: entry.index, item: GridMessageItem(account: account, message: message, chatInteraction: controllerInteraction), previousIndex: entry.previousIndex) - case .HoleEntry: - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - case .UnreadEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - default: - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - } - } -} - -private func mappedUpdateEntries(account: Account, peerId: PeerId, controllerInteraction: ChatInteraction, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [GridNodeUpdateItem] { - return entries.map { entry -> GridNodeUpdateItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _): - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridMessageItem(account: account, message: message, chatInteraction: controllerInteraction)) - case .HoleEntry: - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - case .UnreadEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - default: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - } - } -} - -private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerId, controllerInteraction: ChatInteraction, transition: ChatHistoryViewTransition, from: ChatHistoryView?) -> ChatHistoryGridViewTransition { - var mappedScrollToItem: GridNodeScrollToItem? - if let scrollToItem = transition.scrollToItem { - let mappedPosition: GridNodeScrollToItemPosition - switch scrollToItem.position { - case .Top: - mappedPosition = .top - case .Center: - mappedPosition = .center - case .Bottom: - mappedPosition = .bottom - } - let scrollTransition: ContainedViewLayoutTransition - if scrollToItem.animated { - switch scrollToItem.curve { - case .Default: - scrollTransition = .animated(duration: 0.3, curve: .easeInOut) - case let .Spring(duration): - scrollTransition = .animated(duration: duration, curve: .spring) - } - } else { - scrollTransition = .immediate - } - let directionHint: GridNodePreviousItemsTransitionDirectionHint - switch scrollToItem.directionHint { - case .Up: - directionHint = .up - case .Down: - directionHint = .down - } - mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition, transition: scrollTransition, directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) - } - - var stationaryItems: GridNodeStationaryItems = .none - if let previousView = from { - if let stationaryRange = transition.stationaryItemRange { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - if i >= stationaryRange.0 && i <= stationaryRange.1 { - fromStableIds.insert(previousView.filteredEntries[i].entry.stableId) - } - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } else { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - fromStableIds.insert(previousView.filteredEntries[i].entry.stableId) - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } - } - - - return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: 0, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) -} - - - - -private func mappedInsertEntries(account: Account, chatInteraction: ChatInteraction, entries: [(Int,ChatHistoryEntry,Int?)]) -> [GridNodeInsertItem] { - return entries.map { entry -> GridNodeInsertItem in - switch entry.1 { - case let .MessageEntry(message, _, _, _, _): - return GridNodeInsertItem(index: entry.0, item: GridMessageItem(account: account, message: message, chatInteraction: chatInteraction), previousIndex: entry.2) - case .HoleEntry: - return GridNodeInsertItem(index: entry.0, item: GridHoleItem(), previousIndex: entry.2) - case .UnreadEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.0, item: GridHoleItem(), previousIndex: entry.2) - case .DateEntry: - return GridNodeInsertItem(index: entry.0, item: GridHoleItem(), previousIndex: entry.2) - default: - fatalError() - } - } -} - -private func mappedUpdateEntries(account: Account, chatInteraction: ChatInteraction, entries: [(Int,ChatHistoryEntry,Int)]) -> [GridNodeUpdateItem] { - return entries.map { entry -> GridNodeUpdateItem in - switch entry.1 { - case let .MessageEntry(message, _, _, _, _): - return GridNodeUpdateItem(index: entry.0, previousIndex: entry.2, item: GridMessageItem(account: account, message: message, chatInteraction: chatInteraction)) - case .HoleEntry: - return GridNodeUpdateItem(index: entry.0, previousIndex: entry.2, item: GridHoleItem()) - case .UnreadEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.0, previousIndex: entry.2, item: GridHoleItem()) - case .DateEntry: - return GridNodeUpdateItem(index: entry.0, previousIndex: entry.2, item: GridHoleItem()) - default: - fatalError() - } - } -} - - - -private func itemSizeForContainerLayout(size: CGSize) -> CGSize { - let side = floor(size.width / 4.0) - return CGSize(width: side, height: side) -} - -class PeerMediaGridView : View { - let grid:GridNode - var emptyView:PeerMediaEmptyRowView - var emptyItem:PeerMediaEmptyRowItem = PeerMediaEmptyRowItem(NSZeroSize, tags: .photoOrVideo) - required init(frame frameRect: NSRect) { - grid = GridNode(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) - emptyView = PeerMediaEmptyRowView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) - emptyView.set(item: emptyItem, animated: false) - super.init(frame: frameRect) - addSubview(grid) - addSubview(emptyView) - update(hasEntities: true) - } - - func update(hasEntities: Bool) { - grid.isHidden = !hasEntities - emptyView.isHidden = hasEntities - } - - override func layout() { - super.layout() - grid.frame = bounds - emptyView.frame = bounds - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class PeerMediaGridController: GenericViewController { - - private let account: Account - private let peerId: PeerId - private let messageId: MessageId? - private let tagMask: MessageTags? - private let previousView = Atomic(value: nil) - - public let historyState = ValuePromise() - private var currentHistoryState: ChatHistoryNodeHistoryState? - private var enqueuedHistoryViewTransition: (ChatHistoryGridViewTransition, () -> Void)? - var layoutActionOnViewTransition: ((ChatHistoryGridViewTransition) -> (ChatHistoryGridViewTransition, ListViewUpdateSizeAndInsets?))? - - private var historyView: ChatHistoryView? - - private let historyDisposable = MetaDisposable() - - private let _chatHistoryLocation = ValuePromise(ignoreRepeated: true) - private var chatHistoryLocation: Signal { - return self._chatHistoryLocation.get() - } - - - private var requestCount:Int { - return Int((frame.width / 100) * (frame.height / 100)) * 4 - } - - func enableScroll() -> Void { - genericView.grid.visibleItemsUpdated = { [weak self] visibleItems in - - if let strongSelf = self, let historyView = strongSelf.historyView, let top = visibleItems.top, let bottom = visibleItems.bottom { - if top.0 < 5 && historyView.originalView.laterId != nil { - // - let lastEntry = historyView.filteredEntries[min(max(historyView.filteredEntries.count - 1 - top.0, 0), historyView.filteredEntries.count - 1)] - let location = ChatHistoryLocation.Navigation(index: lastEntry.entry.index, anchorIndex: historyView.originalView.anchorIndex) - - strongSelf._chatHistoryLocation.set(location) - strongSelf.disableScroll() - } else if bottom.0 >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - let firstEntry = historyView.filteredEntries[min(max(historyView.filteredEntries.count - 1 - bottom.0, 0), historyView.filteredEntries.count - 1)] - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: firstEntry.entry.index, anchorIndex: historyView.originalView.anchorIndex)) - strongSelf.disableScroll() - } - } - } - } - - func disableScroll() -> Void { - genericView.grid.visibleItemsUpdated = nil - } - - override func viewDidLoad() { - super.viewDidLoad() - - genericView.grid.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: frame.width, height: frame.height), insets: NSEdgeInsets(), preloadSize: self.bounds.width, type: .fixed(itemSize: CGSize(width: 120, height: 120), lineSpacing: 4)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - self._chatHistoryLocation.set(ChatHistoryLocation.Initial(count: requestCount)) - - - } - - - - public init(account: Account, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, chatInteraction: ChatInteraction) { - self.account = account - self.peerId = peerId - self.messageId = messageId - self.tagMask = tagMask - - super.init() - - let historyViewUpdate = self.chatHistoryLocation - |> distinctUntilChanged - |> mapToSignal { (location) in - return chatHistoryViewForLocation(location, account: account, peerId: peerId, fixedCombinedReadState: nil, tagMask: tagMask) - } - - let previousView = self.previousView - - let historyViewTransition = combineLatest(historyViewUpdate, appearanceSignal) |> mapToQueue { [weak self] update, appearance -> Signal in - switch update { - case .Loading: - Queue.mainQueue().async { [weak self] in - if let strongSelf = self { - let historyState: ChatHistoryNodeHistoryState = .loading - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - } - } - return .complete() - case let .HistoryView(view, type, scrollPosition, _): - let reason: ChatHistoryViewTransitionReason - var prepareOnMainQueue = false - switch type { - case let .Initial(fadeIn): - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: fadeIn) - prepareOnMainQueue = !fadeIn - case let .Generic(genericType): - switch genericType { - case .InitialUnread: - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: false) - case .Generic: - reason = ChatHistoryViewTransitionReason.InteractiveChanges - case .UpdateVisible: - reason = ChatHistoryViewTransitionReason.Reload - case let .FillHole(insertions, deletions): - reason = ChatHistoryViewTransitionReason.HoleChanges(filledHoleDirections: insertions, removeHoleDirections: deletions) - } - } - - let processedView = ChatHistoryView(originalView: view, filteredEntries: messageEntries(view.entries).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)})) - let previous = previousView.swap(processedView) - - - - - return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, account: account, peerId: peerId, controllerInteraction: chatInteraction, scrollPosition: nil, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: chatInteraction, transition: $0, from: previous) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : prepareQueue) - } - } - - let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in - if let strongSelf = self { - return strongSelf.enqueueHistoryViewTransition(transition) - } - return .complete() - } - - self.historyDisposable.set(appliedTransition.start()) - - - - - } - - - - - private func dequeueHistoryViewTransition() { - readyOnce() - if let (transition, completion) = self.enqueuedHistoryViewTransition { - self.enqueuedHistoryViewTransition = nil - - let completion: (GridNodeDisplayedItemRange) -> Void = { [weak self] visibleRange in - if let strongSelf = self { - strongSelf.historyView = transition.historyView - - if let range = visibleRange.loadedRange { - strongSelf.account.postbox.updateMessageHistoryViewVisibleRange(transition.historyView.originalView.id, earliestVisibleIndex: transition.historyView.filteredEntries[transition.historyView.filteredEntries.count - 1 - range.upperBound].entry.index, latestVisibleIndex: transition.historyView.filteredEntries[transition.historyView.filteredEntries.count - 1 - range.lowerBound].entry.index) - } - - let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - - completion() - } - } - - let updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: frame.width, height: frame.height), insets: NSEdgeInsets(), preloadSize: self.frame.width, type: .fixed(itemSize: CGSize(width: 120, height: 120), lineSpacing: 4)), transition: .immediate) - - self.genericView.grid.transaction(GridNodeTransaction(deleteItems: transition.deleteItems, insertItems: transition.insertItems, updateItems: transition.updateItems, scrollToItem: transition.scrollToItem, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.topOffsetWithinMonth), completion: completion) - - genericView.update(hasEntities: !genericView.grid.isEmpty) - } - } - - private func enqueueHistoryViewTransition(_ transition: ChatHistoryGridViewTransition) -> Signal { - return Signal { [weak self] subscriber in - - if let strongSelf = self { - if let _ = strongSelf.enqueuedHistoryViewTransition { - preconditionFailure() - } - - strongSelf.enqueuedHistoryViewTransition = (transition, { - subscriber.putCompletion() - }) - - strongSelf.dequeueHistoryViewTransition() - - strongSelf.enableScroll() - - } else { - subscriber.putCompletion() - } - - return EmptyDisposable - - } |> runOn(Queue.mainQueue()) - } - - deinit { - self.historyDisposable.dispose() - } - -} diff --git a/Telegram-Mac/PeerMediaGroupPeersController.swift b/Telegram-Mac/PeerMediaGroupPeersController.swift new file mode 100644 index 0000000000..a69bfdb09d --- /dev/null +++ b/Telegram-Mac/PeerMediaGroupPeersController.swift @@ -0,0 +1,563 @@ +// +// PeerMediaGroupPeersController.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/03/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + +private final class GroupPeersArguments { + let context: AccountContext + let removePeer: (PeerId)->Void + let promote:(ChannelParticipant)->Void + let restrict:(ChannelParticipant)->Void + let showMore:()->Void + init(context: AccountContext, removePeer:@escaping(PeerId)->Void, showMore: @escaping()->Void, promote:@escaping(ChannelParticipant)->Void, restrict:@escaping(ChannelParticipant)->Void) { + self.context = context + self.removePeer = removePeer + self.promote = promote + self.restrict = restrict + self.showMore = showMore + } + + func peerInfo(_ peerId:PeerId) { + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + } +} + +private struct GroupPeersState : Equatable { + var temporaryParticipants: [TemporaryParticipant] + var successfullyAddedParticipantIds: Set + var removingParticipantIds: Set + var hasShowMoreButton: Bool? +} + +private func _id_peer_id(_ id: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_peer_id_\(id)") +} + +extension GroupInfoEntry : Equatable { + static func ==(lhs: GroupInfoEntry, rhs: GroupInfoEntry) -> Bool { + return lhs.isEqual(to: rhs) + } +} + +private func groupPeersEntries(state: GroupPeersState, isEditing: Bool, view: PeerView, inputActivities: [PeerId: PeerInputActivity], channelMembers: [RenderedChannelParticipant], arguments: GroupPeersArguments) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index:Int32 = 0 + + var usersBlock:[GroupInfoEntry] = [] + + + func applyBlock(_ block:[GroupInfoEntry]) { + var block = block + for (i, item) in block.enumerated() { + var viewType = bestGeneralViewType(block, for: i) + if i == 0 { + if block.count > 1 { + viewType = .innerItem + } else { + viewType = .lastItem + } + } + viewType = viewType.withUpdatedInsets(NSEdgeInsetsMake(16, 18, 16, 18)) + block[i] = item.withUpdatedViewType(viewType) + + } + for item in block { + switch item { + case let .member(_, _, _, peer, presence, inputActivity, memberStatus, editing, menuItems, enabled, viewType): + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_peer_id(peer!.id), equatable: InputDataEquatable(item), item: { initialSize, stableId in + let label: String + switch memberStatus { + case let .admin(rank): + label = rank + case .member: + label = "" + } + + var string:String = L10n.peerStatusRecently + var color:NSColor = theme.colors.grayText + + if let peer = peer as? TelegramUser, let botInfo = peer.botInfo { + string = botInfo.flags.contains(.hasAccessToChatHistory) ? L10n.peerInfoBotStatusHasAccess : L10n.peerInfoBotStatusHasNoAccess + } else if let presence = presence as? TelegramUserPresence { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) + } + + let interactionType:ShortPeerItemInteractionType + if let editing = editing { + + interactionType = .deletable(onRemove: { memberId in + arguments.removePeer(memberId) + }, deletable: editing.editable) + } else { + interactionType = .plain + } + + return ShortPeerRowItem(initialSize, peer: peer!, account: arguments.context.account, stableId: stableId, enabled: enabled, height: 36 + 16, photoSize: NSMakeSize(36, 36), titleStyle: ControlStyle(font: .medium(12.5), foregroundColor: theme.colors.text), statusStyle: ControlStyle(font: NSFont.normal(12.5), foregroundColor:color), status: string, inset: NSEdgeInsets(left: 0, right: 0), interactionType: interactionType, generalType: .context(label), viewType: viewType, action:{ + arguments.peerInfo(peer!.id) + }, contextMenuItems: { + return .single(menuItems) + }, inputActivity: inputActivity) + })) + index += 1 + case let .showMore(_, _, viewType): + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("_id_show_more"), equatable: nil, item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.peerInfoShowMore, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.showMore() + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4), inset: NSEdgeInsetsZero) + })) + index += 1 + default: + break + } + + } + // entries.append(contentsOf: block) + } + + + if let group = peerViewMainPeer(view) { + let access = group.groupAccess + + if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants, let group = group as? TelegramGroup { + + var updatedParticipants = participants.participants + let existingParticipantIds = Set(updatedParticipants.map { $0.peerId }) + + var peerPresences: [PeerId: PeerPresence] = view.peerPresences + var peers: [PeerId: Peer] = view.peers + var disabledPeerIds = state.removingParticipantIds + + if !state.temporaryParticipants.isEmpty { + for participant in state.temporaryParticipants { + if !existingParticipantIds.contains(participant.peer.id) { + updatedParticipants.append(.member(id: participant.peer.id, invitedBy: arguments.context.account.peerId, invitedAt: participant.timestamp)) + if let presence = participant.presence, peerPresences[participant.peer.id] == nil { + peerPresences[participant.peer.id] = presence + } + if peers[participant.peer.id] == nil { + peers[participant.peer.id] = participant.peer + } + disabledPeerIds.insert(participant.peer.id) + } + } + } + + let sortedParticipants = participants.participants.filter({peers[$0.peerId]?.displayTitle != nil}).sorted(by: { lhs, rhs in + let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence + let rhsPresence = view.peerPresences[rhs.peerId] as? TelegramUserPresence + + let lhsActivity = inputActivities[lhs.peerId] + let rhsActivity = inputActivities[rhs.peerId] + + if lhsActivity != nil && rhsActivity == nil { + return true + } else if rhsActivity != nil && lhsActivity == nil { + return false + } + + if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { + return lhsPresence.status > rhsPresence.status + } else if let _ = lhsPresence { + return true + } else if let _ = rhsPresence { + return false + } + + return lhs < rhs + }) + + for i in 0 ..< sortedParticipants.count { + if let peer = view.peers[sortedParticipants[i].peerId] { + let memberStatus: GroupInfoMemberStatus + if access.highlightAdmins { + switch sortedParticipants[i] { + case .admin: + memberStatus = .admin(rank: L10n.chatAdminBadge) + case .creator: + memberStatus = .admin(rank: L10n.chatOwnerBadge) + case .member: + memberStatus = .member + } + } else { + memberStatus = .member + } + + var canRestrict: Bool + if sortedParticipants[i].peerId == arguments.context.peerId { + canRestrict = false + } else { + switch group.role { + case .creator: + canRestrict = true + case .member: + switch sortedParticipants[i] { + case .creator, .admin: + canRestrict = false + case let .member(member): + if member.invitedBy == arguments.context.peerId { + canRestrict = true + } else { + canRestrict = false + } + } + case .admin: + switch sortedParticipants[i] { + case .creator, .admin: + canRestrict = false + case .member: + canRestrict = true + } + } + } + + let editing:ShortPeerDeleting? + + if isEditing { + let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i]) || (sortedParticipants[i].invitedBy == arguments.context.peerId && sortedParticipants[i].peerId != arguments.context.peerId) + editing = ShortPeerDeleting(editable: deletable) + } else { + editing = nil + } + + var menuItems: [ContextMenuItem] = [] + + if canRestrict { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuDelete, handler: { + arguments.removePeer(sortedParticipants[i].peerId) + })) + } + + usersBlock.append(.member(section: Int(sectionId), index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], activity: inputActivities[peer.id], memberStatus: memberStatus, editing: editing, menuItems: menuItems, enabled: !disabledPeerIds.contains(peer.id), viewType: .singleItem)) + } + } + } + + if let cachedGroupData = view.cachedData as? CachedChannelData, let channel = group as? TelegramChannel { + let participants = channelMembers + var updatedParticipants = participants + let existingParticipantIds = Set(updatedParticipants.map { $0.peer.id }) + var peerPresences: [PeerId: PeerPresence] = view.peerPresences + var peers: [PeerId: Peer] = view.peers + var disabledPeerIds = state.removingParticipantIds + + + if !state.temporaryParticipants.isEmpty { + for participant in state.temporaryParticipants { + if !existingParticipantIds.contains(participant.peer.id) { + updatedParticipants.append(RenderedChannelParticipant(participant: .member(id: participant.peer.id, invitedAt: participant.timestamp, adminInfo: nil, banInfo: nil, rank: nil), peer: participant.peer)) + if let presence = participant.presence, peerPresences[participant.peer.id] == nil { + peerPresences[participant.peer.id] = presence + } + if participant.peer.id == arguments.context.account.peerId { + peerPresences[participant.peer.id] = TelegramUserPresence(status: .present(until: Int32.max), lastActivity: Int32.max) + } + if peers[participant.peer.id] == nil { + peers[participant.peer.id] = participant.peer + } + disabledPeerIds.insert(participant.peer.id) + } + } + } + + + var sortedParticipants = participants.filter({!$0.peer.rawDisplayTitle.isEmpty}).sorted(by: { lhs, rhs in + let lhsPresence = lhs.presences[lhs.peer.id] as? TelegramUserPresence + let rhsPresence = rhs.presences[rhs.peer.id] as? TelegramUserPresence + + let lhsActivity = inputActivities[lhs.peer.id] + let rhsActivity = inputActivities[rhs.peer.id] + + if lhsActivity != nil && rhsActivity == nil { + return true + } else if rhsActivity != nil && lhsActivity == nil { + return false + } + + if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { + return lhsPresence.status > rhsPresence.status + } else if let _ = lhsPresence { + return true + } else if let _ = rhsPresence { + return false + } + + return lhs < rhs + }) + + if let hasShowMoreButton = state.hasShowMoreButton, hasShowMoreButton, let memberCount = cachedGroupData.participantsSummary.memberCount, memberCount > 100 { + sortedParticipants = Array(sortedParticipants.prefix(min(50, sortedParticipants.count))) + } + + for i in 0 ..< sortedParticipants.count { + let memberStatus: GroupInfoMemberStatus + if access.highlightAdmins { + switch sortedParticipants[i].participant { + case let .creator(_, rank): + memberStatus = .admin(rank: rank ?? L10n.chatOwnerBadge) + case let .member(_, _, adminRights, _, rank): + memberStatus = adminRights != nil ? .admin(rank: rank ?? L10n.chatAdminBadge) : .member + } + } else { + memberStatus = .member + } + + var canPromote: Bool + var canRestrict: Bool + if sortedParticipants[i].peer.id == arguments.context.peerId { + canPromote = false + canRestrict = false + } else { + switch sortedParticipants[i].participant { + case .creator: + canPromote = false + canRestrict = false + case let .member(_, _, adminRights, bannedRights, _): + if channel.hasPermission(.addAdmins) { + canPromote = true + } else { + canPromote = false + } + if channel.hasPermission(.banMembers) { + canRestrict = true + } else { + canRestrict = false + } + if canPromote { + if let bannedRights = bannedRights { + if bannedRights.restrictedBy != arguments.context.peerId && !channel.flags.contains(.isCreator) { + canPromote = false + } + } + } + if canRestrict { + if let adminRights = adminRights { + if adminRights.promotedBy != arguments.context.peerId && !channel.flags.contains(.isCreator) { + canRestrict = false + } + } + } + } + } + + var menuItems:[ContextMenuItem] = [] + + + if canPromote { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuPromote, handler: { + arguments.promote(sortedParticipants[i].participant) + })) + } + if canRestrict { + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuRestrict, handler: { + arguments.restrict(sortedParticipants[i].participant) + })) + menuItems.append(ContextMenuItem(L10n.peerInfoGroupMenuDelete, handler: { + arguments.removePeer(sortedParticipants[i].peer.id) + })) + } + + let editing:ShortPeerDeleting? + + if isEditing, let group = group as? TelegramChannel { + let deletable:Bool = group.canRemoveParticipant(sortedParticipants[i].participant, accountId: arguments.context.account.peerId) + editing = ShortPeerDeleting(editable: deletable) + } else { + editing = nil + } + + + usersBlock.append(GroupInfoEntry.member(section: Int(sectionId), index: i, peerId: sortedParticipants[i].peer.id, peer: sortedParticipants[i].peer, presence: sortedParticipants[i].presences[sortedParticipants[i].peer.id], activity: inputActivities[sortedParticipants[i].peer.id], memberStatus: memberStatus, editing: editing, menuItems: menuItems, enabled: !disabledPeerIds.contains(sortedParticipants[i].peer.id), viewType: .singleItem)) + } + + if let hasShowMoreButton = state.hasShowMoreButton, hasShowMoreButton, let memberCount = cachedGroupData.participantsSummary.memberCount, memberCount > 100 { + usersBlock.append(.showMore(section: GroupInfoSection.members.rawValue, index: sortedParticipants.count + 1, viewType: .singleItem)) + } + } + + } + + + applyBlock(usersBlock) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 +// + return entries +} + +func PeerMediaGroupPeersController(context: AccountContext, peerId: PeerId, editing: Signal) -> InputDataController { + + + let initialState = GroupPeersState(temporaryParticipants: [], successfullyAddedParticipantIds: Set(), removingParticipantIds: Set(), hasShowMoreButton: true) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((GroupPeersState) -> GroupPeersState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let actionsDisposable = DisposableSet() + + + var loadMoreControl: PeerChannelMemberCategoryControl? + + let channelMembersPromise = Promise<[RenderedChannelParticipant]>() + + let inputActivity = context.account.peerInputActivities(peerId: peerId) + |> map { activities -> [PeerId : PeerInputActivity] in + return activities.reduce([:], { (current, activity) -> [PeerId : PeerInputActivity] in + var current = current + current[activity.0] = activity.1 + return current + }) + } + + if peerId.namespace == Namespaces.Peer.CloudChannel { + let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + channelMembersPromise.set(.single(state.list)) + }) + loadMoreControl = control + actionsDisposable.add(disposable) + } else { + channelMembersPromise.set(.single([])) + } + + let upgradeToSupergroup: (PeerId, @escaping () -> Void) -> Void = { upgradedPeerId, f in + let navigationController = context.sharedContext.bindings.rootNavigation() + + var chatController: ChatController? = ChatController(context: context, chatLocation: .peer(upgradedPeerId)) + + chatController!.navigationController = navigationController + chatController!.loadViewIfNeeded(navigationController.bounds) + + var signal = chatController!.ready.get() |> filter {$0} |> take(1) |> ignoreValues + + var controller: PeerInfoController? = PeerInfoController(context: context, peerId: upgradedPeerId) + + controller!.navigationController = navigationController + controller!.loadViewIfNeeded(navigationController.bounds) + + let mainSignal = combineLatest(controller!.ready.get(), controller!.ready.get()) |> map { $0 && $1 } |> filter {$0} |> take(1) |> ignoreValues + + signal = combineLatest(queue: .mainQueue(), signal, mainSignal) |> ignoreValues + + _ = signal.start(completed: { [weak navigationController] in + navigationController?.removeAll() + navigationController?.push(chatController!, false, style: .none) + navigationController?.push(controller!, false, style: .none) + + chatController = nil + controller = nil + }) + } + + + + let arguments = GroupPeersArguments(context: context, removePeer: { memberId in + + let signal = context.account.postbox.loadedPeerWithId(memberId) + |> deliverOnMainQueue + |> mapToSignal { peer -> Signal in + let result = ValuePromise() + result.set(true) + return result.get() + } + |> mapToSignal { value -> Signal in + if value { + updateState { state in + var state = state + for i in 0 ..< state.temporaryParticipants.count { + if state.temporaryParticipants[i].peer.id == memberId { + state.temporaryParticipants.remove(at: i) + break + } + } + state.successfullyAddedParticipantIds.remove(memberId) + state.removingParticipantIds.insert(memberId) + return state + } + + if peerId.namespace == Namespaces.Peer.CloudChannel { + return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)) + |> afterDisposed { + updateState { state in + var state = state + state.removingParticipantIds.remove(memberId) + return state + } + } + } + + return removePeerMember(account: context.account, peerId: peerId, memberId: memberId) + |> deliverOnMainQueue + |> afterDisposed { + updateState { state in + var state = state + state.removingParticipantIds.remove(memberId) + return state + } + } + } else { + return .complete() + } + } + actionsDisposable.add(signal.start()) + + }, showMore: { + updateState { state in + var state = state + state.hasShowMoreButton = nil + return state + } + }, promote: { participant in + showModal(with: ChannelAdminController(context, peerId: peerId, adminId: participant.peerId, initialParticipant: participant, updated: { _ in }, upgradedToSupergroup: upgradeToSupergroup), for: context.window) + }, restrict: { participant in + showModal(with: RestrictedModalViewController(context, peerId: peerId, memberId: participant.peerId, initialParticipant: participant, updated: { updatedRights in + _ = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerId, memberId: participant.peerId, bannedRights: updatedRights).start() + }), for: context.window) + }) + + let dataSignal = combineLatest(queue: prepareQueue, statePromise.get(), context.account.postbox.peerView(id: peerId), channelMembersPromise.get(), inputActivity, editing) |> map { + return InputDataSignalValue(entries: groupPeersEntries(state: $0, isEditing: $4, view: $1, inputActivities: $3, channelMembers: $2, arguments: arguments)) + } + + let controller = InputDataController(dataSignal: dataSignal, title: "") + controller.bar = .init(height: 0) + + controller.onDeinit = { + actionsDisposable.dispose() + } + + controller.getBackgroundColor = { + theme.colors.listBackground + } + + controller.didLoaded = { controller, _ in + controller.tableView.setScrollHandler { position in + if let loadMoreControl = loadMoreControl { + switch position.direction { + case .bottom: + context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl) + default: + break + } + } + } + + } + + return controller +} diff --git a/Telegram-Mac/PeerMediaListController.swift b/Telegram-Mac/PeerMediaListController.swift index 1ebc607e11..8ab447d028 100644 --- a/Telegram-Mac/PeerMediaListController.swift +++ b/Telegram-Mac/PeerMediaListController.swift @@ -8,161 +8,230 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit enum PeerMediaSharedEntryStableId : Hashable { case messageId(MessageId) case search case emptySearch - + case date(MessageIndex) + case sectionId(MessageIndex) var hashValue: Int { - switch self { - case let .messageId(messageId): - return messageId.hashValue - case .search: - return 0 - case .emptySearch: - return 1 - } + return 0 } - static func ==(lhs:PeerMediaSharedEntryStableId, rhs: PeerMediaSharedEntryStableId) -> Bool { - switch lhs { - case let .messageId(lhsMessageId): - if case let .messageId(rhsMessageId) = rhs, lhsMessageId == rhsMessageId { - return true - } else { - return false - } - case .search: - if case .search = rhs { - return true - } else { - return false - } - case .emptySearch: - if case .emptySearch = rhs { - return true - } else { - return false - } +} + +private func bestGeneralViewType(_ array:[PeerMediaSharedEntry], for item: PeerMediaSharedEntry) -> GeneralViewType { + for _ in array { + if item == array.first && item == array.last { + return .modern(position: .single, insets: NSEdgeInsetsMake(7, 7, 7, 12)) + } else if item == array.first { + return .modern(position: .first, insets: NSEdgeInsetsMake(7, 7, 7, 12)) + } else if item == array.last { + return .modern(position: .last, insets: NSEdgeInsetsMake(7, 7, 7, 12)) + } else { + return .modern(position: .inner, insets: NSEdgeInsetsMake(7, 7, 7, 12)) } } + return .modern(position: .single, insets: NSEdgeInsetsMake(6, 6, 6, 12)) } enum PeerMediaSharedEntry : Comparable, Identifiable { - case messageEntry(Message) - case searchEntry(Bool) - case emptySearchEntry - + case messageEntry(Message, AutomaticMediaDownloadSettings, GeneralViewType) + case emptySearchEntry(Bool) + case date(MessageIndex) + case sectionId(MessageIndex) var stableId: AnyHashable { switch self { - case let .messageEntry(message): + case let .messageEntry(message, _, _): return PeerMediaSharedEntryStableId.messageId(message.id) - case .searchEntry: - return PeerMediaSharedEntryStableId.search + case let .date(index): + return PeerMediaSharedEntryStableId.date(index) + case let .sectionId(index): + return PeerMediaSharedEntryStableId.sectionId(index) case .emptySearchEntry: return PeerMediaSharedEntryStableId.emptySearch } } -} - -func <(lhs:PeerMediaSharedEntry, rhs: PeerMediaSharedEntry) -> Bool { - switch lhs { - case .searchEntry: - if case .searchEntry = rhs { - return true - } else { - return false - } - case .emptySearchEntry: - switch rhs { - case .searchEntry: - return true - default: - return false + + var index: MessageIndex { + switch self { + case let .date(index): + return index + case let .sectionId(index): + return index + case let .messageEntry(message, _, _): + return MessageIndex(message).predecessor() + case .emptySearchEntry: + return MessageIndex.absoluteLowerBound() } - case let .messageEntry(lhsMessage): - switch rhs { - case let .messageEntry(rhsMessage): - return lhsMessage.id < rhsMessage.id + } + + var message:Message? { + switch self { + case let .messageEntry(message, _, _): + return message default: - return true + return nil } } } -func ==(lhs: PeerMediaSharedEntry, rhs: PeerMediaSharedEntry) -> Bool { - switch lhs { - case let .messageEntry(lhsMessage): - if case let .messageEntry(rhsMessage) = rhs { - if lhsMessage.id != rhsMessage.id { - return false - } +func <(lhs:PeerMediaSharedEntry, rhs: PeerMediaSharedEntry) -> Bool { + return lhs.index < rhs.index +} + + +func convertEntries(from update: PeerMediaUpdate, tags: MessageTags, timeDifference: TimeInterval) -> [PeerMediaSharedEntry] { + var converted:[PeerMediaSharedEntry] = [] + + + struct Item { + let date: PeerMediaSharedEntry + let section: PeerMediaSharedEntry + let items:[PeerMediaSharedEntry] + init(_ date: PeerMediaSharedEntry, _ section: PeerMediaSharedEntry, _ items: [PeerMediaSharedEntry]) { + self.date = date + self.section = section + self.items = items.sorted(by: >) + } + } + + + var tempItems:[(PeerMediaSharedEntry, PeerMediaSharedEntry?)] = [] + + for i in 0 ..< update.messages.count { + + let message = update.messages[i] + + let next = i < update.messages.count - 1 ? update.messages[i + 1] : nil + + + + if let nextMessage = next { + + let timestamp = Int32(min(TimeInterval(message.timestamp) - timeDifference, TimeInterval(Int32.max))) + let nextTimestamp = Int32(min(TimeInterval(nextMessage.timestamp) - timeDifference, TimeInterval(Int32.max))) + - if lhsMessage.stableVersion != rhsMessage.stableVersion { - return false + let dateId = mediaDateId(for: timestamp) + let nextDateId = mediaDateId(for: nextTimestamp) + if dateId != nextDateId { + let index = MessageIndex(id: message.id, timestamp: Int32(dateId)) + tempItems.append((.date(index), .sectionId(index.successor()))) } - return true } else { - return false + let timestamp = Int32(min(TimeInterval(message.timestamp) - timeDifference, TimeInterval(Int32.max))) + let dateId = mediaDateId(for: timestamp) + let index = MessageIndex(id: message.id, timestamp: Int32(dateId)) + tempItems.append((.date(index), .sectionId(index.successor()))) } - case let .searchEntry(lhsProgress): - if case let .searchEntry(rhsProgress) = rhs { - return lhsProgress == rhsProgress + tempItems.append((.messageEntry(message, update.automaticDownload, .singleItem), nil)) + + } + + + var groupItems:[Item] = [] + var current:[PeerMediaSharedEntry] = [] + for item in tempItems.sorted(by: { $0.0 < $1.0 }) { + switch item.0 { + case .date: + if !current.isEmpty { + groupItems.append(Item(item.0, item.1!, current)) + current.removeAll() + } + case .messageEntry: + current.insert(item.0, at: 0) + default: + fatalError() + } + } + + if !current.isEmpty { + if !groupItems.isEmpty { + let item = groupItems.last! + groupItems[groupItems.count - 1] = Item(item.date, item.section, item.items + current) } else { - return false + groupItems.append(.init(current.first!, .sectionId(current.first!.index.successor()), current)) } - default : - return lhs.stableId == rhs.stableId } -} - - -func convertEntries(from update: PeerMediaUpdate) -> [PeerMediaSharedEntry] { - var converted:[PeerMediaSharedEntry] = [] + + + for (i, group) in groupItems.reversed().enumerated() { + if i != 0 { + converted.append(group.section) + converted.append(group.date) + } + + + for item in group.items { + switch item { + case let .messageEntry(message, settings, _): + var viewType = bestGeneralViewType(group.items, for: item) + + if i == 0, item == group.items.first { + if group.items.count > 1 { + viewType = .modern(position: .inner, insets: NSEdgeInsetsMake(7, 7, 7, 12)) + } else { + viewType = .modern(position: .last, insets: NSEdgeInsetsMake(7, 7, 7, 12)) + } + } + + converted.append(.messageEntry(message, settings, viewType)) + default: + fatalError() + } + } + } + - for message in update.messages { - converted.append(.messageEntry(message)) + + if !tempItems.isEmpty { + converted.append(.sectionId(MessageIndex.absoluteLowerBound())) } if update.updateType == .search { - converted.append(.searchEntry(false)) - if update.messages.isEmpty { - //converted.append(.emptySearchEntry) + if converted.isEmpty { + converted.append(.emptySearchEntry(false)) } } else if update.updateType == .loading { - converted.append(.searchEntry(true)) - // converted.append(.emptySearchEntry) - } else if update.laterId == nil { - if !update.messages.isEmpty { - converted.append(.searchEntry(false)) + if converted.isEmpty { + converted.append(.emptySearchEntry(true)) } } - - return converted.sorted(by: <) + converted = converted.sorted(by: <) + + + + return converted } -fileprivate func preparedMediaTransition(from fromView:[PeerMediaSharedEntry]?, to toView:[PeerMediaSharedEntry], account:Account, initialSize:NSSize, interaction:ChatInteraction, animated:Bool, scroll:TableScrollState, tags:MessageTags, searchInteractions:SearchInteractions) -> TableUpdateTransition { - let (removed,inserted,updated) = proccessEntries(fromView, right: toView, { (entry) -> TableRowItem in +fileprivate func preparedMediaTransition(from fromView:[AppearanceWrapperEntry]?, to toView:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, interaction:ChatInteraction, animated:Bool, scroll:TableScrollState, tags:MessageTags, searchInteractions:SearchInteractions) -> TableUpdateTransition { + let (removed,inserted,updated) = proccessEntries(fromView, right: toView, { entry -> TableRowItem in - switch entry { - case .messageEntry: - if tags == .file { - return PeerMediaFileRowItem(initialSize, interaction, account, entry) + switch entry.entry { + case let .messageEntry(message, _, viewType): + if tags == .file, message.media.first is TelegramMediaFile { + return PeerMediaFileRowItem(initialSize, interaction, entry.entry, viewType: viewType) } else if tags == .webPage { - return PeerMediaWebpageRowItem(initialSize,interaction,account,entry) - } else if tags == .music { - return PeerMediaMusicRowItem(initialSize, interaction, account, entry) + return PeerMediaWebpageRowItem(initialSize,interaction, entry.entry, viewType: viewType) + } else if tags == .music, message.media.first is TelegramMediaFile { + return PeerMediaMusicRowItem(initialSize, interaction, entry.entry, viewType: viewType) + } else if tags == .voiceOrInstantVideo, message.media.first is TelegramMediaFile { + return PeerMediaVoiceRowItem(initialSize,interaction, entry.entry, viewType: viewType) } else { - return GeneralRowItem(initialSize, height: 20, stableId: entry.stableId) + return GeneralRowItem(initialSize, height: 1, stableId: entry.stableId) } - case let .searchEntry(isLoading): - return SearchRowItem(initialSize, stableId: entry.stableId, searchInteractions: searchInteractions, isLoading: isLoading, inset: NSEdgeInsets(left: 10, right: 10, top: 10, bottom: 10)) + case .date(let index): + return PeerMediaDateItem(initialSize, index: index, stableId: entry.stableId) + case .sectionId: + return GeneralRowItem(initialSize, height: 20, stableId: entry.stableId, viewType: .separator) case .emptySearchEntry: - return SearchEmptyRowItem(initialSize, stableId: entry.stableId) + return SearchEmptyRowItem(initialSize, stableId: entry.stableId, isLoading: false, viewType: .separator) } }) @@ -188,30 +257,61 @@ struct PeerMediaUpdate { let updateType: PeerMediaUpdateState let laterId: MessageIndex? let earlierId: MessageIndex? - init (messages: [Message] = [], updateType:PeerMediaUpdateState = .loading, laterId:MessageIndex? = nil, earlierId:MessageIndex? = nil) { + let automaticDownload: AutomaticMediaDownloadSettings + let searchState: SearchState + init (messages: [Message] = [], updateType:PeerMediaUpdateState = .loading, laterId:MessageIndex? = nil, earlierId:MessageIndex? = nil, automaticDownload: AutomaticMediaDownloadSettings = .defaultSettings, searchState: SearchState = SearchState(state: .None, request: nil)) { self.messages = messages self.updateType = updateType self.laterId = laterId self.earlierId = earlierId + self.automaticDownload = automaticDownload + self.searchState = searchState + } + + func withUpdatedUpdatedType(_ updateType:PeerMediaUpdateState) -> PeerMediaUpdate { + return PeerMediaUpdate(messages: self.messages, updateType: updateType, laterId: self.laterId, earlierId: self.earlierId, automaticDownload: self.automaticDownload, searchState: self.searchState) } } +struct MediaSearchState : Equatable { + let state: SearchState + let animated: Bool + let isLoading: Bool +} -class PeerMediaListController: GenericViewController { +class PeerMediaListController: TableViewController { - private var account:Account - private var peerId:PeerId + private var chatLocation:ChatLocation private var chatInteraction:ChatInteraction private let disposable: MetaDisposable = MetaDisposable() - private let entires = Atomic<[PeerMediaSharedEntry]?>(value: nil) + private let entires = Atomic<[AppearanceWrapperEntry]?>(value: nil) private let updateView = Atomic(value: nil) - private let searchState:ValuePromise = ValuePromise(ignoreRepeated: true) - public init(account: Account, peerId: PeerId, chatInteraction: ChatInteraction) { - self.account = account - self.peerId = peerId + private let mediaSearchState:ValuePromise = ValuePromise(ignoreRepeated: true) + let searchState:ValuePromise = ValuePromise(ignoreRepeated: true) + + var mediaSearchValue:Signal { + return mediaSearchState.get() + } + private var isSearch: Bool = false { + didSet { + if isSearch { + searchState.set(.init(state: .Focus, request: nil)) + } else { + searchState.set(.init(state: .None, request: nil)) + } + } + } + func toggleSearch() { + let old = self.isSearch + self.isSearch = !old + } + + + public init(context: AccountContext, chatLocation: ChatLocation, chatInteraction: ChatInteraction) { + self.chatLocation = chatLocation self.chatInteraction = chatInteraction - super.init() + super.init(context) } @@ -223,57 +323,111 @@ class PeerMediaListController: GenericViewController { super.viewWillAppear(animated) } + override func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + if let stableId = stableId.base as? ChatHistoryEntryId { + switch stableId { + case let .message(message): + return PeerMediaSharedEntryStableId.messageId(message.id) + default: + break + } + } + return nil + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + + } + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) genericView.stopMerge() } + private var isFirst: Bool = true + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.getBackgroundColor = { + theme.colors.listBackground + } + } public func load(with tagMask:MessageTags) -> Void { + + genericView.clipView.scroll(to: NSMakePoint(0, 0), animated: false) + + let isFirst = self.isFirst + self.isFirst = false + let location = ValuePromise(ignoreRepeated: true) searchState.set(SearchState(state: .None, request: nil)) genericView.emptyItem = PeerMediaEmptyRowItem(atomicSize.modify {$0}, tags: tagMask) + genericView.set(stickClass: PeerMediaDateItem.self, handler: { item in + + }) + + let historyPromise: Promise = Promise() + + let historyViewUpdate = combineLatest(location.get(), searchState.get()) |> deliverOnMainQueue |> mapToSignal { [weak self] location, searchState -> Signal in if let strongSelf = self { if searchState.request.isEmpty { - return chatHistoryViewForLocation(location, account: strongSelf.account, peerId: strongSelf.peerId, fixedCombinedReadState: nil, tagMask: tagMask, additionalData: []) |> mapToQueue { view -> Signal in + return combineLatest(queue: prepareQueue, chatHistoryViewForLocation(location, account: strongSelf.context.account, chatLocation: strongSelf.chatLocation, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []), automaticDownloadSettings(postbox: strongSelf.context.account.postbox)) |> mapToQueue { view, settings -> Signal in switch view { case .Loading: return .single(PeerMediaUpdate()) case let .HistoryView(view: view, type: _, scrollPosition: _, initialData: _): var messages:[Message] = [] for entry in view.entries { - switch entry { - case let .MessageEntry(message, _, _, _): - messages.append(message) - default: - break - } + messages.append(entry.message) } - return .single(PeerMediaUpdate(messages: messages, updateType: .history, laterId: view.laterId, earlierId: view.earlierId)) + + let laterId = view.laterId + let earlierId = view.earlierId + + return .single(PeerMediaUpdate(messages: messages, updateType: .history, laterId: laterId, earlierId: earlierId, automaticDownload: settings, searchState: searchState)) } } } else { - return .single(PeerMediaUpdate()) |> then(searchMessages(account: strongSelf.account, peerId: strongSelf.peerId, query: searchState.request, tagMask: tagMask) |> deliverOnMainQueue |> map { messages -> PeerMediaUpdate in - return PeerMediaUpdate(messages: messages, updateType: .search, laterId: nil, earlierId: nil) - }) + let searchMessagesLocation: SearchMessagesLocation + switch strongSelf.chatLocation { + case let .peer(peerId): + searchMessagesLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask) + } + + let signal = searchMessages(account: strongSelf.context.account, location: searchMessagesLocation, query: searchState.request, state: nil) |> deliverOnMainQueue |> map {$0.0.messages} |> map { messages -> PeerMediaUpdate in + return PeerMediaUpdate(messages: messages, updateType: .search, laterId: nil, earlierId: nil, searchState: searchState) + } + + let update = strongSelf.updateView.modify {$0?.withUpdatedUpdatedType(.loading)} ?? PeerMediaUpdate() + + if isFirst { + return .single(update) |> then(signal) + } else { + return .single(update) |> then(signal) + } + } } return .complete() } - let animated:Atomic = Atomic(value:true) + let animated:Atomic = Atomic(value:false) - let searchInteractions = SearchInteractions({ [weak self] state in + let searchInteractions = SearchInteractions({ [weak self] state, _ in if let strongSelf = self { strongSelf.searchState.set(state) } @@ -282,69 +436,82 @@ class PeerMediaListController: GenericViewController { strongSelf.searchState.set(state) } }) - let account = self.account + let context = self.context let chatInteraction = self.chatInteraction let initialSize = self.atomicSize + + let _updateView = self.updateView let _entries = self.entires - let historyViewTransition = historyViewUpdate |> deliverOnPrepareQueue |> map { update -> TableUpdateTransition in + + + let historyViewTransition = combineLatest(queue: prepareQueue,historyPromise.get(), appearanceSignal) |> map { update, appearance -> (transition: TableUpdateTransition, previousUpdate: PeerMediaUpdate?, currentUpdate: PeerMediaUpdate) in let animated = animated.swap(true) - let scroll:TableScrollState = animated ? .none(nil) : .saveVisible(.upper) + var scroll:TableScrollState = animated ? .none(nil) : .saveVisible(.upper) - let entries = convertEntries(from: update) + + + let entries = convertEntries(from: update, tags: tagMask, timeDifference: context.timeDifference).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) let previous = _entries.swap(entries) - _ = _updateView.swap(update) + let previousUpdate = _updateView.swap(update) + + if previousUpdate?.searchState != update.searchState { + scroll = .up(animated) + } + + let transition = preparedMediaTransition(from: previous, to: entries, account: context.account, initialSize: initialSize.modify({$0}), interaction: chatInteraction, animated: previousUpdate?.searchState.state != update.searchState.state, scroll:scroll, tags:tagMask, searchInteractions: searchInteractions) - return preparedMediaTransition(from: previous, to: entries, account: account, initialSize: initialSize.modify({$0}), interaction: chatInteraction, animated: animated, scroll:scroll, tags:tagMask, searchInteractions: searchInteractions) + return (transition: transition, previousUpdate: previousUpdate, currentUpdate: update) } |> deliverOnMainQueue - disposable.set(historyViewTransition.start(next: { [weak self] transition in - self?.genericView.merge(with: transition) + disposable.set(historyViewTransition.start(next: { [weak self] values in + guard let `self` = self else {return} + + let state = MediaSearchState(state: values.currentUpdate.searchState, animated: values.currentUpdate.searchState != values.previousUpdate?.searchState, isLoading: values.currentUpdate.updateType == .loading) + self.genericView.merge(with: values.transition) + self.mediaSearchState.set(state) + self.readyOnce() + if let controller = globalAudio { + (self.navigationController?.header?.view as? InlineAudioPlayerView)?.update(with: controller, context: context, tableView: (self.navigationController?.first {$0 is ChatController} as? ChatController)?.genericView.tableView, supportTableView: self.genericView) + } })) + + historyPromise.set(historyViewUpdate) - location.set(.Scroll(index: MessageIndex.upperBound(peerId: peerId), anchorIndex: MessageIndex.upperBound(peerId: peerId), sourceIndex: MessageIndex.upperBound(peerId: peerId), scrollPosition: .none(nil), animated: false)) + let perPageCount:()->Int = { [weak self] in + guard let `self` = self else { + return 0 + } + return Int(self.frame.height / 50) + } + + var requestCount: Int = perPageCount() + 5 + + + location.set(.Initial(count: requestCount)) - genericView.setScrollHandler { [weak self] scroll in - - let view = self?.updateView.modify({$0}) - if let view = view, view.updateType == .history { - var messageIndex:MessageIndex? - switch scroll.direction { - case .bottom: - messageIndex = view.earlierId - case .top: - messageIndex = view.laterId - case .none: - break - } - - if let messageIndex = messageIndex { - let _ = animated.swap(false) - location.set(.Navigation(index: messageIndex, anchorIndex: messageIndex)) - } + genericView.setScrollHandler { scroll in + switch scroll.direction { + case .bottom: + _ = animated.swap(false) + requestCount += perPageCount() * 3 + location.set(.Initial(count: requestCount)) + default: + break } } } + override func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> () -> Void { + return { + + } + } } - - - -// let view = strongSelf.messagesView.modify {$0} -// if let view = view { -// let range = strongSelf.genericView.visibleRows() -// let indexRange = (max(view.entries.count - 1 - range.max,0), max(view.entries.count - 1 - range.min,0)) -// -// if indexRange.1 > 0 { -// strongSelf.account.postbox.updateMessageHistoryViewVisibleRange(view.id, earliestVisibleIndex: view.entries[indexRange.0].index, latestVisibleIndex: view.entries[indexRange.1].index) -// } -// -// } - diff --git a/Telegram-Mac/PeerMediaMusicRowContent.swift b/Telegram-Mac/PeerMediaMusicRowContent.swift index edba940523..5f1df72002 100644 --- a/Telegram-Mac/PeerMediaMusicRowContent.swift +++ b/Telegram-Mac/PeerMediaMusicRowContent.swift @@ -8,60 +8,123 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class PeerMediaMusicRowItem: PeerMediaRowItem { - fileprivate var textLayout:TextViewLayout? - fileprivate var file:TelegramMediaFile! - override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ account:Account, _ object: PeerMediaSharedEntry) { - super.init(initialSize,interface,account,object) + fileprivate let textLayout:TextViewLayout + fileprivate let descLayout:TextViewLayout? + fileprivate let file:TelegramMediaFile + fileprivate let thumbResource: ExternalMusicAlbumArtResource + fileprivate let isCompactPlayer: Bool + init(_ initialSize:NSSize, _ interface:ChatInteraction, _ object: PeerMediaSharedEntry, isCompactPlayer: Bool = false, viewType: GeneralViewType = .legacy) { + self.isCompactPlayer = isCompactPlayer - file = message.media[0] as! TelegramMediaFile - let attr = NSMutableAttributedString() + file = object.message!.media[0] as! TelegramMediaFile let music = file.musicText - _ = attr.append(string: music.0, color: theme.colors.text, font: .medium(.header)) - _ = attr.append(string: "\n") - _ = attr.append(string: music.1, color: theme.colors.grayText, font: .normal(.text)) - textLayout = TextViewLayout(attr, maximumNumberOfLines: 2, truncationType: .middle) + self.textLayout = TextViewLayout(.initialize(string: music.0, color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1, truncationType: .end) + + if !music.1.isEmpty { + self.descLayout = TextViewLayout(.initialize(string: music.1, color: theme.colors.grayText, font: .normal(.short)), maximumNumberOfLines: 1) + } else if let size = file.size { + self.descLayout = TextViewLayout(.initialize(string: String.prettySized(with: size), color: theme.colors.grayText, font: .normal(.short)), maximumNumberOfLines: 1) + } else { + descLayout = nil + } + thumbResource = ExternalMusicAlbumArtResource(title: file.musicText.0, performer: file.musicText.1, isThumbnail: true) + + + + super.init(initialSize, interface, object, viewType: viewType) + + } + + override var inset: NSEdgeInsets { + if isCompactPlayer { + return NSEdgeInsetsMake(5, 10, 5, 10) + } else { + return NSEdgeInsetsMake(0, 0, 0, 0) + } } override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { - textLayout?.measure(width: width - 70) - return super.makeSize(width, oldWidth: oldWidth) + let success = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + descLayout?.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + return success } override func viewClass() -> AnyClass { return PeerMediaMusicRowView.self } + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + if isCompactPlayer { + return .single([]) + } else { + return super.menuItems(in: location) + } + } + } class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { private let textView:TextView = TextView() - let statusView:RadialProgressView = RadialProgressView() + private let descView:TextView = TextView() + + let thumbView:TransformImageView = TransformImageView(frame: NSMakeRect(0, 0, 40, 40)) var fetchStatus: MediaResourceStatus? let statusDisposable = MetaDisposable() let fetchDisposable = MetaDisposable() + private var playAnimationView: PeerMediaPlayerAnimationView? private(set) var fetchControls:FetchControls! required init(frame frameRect: NSRect) { super.init(frame: frameRect) + textView.isSelectable = false + textView.userInteractionEnabled = false + + descView.isSelectable = false + descView.userInteractionEnabled = false + addSubview(textView) - addSubview(statusView) - fetchControls = FetchControls(fetch: { [weak self] in - self?.executeInteraction(true) - }) - statusView.fetchControls = fetchControls + addSubview(descView) + addSubview(thumbView) +// fetchControls = FetchControls(fetch: { [weak self] in +// self?.executeInteraction(true) +// }) + + // thumbView.fetchControls = fetchControls + } + + override func mouseUp(with event: NSEvent) { + guard let item = item as? PeerMediaMusicRowItem else { + super.mouseUp(with: event) + return + } + if item.interface.presentation.state == .normal { + executeInteraction(true) + } else { + super.mouseUp(with: event) + } } override func layout() { super.layout() - if let item = item as? PeerMediaMusicRowItem, let layout = item.textLayout { - let f = focus(layout.layoutSize) - textView.update(layout, origin: NSMakePoint(60, f.minY)) - statusView.centerY(x: 10) + if let item = item as? PeerMediaMusicRowItem { + textView.update(item.textLayout, origin: NSMakePoint(item.contentInset.left, item.contentInset.top + 2)) + + if let descLayout = item.descLayout { + descView.update(descLayout, origin: NSMakePoint(item.contentInset.left, item.contentSize.height - descLayout.layoutSize.height - item.contentInset.bottom - 2)) + } else { + descView.update(nil) + textView.centerY() + } + + thumbView.centerY(x: 0) + playAnimationView?.centerY(x: 0) } } @@ -73,17 +136,17 @@ class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { } func songDidStartPlaying(song:APSongItem, for controller:APController) { - + checkState() } func songDidStopPlaying(song:APSongItem, for controller:APController) { - + checkState() } func playerDidChangedTimebase(song:APSongItem, for controller:APController) { - + //checkState() } func audioDidCompleteQueue(for controller:APController) { - + checkState() } func executeInteraction(_ isControl:Bool) -> Void { @@ -105,13 +168,28 @@ class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { func checkState() { if let item = item as? PeerMediaMusicRowItem { if let controller = globalAudio, let song = controller.currentSong { - if song.entry.isEqual(to: item.message), case .playing = song.state { - statusView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPause, iconInset:NSEdgeInsets(left:1)) + if song.entry.isEqual(to: item.message) { + if playAnimationView == nil { + playAnimationView = PeerMediaPlayerAnimationView() + addSubview(playAnimationView!) + playAnimationView?.centerY(x: 0) + } + if case .playing = song.state { + playAnimationView?.isPlaying = true + } else if case .stoped = song.state { + playAnimationView?.removeFromSuperview() + playAnimationView = nil + } else { + playAnimationView?.isPlaying = false + } + } else { - statusView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + playAnimationView?.removeFromSuperview() + playAnimationView = nil } } else { - statusView.theme = RadialProgressTheme(backgroundColor: theme.colors.blueFill, foregroundColor: .white, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + playAnimationView?.removeFromSuperview() + playAnimationView = nil } } } @@ -122,39 +200,45 @@ class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { if let item = item as? PeerMediaMusicRowItem { var updatedStatusSignal: Signal? textView.update(item.textLayout) - textView.centerY(x: 60) + textView.centerY(x: item.contentInset.left) textView.backgroundColor = backdorColor globalAudio?.add(listener: self) + + let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0)) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: PeerMediaIconSize, boundingSize: PeerMediaIconSize, intrinsicInsets: NSEdgeInsets()) + + thumbView.layer?.contents = theme.icons.playerMusicPlaceholder + thumbView.layer?.cornerRadius = .cornerRadius + + let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(PeerMediaIconSize), resource: item.thumbResource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + + thumbView.setSignal(chatMessagePhotoThumbnail(account: item.interface.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: image))) + + thumbView.set(arguments: arguments) + + if item.message.flags.contains(.Unsent) && !item.message.flags.contains(.Failed) { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: item.account, file: item.file), item.account.pendingMessageManager.pendingMessageStatus(item.message.id)) + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: item.interface.context.account, file: item.file), item.interface.context.account.pendingMessageManager.pendingMessageStatus(item.message.id)) |> map { resourceStatus, pendingStatus -> MediaResourceStatus in - if let pendingStatus = pendingStatus { + if let pendingStatus = pendingStatus.0 { return .Fetching(isActive: true, progress: pendingStatus.progress) } else { return resourceStatus } } |> deliverOnMainQueue } else { - updatedStatusSignal = chatMessageFileStatus(account: item.account, file: item.file) |> deliverOnMainQueue + updatedStatusSignal = chatMessageFileStatus(account: item.interface.context.account, file: item.file) |> deliverOnMainQueue } if let updatedStatusSignal = updatedStatusSignal { self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self { - strongSelf.fetchStatus = status - switch status { - case let .Fetching(_, progress): - strongSelf.statusView.state = .Fetching(progress: progress, force: false) - case .Local, .Remote: - strongSelf.statusView.state = .Play - } - } + self?.fetchStatus = status })) checkState() } - + needsLayout = true } } @@ -163,30 +247,18 @@ class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: item.message) { controller.playOrPause() } else { - let controller = APChatMusicController(account: item.account, peerId: item.message.id.peerId, index: MessageIndex(item.message)) + let controller = APChatMusicController(account: item.interface.context.account, peerId: item.message.id.peerId, index: MessageIndex(item.message)) item.interface.inlineAudioPlayer(controller) controller.start() - addGlobalAudioToVisible() } } } - - func addGlobalAudioToVisible() { - if let controller = globalAudio { - item?.table?.enumerateViews(with: { (view) in - if let view = (view as? PeerMediaMusicRowView) { - controller.add(listener: view) - } - return true - }) - } - - } + func fetch() { if let item = item as? PeerMediaMusicRowItem { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: item.account, file: item.file).start()) + fetchDisposable.set(messageMediaFileInteractiveFetched(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: item.file)).start()) } open() } @@ -194,7 +266,7 @@ class PeerMediaMusicRowView : PeerMediaRowView, APDelegate { func cancelFetching() { if let item = item as? PeerMediaMusicRowItem { - fetchDisposable.set(chatMessageFileInteractiveFetched(account: item.account, file: item.file).start()) + messageMediaFileCancelInteractiveFetch(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: item.file)) } } diff --git a/Telegram-Mac/PeerMediaPhotosController.swift b/Telegram-Mac/PeerMediaPhotosController.swift new file mode 100644 index 0000000000..c628c59498 --- /dev/null +++ b/Telegram-Mac/PeerMediaPhotosController.swift @@ -0,0 +1,369 @@ +// +// PeerMediaPhotosController.swift +// Telegram +// +// Created by Mikhail Filimonov on 17.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +extension Message : Equatable { + public static func ==(lhs: Message, rhs: Message) -> Bool { + return isEqualMessages(lhs, rhs) + } +} + +private enum PeerMediaMonthEntry : TableItemListNodeEntry { + case month(index: MessageIndex, items: [Message], viewType: GeneralViewType) + case date(index: MessageIndex) + case section(index: MessageIndex) + + static func < (lhs: PeerMediaMonthEntry, rhs: PeerMediaMonthEntry) -> Bool { + return lhs.index < rhs.index + } + + var description: String { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM yyyy"; + switch self { + case let .month(index, _, _): + let date = Date(timeIntervalSince1970: TimeInterval(index.timestamp)) + return "items: \(formatter.string(from: date))" + case let .date(index): + let date = Date(timeIntervalSince1970: TimeInterval(index.timestamp)) + return "date: \(formatter.string(from: date))" + case let .section(index): + let date = Date(timeIntervalSince1970: TimeInterval(index.timestamp)) + return "section: \(formatter.string(from: date))" + } + } + + func item(_ arguments: PeerMediaPhotosArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .month(_, items, viewType): + return PeerPhotosMonthItem(initialSize, stableId: stableId, viewType: viewType, context: arguments.context, chatInteraction: arguments.chatInteraction, gallerySupplyment: arguments.gallerySupplyment, items: items) + case .date: + return PeerMediaDateItem(initialSize, index: index, stableId: stableId) + case .section: + return GeneralRowItem(initialSize, height: 20, stableId: stableId, viewType: .separator) + } + } + + var stableId: MessageIndex { + return self.index + } + + var index: MessageIndex { + switch self { + case let .month(index, _, _): + return index + case let .date(index): + return index + case let .section(index): + return index + } + } +} + +private final class PeerMediaPhotosArguments { + let context: AccountContext + let chatInteraction: ChatInteraction + let gallerySupplyment: InteractionContentViewProtocol + init(context: AccountContext, chatInteraction: ChatInteraction, gallerySupplyment: InteractionContentViewProtocol) { + self.context = context + self.gallerySupplyment = gallerySupplyment + self.chatInteraction = chatInteraction + } +} + + + +private struct PeerMediaPhotosState : Equatable { + let isLoading: Bool + let messages:[Message] + init(isLoading: Bool, messages: [Message]) { + self.isLoading = isLoading + self.messages = messages.reversed() + } + func withAppendMessages(_ collection: [Message]) -> PeerMediaPhotosState { + var messages = self.messages + messages.append(contentsOf: collection) + return PeerMediaPhotosState(isLoading: self.isLoading, messages: messages) + } + func withUpdatedMessages(_ collection: [Message]) -> PeerMediaPhotosState { + return PeerMediaPhotosState(isLoading: self.isLoading, messages: collection) + } + func withUpdatedLoading(_ isLoading: Bool) -> PeerMediaPhotosState { + return PeerMediaPhotosState(isLoading: isLoading, messages: self.messages) + } +} + +private func mediaEntires(state: PeerMediaPhotosState, arguments: PeerMediaPhotosArguments) -> [PeerMediaMonthEntry] { + var entries:[PeerMediaMonthEntry] = [] + + let timeDifference = Int32(arguments.context.timeDifference) + var temp:[Message] = [] + for i in 0 ..< state.messages.count { + + let message = state.messages[i] + + temp.append(message) + let next = i < state.messages.count - 1 ? state.messages[i + 1] : nil + if let nextMessage = next { + let dateId = mediaDateId(for: message.timestamp - timeDifference) + let nextDateId = mediaDateId(for: nextMessage.timestamp - timeDifference) + if dateId != nextDateId { + let index = MessageIndex(id: MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: 0), timestamp: Int32(dateId)) + var viewType: GeneralViewType = .modern(position: .single, insets: NSEdgeInsetsMake(0, 0, 1, 0)) + if !entries.isEmpty { + entries.append(.section(index: index.successor())) + entries.append(.date(index: index)) + } else { + viewType = .modern(position: .last, insets: NSEdgeInsetsMake(0, 0, 1, 0)) + } + entries.append(.month(index: index.predecessor(), items: temp, viewType: viewType)) + temp.removeAll() + } + } else { + let dateId = mediaDateId(for: message.timestamp - timeDifference) + let index = MessageIndex(id: MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: 0), timestamp: Int32(dateId)) + + if !entries.isEmpty { + switch entries[entries.count - 1] { + case let .month(prevIndex, items, viewType): + let prevDateId = mediaDateId(for: prevIndex.timestamp) + if prevDateId != dateId { + entries.append(.section(index: index.successor())) + entries.append(.date(index: index)) + entries.append(.month(index: index.predecessor(), items: temp, viewType: .modern(position: .single, insets: NSEdgeInsetsMake(0, 0, 1, 0)))) + } else { + entries[entries.count - 1] = .month(index: prevIndex, items: items + temp, viewType: viewType) + } + default: + assertionFailure() + } + } else { + //entries.append(.section(index: index.successor())) + //entries.append(.date(index: index)) + entries.append(.month(index: index.predecessor(), items: temp, viewType: .modern(position: .last, insets: NSEdgeInsetsMake(0, 0, 1, 0)))) + } + + } + } + if !state.messages.isEmpty { + entries.append(.section(index: MessageIndex.absoluteLowerBound())) + } + + return entries +} + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], animated: Bool, initialSize:NSSize, arguments: PeerMediaPhotosArguments) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: animated) +} + + +private final class PeerMediaSupplyment : InteractionContentViewProtocol { + private weak var tableView: TableView? + init(tableView: TableView) { + self.tableView = tableView + } + + func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { + if let stableId = stableId.base as? ChatHistoryEntryId, let tableView = tableView { + switch stableId { + case let .message(message): + var found: NSView? = nil + tableView.enumerateItems { item -> Bool in + if let item = item as? PeerPhotosMonthItem { + if item.contains(message.id) { + found = item.view?.interactionContentView(for: message.id, animateIn: animateIn) + } + } + return found == nil + } + return found + default: + break + } + } + return nil + } + func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + + } + func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + if let stableId = stableId.base as? ChatHistoryEntryId, let tableView = tableView { + switch stableId { + case let .message(message): + tableView.enumerateItems { item -> Bool in + if let item = item as? PeerPhotosMonthItem { + if item.contains(message.id) { + item.view?.addAccesoryOnCopiedView(innerId: message.id, view: view) + return false + } + } + return true + } + default: + break + } + } + } + func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + return nil + } + func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + + } +} + +class PeerMediaPhotosController: TableViewController { + private let peerId: PeerId + private let disposable = MetaDisposable() + private let historyDisposable = MetaDisposable() + private let chatInteraction: ChatInteraction + private let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + private let tags: MessageTags + init(_ context: AccountContext, chatInteraction: ChatInteraction, peerId: PeerId, tags: MessageTags) { + self.peerId = peerId + self.chatInteraction = chatInteraction + self.tags = tags + super.init(context) + } + + override func viewDidLoad() { + super.viewDidLoad() + + let context = self.context + let peerId = self.peerId + let initialSize = self.atomicSize + let tags = self.tags + + + self.genericView.set(stickClass: PeerMediaDateItem.self, handler: { item in + + }) + + self.genericView.needUpdateVisibleAfterScroll = true + + self.genericView.emptyItem = PeerMediaEmptyRowItem(NSZeroSize, tags: self.tags) + + let perPageCount:()->Int = { + var rowCount:Int = 4 + var perWidth: CGFloat = 0 + let blockWidth = min(600, initialSize.with { $0.width } - 60) + while true { + let maximum = blockWidth - 7 - 7 - CGFloat(rowCount * 2) + perWidth = maximum / CGFloat(rowCount) + if perWidth >= 90 { + break + } else { + rowCount -= 1 + } + } + return Int((initialSize.with { $0.height } / perWidth) * CGFloat(rowCount) + CGFloat(rowCount)) + } + + var requestCount = perPageCount() + 20 + + let location: ValuePromise = ValuePromise(.Initial(count: requestCount), ignoreRepeated: true) + + let initialState = PeerMediaPhotosState(isLoading: false, messages: []) + let state: ValuePromise = ValuePromise() + let stateValue: Atomic = Atomic(value: initialState) + let updateState:((PeerMediaPhotosState)->PeerMediaPhotosState) -> Void = { f in + state.set(stateValue.modify(f)) + } + + let supplyment = PeerMediaSupplyment(tableView: genericView) + + let arguments = PeerMediaPhotosArguments(context: context, chatInteraction: chatInteraction, gallerySupplyment: supplyment) + + + let applyHole:() -> Void = { + location.set(.Initial(count: requestCount)) + } + + let history = location.get() |> mapToSignal { location in + return chatHistoryViewForLocation(location, account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: tags) + } + + historyDisposable.set(history.start(next: { update in + + let isLoading: Bool + let view: MessageHistoryView? + let updateType: ChatHistoryViewUpdateType + switch update { + case let .Loading(_, ut): + view = nil + isLoading = true + updateType = ut + case let .HistoryView(values): + view = values.view + isLoading = values.view.isLoading + updateType = values.type + } + + switch updateType { + case let .Generic(type: type): + switch type { + case .FillHole: + DispatchQueue.main.async(execute: applyHole) + default: + break + } + default: + break + } + let messages = view?.entries.map { value in + return value.message + } ?? [] + + updateState { + $0.withUpdatedMessages(messages).withUpdatedLoading(false).withUpdatedLoading(isLoading) + } + })) + + let previous = self.previous + + let transition: Signal = combineLatest(queue: prepareQueue, state.get(), appearanceSignal) |> mapToSignal { state, appearance in + let entries = mediaEntires(state: state, arguments: arguments).map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + return .single(prepareTransition(left: previous.swap(entries), right: entries, animated: true, initialSize: initialSize.with { $0 }, arguments: arguments)) + } |> deliverOnMainQueue + + + + disposable.set(transition.start(next: { [weak self] transition in + guard let `self` = self else { + return + } + self.genericView.merge(with: transition) + self.readyOnce() + })) + + genericView.setScrollHandler { position in + switch position.direction { + case .bottom: + requestCount += perPageCount() * 10 + location.set(.Initial(count: requestCount)) + default: + break + } + } + } + + deinit { + disposable.dispose() + historyDisposable.dispose() + _ = previous.swap([]) + } + +} diff --git a/Telegram-Mac/PeerMediaPlayerAnimationView.swift b/Telegram-Mac/PeerMediaPlayerAnimationView.swift new file mode 100644 index 0000000000..7254598e93 --- /dev/null +++ b/Telegram-Mac/PeerMediaPlayerAnimationView.swift @@ -0,0 +1,97 @@ +// +// PeerMediaPlayerAnimationView.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +class PeerMediaPlayerAnimationView: View { + + var isPlaying: Bool = false { + didSet { + if self.isPlaying != oldValue { + if self.isPlaying { + self.animateToPlaying() + } else { + self.animateToPaused() + } + } + } + } + + private let barNodes: [View] + + override init() { + + let baseSize = CGSize(width: 40, height: 40) + let barSize = CGSize(width: 3.0, height: 3) + let barSpacing: CGFloat = 2.0 + + let barsOrigin = CGPoint(x: floor((baseSize.width - (barSize.width * 4.0 + barSpacing * 3.0)) / 2.0), y: 18) + + var barNodes: [View] = [] + for i in 0 ..< 4 { + let barNode = View() + barNode.flip = false + barNode.frame = CGRect(origin: barsOrigin.offsetBy(dx: CGFloat(i) * (barSize.width + barSpacing), dy: 0.0), size: barSize) + barNode.backgroundColor = .white + barNode.layer?.anchorPoint = CGPoint(x: 0.5, y: 1) + barNodes.append(barNode) + } + self.barNodes = barNodes + + super.init(frame: NSMakeRect(0, 0, baseSize.width, baseSize.height)) + + flip = false + + self.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.5).cgColor + self.layer?.cornerRadius = .cornerRadius + for barNode in self.barNodes { + self.addSubview(barNode) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + func animateToPlaying() { + for barNode in self.barNodes { + let randValueMul = Float(4 % arc4random()) + let randDurationMul = Double(arc4random()) / Double(UInt32.max) + + let animation = CABasicAnimation(keyPath: "transform.scale.y") + animation.toValue = Float(randValueMul) as NSNumber + animation.autoreverses = true + animation.duration = 0.25 + 0.25 * randDurationMul + animation.repeatCount = Float.greatestFiniteMagnitude; + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn) + + barNode.layer?.removeAnimation(forKey: "transform.scale.y") + barNode.layer?.add(animation, forKey: "transform.scale.y") + } + } + + func animateToPaused() { + for barNode in self.barNodes { + if let presentationLayer = barNode.layer?.presentation() { + let animation = CABasicAnimation(keyPath: "transform.scale.y") + animation.fromValue = (presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0 + animation.toValue = 1.0 as NSNumber + animation.duration = 0.25 + animation.isRemovedOnCompletion = false + barNode.layer?.add(animation, forKey: "transform.scale.y") + } + } + } + +} diff --git a/Telegram-Mac/PeerMediaRowContent.swift b/Telegram-Mac/PeerMediaRowContent.swift index b8ed2d4378..79127b0a6d 100644 --- a/Telegram-Mac/PeerMediaRowContent.swift +++ b/Telegram-Mac/PeerMediaRowContent.swift @@ -8,66 +8,64 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit -class PeerMediaRowItem: TableRowItem { +let PeerMediaIconSize:NSSize = NSMakeSize(40, 40) + +class PeerMediaRowItem: GeneralRowItem { - var iconSize:NSSize = NSZeroSize - let contentInset:NSEdgeInsets = NSEdgeInsets(left: 70.0, right: 10, top: 5, bottom: 5) - var contentSize:NSSize = NSMakeSize(0, 50) + var contentInset:NSEdgeInsets = NSEdgeInsets(left: 50.0, right: 0, top: 0, bottom: 0) - override var stableId: AnyHashable { - return entry.stableId - } + var contentSize:NSSize = NSMakeSize(0, 40) override var height: CGFloat { - return contentSize.height + return contentSize.height + viewType.innerInset.top + viewType.innerInset.bottom + inset.top + inset.bottom } private var entry:PeerMediaSharedEntry - var message:Message - var account:Account - var interface:ChatInteraction + let message:Message + let interface:ChatInteraction + let automaticDownload: AutomaticMediaDownloadSettings - init(_ initialSize:NSSize, _ interface:ChatInteraction, _ account:Account, _ object: PeerMediaSharedEntry) { - + init(_ initialSize:NSSize, _ interface:ChatInteraction, _ object: PeerMediaSharedEntry, viewType: GeneralViewType = .legacy) { self.entry = object - self.account = account self.interface = interface - - if case let .messageEntry(message) = object { + if case let .messageEntry(message, automaticDownload, _) = object { self.message = message + self.automaticDownload = automaticDownload } else { fatalError("entry haven't message") } - super.init(initialSize) + super.init(initialSize, stableId: object.stableId, viewType: viewType, inset: NSEdgeInsetsZero) } - override func menuItems() -> Signal<[ContextMenuItem], Void> { + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] - if canForwardMessage(message, account: account) { - items.append(ContextMenuItem(tr(.messageContextForward), handler: { [weak self] in + if canForwardMessage(message, account: interface.context.account) { + items.append(ContextMenuItem(L10n.messageContextForward, handler: { [weak self] in if let strongSelf = self { strongSelf.interface.forwardMessages([strongSelf.message.id]) } })) } - if canDeleteMessage(message, account: account) { - items.append(ContextMenuItem(tr(.messageContextDelete), handler: { [weak self] in + if canDeleteMessage(message, account: interface.context.account) { + items.append(ContextMenuItem(L10n.messageContextDelete, handler: { [weak self] in if let strongSelf = self { strongSelf.interface.deleteMessages([strongSelf.message.id]) } })) } - items.append(ContextMenuItem(tr(.messageContextGoto), handler: { [weak self] in + items.append(ContextMenuItem(L10n.messageContextGoto, handler: { [weak self] in if let strongSelf = self { - strongSelf.interface.focusMessageId(nil, strongSelf.message.id, .center(id: 0, animated: false, focus: false, inset: 0)) + strongSelf.interface.focusMessageId(nil, strongSelf.message.id, .center(id: 0, innerId: nil, animated: false, focus: .init(focus: false), inset: 0)) } })) @@ -75,26 +73,69 @@ class PeerMediaRowItem: TableRowItem { return .single(items) } + override var instantlyResize: Bool { + return true + } + override func viewClass() -> AnyClass { return PeerMediaRowView.self } + + var separatorOffset: CGFloat { + return 10 + 40 + viewType.innerInset.left + } } -private let selectedImage = #imageLiteral(resourceName: "Icon_SelectionChecked").precomposed() -private let unselectedImage = #imageLiteral(resourceName: "Icon_SelectionUncheck").precomposed() - class PeerMediaRowView : TableRowView,ViewDisplayDelegate,Notifable { - + let containerView: GeneralRowContainerView = GeneralRowContainerView(frame: NSZeroRect) var contentView:View = View() - private var selectingControl:SelectingControl = SelectingControl(unselectedImage:unselectedImage, selectedImage:selectedImage) + private let separatorView = View() + private var selectingControl:SelectingControl? required init(frame frameRect: NSRect) { super.init(frame: frameRect) - super.addSubview(selectingControl) - super.addSubview(contentView) + containerView.addSubview(contentView) + containerView.addSubview(separatorView) + super.addSubview(containerView) contentView.displayDelegate = self - selectingControl.centerY(x:-selectingControl.frame.width) + + containerView.set(handler: { [weak self] _ in + if let item = self?.item as? PeerMediaRowItem { + if item.interface.presentation.state == .selecting { + item.interface.update({$0.withToggledSelectedMessage(item.message.id)}) + } + } + }, for: .Click) + + } + + override func updateColors() { + guard let item = item as? PeerMediaRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + self.separatorView.backgroundColor = theme.colors.border + } + + override func layout() { + guard let item = item as? PeerMediaRowItem else { + return + } + + let contentX = item.interface.presentation.state == .selecting ? item.viewType.innerInset.left + 22 + item.viewType.innerInset.left : item.viewType.innerInset.left + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + self.contentView.setFrameSize(NSMakeSize(self.containerView.frame.width - item.viewType.innerInset.left - item.viewType.innerInset.right, self.containerView.frame.height - item.viewType.innerInset.bottom - item.viewType.innerInset.top)) + self.contentView.centerY(x: contentX) + + self.separatorView.frame = NSMakeRect(item.separatorOffset + (item.interface.presentation.state == .selecting ? 22 + item.viewType.innerInset.left : 0), self.containerView.frame.height - .borderSize, self.containerView.frame.width - item.separatorOffset - item.viewType.innerInset.right, .borderSize) + + selectingControl?.centerY(x: item.interface.presentation.state == .selecting ? item.viewType.innerInset.left : -22) + super.layout() } func notify(with value: Any, oldValue:Any, animated:Bool) { @@ -116,21 +157,9 @@ class PeerMediaRowView : TableRowView,ViewDisplayDelegate,Notifable { } override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - if layer == contentView.layer { - - if let item = self.item as? PeerMediaRowItem { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.contentInset.left, layer.frame.height - .borderSize, layer.frame.width - item.contentInset.left - item.contentInset.right, .borderSize)) - } - } - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - contentView.setFrameSize(newSize) } + override func set(item: TableRowItem, animated: Bool) { @@ -141,8 +170,11 @@ class PeerMediaRowView : TableRowView,ViewDisplayDelegate,Notifable { if let item = self.item as? PeerMediaRowItem { item.interface.add(observer: self) - updateSelectingMode(with: item.interface.presentation.state == .selecting) + updateSelectingMode(with: item.interface.presentation.state == .selecting, animated: animated) + + separatorView.isHidden = !item.viewType.hasBorder } + needsLayout = true } override func viewDidMoveToSuperview() { @@ -152,36 +184,47 @@ class PeerMediaRowView : TableRowView,ViewDisplayDelegate,Notifable { item.interface.remove(observer: self) } else { item.interface.add(observer: self) + updateSelectingMode(with: item.interface.presentation.state == .selecting, animated: !NSIsEmptyRect(visibleRect)) } } } - override func mouseDown(with event: NSEvent) { - super.mouseDown(with: event) - if let item = item as? PeerMediaRowItem { - if item.interface.presentation.state == .selecting { - item.interface.update({$0.withToggledSelectedMessage(item.message.id)}) - } - } - } - - - func updateSelectingMode(with selectingMode:Bool, animated:Bool = false) { if let item = item as? PeerMediaRowItem { + + containerView.userInteractionEnabled = selectingMode + let to:NSPoint if selectingMode { - to = NSMakePoint(35,0) + to = NSMakePoint(item.viewType.innerInset.left + 22 + item.viewType.innerInset.left, self.contentView.frame.minY) } else { - to = NSMakePoint(0,0) + to = NSMakePoint(item.viewType.innerInset.left, self.contentView.frame.minY) } + self.separatorView.change(pos: NSMakePoint(item.separatorOffset + (selectingMode ? 22 + item.viewType.innerInset.left : 0), self.containerView.frame.height - .borderSize), animated: animated) contentView.change(pos: to, animated: animated) - let selectingFrom = NSMakePoint(-selectingControl.frame.width,selectingControl.frame.minY) - let selectingTo = NSMakePoint(20.0 - floorToScreenPixels(selectingControl.frame.width/2.0),selectingControl.frame.minY) - selectingControl.change(pos: selectingMode ? selectingTo : selectingFrom, animated: animated) - selectingControl.set(selected: item.interface.presentation.isSelectedMessageId(item.message.id), animated: animated) + + if selectingMode { + if selectingControl == nil { + selectingControl = SelectingControl(unselectedImage: theme.icons.chatToggleUnselected, selectedImage: theme.icons.chatToggleSelected) + containerView.addSubview(selectingControl!) + selectingControl!.centerY(x: -22) + selectingControl?.change(pos: NSMakePoint(item.viewType.innerInset.left, selectingControl!.frame.minY), animated: animated) + } + } else { + if let selectingControl = selectingControl { + let point = NSMakePoint(-22, selectingControl.frame.minY) + self.selectingControl = nil + selectingControl.change(pos: point, animated: animated, completion: { [weak selectingControl] _ in + selectingControl?.removeFromSuperview() + }) + } + } + selectingControl?.set(selected: item.interface.presentation.isSelectedMessageId(item.message.id), animated: animated) + + +// selectingControl.change(pos: selectingMode ? selectingTo : selectingFrom, animated: animated) } } diff --git a/Telegram-Mac/PeerMediaTouchBar.swift b/Telegram-Mac/PeerMediaTouchBar.swift new file mode 100644 index 0000000000..0cc63c7edd --- /dev/null +++ b/Telegram-Mac/PeerMediaTouchBar.swift @@ -0,0 +1,181 @@ +// +// PeerMediaTouchBar.swift +// Telegram +// +// Created by Mikhail Filimonov on 04/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +@available(OSX 10.12.2, *) +private func peerMediaTouchBarItems(presentation: ChatPresentationInterfaceState) -> [NSTouchBarItem.Identifier] { + var items: [NSTouchBarItem.Identifier] = [] + items.append(.flexibleSpace) + if presentation.selectionState != nil { + items.append(.forward) + items.append(.delete) + } else { + items.append(.segmentMedias) + } + items.append(.flexibleSpace) + return items +} + +@available(OSX 10.12.2, *) +private extension NSTouchBarItem.Identifier { + static let segmentMedias = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.sharedMedia.segment") + static let forward = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.sharedMedia.forward") + static let delete = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.sharedMedia.delete") + +} + +@available(OSX 10.12.2, *) +class PeerMediaTouchBar: NSTouchBar, NSTouchBarDelegate, Notifable { + + private let modeDisposable = MetaDisposable() + private let chatInteraction: ChatInteraction + private let toggleMode: (PeerMediaCollectionMode) -> Void + private var currentMode: PeerMediaCollectionMode = .photoOrVideo + init(chatInteraction: ChatInteraction, currentMode: Signal, toggleMode: @escaping(PeerMediaCollectionMode) -> Void) { + self.chatInteraction = chatInteraction + self.toggleMode = toggleMode + super.init() + self.delegate = self + chatInteraction.add(observer: self) + self.defaultItemIdentifiers = peerMediaTouchBarItems(presentation: chatInteraction.presentation) + modeDisposable.set(currentMode.start(next: { [weak self] mode in + if let mode = mode { + let view = ((self?.item(forIdentifier: .segmentMedias) as? NSCustomTouchBarItem)?.view as? NSSegmentedControl) + let selected = Int(mode.rawValue + 1) + if selected > 0 { + view?.setSelected(true, forSegment: Int(mode.rawValue + 1)) + self?.currentMode = mode + } + } + })) + } + + private func updateUserInterface() { + for identifier in itemIdentifiers { + switch identifier { + case .forward: + let button = (item(forIdentifier: identifier) as? NSCustomTouchBarItem)?.view as? NSButton + button?.bezelColor = chatInteraction.presentation.canInvokeBasicActions.forward ? theme.colors.accent : nil + button?.isEnabled = chatInteraction.presentation.canInvokeBasicActions.forward + + case .delete: + let button = (item(forIdentifier: identifier) as? NSCustomTouchBarItem)?.view as? NSButton + button?.bezelColor = chatInteraction.presentation.canInvokeBasicActions.delete ? theme.colors.redUI : nil + button?.isEnabled = chatInteraction.presentation.canInvokeBasicActions.delete + case .segmentMedias: + let view = ((item(forIdentifier: identifier) as? NSCustomTouchBarItem)?.view as? NSSegmentedControl) + view?.setSelected(true, forSegment: Int(self.currentMode.rawValue)) + default: + break + } + } + } + + deinit { + chatInteraction.remove(observer: self) + modeDisposable.dispose() + } + + func isEqual(to other: Notifable) -> Bool { + return false + } + + func notify(with value: Any, oldValue: Any, animated: Bool) { + if let value = value as? ChatPresentationInterfaceState { + self.defaultItemIdentifiers = peerMediaTouchBarItems(presentation: value) + updateUserInterface() + } + } + + @objc private func segmentMediasAction(_ sender: Any?) { + if let sender = sender as? NSSegmentedControl { + switch sender.selectedSegment { + case 0: + toggleMode(.photoOrVideo) + case 1: + toggleMode(.file) + case 2: + toggleMode(.webpage) + case 3: + toggleMode(.music) + case 4: + toggleMode(.voice) + default: + break + } + } + } + + @objc private func forwardMessages() { + chatInteraction.forwardSelectedMessages() + } + @objc private func deleteMessages() { + chatInteraction.deleteSelectedMessages() + } + + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .segmentMedias: + let item = NSCustomTouchBarItem(identifier: identifier) + + let segment = NSSegmentedControl() + segment.segmentStyle = .automatic + segment.segmentCount = 5 + segment.setLabel(L10n.peerMediaMedia, forSegment: 0) + segment.setLabel(L10n.peerMediaFiles, forSegment: 1) + segment.setLabel(L10n.peerMediaLinks, forSegment: 2) + segment.setLabel(L10n.peerMediaAudio, forSegment: 3) + segment.setLabel(L10n.peerMediaVoice, forSegment: 4) + + segment.setWidth(93, forSegment: 0) + segment.setWidth(93, forSegment: 1) + segment.setWidth(93, forSegment: 2) + segment.setWidth(93, forSegment: 3) + segment.setWidth(93, forSegment: 4) + + segment.trackingMode = .selectOne + segment.target = self + segment.action = #selector(segmentMediasAction(_:)) + item.view = segment + return item + case .forward: + let item = NSCustomTouchBarItem(identifier: identifier) + let icon = NSImage(named: NSImage.Name("Icon_TouchBar_MessagesForward"))! + let button = NSButton(title: L10n.messageActionsPanelForward, image: icon, target: self, action: #selector(forwardMessages)) + button.addWidthConstraint(size: 160) + button.bezelColor = theme.colors.accent + button.imageHugsTitle = true + button.isEnabled = chatInteraction.presentation.canInvokeBasicActions.forward + item.view = button + item.customizationLabel = button.title + return item + case .delete: + let item = NSCustomTouchBarItem(identifier: identifier) + let icon = NSImage(named: NSImage.Name("Icon_TouchBar_MessagesDelete"))! + let button = NSButton(title: L10n.messageActionsPanelDelete, image: icon, target: self, action: #selector(deleteMessages)) + button.addWidthConstraint(size: 160) + button.bezelColor = theme.colors.redUI + button.imageHugsTitle = true + button.isEnabled = chatInteraction.presentation.canInvokeBasicActions.delete + item.view = button + item.customizationLabel = button.title + return item + default: + return nil + } + } + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/PeerMediaVoiceRowItem.swift b/Telegram-Mac/PeerMediaVoiceRowItem.swift new file mode 100644 index 0000000000..e3fefbc52a --- /dev/null +++ b/Telegram-Mac/PeerMediaVoiceRowItem.swift @@ -0,0 +1,379 @@ +// +// PeerMediaVoiceRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +class PeerMediaVoiceRowItem: PeerMediaRowItem { + fileprivate let file:TelegramMediaFile + fileprivate let titleLayout: TextViewLayout + fileprivate let nameLayout: TextViewLayout + override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ object: PeerMediaSharedEntry, viewType: GeneralViewType = .legacy) { + let message = object.message! + file = message.media[0] as! TelegramMediaFile + + let formatter = DateFormatter() + formatter.dateStyle = .medium + + let date = Date(timeIntervalSince1970: TimeInterval(object.message!.timestamp) - interface.context.timeDifference) + + + titleLayout = TextViewLayout(.initialize(string: formatter.string(from: date), color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 1) + + var peer:Peer? = message.chatPeer(interface.context.peerId) + + var title:String = peer?.displayTitle ?? "" + if let _peer = messageMainPeer(message) as? TelegramChannel, case .broadcast(_) = _peer.info { + title = _peer.displayTitle + peer = _peer + } + + nameLayout = TextViewLayout(.initialize(string: title, color: theme.colors.grayText, font: .normal(.short)), maximumNumberOfLines: 1) + + super.init(initialSize, interface, object, viewType: viewType) + + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + nameLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + titleLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + return success + } + + override func viewClass() -> AnyClass { + return PeerMediaVoiceRowView.self + } +} + + +final class PeerMediaVoiceRowView : PeerMediaRowView, APDelegate { + private let titleView: TextView = TextView() + private let nameView: TextView = TextView() + private let progressView:RadialProgressView = RadialProgressView() + private let statusDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private var player:GIFPlayerView = GIFPlayerView() + private let resourceDataDisposable = MetaDisposable() + private let unreadDot: View = View() + private var instantVideoData: AVGifData? { + didSet { + updatePlayerIfNeeded() + } + } + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(titleView) + addSubview(nameView) + addSubview(player) + addSubview(progressView) + addSubview(unreadDot) + player.setFrameSize(40, 40) + unreadDot.setFrameSize(NSMakeSize(6, 6)) + unreadDot.layer?.cornerRadius = 3 + progressView.fetchControls = FetchControls(fetch: { [weak self] in + self?.executeInteraction(true) + }) + + titleView.userInteractionEnabled = false + titleView.isSelectable = false + nameView.userInteractionEnabled = false + nameView.isSelectable = false + } + + func removeNotificationListeners() { + NotificationCenter.default.removeObserver(self) + } + + @objc func updatePlayerIfNeeded() { + player.set(data: acceptVisibility ? instantVideoData : nil) + } + + var acceptVisibility:Bool { + return window != nil && window!.isKeyWindow && !NSIsEmptyRect(visibleRect) + } + + func updateListeners() { + if let window = window { + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSWindow.didResignKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.boundsDidChangeNotification, object: item?.table?.clipView) + NotificationCenter.default.addObserver(self, selector: #selector(updatePlayerIfNeeded), name: NSView.frameDidChangeNotification, object: item?.table?.view) + } else { + removeNotificationListeners() + } + } + + override func viewDidMoveToWindow() { + updateListeners() + updatePlayerIfNeeded() + } + + + func open() { + + guard let item = item as? PeerMediaVoiceRowItem else {return} + + if let controller = globalAudio, let song = controller.currentSong, song.entry.isEqual(to: item.message) { + controller.playOrPause() + } else { + + let controller:APController = APChatVoiceController(account: item.interface.context.account, peerId: item.message.id.peerId, index: MessageIndex(item.message)) + item.interface.inlineAudioPlayer(controller) + controller.start() + } + } + + + + func fetch() { + if let item = item as? PeerMediaVoiceRowItem { + fetchDisposable.set(messageMediaFileInteractiveFetched(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference.init(item.message), media: item.file)).start()) + } + } + + + func cancelFetching() { + if let item = item as? PeerMediaVoiceRowItem { + messageMediaFileCancelInteractiveFetch(context: item.interface.context, messageId: item.message.id, fileReference: FileMediaReference.message(message: MessageReference.init(item.message), media: item.file)) + } + } + + func songDidChanged(song: APSongItem, for controller: APController) { + checkState() + } + func songDidChangedState(song: APSongItem, for controller: APController) { + checkState() + } + + func songDidStartPlaying(song:APSongItem, for controller:APController) { + + } + func songDidStopPlaying(song:APSongItem, for controller:APController) { + + } + func playerDidChangedTimebase(song:APSongItem, for controller:APController) { + + } + + func audioDidCompleteQueue(for controller:APController) { + + } + + func delete() -> Void { + guard let item = item as? PeerMediaVoiceRowItem else {return} + let messageId = item.message.id + let mediaBox = item.interface.context.account.postbox.mediaBox + _ = item.interface.context.account.postbox.transaction { transaction -> Void in + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: [messageId]) + }.start() + } + + func executeInteraction(_ isControl:Bool) -> Void { + guard let item = item as? PeerMediaVoiceRowItem else {return} + + if let fetchStatus = self.fetchStatus { + switch fetchStatus { + case .Fetching: + if isControl { + if item.message.flags.contains(.Unsent) && !item.message.flags.contains(.Failed) { + delete() + } + cancelFetching() + } else { + //open() + } + case .Remote: + fetch() + //open() + case .Local: + open() + break + } + } + } + + + deinit { + statusDisposable.dispose() + fetchDisposable.dispose() + resourceDataDisposable.dispose() + player.set(data: nil) + removeNotificationListeners() + } + + var fetchStatus: MediaResourceStatus? { + didSet { + if let fetchStatus = fetchStatus { + switch fetchStatus { + case let .Fetching(_, progress): + progressView.state = .Fetching(progress: progress, force: false) + case .Remote: + progressView.state = .Remote + case .Local: + progressView.state = .Play + } + } + } + } + + override func updateSelectingMode(with selectingMode: Bool, animated: Bool = false) { + super.updateSelectingMode(with: selectingMode, animated: animated) + progressView.userInteractionEnabled = !selectingMode + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateColors() { + super.updateColors() + titleView.backgroundColor = backdorColor + nameView.backgroundColor = backdorColor + unreadDot.backgroundColor = theme.colors.accent + } + + override func layout() { + super.layout() + + guard let item = item as? PeerMediaVoiceRowItem else {return} + + let center = floorToScreenPixels(backingScaleFactor, contentView.frame.height / 2) + + titleView.setFrameOrigin(item.contentInset.left, center - titleView.frame.height - 1) + nameView.setFrameOrigin(item.contentInset.left, center + 1) + + progressView.centerY(x: 0) + player.centerY(x: 0) + + unreadDot.setFrameOrigin(titleView.frame.maxX + 5, center - titleView.frame.height / 2 - unreadDot.frame.height / 2) + } + + var isIncomingConsumed:Bool { + var isConsumed:Bool = false + if let parent = (item as? PeerMediaRowItem)?.message { + for attr in parent.attributes { + if let attr = attr as? ConsumableContentMessageAttribute { + isConsumed = attr.consumed + break + } + } + } + return isConsumed + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? PeerMediaVoiceRowItem else {return} + + titleView.update(item.titleLayout) + nameView.update(item.nameLayout) + + unreadDot.isHidden = isIncomingConsumed + + updateListeners() + + if item.file.isInstantVideo { + let size = player.frame.size + player.layer?.cornerRadius = player.frame.height / 2 + + let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: item.file.previewRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + player.setSignal( chatMessagePhoto(account: item.interface.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: image), scale: backingScaleFactor)) + let arguments = TransformImageArguments(corners: ImageCorners(radius: 20), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets()) + player.set(arguments: arguments) + + + resourceDataDisposable.set((item.interface.context.account.postbox.mediaBox.resourceData(item.file.resource) |> deliverOnResourceQueue |> map { data in return data.complete ? AVGifData.dataFrom(data.path) : nil} |> deliverOnMainQueue).start(next: { [weak self] data in + self?.instantVideoData = data + })) + + } else { + player.setSignal(signal: .single(TransformImageResult(nil, false))) + player.set(data: nil) + instantVideoData = nil + resourceDataDisposable.set(nil) + } + + + + var updatedStatusSignal: Signal + + let file:TelegramMediaFile = item.file + + if item.message.flags.contains(.Unsent) && !item.message.flags.contains(.Failed) { + updatedStatusSignal = combineLatest(chatMessageFileStatus(account: item.interface.context.account, file: file), item.interface.context.account.pendingMessageManager.pendingMessageStatus(item.message.id)) + |> map { resourceStatus, pendingStatus -> MediaResourceStatus in + if let pendingStatus = pendingStatus.0 { + return .Fetching(isActive: true, progress: pendingStatus.progress) + } else { + return resourceStatus + } + } |> deliverOnMainQueue + } else { + updatedStatusSignal = chatMessageFileStatus(account: item.interface.context.account, file: file) |> deliverOnMainQueue + } + + self.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self { + strongSelf.fetchStatus = status + + switch status { + case let .Fetching(_, progress): + strongSelf.progressView.state = .Fetching(progress: progress, force: false) + case .Remote: + strongSelf.progressView.state = .Remote + case .Local: + strongSelf.progressView.state = .Play + } + } + })) + + checkState() + + needsLayout = true + + if item.automaticDownload.isDownloable(item.message) { + fetch() + } + + } + + func checkState() { + guard let item = item as? PeerMediaVoiceRowItem else {return} + let backgroundColor: NSColor + let foregroundColor: NSColor + if let media = item.message.media.first as? TelegramMediaFile, media.isInstantVideo { + backgroundColor = .blackTransparent + foregroundColor = .white + } else { + backgroundColor = theme.colors.fileActivityBackground + foregroundColor = theme.colors.fileActivityForeground + } + if let controller = globalAudio, let song = controller.currentSong { + + + if song.entry.isEqual(to: item.message), case .playing = song.state { + progressView.theme = RadialProgressTheme(backgroundColor: backgroundColor, foregroundColor: foregroundColor, icon: theme.icons.chatMusicPause, iconInset:NSEdgeInsets(left:0)) + progressView.state = .Icon(image: theme.icons.chatMusicPause, mode: .normal) + } else { + progressView.theme = RadialProgressTheme(backgroundColor: backgroundColor, foregroundColor: foregroundColor, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + progressView.state = .Play + } + } else { + progressView.theme = RadialProgressTheme(backgroundColor: backgroundColor, foregroundColor: foregroundColor, icon: theme.icons.chatMusicPlay, iconInset:NSEdgeInsets(left:1)) + } + } + +} diff --git a/Telegram-Mac/PeerMediaWebpageRowContent.swift b/Telegram-Mac/PeerMediaWebpageRowContent.swift index 3b406d6a13..84b70b6a3b 100644 --- a/Telegram-Mac/PeerMediaWebpageRowContent.swift +++ b/Telegram-Mac/PeerMediaWebpageRowContent.swift @@ -8,22 +8,64 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + class PeerMediaWebpageRowItem: PeerMediaRowItem { - var textLayout:TextViewLayout? - var linkLayout:TextViewLayout? + private(set) var textLayout:TextViewLayout? + private(set) var linkLayouts:[TextViewLayout] = [] - var iconText:NSAttributedString? - var firstCharacter:String? - var icon:TelegramMediaImage? - var iconArguments:TransformImageArguments? - var thumb:CGImage? = nil - override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ account:Account, _ object: PeerMediaSharedEntry) { - super.init(initialSize,interface,account,object) - iconSize = NSMakeSize(50, 50) + private(set) var iconText:NSAttributedString? + private(set) var firstCharacter:String? + private(set) var icon:TelegramMediaImage? + private(set) var iconArguments:TransformImageArguments? + private(set) var thumb:CGImage? = nil + override init(_ initialSize:NSSize, _ interface:ChatInteraction, _ object: PeerMediaSharedEntry, viewType: GeneralViewType = .legacy) { + super.init(initialSize,interface,object, viewType: viewType) + + + var linkLayouts:[TextViewLayout] = [] + + var links:[NSAttributedString] = [] + + for attr in message.attributes { + if let attr = attr as? TextEntitiesMessageAttribute { + for entity in attr.entities { + inner: switch entity.type { + case .Email: + let attributed = NSMutableAttributedString() + let link = message.text.nsstring.substring(with: NSMakeRange(min(entity.range.lowerBound, message.text.length), max(min(entity.range.upperBound - entity.range.lowerBound, message.text.length - entity.range.lowerBound), 0))) + let range = attributed.append(string: link, color: theme.colors.link, font: .normal(.text)) + attributed.addAttribute(.link, value: inApp(for: link as NSString, context: interface.context, peerId: interface.peerId, openInfo: interface.openInfo, applyProxy: interface.applyProxy, confirm: false), range: range) + links.append(attributed) + case .Url: + let attributed = NSMutableAttributedString() + let link = message.text.nsstring.substring(with: NSMakeRange(min(entity.range.lowerBound, message.text.length), max(min(entity.range.upperBound - entity.range.lowerBound, message.text.length - entity.range.lowerBound), 0))) + let range = attributed.append(string: link, color: theme.colors.link, font: .normal(.text)) + attributed.addAttribute(.link, value: inApp(for: link as NSString, context: interface.context, peerId: interface.peerId, openInfo: interface.openInfo, applyProxy: interface.applyProxy, confirm: false), range: range) + links.append(attributed) + case let .TextUrl(url): + let attributed = NSMutableAttributedString() + let range = attributed.append(string: url, color: theme.colors.link, font: .normal(.text)) + attributed.addAttribute(.link, value: inApp(for: url as NSString, context: interface.context, peerId: + interface.peerId, openInfo: interface.openInfo, applyProxy: interface.applyProxy, confirm: false), range: range) + links.append(attributed) + default: + break inner + } + } + break + } + } + + for attributed in links { + let linkLayout = TextViewLayout(attributed, maximumNumberOfLines: 1, truncationType: .middle) + linkLayout.interactions = globalLinkExecutor + linkLayouts.append(linkLayout) + } if let webpage = message.media.first as? TelegramMediaWebpage { @@ -39,16 +81,16 @@ class PeerMediaWebpageRowItem: PeerMediaRowItem { var iconImageRepresentation:TelegramMediaImageRepresentation? = nil if let image = content.image { - iconImageRepresentation = smallestImageRepresentation(image.representations) + iconImageRepresentation = largestImageRepresentation(image.representations) } else if let file = content.file { - iconImageRepresentation = smallestImageRepresentation(file.previewRepresentations) + iconImageRepresentation = largestImageRepresentation(file.previewRepresentations) } if let iconImageRepresentation = iconImageRepresentation { - icon = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation]) + icon = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - let imageCorners = ImageCorners(radius: iconSize.width/2) - iconArguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageRepresentation.dimensions.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: NSEdgeInsets()) + let imageCorners = ImageCorners(radius: .cornerRadius) + iconArguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageRepresentation.dimensions.size.aspectFilled(PeerMediaIconSize), boundingSize: PeerMediaIconSize, intrinsicInsets: NSEdgeInsets()) } @@ -58,39 +100,20 @@ class PeerMediaWebpageRowItem: PeerMediaRowItem { if let text = content.text { let _ = attributedText.append(string: "\n") - let _ = attributedText.append(string: text, color: theme.colors.text, font: NSFont.normal(FontSize.text)) - attributedText.detectLinks(type: [.Links, .Mentions, .Hashtags], account: account, openInfo: interface.openInfo) + let _ = attributedText.append(string: text, color: theme.colors.text, font: .normal(.short)) + attributedText.detectLinks(type: [.Links], context: interface.context, openInfo: interface.openInfo) } - textLayout = TextViewLayout(attributedText, maximumNumberOfLines: 6, truncationType: .end) - - let linkAttributed:NSMutableAttributedString = NSMutableAttributedString() - let _ = linkAttributed.append(string: content.displayUrl, color: theme.colors.link, font: NSFont.normal(FontSize.text)) - linkAttributed.detectLinks(type: [.Links, .Mentions, .Hashtags], account: account, openInfo: interface.openInfo) - - linkLayout = TextViewLayout(linkAttributed, maximumNumberOfLines: 1, truncationType: .end) - } - } else { - - var link:String = "" - let links = ObjcUtils.textCheckingResults(forText: message.text, highlightMentionsAndTags: false, highlightCommands: false) - if let links = links, !links.isEmpty { - let range = (links[0] as! NSValue).rangeValue - link = message.text.nsstring.substring(with: range) - - let attr = NSMutableAttributedString() - _ = attr.append(string: link, color: theme.colors.link, font: .normal(.text)) - attr.detectLinks(type: [.Links]) - - linkLayout = TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end) + textLayout = TextViewLayout(attributedText, maximumNumberOfLines: 3, truncationType: .end) } - - var hostName: String = link - if let url = URL(string: link), let host = url.host, !host.isEmpty { + } else if let linkLayout = linkLayouts.first { + let attributed = linkLayout.attributedString + var hostName: String = attributed.string + if let url = URL(string: attributed.string), let host = url.host, !host.isEmpty { hostName = host firstCharacter = host.prefix(1) } else { - firstCharacter = link.prefix(1) + firstCharacter = "L" } let attributedText = NSMutableAttributedString() @@ -99,36 +122,97 @@ class PeerMediaWebpageRowItem: PeerMediaRowItem { if !hostName.isEmpty { let _ = attributedText.append(string: "\n") } - let _ = attributedText.append(string: message.text, color: theme.colors.text, font: NSFont.normal(.text)) - - textLayout = TextViewLayout(attributedText, maximumNumberOfLines: 6, truncationType: .end) + if message.text != linkLayout.attributedString.string { + let _ = attributedText.append(string: message.text, color: theme.colors.text, font: .normal(.short)) + } + textLayout = TextViewLayout(attributedText, maximumNumberOfLines: 3, truncationType: .end) } if icon == nil { - thumb = generateMediaEmptyLinkThumb(color: theme.colors.border, host: firstCharacter?.uppercased() ?? "H") + thumb = generateMediaEmptyLinkThumb(color: theme.colors.listBackground, textColor: theme.colors.listGrayText, host: firstCharacter?.uppercased() ?? "H") } textLayout?.interactions = globalLinkExecutor - linkLayout?.interactions = globalLinkExecutor + if message.stableId != UINT32_MAX { + textLayout?.interactions.menuItems = { [weak self] inside in + guard let `self` = self else {return .complete()} + return self.menuItems(in: NSZeroPoint) |> map { items in + var items = items + if let layout = self.textLayout, layout.selectedRange.hasSelectText { + let text = layout.attributedString.attributedSubstring(from: layout.selectedRange.range) + items.insert(ContextMenuItem(L10n.textCopy, handler: { + copyToClipboard(text.string) + }), at: 0) + items.insert(ContextSeparatorItem(), at: 1) + } + return items + } + } + } + + for linkLayout in linkLayouts { + linkLayout.interactions = TextViewInteractions(processURL: { [weak self] url in + if let webpage = self?.message.media.first as? TelegramMediaWebpage, let `self` = self { + if self.hasInstantPage { + showInstantPage(InstantPageViewController(self.interface.context, webPage: webpage, message: nil, saveToRecent: false)) + return + } + } + globalLinkExecutor.processURL(url) + }, copy: { [weak linkLayout] in + guard let linkLayout = linkLayout else {return false} + copyToClipboard(linkLayout.attributedString.string) + return false + }, localizeLinkCopy: { link in + return L10n.textContextCopyLink + }) + } + + self.linkLayouts = linkLayouts + _ = makeSize(initialSize.width, oldWidth: 0) + + } + + var hasInstantPage: Bool { + if let webpage = message.media.first as? TelegramMediaWebpage { + if case let .Loaded(content) = webpage.content { + if let _ = content.instantPage { + if content.websiteName?.lowercased() == "instagram" || content.websiteName?.lowercased() == "twitter" || content.websiteName?.lowercased() == "telegram" { + return false + } + return true + } + } + } + return false + } + + var isArticle: Bool { + return message.stableId == UINT32_MAX } override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { - textLayout?.measure(width: width - contentInset.left - contentInset.right) - linkLayout?.measure(width: width - contentInset.left - contentInset.right) + let result = super.makeSize(width, oldWidth: oldWidth) + textLayout?.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right) + + for linkLayout in linkLayouts { + linkLayout.measure(width: self.blockWidth - contentInset.left - contentInset.right - self.viewType.innerInset.left - self.viewType.innerInset.right - (hasInstantPage ? 10 : 0)) + } var textSizes:CGFloat = 0 if let tLayout = textLayout { textSizes += tLayout.layoutSize.height } - if let lLayout = linkLayout { - textSizes += lLayout.layoutSize.height + for linkLayout in linkLayouts { + textSizes += linkLayout.layoutSize.height } - contentSize = NSMakeSize(width, max(textSizes + contentInset.top + contentInset.bottom + 2.0,60)) - return super.makeSize(width, oldWidth: oldWidth) + contentSize = NSMakeSize(width, max(textSizes + contentInset.top + contentInset.bottom + 2.0, 40)) + + return result } override func viewClass() -> AnyClass { @@ -141,48 +225,94 @@ class PeerMediaWebpageRowView : PeerMediaRowView { private var imageView:TransformImageView private var textView:TextView - private var linkView:TextView - + private var linkViews:[TextView] = [] + private var ivImage: ImageView? = nil required init(frame frameRect: NSRect) { - imageView = TransformImageView(frame:NSMakeRect(10, 5, 50.0, 50.0)) + imageView = TransformImageView(frame:NSMakeRect(0, 0, PeerMediaIconSize.width, PeerMediaIconSize.height)) textView = TextView() - linkView = TextView() super.init(frame: frameRect) - linkView.isSelectable = false addSubview(imageView) addSubview(textView) - addSubview(linkView) - } override func layout() { super.layout() if let item = item as? PeerMediaWebpageRowItem { - textView.update(item.textLayout, origin: NSMakePoint(item.contentInset.left,item.contentInset.top)) - linkView.isHidden = item.linkLayout == nil - linkView.update(item.linkLayout, origin: NSMakePoint(item.contentInset.left,textView.frame.maxY + 2.0)) + ivImage?.setFrameOrigin(item.contentInset.left, textView.frame.maxY + 6.0) + textView.setFrameOrigin(NSMakePoint(item.contentInset.left,item.contentInset.top)) + + var linkY: CGFloat = textView.frame.maxY + 2.0 + + for linkView in self.linkViews { + linkView.setFrameOrigin(NSMakePoint(item.contentInset.left + (item.hasInstantPage ? 10 : 0), linkY)) + linkY += linkView.frame.height + } + + } + } + + override func mouseUp(with event: NSEvent) { + guard let item = item as? PeerMediaWebpageRowItem, item.isArticle else { + super.mouseUp(with: event) + return } + // item.linkLayout?.interactions.processURL(event) + } override func set(item: TableRowItem, animated: Bool) { super.set(item: item,animated:animated) textView.backgroundColor = backdorColor - linkView.backgroundColor = backdorColor if let item = item as? PeerMediaWebpageRowItem { + textView.userInteractionEnabled = !item.isArticle + + textView.update(item.textLayout, origin: NSMakePoint(item.contentInset.left,item.contentInset.top)) + + + while self.linkViews.count > item.linkLayouts.count { + let last = self.linkViews.removeLast() + last.removeFromSuperview() + } + while self.linkViews.count < item.linkLayouts.count { + let new = TextView() + addSubview(new) + self.linkViews.append(new) + } + + var linkY: CGFloat = textView.frame.maxY + 2.0 + + for (i, linkView) in self.linkViews.enumerated() { + let linkLayout = item.linkLayouts[i] + linkView.backgroundColor = backdorColor + linkView.update(linkLayout, origin: NSMakePoint(item.contentInset.left + (item.hasInstantPage ? 10 : 0), linkY)) + linkY += linkLayout.layoutSize.height + } + + if item.hasInstantPage { + if ivImage == nil { + ivImage = ImageView() + } + ivImage!.image = theme.icons.chatInstantView + ivImage!.sizeToFit() + addSubview(ivImage!) + } else { + ivImage?.removeFromSuperview() + ivImage = nil + } - let updateIconImageSignal:Signal<(TransformImageArguments) -> DrawingContext?,NoError> + let updateIconImageSignal:Signal if let icon = item.icon { - updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photo: icon, scale: backingScaleFactor, small:true) + updateIconImageSignal = chatWebpageSnippetPhoto(account: item.interface.context.account, imageReference: ImageMediaReference.message(message: MessageReference(item.message), media: icon), scale: backingScaleFactor, small:true) } else { - updateIconImageSignal = .single({_ in return nil}) + updateIconImageSignal = .single(ImageDataTransformation()) } if let arguments = item.iconArguments { imageView.set(arguments: arguments) - imageView.setSignal(account: item.account, signal: updateIconImageSignal) + imageView.setSignal( updateIconImageSignal) } if item.icon == nil { @@ -196,7 +326,9 @@ class PeerMediaWebpageRowView : PeerMediaRowView { override func updateSelectingMode(with selectingMode:Bool, animated:Bool = false) { super.updateSelectingMode(with: selectingMode, animated: animated) self.textView.isSelectable = !selectingMode - self.linkView.userInteractionEnabled = !selectingMode + for linkView in self.linkViews { + linkView.userInteractionEnabled = !selectingMode + } self.textView.userInteractionEnabled = !selectingMode } diff --git a/Telegram-Mac/PeerPhotos.swift b/Telegram-Mac/PeerPhotos.swift new file mode 100644 index 0000000000..e53f637d76 --- /dev/null +++ b/Telegram-Mac/PeerPhotos.swift @@ -0,0 +1,122 @@ +// +// PeerPhotos.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import TelegramCore +import SyncCore + +private struct PeerPhotos { + let photos: [TelegramPeerPhoto] + let time: TimeInterval +} + +private var peerAvatars:Atomic<[PeerId: PeerPhotos]> = Atomic(value: [:]) + + +func syncPeerPhotos(peerId: PeerId) -> [TelegramPeerPhoto] { + return peerAvatars.with { $0[peerId].map { $0.photos } ?? [] } +} + +func peerPhotos(account: Account, peerId: PeerId, force: Bool = false) -> Signal<[TelegramPeerPhoto], NoError> { + let photos = peerAvatars.with { $0[peerId] } + if let photos = photos, photos.time > Date().timeIntervalSince1970, !force { + return .single(photos.photos) + } else { + return .single(peerAvatars.with { $0[peerId]?.photos } ?? []) |> then(requestPeerPhotos(postbox: account.postbox, network: account.network, peerId: peerId) |> delay(0.4, queue: .concurrentDefaultQueue()) |> map { photos in + return peerAvatars.modify { value in + var value = value + value[peerId] = PeerPhotos(photos: photos, time: Date().timeIntervalSince1970 + 5 * 60) + return value + }[peerId]?.photos ?? [] + }) + } +} + + +func peerPhotosGalleryEntries(account: Account, peerId: PeerId, firstStableId: AnyHashable) -> Signal<(entries: [GalleryEntry], selected:Int), NoError> { + return combineLatest(queue: prepareQueue, peerPhotos(account: account, peerId: peerId, force: true), account.postbox.loadedPeerWithId(peerId)) |> map { photos, peer in + + var entries: [GalleryEntry] = [] + + + var representations:[TelegramMediaImageRepresentation] = []//peer.profileImageRepresentations + if let representation = peer.smallProfileImage { + representations.append(representation) + } + if let representation = peer.largeProfileImage { + representations.append(representation) + } + + let videoRepresentations: [TelegramMediaImage.VideoRepresentation] = [] + + + var image:TelegramMediaImage? = nil + var msg: Message? = nil + if let base = firstStableId.base as? ChatHistoryEntryId, case let .message(message) = base { + let action = message.media.first as! TelegramMediaAction + switch action.action { + case let .photoUpdated(updated): + image = updated + msg = message + default: + break + } + } + + if image == nil { + image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: 0), representations: representations, videoRepresentations: videoRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + } + + let firstEntry: GalleryEntry = .photo(index: 0, stableId: firstStableId, photo: image!, reference: nil, peer: peer, message: msg, date: 0) + + var foundIndex: Bool = peerId.namespace == Namespaces.Peer.CloudUser && !photos.isEmpty + var currentIndex: Int = 0 + + var photosDate:[TimeInterval] = [] + for i in 0 ..< photos.count { + let photo = photos[i] + photosDate.append(TimeInterval(photo.date)) + if let base = firstStableId.base as? ChatHistoryEntryId, case let .message(message) = base { + let action = message.media.first as! TelegramMediaAction + switch action.action { + case let .photoUpdated(updated): + if photo.image.id == updated?.id { + currentIndex = i + foundIndex = true + } + default: + break + } + } else if i == 0 { + foundIndex = true + currentIndex = i + + } + } + for i in 0 ..< photos.count { + if currentIndex == i && foundIndex { + let image = TelegramMediaImage.init(imageId: photos[i].image.imageId, representations: image!.representations, videoRepresentations: photos[i].image.videoRepresentations, immediateThumbnailData: photos[i].image.immediateThumbnailData, reference: photos[i].image.reference, partialReference: photos[i].image.partialReference, flags: photos[i].image.flags) + + entries.append(.photo(index: photos[i].index, stableId: firstStableId, photo: image, reference: photos[i].reference, peer: peer, message: nil, date: photosDate[i])) + } else { + entries.append(.photo(index: photos[i].index, stableId: photos[i].image.imageId, photo: photos[i].image, reference: photos[i].reference, peer: peer, message: nil, date: photosDate[i])) + } + } + + if !foundIndex && entries.isEmpty { + entries.append(firstEntry) + } + + return (entries: entries, selected: currentIndex) + + } +} diff --git a/Telegram-Mac/PeerPhotosMonthItem.swift b/Telegram-Mac/PeerPhotosMonthItem.swift new file mode 100644 index 0000000000..69e09dd9c7 --- /dev/null +++ b/Telegram-Mac/PeerPhotosMonthItem.swift @@ -0,0 +1,961 @@ +// +// PeerPhotosMonthItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 17.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import TGUIKit +import Postbox +import SwiftSignalKit + +private struct LayoutItem : Equatable { + static func == (lhs: LayoutItem, rhs: LayoutItem) -> Bool { + return lhs.message == rhs.message && lhs.corners == rhs.corners && lhs.frame == rhs.frame + } + + let message: Message + let frame: NSRect + let viewType:MediaCell.Type + let corners:ImageCorners + let chatInteraction: ChatInteraction +} + +class PeerPhotosMonthItem: GeneralRowItem { + private let items:[Message] + fileprivate let context: AccountContext + private var contentHeight: CGFloat = 0 + + fileprivate private(set) var layoutItems:[LayoutItem] = [] + fileprivate private(set) var itemSize: NSSize = NSZeroSize + fileprivate let chatInteraction: ChatInteraction + fileprivate let gallerySupplyment: InteractionContentViewProtocol + init(_ initialSize: NSSize, stableId: AnyHashable, viewType: GeneralViewType, context: AccountContext, chatInteraction: ChatInteraction, gallerySupplyment: InteractionContentViewProtocol, items: [Message]) { + self.items = items + self.context = context + self.gallerySupplyment = gallerySupplyment + self.chatInteraction = chatInteraction + + super.init(initialSize, stableId: stableId, viewType: viewType, inset: NSEdgeInsets()) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + + if !items.isEmpty { + var t: time_t = time_t(TimeInterval(items[0].timestamp)) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + if timeinfo.tm_mon == 2 { + var bp:Int = 0 + bp += 1 + } + + } + + var rowCount:Int = 4 + var perWidth: CGFloat = 0 + while true { + let maximum = self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right - CGFloat(rowCount * 2) + perWidth = maximum / CGFloat(rowCount) + if perWidth >= 90 { + break + } else { + rowCount -= 1 + } + } + assert(rowCount >= 1) + + let itemSize = NSMakeSize(ceil(perWidth) + 2, ceil(perWidth) + 2) + + layoutItems.removeAll() + var point: CGPoint = CGPoint(x: self.viewType.innerInset.left, y: self.viewType.innerInset.top + itemSize.height) + for (i, message) in self.items.enumerated() { + let viewType: MediaCell.Type + if let file = message.media.first as? TelegramMediaFile { + if file.isAnimated && file.isVideo { + viewType = MediaGifCell.self + } else { + viewType = MediaVideoCell.self + } + } else { + viewType = MediaPhotoCell.self + } + + var topLeft: ImageCorner = .Corner(0) + var topRight: ImageCorner = .Corner(0) + var bottomLeft: ImageCorner = .Corner(0) + var bottomRight: ImageCorner = .Corner(0) + + if self.items.count < rowCount { + if message == self.items.first { + if self.viewType.position != .last { + topLeft = .Corner(.cornerRadius) + } + bottomLeft = .Corner(.cornerRadius) + } + } else if self.items.count == rowCount { + if message == self.items.first { + if self.viewType.position != .last { + topLeft = .Corner(.cornerRadius) + } + bottomLeft = .Corner(.cornerRadius) + } else if message == self.items.last { + if message == self.items.last { + if self.viewType.position != .last { + topRight = .Corner(.cornerRadius) + } + bottomRight = .Corner(.cornerRadius) + } + } + } else { + let i = i + 1 + let firstLine = i <= rowCount + let div = (items.count % rowCount) == 0 ? rowCount : (items.count % rowCount) + let lastLine = i > (items.count - div) + + if firstLine { + if self.viewType.position != .last { + if i % rowCount == 1 { + topLeft = .Corner(.cornerRadius) + } else if i % rowCount == 0 { + topRight = .Corner(.cornerRadius) + } + } + } else if lastLine { + if i % rowCount == 1 { + bottomLeft = .Corner(.cornerRadius) + } else if i % rowCount == 0 { + bottomRight = .Corner(.cornerRadius) + } + } + } + + + let corners = ImageCorners(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight) + self.layoutItems.append(LayoutItem(message: message, frame: CGRect(origin: point.offsetBy(dx: 0, dy: -itemSize.height), size: itemSize), viewType: viewType, corners: corners, chatInteraction: self.chatInteraction)) + point.x += itemSize.width + if self.layoutItems.count % rowCount == 0, message != self.items.last { + point.y += itemSize.height + point.x = self.viewType.innerInset.left + } + } + self.itemSize = itemSize + self.contentHeight = point.y - self.viewType.innerInset.top + return true + } + + func contains(_ id: MessageId) -> Bool { + return layoutItems.contains(where: { $0.message.id == id}) + } + + override var height: CGFloat { + return self.contentHeight + self.viewType.innerInset.top + self.viewType.innerInset.bottom + } + + override var instantlyResize: Bool { + return true + } + + deinit { + + } + + override func viewClass() -> AnyClass { + return PeerPhotosMonthView.self + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + let layoutItem = layoutItems.first(where: { NSPointInRect(location, $0.frame) }) + if let layoutItem = layoutItem { + let message = layoutItem.message + if canForwardMessage(message, account: context.account) { + items.append(ContextMenuItem(L10n.messageContextForward, handler: { [weak self] in + self?.chatInteraction.forwardMessages([message.id]) + })) + } + if canDeleteMessage(message, account: context.account) { + items.append(ContextMenuItem(L10n.messageContextDelete, handler: { [weak self] in + self?.chatInteraction.deleteMessages([message.id]) + })) + } + items.append(ContextMenuItem(L10n.messageContextGoto, handler: { [weak self] in + self?.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: false, focus: .init(focus: true), inset: 0)) + })) + } + return .single(items) + } +} + +private class MediaCell : Control { + private var selectionView:SelectingControl? + fileprivate let imageView: TransformImageView + private(set) var layoutItem: LayoutItem? + fileprivate var context: AccountContext? + required init(frame frameRect: NSRect) { + imageView = TransformImageView(frame: NSMakeRect(1, 1, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + addSubview(imageView) + userInteractionEnabled = false + } + + override func mouseMoved(with event: NSEvent) { + superview?.superview?.mouseMoved(with: event) + } + override func mouseEntered(with event: NSEvent) { + superview?.superview?.mouseEntered(with: event) + } + override func mouseExited(with event: NSEvent) { + superview?.superview?.mouseExited(with: event) + } + func update(layout: LayoutItem, context: AccountContext, table: TableView?) { + let previousLayout = self.layoutItem + self.layoutItem = layout + self.context = context + if previousLayout != layout, !(self is MediaGifCell) { + let media: Media + let imageSize: NSSize + let arguments: TransformImageArguments + let cacheArguments: TransformImageArguments + let signal: Signal + if let image = layout.message.media.first as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions.size { + media = image + imageSize = largestSize.aspectFilled(NSMakeSize(150, 150)) + arguments = TransformImageArguments(corners: layout.corners, imageSize: imageSize, boundingSize: layout.frame.size, intrinsicInsets: NSEdgeInsets()) + cacheArguments = TransformImageArguments(corners: layout.corners, imageSize: imageSize, boundingSize: NSMakeSize(150, 150), intrinsicInsets: NSEdgeInsets()) + signal = mediaGridMessagePhoto(account: context.account, imageReference: ImageMediaReference.message(message: MessageReference(layout.message), media: image), scale: backingScaleFactor) + } else if let file = layout.message.media.first as? TelegramMediaFile { + media = file + let largestSize = file.previewRepresentations.last?.dimensions.size ?? file.imageSize + imageSize = largestSize.aspectFilled(NSMakeSize(150, 150)) + arguments = TransformImageArguments(corners: layout.corners, imageSize: imageSize, boundingSize: layout.frame.size, intrinsicInsets: NSEdgeInsets()) + cacheArguments = TransformImageArguments(corners: layout.corners, imageSize: imageSize, boundingSize: NSMakeSize(150, 150), intrinsicInsets: NSEdgeInsets()) + signal = chatMessageVideo(postbox: context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(layout.message), media: file), scale: backingScaleFactor) //mediaGridMessageVideo(postbox: context.account.postbox, fileReference: FileMediaReference.message(message: MessageReference(layout.message), media: file), scale: backingScaleFactor) + } else { + return + } + + self.imageView.set(arguments: arguments) + self.imageView.setSignal(signal: cachedMedia(media: media, arguments: cacheArguments, scale: backingScaleFactor), clearInstantly: true) + if !self.imageView.isFullyLoaded { + self.imageView.setSignal(signal, animate: true, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: cacheArguments, scale: System.backingScale) + } + }) + } + } + updateSelectionState(animated: false) + } + + override func copy() -> Any { + return imageView.copy() + } + + func innerAction() -> InvokeActionResult { + return .gallery + } + + func addAccesoryOnCopiedView(view: NSView) { + + } + + func updateMouse(_ inside: Bool) { + + } + + func updateSelectionState(animated: Bool) { + if let layoutItem = layoutItem { + if let selectionState = layoutItem.chatInteraction.presentation.selectionState { + let selected = selectionState.selectedIds.contains(layoutItem.message.id) + if let selectionView = self.selectionView { + selectionView.set(selected: selected, animated: animated) + } else { + selectionView = SelectingControl(unselectedImage: theme.icons.chatGroupToggleUnselected, selectedImage: theme.icons.chatGroupToggleSelected) + + addSubview(selectionView!) + selectionView?.set(selected: selected, animated: animated) + if animated { + selectionView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + selectionView?.layer?.animateScaleCenter(from: 0.5, to: 1.0, duration: 0.2) + } + } + } else { + if let selectionView = selectionView { + self.selectionView = nil + if animated { + selectionView.layer?.animateScaleCenter(from: 1.0, to: 0.5, duration: 0.2) + selectionView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak selectionView] completion in + selectionView?.removeFromSuperview() + }) + } else { + selectionView.removeFromSuperview() + } + } + } + needsLayout = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + imageView.frame = NSMakeRect(1, 1, frame.width - 2, frame.height - 2) + + if let selectionView = selectionView { + selectionView.setFrameOrigin(frame.width - selectionView.frame.width - 5, 5) + } + } +} + +private final class MediaPhotoCell : MediaCell { + +} + +private enum InvokeActionResult { + case nothing + case gallery +} + + + +private final class MediaVideoCell : MediaCell { + + + private final class VideoAutoplayView { + let mediaPlayer: MediaPlayer + let view: MediaPlayerView + + fileprivate var playTimer: SwiftSignalKit.Timer? + var status: MediaPlayerStatus? + + init(mediaPlayer: MediaPlayer, view: MediaPlayerView) { + self.mediaPlayer = mediaPlayer + self.view = view + mediaPlayer.actionAtEnd = .loop(nil) + } + + deinit { + view.removeFromSuperview() + playTimer?.invalidate() + } + } + + private let mediaPlayerStatusDisposable = MetaDisposable() + + private let progressView:RadialProgressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: playerPlayThumb)) + private let videoAccessory: ChatMessageAccessoryView = ChatMessageAccessoryView(frame: NSZeroRect) + private var status:MediaResourceStatus? + private var authenticStatus: MediaResourceStatus? + private let statusDisposable = MetaDisposable() + private let fetchingDisposable = MetaDisposable() + private let partDisposable = MetaDisposable() + + private var videoView:VideoAutoplayView? + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.addSubview(self.videoAccessory) + self.progressView.userInteractionEnabled = false + self.addSubview(self.progressView) + } + + override func updateMouse(_ inside: Bool) { + if let layout = self.layoutItem { + let file = layout.message.media.first as! TelegramMediaFile + if inside { + if file.isStreamable { + if videoView == nil { + let context = layout.chatInteraction.context + let player = MediaPlayer(postbox: context.account.postbox, reference: MediaResourceReference.media(media: AnyMediaReference.message(message: MessageReference(layout.message), media: file), resource: file.resource), streamable: true, video: true, preferSoftwareDecoding: true, enableSound: false, fetchAutomatically: false) + videoView = MediaVideoCell.VideoAutoplayView(mediaPlayer: player, view: MediaPlayerView(backgroundThread: true)) + + videoView?.view.setVideoLayerGravity(.resizeAspectFill) + + var posititionFlags: LayoutPositionFlags = [] + if layout.corners.topLeft.corner > 0 { + posititionFlags.insert(.top) + posititionFlags.insert(.left) + } + if layout.corners.topRight.corner > 0 { + posititionFlags.insert(.top) + posititionFlags.insert(.right) + } + if layout.corners.bottomLeft.corner > 0 { + posititionFlags.insert(.bottom) + posititionFlags.insert(.left) + } + if layout.corners.bottomRight.corner > 0 { + posititionFlags.insert(.bottom) + posititionFlags.insert(.right) + } + videoView?.view.positionFlags = posititionFlags.isEmpty ? nil : posititionFlags + videoView?.view.frame = self.imageView.frame + + videoView!.mediaPlayer.attachPlayerView(videoView!.view) + + videoView?.mediaPlayer.play() + + + self.addSubview(videoView!.view, positioned: .above, relativeTo: self.imageView) + + progressView.change(opacity: 0) + } + if let videoView = videoView { + mediaPlayerStatusDisposable.set((videoView.mediaPlayer.status |> deliverOnMainQueue).start(next: { [weak self] status in + self?.updateMediaStatus(status, animated: true) + })) + } + + + } else { + progressView.change(opacity: 1) + videoView = nil + mediaPlayerStatusDisposable.set(nil) + updateVideoAccessory(self.authenticStatus ?? .Remote, mediaPlayerStatus: nil, file: file, animated: true) + } + } else { + progressView.change(opacity: 1) + videoView = nil + mediaPlayerStatusDisposable.set(nil) + updateVideoAccessory(self.authenticStatus ?? .Remote, mediaPlayerStatus: nil, file: file, animated: true) + } + } + } + + private func updateMediaStatus(_ status: MediaPlayerStatus, animated: Bool = false) { + if let videoView = videoView, let media = self.layoutItem?.message.media.first as? TelegramMediaFile { + videoView.status = status + updateVideoAccessory(self.authenticStatus ?? .Local, mediaPlayerStatus: status, file: media, animated: animated) + + switch status.status { + case .playing: + videoView.playTimer?.invalidate() + videoView.playTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.updateVideoAccessory(self?.authenticStatus ?? .Local, mediaPlayerStatus: status, file: media, animated: animated) + }, queue: .mainQueue()) + + videoView.playTimer?.start() + default: + videoView.playTimer?.invalidate() + } + + + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func fetch() { + if let context = context, let layoutItem = self.layoutItem { + let file = layoutItem.message.media.first as! TelegramMediaFile + fetchingDisposable.set(messageMediaFileInteractiveFetched(context: context, messageId: layoutItem.message.id, fileReference: FileMediaReference.message(message: MessageReference(layoutItem.message), media: file)).start()) + } + } + + private func cancelFetching() { + if let context = context, let layoutItem = self.layoutItem { + let file = layoutItem.message.media.first as! TelegramMediaFile + messageMediaFileCancelInteractiveFetch(context: context, messageId: layoutItem.message.id, fileReference: FileMediaReference.message(message: MessageReference(layoutItem.message), media: file)) + } + } + + override func innerAction() -> InvokeActionResult { + if let file = layoutItem?.message.media.first as? TelegramMediaFile, let window = self.window { + switch progressView.state { + case .Fetching: + if NSPointInRect(self.convert(window.mouseLocationOutsideOfEventStream, from: nil), progressView.frame) { + cancelFetching() + } else if file.isStreamable { + return .gallery + } + case .Remote: + fetch() + default: + return .gallery + } + } + return .nothing + } + + func preloadStreamblePart() { + if let layoutItem = self.layoutItem { + let context = layoutItem.chatInteraction.context + if context.autoplayMedia.preloadVideos { + if let media = layoutItem.message.media.first as? TelegramMediaFile, let fileSize = media.size { + let reference = FileMediaReference.message(message: MessageReference(layoutItem.message), media: media) + let preload = combineLatest(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(media.resource), range: (0 ..< Int(2.0 * 1024 * 1024), .default), statsCategory: .video), fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: reference.resourceReference(media.resource), range: (max(0, fileSize - Int(256 * 1024)) ..< Int(Int32.max), .default), statsCategory: .video)) + partDisposable.set(preload.start()) + } + } + } + } + + private func updateVideoAccessory(_ status: MediaResourceStatus, mediaPlayerStatus: MediaPlayerStatus? = nil, file: TelegramMediaFile, animated: Bool) { + let maxWidth = frame.width - 10 + let text: String + + let status: MediaResourceStatus = .Local + + + if let status = mediaPlayerStatus, status.generationTimestamp > 0, status.duration > 0 { + text = String.durationTransformed(elapsed: Int(status.duration - (status.timestamp + (CACurrentMediaTime() - status.generationTimestamp)))) + } else { + text = String.durationTransformed(elapsed: file.videoDuration) + } + + videoAccessory.updateText(text, maxWidth: maxWidth, status: status, isStreamable: file.isStreamable, isCompact: true, animated: animated, fetch: { [weak self] in + self?.fetch() + }, cancelFetch: { [weak self] in + self?.cancelFetching() + }) + needsLayout = true + } + + override func update(layout: LayoutItem, context: AccountContext, table: TableView?) { + super.update(layout: layout, context: context, table: table) + + let file = layout.message.media.first as! TelegramMediaFile + + let updatedStatusSignal = chatMessageFileStatus(account: context.account, file: file) |> deliverOnMainQueue |> map { status -> (MediaResourceStatus, MediaResourceStatus) in + if file.isStreamable && layout.message.id.peerId.namespace != Namespaces.Peer.SecretChat { + return (.Local, status) + } + return (status, status) + } |> deliverOnMainQueue + + var first: Bool = true + + statusDisposable.set(updatedStatusSignal.start(next: { [weak self] status, authentic in + guard let `self` = self else {return} + + self.updateVideoAccessory(authentic, mediaPlayerStatus: self.videoView?.status, file: file, animated: !first) + first = false + self.status = status + self.authenticStatus = authentic + let progressStatus: MediaResourceStatus + switch authentic { + case .Fetching: + progressStatus = authentic + default: + progressStatus = status + } + switch progressStatus { + case let .Fetching(_, progress): + self.progressView.state = .Fetching(progress: progress, force: false) + case .Remote: + self.progressView.state = .Remote + case .Local: + self.progressView.state = .Play + } + })) + partDisposable.set(nil) + self.preloadStreamblePart() + } + + override func addAccesoryOnCopiedView(view: NSView) { + let videoAccessory = self.videoAccessory.copy() as! ChatMessageAccessoryView + if visibleRect.minY < videoAccessory.frame.midY && visibleRect.minY + visibleRect.height > videoAccessory.frame.midY { + videoAccessory.frame.origin.y = frame.height - videoAccessory.frame.maxY + view.addSubview(videoAccessory) + } + + let pView = RadialProgressView(theme: progressView.theme, twist: true) + pView.state = progressView.state + pView.frame = progressView.frame + if visibleRect.minY < progressView.frame.midY && visibleRect.minY + visibleRect.height > progressView.frame.midY { + pView.frame.origin.y = frame.height - progressView.frame.maxY + view.addSubview(pView) + } + } + + override func layout() { + super.layout() + progressView.center() + videoAccessory.setFrameOrigin(5, 5) + videoView?.view.frame = self.imageView.frame + } + + deinit { + statusDisposable.dispose() + fetchingDisposable.dispose() + partDisposable.dispose() + mediaPlayerStatusDisposable.dispose() + } +} + + + +private final class MediaGifCell : MediaCell { + private let gifView: GIFContainerView = GIFContainerView(frame: .zero) + private var status:MediaResourceStatus? + private let statusDisposable = MetaDisposable() + private let fetchingDisposable = MetaDisposable() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.addSubview(self.gifView) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func fetch() { + + } + + private func cancelFetching() { + + } + + override func innerAction() -> InvokeActionResult { + return .gallery + } + + + override func copy() -> Any { + return gifView.copy() + } + + override func update(layout: LayoutItem, context: AccountContext, table: TableView?) { + let previousLayout = self.layoutItem + super.update(layout: layout, context: context, table: table) + if layout != previousLayout { + let file = layout.message.media.first as! TelegramMediaFile + + let messageRefence = MessageReference(layout.message) + + let reference = FileMediaReference.message(message: messageRefence, media: file) + + var effectiveFile = reference + if let preview = file.videoThumbnails.first { + let updated = file.withUpdatedResource(preview.resource) + effectiveFile = FileMediaReference.message(message: messageRefence, media: updated) + } + let signal = chatMessageVideo(postbox: context.account.postbox, fileReference: effectiveFile, scale: backingScaleFactor) + + + gifView.update(with: reference, size: frame.size, viewSize: frame.size, context: context, table: nil, iconSignal: signal) + gifView.userInteractionEnabled = false + + } + + + } + + + override func layout() { + super.layout() + gifView.frame = NSMakeRect(1, 1, frame.width - 2, frame.height - 2) + + } + + deinit { + statusDisposable.dispose() + fetchingDisposable.dispose() + } +} + + +private final class PeerPhotosMonthView : TableRowView, Notifable { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private var contentViews:[Optional] = [] + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.addSubview(self.containerView) + + containerView.set(handler: { [weak self] _ in + self?.action(event: .Down) + }, for: .Down) + + containerView.set(handler: { [weak self] _ in + self?.action(event: .MouseDragging) + }, for: .MouseDragging) + + containerView.set(handler: { [weak self] _ in + self?.action(event: .Click) + }, for: .Click) + } + + private var haveToSelectOnDrag: Bool = false + + + private weak var currentMouseCell: MediaCell? + + @objc override func updateMouse() { + super.updateMouse() + guard let window = self.window else { + return + } + let point = self.containerView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + let mediaCell = self.contentViews.first(where: { + return $0 != nil && NSPointInRect(point, $0!.frame) + })?.map { $0 } + + if currentMouseCell != mediaCell { + currentMouseCell?.updateMouse(false) + } + currentMouseCell = mediaCell + mediaCell?.updateMouse(window.isKeyWindow) + + } + + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateMouse() + } + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateMouse() + } + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateMouse() + } + + private func action(event: ControlEvent) { + guard let item = self.item as? PeerPhotosMonthItem, let window = window else { + return + } + let point = containerView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let layoutItem = item.layoutItems.first(where: { NSPointInRect(point, $0.frame) }) { + if layoutItem.chatInteraction.presentation.state == .selecting { + switch event { + case .MouseDragging: + layoutItem.chatInteraction.update { current in + if !haveToSelectOnDrag { + return current.withRemovedSelectedMessage(layoutItem.message.id) + } else { + return current.withUpdatedSelectedMessage(layoutItem.message.id) + } + } + case .Down: + layoutItem.chatInteraction.update { $0.withToggledSelectedMessage(layoutItem.message.id) } + haveToSelectOnDrag = layoutItem.chatInteraction.presentation.isSelectedMessageId(layoutItem.message.id) + default: + break + } + } else { + switch event { + case .Click: + let view = self.contentViews.compactMap { $0 }.first(where: { $0.layoutItem == layoutItem }) + if let view = view { + switch view.innerAction() { + case .gallery: + showChatGallery(context: item.context, message: layoutItem.message, item.gallerySupplyment, ChatMediaGalleryParameters(showMedia: { _ in}, showMessage: { message in + layoutItem.chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: false, focus: .init(focus: true), inset: 0)) + }, isWebpage: false, media: layoutItem.message.media.first!, automaticDownload: true), reversed: true) + case .nothing: + break + } + } + default: + break + } + } + } + } + + func notify(with value: Any, oldValue:Any, animated:Bool) { + if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { + let views = contentViews.compactMap { $0 } + for view in views { + if let item = view.layoutItem { + if (value.state == .selecting) != (oldValue.state == .selecting) || value.isSelectedMessageId(item.message.id) != oldValue.isSelectedMessageId(item.message.id) { + view.updateSelectionState(animated: animated) + } + } + } + } + } + + func isEqual(to other: Notifable) -> Bool { + if let other = other as? PeerPhotosMonthView { + return other == self + } + return false + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? PeerPhotosMonthItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + containerView.set(background: self.backdorColor, for: .Normal) + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateVisibleItems() + + if let item = self.item as? PeerPhotosMonthItem { + if superview == nil { + item.chatInteraction.remove(observer: self) + } else { + item.chatInteraction.add(observer: self) + } + } + } + + @objc private func updateVisibleItems() { + layoutVisibleItems(animated: false) + } + + private var previousRange: (Int, Int) = (0, 0) + private var isCleaned: Bool = false + + private func layoutVisibleItems(animated: Bool) { + guard let item = item as? PeerPhotosMonthItem else { + return + } + let visibleRect = NSMakeRect(0, self.visibleRect.minY - item.itemSize.height, self.visibleRect.width, self.visibleRect.height + item.itemSize.height * 2) + let size = item.itemSize + + if self.visibleRect != NSZeroRect && superview != nil && window != nil { + let visibleRange = (Int(ceil(visibleRect.minY / (size.height))), Int(ceil(visibleRect.height / (size.height)))) + if visibleRange != self.previousRange { + self.previousRange = visibleRange + isCleaned = false + } else { + return + } + } else { + self.previousRange = (0, 0) + CATransaction.begin() + if !isCleaned { + for (i, view) in self.contentViews.enumerated() { + view?.removeFromSuperview() + self.contentViews[i] = nil + } + } + isCleaned = true + CATransaction.commit() + return + } + + + CATransaction.begin() + + var unused:[MediaCell] = [] + for (i, layout) in item.layoutItems.enumerated() { + if NSPointInRect(layout.frame.origin, visibleRect) { + var view: MediaCell + if self.contentViews[i] == nil || !self.contentViews[i]!.isKind(of: layout.viewType) { + view = layout.viewType.init(frame: layout.frame) + self.contentViews[i] = view + } else { + view = self.contentViews[i]! + } + view.update(layout: layout, context: item.context, table: item.table) + + view.frame = layout.frame + } else { + if let view = self.contentViews[i] { + unused.append(view) + self.contentViews[i] = nil + } + } + } + + for view in unused { + view.removeFromSuperview() + } + + containerView.subviews = self.contentViews.compactMap { $0 } + + CATransaction.commit() + + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidMoveToWindow() { + if window == nil { + NotificationCenter.default.removeObserver(self) + } else { + NotificationCenter.default.addObserver(self, selector: #selector(updateVisibleItems), name: NSView.boundsDidChangeNotification, object: self.enclosingScrollView?.contentView) + NotificationCenter.default.addObserver(self, selector: #selector(updateMouse), name: NSWindow.didBecomeKeyNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(updateMouse), name: NSWindow.didResignKeyNotification, object: nil) + } + updateVisibleItems() + } + + override func layout() { + super.layout() + guard let item = item as? PeerPhotosMonthItem else { + return + } + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + updateVisibleItems() + } + + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool) -> NSView { + if let innerId = innerId.base as? MessageId { + let view = contentViews.compactMap { $0 }.first(where: { $0.layoutItem?.message.id == innerId }) + return view ?? NSView() + } + return self + } + + override func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + if let innerId = innerId.base as? MessageId { + let cell = contentViews.compactMap { $0 }.first(where: { $0.layoutItem?.message.id == innerId }) + cell?.addAccesoryOnCopiedView(view: view) + } + } + + override func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + return containerView.convert(point, from: nil) + } + + override func set(item: TableRowItem, animated: Bool = false) { + + super.set(item: item, animated: animated) + + guard let item = item as? PeerPhotosMonthItem else { + return + } + + item.chatInteraction.add(observer: self) + + self.previousRange = (0, 0) + + while self.contentViews.count > item.layoutItems.count { + self.contentViews.removeLast() + } + while self.contentViews.count < item.layoutItems.count { + self.contentViews.append(nil) + } + + layoutVisibleItems(animated: animated) + } +} + diff --git a/Telegram-Mac/PeerPresenceStatusManager.swift b/Telegram-Mac/PeerPresenceStatusManager.swift index cf75b3e444..8c6cc5d592 100644 --- a/Telegram-Mac/PeerPresenceStatusManager.swift +++ b/Telegram-Mac/PeerPresenceStatusManager.swift @@ -8,12 +8,13 @@ import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore final class PeerPresenceStatusManager { private let update: () -> Void - private var timer: SwiftSignalKitMac.Timer? + private var timer: SwiftSignalKit.Timer? init(update: @escaping () -> Void) { self.update = update @@ -23,14 +24,14 @@ final class PeerPresenceStatusManager { self.timer?.invalidate() } - func reset(presence: TelegramUserPresence) { + func reset(presence: TelegramUserPresence, timeDifference: Int32) { timer?.invalidate() timer = nil let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - let timeout = userPresenceStringRefreshTimeout(presence, relativeTo: Int32(timestamp)) + let timeout = userPresenceStringRefreshTimeout(presence, timeDifference: timeDifference, relativeTo: Int32(timestamp)) if timeout.isFinite { - self.timer = SwiftSignalKitMac.Timer(timeout: timeout, repeat: false, completion: { [weak self] in + self.timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in if let strongSelf = self { strongSelf.update() } diff --git a/Telegram-Mac/PeerUtils.swift b/Telegram-Mac/PeerUtils.swift new file mode 100644 index 0000000000..cc67c0a096 --- /dev/null +++ b/Telegram-Mac/PeerUtils.swift @@ -0,0 +1,310 @@ +// +// PeerUtils.swift +// Telegram +// +// Created by Mikhail Filimonov on 07/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SyncCore + + +extension ChatListFilterPeerCategories { + + static let excludeRead = ChatListFilterPeerCategories(rawValue: 1 << 6) + static let excludeMuted = ChatListFilterPeerCategories(rawValue: 1 << 7) + static let excludeArchived = ChatListFilterPeerCategories(rawValue: 1 << 8) + + static let Namespace: Int32 = 10 +} + + +final class TelegramFilterCategory : Peer { + + + + var id: PeerId + + var indexName: PeerIndexNameRepresentation + + var associatedPeerId: PeerId? + + var notificationSettingsPeerId: PeerId? + + func isEqual(_ other: Peer) -> Bool { + if let other = other as? TelegramFilterCategory { + return other.category == self.category + } + return false + } + + let category: ChatListFilterPeerCategories + + init(category: ChatListFilterPeerCategories) { + self.id = PeerId(namespace: 10, id: category.rawValue) + self.indexName = .title(title: "", addressName: "") + self.notificationSettingsPeerId = nil + self.category = category + } + + var displayTitle: String? { + if category == .contacts { + return L10n.chatListFilterContacts + } + if category == .nonContacts { + return L10n.chatListFilterNonContacts + } + if category == .groups { + return L10n.chatListFilterGroups + } + if category == .channels { + return L10n.chatListFilterChannels + } + if category == .bots { + return L10n.chatListFilterBots + } + if category == .excludeRead { + return L10n.chatListFilterReadChats + } + if category == .excludeMuted { + return L10n.chatListFilterMutedChats + } + if category == .excludeArchived { + return L10n.chatListFilterArchive + } + return nil + } + + var icon: EmptyAvatartType? { + if category == .contacts { + return .icon(colors: theme.colors.peerColors(5), icon: theme.icons.chat_filter_private_chats_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .nonContacts { + return .icon(colors: theme.colors.peerColors(1), icon: theme.icons.chat_filter_non_contacts_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .groups { + return .icon(colors: theme.colors.peerColors(2), icon: theme.icons.chat_filter_large_groups_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .channels { + return .icon(colors: theme.colors.peerColors(0), icon: theme.icons.chat_filter_channels_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .bots { + return .icon(colors: theme.colors.peerColors(6), icon: theme.icons.chat_filter_bots_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .excludeMuted { + return .icon(colors: theme.colors.peerColors(0), icon: theme.icons.chat_filter_muted_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .excludeRead { + return .icon(colors: theme.colors.peerColors(3), icon: theme.icons.chat_filter_read_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + if category == .excludeArchived { + return .icon(colors: theme.colors.peerColors(5), icon: theme.icons.chat_filter_archive_avatar, iconSize: NSMakeSize(24, 24), cornerRadius: nil) + } + return nil + } + + + init(decoder: PostboxDecoder) { + self.id = PeerId(0) + self.indexName = .title(title: "", addressName: "") + self.notificationSettingsPeerId = nil + self.category = [] + } + func encode(_ encoder: PostboxEncoder) { + + } +} + +extension Peer { + + func hasBannedRights(_ flags: TelegramChatBannedRightsFlags) -> Bool { + if let peer = self as? TelegramChannel { + if let _ = peer.hasBannedPermission(flags) { + return true + } + } else if let peer = self as? TelegramGroup { + return peer.hasBannedPermission(flags) + } + return false + } + + var webUrlRestricted: Bool { + return hasBannedRights([.banEmbedLinks]) + } + + + + + var canSendMessage: Bool { + if let channel = self as? TelegramChannel { + if case .broadcast(_) = channel.info { + return channel.hasPermission(.sendMessages) + } else if case .group = channel.info { + switch channel.participationStatus { + case .member: + return !channel.hasBannedRights(.banSendMessages) + default: + return false + } + } + } else if let group = self as? TelegramGroup { + return group.membership == .Member && !group.hasBannedPermission(.banSendMessages) + } else if let secret = self as? TelegramSecretChat { + switch secret.embeddedState { + case .terminated: + return false + case .handshake: + return false + default: + return true + } + } + + return true + } + + var username:String? { + if let peer = self as? TelegramChannel { + return peer.username + } else if let peer = self as? TelegramGroup { + return peer.username + } else if let peer = self as? TelegramUser { + return peer.username + } + return nil + } + + var emptyAvatar: EmptyAvatartType? { + if let peer = self as? TelegramFilterCategory { + return peer.icon + } + return nil + } + + public var displayTitle: String { + switch self { + case let user as TelegramUser: + if user.firstName == nil && user.lastName == nil { + return L10n.peerDeletedUser + } else { + var name: String = "" + if let firstName = user.firstName { + name += firstName + } + if let lastName = user.lastName { + if user.firstName != nil { + name += " " + } + name += lastName + } + + return name.replacingOccurrences(of: "􀇻", with: "") + } + case let group as TelegramGroup: + return group.title.replacingOccurrences(of: "􀇻", with: "") + case let channel as TelegramChannel: + return channel.title.replacingOccurrences(of: "􀇻", with: "") + case let filter as TelegramFilterCategory: + return filter.displayTitle ?? "" + default: + return "" + } + } + + var rawDisplayTitle: String { + switch self { + case let user as TelegramUser: + if user.firstName == nil && user.lastName == nil { + return "" + } else { + var name: String = "" + if let firstName = user.firstName { + name += firstName + } + if let lastName = user.lastName { + if user.firstName != nil { + name += " " + } + name += lastName + } + return name + } + case let group as TelegramGroup: + return group.title + case let channel as TelegramChannel: + return channel.title + default: + return "" + } + } + + public var compactDisplayTitle: String { + switch self { + case let user as TelegramUser: + if let firstName = user.firstName { + return firstName.replacingOccurrences(of: "􀇻", with: "") + } else if let lastName = user.lastName { + return lastName.replacingOccurrences(of: "􀇻", with: "") + } else { + return tr(L10n.peerDeletedUser) + } + case let group as TelegramGroup: + return group.title.replacingOccurrences(of: "􀇻", with: "") + case let channel as TelegramChannel: + return channel.title.replacingOccurrences(of: "􀇻", with: "") + case let filter as TelegramFilterCategory: + return filter.displayTitle ?? "" + default: + return "" + } + } + + public var displayLetters: [String] { + switch self { + case let user as TelegramUser: + if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty && !lastName.isEmpty { + return [firstName[firstName.startIndex ..< firstName.index(after: firstName.startIndex)].uppercased(), lastName[lastName.startIndex ..< lastName.index(after: lastName.startIndex)].uppercased()] + } else if let firstName = user.firstName, !firstName.isEmpty { + return [firstName[firstName.startIndex ..< firstName.index(after: firstName.startIndex)].uppercased()] + } else if let lastName = user.lastName, !lastName.isEmpty { + return [lastName[lastName.startIndex ..< lastName.index(after: lastName.startIndex)].uppercased()] + } else { + let name = L10n.peerDeletedUser + if !name.isEmpty { + return [name[name.startIndex ..< name.index(after: name.startIndex)].uppercased()] + } + } + + return [] + case let group as TelegramGroup: + if !group.title.isEmpty { + return [group.title[group.title.startIndex ..< group.title.index(after: group.title.startIndex)].uppercased()] + } else { + return [] + } + case let channel as TelegramChannel: + if !channel.title.isEmpty { + return [channel.title[channel.title.startIndex ..< channel.title.index(after: channel.title.startIndex)].uppercased()] + } else { + return [] + } + default: + return [] + } + } + + var isVerified: Bool { + if let peer = self as? TelegramUser { + return peer.flags.contains(.isVerified) + } else if let peer = self as? TelegramChannel { + return peer.flags.contains(.isVerified) + } else { + return false + } + } + +} diff --git a/Telegram-Mac/PeersListController.swift b/Telegram-Mac/PeersListController.swift index 8a1f1b6507..8e66d6a91d 100644 --- a/Telegram-Mac/PeersListController.swift +++ b/Telegram-Mac/PeersListController.swift @@ -8,83 +8,451 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import SyncCore +/* + + class PeerListContainerView : View { + var tableView = TableView(frame:NSZeroRect, drawBorder: true) { + didSet { + oldValue.removeFromSuperview() + addSubview(tableView) + } + } + let searchView:SearchView = SearchView(frame:NSZeroRect) + fileprivate let proxyButton:ImageButton = ImageButton() + private let proxyConnecting: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 11, 11)) + + private let titleView = TextView() + + private let separatorView = View() + + let compose:ImageButton = ImageButton() + private let headerContainerView = View() + private let searchContainerView = View() + private var searchState: SearchFieldState = .None + + var mode: PeerListMode = .plain { + didSet { + switch mode { + case .feedChannels: + compose.isHidden = true + case .plain: + compose.isHidden = false + } + needsLayout = true + } + } + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.border = [.Right] + compose.autohighlight = false + autoresizesSubviews = false + addSubview(tableView) + headerContainerView.addSubview(compose) + headerContainerView.addSubview(proxyButton) + headerContainerView.addSubview(titleView) + searchContainerView.addSubview(searchView) + addSubview(separatorView) + addSubview(headerContainerView) + addSubview(searchContainerView) + + proxyButton.addSubview(proxyConnecting) + setFrameSize(frameRect.size) + updateLocalizationAndTheme(theme: theme) + proxyButton.disableActions() + + + } + + fileprivate func updateProxyPref(_ pref: ProxySettings, _ connection: ConnectionStatus) { + proxyButton.isHidden = pref.servers.isEmpty && pref.effectiveActiveServer == nil + switch connection { + case .connecting, .waitingForNetwork: + proxyConnecting.isHidden = !pref.enabled + proxyButton.set(image: pref.enabled ? theme.icons.proxyState : theme.icons.proxyEnable, for: .Normal) + case .online, .updating: + proxyConnecting.isHidden = true + if pref.enabled { + proxyButton.set(image: theme.icons.proxyEnabled, for: .Normal) + } else { + proxyButton.set(image: theme.icons.proxyEnable, for: .Normal) + } + } + proxyConnecting.isEventLess = true + proxyConnecting.userInteractionEnabled = false + _ = proxyButton.sizeToFit() + proxyConnecting.centerX() + needsLayout = true + } + + func searchStateChanged(_ state: SearchFieldState, animated: Bool) { + self.searchState = state + + searchContainerView.change(pos: NSMakePoint(0, state == .Focus ? 10 : headerContainerView.frame.height), animated: animated) + headerContainerView.change(pos: NSMakePoint(0, state == .Focus ? -headerContainerView.frame.height : 0), animated: animated) + + // searchView.change(size: NSMakeSize(state == .Focus ? frame.width - searchView.frame.minX * 2 : (frame.width - (!mode.isFeedChannels ? 36 + compose.frame.width : 20) - (proxyButton.isHidden ? 0 : proxyButton.frame.width + 12)), 30), animated: animated) + // compose.change(opacity: state == .Focus ? 0 : 1, animated: animated) + // proxyButton.change(opacity: state == .Focus ? 0 : 1, animated: animated) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + self.backgroundColor = theme.colors.background + compose.background = .clear + compose.set(background: .clear, for: .Normal) + compose.set(background: .clear, for: .Hover) + compose.set(background: theme.colors.accent, for: .Highlight) + compose.set(image: theme.icons.composeNewChat, for: .Normal) + compose.set(image: theme.icons.composeNewChatActive, for: .Highlight) + compose.layer?.cornerRadius = .cornerRadius + compose.setFrameSize(NSMakeSize(40, 30)) + proxyConnecting.progressColor = theme.colors.accentIcon + proxyConnecting.lineWidth = 1.0 + + separatorView.backgroundColor = theme.colors.border + + headerContainerView.border = [.Right] + searchContainerView.border = [.Right] + headerContainerView.backgroundColor = theme.colors.background + searchContainerView.backgroundColor = theme.colors.background + + let titleLayout = TextViewLayout.init(.initialize(string: "Chats", color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1, alwaysStaticItems: true) + titleLayout.measure(width: .greatestFiniteMagnitude) + + titleView.update(titleLayout) + + super.updateLocalizationAndTheme(theme: theme) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + + + headerContainerView.frame = NSMakeRect(0, searchState == .Focus ? -headerContainerView.frame.height : 0, frame.width, 50) + searchContainerView.frame = NSMakeRect(0, searchState == .Focus ? 10 : headerContainerView.frame.maxY, frame.width, 40) + + let offset: CGFloat = searchState == .Focus ? searchContainerView.frame.height : headerContainerView.frame.height + searchContainerView.frame.height + + + searchView.frame = NSMakeRect(10, 0, searchContainerView.frame.width - 20, 30) + + + tableView.frame = NSMakeRect(0, offset, frame.width, frame.height - offset) + + // searchView.isHidden = frame.width < 200 + // if searchView.isHidden { + // compose.centerX(y: floorToScreenPixels(backingScaleFactor, (49 - compose.frame.height)/2.0)) + // proxyButton.setFrameOrigin(-proxyButton.frame.width, 0) + // } else { + // compose.setFrameOrigin(frame.width - 12 - compose.frame.width, floorToScreenPixels(backingScaleFactor, (offset - compose.frame.height)/2.0)) + // proxyButton.setFrameOrigin(frame.width - 12 - compose.frame.width - proxyButton.frame.width - 6, floorToScreenPixels(backingScaleFactor, (offset - proxyButton.frame.height)/2.0)) + // } + + proxyConnecting.centerX() + proxyConnecting.centerY(addition: -(backingScaleFactor == 2.0 ? 0.5 : 0)) + + titleView.center() + compose.centerY(x: frame.width - compose.frame.width - 10) + + separatorView.frame = NSMakeRect(0, searchContainerView.frame.maxY, frame.width, .borderSize) + + self.needsDisplay = true + } + + } + */ +final class RevealAllChatsView : Control { + let textView: TextView = TextView() + var layoutState: SplitViewState = .dual { + didSet { + needsLayout = true + } + } + + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + textView.userInteractionEnabled = false + textView.isSelectable = false + addSubview(textView) + + let layout = TextViewLayout(.initialize(string: L10n.chatListCloseFilter, color: .white, font: .medium(.title))) + layout.measure(width: max(280, frame.width)) + textView.update(layout) + + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.1) + shadow.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow + set(background: theme.colors.accent, for: .Normal) + } + + override func cursorUpdate(with event: NSEvent) { + NSCursor.pointingHand.set() + } + + override var backgroundColor: NSColor { + didSet { + textView.backgroundColor = backgroundColor + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + needsLayout = true + } + + + + override func layout() { + super.layout() + textView.center() + + layer?.cornerRadius = frame.height / 2 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} +final class FilterTabsView : View { + let tabs: ScrollableSegmentView = ScrollableSegmentView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tabs) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + tabs.frame = bounds + } +} class PeerListContainerView : View { - let tableView = TableView(frame:NSZeroRect, drawBorder: true) - var searchView:SearchView = SearchView(frame:NSZeroRect) - var compose:ImageButton = ImageButton() + private let backgroundView = BackgroundView(frame: NSZeroRect) + var tableView = TableView(frame:NSZeroRect, drawBorder: true) { + didSet { + oldValue.removeFromSuperview() + addSubview(tableView) + } + } + private let searchContainer: View = View() + let searchView:SearchView = SearchView(frame:NSMakeRect(10, 0, 0, 0)) + let compose:ImageButton = ImageButton() + fileprivate let proxyButton:ImageButton = ImageButton() + private let proxyConnecting: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 11, 11)) + private var searchState: SearchFieldState = .None + + + var mode: PeerListMode = .plain { + didSet { + switch mode { + case .folder: + compose.isHidden = true + case .plain: + compose.isHidden = false + case .filter: + compose.isHidden = true + } + needsLayout = true + } + } required init(frame frameRect: NSRect) { super.init(frame: frameRect) self.border = [.Right] compose.autohighlight = false autoresizesSubviews = false + addSubview(searchContainer) addSubview(tableView) - addSubview(searchView) - addSubview(compose) + searchContainer.addSubview(compose) + searchContainer.addSubview(proxyButton) + searchContainer.addSubview(searchView) + proxyButton.addSubview(proxyConnecting) setFrameSize(frameRect.size) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) + proxyButton.disableActions() + addSubview(backgroundView) + backgroundView.isHidden = true + + tableView.getBackgroundColor = { + .clear + } + layout() + } + + fileprivate func updateProxyPref(_ pref: ProxySettings, _ connection: ConnectionStatus) { + proxyButton.isHidden = pref.servers.isEmpty && pref.effectiveActiveServer == nil + switch connection { + case .connecting, .waitingForNetwork: + proxyConnecting.isHidden = !pref.enabled + proxyButton.set(image: pref.enabled ? theme.icons.proxyState : theme.icons.proxyEnable, for: .Normal) + case .online, .updating: + proxyConnecting.isHidden = true + if pref.enabled { + proxyButton.set(image: theme.icons.proxyEnabled, for: .Normal) + } else { + proxyButton.set(image: theme.icons.proxyEnable, for: .Normal) + } + } + proxyConnecting.isEventLess = true + proxyConnecting.userInteractionEnabled = false + _ = proxyButton.sizeToFit() + proxyConnecting.centerX() + needsLayout = true } - override func updateLocalizationAndTheme() { + func searchStateChanged(_ state: SearchFieldState, animated: Bool) { + self.searchState = state + searchView.change(size: NSMakeSize(state == .Focus || !mode.isPlain ? frame.width - searchView.frame.minX * 2 : (frame.width - (36 + compose.frame.width) - (proxyButton.isHidden ? 0 : proxyButton.frame.width + 12)), 30), animated: animated) + compose.change(opacity: state == .Focus ? 0 : 1, animated: animated) + proxyButton.change(opacity: state == .Focus ? 0 : 1, animated: animated) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + let theme = (theme as! TelegramPresentationTheme) self.backgroundColor = theme.colors.background - compose.disableActions() - compose.set(background: theme.colors.background, for: .Normal) - compose.set(background: theme.colors.background, for: .Hover) - compose.set(background: theme.colors.blueFill, for: .Highlight) + compose.background = .clear + compose.set(background: .clear, for: .Normal) + compose.set(background: .clear, for: .Hover) + compose.set(background: theme.colors.accent, for: .Highlight) compose.set(image: theme.icons.composeNewChat, for: .Normal) compose.set(image: theme.icons.composeNewChatActive, for: .Highlight) compose.layer?.cornerRadius = .cornerRadius compose.setFrameSize(NSMakeSize(40, 30)) - super.updateLocalizationAndTheme() + proxyConnecting.progressColor = theme.colors.accentIcon +// proxyConnecting.lineWidth = 1.0 + super.updateLocalizationAndTheme(theme: theme) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - searchView.setFrameSize(newSize.width - 36 - compose.frame.width, 30) - tableView.setFrameSize(newSize.width, newSize.height - 49) - } - - - + override func layout() { super.layout() + + var offset: CGFloat + switch theme.controllerBackgroundMode { + case .background: + offset = 50 + case .tiled: + offset = 50 + default: + offset = 50 + } + + if frame.width < 200 { + switch self.mode { + case .folder: + offset = 0 + + default: + break + } + } + + searchContainer.frame = NSMakeRect(0, 0, frame.width, offset) + + + searchView.setFrameSize(NSMakeSize(searchState == .Focus || !mode.isPlain ? frame.width - searchView.frame.minX * 2 : (frame.width - (36 + compose.frame.width) - (proxyButton.isHidden ? 0 : proxyButton.frame.width + 12)), 30)) + + + tableView.setFrameSize(frame.width, frame.height - offset) + searchView.isHidden = frame.width < 200 if searchView.isHidden { - compose.centerX(y: floorToScreenPixels((49 - compose.frame.height)/2.0)) + compose.center() + proxyButton.setFrameOrigin(-proxyButton.frame.width, 0) } else { - compose.setFrameOrigin(frame.width - 12 - compose.frame.width, floorToScreenPixels((50 - compose.frame.height)/2.0)) + compose.setFrameOrigin(searchContainer.frame.width - 12 - compose.frame.width, floorToScreenPixels(backingScaleFactor, (searchContainer.frame.height - compose.frame.height)/2.0)) + proxyButton.setFrameOrigin(searchContainer.frame.width - 12 - compose.frame.width - proxyButton.frame.width - 6, floorToScreenPixels(backingScaleFactor, (searchContainer.frame.height - proxyButton.frame.height)/2.0)) } - searchView.setFrameOrigin(10, floorToScreenPixels((49 - searchView.frame.height)/2.0)) - tableView.setFrameOrigin(0, 49) + searchView.setFrameOrigin(10, floorToScreenPixels(backingScaleFactor, (offset - searchView.frame.height)/2.0)) + tableView.setFrameOrigin(0, offset) + + proxyConnecting.centerX() + proxyConnecting.centerY(addition: -(backingScaleFactor == 2.0 ? 0.5 : 0)) + + backgroundView.frame = bounds + self.needsDisplay = true } + +} + + +enum PeerListMode { + case plain + case folder(PeerGroupId) + case filter(Int32) + + var isPlain:Bool { + switch self { + case .plain: + return true + default: + return false + } + } + var groupId: PeerGroupId { + switch self { + case let .folder(groupId): + return groupId + default: + return .root + } + } + var filterId: Int32? { + switch self { + case let .filter(id): + return id + default: + return nil + } + } } class PeersListController: TelegramGenericViewController, TableViewDelegate { - private let globalPeerDisposable:MetaDisposable = MetaDisposable() + + + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + private let progressDisposable = MetaDisposable() private let createSecretChatDisposable = MetaDisposable() private let layoutDisposable = MetaDisposable() + private let actionsDisposable = DisposableSet() private let followGlobal:Bool - private var searchController:SearchController? { + private let searchOptions: AppSearchOptions + let mode:PeerListMode + private(set) var searchController:SearchController? { didSet { if let controller = searchController { genericView.customHandler.size = { [weak controller] size in - controller?.view.setFrameSize(NSMakeSize(size.width, size.height - 50)) + controller?.view.setFrameSize(NSMakeSize(size.width, size.height - 49)) } progressDisposable.set((controller.isLoading.get() |> deliverOnMainQueue).start(next: { [weak self] isLoading in self?.genericView.searchView.isLoading = isLoading @@ -93,18 +461,27 @@ class PeersListController: TelegramGenericViewController, } } - init(_ account:Account, followGlobal:Bool = true) { + init(_ context: AccountContext, followGlobal:Bool = true, mode: PeerListMode = .plain, searchOptions: AppSearchOptions = [.chats, .messages]) { self.followGlobal = followGlobal - - super.init(account) - + self.mode = mode + self.searchOptions = searchOptions + super.init(context) + self.bar = .init(height: !mode.isPlain ? 50 : 0) + } + + override var redirectUserInterfaceCalls: Bool { + return true + } + + override var responderPriority: HandlerPriority { + return .low } deinit { - globalPeerDisposable.dispose() progressDisposable.dispose() createSecretChatDisposable.dispose() layoutDisposable.dispose() + actionsDisposable.dispose() } override func viewDidResized(_ size: NSSize) { @@ -114,170 +491,263 @@ class PeersListController: TelegramGenericViewController, override func viewDidLoad() { super.viewDidLoad() + let context = self.context + + + layoutDisposable.set(context.sharedContext.layoutHandler.get().start(next: { [weak self] state in + if let strongSelf = self, case .minimisize = state { + if strongSelf.genericView.searchView.state == .Focus { + strongSelf.genericView.searchView.change(state: .None, false) + } + } + self?.genericView.tableView.alwaysOpenRowsOnMouseUp = state == .single + self?.genericView.tableView.reloadData() + Queue.mainQueue().justDispatch { + self?.requestUpdateBackBar() + } + })) + + let actionsDisposable = self.actionsDisposable + + actionsDisposable.add((context.cancelGlobalSearch.get() |> deliverOnMainQueue).start(next: { [weak self] animated in + self?.genericView.searchView.cancel(animated) + })) + + genericView.mode = mode + if followGlobal { - globalPeerDisposable.set((globalPeerHandler.get() |> deliverOnMainQueue).start(next: { [weak self] peerId in - self?.genericView.tableView.changeSelection(stableId: peerId) + actionsDisposable.add((context.globalPeerHandler.get() |> deliverOnMainQueue).start(next: { [weak self] location in + guard let `self` = self else {return} + self.changeSelection(location) + if location == nil { + if !self.genericView.searchView.isEmpty { + _ = self.window?.makeFirstResponder(self.genericView.searchView.input) + } + } })) } if self.navigationController?.modalAction is FWDNavigationAction { - self.setCenterTitle(tr(.chatForwardActionHeader)) + self.setCenterTitle(L10n.chatForwardActionHeader) } if self.navigationController?.modalAction is ShareInlineResultNavigationAction { - self.setCenterTitle(tr(.chatShareInlineResultActionHeader)) + self.setCenterTitle(L10n.chatShareInlineResultActionHeader) } genericView.tableView.delegate = self - let table = genericView.tableView + var settings:(ProxySettings, ConnectionStatus)? = nil + + + + actionsDisposable.add(combineLatest(proxySettings(accountManager: context.sharedContext.accountManager) |> mapToSignal { ps -> Signal<(ProxySettings, ConnectionStatus), NoError> in + return context.account.network.connectionStatus |> map { status -> (ProxySettings, ConnectionStatus) in + return (ps, status) + } + } |> deliverOnMainQueue, appearanceSignal |> deliverOnMainQueue).start(next: { [weak self] pref, _ in + settings = (pref.0, pref.1) + self?.genericView.updateProxyPref(pref.0, pref.1) + })) + + let pushController:(ViewController)->Void = { [weak self] c in + self?.context.sharedContext.bindings.rootNavigation().push(c) + } + + let openProxySettings:()->Void = { [weak self] in + if let controller = self?.context.sharedContext.bindings.rootNavigation().controller as? InputDataController { + if controller.identifier == "proxy" { + return + } + } + let controller = proxyListController(accountManager: context.sharedContext.accountManager, network: context.account.network, share: { servers in + var message: String = "" + for server in servers { + message += server.link + "\n\n" + } + message = message.trimmed + + showModal(with: ShareModalController(ShareLinkObject(context, link: message)), for: mainWindow) + }, pushController: { controller in + pushController(controller) + }) + pushController(controller) + } + genericView.proxyButton.set(handler: { _ in + if let settings = settings { + openProxySettings() +// if settings.0.enabled { +// +// } else { +// actionsDisposable.add(updateProxySettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> ProxySettings in +// if let first = current.servers.first { +// return current.withUpdatedActiveServer(first).withUpdatedEnabled(true) +// } else { +// return current +// } +// }).start()) +// } + } + }, for: .Click) genericView.compose.set(handler: { [weak self] control in if let strongSelf = self, !control.isSelected { - let items = [SPopoverItem(tr(.composePopoverNewGroup), { [weak strongSelf] in - if let strongSelf = strongSelf, let navigation = strongSelf.navigationController { - createGroup(with: strongSelf.account, for: navigation) - } - - }, theme.icons.composeNewGroup),SPopoverItem(tr(.composePopoverNewSecretChat), { [weak strongSelf] in - if let strongSelf = strongSelf, let account = self?.account { - let confirmationImpl:([PeerId])->Signal = { peerIds in - if let first = peerIds.first, peerIds.count == 1 { - return account.postbox.loadedPeerWithId(first) |> deliverOnMainQueue |> mapToSignal { peer in - return confirmSignal(for: mainWindow, header: appName, information: tr(.composeConfirmStartSecretChat(peer.displayTitle))) - } - } - return confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmAddMembers(peerIds.count))) - } - let select = selectModalPeers(account: account, title: tr(.composeSelectSecretChat), limit: 1, confirmation: confirmationImpl) - - let create = select |> map { $0.first! } |> mapToSignal { peerId in - return createSecretChat(account: account, peerId: peerId) |> mapError {_ in} - } |> deliverOnMainQueue |> mapToSignal{ peerId -> Signal in - return showModalProgress(signal: .single(peerId), for: mainWindow) - } - - strongSelf.createSecretChatDisposable.set(create.start(next: { [weak self] peerId in - self?.navigationController?.push(ChatController(account: account, peerId: peerId)) - })) - - } - }, theme.icons.composeNewSecretChat),SPopoverItem(tr(.composePopoverNewChannel), { [weak strongSelf] in - if let strongSelf = strongSelf, let navigation = strongSelf.navigationController { - createChannel(with: strongSelf.account, for: navigation) - } + let items = [SPopoverItem(tr(L10n.composePopoverNewGroup), { [weak strongSelf] in + guard let strongSelf = strongSelf else {return} + strongSelf.context.composeCreateGroup() + }, theme.icons.composeNewGroup),SPopoverItem(tr(L10n.composePopoverNewSecretChat), { [weak strongSelf] in + guard let strongSelf = strongSelf else {return} + strongSelf.context.composeCreateSecretChat() + }, theme.icons.composeNewSecretChat),SPopoverItem(tr(L10n.composePopoverNewChannel), { [weak strongSelf] in + guard let strongSelf = strongSelf else {return} + strongSelf.context.composeCreateChannel() }, theme.icons.composeNewChannel)]; - - showPopover(for: control, with: SPopoverViewController(items: items), edge: .maxY, inset: NSMakePoint(-138, -(strongSelf.genericView.compose.frame.maxY + 10))) + if let popover = control.popover { + popover.hide() + } else { + showPopover(for: control, with: SPopoverViewController(items: items), edge: .maxY, inset: NSMakePoint(-138, -(strongSelf.genericView.compose.frame.maxY + 10))) + } } }, for: .Click) - genericView.searchView.searchInteractions = SearchInteractions({[weak self] (state) in - if let strongSelf = self { - switch state.state { - case .Focus: - - assert(strongSelf.searchController == nil) - - let searchController = SearchController(account: strongSelf.account, open:{ [weak strongSelf] (peerId, message, close) in - strongSelf?.open(with: peerId, message:message, close:close) - }, frame:table.frame) - strongSelf.searchController = searchController + genericView.searchView.searchInteractions = SearchInteractions({ [weak self] state, animated in + guard let `self` = self else {return} + self.genericView.searchStateChanged(state.state, animated: animated) + switch state.state { + case .Focus: + assert(self.searchController == nil) + self.showSearchController(animated: animated) - searchController.navigationController = strongSelf.navigationController - searchController.viewWillAppear(true) - searchController.view.layer?.opacity = 1.0 - searchController.view.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion:{[weak strongSelf](complete) in - strongSelf?.searchController?.viewDidAppear(true) - }) - - strongSelf.addSubview(searchController.view) - case .None: - - assert(strongSelf.searchController != nil) - - let searchController = strongSelf.searchController! - searchController.viewWillDisappear(true) - searchController.view.layer?.opacity = 0.0 - searchController.view.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion:{[weak strongSelf](complete) in - - strongSelf?.searchController?.viewDidDisappear(true) - strongSelf?.searchController?.removeFromSuperview() - strongSelf?.searchController = nil - - }) - - } + case .None: + self.hideSearchController(animated: animated) } - }, { [weak self] state in - self?.searchController?.request(with: state.request) + }, { [weak self] state in + guard let `self` = self else {return} + self.searchController?.request(with: state.request) + }, responderModified: { [weak self] state in + self?.context.isInGlobalSearch = state.responder }) - readyOnce() } - + override func requestUpdateBackBar() { + self.leftBarView.minWidth = 70 + super.requestUpdateBackBar() + } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if animated { - genericView.tableView.layoutItems() - } - - if account.context.layout == .single && animated { - globalPeerHandler.set(.single(nil)) + override func getLeftBarViewOnce() -> BarView { + let view = BackNavigationBar(self, canBeEmpty: true) + view.minWidth = 70 + return view + } + + override func backSettings() -> (String, CGImage?) { + return context.sharedContext.layout == .minimisize ? ("", theme.icons.instantViewBack) : super.backSettings() + } + + + func changeSelection(_ location: ChatLocation?) { + if let location = location { + self.genericView.tableView.changeSelection(stableId: UIChatListEntryId.chatId(location.peerId, nil)) + } else { + self.genericView.tableView.changeSelection(stableId: nil) } - layoutDisposable.set(account.context.layoutHandler.get().start(next: { [weak self] state in - if let strongSelf = self, case .minimisize = state { - if strongSelf.genericView.searchView.state == .Focus { - strongSelf.genericView.searchView.change(state: .None, false) - } - } - self?.genericView.tableView.reloadData() - })) - - account.context.globalSearch = { [weak self] query in - if let strongSelf = self { - _ = (strongSelf.account.context.layoutHandler.get() |> take(1)).start(next: { [weak strongSelf] state in - if let strongSelf = strongSelf { - - let invoke = { [weak strongSelf] in - strongSelf?.genericView.searchView.change(state: .Focus, false) - strongSelf?.genericView.searchView.setString(query) - } - - switch state { - case .single: - strongSelf.account.context.mainNavigation?.back() - Queue.mainQueue().justDispatch(invoke) - case .minimisize: - (strongSelf.window?.contentView?.subviews.first as? SplitView)?.needFullsize() - Queue.mainQueue().justDispatch { - if strongSelf.navigationController?.controller is ChatController { - strongSelf.navigationController?.back() - Queue.mainQueue().justDispatch(invoke) - } - } - default: - invoke() - } - + } + + private func showSearchController(animated: Bool) { + if searchController == nil { + // delay(0.15, closure: { + let rect = self.genericView.tableView.frame + let searchController = SearchController(context: self.context, open:{ [weak self] (peerId, messageId, close) in + if let peerId = peerId { + self?.open(with: .chatId(peerId, nil), messageId: messageId, close:close) + } else { + self?.genericView.searchView.cancel(true) } - }) - } + }, options: self.searchOptions, frame:NSMakeRect(rect.minX, rect.minY, self.frame.width, rect.height)) + + searchController.pinnedItems = self.collectPinnedItems + + self.searchController = searchController +// self.genericView.tableView.change(opacity: 0, animated: animated, completion: { [weak self] _ in +// self?.genericView.tableView.isHidden = true +// }) + searchController.defaultQuery = self.genericView.searchView.query + searchController.navigationController = self.navigationController + searchController.viewWillAppear(true) + + + + if animated { + searchController.view.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion:{ [weak self] complete in + if complete { + self?.searchController?.viewDidAppear(animated) + // self?.genericView.tableView.isHidden = true + } + }) + searchController.view.layer?.animateScaleSpring(from: 1.05, to: 1.0, duration: 0.4, bounce: false) + searchController.view.layer?.animatePosition(from: NSMakePoint(rect.minX, rect.minY + 15), to: rect.origin, duration: 0.4, timingFunction: .spring) + + } else { + searchController.viewDidAppear(animated) + } + self.addSubview(searchController.view) + // }) } + } + + private func hideSearchController(animated: Bool) { + if let searchController = self.searchController { + searchController.viewWillDisappear(animated) + searchController.view.layer?.opacity = animated ? 1.0 : 0.0 + searchController.viewDidDisappear(true) + self.searchController = nil + self.genericView.tableView.isHidden = false + self.genericView.tableView.change(opacity: 1, animated: animated) + let view = searchController.view + + searchController.view._change(opacity: 0, animated: animated, duration: 0.25, timingFunction: CAMediaTimingFunctionName.spring, completion: { [weak view] completed in + view?.removeFromSuperview() + }) + searchController.view.layer?.animateScaleSpring(from: 1.0, to: 1.05, duration: 0.4, removeOnCompletion: false, bounce: false) + genericView.tableView.layer?.animateScaleSpring(from: 0.95, to: 1.00, duration: 0.4, removeOnCompletion: false, bounce: false) + + } } + override func focusSearch(animated: Bool) { + genericView.searchView.change(state: .Focus, animated) + } + + override func navigationUndoHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + genericView.layer?.animatePosition(from: NSMakePoint(0, previous), to: NSMakePoint(0, current), removeOnCompletion: false) + return { [weak genericView] in + genericView?.layer?.removeAllAnimations() + } + } + + + + + var collectPinnedItems:[PinnedItemId] { + return [] + } + + + public override func escapeKeyAction() -> KeyHandlerResult { - guard account.context.layout != .minimisize else { + guard context.sharedContext.layout != .minimisize else { + return .invoked + } + if genericView.tableView.highlightedItem() != nil { + genericView.tableView.cancelHighlight() return .invoked } if genericView.searchView.state == .None { @@ -290,20 +760,43 @@ class PeersListController: TelegramGenericViewController, } public override func returnKeyAction() -> KeyHandlerResult { + if let highlighted = genericView.tableView.highlightedItem() { + _ = genericView.tableView.select(item: highlighted) + return .invoked + } return .rejected } - func open(with peerId:PeerId, message:Message? = nil, close:Bool = true) ->Void { - if let navigationController = navigationController { - let chat:ChatController = ChatController(account: self.account, peerId:peerId, messageId:message?.id) - navigationController.push(chat) + func open(with entryId: UIChatListEntryId, messageId:MessageId? = nil, initialAction: ChatInitialAction? = nil, close:Bool = true, addition: Bool = false) ->Void { + + switch entryId { + case let .chatId(peerId, _): + let navigation = context.sharedContext.bindings.rootNavigation() + + if let modalAction = navigation.modalAction as? FWDNavigationAction, peerId == context.peerId { + _ = Sender.forwardMessages(messageIds: modalAction.messages.map{$0.id}, context: context, peerId: context.peerId).start() + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + modalAction.afterInvoke() + navigation.removeModalAction() + } else { + let chat:ChatController = addition ? ChatAdditionController(context: context, chatLocation: .peer(peerId), messageId: messageId) : ChatController(context: self.context, chatLocation: .peer(peerId), messageId: messageId, initialAction: initialAction) + navigation.push(chat, context.sharedContext.layout == .single) + } + case let .groupId(groupId): + self.navigationController?.push(ChatListController(context, modal: false, groupId: groupId)) + case .reveal: + break + case .empty: + break + case .loading: + break } if close { - genericView.searchView.cancel(true) + self.genericView.searchView.cancel(true) } } - func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { return true } @@ -315,50 +808,76 @@ class PeersListController: TelegramGenericViewController, return true } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) } + private var effectiveTableView: TableView { + switch genericView.searchView.state { + case .Focus: + return searchController?.genericView ?? genericView.tableView + case .None: + return genericView.tableView + } + } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.window?.set(handler: { [weak self] in + + if animated { + // genericView.tableView.layoutItems() + } + + if context.sharedContext.layout == .single && animated { + context.globalPeerHandler.set(.single(nil)) + } + + + context.window.set(handler: { [weak self] in if let strongSelf = self { return strongSelf.escapeKeyAction() } return .invokeNext }, with: self, for: .Escape, priority:.low) + context.window.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.returnKeyAction() + } + return .invokeNext + }, with: self, for: .Return, priority:.low) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - if let item = self?.genericView.tableView.selectedItem(), item.index > 0 { - self?.genericView.tableView.selectPrev() + context.window.set(handler: {[weak self] () -> KeyHandlerResult in + if let item = self?.effectiveTableView.selectedItem(), item.index > 0 { + self?.effectiveTableView.selectPrev() } return .invoked }, with: self, for: .UpArrow, priority: .medium, modifierFlags: [.option]) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - self?.genericView.tableView.selectNext() + context.window.set(handler: {[weak self] () -> KeyHandlerResult in + self?.effectiveTableView.selectNext() return .invoked }, with: self, for: .DownArrow, priority:.medium, modifierFlags: [.option]) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - self?.genericView.tableView.selectNext() + context.window.set(handler: {[weak self] () -> KeyHandlerResult in + self?.effectiveTableView.selectNext(turnDirection: false) return .invoked - }, with: self, for: .Tab, priority: .low, modifierFlags: [.control]) + }, with: self, for: .Tab, priority: .modal, modifierFlags: [.control]) - self.window?.set(handler: {[weak self] () -> KeyHandlerResult in - self?.genericView.tableView.selectPrev() + context.window.set(handler: {[weak self] () -> KeyHandlerResult in + self?.effectiveTableView.selectPrev(turnDirection: false) return .invoked - }, with: self, for: .Tab, priority:.medium, modifierFlags: [.control, .shift]) + }, with: self, for: .Tab, priority: .modal, modifierFlags: [.control, .shift]) + - } + + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - self.window?.removeAllHandlers(for: self) + context.window.removeAllHandlers(for: self) } diff --git a/Telegram-Mac/PhoneCallWindowController.swift b/Telegram-Mac/PhoneCallWindowController.swift index 82fb2c781f..d0bfa0e4c5 100644 --- a/Telegram-Mac/PhoneCallWindowController.swift +++ b/Telegram-Mac/PhoneCallWindowController.swift @@ -8,10 +8,11 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac -import MtProtoKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + private class ShadowView : View { @@ -19,7 +20,6 @@ private class ShadowView : View { ctx.clear(NSMakeRect(0, 0, frame.width, frame.height)) - var locations: [CGFloat] = [1.0, 0.2]; let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [NSColor.black.withAlphaComponent(0.4).cgColor, NSColor.clear.cgColor]), locations: nil)! @@ -32,7 +32,7 @@ private class PhoneCallWindowView : View { //private var avatar:AvatarControl = AvatarControl fileprivate let imageView:TransformImageView = TransformImageView() fileprivate let controls:NSVisualEffectView = NSVisualEffectView() - private let backgroundView:View = View() + fileprivate let backgroundView:View = View() let acceptControl:ImageButton = ImageButton() let declineControl:ImageButton = ImageButton() let muteControl:ImageButton = ImageButton() @@ -55,7 +55,6 @@ private class PhoneCallWindowView : View { controls.material = .dark controls.blendingMode = .behindWindow - secureContainerView.wantsLayer = true secureContainerView.background = NSColor(0x000000, 0.75) secureTextView.backgroundColor = .clear @@ -72,15 +71,15 @@ private class PhoneCallWindowView : View { acceptControl.autohighlight = false acceptControl.set(image: theme.icons.callWindowAccept, for: .Normal) - acceptControl.sizeToFit() + _ = acceptControl.sizeToFit() declineControl.autohighlight = false declineControl.set(image: theme.icons.callWindowDecline, for: .Normal) - declineControl.sizeToFit() + _ = declineControl.sizeToFit() muteControl.autohighlight = false muteControl.set(image: theme.icons.callWindowMute, for: .Normal) - muteControl.sizeToFit() + _ = muteControl.sizeToFit() controls.addSubview(muteControl) closeMissedControl.autohighlight = false @@ -98,10 +97,10 @@ private class PhoneCallWindowView : View { controls.addSubview(statusTextView) controls.addSubview(closeMissedControl) - textNameView.font = .medium(.custom(18)) + textNameView.font = .medium(18.0) textNameView.drawsBackground = false textNameView.backgroundColor = .clear - textNameView.textColor = darkPallete.text + textNameView.textColor = nightAccentPalette.text textNameView.isSelectable = false textNameView.isEditable = false textNameView.isBordered = false @@ -110,10 +109,10 @@ private class PhoneCallWindowView : View { textNameView.alignment = .center textNameView.cell?.truncatesLastVisibleLine = true textNameView.lineBreakMode = .byTruncatingTail - statusTextView.font = .normal(.custom(15)) + statusTextView.font = .normal(.header) statusTextView.drawsBackground = false statusTextView.backgroundColor = .clear - statusTextView.textColor = darkPallete.text + statusTextView.textColor = nightAccentPalette.text statusTextView.isSelectable = false statusTextView.isEditable = false statusTextView.isBordered = false @@ -132,12 +131,12 @@ private class PhoneCallWindowView : View { declineControl.setFrameOrigin(80, 30) acceptControl.setFrameOrigin(frame.width - acceptControl.frame.width - 80, 30) - layer?.cornerRadius = 6 + layer?.cornerRadius = 10 closeMissedControl.isHidden = true closeMissedControl.layer?.opacity = 0 - + } @@ -153,10 +152,10 @@ private class PhoneCallWindowView : View { secureTextView.center() secureTextView.setFrameOrigin(secureTextView.frame.minX + 2, secureTextView.frame.minY) secureContainerView.centerX(y: frame.height - 170 - secureContainerView.frame.height) - muteControl.setFrameOrigin(frame.width - 60 - muteControl.frame.width, 30 + floorToScreenPixels((declineControl.frame.height - muteControl.frame.height)/2)) + muteControl.setFrameOrigin(frame.width - 60 - muteControl.frame.width, 30 + floorToScreenPixels(backingScaleFactor, (declineControl.frame.height - muteControl.frame.height)/2)) closeMissedControl.setFrameOrigin(80, 30) - + } func updateName(_ name:String) { @@ -169,45 +168,47 @@ private class PhoneCallWindowView : View { needsLayout = true } - func updateState(_ state:CallSessionState, animated: Bool) { + func updateState(_ state:CallSessionState, accountPeer: Peer?, animated: Bool) { switch state { case .accepting: - statusTextView.stringValue = tr(.callStatusConnecting) - case .active(_, let visual, _): - let layout = TextViewLayout(.initialize(string: ObjcUtils.callEmojies(visual), color: .black, font: .normal(.custom(16))), alignment: .center) + statusTextView.stringValue = L10n.callStatusConnecting + case .active(_, _, let visual, _, _, _, _): + let layout = TextViewLayout(.initialize(string: ObjcUtils.callEmojies(visual), color: .black, font: .normal(16.0)), alignment: .center) layout.measure(width: .greatestFiniteMagnitude) secureTextView.update(layout) secureContainerView.isHidden = false secureContainerView.setFrameSize(NSMakeSize(layout.layoutSize.width + 16, layout.layoutSize.height + 10)) secureContainerView.layer?.cornerRadius = secureContainerView.frame.height / 2 - statusTextView.stringValue = tr(.callStatusConnecting) + statusTextView.stringValue = L10n.callStatusConnecting case .ringing: - statusTextView.stringValue = tr(.callStatusCalling) - case .terminated(let error, _): + if let accountPeer = accountPeer { + statusTextView.stringValue = L10n.callStatusCallingAccount(accountPeer.addressName ?? accountPeer.compactDisplayTitle) + } else { + statusTextView.stringValue = L10n.callStatusCalling + } + case .terminated(_, let error, _): switch error { case .ended(let reason): switch reason { case .busy: - statusTextView.stringValue = tr(.callStatusBusy) + statusTextView.stringValue = L10n.callStatusBusy case .missed: - statusTextView.stringValue = tr(.callStatusEnded) + statusTextView.stringValue = L10n.callStatusEnded default: - statusTextView.stringValue = tr(.callStatusEnded) - acceptControl.isEnabled = false - acceptControl.change(opacity: 0.8) + statusTextView.stringValue = L10n.callStatusEnded } case .error: - statusTextView.stringValue = tr(.callStatusFailed) + statusTextView.stringValue = L10n.callStatusFailed acceptControl.isEnabled = false acceptControl.change(opacity: 0.8) } case .requesting(let ringing): - statusTextView.stringValue = !ringing ? tr(.callStatusRequesting) : tr(.callStatusRinging) + statusTextView.stringValue = !ringing ? L10n.callStatusRequesting : L10n.callStatusRinging default: break } @@ -215,23 +216,27 @@ private class PhoneCallWindowView : View { switch state { case .active, .accepting, .requesting: - declineControl.change(opacity: 0, animated: animated, completion: { [weak self] complete in - self?.declineControl.isHidden = true + declineControl.change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { + self?.declineControl.isHidden = true + } }) - acceptControl.change(pos: NSMakePoint(floorToScreenPixels((frame.width - acceptControl.frame.width) / 2), 30), animated: animated) + acceptControl.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, (frame.width - acceptControl.frame.width) / 2), 30), animated: animated) acceptControl.set(image: theme.icons.callWindowDecline, for: .Normal) muteControl.isHidden = false muteControl.change(opacity: 1, animated: animated) closeMissedControl.change(opacity: 0, animated: animated, completion: { [weak self] completed in - self?.closeMissedControl.isHidden = true + if completed { + self?.closeMissedControl.isHidden = true + } }) case .ringing: declineControl.isHidden = false - muteControl.change(opacity: 0, animated: animated, completion: { [weak self] complete in - if complete { + muteControl.change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { self?.muteControl.isHidden = true } }) @@ -240,10 +245,12 @@ private class PhoneCallWindowView : View { declineControl.change(opacity: 1, animated: animated) closeMissedControl.change(opacity: 0, animated: animated, completion: { [weak self] completed in - self?.closeMissedControl.isHidden = true + if completed { + self?.closeMissedControl.isHidden = true + } }) - case .terminated(let reason, _): + case .terminated(_, let reason, _): let recall:Bool @@ -268,8 +275,8 @@ private class PhoneCallWindowView : View { closeMissedControl.isHidden = false closeMissedControl.change(opacity: 1, animated: animated) - muteControl.change(opacity: 0, animated: animated, completion: { [weak self] complete in - if complete { + muteControl.change(opacity: 0, animated: animated, completion: { [weak self] completed in + if completed { self?.muteControl.isHidden = true } }) @@ -290,7 +297,7 @@ private class PhoneCallWindowView : View { } class PhoneCallWindowController { - let window:NSWindow + let window:Window let updateLocalizationAndThemeDisposable = MetaDisposable() fileprivate var session:PCallSession! { didSet { @@ -302,7 +309,7 @@ class PhoneCallWindowController { private func sessionDidUpdated() { view.secureContainerView.isHidden = true - peerDisposable.set((session.account.viewTracker.peerView( session.peerId) |> deliverOnMainQueue).start(next: { [weak self] peerView in + peerDisposable.set((session.account.viewTracker.peerView(session.peerId) |> deliverOnMainQueue).start(next: { [weak self] peerView in if let strongSelf = self { if let user = peerView.peers[peerView.peerId] as? TelegramUser { strongSelf.updatePeerUI(user) @@ -310,9 +317,19 @@ class PhoneCallWindowController { } })) - stateDisposable.set((session.state.get() |> deliverOnMainQueue).start(next: { [weak self] state in + let account = session.account + + let accountPeer: Signal = session.sharedContext.activeAccounts |> mapToSignal { accounts in + if accounts.accounts.count == 1 { + return .single(nil) + } else { + return account.postbox.loadedPeerWithId(account.peerId) |> map(Optional.init) + } + } + + stateDisposable.set(combineLatest(queue: .mainQueue(), session.state.get(), accountPeer).start(next: { [weak self] state, accountPeer in if let strongSelf = self { - strongSelf.applyState(state, animated: !strongSelf.first) + strongSelf.applyState(state, accountPeer: accountPeer, animated: !strongSelf.first) strongSelf.first = false } })) @@ -329,14 +346,16 @@ class PhoneCallWindowController { private let recallDisposable = MetaDisposable() private let peerDisposable = MetaDisposable() private let keyStateDisposable = MetaDisposable() + private let fetching = MetaDisposable() init(_ session:PCallSession) { self.session = session let size = NSMakeSize(300, 460) if let screen = NSScreen.main { - self.window = NSWindow(contentRect: NSMakeRect(floorToScreenPixels((screen.frame.width - size.width) / 2), floorToScreenPixels((screen.frame.height - size.height) / 2), size.width, size.height), styleMask: [.titled, .fullSizeContentView], backing: .buffered, defer: false, screen: screen) - self.window.level = .screenSaver + self.window = Window(contentRect: NSMakeRect(floorToScreenPixels(System.backingScale, (screen.frame.width - size.width) / 2), floorToScreenPixels(System.backingScale, (screen.frame.height - size.height) / 2), size.width, size.height), styleMask: [.fullSizeContentView], backing: .buffered, defer: true, screen: screen) + self.window.level = .modalPanel + self.window.backgroundColor = .clear } else { fatalError("screen not found") } @@ -351,7 +370,7 @@ class PhoneCallWindowController { switch state { case .ringing: self?.session.acceptCallSession() - case .terminated(let reason, _): + case .terminated(_, let reason, _): let recall:Bool switch reason { @@ -403,55 +422,18 @@ class PhoneCallWindowController { self?.session.hangUpCurrentCall() }, for: .Click) - - /* view.deviceSettingsButton.set(handler: { [weak self] _ in - - if let session = self?.session, let strongSelf = self { - _ = (combineLatest(session.inputDevices(), session.outputDevices(), session.currentInputDeviceId(), session.currentOutputDeviceId()) |> deliverOnMainQueue).start(next: { [weak strongSelf] input, output, currentInputId, currentOutputId in - - var settingsWindow:NSWindow! - - let deviceSettings = NativeCallSettingsViewController(inputDevices: input, outputDevices: output, currentInputDeviceId: currentInputId, currentOutputDeviceId: currentOutputId, onSave: { [weak strongSelf] (inputDevice, outputDevice) in - strongSelf?.session.setCurrentInputDevice(inputDevice) - strongSelf?.session.setCurrentOutputDevice(outputDevice) - }, onCancel: { - }) - - settingsWindow = NSWindow(contentViewController: deviceSettings) - settingsWindow.styleMask = [.borderless] - //settingsWindow.appearance = mainWindow.appearance - settingsWindow.contentView?.wantsLayer = true - settingsWindow.contentView?.layer?.cornerRadius = 4 - //settingsWindow.contentView?.background = theme.colors.background - //settingsWindow.backgroundColor = theme.colors.background - //settingsWindow.isOpaque = false - - strongSelf?.window.beginSheet(settingsWindow, completionHandler: { response in - - }) - }) - - - } - - }, for: .Click) - */ - + self.window.contentView = view self.window.backgroundColor = .clear self.window.contentView?.layer?.cornerRadius = 4 self.window.titlebarAppearsTransparent = true - self.window.isMovableByWindowBackground = true sessionDidUpdated() } private func recall() { - let account = session.account - let peerId = session.peerId - - recallDisposable.set((phoneCall(account, peerId: peerId, ignoreSame: true) |> deliverOnMainQueue).start(next: { [weak self] result in + recallDisposable.set((phoneCall(account: session.account, sharedContext: session.sharedContext, peerId: session.peerId, ignoreSame: true) |> deliverOnMainQueue).start(next: { [weak self] result in switch result { case let .success(session): self?.session = session @@ -479,9 +461,9 @@ class PhoneCallWindowController { })) } - private func applyState(_ state:CallSessionState, animated: Bool) { + private func applyState(_ state:CallSessionState, accountPeer: Peer?, animated: Bool) { self.state = state - view.updateState(state, animated: animated) + view.updateState(state, accountPeer: accountPeer, animated: animated) switch state { case .ringing: break @@ -490,10 +472,10 @@ class PhoneCallWindowController { case .requesting: break case .active: - session.account.context.showCallHeader(with: session) + session.sharedContext.showCallHeader(with: session) case .dropping: break - case .terminated(let error, _): + case .terminated(_, let error, _): switch error { case .ended(let reason): switch reason { @@ -504,27 +486,21 @@ class PhoneCallWindowController { } case let .error(error): closeCall(1.0) - disposable.set((session.account.viewTracker.peerView( session.peerId) |> deliverOnMainQueue).start(next: { peerView in - if let peer = peerViewMainPeer(peerView) { - switch error { - case .privacyRestricted: - alert(for: mainWindow, info: tr(.callPrivacyErrorMessage(peer.compactDisplayTitle))) - case .notSupportedByPeer: - alert(for: mainWindow, info: tr(.callParticipantVersionOutdatedError(peer.compactDisplayTitle))) - case .serverProvided(let serverError): - alert(for: mainWindow, info: serverError) - case .generic: - alert(for: mainWindow, info: tr(.callUndefinedError)) - default: - break - } - + disposable.set((session.account.postbox.loadedPeerWithId(session.peerId) |> deliverOnMainQueue).start(next: { peer in + switch error { + case .privacyRestricted: + alert(for: mainWindow, info: L10n.callPrivacyErrorMessage(peer.compactDisplayTitle)) + case .notSupportedByPeer: + alert(for: mainWindow, info: L10n.callParticipantVersionOutdatedError(peer.compactDisplayTitle)) + case .serverProvided(let serverError): + alert(for: mainWindow, info: serverError) + case .generic: + alert(for: mainWindow, info: L10n.callUndefinedError) + default: + break } })) } - - - } } @@ -535,17 +511,17 @@ class PhoneCallWindowController { recallDisposable.dispose() peerDisposable.dispose() keyStateDisposable.dispose() + fetching.dispose() updateLocalizationAndThemeDisposable.dispose() NotificationCenter.default.removeObserver(self) } func show() { var first: Bool = true - disposable.set((session.account.viewTracker.peerView( session.peerId) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerView in + disposable.set((session.account.postbox.loadedPeerWithId(session.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { - if let user = peerView.peers[peerView.peerId] as? TelegramUser { - strongSelf.updatePeerUI(user) - } + strongSelf.updatePeerUI(peer as! TelegramUser) + if first { first = false strongSelf.window.makeKeyAndOrderFront(self) @@ -559,32 +535,26 @@ class PhoneCallWindowController { private func updatePeerUI(_ user:TelegramUser) { - let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: user.profileImageRepresentations) + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: user.id.toInt64()), representations: user.profileImageRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - - if let dimension = user.profileImageRepresentations.last?.dimensions { + if let dimension = user.profileImageRepresentations.last?.dimensions.size { let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimension, boundingSize: view.imageView.frame.size, intrinsicInsets: NSEdgeInsets()) - view.imageView.setSignal(signal: cachedMedia(media: media, size: arguments.imageSize, scale: view.backingScaleFactor)) - view.imageView.setSignal(account: session.account, signal: chatMessagePhoto(account: session.account, photo: media, scale: view.backingScaleFactor), clearInstantly: false, animate: true, cacheImage: { [weak self] image in - if let strongSelf = self { - return cacheMedia(signal: image, media: media, size: arguments.imageSize, scale: strongSelf.view.backingScaleFactor) - } else { - return .complete() - } + view.imageView.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: view.backingScaleFactor), clearInstantly: true) + view.imageView.setSignal(chatMessagePhoto(account: session.account, imageReference: ImageMediaReference.standalone(media: media), peer: user, scale: view.backingScaleFactor), clearInstantly: false, animate: true, cacheImage: { result in + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) }) view.imageView.set(arguments: arguments) + if let reference = PeerReference(user) { + fetching.set(fetchedMediaResource(mediaBox: session.account.postbox.mediaBox, reference: .avatar(peer: reference, resource: media.representations.last!.resource)).start()) + } + } else { - view.imageView.setSignal(signal: generateEmptyRoundAvatar(view.imageView.frame.size, font: .avatar(.custom(90)), account: session.account, peer: user)) + view.imageView.setSignal(signal: generateEmptyRoundAvatar(view.imageView.frame.size, font: .avatar(90.0), account: session.account, peer: user) |> map { TransformImageResult($0, true) }) } - - - _ = chatMessagePhotoInteractiveFetched(account: session.account, photo: media).start() - - view.updateName(user.displayTitle) } @@ -609,31 +579,27 @@ func showPhoneCallWindow(_ session:PCallSession) { private let closeDisposable = MetaDisposable() func closeCall(_ timeout:TimeInterval? = nil) { - var signal = Signal.single(Void()) |> deliverOnMainQueue + var signal = Signal.single(Void()) |> deliverOnMainQueue if let timeout = timeout { signal = signal |> delay(timeout, queue: Queue.mainQueue()) } closeDisposable.set(signal.start(completed: { - controller?.window.styleMask = [.borderless] controller?.view.controls.removeFromSuperview() - controller?.window.contentView?._change(opacity: 0.0, removeOnCompletion: false, completion: { completed in - controller?.window.orderOut(nil) - controller = nil - }) + controller?.window.orderOut(nil) + controller = nil })) } -func applyUIPCallResult(_ account:Account, _ result:PCallResult) { +func applyUIPCallResult(_ sharedContext: SharedAccountContext, _ result:PCallResult) { assertOnMainThread() - switch result { case let .success(session): showPhoneCallWindow(session) case .fail: break case let .samePeer(session): - if let header = account.context.mainNavigation?.callHeader, header.needShown { + if let header = sharedContext.bindings.rootNavigation().callHeader, header.needShown { (header.view as? CallNavigationHeaderView)?.hide() showPhoneCallWindow(session) } else { diff --git a/Telegram-Mac/PhoneCountries.txt b/Telegram-Mac/PhoneCountries.txt index fc3b5f36f7..417ab7fecc 100644 --- a/Telegram-Mac/PhoneCountries.txt +++ b/Telegram-Mac/PhoneCountries.txt @@ -228,4 +228,5 @@ 1;US;USA 1;PR;Puerto Rico 1;DO;Dominican Rep. -1;CA;Canada \ No newline at end of file +1;CA;Canada +383;XK;Kosovo diff --git a/Telegram-Mac/PhoneNumberConfirmController.swift b/Telegram-Mac/PhoneNumberConfirmController.swift index 63c72fee2c..8ba9e8dcb0 100644 --- a/Telegram-Mac/PhoneNumberConfirmController.swift +++ b/Telegram-Mac/PhoneNumberConfirmController.swift @@ -8,141 +8,28 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac -import MtProtoKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + private let manager = CountryManager() -private final class ChangePhoneNumberArguments { - let sendCode:(String)->Void - init(sendCode:@escaping(String)->Void) { - self.sendCode = sendCode - } -} + class ChangePhoneNumberView : View { - fileprivate let container: ChangePhoneNumberContainerView = ChangePhoneNumberContainerView(frame: NSMakeRect(0, 0, 300, 110)) + fileprivate let container: ChangePhoneNumberContainerView = ChangePhoneNumberContainerView(frame: NSMakeRect(0, 0, 300, 110), manager: manager) required init(frame frameRect: NSRect) { super.init(frame: frameRect) addSubview(container) + updateLocalizationAndTheme(theme: theme) } - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layout() { - container.centerX(y: 20) - } -} - - class ChangePhoneNumberContainerView : View, NSTextFieldDelegate { - - fileprivate var arguments:ChangePhoneNumberArguments? - - - private let countrySelector:TitleButton = TitleButton() - - let countryLabel:TextViewLabel = TextViewLabel() - let numberLabel:TextViewLabel = TextViewLabel() - - fileprivate let errorLabel:LoginErrorStateView = LoginErrorStateView() - - let codeText:NSTextField = NSTextField() - let numberText:NSTextField = NSTextField() - - fileprivate var selectedItem:CountryItem? - - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - - countrySelector.style = ControlStyle(font: NSFont.medium(.title), foregroundColor: theme.colors.blueUI, backgroundColor: theme.colors.background) - countrySelector.set(text: "France", for: .Normal) - countrySelector.sizeToFit() - addSubview(countrySelector) - - - - - addSubview(countryLabel) - addSubview(numberLabel) - - countrySelector.set(handler: { [weak self] _ in - self?.showCountrySelector() - }, for: .Click) - - updateLocalizationAndTheme() - - codeText.stringValue = "+" - - codeText.textColor = theme.colors.text - codeText.font = NSFont.normal(.title) - numberText.textColor = theme.colors.text - numberText.font = NSFont.normal(.title) - - numberText.isBordered = false - numberText.isBezeled = false - numberText.drawsBackground = false - numberText.focusRingType = .none - - codeText.drawsBackground = false - codeText.isBordered = false - codeText.isBezeled = false - codeText.focusRingType = .none - - codeText.delegate = self - codeText.nextResponder = numberText - codeText.nextKeyView = numberText - - numberText.delegate = self - numberText.nextResponder = codeText - numberText.nextKeyView = codeText - addSubview(codeText) - addSubview(numberText) - - errorLabel.layer?.opacity = 0 - addSubview(errorLabel) - - let code = NSLocale.current.regionCode ?? "US" - update(selectedItem: manager.item(bySmallCountryName: code), update: true) - - } - - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - - countryLabel.attributedString = .initialize(string: tr(.loginCountryLabel), color: theme.colors.grayText, font: NSFont.normal(FontSize.title)) - countryLabel.sizeToFit() - - numberLabel.attributedString = .initialize(string: tr(.loginYourPhoneLabel), color: theme.colors.grayText, font: NSFont.normal(FontSize.title)) - numberLabel.sizeToFit() - - numberText.placeholderAttributedString = NSAttributedString.initialize(string: tr(.loginPhoneFieldPlaceholder), color: theme.colors.grayText, font: NSFont.normal(.header), coreText: false) - - needsLayout = true - } - - func setPhoneError(_ error: AuthorizationCodeRequestError) { - let text:String - switch error { - case .invalidPhoneNumber: - text = tr(.phoneNumberInvalid) - case .limitExceeded: - text = tr(.loginFloodWait) - case .generic: - text = "undefined error" - } - errorLabel.state.set(.single(.error(text))) - } - - func update(countryCode: Int32, number: String) { - self.codeText.stringValue = "\(countryCode)" - self.numberText.stringValue = formatPhoneNumber(number) + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.background } required init?(coder: NSCoder) { @@ -150,171 +37,11 @@ class ChangePhoneNumberView : View { } override func layout() { - super.layout() - codeText.sizeToFit() - numberText.sizeToFit() - - let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) - let contentInset = maxInset + 20 + 5 - countrySelector.setFrameOrigin(contentInset, floorToScreenPixels(25 - countrySelector.frame.height/2)) - - countryLabel.setFrameOrigin(maxInset - countryLabel.frame.width, floorToScreenPixels(25 - countryLabel.frame.height/2)) - numberLabel.setFrameOrigin(maxInset - numberLabel.frame.width, floorToScreenPixels(75 - numberLabel.frame.height/2)) - - codeText.setFrameOrigin(contentInset, floorToScreenPixels(75 - codeText.frame.height/2)) - numberText.setFrameOrigin(contentInset + separatorInset, floorToScreenPixels(75 - codeText.frame.height/2)) - errorLabel.centerX(y: 120) - } - - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - - let maxInset = max(countryLabel.frame.width,numberLabel.frame.width) + 20 - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(maxInset, 50, frame.width - maxInset, .borderSize)) - ctx.fill(NSMakeRect(maxInset, 100, frame.width - maxInset, .borderSize)) - // ctx.fill(NSMakeRect(maxInset + separatorInset, 50, .borderSize, 50)) - } - - - func showCountrySelector() { - - var items:[ContextMenuItem] = [] - for country in manager.countries { - let item = ContextMenuItem(country.fullName, handler: { [weak self] in - self?.update(selectedItem: country, update: true) - }) - items.append(item) - } - if let currentEvent = NSApp.currentEvent { - ContextMenu.show(items: items, view: countrySelector, event: currentEvent, onShow: {(menu) in - - }, onClose: {}) - } - - } - - override func controlTextDidChange(_ obj: Notification) { - - if let field = obj.object as? NSTextField { - let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() - let dec = code.prefix(4) - - if field == codeText { - - - if code.length > 4 { - let list = Array(code.characters).map {String($0)} - let reduced = list.reduce([], { current, value -> [String] in - var current = current - current.append((current.last ?? "") + value) - return current - }).map({Int($0)}).filter({$0 != nil}).map({$0!}) - - var found: Bool = false - for _code in reduced { - if let item = manager.item(byCodeNumber: _code) { - codeText.stringValue = "+" + String(_code) - update(selectedItem: item, update: true, updateCode: false) - - let codeString = String(_code) - var formated = formatPhoneNumber(codeString + String(code[codeString.endIndex.. Bool { - if commandSelector == #selector(insertNewline(_:)) { - if control == codeText { - self.window?.makeFirstResponder(self.numberText) - self.numberText.selectText(nil) - } else if !numberText.stringValue.isEmpty { - arguments?.sendCode(number) - } - //Queue.mainQueue().justDispatch { - (control as? NSTextField)?.setCursorToEnd() - //} - return true - } else if commandSelector == #selector(deleteBackward(_:)) { - if control == numberText { - if numberText.stringValue.isEmpty { - Queue.mainQueue().justDispatch { - self.window?.makeFirstResponder(self.codeText) - self.codeText.setCursorToEnd() - } - } - } - return false - - } - return false - } - - func update(selectedItem:CountryItem?, update:Bool, updateCode:Bool = true) -> Void { - self.selectedItem = selectedItem - if update { - countrySelector.set(text: selectedItem?.shortName ?? tr(.loginInvalidCountryCode), for: .Normal) - countrySelector.sizeToFit() - if updateCode { - codeText.stringValue = selectedItem != nil ? "+\(selectedItem!.code)" : "+" - } - needsLayout = true - setNeedsDisplayLayer() - - } - } - - - - var separatorInset:CGFloat { - return codeText.frame.width + 10 + container.centerX(y: 20) } - } + class PhoneNumberConfirmController: TelegramGenericViewController { private let actionDisposable = MetaDisposable() @@ -322,40 +49,39 @@ class PhoneNumberConfirmController: TelegramGenericViewController deliverOnMainQueue, for: mainWindow).start(next: { [weak strongSelf] data in + strongSelf.actionDisposable.set(showModalProgress(signal: requestChangeAccountPhoneNumberVerification(account: context.account, phoneNumber: phoneNumber) |> deliverOnMainQueue, for: mainWindow).start(next: { [weak strongSelf] data in - strongSelf?.navigationController?.push(PhoneNumberInputCodeController(account, data: data, formattedNumber: formatPhoneNumber(phoneNumber))) + strongSelf?.navigationController?.push(PhoneNumberInputCodeController(context, data: data, formattedNumber: formatPhoneNumber(phoneNumber))) }, error: { error in let text: String switch error { case .limitExceeded: - text = tr(.changeNumberSendDataErrorLimitExceeded) + text = tr(L10n.changeNumberSendDataErrorLimitExceeded) case .invalidPhoneNumber: - text = tr(.changeNumberSendDataErrorInvalidPhoneNumber) + text = tr(L10n.changeNumberSendDataErrorInvalidPhoneNumber) case .phoneNumberOccupied: - text = tr(.changeNumberSendDataErrorPhoneNumberOccupied(phoneNumber)) + text = tr(L10n.changeNumberSendDataErrorPhoneNumberOccupied(phoneNumber)) case .generic: - text = tr(.changeNumberSendDataErrorGeneric) + text = tr(L10n.changeNumberSendDataErrorGeneric) } - alert(for: mainWindow, header: appName, info: text) + alert(for: mainWindow, info: text) })) }) genericView.container.arguments = arguments - (self.rightBarView as? TextButtonBarView)?.button.set(handler:{ [weak self] _ in + self.rightBarView.set(handler:{ [weak self] _ in if let strongSelf = self { arguments.sendCode(strongSelf.genericView.container.number) } @@ -385,7 +111,7 @@ class PhoneNumberConfirmController: TelegramGenericViewController BarView { - return TextButtonBarView(controller: self, text: tr(.composeNext), style: navigationButtonStyle, alignment:.Right) + return TextButtonBarView(controller: self, text: tr(L10n.composeNext), style: navigationButtonStyle, alignment:.Right) } } diff --git a/Telegram-Mac/PhoneNumberInputCodeController.swift b/Telegram-Mac/PhoneNumberInputCodeController.swift index 9849b1675b..a5ed4b12ce 100644 --- a/Telegram-Mac/PhoneNumberInputCodeController.swift +++ b/Telegram-Mac/PhoneNumberInputCodeController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private final class ConfirmCodeArguments { @@ -60,7 +61,7 @@ class PhoneNumberInputCodeView : View, NSTextFieldDelegate { codeText.delegate = self - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { @@ -70,7 +71,7 @@ class PhoneNumberInputCodeView : View, NSTextFieldDelegate { return false } - override func controlTextDidChange(_ obj: Notification) { + func controlTextDidChange(_ obj: Notification) { let code = codeText.stringValue.components(separatedBy: CharacterSet.decimalDigits.inverted).joined().prefix(Int(codeLength)) codeText.stringValue = code codeText.sizeToFit() @@ -93,17 +94,17 @@ class PhoneNumberInputCodeView : View, NSTextFieldDelegate { var nextText: String = "" switch nextType { case .call: - nextText = tr(.loginWillCall(minutes, secValue)) + nextText = tr(L10n.loginWillCall(minutes, secValue)) break case .sms: - nextText = tr(.loginWillSendSms(minutes, secValue)) + nextText = tr(L10n.loginWillSendSms(minutes, secValue)) break default: break } layout = TextViewLayout(.initialize(string: nextText, color: theme.colors.grayText, font: .normal(.text))) } else { - layout = TextViewLayout(.initialize(string: tr(.loginPhoneDialed), color: theme.colors.grayText, font: .normal(.text))) + layout = TextViewLayout(.initialize(string: tr(L10n.loginPhoneDialed), color: theme.colors.grayText, font: .normal(.text))) } layout.measure(width: frame.width - 60) callField.update(layout) @@ -120,21 +121,21 @@ class PhoneNumberInputCodeView : View, NSTextFieldDelegate { super.draw(layer, in: ctx) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) yourCodeField.backgroundColor = theme.colors.grayBackground sentCodeField.backgroundColor = theme.colors.grayBackground callField.backgroundColor = theme.colors.grayBackground - let yourCodeLayout = TextViewLayout(.initialize(string: tr(.loginYourCodeLabel).uppercased(), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + let yourCodeLayout = TextViewLayout(.initialize(string: tr(L10n.loginYourCodeLabel).uppercased(), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) - let sentCodeLayout = TextViewLayout(.initialize(string: tr(.loginJustSentSms), color: theme.colors.grayText, font: .normal(.text))) + let sentCodeLayout = TextViewLayout(.initialize(string: tr(L10n.loginJustSentSms), color: theme.colors.grayText, font: .normal(.text))) yourCodeField.update(yourCodeLayout) sentCodeField.update(sentCodeLayout) let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.loginCodePlaceholder), color: theme.colors.grayText, font: .normal(.title)) + _ = attr.append(string: tr(L10n.loginCodePlaceholder), color: theme.colors.grayText, font: .normal(.title)) attr.setAlignment(.center, range: attr.range) codeText.placeholderAttributedString = attr backgroundColor = theme.colors.grayBackground @@ -167,10 +168,10 @@ class PhoneNumberInputCodeController: TelegramGenericViewControllerVoid { - changePhoneDisposable.set(showModalProgress(signal: requestChangeAccountPhoneNumber(account: account, phoneNumber: formattedNumber, phoneCodeHash: data.hash, phoneCode: code) |> deliverOnMainQueue, for: mainWindow).start(error: { [weak self] error in + changePhoneDisposable.set(showModalProgress(signal: requestChangeAccountPhoneNumber(account: context.account, phoneNumber: formattedNumber, phoneCodeHash: data.hash, phoneCode: code) |> deliverOnMainQueue, for: mainWindow).start(error: { [weak self] error in var alertText: String = "" switch error { case .generic: - alertText = tr(.changeNumberConfirmCodeErrorGeneric) + alertText = tr(L10n.changeNumberConfirmCodeErrorGeneric) case .invalidCode: self?.genericView.codeText.shake() self?.genericView.codeText.setSelectionRange(NSMakeRange(0, code.length)) return case .codeExpired: - alertText = tr(.changeNumberConfirmCodeErrorCodeExpired) + alertText = tr(L10n.changeNumberConfirmCodeErrorCodeExpired) case .limitExceeded: - alertText = tr(.changeNumberConfirmCodeErrorLimitExceeded) + alertText = tr(L10n.changeNumberConfirmCodeErrorLimitExceeded) } - alert(for: mainWindow, header: appName, info: alertText) + alert(for: mainWindow, info: alertText) }, completed: { [weak self] in if let strongSelf = self { strongSelf.navigationController?.close(animated: true) - alert(for: mainWindow, header: appName, info: tr(.changeNumberConfirmCodeSuccess(strongSelf.formattedNumber))) + alert(for: mainWindow, info: tr(L10n.changeNumberConfirmCodeSuccess(strongSelf.formattedNumber))) } })) } @@ -230,7 +231,7 @@ class PhoneNumberInputCodeController: TelegramGenericViewController BarView { - return TextButtonBarView(controller: self, text: tr(.composeNext), style: navigationButtonStyle, alignment:.Right) + return TextButtonBarView(controller: self, text: tr(L10n.composeNext), style: navigationButtonStyle, alignment:.Right) } } diff --git a/Telegram-Mac/PhoneNumberIntroController.swift b/Telegram-Mac/PhoneNumberIntroController.swift index baf2ec96a5..d71b505401 100644 --- a/Telegram-Mac/PhoneNumberIntroController.swift +++ b/Telegram-Mac/PhoneNumberIntroController.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit class ChaneNumberIntroView : NSScrollView, AppearanceViewProtocol { let imageView:ImageView = ImageView() @@ -22,11 +23,11 @@ class ChaneNumberIntroView : NSScrollView, AppearanceViewProtocol { documentView?.addSubview(imageView) documentView?.addSubview(textView) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - func updateLocalizationAndTheme() { - + func updateLocalizationAndTheme(theme: PresentationTheme) { + let theme = (theme as! TelegramPresentationTheme) imageView.image = theme.icons.changePhoneNumberIntro imageView.sizeToFit() @@ -34,7 +35,7 @@ class ChaneNumberIntroView : NSScrollView, AppearanceViewProtocol { textView.background = theme.colors.background documentView?.background = theme.colors.background let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.changePhoneNumberIntroDescription), color: theme.colors.grayText, font: .normal(.text)) + _ = attr.append(string: tr(L10n.changePhoneNumberIntroDescription), color: theme.colors.grayText, font: .normal(.text)) attr.detectBoldColorInString(with: .bold(.text)) textView.set(layout: TextViewLayout(attr, alignment:.center)) @@ -63,14 +64,14 @@ class PhoneNumberIntroController: EmptyComposeController deliverOnMainQueue |> map { [weak self] peer -> Bool in + ready.set(context.account.postbox.loadedPeerWithId(context.peerId) |> deliverOnMainQueue |> map { [weak self] peer -> Bool in if let phone = (peer as? TelegramUser)?.phone { self?.setCenterTitle(formatPhoneNumber("+" + phone)) } return true }) - (self.rightBarView as? TextButtonBarView)?.button.set(handler:{ [weak self] _ in + self.rightBarView.set(handler:{ [weak self] _ in self?.executeNext() }, for: .Click) @@ -85,13 +86,13 @@ class PhoneNumberIntroController: EmptyComposeController BarView { - return TextButtonBarView(controller: self, text: tr(.composeNext), style: navigationButtonStyle, alignment:.Right) + return TextButtonBarView(controller: self, text: L10n.composeNext, style: navigationButtonStyle, alignment:.Right) } func executeNext() { - confirm(for: mainWindow, with: appName, and: tr(.changePhoneNumberIntroAlert), successHandler: { [weak self] _ in - if let account = self?.account { - self?.navigationController?.push(PhoneNumberConfirmController(account)) + confirm(for: mainWindow, information: L10n.changePhoneNumberIntroAlert, successHandler: { [weak self] _ in + if let context = self?.context { + self?.navigationController?.push(PhoneNumberConfirmController(context)) } }) } diff --git a/Telegram-Mac/PhoneNumberUtils.swift b/Telegram-Mac/PhoneNumberUtils.swift new file mode 100644 index 0000000000..50edc47a4d --- /dev/null +++ b/Telegram-Mac/PhoneNumberUtils.swift @@ -0,0 +1,49 @@ +// +// PhoneNumberUtils.swift +// Telegram +// +// Created by Mikhail Filimonov on 01.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import libphonenumber + +private let phoneNumberUtil = NBPhoneNumberUtil() +func formatPhoneNumber(_ string: String) -> String { + do { + let number = try phoneNumberUtil.parse("+" + string, defaultRegion: nil) + return try phoneNumberUtil.format(number, numberFormat: .INTERNATIONAL) + } catch _ { + return string + } +} + +func isViablePhoneNumber(_ string: String) -> Bool { + return phoneNumberUtil.isViablePhoneNumber(string) +} + +class ParsedPhoneNumber: Equatable { + let rawPhoneNumber: NBPhoneNumber? + + init?(string: String) { + if let number = try? phoneNumberUtil.parse(string, defaultRegion: NB_UNKNOWN_REGION) { + self.rawPhoneNumber = number + } else { + return nil + } + } + + static func == (lhs: ParsedPhoneNumber, rhs: ParsedPhoneNumber) -> Bool { + var error: NSError? + let result = phoneNumberUtil.isNumberMatch(lhs.rawPhoneNumber, second: rhs.rawPhoneNumber, error: &error) + if error != nil { + return false + } + if result != .NO_MATCH && result != .NOT_A_NUMBER { + return true + } else { + return false + } + } +} diff --git a/Telegram-Mac/PhotoCache.swift b/Telegram-Mac/PhotoCache.swift index a7aa8125c4..0d59f1d9f0 100644 --- a/Telegram-Mac/PhotoCache.swift +++ b/Telegram-Mac/PhotoCache.swift @@ -7,33 +7,97 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox +import TGUIKit +import SyncCore -struct PhotoCachedRecord { +enum ThemeSource : Equatable { + case local(ColorPalette, TelegramTheme?) + case cloud(TelegramTheme) +} + +private final class PhotoCachedRecord { let date:TimeInterval let image:CGImage let size:Int init(image:CGImage, size:Int) { - self.date = CFAbsoluteTimeGetCurrent() + self.date = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970 self.size = size self.image = image } } +public final class TransformImageResult { + let image: CGImage? + let highQuality: Bool + init(_ image: CGImage?, _ highQuality: Bool) { + self.image = image + self.highQuality = highQuality + } + deinit { + + } +} + + enum PhotoCacheKeyEntry : Hashable { case avatar(PeerId, TelegramMediaImageRepresentation, NSSize, CGFloat) case emptyAvatar(PeerId, String, NSColor, NSSize, CGFloat) - case media(Media, NSSize, CGFloat) + case media(Media, TransformImageArguments, CGFloat, LayoutPositionFlags?) + case platformTheme(TelegramThemeSettings, TransformImageArguments, CGFloat, LayoutPositionFlags?) + case messageId(stableId: Int64, TransformImageArguments, CGFloat, LayoutPositionFlags) + case theme(ThemeSource, Bool) var hashValue:Int { + return 0 + } + + var stringValue: NSString { switch self { - case let .avatar(peerId, _, _, _): - return peerId.id.hashValue - case let .emptyAvatar(peerId, _, _, _, _): - return peerId.id.hashValue - case .media: - return 0 + case let .avatar(peerId, rep, size, scale): + return "avatar-\(peerId.toInt64())-\(rep.resource.id.hashValue)-\(size.width)-\(size.height)-\(scale)".nsstring + case let .emptyAvatar(peerId, letters, color, size, scale): + return "emptyAvatar-\(peerId.toInt64())-\(letters)-\(color.hexString)-\(size.width)-\(size.height)-\(scale)".nsstring + case let .media(media, transform, scale, layout): + var addition: String = "" + if let media = media as? TelegramMediaMap { + addition = "\(media.longitude)-\(media.latitude)" + } + if let media = media as? TelegramMediaFile { + addition += "\(media.resource.id.uniqueId)-\(String(describing: media.resource.size))" + #if !SHARE + if let fitz = media.animatedEmojiFitzModifier { + addition += "fitz-\(fitz.rawValue)" + } + #endif + } + return "media-\(String(describing: media.id?.id))-\(transform)-\(scale)-\(String(describing: layout?.rawValue))-\(addition)".nsstring + case let .messageId(stableId, transform, scale, layout): + return "messageId-\(stableId)-\(transform)-\(scale)-\(layout.rawValue)".nsstring + case let .theme(source, bubbled): + switch source { + case let .local(palette, cloud): + if let settings = cloud?.settings { + #if !SHARE + return "theme-local-\(palette.name)-bubbled\(bubbled ? 1 : 0)-\(settings.desc)".nsstring + #else + return "" + #endif + } else { + return "theme-local-\(palette.name)-bubbled\(bubbled ? 1 : 0)-\(palette.accent.argb)".nsstring + } + case let .cloud(cloud): + return "theme-remote-\(cloud.id)\(String(describing: cloud.file?.id))-bubbled\(bubbled ? 1 : 0)".nsstring + } + case let .platformTheme(settings, arguments, scale, layout): + #if !SHARE + return "theme-\(settings.desc)-\(arguments)-\(scale)-\(String(describing: layout?.rawValue))".nsstring + #else + return "" + #endif + } } @@ -63,14 +127,22 @@ enum PhotoCacheKeyEntry : Hashable { } else { return false } - case let .media(lhsMedia, lhsSize, lhsScale): - if case let .media(rhsMedia, rhsSize, rhsScale) = rhs { - if !lhsMedia.isEqual(rhsMedia) { + case let .media(lhsMedia, lhsSize, lhsScale, lhsPositionFlags): + if case let .media(rhsMedia, rhsSize, rhsScale, rhsPositionFlags) = rhs { + if lhsMedia.id != rhsMedia.id { return false } + if let lhsMedia = lhsMedia as? TelegramMediaMap, let rhsMedia = rhsMedia as? TelegramMediaMap { + if lhsMedia.latitude != rhsMedia.latitude || lhsMedia.longitude != rhsMedia.longitude { + return false + } + } if lhsSize != rhsSize { return false } + if lhsPositionFlags != rhsPositionFlags { + return false + } if lhsScale != rhsScale { return false } @@ -78,109 +150,211 @@ enum PhotoCacheKeyEntry : Hashable { } else { return false } + case let .messageId(stableId, size, scale, positionFlags): + if case .messageId(stableId, size, scale, positionFlags) = rhs { + return true + } else { + return false + } + case let .theme(source, bubbled): + if case .theme(source, bubbled) = rhs { + return true + } else { + return false + } + case let .platformTheme(settings, arguments, scale, position): + if case .platformTheme(settings, arguments, scale, position) = rhs { + return true + } else { + return false + } } } } -class PhotoCache { +private class PhotoCache { let memoryLimit:Int - let maxCount:Int = 1000 - private var values:[PhotoCacheKeyEntry:PhotoCachedRecord] = [:] - private let queue:Queue = Queue() + let maxCount:Int = 50 + private var values:NSCache = NSCache() - init(_ memoryLimit:Int = 16*1024*1024) { + init(_ memoryLimit:Int = 15) { self.memoryLimit = memoryLimit + self.values.countLimit = memoryLimit } - func cacheImage(_ image:CGImage, for key:PhotoCacheKeyEntry) { - queue.justDispatch { - self.values[key] = PhotoCachedRecord(image: image, size: Int(image.size.width * image.size.height * 4)) - self.freeMemoryIfNeeded() - } + fileprivate func cacheImage(_ image:CGImage, for key:PhotoCacheKeyEntry) { + self.values.setObject(PhotoCachedRecord(image: image, size: Int(image.backingSize.width * image.backingSize.height * 4)), forKey: key.stringValue) } private func freeMemoryIfNeeded() { - assert(queue.isCurrent()) - - let total = values.reduce(0, { (current, value: (key: PhotoCacheKeyEntry, value: PhotoCachedRecord)) -> Int in - return current + value.value.size - }) - - if total > memoryLimit { - let list = values.map ({($0.key, $0.value)}).sorted(by: { lhs, rhs -> Bool in - return lhs.1.date < rhs.1.date - }) - - var clearedMemorySize: Int = 0 - - for entry in list { - values.removeValue(forKey: entry.0) - clearedMemorySize += entry.1.size - - if total - clearedMemorySize < memoryLimit { - break - } - } - } } func cachedImage(for key:PhotoCacheKeyEntry) -> CGImage? { var image:CGImage? = nil - queue.sync { - image = self.values[key]?.image - } + image = self.values.object(forKey: key.stringValue)?.image return image } func removeRecord(for key:PhotoCacheKeyEntry) { - queue.justDispatch { - self.values.removeValue(forKey: key) - } + self.values.removeObject(forKey: key.stringValue) + } + + func clearAll() { + self.values.removeAllObjects() } } -private let peerPhotoCache = PhotoCache() -private let stickersCache = PhotoCache(32 * 1024 * 1024) +private let peerPhotoCache = PhotoCache(100) +private let photosCache = PhotoCache(50) +private let photoThumbsCache = PhotoCache(50) +private let themeThums = PhotoCache(100) + +private let stickersCache = PhotoCache(500) + +func clearImageCache() -> Signal { + return Signal { subscriber -> Disposable in + photosCache.clearAll() + photoThumbsCache.clearAll() + peerPhotoCache.clearAll() + subscriber.putNext(Void()) + subscriber.putCompletion() + return EmptyDisposable + } +} -func cachedPeerPhoto(_ peerId:PeerId, representation: TelegramMediaImageRepresentation, size: NSSize, scale: CGFloat) -> Signal { +func cachedPeerPhoto(_ peerId:PeerId, representation: TelegramMediaImageRepresentation, size: NSSize, scale: CGFloat) -> Signal { let entry:PhotoCacheKeyEntry = .avatar(peerId, representation, size, scale) return .single(peerPhotoCache.cachedImage(for: entry)) } -func cachePeerPhoto(image:CGImage, peerId:PeerId, representation: TelegramMediaImageRepresentation, size: NSSize, scale: CGFloat) -> Signal { +func cachePeerPhoto(image:CGImage, peerId:PeerId, representation: TelegramMediaImageRepresentation, size: NSSize, scale: CGFloat) -> Signal { let entry:PhotoCacheKeyEntry = .avatar(peerId, representation, size, scale) return .single(peerPhotoCache.cacheImage(image, for: entry)) } -func cachedEmptyPeerPhoto(_ peerId:PeerId, symbol: String, color: NSColor, size: NSSize, scale: CGFloat) -> Signal { +func cachedEmptyPeerPhoto(_ peerId:PeerId, symbol: String, color: NSColor, size: NSSize, scale: CGFloat) -> Signal { let entry:PhotoCacheKeyEntry = .emptyAvatar(peerId, symbol, color, size, scale) return .single(peerPhotoCache.cachedImage(for: entry)) } -func cacheEmptyPeerPhoto(image:CGImage, peerId:PeerId, symbol: String, color: NSColor, size: NSSize, scale: CGFloat) -> Signal { +func cacheEmptyPeerPhoto(image:CGImage, peerId:PeerId, symbol: String, color: NSColor, size: NSSize, scale: CGFloat) -> Signal { let entry:PhotoCacheKeyEntry = .emptyAvatar(peerId, symbol, color, size, scale) return .single(peerPhotoCache.cacheImage(image, for: entry)) } +func cachedPeerPhotoImmediatly(_ peerId:PeerId, representation: TelegramMediaImageRepresentation, size: NSSize, scale: CGFloat) -> CGImage? { + let entry:PhotoCacheKeyEntry = .avatar(peerId, representation, size, scale) + return peerPhotoCache.cachedImage(for: entry) +} +func cachedEmptyPeerPhotoImmediatly(_ peerId:PeerId, symbol: String, color: NSColor, size: NSSize, scale: CGFloat) -> CGImage? { + let entry:PhotoCacheKeyEntry = .emptyAvatar(peerId, symbol, color, size, scale) + return peerPhotoCache.cachedImage(for: entry) +} - -func cachedMedia(media: Media, size: NSSize, scale: CGFloat) -> Signal { - let entry:PhotoCacheKeyEntry = .media(media, size, scale) - return .single(stickersCache.cachedImage(for: entry)) +func cachedMedia(media: Media, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Signal { + let entry:PhotoCacheKeyEntry = .media(media, arguments, scale, positionFlags) + let value: CGImage? + var full: Bool = false + + if arguments.imageSize.width <= 60, let media = media as? TelegramMediaFile, media.isStaticSticker || media.isAnimatedSticker, let image = stickersCache.cachedImage(for: entry) { + value = image + full = true + } else if let image = photosCache.cachedImage(for: entry) { + value = image + full = true + } else { + value = photoThumbsCache.cachedImage(for: entry) + } + return .single(TransformImageResult(value, full)) } -func cacheMedia(signal:Signal, media: Media, size: NSSize, scale: CGFloat) -> Signal { - let entry:PhotoCacheKeyEntry = .media(media, size, scale) +func cachedMedia(media: TelegramThemeSettings, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Signal { + let entry:PhotoCacheKeyEntry = .platformTheme(media, arguments, scale, positionFlags) + let value: CGImage? + var full: Bool = false - return signal |> mapToSignal { image -> Signal in - if let image = image { - return .single(stickersCache.cacheImage(image, for: entry)) + if let image = photosCache.cachedImage(for: entry) { + value = image + full = true + } else { + value = nil + } + return .single(TransformImageResult(value, full)) +} + +func cachedMedia(messageId: Int64, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Signal { + let entry:PhotoCacheKeyEntry = .messageId(stableId: messageId, arguments, scale, positionFlags ?? []) + let value: CGImage? + var full: Bool = false + if let image = photosCache.cachedImage(for: entry) { + value = image + full = true + } else { + value = photoThumbsCache.cachedImage(for: entry) + } + return .single(TransformImageResult(value, full)) +} + +func cacheMedia(_ result: TransformImageResult, media: Media, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Void { + if let image = result.image { + let entry:PhotoCacheKeyEntry = .media(media, arguments, scale, positionFlags) + if arguments.imageSize.width <= 60, result.highQuality, let media = media as? TelegramMediaFile, media.isStaticSticker || media.isAnimatedSticker { + stickersCache.cacheImage(image, for: entry) + } else if !result.highQuality { + photoThumbsCache.cacheImage(image, for: entry) + } else { + photosCache.cacheImage(image, for: entry) } - return .complete() } +} + +func cacheMedia(_ result: TransformImageResult, media: TelegramThemeSettings, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Void { + if let image = result.image { + let entry:PhotoCacheKeyEntry = .platformTheme(media, arguments, scale, positionFlags) + photosCache.cacheImage(image, for: entry) + } +} + +func cacheMedia(_ result: TransformImageResult, messageId: Int64, arguments: TransformImageArguments, scale: CGFloat, positionFlags: LayoutPositionFlags? = nil) -> Void { + if let image = result.image { + let entry:PhotoCacheKeyEntry = .messageId(stableId: messageId, arguments, scale, positionFlags ?? []) + if !result.highQuality { + photoThumbsCache.cacheImage(image, for: entry) + } else { + photosCache.cacheImage(image, for: entry) + } + } +} + +func cachedThemeThumb(source: ThemeSource, bubbled: Bool) -> Signal { + let entry:PhotoCacheKeyEntry = .theme(source, bubbled) + let value: CGImage? + var full: Bool = false + if let image = themeThums.cachedImage(for: entry) { + value = image + full = true + } else { + value = themeThums.cachedImage(for: entry) + } + if value == nil { + var bp:Int = 0 + bp += 1 + } + return .single(TransformImageResult(value, full)) } +func cacheThemeThumb(_ result: TransformImageResult, source: ThemeSource, bubbled: Bool) -> Void { + let entry:PhotoCacheKeyEntry = .theme(source, bubbled) + + if let image = result.image { + if !result.highQuality { + themeThums.cacheImage(image, for: entry) + } else { + themeThums.cacheImage(image, for: entry) + } + } +} diff --git a/Telegram-Mac/PicturePicker.swift b/Telegram-Mac/PicturePicker.swift index 576332ebd2..8bc088423e 100644 --- a/Telegram-Mac/PicturePicker.swift +++ b/Telegram-Mac/PicturePicker.swift @@ -15,7 +15,15 @@ fileprivate class PickerObserver { @objc fileprivate func validated(_ picker:IKPictureTaker, _ code:Int, _ contextInfo:Any?) { if code == NSApplication.ModalResponse.OK.rawValue { - let image = picker.outputImage() + var image = picker.outputImage() + if let img = image { + let size = img.size.aspectFilled(NSMakeSize(640, 640)) + let resized = generateImage(size, contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.draw(img.precomposed(), in: NSMakeRect(0, 0, size.width, size.height)) + })! + image = NSImage(cgImage: resized, size: size) + } completion(image) } } diff --git a/Telegram-Mac/PlayerListController.swift b/Telegram-Mac/PlayerListController.swift new file mode 100644 index 0000000000..958b5338a3 --- /dev/null +++ b/Telegram-Mac/PlayerListController.swift @@ -0,0 +1,207 @@ +// +// PlayerListController.swift +// Telegram +// +// Created by keepcoder on 26/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class PlayerListArguments { + let chatInteraction: ChatInteraction + init(chatInteraction: ChatInteraction) { + self.chatInteraction = chatInteraction + } +} + +private enum PlayerListEntry: TableItemListNodeEntry { + static func < (lhs: PlayerListEntry, rhs: PlayerListEntry) -> Bool { + return lhs.index < rhs.index + } + + var index: MessageIndex { + switch self { + case let .message(_, message): + return MessageIndex(message) + } + } + + case message(sectionId: Int32, Message) + + var stableId: ChatHistoryEntryId { + switch self { + case let .message(_, message): + return .message(message) + } + } + + func item(_ arguments: PlayerListArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .message(_, message): + return PeerMediaMusicRowItem(initialSize, arguments.chatInteraction, .messageEntry(message, .defaultSettings, .singleItem), isCompactPlayer: true) + } + } + + static func ==(lhs: PlayerListEntry, rhs: PlayerListEntry) -> Bool { + switch lhs { + case let .message(_, lhsMessage): + if case let .message(_, rhsMessage) = rhs { + return isEqualMessages(lhsMessage, rhsMessage) + } else { + return false + } + } + } +} + + +private func playerAudioEntries(_ update: PeerMediaUpdate, timeDifference: TimeInterval) -> [PlayerListEntry] { + var entries: [PlayerListEntry] = [] + var sectionId: Int32 = 0 + + for message in update.messages { + entries.append(.message(sectionId: sectionId, message)) + } + + return entries +} +fileprivate func preparedAudioListTransition(from fromView:[PlayerListEntry], to toView:[PlayerListEntry], initialSize:NSSize, arguments: PlayerListArguments, animated:Bool, scroll:TableScrollState) -> TableUpdateTransition { + let (removed,inserted,updated) = proccessEntries(fromView, right: toView, { (entry) -> TableRowItem in + + return entry.item(arguments, initialSize: initialSize) + + }) + + for item in inserted { + _ = item.1.makeSize(initialSize.width, oldWidth: initialSize.width) + } + for item in updated { + _ = item.1.makeSize(initialSize.width, oldWidth: initialSize.width) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated:updated, animated:animated, state:scroll) +} + + +class PlayerListController: TableViewController { + private let audioPlayer: InlineAudioPlayerView + private let chatInteraction: ChatInteraction + private let disposable = MetaDisposable() + private let messageIndex: MessageIndex + init(audioPlayer: InlineAudioPlayerView, context: AccountContext, messageIndex: MessageIndex) { + self.chatInteraction = ChatInteraction(chatLocation: .peer(messageIndex.id.peerId), context: context) + self.messageIndex = messageIndex + self.audioPlayer = audioPlayer + super.init(context) + + + chatInteraction.inlineAudioPlayer = { [weak self] controller in + self?.audioPlayer.update(with: controller, context: context, tableView: self?.genericView) + } + } + + deinit { + disposable.dispose() + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.getBackgroundColor = { + return theme.colors.background + } + + let location = ValuePromise(ignoreRepeated: true) + + let historyViewUpdate = location.get() |> deliverOnMainQueue + |> mapToSignal { [weak self] location -> Signal<(PeerMediaUpdate, TableScrollState?), NoError> in + + guard let `self` = self else {return .complete()} + + return chatHistoryViewForLocation(location, account: self.chatInteraction.context.account, chatLocation: self.chatInteraction.chatLocation, fixedCombinedReadStates: nil, tagMask: [.music], additionalData: []) |> mapToQueue { view -> Signal<(PeerMediaUpdate, TableScrollState?), NoError> in + switch view { + case .Loading: + return .single((PeerMediaUpdate(), nil)) + case let .HistoryView(view: view, _, scroll, _): + var messages:[Message] = [] + for entry in view.entries { + messages.append(entry.message) + } + var laterId = view.laterId + var earlierId = view.earlierId + + var state: TableScrollState? + if let scroll = scroll { + switch scroll { + case let .index(_, position, _, _): + state = position + default: + break + } + } + return .single((PeerMediaUpdate(messages: messages, updateType: .history, laterId: laterId, earlierId: earlierId), state)) + } + } + } + + let animated: Atomic = Atomic(value: false) + let context = self.chatInteraction.context + let previous:Atomic<[PlayerListEntry]> = Atomic(value: []) + let updateView = Atomic(value: nil) + + + let arguments = PlayerListArguments(chatInteraction: chatInteraction) + + let historyViewTransition = historyViewUpdate |> deliverOnPrepareQueue |> map { update, scroll -> TableUpdateTransition in + let animated = animated.swap(true) + let scroll:TableScrollState = scroll ?? (animated ? .none(nil) : .saveVisible(.upper)) + + let entries = playerAudioEntries(update, timeDifference: context.timeDifference) + _ = updateView.swap(update) + + return preparedAudioListTransition(from: previous.swap(entries), to: entries, initialSize: NSMakeSize(300, 0), arguments: arguments, animated: animated, scroll: scroll) + + } |> deliverOnMainQueue + + + disposable.set(historyViewTransition.start(next: { [weak self] transition in + guard let `self` = self else {return} + self.genericView.merge(with: transition) + if !self.didSetReady, !self.genericView.isEmpty { + self.view.setFrameSize(300, min(self.genericView.listHeight, 325)) + self.genericView.scroll(to: .top(id: PeerMediaSharedEntryStableId.messageId(self.messageIndex.id), innerId: nil, animated: false, focus: .init(focus: false), inset: -25)) + self.readyOnce() + } + })) + + location.set(.Navigation(index: MessageHistoryAnchorIndex.message(messageIndex), anchorIndex: MessageHistoryAnchorIndex.message(messageIndex), count: 50, side: .upper)) + + + genericView.setScrollHandler { scroll in + let view = updateView.modify({$0}) + if let view = view { + var messageIndex:MessageIndex? + switch scroll.direction { + case .bottom: + messageIndex = view.earlierId + case .top: + messageIndex = view.laterId + case .none: + break + } + + if let messageIndex = messageIndex { + let _ = animated.swap(false) + location.set(.Navigation(index: MessageHistoryAnchorIndex.message(messageIndex), anchorIndex: MessageHistoryAnchorIndex.message(messageIndex), count: 50, side: scroll.direction == .bottom ? .lower : .upper)) + } + } + } + } + +} diff --git a/Telegram-Mac/PollResultController.swift b/Telegram-Mac/PollResultController.swift new file mode 100644 index 0000000000..4825941f92 --- /dev/null +++ b/Telegram-Mac/PollResultController.swift @@ -0,0 +1,386 @@ +// +// PollResultController.swift +// Telegram +// +// Created by Mikhail Filimonov on 07.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import Postbox +import SyncCore + + +private struct PollResultState : Equatable { + let results: PollResultsState? + let shouldLoadMore: Data? + let poll: TelegramMediaPoll + let expandedOptions: [Data: Int] + + init(results: PollResultsState?, poll: TelegramMediaPoll, shouldLoadMore: Data?, expandedOptions: [Data: Int]) { + self.results = results + self.poll = poll + self.shouldLoadMore = nil + self.expandedOptions = expandedOptions + } + func withUpdatedResults(_ results: PollResultsState?) -> PollResultState { + return PollResultState(results: results, poll: self.poll, shouldLoadMore: self.shouldLoadMore, expandedOptions: self.expandedOptions) + } + func withUpdatedShouldLoadMore(_ shouldLoadMore: Data?) -> PollResultState { + return PollResultState(results: self.results, poll: self.poll, shouldLoadMore: shouldLoadMore, expandedOptions: self.expandedOptions) + } + func withAddedExpandedOption(_ identifier: Data) -> PollResultState { + var expandedOptions = self.expandedOptions + if let optionState = results?.options[identifier] { + expandedOptions[identifier] = optionState.peers.count + } + + return PollResultState(results: self.results, poll: self.poll, shouldLoadMore: self.shouldLoadMore, expandedOptions: expandedOptions) + } + func withRemovedExpandedOption(_ identifier: Data) -> PollResultState { + var expandedOptions = self.expandedOptions + expandedOptions.removeValue(forKey: identifier) + return PollResultState(results: self.results, poll: self.poll, shouldLoadMore: self.shouldLoadMore, expandedOptions: expandedOptions) + } +} +private func _id_option(_ identifier: Data, _ peerId: PeerId) -> InputDataIdentifier { + return InputDataIdentifier("_id_option_\(identifier.base64EncodedString())_\(peerId.toInt64())") +} +private func _id_load_more(_ identifier: Data) -> InputDataIdentifier { + return InputDataIdentifier("_id_load_more_\(identifier.base64EncodedString())") +} +private func _id_loading_for(_ identifier: Data) -> InputDataIdentifier { + return InputDataIdentifier("_id_loading_for_\(identifier.base64EncodedString())") +} +private func _id_option_header(_ identifier: Data) -> InputDataIdentifier { + return InputDataIdentifier("_id_option_header_\(identifier.base64EncodedString())") +} +private func _id_option_empty(_ index: Int) -> InputDataIdentifier { + return InputDataIdentifier("_id_option_empty_\(index)") +} + +private let collapsedResultCount: Int = 10 +private let collapsedInitialLimit: Int = 14 + + +private let _id_loading = InputDataIdentifier("_id_loading") + +private func pollResultEntries(_ state: PollResultState, context: AccountContext, openProfile:@escaping(PeerId)->Void, expandOption: @escaping(Data)->Void, collapseOption: @escaping(Data)->Void) -> [InputDataEntry] { + var sectionId: Int32 = 0 + var index: Int32 = 0 + + var entries:[InputDataEntry] = [] + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(state.poll.text), data: InputDataGeneralTextData(color: theme.colors.text, detectBold: true, viewType: .modern(position: .inner, insets: NSEdgeInsetsMake(0, 16, 0, 16)), fontSize: .huge))) + index += 1 + + + + let poll = state.poll + + var votes:[Int] = [] + + + + for option in poll.options { + let count = Int(poll.results.voters?.first(where: {$0.opaqueIdentifier == option.opaqueIdentifier})?.count ?? 0) + votes.append(count) + } + + let percents = countNicePercent(votes: votes, total: Int(poll.results.totalVoters ?? 0)) + + struct Option : Equatable { + let option: TelegramMediaPollOption + let percent: Int + let voters:PollResultsOptionState? + let votesCount: Int + } + + + var options:[Option] = [] + for (i, option) in poll.options.enumerated() { + if let voters = state.results?.options[option.opaqueIdentifier], !voters.peers.isEmpty { + let votesCount = Int(poll.results.voters?.first(where: {$0.opaqueIdentifier == option.opaqueIdentifier})?.count ?? 0) + options.append(Option(option: option, percent: percents[i], voters: voters, votesCount: votesCount)) + } else { + let votesCount = Int(poll.results.voters?.first(where: {$0.opaqueIdentifier == option.opaqueIdentifier})?.count ?? 0) + options.append(Option(option: option, percent: percents[i], voters: nil, votesCount: votesCount)) + } + } + + + var isEmpty = false + if let resultsState = state.results { + for (_, optionState) in resultsState.options { + if !optionState.hasLoadedOnce { + isEmpty = true + break + } + } + } + + + for option in options { + if option.votesCount > 0 { + if option == options.first { + entries.append(.sectionId(sectionId, type: .customModern(16))) + sectionId += 1 + } else { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + } + + let text = option.option.text + let additionText:String = " — \(option.percent)%" + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option_header(option.option.opaqueIdentifier), equatable: InputDataEquatable(state), item: { initialSize, stableId in + + let collapse:(()->Void)? + if state.expandedOptions[option.option.opaqueIdentifier] != nil { + collapse = { + collapseOption(option.option.opaqueIdentifier) + } + } else { + collapse = nil + } + + return PollResultStickItem(initialSize, stableId: stableId, left: text, additionText: additionText, right: poll.isQuiz ? L10n.chatQuizTotalVotesCountable(option.votesCount) : L10n.chatPollTotalVotes1Countable(option.votesCount), collapse: collapse, viewType: .textTopItem) + + })) + index += 1 + + if let optionState = option.voters { + + let optionExpandedAtCount = state.expandedOptions[option.option.opaqueIdentifier] + + var peers = optionState.peers + let count = optionState.count + + let displayCount: Int + if peers.count > collapsedInitialLimit + 1 { + if optionExpandedAtCount != nil { + displayCount = peers.count + } else { + displayCount = collapsedResultCount + } + } else { + if let optionExpandedAtCount = optionExpandedAtCount { + if optionExpandedAtCount == collapsedInitialLimit + 1 && optionState.canLoadMore { + displayCount = collapsedResultCount + } else { + displayCount = peers.count + } + } else { + if !optionState.canLoadMore { + displayCount = peers.count + } else { + displayCount = collapsedResultCount + } + } + } + + peers = Array(peers.prefix(displayCount)) + + for (i, voter) in peers.enumerated() { + if let peer = voter.peer { + var viewType = bestGeneralViewType(peers, for: i) + if i == peers.count - 1, optionState.canLoadMore { + if peers.count == 1 { + viewType = .firstItem + } else { + viewType = .innerItem + } + } + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option(option.option.opaqueIdentifier, peer.id), equatable: InputDataEquatable(option), item: { initialSize, stableId in + return ShortPeerRowItem(initialSize, peer: peer, account: context.account, stableId: stableId, height: 46, photoSize: NSMakeSize(32, 32), inset: NSEdgeInsets(left: 30, right: 30), generalType: .none, viewType: viewType, action: { + openProfile(peer.id) + }) + })) + index += 1 + } + } + + let remainingCount = count - peers.count + + + + if remainingCount > 0 { + if optionState.isLoadingMore && state.expandedOptions[option.option.opaqueIdentifier] != nil { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_loading_for(option.option.opaqueIdentifier), equatable: InputDataEquatable(option), item: { initialSize, stableId in + return LoadingTableItem(initialSize, height: 41, stableId: stableId, viewType: .lastItem) + })) + index += 1 + } else { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_load_more(option.option.opaqueIdentifier), equatable: InputDataEquatable(option), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.pollResultsLoadMoreCountable(remainingCount), nameStyle: blueActionButton, type: .none, viewType: .lastItem, action: { + expandOption(option.option.opaqueIdentifier) + }, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUp, textInset: 52, thumbInset: 4)) + })) + index += 1 + } + } + } else { + let displayCount: Int + let voterCount = option.votesCount + if voterCount > collapsedInitialLimit { + displayCount = collapsedResultCount + } else { + displayCount = voterCount + } + let remainingCount: Int? + if displayCount < voterCount { + remainingCount = voterCount - displayCount + } else { + remainingCount = nil + } + + var display:[Int] = [] + for peerIndex in 0 ..< displayCount { + display.append(peerIndex) + } + + for peerIndex in display { + var viewType = bestGeneralViewType(display, for: peerIndex) + if peerIndex == displayCount - 1, remainingCount != nil { + if displayCount == 1 { + viewType = .firstItem + } else { + viewType = .innerItem + } + } + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_option_empty(Int(index)), equatable: nil, item: { initialSize, stableId in + return PeerEmptyHolderItem(initialSize, stableId: stableId, height: 46, photoSize: NSMakeSize(32, 32), viewType: viewType) + })) + index += 1 + } + if let remainingCount = remainingCount { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_load_more(option.option.opaqueIdentifier), equatable: InputDataEquatable(option), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.pollResultsLoadMoreCountable(remainingCount), nameStyle: blueActionButton, type: .none, viewType: .lastItem, thumb: GeneralThumbAdditional(thumb: theme.icons.chatSearchUpDisabled, textInset: 52, thumbInset: 4), enabled: false) + })) + index += 1 + } + } + } + + + } + + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + +func PollResultController(context: AccountContext, message: Message, scrollToOption: Data? = nil) -> InputDataModalController { + + let poll = message.media[0] as! TelegramMediaPoll + + var scrollToOption = scrollToOption + + let resultsContext: PollResultsContext = PollResultsContext(account: context.account, messageId: message.id, poll: poll) + + let initialState = PollResultState(results: nil, poll: poll, shouldLoadMore: nil, expandedOptions: [:]) + + let disposable = MetaDisposable() + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((PollResultState) -> PollResultState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + disposable.set(resultsContext.state.start(next: { results in + updateState { + $0.withUpdatedResults(results) + } + })) + + var openProfile:((PeerId)->Void)? = nil + + let signal = statePromise.get() |> map { + pollResultEntries($0, context: context, openProfile: { peerId in + openProfile?(peerId) + }, expandOption: { identifier in + updateState { + $0.withAddedExpandedOption(identifier) + } + resultsContext.loadMore(optionOpaqueIdentifier: identifier) + }, collapseOption: { identifier in + updateState { + $0.withRemovedExpandedOption(identifier) + } + }) + } |> map { + InputDataSignalValue(entries: $0, animated: true) + } + + let controller = InputDataController(dataSignal: signal, title: !poll.isQuiz ? L10n.pollResultsTitlePoll : L10n.pollResultsTitleQuiz) + + controller.getBackgroundColor = { + theme.colors.background + } + + controller.contextOject = resultsContext + + let modalController = InputDataModalController(controller) + + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { [weak modalController] in + modalController?.close() + }) + + controller.centerModalHeader = ModalHeaderData(title: controller.defaultBarTitle, subtitle: poll.isQuiz ? L10n.chatQuizTotalVotesCountable(Int(poll.results.totalVoters ?? 0)) : L10n.chatPollTotalVotes1Countable(Int(poll.results.totalVoters ?? 0))) + + controller.getBackgroundColor = { + theme.colors.listBackground + } + + + + openProfile = { [weak modalController] peerId in + context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: context, peerId: peerId)) + modalController?.close() + } + controller.afterTransaction = { controller in + if let scroll = scrollToOption { + let item = controller.tableView.item(stableId: InputDataEntryId.custom(_id_option_header(scroll))) + + if let item = item { + controller.tableView.scroll(to: .top(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: -10)) + scrollToOption = nil + } + } + } + + controller.didLoaded = { controller, _ in + controller.tableView.set(stickClass: PollResultStickItem.self, handler: { _ in + + }) + } + +// controller.didLoaded = { controller, _ in +// controller.tableView.setScrollHandler { position in +// switch position.direction { +// case .bottom: +// let shouldLoadMore = stateValue.with { $0.shouldLoadMore } +// if let shouldLoadMore = shouldLoadMore { +// resultsContext.loadMore(optionOpaqueIdentifier: shouldLoadMore) +// } +// break +// default: +// break +// } +// } +// } + + + + return modalController +} diff --git a/Telegram-Mac/PollResultStickItem.swift b/Telegram-Mac/PollResultStickItem.swift new file mode 100644 index 0000000000..913a153b50 --- /dev/null +++ b/Telegram-Mac/PollResultStickItem.swift @@ -0,0 +1,168 @@ +// +// PollResultStickItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/01/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class PollResultStickItem: TableStickItem { + + let leftLayout:TextViewLayout + let leftAdditionLayout: TextViewLayout + let rightLayout: TextViewLayout + let viewType: GeneralViewType + let inset: NSEdgeInsets + let collapse: (()->Void)? + let _stableId: AnyHashable + init(_ initialSize:NSSize, stableId: AnyHashable, left: String, additionText: String, right: String, collapse: (()->Void)?, viewType: GeneralViewType) { + self.viewType = viewType + self._stableId = stableId + self.inset = NSEdgeInsets(left: 30, right: 30) + self.collapse = collapse + self.leftLayout = TextViewLayout(.initialize(string: left, color: theme.colors.listGrayText, font: .normal(11.5)), maximumNumberOfLines: 1, truncationType: .end) + self.leftAdditionLayout = TextViewLayout(.initialize(string: additionText, color: theme.colors.listGrayText, font: .normal(11.5)), maximumNumberOfLines: 1, truncationType: .end) + + if let collapse = collapse { + let attrs = parseMarkdownIntoAttributedString(L10n.pollResultsCollapse, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(11.5), textColor: theme.colors.listGrayText), bold: MarkdownAttributeSet(font: .bold(11.5), textColor: theme.colors.listGrayText), link: MarkdownAttributeSet(font: .normal(11.5), textColor: theme.colors.link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { _ in })) + })) + + self.rightLayout = TextViewLayout(attrs, maximumNumberOfLines: 1, truncationType: .end, alignment: .center) + self.rightLayout.interactions = TextViewInteractions(processURL: { _ in + collapse() + }) + } else { + self.rightLayout = TextViewLayout(.initialize(string: right, color: theme.colors.listGrayText, font: .normal(11.5)), maximumNumberOfLines: 1, truncationType: .end, alignment: .center) + } + + + + super.init(initialSize) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + + + override var canBeAnchor: Bool { + return false + } + + required init(_ initialSize: NSSize) { + self.viewType = .legacy + self.leftLayout = TextViewLayout(NSAttributedString()) + self.rightLayout = TextViewLayout(NSAttributedString()) + self.leftAdditionLayout = TextViewLayout(NSAttributedString()) + self.inset = NSEdgeInsets(left: 30, right: 30) + self.collapse = nil + self._stableId = arc4random() + super.init(initialSize) + } + + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + rightLayout.measure(width: .greatestFiniteMagnitude) + leftAdditionLayout.measure(width: .greatestFiniteMagnitude) + + let blockWidth = min(600, width - inset.left - inset.right) + leftLayout.measure(width: blockWidth - rightLayout.layoutSize.width - viewType.innerInset.left * 3 - leftAdditionLayout.layoutSize.width) + + return success + } + + override var stableId: AnyHashable { + return self._stableId + } + + override var height: CGFloat { + return 30 + } + + override func viewClass() -> AnyClass { + return PollResultStickView.self + } + +} + + +private final class PollResultStickView : TableStickView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let textView = TextView() + private let textAdditionView = TextView() + + private let rightView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.containerView) + containerView.addSubview(self.textView) + containerView.addSubview(self.rightView) + containerView.addSubview(self.textAdditionView) + self.textView.disableBackgroundDrawing = true + self.textView.isSelectable = false + self.textView.userInteractionEnabled = false + + self.textAdditionView.disableBackgroundDrawing = true + self.textAdditionView.isSelectable = false + self.textAdditionView.userInteractionEnabled = false + + self.rightView.disableBackgroundDrawing = true + self.rightView.isSelectable = false + + } + + override var header: Bool { + didSet { + updateColors() + } + } + + + override var backdorColor: NSColor { + return theme.colors.listBackground + } + + override func updateColors() { + guard let item = item as? PollResultStickItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + } + + override func layout() { + super.layout() + guard let item = item as? PollResultStickItem else { + return + } + + let blockWidth = min(600, frame.width - item.inset.left - item.inset.right) + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - blockWidth) / 2), item.inset.top, blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners([]) + + textView.centerY(x: item.viewType.innerInset.left) + textAdditionView.centerY(x: textView.frame.maxX) + rightView.centerY(x: self.containerView.frame.width - item.viewType.innerInset.left - rightView.frame.width) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? PollResultStickItem else { + return + } + self.textView.update(item.leftLayout) + self.textAdditionView.update(item.leftAdditionLayout) + self.rightView.update(item.rightLayout) + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PollTimerView.swift b/Telegram-Mac/PollTimerView.swift new file mode 100644 index 0000000000..56fc854a4c --- /dev/null +++ b/Telegram-Mac/PollTimerView.swift @@ -0,0 +1,273 @@ +// +// PollTimerView.swift +// Telegram +// +// Created by Mikhail Filimonov on 09/04/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + +private func textForTimeout(value: Int) -> String { + //TODO: localize + if value > 60 * 60 { + let hours = value / (60 * 60) + return "\(hours)h" + } else { + let minutes = value / 60 + let seconds = value % 60 + let minutesPadding = minutes < 10 ? "0" : "" + let secondsPadding = seconds < 10 ? "0" : "" + return "\(minutesPadding)\(minutes):\(secondsPadding)\(seconds)" + } +} + +private enum ContentState: Equatable { + case clock(NSColor) + case timeout(NSColor, CGFloat) +} + +private struct ContentParticle { + var position: CGPoint + var direction: CGPoint + var velocity: CGFloat + var alpha: CGFloat + var lifetime: Double + var beginTime: Double + + init(position: CGPoint, direction: CGPoint, velocity: CGFloat, alpha: CGFloat, lifetime: Double, beginTime: Double) { + self.position = position + self.direction = direction + self.velocity = velocity + self.alpha = alpha + self.lifetime = lifetime + self.beginTime = beginTime + } +} + +final class PollBubbleTimerView: View { + private struct Params: Equatable { + var regularColor: NSColor + var proximityColor: NSColor + var timeout: Int32 + var deadlineTimestamp: Int32? + } + + private var animator: ConstantDisplayLinkAnimator? + private let textView: TextView = TextView() + private let contentView: ImageView = ImageView() + private var currentContentState: ContentState? + private var particles: [ContentParticle] = [] + + private var currentParams: Params? + + var reachedTimeout: (() -> Void)? + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + self.addSubview(self.textView) + self.addSubview(self.contentView) + textView.userInteractionEnabled = false + textView.isSelectable = false + textView.isEventLess = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.animator?.invalidate() + } + + func update(regularColor: NSColor, proximityColor: NSColor, timeout: Int32, deadlineTimestamp: Int32?) { + let params = Params( + regularColor: regularColor, + proximityColor: proximityColor, + timeout: timeout, + deadlineTimestamp: deadlineTimestamp + ) + self.currentParams = params + + self.updateValues() + } + + override func viewWillMove(toWindow newWindow: NSWindow?) { + super.viewWillMove(toWindow: newWindow) + self.animator?.isPaused = newWindow == nil + } + + private func updateValues() { + guard let params = self.currentParams else { + return + } + + let fractionalTimeout: Double + + if let deadlineTimestamp = params.deadlineTimestamp { + let fractionalTimestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + fractionalTimeout = min(Double(params.timeout), max(0.0, Double(deadlineTimestamp) + 1.0 - fractionalTimestamp)) + } else { + fractionalTimeout = Double(params.timeout) + } + + let timeout = Int(round(fractionalTimeout)) + + let proximityInterval: Double = 5.0 + let timerInterval: Double = 60.0 + + let isProximity = timeout <= Int(proximityInterval) + let isTimer = timeout <= Int(timerInterval) + + let color = isProximity ? params.proximityColor : params.regularColor + + let attributed = NSAttributedString.initialize(string: textForTimeout(value: timeout), color: color, font: .normal(.short)) + + let textLayout = TextViewLayout(attributed) + textLayout.measure(width: 100) + + + // self.textView.attributedText = NSAttributedString(string: textForTimeout(value: timeout), font: Font.regular(14.0), textColor: color) + let textSize = textLayout.layoutSize + + self.textView.update(textLayout) + + let contentState: ContentState + if isTimer { + var fraction: CGFloat = 1.0 + if fractionalTimeout <= timerInterval { + fraction = CGFloat(fractionalTimeout) / min(CGFloat(timerInterval), CGFloat(params.timeout)) + } + fraction = max(0.0, min(0.99, fraction)) + contentState = .timeout(color, 1.0 - fraction) + } else { + contentState = .clock(color) + } + + if self.currentContentState != contentState { + self.currentContentState = contentState + let image: CGImage? + + let diameter: CGFloat = 14.0 + let inset: CGFloat = 8.0 + let lineWidth: CGFloat = 1.2 + + switch contentState { + case let .clock(color): + image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(color.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + + let clockFrame = CGRect(origin: CGPoint(x: (size.width - diameter) / 2.0, y: (size.height - diameter) / 2.0), size: CGSize(width: diameter, height: diameter)) + context.strokeEllipse(in: clockFrame.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) + + context.move(to: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + context.addLine(to: CGPoint(x: size.width / 2.0, y: clockFrame.minY + 4.0)) + context.strokePath() + + let topWidth: CGFloat = 4.0 + context.move(to: CGPoint(x: size.width / 2.0 - topWidth / 2.0, y: clockFrame.minY - 2.0)) + context.addLine(to: CGPoint(x: size.width / 2.0 + topWidth / 2.0, y: clockFrame.minY - 2.0)) + context.strokePath() + }) + case let .timeout(color, fraction): + let timestamp = CACurrentMediaTime() + + let center = CGPoint(x: (diameter + inset) / 2.0, y: (diameter + inset) / 2.0) + let radius: CGFloat = (diameter - lineWidth / 2.0) / 2.0 + + let startAngle: CGFloat = -CGFloat.pi / 2.0 + let endAngle: CGFloat = -CGFloat.pi / 2.0 + 2.0 * CGFloat.pi * fraction + + let v = CGPoint(x: sin(endAngle), y: -cos(endAngle)) + let c = CGPoint(x: -v.y * radius + center.x, y: v.x * radius + center.y) + + let dt: CGFloat = 1.0 / 60.0 + var removeIndices: [Int] = [] + for i in 0 ..< self.particles.count { + let currentTime = timestamp - self.particles[i].beginTime + if currentTime > self.particles[i].lifetime { + removeIndices.append(i) + } else { + let input: CGFloat = CGFloat(currentTime / self.particles[i].lifetime) + let decelerated: CGFloat = (1.0 - (1.0 - input) * (1.0 - input)) + self.particles[i].alpha = 1.0 - decelerated + + var p = self.particles[i].position + let d = self.particles[i].direction + let v = self.particles[i].velocity + p = CGPoint(x: p.x + d.x * v * dt, y: p.y + d.y * v * dt) + self.particles[i].position = p + } + } + + for i in removeIndices.reversed() { + self.particles.remove(at: i) + } + + let newParticleCount = 1 + for _ in 0 ..< newParticleCount { + let degrees: CGFloat = CGFloat(arc4random_uniform(140)) - 40.0 + let angle: CGFloat = degrees * CGFloat.pi / 180.0 + + let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle)) + let velocity = (20.0 + (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * 4.0) * 0.3 + + let lifetime = Double(0.4 + CGFloat(arc4random_uniform(100)) * 0.01) + + let particle = ContentParticle(position: c, direction: direction, velocity: velocity, alpha: 1.0, lifetime: lifetime, beginTime: timestamp) + self.particles.append(particle) + } + + image = generateImage(CGSize(width: diameter + inset, height: diameter + inset), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(color.cgColor) + context.setFillColor(color.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + + let path = CGMutablePath() + path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) + context.addPath(path) + context.strokePath() + + for particle in self.particles { + let size: CGFloat = 1.15 + context.setAlpha(particle.alpha) + context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size))) + } + }) + } + + self.contentView.image = image + self.contentView.sizeToFit() + self.contentView.centerY(x: frame.width - contentView.frame.width) + + self.textView.centerY(x: frame.width - contentView.frame.width - textSize.width - 4) + } + + if let reachedTimeout = self.reachedTimeout, fractionalTimeout <= .ulpOfOne { + reachedTimeout() + } + + if fractionalTimeout <= .ulpOfOne { + self.animator?.invalidate() + self.animator = nil + } else { + if self.animator == nil { + let animator = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.updateValues() + }) + animator.isPaused = self.window != nil + self.animator = animator +// animator.isPaused = self.inHierarchyValue + } + } + } +} diff --git a/Telegram-Mac/PopularPeersRowItem.swift b/Telegram-Mac/PopularPeersRowItem.swift new file mode 100644 index 0000000000..5bc07e81d4 --- /dev/null +++ b/Telegram-Mac/PopularPeersRowItem.swift @@ -0,0 +1,283 @@ +// +// PopularPeersRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 05/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + +enum PopularItemType : Hashable { + + + var hashValue: Int { + return 0 + } + + static func == (lhs: PopularItemType, rhs: PopularItemType) -> Bool { + switch lhs { + case .savedMessages: + if case .savedMessages = rhs { + return true + } else { + return false + } + case let .articles(unreadCount): + if case .articles(unreadCount) = rhs { + return true + } else { + return false + } + case let .peer(lhsPeer, lhsBadge, lhsActive): + if case let .peer(rhsPeer, rhsBadge, rhsActive) = rhs { + return lhsPeer.isEqual(rhsPeer) && lhsBadge == rhsBadge && lhsActive == rhsActive + } else { + return false + } + } + } + + case savedMessages(Peer) + case articles(Int32) + case peer(Peer, UnreadSearchBadge?, Bool) + + + +} + +private final class PopularPeerItem : TableRowItem { + fileprivate let type: PopularItemType + fileprivate let account: Account + fileprivate let actionHandler: (PopularItemType)->Void + init(type: PopularItemType, account: Account, action: @escaping(PopularItemType)->Void) { + self.type = type + self.account = account + self.actionHandler = action + super.init(NSZeroSize) + } + + override var height: CGFloat { + return 66 + } + + override var width: CGFloat { + return 74 + } + + override var stableId: AnyHashable { + return type + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + switch type { + case let .peer(peer, _, _): + items.append(ContextMenuItem(L10n.searchPopularDelete, handler: { [weak self] in + guard let `self` = self else {return} + // self.table?.remove(at: self.index, redraw: true, animation: .effectFade) + _ = removeRecentPeer(account: self.account, peerId: peer.id).start() + + })) + default: + break + } + + + return .single(items) + } + + override func viewClass() -> AnyClass { + return PopularPeerItemView.self + } +} + + +private final class PopularPeerItemView : HorizontalRowView { + private let imageView: AvatarControl = AvatarControl(font: .avatar(18)) + private let textView: TextView = TextView() + private let badgeView: View = View() + private let activeImage: ImageView = ImageView() + private var badgeNode: BadgeNode? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + imageView.setFrameSize(45, 45) + addSubview(imageView) + addSubview(textView) + addSubview(activeImage) + activeImage.isEventLess = true + textView.isSelectable = false + textView.userInteractionEnabled = false + badgeView.userInteractionEnabled = false + badgeView.isEventLess = true + imageView.set(handler: { [weak self] _ in + guard let item = self?.item as? PopularPeerItem else {return} + item.actionHandler(item.type) + }, for: .Click) + + + } +// +// override var backdorColor: NSColor { +// return .random +// } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? PopularPeerItem else {return} + badgeView.removeFromSuperview() + activeImage.isHidden = true + badgeNode = nil + let text: String + switch item.type { + case .savedMessages: + let icon = theme.icons.searchSaved + imageView.setSignal(generateEmptyPhoto(imageView.frame.size, type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(imageView.frame.size.width - 20, imageView.frame.size.height - 20)), cornerRadius: nil)) |> map {($0, false)}) + text = L10n.searchPopularSavedMessages + case let .articles(unreadCount): + let icon = theme.icons.searchArticle + imageView.setSignal(generateEmptyPhoto(imageView.frame.size, type: .icon(colors: theme.colors.peerColors(4), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(imageView.frame.size.width - 20, imageView.frame.size.height - 20)), cornerRadius: nil)) |> map {($0, false)}) + text = L10n.searchPopularArticles + if unreadCount > 0 { + let node = BadgeNode(NSAttributedString.initialize(string: "\(unreadCount)", color: .white, font: .medium(11)), theme.chatList.badgeBackgroundColor) + node.view = badgeView + self.badgeNode = node + badgeView.setFrameSize(node.size) + addSubview(badgeView) + } else { + badgeView.removeFromSuperview() + } + case let .peer(peer, unreadBadge, isActive): + imageView.setPeer(account: item.account, peer: peer) + text = peer.compactDisplayTitle + + activeImage.isHidden = !isActive + activeImage.image = theme.icons.hintPeerActive + activeImage.sizeToFit() + + if let unreadBadge = unreadBadge { + let isMuted: Bool + let count: Int32? + switch unreadBadge { + case let .muted(c): + isMuted = true + count = c + case let .unmuted(c): + isMuted = false + count = c + case .none: + isMuted = true + count = nil + } + if let unreadCount = count { + let node = BadgeNode(.initialize(string: "\(unreadCount)", color: .white, font: .medium(11)), isMuted ? theme.chatList.badgeMutedBackgroundColor : theme.chatList.badgeBackgroundColor) + node.view = badgeView + self.badgeNode = node + badgeView.setFrameSize(node.size) + addSubview(badgeView) + } else { + badgeView.removeFromSuperview() + } + } else { + badgeView.removeFromSuperview() + } + } + let layout = TextViewLayout(.initialize(string: text, color: theme.colors.text, font: .normal(11)), maximumNumberOfLines: 1) + layout.measure(width: frame.width - 10) + textView.update(layout) + + self.needsLayout = true + } + + override func layout() { + super.layout() + imageView.centerX(addition: -4) + textView.centerX(y: imageView.frame.maxY + 5, addition: -4) + badgeView.setFrameOrigin(imageView.frame.maxX - badgeView.frame.width / 2, 0) + activeImage.setFrameOrigin(imageView.frame.maxX - activeImage.frame.width - 1, imageView.frame.maxY - 12) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class PopularPeersRowItem: GeneralRowItem { + + let peers: [Peer] + fileprivate let account: Account + fileprivate let unreadArticles: Int32 + fileprivate let selfPeer: Peer + fileprivate let actionHandler: (PopularItemType)->Void + fileprivate let articlesEnabled: Bool + fileprivate let unread: [PeerId : UnreadSearchBadge] + fileprivate let online: [PeerId: Bool] + init(_ initialSize: NSSize, stableId: AnyHashable, account: Account, selfPeer: Peer, articlesEnabled: Bool, unreadArticles: Int32, peers:[Peer], unread: [PeerId : UnreadSearchBadge], online: [PeerId: Bool], action: @escaping(PopularItemType)->Void) { + self.peers = peers + self.account = account + self.unread = unread + self.online = online + self.articlesEnabled = articlesEnabled + self.selfPeer = selfPeer + self.actionHandler = action + self.unreadArticles = unreadArticles + super.init(initialSize, height: 74, stableId: stableId) + } + + override func viewClass() -> AnyClass { + return PopularPeersRowView.self + } + +} + + +private final class PopularPeersRowView : TableRowView { + + + + private let tableView: HorizontalTableView + private let separator: View = View() + required init(frame frameRect: NSRect) { + tableView = HorizontalTableView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + addSubview(tableView) + addSubview(separator) + } + + override func layout() { + super.layout() + tableView.frame = bounds + separator.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + + tableView.beginTableUpdates() + tableView.removeAll(animation: .effectFade) + + guard let item = item as? PopularPeersRowItem else {return} + _ = tableView.addItem(item: PopularPeerItem(type: .savedMessages(item.selfPeer), account: item.account, action: item.actionHandler)) + if item.articlesEnabled { + _ = tableView.addItem(item: PopularPeerItem(type: .articles(item.unreadArticles), account: item.account, action: item.actionHandler)) + } + + for peer in item.peers { + _ = tableView.addItem(item: PopularPeerItem(type: .peer(peer, item.unread[peer.id], item.online[peer.id] ?? false), account: item.account, action: item.actionHandler)) + } + + tableView.endTableUpdates() + separator.backgroundColor = theme.colors.border + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/PreHistoryControllerStructures.swift b/Telegram-Mac/PreHistoryControllerStructures.swift index dc02ca5a5e..e416e39bc1 100644 --- a/Telegram-Mac/PreHistoryControllerStructures.swift +++ b/Telegram-Mac/PreHistoryControllerStructures.swift @@ -8,136 +8,5 @@ import Cocoa import TGUIKit -import TelegramCoreMac - -final class PreHistoryArguments { - fileprivate let account: Account - fileprivate let preHistory:(Bool)->Void - init(account:Account, preHistory:@escaping(Bool)->Void) { - self.account = account - self.preHistory = preHistory - } -} - -enum PreHistoryEntryId : Hashable { - case type(Int32) - case text(Int32) - case section(Int32) - var hashValue: Int { - switch self { - case .type(let index): - return Int(index) - case .text(let index): - return Int(index) - case .section(let index): - return Int(index) - } - } -} - -func ==(lhs: PreHistoryEntryId, rhs: PreHistoryEntryId) -> Bool { - switch lhs { - case .type(let index): - if case .type(index) = rhs { - return true - } else { - return false - } - case .text(let index): - if case .text(index) = rhs { - return true - } else { - return false - } - case .section(let index): - if case .section(index) = rhs { - return true - } else { - return false - } - } -} - -enum PreHistoryEntry : TableItemListNodeEntry { - case section(Int32) - case type(sectionId:Int32, index: Int32, text: String, enabled: Bool, selected: Bool) - case text(sectionId:Int32, index: Int32, text: String) - - var stableId: PreHistoryEntryId { - switch self { - case .type(_, let index, _, _, _): - return .type(index) - case .text(_, let index, _): - return .text(index) - case .section(let index): - return .section(index) - } - } - - var index:Int32 { - switch self { - case let .type(sectionId, index, _, _, _): - return (sectionId * 1000) + index - case let .text(sectionId, index, _): - return (sectionId * 1000) + index - case let .section(sectionId): - return (sectionId + 1) * 1000 - sectionId - } - } - - func item(_ arguments: PreHistoryArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case let .type(_, _, text, enabled, selected): - return GeneralInteractedRowItem.init(initialSize, stableId: stableId, name: text, type: .selectable(stateback: { () -> Bool in - return selected - }), action: { - arguments.preHistory(enabled) - }) - case let .text(_, _, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - } - } - -} - -func <(lhs: PreHistoryEntry, rhs: PreHistoryEntry) -> Bool { - return lhs.index < rhs.index -} - -func ==(lhs: PreHistoryEntry, rhs: PreHistoryEntry) -> Bool { - switch lhs { - case let .type(section, index, text, enabled, selected: Bool): - if case .type(section, index, text, enabled, selected: Bool) = rhs { - return true - } else { - return false - } - case let .text(section, index, text): - if case .text(section, index, text) = rhs { - return true - } else { - return false - } - case let .section(index): - if case .section(index) = rhs { - return true - } else { - return false - } - } -} - -final class PreHistoryControllerState : Equatable { - let enabled: Bool? - init(enabled:Bool? = nil) { - self.enabled = enabled - } - func withUpdatedEnabled(_ enabled: Bool) -> PreHistoryControllerState { - return PreHistoryControllerState(enabled: enabled) - } -} -func ==(lhs: PreHistoryControllerState, rhs: PreHistoryControllerState) -> Bool { - return lhs.enabled == rhs.enabled -} +import TelegramCore +import SyncCore diff --git a/Telegram-Mac/PreHistorySettingsController.swift b/Telegram-Mac/PreHistorySettingsController.swift index 54891cc130..74d8050375 100644 --- a/Telegram-Mac/PreHistorySettingsController.swift +++ b/Telegram-Mac/PreHistorySettingsController.swift @@ -8,9 +8,104 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + + +private final class PreHistoryArguments { + fileprivate let context: AccountContext + fileprivate let preHistory:(Bool)->Void + init(context: AccountContext, preHistory:@escaping(Bool)->Void) { + self.context = context + self.preHistory = preHistory + } +} + +private enum PreHistoryEntryId : Hashable { + case type(Int32) + case text(Int32) + case section(Int32) + var hashValue: Int { + switch self { + case .type(let index): + return Int(index) + case .text(let index): + return Int(index) + case .section(let index): + return Int(index) + } + } +} + + +private enum PreHistoryEntry : TableItemListNodeEntry { + case section(Int32) + case type(sectionId:Int32, index: Int32, text: String, enabled: Bool, selected: Bool, viewType: GeneralViewType) + case text(sectionId:Int32, index: Int32, text: String, viewType: GeneralViewType) + + var stableId: PreHistoryEntryId { + switch self { + case .type(_, let index, _, _, _, _): + return .type(index) + case .text(_, let index, _, _): + return .text(index) + case .section(let index): + return .section(index) + } + } + + var index:Int32 { + switch self { + case let .type(sectionId, index, _, _, _, _): + return (sectionId * 1000) + index + case let .text(sectionId, index, _, _): + return (sectionId * 1000) + index + case let .section(sectionId): + return (sectionId + 1) * 1000 - sectionId + } + } + + func item(_ arguments: PreHistoryArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .type(_, _, text, enabled, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .selectable(enabled), viewType: viewType, action: { + arguments.preHistory(selected) + }) + case let .text(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + } + } + +} + +private func <(lhs: PreHistoryEntry, rhs: PreHistoryEntry) -> Bool { + return lhs.index < rhs.index +} + + +private struct PreHistoryControllerState : Equatable { + let enabled: Bool? + var applyingSetting: Bool = false + + + init(enabled:Bool? = nil, applyingSetting: Bool = false) { + self.enabled = enabled + self.applyingSetting = applyingSetting + } + func withUpdatedEnabled(_ enabled: Bool) -> PreHistoryControllerState { + return PreHistoryControllerState(enabled: enabled, applyingSetting: self.applyingSetting) + } + func withUpdatedApplyingSetting(_ applyingSetting: Bool) -> PreHistoryControllerState { + return PreHistoryControllerState(enabled: enabled, applyingSetting: self.applyingSetting) + } +} + + fileprivate func prepareEntries(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:PreHistoryArguments) -> TableUpdateTransition { @@ -21,7 +116,7 @@ fileprivate func prepareEntries(left:[AppearanceWrapperEntry], return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } -fileprivate func preHistoryEntries(cachedData: CachedChannelData?, state: PreHistoryControllerState) -> [PreHistoryEntry] { +fileprivate func preHistoryEntries(cachedData: CachedChannelData?, isGrpup: Bool, state: PreHistoryControllerState) -> [PreHistoryEntry] { var entries:[PreHistoryEntry] = [] @@ -31,35 +126,43 @@ fileprivate func preHistoryEntries(cachedData: CachedChannelData?, state: PreHis entries.append(.section(sectionId)) sectionId += 1 - entries.append(.text(sectionId: sectionId, index: index, text: tr(.preHistorySettingsHeader))) + entries.append(.text(sectionId: sectionId, index: index, text: L10n.preHistorySettingsHeader, viewType: .textTopItem)) index += 1 let enabled = state.enabled ?? cachedData?.flags.contains(.preHistoryEnabled) ?? false - entries.append(.type(sectionId: sectionId, index: index, text: tr(.peerInfoPreHistoryVisible), enabled: true, selected: enabled)) + entries.append(.type(sectionId: sectionId, index: index, text: L10n.peerInfoPreHistoryVisible, enabled: enabled, selected: true, viewType: .firstItem)) index += 1 - entries.append(.type(sectionId: sectionId, index: index, text: tr(.peerInfoPreHistoryHidden), enabled: false, selected: !enabled)) + entries.append(.type(sectionId: sectionId, index: index, text: L10n.peerInfoPreHistoryHidden, enabled: !enabled, selected: false, viewType: .lastItem)) index += 1 - entries.append(.text(sectionId: sectionId, index: index, text: enabled ? tr(.preHistorySettingsDescriptionVisible) : tr(.preHistorySettingsDescriptionHidden))) + entries.append(.text(sectionId: sectionId, index: index, text: enabled ? L10n.preHistorySettingsDescriptionVisible : isGrpup ? L10n.preHistorySettingsDescriptionGroupHidden : L10n.preHistorySettingsDescriptionHidden, viewType: .textBottomItem)) index += 1 return entries } -class PreHistorySettingsController: EmptyComposeController { +class PreHistorySettingsController: EmptyComposeController { private let peerId: PeerId private let statePromise = ValuePromise(PreHistoryControllerState(), ignoreRepeated: true) private let stateValue = Atomic(value: PreHistoryControllerState()) private let disposable = MetaDisposable() - init(_ account: Account, peerId:PeerId) { + private let applyDisposable = MetaDisposable() + init(_ context: AccountContext, peerId:PeerId) { self.peerId = peerId - super.init(account) + super.init(context) } override func viewDidLoad() { super.viewDidLoad() + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let context = self.context + let peerId = self.peerId + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let updateState: ((PreHistoryControllerState) -> PreHistoryControllerState) -> Void = { [weak self] f in @@ -69,30 +172,107 @@ class PreHistorySettingsController: EmptyComposeController = combineLatest(account.postbox.combinedView(keys: [key]) |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue, statePromise.get() |> deliverOnPrepareQueue) |> map { view, appearance, state in + + let signal: Signal<(TableUpdateTransition, PreHistoryControllerState, Bool, CachedChannelData?, Peer?), NoError> = combineLatest(queue: prepareQueue, context.account.postbox.peerView(id: peerId), appearanceSignal, statePromise.get()) |> map { peerView, appearance, state in - let cachedData = view.views[key] as? CachedPeerDataView - let entries = preHistoryEntries(cachedData: cachedData?.cachedPeerData as? CachedChannelData, state: state).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let cachedData = peerView.cachedData as? CachedChannelData + let peer = peerViewMainPeer(peerView) + let entries = preHistoryEntries(cachedData: cachedData, isGrpup: peerId.namespace == Namespaces.Peer.CloudGroup, state: state).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let defaultValue: Bool = cachedData?.flags.contains(.preHistoryEnabled) ?? false - return (prepareEntries(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), state) + + return (prepareEntries(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), state, defaultValue, cachedData, peer) } |> deliverOnMainQueue - disposable.set(signal.start(next: { [weak self] transition, state in + disposable.set(signal.start(next: { [weak self] transition, state, defaultValue, cachedData, peer in self?.genericView.merge(with: transition) self?.readyOnce() + + self?.doneButton?.removeAllHandlers() + self?.doneButton?.set(handler: { _ in + var value: Bool? + updateState { state in + value = state.enabled + return state.withUpdatedApplyingSetting(true) + } + if let value = value, value != defaultValue { + if peerId.namespace == Namespaces.Peer.CloudGroup { + let signal = convertGroupToSupergroup(account: context.account, peerId: peerId) + |> map(Optional.init) + |> mapToSignal { upgradedPeerId -> Signal in + guard let upgradedPeerId = upgradedPeerId else { + return .single(nil) + } + return updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: upgradedPeerId, historyAvailableForNewMembers: value) + |> mapError { _ in + return ConvertGroupToSupergroupError.generic + } + |> mapToSignal { _ -> Signal in + return .complete() + } + |> then(.single(upgradedPeerId) |> mapError { ConvertGroupToSupergroupError.generic }) + } + |> deliverOnMainQueue + + _ = showModalProgress(signal: signal, for: context.window).start(next: { [weak self] peerId in + self?.onComplete.set(.single(peerId)) + }, error: { error in + switch error { + case .tooManyChannels: + showInactiveChannels(context: context, source: .upgrade) + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }) + + } else { + let signal: Signal = updateChannelHistoryAvailabilitySettingsInteractively(postbox: context.account.postbox, network: context.account.network, accountStateManager: context.account.stateManager, peerId: peerId, historyAvailableForNewMembers: value) |> deliverOnMainQueue |> `catch` { _ in return .complete() } |> map { _ in return nil } + + if let cachedData = cachedData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = peer as? TelegramChannel { + confirm(for: context.window, information: L10n.preHistoryConfirmUnlink(peer.displayTitle), successHandler: { [weak self] _ in + if peer.adminRights == nil || !peer.hasPermission(.pinMessages) { + alert(for: context.window, info: L10n.channelErrorDontHavePermissions) + } else { + let signal = updateGroupDiscussionForChannel(network: context.account.network, postbox: context.account.postbox, channelId: linkedDiscussionPeerId, groupId: nil) + |> `catch` { _ in return .complete() } + |> map { _ -> PeerId? in return nil } + |> then(signal) + self?.onComplete.set(showModalProgress(signal: signal, for: context.window)) + } + + }) + } else { + self?.onComplete.set(showModalProgress(signal: signal, for: mainWindow)) + } + + } + } else { + self?.onComplete.set(.single(nil)) + } + + }, for: .SingleClick) + })) + } + var doneButton:Control? { + return rightBarView + } + + override func getRightBarViewOnce() -> BarView { + let button = TextButtonBarView(controller: self, text: L10n.navigationDone) + + return button } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - onComplete.set(statePromise.get() |> filter {$0.enabled != nil} |> map {$0.enabled!}) } override var enableBack: Bool { @@ -101,5 +281,6 @@ class PreHistorySettingsController: EmptyComposeController = Promise() + private let queue: Queue = Queue() + init(_ path: String, account: Account, id: Int64) { + self.path = path + self.id = id + account.messageMediaPreuploadManager.add(network: account.network, postbox: account.postbox, id: id, encrypt: false, tag: nil, source: resource.get()) + } + + + func fileDidChangedSize(_ complete: Bool) { + self.queue.async { + if let size = fileSize(self.path), self.previousSize != size || complete { + self.previousSize = size + self.resource.set(.single(MediaResourceData(path: self.path, offset: 0, size: size, complete: complete))) + } + } + } + + +} diff --git a/Telegram-Mac/Preferences.swift b/Telegram-Mac/Preferences.swift new file mode 100644 index 0000000000..432d37b1f0 --- /dev/null +++ b/Telegram-Mac/Preferences.swift @@ -0,0 +1,120 @@ +// +// Preferences.swift +// Muse +// +// Created by Marco Albera on 28/07/2017. +// Copyright © 2017 Edge Apps. All rights reserved. +// + +import Cocoa + +enum PreferenceKey: String { + + case peekToolbarsOnHover = "peekToolbarsOnHover" + + case menuBarTitle = "menuBarTitle" + + case controlStripItem = "controlStripItem" + + case controlStripHUD = "controlStripHUD" + + case actionBar = "actionBar" + + var name: RawValue { + return rawValue + } + + var defaultValue: Any { + return PreferenceKey.defaults[self]! + } + + static let defaults: [PreferenceKey: Any] = [.peekToolbarsOnHover: true, + .menuBarTitle: true, + .controlStripItem: true, + .controlStripHUD: true, + .actionBar: true] + + static func registerDefaults() { + UserDefaults.standard.register(defaults: defaults.userDefaultsCompatible) + } + +} + +protocol Preferenceable { + + associatedtype ValueType + + var key: PreferenceKey { get } + +} + +extension Preferenceable { + + var userDefaults: UserDefaults { + return UserDefaults.standard + } + + var value: ValueType { + return userDefaults.object(for: key) as? ValueType ?? defaultValue + } + + func set(_ value: ValueType) { + userDefaults.set(value, for: key) + } + + var defaultValue: ValueType { + return key.defaultValue as! ValueType + } + +} + +struct Preference: Preferenceable { + + typealias ValueType = T + + var key: PreferenceKey + + init (_ key: PreferenceKey) { + self.key = key + } + +} + +extension Dictionary { + + /** + Initializes a Dictionary from two given sequences by zipping them into one + - parameter sequence1: the first sequence + - parameter sequence2: the second sequence + */ + init(_ sequence1: [Key], _ sequence2: [Value]) { + self.init() + + zip(sequence1, sequence2).forEach { self[$0] = $1 } + } + +} + +extension Dictionary where Key == PreferenceKey { + + /** + UserDefaults needs string keys, so we build a new dicitionary + with PreferenceKey's rawValues + */ + var userDefaultsCompatible: [String: Any] { + return Dictionary(self.keys.map { $0.name }, self.values.map { $0 }) + } + +} + +extension UserDefaults { + + func set(_ value: Any, for preferenceKey: PreferenceKey) { + set(value, forKey: preferenceKey.name) + } + + func object(for preferenceKey: PreferenceKey) -> Any? { + return object(forKey: preferenceKey.name) + } + +} diff --git a/Telegram-Mac/PreparedChatHistoryViewTransition.swift b/Telegram-Mac/PreparedChatHistoryViewTransition.swift deleted file mode 100644 index 73e3749922..0000000000 --- a/Telegram-Mac/PreparedChatHistoryViewTransition.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// PreparedChatHistoryViewTransition.swift -// Telegram -// -// Created by keepcoder on 19/04/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac - -struct ChatHistoryViewTransition { - let historyView: ChatHistoryView - let deleteItems: [ListViewDeleteItem] - let insertEntries: [ChatHistoryViewTransitionInsertEntry] - let updateEntries: [ChatHistoryViewTransitionUpdateEntry] - let options: ListViewDeleteAndInsertOptions - let scrollToItem: ListViewScrollToItem? - let stationaryItemRange: (Int, Int)? - let initialData: InitialMessageHistoryData? - let keyboardButtonsMessage: Message? - let cachedData: CachedPeerData? - let scrolledToIndex: MessageIndex? -} - - -enum ChatHistoryViewGridScrollPosition { - case Unread(index: MessageIndex) - case Index(index: MessageIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) -} - -func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, account: Account, peerId: PeerId, controllerInteraction: ChatInteraction, scrollPosition: ChatHistoryViewGridScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?) -> Signal { - return Signal { subscriber in - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries) - - var adjustedDeleteIndices: [ListViewDeleteItem] = [] - let previousCount: Int - if let fromView = fromView { - previousCount = fromView.filteredEntries.count - } else { - previousCount = 0; - } - for index in deleteIndices { - adjustedDeleteIndices.append(ListViewDeleteItem(index: previousCount - 1 - index, directionHint: nil)) - } - - var adjustedIndicesAndItems: [ChatHistoryViewTransitionInsertEntry] = [] - var adjustedUpdateItems: [ChatHistoryViewTransitionUpdateEntry] = [] - let updatedCount = toView.filteredEntries.count - - var options: ListViewDeleteAndInsertOptions = [] - var maxAnimatedInsertionIndex = -1 - var stationaryItemRange: (Int, Int)? - var scrollToItem: ListViewScrollToItem? - - switch reason { - case let .Initial(fadeIn): - if fadeIn { - let _ = options.insert(.AnimateAlpha) - } else { - let _ = options.insert(.LowLatency) - let _ = options.insert(.Synchronous) - } - case .InteractiveChanges: - let _ = options.insert(.AnimateAlpha) - let _ = options.insert(.AnimateInsertion) - - for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) { - let adjustedIndex = updatedCount - 1 - index - if adjustedIndex == maxAnimatedInsertionIndex + 1 { - maxAnimatedInsertionIndex += 1 - } - } - case .Reload: - break - case let .HoleChanges(filledHoleDirections, removeHoleDirections): - if let (_, removeDirection) = removeHoleDirections.first { - switch removeDirection { - case .LowerToUpper: - var holeIndex: MessageIndex? - for (index, _) in filledHoleDirections { - if holeIndex == nil || index < holeIndex! { - holeIndex = index - } - } - - if let holeIndex = holeIndex { - for i in 0 ..< toView.filteredEntries.count { - if toView.filteredEntries[i].entry.index >= holeIndex { - let index = toView.filteredEntries.count - 1 - (i - 1) - stationaryItemRange = (index, Int.max) - break - } - } - } - case .UpperToLower: - break - case .AroundIndex: - break - } - } - } - - for (index, entry, previousIndex) in indicesAndItems { - let adjustedIndex = updatedCount - 1 - index - - let adjustedPrevousIndex: Int? - if let previousIndex = previousIndex { - adjustedPrevousIndex = previousCount - 1 - previousIndex - } else { - adjustedPrevousIndex = nil - } - - var directionHint: ListViewItemOperationDirectionHint? - if maxAnimatedInsertionIndex >= 0 && adjustedIndex <= maxAnimatedInsertionIndex { - directionHint = .Down - } - - adjustedIndicesAndItems.append(ChatHistoryViewTransitionInsertEntry(index: adjustedIndex, previousIndex: adjustedPrevousIndex, entry: entry.entry, directionHint: directionHint)) - } - - for (index, entry, previousIndex) in updateIndices { - let adjustedIndex = updatedCount - 1 - index - let adjustedPreviousIndex = previousCount - 1 - previousIndex - - let directionHint: ListViewItemOperationDirectionHint? = nil - adjustedUpdateItems.append(ChatHistoryViewTransitionUpdateEntry(index: adjustedIndex, previousIndex: adjustedPreviousIndex, entry: entry.entry, directionHint: directionHint)) - } - - var scrolledToIndex: MessageIndex? - - if let scrollPosition = scrollPosition { - switch scrollPosition { - case let .Unread(unreadIndex): - var index = toView.filteredEntries.count - 1 - for entry in toView.filteredEntries { - if case .UnreadEntry = entry.entry { - scrollToItem = ListViewScrollToItem(index: index, position: .Bottom, animated: false, curve: .Default, directionHint: .Down) - break - } - index -= 1 - } - - if scrollToItem == nil { - var index = toView.filteredEntries.count - 1 - for entry in toView.filteredEntries { - if entry.entry.index >= unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .Bottom, animated: false, curve: .Default, directionHint: .Down) - break - } - index -= 1 - } - } - - if scrollToItem == nil { - var index = 0 - for entry in toView.filteredEntries.reversed() { - if entry.entry.index < unreadIndex { - scrollToItem = ListViewScrollToItem(index: index, position: .Bottom, animated: false, curve: .Default, directionHint: .Down) - break - } - index += 1 - } - } - case let .Index(scrollIndex, position, directionHint, animated): - if case .Center = position { - scrolledToIndex = scrollIndex - } - var index = toView.filteredEntries.count - 1 - for entry in toView.filteredEntries { - if entry.entry.index >= scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default, directionHint: directionHint) - break - } - index -= 1 - } - - if scrollToItem == nil { - var index = 0 - for entry in toView.filteredEntries.reversed() { - if entry.entry.index < scrollIndex { - scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default, directionHint: directionHint) - break - } - index += 1 - } - } - } - } - - subscriber.putNext(ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: cachedData, scrolledToIndex: scrolledToIndex)) - subscriber.putCompletion() - - return EmptyDisposable - } -} diff --git a/Telegram-Mac/PresenceStrings.swift b/Telegram-Mac/PresenceStrings.swift index 250207f3f2..0980e2a98e 100644 --- a/Telegram-Mac/PresenceStrings.swift +++ b/Telegram-Mac/PresenceStrings.swift @@ -7,8 +7,9 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore import TGUIKit func stringForTimestamp(day: Int32, month: Int32, year: Int32) -> String { return String(format: "%d.%02d.%02d", day, month, year - 100) @@ -27,11 +28,11 @@ func stringForUserPresence(day: UserPresenceDay, hours: Int32, minutes: Int32) - let dayString: String switch day { case .today: - dayString = tr(.peerStatusToday) + dayString = tr(L10n.peerStatusToday) case .yesterday: - dayString = tr(.peerStatusYesterday) + dayString = tr(L10n.peerStatusYesterday) } - return tr(.peerStatusLastSeenAt(dayString, stringForTime(hours: hours, minutes: minutes))) + return tr(L10n.peerStatusLastSeenAt(dayString, stringForTime(hours: hours, minutes: minutes))) } enum RelativeUserPresenceLastSeen { @@ -53,18 +54,26 @@ enum RelativeUserPresenceStatus { case lastMonth } -func relativeUserPresenceStatus(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> RelativeUserPresenceStatus { +func relativeUserPresenceStatus(_ presence: TelegramUserPresence, timeDifference: TimeInterval, relativeTo timestamp: Int32) -> RelativeUserPresenceStatus { switch presence.status { case .none: return .offline case let .present(statusTimestamp): + let statusTimestampInt: Int = Int(statusTimestamp) + let statusTimestamp = Int32(min(statusTimestampInt - Int(timeDifference), Int(INT32_MAX))) if statusTimestamp >= timestamp { return .online(at: statusTimestamp) } else { return .lastSeen(at: statusTimestamp) } + case .recently: - return .recently + let activeUntil = presence.lastActivity - Int32(timeDifference) + 30 + if activeUntil >= timestamp { + return .online(at: activeUntil) + } else { + return .recently + } case .lastWeek: return .lastWeek case .lastMonth: @@ -72,21 +81,23 @@ func relativeUserPresenceStatus(_ presence: TelegramUserPresence, relativeTo tim } } -func stringAndActivityForUserPresence(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> (String, Bool, NSColor) { +func stringAndActivityForUserPresence(_ presence: TelegramUserPresence, timeDifference: TimeInterval, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool, NSColor) { switch presence.status { case .none: - return (tr(.peerStatusRecently), false, theme.colors.grayText) + return (L10n.peerStatusLongTimeAgo, false, theme.colors.grayText) case let .present(statusTimestamp): + let statusTimestampInt: Int = Int(statusTimestamp) + let statusTimestamp = Int32(min(statusTimestampInt - Int(timeDifference), Int(INT32_MAX))) if statusTimestamp >= timestamp { - return (tr(.peerStatusOnline), true, theme.colors.blueText) + return (L10n.peerStatusOnline, true, theme.colors.accent) } else { let difference = timestamp - statusTimestamp if difference < 59 { - return (tr(.peerStatusJustNow), false, theme.colors.grayText) - } else if difference < 60 * 60 { + return (tr(L10n.peerStatusJustNow), false, theme.colors.grayText) + } else if difference < 60 * 60 && !expanded { let minutes = max(difference / 60, 1) - return (tr(.peerStatusMinAgoCountable(Int(minutes))), false, theme.colors.grayText) + return (L10n.peerStatusMinAgoCountable(Int(minutes)), false, theme.colors.grayText) } else { var t: time_t = time_t(statusTimestamp) var timeinfo: tm = tm() @@ -97,35 +108,53 @@ func stringAndActivityForUserPresence(_ presence: TelegramUserPresence, relative localtime_r(&now, &timeinfoNow) if timeinfo.tm_year != timeinfoNow.tm_year { - return ("\(tr(.timeLastSeen)) \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false, theme.colors.grayText) + return ("\(L10n.timeLastSeen) \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false, theme.colors.grayText) } let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday if dayDifference == 0 || dayDifference == -1 { let day: UserPresenceDay if dayDifference == 0 { - day = .today + if expanded { + day = .today + } else { + let minutes = difference / (60 * 60) + + return (L10n.lastSeenHoursAgoCountable(Int(minutes)), false, theme.colors.grayText) + } } else { day = .yesterday } return (stringForUserPresence(day: day, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min), false, theme.colors.grayText) } else { - return ("\(tr(.timeLastSeen)) \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false, theme.colors.grayText) + return ("\(L10n.timeLastSeen) \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false, theme.colors.grayText) } } } case .recently: - return (tr(.peerStatusRecently), false, theme.colors.grayText) + let activeUntil = presence.lastActivity - Int32(timeDifference) + 30 + if activeUntil >= timestamp { + return (L10n.peerStatusOnline, true, theme.colors.accent) + } else { + return (L10n.peerStatusRecently, false, theme.colors.grayText) + } case .lastWeek: - return (tr(.peerStatusLastWeek), false, theme.colors.grayText) + return (L10n.peerStatusLastWeek, false, theme.colors.grayText) case .lastMonth: - return (tr(.peerStatusLastMonth), false, theme.colors.grayText) + return (L10n.peerStatusLastMonth, false, theme.colors.grayText) } } -func userPresenceStringRefreshTimeout(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> Double { +func userPresenceStringRefreshTimeout(_ presence: TelegramUserPresence, timeDifference: Int32, relativeTo timestamp: Int32) -> Double { switch presence.status { case let .present(statusTimestamp): + + let statusTimestampInt: Int = Int(statusTimestamp) + let statusTimestamp = Int32(min(statusTimestampInt, Int(INT32_MAX))) + + if statusTimestamp > INT32_MAX - 1 { + return Double.infinity + } if statusTimestamp >= timestamp { return Double(statusTimestamp - timestamp) } else { @@ -138,7 +167,99 @@ func userPresenceStringRefreshTimeout(_ presence: TelegramUserPresence, relative return Double.infinity } } - case .recently, .none, .lastWeek, .lastMonth: + case .recently: + let activeUntil = presence.lastActivity - timeDifference + 30 + if activeUntil >= timestamp { + return Double(activeUntil - timestamp + 1) + } else { + return Double.infinity + } + + case .none, .lastWeek, .lastMonth: return Double.infinity } } + + +func stringForRelativeSymbolicTimestamp(relativeTimestamp: Int32, relativeTo timestamp: Int32) -> String { + var t: time_t = time_t(relativeTimestamp) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + var now: time_t = time_t(timestamp) + var timeinfoNow: tm = tm() + localtime_r(&now, &timeinfoNow) + + let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday + + let hours = timeinfo.tm_hour + let minutes = timeinfo.tm_min + + if dayDifference == 0 { + return L10n.timeTodayAt(stringForShortTimestamp(hours: hours, minutes: minutes)) + } else { + return stringForFullDate(timestamp: relativeTimestamp) + } +} + + + +func stringForShortTimestamp(hours: Int32, minutes: Int32) -> String { + let hourString: String + if hours == 0 { + hourString = "12" + } else if hours > 12 { + hourString = "\(hours - 12)" + } else { + hourString = "\(hours)" + } + + let periodString: String + if hours >= 12 { + periodString = "PM" + } else { + periodString = "AM" + } + if minutes >= 10 { + return "\(hourString):\(minutes) \(periodString)" + } else { + return "\(hourString):0\(minutes) \(periodString)" + } +} + + + +func stringForFullDate(timestamp: Int32) -> String { + var t: time_t = Int(timestamp) + var timeinfo = tm() + localtime_r(&t, &timeinfo); + + switch timeinfo.tm_mon + 1 { + case 1: + return L10n.timePreciseDateM1("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 2: + return L10n.timePreciseDateM2("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 3: + return L10n.timePreciseDateM3("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 4: + return L10n.timePreciseDateM4("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 5: + return L10n.timePreciseDateM5("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 6: + return L10n.timePreciseDateM6("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 7: + return L10n.timePreciseDateM7("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 8: + return L10n.timePreciseDateM8("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 9: + return L10n.timePreciseDateM9("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 10: + return L10n.timePreciseDateM10("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 11: + return L10n.timePreciseDateM11("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + case 12: + return L10n.timePreciseDateM12("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min))) + default: + return "" + } +} diff --git a/Telegram-Mac/PrettyGridUtils.swift b/Telegram-Mac/PrettyGridUtils.swift index 9ed53b5d60..3f62be1034 100644 --- a/Telegram-Mac/PrettyGridUtils.swift +++ b/Telegram-Mac/PrettyGridUtils.swift @@ -7,8 +7,9 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit @@ -25,9 +26,9 @@ let kBotInlineTypeFile:String = "file"; let kBotInlineTypeVoice:String = "voice"; enum InputMediaContextEntry : Equatable { - case gif(thumb:TelegramMediaImage?, file:TelegramMediaResource) + case gif(thumb: ImageMediaReference?, file: FileMediaReference) case photo(image:TelegramMediaImage) - case sticker(thumb: TelegramMediaImage?, file:TelegramMediaFile) + case sticker(thumb: TelegramMediaImage?, file: TelegramMediaFile) } @@ -36,12 +37,12 @@ func ==(lhs:InputMediaContextEntry, rhs:InputMediaContextEntry) -> Bool { switch lhs { case let .gif(lhsData): if case let .gif(rhsData) = rhs { - if !lhsData.file.isEqual(to: rhsData.file) { + if !lhsData.file.media.isEqual(to: rhsData.file.media) { return false } if (lhsData.thumb == nil) != (lhsData.thumb == nil) { return false - } else if let lhsThumb = lhsData.thumb, let rhsThumb = rhsData.thumb, lhsThumb != rhsThumb { + } else if let lhsThumb = lhsData.thumb, let rhsThumb = rhsData.thumb, lhsThumb.media != rhsThumb.media { return false } @@ -83,8 +84,16 @@ func ==(lhs:InputMediaContextEntry, rhs:InputMediaContextEntry) -> Bool { struct InputMediaContextRow :Equatable { let entries:[InputMediaContextEntry] let results:[ChatContextResult] + let messages:[Message] let sizes:[NSSize] + init(entries:[InputMediaContextEntry], results: [ChatContextResult], sizes: [NSSize], messages: [Message] = []) { + self.entries = entries + self.results = results + self.sizes = sizes + self.messages = messages + } + func isFilled(for width:CGFloat) -> Bool { let sum:CGFloat = sizes.reduce(0, { (acc, size) -> CGFloat in return acc + size.width @@ -121,7 +130,7 @@ func fitPrettyDimensions(_ dimensions:[NSSize], isLastRow:Bool, fitToHeight:Bool var row:[NSSize] = [] var idx:Int = 0 for dimension in dimensions { - var fitted = dimension.fitted(NSMakeSize(perSize.width, maxHeight)) + var fitted = dimension.aspectFitted(NSMakeSize(floor(perSize.width / 3), maxHeight)) if fitted.width < maxHeight || fitted.height < maxHeight { let more: CGFloat = max(maxHeight - fitted.width, maxHeight - fitted.height) @@ -129,7 +138,7 @@ func fitPrettyDimensions(_ dimensions:[NSSize], isLastRow:Bool, fitToHeight:Bool fitted.height += more } - if !isLastRow && idx == dimensions.count - 1 && !fitToHeight { + if !isLastRow && idx == dimensions.count - 1 { let width:CGFloat = row.reduce(0, { (acc, size) -> CGFloat in return acc + size.width }) @@ -147,28 +156,35 @@ func fitPrettyDimensions(_ dimensions:[NSSize], isLastRow:Bool, fitToHeight:Bool return row } var rows:[NSSize] = [] + var plus: Int = 0 while true { - rows = sizeup(dimensions) - let width:CGFloat = rows.reduce(0, { (acc, size) -> CGFloat in - return acc + size.width - }) - - if width - perSize.width > 0 && dimensions.count > 1 { - dimensions.removeLast() - continue - } - if (width < perSize.width && !isLastRow) && !dimensions.isEmpty && !fitToHeight { - maxHeight += CGFloat(6 * dimensions.count) + let about = Array(dimensions.prefix(Int(ceil(perSize.width / perSize.height)) + plus)) + if !about.isEmpty { + rows = sizeup(about) + + let width:CGFloat = rows.reduce(0, { (acc, size) -> CGFloat in + return acc + size.width + }) + + if perSize.width < width { + plus -= 1 + continue + } + if (width < perSize.width && !isLastRow) && !fitToHeight { + maxHeight += CGFloat(6 * dimensions.count) + } else { + break + } } else { break } + } - return rows } func makeStickerEntries(_ stickers:[FoundStickerItem], initialSize:NSSize, maxSize:NSSize = NSMakeSize(80, 80)) -> [InputMediaStickersRow] { - let s = floorToScreenPixels(initialSize.width/floor(initialSize.width/maxSize.width)) + let s = floorToScreenPixels(System.backingScale, initialSize.width/floor(initialSize.width/maxSize.width)) let perRow = Int(initialSize.width / s) var entries:[InputMediaContextEntry] = [] @@ -180,7 +196,7 @@ func makeStickerEntries(_ stickers:[FoundStickerItem], initialSize:NSSize, maxSi while !stickers.isEmpty { let sticker = stickers[0] - entries.append(.sticker(thumb: TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: sticker.file.previewRepresentations), file: sticker.file)) + entries.append(.sticker(thumb: TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: sticker.file.previewRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []), file: sticker.file)) sizes.append(NSMakeSize(s, s)) items.append(sticker) if entries.count == perRow { @@ -199,7 +215,7 @@ func makeStickerEntries(_ stickers:[FoundStickerItem], initialSize:NSSize, maxSi return rows } -func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [InputMediaContextRow] { +func makeMediaEnties(_ results:[ChatContextResult], isSavedGifs: Bool, initialSize:NSSize) -> [InputMediaContextRow] { var entries:[InputMediaContextEntry] = [] var rows:[InputMediaContextRow] = [] @@ -214,21 +230,27 @@ func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [Inpu case let .externalReference(data): switch data.type { case kBotInlineTypeGif: - if let dimension = data.dimensions, let contentUrl = data.contentUrl { - var image:TelegramMediaImage? = nil - if let thumbUrl = data.thumbnailUrl { - image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: dimension, resource: HttpReferenceMediaResource(url: thumbUrl, size: nil))]) + if let content = data.content { + var image:ImageMediaReference? = nil + if let thumbnail = data.thumbnail, let dimensions = thumbnail.dimensions { + let tmp = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnail.resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + image = isSavedGifs ? ImageMediaReference.savedGif(media: tmp) : ImageMediaReference.standalone(media: tmp) } - entries.append(.gif(thumb: image, file: HttpReferenceMediaResource(url: contentUrl, size: nil))) + let file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/gif", size: content.resource.size, attributes: [TelegramMediaFileAttribute.Animated]) + entries.append(.gif(thumb: image, file: isSavedGifs ? FileMediaReference.savedGif(media: file) : FileMediaReference.standalone(media: file))) } else { removeResultIndexes.append(i) } case kBotInlineTypePhoto: - let dimension = data.dimensions ?? NSMakeSize(100, 100) var image:TelegramMediaImage? = nil - if let thumbUrl = data.thumbnailUrl { - image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: dimension, resource: HttpReferenceMediaResource(url: thumbUrl, size: nil))]) + if let content = data.content, let dimensions = content.dimensions { + var representations: [TelegramMediaImageRepresentation] = [] + if let thumbnail = data.thumbnail, let dimensions = thumbnail.dimensions { + representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnail.resource)) + } + representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: content.resource)) + image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) } if let image = image { entries.append(.photo(image: image)) @@ -236,12 +258,12 @@ func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [Inpu removeResultIndexes.append(i) } case kBotInlineTypeSticker: - if let dimension = data.dimensions, let contentUrl = data.contentUrl { + if let content = data.content { var image:TelegramMediaImage? = nil - if let thumbUrl = data.thumbnailUrl { - image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: dimension, resource: HttpReferenceMediaResource(url: thumbUrl, size: nil))]) + if let thumbnail = data.thumbnail, let dimensions = thumbnail.dimensions { + image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnail.resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) } - entries.append(.sticker(thumb: image, file: TelegramMediaFile.init(fileId: MediaId(namespace: 0, id: 0), resource: HttpReferenceMediaResource(url: contentUrl, size: nil), previewRepresentations: [], mimeType: "image/webp", size: nil, attributes: []))) + entries.append(.sticker(thumb: image, file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: nil, attributes: content.attributes))) } else { removeResultIndexes.append(i) } @@ -253,14 +275,21 @@ func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [Inpu case kBotInlineTypeGif: if let file = data.file { dimension = file.videoSize - entries.append(.gif(thumb: data.image, file: file.resource)) + var thumb: ImageMediaReference? = nil + if let image = data.image { + thumb = ImageMediaReference.standalone(media: image) + } else if !file.previewRepresentations.isEmpty { + let tmp = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: file.previewRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + thumb = isSavedGifs ? ImageMediaReference.savedGif(media: tmp) : ImageMediaReference.standalone(media: tmp) + } + entries.append(.gif(thumb: thumb, file: isSavedGifs ? FileMediaReference.savedGif(media: file) : FileMediaReference.standalone(media: file))) } else { removeResultIndexes.append(i) } case kBotInlineTypePhoto: if let image = data.image, let representation = image.representations.last { - dimension = representation.dimensions + dimension = representation.dimensions.size entries.append(.photo(image: image)) } else { removeResultIndexes.append(i) @@ -284,13 +313,28 @@ func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [Inpu for i in removeResultIndexes.reversed() { results.remove(at: i) } - var fitted:[[NSSize]] = [] let f:Int = Int(round(initialSize.width / initialSize.height)) + + let rowCount = Int(floor(initialSize.width / 100)) + while !dimensions.isEmpty { - let row = fitPrettyDimensions(dimensions, isLastRow: f > dimensions.count, fitToHeight: false, perSize:initialSize) + //let row = fitPrettyDimensions(dimensions, isLastRow: f > dimensions.count, fitToHeight: false, perSize:initialSize) + var row:[NSSize] = [] + + while !dimensions.isEmpty && row.count < rowCount { + dimensions.removeFirst() + row.append(NSMakeSize(floor(initialSize.width / CGFloat(rowCount)), initialSize.height)) + } + fitted.append(row) - dimensions.removeSubrange(0 ..< row.count) + } + + + if fitted.count >= 2, fitted[fitted.count - 1].count == 1 && fitted[fitted.count - 2].reduce(0, { $0 + $1.width}) < (initialSize.width - 50) { + let width = fitted[fitted.count - 2].reduce(0, { $0 + $1.width}) + let last = fitted.removeLast() + fitted[fitted.count - 1] = fitted[fitted.count - 1] + [NSMakeSize(initialSize.width - width, last[0].height)] } for row in fitted { @@ -312,3 +356,65 @@ func makeMediaEnties(_ results:[ChatContextResult], initialSize:NSSize) -> [Inpu +func makeChatGridMediaEnties(_ results:[Message], initialSize:NSSize) -> [InputMediaContextRow] { + var entries:[InputMediaContextEntry] = [] + var rows:[InputMediaContextRow] = [] + + var dimensions:[NSSize] = [] + var removeResultIndexes:[Int] = [] + var results = results + for i in 0 ..< results.count { + + let result = results[i] + + if let file = result.media.first as? TelegramMediaFile { + let dimension:NSSize = file.videoSize + + let imageReference: ImageMediaReference? + if !file.previewRepresentations.isEmpty { + let img = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: file.previewRepresentations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + imageReference = ImageMediaReference.message(message: MessageReference(result), media: img) + } else { + imageReference = nil + } + entries.append(.gif(thumb: imageReference, file: FileMediaReference.message(message: MessageReference(result), media: file))) + + dimensions.append(dimension) + } else { + removeResultIndexes.append(i) + } + } + + for i in removeResultIndexes.reversed() { + results.remove(at: i) + } + var fitted:[[NSSize]] = [] + let f:Int = Int(round(initialSize.width / initialSize.height)) + while !dimensions.isEmpty { + let row = fitPrettyDimensions(dimensions, isLastRow: f > dimensions.count, fitToHeight: false, perSize:initialSize) + fitted.append(row) + dimensions.removeSubrange(0 ..< row.count) + } + + if fitted.count >= 2, fitted[fitted.count - 1].count == 1 && fitted[fitted.count - 2].reduce(0, { $0 + $1.width}) < (initialSize.width - 50) { + let width = fitted[fitted.count - 2].reduce(0, { $0 + $1.width}) + let last = fitted.removeLast() + fitted[fitted.count - 1] = fitted[fitted.count - 1] + [NSMakeSize(initialSize.width - width, last[0].height)] + } + + for row in fitted { + let subentries = Array(entries.prefix(row.count)) + let subresult = Array(results.prefix(row.count)) + rows.append(InputMediaContextRow(entries: subentries, results: [], sizes: row, messages: subresult)) + + if entries.count >= row.count { + entries.removeSubrange(0 ..< row.count) + } + if results.count >= row.count { + results.removeSubrange(0 ..< row.count) + } + } + + return rows +} + diff --git a/Telegram-Mac/PreviewSenderController.swift b/Telegram-Mac/PreviewSenderController.swift index 512e877be1..c43eebc557 100644 --- a/Telegram-Mac/PreviewSenderController.swift +++ b/Telegram-Mac/PreviewSenderController.swift @@ -8,199 +8,1293 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox private enum SecretMediaTtl { case off case seconds(Int32) } +private enum PreviewSenderType { + case files + case photo + case video + case gif + case audio + case media +} + + +fileprivate enum PreviewSendingState : Int32 { + case media = 0 + case file = 1 + case collage = 2 + case archive = 3 +} + +private final class PreviewContextState : Equatable { + let inputQueryResult: ChatPresentationInputQueryResult? + init(inputQueryResult: ChatPresentationInputQueryResult? = nil) { + self.inputQueryResult = inputQueryResult + } + func updatedInputQueryResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> PreviewContextState { + return PreviewContextState(inputQueryResult: f(self.inputQueryResult)) + } +} + +private func ==(lhs: PreviewContextState, rhs: PreviewContextState) -> Bool { + return lhs.inputQueryResult == rhs.inputQueryResult +} + +private final class PreviewContextInteraction : InterfaceObserver { + private(set) var state: PreviewContextState = PreviewContextState() + + func update(animated:Bool = true, _ f:(PreviewContextState)->PreviewContextState) -> Void { + let oldValue = self.state + self.state = f(state) + if oldValue != state { + notifyObservers(value: state, oldValue:oldValue, animated: animated) + } + } +} + + fileprivate class PreviewSenderView : Control { - fileprivate let tableView:TableView = TableView() - fileprivate let textView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) + fileprivate let tableView:TableView = TableView(frame: NSZeroRect) + fileprivate let textView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSMakeRect(0, 0, 280, 34)) + fileprivate let sendButton = ImageButton() + fileprivate let emojiButton = ImageButton() + fileprivate let actionsContainerView: View = View() + fileprivate let headerView: View = View() + fileprivate let draggingView = DraggingView(frame: NSZeroRect) + fileprivate let closeButton = ImageButton() + fileprivate let photoButton = ImageButton() + fileprivate let fileButton = ImageButton() + fileprivate let collageButton = ImageButton() + fileprivate let archiveButton = ImageButton() + fileprivate let textContainerView: View = View() + fileprivate let separator: View = View() + fileprivate let forHelperView: View = View() + fileprivate weak var controller: PreviewSenderController? + fileprivate var stateValueInteractiveUpdate: ((PreviewSendingState)->Void)? + private var _state: PreviewSendingState = .file + var state: PreviewSendingState { + set { + _state = newValue + self.fileButton.isSelected = newValue == .file + self.photoButton.isSelected = newValue == .media + self.collageButton.isSelected = newValue == .collage + self.archiveButton.isSelected = newValue == .archive + + Queue.mainQueue().justDispatch { + removeAllTooltips(mainWindow) + self.fileButton.controlState = .Normal + self.photoButton.controlState = .Normal + self.collageButton.controlState = .Normal + self.archiveButton.controlState = .Normal + } + } + get { + return self._state + } + } + + fileprivate func updateWithSlowMode(_ slowMode: SlowMode?, urlsCount: Int) { + if urlsCount > 1, let _ = slowMode { + self.fileButton.isEnabled = false + self.photoButton.isEnabled = false + self.photoButton.appTooltip = L10n.slowModePreviewSenderFileTooltip + self.fileButton.appTooltip = L10n.slowModePreviewSenderFileTooltip + } else { + self.fileButton.isEnabled = true + self.photoButton.isEnabled = true + self.photoButton.appTooltip = L10n.previewSenderMediaTooltip + self.fileButton.appTooltip = L10n.previewSenderFileTooltip + } + } + + + + private let disposable = MetaDisposable() required init(frame frameRect: NSRect) { super.init(frame: frameRect) - tableView.setFrameSize(frameRect.width, frameRect.height - 34) - backgroundColor = theme.colors.background - textView.setPlaceholderAttributedString(.initialize(string: tr(.previderSenderCaptionPlaceholder), color: theme.colors.grayText, font: .normal(.text)), update: false) - textView.background = theme.colors.background - textView.textFont = .normal(.text) - textView.textColor = theme.colors.text - textView.linkColor = theme.colors.link - textView.max_height = 120 - backgroundColor = theme.colors.background - textView.setFrameSize(NSMakeSize(frameRect.width - 48, 34)) + backgroundColor = theme.colors.background + separator.backgroundColor = theme.colors.border + textContainerView.backgroundColor = theme.colors.background + textView.setBackgroundColor(theme.colors.background) + closeButton.set(image: theme.icons.modalClose, for: .Normal) + _ = closeButton.sizeToFit() + + + photoButton.appTooltip = L10n.previewSenderMediaTooltip + fileButton.appTooltip = L10n.previewSenderFileTooltip + collageButton.appTooltip = L10n.previewSenderCollageTooltip + archiveButton.appTooltip = L10n.previewSenderArchiveTooltip + + photoButton.set(image: ControlStyle(highlightColor: theme.colors.grayIcon).highlight(image: theme.icons.previewSenderPhoto), for: .Normal) + _ = photoButton.sizeToFit() + + photoButton.set(handler: { [weak self] _ in + self?.stateValueInteractiveUpdate?(.media) + FastSettings.toggleIsNeedCollage(false) + }, for: .Click) + + + archiveButton.set(handler: { [weak self] _ in + self?.stateValueInteractiveUpdate?(.archive) + // getAppTooltip(for: ., callback: <#T##(String) -> Void#>) + }, for: .Click) + + collageButton.set(handler: { [weak self] control in + guard let `self` = self else { return } + if control.isSelected { + if self.photoButton.isEnabled { + self.stateValueInteractiveUpdate?(.media) + } else if self.photoButton.isEnabled { + self.stateValueInteractiveUpdate?(.file) + } else if self.archiveButton.isEnabled { + self.stateValueInteractiveUpdate?(.archive) + } + FastSettings.toggleIsNeedCollage(false) + } else { + self.stateValueInteractiveUpdate?(.collage) + FastSettings.toggleIsNeedCollage(true) + } + + }, for: .Click) + + fileButton.set(handler: { [weak self] _ in + self?.stateValueInteractiveUpdate?(.file) + }, for: .Click) + + closeButton.set(handler: { [weak self] _ in + self?.controller?.closeModal() + }, for: .Click) + + fileButton.set(image: ControlStyle(highlightColor: theme.colors.grayIcon).highlight(image: theme.icons.previewSenderFile), for: .Normal) + _ = fileButton.sizeToFit() + + collageButton.set(image: theme.icons.previewSenderCollage, for: .Normal) + _ = collageButton.sizeToFit() + + archiveButton.set(image: theme.icons.previewSenderArchive, for: .Normal) + _ = archiveButton.sizeToFit() + + + + headerView.addSubview(closeButton) + headerView.addSubview(fileButton) + headerView.addSubview(photoButton) + headerView.addSubview(collageButton) + headerView.addSubview(archiveButton) + + sendButton.set(image: theme.icons.chatSendMessage, for: .Normal) + sendButton.autohighlight = false + _ = sendButton.sizeToFit() + + emojiButton.set(image: theme.icons.chatEntertainment, for: .Normal) + _ = emojiButton.sizeToFit() + + actionsContainerView.addSubview(sendButton) + actionsContainerView.addSubview(emojiButton) + + + actionsContainerView.setFrameSize(sendButton.frame.width + emojiButton.frame.width + 40, 50) + + emojiButton.centerY(x: 0) + sendButton.centerY(x: emojiButton.frame.maxX + 20) + + backgroundColor = theme.colors.background + textView.background = theme.colors.background + textView.textFont = .normal(.text) + textView.textColor = theme.colors.text + textView.linkColor = theme.colors.link + textView.max_height = 180 + + emojiButton.set(handler: { [weak self] control in + self?.controller?.showEmoji(for: control) + }, for: .Hover) + + sendButton.set(handler: { [weak self] _ in + self?.controller?.send(false) + }, for: .SingleClick) + + let handler:(Control)->Void = { [weak self] control in + if let controller = self?.controller, let peer = controller.chatInteraction.peer, !peer.isSecretChat { + + let chatInteraction = controller.chatInteraction + let context = chatInteraction.context + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + return + } + + var items:[SPopoverItem] = [] + + if peer.id != chatInteraction.context.account.peerId { + items.append(SPopoverItem(L10n.chatSendWithoutSound, { [weak controller] in + controller?.send(true) + })) + } + switch chatInteraction.mode { + case .history: + items.append(SPopoverItem(peer.id == chatInteraction.context.peerId ? L10n.chatSendSetReminder : L10n.chatSendScheduledMessage, { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak controller] date in + controller?.send(false, atDate: date) + }), for: context.window) + })) + case .scheduled: + break + } + if !items.isEmpty { + showPopover(for: control, with: SPopoverViewController(items: items)) + } + } + } + + sendButton.set(handler: handler, for: .RightDown) + sendButton.set(handler: handler, for: .LongMouseDown) + + textView.setFrameSize(NSMakeSize(280, 34)) + + addSubview(tableView) + + + textContainerView.addSubview(textView) + + addSubview(headerView) + addSubview(forHelperView) + addSubview(textContainerView) + addSubview(actionsContainerView) + addSubview(separator) + addSubview(draggingView) + + layout() + } + + deinit { + disposable.dispose() + } + + var additionHeight: CGFloat { + return max(50, textView.frame.height + 16) + headerView.frame.height + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + override func change(size: NSSize, animated: Bool, _ save: Bool = true, removeOnCompletion: Bool = true, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + self.updateHeight(self.textView.frame.height, animated) + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + func updateHeight(_ height: CGFloat, _ animated: Bool) { + CATransaction.begin() + textContainerView.change(size: NSMakeSize(frame.width, height + 16), animated: animated) + textContainerView.change(pos: NSMakePoint(0, frame.height - textContainerView.frame.height), animated: animated) + textView._change(pos: NSMakePoint(10, height == 34 ? 8 : 11), animated: animated) + + actionsContainerView.change(pos: NSMakePoint(frame.width - actionsContainerView.frame.width, frame.height - actionsContainerView.frame.height), animated: animated) + + separator.change(pos: NSMakePoint(0, textContainerView.frame.minY), animated: animated) + CATransaction.commit() + + // needsLayout = true + } + + func applyOptions(_ options:[PreviewOptions], count: Int, canCollage: Bool) { + fileButton.isHidden = false//!options.contains(.media) + photoButton.isHidden = !options.contains(.media) + archiveButton.isHidden = count < 2 + self.collageButton.isHidden = !canCollage + separator.isHidden = false + needsLayout = true + } + + override func layout() { + super.layout() + actionsContainerView.setFrameOrigin(frame.width - actionsContainerView.frame.width, frame.height - actionsContainerView.frame.height) + headerView.setFrameSize(frame.width, 50) + + tableView.setFrameSize(NSMakeSize(frame.width, frame.height - additionHeight)) + tableView.centerX(y: headerView.frame.maxY - 6) + + draggingView.frame = tableView.frame + + + + closeButton.centerY(x: headerView.frame.width - closeButton.frame.width - 10) + collageButton.centerY(x: closeButton.frame.minX - 10 - collageButton.frame.width) + + var inset: CGFloat = 10 + + if !photoButton.isHidden { + photoButton.centerY(x: inset) + inset += photoButton.frame.width + 10 + } + + if !fileButton.isHidden { + fileButton.centerY(x: inset) + inset += fileButton.frame.width + 10 + } + + if !archiveButton.isHidden { + archiveButton.centerY(x: inset) + inset += archiveButton.frame.width + 10 + } + + textContainerView.setFrameSize(frame.width, textView.frame.height + 16) + textContainerView.setFrameOrigin(0, frame.height - textContainerView.frame.height) + textView.setFrameSize(NSMakeSize(textContainerView.frame.width - 10 - actionsContainerView.frame.width, textView.frame.height)) + textView.setFrameOrigin(10, textView.frame.height == 34 ? 8 : 11) + + separator.frame = NSMakeRect(0, textContainerView.frame.minY, frame.width, .borderSize) + + forHelperView.frame = NSMakeRect(0, textContainerView.frame.minY, 0, 0) + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class SenderPreviewArguments { + let context: AccountContext + let edit:(URL)->Void + let delete:(URL)->Void + let reorder:(Int, Int) -> Void + init(context: AccountContext, edit: @escaping(URL)->Void, delete: @escaping(URL)->Void, reorder: @escaping(Int, Int) -> Void) { + self.context = context + self.edit = edit + self.delete = delete + self.reorder = reorder + } +} + +private struct PreviewState : Equatable { + let urls:[URL] + let medias:[Media] + let currentState: PreviewSendingState + let editedData: [URL : EditedImageData] + init(urls: [URL], medias: [Media], currentState: PreviewSendingState, editedData: [URL : EditedImageData]) { + self.urls = urls + self.medias = medias + self.currentState = currentState + self.editedData = editedData + } + + func withUpdatedEditedData(_ f:([URL : EditedImageData]) -> [URL : EditedImageData]) -> PreviewState { + return PreviewState(urls: self.urls, medias: self.medias, currentState: self.currentState, editedData: f(self.editedData)) + } + func apply(transition: UpdateTransition, urls:[URL], state: PreviewSendingState) -> PreviewState { + var medias:[Media] = self.medias + for rdx in transition.deleted.reversed() { + medias.remove(at: rdx) + } + for (idx, media) in transition.inserted { + medias.insert(media, at: idx) + } + for (idx, item) in transition.updated { + medias[idx] = item + } + + return PreviewState(urls: urls, medias: medias, currentState: state, editedData: self.editedData) + } +} +private func == (lhs: PreviewState, rhs: PreviewState) -> Bool { + if lhs.medias.count != rhs.medias.count { + return false + } else { + for i in 0 ..< lhs.medias.count { + if !lhs.medias[i].isEqual(to: rhs.medias[i]) { + return false + } + } + } + return lhs.urls == rhs.urls && lhs.currentState == rhs.currentState && lhs.editedData == rhs.editedData +} + + +private enum PreviewEntryId : Hashable { + static func == (lhs: PreviewEntryId, rhs: PreviewEntryId) -> Bool { + switch lhs { + case let .media(lhsMedia): + if case let .media(rhsMedia) = rhs { + return lhsMedia.isEqual(to: rhsMedia) + } else { + return false + } + case .mediaGroup: + if case .mediaGroup = rhs { + return true + } else { + return false + } + case let .section(index): + if case .section(index) = rhs { + return true + } else { + return false + } + case .archive: + if case .archive = rhs { + return true + } else { + return false + } + } + } + + func hash(into hasher: inout Hasher) { + + } + + case media(Media) + case mediaGroup + case archive + case section(Int) +} + +private enum PreviewEntry : Comparable, Identifiable { + case section(Int) + case media(index: Int, sectionId: Int, url: URL, media: Media) + case mediaGroup(index: Int, sectionId: Int, urls: [URL], messages: [Message]) + case archive(index: Int, sectionId: Int, urls: [URL], media: Media) + var stableId: PreviewEntryId { + switch self { + case let .section(sectionId): + return .section(sectionId) + case let .media(_, _, _, media): + return .media(media) + case .mediaGroup: + return .mediaGroup + case .archive: + return .archive + } + } + + var index: Int { + switch self { + case let .section(sectionId): + return (sectionId + 1) * 1000 - sectionId + case let .media(index, sectionId, _, _): + return (sectionId * 1000) + index + case let .mediaGroup(index, sectionId, _, _): + return (sectionId * 1000) + index + case let .archive(index, sectionId, _, _): + return (sectionId * 1000) + index + } + } + + func item(arguments: SenderPreviewArguments, state: PreviewState, initialSize: NSSize) -> TableRowItem { + switch self { + case .section: + return GeneralRowItem(initialSize, height: 20, stableId: stableId) + case let .media(_, _, url, media): + return MediaPreviewRowItem(initialSize, media: media, context: arguments.context, hasEditedData: state.editedData[url] != nil, edit: { + arguments.edit(url) + }, delete: { + arguments.delete(url) + }) + case let .archive(_, _, _, media): + return MediaPreviewRowItem(initialSize, media: media, context: arguments.context, hasEditedData: false, edit: { + // arguments.edit(url) + }, delete: { + // arguments.delete(url) + }) + case let .mediaGroup(_, _, urls, messages): + return MediaGroupPreviewRowItem(initialSize, messages: messages, urls: urls, editedData: state.editedData, edit: { url in + arguments.edit(url) + }, delete: { url in + arguments.delete(url) + }, context: arguments.context, reorder: { from, to in + arguments.reorder(from, to) + }) + } + } + +} +private func == (lhs: PreviewEntry, rhs: PreviewEntry) -> Bool { + switch lhs { + case let .media(index, sectionId, url, lhsMedia): + if case .media(index, sectionId, url, let rhsMedia) = rhs { + return lhsMedia.isEqual(to: rhsMedia) + } else { + return false + } + case let .archive(index, sectionId, urls, lhsMedia): + if case .archive(index, sectionId, urls, let rhsMedia) = rhs { + return lhsMedia.isEqual(to: rhsMedia) + } else { + return false + } + case let .mediaGroup(index, sectionId, url, lhsMessages): + if case .mediaGroup(index, sectionId, url, let rhsMessages) = rhs { + if lhsMessages.count != rhsMessages.count { + return false + } else { + for i in 0 ..< lhsMessages.count { + if !isEqualMessages(lhsMessages[i], rhsMessages[i]) { + return false + } + } + return true + } + } else { + return false + } + case let .section(section): + if case .section(section) = rhs { + return true + } else { + return false + } + } +} +private func < (lhs: PreviewEntry, rhs: PreviewEntry) -> Bool { + return lhs.index < rhs.index +} + +private func previewMediaEntries( _ state: PreviewState) -> [PreviewEntry] { + + var entries: [PreviewEntry] = [] + var index: Int = 0 + + let sectionId: Int = 0 + + switch state.currentState { + case .archive: + assert(state.medias.count == 1) + entries.append(.archive(index: index, sectionId: sectionId, urls: state.urls, media: state.medias[0])) + case .file, .media: + for (i, media) in state.medias.enumerated() { + entries.append(.media(index: index, sectionId: sectionId, url: state.urls[i], media: media)) + index += 1 + } + case .collage: + var messages: [Message] = [] + for (id, media) in state.medias.enumerated() { + messages.append(Message(media, stableId: UInt32(id), messageId: MessageId(peerId: PeerId(0), namespace: 0, id: MessageId.Id(id)))) + } + entries.append(.mediaGroup(index: index, sectionId: sectionId, urls: state.urls, messages: messages)) + } + + return entries +} + + +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], state: PreviewState, arguments: SenderPreviewArguments, animated: Bool, initialSize:NSSize) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments: arguments, state: state, initialSize: initialSize) + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: animated, grouping: false) +} + +private enum PreviewMediaId : Hashable { + case container(MediaSenderContainer) + + func hash(into hasher: inout Hasher) { + + } + +} + +private final class PreviewMedia : Comparable, Identifiable { + + static func < (lhs: PreviewMedia, rhs: PreviewMedia) -> Bool { + return lhs.index < rhs.index + } + static func == (lhs: PreviewMedia, rhs: PreviewMedia) -> Bool { + return lhs.container == rhs.container + } + let container: MediaSenderContainer + let index: Int + private(set) var media: Media? + + init(container: MediaSenderContainer, index: Int, media: Media?) { + self.container = container + self.index = index + self.media = media + } + + + + var stableId: PreviewMediaId { + return .container(container) + } + + func withApplyCachedMedia(_ media: Media) -> PreviewMedia { + return PreviewMedia(container: container, index: index, media: media) + } + + func generateMedia(account: Account, isSecretRelated: Bool) -> Media { + + if let media = self.media { + return media + } + + let semaphore = DispatchSemaphore(value: 0) + var generated: Media! + + if let container = container as? ArchiverSenderContainer { + for url in container.files { + try? FileManager.default.copyItem(atPath: url.path, toPath: container.path + "/" + url.path.nsstring.lastPathComponent) + } + } + + _ = Sender.generateMedia(for: container, account: account, isSecretRelated: isSecretRelated).start(next: { media, path in + generated = media + semaphore.signal() + }) + semaphore.wait() + + self.media = generated + + return generated + } +} + + +private func previewMedias(containers:[MediaSenderContainer], savedState: [PreviewMedia]?) -> [PreviewMedia] { + var index: Int = 0 + var result:[PreviewMedia] = [] + for container in containers { + let found = savedState?.first(where: {$0.stableId == .container(container)}) + result.append(PreviewMedia(container: container, index: index, media: found?.media)) + index += 1 + } + return result +} + + +private func prepareMedias(left: [PreviewMedia], right: [PreviewMedia], isSecretRelated: Bool, account: Account) -> UpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right, { item in + return item.generateMedia(account: account, isSecretRelated: isSecretRelated) + }) + return UpdateTransition(deleted: removed, inserted: inserted, updated: updated) +} + +private struct UrlAndState : Equatable { + let urls:[URL] + let state: PreviewSendingState + init(_ urls:[URL], _ state: PreviewSendingState) { + self.urls = urls + self.state = state + } +} + + +class PreviewSenderController: ModalViewController, TGModernGrowingDelegate, Notifable { + + + private var _urls:[URL] = [] + + fileprivate var urls:[URL] { + set { + _urls = newValue.uniqueElements + let canCollage: Bool = canCollagesFromUrl(_urls) + if chatInteraction.presentation.slowMode != nil, self.urls.count > 1 { + switch self.genericView.state { + case .media, .file: + if canCollage { + self.genericView.state = .collage + } else { + self.genericView.state = .archive + } + case .collage: + if !canCollage { + self.genericView.state = .archive + } + case .archive: + break + } + } else if self.genericView.state == .collage, !canCollage { + self.genericView.state = .media + } + self.genericView.updateWithSlowMode(chatInteraction.presentation.slowMode, urlsCount: _urls.count) + self.urlsAndStateValue.set(UrlAndState(_urls, self.genericView.state)) + } + get { + return _urls + } + } + + fileprivate let urlsAndStateValue:ValuePromise = ValuePromise(ignoreRepeated: true) + + + private let context:AccountContext + let chatInteraction:ChatInteraction + private let disposable = MetaDisposable() + private let emoji: EmojiViewController + private var cachedMedia:[PreviewSendingState: (media: [Media], items: [TableRowItem])] = [:] + private var sent: Bool = false + private let pasteDisposable = MetaDisposable() + + + private var temporaryInputState: ChatTextInputState? + private var contextQueryState: (ChatPresentationInputQuery?, Disposable)? + private let inputContextHelper: InputContextHelper + private let inputInteraction:PreviewContextInteraction = PreviewContextInteraction() + private let contextChatInteraction: ChatInteraction + private let editorDisposable = MetaDisposable() + private let archiverStatusesDisposable = MetaDisposable() + private var archiveStatuses: [ArchiveSource : ArchiveStatus] = [:] + private var genericView:PreviewSenderView { + return self.view as! PreviewSenderView + } + + override var responderPriority: HandlerPriority { + return .high + } + + private var sendCurrentMedia:((Bool, Date?)->Void)? = nil + private var runEditor:((URL)->Void)? = nil + private var insertAdditionUrls:(([URL]) -> Void)? = nil + + private let animated: Atomic = Atomic(value: false) + + private func updateSize(_ width: CGFloat, animated: Bool) { + if let contentSize = context.window.contentView?.frame.size { + + var listHeight = genericView.tableView.listHeight + if let inputQuery = inputInteraction.state.inputQueryResult { + switch inputQuery { + case let .emoji(emoji, _): + if !emoji.isEmpty { + listHeight = listHeight > 0 ? max(40, listHeight) : 0 + } + default: + listHeight = listHeight > 0 ? max(150, listHeight) : 0 + } + } + + let height = listHeight + max(genericView.additionHeight, 88) + + + self.modal?.resize(with: NSMakeSize(width, min(contentSize.height - 70, height)), animated: animated) + // genericView.layout() + } + } + + override var dynamicSize: Bool { + return true + } + + override func draggingItems(for pasteboard: NSPasteboard) -> [DragItem] { + if let types = pasteboard.types, types.contains(.kFilenames) { + let list = pasteboard.propertyList(forType: .kFilenames) as? [String] + if let list = list { + return [DragItem(title: L10n.previewDraggingAddItemsCountable(list.count), desc: "", handler: { [weak self] in + self?.insertAdditionUrls?(list.map({URL(fileURLWithPath: $0)})) + + })] + } + } + return [] + } + + override func returnKeyAction() -> KeyHandlerResult { + if let currentEvent = NSApp.currentEvent { + if FastSettings.checkSendingAbility(for: currentEvent), didSetReady { + send(false) + return .invoked + } + } + return .invokeNext + } + + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + + let currentText = self.genericView.textView.string() + let basicText = self.temporaryInputState?.inputText ?? "" + if (self.temporaryInputState == nil && !currentText.isEmpty) || (basicText != currentText) { + confirm(for: context.window, header: L10n.mediaSenderDiscardChangesHeader, information: L10n.mediaSenderDiscardChangesText, okTitle: L10n.mediaSenderDiscardChangesOK, successHandler: { [weak self] _ in + self?.closeModal() + }) + } else { + self.closeModal() + } + } + + fileprivate func closeModal() { + super.close() + } + + func send(_ silent: Bool, atDate: Date? = nil) { + + let text = self.genericView.textView.string().trimmed + if text.length > ChatPresentationInterfaceState.maxShortInput { + alert(for: chatInteraction.context.window, info: L10n.chatInputErrorMessageTooLongCountable(text.length - Int(ChatPresentationInterfaceState.maxShortInput))) + return + } + + switch chatInteraction.mode { + case .scheduled: + if let peer = chatInteraction.peer { + showModal(with: ScheduledMessageModalController(context: context, peerId: peer.id, scheduleAt: { [weak self] date in + self?.sendCurrentMedia?(silent, date) + }), for: context.window) + } + case .history: + sendCurrentMedia?(silent, atDate) + } + } + + + override func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.tableView.listHeight + max(genericView.additionHeight, 88))), animated: false) + } + + override var handleAllEvents: Bool { + return true + } + + private var inputPlaceholder: String { + var placeholder: String = L10n.previewSenderCommentPlaceholder + if self.genericView.tableView.count == 1 { + if let item = self.genericView.tableView.firstItem { + if let item = item as? MediaPreviewRowItem { + if item.media.canHaveCaption { + placeholder = L10n.previewSenderCaptionPlaceholder + } + } else if item is MediaGroupPreviewRowItem { + placeholder = L10n.previewSenderCaptionPlaceholder + } + } + } + + return placeholder + } + + override func viewDidLoad() { + super.viewDidLoad() + + + genericView.draggingView.controller = self + genericView.controller = self + genericView.textView.delegate = self + inputInteraction.add(observer: self) + + self.genericView.textView.setPlaceholderAttributedString(.initialize(string: self.inputPlaceholder, color: theme.colors.grayText, font: .normal(.text)), update: false) + + if let attributedString = attributedString { + genericView.textView.setAttributedString(attributedString, animated: false) + } else { + self.temporaryInputState = chatInteraction.presentation.interfaceState.inputState + let text = chatInteraction.presentation.interfaceState.inputState.attributedString + + genericView.textView.setAttributedString(text, animated: false) + chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedInputState(ChatTextInputState())})}) + } + + let interactions = EntertainmentInteractions(.emoji, peerId: chatInteraction.peerId) + + interactions.sendEmoji = { [weak self] emoji in + self?.genericView.textView.appendText(emoji) + } + + emoji.update(with: interactions) + + let actionsDisposable = DisposableSet() + self.disposable.set(actionsDisposable) + + let context = self.context + let initialSize = self.atomicSize + + let initialState = PreviewState(urls: [], medias: [], currentState: .media, editedData: [:]) + + let statePromise:ValuePromise = ValuePromise(ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((PreviewState) -> PreviewState) -> Void = { f in + statePromise.set(stateValue.modify (f)) + } + + let removeTransitionAnimation: Atomic = Atomic(value: false) - addSubview(tableView) - addSubview(textView) - } - - override func layout() { - super.layout() - textView.setFrameOrigin(NSMakePoint(24, frame.height - textView.frame.height)) - } - - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} + let arguments = SenderPreviewArguments(context: context, edit: { [weak self] url in + self?.runEditor?(url) + }, delete: { [weak self] url in + guard let `self` = self else { return } + self.urls.removeAll(where: {$0 == url}) + }, reorder: { [weak self] from, to in + guard let `self` = self else { return } + _ = removeTransitionAnimation.swap(true) + self.urls.move(at: from, to: to) + }) + + let archiveRandomId = arc4random() + + let isSecretRelated = chatInteraction.peerId.namespace == Namespaces.Peer.SecretChat + + + let previousMedias:Atomic<[PreviewMedia]> = Atomic(value: []) + let savedStateMedias:Atomic<[PreviewSendingState : [PreviewMedia]]> = Atomic(value: [:]) -class PreviewSenderController: ModalViewController, TGModernGrowingDelegate { + let urlSignal = self.urlsAndStateValue.get() |> deliverOnPrepareQueue + + let urlsTransition: Signal<(UpdateTransition, [URL], PreviewSendingState, [PreviewMedia]), NoError> = urlSignal |> map { urlsAndState -> ([PreviewMedia], [URL], PreviewSendingState) in + + let urls = urlsAndState.urls + let state = urlsAndState.state + + var containers = urls.compactMap { url -> MediaSenderContainer? in + switch state { + case .media, .collage: + return MediaSenderContainer(path: url.path, isFile: false) + case .file: + return MediaSenderContainer(path: url.path, isFile: true) + case .archive: + return nil + } + } + + if state == .archive { + let dir = NSTemporaryDirectory() + "tg_temp_archive_\(archiveRandomId)" + try? FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil) + containers.append(ArchiverSenderContainer(path: dir, files: urls)) + } + + return (previewMedias(containers: containers, savedState: savedStateMedias.with { $0[state]}), urls, state) + } |> map { previews, urls, state in + return (prepareMedias(left: previousMedias.swap(previews), right: previews, isSecretRelated: isSecretRelated, account: context.account), urls, state, previews) + } - private var urls:[URL] - private let account:Account - private let chatInteraction:ChatInteraction - private var isNeedAsMedia:Bool = true - - override func viewClass() -> AnyClass { - return PreviewSenderView.self - } - - private var genericView:PreviewSenderView { - return self.view as! PreviewSenderView - } - + actionsDisposable.add(urlsTransition.start(next: { transition, urls, state, previews in + updateState { + $0.apply(transition: transition, urls: urls, state: state) + } + _ = savedStateMedias.modify { current in + var current = current + current[state] = previews + return current + } + })) - func makeItems(_ urls:[URL]) -> Signal { - let initialSize = atomicSize - let account = self.account - return Signal {[weak self] (subscriber) in - if let strongSelf = self { - - let headerItem:TableRowItem? - - let options = takeSenderOptions(for: urls) - - if urls.count == 1 { - let url = urls[0] - let mime = MIMEType(url.path.nsstring.pathExtension.lowercased()) - if mime.hasPrefix("image") && mediaExts.contains(url.path.nsstring.pathExtension.lowercased()) { - headerItem = PreviewThumbRowItem(initialSize.modify({$0}), url: url, account:account) - } else { - headerItem = PreviewDocumentRowItem(initialSize.modify({$0}), url: url, account:account) - } + let previousEntries:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let itemsTransition: Signal = combineLatest(queue: prepareQueue, statePromise.get() |> map { state -> ([PreviewEntry], PreviewState) in + return (previewMediaEntries(state), state) + }, appearanceSignal) |> map { datas, appearance in + let entries = datas.0.map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + return prepareTransition(left: previousEntries.swap(entries), right: entries, state: datas.1, arguments: arguments, animated: !removeTransitionAnimation.swap(false), initialSize: initialSize.with { $0 }) + } |> deliverOnMainQueue + + let first: Atomic = Atomic(value: false) + let scrollAfterTransition: Atomic = Atomic(value: false) + + actionsDisposable.add(itemsTransition.start(next: { [weak self] transition in + guard let `self` = self else {return} + + let state = stateValue.with { $0.currentState } + let medias = stateValue.with { $0.medias } + + + let sources:[ArchiveSource] = medias.filter { media in + if let media = media as? TelegramMediaFile { + return media.resource is LocalFileArchiveMediaResource } else { - headerItem = nil + return false + } + }.map { ($0 as! TelegramMediaFile).resource as! LocalFileArchiveMediaResource}.map {.resource($0)} + + self.archiverStatusesDisposable.set(combineLatest(sources.map {archiver.archive($0)}).start(next: { [weak self] statuses in + guard let `self` = self else {return} + self.archiveStatuses.removeAll() + for i in 0 ..< sources.count { + self.archiveStatuses[sources[i]] = statuses[i] } - if let headerItem = headerItem { - let _ = strongSelf.genericView.tableView.addItem(item: headerItem) + })) + + self.genericView.state = state + + let options = takeSenderOptions(for: self.urls) + self.genericView.applyOptions(options, count: self.urls.count, canCollage: canCollagesFromUrl(self.urls)) + + self.genericView.tableView.merge(with: transition) + + self.genericView.textView.setPlaceholderAttributedString(.initialize(string: self.inputPlaceholder, color: theme.colors.grayText, font: .normal(.text)), update: false) + + if self.genericView.tableView.isEmpty { + self.closeModal() + if self.chatInteraction.presentation.effectiveInput.inputText.isEmpty { + let attributedString = self.genericView.textView.attributedString() + let input = ChatTextInputState(inputText: attributedString.string, selectionRange: attributedString.string.length ..< attributedString.string.length, attributes: chatTextAttributes(from: attributedString)) + self.chatInteraction.update({$0.withUpdatedEffectiveInputState(input)}) + } + } else { + let oldSize = self.genericView.frame.size + self.updateSize(320, animated: first.swap(!self.genericView.tableView.isEmpty)) + if scrollAfterTransition.swap(false), self.genericView.frame.size == oldSize { + self.genericView.tableView.scroll(to: .down(true)) } + } + self.readyOnce() + + + if self.genericView.tableView.count > 1 { + self.genericView.tableView.resortController = TableResortController(resortRange: NSMakeRange(0, self.genericView.tableView.count), startTimeout: 0.0, start: { _ in }, resort: { _ in }, complete: { from, to in + arguments.reorder(from, to) + }) + } else { + self.genericView.tableView.resortController = nil + } + + })) + + + let canCollage: Bool = canCollagesFromUrl(self.urls) + let options = takeSenderOptions(for: self.urls) + + var state: PreviewSendingState = asMedia ? FastSettings.isNeedCollage && canCollage ? .collage : (options == [.file] ? .file : .media) : .file + if let _ = chatInteraction.presentation.slowMode { + if state != .archive && self.urls.count > 1, state != .collage { + state = .archive + } + } + + self.genericView.state = state + self.urlsAndStateValue.set(UrlAndState(self.urls, state)) + self.genericView.updateWithSlowMode(chatInteraction.presentation.slowMode, urlsCount: self.urls.count) + + self.genericView.textView.setPlaceholderAttributedString(.initialize(string: self.inputPlaceholder, color: theme.colors.grayText, font: .normal(.text)), update: false) + + self.genericView.stateValueInteractiveUpdate = { [weak self] state in + guard let `self` = self else { return } + self.genericView.tableView.scroll(to: .up(true)) + self.urlsAndStateValue.set(UrlAndState(self.urls, state)) + } + + self.sendCurrentMedia = { [weak self] silent, atDate in + guard let `self` = self else { return } + + let slowMode = self.chatInteraction.presentation.slowMode + let attributed = self.genericView.textView.attributedString() + + if let slowMode = slowMode, slowMode.hasLocked { + self.genericView.textView.shake() + } else if self.inputPlaceholder != L10n.previewSenderCaptionPlaceholder && slowMode != nil && attributed.length > 0 { + tooltip(for: self.genericView.sendButton, text: L10n.slowModeMultipleError) + self.genericView.textView.setSelectedRange(NSMakeRange(0, attributed.length)) + self.genericView.textView.shake() + } else { + let state = stateValue.with { $0.currentState } + let medias = stateValue.with { $0.medias } - if options.contains(.image) || options.contains(.video) { - let _ = strongSelf.genericView.tableView.addItem(item: GeneralRowItem(initialSize.modify({$0}), height:10)) - let _ = strongSelf.genericView.tableView.addItem(item: GeneralInteractedRowItem(initialSize.modify({$0}), name: tr(.previewSenderCompressFile), type: .switchable(stateback: { [weak strongSelf] () -> Bool in - if let strongSelf = strongSelf { - return strongSelf.isNeedAsMedia - } - return true - }), action:{ [weak strongSelf] in - if let strongSelf = strongSelf { - strongSelf.isNeedAsMedia = !strongSelf.isNeedAsMedia + for i in 0 ..< medias.count { + if let media = medias[i] as? TelegramMediaFile, let resource = media.resource as? LocalFileArchiveMediaResource { + if let status = self.archiveStatuses[.resource(resource)] { + switch status { + case .waiting, .fail, .none: + self.genericView.tableView.item(at: i).view?.shakeView() + return + default: + break + } + } else { + self.genericView.tableView.item(at: i).view?.shakeView() + return } - })) - + } } - - let _ = strongSelf.genericView.tableView.addItem(item: GeneralRowItem(initialSize.modify({$0}), height:10)) - if headerItem == nil { - strongSelf.expandUrls(urls) - } else { - strongSelf.textViewHeightChanged(34, animated: false) + self.sent = true + self.emoji.popover?.hide() + self.closeModal() + + var input:ChatTextInputState = ChatTextInputState(inputText: attributed.string, selectionRange: 0 ..< 0, attributes: chatTextAttributes(from: attributed)).subInputState(from: NSMakeRange(0, attributed.length)) + + if input.attributes.isEmpty { + input = ChatTextInputState(inputText: input.inputText.trimmed) } + var additionalMessage: ChatTextInputState? = nil - subscriber.putNext(true) - subscriber.putCompletion() - + if (medias.count > 1 || (medias.count == 1 && !medias[0].canHaveCaption)) && !input.inputText.isEmpty { + if state != .collage { + additionalMessage = input + input = ChatTextInputState() + + } + } + self.chatInteraction.sendMedias(medias, input, state == .collage, additionalMessage, silent, atDate) } - return EmptyDisposable - } |> runOn(Queue.mainQueue()) - } - - private func expandUrls(_ urls:[URL]) { - var index:Int = -1 - let initialSize = atomicSize.modify({$0}) - var inserted:[(Int, TableRowItem)] = [] - for url in urls { - index += 1 - inserted.append((index, ExpandedPreviewRowItem(initialSize, account:account, url: url, onDelete: { [weak self] item in - if let strongSelf = self { - if let index = strongSelf.genericView.tableView.index(of: item) { - strongSelf.genericView.tableView.remove(at: index, redraw: true, animation: .effectFade) - if let urlIndex = strongSelf.urls.index(of: url) { - strongSelf.urls.remove(at: urlIndex) - } - strongSelf.updateSize() - if strongSelf.urls.isEmpty { - strongSelf.modal?.close() + + } + + self.runEditor = { [weak self] url in + guard let `self` = self else { return } + + let editedData = stateValue.with { $0.editedData } + + let data = editedData[url] + let editor = EditImageModalController(data?.originalUrl ?? url, defaultData: data) + showModal(with: editor, for: mainWindow, animationType: .scaleCenter) + self.editorDisposable.set((editor.result |> deliverOnMainQueue).start(next: { [weak self] new, editedData in + guard let `self` = self else {return} + if let index = self.urls.firstIndex(where: { ($0 as NSURL) === (url as NSURL) }) { + updateState { $0.withUpdatedEditedData { data in + var data = data + if let editedData = editedData { + data[new] = editedData + } else { + data.removeValue(forKey: new) } - } + return data + }} + self.urls[index] = new + addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: AppLogEvents.imageEditor.rawValue, peerId: context.peerId, data: [:]) } - - }))) + })) } - - genericView.tableView.merge(with: TableUpdateTransition(deleted: [0], inserted: inserted, updated: [], animated: false, state: .saveVisible(.lower))) - updateSize() - genericView.tableView.scroll(to: .down(false)) - } - - private func updateSize() { - if let contentSize = self.window?.contentView?.frame.size { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 70, genericView.tableView.listHeight + genericView.textView.frame.height)), animated: false) + + self.insertAdditionUrls = { [weak self] list in + guard let `self` = self else { return } + let previous = self.urls + _ = scrollAfterTransition.swap(true) + self.urls.append(contentsOf: list) + if previous == self.urls { + _ = scrollAfterTransition.swap(false) + NSSound.beep() + } } - } - - override var modalInteractions: ModalInteractions? { - let chatInteraction = self.chatInteraction + - return ModalInteractions(acceptTitle:tr(.modalSend), accept: { [weak self] in - if let urls = self?.urls, let asMedia = self?.isNeedAsMedia { - let text = self?.genericView.textView.string() ?? "" - var containers:[MediaSenderContainer] = [] - for url in urls { - let asMedia = asMedia && mediaExts.contains(url.path.nsstring.pathExtension.lowercased()) - containers.append(MediaSenderContainer(path:url.path, caption: urls.count == 1 ? text : "", isFile:!asMedia)) - } - if urls.count > 1 && !text.isEmpty { - chatInteraction.forceSendMessage(text) - } - chatInteraction.sendMedia(containers) + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + let state = stateValue.with { $0.currentState } + let medias = stateValue.with { $0.medias } + + if state == .media, medias.count == 1, medias.first is TelegramMediaImage { + self.runEditor?(self.urls[0]) } - self?.modal?.close() - }, cancelTitle: tr(.modalCancel), drawBorder: true) - } - - override var dynamicSize: Bool { - return true - } - - override func returnKeyAction() -> KeyHandlerResult { - if let currentEvent = NSApp.currentEvent { - if FastSettings.checkSendingAbility(for: currentEvent) { - self.modal?.close(true) + return .invoked + }, with: self, for: .E, priority: .high, modifierFlags: [.command]) + + + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .LeftArrow, priority: .modal) + + context.window.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: self, for: .RightArrow, priority: .modal) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.inputInteraction.state.inputQueryResult != nil { + return .rejected + } + return .invokeNext + }, with: self, for: .UpArrow, priority: .modal) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.inputInteraction.state.inputQueryResult != nil { + return .rejected + } + return .invokeNext + }, with: self, for: .DownArrow, priority: .modal) + + self.context.window.set(handler: { [weak self] () -> KeyHandlerResult in + if let strongSelf = self, strongSelf.context.window.firstResponder != strongSelf.genericView.textView.inputView { + _ = strongSelf.context.window.makeFirstResponder(strongSelf.genericView.textView.inputView) return .invoked } - } + return .invoked + }, with: self, for: .Tab, priority: .modal) - return .invokeNext + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.textView.boldWord() + return .invoked + }, with: self, for: .B, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + self.makeUrl(of: self.genericView.textView.selectedRange()) + return .invoked + }, with: self, for: .U, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.textView.italicWord() + return .invoked + }, with: self, for: .I, priority: .modal, modifierFlags: [.command]) + + context.window.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.textView.codeWord() + return .invoked + }, with: self, for: .K, priority: .modal, modifierFlags: [.command, .shift]) + + + context.window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + if !self.genericView.tableView.isEmpty, let view = self.genericView.tableView.item(at: 0).view as? MediaGroupPreviewRowView { + if view.draggingIndex != nil { + view.mouseUp(with: event) + return .invoked + } + } + return .rejected + }, with: self, for: .leftMouseUp, priority: .high) + + + + context.window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else { return .rejected } + if !self.genericView.tableView.isEmpty, let view = self.genericView.tableView.item(at: 0).view { + view.pressureChange(with: event) + } + return .invoked + }, with: self, for: .pressure, priority: .high) + + context.window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + self.genericView.tableView.enumerateViews(with: { view -> Bool in + view.updateMouse() + return true + }) + + return .invokeNext + }, with: self, for: .mouseMoved, priority: .high) + + genericView.tableView.needUpdateVisibleAfterScroll = true } - override func measure(size: NSSize) { - self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.tableView.listHeight + genericView.textView.frame.height)), animated: false) + deinit { + inputInteraction.remove(observer: self) + disposable.dispose() + editorDisposable.dispose() + archiverStatusesDisposable.dispose() } - override func viewDidLoad() { - super.viewDidLoad() - genericView.textView.delegate = self - textViewHeightChanged(34, animated: false) - ready.set(makeItems(self.urls)) + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + closeAllPopovers(for: mainWindow) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if !sent, let temp = temporaryInputState { + chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedInputState(temp)})}) + } + if !sent { + for (_, cached) in cachedMedia { + for media in cached.media { + if let media = media as? TelegramMediaFile, let resource = media.resource as? LocalFileArchiveMediaResource { + archiver.remove(.resource(resource)) + } + } + } + } + window?.removeAllHandlers(for: self) } override func becomeFirstResponder() -> Bool? { @@ -210,20 +1304,76 @@ class PreviewSenderController: ModalViewController, TGModernGrowingDelegate { return genericView.textView } - init(urls:[URL], account:Account, chatInteraction:ChatInteraction, asMedia:Bool = true) { - self.urls = urls - self.account = account - self.isNeedAsMedia = asMedia + private let asMedia: Bool + private let attributedString: NSAttributedString? + init(urls:[URL], chatInteraction:ChatInteraction, asMedia:Bool = true, attributedString: NSAttributedString? = nil) { + + let filtred = urls.filter { url in + return FileManager.default.fileExists(atPath: url.path) + }.uniqueElements + + self._urls = filtred + + self.attributedString = attributedString + let context = chatInteraction.context + self.asMedia = asMedia + self.context = context + self.emoji = EmojiViewController(context) + + + + self.contextChatInteraction = ChatInteraction(chatLocation: chatInteraction.chatLocation, context: context) + + inputContextHelper = InputContextHelper(chatInteraction: contextChatInteraction) self.chatInteraction = chatInteraction - super.init(frame:NSMakeRect(0,0,350,350)) + super.init(frame:NSMakeRect(0, 0, 320, mainWindow.frame.height - 80)) bar = .init(height: 0) + + + contextChatInteraction.movePeerToInput = { [weak self] peer in + if let strongSelf = self { + let string = strongSelf.genericView.textView.string() + let range = strongSelf.genericView.textView.selectedRange() + let textInputState = ChatTextInputState(inputText: string, selectionRange: range.min ..< range.max, attributes: chatTextAttributes(from: strongSelf.genericView.textView.attributedString())) + strongSelf.contextChatInteraction.update({$0.withUpdatedEffectiveInputState(textInputState)}) + if let (range, _, _) = textInputStateContextQueryRangeAndType(textInputState, includeContext: false) { + let inputText = textInputState.inputText + + let name:String = peer.addressName ?? peer.compactDisplayTitle + + let distance = inputText.distance(from: range.lowerBound, to: range.upperBound) + let replacementText = name + " " + + let atLength = peer.addressName != nil ? 0 : 1 + + let range = strongSelf.contextChatInteraction.appendText(replacementText, selectedRange: textInputState.selectionRange.lowerBound - distance - atLength ..< textInputState.selectionRange.upperBound) + + if peer.addressName == nil { + let state = strongSelf.contextChatInteraction.presentation.effectiveInput + var attributes = state.attributes + attributes.append(.uid(range.lowerBound ..< range.upperBound - 1, peer.id.id)) + let updatedState = ChatTextInputState(inputText: state.inputText, selectionRange: state.selectionRange, attributes: attributes) + strongSelf.contextChatInteraction.update({$0.withUpdatedEffectiveInputState(updatedState)}) + } + + let updatedText = strongSelf.contextChatInteraction.presentation.effectiveInput + + strongSelf.genericView.textView.setAttributedString(updatedText.attributedString, animated: true) + strongSelf.genericView.textView.setSelectedRange(NSMakeRange(updatedText.selectionRange.lowerBound, updatedText.selectionRange.lowerBound + updatedText.selectionRange.upperBound)) + } + } + } + + self.contextChatInteraction.add(observer: self) + self.chatInteraction.add(observer: self) } + func showEmoji(for control: Control) { + showPopover(for: control, with: emoji) + } func textViewHeightChanged(_ height: CGFloat, animated: Bool) { - // genericView.tableView.change(size: NSMakeSize(frame.width, frame.height - height), animated: animated) - modal?.resize(with:NSMakeSize(genericView.frame.width, min(mainWindow.frame.height - 80, genericView.tableView.listHeight + genericView.textView.frame.height)), animated: animated) - genericView.textView._change(pos: NSMakePoint(genericView.textView.frame.minX, frame.height - genericView.textView.frame.height), animated: animated) + updateSize(frame.width, animated: animated) } func textViewEnterPressed(_ event: NSEvent) -> Bool { @@ -233,28 +1383,219 @@ class PreviewSenderController: ModalViewController, TGModernGrowingDelegate { return false } + func textViewTextDidChange(_ string: String) { + if FastSettings.isPossibleReplaceEmojies { + let previousString = contextChatInteraction.presentation.effectiveInput.inputText + + if previousString != string { + let difference = string.replacingOccurrences(of: previousString, with: "") + if difference.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty { + let replacedEmojies = string.stringEmojiReplacements + if string != replacedEmojies { + self.genericView.textView.setString(replacedEmojies) + } + } + } + + } + + let attributed = genericView.textView.attributedString() + let range = self.genericView.textView.selectedRange() + let state = ChatTextInputState(inputText: attributed.string, selectionRange: range.location ..< range.location + range.length, attributes: chatTextAttributes(from: attributed)) + contextChatInteraction.update({$0.withUpdatedEffectiveInputState(state)}) + } + + func isEqual(to other: Notifable) -> Bool { + return false + } + + func notify(with value: Any, oldValue: Any, animated: Bool) { + if let value = value as? PreviewContextState, let oldValue = oldValue as? PreviewContextState { + if value.inputQueryResult != oldValue.inputQueryResult { + self.updateSize(frame.width, animated: animated) + inputContextHelper.context(with: value.inputQueryResult, for: self.genericView, relativeView: self.genericView.forHelperView, animated: animated) + } + } else if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState { + if value == self.contextChatInteraction.presentation { + if value.effectiveInput != oldValue.effectiveInput { + updateInput(value, prevState: oldValue, animated) + } + } else if value == self.chatInteraction.presentation { + if value.slowMode != oldValue.slowMode { + let urls = self.urls + self.urls = urls + } + } + } + } + + private func updateInput(_ state:ChatPresentationInterfaceState, prevState: ChatPresentationInterfaceState, _ animated:Bool = true) -> Void { + let textView = genericView.textView + + if textView.string() != state.effectiveInput.inputText || state.effectiveInput.attributes != prevState.effectiveInput.attributes { + textView.animates = false + textView.setAttributedString(state.effectiveInput.attributedString, animated:animated) + textView.animates = true + } + let range = NSMakeRange(state.effectiveInput.selectionRange.lowerBound, state.effectiveInput.selectionRange.upperBound - state.effectiveInput.selectionRange.lowerBound) + if textView.selectedRange().location != range.location || textView.selectedRange().length != range.length { + textView.setSelectedRange(range) + } + textViewTextDidChangeSelectedRange(range) } + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + let animated: Bool = true + let string = genericView.textView.string() + + if let peer = chatInteraction.peer, !string.isEmpty, let (possibleQueryRange, possibleTypes, _) = textInputStateContextQueryRangeAndType(ChatTextInputState(inputText: string, selectionRange: range.min ..< range.max, attributes: []), includeContext: false) { + + if (possibleTypes.contains(.mention) && (peer.isGroup || peer.isSupergroup)) || possibleTypes.contains(.emoji) || possibleTypes.contains(.emojiFast) { + let query = String(string[possibleQueryRange]) + if let (updatedContextQueryState, updatedContextQuerySignal) = chatContextQueryForSearchMention(peer: peer, possibleTypes.contains(.emoji) ? .emoji(query, firstWord: false) : possibleTypes.contains(.emojiFast) ? .emoji(query, firstWord: true) : .mention(query: query, includeRecent: false), currentQuery: self.contextQueryState?.0, context: context, filter: .filterSelf(includeNameless: true, includeInlineBots: false)) { + self.contextQueryState?.1.dispose() + var inScope = true + var inScopeResult: ((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?)? + self.contextQueryState = (updatedContextQueryState, (updatedContextQuerySignal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + if Thread.isMainThread && inScope { + inScope = false + inScopeResult = result + } else { + strongSelf.inputInteraction.update(animated: animated, { + $0.updatedInputQueryResult { previousResult in + return result(previousResult) + } + }) + + } + } + })) + inScope = false + if let inScopeResult = inScopeResult { + inputInteraction.update(animated: animated, { + $0.updatedInputQueryResult { previousResult in + return inScopeResult(previousResult) + } + }) + } + } + } else { + inputInteraction.update(animated: animated, { + $0.updatedInputQueryResult { _ in + return nil + } + }) + } + + + } else { + inputInteraction.update(animated: animated, { + $0.updatedInputQueryResult { _ in + return nil + } + }) + } + + let attributed = self.genericView.textView.attributedString() + + let state = ChatTextInputState(inputText: attributed.string, selectionRange: range.location ..< range.location + range.length, attributes: chatTextAttributes(from: attributed)) + contextChatInteraction.update({$0.withUpdatedEffectiveInputState(state)}) + + } + + func textViewDidReachedLimit(_ textView: Any) { + genericView.textView.shake() + } + + func canTransformInputText() -> Bool { + return true + } + + + func makeUrl(of range: NSRange) { + guard range.min != range.max, let window = window else { + return + } + var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) + let defaultTag: TGInputTextTag? = genericView.textView.attributedString().attribute(NSAttributedString.Key(rawValue: TGCustomLinkAttributeName), at: range.location, effectiveRange: &effectiveRange) as? TGInputTextTag + + let defaultUrl = defaultTag?.attachment as? String + + if defaultUrl == nil { + effectiveRange = range + } + if effectiveRange.location == NSNotFound { + effectiveRange = range + } + + showModal(with: InputURLFormatterModalController(string: self.genericView.textView.string().nsstring.substring(with: effectiveRange), defaultUrl: defaultUrl, completion: { [weak self] url in + self?.genericView.textView.addLink(url, range: effectiveRange) + }), for: window) } func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { - return false + + let result = InputPasteboardParser.canProccessPasteboard(pasteboard) + + if let data = pasteboard.data(forType: .rtfd) ?? pasteboard.data(forType: .rtf) { + if let attributed = (try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtfd], documentAttributes: nil)) ?? (try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)) { + + let (attributed, attachments) = attributed.applyRtf() + let current = genericView.textView.attributedString().copy() as! NSAttributedString + let currentRange = genericView.textView.selectedRange() + let (attributedString, range) = current.appendAttributedString(attributed.attributedSubstring(from: NSMakeRange(0, min(Int(self.maxCharactersLimit(genericView.textView)), attributed.length))), selectedRange: currentRange) + let item = SimpleUndoItem(attributedString: current, be: attributedString, wasRange: currentRange, be: range) + genericView.textView.addSimpleItem(item) + + if !attachments.isEmpty { + pasteDisposable.set((prepareTextAttachments(attachments) |> deliverOnMainQueue).start(next: { [weak self] urls in + if !urls.isEmpty { + self?.insertAdditionUrls?(urls) + } + })) + } + return true + } + } + + if !result { + self.pasteDisposable.set(InputPasteboardParser.getPasteboardUrls(pasteboard).start(next: { [weak self] urls in + self?.insertAdditionUrls?(urls) + })) + } + + return !result + } + + func copyText(withRTF rtf: NSAttributedString!) -> Bool { + return globalLinkExecutor.copyAttributedString(rtf) } - func textViewSize() -> NSSize { - return NSMakeSize(frame.width - 40, genericView.textView.frame.height) + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return NSMakeSize(textView.frame.width, textView.frame.height) } func textViewIsTypingEnabled() -> Bool { return true } - func maxCharactersLimit() -> Int32 { - return 200 + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return ChatPresentationInterfaceState.maxInput + } + + override func viewClass() -> AnyClass { + return PreviewSenderView.self + } + + + + override func didResizeView(_ size: NSSize, animated: Bool) { + self.genericView.updateHeight(self.genericView.textView.frame.height, animated) } } diff --git a/Telegram-Mac/PreviewSenderItems.swift b/Telegram-Mac/PreviewSenderItems.swift deleted file mode 100644 index 237289dfd3..0000000000 --- a/Telegram-Mac/PreviewSenderItems.swift +++ /dev/null @@ -1,334 +0,0 @@ -// -// PreviewSenderItems.swift -// TelegramMac -// -// Created by keepcoder on 11/01/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import TGUIKit -import SwiftSignalKitMac -class PreviewDocumentRowItem: TableRowItem { - let url:URL - let account:Account - let thumb:CGImage - let name:(TextNodeLayout, TextNode) - init(_ initialSize:NSSize, url:URL, account:Account) { - self.url = url - self.thumb = extensionImage(fileExtension: url.pathExtension.isEmpty ? "F" : url.pathExtension)! - self.account = account - self.name = TextNode.layoutText(maybeNode: nil, .initialize(string: url.path.nsstring.lastPathComponent, color: theme.colors.text, font: .normal(.text)), nil, 1, .end, NSMakeSize(initialSize.width - (30 + 40 + 10 + 30), 20), nil, false, .left) - super.init(initialSize) - } - private let _stableId = Int64(arc4random()) - override var stableId: AnyHashable { - return _stableId - } - - override var height: CGFloat { - return 100 - } - - override func viewClass() -> AnyClass { - return PreviewDocumentRowView.self - } -} - -class PreviewDocumentRowView : TableRowView { - - var imageView:ImageView = ImageView() - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(imageView) - } - - - override func set(item: TableRowItem, animated: Bool) { - super.set(item: item,animated:animated) - - if let item = item as? PreviewDocumentRowItem { - imageView.image = item.thumb - } - - } - - override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - - if let item = item as? PreviewDocumentRowItem { - let f = focus(item.name.0.size) - item.name.1.draw(NSMakeRect(80, f.minY, item.name.0.size.width, item.name.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } - } - - override func layout() { - super.layout() - if let item = item as? PreviewDocumentRowItem { - imageView.setFrameSize(item.thumb.backingSize) - imageView.centerY(x:30) - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - - - - -class PreviewThumbRowItem :TableRowItem { - let url:URL - let account:Account - let thumbSize:NSSize - init(_ initialSize:NSSize, url:URL, account:Account) { - self.url = url - self.account = account - self.thumbSize = NSImage(contentsOf: url)?.size ?? NSZeroSize - - super.init(initialSize) - } - private let _stableId = Int64(arc4random()) - override var stableId: AnyHashable { - return _stableId - } - - override var height: CGFloat { - return 140 - } - - override func viewClass() -> AnyClass { - return PreviewThumbRowView.self - } -} - -class PreviewThumbRowView : TableRowView { - - var imageView:TransformImageView = TransformImageView() - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(imageView) - } - - - - override func set(item: TableRowItem, animated: Bool) { - super.set(item: item,animated:animated) - - if let item = item as? PreviewThumbRowItem { - imageView.setSignal(account: item.account, signal: filethumb(with: item.url, account:item.account, scale: backingScaleFactor)) - } - - } - - override func layout() { - super.layout() - if let item = item as? PreviewThumbRowItem { - - let boundingSize = NSMakeSize(frame.size.width - 20, frame.size.height - 20) - - let imageSize = item.thumbSize.aspectFitted(boundingSize) - let arguments = TransformImageArguments(corners: ImageCorners(radius:.cornerRadius), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) - - imageView.setFrameSize(arguments.imageSize) - imageView.center() - imageView.set(arguments: arguments) - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - - -//enum MultiplePreviewSetting { -// case files -// case images -// case mixed -//} -// -//class MultiplePreviewRowItem :TableRowItem { -// let urls:[URL] -// let account:Account -// let textLayout:TextViewLayout -// init(_ initialSize:NSSize, urls:[URL], options:[PreviewOptions], account:Account, onExpand:@escaping()->Void) { -// self.urls = urls -// self.account = account -// -// let text:String -// if options.contains(.mixed) { -// text = tr(.previewSenderSendMediaFilesCountable(urls.count)) -// } else if options.contains(.image) { -// text = tr(.previewSenderSendImagesCountable(urls.count)) -// } else if options.contains(.video) { -// text = tr(.previewSenderSendVideosCountable(urls.count)) -// } else { -// text = tr(.previewSenderSendFilesCountable(urls.count)) -// } -// // let text:String = localizedString(localizedKey, countable:urls.count) -// let attr = NSMutableAttributedString() -// _ = attr.append(string: text, color: .text, font: .normal(.title)) -// _ = attr.append(string: " (", color: .text, font: .normal(.title)) -// let range = attr.append(string: tr(.previewSenderExpandItems), color: .link, font: .normal(.title)) -// -// attr.add(link: "expand", for: range) -// _ = attr.append(string: ")", color: .text, font: .normal(.title)) -// textLayout = TextViewLayout(attr) -// textLayout.measure(width: initialSize.width - 60) -// textLayout.interactions = TextViewInteractions(processURL: { (any) in -// onExpand() -// }) -// super.init(initialSize) -// } -// -// override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { -// textLayout.measure(width: width - 60) -// return super.makeSize(width, oldWidth: oldWidth) -// } -// -// private let _stableId = Int64(arc4random()) -// override var stableId: AnyHashable { -// return _stableId -// } -// -// override var height: CGFloat { -// return 60 -// } -// -// override func viewClass() -> AnyClass { -// return MultiplePreviewRowView.self -// } -//} -// -//class MultiplePreviewRowView : TableRowView { -// private let textView:TextView = TextView() -// required init(frame frameRect: NSRect) { -// super.init(frame: frameRect) -// addSubview(textView) -// } -// -// override func layout() { -// super.layout() -// if let item = item as? MultiplePreviewRowItem { -// textView.update(item.textLayout) -// textView.centerY(x: 30) -// } -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -//} - - -class ExpandedPreviewRowItem : TableRowItem { - fileprivate let onDelete:(ExpandedPreviewRowItem)->Void - fileprivate let url:URL - fileprivate let textLayout:TextViewLayout - fileprivate let thumbSize:NSSize - fileprivate let account:Account - fileprivate let thumb:CGImage? - init(_ initialSize: NSSize, account:Account, url:URL, onDelete:@escaping(ExpandedPreviewRowItem)->Void) { - self.onDelete = onDelete - self.url = url - self.account = account - self.textLayout = TextViewLayout(.initialize(string: url.path.nsstring.lastPathComponent, color: theme.colors.grayText, font: NSFont.normal(FontSize.text)), maximumNumberOfLines: 2, truncationType: .middle) - self.textLayout.measure(width: initialSize.width - 60 - 20 - 40 - 10 - 10) - let mimeType = MIMEType(url.pathExtension.lowercased()) - if mimeType.hasPrefix("image"), let image = NSImage(contentsOf: url) { - self.thumbSize = image.size.aspectFilled(NSMakeSize(40, 40)) - self.thumb = nil - } else { - self.thumbSize = NSMakeSize(40, 40) - self.thumb = extensionImage(fileExtension: url.path.nsstring.pathExtension.lowercased())! - } - - super.init(initialSize) - } - - override var stableId: AnyHashable { - return url.hashValue - } - - override var height: CGFloat { - return 50 //max(thumbSize.height + 20,50) - } - - override func viewClass() -> AnyClass { - return ExpandedPreviewRowView.self - } -} - -class ExpandedPreviewRowView : TableRowView { - private let textView:TextView = TextView() - private let deleteControl:ImageButton = ImageButton() - private let imageView:TransformImageView = TransformImageView() - private let thumbView:ImageView = ImageView() - private var arguments:TransformImageArguments? - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - - deleteControl.autohighlight = false - - - deleteControl.set(handler: { [weak self] _ in - if let item = self?.item as? ExpandedPreviewRowItem { - item.onDelete(item) - } - }, for: .Click) - textView.isSelectable = false - addSubview(textView) - addSubview(deleteControl) - addSubview(imageView) - addSubview(thumbView) - } - - override func layout() { - super.layout() - if let item = item as? ExpandedPreviewRowItem { - deleteControl.centerY(x:30) - - - let arguments = TransformImageArguments(corners: ImageCorners(radius: 20), imageSize: item.thumbSize, boundingSize: NSMakeSize(40, 40), intrinsicInsets: NSEdgeInsets()) - - - imageView.setFrameSize(arguments.boundingSize) - imageView.set(arguments: arguments) - - thumbView.setFrameSize(arguments.boundingSize) - thumbView.centerY(x:deleteControl.frame.maxX + 10) - imageView.centerY(x:deleteControl.frame.maxX + 10 + floorToScreenPixels((40 - imageView.frame.width)/2)) - - textView.update(item.textLayout) - textView.centerY(x: deleteControl.frame.maxX + 40 + 20) - } - } - - override func set(item: TableRowItem, animated: Bool) { - super.set(item: item, animated: animated) - if let item = item as? ExpandedPreviewRowItem { - deleteControl.set(image: theme.icons.deleteItem, for: .Normal) - deleteControl.sizeToFit() - textView.backgroundColor = theme.colors.background - imageView.dispose() - if let thumb = item.thumb { - thumbView.image = thumb - } else { - imageView.setSignal(account: item.account, signal: filethumb(with: item.url, account:item.account, scale: backingScaleFactor)) - } - thumbView.isHidden = item.thumb == nil - imageView.isHidden = !thumbView.isHidden - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - -} diff --git a/Telegram-Mac/PrivacyAndSecurityViewController.swift b/Telegram-Mac/PrivacyAndSecurityViewController.swift index d08c26c4b7..94bc0dabbe 100644 --- a/Telegram-Mac/PrivacyAndSecurityViewController.swift +++ b/Telegram-Mac/PrivacyAndSecurityViewController.swift @@ -8,23 +8,110 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + +/* + + struct InteractiveEmojiConfiguration : Equatable { + static var defaultValue: InteractiveEmojiConfiguration { + return InteractiveEmojiConfiguration(emojis: [], confettiCompitable: [:]) + } + + let emojis: [String] + private let confettiCompitable: [String: InteractiveEmojiConfetti] + + fileprivate init(emojis: [String], confettiCompitable: [String: InteractiveEmojiConfetti]) { + self.emojis = emojis.map { $0.fixed } + self.confettiCompitable = confettiCompitable + } + + static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration { + if let data = appConfiguration.data, let value = data["emojies_send_dice"] as? [String] { + let dict:[String : Any]? = data["emojies_send_dice_success"] as? [String:Any] + + var confetti:[String: InteractiveEmojiConfetti] = [:] + if let dict = dict { + for (key, value) in dict { + if let data = value as? [String: Any], let frameStart = data["frame_start"] as? Double, let value = data["value"] as? Double { + confetti[key] = InteractiveEmojiConfetti(playAt: Int32(frameStart), value: Int32(value)) + } + } + } + return InteractiveEmojiConfiguration(emojis: value, confettiCompitable: confetti) + } else { + return .defaultValue + } + } + + func playConfetti(_ emoji: String) -> InteractiveEmojiConfetti? { + return confettiCompitable[emoji] + } + } + */ + +private struct AutoarchiveConfiguration : Equatable { + let autoarchive_setting_available: Bool + init(autoarchive_setting_available: Bool) { + self.autoarchive_setting_available = autoarchive_setting_available + } + static func with(appConfiguration: AppConfiguration) -> AutoarchiveConfiguration { + return AutoarchiveConfiguration(autoarchive_setting_available: appConfiguration.data?["autoarchive_setting_available"] as? Bool ?? false) + } +} + + +enum PrivacyAndSecurityEntryTag: ItemListItemTag { + case accountTimeout + case topPeers + case cloudDraft + case autoArchive + func isEqual(to other: ItemListItemTag) -> Bool { + if let other = other as? PrivacyAndSecurityEntryTag, self == other { + return true + } else { + return false + } + } + + fileprivate var stableId: AnyHashable { + switch self { + case .accountTimeout: + return PrivacyAndSecurityEntry.accountTimeout(sectionId: 0, "", viewType: .singleItem).stableId + case .topPeers: + return PrivacyAndSecurityEntry.togglePeerSuggestions(sectionId: 0, enabled: false, viewType: .singleItem).stableId + case .cloudDraft: + return PrivacyAndSecurityEntry.clearCloudDrafts(sectionId: 0, viewType: .singleItem).stableId + case .autoArchive: + return PrivacyAndSecurityEntry.autoArchiveToggle(sectionId: 0, value: false, viewType: .singleItem).stableId + } + } +} private final class PrivacyAndSecurityControllerArguments { - let account: Account + let context: AccountContext let openBlockedUsers: () -> Void let openLastSeenPrivacy: () -> Void let openGroupsPrivacy: () -> Void let openVoiceCallPrivacy: () -> Void + let openProfilePhotoPrivacy: () -> Void + let openForwardPrivacy: () -> Void + let openPhoneNumberPrivacy: () -> Void let openPasscode: () -> Void - let openTwoStepVerification: () -> Void - let openActiveSessions: () -> Void + let openTwoStepVerification: (TwoStepVeriticationAccessConfiguration?) -> Void + let openActiveSessions: ([RecentAccountSession]?) -> Void + let openWebAuthorizations: () -> Void let setupAccountAutoremove: () -> Void let openProxySettings:() ->Void - init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping () -> Void, openActiveSessions: @escaping () -> Void, setupAccountAutoremove: @escaping () -> Void, openProxySettings:@escaping() ->Void) { - self.account = account + let togglePeerSuggestions:(Bool)->Void + let clearCloudDrafts: () -> Void + let toggleSensitiveContent:(Bool)->Void + let toggleSecretChatWebPreview: (Bool)->Void + let toggleAutoArchive: (Bool)->Void + init(context: AccountContext, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping() -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVeriticationAccessConfiguration?) -> Void, openActiveSessions: @escaping ([RecentAccountSession]?) -> Void, openWebAuthorizations: @escaping() -> Void, setupAccountAutoremove: @escaping () -> Void, openProxySettings:@escaping() ->Void, togglePeerSuggestions:@escaping(Bool)->Void, clearCloudDrafts: @escaping() -> Void, toggleSensitiveContent: @escaping(Bool)->Void, toggleSecretChatWebPreview: @escaping(Bool)->Void, toggleAutoArchive: @escaping(Bool)->Void) { + self.context = context self.openBlockedUsers = openBlockedUsers self.openLastSeenPrivacy = openLastSeenPrivacy self.openGroupsPrivacy = openGroupsPrivacy @@ -32,100 +119,202 @@ private final class PrivacyAndSecurityControllerArguments { self.openPasscode = openPasscode self.openTwoStepVerification = openTwoStepVerification self.openActiveSessions = openActiveSessions + self.openWebAuthorizations = openWebAuthorizations self.setupAccountAutoremove = setupAccountAutoremove self.openProxySettings = openProxySettings + self.togglePeerSuggestions = togglePeerSuggestions + self.clearCloudDrafts = clearCloudDrafts + self.openProfilePhotoPrivacy = openProfilePhotoPrivacy + self.openForwardPrivacy = openForwardPrivacy + self.openPhoneNumberPrivacy = openPhoneNumberPrivacy + self.toggleSensitiveContent = toggleSensitiveContent + self.toggleSecretChatWebPreview = toggleSecretChatWebPreview + self.toggleAutoArchive = toggleAutoArchive } } private enum PrivacyAndSecurityEntry: Comparable, Identifiable { case privacyHeader(sectionId:Int) - case blockedPeers(sectionId:Int) - case lastSeenPrivacy(sectionId: Int, String) - case groupPrivacy(sectionId: Int, String) - case voiceCallPrivacy(sectionId: Int, String) + case blockedPeers(sectionId:Int, Int?, viewType: GeneralViewType) + case phoneNumberPrivacy(sectionId: Int, String, viewType: GeneralViewType) + case lastSeenPrivacy(sectionId: Int, String, viewType: GeneralViewType) + case groupPrivacy(sectionId: Int, String, viewType: GeneralViewType) + case profilePhotoPrivacy(sectionId: Int, String, viewType: GeneralViewType) + case forwardPrivacy(sectionId: Int, String, viewType: GeneralViewType) + case voiceCallPrivacy(sectionId: Int, String, viewType: GeneralViewType) case securityHeader(sectionId:Int) - case passcode(sectionId:Int) - case twoStepVerification(sectionId:Int) - case activeSessions(sectionId:Int) + case passcode(sectionId:Int, enabled: Bool, viewType: GeneralViewType) + case twoStepVerification(sectionId:Int, configuration: TwoStepVeriticationAccessConfiguration?, viewType: GeneralViewType) + case activeSessions(sectionId:Int, [RecentAccountSession]?, viewType: GeneralViewType) + case webAuthorizationsHeader(sectionId: Int) + case webAuthorizations(sectionId:Int, viewType: GeneralViewType) case accountHeader(sectionId:Int) - case accountTimeout(sectionId: Int, String) + case accountTimeout(sectionId: Int, String, viewType: GeneralViewType) case accountInfo(sectionId:Int) case proxyHeader(sectionId:Int) - case proxySettings(sectionId:Int, String) - case section(sectionId:Int) + case proxySettings(sectionId:Int, String, viewType: GeneralViewType) + case togglePeerSuggestions(sectionId: Int, enabled: Bool, viewType: GeneralViewType) + case togglePeerSuggestionsDesc(sectionId: Int) + case sensitiveContentHeader(sectionId: Int) + case autoArchiveToggle(sectionId: Int, value: Bool?, viewType: GeneralViewType) + case autoArchiveDesc(sectionId: Int) + case autoArchiveHeader(sectionId: Int) + case sensitiveContentToggle(sectionId: Int, value: Bool?, viewType: GeneralViewType) + case sensitiveContentDesc(sectionId: Int) + case clearCloudDraftsHeader(sectionId: Int) + case clearCloudDrafts(sectionId: Int, viewType: GeneralViewType) + + case secretChatWebPreviewHeader(sectionId: Int) + case secretChatWebPreviewToggle(sectionId: Int, value: Bool?, viewType: GeneralViewType) + case secretChatWebPreviewDesc(sectionId: Int) + case section(sectionId:Int) + var sectionId: Int { switch self { case let .privacyHeader(sectionId): return sectionId - case let .blockedPeers(sectionId): + case let .blockedPeers(sectionId, _, _): + return sectionId + case let .phoneNumberPrivacy(sectionId, _, _): + return sectionId + case let .lastSeenPrivacy(sectionId, _, _): return sectionId - case let .lastSeenPrivacy(sectionId, _): + case let .groupPrivacy(sectionId, _, _): return sectionId - case let .groupPrivacy(sectionId, _): + case let .profilePhotoPrivacy(sectionId, _, _): return sectionId - case let .voiceCallPrivacy(sectionId, _): + case let .forwardPrivacy(sectionId, _, _): + return sectionId + case let .voiceCallPrivacy(sectionId, _, _): return sectionId case let .securityHeader(sectionId): return sectionId - case let .passcode(sectionId): + case let .passcode(sectionId, _, _): + return sectionId + case let .twoStepVerification(sectionId, _, _): + return sectionId + case let .activeSessions(sectionId, _, _): + return sectionId + case let .webAuthorizationsHeader(sectionId): + return sectionId + case let .webAuthorizations(sectionId, _): + return sectionId + case let .autoArchiveHeader(sectionId): return sectionId - case let .twoStepVerification(sectionId): + case let .autoArchiveToggle(sectionId, _, _): return sectionId - case let .activeSessions(sectionId): + case let .autoArchiveDesc(sectionId): return sectionId case let .accountHeader(sectionId): return sectionId - case let .accountTimeout(sectionId, _): + case let .accountTimeout(sectionId, _, _): return sectionId case let .accountInfo(sectionId): return sectionId - case let .proxySettings(sectionId, _): + case let .togglePeerSuggestions(sectionId, _, _): + return sectionId + case let .togglePeerSuggestionsDesc(sectionId): + return sectionId + case let .clearCloudDraftsHeader(sectionId): + return sectionId + case let .clearCloudDrafts(sectionId, _): return sectionId case let .proxyHeader(sectionId): return sectionId + case let .proxySettings(sectionId, _, _): + return sectionId + case let .sensitiveContentHeader(sectionId): + return sectionId + case let .sensitiveContentToggle(sectionId, _, _): + return sectionId + case let .sensitiveContentDesc(sectionId): + return sectionId + case let .secretChatWebPreviewHeader(sectionId): + return sectionId + case let .secretChatWebPreviewToggle(sectionId, _, _): + return sectionId + case let .secretChatWebPreviewDesc(sectionId): + return sectionId case let .section(sectionId): return sectionId } } + var stableId:Int { switch self { - case .privacyHeader: - return 0 case .blockedPeers: + return 0 + case .activeSessions: return 1 - case .lastSeenPrivacy: + case .passcode: return 2 - case .groupPrivacy: + case .twoStepVerification: return 3 - case .voiceCallPrivacy: + case .privacyHeader: return 4 - case .securityHeader: + case .phoneNumberPrivacy: return 5 - case .passcode: + case .lastSeenPrivacy: return 6 - case .twoStepVerification: + case .groupPrivacy: return 7 - case .activeSessions: + case .voiceCallPrivacy: return 8 - case .accountHeader: + case .forwardPrivacy: return 9 - case .accountTimeout: + case .profilePhotoPrivacy: return 10 - case .accountInfo: + case .securityHeader: return 11 - case .proxyHeader: + case .autoArchiveHeader: return 12 - case .proxySettings: + case .autoArchiveToggle: return 13 + case .autoArchiveDesc: + return 14 + case .accountHeader: + return 15 + case .accountTimeout: + return 16 + case .accountInfo: + return 17 + case .webAuthorizationsHeader: + return 18 + case .webAuthorizations: + return 19 + case .proxyHeader: + return 20 + case .proxySettings: + return 21 + case .togglePeerSuggestions: + return 22 + case .togglePeerSuggestionsDesc: + return 23 + case .clearCloudDraftsHeader: + return 24 + case .clearCloudDrafts: + return 25 + case .sensitiveContentHeader: + return 26 + case .sensitiveContentToggle: + return 27 + case .sensitiveContentDesc: + return 28 + case .secretChatWebPreviewHeader: + return 29 + case .secretChatWebPreviewToggle: + return 30 + case .secretChatWebPreviewDesc: + return 31 case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId } } - - + + private var stableIndex:Int { switch self { case let .section(sectionId): @@ -135,171 +324,372 @@ private enum PrivacyAndSecurityEntry: Comparable, Identifiable { } } - - static func ==(lhs: PrivacyAndSecurityEntry, rhs: PrivacyAndSecurityEntry) -> Bool { - switch lhs { - case .privacyHeader, .blockedPeers, .securityHeader, .passcode, .twoStepVerification, .activeSessions, .accountHeader, .accountInfo, .proxyHeader, .section: - return lhs.stableId == rhs.stableId && lhs.sectionId == rhs.sectionId - case let .lastSeenPrivacy(sectionId, text): - if case .lastSeenPrivacy(sectionId, text) = rhs { - return true - } else { - return false - } - case let .groupPrivacy(sectionId, text): - if case .groupPrivacy(sectionId, text) = rhs { - return true - } else { - return false - } - case let .proxySettings(sectionId, text): - if case .proxySettings(sectionId, text) = rhs { - return true - } else { - return false - } - case let .voiceCallPrivacy(sectionId, text): - if case .voiceCallPrivacy(sectionId, text) = rhs { - return true - } else { - return false - } - case let .accountTimeout(sectionId, text): - if case .accountTimeout(sectionId, text) = rhs { - return true - } else { - return false - } - } - } - + static func <(lhs: PrivacyAndSecurityEntry, rhs: PrivacyAndSecurityEntry) -> Bool { return lhs.stableIndex < rhs.stableIndex } func item(_ arguments: PrivacyAndSecurityControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { case .privacyHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.privacySettingsPrivacyHeader), drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case .blockedPeers: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsBlockedUsers), type: .next, action: { + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsPrivacyHeader, viewType: .textTopItem) + case let .blockedPeers(_, count, viewType): + let text: String + if let count = count, count > 0 { + text = L10n.privacyAndSecurityBlockedUsers("\(count)") + } else { + text = "" + } + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsBlockedUsers, icon: theme.icons.privacySettings_blocked, type: .nextContext(text), viewType: viewType, action: { arguments.openBlockedUsers() }) - case let .lastSeenPrivacy(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsLastSeen), type: .next, action: { + case let .phoneNumberPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsPhoneNumber, type: .nextContext(text), viewType: viewType, action: { + arguments.openPhoneNumberPrivacy() + }) + case let .lastSeenPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsLastSeen, type: .nextContext(text), viewType: viewType, action: { arguments.openLastSeenPrivacy() }) - case let .groupPrivacy(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsGroups), type: .next, action: { + case let .groupPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsGroups, type: .nextContext(text), viewType: viewType, action: { arguments.openGroupsPrivacy() }) - case let .voiceCallPrivacy(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsVoiceCalls), type: .next, action: { + case let .profilePhotoPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsProfilePhoto, type: .nextContext(text), viewType: viewType, action: { + arguments.openProfilePhotoPrivacy() + }) + case let .forwardPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsForwards, type: .nextContext(text), viewType: viewType, action: { + arguments.openForwardPrivacy() + }) + case let .voiceCallPrivacy(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsVoiceCalls, type: .nextContext(text), viewType: viewType, action: { arguments.openVoiceCallPrivacy() }) case .securityHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.privacySettingsSecurityHeader), drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case .passcode: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsPasscode), action: { + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsSecurityHeader, viewType: .textTopItem) + case let .passcode(_, enabled, viewType): + let desc = enabled ? L10n.privacyAndSecurityItemOn : L10n.privacyAndSecurityItemOff + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsPasscode, icon: theme.icons.privacySettings_passcode, type: .nextContext(desc), viewType: viewType, action: { arguments.openPasscode() }) - case .twoStepVerification: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsTwoStepVerification), action: { - arguments.openTwoStepVerification() + case let .twoStepVerification(_, configuration, viewType): + let desc: String + if let configuration = configuration { + switch configuration { + case .set: + desc = L10n.privacyAndSecurityItemOn + case .notSet: + desc = L10n.privacyAndSecurityItemOff + } + } else { + desc = "" + } + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsTwoStepVerification, icon: theme.icons.privacySettings_twoStep, type: .nextContext(desc), viewType: viewType, action: { + arguments.openTwoStepVerification(configuration) }) - case .activeSessions: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsActiveSessions), action: { - arguments.openActiveSessions() + case let .activeSessions(_, sessions, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsActiveSessions, icon: theme.icons.privacySettings_activeSessions, type: .nextContext(sessions != nil ? "\(sessions!.count)" : ""), viewType: viewType, action: { + arguments.openActiveSessions(sessions) + }) + case .webAuthorizationsHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecurityWebAuthorizationHeader, viewType: .textTopItem) + case let .webAuthorizations(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.telegramWebSessionsController, viewType: viewType, action: { + arguments.openWebAuthorizations() }) case .accountHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: "tr(.privacySettingsDeleteAccountHeader)", drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case let .accountTimeout(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: "tr(.privacySettingsDeleteAccount)", action: { + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsDeleteAccountHeader, viewType: .textTopItem) + case let .accountTimeout(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsDeleteAccount, type: .context(text), viewType: viewType, action: { arguments.setupAccountAutoremove() }) case .accountInfo: - return GeneralTextRowItem(initialSize, stableId: stableId, text: "tr(.privacySettingsDeleteAccountDescription)") + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsDeleteAccountDescription, viewType: .textBottomItem) case .proxyHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.privacySettingsProxyHeader), drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case let .proxySettings(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsUseProxy), type: .context(stateback: { () -> String in - return text - }), action: { + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsProxyHeader, viewType: .textTopItem) + case let .proxySettings(_, text, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsUseProxy, type: .nextContext(text), viewType: viewType, action: { arguments.openProxySettings() }) - case .section : - return GeneralRowItem(initialSize, height:20, stableId: stableId) + case let .togglePeerSuggestions(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.suggestFrequentContacts, type: .switchable(enabled), viewType: viewType, action: { + if enabled { + confirm(for: mainWindow, information: L10n.suggestFrequentContactsAlert, successHandler: { _ in + arguments.togglePeerSuggestions(!enabled) + }) + } else { + arguments.togglePeerSuggestions(!enabled) + } + }, autoswitch: false) + case .togglePeerSuggestionsDesc: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.suggestFrequentContactsDesc, viewType: .textBottomItem) + case .clearCloudDraftsHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecurityClearCloudDraftsHeader, viewType: .textTopItem) + case let .clearCloudDrafts(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacyAndSecurityClearCloudDrafts, type: .none, viewType: viewType, action: { + arguments.clearCloudDrafts() + }) + case .autoArchiveHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecurityAutoArchiveHeader, viewType: .textTopItem) + case let .autoArchiveToggle(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacyAndSecurityAutoArchiveText, type: enabled != nil ? .switchable(enabled!) : .loading, viewType: viewType, action: { + if let enabled = enabled { + arguments.toggleAutoArchive(!enabled) + } else { + arguments.toggleAutoArchive(true) + } + }, autoswitch: true) + case .autoArchiveDesc: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecurityAutoArchiveDesc, viewType: .textBottomItem) + case .sensitiveContentHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecuritySensitiveHeader, viewType: .textTopItem) + case let .sensitiveContentToggle(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacyAndSecuritySensitiveText, type: enabled != nil ? .switchable(enabled!) : .loading, viewType: viewType, action: { + if let enabled = enabled { + arguments.toggleSensitiveContent(!enabled) + } + }, autoswitch: true) + case .sensitiveContentDesc: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecuritySensitiveDesc, viewType: .textBottomItem) + case .secretChatWebPreviewHeader: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecuritySecretChatWebPreviewHeader, viewType: .textTopItem) + case let .secretChatWebPreviewToggle(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacyAndSecuritySecretChatWebPreviewText, type: enabled != nil ? .switchable(enabled!) : .loading, viewType: viewType, action: { + if let enabled = enabled { + arguments.toggleSecretChatWebPreview(!enabled) + } + }, autoswitch: true) + case .secretChatWebPreviewDesc: + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacyAndSecuritySecretChatWebPreviewDesc, viewType: .textBottomItem) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + } + } +} + +func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> Int { + var result = 0 + for (_, peer) in peers { + result += peer.userCount + } + return result +} + + +private func stringForSelectiveSettings(settings: SelectivePrivacySettings) -> String { + switch settings { + case let .disableEveryone(enableFor): + if enableFor.isEmpty { + return L10n.privacySettingsControllerNobody + } else { + return L10n.privacySettingsLastSeenNobodyPlus("\(countForSelectivePeers(enableFor))") + } + case let .enableEveryone(disableFor): + if disableFor.isEmpty { + return L10n.privacySettingsControllerEverbody + } else { + return L10n.privacySettingsLastSeenEverybodyMinus("\(countForSelectivePeers(disableFor))") + } + case let .enableContacts(enableFor, disableFor): + if !enableFor.isEmpty && !disableFor.isEmpty { + return L10n.privacySettingsLastSeenContactsMinusPlus("\(countForSelectivePeers(enableFor))", "\(countForSelectivePeers(disableFor))") + } else if !enableFor.isEmpty { + return L10n.privacySettingsLastSeenContactsPlus("\(countForSelectivePeers(enableFor))") + } else if !disableFor.isEmpty { + return L10n.privacySettingsLastSeenContactsMinus("\(countForSelectivePeers(disableFor))") + } else { + return L10n.privacySettingsControllerMyContacts } } } private struct PrivacyAndSecurityControllerState: Equatable { + let updatingAccountTimeoutValue: Int32? + init() { + self.updatingAccountTimeoutValue = nil } - + + init(updatingAccountTimeoutValue: Int32?) { + self.updatingAccountTimeoutValue = updatingAccountTimeoutValue + } + static func ==(lhs: PrivacyAndSecurityControllerState, rhs: PrivacyAndSecurityControllerState) -> Bool { + if lhs.updatingAccountTimeoutValue != rhs.updatingAccountTimeoutValue { + return false + } + return true } + + func withUpdatedUpdatingAccountTimeoutValue(_ updatingAccountTimeoutValue: Int32?) -> PrivacyAndSecurityControllerState { + return PrivacyAndSecurityControllerState(updatingAccountTimeoutValue: updatingAccountTimeoutValue) + } } fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:PrivacyAndSecurityControllerArguments) -> TableUpdateTransition { - + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in return entry.entry.item(arguments, initialSize: initialSize) } - + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } -private func privacyAndSecurityControllerEntries(state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, proxy: ProxySettings?) -> [PrivacyAndSecurityEntry] { +private func privacyAndSecurityControllerEntries(state: PrivacyAndSecurityControllerState, contentConfiguration: ContentSettingsConfiguration?, privacySettings: AccountPrivacySettings?, webSessions: ([WebAuthorization], [PeerId : Peer])?, blockedState: BlockedPeersContextState, proxy: ProxySettings, recentPeers: RecentPeers, configuration: TwoStepVeriticationAccessConfiguration?, activeSessions: [RecentAccountSession]?, passcodeData: PostboxAccessChallengeData, context: AccountContext) -> [PrivacyAndSecurityEntry] { var entries: [PrivacyAndSecurityEntry] = [] - + var sectionId:Int = 1 entries.append(.section(sectionId: sectionId)) sectionId += 1 + + entries.append(.blockedPeers(sectionId: sectionId, blockedState.totalCount, viewType: .firstItem)) + // entries.append(.activeSessions(sectionId: sectionId, activeSessions, viewType: .innerItem)) - entries.append(.privacyHeader(sectionId: sectionId)) - entries.append(.blockedPeers(sectionId: sectionId)) - entries.append(.lastSeenPrivacy(sectionId: sectionId, "")) - entries.append(.groupPrivacy(sectionId: sectionId, "")) - entries.append(.voiceCallPrivacy(sectionId: sectionId, "")) + let hasPasscode: Bool + switch passcodeData { + case .none: + hasPasscode = false + default: + hasPasscode = true + } + entries.append(.passcode(sectionId: sectionId, enabled: hasPasscode, viewType: .innerItem)) + entries.append(.twoStepVerification(sectionId: sectionId, configuration: configuration, viewType: .lastItem)) + entries.append(.section(sectionId: sectionId)) sectionId += 1 + entries.append(.privacyHeader(sectionId: sectionId)) + if let privacySettings = privacySettings { + entries.append(.phoneNumberPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.phoneNumber), viewType: .firstItem)) + entries.append(.lastSeenPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.presence), viewType: .innerItem)) + entries.append(.groupPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.groupInvitations), viewType: .innerItem)) + entries.append(.voiceCallPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.voiceCalls), viewType: .innerItem)) + entries.append(.profilePhotoPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.profilePhoto), viewType: .innerItem)) + entries.append(.forwardPrivacy(sectionId: sectionId, stringForSelectiveSettings(settings: privacySettings.forwards), viewType: .lastItem)) + } else { + entries.append(.phoneNumberPrivacy(sectionId: sectionId, "", viewType: .firstItem)) + entries.append(.lastSeenPrivacy(sectionId: sectionId, "", viewType: .innerItem)) + entries.append(.groupPrivacy(sectionId: sectionId, "", viewType: .innerItem)) + entries.append(.voiceCallPrivacy(sectionId: sectionId, "", viewType: .innerItem)) + entries.append(.profilePhotoPrivacy(sectionId: sectionId, "", viewType: .innerItem)) + entries.append(.forwardPrivacy(sectionId: sectionId, "", viewType: .lastItem)) + } + + entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.securityHeader(sectionId: sectionId)) - entries.append(.passcode(sectionId: sectionId)) - entries.append(.twoStepVerification(sectionId: sectionId)) - entries.append(.activeSessions(sectionId: sectionId)) + let autoarchiveConfiguration = AutoarchiveConfiguration.with(appConfiguration: context.appConfiguration) + + + if autoarchiveConfiguration.autoarchive_setting_available { + entries.append(.autoArchiveHeader(sectionId: sectionId)) + entries.append(.autoArchiveToggle(sectionId: sectionId, value: privacySettings?.automaticallyArchiveAndMuteNonContacts, viewType: .singleItem)) + entries.append(.autoArchiveDesc(sectionId: sectionId)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + } + + entries.append(.accountHeader(sectionId: sectionId)) + + + if let privacySettings = privacySettings { + let value: Int32 + if let updatingAccountTimeoutValue = state.updatingAccountTimeoutValue { + value = updatingAccountTimeoutValue + } else { + value = privacySettings.accountRemovalTimeout + } + entries.append(.accountTimeout(sectionId: sectionId, timeIntervalString(Int(value)), viewType: .singleItem)) + + } else { + entries.append(.accountTimeout(sectionId: sectionId, "", viewType: .singleItem)) + } + entries.append(.accountInfo(sectionId: sectionId)) + + entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.proxyHeader(sectionId: sectionId)) - entries.append(.proxySettings(sectionId: sectionId, proxy != nil ? tr(.proxySettingsSocks5) : tr(.proxySettingsDisabled))) + if let contentConfiguration = contentConfiguration, contentConfiguration.canAdjustSensitiveContent { + #if !APP_STORE + entries.append(.sensitiveContentHeader(sectionId: sectionId)) + entries.append(.sensitiveContentToggle(sectionId: sectionId, value: contentConfiguration.sensitiveContentEnabled, viewType: .singleItem)) + entries.append(.sensitiveContentDesc(sectionId: sectionId)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + #endif + } -// entries.append(.section(sectionId: sectionId)) -// sectionId += 1 -// -// entries.append(.accountHeader(sectionId: sectionId)) -// entries.append(.accountTimeout(sectionId: sectionId, "")) -// entries.append(.accountInfo(sectionId: sectionId)) + + let enabled: Bool + switch recentPeers { + case .disabled: + enabled = false + case .peers: + enabled = true + } + + entries.append(.togglePeerSuggestions(sectionId: sectionId, enabled: enabled, viewType: .singleItem)) + entries.append(.togglePeerSuggestionsDesc(sectionId: sectionId)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + + entries.append(.clearCloudDraftsHeader(sectionId: sectionId)) + entries.append(.clearCloudDrafts(sectionId: sectionId, viewType: .singleItem)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + + if let webSessions = webSessions, !webSessions.0.isEmpty { + entries.append(.webAuthorizationsHeader(sectionId: sectionId)) + entries.append(.webAuthorizations(sectionId: sectionId, viewType: .singleItem)) + + if FastSettings.isSecretChatWebPreviewAvailable(for: context.account.id.int64) != nil { + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + } + } + if let value = FastSettings.isSecretChatWebPreviewAvailable(for: context.account.id.int64) { + entries.append(.secretChatWebPreviewHeader(sectionId: sectionId)) + entries.append(.secretChatWebPreviewToggle(sectionId: sectionId, value: value, viewType: .singleItem)) + entries.append(.secretChatWebPreviewDesc(sectionId: sectionId)) + } + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + return entries } + + + class PrivacyAndSecurityViewController: TableViewController { - private let initialSettings: Signal - -// override var removeAfterDisapper: Bool { -// return true -// } - + private let privacySettingsPromise = Promise<(AccountPrivacySettings?, ([WebAuthorization], [PeerId : Peer])?)>() + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + twoStepAccessConfiguration.set(twoStepVerificationConfiguration(account: context.account) |> map { TwoStepVeriticationAccessConfiguration(configuration: $0, password: nil)}) + activeSessions.set(requestRecentAccountSessions(account: context.account) |> map(Optional.init)) + } + + private let twoStepAccessConfiguration: Promise = Promise(nil) + private let activeSessions: Promise<[RecentAccountSession]?> = Promise(nil) + override func viewDidLoad() { super.viewDidLoad() + let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true) @@ -307,39 +697,44 @@ class PrivacyAndSecurityViewController: TableViewController { let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - + let actionsDisposable = DisposableSet() - let account = self.account - - let pushControllerImpl: ((ViewController) -> Void) = { [weak self] c in + let context = self.context + + let pushControllerImpl: (ViewController) -> Void = { [weak self] c in self?.navigationController?.push(c) } + + let settings:Signal = proxySettings(accountManager: context.sharedContext.accountManager) + let currentInfoDisposable = MetaDisposable() actionsDisposable.add(currentInfoDisposable) - - let privacySettingsPromise = Promise() - privacySettingsPromise.set(initialSettings) - - let arguments = PrivacyAndSecurityControllerArguments(account: account, openBlockedUsers: { [weak self] in - if let account = self?.account { - pushControllerImpl(BlockedPeersViewController(account)) + + let updateAccountTimeoutDisposable = MetaDisposable() + actionsDisposable.add(updateAccountTimeoutDisposable) + + let privacySettingsPromise = self.privacySettingsPromise + + let arguments = PrivacyAndSecurityControllerArguments(context: context, openBlockedUsers: { [weak self] in + if let context = self?.context { + pushControllerImpl(BlockedPeersViewController(context)) } }, openLastSeenPrivacy: { let signal = privacySettingsPromise.get() |> take(1) |> deliverOnMainQueue - currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in if let info = info { - pushControllerImpl(SelectivePrivacySettingsController(account: account, kind: .presence, current: info.presence, updated: { updated in + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .presence, current: info.presence, callSettings: nil, phoneDiscoveryEnabled: nil, updated: { updated, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } + |> filter { $0.0 != nil } |> take(1) |> deliverOnMainQueue - |> mapToSignal { value -> Signal in + |> mapToSignal { value, sessions -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, accountRemovalTimeout: value.accountRemovalTimeout))) + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) } return .complete() } @@ -352,17 +747,17 @@ class PrivacyAndSecurityViewController: TableViewController { let signal = privacySettingsPromise.get() |> take(1) |> deliverOnMainQueue - currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in if let info = info { - pushControllerImpl(SelectivePrivacySettingsController(account: account, kind: .groupInvitations, current: info.groupInvitations, updated: { updated in + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .groupInvitations, current: info.groupInvitations, callSettings: nil, phoneDiscoveryEnabled: nil, updated: { updated, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } + |> filter { $0.0 != nil } |> take(1) |> deliverOnMainQueue - |> mapToSignal { value -> Signal in + |> mapToSignal { value, sessions -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, accountRemovalTimeout: value.accountRemovalTimeout))) + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) } return .complete() } @@ -375,17 +770,86 @@ class PrivacyAndSecurityViewController: TableViewController { let signal = privacySettingsPromise.get() |> take(1) |> deliverOnMainQueue - currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in if let info = info { - pushControllerImpl(SelectivePrivacySettingsController(account: account, kind: .voiceCalls, current: info.voiceCalls, updated: { updated in + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .voiceCalls, current: info.voiceCalls, callSettings: info.voiceCallsP2P, phoneDiscoveryEnabled: nil, updated: { updated, p2pUpdated, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } + |> filter { $0.0 != nil } |> take(1) |> deliverOnMainQueue - |> mapToSignal { value -> Signal in + |> mapToSignal { value, sessions -> Signal in if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, accountRemovalTimeout: value.accountRemovalTimeout))) + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: p2pUpdated ?? value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) + } + return .complete() + } + currentInfoDisposable.set(applySetting.start()) + } + })) + } + })) + }, openProfilePhotoPrivacy: { + let signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in + if let info = info { + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .profilePhoto, current: info.profilePhoto, phoneDiscoveryEnabled: nil, updated: { updated, _, _ in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> filter { $0.0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value, sessions -> Signal in + if let value = value { + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) + } + return .complete() + } + currentInfoDisposable.set(applySetting.start()) + } + })) + } + })) + }, openForwardPrivacy: { + let signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in + if let info = info { + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .forwards, current: info.forwards, phoneDiscoveryEnabled: nil, updated: { updated, _, _ in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> filter { $0.0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value, sessions -> Signal in + if let value = value { + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) + } + return .complete() + } + currentInfoDisposable.set(applySetting.start()) + } + })) + } + })) + }, openPhoneNumberPrivacy: { + let signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info, _ in + if let info = info { + pushControllerImpl(SelectivePrivacySettingsController(context, kind: .phoneNumber, current: info.phoneNumber, phoneDiscoveryEnabled: info.phoneDiscoveryEnabled, updated: { updated, _, phoneDiscoveryEnabled in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> filter { $0.0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value, sessions -> Signal in + if let value = value { + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, phoneDiscoveryEnabled: phoneDiscoveryEnabled!, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout), sessions))) } return .complete() } @@ -395,49 +859,185 @@ class PrivacyAndSecurityViewController: TableViewController { } })) }, openPasscode: { [weak self] in - if let account = self?.account { - self?.navigationController?.push(PasscodeSettingsViewController(account)) + if let context = self?.context { + self?.navigationController?.push(PasscodeSettingsViewController(context)) } - }, openTwoStepVerification: { [weak self] in - if let account = self?.account { - self?.navigationController?.push(TwoStepVerificationUnlockController(account: account, mode: .access)) + }, openTwoStepVerification: { [weak self] configuration in + if let context = self?.context, let `self` = self { + self.navigationController?.push(twoStepVerificationUnlockController(context: context, mode: .access(configuration), presentController: { [weak self] controller, isRoot, animated in + guard let `self` = self, let navigation = self.navigationController else {return} + if isRoot { + navigation.removeUntil(PrivacyAndSecurityViewController.self) + } + + if !animated { + navigation.stackInsert(controller, at: navigation.stackCount) + } else { + navigation.push(controller) + } + })) + } + }, openActiveSessions: { [weak self] sessions in + if let context = self?.context { + self?.navigationController?.push(RecentSessionsController(context)) } - }, openActiveSessions: { [weak self] in - if let account = self?.account { - self?.navigationController?.push(RecentSessionsController(account)) + }, openWebAuthorizations: { + + let signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] _, sessions in + pushControllerImpl(WebSessionsController(context, sessions, updated: { updated in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { privacy, _ -> Signal in + privacySettingsPromise.set(.single((privacy, updated))) + return .complete() + } + currentInfoDisposable.set(applySetting.start()) + } + })) + })) + + }, setupAccountAutoremove: { [weak self] in + + if let strongSelf = self { + + let signal = privacySettingsPromise.get() + |> take(1) + |> deliverOnMainQueue + updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable, weak strongSelf] privacySettingsValue, _ in + if let _ = privacySettingsValue, let strongSelf = strongSelf { + + let timeoutAction: (Int32) -> Void = { timeout in + if let updateAccountTimeoutDisposable = updateAccountTimeoutDisposable { + updateState { + return $0.withUpdatedUpdatingAccountTimeoutValue(timeout) + } + let applyTimeout: Signal = privacySettingsPromise.get() + |> filter { $0.0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value, sessions -> Signal in + if let value = value { + privacySettingsPromise.set(.single((AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: timeout), sessions))) + } + return .complete() + } + updateAccountTimeoutDisposable.set((updateAccountRemovalTimeout(account: context.account, timeout: timeout) + |> then(applyTimeout) + |> deliverOnMainQueue).start(completed: { +// updateState { +// return $0.withUpdatedUpdatingAccountTimeoutValue(nil) +// } + })) + } + } + let timeoutValues: [Int32] = [ + 1 * 30 * 24 * 60 * 60, + 3 * 30 * 24 * 60 * 60, + 180 * 24 * 60 * 60, + 365 * 24 * 60 * 60 + ] + var items: [SPopoverItem] = [] + + items.append(SPopoverItem(tr(L10n.timerMonthsCountable(1)), { + timeoutAction(timeoutValues[0]) + })) + items.append(SPopoverItem(tr(L10n.timerMonthsCountable(3)), { + timeoutAction(timeoutValues[1]) + })) + items.append(SPopoverItem(tr(L10n.timerMonthsCountable(6)), { + timeoutAction(timeoutValues[2]) + })) + items.append(SPopoverItem(tr(L10n.timerYearsCountable(1)), { + timeoutAction(timeoutValues[3]) + })) + + if let index = strongSelf.genericView.index(hash: PrivacyAndSecurityEntry.accountTimeout(sectionId: 0, "", viewType: .singleItem).stableId) { + if let view = (strongSelf.genericView.viewNecessary(at: index) as? GeneralInteractedRowView)?.textView { + showPopover(for: view, with: SPopoverViewController(items: items)) + } + } + } + })) + + } - }, setupAccountAutoremove: { - + }, openProxySettings: { [weak self] in - if let account = self?.account { - self?.navigationController?.push(ProxySettingsViewController(account)) + if let context = self?.context { + + let controller = proxyListController(accountManager: context.sharedContext.accountManager, network: context.account.network, share: { servers in + var message: String = "" + for server in servers { + message += server.link + "\n\n" + } + message = message.trimmed + + showModal(with: ShareModalController(ShareLinkObject(context, link: message)), for: mainWindow) + }, pushController: { controller in + pushControllerImpl(controller) + }) + pushControllerImpl(controller) } + }, togglePeerSuggestions: { enabled in + _ = (updateRecentPeersEnabled(postbox: context.account.postbox, network: context.account.network, enabled: enabled) |> then(enabled ? managedUpdatedRecentPeers(accountPeerId: context.account.peerId, postbox: context.account.postbox, network: context.account.network) : Signal.complete())).start() + }, clearCloudDrafts: { + confirm(for: context.window, information: L10n.privacyAndSecurityConfirmClearCloudDrafts, successHandler: { _ in + _ = showModalProgress(signal: clearCloudDraftsInteractively(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId), for: context.window).start() + }) + }, toggleSensitiveContent: { value in + _ = updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start() + }, toggleSecretChatWebPreview: { value in + FastSettings.setSecretChatWebPreviewAvailable(for: context.account.id.int64, value: value) + }, toggleAutoArchive: { value in + _ = showModalProgress(signal: updateAccountAutoArchiveChats(account: context.account, value: value), for: context.window).start() }) - - + + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = self.atomicSize - let privacySettings: Signal = .single(nil) |> then(requestAccountPrivacySettings(account: account) |> map { Optional($0) }) - |> deliverOnMainQueue + + let contentConfiguration: Signal = .single(nil) |> then(contentSettingsConfiguration(network: context.account.network) |> map(Optional.init)) + - let proxySettings:Signal = account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]) |> map { view in - return view.values[PreferencesKeys.proxySettings] as? ProxySettings + let signal = combineLatest(queue: .mainQueue(), statePromise.get(), contentConfiguration, appearanceSignal, settings, privacySettingsPromise.get(), combineLatest(queue: .mainQueue(), recentPeers(account: context.account), twoStepAccessConfiguration.get(), activeSessions.get(), context.sharedContext.accountManager.accessChallengeData()), context.blockedPeersContext.state) + |> map { state, contentConfiguration, appearance, proxy, values, additional, blockedState -> TableUpdateTransition in + let entries = privacyAndSecurityControllerEntries(state: state, contentConfiguration: contentConfiguration, privacySettings: values.0, webSessions: values.1, blockedState: blockedState, proxy: proxy, recentPeers: additional.0, configuration: additional.1, activeSessions: additional.2, passcodeData: additional.3.data, context: context).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify {$0}, arguments: arguments) + } |> afterDisposed { + actionsDisposable.dispose() } |> deliverOnMainQueue - genericView.merge(with: combineLatest(statePromise.get() |> deliverOnMainQueue, privacySettings |> deliverOnMainQueue, appearanceSignal, proxySettings) - |> map { state, privacySettings, appearance, proxy -> TableUpdateTransition in - let entries = privacyAndSecurityControllerEntries(state: state, privacySettings: privacySettings, proxy: proxy).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify {$0}, arguments: arguments) - } |> afterDisposed { - actionsDisposable.dispose() - }) - + disposable.set(signal.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + self?.readyOnce() + if let focusOnItemTag = self?.focusOnItemTag { + self?.genericView.scroll(to: .center(id: focusOnItemTag.stableId, innerId: nil, animated: true, focus: .init(focus: true), inset: 0), inset: NSEdgeInsets()) + self?.focusOnItemTag = nil + } + })) - readyOnce() } - init(_ account:Account, initialSettings: Signal) { - self.initialSettings = initialSettings - super.init(account) + deinit { + disposable.dispose() + } + + private var focusOnItemTag: PrivacyAndSecurityEntryTag? + private let disposable = MetaDisposable() + init(_ context: AccountContext, initialSettings: (AccountPrivacySettings?, ([WebAuthorization], [PeerId : Peer])?), focusOnItemTag: PrivacyAndSecurityEntryTag? = nil) { + self.focusOnItemTag = focusOnItemTag + super.init(context) + + let thenSignal:Signal<(AccountPrivacySettings?, ([WebAuthorization], [PeerId : Peer])?), NoError> = requestAccountPrivacySettings(account: context.account) |> map { + return ($0, initialSettings.1) + } + + self.privacySettingsPromise.set(.single(initialSettings) |> then(thenSignal)) } } + diff --git a/Telegram-Mac/ProxyListController.swift b/Telegram-Mac/ProxyListController.swift new file mode 100644 index 0000000000..5d1e388731 --- /dev/null +++ b/Telegram-Mac/ProxyListController.swift @@ -0,0 +1,498 @@ +// +// ProxyListController.swift +// Telegram +// +// Created by Mikhail Filimonov on 17/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import TGUIKit +import MtProtoKit + +private let _p_id_enable: InputDataIdentifier = InputDataIdentifier("_p_id_enable") +private let _p_id_add: InputDataIdentifier = InputDataIdentifier("_p_id_add") +private let _id_calls: InputDataIdentifier = InputDataIdentifier("_id_calls") +private struct ProxyListState : Equatable { + let settings: ProxySettings + init(settings: ProxySettings = ProxySettings.defaultSettings) { + self.settings = settings + } + + func withUpdatedSettings(_ settings: ProxySettings) -> ProxyListState { + return ProxyListState(settings: settings) + } +} + +//private func ==(lhs: ProxyListState, rhs: ProxyListState) -> Bool { +// return lhs.pref == rhs.pref && lhs.current == rhs.current +//} + +extension ProxyServerSettings { + func withHexedStringData() -> ProxyServerSettings { + switch self.connection { + case let .mtp(secret): + let data = MTProxySecret.parseData(secret)?.serializeToString().data(using: .utf8) ?? Data() + return ProxyServerSettings(host: host, port: port, connection: .mtp(secret: data)) + default: + return self + } + } + + func withDataHextString() -> ProxyServerSettings { + switch self.connection { + case let .mtp(secret): + let data = MTProxySecret.parse(String(data: secret, encoding: .utf8) ?? "")?.serialize() ?? Data() + return ProxyServerSettings(host: host, port: port, connection: .mtp(secret: data)) + default: + return self + } + } +} + + + +private func proxyListSettingsEntries(_ state: ProxyListState, status: ConnectionStatus, statuses: [ProxyServerSettings : ProxyServerStatus], arguments: ProxyListArguments, showUseCalls: Bool) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + struct UpdateEnableRow : Equatable { + let enabled: Bool + let hasActiveServer: Bool + let hasServers: Bool + } + + let updateEnableRow: UpdateEnableRow = UpdateEnableRow(enabled: state.settings.enabled, hasActiveServer: state.settings.effectiveActiveServer != nil, hasServers: !state.settings.servers.isEmpty) + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .string(nil), identifier: _p_id_enable, equatable: InputDataEquatable(updateEnableRow), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.proxySettingsEnable, type: .switchable(state.settings.effectiveActiveServer != nil), viewType: showUseCalls ? .firstItem : .singleItem, action: { + if state.settings.enabled { + arguments.disconnect() + } else { + arguments.reconnectLatest() + } + }, enabled: !state.settings.servers.isEmpty || state.settings.effectiveActiveServer != nil) + })) + index += 1 + + if showUseCalls { + var enabled = true + if let server = state.settings.effectiveActiveServer { + switch server.connection { + case .mtp: + enabled = false + default: + break + } + } + + struct UseForCallEquatable : Equatable { + let enabled: Bool + let useForCalls: Bool + } + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .string(nil), identifier: _id_calls, equatable: InputDataEquatable(UseForCallEquatable(enabled: enabled, useForCalls: state.settings.useForCalls)), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.proxySettingsUseForCalls, type: .switchable(state.settings.useForCalls && enabled), viewType: .lastItem, action: { + arguments.enableForCalls(!state.settings.useForCalls) + }, enabled: enabled) + })) + } + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + var list: [ProxyServerSettings] = state.settings.servers.uniqueElements + if let current = state.settings.effectiveActiveServer, list.first(where: {$0 == current}) == nil { + list.insert(current, at: 0) + } + + let addViewType: GeneralViewType = list.isEmpty ? .singleItem : .firstItem + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .string(nil), identifier: _p_id_add, equatable: InputDataEquatable(addViewType), item: { initialSize, stableId in + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.proxySettingsAddProxy, nameStyle: blueActionButton, type: .none, viewType: addViewType, action: { () in + arguments.edit(nil) + }, thumb: GeneralThumbAdditional(thumb: theme.icons.proxyAddProxy, textInset: 30, thumbInset: -5), inset:NSEdgeInsets(left: 30, right: 30)) + })) + index += 1 + + + + for proxy in list { + struct ProxyEquatable : Equatable { + let enabled: Bool + let isActiveServer: Bool + let connectionStatus: ConnectionStatus? + let proxy: ProxyServerSettings + let status: ProxyServerStatus? + let viewType: GeneralViewType + } + + let viewType = list.count == 1 ? .lastItem : (list.first == proxy ? .innerItem : bestGeneralViewType(list, for: proxy)) + + let value = ProxyEquatable(enabled: state.settings.enabled, isActiveServer: state.settings.activeServer == proxy, connectionStatus: proxy == state.settings.effectiveActiveServer ? status : nil, proxy: proxy, status: statuses[proxy], viewType: viewType) + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .string(nil), identifier: InputDataIdentifier("_proxy_\(proxy.hashValue))"), equatable: InputDataEquatable(value), item: { initialSize, stableId -> TableRowItem in + return ProxyListRowItem(initialSize, stableId: stableId, proxy: proxy, waiting: !value.enabled && state.settings.activeServer == proxy, connectionStatus: value.connectionStatus, status: value.status, viewType: viewType, action: { + arguments.connect(proxy) + }, info: { + arguments.edit(proxy) + }, delete: { + arguments.delete(proxy) + }) + })) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + + +private final class ProxyListArguments { + let edit:(ProxyServerSettings?)->Void + let delete:(ProxyServerSettings)->Void + let connect:(ProxyServerSettings)->Void + let disconnect:()->Void + let reconnectLatest:()->Void + let enableForCalls:(Bool)->Void + init(edit:@escaping(ProxyServerSettings?)->Void, delete: @escaping(ProxyServerSettings)->Void, connect: @escaping(ProxyServerSettings)->Void, disconnect: @escaping()->Void, reconnectLatest:@escaping()->Void, enableForCalls:@escaping(Bool)->Void) { + self.edit = edit + self.delete = delete + self.connect = connect + self.disconnect = disconnect + self.reconnectLatest = reconnectLatest + self.enableForCalls = enableForCalls + } +} + +private extension ProxyServerConnection { + var type: ProxyType { + switch self { + case .socks5: + return .socks5 + case .mtp: + return .mtp + } + } +} + +func proxyListController(accountManager: AccountManager, network: Network, showUseCalls: Bool = true, share:@escaping([ProxyServerSettings])->Void = {_ in}, pushController:@escaping(ViewController) -> Void = { _ in }) -> ViewController { + let actionsDisposable = DisposableSet() + + let updateDisposable = MetaDisposable() + actionsDisposable.add(updateDisposable) + + let statuses: ProxyServersStatuses = ProxyServersStatuses(network: network, servers: proxySettings(accountManager: accountManager) |> map { $0.servers}) + + let stateValue:Atomic = Atomic(value: ProxyListState()) + let statePromise:ValuePromise = ValuePromise(ignoreRepeated: true) + let updateState:(_ f:(ProxyListState)->ProxyListState)-> Void = { f in + statePromise.set(stateValue.modify(f)) + } + + actionsDisposable.add((proxySettings(accountManager: accountManager) |> deliverOnPrepareQueue).start(next: { settings in + updateState { current in + return current.withUpdatedSettings(settings) + } + })) + + let arguments = ProxyListArguments(edit: { proxy in + if let proxy = proxy { + pushController(addProxyController(accountManager: accountManager, network: network, settings: proxy, type: proxy.connection.type)) + } else { + let values: [ValuesSelectorValue] = [ValuesSelectorValue(localized: L10n.proxySettingsSocks5, value: .socks5), ValuesSelectorValue(localized: L10n.proxySettingsMTP, value: .mtp)] + showModal(with: ValuesSelectorModalController(values: values, selected: nil, title: L10n.proxySettingsType, onComplete: { selected in + pushController(addProxyController(accountManager: accountManager, network: network, settings: nil, type: selected.value)) + }), for: mainWindow) + } + }, delete: { proxy in + updateDisposable.set(updateProxySettingsInteractively(accountManager: accountManager, { current in + return current.withRemovedServer(proxy) + }).start()) + }, connect: { proxy in + updateDisposable.set(updateProxySettingsInteractively(accountManager: accountManager, {$0.withUpdatedActiveServer(proxy).withUpdatedEnabled(true)}).start()) + }, disconnect: { + updateDisposable.set(updateProxySettingsInteractively(accountManager: accountManager, {$0.withUpdatedEnabled(false)}).start()) + }, reconnectLatest: { + updateDisposable.set(updateProxySettingsInteractively(accountManager: accountManager, { current in + if !current.enabled, let _ = current.activeServer { + return current.withUpdatedEnabled(true) + } else if let first = current.servers.first { + return current.withUpdatedActiveServer(first).withUpdatedEnabled(true) + } else { + return current + } + }).start()) + }, enableForCalls: { enable in + updateDisposable.set(updateProxySettingsInteractively(accountManager: accountManager, {$0.withUpdatedUseForCalls(enable)}).start()) + }) + + let controller = InputDataController(dataSignal: combineLatest(statePromise.get() |> deliverOnPrepareQueue, network.connectionStatus |> deliverOnPrepareQueue, statuses.statuses() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map {proxyListSettingsEntries($0.0, status: $0.1, statuses: $0.2, arguments: arguments, showUseCalls: showUseCalls)} |> map { InputDataSignalValue(entries: $0) }, title: L10n.proxySettingsTitle, validateData: { + data in + + if data[_p_id_add] != nil { + arguments.edit(nil) + } + + + return .fail(.none) + }, afterDisappear: { + actionsDisposable.dispose() + }, removeAfterDisappear: false, hasDone: false, identifier: "proxy", customRightButton: { controller in + let view = ImageBarView(controller: controller, theme.icons.webgameShare) + + view.button.set(handler: { control in + showPopover(for: control, with: SPopoverViewController(items: [SPopoverItem(L10n.proxySettingsShareProxyList, { + updateState { current in + share(Array(current.settings.servers.prefix(20))) + return current + } + })]), edge: .minX, inset: NSMakePoint(0,-50)) + }, for: .Click) + view.set(image: theme.icons.webgameShare, highlightImage: nil) + return view + }, afterTransaction: { controller in + controller.rightBarView.isHidden = stateValue.with { $0.settings.servers.isEmpty } + }) + + return controller +} + + +private enum ProxyType { + case socks5 + case mtp + var defaultConnection: ProxyServerConnection { + switch self { + case .socks5: + return .socks5(username: nil, password: nil) + case .mtp: + return .mtp(secret: Data()) + } + } +} + +private func addProxyController(accountManager: AccountManager, network: Network, settings: ProxyServerSettings?, type: ProxyType) -> (InputDataController) { + + let actionsDisposable = DisposableSet() + + let new = settings?.withHexedStringData() ?? ProxyServerSettings(host: "", port: 0, connection: type.defaultConnection) + + let stateValue:Atomic = Atomic(value: ProxySettingsState(server: new)) + let statePromise:ValuePromise = ValuePromise(ProxySettingsState(server: new), ignoreRepeated: false) + let updateState:(_ f:(ProxySettingsState)->ProxySettingsState)-> Void = { f in + statePromise.set(stateValue.modify(f)) + } + + let title: String + switch type { + case .socks5: + title = L10n.proxySettingsSocks5 + case .mtp: + title = L10n.proxySettingsMTP + } + + weak var _controller: ViewController? + + let controller = InputDataController(dataSignal: combineLatest(statePromise.get() |> deliverOnPrepareQueue, appearanceSignal |> deliverOnPrepareQueue) |> map { state, _ in + return addProxySettingsEntries(state: state) + } |> map { InputDataSignalValue(entries: $0) }, title: title, validateData: { data -> InputDataValidation in + if data[_id_export] != nil { + updateState { current in + copyToClipboard(current.server.withDataHextString().link) + _controller?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) + return current + } + return .fail(.none) + } + + return .fail(.doSomething { f in + updateState { current in + var fails:[InputDataIdentifier : InputDataValidationFailAction] = [:] + if current.server.host.isEmpty { + fails[_id_host] = .shake + } + if current.server.port == 0 { + fails[_id_port] = .shake + } + switch current.server.connection { + case let .mtp(secret): + if secret.isEmpty { + fails[_id_secret] = .shake + } + default: + break + } + if !fails.isEmpty { + f(.fail(.fields(fails))) + return current + } + + let server = current.server.withDataHextString() + + switch server.connection { + case let .mtp(secret): + if secret.count == 0 { + alert(for: mainWindow, info: L10n.proxySettingsIncorrectSecret) + return current + } + default: + break + } + + actionsDisposable.add((updateProxySettingsInteractively(accountManager: accountManager, { proxySetting in + if let settings = settings { + return proxySetting + .withUpdatedServer(settings, with: server) + } else { + return proxySetting + .withAddedServer(server) + .withUpdatedActiveServer(server) + .withUpdatedEnabled(true) + } + }) |> deliverOnMainQueue).start(next: { _ in + f(.success(.navigationBack)) + })) + return current + } + }) + }, updateDatas: { data in + updateState { current in + let port = data[_id_port]!.stringValue! + switch current.server.connection { + case .mtp: + let secret = data[_id_secret]?.stringValue?.data(using: .utf8) ?? Data() + return current.withUpdatedServer(ProxyServerSettings(host: data[_id_host]?.stringValue ?? "", port: port.isEmpty ? 0 : Int32(port)!, connection: .mtp(secret: secret))) + case .socks5: + return current.withUpdatedServer(ProxyServerSettings(host: data[_id_host]?.stringValue ?? "", port: port.isEmpty ? 0 : Int32(port)!, connection: .socks5(username: data[_id_username]?.stringValue, password: data[_id_pass]?.stringValue))) + } + } + return .fail(.none) + }, afterDisappear: { + actionsDisposable.dispose() + }, identifier: "proxy") + + _controller = controller + + return (controller) +} + + +private struct ProxySettingsState: Equatable { + let server: ProxyServerSettings + init(server: ProxyServerSettings) { + self.server = server + } + + func withUpdatedServer(_ server: ProxyServerSettings) -> ProxySettingsState { + return ProxySettingsState(server: server) + } +} + + + +private let _id_disable = InputDataIdentifier("disable") +private let _id_socks5 = InputDataIdentifier("socks5") +private let _id_export = InputDataIdentifier("export") + +private let _id_host = InputDataIdentifier("host") +private let _id_port = InputDataIdentifier("port") +private let _id_username = InputDataIdentifier("username") +private let _id_secret = InputDataIdentifier("secret") +private let _id_pass = InputDataIdentifier("pass") +private let _id_qrcode = InputDataIdentifier("_id_qrcode") + +private func addProxySettingsEntries(state: ProxySettingsState) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + + let server = state.server + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.proxySettingsConnectionHeader.uppercased()), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + + + + entries.append(.input(sectionId: sectionId, index: index, value: .string(server.host), error: nil, identifier: _id_host, mode: .plain, data: InputDataRowData(viewType: .firstItem), placeholder: nil, inputPlaceholder: L10n.proxySettingsServer, filter: {$0}, limit: 255)) + index += 1 + + + let portViewType: GeneralViewType + switch server.connection { + case .mtp: + portViewType = .innerItem + case .socks5: + portViewType = .lastItem + } + + entries.append(.input(sectionId: sectionId, index: index, value: .string("\(server.port > 0 ? "\(server.port)" : "")"), error: nil, identifier: _id_port, mode: .plain, data: InputDataRowData(viewType: portViewType), placeholder: nil, inputPlaceholder: L10n.proxySettingsPort, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: 10)) + index += 1 + + switch server.connection { + case let .mtp(secret): + entries.append(.input(sectionId: sectionId, index: index, value: .string(String(data: secret, encoding: .utf8)), error: nil, identifier: _id_secret, mode: .plain, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.proxySettingsSecret, filter: {$0}, limit: 255)) + index += 1 + case let .socks5(username, password): + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.proxySettingsCredentialsHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) + index += 1 + entries.append(.input(sectionId: sectionId, index: index, value: .string(username ?? ""), error: nil, identifier: _id_username, mode: .plain, data: InputDataRowData(viewType: .firstItem), placeholder: nil, inputPlaceholder: L10n.proxySettingsUsername, filter: {$0}, limit: 255)) + index += 1 + + entries.append(.input(sectionId: sectionId, index: index, value: .string(password ?? ""), error: nil, identifier: _id_pass, mode: .secure, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.proxySettingsPassword, filter: {$0}, limit: 255)) + index += 1 + } + + if case .mtp = server.connection { + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.proxySettingsMtpSponsor), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + } + + if !server.isEmpty { + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .string(""), error: nil, identifier: _id_export, data: InputDataGeneralData(name: L10n.proxySettingsCopyLink, color: theme.colors.accent, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let link = server.withDataHextString().link + + entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_qrcode, equatable: InputDataEquatable(link), item: { initialSize, stableId in + return ProxyQRCodeRowItem(initialSize, stableId: stableId, link: link) + })) + index += 1 + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + + + diff --git a/Telegram-Mac/ProxyListRowItem.swift b/Telegram-Mac/ProxyListRowItem.swift new file mode 100644 index 0000000000..14b229fd4e --- /dev/null +++ b/Telegram-Mac/ProxyListRowItem.swift @@ -0,0 +1,254 @@ +// +// ProxyListRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 17/04/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox + +class ProxyListRowItem: GeneralRowItem { + fileprivate let headerLayout: TextViewLayout + fileprivate let statusLayout: TextViewLayout + fileprivate let delete:()->Void + fileprivate let info:()->Void + fileprivate let status: (isConnecting: Bool, isCurrent: Bool) + fileprivate let waiting: Bool + init(_ initialSize: NSSize, stableId: AnyHashable, proxy: ProxyServerSettings, waiting: Bool, connectionStatus: ConnectionStatus?, status: ProxyServerStatus?, viewType: GeneralViewType, action:@escaping()->Void, info:@escaping()->Void, delete:@escaping()->Void) { + self.delete = delete + self.info = info + self.waiting = waiting + let attr = NSMutableAttributedString() + let title: String + switch proxy.connection { + case .socks5: + title = L10n.proxySettingsSocks5 + case .mtp: + title = L10n.proxySettingsMTP + } + _ = attr.append(string: "\(proxy.host)", color: theme.colors.text, font: .medium(.text)) + _ = attr.append(string: ":\(proxy.port)", color: theme.colors.grayText, font: .normal(.text)) + + self.headerLayout = TextViewLayout(attr, maximumNumberOfLines: 1) + + var statusText: String + var color: NSColor = theme.colors.grayText + if let connectionStatus = connectionStatus { + switch connectionStatus { + case .connecting: + statusText = L10n.connectingStatusConnecting + self.status = (isConnecting: true, isCurrent: true) + case .waitingForNetwork: + statusText = L10n.connectingStatusConnecting + self.status = (isConnecting: true, isCurrent: true) + case .online, .updating: + statusText = L10n.proxySettingsItemConnected + if let status = status { + switch status { + case let .available(ping): + statusText = L10n.proxySettingsItemConnectedPing("\(Int(ping * 1000))") + default: + break + } + } + color = theme.colors.accent + self.status = (isConnecting: false, isCurrent: true) + } + } else { + statusText = L10n.proxySettingsItemNeverConnected + if let status = status { + switch status { + case .notAvailable: + color = theme.colors.redUI + case let .available(ping): + statusText = L10n.proxySettingsItemAvailable("\(Int(ping * 1000))") //"available (ping: \(ping * 1000)ms)" + case .checking: + statusText = L10n.proxySettingsItemChecking + } + } + + self.status = (isConnecting: false, isCurrent: false) + } + statusText = title.lowercased() + ": " + statusText + + self.statusLayout = TextViewLayout(.initialize(string: statusText, color: color, font: .normal(.text)), maximumNumberOfLines: 1) + super.init(initialSize, height: 50, stableId: stableId, viewType: viewType, action: action, inset: NSEdgeInsetsMake(0, 30, 0, 30)) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + switch viewType { + case .legacy: + headerLayout.measure(width: width - inset.left - inset.right - 80) + statusLayout.measure(width: width - inset.left - inset.right - 80) + case let .modern(_, insets): + headerLayout.measure(width: blockWidth - insets.left - insets.right - 100) + statusLayout.measure(width: blockWidth - insets.left - insets.right - 100) + } + return success + } + + override func viewClass() -> AnyClass { + return ProxyListRowView.self + } +} + + +private final class ProxyListRowView : TableRowView, ViewDisplayDelegate { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let headerView: TextView = TextView() + private let statusView: TextView = TextView() + private let delete: ImageButton = ImageButton() + private let info: ImageButton = ImageButton() + private let connectingView: ProgressIndicator = ProgressIndicator() + private let connected:ImageView = ImageView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + headerView.userInteractionEnabled = false + statusView.userInteractionEnabled = false + statusView.isSelectable = false + headerView.isSelectable = false + containerView.addSubview(delete) + containerView.addSubview(headerView) + containerView.addSubview(statusView) + containerView.addSubview(info) + containerView.addSubview(connectingView) + containerView.addSubview(connected) + addSubview(containerView) + + containerView.displayDelegate = self + + containerView.set(handler: { [weak self] _ in + guard let item = self?.item as? ProxyListRowItem else {return} + item.action() + }, for: .Click) + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Highlight) + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Hover) + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Normal) + + delete.set(handler: { [weak self] _ in + guard let item = self?.item as? ProxyListRowItem else {return} + item.delete() + }, for: .Click) + + info.set(handler: { [weak self] _ in + guard let item = self?.item as? ProxyListRowItem else {return} + item.info() + }, for: .Click) + } + + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? ProxyListRowItem else {return} + + let highlighted = item.viewType.isPlainMode ? self.backdorColor : theme.colors.grayHighlight + headerView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor + statusView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor + self.layer?.backgroundColor = item.viewType.rowBackground.cgColor + + containerView.set(background: self.backdorColor, for: .Normal) + containerView.set(background: highlighted, for: .Highlight) + + } + + override func layout() { + super.layout() + guard let item = item as? ProxyListRowItem else {return} + + switch item.viewType { + case .legacy: + self.containerView.frame = self.bounds + self.containerView.setCorners([]) + headerView.setFrameOrigin(item.inset.left + item.inset.left, 7) + statusView.setFrameOrigin(item.inset.left + item.inset.left, self.containerView.frame.height - statusView.frame.height - 7) + delete.centerY(x: self.containerView.frame.width - delete.frame.width - item.inset.right) + info.centerY(x: self.containerView.frame.width - delete.frame.width - item.inset.right - 10 - info.frame.width) + connected.centerY(x: 30) + connectingView.centerY(x: 30) + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + headerView.setFrameOrigin(innerInsets.left + item.inset.left, 7) + statusView.setFrameOrigin(innerInsets.left + item.inset.left, self.containerView.frame.height - statusView.frame.height - 7) + delete.centerY(x: self.containerView.frame.width - delete.frame.width - innerInsets.right) + info.centerY(x: self.containerView.frame.width - delete.frame.width - innerInsets.right - 10 - info.frame.width) + connected.centerY(x: innerInsets.left - 4) + connectingView.centerY(x: innerInsets.left - 1) + } + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + guard let item = item as? ProxyListRowItem else {return} + if layer == containerView.layer { + ctx.setFillColor(theme.colors.border.cgColor) + switch item.viewType { + case .legacy: + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + case let .modern(position, insets): + switch position { + case .first, .inner: + ctx.fill(NSMakeRect(insets.left + 30, containerView.frame.height - .borderSize, containerView.frame.width - item.inset.left - item.inset.right, .borderSize)) + default: + break + } + } + } + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? ProxyListRowItem else {return} + + switch item.viewType { + case .legacy: + containerView.setCorners([], animated: animated) + case let .modern(position, _): + containerView.setCorners(position.corners, animated: animated) + } + + headerView.update(item.headerLayout) + statusView.update(item.statusLayout) + + connected.isHidden = (!item.status.isCurrent || item.status.isConnecting) && !item.waiting + connectingView.isHidden = !item.status.isCurrent || !item.status.isConnecting + + connected.image = item.waiting ? theme.icons.proxyNextWaitingListItem : theme.icons.proxyConnectedListItem + connected.sizeToFit() + + connectingView.progressColor = theme.colors.indicatorColor + + delete.set(image: theme.icons.proxyDeleteListItem, for: .Normal) + _ = delete.sizeToFit() + + info.set(image: theme.icons.proxyInfoListItem, for: .Normal) + _ = info.sizeToFit() + layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ProxyQRCodeRowItem.swift b/Telegram-Mac/ProxyQRCodeRowItem.swift new file mode 100644 index 0000000000..9a7f8a3275 --- /dev/null +++ b/Telegram-Mac/ProxyQRCodeRowItem.swift @@ -0,0 +1,93 @@ +// +// ProxyQRCodeRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +class ProxyQRCodeRowItem: GeneralRowItem { + + let link: String + + + fileprivate let textLayout: TextViewLayout + + init(_ initialSize: NSSize, stableId: AnyHashable, link: String) { + self.link = link + textLayout = TextViewLayout(.initialize(string: L10n.proxySettingsQRText, color: theme.colors.grayText, font: .normal(.text)), alignment: .center, alwaysStaticItems: true) + textLayout.measure(width: 256) + super.init(initialSize, stableId: stableId, viewType: .singleItem) + } + + + + override var height: CGFloat { + return 256.0 + textLayout.layoutSize.height + 30 + } + + override func viewClass() -> AnyClass { + return ProxyQRCodeRowView.self + } +} + + +private final class ProxyQRCodeRowView : TableRowView { + private let disposable = MetaDisposable() + private let imageView: ImageView = ImageView(frame: NSMakeRect(0, 0, 256, 256)) + private let textView: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + textView.userInteractionEnabled = false + textView.isSelectable = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var backdorColor: NSColor { + guard let item = item as? ProxyQRCodeRowItem else { return theme.colors.background } + return item.viewType.rowBackground + } + + override func layout() { + super.layout() + textView.centerX(y: 10) + imageView.centerX(y: textView.frame.maxY + 10) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + + guard let item = item as? ProxyQRCodeRowItem else { return } + + textView.update(item.textLayout) + + disposable.set((qrCode(string: item.link, color: theme.colors.text, backgroundColor: theme.colors.grayBackground, icon: .proxy) + |> map { generator -> CGImage? in + let imageSize = CGSize(width: 256, height: 256) + let context = generator.1(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: NSEdgeInsets())) + + return context?.generateImage() + } + |> deliverOnMainQueue).start(next: { [weak self] image in + if let image = image { + self?.imageView.image = image + } + })) + + needsLayout = true + } + + deinit { + disposable.dispose() + } +} diff --git a/Telegram-Mac/ProxySettingsViewController.swift b/Telegram-Mac/ProxySettingsViewController.swift deleted file mode 100644 index 5676740305..0000000000 --- a/Telegram-Mac/ProxySettingsViewController.swift +++ /dev/null @@ -1,483 +0,0 @@ -// -// ProxySettingsViewController.swift -// Telegram -// -// Created by keepcoder on 19/06/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - -private final class ProxySettingsArguments { - let account:Account - let changeSettingsType:(ProxySettingsStateType)->Void - let changeServerHandler:(String)->Void - let changePortHandler:(String)->Void - let changeUsernameHandler:(String)->Void - let changePasswordHandler:(String)->Void - let copyShareLink:()->Void - let exportProxy:()->Void - init(_ account:Account, changeSettingsType:@escaping(ProxySettingsStateType)->Void, changeServerHandler:@escaping(String)->Void, changePortHandler:@escaping(String)->Void, changeUsernameHandler:@escaping(String)->Void, changePasswordHandler:@escaping(String)->Void, copyShareLink:@escaping()->Void, exportProxy:@escaping()->Void) { - self.account = account - self.changeSettingsType = changeSettingsType - self.changeServerHandler = changeServerHandler - self.changePortHandler = changePortHandler - self.changeUsernameHandler = changeUsernameHandler - self.changePasswordHandler = changePasswordHandler - self.copyShareLink = copyShareLink - self.exportProxy = exportProxy - } -} - -private enum ProxySettingsEntryId : Hashable { - case section(Int32) - case index(Int32) - case header(Int32) - var hashValue: Int { - switch self { - case .index(let index): - return Int(index) - case .section(let section): - return Int(section) - case .header(let index): - return Int(index) - } - } - - static func ==(lhs: ProxySettingsEntryId, rhs: ProxySettingsEntryId) -> Bool { - switch lhs { - case .section(let index): - if case .section(index) = rhs { - return true - } else { - return false - } - case .index(let index): - if case .index(index) = rhs { - return true - } else { - return false - } - case .header(let index): - if case .header(index) = rhs { - return true - } else { - return false - } - } - } -} - -private enum ProxySettingsEntry : TableItemListNodeEntry { - case disabled(Int32, Bool) - case socks5(Int32, Bool) - case server(Int32, String) - case port(Int32, String) - case username(Int32, String) - case password(Int32, String) - case section(Int32) - case header(Int32, Int32, String) - case share(Int32, Int32, String) - case exportProxy(Int32) - var stableId: ProxySettingsEntryId { - switch self { - case .disabled: - return .index(0) - case .socks5: - return .index(1) - case .server: - return .index(2) - case .port: - return .index(3) - case .username: - return .index(4) - case .password: - return .index(5) - case .share: - return .index(6) - case .exportProxy: - return .index(7) - case .header(_, let index, _): - return .header(index) - case .section(let id): - return .section(id) - } - } - - var index:Int32 { - switch self { - case .exportProxy: - return 0 - case .disabled: - return 1 - case .socks5: - return 2 - case .server: - return 3 - case .port: - return 4 - case .username: - return 5 - case .password: - return 6 - case .header(let section, let index, _): - return (section + 1) * 1000 - index - 30 - case .share(let section, let index, _): - return (section + 1) * 1000 - index + 30 - case .section(let index): - return (index + 1) * 1000 - index - } - } - - static func <(lhs:ProxySettingsEntry, rhs: ProxySettingsEntry) -> Bool { - return lhs.index < rhs.index - } - - static func ==(lhs:ProxySettingsEntry, rhs: ProxySettingsEntry) -> Bool { - switch lhs { - case let .disabled(index, value): - if case .disabled(index, value) = rhs { - return true - } else { - return false - } - case let .exportProxy(index): - if case .exportProxy(index) = rhs { - return true - } else { - return false - } - case let .socks5(index, value): - if case .socks5(index, value) = rhs { - return true - } else { - return false - } - case let .server(index, current): - if case .server(index, current) = rhs { - return true - } else { - return false - } - case let .port(index, current): - if case .port(index, current) = rhs { - return true - } else { - return false - } - case let .username(index, current): - if case .username(index, current) = rhs { - return true - } else { - return false - } - case let .password(index, current): - if case .password(index, current) = rhs { - return true - } else { - return false - } - case .header(let section, let index, let text): - if case .header(section, index, text) = rhs { - return true - } else { - return false - } - case .share(let section, let index, let text): - if case .share(section, index, text) = rhs { - return true - } else { - return false - } - case .section(let index): - if case .section(index) = rhs { - return true - } else { - return false - } - } - } - - func item(_ arguments: ProxySettingsArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .header(_, _, let text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text, drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case .share(_, _, let text): - let attributed = NSMutableAttributedString() - _ = attributed.append(string: text, color: .link, font: .medium(.text)) - return GeneralTextRowItem(initialSize, stableId: stableId, text: attributed, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:10, bottom:2), action: { - arguments.copyShareLink() - }) - case .disabled(_, let value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.proxySettingsDisabled), type: .selectable(stateback: { () -> Bool in - return value - }), action: { - arguments.changeSettingsType(.disabled) - }) - case .socks5(_, let value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.proxySettingsSocks5), type: .selectable(stateback: { () -> Bool in - return value - }), action: { - arguments.changeSettingsType(.socks5) - }) - case .server(_, let value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.proxySettingsServer), text: value, limit: 250, insets: NSEdgeInsets(left:25,right:25,top:10,bottom:3), textChangeHandler: { modified in - arguments.changeServerHandler(modified) - }) - case .port(_, let value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.proxySettingsPort), text: Int(value) == 0 ? "" : value, limit: 6, insets: NSEdgeInsets(left:25,right:25,top:10,bottom:3), textChangeHandler: { modified in - arguments.changePortHandler(modified) - }, textFilter: { value in - if let _ = Int32(value) { - return value - } else { - return "" - } - }) - case .username(_, let value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.proxySettingsUsername), text: value, limit: 250, insets: NSEdgeInsets(left:25,right:25,top:10,bottom:3), textChangeHandler: { modified in - arguments.changeUsernameHandler(modified) - }) - case .password(_, let value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.proxySettingsPassword), text: value, limit: 250, insets: NSEdgeInsets(left:25,right:25,top:10,bottom:3), textChangeHandler: { modified in - arguments.changePasswordHandler(modified) - }) - case .exportProxy: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.proxySettingsExportLink), nameStyle: blueActionButton, type: .next, action: { - arguments.exportProxy() - }) - } - } -} - -private enum ProxySettingsStateType { - case disabled - case socks5 -} -private class ProxySettingsState: Equatable { - let type:ProxySettingsStateType - let settings: ProxySettings? - init() { - self.type = .disabled - self.settings = nil - } - init(type:ProxySettingsStateType, settings: ProxySettings?) { - self.type = type - self.settings = settings - } - - static func ==(lhs: ProxySettingsState, rhs: ProxySettingsState) -> Bool { - if let lhsSettings = lhs.settings, let rhsSettings = rhs.settings { - if !lhsSettings.isEqual(to: rhsSettings) { - return false - } - } else if (lhs.settings != nil) != (rhs.settings != nil) { - return false - } - return lhs.type == rhs.type - } - func withUpdatedSettings(_ settings: ProxySettings?) -> ProxySettingsState { - return ProxySettingsState(type: self.type, settings: settings) - } - func withUpdatedType(_ type: ProxySettingsStateType) -> ProxySettingsState { - return ProxySettingsState(type: type, settings: self.settings) - } - -} - -private func proxySettingsEntries(_ state: ProxySettingsState) -> [ProxySettingsEntry] { - var entries:[ProxySettingsEntry] = [] - - var sectionId:Int32 = 1 - - entries.append(.section(sectionId)) - sectionId += 1 - - var headerIndex:Int32 = 1 - - entries.append(.exportProxy(sectionId)) - entries.append(.header(sectionId, headerIndex, tr(.proxySettingsExportDescription))) - headerIndex += 1 - - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.disabled(sectionId, state.type == .disabled)) - entries.append(.socks5(sectionId, state.type == .socks5)) - - if state.type == .socks5 { - let settings = state.settings - - entries.append(.section(sectionId)) - sectionId += 1 - entries.append(.header(sectionId, headerIndex, tr(.proxySettingsConnectionHeader))) - headerIndex += 1 - - entries.append(.server(sectionId, settings?.host ?? "")) - entries.append(.port(sectionId, settings?.port != nil ? "\(settings!.port)" : "")) - - entries.append(.section(sectionId)) - sectionId += 1 - entries.append(.header(sectionId, headerIndex, tr(.proxySettingsCredentialsHeader))) - headerIndex += 1 - - entries.append(.username(sectionId, settings?.username ?? "")) - entries.append(.password(sectionId, settings?.password ?? "")) - - if !(settings?.host ?? "").isEmpty && (settings?.port ?? 0) > 0 { - entries.append(.share(sectionId, headerIndex, tr(.proxySettingsShare))) - } - } - - return entries -} - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:ProxySettingsArguments) -> TableUpdateTransition { - - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - - -class ProxySettingsViewController: EditableViewController { - private let preferencesDisposable = MetaDisposable() - private let stateValue:Atomic = Atomic(value: ProxySettingsState()) - - private let statePromise:ValuePromise = ValuePromise(ProxySettingsState(), ignoreRepeated: true) - - override init(_ account: Account) { - super.init(account) - } - - override var enableBack: Bool { - return true - } - - override func viewDidLoad() { - super.viewDidLoad() - let stateValue = self.stateValue - let account = self.account - let statePromise:ValuePromise = self.statePromise - let updateState:(_ f:(ProxySettingsState)->ProxySettingsState)-> Void = { f in - statePromise.set(stateValue.modify(f)) - } - - let arguments = ProxySettingsArguments(account, changeSettingsType: { value in - updateState({ current in - return current.withUpdatedType(value) - }) - - }, changeServerHandler: { updated in - updateState({$0.withUpdatedSettings(ProxySettings(host: updated, port: $0.settings?.port ?? 0, username: $0.settings?.username, password: $0.settings?.password))}) - }, changePortHandler: { updated in - updateState({$0.withUpdatedSettings(ProxySettings(host: $0.settings?.host ?? "", port: Int32(updated) ?? 0, username: $0.settings?.username, password: $0.settings?.password))}) - }, changeUsernameHandler: { updated in - updateState({$0.withUpdatedSettings(ProxySettings(host: $0.settings?.host ?? "", port: $0.settings?.port ?? 0, username: updated, password: $0.settings?.password))}) - - }, changePasswordHandler: { updated in - updateState({$0.withUpdatedSettings(ProxySettings(host: $0.settings?.host ?? "", port: $0.settings?.port ?? 0, username: $0.settings?.username, password: updated))}) - }, copyShareLink: { [weak self] in - if let value = stateValue.modify({$0}).settings { - var link = "https://t.me/socks?server=\(value.host)&port=\(value.port)" - if let username = value.username { - link += "&username=\(username)" - } - if let password = value.password { - link += "&password=\(password)" - } - copyToClipboard(link) - self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied))) - } - }, exportProxy: { - let link = NSPasteboard.general.string(forType: .string) - var found: Bool = false - if let link = link, !link.isEmpty { - let attributed = NSMutableAttributedString() - _ = attributed.append(string: link) - attributed.detectLinks(type: [.Links], account: account, applyProxy: { settings in - applyExternalProxy(settings, postbox: account.postbox, network: account.network) - }) - attributed.enumerateAttribute(NSAttributedStringKey.link, in: attributed.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) in - if let value = value as? inAppLink { - switch value { - case .socks(let proxy, let applyProxy): - applyProxy(proxy) - found = true - stop.pointee = true - default: - break - } - } - }) - } - if !found { - alert(for: mainWindow, info: tr(.proxySettingsProxyNotFound)) - } - }) - - let initialState:Atomic = Atomic(value: ProxySettingsState()) - - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - preferencesDisposable.set((account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings])).start(next: { view in - let settings = view.values[PreferencesKeys.proxySettings] as? ProxySettings - updateState({ current in - let updated = current.withUpdatedSettings(settings).withUpdatedType(settings == nil ? .disabled : .socks5) - return updated - }) - _ = initialState.swap(stateValue.modify({$0})) - })) - - genericView.merge(with: (combineLatest(statePromise.get() |> deliverOnMainQueue, appearanceSignal) ) |> map { values in - return proxySettingsEntries(values.0).map({AppearanceWrapperEntry(entry: $0, appearance: values.1)}) - } |> map { - return prepareTransition(left: previous.swap($0), right: $0, initialSize: initialSize.modify{$0}, arguments: arguments) - } |> afterNext { [weak self] value -> TableUpdateTransition in - let state = stateValue.modify{$0} - let initial = initialState.modify{$0} - if initial != state { - if state.type == .disabled { - self?.set(editable: initial != state) - } else { - if let settings = state.settings { - self?.set(editable: !settings.host.isEmpty && settings.port > 0) - } else { - self?.set(editable: false) - } - } - } else { - self?.set(editable: false) - } - - - return value - }) - readyOnce() - } - - override var normalString: String { - return tr(.proxySettingsSave) - } - - - - override func changeState() { - let state = stateValue.modify({$0}) - set(editable: false) - _ = applyProxySettings(postbox: account.postbox, network: account.network, settings: state.type == .disabled ? nil : state.settings).start() - - } - - deinit { - preferencesDisposable.dispose() - } -} diff --git a/Telegram-Mac/QRCode.swift b/Telegram-Mac/QRCode.swift new file mode 100644 index 0000000000..98caa292ff --- /dev/null +++ b/Telegram-Mac/QRCode.swift @@ -0,0 +1,303 @@ +// +// QRCode.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import CoreImage +import SwiftSignalKit +import TGUIKit + +public enum QrCodeIcon { + case none + case cutout + case proxy + case custom(CGImage?) +} + + +private func floorToContextPixels(_ value: CGFloat, scale: CGFloat = System.backingScale) -> CGFloat { + return floor(value * scale) / scale +} + +private func roundToContextPixels(_ value: CGFloat, scale: CGFloat = System.backingScale) -> CGFloat { + return round(value * scale) / scale +} + +public func qrCodeCutout(size: Int, dimensions: CGSize, scale: CGFloat) -> (Int, CGRect, CGFloat) { + var cutoutSize = Int(round(CGFloat(size) * 0.297)) + if size == 39 { + cutoutSize = 11 + } else if cutoutSize % 2 == 0 { + cutoutSize += 1 + } + cutoutSize = min(23, cutoutSize) + + let quadSize = floorToContextPixels(dimensions.width / CGFloat(size), scale: scale) + let cutoutSide = quadSize * CGFloat(cutoutSize - 2) + let cutoutRect = CGRect(x: floorToContextPixels((dimensions.width - cutoutSide) / 2.0, scale: scale), y: floorToContextPixels((dimensions.height - cutoutSide) / 2.0, scale: scale), width: cutoutSide, height: cutoutSide) + + return (cutoutSize, cutoutRect, quadSize) +} + +public func qrCode(string: String, color: NSColor, backgroundColor: NSColor? = nil, icon: QrCodeIcon, ecl: String = "M") -> Signal<(Int, (TransformImageArguments) -> DrawingContext?), NoError> { + return Signal<(Data, Int, Int), NoError> { subscriber in + if let data = string.data(using: .isoLatin1, allowLossyConversion: false), let filter = CIFilter(name: "CIQRCodeGenerator") { + filter.setValue(data, forKey: "inputMessage") + filter.setValue(ecl, forKey: "inputCorrectionLevel") + + if let output = filter.outputImage { + let size = Int(output.extent.width) + let bytesPerRow = (4 * Int(size) + 15) & (~15) + let length = bytesPerRow * size + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue) + + guard let bytes = malloc(length)?.assumingMemoryBound(to: UInt8.self) else { + return EmptyDisposable + } + let data = Data(bytesNoCopy: bytes, count: length, deallocator: .free) + + guard let context = CGContext(data: bytes, width: size, height: size, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { + return EmptyDisposable + } + +// context.translateBy(x: CGFloat(size) / 2.0, y: CGFloat(size) / 2.0) +// context.scaleBy(x: 1.0, y: -1.0) +// context.translateBy(x: -CGFloat(size) / 2.0, y: -CGFloat(size) / 2.0) + + let ciContext = CIContext(cgContext: context, options: [CIContextOption.useSoftwareRenderer : NSNumber(value: true)]) + ciContext.draw(output, in: CGRect(x: 0, y: 0, width: size, height: size), from: output.extent) + + + + subscriber.putNext((data, size, bytesPerRow)) + } + } + subscriber.putCompletion() + return EmptyDisposable + } + |> map { data, size, bytesPerRow in + return (size, { arguments in + let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale, clear: true) + + 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) + + let (cutoutSize, clipRect, side) = qrCodeCutout(size: size, dimensions: fittedSize, scale: arguments.scale) + let padding: CGFloat = roundToContextPixels((arguments.drawingSize.width - CGFloat(side * CGFloat(size))) / 2.0, scale: arguments.scale) + + let cutout: (Int, Int)? + if case .none = icon { + cutout = nil + } else { + let start = (size - cutoutSize) / 2 + cutout = (start, start + cutoutSize - 1) + } + func valueAt(x: Int, y: Int) -> Bool { + if x >= 0 && x < size && y >= 0 && y < size { + if let cutout = cutout, x > cutout.0 && x < cutout.1 && y > cutout.0 && y < cutout.1 { + return false + } + + return data.withUnsafeBytes { bytes -> Bool in + if let value = bytes.baseAddress?.advanced(by: y * bytesPerRow + x * 4).assumingMemoryBound(to: UInt8.self).pointee { + return value < 255 + } else { + return false + } + } + } else { + return false + } + } + + let squareSize = CGSize(width: side, height: side) + let tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale, clear: true) + tmpContext.withContext { c in + if let backgroundColor = backgroundColor { + c.setFillColor(backgroundColor.cgColor) + c.fill(CGRect(origin: CGPoint(), size: squareSize)) + } + c.setFillColor(color.cgColor) + + let outerRadius = squareSize.width / 3.0 + +// var path = NSBezierPath(roundedRect: CGRect(origin: CGPoint(), size: squareSize), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: outerRadius, height: outerRadius)) + c.addPath(CGPath(roundedRect: CGRect(origin: CGPoint(), size: squareSize), cornerWidth: outerRadius, cornerHeight: outerRadius, transform: nil)) + c.fillPath() + + c.fill(CGRect(origin: CGPoint(x: squareSize.width * 2.0, y: 0.0), size: squareSize)) + + c.fill(CGRect(origin: CGPoint(x: squareSize.width, y: 0.0), size: squareSize)) + if let backgroundColor = backgroundColor { + c.setFillColor(backgroundColor.cgColor) + } else { + c.setBlendMode(.clear) + } + + let innerRadius = squareSize.width / 4.0 +// path = NSBezierPath(roundedRect: CGRect(origin: CGPoint(x: squareSize.width, y: 0.0), size: squareSize), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: innerRadius, height: innerRadius)) + c.addPath(CGPath(roundedRect: CGRect(origin: CGPoint(x: squareSize.width, y: 0.0), size: squareSize), cornerWidth: innerRadius, cornerHeight: innerRadius, transform: nil)) + c.fillPath() + + c.fill(CGRect(origin: CGPoint(x: squareSize.width * 3.0, y: 0.0), size: squareSize)) + } + + let scaledSquareSize = Int(squareSize.width * tmpContext.scale) + let scaledPadding = Int(padding * tmpContext.scale) + let halfLen = scaledSquareSize * 4 / 2 + let blockLen = scaledSquareSize * 4 * 2 + + func drawAt(x: Int, y: Int, fill: Bool, corners: NSRectCorner) { + if !fill && corners.isEmpty { + return + } + + for i in 0 ..< scaledSquareSize { + var dst = context.bytes.advanced(by: (scaledPadding + y * scaledSquareSize + i) * context.bytesPerRow + (scaledPadding + x * scaledSquareSize) * 4) + let srcOffset = (fill ? 0 : scaledSquareSize * 4) + let src = tmpContext.bytes.advanced(by: i * tmpContext.bytesPerRow + srcOffset) + + if corners.contains(i < scaledSquareSize / 2 ? .topLeft : .bottomLeft) { + memcpy(dst, src, halfLen) + } else { + memcpy(dst, src + blockLen, halfLen) + } + dst += halfLen + if corners.contains(i < scaledSquareSize / 2 ? .topRight : .bottomRight) { + memcpy(dst, src + halfLen, halfLen) + } else { + memcpy(dst, src + blockLen + halfLen, halfLen) + } + } + } + + context.withContext { c in + if let backgroundColor = backgroundColor { + c.setFillColor(backgroundColor.cgColor) + c.fill(arguments.drawingRect) + } + + var markerSize: Int = 0 + for i in 1 ..< size { + if !valueAt(x: i, y: 1) { + markerSize = i - 1 + break + } + } + + for y in 0 ..< size { + for x in 0 ..< size { + if (y < markerSize + 1 && (x < markerSize + 1 || x > size - markerSize - 2)) || (y > size - markerSize - 2 && x < markerSize + 1) { + continue + } + + var corners: NSRectCorner = [] + if valueAt(x: x, y: y) { + corners = .all + if valueAt(x: x, y: y - 1) { + corners.remove(.topLeft) + corners.remove(.topRight) + } + if valueAt(x: x, y: y + 1) { + corners.remove(.bottomLeft) + corners.remove(.bottomRight) + } + if valueAt(x: x - 1, y: y) { + corners.remove(.topLeft) + corners.remove(.bottomLeft) + } + if valueAt(x: x + 1, y: y) { + corners.remove(.topRight) + corners.remove(.bottomRight) + } + drawAt(x: x, y: y, fill: true, corners: corners) + } else { + if valueAt(x: x - 1, y: y - 1) && valueAt(x: x - 1, y: y) && valueAt(x: x, y: y - 1) { + corners.insert(.topLeft) + } + if valueAt(x: x + 1, y: y - 1) && valueAt(x: x + 1, y: y) && valueAt(x: x, y: y - 1) { + corners.insert(.topRight) + } + if valueAt(x: x - 1, y: y + 1) && valueAt(x: x - 1, y: y) && valueAt(x: x, y: y + 1) { + corners.insert(.bottomLeft) + } + if valueAt(x: x + 1, y: y + 1) && valueAt(x: x + 1, y: y) && valueAt(x: x, y: y + 1) { + corners.insert(.bottomRight) + } + drawAt(x: x, y: y, fill: false, corners: corners) + } + } + } + + c.translateBy(x: padding, y: padding) + + + c.setLineWidth(squareSize.width) + c.setStrokeColor(color.cgColor) + c.setFillColor(color.cgColor) + + let markerSide = floorToContextPixels(CGFloat(markerSize - 1) * squareSize.width * 1.05, scale: arguments.scale) + + func drawMarker(x: CGFloat, y: CGFloat) { + //var path = NSBezierPath(roundedRect: CGRect(x: x + squareSize.width / 2.0, y: y + squareSize.width / 2.0, width: markerSide, height: markerSide), cornerRadius: markerSide / 3.5) + c.addPath(CGPath(roundedRect: CGRect(x: x + squareSize.width / 2.0, y: y + squareSize.width / 2.0, width: markerSide, height: markerSide), cornerWidth: markerSide / 3.5, cornerHeight: markerSide / 3.5, transform: nil)) + c.strokePath() + + let dotSide = markerSide - squareSize.width * 3.0 +// path = NSBezierPath(roundedRect: CGRect(x: x + squareSize.width * 2.0, y: y + squareSize.height * 2.0, width: dotSide, height: dotSide), cornerRadius: dotSide / 3.5) + c.addPath(CGPath(roundedRect: CGRect(x: x + squareSize.width * 2.0, y: y + squareSize.height * 2.0, width: dotSide, height: dotSide), cornerWidth: dotSide / 3.5, cornerHeight: dotSide / 3.5, transform: nil)) + c.fillPath() + } + + drawMarker(x: squareSize.width, y: squareSize.height) + drawMarker(x: CGFloat(size - 2) * squareSize.width - markerSide, y: CGFloat(size - 2) * squareSize.height - markerSide) + drawMarker(x: squareSize.width, y: CGFloat(size - 2) * squareSize.height - markerSide) + + c.translateBy(x: -padding, y: -padding) + + + switch icon { + case .proxy: + let iconScale = clipRect.size.width * 0.01111 + let iconSize = CGSize(width: 65.0 * iconScale, height: 79.0 * iconScale) + let point = CGPoint(x: fittedRect.midX - iconSize.width / 2.0, y: fittedRect.midY + iconSize.height / 2.0) + c.translateBy(x: point.x, y: point.y) + c.scaleBy(x: iconScale, y: -iconScale) + c.setFillColor(color.cgColor) + let _ = try? drawSvgPath(c, path: "M0.0,40 C0,20.3664202 20.1230605,0.0 32.5,0.0 C44.8769395,0.0 65,20.3664202 65,40 C65,47.217934 65,55.5505326 65,64.9977957 L32.5,79 L0.0,64.9977957 C0.0,55.0825772 0.0,46.7499786 0.0,40 Z") + + if let backgroundColor = backgroundColor { + c.setFillColor(backgroundColor.cgColor) + } else { + c.setBlendMode(.clear) + c.setFillColor(NSColor.clear.cgColor) + } + let _ = try? drawSvgPath(c, path: "M7.03608247,43.556701 L18.9836689,32.8350515 L32.5,39.871134 L45.8888139,32.8350515 L57.9639175,43.556701 L57.9639175,60.0 L32.5,71.0 L7.03608247,60.0 Z") + + c.setBlendMode(.normal) + c.setFillColor(color.cgColor) + let _ = try? drawSvgPath(c, path: "M24.1237113,50.5927835 L40.8762887,50.5927835 L40.8762887,60.9793814 L32.5,64.0928525 L24.1237113,60.9793814 Z") + case let .custom(image): + if let image = image { + let fittedSize = image.size.aspectFitted(NSMakeSize(clipRect.width - 3, clipRect.height - 3)) + let fittedRect = CGRect(origin: CGPoint(x: fittedRect.midX - fittedSize.width / 2.0, y: fittedRect.midY - fittedSize.height / 2.0), size: fittedSize) + c.translateBy(x: fittedRect.midX, y: fittedRect.midY) + c.translateBy(x: -fittedRect.midX, y: -fittedRect.midY) + c.draw(image, in: fittedRect) + } + break + default: + break + } + } + + return context + }) + } +} diff --git a/Telegram-Mac/QRLoginConfiguration.swift b/Telegram-Mac/QRLoginConfiguration.swift new file mode 100644 index 0000000000..e44b202d66 --- /dev/null +++ b/Telegram-Mac/QRLoginConfiguration.swift @@ -0,0 +1,64 @@ +// +// QRLoginConfiguration.swift +// Telegram +// +// Created by Mikhail Filimonov on 26.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TelegramApi + +enum QRLoginType : String { + case primary = "primary" + case secondary = "secondary" + case disabled = "disabled" +} + struct UnauthorizedConfiguration { + static var defaultValue: UnauthorizedConfiguration { + return UnauthorizedConfiguration(qr: .disabled) + } + + let qr: QRLoginType + + fileprivate init(qr: QRLoginType) { + self.qr = qr + } + +} + + +func unauthorizedConfiguration(network: Network) -> Signal { + return network.request(Api.functions.help.getAppConfig()) |> retryRequest + |> map { result -> UnauthorizedConfiguration in + if let data = JSON(apiJson: result), let rawQr = data["qr_login_code"] as? String, let qr = QRLoginType(rawValue: rawQr) { + return UnauthorizedConfiguration(qr: .secondary) + } else { + return .defaultValue + } + } +} + + +func managedAppConfigurationUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + return (network.request(Api.functions.help.getAppConfig()) + |> retryRequest + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + if let data = JSON(apiJson: result) { + updateAppConfiguration(transaction: transaction, { configuration -> AppConfiguration in + var configuration = configuration + configuration.data = data + return configuration + }) + } + } + }).start() + } + return (poll |> then(.complete() |> suspendAwareDelay(12.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/Telegram-Mac/QuickLookPreview.swift b/Telegram-Mac/QuickLookPreview.swift index 934929c15b..fbde9dcb14 100644 --- a/Telegram-Mac/QuickLookPreview.swift +++ b/Telegram-Mac/QuickLookPreview.swift @@ -7,13 +7,14 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox import TGUIKit import Quartz import Foundation - +import SyncCore private class QuickLookPreviewItem : NSObject, QLPreviewItem { let media:Media @@ -32,9 +33,9 @@ private class QuickLookPreviewItem : NSObject, QLPreviewItem { var previewItemTitle: String! { if let media = media as? TelegramMediaFile { - return media.fileName ?? tr(.quickLookPreview) + return media.fileName ?? L10n.quickLookPreview } - return tr(.quickLookPreview) + return L10n.quickLookPreview } } @@ -50,7 +51,7 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou private weak var delegate:InteractionContentViewProtocol? private var item:QuickLookPreviewItem! - private var account:Account! + private var context: AccountContext! private var media:Media! private var ready:Promise<(String?,String?)> = Promise() private let disposable:MetaDisposable = MetaDisposable() @@ -62,46 +63,73 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou super.init() } - public func show(account:Account, with media:Media, stableId:ChatHistoryEntryId?, _ delegate:InteractionContentViewProtocol? = nil) { - self.account = account + public func show(context: AccountContext, with media:Media, stableId:ChatHistoryEntryId?, _ delegate:InteractionContentViewProtocol? = nil) { + self.context = context self.media = media self.delegate = delegate self.stableId = stableId panel = QLPreviewPanel.shared() + + var mimeType:String = "image/jpeg" var fileResource:TelegramMediaResource? var fileName:String? = nil + var forceExtension: String? = nil + + let signal:Signal<(String?, String?), NoError> + if let file = media as? TelegramMediaFile { fileResource = file.resource mimeType = file.mimeType fileName = file.fileName - } else if let image = media as? TelegramMediaImage { - fileResource = largestImageRepresentation(image.representations)?.resource - } - - if let fileResource = fileResource { + if let ext = fileName?.nsstring.pathExtension, !ext.isEmpty { + forceExtension = ext + } - let signal = combineLatest(account.postbox.mediaBox.resourceData(fileResource), resourceType(mimeType: mimeType)) - |> mapToSignal({ (data) -> Signal<(String?,String?), Void> in - return .single((data.0.path,data.1)) - }) - |> deliverOnMainQueue - self.ready.set(signal) + signal = copyToDownloads(file, postbox: context.account.postbox) |> map { path in + if let path = path { + return (Optional(path.nsstring.deletingPathExtension), Optional(path.nsstring.pathExtension)) + } else { + return (nil, nil) + } + } + } else if let image = media as? TelegramMediaImage { + fileResource = largestImageRepresentation(image.representations)?.resource + if let fileResource = fileResource { + signal = combineLatest(context.account.postbox.mediaBox.resourceData(fileResource), resourceType(mimeType: mimeType)) + |> mapToSignal({ (data) -> Signal<(String?,String?), NoError> in + + return .single((data.0.path, forceExtension ?? data.1)) + }) |> deliverOnMainQueue + } else { + signal = .complete() + } + + } else { + signal = .complete() } + self.ready.set(signal |> deliverOnMainQueue) + - disposable.set(ready.get().start(next: {[weak self] (path,ext) in + disposable.set(ready.get().start(next: { [weak self] (path,ext) in if let strongSelf = self, let path = path { var ext:String? = ext if ext == nil || ext == "*" { ext = fileName?.nsstring.pathExtension } if let ext = ext { - strongSelf.item = QuickLookPreviewItem(with: strongSelf.media, path:path, ext:ext) - RunLoop.current.add(Timer.scheduledTimer(timeInterval: 0, target: strongSelf, selector: #selector(strongSelf.openPanelInRunLoop), userInfo: nil, repeats: false), forMode: RunLoopMode.modalPanelRunLoopMode) + + let item = QuickLookPreviewItem(with: strongSelf.media, path:path, ext:ext) + if ext == "pkpass" || !FastSettings.openInQuickLook(ext) { + NSWorkspace.shared.openFile(item.path) + return + } + strongSelf.item = item + RunLoop.current.add(Timer.scheduledTimer(timeInterval: 0, target: strongSelf, selector: #selector(strongSelf.openPanelInRunLoop), userInfo: nil, repeats: false), forMode: RunLoop.Mode.modalPanel) } } })) @@ -117,7 +145,7 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou } else { panel.currentPreviewItemIndex = 0 } - + } @@ -149,7 +177,7 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou func previewPanel(_ panel: QLPreviewPanel!, sourceFrameOnScreenFor item: QLPreviewItem!) -> NSRect { if let stableId = stableId { - let view:NSView? = delegate?.contentInteractionView(for: stableId) + let view:NSView? = delegate?.contentInteractionView(for: stableId, animateIn: false) if let view = view, let window = view.window { // let tframe = view.frame @@ -163,7 +191,7 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou func previewPanel(_ panel: QLPreviewPanel!, transitionImageFor item: QLPreviewItem!, contentRect: UnsafeMutablePointer!) -> Any! { if let stableId = stableId { - let view:NSView? = delegate?.contentInteractionView(for: stableId) + let view:NSView? = delegate?.contentInteractionView(for: stableId, animateIn: true) if let view = view?.copy() as? View, let contents = view.layer?.contents { return NSImage(cgImage: contents as! CGImage, size: view.frame.size) @@ -176,7 +204,7 @@ class QuickLookPreview : NSObject, QLPreviewPanelDelegate, QLPreviewPanelDataSou if isOpened() { panel.orderOut(nil) } - self.account = nil + self.context = nil self.media = nil self.stableId = nil self.disposable.set(nil) diff --git a/Telegram-Mac/QuickSwitcherModalController.swift b/Telegram-Mac/QuickSwitcherModalController.swift index 8557d7b996..866236aff6 100644 --- a/Telegram-Mac/QuickSwitcherModalController.swift +++ b/Telegram-Mac/QuickSwitcherModalController.swift @@ -8,14 +8,15 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private class QuickSwitcherArguments { - let account:Account - init(_ account:Account) { - self.account = account + let context: AccountContext + init(_ context:AccountContext) { + self.context = context } } @@ -25,52 +26,34 @@ private enum QuickSwitcherSeparator : Int32 { } private enum QuickSwitcherStableId : Hashable { - case peerId(PeerId) + case peerId(PeerId, SecretChatWrapper?) case separator(QuickSwitcherSeparator) case empty var hashValue: Int { - switch self { - case .peerId(let peerId): - return Int(peerId.id) - case .separator(let id): - return Int(id.hashValue) - case .empty: - return 0 - } + return 0 } - - static func ==(lhs:QuickSwitcherStableId, rhs: QuickSwitcherStableId) -> Bool { - switch lhs { - case .peerId(let peerId): - if case .peerId(peerId) = rhs { - return true - } else { - return false - } - case .separator(let id): - if case .separator(id) = rhs { - return true - } else { - return false - } - case .empty: - if case .empty = rhs { - return true - } else { - return false - } + var effectivePeerId: PeerId? { + switch self { + case let .peerId(peerId, secretPeerId): + return secretPeerId?.peerId ?? peerId + default: + return nil } } } +private struct SecretChatWrapper : Equatable { + let peerId:PeerId +} + private enum QuickSwitcherEntry : TableItemListNodeEntry { - case peer(Int32, Peer, Bool) + case peer(Int32, Peer, Bool, SecretChatWrapper?) case separator(Int32, QuickSwitcherSeparator) case empty var stableId:QuickSwitcherStableId { switch self { - case .peer(_, let peer, _): - return .peerId(peer.id) + case let .peer(_, peer, _, secretChat): + return .peerId(peer.id, secretChat) case .separator(_, let id): return .separator(id) case .empty: @@ -80,7 +63,7 @@ private enum QuickSwitcherEntry : TableItemListNodeEntry { var index:Int32 { switch self { - case .peer(let index, _, _): + case .peer(let index, _, _, _): return index case .separator(let index, _): return index @@ -91,17 +74,17 @@ private enum QuickSwitcherEntry : TableItemListNodeEntry { func item(_ arguments: QuickSwitcherArguments, initialSize: NSSize) -> TableRowItem { switch self { - case .peer(_, let peer, let drawSeparator): - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, height: 40, photoSize: NSMakeSize(30, 30), drawCustomSeparator: drawSeparator, action: { + case let .peer(_, peer, drawSeparator, secretChat): + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 40, photoSize: NSMakeSize(30, 30), titleStyle: ControlStyle(font: .medium(.text), foregroundColor: secretChat != nil ? theme.colors.accent : theme.colors.text, highlightColor:.white), drawCustomSeparator: drawSeparator, isLookSavedMessage: true, action: { }) case .separator(_, let id): let text:String switch id { case .recently: - text = tr(.quickSwitcherRecently) + text = tr(L10n.quickSwitcherRecently) case .popular: - text = tr(.quickSwitcherPopular) + text = tr(L10n.quickSwitcherPopular) } return SeparatorRowItem(initialSize, stableId, string: text.uppercased()) case .empty: @@ -112,11 +95,14 @@ private enum QuickSwitcherEntry : TableItemListNodeEntry { private func ==(lhs: QuickSwitcherEntry, rhs: QuickSwitcherEntry) -> Bool { switch lhs { - case let .peer(lhsIndex, lhsPeer, lhsDrawSeparator): - if case let .peer(rhsIndex, rhsPeer, rhsDrawSeparator) = rhs { + case let .peer(lhsIndex, lhsPeer, lhsDrawSeparator, lhsSecretChat): + if case let .peer(rhsIndex, rhsPeer, rhsDrawSeparator, rhsSecretChat) = rhs { if lhsIndex != rhsIndex { return false } + if lhsSecretChat != rhsSecretChat { + return false + } if lhsDrawSeparator != rhsDrawSeparator { return false } @@ -162,7 +148,7 @@ private class QuickSwitcherView : View { separator.backgroundColor = theme.colors.border self.backgroundColor = theme.colors.background let attributed = NSMutableAttributedString() - _ = attributed.append(string: tr(.quickSwitcherDescription), color: theme.colors.grayText, font: .normal(.text)) + _ = attributed.append(string: L10n.quickSwitcherDescription, color: theme.colors.grayText, font: .normal(.text)) attributed.detectBoldColorInString(with: .medium(.text)) let descLayout = TextViewLayout(attributed, alignment: .center) descLayout.measure(width: frameRect.width - 20) @@ -173,9 +159,9 @@ private class QuickSwitcherView : View { override func layout() { super.layout() - searchView.centerX(y: floorToScreenPixels((50 - 30)/2)) + searchView.centerX(y: floorToScreenPixels(backingScaleFactor, (50 - 30)/2)) tableView.frame = NSMakeRect(0, 50, frame.width, frame.height - 100) - textView.centerX(y: frame.height - floorToScreenPixels((50 - textView.frame.height)/2) - textView.frame.height) + textView.centerX(y: frame.height - floorToScreenPixels(backingScaleFactor, (50 - textView.frame.height)/2) - textView.frame.height) separator.frame = NSMakeRect(0, frame.height - 50, frame.width, .borderSize) } @@ -185,7 +171,7 @@ private class QuickSwitcherView : View { } -private func searchEntriesForPeers(_ peers:[Peer], account:Account, recentlyUsed:[Peer], isLoading: Bool) -> [QuickSwitcherEntry] { +private func searchEntriesForPeers(_ peers:[Peer], account:Account, recentlyUsed:[(Peer, SecretChatWrapper?)], isLoading: Bool) -> [QuickSwitcherEntry] { var entries: [QuickSwitcherEntry] = [] var index:Int32 = 0 @@ -198,10 +184,10 @@ private func searchEntriesForPeers(_ peers:[Peer], account:Account, recentlyUsed var isset:[PeerId:PeerId] = [:] for peer in recentlyUsed { - if account.peerId != peer.id, isset[peer.id] == nil { - entries.append(.peer(index, peer, peer.id != recentlyUsed.last?.id)) + if isset[peer.0.id] == nil { + entries.append(.peer(index, peer.0, peer.0.id != recentlyUsed.last?.0.id, peer.1)) index += 1 - isset[peer.id] = peer.id + isset[peer.0.id] = peer.0.id } } @@ -211,8 +197,8 @@ private func searchEntriesForPeers(_ peers:[Peer], account:Account, recentlyUsed } for peer in peers { - if account.peerId != peer.id, isset[peer.id] == nil { - entries.append(.peer(index, peer, true)) + if isset[peer.id] == nil { + entries.append(.peer(index, peer, true, nil)) index += 1 isset[peer.id] = peer.id } @@ -231,54 +217,104 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry AnyHashable? { + return nil + } + + private let context:AccountContext private let search:ValuePromise = ValuePromise(ignoreRepeated: true) private let disposable = MetaDisposable() - fileprivate func start(account: Account, recentlyUsed:[PeerId], search:Signal) -> Signal<([QuickSwitcherEntry], Bool), Void> { + fileprivate func start(context: AccountContext, recentlyUsed:[PeerId], search:Signal) -> Signal<([QuickSwitcherEntry], Bool), NoError> { - return search |> mapToSignal { search -> Signal<([QuickSwitcherEntry], Bool), Void> in + return search |> mapToSignal { search -> Signal<([QuickSwitcherEntry], Bool), NoError> in if search.request.isEmpty { - return combineLatest(account.postbox.recentPeers(), account.postbox.multiplePeersView(recentlyUsed) |> take(1)) + return combineLatest(recentPeers(account: context.account) |> take(1), context.account.postbox.multiplePeersView(recentlyUsed) |> take(1)) |> deliverOn(prepareQueue) - |> mapToSignal { peers, view -> Signal<([QuickSwitcherEntry], Bool), Void> in + |> mapToSignal { recentPeers, view -> Signal<([QuickSwitcherEntry], Bool), NoError> in - var recentl:[Peer] = [] + var peers:[Peer] = [] + + switch recentPeers { + case let .peers(list): + peers = list + default: + break + } + + var recentl:[(Peer, SecretChatWrapper?)] = [] for peerId in recentlyUsed { if let peer = view.peers[peerId] { - recentl.append(peer) + recentl.append((peer, nil)) + } + } + let secretChats = recentl.compactMap { $0.0 as? TelegramSecretChat }.compactMap { $0.associatedPeerId } + + if !secretChats.isEmpty { + return context.account.postbox.multiplePeersView(secretChats) |> take(1) |> map { secretPeers in + var recentl:[(Peer, SecretChatWrapper?)] = [] + for peerId in recentlyUsed { + if let peer = view.peers[peerId] { + if let peer = peer as? TelegramSecretChat { + if let secretPeer = secretPeers.peers[peer.associatedPeerId!] { + recentl.append((secretPeer, SecretChatWrapper(peerId: peer.id))) + } + } else { + recentl.append((peer, nil)) + } + } + } + return (searchEntriesForPeers(peers, account: context.account, recentlyUsed: recentl, isLoading: false), false) } + } else { + return .single((searchEntriesForPeers(peers, account: context.account, recentlyUsed: recentl, isLoading: false), false)) } - return .single((searchEntriesForPeers(peers, account: account, recentlyUsed: recentl, isLoading: false), false)) } } else { - let foundLocalPeers = account.postbox.searchContacts(query: search.request.lowercased()) - let foundRemotePeers = account.postbox.searchPeers(query: search.request.lowercased()) |> map {$0.flatMap({$0.chatMainPeer}).filter({!($0 is TelegramSecretChat)})} + var all = search.request.transformKeyboard + all.insert(search.request.lowercased(), at: 0) + all = all.uniqueElements + let localPeers = combineLatest(all.map { + return context.account.postbox.searchPeers(query: $0) + }) |> map { result in + return result.reduce([], { + return $0 + $1 + }) + } - return combineLatest(foundLocalPeers, foundRemotePeers) |> map { values -> ([Peer], Bool) in - return (uniquePeers(from: (values.1 + values.0)), false) + let foundLocalPeers = localPeers |> map { + return $0.compactMap({$0.chatMainPeer}).filter({!($0 is TelegramSecretChat)}) + } + + let foundRemotePeers = Signal<[Peer], NoError>.single([]) |> then( searchPeers(account: context.account, query: search.request.lowercased()) |> map { $0.0.map({$0.peer}) + $0.1.map{$0.peer} } ) + + return combineLatest(combineLatest(foundLocalPeers, foundRemotePeers) |> map {$0 + $1}, context.account.postbox.loadedPeerWithId(context.peerId)) |> map { values -> ([Peer], Bool) in + var peers = values.0 + if L10n.peerSavedMessages.lowercased().hasPrefix(search.request.lowercased()) || NSLocalizedString("Peer.SavedMessages", comment: "nil").lowercased().hasPrefix(search.request.lowercased()) { + peers.insert(values.1, at: 0) + } + + return (uniquePeers(from: peers), false) } |> runOn(prepareQueue) |> map { values -> ([QuickSwitcherEntry], Bool) in - return (searchEntriesForPeers(values.0, account: account, recentlyUsed: [], isLoading: values.1), values.1) + return (searchEntriesForPeers(values.0, account: context.account, recentlyUsed: [], isLoading: values.1), values.1) } } - } - } - init(account:Account) { - self.account = account + init(_ context: AccountContext) { + self.context = context super.init(frame: NSMakeRect(0, 0, 300, 360)) bar = .init(height: 0) } @@ -307,7 +343,7 @@ class QuickSwitcherModalController: ModalViewController, TableViewDelegate { return true } - func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { return item is ShortPeerRowItem } @@ -326,13 +362,13 @@ class QuickSwitcherModalController: ModalViewController, TableViewDelegate { genericView.tableView.delegate = self search.set(SearchState(state: .None, request: nil)) - let searchInteractions = SearchInteractions({ [weak self] state in + let searchInteractions = SearchInteractions({ [weak self] state, _ in self?.search.set(state) }, { [weak self] state in self?.search.set(state) }) - let arguments = QuickSwitcherArguments(account) + let arguments = QuickSwitcherArguments(context) genericView.searchView.searchInteractions = searchInteractions @@ -341,7 +377,7 @@ class QuickSwitcherModalController: ModalViewController, TableViewDelegate { let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) let initialSize = atomicSize - disposable.set((combineLatest(start(account: account, recentlyUsed: account.context.recentlyPeerUsed, search: search.get()), appearanceSignal) |> map { value, appearance -> (TableUpdateTransition, Bool) in + disposable.set((combineLatest(start(context: context, recentlyUsed: context.recentlyPeerUsed, search: search.get()), appearanceSignal) |> map { value, appearance -> (TableUpdateTransition, Bool) in let entries = value.0.map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify {$0}, arguments: arguments), value.1) } |> deliverOnMainQueue).start(next: { [weak self] value in @@ -356,10 +392,27 @@ class QuickSwitcherModalController: ModalViewController, TableViewDelegate { override func returnKeyAction() -> KeyHandlerResult { if let selectedItem = genericView.tableView.selectedItem() as? ShortPeerRowItem { - account.context.mainNavigation?.push(ChatController(account: account, peerId: selectedItem.peer.id)) + let query = self.genericView.searchView.query + var peerId = selectedItem.peer.id + var messageId: MessageId? = nil + let link = inApp(for: query as NSString, context: context, peerId: peerId, openInfo: { _, _, _, _ in }, hashtag: nil, command: nil, applyProxy: nil, confirm: false) + switch link { + case let .followResolvedName(_, _, postId, _, _, _): + if let postId = postId { + messageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: postId) + } + default: + break + } + + if let stableId = selectedItem.stableId as? QuickSwitcherStableId, let effectivePeerId = stableId.effectivePeerId { + peerId = effectivePeerId + } + + context.sharedContext.bindings.rootNavigation().push(ChatController(context: context, chatLocation: .peer(peerId), messageId: messageId)) close() } - return .rejected + return .invoked } override func viewDidAppear(_ animated: Bool) { diff --git a/Telegram-Mac/ReadArticlesListPreferences.swift b/Telegram-Mac/ReadArticlesListPreferences.swift new file mode 100644 index 0000000000..ba0788c2a2 --- /dev/null +++ b/Telegram-Mac/ReadArticlesListPreferences.swift @@ -0,0 +1,184 @@ +// +// ReadArticlesListPreferences.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + +final class ReadArticle : PreferencesEntry, Equatable { + static func == (lhs: ReadArticle, rhs: ReadArticle) -> Bool { + return lhs.messageId == rhs.messageId && lhs.webPage.webpageId == rhs.webPage.webpageId && lhs.percent == rhs.percent && lhs.date == rhs.date + } + + var id: MediaId { + return webPage.webpageId + } + + init(webPage: TelegramMediaWebpage, messageId: MessageId?, percent: Int32, date: Int32) { + self.messageId = messageId + self.webPage = webPage + self.percent = percent + self.date = date + } + let percent: Int32 + let webPage: TelegramMediaWebpage + let messageId: MessageId? + let date: Int32 + func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? ReadArticle { + return to == self + } else { + return false + } + } + + init(decoder: PostboxDecoder) { + if let messageIdPeerId = decoder.decodeOptionalInt64ForKey("m.p"), let messageIdNamespace = decoder.decodeOptionalInt32ForKey("m.n"), let messageIdId = decoder.decodeOptionalInt32ForKey("m.i") { + self.messageId = MessageId(peerId: PeerId(messageIdPeerId), namespace: messageIdNamespace, id: messageIdId) + } else { + self.messageId = nil + } + self.webPage = decoder.decodeObjectForKey("wp", decoder: {TelegramMediaWebpage(decoder: $0)}) as! TelegramMediaWebpage + self.percent = decoder.decodeInt32ForKey("p", orElse: 0) + self.date = decoder.decodeInt32ForKey("d", orElse: 0) + } + + func encode(_ encoder: PostboxEncoder) { + if let messageId = messageId { + encoder.encodeInt64(messageId.peerId.toInt64(), forKey: "m.p") + encoder.encodeInt32(messageId.namespace, forKey: "m.n") + encoder.encodeInt32(messageId.id, forKey: "m.i") + } else { + encoder.encodeNil(forKey: "m.p") + encoder.encodeNil(forKey: "m.n") + encoder.encodeNil(forKey: "m.i") + } + encoder.encodeObject(webPage, forKey: "wp") + encoder.encodeInt32(percent, forKey: "p") + encoder.encodeInt32(date, forKey: "d") + } + + func withUpdatedPercent(_ percent: Int32, force: Bool = false) -> ReadArticle { + return ReadArticle(webPage: webPage, messageId: messageId, percent: force ? percent : min(max(percent, self.percent), 100), date: force && percent == 100 ? Int32(Date().timeIntervalSince1970) : self.date) + } + + +} + +class ReadArticlesListPreferences: PreferencesEntry, Equatable { + + + let list: [ReadArticle] + + func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? ReadArticlesListPreferences { + return self == to + } else { + return false + } + } + + init(list: [ReadArticle] = []) { + self.list = list + } + + static func == (lhs: ReadArticlesListPreferences, rhs: ReadArticlesListPreferences) -> Bool { + return lhs.list == rhs.list + } + + required init(decoder: PostboxDecoder) { + self.list = decoder.decodeObjectArrayForKey("l") + } + + func withAddedArticle(_ article: ReadArticle) -> ReadArticlesListPreferences { + var list = self.list + if let index = firstIndex(article) { + list.remove(at: index) + list.insert(article, at: 0) + } else { + list.insert(article, at: 0) + } + return ReadArticlesListPreferences(list: list) + } + + func withReadAll() -> ReadArticlesListPreferences { + var list = self.list + for i in 0 ..< list.count { + list[i] = list[i].withUpdatedPercent(100) + } + return ReadArticlesListPreferences(list: list) + } + + func withRemovedAll() -> ReadArticlesListPreferences { + return ReadArticlesListPreferences(list: []) + } + + func withUpdatedArticle(_ article: ReadArticle) -> ReadArticlesListPreferences { + var list = self.list + + if let index = firstIndex(article) { + list[index] = article + } + return ReadArticlesListPreferences(list: list) + } + + private func firstIndex(_ article: ReadArticle) -> Int? { + for i in 0 ..< list.count { + if list[i].id == article.id { + return i + } + } + return nil + } + + var unreadList: [ReadArticle] { + return list.filter({$0.percent < 100}) + } + + func withRemovedArticles(_ article: ReadArticle) -> ReadArticlesListPreferences { + var list = self.list + if let index = firstIndex(article) { + list.remove(at: index) + } + return ReadArticlesListPreferences(list: list) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.list, forKey: "l") + } + + + static var defaultSettings: ReadArticlesListPreferences { + return ReadArticlesListPreferences() + } + +} + + +func readArticlesListPreferences(_ postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.readArticles]) |> map { preferences in + return (preferences.values[ApplicationSpecificPreferencesKeys.readArticles] as? ReadArticlesListPreferences) ?? ReadArticlesListPreferences.defaultSettings + } +} + +func updateReadArticlesPreferences(postbox: Postbox, _ f:@escaping(ReadArticlesListPreferences)->ReadArticlesListPreferences) -> Signal { + + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.readArticles, { entry in + let currentSettings: ReadArticlesListPreferences + if let entry = entry as? ReadArticlesListPreferences { + currentSettings = entry + } else { + currentSettings = ReadArticlesListPreferences.defaultSettings + } + return f(currentSettings) + }) + } +} diff --git a/Telegram-Mac/RecentCallsViewController.swift b/Telegram-Mac/RecentCallsViewController.swift index 88b462de9a..5afc058c10 100644 --- a/Telegram-Mac/RecentCallsViewController.swift +++ b/Telegram-Mac/RecentCallsViewController.swift @@ -8,16 +8,17 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private final class RecentCallsArguments { let call:(PeerId)->Void let removeCalls:([MessageId]) -> Void - let account:Account - init(account: Account, call:@escaping(PeerId)->Void, removeCalls:@escaping([MessageId]) ->Void ) { - self.account = account + let context:AccountContext + init(context: AccountContext, call:@escaping(PeerId)->Void, removeCalls:@escaping([MessageId]) ->Void ) { + self.context = context self.removeCalls = removeCalls self.call = call } @@ -52,7 +53,7 @@ private enum RecentCallEntry : TableItemListNodeEntry { return false } - if lhsMessage.stableVersion != rhsMessage.stableVersion { + if lhsMessage.id != rhsMessage.id { return false } @@ -60,7 +61,7 @@ private enum RecentCallEntry : TableItemListNodeEntry { return false } else { for i in 0 ..< lhsMessages.count { - if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion { + if lhsMessages[i].id != rhsMessages[i].id { return false } } @@ -118,11 +119,11 @@ private enum RecentCallEntry : TableItemListNodeEntry { let statusText:String if failed { - statusText = tr(.callRecentMissed) + statusText = tr(L10n.callRecentMissed) } else { - let text = outgoing ? tr(.callRecentOutgoing) : tr(.callRecentIncoming) + let text = outgoing ? tr(L10n.callRecentOutgoing) : tr(L10n.callRecentIncoming) if messages.count == 1 { - if let action = messages[0].media.first as? TelegramMediaAction, case .phoneCall(_,_,let duration) = action.action, let value = duration, value > 0 { + if let action = messages[0].media.first as? TelegramMediaAction, case .phoneCall(_, _, let duration, _) = action.action, let value = duration, value > 0 { statusText = text + " (\(String.stringForShortCallDurationSeconds(for: value)))" } else { statusText = text @@ -140,13 +141,13 @@ private enum RecentCallEntry : TableItemListNodeEntry { } - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, height: 46, titleStyle: titleStyle, titleAddition: countText, leftImage: outgoing ? theme.icons.callOutgoing : nil, status: statusText , borderType: [.Right], drawCustomSeparator:true, deleteInset: 10, inset: NSEdgeInsets( left: outgoing ? 10 : theme.icons.callOutgoing.backingSize.width + 15, right: 10), drawSeparatorIgnoringInset: true, interactionType: interactionType, generalType: .context(stateback: {return DateUtils.string(forMessageListDate: messages.first!.timestamp)}), action: { + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 46, titleStyle: titleStyle, titleAddition: countText, leftImage: outgoing ? theme.icons.callOutgoing : nil, status: statusText , borderType: [.Right], drawCustomSeparator:true, deleteInset: 10, inset: NSEdgeInsets( left: outgoing ? 10 : theme.icons.callOutgoing.backingSize.width + 15, right: 10), drawSeparatorIgnoringInset: true, interactionType: interactionType, generalType: .context(DateUtils.string(forMessageListDate: messages.first!.timestamp)), action: { if !editing { arguments.call(peer.id) } }) case .empty(let loading): - return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: loading, text: tr(.recentCallsEmpty), border: [.Right]) + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: loading, text: tr(L10n.recentCallsEmpty), border: [.Right]) } } } @@ -155,13 +156,23 @@ private enum RecentCallEntry : TableItemListNodeEntry { class RecentCallsViewController: NavigationViewController { private var layoutController:LayoutRecentCallsViewController - init(_ account:Account) { - self.layoutController = LayoutRecentCallsViewController(account) - super.init(layoutController) + init(_ context:AccountContext) { + self.layoutController = LayoutRecentCallsViewController(context) + super.init(layoutController, context.window) bar = .init(height: 0) + } + + override func viewDidLoad() { + super.viewDidLoad() self.push(layoutController, false) } + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + navigationBar.frame = NSMakeRect(0, 0, bounds.width, layoutController.bar.height) + layoutController.frame = NSMakeRect(0, layoutController.bar.height, bounds.width, bounds.height - layoutController.bar.height) + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -258,8 +269,7 @@ private func makeEntries(from: [CallListViewEntry], state: RecentCallsController var failed:Bool = false let outgoing: Bool = !message.flags.contains(.Incoming) if let action = message.media.first as? TelegramMediaAction { - if case .phoneCall(_, let discardReason, _) = action.action { - + if case .phoneCall(_, let discardReason, _, _) = action.action { var missed: Bool = false if let reason = discardReason { switch reason { @@ -270,7 +280,6 @@ private func makeEntries(from: [CallListViewEntry], state: RecentCallsController } } failed = !outgoing && missed - } } entries.append(.calls( message, messages, state.editing, failed)) @@ -293,7 +302,7 @@ class LayoutRecentCallsViewController: EditableViewController { private let callDisposable:MetaDisposable = MetaDisposable() private let againDisposable:MetaDisposable = MetaDisposable() private var first:Bool = false - + private let disposable = MetaDisposable() var navigation:NavigationViewController? { @@ -301,11 +310,11 @@ class LayoutRecentCallsViewController: EditableViewController { } override var enableBack: Bool { - return false + return true } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - navigationController?.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + navigationController?.updateLocalizationAndTheme(theme: theme) } override func viewDidLoad() { @@ -313,19 +322,9 @@ class LayoutRecentCallsViewController: EditableViewController { genericView.border = [.Right] self.rightBarView.border = [.Right] - } - - override func update(with state: ViewControllerState) { - super.update(with: state) - self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - updateLocalizationAndTheme() let previous = self.previous let initialSize = self.atomicSize - let account = self.account + let context = self.context let updateState: ((RecentCallsControllerState) -> RecentCallsControllerState) -> Void = { [weak self] f in @@ -334,37 +333,42 @@ class LayoutRecentCallsViewController: EditableViewController { } } - let arguments = RecentCallsArguments(account: account, call: { [weak self] peerId in - self?.callDisposable.set((phoneCall(account, peerId: peerId) |> deliverOnMainQueue).start(next: { result in - applyUIPCallResult(account, result) + let arguments = RecentCallsArguments(context: context, call: { [weak self] peerId in + self?.callDisposable.set((phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: peerId) |> deliverOnMainQueue).start(next: { result in + applyUIPCallResult(context.sharedContext, result) })) - }, removeCalls: { [weak self] messageIds in - _ = deleteMessagesInteractively(postbox: account.postbox, messageIds: messageIds, type: .forLocalPeer).start() - updateState({$0.withAdditionalIgnoringIds(messageIds)}) - - if let strongSelf = self { - strongSelf.againDisposable.set((Signal<()->Void, Void>.single({ [weak strongSelf] in - strongSelf?.viewWillAppear(false) - }) |> delay(1.5, queue: Queue.mainQueue())).start(next: {value in value()})) - } - self?.viewWillAppear(false) + }, removeCalls: { [weak self] messageIds in + _ = deleteMessagesInteractively(account: context.account, messageIds: messageIds, type: .forLocalPeer).start() + updateState({$0.withAdditionalIgnoringIds(messageIds)}) + + if let strongSelf = self { + strongSelf.againDisposable.set((Signal<()->Void, NoError>.single({ [weak strongSelf] in + strongSelf?.viewWillAppear(false) + }) |> delay(1.5, queue: Queue.mainQueue())).start(next: {value in value()})) + } + self?.viewWillAppear(false) }) let callListView:Atomic = Atomic(value: nil) let location:ValuePromise = ValuePromise() - + let first:Atomic = Atomic(value: true) - let signal = location.get() |> distinctUntilChanged |> mapToSignal { index in - return account.viewTracker.callListView(type: .all, index: index, count: 200) + let signal: Signal = location.get() |> distinctUntilChanged |> mapToSignal { index in + return context.account.viewTracker.callListView(type: .all, index: index, count: 100) } - genericView.merge(with: combineLatest(signal |> deliverOn(prepareQueue), statePromise.get() |> deliverOn(prepareQueue), appearanceSignal |> deliverOn(prepareQueue)) |> map { result in + let transition:Signal = combineLatest(queue: prepareQueue, signal, statePromise.get(), appearanceSignal) |> map { result in _ = callListView.swap(result.0) let entries = makeEntries(from: result.0.entries, state: result.1).map({AppearanceWrapperEntry(entry: $0, appearance: result.2)}) return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments, animated: !first.swap(false)) - } |> deliverOnMainQueue) + } |> deliverOnMainQueue + + disposable.set(transition.start(next: { [weak self] transition in + self?.genericView.merge(with: transition) + })) + readyOnce() @@ -393,6 +397,23 @@ class LayoutRecentCallsViewController: EditableViewController { } + override func update(with state: ViewControllerState) { + super.update(with: state) + self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateLocalizationAndTheme(theme: theme) + } + + override func backSettings() -> (String, CGImage?) { + return ("", theme.icons.callSettings) + } + + override func executeReturn() { + showModal(with: CallSettingsModalController(context.sharedContext), for: context.window) + } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -403,6 +424,7 @@ class LayoutRecentCallsViewController: EditableViewController { deinit { callDisposable.dispose() againDisposable.dispose() + disposable.dispose() } } diff --git a/Telegram-Mac/RecentGIFRowItem.swift b/Telegram-Mac/RecentGIFRowItem.swift index fda92991f8..432b4f8977 100644 --- a/Telegram-Mac/RecentGIFRowItem.swift +++ b/Telegram-Mac/RecentGIFRowItem.swift @@ -8,151 +8,8 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox -class RecentGIFRowItem: TableRowItem { - fileprivate let entry:RecentGifRowEntry - fileprivate let row:RecentGifRow - fileprivate let account:Account - fileprivate let arguments:RecentGifsArguments - init(_ initialSize: NSSize, account:Account, entry:RecentGifRowEntry, arguments:RecentGifsArguments) { - self.entry = entry - self.account = account - self.arguments = arguments - switch entry { - case let .gif(index: _, row: r): - self.row = r - } - super.init(initialSize) - } - - override var stableId: AnyHashable { - return entry.stableId - } - - - - override var height: CGFloat { - var height:CGFloat = 120 - for size in row.sizes { - height = min(height, size.height) - } - return height - } - - override func viewClass() -> AnyClass { - return RecentGIFRowView.self - } - -} - - -private var dif:CGFloat = 0 - -class RecentGIFRowView: TableRowView { - private let stickerFetchedDisposable:MetaDisposable = MetaDisposable() - - deinit { - stickerFetchedDisposable.dispose() - removeAllSubviews() - } - - override func set(item: TableRowItem, animated: Bool) { - super.set(item: item, animated: animated) - removeAllSubviews() - if let item = item as? RecentGIFRowItem { - var inset:CGFloat = 0 - for i in 0 ..< item.row.entries.count { - - let view = GIFContainerView() - - view.playerInset = NSEdgeInsets(left: i == 0 ? 2 : 1, right: i == item.row.entries.count - 1 ? 2 : 1, top: i == 0 ? 2 : 1, bottom: i == item.row.entries.count - 1 ? 2 : 1) - - let signal:Signal<(TransformImageArguments) -> DrawingContext?, NoError> - signal = chatWebpageSnippetPhoto(account: item.account, photo: TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: item.row.results[i].previewRepresentations), scale: backingScaleFactor, small:true) - - - view.update(with: item.row.results[i].resource, size: NSMakeSize(item.row.sizes[i].width, item.height), viewSize: item.row.sizes[i] , account: item.account, table: item.table, iconSignal: signal) - - - addSubview(view) - view.setFrameOrigin(inset, 0) - inset += item.row.sizes[i].width - } - - needsLayout = true - } - } - - - - override func mouseUp(with event: NSEvent) { - super.mouseUp(with: event) - let point = convert(event.locationInWindow, from: nil) - if let item = item as? RecentGIFRowItem { - var inset:CGFloat = 0 - var i:Int = 0 - for size in item.row.sizes { - - if point.x > inset && point.x < inset + size.width { - item.arguments.sendGif(item.row.results[i]) - break - } - inset += size.width - i += 1 - } - } - } - - override func layout() { - super.layout() - - if let item = item as? ContextMediaRowItem { - if item.result.isFilled(for: frame.width) { - let drawn = subviews.reduce(0, { (acc, view) -> CGFloat in - return acc + view.frame.width - }) - if drawn < frame.width { - dif = (frame.width - drawn) / CGFloat(subviews.count + 1) - var inset:CGFloat = dif - for subview in subviews { - subview.setFrameOrigin(inset, 0) - inset += (dif + subview.frame.width) - } - } - } else { - var inset:CGFloat = dif - for subview in subviews { - subview.setFrameOrigin(inset, 0) - inset += (dif + subview.frame.width) - } - } - } - } - - override func menu(for event: NSEvent) -> NSMenu? { - let menu = NSMenu() - menu.addItem(ContextMenuItem(tr(.contextRecentGifRemove), handler: { [weak self] in - if let item = self?.item as? RecentGIFRowItem, let point = self?.convert(mainWindow.mouseLocationOutsideOfEventStream, from: nil) { - var inset:CGFloat = 0 - var i:Int = 0 - for size in item.row.sizes { - - if point.x > inset && point.x < inset + size.width { - if let id = item.row.results[i].id { - _ = removeSavedGif(postbox: item.account.postbox, mediaId: id).start() - } - break - } - inset += size.width - i += 1 - } - } - })) - - return menu - } - -} diff --git a/Telegram-Mac/RecentPeerRowItem.swift b/Telegram-Mac/RecentPeerRowItem.swift index 22ab205fac..f0c7cb30c3 100644 --- a/Telegram-Mac/RecentPeerRowItem.swift +++ b/Telegram-Mac/RecentPeerRowItem.swift @@ -8,18 +8,29 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class RecentPeerRowItem: ShortPeerRowItem { - let removeAction:()->Void - let canRemoveFromRecent:Bool - - init(_ initialSize:NSSize, peer: Peer, account:Account, stableId:AnyHashable? = nil, enabled: Bool = true, height:CGFloat = 50, photoSize:NSSize = NSMakeSize(36, 36), titleStyle:ControlStyle = ControlStyle(font:.medium(.title), foregroundColor: theme.colors.text, highlightColor: .white), titleAddition:String? = nil, leftImage:CGImage? = nil, statusStyle:ControlStyle = ControlStyle(font:.normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status:String? = nil, borderType:BorderType = [], drawCustomSeparator:Bool = true, deleteInset:CGFloat? = nil, drawLastSeparator:Bool = false, inset:NSEdgeInsets = NSEdgeInsets(left:10.0), drawSeparatorIgnoringInset: Bool = false, interactionType:ShortPeerItemInteractionType = .plain, generalType:GeneralInteractedType = .none, action:@escaping ()->Void = {}, canRemoveFromRecent: Bool = false, removeAction:@escaping()->Void = {}) { + fileprivate let removeAction:()->Void + fileprivate let canRemoveFromRecent:Bool + fileprivate let badge: BadgeNode? + init(_ initialSize:NSSize, peer: Peer, account:Account, stableId:AnyHashable? = nil, enabled: Bool = true, height:CGFloat = 50, photoSize:NSSize = NSMakeSize(36, 36), titleStyle:ControlStyle = ControlStyle(font:.medium(.title), foregroundColor: theme.colors.text, highlightColor: .white), titleAddition:String? = nil, leftImage:CGImage? = nil, statusStyle:ControlStyle = ControlStyle(font:.normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status:String? = nil, borderType:BorderType = [], drawCustomSeparator:Bool = true, isLookSavedMessage: Bool = false, deleteInset:CGFloat? = nil, drawLastSeparator:Bool = false, inset:NSEdgeInsets = NSEdgeInsets(left:10.0), drawSeparatorIgnoringInset: Bool = false, interactionType:ShortPeerItemInteractionType = .plain, generalType:GeneralInteractedType = .none, action:@escaping ()->Void = {}, canRemoveFromRecent: Bool = false, removeAction:@escaping()->Void = {}, contextMenuItems:@escaping()->Signal<[ContextMenuItem], NoError> = { .single([]) }, unreadBadge: UnreadSearchBadge = .none) { self.canRemoveFromRecent = canRemoveFromRecent self.removeAction = removeAction - super.init(initialSize, peer: peer, account: account, stableId: stableId, enabled: enabled, height: height, photoSize: photoSize, titleStyle: titleStyle, titleAddition: titleAddition, leftImage: leftImage, statusStyle: statusStyle, status: status, borderType: borderType, drawCustomSeparator: drawCustomSeparator, deleteInset: deleteInset, drawLastSeparator: drawLastSeparator, inset: inset, drawSeparatorIgnoringInset: drawSeparatorIgnoringInset, interactionType: interactionType, generalType: generalType, action: action) + switch unreadBadge { + case let .muted(count): + badge = BadgeNode(.initialize(string: "\(count)", color: theme.chatList.badgeTextColor, font: .medium(.small)), theme.chatList.badgeMutedBackgroundColor) + case let .unmuted(count): + badge = BadgeNode(.initialize(string: "\(count)", color: theme.chatList.badgeTextColor, font: .medium(.small)), theme.chatList.badgeBackgroundColor) + case .none: + self.badge = nil + } + + super.init(initialSize, peer: peer, account: account, stableId: stableId, enabled: enabled, height: height, photoSize: photoSize, titleStyle: titleStyle, titleAddition: titleAddition, leftImage: leftImage, statusStyle: statusStyle, status: status, borderType: borderType, drawCustomSeparator: drawCustomSeparator, isLookSavedMessage: isLookSavedMessage, deleteInset: deleteInset, drawLastSeparator: drawLastSeparator, inset: inset, drawSeparatorIgnoringInset: drawSeparatorIgnoringInset, interactionType: interactionType, generalType: generalType, action: action, contextMenuItems: contextMenuItems, highlightVerified: true) } @@ -28,17 +39,19 @@ class RecentPeerRowItem: ShortPeerRowItem { } override var textAdditionInset:CGFloat { - return 15 + return 20 + (highlightVerified ? 25 : 0) } } class RecentPeerRowView : ShortPeerRowView { private var trackingArea:NSTrackingArea? private let removeControl:ImageButton = ImageButton() + private var badgeView:View? + required init(frame frameRect: NSRect) { super.init(frame: frameRect) //removeControl.autohighlight = false - + layerContentsRedrawPolicy = .onSetNeedsDisplay removeControl.isHidden = true removeControl.set(handler: { [weak self] _ in @@ -93,30 +106,56 @@ class RecentPeerRowView : ShortPeerRowView { } override func updateMouse() { - if mouseInside() { + if mouseInside(), removeControl.superview != nil { removeControl.isHidden = false + badgeView?.isHidden = true } else { removeControl.isHidden = true + badgeView?.isHidden = false } } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) removeControl.set(image: isSelect ? theme.icons.recentDismissActive : theme.icons.recentDismiss, for: .Normal) - removeControl.sizeToFit() + _ = removeControl.sizeToFit() if let item = item as? RecentPeerRowItem { if item.canRemoveFromRecent { addSubview(removeControl) } else { removeControl.removeFromSuperview() } + + if let badgeNode = item.badge { + if badgeView == nil { + badgeView = View() + addSubview(badgeView!) + } + badgeView?.setFrameSize(badgeNode.size) + badgeNode.view = badgeView + badgeNode.setNeedDisplay() + } else { + badgeView?.removeFromSuperview() + badgeView = nil + } } needsLayout = true } + override var backdorColor: NSColor { + if let item = item { + return item.isHighlighted && !item.isSelected ? theme.colors.grayForeground : super.backdorColor + } else { + return super.backdorColor + } + } + override func layout() { super.layout() - removeControl.centerY(x: frame.width - removeControl.frame.width - 10) + removeControl.centerY(x: frame.width - removeControl.frame.width - 13) + if let badgeView = badgeView { + badgeView.centerY(x: frame.width - badgeView.frame.width - 10) + } } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/RecentSessionRowItem.swift b/Telegram-Mac/RecentSessionRowItem.swift index 9fadf72ed6..7281703bce 100644 --- a/Telegram-Mac/RecentSessionRowItem.swift +++ b/Telegram-Mac/RecentSessionRowItem.swift @@ -8,52 +8,57 @@ import Cocoa import TGUIKit -import TelegramCoreMac -class RecentSessionRowItem: TableRowItem { +import TelegramCore +import SyncCore +class RecentSessionRowItem: GeneralRowItem { let session:RecentAccountSession - let _stableId:AnyHashable let headerLayout:TextViewLayout let descLayout:TextViewLayout let dateLayout:TextViewLayout let revoke:()->Void - override var stableId: AnyHashable { - return _stableId - } + - init(_ initialSize: NSSize, session:RecentAccountSession, stableId:AnyHashable, revoke: @escaping()->Void) { - self._stableId = stableId + init(_ initialSize: NSSize, session:RecentAccountSession, stableId:AnyHashable, viewType: GeneralViewType, revoke: @escaping()->Void) { self.session = session self.revoke = revoke headerLayout = TextViewLayout(.initialize(string: session.appName + " " + session.appVersion, color: theme.colors.text, font: .normal(.title))) let attr = NSMutableAttributedString() - _ = attr.append(string: session.deviceModel + ", " + session.platform + " " + session.systemVersion, color: theme.colors.text, font: .normal(.text)) + + var trimmed = session.deviceModel.trimmingCharacters(in: CharacterSet(charactersIn: "1234567890,")) + + if trimmed.hasSuffix("Pro") || trimmed.hasSuffix("Air") { + trimmed = trimmed.nsstring.substring(to: trimmed.length - 3) + " " + trimmed.nsstring.substring(from: trimmed.length - 3) + } + + _ = attr.append(string:trimmed + ", " + session.platform + " " + session.systemVersion, color: theme.colors.text, font: .normal(.text)) _ = attr.append(string: "\n") _ = attr.append(string: session.ip + " " + session.country, color: theme.colors.grayText, font: .normal(.text)) - descLayout = TextViewLayout(attr, lineSpacing: 2) + descLayout = TextViewLayout(attr, maximumNumberOfLines: 2, lineSpacing: 2) - dateLayout = TextViewLayout(.initialize(string: session.isCurrent ? tr(.peerStatusOnline) : DateUtils.string(forMessageListDate: session.creationDate), color: session.isCurrent ? theme.colors.blueText : theme.colors.grayText, font: .normal(.text))) + dateLayout = TextViewLayout(.initialize(string: session.isCurrent ? tr(L10n.peerStatusOnline) : DateUtils.string(forMessageListDate: session.activityDate), color: session.isCurrent ? theme.colors.accent : theme.colors.grayText, font: .normal(.text))) - super.init(initialSize) + super.init(initialSize, stableId: stableId, viewType: viewType) _ = makeSize(initialSize.width, oldWidth: initialSize.width) } override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { - headerLayout.measure(width: width - 60) - descLayout.measure(width: width - 60) + let success = super.makeSize(width, oldWidth: oldWidth) + headerLayout.measure(width: blockWidth - 80) + descLayout.measure(width: blockWidth - 80) dateLayout.measure(width: .greatestFiniteMagnitude) - return super.makeSize(width, oldWidth: oldWidth) + return success } override var height: CGFloat { - return 70 + return 75 } override func viewClass() -> AnyClass { @@ -61,7 +66,8 @@ class RecentSessionRowItem: TableRowItem { } } -class RecentSessionRowView : TableRowView { +class RecentSessionRowView : TableRowView, ViewDisplayDelegate { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) let headerTextView = TextView() let descTextView = TextView() let dateTextView = TextView() @@ -71,14 +77,18 @@ class RecentSessionRowView : TableRowView { reset.set(font: .normal(.title), for: .Normal) super.init(frame: frameRect) - addSubview(headerTextView) - addSubview(descTextView) - addSubview(dateTextView) - addSubview(reset) + containerView.addSubview(headerTextView) + containerView.addSubview(descTextView) + containerView.addSubview(dateTextView) + containerView.addSubview(reset) + + addSubview(containerView) + + containerView.displayDelegate = self reset.set(handler: { [weak self] _ in if let item = self?.item as? RecentSessionRowItem { - confirm(for: mainWindow, with: appName, and: tr(.recentSessionsConfirmRevoke), successHandler: { _ in + confirm(for: mainWindow, information: tr(L10n.recentSessionsConfirmRevoke), successHandler: { _ in item.revoke() }) } @@ -86,30 +96,47 @@ class RecentSessionRowView : TableRowView { } override func updateColors() { - super.updateColors() headerTextView.backgroundColor = backdorColor descTextView.backgroundColor = backdorColor dateTextView.backgroundColor = backdorColor + containerView.backgroundColor = backdorColor + if let item = item as? RecentSessionRowItem { + self.background = item.viewType.rowBackground + } + } + + override var backdorColor: NSColor { + return theme.colors.background } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(30, frame.height - .borderSize, frame.width - 60, .borderSize)) + if let item = item as? RecentSessionRowItem, layer == containerView.layer { + ctx.setFillColor(theme.colors.border.cgColor) + switch item.viewType { + case .legacy: + ctx.fill(NSMakeRect(30, containerView.frame.height - .borderSize, frame.width - 60, .borderSize)) + case let .modern(position, insets): + if position.border { + ctx.fill(NSMakeRect(insets.left, containerView.frame.height - .borderSize, containerView.frame.width - insets.left - insets.right, .borderSize)) + } + } + } } override func set(item: TableRowItem, animated: Bool) { super.set(item: item) - - reset.set(text: tr(.recentSessionsRevoke), for: .Normal) - reset.set(color: theme.colors.blueUI, for: .Normal) + + reset.set(text: tr(L10n.recentSessionsRevoke), for: .Normal) + reset.set(color: theme.colors.accent, for: .Normal) reset.set(background: theme.colors.background, for: .Normal) - reset.sizeToFit() + _ = reset.sizeToFit() if let item = item as? RecentSessionRowItem { reset.isHidden = item.session.isCurrent + containerView.setCorners(item.viewType.corners, animated: animated) } self.needsLayout = true @@ -118,10 +145,22 @@ class RecentSessionRowView : TableRowView { override func layout() { super.layout() if let item = item as? RecentSessionRowItem { - headerTextView.update(item.headerLayout, origin: NSMakePoint(30, 10)) - descTextView.update(item.descLayout, origin: NSMakePoint(30, headerTextView.frame.maxY + 4)) - dateTextView.update(item.dateLayout, origin: NSMakePoint(frame.width - 30 - item.dateLayout.layoutSize.width, 10)) - reset.setFrameOrigin(frame.width - 30 - reset.frame.width, frame.height - reset.frame.height - 10) + switch item.viewType { + case .legacy: + self.containerView.frame = self.bounds + self.containerView.setCorners([]) + self.headerTextView.update(item.headerLayout, origin: NSMakePoint(30, 10)) + self.descTextView.update(item.descLayout, origin: NSMakePoint(30, headerTextView.frame.maxY + 4)) + self.dateTextView.update(item.dateLayout, origin: NSMakePoint(self.containerView.frame.width - 30 - item.dateLayout.layoutSize.width, 10)) + self.reset.setFrameOrigin(frame.width - 25 - reset.frame.width, self.containerView.frame.height - reset.frame.height - 10) + case let .modern(position, insets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + self.headerTextView.update(item.headerLayout, origin: NSMakePoint(insets.left, insets.top)) + self.descTextView.update(item.descLayout, origin: NSMakePoint(insets.left, headerTextView.frame.maxY + 4)) + self.dateTextView.update(item.dateLayout, origin: NSMakePoint(self.containerView.frame.width - insets.right - item.dateLayout.layoutSize.width, insets.top)) + self.reset.setFrameOrigin(self.containerView.frame.width - insets.right + 5 - reset.frame.width, self.containerView.frame.height - reset.frame.height - 7) + } } } diff --git a/Telegram-Mac/RecentSessionsController.swift b/Telegram-Mac/RecentSessionsController.swift index e3de27c2f2..b163b7f6fc 100644 --- a/Telegram-Mac/RecentSessionsController.swift +++ b/Telegram-Mac/RecentSessionsController.swift @@ -10,17 +10,18 @@ import Cocoa import Foundation import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore private final class RecentSessionsControllerArguments { - let account: Account + let context: AccountContext let removeSession: (Int64) -> Void let terminateOthers:() -> Void - init(account: Account, removeSession: @escaping (Int64) -> Void, terminateOthers: @escaping()->Void) { - self.account = account + init(context: AccountContext, removeSession: @escaping (Int64) -> Void, terminateOthers: @escaping()->Void) { + self.context = context self.removeSession = removeSession self.terminateOthers = terminateOthers } @@ -42,39 +43,21 @@ private enum RecentSessionsEntryStableId: Hashable { } } - static func ==(lhs: RecentSessionsEntryStableId, rhs: RecentSessionsEntryStableId) -> Bool { - switch lhs { - case let .session(hash): - if case .session(hash) = rhs { - return true - } else { - return false - } - case let .index(index): - if case .index(index) = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } } private enum RecentSessionsEntry: Comparable, Identifiable { case loading(sectionId:Int) - case currentSessionHeader(sectionId:Int) - case currentSession(sectionId:Int, RecentAccountSession) - case terminateOtherSessions(sectionId:Int) - case currentSessionInfo(sectionId:Int) + case currentSessionHeader(sectionId:Int, viewType: GeneralViewType) + case currentSession(sectionId:Int, RecentAccountSession, viewType: GeneralViewType) + case terminateOtherSessions(sectionId:Int, viewType: GeneralViewType) + case currentSessionInfo(sectionId:Int, viewType: GeneralViewType) + + case otherSessionsHeader(sectionId:Int, viewType: GeneralViewType) + + case incompleteHeader(sectionId: Int, viewType: GeneralViewType) + case incompleteDesc(sectionId: Int, viewType: GeneralViewType) - case otherSessionsHeader(sectionId:Int) - case session(sectionId:Int, index: Int32, session: RecentAccountSession, enabled: Bool, editing: Bool) + case session(sectionId:Int, index: Int32, session: RecentAccountSession, enabled: Bool, editing: Bool, viewType: GeneralViewType) case section(sectionId:Int) @@ -91,9 +74,13 @@ private enum RecentSessionsEntry: Comparable, Identifiable { return .index(3) case .currentSessionInfo: return .index(4) - case .otherSessionsHeader: + case .incompleteHeader: return .index(5) - case let .session(_, _, session, _, _): + case .incompleteDesc: + return .index(6) + case .otherSessionsHeader: + return .index(7) + case let .session(_, _, session, _, _, _): return .session(session.hash) case let .section(sectionId): return .section(sectionId) @@ -112,9 +99,13 @@ private enum RecentSessionsEntry: Comparable, Identifiable { return 3 case .currentSessionInfo: return 4 - case .otherSessionsHeader: + case .incompleteHeader: return 5 - case let .session(_, _, _, _, _): + case .incompleteDesc: + return 6 + case .otherSessionsHeader: + return 7 + case .session: fatalError() case let .section(sectionId): return (sectionId + 1) * 1000 - sectionId @@ -125,17 +116,21 @@ private enum RecentSessionsEntry: Comparable, Identifiable { switch self { case let .loading(sectionId): return sectionId - case let .currentSessionHeader(sectionId): + case let .currentSessionHeader(sectionId, _): + return sectionId + case let .currentSession(sectionId, _, _): + return sectionId + case let .terminateOtherSessions(sectionId, _): return sectionId - case let .currentSession(sectionId, _): + case let .currentSessionInfo(sectionIdv): return sectionId - case let .terminateOtherSessions(sectionId): + case let .incompleteHeader(sectionId, _): return sectionId - case let .currentSessionInfo(sectionId): + case let .incompleteDesc(sectionId, _): return sectionId - case let .otherSessionsHeader(sectionId): + case let .otherSessionsHeader(sectionId, _): return sectionId - case let .session(sectionId, _, _, _, _): + case let .session(sectionId, _, _, _, _, _): return sectionId case let .section(sectionId): return sectionId @@ -146,43 +141,27 @@ private enum RecentSessionsEntry: Comparable, Identifiable { switch self { case let .loading(sectionId): return (sectionId * 1000) + stableIndex - case let .currentSessionHeader(sectionId): + case let .currentSessionHeader(sectionId, _): return (sectionId * 1000) + stableIndex - case let .currentSession(sectionId, _): + case let .currentSession(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .terminateOtherSessions(sectionId): + case let .terminateOtherSessions(sectionId, _): return (sectionId * 1000) + stableIndex - case let .currentSessionInfo(sectionId): + case let .currentSessionInfo(sectionId, _): return (sectionId * 1000) + stableIndex - case let .otherSessionsHeader(sectionId): + case let .incompleteHeader(sectionId, _): return (sectionId * 1000) + stableIndex - case let .session(sectionId, index, _, _, _): + case let .incompleteDesc(sectionId, _): + return (sectionId * 1000) + stableIndex + case let .otherSessionsHeader(sectionId, _): + return (sectionId * 1000) + stableIndex + case let .session(sectionId, index, _, _, _, _): return (sectionId * 1000) + Int(index) + 100 case let .section(sectionId): return (sectionId * 1000) + stableIndex } } - static func ==(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool { - switch lhs { - case .currentSessionHeader, .terminateOtherSessions, .currentSessionInfo, .otherSessionsHeader, .section, .loading: - return lhs.stableId == rhs.stableId && lhs.sectionId == rhs.sectionId - case let .currentSession(sectionId, session): - if case .currentSession(sectionId, session) = rhs { - return true - } else { - return false - } - case let .session(sectionId, index, session, enabled, editing): - if case .session(sectionId, index, session, enabled, editing) = rhs { - return true - } else { - return false - } - } - } - - static func <(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool { return lhs.sortIndex < rhs.sortIndex @@ -190,24 +169,28 @@ private enum RecentSessionsEntry: Comparable, Identifiable { func item(_ arguments: RecentSessionsControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case .currentSessionHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.sessionsCurrentSessionHeader)) - case let .currentSession(_, session): - return RecentSessionRowItem(initialSize, session: session, stableId: stableId, revoke: {}) - case .terminateOtherSessions: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.sessionsTerminateOthers), nameStyle: redActionButton, type: .none, action: { + case let .currentSessionHeader(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.sessionsCurrentSessionHeader, viewType: viewType) + case let .currentSession(_, session, viewType): + return RecentSessionRowItem(initialSize, session: session, stableId: stableId, viewType: viewType, revoke: {}) + case let .terminateOtherSessions(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.sessionsTerminateOthers, nameStyle: redActionButton, type: .none, viewType: viewType, action: { arguments.terminateOthers() }) - case .currentSessionInfo: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.sessionsTerminateDescription)) - case .otherSessionsHeader: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.sessionsActiveSessionsHeader)) - case let .session(_, _, session, _, _): - return RecentSessionRowItem(initialSize, session: session, stableId: stableId, revoke: { + case let .currentSessionInfo(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.sessionsTerminateDescription, viewType: viewType) + case let .incompleteHeader(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.recentSessionsIncompleteAttemptHeader, viewType: viewType) + case let .incompleteDesc(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.recentSessionsIncompleteAttemptDesc, viewType: viewType) + case let .otherSessionsHeader(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.sessionsActiveSessionsHeader, viewType: viewType) + case let .session(_, _, session, _, _, viewType): + return RecentSessionRowItem(initialSize, session: session, stableId: stableId, viewType: viewType, revoke: { arguments.removeSession(session.hash) }) case .section(sectionId: _): - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) case .loading: return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) } @@ -268,34 +251,57 @@ private func recentSessionsControllerEntries(state: RecentSessionsControllerStat sectionId += 1 var existingSessionIds = Set() - entries.append(.currentSessionHeader(sectionId: sectionId)) - if let index = sessions.index(where: { $0.hash == 0 }) { + entries.append(.currentSessionHeader(sectionId: sectionId, viewType: .textTopItem)) + if let index = sessions.firstIndex(where: { $0.hash == 0 }) { existingSessionIds.insert(sessions[index].hash) - entries.append(.currentSession(sectionId: sectionId, sessions[index])) + entries.append(.currentSession(sectionId: sectionId, sessions[index], viewType: .firstItem)) } - entries.append(.terminateOtherSessions(sectionId: sectionId)) - entries.append(.currentSessionInfo(sectionId: sectionId)) + entries.append(.terminateOtherSessions(sectionId: sectionId, viewType: .lastItem)) + entries.append(.currentSessionInfo(sectionId: sectionId, viewType: .textBottomItem)) if sessions.count > 1 { entries.append(.section(sectionId: sectionId)) sectionId += 1 - entries.append(.section(sectionId: sectionId)) - sectionId += 1 - entries.append(.otherSessionsHeader(sectionId: sectionId)) - let filteredSessions: [RecentAccountSession] = sessions.sorted(by: { lhs, rhs in return lhs.activityDate > rhs.activityDate }) - for i in 0 ..< filteredSessions.count { - if !existingSessionIds.contains(sessions[i].hash) { - existingSessionIds.insert(sessions[i].hash) - let session = sessions[i] - let enabled = state.removingSessionId != sessions[i].hash - entries.append(.session(sectionId: sectionId, index: Int32(i), session: session, enabled: enabled, editing: state.editing)) + let nonApplied = filteredSessions.filter {$0.flags.contains(.passwordPending)} + let applied = filteredSessions.filter {!$0.flags.contains(.passwordPending)} + + var index: Int32 = 0 + + if !nonApplied.isEmpty { + entries.append(.incompleteHeader(sectionId: sectionId, viewType: .textTopItem)) + + let nonApplied = nonApplied.filter({ + !existingSessionIds.contains($0.hash) + }) + for session in nonApplied { + existingSessionIds.insert(session.hash) + let enabled = state.removingSessionId != session.hash + entries.append(.session(sectionId: sectionId, index: index, session: session, enabled: enabled, editing: state.editing, viewType: bestGeneralViewType(nonApplied, for: session))) + index += 1 } + entries.append(.incompleteDesc(sectionId: sectionId, viewType: .textBottomItem)) + + entries.append(.section(sectionId: sectionId)) + sectionId += 1 + } + + entries.append(.otherSessionsHeader(sectionId: sectionId, viewType: .textTopItem)) + let newApplied = applied.filter({ + !existingSessionIds.contains($0.hash) + }) + for session in newApplied { + existingSessionIds.insert(session.hash) + let enabled = state.removingSessionId != session.hash + entries.append(.session(sectionId: sectionId, index: index, session: session, enabled: enabled, editing: state.editing, viewType: bestGeneralViewType(newApplied, for: session))) + index += 1 } + entries.append(.section(sectionId: sectionId)) + sectionId += 1 } } else { entries.append(.loading(sectionId: 1)) @@ -316,26 +322,30 @@ private func prepareSessions(left:[AppearanceWrapperEntry], class RecentSessionsController : TableViewController { override func viewDidLoad() { + super.viewDidLoad() + + + let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: RecentSessionsControllerState()) let updateState: ((RecentSessionsControllerState) -> RecentSessionsControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - let account = self.account + let context = self.context let initialSize = self.atomicSize let actionsDisposable = DisposableSet() let removeSessionDisposable = MetaDisposable() actionsDisposable.add(removeSessionDisposable) - let sessionsPromise = Promise<[RecentAccountSession]?>(nil) + let sessionsPromise = Promise<[RecentAccountSession]?>() - let arguments = RecentSessionsControllerArguments(account: account, removeSession: { sessionId in + let arguments = RecentSessionsControllerArguments(context: context, removeSession: { sessionId in updateState { return $0.withUpdatedRemovingSessionId(sessionId) } - let applySessions: Signal = sessionsPromise.get() + let applySessions: Signal = sessionsPromise.get() |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue @@ -352,9 +362,10 @@ class RecentSessionsController : TableViewController { } return .complete() - } + } |> mapError {_ in return .generic} + - removeSessionDisposable.set((terminateAccountSession(account: account, hash: sessionId) |> then(applySessions) |> deliverOnMainQueue).start(error: { _ in + removeSessionDisposable.set((terminateAccountSession(account: context.account, hash: sessionId) |> then(applySessions) |> deliverOnMainQueue).start(error: { _ in updateState { return $0.withUpdatedRemovingSessionId(nil) } @@ -364,10 +375,16 @@ class RecentSessionsController : TableViewController { } })) }, terminateOthers: { - _ = (confirmSignal(for: mainWindow, header: appName, information: tr(.recentSessionsConfirmTerminateOthers)) |> filter {$0} |> map {_ in} |> mapToSignal{terminateOtherAccountSessions(account: account)}).start() + confirm(for: context.window, information: L10n.recentSessionsConfirmTerminateOthers, successHandler: { _ in + _ = showModalProgress(signal: terminateOtherAccountSessions(account: context.account), for: context.window).start(error: { error in + + }) + }) }) - let sessionsSignal: Signal<[RecentAccountSession]?, NoError> = .single(nil) |> then(requestRecentAccountSessions(account: account) |> map { Optional($0) }) + let sessionsSignal: Signal<[RecentAccountSession]?, NoError> = context.activeSessionsContext.state |> map { + $0.sessions + } sessionsPromise.set(sessionsSignal) @@ -383,6 +400,16 @@ class RecentSessionsController : TableViewController { } genericView.merge(with: signal) + + genericView.setScrollHandler { position in + switch position.direction { + case .bottom: + context.activeSessionsContext.loadMore() + default: + break + } + } + readyOnce() } diff --git a/Telegram-Mac/RecentUsedEmoji.swift b/Telegram-Mac/RecentUsedEmoji.swift index b35448dac2..e522b4ee3c 100644 --- a/Telegram-Mac/RecentUsedEmoji.swift +++ b/Telegram-Mac/RecentUsedEmoji.swift @@ -7,35 +7,107 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit + +struct EmojiSkinModifier : PostboxCoding, Equatable { + let emoji: String + let modifier: String + init(emoji: String, modifier: String) { + var emoji = emoji + for skin in emoji.emojiSkinToneModifiers { + emoji = emoji.replacingOccurrences(of: skin, with: "") + } + self.emoji = emoji + self.modifier = modifier + } + func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(emoji, forKey: "e") + encoder.encodeString(modifier, forKey: "m") + } + + var modify: String { + var e:String = emoji + if emoji.length == 5 { + let mutable = NSMutableString() + mutable.insert(e, at: 0) + mutable.insert(modifier, at: 2) + e = mutable as String + } else { + e = emoji + modifier + } + return e + } + + init(decoder: PostboxDecoder) { + self.emoji = decoder.decodeStringForKey("e", orElse: "") + self.modifier = decoder.decodeStringForKey("m", orElse: "") + var bp:Int = 0 + bp += 1 + } +} class RecentUsedEmoji: PreferencesEntry, Equatable { - let emojies:[String] - let skinModifiers:[String] - init(emojies:[String], skinModifiers: [String]) { - self.emojies = emojies + private let _emojies:[String] + let skinModifiers:[EmojiSkinModifier] + init(emojies:[String], skinModifiers: [EmojiSkinModifier]) { + self._emojies = emojies self.skinModifiers = skinModifiers } public static var defaultSettings: RecentUsedEmoji { - return RecentUsedEmoji(emojies: ["😂", "😘", "❤️", "😍", "😊", "🤔", "😁", "👍", "☺️", "😔", "😄", "😭", "💋", "😒", "😳", "😜", "🙈", "😉", "😃", "😢", "😝", "😱", "😡", "😏", "😞", "😅", "😚", "🙊", "😌", "😀", "😋", "😆", "😐", "😕", "👎"], skinModifiers: []) + return RecentUsedEmoji(emojies: ["😂", "😘", "❤️", "😍", "😊", "🤔", "😁", "👍", "☺️", "😔", "😄", "😭", "💋", "😒", "😳", "😜", "🙈", "😉", "😃", "😢", "😝", "😱", "😡", "😏", "😞", "😅", "😚", "🙊", "😌", "😀", "😋", "😆", "🌚", "😐", "😕", "👎", diceSymbol, dartSymbol], skinModifiers: []) + } + + var emojies: [String] { + var isset:[String: String] = [:] + var list:[String] = [] + for emoji in _emojies { + if isset[emoji] == nil, emoji != "�", !emoji.emojiSkinToneModifiers.contains(emoji), emoji != "️" { + var emoji = emoji + isset[emoji] = emoji + for skin in skinModifiers { + if skin.emoji == emoji { + emoji = skin.modify + } + } + list.append(emoji) + } + } + return list.reduce([], { current, value in + var value = value + if let modifier = value.emojiSkinToneModifiers.first(where: { value.contains($0) }), value.glyphCount > 1 { + value = value.replacingOccurrences(of: modifier, with: "") + } + if let first = value.first { + return current + [String(first)] + } else { + return current + } + }) } public required init(decoder: PostboxDecoder) { let emojies = decoder.decodeStringArrayForKey("e") - + self.skinModifiers = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("sm_new", decoder: {EmojiSkinModifier(decoder: $0)})) ?? [] + var isset:[String: String] = [:] var list:[String] = [] for emoji in emojies { - if isset[emoji] == nil { - list.append(emoji) + if isset[emoji] == nil, emoji != "�", !emoji.emojiSkinToneModifiers.contains(emoji), emoji != "️" { + var emoji = emoji isset[emoji] = emoji + for skin in skinModifiers { + if skin.emoji == emoji { + emoji = skin.modify + } + } + list.append(emoji) } } - self.emojies = list + self._emojies = list + - self.skinModifiers = decoder.decodeStringArrayForKey("sm") } @@ -49,8 +121,8 @@ class RecentUsedEmoji: PreferencesEntry, Equatable { } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeStringArray(emojies, forKey: "e") - encoder.encodeStringArray(skinModifiers, forKey: "sm") + encoder.encodeStringArray(_emojies, forKey: "e") + encoder.encodeObjectArray(skinModifiers, forKey: "sm_new") } } @@ -59,9 +131,9 @@ func ==(lhs: RecentUsedEmoji, rhs: RecentUsedEmoji) -> Bool { } -func saveUsedEmoji(_ list:[String], postbox:Postbox) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.recentEmoji, { entry in +func saveUsedEmoji(_ list:[String], postbox:Postbox) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.recentEmoji, { entry in var emojies: [String] if let entry = entry as? RecentUsedEmoji { emojies = entry.emojies @@ -70,48 +142,48 @@ func saveUsedEmoji(_ list:[String], postbox:Postbox) -> Signal { } for emoji in list.reversed() { - let emoji = emoji.emojiString - if !emoji.isEmpty { - if let index = emojies.index(of: emoji) { - emojies.remove(at: index) + if emoji.containsOnlyEmoji { + let emoji = emoji.emojiString.emojiUnmodified + if !emoji.isEmpty && emoji.count == 1 { + if let index = emojies.firstIndex(of: emoji) { + emojies.remove(at: index) + } + emojies.insert(emoji, at: 0) } - emojies.insert(emoji, at: 0) } } - emojies = Array(emojies.prefix(35)) + emojies = Array(emojies.filter({$0.containsEmoji}).prefix(35)) return RecentUsedEmoji(emojies: emojies, skinModifiers: (entry as? RecentUsedEmoji)?.skinModifiers ?? []) }) } } -func modifySkinEmoji(_ emoji:String, postbox: Postbox) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.recentEmoji, { entry in - if let settings = (entry as? RecentUsedEmoji) { - var skinModifiers = settings.skinModifiers - var index:Int? = nil - for i in 0 ..< skinModifiers.count { - let local = skinModifiers[i] - if emoji.emojiUnmodified == local.emojiUnmodified { - index = i - } - } - +func modifySkinEmoji(_ emoji:String, modifier: String?, postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.recentEmoji, { entry in + let settings = (entry as? RecentUsedEmoji) ?? RecentUsedEmoji.defaultSettings + var skinModifiers = settings.skinModifiers + let index:Int? = skinModifiers.firstIndex(where: {$0.emoji == emoji}) + + if let modifier = modifier { if let index = index { - skinModifiers[index] = emoji + skinModifiers[index] = EmojiSkinModifier(emoji: emoji, modifier: modifier) } else { - skinModifiers.append(emoji) + skinModifiers.append(EmojiSkinModifier(emoji: emoji, modifier: modifier)) } - return RecentUsedEmoji(emojies: settings.emojies, skinModifiers: skinModifiers) + } else if let index = index { + skinModifiers.remove(at: index) } + + return RecentUsedEmoji(emojies: settings.emojies, skinModifiers: skinModifiers) - return entry }) } } -func recentUsedEmoji(postbox: Postbox) -> Signal { +func recentUsedEmoji(postbox: Postbox) -> Signal { return postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.recentEmoji]) |> map { preferences in + return (preferences.values[ApplicationSpecificPreferencesKeys.recentEmoji] as? RecentUsedEmoji) ?? RecentUsedEmoji.defaultSettings } } diff --git a/Telegram-Mac/Release.xcconfig b/Telegram-Mac/Release.xcconfig index beb2aad321..319a9d8081 100644 --- a/Telegram-Mac/Release.xcconfig +++ b/Telegram-Mac/Release.xcconfig @@ -10,4 +10,4 @@ DSA_PEM_FILE = dsa_pub_prod.pem SIMPLE_SLASH=/ SFEED_URL = https:$(SIMPLE_SLASH)/osx.telegram.org/updates/versions.xml - +APPCENTER_SECRET = 0af668a6-29fa-4a9e-8a70-002913f8efba diff --git a/Telegram-Mac/RenderedTotalUnreadCount.swift b/Telegram-Mac/RenderedTotalUnreadCount.swift new file mode 100644 index 0000000000..1c303cc6a3 --- /dev/null +++ b/Telegram-Mac/RenderedTotalUnreadCount.swift @@ -0,0 +1,67 @@ +import Foundation +import Postbox +import SwiftSignalKit + +enum RenderedTotalUnreadCountType { + case raw + case filtered +} + +func renderedTotalUnreadCount(transaction: Transaction) -> (Int32, RenderedTotalUnreadCountType) { + let totalUnreadState = transaction.getTotalUnreadState(groupId: .root) + let inAppSettings: InAppNotificationSettings = (transaction.getPreferencesEntry(key: ApplicationSharedPreferencesKeys.inAppNotificationSettings) as? InAppNotificationSettings) ?? .defaultSettings + let type: RenderedTotalUnreadCountType + switch inAppSettings.totalUnreadCountDisplayStyle { + case .raw: + type = .raw + case .filtered: + type = .filtered + } + return (totalUnreadState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags), type) +} + +func renderedTotalUnreadCount(inAppSettings: InAppNotificationSettings, totalUnreadState: ChatListTotalUnreadState) -> (Int32, RenderedTotalUnreadCountType) { + let type: RenderedTotalUnreadCountType + switch inAppSettings.totalUnreadCountDisplayStyle { + case .raw: + type = .raw + case .filtered: + type = .filtered + } + return (totalUnreadState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags), type) +} + +func renderedTotalUnreadCount(accountManager: AccountManager, postbox: Postbox) -> Signal<(Int32, RenderedTotalUnreadCountType), NoError> { + let unreadCountsKey = PostboxViewKey.unreadCounts(items: [.total(nil)]) + return combineLatest(accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.inAppNotificationSettings]), postbox.combinedView(keys: [unreadCountsKey])) + |> map { sharedData, view -> (Int32, RenderedTotalUnreadCountType) in + let totalUnreadState: ChatListTotalUnreadState + if let value = view.views[unreadCountsKey] as? UnreadMessageCountsView, let (_, total) = value.total() { + totalUnreadState = total + } else { + totalUnreadState = ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]) + } + + let inAppSettings: InAppNotificationSettings + if let value = sharedData.entries[ApplicationSharedPreferencesKeys.inAppNotificationSettings] as? InAppNotificationSettings { + inAppSettings = value + } else { + inAppSettings = .defaultSettings + } + let type: RenderedTotalUnreadCountType + switch inAppSettings.totalUnreadCountDisplayStyle { + case .raw: + type = .raw + case .filtered: + type = .filtered + } + if inAppSettings.badgeEnabled { + return (totalUnreadState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags), type) + } else { + return (0, type) + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs == rhs + }) +} diff --git a/Telegram-Mac/ReplyMarkupNode.swift b/Telegram-Mac/ReplyMarkupNode.swift index 41c5ebaaa9..47850f90ef 100644 --- a/Telegram-Mac/ReplyMarkupNode.swift +++ b/Telegram-Mac/ReplyMarkupNode.swift @@ -7,10 +7,11 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit class ReplyMarkupButtonLayout { @@ -20,10 +21,10 @@ class ReplyMarkupButtonLayout { let style:ControlStyle let button:ReplyMarkupButton - init(_ button:ReplyMarkupButton, _ style:ControlStyle = ControlStyle(backgroundColor: theme.colors.grayForeground, highlightColor: theme.colors.text)) { + init(button:ReplyMarkupButton, style:ControlStyle = ControlStyle(backgroundColor: theme.colors.grayForeground, highlightColor: theme.colors.text), isInput: Bool) { self.button = button self.style = style - self.text = TextViewLayout(NSAttributedString.initialize(string: button.title.fixed, color: theme.colors.text, font: .normal(.short)), maximumNumberOfLines: 1, truncationType: .middle, cutout: nil, alignment: .center) + self.text = TextViewLayout(NSAttributedString.initialize(string: button.title.fixed, color: theme.controllerBackgroundMode.hasWallpapaer && !isInput ? theme.chatServiceItemTextColor : theme.colors.text, font: .normal(.short)), maximumNumberOfLines: 1, truncationType: .middle, cutout: nil, alignment: .center) } func measure(_ width:CGFloat) { @@ -31,9 +32,18 @@ class ReplyMarkupButtonLayout { self.width = width } + deinit { + var bp:Int = 0 + bp += 1 + } + } class ReplyMarkupNode: Node { + + static let buttonHeight:CGFloat = 34 + static let buttonPadding:CGFloat = 4 + static let rowHeight = buttonHeight + buttonPadding private var width:CGFloat = 0 private var height:CGFloat = 0 @@ -42,14 +52,15 @@ class ReplyMarkupNode: Node { private let flags:ReplyMarkupMessageFlags private let interactions:ReplyMarkupInteractions - - init(_ rows:[ReplyMarkupRow], _ flags:ReplyMarkupMessageFlags, _ interactions:ReplyMarkupInteractions, _ view:View? = nil) { + private let isInput: Bool + init(_ rows:[ReplyMarkupRow], _ flags:ReplyMarkupMessageFlags, _ interactions:ReplyMarkupInteractions, _ view:View? = nil, _ isInput: Bool = false) { self.flags = flags + self.isInput = isInput self.interactions = interactions var layoutRows:[[ReplyMarkupButtonLayout]] = Array(repeating: [], count: rows.count) for i in 0 ..< rows.count { for button in rows[i].buttons { - layoutRows[i].append(ReplyMarkupButtonLayout(button)) + layoutRows[i].append(ReplyMarkupButtonLayout(button: button, isInput: isInput)) } } self.markup = layoutRows @@ -63,21 +74,29 @@ class ReplyMarkupNode: Node { var urlView:ImageView? switch button.button.action { - case .url, .switchInline: + case let .url(url): + if !url.isSingleEmoji { + urlView = ImageView() + urlView?.image = theme.chat.chatActionUrl(theme: theme) + urlView?.sizeToFit() + } + case .switchInline: urlView = ImageView() - urlView?.image = theme.icons.chatActionUrl + urlView?.image = theme.chat.chatActionUrl(theme: theme) urlView?.sizeToFit() default: break } let btnView = TextView() - btnView.set(handler: { [weak self] _ in - self?.proccess(btnView,button.button) + btnView.set(handler: { [weak self, weak button] control in + if let button = button { + self?.proccess(control, button.button) + } }, for: .Click) btnView.set(handler: { control in - control.change(opacity: 0.85, animated: true) + control.change(opacity: 0.7, animated: true) }, for: .Highlight) btnView.set(handler: { control in control.change(opacity: 1.0, animated: true) @@ -87,6 +106,8 @@ class ReplyMarkupNode: Node { }, for: .Hover) btnView.layer?.cornerRadius = .cornerRadius btnView.isSelectable = false + btnView.disableBackgroundDrawing = true + btnView.backgroundColor = button.style.backgroundColor btnView.set(layout:button.text) @@ -100,8 +121,8 @@ class ReplyMarkupNode: Node { } func proccess(_ control:Control, _ button:ReplyMarkupButton) { - interactions.proccess(button, { loading in - control.backgroundColor = loading ? .black : theme.colors.grayBackground + interactions.proccess(button, { [weak control] loading in + // control?.backgroundColor = loading ? .black : theme.colors.grayBackground }) } @@ -117,9 +138,9 @@ class ReplyMarkupNode: Node { if j == row.count - 1 { w = self.width - rect.minX } - rect.size = NSMakeSize(w, 34) + rect.size = NSMakeSize(w, ReplyMarkupNode.buttonHeight) let button:View? = view?.subviews[i] as? View - button?.backgroundColor = theme.colors.grayBackground + button?.backgroundColor = theme.controllerBackgroundMode.hasWallpapaer && !isInput ? theme.chatServiceItemColor : theme.colors.grayBackground if let button = button { button.frame = rect button.setNeedsDisplayLayer() @@ -128,11 +149,11 @@ class ReplyMarkupNode: Node { } } - rect = rect.offsetBy(dx: w + 6, dy: 0) + rect = rect.offsetBy(dx: w + ReplyMarkupNode.buttonPadding, dy: 0) i += 1 j += 1 } - y += 40 + y += ReplyMarkupNode.rowHeight } } @@ -143,7 +164,7 @@ class ReplyMarkupNode: Node { override func measureSize(_ width: CGFloat) { for row in markup { let count = row.count - let single:CGFloat = floorToScreenPixels((width - CGFloat(6 * (count - 1))) / CGFloat(count)) + let single:CGFloat = floorToScreenPixels(System.backingScale, (width - CGFloat(6 * (count - 1))) / CGFloat(count)) for button in row { button.measure(single) } diff --git a/Telegram-Mac/ReplyModel.swift b/Telegram-Mac/ReplyModel.swift index 0e4e5e332b..4bc55954a6 100644 --- a/Telegram-Mac/ReplyModel.swift +++ b/Telegram-Mac/ReplyModel.swift @@ -8,35 +8,53 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox class ReplyModel: ChatAccessoryModel { - private var account:Account + private let account:Account private(set) var replyMessage:Message? private var disposable:MetaDisposable = MetaDisposable() private let isPinned:Bool private var previousMedia: Media? private var isLoading: Bool = false private let fetchDisposable = MetaDisposable() - init(replyMessageId:MessageId, account:Account, replyMessage:Message? = nil, isPinned: Bool = false) { + private let makesizeCallback:(()->Void)? + private let autodownload: Bool + init(replyMessageId:MessageId, account:Account, replyMessage:Message? = nil, isPinned: Bool = false, autodownload: Bool = false, presentation: ChatAccessoryPresentation? = nil, makesizeCallback: (()->Void)? = nil) { self.isPinned = isPinned self.account = account + self.makesizeCallback = makesizeCallback + self.autodownload = autodownload self.replyMessage = replyMessage - super.init() + super.init(presentation: presentation) + + let messageViewSignal = account.postbox.messageView(replyMessageId) |> take(1) |> mapToSignal { view -> Signal in + if let message = view.message { + return .single(message) + } + return getMessagesLoadIfNecessary([view.messageId], postbox: account.postbox, network: account.network, accountPeerId: account.peerId) |> map {$0.first} + } + if let replyMessage = replyMessage { make(with :replyMessage, display: false) - nodeReady.set(.single(true)) + if isPinned { + nodeReady.set(.single(true) |> then(messageViewSignal |> deliverOn(Queue.mainQueue()) |> map { [weak self] message -> Bool in + self?.make(with: message, isLoading: false, display: true) + return message != nil + })) + } else { + nodeReady.set(.single(true)) + } + } else { make(with: nil, display: false) - nodeReady.set( account.postbox.messageView(replyMessageId) |> mapToSignal { view -> Signal in - if let message = view.message { - return .single(message) - } - return getMessagesLoadIfNecessary([view.messageId], postbox: account.postbox, network: account.network) |> map {$0.first} - } |> deliverOn(Queue.mainQueue().isCurrent() ? Queue.mainQueue() : prepareQueue) |> map { [weak self] message -> Bool in + + + nodeReady.set( messageViewSignal |> deliverOn(Queue.mainQueue()) |> map { [weak self] message -> Bool in self?.make(with: message, isLoading: false, display: true) return message != nil }) @@ -58,20 +76,26 @@ class ReplyModel: ChatAccessoryModel { override var leftInset: CGFloat { var imageDimensions: CGSize? if let message = replyMessage { - for media in message.media { - if let image = media as? TelegramMediaImage { - if let representation = largestRepresentationForPhoto(image) { - imageDimensions = representation.dimensions + if !message.containsSecretMedia { + for media in message.media { + if let image = media as? TelegramMediaImage { + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.size + } + break + } else if let file = media as? TelegramMediaFile, (file.isVideo || file.isSticker) { + if let dimensions = file.dimensions { + imageDimensions = dimensions.size + } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isStaticSticker { + imageDimensions = representation.dimensions.size + } else if file.isAnimatedSticker { + imageDimensions = NSMakeSize(30, 30) + } + break } - break } -// else if let file = media as? TelegramMediaFile { -// if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker { -// imageDimensions = representation.dimensions -// } -// break -// } } + if let _ = imageDimensions { return 30 + super.leftInset * 2 @@ -91,74 +115,105 @@ class ReplyModel: ChatAccessoryModel { } private func updateImageIfNeeded() { - Queue.mainQueue().async { - if let message = self.replyMessage, let view = self.view, view.frame != NSZeroRect { - var updatedMedia: Media? - var imageDimensions: CGSize? + if let message = self.replyMessage, let view = self.view { + var updatedMedia: Media? + var imageDimensions: CGSize? + var hasRoundImage = false + if !message.containsSecretMedia { for media in message.media { if let image = media as? TelegramMediaImage { updatedMedia = image if let representation = largestRepresentationForPhoto(image) { - imageDimensions = representation.dimensions + imageDimensions = representation.dimensions.size + } + break + } else if let file = media as? TelegramMediaFile, (file.isVideo || file.isSticker) { + updatedMedia = file + + if let dimensions = file.dimensions?.size { + imageDimensions = dimensions + } else if let representation = largestImageRepresentation(file.previewRepresentations) { + imageDimensions = representation.dimensions.size + } else if file.isAnimatedSticker { + imageDimensions = NSMakeSize(30, 30) + } + if file.isInstantVideo { + hasRoundImage = true } break } } + } + + + if let imageDimensions = imageDimensions { + let boundingSize = CGSize(width: 30.0, height: 30.0) + let arguments = TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets()) - if let imageDimensions = imageDimensions { - let boundingSize = CGSize(width: 30.0, height: 30.0) - let arguments = TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets()) - - if view.imageView == nil { - view.imageView = TransformImageView() - } - view.imageView?.setFrameSize(boundingSize) + if view.imageView == nil { + view.imageView = TransformImageView() + } + view.imageView?.setFrameSize(boundingSize) + if view.imageView?.superview == nil { view.addSubview(view.imageView!) - view.imageView?.centerY(x: super.leftInset) - - - var mediaUpdated = false - if let updatedMedia = updatedMedia, let previousMedia = self.previousMedia { - mediaUpdated = !updatedMedia.isEqual(previousMedia) - } else if (updatedMedia != nil) != (self.previousMedia != nil) { - mediaUpdated = true + } + + view.imageView?.setFrameOrigin(super.leftInset + (self.isSideAccessory ? 10 : 0), floorToScreenPixels(System.backingScale, self.topOffset + (max(34, self.size.height) - self.topOffset - boundingSize.height)/2)) + + + let mediaUpdated = true + + + var updateImageSignal: Signal? + if mediaUpdated { + if let image = updatedMedia as? TelegramMediaImage { + updateImageSignal = chatMessagePhotoThumbnail(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: image), scale: view.backingScaleFactor, synchronousLoad: true) + } else if let file = updatedMedia as? TelegramMediaFile { + if file.isVideo { + updateImageSignal = chatMessageVideoThumbnail(account: self.account, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), scale: view.backingScaleFactor, synchronousLoad: false) + } else if file.isAnimatedSticker { + updateImageSignal = chatMessageAnimatedSticker(postbox: self.account.postbox, file: file, small: true, scale: view.backingScaleFactor, size: imageDimensions.aspectFitted(boundingSize)) + } else if file.isSticker { + updateImageSignal = chatMessageSticker(postbox: self.account.postbox, file: file, small: true, scale: view.backingScaleFactor) + } else if let iconImageRepresentation = smallestImageRepresentation(file.previewRepresentations) { + let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [iconImageRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + updateImageSignal = chatWebpageSnippetPhoto(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: tmpImage), scale: view.backingScaleFactor, small: true, synchronousLoad: true) + } } + } + + + if let updateImageSignal = updateImageSignal, let media = updatedMedia { + view.imageView?.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: System.backingScale)) + - var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - if mediaUpdated { - if let image = updatedMedia as? TelegramMediaImage { - updateImageSignal = chatMessagePhotoThumbnail(account: self.account, photo: image, scale: view.backingScaleFactor) - } else if let file = updatedMedia as? TelegramMediaFile { - + view.imageView?.setSignal(updateImageSignal, animate: true, synchronousLoad: true, cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) } + }) + + if let media = media as? TelegramMediaImage { + self.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: self.account, imageReference: ImageMediaReference.message(message: MessageReference(message), media: media)).start()) } - if let updateImageSignal = updateImageSignal, let media = updatedMedia { - - view.imageView?.setSignal(signal: cachedMedia(media: media, size: arguments.imageSize, scale: view.backingScaleFactor)) - - if view.imageView?.layer?.contents == nil { - view.imageView?.setSignal(account: self.account, signal: updateImageSignal, animate: true, cacheImage: { image in - return cacheMedia(signal: image, media: media, size: arguments.imageSize, scale: System.backingScale) - }) - if let media = media as? TelegramMediaImage { - self.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: self.account, photo: media).start()) - } - } - - view.imageView?.set(arguments: arguments) + view.imageView?.set(arguments: arguments) + if hasRoundImage { + view.imageView!.layer?.cornerRadius = 15 + } else { + view.imageView?.layer?.cornerRadius = 0 } - } else { - view.imageView?.removeFromSuperview() - view.imageView = nil } - - self.previousMedia = updatedMedia } else { - self.view?.imageView?.removeFromSuperview() - self.view?.imageView = nil + view.imageView?.removeFromSuperview() + view.imageView = nil } + + self.previousMedia = updatedMedia + } else { + self.view?.imageView?.removeFromSuperview() + self.view?.imageView = nil } } @@ -166,27 +221,49 @@ class ReplyModel: ChatAccessoryModel { self.replyMessage = message self.isLoading = isLoading + var display: Bool = display updateImageIfNeeded() if let message = message { + + var title: String? = message.author?.displayTitle + for attr in message.attributes { + if let _ = attr as? SourceReferenceMessageAttribute { + if let info = message.forwardInfo { + title = info.authorTitle + } + break + } + } + - var text = pullText(from:message, attachEmoji: false) as String + var text = pullText(from:message, mediaViewType: .text) as String if text.isEmpty { - text = serviceMessageText(message, account: account) + text = serviceMessageText(message, account: account, isReplied: true) } - self.headerAttr = .initialize(string: !isPinned ? message.author?.displayTitle : tr(.chatHeaderPinnedMessage), color: theme.colors.blueUI, font: .medium(.text)) - self.messageAttr = .initialize(string: text, color: message.media.isEmpty ? theme.colors.text : theme.colors.grayText, font: .normal(.text)) + self.headerAttr = .initialize(string: !isPinned ? title : L10n.chatHeaderPinnedMessage, color: presentation.title, font: .medium(.text)) + self.messageAttr = .initialize(string: text, color: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? presentation.enabledText : presentation.disabledText, font: .normal(.text)) } else { self.headerAttr = nil - self.messageAttr = .initialize(string: isLoading ? tr(.messagesReplyLoadingLoading) : tr(.messagesDeletedMessage), color: theme.colors.grayText, font: .normal(.text)) + self.messageAttr = .initialize(string: isLoading ? tr(L10n.messagesReplyLoadingLoading) : tr(L10n.messagesDeletedMessage), color: presentation.disabledText, font: .normal(.text)) + display = true } if !isLoading { - measureSize(size.width) + if let makesizeCallback = makesizeCallback { + messagesViewQueue.async { + makesizeCallback() + } + return + } else { + measureSize(width, sizeToFit: sizeToFit) + display = true + } } if display { Queue.mainQueue().async { + self.view?.setFrameSize(self.size) self.setNeedDisplay() } } diff --git a/Telegram-Mac/ReportReasonModalController.swift b/Telegram-Mac/ReportReasonModalController.swift index 789b77ba6d..bd888c3e54 100644 --- a/Telegram-Mac/ReportReasonModalController.swift +++ b/Telegram-Mac/ReportReasonModalController.swift @@ -8,85 +8,225 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +func reportReasonSelector(context: AccountContext) -> Signal { + let promise: ValuePromise = ValuePromise() + let controller = ReportReasonController(callback: { reason in + promise.set(reason) + }) + showModal(with: controller, for: context.window) + + return promise.get() |> take(1) +} -fileprivate class ReportReasonModalController: ModalViewController { - fileprivate var onComplete:Signal { - return _complete.get() |> take(1) +private final class ReportReasonArguments { + let toggleReason:(ReportReason)->Void + init(toggleReason:@escaping(ReportReason)->Void) { + self.toggleReason = toggleReason } - private let _complete:Promise = Promise() - private var current:ReportPeerReason = .spam - override func viewClass() -> AnyClass { - return TableView.self +} + +private struct ReportReasonState : Equatable { + let reason: ReportReason + init(reason: ReportReason) { + self.reason = reason } - var genericView:TableView { - return self.view as! TableView + func withUpdatedReason(_ reason: ReportReason) -> ReportReasonState { + return ReportReasonState(reason: reason) } - - override func viewDidLoad() { - super.viewDidLoad() - - let updateState:(ReportPeerReason) -> Void = { [weak self] reason in - self?.current = reason - self?.genericView.reloadData() +} + +private let _id_spam = InputDataIdentifier("_id_spam") +private let _id_violence = InputDataIdentifier("_id_violence") +private let _id_porno = InputDataIdentifier("_id_porno") +private let _id_childAbuse = InputDataIdentifier("_id_childAbuse") +private let _id_copyright = InputDataIdentifier("_id_copyright") +private let _id_custom = InputDataIdentifier("_id_custom") +private let _id_custom_input = InputDataIdentifier("_id_custom_input") + +private extension ReportReason { + var id: InputDataIdentifier { + switch self { + case .spam: + return _id_spam + case .violence: + return _id_violence + case .porno: + return _id_porno + case .childAbuse: + return _id_childAbuse + case .copyright: + return _id_copyright + case .custom: + return _id_custom + default: + fatalError("unsupported") } - - let initialSize = atomicSize.modify {$0} - _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, name: tr(.reportReasonSpam), type: .selectable(stateback: { [weak self] () -> Bool in - if let current = self?.current { - return current == .spam + } + var title: String { + switch self { + case .spam: + return L10n.reportReasonSpam + case .violence: + return L10n.reportReasonViolence + case .porno: + return L10n.reportReasonPorno + case .childAbuse: + return L10n.reportReasonChildAbuse + case .copyright: + return L10n.reportReasonCopyright + case .custom: + return L10n.reportReasonOther + default: + fatalError("unsupported") + } + } + + func isEqual(to other: ReportReason) -> Bool { + switch self { + case .spam: + if case .spam = other { + return true } - return false - }), action: { - updateState(.spam) - })) - - _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, name: tr(.reportReasonViolence), type: .selectable(stateback: { [weak self] () -> Bool in - if let current = self?.current { - return current == .violence + case .violence: + if case .violence = other { + return true } - return false - }), action: { - updateState(.violence) - })) - - _ = genericView.addItem(item: GeneralInteractedRowItem(initialSize, name: tr(.reportReasonPorno), type: .selectable(stateback: { [weak self] () -> Bool in - if let current = self?.current { - return current == .porno + case .porno: + if case .porno = other { + return true } - return false - }), action: { - updateState(.porno) - }, drawCustomSeparator: false)) - - readyOnce() + case .childAbuse: + if case .childAbuse = other { + return true + } + case .copyright: + if case .copyright = other { + return true + } + case .custom: + if case .custom = other { + return true + } + default: + fatalError("unsupported") + } + return false } +} + +private func reportReasonEntries(state: ReportReasonState, arguments: ReportReasonArguments) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] - override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in - if let strongSelf = self { - self?._complete.set(.single(strongSelf.current)) - self?.close() - } - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + var sectionId:Int32 = 0 + var index:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + let reasons:[ReportReason] = [.spam, .violence, .porno, .childAbuse, .copyright, .custom("")] + + for (i, reason) in reasons.enumerated() { + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: reason.id, data: InputDataGeneralData(name: reason.title, color: theme.colors.text, type: .selectable(state.reason.isEqual(to: reason)), viewType: bestGeneralViewType(reasons, for: i), action: { + arguments.toggleReason(reason) + }))) + index += 1 } - override init() { - super.init(frame: NSMakeRect(0, 0, 260, 130)) - bar = .init(height: 0) + switch state.reason { + case let .custom(text): + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: nil, identifier: _id_custom_input, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.reportReasonOtherPlaceholder, filter: { $0 }, limit: 128)) + index += 1 + + default: + break } + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries } -func reportReasonSelector() -> Signal { - let reportModalView = ReportReasonModalController() - showModal(with: reportModalView, for: mainWindow) +func ReportReasonController(callback: @escaping(ReportReason)->Void) -> InputDataModalController { + let initialState = ReportReasonState(reason: .spam) + let state: ValuePromise = ValuePromise(initialState) + let stateValue: Atomic = Atomic(value: initialState) + + let updateState:((ReportReasonState)->ReportReasonState) -> Void = { f in + state.set(stateValue.modify(f)) + } + + let arguments = ReportReasonArguments(toggleReason: { reason in + updateState { current in + if !current.reason.isEqual(to: reason) { + return current.withUpdatedReason(reason) + } else { + return current + } + } + }) + + let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in + return reportReasonEntries(state: state, arguments: arguments) + } |> map { entries in + return InputDataSignalValue(entries: entries) + } + + var getModalController:(()->InputDataModalController?)? = nil + + + let controller = InputDataController(dataSignal: dataSignal, title: L10n.peerInfoReport) - return reportModalView.onComplete + controller.leftModalHeader = ModalHeaderData(image: theme.icons.modalClose, handler: { + getModalController?()?.close() + }) + + controller.updateDatas = { data in + updateState { current in + switch current.reason { + case .custom: + return current.withUpdatedReason(.custom(data[_id_custom_input]?.stringValue ?? "")) + default: + return current + } + } + return .none + } + + + let modalInteractions = ModalInteractions(acceptTitle: L10n.reportReasonReport, accept: { [weak controller] in + controller?.validateInputValues() + }, drawBorder: true, singleButton: true) + + let modalController = InputDataModalController(controller, modalInteractions: modalInteractions, closeHandler: { f in + f() + }, size: NSMakeSize(300, 350)) + + getModalController = { [weak modalController] in + return modalController + } + + controller.validateData = { data in + return .success(.custom { + callback(stateValue.with { $0.reason }) + getModalController?()?.close() + }) + } + + + return modalController } + diff --git a/Telegram-Mac/RestictedModalViewController.swift b/Telegram-Mac/RestictedModalViewController.swift index a62482c14d..62a905798a 100644 --- a/Telegram-Mac/RestictedModalViewController.swift +++ b/Telegram-Mac/RestictedModalViewController.swift @@ -9,104 +9,53 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private final class RestrictedControllerArguments { - let account: Account - let toggleRight: (TelegramChannelBannedRightsFlags, TelegramChannelBannedRightsFlags) -> Void + let context: AccountContext + let toggleRight: (TelegramChatBannedRightsFlags, Bool) -> Void let changeUntil:()->Void - init(account: Account, toggleRight: @escaping (TelegramChannelBannedRightsFlags, TelegramChannelBannedRightsFlags) -> Void, changeUntil: @escaping () -> Void) { - self.account = account + let alertError:() -> Void + let deleteException:()->Void + init(context: AccountContext, toggleRight: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, changeUntil: @escaping () -> Void, alertError: @escaping() -> Void, deleteException:@escaping()->Void) { + self.context = context self.toggleRight = toggleRight self.changeUntil = changeUntil + self.alertError = alertError + self.deleteException = deleteException } } private enum RestrictedEntryStableId: Hashable { case info - case right(TelegramChannelBannedRightsFlags) + case right(TelegramChatBannedRightsFlags) case description(Int32) case section(Int32) - case blockFor + case timeout + case exceptionInfo + case delete var hashValue: Int { - switch self { - case .info: - return 0 - case .description(let index): - return Int(index) - case .section(let section): - return Int(section) - case let .right(flags): - return flags.rawValue.hashValue - case .blockFor: - return 1 - } - } - - static func ==(lhs: RestrictedEntryStableId, rhs: RestrictedEntryStableId) -> Bool { - switch lhs { - case .info: - if case .info = rhs { - return true - } else { - return false - } - case .blockFor: - if case .blockFor = rhs { - return true - } else { - return false - } - case let .right(flags): - if case .right(flags) = rhs { - return true - } else { - return false - } - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - case .description(let text): - if case .description(text) = rhs { - return true - } else { - return false - } - } + return 0 } } private enum RestrictedEntry: TableItemListNodeEntry { - case info(Int32, Peer, TelegramUserPresence?) - case rightItem(Int32, Int32, String, TelegramChannelBannedRightsFlags, TelegramChannelBannedRightsFlags, Bool, Bool) - case description(Int32, Int32, String) + case info(Int32, Peer, TelegramUserPresence?, GeneralViewType) + case rightItem(Int32, Int32, String, TelegramChatBannedRightsFlags, Bool, Bool, GeneralViewType) + case description(Int32, Int32, String, GeneralViewType) case section(Int32) - case blockFor(Int32, Int32, Int32) + case timeout(Int32, Int32, String, String, GeneralViewType) + case exceptionInfo(Int32, Int32, String, GeneralViewType) + case delete(Int32, Int32, String, GeneralViewType) - var stableId: RestrictedEntryStableId { - switch self { - case .info: - return .info - case .blockFor: - return .blockFor - case let .rightItem(_, _, _, right, _, _, _): - return .right(right) - case .description(_, let index, _): - return .description(index) - case .section(let sectionId): - return .section(sectionId) - } - } static func ==(lhs: RestrictedEntry, rhs: RestrictedEntry) -> Bool { switch lhs { - case let .info(lhsSectionId, lhsPeer, lhsPresence): - if case let .info(rhsSectionId, rhsPeer, rhsPresence) = rhs { + case let .info(lhsSectionId, lhsPeer, lhsPresence, lhsViewType): + if case let .info(rhsSectionId, rhsPeer, rhsPresence, rhsViewType) = rhs { if lhsSectionId != rhsSectionId { return false } @@ -116,40 +65,21 @@ private enum RestrictedEntry: TableItemListNodeEntry { if lhsPresence != rhsPresence { return false } - + if lhsViewType != rhsViewType { + return false + } return true } else { return false } - case let .rightItem(lhsSectionId, lhsIndex, lhsText, lhsRight, lhsFlags, lhsValue, lhsEnabled): - if case let .rightItem(rhsSectionId, rhsIndex, rhsText, rhsRight, rhsFlags, rhsValue, rhsEnabled) = rhs { - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if lhsText != rhsText { - return false - } - if lhsRight != rhsRight { - return false - } - if lhsFlags != rhsFlags { - return false - } - if lhsValue != rhsValue { - return false - } - if lhsEnabled != rhsEnabled { - return false - } + case let .rightItem(sectionId, index, text, flags, value, enabled, viewType): + if case .rightItem(sectionId, index, text, flags, value, enabled, viewType) = rhs { return true } else { return false } - case let .description(sectionId, index, text): - if case .description(sectionId, index, text) = rhs{ + case let .description(sectionId, index, text, viewType): + if case .description(sectionId, index, text, viewType) = rhs{ return true } else { return false @@ -160,8 +90,20 @@ private enum RestrictedEntry: TableItemListNodeEntry { } else { return false } - case let .blockFor(sectionId, index, until): - if case .blockFor(sectionId, index, until) = rhs{ + case let .exceptionInfo(sectionId, index, text, viewType): + if case .exceptionInfo(sectionId, index, text, viewType) = rhs { + return true + } else { + return false + } + case let .delete(sectionId, index, text, viewType): + if case .delete(sectionId, index, text, viewType) = rhs { + return true + } else { + return false + } + case let .timeout(sectionId, index, title, value, viewType): + if case .timeout(sectionId, index, title, value, viewType) = rhs{ return true } else { return false @@ -169,17 +111,41 @@ private enum RestrictedEntry: TableItemListNodeEntry { } } + + var stableId: RestrictedEntryStableId { + switch self { + case .info: + return .info + case .timeout: + return .timeout + case let .rightItem(_, _, _, right, _, _, _): + return .right(right) + case .description(_, let index, _, _): + return .description(index) + case .exceptionInfo: + return .exceptionInfo + case .delete: + return .delete + case .section(let sectionId): + return .section(sectionId) + } + } + var index:Int32 { switch self { - case .info(let sectionId, _, _): + case .info(let sectionId, _, _, _): return (sectionId * 1000) + 0 - case .description(let sectionId, let index, _): + case .description(let sectionId, let index, _, _): + return (sectionId * 1000) + index + case .delete(let sectionId, let index, _, _): + return (sectionId * 1000) + index + case .exceptionInfo(let sectionId, let index, _, _): return (sectionId * 1000) + index case .rightItem(let sectionId, let index, _, _, _, _, _): return (sectionId * 1000) + Int32(index) + 10 case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId - case .blockFor(let sectionId, let index, _): + case .timeout(let sectionId, let index, _, _, _): return (sectionId * 1000) + index } } @@ -191,42 +157,34 @@ private enum RestrictedEntry: TableItemListNodeEntry { func item(_ arguments: RestrictedControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - case .info(_, let peer, let presence): - var string:String = peer.isBot ? tr(.presenceBot) : tr(.peerStatusRecently) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .info(_, peer, presence, viewType): + var string:String = peer.isBot ? L10n.presenceBot : L10n.peerStatusRecently var color:NSColor = theme.colors.grayText if let presence = presence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string,_, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + (string,_, color) = stringAndActivityForUserPresence(presence, timeDifference: arguments.context.timeDifference, relativeTo: Int32(timestamp)) } - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: true, height: 60, photoSize: NSMakeSize(50, 50), statusStyle: ControlStyle(font: NSFont.normal(.custom(14)), foregroundColor: color), status: string, borderType: [], drawCustomSeparator: false, drawLastSeparator: false, inset: NSEdgeInsets(left: 25, right: 25), drawSeparatorIgnoringInset: false, action: {}) - case let .rightItem(_, _, name, right, flags, value, enabled): + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, enabled: true, height: 60, photoSize: NSMakeSize(40, 40), statusStyle: ControlStyle(font: .normal(.title), foregroundColor: color), status: string, borderType: [], drawCustomSeparator: false, drawLastSeparator: false, inset: NSEdgeInsets(left: 25, right: 25), drawSeparatorIgnoringInset: false, viewType: viewType, action: {}) + case let .rightItem(_, _, name, right, value, enabled, viewType): //ControlStyle(font: NSFont.) - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: enabled ? .text : .gray), type: .switchable(stateback: { () -> Bool in - return value - }), action: { - arguments.toggleRight(right, flags) - }, enabled: enabled, switchAppearance: SwitchViewAppearance(backgroundColor: .white, stateOnColor: theme.colors.blueUI, stateOffColor: theme.colors.redUI, disabledColor: theme.colors.grayBackground, borderColor: .clear)) - case .description(_, _, let name): - return GeneralTextRowItem(initialSize, stableId: stableId, text: name) - case .blockFor(_, _, let until): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.channelBlockUserBlockFor), type: .context(stateback: { () -> String in - if until == 0 || until == .max { - return tr(.channelBanForever) - } else { - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.locale = Locale(identifier: appCurrentLanguage.languageCode) - formatter.timeStyle = .short - return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(until))) - } - - }), action: { + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: enabled ? theme.colors.text : theme.colors.grayText), type: .switchable(value), viewType: viewType, action: { + arguments.toggleRight(right, !value) + }, enabled: enabled, switchAppearance: SwitchViewAppearance(backgroundColor: .white, stateOnColor: theme.colors.accent, stateOffColor: theme.colors.redUI, disabledColor: theme.colors.grayBackground, borderColor: .clear), disabledAction: { + arguments.alertError() + }) + case let .description(_, _, name, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: name, viewType: viewType) + case let .timeout(_, _, title, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .nextContext(value), viewType: viewType, action: { arguments.changeUntil() }) + case let .exceptionInfo(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .delete(_, _, name, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: name, nameStyle: redActionButton, type: .next, viewType: viewType, action: arguments.deleteException) } - //return TableRowItem(initialSize) } } @@ -239,293 +197,482 @@ private enum RestrictUntil : Int32 { } private struct RestrictedControllerState: Equatable { - let updatedFlags: TelegramChannelBannedRightsFlags? - let until: Int32 - - init(updatedFlags: TelegramChannelBannedRightsFlags? = nil, until: Int32 = 0) { - self.updatedFlags = updatedFlags - self.until = until - } - - static func ==(lhs: RestrictedControllerState, rhs: RestrictedControllerState) -> Bool { - if lhs.updatedFlags != rhs.updatedFlags { - return false - } - if lhs.until != rhs.until { - return false - } - return true - } - - func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChannelBannedRightsFlags?) -> RestrictedControllerState { - return RestrictedControllerState(updatedFlags: updatedFlags, until: self.until) - } - - func withUpdatedUntil(_ until: Int32) -> RestrictedControllerState { - return RestrictedControllerState(updatedFlags: self.updatedFlags, until: until) - } + var referenceTimestamp: Int32 + var updatedFlags: TelegramChatBannedRightsFlags? + var updatedTimeout: Int32? + var updating: Bool = false } - -private func banRightDependencies(_ right: TelegramChannelBannedRightsFlags) -> [TelegramChannelBannedRightsFlags] { - - if right.contains(.banReadMessages) { - return [.banSendMessages, .banSendMedia, .banSendStickers, .banSendGifs, .banEmbedLinks] - } else if right.contains(.banSendMessages) { - return [.banSendMedia, .banSendStickers, .banSendGifs, .banEmbedLinks] - } else if right.contains(.banSendMedia) { - return [.banSendStickers, .banSendGifs, .banEmbedLinks] - } else if right.contains(.banSendStickers) { - return [.banSendGifs] - } - - return [] -} - -private func unbanRightDependencies(_ right: TelegramChannelBannedRightsFlags) -> [TelegramChannelBannedRightsFlags] { - - if right.contains(.banReadMessages) { - return [] - } else if right.contains(.banSendMessages) { - return [.banReadMessages] - } else if right.contains(.banSendMedia) { - return [.banSendMessages, .banReadMessages] - } else if right.contains(.banSendStickers) { - return [.banSendMessages, .banReadMessages, .banSendMedia, .banSendGifs] - } else if right.contains(.banEmbedLinks) { - return [.banSendMessages, .banReadMessages, .banSendMedia] +private func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags { + var result = flags + result.remove(.banReadMessages) + if result.contains(.banSendGifs) { + result.insert(.banSendStickers) + result.insert(.banSendGifs) + result.insert(.banSendGames) + result.insert(.banSendInline) + } else { + result.remove(.banSendStickers) + result.remove(.banSendGifs) + result.remove(.banSendGames) + result.insert(.banSendInline) } - - return [] + return result } -private func RestrictedEntries(state: RestrictedControllerState, participant: RenderedChannelParticipant, view: PeerView) -> [RestrictedEntry] { +private func restrictedEntries(state: RestrictedControllerState, accountPeerId: PeerId, channelView: PeerView, memberView: PeerView, initialParticipant: ChannelParticipant?, initialBannedBy: Peer?) -> [RestrictedEntry] { var index:Int32 = 0 - var sectionId:Int32 = 1 + var sectionId:Int32 = 0 var entries:[RestrictedEntry] = [] entries.append(.section(sectionId)) sectionId += 1 - entries.append(.info(sectionId, participant.peer, participant.presences[participant.peer.id] as? TelegramUserPresence)) - entries.append(.section(sectionId)) - sectionId += 1 - entries.append(.description(sectionId, index, tr(.channelUserRestriction))) - index += 1 - - if let peer = peerViewMainPeer(view) as? TelegramChannel { - switch participant.participant { - case .member(_, _, _, let banInfo): - - if let banInfo = banInfo { - let restrictions:[(TelegramChannelBannedRightsFlags,String)] = [(.banReadMessages, tr(.channelBlockUserCanReadMessages)), (.banSendMessages, tr(.channelBlockUserCanSendMessages)), (.banSendMedia, tr(.channelBlockUserCanSendMedia)), ([.banSendStickers], tr(.channelBlockUserCanSendStickers)), (.banEmbedLinks, tr(.channelBlockUserCanEmbedLinks))] - let currentRightsFlags: TelegramChannelBannedRightsFlags - if let updatedFlags = state.updatedFlags { - currentRightsFlags = updatedFlags - } else { - currentRightsFlags = banInfo.rights.flags - } + if let peer = channelView.peers[channelView.peerId] as? TelegramChannel, let defaultBannedRights = peer.defaultBannedRights, let member = memberView.peers[memberView.peerId] { + entries.append(.info(sectionId, member, memberView.peerPresences[member.id] as? TelegramUserPresence, .singleItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.description(sectionId, index, L10n.groupPermissionSectionTitle, .textTopItem)) + index += 1 + + let currentRightsFlags: TelegramChatBannedRightsFlags + if let updatedFlags = state.updatedFlags { + currentRightsFlags = updatedFlags + } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentRightsFlags = banInfo.rights.flags + } else { + currentRightsFlags = defaultBannedRights.flags + } + + let currentTimeout: Int32 + if let updatedTimeout = state.updatedTimeout { + currentTimeout = updatedTimeout + } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentTimeout = banInfo.rights.untilDate + } else { + currentTimeout = Int32.max + } + + let currentTimeoutString: String + if currentTimeout == 0 || currentTimeout == Int32.max { + currentTimeoutString = L10n.timerForever + } else { + let remainingTimeout = currentTimeout - state.referenceTimestamp + currentTimeoutString = timeIntervalString(Int(remainingTimeout)) + } + + + for right in allGroupPermissionList { + let defaultEnabled = !defaultBannedRights.flags.contains(right) + entries.append(.rightItem(sectionId, index, stringForGroupPermission(right: right), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating, bestGeneralViewType(allGroupPermissionList, for: right))) + index += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + + + if let initialParticipant = initialParticipant, case let .member(member) = initialParticipant, let banInfo = member.banInfo, let initialBannedBy = initialBannedBy { + entries.append(.timeout(sectionId, index, L10n.groupPermissionDuration, currentTimeoutString, .firstItem)) + index += 1 + entries.append(.delete(sectionId, index, L10n.groupPermissionDelete, .lastItem)) + index += 1 + entries.append(.exceptionInfo(sectionId, index, L10n.groupPermissionAddedInfo(initialBannedBy.displayTitle, stringForRelativeSymbolicTimestamp(relativeTimestamp: banInfo.timestamp, relativeTo: state.referenceTimestamp)), .textBottomItem)) + index += 1 + } else { + entries.append(.timeout(sectionId, index, L10n.groupPermissionDuration, currentTimeoutString, .singleItem)) + index += 1 + } + + } else if let group = channelView.peers[channelView.peerId] as? TelegramGroup, let defaultBannedRights = group.defaultBannedRights, let member = memberView.peers[memberView.peerId] { + entries.append(.info(sectionId, member, memberView.peerPresences[member.id] as? TelegramUserPresence, .singleItem)) + + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.description(sectionId, index, L10n.groupPermissionSectionTitle, .textTopItem)) + index += 1 + + let currentRightsFlags: TelegramChatBannedRightsFlags + if let updatedFlags = state.updatedFlags { + currentRightsFlags = updatedFlags + } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentRightsFlags = banInfo.rights.flags + } else { + currentRightsFlags = defaultBannedRights.flags + } + + let currentTimeout: Int32 + if let updatedTimeout = state.updatedTimeout { + currentTimeout = updatedTimeout + } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentTimeout = banInfo.rights.untilDate + } else { + currentTimeout = Int32.max + } + + let currentTimeoutString: String + if currentTimeout == 0 || currentTimeout == Int32.max { + currentTimeoutString = L10n.timerForever + } else { + let remainingTimeout = currentTimeout - state.referenceTimestamp + currentTimeoutString = timeIntervalString(Int(remainingTimeout)) + } - for restriction in restrictions { - entries.append(.rightItem(sectionId, index, restriction.1, restriction.0, currentRightsFlags, !currentRightsFlags.contains(restriction.0) && !currentRightsFlags.contains(.banReadMessages), peer.hasAdminRights(.canBanUsers))) - index += 1 - } - } - default: - break + + for right in allGroupPermissionList { + let defaultEnabled = !defaultBannedRights.flags.contains(right) + entries.append(.rightItem(sectionId, index, stringForGroupPermission(right: right), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating, bestGeneralViewType(allGroupPermissionList, for: right))) + index += 1 + } + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.timeout(sectionId, index, L10n.groupPermissionDuration, currentTimeoutString, .singleItem)) + index += 1 + + if let initialParticipant = initialParticipant, case let .member(member) = initialParticipant, let banInfo = member.banInfo, let initialBannedBy = initialBannedBy { + entries.append(.timeout(sectionId, index, L10n.groupPermissionDuration, currentTimeoutString, .firstItem)) + index += 1 + entries.append(.delete(sectionId, index, L10n.groupPermissionDelete, .lastItem)) + index += 1 + entries.append(.exceptionInfo(sectionId, index, L10n.groupPermissionAddedInfo(initialBannedBy.displayTitle, stringForRelativeSymbolicTimestamp(relativeTimestamp: banInfo.timestamp, relativeTo: state.referenceTimestamp)), .textBottomItem)) + index += 1 + } else { + entries.append(.timeout(sectionId, index, L10n.groupPermissionDuration, currentTimeoutString, .singleItem)) + index += 1 } } - - - - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.blockFor(sectionId, index, state.until)) - index += 1 - + + entries.append(.section(sectionId)) sectionId += 1 return entries } -fileprivate func prepareTransition(left:[RestrictedEntry], right: [RestrictedEntry], initialSize:NSSize, arguments:RestrictedControllerArguments) -> TableUpdateTransition { +fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:RestrictedControllerArguments) -> TableUpdateTransition { let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.item(arguments, initialSize: initialSize) + return entry.entry.item(arguments, initialSize: initialSize) } return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } class RestrictedModalViewController: TableModalViewController { - private let participant:RenderedChannelParticipant - private let account:Account + private let initialParticipant:ChannelParticipant? + private let context:AccountContext private let disposable = MetaDisposable() private let peerId:PeerId - private let stateValue:Atomic = Atomic(value: RestrictedControllerState()) - private let unban: Bool - private let updated:(TelegramChannelBannedRights)->Void - init(account:Account, peerId:PeerId, participant:RenderedChannelParticipant, unban: Bool, updated: @escaping(TelegramChannelBannedRights)->Void) { - self.participant = participant - self.account = account - self.unban = unban + private let memberId: PeerId + private let updated:(TelegramChatBannedRights)->Void + + private var okClicked:(()->Void)? + private var cancelClicked:(()->Void)? + + init(_ context: AccountContext, peerId:PeerId, memberId: PeerId, initialParticipant:ChannelParticipant?, updated: @escaping(TelegramChatBannedRights)->Void) { + self.initialParticipant = initialParticipant + self.context = context self.updated = updated self.peerId = peerId - super.init(frame: NSMakeRect(0, 0, 300, 360)) + self.memberId = memberId + super.init(frame: NSMakeRect(0, 0, 350, 360)) bar = .init(height : 0) } override func viewDidLoad() { super.viewDidLoad() - let participant = self.participant - let unban = self.unban - let stateValue = self.stateValue - let statePromise = ValuePromise(RestrictedControllerState(), ignoreRepeated: true) + let initialState = RestrictedControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970), updatedFlags: nil, updatedTimeout: nil, updating: false) + + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + + let initialParticipant = self.initialParticipant + let memberId = self.memberId + let peerId = self.peerId + let context = self.context + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) let updateState: ((RestrictedControllerState) -> RestrictedControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - updateState { current in - switch participant.participant { - case let .member(_, _, _, banInfo): - if let banInfo = banInfo { - return current.withUpdatedUpdatedFlags(banInfo.rights.flags).withUpdatedUntil(banInfo.rights.untilDate) - } - default: - break - } - return current - } + + let actionsDisposable = DisposableSet() - let arguments = RestrictedControllerArguments(account: account, toggleRight: { right, flags in - updateState { current in - var updated = flags - - let banDepencies = banRightDependencies(right) - let unbanDepencies = unbanRightDependencies(right) - - if flags == .banReadMessages { - updated = [] + let updateRightsDisposable = MetaDisposable() + actionsDisposable.add(updateRightsDisposable) + + + let peerView = Promise() + peerView.set(context.account.viewTracker.peerView(peerId)) + + + let arguments = RestrictedControllerArguments(context: context, toggleRight: { rights, value in + let _ = (peerView.get() + |> take(1) + |> deliverOnMainQueue).start(next: { view in - for depend in banDepencies { - updated.insert(depend) + var defaultBannedRightsFlagsValue: TelegramChatBannedRightsFlags? + guard let peer = view.peers[peerId] else { + return } - } else { - if flags.contains(right) { - updated.remove(right) - for depend in unbanDepencies { - if updated.contains(depend) { - updated.remove(depend) - } + if let channel = peer as? TelegramChannel, let initialRightFlags = channel.defaultBannedRights?.flags { + defaultBannedRightsFlagsValue = initialRightFlags + } else if let group = peer as? TelegramGroup, let initialRightFlags = group.defaultBannedRights?.flags { + defaultBannedRightsFlagsValue = initialRightFlags + } + guard let defaultBannedRightsFlags = defaultBannedRightsFlagsValue else { + return + } + updateState { state in + var state = state + var effectiveRightsFlags: TelegramChatBannedRightsFlags + if let updatedFlags = state.updatedFlags { + effectiveRightsFlags = updatedFlags + } else if let initialParticipant = initialParticipant, case let .member(member) = initialParticipant, let banInfo = member.banInfo { + effectiveRightsFlags = banInfo.rights.flags + } else { + effectiveRightsFlags = defaultBannedRightsFlags } - } else { - updated.insert(right) - for depend in banDepencies { - if !updated.contains(depend) { - updated.insert(depend) + if value { + effectiveRightsFlags.remove(rights) + effectiveRightsFlags = effectiveRightsFlags.subtracting(groupPermissionDependencies(rights)) + } else { + effectiveRightsFlags.insert(rights) + for right in allGroupPermissionList { + if groupPermissionDependencies(right).contains(rights) { + effectiveRightsFlags.insert(right) + } } } + state.updatedFlags = effectiveRightsFlags + return state } - } + }) + }, changeUntil: { [weak self] in + guard let `self` = self else {return} + + if let index = self.genericView.index(hash: RestrictedEntryStableId.timeout) { - return current.withUpdatedUpdatedFlags(updated) - } - }, changeUntil: { [weak self] in - if let strongSelf = self { - if let index = strongSelf.genericView.index(hash: RestrictedEntryStableId.blockFor) { - if let view = (strongSelf.genericView.viewNecessary(at: index) as? GeneralInteractedRowView)?.textView { - var items:[SPopoverItem] = [] - items.append(SPopoverItem(tr(.timerDaysCountable(1)), { - updateState { - $0.withUpdatedUntil(Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 24 * 60 * 60)) - } - })) - items.append(SPopoverItem(tr(.timerWeeksCountable(1)), { - updateState { - $0.withUpdatedUntil(Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 7 * 24 * 60 * 60)) - } - })) - items.append(SPopoverItem(tr(.timerMonthsCountable(1)), { - updateState { - $0.withUpdatedUntil(Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 30 * 24 * 60 * 60)) - } - })) - items.append(SPopoverItem(tr(.channelBanForever), { - updateState { - $0.withUpdatedUntil(0) - } + let applyValue: (Int32?) -> Void = { value in + updateState { state in + var state = state + state.updatedTimeout = value + return state + } + } + + let intervals: [Int32] = [ + 1 * 60 * 60 * 24, + 7 * 60 * 60 * 24, + 30 * 60 * 60 * 24 + ] + if let view = (self.genericView.viewNecessary(at: index) as? GeneralInteractedRowView)?.textView { + var items:[SPopoverItem] = [] + for interval in intervals { + items.append(SPopoverItem(timeIntervalString(Int(interval)), { + applyValue(initialState.referenceTimestamp + interval) })) - showPopover(for: view, with: SPopoverViewController(items: items), edge: .maxX, inset: NSMakePoint(view.frame.width,-10)) } + items.append(SPopoverItem(tr(L10n.channelBanForever), { + applyValue(Int32.max) + })) + showPopover(for: view, with: SPopoverViewController(items: items), edge: .maxX, inset: NSMakePoint(view.frame.width,-10)) } } - + }, alertError: { [weak self] in + let _ = (peerView.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] view in + if let peer = peerViewMainPeer(view) { + self?.show(toaster: ControllerToaster(text: peer.isSupergroup || peer.isGroup ? L10n.channelExceptionDisabledOptionGroup : L10n.channelExceptionDisabledOptionChannel)) + } + }) + }, deleteException: { [weak self] in + self?.updated(TelegramChatBannedRights(flags: TelegramChatBannedRightsFlags(rawValue: 0), untilDate: 0)) + self?.close() }) + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + let initialSize = self.atomicSize + + + var keys: [PostboxViewKey] = [.peer(peerId: peerId, components: .all), .peer(peerId: memberId, components: .all)] + if let banInfo = initialParticipant?.banInfo { + keys.append(.peer(peerId: banInfo.restrictedBy, components: [])) + } + let combinedView = context.account.postbox.combinedView(keys: keys) - let previous:Atomic<[RestrictedEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - let signal:Signal<(TableUpdateTransition, PeerView), Void> = combineLatest(statePromise.get(), account.viewTracker.peerView(peerId)) |> deliverOn(prepareQueue) |> map { state, view in - return (RestrictedEntries(state: state, participant: participant, view: view), view) - } |> map { entries, view in - return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), view) + let signal:Signal<(TableUpdateTransition, PeerView, PeerView), NoError> = combineLatest(queue: prepareQueue, appearanceSignal, statePromise.get(), combinedView) |> map { appearance, state, combinedView in + + let channelView = combinedView.views[.peer(peerId: peerId, components: .all)] as! PeerView + let memberView = combinedView.views[.peer(peerId: memberId, components: .all)] as! PeerView + var initialBannedByPeer: Peer? + if let banInfo = initialParticipant?.banInfo { + initialBannedByPeer = (combinedView.views[.peer(peerId: banInfo.restrictedBy, components: [])] as? PeerView)?.peers[banInfo.restrictedBy] + } + + let entries = restrictedEntries(state: state, accountPeerId: context.account.peerId, channelView: channelView, memberView: memberView, initialParticipant: initialParticipant, initialBannedBy: initialBannedByPeer).map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + + return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.with {$0}, arguments: arguments), channelView, memberView) } |> deliverOnMainQueue + let animated:Atomic = Atomic(value: false) - disposable.set(signal.start(next: { [weak self] transition, view in + disposable.set(signal.start(next: { [weak self] transition, channelView, memberView in self?.genericView.merge(with: transition) self?.updateSize(animated.swap(true)) self?.readyOnce() self?.modal?.interactions?.updateDone({ button in - if let peer = peerViewMainPeer(view) as? TelegramChannel { - button.isEnabled = peer.hasAdminRights(.canBanUsers) + if let peer = peerViewMainPeer(memberView) as? TelegramChannel { + button.isEnabled = peer.hasPermission(.banMembers) } }) - self?.modal?.interactions?.updateCancel({ button in - if unban { - button.set(text: tr(.channelBlacklistUnban), for: .Normal) + self?.modal?.interactions?.updateCancel({ [weak self] button in + if self?.genericView.item(stableId: RestrictedEntryStableId.exceptionInfo) != nil { + button.set(text: L10n.groupPermissionDelete, for: .Normal) button.set(color: theme.colors.redUI, for: .Normal) } else { button.set(text: "", for: .Normal) } }) + + + self?.okClicked = { [weak self] in + + let _ = (peerView.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] view in + var defaultBannedRightsFlagsValue: TelegramChatBannedRightsFlags? + guard let peer = view.peers[peerId] else { + return + } + if let channel = peer as? TelegramChannel, let initialRightFlags = channel.defaultBannedRights?.flags { + defaultBannedRightsFlagsValue = initialRightFlags + } else if let group = peer as? TelegramGroup, let initialRightFlags = group.defaultBannedRights?.flags { + defaultBannedRightsFlagsValue = initialRightFlags + } + guard let defaultBannedRightsFlags = defaultBannedRightsFlagsValue else { + return + } + + + var resolvedRights: TelegramChatBannedRights? + if let initialParticipant = initialParticipant { + var updateFlags: TelegramChatBannedRightsFlags? + var updateTimeout: Int32? + updateState { current in + updateFlags = current.updatedFlags + updateTimeout = current.updatedTimeout + return current + } + + if updateFlags == nil && updateTimeout == nil { + if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant { + if maybeBanInfo == nil { + updateFlags = defaultBannedRightsFlags + updateTimeout = Int32.max + } + } + } + + if updateFlags != nil || updateTimeout != nil { + let currentRightsFlags: TelegramChatBannedRightsFlags + if let updatedFlags = updateFlags { + currentRightsFlags = updatedFlags + } else if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentRightsFlags = banInfo.rights.flags + } else { + currentRightsFlags = defaultBannedRightsFlags + } + + let currentTimeout: Int32 + if let updateTimeout = updateTimeout { + currentTimeout = updateTimeout + } else if case let .member(_, _, _, maybeBanInfo, _) = initialParticipant, let banInfo = maybeBanInfo { + currentTimeout = banInfo.rights.untilDate + } else { + currentTimeout = Int32.max + } + + resolvedRights = TelegramChatBannedRights(flags: completeRights(currentRightsFlags), untilDate: currentTimeout) + } + } else if let _ = channelView.peers[channelView.peerId] as? TelegramChannel { + var updateFlags: TelegramChatBannedRightsFlags? + var updateTimeout: Int32? + updateState { state in + var state = state + updateFlags = state.updatedFlags + updateTimeout = state.updatedTimeout + state.updating = false + return state + } + + if updateFlags == nil { + updateFlags = defaultBannedRightsFlags + } + if updateTimeout == nil { + updateTimeout = Int32.max + } + + if let updateFlags = updateFlags, let updateTimeout = updateTimeout { + resolvedRights = TelegramChatBannedRights(flags: completeRights(updateFlags), untilDate: updateTimeout) + } + } + + var previousRights: TelegramChatBannedRights? + if let initialParticipant = initialParticipant, case let .member(member) = initialParticipant, member.banInfo != nil { + previousRights = member.banInfo?.rights + } + + if let resolvedRights = resolvedRights, previousRights != resolvedRights { + let cleanResolvedRightsFlags = resolvedRights.flags.union(defaultBannedRightsFlags) + let cleanResolvedRights = TelegramChatBannedRights(flags: cleanResolvedRightsFlags, untilDate: resolvedRights.untilDate) + + if cleanResolvedRights.flags.isEmpty && previousRights == nil { + self?.close() + } else { + self?.updated(cleanResolvedRights) + } + + } + }) + + } })) - - } deinit { disposable.dispose() } + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: ModalHeaderData(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: L10n.groupPermissionTitle), right: nil) + } + override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in - if let strongSelf = self { - strongSelf.close() - switch strongSelf.participant.participant { - case let .member(_, _, _, banInfo): - if let banInfo = banInfo { - let state = strongSelf.stateValue.modify({$0}) - let flags = state.updatedFlags ?? banInfo.rights.flags - strongSelf.updated(TelegramChannelBannedRights(flags: flags, untilDate: state.until)) - } - default: - break - } - - } - }, cancelTitle: tr(.modalCancel), cancel: { [weak self] in + return ModalInteractions(acceptTitle: L10n.modalApply, accept: { [weak self] in self?.close() - self?.updated(TelegramChannelBannedRights(flags: [], untilDate: 0)) - }, drawBorder: true, height: 40) + self?.okClicked?() + }, drawBorder: true, height: 50, singleButton: true) } } diff --git a/Telegram-Mac/RingBuffer.h b/Telegram-Mac/RingBuffer.h new file mode 100755 index 0000000000..46f07f9dfb --- /dev/null +++ b/Telegram-Mac/RingBuffer.h @@ -0,0 +1,140 @@ +#import + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct { + void *buffer; + int32_t length; + int32_t tail; + int32_t head; + int32_t fillCount; + } TPCircularBuffer; + + /*! + * Initialise buffer + * + * Note that the length is advisory only: Because of the way the + * memory mirroring technique works, the true buffer length will + * be multiples of the device page size (e.g. 4096 bytes) + * + * If you intend to use the AudioBufferList utilities, you should + * always allocate a bit more space than you need for pure audio + * data, so there's room for the metadata. How much extra is required + * depends on how many AudioBufferList structures are used, which is + * a function of how many audio frames each buffer holds. A good rule + * of thumb is to add 15%, or at least another 2048 bytes or so. + * + * @param buffer Circular buffer + * @param length Length of buffer + */ + bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length); + bool _TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length, size_t structSize); + + /*! + * Cleanup buffer + * + * Releases buffer resources. + */ + void TPCircularBufferCleanup(TPCircularBuffer *buffer); + + /*! + * Clear buffer + * + * Resets buffer to original, empty state. + * + * This is safe for use by consumer while producer is accessing + * buffer. + */ + void TPCircularBufferClear(TPCircularBuffer *buffer); + + // Reading (consuming) + + /*! + * Access end of buffer + * + * This gives you a pointer to the end of the buffer, ready + * for reading, and the number of available bytes to read. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for reading + * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty + */ + static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, int32_t* availableBytes) { + *availableBytes = buffer->fillCount; + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->tail); + } + + /*! + * Consume bytes in buffer + * + * This frees up the just-read bytes, ready for writing again. + * + * @param buffer Circular buffer + * @param amount Number of bytes to consume + */ + static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, int32_t amount) { + buffer->tail = (buffer->tail + amount) % buffer->length; + buffer->fillCount -= amount; + assert(buffer->fillCount >= 0); + } + + /*! + * Access front of buffer + * + * This gives you a pointer to the front of the buffer, ready + * for writing, and the number of available bytes to write. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for writing + * @return Pointer to the first bytes ready for writing, or NULL if buffer is full + */ + static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, int32_t* availableBytes) { + *availableBytes = (buffer->length - buffer->fillCount); + if ( *availableBytes == 0 ) return NULL; + return (void*)((char*)buffer->buffer + buffer->head); + } + + // Writing (producing) + + /*! + * Produce bytes in buffer + * + * This marks the given section of the buffer ready for reading. + * + * @param buffer Circular buffer + * @param amount Number of bytes to produce + */ + static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, int32_t amount) { + buffer->head = (buffer->head + amount) % buffer->length; + buffer->fillCount += amount; + assert(buffer->fillCount <= buffer->length); + } + + /*! + * Helper routine to copy bytes to buffer + * + * This copies the given bytes to the buffer, and marks them ready for reading. + * + * @param buffer Circular buffer + * @param src Source buffer + * @param len Number of bytes in source buffer + * @return true if bytes copied, false if there was insufficient space + */ + static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, int32_t len) { + int32_t space; + void *ptr = TPCircularBufferHead(buffer, &space); + if ( space < len ) return false; + memcpy(ptr, src, len); + TPCircularBufferProduce(buffer, len); + return true; + } +#ifdef __cplusplus +} +#endif + diff --git a/Telegram-Mac/RingBuffer.m b/Telegram-Mac/RingBuffer.m new file mode 100755 index 0000000000..9c6db69b7a --- /dev/null +++ b/Telegram-Mac/RingBuffer.m @@ -0,0 +1,121 @@ +#import "RingBuffer.h" + +#include +#include +#include + +#define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) +static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { + if ( result != ERR_SUCCESS ) { + printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); + return false; + } + return true; +} + +bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length) { + return _TPCircularBufferInit(buffer, length, sizeof(TPCircularBuffer)); +} + +bool _TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length, size_t structSize) { + + assert(length > 0); + + if ( structSize != sizeof(TPCircularBuffer) ) { + fprintf(stderr, "TPCircularBuffer: Header version mismatch. Check for old versions of TPCircularBuffer in your project\n"); + abort(); + } + + // Keep trying until we get our buffer, needed to handle race conditions + int retries = 3; + while ( true ) { + + buffer->length = (int32_t)round_page(length); // We need whole page sizes + + // Temporarily allocate twice the length, so we have the contiguous address space to + // support a second instance of the buffer directly after + vm_address_t bufferAddress; + kern_return_t result = vm_allocate(mach_task_self(), + &bufferAddress, + buffer->length * 2, + VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer allocation"); + return false; + } + // Try again if we fail + continue; + } + + // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... + result = vm_deallocate(mach_task_self(), + bufferAddress + buffer->length, + buffer->length); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Buffer deallocation"); + return false; + } + // If this fails somehow, deallocate the whole region and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + // Re-map the buffer to the address space immediately after the buffer + vm_address_t virtualAddress = bufferAddress + buffer->length; + vm_prot_t cur_prot, max_prot; + result = vm_remap(mach_task_self(), + &virtualAddress, // mirror target + buffer->length, // size of mirror + 0, // auto alignment + 0, // force remapping to virtualAddress + mach_task_self(), // same task + bufferAddress, // mirror source + 0, // MAP READ-WRITE, NOT COPY + &cur_prot, // unused protection struct + &max_prot, // unused protection struct + VM_INHERIT_DEFAULT); + if ( result != ERR_SUCCESS ) { + if ( retries-- == 0 ) { + reportResult(result, "Remap buffer memory"); + return false; + } + // If this remap failed, we hit a race condition, so deallocate and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + if ( virtualAddress != bufferAddress+buffer->length ) { + // If the memory is not contiguous, clean up both allocated buffers and try again + if ( retries-- == 0 ) { + printf("Couldn't map buffer memory to end of buffer\n"); + return false; + } + + vm_deallocate(mach_task_self(), virtualAddress, buffer->length); + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + buffer->buffer = (void*)bufferAddress; + buffer->fillCount = 0; + buffer->head = buffer->tail = 0; + + return true; + } + return false; +} + +void TPCircularBufferCleanup(TPCircularBuffer *buffer) { + vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); + memset(buffer, 0, sizeof(TPCircularBuffer)); +} + +void TPCircularBufferClear(TPCircularBuffer *buffer) { + int32_t fillCount; + if ( TPCircularBufferTail(buffer, &fillCount) ) { + TPCircularBufferConsume(buffer, fillCount); + } +} + diff --git a/Telegram-Mac/RingByteBuffer.swift b/Telegram-Mac/RingByteBuffer.swift new file mode 100755 index 0000000000..8ce25943d3 --- /dev/null +++ b/Telegram-Mac/RingByteBuffer.swift @@ -0,0 +1,69 @@ +import Foundation +import Darwin + +public final class RingByteBuffer { + public let size: Int + private var buffer: TPCircularBuffer + + public init(size: Int) { + self.size = size + self.buffer = TPCircularBuffer() + TPCircularBufferInit(&self.buffer, Int32(size)) + } + + deinit { + TPCircularBufferCleanup(&self.buffer) + } + + public func enqueue(data: Data) -> Bool { + return data.withUnsafeBytes { (bytes: UnsafePointer) -> Bool in + return TPCircularBufferProduceBytes(&self.buffer, UnsafeRawPointer(bytes), Int32(data.count)) + } + } + + public func enqueue(_ bytes: UnsafeRawPointer, count: Int) -> Bool { + return TPCircularBufferProduceBytes(&self.buffer, bytes, Int32(count)) + } + + public func withMutableHeadBytes(_ f: (UnsafeMutableRawPointer, Int) -> Int) { + var availableBytes: Int32 = 0 + let bytes = TPCircularBufferHead(&self.buffer, &availableBytes) + let enqueuedBytes = f(bytes!, Int(availableBytes)) + TPCircularBufferProduce(&self.buffer, Int32(enqueuedBytes)) + } + + public func dequeue(_ bytes: UnsafeMutableRawPointer, count: Int) -> Int { + var availableBytes: Int32 = 0 + let tail = TPCircularBufferTail(&self.buffer, &availableBytes) + + let copiedCount = min(count, Int(availableBytes)) + memcpy(bytes, tail, copiedCount) + + TPCircularBufferConsume(&self.buffer, Int32(copiedCount)) + + return copiedCount + } + + public func dequeue(count: Int) -> Data { + var availableBytes: Int32 = 0 + let tail = TPCircularBufferTail(&self.buffer, &availableBytes) + + let copiedCount = min(count, Int(availableBytes)) + let bytes = malloc(copiedCount)! + memcpy(bytes, tail, copiedCount) + + TPCircularBufferConsume(&self.buffer, Int32(copiedCount)) + + return Data(bytesNoCopy: bytes.assumingMemoryBound(to: UInt8.self), count: copiedCount, deallocator: .free) + } + + public func clear() { + TPCircularBufferClear(&self.buffer) + } + + public var availableBytes: Int { + var count: Int32 = 0 + TPCircularBufferTail(&self.buffer, &count) + return Int(count) + } +} diff --git a/Telegram-Mac/SImageView.swift b/Telegram-Mac/SImageView.swift new file mode 100644 index 0000000000..94debc373b --- /dev/null +++ b/Telegram-Mac/SImageView.swift @@ -0,0 +1,59 @@ +// +// SImageView.swift +// Telegram +// +// Created by keepcoder on 04/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class SImageView: NSView { + + init() { + super.init(frame: NSZeroRect) + wantsLayer = true + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required override init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } + + var data: (CGImage, NSEdgeInsets)? { + didSet { + if let image = data { + layer?.contentsScale = 2.0 + let imageSize = image.0.backingSize + let insets = image.1 + let halfPixelFudge: CGFloat = 0.49 + let otherPixelFudge: CGFloat = 0.02 + var contentsCenter: CGRect = NSMakeRect(0.0, 0.0, 1.0, 1.0); + if (insets.left > 0 || insets.right > 0) { + contentsCenter.origin.x = ((insets.left + halfPixelFudge) / imageSize.width); + contentsCenter.size.width = (imageSize.width - (insets.left + insets.right + 1.0) + otherPixelFudge) / imageSize.width; + } + if (insets.top > 0 || insets.bottom > 0) { + contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height); + contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.0) + otherPixelFudge) / imageSize.height; + } + self.layer?.contentsGravity = .resize; + self.layer?.contentsCenter = contentsCenter; + self.layer?.contents = image.0 + } else { + self.layer?.contents = nil + } + + } + } + + +} diff --git a/Telegram-Mac/SPopoverRowItem.swift b/Telegram-Mac/SPopoverRowItem.swift deleted file mode 100644 index c9d12f5ccd..0000000000 --- a/Telegram-Mac/SPopoverRowItem.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// SPopoverRowItem.swift -// Telegram-Mac -// -// Created by keepcoder on 28/09/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac -class SPopoverRowItem: TableRowItem { - - override var height: CGFloat { - return 40 - } - - private var unique:Int64 - - override var stableId: AnyHashable { - return unique - } - - let iStyle:ControlStyle = ControlStyle(backgroundColor: theme.colors.blueSelect, highlightColor:.white) - - - // data - let image:CGImage? - let title:TextViewLayout - let activeTitle: TextViewLayout - let clickHandler:() -> Void - - override func viewClass() -> AnyClass { - return SPopoverRowView.self - } - let alignAsImage: Bool - init(_ initialSize:NSSize, image:CGImage? = nil, alignAsImage: Bool, title:String, textColor: NSColor, clickHandler:@escaping() ->Void = {}) { - self.image = image - self.alignAsImage = alignAsImage - self.title = TextViewLayout(.initialize(string: title, color: textColor, font: .normal(.title))) - self.activeTitle = TextViewLayout(.initialize(string: title, color: .white, font: .normal(.title))) - - self.title.measure(width: .greatestFiniteMagnitude) - self.activeTitle.measure(width: .greatestFiniteMagnitude) - self.clickHandler = clickHandler - unique = Int64(arc4random()) - super.init(initialSize) - } - -} - - -private class SPopoverRowView: TableRowView { - - var image:ImageView = ImageView() - - var overlay:OverlayControl = OverlayControl(); - - var text:TextView = TextView(); - - - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - self.addSubview(overlay) - self.addSubview(image) - - self.addSubview(text) - text.isSelectable = false - text.userInteractionEnabled = false - - overlay.set(handler: {[weak self] (state) in - self?.overlay.backgroundColor = theme.colors.blueSelect - if let item = self?.item as? SPopoverRowItem { - if let image = item.image { - self?.image.image = item.iStyle.highlight(image: image) - } - self?.text.backgroundColor = theme.colors.blueSelect - self?.text.update(item.activeTitle) - } - }, for: .Hover) - - overlay.set(handler: {[weak self] (state) in - self?.overlay.backgroundColor = theme.colors.background - if let item = self?.item as? SPopoverRowItem { - self?.image.image = item.image - self?.text.backgroundColor = theme.colors.background - self?.text.update(item.title) - } - }, for: .Normal) - } - - override func setFrameSize(_ newSize: NSSize) { - super.setFrameSize(newSize) - overlay.setFrameSize(newSize) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - override func updateMouse() { - overlay.updateState() - } - - override func set(item:TableRowItem, animated:Bool = false) { - super.set(item: item, animated: animated) - - overlay.removeAllHandlers() - overlay.backgroundColor = theme.colors.background - text.backgroundColor = theme.colors.background - if let item = item as? SPopoverRowItem { - image.image = item.image - overlay.removeAllHandlers() - overlay.set(handler: {_ in - item.clickHandler() - }, for: .Click) - image.sizeToFit() - image.centerY(self, x: floorToScreenPixels((45 - image.frame.width) / 2)) - - text.update(item.title) - - if item.image != nil || item.alignAsImage { - text.centerY(self, x: 45) - } else { - text.center() - } - } - - } - -} diff --git a/Telegram-Mac/SPopoverViewController.swift b/Telegram-Mac/SPopoverViewController.swift deleted file mode 100644 index 618ad6e892..0000000000 --- a/Telegram-Mac/SPopoverViewController.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// SPopoverViewController.swift -// Telegram-Mac -// -// Created by keepcoder on 09/11/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac - -struct SPopoverItem { - let title:String - let image:CGImage? - let textColor: NSColor - let handler:()->Void - init(_ title:String, _ handler:@escaping ()->Void, _ image:CGImage? = nil, _ textColor: NSColor = theme.colors.text) { - self.title = title - self.image = image - self.textColor = textColor - self.handler = handler - } -} - -class SPopoverViewController: GenericViewController { - private let items:[SPopoverRowItem] - private let disposable = MetaDisposable() - override func viewDidLoad() { - super.viewDidLoad() - - genericView.insert(items: items) - genericView.needUpdateVisibleAfterScroll = true - genericView.reloadData() - - readyOnce() - } - - init(items:[SPopoverItem], visibility:Int = 4) { - weak var controller:SPopoverViewController? - let alignAsImage = !items.filter({$0.image != nil}).isEmpty - self.items = items.map({ item in SPopoverRowItem(NSZeroSize, image: item.image, alignAsImage: alignAsImage, title: item.title, textColor: item.textColor, clickHandler: { - Queue.mainQueue().justDispatch { - controller?.popover?.hide() - - _ = (Signal.single(Void()) |> delay(0.15, queue: Queue.mainQueue())).start(next: { - item.handler() - }) - } - })}) - let width: CGFloat = self.items.max(by: {$0.title.layoutSize.width < $1.title.layoutSize.width})!.title.layoutSize.width - let height = min(visibility * 40 + 20, items.count * 40) - super.init(frame: NSMakeRect(0, 0, width + 45 + 18, CGFloat(height))) - bar = .init(height: 0) - controller = self - } - - deinit { - disposable.dispose() - } - - - override func viewWillAppear(_ animated: Bool) { - - } - - -} - - diff --git a/Telegram-Mac/SVideoController.swift b/Telegram-Mac/SVideoController.swift new file mode 100644 index 0000000000..d6d570806a --- /dev/null +++ b/Telegram-Mac/SVideoController.swift @@ -0,0 +1,476 @@ +// +// VideoStreamingTestModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/11/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import IOKit.pwr_mgt + +extension MediaPlayerStatus { + func withUpdatedVolume(_ volume: Float) -> MediaPlayerStatus { + return MediaPlayerStatus(generationTimestamp: self.generationTimestamp, duration: self.duration, dimensions: self.dimensions, timestamp: self.timestamp, baseRate: self.baseRate, volume: volume, seekId: self.seekId, status: self.status) + } + func withUpdatedTimestamp(_ timestamp: Double) -> MediaPlayerStatus { + return MediaPlayerStatus(generationTimestamp: self.generationTimestamp, duration: self.duration, dimensions: self.dimensions, timestamp: timestamp, baseRate: self.baseRate, volume: self.volume, seekId: self.seekId, status: self.status) + } + func withUpdatedDuration(_ duration: Double) -> MediaPlayerStatus { + return MediaPlayerStatus(generationTimestamp: self.generationTimestamp, duration: duration, dimensions: self.dimensions, timestamp: self.timestamp, baseRate: self.baseRate, volume: self.volume, seekId: self.seekId, status: self.status) + } +} + +enum SVideoStyle { + case regular + case pictureInPicture +} + +class SVideoController: GenericViewController, PictureInPictureControl { + + + var style: SVideoStyle = .regular + private var fullScreenWindow: Window? + private var fullScreenRestoreState: (rect: NSRect, view: NSView)? + private let mediaPlayer: MediaPlayer + private let reference: FileMediaReference + private let statusDisposable = MetaDisposable() + private let bufferingDisposable = MetaDisposable() + private let hideOnIdleDisposable = MetaDisposable() + private let hideControlsDisposable = MetaDisposable() + private let postbox: Postbox + private var pictureInPicture: Bool = false + private var hideControls: ValuePromise = ValuePromise(true, ignoreRepeated: true) + private var controlsIsHidden: Bool = false + var togglePictureInPictureImpl:((Bool, PictureInPictureControl)->Void)? + + private var isPaused: Bool = true + + private var _videoFramePreview: MediaPlayerFramePreview? + private var videoFramePreview: MediaPlayerFramePreview { + if let videoFramePreview = _videoFramePreview { + return videoFramePreview + } else { + self._videoFramePreview = MediaPlayerFramePreview(postbox: postbox, fileReference: reference) + } + return _videoFramePreview! + } + + + private var scrubbingFrame = Promise(nil) + private var scrubbingFrames = false + private var scrubbingFrameDisposable: Disposable? + + + init(postbox: Postbox, reference: FileMediaReference, fetchAutomatically: Bool = false) { + self.reference = reference + self.postbox = postbox + mediaPlayer = MediaPlayer(postbox: postbox, reference: reference.resourceReference(reference.media.resource), streamable: reference.media.isStreamable, video: true, preferSoftwareDecoding: false, enableSound: true, volume: FastSettings.volumeRate, fetchAutomatically: fetchAutomatically) + super.init() + bar = .init(height: 0) + } + + var status: Signal { + return mediaPlayer.status + } + + func play(_ startTime: TimeInterval? = nil) { + mediaPlayer.play() + self.isPaused = false + if let startTime = startTime, startTime > 0 { + mediaPlayer.seek(timestamp: startTime) + } + } + + func playOrPause() { + self.isPaused = !self.isPaused + mediaPlayer.togglePlayPause() + if let status = genericView.status { + switch status.status { + case .buffering: + mediaPlayer.seek(timestamp: status.timestamp / status.duration) + default: + break + } + } + } + + func pause() { + self.isPaused = true + mediaPlayer.pause() + } + + func play() { + self.isPaused = false + self.play(nil) + } + + + func didEnter() { + + } + + func didExit() { + + } + + private func updateIdleTimer() { + NSCursor.unhide() + hideOnIdleDisposable.set((Signal.complete() |> delay(1.0, queue: Queue.mainQueue())).start(completed: { [weak self] in + guard let `self` = self else {return} + self.hideControls.set(true) + if !self.pictureInPicture, !self.isPaused { + NSCursor.hide() + } + })) + } + + private func updateControlVisibility(_ isMouseUpOrDown: Bool = false) { + updateIdleTimer() + if let rootView = genericView.superview?.superview { + var hide = !genericView._mouseInside() && !rootView.isHidden && (NSEvent.pressedMouseButtons & (1 << 0)) == 0 + if self.fullScreenWindow != nil && isMouseUpOrDown, !genericView.insideControls { + hide = true + if !self.isPaused { + NSCursor.hide() + } + } + hideControls.set(hide) + } else { + hideControls.set(false) + } + } + + + + private func setHandlersOn(window: Window) { + + updateIdleTimer() + + let mouseInsidePlayer = genericView.mediaPlayer.mouseInside() + + hideControls.set(!mouseInsidePlayer) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + if let window = self?.genericView.window, let contentView = window.contentView { + let point = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if contentView.hitTest(point) != nil { + self?.updateControlVisibility() + } + } + return .rejected + }, with: self, for: .mouseMoved, priority: .modal) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + if let window = self?.genericView.window, let contentView = window.contentView { + let point = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if contentView.hitTest(point) != nil { + self?.updateControlVisibility() + } + } + return .rejected + }, with: self, for: .mouseExited, priority: .modal) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + self?.updateIdleTimer() + + return .rejected + }, with: self, for: .leftMouseDragged, priority: .modal) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + if let window = self?.genericView.window, let contentView = window.contentView { + let point = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if contentView.hitTest(point) != nil { + self?.updateControlVisibility() + } + } + return .rejected + }, with: self, for: .mouseEntered, priority: .modal) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + if let window = self?.genericView.window, self?.genericView.mediaPlayer.mouseInside() == true { + self?.updateControlVisibility(true) + } + return .rejected + }, with: self, for: .leftMouseDown, priority: .modal) + + window.set(mouseHandler: { [weak self] (event) -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + if let window = self.genericView.window, let contentView = window.contentView { + let point = contentView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if contentView.hitTest(point) != nil { + self.updateControlVisibility(true) + } + } + self.genericView.subviews.last?.mouseUp(with: event) + return .rejected + }, with: self, for: .leftMouseUp, priority: .modal) + + } + + private var assertionID: IOPMAssertionID = 0 + private var success: IOReturn? + + private func disableScreenSleep() -> Bool? { + guard success == nil else { return nil } + success = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoDisplaySleep as CFString, + IOPMAssertionLevel(kIOPMAssertionLevelOn), + "Video Playing" as CFString, + &assertionID ) + return success == kIOReturnSuccess + } + + private func enableScreenSleep() -> Bool { + if success != nil { + success = IOPMAssertionRelease(assertionID) + success = nil + return true + } + return false + } + + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let window = window { + setHandlersOn(window: window) + } + + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + hideOnIdleDisposable.set(nil) + _ = enableScreenSleep() + NSCursor.unhide() + window?.removeAllHandlers(for: self) + } + + var isPictureInPicture: Bool { + return self.pictureInPicture + } + + + func hideControlsIfNeeded() -> Bool { + if !controlsIsHidden { + hideControls.set(true) + return true + } + return false + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.layerContentsRedrawPolicy = .duringViewResize + + + mediaPlayer.attachPlayerView(genericView.mediaPlayer) + genericView.isStreamable = reference.media.isStreamable + hideControlsDisposable.set(hideControls.get().start(next: { [weak self] hide in + self?.genericView.hideControls(hide, animated: true) + self?.controlsIsHidden = hide + })) + + + let statusValue:Atomic = Atomic(value: nil) + let updateTemporaryStatus:(_ f: (MediaPlayerStatus?)->MediaPlayerStatus?) -> Void = { [weak self] f in + self?.genericView.status = statusValue.modify(f) + } + + let duration = Double(reference.media.duration ?? 0) + + statusDisposable.set((mediaPlayer.status |> deliverOnMainQueue).start(next: { [weak self] status in + let status = status.withUpdatedDuration(status.duration != 0 ? status.duration : duration) + switch status.status { + case .playing: + _ = self?.disableScreenSleep() + case let .buffering(_, whilePlaying): + if whilePlaying { + _ = self?.disableScreenSleep() + } else { + _ = self?.enableScreenSleep() + } + case .paused: + _ = self?.enableScreenSleep() + } + _ = statusValue.swap(status) + + self?.genericView.status = status + })) + let size = reference.media.resource.size ?? 0 + + let bufferingStatus = postbox.mediaBox.resourceRangesStatus(reference.media.resource) + |> map { ranges -> (IndexSet, Int) in + return (ranges, size) + } |> deliverOnMainQueue + + bufferingDisposable.set(bufferingStatus.start(next: { [weak self] bufferingStatus in + self?.genericView.bufferingStatus = bufferingStatus + })) + + self.scrubbingFrameDisposable = (self.scrubbingFrame.get() + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let `self` = self else { + return + } + if let result = result { + self.genericView.showScrubblerPreviewIfNeeded() + self.genericView.setCurrentScrubblingState(result) + } else { + self.genericView.hideScrubblerPreviewIfNeeded() + // empty image + } + }) + + + genericView.interactions = SVideoInteractions(playOrPause: { [weak self] in + self?.playOrPause() + }, rewind: { [weak self] timestamp in + guard let `self` = self else { return } + self.mediaPlayer.seek(timestamp: timestamp) + }, scrobbling: { [weak self] timecode in + guard let `self` = self else { return } + + if let timecode = timecode { + if !self.scrubbingFrames { + self.scrubbingFrames = true + self.scrubbingFrame.set(self.videoFramePreview.generatedFrames + |> map(Optional.init)) + } + self.videoFramePreview.generateFrame(at: timecode) + } else { + self.scrubbingFrame.set(.single(nil)) + self.videoFramePreview.cancelPendingFrames() + self.scrubbingFrames = false + } + }, volume: { [weak self] value in + self?.mediaPlayer.setVolume(value) + FastSettings.setVolumeRate(value) + updateTemporaryStatus { status in + return status?.withUpdatedVolume(value) + } + }, toggleFullScreen: { [weak self] in + self?.toggleFullScreen() + }, togglePictureInPicture: { [weak self] in + self?.togglePictureInPicture() + }, closePictureInPicture: { + closePipVideo() + }) + + if let duration = reference.media.duration, duration < 30 { + mediaPlayer.actionAtEnd = .loop({ [weak self] in + Queue.mainQueue().async { + self?.updateIdleTimer() + self?.hideControls.set(false) + } + }) + } else { + mediaPlayer.actionAtEnd = .action { [weak self] in + Queue.mainQueue().async { + self?.mediaPlayer.seek(timestamp: 0) + self?.mediaPlayer.pause() + self?.updateIdleTimer() + self?.hideControls.set(false) + } + } + } + + readyOnce() + } + + func togglePictureInPicture() { + if let function = togglePictureInPictureImpl { + if fullScreenRestoreState != nil { + toggleFullScreen() + } + self.pictureInPicture = !pictureInPicture + window?.removeAllHandlers(for: self) + function(pictureInPicture, self) + if let window = view.window?.contentView?.window as? Window { + setHandlersOn(window: window) + } + + genericView.set(isInPictureInPicture: pictureInPicture) + } + } + + func togglePlayerOrPause() { + playOrPause() + } + + + func rewindBackward() { + genericView.rewindBackward() + } + func rewindForward() { + genericView.rewindForward() + } + + var isFullscreen: Bool { + return self.fullScreenRestoreState != nil + } + + func toggleFullScreen() { + if let screen = NSScreen.main { + if let window = fullScreenWindow, let state = fullScreenRestoreState { + + + + window.setFrame(NSMakeRect(screen.frame.minX + state.rect.minX, screen.frame.minY + screen.frame.height - state.rect.maxY, state.rect.width, state.rect.height), display: true, animate: true) + window.orderOut(nil) + view.frame = state.rect + state.view.addSubview(view) + + genericView.set(isInFullScreen: false) + genericView.mediaPlayer.setVideoLayerGravity(.resizeAspectFill) + + + window.removeAllHandlers(for: self) + if let window = self.window { + setHandlersOn(window: window) + } + + self.fullScreenWindow = nil + self.fullScreenRestoreState = nil + } else { + + genericView.mediaPlayer.setVideoLayerGravity(.resizeAspect) + + + fullScreenRestoreState = (rect: view.frame, view: view.superview!) + fullScreenWindow = Window(contentRect: NSMakeRect(view.frame.minX, screen.frame.height - view.frame.maxY, view.frame.width, view.frame.height), styleMask: [.fullSizeContentView, .borderless], backing: .buffered, defer: true, screen: screen) + + setHandlersOn(window: fullScreenWindow!) + window?.removeAllHandlers(for: self) + + + fullScreenWindow?.isOpaque = true + fullScreenWindow?.hasShadow = false + fullScreenWindow?.level = .screenSaver + self.view.frame = self.view.bounds + fullScreenWindow?.contentView?.addSubview(self.view) + fullScreenWindow?.orderFront(nil) + genericView.set(isInFullScreen: true) + fullScreenWindow?.becomeKey() + fullScreenWindow?.setFrame(screen.frame, display: true, animate: true) + } + } + } + + deinit { + statusDisposable.dispose() + bufferingDisposable.dispose() + hideOnIdleDisposable.dispose() + hideControlsDisposable.dispose() + _ = IOPMAssertionRelease(assertionID) + NSCursor.unhide() + } + +} diff --git a/Telegram-Mac/SVideoView.swift b/Telegram-Mac/SVideoView.swift new file mode 100644 index 0000000000..9b0df297b2 --- /dev/null +++ b/Telegram-Mac/SVideoView.swift @@ -0,0 +1,778 @@ +// +// SVideoView.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/11/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +enum SVideoControlsStyle : Equatable { + case regular(pip: Bool, fullScreen: Bool, hideRewind: Bool) + case compact(pip: Bool, fullScreen: Bool, hideRewind: Bool) + + func withUpdatedPip(_ pip: Bool) -> SVideoControlsStyle { + switch self { + case let .regular(_, fullScreen, hideRewind): + return .regular(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + case let .compact(_, fullScreen, hideRewind): + return .compact(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + } + } + + func withUpdatedFullScreen(_ fullScreen: Bool) -> SVideoControlsStyle { + switch self { + case let .regular(pip, _, hideRewind): + return .regular(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + case let .compact(pip, _, hideRewind): + return .compact(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + } + } + + func withUpdatedStyle(compact: Bool) -> SVideoControlsStyle { + switch self { + case let .regular(pip, fullScreen, hideRewind), let .compact(pip, fullScreen, hideRewind): + return compact ? .compact(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) : .regular(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + } + } + func withUpdatedHideRewind(hideRewind: Bool) -> SVideoControlsStyle { + switch self { + case let .regular(pip, fullScreen, _): + return .regular(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + case let .compact(pip, fullScreen, _): + return .compact(pip: pip, fullScreen: fullScreen, hideRewind: hideRewind) + } + } + + var isPip: Bool { + switch self { + case let .regular(pip, _, _), let .compact(pip, _, _): + return pip + } + } + var isFullScreen: Bool { + switch self { + case let .regular(_, fullScreen, _), let .compact(_, fullScreen, _): + return fullScreen + } + } + var hideRewind: Bool { + switch self { + case let .regular(_, _, hideRewind), let .compact(_, _, hideRewind): + return hideRewind + } + } + + var isCompact: Bool { + switch self { + case .compact: + return true + case .regular: + return false + } + } +} + + +final class SVideoInteractions { + let playOrPause: ()->Void + let rewind:(Double)->Void + let scrobbling:(Double?)->Void + let volume:(Float) -> Void + let toggleFullScreen:() -> Void + let togglePictureInPicture: ()->Void + let closePictureInPicture: ()->Void + init(playOrPause: @escaping()->Void, rewind: @escaping(Double)->Void, scrobbling: @escaping(Double?)->Void, volume: @escaping(Float) -> Void, toggleFullScreen: @escaping()->Void, togglePictureInPicture: @escaping() -> Void, closePictureInPicture:@escaping()->Void) { + self.playOrPause = playOrPause + self.rewind = rewind + self.scrobbling = scrobbling + self.volume = volume + self.toggleFullScreen = toggleFullScreen + self.togglePictureInPicture = togglePictureInPicture + self.closePictureInPicture = closePictureInPicture + } +} + +private final class SVideoControlsView : Control { + + var bufferingRanges:[Range] = [] { + didSet { + progress.set(fetchingProgressRanges: bufferingRanges, animated: oldValue != bufferingRanges) + } + } + + var scrubberInsideBuffering: Bool { + for range in bufferingRanges { + if range.contains(progress.currentValue) { + return true + } + } + return bufferingRanges.isEmpty + } + + var controlStyle: SVideoControlsStyle = .regular(pip: false, fullScreen: false, hideRewind: false) { + didSet { + rewindBackward.isHidden = controlStyle.hideRewind + rewindForward.isHidden = controlStyle.hideRewind + volumeContainer.isHidden = controlStyle.isCompact + togglePip.set(image: controlStyle.isPip ? theme.icons.videoPlayerPIPOut : theme.icons.videoPlayerPIPIn, for: .Normal) + toggleFullscreen.set(image: controlStyle.isPip ? theme.icons.videoPlayerClose : controlStyle.isFullScreen ? theme.icons.videoPlayerExitFullScreen : theme.icons.videoPlayerEnterFullScreen, for: .Normal) + layout() + } + } + + override func mouseUp(with event: NSEvent) { + if progress.hasTemporaryState { + progress.mouseUp(with: event) + } else if volumeSlider.hasTemporaryState { + volumeSlider.mouseUp(with: event) + } else { + let point = self.convert(event.locationInWindow, from: nil) + let rect = NSMakeRect(self.progress.frame.minX, self.progress.frame.minY - 5, self.progress.frame.width, self.progress.frame.height + 10) + if NSPointInRect(point, rect) { + progress.mouseUp(with: event) + } else { + super.mouseUp(with: event) + } + } + } + + private func updateLivePreview() { + guard let window = window else { + return + } + let point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + let rect = NSMakeRect(self.progress.frame.minX, self.progress.frame.minY - 5, self.progress.frame.width, self.progress.frame.height + 10) + + if NSPointInRect(point, rect) || self.progress.hasTemporaryState { + let point = self.progress.convert(window.mouseLocationOutsideOfEventStream, from: nil) + let result = max(min(point.x, self.progress.frame.width), 0) / self.progress.frame.width + self.livePreview?(Float(result)) + } else { + self.livePreview?(nil) + } + } + + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateLivePreview() + + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateLivePreview() + } + + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateLivePreview() + } + + + override var mouseDownCanMoveWindow: Bool { + return false + } + + + fileprivate func update(with status: MediaPlayerStatus, animated: Bool) { + volumeSlider.set(progress: CGFloat(status.volume)) + volumeToggle.set(image: status.volume.isZero ? theme.icons.videoPlayerVolumeOff : theme.icons.videoPlayerVolume, for: .Normal) + + rewindForward.isEnabled = status.duration > 30 && !status.generationTimestamp.isZero + rewindBackward.isEnabled = status.duration > 30 && !status.generationTimestamp.isZero + rewindForward.layer?.opacity = rewindForward.isEnabled ? 1.0 : 0.3 + rewindBackward.layer?.opacity = rewindForward.isEnabled ? 1.0 : 0.3 + + playOrPause.isEnabled = status.duration > 0 + progress.isEnabled = status.duration > 0 + + + switch status.status { + case .playing: + playOrPause.set(image: theme.icons.videoPlayerPause, for: .Normal) + progress.set(progress: status.duration == 0 ? 0 : CGFloat(status.timestamp / status.duration), animated: animated, duration: status.duration, beginTime: status.generationTimestamp, offset: status.timestamp, speed: Float(status.baseRate)) + case .paused: + playOrPause.set(image: status.generationTimestamp == 0 ? theme.icons.videoPlayerPause : theme.icons.videoPlayerPlay, for: .Normal) + progress.set(progress: status.duration == 0 ? 0 : CGFloat(status.timestamp / status.duration), animated: false) + case let .buffering(_, whilePlaying): + playOrPause.set(image: whilePlaying ? theme.icons.videoPlayerPause : theme.icons.videoPlayerPlay, for: .Normal) + progress.set(progress: status.duration == 0 ? 0 : CGFloat(status.timestamp / status.duration), animated: false) + } + let currentTimeAttr: NSAttributedString = .initialize(string: status.timestamp == 0 && status.duration == 0 ? "--:--" : String.durationTransformed(elapsed: Int(status.timestamp)), color: .white, font: .medium(11)) + let durationTimeAttr: NSAttributedString = .initialize(string: status.duration == 0 ? "--:--" : String.durationTransformed(elapsed: Int(status.duration)), color: .white, font: .medium(11)) + + let currentTimeLayout = TextViewLayout(currentTimeAttr, alignment: .right) + let durationLayout = TextViewLayout(durationTimeAttr, alignment: .center) + currentTimeLayout.measure(width: .greatestFiniteMagnitude) + durationLayout.measure(width: .greatestFiniteMagnitude) + + currentTimeView.setFrameSize(currentTimeLayout.layoutSize.width, currentTimeView.frame.height) + durationView.setFrameSize(durationLayout.layoutSize.width > 33 ? 40 : 33, durationView.frame.height) + + + currentTimeView.set(layout: currentTimeLayout) + durationView.set(layout: durationLayout) + + currentTimeView.needsDisplay = true + durationView.needsDisplay = true + } + + var status: MediaPlayerStatus? { + didSet { + if let status = status { + let animated = oldValue?.seekId == status.seekId && (oldValue?.timestamp ?? 0) <= status.timestamp && !status.generationTimestamp.isZero && status != oldValue + update(with: status, animated: animated) + } else { + rewindForward.isEnabled = false + rewindBackward.isEnabled = false + playOrPause.isEnabled = false + } + } + } + + let backgroundView: NSVisualEffectView = NSVisualEffectView() + let playOrPause: ImageButton = ImageButton() + let progress: LinearProgressControl = LinearProgressControl(progressHeight: 5) + let rewindForward: ImageButton = ImageButton() + let rewindBackward: ImageButton = ImageButton() + let toggleFullscreen: ImageButton = ImageButton() + let togglePip: ImageButton = ImageButton() + + var livePreview: ((Float?)->Void)? + + let volumeContainer: View = View() + let volumeToggle: ImageButton = ImageButton() + let volumeSlider: LinearProgressControl = LinearProgressControl(progressHeight: 5) + + private let durationView: TextView = TextView() + private let currentTimeView: TextView = TextView() + + private var controlMovePosition: NSPoint? = nil + + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(backgroundView) + addSubview(playOrPause) + addSubview(progress) + addSubview(rewindForward) + addSubview(rewindBackward) + addSubview(toggleFullscreen) + addSubview(togglePip) + addSubview(durationView) + addSubview(currentTimeView) + + togglePip.hideAnimated = true + + + + durationView.setFrameSize(33, 13) + currentTimeView.setFrameSize(33, 13) + + durationView.userInteractionEnabled = false + durationView.isSelectable = false + durationView.backgroundColor = .clear + + currentTimeView.userInteractionEnabled = false + currentTimeView.isSelectable = false + currentTimeView.backgroundColor = .clear + + + volumeContainer.addSubview(volumeToggle) + volumeContainer.addSubview(volumeSlider) + + volumeToggle.autohighlight = false + volumeToggle.set(image: theme.icons.videoPlayerVolume, for: .Normal) + _ = volumeToggle.sizeToFit() + volumeSlider.setFrameSize(NSMakeSize(60, 12)) + volumeContainer.setFrameSize(NSMakeSize(volumeToggle.frame.width + 60 + 16, volumeToggle.frame.height)) + + volumeSlider.scrubberImage = theme.icons.videoPlayerSliderInteractor + volumeSlider.roundCorners = true + volumeSlider.alignment = .center + volumeSlider.containerBackground = NSColor.grayBackground.withAlphaComponent(0.2) + volumeSlider.style = ControlStyle(foregroundColor: .white, backgroundColor: .clear, highlightColor: .clear) + volumeSlider.set(progress: 0.8) + + volumeSlider.insets = NSEdgeInsetsMake(0, 4.5, 0, 4.5) + + addSubview(volumeContainer) + + backgroundView.material = .dark + backgroundView.blendingMode = .withinWindow + + playOrPause.autohighlight = false + rewindForward.autohighlight = false + rewindBackward.autohighlight = false + toggleFullscreen.autohighlight = false + togglePip.autohighlight = false + + + rewindForward.set(image: theme.icons.videoPlayerRewind15Forward, for: .Normal) + rewindBackward.set(image: theme.icons.videoPlayerRewind15Backward, for: .Normal) + + playOrPause.set(image: theme.icons.videoPlayerPause, for: .Normal) + + toggleFullscreen.set(image: theme.icons.videoPlayerEnterFullScreen, for: .Normal) + togglePip.set(image: theme.icons.videoPlayerPIPIn, for: .Normal) + + + _ = rewindForward.sizeToFit() + _ = rewindBackward.sizeToFit() + _ = playOrPause.sizeToFit() + _ = toggleFullscreen.sizeToFit() + _ = togglePip.sizeToFit() + + progress.insets = NSEdgeInsetsMake(0, 4.5, 0, 4.5) + progress.scrubberImage = theme.icons.videoPlayerSliderInteractor + progress.roundCorners = true + progress.alignment = .center + progress.liveScrobbling = false + progress.fetchingColor = NSColor.grayBackground.withAlphaComponent(0.6) + progress.containerBackground = NSColor.grayBackground.withAlphaComponent(0.2) + progress.style = ControlStyle(foregroundColor: .white, backgroundColor: .clear, highlightColor: .clear) + progress.set(progress: 0, animated: false, duration: 0) + wantsLayer = true + layer?.cornerRadius = 15 + + + self.progress.onLiveScrobbling = { [weak self] _ in + if let `self` = self, !self.progress.mouseInside() { + self.updateLivePreview() + } + } + + set(handler: { [weak self] control in + guard let window = control.window, let superview = control.superview else { + return + } + self?.controlMovePosition = superview.convert(window.mouseLocationOutsideOfEventStream, from: nil) + }, for: .Down) + + + + set(handler: { [weak self] control in + guard let window = control.window, let superview = control.superview, let start = self?.controlMovePosition else { + return + } + var mouse = superview.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + var dif = NSMakePoint(mouse.x - start.x, mouse.y - start.y) + var point = NSMakePoint(control.frame.minX + dif.x, control.frame.minY + dif.y) + + if point.x < 2 || point.x > superview.frame.width - control.frame.width - 4 { + mouse.x = start.x + } + if point.y < 2 || point.y > superview.frame.height - control.frame.height - 4 { + mouse.y = start.y + } + self?.controlMovePosition = mouse + + dif = NSMakePoint(mouse.x - start.x, mouse.y - start.y) + point = NSMakePoint(control.frame.minX + dif.x, control.frame.minY + dif.y) + control.setFrameOrigin(point) + + + }, for: .MouseDragging) + } + + override var isFlipped: Bool { + return true + } + + override func viewWillMove(toWindow newWindow: NSWindow?) { + if let window = newWindow as? Window { + window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + self?.controlMovePosition = nil + return .rejected + }, with: self, for: .leftMouseUp) + } else { + (window as? Window)?.remove(object: self, for: .leftMouseUp) + } + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + override func layout() { + super.layout() + backgroundView.frame = bounds + + playOrPause.centerX(y: 16) + + rewindBackward.setFrameOrigin(playOrPause.frame.minX - rewindBackward.frame.width - 36, 16) + rewindForward.setFrameOrigin(playOrPause.frame.maxX + 36, 16) + + toggleFullscreen.setFrameOrigin(frame.width - toggleFullscreen.frame.width - 16, 16) + + switch controlStyle { + case .compact: + togglePip.setFrameOrigin(16, 16) + case .regular: + togglePip.setFrameOrigin(toggleFullscreen.frame.minX - togglePip.frame.width - 24, 16) + } + + volumeContainer.setFrameOrigin(16, 16) + volumeToggle.centerY(x: 0) + volumeSlider.centerY(x: volumeToggle.frame.maxX + 16) + + + switch controlStyle { + case .compact: + progress.setFrameOrigin(16 + currentTimeView.frame.width + 16, frame.height - 20 - progress.frame.height + (progress.frame.height - progress.progressHeight) / 2) + case .regular: + progress.setFrameOrigin(volumeContainer.frame.minX + volumeSlider.frame.minX, frame.height - 20 - progress.frame.height + (progress.frame.height - progress.progressHeight) / 2) + } + progress.setFrameSize(NSMakeSize(frame.width - progress.frame.origin.x - 16 - 16 - durationView.frame.width, 12)) + + currentTimeView.setFrameOrigin(16, progress.frame.minY) + durationView.setFrameOrigin(frame.width - durationView.frame.width - 16, progress.frame.minY) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class PreviewView : View { + fileprivate let imageView: ImageView = ImageView() + fileprivate let duration: TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(duration) + background = .black + duration.background = darkPalette.grayBackground.withAlphaComponent(0.85) + duration.disableBackgroundDrawing = true + duration.layer?.cornerRadius = 2 + } + + override func layout() { + super.layout() + self.imageView.frame = bounds + self.duration.centerX(y: frame.height - self.duration.frame.height + 1) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +class SVideoView: NSView { + + var initialedSize: NSSize = NSZeroSize + + var controlsStyle:SVideoControlsStyle = .regular(pip: false, fullScreen: false, hideRewind: false) { + didSet { + if oldValue != controlsStyle { + controls.controlStyle = controlsStyle + + if let status = status { + self.controls.update(with: status, animated: false) + self.controls.update(with: status, animated: true) + } + let bufferingStatus = self.bufferingStatus + self.bufferingStatus = bufferingStatus + } + } + } + private let bufferingIndicatorValueDisposable = MetaDisposable() + let bufferingIndicatorValue: Promise = Promise(false) + + var interactions: SVideoInteractions? + + var isStreamable: Bool = true + + private var previewView: PreviewView? + + var status: MediaPlayerStatus? = nil { + didSet { + if status != oldValue { + controls.status = status + if let status = status { + switch status.status { + case .buffering: + bufferingIndicatorValue.set(.single(!isStreamable) |> delay(0.2, queue: Queue.mainQueue())) + default: + bufferingIndicatorValue.set(.single(true)) + } + } else { + bufferingIndicatorValue.set(.single(!isStreamable)) + } + } + + } + } + var bufferingStatus: (IndexSet, Int)? { + didSet { + if let ranges = bufferingStatus { + var bufRanges: [Range] = [] + for range in ranges.0.rangeView { + let low = CGFloat(range.lowerBound) / CGFloat(ranges.1) + let high = CGFloat(range.upperBound) / CGFloat(ranges.1) + let br: Range = Range(uncheckedBounds: (lower: low, upper: high)) + bufRanges.append(br) + } + controls.bufferingRanges = bufRanges + } else { + controls.bufferingRanges = [Range(uncheckedBounds: (lower: -1, upper: -1))] + } + } + } + private let bufferingIndicator: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 40, 40)) + private let controls: SVideoControlsView = SVideoControlsView(frame: NSZeroRect) + let mediaPlayer: MediaPlayerView = MediaPlayerView() + private let backgroundView: NSView = NSView() + override func layout() { + super.layout() + let oldSize = mediaPlayer.frame.size + mediaPlayer.frame = bounds + mediaPlayer.updateLayout() + self.controlsStyle = self.controlsStyle.withUpdatedStyle(compact: frame.width < 300).withUpdatedHideRewind(hideRewind: frame.width < 400) + controls.setFrameSize(self.controlsStyle.isCompact ? 220 : min(frame.width - 10, 510), 94) + let bufferingStatus = self.bufferingStatus + self.bufferingStatus = bufferingStatus + if controls.frame.origin == .zero { + controls.centerX(y: frame.height - controls.frame.height - 24) + } else if oldSize != frame.size { + let dif = oldSize - frame.size + var point = NSMakePoint(controls.frame.minX - dif.width / 2, controls.frame.minY - dif.height / 2) + point.x = min(max(2, point.x), frame.width - controls.frame.width - 4) + point.y = min(max(2, point.y), frame.height - controls.frame.height - 4) + + controls.setFrameOrigin(point) + } + bufferingIndicator.center() + bufferingIndicator.progressColor = .white + backgroundView.frame = bounds + + } + + override var mouseDownCanMoveWindow: Bool { + return true + } + + func hideControls(_ hide: Bool, animated: Bool) { + if !hide { + controls.isHidden = false + } + if hide { + self.hideScrubblerPreviewIfNeeded() + } + + controls._change(opacity: hide ? 0 : 1, animated: animated, duration: 0.2, timingFunction: .linear, completion: { [weak self] completed in + if completed { + self?.controls.isHidden = hide + } + }) + } + + override var isOpaque: Bool { + return true + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + if initialedSize == NSZeroSize { + self.initialedSize = newSize + } + } + + override func mouseUp(with event: NSEvent) { + let point = self.convert(event.locationInWindow, from: nil) + if !NSPointInRect(point, controls.frame) { + super.mouseUp(with: event) + } + } + + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + } + + + var insideControls: Bool { + guard let window = window else {return false} + let point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + return NSPointInRect(point, controls.frame) && !controls.isHidden + } + + private func updateLayout() { + + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var isFlipped: Bool { + return true + } + + func rewindBackward() { + controls.rewindBackward.send(event: .Click) + } + func rewindForward() { + controls.rewindForward.send(event: .Click) + } + + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(backgroundView) + addSubview(mediaPlayer) + addSubview(bufferingIndicator) + addSubview(controls) + bufferingIndicator.innerInset = 8.0 + backgroundView.wantsLayer = true + backgroundView.background = .black + + bufferingIndicator.backgroundColor = .blackTransparent + bufferingIndicator.layer?.cornerRadius = 20 + + backgroundView.isHidden = true + + + controls.playOrPause.set(handler: { [weak self] _ in + self?.interactions?.playOrPause() + }, for: .Click) + + controls.livePreview = { [weak self] value in + guard let `self` = self else {return} + if let status = self.status { + self.interactions?.scrobbling(value != nil ? status.duration * Double(value!) : nil) + self.setCurrentScrubblingState(self.currentPreviewState) + } + } + + controls.progress.onUserChanged = { [weak self] value in + guard let `self` = self else {return} + if let status = self.status { + let result = min(status.duration * Double(value), status.duration) + self.status = status.withUpdatedTimestamp(result) + self.interactions?.rewind(result) + } + } + + controls.volumeSlider.onUserChanged = { [weak self] value in + guard let `self` = self else {return} + self.interactions?.volume(value) + } + controls.volumeToggle.set(handler: { [weak self] _ in + guard let `self` = self else {return} + if let status = self.status { + self.interactions?.volume(status.volume == 0 ? 0.8 : 0) + } + }, for: .Click) + + controls.rewindForward.set(handler: { [weak self] _ in + guard let `self` = self else {return} + if let status = self.status { + self.interactions?.rewind(min(status.timestamp + 15, status.duration)) + } + }, for: .Click) + + controls.rewindBackward.set(handler: { [weak self] _ in + guard let `self` = self else {return} + if let status = self.status { + self.interactions?.rewind(max(status.timestamp - 15, 0)) + } + }, for: .Click) + + controls.toggleFullscreen.set(handler: { [weak self] _ in + guard let `self` = self else {return} + if self.controlsStyle.isPip { + self.interactions?.closePictureInPicture() + } else { + self.interactions?.toggleFullScreen() + } + }, for: .Click) + + controls.togglePip.set(handler: { [weak self] _ in + self?.interactions?.togglePictureInPicture() + }, for: .Click) + + + bufferingIndicatorValueDisposable.set(bufferingIndicatorValue.get().start(next: { [weak self] isHidden in + self?.bufferingIndicator.isHidden = isHidden + })) + } + + deinit { + bufferingIndicatorValueDisposable.dispose() + } + + func set(isInPictureInPicture: Bool) { + self.controlsStyle = self.controlsStyle.withUpdatedPip(isInPictureInPicture) + } + + func set(isInFullScreen: Bool) { + self.controlsStyle = self.controlsStyle.withUpdatedFullScreen(isInFullScreen) + backgroundView.isHidden = !isInFullScreen + } + + func showScrubblerPreviewIfNeeded() { + if previewView == nil { + previewView = PreviewView(frame: NSZeroRect) + previewView?.background = .black + addSubview(previewView!) + } + previewView?.setFrameSize(initialedSize.aspectFitted(NSMakeSize(150, 150))) + } + + private var currentPreviewState: MediaPlayerFramePreviewResult? + + func setCurrentScrubblingState(_ state: MediaPlayerFramePreviewResult?) { + self.currentPreviewState = state + guard let previewView = self.previewView, let window = self.window, let status = self.status, !self.controls.isHidden else { + self.previewView?.removeFromSuperview() + self.previewView = nil + return + } + let point = self.controls.progress.convert(window.mouseLocationOutsideOfEventStream, from: nil) + + if let state = currentPreviewState { + switch state { + case let .image(image): + previewView.imageView.image = image + previewView.imageView.isHidden = false + case .waitingForData: + break + } + } + + + let progressPoint = NSMakePoint(max(0, min(point.x, self.controls.progress.frame.width)), 0) + let converted = self.convert(progressPoint, from: self.controls.progress) + previewView.setFrameOrigin(NSMakePoint(max(10, min(frame.width - previewView.frame.width - 10, converted.x - previewView.frame.width / 2)), self.controls.frame.minY - previewView.frame.height - 10)) + + + let currentTime = Int(round(progressPoint.x / self.controls.progress.frame.width * CGFloat(status.duration))) + + + let duration = String.durationTransformed(elapsed: currentTime) + let layout = TextViewLayout(.initialize(string: duration, color: .white, font: .medium(.text)), maximumNumberOfLines: 1, alignment: .center, alwaysStaticItems: true) + + layout.measure(width: .greatestFiniteMagnitude) + + previewView.duration.update(layout) + previewView.duration.setFrameSize(NSMakeSize(layout.layoutSize.width + 10, layout.layoutSize.height + 10)) + previewView.duration.display() + previewView.needsLayout = true + } + + + func hideScrubblerPreviewIfNeeded() { + previewView?.removeFromSuperview() + previewView = nil + self.currentPreviewState = nil + } +} diff --git a/Telegram-Mac/SampleBufferPool.swift b/Telegram-Mac/SampleBufferPool.swift new file mode 100644 index 0000000000..e1cd1cb3a5 --- /dev/null +++ b/Telegram-Mac/SampleBufferPool.swift @@ -0,0 +1,73 @@ +// +// SampleBufferPool.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/05/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import AVFoundation +import SwiftSignalKit + + +private final class SampleBufferLayerImplNullAction: NSObject, CAAction { + @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { + } +} + +private final class SampleBufferLayerImpl: AVSampleBufferDisplayLayer { + override func action(forKey event: String) -> CAAction? { + return SampleBufferLayerImplNullAction() + } +} + +final class SampleBufferLayer { + let layer: AVSampleBufferDisplayLayer + private let enqueue: (AVSampleBufferDisplayLayer) -> Void + + + var isFreed: Bool = false + fileprivate init(layer: AVSampleBufferDisplayLayer, enqueue: @escaping (AVSampleBufferDisplayLayer) -> Void) { + self.layer = layer + self.enqueue = enqueue + } + + deinit { + if !isFreed { + self.enqueue(self.layer) + } + } +} + +private let pool = Atomic<[AVSampleBufferDisplayLayer]>(value: []) + +func clearSampleBufferLayerPoll() { + let _ = pool.modify { _ in return [] } +} + +func takeSampleBufferLayer() -> SampleBufferLayer { + var layer: AVSampleBufferDisplayLayer? +// let _ = pool.modify { list in +// var list = list +// if !list.isEmpty { +// layer = list.removeLast() +// } +// return list +// } + if layer == nil { + layer = SampleBufferLayerImpl() + } + return SampleBufferLayer(layer: layer!, enqueue: { layer in + Queue.mainQueue().async { + layer.flushAndRemoveImage() + layer.setAffineTransform(CGAffineTransform.identity) +// let _ = pool.modify { list in +// var list = list +// list.append(layer) +// return list +// } + } + }) +} diff --git a/Telegram-Mac/SaveModalController.swift b/Telegram-Mac/SaveModalController.swift new file mode 100644 index 0000000000..915b4be052 --- /dev/null +++ b/Telegram-Mac/SaveModalController.swift @@ -0,0 +1,141 @@ +// +// SaveModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 19.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + + +private final class SaveModalView : NSVisualEffectView { + private let imageView:MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + private let textView: TextView = TextView() + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(textView) + self.wantsLayer = true + self.layer?.cornerRadius = 10.0 + self.autoresizingMask = [] + self.autoresizesSubviews = false + self.material = .ultraDark + self.blendingMode = .withinWindow + self.state = .active + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + } + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + } + + override var isFlipped: Bool { + return true + } + + override func layout() { + super.layout() + + if !textView.isHidden { + imageView.centerX(y: 0) + textView.centerX(y: imageView.frame.maxY - 15) + } else { + imageView.centerX(y: 0) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(animation: LocalAnimatedSticker, size: NSSize, context: AccountContext, text: TextViewLayout?) { + imageView.update(with: animation.file, size: size, context: context, parent: nil, table: nil, parameters: animation.parameters, animated: false, positionFlags: nil, approximateSynchronousValue: false) + textView.isSelectable = false + textView.isHidden = text == nil + textView.update(text) + needsLayout = true + } +} + +class SaveModalController : ModalViewController { + override var background: NSColor { + return .clear + } + + override var contentBelowBackground: Bool { + return true + } + + override var containerBackground: NSColor { + return .clear + } + + override func viewClass() -> AnyClass { + return SaveModalView.self + } + private var genericView: SaveModalView { + return self.view as! SaveModalView + } + + override var redirectMouseAfterClosing: Bool { + return true + } + + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + + override func viewDidLoad() { + super.viewDidLoad() + genericView.update(animation: self.animation, size: NSMakeSize(200, 150), context: self.context, text: self.text) + readyOnce() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + private let animation: LocalAnimatedSticker + private let text: TextViewLayout? + private let context: AccountContext + init(_ animation: LocalAnimatedSticker, context: AccountContext, text: TextViewLayout? = nil) { + self.animation = animation + self.text = text + self.context = context + super.init(frame: NSMakeRect(0, 0, 200, 200)) + self.bar = .init(height: 0) + } +} + + + +func showSaveModal(for window: Window, context: AccountContext, animation: LocalAnimatedSticker, text: TextViewLayout? = nil, delay _delay: Double) -> Signal { + + let modal = SaveModalController(animation, context: context, text: text) + + return Signal({ _ -> Disposable in + showModal(with: modal, for: window, animationType: .scaleCenter) + return ActionDisposable { + modal.close() + } + }) |> timeout(_delay, queue: Queue.mainQueue(), alternate: Signal({ _ -> Disposable in + modal.close() + return EmptyDisposable + })) +} diff --git a/Telegram-Mac/ScheduledMessageModalController.swift b/Telegram-Mac/ScheduledMessageModalController.swift new file mode 100644 index 0000000000..7f1cbdbda6 --- /dev/null +++ b/Telegram-Mac/ScheduledMessageModalController.swift @@ -0,0 +1,387 @@ +// +// ScheduledMessageModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 07/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import TGUIKit +import TelegramCore +import SwiftSignalKit +import SyncCore +import Postbox + +private var timeIntervals:[TimeInterval?] { + var intervals:[TimeInterval?] = [] + for i in 0 ... 23 { + let current = Double(i) * 60.0 * 60 + intervals.append(current) +// #if DEBUG + for i in 1 ... 59 { + intervals.append(current + Double(i) * 60.0) + } + if i < 23 { + intervals.append(nil) + } + + } + return intervals +} + +private var dayFormatter: DateFormatter { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: appAppearance.language.languageCode) + //dateFormatter.timeZone = TimeZone(abbreviation: "UTC")! + dateFormatter.dateFormat = "MMM d, yyyy" + return dateFormatter +} + +private var dayFormatterRelative: DateFormatter { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: appAppearance.language.languageCode) + // dateFormatter.timeZone = TimeZone(abbreviation: "UTC")! + + dateFormatter.dateStyle = .short + dateFormatter.doesRelativeDateFormatting = true + return dateFormatter +} + +private func formatDay(_ date: Date) -> String { + if CalendarUtils.isSameDate(date, date: Date(), checkDay: true) { + return dayFormatterRelative.string(from: date) + } else { + return dayFormatter.string(from: date) + } +} + +private func formatTime(_ date: Date) -> String { + let timeFormatter = DateFormatter() + timeFormatter.timeStyle = .medium + // timeFormatter.timeZone = TimeZone(abbreviation: "UTC")! + return timeFormatter.string(from: date) +} + +final class ScheduledMessageModalView : View { + fileprivate let dayPicker: DatePicker + private let atView = TextView() + fileprivate let timePicker: TimePicker + private let containerView = View() + fileprivate let sendOn = TitleButton() + fileprivate let sendWhenOnline = TitleButton() + required init(frame frameRect: NSRect) { + + self.dayPicker = DatePicker(selected: DatePickerOption(name: formatDay(Date()), value: Date())) + self.timePicker = TimePicker(selected: TimePickerOption(hours: 0, minutes: 0, seconds: 0)) + super.init(frame: frameRect) + containerView.addSubview(self.dayPicker) + containerView.addSubview(self.atView) + containerView.addSubview(self.timePicker) + self.addSubview(self.containerView) + self.addSubview(sendOn) + self.atView.userInteractionEnabled = false + self.atView.isSelectable = false + self.sendOn.layer?.cornerRadius = .cornerRadius + self.sendOn.disableActions() + self.addSubview(self.sendWhenOnline) + self.updateLocalizationAndTheme(theme: theme) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + self.sendOn.set(font: .medium(.text), for: .Normal) + self.sendOn.set(color: .white, for: .Normal) + self.sendOn.set(background: theme.colors.accent, for: .Normal) + self.sendOn.set(background: theme.colors.accent.withAlphaComponent(0.8), for: .Highlight) + + self.sendWhenOnline.set(font: .normal(.text), for: .Normal) + self.sendWhenOnline.set(color: theme.colors.accent, for: .Normal) + self.sendWhenOnline.set(text: L10n.scheduleSendWhenOnline, for: .Normal) + _ = self.sendWhenOnline.sizeToFit() + + let atLayout = TextViewLayout(.initialize(string: L10n.scheduleControllerAt, color: theme.colors.text, font: .normal(.title)), alwaysStaticItems: true) + atLayout.measure(width: .greatestFiniteMagnitude) + atView.update(atLayout) + + needsLayout = true + } + + func possibleSendWhenOnline(_ sendWhenOnline: Bool) { + self.sendWhenOnline.isHidden = !sendWhenOnline + } + + override func layout() { + super.layout() + self.dayPicker.setFrameSize(NSMakeSize(115, 30)) + self.timePicker.setFrameSize(NSMakeSize(115, 30)) + + let fullWidth = dayPicker.frame.width + 15 + atView.frame.width + 15 + timePicker.frame.width + self.containerView.setFrameSize(NSMakeSize(fullWidth, max(dayPicker.frame.height, timePicker.frame.height))) + + self.dayPicker.centerY(x: 0) + self.atView.centerY(x: self.dayPicker.frame.maxX + 15) + self.timePicker.centerY(x: self.atView.frame.maxX + 15) + + self.containerView.centerX(y: 30) + + _ = self.sendOn.sizeToFit(NSZeroSize, NSMakeSize(fullWidth, 30), thatFit: true) + + self.sendOn.centerX(y: containerView.frame.maxY + 30) + + self.sendWhenOnline.centerX(y: self.sendOn.frame.maxY + 15) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +private extension TimePickerOption { + var interval: TimeInterval { + let hours = Double(self.hours) * 60.0 * 60 + let minutes = Double(self.minutes) * 60.0 + let seconds = Double(self.seconds) + return hours + minutes + seconds + } +} +class ScheduledMessageModalController: ModalViewController { + private let context: AccountContext + private let scheduleAt: (Date)->Void + private let defaultDate: Date? + private let peerId: PeerId + private var sendWhenOnline: Bool = false + private let disposable = MetaDisposable() + init(context: AccountContext, defaultDate: Date? = nil, peerId: PeerId, scheduleAt:@escaping(Date)->Void) { + self.context = context + self.defaultDate = defaultDate + self.scheduleAt = scheduleAt + self.peerId = peerId + super.init(frame: NSMakeRect(0, 0, 350, 200)) + self.bar = .init(height: 0) + } + + override func viewClass() -> AnyClass { + return ScheduledMessageModalView.self + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: ModalHeaderData(title: nil, image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: L10n.scheduleControllerTitle, handler: { + + }), right: nil) + } + + + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(frame.width, sendWhenOnline ? 170 : 150), animated: false) + } + + override var dynamicSize: Bool { + return true + } + + var genericView: ScheduledMessageModalView { + return self.view as! ScheduledMessageModalView + } + + private func applyDay(_ date: Date) { + genericView.dayPicker.selected = DatePickerOption(name: formatDay(date), value: date) + let current = date.addingTimeInterval(self.genericView.timePicker.selected.interval) + + if CalendarUtils.isSameDate(Date(), date: date, checkDay: true) { + + if current < Date() { + for interval in timeIntervals.compactMap ({$0}) { + let new = date.startOfDay.addingTimeInterval(interval) + if new > Date() { + applyTime(new) + break + } + } + } else { + if date != current { + applyTime(date.addingTimeInterval(current.timeIntervalSince1970 - current.startOfDay.timeIntervalSince1970)) + } else { + applyTime(date) + } + } + } else { + if date != current { + applyTime(date.addingTimeInterval(current.timeIntervalSince1970 - current.startOfDay.timeIntervalSince1970)) + } else { + applyTime(date) + } + } + } + + private func applyTime(_ date: Date) { + + + var t: time_t = time_t(date.timeIntervalSince1970) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + genericView.timePicker.selected = TimePickerOption(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, seconds: timeinfo.tm_sec) + + if CalendarUtils.isSameDate(Date(), date: date, checkDay: true) { + genericView.sendOn.set(text: L10n.scheduleSendToday(formatTime(date)), for: .Normal) + } else { + genericView.sendOn.set(text: L10n.scheduleSendDate(formatDay(date), formatTime(date)), for: .Normal) + } + } + + override var handleAllEvents: Bool { + return true + } + + private func schedule() { + let day = self.genericView.dayPicker.selected.value + let date = day.startOfDay.addingTimeInterval(self.genericView.timePicker.selected.interval) + if CalendarUtils.isSameDate(Date(), date: day, checkDay: true) { + if Date() > date { + genericView.timePicker.shake() + return + } + } + self.scheduleAt(date) + self.close() + } + + override func returnKeyAction() -> KeyHandlerResult { + self.schedule() + return .invoked + } + + override func firstResponder() -> NSResponder? { + return genericView.timePicker.firstResponder + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(handler: { () -> KeyHandlerResult in + + return .invokeNext + }, with: self, for: .Tab, priority: .modal) + window?.set(handler: { () -> KeyHandlerResult in + + return .invokeNext + }, with: self, for: .LeftArrow, priority: .modal) + window?.set(handler: { () -> KeyHandlerResult in + + return .invokeNext + }, with: self, for: .RightArrow, priority: .modal) + } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + override func viewDidLoad() { + super.viewDidLoad() + + let peerId = self.peerId + let context = self.context + let presence = context.account.postbox.transaction { + $0.getPeerPresence(peerId: peerId) as? TelegramUserPresence + } |> deliverOnMainQueue + + disposable.set(presence.start(next: { [weak self] presence in + + var sendWhenOnline: Bool = false + if let presence = presence { + switch presence.status { + case .present: + sendWhenOnline = peerId != context.peerId + default: + break + } + } + self?.sendWhenOnline = sendWhenOnline + self?.initialize() + })) + + } + + private func initialize() { + let date = self.defaultDate ?? Date() + + var t: time_t = time_t(date.timeIntervalSince1970) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + self.genericView.dayPicker.selected = DatePickerOption(name: formatDay(date), value: date) + self.genericView.timePicker.selected = TimePickerOption(hours: 0, minutes: 0, seconds: 0) + + self.genericView.possibleSendWhenOnline(self.sendWhenOnline) + + self.applyDay(date) + + self.genericView.timePicker.update = { [weak self] updated in + guard let `self` = self else { + return false + } + + let day = self.genericView.dayPicker.selected.value + + let date = day.startOfDay.addingTimeInterval(updated.interval) + + self.applyTime(date) + return true + } + + self.genericView.sendOn.set(handler: { [weak self] _ in + self?.schedule() + }, for: .Click) + + self.genericView.sendWhenOnline.set(handler: { [weak self] _ in + self?.scheduleAt(Date(timeIntervalSince1970: TimeInterval(scheduleWhenOnlineTimestamp))) + self?.close() + }, for: .Click) + + self.readyOnce() + + self.genericView.dayPicker.set(handler: { [weak self] control in + if let control = control as? DatePicker, let window = self?.window, !hasPopover(window) { + let calendar = CalendarController(NSMakeRect(0, 0, 250, 250), window, current: control.selected.value, onlyFuture: true, selectHandler: { [weak self] date in + self?.applyDay(date) + }) + showPopover(for: control, with: calendar, edge: .maxY, inset: NSMakePoint(-8, -50)) + } + + }, for: .Down) + + self.genericView.timePicker.set(handler: { [weak self] control in + if let control = control as? DatePicker, let `self` = self, let window = self.window, !hasPopover(window) { + var items:[SPopoverItem] = [] + + let day = self.genericView.dayPicker.selected.value + + for interval in timeIntervals { + if let interval = interval { + let date = day.startOfDay.addingTimeInterval(interval) + if CalendarUtils.isSameDate(Date(), date: day, checkDay: true) { + if Date() > date { + continue + } + } + items.append(SPopoverItem(formatTime(date), { [weak self] in + self?.applyTime(date) + }, height: 30)) + } else if !items.isEmpty { + items.append(SPopoverItem()) + } + } + showPopover(for: control, with: SPopoverViewController(items: items, visibility: 6), edge: .maxY, inset: NSMakePoint(0, -50)) + } + + }, for: .Down) + } + + deinit { + disposable.dispose() + } +} diff --git a/Telegram-Mac/SearchController.swift b/Telegram-Mac/SearchController.swift index cc4c75a943..fc7e7b8691 100644 --- a/Telegram-Mac/SearchController.swift +++ b/Telegram-Mac/SearchController.swift @@ -8,19 +8,28 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +enum UnreadSearchBadge : Equatable { + case none + case muted(Int32) + case unmuted(Int32) +} + final class SearchControllerArguments { - let account: Account + let context: AccountContext let removeRecentPeerId:(PeerId)->Void let clearRecent:()->Void - init(account: Account, removeRecentPeerId:@escaping(PeerId)->Void, clearRecent:@escaping()->Void) { - self.account = account + let openTopPeer:(PopularItemType)->Void + init(context: AccountContext, removeRecentPeerId:@escaping(PeerId)->Void, clearRecent:@escaping()->Void, openTopPeer:@escaping(PopularItemType)->Void) { + self.context = context self.removeRecentPeerId = removeRecentPeerId self.clearRecent = clearRecent + self.openTopPeer = openTopPeer } } @@ -28,57 +37,13 @@ final class SearchControllerArguments { enum ChatListSearchEntryStableId: Hashable { case localPeerId(PeerId) case secretChat(PeerId) + case savedMessages case recentSearchPeerId(PeerId) case globalPeerId(PeerId) case messageId(MessageId) + case topPeers case separator(Int) case emptySearch - static func ==(lhs: ChatListSearchEntryStableId, rhs: ChatListSearchEntryStableId) -> Bool { - switch lhs { - case let .localPeerId(lhsPeerId): - if case let .localPeerId(rhsPeerId) = rhs { - return lhsPeerId == rhsPeerId - } else { - return false - } - case let .secretChat(peerId): - if case .secretChat(peerId) = rhs { - return true - } else { - return false - } - case let .recentSearchPeerId(lhsPeerId): - if case let .recentSearchPeerId(rhsPeerId) = rhs { - return lhsPeerId == rhsPeerId - } else { - return false - } - case let .globalPeerId(lhsPeerId): - if case let .globalPeerId(rhsPeerId) = rhs { - return lhsPeerId == rhsPeerId - } else { - return false - } - case let .messageId(lhsMessageId): - if case let .messageId(rhsMessageId) = rhs { - return lhsMessageId == rhsMessageId - } else { - return false - } - case let .separator(lhsIndex): - if case let .separator(rhsIndex) = rhs { - return lhsIndex == rhsIndex - } else { - return false - } - case .emptySearch: - if case .emptySearch = rhs { - return true - } else { - return false - } - } - } var hashValue: Int { switch self { @@ -90,49 +55,56 @@ enum ChatListSearchEntryStableId: Hashable { return peerId.hashValue case let .globalPeerId(peerId): return peerId.hashValue + case .savedMessages: + return 1000 case let .messageId(messageId): return messageId.hashValue case let .separator(index): return index case .emptySearch: return 0 + case .topPeers: + return -1 } } } private struct SearchSecretChatWrapper : Equatable { let peerId:PeerId - static func ==(lhs: SearchSecretChatWrapper, rhs: SearchSecretChatWrapper) -> Bool { - return lhs.peerId == rhs.peerId - } } fileprivate enum ChatListSearchEntry: Comparable, Identifiable { - case localPeer(Peer, Int, SearchSecretChatWrapper?, Bool) - case recentlySearch(Peer, Int, SearchSecretChatWrapper?, Bool) - case globalPeer(Peer, Int) - case message(Message,Int) + case localPeer(Peer, Int, SearchSecretChatWrapper?, UnreadSearchBadge, Bool) + case recentlySearch(Peer, Int, SearchSecretChatWrapper?, PeerStatusStringResult, UnreadSearchBadge, Bool) + case globalPeer(FoundPeer, UnreadSearchBadge, Int) + case savedMessages(Peer) + case message(Message, String, CombinedPeerReadState?, Int) case separator(text: String, index:Int, state:SeparatorBlockState) + case topPeers(Int, articlesEnabled: Bool, unreadArticles: Int32, selfPeer: Peer, peers: [Peer], unread: [PeerId: UnreadSearchBadge], online: [PeerId : Bool]) case emptySearch var stableId: ChatListSearchEntryStableId { switch self { - case let .localPeer(peer, _, secretChat, _): + case let .localPeer(peer, _, secretChat, _, _): if let secretChat = secretChat { return .secretChat(secretChat.peerId) } return .localPeerId(peer.id) - case let .globalPeer(peer, _): - return .globalPeerId(peer.id) - case let .message(message,_): + case let .globalPeer(found, _, _): + return .globalPeerId(found.peer.id) + case let .message(message, _, _, _): return .messageId(message.id) + case .savedMessages: + return .savedMessages case let .separator(_,index, _): return .separator(index) - case let .recentlySearch(peer, _, secretChat, _): + case let .recentlySearch(peer, _, secretChat, _, _, _): if let secretChat = secretChat { return .secretChat(secretChat.peerId) } return .recentSearchPeerId(peer.id) + case .topPeers: + return .topPeers case .emptySearch: return .emptySearch } @@ -140,15 +112,19 @@ fileprivate enum ChatListSearchEntry: Comparable, Identifiable { var index:Int { switch self { - case let .localPeer(_,index, _, _): + case let .localPeer(_,index, _, _, _): return index - case let .globalPeer(_,index): + case let .globalPeer(_, _,index): return index - case let .message(_,index): + case let .message(_, _, _, index): return index + case .savedMessages: + return -1 case let .separator(_,index, _): return index - case let .recentlySearch(_,index, _, _): + case let .recentlySearch(_,index, _, _, _, _): + return index + case let .topPeers(index, _, _, _, _, _, _): return index case .emptySearch: return 0 @@ -157,30 +133,33 @@ fileprivate enum ChatListSearchEntry: Comparable, Identifiable { static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool { switch lhs { - case let .localPeer(lhsPeer, lhsIndex, lhsSecretChat, lhsDrawBorder): - if case let .localPeer(rhsPeer, rhsIndex, rhsSecretChat, rhsDrawBorder) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsSecretChat == rhsSecretChat && lhsDrawBorder == rhsDrawBorder { + case let .localPeer(lhsPeer, lhsIndex, lhsSecretChat, lhsBadge, lhsDrawBorder): + if case let .localPeer(rhsPeer, rhsIndex, rhsSecretChat, rhsBadge, rhsDrawBorder) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsSecretChat == rhsSecretChat && lhsDrawBorder == rhsDrawBorder && lhsBadge == rhsBadge { return true } else { return false } - case let .recentlySearch(lhsPeer, lhsIndex, lhsSecretChat, lhsDrawBorder): - if case let .recentlySearch(rhsPeer, rhsIndex, rhsSecretChat, rhsDrawBorder) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsSecretChat == rhsSecretChat && lhsDrawBorder == rhsDrawBorder { + case let .recentlySearch(lhsPeer, lhsIndex, lhsSecretChat, lhsStatus, lhsBadge, lhsDrawBorder): + if case let .recentlySearch(rhsPeer, rhsIndex, rhsSecretChat, rhsStatus, rhsBadge, rhsDrawBorder) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsSecretChat == rhsSecretChat && lhsDrawBorder == rhsDrawBorder && lhsStatus == rhsStatus && lhsBadge == rhsBadge { return true } else { return false } - case let .globalPeer(lhsPeer, lhsIndex): - if case let .globalPeer(rhsPeer, rhsIndex) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex { + case let .globalPeer(lhsPeer, lhsBadge, lhsIndex): + if case let .globalPeer(rhsPeer, rhsBadge, rhsIndex) = rhs, lhsPeer.peer.isEqual(rhsPeer.peer) && lhsIndex == rhsIndex && lhsPeer.subscribers == rhsPeer.subscribers && lhsBadge == rhsBadge { return true } else { return false } - case let .message(lhsMessage, lhsIndex): - if case let .message(rhsMessage, rhsIndex) = rhs { - - if lhsIndex != rhsIndex { - return false - } + case .savedMessages: + if case .savedMessages = rhs { + return true + } else { + return false + } + case let .message(lhsMessage, text, combinedState, index): + if case .message(let rhsMessage, text, combinedState, index) = rhs { + if lhsMessage.id != rhsMessage.id { return false } @@ -207,6 +186,29 @@ fileprivate enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } + case let .topPeers(index, articlesEnabled, unreadArticles, lhsSelfPeer, lhsPeers, lhsUnread, online): + if case .topPeers(index, articlesEnabled, unreadArticles, let rhsSelfPeer, let rhsPeers, let rhsUnread, online) = rhs { + if !lhsSelfPeer.isEqual(rhsSelfPeer) { + return false + } + + if lhsUnread != rhsUnread { + return false + } + + if lhsPeers.count != rhsPeers.count { + return false + } else { + for i in 0 ..< lhsPeers.count { + if !lhsPeers[i].isEqual(lhsPeers[i]) { + return false + } + } + return true + } + } else { + return false + } } } @@ -216,36 +218,155 @@ fileprivate enum ChatListSearchEntry: Comparable, Identifiable { } -fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], arguments:SearchControllerArguments, initialSize:NSSize) -> TableEntriesTransition<[AppearanceWrapperEntry]> { +private func peerContextMenuItems(peer: Peer, pinnedItems:[PinnedItemId], arguments: SearchControllerArguments) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + + let togglePin:(Peer) -> Void = { peer in + let updatePeer = arguments.context.account.postbox.transaction { transaction -> Void in + updatePeers(transaction: transaction, peers: [peer], update: { (_, updated) -> Peer? in + return updated + }) + } |> mapToSignal { _ -> Signal in + return toggleItemPinned(postbox: arguments.context.account.postbox, location: .group(.root), itemId: .peer(peer.id)) + } |> deliverOnMainQueue + + _ = updatePeer.start(next: { result in + switch result { + case .limitExceeded: + confirm(for: arguments.context.window, information: L10n.chatListContextPinErrorNew2, okTitle: L10n.alertOK, cancelTitle: "", thridTitle: L10n.chatListContextPinErrorNewSetupFolders, successHandler: { result in + switch result { + case .thrid: + arguments.context.sharedContext.bindings.rootNavigation().push(ChatListFiltersListController(context: arguments.context)) + default: + break + } + }) + default: + break + } + }) + } + + var isPinned: Bool = false + for item in pinnedItems { + switch item { + case let .peer(peerId): + if peerId == peer.id { + isPinned = true + break + } + } + } + + items.append(ContextMenuItem(isPinned ? L10n.chatListContextUnpin : L10n.chatListContextPin, handler: { + togglePin(peer) + })) + + let peerId = peer.id + + return .single(items) |> mapToSignal { items in + return chatListFilterPreferences(postbox: arguments.context.account.postbox) |> deliverOnMainQueue |> take(1) |> map { filters -> [ContextMenuItem] in + var items = items + var submenu: [ContextMenuItem] = [] + if peerId.namespace != Namespaces.Peer.SecretChat { + for item in filters.list { + submenu.append(ContextMenuItem(item.title, handler: { + _ = updateChatListFiltersInteractively(postbox: arguments.context.account.postbox, { list in + var list = list + for (i, folder) in list.enumerated() { + var folder = folder + if folder.id == item.id { + if item.data.includePeers.peers.contains(peerId) { + var peers = folder.data.includePeers.peers + peers.removeAll(where: { $0 == peerId }) + folder.data.includePeers.setPeers(peers) + } else { + folder.data.includePeers.setPeers(folder.data.includePeers.peers + [peerId]) + } + list[i] = folder + + } + } + return list + }).start() + }, state: item.data.includePeers.peers.contains(peerId) ? NSControl.StateValue.on : nil)) + } + } + + if !submenu.isEmpty { + items.append(ContextSeparatorItem()) + let item = ContextMenuItem(L10n.chatListFilterAddToFolder) + let menu = NSMenu() + for item in submenu { + menu.addItem(item) + } + item.submenu = menu + items.append(item) + } + return items + } + } +} + + +fileprivate func prepareEntries(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], arguments:SearchControllerArguments, pinnedItems:[PinnedItemId], initialSize:NSSize, animated: Bool) -> TableEntriesTransition<[AppearanceWrapperEntry]> { let (deleted,inserted, updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in switch entry.entry { - case let .message(message,_): - let item = ChatListMessageRowItem(initialSize, account: arguments.account, message: message, renderedPeer: RenderedPeer(message: message)) + case let .message(message, query, combinedState, _): + var peer = RenderedPeer(message: message) + if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference { + if let channelPeer = message.peers[migrationReference.peerId] { + peer = RenderedPeer(peer: channelPeer) + } + } + let item = ChatListMessageRowItem(initialSize, context: arguments.context, message: message, query: query, renderedPeer: peer, readState: combinedState) return item - case let .globalPeer(peer,_): - return RecentPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: entry.stableId, borderType: [.Right]) - case let .localPeer(peer, _, secretChat, drawBorder), let .recentlySearch(peer, _, secretChat, drawBorder): - - var canRemoveFromRecent: Bool = false - if case .recentlySearch = entry.entry { - canRemoveFromRecent = true + case let .globalPeer(foundPeer, badge, _): + var status: String? = nil + if let addressName = foundPeer.peer.addressName { + status = "@\(addressName)" + } + if let subscribers = foundPeer.subscribers, let username = status { + if foundPeer.peer.isChannel { + status = tr(L10n.searchGlobalChannel1Countable(username, Int(subscribers))) + } else if foundPeer.peer.isSupergroup || foundPeer.peer.isGroup { + status = tr(L10n.searchGlobalGroup1Countable(username, Int(subscribers))) + } } - let item = RecentPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: entry.stableId, titleStyle: ControlStyle(font: .medium(.text), foregroundColor: secretChat != nil ? theme.colors.blueUI : theme.colors.text, highlightColor:.white), borderType: [.Right], drawCustomSeparator: drawBorder, canRemoveFromRecent: canRemoveFromRecent, removeAction: { - arguments.removeRecentPeerId(peer.id) - }) - return item + + return RecentPeerRowItem(initialSize, peer: foundPeer.peer, account: arguments.context.account, stableId: entry.stableId, statusStyle:ControlStyle(font:.normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status: status, borderType: [.Right], contextMenuItems: { + return peerContextMenuItems(peer: foundPeer.peer, pinnedItems: pinnedItems, arguments: arguments) + }, unreadBadge: badge) + case let .localPeer(peer, _, secretChat, badge, drawBorder): + return RecentPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: entry.stableId, titleStyle: ControlStyle(font: .medium(.text), foregroundColor: secretChat != nil ? theme.colors.accent : theme.colors.text, highlightColor:.white), borderType: [.Right], drawCustomSeparator: drawBorder, isLookSavedMessage: true, drawLastSeparator: true, canRemoveFromRecent: false, contextMenuItems: { + return peerContextMenuItems(peer: peer, pinnedItems: pinnedItems, arguments: arguments) + }, unreadBadge: badge) + case let .recentlySearch(peer, _, secretChat, status, badge, drawBorder): + return RecentPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: entry.stableId, titleStyle: ControlStyle(font: .medium(.text), foregroundColor: secretChat != nil ? theme.colors.accent : theme.colors.text, highlightColor:.white), statusStyle: ControlStyle(font:.normal(.text), foregroundColor: status.status.attribute(NSAttributedString.Key.foregroundColor, at: 0, effectiveRange: nil) as? NSColor ?? theme.colors.grayText, highlightColor:.white), status: status.status.string, borderType: [.Right], drawCustomSeparator: drawBorder, isLookSavedMessage: true, drawLastSeparator: true, canRemoveFromRecent: true, removeAction: { + if let secretChat = secretChat { + arguments.removeRecentPeerId(secretChat.peerId) + } else { + arguments.removeRecentPeerId(peer.id) + } + }, contextMenuItems: { + return peerContextMenuItems(peer: peer, pinnedItems: pinnedItems, arguments: arguments) + }, unreadBadge: badge) + case let .savedMessages(peer): + return RecentPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: entry.stableId, titleStyle: ControlStyle(font: .medium(.text), foregroundColor: theme.colors.text, highlightColor:.white), borderType: [.Right], drawCustomSeparator: false, isLookSavedMessage: true, contextMenuItems: { + return peerContextMenuItems(peer: peer, pinnedItems: pinnedItems, arguments: arguments) + }) case let .separator(text, index, state): let right:String? switch state { case .short: - right = tr(.separatorShowMore) + right = tr(L10n.separatorShowMore) case .all: - right = tr(.separatorShowLess) + right = tr(L10n.separatorShowLess) case .clear: - right = tr(.separatorClear) + right = tr(L10n.separatorClear) default: right = nil @@ -253,25 +374,77 @@ fileprivate func prepareEntries(from:[AppearanceWrapperEntry,TableViewDelegate { - private let account:Account - private let arguments:SearchControllerArguments - private var open:(PeerId, Message?, Bool) -> Void = {_,_,_ in} + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + + + var defaultQuery: String? = nil + private let context:AccountContext + private var marked: Bool = false + private let arguments:SearchControllerArguments + private var open:(PeerId?, MessageId?, Bool) -> Void = {_,_,_ in} + private let groupId: PeerGroupId private let searchQuery:Promise = Promise() + private var query: String? = nil private let openPeerDisposable:MetaDisposable = MetaDisposable() private let statePromise:Promise<(SeparatorBlockState,SeparatorBlockState)> = Promise((SeparatorBlockState.short, SeparatorBlockState.short)) private let disposable:MetaDisposable = MetaDisposable() + private let pinnedPromise: ValuePromise<[PinnedItemId]> = ValuePromise([], ignoreRepeated: true) + + private let isRevealed: ValuePromise = ValuePromise(false, ignoreRepeated: true) + + var pinnedItems:[PinnedItemId] = [] { + didSet { + pinnedPromise.set(pinnedItems) + } + } + let isLoading = Promise(false) @@ -281,23 +454,87 @@ class SearchController: GenericViewController,TableViewDelegate { genericView.needUpdateVisibleAfterScroll = true genericView.border = [.Right] - let account = self.account + genericView.getBackgroundColor = { + theme.colors.background + } + + let context = self.context + let options = self.options + let searchMessagesState: ValuePromise = ValuePromise() + let searchMessagesStateValue: Atomic = Atomic(value: nil) - + let isRevealed = self.isRevealed.get() + let arguments = self.arguments let statePromise = self.statePromise.get() let atomicSize = self.atomicSize let previousSearchItems = Atomic<[AppearanceWrapperEntry]>(value: []) - - let searchItems = searchQuery.get() |> mapToSignal { (query) -> Signal<([ChatListSearchEntry], Bool), Void> in + let groupId: PeerGroupId = self.groupId + let searchItems = searchQuery.get() |> mapToSignal { query -> Signal<([ChatListSearchEntry], Bool, Bool, SearchMessagesState?), NoError> in if let query = query, !query.isEmpty { + + var ids:[PeerId:PeerId] = [:] - let foundLocalPeers = combineLatest(account.postbox.searchPeers(query: query.lowercased()),account.postbox.searchContacts(query: query.lowercased())) - |> map { peers, contacts -> [ChatListSearchEntry] in + + let foundQueryPeers: Promise = Promise() + + let callback:(PeerId, Bool, MessageId?, ChatInitialAction?)->Void = { peerId, _, _, _ in } + + let link = inApp(for: query as NSString, context: context, peerId: nil, openInfo: callback, hashtag: nil, command: nil, applyProxy: nil, confirm: false) + + switch link { + case let .followResolvedName(_, username, _, context, _, _): + foundQueryPeers.set(resolveUsername(username: username, context: context)) + default: + foundQueryPeers.set(.single(nil)) + } + + var all = query.transformKeyboard + all.insert(query.lowercased(), at: 0) + all = all.uniqueElements + let localPeers:Signal<([RenderedPeer], [PeerId: UnreadSearchBadge]), NoError> = combineLatest(all.map { + return context.account.postbox.searchPeers(query: $0) + }) |> map { result in + return Array(result.joined()) + } |> mapToSignal { peers in + return combineLatest(peers.map { context.account.viewTracker.peerView($0.peerId) |> take(1) }) |> map { ($0, peers) } + } |> mapToSignal { peerViews, peers in + return context.account.postbox.unreadMessageCountsView(items: peers.map {.peer($0.peerId)}) |> take(1) |> map { values in + var unread:[PeerId: UnreadSearchBadge] = [:] + for peerView in peerViews { + let isMuted = peerView.isMuted + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount) + } + } + return (peers, unread) + } + } + + + let foundLocalPeers: Signal<[ChatListSearchEntry], NoError> = query.hasPrefix("#") || !options.contains(.chats) ? .single([]) : combineLatest(localPeers, context.account.postbox.loadedPeerWithId(context.peerId), foundQueryPeers.get()) + |> map { peers, accountPeer, inLinkPeer -> [ChatListSearchEntry] in var entries: [ChatListSearchEntry] = [] + + + if L10n.peerSavedMessages.lowercased().hasPrefix(query.lowercased()) || NSLocalizedString("Peer.SavedMessages", comment: "nil").lowercased().hasPrefix(query.lowercased()) { + entries.append(.savedMessages(accountPeer)) + ids[accountPeer.id] = accountPeer.id + } + var index = 1 - for rendered in peers { + + if let peer = inLinkPeer { + if ids[peer.id] == nil { + ids[peer.id] = peer.id + entries.append(.localPeer(peer, index, nil, .none, true)) + index += 1 + } + } + + for rendered in peers.0 { if ids[rendered.peerId] == nil { ids[rendered.peerId] = rendered.peerId if let peer = rendered.chatMainPeer { @@ -305,7 +542,7 @@ class SearchController: GenericViewController,TableViewDelegate { if rendered.peers[rendered.peerId] is TelegramSecretChat { wrapper = SearchSecretChatWrapper(peerId: rendered.peerId) } - entries.append(.localPeer(peer, index, wrapper, true)) + entries.append(.localPeer(peer, index, wrapper, peers.1[rendered.peerId] ?? .none, true)) index += 1 } @@ -315,109 +552,224 @@ class SearchController: GenericViewController,TableViewDelegate { return entries } - let foundRemotePeers: Signal<([ChatListSearchEntry], Bool), NoError> = .single(([], true)) |> then(searchPeers(account: account, query: query) - |> delay(0.2, queue: prepareQueue) - |> map { peers -> [Peer] in - return peers.filter { (peer) -> Bool in - let first = ids[peer.id] == nil - ids[peer.id] = peer.id - return first - } + let foundRemotePeers: Signal<([ChatListSearchEntry], [ChatListSearchEntry], Bool), NoError> + + let location: SearchMessagesLocation + if groupId != .root { + location = .group(groupId) + foundRemotePeers = .single(([], [], false)) + } else if query.hasPrefix("#") || !options.contains(.chats) { + location = .general + foundRemotePeers = .single(([], [], false)) + } else { + location = .general + foundRemotePeers = .single(([], [], true)) |> then(searchPeers(account: context.account, query: query) + |> delay(0.2, queue: prepareQueue) + |> map { founds -> ([FoundPeer], [FoundPeer]) in + + return (founds.0.filter { found -> Bool in + let first = ids[found.peer.id] == nil + ids[found.peer.id] = found.peer.id + return first + }, founds.1.filter { found -> Bool in + let first = ids[found.peer.id] == nil + ids[found.peer.id] = found.peer.id + return first + }) + + } + |> mapToSignal { peers -> Signal<([FoundPeer], [FoundPeer], [PeerId : UnreadSearchBadge]), NoError> in + let all = peers.0 + peers.1 + return combineLatest(all.map { context.account.viewTracker.peerView($0.peer.id) |> take(1) }) |> mapToSignal { peerViews in + return context.account.postbox.unreadMessageCountsView(items: all.map {.peer($0.peer.id)}) |> take(1) |> map { values in + var unread:[PeerId: UnreadSearchBadge] = [:] + for peerView in peerViews { + let isMuted = peerView.isMuted + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount) + } + } + return (peers.0, peers.1, unread) + } + } + } + |> map { _local, _remote, unread -> ([ChatListSearchEntry], [ChatListSearchEntry], Bool) in + var local: [ChatListSearchEntry] = [] + var index = 1000 + for peer in _local { + local.append(.localPeer(peer.peer, index, nil, unread[peer.peer.id] ?? .none, true)) + index += 1 + } + + var remote: [ChatListSearchEntry] = [] + index = 10001 + for peer in _remote { + remote.append(.globalPeer(peer, unread[peer.peer.id] ?? .none, index)) + index += 1 + } + return (local, remote, false) + }) + } + + searchMessagesState.set(nil) + + + let remoteSearch = searchMessagesState.get() |> mapToSignal { state in + return searchMessages(account: context.account, location: location , query: query, state: state) + |> delay(0.2, queue: prepareQueue) + |> map { result -> ([ChatListSearchEntry], Bool, SearchMessagesState?) in + + var entries: [ChatListSearchEntry] = [] + var index = 20001 + for message in result.0.messages { + entries.append(.message(message, query, result.0.readStates[message.id.peerId], index)) + index += 1 + } + + return (entries, false, result.1) } - |> map { peers -> ([ChatListSearchEntry], Bool) in - var entries: [ChatListSearchEntry] = [] - var index = 10001 - for peer in peers { - entries.append(.globalPeer(peer, index)) - index += 1 - } - return (entries, false) - }) + } - let foundRemoteMessages: Signal<([ChatListSearchEntry], Bool), NoError> = .single(([], true)) |> then(searchMessages(account: account, peerId:nil , query: query) - |> delay(0.2, queue: prepareQueue) - |> map { messages -> ([ChatListSearchEntry], Bool) in - - - var entries: [ChatListSearchEntry] = [] - var index = 20001 - for message in messages { - entries.append(.message(message, index)) - index += 1 - } - - return (entries, false) - }) - return combineLatest(foundLocalPeers, foundRemotePeers, foundRemoteMessages) - |> map { localPeers, remotePeers, remoteMessages -> ([ChatListSearchEntry], Bool) in + let foundRemoteMessages: Signal<([ChatListSearchEntry], Bool, SearchMessagesState?), NoError> = !options.contains(.messages) ? .single(([], false, nil)) : .single(([], true, nil)) |> then(remoteSearch) + + return combineLatest(queue: prepareQueue, foundLocalPeers, foundRemotePeers, foundRemoteMessages, isRevealed) + |> map { localPeers, remotePeers, remoteMessages, isRevealed -> ([ChatListSearchEntry], Bool, SearchMessagesState?) in var entries:[ChatListSearchEntry] = [] - if !localPeers.isEmpty { - entries.append(.separator(text: tr(.searchSeparatorChatsAndContacts), index: 0, state: .none)) + if !localPeers.isEmpty || !remotePeers.0.isEmpty { + + let peers = (localPeers + remotePeers.0) + + + - entries += localPeers + entries.append(.separator(text: L10n.searchSeparatorChatsAndContacts, index: 0, state: .none)) + if !remoteMessages.0.isEmpty { + entries += peers + } else { + entries += peers + } } - if !remotePeers.0.isEmpty { - entries.append(.separator(text: tr(.searchSeparatorGlobalPeers), index: 10000, state: .none)) - entries += remotePeers.0 + if !remotePeers.1.isEmpty { + + let state: SeparatorBlockState + if remotePeers.1.count > 5 { + if isRevealed { + state = .all + } else { + state = .short + } + } else { + state = .none + } + + entries.append(.separator(text: L10n.searchSeparatorGlobalPeers, index: 10000, state: state)) + + if !isRevealed { + entries += remotePeers.1.prefix(5) + } else { + entries += remotePeers.1 + } } if !remoteMessages.0.isEmpty { - entries.append(.separator(text: tr(.searchSeparatorMessages), index: 20000, state: .none)) + entries.append(.separator(text: L10n.searchSeparatorMessages, index: 20000, state: .none)) entries += remoteMessages.0 } - if entries.isEmpty && !remotePeers.1 && !remoteMessages.1 { + if entries.isEmpty && !remotePeers.2 && !remoteMessages.1 { entries.append(.emptySearch) } - return (entries, remotePeers.1 || remoteMessages.1) - } + return (entries, remotePeers.2 || remoteMessages.1, remoteMessages.2) + } |> map { value in + return (value.0, value.1, false, value.2) + } } else { + + let recently = recentlySearchedPeers(postbox: context.account.postbox) |> mapToSignal { recently -> Signal<[PeerView], NoError> in + return combineLatest(recently.map {context.account.viewTracker.peerView($0.peer.peerId)}) + } |> map { peerViews -> [PeerView] in + return peerViews.filter { peerView in + if let group = peerViewMainPeer(peerView) as? TelegramGroup, group.migrationReference != nil { + return false + } + return true + } + } |> mapToSignal { peerViews -> Signal<([PeerView], [PeerId: UnreadSearchBadge]), NoError> in + return context.account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in + + var unread:[PeerId: UnreadSearchBadge] = [:] + for peerView in peerViews { + let isMuted = peerView.isMuted + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount) + } + } + return (peerViews, unread) + } + + } |> deliverOnPrepareQueue + + let top: Signal<([Peer], [PeerId : UnreadSearchBadge], [PeerId : Bool]), NoError> = recentPeers(account: context.account) |> mapToSignal { recent in + switch recent { + case .disabled: + return .single(([], [:], [:])) + case let .peers(peers): + return combineLatest(peers.map {context.account.viewTracker.peerView($0.id)}) |> mapToSignal { peerViews -> Signal<([Peer], [PeerId: UnreadSearchBadge], [PeerId : Bool]), NoError> in + return context.account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in + + var peers:[Peer] = [] + var unread:[PeerId: UnreadSearchBadge] = [:] + var online: [PeerId : Bool] = [:] + for peerView in peerViews { + if let peer = peerViewMainPeer(peerView) { + var isActive:Bool = false + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + (_, isActive, _) = stringAndActivityForUserPresence(presence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp)) + } + let isMuted = peerView.isMuted + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount) + } + + online[peer.id] = isActive + peers.append(peer) + } + } + return (peers, unread, online) + } + } + } + } |> deliverOnPrepareQueue - return combineLatest(recentPeers(account: account), recentlySearchedPeers(postbox: account.postbox), statePromise) |> map { (top, recent, state) -> ([ChatListSearchEntry], Bool) in + return combineLatest(queue: prepareQueue, context.account.postbox.loadedPeerWithId(context.peerId), top, recently, statePromise) |> map { user, top, recent, state -> ([ChatListSearchEntry], Bool) in var entries:[ChatListSearchEntry] = [] var i:Int = 0 var ids:[PeerId:PeerId] = [:] - var topIds:[PeerId:PeerId] = [:] - for t in top { - topIds[t.id] = t.id - } - var recent = recent.filter({topIds[$0.peerId] == nil}) - - if recent.count > 0 && top.count > 5 { - entries.append(.separator(text: tr(.searchSeparatorPopular), index: i, state: state.0)) - } + ids[context.peerId] = context.peerId - for peer in top { - if ids[peer.id] == nil { - ids[peer.id] = peer.id - var stop:Bool = false - recent = recent.filter({ids[$0.peerId] == nil}) - if case .short = state.0, (i == 4 && recent.count > 0) { - stop = true - } - entries.append(.localPeer(peer, i, nil, !stop)) - i += 1 - if stop { - break - } - } - - } - if recent.count > 0 { - entries.append(.separator(text: tr(.searchSeparatorRecent), index: i, state: .clear)) + entries.append(ChatListSearchEntry.topPeers(i, articlesEnabled: false, unreadArticles: 0, selfPeer: user, peers: top.0, unread: top.1, online: top.2)) + + if recent.0.count > 0 { + entries.append(.separator(text: L10n.searchSeparatorRecent, index: i, state: .clear)) i += 1 - for rendered in recent { - if ids[rendered.peerId] == nil { - ids[rendered.peerId] = rendered.peerId - if let peer = rendered.chatMainPeer { + for peerView in recent.0 { + if ids[peerView.peerId] == nil { + ids[peerView.peerId] = peerView.peerId + if let peer = peerViewMainPeer(peerView) { var wrapper:SearchSecretChatWrapper? = nil - if rendered.peers[rendered.peerId] is TelegramSecretChat { - wrapper = SearchSecretChatWrapper(peerId: rendered.peerId) + if peerView.peers[peerView.peerId] is TelegramSecretChat { + wrapper = SearchSecretChatWrapper(peerId: peerView.peerId) } - entries.append(.recentlySearch(peer, i, wrapper, true)) + let result = stringStatus(for: peerView, context: context, theme: PeerStatusStringTheme(titleFont: .medium(.title))) + + entries.append(.recentlySearch(peer, i, wrapper, result, recent.1[peerView.peerId] ?? .none, true)) i += 1 } @@ -429,35 +781,64 @@ class SearchController: GenericViewController,TableViewDelegate { entries.append(.emptySearch) } - return (entries, false) + return (entries.sorted(by: <), false) + } |> map {value in + return (value.0, value.1, true, nil) } } } - isLoading.set(searchItems |> mapToSignal { values -> Signal in - return .single(values.1) - }) - let transition = combineLatest(searchItems, appearanceSignal) |> map { value, appearance in - return value.0.map {AppearanceWrapperEntry(entry: $0, appearance: appearance)} + let transition = combineLatest(queue: prepareQueue, searchItems, appearanceSignal, context.globalPeerHandler.get() |> distinctUntilChanged, pinnedPromise.get()) |> map { value, appearance, location, pinnedItems in + return (value.0.map {AppearanceWrapperEntry(entry: $0, appearance: appearance)}, value.1, value.2 ? nil : location, value.2, pinnedItems, value.3) } - |> map { entries -> TableEntriesTransition<[AppearanceWrapperEntry]> in - return prepareEntries(from: previousSearchItems.swap(entries) , to: entries, arguments: arguments, initialSize:atomicSize.modify { $0 }) + |> map { entries, loading, location, animated, pinnedItems, searchMessagesState -> (TableUpdateTransition, Bool, ChatLocation?, SearchMessagesState?) in + let transition = prepareEntries(from: previousSearchItems.swap(entries) , to: entries, arguments: arguments, pinnedItems: pinnedItems, initialSize: atomicSize.modify { $0 }, animated: animated) + return (transition, loading, location, searchMessagesState) } |> deliverOnMainQueue - disposable.set(transition.start(next: { [weak self] transition in - self?.genericView.merge(with: transition) + + disposable.set(transition.start(next: { [weak self] (transition, loading, location, searchMessagesState) in + guard let `self` = self else {return} + self.genericView.merge(with: transition) + self.isLoading.set(.single(loading)) + if self.scrollupOnNextTransition { + self.scrollup() + } + self.scrollupOnNextTransition = false + _ = searchMessagesStateValue.swap(searchMessagesState) + + if let location = location { + if !(self.genericView.selectedItem() is ChatListMessageRowItem) { + switch location { + case let .peer(peerId): + let item = self.genericView.item(stableId: ChatListSearchEntryStableId.globalPeerId(peerId)) ?? self.genericView.item(stableId: ChatListSearchEntryStableId.localPeerId(peerId)) + if let item = item { + _ = self.genericView.select(item: item, notify: false, byClick: false) + } + } + } + } else { + self.genericView.cancelSelection() + } })) + genericView.setScrollHandler { position in + switch position.direction { + case .bottom: + searchMessagesState.set(searchMessagesStateValue.swap(nil)) + default: + break + } + } + ready.set(.single(true)) } override func initializer() -> TableView { - let vz = TableView.self - //controller.bar.height - return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), drawBorder: true); + return TableView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), drawBorder: true); } override func viewWillDisappear(_ animated: Bool) { @@ -465,64 +846,159 @@ class SearchController: GenericViewController,TableViewDelegate { isLoading.set(.single(false)) self.window?.remove(object: self, for: .UpArrow) self.window?.remove(object: self, for: .DownArrow) + openPeerDisposable.set(nil) + globalDisposable.set(nil) + disposable.set(nil) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in - if let item = self?.genericView.selectedItem(), item.index > 0 { - self?.genericView.selectPrev() - if self?.genericView.selectedItem() is SeparatorRowItem { - self?.genericView.selectPrev() + guard let `self` = self else {return .rejected} + + if self.window?.firstResponder?.className != "TGUIKit.SearchTextField" { + return .rejected + } + + if let highlighted = self.genericView.highlightedItem() { + _ = self.genericView.select(item: highlighted) + self.closeNext = true + return .invoked + } else if !self.marked { + self.genericView.cancelSelection() + self.genericView.selectNext() + self.closeNext = true + return .invoked + } + + return .rejected + }, with: self, for: .Return, priority: .modal) + + + setHighlightEvents() + + } + + func updateHighlightEvents(_ hasChat: Bool) { + if !hasChat { + setHighlightEvents() + } else { + removeHighlightEvents() + } + } + + private func setHighlightEvents() { + + removeHighlightEvents() + + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.window?.firstResponder?.className != "TGUIKit.SearchTextField" { + return .rejected + } + if let item = self?.genericView.highlightedItem(), item.index > 0 { + self?.genericView.highlightPrev(turnDirection: false) + while self?.genericView.highlightedItem() is PopularPeersRowItem || self?.genericView.highlightedItem() is SeparatorRowItem { + self?.genericView.highlightNext(turnDirection: false) } } return .invoked - }, with: self, for: .UpArrow, priority: .modal, modifierFlags: [.option]) + }, with: self, for: .UpArrow, priority: .modal) + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in - self?.genericView.selectNext() - if self?.genericView.selectedItem() is SeparatorRowItem { - self?.genericView.selectNext() + if self?.window?.firstResponder?.className != "TGUIKit.SearchTextField" { + return .rejected } + self?.genericView.highlightNext(turnDirection: false) + + while self?.genericView.highlightedItem() is PopularPeersRowItem || self?.genericView.highlightedItem() is SeparatorRowItem { + self?.genericView.highlightNext(turnDirection: false) + } + return .invoked - }, with: self, for: .DownArrow, priority: .modal, modifierFlags: [.option]) + }, with: self, for: .DownArrow, priority: .modal) + + + self.window?.set(handler: { () -> KeyHandlerResult in + return .rejected + }, with: self, for: .UpArrow, priority: .modal, modifierFlags: [.command]) + + self.window?.set(handler: { () -> KeyHandlerResult in + return .rejected + }, with: self, for: .DownArrow, priority: .modal, modifierFlags: [.command]) + + + } + + private func removeHighlightEvents() { + genericView.cancelHighlight() + self.window?.remove(object: self, for: .DownArrow) + self.window?.remove(object: self, for: .UpArrow) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - genericView.startMerge() - request(with: nil) + request(with: self.defaultQuery) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - self.genericView.removeAll() - genericView.stopMerge() } - + private let globalDisposable = MetaDisposable() + private let options: AppSearchOptions deinit { openPeerDisposable.dispose() + globalDisposable.dispose() + disposable.dispose() } - init(account: Account, open:@escaping(PeerId,Message?, Bool) ->Void , frame:NSRect = NSZeroRect) { - self.account = account + init(context: AccountContext, open:@escaping(PeerId?, MessageId?, Bool) ->Void, options: AppSearchOptions = [.chats, .messages], frame:NSRect = NSZeroRect, groupId: PeerGroupId = .root) { + self.context = context self.open = open - self.arguments = SearchControllerArguments(account: account, removeRecentPeerId: { peerId in - _ = removeRecentlySearchedPeer(postbox: account.postbox, peerId: peerId).start() + self.options = options + self.groupId = groupId + self.arguments = SearchControllerArguments(context: context, removeRecentPeerId: { peerId in + _ = removeRecentlySearchedPeer(postbox: context.account.postbox, peerId: peerId).start() }, clearRecent: { - _ = (recentlySearchedPeers(postbox: account.postbox) |> take(1) |> mapToSignal { - return combineLatest($0.map {removeRecentlySearchedPeer(postbox: account.postbox, peerId: $0.peerId)}) - }).start() + confirm(for: context.window, information: L10n.searchConfirmClearHistory, successHandler: { _ in + _ = (recentlySearchedPeers(postbox: context.account.postbox) |> take(1) |> mapToSignal { + return combineLatest($0.map {removeRecentlySearchedPeer(postbox: context.account.postbox, peerId: $0.peer.peerId)}) + }).start() + }) + + }, openTopPeer: { type in + switch type { + case let .peer(peer, _, _): + open(peer.id, nil, false) + _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() + case let .savedMessages(peer): + open(peer.id, nil, false) + case .articles: + break + } }) super.init(frame:frame) self.bar = .init(height: 0) + + globalDisposable.set(context.globalPeerHandler.get().start(next: { [weak self] peerId in + if peerId == nil { + self?.genericView.cancelSelection() + } + })) } + private var scrollupOnNextTransition: Bool = false + func request(with query:String?) -> Void { + setHighlightEvents() + self.query = query + self.scrollupOnNextTransition = true if let query = query, !query.isEmpty { searchQuery.set(.single(query)) } else { @@ -530,66 +1006,155 @@ class SearchController: GenericViewController,TableViewDelegate { } } + override func scrollup(force: Bool = false) { + genericView.clipView.scroll(to: NSMakePoint(0, 50), animated: false) + } + private var closeNext: Bool = false func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void { - var peer:Peer! - var peerId:PeerId! - var message:Message? + var peer:Peer? + var peerId:PeerId? + var messageId:MessageId? + + let context = self.context + if let item = item as? ChatListMessageRowItem { peer = item.peer - message = item.message - peerId = item.message!.id.peerId + messageId = item.message?.id + peerId = item.peer?.id } else if let item = item as? ShortPeerRowItem { if let stableId = item.stableId.base as? ChatListSearchEntryStableId { switch stableId { case let .localPeerId(pId), let .recentSearchPeerId(pId), let .secretChat(pId), let .globalPeerId(pId): peerId = pId + case .savedMessages: + peerId = context.peerId default: break } } peer = item.peer } else if let item = item as? SeparatorRowItem { - switch item.state { - case .short: - statePromise.set(.single((.all, .short))) - case .all: - statePromise.set(.single((.short, .short))) - case .clear: - arguments.clearRecent() - default: - break + if item.stableId == AnyHashable(ChatListSearchEntryStableId.separator(10000)) { + switch item.state { + case .short: + self.isRevealed.set(true) + case .all: + self.isRevealed.set(false) + default: + break + } + } else { + switch item.state { + case .short: + statePromise.set(.single((.all, .short))) + case .all: + statePromise.set(.single((.short, .short))) + case .clear: + arguments.clearRecent() + default: + break + } } + return + } else if item is PopularPeersRowItem { + peerId = context.peerId } - - let storedPeer = account.postbox.modify { modifier -> Void in - if modifier.getPeer(peer.id) == nil { - updatePeers(modifier: modifier, peers: [peer], update: { (previous, updated) -> Peer? in - return updated - }) + var storedPeer: Signal + if let peer = peer { + storedPeer = context.account.postbox.transaction { transaction -> Void in + if transaction.getPeer(peer.id) == nil { + updatePeers(transaction: transaction, peers: [peer], update: { (previous, updated) -> Peer? in + return updated + }) + } + + } |> mapToSignal { + return storedMessageFromSearchPeer(account: context.account, peer: peer) } - + } else if let peerId = peerId { + storedPeer = .single(peerId) + } else { + storedPeer = .complete() } + if let query = query, let peerId = peerId { + let link = inApp(for: query as NSString, context: context, peerId: peerId, openInfo: { _, _, _, _ in }, hashtag: nil, command: nil, applyProxy: nil, confirm: false) + switch link { + case let .followResolvedName(_, _, postId, _, _, _): + if let postId = postId { + messageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: postId) + } + default: + break + } + } + - let recently = (searchQuery.get() |> take(1)) |> mapToSignal { [weak self] query -> Signal in - if let _ = query, let account = self?.account, !(item is ChatListMessageRowItem) { - return addRecentlySearchedPeer(postbox: account.postbox, peerId: peerId) + let recently: Signal + if let peerId = peerId { + recently = (searchQuery.get() |> take(1)) |> mapToSignal { [weak self] query -> Signal in + if let context = self?.context, !(item is ChatListMessageRowItem) { + return addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peerId) + } + return .single(Void()) } - return .complete() + } else { + recently = .single(Void()) } - openPeerDisposable.set((combineLatest(storedPeer, recently) |> deliverOnMainQueue).start( completed: { [weak self] in - self?.open(peerId, message, !(item is ChatListMessageRowItem) && byClick) - })) + _ = combineLatest(storedPeer, recently).start() + + removeHighlightEvents() + + marked = true + + if let peerId = peerId { + self.open(peerId, messageId, self.closeNext || messageId == nil) + } } - func selectionWillChange(row: Int, item: TableRowItem) -> Bool { + func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { + + + + var peer: Peer? = nil + if let item = item as? ChatListMessageRowItem { + peer = item.peer + } else if let item = item as? ShortPeerRowItem { + peer = item.peer + } else if let item = item as? SeparatorRowItem { + switch item.state { + case .none: + return false + default: + return true + } + } + + if let peer = peer, let modalAction = navigationController?.modalAction { + + if !modalAction.isInvokable(for: peer) { + modalAction.alertError(for: peer, with:window!) + return false + } + modalAction.afterInvoke() + + if let modalAction = modalAction as? FWDNavigationAction { + if peer.id == context.peerId { + _ = Sender.forwardMessages(messageIds: modalAction.messages.map{$0.id}, context: context, peerId: context.peerId).start() + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + navigationController?.removeModalAction() + return false + } + } + + } return !(item is SearchEmptyRowItem) } diff --git a/Telegram-Mac/SearchEmptyRowItem.swift b/Telegram-Mac/SearchEmptyRowItem.swift index 4155a90c83..012160c6c6 100644 --- a/Telegram-Mac/SearchEmptyRowItem.swift +++ b/Telegram-Mac/SearchEmptyRowItem.swift @@ -9,20 +9,14 @@ import Cocoa import TGUIKit -class SearchEmptyRowItem: TableRowItem { +class SearchEmptyRowItem: GeneralRowItem { - private let _stableId:AnyHashable let isLoading:Bool let icon:CGImage - let border:BorderType let text:TextViewLayout? - override var stableId: AnyHashable { - return _stableId - } + - init(_ initialSize: NSSize, stableId:AnyHashable, isLoading:Bool = false, icon:CGImage = theme.icons.emptySearch, text:String? = nil, border:BorderType = []) { - _stableId = stableId - self.border = border + init(_ initialSize: NSSize, stableId:AnyHashable, isLoading:Bool = false, icon:CGImage = theme.icons.emptySearch, text:String? = nil, border:BorderType = [], viewType: GeneralViewType = .legacy) { self.isLoading = isLoading self.icon = icon if let text = text { @@ -31,12 +25,13 @@ class SearchEmptyRowItem: TableRowItem { } else { self.text = nil } - super.init(initialSize) + super.init(initialSize, stableId: stableId, viewType: viewType, border: border) } override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) text?.measure(width: width - 60) - return super.makeSize(width, oldWidth: oldWidth) + return success } override var height: CGFloat { @@ -50,7 +45,7 @@ class SearchEmptyRowItem: TableRowItem { } return true }) - return table.frame.height - basic - 50 + return table.frame.height - basic } else { return initialSize.height } @@ -76,6 +71,13 @@ class SearchEmptyRowView : TableRowView { } + override var backdorColor: NSColor { + if let item = item as? SearchEmptyRowItem { + return item.viewType.rowBackground + } else { + return super.backdorColor + } + } override func layout() { super.layout() diff --git a/Telegram-Mac/SearchPeerMembers.swift b/Telegram-Mac/SearchPeerMembers.swift new file mode 100644 index 0000000000..7954570ad8 --- /dev/null +++ b/Telegram-Mac/SearchPeerMembers.swift @@ -0,0 +1,78 @@ +// +// SearchPeerMembers.swift +// Telegram +// +// Created by Mikhail Filimonov on 02/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +func searchPeerMembers(context: AccountContext, peerId: PeerId, query: String) -> Signal<[Peer], NoError> { + if peerId.namespace == Namespaces.Peer.CloudChannel { + return context.account.postbox.transaction { transaction -> CachedChannelData? in + return transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData + } + |> mapToSignal { cachedData -> Signal<[Peer], NoError> in + if let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 { + return Signal { subscriber in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in + if case .ready = state.loadingState { + let normalizedQuery = query.lowercased() + subscriber.putNext(state.list.compactMap { participant -> Peer? in + if participant.peer.displayTitle.isEmpty { + return nil + } + if normalizedQuery.isEmpty { + return participant.peer + } + if normalizedQuery.isEmpty { + return participant.peer + } else { + if participant.peer.indexName.matchesByTokens(normalizedQuery) { + return participant.peer + } + if let addressName = participant.peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) { + return participant.peer + } + + return nil + } + }) + } + }) + + return ActionDisposable { + disposable.dispose() + } + } + |> runOn(Queue.mainQueue()) + } + + return Signal { subscriber in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.peerId, peerId: peerId, searchQuery: query.isEmpty ? nil : query, updated: { state in + if case .ready = state.loadingState { + subscriber.putNext(state.list.compactMap { participant in + if participant.peer.displayTitle.isEmpty { + return nil + } + return participant.peer + }) + } + }) + + return ActionDisposable { + disposable.dispose() + } + } |> runOn(Queue.mainQueue()) + } + } else { + return searchGroupMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, query: query) + } +} diff --git a/Telegram-Mac/SearchResultModalController.swift b/Telegram-Mac/SearchResultModalController.swift index d2356ee0f4..da0e77ed4f 100644 --- a/Telegram-Mac/SearchResultModalController.swift +++ b/Telegram-Mac/SearchResultModalController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit fileprivate enum SearchResultEntry : Comparable, Identifiable { case message(Message) @@ -52,15 +53,15 @@ fileprivate class SearchResultModalView : View { required init(frame frameRect: NSRect) { super.init(frame: frameRect) - separator.backgroundColor = .border - + separator.backgroundColor = theme.colors.border + textView.backgroundColor = theme.colors.background addSubview(table) addSubview(textView) addSubview(separator) } func updateTitle(_ string:String) { - textView.set(layout: TextViewLayout(.initialize(string: string, color: .text, font: .medium(.title)), maximumNumberOfLines: 1, truncationType:.middle)) + textView.set(layout: TextViewLayout(.initialize(string: string, color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1, truncationType:.middle)) self.needsLayout = true } @@ -68,7 +69,7 @@ fileprivate class SearchResultModalView : View { super.layout() textView.layout?.measure(width: frame.width - 40) textView.update(textView.layout) - textView.centerX(y:floorToScreenPixels((50 - textView.frame.height)/2.0)) + textView.centerX(y:floorToScreenPixels(backingScaleFactor, (50 - textView.frame.height)/2.0)) separator.frame = NSMakeRect(0, 50 - .borderSize, frame.width, .borderSize) table.frame = NSMakeRect(0, 50, frame.width, frame.height - 50) @@ -78,32 +79,37 @@ fileprivate class SearchResultModalView : View { } } -fileprivate func prepareEntries(from:[SearchResultEntry], to:[SearchResultEntry], initialSize:NSSize, account:Account) -> TableUpdateTransition { +fileprivate func prepareEntries(from:[SearchResultEntry], to:[SearchResultEntry], initialSize:NSSize, context: AccountContext) -> TableUpdateTransition { let (removed,inserted,updated) = proccessEntriesWithoutReverse(from, right: to) { entry -> TableRowItem in switch entry { case let .message(message): - return ChatListMessageRowItem(initialSize, account: account, message: message, renderedPeer: RenderedPeer(message: message)) + return ChatListMessageRowItem(initialSize, context: context, message: message, query: "", renderedPeer: RenderedPeer(message: message), readState: nil) } } return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated) } class SearchResultModalController: ModalViewController, TableViewDelegate { - private let account:Account + + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + + private let context:AccountContext private let entries:Atomic<[SearchResultEntry]> = Atomic(value:[]) private let promise:Promise<[Message]> = Promise() private let query:String private let chatInteraction:ChatInteraction - init(_ account:Account, messages:[Message] = [], query:String, chatInteraction:ChatInteraction) { - self.account = account + init(_ context: AccountContext, messages:[Message] = [], query:String, chatInteraction:ChatInteraction) { + self.context = context self.query = query self.chatInteraction = chatInteraction promise.set(.single(messages)) super.init(frame: NSMakeRect(0, 0, 300, 360)) } - init(_ account:Account, request:Signal<[Message],Void>, query:String, chatInteraction:ChatInteraction) { - self.account = account + init(_ context: AccountContext, request:Signal<[Message], NoError>, query:String, chatInteraction:ChatInteraction) { + self.context = context self.query = query promise.set(request) self.chatInteraction = chatInteraction @@ -125,7 +131,7 @@ class SearchResultModalController: ModalViewController, TableViewDelegate { genericView.updateTitle(query) let entries = self.entries let initialSize = self.atomicSize - let account = self.account + let context = self.context genericView.table.delegate = self genericView.table.merge(with: promise.get() @@ -133,17 +139,17 @@ class SearchResultModalController: ModalViewController, TableViewDelegate { return messages.map({.message($0)}) } |> map { [weak self] new -> TableUpdateTransition in self?.readyOnce() - return prepareEntries(from: entries.swap(new), to: entries.modify({$0}), initialSize: initialSize.modify({$0}), account: account) + return prepareEntries(from: entries.swap(new), to: entries.modify({$0}), initialSize: initialSize.modify({$0}), context: context) }) } func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void { if let item = item as? ChatListMessageRowItem, let message = item.message { - chatInteraction.focusMessageId(nil, message.id, .center(id: 0, animated: true, focus: true, inset: 0)) + chatInteraction.focusMessageId(nil, message.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) } close() } - func selectionWillChange(row:Int, item:TableRowItem) -> Bool { + func selectionWillChange(row:Int, item:TableRowItem, byClick: Bool) -> Bool { return true } func isSelectable(row:Int, item:TableRowItem) -> Bool { diff --git a/Telegram-Mac/SearchRowItem.swift b/Telegram-Mac/SearchRowItem.swift index 51476b99c6..06cb57ab0c 100644 --- a/Telegram-Mac/SearchRowItem.swift +++ b/Telegram-Mac/SearchRowItem.swift @@ -21,12 +21,13 @@ class SearchRowItem: GeneralRowItem { } - init(_ initialSize: NSSize, stableId: AnyHashable, searchInteractions:SearchInteractions, isLoading:Bool = false, drawCustomSeparator: Bool = true, border: BorderType = [], inset: NSEdgeInsets = NSEdgeInsets(left:30,right:30, top: 10, bottom: 10)) { + init(_ initialSize: NSSize, stableId: AnyHashable, searchInteractions:SearchInteractions, isLoading:Bool = false, drawCustomSeparator: Bool = true, border: BorderType = [], inset: NSEdgeInsets = NSEdgeInsets(left:30,right:30, top: 10, bottom: 10), viewType: GeneralViewType = .legacy) { self.searchInteractions = searchInteractions self.isLoading = isLoading - super.init(initialSize, height: 0, stableId: stableId, type: .none, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) + super.init(initialSize, height: 0, stableId: stableId, type: .none, viewType: viewType, drawCustomSeparator: drawCustomSeparator, border: border, inset: inset) } + } @@ -40,15 +41,7 @@ class SearchRowView : TableRowView { super.init(frame: frameRect) addSubview(searchView) - searchView.searchInteractions = SearchInteractions ({ [weak self] state in - if let item = self?.item as? SearchRowItem { - item.searchInteractions.stateModified(state) - } - }, { [weak self] text in - if let item = self?.item as? SearchRowItem { - item.searchInteractions.textModified(text) - } - }) + } @@ -65,7 +58,18 @@ class SearchRowView : TableRowView { super.set(item: item) if let item = item as? SearchRowItem { self.searchView.isLoading = item.isLoading - self.searchView.updateLocalizationAndTheme() + self.searchView.updateLocalizationAndTheme(theme: theme) + + + searchView.searchInteractions = SearchInteractions ({ [weak self] state, animated in + if let item = self?.item as? SearchRowItem { + item.searchInteractions.stateModified(state, animated) + } + }, { [weak self] text in + if let item = self?.item as? SearchRowItem { + item.searchInteractions.textModified(text) + } + }) } } @@ -77,6 +81,24 @@ class SearchRowView : TableRowView { return searchView.input } + override func onRemove(_ animation: NSTableView.AnimationOptions) { + self.isHidden = true +// searchView.cancel(false) +// searchView.isHidden = true + } + + override func onInsert(_ animation: NSTableView.AnimationOptions) { + + if animation.contains(.effectFade) { + self.isHidden = true + self.searchView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, timingFunction: .easeOut, completion: { [weak self] _ in + self?.isHidden = false + }) + } + // searchView.cancel(false) + // searchView.isHidden = true + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Telegram-Mac/SearchSettingsController.swift b/Telegram-Mac/SearchSettingsController.swift new file mode 100644 index 0000000000..d8ead3d86a --- /dev/null +++ b/Telegram-Mac/SearchSettingsController.swift @@ -0,0 +1,179 @@ +// +// SearchSettingsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + +private func searchSettingsEntries(context: AccountContext, items:[SettingsSearchableItem], recent: Bool) -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + let sectionId: Int32 = 0 + var index: Int32 = 0 + + var previousIcon: SettingsSearchableItemIcon? + + if recent, !items.isEmpty { + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("separator"), equatable: InputDataEquatable(true), item: { initialSize, stableId in + return SeparatorRowItem(initialSize, stableId, string: L10n.settingsSearchRecent, right: L10n.settingsSearchRecentClear, state: .clear, height: 20, action: { + clearRecentSettingsSearchItems(postbox: context.account.postbox) + }) + })) + index += 1 + } + + for item in items { + var image: CGImage? = nil + var leftInset: CGFloat = 21 + if previousIcon != item.icon { + image = item.icon.thumb + } else { + leftInset += 33 + } + previousIcon = item.icon + + let desc = item.breadcrumbs.joined(separator: " ") + + entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("search_\(item.id.index)"), equatable: InputDataEquatable(item.id), item: { initialSize, stableId in + + let icon:GeneralThumbAdditional? + if let image = image { + icon = GeneralThumbAdditional(thumb: image, textInset: 33, thumbInset: 0) + } else { + icon = nil + } + + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: item.title, description: desc.isEmpty ? nil : desc, type: .context(" "), action: { + + addRecentSettingsSearchItem(postbox: context.account.postbox, item: item.id) + + item.present(context, context.sharedContext.bindings.rootNavigation(), { presentation, controller in + switch presentation { + case .push: + if let controller = controller { + context.sharedContext.bindings.rootNavigation().push(controller) + } + default: + break + } + }) + }, thumb: icon, border:[BorderType.Right], inset:NSEdgeInsets(left: leftInset)) + })) + index += 1 + +// entries.append(InputDataEntry.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("search_\(item.id.index)"), data: InputDataGeneralData(name: item.title, color: theme.colors.text, icon: nil, type: .none, description: item.breadcrumbs.joined(separator: " "), action: { +// +// }))) +// index += 1 + } + + /* + return + */ + + return entries +} + +func SearchSettingsController(context: AccountContext, searchQuery: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal) -> InputDataController { + + let searchableItems = Promise<[SettingsSearchableItem]>() + searchableItems.set(settingsSearchableItems(context: context, archivedStickerPacks: archivedStickerPacks, privacySettings: privacySettings)) + + + let previousRecentlySearchedItemOrder = Atomic<[SettingsSearchableItemId]>(value: []) + let fixedRecentlySearchedItems = settingsSearchRecentItems(postbox: context.account.postbox) + |> map { recentIds -> [SettingsSearchableItemId] in + var result: [SettingsSearchableItemId] = [] + let _ = previousRecentlySearchedItemOrder.modify { current in + var updated: [SettingsSearchableItemId] = [] + for id in current { + inner: for recentId in recentIds { + if recentId == id { + updated.append(id) + result.append(recentId) + break inner + } + } + } + for recentId in recentIds.reversed() { + if !updated.contains(recentId) { + updated.insert(recentId, at: 0) + result.insert(recentId, at: 0) + } + } + return updated + } + return result + } + + + let items:Signal<([SettingsSearchableItem], Bool), NoError> = searchQuery |> mapToSignal { state in + switch state.state { + case .Focus: + if !state.request.isEmpty { + return combineLatest(searchableItems.get(), faqSearchableItems(context: context)) + |> mapToSignal { searchableItems, faqSearchableItems -> Signal<([SettingsSearchableItem], Bool), NoError> in + let results = searchSettingsItems(items: searchableItems, query: state.request) + let faqResults = searchSettingsItems(items: faqSearchableItems, query: state.request) + let finalResults: [SettingsSearchableItem] + if faqResults.first?.id == .faq(1) { + finalResults = faqResults + results + } else { + finalResults = results + faqResults + } + return .single((finalResults, false)) + } + } else { + return combineLatest(searchableItems.get(), fixedRecentlySearchedItems) + |> map { searchableItems, recentItems -> ([SettingsSearchableItem], Bool) in + let searchableItemsMap = searchableItems.reduce([SettingsSearchableItemId : SettingsSearchableItem]()) { (map, item) -> [SettingsSearchableItemId: SettingsSearchableItem] in + var map = map + map[item.id] = item + return map + } + var result: [SettingsSearchableItem] = [] + for itemId in recentItems { + if let searchItem = searchableItemsMap[itemId] { + if case let .language(id) = searchItem.id, id > 0 { + } else { + result.append(searchItem) + } + } + } + return (result, true) + } + } + case .None: + return .complete() + } + } + + let entries:Signal = items |> map { items, recent in + return searchSettingsEntries(context: context, items: items, recent: recent) + } |> map { + return InputDataSignalValue(entries: $0, animated: false) + } + + let controller = InputDataController(dataSignal: entries, title: "") + + controller.didLoaded = { controller, _ in + controller.genericView.tableView.needUpdateVisibleAfterScroll = true + controller.genericView.tableView.border = [.Right] + controller.tableView.emptyItem = SearchSettingsEmptyItem(NSZeroSize) + } + + + controller.getBackgroundColor = { + return theme.colors.background + } + + return controller +} diff --git a/Telegram-Mac/SearchSettingsEmptyItem.swift b/Telegram-Mac/SearchSettingsEmptyItem.swift new file mode 100644 index 0000000000..69ab987c40 --- /dev/null +++ b/Telegram-Mac/SearchSettingsEmptyItem.swift @@ -0,0 +1,72 @@ +// +// SearchSettingsEmptyItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 14.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class SearchSettingsEmptyItem: TableRowItem { + let textLayout:TextViewLayout + override init(_ initialSize:NSSize) { + textLayout = TextViewLayout(.initialize(string: L10n.settingsSearchEmptyItem, color: theme.colors.grayText, font: .normal(.title)), alignment: .center) + super.init(initialSize) + } + + override var height: CGFloat { + if let table = table { + return table.frame.height + } else { + return initialSize.height + } + } + + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + return true + } + + override func viewClass() -> AnyClass { + return SearchSettingsEmptyView.self + } +} + +class SearchSettingsEmptyView : TableRowView { + private let textView:TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + border = [.Right] + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + } + + override func layout() { + super.layout() + if let item = item as? SearchSettingsEmptyItem { + item.textLayout.measure(width: frame.width - 60) + textView.update(item.textLayout) + textView.center() + } + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/SearchUtils.swift b/Telegram-Mac/SearchUtils.swift new file mode 100644 index 0000000000..5f6dadb6ee --- /dev/null +++ b/Telegram-Mac/SearchUtils.swift @@ -0,0 +1,93 @@ +// +// SearchUtils.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + +func rangeOfSearch(_ query: String, in text: String) -> NSRange? { + + guard !text.isEmpty && !query.isEmpty else { + return nil + } + + let query = (query.components(separatedBy: " ").max(by: { $0.count < $1.count}) ?? query).lowercased().trimmed.nsstring + let text = text.lowercased().nsstring + var start: Int = -1 + var length: Int = -1 + let N1 = text.length + for a in 0 ..< N1 { + var currentLen:Int = 0 + let N2 = min(query.length, N1 - a) + loop: for b in 0 ..< N2 { + let match = text.character(at: a + b) == query.character(at: b) + if match { + currentLen += 1 + } + if !match || b == N2 - 1 { + if currentLen > 0 && currentLen > length { + length = currentLen + start = a + } + break loop + } + } + } + if start == -1 { + return nil + } + let punctuationsChars = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + loop: for a in start + length ..< text.length { + if !punctuationsChars.contains(text.substring(with: NSMakeRange(a, 1))) { + length += 1 + } else { + break loop + } + } + + return start != NSNotFound ? NSMakeRange(start, length) : nil + +// text = text.toLowerCase(); +// String message = messageObject.messageOwner.message.toLowerCase(); +// int start = -1; +// int length = -1; +// for (int a = 0, N1 = message.length(); a < N1; a++) { +// int currentLen = 0; +// for (int b = 0, N2 = Math.min(text.length(), N1 - a); b < N2; b++) { +// boolean match = message.charAt(a + b) == text.charAt(b); +// if (match) { +// currentLen++; +// } +// if (!match || b == N2 - 1) { +// if (currentLen > 0 && currentLen > length) { +// length = currentLen; +// start = a; +// } +// break; +// } +// } +// } +// if (start == -1) { +// if (!urlPathSelection.isEmpty()) { +// linkSelectionBlockNum = -1; +// resetUrlPaths(true); +// invalidate(); +// } +// return; +// } +// String punctuationsChars = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; +// for (int a = start + length, N = message.length(); a < N; a++) { +// if (punctuationsChars.indexOf(message.charAt(a)) < 0) { +// length++; +// } else { +// break; +// } +// } + + +} diff --git a/Telegram-Mac/SecretChatKeyViewController.swift b/Telegram-Mac/SecretChatKeyViewController.swift index 156d16faef..38aa51ec0c 100644 --- a/Telegram-Mac/SecretChatKeyViewController.swift +++ b/Telegram-Mac/SecretChatKeyViewController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class SecretChatKeyView : View { let imageView:ImageView = ImageView() @@ -22,7 +23,7 @@ class SecretChatKeyView : View { addSubview(textView) addSubview(descriptionView) descriptionView.userInteractionEnabled = false - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } required init?(coder: NSCoder) { @@ -55,7 +56,7 @@ class SecretChatKeyView : View { style.lineSpacing = 3.0 style.lineBreakMode = .byWordWrapping style.alignment = .center - let attributedString = NSAttributedString(string: text, attributes: [.paragraphStyle: style, NSAttributedStringKey.font: NSFont.code(.title), .foregroundColor: theme.colors.text]) + let attributedString = NSAttributedString(string: text, attributes: [.paragraphStyle: style, NSAttributedString.Key.font: NSFont.code(.title), .foregroundColor: theme.colors.text]) let layout = TextViewLayout(attributedString, maximumNumberOfLines: 4, alignment: .center) @@ -63,7 +64,7 @@ class SecretChatKeyView : View { textView.update(layout) let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.encryptionKeyDescription(participant.compactDisplayTitle, participant.compactDisplayTitle)), color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: tr(L10n.encryptionKeyDescription(participant.compactDisplayTitle, participant.compactDisplayTitle)), color: theme.colors.grayText, font: .normal(.text)) attr.detectBoldColorInString(with: .medium(.text)) @@ -77,21 +78,21 @@ class SecretChatKeyView : View { needsLayout = true } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() - backgroundColor = theme.colors.background - textView.backgroundColor = theme.colors.background - descriptionView.backgroundColor = theme.colors.background + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.grayBackground + textView.backgroundColor = theme.colors.grayBackground + descriptionView.backgroundColor = theme.colors.grayBackground } override func layout() { super.layout() - imageView.centerX(y: 20) - textView.centerX(y: imageView.frame.maxY + 20) + imageView.centerX(y: 30) + textView.centerX(y: imageView.frame.maxY + 30) descriptionView.layout?.measure(width: frame.width - 60) - descriptionView.centerX(y: textView.frame.maxY + 20) + descriptionView.centerX(y: textView.frame.maxY + 30) } } @@ -102,7 +103,7 @@ class SecretChatKeyViewController: TelegramGenericViewController deliverOnMainQueue).start(next: { [weak self] view, peerView, _ in + disposable.set((combineLatest(context.account.postbox.combinedView(keys: [.peerChatState(peerId: peerId)]), context.account.viewTracker.peerView( peerId), appearanceSignal) |> deliverOnMainQueue).start(next: { [weak self] view, peerView, _ in if let peerId = self?.peerId, let view = view.views[.peerChatState(peerId: peerId)] as? PeerChatStateView, let state = view.chatState as? SecretChatKeyState { if let keyFingerprint = state.keyFingerprint { @@ -124,8 +125,8 @@ class SecretChatKeyViewController: TelegramGenericViewController Bool { + switch self { + case let .uploading(progress): + if case .uploading(progress) = to { + return true + } else { + return false + } + case let .uploaded(file): + if case .uploaded(file) = to { + return true + } else { + return false + } + } + } +} + +struct SecureIdVerificationLocalDocument { + let id: Int64 + let resource: TelegramMediaResource + var state: SecureIdVerificationLocalDocumentState + + func isEqual(to: SecureIdVerificationLocalDocument) -> Bool { + if self.id != to.id { + return false + } + if !self.resource.isEqual(to: to.resource) { + return false + } + if !self.state.isEqual(to: to.state) { + return false + } + return true + } +} + +enum SecureIdVerificationDocumentId: Hashable { + case remote(Int64) + case local(Int64) + + static func ==(lhs: SecureIdVerificationDocumentId, rhs: SecureIdVerificationDocumentId) -> Bool { + switch lhs { + case let .remote(id): + if case .remote(id) = rhs { + return true + } else { + return false + } + case let .local(id): + if case .local(id) = rhs { + return true + } else { + return false + } + } + } + + var hashValue: Int { + switch self { + case let .local(id): + return Int(id) + case let .remote(id): + return Int(id) + } + } +} + +enum SecureIdVerificationDocument : Equatable { + case remote(SecureIdFileReference) + case local(SecureIdVerificationLocalDocument) + + var id: SecureIdVerificationDocumentId { + switch self { + case let .remote(file): + return .remote(file.id) + case let .local(file): + return .local(file.id) + } + } + + var resource: TelegramMediaResource { + switch self { + case let .remote(file): + return SecureFileMediaResource(file: file) + case let .local(file): + return file.resource + } + } + + func isEqual(to: SecureIdVerificationDocument) -> Bool { + switch self { + case let .remote(reference): + if case .remote(reference) = to { + return true + } else { + return false + } + case let .local(lhsDocument): + if case let .local(rhsDocument) = to, lhsDocument.isEqual(to: rhsDocument) { + return true + } else { + return false + } + } + } + static func ==(lhs: SecureIdVerificationDocument, rhs: SecureIdVerificationDocument) -> Bool { + return lhs.isEqual(to: rhs) + } +} diff --git a/Telegram-Mac/SecureIdVerificationDocumentsContext.swift b/Telegram-Mac/SecureIdVerificationDocumentsContext.swift new file mode 100644 index 0000000000..71a65d7a97 --- /dev/null +++ b/Telegram-Mac/SecureIdVerificationDocumentsContext.swift @@ -0,0 +1,78 @@ +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +private final class DocumentContext { + private let disposable: Disposable + + init(disposable: Disposable) { + self.disposable = disposable + } + + deinit { + self.disposable.dispose() + } +} + +final class SecureIdVerificationDocumentsContext { + private let context: SecureIdAccessContext + private let postbox: Postbox + private let network: Network + private let update: (Int64, SecureIdVerificationLocalDocumentState) -> Void + private var contexts: [Int64: DocumentContext] = [:] + + init(postbox: Postbox, network: Network, context: SecureIdAccessContext, update: @escaping (Int64, SecureIdVerificationLocalDocumentState) -> Void) { + self.postbox = postbox + self.network = network + self.context = context + self.update = update + } + + func stateUpdated(_ documents: [SecureIdVerificationDocument]) { + var validIds = Set() + + for document in documents { + switch document { + case let .local(info): + validIds.insert(info.id) + 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) + |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + switch result { + case let .progress(value): + if strongSelf.contexts[info.id] != nil { + strongSelf.update(info.id, .uploading(value)) + } + case let .result(file): + if strongSelf.contexts[info.id] != nil { + strongSelf.update(info.id, .uploaded(file.0)) + } + } + } + }, error: { [weak self] _ in + if let strongSelf = self { + + } + })) + } + case .remote: + break + } + } + + var removeIds: [Int64] = [] + for (id, _) in self.contexts { + if !validIds.contains(id) { + removeIds.append(id) + } + } + for id in removeIds { + self.contexts.removeValue(forKey: id) + } + } +} diff --git a/Telegram-Mac/SelectPeersController.swift b/Telegram-Mac/SelectPeersController.swift index 32858c0be9..c2f58d9525 100644 --- a/Telegram-Mac/SelectPeersController.swift +++ b/Telegram-Mac/SelectPeersController.swift @@ -8,70 +8,48 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit enum SelectPeerEntryStableId : Hashable { case search - case peerId(PeerId) + case peerId(PeerId, Int32) case searchEmpty case separator(Int32) + case inviteLink var hashValue: Int { switch self { case .search: return 0 case .searchEmpty: return 1 + case .inviteLink: + return -1 case .separator(let index): return Int(index) - case let .peerId(peerId): + case let .peerId(peerId, _): return peerId.hashValue } } - - static func ==(lhs:SelectPeerEntryStableId, rhs:SelectPeerEntryStableId) -> Bool { - switch lhs { - case .search: - if case .search = rhs { - return true - } else { - return false - } - case .searchEmpty: - if case .searchEmpty = rhs { - return true - } else { - return false - } - case let .peerId(peerId): - if case .peerId(peerId) = rhs { - return true - } else { - return false - } - case let .separator(index): - if case .separator(index) = rhs { - return true - } else { - return false - } - } - } } enum SelectPeerEntry : Comparable, Identifiable { - case peer(Peer, Int32, PeerPresence?, Bool) + case peer(SelectPeerValue, Int32, Bool) case searchEmpty case separator(Int32, String) + case inviteLink(()->Void) var stableId: SelectPeerEntryStableId { switch self { case .searchEmpty: return .searchEmpty case .separator(let index, _): return .separator(index) - case let .peer(peer, _, _, _): - return .peerId(peer.id) + case let .peer(peer, index, _): + return .peerId(peer.peer.id, index) + case .inviteLink: + return .inviteLink } } @@ -89,14 +67,15 @@ enum SelectPeerEntry : Comparable, Identifiable { } else { return false } - case let .peer(lhsPeer, lhsIndex, lhsPresence, lhsEnabled): + case .inviteLink: + if case .inviteLink = rhs { + return true + } else { + return false + } + case let .peer(peer, index, enabled): switch rhs { - case let .peer(rhsPeer, rhsIndex, rhsPresence, rhsEnabled) where lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsEnabled == rhsEnabled: - if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence { - return lhsPresence.isEqual(to: rhsPresence) - } else if (lhsPresence != nil) != (rhsPresence != nil) { - return false - } + case .peer(peer, index, enabled): return true default: return false @@ -108,9 +87,11 @@ enum SelectPeerEntry : Comparable, Identifiable { switch self { case .searchEmpty: return 1 + case .inviteLink: + return -1 case .separator(let index, _): return index - case .peer(_, let index, _, _): + case .peer(_, let index, _): return index } } @@ -150,7 +131,7 @@ private extension PeerIndexNameRepresentation { if lastResult == .orderedSame { let f = lhsFirst.prefix(1) - if let character = f.characters.first { + if let character = f.first { let characterString = String(character) let scalars = characterString.unicodeScalars @@ -176,8 +157,69 @@ func <(lhs:Peer, rhs:Peer) -> Bool { return lhs.indexName.isLessThan(other: rhs.indexName) == .orderedAscending } -private func entriesForView(_ view: ContactPeersView, searchPeers:[PeerId], searchView:MultiplePeersView, excludeIds:[PeerId] = []) -> [SelectPeerEntry] { +struct SelectPeerValue : Equatable { + + + let peer: Peer + let presence: PeerPresence? + let subscribers: Int? + init(peer: Peer, presence: PeerPresence?, subscribers: Int?) { + self.peer = peer + self.presence = presence + self.subscribers = subscribers + } + + static func == (lhs: SelectPeerValue, rhs: SelectPeerValue) -> Bool { + if !lhs.peer.isEqual(rhs.peer) { + return false + } + if let lhsPresence = lhs.presence, let rhsPresence = rhs.presence { + if !lhsPresence.isEqual(to: rhsPresence) { + return false + } + } else if (lhs.presence != nil) != (rhs.presence != nil) { + return false + } + + if lhs.subscribers != rhs.subscribers { + return false + } + + return true + } + + func status(_ context: AccountContext) -> (String?, NSColor) { + var color:NSColor = theme.colors.grayText + var string:String = L10n.peerStatusLongTimeAgo + + if let count = subscribers, peer.isGroup || peer.isSupergroup { + let countValue = L10n.privacySettingsGroupMembersCountCountable(count) + string = countValue.replacingOccurrences(of: "\(count)", with: count.separatedNumber) + } else if peer.isGroup || peer.isSupergroup { + return (nil, color) + } else if let presence = presence as? TelegramUserPresence { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + (string, _, color) = stringAndActivityForUserPresence(presence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp)) + } else { + if let addressName = peer.addressName { + color = theme.colors.accent + string = "@\(addressName)" + } + } + if peer.isBot { + string = L10n.presenceBot.lowercased() + } + return (string, color) + } +} + +private func entriesForView(_ view: ContactPeersView, searchPeers:[PeerId], searchView:MultiplePeersView, excludeIds:[PeerId] = [], linkInvation: (()->Void)? = nil) -> [SelectPeerEntry] { var entries: [SelectPeerEntry] = [] + + if let linkInvation = linkInvation { + entries.append(SelectPeerEntry.inviteLink(linkInvation)) + } + //entries.append(.search(false)) if let accountPeer = view.accountPeer { var index:Int32 = 0 @@ -194,7 +236,8 @@ private func entriesForView(_ view: ContactPeersView, searchPeers:[PeerId], sear continue } } - entries.append(.peer(peer,index,searchView.presences[peer.id], !excludeIds.contains(peer.id))) + + entries.append(.peer(SelectPeerValue(peer: peer, presence: searchView.presences[peer.id], subscribers: nil), index, !excludeIds.contains(peer.id))) index += 1 } } @@ -207,9 +250,10 @@ private func entriesForView(_ view: ContactPeersView, searchPeers:[PeerId], sear continue } } - entries.append(.peer(peer,index,view.peerPresences[peer.id], !excludeIds.contains(peer.id))) + + entries.append(.peer(SelectPeerValue(peer: peer, presence: view.peerPresences[peer.id], subscribers: nil), index, !excludeIds.contains(peer.id))) index += 1 - if index == 150 { + if index == 230 { break } } @@ -224,69 +268,158 @@ private func entriesForView(_ view: ContactPeersView, searchPeers:[PeerId], sear return entries } -private func searchEntriesForPeers(_ peers:[Peer], account:Account, view:MultiplePeersView, isLoading: Bool, excludeIds:[PeerId] = []) -> [SelectPeerEntry] { +private func searchEntriesForPeers(_ peers:[SelectPeerValue], _ global: [SelectPeerValue], context: AccountContext, isLoading: Bool, excludeIds:Set = Set()) -> [SelectPeerEntry] { var entries: [SelectPeerEntry] = [] + var excludeIds = excludeIds var index:Int32 = 0 for peer in peers { - if account.peerId != peer.id { - if let peer = peer as? TelegramUser, let botInfo = peer.botInfo { + if context.account.peerId != peer.peer.id { + if let peer = peer.peer as? TelegramUser, let botInfo = peer.botInfo { if !botInfo.flags.contains(.worksWithGroups) { continue } } - entries.append(.peer(peer,index,view.presences[peer.id], !excludeIds.contains(peer.id))) + + entries.append(.peer(peer, index, !excludeIds.contains(peer.peer.id))) + excludeIds.insert(peer.peer.id) index += 1 } } - if entries.count == 1 && !isLoading { + if !global.isEmpty { + + let global = global.filter { peer in + if context.account.peerId != peer.peer.id, !excludeIds.contains(peer.peer.id) { + if let peer = peer.peer as? TelegramUser, let botInfo = peer.botInfo { + if !botInfo.flags.contains(.worksWithGroups) { + return false + } + } + return true + } else { + return false + } + } + + if !global.isEmpty { + entries.append(.separator(index, L10n.searchSeparatorGlobalPeers)) + index += 1 + + } + + for peer in global { + entries.append(.peer(peer, index, !excludeIds.contains(peer.peer.id))) + excludeIds.insert(peer.peer.id) + index += 1 + } + } + + if entries.isEmpty && !isLoading { entries.append(.searchEmpty) } - + return entries } -fileprivate func prepareEntries(from:[SelectPeerEntry]?, to:[SelectPeerEntry], account:Account, initialSize:NSSize, animated:Bool, interactions:SelectPeerInteraction, singleAction:((Peer)->Void)? = nil) -> TableUpdateTransition { - let (deleted,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in +fileprivate func prepareEntries(from:[SelectPeerEntry]?, to:[SelectPeerEntry], context: AccountContext, initialSize:NSSize, animated:Bool, interactions:SelectPeerInteraction, singleAction:((Peer)->Void)? = nil, scroll: TableScrollState = .none(nil)) -> Signal { + return Signal { subscriber in + var cancelled = false - var item:TableRowItem - - switch entry { - case let .peer(peer, _, presence, enabled): + func makeItem(_ entry: SelectPeerEntry) -> TableRowItem { + var item:TableRowItem - var color:NSColor = theme.colors.grayText - var string:String = tr(.peerStatusRecently) - if let presence = presence as? TelegramUserPresence { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - (string, _, color) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + switch entry { + case let .peer(peer, _, enabled): + + + + let interactionType:ShortPeerItemInteractionType + if singleAction != nil { + interactionType = .plain + } else { + interactionType = .selectable(interactions) + } + + let (status, color) = peer.status(context) + + item = ShortPeerRowItem(initialSize, peer: peer.peer, account: context.account, stableId: entry.stableId, enabled: enabled, statusStyle: ControlStyle(foregroundColor: color), status: status, drawLastSeparator: true, inset:NSEdgeInsets(left: 10, right:10), interactionType:interactionType, action: { + if let singleAction = singleAction { + singleAction(peer.peer) + } + }) + case .searchEmpty: + return SearchEmptyRowItem(initialSize, stableId: entry.stableId) + case .separator(_, let text): + return SeparatorRowItem(initialSize, entry.stableId, string: text.uppercased()) + case let .inviteLink(action): + return GeneralInteractedRowItem(initialSize, stableId: entry.stableId, name: L10n.peerSelectInviteViaLink, nameStyle: blueActionButton, type: .none, action: { + action() + interactions.close() + }, thumb: GeneralThumbAdditional(thumb: theme.icons.group_invite_via_link, textInset: 39), inset: NSEdgeInsetsMake(0, 16, 0, 10)) } - let interactionType:ShortPeerItemInteractionType - if singleAction != nil { - interactionType = .plain - } else { - interactionType = .selectable(interactions) + let _ = item.makeSize(initialSize.width) + + return item + } + + + + if Thread.isMainThread { + var initialIndex:Int = 0 + var height:CGFloat = 0 + var firstInsertion:[(Int, TableRowItem)] = [] + let entries = Array(to) + + let index:Int = 0 + + for i in index ..< entries.count { + let item = makeItem(to[i]) + height += item.height + firstInsertion.append((i, item)) + if initialSize.height < height { + break + } } - item = ShortPeerRowItem(initialSize, peer: peer, account: account, stableId: entry.stableId, enabled: enabled, statusStyle: ControlStyle(foregroundColor:color), status: string, inset:NSEdgeInsets(left: 10, right:10), interactionType:interactionType, action: { - if let singleAction = singleAction { - singleAction(peer) + + initialIndex = firstInsertion.count + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: firstInsertion, updated: [], state: scroll)) + + prepareQueue.async { + if !cancelled { + + + var insertions:[(Int, TableRowItem)] = [] + let updates:[(Int, TableRowItem)] = [] + + for i in initialIndex ..< entries.count { + let item:TableRowItem + item = makeItem(to[i]) + insertions.append((i, item)) + } + + + subscriber.putNext(TableUpdateTransition(deleted: [], inserted: insertions, updated: updates, state: scroll)) + subscriber.putCompletion() } + } + } else { + let (deleted,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in + return makeItem(entry) }) - case .searchEmpty: - return SearchEmptyRowItem(initialSize, stableId: entry.stableId) - case .separator(_, let text): - return SeparatorRowItem(initialSize, entry.stableId, string: text.uppercased()) + + subscriber.putNext(TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated, state: scroll)) + subscriber.putCompletion() } - let _ = item.makeSize(initialSize.width) + return ActionDisposable { + cancelled = true + } - return item - - }) + } - return TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated:animated) } @@ -311,12 +444,16 @@ public struct SelectPeerSettings: OptionSet { if flags.contains(SelectPeerSettings.contacts) { rawValue |= SelectPeerSettings.contacts.rawValue } - + if flags.contains(SelectPeerSettings.excludeBots) { + rawValue |= SelectPeerSettings.excludeBots.rawValue + } self.rawValue = rawValue } - public static let remote = SelectPeerSettings(rawValue: 1) - public static let contacts = SelectPeerSettings(rawValue: 2) + public static let remote = SelectPeerSettings(rawValue: 1 << 1) + public static let contacts = SelectPeerSettings(rawValue: 1 << 2) + public static let groups = SelectPeerSettings(rawValue: 1 << 3) + public static let excludeBots = SelectPeerSettings(rawValue: 1 << 4) } class SelectPeersBehavior { @@ -326,6 +463,9 @@ class SelectPeersBehavior { fileprivate let _peersResult:Atomic<[PeerId:TemporaryPeer]> = Atomic(value: [:]) + var participants:[PeerId:RenderedChannelParticipant] { + return [:] + } fileprivate let inSearchSelected:Atomic<[PeerId]> = Atomic(value:[]) @@ -340,17 +480,120 @@ class SelectPeersBehavior { } - func start(account: Account, search:Signal) -> Signal<[SelectPeerEntry], Void> { + func start(context: AccountContext, search:Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { return .complete() } } -class SelectChannelMembersBehavior : SelectPeersBehavior { + + +class SelectGroupMembersBehavior : SelectPeersBehavior { fileprivate let peerId:PeerId private let _renderedResult:Atomic<[PeerId:RenderedChannelParticipant]> = Atomic(value: [:]) + override var participants:[PeerId:RenderedChannelParticipant] { + return _renderedResult.modify({$0}) + } - var participants:[PeerId:RenderedChannelParticipant] { + init(peerId:PeerId, limit: Int32 = .max, settings: SelectPeerSettings = [.remote]) { + self.peerId = peerId + super.init(settings: settings, limit: limit) + } + + override func start(context: AccountContext, search: Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { + let peerId = self.peerId + let _renderedResult = self._renderedResult + + let previousSearch = Atomic(value: nil) + + return search |> map { SearchState(state: .Focus, request: $0.request) } |> distinctUntilChanged |> mapToSignal { search -> Signal<([SelectPeerEntry], Bool), NoError> in + + let participantsPromise: Promise<[RenderedChannelParticipant]> = Promise() + + + let viewKey = PostboxViewKey.peer(peerId: peerId, components: .all) + + participantsPromise.set(context.account.postbox.combinedView(keys: [viewKey]) |> map { combinedView in + let peerView = combinedView.views[viewKey] as? PeerView + + if let peerView = peerView { + if let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants { + + var creatorPeer: Peer? + for participant in participants.participants { + if let peer = peerView.peers[participant.peerId] { + switch participant { + case .creator: + creatorPeer = peer + default: + break + } + } + } + guard let creator = creatorPeer else { + return [] + } + + return participants.participants.compactMap { participant in + + if let peer = peerView.peers[participant.peerId] { + + let rendered: RenderedChannelParticipant + + switch participant { + case .creator: + rendered = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer) + case .admin: + var peers: [PeerId: Peer] = [:] + peers[creator.id] = creator + peers[peer.id] = peer + rendered = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers) + case .member: + var peers: [PeerId: Peer] = [:] + peers[creator.id] = creator + peers[peer.id] = peer + rendered = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers) + } + + if search.request.isEmpty { + return rendered + } else { + let found = !rendered.peer.displayTitle.lowercased().components(separatedBy: " ").filter {$0.hasPrefix(search.request.lowercased())}.isEmpty + if found { + return rendered + } else { + return nil + } + } + } else { + return nil + } + } + + } + } + return [] + }) + + return participantsPromise.get() |> map { participants in + _ = _renderedResult.swap(participants.toDictionary(with: { $0.peer.id })) + let updatedSearch = previousSearch.swap(search.request) != search.request + return (channelMembersEntries(participants, users: [], remote: [], context: context, isLoading: false), updatedSearch) + } + } + } + + deinit { + _ = _renderedResult.swap([:]) + + } +} + +class SelectChannelMembersBehavior : SelectPeersBehavior { + fileprivate let peerId:PeerId + private let _renderedResult:Atomic<[PeerId:RenderedChannelParticipant]> = Atomic(value: [:]) + private let loadDisposable = MetaDisposable() + override var participants:[PeerId:RenderedChannelParticipant] { return _renderedResult.modify({$0}) } @@ -359,47 +602,53 @@ class SelectChannelMembersBehavior : SelectPeersBehavior { super.init(settings: settings, limit: limit) } - override func start(account: Account, search: Signal) -> Signal<[SelectPeerEntry], Void> { + override func start(context: AccountContext, search: Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { let peerId = self.peerId let _renderedResult = self._renderedResult let _peersResult = self._peersResult let settings = self.settings - return search |> map {SearchState(state: .Focus, request: $0.request)} |> distinctUntilChanged |> mapToSignal { search -> Signal<[SelectPeerEntry], Void> in + let loadDisposable = self.loadDisposable + let previousSearch = Atomic(value: nil) + return search |> map { SearchState(state: .Focus, request: $0.request) } |> distinctUntilChanged |> mapToSignal { search -> Signal<([SelectPeerEntry], Bool), NoError> in - let filter:ChannelMembersFilter + let participantsPromise: Promise<[RenderedChannelParticipant]> = Promise() - if !search.request.isEmpty { - filter = .search(search.request) - } else { - filter = .none - } + var isListLoading: Bool = false - let participantsSignal:Signal<[RenderedChannelParticipant]?, Void> = channelMembers(account: account, peerId: peerId, filter: filter) |> map {_ = _renderedResult.swap($0.reduce([:], { current, participant in - var current = current - current[participant.peer.id] = participant - return current - })); return $0} + let value = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: search.request.isEmpty ? nil : search.request, requestUpdate: true, updated: { state in + participantsPromise.set(.single(state.list)) + + if case .loading = state.loadingState { + isListLoading = true + } else { + isListLoading = false + } + _ = _renderedResult.swap(state.list.toDictionary(with: {$0.peer.id})) + }) + - let foundLocalPeers = account.postbox.searchContacts(query: search.request.lowercased()) + loadDisposable.set(value.0) + + let foundLocalPeers = context.account.postbox.searchContacts(query: search.request.lowercased()) - let foundRemotePeers:Signal<([Peer], Bool), Void> = .single(([], true)) |> then ( searchPeers(account: account, query: search.request.lowercased()) |> map {($0, false)} ) + let foundRemotePeers:Signal<([Peer], [Peer], Bool), NoError> = .single(([], [], true)) |> then ( searchPeers(account: context.account, query: search.request.lowercased()) |> map {($0.map{$0.peer}, $1.map{$0.peer}, false)} ) - let contactsSearch: Signal<([TemporaryPeer], [TemporaryPeer], Bool), Void> + let contactsSearch: Signal<([TemporaryPeer], [TemporaryPeer], Bool), NoError> if settings.contains(.remote) { - contactsSearch = combineLatest(foundLocalPeers, foundRemotePeers) |> map { values -> ([Peer], [Peer], Bool) in - return (values.0, values.1.0, values.1.1 && search.request.length >= 5) + contactsSearch = combineLatest(foundLocalPeers |> map {$0.0}, foundRemotePeers) |> map { values -> ([Peer], [Peer], Bool) in + return (values.0 + values.1.0, values.1.1, values.1.2 && search.request.length >= 5) } - |> mapToSignal { values -> Signal<([Peer], [Peer], MultiplePeersView, Bool), Void> in - return account.postbox.multiplePeersView(values.0.map {$0.id}) |> take(1) |> map { views in + |> mapToSignal { values -> Signal<([Peer], [Peer], MultiplePeersView, Bool), NoError> in + return context.account.postbox.multiplePeersView(values.0.map {$0.id}) |> take(1) |> map { views in return (values.0, values.1, views, values.2) } } |> map { value -> ([TemporaryPeer], [TemporaryPeer], Bool) in - let contacts = value.0.map({TemporaryPeer(peer: $0, presence: value.2.presences[$0.id])}) - let global = value.1.map({TemporaryPeer(peer: $0, presence: value.2.presences[$0.id])}) + let contacts = value.0.filter {$0.isUser || ($0.isBot && !settings.contains(.excludeBots))}.map({TemporaryPeer(peer: $0, presence: value.2.presences[$0.id])}) + let global = value.1.filter {$0.isUser || ($0.isBot && !settings.contains(.excludeBots))}.map({TemporaryPeer(peer: $0, presence: value.2.presences[$0.id])}) let _ = _peersResult.swap((contacts + global).reduce([:], { current, peer in var current = current @@ -415,23 +664,27 @@ class SelectChannelMembersBehavior : SelectPeersBehavior { if !search.request.isEmpty { - return combineLatest(participantsSignal, contactsSearch) |> map { participants, peers in - return channelMembersEntries(participants ?? [], users: peers.0, remote: peers.1, account: account, isLoading: participants == nil && peers.2) + return combineLatest(participantsPromise.get(), contactsSearch) |> map { participants, peers in + let updatedSearch = previousSearch.swap(search.request) != search.request + return (channelMembersEntries(participants, users: peers.0, remote: peers.1, context: context, isLoading: isListLoading && peers.2), updatedSearch) } } else { - return participantsSignal |> map { participants in - return channelMembersEntries(participants ?? [], account: account, isLoading: participants == nil) + return participantsPromise.get() |> map { participants in + let updatedSearch = previousSearch.swap(search.request) != search.request + return (channelMembersEntries(participants, context: context, isLoading: isListLoading), updatedSearch) } } } } deinit { + loadDisposable.dispose() _ = _renderedResult.swap([:]) + } } -private func channelMembersEntries(_ participants:[RenderedChannelParticipant], users:[TemporaryPeer]? = nil, remote:[TemporaryPeer] = [], account:Account, isLoading: Bool) -> [SelectPeerEntry] { +private func channelMembersEntries(_ participants:[RenderedChannelParticipant], users:[TemporaryPeer]? = nil, remote:[TemporaryPeer] = [], context: AccountContext, isLoading: Bool) -> [SelectPeerEntry] { var entries: [SelectPeerEntry] = [] var peerIds:[PeerId:PeerId] = [:] @@ -454,38 +707,40 @@ private func channelMembersEntries(_ participants:[RenderedChannelParticipant], var index:Int32 = 0 if !participants.isEmpty { - //entries.append(.separator(index, tr(.channelSelectPeersMembers))) + //entries.append(.separator(index, tr(L10n.channelSelectPeersMembers))) index += 1 for participant in participants { - if account.peerId != participant.peer.id { - entries.append(.peer(participant.peer, index, participant.presences[participant.peer.id], true)) + if context.account.peerId != participant.peer.id { + + entries.append(.peer(SelectPeerValue(peer: participant.peer, presence: participant.presences[participant.peer.id], subscribers: nil), index, true)) index += 1 } } } if let users = users, !users.isEmpty { - entries.append(.separator(index, tr(.channelSelectPeersContacts))) + entries.append(.separator(index, tr(L10n.channelSelectPeersContacts))) index += 1 for peer in users { - if account.peerId != peer.peer.id { - entries.append(.peer(peer.peer, index, peer.presence, true)) + if context.account.peerId != peer.peer.id { + + entries.append(.peer(SelectPeerValue(peer: peer.peer, presence: peer.presence, subscribers: nil), index, true)) index += 1 } } } if !remote.isEmpty { - entries.append(.separator(index, tr(.channelSelectPeersGlobal))) + entries.append(.separator(index, tr(L10n.channelSelectPeersGlobal))) index += 1 for peer in remote { - if account.peerId != peer.peer.id { - entries.append(.peer(peer.peer, index, peer.presence, true)) + if context.account.peerId != peer.peer.id { + entries.append(.peer(SelectPeerValue(peer: peer.peer, presence: peer.presence, subscribers: nil), index, true)) index += 1 } } } - if entries.count == 1 && !isLoading { + if entries.isEmpty && !isLoading { entries.append(.searchEmpty) } @@ -494,17 +749,21 @@ private func channelMembersEntries(_ participants:[RenderedChannelParticipant], final class SelectChatsBehavior: SelectPeersBehavior { - override func start(account: Account, search: Signal) -> Signal<[SelectPeerEntry], Void> { - return search |> distinctUntilChanged |> mapToSignal { search -> Signal<[SelectPeerEntry], Void> in + override func start(context: AccountContext, search: Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { + + let previousSearch = Atomic(value: nil) + + return search |> distinctUntilChanged |> mapToSignal { search -> Signal<([SelectPeerEntry], Bool), NoError> in if search.request.isEmpty { - return account.viewTracker.tailChatListView(count: 200) |> deliverOn(prepareQueue) |> mapToQueue { value -> Signal<[SelectPeerEntry], Void> in + + return context.account.viewTracker.tailChatListView(groupId: .root, count: 200) |> deliverOn(prepareQueue) |> mapToQueue { value -> Signal<([SelectPeerEntry], Bool), NoError> in var entries:[Peer] = [] for entry in value.0.entries.reversed() { switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _): if let peer = renderedPeer.chatMainPeer, peer.canSendMessage, peer.canInviteUsers, peer.isSupergroup || peer.isGroup { entries.append(peer) } @@ -513,37 +772,41 @@ final class SelectChatsBehavior: SelectPeersBehavior { } } - var common:[SelectPeerEntry] = [] + let updatedSearch = previousSearch.swap(search.request) != search.request + if entries.isEmpty { - common.append(.searchEmpty) + return .single(([.searchEmpty], updatedSearch)) } else { + var common:[SelectPeerEntry] = [] var index:Int32 = 0 - for peer in entries { - common.append(.peer(peer, index, nil, true)) + for value in entries { + common.append(.peer(SelectPeerValue(peer: value, presence: nil, subscribers: nil), index, true)) index += 1 } - + return .single((common, updatedSearch)) } - return .single(common) } } else { - return account.postbox.searchPeers(query: search.request.lowercased()) |> map { - return $0.flatMap({$0.chatMainPeer}).filter {($0.isSupergroup || $0.isGroup) && $0.canInviteUsers} - } |> deliverOn(prepareQueue) |> map { entries -> [SelectPeerEntry] in + return context.account.postbox.searchPeers(query: search.request.lowercased()) |> map { + return $0.compactMap({$0.chatMainPeer}).filter {($0.isSupergroup || $0.isGroup) && $0.canInviteUsers} + } |> deliverOn(prepareQueue) |> map { entries -> ([SelectPeerEntry], Bool) in var common:[SelectPeerEntry] = [] + let updatedSearch = previousSearch.swap(search.request) != search.request + + if entries.isEmpty { common.append(.searchEmpty) } else { var index:Int32 = 0 for peer in entries { - common.append(.peer(peer, index, nil, true)) + common.append(.peer(SelectPeerValue(peer: peer, presence: nil, subscribers: nil), index, true)) index += 1 } } - return common + return (common, updatedSearch) } } @@ -552,38 +815,235 @@ final class SelectChatsBehavior: SelectPeersBehavior { } } + +class SelectUsersAndGroupsBehavior : SelectPeersBehavior { + + + override func start(context: AccountContext, search:Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { + + let previousSearch = Atomic(value: nil) + + return search |> mapToSignal { [weak self] search -> Signal<([SelectPeerEntry], Bool), NoError> in + + let settings = self?.settings ?? SelectPeerSettings() + let excludePeerIds = (self?.excludePeerIds ?? []) + + if search.request.isEmpty { + let inSearch:[PeerId] = self?.inSearchSelected.modify({$0}) ?? [] + + + return context.account.viewTracker.tailChatListView(groupId: .root, count: 200) |> take(1) |> mapToSignal { view in + let entries = view.0.entries + var peers:[Peer] = [] + var presences:[PeerId : PeerPresence] = [:] + for entry in entries.reversed() { + switch entry { + case let .MessageEntry(_, _, _, _, _, peer, presence, _, _, _): + if let peer = peer.chatMainPeer, !peer.isChannel && !peer.isBot { + peers.append(peer) + if let presence = presence { + presences[peer.id] = presence + } + } + default: + break + } + } + + return context.account.postbox.transaction { transaction -> [PeerId: CachedPeerData] in + var cachedData:[PeerId: CachedPeerData] = [:] + for peer in peers { + if peer.isSupergroup, let data = transaction.getPeerCachedData(peerId: peer.id) { + cachedData[peer.id] = data + } + } + return cachedData + } |> map { cachedData in + let local = peers.map { peer -> SelectPeerValue in + if let cachedData = cachedData[peer.id] as? CachedChannelData { + let subscribers: Int? + if let count = cachedData.participantsSummary.memberCount { + subscribers = Int(count) + } else { + subscribers = nil + } + return SelectPeerValue(peer: peer, presence: nil, subscribers: subscribers) + } else if let peer = peer as? TelegramGroup { + return SelectPeerValue(peer: peer, presence: nil, subscribers: peer.participantCount) + } else { + return SelectPeerValue(peer: peer, presence: presences[peer.id], subscribers: nil) + } + } + let updatedSearch = previousSearch.swap(search.request) != search.request + + return (searchEntriesForPeers(local, [], context: context, isLoading: false), updatedSearch) + } + } + + } else { + + let foundLocalPeers = context.account.postbox.searchPeers(query: search.request.lowercased()) + + let foundRemotePeers:Signal<([Peer], [Peer], Bool), NoError> = settings.contains(.remote) ? .single(([], [], true)) |> then ( searchPeers(account: context.account, query: search.request.lowercased()) |> map {($0.map{$0.peer}, $1.map{$0.peer}, false)} ) : .single(([], [], false)) + + return combineLatest(foundLocalPeers |> map {$0.compactMap( {$0.chatMainPeer })}, foundRemotePeers) |> map { values -> ([Peer], [Peer], Bool) in + return (uniquePeers(from: values.0), values.1.0 + values.1.1, values.1.2 && search.request.length >= 5) + } + |> runOn(prepareQueue) + |> mapToSignal { values -> Signal<([SelectPeerEntry], Bool), NoError> in + + var values = values + if settings.contains(.excludeBots) { + values.0 = values.0.filter {!$0.isBot} + } + values.0 = values.0.filter { !$0.isChannel } + values.1 = values.1.filter { !$0.isChannel } + + let local = uniquePeers(from: values.0 + values.1) + + return context.account.postbox.transaction { transaction -> ([PeerId : PeerPresence], [PeerId : CachedPeerData]) in + var presences: [PeerId : PeerPresence] = [:] + var cachedData: [PeerId : CachedPeerData] = [:] + for peer in local { + if peer.isSupergroup { + if let data = transaction.getPeerCachedData(peerId: peer.id) { + cachedData[peer.id] = data + } + } else { + if let presence = transaction.getPeerPresence(peerId: peer.id) { + presences[peer.id] = presence + } + } + } + return (presences, cachedData) + } |> map { (presences, cachedData) -> ([SelectPeerEntry], Bool) in + let local:[SelectPeerValue] = local.map { peer in + if let cachedData = cachedData[peer.id] as? CachedChannelData { + let subscribers: Int? + if let count = cachedData.participantsSummary.memberCount { + subscribers = Int(count) + } else { + subscribers = nil + } + return SelectPeerValue(peer: peer, presence: nil, subscribers: subscribers) + } else if let peer = peer as? TelegramGroup { + return SelectPeerValue(peer: peer, presence: nil, subscribers: peer.participantCount) + } else { + return SelectPeerValue(peer: peer, presence: presences[peer.id], subscribers: nil) + } + } + let updatedSearch = previousSearch.swap(search.request) != search.request + + return (searchEntriesForPeers(local, [], context: context, isLoading: values.2), updatedSearch) + } + + } + } + + } + + } + +} + + fileprivate class SelectContactsBehavior : SelectPeersBehavior { fileprivate let index: PeerNameIndex = .lastNameFirst - + private var previousGlobal:Atomic<[SelectPeerValue]> = Atomic(value: []) - override func start(account: Account, search:Signal) -> Signal<[SelectPeerEntry], Void> { + deinit { + var bp:Int = 0 + bp += 1 + _ = previousGlobal.swap([]) + } + override func start(context: AccountContext, search:Signal, linkInvation: (()->Void)? = nil) -> Signal<([SelectPeerEntry], Bool), NoError> { - return search |> mapToSignal { [weak self] search -> Signal<[SelectPeerEntry], Void> in + let previousGlobal = self.previousGlobal + let previousSearch = Atomic(value: nil) + return search |> mapToSignal { [weak self] search -> Signal<([SelectPeerEntry], Bool), NoError> in let settings = self?.settings ?? SelectPeerSettings() let excludePeerIds = (self?.excludePeerIds ?? []) if search.request.isEmpty { let inSearch:[PeerId] = self?.inSearchSelected.modify({$0}) ?? [] - return combineLatest(account.postbox.contactPeersView(accountPeerId: account.peerId), account.postbox.multiplePeersView(inSearch)) + return combineLatest(context.account.postbox.contactPeersView(accountPeerId: context.account.peerId, includePresences: true), context.account.postbox.multiplePeersView(inSearch)) |> deliverOn(prepareQueue) - |> mapToQueue { view, searchView -> Signal<[SelectPeerEntry], Void> in - return .single(entriesForView(view, searchPeers: inSearch, searchView: searchView, excludeIds: excludePeerIds)) + |> map { view, searchView -> ([SelectPeerEntry], Bool) in + let updatedSearch = previousSearch.swap(search.request) != search.request + return (entriesForView(view, searchPeers: inSearch, searchView: searchView, excludeIds: excludePeerIds, linkInvation: linkInvation), updatedSearch) } } else { - let foundLocalPeers = account.postbox.searchContacts(query: search.request.lowercased()) + let foundLocalPeers = context.account.postbox.searchContacts(query: search.request.lowercased()) - let foundRemotePeers:Signal<([Peer], Bool), Void> = settings.contains(.remote) ? .single(([], true)) |> then ( searchPeers(account: account, query: search.request.lowercased()) |> map {($0, false)} ) : .single(([], false)) + let foundRemotePeers:Signal<([Peer], [Peer], Bool), NoError> = settings.contains(.remote) ? .single(([], [], true)) |> then ( searchPeers(account: context.account, query: search.request.lowercased()) |> map {($0.map{$0.peer}, $1.map{$0.peer}, false)} ) : .single(([], [], false)) - return combineLatest(foundLocalPeers, foundRemotePeers) |> map { values -> ([Peer], Bool) in - return (uniquePeers(from: (values.0 + values.1.0)), values.1.1 && search.request.length >= 5) - } + return combineLatest(foundLocalPeers |> map {$0.0}, foundRemotePeers) |> map { values -> ([Peer], [Peer], Bool) in + return (uniquePeers(from: values.0), values.1.0 + values.1.1, values.1.2 && search.request.length >= 5) + } |> runOn(prepareQueue) - |> mapToSignal { values -> Signal<[SelectPeerEntry], Void> in - return account.postbox.multiplePeersView(values.0.map {$0.id}) |> take(1) |> map { view -> [SelectPeerEntry] in - return searchEntriesForPeers(values.0, account: account, view: view, isLoading: values.1, excludeIds: excludePeerIds) + |> mapToSignal { values -> Signal<([SelectPeerEntry], Bool), NoError> in + var values = values + if settings.contains(.excludeBots) { + values.0 = values.0.filter {!$0.isBot} + } + values.0 = values.0.filter {!$0.isChannel && (settings.contains(.groups) || (!$0.isSupergroup && !$0.isGroup))} + values.1 = values.1.filter {!$0.isChannel && (settings.contains(.groups) || (!$0.isSupergroup && !$0.isGroup))} + let local = values.0 + let global = values.1 + + return context.account.postbox.transaction { transaction -> [PeerId : PeerPresence] in + var presences: [PeerId : PeerPresence] = [:] + for peer in local { + if let presence = transaction.getPeerPresence(peerId: peer.id) { + presences[peer.id] = presence + } + } + return presences + } |> map { presences -> ([SelectPeerEntry], Bool) in + let local:[SelectPeerValue] = local.map { peer in + return SelectPeerValue(peer: peer, presence: presences[peer.id], subscribers: nil) + } + + var filteredLocal:[SelectPeerValue] = [] + var excludeIds = Set() + for peer in local { + if context.account.peerId != peer.peer.id { + if let peer = peer.peer as? TelegramUser, let botInfo = peer.botInfo { + if !botInfo.flags.contains(.worksWithGroups) { + continue + } + } + excludeIds.insert(peer.peer.id) + filteredLocal.append(peer) + } + } + + var global:[SelectPeerValue] = global.map { peer in + return SelectPeerValue(peer: peer, presence: presences[peer.id], subscribers: nil) + }.filter { peer in + if context.account.peerId != peer.peer.id, !excludeIds.contains(peer.peer.id) { + if let peer = peer.peer as? TelegramUser, let botInfo = peer.botInfo { + if !botInfo.flags.contains(.worksWithGroups) { + return false + } + } + return true + } else { + return false + } + } + + + if !global.isEmpty { + _ = previousGlobal.swap(global) + } else { + global = previousGlobal.with { $0 } + } + let updatedSearch = previousSearch.swap(search.request) != search.request + return (searchEntriesForPeers(local, global, context: context, isLoading: values.2), updatedSearch) } } } @@ -609,11 +1069,12 @@ final class SelectPeersControllerView: View, TokenizedProtocol { addSubview(separatorView) tokenView.delegate = self needsLayout = true - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = theme.colors.background separatorView.backgroundColor = theme.colors.border } @@ -645,6 +1106,21 @@ class SelectPeersController: ComposeViewController<[PeerId], Void, SelectPeersCo let interactions:SelectPeerInteraction = SelectPeerInteraction() private var previous:Atomic<[SelectPeerEntry]?> = Atomic(value:nil) private let tokenDisposable: MetaDisposable = MetaDisposable() + private let isNewGroup: Bool + private var limitsConfiguration: LimitsConfiguration? { + didSet { + if oldValue == nil { + requestUpdateCenterBar() + return + } + if let limitsConfiguration = limitsConfiguration { + self.interactions.update({$0.withUpdateLimit(limitsConfiguration.maxGroupMemberCount)}) + if limitsConfiguration.isEqual(to: oldValue!) == false { + requestUpdateCenterBar() + } + } + } + } func notify(with value: Any, oldValue: Any, animated: Bool) { if let value = value as? SelectPeerPresentation, let oldValue = oldValue as? SelectPeerPresentation { @@ -652,30 +1128,60 @@ class SelectPeersController: ComposeViewController<[PeerId], Void, SelectPeersCo let added = value.selected.subtracting(oldValue.selected) let removed = oldValue.selected.subtracting(value.selected) - for item in added { - genericView.tokenView.addToken(token: SearchToken(name: value.peers[item]?.compactDisplayTitle ?? tr(.peerDeletedUser), uniqueId: item.toInt64()), animated: animated) + if added.count == 0 && value.isLimitReached { + alert(for: mainWindow, info: L10n.composeCreateGroupLimitError) } - for item in removed { - genericView.tokenView.removeToken(uniqueId: item.toInt64(), animated: animated) + let tokens = added.map { + return SearchToken(name: value.peers[$0]?.compactDisplayTitle ?? L10n.peerDeletedUser, uniqueId: $0.toInt64()) } + genericView.tokenView.addTokens(tokens: tokens, animated: animated) + + let idsToRemove:[Int64] = removed.map { + $0.toInt64() + } + genericView.tokenView.removeTokens(uniqueIds: idsToRemove, animated: animated) self.nextEnabled(!value.selected.isEmpty) + + + + if let limits = limitsConfiguration { + let attributed = NSMutableAttributedString() + _ = attributed.append(string: L10n.telegramSelectPeersController, color: theme.colors.text, font: .medium(.title)) + _ = attributed.append(string: " ") + _ = attributed.append(string: "\(interactions.presentation.selected.count.formattedWithSeparator)/\(limits.maxSupergroupMemberCount.formattedWithSeparator)", color: theme.colors.grayText, font: .normal(.title)) + self.centerBarView.text = attributed + } else { + setCenterTitle(defaultBarTitle) + } + } } + override func requestUpdateCenterBar() { + notify(with: interactions.presentation, oldValue: interactions.presentation, animated: false) + } + func isEqual(to other: Notifable) -> Bool { if other is SelectPeersController { return true } return false } + + override func returnKeyAction() -> KeyHandlerResult { + if !self.interactions.presentation.selected.isEmpty { + return super.returnKeyAction() + } + return .rejected + } override func viewDidLoad() { super.viewDidLoad() self.nextEnabled(false) - let account = self.account + let context = self.context let interactions = self.interactions interactions.add(observer: self) @@ -694,25 +1200,34 @@ class SelectPeersController: ComposeViewController<[PeerId], Void, SelectPeersCo let previous = self.previous let initialSize = atomicSize - let transition = behavior.start(account: account, search: search.get() |> distinctUntilChanged |> map {SearchState(state: .None, request: $0)}) |> deliverOn(prepareQueue) |> map { entries -> TableUpdateTransition in - return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize.modify({$0}), animated: true, interactions:interactions) + let limitsSignal:Signal = isNewGroup ? context.account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration]) |> map { values -> LimitsConfiguration? in + return values.values[PreferencesKeys.limitsConfiguration] as? LimitsConfiguration + } : .single(nil) + + let first: Atomic = Atomic(value: true) + + let transition = combineLatest(queue: prepareQueue, behavior.start(context: context, search: search.get() |> distinctUntilChanged |> map {SearchState(state: .None, request: $0)}), limitsSignal) |> mapToQueue { entries, limits -> Signal<(TableUpdateTransition, LimitsConfiguration?), NoError> in + return prepareEntries(from: previous.swap(entries.0), to: entries.0, context: context, initialSize: initialSize.modify({$0}), animated: false, interactions: interactions, scroll: entries.1 ? .up(false) : .none(nil)) |> runOn(first.swap(false) ? .mainQueue() : prepareQueue) |> map { ($0, limits) } } |> deliverOnMainQueue - disposable.set(transition.start(next: { [weak self] transition in - self?.genericView.tableView.merge(with: transition) + disposable.set(transition.start(next: { [weak self] transition, limits in self?.readyOnce() + self?.genericView.tableView.merge(with: transition) + self?.limitsConfiguration = limits })) } + var tokenView:TokenizedView { return genericView.tokenView } - init(titles: ComposeTitles, account: Account, settings:SelectPeerSettings = [.contacts], excludePeerIds:[PeerId] = [], limit: Int32 = INT32_MAX) { + init(titles: ComposeTitles, context: AccountContext, settings:SelectPeerSettings = [.contacts], excludePeerIds:[PeerId] = [], limit: Int32 = INT32_MAX, isNewGroup: Bool = false) { self.behavior = SelectContactsBehavior(settings: settings, excludePeerIds: excludePeerIds, limit: limit) - super.init(titles: titles, account: account) + self.isNewGroup = isNewGroup + super.init(titles: titles, context: context) } override func firstResponder() -> NSResponder? { @@ -749,12 +1264,13 @@ fileprivate class SelectPeersView : View, TokenizedProtocol { addSubview(separatorView) tokenView.delegate = self backgroundColor = theme.colors.background - needsLayout = true - updateLocalizationAndTheme() + + updateLocalizationAndTheme(theme: theme) + layout() } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) separatorView.backgroundColor = theme.colors.border } @@ -762,7 +1278,7 @@ fileprivate class SelectPeersView : View, TokenizedProtocol { fileprivate override func layout() { super.layout() tableView.frame = NSMakeRect(0, 50, frame.width , frame.height - 50) - tokenView.frame = NSMakeRect(10, 10, frame.width - 20, frame.height - 50) + tokenView.frame = NSMakeRect(10, 10, frame.width - 20, 50) } func tokenizedViewDidChangedHeight(_ view: TokenizedView, height: CGFloat, animated: Bool) { @@ -786,9 +1302,9 @@ private class SelectPeersModalController : ModalViewController, Notifable { private let disposable:MetaDisposable = MetaDisposable() let interactions:SelectPeerInteraction = SelectPeerInteraction() private var previous:Atomic<[SelectPeerEntry]?> = Atomic(value:nil) - private let account:Account + private let context:AccountContext private let defaultTitle:String - private let confirmation:([PeerId])->Signal + private let confirmation:([PeerId])->Signal fileprivate let onComplete:Promise<[PeerId]> = Promise() private let completeDisposable = MetaDisposable() private let tokenDisposable = MetaDisposable() @@ -799,14 +1315,15 @@ private class SelectPeersModalController : ModalViewController, Notifable { let added = value.selected.subtracting(oldValue.selected) let removed = oldValue.selected.subtracting(value.selected) - for item in added { - genericView.tokenView.addToken(token: SearchToken(name: value.peers[item]?.compactDisplayTitle ?? tr(.peerDeletedUser), uniqueId: item.toInt64()), animated: animated) + let tokens = added.map { + return SearchToken(name: value.peers[$0]?.compactDisplayTitle ?? L10n.peerDeletedUser, uniqueId: $0.toInt64()) } + genericView.tokenView.addTokens(tokens: tokens, animated: animated) - for item in removed { - genericView.tokenView.removeToken(uniqueId: item.toInt64(), animated: animated) + let idsToRemove:[Int64] = removed.map { + $0.toInt64() } - + genericView.tokenView.removeTokens(uniqueIds: idsToRemove, animated: animated) modal?.interactions?.updateEnables(!value.selected.isEmpty) } @@ -821,7 +1338,19 @@ private class SelectPeersModalController : ModalViewController, Notifable { return tokenView } + override var dynamicSize: Bool { + return true + } + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 120, max(genericView.tableView.listHeight, 350))), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 120, max(genericView.tableView.listHeight, 350))), animated: animated) + } + } override func escapeKeyAction() -> KeyHandlerResult { return .rejected @@ -853,10 +1382,13 @@ private class SelectPeersModalController : ModalViewController, Notifable { override func viewDidLoad() { super.viewDidLoad() - let account = self.account + let context = self.context let interactions = self.interactions let initialSize = atomicSize + interactions.close = { [weak self] in + self?.close() + } interactions.add(observer: self) @@ -877,17 +1409,20 @@ private class SelectPeersModalController : ModalViewController, Notifable { if behavior.limit == 1 { singleAction = { [weak self] peer in - _ = (self?.account.postbox.modify { modifier -> Void in - updatePeers(modifier: modifier, peers: [peer], update: { _, updated -> Peer? in + _ = (context.account.postbox.transaction { transaction -> Void in + updatePeers(transaction: transaction, peers: [peer], update: { _, updated -> Peer? in return updated }) - })?.start() - self?.confirmSelected([peer.id], [peer]) + }).start() + self?.confirmSelected([peer.id], [peer]) } } - let transition = behavior.start(account: account, search: search.get() |> distinctUntilChanged) |> deliverOn(prepareQueue) |> map { entries -> TableUpdateTransition in - return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize.modify({$0}), animated: true, interactions:interactions, singleAction: singleAction) + let first: Atomic = Atomic(value: true) + + + let transition = behavior.start(context: context, search: search.get() |> distinctUntilChanged, linkInvation: linkInvation) |> mapToQueue { entries, updateSearch -> Signal in + return prepareEntries(from: previous.swap(entries), to: entries, context: context, initialSize: initialSize.modify({$0}), animated: false, interactions:interactions, singleAction: singleAction, scroll: updateSearch ? .up(false) : .none(nil)) |> runOn(first.swap(false) ? .mainQueue() : prepareQueue) } |> deliverOnMainQueue disposable.set(transition.start(next: { [weak self] transition in @@ -896,11 +1431,13 @@ private class SelectPeersModalController : ModalViewController, Notifable { })) } + private let linkInvation: (()->Void)? - init(account: Account, title:String, settings:SelectPeerSettings = [.contacts, .remote], excludePeerIds:[PeerId] = [], limit: Int32 = INT32_MAX, confirmation:@escaping([PeerId])->Signal, behavior: SelectPeersBehavior? = nil) { - self.account = account + init(context: AccountContext, title:String, settings:SelectPeerSettings = [.contacts, .remote], excludePeerIds:[PeerId] = [], limit: Int32 = INT32_MAX, confirmation:@escaping([PeerId])->Signal, behavior: SelectPeersBehavior? = nil, linkInvation:(()->Void)? = nil) { + self.context = context self.defaultTitle = title self.confirmation = confirmation + self.linkInvation = linkInvation self.behavior = behavior ?? SelectContactsBehavior(settings: settings, excludePeerIds: excludePeerIds, limit: limit) super.init(frame: NSMakeRect(0, 0, 360, 380)) @@ -911,11 +1448,11 @@ private class SelectPeersModalController : ModalViewController, Notifable { } func confirmSelected(_ peerIds:[PeerId], _ peers:[Peer]) { - let signal = account.postbox.modify { modifier -> Void in - updatePeers(modifier: modifier, peers: peers, update: { (_, updated) -> Peer? in + let signal = context.account.postbox.transaction { transaction -> Void in + updatePeers(transaction: transaction, peers: peers, update: { (_, updated) -> Peer? in return updated }) - } |> deliverOnMainQueue |> mapToSignal { [weak self] () -> Signal<[PeerId], Void> in + } |> deliverOnMainQueue |> mapToSignal { [weak self] () -> Signal<[PeerId], NoError> in if let strongSelf = self { return strongSelf.confirmation(peerIds) |> filter {$0} |> map { _ -> [PeerId] in return peerIds @@ -926,15 +1463,29 @@ private class SelectPeersModalController : ModalViewController, Notifable { onComplete.set(signal) } + override func returnKeyAction() -> KeyHandlerResult { + if !interactions.presentation.peers.values.isEmpty { + self.confirmSelected(Array(interactions.presentation.selected), Array(interactions.presentation.peers.values)) + } + + return .invoked + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: ModalHeaderData(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: self.defaultTitle), right: nil) + } + override var modalInteractions: ModalInteractions? { if behavior.limit == 1 { - return ModalInteractions(acceptTitle: tr(.modalCancel), drawBorder: true, height: 40) + return nil } else { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in + return ModalInteractions(acceptTitle: L10n.modalOK, accept: { [weak self] in if let interactions = self?.interactions { self?.confirmSelected(Array(interactions.presentation.selected), Array(interactions.presentation.peers.values)) } - }, cancelTitle: tr(.modalCancel), drawBorder: true, height: 40) + }, drawBorder: true, height: 50, singleButton: true) } } @@ -949,11 +1500,11 @@ private class SelectPeersModalController : ModalViewController, Notifable { } -func selectModalPeers(account:Account, title:String , settings:SelectPeerSettings = [.contacts, .remote], excludePeerIds:[PeerId] = [], limit: Int32 = INT_MAX, behavior: SelectPeersBehavior? = nil, confirmation:@escaping ([PeerId]) -> Signal = {_ in return .single(true) }) -> Signal<[PeerId], Void> { +func selectModalPeers(context: AccountContext, title:String , settings:SelectPeerSettings = [.contacts, .remote], excludePeerIds:[PeerId] = [], limit: Int32 = INT_MAX, behavior: SelectPeersBehavior? = nil, confirmation:@escaping ([PeerId]) -> Signal = {_ in return .single(true) }, linkInvation:(()->Void)? = nil) -> Signal<[PeerId], NoError> { - let modal = SelectPeersModalController(account: account, title: title, settings: settings, excludePeerIds: excludePeerIds, limit: limit, confirmation: confirmation, behavior: behavior) + let modal = SelectPeersModalController(context: context, title: title, settings: settings, excludePeerIds: excludePeerIds, limit: limit, confirmation: confirmation, behavior: behavior, linkInvation: linkInvation) - showModal(with: modal, for: mainWindow) + showModal(with: modal, for: context.window) return modal.onComplete.get() |> take(1) diff --git a/Telegram-Mac/SelectSizeRowItem.swift b/Telegram-Mac/SelectSizeRowItem.swift new file mode 100644 index 0000000000..7a6425eb57 --- /dev/null +++ b/Telegram-Mac/SelectSizeRowItem.swift @@ -0,0 +1,367 @@ +// +// SelectSizeRowItem.swift +// Telegram +// +// Created by keepcoder on 15/12/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + + + +class SelectSizeRowItem: GeneralRowItem { + + fileprivate let titles:[String]? + fileprivate let sizes: [Int32] + fileprivate var current: Int32 + fileprivate let initialCurrent: Int32 + fileprivate let selectAction:(Int)->Void + fileprivate let hasMarkers: Bool + init(_ initialSize: NSSize, stableId: AnyHashable, current: Int32, sizes: [Int32], hasMarkers: Bool, titles:[String]? = nil, viewType: GeneralViewType = .legacy, selectAction: @escaping(Int)->Void) { + self.sizes = sizes + self.titles = titles + self.initialCurrent = current + self.hasMarkers = hasMarkers + self.current = current + self.selectAction = selectAction + super.init(initialSize, height: titles != nil ? 70 : 40, stableId: stableId, viewType: viewType, inset: NSEdgeInsets(left: 30, right: 30)) + } + + override func viewClass() -> AnyClass { + return SelectSizeRowView.self + } + + +} + +private class SelectSizeRowView : TableRowView, ViewDisplayDelegate { + + private var availableRects:[NSRect] = [] + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.addSubview(containerView) + containerView.displayDelegate = self + containerView.userInteractionEnabled = false + containerView.isEventLess = true + layerContentsRedrawPolicy = .onSetNeedsDisplay + } + + + + + override func mouseDragged(with event: NSEvent) { + guard let item = item as? SelectSizeRowItem, !item.sizes.isEmpty else { + super.mouseDragged(with: event) + return + } + + if item.sizes.count == availableRects.count { + let point = containerView.convert(event.locationInWindow, from: nil) + for i in 0 ..< availableRects.count { + if NSPointInRect(point, availableRects[i]), item.current != i { + item.current = item.sizes[i] + containerView.needsDisplay = true + } + } + } + } + + override func mouseUp(with event: NSEvent) { + guard let item = item as? SelectSizeRowItem, !item.sizes.isEmpty else { + super.mouseUp(with: event) + return + } + + if item.sizes.count == availableRects.count { + let point = containerView.convert(event.locationInWindow, from: nil) + for i in 0 ..< availableRects.count { + if NSPointInRect(point, availableRects[i]), item.current != i { + item.selectAction(i) + return + } + } + if item.initialCurrent != item.current { + item.selectAction(item.sizes.firstIndex(of: item.current)!) + } + } + + } + + func _focus(_ size: NSSize) -> NSRect { + var focus = self.containerView.focus(size) + if let item = item as? SelectSizeRowItem { + switch item.viewType { + case .legacy: + if item.titles != nil { + focus.origin.y += 20 + } + case let .modern(_, insets): + if item.titles != nil { + focus.origin.y += (24 - insets.bottom) + } + } + + } + return focus + } + + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + guard let item = item as? SelectSizeRowItem, !item.sizes.isEmpty, containerView.layer == layer else {return} + + switch item.viewType { + case .legacy: + let minFontSize = CGFloat(item.sizes.first!) + let maxFontSize = CGFloat(item.sizes.last!) + + let minNode = TextNode.layoutText(.initialize(string: "A", color: theme.colors.text, font: .normal(min(minFontSize, 11))), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + + let maxNode = TextNode.layoutText(.initialize(string: "A", color: theme.colors.text, font: .normal(min(maxFontSize, 15))), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + + let minF = _focus(item.hasMarkers ? minNode.0.size : NSZeroSize) + let maxF = _focus(item.hasMarkers ? maxNode.0.size : NSZeroSize) + + if item.hasMarkers { + minNode.1.draw(NSMakeRect(item.inset.left, minF.minY, minF.width, minF.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + maxNode.1.draw(NSMakeRect(containerView.frame.width - item.inset.right - maxF.width, maxF.minY, maxF.width, maxF.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + let count = CGFloat(item.sizes.count) + + let insetBetweenFont: CGFloat = item.hasMarkers ? 20 : 0 + + let width: CGFloat = containerView.frame.width - (item.inset.left + minF.width) - (item.inset.right + maxF.width) - insetBetweenFont * 2 + + let per = floorToScreenPixels(backingScaleFactor, width / (count - 1)) + + ctx.setFillColor(theme.colors.accent.cgColor) + let lineSize = NSMakeSize(width, 2) + let lc = _focus(lineSize) + let minX = item.inset.left + minF.width + insetBetweenFont + + let interactionRect = NSMakeRect(minX, lc.minY, lc.width, lc.height) + + ctx.fill(interactionRect) + + let current: CGFloat = CGFloat(item.sizes.firstIndex(of: item.current) ?? 0) + + let selectSize = NSMakeSize(20, 20) + + let selectPoint = NSMakePoint(minX + floorToScreenPixels(backingScaleFactor, interactionRect.width / CGFloat(item.sizes.count - 1)) * current - selectSize.width / 2, _focus(selectSize).minY) + + ctx.setFillColor(theme.colors.grayText.cgColor) + let unMinX = selectPoint.x + selectSize.width / 2 + ctx.fill(NSMakeRect(unMinX, lc.minY, lc.maxX - unMinX, lc.height)) + + + for i in 0 ..< item.sizes.count { + let perSize = NSMakeSize(10, 10) + let perF = _focus(perSize) + let point = NSMakePoint(minX + per * CGFloat(i) - (i > 0 ? perSize.width / 2 : 0), perF.minY) + ctx.setFillColor(theme.colors.background.cgColor) + ctx.fill(NSMakeRect(point.x, point.y, perSize.width, perSize.height)) + + ctx.setFillColor(item.sizes[i] <= item.current ? theme.colors.accent.cgColor : theme.colors.grayText.cgColor) + ctx.fillEllipse(in: NSMakeRect(point.x + perSize.width/2 - 2, point.y + 3, 4, 4)) + + if let titles = item.titles, titles.count == item.sizes.count { + let title = titles[i] + let titleNode = TextNode.layoutText(.initialize(string: title, color: theme.colors.text, font: .normal(.text)), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + titleNode.1.draw(NSMakeRect(min(max(point.x - titleNode.0.size.width / 2 + 3, minX), frame.width - titleNode.0.size.width - minX), point.y - 15 - titleNode.0.size.height, titleNode.0.size.width, titleNode.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } + + if let titles = item.titles, titles.count == 1, let title = titles.first { + let perSize = NSMakeSize(10, 10) + let perF = _focus(perSize) + let titleNode = TextNode.layoutText(.initialize(string: title, color: theme.colors.text, font: .normal(.text)), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + titleNode.1.draw(NSMakeRect(_focus(titleNode.0.size).minX, perF.minY - 15 - titleNode.0.size.height, titleNode.0.size.width, titleNode.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fillEllipse(in: NSMakeRect(selectPoint.x, selectPoint.y, selectSize.width, selectSize.height)) + + ctx.setFillColor(.white) + ctx.fillEllipse(in: NSMakeRect(selectPoint.x + 1, selectPoint.y + 1, selectSize.width - 2, selectSize.height - 2)) + + resetCursorRects() + availableRects.removeAll() + + for i in 0 ..< item.sizes.count { + let perF = _focus(selectSize) + let point = NSMakePoint(interactionRect.minX + floorToScreenPixels(backingScaleFactor, interactionRect.width / (count - 1)) * CGFloat(i) - selectSize.width / 2, perF.minY) + let rect = NSMakeRect(point.x, point.y, selectSize.width, selectSize.height) + addCursorRect(rect, cursor: NSCursor.pointingHand) + availableRects.append(rect) + } + case let .modern(_, insets): + let minFontSize = CGFloat(item.sizes.first!) + let maxFontSize = CGFloat(item.sizes.last!) + + let minNode = TextNode.layoutText(.initialize(string: "A", color: theme.colors.text, font: .normal(min(minFontSize, 11))), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + + let maxNode = TextNode.layoutText(.initialize(string: "A", color: theme.colors.text, font: .normal(min(maxFontSize, 15))), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + + let minF = _focus(item.hasMarkers ? minNode.0.size : NSZeroSize) + let maxF = _focus(item.hasMarkers ? maxNode.0.size : NSZeroSize) + + if item.hasMarkers { + minNode.1.draw(NSMakeRect(insets.left, minF.minY, minF.width, minF.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + maxNode.1.draw(NSMakeRect(containerView.frame.width - insets.right - maxF.width, maxF.minY, maxF.width, maxF.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + let count = CGFloat(item.sizes.count) + + let insetBetweenFont: CGFloat = item.hasMarkers ? 20 : 0 + + let width: CGFloat = containerView.frame.width - (insets.left + minF.width) - (insets.right + maxF.width) - insetBetweenFont * 2 + + let per = floorToScreenPixels(backingScaleFactor, width / (count - 1)) + + ctx.setFillColor(theme.colors.accent.cgColor) + let lineSize = NSMakeSize(width, 2) + let lc = _focus(lineSize) + let minX = insets.left + minF.width + insetBetweenFont + + let interactionRect = NSMakeRect(minX, lc.minY, lc.width, lc.height) + + ctx.fill(interactionRect) + + let current: CGFloat = CGFloat(item.sizes.firstIndex(of: item.current) ?? 0) + + let selectSize = NSMakeSize(20, 20) + + let selectPoint = NSMakePoint(minX + floorToScreenPixels(backingScaleFactor, interactionRect.width / CGFloat(item.sizes.count - 1)) * current - selectSize.width / 2, _focus(selectSize).minY) + + ctx.setFillColor(theme.colors.grayText.cgColor) + let unMinX = selectPoint.x + selectSize.width / 2 + ctx.fill(NSMakeRect(unMinX, lc.minY, lc.maxX - unMinX, lc.height)) + + + for i in 0 ..< item.sizes.count { + let perSize = NSMakeSize(10, 10) + let perF = _focus(perSize) + let point = NSMakePoint(minX + per * CGFloat(i) - (i > 0 ? perSize.width / 2 : 0), perF.minY) + ctx.setFillColor(theme.colors.background.cgColor) + ctx.fill(NSMakeRect(point.x, point.y, perSize.width, perSize.height)) + + ctx.setFillColor(item.sizes[i] <= item.current ? theme.colors.accent.cgColor : theme.colors.grayText.cgColor) + ctx.fillEllipse(in: NSMakeRect(point.x + perSize.width/2 - 2, point.y + 3, 4, 4)) + + if let titles = item.titles, titles.count == item.sizes.count { + let title = titles[i] + let titleNode = TextNode.layoutText(.initialize(string: title, color: theme.colors.text, font: .normal(.text)), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + + var rect = NSMakeRect(min(max(point.x - titleNode.0.size.width / 2 + 3, minX), frame.width - titleNode.0.size.width - minX), point.y - 15 - titleNode.0.size.height, titleNode.0.size.width, titleNode.0.size.height) + + if i == titles.count - 1 { + rect.origin.x = min(rect.minX, (point.x + 5) - titleNode.0.size.width) + } + + titleNode.1.draw(rect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } + + if let titles = item.titles, titles.count == 1, let title = titles.first { + let titleNode = TextNode.layoutText(.initialize(string: title, color: theme.colors.text, font: .normal(.text)), backdorColor, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .left) + titleNode.1.draw(NSMakeRect(_focus(titleNode.0.size).minX, insets.top , titleNode.0.size.width, titleNode.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + + + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fillEllipse(in: NSMakeRect(selectPoint.x, selectPoint.y, selectSize.width, selectSize.height)) + + ctx.setFillColor(.white) + ctx.fillEllipse(in: NSMakeRect(selectPoint.x + 1, selectPoint.y + 1, selectSize.width - 2, selectSize.height - 2)) + + resetCursorRects() + availableRects.removeAll() + + for i in 0 ..< item.sizes.count { + let perF = _focus(selectSize) + let point = NSMakePoint(interactionRect.minX + floorToScreenPixels(backingScaleFactor, interactionRect.width / (count - 1)) * CGFloat(i) - selectSize.width / 2, perF.minY) + let rect = NSMakeRect(point.x, point.y, selectSize.width, selectSize.height) + addCursorRect(rect, cursor: NSCursor.pointingHand) + availableRects.append(rect) + } + } + + layout() + + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func layout() { + super.layout() + guard let item = item as? SelectSizeRowItem else { + return + } + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + case let .modern(position, _): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + } + } + + + override var firstResponder: NSResponder? { + return self + } + + override func updateColors() { + guard let item = item as? SelectSizeRowItem else { + return + } + containerView.backgroundColor = backdorColor + self.backgroundColor = item.viewType.rowBackground + + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? SelectSizeRowItem else { + return + } + switch item.viewType { + case .legacy: + self.containerView.setCorners([]) + case let .modern(position, _): + self.containerView.setCorners(position.corners) + } + + needsLayout = true + containerView.needsDisplay = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + + + + + +struct SliderSelectorItem : Equatable { + let value: Int32? + let localizedText: String? + init(value: Int32?, localizedText:String? = nil) { + assert(value != nil || localizedText != nil) + self.value = value + self.localizedText = localizedText + } +} + diff --git a/Telegram-Mac/SelectivePrivacySettingsController.swift b/Telegram-Mac/SelectivePrivacySettingsController.swift index 29b03462e4..32b9e5d1a9 100644 --- a/Telegram-Mac/SelectivePrivacySettingsController.swift +++ b/Telegram-Mac/SelectivePrivacySettingsController.swift @@ -8,21 +8,25 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox enum SelectivePrivacySettingsKind { case presence case groupInvitations case voiceCalls + case profilePhoto + case forwards + case phoneNumber } private enum SelectivePrivacySettingType { case everybody case contacts case nobody - + init(_ setting: SelectivePrivacySettings) { switch setting { case .disableEveryone: @@ -35,18 +39,27 @@ private enum SelectivePrivacySettingType { } } +enum SelectivePrivacySettingsPeerTarget { + case main + case callP2P +} + + private final class SelectivePrivacySettingsControllerArguments { - let account: Account - + let context: AccountContext + let updateType: (SelectivePrivacySettingType) -> Void - let openEnableFor: () -> Void - let openDisableFor: () -> Void - - init(account: Account, updateType: @escaping (SelectivePrivacySettingType) -> Void, openEnableFor: @escaping () -> Void, openDisableFor: @escaping () -> Void) { - self.account = account + let openEnableFor: (SelectivePrivacySettingsPeerTarget) -> Void + let openDisableFor: (SelectivePrivacySettingsPeerTarget) -> Void + let p2pMode: (SelectivePrivacySettingType) -> Void + let updatePhoneDiscovery:(Bool)->Void + init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openEnableFor: @escaping (SelectivePrivacySettingsPeerTarget) -> Void, openDisableFor: @escaping (SelectivePrivacySettingsPeerTarget) -> Void, p2pMode: @escaping(SelectivePrivacySettingType) -> Void, updatePhoneDiscovery:@escaping(Bool)->Void) { + self.context = context self.updateType = updateType self.openEnableFor = openEnableFor self.openDisableFor = openDisableFor + self.updatePhoneDiscovery = updatePhoneDiscovery + self.p2pMode = p2pMode } } @@ -57,327 +70,390 @@ private enum SelectivePrivacySettingsSection: Int32 { private func stringForUserCount(_ count: Int) -> String { if count == 0 { - return tr(.privacySettingsControllerAddUsers) + return tr(L10n.privacySettingsControllerAddUsers) } else { - return tr(.privacySettingsControllerUserCountCountable(count)) + return tr(L10n.privacySettingsControllerUserCountCountable(count)) } } private enum SelectivePrivacySettingsEntry: TableItemListNodeEntry { - case settingHeader(Int32, String) - case everybody(Int32, Bool) - case contacts(Int32, Bool) - case nobody(Int32, Bool) - case settingInfo(Int32, String) - case disableFor(Int32, String, Int) - case enableFor(Int32, String, Int) - case peersInfo(Int32) + case settingHeader(Int32, String, GeneralViewType) + case everybody(Int32, Bool, GeneralViewType) + case contacts(Int32, Bool, GeneralViewType) + case nobody(Int32, Bool, GeneralViewType) + case p2pAlways(Int32, Bool, GeneralViewType) + case p2pContacts(Int32, Bool, GeneralViewType) + case p2pNever(Int32, Bool, GeneralViewType) + case p2pHeader(Int32, String, GeneralViewType) + case p2pDesc(Int32, String, GeneralViewType) + case settingInfo(Int32, String, GeneralViewType) + case disableFor(Int32, String, Int, GeneralViewType) + case enableFor(Int32, String, Int, GeneralViewType) + case p2pDisableFor(Int32, String, Int, GeneralViewType) + case p2pEnableFor(Int32, String, Int, GeneralViewType) + case p2pPeersInfo(Int32, GeneralViewType) + case phoneDiscoveryHeader(Int32, String, GeneralViewType) + case phoneDiscoveryEverybody(Int32, String, Bool, GeneralViewType) + case phoneDiscoveryMyContacts(Int32, String, Bool, GeneralViewType) + case phoneDiscoveryInfo(Int32, String, GeneralViewType) + case peersInfo(Int32, GeneralViewType) case section(Int32) - + var stableId: Int32 { switch self { - case .settingHeader: - return 0 - case .everybody: - return 1 - case .contacts: - return 2 - case .nobody: - return 3 - case .settingInfo: - return 4 - case .disableFor: - return 5 - case .enableFor: - return 6 - case .peersInfo: - return 7 - case .section(let sectionId): - return (sectionId + 1) * 1000 - sectionId + case .settingHeader: return 0 + case .everybody: return 1 + case .contacts: return 2 + case .nobody: return 3 + case .settingInfo: return 4 + case .disableFor: return 5 + case .enableFor: return 6 + case .peersInfo: return 7 + case .p2pHeader: return 8 + case .p2pAlways: return 9 + case .p2pContacts: return 10 + case .p2pNever: return 11 + case .p2pDesc: return 12 + case .p2pDisableFor: return 13 + case .p2pEnableFor: return 14 + case .p2pPeersInfo: return 15 + case .phoneDiscoveryHeader: return 16 + case .phoneDiscoveryEverybody: return 17 + case .phoneDiscoveryMyContacts: return 18 + case .phoneDiscoveryInfo: return 19 + + case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId } } - + var index:Int32 { switch self { - case .settingHeader(let sectionId, _): - return (sectionId * 1000) + stableId - case .everybody(let sectionId, _): - return (sectionId * 1000) + stableId - case .contacts(let sectionId, _): - return (sectionId * 1000) + stableId - case .nobody(let sectionId, _): - return (sectionId * 1000) + stableId - case .settingInfo(let sectionId, _): - return (sectionId * 1000) + stableId - case .disableFor(let sectionId, _, _): - return (sectionId * 1000) + stableId - case .enableFor(let sectionId, _, _): - return (sectionId * 1000) + stableId - case .peersInfo(let sectionId): - return (sectionId * 1000) + stableId - case .section(let sectionId): - return (sectionId + 1) * 1000 - sectionId + case .settingHeader(let sectionId, _, _): return (sectionId * 1000) + stableId + case .everybody(let sectionId, _, _): return (sectionId * 1000) + stableId + case .contacts(let sectionId, _, _): return (sectionId * 1000) + stableId + case .nobody(let sectionId, _, _): return (sectionId * 1000) + stableId + case .settingInfo(let sectionId, _, _): return (sectionId * 1000) + stableId + case .disableFor(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .enableFor(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .peersInfo(let sectionId, _): return (sectionId * 1000) + stableId + case .p2pAlways(let sectionId, _, _): return (sectionId * 1000) + stableId + case .p2pContacts(let sectionId, _, _): return (sectionId * 1000) + stableId + case .p2pNever(let sectionId, _, _): return (sectionId * 1000) + stableId + case .p2pHeader(let sectionId, _, _): return (sectionId * 1000) + stableId + case .p2pDesc(let sectionId, _, _): return (sectionId * 1000) + stableId + case .p2pDisableFor(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .p2pEnableFor(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .p2pPeersInfo(let sectionId, _): return (sectionId * 1000) + stableId + case .phoneDiscoveryHeader(let sectionId, _, _): return (sectionId * 1000) + stableId + case .phoneDiscoveryEverybody(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .phoneDiscoveryMyContacts(let sectionId, _, _, _): return (sectionId * 1000) + stableId + case .phoneDiscoveryInfo(let sectionId, _, _): return (sectionId * 1000) + stableId + case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId } } - - static func ==(lhs: SelectivePrivacySettingsEntry, rhs: SelectivePrivacySettingsEntry) -> Bool { - switch lhs { - case let .settingHeader(sectionId, text): - if case .settingHeader(sectionId, text) = rhs { - return true - } else { - return false - } - case let .everybody(sectionId, value): - if case .everybody(sectionId, value) = rhs { - return true - } else { - return false - } - case let .contacts(sectionId, value): - if case .contacts(sectionId, value) = rhs { - return true - } else { - return false - } - case let .nobody(sectionId, value): - if case .nobody(sectionId, value) = rhs { - return true - } else { - return false - } - case let .settingInfo(sectionId, text): - if case .settingInfo(sectionId, text) = rhs { - return true - } else { - return false - } - case let .disableFor(sectionId, title, count): - if case .disableFor(sectionId, title, count) = rhs { - return true - } else { - return false - } - case let .enableFor(sectionId, title, count): - if case .enableFor(sectionId, title, count) = rhs { - return true - } else { - return false - } - case .peersInfo(let sectionId): - if case .peersInfo(sectionId) = rhs { - return true - } else { - return false - } - case .section(let sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } - + + static func <(lhs: SelectivePrivacySettingsEntry, rhs: SelectivePrivacySettingsEntry) -> Bool { return lhs.index < rhs.index } - + func item(_ arguments: SelectivePrivacySettingsControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .settingHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text, drawCustomSeparator: true, inset: NSEdgeInsets(left: 30.0, right: 30.0, top:2, bottom:6)) - case let .everybody(_, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsControllerEverbody), type: .selectable(stateback: { () -> Bool in - return value - }), action: { + case let .settingHeader(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .everybody(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerEverbody, type: .selectable(value), viewType: viewType, action: { arguments.updateType(.everybody) }) - case let .contacts(_, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsControllerMyContacts), type: .selectable(stateback: { () -> Bool in - return value - }), action: { + case let .contacts(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerMyContacts, type: .selectable(value), viewType: viewType, action: { arguments.updateType(.contacts) }) - case let .nobody(_, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsControllerNobody), type: .selectable(stateback: { () -> Bool in - return value - }), action: { + case let .nobody(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerNobody, type: .selectable(value), viewType: viewType, action: { arguments.updateType(.nobody) }) - case let .settingInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .disableFor(_, title, count): - - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stateback: { () -> String in - return stringForUserCount(count) - }), action: { - arguments.openDisableFor() + case let .p2pHeader(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .p2pAlways(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerP2pAlways, type: .selectable(value), viewType: viewType, action: { + arguments.p2pMode(.everybody) }) - - case let .enableFor(_, title, count): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stateback: { () -> String in - return stringForUserCount(count) - }), action: { - arguments.openEnableFor() + case let .p2pContacts(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerP2pContacts, type: .selectable(value), viewType: viewType, action: { + arguments.p2pMode(.contacts) + }) + case let .p2pNever(_, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsControllerP2pNever, type: .selectable(value), viewType: viewType, action: { + arguments.p2pMode(.nobody) + }) + case let .p2pDesc(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .settingInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .disableFor(_, title, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stringForUserCount(count)), viewType: viewType, action: { + arguments.openDisableFor(.main) + }) + case let .enableFor(_, title, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stringForUserCount(count)), viewType: viewType, action: { + arguments.openEnableFor(.main) + }) + case let .peersInfo(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsControllerPeerInfo, viewType: viewType) + case let .p2pDisableFor(_, title, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stringForUserCount(count)), viewType: viewType, action: { + arguments.openDisableFor(.callP2P) + }) + case let .p2pEnableFor(_, title, count, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .context(stringForUserCount(count)), viewType: viewType, action: { + arguments.openEnableFor(.callP2P) }) - case .peersInfo: - return GeneralTextRowItem(initialSize, stableId: stableId, text: tr(.privacySettingsControllerPeerInfo)) + case let .p2pPeersInfo(_, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: L10n.privacySettingsControllerPeerInfo, viewType: viewType) + case let .phoneDiscoveryHeader(_, title, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: title, viewType: viewType) + case let .phoneDiscoveryEverybody(_, title, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .selectable(selected), viewType: viewType, action: { + arguments.updatePhoneDiscovery(true) + }) + case let .phoneDiscoveryMyContacts(_, title, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: title, type: .selectable(selected), viewType: viewType, action: { + arguments.updatePhoneDiscovery(false) + }) + case let .phoneDiscoveryInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } private struct SelectivePrivacySettingsControllerState: Equatable { let setting: SelectivePrivacySettingType - let enableFor: Set - let disableFor: Set - + let enableFor: [PeerId: SelectivePrivacyPeer] + let disableFor: [PeerId: SelectivePrivacyPeer] + + let saving: Bool - - init(setting: SelectivePrivacySettingType, enableFor: Set, disableFor: Set, saving: Bool) { + + let callP2PMode: SelectivePrivacySettingType? + let callP2PEnableFor: [PeerId: SelectivePrivacyPeer] + let callP2PDisableFor: [PeerId: SelectivePrivacyPeer] + let phoneDiscoveryEnabled: Bool? + + init(setting: SelectivePrivacySettingType, enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer], saving: Bool, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [PeerId: SelectivePrivacyPeer], callP2PDisableFor: [PeerId: SelectivePrivacyPeer], phoneDiscoveryEnabled: Bool?) { self.setting = setting self.enableFor = enableFor self.disableFor = disableFor self.saving = saving + self.callP2PMode = callP2PMode + self.callP2PEnableFor = callP2PEnableFor + self.callP2PDisableFor = callP2PDisableFor + self.phoneDiscoveryEnabled = phoneDiscoveryEnabled + } - - static func ==(lhs: SelectivePrivacySettingsControllerState, rhs: SelectivePrivacySettingsControllerState) -> Bool { - if lhs.setting != rhs.setting { - return false - } - if lhs.enableFor != rhs.enableFor { - return false - } - if lhs.disableFor != rhs.disableFor { - return false - } - if lhs.saving != rhs.saving { - return false - } - - return true - } - + func withUpdatedSetting(_ setting: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving) + return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) } - - func withUpdatedEnableFor(_ enableFor: Set) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving) + + func withUpdatedEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) } - - func withUpdatedDisableFor(_ disableFor: Set) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving) + + func withUpdatedDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) } - + func withUpdatedSaving(_ saving: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: saving) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) + } + + func withUpdatedCallP2PMode(_ mode: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) + } + + func withUpdatedCallP2PEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) + } + + func withUpdatedCallP2PDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled) + } + func withUpdatedPhoneDiscoveryEnabled(_ phoneDiscoveryEnabled: Bool?) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, phoneDiscoveryEnabled: phoneDiscoveryEnabled) } + } private func selectivePrivacySettingsControllerEntries(kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState) -> [SelectivePrivacySettingsEntry] { var entries: [SelectivePrivacySettingsEntry] = [] - + var sectionId:Int32 = 1 entries.append(.section(sectionId)) sectionId += 1 - + let settingTitle: String - let settingInfoText: String + let settingInfoText: String? let disableForText: String let enableForText: String switch kind { case .presence: - settingTitle = tr(.privacySettingsControllerLastSeenHeader) - settingInfoText = tr(.privacySettingsControllerLastSeenDescription) - disableForText = tr(.privacySettingsControllerNeverShareWith) - enableForText = tr(.privacySettingsControllerAlwaysShareWith) + settingTitle = L10n.privacySettingsControllerLastSeenHeader + settingInfoText = L10n.privacySettingsControllerLastSeenDescription + disableForText = L10n.privacySettingsControllerNeverShareWith + enableForText = L10n.privacySettingsControllerAlwaysShareWith case .groupInvitations: - settingTitle = tr(.privacySettingsControllerGroupHeader) - settingInfoText = tr(.privacySettingsControllerGroupDescription) - disableForText = tr(.privacySettingsControllerNeverAllow) - enableForText = tr(.privacySettingsControllerAlwaysAllow) + settingTitle = L10n.privacySettingsControllerGroupHeader + settingInfoText = L10n.privacySettingsControllerGroupDescription + disableForText = L10n.privacySettingsControllerNeverAllow + enableForText = L10n.privacySettingsControllerAlwaysAllow case .voiceCalls: - settingTitle = tr(.privacySettingsControllerPhoneCallHeader) - settingInfoText = tr(.privacySettingsControllerPhoneCallDescription) - disableForText = tr(.privacySettingsControllerNeverAllow) - enableForText = tr(.privacySettingsControllerAlwaysAllow) + settingTitle = L10n.privacySettingsControllerPhoneCallHeader + settingInfoText = L10n.privacySettingsControllerPhoneCallDescription + disableForText = L10n.privacySettingsControllerNeverAllow + enableForText = L10n.privacySettingsControllerAlwaysAllow + case .profilePhoto: + settingTitle = L10n.privacySettingsControllerProfilePhotoWhoCanSeeMyPhoto + settingInfoText = L10n.privacySettingsControllerProfilePhotoCustomHelp + disableForText = L10n.privacySettingsControllerNeverShareWith + enableForText = L10n.privacySettingsControllerAlwaysShareWith + case .forwards: + settingTitle = L10n.privacySettingsControllerForwardsWhoCanForward + settingInfoText = L10n.privacySettingsControllerForwardsCustomHelp + disableForText = L10n.privacySettingsControllerNeverAllow + enableForText = L10n.privacySettingsControllerAlwaysAllow + case .phoneNumber: + if state.setting == .nobody { + settingInfoText = nil + } else { + settingInfoText = L10n.privacySettingsControllerPhoneNumberCustomHelp + } + settingTitle = L10n.privacySettingsControllerPhoneNumberWhoCanSeePhoneNumber + disableForText = L10n.privacySettingsControllerNeverShareWith + enableForText = L10n.privacySettingsControllerAlwaysShareWith + } + + entries.append(.settingHeader(sectionId, settingTitle, .textTopItem)) + + entries.append(.everybody(sectionId, state.setting == .everybody, .firstItem)) - entries.append(.settingHeader(sectionId, settingTitle)) - - entries.append(.everybody(sectionId, state.setting == .everybody)) - entries.append(.contacts(sectionId, state.setting == .contacts)) switch kind { - case .presence, .voiceCalls: - entries.append(.nobody(sectionId, state.setting == .nobody)) - case .groupInvitations: - break + case .presence, .voiceCalls, .forwards, .phoneNumber: + entries.append(.contacts(sectionId, state.setting == .contacts, .innerItem)) + entries.append(.nobody(sectionId, state.setting == .nobody, .lastItem)) + default: + entries.append(.contacts(sectionId, state.setting == .contacts, .lastItem)) + } + if let settingInfoText = settingInfoText { + entries.append(.settingInfo(sectionId, settingInfoText, .textBottomItem)) } - entries.append(.settingInfo(sectionId, settingInfoText)) + entries.append(.section(sectionId)) sectionId += 1 + if case .phoneNumber = kind, state.setting == .nobody { + entries.append(.phoneDiscoveryHeader(sectionId, L10n.privacyPhoneNumberSettingsDiscoveryHeader, .textTopItem)) + entries.append(.phoneDiscoveryEverybody(sectionId, L10n.privacySettingsControllerEverbody, state.phoneDiscoveryEnabled != false, .firstItem)) + entries.append(.phoneDiscoveryMyContacts(sectionId, L10n.privacySettingsControllerMyContacts, state.phoneDiscoveryEnabled == false, .lastItem)) + entries.append(.phoneDiscoveryInfo(sectionId, L10n.privacyPhoneNumberSettingsCustomDisabledHelp, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + } + + + switch state.setting { case .everybody: - entries.append(.disableFor(sectionId, disableForText, state.disableFor.count)) + entries.append(.disableFor(sectionId, disableForText, countForSelectivePeers(state.disableFor), .singleItem)) case .contacts: - entries.append(.disableFor(sectionId, disableForText, state.disableFor.count)) - entries.append(.enableFor(sectionId, enableForText, state.enableFor.count)) + entries.append(.disableFor(sectionId, disableForText, countForSelectivePeers(state.disableFor), .firstItem)) + entries.append(.enableFor(sectionId, enableForText, countForSelectivePeers(state.enableFor), .lastItem)) case .nobody: - entries.append(.enableFor(sectionId, enableForText, state.enableFor.count)) + entries.append(.enableFor(sectionId, enableForText, countForSelectivePeers(state.enableFor), .singleItem)) } - entries.append(.peersInfo(sectionId)) - + entries.append(.peersInfo(sectionId, .textBottomItem)) + + if let callSettings = state.callP2PMode { + switch kind { + case .voiceCalls: + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.p2pHeader(sectionId, L10n.privacySettingsControllerP2pHeader, .textTopItem)) + entries.append(.p2pAlways(sectionId, callSettings == .everybody, .firstItem)) + entries.append(.p2pContacts(sectionId, callSettings == .contacts, .innerItem)) + entries.append(.p2pNever(sectionId, callSettings == .nobody, .lastItem)) + entries.append(.p2pDesc(sectionId, L10n.privacySettingsControllerP2pDesc, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + + switch callSettings { + case .everybody: + entries.append(.p2pDisableFor(sectionId, disableForText, countForSelectivePeers(state.callP2PDisableFor), .singleItem)) + case .contacts: + entries.append(.p2pDisableFor(sectionId, disableForText, countForSelectivePeers(state.callP2PDisableFor), .firstItem)) + entries.append(.p2pEnableFor(sectionId, enableForText, countForSelectivePeers(state.callP2PEnableFor), .lastItem)) + case .nobody: + entries.append(.p2pEnableFor(sectionId, enableForText, countForSelectivePeers(state.callP2PEnableFor), .singleItem)) + } + entries.append(.p2pPeersInfo(sectionId, .textBottomItem)) + + default: + break + } + } + + return entries } fileprivate func prepareTransition(left:[SelectivePrivacySettingsEntry], right: [SelectivePrivacySettingsEntry], initialSize:NSSize, arguments:SelectivePrivacySettingsControllerArguments) -> TableUpdateTransition { - + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in return entry.item(arguments, initialSize: initialSize) } - + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } -class SelectivePrivacySettingsController: EditableViewController { +class SelectivePrivacySettingsController: TableViewController { private let kind: SelectivePrivacySettingsKind private let current: SelectivePrivacySettings - private let updated: (SelectivePrivacySettings) -> Void + private let updated: (SelectivePrivacySettings, SelectivePrivacySettings?, Bool?) -> Void private var savePressed:(()->Void)? - init(account: Account, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, updated: @escaping (SelectivePrivacySettings) -> Void) { + private let callSettings: SelectivePrivacySettings? + private let phoneDiscoveryEnabled: Bool? + init(_ context: AccountContext, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: SelectivePrivacySettings? = nil, phoneDiscoveryEnabled: Bool?, updated: @escaping (SelectivePrivacySettings, SelectivePrivacySettings?, Bool?) -> Void) { self.kind = kind self.current = current self.updated = updated - super.init(account) - } - - override func changeState() { - super.changeState() - savePressed?() - } - - override var normalString:String { - return "" + self.phoneDiscoveryEnabled = phoneDiscoveryEnabled + self.callSettings = callSettings + super.init(context) } - + + override func viewDidLoad() { - let account = self.account + super.viewDidLoad() + + let context = self.context let kind = self.kind let current = self.current let updated = self.updated - + let initialSize = self.atomicSize let previous:Atomic<[SelectivePrivacySettingsEntry]> = Atomic(value: []) - - var initialEnableFor = Set() - var initialDisableFor = Set() + + var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:] + var initialDisableFor: [PeerId: SelectivePrivacyPeer] = [:] + switch current { case let .disableEveryone(enableFor): initialEnableFor = enableFor @@ -387,149 +463,252 @@ class SelectivePrivacySettingsController: EditableViewController { case let .enableEveryone(disableFor): initialDisableFor = disableFor } - let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false) - + + var initialCallP2PEnableFor: [PeerId: SelectivePrivacyPeer] = [:] + var initialCallP2PDisableFor: [PeerId: SelectivePrivacyPeer] = [:] + + if let callCurrent = callSettings { + switch callCurrent { + case let .disableEveryone(enableFor): + initialCallP2PEnableFor = enableFor + initialCallP2PDisableFor = [:] + case let .enableContacts(enableFor, disableFor): + initialCallP2PEnableFor = enableFor + initialCallP2PDisableFor = disableFor + case let .enableEveryone(disableFor): + initialCallP2PEnableFor = [:] + initialCallP2PDisableFor = disableFor + } + + } + + + let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, phoneDiscoveryEnabled: phoneDiscoveryEnabled) + let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) let updateState: ((SelectivePrivacySettingsControllerState) -> SelectivePrivacySettingsControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - + var dismissImpl: (() -> Void)? var pushControllerImpl: ((ViewController) -> Void)? - + let actionsDisposable = DisposableSet() - + let updateSettingsDisposable = MetaDisposable() - actionsDisposable.add(updateSettingsDisposable) - - let arguments = SelectivePrivacySettingsControllerArguments(account: account, updateType: { type in + // actionsDisposable.add(updateSettingsDisposable) + + + let arguments = SelectivePrivacySettingsControllerArguments(context: context, updateType: { type in updateState { $0.withUpdatedSetting(type) } - }, openEnableFor: { + }, openEnableFor: { target in let title: String switch kind { case .presence: - title = tr(.privacySettingsControllerAlwaysShare) + title = L10n.privacySettingsControllerAlwaysShare case .groupInvitations: - title = tr(.privacySettingsControllerAlwaysAllow) + title = L10n.privacySettingsControllerAlwaysAllow case .voiceCalls: - title = tr(.privacySettingsControllerAlwaysAllow) + title = L10n.privacySettingsControllerAlwaysAllow + case .profilePhoto: + title = L10n.privacySettingsControllerAlwaysShare + case .forwards: + title = L10n.privacySettingsControllerAlwaysAllow + case .phoneNumber: + title = L10n.privacySettingsControllerAlwaysShareWith } - var peerIds = Set() + var peerIds:[PeerId: SelectivePrivacyPeer] = [:] updateState { state in peerIds = state.enableFor return state } - - - pushControllerImpl?(SelectivePrivacySettingsPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in + pushControllerImpl?(SelectivePrivacySettingsPeersController(context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in updateState { state in - return state.withUpdatedEnableFor(Set(updatedPeerIds)).withUpdatedDisableFor(state.disableFor.subtracting(Set(updatedPeerIds))) + switch target { + case .main: + var disableFor = state.disableFor + for (key, _) in updatedPeerIds { + disableFor.removeValue(forKey: key) + } + return state.withUpdatedEnableFor(updatedPeerIds).withUpdatedDisableFor(disableFor) + case .callP2P: + var callP2PDisableFor = state.callP2PDisableFor + //var disableFor = state.disableFor + for (key, _) in updatedPeerIds { + callP2PDisableFor.removeValue(forKey: key) + } + return state.withUpdatedCallP2PEnableFor(updatedPeerIds).withUpdatedCallP2PDisableFor(callP2PDisableFor) + } } })) - }, openDisableFor: { + }, openDisableFor: { target in let title: String switch kind { case .presence: - title = tr(.privacySettingsControllerNeverShareWith) + title = L10n.privacySettingsControllerNeverShareWith case .groupInvitations: - title = tr(.privacySettingsControllerNeverAllow) + title = L10n.privacySettingsControllerNeverAllow case .voiceCalls: - title = tr(.privacySettingsControllerNeverAllow) + title = L10n.privacySettingsControllerNeverAllow + case .profilePhoto: + title = L10n.privacySettingsControllerNeverShareWith + case .forwards: + title = L10n.privacySettingsControllerNeverAllow + case .phoneNumber: + title = L10n.privacySettingsControllerNeverShareWith } - var peerIds = Set() + var peerIds:[PeerId: SelectivePrivacyPeer] = [:] updateState { state in peerIds = state.disableFor return state } - pushControllerImpl?(SelectivePrivacySettingsPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in + pushControllerImpl?(SelectivePrivacySettingsPeersController(context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in updateState { state in - return state.withUpdatedDisableFor(Set(updatedPeerIds)).withUpdatedEnableFor(state.enableFor.subtracting(Set(updatedPeerIds))) + switch target { + case .main: + var enableFor = state.enableFor + for (key, _) in updatedPeerIds { + enableFor.removeValue(forKey: key) + } + return state.withUpdatedDisableFor(updatedPeerIds).withUpdatedEnableFor(enableFor) + case .callP2P: + var callP2PEnableFor = state.callP2PEnableFor + for (key, _) in updatedPeerIds { + callP2PEnableFor.removeValue(forKey: key) + } + return state.withUpdatedCallP2PDisableFor(updatedPeerIds).withUpdatedCallP2PEnableFor(callP2PEnableFor) + } } })) + }, p2pMode: { mode in + updateState { state in + return state.withUpdatedCallP2PMode(mode) + } + }, updatePhoneDiscovery: { value in + updateState { state in + return state.withUpdatedPhoneDiscoveryEnabled(value) + } }) - - let signal = statePromise.get() |> deliverOnMainQueue - |> map { [weak self] state -> TableUpdateTransition in - - if state.saving { - self?.state = .Edit - } else { - self?.state = initialState == state ? .Normal : .Edit - - self?.savePressed = { - var wasSaving = false - var settings: SelectivePrivacySettings? - updateState { state in - wasSaving = state.saving - switch state.setting { - case .everybody: - settings = SelectivePrivacySettings.enableEveryone(disableFor: state.disableFor) - case .contacts: - settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor) - case .nobody: - settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor) - } - return state.withUpdatedSaving(true) - } - - if let settings = settings, !wasSaving { - let type: UpdateSelectiveAccountPrivacySettingsType - switch kind { - case .presence: - type = .presence - case .groupInvitations: - type = .groupInvitations - case .voiceCalls: - type = .voiceCalls - } - - updateSettingsDisposable.set((updateSelectiveAccountPrivacySettings(account: account, type: type, settings: settings) |> deliverOnMainQueue).start(completed: { - updateState { state in - return state.withUpdatedSaving(false) - } - updated(settings) - dismissImpl?() - })) - } + + + savePressed = { + var wasSaving = false + var settings: SelectivePrivacySettings? + var callSettings: SelectivePrivacySettings? + var phoneDiscoveryEnabled: Bool? = nil + updateState { state in + phoneDiscoveryEnabled = state.phoneDiscoveryEnabled + wasSaving = state.saving + switch state.setting { + case .everybody: + settings = SelectivePrivacySettings.enableEveryone(disableFor: state.disableFor) + case .contacts: + settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor) + case .nobody: + settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor) + } + + if let mode = state.callP2PMode { + switch mode { + case .everybody: + callSettings = SelectivePrivacySettings.enableEveryone(disableFor: state.callP2PDisableFor) + case .contacts: + callSettings = SelectivePrivacySettings.enableContacts(enableFor: state.callP2PEnableFor, disableFor: state.callP2PDisableFor) + case .nobody: + callSettings = SelectivePrivacySettings.disableEveryone(enableFor: state.callP2PEnableFor) } } + + return state.withUpdatedSaving(true) + } + + if let settings = settings, !wasSaving { + let type: UpdateSelectiveAccountPrivacySettingsType + switch kind { + case .presence: + type = .presence + case .groupInvitations: + type = .groupInvitations + case .voiceCalls: + type = .voiceCalls + case .profilePhoto: + type = .profilePhoto + case .forwards: + type = .forwards + case .phoneNumber: + type = .phoneNumber + } + var updatePhoneDiscoverySignal: Signal = Signal.complete() + if let phoneDiscoveryEnabled = phoneDiscoveryEnabled { + updatePhoneDiscoverySignal = updatePhoneNumberDiscovery(account: context.account, value: phoneDiscoveryEnabled) + } + + let basic = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings) + + + updateSettingsDisposable.set(combineLatest(queue: .mainQueue(), updatePhoneDiscoverySignal, basic).start(completed: { + updateState { state in + return state.withUpdatedSaving(false) + } + updated(settings, callSettings, phoneDiscoveryEnabled) + dismissImpl?() + })) + } + } + + let signal = statePromise.get() |> deliverOnMainQueue + |> map { [weak self] state -> TableUpdateTransition in + + let title: String switch kind { case .presence: - title = tr(.privacySettingsLastSeen) + title = L10n.privacySettingsLastSeen case .groupInvitations: - title = tr(.privacySettingsGroups) + title = L10n.privacySettingsGroups case .voiceCalls: - title = tr(.privacySettingsVoiceCalls) + title = L10n.privacySettingsVoiceCalls + case .profilePhoto: + title = L10n.privacySettingsProfilePhoto + case .forwards: + title = L10n.privacySettingsForwards + case .phoneNumber: + title = L10n.privacySettingsPhoneNumber } - + self?.setCenterTitle(title) - + let entries = selectivePrivacySettingsControllerEntries(kind: kind, state: state) return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) } |> afterDisposed { actionsDisposable.dispose() } - + genericView.merge(with: signal) readyOnce() - + pushControllerImpl = { [weak self] c in self?.navigationController?.push(c) } dismissImpl = { [weak self] in - self?.navigationController?.back() + if self?.navigationController?.controller == self { + self?.navigationController?.back() + } } - + } - + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + override func didRemovedFromStack() { super.didRemovedFromStack() - + savePressed?() } } diff --git a/Telegram-Mac/SelectivePrivacySettingsPeersController.swift b/Telegram-Mac/SelectivePrivacySettingsPeersController.swift index 78fa256f69..12510e2155 100644 --- a/Telegram-Mac/SelectivePrivacySettingsPeersController.swift +++ b/Telegram-Mac/SelectivePrivacySettingsPeersController.swift @@ -8,19 +8,20 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox private final class SelectivePrivacyPeersControllerArguments { - let account: Account - + let context: AccountContext + let removePeer: (PeerId) -> Void let addPeer: () -> Void let openInfo:(Peer) -> Void - init(account: Account, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openInfo:@escaping(Peer) -> Void) { - self.account = account + init(context: AccountContext, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openInfo:@escaping(Peer) -> Void) { + self.context = context self.removePeer = removePeer self.addPeer = addPeer self.openInfo = openInfo @@ -43,185 +44,145 @@ private enum SelectivePrivacyPeersEntryStableId: Hashable { return 100 + Int(id) } } - - static func ==(lhs: SelectivePrivacyPeersEntryStableId, rhs: SelectivePrivacyPeersEntryStableId) -> Bool { - switch lhs { - case let .peer(peerId): - if case .peer(peerId) = rhs { - return true - } else { - return false - } - case .add: - if case .add = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } } private enum SelectivePrivacyPeersEntry: TableItemListNodeEntry { - case peerItem(Int32, Int32, Peer, ShortPeerDeleting?) - case addItem(Int32, Bool) + case addItem(Int32, Bool, GeneralViewType) + case peerItem(Int32, Int32, SelectivePrivacyPeer, ShortPeerDeleting?, GeneralViewType) case section(Int32) - + var stableId: SelectivePrivacyPeersEntryStableId { switch self { - case let .peerItem(_, _, peer, _): - return .peer(peer.id) + case let .peerItem(_, _, peer, _, _): + return .peer(peer.peer.id) case .addItem: return .add case let .section(sectionId): return .section(sectionId) } } - + var stableIndex:Int32 { switch self { - case let .peerItem(sectionId, index, _, _): - return (sectionId * 1000) + index + 100 - case .addItem(let sectionId, _): - return (sectionId * 1000) + 9999 + case .addItem(let sectionId, _, _): + return (sectionId * 1000) + 1_000_000 + case let .peerItem(sectionId, index, _, _, _): + return (sectionId * 1000) + index case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId } } - - static func ==(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { - switch lhs { - case let .peerItem(lhsSectionId, lhsIndex, lhsPeer, lhsEditing): - if case let .peerItem(rhsSectionId, rhsIndex, rhsPeer, rhsEditing) = rhs { - - if lhsSectionId != rhsSectionId { - return false - } - if lhsIndex != rhsIndex { - return false - } - if !lhsPeer.isEqual(rhsPeer) { - return false - } - if lhsEditing != rhsEditing { - return false - } - return true - } else { - return false - } - case let .addItem(sectionId, editing): - if case .addItem(sectionId, editing) = rhs { - return true - } else { - return false - } - case let .section(sectionId): - if case .section(sectionId) = rhs { - return true - } else { - return false - } - } - } - + static func <(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { return lhs.stableIndex < rhs.stableIndex } - + func item(_ arguments: SelectivePrivacyPeersControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .peerItem(_, _, peer, editing): - - - + case let .peerItem(_, _, peer, editing, viewType): + + + let interactionType:ShortPeerItemInteractionType if let editing = editing { - + interactionType = .deletable(onRemove: { peerId in arguments.removePeer(peerId) }, deletable: editing.editable) } else { interactionType = .plain } - - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: true, height:44, photoSize: NSMakeSize(32, 32), drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, action: { - arguments.openInfo(peer) + + var status: String? = nil + let count = peer.participantCount + if let count = count { + let count = Int(count) + let countValue = L10n.privacySettingsGroupMembersCountCountable(count) + status = countValue.replacingOccurrences(of: "\(count)", with: count.separatedNumber) + } + + + return ShortPeerRowItem(initialSize, peer: peer.peer, account: arguments.context.account, stableId: stableId, enabled: true, height:44, photoSize: NSMakeSize(30, 30), status: status, drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), interactionType: interactionType, generalType: .none, viewType: viewType, action: { + arguments.openInfo(peer.peer) + }, contextMenuItems: { + return .single([ContextMenuItem(L10n.confirmDelete, handler: { + arguments.removePeer(peer.peer.id) + })]) }) - case .addItem: - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(.privacySettingsPeerSelectAddNew), nameStyle: blueActionButton, type: .none, action: { + case let .addItem(_, _, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.privacySettingsPeerSelectAddUserOrGroup, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.addPeer() }) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } private struct SelectivePrivacyPeersControllerState: Equatable { let editing: Bool - + init() { self.editing = false } - + init(editing: Bool) { self.editing = editing } - + static func ==(lhs: SelectivePrivacyPeersControllerState, rhs: SelectivePrivacyPeersControllerState) -> Bool { if lhs.editing != rhs.editing { return false } return true } - + func withUpdatedEditing(_ editing: Bool) -> SelectivePrivacyPeersControllerState { return SelectivePrivacyPeersControllerState(editing: editing) } - + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> SelectivePrivacyPeersControllerState { return SelectivePrivacyPeersControllerState(editing: self.editing) } } -private func selectivePrivacyPeersControllerEntries(state: SelectivePrivacyPeersControllerState, peers: [Peer]) -> [SelectivePrivacyPeersEntry] { +private func selectivePrivacyPeersControllerEntries(state: SelectivePrivacyPeersControllerState, peers: [SelectivePrivacyPeer]) -> [SelectivePrivacyPeersEntry] { var entries: [SelectivePrivacyPeersEntry] = [] - + var sectionId:Int32 = 1 + + entries.append(.section(sectionId)) + sectionId += 1 + entries.append(.addItem(sectionId, state.editing, .singleItem)) + entries.append(.section(sectionId)) sectionId += 1 var index: Int32 = 0 - for peer in peers { + for (i, peer) in peers.enumerated() { var deleting:ShortPeerDeleting? = nil if state.editing { deleting = ShortPeerDeleting(editable: true) } - entries.append(.peerItem(sectionId, index, peer, deleting)) + entries.append(.peerItem(sectionId, index, peer, deleting, bestGeneralViewType(peers, for: i))) index += 1 } - entries.append(.addItem(sectionId, state.editing)) - + entries.append(.section(sectionId)) + sectionId += 1 + return entries } fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:SelectivePrivacyPeersControllerArguments) -> TableUpdateTransition { - + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in return entry.entry.item(arguments, initialSize: initialSize) } - + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) } @@ -229,84 +190,95 @@ fileprivate func prepareTransition(left:[AppearanceWrapperEntry { private let title:String - private let initialPeerIds:[PeerId] - private let updated:([PeerId])->Void + private let initialPeers:[PeerId: SelectivePrivacyPeer] + private let updated:([PeerId: SelectivePrivacyPeer])->Void private let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true) private let stateValue = Atomic(value: SelectivePrivacyPeersControllerState()) - init(account: Account, title: String, initialPeerIds: [PeerId], updated: @escaping ([PeerId]) -> Void) { + init(_ context: AccountContext, title: String, initialPeers: [PeerId: SelectivePrivacyPeer], updated: @escaping ([PeerId: SelectivePrivacyPeer]) -> Void) { self.title = title - self.initialPeerIds = initialPeerIds + self.initialPeers = initialPeers self.updated = updated - super.init(account) + super.init(context) } - + override func viewDidLoad() { super.viewDidLoad() - let account = self.account + genericView.getBackgroundColor = { + theme.colors.listBackground + } + + let context = self.context let title = self.title self.setCenterTitle(title) - let initialPeerIds = self.initialPeerIds + let initialPeers = self.initialPeers let updated = self.updated let initialSize = self.atomicSize - + let statePromise = self.statePromise let stateValue = self.stateValue - + let actionsDisposable = DisposableSet() - + let addPeerDisposable = MetaDisposable() actionsDisposable.add(addPeerDisposable) - + let removePeerDisposable = MetaDisposable() actionsDisposable.add(removePeerDisposable) - - let peersPromise = Promise<[Peer]>() - peersPromise.set(account.postbox.modify { modifier -> [Peer] in - var result: [Peer] = [] - for peerId in initialPeerIds { - if let peer = modifier.getPeer(peerId) { - result.append(peer) - } - } - return result + + let peersPromise = Promise<[SelectivePrivacyPeer]>() + peersPromise.set(context.account.postbox.transaction { transaction -> [SelectivePrivacyPeer] in + return Array(initialPeers.values) }) - + + var currentPeerIds:[PeerId] = [] - - let arguments = SelectivePrivacyPeersControllerArguments(account: account, removePeer: { memberId in + + let arguments = SelectivePrivacyPeersControllerArguments(context: context, removePeer: { memberId in let applyPeers: Signal = peersPromise.get() |> take(1) |> deliverOnMainQueue |> mapToSignal { peers -> Signal in var updatedPeers = peers for i in 0 ..< updatedPeers.count { - if updatedPeers[i].id == memberId { + if updatedPeers[i].peer.id == memberId { updatedPeers.remove(at: i) break } } peersPromise.set(.single(updatedPeers)) - updated(updatedPeers.map { $0.id }) - + + var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + for peer in updatedPeers { + updatedPeerDict[peer.peer.id] = peer + } + updated(updatedPeerDict) + return .complete() } - + removePeerDisposable.set(applyPeers.start()) + }, addPeer: { - - addPeerDisposable.set(selectModalPeers(account: account, title: title, settings: [.contacts], excludePeerIds: currentPeerIds, limit: 0, confirmation: {_ in return .single(true)}).start(next: { peerIds in - + + addPeerDisposable.set(selectModalPeers(context: context, title: title, excludePeerIds: currentPeerIds, limit: 0, behavior: SelectUsersAndGroupsBehavior(), confirmation: {_ in return .single(true)}).start(next: { peerIds in let applyPeers: Signal = peersPromise.get() |> take(1) - |> mapToSignal { peers -> Signal<[Peer], NoError> in - return account.postbox.modify { modifier -> [Peer] in + |> mapToSignal { peers -> Signal<[SelectivePrivacyPeer], NoError> in + return context.account.postbox.transaction { transaction -> [SelectivePrivacyPeer] in var updatedPeers = peers - var existingIds = Set(updatedPeers.map { $0.id }) + var existingIds = Set(updatedPeers.map { $0.peer.id }) for peerId in peerIds { - if let peer = modifier.getPeer(peerId), !existingIds.contains(peerId) { + if let peer = transaction.getPeer(peerId), !existingIds.contains(peerId) { existingIds.insert(peerId) - updatedPeers.append(peer) + var participantCount: Int32? + if let channel = peer as? TelegramChannel, case .group = channel.info { + if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { + participantCount = cachedData.participantsSummary.memberCount + } + } + + updatedPeers.append(SelectivePrivacyPeer(peer: peer, participantCount: participantCount)) } } return updatedPeers @@ -315,39 +287,48 @@ class SelectivePrivacySettingsPeersController: EditableViewController |> deliverOnMainQueue |> mapToSignal { updatedPeers -> Signal in peersPromise.set(.single(updatedPeers)) - updated(updatedPeers.map { $0.id }) + + var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + for peer in updatedPeers { + updatedPeerDict[peer.peer.id] = peer + } + updated(updatedPeerDict) + return .complete() } - + removePeerDisposable.set(applyPeers.start()) })) }, openInfo: { [weak self] peer in - self?.navigationController?.push(PeerInfoController(account: account, peer: peer)) + self?.navigationController?.push(PeerInfoController(context: context, peerId: peer.id)) }) - + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - + let signal = combineLatest(statePromise.get() |> deliverOnMainQueue, peersPromise.get() |> deliverOnMainQueue, appearanceSignal) |> map { state, peers, appearance -> TableUpdateTransition in - - currentPeerIds = peers.map({$0.id}) - + + currentPeerIds = peers.map { $0.peer.id } + let entries = selectivePrivacyPeersControllerEntries(state: state, peers: peers).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) - + } |> afterDisposed { actionsDisposable.dispose() } - genericView.merge(with: signal) + actionsDisposable.add(signal.start(next: { [weak self] transition in + guard let `self` = self else { return } + self.genericView.merge(with: transition) + self.readyOnce() + + })) - readyOnce() - } override func update(with state: ViewControllerState) { super.update(with: state) self.statePromise.set(stateValue.modify({$0.withUpdatedEditing(state == .Edit)})) } - + } diff --git a/Telegram-Mac/SenderController.swift b/Telegram-Mac/SenderController.swift index 3ae0d42fcf..c39e482dcf 100644 --- a/Telegram-Mac/SenderController.swift +++ b/Telegram-Mac/SenderController.swift @@ -7,11 +7,19 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import AVFoundation -class MediaSenderContainer { +import QuickLook +import TGUIKit + +let diceSymbol: String = "🎲" +let dartSymbol: String = "🎯" + + +class MediaSenderContainer : Equatable { let path:String let caption:String let isFile:Bool @@ -20,12 +28,31 @@ class MediaSenderContainer { self.caption = caption self.isFile = isFile } + + static func ==(lhs: MediaSenderContainer, rhs: MediaSenderContainer) -> Bool { + return lhs.path == rhs.path && lhs.caption == rhs.caption && lhs.isFile == rhs.isFile + } +} + +class ArchiverSenderContainer : MediaSenderContainer { + let files: [URL] + public init(path:String, caption:String = "", isFile:Bool = true, files: [URL] = []) { + self.files = files + super.init(path: path, caption: caption, isFile: isFile) + } + + static func ==(lhs: ArchiverSenderContainer, rhs: ArchiverSenderContainer) -> Bool { + return lhs.path == rhs.path && lhs.caption == rhs.caption && lhs.isFile == rhs.isFile && lhs.files == rhs.files + } } + class VoiceSenderContainer : MediaSenderContainer { fileprivate let data:RecordedAudioData - public init(data:RecordedAudioData) { + fileprivate let id:Int64? + public init(data:RecordedAudioData, id: Int64?) { self.data = data + self.id = id super.init(path: data.path) } @@ -34,9 +61,11 @@ class VoiceSenderContainer : MediaSenderContainer { class VideoMessageSenderContainer : MediaSenderContainer { fileprivate let duration:Int fileprivate let size: CGSize - public init(path:String, duration: Int, size: CGSize) { + fileprivate let id:Int64? + public init(path:String, duration: Int, size: CGSize, id: Int64?) { self.duration = duration self.size = size + self.id = id super.init(path: path, caption: "", isFile: false) } } @@ -44,21 +73,41 @@ class VideoMessageSenderContainer : MediaSenderContainer { class Sender: NSObject { - private static func previewForFile(_ path: String, account: Account) -> [TelegramMediaImageRepresentation] { + private static func previewForFile(_ path: String, isSecretRelated: Bool, account: Account) -> [TelegramMediaImageRepresentation] { var preview:[TelegramMediaImageRepresentation] = [] - let options = NSMutableDictionary() - options.setValue(90 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) +// if isDirectory(path) { +// let image = NSWorkspace.shared.icon(forFile: path) +// image.lockFocus() +// let imageRep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height)) +// image.unlockFocus() +// +// let compressedData: Data? = imageRep?.representation(using: .jpeg, properties: [:]) +// if let compressedData = compressedData { +// let resource = LocalFileMediaResource(fileId: arc4random64()) +// account.postbox.mediaBox.storeResourceData(resource.id, data: compressedData) +// preview.append(TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)) +// } +// return preview +// } - let colorQuality: Float = 0.6 - options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) - + let mimeType = MIMEType(path) + - if path.nsstring.pathExtension.hasPrefix("mp4") { + if mimeType.hasPrefix("video") { + + + + let options = NSMutableDictionary() + options.setValue(320 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + let colorQuality: Float = 0.3 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + let asset = AVAsset(url: URL(fileURLWithPath: path)) let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 200, height: 200) + imageGenerator.maximumSize = CGSize(width: 320, height: 320) imageGenerator.appliesPreferredTrackTransform = true let fullSizeImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) @@ -70,30 +119,41 @@ class Sender: NSObject { CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - let resource = LocalFileMediaResource(fileId: arc4random64()) + let resource = LocalFileMediaResource(fileId: arc4random64(), isSecretRelated: isSecretRelated) account.postbox.mediaBox.storeResourceData(resource.id, data: mutableData as Data) - preview.append(TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)) + preview.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource)) } } - - } + } else if mimeType.hasPrefix("image") || mimeType.hasSuffix("pdf"), let thumbData = try? Data(contentsOf: URL(fileURLWithPath: path)) { - - } else if let thumbData = try? Data(contentsOf: URL(fileURLWithPath: path)) { + let options = NSMutableDictionary() + options.setValue(320 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) - if let imageSource = CGImageSourceCreateWithData(thumbData as CFData, options) { - if let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { - let imageRep = NSBitmapImageRep(cgImage: image) - let options: [NSBitmapImageRep.PropertyKey: Any] = [NSBitmapImageRep.PropertyKey.compressionFactor: 0.6] - let compressedData: Data? = imageRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: options) + let colorQuality: Float = 0.7 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + let sourceOptions = NSMutableDictionary() + sourceOptions.setValue(320 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + sourceOptions.setObject(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as NSString) + sourceOptions.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + if let imageSource = CGImageSourceCreateWithData(thumbData as CFData, sourceOptions) { + let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, sourceOptions) + if let image = image { - if let compressedData = compressedData { - let resource = LocalFileMediaResource(fileId: arc4random64()) - account.postbox.mediaBox.storeResourceData(resource.id, data: compressedData) - preview.append(TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)) - } + let mutableData: CFMutableData = NSMutableData() as CFMutableData + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, options) { + CGImageDestinationSetProperties(colorDestination, nil) + CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let resource = LocalFileMediaResource(fileId: arc4random64(), isSecretRelated: isSecretRelated) + account.postbox.mediaBox.storeResourceData(resource.id, data: mutableData as Data) + preview.append(TelegramMediaImageRepresentation(dimensions: image.size.pixel, resource: resource)) + } + } } } @@ -101,35 +161,66 @@ class Sender: NSObject { return preview } - public static func enqueue( input:ChatTextInputState, account:Account, peerId:PeerId, replyId:MessageId?, disablePreview:Bool = false) ->Signal<[MessageId?],NoError> { + public static func enqueue( input:ChatTextInputState, context: AccountContext, peerId:PeerId, replyId:MessageId?, disablePreview:Bool = false, silent: Bool = false, atDate:Date? = nil, secretMediaPreview: TelegramMediaWebpage? = nil) ->Signal<[MessageId?],NoError> { var inset:Int = 0 var input:ChatTextInputState = input - let emojis = ObjcUtils.getEmojiFrom(input.inputText.fixed) + let emojis = Array(input.inputText.fixed.emojiString).map { String($0) }.compactMap {!$0.isEmpty ? $0 : nil} if input.attributes.isEmpty { input = ChatTextInputState(inputText: input.inputText.trimmed) } + + + if FastSettings.isPossibleReplaceEmojies { + let text = input.attributedString.stringEmojiReplacements + if text != input.attributedString { + input = ChatTextInputState(inputText: text.string, selectionRange: 0 ..< text.string.length, attributes: chatTextAttributes(from: text)) + } + } + + var mediaReference: AnyMediaReference? = nil + + + let dices = InteractiveEmojiConfiguration.with(appConfiguration: context.appConfiguration) + if dices.emojis.contains(input.inputText), peerId.namespace != Namespaces.Peer.SecretChat { + mediaReference = AnyMediaReference.standalone(media: TelegramMediaDice(emoji: input.inputText, value: nil)) + input = ChatTextInputState(inputText: "") + } + + if peerId.namespace == Namespaces.Peer.SecretChat, let media = secretMediaPreview { + mediaReference = AnyMediaReference.standalone(media: media) + } + + let parsingUrlType: ParsingType + if peerId.namespace != Namespaces.Peer.SecretChat { + parsingUrlType = [.Hashtags] + } else { + parsingUrlType = [.Links, .Hashtags] + } + let mapped = cut_long_message( input.inputText, 4096).map { message -> EnqueueMessage in let subState = input.subInputState(from: NSMakeRange(inset, message.length)) inset += message.length - var attributes:[MessageAttribute] = [TextEntitiesMessageAttribute(entities: subState.messageTextEntities)] + + var attributes:[MessageAttribute] = [TextEntitiesMessageAttribute(entities: subState.messageTextEntities(parsingUrlType))] + if let date = atDate { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(date.timeIntervalSince1970))) + } if disablePreview { attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews])) } - if FastSettings.isChannelMessagesMuted(peerId) { + if FastSettings.isChannelMessagesMuted(peerId) || silent { attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) } - - - return EnqueueMessage.message(text: subState.inputText, attributes: attributes, media: nil, replyToMessageId: replyId) + return EnqueueMessage.message(text: subState.inputText, attributes: attributes, mediaReference: mediaReference, replyToMessageId: replyId, localGroupingKey: nil) } - return enqueueMessages(account: account, peerId: peerId, messages: mapped) |> mapToSignal { value in + return enqueueMessages(context: context, peerId: peerId, messages: mapped) |> mapToSignal { value in if !emojis.isEmpty { - return saveUsedEmoji(emojis, postbox: account.postbox) |> map { + return saveUsedEmoji(emojis, postbox: context.account.postbox) |> map { return value } } @@ -138,13 +229,12 @@ class Sender: NSObject { } - public static func enqueue(message:EnqueueMessage, account:Account, peerId:PeerId) ->Signal<[MessageId?],NoError> { - return enqueueMessages(account: account, peerId: peerId, messages: [message]) + public static func enqueue(message:EnqueueMessage, context: AccountContext, peerId:PeerId) ->Signal<[MessageId?],NoError> { + return enqueueMessages(context: context, peerId: peerId, messages: [message]) |> deliverOnMainQueue - } - private static func generateMedia(for container:MediaSenderContainer, account: Account) -> Signal<(Media,String),Void> { + static func generateMedia(for container:MediaSenderContainer, account: Account, isSecretRelated: Bool) -> Signal<(Media,String), NoError> { return Signal { (subscriber) in let path = container.path @@ -154,14 +244,14 @@ class Sender: NSObject { arc4random_buf(&randomId, 8) func makeFileMedia(_ isMedia: Bool) { - let mimeType = MIMEType(path.nsstring.pathExtension) + let mimeType = MIMEType(path) let attrs:[TelegramMediaFileAttribute] = fileAttributes(for:mimeType, path:path, isMedia: isMedia) - let resource = LocalFileReferenceMediaResource(localFilePath:path,randomId:randomId, size: fileSize(path)) - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: resource, previewRepresentations: [], mimeType: mimeType, size: nil, attributes: attrs) + let resource: TelegramMediaResource = path.isDirectory ? LocalFileArchiveMediaResource(randomId: randomId, path: path) : LocalFileReferenceMediaResource(localFilePath:path,randomId:randomId, size: fs(path)) + media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewForFile(path, isSecretRelated: isSecretRelated, account: account), videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: attrs) } if !container.isFile { - let mimeType = MIMEType(path.nsstring.pathExtension.lowercased()) + let mimeType = MIMEType(path) if let container = container as? VoiceSenderContainer { let mimeType = voiceMime var attrs:[TelegramMediaFileAttribute] = [] @@ -169,13 +259,31 @@ class Sender: NSObject { if let waveformData = container.data.waveform { memoryWaveform = MemoryBuffer(data: waveformData) } + + let resource: TelegramMediaResource + if let id = container.id, let data = try? Data.init(contentsOf: URL(fileURLWithPath: path)) { + resource = LocalFileMediaResource(fileId: id, size: fileSize(path), isSecretRelated: isSecretRelated) + account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } else { + resource = LocalFileReferenceMediaResource(localFilePath:path, randomId: randomId, isUniquelyReferencedTemporaryFile: true, size: fs(path)) + } + attrs.append(.Audio(isVoice: true, duration: Int(container.data.duration), title: nil, performer: nil, waveform: memoryWaveform)) - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: LocalFileReferenceMediaResource(localFilePath:path,randomId: randomId, isUniquelyReferencedTemporaryFile: true, size: fileSize(path)), previewRepresentations: [], mimeType: mimeType, size: nil, attributes: attrs) + media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: attrs) } else if let container = container as? VideoMessageSenderContainer { var attrs:[TelegramMediaFileAttribute] = [] - - attrs.append(TelegramMediaFileAttribute.Video(duration: Int(container.duration), size: container.size, flags: [.instantRoundVideo])) - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: LocalFileReferenceMediaResource(localFilePath:path,randomId: randomId, isUniquelyReferencedTemporaryFile: true, size: fileSize(path)), previewRepresentations: previewForFile(path, account: account), mimeType: mimeType, size: nil, attributes: attrs) + + let resource: TelegramMediaResource + if let id = container.id, let data = try? Data.init(contentsOf: URL(fileURLWithPath: path)) { + resource = LocalFileMediaResource(fileId: id, size: fileSize(path), isSecretRelated: isSecretRelated) + account.postbox.mediaBox.storeResourceData(resource.id, data: data) + } else { + resource = LocalFileReferenceMediaResource(localFilePath:path, randomId: randomId, isUniquelyReferencedTemporaryFile: true, size: fs(path)) + } + + + attrs.append(TelegramMediaFileAttribute.Video(duration: Int(container.duration), size: PixelDimensions(container.size), flags: [.instantRoundVideo])) + media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewForFile(path, isSecretRelated: isSecretRelated, account: account), videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: attrs) } else if mimeType.hasPrefix("image/") && !mimeType.hasSuffix("gif"), let imageData = try? Data(contentsOf: URL(fileURLWithPath: path)) { @@ -196,16 +304,18 @@ class Sender: NSObject { if size.width / 10 > size.height || size.height < 40 { makeFileMedia(true) } else { - let imageRep = NSBitmapImageRep(cgImage: image) - let options: [NSBitmapImageRep.PropertyKey: Any] = [NSBitmapImageRep.PropertyKey.compressionFactor: Float(0.83)] + let data = NSImage(cgImage: image, size: image.size).tiffRepresentation(using: .jpeg, factor: 0.83) let path = NSTemporaryDirectory() + "tg_image_\(arc4random()).jpeg" - try? imageRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: options)?.write(to: URL(fileURLWithPath: path)) + if let data = data { + let imageRep = NSBitmapImageRep(data: data) + try? imageRep?.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])?.write(to: URL(fileURLWithPath: path)) + } - let scaledSize = size.aspectFilled(CGSize(width: 1280.0, height: 1280.0)) + let scaledSize = size.fitted(CGSize(width: 1280.0, height: 1280.0)) let resource = LocalFileReferenceMediaResource(localFilePath:path,randomId:randomId, isUniquelyReferencedTemporaryFile: true) - media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)]) + media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource)], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) } } else { @@ -216,13 +326,14 @@ class Sender: NSObject { } - } else if mimeType.hasSuffix("gif") { + } else if mimeType.hasPrefix("video") { let attrs:[TelegramMediaFileAttribute] = fileAttributes(for:mimeType, path:path, isMedia: true) + media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: LocalFileVideoMediaResource(randomId: randomId, path: container.path), previewRepresentations: previewForFile(path, isSecretRelated: isSecretRelated, account: account), videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: attrs) + } else if mimeType.hasPrefix("image/gif") { + let attrs:[TelegramMediaFileAttribute] = fileAttributes(for:mimeType, path:path, isMedia: true) - - - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: LocalFileGifMediaResource(randomId: arc4random64(), path: container.path), previewRepresentations: previewForFile(path, account: account), mimeType: "video/mp4", size: nil, attributes: attrs) + media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: LocalFileGifMediaResource(randomId: randomId, path: container.path), previewRepresentations: previewForFile(path, isSecretRelated: isSecretRelated, account: account), videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: attrs) } else { makeFileMedia(true) } @@ -262,66 +373,98 @@ class Sender: NSObject { } attrs.append(.Audio(isVoice: false, duration: Int(CMTimeGetSeconds(asset.duration)), title: defaultTitle, performer: defaultPerformer, waveform: nil)) } - if mime.hasPrefix("video"), isMedia { let asset = AVURLAsset(url: URL(fileURLWithPath: path)) let video = asset.tracks(withMediaType: AVMediaType.video).first let audio = asset.tracks(withMediaType: AVMediaType.audio).first if let video = video { - attrs.append(TelegramMediaFileAttribute.Video(duration: Int(CMTimeGetSeconds(asset.duration)), size: video.naturalSize, flags: [])) - } - if audio == nil { - attrs.append(TelegramMediaFileAttribute.Animated) + let size = video.naturalSize.applying(video.preferredTransform) + attrs.append(TelegramMediaFileAttribute.Video(duration: Int(CMTimeGetSeconds(asset.duration)), size: PixelDimensions(size), flags: [])) + attrs.append(TelegramMediaFileAttribute.FileName(fileName: path.nsstring.lastPathComponent.nsstring.deletingPathExtension.appending(".mp4"))) + if audio == nil, let size = fileSize(path), size < Int32(10 * 1024 * 1024), mime.hasSuffix("mp4") { + attrs.append(TelegramMediaFileAttribute.Animated) + } + if !mime.hasSuffix("mp4") { + attrs.append(.hintFileIsLarge) + } + return attrs } } if mime.hasSuffix("gif"), isMedia { - attrs.append(TelegramMediaFileAttribute.Video(duration: 0, size:TGGifConverter.gifDimensionSize(path), flags: [])) + attrs.append(TelegramMediaFileAttribute.Video(duration: 0, size:TGGifConverter.gifDimensionSize(path).pixel, flags: [])) attrs.append(TelegramMediaFileAttribute.Animated) attrs.append(TelegramMediaFileAttribute.FileName(fileName: path.nsstring.lastPathComponent.nsstring.deletingPathExtension.appending(".mp4"))) - } else { + } else if mime.hasPrefix("image"), let image = NSImage(contentsOf: URL(fileURLWithPath: path)) { + var size = image.size + if size.width == .infinity || size.height == .infinity { + size = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!.size + } + attrs.append(TelegramMediaFileAttribute.ImageSize(size: size.pixel)) attrs.append(TelegramMediaFileAttribute.FileName(fileName: path.nsstring.lastPathComponent)) + } else { + let getname:(String)->String = { path in + var result: String = path.nsstring.lastPathComponent + if result.contains("tg_temp_archive_") { + result = "Telegram Archive" + } + if path.isDirectory { + result += ".zip" + } + return result + } + attrs.append(TelegramMediaFileAttribute.FileName(fileName: getname(path))) } return attrs } - public static func forwardMessages(messageIds:[MessageId], account:Account, peerId:PeerId) -> Signal<[MessageId?], NoError> { + public static func forwardMessages(messageIds:[MessageId], context: AccountContext, peerId:PeerId, silent: Bool = false, atDate: Date? = nil) -> Signal<[MessageId?], NoError> { var fwdMessages:[EnqueueMessage] = [] let sorted = messageIds.sorted(by: >) + var attributes: [MessageAttribute] = [] + if FastSettings.isChannelMessagesMuted(peerId) || silent { + attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) + } + + if let date = atDate { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(date.timeIntervalSince1970))) + } for msgId in sorted { - fwdMessages.append(EnqueueMessage.forward(source: msgId)) + fwdMessages.append(EnqueueMessage.forward(source: msgId, grouping: messageIds.count > 1 ? .auto : .none, attributes: attributes)) } - return enqueueMessages(account: account, peerId: peerId, messages: fwdMessages.reversed()) + return enqueueMessages(context: context, peerId: peerId, messages: fwdMessages.reversed()) } - public static func shareContact(account:Account, peerId:PeerId, contact:TelegramUser) -> Signal<[MessageId?], NoError> { + public static func shareContact(context: AccountContext, peerId:PeerId, contact:TelegramUser) -> Signal<[MessageId?], NoError> { var attributes:[MessageAttribute] = [] if FastSettings.isChannelMessagesMuted(peerId) { attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) } - return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: "", attributes: attributes, media: TelegramMediaContact(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumber: contact.phone ?? "", peerId: contact.id), replyToMessageId: nil)]) + return enqueueMessages(context: context, peerId: peerId, messages: [EnqueueMessage.message(text: "", attributes: attributes, mediaReference: AnyMediaReference.standalone(media: TelegramMediaContact(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumber: contact.phone ?? "", peerId: contact.id, vCardData: nil)), replyToMessageId: nil, localGroupingKey: nil)]) } - public static func enqueue(media:[MediaSenderContainer], account:Account, peerId:PeerId, chatInteraction:ChatInteraction) ->Signal<[MessageId?],NoError> { - var senders:[Signal<[MessageId?],NoError>] = [] + public static func enqueue(media:[MediaSenderContainer], context: AccountContext, peerId:PeerId, chatInteraction:ChatInteraction, silent: Bool = false, atDate:Date? = nil) ->Signal<[MessageId?], NoError> { + var senders:[Signal<[MessageId?], NoError>] = [] + var attributes:[MessageAttribute] = [] - if FastSettings.isChannelMessagesMuted(peerId) { + if FastSettings.isChannelMessagesMuted(peerId) || silent { attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) } + if let date = atDate { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(date.timeIntervalSince1970))) + } for path in media { - senders.append(generateMedia(for: path, account: account) |> mapToSignal { media, caption -> Signal< [MessageId?], NoError> in - - return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: caption, attributes:attributes, media: media, replyToMessageId: chatInteraction.presentation.interfaceState.replyMessageId)]) - + senders.append(generateMedia(for: path, account: context.account, isSecretRelated: peerId.namespace == Namespaces.Peer.SecretChat) |> mapToSignal { media, caption -> Signal< [MessageId?], NoError> in + return enqueueMessages(context: context, peerId: peerId, messages: [EnqueueMessage.message(text: caption, attributes:attributes, mediaReference: AnyMediaReference.standalone(media: media), replyToMessageId: chatInteraction.presentation.interfaceState.replyMessageId, localGroupingKey: nil)]) }) } @@ -338,16 +481,61 @@ class Sender: NSObject { } } - public static func enqueue(media:TelegramMediaFile, account:Account, peerId:PeerId, chatInteraction:ChatInteraction) ->Signal<[MessageId?],NoError> { + public static func enqueue(media:Media, context: AccountContext, peerId:PeerId, chatInteraction:ChatInteraction, silent: Bool = false, atDate: Date? = nil) ->Signal<[MessageId?],NoError> { + return enqueue(media: [media], caption: ChatTextInputState(), context: context, peerId: peerId, chatInteraction: chatInteraction, silent: silent, atDate: atDate) + } + + public static func enqueue(media:[Media], caption: ChatTextInputState, context: AccountContext, peerId:PeerId, chatInteraction:ChatInteraction, isCollage: Bool = false, additionText: ChatTextInputState? = nil, silent: Bool = false, atDate: Date? = nil) ->Signal<[MessageId?],NoError> { - var attributes:[MessageAttribute] = [] - if FastSettings.isChannelMessagesMuted(peerId) { + + let parsingUrlType: ParsingType + if peerId.namespace != Namespaces.Peer.SecretChat { + parsingUrlType = [.Hashtags] + } else { + parsingUrlType = [.Links, .Hashtags] + } + + var attributes:[MessageAttribute] = [TextEntitiesMessageAttribute(entities: caption.messageTextEntities(parsingUrlType))] + let caption = Atomic(value: caption) + if FastSettings.isChannelMessagesMuted(peerId) || silent { attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) } + if let date = atDate { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(date.timeIntervalSince1970))) + } + + let replyId = chatInteraction.presentation.interfaceState.replyMessageId + + let localGroupingKey = isCollage ? arc4random64() : nil - return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: "", attributes: attributes, media: media, replyToMessageId: chatInteraction.presentation.interfaceState.replyMessageId)]) |> deliverOnMainQueue |> afterNext({ (value) -> Void in + var messages = media.map({EnqueueMessage.message(text: caption.swap(ChatTextInputState()).inputText, attributes: attributes, mediaReference: AnyMediaReference.standalone(media: $0), replyToMessageId: replyId, localGroupingKey: localGroupingKey)}) + if let input = additionText { + var inset:Int = 0 + var input:ChatTextInputState = input + + if input.attributes.isEmpty { + input = ChatTextInputState(inputText: input.inputText.trimmed) + } + let mapped = cut_long_message( input.inputText, 4096).map { message -> EnqueueMessage in + let subState = input.subInputState(from: NSMakeRange(inset, message.length)) + inset += message.length + + var attributes:[MessageAttribute] = [TextEntitiesMessageAttribute(entities: subState.messageTextEntities(parsingUrlType))] + + if FastSettings.isChannelMessagesMuted(peerId) || silent { + attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) + } + if let date = atDate { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(date.timeIntervalSince1970))) + } + + return EnqueueMessage.message(text: subState.inputText, attributes: attributes, mediaReference: nil, replyToMessageId: replyId, localGroupingKey: nil) + } + messages.insert(contentsOf: mapped, at: 0) + } + return enqueueMessages(context: context, peerId: peerId, messages: messages) |> deliverOnMainQueue |> afterNext { _ -> Void in chatInteraction.update({$0.updatedInterfaceState({$0.withUpdatedReplyMessageId(nil)})}) - }) + } |> take(1) } diff --git a/Telegram-Mac/SendingClockProgress.swift b/Telegram-Mac/SendingClockProgress.swift index 3f47233502..7075450a73 100644 --- a/Telegram-Mac/SendingClockProgress.swift +++ b/Telegram-Mac/SendingClockProgress.swift @@ -24,23 +24,46 @@ class SendingClockProgress: View { override init() { clockFrame = CALayer() - clockFrame.contents = theme.icons.chatSendingFrame - clockFrame.frame = NSMakeRect(0, 0, theme.icons.chatSendingFrame.backingSize.width, theme.icons.chatSendingFrame.backingSize.height) + clockFrame.contents = theme.icons.chatSendingOutFrame + clockFrame.frame = theme.icons.chatSendingOutFrame.backingBounds clockHour = CALayer() - clockHour.contents = theme.icons.chatSendingHour - clockHour.frame = NSMakeRect(0, 0, theme.icons.chatSendingHour.backingSize.width, theme.icons.chatSendingHour.backingSize.height) + clockHour.contents = theme.icons.chatSendingOutHour + clockHour.frame = theme.icons.chatSendingOutHour.backingBounds clockMin = CALayer() - clockMin.contents = theme.icons.chatSendingMin - clockMin.frame = NSMakeRect(0, 0, theme.icons.chatSendingMin.backingSize.width, theme.icons.chatSendingMin.backingSize.height) + clockMin.contents = theme.icons.chatSendingOutMin + clockMin.frame = theme.icons.chatSendingOutMin.backingBounds super.init(frame:NSMakeRect(0, 0, 12, 12)) - self.backgroundColor = .white + self.backgroundColor = .clear self.layer?.addSublayer(clockFrame) self.layer?.addSublayer(clockHour) self.layer?.addSublayer(clockMin) + + } + + override func layout() { + super.layout() + + clockMin.frame = focus(theme.icons.chatSendingOutMin.backingSize) + clockHour.frame = focus(theme.icons.chatSendingOutHour.backingSize) + } + + + func set(item: ChatRowItem) { + clockFrame.contents = item.presentation.chat.sendingFrameIcon(item) + clockHour.contents = item.presentation.chat.sendingHourIcon(item) + clockMin.contents = item.presentation.chat.sendingMinIcon(item) + viewDidMoveToWindow() + } + + func applyGray() { + clockFrame.contents = theme.icons.chatSendingOutFrame + clockHour.contents = theme.icons.chatSendingOutHour + clockMin.contents = theme.icons.chatSendingOutMin + viewDidMoveToWindow() } required init?(coder: NSCoder) { @@ -68,21 +91,27 @@ class SendingClockProgress: View { private func animateHour() -> Void { let animation = CABasicAnimation(keyPath: "transform.rotation.z") - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - animation.duration = (minute_duration * 4.0) + 0.6 - animation.repeatCount = .greatestFiniteMagnitude - animation.toValue = (Double.pi * 2.0) as NSNumber - clockHour.add(animation, forKey: "rotate") + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + animation.duration = 6 + animation.repeatCount = .infinity + animation.fromValue = 0 + animation.toValue = (Double.pi * 2.0) + animation.beginTime = 1.0 + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + clockHour.add(animation, forKey: "clockFrameAnimation") } private func animateMin() -> Void { let animation = CABasicAnimation(keyPath: "transform.rotation.z") - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - animation.duration = minute_duration - animation.repeatCount = .greatestFiniteMagnitude - animation.toValue = (Double.pi * 2.0) as NSNumber - clockMin.add(animation, forKey: "rotate") + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + animation.duration = 1 + animation.repeatCount = .infinity + animation.fromValue = 0 + animation.toValue = (Double.pi * 2.0) + animation.beginTime = 1.0 + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + clockMin.add(animation, forKey: "clockFrameAnimation") } public func stopAnimating() -> Void { @@ -95,8 +124,16 @@ class SendingClockProgress: View { clockMin.removeAllAnimations() } + + override func viewDidMoveToSuperview() { + if window != nil && superview != nil { + startAnimating() + } else { + stopAnimating() + } + } override func viewDidMoveToWindow() { - if window != nil { + if window != nil && superview != nil { startAnimating() } else { stopAnimating() diff --git a/Telegram-Mac/SeparatorRowItem.swift b/Telegram-Mac/SeparatorRowItem.swift index 5f59998e1e..cae917df22 100644 --- a/Telegram-Mac/SeparatorRowItem.swift +++ b/Telegram-Mac/SeparatorRowItem.swift @@ -25,6 +25,7 @@ class SeparatorRowItem: TableRowItem { let rightText:NSAttributedString? var border:BorderType = [.Right] let state:SeparatorBlockState + let action: (()->Void)? override var height: CGFloat { return h } @@ -33,9 +34,10 @@ class SeparatorRowItem: TableRowItem { return _stableId } - init(_ initialSize:NSSize, _ stableId:AnyHashable, string:String, right:String? = nil, state: SeparatorBlockState = .none, height:CGFloat = 20.0) { + init(_ initialSize:NSSize, _ stableId:AnyHashable, string:String, right:String? = nil, state: SeparatorBlockState = .none, height:CGFloat = 20.0, action: (()->Void)? = nil) { self._stableId = stableId self.h = height + self.action = action self.state = state text = .initialize(string: string, color: theme.colors.grayText, font:.normal(.short)) if let right = right { @@ -47,6 +49,9 @@ class SeparatorRowItem: TableRowItem { super.init(initialSize) } + override var instantlyResize: Bool { + return true + } override func viewClass() -> AnyClass { @@ -62,6 +67,7 @@ class SeparatorRowView: TableRowView { required init(frame frameRect: NSRect) { super.init(frame: frameRect) + layerContentsRedrawPolicy = .onSetNeedsDisplay } override var backdorColor: NSColor { @@ -72,11 +78,35 @@ class SeparatorRowView: TableRowView { fatalError("init(coder:) has not been implemented") } + override func mouseDown(with event: NSEvent) { + guard let item = item as? SeparatorRowItem else {return} + let point = convert(event.locationInWindow, from: nil) + + if let text = item.rightText { + let (layout, _) = TextNode.layoutText(maybeNode: stateText, text, nil, 1, .end, NSMakeSize(frame.width, frame.height), nil, false, .left) + + let rect = NSMakeRect(frame.width - 10 - layout.size.width, round((frame.height - layout.size.height)/2.0), layout.size.width, frame.height) + if NSPointInRect(point, rect) { + if let action = item.action { + action() + } else { + super.mouseDown(with: event) + } + } + } else { + super.mouseDown(with: event) + } + } + override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(layer.bounds) + } if let item = self.item as? SeparatorRowItem { let (layout, apply) = TextNode.layoutText(maybeNode: text, item.text, nil, 1, .end, NSMakeSize(frame.width, frame.height), nil,false, .left) @@ -84,13 +114,13 @@ class SeparatorRowView: TableRowView { if let text = item.rightText { textPoint = NSMakePoint(10, round((frame.height - layout.size.height)/2.0)) let (layout, apply) = TextNode.layoutText(maybeNode: stateText, text, nil, 1, .end, NSMakeSize(frame.width, frame.height), nil, false, .left) - apply.draw(NSMakeRect(frame.width - 10 - layout.size.width, round((frame.height - layout.size.height)/2.0), layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + apply.draw(NSMakeRect(frame.width - 10 - layout.size.width, round((frame.height - layout.size.height)/2.0), layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } else { textPoint = NSMakePoint(10, round((frame.height - layout.size.height)/2.0)) } - apply.draw(NSMakeRect(textPoint.x, textPoint.y, layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + apply.draw(NSMakeRect(textPoint.x, textPoint.y, layout.size.width, layout.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } } @@ -99,7 +129,7 @@ class SeparatorRowView: TableRowView { if let item = item as? SeparatorRowItem { self.border = item.border } - + needsDisplay = true } } diff --git a/Telegram-Mac/SettingsSearchRecentQueries.swift b/Telegram-Mac/SettingsSearchRecentQueries.swift new file mode 100644 index 0000000000..7abd26bd22 --- /dev/null +++ b/Telegram-Mac/SettingsSearchRecentQueries.swift @@ -0,0 +1,78 @@ +// +// SettingsSearchRecentQueries.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import SwiftSignalKit + +private struct SettingsSearchRecentQueryItemId { + public let rawValue: MemoryBuffer + + var value: Int64 { + return self.rawValue.makeData().withUnsafeBytes { $0.pointee } as Int64 + } + + init(_ rawValue: MemoryBuffer) { + self.rawValue = rawValue + } + + init(_ value: Int64) { + var value = value + self.rawValue = MemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) + } +} + +public final class RecentSettingsSearchQueryItem: OrderedItemListEntryContents { + public init() { + } + + public init(decoder: PostboxDecoder) { + } + + public func encode(_ encoder: PostboxEncoder) { + } +} + +func addRecentSettingsSearchItem(postbox: Postbox, item: SettingsSearchableItemId) { + let _ = (postbox.transaction { transaction in + let itemId = SettingsSearchRecentQueryItemId(item.index) + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, item: OrderedItemListEntry(id: itemId.rawValue, contents: RecentSettingsSearchQueryItem()), removeTailIfCountExceeds: 100) + }).start() +} + +func removeRecentSettingsSearchItem(postbox: Postbox, item: SettingsSearchableItemId) { + let _ = (postbox.transaction { transaction -> Void in + let itemId = SettingsSearchRecentQueryItemId(item.index) + transaction.removeOrderedItemListItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, itemId: itemId.rawValue) + }).start() +} + +func clearRecentSettingsSearchItems(postbox: Postbox) { + let _ = (postbox.transaction { transaction -> Void in + transaction.replaceOrderedItemListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, items: []) + }).start() +} + +func settingsSearchRecentItems(postbox: Postbox) -> Signal<[SettingsSearchableItemId], NoError> { + return postbox.combinedView(keys: [.orderedItemList(id: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems)]) + |> mapToSignal { view -> Signal<[SettingsSearchableItemId], NoError> in + return postbox.transaction { transaction -> [SettingsSearchableItemId] in + var result: [SettingsSearchableItemId] = [] + if let view = view.views[.orderedItemList(id: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems)] as? OrderedItemListView { + for item in view.items { + let index = SettingsSearchRecentQueryItemId(item.id).value + if let itemId = SettingsSearchableItemId(index: index) { + result.append(itemId) + } + } + } + return result + } + } +} + diff --git a/Telegram-Mac/SettingsSearchableItems.swift b/Telegram-Mac/SettingsSearchableItems.swift new file mode 100644 index 0000000000..1313ff224c --- /dev/null +++ b/Telegram-Mac/SettingsSearchableItems.swift @@ -0,0 +1,761 @@ +// +// SettingsSearchableItems.swift +// Telegram +// +// Created by Mikhail Filimonov on 02.12.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore + +enum SettingsSearchableItemIcon { + case profile + case proxy + case savedMessages + case calls + case stickers + case notifications + case privacy + case data + case appearance + case language + case watch + case wallet + case passport + case support + case faq +} + +extension SettingsSearchableItemIcon { + var thumb: CGImage? { + switch self { + case .profile: + return theme.icons.settingsProfile + case .proxy: + return theme.icons.settingsProxy + case .stickers: + return theme.icons.settingsStickers + case .notifications: + return theme.icons.settingsNotifications + case .privacy: + return theme.icons.settingsSecurity + case .data: + return theme.icons.settingsStorage + case .appearance: + return theme.icons.settingsAppearance + case .language: + return theme.icons.settingsLanguage + case .support: + return theme.icons.settingsAskQuestion + case .faq: + return theme.icons.settingsFaq + default: + return nil + } + } +} + + + + +enum SettingsSearchableItemId: Hashable { + case profile(Int32) + case proxy(Int32) + case savedMessages(Int32) + case calls(Int32) + case stickers(Int32) + case notifications(Int32) + case privacy(Int32) + case data(Int32) + case appearance(Int32) + case language(Int32) + case watch(Int32) + case passport(Int32) + case wallet(Int32) + case support(Int32) + case faq(Int32) + + private var namespace: Int32 { + switch self { + case .profile: + return 1 + case .proxy: + return 2 + case .savedMessages: + return 3 + case .calls: + return 4 + case .stickers: + return 5 + case .notifications: + return 6 + case .privacy: + return 7 + case .data: + return 8 + case .appearance: + return 9 + case .language: + return 10 + case .watch: + return 11 + case .passport: + return 12 + case .wallet: + return 13 + case .support: + return 14 + case .faq: + return 15 + } + } + + private var id: Int32 { + switch self { + case let .profile(id), + let .proxy(id), + let .savedMessages(id), + let .calls(id), + let .stickers(id), + let .notifications(id), + let .privacy(id), + let .data(id), + let .appearance(id), + let .language(id), + let .watch(id), + let .passport(id), + let .wallet(id), + let .support(id), + let .faq(id): + return id + } + } + + var index: Int64 { + return (Int64(self.namespace) << 32) | Int64(self.id) + } + + init?(index: Int64) { + let namespace = Int32((index >> 32) & 0x7fffffff) + let id = Int32(bitPattern: UInt32(index & 0xffffffff)) + switch namespace { + case 1: + self = .profile(id) + case 2: + self = .proxy(id) + case 3: + self = .savedMessages(id) + case 4: + self = .calls(id) + case 5: + self = .stickers(id) + case 6: + self = .notifications(id) + case 7: + self = .privacy(id) + case 8: + self = .data(id) + case 9: + self = .appearance(id) + case 10: + self = .language(id) + case 11: + self = .watch(id) + case 12: + self = .passport(id) + case 13: + self = .wallet(id) + case 14: + self = .support(id) + case 15: + self = .faq(id) + default: + return nil + } + } +} + +enum SettingsSearchableItemPresentation { + case push + case modal + case immediate + case dismiss +} + + + +struct SettingsSearchableItem { + let id: SettingsSearchableItemId + let title: String + let alternate: [String] + let icon: SettingsSearchableItemIcon + let breadcrumbs: [String] + let present: (AccountContext, NavigationViewController?, @escaping (SettingsSearchableItemPresentation, ViewController?) -> Void) -> Void +} + + + +func searchSettingsItems(items: [SettingsSearchableItem], query: String) -> [SettingsSearchableItem] { + let queryTokens = stringTokens(query.lowercased()) + + var result: [SettingsSearchableItem] = [] + for item in items { + var string = item.title + if !item.alternate.isEmpty { + for alternate in item.alternate { + let trimmed = alternate.trimmingCharacters(in: .whitespacesAndNewlines) + if !trimmed.isEmpty { + string += " \(trimmed)" + } + } + } + if item.breadcrumbs.count > 1 { + string += " \(item.breadcrumbs.suffix(from: 1).joined(separator: " "))" + } + + let tokens = stringTokens(string) + if matchStringTokens(tokens, with: queryTokens) { + result.append(item) + } + } + + return result +} + + +private func synonyms(_ string: String?) -> [String] { + if let string = string, !string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return string.components(separatedBy: "\n") + } else { + return [] + } +} + + + +private func stringTokens(_ string: String) -> [ValueBoxKey] { + 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 addedTokens = Set() + while tokenType != [] { + let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer) + + if currentTokenRange.location >= 0 && currentTokenRange.length != 0 { + let token = ValueBoxKey(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) + addedTokens.insert(token) + } + } + tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) + } + + return tokens +} + +private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool { + if other.isEmpty { + return false + } else if other.count == 1 { + let otherToken = other[0] + for token in tokens { + if otherToken.isPrefix(to: token) { + return true + } + } + } else { + for otherToken in other { + var found = false + for token in tokens { + if otherToken.isPrefix(to: token) { + found = true + break + } + } + if !found { + return false + } + } + return true + } + return false +} + + +private func profileSearchableItems(context: AccountContext, canAddAccount: Bool) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .profile + + let presentProfileSettings: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController?) -> Void, EditSettingsEntryTag?) -> Void = { context, present, itemTag in + EditAccountInfoController(context: context, focusOnItemTag: itemTag, f: { controller in + present(.push, controller) + }) + } + + var items: [SettingsSearchableItem] = [] + items.append(SettingsSearchableItem(id: .profile(0), title: L10n.editAccountTitle, alternate: synonyms(L10n.settingsSearchSynonymsEditProfileTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentProfileSettings(context, present, nil) + })) + + items.append(SettingsSearchableItem(id: .profile(1), title: L10n.accountSettingsBio, alternate: synonyms(L10n.settingsSearchSynonymsEditProfileTitle), icon: icon, breadcrumbs: [L10n.editAccountTitle], present: { context, _, present in + presentProfileSettings(context, present, .bio) + })) + items.append(SettingsSearchableItem(id: .profile(2), title: L10n.editAccountChangeNumber, alternate: synonyms(L10n.settingsSearchSynonymsEditProfilePhoneNumber), icon: icon, breadcrumbs: [L10n.editAccountTitle], present: { context, _, present in + present(.push, PhoneNumberIntroController.init(context)) + })) + items.append(SettingsSearchableItem(id: .profile(3), title: L10n.editAccountUsername, alternate: synonyms(L10n.settingsSearchSynonymsEditProfileUsername), icon: icon, breadcrumbs: [L10n.editAccountTitle], present: { context, _, present in + present(.push, UsernameSettingsViewController(context)) + })) + if canAddAccount { + items.append(SettingsSearchableItem(id: .profile(4), title: L10n.editAccountAddAccount, alternate: synonyms(L10n.settingsSearchSynonymsEditProfileAddAccount), icon: icon, breadcrumbs: [L10n.editAccountTitle], present: { context, _, present in + let isTestingEnvironment = NSApp.currentEvent?.modifierFlags.contains(.command) == true + context.sharedContext.beginNewAuth(testingEnvironment: isTestingEnvironment) + })) + } + items.append(SettingsSearchableItem(id: .profile(5), title: L10n.editAccountLogout, alternate: synonyms(L10n.settingsSearchSynonymsEditProfileLogout), icon: icon, breadcrumbs: [L10n.editAccountTitle], present: { context, navigationController, present in + showModal(with: LogoutViewController(context: context, f: { controller in + present(.push, controller) + }), for: context.window) + })) + return items +} + + + +private func stickerSearchableItems(context: AccountContext, archivedStickerPacks: [ArchivedStickerPackItem]?) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .stickers + + let presentStickerSettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController?) -> Void, InstalledStickerPacksEntryTag?) -> Void = { context, present, itemTag in + present(.push, InstalledStickerPacksController(context, focusOnItemTag: itemTag)) + } + + var items: [SettingsSearchableItem] = [] + + items.append(SettingsSearchableItem(id: .stickers(0), title: L10n.accountSettingsStickers, alternate: synonyms(L10n.settingsSearchSynonymsStickersTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentStickerSettings(context, present, nil) + })) + items.append(SettingsSearchableItem(id: .stickers(1), title: L10n.stickersSuggestStickers, alternate: synonyms(L10n.settingsSearchSynonymsStickersSuggestStickers), icon: icon, breadcrumbs: [L10n.accountSettingsStickers], present: { context, _, present in + presentStickerSettings(context, present, .suggestOptions) + })) + items.append(SettingsSearchableItem(id: .stickers(3), title: L10n.installedStickersTranding, alternate: synonyms(L10n.settingsSearchSynonymsStickersFeaturedPacks), icon: icon, breadcrumbs: [L10n.accountSettingsStickers], present: { context, _, present in + present(.push, FeaturedStickerPacksController(context)) + })) + items.append(SettingsSearchableItem(id: .stickers(4), title: L10n.installedStickersArchived, alternate: synonyms(L10n.settingsSearchSynonymsStickersArchivedPacks), icon: icon, breadcrumbs: [L10n.accountSettingsStickers], present: { context, _, present in + present(.push, ArchivedStickerPacksController(context, archived: nil, updatedPacks: { _ in })) + })) + return items +} + +private func notificationSearchableItems(context: AccountContext, settings: GlobalNotificationSettingsSet) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .notifications + + let presentNotificationSettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController?) -> Void, NotificationsAndSoundsEntryTag?) -> Void = { context, present, itemTag in + present(.push, NotificationPreferencesController(context, focusOnItemTag: itemTag)) + } + + return [ + SettingsSearchableItem(id: .notifications(0), title: L10n.accountSettingsNotifications, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentNotificationSettings(context, present, nil) + }), + SettingsSearchableItem(id: .notifications(2), title: L10n.notificationSettingsMessagesPreview, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsMessageNotificationsPreview), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications, L10n.notificationSettingsToggleNotificationsHeader], present: { context, _, present in + presentNotificationSettings(context, present, .messagePreviews) + }), +// SettingsSearchableItem(id: .notifications(18), title: L10n.notificationSettingsIncludeGroups, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsBadgeIncludeMutedPublicGroups), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications, L10n.notificationSettingsBadgeHeader], present: { context, _, present in +// presentNotificationSettings(context, present, .includePublicGroups) +// }), + SettingsSearchableItem(id: .notifications(19), title: L10n.notificationSettingsIncludeChannels, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsBadgeIncludeMutedChannels), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications, L10n.notificationSettingsBadgeHeader], present: { context, _, present in + presentNotificationSettings(context, present, .includeChannels) + }), + SettingsSearchableItem(id: .notifications(20), title: L10n.notificationSettingsCountUnreadMessages, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsBadgeCountUnreadMessages), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications, L10n.notificationSettingsBadgeHeader], present: { context, _, present in + presentNotificationSettings(context, present, .unreadCountCategory) + }), + SettingsSearchableItem(id: .notifications(21), title: L10n.notificationSettingsContactJoined, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsContactJoined), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications], present: { context, _, present in + presentNotificationSettings(context, present, .joinedNotifications) + }), + SettingsSearchableItem(id: .notifications(22), title: L10n.notificationSettingsResetNotifications, alternate: synonyms(L10n.settingsSearchSynonymsNotificationsResetAllNotifications), icon: icon, breadcrumbs: [L10n.accountSettingsNotifications], present: { context, _, present in + presentNotificationSettings(context, present, .reset) + }) + ] +} + +private func privacySearchableItems(context: AccountContext, privacySettings: AccountPrivacySettings?) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .privacy + + let presentPrivacySettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController?) -> Void, PrivacyAndSecurityEntryTag?) -> Void = { context, present, itemTag in + present(.push, PrivacyAndSecurityViewController(context, initialSettings: (privacySettings, nil), focusOnItemTag: itemTag)) + } + + let presentSelectivePrivacySettings: (AccountContext, SelectivePrivacySettingsKind, @escaping (SettingsSearchableItemPresentation, ViewController?) -> Void) -> Void = { context, kind, present in + let privacySignal: Signal + if let privacySettings = privacySettings { + privacySignal = .single(privacySettings) + } else { + privacySignal = requestAccountPrivacySettings(account: context.account) + } + let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError> + if case .voiceCalls = kind { + callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.voiceCallSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration])) + |> take(1) + |> map { sharedData, view -> (VoiceCallSettings, VoipConfiguration)? in + let voiceCallSettings: VoiceCallSettings = sharedData.entries[ApplicationSharedPreferencesKeys.voiceCallSettings] as? VoiceCallSettings ?? .defaultSettings + let voipConfiguration = view.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue + return (voiceCallSettings, voipConfiguration) + } + } else { + callsSignal = .single(nil) + } + + let _ = (combineLatest(privacySignal, callsSignal) + |> deliverOnMainQueue).start(next: { info, callSettings in + let current: SelectivePrivacySettings + switch kind { + case .presence: + current = info.presence + case .groupInvitations: + current = info.groupInvitations + case .voiceCalls: + current = info.voiceCalls + case .profilePhoto: + current = info.profilePhoto + case .forwards: + current = info.forwards + case .phoneNumber: + current = info.phoneNumber + } + + present(.push, SelectivePrivacySettingsController(context, kind: kind, current: current, callSettings: kind == .voiceCalls ? info.voiceCallsP2P : nil, phoneDiscoveryEnabled: nil, updated: { updated, updatedCallSettings, _ in })) + }) + } + + + + let passcodeTitle: String = L10n.privacySettingsPasscode + + + return [ + SettingsSearchableItem(id: .privacy(0), title: L10n.accountSettingsPrivacyAndSecurity, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentPrivacySettings(context, present, nil) + }), + SettingsSearchableItem(id: .privacy(1), title: L10n.privacySettingsBlockedUsers, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyBlockedUsers), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + present(.push, BlockedPeersViewController(context)) + }), + SettingsSearchableItem(id: .privacy(2), title: L10n.privacySettingsLastSeen, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyLastSeen), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentSelectivePrivacySettings(context, .presence, present) + }), + SettingsSearchableItem(id: .privacy(3), title: L10n.privacySettingsProfilePhoto, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyProfilePhoto), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentSelectivePrivacySettings(context, .profilePhoto, present) + }), + SettingsSearchableItem(id: .privacy(4), title: L10n.privacySettingsForwards, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyForwards), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentSelectivePrivacySettings(context, .forwards, present) + }), + SettingsSearchableItem(id: .privacy(5), title: L10n.privacySettingsVoiceCalls, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyCalls), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentSelectivePrivacySettings(context, .voiceCalls, present) + }), + SettingsSearchableItem(id: .privacy(6), title: L10n.privacySettingsGroups, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyGroupsAndChannels), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentSelectivePrivacySettings(context, .groupInvitations, present) + }), + SettingsSearchableItem(id: .privacy(7), title: passcodeTitle, alternate: [], icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + present(.push, PasscodeSettingsViewController(context)) + }), + SettingsSearchableItem(id: .privacy(8), title: L10n.privacySettingsTwoStepVerification, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyTwoStepAuth), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, navigation, present in + present(.push, twoStepVerificationUnlockController(context: context, mode: .access(nil), presentController: { controller, root, animated in + guard let navigation = navigation else {return} + if root { + navigation.removeUntil(PrivacyAndSecurityViewController.self) + } + if !animated { + navigation.stackInsert(controller, at: navigation.stackCount) + } else { + navigation.push(controller) + } + })) + }), + SettingsSearchableItem(id: .privacy(9), title: L10n.privacySettingsActiveSessions, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyAuthSessions), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + present(.push, RecentSessionsController(context)) + }), + SettingsSearchableItem(id: .privacy(10), title: L10n.privacySettingsDeleteAccountHeader, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyDeleteAccountIfAwayFor), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentPrivacySettings(context, present, .accountTimeout) + }), + SettingsSearchableItem(id: .privacy(14), title: L10n.suggestFrequentContacts, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyDataTopPeers), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentPrivacySettings(context, present, .topPeers) + }), + SettingsSearchableItem(id: .privacy(15), title: L10n.privacyAndSecurityClearCloudDrafts, alternate: synonyms(L10n.settingsSearchSynonymsPrivacyDataDeleteDrafts), icon: icon, breadcrumbs: [L10n.accountSettingsPrivacyAndSecurity], present: { context, _, present in + presentPrivacySettings(context, present, .cloudDraft) + }) + ] +} + +private func dataSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .data + + let presentDataSettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController?) -> Void, DataAndStorageEntryTag?) -> Void = { context, present, itemTag in + present(.push, DataAndStorageViewController(context, focusOnItemTag: itemTag)) + } + + return [ + SettingsSearchableItem(id: .data(0), title: L10n.accountSettingsDataAndStorage, alternate: synonyms(L10n.settingsSearchSynonymsDataTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentDataSettings(context, present, nil) + }), + SettingsSearchableItem(id: .data(1), title: L10n.dataAndStorageStorageUsage, alternate: synonyms(L10n.settingsSearchSynonymsDataStorageTitle), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage], present: { context, _, present in + present(.push, StorageUsageController(context)) + }), + SettingsSearchableItem(id: .data(2), title: L10n.storageUsageKeepMedia, alternate: synonyms(L10n.settingsSearchSynonymsDataStorageKeepMedia), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage, L10n.dataAndStorageStorageUsage], present: { context, _, present in + present(.push, StorageUsageController(context)) + }), + SettingsSearchableItem(id: .data(3), title: L10n.logoutOptionsClearCacheTitle, alternate: synonyms(L10n.settingsSearchSynonymsDataStorageClearCache), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage, L10n.dataAndStorageStorageUsage], present: { context, _, present in + present(.push, StorageUsageController(context)) + }), + SettingsSearchableItem(id: .data(4), title: L10n.dataAndStorageNetworkUsage, alternate: synonyms(L10n.settingsSearchSynonymsDataNetworkUsage), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage], present: { context, _, present in + present(.push, networkUsageStatsController(context: context)) + }), + SettingsSearchableItem(id: .data(7), title: L10n.dataAndStorageAutomaticDownloadReset, alternate: synonyms(L10n.settingsSearchSynonymsDataAutoDownloadReset), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage], present: { context, _, present in + presentDataSettings(context, present, .automaticDownloadReset) + }), + SettingsSearchableItem(id: .data(8), title: L10n.dataAndStorageAutoplayGIFs, alternate: synonyms(L10n.settingsSearchSynonymsDataAutoplayGifs), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage, L10n.dataAndStorageAutoplayHeader], present: { context, _, present in + presentDataSettings(context, present, .autoplayGifs) + }), + SettingsSearchableItem(id: .data(9), title: L10n.dataAndStorageAutoplayVideos, alternate: synonyms(L10n.settingsSearchSynonymsDataAutoplayVideos), icon: icon, breadcrumbs: [L10n.accountSettingsDataAndStorage, L10n.dataAndStorageAutoplayHeader], present: { context, _, present in + presentDataSettings(context, present, .autoplayVideos) + }) + ] +} + +private func proxySearchableItems(context: AccountContext, servers: [ProxyServerSettings]) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .proxy + + let presentProxySettings: (AccountContext, @escaping(SettingsSearchableItemPresentation, ViewController?) -> Void) -> Void = { context, present in + let controller = proxyListController(accountManager: context.sharedContext.accountManager, network: context.account.network, share: { servers in + var message: String = "" + for server in servers { + message += server.link + "\n\n" + } + message = message.trimmed + + showModal(with: ShareModalController(ShareLinkObject(context, link: message)), for: mainWindow) + }, pushController: { controller in + present(.push, controller) + }) + present(.push, controller) + } + + var items: [SettingsSearchableItem] = [] + items.append(SettingsSearchableItem(id: .proxy(0), title: L10n.accountSettingsProxy, alternate: synonyms(L10n.settingsSearchSynonymsProxyTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentProxySettings(context, present) + })) + items.append(SettingsSearchableItem(id: .proxy(1), title: L10n.proxySettingsAddProxy, alternate: synonyms(L10n.settingsSearchSynonymsProxyAddProxy), icon: icon, breadcrumbs: [L10n.accountSettingsProxy], present: { context, _, present in + presentProxySettings(context, present) + })) + + var hasSocksServers = false + for server in servers { + if case .socks5 = server.connection { + hasSocksServers = true + break + } + } + if hasSocksServers { + items.append(SettingsSearchableItem(id: .proxy(2), title: L10n.proxySettingsUseForCalls, alternate: synonyms(L10n.settingsSearchSynonymsProxyUseForCalls), icon: icon, breadcrumbs: [L10n.accountSettingsProxy], present: { context, _, present in + presentProxySettings(context, present) + })) + } + return items +} + +private func appearanceSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .appearance + + let presentAppearanceSettings: (AccountContext, (SettingsSearchableItemPresentation, ViewController?) -> Void, ThemeSettingsEntryTag?) -> Void = { context, present, itemTag in + present(.push, AppAppearanceViewController(context: context, focusOnItemTag: itemTag)) + } + + return [ + SettingsSearchableItem(id: .appearance(0), title: L10n.accountSettingsTheme, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceTitle), icon: icon, breadcrumbs: [], present: { context, _, present in + presentAppearanceSettings(context, present, nil) + }), + SettingsSearchableItem(id: .appearance(1), title: L10n.appearanceSettingsTextSizeHeader, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceTextSize), icon: icon, breadcrumbs: [L10n.accountSettingsTheme], present: { context, _, present in + presentAppearanceSettings(context, present, .fontSize) + }), + SettingsSearchableItem(id: .appearance(2), title: L10n.generalSettingsChatBackground, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceChatBackground), icon: icon, breadcrumbs: [L10n.accountSettingsTheme], present: { context, _, present in + showModal(with: ChatWallpaperModalController(context), for: context.window) + }), + SettingsSearchableItem(id: .appearance(5), title: L10n.appearanceSettingsAutoNight, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceAutoNightTheme), icon: icon, breadcrumbs: [L10n.accountSettingsTheme], present: { context, _, present in + present(.push, AutoNightSettingsController(context: context)) + }), + SettingsSearchableItem(id: .appearance(6), title: L10n.appearanceSettingsColorThemeHeader, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceColorTheme), icon: icon, breadcrumbs: [L10n.accountSettingsTheme], present: { context, _, present in + presentAppearanceSettings(context, present, .accentColor) + }), + SettingsSearchableItem(id: .appearance(6), title: L10n.appearanceSettingsChatViewHeader, alternate: synonyms(L10n.settingsSearchSynonymsAppearanceChatMode), icon: icon, breadcrumbs: [L10n.accountSettingsTheme], present: { context, _, present in + presentAppearanceSettings(context, present, .chatMode) + }), + ] +} + +private func languageSearchableItems(context: AccountContext, localizations: [LocalizationInfo]) -> [SettingsSearchableItem] { + let icon: SettingsSearchableItemIcon = .language + + let applyLocalization: (AccountContext, @escaping (SettingsSearchableItemPresentation, ViewController?) -> Void, String) -> Void = { context, present, languageCode in + _ = showModalProgress(signal: downloadAndApplyLocalization(accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, languageCode: languageCode), for: context.window).start() + } + + var items: [SettingsSearchableItem] = [] + items.append(SettingsSearchableItem(id: .language(0), title: L10n.accountSettingsLanguage, alternate: synonyms(L10n.settingsSearchSynonymsAppLanguage), icon: icon, breadcrumbs: [], present: { context, _, present in + present(.push, LanguageViewController(context)) + })) + var index: Int32 = 1 + for localization in localizations { + items.append(SettingsSearchableItem(id: .language(index), title: localization.localizedTitle, alternate: [localization.title], icon: icon, breadcrumbs: [L10n.accountSettingsLanguage], present: { context, _, present in + applyLocalization(context, present, localization.languageCode) + })) + index += 1 + } + return items +} + +func settingsSearchableItems(context: AccountContext, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal) -> Signal<[SettingsSearchableItem], NoError> { + + let canAddAccount = activeAccountsAndPeers(context: context) + |> take(1) + |> map { accountsAndPeers -> Bool in + return accountsAndPeers.1.count + 1 < maximumNumberOfAccounts + } + + let notificationSettings = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + |> take(1) + |> map { view -> GlobalNotificationSettingsSet in + let viewSettings: GlobalNotificationSettingsSet + if let settings = view.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings { + viewSettings = settings.effective + } else { + viewSettings = GlobalNotificationSettingsSet.defaultSettings + } + return viewSettings + } + + let archivedStickerPacks = archivedStickerPacks + |> take(1) + + let privacySettings = privacySettings + |> take(1) + + let proxyServers = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) + |> map { sharedData -> ProxySettings in + if let value = sharedData.entries[SharedDataKeys.proxySettings] as? ProxySettings { + return value + } else { + return ProxySettings.defaultSettings + } + } + |> map { settings -> [ProxyServerSettings] in + return settings.servers + } + + let localizationPreferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.localizationListState])) + let localizations = combineLatest(context.account.postbox.combinedView(keys: [localizationPreferencesKey]), context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings])) + |> map { view, sharedData -> [LocalizationInfo] in + if let localizationListState = (view.views[localizationPreferencesKey] as? PreferencesView)?.values[PreferencesKeys.localizationListState] as? LocalizationListState, !localizationListState.availableOfficialLocalizations.isEmpty { + + var existingIds = Set() + let availableSavedLocalizations = localizationListState.availableSavedLocalizations.filter({ info in !localizationListState.availableOfficialLocalizations.contains(where: { $0.languageCode == info.languageCode }) }) + + var activeLanguageCode: String? + if let localizationSettings = sharedData.entries[SharedDataKeys.localizationSettings] as? LocalizationSettings { + activeLanguageCode = localizationSettings.primaryComponent.languageCode + } + + var localizationItems: [LocalizationInfo] = [] + if !availableSavedLocalizations.isEmpty { + for info in availableSavedLocalizations { + if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode { + continue + } + existingIds.insert(info.languageCode) + localizationItems.append(info) + } + } + for info in localizationListState.availableOfficialLocalizations { + if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode { + continue + } + existingIds.insert(info.languageCode) + localizationItems.append(info) + } + + return localizationItems + } else { + return [] + } + } + + return combineLatest(canAddAccount, localizations, notificationSettings, archivedStickerPacks, proxyServers, privacySettings) + |> map { canAddAccount, localizations, notificationSettings, archivedStickerPacks, proxyServers, privacySettings in + + var allItems: [SettingsSearchableItem] = [] + + let profileItems = profileSearchableItems(context: context, canAddAccount: canAddAccount) + allItems.append(contentsOf: profileItems) + + + let stickerItems = stickerSearchableItems(context: context, archivedStickerPacks: archivedStickerPacks) + allItems.append(contentsOf: stickerItems) + + let notificationItems = notificationSearchableItems(context: context, settings: notificationSettings) + allItems.append(contentsOf: notificationItems) + + let privacyItems = privacySearchableItems(context: context, privacySettings: privacySettings) + allItems.append(contentsOf: privacyItems) + + let dataItems = dataSearchableItems(context: context) + allItems.append(contentsOf: dataItems) + + let proxyItems = proxySearchableItems(context: context, servers: proxyServers) + allItems.append(contentsOf: proxyItems) + + let appearanceItems = appearanceSearchableItems(context: context) + allItems.append(contentsOf: appearanceItems) + + let languageItems = languageSearchableItems(context: context, localizations: localizations) + allItems.append(contentsOf: languageItems) + + + let support = SettingsSearchableItem(id: .support(0), title: L10n.accountSettingsAskQuestion, alternate: synonyms(L10n.settingsSearchSynonymsSupport), icon: .support, breadcrumbs: [], present: { context, _, present in + confirm(for: context.window, information: L10n.accountConfirmAskQuestion, thridTitle: L10n.accountConfirmGoToFaq, successHandler: { result in + switch result { + case .basic: + _ = showModalProgress(signal: supportPeerId(account: context.account), for: context.window).start(next: { peerId in + if let peerId = peerId { + present(.push, ChatController(context: context, chatLocation: .peer(peerId))) + } + }) + case .thrid: + let _ = (cachedFaqInstantPage(context: context) |> deliverOnMainQueue).start(next: { resolvedUrl in + execute(inapp: resolvedUrl) + }) + } + }) + }) + allItems.append(support) + + let faq = SettingsSearchableItem(id: .faq(0), title: L10n.accountSettingsFAQ, alternate: synonyms(L10n.settingsSearchSynonymsFAQ), icon: .faq, breadcrumbs: [], present: { context, navigationController, present in + let _ = (cachedFaqInstantPage(context: context) |> deliverOnMainQueue).start(next: { resolvedUrl in + execute(inapp: resolvedUrl) + }) + }) + allItems.append(faq) + + return allItems + } +} + + + + diff --git a/Telegram-Mac/ShareInlineResultNavigationAction.swift b/Telegram-Mac/ShareInlineResultNavigationAction.swift index 9072dc48b2..d2c9fbb9e2 100644 --- a/Telegram-Mac/ShareInlineResultNavigationAction.swift +++ b/Telegram-Mac/ShareInlineResultNavigationAction.swift @@ -8,16 +8,17 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class ShareInlineResultNavigationAction: NavigationModalAction { let payload:String init(payload:String, botName:String) { self.payload = payload - super.init(reason: tr(.inlineModalActionTitle), desc: tr(.inlineModalActionDesc(botName))) + super.init(reason: tr(L10n.inlineModalActionTitle), desc: tr(L10n.inlineModalActionDesc(botName))) } override func isInvokable(for value:Any) -> Bool { @@ -29,7 +30,7 @@ class ShareInlineResultNavigationAction: NavigationModalAction { override func alertError(for value:Any, with window:Window) -> Void { if let _ = value as? Peer { - alert(for: window, header: appName, info: tr(.alertForwardError)) + alert(for: window, info: tr(L10n.alertForwardError)) } } } diff --git a/Telegram-Mac/ShareModalController.swift b/Telegram-Mac/ShareModalController.swift index 257f2c9a01..1de5acc903 100644 --- a/Telegram-Mac/ShareModalController.swift +++ b/Telegram-Mac/ShareModalController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox @@ -22,15 +23,15 @@ fileprivate class ShareButton : Control { super.init(frame: frameRect) addSubview(badgeView) addSubview(shareText) - let layout = TextViewLayout(.initialize(string: tr(.modalShare).uppercased(), color: .white, font: .normal(.header)), maximumNumberOfLines: 1) + let layout = TextViewLayout(.initialize(string: tr(L10n.modalShare).uppercased(), color: .white, font: .normal(.header)), maximumNumberOfLines: 1) layout.measure(width: .greatestFiniteMagnitude) shareText.update(layout) setFrameSize(NSMakeSize(22 + shareText.frame.width + 47, 41)) layer?.cornerRadius = 20 - set(background: theme.colors.blueFill, for: .Hover) - set(background: theme.colors.blueFill, for: .Normal) - set(background: theme.colors.blueFill, for: .Highlight) - shareText.backgroundColor = theme.colors.blueFill + set(background: theme.colors.accent, for: .Hover) + set(background: theme.colors.accent, for: .Normal) + set(background: theme.colors.accent, for: .Highlight) + shareText.backgroundColor = theme.colors.accent needsLayout = true updateCount(0) shareText.userInteractionEnabled = false @@ -47,7 +48,7 @@ fileprivate class ShareButton : Control { } func updateCount(_ count:Int) -> Void { - badge = BadgeNode(.initialize(string: "\(max(count, 1))", color: theme.colors.blueFill, font: .medium(.small)), .white) + badge = BadgeNode(.initialize(string: "\(max(count, 1))", color: theme.colors.accent, font: .medium(.small)), .white) badgeView.setFrameSize(badge!.size) badge?.view = badgeView badge?.setNeedDisplay() @@ -60,14 +61,25 @@ fileprivate class ShareButton : Control { } fileprivate class ShareModalView : View, TokenizedProtocol { - let searchView:TokenizedView + let tokenizedView:TokenizedView + let basicSearchView: SearchView = SearchView(frame: NSMakeRect(0,0, 260, 30)) let tableView:TableView = TableView() fileprivate let share:ImageButton = ImageButton() fileprivate let dismiss:ImageButton = ImageButton() - private let separator = View() - fileprivate let invokeButton = ShareButton(frame: NSZeroRect) - private let shadowView: View = ShadowView() + deinit { + var bp:Int = 0 + bp += 1 + } + + fileprivate let textView:TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect) + fileprivate let sendButton = ImageButton() + fileprivate let emojiButton = ImageButton() + fileprivate let actionsContainerView: View = View() + fileprivate let textContainerView: View = View() + fileprivate let bottomSeparator: View = View() + + private let topSeparator = View() fileprivate var hasShareMenu: Bool = true { didSet { share.isHidden = !hasShareMenu @@ -75,65 +87,164 @@ fileprivate class ShareModalView : View, TokenizedProtocol { } } - required init(frame frameRect: NSRect) { - searchView = TokenizedView(frame: NSMakeRect(0, 0, 260, 30), localizationFunc: { key in + + required init(frame frameRect: NSRect, shareObject: ShareObject) { + tokenizedView = TokenizedView(frame: NSMakeRect(0, 0, 300, 30), localizationFunc: { key in return translate(key: key, []) - }, placeholderKey: "ShareModal.Search.Placeholder") + }, placeholderKey: shareObject.searchPlaceholderKey) super.init(frame: frameRect) - addSubview(searchView) + + backgroundColor = theme.colors.background + textContainerView.backgroundColor = theme.colors.background + actionsContainerView.backgroundColor = theme.colors.background + textView.setBackgroundColor(theme.colors.background) + + addSubview(tokenizedView) + addSubview(basicSearchView) addSubview(tableView) - addSubview(separator) - searchView.delegate = self - separator.backgroundColor = theme.colors.border + addSubview(topSeparator) + tokenizedView.delegate = self + bottomSeparator.backgroundColor = theme.colors.border + topSeparator.backgroundColor = theme.colors.border + self.backgroundColor = theme.colors.background share.set(image: theme.icons.modalShare, for: .Normal) dismiss.set(image: theme.icons.modalClose, for: .Normal) - share.sizeToFit() - dismiss.sizeToFit() + _ = share.sizeToFit() + _ = dismiss.sizeToFit() addSubview(share) addSubview(dismiss) - shadowView.backgroundColor = theme.colors.background.withAlphaComponent(1.0) - shadowView.setFrameSize(frame.width, 70) - addSubview(shadowView) - addSubview(invokeButton) + + + sendButton.set(image: theme.icons.chatSendMessage, for: .Normal) + sendButton.autohighlight = false + _ = sendButton.sizeToFit() + + emojiButton.set(image: theme.icons.chatEntertainment, for: .Normal) + _ = emojiButton.sizeToFit() + + actionsContainerView.addSubview(sendButton) + actionsContainerView.addSubview(emojiButton) + + + actionsContainerView.setFrameSize(sendButton.frame.width + emojiButton.frame.width + 40, 50) + + emojiButton.centerY(x: 0) + sendButton.centerY(x: emojiButton.frame.maxX + 20) + + backgroundColor = theme.colors.background + textView.background = theme.colors.background + textView.textFont = .normal(.text) + textView.textColor = theme.colors.text + textView.linkColor = theme.colors.link + textView.max_height = 120 + + textView.setFrameSize(NSMakeSize(0, 34)) + textView.setPlaceholderAttributedString(.initialize(string: tr(L10n.previewSenderCommentPlaceholder), color: theme.colors.grayText, font: .normal(.text)), update: false) + + + textContainerView.addSubview(textView) + + addSubview(textContainerView) + addSubview(actionsContainerView) + addSubview(bottomSeparator) } - private var count:Int = 0 - func updateCount(_ count:Int, animated: Bool) -> Void { - self.count = count - invokeButton.updateCount(count) - if count == 0 { - invokeButton.change(pos: NSMakePoint(invokeButton.frame.minX, frame.height), animated: animated, timingFunction: kCAMediaTimingFunctionSpring) - shadowView.change(pos: NSMakePoint(shadowView.frame.minX, frame.height), animated: animated, timingFunction: kCAMediaTimingFunctionSpring) + var searchView: NSView { + if hasCaptionView { + return tokenizedView } else { - invokeButton.change(pos: NSMakePoint(invokeButton.frame.minX, frame.height - invokeButton.frame.height - 16), animated: animated, timingFunction: kCAMediaTimingFunctionSpring) - shadowView.change(pos: NSMakePoint(shadowView.frame.minX, frame.height - shadowView.frame.height), animated: animated, timingFunction: kCAMediaTimingFunctionSpring) + return basicSearchView + } + } + + var hasCaptionView: Bool = true { + didSet { + textContainerView.isHidden = !hasCaptionView + actionsContainerView.isHidden = !hasCaptionView + bottomSeparator.isHidden = !hasCaptionView + + basicSearchView.isHidden = hasCaptionView + tokenizedView.isHidden = !hasCaptionView + dismiss.isHidden = !hasCaptionView + needsLayout = true } } + var hasCommentView: Bool = true { + didSet { + textContainerView.isHidden = !hasCommentView + bottomSeparator.isHidden = !hasCommentView + actionsContainerView.isHidden = !hasCommentView + needsLayout = true + } + } + + var hasSendView: Bool = true { + didSet { + sendButton.isHidden = !hasSendView + needsLayout = true + } + } + + + func tokenizedViewDidChangedHeight(_ view: TokenizedView, height: CGFloat, animated: Bool) { searchView._change(pos: NSMakePoint(50, 10), animated: animated) - tableView.change(size: NSMakeSize(frame.width, frame.height - height - 20), animated: animated) + tableView.change(size: NSMakeSize(frame.width, frame.height - height - 20 - (textContainerView.isHidden ? 0 : textContainerView.frame.height)), animated: animated) tableView.change(pos: NSMakePoint(0, height + 20), animated: animated) - separator.change(pos: NSMakePoint(0, searchView.frame.maxY + 10), animated: animated) + topSeparator.change(pos: NSMakePoint(0, searchView.frame.maxY + 10), animated: animated) + } + + func textViewUpdateHeight(_ height: CGFloat, _ animated: Bool) { + CATransaction.begin() + textContainerView.change(size: NSMakeSize(frame.width, height + 16), animated: animated) + textContainerView.change(pos: NSMakePoint(0, frame.height - textContainerView.frame.height), animated: animated) + textView._change(pos: NSMakePoint(10, height == 34 ? 8 : 11), animated: animated) + tableView.change(size: NSMakeSize(frame.width, frame.height - searchView.frame.height - 20 - (!textContainerView.isHidden ? 50 : 0)), animated: animated) + + actionsContainerView.change(pos: NSMakePoint(frame.width - actionsContainerView.frame.width, frame.height - actionsContainerView.frame.height), animated: animated) + + bottomSeparator.change(pos: NSMakePoint(0, textContainerView.frame.minY), animated: animated) + CATransaction.commit() + + needsLayout = true + } + + var additionHeight: CGFloat { + return textView.frame.height + 16 + searchView.frame.height + 20 } fileprivate override func layout() { super.layout() - searchView.setFrameSize(frame.width - 50 - (share.isHidden ? 10 : 50), searchView.frame.height) + + emojiButton.centerY(x: 0) + actionsContainerView.setFrameSize((sendButton.isHidden ? 0 : (sendButton.frame.width + 20)) + emojiButton.frame.width + 20, 50) + + sendButton.centerY(x: emojiButton.frame.maxX + 20) + + searchView.setFrameSize(frame.width - 10 - (!dismiss.isHidden ? 40 : 0) - (share.isHidden ? 10 : 50), searchView.frame.height) share.setFrameOrigin(frame.width - share.frame.width - 10, 10) dismiss.setFrameOrigin(10, 10) - searchView.setFrameOrigin(50, 10) - tableView.frame = NSMakeRect(0, searchView.frame.maxY + 10, frame.width, frame.height - searchView.frame.height - 20) - separator.frame = NSMakeRect(0, searchView.frame.maxY + 10, frame.width, .borderSize) - invokeButton.centerX(y: count == 0 ? frame.height : frame.height - invokeButton.frame.height - 16) - shadowView.setFrameOrigin(0, count == 0 ? frame.height : frame.height - shadowView.frame.height) + searchView.setFrameOrigin(10 + (!dismiss.isHidden ? 40 : 0), 10) + tableView.frame = NSMakeRect(0, searchView.frame.maxY + 10, frame.width, frame.height - searchView.frame.height - 20 - (!textContainerView.isHidden ? 50 : 0)) + topSeparator.frame = NSMakeRect(0, searchView.frame.maxY + 10, frame.width, .borderSize) + actionsContainerView.setFrameOrigin(frame.width - actionsContainerView.frame.width, frame.height - actionsContainerView.frame.height) + + textContainerView.setFrameSize(frame.width, textView.frame.height + 16) + textContainerView.setFrameOrigin(0, frame.height - textContainerView.frame.height) + + + textView.setFrameSize(NSMakeSize(textContainerView.frame.width - 10 - actionsContainerView.frame.width, textView.frame.height)) + textView.setFrameOrigin(10, textView.frame.height == 34 ? 8 : 11) + bottomSeparator.frame = NSMakeRect(0, textContainerView.frame.minY, frame.width, .borderSize) + } @@ -141,16 +252,73 @@ fileprivate class ShareModalView : View, TokenizedProtocol { fatalError("init(coder:) has not been implemented") } + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} + +final class ShareAdditionItem { + let peer: Peer + let status: String? + init(peer: Peer, status: String?) { + self.peer = peer + self.status = status + } +} + +final class ShareAdditionItems { + let items: [ShareAdditionItem] + let topSeparator: String + let bottomSeparator: String + init(items: [ShareAdditionItem], topSeparator: String, bottomSeparator: String) { + self.items = items + self.topSeparator = topSeparator + self.bottomSeparator = bottomSeparator + } } class ShareObject { - let account:Account - init(_ account:Account) { - self.account = account + + let additionTopItems:ShareAdditionItems? + + let context: AccountContext + let emptyPerformOnClose: Bool + let excludePeerIds: Set + let defaultSelectedIds:Set + let limit: Int? + init(_ context:AccountContext, emptyPerformOnClose: Bool = false, excludePeerIds:Set = [], defaultSelectedIds: Set = [], additionTopItems:ShareAdditionItems? = nil, limit: Int? = nil) { + self.limit = limit + self.context = context + self.emptyPerformOnClose = emptyPerformOnClose + self.excludePeerIds = excludePeerIds + self.additionTopItems = additionTopItems + self.defaultSelectedIds = defaultSelectedIds + } + + var multipleSelection: Bool { + return true + } + var hasCaptionView: Bool { + return true + } + var interactionOk: String { + return L10n.modalOK } - func perform(to entries:[PeerId]) { + var searchPlaceholderKey: String { + return "ShareModal.Search.Placeholder" + } + + var alwaysEnableDone: Bool { + return false + } + + func perform(to entries:[PeerId], comment: String? = nil) -> Signal { + return .complete() + } + func limitReached() { } @@ -163,15 +331,17 @@ class ShareObject { } func possibilityPerformTo(_ peer:Peer) -> Bool { - return peer.canSendMessage + return peer.canSendMessage && !self.excludePeerIds.contains(peer.id) } + + } class ShareLinkObject : ShareObject { let link:String - init(_ account:Account, link:String) { - self.link = link - super.init(account) + init(_ context: AccountContext, link:String) { + self.link = link.removingPercentEncoding ?? link + super.init(context) } override var hasLink: Bool { @@ -182,58 +352,83 @@ class ShareLinkObject : ShareObject { copyToClipboard(link) } - override func perform(to peerIds:[PeerId]) { + override func perform(to peerIds:[PeerId], comment: String? = nil) -> Signal { for peerId in peerIds { + + if let comment = comment?.trimmed, !comment.isEmpty { + _ = Sender.enqueue(message: EnqueueMessage.message(text: comment, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil), context: context, peerId: peerId).start() + } + var attributes:[MessageAttribute] = [] if FastSettings.isChannelMessagesMuted(peerId) { attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) } - _ = enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: link, attributes: attributes, media: nil, replyToMessageId: nil)]).start() + _ = enqueueMessages(context: context, peerId: peerId, messages: [EnqueueMessage.message(text: link, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]).start() } + return .complete() } } class ShareContactObject : ShareObject { let user:TelegramUser - init(_ account:Account, user:TelegramUser) { + init(_ context: AccountContext, user:TelegramUser) { self.user = user - super.init(account) + super.init(context) } - override func perform(to peerIds:[PeerId]) { + override func perform(to peerIds:[PeerId], comment: String? = nil) -> Signal { for peerId in peerIds { - _ = Sender.shareContact(account: account, peerId: peerId, contact: user).start() + _ = Sender.shareContact(context: context, peerId: peerId, contact: user).start() } + return .complete() } } +class ShareCallbackObject : ShareObject { + private let callback:([PeerId])->Signal + init(_ context: AccountContext, callback:@escaping([PeerId])->Signal) { + self.callback = callback + super.init(context) + } + + override func perform(to peerIds:[PeerId], comment: String? = nil) -> Signal { + return callback(peerIds) |> mapError { _ in return String() } + } + +} + + + + class ShareMessageObject : ShareObject { fileprivate let messageIds:[MessageId] private let message:Message let link:String? private let exportLinkDisposable = MetaDisposable() - init(_ account:Account, _ message:Message) { - self.messageIds = [message.id] + init(_ context: AccountContext, _ message:Message, _ groupMessages:[Message] = []) { + self.messageIds = groupMessages.isEmpty ? [message.id] : groupMessages.map{$0.id} self.message = message - let peer:TelegramChannel? + var peer = messageMainPeer(message) as? TelegramChannel + var messageId = message.id if let author = message.forwardInfo?.author as? TelegramChannel { peer = author - } else { - peer = messageMainPeer(message) as? TelegramChannel + messageId = message.forwardInfo?.sourceMessageId ?? message.id } + // peer = messageMainPeer(message) as? TelegramChannel + // } if let peer = peer, let address = peer.username { switch peer.info { case .broadcast: - self.link = "https://t.me/" + address + "/" + "\(message.id.id)" + self.link = "https://t.me/" + address + "/" + "\(messageId.id)" default: self.link = nil } } else { self.link = nil } - super.init(account) + super.init(context) } override var hasLink: Bool { @@ -242,7 +437,7 @@ class ShareMessageObject : ShareObject { override func shareLink() { if let link = link { - exportLinkDisposable.set(exportMessageLink(account: account, peerId: messageIds[0].peerId, messageId: messageIds[0]).start(next: { valueLink in + exportLinkDisposable.set(exportMessageLink(account: context.account, peerId: messageIds[0].peerId, messageId: messageIds[0]).start(next: { valueLink in if let valueLink = valueLink { copyToClipboard(valueLink) } else { @@ -256,10 +451,15 @@ class ShareMessageObject : ShareObject { exportLinkDisposable.dispose() } - override func perform(to peerIds:[PeerId]) { + override func perform(to peerIds:[PeerId], comment: String? = nil) -> Signal { for peerId in peerIds { - _ = Sender.forwardMessages(messageIds: messageIds, account: account, peerId: peerId).start() + if let comment = comment?.trimmed, !comment.isEmpty { + _ = Sender.enqueue(message: EnqueueMessage.message(text: comment, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil), context: context, peerId: peerId).start() + } + + _ = Sender.forwardMessages(messageIds: messageIds, context: context, peerId: peerId).start() } + return .complete() } override func possibilityPerformTo(_ peer:Peer) -> Bool { @@ -267,13 +467,105 @@ class ShareMessageObject : ShareObject { } } +final class ForwardMessagesObject : ShareObject { + fileprivate let messageIds: [MessageId] + private let disposable = MetaDisposable() + init(_ context: AccountContext, messageIds: [MessageId], emptyPerformOnClose: Bool = false) { + self.messageIds = messageIds + super.init(context, emptyPerformOnClose: emptyPerformOnClose) + } + + deinit { + disposable.dispose() + } + + override var multipleSelection: Bool { + return false + } + + override func perform(to peerIds: [PeerId], comment: String?) -> Signal { + let comment = comment != nil && !comment!.isEmpty ? comment : nil + let context = self.context + + let peers = context.account.postbox.transaction { transaction -> Peer? in + for peerId in peerIds { + if let peer = transaction.getPeer(peerId) { + return peer + } + } + return nil + } + + return combineLatest(context.account.postbox.messagesAtIds(messageIds), peers) + |> deliverOnMainQueue + |> mapError { _ in return String() } + |> mapToSignal { messages, peer in + + let messageIds = messages.map { $0.id } + + if let peer = peer, peer.isChannel { + for message in messages { + if message.isPublicPoll { + return .fail(L10n.pollForwardError) + } + } + } + + let navigation = self.context.sharedContext.bindings.rootNavigation() + if let peerId = peerIds.first { + if peerId == context.peerId { + _ = Sender.forwardMessages(messageIds: messageIds, context: context, peerId: context.account.peerId).start() + if let controller = context.sharedContext.bindings.rootNavigation().controller as? ChatController { + controller.chatInteraction.update({$0.withoutSelectionState()}) + } + delay(0.2, closure: { + _ = showModalSuccess(for: context.window, icon: theme.icons.successModalProgress, delay: 1.0).start() + }) + } else { + if let controller = navigation.controller as? ChatController, controller.chatInteraction.peerId == peerId { + controller.chatInteraction.update({$0.withoutSelectionState().updatedInterfaceState({$0.withUpdatedForwardMessageIds(messageIds)})}) + } else { + (navigation.controller as? ChatController)?.chatInteraction.update({ $0.withoutSelectionState() }) + + var existed: Bool = false + navigation.enumerateControllers { controller, _ in + if let controller = controller as? ChatController, controller.chatInteraction.peerId == peerId { + existed = true + } + return existed + } + let newone: ChatController + if existed { + newone = ChatController(context: context, chatLocation: .peer(peerId), initialAction: .forward(messageIds: messageIds, text: comment, behavior: .automatic)) + } else { + newone = ChatAdditionController(context: context, chatLocation: .peer(peerId), initialAction: .forward(messageIds: messageIds, text: comment, behavior: .automatic)) + } + navigation.push(newone) + + return newone.ready.get() |> filter {$0} |> take(1) |> ignoreValues |> mapError { _ in return String() } + } + } + } else { + if let controller = navigation.controller as? ChatController { + controller.chatInteraction.update({$0.withoutSelectionState().updatedInterfaceState({$0.withUpdatedForwardMessageIds(messageIds)})}) + } + } + return .complete() + } + } + + override var searchPlaceholderKey: String { + return "ShareModal.Search.ForwardPlaceholder" + } +} + enum SelectablePeersEntryStableId : Hashable { - case plain(PeerId) + case plain(PeerId, ChatListIndex) case emptySearch case separator(ChatListIndex) var hashValue: Int { switch self { - case let .plain(peerId): + case let .plain(peerId, _): return peerId.hashValue case .separator(let index): return index.hashValue @@ -281,39 +573,19 @@ enum SelectablePeersEntryStableId : Hashable { return 0 } } - - static func ==(lhs:SelectablePeersEntryStableId, rhs:SelectablePeersEntryStableId) -> Bool { - switch lhs { - case let .plain(peerId): - if case .plain(peerId) = rhs { - return true - } else { - return false - } - case let .separator(index): - if case .separator(index) = rhs { - return true - } else { - return false - } - case .emptySearch: - if case .emptySearch = rhs { - return true - } else { - return false - } - } - } } enum SelectablePeersEntry : Comparable, Identifiable { + case secretChat(Peer, PeerId, ChatListIndex, PeerStatusStringResult?, Bool) case plain(Peer, ChatListIndex, PeerStatusStringResult?, Bool) case separator(String, ChatListIndex) case emptySearch var stableId: SelectablePeersEntryStableId { switch self { - case let .plain(peer,_, _, _): - return .plain(peer.id) + case let .plain(peer, index, _, _): + return .plain(peer.id, index) + case let .secretChat(_, peerId, index, _, _): + return .plain(peerId, index) case let .separator(_, index): return .separator(index) case .emptySearch: @@ -325,6 +597,8 @@ enum SelectablePeersEntry : Comparable, Identifiable { switch self { case let .plain(_, id, _, _): return id + case let .secretChat(_, _, id, _, _): + return id case let .separator(_, index): return index case .emptySearch: @@ -345,6 +619,12 @@ func ==(lhs:SelectablePeersEntry, rhs:SelectablePeersEntry) -> Bool { } else { return false } + case let .secretChat(lhsPeer, lhsPeerId, lhsIndex, lhsPresence, lhsSeparator): + if case let .secretChat(rhsPeer, rhsPeerId, rhsIndex, rhsPresence, rhsSeparator) = rhs { + return lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex && lhsPresence == rhsPresence && lhsSeparator == rhsSeparator && lhsPeerId == rhsPeerId + } else { + return false + } case let .separator(text, index): if case .separator(text, index) = rhs { return true @@ -362,14 +642,20 @@ func ==(lhs:SelectablePeersEntry, rhs:SelectablePeersEntry) -> Bool { -fileprivate func prepareEntries(from:[SelectablePeersEntry]?, to:[SelectablePeersEntry], account:Account, initialSize:NSSize, animated:Bool, selectInteraction:SelectPeerInteraction) -> TableUpdateTransition { +fileprivate func prepareEntries(from:[SelectablePeersEntry]?, to:[SelectablePeersEntry], account:Account, initialSize:NSSize, animated:Bool, multipleSelection: Bool, selectInteraction:SelectPeerInteraction) -> TableUpdateTransition { let (deleted,inserted,updated) = proccessEntries(from, right: to, { entry -> TableRowItem in switch entry { case let .plain(peer, _, presence, drawSeparator): - let color = presence?.status.attribute(NSAttributedStringKey.foregroundColor, at: 0, effectiveRange: nil) as? NSColor - return ShortPeerRowItem(initialSize, peer: peer, account:account, stableId: entry.stableId, height: 48, photoSize:NSMakeSize(36, 36), statusStyle: ControlStyle(font: .normal(.text), foregroundColor: color ?? theme.colors.grayText, highlightColor:.white), status: presence?.status.string, drawCustomSeparator: drawSeparator, inset:NSEdgeInsets(left: 10, right: 10), interactionType:.selectable(selectInteraction)) + let color = presence?.status.string.isEmpty == false ? presence?.status.attribute(NSAttributedString.Key.foregroundColor, at: 0, effectiveRange: nil) as? NSColor : nil + return ShortPeerRowItem(initialSize, peer: peer, account:account, stableId: entry.stableId, height: 48, photoSize:NSMakeSize(36, 36), statusStyle: ControlStyle(font: .normal(.text), foregroundColor: peer.id == account.peerId ? theme.colors.grayText : color ?? theme.colors.grayText, highlightColor:.white), status: peer.id == account.peerId ? (multipleSelection ? nil : L10n.forwardToSavedMessages) : presence?.status.string, drawCustomSeparator: drawSeparator, isLookSavedMessage : peer.id == account.peerId, inset:NSEdgeInsets(left: 10, right: 10), drawSeparatorIgnoringInset: true, interactionType: multipleSelection ? .selectable(selectInteraction) : .plain, action: { + selectInteraction.action(peer.id) + }) + case let .secretChat(peer, peerId, _, _, drawSeparator): + return ShortPeerRowItem(initialSize, peer: peer, account :account, peerId: peerId, stableId: entry.stableId, height: 48, photoSize:NSMakeSize(36, 36), titleStyle: ControlStyle(font: .medium(.title), foregroundColor: theme.colors.accent, highlightColor: .white), statusStyle: ControlStyle(font: .normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status: L10n.composeSelectSecretChat.lowercased(), drawCustomSeparator: drawSeparator, isLookSavedMessage : peer.id == account.peerId, inset:NSEdgeInsets(left: 10, right: 10), drawSeparatorIgnoringInset: true, interactionType: multipleSelection ? .selectable(selectInteraction) : .plain, action: { + selectInteraction.action(peerId) + }) case let .separator(text, _): return SeparatorRowItem(initialSize, entry.stableId, string: text) case .emptySearch: @@ -380,16 +666,18 @@ fileprivate func prepareEntries(from:[SelectablePeersEntry]?, to:[SelectablePeer }) - return TableUpdateTransition(deleted: deleted, inserted: inserted, updated: updated, animated: animated, state: animated ? .none(nil) : .saveVisible(.lower), grouping: !animated, animateVisibleOnly: false) + return TableUpdateTransition(deleted: deleted, inserted: inserted, updated: updated, animated: animated, state: animated ? .none(nil) : .saveVisible(.lower), grouping: true, animateVisibleOnly: false) } -class ShareModalController: ModalViewController, Notifable { +class ShareModalController: ModalViewController, Notifable, TGModernGrowingDelegate, TableViewDelegate { + + private let share:ShareObject private let selectInteractions:SelectPeerInteraction = SelectPeerInteraction() - private let search:Promise = Promise() + private let search:Promise = Promise() private let inSearchSelected:Atomic<[PeerId]> = Atomic(value:[]) private let disposable:MetaDisposable = MetaDisposable() private let exportLinkDisposable:MetaDisposable = MetaDisposable() @@ -401,14 +689,38 @@ class ShareModalController: ModalViewController, Notifable { let added = value.selected.subtracting(oldValue.selected) let removed = oldValue.selected.subtracting(value.selected) - for item in added { - genericView.searchView.addToken(token: SearchToken(name: value.peers[item]?.compactDisplayTitle ?? tr(.peerDeletedUser), uniqueId: item.toInt64()), animated: animated) + + let selected = value.selected.filter { + $0.namespace != ChatListFilterPeerCategories.Namespace } - for item in removed { - genericView.searchView.removeToken(uniqueId: item.toInt64(), animated: animated) + if let limit = self.share.limit, selected.count > limit { + DispatchQueue.main.async { [unowned self] in + self.selectInteractions.update(animated: true, { current in + var current = current + for peerId in added { + if let peer = current.peers[peerId] { + current = current.withToggledSelected(peerId, peer: peer) + } + } + return current + }) + self.share.limitReached() + } + return + } + + let tokens:[SearchToken] = added.map { item in + let title = item == share.context.account.peerId ? L10n.peerSavedMessages : value.peers[item]?.compactDisplayTitle ?? L10n.peerDeletedUser + return SearchToken(name: title, uniqueId: item.toInt64()) + } + genericView.tokenizedView.addTokens(tokens: tokens, animated: animated) + + let idsToRemove:[Int64] = removed.map { + $0.toInt64() } - genericView.updateCount(value.selected.count, animated: animated) + genericView.tokenizedView.removeTokens(uniqueIds: idsToRemove, animated: animated) + self.modal?.interactions?.updateEnables(!value.selected.isEmpty || share.alwaysEnableDone) } } @@ -428,47 +740,203 @@ class ShareModalController: ModalViewController, Notifable { return ShareModalView.self } + override func initializer() -> NSView { + let vz = viewClass() as! ShareModalView.Type + return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), shareObject: share); + } + + override var modal: Modal? { didSet { modal?.interactions?.updateEnables(false) } } + func selectionDidChange(row: Int, item: TableRowItem, byClick: Bool, isNew: Bool) { + + } + + func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { + return !self.share.multipleSelection && !(item is SeparatorRowItem) + } + + func isSelectable(row: Int, item: TableRowItem) -> Bool { + return !self.share.multipleSelection + } + + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + + private func invokeShortCut(_ index: Int) { + if genericView.tableView.count > index, let item = self.genericView.tableView.item(at: index) as? ShortPeerRowItem { + _ = self.genericView.tableView.select(item: item) + (item.view as? ShortPeerRowView)?.invokeAction(item, clickCount: 1) + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + let context = self.share.context + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.tableView.highlightPrev() + return .invoked + }, with: self, for: .UpArrow, priority: .modal) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.tableView.highlightNext() + return .invoked + }, with: self, for: .DownArrow, priority: .modal) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + if let highlighted = self.genericView.tableView.highlightedItem() as? ShortPeerRowItem { + _ = self.genericView.tableView.select(item: highlighted) + (highlighted.view as? ShortPeerRowView)?.invokeAction(highlighted, clickCount: 1) + } + + return .rejected + }, with: self, for: .Return, priority: .low) + + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.selectInteractions.action(context.peerId) + return .invoked + }, with: self, for: .Zero, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(0) + return .invoked + }, with: self, for: .One, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(1) + return .invoked + }, with: self, for: .Two, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(2) + return .invoked + }, with: self, for: .Three, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(3) + return .invoked + }, with: self, for: .Four, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(4) + return .invoked + }, with: self, for: .Five, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(5) + return .invoked + }, with: self, for: .Six, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(6) + return .invoked + }, with: self, for: .Seven, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(7) + return .invoked + }, with: self, for: .Eight, priority: self.responderPriority, modifierFlags: [.command]) + + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.invokeShortCut(8) + return .invoked + }, with: self, for: .Nine, priority: self.responderPriority, modifierFlags: [.command]) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.window?.removeAllHandlers(for: self) + } + + override var handleAllEvents: Bool { + return true + } + + override var responderPriority: HandlerPriority { + return .modal + } + override func viewDidLoad() { super.viewDidLoad() + + genericView.tableView.delegate = self + + let interactions = EntertainmentInteractions(.emoji, peerId: PeerId(0)) + interactions.sendEmoji = { [weak self] emoji in + self?.genericView.textView.appendText(emoji) + _ = self?.window?.makeFirstResponder(self?.genericView.textView.inputView) + } + emoji.update(with: interactions) + + genericView.emojiButton.set(handler: { [weak self] control in + self?.showEmoji(for: control) + }, for: .Hover) + + + genericView.textView.delegate = self genericView.hasShareMenu = self.share.hasLink - search.set(genericView.searchView.textUpdater) + genericView.hasCaptionView = self.share.multipleSelection + genericView.hasCommentView = self.share.hasCaptionView + genericView.hasSendView = self.share.multipleSelection + if self.share.multipleSelection { + search.set(combineLatest(genericView.tokenizedView.textUpdater, genericView.tokenizedView.stateValue.get()) |> map { SearchState(state: $1, request: $0)}) + } else { + search.set(genericView.basicSearchView.searchValue) + } genericView.dismiss.set(handler: { [weak self] _ in self?.close() }, for: .Click) - let initialSize = self.atomicSize.modify({$0}) let request = Promise() - let account = self.share.account + let context = self.share.context let selectInteraction = self.selectInteractions + + + let share = self.share selectInteraction.add(observer: self) let previous:Atomic<[SelectablePeersEntry]?> = Atomic(value: nil) + selectInteraction.action = { [weak self] peerId in + guard let `self` = self else { return } + _ = share.perform(to: [peerId], comment: self.genericView.textView.string()).start(error: { error in + alert(for: context.window, info: error) + }, completed: { [weak self] in + self?.close() + }) + } genericView.share.set(handler: { [weak self] control in - showPopover(for: control, with: SPopoverViewController(items: [SPopoverItem(tr(.modalCopyLink), { + showPopover(for: control, with: SPopoverViewController(items: [SPopoverItem(L10n.modalCopyLink, { if share.hasLink { share.shareLink() - self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied), height:50), for: 2.0, animated: true) + self?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied), for: 2.0, animated: true) } })]), edge: .maxY, inset: NSMakePoint(-100, -40)) }, for: .Click) - genericView.invokeButton.set(handler: { [weak self] _ in - share.perform(to: selectInteraction.presentation.selected.map{$0}) - self?.close() - }, for: .Click) - tokenDisposable.set(genericView.searchView.tokensUpdater.start(next: { tokens in + genericView.sendButton.set(handler: { [weak self] _ in + if let strongSelf = self, !selectInteraction.presentation.selected.isEmpty { + _ = strongSelf.invoke() + } + }, for: .SingleClick) + + + + tokenDisposable.set(genericView.tokenizedView.tokensUpdater.start(next: { tokens in let ids = Set(tokens.map({PeerId($0.uniqueId)})) let unselected = selectInteraction.presentation.selected.symmetricDifference(ids) @@ -478,149 +946,257 @@ class ShareModalController: ModalViewController, Notifable { })) - let list:Signal = combineLatest(request.get() |> distinctUntilChanged |> deliverOnPrepareQueue, search.get() |> distinctUntilChanged |> deliverOnPrepareQueue, genericView.searchView.stateValue.get() |> deliverOnPrepareQueue) |> mapToSignal { location, search, state -> Signal in + let previousChatList:Atomic = Atomic(value: nil) + + let multipleSelection = self.share.multipleSelection + + + let defaultItems = context.account.postbox.transaction { transaction -> [Peer] in + var peers:[Peer] = [] - if state == .None { - return combineLatest(recentPeers(account: account) |> deliverOnPrepareQueue, recentlySearchedPeers(postbox: account.postbox) |> deliverOnPrepareQueue) |> map { top, recent -> TableUpdateTransition in - - var entries:[SelectablePeersEntry] = [] - - var contains:[PeerId:PeerId] = [:] - - var indexId:Int32 = Int32.max - - let chatListIndex:()-> ChatListIndex = { - let index = MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 1, id: indexId), timestamp: indexId) - indexId -= 1 - return ChatListIndex(pinningIndex: nil, messageIndex: index) + if let addition = share.additionTopItems { + for item in addition.items { + if share.defaultSelectedIds.contains(item.peer.id) { + peers.append(item.peer) } - - if !top.isEmpty { - entries.insert(.separator(tr(.searchSeparatorPopular).uppercased(), chatListIndex()), at: 0) + } + } + + for peerId in share.defaultSelectedIds { + if let peer = transaction.getPeer(peerId) { + peers.append(peer) + } + } + return peers + } + + let list:Signal = combineLatest(request.get() |> distinctUntilChanged |> deliverOnPrepareQueue, search.get() |> distinctUntilChanged |> deliverOnPrepareQueue) |> mapToSignal { location, query -> Signal in + + if query.request.isEmpty { + if !multipleSelection && query.state == .Focus { + return combineLatest(context.account.postbox.loadedPeerWithId(context.peerId), recentPeers(account: context.account) |> deliverOnPrepareQueue, recentlySearchedPeers(postbox: context.account.postbox) |> deliverOnPrepareQueue) |> map { user, rawTop, recent -> TableUpdateTransition in - var count: Int32 = 0 - for peer in top { - if contains[peer.id] == nil { - if share.possibilityPerformTo(peer) { - entries.insert(.plain(peer, chatListIndex(), nil, count < 4), at: 0) - contains[peer.id] = peer.id - count += 1 - } - } - if count >= 5 { - break - } + var entries:[SelectablePeersEntry] = [] + + let top:[Peer] + switch rawTop { + case let .peers(peers): + top = peers + default: + top = [] } - } - - if !recent.isEmpty { - entries.insert(.separator(tr(.searchSeparatorRecent).uppercased(), chatListIndex()), at: 0) - - for rendered in recent { - if let peer = rendered.chatMainPeer { + + var contains:[PeerId:PeerId] = [:] + + var indexId:Int32 = Int32.max + + let chatListIndex:()-> ChatListIndex = { + let index = MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 1, id: indexId), timestamp: indexId) + indexId -= 1 + return ChatListIndex(pinningIndex: nil, messageIndex: index) + } + + entries.append(.plain(user, ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: Int32.max), timestamp: Int32.max)), nil, top.isEmpty && recent.isEmpty)) + contains[user.id] = user.id + + if !top.isEmpty { + entries.insert(.separator(L10n.searchSeparatorPopular.uppercased(), chatListIndex()), at: 0) + + var count: Int32 = 0 + for peer in top { if contains[peer.id] == nil { if share.possibilityPerformTo(peer) { - entries.insert(.plain(peer, chatListIndex(), nil, true), at: 0) + entries.insert(.plain(peer, chatListIndex(), nil, count < 4), at: 0) contains[peer.id] = peer.id + count += 1 } } + if count >= 5 { + break + } } } + + if !recent.isEmpty { + + entries.insert(.separator(L10n.searchSeparatorRecent.uppercased(), chatListIndex()), at: 0) + + for rendered in recent { + if let peer = rendered.peer.chatMainPeer { + if contains[peer.id] == nil { + if share.possibilityPerformTo(peer) { + entries.insert(.plain(peer, chatListIndex(), nil, true), at: 0) + contains[peer.id] = peer.id + } + } + } + } + } + + entries.sort(by: <) + + return prepareEntries(from: previous.swap(entries), to: entries, account: context.account, initialSize: initialSize, animated: true, multipleSelection: multipleSelection, selectInteraction:selectInteraction) + } + } else { + var signal:Signal<(ChatListView,ViewUpdateType), NoError> - entries.sort(by: <) - return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) - - } - } else if search.isEmpty { - - - var signal:Signal<(ChatListView,ViewUpdateType),Void> - - switch(location) { - case let .Initial(count, _): - signal = account.viewTracker.tailChatListView(count: count) - case let .Index(index): - signal = account.viewTracker.aroundChatListView(index: index, count: 30) - } - - return signal |> deliverOnPrepareQueue |> mapToSignal { value -> Signal<(ChatListView,ViewUpdateType, [PeerId: PeerStatusStringResult]), Void> in - var peerIds:[PeerId] = [] - for entry in value.0.entries { - switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _): - peerIds.append(renderedPeer.peerId) - default: - break - } + switch(location) { + case let .Initial(count, _): + signal = context.account.viewTracker.tailChatListView(groupId: .root, filterPredicate: nil, count: count) + case let .Index(index, _): + signal = context.account.viewTracker.aroundChatListView(groupId: .root, filterPredicate: nil, index: index, count: 30) } - let keys = peerIds.map {PostboxViewKey.peer(peerId: $0)} - return account.postbox.combinedView(keys: keys) |> map { values -> (ChatListView,ViewUpdateType, [PeerId: PeerStatusStringResult]) in + + return signal |> deliverOnPrepareQueue |> mapToSignal { value -> Signal<(ChatListView,ViewUpdateType, [PeerId: PeerStatusStringResult], Peer), NoError> in + var peerIds:[PeerId] = [] + for entry in value.0.entries { + switch entry { + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _): + peerIds.append(renderedPeer.peerId) + default: + break + } + } - var presences:[PeerId: PeerStatusStringResult] = [:] - for value in values.views { - if let view = value.value as? PeerView { - presences[view.peerId] = stringStatus(for: view) + _ = previousChatList.swap(value.0) + + let keys = peerIds.map {PostboxViewKey.peer(peerId: $0, components: .all)} + return combineLatest(context.account.postbox.combinedView(keys: keys), context.account.postbox.loadedPeerWithId(context.peerId)) |> map { values, selfPeer in + var presences:[PeerId: PeerStatusStringResult] = [:] + for value in values.views { + if let view = value.value as? PeerView { + presences[view.peerId] = stringStatus(for: view, context: context) + } + } + + return (value.0, value.1, presences, selfPeer) + + } |> take(1) + } |> deliverOn(prepareQueue) |> take(1) |> map { value -> TableUpdateTransition in + var entries:[SelectablePeersEntry] = [] + + var contains:[PeerId:PeerId] = [:] + + var offset: Int32 = Int32.max + + if let additionTopItems = share.additionTopItems { + var index = ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: offset), timestamp: offset)) + entries.append(.separator(additionTopItems.topSeparator, index)) + offset -= 1 + + + for item in additionTopItems.items { + index = ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: offset), timestamp: offset)) + let theme = PeerStatusStringTheme() + + let status = NSAttributedString.initialize(string: item.status, color: theme.statusColor, font: theme.statusFont) + let title = NSAttributedString.initialize(string: item.peer.displayTitle, color: theme.titleColor, font: theme.titleFont) + entries.append(.plain(item.peer, index, PeerStatusStringResult(title, status), true)) + offset -= 1 } + + index = ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: offset), timestamp: offset)) + entries.append(.separator(additionTopItems.bottomSeparator, index)) + offset -= 1 } - return (value.0, value.1, presences) + if !share.excludePeerIds.contains(value.3.id) { + entries.append(.plain(value.3, ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: offset), timestamp: offset)), nil, true)) + contains[value.3.id] = value.3.id + } - } |> take(1) - } |> deliverOn(prepareQueue) |> take(1) |> map { value -> TableUpdateTransition in - var entries:[SelectablePeersEntry] = [] - - var contains:[PeerId:PeerId] = [:] - - for entry in value.0.entries { - switch entry { - case let .MessageEntry(id, _, _, _, _, renderedPeer, _): - if let peer = renderedPeer.chatMainPeer { - if contains[peer.id] == nil { - if share.possibilityPerformTo(peer) { - entries.append(.plain(peer,id, value.2[peer.id], true)) - contains[peer.id] = peer.id + for entry in value.0.entries { + switch entry { + case let .MessageEntry(id, _, _, _, _, renderedPeer, _, _, _, _): + if let main = renderedPeer.peer { + if contains[main.id] == nil { + if share.possibilityPerformTo(main) { + if let peer = renderedPeer.chatMainPeer { + if main.id.namespace == Namespaces.Peer.SecretChat { + entries.append(.secretChat(peer, main.id, id, value.2[peer.id], true)) + } else { + entries.append(.plain(peer, id, value.2[peer.id], true)) + } + } + contains[main.id] = main.id + } } } + default: + break } - default: - break } + + entries.sort(by: <) + + return prepareEntries(from: previous.swap(entries), to: entries, account: context.account, initialSize: initialSize, animated: true, multipleSelection: multipleSelection, selectInteraction:selectInteraction) } - - entries.sort(by: <) - - return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) } + + } else { - return account.postbox.searchPeers(query: search.lowercased()) |> map { - return $0.flatMap({$0.chatMainPeer}).filter({!($0 is TelegramSecretChat)}) - } |> mapToSignal { peers -> Signal<([Peer], [PeerId: PeerStatusStringResult]), Void> in - let keys = peers.map {PostboxViewKey.peer(peerId: $0.id)} - return account.postbox.combinedView(keys: keys) |> map { values -> ([Peer], [PeerId: PeerStatusStringResult]) in + + _ = previousChatList.swap(nil) + + var all = query.request.transformKeyboard + all.insert(query.request.lowercased(), at: 0) + all = all.uniqueElements + let localPeers = combineLatest(all.map { + return context.account.postbox.searchPeers(query: $0) + }) |> map { result in + return result.reduce([], { + return $0 + $1 + }) + } + + let remotePeers = Signal<[RenderedPeer], NoError>.single([]) |> then( searchPeers(account: context.account, query: query.request.lowercased()) |> map { $0.0.map {RenderedPeer($0)} + $0.1.map {RenderedPeer($0)} } ) + + return combineLatest(localPeers, remotePeers) |> map {$0 + $1} |> mapToSignal { peers -> Signal<([RenderedPeer], [PeerId: PeerStatusStringResult], Peer), NoError> in + let keys = peers.map {PostboxViewKey.peer(peerId: $0.peerId, components: .all)} + return combineLatest(context.account.postbox.combinedView(keys: keys), context.account.postbox.loadedPeerWithId(context.peerId)) |> map { values, selfPeer -> ([RenderedPeer], [PeerId: PeerStatusStringResult], Peer) in var presences:[PeerId: PeerStatusStringResult] = [:] for value in values.views { if let view = value.value as? PeerView { - presences[view.peerId] = stringStatus(for: view) + presences[view.peerId] = stringStatus(for: view, context: context) } } - return (peers, presences) + return (peers, presences, selfPeer) } |> take(1) - } |> deliverOn(prepareQueue) |> take(1) |> map { values -> TableUpdateTransition in + } |> deliverOn(prepareQueue) |> map { values -> TableUpdateTransition in var entries:[SelectablePeersEntry] = [] var contains:[PeerId:PeerId] = [:] var i:Int32 = Int32.max - for peer in values.0 { - if share.possibilityPerformTo(peer), contains[peer.id] == nil { - let index = MessageIndex(id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Cloud, id: i), timestamp: i) - entries.append(.plain(peer, ChatListIndex(pinningIndex: nil, messageIndex: index), values.1[peer.id], true)) - i -= 1 - contains[peer.id] = peer.id + if L10n.peerSavedMessages.lowercased().hasPrefix(query.request.lowercased()) || NSLocalizedString("Peer.SavedMessages", comment: "nil").lowercased().hasPrefix(query.request.lowercased()) || values.0.contains(where: {$0.peerId == context.peerId}), !share.excludePeerIds.contains(values.2.id) { + let index = MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: i), timestamp: i) + entries.append(.plain(values.2, ChatListIndex(pinningIndex: 0, messageIndex: index), nil, true)) + i -= 1 + contains[values.2.id] = values.2.id + } + for renderedPeer in values.0 { + if let main = renderedPeer.peer { + if contains[main.id] == nil { + if share.possibilityPerformTo(main) { + if let peer = renderedPeer.chatMainPeer { + + let index = MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: i), timestamp: i) + let id = ChatListIndex(pinningIndex: nil, messageIndex: index) + i -= 1 + + if main.id.namespace == Namespaces.Peer.SecretChat { + entries.append(.secretChat(peer, main.id, id, values.1[peer.id], true)) + } else { + entries.append(.plain(peer, id, values.1[peer.id], true)) + } + } + contains[main.id] = main.id + } + } } } if entries.isEmpty { @@ -629,20 +1205,42 @@ class ShareModalController: ModalViewController, Notifable { entries.sort(by: <) - return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) + return prepareEntries(from: previous.swap(entries), to: entries, account: context.account, initialSize: initialSize, animated: false, multipleSelection: multipleSelection, selectInteraction:selectInteraction) } } } |> deliverOnMainQueue - disposable.set(list.start(next: { [weak self] transition in + let signal:Signal = defaultItems |> deliverOnMainQueue |> mapToSignal { [weak self] defaultSelected in + + self?.selectInteractions.update(animated: false, { value in + var value = value + for peer in defaultSelected { + value = value.withToggledSelected(peer.id, peer: peer) + } + return value + }) + + return list + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] transition in self?.genericView.tableView.resetScrollNotifies() - self?.genericView.tableView.merge(with:transition) + self?.genericView.tableView.scroll(to: .up(false)) + self?.genericView.tableView.merge(with: transition) + self?.genericView.tableView.cancelHighlight() + self?.readyOnce() + })) request.set(.single(.Initial(100, nil))) + + self.genericView.tableView.setScrollHandler { position in + let view = previousChatList.modify({$0}) + } + } override var canBecomeResponder: Bool { @@ -650,96 +1248,256 @@ class ShareModalController: ModalViewController, Notifable { } override func becomeFirstResponder() -> Bool? { + _ = window?.makeFirstResponder(nil) return false } override func firstResponder() -> NSResponder? { - return genericView.searchView.responder + if window?.firstResponder == genericView.textView.inputView { + return genericView.textView.inputView + } + + if let event = NSApp.currentEvent { + if event.type == .keyDown { + switch event.keyCode { + case KeyboardKey.UpArrow.rawValue: + return window?.firstResponder + case KeyboardKey.DownArrow.rawValue: + return window?.firstResponder + default: + break + } + } + } + + if self.share.multipleSelection { + return genericView.tokenizedView.responder + } else { + return genericView.basicSearchView.input + } } override func returnKeyAction() -> KeyHandlerResult { - if !selectInteractions.presentation.peers.isEmpty { - share.perform(to: selectInteractions.presentation.peers.map {$0.key}) - modal?.close(true) + if let event = NSApp.currentEvent, !FastSettings.checkSendingAbility(for: event) { + return .rejected + } + return invoke() + } + + private func invoke() -> KeyHandlerResult { + if !genericView.tokenizedView.query.isEmpty { + if !genericView.tableView.isEmpty, let item = genericView.tableView.item(at: 0) as? ShortPeerRowItem { + selectInteractions.update({$0.withToggledSelected(item.peer.id, peer: item.peer)}) + } return .invoked } + if !selectInteractions.presentation.peers.isEmpty || share.alwaysEnableDone { + + let ids = selectInteractions.presentation.selected + + let account = share.context.account + + let peerAndData:Signal<[(TelegramChannel, CachedChannelData?)], NoError> = share.context.account.postbox.transaction { transaction in + var result:[(TelegramChannel, CachedChannelData?)] = [] + for id in ids { + if let peer = transaction.getPeer(id) as? TelegramChannel { + result.append((peer, transaction.getPeerCachedData(peerId: id) as? CachedChannelData)) + } + } + return result + } |> deliverOnMainQueue + + + + let signal = peerAndData |> mapToSignal { peerAndData in + return account.postbox.unsentMessageIdsView() |> take(1) |> map { + (peerAndData, Set($0.ids.map { $0.peerId })) + } + } |> deliverOnMainQueue + + _ = signal.start(next: { [weak self] (peerAndData, unsentIds) in + guard let `self` = self else { return } + let share = self.share + let comment = self.genericView.textView.string() + + enum ShareFailedTarget { + case token + case comment + } + struct ShareFailedReason { + let peerId:PeerId + let reason: String + let target: ShareFailedTarget + } + + var failed:[ShareFailedReason] = [] + + for (peer, cachedData) in peerAndData { + inner: switch peer.info { + case let .group(info): + if info.flags.contains(.slowModeEnabled) && (peer.adminRights != nil || !peer.flags.contains(.isCreator)) { + if let cachedData = cachedData, let validUntil = cachedData.slowModeValidUntilTimestamp { + if validUntil > share.context.timestamp { + failed.append(ShareFailedReason(peerId: peer.id, reason: slowModeTooltipText(validUntil - share.context.timestamp), target: .token)) + } + } + if !comment.isEmpty { + failed.append(ShareFailedReason(peerId: peer.id, reason: L10n.slowModeForwardCommentError, target: .comment)) + } + if unsentIds.contains(peer.id) { + failed.append(ShareFailedReason(peerId: peer.id, reason: L10n.slowModeMultipleError, target: .token)) + } + } + + default: + break inner + } + } + if failed.isEmpty { + self.genericView.tokenizedView.removeAllFailed(animated: true) + _ = share.perform(to: Array(ids), comment: comment).start() + self.emoji.popover?.hide() + self.close() + } else { + self.genericView.tokenizedView.markAsFailed(failed.map { + $0.peerId.toInt64() + }, animated: true) + + let last = failed.last! + + switch last.target { + case .comment: + self.genericView.textView.shake() + tooltip(for: self.genericView.bottomSeparator, text: last.reason) + case .token: + self.genericView.tokenizedView.addTooltip(for: last.peerId.toInt64(), text: last.reason) + } + } + }) + + return .invoked + } + + if share is ForwardMessagesObject { + if genericView.tableView.highlightedItem() == nil, !genericView.tableView.isEmpty { + let item = genericView.tableView.item(at: 0) + if let item = item as? ShortPeerRowItem { + item.action() + } + _ = genericView.tableView.select(item: item) + return .invoked + } + } + return .rejected } override func escapeKeyAction() -> KeyHandlerResult { - - if genericView.searchView.state == .Focus { - window?.makeFirstResponder(nil) + if genericView.tableView.highlightedItem() != nil { + genericView.tableView.cancelHighlight() + return .invoked + } + if genericView.tokenizedView.state == .Focus { + _ = window?.makeFirstResponder(nil) return .invoked } return .rejected } + private let emoji: EmojiViewController init(_ share:ShareObject) { self.share = share + emoji = EmojiViewController(share.context) super.init(frame: NSMakeRect(0, 0, 360, 400)) bar = .init(height: 0) } -// override var modalInteractions: ModalInteractions? { -// if let share = share as? ShareMessageObject { -// if let link = share.link { -// return ModalInteractions(acceptTitle:tr(.modalShare), accept:{ [weak self] in -// if let interactions = self?.selectInteractions, let share = self?.share { -// share.perform(to: interactions.presentation.selected.map({$0})) -// } -// self?.modal?.close() -// }, cancelTitle:tr(.modalCopyLink), cancel: { [weak self] in -// if let strongSelf = self, let share = strongSelf.share as? ShareMessageObject { -// -// self?.exportLinkDisposable.set(exportMessageLink(account: strongSelf.share.account, peerId: share.messageIds[0].peerId, messageId: share.messageIds[0]).start(next: { valueLink in -// if let valueLink = valueLink { -// copyToClipboard(valueLink) -// } else { -// copyToClipboard(link) -// } -// })) -// } -// -// self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied), height:50), for: 2.0, animated: true) -// }, drawBorder:true, height:40) -// } else { -// return ModalInteractions(acceptTitle:tr(.modalShare), accept:{ [weak self] in -// if let interactions = self?.selectInteractions, let share = self?.share { -// share.perform(to: interactions.presentation.selected.map({$0})) -// } -// self?.modal?.close() -// }, cancelTitle: tr(.modalCancel), drawBorder:true, height:40) -// } -// -// } else if let share = share as? ShareLinkObject { -// return ModalInteractions(acceptTitle: tr(.modalShare), accept:{ [weak self] in -// if let interactions = self?.selectInteractions, let share = self?.share { -// share.perform(to: interactions.presentation.selected.map({$0})) -// } -// self?.modal?.close() -// }, cancelTitle: tr(.modalCopyLink), cancel: { [weak self] in -// copyToClipboard(share.link) -// self?.show(toaster: ControllerToaster(text: tr(.shareLinkCopied), height:50), for: 2.0, animated: true) -// }, drawBorder:true, height:40) -// -// } else if let _ = share as? ShareContactObject { -// return ModalInteractions(acceptTitle: tr(.modalShare), accept:{ [weak self] in -// if let interactions = self?.selectInteractions, let share = self?.share { -// share.perform(to: interactions.presentation.selected.map({$0})) -// } -// self?.modal?.close() -// }, drawBorder:true, height:40) -// } -// return nil -// } + + func showEmoji(for control: Control) { + showPopover(for: control, with: emoji) + } + + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + + updateSize(frame.width, animated: animated) + + genericView.textViewUpdateHeight(height, animated) + + } + + func textViewEnterPressed(_ event: NSEvent) -> Bool { + if FastSettings.checkSendingAbility(for: event) { + _ = returnKeyAction() + return true + } + return false + } + + func textViewTextDidChange(_ string: String) { + + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + + } + + func textViewDidReachedLimit(_ textView: Any) { + genericView.textView.shake() + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + return false + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return NSMakeSize(frame.width - 40, textView.frame.height) + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return 1024 + } + + private func updateSize(_ width: CGFloat, animated: Bool) { + if let contentSize = self.window?.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(width, min(contentSize.height - 100, genericView.tableView.listHeight + max(genericView.additionHeight, 88))), animated: animated) + } + } + + override var modalInteractions: ModalInteractions? { + if !share.hasCaptionView { + return ModalInteractions(acceptTitle: share.interactionOk, accept: { [weak self] in + _ = self?.invoke() + }, drawBorder: true, height: 50, singleButton: true) + } else { + return nil + } + } + + override func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 100, genericView.tableView.listHeight + max(genericView.additionHeight, 88))), animated: false) + } + + override var dynamicSize: Bool { + return true + } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) } + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + if self.share.emptyPerformOnClose { + _ = self.share.perform(to: []).start() + } + super.close(animationType: animationType) + } + deinit { disposable.dispose() tokenDisposable.dispose() diff --git a/Telegram-Mac/SharedAccountContext.swift b/Telegram-Mac/SharedAccountContext.swift new file mode 100644 index 0000000000..2c9b96e18b --- /dev/null +++ b/Telegram-Mac/SharedAccountContext.swift @@ -0,0 +1,575 @@ +// +// SharedAccountContext.swift +// Telegram +// +// Created by Mikhail Filimonov on 25/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit +import SyncCore + +private struct AccountAttributes: Equatable { + let sortIndex: Int32 + let isTestingEnvironment: Bool + let backupData: AccountBackupData? +} + + +private enum AddedAccountsResult { + case upgrading(Float) + case ready([(AccountRecordId, Account?, Int32)]) +} +private enum AddedAccountResult { + case upgrading(Float) + case ready(AccountRecordId, Account?, Int32) +} + + + + +public final class AccountWithInfo: Equatable { + public let account: Account + public let peer: Peer + + init(account: Account, peer: Peer) { + 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) { + return false + } + return true + } +} + + + +class SharedAccountContext { + let accountManager: AccountManager + var bindings: AccountContextBindings = AccountContextBindings() + + #if !SHARE + let inputSource: InputSources = InputSources() + + private let _baseSettings: Atomic = Atomic(value: BaseApplicationSettings.defaultSettings) + + var baseSettings: BaseApplicationSettings { + return _baseSettings.with { $0 } + } + #endif + + private let managedAccountDisposables = DisposableDict() + + + private var activeAccountsValue: (primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?)? + private let activeAccountsPromise = Promise<(primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?)>() + var activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account, Int32)], currentAuth: UnauthorizedAccount?), NoError> { + return self.activeAccountsPromise.get() + } + private var activeAccountsInfoValue:(primary: AccountRecordId?, accounts: [AccountWithInfo])? + private let activeAccountsWithInfoPromise = Promise<(primary: AccountRecordId?, accounts: [AccountWithInfo])>() + var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { + return self.activeAccountsWithInfoPromise.get() + } + + private var accountPhotos: [PeerId : CGImage] = [:] + private var cleaningUpAccounts = false + + private(set) var layout:SplitViewState = .none + let layoutHandler:ValuePromise = ValuePromise(ignoreRepeated:true) + + private var statusItem: NSStatusItem? + + + func updateStatusBarImage(_ image: NSImage?) -> Void { + let icon = image ?? NSImage(named: "StatusIcon") + // icon?.isTemplate = true + statusItem?.image = icon + } + + private func updateStatusBarMenuItem() { + let menu = NSMenu() + + if let activeAccountsInfoValue = activeAccountsInfoValue, activeAccountsInfoValue.accounts.count > 1 { + var activeAccountsInfoValue = activeAccountsInfoValue + for (i, value) in activeAccountsInfoValue.accounts.enumerated() { + if value.account.id == activeAccountsInfoValue.primary { + activeAccountsInfoValue.accounts.swapAt(i, 0) + break + } + } + for account in activeAccountsInfoValue.accounts { + let state: NSControl.StateValue? + if account.account.id == activeAccountsInfoValue.primary { + state = .on + } else { + state = nil + } + let image: NSImage? + if let cgImage = self.accountPhotos[account.account.peerId] { + image = NSImage(cgImage: cgImage, size: NSMakeSize(16, 16)) + } else { + image = nil + } + + menu.addItem(ContextMenuItem(account.peer.displayTitle, handler: { + self.switchToAccount(id: account.account.id, action: nil) + }, image: image, state: state)) + + if account.account.id == activeAccountsInfoValue.primary { + menu.addItem(ContextSeparatorItem()) + } + } + + + menu.addItem(ContextSeparatorItem()) + } + + menu.addItem(ContextMenuItem(L10n.statusBarActivate, handler: { + if !mainWindow.isKeyWindow { + NSApp.activate(ignoringOtherApps: true) + mainWindow.deminiaturize(nil) + } else { + NSApp.hide(nil) + } + + }, dynamicTitle: { + return !mainWindow.isKeyWindow ? L10n.statusBarActivate : L10n.statusBarHide + })) + + menu.addItem(ContextMenuItem(L10n.statusBarQuit, handler: { + NSApp.terminate(nil) + })) + + statusItem?.menu = menu + } + + private func updateStatusBar(_ show: Bool) { + if show { + if statusItem == nil { + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + } + } else { + if let statusItem = statusItem { + NSStatusBar.system.removeStatusItem(statusItem) + self.statusItem = nil + } + } + } + + private let layoutDisposable = MetaDisposable() + private let displayUpgradeProgress: (Float?) -> Void + + + + init(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters, displayUpgradeProgress: @escaping(Float?) -> Void) { + self.accountManager = accountManager + self.displayUpgradeProgress = displayUpgradeProgress + #if !SHARE + self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal in + return fetchCachedSharedResourceRepresentation(accountManager: accountManager, resource: resource, representation: representation) + } + _ = (baseAppSettings(accountManager: accountManager) |> deliverOnMainQueue).start(next: { settings in + _ = self._baseSettings.swap(settings) + self.updateStatusBar(settings.statusBar) + forceUpdateStatusBarIconByDockTile(sharedContext: self) + }) + + #endif + + + layoutDisposable.set(layoutHandler.get().start(next: { state in + self.layout = state + })) + + var supplementary: Bool = false + #if SHARE + supplementary = true + #endif + + + + + + + let differenceDisposable = MetaDisposable() + let _ = (accountManager.accountRecords() + |> map { view -> (AccountRecordId?, [AccountRecordId: AccountAttributes], (AccountRecordId, Bool)?) in + var result: [AccountRecordId: AccountAttributes] = [:] + for record in view.records { + let isLoggedOut = record.attributes.contains(where: { attribute in + return attribute is LoggedOutAccountAttribute + }) + if isLoggedOut { + continue + } + let isTestingEnvironment = record.attributes.contains(where: { attribute in + if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + return true + } else { + return false + } + }) + var backupData: AccountBackupData? + var sortIndex: Int32 = 0 + for attribute in record.attributes { + if let attribute = attribute as? AccountSortOrderAttribute { + sortIndex = attribute.order + } else if let attribute = attribute as? AccountBackupDataAttribute { + backupData = attribute.data + } + } + result[record.id] = AccountAttributes(sortIndex: sortIndex, isTestingEnvironment: isTestingEnvironment, backupData: backupData) + } + let authRecord: (AccountRecordId, Bool)? = view.currentAuthAccount.flatMap({ authAccount in + let isTestingEnvironment = authAccount.attributes.contains(where: { attribute in + if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + return true + } else { + return false + } + }) + return (authAccount.id, isTestingEnvironment) + }) + return (view.currentRecord?.id, result, authRecord) + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.0 != rhs.0 { + return false + } + if lhs.1 != rhs.1 { + return false + } + if lhs.2?.0 != rhs.2?.0 { + return false + } + if lhs.2?.1 != rhs.2?.1 { + return false + } + return true + }) + |> deliverOnMainQueue).start(next: { primaryId, records, authRecord in + var addedSignals: [Signal] = [] + var addedAuthSignal: Signal = .single(nil) + for (id, attributes) in records { + if self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == id}) == nil { + addedSignals.append(accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: id, encryptionParameters: encryptionParameters, supplementary: supplementary, rootPath: rootPath, beginWithTestingEnvironment: attributes.isTestingEnvironment, backupData: attributes.backupData, auxiliaryMethods: telegramAccountAuxiliaryMethods) + |> map { result -> AddedAccountResult in + switch result { + case let .authorized(account): + #if SHARE + setupAccount(account, fetchCachedResourceRepresentation: nil, transformOutgoingMessageMedia: nil, preFetchedResourcePath: { resource in + return nil + }) + #else + setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia, preFetchedResourcePath: { resource in + return nil + }) + #endif + + return .ready(id, account, attributes.sortIndex) + case let .upgrading(progress): + return .upgrading(progress) + default: + return .ready(id, nil, attributes.sortIndex) + } + }) + + } + } + if let authRecord = authRecord, authRecord.0 != self.activeAccountsValue?.currentAuth?.id { + addedAuthSignal = accountWithId(accountManager: accountManager, networkArguments: networkArguments, id: authRecord.0, encryptionParameters: encryptionParameters, supplementary: supplementary, rootPath: rootPath, beginWithTestingEnvironment: authRecord.1, backupData: nil, auxiliaryMethods: telegramAccountAuxiliaryMethods) + |> map { result -> UnauthorizedAccount? in + switch result { + case let .unauthorized(account): + return account + default: + return nil + } + } + } + + let mappedAddedAccounts = combineLatest(queue: .mainQueue(), addedSignals) + |> map { results -> AddedAccountsResult in + var readyAccounts: [(AccountRecordId, Account?, Int32)] = [] + var totalProgress: Float = 0.0 + var hasItemsWithProgress = false + for result in results { + switch result { + case let .ready(id, account, sortIndex): + readyAccounts.append((id, account, sortIndex)) + totalProgress += 1.0 + case let .upgrading(progress): + hasItemsWithProgress = true + totalProgress += progress + } + } + if hasItemsWithProgress, !results.isEmpty { + return .upgrading(totalProgress / Float(results.count)) + } else { + return .ready(readyAccounts) + } + } + + + + differenceDisposable.set(combineLatest(queue: .mainQueue(), mappedAddedAccounts, addedAuthSignal).start(next: { mappedAddedAccounts, authAccount in + var addedAccounts: [(AccountRecordId, Account?, Int32)] = [] + switch mappedAddedAccounts { + case let .upgrading(progress): + self.displayUpgradeProgress(progress) + return + case let .ready(value): + addedAccounts = value + } + + + var hadUpdates = false + if self.activeAccountsValue == nil { + self.activeAccountsValue = (nil, [], nil) + hadUpdates = true + } + + struct AccountPeerKey: Hashable { + let peerId: PeerId + let isTestingEnvironment: Bool + } + + + var existingAccountPeerKeys = Set() + for accountRecord in addedAccounts { + if let account = accountRecord.1 { + if existingAccountPeerKeys.contains(AccountPeerKey(peerId: account.peerId, isTestingEnvironment: account.testingEnvironment)) { + let _ = accountManager.transaction({ transaction in + transaction.updateRecord(accountRecord.0, { _ in + return nil + }) + }).start() + } else { + existingAccountPeerKeys.insert(AccountPeerKey(peerId: account.peerId, isTestingEnvironment: account.testingEnvironment)) + if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == account.id }) { + self.activeAccountsValue?.accounts.remove(at: index) + assertionFailure() + } + self.activeAccountsValue!.accounts.append((account.id, account, accountRecord.2)) + self.managedAccountDisposables.set(self.updateAccountBackupData(account: account).start(), forKey: account.id) + account.resetStateManagement() + hadUpdates = true + } + } else { + let _ = accountManager.transaction({ transaction in + transaction.updateRecord(accountRecord.0, { _ in + return nil + }) + }).start() + } + } + var removedIds: [AccountRecordId] = [] + for id in self.activeAccountsValue!.accounts.map({ $0.0 }) { + if records[id] == nil { + removedIds.append(id) + } + } + for id in removedIds { + hadUpdates = true + if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == id }) { + self.activeAccountsValue?.accounts.remove(at: index) + self.managedAccountDisposables.set(nil, forKey: id) + } + } + var primary: Account? + if let primaryId = primaryId { + if let index = self.activeAccountsValue?.accounts.firstIndex(where: { $0.0 == primaryId }) { + primary = self.activeAccountsValue?.accounts[index].1 + } + } + if primary == nil && !self.activeAccountsValue!.accounts.isEmpty { + primary = self.activeAccountsValue!.accounts.first?.1 + } + if primary !== self.activeAccountsValue!.primary { + hadUpdates = true + self.activeAccountsValue!.primary?.postbox.clearCaches() + self.activeAccountsValue!.primary = primary + } + if self.activeAccountsValue!.currentAuth?.id != authRecord?.0 { + hadUpdates = true + self.activeAccountsValue!.currentAuth?.postbox.clearCaches() + self.activeAccountsValue!.currentAuth = nil + } + if let authAccount = authAccount { + hadUpdates = true + self.activeAccountsValue!.currentAuth = authAccount + } + if hadUpdates { + self.activeAccountsValue!.accounts.sort(by: { $0.2 < $1.2 }) + self.activeAccountsPromise.set(.single(self.activeAccountsValue!)) + } + + if self.activeAccountsValue!.primary == nil && self.activeAccountsValue!.currentAuth == nil { + self.beginNewAuth(testingEnvironment: false) + } + + if (authAccount != nil || self.activeAccountsValue!.primary != nil) && !self.cleaningUpAccounts { + self.cleaningUpAccounts = true + let _ = managedCleanupAccounts(networkArguments: networkArguments, accountManager: self.accountManager, rootPath: rootPath, auxiliaryMethods: telegramAccountAuxiliaryMethods, encryptionParameters: encryptionParameters).start() + } + })) + }) + + + + + self.activeAccountsWithInfoPromise.set(self.activeAccounts + |> mapToSignal { primary, accounts, _ -> Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> in + return combineLatest(accounts.map { _, account, _ -> Signal in + let peerViewKey: PostboxViewKey = .peer(peerId: account.peerId, components: []) + return account.postbox.combinedView(keys: [peerViewKey]) + |> map { view -> AccountWithInfo? in + guard let peerView = view.views[peerViewKey] as? PeerView, let peer = peerView.peers[peerView.peerId] else { + return nil + } + return AccountWithInfo(account: account, peer: peer) + } + |> distinctUntilChanged + }) + |> map { accountsWithInfo -> (primary: AccountRecordId?, accounts: [AccountWithInfo]) in + var accountsWithInfoResult: [AccountWithInfo] = [] + for info in accountsWithInfo { + if let info = info { + accountsWithInfoResult.append(info) + } + } + return (primary?.id, accountsWithInfoResult) + } + }) + + let signal = self.activeAccountsWithInfoPromise.get() |> mapToSignal { (primary, accounts) -> Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo], [PeerId : CGImage]), NoError> in + let photos:[Signal<(PeerId, CGImage?), NoError>] = accounts.map { info in + return peerAvatarImage(account: info.account, photo: .peer(info.peer, info.peer.smallProfileImage, info.peer.displayLetters, nil), displayDimensions: NSMakeSize(32, 32)) |> map { + (info.account.peerId, $0.0) + } + } + return combineLatest(photos) |> map { photos in + let photos = photos.compactMap { + return $0.1 == nil ? nil : ($0.0, $0.1!) + } + let dict:[PeerId: CGImage] = photos.reduce([:], { result, current in + var result = result + result[current.0] = current.1 + return result + }) + return (primary, accounts, dict) + } + + } |> deliverOnMainQueue + + #if !SHARE + var spotlights:[AccountRecordId : SpotlightContext] = [:] + + _ = signal.start(next: { (primary, accounts, photos) in + self.activeAccountsInfoValue = (primary, accounts) + self.accountPhotos = photos + self.updateStatusBarMenuItem() + + #if !SHARE + spotlights.removeAll() + for info in accounts { + spotlights[info.account.id] = SpotlightContext(account: info.account) + } + #endif + }) + #endif + } + + public func beginNewAuth(testingEnvironment: Bool) { + let _ = self.accountManager.transaction({ transaction -> Void in + let _ = transaction.createAuth([AccountEnvironmentAttribute(environment: testingEnvironment ? .test : .production)]) + }).start() + } + + private var launchActions:[AccountRecordId : LaunchNavigation] = [:] + + func setLaunchAction(_ action: LaunchNavigation, for accountId: AccountRecordId) -> Void { + assert(Queue.mainQueue().isCurrent()) + launchActions[accountId] = action + } + + func getLaunchActionOnce(for accountId: AccountRecordId) -> LaunchNavigation? { + assert(Queue.mainQueue().isCurrent()) + let action = launchActions[accountId] + launchActions.removeValue(forKey: accountId) + return action + } + + private func updateAccountBackupData(account: Account) -> Signal { + return accountBackupData(postbox: account.postbox) + |> mapToSignal { backupData -> Signal in + guard let backupData = backupData else { + return .complete() + } + return self.accountManager.transaction { transaction -> Void in + transaction.updateRecord(account.id, { record in + guard let record = record else { + return nil + } + var attributes = record.attributes.filter({ !($0 is AccountBackupDataAttribute) }) + attributes.append(AccountBackupDataAttribute(data: backupData)) + return AccountRecord(id: record.id, attributes: attributes, temporarySessionId: record.temporarySessionId) + }) + } + |> ignoreValues + } + } + + + public func switchToAccount(id: AccountRecordId, action: LaunchNavigation?) { + if self.activeAccountsValue?.primary?.id == id { + return + } + if let action = action { + setLaunchAction(action, for: id) + } + + assert(Queue.mainQueue().isCurrent()) + + #if SHARE + if let activeAccountsValue = self.activeAccountsValue, let account = activeAccountsValue.accounts.first(where: {$0.0 == id}) { + var activeAccountsValue = activeAccountsValue + activeAccountsValue.primary = account.1 + self.activeAccountsPromise.set(.single(activeAccountsValue)) + self.activeAccountsValue = activeAccountsValue + } + return + #else + _ = self.accountManager.transaction({ transaction in + if transaction.getCurrent()?.0 != id { + transaction.setCurrentId(id) + } + }).start() + #endif + + } + #if !SHARE + func showCallHeader(with session:PCallSession) { + bindings.rootNavigation().callHeader?.show(true) + if let view = bindings.rootNavigation().callHeader?.view as? CallNavigationHeaderView { + view.update(with: session) + } + } + #endif + deinit { + layoutDisposable.dispose() + } + +} diff --git a/Telegram-Mac/SharedAccountInfo.swift b/Telegram-Mac/SharedAccountInfo.swift new file mode 100644 index 0000000000..4ff472c696 --- /dev/null +++ b/Telegram-Mac/SharedAccountInfo.swift @@ -0,0 +1,64 @@ +// +// SharedAccountInfo.swift +// Telegram +// +// Created by Mikhail Filimonov on 28/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + + +struct AccountNotificationKey: Codable { + let id: Data + let data: Data +} + +struct AccountDatacenterKey: Codable { + let id: Int64 + let data: Data +} + +struct AccountDatacenterAddress: Codable { + let host: String + let port: Int32 + let isMedia: Bool + let secret: Data? +} + +struct AccountDatacenterInfo: Codable { + let masterKey: AccountDatacenterKey + let addressList: [AccountDatacenterAddress] +} + +struct AccountProxyConnection: Codable { + let host: String + let port: Int32 + let username: String? + let password: String? + let secret: Data? +} + +struct StoredAccountInfo: Codable { + let id: Int64 + let primaryId: Int32 + let isTestingEnvironment: Bool + let peerName: String + let datacenters: [Int32: AccountDatacenterInfo] + let notificationKey: AccountNotificationKey +} + +struct StoredAccountInfos: Codable { + let proxy: AccountProxyConnection? + let accounts: [StoredAccountInfo] +} + +func loadAccountsData(rootPath: String) -> StoredAccountInfos { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: rootPath + "/accounts-shared-data")) else { + return StoredAccountInfos(proxy: nil, accounts: []) + } + guard let value = try? JSONDecoder().decode(StoredAccountInfos.self, from: data) else { + return StoredAccountInfos(proxy: nil, accounts: []) + } + return value +} diff --git a/Telegram-Mac/SharedNotificationManager.swift b/Telegram-Mac/SharedNotificationManager.swift new file mode 100644 index 0000000000..8b8400fb92 --- /dev/null +++ b/Telegram-Mac/SharedNotificationManager.swift @@ -0,0 +1,480 @@ +// +// SharedNotificationManager.swift +// Telegram +// +// Created by Mikhail Filimonov on 01/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore +import TGUIKit + +struct LockNotificationsData : Equatable { + let screenLock:Bool + let passcodeLock:Bool + + init() { + self.screenLock = false + self.passcodeLock = false + } + + init(screenLock: Bool, passcodeLock: Bool) { + self.screenLock = screenLock + self.passcodeLock = passcodeLock + } + + func withUpdatedScreenLock(_ lock: Bool) -> LockNotificationsData { + return LockNotificationsData(screenLock: lock, passcodeLock: passcodeLock) + } + func withUpdatedPasscodeLock(_ lock: Bool) -> LockNotificationsData { + return LockNotificationsData(screenLock: screenLock, passcodeLock: lock) + } + + static func ==(lhs:LockNotificationsData, rhs: LockNotificationsData) -> Bool { + return lhs.screenLock == rhs.screenLock && lhs.passcodeLock == rhs.screenLock + } + + var isLocked: Bool { + return screenLock || passcodeLock + } +} + +final class SharedNotificationBindings { + let navigateToChat:(Account, PeerId) -> Void + let updateCurrectController:()->Void + init(navigateToChat: @escaping(Account, PeerId) -> Void, updateCurrectController: @escaping()->Void) { + self.navigateToChat = navigateToChat + self.updateCurrectController = updateCurrectController + } +} + + +final class SharedNotificationManager : NSObject, NSUserNotificationCenterDelegate { + + private let screenLocked:Promise = Promise(LockNotificationsData()) + private var _lockedValue:LockNotificationsData = LockNotificationsData() + private let _passlock = Promise() + + var passlocked: Signal { + return _passlock.get() + } + + + private func updateLocked(_ f:(LockNotificationsData) -> LockNotificationsData) { + _lockedValue = f(_lockedValue) + screenLocked.set(.single(_lockedValue)) + } + + private let disposableDict: DisposableDict = DisposableDict() + private let accountManager: AccountManager + private var resignTimestamp:Int32? = nil + private let window: Window + + private var activeAccounts: (primary: Account?, accounts: [(AccountRecordId, Account)]) = (primary: nil, accounts: []) + private let bindings: SharedNotificationBindings + init(activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, accountManager: AccountManager, window: Window, bindings: SharedNotificationBindings) { + self.accountManager = accountManager + self.window = window + self.bindings = bindings + super.init() + + + NSUserNotificationCenter.default.delegate = self + + NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window) + + + DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenIsLocked), name: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), object: nil) + DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenIsUnlocked), name: NSNotification.Name(rawValue: "com.apple.screenIsUnlocked"), object: nil) + + + _ = (_passlock.get() |> mapToSignal { show in additionalSettings(accountManager: accountManager) |> map { (show, $0) }} |> deliverOnMainQueue |> mapToSignal { show, settings -> Signal in + if show { + let controller = PasscodeLockController(accountManager, useTouchId: settings.useTouchId, logoutImpl: { + return self.logout() + }, updateCurrectController: bindings.updateCurrectController) + closeAllModals() + closeInstantView() + closeGalleryViewer(false) + showModal(with: controller, for: window, isOverlay: true) + return .single(show) |> then( controller.doneValue |> map {_ in return false} |> take(1) ) + } + return .never() + } |> deliverOnMainQueue).start(next: { [weak self] lock in + for subview in window.contentView!.subviews { + if let subview = subview as? SplitView { + subview.isHidden = lock + break + } + } + self?.updateLocked { previous -> LockNotificationsData in + return previous.withUpdatedPasscodeLock(lock) + } + }) + + _ = (activeAccounts |> deliverOnMainQueue).start(next: { accounts in + for account in accounts.accounts { + self.startNotifyListener(with: account.1, primary: account.0 == accounts.primary?.id) + } + self.activeAccounts = accounts + }) + + + let passlock = Signal.single(Void()) |> delay(10, queue: Queue.concurrentDefaultQueue()) |> restart |> mapToSignal { () -> Signal in + return accountManager.transaction { transaction -> Int32? in + if transaction.getAccessChallengeData().isLockable { + return passcodeSettings(transaction).timeout + } else { + return nil + } + } + } |> map { [weak self] timeout -> Bool in + if let timeout = timeout { + if let resignTimestamp = self?.resignTimestamp { + let current = Int32(Date().timeIntervalSince1970) + if current - resignTimestamp > timeout { + return true + } + } + return Int64(timeout) < SystemIdleTime() + } else { + return false + } + } + |> filter { _ in + return !self._lockedValue.passcodeLock + } + |> deliverOnMainQueue + + window.set(handler: { () -> KeyHandlerResult in + + if !self._lockedValue.passcodeLock { + self._passlock.set(accountManager.transaction { transaction -> Bool in + switch transaction.getAccessChallengeData() { + case .none: + return false + default: + return true + } + }) + } + + return .invoked + }, with: self, for: .L, priority: .modal, modifierFlags: [.command]) + + + let showPasslock = passlock + + + var access: PostboxAccessChallengeData = .none + let accessSemaphore = DispatchSemaphore(value: 0) + _ = (accountManager.transaction { transaction in + access = transaction.getAccessChallengeData() + accessSemaphore.signal() + }).start() + accessSemaphore.wait() + + _passlock.set(.single(access != .none) |> then(showPasslock)) + + } + + + func logout() -> Signal { + let accountManager = self.accountManager + let signal = combineLatest(self.activeAccounts.accounts.map { logoutFromAccount(id: $0.0, accountManager: self.accountManager, alreadyLoggedOutRemotely: false) }) |> deliverOnMainQueue + let removePasscode = accountManager.transaction { $0.setAccessChallengeData(.none) } |> deliverOnMainQueue + return combineLatest(removePasscode, signal) |> ignoreValues + } + + + @objc public func windowDidBecomeKey() { + self.resignTimestamp = nil + } + + @objc public func windowDidResignKey() { + self.resignTimestamp = Int32(Date().timeIntervalSince1970) + } + + @objc func screenIsLocked() { + + if !_lockedValue.passcodeLock { + _passlock.set(accountManager.transaction { transaction -> Bool in + switch transaction.getAccessChallengeData() { + case .none: + return false + default: + return true + } + }) + } + + updateLocked { (previous) -> LockNotificationsData in + return previous.withUpdatedScreenLock(true) + } + } + + @objc func screenIsUnlocked() { + updateLocked { (previous) -> LockNotificationsData in + return previous.withUpdatedScreenLock(false) + } + } + + + var isLocked: Bool { + return _lockedValue.isLocked + } + + private var snoofEnabled: Bool = true + private var requestUserAttention: Bool = false + + func startNotifyListener(with account: Account, primary: Bool) { + let screenLocked = self.screenLocked + var alsoNotified:Set = Set() + + disposableDict.set((account.stateManager.notificationMessages |> mapToSignal { messages -> Signal<([([Message], PeerGroupId)], InAppNotificationSettings), NoError> in + return appNotificationSettings(accountManager: self.accountManager) |> take(1) |> mapToSignal { inAppSettings -> Signal<([([Message], PeerGroupId)], InAppNotificationSettings), NoError> in + self.snoofEnabled = inAppSettings.showNotificationsOutOfFocus + self.requestUserAttention = inAppSettings.requestUserAttention + if inAppSettings.enabled && inAppSettings.muteUntil < Int32(Date().timeIntervalSince1970) { + + return .single((messages.filter({$0.2 || ($0.0.isEmpty || $0.0[0].wasScheduled)}).map {($0.0, $0.1)}, inAppSettings)) + } else { + return .complete() + } + + } + } + |> mapToSignal { messages, inAppSettings -> Signal<([([Message], PeerGroupId)],[MessageId:NSImage], InAppNotificationSettings), NoError> in + + var photos:[Signal<(MessageId, CGImage?),NoError>] = [] + for message in messages.reduce([], { current, value in return current + value.0}) { + var peer = message.author + if let mainPeer = messageMainPeer(message) { + if mainPeer is TelegramChannel || mainPeer is TelegramGroup || message.wasScheduled { + peer = mainPeer + } + } + if let peer = peer { + photos.append(peerAvatarImage(account: account, photo: .peer(peer, peer.smallProfileImage, peer.displayLetters, message), genCap: false) |> map { data in return (message.id, data.0)}) + } + } + + return combineLatest(photos) |> map { resources in + var images:[MessageId:NSImage] = [:] + for (messageId,image) in resources { + if let image = image { + images[messageId] = NSImage(cgImage: image, size: NSMakeSize(50,50)) + } + } + return (messages, images, inAppSettings) + } + } |> mapToSignal { messages, images, inAppSettings -> Signal<([([Message], PeerGroupId)],[MessageId:NSImage], InAppNotificationSettings, Bool), NoError> in + return screenLocked.get() + |> take(1) + |> map { data in return (messages, images, inAppSettings, data.isLocked)} + } + |> mapToSignal { values in + return account.postbox.loadedPeerWithId(account.peerId) |> map { peer in + return (values.0, values.1, values.2, values.3, peer) + } + } |> deliverOnMainQueue).start(next: { messages, images, inAppSettings, screenIsLocked, accountPeer in + + if !primary, !inAppSettings.notifyAllAccounts { + return + } + + for (messages, groupId) in messages { + for message in messages { + + if alsoNotified.contains(message.id) { + continue + } + + if message.author?.id != account.peerId || message.wasScheduled { + var title:String = message.author?.displayTitle ?? "" + var hasReplyButton:Bool = !screenIsLocked + if let peer = message.peers[message.id.peerId] { + if peer.isSupergroup || peer.isGroup { + title = peer.displayTitle + hasReplyButton = peer.canSendMessage + } else if peer.isChannel { + hasReplyButton = false + } + } + + if message.wasScheduled { + hasReplyButton = false + } + + if screenIsLocked { + title = appName + } + + + + + var text = chatListText(account: account, for: message, applyUserName: true).string.nsstring + var subText:String? + if text.contains("\n") { + let parts = text.components(separatedBy: "\n") + text = parts[1] as NSString + subText = parts[0] + } + + if message.wasScheduled { + if message.id.peerId == account.peerId { + title = L10n.notificationReminder + } else { + title = "📆 \(title)" + } + subText = nil + } + + + if !inAppSettings.displayPreviews || message.peers[message.id.peerId] is TelegramSecretChat || screenIsLocked { + text = L10n.notificationLockedPreview.nsstring + subText = nil + } + + let notification = NSUserNotification() + + if localizedString(inAppSettings.tone) != tr(L10n.notificationSettingsToneNone) { + notification.soundName = inAppSettings.tone + } else { + notification.soundName = nil + } + + if message.muted { + notification.soundName = nil + title += " 🔕" + } + + + + if self.activeAccounts.accounts.count > 1 && !screenIsLocked { + title += " → \(accountPeer.addressName ?? accountPeer.displayTitle)" + } + + notification.title = title + notification.informativeText = text as String + notification.subtitle = subText + notification.contentImage = screenIsLocked ? nil : images[message.id] + notification.hasReplyButton = hasReplyButton + + notification.hasActionButton = !message.wasScheduled + notification.otherButtonTitle = L10n.notificationMarkAsRead + // notification.additionalActions = [NSUserNotificationAction(identifier: "read", title: "Mark as Read")] + + var dict: [String : Any] = [:] + + + if message.wasScheduled { + dict["wasScheduled"] = true + } + + + + dict["message.id"] = message.id.id + dict["message.namespace"] = message.id.namespace + dict["peer.id"] = message.id.peerId.id + dict["peer.namespace"] = message.id.peerId.namespace + dict["groupId"] = groupId.rawValue + + if screenIsLocked { + dict = [:] + } + + dict["accountId"] = account.id.int64 + dict["timestamp"] = Int32(Date().timeIntervalSince1970) + + alsoNotified.insert(message.id) + + notification.userInfo = dict + NSUserNotificationCenter.default.deliver(notification) + + + + } + } + } + }), forKey: account.id) + } + + func userNotificationCenter(_ center: NSUserNotificationCenter, didDeliver notification: NSUserNotification) { + if requestUserAttention && !window.isKeyWindow { + NSApp.requestUserAttention(.informationalRequest) + } + } + + func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { + guard let id = notification.userInfo?["accountId"] as? Int64 else { + return false + } + let accountId = AccountRecordId(rawValue: id) + + if accountId != self.activeAccounts.primary?.id { + + return true + } + + let wasScheduled = notification.userInfo?["wasScheduled"] as? Bool ?? false + + let result = !snoofEnabled || !window.isKeyWindow || wasScheduled + + + return result + } + + + @objc func userNotificationCenter(_ center: NSUserNotificationCenter, didDismissAlert notification: NSUserNotification) { + if let userInfo = notification.userInfo, let msgId = userInfo["message.id"] as? Int32, let timestamp = userInfo["timestamp"] as? Int32, let msgNamespace = userInfo["message.namespace"] as? Int32, let namespace = userInfo["peer.namespace"] as? Int32, let id = userInfo["peer.id"] as? Int32, let accountId = userInfo["accountId"] as? Int64 { + + let accountId = AccountRecordId(rawValue: accountId) + + let messageId = MessageId(peerId: PeerId(namespace: namespace, id: id), namespace: msgNamespace, id: msgId) + + guard let account = activeAccounts.accounts.first(where: {$0.0 == accountId})?.1 else { + return + } + + _ = applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: timestamp)).start() + } + } + + func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { + if let userInfo = notification.userInfo, let msgId = userInfo["message.id"] as? Int32, let msgNamespace = userInfo["message.namespace"] as? Int32, let namespace = userInfo["peer.namespace"] as? Int32, let id = userInfo["peer.id"] as? Int32, let accountId = userInfo["accountId"] as? Int64 { + + let accountId = AccountRecordId(rawValue: accountId) + + let messageId = MessageId(peerId: PeerId(namespace: namespace, id: id), namespace: msgNamespace, id: msgId) + + guard let account = activeAccounts.accounts.first(where: {$0.0 == accountId})?.1 else { + return + } + + closeAllModals() + + if notification.activationType == .replied, let text = notification.response?.string, !text.isEmpty { + var replyToMessageId:MessageId? + if messageId.peerId.namespace != Namespaces.Peer.CloudUser { + replyToMessageId = messageId + } + _ = enqueueMessages(account: account, peerId: messageId.peerId, messages: [EnqueueMessage.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: replyToMessageId, localGroupingKey: nil)]).start() + } else { + self.bindings.navigateToChat(account, messageId.peerId) + } + } else { + center.removeDeliveredNotification(notification) + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + } + } + + + +} diff --git a/Telegram-Mac/SharedWakeupManager.swift b/Telegram-Mac/SharedWakeupManager.swift new file mode 100644 index 0000000000..a1b51d5d0a --- /dev/null +++ b/Telegram-Mac/SharedWakeupManager.swift @@ -0,0 +1,137 @@ +// +// SharedWakeupManager.swift +// Telegram +// +// Created by Mikhail Filimonov on 01/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private struct AccountTasks { + let stateSynchronization: Bool + let importantTasks: AccountRunningImportantTasks + let backgroundDownloads: Bool + let backgroundAudio: Bool + let activeCalls: Bool + let userInterfaceInUse: Bool + + var isEmpty: Bool { + if self.stateSynchronization { + return false + } + if !self.importantTasks.isEmpty { + return false + } + if self.backgroundDownloads { + return false + } + if self.backgroundAudio { + return false + } + if self.activeCalls { + return false + } + if self.userInterfaceInUse { + return false + } + return true + } +} + + + +class SharedWakeupManager { + private var accountsAndTasks: [(Account, Bool, AccountTasks)] = [] + private let sharedContext: SharedAccountContext + + private var stateManagmentReseted: Set = Set() + private var ringingStatesActivated: Set = Set() + private var inForeground: Bool = false + + init(sharedContext: SharedAccountContext, inForeground: Signal) { + self.sharedContext = sharedContext + + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveWakeNote(_:)), name: NSWorkspace.screensDidWakeNotification, object: nil) + + + _ = (inForeground |> deliverOnMainQueue).start(next: { value in + self.inForeground = value + self.checkTasks() + }) + + + let signal = (sharedContext.activeAccounts |> map { ($0.0, $0.1.map { ($0.0, $0.1) }) } |> mapToSignal { primary, accounts -> Signal<[(Account, Bool, AccountTasks)], NoError> in + + let result: [Signal<(Account, Bool, AccountTasks), NoError>] = accounts.map { (_, account) -> Signal<(Account, Bool, AccountTasks), NoError> in + return account.importantTasksRunning |> map { importantTasks in + return (account, primary?.id == account.id, AccountTasks(stateSynchronization: false, importantTasks: importantTasks, backgroundDownloads: false, backgroundAudio: false, activeCalls: false, userInterfaceInUse: false)) + } + } + + return combineLatest(result) + + } |> deliverOnMainQueue) + + + _ = signal.start(next: { accountsAndTasks in + self.accountsAndTasks = accountsAndTasks + self.updateRindingsStatuses(self.accountsAndTasks.map( { $0.0 } )) + self.checkTasks() + }) + } + + private func checkTasks() { + updateAccounts() + } + + @objc func receiveWakeNote(_ notificaiton:Notification) { + for (account, _, _) in self.accountsAndTasks { + account.shouldBeServiceTaskMaster.set(.single(.never) |> then(.single(.always))) + } + } + + private func updateRindingsStatuses(_ accounts:[Account]) { + + self.ringingStatesActivated = ringingStatesActivated.intersection(accounts.map { $0.id }) + + for account in accounts { + if !ringingStatesActivated.contains(account.id) { + _ = (account.callSessionManager.ringingStates() |> deliverOn(callQueue)).start(next: { states in + pullCurrentSession( { session in + if let state = states.first { + if session != nil { + account.callSessionManager.drop(internalId: state.id, reason: .busy, debugLog: .single(nil)) + } else { + showPhoneCallWindow(PCallSession(account: account, sharedContext: self.sharedContext, peerId: state.peerId, id: state.id)) + } + } + } ) + }) + ringingStatesActivated.insert(account.id) + } + + } + + } + + private func updateAccounts() { + for (account, primary, tasks) in self.accountsAndTasks { + account.shouldBeServiceTaskMaster.set(.single(.always)) + account.shouldExplicitelyKeepWorkerConnections.set(.single(tasks.backgroundAudio)) + account.shouldKeepOnlinePresence.set(.single(primary && self.inForeground)) + account.shouldKeepBackgroundDownloadConnections.set(.single(tasks.backgroundDownloads)) + + if !stateManagmentReseted.contains(account.id) { + account.resetStateManagement() + stateManagmentReseted.insert(account.id) + } + } + } + +} diff --git a/Telegram-Mac/ShortPeerRowItem.swift b/Telegram-Mac/ShortPeerRowItem.swift index 56811cae55..06e090addf 100644 --- a/Telegram-Mac/ShortPeerRowItem.swift +++ b/Telegram-Mac/ShortPeerRowItem.swift @@ -8,20 +8,25 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit final class SelectPeerPresentation : Equatable { let selected:Set let peers:[PeerId: Peer] + let limit:Int32 + private let someFlagsAsNotice: Bool static func ==(lhs:SelectPeerPresentation, rhs:SelectPeerPresentation) -> Bool { - return lhs.selected == rhs.selected + return lhs.selected == rhs.selected && lhs.limit == rhs.limit && lhs.someFlagsAsNotice == rhs.someFlagsAsNotice } - init(_ selected:Set = Set(), peers:[PeerId: Peer] = [:]) { + init(_ selected:Set = Set(), peers:[PeerId: Peer] = [:], limit: Int32 = 0, someFlagsAsNotice:Bool = false) { self.selected = selected self.peers = peers + self.limit = limit + self.someFlagsAsNotice = someFlagsAsNotice } func deselect(peerId:PeerId) -> SelectPeerPresentation { @@ -30,10 +35,19 @@ final class SelectPeerPresentation : Equatable { selectedIds.formUnion(selected) let _ = selectedIds.remove(peerId) peers.removeValue(forKey: peerId) - return SelectPeerPresentation(selectedIds, peers: peers) + return SelectPeerPresentation(selectedIds, peers: peers, limit: limit, someFlagsAsNotice: someFlagsAsNotice) + } + + var isLimitReached: Bool { + return limit > 0 && limit == selected.count + } + + func withUpdateLimit(_ limit: Int32) -> SelectPeerPresentation { + return SelectPeerPresentation(selected, peers: peers, limit: limit, someFlagsAsNotice: someFlagsAsNotice) } func withToggledSelected(_ peerId: PeerId, peer:Peer) -> SelectPeerPresentation { + var someFlagsAsNotice: Bool = self.someFlagsAsNotice var selectedIds:Set = Set() var peers:[PeerId: Peer] = self.peers selectedIds.formUnion(selected) @@ -41,23 +55,31 @@ final class SelectPeerPresentation : Equatable { let _ = selectedIds.remove(peerId) peers.removeValue(forKey: peerId) } else { - selectedIds.insert(peerId) - peers[peerId] = peer + if limit == 0 || selected.count < limit { + selectedIds.insert(peerId) + peers[peerId] = peer + } else { + someFlagsAsNotice = !someFlagsAsNotice + } } - return SelectPeerPresentation(selectedIds, peers: peers) + return SelectPeerPresentation(selectedIds, peers: peers, limit: limit, someFlagsAsNotice: someFlagsAsNotice) } } final class SelectPeerInteraction : InterfaceObserver { private(set) var presentation:SelectPeerPresentation = SelectPeerPresentation() - + var close: ()->Void = {} + var action:(PeerId)->Void = {_ in} + var singleUpdater:((SelectPeerPresentation)->Void)? = nil func update(animated:Bool = true, _ f:(SelectPeerPresentation)->SelectPeerPresentation)->Void { let oldValue = self.presentation presentation = f(presentation) + if oldValue != presentation { notifyObservers(value: presentation, oldValue:oldValue, animated:animated) } + self.singleUpdater?(presentation) } } @@ -89,9 +111,14 @@ class ShortPeerRowItem: GeneralRowItem { let interactionType:ShortPeerItemInteractionType let drawSeparatorIgnoringInset:Bool var textInset:CGFloat { - return inset.left + photoSize.width + 10.0 + (leftImage != nil ? leftImage!.backingSize.width + 5 : 0) + switch viewType { + case .legacy: + return inset.left + photoSize.width + 10.0 + (leftImage != nil ? leftImage!.backingSize.width + 5 : 0) + case let .modern(_, insets): + return photoSize.width + min(10, insets.left) + (leftImage != nil ? leftImage!.backingSize.width + 5 : 0) + } } - + let badgeNode: GlobalBadgeNode? let deleteInset:CGFloat let photoSize:NSSize @@ -100,52 +127,78 @@ class ShortPeerRowItem: GeneralRowItem { private var titleNode:TextNode = TextNode() private var statusNode:TextNode = TextNode() - private var title:(TextNodeLayout, TextNode)? - private var status:(TextNodeLayout, TextNode)? + private(set) var title:(TextNodeLayout, TextNode)? + private(set) var status:(TextNodeLayout, TextNode)? - private var titleSelected:(TextNodeLayout, TextNode)? - private var statusSelected:(TextNodeLayout, TextNode)? + private(set) var titleSelected:(TextNodeLayout, TextNode)? + private(set) var statusSelected:(TextNodeLayout, TextNode)? let leftImage:CGImage? - private(set) var photo:Signal? + private(set) var photo:Signal<(CGImage?, Bool), NoError>? - + let isLookSavedMessage: Bool let titleStyle:ControlStyle let statusStyle:ControlStyle private var titleAttr:NSAttributedString? private var statusAttr:NSAttributedString? - + let inputActivity: PeerInputActivity? let drawLastSeparator:Bool - - init(_ initialSize:NSSize, peer: Peer, account:Account, stableId:AnyHashable? = nil, enabled: Bool = true, height:CGFloat = 50, photoSize:NSSize = NSMakeSize(36, 36), titleStyle:ControlStyle = ControlStyle(font: .medium(.title), foregroundColor: theme.colors.text, highlightColor: .white), titleAddition:String? = nil, leftImage:CGImage? = nil, statusStyle:ControlStyle = ControlStyle(font:.normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status:String? = nil, borderType:BorderType = [], drawCustomSeparator:Bool = true, deleteInset:CGFloat? = nil, drawLastSeparator:Bool = false, inset:NSEdgeInsets = NSEdgeInsets(left:10.0), drawSeparatorIgnoringInset: Bool = false, interactionType:ShortPeerItemInteractionType = .plain, generalType:GeneralInteractedType = .none, action:@escaping ()->Void = {}) { + let highlightVerified: Bool + let highlightOnHover: Bool + let alwaysHighlight: Bool + private let contextMenuItems:()->Signal<[ContextMenuItem], NoError> + fileprivate let _peerId: PeerId? + init(_ initialSize:NSSize, peer: Peer, account:Account, peerId: PeerId? = nil, stableId:AnyHashable? = nil, enabled: Bool = true, height:CGFloat = 50, photoSize:NSSize = NSMakeSize(36, 36), titleStyle:ControlStyle = ControlStyle(font: .medium(.title), foregroundColor: theme.colors.text, highlightColor: .white), titleAddition:String? = nil, leftImage:CGImage? = nil, statusStyle:ControlStyle = ControlStyle(font:.normal(.text), foregroundColor: theme.colors.grayText, highlightColor:.white), status:String? = nil, borderType:BorderType = [], drawCustomSeparator:Bool = true, isLookSavedMessage: Bool = false, deleteInset:CGFloat? = nil, drawLastSeparator:Bool = false, inset:NSEdgeInsets = NSEdgeInsets(left:10.0), drawSeparatorIgnoringInset: Bool = false, interactionType:ShortPeerItemInteractionType = .plain, generalType:GeneralInteractedType = .none, viewType: GeneralViewType = .legacy, action:@escaping ()->Void = {}, contextMenuItems:@escaping()->Signal<[ContextMenuItem], NoError> = { .single([]) }, inputActivity: PeerInputActivity? = nil, highlightOnHover: Bool = false, alwaysHighlight: Bool = false, badgeNode: GlobalBadgeNode? = nil, compactText: Bool = false, highlightVerified: Bool = false) { self.peer = peer + self.contextMenuItems = contextMenuItems self.account = account + self._peerId = peerId self.photoSize = photoSize self.leftImage = leftImage + self.inputActivity = inputActivity if let deleteInset = deleteInset { self.deleteInset = deleteInset } else { - self.deleteInset = inset.left + switch viewType { + case .legacy: + self.deleteInset = inset.left + case let .modern(_, insets): + self.deleteInset = insets.left + } } + + self.badgeNode = badgeNode + self.badgeNode?.customLayout = true + self.alwaysHighlight = alwaysHighlight + self.highlightOnHover = highlightOnHover self.interactionType = interactionType self.drawLastSeparator = drawLastSeparator self.drawSeparatorIgnoringInset = drawSeparatorIgnoringInset self.titleStyle = titleStyle self.statusStyle = statusStyle + self.isLookSavedMessage = isLookSavedMessage + self.highlightVerified = highlightVerified + let icon = theme.icons.searchSaved - photo = peerAvatarImage(account: account, peer: peer, displayDimensions: photoSize) - let tAttr:NSMutableAttributedString = NSMutableAttributedString() - let _ = tAttr.append(string: peer.displayTitle, color: enabled ? titleStyle.foregroundColor : theme.colors.grayText, font: self.titleStyle.font) + if isLookSavedMessage && account.peerId == peer.id { + photo = generateEmptyPhoto(photoSize, type: .icon(colors: theme.colors.peerColors(5), icon: icon, iconSize: icon.backingSize.aspectFitted(NSMakeSize(photoSize.width - 15, photoSize.height - 15)), cornerRadius: nil)) |> map {($0, false)} + } + + if let emptyAvatar = peer.emptyAvatar { + self.photo = generateEmptyPhoto(photoSize, type: emptyAvatar) |> map {($0, false)} + } + + let _ = tAttr.append(string: isLookSavedMessage && account.peerId == peer.id ? L10n.peerSavedMessages : (compactText ? peer.compactDisplayTitle + (account.testingEnvironment ? " [🤖]" : "") : peer.displayTitle), color: enabled ? titleStyle.foregroundColor : theme.colors.grayText, font: self.titleStyle.font) if let titleAddition = titleAddition { _ = tAttr.append(string: titleAddition, color: enabled ? titleStyle.foregroundColor : theme.colors.grayText, font: self.titleStyle.font) } - tAttr.addAttribute(.selectedColor, value: NSColor.white, range: tAttr.range) + tAttr.addAttribute(.selectedColor, value: theme.colors.underSelectedColor, range: tAttr.range) titleAttr = tAttr.copy() as? NSAttributedString @@ -154,17 +207,26 @@ class ShortPeerRowItem: GeneralRowItem { if let status = status { let sAttr:NSMutableAttributedString = NSMutableAttributedString() let _ = sAttr.append(string: status, color: enabled ? self.statusStyle.foregroundColor : theme.colors.grayText, font: self.statusStyle.font, coreText: true) - sAttr.addAttribute(.selectedColor, value: NSColor.white, range: sAttr.range) + sAttr.addAttribute(.selectedColor, value: theme.colors.underSelectedColor, range: sAttr.range) statusAttr = sAttr.copy() as? NSAttributedString } - super.init(initialSize, height: height, stableId: stableId ?? AnyHashable(peer.id), type:generalType, action:action, drawCustomSeparator:drawCustomSeparator, border:borderType,inset:inset, enabled: enabled) + super.init(initialSize, height: height, stableId: stableId ?? AnyHashable(peerId ?? peer.id), type:generalType, viewType: viewType, action:action, drawCustomSeparator:drawCustomSeparator, border:borderType,inset:inset, enabled: enabled) } + var peerId: PeerId { + return _peerId ?? peer.id + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + return contextMenuItems() + } + override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let result = super.makeSize(width, oldWidth: oldWidth) prepare(self.isSelected) - return super.makeSize(width, oldWidth: oldWidth) + return result } var textAdditionInset:CGFloat { @@ -179,7 +241,7 @@ class ShortPeerRowItem: GeneralRowItem { case .selectable(_): addition += 30 case .deletable: - addition += 30 + addition += 24 + 12 default: break } @@ -189,20 +251,47 @@ class ShortPeerRowItem: GeneralRowItem { addition += 48 case .selectable: addition += 20 - case .context: - addition += 48 + case let .context(text): + let attr = NSAttributedString.initialize(string: text, color: .text, font: statusStyle.font) + addition += attr.size().width + 10 + case let .nextContext(text): + let attr = NSAttributedString.initialize(string: text, color: .text, font: statusStyle.font) + addition += attr.size().width + 10 default: break } - if let titleAttr = titleAttr { - title = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - (inset.right == 0 ? 10 : inset.right) - addition - textAdditionInset, 20), nil,false, .left) - titleSelected = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - inset.right - addition - textAdditionInset, 20), nil,true, .left) + if let _ = badgeNode { + addition += 40 + } + + if self.peer.isScam { + addition += 20 } - if let statusAttr = statusAttr { - status = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - (inset.right == 0 ? 10 : inset.right) - addition - textAdditionInset, 20), nil,false, .left) - statusSelected = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - inset.right - addition - textAdditionInset, 20), nil,true, .left) + + switch viewType { + case .legacy: + if let titleAttr = titleAttr { + title = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - (inset.right == 0 ? 10 : inset.right) - addition - textAdditionInset, 20), nil,false, .left) + titleSelected = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - (inset.right == 0 ? 10 : inset.right) - addition - textAdditionInset, 20), nil,true, .left) + } + if let statusAttr = statusAttr { + status = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - (inset.right == 0 ? 10 : inset.right) - addition - textAdditionInset, 20), nil,false, .left) + statusSelected = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, NSMakeSize(self.size.width - textInset - inset.right - addition - textAdditionInset, 20), nil,true, .left) + } + case let .modern(_, insets): + let textSize = NSMakeSize(self.width - textInset - insets.left - insets.right - inset.left - inset.right - addition - textAdditionInset, 20) + if let titleAttr = titleAttr { + title = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, textSize, nil, false, .left) + titleSelected = TextNode.layoutText(maybeNode: nil, titleAttr, nil, 1, .end, textSize, nil,true, .left) + } + if let statusAttr = statusAttr { + status = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, textSize, nil,false, .left) + statusSelected = TextNode.layoutText(maybeNode: nil, statusAttr, nil, 1, .end, textSize, nil,true, .left) + } } + + } var ctxTitle:(TextNodeLayout, TextNode)? { @@ -214,4 +303,13 @@ class ShortPeerRowItem: GeneralRowItem { override func viewClass() -> AnyClass { return ShortPeerRowView.self } + + override var instantlyResize: Bool { + return true + } + + deinit { + var bp:Int = 0 + bp += 1 + } } diff --git a/Telegram-Mac/ShortPeerRowView.swift b/Telegram-Mac/ShortPeerRowView.swift index 751ef39327..0309761043 100644 --- a/Telegram-Mac/ShortPeerRowView.swift +++ b/Telegram-Mac/ShortPeerRowView.swift @@ -8,29 +8,89 @@ import Cocoa import TGUIKit - +import TelegramCore +import SyncCore +import Postbox //FB2126 class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { - + private let containerView: GeneralRowContainerView = GeneralRowContainerView(frame: NSZeroRect) private var image:AvatarControl = AvatarControl(font: .avatar(.text)) private var deleteControl:ImageButton? private var selectControl:SelectingControl? - private let container:View = View() + private let container:Control = Control() private var switchView:SwitchView? private var contextLabel:TextViewLabel? private var choiceControl:ImageView? + #if !SHARE + private var activities: ChatActivitiesModel? + #endif private let rightSeparatorView:View = View() + private let separator:View = View() + + private var hiddenStatus: Bool = true + private var badgeNode: View? = nil + required init(frame frameRect: NSRect) { super.init(frame: frameRect) container.frame = bounds container.addSubview(image) container.displayDelegate = self - addSubview(container) + containerView.addSubview(container) image.userInteractionEnabled = false - addSubview(rightSeparatorView) + containerView.addSubview(rightSeparatorView) + containerView.addSubview(separator) + + container.set(handler: { [weak self] _ in + self?.updateMouse() + }, for: .Hover) + + container.set(handler: { [weak self] _ in + self?.updateMouse() + }, for: .Normal) + + container.userInteractionEnabled = false + + addSubview(self.containerView) + + containerView.set(handler: { [weak self] _ in + self?.invokeIfNeededDown() + }, for: .Down) + + containerView.set(handler: { [weak self] _ in + self?.invokeIfNeededUp() + }, for: .Up) + + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Normal) + containerView.set(handler: { [weak self] _ in + self?.updateColors() + }, for: .Highlight) + } + + private func invokeIfNeededUp() { + if let event = NSApp.currentEvent { + super.mouseUp(with: event) + if let item = item as? ShortPeerRowItem, let table = item.table, table.alwaysOpenRowsOnMouseUp, mouseInside() { + if item.enabled { + invokeAction(item, clickCount: event.clickCount) + } + } + } + + } + private func invokeIfNeededDown() { + if let event = NSApp.currentEvent { + super.mouseDown(with: event) + if let item = item as? ShortPeerRowItem, let table = item.table, !table.alwaysOpenRowsOnMouseUp, let event = NSApp.currentEvent, mouseInside() { + if item.enabled { + invokeAction(item, clickCount: event.clickCount) + } + } + } } override var border: BorderType? { @@ -39,11 +99,29 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { } } + + + private var isRowSelected: Bool { + if let item = item as? ShortPeerRowItem { + if item.highlightOnHover { + return self.mouseInside() || item.isSelected + } else if item.alwaysHighlight { + return false + } + } + return item?.isSelected ?? false + } + override var backdorColor: NSColor { - return item?.isSelected ?? false ? theme.colors.blueSelect : theme.colors.background + if let item = item as? ShortPeerRowItem, item.alwaysHighlight { + return item.isSelected ? theme.colors.grayForeground : theme.colors.background + } + return isRowSelected ? theme.colors.accentSelect : item?.isHighlighted ?? false ? theme.colors.grayForeground : theme.colors.background } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -51,89 +129,204 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { override func draw(_ layer: CALayer, in ctx: CGContext) { if let item = item as? ShortPeerRowItem { - - ctx.setFillColor(backdorColor.cgColor) - ctx.fill(NSMakeRect(0, 0, layer.bounds.width - .borderSize, layer.bounds.height)) if layer == container.layer { - - let canSeparate: Bool = item.index != item.table!.count - 1 - - - if !item.isSelected && item.drawCustomSeparator && (canSeparate || item.drawLastSeparator) { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.textInset, container.frame.height - .borderSize, container.frame.width - (item.drawSeparatorIgnoringInset ? 0 : item.inset.right), .borderSize)) - } - - if let leftImage = item.leftImage { - let focus = container.focus(leftImage.backingSize) - ctx.draw(leftImage, in: NSMakeRect(item.inset.left, focus.minY, focus.width, focus.height)) - } - - if let title = item.ctxTitle { - var tY = NSMinY(focus(title.0.size)) - - if let status = item.ctxStatus { - let t = title.0.size.height + status.0.size.height + 1.0 - tY = (NSHeight(self.frame) - t) / 2.0 + switch item.viewType { + case .legacy: + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(NSMakeRect(0, 0, layer.bounds.width - .borderSize, layer.bounds.height)) + } + if let leftImage = item.leftImage { + let focus = container.focus(leftImage.backingSize) + ctx.draw(leftImage, in: NSMakeRect(item.inset.left, focus.minY, focus.width, focus.height)) + } + if let title = (isRowSelected ? item.titleSelected : item.title) { + var tY = NSMinY(focus(title.0.size)) + + if let status = (isRowSelected ? item.statusSelected : item.status) { + let t = title.0.size.height + status.0.size.height + 1.0 + tY = floorToScreenPixels(backingScaleFactor, (self.frame.height - t) / 2.0) + + let sY = tY + title.0.size.height + 1.0 + if hiddenStatus { + status.1.draw(NSMakeRect(item.textInset, sY, status.0.size.width, status.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } - let sY = tY + title.0.size.height + 1.0 - status.1.draw(NSMakeRect(item.textInset, sY, status.0.size.width, status.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) + title.1.draw(NSMakeRect(item.textInset, tY, title.0.size.width, title.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + if item.peer.isVerified && item.highlightVerified { + ctx.draw(isRowSelected ? theme.icons.verifyDialogActive : theme.icons.verifyDialog, in: NSMakeRect(item.textInset + title.0.size.width - 1, tY - 3, 24, 24)) + } + if item.peer.isScam && item.highlightVerified { + ctx.draw(isRowSelected ? theme.icons.scamActive : theme.icons.scam, in: NSMakeRect(item.textInset + title.0.size.width + 5, tY + 1, theme.icons.scam.backingSize.width, theme.icons.scam.backingSize.height)) + } + } + case .modern: + if backingScaleFactor == 1.0 { + ctx.setFillColor(backdorColor.cgColor) + ctx.fill(NSMakeRect(0, 0, layer.bounds.width - .borderSize, layer.bounds.height)) + } + if let leftImage = item.leftImage { + let focus = container.focus(leftImage.backingSize) + ctx.draw(leftImage, in: NSMakeRect(0, focus.minY, focus.width, focus.height)) + } + if let title = (isRowSelected ? item.titleSelected : item.title) { + var tY = NSMinY(focus(title.0.size)) + + if let status = (isRowSelected ? item.statusSelected : item.status) { + let t = title.0.size.height + status.0.size.height + 1.0 + tY = (NSHeight(self.frame) - t) / 2.0 + + let sY = tY + title.0.size.height + 1.0 + if hiddenStatus { + status.1.draw(NSMakeRect(item.textInset, sY, status.0.size.width, status.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } + + title.1.draw(NSMakeRect(item.textInset, tY, title.0.size.width, title.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + + if item.peer.isVerified && item.highlightVerified { + ctx.draw(isRowSelected ? theme.icons.verifyDialogActive : theme.icons.verifyDialog, in: NSMakeRect(item.textInset + title.0.size.width - 1, tY - 3, 24, 24)) + } + if item.peer.isScam && item.highlightVerified { + ctx.draw(isRowSelected ? theme.icons.scamActive : theme.icons.scam, in: NSMakeRect(item.textInset + title.0.size.width + 5, tY + 1, theme.icons.scam.backingSize.width, theme.icons.scam.backingSize.height)) + } } - - title.1.draw(NSMakeRect(item.textInset, tY, title.0.size.width, title.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } - } else { - super.draw(layer, in: ctx) - let canSeparate: Bool = item.index != item.table!.count - 1 - if !item.isSelected && item.drawCustomSeparator && (canSeparate || item.drawLastSeparator) { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(30, container.frame.height - .borderSize, frame.width, .borderSize)) } - } } } + override func updateColors() { + + let highlighted = backdorColor + + + self.containerView.background = backdorColor + self.separator.backgroundColor = theme.colors.border + self.contextLabel?.background = backdorColor + containerView.set(background: backdorColor, for: .Normal) + containerView.set(background: highlighted, for: .Highlight) + + guard let item = item as? ShortPeerRowItem else { + return + } + self.background = item.viewType.rowBackground + needsDisplay = true + } + override func updateMouse() { + super.updateMouse() + updateColors() + container.needsDisplay = true + guard let item = item as? ShortPeerRowItem else { + return + } + item.badgeNode?.isSelected = isRowSelected + } override func layout() { super.layout() if let item = item as? ShortPeerRowItem { - - if let border = border, border.contains(.Right) { - rightSeparatorView.isHidden = false - rightSeparatorView.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) - } else { - rightSeparatorView.isHidden = true - } - - switch item.interactionType { - case .plain: - container.frame = bounds - case .selectable: - container.frame = .init(x: 0, y: 0, width: frame.width, height: frame.height) - default : - container.frame = .init(x: 30, y: 0, width: frame.width - 30, height: frame.height) - } - - if let deleteControl = deleteControl { - deleteControl.centerY(x: item.deleteInset) - } - if let selectControl = selectControl { - selectControl.centerY(x: frame.width - selectControl.frame.width - item.inset.right) - } - image.frame = NSMakeRect(item.inset.left + (item.leftImage != nil ? item.leftImage!.backingSize.width + 5 : 0), NSMinY(focus(item.photoSize)), item.photoSize.width, item.photoSize.height) - if let switchView = switchView { - switchView.centerY(x:container.frame.width - switchView.frame.width - item.inset.right) - } - if let contextLabel = contextLabel { - contextLabel.centerY(x:frame.width - contextLabel.frame.width - item.inset.right) - } - container.needsDisplay = true - - if let choiceControl = choiceControl { - choiceControl.centerY(x: frame.width - choiceControl.frame.width - item.inset.right) + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + self.containerView.setCorners([]) + if let border = border, border.contains(.Right) { + rightSeparatorView.isHidden = false + rightSeparatorView.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + } else { + rightSeparatorView.isHidden = true + } + switch item.interactionType { + case .plain: + container.frame = bounds + case .selectable: + container.frame = .init(x: 0, y: 0, width: frame.width, height: frame.height) + default : + container.frame = .init(x: 30, y: 0, width: frame.width - 30, height: frame.height) + } + + if let deleteControl = deleteControl { + deleteControl.centerY(x: item.deleteInset) + } + if let selectControl = selectControl { + selectControl.centerY(x: frame.width - selectControl.frame.width - item.inset.right) + } + image.frame = NSMakeRect(item.inset.left + (item.leftImage != nil ? item.leftImage!.backingSize.width + 5 : 0), NSMinY(focus(item.photoSize)), item.photoSize.width, item.photoSize.height) + if let switchView = switchView { + switchView.centerY(x:container.frame.width - switchView.frame.width - item.inset.right) + } + if let contextLabel = contextLabel { + contextLabel.centerY(x:frame.width - contextLabel.frame.width - item.inset.right) + } + container.needsDisplay = true + + if let choiceControl = choiceControl { + choiceControl.centerY(x: frame.width - choiceControl.frame.width - item.inset.right) + } + + if let badgeNode = badgeNode, let itemNode = item.badgeNode { + badgeNode.setFrameSize(itemNode.size) + badgeNode.centerY(x: containerView.frame.width - badgeNode.frame.width - item.inset.left) + } + + separator.frame = NSMakeRect(item.textInset, containerView.frame.height - .borderSize, containerView.frame.width - (item.drawSeparatorIgnoringInset ? 0 : item.inset.right) - item.textInset, .borderSize) + + #if !SHARE + if let view = activities?.view { + view.setFrameOrigin(item.textInset - 2, floorToScreenPixels(backingScaleFactor, frame.height / 2 + 1)) + } + #endif + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + self.rightSeparatorView.isHidden = true + + switch item.interactionType { + case .plain: + container.frame = .init(x: innerInsets.left, y: 0, width: containerView.frame.width - innerInsets.left - innerInsets.right, height: containerView.frame.height) + case .selectable: + container.frame = .init(x: innerInsets.left, y: 0, width: containerView.frame.width - innerInsets.left - innerInsets.right, height: containerView.frame.height) + case .deletable: + let offset = innerInsets.left + 24 + innerInsets.left + container.frame = .init(x: offset, y: 0, width: containerView.frame.width - offset - innerInsets.right, height: containerView.frame.height) + } + + if let deleteControl = deleteControl { + deleteControl.centerY(x: item.deleteInset) + } + if let selectControl = selectControl { + selectControl.centerY(x: containerView.frame.width - selectControl.frame.width - innerInsets.right) + } + image.frame = NSMakeRect((item.leftImage != nil ? item.leftImage!.backingSize.width + 5 : 0), NSMinY(focus(item.photoSize)), item.photoSize.width, item.photoSize.height) + if let switchView = switchView { + switchView.centerY(x: containerView.frame.width - switchView.frame.width - innerInsets.right) + } + if let contextLabel = contextLabel { + contextLabel.centerY(x: containerView.frame.width - contextLabel.frame.width - innerInsets.right) + } + + if let choiceControl = choiceControl { + choiceControl.centerY(x: containerView.frame.width - choiceControl.frame.width - innerInsets.right) + } + if let badgeNode = badgeNode, let itemNode = item.badgeNode { + badgeNode.setFrameSize(itemNode.size) + badgeNode.centerY(x: containerView.frame.width - badgeNode.frame.width - innerInsets.right) + } + + separator.frame = NSMakeRect(container.frame.minX + item.textInset, containerView.frame.height - .borderSize, container.frame.width - item.textInset, .borderSize) + + #if !SHARE + if let view = activities?.view { + view.setFrameOrigin(item.textInset - 2, floorToScreenPixels(backingScaleFactor, frame.height / 2 + 1)) + } + #endif + + container.needsDisplay = true + } } @@ -149,9 +342,52 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { interactive = true } + let containerRect: NSRect + let separatorRect: NSRect + switch item.viewType { + case .legacy: + containerRect = CGRect(origin: NSMakePoint((interactive ? 30 : 0), 0), size: NSMakeSize(containerView.frame.width - (interactive ? 30 : 0), containerView.frame.height)) + separatorRect = NSMakeRect(item.textInset, containerRect.height - .borderSize, containerRect.width - (item.drawSeparatorIgnoringInset ? 0 : item.inset.right) - item.textInset, .borderSize) + case let .modern(_, innerInsets): + switch item.interactionType { + case .plain: + containerRect = .init(x: innerInsets.left, y: 0, width: containerView.frame.width - innerInsets.left - innerInsets.right, height: containerView.frame.height) + case .selectable: + containerRect = .init(x: innerInsets.left, y: 0, width: containerView.frame.width - innerInsets.left - innerInsets.right, height: containerView.frame.height) + case .deletable: + let offset = innerInsets.left + 24 + innerInsets.left + containerRect = .init(x: offset, y: 0, width: containerView.frame.width - offset - innerInsets.right, height: containerView.frame.height) + } + separatorRect = NSMakeRect(containerRect.minX + item.textInset, containerRect.height - .borderSize, containerRect.width - item.textInset, .borderSize) + + if let contextLabel = contextLabel { + var rect = containerView.focus(contextLabel.frame.size) + rect.origin.x = containerView.frame.width - contextLabel.frame.width - innerInsets.right + contextLabel.change(pos: rect.origin, animated: animated) + } + if let switchView = switchView { + var rect = containerView.focus(switchView.frame.size) + rect.origin.x = containerView.frame.width - switchView.frame.width - innerInsets.right + switchView.change(pos: rect.origin, animated: animated) + } + if let choiceControl = choiceControl { + var rect = containerView.focus(choiceControl.frame.size) + rect.origin.x = containerView.frame.width - choiceControl.frame.width - innerInsets.right + choiceControl.change(pos: rect.origin, animated: animated) + } + } + + + + self.separator.change(size: separatorRect.size, animated: animated) + self.separator.change(pos: separatorRect.origin, animated: animated) - self.container.change(size: NSMakeSize(frame.width - (interactive ? 30 : 0), frame.height), animated: false) - self.container.change(pos: NSMakePoint((interactive ? 30 : 0), 0), animated: animated) + + self.container.change(size: containerRect.size, animated: false) + self.container.change(pos: containerRect.origin, animated: animated) + + + switch interactionType { case .plain: @@ -181,9 +417,9 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { if selectControl == nil { selectControl = SelectingControl(unselectedImage: theme.icons.chatToggleUnselected, selectedImage: theme.icons.chatToggleSelected) } - selectControl?.set(selected: interaction.presentation.selected.contains(item.peer.id), animated: animated) + selectControl?.set(selected: interaction.presentation.selected.contains(item.peerId), animated: animated) - addSubview(selectControl!) + containerView.addSubview(selectControl!) deleteControl?.removeFromSuperview() deleteControl = nil @@ -195,13 +431,13 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { deleteControl = ImageButton() deleteControl?.autohighlight = false deleteControl?.set(image: theme.icons.deleteItem, for: .Normal) - deleteControl?.sizeToFit() + _ = deleteControl?.sizeToFit() - addSubview(deleteControl!) + containerView.addSubview(deleteControl!) deleteControl?.layer?.opacity = 0 + deleteControl?.centerY(x: -theme.icons.deleteItem.backingSize.width) } - deleteControl?.centerY(x: -theme.icons.deleteItem.backingSize.width) if item.enabled { deleteControl?.set(image: theme.icons.deleteItem, for: .Normal) @@ -215,7 +451,7 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { deleteControl?.removeAllHandlers() deleteControl?.set(handler: { [weak item] _ in if let item = item, item.enabled { - interaction.onRemove(item.peer.id) + interaction.onRemove(item.peerId) } }, for: .Click) @@ -226,11 +462,11 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { override func set(item:TableRowItem, animated:Bool = false) { - var previousType:ShortPeerItemInteractionType = .plain + let previousType:ShortPeerItemInteractionType = self.item == nil ? .plain : (self.item as? ShortPeerRowItem)!.interactionType + + + guard let item = item as? ShortPeerRowItem else {return} - if let item = self.item as? ShortPeerRowItem { - previousType = item.interactionType - } switch previousType { case let .selectable(interaction): @@ -244,6 +480,47 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { super.set(item: item, animated: animated) + containerView.setCorners(item.viewType.corners, animated: animated && item.viewType != .legacy) + + + self.border = item.border + + if let badge = item.badgeNode { + if badgeNode == nil { + badgeNode = View() + containerView.addSubview(badgeNode!) + } + badge.view = badgeNode + badge.view?.needsDisplay = true + } else { + self.badgeNode?.removeFromSuperview() + self.badgeNode = nil + } + + + #if !SHARE + if let activity = item.inputActivity { + if activities == nil { + activities = ChatActivitiesModel() + } + guard let activities = activities else {return} + + let inputActivites: (PeerId, [(Peer, PeerInputActivity)]) = (item.peerId, [(item.peer, activity)]) + + activities.update(with: inputActivites, for: max(frame.width - 60, 160), theme:theme.activity(key: 4, foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background), layout: { [weak self] show in + self?.needsLayout = true + self?.hiddenStatus = !show + self?.needsDisplay = true + self?.activities?.view?.isHidden = !show + }) + container.addSubview(activities.view!) + + } else { + hiddenStatus = true + activities?.view?.removeFromSuperview() + } + #endif + switch previousType { case let .selectable(interaction): interaction.add(observer: self) @@ -253,59 +530,66 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { break } - if let item = item as? ShortPeerRowItem { - self.border = item.border - image.setFrameSize(item.photoSize) + switch item.viewType { + case .legacy: + let canSeparate: Bool = item.index != item.table!.count - 1 + separator.isHidden = !(!isRowSelected && item.drawCustomSeparator && (canSeparate || item.drawLastSeparator)) + case let .modern(position, _): + separator.isHidden = !position.border + } + + image.setFrameSize(item.photoSize) + if let photo = item.photo { + image.setSignal(photo) + } else { image.setPeer(account: item.account, peer: item.peer) + } + + self.updateInteractionType(previousType, item.interactionType, item:item, animated:animated) + choiceControl?.removeFromSuperview() + choiceControl = nil + + switch item.type { + case let .switchable(stateback): + contextLabel?.removeFromSuperview() + contextLabel = nil + if switchView == nil { + switchView = SwitchView() + containerView.addSubview(switchView!) + } + switchView?.stateChanged = item.action + switchView?.setIsOn(stateback,animated:animated) + switchView?.isEnabled = item.enabled + case let .context(stateback:stateback): + switchView?.removeFromSuperview() + switchView = nil - self.updateInteractionType(previousType,item.interactionType, item:item, animated:animated) - choiceControl?.removeFromSuperview() - choiceControl = nil - - switch item.type { - case let .switchable(stateback: stateback): - contextLabel?.removeFromSuperview() - contextLabel = nil - if switchView == nil { - switchView = SwitchView() - container.addSubview(switchView!) - } - switchView?.stateChanged = item.action - switchView?.setIsOn(stateback(),animated:animated) - switchView?.isEnabled = item.enabled - case let .context(stateback:stateback): - switchView?.removeFromSuperview() - switchView = nil - - let label = stateback() - if !label.isEmpty { - if contextLabel == nil { - contextLabel = TextViewLabel() - addSubview(contextLabel!) - } - contextLabel?.attributedString = .initialize(string: label, color: theme.colors.grayText, font: item.statusStyle.font) - contextLabel?.sizeToFit() - } else { - contextLabel?.removeFromSuperview() - contextLabel = nil - } - case let .selectable(stateback: stateback): - if stateback() { - choiceControl = ImageView() - choiceControl?.image = theme.icons.generalSelect - choiceControl?.sizeToFit() - addSubview(choiceControl!) + let label = stateback + if !label.isEmpty { + if contextLabel == nil { + contextLabel = TextViewLabel() + containerView.addSubview(contextLabel!) } - - default: - switchView?.removeFromSuperview() - switchView = nil + contextLabel?.attributedString = .initialize(string: label, color: theme.colors.grayText, font: item.statusStyle.font) + contextLabel?.sizeToFit() + } else { contextLabel?.removeFromSuperview() contextLabel = nil - break + } + case let .selectable(stateback: stateback): + if stateback { + choiceControl = ImageView() + choiceControl?.image = theme.icons.generalSelect + choiceControl?.sizeToFit() + containerView.addSubview(choiceControl!) } - + default: + switchView?.removeFromSuperview() + switchView = nil + contextLabel?.removeFromSuperview() + contextLabel = nil + break } rightSeparatorView.backgroundColor = theme.colors.border contextLabel?.backgroundColor = backdorColor @@ -313,32 +597,28 @@ class ShortPeerRowView: TableRowView, Notifable, ViewDisplayDelegate { self.container.setNeedsDisplayLayer() } - override func mouseDown(with event: NSEvent) { - super.mouseDown(with:event) - - if let item = item as? ShortPeerRowItem { - if item.enabled { - switch item.interactionType { - case let .selectable(interaction): - interaction.update({$0.withToggledSelected(item.peer.id, peer: item.peer)}) - default: - if event.clickCount == 1 { - item.action() - self.focusAnimation() - } - } - } + func invokeAction(_ item: ShortPeerRowItem, clickCount: Int) { + switch item.interactionType { + case let .selectable(interaction): + interaction.update({$0.withToggledSelected(item.peerId, peer: item.peer)}) + default: + if clickCount <= 1 { + item.action() + // self.focusAnimation(nil) + } } } + + func notify(with value: Any, oldValue: Any, animated: Bool) { if let item = item as? ShortPeerRowItem { switch item.interactionType { case .selectable(_): if let value = value as? SelectPeerPresentation, let oldValue = oldValue as? SelectPeerPresentation { - let new = value.selected.contains(item.peer.id) - let old = oldValue.selected.contains(item.peer.id) + let new = value.selected.contains(item.peerId) + let old = oldValue.selected.contains(item.peerId) if new != old { selectControl?.set(selected: new, animated: animated) } diff --git a/Telegram-Mac/ShortcutListController.swift b/Telegram-Mac/ShortcutListController.swift new file mode 100644 index 0000000000..f145abeaae --- /dev/null +++ b/Telegram-Mac/ShortcutListController.swift @@ -0,0 +1,154 @@ +// +// ShortcutListController.swift +// Telegram +// +// Created by Mikhail Filimonov on 11.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +private func shortcutEntires() -> [InputDataEntry] { + var entries:[InputDataEntry] = [] + + var sectionId:Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + // chat + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerChat), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("chat_open_info"), data: InputDataGeneralData(name: L10n.shortcutsControllerChatOpenInfo, color: theme.colors.text, icon: nil, type: .context("→"), viewType: .firstItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("reply_to_message"), data: InputDataGeneralData(name: L10n.shortcutsControllerChatSelectMessageToReply, color: theme.colors.text, icon: nil, type: .context("⌘↑ / ⌘↓"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("edit_message"), data: InputDataGeneralData(name: L10n.shortcutsControllerChatEditLastMessage, color: theme.colors.text, icon: nil, type: .context("↑"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("edit_media"), data: InputDataGeneralData(name: L10n.shortcutsControllerChatRecordVoiceMessage, color: theme.colors.text, icon: nil, type: .context("⌘R"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("search_in_chat"), data: InputDataGeneralData(name: L10n.shortcutsControllerChatSearchMessages, color: theme.colors.text, icon: nil, type: .context("⌘F"), viewType: .lastItem, enabled: true, description: nil))) + index += 1 + + // messages + + + + //search + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerSearch), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("quick_search"), data: InputDataGeneralData(name: L10n.shortcutsControllerSearchQuickSearch, color: theme.colors.text, icon: nil, type: .context("⌘K"), viewType: .firstItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("global_search"), data: InputDataGeneralData(name: L10n.shortcutsControllerSearchGlobalSearch, color: theme.colors.text, icon: nil, type: .context("⇧⌘F"), viewType: .lastItem, enabled: true, description: nil))) + index += 1 + + + //MARKDOWN + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerMarkdown), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("markdown_bold"), data: InputDataGeneralData(name: L10n.shortcutsControllerMarkdownBold, color: theme.colors.text, icon: nil, type: .context("⌘B / **"), viewType: .firstItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("markdown_italic"), data: InputDataGeneralData(name: L10n.shortcutsControllerMarkdownItalic, color: theme.colors.text, icon: nil, type: .context("⌘I / __"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("markdown_monospace"), data: InputDataGeneralData(name: L10n.shortcutsControllerMarkdownMonospace, color: theme.colors.text, icon: nil, type: .context("⇧⌘K / `"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("markdown_url"), data: InputDataGeneralData(name: L10n.shortcutsControllerMarkdownHyperlink, color: theme.colors.text, icon: nil, type: .context("⌘U"), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("markdown_strikethrough"), data: InputDataGeneralData(name: L10n.shortcutsControllerMarkdownStrikethrough, color: theme.colors.text, icon: nil, type: .context("~~"), viewType: .lastItem, enabled: true, description: nil))) + index += 1 + + // OTHERS + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerOthers), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("lock_passcode"), data: InputDataGeneralData(name: L10n.shortcutsControllerOthersLockByPasscode, color: theme.colors.text, icon: nil, type: .context("⌘L"), viewType: .singleItem, enabled: true, description: nil))) + index += 1 + + + // MOUSE + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerMouse), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("fast_reply"), data: InputDataGeneralData(name: L10n.shortcutsControllerMouseFastReply, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerMouseFastReplyValue), viewType: .firstItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("schedule"), data: InputDataGeneralData(name: L10n.shortcutsControllerMouseScheduleMessage, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerMouseScheduleMessageValue), viewType: .lastItem, enabled: true, description: nil))) + index += 1 + + + //Trackpad Gesture + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.shortcutsControllerGestures), data: .init(color: theme.colors.listGrayText, viewType: .textTopItem))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("swipe_reply"), data: InputDataGeneralData(name: L10n.shortcutsControllerGesturesReply, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerGesturesReplyValue), viewType: .firstItem, enabled: true, description: nil))) + index += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("swipe_actions"), data: InputDataGeneralData(name: L10n.shortcutsControllerGesturesChatAction, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerGesturesChatActionValue), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("swipe_navigation"), data: InputDataGeneralData(name: L10n.shortcutsControllerGesturesNavigation, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerGesturesNavigationsValue), viewType: .innerItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: InputDataIdentifier("swipe_stickers"), data: InputDataGeneralData(name: L10n.shortcutsControllerGesturesStickers, color: theme.colors.text, icon: nil, type: .context(L10n.shortcutsControllerGesturesStickersValue), viewType: .lastItem, enabled: true, description: nil))) + index += 1 + + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + + return entries +} + +func ShortcutListController(context: AccountContext) -> ViewController { + + let controller = InputDataController(dataSignal: .single(InputDataSignalValue(entries: shortcutEntires())), title: L10n.shortcutsControllerTitle, validateData: { data in + return .fail(.none) + }, removeAfterDisappear: true, hasDone: false, identifier: "shortcuts") + + controller._abolishWhenNavigationSame = true + + return controller +} diff --git a/Telegram-Mac/SidebarCapViewController.swift b/Telegram-Mac/SidebarCapViewController.swift index 383c6dbf32..d51bf92ce8 100644 --- a/Telegram-Mac/SidebarCapViewController.swift +++ b/Telegram-Mac/SidebarCapViewController.swift @@ -8,17 +8,19 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class SidebarCapView : View { private let text:NSTextField = NSTextField() fileprivate let close:TitleButton = TitleButton() + fileprivate var restrictedByPeer: Bool = false required init(frame frameRect: NSRect) { super.init(frame: frameRect) - text.font = .normal(.custom(15)) + text.font = .normal(.header) text.drawsBackground = false // text.backgroundColor = .clear text.isSelectable = false @@ -34,18 +36,18 @@ class SidebarCapView : View { addSubview(close) - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) text.textColor = theme.colors.grayText - text.stringValue = tr(.sidebarAvalability); + text.stringValue = restrictedByPeer ? L10n.sidebarPeerRestricted : L10n.sidebarAvalability text.setFrameSize(text.sizeThatFits(NSMakeSize(300, 100))) self.background = theme.colors.background.withAlphaComponent(0.97) - close.set(color: theme.colors.blueUI, for: .Normal) - close.set(text: tr(.navigationClose), for: .Normal) - close.sizeToFit() + close.set(color: theme.colors.accent, for: .Normal) + close.set(text: tr(L10n.sidebarHide), for: .Normal) + _ = close.sizeToFit() needsLayout = true } @@ -70,41 +72,67 @@ class SidebarCapView : View { } class SidebarCapViewController: GenericViewController { - private let account:Account - init(account:Account) { - self.account = account + private let context:AccountContext + private let globalPeerDisposable = MetaDisposable() + private var inChatAbility: Bool = true { + didSet { + navigationWillChangeController() + } + } + init(_ context:AccountContext) { + self.context = context super.init() } override func viewDidLoad() { super.viewDidLoad() - navigation?.add(listener: WeakReference(value: self)) + self.navigationController = context.sharedContext.bindings.rootNavigation() + (navigationController as? MajorNavigationController)?.add(listener: WeakReference(value: self)) genericView.close.set(handler: { [weak self] _ in - self?.navigation?.closeSidebar() + self?.context.sharedContext.bindings.rootNavigation().closeSidebar() FastSettings.toggleSidebarShown(false) - self?.account.context.entertainment.closedBySide() + self?.context.sharedContext.bindings.entertainment().closedBySide() }, for: .Click) + + let postbox = self.context.account.postbox + + globalPeerDisposable.set((context.globalPeerHandler.get() |> mapToSignal { value -> Signal in + if let value = value { + switch value { + case let .peer(peerId): + return postbox.peerView(id: peerId) |> map { + return peerViewMainPeer($0)?.canSendMessage ?? false + } + } + } else { + return .single(false) + } + } |> deliverOnMainQueue).start(next: { [weak self] accept in + self?.readyOnce() + self?.inChatAbility = accept + })) } deinit { - navigation?.remove(listener: WeakReference(value: self)) - } - - var navigation:MajorNavigationController? { - return self.account.context.mainNavigation as? MajorNavigationController + } + override func navigationWillChangeController() { - self.view.setFrameSize(account.context.entertainment.frame.size) - if navigation?.controller is ChatController { + self.genericView.restrictedByPeer = !inChatAbility + self.genericView.updateLocalizationAndTheme(theme: theme) + + self.view.setFrameSize(context.sharedContext.bindings.entertainment().frame.size) + + if context.sharedContext.bindings.rootNavigation().controller is ChatController, inChatAbility { view.removeFromSuperview() } else { - self.account.context.entertainment.addSubview(view) + context.sharedContext.bindings.entertainment().addSubview(view) } - NotificationCenter.default.post(name: NSWindow.didBecomeKeyNotification, object: mainWindow) + // NotificationCenter.default.post(name: NSWindow.didBecomeKeyNotification, object: mainWindow) } diff --git a/Telegram-Mac/SignalUtils.swift b/Telegram-Mac/SignalUtils.swift index 376eab27df..c3d60caf85 100644 --- a/Telegram-Mac/SignalUtils.swift +++ b/Telegram-Mac/SignalUtils.swift @@ -7,14 +7,14 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit func countdown(_ count:Double, delay:Double) -> Signal { return Signal { subscriber in var value:Double = count subscriber.putNext(value) - var timer:SwiftSignalKitMac.Timer? = nil - timer = SwiftSignalKitMac.Timer(timeout: delay, repeat: true, completion: { + var timer:SwiftSignalKit.Timer? = nil + timer = SwiftSignalKit.Timer(timeout: delay, repeat: true, completion: { value -= delay subscriber.putNext(max(value,0)) if value <= 0 { @@ -35,9 +35,9 @@ public func `repeat`(_ delay:Double, onQueue:Queue) -> (Signal) -> S return Signal { subscriber in // let disposable:MEtadi = DisposableSet() - var timer:SwiftSignalKitMac.Timer? = nil + var timer:SwiftSignalKit.Timer? = nil - timer = SwiftSignalKitMac.Timer(timeout: delay, repeat: true, completion: { + timer = SwiftSignalKit.Timer(timeout: delay, repeat: true, completion: { _ = signal.start(next: { (next) in subscriber.putNext(next) }) diff --git a/Telegram-Mac/Signature.swift b/Telegram-Mac/Signature.swift new file mode 100644 index 0000000000..ef409bb82c --- /dev/null +++ b/Telegram-Mac/Signature.swift @@ -0,0 +1,56 @@ +// +// Signature.swift +// Telegram +// +// Created by Mikhail Filimonov on 21.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import Security +import CommonCrypto + +func evaluateApiData() -> String? { + var rawStaticCode: SecStaticCode? = nil + var result = SecStaticCodeCreateWithPath(URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL, [], &rawStaticCode) + + guard result == 0, let staticCode = rawStaticCode else { + return nil + } + + var dictionary: CFDictionary? = nil + + let flags: SecCSFlags = SecCSFlags(rawValue: kSecCSSigningInformation); + + result = SecCodeCopySigningInformation(staticCode, flags, &dictionary) + + guard result == 0, let info = dictionary as? [String: Any] else { + return nil + } + + guard let rawTrast = info[kSecCodeInfoTrust as String] else { + return nil + } + + guard let _ = info[kSecCodeInfoIdentifier as String] else { + return nil + } + + let trust = (rawTrast as! SecTrust) + let certsCount = SecTrustGetCertificateCount(trust) + var certsData: Data = Data() + + for i in 0 ..< certsCount { + if let cert = SecTrustGetCertificateAtIndex(trust, i) { + certsData.append(SecCertificateCopyData(cert) as Data) + } else { + return nil + } + } + var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH)) + certsData.withUnsafeBytes { + _ = CC_SHA1($0, CC_LONG(certsData.count), &digest) + } + let hexBytes = digest.map { String(format: "%02hhx", $0) } + return hexBytes.joined() +} diff --git a/Telegram-Mac/SoftwareVideoLayerFrameManager.swift b/Telegram-Mac/SoftwareVideoLayerFrameManager.swift new file mode 100644 index 0000000000..cf0a251a5d --- /dev/null +++ b/Telegram-Mac/SoftwareVideoLayerFrameManager.swift @@ -0,0 +1,231 @@ +// +// SoftwareVideoLayerFrameManager.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/05/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import CoreMedia + + +private let applyQueue = Queue() +private let workers = ThreadPool(threadCount: 3, threadPriority: 0.2) +private var nextWorker = 0 + +final class SoftwareVideoLayerFrameManager { + private var dataDisposable = MetaDisposable() + private let source = Atomic(value: nil) + + 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 var rotationAngle: CGFloat = 0.0 + private var aspect: CGFloat = 1.0 + + private var layerRotationAngleAndAspect: (CGFloat, CGFloat)? + + init(account: Account, fileReference: FileMediaReference, layerHolder: SampleBufferLayer) { + 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.secondaryResource = secondaryResource + self.queue = ThreadPoolQueue(threadPool: workers) + self.layerHolder = layerHolder + layerHolder.layer.videoGravity = .resizeAspectFill + layerHolder.layer.masksToBounds = true + } + + deinit { + self.dataDisposable.dispose() + } + + func start() { + let secondarySignal: Signal + if let secondaryResource = self.secondaryResource { + secondarySignal = self.account.postbox.mediaBox.resourceData(secondaryResource, option: .complete(waitUntilFetchStatus: false)) + |> map { data -> String? in + if data.complete { + return data.path + } else { + return nil + } + } + } else { + secondarySignal = .single(nil) + } + + let firstReady: Signal = combineLatest( + self.account.postbox.mediaBox.resourceData(self.resource, option: .complete(waitUntilFetchStatus: false)), + secondarySignal + ) + |> mapToSignal { first, second -> Signal in + if let second = second { + return .single(second) + } else if first.complete { + return .single(first.path) + } else { + return .complete() + } + } + + self.dataDisposable.set((firstReady |> deliverOn(applyQueue)).start(next: { [weak self] path in + if let strongSelf = self { + let _ = strongSelf.source.swap(SoftwareVideoSource(path: path)) + } + })) + } + + func tick(timestamp: Double) { + applyQueue.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.layerHolder.layer.status == .failed { + self.layerHolder.layer.flush() + } + //if frame.resetDecoder { +// self.layerHolder.layer.flushAndRemoveImage() + // } + + + /*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.layerHolder.layer.enqueue(frame.sampleBuffer) + } + } + + self.poll() + } + } + + private var polling = false + + private func poll() { + if self.frames.count < 2 && !self.polling, self.source.with ({ $0 != nil }) { + 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 + } + + applyQueue.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)") + } + if strongSelf.source.with ({ $0 == nil }) { + delay(0.2, onQueue: applyQueue.queue, closure: { [weak strongSelf] in + strongSelf?.poll() + }) + } else { + strongSelf.poll() + } + } + } + } + }) + } + } +} diff --git a/Telegram-Mac/SoftwareVideoSource.swift b/Telegram-Mac/SoftwareVideoSource.swift new file mode 100644 index 0000000000..b5c50cf3e8 --- /dev/null +++ b/Telegram-Mac/SoftwareVideoSource.swift @@ -0,0 +1,265 @@ +// +// SoftwareVideoSource.swift +// Telegram +// +// Created by Mikhail Filimonov on 24/07/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import CoreMedia +import SwiftSignalKit + +private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + if let fd = context.fd { + return Int32(read(fd, buffer, Int(bufferSize))) + } + return 0 +} + +private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + if let fd = context.fd { + if (whence & FFMPEG_AVSEEK_SIZE) != 0 { + return Int64(context.size) + } else { + lseek(fd, off_t(offset), SEEK_SET) + return offset + } + } + return 0 +} + +private final class SoftwareVideoStream { + let index: Int + let fps: CMTime + let timebase: CMTime + let duration: CMTime + let decoder: FFMpegMediaVideoFrameDecoder + let rotationAngle: Double + let aspect: Double + + init(index: Int, fps: CMTime, timebase: CMTime, duration: CMTime, decoder: FFMpegMediaVideoFrameDecoder, rotationAngle: Double, aspect: Double) { + self.index = index + self.fps = fps + self.timebase = timebase + self.duration = duration + self.decoder = decoder + self.rotationAngle = rotationAngle + self.aspect = aspect + } +} + +public final class SoftwareVideoSource { + private var readingError = false + private var videoStream: SoftwareVideoStream? + private var avIoContext: FFMpegAVIOContext? + private var avFormatContext: FFMpegAVFormatContext? + private let path: String + fileprivate let fd: Int32? + fileprivate let size: Int32 + + private var enqueuedFrames: [(MediaTrackFrame, CGFloat, CGFloat, Bool)] = [] + private var hasReadToEnd: Bool = false + + public init(path: String) { + let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals + + var s = stat() + stat(path, &s) + self.size = Int32(s.st_size) + + let fd = open(path, O_RDONLY, S_IRUSR) + if fd >= 0 { + self.fd = fd + } else { + self.fd = nil + } + + self.path = path + + let avFormatContext = FFMpegAVFormatContext() + + let ioBufferSize = 64 * 1024 + + let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback) + self.avIoContext = avIoContext + + avFormatContext.setIO(self.avIoContext!) + + if !avFormatContext.openInput() { + self.readingError = true + return + } + + if !avFormatContext.findStreamInfo() { + self.readingError = true + return + } + + self.avFormatContext = avFormatContext + + var videoStream: SoftwareVideoStream? + + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeVideo) { + let streamIndex = streamIndexNumber.int32Value + if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) { + continue + } + + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale) + + let metrics = avFormatContext.metricsForStream(at: streamIndex) + + let rotationAngle: Double = metrics.rotationAngle + let aspect = Double(metrics.width) / Double(metrics.height) + + if let codec = FFMpegAVCodec.find(forId: codecId) { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + videoStream = SoftwareVideoStream(index: Int(streamIndex), fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + break + } + } + } + } + + self.videoStream = videoStream + + if let videoStream = self.videoStream { + avFormatContext.seekFrame(forStreamIndex: Int32(videoStream.index), pts: 0, positionOnKeyframe: true) + } + } + + deinit { + if let fd = self.fd { + close(fd) + } + } + + private func readPacketInternal() -> FFMpegPacket? { + guard let avFormatContext = self.avFormatContext else { + return nil + } + + let packet = FFMpegPacket() + if avFormatContext.readFrame(into: packet) { + return packet + } else { + return nil + } + } + + func readDecodableFrame() -> (MediaTrackDecodableFrame?, Bool) { + var frames: [MediaTrackDecodableFrame] = [] + var endOfStream = false + + while !self.readingError && frames.isEmpty { + if let packet = self.readPacketInternal() { + if let videoStream = videoStream, Int(packet.streamIndex) == videoStream.index { + let packetPts = packet.pts + + let pts = CMTimeMake(value: packetPts, timescale: videoStream.timebase.timescale) + let dts = CMTimeMake(value: packet.dts, timescale: videoStream.timebase.timescale) + + let duration: CMTime + + let frameDuration = packet.duration + if frameDuration != 0 { + duration = CMTimeMake(value: frameDuration * videoStream.timebase.value, timescale: videoStream.timebase.timescale) + } else { + duration = videoStream.fps + } + + let frame = MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration) + frames.append(frame) + } + } else { + if endOfStream { + break + } else { + if let avFormatContext = self.avFormatContext, let videoStream = self.videoStream { + endOfStream = true + break + } else { + endOfStream = true + break + } + } + } + } + + return (frames.first, endOfStream) + } + + func readFrame(maxPts: CMTime?) -> (MediaTrackFrame?, CGFloat, CGFloat, Bool) { + guard let videoStream = self.videoStream, let avFormatContext = self.avFormatContext else { + return (nil, 0.0, 1.0, false) + } + + if !self.enqueuedFrames.isEmpty { + let value = self.enqueuedFrames.removeFirst() + return (value.0, value.1, value.2, value.3) + } + + let (decodableFrame, loop) = self.readDecodableFrame() + var result: (MediaTrackFrame?, CGFloat, CGFloat, Bool) + if let decodableFrame = decodableFrame { + var ptsOffset: CMTime? + if let maxPts = maxPts, CMTimeCompare(decodableFrame.pts, maxPts) < 0 { + ptsOffset = maxPts + } + result = (videoStream.decoder.decode(frame: decodableFrame, ptsOffset: ptsOffset), CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop) + } else { + result = (nil, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop) + } + if loop { + let _ = videoStream.decoder.sendEndToDecoder() + let remainingFrames = videoStream.decoder.receiveRemainingFrames(ptsOffset: maxPts) + for i in 0 ..< remainingFrames.count { + self.enqueuedFrames.append((remainingFrames[i], CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), i == remainingFrames.count - 1)) + } + videoStream.decoder.reset() + avFormatContext.seekFrame(forStreamIndex: Int32(videoStream.index), pts: 0, positionOnKeyframe: true) + + if result.0 == nil && !self.enqueuedFrames.isEmpty { + let value = self.enqueuedFrames.removeFirst() + result = (value.0, value.1, value.2, value.3) + } + } + return result + } + + func readImage() -> (CGImage?, CGFloat, CGFloat, Bool) { + if let videoStream = self.videoStream { + for _ in 0 ..< 10 { + let (decodableFrame, loop) = self.readDecodableFrame() + if let decodableFrame = decodableFrame { + if let renderedFrame = videoStream.decoder.render(frame: decodableFrame) { + return (renderedFrame, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop) + } + } + } + return (nil, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), true) + } else { + return (nil, 0.0, 1.0, false) + } + } + + public func seek(timestamp: Double) { + if let stream = self.videoStream, let avFormatContext = self.avFormatContext { + let pts = CMTimeMakeWithSeconds(timestamp, preferredTimescale: stream.timebase.timescale) + avFormatContext.seekFrame(forStreamIndex: Int32(stream.index), pts: pts.value, positionOnKeyframe: true) + stream.decoder.reset() + } + } +} diff --git a/Telegram-Mac/SoftwareVideoThumbnailLayer.swift b/Telegram-Mac/SoftwareVideoThumbnailLayer.swift new file mode 100644 index 0000000000..ca0f45a9c5 --- /dev/null +++ b/Telegram-Mac/SoftwareVideoThumbnailLayer.swift @@ -0,0 +1,74 @@ +// +// SoftwareVideoThumbnailLayer.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/05/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa + +import Foundation +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class SoftwareVideoThumbnailLayerNullAction: NSObject, CAAction { + @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { + } +} + +final class SoftwareVideoThumbnailView: NSView { + private var asolutePosition: (CGRect, CGSize)? + + var disposable = MetaDisposable() + + var ready: (() -> Void)? { + didSet { + if self.layer?.contents != nil { + self.ready?() + } + } + } + + init(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool) { + super.init(frame: .zero) + + + self.layer?.backgroundColor = NSColor.clear.cgColor + self.layer?.contentsGravity = .resizeAspectFill + self.layer?.masksToBounds = true + + if let dimensions = fileReference.media.dimensions { + self.disposable.set((mediaGridMessageVideo(postbox: account.postbox, fileReference: fileReference, scale: backingScaleFactor, synchronousLoad: synchronousLoad) + |> deliverOnMainQueue).start(next: { [weak self] transform in + var boundingSize = dimensions.size.aspectFilled(CGSize(width: 93.0, height: 93.0)) + let imageSize = boundingSize + boundingSize.width = min(200.0, boundingSize.width) + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets(), resizeMode: .fill(.clear)) + + if let image = transform.execute(arguments, transform.data)?.generateImage() { + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.layer?.contents = image + strongSelf.ready?() + } + } + } + })) + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable.dispose() + } + +} + diff --git a/Telegram-Mac/SoundEffects.swift b/Telegram-Mac/SoundEffects.swift new file mode 100644 index 0000000000..70c91a0da2 --- /dev/null +++ b/Telegram-Mac/SoundEffects.swift @@ -0,0 +1,47 @@ +// +// SoundEffects.swift +// Telegram +// +// Created by Mikhail Filimonov on 10.01.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa + +enum SoundEffect { + case quizCorrect + case quizIncorrect + case confetti + var name: String { + switch self { + case .quizCorrect: + return "quiz-correct" + case .quizIncorrect: + return "quiz-incorrect" + case .confetti: + return "confetti" + } + } + var ext: String { + switch self { + case .quizCorrect, .quizIncorrect, .confetti: + return "mp3" + } + } +} + + +func playSoundEffect(_ sound: SoundEffect) { + let afterSentSound:NSSound? = { + + let p = Bundle.main.path(forResource: sound.name, ofType: sound.ext) + var sound:NSSound? + if let p = p { + sound = NSSound(contentsOfFile: p, byReference: true) + sound?.volume = 0.1 + } + + return sound + }() + afterSentSound?.play() +} diff --git a/Telegram-Mac/Spotlight.swift b/Telegram-Mac/Spotlight.swift new file mode 100644 index 0000000000..eba38912ba --- /dev/null +++ b/Telegram-Mac/Spotlight.swift @@ -0,0 +1,187 @@ +// +// TestSpotlight.swift +// Telegram +// +// Created by Mikhail Filimonov on 20.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import CoreSpotlight +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit + +enum SpotlightIdentifierSource : Equatable { + case peerId(PeerId) + + fileprivate var stringValue: String { + switch self { + case let .peerId(peerId): + return "peerId:\(peerId.toInt64())" + } + } +} + +struct SpotlightIdentifier : Hashable { + let recordId: AccountRecordId + let source:SpotlightIdentifierSource + + + func hash(into hasher: inout Hasher) { + hasher.combine(stringValue) + } + + fileprivate var stringValue: String { + return "accountId=\(recordId.int64)&source=\(source.stringValue)" + } +} + +private func makeSearchItem(for peer: Peer, index: Int, accountPeer: Peer, accountId: AccountRecordId) -> SpotlightItem { + let key = SpotlightIdentifier(recordId: accountId, source: .peerId(peer.id)) + let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeData as String) + attributeSet.title = peer.displayTitle + " → \(accountPeer.addressName ?? accountPeer.displayTitle)" + attributeSet.contentDescription = "Popular contact in telegram" + attributeSet.thumbnailData = theme.icons.appUpdate.data + attributeSet.creator = "Telegram" + attributeSet.kind = "Contact" + + return .recentPeer(key, index, CSSearchableItem(uniqueIdentifier: key.stringValue, domainIdentifier: Bundle.main.bundleIdentifier!, attributeSet: attributeSet), peer) +} + +private enum SpotlightItem : Identifiable, Comparable { + static func == (lhs: SpotlightItem, rhs: SpotlightItem) -> Bool { + switch lhs { + case let .recentPeer(id, index, _, lhsPeer): + if case .recentPeer(id, index, _, let rhsPeer) = rhs { + return lhsPeer.isEqual(rhsPeer) + } else { + return false + } + } + } + + case recentPeer(SpotlightIdentifier, Int, CSSearchableItem, Peer) + + static func < (lhs: SpotlightItem, rhs: SpotlightItem) -> Bool { + return lhs.index < rhs.index + } + var index: Int { + switch self { + case let .recentPeer(_, index, _, _): + return index + } + } + var stableId: SpotlightIdentifier { + switch self { + case let .recentPeer(id, _, _, _): + return id + } + } + var item:CSSearchableItem { + switch self { + case let .recentPeer(_, _, item, _): + return item + } + } +} + + + +final class SpotlightContext { + let account: Account + private let disposable = MetaDisposable() + private var previousItems:[SpotlightItem] = [] + init(account: Account) { + self.account = account + if #available(OSX 10.12, *) { + let accountPeer = account.postbox.loadedPeerWithId(account.peerId) + + + let recently = recentlySearchedPeers(postbox: account.postbox) |> map { + $0.compactMap { $0.peer.chatMainPeer } + } |> distinctUntilChanged(isEqual: { previous, current -> Bool in + return previous.count == current.count + }) + + let peers:Signal<[Peer], NoError> = combineLatest(recently, recentPeers(account: account) |> mapToSignal { recent in + switch recent { + case .disabled: + return .single([]) + case let .peers(peers): + return .single(peers) + } + }) |> map { + $0 + $1 + } |> distinctUntilChanged(isEqual: { previous, current -> Bool in + return previous.count == current.count + }) + + + + let signal = combineLatest(queue: .mainQueue(), accountPeer, peers) + + + + disposable.set(signal.start(next: { [weak self] accountPeer, peers in + guard let `self` = self else { + return + } + var items: [SpotlightItem] = [] + for (i, peer) in peers.enumerated() { + items.append(makeSearchItem(for: peer, index: i, accountPeer: accountPeer, accountId: account.id)) + } + + let (delete, insert, update) = mergeListsStableWithUpdates(leftList: self.previousItems, rightList: items) + + if !insert.isEmpty || !update.isEmpty { + CSSearchableIndex.default().indexSearchableItems(insert.map { $0.1.item }, completionHandler: nil) + CSSearchableIndex.default().indexSearchableItems(update.map { $0.1.item }, completionHandler: nil) + } + + var deleted: [SpotlightItem] = [] + for index in delete.reversed() { + deleted.append(self.previousItems.remove(at: index)) + } + + self.previousItems = items + if !deleted.isEmpty { + CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: deleted.map { $0.stableId.stringValue }, completionHandler: nil) + } + })) + } + + } + + deinit { + if #available(OSX 10.12, *) { + CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: previousItems.map { $0.stableId.stringValue }, completionHandler: nil) + } + } +} + +func parseSpotlightIdentifier(_ unique: String) -> SpotlightIdentifier? { + let vars = urlVars(with: unique) + + if let source = vars["source"], let rawAccountId = vars["accountId"], let int64AccountId = Int64(rawAccountId) { + let accountId = AccountRecordId(rawValue: int64AccountId) + let sourceComponents = source.components(separatedBy: ":") + if sourceComponents.count == 2 { + switch sourceComponents[0] { + case "peerId": + if let id = Int64(sourceComponents[1]) { + let peerId = PeerId(id) + return SpotlightIdentifier(recordId: accountId, source: .peerId(peerId)) + } + default: + break + } + } + } + + + return nil +} + diff --git a/Telegram-Mac/StatisticRowItem.swift b/Telegram-Mac/StatisticRowItem.swift new file mode 100644 index 0000000000..f72aac5069 --- /dev/null +++ b/Telegram-Mac/StatisticRowItem.swift @@ -0,0 +1,312 @@ +// +// StatisticRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphUI +import GraphCore +import SwiftSignalKit +public enum ChartItemType { + case lines + case twoAxis + case pie + case bars + case step + case twoAxisStep + case hourlyStep + case area +} + + + +class StatisticRowItem: GeneralRowItem { + let collection: ChartsCollection + let controller: BaseChartController + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, collection: ChartsCollection, viewType: GeneralViewType, type: ChartItemType, getDetailsData: @escaping (Date, @escaping (String?) -> Void) -> Void) { + self.collection = collection + + let controller: BaseChartController + switch type { + case .lines: + controller = GeneralLinesChartController(chartsCollection: collection) + controller.isZoomable = false + case .twoAxis: + controller = TwoAxisLinesChartController(chartsCollection: collection) + controller.isZoomable = false + case .pie: + controller = PercentPieChartController(chartsCollection: collection) + case .area: + controller = PercentPieChartController(chartsCollection: collection, initiallyZoomed: false) + case .bars: + controller = StackedBarsChartController(chartsCollection: collection) + controller.isZoomable = false + case .step: + controller = StepBarsChartController(chartsCollection: collection) + case .twoAxisStep: + controller = TwoAxisStepBarsChartController(chartsCollection: collection) + case .hourlyStep: + controller = StepBarsChartController(chartsCollection: collection, hourly: true) + controller.isZoomable = false + } + + + + controller.getDetailsData = { date, completion in + let signal:Signal = Signal { subscriber -> Disposable in + var cancelled: Bool = false + getDetailsData(date, { detailsData in + if let detailsData = detailsData, let data = detailsData.data(using: .utf8), !cancelled { + ChartsDataManager.readChart(data: data, extraCopiesCount: 0, sync: true, success: { collection in + if !cancelled { + subscriber.putNext(collection) + subscriber.putCompletion() + } + + }) { error in + if !cancelled { + subscriber.putNext(nil) + subscriber.putCompletion() + } + } + } else { + if !cancelled { + subscriber.putNext(nil) + subscriber.putCompletion() + } + } + }) + + return ActionDisposable { + cancelled = true + } + } + + _ = showModalProgress(signal: signal, for: context.window).start(next: { collection in + completion(collection) + }) + } + self.controller = controller + + super.init(initialSize, stableId: stableId, viewType: viewType) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + private var graphHeight: CGFloat = 0 + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + graphHeight = self.controller.height(for: blockWidth - (viewType.innerInset.left + viewType.innerInset.right)) + viewType.innerInset.bottom + viewType.innerInset.top + return true + } + + override var height: CGFloat { + return graphHeight + } + + override func viewClass() -> AnyClass { + return StatisticRowView.self + } + + override var instantlyResize: Bool { + return false + } +} +class StatisticRowView: TableRowView { + private let chartView: ChartStackSection = ChartStackSection() + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.containerView) + self.containerView.addSubview(chartView) + + } + + override func updateMouse() { + super.updateMouse() + chartView.updateMouse() + } + + private var chartTheme: ChartTheme { + let chartTheme = (theme.colors.isDark ? ChartTheme.defaultNightTheme : ChartTheme.defaultDayTheme) + return ChartTheme(chartTitleColor: theme.colors.text, actionButtonColor: theme.colors.accent, chartBackgroundColor: theme.colors.background, chartLabelsColor: theme.colors.grayText, chartHelperLinesColor: chartTheme.chartHelperLinesColor, chartStrongLinesColor: chartTheme.chartStrongLinesColor, barChartStrongLinesColor: chartTheme.barChartStrongLinesColor, chartDetailsTextColor: theme.colors.grayText, chartDetailsArrowColor: theme.colors.grayText, chartDetailsViewColor: theme.colors.grayBackground, rangeViewFrameColor: chartTheme.rangeViewFrameColor, rangeViewTintColor: theme.colors.grayForeground.withAlphaComponent(0.4), rangeViewMarkerColor: chartTheme.rangeViewTintColor, rangeCropImage: chartTheme.rangeCropImage) + } + + override var backdorColor: NSColor { + return chartTheme.chartBackgroundColor + } + + override func updateColors() { + guard let item = item as? StatisticRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + } + override func layout() { + super.layout() + guard let item = item as? StatisticRowItem else { + return + } + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + chartView.frame = NSMakeRect(item.viewType.innerInset.left, item.viewType.innerInset.top, self.containerView.frame.width - item.viewType.innerInset.left - item.viewType.innerInset.right, self.containerView.frame.height - item.viewType.innerInset.top - item.viewType.innerInset.bottom) + chartView.layout() + } + + private var first: Bool = true + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? StatisticRowItem else { + return + } + + layout() + + chartView.setup(controller: item.controller, title: "Test") + + chartView.apply(theme: chartTheme, strings: ChartStrings(zoomOut: L10n.graphZoomOut, total: L10n.graphTotal), animated: false) + + if first { + chartView.layer?.animateAlpha(from: 0, to: 1, duration: 0.25) + } + first = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +class StatisticLoadingRowItem: GeneralRowItem { + fileprivate let errorTextLayout: TextViewLayout? + init(_ initialSize: NSSize, stableId: AnyHashable, error: String?) { + let height: CGFloat = 308 + GeneralViewType.singleItem.innerInset.bottom + GeneralViewType.singleItem.innerInset.top + 30 + if let error = error { + self.errorTextLayout = TextViewLayout.init(.initialize(string: error, color: theme.colors.grayText, font: .normal(.text))) + } else { + self.errorTextLayout = nil + } + super.init(initialSize, height: height, stableId: stableId, viewType: .singleItem) + _ = self.makeSize(initialSize.width) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + errorTextLayout?.measure(width: blockWidth - viewType.innerInset.left - viewType.innerInset.right) + return true + } + + override func viewClass() -> AnyClass { + return StatisticLoadingRowView.self + } + + override var instantlyResize: Bool { + return false + } +} +class StatisticLoadingRowView: TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private var errorView: TextView? + private var progressIndicator: ProgressIndicator? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(self.containerView) + + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? StatisticLoadingRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.containerView.backgroundColor = backdorColor + } + override func layout() { + super.layout() + guard let item = item as? StatisticLoadingRowItem else { + return + } + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + errorView?.center() + progressIndicator?.center() + } + + + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? StatisticLoadingRowItem else { + return + } + + if let error = item.errorTextLayout { + if self.errorView == nil { + self.errorView = TextView() + self.errorView?.isSelectable = false + self.containerView.addSubview(self.errorView!) + self.errorView?.center() + if animated { + self.errorView?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + if animated { + if let progress = self.progressIndicator { + self.progressIndicator = nil + progress.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak progress] _ in + progress?.removeFromSuperview() + }) + } + } else { + self.progressIndicator?.removeFromSuperview() + self.progressIndicator = nil + } + self.errorView?.update(error) + } else { + if self.progressIndicator == nil { + self.progressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 30, 30)) + self.containerView.addSubview(self.progressIndicator!) + self.errorView?.center() + if animated { + self.progressIndicator?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + if animated { + self.progressIndicator?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + if let errorView = self.errorView { + self.errorView = nil + errorView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak errorView] _ in + errorView?.removeFromSuperview() + }) + } + } else { + self.errorView?.removeFromSuperview() + self.errorView = nil + } + } + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/StatisticsLoadingRowItem.swift b/Telegram-Mac/StatisticsLoadingRowItem.swift new file mode 100644 index 0000000000..6ee25be7cd --- /dev/null +++ b/Telegram-Mac/StatisticsLoadingRowItem.swift @@ -0,0 +1,141 @@ +// +// StatisticsLoadingRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 18.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +class StatisticsLoadingRowItem: GeneralRowItem { + + let text:TextViewLayout? + + let context: AccountContext + init(_ initialSize: NSSize, stableId:AnyHashable, context: AccountContext, text:String? = nil, viewType: GeneralViewType = .legacy) { + self.context = context + if let text = text { + let attr = NSMutableAttributedString() + _ = attr.append(string: text, color: theme.colors.grayText, font: .normal(.title)) + attr.detectBoldColorInString(with: .medium(.title)) + self.text = TextViewLayout(attr, alignment: .center) + self.text?.measure(width: initialSize.width - 60) + } else { + self.text = nil + } + super.init(initialSize, stableId: stableId, viewType: viewType) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + text?.measure(width: width - 60) + return success + } + + override var instantlyResize: Bool { + return false + } + + override var height: CGFloat { + if let table = table { + var basic:CGFloat = 0 + table.enumerateItems(with: { [weak self] item in + if let strongSelf = self { + if item.index < strongSelf.index { + basic += item.height + } + } + return true + }) + return table.frame.height - basic + } else { + return initialSize.height + } + } + + override func viewClass() -> AnyClass { + return StatisticsLoadingRowView.self + } +} + + +class StatisticsLoadingRowView : TableRowView { + private let imageView:MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + private let textView:TextView = TextView() + private let disposable = MetaDisposable() + private let progressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 30, 30)) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(progressIndicator) + addSubview(imageView) + addSubview(textView) + textView.isSelectable = false + + + + self.imageView.change(opacity: 0, animated: false) + self.textView.change(opacity: 0, animated: false) + self.progressIndicator.change(opacity: 1, animated: false) + + let signal = Signal.complete() |> delay(1.5, queue: .mainQueue()) + + disposable.set(signal.start(completed: { [weak self] in + self?.imageView.change(opacity: 1, animated: true) + self?.textView.change(opacity: 1, animated: true) + self?.progressIndicator.change(opacity: 0, animated: true) + })) + } + + + override var backdorColor: NSColor { + if let item = item as? StatisticsLoadingRowItem { + return item.viewType.rowBackground + } else { + return super.backdorColor + } + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = backdorColor + progressIndicator.progressColor = theme.colors.text + } + + override func layout() { + super.layout() + + progressIndicator.center() + + if let item = item as? StatisticsLoadingRowItem { + textView.update(item.text) + textView.centerX(y: frame.midY + 5) + imageView.centerX(y: frame.midY - imageView.frame.height - 5) + } else { + imageView.center() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + disposable.dispose() + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item) + + if let item = item as? StatisticsLoadingRowItem { + + imageView.update(with: LocalAnimatedSticker.graph_loading.file, size: NSMakeSize(80, 80), context: item.context, parent: nil, table: item.table, parameters: LocalAnimatedSticker.graph_loading.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: false) + + self.needsLayout = true + + + } + } +} diff --git a/Telegram-Mac/StickerPackGridItem.swift b/Telegram-Mac/StickerPackGridItem.swift index e0d2ab86ac..ed23982c78 100644 --- a/Telegram-Mac/StickerPackGridItem.swift +++ b/Telegram-Mac/StickerPackGridItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit final class StickerPackGridItem: GridItem { @@ -19,39 +20,213 @@ final class StickerPackGridItem: GridItem { return nil } - let account: Account + let context: AccountContext let file: TelegramMediaFile let selected: () -> Void - let send:(TelegramMediaFile) -> Void - init(account: Account, file: TelegramMediaFile, send:@escaping(TelegramMediaFile) -> Void, selected: @escaping () -> Void) { - self.account = account + let send:(TelegramMediaFile, NSView) -> Void + init(context: AccountContext, file: TelegramMediaFile, send:@escaping(TelegramMediaFile, NSView) -> Void, selected: @escaping () -> Void) { + self.context = context self.file = file self.send = send self.selected = selected } - func node(layout: GridNodeLayout, gridNode:GridNode) -> GridItemNode { - let node = StickerGridItemView(gridNode) - node.inputNodeInteraction = EStickersInteraction(navigateToCollectionId: {_ in}, sendSticker: { [weak self] file in - self?.send(file) - }, previewStickerSet: {_ in}) - node.setup(account: self.account, file: self.file) - node.selected = self.selected - return node + func node(layout: GridNodeLayout, gridNode:GridNode, cachedNode: GridItemNode?) -> GridItemNode { + if self.file.isAnimatedSticker { + let node = AnimatedStickerGridItemView(gridNode) + node.sendFile = { [weak self] file, view in + self?.send(file, view) + } + node.setup(context: self.context, file: self.file) + node.selected = self.selected + return node + } else { + let node = StickerGridItemView(gridNode) + node.sendFile = { [weak self] file, view in + self?.send(file, view) + } + node.setup(context: self.context, file: self.file) + node.selected = self.selected + return node + } } func update(node: GridItemNode) { - guard let node = node as? StickerGridItemView else { - assertionFailure() - return + + if let node = node as? StickerGridItemView { + node.setup(context: self.context, file: self.file) + node.selected = self.selected + } else if let node = node as? AnimatedStickerGridItemView { + node.setup(context: self.context, file: self.file) + node.selected = self.selected } - node.inputNodeInteraction = EStickersInteraction(navigateToCollectionId: {_ in}, sendSticker: { [weak self] file in - self?.send(file) - }, previewStickerSet: {_ in}) - node.setup(account: self.account, file: self.file) - node.selected = self.selected } } + + +final class AnimatedStickerGridItemView: GridItemNode, ModalPreviewRowViewProtocol { + private var currentState: (AccountContext, TelegramMediaFile, CGSize)? + + private let view: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let currentState = currentState { + let reference = currentState.1.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: currentState.1.stickerReference!, media: currentState.1) : FileMediaReference.standalone(media: currentState.1) + return (.file(reference, AnimatedStickerPreviewModalView.self), view) + } + return nil + } + + override func menu(for event: NSEvent) -> NSMenu? { + return nil + } + + private let stickerFetchedDisposable = MetaDisposable() + + var sendFile: ((TelegramMediaFile, NSView)->Void)? + var selected: (() -> Void)? + + override init(_ grid:GridNode) { + super.init(grid) + + //backgroundColor = .random + //layer?.cornerRadius = .cornerRadius + addSubview(view) + view.userInteractionEnabled = false + + set(handler: { [weak self] (control) in + if let window = self?.window as? Window, let currentState = self?.currentState, let grid = self?.grid { + _ = startModalPreviewHandle(grid, window: window, context: currentState.0) + } + }, for: .LongMouseDown) + + set(handler: { [weak self] _ in + self?.click() + }, for: .SingleClick) + } + + private func click() { + if mouseInside() || view._mouseInside() { + if let (_, file, _) = currentState { + sendFile?(file, self) + } + } + } + + override func layout() { + view.center() + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + + func setup(context: AccountContext, file: TelegramMediaFile) { + let size = NSMakeSize(60, 60) + self.currentState = (context, file, size) + view.update(with: file, size: size, context: context, parent: nil, table: nil, parameters: ChatAnimatedStickerMediaLayoutParameters(playPolicy: nil, alwaysAccept: nil, cache: nil, media: file), animated: false, positionFlags: nil, approximateSynchronousValue: false) + } + + +} + + + + +final class StickerGridItemView: GridItemNode, ModalPreviewRowViewProtocol { + private var currentState: (AccountContext, TelegramMediaFile, CGSize)? + + + private let imageView: TransformImageView = TransformImageView() + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let currentState = currentState { + let reference = currentState.1.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: currentState.1.stickerReference!, media: currentState.1) : FileMediaReference.standalone(media: currentState.1) + return (.file(reference, StickerPreviewModalView.self), imageView) + } + return nil + } + + override func menu(for event: NSEvent) -> NSMenu? { + return nil + } + + private let stickerFetchedDisposable = MetaDisposable() + + var sendFile: ((TelegramMediaFile, NSView)->Void)? + var selected: (() -> Void)? + + override init(_ grid:GridNode) { + super.init(grid) + + //backgroundColor = .random + //layer?.cornerRadius = .cornerRadius + addSubview(imageView) + + + set(handler: { [weak self] (control) in + if let window = self?.window as? Window, let currentState = self?.currentState, let grid = self?.grid { + _ = startModalPreviewHandle(grid, window: window, context: currentState.0) + } + }, for: .LongMouseDown) + set(handler: { [weak self] _ in + self?.click() + }, for: .SingleClick) + } + + private func click() { + if mouseInside() || imageView._mouseInside() { + if let (_, file, _) = currentState { + self.sendFile?(file, self) + } + } + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + imageView.center() + + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + stickerFetchedDisposable.dispose() + } + + func setup(context: AccountContext, file: TelegramMediaFile) { + if let dimensions = file.dimensions?.size { + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: NSMakeSize(60, 60), boundingSize: NSMakeSize(60, 60), intrinsicInsets: NSEdgeInsets()) + imageView.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor)) + imageView.setSignal(chatMessageSticker(postbox: context.account.postbox, file: file, small: false, scale: backingScaleFactor, fetched: true), cacheImage: { result in + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + }) + + let imageSize = dimensions.aspectFitted(NSMakeSize(60, 60)) + imageView.set(arguments: arguments) + + imageView.setFrameSize(imageSize) + currentState = (context, file, dimensions) + } + } + + +} diff --git a/Telegram-Mac/StickerPackItems.swift b/Telegram-Mac/StickerPackItems.swift new file mode 100644 index 0000000000..8fc2ec09fa --- /dev/null +++ b/Telegram-Mac/StickerPackItems.swift @@ -0,0 +1,465 @@ +// +// StickerPackItems.swift +// Telegram +// +// Created by Mikhail Filimonov on 09/07/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +class StickerPackRowItem: TableRowItem { + + override var height:CGFloat { + return 40.0 + } + + override var width: CGFloat { + return 40 + } + + let info:StickerPackCollectionInfo + let topItem:StickerPackItem? + let context: AccountContext + + let _stableId:StickerPackCollectionId + override var stableId:AnyHashable { + return _stableId + } + let packIndex: Int + + init(_ initialSize:NSSize, packIndex: Int, context:AccountContext, stableId: StickerPackCollectionId, info:StickerPackCollectionInfo, topItem:StickerPackItem?) { + self.context = context + self.packIndex = packIndex + self._stableId = stableId + self.info = info + self.topItem = topItem + super.init(initialSize) + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + let context = self.context + + switch _stableId { + case let .pack(id): + items.append(ContextMenuItem.init(L10n.stickersContextArchive, handler: { + _ = removeStickerPackInteractively(postbox: context.account.postbox, id: id, option: RemoveStickerPackOption.archive).start() + })) + + + #if BETA || ALPHA || DEBUG + if let resource = info.thumbnail?.resource { + items.append(ContextMenuItem("Copy Pack Thumbnail (Dev.)", handler: { + let signal = context.account.postbox.mediaBox.resourceData(resource) |> take(1) |> deliverOnMainQueue + _ = signal.start(next: { data in + if let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + _ = getAnimatedStickerThumb(data: data, size: NSMakeSize(128, 128)).start(next: { path in + if let path = path { + let pb = NSPasteboard.general + pb.clearContents() + pb.writeObjects([NSURL(fileURLWithPath: path)]) + } + }) + } + }) + })) + } + #endif + default: + break + } + return .single(items) + } + + func contentNode()->ChatMediaContentView.Type { + return MediaAnimatedStickerView.self + } + + override func viewClass() -> AnyClass { + if let file = topItem?.file, file.isAnimatedSticker { + return AnimatedStickerPackRowView.self + } else { + return StickerPackRowView.self + } + } +} + +class RecentPackRowItem: TableRowItem { + + override var height:CGFloat { + return 40.0 + } + override var width: CGFloat { + return 40.0 + } + + let _stableId:StickerPackCollectionId + override var stableId:AnyHashable { + return _stableId + } + + init(_ initialSize:NSSize, _ stableId:StickerPackCollectionId) { + self._stableId = stableId + super.init(initialSize) + } + + override func viewClass() -> AnyClass { + return RecentPackRowView.self + } +} + + +class StickerPackRowView: HorizontalRowView { + + + private let stickerFetchedDisposable = MetaDisposable() + + private var imageView:TransformImageView? + + private let overlay:ImageButton = ImageButton() + + required init(frame frameRect:NSRect) { + super.init(frame:frameRect) + + overlay.setFrameSize(35, 35) + overlay.userInteractionEnabled = false + overlay.autohighlight = false + overlay.canHighlight = false + addSubview(overlay) + + } + + override var backdorColor: NSColor { + return .clear + } + + override func layout() { + super.layout() + + self.imageView?.center() + self.overlay.center() + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + if window == nil { + self.imageView?.removeFromSuperview() + self.imageView = nil + } else if let item = item, self.imageView == nil { + self.set(item: item, animated: false) + } + } + + deinit { + stickerFetchedDisposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item:TableRowItem, animated:Bool = false) { + + var mediaUpdated = true + if let lhs = (self.item as? StickerPackRowItem)?.topItem, let rhs = (item as? StickerPackRowItem)?.topItem { + mediaUpdated = !lhs.file.isEqual(to: rhs.file) + } + + super.set(item: item, animated: animated) + overlay.set(image: theme.icons.stickerPackSelection, for: .Normal) + overlay.set(image: theme.icons.stickerPackSelectionActive, for: .Highlight) + overlay.isSelected = item.isSelected + + if let item = item as? StickerPackRowItem { + var thumbnailItem: TelegramMediaImageRepresentation? + var resourceReference: MediaResourceReference? + + var file: TelegramMediaFile? + + + if let thumbnail = item.info.thumbnail { + thumbnailItem = thumbnail + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.info.id.id, accessHash: item.info.accessHash), resource: thumbnail.resource) + file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: item.info.id.id), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [thumbnail], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: nil, attributes: [.FileName(fileName: "sticker.webp"), .Sticker(displayText: "", packReference: .id(id: item.info.id.id, accessHash: item.info.accessHash), maskData: nil)]) + } else if let item = item.topItem, let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource { + thumbnailItem = TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource) + resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource) + file = item.file + } + + if self.imageView == nil { + self.imageView = TransformImageView() + self.addSubview(self.imageView!) + } + guard let imageView = self.imageView else { + return + } + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: NSMakeSize(30, 30), boundingSize: NSMakeSize(30, 30), intrinsicInsets: NSEdgeInsets()) + + if let thumbnailItem = thumbnailItem { + if let file = file { + imageView.setSignal(signal: cachedMedia(media: file , arguments: arguments, scale: backingScaleFactor)) + } + if !imageView.isFullyLoaded { + imageView.setSignal( chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, representation: thumbnailItem, scale: backingScaleFactor, synchronousLoad: false), cacheImage: { result in + if let file = file { + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + } + }) + } + } + imageView.set(arguments:arguments) + imageView.setFrameSize(arguments.imageSize) + if let resourceReference = resourceReference { + stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: resourceReference, statsCategory: .file).start()) + } + self.needsLayout = true + } + + + } + +} + +class RecentPackRowView: HorizontalRowView { + + var imageView:ImageView = ImageView() + + var overlay:ImageButton = ImageButton() + + required init(frame frameRect:NSRect) { + super.init(frame:frameRect) + + overlay.setFrameSize(35, 35) + overlay.userInteractionEnabled = false + overlay.autohighlight = false + overlay.canHighlight = false + imageView.setFrameSize(30, 30) + + addSubview(overlay) + addSubview(imageView) + + } + + override func layout() { + super.layout() + imageView.center() + overlay.center() + } + + deinit { + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item:TableRowItem, animated:Bool = false) { + + super.set(item: item, animated: animated) + overlay.set(image: theme.icons.stickerPackSelection, for: .Normal) + overlay.set(image: theme.icons.stickerPackSelectionActive, for: .Highlight) + + overlay.isSelected = item.isSelected + + if let item = item as? RecentPackRowItem { + self.needsLayout = true + switch item._stableId { + case .saved: + imageView.image = theme.icons.stickersTabFave + case .recent: + imageView.image = theme.icons.stickersTabRecent + case .featured: + imageView.image = theme.icons.stickers_add_featured + default: + break + } + imageView.sizeToFit() + } + needsLayout = true + } + +} + + + +class StickerSpecificPackItem: TableRowItem { + override var height:CGFloat { + return 40.0 + } + override var width: CGFloat { + return 40.0 + } + fileprivate let specificPack: (StickerPackCollectionInfo, Peer) + fileprivate let account: Account + let _stableId:StickerPackCollectionId + override var stableId:AnyHashable { + return _stableId + } + + init(_ initialSize:NSSize, stableId:StickerPackCollectionId, specificPack: (StickerPackCollectionInfo, Peer), account: Account) { + self._stableId = stableId + self.specificPack = specificPack + self.account = account + super.init(initialSize) + } + + override func viewClass() -> AnyClass { + return StickerSpecificPackView.self + } +} + +class StickerSpecificPackView: HorizontalRowView { + + + var imageView:AvatarControl = AvatarControl(font: .medium(.short)) + + var overlay:ImageButton = ImageButton() + + required init(frame frameRect:NSRect) { + super.init(frame:frameRect) + + imageView.setFrameSize(30, 30) + overlay.setFrameSize(35, 35) + overlay.userInteractionEnabled = false + overlay.autohighlight = false + overlay.canHighlight = false + imageView.userInteractionEnabled = false + addSubview(overlay) + addSubview(imageView) + } + + override func layout() { + super.layout() + imageView.center() + overlay.center() + } + + + deinit { + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item:TableRowItem, animated:Bool = false) { + super.set(item: item, animated: animated) + overlay.set(image: theme.icons.stickerPackSelection, for: .Normal) + overlay.set(image: theme.icons.stickerPackSelectionActive, for: .Highlight) + overlay.isSelected = item.isSelected + if let item = item as? StickerSpecificPackItem { + imageView.setPeer(account: item.account, peer: item.specificPack.1) + } + } + +} + +private final class AnimatedStickerPackRowView : HorizontalRowView { + + + var overlay:ImageButton = ImageButton() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + overlay.setFrameSize(35, 35) + overlay.autohighlight = false + overlay.canHighlight = false + overlay.userInteractionEnabled = false + addSubview(overlay) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + fileprivate(set) var contentNode:ChatMediaContentView? + + + override var backgroundColor: NSColor { + didSet { + contentNode?.backgroundColor = backdorColor + } + } + + override var backdorColor: NSColor { + return .clear + } + + override func shakeView() { + contentNode?.shake() + } + + + override func updateMouse() { + super.updateMouse() + self.contentNode?.updateMouse() + } + + + override func viewWillMove(toSuperview newSuperview: NSView?) { + if newSuperview == nil { + self.contentNode?.willRemove() + } + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + if window == nil { + contentNode?.removeFromSuperview() + contentNode = nil + } else if let item = item, contentNode == nil { + self.set(item: item, animated: false) + } + } + + override func set(item:TableRowItem, animated:Bool = false) { + if let item = item as? StickerPackRowItem { + if contentNode == nil || !contentNode!.isKind(of: item.contentNode()) { + self.contentNode?.removeFromSuperview() + let node = item.contentNode() + self.contentNode = node.init(frame:NSZeroRect) + self.addSubview(self.contentNode!) + } + + var file: TelegramMediaFile? + if let thumbnail = item.info.thumbnail { + file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: item.info.id.id), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [thumbnail], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/x-tgsticker", size: nil, attributes: [.FileName(fileName: "sticker.tgs"), .Sticker(displayText: "", packReference: .id(id: item.info.id.id, accessHash: item.info.accessHash), maskData: nil)]) + } else if let item = item.topItem { + file = item.file + } + self.contentNode?.userInteractionEnabled = false + self.contentNode?.isEventLess = true + if let file = file { + self.contentNode?.update(with: file, size: NSMakeSize(30, 30), context: item.context, parent: nil, table: item.table, parameters: nil, animated: animated, positionFlags: nil, approximateSynchronousValue: false) + } + + } + + overlay.set(image: theme.icons.stickerPackSelection, for: .Normal) + overlay.set(image: theme.icons.stickerPackSelectionActive, for: .Highlight) + + overlay.isSelected = item.isSelected + + super.set(item: item, animated: animated) + + needsLayout = true + } + + override func layout() { + super.layout() + + self.contentNode?.center() + overlay.center() + } +} diff --git a/Telegram-Mac/StickerPackPanelRowItem.swift b/Telegram-Mac/StickerPackPanelRowItem.swift new file mode 100644 index 0000000000..2b4ee407ea --- /dev/null +++ b/Telegram-Mac/StickerPackPanelRowItem.swift @@ -0,0 +1,484 @@ +// +// StickerPackPanelRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/07/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +class StickerPackPanelRowItem: TableRowItem { + let files: [(TelegramMediaFile, ChatMediaContentView.Type, NSPoint)] + let packNameLayout: TextViewLayout? + let context: AccountContext + let arguments: StickerPanelArguments + let namePoint: NSPoint + let packInfo: StickerPackInfo + let collectionId: StickerPackCollectionId + + private let _height: CGFloat + override var stableId: AnyHashable { + return collectionId + } + let packReference: StickerPackReference? + + private let preloadFeaturedDisposable = MetaDisposable() + + init(_ initialSize: NSSize, context: AccountContext, arguments: StickerPanelArguments, files:[TelegramMediaFile], packInfo: StickerPackInfo, collectionId: StickerPackCollectionId) { + self.context = context + self.arguments = arguments + var filesAndPoints:[(TelegramMediaFile, ChatMediaContentView.Type, NSPoint)] = [] + let size: NSSize = NSMakeSize(60, 60) + + + let title: String? + var count: Int32 = 0 + switch packInfo { + case let .pack(info, _, _): + title = info?.title ?? info?.shortName ?? "" + count = info?.count ?? 0 + if let info = info { + self.packReference = .id(id: info.id.id, accessHash: info.accessHash) + } else { + self.packReference = nil + } + case .recent: + title = L10n.stickersRecent + self.packReference = nil + case .saved: + title = nil + self.packReference = nil + case .emojiRelated: + title = nil + self.packReference = nil + case let .speficicPack(info): + title = info?.title ?? info?.shortName ?? "" + if let info = info { + self.packReference = .id(id: info.id.id, accessHash: info.accessHash) + } else { + self.packReference = nil + } + } + + if let title = title { + let attributed = NSMutableAttributedString() + if packInfo.featured { + _ = attributed.append(string: title.uppercased(), color: theme.colors.text, font: .medium(14)) + _ = attributed.append(string: "\n") + _ = attributed.append(string: L10n.stickersCountCountable(Int(count)), color: theme.colors.grayText, font: .normal(12)) + } else { + _ = attributed.append(string: title.uppercased(), color: theme.colors.grayText, font: .medium(.text)) + } + let layout = TextViewLayout(attributed, alwaysStaticItems: true) + layout.measure(width: 300) + self.packNameLayout = layout + + self.namePoint = NSMakePoint(10, floorToScreenPixels(System.backingScale, ((packInfo.featured ? 50 : 30) - layout.layoutSize.height) / 2)) + } else { + namePoint = NSZeroPoint + self.packNameLayout = nil + } + + + + var point: NSPoint = NSMakePoint(5, title == nil ? 5 : !packInfo.featured ? 35 : 55) + for (i, file) in files.enumerated() { + filesAndPoints.append((file, ChatLayoutUtils.contentNode(for: file), point)) + point.x += size.width + 10 + if (i + 1) % 5 == 0 { + point.y += size.height + 5 + point.x = 5 + } + } + + self.files = filesAndPoints + self.packInfo = packInfo + self.collectionId = collectionId + + let rows = ceil((CGFloat(files.count) / 5.0)) + _height = (title == nil ? 0 : !packInfo.featured ? 30 : 50) + 60.0 * rows + ((rows + 1) * 5) + + + + if packInfo.featured, let id = collectionId.itemCollectionId { + preloadFeaturedDisposable.set(preloadedFeaturedStickerSet(network: context.account.network, postbox: context.account.postbox, id: id).start()) + } + + super.init(initialSize) + + } + + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + var items:[ContextMenuItem] = [] + let context = self.context + for file in files { + let rect = NSMakeRect(file.2.x, file.2.y, 60, 60) + let file = file.0 + if NSPointInRect(location, rect) { + inner: switch packInfo { + case .saved, .recent: + if let reference = file.stickerReference { + items.append(ContextMenuItem(L10n.contextViewStickerSet, handler: { [weak self] in + self?.arguments.showPack(reference) + })) + } + default: + break inner + } + inner: switch packInfo { + case .saved: + if let mediaId = file.id { + items.append(ContextMenuItem(L10n.contextRemoveFaveSticker, handler: { + _ = removeSavedSticker(postbox: context.account.postbox, mediaId: mediaId).start() + })) + } + default: + if packInfo.installed { + items.append(ContextMenuItem(L10n.chatContextAddFavoriteSticker, handler: { + _ = addSavedSticker(postbox: context.account.postbox, network: context.account.network, file: file).start() + })) + } + } + + items.append(ContextMenuItem(L10n.chatSendWithoutSound, handler: { [weak self] in + guard let `self` = self else { + return + } + let contentView = (self.view as? StickerPackPanelRowView)?.subviews.compactMap { $0 as? ChatMediaContentView}.first(where: { view -> Bool in + return view.media?.isEqual(to: file) ?? false + }) + + if let contentView = contentView { + self.arguments.sendMedia(file, contentView, true) + } + })) + + break + } + } + return .single(items) + } + + deinit { + preloadFeaturedDisposable.dispose() + NotificationCenter.default.removeObserver(self) + } + + override var height: CGFloat { + return _height + } + + override func viewClass() -> AnyClass { + return StickerPackPanelRowView.self + } +} + +private final class StickerPackPanelRowView : TableRowView, ModalPreviewRowViewProtocol { + + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + for subview in self.subviews { + if let contentView = subview as? ChatMediaContentView { + if NSPointInRect(point, subview.frame) { + if let file = contentView.media as? TelegramMediaFile { + let reference = file.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file) : FileMediaReference.standalone(media: file) + if file.isStaticSticker { + return (.file(reference, StickerPreviewModalView.self), contentView) + } else if file.isAnimatedSticker { + return (.file(reference, AnimatedStickerPreviewModalView.self), contentView) + } + } + } + } + + } + return nil + } + + private var contentViews:[Optional] = [] + private let packNameView = TextView() + private var clearRecentButton: ImageButton? + private var addButton:TitleButton? + private let longDisposable = MetaDisposable() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(packNameView) + packNameView.userInteractionEnabled = false + packNameView.isSelectable = false + wantsLayer = false + + } + private var isMouseDown: Bool = false + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + longDisposable.set(nil) + + self.isMouseDown = true + + guard event.clickCount == 1 else { + return + } + + let point = convert(event.locationInWindow, from: nil) + for subview in self.subviews { + if NSPointInRect(point, subview.frame) { + if subview is ChatMediaContentView { + let signal = Signal.complete() |> delay(0.2, queue: .mainQueue()) + longDisposable.set(signal.start(completed: { [weak self] in + if let `self` = self, self.mouseInside(), + let item = self.item as? StickerPackPanelRowItem, + let table = item.table, + let window = self.window as? Window { + startModalPreviewHandle(table, window: window, context: item.context) + } + })) + } + return + } + } + + } + + override func mouseUp(with event: NSEvent) { + //super.mouseUp(with: event) + longDisposable.set(nil) + if isMouseDown, mouseInside(), event.clickCount == 1 { + let point = convert(event.locationInWindow, from: nil) + + if let item = item as? StickerPackPanelRowItem { + if self.packNameView.mouseInside() { + if let reference = item.packReference { + item.arguments.showPack(reference) + } + } else { + for subview in self.subviews { + if NSPointInRect(point, subview.frame) { + if let contentView = subview as? ChatMediaContentView, let media = contentView.media { + if let reference = item.packReference, item.packInfo.featured { + item.arguments.showPack(reference) + } else { + item.arguments.sendMedia(media, contentView, false) + } + } + break + } + } + } + } + } + isMouseDown = false + } + deinit { + longDisposable.dispose() + } + + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var previousRange: (Int, Int) = (0, 0) + private var isCleaned: Bool = false + + override func layout() { + super.layout() + + guard let item = item as? StickerPackPanelRowItem else { + return + } + packNameView.setFrameOrigin(item.namePoint) + + self.clearRecentButton?.setFrameOrigin(frame.width - 34, item.namePoint.y - 10) + + updateVisibleItems() + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + updateVisibleItems() + } + + override var backdorColor: NSColor { + return .clear + } + + override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateVisibleItems() + } + + @objc func updateVisibleItems() { + + guard let item = item as? StickerPackPanelRowItem else { + return + } + + let size: NSSize = NSMakeSize(60, 60) + + let visibleRect = NSMakeRect(0, self.visibleRect.minY - 120, self.visibleRect.width, self.visibleRect.height + 240) + + if self.visibleRect != NSZeroRect && superview != nil && window != nil { + let visibleRange = (Int(ceil(visibleRect.minY / (size.height + 10))), Int(ceil(visibleRect.height / (size.height + 10)))) + if visibleRange != self.previousRange { + self.previousRange = visibleRange + isCleaned = false + } else { + return + } + } else { + self.previousRange = (0, 0) + CATransaction.begin() + if !isCleaned { + for (i, view) in self.contentViews.enumerated() { + view?.removeFromSuperview() + self.contentViews[i] = nil + } + } + isCleaned = true + CATransaction.commit() + return + } + + + CATransaction.begin() + + var unused:[ChatMediaContentView] = [] + for (i, data) in item.files.enumerated() { + let file = data.0 + let point = data.2 + let viewType = data.1 + if NSPointInRect(point, visibleRect) { + var view: ChatMediaContentView + if self.contentViews[i] == nil || !self.contentViews[i]!.isKind(of: viewType) { + if unused.isEmpty { + view = viewType.init(frame: NSZeroRect) + } else { + view = unused.removeFirst() + } + self.contentViews[i] = view + } else { + view = self.contentViews[i]! + } + if view.media?.id != file.id { + view.update(with: file, size: size, context: item.context, parent: nil, table: item.table) + } + view.userInteractionEnabled = false + view.setFrameOrigin(point) + + } else { + if let view = self.contentViews[i] { + unused.append(view) + self.contentViews[i] = nil + } + } + } + + for view in unused { + view.clean() + view.removeFromSuperview() + } + + self.subviews = (self.clearRecentButton != nil ? [self.clearRecentButton!] : []) + (self.addButton != nil ? [self.addButton!] : []) + [self.packNameView] + self.contentViews.compactMap { $0 } + + CATransaction.commit() + + + } + + override func viewDidMoveToWindow() { + if window == nil { + NotificationCenter.default.removeObserver(self) + } else { + NotificationCenter.default.addObserver(self, selector: #selector(updateVisibleItems), name: NSView.boundsDidChangeNotification, object: self.enclosingScrollView?.contentView) + } + updateVisibleItems() + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? StickerPackPanelRowItem else { + return + } + + packNameView.update(item.packNameLayout) + + switch item.packInfo { + case .recent: + if self.clearRecentButton == nil { + self.clearRecentButton = ImageButton() + addSubview(self.clearRecentButton!) + } + self.clearRecentButton?.set(image: theme.icons.wallpaper_color_close, for: .Normal) + _ = self.clearRecentButton?.sizeToFit(NSMakeSize(5, 5), thatFit: false) + + self.clearRecentButton?.removeAllHandlers() + + self.clearRecentButton?.set(handler: { [weak item] _ in + item?.arguments.clearRecent() + }, for: .Click) + default: + self.clearRecentButton?.removeFromSuperview() + self.clearRecentButton = nil + } + + self.previousRange = (0, 0) + + while self.contentViews.count > item.files.count { + self.contentViews.removeLast() + } + while self.contentViews.count < item.files.count { + self.contentViews.append(nil) + } + + + self.addButton?.removeFromSuperview() + self.addButton = nil + + if let reference = item.packReference, item.packInfo.featured { + if !item.packInfo.installed { + self.addButton = TitleButton() + self.addButton!.set(background: theme.colors.accentSelect, for: .Normal) + self.addButton!.set(background: theme.colors.accentSelect.withAlphaComponent(0.8), for: .Highlight) + self.addButton!.set(font: .medium(.text), for: .Normal) + self.addButton!.set(color: theme.colors.underSelectedColor, for: .Normal) + self.addButton!.set(text: L10n.stickersSearchAdd, for: .Normal) + _ = self.addButton!.sizeToFit(NSMakeSize(14, 8), thatFit: true) + self.addButton!.layer?.cornerRadius = .cornerRadius + self.addButton!.setFrameOrigin(frame.width - self.addButton!.frame.width - 10, 13) + + self.addButton!.set(handler: { [weak item] _ in + item?.arguments.addPack(reference) + }, for: .Click) + } else { + self.addButton = TitleButton() + self.addButton!.set(background: theme.colors.grayForeground, for: .Normal) + self.addButton!.set(background: theme.colors.grayForeground.withAlphaComponent(0.8), for: .Highlight) + self.addButton!.set(font: .medium(.text), for: .Normal) + self.addButton!.set(color: theme.colors.underSelectedColor, for: .Normal) + self.addButton!.set(text: L10n.stickersSearchAdded, for: .Normal) + _ = self.addButton!.sizeToFit(NSMakeSize(14, 8), thatFit: true) + self.addButton!.layer?.cornerRadius = .cornerRadius + self.addButton!.setFrameOrigin(frame.width - self.addButton!.frame.width - 10, 13) + + self.addButton!.set(handler: { [weak item] _ in + if let item = item { + item.arguments.removePack(item.collectionId) + } + }, for: .Click) + } + } + + updateVisibleItems() + } + +} diff --git a/Telegram-Mac/StickerPackPreviewModalController.swift b/Telegram-Mac/StickerPackPreviewModalController.swift new file mode 100644 index 0000000000..345b0a1c32 --- /dev/null +++ b/Telegram-Mac/StickerPackPreviewModalController.swift @@ -0,0 +1,291 @@ +// +// StickerPackPreviewModalController.swift +// Telegram +// +// Created by keepcoder on 27/02/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + + + +final class StickerPackArguments { + let context: AccountContext + let send:(TelegramMediaFile, NSView)->Void + let addpack:(StickerPackCollectionInfo, [ItemCollectionItem], Bool)->Void + let share:(String)->Void + let close:()->Void + init(context: AccountContext, send:@escaping(Media, NSView)->Void, addpack:@escaping(StickerPackCollectionInfo, [ItemCollectionItem], Bool)->Void, share:@escaping(String)->Void, close:@escaping()->Void) { + self.context = context + self.send = send + self.addpack = addpack + self.share = share + self.close = close + } +} + + + +private class StickersModalView : View { + private let grid:GridNode = GridNode(frame: NSZeroRect) + private let add:TitleButton = TitleButton() + private let shareView:ImageButton = ImageButton() + private let close: ImageButton = ImageButton() + private let headerTitle:TextView = TextView() + private let headerSeparatorView:View = View() + private let dismiss:ImageButton = ImageButton() + private var indicatorView:ProgressIndicator? + private let shadowView: ShadowView = ShadowView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + backgroundColor = theme.colors.background + addSubview(grid) + //addSubview(interactionView) + addSubview(headerTitle) + addSubview(shareView) + addSubview(close) + addSubview(headerSeparatorView) + addSubview(dismiss) + + shadowView.shadowBackground = theme.colors.background + shadowView.setFrameSize(frame.width, 70) + + addSubview(shadowView) + + dismiss.set(image: theme.icons.stickerPackDelete, for: .Normal) + _ = dismiss.sizeToFit() + add.disableActions() + add.setFrameSize(170, 40) + add.layer?.cornerRadius = 20 + + add.set(color: theme.colors.underSelectedColor, for: .Normal) + add.set(font: .medium(.title), for: .Normal) + add.set(background: theme.colors.accent, for: .Normal) + add.set(background: theme.colors.accent, for: .Hover) + add.set(background: theme.colors.accent, for: .Highlight) + add.set(text: L10n.stickerPackAdd1Countable(0), for: .Normal) + + addSubview(add) + headerTitle.backgroundColor = theme.colors.background + headerSeparatorView.backgroundColor = theme.colors.border + + shareView.set(image: theme.icons.stickersShare, for: .Normal) + close.set(image: theme.icons.stickerPackClose, for: .Normal) + _ = shareView.sizeToFit() + _ = close.sizeToFit() + + + } + + + func layout(with result: LoadedStickerPack, arguments: StickerPackArguments) -> Void { + + + switch result { + case .none: + break + case .fetching: + dismiss.isHidden = true + shareView.isHidden = true + if self.indicatorView == nil { + self.indicatorView = ProgressIndicator(frame: NSMakeRect(0, 0, 30, 30)) + addSubview(self.indicatorView!) + } + self.indicatorView?.center() + add.isHidden = true + shadowView.isHidden = true + case let .result(info: info, items: collectionItems, installed: installed): + if let indicatorView = self.indicatorView { + self.indicatorView = nil + indicatorView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak indicatorView] _ in + indicatorView?.removeFromSuperview() + }) + } + dismiss.isHidden = !installed + shareView.isHidden = false + add.set(text: tr(L10n.stickerPackAdd1Countable(collectionItems .count)).uppercased(), for: .Normal) + _ = add.sizeToFit(NSMakeSize(20, 0), NSMakeSize(frame.width - 40, 40), thatFit: false) + add.isHidden = installed + shadowView.isHidden = installed + let attr = NSMutableAttributedString() + + _ = attr.append(string: info.title, color: theme.colors.text, font: .medium(16.0)) + attr.detectLinks(type: [.Mentions], context: arguments.context, color: theme.colors.accent, openInfo: { (peerId, _, _, _) in + _ = (arguments.context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in + arguments.close() + if peer.isUser || peer.isBot { + arguments.context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: arguments.context, peerId: peerId)) + } else { + arguments.context.sharedContext.bindings.rootNavigation().push(ChatAdditionController(context: arguments.context, chatLocation: .peer(peer.id))) + } + }) + }) + let layout = TextViewLayout(attr, maximumNumberOfLines: 2, alignment: .center) + layout.interactions = globalLinkExecutor + + + layout.measure(width: frame.width - 160) + headerTitle.update(layout) + + let items = collectionItems.filter({ item -> Bool in + return item is StickerPackItem + }).map ({ item -> StickerPackGridItem in + return StickerPackGridItem(context: arguments.context, file: (item as! StickerPackItem).file, send: arguments.send, selected: {}) + }) + + var insert:[GridNodeInsertItem] = [] + + for index in 0 ..< items.count { + insert.append(GridNodeInsertItem(index: index, item: items[index], previousIndex: nil)) + } + + grid.removeAllItems() + + grid.transaction(GridNodeTransaction(deleteItems: [], insertItems: insert, updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: frame.width, height: frame.height), insets: NSEdgeInsets(left: 0, right: 0, top: 10, bottom: installed ? 0 : 60), preloadSize: self.bounds.width, type: .fixed(itemSize: CGSize(width: 70, height: 70), lineSpacing: 10)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + grid.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + self.needsLayout = true + + + + shareView.set(handler: { _ in + arguments.share("https://t.me/addstickers/\(info.shortName)") + }, for: .SingleClick) + + add.removeAllHandlers() + dismiss.removeAllHandlers() + close.removeAllHandlers() + + func action(_ control:Control) { + arguments.addpack(info, collectionItems, installed) + } + + + add.set(handler: action, for: .SingleClick) + dismiss.set(handler: action, for: .SingleClick) + + close.set(handler: { _ in + arguments.close() + }, for: .Click) + + } + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + + let headerHeight:CGFloat = 50 + + grid.frame = NSMakeRect(0, headerHeight, frame.width, frame.height - headerHeight) + + headerTitle.centerX(y : floorToScreenPixels(backingScaleFactor, (headerHeight - headerTitle.frame.height)/2) + 1) + headerSeparatorView.frame = NSMakeRect(0, headerHeight - .borderSize, frame.width, .borderSize) + shareView.setFrameOrigin(frame.width - close.frame.width - 12, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2)) + close.setFrameOrigin(12, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2)) + add.centerX(y: frame.height - add.frame.height - 15) + dismiss.setFrameOrigin(NSMakePoint(shareView.frame.minX - dismiss.frame.width - 15, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2))) + + shadowView.setFrameOrigin(0, frame.height - shadowView.frame.height) + } +} + + + +class StickerPackPreviewModalController: ModalViewController { + private let context:AccountContext + private let peerId:PeerId? + private let reference:StickerPackReference + private let disposable: MetaDisposable = MetaDisposable() + private var arguments:StickerPackArguments! + + init(_ context: AccountContext, peerId:PeerId?, reference:StickerPackReference) { + self.context = context + self.peerId = peerId + self.reference = reference + super.init(frame: NSMakeRect(0, 0, 350, 400)) + bar = .init(height: 0) + arguments = StickerPackArguments(context: context, send: { [weak self] media, view in + let interactions = (context.sharedContext.bindings.rootNavigation().controller as? ChatController)?.chatInteraction + + if let interactions = interactions, let media = media as? TelegramMediaFile, media.maskData == nil { + if let slowMode = interactions.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else { + interactions.sendAppFile(media, false) + self?.close() + } + } + }, addpack: { [weak self] info, items, installed in + self?.close() + self?.disposable.dispose() + if !installed { + _ = addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items).start() + } else { + _ = removeStickerPackInteractively(postbox: context.account.postbox, id: info.id, option: .archive).start() + } + + }, share: { [weak self] link in + self?.close() + showModal(with: ShareModalController(ShareLinkObject(context, link: link)), for: mainWindow) + }, close: { [weak self] in + self?.close() + }) + } + + fileprivate var genericView:StickersModalView { + return self.view as! StickersModalView + } + + override func viewClass() -> AnyClass { + return StickersModalView.self + } + + + override var dynamicSize: Bool { + return true + } + + + override func measure(size: NSSize) { + // self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 70, genericView.listHeight)), animated: false) + } + + + override func viewDidLoad() { + super.viewDidLoad() + + + disposable.set((loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: reference, forceActualized: false) |> deliverOnMainQueue).start(next: { [weak self] result in + guard let `self` = self else {return} + switch result { + case .none: + alert(for: mainWindow, info: L10n.stickerSetDontExist) + self.close() + default: + self.genericView.layout(with: result, arguments: self.arguments) + self.readyOnce() + } + + })) + + } + + + deinit { + disposable.dispose() + } + +} diff --git a/Telegram-Mac/StickerPreviewHandler.swift b/Telegram-Mac/StickerPreviewHandler.swift index 815b15f356..aef77a8b7e 100644 --- a/Telegram-Mac/StickerPreviewHandler.swift +++ b/Telegram-Mac/StickerPreviewHandler.swift @@ -1,5 +1,5 @@ // -// StickerPreviewHandler.swift +// ModalPreviewHandler.swift // Telegram // // Created by keepcoder on 02/02/2017. @@ -7,33 +7,79 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit -import SwiftSignalKitMac +import SwiftSignalKit -extension GridNode : StickerPreviewProtocol { - func stickerAtLocationInWindow(_ point: NSPoint) -> TelegramMediaFile? { +enum QuickPreviewMedia : Equatable { + case file(FileMediaReference, ModalPreviewControllerView.Type) + case image(ImageMediaReference, ModalPreviewControllerView.Type) + + static func ==(lhs: QuickPreviewMedia, rhs: QuickPreviewMedia) -> Bool { + switch lhs { + case let .file(lhsReference, _): + if case let .file(rhsReference, _) = rhs { + return lhsReference.media.isEqual(to: rhsReference.media) + } else { + return false + } + case let .image(lhsReference, _): + if case let .image(rhsReference, _) = rhs { + return lhsReference.media.isEqual(to: rhsReference.media) + } else { + return false + } + } + } + + var fileReference: FileMediaReference? { + switch self { + case let .file(reference, _): + return reference + default: + return nil + } + } + var imageReference: ImageMediaReference? { + switch self { + case let .image(reference, _): + return reference + default: + return nil + } + } + + var viewType: ModalPreviewControllerView.Type { + switch self { + case let .file(_, type), let .image(_, type): + return type + } + } +} + +extension GridNode : ModalPreviewProtocol { + func fileAtLocationInWindow(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { let point = self.documentView!.convert(point, from: nil) - var file:TelegramMediaFile? = nil + var reference: (QuickPreviewMedia, NSView?)? = nil self.forEachItemNode { node in if NSPointInRect(point, node.frame) { - if let c = node as? StickerPreviewRowViewProtocol { - file = c.fileAtPoint(node.convert(point, from: nil)) - return + if let c = node as? ModalPreviewRowViewProtocol { + reference = c.fileAtPoint(node.convert(point, from: nil)) } } } - return file + return reference } } -extension TableView : StickerPreviewProtocol { - func stickerAtLocationInWindow(_ point: NSPoint) -> TelegramMediaFile? { +extension TableView : ModalPreviewProtocol { + func fileAtLocationInWindow(_ point: NSPoint) ->(QuickPreviewMedia, NSView?)? { let index = self.row(at: documentView!.convert(point, from: nil)) if index != -1 { let item = self.item(at: index) - if let view = self.viewNecessary(at: item.index), let c = view as? StickerPreviewRowViewProtocol { + if let view = self.viewNecessary(at: item.index), let c = view as? ModalPreviewRowViewProtocol { return c.fileAtPoint(view.convert(point, from: nil)) } } @@ -42,60 +88,196 @@ extension TableView : StickerPreviewProtocol { } } -protocol StickerPreviewRowViewProtocol { - func fileAtPoint(_ point:NSPoint) -> TelegramMediaFile? +protocol ModalPreviewRowViewProtocol { + func fileAtPoint(_ point:NSPoint) -> (QuickPreviewMedia, NSView?)? } -protocol StickerPreviewProtocol { - func stickerAtLocationInWindow(_ point:NSPoint) -> TelegramMediaFile? +protocol ModalPreviewProtocol { + func fileAtLocationInWindow(_ point:NSPoint) -> (QuickPreviewMedia, NSView?)? + +} + +protocol ModalPreviewControllerView : class { + func update(with reference: QuickPreviewMedia, context: AccountContext, animated: Bool) } -fileprivate var handler:StickerPreviewHandler? +fileprivate var handler:ModalPreviewHandler? + -func startStickerPreviewHandle(_ global:StickerPreviewProtocol, window:Window, account:Account) { - handler = StickerPreviewHandler(global, window: window, account: account) + +func startModalPreviewHandle(_ global:ModalPreviewProtocol, window:Window, context: AccountContext) { + handler = ModalPreviewHandler(global, window: window, context: context) handler?.startHandler() } -class StickerPreviewHandler : NSObject { - private let global:StickerPreviewProtocol - private let account:Account +class ModalPreviewHandler : NSObject { + private let global:ModalPreviewProtocol + private let context:AccountContext private let window:Window - private let modal:StickerPreviewModalController - init(_ global:StickerPreviewProtocol, window:Window, account:Account) { + private let modal:PreviewModalController + init(_ global:ModalPreviewProtocol, window:Window, context: AccountContext) { self.global = global self.window = window - self.account = account - self.modal = StickerPreviewModalController(account) + self.context = context + + self.modal = PreviewModalController(context) } func startHandler() { + let initial = global.fileAtLocationInWindow(window.mouseLocationOutsideOfEventStream) + if let initial = initial { + modal.update(with: initial.0) + let animation:ModalAnimationType + if let view = initial.1 { + var rect = view.convert(view.bounds, to: nil) + rect.origin.y = window.contentView!.frame.maxY - rect.maxY + animation = .scaleFrom(rect) + } else { + animation = .bottomToCenter + } + showModal(with: modal, for: window, animationType: animation) + + window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + if let strongSelf = self, let reference = strongSelf.global.fileAtLocationInWindow(strongSelf.window.mouseLocationOutsideOfEventStream) { + strongSelf.modal.update(with: reference.0) + } + return .invoked + }, with: self, for: .leftMouseDragged, priority: .modal) + + window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + self?.stopHandler() + return .invoked + }, with: self, for: .leftMouseUp, priority: .modal) + } + + } + + func stopHandler() { + window.removeAllHandlers(for: self) + if let view = self.global.fileAtLocationInWindow(self.window.mouseLocationOutsideOfEventStream)?.1 { + var rect = view.convert(view.bounds, to: nil) + rect.origin.y = window.contentView!.frame.maxY - rect.maxY + modal.close(animationType: .scaleToRect(rect)) + } else { + modal.close() + } + + handler = nil + } + + deinit { - modal.update(with: global.stickerAtLocationInWindow(window.mouseLocationOutsideOfEventStream)) - showModal(with: modal, for: window) + } +} + + +private final class PreviewModalView: View { + private var contentView: NSView? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + override func layout() { + super.layout() + } + + func update(with preview: QuickPreviewMedia, context: AccountContext, animated: Bool) { - window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - if let strongSelf = self, let file = strongSelf.global.stickerAtLocationInWindow(strongSelf.window.mouseLocationOutsideOfEventStream) { - strongSelf.modal.update(with: file) + let viewType = preview.viewType + var changed = false + if contentView == nil || !contentView!.isKind(of: viewType) { + if animated { + let current = self.contentView + self.contentView = nil + current?.layer?.animateScaleSpring(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak current] completed in + if completed { + current?.removeFromSuperview() + } + }) + } else { + self.contentView?.removeFromSuperview() } - return .invokeNext - }, with: self, for: .leftMouseDragged, priority: .modal) + + self.contentView = (viewType as! NSView.Type).init(frame:NSZeroRect) + self.addSubview(self.contentView!) + changed = true + } + contentView?.frame = bounds + (contentView as? ModalPreviewControllerView)?.update(with: preview, context: context, animated: animated && !changed) - window.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in - self?.stopHandler() - return .invokeNext - }, with: self, for: .leftMouseUp, priority: .modal) + if animated { + contentView?.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) + } } - func stopHandler() { - window.remove(object: self, for: .leftMouseDragged) - window.remove(object: self, for: .leftMouseUp) - modal.close() - handler = nil + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class PreviewModalController: ModalViewController { + fileprivate let context:AccountContext + fileprivate var reference:QuickPreviewMedia? + init(_ context: AccountContext) { + self.context = context + + super.init(frame: NSMakeRect(0, 0, min(context.window.frame.width - 50, 500), min(500, context.window.frame.height - 50))) + bar = .init(height: 0) + } + + override var hasOwnTouchbar: Bool { + return false + } + + override var containerBackground: NSColor { + return .clear + } + + override func becomeFirstResponder() -> Bool? { + return nil + } + + override var handleEvents:Bool { + return false + } + + override func viewDidLoad() { + super.viewDidLoad() + if let reference = reference { + genericView.update(with: reference, context: context, animated: false) + } + readyOnce() + } + + func update(with reference:QuickPreviewMedia?) { + if self.reference != reference { + self.reference = reference + if isLoaded(), let reference = reference { + genericView.update(with: reference, context: context, animated: true) + } + } + } + + fileprivate var genericView:PreviewModalView { + return view as! PreviewModalView + } + + override func viewClass() -> AnyClass { + return PreviewModalView.self } deinit { - stopHandler() + var bp:Int = 0 + bp += 1 } + // override var isFullScreen: Bool { + // return true + // } + } diff --git a/Telegram-Mac/StickerPreviewModalController.swift b/Telegram-Mac/StickerPreviewModalController.swift deleted file mode 100644 index a78432dd77..0000000000 --- a/Telegram-Mac/StickerPreviewModalController.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// StickerPreviewModalController.swift -// Telegram -// -// Created by keepcoder on 02/02/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import TelegramCoreMac -import PostboxMac - -fileprivate class StickerPreviewModalView : View { - fileprivate let imageView:TransformImageView = TransformImageView() - fileprivate let textView:TextView = TextView() - required init(frame frameRect: NSRect) { - super.init(frame: frameRect) - addSubview(imageView) - addSubview(textView) - textView.backgroundColor = .clear - imageView.setFrameSize(100,100) - self.background = .clear - } - - override func layout() { - super.layout() - imageView.center() - - } - - func update(with file:TelegramMediaFile, account:Account) -> Void { - imageView.setSignal(account: account, signal: chatMessageSticker(account: account, file: file, type: .full, scale: backingScaleFactor), clearInstantly: true, animate:true) - let size = file.dimensions?.aspectFitted(NSMakeSize(frame.size.width, frame.size.height - 100)) ?? frame.size - imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: NSEdgeInsets())) - imageView.frame = NSMakeRect(0, frame.height - size.height, size.width, size.height) - imageView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) - - let layout = TextViewLayout(.initialize(string: file.stickerText?.fixed, color: nil, font: .normal(.custom(30)))) - layout.measure(width: .greatestFiniteMagnitude) - textView.update(layout) - textView.centerX() - - textView.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.2) - - needsLayout = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class StickerPreviewModalController: ModalViewController { - fileprivate let account:Account - fileprivate var file:TelegramMediaFile? - init(_ account:Account) { - self.account = account - - super.init(frame: NSMakeRect(0, 0, 360, 400)) - } - - override var containerBackground: NSColor { - return .clear - } - - override var handleEvents:Bool { - return false - } - - override func viewDidLoad() { - super.viewDidLoad() - if let file = file { - genericView.update(with: file, account: account) - } - readyOnce() - } - - func update(with file:TelegramMediaFile?) { - if self.file != file { - self.file = file - if isLoaded(), let file = file { - genericView.update(with: file, account: account) - } - } - } - - fileprivate var genericView:StickerPreviewModalView { - return view as! StickerPreviewModalView - } - - override func viewClass() -> AnyClass { - return StickerPreviewModalView.self - } - - // override var isFullScreen: Bool { - // return true - // } - -} diff --git a/Telegram-Mac/StickerSetTableRowItem.swift b/Telegram-Mac/StickerSetTableRowItem.swift index 3a411757eb..e27804e5fa 100644 --- a/Telegram-Mac/StickerSetTableRowItem.swift +++ b/Telegram-Mac/StickerSetTableRowItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac - +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit enum ItemListStickerPackItemControl: Equatable { @@ -19,94 +20,52 @@ enum ItemListStickerPackItemControl: Equatable { case remove case empty case selected - static func ==(lhs: ItemListStickerPackItemControl, rhs: ItemListStickerPackItemControl) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case .remove: - if case .remove = rhs { - return true - } else { - return false - } - case .selected: - if case .selected = rhs { - return true - } else { - return false - } - case .empty: - if case .empty = rhs { - return true - } else { - return false - } - case let .installation(installed): - if case .installation(installed) = rhs { - return true - } else { - return false - } - - } - } } -class StickerSetTableRowItem: TableRowItem { +class StickerSetTableRowItem: GeneralRowItem { - fileprivate let account:Account + fileprivate let context: AccountContext fileprivate let info:StickerPackCollectionInfo fileprivate let topItem:StickerPackItem? fileprivate let unread: Bool fileprivate let editing: ItemListStickerPackItemEditing - fileprivate let enabled:Bool fileprivate let _stableId:AnyHashable fileprivate let itemCount:Int32 fileprivate let control: ItemListStickerPackItemControl fileprivate let nameLayout:TextViewLayout fileprivate let countLayout:TextViewLayout - let action: () -> Void let addPack: () -> Void let removePack: () -> Void - fileprivate let insets: NSEdgeInsets = NSEdgeInsets(left: 30, right: 30) - - override var stableId: AnyHashable { - return _stableId - } - init(_ initialSize: NSSize, account:Account, stableId:AnyHashable, info:StickerPackCollectionInfo, topItem:StickerPackItem?, itemCount:Int32, unread: Bool, editing: ItemListStickerPackItemEditing, enabled: Bool, control: ItemListStickerPackItemControl, action:@escaping()->Void, addPack:@escaping()->Void = {}, removePack:@escaping() -> Void = {}) { - self.account = account + init(_ initialSize: NSSize, context:AccountContext, stableId:AnyHashable, info:StickerPackCollectionInfo, topItem:StickerPackItem?, itemCount:Int32, unread: Bool, editing: ItemListStickerPackItemEditing, enabled: Bool, control: ItemListStickerPackItemControl, viewType: GeneralViewType = .legacy, action:@escaping()->Void, addPack:@escaping()->Void = {}, removePack:@escaping() -> Void = {}) { + self.context = context self._stableId = stableId self.info = info self.topItem = topItem self.unread = unread self.editing = editing - self.enabled = enabled self.itemCount = itemCount self.control = control - self.action = action self.addPack = addPack self.removePack = removePack nameLayout = TextViewLayout(.initialize(string: info.title, color: theme.colors.text, font: .normal(.title)), maximumNumberOfLines: 1) - countLayout = TextViewLayout(.initialize(string: tr(.stickersSetCount(Int(itemCount))), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) - nameLayout.measure(width: initialSize.width - 50 - insets.left - insets.right) - countLayout.measure(width: initialSize.width - 50 - insets.left - insets.right) - super.init(initialSize) - } - - override var height: CGFloat { - return 50 + countLayout = TextViewLayout(.initialize(string: L10n.stickersSetCount1Countable(Int(itemCount)), color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1) + super.init(initialSize, height: 50, stableId: stableId, type: .none, viewType: viewType, action: action, inset: NSEdgeInsets(left: 30, right: 30), enabled: enabled) + _ = makeSize(initialSize.width, oldWidth: 0) } - + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { - nameLayout.measure(width: width - 50 - insets.left - insets.right) - countLayout.measure(width: width - 50 - insets.left - insets.right) - return super.makeSize(width, oldWidth: oldWidth) + let success = super.makeSize(width, oldWidth: oldWidth) + switch self.viewType { + case .legacy: + nameLayout.measure(width: width - 50 - inset.left - inset.right) + countLayout.measure(width: width - 50 - inset.left - inset.right) + case let .modern(_, insets): + nameLayout.measure(width: self.blockWidth - 80 - insets.left - insets.right) + countLayout.measure(width: self.blockWidth - 80 - insets.left - insets.right) + } + return success } override func viewClass() -> AnyClass { @@ -114,40 +73,56 @@ class StickerSetTableRowItem: TableRowItem { } } -class StickerSetTableRowView : TableRowView { +class StickerSetTableRowView : TableRowView, ViewDisplayDelegate { + + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let imageView:TransformImageView = TransformImageView() private let nameView:TextView = TextView() private let countView:TextView = TextView() private let installationControl:ImageView = ImageView() private let removeControl = ImageButton() + private var animatedView: MediaAnimatedStickerView? + private let loadedStickerPackDisposable = MetaDisposable() required init(frame frameRect: NSRect) { super.init(frame: frameRect) - addSubview(imageView) + containerView.addSubview(imageView) imageView.setFrameSize(NSMakeSize(35, 35)) - addSubview(nameView) - addSubview(countView) + containerView.addSubview(nameView) + containerView.addSubview(countView) countView.userInteractionEnabled = false nameView.userInteractionEnabled = false - addSubview(installationControl) + containerView.addSubview(installationControl) - removeControl.set(handler: { [weak self] _ in - if let item = self?.item as? StickerSetTableRowItem { - item.removePack() + containerView.displayDelegate = self + + containerView.set(handler: { control in + if let event = NSApp.currentEvent { + control.superview?.mouseDown(with: event) } - }, for: .SingleClick) - addSubview(removeControl) - } - - override func mouseUp(with event: NSEvent) { - if mouseInside() { - if let item = item as? StickerSetTableRowItem { - let point = convert(event.locationInWindow, from: nil) - if NSPointInRect(point, NSMakeRect(installationControl.frame.minX, 0, installationControl.frame.width, frame.height)) { + }, for: .Down) + + containerView.set(handler: { control in + if let event = NSApp.currentEvent { + control.superview?.mouseDragged(with: event) + } + }, for: .MouseDragging) + + containerView.set(handler: { control in + if let event = NSApp.currentEvent { + control.superview?.mouseUp(with: event) + } + }, for: .Up) + + containerView.set(handler: { [weak self] _ in + if let `self` = self, let item = self.item as? StickerSetTableRowItem, let event = NSApp.currentEvent { + let point = self.containerView.convert(event.locationInWindow, from: nil) + if NSPointInRect(point, self.installationControl.frame) { switch item.control { case .installation: item.addPack() case .none: - break + break case .remove: item.removePack() case .empty: @@ -159,80 +134,175 @@ class StickerSetTableRowView : TableRowView { item.action() } } - } - + }, for: .Click) + + removeControl.set(handler: { [weak self] _ in + if let item = self?.item as? StickerSetTableRowItem { + item.removePack() + } + }, for: .SingleClick) + containerView.addSubview(removeControl) + self.addSubview(containerView) } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - if let item = item as? StickerSetTableRowItem { + + if let item = item as? StickerSetTableRowItem, layer == containerView.layer { ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.insets.left + 50, frame.height - .borderSize, frame.width - item.insets.left - item.insets.right - 50, .borderSize)) + switch item.viewType { + case .legacy: + ctx.fill(NSMakeRect(item.inset.left + 50, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right - 50, .borderSize)) + case let .modern(position, insets): + switch position { + case .first, .inner: + ctx.fill(NSMakeRect(insets.left + 50, containerView.frame.height - .borderSize, containerView.frame.width - insets.left - insets.right - 50, .borderSize)) + default: + break + } + } } } override func layout() { super.layout() if let item = item as? StickerSetTableRowItem { - imageView.centerY(x: item.insets.left) - nameView.update(item.nameLayout, origin: NSMakePoint(item.insets.left + 50, 7)) - countView.update(item.countLayout, origin: NSMakePoint(item.insets.left + 50, frame.height - item.countLayout.layoutSize.height - 7)) - installationControl.centerY(x: frame.width - item.insets.left - installationControl.frame.width) - removeControl.centerY(x: frame.width - item.insets.left - removeControl.frame.width) - + switch item.viewType { + case .legacy: + self.containerView.frame = self.bounds + self.containerView.setCorners([]) + imageView.centerY(x: item.inset.left) + nameView.update(item.nameLayout, origin: NSMakePoint(item.inset.left + 50, 7)) + countView.update(item.countLayout, origin: NSMakePoint(item.inset.left + 50, containerView.frame.height - item.countLayout.layoutSize.height - 7)) + installationControl.centerY(x: containerView.frame.width - item.inset.left - installationControl.frame.width) + removeControl.centerY(x: containerView.frame.width - item.inset.left - removeControl.frame.width) + animatedView?.centerY(x: item.inset.left) + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + imageView.centerY(x: innerInsets.left) + nameView.update(item.nameLayout, origin: NSMakePoint(innerInsets.left + 50, 7)) + countView.update(item.countLayout, origin: NSMakePoint(innerInsets.left + 50, containerView.frame.height - item.countLayout.layoutSize.height - 7)) + installationControl.centerY(x: containerView.frame.width - innerInsets.right - installationControl.frame.width) + removeControl.centerY(x: containerView.frame.width - innerInsets.right - removeControl.frame.width) + animatedView?.centerY(x: innerInsets.left) + } + + } + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + nameView.backgroundColor = backdorColor + countView.backgroundColor = backdorColor + containerView.background = backdorColor + if let item = item as? GeneralRowItem { + self.backgroundColor = item.viewType.rowBackground } } override func set(item: TableRowItem, animated: Bool) { super.set(item: item, animated: animated) - + self.updateMouse() if let item = item as? StickerSetTableRowItem { - if let topItem = item.topItem { - nameView.backgroundColor = backdorColor - countView.backgroundColor = backdorColor + + removeControl.set(image: theme.icons.stickerPackDelete, for: .Normal) + _ = removeControl.sizeToFit() + + if item.info.flags.contains(.isAnimated) { - removeControl.set(image: theme.icons.stickerPackDelete, for: .Normal) - removeControl.sizeToFit() - imageView.setSignal(account: item.account, signal: chatMessageSticker(account: item.account, file: topItem.file, type: .thumb, scale: backingScaleFactor)) - imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: NSMakeSize(35, 35), boundingSize: NSMakeSize(35, 35), intrinsicInsets: NSEdgeInsets())) - _ = fileInteractiveFetched(account: item.account, file: topItem.file).start() - nameView.update(item.nameLayout, origin: NSMakePoint(item.insets.left + 50, 7)) - countView.update(item.countLayout, origin: NSMakePoint(item.insets.left + 50, frame.height - item.countLayout.layoutSize.height - 7)) - switch item.control { - case let .installation(installed: installed): - installationControl.isHidden = false - removeControl.isHidden = true - installationControl.image = installed ? theme.icons.stickersAddedFeatured : theme.icons.stickersAddFeatured - installationControl.sizeToFit() - installationControl.centerY(x: frame.width - item.insets.left - installationControl.frame.width) - case .none: - installationControl.isHidden = true - removeControl.isHidden = false - removeControl.centerY(x: frame.width - item.insets.left - removeControl.frame.width) - case .remove: - removeControl.isHidden = true - installationControl.isHidden = false - installationControl.image = theme.icons.stickersRemove - installationControl.sizeToFit() - installationControl.centerY(x: frame.width - item.insets.left - installationControl.frame.width) - case .empty: - removeControl.isHidden = true - installationControl.isHidden = true - case .selected: - removeControl.isHidden = true - installationControl.isHidden = false - installationControl.image = theme.icons.generalSelect - installationControl.sizeToFit() - installationControl.centerY(x: frame.width - item.insets.left - installationControl.frame.width) + if self.animatedView == nil { + self.animatedView = MediaAnimatedStickerView(frame: NSZeroRect) + containerView.addSubview(self.animatedView!) + } + self.imageView.isHidden = true + + var file: TelegramMediaFile? + if let thumbnail = item.info.thumbnail { + file = TelegramMediaFile(fileId: MediaId(namespace: 0, id: item.info.id.id), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [thumbnail], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/x-tgsticker", size: nil, attributes: [.FileName(fileName: "sticker.tgs"), .Sticker(displayText: "", packReference: .id(id: item.info.id.id, accessHash: item.info.accessHash), maskData: nil)]) + } else if let item = item.topItem { + file = item.file + } + self.animatedView?.userInteractionEnabled = false + if let file = file { + self.animatedView?.update(with: file, size: NSMakeSize(35, 35), context: item.context, parent: nil, table: item.table, parameters: nil, animated: animated, positionFlags: nil, approximateSynchronousValue: false) } + } else { + + self.animatedView?.removeFromSuperview() + self.animatedView = nil + + self.imageView.isHidden = false + + var thumbnailItem: TelegramMediaImageRepresentation? + var resourceReference: MediaResourceReference? + + if let thumbnail = item.info.thumbnail { + thumbnailItem = thumbnail + resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.info.id.id, accessHash: item.info.accessHash), resource: thumbnail.resource) + } else if let topItem = item.topItem { + let dimensions = topItem.file.dimensions?.size ?? NSMakeSize(35, 35) + thumbnailItem = TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimensions), resource: topItem.file.resource) + resourceReference = MediaResourceReference.media(media: .stickerPack(stickerPack: StickerPackReference.id(id: item.info.id.id, accessHash: item.info.accessHash), media: topItem.file), resource: topItem.file.resource) + } + if let thumbnailItem = thumbnailItem { + imageView.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, representation: thumbnailItem, scale: backingScaleFactor, synchronousLoad: false)) + } + if let resourceReference = resourceReference { + _ = fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: resourceReference, statsCategory: .file).start() + } + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: NSMakeSize(35, 35), boundingSize: NSMakeSize(35, 35), intrinsicInsets: NSEdgeInsets())) + } + + nameView.update(item.nameLayout) + countView.update(item.countLayout) + switch item.control { + case let .installation(installed: installed): + installationControl.image = installed ? theme.icons.stickersAddedFeatured : theme.icons.stickersAddFeatured + installationControl.sizeToFit() + case .remove: + installationControl.image = theme.icons.stickersRemove + installationControl.sizeToFit() + case .selected: + installationControl.image = theme.icons.generalSelect + installationControl.sizeToFit() + default: + break } + + switch item.control { + case .installation: + installationControl.isHidden = false//!containerView.mouseInside() + removeControl.isHidden = true + case .none: + installationControl.isHidden = true + removeControl.isHidden = false//!containerView.mouseInside() + case .remove: + removeControl.isHidden = true + installationControl.isHidden = false//!containerView.mouseInside() + case .empty: + removeControl.isHidden = true + installationControl.isHidden = true + case .selected: + removeControl.isHidden = true + installationControl.isHidden = false//!containerView.mouseInside() + } + } needsLayout = true } + deinit { + loadedStickerPackDisposable.dispose() + } } diff --git a/Telegram-Mac/StickerSettings.swift b/Telegram-Mac/StickerSettings.swift new file mode 100644 index 0000000000..f05007a00b --- /dev/null +++ b/Telegram-Mac/StickerSettings.swift @@ -0,0 +1,59 @@ +import Foundation +import Postbox +import SwiftSignalKit + +public enum EmojiStickerSuggestionMode: Int32 { + case none + case all + case installed +} + +public struct StickerSettings: PreferencesEntry, Equatable { + public var emojiStickerSuggestionMode: EmojiStickerSuggestionMode + + public static var defaultSettings: StickerSettings { + return StickerSettings(emojiStickerSuggestionMode: .all) + } + + init(emojiStickerSuggestionMode: EmojiStickerSuggestionMode) { + self.emojiStickerSuggestionMode = emojiStickerSuggestionMode + } + + public init(decoder: PostboxDecoder) { + self.emojiStickerSuggestionMode = EmojiStickerSuggestionMode(rawValue: decoder.decodeInt32ForKey("emojiStickerSuggestionMode", orElse: 0))! + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.emojiStickerSuggestionMode.rawValue, forKey: "emojiStickerSuggestionMode") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? StickerSettings { + return self == to + } else { + return false + } + } + + public static func ==(lhs: StickerSettings, rhs: StickerSettings) -> Bool { + return lhs.emojiStickerSuggestionMode == rhs.emojiStickerSuggestionMode + } + + func withUpdatedEmojiStickerSuggestionMode(_ emojiStickerSuggestionMode: EmojiStickerSuggestionMode) -> StickerSettings { + return StickerSettings(emojiStickerSuggestionMode: emojiStickerSuggestionMode) + } +} + +func updateStickerSettingsInteractively(postbox: Postbox, _ f: @escaping (StickerSettings) -> StickerSettings) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.stickerSettings, { entry in + let currentSettings: StickerSettings + if let entry = entry as? StickerSettings { + currentSettings = entry + } else { + currentSettings = StickerSettings.defaultSettings + } + return f(currentSettings) + }) + } +} diff --git a/Telegram-Mac/StickersPackPreviewModalController.swift b/Telegram-Mac/StickersPackPreviewModalController.swift index 0db0e3ccf0..297e35a095 100644 --- a/Telegram-Mac/StickersPackPreviewModalController.swift +++ b/Telegram-Mac/StickersPackPreviewModalController.swift @@ -16,13 +16,13 @@ import MtProtoKitMac final class StickerPackArguments { - let account: Account - let send:(TelegramMediaFile)->Void + let context: AccountContext + let send:(TelegramMediaFile, NSView)->Void let addpack:(StickerPackCollectionInfo, [ItemCollectionItem], Bool)->Void let share:(String)->Void let close:()->Void - init(account:Account, send:@escaping(Media)->Void, addpack:@escaping(StickerPackCollectionInfo, [ItemCollectionItem], Bool)->Void, share:@escaping(String)->Void, close:@escaping()->Void) { - self.account = account + init(context: AccountContext, send:@escaping(Media, NSView)->Void, addpack:@escaping(StickerPackCollectionInfo, [ItemCollectionItem], Bool)->Void, share:@escaping(String)->Void, close:@escaping()->Void) { + self.context = context self.send = send self.addpack = addpack self.share = share @@ -41,7 +41,7 @@ private class StickersModalView : View { private let headerSeparatorView:View = View() private let dismiss:ImageButton = ImageButton() private let indicatorView:ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 25, 25)) - private let shadowView: View = ShadowView() + private let shadowView: ShadowView = ShadowView() required init(frame frameRect: NSRect) { super.init(frame: frameRect) backgroundColor = theme.colors.background @@ -53,23 +53,23 @@ private class StickersModalView : View { addSubview(headerSeparatorView) addSubview(dismiss) - shadowView.backgroundColor = theme.colors.background.withAlphaComponent(1.0) + shadowView.shadowBackground = theme.colors.background shadowView.setFrameSize(frame.width, 70) addSubview(shadowView) dismiss.set(image: theme.icons.stickerPackDelete, for: .Normal) - dismiss.sizeToFit() + _ = dismiss.sizeToFit() add.disableActions() add.setFrameSize(170, 40) add.layer?.cornerRadius = 20 - add.set(color: .white, for: .Normal) + add.set(color: theme.colors.underSelectedColor, for: .Normal) add.set(font: .medium(.title), for: .Normal) - add.set(background: theme.colors.blueFill, for: .Normal) - add.set(background: theme.colors.blueFill, for: .Hover) - add.set(background: theme.colors.blueFill, for: .Highlight) - add.set(text: tr(.stickerPackAdd(0)), for: .Normal) + add.set(background: theme.colors.accent, for: .Normal) + add.set(background: theme.colors.accent, for: .Hover) + add.set(background: theme.colors.accent, for: .Highlight) + add.set(text: L10n.stickerPackAdd1Countable(0), for: .Normal) addSubview(add) headerTitle.backgroundColor = theme.colors.background @@ -77,8 +77,8 @@ private class StickersModalView : View { shareView.set(image: theme.icons.stickersShare, for: .Normal) close.set(image: theme.icons.stickerPackClose, for: .Normal) - shareView.sizeToFit() - close.sizeToFit() + _ = shareView.sizeToFit() + _ = close.sizeToFit() } @@ -104,29 +104,34 @@ private class StickersModalView : View { indicatorView.removeFromSuperview() dismiss.isHidden = !installed shareView.isHidden = false - add.set(text: tr(.stickerPackAdd(collectionItems .count)).uppercased(), for: .Normal) + add.set(text: tr(L10n.stickerPackAdd1Countable(collectionItems .count)).uppercased(), for: .Normal) + _ = add.sizeToFit(NSMakeSize(20, 0), NSMakeSize(frame.width - 40, 40), thatFit: false) add.isHidden = installed shadowView.isHidden = installed let attr = NSMutableAttributedString() - _ = attr.append(string: info.title, color: theme.colors.text, font: .medium(.custom(16))) - attr.detectLinks(type: [.Mentions], account: arguments.account, color: .blueUI, openInfo: { (peerId, _, _, _) in - _ = (arguments.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in + _ = attr.append(string: info.title, color: theme.colors.text, font: .medium(16.0)) + attr.detectLinks(type: [.Mentions], context: arguments.context, color: .accent, openInfo: { (peerId, _, _, _) in + _ = (arguments.context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { peer in arguments.close() - arguments.account.context.mainNavigation?.push(PeerInfoController(account: arguments.account, peer: peer)) + if peer.isUser || peer.isBot { + arguments.context.sharedContext.bindings.rootNavigation().push(PeerInfoController(context: arguments.context, peerId: peerId)) + } else { + arguments.context.sharedContext.bindings.rootNavigation().push(ChatAdditionController(context: arguments.context, chatLocation: .peer(peer.id))) + } }) }) - let layout = TextViewLayout(attr, maximumNumberOfLines: 1) + let layout = TextViewLayout(attr, maximumNumberOfLines: 2, alignment: .center) layout.interactions = globalLinkExecutor - layout.measure(width: frame.width - 140) + layout.measure(width: frame.width - 160) headerTitle.update(layout) let items = collectionItems.filter({ item -> Bool in return item is StickerPackItem }).map ({ item -> StickerPackGridItem in - return StickerPackGridItem(account: arguments.account, file: (item as! StickerPackItem).file, send: arguments.send, selected: {}) + return StickerPackGridItem(context: arguments.context, file: (item as! StickerPackItem).file, send: arguments.send, selected: {}) }) var insert:[GridNodeInsertItem] = [] @@ -137,7 +142,7 @@ private class StickersModalView : View { grid.removeAllItems() - grid.transaction(GridNodeTransaction(deleteItems: [], insertItems: insert, updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: frame.width, height: frame.height), insets: NSEdgeInsets(left: 10, right: 10, top: 10, bottom: installed ? 0 : 60), preloadSize: self.bounds.width, type: .fixed(itemSize: CGSize(width: 80, height: 80), lineSpacing: 10)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + grid.transaction(GridNodeTransaction(deleteItems: [], insertItems: insert, updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: frame.width, height: frame.height), insets: NSEdgeInsets(left: 0, right: 0, top: 10, bottom: installed ? 0 : 60), preloadSize: self.bounds.width, type: .fixed(itemSize: CGSize(width: 70, height: 70), lineSpacing: 10)), transition: .immediate), itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) grid.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) self.needsLayout = true @@ -180,12 +185,12 @@ private class StickersModalView : View { grid.frame = NSMakeRect(0, headerHeight, frame.width, frame.height - headerHeight) - headerTitle.centerX(y : floorToScreenPixels((headerHeight - headerTitle.frame.height)/2) + 1) + headerTitle.centerX(y : floorToScreenPixels(backingScaleFactor, (headerHeight - headerTitle.frame.height)/2) + 1) headerSeparatorView.frame = NSMakeRect(0, headerHeight - .borderSize, frame.width, .borderSize) - shareView.setFrameOrigin(frame.width - close.frame.width - 12, floorToScreenPixels((headerHeight - shareView.frame.height)/2)) - close.setFrameOrigin(12, floorToScreenPixels((headerHeight - shareView.frame.height)/2)) + shareView.setFrameOrigin(frame.width - close.frame.width - 12, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2)) + close.setFrameOrigin(12, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2)) add.centerX(y: frame.height - add.frame.height - 15) - dismiss.setFrameOrigin(NSMakePoint(shareView.frame.minX - dismiss.frame.width - 15, floorToScreenPixels((headerHeight - shareView.frame.height)/2))) + dismiss.setFrameOrigin(NSMakePoint(shareView.frame.minX - dismiss.frame.width - 15, floorToScreenPixels(backingScaleFactor, (headerHeight - shareView.frame.height)/2))) shadowView.setFrameOrigin(0, frame.height - shadowView.frame.height) } @@ -193,41 +198,37 @@ private class StickersModalView : View { -class StickersPackPreviewModalController: ModalViewController { - private let account:Account +class StickerPackPreviewModalController: ModalViewController { + private let context:AccountContext private let peerId:PeerId? private let reference:StickerPackReference private let disposable: MetaDisposable = MetaDisposable() private var arguments:StickerPackArguments! - init(_ account:Account, peerId:PeerId?, reference:StickerPackReference) { - self.account = account + init(_ context: AccountContext, peerId:PeerId?, reference:StickerPackReference) { + self.context = context self.peerId = peerId self.reference = reference - super.init(frame: NSMakeRect(0, 0, 360, 400)) + super.init(frame: NSMakeRect(0, 0, 350, 400)) bar = .init(height: 0) - arguments = StickerPackArguments(account: account, send: { [weak self] media in - self?.close() - if let peerId = peerId, let strongSelf = self { - - var attributes:[MessageAttribute] = [] - if FastSettings.isChannelMessagesMuted(peerId) { - attributes.append(NotificationInfoMessageAttribute(flags: [.muted])) + arguments = StickerPackArguments(context: context, send: { [weak self] media, view in + let interactions = (context.sharedContext.bindings.rootNavigation().controller as? ChatController)?.chatInteraction + + if let interactions = interactions, let media = media as? TelegramMediaFile, media.maskData == nil { + if let slowMode = interactions.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else { + interactions.sendAppFile(media) + self?.close() } - - _ = (strongSelf.account.postbox.loadedPeerWithId(peerId) |> filter {$0.canSendMessage && !$0.stickersRestricted} |> mapToSignal { _ in - return enqueueMessages(account: account, peerId: peerId, messages: [EnqueueMessage.message(text: "", attributes: attributes, media: media, replyToMessageId: nil)]) - }) .start() - } }, addpack: { [weak self] info, items, installed in self?.close() self?.disposable.dispose() - //installStickerSetInteractively(account: account, info: info, items: items) - _ = (!installed ? addStickerPackInteractively(postbox: account.postbox, info: info, items: items) : removeStickerPackInteractively(postbox: account.postbox, id: info.id)).start() + _ = (!installed ? addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items) : removeStickerPackInteractively(postbox: context.account.postbox, id: info.id, option: .archive)).start() }, share: { [weak self] link in self?.close() - showModal(with: ShareModalController(ShareLinkObject(account, link: link)), for: mainWindow) + showModal(with: ShareModalController(ShareLinkObject(context, link: link)), for: mainWindow) }, close: { [weak self] in self?.close() }) @@ -256,17 +257,25 @@ class StickersPackPreviewModalController: ModalViewController { super.viewDidLoad() - disposable.set((loadedStickerPack(postbox: account.postbox, network: account.network, reference: reference) |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - strongSelf.genericView.layout(with: result, arguments: strongSelf.arguments) - strongSelf.readyOnce() + disposable.set((loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: reference, forceActualized: false) |> deliverOnMainQueue).start(next: { [weak self] result in + guard let `self` = self else {return} + switch result { + case .none: + alert(for: mainWindow, info: L10n.stickerSetDontExist) + self.close() + default: + self.genericView.layout(with: result, arguments: self.arguments) + self.readyOnce() } - }, error: { error in - + })) } + override func becomeFirstResponder() -> Bool? { + return false + } + deinit { disposable.dispose() } diff --git a/Telegram-Mac/StickersViewController.swift b/Telegram-Mac/StickersViewController.swift new file mode 100644 index 0000000000..53fb44b2d7 --- /dev/null +++ b/Telegram-Mac/StickersViewController.swift @@ -0,0 +1,1215 @@ +// +// StickersViewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/07/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + +final class StickerPanelArguments { + let context: AccountContext + let sendMedia:(Media, NSView, Bool)->Void + let showPack:(StickerPackReference)->Void + let navigate:(ItemCollectionViewEntryIndex)->Void + let addPack: (StickerPackReference)->Void + let clearRecent:()->Void + let removePack:(StickerPackCollectionId)->Void + init(context: AccountContext, sendMedia: @escaping(Media, NSView, Bool)->Void, showPack: @escaping(StickerPackReference)->Void, addPack: @escaping(StickerPackReference)->Void, navigate: @escaping(ItemCollectionViewEntryIndex)->Void, clearRecent:@escaping()->Void, removePack:@escaping(StickerPackCollectionId)->Void) { + self.context = context + self.sendMedia = sendMedia + self.showPack = showPack + self.addPack = addPack + self.navigate = navigate + self.clearRecent = clearRecent + self.removePack = removePack + } +} + +extension FoundStickerSets { + func updateInfos(_ f:([(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)])->[(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)]) -> FoundStickerSets { + return FoundStickerSets.init(infos: f(self.infos), entries: self.entries) + } +} + +struct SpecificPackData : Equatable { + let info: StickerPackCollectionInfo + let peer: Peer + + static func ==(lhs: SpecificPackData, rhs: SpecificPackData) -> Bool { + if lhs.info != rhs.info { + return false + } else if !lhs.peer.isEqual(rhs.peer) { + return false + } else { + return true + } + } +} + +enum PackEntry: Comparable, Identifiable { + case stickerPack(index:Int, stableId: StickerPackCollectionId, info: StickerPackCollectionInfo, topItem: StickerPackItem?) + case recent + case saved + case featured + case specificPack(data: SpecificPackData) + + var stableId: StickerPackCollectionId { + switch self { + case let .stickerPack(data): + return data.stableId + case .recent: + return .recent + case .saved: + return .saved + case .featured: + return .featured + case let .specificPack(data): + return .specificPack(data.info.id) + + } + } + + var index: Int { + switch self { + case .featured: + return -1 + case .saved: + return 0 + case .recent: + return 1 + case .specificPack: + return 2 + case let .stickerPack(index, _, _, _): + return 3 + index + } + } + + static func <(lhs: PackEntry, rhs: PackEntry) -> Bool { + return lhs.index < rhs.index + } + + +} + + +private enum StickerPacksUpdate { + case generic(animated: Bool, scrollToTop: Bool?) + case scroll(animated: Bool) + case navigate(StickerPacksIndex, animated: Bool) +} + + +private enum StickerPacksIndex : Hashable, Comparable { + case sticker(ItemCollectionViewEntryIndex) + case speficicPack(ItemCollectionId) + case recent(Int) + case saved(Int) + case featured(Int) + case emojiRelated(Int) + var packIndex:ItemCollectionViewEntryIndex { + switch self { + case let .sticker(index): + return index + case let .saved(index), let .recent(index), let .featured(index), let .emojiRelated(index): + return ItemCollectionViewEntryIndex.lowerBound(collectionIndex: Int32(index), collectionId: ItemCollectionId(namespace: 0, id: 0)) + case let .speficicPack(id): + return ItemCollectionViewEntryIndex.lowerBound(collectionIndex: 2, collectionId: id) + } + } + + var collectionId: StickerPackCollectionId { + switch self { + case let .sticker(index): + return .pack(index.collectionId) + case .recent: + return .recent + case .saved: + return .saved + case let .speficicPack(id): + return .specificPack(id) + case .featured: + return .featured + case .emojiRelated: + return .emojiRelated + } + } + + func hash(into hasher: inout Hasher) { + + } + + var index: Int { + switch self { + case .emojiRelated: + return -2 + case .featured: + return -1 + case .saved: + return 0 + case .recent: + return 1 + case .speficicPack: + return 2 + case .sticker: + return 3 + } + } + + static func <(lhs: StickerPacksIndex, rhs: StickerPacksIndex) -> Bool { + switch lhs { + case let .sticker(lhsIndex): + if case let .sticker(rhsIndex) = rhs { + return lhsIndex < rhsIndex + } else { + return lhs.index < rhs.index + } + default: + return lhs.index < rhs.index + } + } +} + +private enum StickerPacksScrollState: Equatable { + static func == (lhs: StickerPacksScrollState, rhs: StickerPacksScrollState) -> Bool { + switch lhs { + case .initial: + if case .initial = rhs { + return true + } else { + return false + } + case let .loadFeaturedMore(lhsFound): + if case .loadFeaturedMore(let rhsFound) = rhs { + return lhsFound.sets.infos.map { $0.0 } == rhsFound.sets.infos.map { $0.0 } + } else { + return false + } + case let .scroll(aroundIndex): + if case .scroll(aroundIndex) = rhs { + return true + } else { + return false + } + case let .navigate(aroundIndex): + if case .navigate(aroundIndex) = rhs { + return true + } else { + return false + } + } + } + + case initial + case loadFeaturedMore(StickerPacksSearchData) + case scroll(aroundIndex: StickerPacksIndex) + case navigate(index: StickerPacksIndex) +} + +private struct StickerPacksSearchData { + let sets: FoundStickerSets + let loading: Bool + let basicFeaturedCount: Int + let emojiRelated: [FoundStickerItem] +} + +private struct StickerPacksUpdateData { + let view: ItemCollectionsView? + let update: StickerPacksUpdate + let specificPack:Tuple2? + let searchData: StickerPacksSearchData? + init(_ view: ItemCollectionsView?, _ update: StickerPacksUpdate, _ specificPack: Tuple2?, searchData: StickerPacksSearchData? = nil) { + self.view = view + self.update = update + self.specificPack = specificPack + self.searchData = searchData + } +} +enum StickerPackInfo : Equatable { + case pack(StickerPackCollectionInfo?, installed: Bool, featured: Bool) + case speficicPack(StickerPackCollectionInfo?) + case recent + case saved + case emojiRelated + + var installed: Bool { + switch self { + case let .pack(_, installed, _): + return installed + default: + return true + } + } + var featured: Bool { + switch self { + case let .pack(_, _, featured): + return featured + default: + return false + } + } +} + +enum StickerPackCollectionId : Hashable { + case pack(ItemCollectionId) + case recent + case featured + case specificPack(ItemCollectionId) + case saved + case emojiRelated + var itemCollectionId:ItemCollectionId? { + switch self { + case let .pack(collectionId): + return collectionId + case let .specificPack(collectionId): + return collectionId + default: + return nil + } + } + +} + + +private enum StickerPackEntry : TableItemListNodeEntry { + case pack(index: StickerPacksIndex, files:[TelegramMediaFile], packInfo: StickerPackInfo, collectionId: StickerPackCollectionId) + + + static func < (lhs: StickerPackEntry, rhs: StickerPackEntry) -> Bool { + return lhs.index < rhs.index + } + + static func == (lhs: StickerPackEntry, rhs: StickerPackEntry) -> Bool { + switch lhs { + case let .pack(index, lhsFiles, packInfo, collectionId): + if case .pack(index, let rhsFiles, packInfo, collectionId) = rhs { + if lhsFiles.count != rhsFiles.count { + return false + } else { + for (i, lhsFile) in lhsFiles.enumerated() { + if !lhsFile.isEqual(to: rhsFiles[i]) { + return false + } + } + } + return true + } else { + return false + } + } + } + + var index: StickerPacksIndex { + switch self { + case let .pack(index, _, _, _): + return index + } + } + + var stableId: StickerPackCollectionId { + switch self { + case let .pack( _, _, _, collectionId): + return collectionId + } + } + + func item(_ arguments: StickerPanelArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .pack(_, files, packInfo, collectionId): + return StickerPackPanelRowItem(initialSize, context: arguments.context, arguments: arguments, files: files, packInfo: packInfo, collectionId: collectionId) + } + } +} + +private func stickersEntries(view: ItemCollectionsView?, searchData: StickerPacksSearchData?, specificPack:Tuple2?) -> [StickerPackEntry] { + var entries:[StickerPackEntry] = [] + + if let view = view { + var available: [ItemCollectionViewEntry] = view.entries + var index: Int32 = 0 + + var ids:[MediaId : MediaId] = [:] + + if view.lower == nil { + if !view.orderedItemListsViews[1].items.isEmpty { + var files:[TelegramMediaFile] = [] + for item in view.orderedItemListsViews[1].items { + if let entry = item.contents as? SavedStickerItem { + if let id = entry.file.id, ids[id] == nil, entry.file.isStaticSticker || entry.file.isAnimatedSticker { + ids[id] = id + files.append(entry.file) + } + } + } + if !files.isEmpty { + entries.append(.pack(index: .saved(0), files: files, packInfo: .saved, collectionId: .saved)) + } + } + + if !view.orderedItemListsViews[0].items.isEmpty { + var files:[TelegramMediaFile] = [] + for item in view.orderedItemListsViews[0].items { + if let entry = item.contents as? RecentMediaItem { + if let file = entry.media as? TelegramMediaFile, let id = file.id, ids[id] == nil, file.isStaticSticker || file.isAnimatedSticker { + ids[id] = id + files.append(file) + } + } + if files.count == 20 { + break + } + } + if !files.isEmpty { + entries.append(.pack(index: .recent(1), files: files, packInfo: .recent, collectionId: .recent)) + } + } + + + if let specificPack = specificPack, let info = specificPack._0.packInfo { + var files:[TelegramMediaFile] = [] + for item in info.1 { + if let item = item as? StickerPackItem { + if let id = item.file.id, ids[id] == nil, item.file.isStaticSticker || item.file.isAnimatedSticker { + ids[id] = id + files.append(item.file) + } + } + } + if !files.isEmpty { + entries.append(.pack(index: .speficicPack(info.0.id), files: files, packInfo: .speficicPack(info.0), collectionId: .specificPack(info.0.id))) + } + } + + } + + for (id, info, item) in view.collectionInfos { + if !available.isEmpty, let item = item { + var files: [TelegramMediaFile] = [] + if let info = info as? StickerPackCollectionInfo { + let items = available.enumerated().reversed() + for (i, entry) in items { + if entry.index.collectionId == info.id { + if let item = available.remove(at: i).item as? StickerPackItem { + files.insert(item.file, at: 0) + } + } + } + if !files.isEmpty { + entries.append(.pack(index: .sticker(ItemCollectionViewEntryIndex(collectionIndex: index, collectionId: id, itemIndex: item.index)), files: files, packInfo: .pack(info, installed: true, featured: false), collectionId: .pack(id))) + } + } + } else { + break + } + index += 1 + } + } else if let searchData = searchData { + if !searchData.loading { + var available = searchData.sets.entries + var index: Int32 = 0 + + if !searchData.emojiRelated.isEmpty { + + var validIds:Set = Set() + + let files:[TelegramMediaFile] = searchData.emojiRelated.map { $0.file }.reduce([], { current, value in + var current = current + guard let id = value.id else { + return current + } + if !validIds.contains(id) { + validIds.insert(id) + current.append(value) + } + return current + }).sorted(by: { lhs, rhs in + if lhs.isAnimatedSticker && !rhs.isAnimatedSticker { + return true + } else { + return false + } + }) + entries.append(.pack(index: .emojiRelated(0), files: files, packInfo: .emojiRelated, collectionId: .emojiRelated)) + + // entries.append(.pack(index: .sticker(ItemCollectionViewEntryIndex(collectionIndex: index, collectionId: 0, itemIndex: .init(index: 0, id: 0))), files: Array(files), packInfo: .emojiRelated, collectionId: nil)) + index += 1 + } + + for set in searchData.sets.infos { + if !available.isEmpty { + var files: [TelegramMediaFile] = [] + if let info = set.1 as? StickerPackCollectionInfo { + let items = available.enumerated().reversed() + for (i, entry) in items { + if entry.index.collectionId == info.id { + if let item = available.remove(at: i).item as? StickerPackItem { + files.insert(item.file, at: 0) + } + } + } + if !files.isEmpty { + entries.append(.pack(index: .sticker(ItemCollectionViewEntryIndex(collectionIndex: index, collectionId: info.id, itemIndex: .init(index: 0, id: 0))), files: Array(files.prefix(5)), packInfo: .pack(info, installed: set.3, featured: true), collectionId: .pack(info.id))) + } + } + } else { + break + } + index += 1 + } + } + + } + + return entries +} + +private func packEntries(view: ItemCollectionsView?, specificPack:Tuple2?) -> [PackEntry] { + var entries:[PackEntry] = [] + var index: Int = 0 + + if let view = view { + entries.append(.featured) + + if !view.orderedItemListsViews[1].items.isEmpty { + entries.append(.saved) + } + if !view.orderedItemListsViews[0].items.isEmpty { + entries.append(.recent) + } + if let specificPack = specificPack, let info = specificPack._0.packInfo?.0 { + entries.append(.specificPack(data: SpecificPackData(info: info, peer: specificPack._1))) + } + + for (_, info, item) in view.collectionInfos { + if let info = info as? StickerPackCollectionInfo { + entries.append(.stickerPack(index: index, stableId: .pack(info.id), info: info, topItem: item as? StickerPackItem)) + index += 1 + } + } + } + + return entries +} + + +private func prepareStickersTransition(from:[AppearanceWrapperEntry], to: [AppearanceWrapperEntry], initialSize: NSSize, arguments: StickerPanelArguments, update: StickerPacksUpdate) -> TableUpdateTransition { + let (removed,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + }) + let state: TableScrollState + var anim: Bool + switch update { + case let .generic(animated, scrollToTop): + anim = animated + if let scrollToTop = scrollToTop { + if scrollToTop { + state = .up(animated) + } else { + state = .saveVisible(.lower) + } + } else { + state = .none(nil) + } + + case let .scroll(animated): + state = .saveVisible(.upper) + anim = animated + case let .navigate(index, animated): + state = .top(id: index.collectionId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0) + anim = animated + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: anim, state: state, grouping: !anim) +} + +fileprivate func preparePackTransition(from:[AppearanceWrapperEntry]?, to:[AppearanceWrapperEntry], context: AccountContext, initialSize:NSSize) -> TableUpdateTransition { + + let (deleted,inserted,updated) = proccessEntriesWithoutReverse(from, right: to, { (entry) -> TableRowItem in + switch entry.entry { + case let .stickerPack(index, stableId, info, topItem): + return StickerPackRowItem(initialSize, packIndex: index, context: context, stableId: stableId, info: info, topItem: topItem) + case .recent: + return RecentPackRowItem(initialSize, entry.entry.stableId) + case .featured: + return RecentPackRowItem(initialSize, entry.entry.stableId) + case .saved: + return RecentPackRowItem(initialSize, entry.entry.stableId) + case let .specificPack(data): + return StickerSpecificPackItem(initialSize, stableId: entry.entry.stableId, specificPack: (data.info, data.peer), account: context.account) + } + }) + + return TableUpdateTransition(deleted: deleted, inserted: inserted, updated:updated, animated: true, state: .none(nil)) + +} + +class NStickersView : View { + fileprivate let tableView:TableView = TableView(frame: NSZeroRect) + fileprivate var restrictedView:RestrictionWrappedView? + private let emptySearchView = ImageView() + private let emptySearchContainer: View = View() + + let searchView = SearchView(frame: .zero) + private let searchContainer = View() + fileprivate let packsView:HorizontalTableView = HorizontalTableView(frame: NSZeroRect) + private let separator:View = View() + fileprivate let tabsContainer: View = View() + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + addSubview(tableView) + + searchContainer.addSubview(searchView) + addSubview(searchContainer) + + emptySearchContainer.addSubview(emptySearchView) + tabsContainer.addSubview(packsView) + tabsContainer.addSubview(separator) + addSubview(tabsContainer) + addSubview(emptySearchContainer) + + emptySearchContainer.isHidden = true + emptySearchContainer.isEventLess = true + + updateLocalizationAndTheme(theme: theme) + } + + func updateRestricion(_ peer: Peer?) { + if let peer = peer, let text = permissionText(from: peer, for: .banSendStickers) { + restrictedView?.removeFromSuperview() + restrictedView = RestrictionWrappedView(text) + addSubview(restrictedView!) + } else { + restrictedView?.removeFromSuperview() + restrictedView = nil + } + setFrameSize(frame.size) + needsLayout = true + } + + func updateEmpties(isEmpty: Bool, animated: Bool) { + + let emptySearchHidden: Bool = !isEmpty + + if !emptySearchHidden { + emptySearchContainer.isHidden = false + } + + emptySearchContainer.change(opacity: emptySearchHidden ? 0 : 1, animated: animated, completion: { [weak self] completed in + if completed { + self?.emptySearchContainer.isHidden = emptySearchHidden + } + }) + + needsLayout = true + } + + private var searchState: SearchState? = nil + + func updateSearchState(_ searchState: SearchState, animated: Bool) { + self.searchState = searchState + switch searchState.state { + case .Focus: + tabsContainer.change(pos: NSMakePoint(0, -tabsContainer.frame.height), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + case .None: + tabsContainer.change(pos: NSMakePoint(0, 0), animated: animated) + searchContainer.change(pos: NSMakePoint(0, tabsContainer.frame.maxY), animated: animated) + } + tableView.change(size: NSMakeSize(frame.width, frame.height - searchContainer.frame.maxY), animated: animated) + tableView.change(pos: NSMakePoint(0, searchContainer.frame.maxY), animated: animated) + + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + self.restrictedView?.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + self.separator.backgroundColor = theme.colors.border + self.tableView.updateLocalizationAndTheme(theme: theme) + self.tableView.backgroundColor = theme.colors.background + self.tableView.documentView?.background = theme.colors.background + self.emptySearchView.image = theme.icons.stickersEmptySearch + self.emptySearchView.sizeToFit() + self.emptySearchContainer.backgroundColor = theme.colors.background + self.searchView.updateLocalizationAndTheme(theme: theme) + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + override func layout() { + super.layout() + + let initial: CGFloat = searchState?.state == .Focus ? -50 : 0 + + tabsContainer.frame = NSMakeRect(0, initial, frame.width, 50) + separator.frame = NSMakeRect(0, tabsContainer.frame.height - .borderSize, tabsContainer.frame.width, .borderSize) + packsView.frame = tabsContainer.focus(NSMakeSize(frame.width, 40)) + + + searchContainer.frame = NSMakeRect(0, tabsContainer.frame.maxY, frame.width, 50) + searchView.setFrameSize(NSMakeSize(frame.width - 20, 30)) + searchView.center() + + + tableView.frame = NSMakeRect(0, searchContainer.frame.maxY, frame.width, frame.height - searchContainer.frame.maxY) + restrictedView?.setFrameSize(frame.size) + + emptySearchContainer.frame = tableView.frame + emptySearchView.center() + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +class NStickersViewController: TelegramGenericViewController, TableViewDelegate, Notifable { + + private let searchValue = ValuePromise(.init(state: .None, request: nil)) + private var searchState: SearchState = .init(state: .None, request: nil) { + didSet { + self.searchValue.set(searchState) + } + } + private let position = ValuePromise(ignoreRepeated: true) + private let disposable = MetaDisposable() + private let searchStateDisposable = MetaDisposable() + private let specificPeerId = ValuePromise(ignoreRepeated: true) + private var listener: TableScrollListener! + private var interactions: EntertainmentInteractions? + private weak var chatInteraction: ChatInteraction? + var makeSearchCommand:((ESearchCommand)->Void)? + override init(_ context: AccountContext) { + super.init(context) + bar = .init(height: 0) + } + + private func updateSearchState(_ state: SearchState) { + self.position.set(.initial) + self.searchState = state + if !state.request.isEmpty { + self.makeSearchCommand?(.loading) + } + if self.isLoaded() == true { + self.genericView.updateSearchState(state, animated: true) + self.genericView.tableView.scroll(to: .up(true)) + + } + } + + deinit { + disposable.dispose() + searchStateDisposable.dispose() + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.genericView.packsView.updateLocalizationAndTheme(theme: theme) + } + + func update(with interactions:EntertainmentInteractions, chatInteraction: ChatInteraction) { + self.interactions = interactions + self.chatInteraction?.remove(observer: self) + self.chatInteraction = chatInteraction + chatInteraction.add(observer: self) + if isLoaded() { + genericView.updateRestricion(chatInteraction.presentation.peer) + } + self.specificPeerId.set(chatInteraction.peerId) + } + + func notify(with value: Any, oldValue: Any, animated: Bool) { + if let value = value as? ChatPresentationInterfaceState, let oldValue = oldValue as? ChatPresentationInterfaceState, let peer = value.peer, let oldPeer = oldValue.peer { + if permissionText(from: peer, for: .banSendStickers) != permissionText(from: oldPeer, for: .banSendStickers) { + genericView.updateRestricion(peer) + } + } + } + + func isEqual(to other: Notifable) -> Bool { + return other === self + } + + func isSelectable(row: Int, item: TableRowItem) -> Bool { + return true + } + func selectionWillChange(row: Int, item: TableRowItem, byClick: Bool) -> Bool { + return true + } + func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) { + if byClick, let collectionId = item.stableId.base as? StickerPackCollectionId { + if let item = genericView.tableView.item(stableId: collectionId) { + self.genericView.tableView.removeScroll(listener: self.listener) + self.genericView.tableView.scroll(to: .top(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), completion: { [weak self] _ in + if let `self` = self { + self.genericView.tableView.addScroll(listener: self.listener) + } + }) + } else { + var index: StickerPacksIndex? = nil + switch collectionId { + case let .pack(id): + if let item = item as? StickerPackRowItem { + index = .sticker(ItemCollectionViewEntryIndex.lowerBound(collectionIndex: Int32(item.packIndex), collectionId: id)) + } + case .featured: + self.interactions?.toggleSearch() + case .saved: + index = .saved(0) + case .recent: + index = .recent(1) + case let .specificPack(id): + index = .speficicPack(id) + case .emojiRelated: + break + } + if let index = index { + self.genericView.tableView.removeScroll(listener: self.listener) + self.position.set(.navigate(index: index)) + } + } + + } + } + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } + + override func viewDidLoad() { + super.viewDidLoad() + let context = self.context + let initialSize = self.atomicSize + + + let searchInteractions = SearchInteractions({ [weak self] state, _ in + self?.updateSearchState(state) + }, { [weak self] state in + self?.updateSearchState(state) + }) + + genericView.searchView.searchInteractions = searchInteractions + + listener = TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self, position.visibleRows.length > 0 else { + return + } + let item = self.genericView.tableView.item(at: position.visibleRows.location) + self.genericView.packsView.changeSelection(stableId: item.stableId) + self.genericView.packsView.scroll(to: .center(id: item.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0)) + }) + + self.genericView.packsView.delegate = self + + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let foundPacks: Atomic = Atomic(value: nil) + + let previousPacks:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + + let arguments = StickerPanelArguments(context: context, sendMedia: { [weak self] media, view, silent in + guard let `self` = self, let chatInteraction = self.chatInteraction else { return } + if let slowMode = chatInteraction.presentation.slowMode, slowMode.hasLocked { + showSlowModeTimeoutTooltip(slowMode, for: view) + } else if let file = media as? TelegramMediaFile { + self.interactions?.sendSticker(file, silent) + } + }, showPack: { [weak self] reference in + if let peerId = self?.chatInteraction?.peerId { + showModal(with: StickerPackPreviewModalController(context, peerId: peerId, reference: reference), for: context.window) + } + }, addPack: { [weak self] reference in + _ = showModalProgress(signal: loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: reference, forceActualized: false) + |> filter { result in + switch result { + case .result: + return true + default: + return false + } + } + |> take(1) + |> mapToSignal { result -> Signal in + switch result { + case let .result(info, items, _): + return addStickerPackInteractively(postbox: context.account.postbox, info: info, items: items) |> map { info.id } + default: + return .complete() + } + } + |> deliverOnMainQueue, for: context.window).start(next: { [weak self] result in + if let `self` = self { + if !self.searchState.request.isEmpty { + self.makeSearchCommand?(.close) + self.position.set(.navigate(index: StickerPacksIndex.sticker(ItemCollectionViewEntryIndex.lowerBound(collectionIndex: 0, collectionId: result)))) + } + } + }) + }, navigate: { [weak self] index in + self?.position.set(.navigate(index: .sticker(index))) + }, clearRecent: { + confirm(for: context.window, header: L10n.stickersConfirmClearRecentHeader, information: L10n.stickersConfirmClearRecentText, okTitle: L10n.stickersConfirmClearRecentOK, successHandler: { _ in + _ = context.account.postbox.transaction({ transaction in + clearRecentlyUsedStickers(transaction: transaction) + }).start() + }) + }, removePack: { collectionId in + if let id = collectionId.itemCollectionId { + _ = showModalProgress(signal: removeStickerPackInteractively(postbox: context.account.postbox, id: id, option: .delete), for: context.window).start() + } + }) + + let specificPackData: Signal?, NoError> = self.specificPeerId.get() |> mapToSignal { peerId -> Signal in + return context.account.postbox.loadedPeerWithId(peerId) + } |> mapToSignal { peer -> Signal?, NoError> in + if peer.isSupergroup { + return peerSpecificStickerPack(postbox: context.account.postbox, network: context.account.network, peerId: peer.id) |> map { data in + return Tuple2(data, peer) + } + } else { + return .single(nil) + } + } + + let signal = combineLatest(queue: prepareQueue, self.searchValue.get(), self.position.get()) |> mapToSignal { values -> Signal in + + let count = initialSize.with { size -> Int in + return Int(round((size.height * (values.1 == .initial ? 2 : 20)) / 60 * 5)) + } + if values.0.state == .None { + var firstTime: Bool = true + switch values.1 { + case .initial: + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: count) + |> mapToSignal { view in + return specificPackData |> map { specificPack in + let scrollToTop = firstTime + firstTime = false + return StickerPacksUpdateData(view, .generic(animated: scrollToTop, scrollToTop: scrollToTop), specificPack) + } + } + case let .scroll(aroundIndex): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex.packIndex, count: count) + |> mapToSignal { view in + return specificPackData |> map { specificPack in + let update: StickerPacksUpdate + if firstTime { + firstTime = false + update = .scroll(animated: false) + } else { + update = .generic(animated: false, scrollToTop: false) + } + return StickerPacksUpdateData(view, update, specificPack) + } + } + case let .navigate(index): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudSavedStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index.packIndex, count: count) + |> mapToSignal { view in + return specificPackData |> map { specificPack in + let update: StickerPacksUpdate + if firstTime { + firstTime = false + update = .navigate(index, animated: true) + } else { + update = .generic(animated: false, scrollToTop: false) + } + return StickerPacksUpdateData(view, update, specificPack) + } + } + case .loadFeaturedMore: + fatalError("load featured for basic packs is not possible") + } + } else { + let searchText = values.0.request.lowercased() + if values.0.request.isEmpty { + switch values.1 { + case .initial: + return combineLatest(context.account.viewTracker.featuredStickerPacks(), context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) |> map { value, view in + var found = FoundStickerSets() + + 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) + } + } + } + + for (collectionIndex, set) in value.enumerated() { + var entries:[ItemCollectionViewEntry] = [] + + for item in set.topItems { + entries.append(ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: Int32(collectionIndex), collectionId: set.info.id, itemIndex: item.index), item: item)) + } + if !entries.isEmpty { + found = found.merge(with: FoundStickerSets(infos: [(set.info.id, set.info, nil, installedPacks.contains(set.info.id))], entries: entries)) + } + } + let searchData = StickerPacksSearchData(sets: found, loading: false, basicFeaturedCount: found.infos.count, emojiRelated: []) + return StickerPacksUpdateData(nil, .generic(animated: true, scrollToTop: true), nil, searchData: searchData) + } + case let .loadFeaturedMore(current): + return combineLatest(requestOldFeaturedStickerPacks(network: context.account.network, postbox: context.account.postbox, offset: current.sets.infos.count - current.basicFeaturedCount, limit: 50), context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) |> map { values, view in + var found = current.sets + + + 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) + } + } + } + + found = found.updateInfos( { infos in + var infos = infos + for (i, info) in infos.enumerated() { + infos[i] = (info.0, info.1, info.2, installedPacks.contains(info.0)) + } + return infos + }) + + for (collectionIndex, set) in values.enumerated() { + var entries:[ItemCollectionViewEntry] = [] + + for item in set.topItems { + entries.append(ItemCollectionViewEntry(index: ItemCollectionViewEntryIndex(collectionIndex: Int32(collectionIndex), collectionId: set.info.id, itemIndex: item.index), item: item)) + } + if !entries.isEmpty { + found = found.merge(with: FoundStickerSets(infos: [(set.info.id, set.info, nil, installedPacks.contains(set.info.id))], entries: entries)) + } + } + let searchData = StickerPacksSearchData(sets: found, loading: false, basicFeaturedCount: current.basicFeaturedCount, emojiRelated: []) + return StickerPacksUpdateData(nil, .generic(animated: false, scrollToTop: nil), nil, searchData: searchData) + } + default: + fatalError() + } + + } else { + let searchLocal = searchStickerSets(postbox: context.account.postbox, query: searchText) |> delay(0.2, queue: prepareQueue) |> map(Optional.init) + let searchRemote = searchStickerSetsRemotely(network: context.account.network, query: searchText) |> delay(0.2, queue: prepareQueue) |> map(Optional.init) + + let emojiRelated: Signal<[FoundStickerItem], NoError> = context.sharedContext.inputSource.searchEmoji(postbox: context.account.postbox, sharedContext: context.sharedContext, query: searchText, completeMatch: true, checkPrediction: false) |> mapToSignal { emojis in + + let signals = emojis.map { + searchStickers(account: context.account, query: $0, scope: [.installed]) + } + return combineLatest(signals) |> map { + $0.reduce([], { current, value in + return current + value.filter { $0.file.stickerText != nil && emojis.contains($0.file.stickerText!) } + }) + } + } |> delay(0.2, queue: prepareQueue) + + return combineLatest(searchLocal, searchRemote, emojiRelated) |> map { local, remote, emojiRelated in + var value = FoundStickerSets() + if let local = local { + value = value.merge(with: local) + } + if let remote = remote { + value = value.merge(with: remote) + } + + let searchData = StickerPacksSearchData(sets: value, loading: remote == nil && value.entries.isEmpty, basicFeaturedCount: 0, emojiRelated: emojiRelated) + return StickerPacksUpdateData(nil, .generic(animated: true, scrollToTop: nil), nil, searchData: searchData) + } + } + + } + + } |> deliverOnPrepareQueue + + let transition = combineLatest(queue: prepareQueue, appearanceSignal, signal) + |> map { appearance, data -> (TableUpdateTransition, TableUpdateTransition, [AppearanceWrapperEntry]) in + + _ = foundPacks.swap(data.searchData) + + let entries = stickersEntries(view: data.view, searchData: data.searchData, specificPack: data.specificPack).map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + let from = previous.swap(entries) + + let entriesPack = packEntries(view: data.view, specificPack: data.specificPack).map { AppearanceWrapperEntry(entry: $0, appearance: appearance) } + let fromPacks = previousPacks.swap(entriesPack) + + let transition = prepareStickersTransition(from: from, to: entries, initialSize: initialSize.with { $0 }, arguments: arguments, update: data.update) + let packTransition = preparePackTransition(from: fromPacks, to: entriesPack, context: context, initialSize: initialSize.with { $0 }) + + return (transition, packTransition, entriesPack) + } |> deliverOnMainQueue + + var first: Bool = true + + disposable.set(transition.start(next: { [weak self] (transition, packTransition, entriesPack) in + guard let `self` = self else { return } + + self.genericView.tableView.removeScroll(listener: self.listener) + self.genericView.tableView.merge(with: transition) + self.genericView.packsView.merge(with: packTransition) + self.genericView.updateEmpties(isEmpty: self.genericView.tableView.isEmpty, animated: !first) + + self.genericView.tableView.addScroll(listener: self.listener) + first = false + + var visibleRows = self.genericView.tableView.visibleRows() + if visibleRows.length == 0, !self.genericView.tableView.isEmpty { + visibleRows.location = 0 + visibleRows.length = 1 + } + if visibleRows.length > 0 { + let item = self.genericView.tableView.item(at: visibleRows.location) + self.genericView.packsView.changeSelection(stableId: item.stableId) + } + + self.makeSearchCommand?(.normal) + + + if !packTransition.isEmpty { + var resortRange: NSRange = NSMakeRange(0, 0) + let entries = entriesPack.map( {$0.entry }) + + for entry in entries { + switch entry { + case .saved, .recent, .specificPack, .featured: + resortRange.location += 1 + default: + break + } + } + if entries.count > resortRange.location { + resortRange.length = entries.count - resortRange.location + } + self.genericView.packsView.resortController = TableResortController(resortRange: resortRange, start: { _ in }, resort: { _ in }, complete: { fromIndex, toIndex in + + + if fromIndex == toIndex { + return + } + + let entries = entriesPack.map( {$0.entry }) + + + let fromEntry = entries[fromIndex] + + guard case let .stickerPack(_, _, fromPackInfo, _) = fromEntry else { + return + } + + var referenceId: ItemCollectionId? + var beforeAll = false + var afterAll = false + if toIndex < entries.count { + switch entries[toIndex] { + case let .stickerPack(_, _, toPackInfo, _): + referenceId = toPackInfo.id + default: + if entries[toIndex] < fromEntry { + beforeAll = true + } else { + afterAll = true + } + } + } else { + afterAll = true + } + + + let _ = (context.account.postbox.transaction { transaction -> Void in + var infos = transaction.getItemCollectionsInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks) + var reorderInfo: ItemCollectionInfo? + for i in 0 ..< infos.count { + if infos[i].0 == fromPackInfo.id { + reorderInfo = infos[i].1 + infos.remove(at: i) + break + } + } + if let reorderInfo = reorderInfo { + if let referenceId = referenceId { + var inserted = false + for i in 0 ..< infos.count { + if infos[i].0 == referenceId { + if fromIndex < toIndex { + infos.insert((fromPackInfo.id, reorderInfo), at: i + 1) + } else { + infos.insert((fromPackInfo.id, reorderInfo), at: i) + } + inserted = true + break + } + } + if !inserted { + infos.append((fromPackInfo.id, reorderInfo)) + } + } else if beforeAll { + infos.insert((fromPackInfo.id, reorderInfo), at: 0) + } else if afterAll { + infos.append((fromPackInfo.id, reorderInfo)) + } + addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: Namespaces.ItemCollection.CloudStickerPacks, content: .sync, noDelay: false) + transaction.replaceItemCollectionInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks, itemCollectionInfos: infos) + } + } |> deliverOnMainQueue).start(completed: { [weak self] in + if let `self` = self { + self.genericView.tableView.removeScroll(listener: self.listener) + } + }) + }) + } + + self.readyOnce() + })) + + self.genericView.tableView.setScrollHandler { [weak self] position in + if let `self` = self { + let entries = previous.with ({ $0 }) + let index:StickerPacksIndex? + + if let foundPacks = foundPacks.with ({ $0 }), self.searchState.state == .Focus { + self.position.set(.loadFeaturedMore(foundPacks)) + } else { + switch position.direction { + case .bottom: + index = entries.last?.entry.index + case .top: + index = entries.first?.entry.index + case .none: + index = nil + } + if let index = index, self.searchState.state == .None { + self.position.set(.scroll(aroundIndex: index)) + } + } + } + } + + self.position.set(.initial) + + } + override func scrollup(force: Bool = false) { + self.position.set(.initial) + self.genericView.packsView.scroll(to: .up(true)) + // self.genericView.tableView.scroll(to: .up(true)) + } + + override var supportSwipes: Bool { + return !self.genericView.packsView._mouseInside() + } + +} diff --git a/Telegram-Mac/StorageUsageController.swift b/Telegram-Mac/StorageUsageController.swift index d24a083afc..eab13343e0 100644 --- a/Telegram-Mac/StorageUsageController.swift +++ b/Telegram-Mac/StorageUsageController.swift @@ -8,19 +8,23 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit private final class StorageUsageControllerArguments { - let account: Account + let context: AccountContext let updateKeepMedia: () -> Void + let updateMediaLimit: (Int32) -> Void let openPeerMedia: (PeerId) -> Void - - init(account: Account, updateKeepMedia: @escaping () -> Void, openPeerMedia: @escaping (PeerId) -> Void) { - self.account = account + let clearAll:()->Void + init(context: AccountContext, updateKeepMedia: @escaping () -> Void, updateMediaLimit: @escaping(Int32)->Void, openPeerMedia: @escaping (PeerId) -> Void, clearAll: @escaping () -> Void) { + self.context = context self.updateKeepMedia = updateKeepMedia self.openPeerMedia = openPeerMedia + self.clearAll = clearAll + self.updateMediaLimit = updateMediaLimit } } @@ -30,12 +34,15 @@ private enum StorageUsageSection: Int32 { } private enum StorageUsageEntry: TableItemListNodeEntry { - case keepMedia(Int32, String, String) - case keepMediaInfo(Int32, String) - - case collecting(Int32, String) - case peersHeader(Int32, String) - case peer(Int32, Int32, Peer, String) + case keepMedia(Int32, String, String, GeneralViewType) + case keepMediaInfo(Int32, String, GeneralViewType) + case keepMediaLimitHeader(Int32, String, GeneralViewType) + case keepMediaLimit(Int32, Int32, GeneralViewType) + case keepMediaLimitInfo(Int32, String, GeneralViewType) + case clearAll(Int32, Bool, GeneralViewType) + case collecting(Int32, String, GeneralViewType) + case peersHeader(Int32, String, GeneralViewType) + case peer(Int32, Int32, Peer, String, GeneralViewType) case section(Int32) var stableId: Int32 { @@ -44,11 +51,19 @@ private enum StorageUsageEntry: TableItemListNodeEntry { return 0 case .keepMediaInfo: return 1 - case .collecting: + case .keepMediaLimitHeader: return 2 - case .peersHeader: + case .keepMediaLimit: return 3 - case let .peer(_, _, peer, _): + case .keepMediaLimitInfo: + return 4 + case .clearAll: + return 5 + case .collecting: + return 6 + case .peersHeader: + return 7 + case let .peer(_, _, peer, _, _): return Int32(peer.id.hashValue) case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId @@ -61,12 +76,20 @@ private enum StorageUsageEntry: TableItemListNodeEntry { return 0 case .keepMediaInfo: return 1 - case .collecting: + case .keepMediaLimitHeader: return 2 - case .peersHeader: + case .keepMediaLimit: return 3 - case let .peer(_, index, _, _): - return 4 + index + case .keepMediaLimitInfo: + return 4 + case .clearAll: + return 5 + case .collecting: + return 6 + case .peersHeader: + return 7 + case let .peer(_, index, _, _, _): + return 8 + index case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId } @@ -74,15 +97,23 @@ private enum StorageUsageEntry: TableItemListNodeEntry { var index:Int32 { switch self { - case .keepMedia(let sectionId, _, _): + case .keepMedia(let sectionId, _, _, _): + return (sectionId * 1000) + stableIndex + case .keepMediaInfo(let sectionId, _, _): + return (sectionId * 1000) + stableIndex + case .keepMediaLimitHeader(let sectionId, _, _): return (sectionId * 1000) + stableIndex - case .keepMediaInfo(let sectionId, _): + case .keepMediaLimit(let sectionId, _, _): return (sectionId * 1000) + stableIndex - case .collecting(let sectionId, _): + case .keepMediaLimitInfo(let sectionId, _, _): return (sectionId * 1000) + stableIndex - case .peersHeader(let sectionId, _): + case .clearAll(let sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .peer(sectionId, _, _, _): + case .collecting(let sectionId, _, _): + return (sectionId * 1000) + stableIndex + case .peersHeader(let sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .peer(sectionId, _, _, _, _): return (sectionId * 1000) + stableIndex case .section(let sectionId): return (sectionId + 1) * 1000 - sectionId @@ -91,26 +122,50 @@ private enum StorageUsageEntry: TableItemListNodeEntry { static func ==(lhs: StorageUsageEntry, rhs: StorageUsageEntry) -> Bool { switch lhs { - case let .keepMedia(sectionId, text, value): - if case .keepMedia(sectionId, text, value) = rhs { + case let .keepMedia(sectionId, text, value, viewType): + if case .keepMedia(sectionId, text, value, viewType) = rhs { return true } else { return false } - case let .keepMediaInfo(sectionId, text): - if case .keepMediaInfo(sectionId, text) = rhs { + case let .keepMediaInfo(sectionId, text, viewType): + if case .keepMediaInfo(sectionId, text, viewType) = rhs { return true } else { return false } - case let .collecting(sectionId, text): - if case .collecting(sectionId, text) = rhs { + case let .keepMediaLimitHeader(sectionId, value, viewType): + if case .keepMediaLimitHeader(sectionId, value, viewType) = rhs { return true } else { return false } - case let .peersHeader(sectionId, text): - if case .peersHeader(sectionId, text) = rhs { + case let .keepMediaLimit(sectionId, value, viewType): + if case .keepMediaLimit(sectionId, value, viewType) = rhs { + return true + } else { + return false + } + case let .keepMediaLimitInfo(sectionId, value, viewType): + if case .keepMediaLimitInfo(sectionId, value, viewType) = rhs { + return true + } else { + return false + } + case let .clearAll(sectionId, enabled, viewType): + if case .clearAll(sectionId, enabled, viewType) = rhs { + return true + } else { + return false + } + case let .collecting(sectionId, text, viewType): + if case .collecting(sectionId, text, viewType) = rhs { + return true + } else { + return false + } + case let .peersHeader(sectionId, text, viewType): + if case .peersHeader(sectionId, text, viewType) = rhs { return true } else { return false @@ -121,8 +176,8 @@ private enum StorageUsageEntry: TableItemListNodeEntry { } else { return false } - case let .peer(lhsSectionId, lhsIndex, lhsPeer, lhsValue): - if case let .peer(rhsSectionId, rhsIndex, rhsPeer, rhsValue) = rhs { + case let .peer(lhsSectionId, lhsIndex, lhsPeer, lhsValue, lhsViewType): + if case let .peer(rhsSectionId, rhsIndex, rhsPeer, rhsValue, rhsViewType) = rhs { if lhsIndex != rhsIndex { return false } @@ -132,6 +187,9 @@ private enum StorageUsageEntry: TableItemListNodeEntry { if !arePeersEqual(lhsPeer, rhsPeer) { return false } + if lhsViewType != rhsViewType { + return false + } if lhsValue != rhsValue { return false } @@ -149,38 +207,50 @@ private enum StorageUsageEntry: TableItemListNodeEntry { func item(_ arguments: StorageUsageControllerArguments, initialSize: NSSize) -> TableRowItem { switch self { - case let .keepMedia(_, text, value): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .context(stateback: { - return value - }), action: { + case let .keepMedia(_, text, value, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .context(value), viewType: viewType, action: { arguments.updateKeepMedia() }) - - case let .keepMediaInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .collecting(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .peersHeader(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: text) - case let .peer(_, _, peer, value): - return ShortPeerRowItem(initialSize, peer: peer, account: arguments.account, stableId: stableId, enabled: true, height: 40, photoSize: NSMakeSize(30, 30), drawCustomSeparator: true, drawLastSeparator: true, inset: NSEdgeInsets(left: 30, right: 30), generalType: .context(stateback: { () -> String in - return value - }), action: { + case let .keepMediaInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .keepMediaLimitHeader(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .keepMediaLimit(_, value, viewType): + let values = [5, 16, 36, Int32.max] + var value = value + if !values.contains(value) { + value = Int32.max + } + return SelectSizeRowItem(initialSize, stableId: stableId, current: value, sizes: values, hasMarkers: false, titles: ["5GB", "16GB", "32GB", L10n.storageUsageLimitNoLimit], viewType: viewType, selectAction: { selected in + arguments.updateMediaLimit(values[selected]) + }) + case let .keepMediaLimitInfo(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .collecting(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, alignment: .center, additionLoading: true, viewType: viewType) + case let .clearAll(_, enabled, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: tr(L10n.storageClearAll), type: .next, viewType: viewType, action: { + arguments.clearAll() + }, enabled: enabled) + case let .peersHeader(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .peer(_, _, peer, value, viewType): + return ShortPeerRowItem(initialSize, peer: peer, account: arguments.context.account, stableId: stableId, height: 44, photoSize: NSMakeSize(30, 30), isLookSavedMessage: true, inset: NSEdgeInsets(left: 30, right: 30), generalType: .context(value), viewType: viewType, action: { arguments.openPeerMedia(peer.id) }) case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) } } } private func stringForKeepMediaTimeout(_ timeout: Int32) -> String { if timeout <= 7 * 24 * 60 * 60 { - return tr(.timerWeeksCountable(1)) + return tr(L10n.timerWeeksCountable(1)) } else if timeout <= 1 * 31 * 24 * 60 * 60 { - return tr(.timerMonthsCountable(1)) + return tr(L10n.timerMonthsCountable(1)) } else { - return tr(.timerForever) + return tr(L10n.timerForever) } } @@ -192,16 +262,31 @@ private func storageUsageControllerEntries(cacheSettings: CacheStorageSettings, entries.append(.section(sectionId)) sectionId += 1 - entries.append(.keepMedia(sectionId, tr(.storageUsageKeepMedia), stringForKeepMediaTimeout(cacheSettings.defaultCacheStorageTimeout))) - entries.append(.keepMediaInfo(sectionId, tr(.storageUsageKeepMediaDescription))) + entries.append(.keepMedia(sectionId, L10n.storageUsageKeepMedia, stringForKeepMediaTimeout(cacheSettings.defaultCacheStorageTimeout), .singleItem)) + + entries.append(.keepMediaInfo(sectionId, L10n.storageUsageKeepMediaDescription1, .textBottomItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + - var addedHeader = false + // + entries.append(.keepMediaLimitHeader(sectionId, L10n.storageUsageLimitHeader, .textTopItem)) + entries.append(.keepMediaLimit(sectionId, cacheSettings.defaultCacheStorageLimitGigabytes, .singleItem)) + entries.append(.keepMediaLimitInfo(sectionId, L10n.storageUsageLimitDesc, .textBottomItem)) + entries.append(.section(sectionId)) sectionId += 1 var exists:[PeerId:PeerId] = [:] if let cacheStats = cacheStats, case let .result(stats) = cacheStats { + + entries.append(.clearAll(sectionId, !stats.peers.isEmpty, .singleItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + var statsByPeerId: [(PeerId, Int64)] = [] for (peerId, categories) in stats.media { if exists[peerId] == nil { @@ -217,22 +302,33 @@ private func storageUsageControllerEntries(cacheSettings: CacheStorageSettings, } var index: Int32 = 0 - for (peerId, size) in statsByPeerId.sorted(by: { $0.1 > $1.1 }) { - if size >= 32 * 1024 { - if let peer = stats.peers[peerId], !peer.isSecretChat { - if !addedHeader { - addedHeader = true - entries.append(.peersHeader(sectionId, tr(.storageUsageChatsHeader))) - } - entries.append(.peer(sectionId, index, peer, dataSizeString(Int(size)))) - index += 1 - } - } + + let filtered = statsByPeerId.sorted(by: { $0.1 > $1.1 }).filter { peerId, size -> Bool in + return size >= 32 * 1024 && stats.peers[peerId] != nil && !stats.peers[peerId]!.isSecretChat + } + + if !filtered.isEmpty { + entries.append(.peersHeader(sectionId, L10n.storageUsageChatsHeader, .textTopItem)) + } + + for (i, value) in filtered.enumerated() { + let peer = stats.peers[value.0]! + entries.append(.peer(sectionId, index, peer, dataSizeString(Int(value.1)), bestGeneralViewType(filtered, for: i))) + index += 1 } } else { - entries.append(.collecting(sectionId, tr(.storageUsageCalculating))) + + entries.append(.clearAll(sectionId, true, .singleItem)) + + entries.append(.section(sectionId)) + sectionId += 1 + + entries.append(.collecting(sectionId, L10n.storageUsageCalculating, .singleItem)) } + entries.append(.section(sectionId)) + sectionId += 1 + return entries } @@ -253,49 +349,46 @@ class StorageUsageController: TableViewController { readyOnce() - let account = self.account + let context = self.context let initialSize = self.atomicSize let cacheSettingsPromise = Promise() - cacheSettingsPromise.set(account.postbox.preferencesView(keys: [PreferencesKeys.cacheStorageSettings]) + cacheSettingsPromise.set(context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]) |> map { view -> CacheStorageSettings in - let cacheSettings: CacheStorageSettings - if let value = view.values[PreferencesKeys.cacheStorageSettings] as? CacheStorageSettings { - cacheSettings = value - } else { - cacheSettings = CacheStorageSettings.defaultSettings - } - - return cacheSettings + return view.entries[SharedDataKeys.cacheStorageSettings] as? CacheStorageSettings ?? CacheStorageSettings.defaultSettings }) let statsPromise = Promise() - statsPromise.set(.single(nil) |> then(collectCacheUsageStats(account: account) |> map { Optional($0) })) + statsPromise.set(.single(nil) |> then(collectCacheUsageStats(account: context.account, additionalCachePaths: [], logFilesPath: "~/Library/Group Containers/\(ApiEnvironment.group)/logs".nsstring.expandingTildeInPath) |> map { Optional($0) })) let actionDisposables = DisposableSet() let clearDisposable = MetaDisposable() actionDisposables.add(clearDisposable) - let arguments = StorageUsageControllerArguments(account: account, updateKeepMedia: { [weak self] in + let arguments = StorageUsageControllerArguments(context: context, updateKeepMedia: { [weak self] in if let strongSelf = self { let timeoutAction: (Int32) -> Void = { timeout in - let _ = updateCacheStorageSettingsInteractively(postbox: account.postbox, { current in + let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in return current.withUpdatedDefaultCacheStorageTimeout(timeout) }).start() } - if let item = strongSelf.genericView.item(stableId: StorageUsageEntry.keepMedia(0, "", "").stableId), let view = (strongSelf.genericView.viewNecessary(at: item.index) as? GeneralInteractedRowView)?.textView { + if let item = strongSelf.genericView.item(stableId: StorageUsageEntry.keepMedia(0, "", "", .singleItem).stableId), let view = (strongSelf.genericView.viewNecessary(at: item.index) as? GeneralInteractedRowView)?.textView { - showPopover(for: view, with: SPopoverViewController(items: [SPopoverItem(tr(.timerWeeksCountable(1)), { + showPopover(for: view, with: SPopoverViewController(items: [SPopoverItem(tr(L10n.timerWeeksCountable(1)), { timeoutAction(7 * 24 * 60 * 60) - }), SPopoverItem(tr(.timerMonthsCountable(1)), { + }), SPopoverItem(tr(L10n.timerMonthsCountable(1)), { timeoutAction(1 * 31 * 24 * 60 * 60) - }), SPopoverItem(tr(.timerForever), { + }), SPopoverItem(tr(L10n.timerForever), { timeoutAction(Int32.max) })]), edge: .minX, inset: NSMakePoint(0,-30)) } } + }, updateMediaLimit: { limit in + let _ = updateCacheStorageSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in + return current.withUpdatedDefaultCacheStorageLimitGigabytes(limit) + }).start() }, openPeerMedia: { peerId in let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in if let result = result, case let .result(stats) = result { @@ -329,16 +422,21 @@ class StorageUsageController: TableViewController { } } } + statsPromise.set(.single(.result(CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers, otherSize: stats.otherSize, otherPaths: stats.otherPaths, cacheSize: stats.cacheSize, tempPaths: stats.tempPaths, tempSize: stats.tempSize, immutableSize: stats.immutableSize)))) - statsPromise.set(.single(.result(CacheUsageStats(media: media, mediaResourceIds: stats.mediaResourceIds, peers: stats.peers)))) - - clearDisposable.set(clearCachedMediaResources(account: account, mediaResourceIds: clearResourceIds).start()) + clearDisposable.set(clearCachedMediaResources(account: context.account, mediaResourceIds: clearResourceIds).start()) } }), for: mainWindow) } } }) + }, clearAll: { + confirm(for: context.window, information: L10n.storageClearAllConfirmDescription, okTitle: L10n.storageClearAll, successHandler: { _ in + let path = context.account.postbox.mediaBox.basePath + _ = showModalProgress(signal: combineLatest(clearImageCache(), context.account.postbox.mediaBox.allFileContexts() |> mapToSignal { clearCache(path, excludes: $0) }), for: context.window).start() + statsPromise.set(.single(CacheUsageStatsResult.result(.init(media: [:], mediaResourceIds: [:], peers: [:], otherSize: 0, otherPaths: [], cacheSize: 0, tempPaths: [], tempSize: 0, immutableSize: 0)))) + }) }) let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) diff --git a/Telegram-Mac/StoredMessageFromSearchPeer.swift b/Telegram-Mac/StoredMessageFromSearchPeer.swift new file mode 100644 index 0000000000..ca349999c1 --- /dev/null +++ b/Telegram-Mac/StoredMessageFromSearchPeer.swift @@ -0,0 +1,62 @@ +// +// StoredMessageFromSearchPeer.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import SyncCore + +func storedMessageFromSearchPeer(account: Account, peer: Peer) -> Signal { + return account.postbox.transaction { transaction -> PeerId in + if transaction.getPeer(peer.id) == nil { + updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in + return updatedPeer + }) + } + if let group = transaction.getPeer(peer.id) as? TelegramGroup, let migrationReference = group.migrationReference { + return migrationReference.peerId + } + return peer.id + } +} + +func storedMessageFromSearch(account: Account, message: Message) -> Signal { + return account.postbox.transaction { transaction -> Void in + if transaction.getMessage(message.id) == nil { + for (_, peer) in message.peers { + if transaction.getPeer(peer.id) == nil { + updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in + return updatedPeer + }) + } + } + + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + + let _ = transaction.addMessages([storeMessage], location: .Random) + } + } +} + +func storeMessageFromSearch(transaction: Transaction, message: Message) { + if transaction.getMessage(message.id) == nil { + for (_, peer) in message.peers { + if transaction.getPeer(peer.id) == nil { + updatePeers(transaction: transaction, peers: [peer], update: { previousPeer, updatedPeer in + return updatedPeer + }) + } + } + + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + + let _ = transaction.addMessages([storeMessage], location: .Random) + } +} diff --git a/Telegram-Mac/StringPluralization.swift b/Telegram-Mac/StringPluralization.swift index 790f3991ba..2ea0520852 100644 --- a/Telegram-Mac/StringPluralization.swift +++ b/Telegram-Mac/StringPluralization.swift @@ -27,6 +27,9 @@ enum PluralizationForm { } func presentationStringsPluralizationForm(_ lc: UInt32, _ value: Int32) -> PluralizationForm { + if value == 0 { + return .zero + } switch numberPluralizationForm(lc, value) { case .zero: return .zero @@ -42,3 +45,5 @@ func presentationStringsPluralizationForm(_ lc: UInt32, _ value: Int32) -> Plura return .other } } + + diff --git a/Telegram-Mac/SuggestionLocalizationViewController.swift b/Telegram-Mac/SuggestionLocalizationViewController.swift index 73aeb249eb..2396a89e30 100644 --- a/Telegram-Mac/SuggestionLocalizationViewController.swift +++ b/Telegram-Mac/SuggestionLocalizationViewController.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit private class SuggestionControllerView : View { let textView:TextView = TextView() @@ -24,10 +25,12 @@ private class SuggestionControllerView : View { addSubview(tableView) addSubview(suggestTextView) + textView.backgroundColor = theme.colors.background + suggestTextView.backgroundColor = theme.colors.background tableView.setFrameSize(NSMakeSize(frameRect.width, frameRect.height - 50)) separatorView.setFrameSize(frameRect.width, .borderSize) - separatorView.backgroundColor = .border + separatorView.backgroundColor = theme.colors.border layout() } @@ -58,11 +61,11 @@ private class SuggestionControllerView : View { } class SuggestionLocalizationViewController: ModalViewController { - private let account:Account + private let context:AccountContext private let suggestionInfo:SuggestedLocalizationInfo private var languageCode:String = "en" - init(_ account:Account, suggestionInfo: SuggestedLocalizationInfo) { - self.account = account + init(_ context: AccountContext, suggestionInfo: SuggestedLocalizationInfo) { + self.context = context self.suggestionInfo = suggestionInfo super.init(frame: NSMakeRect(0, 0, 280, 198)) bar = .init(height: 0) @@ -73,11 +76,11 @@ class SuggestionLocalizationViewController: ModalViewController { } override var modalInteractions: ModalInteractions? { - return ModalInteractions(acceptTitle: tr(.modalOK), accept: { [weak self] in + return ModalInteractions(acceptTitle: L10n.modalOK, accept: { [weak self] in if let strongSelf = self { strongSelf.close() - _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.account.postbox, languageCode: strongSelf.suggestionInfo.languageCode).start() - _ = showModalProgress(signal: downoadAndApplyLocalization(postbox: strongSelf.account.postbox, network: strongSelf.account.network, languageCode: strongSelf.languageCode), for: mainWindow).start() + _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.context.account.postbox, languageCode: strongSelf.suggestionInfo.languageCode).start() + _ = showModalProgress(signal: downloadAndApplyLocalization(accountManager: strongSelf.context.sharedContext.accountManager, postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, languageCode: strongSelf.languageCode), for: mainWindow).start() } }, drawBorder: true, height: 40) } @@ -129,29 +132,27 @@ class SuggestionLocalizationViewController: ModalViewController { if let info = enInfo { - _ = genericView.tableView.insert(item: LanguageRowItem(initialSize: initialSize, stableId: 0, selected: selected == 0, value: info, action: { [weak self] in + _ = genericView.tableView.insert(item: LanguageRowItem(initialSize: initialSize, stableId: 0, selected: selected == 0, deletable: false, value: info, action: { [weak self] in self?.reloadItems(0, swap) }, reversed: true), at: 0) } if let info = currentInfo { - _ = genericView.tableView.insert(item: LanguageRowItem(initialSize: initialSize, stableId: 1, selected: selected == 1, value: info, action: { [weak self] in + _ = genericView.tableView.insert(item: LanguageRowItem(initialSize: initialSize, stableId: 1, selected: selected == 1, deletable: false, value: info, action: { [weak self] in self?.reloadItems(1, swap) }, reversed: true), at: swap ? 0 : 1) } - - let otherInfo = LocalizationInfo(languageCode: "", title: NativeLocalization("Suggest.Localization.Other"), localizedTitle: suggestionInfo.localizedKey("Suggest.Localization.Other") ) - _ = genericView.tableView.addItem(item: LanguageRowItem(initialSize: initialSize, stableId: 10, selected: false, value: otherInfo, action: { [weak self] in + // public init(languageCode: String, baseLanguageCode: String?, customPluralizationCode: String?, title: String, localizedTitle: String, isOfficial: Bool, totalStringCount: Int32, translatedStringCount: Int32, platformUrl: String) { + let otherInfo = LocalizationInfo(languageCode: "", baseLanguageCode: nil, customPluralizationCode: nil, title: NativeLocalization("Suggest.Localization.Other"), localizedTitle: suggestionInfo.localizedKey("Suggest.Localization.Other"), isOfficial: true, totalStringCount: 0, translatedStringCount: 0, platformUrl: "" ) + + _ = genericView.tableView.addItem(item: LanguageRowItem(initialSize: initialSize, stableId: 10, selected: false, deletable: false, value: otherInfo, action: { [weak self] in if let strongSelf = self { strongSelf.close() - strongSelf.account.context.mainNavigation?.push(LanguageViewController(strongSelf.account)) - _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.account.postbox, languageCode: strongSelf.suggestionInfo.languageCode).start() + strongSelf.context.sharedContext.bindings.rootNavigation().push(LanguageViewController(strongSelf.context)) + _ = markSuggestedLocalizationAsSeenInteractively(postbox: strongSelf.context.account.postbox, languageCode: strongSelf.suggestionInfo.languageCode).start() } }, reversed: true)) - -// _ = genericView.tableView.addItem(item: GeneralInteractedRowItem(initialSize, name: suggestionInfo.localizedKey("Suggest.Localization.Other"), type: .next, action: { [weak self] in -// -// }, drawCustomSeparator: false, inset: NSEdgeInsets(left: 25, right: 25))) + } } diff --git a/Telegram-Mac/SyncCoreExtension.swift b/Telegram-Mac/SyncCoreExtension.swift new file mode 100644 index 0000000000..b699d34ff9 --- /dev/null +++ b/Telegram-Mac/SyncCoreExtension.swift @@ -0,0 +1,31 @@ +// +// SyncCoreExtension.swift +// Telegram +// +// Created by Mikhail Filimonov on 01.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SyncCore + +extension PixelDimensions { + var size: CGSize { + return CGSize(width: CGFloat(self.width), height: CGFloat(self.height)) + } + init(_ size: CGSize) { + self.init(width: Int32(abs(size.width)), height: Int32(abs(size.height))) + } + init(_ width: Int32, _ height: Int32) { + self.init(width: width, height: height) + } +} +extension CGSize { + var pixel: PixelDimensions { + return PixelDimensions(self) + } +} + +enum AppLogEvents : String { + case imageEditor = "image_editor_used" +} diff --git a/Telegram-Mac/System.swift b/Telegram-Mac/System.swift index 30f3d333cc..eeeb5010d5 100644 --- a/Telegram-Mac/System.swift +++ b/Telegram-Mac/System.swift @@ -7,10 +7,11 @@ // import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore import TGUIKit -import PostboxMac +import Postbox private let _dQueue = Queue.init(name: "chatListQueue") private let _sQueue = Queue.init(name: "ChatQueue") @@ -36,10 +37,25 @@ var mainWindow:Window { fatalError("window not found") } +var systemAppearance: NSAppearance { + if #available(OSX 10.14, *) { + return NSApp.effectiveAppearance + } else { + return NSAppearance.current + } +} + public func deliverOnPrepareQueue(_ signal: Signal) -> Signal { return signal |> deliverOn(prepareQueue) } +public func deliverOnMessagesViewQueue(_ signal: Signal) -> Signal { + return signal |> deliverOn(messagesViewQueue) +} + +public func deliverOnResourceQueue(_ signal: Signal) -> Signal { + return signal |> deliverOn(resourcesQueue) +} func proccessEntriesWithoutReverse(_ left:[R]?,right:[R],_ convertEntry:@escaping (R) -> T) -> ([Int],[(Int,T)],[(Int,T)]) where R:Comparable, R:Identifiable { @@ -102,16 +118,8 @@ func link(path:String?, ext:String) -> String? { if let path = path, path.nsstring.pathExtension.length == 0 && FileManager.default.fileExists(atPath: path) { let path = path.nsstring.appendingPathExtension(ext)! if !FileManager.default.fileExists(atPath: path) { - do { - try FileManager.default.removeItem(atPath: path) - } - catch { - } - do { - try FileManager.default.createSymbolicLink(atPath: path, withDestinationPath: realPath!) - } - catch { - } + try? FileManager.default.removeItem(atPath: path) + try? FileManager.default.createSymbolicLink(atPath: path, withDestinationPath: realPath!) } realPath = path } @@ -122,12 +130,24 @@ func delay(_ delay:Double, closure:@escaping ()->()) { let when = DispatchTime.now() + delay DispatchQueue.main.asyncAfter(deadline: when, execute: closure) } +func delay(_ delay:Double, onQueue queue: DispatchQueue, closure:@escaping ()->()) { + let when = DispatchTime.now() + delay + queue.asyncAfter(deadline: when, execute: closure) +} - -func fileSize(_ path:String) -> Int32? { +func fs(_ path:String) -> Int32? { - if let attrs = try? FileManager.default.attributesOfItem(atPath: path) as NSDictionary { + if var attrs = try? FileManager.default.attributesOfItem(atPath: path) as NSDictionary { + + if attrs["NSFileType"] as? String == "NSFileTypeSymbolicLink" { + if let path = try? FileManager.default.destinationOfSymbolicLink(atPath: path) { + attrs = (try? FileManager.default.attributesOfItem(atPath: path) as NSDictionary) ?? attrs + } + } + + let size = attrs.fileSize() + if size > UInt64(INT32_MAX) { return INT32_MAX } @@ -138,3 +158,4 @@ func fileSize(_ path:String) -> Int32? { + diff --git a/Telegram-Mac/TGCallConnectionDescription.h b/Telegram-Mac/TGCallConnectionDescription.h deleted file mode 100644 index eff41367ce..0000000000 --- a/Telegram-Mac/TGCallConnectionDescription.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// TGCallConnectionDescription.h -// Telegram -// -// Created by keepcoder on 03/05/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -#import - - -@interface TGCallConnectionDescription : NSObject - - @property (nonatomic, readonly) int64_t identifier; - @property (nonatomic, strong, readonly) NSString *ipv4; - @property (nonatomic, strong, readonly) NSString *ipv6; - @property (nonatomic, readonly) int32_t port; - @property (nonatomic, strong, readonly) NSData *peerTag; - -- (instancetype)initWithIdentifier:(int64_t)identifier ipv4:(NSString *)ipv4 ipv6:(NSString *)ipv6 port:(int32_t)port peerTag:(NSData *)peerTag; - - @end - - -@interface TGCallConnection : NSObject - - @property (nonatomic, strong, readonly) NSData *key; - @property (nonatomic, strong, readonly) NSData *keyHash; - @property (nonatomic, strong, readonly) TGCallConnectionDescription *defaultConnection; - @property (nonatomic, strong, readonly) NSArray *alternativeConnections; - -- (instancetype)initWithKey:(NSData *)key keyHash:(NSData *)keyHash defaultConnection:(TGCallConnectionDescription *)defaultConnection alternativeConnections:(NSArray *)alternativeConnections; - -@end diff --git a/Telegram-Mac/TGCallConnectionDescription.m b/Telegram-Mac/TGCallConnectionDescription.m deleted file mode 100644 index 08555a2224..0000000000 --- a/Telegram-Mac/TGCallConnectionDescription.m +++ /dev/null @@ -1,41 +0,0 @@ -// -// TGCallConnectionDescription.m -// Telegram -// -// Created by keepcoder on 03/05/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -#import "TGCallConnectionDescription.h" - -@implementation TGCallConnectionDescription - -- (instancetype)initWithIdentifier:(int64_t)identifier ipv4:(NSString *)ipv4 ipv6:(NSString *)ipv6 port:(int32_t)port peerTag:(NSData *)peerTag { - self = [super init]; - if (self != nil) { - _identifier = identifier; - _ipv4 = ipv4; - _ipv6 = ipv6; - _port = port; - _peerTag = peerTag; - } - return self; -} - - @end - - -@implementation TGCallConnection - -- (instancetype)initWithKey:(NSData *)key keyHash:(NSData *)keyHash defaultConnection:(TGCallConnectionDescription *)defaultConnection alternativeConnections:(NSArray *)alternativeConnections { - self = [super init]; - if (self != nil) { - _key = key; - _keyHash = keyHash; - _defaultConnection = defaultConnection; - _alternativeConnections = alternativeConnections; - } - return self; -} - -@end diff --git a/Telegram-Mac/TGCallUtils.h b/Telegram-Mac/TGCallUtils.h index 9f5aacb045..1c6e26eea1 100644 --- a/Telegram-Mac/TGCallUtils.h +++ b/Telegram-Mac/TGCallUtils.h @@ -1,10 +1,10 @@ #import - -void TGCallAesIgeEncryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv); -void TGCallAesIgeDecryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv); - -void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output); -void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output); - -void TGCallRandomBytes(uint8_t *buffer, size_t length); -void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num); +// +//void TGCallAesIgeEncryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv); +//void TGCallAesIgeDecryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv); +// +//void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output); +//void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output); +// +//void TGCallRandomBytes(uint8_t *buffer, size_t length); +//void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num); diff --git a/Telegram-Mac/TGCallUtils.mm b/Telegram-Mac/TGCallUtils.mm index d541767c57..1921477d60 100644 --- a/Telegram-Mac/TGCallUtils.mm +++ b/Telegram-Mac/TGCallUtils.mm @@ -3,342 +3,342 @@ #import #import #import - - -# define AES_MAXNR 14 -# define AES_BLOCK_SIZE 16 - -#define N_WORDS (AES_BLOCK_SIZE / sizeof(unsigned long)) -typedef struct { - unsigned long data[N_WORDS]; -} aes_block_t; - -/* XXX: probably some better way to do this */ -#if defined(__i386__) || defined(__x86_64__) -# define UNALIGNED_MEMOPS_ARE_FAST 1 -#else -# define UNALIGNED_MEMOPS_ARE_FAST 0 -#endif - -#if UNALIGNED_MEMOPS_ARE_FAST -# define load_block(d, s) (d) = *(const aes_block_t *)(s) -# define store_block(d, s) *(aes_block_t *)(d) = (s) -#else -# define load_block(d, s) memcpy((d).data, (s), AES_BLOCK_SIZE) -# define store_block(d, s) memcpy((d), (s).data, AES_BLOCK_SIZE) -#endif - -void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { - size_t len; - size_t n; - uint8_t const *inB; - uint8_t *outB; - - unsigned char aesIv[AES_BLOCK_SIZE]; - memcpy(aesIv, iv, AES_BLOCK_SIZE); - unsigned char ccIv[AES_BLOCK_SIZE]; - memcpy(ccIv, (void *)((uint8_t *)iv + AES_BLOCK_SIZE), AES_BLOCK_SIZE); - - assert(((size_t)inBytes | (size_t)outBytes | (size_t)aesIv | (size_t)ccIv) % sizeof(long) == - 0); - - void *tmpInBytes = malloc(length); - len = length / AES_BLOCK_SIZE; - inB = (uint8_t *)inBytes; - outB = (uint8_t *)tmpInBytes; - - aes_block_t *inp = (aes_block_t *)inB; - aes_block_t *outp = (aes_block_t *)outB; - for (n = 0; n < N_WORDS; ++n) { - outp->data[n] = inp->data[n]; - } - - --len; - inB += AES_BLOCK_SIZE; - outB += AES_BLOCK_SIZE; - uint8_t const *inBCC = (uint8_t *)inBytes; - - aes_block_t const *iv3p = (aes_block_t *)ccIv; - - if (len > 0) { - while (len) { - aes_block_t *inp = (aes_block_t *)inB; - aes_block_t *outp = (aes_block_t *)outB; - - for (n = 0; n < N_WORDS; ++n) { - outp->data[n] = inp->data[n] ^ iv3p->data[n]; - } - - iv3p = (const aes_block_t *)inBCC; - --len; - inBCC += AES_BLOCK_SIZE; - inB += AES_BLOCK_SIZE; - outB += AES_BLOCK_SIZE; - } - } - - size_t realOutLength = 0; - CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, 0, key, 32, aesIv, tmpInBytes, length, outBytes, length, &realOutLength); - free(tmpInBytes); - - assert(result == kCCSuccess); - - len = length / AES_BLOCK_SIZE; - - aes_block_t const *ivp = (aes_block_t *)inB; - aes_block_t *iv2p = (aes_block_t *)ccIv; - - inB = (uint8_t *)inBytes; - outB = (uint8_t *)outBytes; - - while (len) { - aes_block_t *inp = (aes_block_t *)inB; - aes_block_t *outp = (aes_block_t *)outB; - - for (n = 0; n < N_WORDS; ++n) { - outp->data[n] ^= iv2p->data[n]; - } - ivp = outp; - iv2p = inp; - --len; - inB += AES_BLOCK_SIZE; - outB += AES_BLOCK_SIZE; - } - - memcpy(iv, ivp->data, AES_BLOCK_SIZE); - memcpy((void *)((uint8_t *)iv + AES_BLOCK_SIZE), iv2p->data, AES_BLOCK_SIZE); -} - -void TGCallAesIgeEncryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) -{ - uint8_t *outData = (uint8_t *)malloc(length); - TGCallAesIgeEncrypt(inBytes, outData, length, key, iv); - memcpy(outBytes, outData, length); - free(outData); -} - -void TGCallAesIgeDecrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { - unsigned char aesIv[AES_BLOCK_SIZE]; - memcpy(aesIv, iv, AES_BLOCK_SIZE); - unsigned char ccIv[AES_BLOCK_SIZE]; - memcpy(ccIv, (void *)((uint8_t *)iv + AES_BLOCK_SIZE), AES_BLOCK_SIZE); - - assert(((size_t)inBytes | (size_t)outBytes | (size_t)aesIv | (size_t)ccIv) % sizeof(long) == - 0); - - CCCryptorRef decryptor = NULL; - CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionECBMode, key, 32, nil, &decryptor); - if (decryptor != NULL) { - size_t len; - size_t n; - - len = length / AES_BLOCK_SIZE; - - aes_block_t *ivp = (aes_block_t *)(aesIv); - aes_block_t *iv2p = (aes_block_t *)(ccIv); - - uint8_t *inB = (uint8_t *)inBytes; - uint8_t *outB = (uint8_t *)outBytes; - - while (len) { - aes_block_t tmp; - aes_block_t *inp = (aes_block_t *)inB; - aes_block_t *outp = (aes_block_t *)outB; - - for (n = 0; n < N_WORDS; ++n) - tmp.data[n] = inp->data[n] ^ iv2p->data[n]; - - size_t dataOutMoved = 0; - CCCryptorStatus result = CCCryptorUpdate(decryptor, &tmp, AES_BLOCK_SIZE, outB, AES_BLOCK_SIZE, &dataOutMoved); - assert(result == kCCSuccess); - assert(dataOutMoved == AES_BLOCK_SIZE); - - for (n = 0; n < N_WORDS; ++n) - outp->data[n] ^= ivp->data[n]; - - ivp = inp; - iv2p = outp; - - inB += AES_BLOCK_SIZE; - outB += AES_BLOCK_SIZE; - - --len; - } - - memcpy(iv, ivp->data, AES_BLOCK_SIZE); - memcpy((void *)((uint8_t *)iv + AES_BLOCK_SIZE), iv2p->data, AES_BLOCK_SIZE); - - CCCryptorRelease(decryptor); - } -} - -void TGCallAesIgeDecryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { - uint8_t *outData = (uint8_t *)malloc(length); - TGCallAesIgeDecrypt(inBytes, outData, length, key, iv); - memcpy(outBytes, outData, length); - free(outData); -} - -void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output) -{ - CC_SHA1(msg, (CC_LONG)length, output); -} - -void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output) -{ - CC_SHA256(msg, (CC_LONG)length, output); -} - -void TGCallRandomBytes(uint8_t *buffer, size_t length) -{ - arc4random_buf(buffer, length); -} - - -static void ctr128_inc(unsigned char *counter) -{ - uint32_t n = 16, c = 1; - - do { - --n; - c += counter[n]; - counter[n] = (uint8_t)c; - c >>= 8; - } while (n); -} - -static void ctr128_inc_aligned(unsigned char *counter) -{ - size_t *data, c, d, n; - const union { - long one; - char little; - } is_endian = { - 1 - }; - - if (is_endian.little || ((size_t)counter % sizeof(size_t)) != 0) { - ctr128_inc(counter); - return; - } - - data = (size_t *)counter; - c = 1; - n = 16 / sizeof(size_t); - do { - --n; - d = data[n] += c; - /* did addition carry? */ - c = ((d - c) ^ d) >> (sizeof(size_t) * 8 - 1); - } while (n); -} - -@interface TGCallAesCtr : NSObject { - CCCryptorRef _cryptor; - - unsigned char _ivec[16]; - unsigned int _num; - unsigned char _ecount[16]; -} - -@end - -@implementation TGCallAesCtr - -- (instancetype)initWithKey:(const void *)key keyLength:(int)keyLength iv:(const void *)iv ecount:(void *)ecount num:(uint32_t)num { - self = [super init]; - if (self != nil) { - _num = num; - memcpy(_ecount, ecount, 16); - memcpy(_ivec, iv, 16); - - CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode, key, keyLength, nil, &_cryptor); - } - return self; -} - -- (void)dealloc { - if (_cryptor) { - CCCryptorRelease(_cryptor); - } -} - -- (uint32_t)num { - return _num; -} - -- (void *)ecount { - return _ecount; -} - -- (void)encryptIn:(const unsigned char *)in out:(unsigned char *)out len:(size_t)len { - unsigned int n; - size_t l = 0; - - assert(in && out); - assert(_num < 16); - - n = _num; - - if (16 % sizeof(size_t) == 0) { /* always true actually */ - do { - while (n && len) { - *(out++) = *(in++) ^ _ecount[n]; - --len; - n = (n + 1) % 16; - } - - while (len >= 16) { - size_t dataOutMoved; - CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); - ctr128_inc_aligned(_ivec); - for (n = 0; n < 16; n += sizeof(size_t)) - *(size_t *)(out + n) = - *(size_t *)(in + n) ^ *(size_t *)(_ecount + n); - len -= 16; - out += 16; - in += 16; - n = 0; - } - if (len) { - size_t dataOutMoved; - CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); - ctr128_inc_aligned(_ivec); - while (len--) { - out[n] = in[n] ^ _ecount[n]; - ++n; - } - } - _num = n; - return; - } while (0); - } - /* the rest would be commonly eliminated by x86* compiler */ - - while (l < len) { - if (n == 0) { - size_t dataOutMoved; - CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); - ctr128_inc(_ivec); - } - out[l] = in[l] ^ _ecount[n]; - ++l; - n = (n + 1) % 16; - } - - _num = n; -} - -@end - - -void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num) -{ - uint8_t *outData = (uint8_t *)malloc(length); - TGCallAesCtr *aesCtr = [[TGCallAesCtr alloc] initWithKey:key keyLength:32 iv:iv ecount:ecount num:*num]; - [aesCtr encryptIn:inOut out:outData len:length]; - memcpy(inOut, outData, length); - - memcpy(ecount, [aesCtr ecount], 16); - *num = [aesCtr num]; -} +// +// +//# define AES_MAXNR 14 +//# define AES_BLOCK_SIZE 16 +// +//#define N_WORDS (AES_BLOCK_SIZE / sizeof(unsigned long)) +//typedef struct { +// unsigned long data[N_WORDS]; +//} aes_block_t; +// +///* XXX: probably some better way to do this */ +//#if defined(__i386__) || defined(__x86_64__) +//# define UNALIGNED_MEMOPS_ARE_FAST 1 +//#else +//# define UNALIGNED_MEMOPS_ARE_FAST 0 +//#endif +// +//#if UNALIGNED_MEMOPS_ARE_FAST +//# define load_block(d, s) (d) = *(const aes_block_t *)(s) +//# define store_block(d, s) *(aes_block_t *)(d) = (s) +//#else +//# define load_block(d, s) memcpy((d).data, (s), AES_BLOCK_SIZE) +//# define store_block(d, s) memcpy((d), (s).data, AES_BLOCK_SIZE) +//#endif +// +//void TGCallAesIgeEncrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { +// size_t len; +// size_t n; +// uint8_t const *inB; +// uint8_t *outB; +// +// unsigned char aesIv[AES_BLOCK_SIZE]; +// memcpy(aesIv, iv, AES_BLOCK_SIZE); +// unsigned char ccIv[AES_BLOCK_SIZE]; +// memcpy(ccIv, (void *)((uint8_t *)iv + AES_BLOCK_SIZE), AES_BLOCK_SIZE); +// +// assert(((size_t)inBytes | (size_t)outBytes | (size_t)aesIv | (size_t)ccIv) % sizeof(long) == +// 0); +// +// void *tmpInBytes = malloc(length); +// len = length / AES_BLOCK_SIZE; +// inB = (uint8_t *)inBytes; +// outB = (uint8_t *)tmpInBytes; +// +// aes_block_t *inp = (aes_block_t *)inB; +// aes_block_t *outp = (aes_block_t *)outB; +// for (n = 0; n < N_WORDS; ++n) { +// outp->data[n] = inp->data[n]; +// } +// +// --len; +// inB += AES_BLOCK_SIZE; +// outB += AES_BLOCK_SIZE; +// uint8_t const *inBCC = (uint8_t *)inBytes; +// +// aes_block_t const *iv3p = (aes_block_t *)ccIv; +// +// if (len > 0) { +// while (len) { +// aes_block_t *inp = (aes_block_t *)inB; +// aes_block_t *outp = (aes_block_t *)outB; +// +// for (n = 0; n < N_WORDS; ++n) { +// outp->data[n] = inp->data[n] ^ iv3p->data[n]; +// } +// +// iv3p = (const aes_block_t *)inBCC; +// --len; +// inBCC += AES_BLOCK_SIZE; +// inB += AES_BLOCK_SIZE; +// outB += AES_BLOCK_SIZE; +// } +// } +// +// size_t realOutLength = 0; +// CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, 0, key, 32, aesIv, tmpInBytes, length, outBytes, length, &realOutLength); +// free(tmpInBytes); +// +// assert(result == kCCSuccess); +// +// len = length / AES_BLOCK_SIZE; +// +// aes_block_t const *ivp = (aes_block_t *)inB; +// aes_block_t *iv2p = (aes_block_t *)ccIv; +// +// inB = (uint8_t *)inBytes; +// outB = (uint8_t *)outBytes; +// +// while (len) { +// aes_block_t *inp = (aes_block_t *)inB; +// aes_block_t *outp = (aes_block_t *)outB; +// +// for (n = 0; n < N_WORDS; ++n) { +// outp->data[n] ^= iv2p->data[n]; +// } +// ivp = outp; +// iv2p = inp; +// --len; +// inB += AES_BLOCK_SIZE; +// outB += AES_BLOCK_SIZE; +// } +// +// memcpy(iv, ivp->data, AES_BLOCK_SIZE); +// memcpy((void *)((uint8_t *)iv + AES_BLOCK_SIZE), iv2p->data, AES_BLOCK_SIZE); +//} +// +//void TGCallAesIgeEncryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) +//{ +// uint8_t *outData = (uint8_t *)malloc(length); +// TGCallAesIgeEncrypt(inBytes, outData, length, key, iv); +// memcpy(outBytes, outData, length); +// free(outData); +//} +// +//void TGCallAesIgeDecrypt(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { +// unsigned char aesIv[AES_BLOCK_SIZE]; +// memcpy(aesIv, iv, AES_BLOCK_SIZE); +// unsigned char ccIv[AES_BLOCK_SIZE]; +// memcpy(ccIv, (void *)((uint8_t *)iv + AES_BLOCK_SIZE), AES_BLOCK_SIZE); +// +// assert(((size_t)inBytes | (size_t)outBytes | (size_t)aesIv | (size_t)ccIv) % sizeof(long) == +// 0); +// +// CCCryptorRef decryptor = NULL; +// CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, kCCOptionECBMode, key, 32, nil, &decryptor); +// if (decryptor != NULL) { +// size_t len; +// size_t n; +// +// len = length / AES_BLOCK_SIZE; +// +// aes_block_t *ivp = (aes_block_t *)(aesIv); +// aes_block_t *iv2p = (aes_block_t *)(ccIv); +// +// uint8_t *inB = (uint8_t *)inBytes; +// uint8_t *outB = (uint8_t *)outBytes; +// +// while (len) { +// aes_block_t tmp; +// aes_block_t *inp = (aes_block_t *)inB; +// aes_block_t *outp = (aes_block_t *)outB; +// +// for (n = 0; n < N_WORDS; ++n) +// tmp.data[n] = inp->data[n] ^ iv2p->data[n]; +// +// size_t dataOutMoved = 0; +// CCCryptorStatus result = CCCryptorUpdate(decryptor, &tmp, AES_BLOCK_SIZE, outB, AES_BLOCK_SIZE, &dataOutMoved); +// assert(result == kCCSuccess); +// assert(dataOutMoved == AES_BLOCK_SIZE); +// +// for (n = 0; n < N_WORDS; ++n) +// outp->data[n] ^= ivp->data[n]; +// +// ivp = inp; +// iv2p = outp; +// +// inB += AES_BLOCK_SIZE; +// outB += AES_BLOCK_SIZE; +// +// --len; +// } +// +// memcpy(iv, ivp->data, AES_BLOCK_SIZE); +// memcpy((void *)((uint8_t *)iv + AES_BLOCK_SIZE), iv2p->data, AES_BLOCK_SIZE); +// +// CCCryptorRelease(decryptor); +// } +//} +// +//void TGCallAesIgeDecryptInplace(uint8_t *inBytes, uint8_t *outBytes, size_t length, uint8_t *key, uint8_t *iv) { +// uint8_t *outData = (uint8_t *)malloc(length); +// TGCallAesIgeDecrypt(inBytes, outData, length, key, iv); +// memcpy(outBytes, outData, length); +// free(outData); +//} +// +//void TGCallSha1(uint8_t *msg, size_t length, uint8_t *output) +//{ +// CC_SHA1(msg, (CC_LONG)length, output); +//} +// +//void TGCallSha256(uint8_t *msg, size_t length, uint8_t *output) +//{ +// CC_SHA256(msg, (CC_LONG)length, output); +//} +// +//void TGCallRandomBytes(uint8_t *buffer, size_t length) +//{ +// arc4random_buf(buffer, length); +//} +// +// +//static void ctr128_inc(unsigned char *counter) +//{ +// uint32_t n = 16, c = 1; +// +// do { +// --n; +// c += counter[n]; +// counter[n] = (uint8_t)c; +// c >>= 8; +// } while (n); +//} +// +//static void ctr128_inc_aligned(unsigned char *counter) +//{ +// size_t *data, c, d, n; +// const union { +// long one; +// char little; +// } is_endian = { +// 1 +// }; +// +// if (is_endian.little || ((size_t)counter % sizeof(size_t)) != 0) { +// ctr128_inc(counter); +// return; +// } +// +// data = (size_t *)counter; +// c = 1; +// n = 16 / sizeof(size_t); +// do { +// --n; +// d = data[n] += c; +// /* did addition carry? */ +// c = ((d - c) ^ d) >> (sizeof(size_t) * 8 - 1); +// } while (n); +//} +// +//@interface TGCallAesCtr : NSObject { +// CCCryptorRef _cryptor; +// +// unsigned char _ivec[16]; +// unsigned int _num; +// unsigned char _ecount[16]; +//} +// +//@end +// +//@implementation TGCallAesCtr +// +//- (instancetype)initWithKey:(const void *)key keyLength:(int)keyLength iv:(const void *)iv ecount:(void *)ecount num:(uint32_t)num { +// self = [super init]; +// if (self != nil) { +// _num = num; +// memcpy(_ecount, ecount, 16); +// memcpy(_ivec, iv, 16); +// +// CCCryptorCreate(kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode, key, keyLength, nil, &_cryptor); +// } +// return self; +//} +// +//- (void)dealloc { +// if (_cryptor) { +// CCCryptorRelease(_cryptor); +// } +//} +// +//- (uint32_t)num { +// return _num; +//} +// +//- (void *)ecount { +// return _ecount; +//} +// +//- (void)encryptIn:(const unsigned char *)in out:(unsigned char *)out len:(size_t)len { +// unsigned int n; +// size_t l = 0; +// +// assert(in && out); +// assert(_num < 16); +// +// n = _num; +// +// if (16 % sizeof(size_t) == 0) { /* always true actually */ +// do { +// while (n && len) { +// *(out++) = *(in++) ^ _ecount[n]; +// --len; +// n = (n + 1) % 16; +// } +// +// while (len >= 16) { +// size_t dataOutMoved; +// CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); +// ctr128_inc_aligned(_ivec); +// for (n = 0; n < 16; n += sizeof(size_t)) +// *(size_t *)(out + n) = +// *(size_t *)(in + n) ^ *(size_t *)(_ecount + n); +// len -= 16; +// out += 16; +// in += 16; +// n = 0; +// } +// if (len) { +// size_t dataOutMoved; +// CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); +// ctr128_inc_aligned(_ivec); +// while (len--) { +// out[n] = in[n] ^ _ecount[n]; +// ++n; +// } +// } +// _num = n; +// return; +// } while (0); +// } +// /* the rest would be commonly eliminated by x86* compiler */ +// +// while (l < len) { +// if (n == 0) { +// size_t dataOutMoved; +// CCCryptorUpdate(_cryptor, _ivec, 16, _ecount, 16, &dataOutMoved); +// ctr128_inc(_ivec); +// } +// out[l] = in[l] ^ _ecount[n]; +// ++l; +// n = (n + 1) % 16; +// } +// +// _num = n; +//} +// +//@end +// +// +//void TGCallAesCtrEncrypt(uint8_t *inOut, size_t length, uint8_t *key, uint8_t *iv, uint8_t *ecount, uint32_t *num) +//{ +// uint8_t *outData = (uint8_t *)malloc(length); +// TGCallAesCtr *aesCtr = [[TGCallAesCtr alloc] initWithKey:key keyLength:32 iv:iv ecount:ecount num:*num]; +// [aesCtr encryptIn:inOut out:outData len:length]; +// memcpy(inOut, outData, length); +// +// memcpy(ecount, [aesCtr ecount], 16); +// *num = [aesCtr num]; +//} diff --git a/Telegram-Mac/TGModernGrowingTextView.h b/Telegram-Mac/TGModernGrowingTextView.h index a2f4d0d1a6..4240a8ce0f 100644 --- a/Telegram-Mac/TGModernGrowingTextView.h +++ b/Telegram-Mac/TGModernGrowingTextView.h @@ -9,7 +9,24 @@ #import #import "TGInputTextTag.h" -extern NSString * _Nonnull const TGMentionUidAttributeName; +extern NSString * _Nonnull const TGCustomLinkAttributeName; +@class TGModernGrowingTextView; + +@interface MarkdownUndoItem : NSObject +@property (nonatomic, strong) NSAttributedString *was; +@property (nonatomic, strong) NSAttributedString *be; +@property (nonatomic, assign) NSRange inRange; +-(id)initWithAttributedString:(NSAttributedString *)was be: (NSAttributedString *)be inRange:(NSRange)inRange; +@end + + +@interface SimpleUndoItem : NSObject +@property (nonatomic, strong) NSAttributedString *was; +@property (nonatomic, strong) NSAttributedString *be; +@property (nonatomic, assign) NSRange wasRange; +@property (nonatomic, assign) NSRange beRange; +-(id)initWithAttributedString:(NSAttributedString *)was be: (NSAttributedString *)be wasRange:(NSRange)wasRange beRange:(NSRange)beRange; +@end @protocol TGModernGrowingDelegate @@ -18,24 +35,36 @@ extern NSString * _Nonnull const TGMentionUidAttributeName; -(void) textViewTextDidChange:(NSString * __nonnull)string; -(void) textViewTextDidChangeSelectedRange:(NSRange)range; -(BOOL)textViewDidPaste:(NSPasteboard * __nonnull)pasteboard; --(int)maxCharactersLimit; +-(int)maxCharactersLimit:(TGModernGrowingTextView *)textView; --(NSSize)textViewSize; +-(NSSize)textViewSize:(TGModernGrowingTextView *)textView; -(BOOL)textViewIsTypingEnabled; @optional - (void) textViewNeedClose:(id __nonnull)textView; - (BOOL) canTransformInputText; +- (BOOL) supportContinuityCamera; +- (void)textViewDidReachedLimit:(id __nonnull)textView; +- (void)makeUrlOfRange: (NSRange)range; +- (BOOL)copyTextWithRTF:(NSAttributedString *)rtf; +- (NSArray *)textView:(NSTextView *)textView shouldUpdateTouchBarItemIdentifiers:(NSArray *)identifiers; +//func textView(_ textView: NSTextView, shouldUpdateTouchBarItemIdentifiers identifiers: [NSTouchBarItemIdentifier]) -> [NSTouchBarItemIdentifier] { @end +void setInputLocalizationFunc(NSString* _Nonnull (^ _Nonnull localizationF)(NSString * _Nonnull key)); +void setTextViewEnableTouchBar(BOOL enableTouchBar); - -@interface TGGrowingTextView : NSTextView +@interface TGGrowingTextView : NSTextView @property (nonatomic,weak) id __nullable weakd; +@property (nonatomic,weak) TGModernGrowingTextView * _Nullable weakTextView; + + @end -@interface TGModernGrowingTextView : NSView +@interface TGModernGrowingTextView : NSView + +-(instancetype)initWithFrame:(NSRect)frameRect unscrollable:(BOOL)unscrollable; @property (nonatomic,assign) BOOL animates; @@ -49,7 +78,6 @@ extern NSString * _Nonnull const TGMentionUidAttributeName; @property (nonatomic,strong) NSColor* _Nonnull textColor; @property (nonatomic,strong) NSColor* _Nonnull linkColor; @property (nonatomic,strong) NSFont* _Nonnull textFont; -@property (nonatomic,strong) NSString* _Nonnull defaultText; @property (nonatomic,strong,readonly) TGGrowingTextView* _Nonnull inputView; @@ -73,7 +101,7 @@ extern NSString * _Nonnull const TGMentionUidAttributeName; -(void)appendText:(id __nonnull)aString; -(void)insertText:(id __nonnull)aString replacementRange:(NSRange)replacementRange; -(void)addInputTextTag:(TGInputTextTag * __nonnull)tag range:(NSRange)range; - +-(void)scrollToCursor; -(void)replaceMention:(NSString * __nonnull)mention username:(bool)username userId:(int32_t)userId; -(void)paste:(id __nonnull)sender; @@ -87,7 +115,12 @@ extern NSString * _Nonnull const TGMentionUidAttributeName; -(void)codeWord; -(void)italicWord; -(void)boldWord; - +-(void)addLink:(NSString *_Nullable)link; +-(void)addLink:(NSString *_Nullable)link range: (NSRange)range; - (void)textDidChange:( NSNotification * _Nullable )notification; +- (void)addSimpleItem:(SimpleUndoItem *)item; + +-(void)setBackgroundColor:(NSColor * __nonnull)color; + @end diff --git a/Telegram-Mac/TGModernGrowingTextView.m b/Telegram-Mac/TGModernGrowingTextView.m index b42d501bec..d114bc71ae 100644 --- a/Telegram-Mac/TGModernGrowingTextView.m +++ b/Telegram-Mac/TGModernGrowingTextView.m @@ -8,40 +8,160 @@ #import "TGModernGrowingTextView.h" #import +#import "DateUtils.h" +#import "ObjcUtils.h" -@interface GrowingScrollView : NSScrollView +@interface TGTextFieldPlaceholder : NSTextField + + @end -@end +@interface TGModernGrowingTextView () + @property (nonatomic,strong) TGTextFieldPlaceholder *placeholder; + @end + +@implementation MarkdownUndoItem + -(id)initWithAttributedString:(NSAttributedString *)was be: (NSAttributedString *)be inRange:(NSRange)inRange { + if (self = [super init]) { + self.was = was; + self.be = be; + self.inRange = inRange; + } + return self; + } + @end + + +@implementation SimpleUndoItem +-(id)initWithAttributedString:(NSAttributedString *)was be: (NSAttributedString *)be wasRange:(NSRange)wasRange beRange:(NSRange)beRange { + if (self = [super init]) { + self.was = was; + self.be = be; + self.wasRange = wasRange; + self.beRange = beRange; + } + return self; +} + +-(void)setWas:(NSAttributedString *)was { + self->_was = was; +} + + @end + +static NSString* (^localizationFunc)(NSString *key); + +void setInputLocalizationFunc(NSString* (^localizationF)(NSString *key)) { + localizationFunc = localizationF; +} + +NSString * NSLocalized(NSString * key, NSString *comment) { + if (localizationFunc != nil) { + return localizationFunc(key); + } else { + return NSLocalizedString(key, comment); + } +} + +static BOOL textViewEnableTouchBar = true; + +void setTextViewEnableTouchBar(BOOL enableTouchBar) { + textViewEnableTouchBar = enableTouchBar; +} + +@interface GrowingScrollView : NSScrollView + + @end @implementation GrowingScrollView + + + @end -//-(void)scrollWheel:(NSEvent *)event { -// if ([self documentView].frame.size.height > self.frame.size.height) { -// [super scrollWheel:event]; -// } else { -// [[self superview].enclosingScrollView scrollWheel:event]; -// } -//} +@interface UnscrollableTextScrollView : NSScrollView + + @end -@end +@implementation UnscrollableTextScrollView + +-(void)scrollWheel:(NSEvent *)event { + [[self superview].enclosingScrollView scrollWheel:event]; +} + + @end @interface NSTextView () -(void)_shareServiceSelected:(id)sender; -@end + @end @interface TGModernGrowingTextView () + @property (nonatomic, assign) NSRange _selectedRange; + - (void)refreshAttributes; -@end + @end -NSString *const TGMentionUidAttributeName = @"TGMentionUidAttributeName"; +NSString *const TGCustomLinkAttributeName = @"TGCustomLinkAttributeName"; @interface TGGrowingTextView () -@end + @property (nonatomic, strong) NSUndoManager *undo; + @property (nonatomic, strong) NSMutableArray *markdownItems; + @property (nonatomic, strong) NSTrackingArea *trackingArea; + + + @end + + +@interface TGModernGrowingTextView () +-(void)textDidChange:(NSNotification *)notification; + + @end -@implementation TGGrowingTextView +@implementation TGGrowingTextView + +-(instancetype)initWithFrame:(NSRect)frameRect { + if(self = [super initWithFrame:frameRect]) { + self.markdownItems = [NSMutableArray array]; + + NSTrackingArea *trackingArea = [[NSTrackingArea alloc]initWithRect:self.bounds options:NSTrackingCursorUpdate | NSTrackingActiveInActiveApp owner:self userInfo:nil]; + [self addTrackingArea:trackingArea]; + +#ifdef __MAC_10_12_2 + // self.allowsCharacterPickerTouchBarItem = false; +#endif + } + return self; +} + + - (void)mouseMoved:(NSEvent *)event + { + } + +- (void)updateTrackingAreas { + [super updateTrackingAreas]; + [self removeTrackingArea:_trackingArea]; + _trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options: (NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate) owner:self userInfo:nil]; + [self addTrackingArea:_trackingArea]; +} + +-(id)validRequestorForSendType:(NSPasteboardType)sendType returnType:(NSPasteboardType)returnType { + if (([NSImage.imageTypes containsObject:returnType]) && [self.weakd respondsToSelector:@selector(supportContinuityCamera)] && [self.weakd supportContinuityCamera]) { + return self; + } else { + return nil; + } +} + +-(BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard { + if([pboard canReadItemWithDataConformingToTypes:NSImage.imageTypes]) { + [self.weakd textViewDidPaste:pboard]; + return YES; + } else { + return [super readSelectionFromPasteboard:pboard]; + } +} + -(NSPoint)textContainerOrigin { if(NSHeight(self.frame) <= 34) { @@ -53,26 +173,58 @@ -(NSPoint)textContainerOrigin { return [super textContainerOrigin]; } - + +-(void)drawRect:(NSRect)dirtyRect { + + + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] + graphicsPort]; + + BOOL isRetina = self.window.backingScaleFactor == 2.0; + + CGContextSetAllowsAntialiasing(context, true); + CGContextSetShouldSmoothFonts(context, !isRetina); + CGContextSetAllowsFontSmoothing(context,!isRetina); + + [super drawRect:dirtyRect]; + +} + + +-(void)setSelectedRange:(NSRange)selectedRange { + [super setSelectedRange:selectedRange]; +} +-(void)rightMouseDown:(NSEvent *)event { + [self.window makeFirstResponder:self]; + [super rightMouseDown:event]; +} + -(void)paste:(id)sender { if (![self.weakd textViewDidPaste:[NSPasteboard generalPasteboard]]) { [super paste:sender]; } } - + -(BOOL)becomeFirstResponder { return [super becomeFirstResponder]; } - + +-(BOOL)resignFirstResponder { + return [super resignFirstResponder]; +} + -(void)changeLayoutOrientation:(id)sender { } - + -(NSMenu *)menuForEvent:(NSEvent *)event { NSMenu *menu = [super menuForEvent:event]; NSMutableArray *removeItems = [[NSMutableArray alloc] init]; + __block BOOL addedTransformations = false; + + [menu.itemArray enumerateObjectsUsingBlock:^(NSMenuItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull s) { if (item.action == @selector(submenuAction:)) { @@ -81,12 +233,15 @@ -(NSMenu *)menuForEvent:(NSEvent *)event { [removeItems addObject:item]; *stop = YES; } else if (subItem.action == @selector(capitalizeWord:)) { + addedTransformations = true; if ([_weakd respondsToSelector:@selector(canTransformInputText)]) { if (self.selectedRange.length > 0) { if ([_weakd canTransformInputText]) { [self.transformItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [item.submenu insertItem:obj atIndex:0]; }]; + [item.submenu insertItem:[NSMenuItem separatorItem] atIndex: self.transformItems.count]; + // [item.submenu insertItem:[[NSMenuItem alloc] initWithTitle:@"Remove All Transformations" action:nil keyEquivalent:nil] atIndex:0]; } } else { [removeItems addObject:item]; @@ -97,55 +252,135 @@ -(NSMenu *)menuForEvent:(NSEvent *)event { } }]; + if (!addedTransformations) { + if ([_weakd respondsToSelector:@selector(canTransformInputText)]) { + if (self.selectedRange.length > 0) { + if ([_weakd canTransformInputText]) { + NSMenuItem *sep = [NSMenuItem separatorItem]; + [menu addItem: sep]; + + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"Text.View.Transformations", nil) action:nil keyEquivalent:@""]; + + item.submenu = [[NSMenu alloc] init]; + + [self.transformItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + [item.submenu insertItem:obj atIndex:0]; + }]; + [menu addItem:item]; + } + } + } + } + [removeItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [menu removeItem:obj]; }]; + [self.window makeFirstResponder:self]; return menu; } - + -(NSArray *)transformItems { - NSMenuItem *bold = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"TextView.Transform.Bold", nil) action:@selector(boldWord:) keyEquivalent:@"b"]; + NSMenuItem *bold = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"TextView.Transform.Bold", nil) action:@selector(boldWord:) keyEquivalent:@"b"]; [bold setKeyEquivalentModifierMask: NSCommandKeyMask]; - NSMenuItem *italic = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"TextView.Transform.Italic", nil) action:@selector(italicWord:) keyEquivalent:@"i"]; + NSMenuItem *italic = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"TextView.Transform.Italic", nil) action:@selector(italicWord:) keyEquivalent:@"i"]; [italic setKeyEquivalentModifierMask: NSCommandKeyMask]; - NSMenuItem *code = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"TextView.Transform.Code", nil) action:@selector(codeWord:) keyEquivalent:@"k"]; + NSMenuItem *code = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"TextView.Transform.Code", nil) action:@selector(codeWord:) keyEquivalent:@"k"]; [code setKeyEquivalentModifierMask: NSShiftKeyMask | NSCommandKeyMask]; - return @[code, italic, bold]; + + NSMenuItem *url = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"TextView.Transform.URL", nil) action:@selector(makeUrl:) keyEquivalent:@"u"]; + [url setKeyEquivalentModifierMask: NSCommandKeyMask]; + + NSMenuItem *removeAll = [[NSMenuItem alloc] initWithTitle:NSLocalized(@"TextView.Transform.RemoveAll", nil) action:@selector(removeAll:) keyEquivalent:@""]; + + + return @[removeAll, [NSMenuItem separatorItem], code, italic, bold, url]; } - - - --(void)boldWord:(id)sender { - [self changeFontMarkdown:[NSFont boldSystemFontOfSize:self.font.pointSize]]; - // [self.textStorage addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:self.font.pointSize] range:self.selectedRange]; - // [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; + + +-(NSTouchBar *)makeTouchBar { + return textViewEnableTouchBar ? [super makeTouchBar] : nil; } + +-(void)removeAll:(id)sender { + NSRange selectedRange = self.selectedRange; + NSMutableAttributedString *attr = [self.attributedString mutableCopy]; + [attr removeAttribute:TGCustomLinkAttributeName range:selectedRange]; + [attr addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:self.font.pointSize] range: selectedRange]; + + [self.textStorage setAttributedString:attr]; + [self setSelectedRange:NSMakeRange(selectedRange.location + selectedRange.length, 0)]; + // [attr enumerateAttributesInRange:selectedRange options:nil usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + // + // }]; +} + +-(void)boldWord:(id)sender { + if(self.selectedRange.length == 0) { + return; + } --(void)italicWord:(id)sender { - [self changeFontMarkdown:[[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:self.font.pointSize] toHaveTrait:NSFontItalicTrait]]; + NSRange effectiveRange; + NSFont *effectiveFont = [self.textStorage attribute:NSFontAttributeName atIndex:self.selectedRange.location effectiveRange:&effectiveRange]; + -// [self.textStorage addAttribute:NSFontAttributeName value:[[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:13] toHaveTrait:NSFontItalicTrait] range:self.selectedRange]; -// [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; + [self changeFontMarkdown:[NSFontManager.sharedFontManager convertFont:effectiveFont toHaveTrait:NSBoldFontMask] makeBold:YES makeItalic:NO]; + // [self.textStorage addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:self.font.pointSize] range:self.selectedRange]; + // [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; } + +-(void)makeUrl:(id)sender { + [self.weakd makeUrlOfRange:self.selectedRange]; +} + +-(void)addLink:(NSString *)link { + [self.textStorage addAttribute:NSLinkAttributeName value: link range:self.selectedRange]; +} + +-(void)addLink:(NSString *)link range: (NSRange)range { + [self.textStorage addAttribute:NSLinkAttributeName value: link range: range]; +} + +-(void)italicWord:(id)sender { + if(self.selectedRange.length == 0) { + return; + } --(void)codeWord:(id)sender { - [self changeFontMarkdown:[NSFont fontWithName:@"Menlo-Regular" size:self.font.pointSize]]; -// [self.textStorage addAttribute:NSFontAttributeName value:[NSFont fontWithName:@"Menlo-Regular" size:self.font.pointSize] range:self.selectedRange]; -// [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; + NSRange effectiveRange; + NSFont *effectiveFont = [self.textStorage attribute:NSFontAttributeName atIndex:self.selectedRange.location effectiveRange:&effectiveRange]; + + [self changeFontMarkdown:[[NSFontManager sharedFontManager] convertFont:effectiveFont toHaveTrait:NSFontItalicTrait] makeBold:NO makeItalic:YES]; + + // [self.textStorage addAttribute:NSFontAttributeName value:[[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:13] toHaveTrait:NSFontItalicTrait] range:self.selectedRange]; + // [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; + } + + +-(void)codeWord:(id)sender { + if(self.selectedRange.length == 0) { + return; + } --(void)changeFontMarkdown:(NSFont *)font { + [self changeFontMarkdown:[NSFont fontWithName:@"Menlo-Regular" size:self.font.pointSize] makeBold:NO makeItalic:NO]; + // [self.textStorage addAttribute:NSFontAttributeName value:[NSFont fontWithName:@"Menlo-Regular" size:self.font.pointSize] range:self.selectedRange]; + // [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; +} + +-(void)changeFontMarkdown:(NSFont *)font makeBold:(BOOL)makeBold makeItalic:(BOOL)makeItalic { if(self.selectedRange.length == 0) { return; } + + NSAttributedString *was = [self.attributedString attributedSubstringFromRange:self.selectedRange]; + NSRange effectiveRange; NSFont *effectiveFont = [self.textStorage attribute:NSFontAttributeName atIndex:self.selectedRange.location effectiveRange:&effectiveRange]; @@ -164,125 +399,200 @@ -(void)changeFontMarkdown:(NSFont *)font { dispatch_block_t block = ^{ + + NSFont *newFont = [[NSFontManager sharedFontManager] convertFont:font toNotHaveTrait:makeBold ? NSBoldFontMask : makeItalic ? NSItalicFontMask : 0]; + if (self.selectedRange.location >= effectiveRange.location && self.selectedRange.location + self.selectedRange.length <= effectiveRange.location + effectiveRange.length) { - [self.textStorage addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:13] range:self.selectedRange]; + [self.textStorage addAttribute:NSFontAttributeName value:newFont range:self.selectedRange]; } else if (self.selectedRange.location >= effectiveRange.location) { [self.textStorage addAttribute:NSFontAttributeName value:font range:self.selectedRange]; } else { - [self.textStorage addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:13] range:self.selectedRange]; + [self.textStorage addAttribute:NSFontAttributeName value:newFont range:self.selectedRange]; } }; + BOOL doNext = YES; + if (isBold) { - if (isEffectiveBold) { + if (isEffectiveBold && makeBold) { block(); + doNext = NO; } else { [self.textStorage addAttribute:NSFontAttributeName value:font range:self.selectedRange]; } - } else if (isItalic) { - if (isEffectiveItalic) { + } + if (isItalic) { + if (isEffectiveItalic && makeItalic) { block(); - } else { + } else if (doNext) { [self.textStorage addAttribute:NSFontAttributeName value:font range:self.selectedRange]; + doNext = NO; } - } else if (isMonospace) { + } + if (isMonospace) { if (isEffectiveMonospace) { block(); - } else { + } else if (doNext) { [self.textStorage addAttribute:NSFontAttributeName value:font range:self.selectedRange]; } } + NSAttributedString *be = [self.attributedString attributedSubstringFromRange:self.selectedRange]; + + + [_weakd textViewTextDidChangeSelectedRange:self.selectedRange]; - + + MarkdownUndoItem *item = [[MarkdownUndoItem alloc] initWithAttributedString:was be:be inRange:self.selectedRange]; + [self addItem:item]; + } - - - + + +- (void)addItem:(MarkdownUndoItem *)item { + [[self undoManager] registerUndoWithTarget:self selector:@selector(removeItem:) object:item]; + if (![[self undoManager] isUndoing]) { + [[self undoManager] setActionName:NSLocalizedString(@"actions.add-item", @"Add Item")]; + } + [[self textStorage] replaceCharactersInRange:item.inRange withAttributedString:item.be]; + [self.markdownItems addObject:item]; + [self.weakd textViewTextDidChangeSelectedRange:self.selectedRange]; +} + +- (void)removeItem:(MarkdownUndoItem *)item { + [[self undoManager] registerUndoWithTarget:self selector:@selector(addItem:) object:item]; + if (![[self undoManager] isUndoing]) { + [[self undoManager] setActionName:NSLocalizedString(@"actions.remove-item", @"Remove Item")]; + } + if ([self.markdownItems indexOfObject:item] != NSNotFound) { + [[self textStorage] replaceCharactersInRange:item.inRange withAttributedString:item.was]; + [self.markdownItems removeObject:item]; + [self.weakd textViewTextDidChangeSelectedRange:self.selectedRange]; + } +} + +- (void)addSimpleItem:(SimpleUndoItem *)item { + [[self undoManager] registerUndoWithTarget:self selector:@selector(removeSimpleItem:) object:item]; + if (![[self undoManager] isUndoing]) { + [[self undoManager] setActionName:NSLocalizedString(@"actions.add-item", @"Add Item")]; + } + [[self textStorage] setAttributedString:item.be]; + [self setSelectedRange:item.beRange]; + [self.weakd textViewTextDidChangeSelectedRange:self.selectedRange]; +} + +- (void)removeSimpleItem:(SimpleUndoItem *)item { + [[self undoManager] registerUndoWithTarget:self selector:@selector(addSimpleItem:) object:item]; + if (![[self undoManager] isUndoing]) { + [[self undoManager] setActionName:NSLocalizedString(@"actions.remove-item", @"Remove Item")]; + } + [[self textStorage] setAttributedString:item.was]; + [self setSelectedRange:item.wasRange]; + [self.weakd textViewTextDidChangeSelectedRange:item.wasRange]; + [self.weakTextView update:YES]; +} + + + -(BOOL)validateMenuItem:(NSMenuItem *)menuItem { if(menuItem.action == @selector(changeLayoutOrientation:)) { return NO; } + if(menuItem.action == @selector(copy:)) { + return self.selectedRange.length > 0; + } return [super validateMenuItem:menuItem]; } - - -- (void)setContinuousSpellCheckingEnabled:(BOOL)flag -{ - [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"ContinuousSpellCheckingEnabled%@",NSStringFromClass([self class])]]; - [super setContinuousSpellCheckingEnabled: flag]; + +-(void)copy:(id)sender { + if (self.selectedRange.length > 0) { + if ([self.weakd respondsToSelector:@selector(copyTextWithRTF:)]) { + if (![self.weakd copyTextWithRTF: [self.attributedString attributedSubstringFromRange:self.selectedRange]]) { + [super copy:sender]; + } + } else { + [super copy:sender]; + } + } } - + + +- (void)setContinuousSpellCheckingEnabled:(BOOL)flag + { + [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"ContinuousSpellCheckingEnabled%@",NSStringFromClass([self class])]]; + [super setContinuousSpellCheckingEnabled: flag]; + } + -(BOOL)isContinuousSpellCheckingEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"ContinuousSpellCheckingEnabled%@",NSStringFromClass([self class])]]; } - + -(void)setGrammarCheckingEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"GrammarCheckingEnabled%@",NSStringFromClass([self class])]]; [super setGrammarCheckingEnabled: flag]; } - + -(BOOL)isGrammarCheckingEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"GrammarCheckingEnabled%@",NSStringFromClass([self class])]]; } - - + + -(void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"AutomaticSpellingCorrectionEnabled%@",NSStringFromClass([self class])]]; [super setAutomaticSpellingCorrectionEnabled: flag]; } - + -(BOOL)isAutomaticSpellingCorrectionEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"AutomaticSpellingCorrectionEnabled%@",NSStringFromClass([self class])]]; } - - - + + + -(void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"AutomaticQuoteSubstitutionEnabled%@",NSStringFromClass([self class])]]; [super setAutomaticSpellingCorrectionEnabled: flag]; } - + -(BOOL)isAutomaticQuoteSubstitutionEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"AutomaticQuoteSubstitutionEnabled%@",NSStringFromClass([self class])]]; } - - + + -(void)setAutomaticLinkDetectionEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"AutomaticLinkDetectionEnabled%@",NSStringFromClass([self class])]]; [super setAutomaticSpellingCorrectionEnabled: flag]; } - + -(BOOL)isAutomaticLinkDetectionEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"AutomaticLinkDetectionEnabled%@",NSStringFromClass([self class])]]; } - - + + -(void)setAutomaticDataDetectionEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"AutomaticDataDetectionEnabled%@",NSStringFromClass([self class])]]; [super setAutomaticSpellingCorrectionEnabled: flag]; } - + -(BOOL)isAutomaticDataDetectionEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"AutomaticDataDetectionEnabled%@",NSStringFromClass([self class])]]; } - - - + + + -(void)setAutomaticDashSubstitutionEnabled:(BOOL)flag { [[NSUserDefaults standardUserDefaults] setBool: flag forKey:[NSString stringWithFormat:@"AutomaticDashSubstitutionEnabled%@",NSStringFromClass([self class])]]; [super setAutomaticSpellingCorrectionEnabled: flag]; } - + -(BOOL)isAutomaticDashSubstitutionEnabled { return [[NSUserDefaults standardUserDefaults] boolForKey:[NSString stringWithFormat:@"AutomaticDashSubstitutionEnabled%@",NSStringFromClass([self class])]]; } - - - + + + -(NSUInteger)numberOfLines { NSString *s = [self string]; @@ -294,15 +604,19 @@ -(NSUInteger)numberOfLines { } return numberOfLines; } - -BOOL isEnterEvent(NSEvent *theEvent) { - BOOL isEnter = (theEvent.keyCode == 0x24 || theEvent.keyCode == 0x4C); // VK_RETURN - return isEnter; + BOOL isEnterEvent(NSEvent *theEvent) { + BOOL isEnter = (theEvent.keyCode == 0x24 || theEvent.keyCode == 0x4C); // VK_RETURN + + return isEnter; + } + +-(void)insertNewline:(id)sender { + [super insertNewline:sender]; } - - - + + + - (void) keyDown:(NSEvent *)theEvent { if(_weakd.textViewIsTypingEnabled) { @@ -311,153 +625,194 @@ - (void) keyDown:(NSEvent *)theEvent { BOOL result = [_weakd textViewEnterPressed:theEvent]; - if(!result) { - [self insertNewline:self]; + if ((!result && (theEvent.modifierFlags & NSEventModifierFlagCommand)) || (!result && (theEvent.modifierFlags & NSEventModifierFlagShift))) { + [super insertNewline:self]; + return; } - return; - } else if(theEvent.keyCode == 53 && [_weakd respondsToSelector:@selector(textViewNeedClose:)]) { + + if (result) { + return; + } + } else if(theEvent.keyCode == 53 && [_weakd respondsToSelector:@selector(textViewNeedClose:)]) { [_weakd textViewNeedClose:self]; return; } - + if (!(theEvent.modifierFlags & NSEventModifierFlagCommand) || !isEnterEvent(theEvent)) { + [super keyDown:theEvent]; + } + } else if(_weakd == nil) { [super keyDown:theEvent]; } } - + -(void)setFrameSize:(NSSize)newSize { [super setFrameSize:newSize]; } - - - --(BOOL)resignFirstResponder { - return [super resignFirstResponder]; -} - + + + + -(void)setString:(NSString *)string { [super setString:string]; } - - + @end -@interface TGTextFieldPlaceholder : NSTextField -@end @implementation TGTextFieldPlaceholder - - -@end + +-(void)drawRect:(NSRect)dirtyRect { + + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] + graphicsPort]; + + BOOL isRetina = self.window.backingScaleFactor == 2.0; + + if (isRetina) { + CGContextSetAllowsAntialiasing(context, true); + CGContextSetShouldSmoothFonts(context, false); + CGContextSetAllowsFontSmoothing(context, false); + } + [super drawRect:dirtyRect]; + +} + +-(NSMenu *)menuForEvent:(NSEvent *)event { + return [self.superview menuForEvent:event]; +} + + @end @interface TGModernGrowingTextView () { int _last_height; } -@property (nonatomic,strong) TGGrowingTextView *textView; -@property (nonatomic,strong) GrowingScrollView *scrollView; -@property (nonatomic,strong) TGTextFieldPlaceholder *placeholder; -@property (nonatomic,assign) BOOL notify_next; -@property (nonatomic, strong) NSUndoManager *_undo; -@end + @property (nonatomic,strong) TGGrowingTextView *textView; + @property (nonatomic,strong) NSScrollView *scrollView; + @property (nonatomic,assign) BOOL notify_next; + @property (nonatomic, strong) NSUndoManager *_undo; + @end @implementation TGModernGrowingTextView - - - - + + -(instancetype)initWithFrame:(NSRect)frameRect { + if (self = [self initWithFrame: frameRect unscrollable: false]) { + + } + return self; +} + +-(instancetype)initWithFrame:(NSRect)frameRect unscrollable:(BOOL)unscrollable { if(self = [super initWithFrame:frameRect]) { _min_height = 34; _max_height = 200; _animates = YES; _cursorColor = [NSColor blackColor]; - + _textView = [[[self _textViewClass] alloc] initWithFrame:self.bounds]; [_textView setRichText:NO]; [_textView setImportsGraphics:NO]; - _textView.backgroundColor = [NSColor clearColor]; _textView.insertionPointColor = _cursorColor; - self.scrollView.backgroundColor = [NSColor clearColor]; [_textView setAllowsUndo:YES]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectionDidChanged:) name:NSTextViewDidChangeSelectionNotification object:_textView]; self._undo = [[NSUndoManager alloc] init]; + self.textView.undo = self._undo; self.autoresizesSubviews = YES; _textView.delegate = self; [_textView setDrawsBackground:YES]; - self.scrollView = [[GrowingScrollView alloc] initWithFrame:self.bounds]; + + if (unscrollable) { + self.scrollView = [[UnscrollableTextScrollView alloc] initWithFrame:self.bounds]; + } else { + self.scrollView = [[GrowingScrollView alloc] initWithFrame:self.bounds]; + } + + [[self.scrollView verticalScroller] setControlSize:NSSmallControlSize]; self.scrollView.documentView = _textView; - [self.scrollView setDrawsBackground:NO]; [self.scrollView setFrame:NSMakeRect(0, 0, NSWidth(self.frame), NSHeight(self.frame))]; [self addSubview:self.scrollView]; self.wantsLayer = _textView.wantsLayer = _scrollView.wantsLayer = YES; - + _placeholder = [[TGTextFieldPlaceholder alloc] init]; + _placeholder.layer.opacity = 0.7; _placeholder.wantsLayer = YES; [_placeholder setBordered:NO]; [_placeholder setDrawsBackground:NO]; [_placeholder setSelectable:NO]; [_placeholder setEditable:NO]; - [[_placeholder cell] setLineBreakMode:NSLineBreakByTruncatingTail]; [_placeholder setEnabled:NO]; + [_placeholder setLineBreakMode:NSLineBreakByTruncatingTail]; [self addSubview:_placeholder]; - + + _textView.weakTextView = self; + } return self; } - + -(NSUndoManager *)undoManagerForTextView:(NSTextView *)view { return self._undo; } - + -(void)setCursorColor:(NSColor *)cursorColor { _cursorColor = cursorColor; _textView.insertionPointColor = _cursorColor; } - + -(void)setTextColor:(NSColor *)textColor { _textColor = textColor; _textView.insertionPointColor = _textColor; + _textView.textColor = _textColor; [self textDidChange:nil]; } - + -(void)setTextFont:(NSFont *)textFont { _textFont = textFont; _textView.font = textFont; } - + + -(void)selectionDidChanged:(NSNotification *)notification { if (!_notify_next) { _notify_next = YES; return; } - [self.delegate textViewTextDidChangeSelectedRange:self.textView.selectedRange]; + + + if ((self._selectedRange.location != self.textView.selectedRange.location) || (self._selectedRange.length != self.textView.selectedRange.length)) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate textViewTextDidChangeSelectedRange:self.textView.selectedRange]; + }); + self._selectedRange = self.textView.selectedRange; + } NSRect newRect = [_textView.layoutManager usedRectForTextContainer:_textView.textContainer]; NSSize size = newRect.size; size.width = NSWidth(self.frame); NSSize newSize = NSMakeSize(size.width, size.height); - newSize.height+= 8; + newSize.height+= 2; newSize.height = MIN(MAX(newSize.height,_min_height),_max_height); - [self updatePlaceholder:false newSize:newSize]; + [self updatePlaceholder:self.animates newSize:newSize]; } - + -(void)mouseDown:(NSEvent *)theEvent { [super mouseDown:theEvent]; if(self.window.firstResponder != _textView) { @@ -465,79 +820,60 @@ -(void)mouseDown:(NSEvent *)theEvent { } [self update:NO]; } - + -(BOOL)becomeFirstResponder { - // if(self.window.firstResponder != _textView) { - [self.window makeFirstResponder:_textView]; - // } + // if(self.window.firstResponder != _textView) { + [self.window makeFirstResponder:_textView]; + // } return YES; } - + -(BOOL)resignFirstResponder { return [_textView resignFirstResponder]; } - + -(int)height { return _last_height; } - + -(void)setDelegate:(id)delegate { _delegate = _textView.weakd = delegate; } - - + + -(void)update:(BOOL)notify { [self textDidChange:[NSNotification notificationWithName:NSTextDidChangeNotification object:notify ? _textView : nil]]; - [__undo removeAllActionsWithTarget:_textView]; - [__undo removeAllActions]; - } - - + + - (void)drawRect:(NSRect)dirtyRect { [super drawRect:dirtyRect]; } --(BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector { - if ((commandSelector == @selector(deleteBackward:) || commandSelector == @selector(deleteForward:)) && _defaultText.length > 0) { - if ([textView.string isEqualToString:_defaultText]) { - return true; - } - } - return false; -} - --(BOOL)textView:(NSTextView *)textView shouldChangeTextInRanges:(NSArray *)affectedRanges replacementStrings:(NSArray *)replacementStrings { - if (_defaultText.length > 0) { - __block BOOL cancel = true; - [affectedRanges enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - NSRange range = obj.rangeValue; - if (range.location < _defaultText.length) { - cancel = false; - *stop = YES; - } - }]; - if (cancel) { - [self setSelectedRange:NSMakeRange(textView.string.length, 0)]; - } - return cancel; - } - return true; -} - - - - -- (void)textDidChange:(NSNotification *)notification { +-(NSArray *)textView:(NSTextView *)textView shouldUpdateTouchBarItemIdentifiers:(NSArray *)identifiers { + if ([self.delegate respondsToSelector:@selector(textView:shouldUpdateTouchBarItemIdentifiers:)]) { + return [self.delegate textView: textView shouldUpdateTouchBarItemIdentifiers: identifiers]; + } + return identifiers; +} - int limit = self.delegate == nil ? INT32_MAX : [self.delegate maxCharactersLimit]; +- (void)textDidChange:(NSNotification *)notification { + int limit = self.delegate == nil ? INT32_MAX : [self.delegate maxCharactersLimit: self]; - if (self.string != nil && self.string.length > 0 && self.string.length - _defaultText.length > limit) { - NSString *sub = [self.string substringWithRange:NSMakeRange(_defaultText.length, limit)]; - [self setString:sub animated: notification != nil]; + if (self.string != nil && self.string.length > 0 && self.string.length > limit) { + + NSAttributedString *string = [self.attributedString attributedSubstringFromRange:NSMakeRange(0, MIN(limit, self.attributedString.string.length))]; + + NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString: string]; + NSRange selectedRange = _textView.selectedRange; + [_textView.textStorage setAttributedString:attr]; + [self update:notification != nil]; + [self setSelectedRange:NSMakeRange(MIN(selectedRange.location, string.length), 0)]; + if ([self.delegate respondsToSelector:@selector(textViewDidReachedLimit:)]) + [self.delegate textViewDidReachedLimit: self]; return; } @@ -556,6 +892,15 @@ - (void)textDidChange:(NSNotification *)notification { } } + if(notification.object) { + NSString *text = self.string; + [self.delegate textViewTextDidChange:text]; + if (![text isEqualToString:self.string]) { + return; + } + + } + self.scrollView.verticalScrollElasticity = NSHeight(_scrollView.contentView.documentRect) <= NSHeight(_scrollView.frame) ? NSScrollElasticityNone : NSScrollElasticityAllowed; [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer]; @@ -564,16 +909,16 @@ - (void)textDidChange:(NSNotification *)notification { NSSize size = newRect.size; size.width = NSWidth(self.frame); - + NSSize newSize = NSMakeSize(size.width, size.height); - - newSize.height+= 8; + + newSize.height+= 2; newSize.height = MIN(MAX(newSize.height,_min_height),_max_height); - BOOL animated = self.animates; + BOOL animated = self.animates && ![self.window inLiveResize]; if(_last_height != newSize.height) { @@ -588,7 +933,7 @@ - (void)textDidChange:(NSNotification *)notification { [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer]; - newSize.width = [_delegate textViewSize].width; + newSize.width = [_delegate textViewSize: self].width; NSSize layoutSize = NSMakeSize(roundf(newSize.width), roundf(newSize.height)); @@ -638,23 +983,24 @@ - (void)textDidChange:(NSNotification *)notification { [self setFrame:NSMakeRect(NSMinX(self.frame), NSMinY(self.frame), layoutSize.width, layoutSize.height)]; [_scrollView setFrameSize:layoutSize]; - - future(); [CATransaction commit]; - + } else { [self setFrameSize:layoutSize]; future(); } + } else { + int bp = 0; + bp += 1; } - // if(self._needShowPlaceholder) { + // if(self._needShowPlaceholder) { [self updatePlaceholder: animated newSize: newSize]; @@ -662,31 +1008,61 @@ - (void)textDidChange:(NSNotification *)notification { [self setNeedsDisplay:YES]; if (_textView.selectedRange.location != NSNotFound) { - [_textView setSelectedRange:_textView.selectedRange]; + [self setSelectedRange:_textView.selectedRange]; } [self setNeedsDisplay:YES]; - if(notification.object) { - NSString *text = self.string; - if (_defaultText.length > 0) { - NSRange range = [text rangeOfString:_defaultText]; - if (range.location != NSNotFound) { - text = [text substringFromIndex:range.location + range.length]; - } else if ([_defaultText containsString:text]) { - text = @""; - } + + [_textView setNeedsDisplay:YES]; + + [self refreshAttributes]; + +} + +- (NSRect) highlightRectForRange:(NSRange)aRange + { + NSRange r = aRange; + NSRange startLineRange = [[self string] lineRangeForRange:NSMakeRange(r.location, 0)]; + NSInteger er = NSMaxRange(r)-1; + NSString *text = [self string]; + + if (er >= [text length]) { + return NSZeroRect; + } + if (er < r.location) { + er = r.location; } - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate textViewTextDidChange:text]; - }); + + NSRange gr = [[self.textView layoutManager] glyphRangeForCharacterRange:aRange + actualCharacterRange:NULL]; + NSRect br = [[self.textView layoutManager] boundingRectForGlyphRange:gr inTextContainer:[self.textView textContainer]]; + NSRect b = [self bounds]; + CGFloat h = br.size.height; + CGFloat w = b.size.width; + CGFloat y = br.origin.y; + NSPoint containerOrigin = [self.textView textContainerOrigin]; + NSRect aRect = NSMakeRect(0, y, w, h); + // Convert from view coordinates to container coordinates + aRect = NSOffsetRect(aRect, containerOrigin.x, containerOrigin.y); + return aRect; } - [self refreshAttributes]; +-(void)scrollToCursor { + [_textView.layoutManager ensureLayoutForTextContainer:_textView.textContainer]; + + NSRect lineRect = [self highlightRectForRange:self.selectedRange]; + CGFloat maxY = [self.scrollView.contentView documentRect].size.height; + maxY = MIN(MAX(lineRect.origin.y, 0), maxY - self.scrollView.frame.size.height); + + NSPoint point = NSMakePoint(lineRect.origin.x, maxY); + if (!NSPointInRect(lineRect.origin, _scrollView.documentVisibleRect) && _scrollView.documentVisibleRect.size.width > 0) { + [self.scrollView.contentView scrollToPoint:point]; + } } - + -(void)updatePlaceholder:(BOOL)animated newSize:(NSSize)newSize { if(_placeholderAttributedString) { @@ -702,7 +1078,7 @@ -(void)updatePlaceholder:(BOOL)animated newSize:(NSSize)newSize { if(presentLayer && [_placeholder.layer animationForKey:@"opacity"]) { presentOpacity = [[presentLayer valueForKeyPath:@"opacity"] floatValue]; } - [_placeholder setHidden:NO]; + [self addSubview:_placeholder]; CABasicAnimation *oAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; oAnim.fromValue = @(presentOpacity); @@ -717,26 +1093,32 @@ -(void)updatePlaceholder:(BOOL)animated newSize:(NSSize)newSize { [_placeholder.layer addAnimation:oAnim forKey:@"opacity"]; + NSPoint toPoint = self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0))) : NSMakePoint(self._endXPlaceholder, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0))); + + CABasicAnimation *pAnim = [CABasicAnimation animationWithKeyPath:@"position"]; pAnim.removedOnCompletion = YES; pAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; pAnim.duration = 0.2; - pAnim.fromValue = [NSValue valueWithPoint:NSMakePoint(presentX, NSMinY(_placeholder.frame))]; - pAnim.toValue = [NSValue valueWithPoint:self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, roundf((newSize.height - NSHeight(_placeholder.frame))/2.0)) : NSMakePoint(self._endXPlaceholder, NSMinY(_placeholder.frame))]; - + pAnim.fromValue = [NSValue valueWithPoint:NSMakePoint(presentX, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0)))]; + pAnim.toValue = [NSValue valueWithPoint:toPoint]; + pAnim.delegate = self; [_placeholder.layer removeAnimationForKey:@"position"]; [_placeholder.layer addAnimation:pAnim forKey:@"position"]; - - - } else { - [_placeholder setHidden:!self._needShowPlaceholder]; + if (_placeholder.layer.animationKeys.count == 0) { + if (self._needShowPlaceholder) { + [self addSubview:_placeholder]; + } else { + [_placeholder removeFromSuperview]; + } + } } - [_placeholder setFrameOrigin:self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, roundf((newSize.height - NSHeight(_placeholder.frame))/2.0)) : NSMakePoint(NSMinX(_placeholder.frame) + 30, roundf((newSize.height - NSHeight(_placeholder.frame))/2.0))]; + [_placeholder setFrameOrigin:self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0))) : NSMakePoint(NSMinX(_placeholder.frame) + 30, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0)))]; _placeholder.layer.opacity = self._needShowPlaceholder ? 1.0 : 0.0; @@ -744,16 +1126,32 @@ -(void)updatePlaceholder:(BOOL)animated newSize:(NSSize)newSize { [self needsDisplay]; } } - + -(TGGrowingTextView *)inputView { return _textView; } - + +-(void)setMax_height:(int)max_height { + self->_max_height = max_height; + [_scrollView setFrame:NSMakeRect(0, 0, NSWidth(_scrollView.frame), MIN(NSHeight(_scrollView.frame), (CGFloat)max_height))]; +} + -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { - [_placeholder setHidden:!self._needShowPlaceholder]; + if (self._needShowPlaceholder) { + [self addSubview:_placeholder]; + } else { + [_placeholder removeFromSuperview]; + } } - - + +-(void)addSubview:(NSView *)view { + [super addSubview:view]; +} + +-(void)setLinkColor:(NSColor *)linkColor { + _linkColor = linkColor; +} + -(void)setFrameSize:(NSSize)newSize { [super setFrameSize:newSize]; [_scrollView setFrame:NSMakeRect(0, 0, newSize.width, newSize.height)]; @@ -761,44 +1159,47 @@ -(void)setFrameSize:(NSSize)newSize { [_placeholder sizeToFit]; - [_placeholder setFrameSize:NSMakeSize(MIN(NSWidth(_textView.frame) - self._startXPlaceholder - 10,NSWidth(_placeholder.frame)), NSHeight(_placeholder.frame))]; + // [_placeholder setFrameSize:NSMakeSize(MIN(NSWidth(_textView.frame) - self._startXPlaceholder - 10,NSWidth(_placeholder.frame)), NSHeight(_placeholder.frame))]; + [_placeholder setFrameOrigin:self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0))) : NSMakePoint(NSMinX(_placeholder.frame) + 30, fabsf(roundf((newSize.height - NSHeight(_placeholder.frame))/2.0)))]; } - + -(BOOL)_needShowPlaceholder { return self.string.length == 0 && _placeholderAttributedString && !_textView.hasMarkedText; } - + -(void)setPlaceholderAttributedString:(NSAttributedString *)placeholderAttributedString update:(BOOL)update { if([_placeholderAttributedString isEqualToAttributedString:placeholderAttributedString]) - return; + return; - _placeholderAttributedString = placeholderAttributedString; - [_placeholder setAttributedStringValue:_placeholderAttributedString]; - [_placeholder sizeToFit]; + [_placeholder setAttributedStringValue:placeholderAttributedString]; - [_placeholder setFrameSize:NSMakeSize(MIN(NSWidth(_textView.frame) - self._startXPlaceholder - 10,NSWidth(_placeholder.frame)), NSHeight(_placeholder.frame))]; + _placeholderAttributedString = placeholderAttributedString; + [_placeholder setAttributedStringValue:placeholderAttributedString]; + [_placeholder sizeToFit]; + // [_placeholder setFrameSize:NSMakeSize(MIN(NSWidth(_textView.frame) - self._startXPlaceholder - 10,NSWidth(_placeholder.frame)), NSHeight(_placeholder.frame))]; + [_placeholder setFrameOrigin:self._needShowPlaceholder ? NSMakePoint(self._startXPlaceholder, fabsf(roundf((self.frame.size.height - NSHeight(_placeholder.frame))/2.0))) : NSMakePoint(NSMinX(_placeholder.frame) + 30, fabsf(roundf((self.frame.size.height - NSHeight(_placeholder.frame))/2.0)))]; BOOL animates = _animates; _animates = NO; if (self.string.length == 0) { - [self update:update]; + [self update:update]; } _animates = animates; - + } - + -(void)setPlaceholderAttributedString:(NSAttributedString *)placeholderAttributedString { [self setPlaceholderAttributedString:placeholderAttributedString update:YES]; } - + -(NSParagraphStyle *)defaultParagraphStyle { static NSMutableParagraphStyle *para; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - para = [[NSMutableParagraphStyle alloc] init]; + para = [[NSMutableParagraphStyle alloc] init]; }); [para setLineSpacing:0]; @@ -807,8 +1208,8 @@ -(NSParagraphStyle *)defaultParagraphStyle { return para; } - - + + - (void)refreshAttributes { @try { @@ -817,12 +1218,14 @@ - (void)refreshAttributes { return; } + + [_textView.textStorage addAttribute:NSForegroundColorAttributeName value:self.textColor range:NSMakeRange(0, string.length)]; __block NSMutableArray *inputTextTags = [[NSMutableArray alloc] init]; - [string enumerateAttribute:TGMentionUidAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) { + [string enumerateAttribute:TGCustomLinkAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) { if ([value isKindOfClass:[TGInputTextTag class]]) { [inputTextTags addObject:[[TGInputTextTagAndRange alloc] initWithTag:value range:range]]; } @@ -840,7 +1243,7 @@ - (void)refreshAttributes { TGInputTextTagAndRange *tagAndRange = inputTextTags[i]; if ([removeTags containsObject:@(tagAndRange.tag.uniqueId)]) { [inputTextTags removeObjectAtIndex:i]; - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; i--; } else { @@ -855,9 +1258,9 @@ - (void)refreshAttributes { if (j != (NSInteger)tagAndRange.range.location) { NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j); - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tagAndRange.tag range:updatedRange]; inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; @@ -876,9 +1279,9 @@ - (void)refreshAttributes { if (j < ((NSInteger)tagAndRange.range.location)) { NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j); - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tagAndRange.tag range:updatedRange]; inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; @@ -906,10 +1309,10 @@ - (void)refreshAttributes { [removeTags addObject:@(tagAndRange.tag.uniqueId)]; [_textView.textStorage addAttribute:tagAndRange.tag.attribute.name value:tagAndRange.tag.attribute.value range:tagAndRange.range]; } else { - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location); - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tagAndRange.tag range:updatedRange]; inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; i--; @@ -929,23 +1332,23 @@ - (void)refreshAttributes { } if (j == candidateEnd) { - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:nextTagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:nextTagAndRange.range]; NSRange updatedRange = NSMakeRange(tagAndRange.range.location, nextTagAndRange.range.location + nextTagAndRange.range.length - tagAndRange.range.location); - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tagAndRange.tag range:updatedRange]; inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; [inputTextTags removeObjectAtIndex:i + 1]; i--; } else if (j != candidateStart) { - [_textView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range]; + [_textView.textStorage removeAttribute:TGCustomLinkAttributeName range:tagAndRange.range]; NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location); - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tagAndRange.tag range:updatedRange]; inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange]; @@ -959,173 +1362,244 @@ - (void)refreshAttributes { } } } - - + + } @catch (NSException *exception) { } + + [self setSelectedRange:self.selectedRange]; } - + -(void)boldWord { [self.textView boldWord:nil]; } - + -(void)italicWord { [self.textView italicWord:nil]; } - + -(void)codeWord { [self.textView codeWord:nil]; } - - - + + + + + -(NSString *)string { if (_textView.string == nil) { return @""; } return [_textView.string copy]; } - + -(NSAttributedString *)attributedString { return _textView.attributedString; } - + -(void)setAttributedString:(NSAttributedString *)attributedString animated:(BOOL)animated { + int limit = self.delegate == nil ? INT32_MAX : [self.delegate maxCharactersLimit: self]; - NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; + NSAttributedString *string = [attributedString attributedSubstringFromRange:NSMakeRange(0, MIN(limit, attributedString.string.length))]; - [attributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(NSFont *value, NSRange range, BOOL * _Nonnull stop) { - + NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString: string]; + + [string enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(NSFont *value, NSRange range, BOOL * _Nonnull stop) { [attr addAttribute:NSFontAttributeName value:[[NSFontManager sharedFontManager] convertFont:value toSize:_textFont.pointSize] range:range]; - }]; + NSRange selectedRange = _textView.selectedRange; + if (selectedRange.location == self.textView.string.length) { + selectedRange = NSMakeRange(attr.length, 0); + } [_textView.textStorage setAttributedString:attr]; BOOL o = self.animates; self.animates = animated; [self update:animated]; self.animates = o; -} + + [self setSelectedRange:NSMakeRange(MIN(selectedRange.location, string.length), 0)]; --(NSString *)textWithDefault:(NSString *)string { - NSString *text = _defaultText.length > 0 ? [_defaultText stringByAppendingString:string] : string; - - return text; } - + + -(void)setString:(NSString *)string { - - if (![string isEqualToString:[self textWithDefault:self.string]]) { - [self setString:string animated:YES]; + + if (![string isEqualToString:self.string]) { + [self setString:string animated:self.animates]; } } - + -(void)setString:(NSString *)string animated:(BOOL)animated { - [_textView setString:[self textWithDefault:string]]; BOOL o = self.animates; self.animates = animated; + [_textView setString:string]; [self update:animated]; self.animates = o; } -(NSRange)selectedRange { return _textView.selectedRange; } - + -(void)insertText:(id)aString replacementRange:(NSRange)replacementRange { [_textView insertText:aString replacementRange:replacementRange]; } - + -(void)appendText:(id)aString { [_textView insertText:aString replacementRange:self.selectedRange]; } - + +- (void)addSimpleItem:(SimpleUndoItem *)item { + [self.inputView addSimpleItem:item]; + [self update: YES]; +} + -(void)addInputTextTag:(TGInputTextTag *)tag range:(NSRange)range { - [_textView.textStorage addAttribute:TGMentionUidAttributeName value:tag range:range]; + NSAttributedString *was = [self.textView.textStorage attributedSubstringFromRange:range]; + [_textView.textStorage addAttribute:TGCustomLinkAttributeName value:tag range:range]; + MarkdownUndoItem *item = [[MarkdownUndoItem alloc] initWithAttributedString:was be:[self.textView.textStorage attributedSubstringFromRange:range] inRange:range]; + [self.textView addItem:item]; } - - -- (void)replaceMention:(NSString *)mention username:(bool)username userId:(int32_t)userId -{ - NSString *replacementText = [mention stringByAppendingString:@" "]; - NSMutableAttributedString *text = _textView.attributedString == nil ? [[NSMutableAttributedString alloc] init] : [[NSMutableAttributedString alloc] initWithAttributedString:_textView.attributedString]; + static int64_t nextId = 0; - NSRange selRange = _textView.selectedRange; - NSUInteger selStartPos = selRange.location; +-(void)addLink:(NSString *)link { + if (self.selectedRange.length == 0) + return; + if (link == nil) { + NSMutableAttributedString *copy = [self.attributedString mutableCopy]; + [copy removeAttribute:TGCustomLinkAttributeName range: self.selectedRange]; + [self setAttributedString:copy animated:false]; + } else { + id tag = [[TGInputTextTag alloc] initWithUniqueId:++nextId attachment:link attribute:[[TGInputTextAttribute alloc] initWithName:NSForegroundColorAttributeName value:_linkColor]]; + [self addInputTextTag:tag range:self.selectedRange]; + [self update:YES]; + } + +} + +-(void)addLink:(NSString *)link range: (NSRange)range { + if (range.length == 0) + return; - NSInteger idx = selStartPos; - idx--; + if (link == nil) { + NSMutableAttributedString *copy = [self.attributedString mutableCopy]; + [copy removeAttribute:TGCustomLinkAttributeName range: range]; + [self setAttributedString:copy animated:false]; + } else { + id tag = [[TGInputTextTag alloc] initWithUniqueId:++nextId attachment:link attribute:[[TGInputTextAttribute alloc] initWithName:NSForegroundColorAttributeName value:_linkColor]]; + [self addInputTextTag:tag range:range]; + [self update:YES]; + } +} - NSRange candidateMentionRange = NSMakeRange(NSNotFound, 0); - if (idx >= 0 && idx < (int)text.length) +- (void)replaceMention:(NSString *)mention username:(bool)username userId:(int32_t)userId { - for (NSInteger i = idx; i >= 0; i--) + NSString *replacementText = [mention stringByAppendingString:@" "]; + + NSMutableAttributedString *text = _textView.attributedString == nil ? [[NSMutableAttributedString alloc] init] : [[NSMutableAttributedString alloc] initWithAttributedString:_textView.attributedString]; + + NSRange selRange = _textView.selectedRange; + NSUInteger selStartPos = selRange.location; + + NSInteger idx = selStartPos; + idx--; + + NSRange candidateMentionRange = NSMakeRange(NSNotFound, 0); + + if (idx >= 0 && idx < (int)text.length) { - unichar c = [text.string characterAtIndex:i]; - if (c == '@') + for (NSInteger i = idx; i >= 0; i--) { - if (i == idx) + unichar c = [text.string characterAtIndex:i]; + if (c == '@') + { + if (i == idx) candidateMentionRange = NSMakeRange(i + 1, selRange.length); - else + else candidateMentionRange = NSMakeRange(i + 1, idx - i); + break; + } + + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) break; } - - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) - break; } - } - - if (candidateMentionRange.location != NSNotFound) - { - if (!username) { - candidateMentionRange.location -= 1; - candidateMentionRange.length += 1; - - [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; + + if (candidateMentionRange.location != NSNotFound) + { + if (!username) { + candidateMentionRange.location -= 1; + candidateMentionRange.length += 1; + + [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; + + nextId++; + [text addAttributes:@{TGCustomLinkAttributeName: [[TGInputTextTag alloc] initWithUniqueId:nextId attachment:@(userId) attribute:[[TGInputTextAttribute alloc] initWithName:NSForegroundColorAttributeName value:_linkColor]]} range:NSMakeRange(candidateMentionRange.location, replacementText.length - 1)]; + } else { + [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; + } - static int64_t nextId = 0; - nextId++; - [text addAttributes:@{TGMentionUidAttributeName: [[TGInputTextTag alloc] initWithUniqueId:nextId attachment:@(userId) attribute:[[TGInputTextAttribute alloc] initWithName:NSForegroundColorAttributeName value:_linkColor]]} range:NSMakeRange(candidateMentionRange.location, replacementText.length - 1)]; - } else { - [text replaceCharactersInRange:candidateMentionRange withString:replacementText]; + [_textView.textStorage setAttributedString:text]; } - [_textView.textStorage setAttributedString:text]; + [self update:YES]; } - [self update:YES]; -} - -(void)paste:(id)sender { [_textView paste:sender]; } - + +-(void)rightMouseDown:(NSEvent *)event { + [super rightMouseDown:event]; +} + +-(NSMenu *)menuForEvent:(NSEvent *)event { + return [self.textView menuForEvent:event]; +} + +-(id)validRequestorForSendType:(NSPasteboardType)sendType returnType:(NSPasteboardType)returnType { + return [self.textView validRequestorForSendType:sendType returnType:returnType]; + +} + +-(BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard { + [self.delegate textViewDidPaste:pboard]; + return YES; +} + -(void)setSelectedRange:(NSRange)range { _notify_next = NO; if(range.location != NSNotFound) - [_textView setSelectedRange:range]; + [_textView setSelectedRange:range]; } - + -(Class)_textViewClass { return [TGGrowingTextView class]; } - + -(void)dealloc { [__undo removeAllActionsWithTarget:_textView]; [__undo removeAllActions]; } - - + + + -(int)_startXPlaceholder { return NSMinX(_scrollView.frame) + 4; } - + -(int)_endXPlaceholder { return self._startXPlaceholder + 30; } +-(void)setBackgroundColor:(NSColor * __nonnull)color { + self.scrollView.backgroundColor = color; + self.textView.backgroundColor = color; + _placeholder.backgroundColor = [NSColor redColor]; +} + @end diff --git a/Telegram-Mac/TabBadgeItem.swift b/Telegram-Mac/TabBadgeItem.swift index e52f0b52d5..9c6a325c63 100644 --- a/Telegram-Mac/TabBadgeItem.swift +++ b/Telegram-Mac/TabBadgeItem.swift @@ -8,16 +8,135 @@ import Cocoa import TGUIKit -import PostboxMac -import SwiftSignalKitMac -import TelegramCoreMac +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + +private final class AvatarTabContainer : View { + private let avatar = AvatarControl(font: .avatar(12)) + private var selected: Bool = false + private let circle: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + avatar.setFrameSize(frameRect.size) + avatar.userInteractionEnabled = false + circle.setFrameSize(frameRect.size) + circle.layer?.cornerRadius = frameRect.height / 2 + circle.layer?.borderWidth = 1.33 + circle.layer?.borderColor = theme.colors.accentIcon.cgColor + addSubview(circle) + addSubview(avatar) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + func setPeer(account: Account, peer: Peer?) { + avatar.setPeer(account: account, peer: peer) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + circle.layer?.borderColor = theme.colors.accentIcon.cgColor + } + + + override func layout() { + super.layout() + avatar.center() + } + + func setSelected(_ selected: Bool, animated: Bool) { + self.selected = selected + + circle.change(opacity: selected ? 1 : 0, animated: animated, duration: 0.4, timingFunction: .spring) + + avatar.setFrameSize(frame.size) + + if animated { + let from: CGFloat = selected ? 1 : 24 / frame.height + let to: CGFloat = selected ? 24 / frame.height : 1 + avatar.layer?.animateScaleSpring(from: from, to: to, duration: 0.3, removeOnCompletion: false, bounce: false, completion: { completed in + + }) + if selected { + circle.layer?.animateScaleSpring(from: 0.5, to: 1.0, duration: 0.3, bounce: false) + } else { + circle.layer?.animateScaleSpring(from: 1.0, to: 0.5, duration: 0.3, removeOnCompletion: false, bounce: false) + } + } else { + if selected { + avatar.setFrameSize(NSMakeSize(24, 24)) + } else { + avatar.setFrameSize(frame.size) + } + } + + needsLayout = true + } + +} class TabBadgeItem: TabItem { - private let account:Account - init(_ account:Account, controller:ViewController, image: CGImage, selectedImage: CGImage) { - self.account = account - super.init(image: image, selectedImage: selectedImage, controller: controller, subNode:GlobalBadgeNode(account)) + private let context:AccountContext + init(_ context: AccountContext, controller:ViewController, image: CGImage, selectedImage: CGImage, longHoverHandler:((Control)->Void)? = nil) { + self.context = context + super.init(image: image, selectedImage: selectedImage, controller: controller, subNode:GlobalBadgeNode(context.account, sharedContext: context.sharedContext, dockTile: true, view: View(), removeWhenSidebar: true), longHoverHandler: longHoverHandler) + } + override func withUpdatedImages(_ image: CGImage, _ selectedImage: CGImage) -> TabItem { + return TabBadgeItem(context, controller: self.controller, image: image, selectedImage: selectedImage, longHoverHandler: self.longHoverHandler) + } +} +class TabAllBadgeItem: TabItem { + private let context:AccountContext + private let disposable = MetaDisposable() + private var peer: Peer? + init(_ context: AccountContext, image: CGImage, selectedImage: CGImage, controller:ViewController, subNode:Node? = nil, longHoverHandler:((Control)->Void)? = nil) { + self.context = context + super.init(image: image, selectedImage: selectedImage, controller: controller, subNode:GlobalBadgeNode(context.account, sharedContext: context.sharedContext, collectAllAccounts: true, view: View(), applyFilter: false), longHoverHandler: longHoverHandler) + } + deinit { + disposable.dispose() + } + + override func withUpdatedImages(_ image: CGImage, _ selectedImage: CGImage) -> TabItem { + return TabAllBadgeItem(context, image: image, selectedImage: selectedImage, controller: self.controller, subNode: self.subNode, longHoverHandler: self.longHoverHandler) + } + + override func makeView() -> NSView { + let context = self.context + + let semaphore = DispatchSemaphore(value: 0) + var isMultiple = true + _ = (context.sharedContext.activeAccounts |> take(1)).start(next: { accounts in + isMultiple = accounts.accounts.count > 1 + semaphore.signal() + }) + + semaphore.wait() + + if !isMultiple { + return super.makeView() + } + + let view = AvatarTabContainer(frame: NSMakeRect(0, 0, 30, 30)) + /* + |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + return lhs?.smallProfileImage != rhs?.smallProfileImage + }) + */ + disposable.set((context.account.postbox.peerView(id: context.account.peerId) |> map { $0.peers[$0.peerId] } |> deliverOnMainQueue).start(next: { [weak view] peer in + view?.setPeer(account: context.account, peer: peer) + })) + + return view + } + + override func setSelected(_ selected: Bool, for view: NSView, animated: Bool) { + (view as? AvatarTabContainer)?.setSelected(selected, animated: animated) + super.setSelected(selected, for: view, animated: animated) } } diff --git a/Telegram-Mac/TableUtils.swift b/Telegram-Mac/TableUtils.swift index 0f7ec2a833..3044232646 100644 --- a/Telegram-Mac/TableUtils.swift +++ b/Telegram-Mac/TableUtils.swift @@ -9,7 +9,8 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore protocol TableItemListNodeEntry: Comparable, Identifiable { associatedtype ItemGenerationArguments @@ -17,3 +18,7 @@ protocol TableItemListNodeEntry: Comparable, Identifiable { func item(_ arguments: ItemGenerationArguments, initialSize: NSSize) -> TableRowItem } +protocol ItemListItemTag { + func isEqual(to other: ItemListItemTag) -> Bool +} + diff --git a/Telegram-Mac/Telegram-Mac-Bridging-Header.h b/Telegram-Mac/Telegram-Mac-Bridging-Header.h index b2bc9a9ca3..f8a7cff127 100644 --- a/Telegram-Mac/Telegram-Mac-Bridging-Header.h +++ b/Telegram-Mac/Telegram-Mac-Bridging-Header.h @@ -16,6 +16,42 @@ #import #import #import +#import "MP4Atom.h" +#import "HackUtils.h" +#import "BuildConfig.h" +#import "TGModernGrowingTextView.h" + + +#ifndef SHARE +#import "ffmpeg/include/libavcodec/avcodec.h" +#import "ffmpeg/include/libavformat/avformat.h" +#import "libjpeg-turbo/jpeglib.h" +#import "libjpeg-turbo/jerror.h" +#import "libjpeg-turbo/turbojpeg.h" +#import "libjpeg-turbo/jmorecfg.h" +#import "FFMpegRemuxer.h" +#import "FFMpegGlobals.h" +#import "FFMpegAVFormatContext.h" +#import "FFMpegAVIOContext.h" +#import "FFMpegAVCodec.h" +#import "FFMpegAVCodecContext.h" +#import "FFMpegAVFrame.h" +#import "FFMpegPacket.h" +#import "FFMpegSwResample.h" +#import "GZip.h" +#import "Svg.h" +#endif + +#import "OngoingCallThreadLocalContext.h" +#import "OngoingCallThreadLocalContextWebrtc.h" + +#import "CalendarUtils.h" +#import "RingBuffer.h" +#import "ocr.h" +#import "TGPassportMRZ.h" +#import "EDSunriseSet.h" +#import "ObjcUtils.h" + //#import //#include @@ -32,165 +68,17 @@ #define nullable #endif +void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void * __nullable pixels); +void telegramFastBlurMore(int imageWidth, int imageHeight, int imageStride, void * __nullable pixels); void telegramFastBlur(int imageWidth, int imageHeight, int imageStride, void * __nullable pixels); -NSArray * __nonnull cut_long_message(NSString * __nonnull message, int max_length); int64_t SystemIdleTime(void); NSDictionary * __nonnull audioTags(AVURLAsset * __nonnull asset); NSImage * __nonnull TGIdenticonImage(NSData * __nonnull data, NSData * __nonnull additionalData, CGSize size); CGImageRef __nullable convertFromWebP(NSData *__nonnull data); -@interface ObjcUtils : NSObject -+ (NSArray * __nullable)textCheckingResultsForText:(NSString *__nonnull)text highlightMentionsAndTags:(bool)highlightMentionsAndTags highlightCommands:(bool)highlightCommands; -+(NSString * __nonnull) md5:(NSString *__nonnull)string; -+(NSArray *__nonnull)findElementsByClass:(NSString *__nonnull)className inView:(NSView *__nonnull)view; -+(NSString * __nonnull)stringForEmojiHashOfData:(NSData *__nonnull)data count:(NSInteger)count positionExtractor:(int32_t (^__nonnull)(uint8_t *__nonnull, int32_t, int32_t))positionExtractor; -+(NSArray *)bufferList:(CMSampleBufferRef)sampleBuffer; -+(NSString * __nonnull)callEmojies:(NSData *__nonnull)keySha256; -+ (NSArray * __nonnull)getEmojiFromString:(NSString * __nonnull)string; -+(NSOpenPanel * __nonnull)openPanel; -+(NSSavePanel * __nonnull)savePanel; -+(NSEvent * __nonnull)scrollEvent:(NSEvent *__nonnull)from; -+(NSSize)gifDimensionSize:(NSString * __nonnull)path; -+(int)colorMask:(int)idValue mainId:(int)mainId; -+(NSArray * __nonnull)notificationTones:(NSString * __nonnull)def; -+(NSString * __nullable)youtubeIdentifier:(NSString * __nonnull)url;; -@end - -int colorIndexForGroupId(int64_t groupId); -int64_t TGPeerIdFromChannelId(int32_t channelId); -int colorIndexForUid(int32_t uid, int32_t myUserId); - -@interface NSData (TG) -- (NSString *__nonnull)stringByEncodingInHex; -@end - - -@interface NSFileManager (Extension) -+ (NSString * __nonnull)xattrStringValueForKey:(NSString *__nonnull)key atURL:(NSURL *__nonnull)URL; -+ (BOOL)setXAttrStringValue:(NSString *__nonnull)value forKey:(NSString *__nonnull)key atURL:(NSURL *__nonnull)URL; -@end - -@interface NSMutableAttributedString(Extension) --(void)detectBoldColorInStringWithFont:(NSFont *__nonnull)font; -@end - -@interface CalendarUtils : NSObject - -+ (BOOL) isSameDate:(NSDate*__nonnull)d1 date:(NSDate* __nonnull)d2 checkDay:(BOOL)checkDay; -+ (NSString*__nonnull) dd:(NSDate*__nonnull)d; -+ (NSInteger) colForDay:(NSInteger)day; -+ (NSInteger) lastDayOfTheMonth:(NSDate *__nonnull)date; -+ (NSDate*__nonnull) toUTC:(NSDate*__nonnull)d; -+ (NSDate*__nonnull) monthDay:(NSInteger)day date:(NSDate *__nonnull)date; -+ (NSInteger)weekDay:(NSDate *__nonnull)date; -+ (NSDate *__nonnull) stepMonth:(NSInteger)dm date:(NSDate *__nonnull)date; - -@end - -extern NSString *__nonnull const TGMentionUidAttributeName; - - -@interface TGInputTextAttribute : NSObject -@property (nonatomic,strong,readonly) NSString * __nonnull name; -@property (nonatomic,strong,readonly) id __nonnull value; --(id __nonnull )initWithName:(NSString * __nonnull)name value:(id __nonnull)value; -@end - -@interface TGInputTextTag : NSTextAttachment - -@property (nonatomic, readonly) int64_t uniqueId; -@property (nonatomic, strong, readonly) id __nonnull attachment; - -@property (nonatomic,strong, readonly) TGInputTextAttribute * __nonnull attribute; - --(instancetype __nonnull )initWithUniqueId:(int64_t)uniqueId attachment:(id __nonnull )attachment attribute:(TGInputTextAttribute * __nonnull )attribute; - -@end - -@interface TGInputTextTagAndRange : NSObject - -@property (nonatomic, strong, readonly) TGInputTextTag *__nonnull tag; -@property (nonatomic) NSRange range; - -- (instancetype __nonnull )initWithTag:(TGInputTextTag * __nonnull )tag range:(NSRange)range; - -@end - -@protocol TGModernGrowingDelegate --(void) textViewHeightChanged:(CGFloat)height animated:(BOOL)animated; --(BOOL) textViewEnterPressed:(NSEvent * __nonnull)event; --(void) textViewTextDidChange:(NSString * __nonnull)string; --(void) textViewTextDidChangeSelectedRange:(NSRange)range; --(BOOL)textViewDidPaste:(NSPasteboard * __nonnull)pasteboard; --(NSSize)textViewSize; --(BOOL)textViewIsTypingEnabled; --(int)maxCharactersLimit; -@optional -- (void) textViewNeedClose:(id __nonnull)textView; -- (BOOL) canTransformInputText; -@end - - - - -@interface TGGrowingTextView : NSTextView -@property (nonatomic,weak) id __nullable weakd; -@end - -@interface TGModernGrowingTextView : NSView - -@property (nonatomic,assign) BOOL animates; -@property (nonatomic,assign) int min_height; -@property (nonatomic,assign) int max_height; - -@property (nonatomic,assign) BOOL isSingleLine; -@property (nonatomic,assign) BOOL isWhitespaceDisabled; -@property (nonatomic,strong) NSColor * __nonnull cursorColor; -@property (nonatomic,strong) NSColor * __nonnull textColor; -@property (nonatomic,strong) NSColor * __nonnull linkColor; -@property (nonatomic,strong) NSFont * __nonnull textFont; -@property (nonatomic,strong,readonly) TGGrowingTextView * __nonnull inputView; -@property (nonatomic,strong) NSString * __nonnull defaultText; - -@property (nonatomic,strong, nullable) NSAttributedString *placeholderAttributedString; - --(void)setPlaceholderAttributedString:(NSAttributedString * __nonnull)placeholderAttributedString update:(BOOL)update; - -@property (nonatomic,weak) id __nullable delegate; - --(int)height; - - --(void)update:(BOOL)notify; - --(NSAttributedString * __nonnull)attributedString; --(void)setAttributedString:(NSAttributedString * __nonnull)attributedString animated:(BOOL)animated; --(NSString * __nonnull)string; --(void)setString:(NSString * __nonnull)string animated:(BOOL)animated; --(void)setString:(NSString * __nonnull)string; --(NSRange)selectedRange; --(void)appendText:(id __nonnull)aString; --(void)insertText:(id __nonnull)aString replacementRange:(NSRange)replacementRange; --(void)addInputTextTag:(TGInputTextTag * __nonnull)tag range:(NSRange)range; - --(void)replaceMention:(NSString * __nonnull)mention username:(bool)username userId:(int32_t)userId; - --(void)paste:(id __nonnull)sender; - --(void)setSelectedRange:(NSRange)range; - --(Class __nonnull)_textViewClass; --(int)_startXPlaceholder; --(BOOL)_needShowPlaceholder; - --(void)codeWord; --(void)italicWord; --(void)boldWord; -- (void)textDidChange:( NSNotification * _Nullable )notification; -@end @interface NSWeakReference : NSObject @@ -232,9 +120,10 @@ extern NSString *__nonnull const TGMentionUidAttributeName; //BEGIN AUDIO HEADER + + @interface TGDataItem : NSObject -- (instancetype __nonnull)initWithTempFile; - (instancetype __nonnull)initWithFilePath:(NSString * __nonnull)filePath; - (void)moveToPath:(NSString * __nonnull)path; @@ -260,20 +149,7 @@ extern NSString *__nonnull const TGMentionUidAttributeName; - (uint16_t * __nonnull)sampleList; @end -@interface TGOpusAudioRecorder : NSObject - -@property (nonatomic, copy) void (^__nullable pauseRecording)(); -@property (nonatomic, copy) void (^__nullable micLevel)(CGFloat); -- (instancetype __nonnull)initWithFileEncryption:(bool)fileEncryption; - -- (void)_beginAudioSession:(bool)speaker; -- (void)prepareRecord:(bool)playTone completion:(void (^__nonnull)())completion; -- (void)record; -- (TGDataItem * __nonnull)stopRecording:(NSTimeInterval * __nonnull)recordedDuration waveform:(__autoreleasing TGAudioWaveform * __nullable* __nullable)waveform; -- (NSTimeInterval)currentDuration; - -@end double mappingRange(double x, double in_min, double in_max, double out_min, double out_max); @@ -304,6 +180,8 @@ double mappingRange(double x, double in_min, double in_max, double out_min, doub + (void)setDateLocalizationFunc:(NSString* __nonnull (^__nonnull)(NSString * __nonnull key))localizationF; @end +NSString * NSLocalized(NSString * key, NSString *comment); + NS_ASSUME_NONNULL_BEGIN @@ -349,9 +227,9 @@ typedef NS_ENUM(NSUInteger, YTVimeoVideoQuality) { @property (nonatomic, readonly) NSDictionary *metaData; --(NSURL *)highestQualityStreamURL; +-(NSURL * __nullable)highestQualityStreamURL; --(NSURL *)lowestQualityStreamURL; +-(NSURL * __nullable)lowestQualityStreamURL; @property (nonatomic, readonly, nullable) NSURL *HTTPLiveStreamURL; @@ -493,54 +371,6 @@ BOOL isEnterEventObjc(NSEvent *theEvent); -@interface TGCallConnectionDescription : NSObject - - @property (nonatomic, readonly) int64_t identifier; - @property (nonatomic, strong, readonly) NSString *ipv4; - @property (nonatomic, strong, readonly) NSString *ipv6; - @property (nonatomic, readonly) int32_t port; - @property (nonatomic, strong, readonly) NSData *peerTag; - -- (instancetype)initWithIdentifier:(int64_t)identifier ipv4:(NSString *)ipv4 ipv6:(NSString *)ipv6 port:(int32_t)port peerTag:(NSData *)peerTag; - -@end - - -@interface TGCallConnection : NSObject - - @property (nonatomic, strong, readonly) NSData *key; - @property (nonatomic, strong, readonly) NSData *keyHash; - @property (nonatomic, strong, readonly) TGCallConnectionDescription *defaultConnection; - @property (nonatomic, strong, readonly) NSArray *alternativeConnections; - -- (instancetype)initWithKey:(NSData *)key keyHash:(NSData *)keyHash defaultConnection:(TGCallConnectionDescription *)defaultConnection alternativeConnections:(NSArray *)alternativeConnections; - -@end - -@interface AudioDevice : NSObject -@property(nonatomic, strong, readonly) NSString *deviceId; -@property(nonatomic, strong, readonly) NSString *deviceName; --(id)initWithDeviceId:(NSString*)deviceId deviceName:(NSString *)deviceName; -@end - -@interface CallBridge : NSObject --(void)startTransmissionIfNeeded:(bool)outgoing connection:(TGCallConnection *)connection; - --(void)mute; --(void)unmute; --(BOOL)isMuted; - --(NSString *)currentOutputDeviceId; --(NSString *)currentInputDeviceId; --(NSArray *)outputDevices; --(NSArray *)inputDevices; --(void)setCurrentOutputDeviceId:(NSString *)deviceId; --(void)setCurrentInputDeviceId:(NSString *)deviceId; - -@property (nonatomic, copy) void (^stateChangeHandler)(int); - -@end - @interface TGCurrencyFormatterEntry : NSObject @property (nonatomic, strong, readonly) NSString *symbol; @@ -585,42 +415,6 @@ NS_ASSUME_NONNULL_END +(NSArray * __nonnull)getSuggestions:(NSString * __nonnull)q; @end -typedef enum { - MIHSliderTransitionFade, - MIHSliderTransitionPushVertical, - MIHSliderTransitionPushHorizontalFromLeft, - MIHSliderTransitionPushHorizontalFromRight -} MIHSliderTransition; - -@class MIHSliderDotsControl; - -@interface MIHSliderView : NSView - -@property (retain, readonly) NSArray * __nonnull slides; - -- (void)addSlide:(NSView * __nonnull)aSlide; -- (void)removeSlide:(NSView * __nonnull)aSlide; -@property (assign, readonly) NSUInteger indexOfDisplayedSlide; -@property (retain, readonly) NSView * __nonnull displayedSlide; -- (void)displaySlideAtIndex:(NSUInteger)aIndex; -@property (assign) MIHSliderTransition transitionStyle; -@property (assign) BOOL scheduledTransition; -@property (assign) BOOL repeatingScheduledTransition; -@property (assign) NSTimeInterval scheduledTransitionInterval; -@property (assign) NSTimeInterval transitionAnimationDuration; - -@property (retain) MIHSliderDotsControl * __nonnull dotsControl; - -@end - -@interface MIHSliderDotsControl : NSView - -@property (retain) NSImage * __nullable normalDotImage; - -@property (retain) NSImage * __nullable highlightedDotImage; - -@end - @interface TGVideoCameraGLRenderer : NSObject @property (nonatomic, readonly) __attribute__((NSObject)) CMFormatDescriptionRef outputFormatDescription; @@ -703,4 +497,87 @@ typedef enum @end + +@class RHResizableImage; + + +typedef NSEdgeInsets RHEdgeInsets; + + +extern RHEdgeInsets RHEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right); +extern CGRect RHEdgeInsetsInsetRect(CGRect rect, RHEdgeInsets insets, BOOL flipped); // If flipped origin is top-left otherwise origin is bottom-left (OSX Default is NO) +extern BOOL RHEdgeInsetsEqualToEdgeInsets(RHEdgeInsets insets1, RHEdgeInsets insets2); +extern const RHEdgeInsets RHEdgeInsetsZero; + +extern NSString *NSStringFromRHEdgeInsets(RHEdgeInsets insets); +extern RHEdgeInsets RHEdgeInsetsFromString(NSString* string); + + +typedef NSImageResizingMode RHResizableImageResizingMode; +enum { + RHResizableImageResizingModeTile = NSImageResizingModeTile, + RHResizableImageResizingModeStretch = NSImageResizingModeStretch, +}; + + + +@interface NSImage (RHResizableImageAdditions) + +-(RHResizableImage *)resizableImageWithCapInsets:(RHEdgeInsets)capInsets; // Create a resizable version of this image. the interior is tiled when drawn. +-(RHResizableImage *)resizableImageWithCapInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode; // The interior is resized according to the resizingMode + +-(RHResizableImage *)stretchableImageWithLeftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight; // Right cap is calculated as width - leftCapWidth - 1; bottom cap is calculated as height - topCapWidth - 1; + + +-(void)drawTiledInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; +-(void)drawStretchedInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; + +@end + + + +@interface RHResizableImage : NSImage { + // ivars are private + RHEdgeInsets _capInsets; + RHResizableImageResizingMode _resizingMode; + + NSArray *_imagePieces; + + NSBitmapImageRep *_cachedImageRep; + NSSize _cachedImageSize; + CGFloat _cachedImageDeviceScale; +} + +-(id)initWithImage:(NSImage *)image leftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight; // right cap is calculated as width - leftCapWidth - 1; bottom cap is calculated as height - topCapWidth - 1; + +-(id)initWithImage:(NSImage *)image capInsets:(RHEdgeInsets)capInsets; +-(id)initWithImage:(NSImage *)image capInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode; // designated initializer + +@property RHEdgeInsets capInsets; // Default is RHEdgeInsetsZero +@property RHResizableImageResizingMode resizingMode; // Default is UIImageResizingModeTile + +-(void)drawInRect:(NSRect)rect; +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha; +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; +-(void)drawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; + +-(void)originalDrawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; //super passthrough + + +@end + +// utilities +extern NSImage* RHImageByReferencingRectOfExistingImage(NSImage *image, NSRect rect); +extern NSArray* RHNinePartPiecesFromImageWithInsets(NSImage *image, RHEdgeInsets capInsets); +extern CGFloat RHContextGetDeviceScale(CGContextRef context); + +// nine part +extern void RHDrawNinePartImage(NSRect frame, NSImage *topLeftCorner, NSImage *topEdgeFill, NSImage *topRightCorner, NSImage *leftEdgeFill, NSImage *centerFill, NSImage *rightEdgeFill, NSImage *bottomLeftCorner, NSImage *bottomEdgeFill, NSImage *bottomRightCorner, NSCompositingOperation op, CGFloat alphaFraction, BOOL shouldTile); + +extern void RHDrawImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction, BOOL tile); +extern void RHDrawTiledImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction); +extern void RHDrawStretchedImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction); + + + #endif /* Telegram_Mac_Bridging_Header_h */ diff --git a/Telegram-Mac/Telegram-Mac.entitlements b/Telegram-Mac/Telegram-Mac.entitlements index 0fe126a453..a1ed865859 100644 --- a/Telegram-Mac/Telegram-Mac.entitlements +++ b/Telegram-Mac/Telegram-Mac.entitlements @@ -3,22 +3,15 @@ com.apple.security.app-sandbox - + com.apple.security.application-groups - $(TeamIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER) + 6N38VWS5BX.ru.keepcoder.Telegram + 6N38VWS5BX.ru.keepcoder.Telegram.TelegramShare - com.apple.security.device.camera - - com.apple.security.device.microphone - - com.apple.security.files.downloads.read-write + com.apple.security.device.audio-input - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server + com.apple.security.device.camera diff --git a/Telegram-Mac/Telegram-Sandbox.entitlements b/Telegram-Mac/Telegram-Sandbox.entitlements new file mode 100644 index 0000000000..4426af4e60 --- /dev/null +++ b/Telegram-Mac/Telegram-Sandbox.entitlements @@ -0,0 +1,36 @@ + + + + + com.apple.developer.maps + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + 6N38VWS5BX.ru.keepcoder.Telegram + 6N38VWS5BX.ru.keepcoder.Telegram.TelegramShare + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.device.microphone + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.personal-information.location + + keychain-access-groups + + 6N38VWS5BX.ru.keepcoder.Telegram + 6N38VWS5BX.ru.keepcoder.TelegramShare + + + diff --git a/Telegram-Mac/TelegramAccountAuxiliaryMethods.swift b/Telegram-Mac/TelegramAccountAuxiliaryMethods.swift index 4f5998c68b..f92442ccd3 100644 --- a/Telegram-Mac/TelegramAccountAuxiliaryMethods.swift +++ b/Telegram-Mac/TelegramAccountAuxiliaryMethods.swift @@ -7,8 +7,9 @@ // import Foundation -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in if interfaceState == nil { @@ -19,15 +20,22 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC return interfaceState } }, fetchResource: { account, resource, range, tag in - if let resource = resource as? LocalFileGifMediaResource { return fetchGifMediaResource(resource: resource) + } else if let resource = resource as? LocalFileArchiveMediaResource { + return fetchArchiveMediaResource(account: account, resource: resource) + } else if let mapSnapshotResource = resource as? MapSnapshotMediaResource { + return fetchMapSnapshotResource(resource: mapSnapshotResource) + } else if let resource = resource as? ExternalMusicAlbumArtResource { + return fetchExternalMusicAlbumArtResource(account: account, resource: resource) + } else if let resource = resource as? LocalFileVideoMediaResource { + return fetchMovMediaResource(resource: resource) + } else if let resource = resource as? LottieSoundMediaResource { + return fetchLottieSoundData(resource: resource) } - -// if let resource = resource as? VideoLibraryMediaResource { -// return fetchVideoLibraryMediaResource(resource: resource) -// } else if let resource = resource as? LocalFileVideoMediaResource { -// return fetchLocalFileVideoMediaResource(resource: resource) -// } + return nil +}, fetchResourceMediaReferenceHash: { resource in + return .single(nil) +}, prepareSecretThumbnailData: { resource in return nil }) diff --git a/Telegram-Mac/TelegramApplicationContext.swift b/Telegram-Mac/TelegramApplicationContext.swift deleted file mode 100644 index 99a222fe2e..0000000000 --- a/Telegram-Mac/TelegramApplicationContext.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// TelegramApplicationContext.swift -// TelegramMac -// -// Created by keepcoder on 28/11/2016. -// Copyright © 2016 Telegram. All rights reserved. -// - -import Cocoa -import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac - -public var isDebug = false - -class TelegramApplicationContext : NSObject { - var layout:SplitViewState = .none - let layoutHandler:ValuePromise = ValuePromise(ignoreRepeated:true) - private(set) var mediaKeyTap:SPMediaKeyTap? - let entertainment:EntertainmentViewController - private var _recentlyPeerUsed:[PeerId] = [] - let cachedAdminIds: CachedAdminIds = CachedAdminIds() - private(set) var timeDifference:TimeInterval = 0 - private(set) var recentlyPeerUsed:[PeerId] { - set { - _recentlyPeerUsed = newValue - } - get { - if _recentlyPeerUsed.count > 2 { - return Array(_recentlyPeerUsed.prefix(through: 2)) - } else { - return _recentlyPeerUsed - } - } - } - - var globalSearch:((String)->Void)? - - private let logoutDisposable = MetaDisposable() - - weak var mainNavigation:NavigationViewController? - private let updateDifferenceDisposable = MetaDisposable() - init(_ mainNavigation:NavigationViewController?, _ entertainment:EntertainmentViewController, network: Network) { - self.mainNavigation = mainNavigation - self.entertainment = entertainment - timeDifference = network.globalTime - Date().timeIntervalSince1970 - super.init() - - - _ = layoutHandler.get().start(next: { [weak self] (state) in - self?.layout = state - }) - - updateDifferenceDisposable.set((Signal.single(Void()) - |> delay(5 * 60, queue: Queue.mainQueue()) |> restart).start(next: { [weak self, weak network] in - if let network = network { - self?.timeDifference = network.globalTime - Date().timeIntervalSince1970 - } - })) - - } - - func showCallHeader(with session:PCallSession) { - mainNavigation?.callHeader?.show(true) - if let view = mainNavigation?.callHeader?.view as? CallNavigationHeaderView { - view.update(with: session) - } - } - - func checkFirstRecentlyForDuplicate(peerId:PeerId) { - if let index = recentlyPeerUsed.index(of: peerId), index == 0 { - recentlyPeerUsed.remove(at: index) - } - } - - func addRecentlyUsedPeer(peerId:PeerId) { - if let index = recentlyPeerUsed.index(of: peerId) { - recentlyPeerUsed.remove(at: index) - } - recentlyPeerUsed.insert(peerId, at: 0) - if recentlyPeerUsed.count > 4 { - recentlyPeerUsed = Array(recentlyPeerUsed.prefix(through: 4)) - } - } - - deinit { - updateDifferenceDisposable.dispose() - } - - - func deinitMediaKeyTap() { - mediaKeyTap?.stopWatchingMediaKeys() - mediaKeyTap = nil - } - - func initMediaKeyTap() { - mediaKeyTap = SPMediaKeyTap(delegate: self) - } - - override func mediaKeyTap(_ keyTap: SPMediaKeyTap, receivedMediaKeyEvent event: NSEvent) { - let keyCode: Int32 = (Int32((event.data1 & 0xffff0000) >> 16)) - let keyFlags: Int = (event.data1 & 0x0000ffff) - let keyIsPressed: Bool = ((keyFlags & 0xff00) >> 8) == 0xa - if keyIsPressed { - switch keyCode { - case NX_KEYTYPE_PLAY: - globalAudio?.playOrPause() - case NX_KEYTYPE_FAST: - globalAudio?.next() - case NX_KEYTYPE_REWIND: - globalAudio?.prev() - default: - break - } - } - } - -} diff --git a/Telegram-Mac/TelegramIconsTheme.swift b/Telegram-Mac/TelegramIconsTheme.swift new file mode 100644 index 0000000000..4a8232e89a --- /dev/null +++ b/Telegram-Mac/TelegramIconsTheme.swift @@ -0,0 +1,8252 @@ +import SwiftSignalKit + +final class TelegramIconsTheme { + private var cached:Atomic<[String: CGImage]> = Atomic(value: [:]) + private var cachedWithInset:Atomic<[String: (CGImage, NSEdgeInsets)]> = Atomic(value: [:]) + + var dialogMuteImage: CGImage { + if let image = cached.with({ $0["dialogMuteImage"] }) { + return image + } else { + let image = _dialogMuteImage() + _ = cached.modify { current in + var current = current + current["dialogMuteImage"] = image + return current + } + return image + } + } + var dialogMuteImageSelected: CGImage { + if let image = cached.with({ $0["dialogMuteImageSelected"] }) { + return image + } else { + let image = _dialogMuteImageSelected() + _ = cached.modify { current in + var current = current + current["dialogMuteImageSelected"] = image + return current + } + return image + } + } + var outgoingMessageImage: CGImage { + if let image = cached.with({ $0["outgoingMessageImage"] }) { + return image + } else { + let image = _outgoingMessageImage() + _ = cached.modify { current in + var current = current + current["outgoingMessageImage"] = image + return current + } + return image + } + } + var readMessageImage: CGImage { + if let image = cached.with({ $0["readMessageImage"] }) { + return image + } else { + let image = _readMessageImage() + _ = cached.modify { current in + var current = current + current["readMessageImage"] = image + return current + } + return image + } + } + var outgoingMessageImageSelected: CGImage { + if let image = cached.with({ $0["outgoingMessageImageSelected"] }) { + return image + } else { + let image = _outgoingMessageImageSelected() + _ = cached.modify { current in + var current = current + current["outgoingMessageImageSelected"] = image + return current + } + return image + } + } + var readMessageImageSelected: CGImage { + if let image = cached.with({ $0["readMessageImageSelected"] }) { + return image + } else { + let image = _readMessageImageSelected() + _ = cached.modify { current in + var current = current + current["readMessageImageSelected"] = image + return current + } + return image + } + } + var sendingImage: CGImage { + if let image = cached.with({ $0["sendingImage"] }) { + return image + } else { + let image = _sendingImage() + _ = cached.modify { current in + var current = current + current["sendingImage"] = image + return current + } + return image + } + } + var sendingImageSelected: CGImage { + if let image = cached.with({ $0["sendingImageSelected"] }) { + return image + } else { + let image = _sendingImageSelected() + _ = cached.modify { current in + var current = current + current["sendingImageSelected"] = image + return current + } + return image + } + } + var secretImage: CGImage { + if let image = cached.with({ $0["secretImage"] }) { + return image + } else { + let image = _secretImage() + _ = cached.modify { current in + var current = current + current["secretImage"] = image + return current + } + return image + } + } + var secretImageSelected: CGImage { + if let image = cached.with({ $0["secretImageSelected"] }) { + return image + } else { + let image = _secretImageSelected() + _ = cached.modify { current in + var current = current + current["secretImageSelected"] = image + return current + } + return image + } + } + var pinnedImage: CGImage { + if let image = cached.with({ $0["pinnedImage"] }) { + return image + } else { + let image = _pinnedImage() + _ = cached.modify { current in + var current = current + current["pinnedImage"] = image + return current + } + return image + } + } + var pinnedImageSelected: CGImage { + if let image = cached.with({ $0["pinnedImageSelected"] }) { + return image + } else { + let image = _pinnedImageSelected() + _ = cached.modify { current in + var current = current + current["pinnedImageSelected"] = image + return current + } + return image + } + } + var verifiedImage: CGImage { + if let image = cached.with({ $0["verifiedImage"] }) { + return image + } else { + let image = _verifiedImage() + _ = cached.modify { current in + var current = current + current["verifiedImage"] = image + return current + } + return image + } + } + var verifiedImageSelected: CGImage { + if let image = cached.with({ $0["verifiedImageSelected"] }) { + return image + } else { + let image = _verifiedImageSelected() + _ = cached.modify { current in + var current = current + current["verifiedImageSelected"] = image + return current + } + return image + } + } + var errorImage: CGImage { + if let image = cached.with({ $0["errorImage"] }) { + return image + } else { + let image = _errorImage() + _ = cached.modify { current in + var current = current + current["errorImage"] = image + return current + } + return image + } + } + var errorImageSelected: CGImage { + if let image = cached.with({ $0["errorImageSelected"] }) { + return image + } else { + let image = _errorImageSelected() + _ = cached.modify { current in + var current = current + current["errorImageSelected"] = image + return current + } + return image + } + } + var chatSearch: CGImage { + if let image = cached.with({ $0["chatSearch"] }) { + return image + } else { + let image = _chatSearch() + _ = cached.modify { current in + var current = current + current["chatSearch"] = image + return current + } + return image + } + } + var chatSearchActive: CGImage { + if let image = cached.with({ $0["chatSearchActive"] }) { + return image + } else { + let image = _chatSearchActive() + _ = cached.modify { current in + var current = current + current["chatSearchActive"] = image + return current + } + return image + } + } + var chatCall: CGImage { + if let image = cached.with({ $0["chatCall"] }) { + return image + } else { + let image = _chatCall() + _ = cached.modify { current in + var current = current + current["chatCall"] = image + return current + } + return image + } + } + var chatActions: CGImage { + if let image = cached.with({ $0["chatActions"] }) { + return image + } else { + let image = _chatActions() + _ = cached.modify { current in + var current = current + current["chatActions"] = image + return current + } + return image + } + } + var chatFailedCall_incoming: CGImage { + if let image = cached.with({ $0["chatFailedCall_incoming"] }) { + return image + } else { + let image = _chatFailedCall_incoming() + _ = cached.modify { current in + var current = current + current["chatFailedCall_incoming"] = image + return current + } + return image + } + } + var chatFailedCall_outgoing: CGImage { + if let image = cached.with({ $0["chatFailedCall_outgoing"] }) { + return image + } else { + let image = _chatFailedCall_outgoing() + _ = cached.modify { current in + var current = current + current["chatFailedCall_outgoing"] = image + return current + } + return image + } + } + var chatCall_incoming: CGImage { + if let image = cached.with({ $0["chatCall_incoming"] }) { + return image + } else { + let image = _chatCall_incoming() + _ = cached.modify { current in + var current = current + current["chatCall_incoming"] = image + return current + } + return image + } + } + var chatCall_outgoing: CGImage { + if let image = cached.with({ $0["chatCall_outgoing"] }) { + return image + } else { + let image = _chatCall_outgoing() + _ = cached.modify { current in + var current = current + current["chatCall_outgoing"] = image + return current + } + return image + } + } + var chatFailedCallBubble_incoming: CGImage { + if let image = cached.with({ $0["chatFailedCallBubble_incoming"] }) { + return image + } else { + let image = _chatFailedCallBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatFailedCallBubble_incoming"] = image + return current + } + return image + } + } + var chatFailedCallBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatFailedCallBubble_outgoing"] }) { + return image + } else { + let image = _chatFailedCallBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatFailedCallBubble_outgoing"] = image + return current + } + return image + } + } + var chatCallBubble_incoming: CGImage { + if let image = cached.with({ $0["chatCallBubble_incoming"] }) { + return image + } else { + let image = _chatCallBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatCallBubble_incoming"] = image + return current + } + return image + } + } + var chatCallBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatCallBubble_outgoing"] }) { + return image + } else { + let image = _chatCallBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatCallBubble_outgoing"] = image + return current + } + return image + } + } + var chatFallbackCall: CGImage { + if let image = cached.with({ $0["chatFallbackCall"] }) { + return image + } else { + let image = _chatFallbackCall() + _ = cached.modify { current in + var current = current + current["chatFallbackCall"] = image + return current + } + return image + } + } + var chatFallbackCallBubble_incoming: CGImage { + if let image = cached.with({ $0["chatFallbackCallBubble_incoming"] }) { + return image + } else { + let image = _chatFallbackCallBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatFallbackCallBubble_incoming"] = image + return current + } + return image + } + } + var chatFallbackCallBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatFallbackCallBubble_outgoing"] }) { + return image + } else { + let image = _chatFallbackCallBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatFallbackCallBubble_outgoing"] = image + return current + } + return image + } + } + var chatToggleSelected: CGImage { + if let image = cached.with({ $0["chatToggleSelected"] }) { + return image + } else { + let image = _chatToggleSelected() + _ = cached.modify { current in + var current = current + current["chatToggleSelected"] = image + return current + } + return image + } + } + var chatToggleUnselected: CGImage { + if let image = cached.with({ $0["chatToggleUnselected"] }) { + return image + } else { + let image = _chatToggleUnselected() + _ = cached.modify { current in + var current = current + current["chatToggleUnselected"] = image + return current + } + return image + } + } + var chatMusicPlay: CGImage { + if let image = cached.with({ $0["chatMusicPlay"] }) { + return image + } else { + let image = _chatMusicPlay() + _ = cached.modify { current in + var current = current + current["chatMusicPlay"] = image + return current + } + return image + } + } + var chatMusicPlayBubble_incoming: CGImage { + if let image = cached.with({ $0["chatMusicPlayBubble_incoming"] }) { + return image + } else { + let image = _chatMusicPlayBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatMusicPlayBubble_incoming"] = image + return current + } + return image + } + } + var chatMusicPlayBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatMusicPlayBubble_outgoing"] }) { + return image + } else { + let image = _chatMusicPlayBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatMusicPlayBubble_outgoing"] = image + return current + } + return image + } + } + var chatMusicPause: CGImage { + if let image = cached.with({ $0["chatMusicPause"] }) { + return image + } else { + let image = _chatMusicPause() + _ = cached.modify { current in + var current = current + current["chatMusicPause"] = image + return current + } + return image + } + } + var chatMusicPauseBubble_incoming: CGImage { + if let image = cached.with({ $0["chatMusicPauseBubble_incoming"] }) { + return image + } else { + let image = _chatMusicPauseBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatMusicPauseBubble_incoming"] = image + return current + } + return image + } + } + var chatMusicPauseBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatMusicPauseBubble_outgoing"] }) { + return image + } else { + let image = _chatMusicPauseBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatMusicPauseBubble_outgoing"] = image + return current + } + return image + } + } + var chatGradientBubble_incoming: CGImage { + if let image = cached.with({ $0["chatGradientBubble_incoming"] }) { + return image + } else { + let image = _chatGradientBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatGradientBubble_incoming"] = image + return current + } + return image + } + } + var chatGradientBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatGradientBubble_outgoing"] }) { + return image + } else { + let image = _chatGradientBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatGradientBubble_outgoing"] = image + return current + } + return image + } + } + var chatBubble_none_incoming_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubble_none_incoming_withInset"] }) { + return image + } else { + let image = _chatBubble_none_incoming_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubble_none_incoming_withInset"] = image + return current + } + return image + } + } + var chatBubble_none_outgoing_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubble_none_outgoing_withInset"] }) { + return image + } else { + let image = _chatBubble_none_outgoing_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubble_none_outgoing_withInset"] = image + return current + } + return image + } + } + var chatBubbleBorder_none_incoming_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubbleBorder_none_incoming_withInset"] }) { + return image + } else { + let image = _chatBubbleBorder_none_incoming_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubbleBorder_none_incoming_withInset"] = image + return current + } + return image + } + } + var chatBubbleBorder_none_outgoing_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubbleBorder_none_outgoing_withInset"] }) { + return image + } else { + let image = _chatBubbleBorder_none_outgoing_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubbleBorder_none_outgoing_withInset"] = image + return current + } + return image + } + } + var chatBubble_both_incoming_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubble_both_incoming_withInset"] }) { + return image + } else { + let image = _chatBubble_both_incoming_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubble_both_incoming_withInset"] = image + return current + } + return image + } + } + var chatBubble_both_outgoing_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubble_both_outgoing_withInset"] }) { + return image + } else { + let image = _chatBubble_both_outgoing_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubble_both_outgoing_withInset"] = image + return current + } + return image + } + } + var chatBubbleBorder_both_incoming_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubbleBorder_both_incoming_withInset"] }) { + return image + } else { + let image = _chatBubbleBorder_both_incoming_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubbleBorder_both_incoming_withInset"] = image + return current + } + return image + } + } + var chatBubbleBorder_both_outgoing_withInset: (CGImage, NSEdgeInsets) { + if let image = cachedWithInset.with({ $0["chatBubbleBorder_both_outgoing_withInset"] }) { + return image + } else { + let image = _chatBubbleBorder_both_outgoing_withInset() + _ = cachedWithInset.modify { current in + var current = current + current["chatBubbleBorder_both_outgoing_withInset"] = image + return current + } + return image + } + } + var composeNewChat: CGImage { + if let image = cached.with({ $0["composeNewChat"] }) { + return image + } else { + let image = _composeNewChat() + _ = cached.modify { current in + var current = current + current["composeNewChat"] = image + return current + } + return image + } + } + var composeNewChatActive: CGImage { + if let image = cached.with({ $0["composeNewChatActive"] }) { + return image + } else { + let image = _composeNewChatActive() + _ = cached.modify { current in + var current = current + current["composeNewChatActive"] = image + return current + } + return image + } + } + var composeNewGroup: CGImage { + if let image = cached.with({ $0["composeNewGroup"] }) { + return image + } else { + let image = _composeNewGroup() + _ = cached.modify { current in + var current = current + current["composeNewGroup"] = image + return current + } + return image + } + } + var composeNewSecretChat: CGImage { + if let image = cached.with({ $0["composeNewSecretChat"] }) { + return image + } else { + let image = _composeNewSecretChat() + _ = cached.modify { current in + var current = current + current["composeNewSecretChat"] = image + return current + } + return image + } + } + var composeNewChannel: CGImage { + if let image = cached.with({ $0["composeNewChannel"] }) { + return image + } else { + let image = _composeNewChannel() + _ = cached.modify { current in + var current = current + current["composeNewChannel"] = image + return current + } + return image + } + } + var contactsNewContact: CGImage { + if let image = cached.with({ $0["contactsNewContact"] }) { + return image + } else { + let image = _contactsNewContact() + _ = cached.modify { current in + var current = current + current["contactsNewContact"] = image + return current + } + return image + } + } + var chatReadMarkInBubble1_incoming: CGImage { + if let image = cached.with({ $0["chatReadMarkInBubble1_incoming"] }) { + return image + } else { + let image = _chatReadMarkInBubble1_incoming() + _ = cached.modify { current in + var current = current + current["chatReadMarkInBubble1_incoming"] = image + return current + } + return image + } + } + var chatReadMarkInBubble2_incoming: CGImage { + if let image = cached.with({ $0["chatReadMarkInBubble2_incoming"] }) { + return image + } else { + let image = _chatReadMarkInBubble2_incoming() + _ = cached.modify { current in + var current = current + current["chatReadMarkInBubble2_incoming"] = image + return current + } + return image + } + } + var chatReadMarkInBubble1_outgoing: CGImage { + if let image = cached.with({ $0["chatReadMarkInBubble1_outgoing"] }) { + return image + } else { + let image = _chatReadMarkInBubble1_outgoing() + _ = cached.modify { current in + var current = current + current["chatReadMarkInBubble1_outgoing"] = image + return current + } + return image + } + } + var chatReadMarkInBubble2_outgoing: CGImage { + if let image = cached.with({ $0["chatReadMarkInBubble2_outgoing"] }) { + return image + } else { + let image = _chatReadMarkInBubble2_outgoing() + _ = cached.modify { current in + var current = current + current["chatReadMarkInBubble2_outgoing"] = image + return current + } + return image + } + } + var chatReadMarkOutBubble1: CGImage { + if let image = cached.with({ $0["chatReadMarkOutBubble1"] }) { + return image + } else { + let image = _chatReadMarkOutBubble1() + _ = cached.modify { current in + var current = current + current["chatReadMarkOutBubble1"] = image + return current + } + return image + } + } + var chatReadMarkOutBubble2: CGImage { + if let image = cached.with({ $0["chatReadMarkOutBubble2"] }) { + return image + } else { + let image = _chatReadMarkOutBubble2() + _ = cached.modify { current in + var current = current + current["chatReadMarkOutBubble2"] = image + return current + } + return image + } + } + var chatReadMarkOverlayBubble1: CGImage { + if let image = cached.with({ $0["chatReadMarkOverlayBubble1"] }) { + return image + } else { + let image = _chatReadMarkOverlayBubble1() + _ = cached.modify { current in + var current = current + current["chatReadMarkOverlayBubble1"] = image + return current + } + return image + } + } + var chatReadMarkOverlayBubble2: CGImage { + if let image = cached.with({ $0["chatReadMarkOverlayBubble2"] }) { + return image + } else { + let image = _chatReadMarkOverlayBubble2() + _ = cached.modify { current in + var current = current + current["chatReadMarkOverlayBubble2"] = image + return current + } + return image + } + } + var sentFailed: CGImage { + if let image = cached.with({ $0["sentFailed"] }) { + return image + } else { + let image = _sentFailed() + _ = cached.modify { current in + var current = current + current["sentFailed"] = image + return current + } + return image + } + } + var chatChannelViewsInBubble_incoming: CGImage { + if let image = cached.with({ $0["chatChannelViewsInBubble_incoming"] }) { + return image + } else { + let image = _chatChannelViewsInBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatChannelViewsInBubble_incoming"] = image + return current + } + return image + } + } + var chatChannelViewsInBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatChannelViewsInBubble_outgoing"] }) { + return image + } else { + let image = _chatChannelViewsInBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatChannelViewsInBubble_outgoing"] = image + return current + } + return image + } + } + var chatChannelViewsOutBubble: CGImage { + if let image = cached.with({ $0["chatChannelViewsOutBubble"] }) { + return image + } else { + let image = _chatChannelViewsOutBubble() + _ = cached.modify { current in + var current = current + current["chatChannelViewsOutBubble"] = image + return current + } + return image + } + } + var chatChannelViewsOverlayBubble: CGImage { + if let image = cached.with({ $0["chatChannelViewsOverlayBubble"] }) { + return image + } else { + let image = _chatChannelViewsOverlayBubble() + _ = cached.modify { current in + var current = current + current["chatChannelViewsOverlayBubble"] = image + return current + } + return image + } + } + var chatNavigationBack: CGImage { + if let image = cached.with({ $0["chatNavigationBack"] }) { + return image + } else { + let image = _chatNavigationBack() + _ = cached.modify { current in + var current = current + current["chatNavigationBack"] = image + return current + } + return image + } + } + var peerInfoAddMember: CGImage { + if let image = cached.with({ $0["peerInfoAddMember"] }) { + return image + } else { + let image = _peerInfoAddMember() + _ = cached.modify { current in + var current = current + current["peerInfoAddMember"] = image + return current + } + return image + } + } + var chatSearchUp: CGImage { + if let image = cached.with({ $0["chatSearchUp"] }) { + return image + } else { + let image = _chatSearchUp() + _ = cached.modify { current in + var current = current + current["chatSearchUp"] = image + return current + } + return image + } + } + var chatSearchUpDisabled: CGImage { + if let image = cached.with({ $0["chatSearchUpDisabled"] }) { + return image + } else { + let image = _chatSearchUpDisabled() + _ = cached.modify { current in + var current = current + current["chatSearchUpDisabled"] = image + return current + } + return image + } + } + var chatSearchDown: CGImage { + if let image = cached.with({ $0["chatSearchDown"] }) { + return image + } else { + let image = _chatSearchDown() + _ = cached.modify { current in + var current = current + current["chatSearchDown"] = image + return current + } + return image + } + } + var chatSearchDownDisabled: CGImage { + if let image = cached.with({ $0["chatSearchDownDisabled"] }) { + return image + } else { + let image = _chatSearchDownDisabled() + _ = cached.modify { current in + var current = current + current["chatSearchDownDisabled"] = image + return current + } + return image + } + } + var chatSearchCalendar: CGImage { + if let image = cached.with({ $0["chatSearchCalendar"] }) { + return image + } else { + let image = _chatSearchCalendar() + _ = cached.modify { current in + var current = current + current["chatSearchCalendar"] = image + return current + } + return image + } + } + var dismissAccessory: CGImage { + if let image = cached.with({ $0["dismissAccessory"] }) { + return image + } else { + let image = _dismissAccessory() + _ = cached.modify { current in + var current = current + current["dismissAccessory"] = image + return current + } + return image + } + } + var chatScrollUp: CGImage { + if let image = cached.with({ $0["chatScrollUp"] }) { + return image + } else { + let image = _chatScrollUp() + _ = cached.modify { current in + var current = current + current["chatScrollUp"] = image + return current + } + return image + } + } + var chatScrollUpActive: CGImage { + if let image = cached.with({ $0["chatScrollUpActive"] }) { + return image + } else { + let image = _chatScrollUpActive() + _ = cached.modify { current in + var current = current + current["chatScrollUpActive"] = image + return current + } + return image + } + } + var audioPlayerPlay: CGImage { + if let image = cached.with({ $0["audioPlayerPlay"] }) { + return image + } else { + let image = _audioPlayerPlay() + _ = cached.modify { current in + var current = current + current["audioPlayerPlay"] = image + return current + } + return image + } + } + var audioPlayerPause: CGImage { + if let image = cached.with({ $0["audioPlayerPause"] }) { + return image + } else { + let image = _audioPlayerPause() + _ = cached.modify { current in + var current = current + current["audioPlayerPause"] = image + return current + } + return image + } + } + var audioPlayerNext: CGImage { + if let image = cached.with({ $0["audioPlayerNext"] }) { + return image + } else { + let image = _audioPlayerNext() + _ = cached.modify { current in + var current = current + current["audioPlayerNext"] = image + return current + } + return image + } + } + var audioPlayerPrev: CGImage { + if let image = cached.with({ $0["audioPlayerPrev"] }) { + return image + } else { + let image = _audioPlayerPrev() + _ = cached.modify { current in + var current = current + current["audioPlayerPrev"] = image + return current + } + return image + } + } + var auduiPlayerDismiss: CGImage { + if let image = cached.with({ $0["auduiPlayerDismiss"] }) { + return image + } else { + let image = _auduiPlayerDismiss() + _ = cached.modify { current in + var current = current + current["auduiPlayerDismiss"] = image + return current + } + return image + } + } + var audioPlayerRepeat: CGImage { + if let image = cached.with({ $0["audioPlayerRepeat"] }) { + return image + } else { + let image = _audioPlayerRepeat() + _ = cached.modify { current in + var current = current + current["audioPlayerRepeat"] = image + return current + } + return image + } + } + var audioPlayerRepeatActive: CGImage { + if let image = cached.with({ $0["audioPlayerRepeatActive"] }) { + return image + } else { + let image = _audioPlayerRepeatActive() + _ = cached.modify { current in + var current = current + current["audioPlayerRepeatActive"] = image + return current + } + return image + } + } + var audioPlayerLockedPlay: CGImage { + if let image = cached.with({ $0["audioPlayerLockedPlay"] }) { + return image + } else { + let image = _audioPlayerLockedPlay() + _ = cached.modify { current in + var current = current + current["audioPlayerLockedPlay"] = image + return current + } + return image + } + } + var audioPlayerLockedNext: CGImage { + if let image = cached.with({ $0["audioPlayerLockedNext"] }) { + return image + } else { + let image = _audioPlayerLockedNext() + _ = cached.modify { current in + var current = current + current["audioPlayerLockedNext"] = image + return current + } + return image + } + } + var audioPlayerLockedPrev: CGImage { + if let image = cached.with({ $0["audioPlayerLockedPrev"] }) { + return image + } else { + let image = _audioPlayerLockedPrev() + _ = cached.modify { current in + var current = current + current["audioPlayerLockedPrev"] = image + return current + } + return image + } + } + var chatSendMessage: CGImage { + if let image = cached.with({ $0["chatSendMessage"] }) { + return image + } else { + let image = _chatSendMessage() + _ = cached.modify { current in + var current = current + current["chatSendMessage"] = image + return current + } + return image + } + } + var chatSaveEditedMessage: CGImage { + if let image = cached.with({ $0["chatSaveEditedMessage"] }) { + return image + } else { + let image = _chatSaveEditedMessage() + _ = cached.modify { current in + var current = current + current["chatSaveEditedMessage"] = image + return current + } + return image + } + } + var chatRecordVoice: CGImage { + if let image = cached.with({ $0["chatRecordVoice"] }) { + return image + } else { + let image = _chatRecordVoice() + _ = cached.modify { current in + var current = current + current["chatRecordVoice"] = image + return current + } + return image + } + } + var chatEntertainment: CGImage { + if let image = cached.with({ $0["chatEntertainment"] }) { + return image + } else { + let image = _chatEntertainment() + _ = cached.modify { current in + var current = current + current["chatEntertainment"] = image + return current + } + return image + } + } + var chatInlineDismiss: CGImage { + if let image = cached.with({ $0["chatInlineDismiss"] }) { + return image + } else { + let image = _chatInlineDismiss() + _ = cached.modify { current in + var current = current + current["chatInlineDismiss"] = image + return current + } + return image + } + } + var chatActiveReplyMarkup: CGImage { + if let image = cached.with({ $0["chatActiveReplyMarkup"] }) { + return image + } else { + let image = _chatActiveReplyMarkup() + _ = cached.modify { current in + var current = current + current["chatActiveReplyMarkup"] = image + return current + } + return image + } + } + var chatDisabledReplyMarkup: CGImage { + if let image = cached.with({ $0["chatDisabledReplyMarkup"] }) { + return image + } else { + let image = _chatDisabledReplyMarkup() + _ = cached.modify { current in + var current = current + current["chatDisabledReplyMarkup"] = image + return current + } + return image + } + } + var chatSecretTimer: CGImage { + if let image = cached.with({ $0["chatSecretTimer"] }) { + return image + } else { + let image = _chatSecretTimer() + _ = cached.modify { current in + var current = current + current["chatSecretTimer"] = image + return current + } + return image + } + } + var chatForwardMessagesActive: CGImage { + if let image = cached.with({ $0["chatForwardMessagesActive"] }) { + return image + } else { + let image = _chatForwardMessagesActive() + _ = cached.modify { current in + var current = current + current["chatForwardMessagesActive"] = image + return current + } + return image + } + } + var chatForwardMessagesInactive: CGImage { + if let image = cached.with({ $0["chatForwardMessagesInactive"] }) { + return image + } else { + let image = _chatForwardMessagesInactive() + _ = cached.modify { current in + var current = current + current["chatForwardMessagesInactive"] = image + return current + } + return image + } + } + var chatDeleteMessagesActive: CGImage { + if let image = cached.with({ $0["chatDeleteMessagesActive"] }) { + return image + } else { + let image = _chatDeleteMessagesActive() + _ = cached.modify { current in + var current = current + current["chatDeleteMessagesActive"] = image + return current + } + return image + } + } + var chatDeleteMessagesInactive: CGImage { + if let image = cached.with({ $0["chatDeleteMessagesInactive"] }) { + return image + } else { + let image = _chatDeleteMessagesInactive() + _ = cached.modify { current in + var current = current + current["chatDeleteMessagesInactive"] = image + return current + } + return image + } + } + var generalNext: CGImage { + if let image = cached.with({ $0["generalNext"] }) { + return image + } else { + let image = _generalNext() + _ = cached.modify { current in + var current = current + current["generalNext"] = image + return current + } + return image + } + } + var generalNextActive: CGImage { + if let image = cached.with({ $0["generalNextActive"] }) { + return image + } else { + let image = _generalNextActive() + _ = cached.modify { current in + var current = current + current["generalNextActive"] = image + return current + } + return image + } + } + var generalSelect: CGImage { + if let image = cached.with({ $0["generalSelect"] }) { + return image + } else { + let image = _generalSelect() + _ = cached.modify { current in + var current = current + current["generalSelect"] = image + return current + } + return image + } + } + var chatVoiceRecording: CGImage { + if let image = cached.with({ $0["chatVoiceRecording"] }) { + return image + } else { + let image = _chatVoiceRecording() + _ = cached.modify { current in + var current = current + current["chatVoiceRecording"] = image + return current + } + return image + } + } + var chatVideoRecording: CGImage { + if let image = cached.with({ $0["chatVideoRecording"] }) { + return image + } else { + let image = _chatVideoRecording() + _ = cached.modify { current in + var current = current + current["chatVideoRecording"] = image + return current + } + return image + } + } + var chatRecord: CGImage { + if let image = cached.with({ $0["chatRecord"] }) { + return image + } else { + let image = _chatRecord() + _ = cached.modify { current in + var current = current + current["chatRecord"] = image + return current + } + return image + } + } + var deleteItem: CGImage { + if let image = cached.with({ $0["deleteItem"] }) { + return image + } else { + let image = _deleteItem() + _ = cached.modify { current in + var current = current + current["deleteItem"] = image + return current + } + return image + } + } + var deleteItemDisabled: CGImage { + if let image = cached.with({ $0["deleteItemDisabled"] }) { + return image + } else { + let image = _deleteItemDisabled() + _ = cached.modify { current in + var current = current + current["deleteItemDisabled"] = image + return current + } + return image + } + } + var chatAttach: CGImage { + if let image = cached.with({ $0["chatAttach"] }) { + return image + } else { + let image = _chatAttach() + _ = cached.modify { current in + var current = current + current["chatAttach"] = image + return current + } + return image + } + } + var chatAttachFile: CGImage { + if let image = cached.with({ $0["chatAttachFile"] }) { + return image + } else { + let image = _chatAttachFile() + _ = cached.modify { current in + var current = current + current["chatAttachFile"] = image + return current + } + return image + } + } + var chatAttachPhoto: CGImage { + if let image = cached.with({ $0["chatAttachPhoto"] }) { + return image + } else { + let image = _chatAttachPhoto() + _ = cached.modify { current in + var current = current + current["chatAttachPhoto"] = image + return current + } + return image + } + } + var chatAttachCamera: CGImage { + if let image = cached.with({ $0["chatAttachCamera"] }) { + return image + } else { + let image = _chatAttachCamera() + _ = cached.modify { current in + var current = current + current["chatAttachCamera"] = image + return current + } + return image + } + } + var chatAttachLocation: CGImage { + if let image = cached.with({ $0["chatAttachLocation"] }) { + return image + } else { + let image = _chatAttachLocation() + _ = cached.modify { current in + var current = current + current["chatAttachLocation"] = image + return current + } + return image + } + } + var chatAttachPoll: CGImage { + if let image = cached.with({ $0["chatAttachPoll"] }) { + return image + } else { + let image = _chatAttachPoll() + _ = cached.modify { current in + var current = current + current["chatAttachPoll"] = image + return current + } + return image + } + } + var mediaEmptyShared: CGImage { + if let image = cached.with({ $0["mediaEmptyShared"] }) { + return image + } else { + let image = _mediaEmptyShared() + _ = cached.modify { current in + var current = current + current["mediaEmptyShared"] = image + return current + } + return image + } + } + var mediaEmptyFiles: CGImage { + if let image = cached.with({ $0["mediaEmptyFiles"] }) { + return image + } else { + let image = _mediaEmptyFiles() + _ = cached.modify { current in + var current = current + current["mediaEmptyFiles"] = image + return current + } + return image + } + } + var mediaEmptyMusic: CGImage { + if let image = cached.with({ $0["mediaEmptyMusic"] }) { + return image + } else { + let image = _mediaEmptyMusic() + _ = cached.modify { current in + var current = current + current["mediaEmptyMusic"] = image + return current + } + return image + } + } + var mediaEmptyLinks: CGImage { + if let image = cached.with({ $0["mediaEmptyLinks"] }) { + return image + } else { + let image = _mediaEmptyLinks() + _ = cached.modify { current in + var current = current + current["mediaEmptyLinks"] = image + return current + } + return image + } + } + var stickersAddFeatured: CGImage { + if let image = cached.with({ $0["stickersAddFeatured"] }) { + return image + } else { + let image = _stickersAddFeatured() + _ = cached.modify { current in + var current = current + current["stickersAddFeatured"] = image + return current + } + return image + } + } + var stickersAddedFeatured: CGImage { + if let image = cached.with({ $0["stickersAddedFeatured"] }) { + return image + } else { + let image = _stickersAddedFeatured() + _ = cached.modify { current in + var current = current + current["stickersAddedFeatured"] = image + return current + } + return image + } + } + var stickersRemove: CGImage { + if let image = cached.with({ $0["stickersRemove"] }) { + return image + } else { + let image = _stickersRemove() + _ = cached.modify { current in + var current = current + current["stickersRemove"] = image + return current + } + return image + } + } + var peerMediaDownloadFileStart: CGImage { + if let image = cached.with({ $0["peerMediaDownloadFileStart"] }) { + return image + } else { + let image = _peerMediaDownloadFileStart() + _ = cached.modify { current in + var current = current + current["peerMediaDownloadFileStart"] = image + return current + } + return image + } + } + var peerMediaDownloadFilePause: CGImage { + if let image = cached.with({ $0["peerMediaDownloadFilePause"] }) { + return image + } else { + let image = _peerMediaDownloadFilePause() + _ = cached.modify { current in + var current = current + current["peerMediaDownloadFilePause"] = image + return current + } + return image + } + } + var stickersShare: CGImage { + if let image = cached.with({ $0["stickersShare"] }) { + return image + } else { + let image = _stickersShare() + _ = cached.modify { current in + var current = current + current["stickersShare"] = image + return current + } + return image + } + } + var emojiRecentTab: CGImage { + if let image = cached.with({ $0["emojiRecentTab"] }) { + return image + } else { + let image = _emojiRecentTab() + _ = cached.modify { current in + var current = current + current["emojiRecentTab"] = image + return current + } + return image + } + } + var emojiSmileTab: CGImage { + if let image = cached.with({ $0["emojiSmileTab"] }) { + return image + } else { + let image = _emojiSmileTab() + _ = cached.modify { current in + var current = current + current["emojiSmileTab"] = image + return current + } + return image + } + } + var emojiNatureTab: CGImage { + if let image = cached.with({ $0["emojiNatureTab"] }) { + return image + } else { + let image = _emojiNatureTab() + _ = cached.modify { current in + var current = current + current["emojiNatureTab"] = image + return current + } + return image + } + } + var emojiFoodTab: CGImage { + if let image = cached.with({ $0["emojiFoodTab"] }) { + return image + } else { + let image = _emojiFoodTab() + _ = cached.modify { current in + var current = current + current["emojiFoodTab"] = image + return current + } + return image + } + } + var emojiSportTab: CGImage { + if let image = cached.with({ $0["emojiSportTab"] }) { + return image + } else { + let image = _emojiSportTab() + _ = cached.modify { current in + var current = current + current["emojiSportTab"] = image + return current + } + return image + } + } + var emojiCarTab: CGImage { + if let image = cached.with({ $0["emojiCarTab"] }) { + return image + } else { + let image = _emojiCarTab() + _ = cached.modify { current in + var current = current + current["emojiCarTab"] = image + return current + } + return image + } + } + var emojiObjectsTab: CGImage { + if let image = cached.with({ $0["emojiObjectsTab"] }) { + return image + } else { + let image = _emojiObjectsTab() + _ = cached.modify { current in + var current = current + current["emojiObjectsTab"] = image + return current + } + return image + } + } + var emojiSymbolsTab: CGImage { + if let image = cached.with({ $0["emojiSymbolsTab"] }) { + return image + } else { + let image = _emojiSymbolsTab() + _ = cached.modify { current in + var current = current + current["emojiSymbolsTab"] = image + return current + } + return image + } + } + var emojiFlagsTab: CGImage { + if let image = cached.with({ $0["emojiFlagsTab"] }) { + return image + } else { + let image = _emojiFlagsTab() + _ = cached.modify { current in + var current = current + current["emojiFlagsTab"] = image + return current + } + return image + } + } + var emojiRecentTabActive: CGImage { + if let image = cached.with({ $0["emojiRecentTabActive"] }) { + return image + } else { + let image = _emojiRecentTabActive() + _ = cached.modify { current in + var current = current + current["emojiRecentTabActive"] = image + return current + } + return image + } + } + var emojiSmileTabActive: CGImage { + if let image = cached.with({ $0["emojiSmileTabActive"] }) { + return image + } else { + let image = _emojiSmileTabActive() + _ = cached.modify { current in + var current = current + current["emojiSmileTabActive"] = image + return current + } + return image + } + } + var emojiNatureTabActive: CGImage { + if let image = cached.with({ $0["emojiNatureTabActive"] }) { + return image + } else { + let image = _emojiNatureTabActive() + _ = cached.modify { current in + var current = current + current["emojiNatureTabActive"] = image + return current + } + return image + } + } + var emojiFoodTabActive: CGImage { + if let image = cached.with({ $0["emojiFoodTabActive"] }) { + return image + } else { + let image = _emojiFoodTabActive() + _ = cached.modify { current in + var current = current + current["emojiFoodTabActive"] = image + return current + } + return image + } + } + var emojiSportTabActive: CGImage { + if let image = cached.with({ $0["emojiSportTabActive"] }) { + return image + } else { + let image = _emojiSportTabActive() + _ = cached.modify { current in + var current = current + current["emojiSportTabActive"] = image + return current + } + return image + } + } + var emojiCarTabActive: CGImage { + if let image = cached.with({ $0["emojiCarTabActive"] }) { + return image + } else { + let image = _emojiCarTabActive() + _ = cached.modify { current in + var current = current + current["emojiCarTabActive"] = image + return current + } + return image + } + } + var emojiObjectsTabActive: CGImage { + if let image = cached.with({ $0["emojiObjectsTabActive"] }) { + return image + } else { + let image = _emojiObjectsTabActive() + _ = cached.modify { current in + var current = current + current["emojiObjectsTabActive"] = image + return current + } + return image + } + } + var emojiSymbolsTabActive: CGImage { + if let image = cached.with({ $0["emojiSymbolsTabActive"] }) { + return image + } else { + let image = _emojiSymbolsTabActive() + _ = cached.modify { current in + var current = current + current["emojiSymbolsTabActive"] = image + return current + } + return image + } + } + var emojiFlagsTabActive: CGImage { + if let image = cached.with({ $0["emojiFlagsTabActive"] }) { + return image + } else { + let image = _emojiFlagsTabActive() + _ = cached.modify { current in + var current = current + current["emojiFlagsTabActive"] = image + return current + } + return image + } + } + var stickerBackground: CGImage { + if let image = cached.with({ $0["stickerBackground"] }) { + return image + } else { + let image = _stickerBackground() + _ = cached.modify { current in + var current = current + current["stickerBackground"] = image + return current + } + return image + } + } + var stickerBackgroundActive: CGImage { + if let image = cached.with({ $0["stickerBackgroundActive"] }) { + return image + } else { + let image = _stickerBackgroundActive() + _ = cached.modify { current in + var current = current + current["stickerBackgroundActive"] = image + return current + } + return image + } + } + var stickersTabRecent: CGImage { + if let image = cached.with({ $0["stickersTabRecent"] }) { + return image + } else { + let image = _stickersTabRecent() + _ = cached.modify { current in + var current = current + current["stickersTabRecent"] = image + return current + } + return image + } + } + var stickersTabGIF: CGImage { + if let image = cached.with({ $0["stickersTabGIF"] }) { + return image + } else { + let image = _stickersTabGIF() + _ = cached.modify { current in + var current = current + current["stickersTabGIF"] = image + return current + } + return image + } + } + var chatSendingInFrame_incoming: CGImage { + if let image = cached.with({ $0["chatSendingInFrame_incoming"] }) { + return image + } else { + let image = _chatSendingInFrame_incoming() + _ = cached.modify { current in + var current = current + current["chatSendingInFrame_incoming"] = image + return current + } + return image + } + } + var chatSendingInHour_incoming: CGImage { + if let image = cached.with({ $0["chatSendingInHour_incoming"] }) { + return image + } else { + let image = _chatSendingInHour_incoming() + _ = cached.modify { current in + var current = current + current["chatSendingInHour_incoming"] = image + return current + } + return image + } + } + var chatSendingInMin_incoming: CGImage { + if let image = cached.with({ $0["chatSendingInMin_incoming"] }) { + return image + } else { + let image = _chatSendingInMin_incoming() + _ = cached.modify { current in + var current = current + current["chatSendingInMin_incoming"] = image + return current + } + return image + } + } + var chatSendingInFrame_outgoing: CGImage { + if let image = cached.with({ $0["chatSendingInFrame_outgoing"] }) { + return image + } else { + let image = _chatSendingInFrame_outgoing() + _ = cached.modify { current in + var current = current + current["chatSendingInFrame_outgoing"] = image + return current + } + return image + } + } + var chatSendingInHour_outgoing: CGImage { + if let image = cached.with({ $0["chatSendingInHour_outgoing"] }) { + return image + } else { + let image = _chatSendingInHour_outgoing() + _ = cached.modify { current in + var current = current + current["chatSendingInHour_outgoing"] = image + return current + } + return image + } + } + var chatSendingInMin_outgoing: CGImage { + if let image = cached.with({ $0["chatSendingInMin_outgoing"] }) { + return image + } else { + let image = _chatSendingInMin_outgoing() + _ = cached.modify { current in + var current = current + current["chatSendingInMin_outgoing"] = image + return current + } + return image + } + } + var chatSendingOutFrame: CGImage { + if let image = cached.with({ $0["chatSendingOutFrame"] }) { + return image + } else { + let image = _chatSendingOutFrame() + _ = cached.modify { current in + var current = current + current["chatSendingOutFrame"] = image + return current + } + return image + } + } + var chatSendingOutHour: CGImage { + if let image = cached.with({ $0["chatSendingOutHour"] }) { + return image + } else { + let image = _chatSendingOutHour() + _ = cached.modify { current in + var current = current + current["chatSendingOutHour"] = image + return current + } + return image + } + } + var chatSendingOutMin: CGImage { + if let image = cached.with({ $0["chatSendingOutMin"] }) { + return image + } else { + let image = _chatSendingOutMin() + _ = cached.modify { current in + var current = current + current["chatSendingOutMin"] = image + return current + } + return image + } + } + var chatSendingOverlayFrame: CGImage { + if let image = cached.with({ $0["chatSendingOverlayFrame"] }) { + return image + } else { + let image = _chatSendingOverlayFrame() + _ = cached.modify { current in + var current = current + current["chatSendingOverlayFrame"] = image + return current + } + return image + } + } + var chatSendingOverlayHour: CGImage { + if let image = cached.with({ $0["chatSendingOverlayHour"] }) { + return image + } else { + let image = _chatSendingOverlayHour() + _ = cached.modify { current in + var current = current + current["chatSendingOverlayHour"] = image + return current + } + return image + } + } + var chatSendingOverlayMin: CGImage { + if let image = cached.with({ $0["chatSendingOverlayMin"] }) { + return image + } else { + let image = _chatSendingOverlayMin() + _ = cached.modify { current in + var current = current + current["chatSendingOverlayMin"] = image + return current + } + return image + } + } + var chatActionUrl: CGImage { + if let image = cached.with({ $0["chatActionUrl"] }) { + return image + } else { + let image = _chatActionUrl() + _ = cached.modify { current in + var current = current + current["chatActionUrl"] = image + return current + } + return image + } + } + var callInlineDecline: CGImage { + if let image = cached.with({ $0["callInlineDecline"] }) { + return image + } else { + let image = _callInlineDecline() + _ = cached.modify { current in + var current = current + current["callInlineDecline"] = image + return current + } + return image + } + } + var callInlineMuted: CGImage { + if let image = cached.with({ $0["callInlineMuted"] }) { + return image + } else { + let image = _callInlineMuted() + _ = cached.modify { current in + var current = current + current["callInlineMuted"] = image + return current + } + return image + } + } + var callInlineUnmuted: CGImage { + if let image = cached.with({ $0["callInlineUnmuted"] }) { + return image + } else { + let image = _callInlineUnmuted() + _ = cached.modify { current in + var current = current + current["callInlineUnmuted"] = image + return current + } + return image + } + } + var eventLogTriangle: CGImage { + if let image = cached.with({ $0["eventLogTriangle"] }) { + return image + } else { + let image = _eventLogTriangle() + _ = cached.modify { current in + var current = current + current["eventLogTriangle"] = image + return current + } + return image + } + } + var channelIntro: CGImage { + if let image = cached.with({ $0["channelIntro"] }) { + return image + } else { + let image = _channelIntro() + _ = cached.modify { current in + var current = current + current["channelIntro"] = image + return current + } + return image + } + } + var chatFileThumb: CGImage { + if let image = cached.with({ $0["chatFileThumb"] }) { + return image + } else { + let image = _chatFileThumb() + _ = cached.modify { current in + var current = current + current["chatFileThumb"] = image + return current + } + return image + } + } + var chatFileThumbBubble_incoming: CGImage { + if let image = cached.with({ $0["chatFileThumbBubble_incoming"] }) { + return image + } else { + let image = _chatFileThumbBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatFileThumbBubble_incoming"] = image + return current + } + return image + } + } + var chatFileThumbBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatFileThumbBubble_outgoing"] }) { + return image + } else { + let image = _chatFileThumbBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatFileThumbBubble_outgoing"] = image + return current + } + return image + } + } + var chatSecretThumb: CGImage { + if let image = cached.with({ $0["chatSecretThumb"] }) { + return image + } else { + let image = _chatSecretThumb() + _ = cached.modify { current in + var current = current + current["chatSecretThumb"] = image + return current + } + return image + } + } + var chatSecretThumbSmall: CGImage { + if let image = cached.with({ $0["chatSecretThumbSmall"] }) { + return image + } else { + let image = _chatSecretThumbSmall() + _ = cached.modify { current in + var current = current + current["chatSecretThumbSmall"] = image + return current + } + return image + } + } + var chatMapPin: CGImage { + if let image = cached.with({ $0["chatMapPin"] }) { + return image + } else { + let image = _chatMapPin() + _ = cached.modify { current in + var current = current + current["chatMapPin"] = image + return current + } + return image + } + } + var chatSecretTitle: CGImage { + if let image = cached.with({ $0["chatSecretTitle"] }) { + return image + } else { + let image = _chatSecretTitle() + _ = cached.modify { current in + var current = current + current["chatSecretTitle"] = image + return current + } + return image + } + } + var emptySearch: CGImage { + if let image = cached.with({ $0["emptySearch"] }) { + return image + } else { + let image = _emptySearch() + _ = cached.modify { current in + var current = current + current["emptySearch"] = image + return current + } + return image + } + } + var calendarBack: CGImage { + if let image = cached.with({ $0["calendarBack"] }) { + return image + } else { + let image = _calendarBack() + _ = cached.modify { current in + var current = current + current["calendarBack"] = image + return current + } + return image + } + } + var calendarNext: CGImage { + if let image = cached.with({ $0["calendarNext"] }) { + return image + } else { + let image = _calendarNext() + _ = cached.modify { current in + var current = current + current["calendarNext"] = image + return current + } + return image + } + } + var calendarBackDisabled: CGImage { + if let image = cached.with({ $0["calendarBackDisabled"] }) { + return image + } else { + let image = _calendarBackDisabled() + _ = cached.modify { current in + var current = current + current["calendarBackDisabled"] = image + return current + } + return image + } + } + var calendarNextDisabled: CGImage { + if let image = cached.with({ $0["calendarNextDisabled"] }) { + return image + } else { + let image = _calendarNextDisabled() + _ = cached.modify { current in + var current = current + current["calendarNextDisabled"] = image + return current + } + return image + } + } + var newChatCamera: CGImage { + if let image = cached.with({ $0["newChatCamera"] }) { + return image + } else { + let image = _newChatCamera() + _ = cached.modify { current in + var current = current + current["newChatCamera"] = image + return current + } + return image + } + } + var peerInfoVerify: CGImage { + if let image = cached.with({ $0["peerInfoVerify"] }) { + return image + } else { + let image = _peerInfoVerify() + _ = cached.modify { current in + var current = current + current["peerInfoVerify"] = image + return current + } + return image + } + } + var peerInfoVerifyProfile: CGImage { + if let image = cached.with({ $0["peerInfoVerifyProfile"] }) { + return image + } else { + let image = _peerInfoVerifyProfile() + _ = cached.modify { current in + var current = current + current["peerInfoVerifyProfile"] = image + return current + } + return image + } + } + var peerInfoCall: CGImage { + if let image = cached.with({ $0["peerInfoCall"] }) { + return image + } else { + let image = _peerInfoCall() + _ = cached.modify { current in + var current = current + current["peerInfoCall"] = image + return current + } + return image + } + } + var callOutgoing: CGImage { + if let image = cached.with({ $0["callOutgoing"] }) { + return image + } else { + let image = _callOutgoing() + _ = cached.modify { current in + var current = current + current["callOutgoing"] = image + return current + } + return image + } + } + var recentDismiss: CGImage { + if let image = cached.with({ $0["recentDismiss"] }) { + return image + } else { + let image = _recentDismiss() + _ = cached.modify { current in + var current = current + current["recentDismiss"] = image + return current + } + return image + } + } + var recentDismissActive: CGImage { + if let image = cached.with({ $0["recentDismissActive"] }) { + return image + } else { + let image = _recentDismissActive() + _ = cached.modify { current in + var current = current + current["recentDismissActive"] = image + return current + } + return image + } + } + var webgameShare: CGImage { + if let image = cached.with({ $0["webgameShare"] }) { + return image + } else { + let image = _webgameShare() + _ = cached.modify { current in + var current = current + current["webgameShare"] = image + return current + } + return image + } + } + var chatSearchCancel: CGImage { + if let image = cached.with({ $0["chatSearchCancel"] }) { + return image + } else { + let image = _chatSearchCancel() + _ = cached.modify { current in + var current = current + current["chatSearchCancel"] = image + return current + } + return image + } + } + var chatSearchFrom: CGImage { + if let image = cached.with({ $0["chatSearchFrom"] }) { + return image + } else { + let image = _chatSearchFrom() + _ = cached.modify { current in + var current = current + current["chatSearchFrom"] = image + return current + } + return image + } + } + var callWindowDecline: CGImage { + if let image = cached.with({ $0["callWindowDecline"] }) { + return image + } else { + let image = _callWindowDecline() + _ = cached.modify { current in + var current = current + current["callWindowDecline"] = image + return current + } + return image + } + } + var callWindowAccept: CGImage { + if let image = cached.with({ $0["callWindowAccept"] }) { + return image + } else { + let image = _callWindowAccept() + _ = cached.modify { current in + var current = current + current["callWindowAccept"] = image + return current + } + return image + } + } + var callWindowMute: CGImage { + if let image = cached.with({ $0["callWindowMute"] }) { + return image + } else { + let image = _callWindowMute() + _ = cached.modify { current in + var current = current + current["callWindowMute"] = image + return current + } + return image + } + } + var callWindowUnmute: CGImage { + if let image = cached.with({ $0["callWindowUnmute"] }) { + return image + } else { + let image = _callWindowUnmute() + _ = cached.modify { current in + var current = current + current["callWindowUnmute"] = image + return current + } + return image + } + } + var callWindowClose: CGImage { + if let image = cached.with({ $0["callWindowClose"] }) { + return image + } else { + let image = _callWindowClose() + _ = cached.modify { current in + var current = current + current["callWindowClose"] = image + return current + } + return image + } + } + var callWindowDeviceSettings: CGImage { + if let image = cached.with({ $0["callWindowDeviceSettings"] }) { + return image + } else { + let image = _callWindowDeviceSettings() + _ = cached.modify { current in + var current = current + current["callWindowDeviceSettings"] = image + return current + } + return image + } + } + var callSettings: CGImage { + if let image = cached.with({ $0["callSettings"] }) { + return image + } else { + let image = _callSettings() + _ = cached.modify { current in + var current = current + current["callSettings"] = image + return current + } + return image + } + } + var callWindowCancel: CGImage { + if let image = cached.with({ $0["callWindowCancel"] }) { + return image + } else { + let image = _callWindowCancel() + _ = cached.modify { current in + var current = current + current["callWindowCancel"] = image + return current + } + return image + } + } + var chatActionEdit: CGImage { + if let image = cached.with({ $0["chatActionEdit"] }) { + return image + } else { + let image = _chatActionEdit() + _ = cached.modify { current in + var current = current + current["chatActionEdit"] = image + return current + } + return image + } + } + var chatActionInfo: CGImage { + if let image = cached.with({ $0["chatActionInfo"] }) { + return image + } else { + let image = _chatActionInfo() + _ = cached.modify { current in + var current = current + current["chatActionInfo"] = image + return current + } + return image + } + } + var chatActionMute: CGImage { + if let image = cached.with({ $0["chatActionMute"] }) { + return image + } else { + let image = _chatActionMute() + _ = cached.modify { current in + var current = current + current["chatActionMute"] = image + return current + } + return image + } + } + var chatActionUnmute: CGImage { + if let image = cached.with({ $0["chatActionUnmute"] }) { + return image + } else { + let image = _chatActionUnmute() + _ = cached.modify { current in + var current = current + current["chatActionUnmute"] = image + return current + } + return image + } + } + var chatActionClearHistory: CGImage { + if let image = cached.with({ $0["chatActionClearHistory"] }) { + return image + } else { + let image = _chatActionClearHistory() + _ = cached.modify { current in + var current = current + current["chatActionClearHistory"] = image + return current + } + return image + } + } + var chatActionDeleteChat: CGImage { + if let image = cached.with({ $0["chatActionDeleteChat"] }) { + return image + } else { + let image = _chatActionDeleteChat() + _ = cached.modify { current in + var current = current + current["chatActionDeleteChat"] = image + return current + } + return image + } + } + var dismissPinned: CGImage { + if let image = cached.with({ $0["dismissPinned"] }) { + return image + } else { + let image = _dismissPinned() + _ = cached.modify { current in + var current = current + current["dismissPinned"] = image + return current + } + return image + } + } + var chatActionsActive: CGImage { + if let image = cached.with({ $0["chatActionsActive"] }) { + return image + } else { + let image = _chatActionsActive() + _ = cached.modify { current in + var current = current + current["chatActionsActive"] = image + return current + } + return image + } + } + var chatEntertainmentSticker: CGImage { + if let image = cached.with({ $0["chatEntertainmentSticker"] }) { + return image + } else { + let image = _chatEntertainmentSticker() + _ = cached.modify { current in + var current = current + current["chatEntertainmentSticker"] = image + return current + } + return image + } + } + var chatEmpty: CGImage { + if let image = cached.with({ $0["chatEmpty"] }) { + return image + } else { + let image = _chatEmpty() + _ = cached.modify { current in + var current = current + current["chatEmpty"] = image + return current + } + return image + } + } + var stickerPackClose: CGImage { + if let image = cached.with({ $0["stickerPackClose"] }) { + return image + } else { + let image = _stickerPackClose() + _ = cached.modify { current in + var current = current + current["stickerPackClose"] = image + return current + } + return image + } + } + var stickerPackDelete: CGImage { + if let image = cached.with({ $0["stickerPackDelete"] }) { + return image + } else { + let image = _stickerPackDelete() + _ = cached.modify { current in + var current = current + current["stickerPackDelete"] = image + return current + } + return image + } + } + var modalShare: CGImage { + if let image = cached.with({ $0["modalShare"] }) { + return image + } else { + let image = _modalShare() + _ = cached.modify { current in + var current = current + current["modalShare"] = image + return current + } + return image + } + } + var modalClose: CGImage { + if let image = cached.with({ $0["modalClose"] }) { + return image + } else { + let image = _modalClose() + _ = cached.modify { current in + var current = current + current["modalClose"] = image + return current + } + return image + } + } + var ivChannelJoined: CGImage { + if let image = cached.with({ $0["ivChannelJoined"] }) { + return image + } else { + let image = _ivChannelJoined() + _ = cached.modify { current in + var current = current + current["ivChannelJoined"] = image + return current + } + return image + } + } + var chatListMention: CGImage { + if let image = cached.with({ $0["chatListMention"] }) { + return image + } else { + let image = _chatListMention() + _ = cached.modify { current in + var current = current + current["chatListMention"] = image + return current + } + return image + } + } + var chatListMentionActive: CGImage { + if let image = cached.with({ $0["chatListMentionActive"] }) { + return image + } else { + let image = _chatListMentionActive() + _ = cached.modify { current in + var current = current + current["chatListMentionActive"] = image + return current + } + return image + } + } + var chatListMentionArchived: CGImage { + if let image = cached.with({ $0["chatListMentionArchived"] }) { + return image + } else { + let image = _chatListMentionArchived() + _ = cached.modify { current in + var current = current + current["chatListMentionArchived"] = image + return current + } + return image + } + } + var chatListMentionArchivedActive: CGImage { + if let image = cached.with({ $0["chatListMentionArchivedActive"] }) { + return image + } else { + let image = _chatListMentionArchivedActive() + _ = cached.modify { current in + var current = current + current["chatListMentionArchivedActive"] = image + return current + } + return image + } + } + var chatMention: CGImage { + if let image = cached.with({ $0["chatMention"] }) { + return image + } else { + let image = _chatMention() + _ = cached.modify { current in + var current = current + current["chatMention"] = image + return current + } + return image + } + } + var chatMentionActive: CGImage { + if let image = cached.with({ $0["chatMentionActive"] }) { + return image + } else { + let image = _chatMentionActive() + _ = cached.modify { current in + var current = current + current["chatMentionActive"] = image + return current + } + return image + } + } + var sliderControl: CGImage { + if let image = cached.with({ $0["sliderControl"] }) { + return image + } else { + let image = _sliderControl() + _ = cached.modify { current in + var current = current + current["sliderControl"] = image + return current + } + return image + } + } + var sliderControlActive: CGImage { + if let image = cached.with({ $0["sliderControlActive"] }) { + return image + } else { + let image = _sliderControlActive() + _ = cached.modify { current in + var current = current + current["sliderControlActive"] = image + return current + } + return image + } + } + var stickersTabFave: CGImage { + if let image = cached.with({ $0["stickersTabFave"] }) { + return image + } else { + let image = _stickersTabFave() + _ = cached.modify { current in + var current = current + current["stickersTabFave"] = image + return current + } + return image + } + } + var chatInstantView: CGImage { + if let image = cached.with({ $0["chatInstantView"] }) { + return image + } else { + let image = _chatInstantView() + _ = cached.modify { current in + var current = current + current["chatInstantView"] = image + return current + } + return image + } + } + var chatInstantViewBubble_incoming: CGImage { + if let image = cached.with({ $0["chatInstantViewBubble_incoming"] }) { + return image + } else { + let image = _chatInstantViewBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatInstantViewBubble_incoming"] = image + return current + } + return image + } + } + var chatInstantViewBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatInstantViewBubble_outgoing"] }) { + return image + } else { + let image = _chatInstantViewBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatInstantViewBubble_outgoing"] = image + return current + } + return image + } + } + var instantViewShare: CGImage { + if let image = cached.with({ $0["instantViewShare"] }) { + return image + } else { + let image = _instantViewShare() + _ = cached.modify { current in + var current = current + current["instantViewShare"] = image + return current + } + return image + } + } + var instantViewActions: CGImage { + if let image = cached.with({ $0["instantViewActions"] }) { + return image + } else { + let image = _instantViewActions() + _ = cached.modify { current in + var current = current + current["instantViewActions"] = image + return current + } + return image + } + } + var instantViewActionsActive: CGImage { + if let image = cached.with({ $0["instantViewActionsActive"] }) { + return image + } else { + let image = _instantViewActionsActive() + _ = cached.modify { current in + var current = current + current["instantViewActionsActive"] = image + return current + } + return image + } + } + var instantViewSafari: CGImage { + if let image = cached.with({ $0["instantViewSafari"] }) { + return image + } else { + let image = _instantViewSafari() + _ = cached.modify { current in + var current = current + current["instantViewSafari"] = image + return current + } + return image + } + } + var instantViewBack: CGImage { + if let image = cached.with({ $0["instantViewBack"] }) { + return image + } else { + let image = _instantViewBack() + _ = cached.modify { current in + var current = current + current["instantViewBack"] = image + return current + } + return image + } + } + var instantViewCheck: CGImage { + if let image = cached.with({ $0["instantViewCheck"] }) { + return image + } else { + let image = _instantViewCheck() + _ = cached.modify { current in + var current = current + current["instantViewCheck"] = image + return current + } + return image + } + } + var groupStickerNotFound: CGImage { + if let image = cached.with({ $0["groupStickerNotFound"] }) { + return image + } else { + let image = _groupStickerNotFound() + _ = cached.modify { current in + var current = current + current["groupStickerNotFound"] = image + return current + } + return image + } + } + var settingsAskQuestion: CGImage { + if let image = cached.with({ $0["settingsAskQuestion"] }) { + return image + } else { + let image = _settingsAskQuestion() + _ = cached.modify { current in + var current = current + current["settingsAskQuestion"] = image + return current + } + return image + } + } + var settingsFaq: CGImage { + if let image = cached.with({ $0["settingsFaq"] }) { + return image + } else { + let image = _settingsFaq() + _ = cached.modify { current in + var current = current + current["settingsFaq"] = image + return current + } + return image + } + } + var settingsGeneral: CGImage { + if let image = cached.with({ $0["settingsGeneral"] }) { + return image + } else { + let image = _settingsGeneral() + _ = cached.modify { current in + var current = current + current["settingsGeneral"] = image + return current + } + return image + } + } + var settingsLanguage: CGImage { + if let image = cached.with({ $0["settingsLanguage"] }) { + return image + } else { + let image = _settingsLanguage() + _ = cached.modify { current in + var current = current + current["settingsLanguage"] = image + return current + } + return image + } + } + var settingsNotifications: CGImage { + if let image = cached.with({ $0["settingsNotifications"] }) { + return image + } else { + let image = _settingsNotifications() + _ = cached.modify { current in + var current = current + current["settingsNotifications"] = image + return current + } + return image + } + } + var settingsSecurity: CGImage { + if let image = cached.with({ $0["settingsSecurity"] }) { + return image + } else { + let image = _settingsSecurity() + _ = cached.modify { current in + var current = current + current["settingsSecurity"] = image + return current + } + return image + } + } + var settingsStickers: CGImage { + if let image = cached.with({ $0["settingsStickers"] }) { + return image + } else { + let image = _settingsStickers() + _ = cached.modify { current in + var current = current + current["settingsStickers"] = image + return current + } + return image + } + } + var settingsStorage: CGImage { + if let image = cached.with({ $0["settingsStorage"] }) { + return image + } else { + let image = _settingsStorage() + _ = cached.modify { current in + var current = current + current["settingsStorage"] = image + return current + } + return image + } + } + var settingsSessions: CGImage { + if let image = cached.with({ $0["settingsSessions"] }) { + return image + } else { + let image = _settingsSessions() + _ = cached.modify { current in + var current = current + current["settingsSessions"] = image + return current + } + return image + } + } + var settingsProxy: CGImage { + if let image = cached.with({ $0["settingsProxy"] }) { + return image + } else { + let image = _settingsProxy() + _ = cached.modify { current in + var current = current + current["settingsProxy"] = image + return current + } + return image + } + } + var settingsAppearance: CGImage { + if let image = cached.with({ $0["settingsAppearance"] }) { + return image + } else { + let image = _settingsAppearance() + _ = cached.modify { current in + var current = current + current["settingsAppearance"] = image + return current + } + return image + } + } + var settingsPassport: CGImage { + if let image = cached.with({ $0["settingsPassport"] }) { + return image + } else { + let image = _settingsPassport() + _ = cached.modify { current in + var current = current + current["settingsPassport"] = image + return current + } + return image + } + } + var settingsWallet: CGImage { + if let image = cached.with({ $0["settingsWallet"] }) { + return image + } else { + let image = _settingsWallet() + _ = cached.modify { current in + var current = current + current["settingsWallet"] = image + return current + } + return image + } + } + var settingsUpdate: CGImage { + if let image = cached.with({ $0["settingsUpdate"] }) { + return image + } else { + let image = _settingsUpdate() + _ = cached.modify { current in + var current = current + current["settingsUpdate"] = image + return current + } + return image + } + } + var settingsFilters: CGImage { + if let image = cached.with({ $0["settingsFilters"] }) { + return image + } else { + let image = _settingsFilters() + _ = cached.modify { current in + var current = current + current["settingsFilters"] = image + return current + } + return image + } + } + var settingsAskQuestionActive: CGImage { + if let image = cached.with({ $0["settingsAskQuestionActive"] }) { + return image + } else { + let image = _settingsAskQuestionActive() + _ = cached.modify { current in + var current = current + current["settingsAskQuestionActive"] = image + return current + } + return image + } + } + var settingsFaqActive: CGImage { + if let image = cached.with({ $0["settingsFaqActive"] }) { + return image + } else { + let image = _settingsFaqActive() + _ = cached.modify { current in + var current = current + current["settingsFaqActive"] = image + return current + } + return image + } + } + var settingsGeneralActive: CGImage { + if let image = cached.with({ $0["settingsGeneralActive"] }) { + return image + } else { + let image = _settingsGeneralActive() + _ = cached.modify { current in + var current = current + current["settingsGeneralActive"] = image + return current + } + return image + } + } + var settingsLanguageActive: CGImage { + if let image = cached.with({ $0["settingsLanguageActive"] }) { + return image + } else { + let image = _settingsLanguageActive() + _ = cached.modify { current in + var current = current + current["settingsLanguageActive"] = image + return current + } + return image + } + } + var settingsNotificationsActive: CGImage { + if let image = cached.with({ $0["settingsNotificationsActive"] }) { + return image + } else { + let image = _settingsNotificationsActive() + _ = cached.modify { current in + var current = current + current["settingsNotificationsActive"] = image + return current + } + return image + } + } + var settingsSecurityActive: CGImage { + if let image = cached.with({ $0["settingsSecurityActive"] }) { + return image + } else { + let image = _settingsSecurityActive() + _ = cached.modify { current in + var current = current + current["settingsSecurityActive"] = image + return current + } + return image + } + } + var settingsStickersActive: CGImage { + if let image = cached.with({ $0["settingsStickersActive"] }) { + return image + } else { + let image = _settingsStickersActive() + _ = cached.modify { current in + var current = current + current["settingsStickersActive"] = image + return current + } + return image + } + } + var settingsStorageActive: CGImage { + if let image = cached.with({ $0["settingsStorageActive"] }) { + return image + } else { + let image = _settingsStorageActive() + _ = cached.modify { current in + var current = current + current["settingsStorageActive"] = image + return current + } + return image + } + } + var settingsSessionsActive: CGImage { + if let image = cached.with({ $0["settingsSessionsActive"] }) { + return image + } else { + let image = _settingsSessionsActive() + _ = cached.modify { current in + var current = current + current["settingsSessionsActive"] = image + return current + } + return image + } + } + var settingsProxyActive: CGImage { + if let image = cached.with({ $0["settingsProxyActive"] }) { + return image + } else { + let image = _settingsProxyActive() + _ = cached.modify { current in + var current = current + current["settingsProxyActive"] = image + return current + } + return image + } + } + var settingsAppearanceActive: CGImage { + if let image = cached.with({ $0["settingsAppearanceActive"] }) { + return image + } else { + let image = _settingsAppearanceActive() + _ = cached.modify { current in + var current = current + current["settingsAppearanceActive"] = image + return current + } + return image + } + } + var settingsPassportActive: CGImage { + if let image = cached.with({ $0["settingsPassportActive"] }) { + return image + } else { + let image = _settingsPassportActive() + _ = cached.modify { current in + var current = current + current["settingsPassportActive"] = image + return current + } + return image + } + } + var settingsWalletActive: CGImage { + if let image = cached.with({ $0["settingsWalletActive"] }) { + return image + } else { + let image = _settingsWalletActive() + _ = cached.modify { current in + var current = current + current["settingsWalletActive"] = image + return current + } + return image + } + } + var settingsUpdateActive: CGImage { + if let image = cached.with({ $0["settingsUpdateActive"] }) { + return image + } else { + let image = _settingsUpdateActive() + _ = cached.modify { current in + var current = current + current["settingsUpdateActive"] = image + return current + } + return image + } + } + var settingsFiltersActive: CGImage { + if let image = cached.with({ $0["settingsFiltersActive"] }) { + return image + } else { + let image = _settingsFiltersActive() + _ = cached.modify { current in + var current = current + current["settingsFiltersActive"] = image + return current + } + return image + } + } + var settingsProfile: CGImage { + if let image = cached.with({ $0["settingsProfile"] }) { + return image + } else { + let image = _settingsProfile() + _ = cached.modify { current in + var current = current + current["settingsProfile"] = image + return current + } + return image + } + } + var generalCheck: CGImage { + if let image = cached.with({ $0["generalCheck"] }) { + return image + } else { + let image = _generalCheck() + _ = cached.modify { current in + var current = current + current["generalCheck"] = image + return current + } + return image + } + } + var settingsAbout: CGImage { + if let image = cached.with({ $0["settingsAbout"] }) { + return image + } else { + let image = _settingsAbout() + _ = cached.modify { current in + var current = current + current["settingsAbout"] = image + return current + } + return image + } + } + var settingsLogout: CGImage { + if let image = cached.with({ $0["settingsLogout"] }) { + return image + } else { + let image = _settingsLogout() + _ = cached.modify { current in + var current = current + current["settingsLogout"] = image + return current + } + return image + } + } + var fastSettingsLock: CGImage { + if let image = cached.with({ $0["fastSettingsLock"] }) { + return image + } else { + let image = _fastSettingsLock() + _ = cached.modify { current in + var current = current + current["fastSettingsLock"] = image + return current + } + return image + } + } + var fastSettingsDark: CGImage { + if let image = cached.with({ $0["fastSettingsDark"] }) { + return image + } else { + let image = _fastSettingsDark() + _ = cached.modify { current in + var current = current + current["fastSettingsDark"] = image + return current + } + return image + } + } + var fastSettingsSunny: CGImage { + if let image = cached.with({ $0["fastSettingsSunny"] }) { + return image + } else { + let image = _fastSettingsSunny() + _ = cached.modify { current in + var current = current + current["fastSettingsSunny"] = image + return current + } + return image + } + } + var fastSettingsMute: CGImage { + if let image = cached.with({ $0["fastSettingsMute"] }) { + return image + } else { + let image = _fastSettingsMute() + _ = cached.modify { current in + var current = current + current["fastSettingsMute"] = image + return current + } + return image + } + } + var fastSettingsUnmute: CGImage { + if let image = cached.with({ $0["fastSettingsUnmute"] }) { + return image + } else { + let image = _fastSettingsUnmute() + _ = cached.modify { current in + var current = current + current["fastSettingsUnmute"] = image + return current + } + return image + } + } + var chatRecordVideo: CGImage { + if let image = cached.with({ $0["chatRecordVideo"] }) { + return image + } else { + let image = _chatRecordVideo() + _ = cached.modify { current in + var current = current + current["chatRecordVideo"] = image + return current + } + return image + } + } + var inputChannelMute: CGImage { + if let image = cached.with({ $0["inputChannelMute"] }) { + return image + } else { + let image = _inputChannelMute() + _ = cached.modify { current in + var current = current + current["inputChannelMute"] = image + return current + } + return image + } + } + var inputChannelUnmute: CGImage { + if let image = cached.with({ $0["inputChannelUnmute"] }) { + return image + } else { + let image = _inputChannelUnmute() + _ = cached.modify { current in + var current = current + current["inputChannelUnmute"] = image + return current + } + return image + } + } + var changePhoneNumberIntro: CGImage { + if let image = cached.with({ $0["changePhoneNumberIntro"] }) { + return image + } else { + let image = _changePhoneNumberIntro() + _ = cached.modify { current in + var current = current + current["changePhoneNumberIntro"] = image + return current + } + return image + } + } + var peerSavedMessages: CGImage { + if let image = cached.with({ $0["peerSavedMessages"] }) { + return image + } else { + let image = _peerSavedMessages() + _ = cached.modify { current in + var current = current + current["peerSavedMessages"] = image + return current + } + return image + } + } + var previewSenderCollage: CGImage { + if let image = cached.with({ $0["previewSenderCollage"] }) { + return image + } else { + let image = _previewSenderCollage() + _ = cached.modify { current in + var current = current + current["previewSenderCollage"] = image + return current + } + return image + } + } + var previewSenderPhoto: CGImage { + if let image = cached.with({ $0["previewSenderPhoto"] }) { + return image + } else { + let image = _previewSenderPhoto() + _ = cached.modify { current in + var current = current + current["previewSenderPhoto"] = image + return current + } + return image + } + } + var previewSenderFile: CGImage { + if let image = cached.with({ $0["previewSenderFile"] }) { + return image + } else { + let image = _previewSenderFile() + _ = cached.modify { current in + var current = current + current["previewSenderFile"] = image + return current + } + return image + } + } + var previewSenderCrop: CGImage { + if let image = cached.with({ $0["previewSenderCrop"] }) { + return image + } else { + let image = _previewSenderCrop() + _ = cached.modify { current in + var current = current + current["previewSenderCrop"] = image + return current + } + return image + } + } + var previewSenderDelete: CGImage { + if let image = cached.with({ $0["previewSenderDelete"] }) { + return image + } else { + let image = _previewSenderDelete() + _ = cached.modify { current in + var current = current + current["previewSenderDelete"] = image + return current + } + return image + } + } + var previewSenderDeleteFile: CGImage { + if let image = cached.with({ $0["previewSenderDeleteFile"] }) { + return image + } else { + let image = _previewSenderDeleteFile() + _ = cached.modify { current in + var current = current + current["previewSenderDeleteFile"] = image + return current + } + return image + } + } + var previewSenderArchive: CGImage { + if let image = cached.with({ $0["previewSenderArchive"] }) { + return image + } else { + let image = _previewSenderArchive() + _ = cached.modify { current in + var current = current + current["previewSenderArchive"] = image + return current + } + return image + } + } + var chatGroupToggleSelected: CGImage { + if let image = cached.with({ $0["chatGroupToggleSelected"] }) { + return image + } else { + let image = _chatGroupToggleSelected() + _ = cached.modify { current in + var current = current + current["chatGroupToggleSelected"] = image + return current + } + return image + } + } + var chatGroupToggleUnselected: CGImage { + if let image = cached.with({ $0["chatGroupToggleUnselected"] }) { + return image + } else { + let image = _chatGroupToggleUnselected() + _ = cached.modify { current in + var current = current + current["chatGroupToggleUnselected"] = image + return current + } + return image + } + } + var successModalProgress: CGImage { + if let image = cached.with({ $0["successModalProgress"] }) { + return image + } else { + let image = _successModalProgress() + _ = cached.modify { current in + var current = current + current["successModalProgress"] = image + return current + } + return image + } + } + var accentColorSelect: CGImage { + if let image = cached.with({ $0["accentColorSelect"] }) { + return image + } else { + let image = _accentColorSelect() + _ = cached.modify { current in + var current = current + current["accentColorSelect"] = image + return current + } + return image + } + } + var transparentBackground: CGImage { + if let image = cached.with({ $0["transparentBackground"] }) { + return image + } else { + let image = _transparentBackground() + _ = cached.modify { current in + var current = current + current["transparentBackground"] = image + return current + } + return image + } + } + var lottieTransparentBackground: CGImage { + if let image = cached.with({ $0["lottieTransparentBackground"] }) { + return image + } else { + let image = _lottieTransparentBackground() + _ = cached.modify { current in + var current = current + current["lottieTransparentBackground"] = image + return current + } + return image + } + } + var passcodeTouchId: CGImage { + if let image = cached.with({ $0["passcodeTouchId"] }) { + return image + } else { + let image = _passcodeTouchId() + _ = cached.modify { current in + var current = current + current["passcodeTouchId"] = image + return current + } + return image + } + } + var passcodeLogin: CGImage { + if let image = cached.with({ $0["passcodeLogin"] }) { + return image + } else { + let image = _passcodeLogin() + _ = cached.modify { current in + var current = current + current["passcodeLogin"] = image + return current + } + return image + } + } + var confirmDeleteMessagesAccessory: CGImage { + if let image = cached.with({ $0["confirmDeleteMessagesAccessory"] }) { + return image + } else { + let image = _confirmDeleteMessagesAccessory() + _ = cached.modify { current in + var current = current + current["confirmDeleteMessagesAccessory"] = image + return current + } + return image + } + } + var alertCheckBoxSelected: CGImage { + if let image = cached.with({ $0["alertCheckBoxSelected"] }) { + return image + } else { + let image = _alertCheckBoxSelected() + _ = cached.modify { current in + var current = current + current["alertCheckBoxSelected"] = image + return current + } + return image + } + } + var alertCheckBoxUnselected: CGImage { + if let image = cached.with({ $0["alertCheckBoxUnselected"] }) { + return image + } else { + let image = _alertCheckBoxUnselected() + _ = cached.modify { current in + var current = current + current["alertCheckBoxUnselected"] = image + return current + } + return image + } + } + var confirmPinAccessory: CGImage { + if let image = cached.with({ $0["confirmPinAccessory"] }) { + return image + } else { + let image = _confirmPinAccessory() + _ = cached.modify { current in + var current = current + current["confirmPinAccessory"] = image + return current + } + return image + } + } + var confirmDeleteChatAccessory: CGImage { + if let image = cached.with({ $0["confirmDeleteChatAccessory"] }) { + return image + } else { + let image = _confirmDeleteChatAccessory() + _ = cached.modify { current in + var current = current + current["confirmDeleteChatAccessory"] = image + return current + } + return image + } + } + var stickersEmptySearch: CGImage { + if let image = cached.with({ $0["stickersEmptySearch"] }) { + return image + } else { + let image = _stickersEmptySearch() + _ = cached.modify { current in + var current = current + current["stickersEmptySearch"] = image + return current + } + return image + } + } + var twoStepVerificationCreateIntro: CGImage { + if let image = cached.with({ $0["twoStepVerificationCreateIntro"] }) { + return image + } else { + let image = _twoStepVerificationCreateIntro() + _ = cached.modify { current in + var current = current + current["twoStepVerificationCreateIntro"] = image + return current + } + return image + } + } + var secureIdAuth: CGImage { + if let image = cached.with({ $0["secureIdAuth"] }) { + return image + } else { + let image = _secureIdAuth() + _ = cached.modify { current in + var current = current + current["secureIdAuth"] = image + return current + } + return image + } + } + var ivAudioPlay: CGImage { + if let image = cached.with({ $0["ivAudioPlay"] }) { + return image + } else { + let image = _ivAudioPlay() + _ = cached.modify { current in + var current = current + current["ivAudioPlay"] = image + return current + } + return image + } + } + var ivAudioPause: CGImage { + if let image = cached.with({ $0["ivAudioPause"] }) { + return image + } else { + let image = _ivAudioPause() + _ = cached.modify { current in + var current = current + current["ivAudioPause"] = image + return current + } + return image + } + } + var proxyEnable: CGImage { + if let image = cached.with({ $0["proxyEnable"] }) { + return image + } else { + let image = _proxyEnable() + _ = cached.modify { current in + var current = current + current["proxyEnable"] = image + return current + } + return image + } + } + var proxyEnabled: CGImage { + if let image = cached.with({ $0["proxyEnabled"] }) { + return image + } else { + let image = _proxyEnabled() + _ = cached.modify { current in + var current = current + current["proxyEnabled"] = image + return current + } + return image + } + } + var proxyState: CGImage { + if let image = cached.with({ $0["proxyState"] }) { + return image + } else { + let image = _proxyState() + _ = cached.modify { current in + var current = current + current["proxyState"] = image + return current + } + return image + } + } + var proxyDeleteListItem: CGImage { + if let image = cached.with({ $0["proxyDeleteListItem"] }) { + return image + } else { + let image = _proxyDeleteListItem() + _ = cached.modify { current in + var current = current + current["proxyDeleteListItem"] = image + return current + } + return image + } + } + var proxyInfoListItem: CGImage { + if let image = cached.with({ $0["proxyInfoListItem"] }) { + return image + } else { + let image = _proxyInfoListItem() + _ = cached.modify { current in + var current = current + current["proxyInfoListItem"] = image + return current + } + return image + } + } + var proxyConnectedListItem: CGImage { + if let image = cached.with({ $0["proxyConnectedListItem"] }) { + return image + } else { + let image = _proxyConnectedListItem() + _ = cached.modify { current in + var current = current + current["proxyConnectedListItem"] = image + return current + } + return image + } + } + var proxyAddProxy: CGImage { + if let image = cached.with({ $0["proxyAddProxy"] }) { + return image + } else { + let image = _proxyAddProxy() + _ = cached.modify { current in + var current = current + current["proxyAddProxy"] = image + return current + } + return image + } + } + var proxyNextWaitingListItem: CGImage { + if let image = cached.with({ $0["proxyNextWaitingListItem"] }) { + return image + } else { + let image = _proxyNextWaitingListItem() + _ = cached.modify { current in + var current = current + current["proxyNextWaitingListItem"] = image + return current + } + return image + } + } + var passportForgotPassword: CGImage { + if let image = cached.with({ $0["passportForgotPassword"] }) { + return image + } else { + let image = _passportForgotPassword() + _ = cached.modify { current in + var current = current + current["passportForgotPassword"] = image + return current + } + return image + } + } + var confirmAppAccessoryIcon: CGImage { + if let image = cached.with({ $0["confirmAppAccessoryIcon"] }) { + return image + } else { + let image = _confirmAppAccessoryIcon() + _ = cached.modify { current in + var current = current + current["confirmAppAccessoryIcon"] = image + return current + } + return image + } + } + var passportPassport: CGImage { + if let image = cached.with({ $0["passportPassport"] }) { + return image + } else { + let image = _passportPassport() + _ = cached.modify { current in + var current = current + current["passportPassport"] = image + return current + } + return image + } + } + var passportIdCardReverse: CGImage { + if let image = cached.with({ $0["passportIdCardReverse"] }) { + return image + } else { + let image = _passportIdCardReverse() + _ = cached.modify { current in + var current = current + current["passportIdCardReverse"] = image + return current + } + return image + } + } + var passportIdCard: CGImage { + if let image = cached.with({ $0["passportIdCard"] }) { + return image + } else { + let image = _passportIdCard() + _ = cached.modify { current in + var current = current + current["passportIdCard"] = image + return current + } + return image + } + } + var passportSelfie: CGImage { + if let image = cached.with({ $0["passportSelfie"] }) { + return image + } else { + let image = _passportSelfie() + _ = cached.modify { current in + var current = current + current["passportSelfie"] = image + return current + } + return image + } + } + var passportDriverLicense: CGImage { + if let image = cached.with({ $0["passportDriverLicense"] }) { + return image + } else { + let image = _passportDriverLicense() + _ = cached.modify { current in + var current = current + current["passportDriverLicense"] = image + return current + } + return image + } + } + var chatOverlayVoiceRecording: CGImage { + if let image = cached.with({ $0["chatOverlayVoiceRecording"] }) { + return image + } else { + let image = _chatOverlayVoiceRecording() + _ = cached.modify { current in + var current = current + current["chatOverlayVoiceRecording"] = image + return current + } + return image + } + } + var chatOverlayVideoRecording: CGImage { + if let image = cached.with({ $0["chatOverlayVideoRecording"] }) { + return image + } else { + let image = _chatOverlayVideoRecording() + _ = cached.modify { current in + var current = current + current["chatOverlayVideoRecording"] = image + return current + } + return image + } + } + var chatOverlaySendRecording: CGImage { + if let image = cached.with({ $0["chatOverlaySendRecording"] }) { + return image + } else { + let image = _chatOverlaySendRecording() + _ = cached.modify { current in + var current = current + current["chatOverlaySendRecording"] = image + return current + } + return image + } + } + var chatOverlayLockArrowRecording: CGImage { + if let image = cached.with({ $0["chatOverlayLockArrowRecording"] }) { + return image + } else { + let image = _chatOverlayLockArrowRecording() + _ = cached.modify { current in + var current = current + current["chatOverlayLockArrowRecording"] = image + return current + } + return image + } + } + var chatOverlayLockerBodyRecording: CGImage { + if let image = cached.with({ $0["chatOverlayLockerBodyRecording"] }) { + return image + } else { + let image = _chatOverlayLockerBodyRecording() + _ = cached.modify { current in + var current = current + current["chatOverlayLockerBodyRecording"] = image + return current + } + return image + } + } + var chatOverlayLockerHeadRecording: CGImage { + if let image = cached.with({ $0["chatOverlayLockerHeadRecording"] }) { + return image + } else { + let image = _chatOverlayLockerHeadRecording() + _ = cached.modify { current in + var current = current + current["chatOverlayLockerHeadRecording"] = image + return current + } + return image + } + } + var locationPin: CGImage { + if let image = cached.with({ $0["locationPin"] }) { + return image + } else { + let image = _locationPin() + _ = cached.modify { current in + var current = current + current["locationPin"] = image + return current + } + return image + } + } + var locationMapPin: CGImage { + if let image = cached.with({ $0["locationMapPin"] }) { + return image + } else { + let image = _locationMapPin() + _ = cached.modify { current in + var current = current + current["locationMapPin"] = image + return current + } + return image + } + } + var locationMapLocate: CGImage { + if let image = cached.with({ $0["locationMapLocate"] }) { + return image + } else { + let image = _locationMapLocate() + _ = cached.modify { current in + var current = current + current["locationMapLocate"] = image + return current + } + return image + } + } + var locationMapLocated: CGImage { + if let image = cached.with({ $0["locationMapLocated"] }) { + return image + } else { + let image = _locationMapLocated() + _ = cached.modify { current in + var current = current + current["locationMapLocated"] = image + return current + } + return image + } + } + var passportSettings: CGImage { + if let image = cached.with({ $0["passportSettings"] }) { + return image + } else { + let image = _passportSettings() + _ = cached.modify { current in + var current = current + current["passportSettings"] = image + return current + } + return image + } + } + var passportInfo: CGImage { + if let image = cached.with({ $0["passportInfo"] }) { + return image + } else { + let image = _passportInfo() + _ = cached.modify { current in + var current = current + current["passportInfo"] = image + return current + } + return image + } + } + var editMessageMedia: CGImage { + if let image = cached.with({ $0["editMessageMedia"] }) { + return image + } else { + let image = _editMessageMedia() + _ = cached.modify { current in + var current = current + current["editMessageMedia"] = image + return current + } + return image + } + } + var playerMusicPlaceholder: CGImage { + if let image = cached.with({ $0["playerMusicPlaceholder"] }) { + return image + } else { + let image = _playerMusicPlaceholder() + _ = cached.modify { current in + var current = current + current["playerMusicPlaceholder"] = image + return current + } + return image + } + } + var chatMusicPlaceholder: CGImage { + if let image = cached.with({ $0["chatMusicPlaceholder"] }) { + return image + } else { + let image = _chatMusicPlaceholder() + _ = cached.modify { current in + var current = current + current["chatMusicPlaceholder"] = image + return current + } + return image + } + } + var chatMusicPlaceholderCap: CGImage { + if let image = cached.with({ $0["chatMusicPlaceholderCap"] }) { + return image + } else { + let image = _chatMusicPlaceholderCap() + _ = cached.modify { current in + var current = current + current["chatMusicPlaceholderCap"] = image + return current + } + return image + } + } + var searchArticle: CGImage { + if let image = cached.with({ $0["searchArticle"] }) { + return image + } else { + let image = _searchArticle() + _ = cached.modify { current in + var current = current + current["searchArticle"] = image + return current + } + return image + } + } + var searchSaved: CGImage { + if let image = cached.with({ $0["searchSaved"] }) { + return image + } else { + let image = _searchSaved() + _ = cached.modify { current in + var current = current + current["searchSaved"] = image + return current + } + return image + } + } + var archivedChats: CGImage { + if let image = cached.with({ $0["archivedChats"] }) { + return image + } else { + let image = _archivedChats() + _ = cached.modify { current in + var current = current + current["archivedChats"] = image + return current + } + return image + } + } + var hintPeerActive: CGImage { + if let image = cached.with({ $0["hintPeerActive"] }) { + return image + } else { + let image = _hintPeerActive() + _ = cached.modify { current in + var current = current + current["hintPeerActive"] = image + return current + } + return image + } + } + var hintPeerActiveSelected: CGImage { + if let image = cached.with({ $0["hintPeerActiveSelected"] }) { + return image + } else { + let image = _hintPeerActiveSelected() + _ = cached.modify { current in + var current = current + current["hintPeerActiveSelected"] = image + return current + } + return image + } + } + var chatSwiping_delete: CGImage { + if let image = cached.with({ $0["chatSwiping_delete"] }) { + return image + } else { + let image = _chatSwiping_delete() + _ = cached.modify { current in + var current = current + current["chatSwiping_delete"] = image + return current + } + return image + } + } + var chatSwiping_mute: CGImage { + if let image = cached.with({ $0["chatSwiping_mute"] }) { + return image + } else { + let image = _chatSwiping_mute() + _ = cached.modify { current in + var current = current + current["chatSwiping_mute"] = image + return current + } + return image + } + } + var chatSwiping_unmute: CGImage { + if let image = cached.with({ $0["chatSwiping_unmute"] }) { + return image + } else { + let image = _chatSwiping_unmute() + _ = cached.modify { current in + var current = current + current["chatSwiping_unmute"] = image + return current + } + return image + } + } + var chatSwiping_read: CGImage { + if let image = cached.with({ $0["chatSwiping_read"] }) { + return image + } else { + let image = _chatSwiping_read() + _ = cached.modify { current in + var current = current + current["chatSwiping_read"] = image + return current + } + return image + } + } + var chatSwiping_unread: CGImage { + if let image = cached.with({ $0["chatSwiping_unread"] }) { + return image + } else { + let image = _chatSwiping_unread() + _ = cached.modify { current in + var current = current + current["chatSwiping_unread"] = image + return current + } + return image + } + } + var chatSwiping_pin: CGImage { + if let image = cached.with({ $0["chatSwiping_pin"] }) { + return image + } else { + let image = _chatSwiping_pin() + _ = cached.modify { current in + var current = current + current["chatSwiping_pin"] = image + return current + } + return image + } + } + var chatSwiping_unpin: CGImage { + if let image = cached.with({ $0["chatSwiping_unpin"] }) { + return image + } else { + let image = _chatSwiping_unpin() + _ = cached.modify { current in + var current = current + current["chatSwiping_unpin"] = image + return current + } + return image + } + } + var chatSwiping_archive: CGImage { + if let image = cached.with({ $0["chatSwiping_archive"] }) { + return image + } else { + let image = _chatSwiping_archive() + _ = cached.modify { current in + var current = current + current["chatSwiping_archive"] = image + return current + } + return image + } + } + var chatSwiping_unarchive: CGImage { + if let image = cached.with({ $0["chatSwiping_unarchive"] }) { + return image + } else { + let image = _chatSwiping_unarchive() + _ = cached.modify { current in + var current = current + current["chatSwiping_unarchive"] = image + return current + } + return image + } + } + var galleryPrev: CGImage { + if let image = cached.with({ $0["galleryPrev"] }) { + return image + } else { + let image = _galleryPrev() + _ = cached.modify { current in + var current = current + current["galleryPrev"] = image + return current + } + return image + } + } + var galleryNext: CGImage { + if let image = cached.with({ $0["galleryNext"] }) { + return image + } else { + let image = _galleryNext() + _ = cached.modify { current in + var current = current + current["galleryNext"] = image + return current + } + return image + } + } + var galleryMore: CGImage { + if let image = cached.with({ $0["galleryMore"] }) { + return image + } else { + let image = _galleryMore() + _ = cached.modify { current in + var current = current + current["galleryMore"] = image + return current + } + return image + } + } + var galleryShare: CGImage { + if let image = cached.with({ $0["galleryShare"] }) { + return image + } else { + let image = _galleryShare() + _ = cached.modify { current in + var current = current + current["galleryShare"] = image + return current + } + return image + } + } + var galleryFastSave: CGImage { + if let image = cached.with({ $0["galleryFastSave"] }) { + return image + } else { + let image = _galleryFastSave() + _ = cached.modify { current in + var current = current + current["galleryFastSave"] = image + return current + } + return image + } + } + var playingVoice1x: CGImage { + if let image = cached.with({ $0["playingVoice1x"] }) { + return image + } else { + let image = _playingVoice1x() + _ = cached.modify { current in + var current = current + current["playingVoice1x"] = image + return current + } + return image + } + } + var playingVoice2x: CGImage { + if let image = cached.with({ $0["playingVoice2x"] }) { + return image + } else { + let image = _playingVoice2x() + _ = cached.modify { current in + var current = current + current["playingVoice2x"] = image + return current + } + return image + } + } + var galleryRotate: CGImage { + if let image = cached.with({ $0["galleryRotate"] }) { + return image + } else { + let image = _galleryRotate() + _ = cached.modify { current in + var current = current + current["galleryRotate"] = image + return current + } + return image + } + } + var galleryZoomIn: CGImage { + if let image = cached.with({ $0["galleryZoomIn"] }) { + return image + } else { + let image = _galleryZoomIn() + _ = cached.modify { current in + var current = current + current["galleryZoomIn"] = image + return current + } + return image + } + } + var galleryZoomOut: CGImage { + if let image = cached.with({ $0["galleryZoomOut"] }) { + return image + } else { + let image = _galleryZoomOut() + _ = cached.modify { current in + var current = current + current["galleryZoomOut"] = image + return current + } + return image + } + } + var editMessageCurrentPhoto: CGImage { + if let image = cached.with({ $0["editMessageCurrentPhoto"] }) { + return image + } else { + let image = _editMessageCurrentPhoto() + _ = cached.modify { current in + var current = current + current["editMessageCurrentPhoto"] = image + return current + } + return image + } + } + var videoPlayerPlay: CGImage { + if let image = cached.with({ $0["videoPlayerPlay"] }) { + return image + } else { + let image = _videoPlayerPlay() + _ = cached.modify { current in + var current = current + current["videoPlayerPlay"] = image + return current + } + return image + } + } + var videoPlayerPause: CGImage { + if let image = cached.with({ $0["videoPlayerPause"] }) { + return image + } else { + let image = _videoPlayerPause() + _ = cached.modify { current in + var current = current + current["videoPlayerPause"] = image + return current + } + return image + } + } + var videoPlayerEnterFullScreen: CGImage { + if let image = cached.with({ $0["videoPlayerEnterFullScreen"] }) { + return image + } else { + let image = _videoPlayerEnterFullScreen() + _ = cached.modify { current in + var current = current + current["videoPlayerEnterFullScreen"] = image + return current + } + return image + } + } + var videoPlayerExitFullScreen: CGImage { + if let image = cached.with({ $0["videoPlayerExitFullScreen"] }) { + return image + } else { + let image = _videoPlayerExitFullScreen() + _ = cached.modify { current in + var current = current + current["videoPlayerExitFullScreen"] = image + return current + } + return image + } + } + var videoPlayerPIPIn: CGImage { + if let image = cached.with({ $0["videoPlayerPIPIn"] }) { + return image + } else { + let image = _videoPlayerPIPIn() + _ = cached.modify { current in + var current = current + current["videoPlayerPIPIn"] = image + return current + } + return image + } + } + var videoPlayerPIPOut: CGImage { + if let image = cached.with({ $0["videoPlayerPIPOut"] }) { + return image + } else { + let image = _videoPlayerPIPOut() + _ = cached.modify { current in + var current = current + current["videoPlayerPIPOut"] = image + return current + } + return image + } + } + var videoPlayerRewind15Forward: CGImage { + if let image = cached.with({ $0["videoPlayerRewind15Forward"] }) { + return image + } else { + let image = _videoPlayerRewind15Forward() + _ = cached.modify { current in + var current = current + current["videoPlayerRewind15Forward"] = image + return current + } + return image + } + } + var videoPlayerRewind15Backward: CGImage { + if let image = cached.with({ $0["videoPlayerRewind15Backward"] }) { + return image + } else { + let image = _videoPlayerRewind15Backward() + _ = cached.modify { current in + var current = current + current["videoPlayerRewind15Backward"] = image + return current + } + return image + } + } + var videoPlayerVolume: CGImage { + if let image = cached.with({ $0["videoPlayerVolume"] }) { + return image + } else { + let image = _videoPlayerVolume() + _ = cached.modify { current in + var current = current + current["videoPlayerVolume"] = image + return current + } + return image + } + } + var videoPlayerVolumeOff: CGImage { + if let image = cached.with({ $0["videoPlayerVolumeOff"] }) { + return image + } else { + let image = _videoPlayerVolumeOff() + _ = cached.modify { current in + var current = current + current["videoPlayerVolumeOff"] = image + return current + } + return image + } + } + var videoPlayerClose: CGImage { + if let image = cached.with({ $0["videoPlayerClose"] }) { + return image + } else { + let image = _videoPlayerClose() + _ = cached.modify { current in + var current = current + current["videoPlayerClose"] = image + return current + } + return image + } + } + var videoPlayerSliderInteractor: CGImage { + if let image = cached.with({ $0["videoPlayerSliderInteractor"] }) { + return image + } else { + let image = _videoPlayerSliderInteractor() + _ = cached.modify { current in + var current = current + current["videoPlayerSliderInteractor"] = image + return current + } + return image + } + } + var streamingVideoDownload: CGImage { + if let image = cached.with({ $0["streamingVideoDownload"] }) { + return image + } else { + let image = _streamingVideoDownload() + _ = cached.modify { current in + var current = current + current["streamingVideoDownload"] = image + return current + } + return image + } + } + var videoCompactFetching: CGImage { + if let image = cached.with({ $0["videoCompactFetching"] }) { + return image + } else { + let image = _videoCompactFetching() + _ = cached.modify { current in + var current = current + current["videoCompactFetching"] = image + return current + } + return image + } + } + var compactStreamingFetchingCancel: CGImage { + if let image = cached.with({ $0["compactStreamingFetchingCancel"] }) { + return image + } else { + let image = _compactStreamingFetchingCancel() + _ = cached.modify { current in + var current = current + current["compactStreamingFetchingCancel"] = image + return current + } + return image + } + } + var customLocalizationDelete: CGImage { + if let image = cached.with({ $0["customLocalizationDelete"] }) { + return image + } else { + let image = _customLocalizationDelete() + _ = cached.modify { current in + var current = current + current["customLocalizationDelete"] = image + return current + } + return image + } + } + var pollAddOption: CGImage { + if let image = cached.with({ $0["pollAddOption"] }) { + return image + } else { + let image = _pollAddOption() + _ = cached.modify { current in + var current = current + current["pollAddOption"] = image + return current + } + return image + } + } + var pollDeleteOption: CGImage { + if let image = cached.with({ $0["pollDeleteOption"] }) { + return image + } else { + let image = _pollDeleteOption() + _ = cached.modify { current in + var current = current + current["pollDeleteOption"] = image + return current + } + return image + } + } + var resort: CGImage { + if let image = cached.with({ $0["resort"] }) { + return image + } else { + let image = _resort() + _ = cached.modify { current in + var current = current + current["resort"] = image + return current + } + return image + } + } + var chatPollVoteUnselected: CGImage { + if let image = cached.with({ $0["chatPollVoteUnselected"] }) { + return image + } else { + let image = _chatPollVoteUnselected() + _ = cached.modify { current in + var current = current + current["chatPollVoteUnselected"] = image + return current + } + return image + } + } + var chatPollVoteUnselectedBubble_incoming: CGImage { + if let image = cached.with({ $0["chatPollVoteUnselectedBubble_incoming"] }) { + return image + } else { + let image = _chatPollVoteUnselectedBubble_incoming() + _ = cached.modify { current in + var current = current + current["chatPollVoteUnselectedBubble_incoming"] = image + return current + } + return image + } + } + var chatPollVoteUnselectedBubble_outgoing: CGImage { + if let image = cached.with({ $0["chatPollVoteUnselectedBubble_outgoing"] }) { + return image + } else { + let image = _chatPollVoteUnselectedBubble_outgoing() + _ = cached.modify { current in + var current = current + current["chatPollVoteUnselectedBubble_outgoing"] = image + return current + } + return image + } + } + var peerInfoAdmins: CGImage { + if let image = cached.with({ $0["peerInfoAdmins"] }) { + return image + } else { + let image = _peerInfoAdmins() + _ = cached.modify { current in + var current = current + current["peerInfoAdmins"] = image + return current + } + return image + } + } + var peerInfoPermissions: CGImage { + if let image = cached.with({ $0["peerInfoPermissions"] }) { + return image + } else { + let image = _peerInfoPermissions() + _ = cached.modify { current in + var current = current + current["peerInfoPermissions"] = image + return current + } + return image + } + } + var peerInfoBanned: CGImage { + if let image = cached.with({ $0["peerInfoBanned"] }) { + return image + } else { + let image = _peerInfoBanned() + _ = cached.modify { current in + var current = current + current["peerInfoBanned"] = image + return current + } + return image + } + } + var peerInfoMembers: CGImage { + if let image = cached.with({ $0["peerInfoMembers"] }) { + return image + } else { + let image = _peerInfoMembers() + _ = cached.modify { current in + var current = current + current["peerInfoMembers"] = image + return current + } + return image + } + } + var chatUndoAction: CGImage { + if let image = cached.with({ $0["chatUndoAction"] }) { + return image + } else { + let image = _chatUndoAction() + _ = cached.modify { current in + var current = current + current["chatUndoAction"] = image + return current + } + return image + } + } + var appUpdate: CGImage { + if let image = cached.with({ $0["appUpdate"] }) { + return image + } else { + let image = _appUpdate() + _ = cached.modify { current in + var current = current + current["appUpdate"] = image + return current + } + return image + } + } + var inlineVideoSoundOff: CGImage { + if let image = cached.with({ $0["inlineVideoSoundOff"] }) { + return image + } else { + let image = _inlineVideoSoundOff() + _ = cached.modify { current in + var current = current + current["inlineVideoSoundOff"] = image + return current + } + return image + } + } + var inlineVideoSoundOn: CGImage { + if let image = cached.with({ $0["inlineVideoSoundOn"] }) { + return image + } else { + let image = _inlineVideoSoundOn() + _ = cached.modify { current in + var current = current + current["inlineVideoSoundOn"] = image + return current + } + return image + } + } + var logoutOptionAddAccount: CGImage { + if let image = cached.with({ $0["logoutOptionAddAccount"] }) { + return image + } else { + let image = _logoutOptionAddAccount() + _ = cached.modify { current in + var current = current + current["logoutOptionAddAccount"] = image + return current + } + return image + } + } + var logoutOptionSetPasscode: CGImage { + if let image = cached.with({ $0["logoutOptionSetPasscode"] }) { + return image + } else { + let image = _logoutOptionSetPasscode() + _ = cached.modify { current in + var current = current + current["logoutOptionSetPasscode"] = image + return current + } + return image + } + } + var logoutOptionClearCache: CGImage { + if let image = cached.with({ $0["logoutOptionClearCache"] }) { + return image + } else { + let image = _logoutOptionClearCache() + _ = cached.modify { current in + var current = current + current["logoutOptionClearCache"] = image + return current + } + return image + } + } + var logoutOptionChangePhoneNumber: CGImage { + if let image = cached.with({ $0["logoutOptionChangePhoneNumber"] }) { + return image + } else { + let image = _logoutOptionChangePhoneNumber() + _ = cached.modify { current in + var current = current + current["logoutOptionChangePhoneNumber"] = image + return current + } + return image + } + } + var logoutOptionContactSupport: CGImage { + if let image = cached.with({ $0["logoutOptionContactSupport"] }) { + return image + } else { + let image = _logoutOptionContactSupport() + _ = cached.modify { current in + var current = current + current["logoutOptionContactSupport"] = image + return current + } + return image + } + } + var disableEmojiPrediction: CGImage { + if let image = cached.with({ $0["disableEmojiPrediction"] }) { + return image + } else { + let image = _disableEmojiPrediction() + _ = cached.modify { current in + var current = current + current["disableEmojiPrediction"] = image + return current + } + return image + } + } + var scam: CGImage { + if let image = cached.with({ $0["scam"] }) { + return image + } else { + let image = _scam() + _ = cached.modify { current in + var current = current + current["scam"] = image + return current + } + return image + } + } + var scamActive: CGImage { + if let image = cached.with({ $0["scamActive"] }) { + return image + } else { + let image = _scamActive() + _ = cached.modify { current in + var current = current + current["scamActive"] = image + return current + } + return image + } + } + var chatScam: CGImage { + if let image = cached.with({ $0["chatScam"] }) { + return image + } else { + let image = _chatScam() + _ = cached.modify { current in + var current = current + current["chatScam"] = image + return current + } + return image + } + } + var chatUnarchive: CGImage { + if let image = cached.with({ $0["chatUnarchive"] }) { + return image + } else { + let image = _chatUnarchive() + _ = cached.modify { current in + var current = current + current["chatUnarchive"] = image + return current + } + return image + } + } + var chatArchive: CGImage { + if let image = cached.with({ $0["chatArchive"] }) { + return image + } else { + let image = _chatArchive() + _ = cached.modify { current in + var current = current + current["chatArchive"] = image + return current + } + return image + } + } + var privacySettings_blocked: CGImage { + if let image = cached.with({ $0["privacySettings_blocked"] }) { + return image + } else { + let image = _privacySettings_blocked() + _ = cached.modify { current in + var current = current + current["privacySettings_blocked"] = image + return current + } + return image + } + } + var privacySettings_activeSessions: CGImage { + if let image = cached.with({ $0["privacySettings_activeSessions"] }) { + return image + } else { + let image = _privacySettings_activeSessions() + _ = cached.modify { current in + var current = current + current["privacySettings_activeSessions"] = image + return current + } + return image + } + } + var privacySettings_passcode: CGImage { + if let image = cached.with({ $0["privacySettings_passcode"] }) { + return image + } else { + let image = _privacySettings_passcode() + _ = cached.modify { current in + var current = current + current["privacySettings_passcode"] = image + return current + } + return image + } + } + var privacySettings_twoStep: CGImage { + if let image = cached.with({ $0["privacySettings_twoStep"] }) { + return image + } else { + let image = _privacySettings_twoStep() + _ = cached.modify { current in + var current = current + current["privacySettings_twoStep"] = image + return current + } + return image + } + } + var deletedAccount: CGImage { + if let image = cached.with({ $0["deletedAccount"] }) { + return image + } else { + let image = _deletedAccount() + _ = cached.modify { current in + var current = current + current["deletedAccount"] = image + return current + } + return image + } + } + var stickerPackSelection: CGImage { + if let image = cached.with({ $0["stickerPackSelection"] }) { + return image + } else { + let image = _stickerPackSelection() + _ = cached.modify { current in + var current = current + current["stickerPackSelection"] = image + return current + } + return image + } + } + var stickerPackSelectionActive: CGImage { + if let image = cached.with({ $0["stickerPackSelectionActive"] }) { + return image + } else { + let image = _stickerPackSelectionActive() + _ = cached.modify { current in + var current = current + current["stickerPackSelectionActive"] = image + return current + } + return image + } + } + var entertainment_Emoji: CGImage { + if let image = cached.with({ $0["entertainment_Emoji"] }) { + return image + } else { + let image = _entertainment_Emoji() + _ = cached.modify { current in + var current = current + current["entertainment_Emoji"] = image + return current + } + return image + } + } + var entertainment_Stickers: CGImage { + if let image = cached.with({ $0["entertainment_Stickers"] }) { + return image + } else { + let image = _entertainment_Stickers() + _ = cached.modify { current in + var current = current + current["entertainment_Stickers"] = image + return current + } + return image + } + } + var entertainment_Gifs: CGImage { + if let image = cached.with({ $0["entertainment_Gifs"] }) { + return image + } else { + let image = _entertainment_Gifs() + _ = cached.modify { current in + var current = current + current["entertainment_Gifs"] = image + return current + } + return image + } + } + var entertainment_Search: CGImage { + if let image = cached.with({ $0["entertainment_Search"] }) { + return image + } else { + let image = _entertainment_Search() + _ = cached.modify { current in + var current = current + current["entertainment_Search"] = image + return current + } + return image + } + } + var entertainment_Settings: CGImage { + if let image = cached.with({ $0["entertainment_Settings"] }) { + return image + } else { + let image = _entertainment_Settings() + _ = cached.modify { current in + var current = current + current["entertainment_Settings"] = image + return current + } + return image + } + } + var entertainment_SearchCancel: CGImage { + if let image = cached.with({ $0["entertainment_SearchCancel"] }) { + return image + } else { + let image = _entertainment_SearchCancel() + _ = cached.modify { current in + var current = current + current["entertainment_SearchCancel"] = image + return current + } + return image + } + } + var scheduledAvatar: CGImage { + if let image = cached.with({ $0["scheduledAvatar"] }) { + return image + } else { + let image = _scheduledAvatar() + _ = cached.modify { current in + var current = current + current["scheduledAvatar"] = image + return current + } + return image + } + } + var scheduledInputAction: CGImage { + if let image = cached.with({ $0["scheduledInputAction"] }) { + return image + } else { + let image = _scheduledInputAction() + _ = cached.modify { current in + var current = current + current["scheduledInputAction"] = image + return current + } + return image + } + } + var verifyDialog: CGImage { + if let image = cached.with({ $0["verifyDialog"] }) { + return image + } else { + let image = _verifyDialog() + _ = cached.modify { current in + var current = current + current["verifyDialog"] = image + return current + } + return image + } + } + var verifyDialogActive: CGImage { + if let image = cached.with({ $0["verifyDialogActive"] }) { + return image + } else { + let image = _verifyDialogActive() + _ = cached.modify { current in + var current = current + current["verifyDialogActive"] = image + return current + } + return image + } + } + var chatInputScheduled: CGImage { + if let image = cached.with({ $0["chatInputScheduled"] }) { + return image + } else { + let image = _chatInputScheduled() + _ = cached.modify { current in + var current = current + current["chatInputScheduled"] = image + return current + } + return image + } + } + var appearanceAddPlatformTheme: CGImage { + if let image = cached.with({ $0["appearanceAddPlatformTheme"] }) { + return image + } else { + let image = _appearanceAddPlatformTheme() + _ = cached.modify { current in + var current = current + current["appearanceAddPlatformTheme"] = image + return current + } + return image + } + } + var wallet_close: CGImage { + if let image = cached.with({ $0["wallet_close"] }) { + return image + } else { + let image = _wallet_close() + _ = cached.modify { current in + var current = current + current["wallet_close"] = image + return current + } + return image + } + } + var wallet_qr: CGImage { + if let image = cached.with({ $0["wallet_qr"] }) { + return image + } else { + let image = _wallet_qr() + _ = cached.modify { current in + var current = current + current["wallet_qr"] = image + return current + } + return image + } + } + var wallet_receive: CGImage { + if let image = cached.with({ $0["wallet_receive"] }) { + return image + } else { + let image = _wallet_receive() + _ = cached.modify { current in + var current = current + current["wallet_receive"] = image + return current + } + return image + } + } + var wallet_send: CGImage { + if let image = cached.with({ $0["wallet_send"] }) { + return image + } else { + let image = _wallet_send() + _ = cached.modify { current in + var current = current + current["wallet_send"] = image + return current + } + return image + } + } + var wallet_settings: CGImage { + if let image = cached.with({ $0["wallet_settings"] }) { + return image + } else { + let image = _wallet_settings() + _ = cached.modify { current in + var current = current + current["wallet_settings"] = image + return current + } + return image + } + } + var wallet_update: CGImage { + if let image = cached.with({ $0["wallet_update"] }) { + return image + } else { + let image = _wallet_update() + _ = cached.modify { current in + var current = current + current["wallet_update"] = image + return current + } + return image + } + } + var wallet_passcode_visible: CGImage { + if let image = cached.with({ $0["wallet_passcode_visible"] }) { + return image + } else { + let image = _wallet_passcode_visible() + _ = cached.modify { current in + var current = current + current["wallet_passcode_visible"] = image + return current + } + return image + } + } + var wallet_passcode_hidden: CGImage { + if let image = cached.with({ $0["wallet_passcode_hidden"] }) { + return image + } else { + let image = _wallet_passcode_hidden() + _ = cached.modify { current in + var current = current + current["wallet_passcode_hidden"] = image + return current + } + return image + } + } + var wallpaper_color_close: CGImage { + if let image = cached.with({ $0["wallpaper_color_close"] }) { + return image + } else { + let image = _wallpaper_color_close() + _ = cached.modify { current in + var current = current + current["wallpaper_color_close"] = image + return current + } + return image + } + } + var wallpaper_color_add: CGImage { + if let image = cached.with({ $0["wallpaper_color_add"] }) { + return image + } else { + let image = _wallpaper_color_add() + _ = cached.modify { current in + var current = current + current["wallpaper_color_add"] = image + return current + } + return image + } + } + var wallpaper_color_swap: CGImage { + if let image = cached.with({ $0["wallpaper_color_swap"] }) { + return image + } else { + let image = _wallpaper_color_swap() + _ = cached.modify { current in + var current = current + current["wallpaper_color_swap"] = image + return current + } + return image + } + } + var wallpaper_color_rotate: CGImage { + if let image = cached.with({ $0["wallpaper_color_rotate"] }) { + return image + } else { + let image = _wallpaper_color_rotate() + _ = cached.modify { current in + var current = current + current["wallpaper_color_rotate"] = image + return current + } + return image + } + } + var login_cap: CGImage { + if let image = cached.with({ $0["login_cap"] }) { + return image + } else { + let image = _login_cap() + _ = cached.modify { current in + var current = current + current["login_cap"] = image + return current + } + return image + } + } + var login_qr_cap: CGImage { + if let image = cached.with({ $0["login_qr_cap"] }) { + return image + } else { + let image = _login_qr_cap() + _ = cached.modify { current in + var current = current + current["login_qr_cap"] = image + return current + } + return image + } + } + var login_qr_empty_cap: CGImage { + if let image = cached.with({ $0["login_qr_empty_cap"] }) { + return image + } else { + let image = _login_qr_empty_cap() + _ = cached.modify { current in + var current = current + current["login_qr_empty_cap"] = image + return current + } + return image + } + } + var chat_failed_scroller: CGImage { + if let image = cached.with({ $0["chat_failed_scroller"] }) { + return image + } else { + let image = _chat_failed_scroller() + _ = cached.modify { current in + var current = current + current["chat_failed_scroller"] = image + return current + } + return image + } + } + var chat_failed_scroller_active: CGImage { + if let image = cached.with({ $0["chat_failed_scroller_active"] }) { + return image + } else { + let image = _chat_failed_scroller_active() + _ = cached.modify { current in + var current = current + current["chat_failed_scroller_active"] = image + return current + } + return image + } + } + var poll_quiz_unselected: CGImage { + if let image = cached.with({ $0["poll_quiz_unselected"] }) { + return image + } else { + let image = _poll_quiz_unselected() + _ = cached.modify { current in + var current = current + current["poll_quiz_unselected"] = image + return current + } + return image + } + } + var poll_selected: CGImage { + if let image = cached.with({ $0["poll_selected"] }) { + return image + } else { + let image = _poll_selected() + _ = cached.modify { current in + var current = current + current["poll_selected"] = image + return current + } + return image + } + } + var poll_selected_correct: CGImage { + if let image = cached.with({ $0["poll_selected_correct"] }) { + return image + } else { + let image = _poll_selected_correct() + _ = cached.modify { current in + var current = current + current["poll_selected_correct"] = image + return current + } + return image + } + } + var poll_selected_incorrect: CGImage { + if let image = cached.with({ $0["poll_selected_incorrect"] }) { + return image + } else { + let image = _poll_selected_incorrect() + _ = cached.modify { current in + var current = current + current["poll_selected_incorrect"] = image + return current + } + return image + } + } + var poll_selected_incoming: CGImage { + if let image = cached.with({ $0["poll_selected_incoming"] }) { + return image + } else { + let image = _poll_selected_incoming() + _ = cached.modify { current in + var current = current + current["poll_selected_incoming"] = image + return current + } + return image + } + } + var poll_selected_correct_incoming: CGImage { + if let image = cached.with({ $0["poll_selected_correct_incoming"] }) { + return image + } else { + let image = _poll_selected_correct_incoming() + _ = cached.modify { current in + var current = current + current["poll_selected_correct_incoming"] = image + return current + } + return image + } + } + var poll_selected_incorrect_incoming: CGImage { + if let image = cached.with({ $0["poll_selected_incorrect_incoming"] }) { + return image + } else { + let image = _poll_selected_incorrect_incoming() + _ = cached.modify { current in + var current = current + current["poll_selected_incorrect_incoming"] = image + return current + } + return image + } + } + var poll_selected_outgoing: CGImage { + if let image = cached.with({ $0["poll_selected_outgoing"] }) { + return image + } else { + let image = _poll_selected_outgoing() + _ = cached.modify { current in + var current = current + current["poll_selected_outgoing"] = image + return current + } + return image + } + } + var poll_selected_correct_outgoing: CGImage { + if let image = cached.with({ $0["poll_selected_correct_outgoing"] }) { + return image + } else { + let image = _poll_selected_correct_outgoing() + _ = cached.modify { current in + var current = current + current["poll_selected_correct_outgoing"] = image + return current + } + return image + } + } + var poll_selected_incorrect_outgoing: CGImage { + if let image = cached.with({ $0["poll_selected_incorrect_outgoing"] }) { + return image + } else { + let image = _poll_selected_incorrect_outgoing() + _ = cached.modify { current in + var current = current + current["poll_selected_incorrect_outgoing"] = image + return current + } + return image + } + } + var chat_filter_edit: CGImage { + if let image = cached.with({ $0["chat_filter_edit"] }) { + return image + } else { + let image = _chat_filter_edit() + _ = cached.modify { current in + var current = current + current["chat_filter_edit"] = image + return current + } + return image + } + } + var chat_filter_add: CGImage { + if let image = cached.with({ $0["chat_filter_add"] }) { + return image + } else { + let image = _chat_filter_add() + _ = cached.modify { current in + var current = current + current["chat_filter_add"] = image + return current + } + return image + } + } + var chat_filter_bots: CGImage { + if let image = cached.with({ $0["chat_filter_bots"] }) { + return image + } else { + let image = _chat_filter_bots() + _ = cached.modify { current in + var current = current + current["chat_filter_bots"] = image + return current + } + return image + } + } + var chat_filter_channels: CGImage { + if let image = cached.with({ $0["chat_filter_channels"] }) { + return image + } else { + let image = _chat_filter_channels() + _ = cached.modify { current in + var current = current + current["chat_filter_channels"] = image + return current + } + return image + } + } + var chat_filter_custom: CGImage { + if let image = cached.with({ $0["chat_filter_custom"] }) { + return image + } else { + let image = _chat_filter_custom() + _ = cached.modify { current in + var current = current + current["chat_filter_custom"] = image + return current + } + return image + } + } + var chat_filter_groups: CGImage { + if let image = cached.with({ $0["chat_filter_groups"] }) { + return image + } else { + let image = _chat_filter_groups() + _ = cached.modify { current in + var current = current + current["chat_filter_groups"] = image + return current + } + return image + } + } + var chat_filter_muted: CGImage { + if let image = cached.with({ $0["chat_filter_muted"] }) { + return image + } else { + let image = _chat_filter_muted() + _ = cached.modify { current in + var current = current + current["chat_filter_muted"] = image + return current + } + return image + } + } + var chat_filter_private_chats: CGImage { + if let image = cached.with({ $0["chat_filter_private_chats"] }) { + return image + } else { + let image = _chat_filter_private_chats() + _ = cached.modify { current in + var current = current + current["chat_filter_private_chats"] = image + return current + } + return image + } + } + var chat_filter_read: CGImage { + if let image = cached.with({ $0["chat_filter_read"] }) { + return image + } else { + let image = _chat_filter_read() + _ = cached.modify { current in + var current = current + current["chat_filter_read"] = image + return current + } + return image + } + } + var chat_filter_secret_chats: CGImage { + if let image = cached.with({ $0["chat_filter_secret_chats"] }) { + return image + } else { + let image = _chat_filter_secret_chats() + _ = cached.modify { current in + var current = current + current["chat_filter_secret_chats"] = image + return current + } + return image + } + } + var chat_filter_unmuted: CGImage { + if let image = cached.with({ $0["chat_filter_unmuted"] }) { + return image + } else { + let image = _chat_filter_unmuted() + _ = cached.modify { current in + var current = current + current["chat_filter_unmuted"] = image + return current + } + return image + } + } + var chat_filter_unread: CGImage { + if let image = cached.with({ $0["chat_filter_unread"] }) { + return image + } else { + let image = _chat_filter_unread() + _ = cached.modify { current in + var current = current + current["chat_filter_unread"] = image + return current + } + return image + } + } + var chat_filter_large_groups: CGImage { + if let image = cached.with({ $0["chat_filter_large_groups"] }) { + return image + } else { + let image = _chat_filter_large_groups() + _ = cached.modify { current in + var current = current + current["chat_filter_large_groups"] = image + return current + } + return image + } + } + var chat_filter_non_contacts: CGImage { + if let image = cached.with({ $0["chat_filter_non_contacts"] }) { + return image + } else { + let image = _chat_filter_non_contacts() + _ = cached.modify { current in + var current = current + current["chat_filter_non_contacts"] = image + return current + } + return image + } + } + var chat_filter_archive: CGImage { + if let image = cached.with({ $0["chat_filter_archive"] }) { + return image + } else { + let image = _chat_filter_archive() + _ = cached.modify { current in + var current = current + current["chat_filter_archive"] = image + return current + } + return image + } + } + var chat_filter_bots_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_bots_avatar"] }) { + return image + } else { + let image = _chat_filter_bots_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_bots_avatar"] = image + return current + } + return image + } + } + var chat_filter_channels_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_channels_avatar"] }) { + return image + } else { + let image = _chat_filter_channels_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_channels_avatar"] = image + return current + } + return image + } + } + var chat_filter_custom_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_custom_avatar"] }) { + return image + } else { + let image = _chat_filter_custom_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_custom_avatar"] = image + return current + } + return image + } + } + var chat_filter_groups_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_groups_avatar"] }) { + return image + } else { + let image = _chat_filter_groups_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_groups_avatar"] = image + return current + } + return image + } + } + var chat_filter_muted_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_muted_avatar"] }) { + return image + } else { + let image = _chat_filter_muted_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_muted_avatar"] = image + return current + } + return image + } + } + var chat_filter_private_chats_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_private_chats_avatar"] }) { + return image + } else { + let image = _chat_filter_private_chats_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_private_chats_avatar"] = image + return current + } + return image + } + } + var chat_filter_read_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_read_avatar"] }) { + return image + } else { + let image = _chat_filter_read_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_read_avatar"] = image + return current + } + return image + } + } + var chat_filter_secret_chats_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_secret_chats_avatar"] }) { + return image + } else { + let image = _chat_filter_secret_chats_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_secret_chats_avatar"] = image + return current + } + return image + } + } + var chat_filter_unmuted_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_unmuted_avatar"] }) { + return image + } else { + let image = _chat_filter_unmuted_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_unmuted_avatar"] = image + return current + } + return image + } + } + var chat_filter_unread_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_unread_avatar"] }) { + return image + } else { + let image = _chat_filter_unread_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_unread_avatar"] = image + return current + } + return image + } + } + var chat_filter_large_groups_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_large_groups_avatar"] }) { + return image + } else { + let image = _chat_filter_large_groups_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_large_groups_avatar"] = image + return current + } + return image + } + } + var chat_filter_non_contacts_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_non_contacts_avatar"] }) { + return image + } else { + let image = _chat_filter_non_contacts_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_non_contacts_avatar"] = image + return current + } + return image + } + } + var chat_filter_archive_avatar: CGImage { + if let image = cached.with({ $0["chat_filter_archive_avatar"] }) { + return image + } else { + let image = _chat_filter_archive_avatar() + _ = cached.modify { current in + var current = current + current["chat_filter_archive_avatar"] = image + return current + } + return image + } + } + var group_invite_via_link: CGImage { + if let image = cached.with({ $0["group_invite_via_link"] }) { + return image + } else { + let image = _group_invite_via_link() + _ = cached.modify { current in + var current = current + current["group_invite_via_link"] = image + return current + } + return image + } + } + var tab_contacts: CGImage { + if let image = cached.with({ $0["tab_contacts"] }) { + return image + } else { + let image = _tab_contacts() + _ = cached.modify { current in + var current = current + current["tab_contacts"] = image + return current + } + return image + } + } + var tab_contacts_active: CGImage { + if let image = cached.with({ $0["tab_contacts_active"] }) { + return image + } else { + let image = _tab_contacts_active() + _ = cached.modify { current in + var current = current + current["tab_contacts_active"] = image + return current + } + return image + } + } + var tab_calls: CGImage { + if let image = cached.with({ $0["tab_calls"] }) { + return image + } else { + let image = _tab_calls() + _ = cached.modify { current in + var current = current + current["tab_calls"] = image + return current + } + return image + } + } + var tab_calls_active: CGImage { + if let image = cached.with({ $0["tab_calls_active"] }) { + return image + } else { + let image = _tab_calls_active() + _ = cached.modify { current in + var current = current + current["tab_calls_active"] = image + return current + } + return image + } + } + var tab_chats: CGImage { + if let image = cached.with({ $0["tab_chats"] }) { + return image + } else { + let image = _tab_chats() + _ = cached.modify { current in + var current = current + current["tab_chats"] = image + return current + } + return image + } + } + var tab_chats_active: CGImage { + if let image = cached.with({ $0["tab_chats_active"] }) { + return image + } else { + let image = _tab_chats_active() + _ = cached.modify { current in + var current = current + current["tab_chats_active"] = image + return current + } + return image + } + } + var tab_chats_active_filters: CGImage { + if let image = cached.with({ $0["tab_chats_active_filters"] }) { + return image + } else { + let image = _tab_chats_active_filters() + _ = cached.modify { current in + var current = current + current["tab_chats_active_filters"] = image + return current + } + return image + } + } + var tab_settings: CGImage { + if let image = cached.with({ $0["tab_settings"] }) { + return image + } else { + let image = _tab_settings() + _ = cached.modify { current in + var current = current + current["tab_settings"] = image + return current + } + return image + } + } + var tab_settings_active: CGImage { + if let image = cached.with({ $0["tab_settings_active"] }) { + return image + } else { + let image = _tab_settings_active() + _ = cached.modify { current in + var current = current + current["tab_settings_active"] = image + return current + } + return image + } + } + var profile_add_member: CGImage { + if let image = cached.with({ $0["profile_add_member"] }) { + return image + } else { + let image = _profile_add_member() + _ = cached.modify { current in + var current = current + current["profile_add_member"] = image + return current + } + return image + } + } + var profile_call: CGImage { + if let image = cached.with({ $0["profile_call"] }) { + return image + } else { + let image = _profile_call() + _ = cached.modify { current in + var current = current + current["profile_call"] = image + return current + } + return image + } + } + var profile_leave: CGImage { + if let image = cached.with({ $0["profile_leave"] }) { + return image + } else { + let image = _profile_leave() + _ = cached.modify { current in + var current = current + current["profile_leave"] = image + return current + } + return image + } + } + var profile_message: CGImage { + if let image = cached.with({ $0["profile_message"] }) { + return image + } else { + let image = _profile_message() + _ = cached.modify { current in + var current = current + current["profile_message"] = image + return current + } + return image + } + } + var profile_more: CGImage { + if let image = cached.with({ $0["profile_more"] }) { + return image + } else { + let image = _profile_more() + _ = cached.modify { current in + var current = current + current["profile_more"] = image + return current + } + return image + } + } + var profile_mute: CGImage { + if let image = cached.with({ $0["profile_mute"] }) { + return image + } else { + let image = _profile_mute() + _ = cached.modify { current in + var current = current + current["profile_mute"] = image + return current + } + return image + } + } + var profile_unmute: CGImage { + if let image = cached.with({ $0["profile_unmute"] }) { + return image + } else { + let image = _profile_unmute() + _ = cached.modify { current in + var current = current + current["profile_unmute"] = image + return current + } + return image + } + } + var profile_search: CGImage { + if let image = cached.with({ $0["profile_search"] }) { + return image + } else { + let image = _profile_search() + _ = cached.modify { current in + var current = current + current["profile_search"] = image + return current + } + return image + } + } + var profile_secret_chat: CGImage { + if let image = cached.with({ $0["profile_secret_chat"] }) { + return image + } else { + let image = _profile_secret_chat() + _ = cached.modify { current in + var current = current + current["profile_secret_chat"] = image + return current + } + return image + } + } + var profile_edit_photo: CGImage { + if let image = cached.with({ $0["profile_edit_photo"] }) { + return image + } else { + let image = _profile_edit_photo() + _ = cached.modify { current in + var current = current + current["profile_edit_photo"] = image + return current + } + return image + } + } + var profile_block: CGImage { + if let image = cached.with({ $0["profile_block"] }) { + return image + } else { + let image = _profile_block() + _ = cached.modify { current in + var current = current + current["profile_block"] = image + return current + } + return image + } + } + var profile_report: CGImage { + if let image = cached.with({ $0["profile_report"] }) { + return image + } else { + let image = _profile_report() + _ = cached.modify { current in + var current = current + current["profile_report"] = image + return current + } + return image + } + } + var profile_share: CGImage { + if let image = cached.with({ $0["profile_share"] }) { + return image + } else { + let image = _profile_share() + _ = cached.modify { current in + var current = current + current["profile_share"] = image + return current + } + return image + } + } + var profile_stats: CGImage { + if let image = cached.with({ $0["profile_stats"] }) { + return image + } else { + let image = _profile_stats() + _ = cached.modify { current in + var current = current + current["profile_stats"] = image + return current + } + return image + } + } + var profile_unblock: CGImage { + if let image = cached.with({ $0["profile_unblock"] }) { + return image + } else { + let image = _profile_unblock() + _ = cached.modify { current in + var current = current + current["profile_unblock"] = image + return current + } + return image + } + } + var chat_quiz_explanation: CGImage { + if let image = cached.with({ $0["chat_quiz_explanation"] }) { + return image + } else { + let image = _chat_quiz_explanation() + _ = cached.modify { current in + var current = current + current["chat_quiz_explanation"] = image + return current + } + return image + } + } + var chat_quiz_explanation_bubble_incoming: CGImage { + if let image = cached.with({ $0["chat_quiz_explanation_bubble_incoming"] }) { + return image + } else { + let image = _chat_quiz_explanation_bubble_incoming() + _ = cached.modify { current in + var current = current + current["chat_quiz_explanation_bubble_incoming"] = image + return current + } + return image + } + } + var chat_quiz_explanation_bubble_outgoing: CGImage { + if let image = cached.with({ $0["chat_quiz_explanation_bubble_outgoing"] }) { + return image + } else { + let image = _chat_quiz_explanation_bubble_outgoing() + _ = cached.modify { current in + var current = current + current["chat_quiz_explanation_bubble_outgoing"] = image + return current + } + return image + } + } + var stickers_add_featured: CGImage { + if let image = cached.with({ $0["stickers_add_featured"] }) { + return image + } else { + let image = _stickers_add_featured() + _ = cached.modify { current in + var current = current + current["stickers_add_featured"] = image + return current + } + return image + } + } + var channel_info_promo: CGImage { + if let image = cached.with({ $0["channel_info_promo"] }) { + return image + } else { + let image = _channel_info_promo() + _ = cached.modify { current in + var current = current + current["channel_info_promo"] = image + return current + } + return image + } + } + var channel_info_promo_bubble_incoming: CGImage { + if let image = cached.with({ $0["channel_info_promo_bubble_incoming"] }) { + return image + } else { + let image = _channel_info_promo_bubble_incoming() + _ = cached.modify { current in + var current = current + current["channel_info_promo_bubble_incoming"] = image + return current + } + return image + } + } + var channel_info_promo_bubble_outgoing: CGImage { + if let image = cached.with({ $0["channel_info_promo_bubble_outgoing"] }) { + return image + } else { + let image = _channel_info_promo_bubble_outgoing() + _ = cached.modify { current in + var current = current + current["channel_info_promo_bubble_outgoing"] = image + return current + } + return image + } + } + var chat_share_message: CGImage { + if let image = cached.with({ $0["chat_share_message"] }) { + return image + } else { + let image = _chat_share_message() + _ = cached.modify { current in + var current = current + current["chat_share_message"] = image + return current + } + return image + } + } + var chat_goto_message: CGImage { + if let image = cached.with({ $0["chat_goto_message"] }) { + return image + } else { + let image = _chat_goto_message() + _ = cached.modify { current in + var current = current + current["chat_goto_message"] = image + return current + } + return image + } + } + var chat_swipe_reply: CGImage { + if let image = cached.with({ $0["chat_swipe_reply"] }) { + return image + } else { + let image = _chat_swipe_reply() + _ = cached.modify { current in + var current = current + current["chat_swipe_reply"] = image + return current + } + return image + } + } + var chat_like_message: CGImage { + if let image = cached.with({ $0["chat_like_message"] }) { + return image + } else { + let image = _chat_like_message() + _ = cached.modify { current in + var current = current + current["chat_like_message"] = image + return current + } + return image + } + } + var chat_like_message_unlike: CGImage { + if let image = cached.with({ $0["chat_like_message_unlike"] }) { + return image + } else { + let image = _chat_like_message_unlike() + _ = cached.modify { current in + var current = current + current["chat_like_message_unlike"] = image + return current + } + return image + } + } + var chat_like_inside: CGImage { + if let image = cached.with({ $0["chat_like_inside"] }) { + return image + } else { + let image = _chat_like_inside() + _ = cached.modify { current in + var current = current + current["chat_like_inside"] = image + return current + } + return image + } + } + var chat_like_inside_bubble_incoming: CGImage { + if let image = cached.with({ $0["chat_like_inside_bubble_incoming"] }) { + return image + } else { + let image = _chat_like_inside_bubble_incoming() + _ = cached.modify { current in + var current = current + current["chat_like_inside_bubble_incoming"] = image + return current + } + return image + } + } + var chat_like_inside_bubble_outgoing: CGImage { + if let image = cached.with({ $0["chat_like_inside_bubble_outgoing"] }) { + return image + } else { + let image = _chat_like_inside_bubble_outgoing() + _ = cached.modify { current in + var current = current + current["chat_like_inside_bubble_outgoing"] = image + return current + } + return image + } + } + var chat_like_inside_bubble_overlay: CGImage { + if let image = cached.with({ $0["chat_like_inside_bubble_overlay"] }) { + return image + } else { + let image = _chat_like_inside_bubble_overlay() + _ = cached.modify { current in + var current = current + current["chat_like_inside_bubble_overlay"] = image + return current + } + return image + } + } + var chat_like_inside_empty: CGImage { + if let image = cached.with({ $0["chat_like_inside_empty"] }) { + return image + } else { + let image = _chat_like_inside_empty() + _ = cached.modify { current in + var current = current + current["chat_like_inside_empty"] = image + return current + } + return image + } + } + var chat_like_inside_empty_bubble_incoming: CGImage { + if let image = cached.with({ $0["chat_like_inside_empty_bubble_incoming"] }) { + return image + } else { + let image = _chat_like_inside_empty_bubble_incoming() + _ = cached.modify { current in + var current = current + current["chat_like_inside_empty_bubble_incoming"] = image + return current + } + return image + } + } + var chat_like_inside_empty_bubble_outgoing: CGImage { + if let image = cached.with({ $0["chat_like_inside_empty_bubble_outgoing"] }) { + return image + } else { + let image = _chat_like_inside_empty_bubble_outgoing() + _ = cached.modify { current in + var current = current + current["chat_like_inside_empty_bubble_outgoing"] = image + return current + } + return image + } + } + var chat_like_inside_empty_bubble_overlay: CGImage { + if let image = cached.with({ $0["chat_like_inside_empty_bubble_overlay"] }) { + return image + } else { + let image = _chat_like_inside_empty_bubble_overlay() + _ = cached.modify { current in + var current = current + current["chat_like_inside_empty_bubble_overlay"] = image + return current + } + return image + } + } + var gif_trending: CGImage { + if let image = cached.with({ $0["gif_trending"] }) { + return image + } else { + let image = _gif_trending() + _ = cached.modify { current in + var current = current + current["gif_trending"] = image + return current + } + return image + } + } + var chat_list_thumb_play: CGImage { + if let image = cached.with({ $0["chat_list_thumb_play"] }) { + return image + } else { + let image = _chat_list_thumb_play() + _ = cached.modify { current in + var current = current + current["chat_list_thumb_play"] = image + return current + } + return image + } + } + + private let _dialogMuteImage: ()->CGImage + private let _dialogMuteImageSelected: ()->CGImage + private let _outgoingMessageImage: ()->CGImage + private let _readMessageImage: ()->CGImage + private let _outgoingMessageImageSelected: ()->CGImage + private let _readMessageImageSelected: ()->CGImage + private let _sendingImage: ()->CGImage + private let _sendingImageSelected: ()->CGImage + private let _secretImage: ()->CGImage + private let _secretImageSelected: ()->CGImage + private let _pinnedImage: ()->CGImage + private let _pinnedImageSelected: ()->CGImage + private let _verifiedImage: ()->CGImage + private let _verifiedImageSelected: ()->CGImage + private let _errorImage: ()->CGImage + private let _errorImageSelected: ()->CGImage + private let _chatSearch: ()->CGImage + private let _chatSearchActive: ()->CGImage + private let _chatCall: ()->CGImage + private let _chatActions: ()->CGImage + private let _chatFailedCall_incoming: ()->CGImage + private let _chatFailedCall_outgoing: ()->CGImage + private let _chatCall_incoming: ()->CGImage + private let _chatCall_outgoing: ()->CGImage + private let _chatFailedCallBubble_incoming: ()->CGImage + private let _chatFailedCallBubble_outgoing: ()->CGImage + private let _chatCallBubble_incoming: ()->CGImage + private let _chatCallBubble_outgoing: ()->CGImage + private let _chatFallbackCall: ()->CGImage + private let _chatFallbackCallBubble_incoming: ()->CGImage + private let _chatFallbackCallBubble_outgoing: ()->CGImage + private let _chatToggleSelected: ()->CGImage + private let _chatToggleUnselected: ()->CGImage + private let _chatMusicPlay: ()->CGImage + private let _chatMusicPlayBubble_incoming: ()->CGImage + private let _chatMusicPlayBubble_outgoing: ()->CGImage + private let _chatMusicPause: ()->CGImage + private let _chatMusicPauseBubble_incoming: ()->CGImage + private let _chatMusicPauseBubble_outgoing: ()->CGImage + private let _chatGradientBubble_incoming: ()->CGImage + private let _chatGradientBubble_outgoing: ()->CGImage + private let _chatBubble_none_incoming_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubble_none_outgoing_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubbleBorder_none_incoming_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubbleBorder_none_outgoing_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubble_both_incoming_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubble_both_outgoing_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubbleBorder_both_incoming_withInset: ()->(CGImage, NSEdgeInsets) + private let _chatBubbleBorder_both_outgoing_withInset: ()->(CGImage, NSEdgeInsets) + private let _composeNewChat: ()->CGImage + private let _composeNewChatActive: ()->CGImage + private let _composeNewGroup: ()->CGImage + private let _composeNewSecretChat: ()->CGImage + private let _composeNewChannel: ()->CGImage + private let _contactsNewContact: ()->CGImage + private let _chatReadMarkInBubble1_incoming: ()->CGImage + private let _chatReadMarkInBubble2_incoming: ()->CGImage + private let _chatReadMarkInBubble1_outgoing: ()->CGImage + private let _chatReadMarkInBubble2_outgoing: ()->CGImage + private let _chatReadMarkOutBubble1: ()->CGImage + private let _chatReadMarkOutBubble2: ()->CGImage + private let _chatReadMarkOverlayBubble1: ()->CGImage + private let _chatReadMarkOverlayBubble2: ()->CGImage + private let _sentFailed: ()->CGImage + private let _chatChannelViewsInBubble_incoming: ()->CGImage + private let _chatChannelViewsInBubble_outgoing: ()->CGImage + private let _chatChannelViewsOutBubble: ()->CGImage + private let _chatChannelViewsOverlayBubble: ()->CGImage + private let _chatNavigationBack: ()->CGImage + private let _peerInfoAddMember: ()->CGImage + private let _chatSearchUp: ()->CGImage + private let _chatSearchUpDisabled: ()->CGImage + private let _chatSearchDown: ()->CGImage + private let _chatSearchDownDisabled: ()->CGImage + private let _chatSearchCalendar: ()->CGImage + private let _dismissAccessory: ()->CGImage + private let _chatScrollUp: ()->CGImage + private let _chatScrollUpActive: ()->CGImage + private let _audioPlayerPlay: ()->CGImage + private let _audioPlayerPause: ()->CGImage + private let _audioPlayerNext: ()->CGImage + private let _audioPlayerPrev: ()->CGImage + private let _auduiPlayerDismiss: ()->CGImage + private let _audioPlayerRepeat: ()->CGImage + private let _audioPlayerRepeatActive: ()->CGImage + private let _audioPlayerLockedPlay: ()->CGImage + private let _audioPlayerLockedNext: ()->CGImage + private let _audioPlayerLockedPrev: ()->CGImage + private let _chatSendMessage: ()->CGImage + private let _chatSaveEditedMessage: ()->CGImage + private let _chatRecordVoice: ()->CGImage + private let _chatEntertainment: ()->CGImage + private let _chatInlineDismiss: ()->CGImage + private let _chatActiveReplyMarkup: ()->CGImage + private let _chatDisabledReplyMarkup: ()->CGImage + private let _chatSecretTimer: ()->CGImage + private let _chatForwardMessagesActive: ()->CGImage + private let _chatForwardMessagesInactive: ()->CGImage + private let _chatDeleteMessagesActive: ()->CGImage + private let _chatDeleteMessagesInactive: ()->CGImage + private let _generalNext: ()->CGImage + private let _generalNextActive: ()->CGImage + private let _generalSelect: ()->CGImage + private let _chatVoiceRecording: ()->CGImage + private let _chatVideoRecording: ()->CGImage + private let _chatRecord: ()->CGImage + private let _deleteItem: ()->CGImage + private let _deleteItemDisabled: ()->CGImage + private let _chatAttach: ()->CGImage + private let _chatAttachFile: ()->CGImage + private let _chatAttachPhoto: ()->CGImage + private let _chatAttachCamera: ()->CGImage + private let _chatAttachLocation: ()->CGImage + private let _chatAttachPoll: ()->CGImage + private let _mediaEmptyShared: ()->CGImage + private let _mediaEmptyFiles: ()->CGImage + private let _mediaEmptyMusic: ()->CGImage + private let _mediaEmptyLinks: ()->CGImage + private let _stickersAddFeatured: ()->CGImage + private let _stickersAddedFeatured: ()->CGImage + private let _stickersRemove: ()->CGImage + private let _peerMediaDownloadFileStart: ()->CGImage + private let _peerMediaDownloadFilePause: ()->CGImage + private let _stickersShare: ()->CGImage + private let _emojiRecentTab: ()->CGImage + private let _emojiSmileTab: ()->CGImage + private let _emojiNatureTab: ()->CGImage + private let _emojiFoodTab: ()->CGImage + private let _emojiSportTab: ()->CGImage + private let _emojiCarTab: ()->CGImage + private let _emojiObjectsTab: ()->CGImage + private let _emojiSymbolsTab: ()->CGImage + private let _emojiFlagsTab: ()->CGImage + private let _emojiRecentTabActive: ()->CGImage + private let _emojiSmileTabActive: ()->CGImage + private let _emojiNatureTabActive: ()->CGImage + private let _emojiFoodTabActive: ()->CGImage + private let _emojiSportTabActive: ()->CGImage + private let _emojiCarTabActive: ()->CGImage + private let _emojiObjectsTabActive: ()->CGImage + private let _emojiSymbolsTabActive: ()->CGImage + private let _emojiFlagsTabActive: ()->CGImage + private let _stickerBackground: ()->CGImage + private let _stickerBackgroundActive: ()->CGImage + private let _stickersTabRecent: ()->CGImage + private let _stickersTabGIF: ()->CGImage + private let _chatSendingInFrame_incoming: ()->CGImage + private let _chatSendingInHour_incoming: ()->CGImage + private let _chatSendingInMin_incoming: ()->CGImage + private let _chatSendingInFrame_outgoing: ()->CGImage + private let _chatSendingInHour_outgoing: ()->CGImage + private let _chatSendingInMin_outgoing: ()->CGImage + private let _chatSendingOutFrame: ()->CGImage + private let _chatSendingOutHour: ()->CGImage + private let _chatSendingOutMin: ()->CGImage + private let _chatSendingOverlayFrame: ()->CGImage + private let _chatSendingOverlayHour: ()->CGImage + private let _chatSendingOverlayMin: ()->CGImage + private let _chatActionUrl: ()->CGImage + private let _callInlineDecline: ()->CGImage + private let _callInlineMuted: ()->CGImage + private let _callInlineUnmuted: ()->CGImage + private let _eventLogTriangle: ()->CGImage + private let _channelIntro: ()->CGImage + private let _chatFileThumb: ()->CGImage + private let _chatFileThumbBubble_incoming: ()->CGImage + private let _chatFileThumbBubble_outgoing: ()->CGImage + private let _chatSecretThumb: ()->CGImage + private let _chatSecretThumbSmall: ()->CGImage + private let _chatMapPin: ()->CGImage + private let _chatSecretTitle: ()->CGImage + private let _emptySearch: ()->CGImage + private let _calendarBack: ()->CGImage + private let _calendarNext: ()->CGImage + private let _calendarBackDisabled: ()->CGImage + private let _calendarNextDisabled: ()->CGImage + private let _newChatCamera: ()->CGImage + private let _peerInfoVerify: ()->CGImage + private let _peerInfoVerifyProfile: ()->CGImage + private let _peerInfoCall: ()->CGImage + private let _callOutgoing: ()->CGImage + private let _recentDismiss: ()->CGImage + private let _recentDismissActive: ()->CGImage + private let _webgameShare: ()->CGImage + private let _chatSearchCancel: ()->CGImage + private let _chatSearchFrom: ()->CGImage + private let _callWindowDecline: ()->CGImage + private let _callWindowAccept: ()->CGImage + private let _callWindowMute: ()->CGImage + private let _callWindowUnmute: ()->CGImage + private let _callWindowClose: ()->CGImage + private let _callWindowDeviceSettings: ()->CGImage + private let _callSettings: ()->CGImage + private let _callWindowCancel: ()->CGImage + private let _chatActionEdit: ()->CGImage + private let _chatActionInfo: ()->CGImage + private let _chatActionMute: ()->CGImage + private let _chatActionUnmute: ()->CGImage + private let _chatActionClearHistory: ()->CGImage + private let _chatActionDeleteChat: ()->CGImage + private let _dismissPinned: ()->CGImage + private let _chatActionsActive: ()->CGImage + private let _chatEntertainmentSticker: ()->CGImage + private let _chatEmpty: ()->CGImage + private let _stickerPackClose: ()->CGImage + private let _stickerPackDelete: ()->CGImage + private let _modalShare: ()->CGImage + private let _modalClose: ()->CGImage + private let _ivChannelJoined: ()->CGImage + private let _chatListMention: ()->CGImage + private let _chatListMentionActive: ()->CGImage + private let _chatListMentionArchived: ()->CGImage + private let _chatListMentionArchivedActive: ()->CGImage + private let _chatMention: ()->CGImage + private let _chatMentionActive: ()->CGImage + private let _sliderControl: ()->CGImage + private let _sliderControlActive: ()->CGImage + private let _stickersTabFave: ()->CGImage + private let _chatInstantView: ()->CGImage + private let _chatInstantViewBubble_incoming: ()->CGImage + private let _chatInstantViewBubble_outgoing: ()->CGImage + private let _instantViewShare: ()->CGImage + private let _instantViewActions: ()->CGImage + private let _instantViewActionsActive: ()->CGImage + private let _instantViewSafari: ()->CGImage + private let _instantViewBack: ()->CGImage + private let _instantViewCheck: ()->CGImage + private let _groupStickerNotFound: ()->CGImage + private let _settingsAskQuestion: ()->CGImage + private let _settingsFaq: ()->CGImage + private let _settingsGeneral: ()->CGImage + private let _settingsLanguage: ()->CGImage + private let _settingsNotifications: ()->CGImage + private let _settingsSecurity: ()->CGImage + private let _settingsStickers: ()->CGImage + private let _settingsStorage: ()->CGImage + private let _settingsSessions: ()->CGImage + private let _settingsProxy: ()->CGImage + private let _settingsAppearance: ()->CGImage + private let _settingsPassport: ()->CGImage + private let _settingsWallet: ()->CGImage + private let _settingsUpdate: ()->CGImage + private let _settingsFilters: ()->CGImage + private let _settingsAskQuestionActive: ()->CGImage + private let _settingsFaqActive: ()->CGImage + private let _settingsGeneralActive: ()->CGImage + private let _settingsLanguageActive: ()->CGImage + private let _settingsNotificationsActive: ()->CGImage + private let _settingsSecurityActive: ()->CGImage + private let _settingsStickersActive: ()->CGImage + private let _settingsStorageActive: ()->CGImage + private let _settingsSessionsActive: ()->CGImage + private let _settingsProxyActive: ()->CGImage + private let _settingsAppearanceActive: ()->CGImage + private let _settingsPassportActive: ()->CGImage + private let _settingsWalletActive: ()->CGImage + private let _settingsUpdateActive: ()->CGImage + private let _settingsFiltersActive: ()->CGImage + private let _settingsProfile: ()->CGImage + private let _generalCheck: ()->CGImage + private let _settingsAbout: ()->CGImage + private let _settingsLogout: ()->CGImage + private let _fastSettingsLock: ()->CGImage + private let _fastSettingsDark: ()->CGImage + private let _fastSettingsSunny: ()->CGImage + private let _fastSettingsMute: ()->CGImage + private let _fastSettingsUnmute: ()->CGImage + private let _chatRecordVideo: ()->CGImage + private let _inputChannelMute: ()->CGImage + private let _inputChannelUnmute: ()->CGImage + private let _changePhoneNumberIntro: ()->CGImage + private let _peerSavedMessages: ()->CGImage + private let _previewSenderCollage: ()->CGImage + private let _previewSenderPhoto: ()->CGImage + private let _previewSenderFile: ()->CGImage + private let _previewSenderCrop: ()->CGImage + private let _previewSenderDelete: ()->CGImage + private let _previewSenderDeleteFile: ()->CGImage + private let _previewSenderArchive: ()->CGImage + private let _chatGroupToggleSelected: ()->CGImage + private let _chatGroupToggleUnselected: ()->CGImage + private let _successModalProgress: ()->CGImage + private let _accentColorSelect: ()->CGImage + private let _transparentBackground: ()->CGImage + private let _lottieTransparentBackground: ()->CGImage + private let _passcodeTouchId: ()->CGImage + private let _passcodeLogin: ()->CGImage + private let _confirmDeleteMessagesAccessory: ()->CGImage + private let _alertCheckBoxSelected: ()->CGImage + private let _alertCheckBoxUnselected: ()->CGImage + private let _confirmPinAccessory: ()->CGImage + private let _confirmDeleteChatAccessory: ()->CGImage + private let _stickersEmptySearch: ()->CGImage + private let _twoStepVerificationCreateIntro: ()->CGImage + private let _secureIdAuth: ()->CGImage + private let _ivAudioPlay: ()->CGImage + private let _ivAudioPause: ()->CGImage + private let _proxyEnable: ()->CGImage + private let _proxyEnabled: ()->CGImage + private let _proxyState: ()->CGImage + private let _proxyDeleteListItem: ()->CGImage + private let _proxyInfoListItem: ()->CGImage + private let _proxyConnectedListItem: ()->CGImage + private let _proxyAddProxy: ()->CGImage + private let _proxyNextWaitingListItem: ()->CGImage + private let _passportForgotPassword: ()->CGImage + private let _confirmAppAccessoryIcon: ()->CGImage + private let _passportPassport: ()->CGImage + private let _passportIdCardReverse: ()->CGImage + private let _passportIdCard: ()->CGImage + private let _passportSelfie: ()->CGImage + private let _passportDriverLicense: ()->CGImage + private let _chatOverlayVoiceRecording: ()->CGImage + private let _chatOverlayVideoRecording: ()->CGImage + private let _chatOverlaySendRecording: ()->CGImage + private let _chatOverlayLockArrowRecording: ()->CGImage + private let _chatOverlayLockerBodyRecording: ()->CGImage + private let _chatOverlayLockerHeadRecording: ()->CGImage + private let _locationPin: ()->CGImage + private let _locationMapPin: ()->CGImage + private let _locationMapLocate: ()->CGImage + private let _locationMapLocated: ()->CGImage + private let _passportSettings: ()->CGImage + private let _passportInfo: ()->CGImage + private let _editMessageMedia: ()->CGImage + private let _playerMusicPlaceholder: ()->CGImage + private let _chatMusicPlaceholder: ()->CGImage + private let _chatMusicPlaceholderCap: ()->CGImage + private let _searchArticle: ()->CGImage + private let _searchSaved: ()->CGImage + private let _archivedChats: ()->CGImage + private let _hintPeerActive: ()->CGImage + private let _hintPeerActiveSelected: ()->CGImage + private let _chatSwiping_delete: ()->CGImage + private let _chatSwiping_mute: ()->CGImage + private let _chatSwiping_unmute: ()->CGImage + private let _chatSwiping_read: ()->CGImage + private let _chatSwiping_unread: ()->CGImage + private let _chatSwiping_pin: ()->CGImage + private let _chatSwiping_unpin: ()->CGImage + private let _chatSwiping_archive: ()->CGImage + private let _chatSwiping_unarchive: ()->CGImage + private let _galleryPrev: ()->CGImage + private let _galleryNext: ()->CGImage + private let _galleryMore: ()->CGImage + private let _galleryShare: ()->CGImage + private let _galleryFastSave: ()->CGImage + private let _playingVoice1x: ()->CGImage + private let _playingVoice2x: ()->CGImage + private let _galleryRotate: ()->CGImage + private let _galleryZoomIn: ()->CGImage + private let _galleryZoomOut: ()->CGImage + private let _editMessageCurrentPhoto: ()->CGImage + private let _videoPlayerPlay: ()->CGImage + private let _videoPlayerPause: ()->CGImage + private let _videoPlayerEnterFullScreen: ()->CGImage + private let _videoPlayerExitFullScreen: ()->CGImage + private let _videoPlayerPIPIn: ()->CGImage + private let _videoPlayerPIPOut: ()->CGImage + private let _videoPlayerRewind15Forward: ()->CGImage + private let _videoPlayerRewind15Backward: ()->CGImage + private let _videoPlayerVolume: ()->CGImage + private let _videoPlayerVolumeOff: ()->CGImage + private let _videoPlayerClose: ()->CGImage + private let _videoPlayerSliderInteractor: ()->CGImage + private let _streamingVideoDownload: ()->CGImage + private let _videoCompactFetching: ()->CGImage + private let _compactStreamingFetchingCancel: ()->CGImage + private let _customLocalizationDelete: ()->CGImage + private let _pollAddOption: ()->CGImage + private let _pollDeleteOption: ()->CGImage + private let _resort: ()->CGImage + private let _chatPollVoteUnselected: ()->CGImage + private let _chatPollVoteUnselectedBubble_incoming: ()->CGImage + private let _chatPollVoteUnselectedBubble_outgoing: ()->CGImage + private let _peerInfoAdmins: ()->CGImage + private let _peerInfoPermissions: ()->CGImage + private let _peerInfoBanned: ()->CGImage + private let _peerInfoMembers: ()->CGImage + private let _chatUndoAction: ()->CGImage + private let _appUpdate: ()->CGImage + private let _inlineVideoSoundOff: ()->CGImage + private let _inlineVideoSoundOn: ()->CGImage + private let _logoutOptionAddAccount: ()->CGImage + private let _logoutOptionSetPasscode: ()->CGImage + private let _logoutOptionClearCache: ()->CGImage + private let _logoutOptionChangePhoneNumber: ()->CGImage + private let _logoutOptionContactSupport: ()->CGImage + private let _disableEmojiPrediction: ()->CGImage + private let _scam: ()->CGImage + private let _scamActive: ()->CGImage + private let _chatScam: ()->CGImage + private let _chatUnarchive: ()->CGImage + private let _chatArchive: ()->CGImage + private let _privacySettings_blocked: ()->CGImage + private let _privacySettings_activeSessions: ()->CGImage + private let _privacySettings_passcode: ()->CGImage + private let _privacySettings_twoStep: ()->CGImage + private let _deletedAccount: ()->CGImage + private let _stickerPackSelection: ()->CGImage + private let _stickerPackSelectionActive: ()->CGImage + private let _entertainment_Emoji: ()->CGImage + private let _entertainment_Stickers: ()->CGImage + private let _entertainment_Gifs: ()->CGImage + private let _entertainment_Search: ()->CGImage + private let _entertainment_Settings: ()->CGImage + private let _entertainment_SearchCancel: ()->CGImage + private let _scheduledAvatar: ()->CGImage + private let _scheduledInputAction: ()->CGImage + private let _verifyDialog: ()->CGImage + private let _verifyDialogActive: ()->CGImage + private let _chatInputScheduled: ()->CGImage + private let _appearanceAddPlatformTheme: ()->CGImage + private let _wallet_close: ()->CGImage + private let _wallet_qr: ()->CGImage + private let _wallet_receive: ()->CGImage + private let _wallet_send: ()->CGImage + private let _wallet_settings: ()->CGImage + private let _wallet_update: ()->CGImage + private let _wallet_passcode_visible: ()->CGImage + private let _wallet_passcode_hidden: ()->CGImage + private let _wallpaper_color_close: ()->CGImage + private let _wallpaper_color_add: ()->CGImage + private let _wallpaper_color_swap: ()->CGImage + private let _wallpaper_color_rotate: ()->CGImage + private let _login_cap: ()->CGImage + private let _login_qr_cap: ()->CGImage + private let _login_qr_empty_cap: ()->CGImage + private let _chat_failed_scroller: ()->CGImage + private let _chat_failed_scroller_active: ()->CGImage + private let _poll_quiz_unselected: ()->CGImage + private let _poll_selected: ()->CGImage + private let _poll_selected_correct: ()->CGImage + private let _poll_selected_incorrect: ()->CGImage + private let _poll_selected_incoming: ()->CGImage + private let _poll_selected_correct_incoming: ()->CGImage + private let _poll_selected_incorrect_incoming: ()->CGImage + private let _poll_selected_outgoing: ()->CGImage + private let _poll_selected_correct_outgoing: ()->CGImage + private let _poll_selected_incorrect_outgoing: ()->CGImage + private let _chat_filter_edit: ()->CGImage + private let _chat_filter_add: ()->CGImage + private let _chat_filter_bots: ()->CGImage + private let _chat_filter_channels: ()->CGImage + private let _chat_filter_custom: ()->CGImage + private let _chat_filter_groups: ()->CGImage + private let _chat_filter_muted: ()->CGImage + private let _chat_filter_private_chats: ()->CGImage + private let _chat_filter_read: ()->CGImage + private let _chat_filter_secret_chats: ()->CGImage + private let _chat_filter_unmuted: ()->CGImage + private let _chat_filter_unread: ()->CGImage + private let _chat_filter_large_groups: ()->CGImage + private let _chat_filter_non_contacts: ()->CGImage + private let _chat_filter_archive: ()->CGImage + private let _chat_filter_bots_avatar: ()->CGImage + private let _chat_filter_channels_avatar: ()->CGImage + private let _chat_filter_custom_avatar: ()->CGImage + private let _chat_filter_groups_avatar: ()->CGImage + private let _chat_filter_muted_avatar: ()->CGImage + private let _chat_filter_private_chats_avatar: ()->CGImage + private let _chat_filter_read_avatar: ()->CGImage + private let _chat_filter_secret_chats_avatar: ()->CGImage + private let _chat_filter_unmuted_avatar: ()->CGImage + private let _chat_filter_unread_avatar: ()->CGImage + private let _chat_filter_large_groups_avatar: ()->CGImage + private let _chat_filter_non_contacts_avatar: ()->CGImage + private let _chat_filter_archive_avatar: ()->CGImage + private let _group_invite_via_link: ()->CGImage + private let _tab_contacts: ()->CGImage + private let _tab_contacts_active: ()->CGImage + private let _tab_calls: ()->CGImage + private let _tab_calls_active: ()->CGImage + private let _tab_chats: ()->CGImage + private let _tab_chats_active: ()->CGImage + private let _tab_chats_active_filters: ()->CGImage + private let _tab_settings: ()->CGImage + private let _tab_settings_active: ()->CGImage + private let _profile_add_member: ()->CGImage + private let _profile_call: ()->CGImage + private let _profile_leave: ()->CGImage + private let _profile_message: ()->CGImage + private let _profile_more: ()->CGImage + private let _profile_mute: ()->CGImage + private let _profile_unmute: ()->CGImage + private let _profile_search: ()->CGImage + private let _profile_secret_chat: ()->CGImage + private let _profile_edit_photo: ()->CGImage + private let _profile_block: ()->CGImage + private let _profile_report: ()->CGImage + private let _profile_share: ()->CGImage + private let _profile_stats: ()->CGImage + private let _profile_unblock: ()->CGImage + private let _chat_quiz_explanation: ()->CGImage + private let _chat_quiz_explanation_bubble_incoming: ()->CGImage + private let _chat_quiz_explanation_bubble_outgoing: ()->CGImage + private let _stickers_add_featured: ()->CGImage + private let _channel_info_promo: ()->CGImage + private let _channel_info_promo_bubble_incoming: ()->CGImage + private let _channel_info_promo_bubble_outgoing: ()->CGImage + private let _chat_share_message: ()->CGImage + private let _chat_goto_message: ()->CGImage + private let _chat_swipe_reply: ()->CGImage + private let _chat_like_message: ()->CGImage + private let _chat_like_message_unlike: ()->CGImage + private let _chat_like_inside: ()->CGImage + private let _chat_like_inside_bubble_incoming: ()->CGImage + private let _chat_like_inside_bubble_outgoing: ()->CGImage + private let _chat_like_inside_bubble_overlay: ()->CGImage + private let _chat_like_inside_empty: ()->CGImage + private let _chat_like_inside_empty_bubble_incoming: ()->CGImage + private let _chat_like_inside_empty_bubble_outgoing: ()->CGImage + private let _chat_like_inside_empty_bubble_overlay: ()->CGImage + private let _gif_trending: ()->CGImage + private let _chat_list_thumb_play: ()->CGImage + + init( + dialogMuteImage: @escaping()->CGImage, + dialogMuteImageSelected: @escaping()->CGImage, + outgoingMessageImage: @escaping()->CGImage, + readMessageImage: @escaping()->CGImage, + outgoingMessageImageSelected: @escaping()->CGImage, + readMessageImageSelected: @escaping()->CGImage, + sendingImage: @escaping()->CGImage, + sendingImageSelected: @escaping()->CGImage, + secretImage: @escaping()->CGImage, + secretImageSelected: @escaping()->CGImage, + pinnedImage: @escaping()->CGImage, + pinnedImageSelected: @escaping()->CGImage, + verifiedImage: @escaping()->CGImage, + verifiedImageSelected: @escaping()->CGImage, + errorImage: @escaping()->CGImage, + errorImageSelected: @escaping()->CGImage, + chatSearch: @escaping()->CGImage, + chatSearchActive: @escaping()->CGImage, + chatCall: @escaping()->CGImage, + chatActions: @escaping()->CGImage, + chatFailedCall_incoming: @escaping()->CGImage, + chatFailedCall_outgoing: @escaping()->CGImage, + chatCall_incoming: @escaping()->CGImage, + chatCall_outgoing: @escaping()->CGImage, + chatFailedCallBubble_incoming: @escaping()->CGImage, + chatFailedCallBubble_outgoing: @escaping()->CGImage, + chatCallBubble_incoming: @escaping()->CGImage, + chatCallBubble_outgoing: @escaping()->CGImage, + chatFallbackCall: @escaping()->CGImage, + chatFallbackCallBubble_incoming: @escaping()->CGImage, + chatFallbackCallBubble_outgoing: @escaping()->CGImage, + chatToggleSelected: @escaping()->CGImage, + chatToggleUnselected: @escaping()->CGImage, + chatMusicPlay: @escaping()->CGImage, + chatMusicPlayBubble_incoming: @escaping()->CGImage, + chatMusicPlayBubble_outgoing: @escaping()->CGImage, + chatMusicPause: @escaping()->CGImage, + chatMusicPauseBubble_incoming: @escaping()->CGImage, + chatMusicPauseBubble_outgoing: @escaping()->CGImage, + chatGradientBubble_incoming: @escaping()->CGImage, + chatGradientBubble_outgoing: @escaping()->CGImage, + chatBubble_none_incoming_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubble_none_outgoing_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubbleBorder_none_incoming_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubbleBorder_none_outgoing_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubble_both_incoming_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubble_both_outgoing_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubbleBorder_both_incoming_withInset: @escaping()->(CGImage, NSEdgeInsets), + chatBubbleBorder_both_outgoing_withInset: @escaping()->(CGImage, NSEdgeInsets), + composeNewChat: @escaping()->CGImage, + composeNewChatActive: @escaping()->CGImage, + composeNewGroup: @escaping()->CGImage, + composeNewSecretChat: @escaping()->CGImage, + composeNewChannel: @escaping()->CGImage, + contactsNewContact: @escaping()->CGImage, + chatReadMarkInBubble1_incoming: @escaping()->CGImage, + chatReadMarkInBubble2_incoming: @escaping()->CGImage, + chatReadMarkInBubble1_outgoing: @escaping()->CGImage, + chatReadMarkInBubble2_outgoing: @escaping()->CGImage, + chatReadMarkOutBubble1: @escaping()->CGImage, + chatReadMarkOutBubble2: @escaping()->CGImage, + chatReadMarkOverlayBubble1: @escaping()->CGImage, + chatReadMarkOverlayBubble2: @escaping()->CGImage, + sentFailed: @escaping()->CGImage, + chatChannelViewsInBubble_incoming: @escaping()->CGImage, + chatChannelViewsInBubble_outgoing: @escaping()->CGImage, + chatChannelViewsOutBubble: @escaping()->CGImage, + chatChannelViewsOverlayBubble: @escaping()->CGImage, + chatNavigationBack: @escaping()->CGImage, + peerInfoAddMember: @escaping()->CGImage, + chatSearchUp: @escaping()->CGImage, + chatSearchUpDisabled: @escaping()->CGImage, + chatSearchDown: @escaping()->CGImage, + chatSearchDownDisabled: @escaping()->CGImage, + chatSearchCalendar: @escaping()->CGImage, + dismissAccessory: @escaping()->CGImage, + chatScrollUp: @escaping()->CGImage, + chatScrollUpActive: @escaping()->CGImage, + audioPlayerPlay: @escaping()->CGImage, + audioPlayerPause: @escaping()->CGImage, + audioPlayerNext: @escaping()->CGImage, + audioPlayerPrev: @escaping()->CGImage, + auduiPlayerDismiss: @escaping()->CGImage, + audioPlayerRepeat: @escaping()->CGImage, + audioPlayerRepeatActive: @escaping()->CGImage, + audioPlayerLockedPlay: @escaping()->CGImage, + audioPlayerLockedNext: @escaping()->CGImage, + audioPlayerLockedPrev: @escaping()->CGImage, + chatSendMessage: @escaping()->CGImage, + chatSaveEditedMessage: @escaping()->CGImage, + chatRecordVoice: @escaping()->CGImage, + chatEntertainment: @escaping()->CGImage, + chatInlineDismiss: @escaping()->CGImage, + chatActiveReplyMarkup: @escaping()->CGImage, + chatDisabledReplyMarkup: @escaping()->CGImage, + chatSecretTimer: @escaping()->CGImage, + chatForwardMessagesActive: @escaping()->CGImage, + chatForwardMessagesInactive: @escaping()->CGImage, + chatDeleteMessagesActive: @escaping()->CGImage, + chatDeleteMessagesInactive: @escaping()->CGImage, + generalNext: @escaping()->CGImage, + generalNextActive: @escaping()->CGImage, + generalSelect: @escaping()->CGImage, + chatVoiceRecording: @escaping()->CGImage, + chatVideoRecording: @escaping()->CGImage, + chatRecord: @escaping()->CGImage, + deleteItem: @escaping()->CGImage, + deleteItemDisabled: @escaping()->CGImage, + chatAttach: @escaping()->CGImage, + chatAttachFile: @escaping()->CGImage, + chatAttachPhoto: @escaping()->CGImage, + chatAttachCamera: @escaping()->CGImage, + chatAttachLocation: @escaping()->CGImage, + chatAttachPoll: @escaping()->CGImage, + mediaEmptyShared: @escaping()->CGImage, + mediaEmptyFiles: @escaping()->CGImage, + mediaEmptyMusic: @escaping()->CGImage, + mediaEmptyLinks: @escaping()->CGImage, + stickersAddFeatured: @escaping()->CGImage, + stickersAddedFeatured: @escaping()->CGImage, + stickersRemove: @escaping()->CGImage, + peerMediaDownloadFileStart: @escaping()->CGImage, + peerMediaDownloadFilePause: @escaping()->CGImage, + stickersShare: @escaping()->CGImage, + emojiRecentTab: @escaping()->CGImage, + emojiSmileTab: @escaping()->CGImage, + emojiNatureTab: @escaping()->CGImage, + emojiFoodTab: @escaping()->CGImage, + emojiSportTab: @escaping()->CGImage, + emojiCarTab: @escaping()->CGImage, + emojiObjectsTab: @escaping()->CGImage, + emojiSymbolsTab: @escaping()->CGImage, + emojiFlagsTab: @escaping()->CGImage, + emojiRecentTabActive: @escaping()->CGImage, + emojiSmileTabActive: @escaping()->CGImage, + emojiNatureTabActive: @escaping()->CGImage, + emojiFoodTabActive: @escaping()->CGImage, + emojiSportTabActive: @escaping()->CGImage, + emojiCarTabActive: @escaping()->CGImage, + emojiObjectsTabActive: @escaping()->CGImage, + emojiSymbolsTabActive: @escaping()->CGImage, + emojiFlagsTabActive: @escaping()->CGImage, + stickerBackground: @escaping()->CGImage, + stickerBackgroundActive: @escaping()->CGImage, + stickersTabRecent: @escaping()->CGImage, + stickersTabGIF: @escaping()->CGImage, + chatSendingInFrame_incoming: @escaping()->CGImage, + chatSendingInHour_incoming: @escaping()->CGImage, + chatSendingInMin_incoming: @escaping()->CGImage, + chatSendingInFrame_outgoing: @escaping()->CGImage, + chatSendingInHour_outgoing: @escaping()->CGImage, + chatSendingInMin_outgoing: @escaping()->CGImage, + chatSendingOutFrame: @escaping()->CGImage, + chatSendingOutHour: @escaping()->CGImage, + chatSendingOutMin: @escaping()->CGImage, + chatSendingOverlayFrame: @escaping()->CGImage, + chatSendingOverlayHour: @escaping()->CGImage, + chatSendingOverlayMin: @escaping()->CGImage, + chatActionUrl: @escaping()->CGImage, + callInlineDecline: @escaping()->CGImage, + callInlineMuted: @escaping()->CGImage, + callInlineUnmuted: @escaping()->CGImage, + eventLogTriangle: @escaping()->CGImage, + channelIntro: @escaping()->CGImage, + chatFileThumb: @escaping()->CGImage, + chatFileThumbBubble_incoming: @escaping()->CGImage, + chatFileThumbBubble_outgoing: @escaping()->CGImage, + chatSecretThumb: @escaping()->CGImage, + chatSecretThumbSmall: @escaping()->CGImage, + chatMapPin: @escaping()->CGImage, + chatSecretTitle: @escaping()->CGImage, + emptySearch: @escaping()->CGImage, + calendarBack: @escaping()->CGImage, + calendarNext: @escaping()->CGImage, + calendarBackDisabled: @escaping()->CGImage, + calendarNextDisabled: @escaping()->CGImage, + newChatCamera: @escaping()->CGImage, + peerInfoVerify: @escaping()->CGImage, + peerInfoVerifyProfile: @escaping()->CGImage, + peerInfoCall: @escaping()->CGImage, + callOutgoing: @escaping()->CGImage, + recentDismiss: @escaping()->CGImage, + recentDismissActive: @escaping()->CGImage, + webgameShare: @escaping()->CGImage, + chatSearchCancel: @escaping()->CGImage, + chatSearchFrom: @escaping()->CGImage, + callWindowDecline: @escaping()->CGImage, + callWindowAccept: @escaping()->CGImage, + callWindowMute: @escaping()->CGImage, + callWindowUnmute: @escaping()->CGImage, + callWindowClose: @escaping()->CGImage, + callWindowDeviceSettings: @escaping()->CGImage, + callSettings: @escaping()->CGImage, + callWindowCancel: @escaping()->CGImage, + chatActionEdit: @escaping()->CGImage, + chatActionInfo: @escaping()->CGImage, + chatActionMute: @escaping()->CGImage, + chatActionUnmute: @escaping()->CGImage, + chatActionClearHistory: @escaping()->CGImage, + chatActionDeleteChat: @escaping()->CGImage, + dismissPinned: @escaping()->CGImage, + chatActionsActive: @escaping()->CGImage, + chatEntertainmentSticker: @escaping()->CGImage, + chatEmpty: @escaping()->CGImage, + stickerPackClose: @escaping()->CGImage, + stickerPackDelete: @escaping()->CGImage, + modalShare: @escaping()->CGImage, + modalClose: @escaping()->CGImage, + ivChannelJoined: @escaping()->CGImage, + chatListMention: @escaping()->CGImage, + chatListMentionActive: @escaping()->CGImage, + chatListMentionArchived: @escaping()->CGImage, + chatListMentionArchivedActive: @escaping()->CGImage, + chatMention: @escaping()->CGImage, + chatMentionActive: @escaping()->CGImage, + sliderControl: @escaping()->CGImage, + sliderControlActive: @escaping()->CGImage, + stickersTabFave: @escaping()->CGImage, + chatInstantView: @escaping()->CGImage, + chatInstantViewBubble_incoming: @escaping()->CGImage, + chatInstantViewBubble_outgoing: @escaping()->CGImage, + instantViewShare: @escaping()->CGImage, + instantViewActions: @escaping()->CGImage, + instantViewActionsActive: @escaping()->CGImage, + instantViewSafari: @escaping()->CGImage, + instantViewBack: @escaping()->CGImage, + instantViewCheck: @escaping()->CGImage, + groupStickerNotFound: @escaping()->CGImage, + settingsAskQuestion: @escaping()->CGImage, + settingsFaq: @escaping()->CGImage, + settingsGeneral: @escaping()->CGImage, + settingsLanguage: @escaping()->CGImage, + settingsNotifications: @escaping()->CGImage, + settingsSecurity: @escaping()->CGImage, + settingsStickers: @escaping()->CGImage, + settingsStorage: @escaping()->CGImage, + settingsSessions: @escaping()->CGImage, + settingsProxy: @escaping()->CGImage, + settingsAppearance: @escaping()->CGImage, + settingsPassport: @escaping()->CGImage, + settingsWallet: @escaping()->CGImage, + settingsUpdate: @escaping()->CGImage, + settingsFilters: @escaping()->CGImage, + settingsAskQuestionActive: @escaping()->CGImage, + settingsFaqActive: @escaping()->CGImage, + settingsGeneralActive: @escaping()->CGImage, + settingsLanguageActive: @escaping()->CGImage, + settingsNotificationsActive: @escaping()->CGImage, + settingsSecurityActive: @escaping()->CGImage, + settingsStickersActive: @escaping()->CGImage, + settingsStorageActive: @escaping()->CGImage, + settingsSessionsActive: @escaping()->CGImage, + settingsProxyActive: @escaping()->CGImage, + settingsAppearanceActive: @escaping()->CGImage, + settingsPassportActive: @escaping()->CGImage, + settingsWalletActive: @escaping()->CGImage, + settingsUpdateActive: @escaping()->CGImage, + settingsFiltersActive: @escaping()->CGImage, + settingsProfile: @escaping()->CGImage, + generalCheck: @escaping()->CGImage, + settingsAbout: @escaping()->CGImage, + settingsLogout: @escaping()->CGImage, + fastSettingsLock: @escaping()->CGImage, + fastSettingsDark: @escaping()->CGImage, + fastSettingsSunny: @escaping()->CGImage, + fastSettingsMute: @escaping()->CGImage, + fastSettingsUnmute: @escaping()->CGImage, + chatRecordVideo: @escaping()->CGImage, + inputChannelMute: @escaping()->CGImage, + inputChannelUnmute: @escaping()->CGImage, + changePhoneNumberIntro: @escaping()->CGImage, + peerSavedMessages: @escaping()->CGImage, + previewSenderCollage: @escaping()->CGImage, + previewSenderPhoto: @escaping()->CGImage, + previewSenderFile: @escaping()->CGImage, + previewSenderCrop: @escaping()->CGImage, + previewSenderDelete: @escaping()->CGImage, + previewSenderDeleteFile: @escaping()->CGImage, + previewSenderArchive: @escaping()->CGImage, + chatGroupToggleSelected: @escaping()->CGImage, + chatGroupToggleUnselected: @escaping()->CGImage, + successModalProgress: @escaping()->CGImage, + accentColorSelect: @escaping()->CGImage, + transparentBackground: @escaping()->CGImage, + lottieTransparentBackground: @escaping()->CGImage, + passcodeTouchId: @escaping()->CGImage, + passcodeLogin: @escaping()->CGImage, + confirmDeleteMessagesAccessory: @escaping()->CGImage, + alertCheckBoxSelected: @escaping()->CGImage, + alertCheckBoxUnselected: @escaping()->CGImage, + confirmPinAccessory: @escaping()->CGImage, + confirmDeleteChatAccessory: @escaping()->CGImage, + stickersEmptySearch: @escaping()->CGImage, + twoStepVerificationCreateIntro: @escaping()->CGImage, + secureIdAuth: @escaping()->CGImage, + ivAudioPlay: @escaping()->CGImage, + ivAudioPause: @escaping()->CGImage, + proxyEnable: @escaping()->CGImage, + proxyEnabled: @escaping()->CGImage, + proxyState: @escaping()->CGImage, + proxyDeleteListItem: @escaping()->CGImage, + proxyInfoListItem: @escaping()->CGImage, + proxyConnectedListItem: @escaping()->CGImage, + proxyAddProxy: @escaping()->CGImage, + proxyNextWaitingListItem: @escaping()->CGImage, + passportForgotPassword: @escaping()->CGImage, + confirmAppAccessoryIcon: @escaping()->CGImage, + passportPassport: @escaping()->CGImage, + passportIdCardReverse: @escaping()->CGImage, + passportIdCard: @escaping()->CGImage, + passportSelfie: @escaping()->CGImage, + passportDriverLicense: @escaping()->CGImage, + chatOverlayVoiceRecording: @escaping()->CGImage, + chatOverlayVideoRecording: @escaping()->CGImage, + chatOverlaySendRecording: @escaping()->CGImage, + chatOverlayLockArrowRecording: @escaping()->CGImage, + chatOverlayLockerBodyRecording: @escaping()->CGImage, + chatOverlayLockerHeadRecording: @escaping()->CGImage, + locationPin: @escaping()->CGImage, + locationMapPin: @escaping()->CGImage, + locationMapLocate: @escaping()->CGImage, + locationMapLocated: @escaping()->CGImage, + passportSettings: @escaping()->CGImage, + passportInfo: @escaping()->CGImage, + editMessageMedia: @escaping()->CGImage, + playerMusicPlaceholder: @escaping()->CGImage, + chatMusicPlaceholder: @escaping()->CGImage, + chatMusicPlaceholderCap: @escaping()->CGImage, + searchArticle: @escaping()->CGImage, + searchSaved: @escaping()->CGImage, + archivedChats: @escaping()->CGImage, + hintPeerActive: @escaping()->CGImage, + hintPeerActiveSelected: @escaping()->CGImage, + chatSwiping_delete: @escaping()->CGImage, + chatSwiping_mute: @escaping()->CGImage, + chatSwiping_unmute: @escaping()->CGImage, + chatSwiping_read: @escaping()->CGImage, + chatSwiping_unread: @escaping()->CGImage, + chatSwiping_pin: @escaping()->CGImage, + chatSwiping_unpin: @escaping()->CGImage, + chatSwiping_archive: @escaping()->CGImage, + chatSwiping_unarchive: @escaping()->CGImage, + galleryPrev: @escaping()->CGImage, + galleryNext: @escaping()->CGImage, + galleryMore: @escaping()->CGImage, + galleryShare: @escaping()->CGImage, + galleryFastSave: @escaping()->CGImage, + playingVoice1x: @escaping()->CGImage, + playingVoice2x: @escaping()->CGImage, + galleryRotate: @escaping()->CGImage, + galleryZoomIn: @escaping()->CGImage, + galleryZoomOut: @escaping()->CGImage, + editMessageCurrentPhoto: @escaping()->CGImage, + videoPlayerPlay: @escaping()->CGImage, + videoPlayerPause: @escaping()->CGImage, + videoPlayerEnterFullScreen: @escaping()->CGImage, + videoPlayerExitFullScreen: @escaping()->CGImage, + videoPlayerPIPIn: @escaping()->CGImage, + videoPlayerPIPOut: @escaping()->CGImage, + videoPlayerRewind15Forward: @escaping()->CGImage, + videoPlayerRewind15Backward: @escaping()->CGImage, + videoPlayerVolume: @escaping()->CGImage, + videoPlayerVolumeOff: @escaping()->CGImage, + videoPlayerClose: @escaping()->CGImage, + videoPlayerSliderInteractor: @escaping()->CGImage, + streamingVideoDownload: @escaping()->CGImage, + videoCompactFetching: @escaping()->CGImage, + compactStreamingFetchingCancel: @escaping()->CGImage, + customLocalizationDelete: @escaping()->CGImage, + pollAddOption: @escaping()->CGImage, + pollDeleteOption: @escaping()->CGImage, + resort: @escaping()->CGImage, + chatPollVoteUnselected: @escaping()->CGImage, + chatPollVoteUnselectedBubble_incoming: @escaping()->CGImage, + chatPollVoteUnselectedBubble_outgoing: @escaping()->CGImage, + peerInfoAdmins: @escaping()->CGImage, + peerInfoPermissions: @escaping()->CGImage, + peerInfoBanned: @escaping()->CGImage, + peerInfoMembers: @escaping()->CGImage, + chatUndoAction: @escaping()->CGImage, + appUpdate: @escaping()->CGImage, + inlineVideoSoundOff: @escaping()->CGImage, + inlineVideoSoundOn: @escaping()->CGImage, + logoutOptionAddAccount: @escaping()->CGImage, + logoutOptionSetPasscode: @escaping()->CGImage, + logoutOptionClearCache: @escaping()->CGImage, + logoutOptionChangePhoneNumber: @escaping()->CGImage, + logoutOptionContactSupport: @escaping()->CGImage, + disableEmojiPrediction: @escaping()->CGImage, + scam: @escaping()->CGImage, + scamActive: @escaping()->CGImage, + chatScam: @escaping()->CGImage, + chatUnarchive: @escaping()->CGImage, + chatArchive: @escaping()->CGImage, + privacySettings_blocked: @escaping()->CGImage, + privacySettings_activeSessions: @escaping()->CGImage, + privacySettings_passcode: @escaping()->CGImage, + privacySettings_twoStep: @escaping()->CGImage, + deletedAccount: @escaping()->CGImage, + stickerPackSelection: @escaping()->CGImage, + stickerPackSelectionActive: @escaping()->CGImage, + entertainment_Emoji: @escaping()->CGImage, + entertainment_Stickers: @escaping()->CGImage, + entertainment_Gifs: @escaping()->CGImage, + entertainment_Search: @escaping()->CGImage, + entertainment_Settings: @escaping()->CGImage, + entertainment_SearchCancel: @escaping()->CGImage, + scheduledAvatar: @escaping()->CGImage, + scheduledInputAction: @escaping()->CGImage, + verifyDialog: @escaping()->CGImage, + verifyDialogActive: @escaping()->CGImage, + chatInputScheduled: @escaping()->CGImage, + appearanceAddPlatformTheme: @escaping()->CGImage, + wallet_close: @escaping()->CGImage, + wallet_qr: @escaping()->CGImage, + wallet_receive: @escaping()->CGImage, + wallet_send: @escaping()->CGImage, + wallet_settings: @escaping()->CGImage, + wallet_update: @escaping()->CGImage, + wallet_passcode_visible: @escaping()->CGImage, + wallet_passcode_hidden: @escaping()->CGImage, + wallpaper_color_close: @escaping()->CGImage, + wallpaper_color_add: @escaping()->CGImage, + wallpaper_color_swap: @escaping()->CGImage, + wallpaper_color_rotate: @escaping()->CGImage, + login_cap: @escaping()->CGImage, + login_qr_cap: @escaping()->CGImage, + login_qr_empty_cap: @escaping()->CGImage, + chat_failed_scroller: @escaping()->CGImage, + chat_failed_scroller_active: @escaping()->CGImage, + poll_quiz_unselected: @escaping()->CGImage, + poll_selected: @escaping()->CGImage, + poll_selected_correct: @escaping()->CGImage, + poll_selected_incorrect: @escaping()->CGImage, + poll_selected_incoming: @escaping()->CGImage, + poll_selected_correct_incoming: @escaping()->CGImage, + poll_selected_incorrect_incoming: @escaping()->CGImage, + poll_selected_outgoing: @escaping()->CGImage, + poll_selected_correct_outgoing: @escaping()->CGImage, + poll_selected_incorrect_outgoing: @escaping()->CGImage, + chat_filter_edit: @escaping()->CGImage, + chat_filter_add: @escaping()->CGImage, + chat_filter_bots: @escaping()->CGImage, + chat_filter_channels: @escaping()->CGImage, + chat_filter_custom: @escaping()->CGImage, + chat_filter_groups: @escaping()->CGImage, + chat_filter_muted: @escaping()->CGImage, + chat_filter_private_chats: @escaping()->CGImage, + chat_filter_read: @escaping()->CGImage, + chat_filter_secret_chats: @escaping()->CGImage, + chat_filter_unmuted: @escaping()->CGImage, + chat_filter_unread: @escaping()->CGImage, + chat_filter_large_groups: @escaping()->CGImage, + chat_filter_non_contacts: @escaping()->CGImage, + chat_filter_archive: @escaping()->CGImage, + chat_filter_bots_avatar: @escaping()->CGImage, + chat_filter_channels_avatar: @escaping()->CGImage, + chat_filter_custom_avatar: @escaping()->CGImage, + chat_filter_groups_avatar: @escaping()->CGImage, + chat_filter_muted_avatar: @escaping()->CGImage, + chat_filter_private_chats_avatar: @escaping()->CGImage, + chat_filter_read_avatar: @escaping()->CGImage, + chat_filter_secret_chats_avatar: @escaping()->CGImage, + chat_filter_unmuted_avatar: @escaping()->CGImage, + chat_filter_unread_avatar: @escaping()->CGImage, + chat_filter_large_groups_avatar: @escaping()->CGImage, + chat_filter_non_contacts_avatar: @escaping()->CGImage, + chat_filter_archive_avatar: @escaping()->CGImage, + group_invite_via_link: @escaping()->CGImage, + tab_contacts: @escaping()->CGImage, + tab_contacts_active: @escaping()->CGImage, + tab_calls: @escaping()->CGImage, + tab_calls_active: @escaping()->CGImage, + tab_chats: @escaping()->CGImage, + tab_chats_active: @escaping()->CGImage, + tab_chats_active_filters: @escaping()->CGImage, + tab_settings: @escaping()->CGImage, + tab_settings_active: @escaping()->CGImage, + profile_add_member: @escaping()->CGImage, + profile_call: @escaping()->CGImage, + profile_leave: @escaping()->CGImage, + profile_message: @escaping()->CGImage, + profile_more: @escaping()->CGImage, + profile_mute: @escaping()->CGImage, + profile_unmute: @escaping()->CGImage, + profile_search: @escaping()->CGImage, + profile_secret_chat: @escaping()->CGImage, + profile_edit_photo: @escaping()->CGImage, + profile_block: @escaping()->CGImage, + profile_report: @escaping()->CGImage, + profile_share: @escaping()->CGImage, + profile_stats: @escaping()->CGImage, + profile_unblock: @escaping()->CGImage, + chat_quiz_explanation: @escaping()->CGImage, + chat_quiz_explanation_bubble_incoming: @escaping()->CGImage, + chat_quiz_explanation_bubble_outgoing: @escaping()->CGImage, + stickers_add_featured: @escaping()->CGImage, + channel_info_promo: @escaping()->CGImage, + channel_info_promo_bubble_incoming: @escaping()->CGImage, + channel_info_promo_bubble_outgoing: @escaping()->CGImage, + chat_share_message: @escaping()->CGImage, + chat_goto_message: @escaping()->CGImage, + chat_swipe_reply: @escaping()->CGImage, + chat_like_message: @escaping()->CGImage, + chat_like_message_unlike: @escaping()->CGImage, + chat_like_inside: @escaping()->CGImage, + chat_like_inside_bubble_incoming: @escaping()->CGImage, + chat_like_inside_bubble_outgoing: @escaping()->CGImage, + chat_like_inside_bubble_overlay: @escaping()->CGImage, + chat_like_inside_empty: @escaping()->CGImage, + chat_like_inside_empty_bubble_incoming: @escaping()->CGImage, + chat_like_inside_empty_bubble_outgoing: @escaping()->CGImage, + chat_like_inside_empty_bubble_overlay: @escaping()->CGImage, + gif_trending: @escaping()->CGImage, + chat_list_thumb_play: @escaping()->CGImage + ) { + self._dialogMuteImage = dialogMuteImage + self._dialogMuteImageSelected = dialogMuteImageSelected + self._outgoingMessageImage = outgoingMessageImage + self._readMessageImage = readMessageImage + self._outgoingMessageImageSelected = outgoingMessageImageSelected + self._readMessageImageSelected = readMessageImageSelected + self._sendingImage = sendingImage + self._sendingImageSelected = sendingImageSelected + self._secretImage = secretImage + self._secretImageSelected = secretImageSelected + self._pinnedImage = pinnedImage + self._pinnedImageSelected = pinnedImageSelected + self._verifiedImage = verifiedImage + self._verifiedImageSelected = verifiedImageSelected + self._errorImage = errorImage + self._errorImageSelected = errorImageSelected + self._chatSearch = chatSearch + self._chatSearchActive = chatSearchActive + self._chatCall = chatCall + self._chatActions = chatActions + self._chatFailedCall_incoming = chatFailedCall_incoming + self._chatFailedCall_outgoing = chatFailedCall_outgoing + self._chatCall_incoming = chatCall_incoming + self._chatCall_outgoing = chatCall_outgoing + self._chatFailedCallBubble_incoming = chatFailedCallBubble_incoming + self._chatFailedCallBubble_outgoing = chatFailedCallBubble_outgoing + self._chatCallBubble_incoming = chatCallBubble_incoming + self._chatCallBubble_outgoing = chatCallBubble_outgoing + self._chatFallbackCall = chatFallbackCall + self._chatFallbackCallBubble_incoming = chatFallbackCallBubble_incoming + self._chatFallbackCallBubble_outgoing = chatFallbackCallBubble_outgoing + self._chatToggleSelected = chatToggleSelected + self._chatToggleUnselected = chatToggleUnselected + self._chatMusicPlay = chatMusicPlay + self._chatMusicPlayBubble_incoming = chatMusicPlayBubble_incoming + self._chatMusicPlayBubble_outgoing = chatMusicPlayBubble_outgoing + self._chatMusicPause = chatMusicPause + self._chatMusicPauseBubble_incoming = chatMusicPauseBubble_incoming + self._chatMusicPauseBubble_outgoing = chatMusicPauseBubble_outgoing + self._chatGradientBubble_incoming = chatGradientBubble_incoming + self._chatGradientBubble_outgoing = chatGradientBubble_outgoing + self._chatBubble_none_incoming_withInset = chatBubble_none_incoming_withInset + self._chatBubble_none_outgoing_withInset = chatBubble_none_outgoing_withInset + self._chatBubbleBorder_none_incoming_withInset = chatBubbleBorder_none_incoming_withInset + self._chatBubbleBorder_none_outgoing_withInset = chatBubbleBorder_none_outgoing_withInset + self._chatBubble_both_incoming_withInset = chatBubble_both_incoming_withInset + self._chatBubble_both_outgoing_withInset = chatBubble_both_outgoing_withInset + self._chatBubbleBorder_both_incoming_withInset = chatBubbleBorder_both_incoming_withInset + self._chatBubbleBorder_both_outgoing_withInset = chatBubbleBorder_both_outgoing_withInset + self._composeNewChat = composeNewChat + self._composeNewChatActive = composeNewChatActive + self._composeNewGroup = composeNewGroup + self._composeNewSecretChat = composeNewSecretChat + self._composeNewChannel = composeNewChannel + self._contactsNewContact = contactsNewContact + self._chatReadMarkInBubble1_incoming = chatReadMarkInBubble1_incoming + self._chatReadMarkInBubble2_incoming = chatReadMarkInBubble2_incoming + self._chatReadMarkInBubble1_outgoing = chatReadMarkInBubble1_outgoing + self._chatReadMarkInBubble2_outgoing = chatReadMarkInBubble2_outgoing + self._chatReadMarkOutBubble1 = chatReadMarkOutBubble1 + self._chatReadMarkOutBubble2 = chatReadMarkOutBubble2 + self._chatReadMarkOverlayBubble1 = chatReadMarkOverlayBubble1 + self._chatReadMarkOverlayBubble2 = chatReadMarkOverlayBubble2 + self._sentFailed = sentFailed + self._chatChannelViewsInBubble_incoming = chatChannelViewsInBubble_incoming + self._chatChannelViewsInBubble_outgoing = chatChannelViewsInBubble_outgoing + self._chatChannelViewsOutBubble = chatChannelViewsOutBubble + self._chatChannelViewsOverlayBubble = chatChannelViewsOverlayBubble + self._chatNavigationBack = chatNavigationBack + self._peerInfoAddMember = peerInfoAddMember + self._chatSearchUp = chatSearchUp + self._chatSearchUpDisabled = chatSearchUpDisabled + self._chatSearchDown = chatSearchDown + self._chatSearchDownDisabled = chatSearchDownDisabled + self._chatSearchCalendar = chatSearchCalendar + self._dismissAccessory = dismissAccessory + self._chatScrollUp = chatScrollUp + self._chatScrollUpActive = chatScrollUpActive + self._audioPlayerPlay = audioPlayerPlay + self._audioPlayerPause = audioPlayerPause + self._audioPlayerNext = audioPlayerNext + self._audioPlayerPrev = audioPlayerPrev + self._auduiPlayerDismiss = auduiPlayerDismiss + self._audioPlayerRepeat = audioPlayerRepeat + self._audioPlayerRepeatActive = audioPlayerRepeatActive + self._audioPlayerLockedPlay = audioPlayerLockedPlay + self._audioPlayerLockedNext = audioPlayerLockedNext + self._audioPlayerLockedPrev = audioPlayerLockedPrev + self._chatSendMessage = chatSendMessage + self._chatSaveEditedMessage = chatSaveEditedMessage + self._chatRecordVoice = chatRecordVoice + self._chatEntertainment = chatEntertainment + self._chatInlineDismiss = chatInlineDismiss + self._chatActiveReplyMarkup = chatActiveReplyMarkup + self._chatDisabledReplyMarkup = chatDisabledReplyMarkup + self._chatSecretTimer = chatSecretTimer + self._chatForwardMessagesActive = chatForwardMessagesActive + self._chatForwardMessagesInactive = chatForwardMessagesInactive + self._chatDeleteMessagesActive = chatDeleteMessagesActive + self._chatDeleteMessagesInactive = chatDeleteMessagesInactive + self._generalNext = generalNext + self._generalNextActive = generalNextActive + self._generalSelect = generalSelect + self._chatVoiceRecording = chatVoiceRecording + self._chatVideoRecording = chatVideoRecording + self._chatRecord = chatRecord + self._deleteItem = deleteItem + self._deleteItemDisabled = deleteItemDisabled + self._chatAttach = chatAttach + self._chatAttachFile = chatAttachFile + self._chatAttachPhoto = chatAttachPhoto + self._chatAttachCamera = chatAttachCamera + self._chatAttachLocation = chatAttachLocation + self._chatAttachPoll = chatAttachPoll + self._mediaEmptyShared = mediaEmptyShared + self._mediaEmptyFiles = mediaEmptyFiles + self._mediaEmptyMusic = mediaEmptyMusic + self._mediaEmptyLinks = mediaEmptyLinks + self._stickersAddFeatured = stickersAddFeatured + self._stickersAddedFeatured = stickersAddedFeatured + self._stickersRemove = stickersRemove + self._peerMediaDownloadFileStart = peerMediaDownloadFileStart + self._peerMediaDownloadFilePause = peerMediaDownloadFilePause + self._stickersShare = stickersShare + self._emojiRecentTab = emojiRecentTab + self._emojiSmileTab = emojiSmileTab + self._emojiNatureTab = emojiNatureTab + self._emojiFoodTab = emojiFoodTab + self._emojiSportTab = emojiSportTab + self._emojiCarTab = emojiCarTab + self._emojiObjectsTab = emojiObjectsTab + self._emojiSymbolsTab = emojiSymbolsTab + self._emojiFlagsTab = emojiFlagsTab + self._emojiRecentTabActive = emojiRecentTabActive + self._emojiSmileTabActive = emojiSmileTabActive + self._emojiNatureTabActive = emojiNatureTabActive + self._emojiFoodTabActive = emojiFoodTabActive + self._emojiSportTabActive = emojiSportTabActive + self._emojiCarTabActive = emojiCarTabActive + self._emojiObjectsTabActive = emojiObjectsTabActive + self._emojiSymbolsTabActive = emojiSymbolsTabActive + self._emojiFlagsTabActive = emojiFlagsTabActive + self._stickerBackground = stickerBackground + self._stickerBackgroundActive = stickerBackgroundActive + self._stickersTabRecent = stickersTabRecent + self._stickersTabGIF = stickersTabGIF + self._chatSendingInFrame_incoming = chatSendingInFrame_incoming + self._chatSendingInHour_incoming = chatSendingInHour_incoming + self._chatSendingInMin_incoming = chatSendingInMin_incoming + self._chatSendingInFrame_outgoing = chatSendingInFrame_outgoing + self._chatSendingInHour_outgoing = chatSendingInHour_outgoing + self._chatSendingInMin_outgoing = chatSendingInMin_outgoing + self._chatSendingOutFrame = chatSendingOutFrame + self._chatSendingOutHour = chatSendingOutHour + self._chatSendingOutMin = chatSendingOutMin + self._chatSendingOverlayFrame = chatSendingOverlayFrame + self._chatSendingOverlayHour = chatSendingOverlayHour + self._chatSendingOverlayMin = chatSendingOverlayMin + self._chatActionUrl = chatActionUrl + self._callInlineDecline = callInlineDecline + self._callInlineMuted = callInlineMuted + self._callInlineUnmuted = callInlineUnmuted + self._eventLogTriangle = eventLogTriangle + self._channelIntro = channelIntro + self._chatFileThumb = chatFileThumb + self._chatFileThumbBubble_incoming = chatFileThumbBubble_incoming + self._chatFileThumbBubble_outgoing = chatFileThumbBubble_outgoing + self._chatSecretThumb = chatSecretThumb + self._chatSecretThumbSmall = chatSecretThumbSmall + self._chatMapPin = chatMapPin + self._chatSecretTitle = chatSecretTitle + self._emptySearch = emptySearch + self._calendarBack = calendarBack + self._calendarNext = calendarNext + self._calendarBackDisabled = calendarBackDisabled + self._calendarNextDisabled = calendarNextDisabled + self._newChatCamera = newChatCamera + self._peerInfoVerify = peerInfoVerify + self._peerInfoVerifyProfile = peerInfoVerifyProfile + self._peerInfoCall = peerInfoCall + self._callOutgoing = callOutgoing + self._recentDismiss = recentDismiss + self._recentDismissActive = recentDismissActive + self._webgameShare = webgameShare + self._chatSearchCancel = chatSearchCancel + self._chatSearchFrom = chatSearchFrom + self._callWindowDecline = callWindowDecline + self._callWindowAccept = callWindowAccept + self._callWindowMute = callWindowMute + self._callWindowUnmute = callWindowUnmute + self._callWindowClose = callWindowClose + self._callWindowDeviceSettings = callWindowDeviceSettings + self._callSettings = callSettings + self._callWindowCancel = callWindowCancel + self._chatActionEdit = chatActionEdit + self._chatActionInfo = chatActionInfo + self._chatActionMute = chatActionMute + self._chatActionUnmute = chatActionUnmute + self._chatActionClearHistory = chatActionClearHistory + self._chatActionDeleteChat = chatActionDeleteChat + self._dismissPinned = dismissPinned + self._chatActionsActive = chatActionsActive + self._chatEntertainmentSticker = chatEntertainmentSticker + self._chatEmpty = chatEmpty + self._stickerPackClose = stickerPackClose + self._stickerPackDelete = stickerPackDelete + self._modalShare = modalShare + self._modalClose = modalClose + self._ivChannelJoined = ivChannelJoined + self._chatListMention = chatListMention + self._chatListMentionActive = chatListMentionActive + self._chatListMentionArchived = chatListMentionArchived + self._chatListMentionArchivedActive = chatListMentionArchivedActive + self._chatMention = chatMention + self._chatMentionActive = chatMentionActive + self._sliderControl = sliderControl + self._sliderControlActive = sliderControlActive + self._stickersTabFave = stickersTabFave + self._chatInstantView = chatInstantView + self._chatInstantViewBubble_incoming = chatInstantViewBubble_incoming + self._chatInstantViewBubble_outgoing = chatInstantViewBubble_outgoing + self._instantViewShare = instantViewShare + self._instantViewActions = instantViewActions + self._instantViewActionsActive = instantViewActionsActive + self._instantViewSafari = instantViewSafari + self._instantViewBack = instantViewBack + self._instantViewCheck = instantViewCheck + self._groupStickerNotFound = groupStickerNotFound + self._settingsAskQuestion = settingsAskQuestion + self._settingsFaq = settingsFaq + self._settingsGeneral = settingsGeneral + self._settingsLanguage = settingsLanguage + self._settingsNotifications = settingsNotifications + self._settingsSecurity = settingsSecurity + self._settingsStickers = settingsStickers + self._settingsStorage = settingsStorage + self._settingsSessions = settingsSessions + self._settingsProxy = settingsProxy + self._settingsAppearance = settingsAppearance + self._settingsPassport = settingsPassport + self._settingsWallet = settingsWallet + self._settingsUpdate = settingsUpdate + self._settingsFilters = settingsFilters + self._settingsAskQuestionActive = settingsAskQuestionActive + self._settingsFaqActive = settingsFaqActive + self._settingsGeneralActive = settingsGeneralActive + self._settingsLanguageActive = settingsLanguageActive + self._settingsNotificationsActive = settingsNotificationsActive + self._settingsSecurityActive = settingsSecurityActive + self._settingsStickersActive = settingsStickersActive + self._settingsStorageActive = settingsStorageActive + self._settingsSessionsActive = settingsSessionsActive + self._settingsProxyActive = settingsProxyActive + self._settingsAppearanceActive = settingsAppearanceActive + self._settingsPassportActive = settingsPassportActive + self._settingsWalletActive = settingsWalletActive + self._settingsUpdateActive = settingsUpdateActive + self._settingsFiltersActive = settingsFiltersActive + self._settingsProfile = settingsProfile + self._generalCheck = generalCheck + self._settingsAbout = settingsAbout + self._settingsLogout = settingsLogout + self._fastSettingsLock = fastSettingsLock + self._fastSettingsDark = fastSettingsDark + self._fastSettingsSunny = fastSettingsSunny + self._fastSettingsMute = fastSettingsMute + self._fastSettingsUnmute = fastSettingsUnmute + self._chatRecordVideo = chatRecordVideo + self._inputChannelMute = inputChannelMute + self._inputChannelUnmute = inputChannelUnmute + self._changePhoneNumberIntro = changePhoneNumberIntro + self._peerSavedMessages = peerSavedMessages + self._previewSenderCollage = previewSenderCollage + self._previewSenderPhoto = previewSenderPhoto + self._previewSenderFile = previewSenderFile + self._previewSenderCrop = previewSenderCrop + self._previewSenderDelete = previewSenderDelete + self._previewSenderDeleteFile = previewSenderDeleteFile + self._previewSenderArchive = previewSenderArchive + self._chatGroupToggleSelected = chatGroupToggleSelected + self._chatGroupToggleUnselected = chatGroupToggleUnselected + self._successModalProgress = successModalProgress + self._accentColorSelect = accentColorSelect + self._transparentBackground = transparentBackground + self._lottieTransparentBackground = lottieTransparentBackground + self._passcodeTouchId = passcodeTouchId + self._passcodeLogin = passcodeLogin + self._confirmDeleteMessagesAccessory = confirmDeleteMessagesAccessory + self._alertCheckBoxSelected = alertCheckBoxSelected + self._alertCheckBoxUnselected = alertCheckBoxUnselected + self._confirmPinAccessory = confirmPinAccessory + self._confirmDeleteChatAccessory = confirmDeleteChatAccessory + self._stickersEmptySearch = stickersEmptySearch + self._twoStepVerificationCreateIntro = twoStepVerificationCreateIntro + self._secureIdAuth = secureIdAuth + self._ivAudioPlay = ivAudioPlay + self._ivAudioPause = ivAudioPause + self._proxyEnable = proxyEnable + self._proxyEnabled = proxyEnabled + self._proxyState = proxyState + self._proxyDeleteListItem = proxyDeleteListItem + self._proxyInfoListItem = proxyInfoListItem + self._proxyConnectedListItem = proxyConnectedListItem + self._proxyAddProxy = proxyAddProxy + self._proxyNextWaitingListItem = proxyNextWaitingListItem + self._passportForgotPassword = passportForgotPassword + self._confirmAppAccessoryIcon = confirmAppAccessoryIcon + self._passportPassport = passportPassport + self._passportIdCardReverse = passportIdCardReverse + self._passportIdCard = passportIdCard + self._passportSelfie = passportSelfie + self._passportDriverLicense = passportDriverLicense + self._chatOverlayVoiceRecording = chatOverlayVoiceRecording + self._chatOverlayVideoRecording = chatOverlayVideoRecording + self._chatOverlaySendRecording = chatOverlaySendRecording + self._chatOverlayLockArrowRecording = chatOverlayLockArrowRecording + self._chatOverlayLockerBodyRecording = chatOverlayLockerBodyRecording + self._chatOverlayLockerHeadRecording = chatOverlayLockerHeadRecording + self._locationPin = locationPin + self._locationMapPin = locationMapPin + self._locationMapLocate = locationMapLocate + self._locationMapLocated = locationMapLocated + self._passportSettings = passportSettings + self._passportInfo = passportInfo + self._editMessageMedia = editMessageMedia + self._playerMusicPlaceholder = playerMusicPlaceholder + self._chatMusicPlaceholder = chatMusicPlaceholder + self._chatMusicPlaceholderCap = chatMusicPlaceholderCap + self._searchArticle = searchArticle + self._searchSaved = searchSaved + self._archivedChats = archivedChats + self._hintPeerActive = hintPeerActive + self._hintPeerActiveSelected = hintPeerActiveSelected + self._chatSwiping_delete = chatSwiping_delete + self._chatSwiping_mute = chatSwiping_mute + self._chatSwiping_unmute = chatSwiping_unmute + self._chatSwiping_read = chatSwiping_read + self._chatSwiping_unread = chatSwiping_unread + self._chatSwiping_pin = chatSwiping_pin + self._chatSwiping_unpin = chatSwiping_unpin + self._chatSwiping_archive = chatSwiping_archive + self._chatSwiping_unarchive = chatSwiping_unarchive + self._galleryPrev = galleryPrev + self._galleryNext = galleryNext + self._galleryMore = galleryMore + self._galleryShare = galleryShare + self._galleryFastSave = galleryFastSave + self._playingVoice1x = playingVoice1x + self._playingVoice2x = playingVoice2x + self._galleryRotate = galleryRotate + self._galleryZoomIn = galleryZoomIn + self._galleryZoomOut = galleryZoomOut + self._editMessageCurrentPhoto = editMessageCurrentPhoto + self._videoPlayerPlay = videoPlayerPlay + self._videoPlayerPause = videoPlayerPause + self._videoPlayerEnterFullScreen = videoPlayerEnterFullScreen + self._videoPlayerExitFullScreen = videoPlayerExitFullScreen + self._videoPlayerPIPIn = videoPlayerPIPIn + self._videoPlayerPIPOut = videoPlayerPIPOut + self._videoPlayerRewind15Forward = videoPlayerRewind15Forward + self._videoPlayerRewind15Backward = videoPlayerRewind15Backward + self._videoPlayerVolume = videoPlayerVolume + self._videoPlayerVolumeOff = videoPlayerVolumeOff + self._videoPlayerClose = videoPlayerClose + self._videoPlayerSliderInteractor = videoPlayerSliderInteractor + self._streamingVideoDownload = streamingVideoDownload + self._videoCompactFetching = videoCompactFetching + self._compactStreamingFetchingCancel = compactStreamingFetchingCancel + self._customLocalizationDelete = customLocalizationDelete + self._pollAddOption = pollAddOption + self._pollDeleteOption = pollDeleteOption + self._resort = resort + self._chatPollVoteUnselected = chatPollVoteUnselected + self._chatPollVoteUnselectedBubble_incoming = chatPollVoteUnselectedBubble_incoming + self._chatPollVoteUnselectedBubble_outgoing = chatPollVoteUnselectedBubble_outgoing + self._peerInfoAdmins = peerInfoAdmins + self._peerInfoPermissions = peerInfoPermissions + self._peerInfoBanned = peerInfoBanned + self._peerInfoMembers = peerInfoMembers + self._chatUndoAction = chatUndoAction + self._appUpdate = appUpdate + self._inlineVideoSoundOff = inlineVideoSoundOff + self._inlineVideoSoundOn = inlineVideoSoundOn + self._logoutOptionAddAccount = logoutOptionAddAccount + self._logoutOptionSetPasscode = logoutOptionSetPasscode + self._logoutOptionClearCache = logoutOptionClearCache + self._logoutOptionChangePhoneNumber = logoutOptionChangePhoneNumber + self._logoutOptionContactSupport = logoutOptionContactSupport + self._disableEmojiPrediction = disableEmojiPrediction + self._scam = scam + self._scamActive = scamActive + self._chatScam = chatScam + self._chatUnarchive = chatUnarchive + self._chatArchive = chatArchive + self._privacySettings_blocked = privacySettings_blocked + self._privacySettings_activeSessions = privacySettings_activeSessions + self._privacySettings_passcode = privacySettings_passcode + self._privacySettings_twoStep = privacySettings_twoStep + self._deletedAccount = deletedAccount + self._stickerPackSelection = stickerPackSelection + self._stickerPackSelectionActive = stickerPackSelectionActive + self._entertainment_Emoji = entertainment_Emoji + self._entertainment_Stickers = entertainment_Stickers + self._entertainment_Gifs = entertainment_Gifs + self._entertainment_Search = entertainment_Search + self._entertainment_Settings = entertainment_Settings + self._entertainment_SearchCancel = entertainment_SearchCancel + self._scheduledAvatar = scheduledAvatar + self._scheduledInputAction = scheduledInputAction + self._verifyDialog = verifyDialog + self._verifyDialogActive = verifyDialogActive + self._chatInputScheduled = chatInputScheduled + self._appearanceAddPlatformTheme = appearanceAddPlatformTheme + self._wallet_close = wallet_close + self._wallet_qr = wallet_qr + self._wallet_receive = wallet_receive + self._wallet_send = wallet_send + self._wallet_settings = wallet_settings + self._wallet_update = wallet_update + self._wallet_passcode_visible = wallet_passcode_visible + self._wallet_passcode_hidden = wallet_passcode_hidden + self._wallpaper_color_close = wallpaper_color_close + self._wallpaper_color_add = wallpaper_color_add + self._wallpaper_color_swap = wallpaper_color_swap + self._wallpaper_color_rotate = wallpaper_color_rotate + self._login_cap = login_cap + self._login_qr_cap = login_qr_cap + self._login_qr_empty_cap = login_qr_empty_cap + self._chat_failed_scroller = chat_failed_scroller + self._chat_failed_scroller_active = chat_failed_scroller_active + self._poll_quiz_unselected = poll_quiz_unselected + self._poll_selected = poll_selected + self._poll_selected_correct = poll_selected_correct + self._poll_selected_incorrect = poll_selected_incorrect + self._poll_selected_incoming = poll_selected_incoming + self._poll_selected_correct_incoming = poll_selected_correct_incoming + self._poll_selected_incorrect_incoming = poll_selected_incorrect_incoming + self._poll_selected_outgoing = poll_selected_outgoing + self._poll_selected_correct_outgoing = poll_selected_correct_outgoing + self._poll_selected_incorrect_outgoing = poll_selected_incorrect_outgoing + self._chat_filter_edit = chat_filter_edit + self._chat_filter_add = chat_filter_add + self._chat_filter_bots = chat_filter_bots + self._chat_filter_channels = chat_filter_channels + self._chat_filter_custom = chat_filter_custom + self._chat_filter_groups = chat_filter_groups + self._chat_filter_muted = chat_filter_muted + self._chat_filter_private_chats = chat_filter_private_chats + self._chat_filter_read = chat_filter_read + self._chat_filter_secret_chats = chat_filter_secret_chats + self._chat_filter_unmuted = chat_filter_unmuted + self._chat_filter_unread = chat_filter_unread + self._chat_filter_large_groups = chat_filter_large_groups + self._chat_filter_non_contacts = chat_filter_non_contacts + self._chat_filter_archive = chat_filter_archive + self._chat_filter_bots_avatar = chat_filter_bots_avatar + self._chat_filter_channels_avatar = chat_filter_channels_avatar + self._chat_filter_custom_avatar = chat_filter_custom_avatar + self._chat_filter_groups_avatar = chat_filter_groups_avatar + self._chat_filter_muted_avatar = chat_filter_muted_avatar + self._chat_filter_private_chats_avatar = chat_filter_private_chats_avatar + self._chat_filter_read_avatar = chat_filter_read_avatar + self._chat_filter_secret_chats_avatar = chat_filter_secret_chats_avatar + self._chat_filter_unmuted_avatar = chat_filter_unmuted_avatar + self._chat_filter_unread_avatar = chat_filter_unread_avatar + self._chat_filter_large_groups_avatar = chat_filter_large_groups_avatar + self._chat_filter_non_contacts_avatar = chat_filter_non_contacts_avatar + self._chat_filter_archive_avatar = chat_filter_archive_avatar + self._group_invite_via_link = group_invite_via_link + self._tab_contacts = tab_contacts + self._tab_contacts_active = tab_contacts_active + self._tab_calls = tab_calls + self._tab_calls_active = tab_calls_active + self._tab_chats = tab_chats + self._tab_chats_active = tab_chats_active + self._tab_chats_active_filters = tab_chats_active_filters + self._tab_settings = tab_settings + self._tab_settings_active = tab_settings_active + self._profile_add_member = profile_add_member + self._profile_call = profile_call + self._profile_leave = profile_leave + self._profile_message = profile_message + self._profile_more = profile_more + self._profile_mute = profile_mute + self._profile_unmute = profile_unmute + self._profile_search = profile_search + self._profile_secret_chat = profile_secret_chat + self._profile_edit_photo = profile_edit_photo + self._profile_block = profile_block + self._profile_report = profile_report + self._profile_share = profile_share + self._profile_stats = profile_stats + self._profile_unblock = profile_unblock + self._chat_quiz_explanation = chat_quiz_explanation + self._chat_quiz_explanation_bubble_incoming = chat_quiz_explanation_bubble_incoming + self._chat_quiz_explanation_bubble_outgoing = chat_quiz_explanation_bubble_outgoing + self._stickers_add_featured = stickers_add_featured + self._channel_info_promo = channel_info_promo + self._channel_info_promo_bubble_incoming = channel_info_promo_bubble_incoming + self._channel_info_promo_bubble_outgoing = channel_info_promo_bubble_outgoing + self._chat_share_message = chat_share_message + self._chat_goto_message = chat_goto_message + self._chat_swipe_reply = chat_swipe_reply + self._chat_like_message = chat_like_message + self._chat_like_message_unlike = chat_like_message_unlike + self._chat_like_inside = chat_like_inside + self._chat_like_inside_bubble_incoming = chat_like_inside_bubble_incoming + self._chat_like_inside_bubble_outgoing = chat_like_inside_bubble_outgoing + self._chat_like_inside_bubble_overlay = chat_like_inside_bubble_overlay + self._chat_like_inside_empty = chat_like_inside_empty + self._chat_like_inside_empty_bubble_incoming = chat_like_inside_empty_bubble_incoming + self._chat_like_inside_empty_bubble_outgoing = chat_like_inside_empty_bubble_outgoing + self._chat_like_inside_empty_bubble_overlay = chat_like_inside_empty_bubble_overlay + self._gif_trending = gif_trending + self._chat_list_thumb_play = chat_list_thumb_play + } +} \ No newline at end of file diff --git a/Telegram-Mac/TelegramShare-Objc-Bridge-Header.h b/Telegram-Mac/TelegramShare-Objc-Bridge-Header.h new file mode 100644 index 0000000000..f15bfcb98b --- /dev/null +++ b/Telegram-Mac/TelegramShare-Objc-Bridge-Header.h @@ -0,0 +1,15 @@ +// +// TelegramShare-Objc-Bridge-Header.h +// Telegram +// +// Created by Mikhail Filimonov on 19/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// +#ifndef TelegramShare_Objc_Bridge_Header_h +#define TelegramShare_Objc_Bridge_Header_h + +#import "NumberPluralizationForm.h" + + + +#endif diff --git a/Telegram-Mac/TermsModalController.swift b/Telegram-Mac/TermsModalController.swift new file mode 100644 index 0000000000..931be72f7c --- /dev/null +++ b/Telegram-Mac/TermsModalController.swift @@ -0,0 +1,194 @@ +// +// TermsModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 04/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class TermsView : View { + private let headerView: View = View() + private let titleView = TextView() + let tableView = TableView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(headerView) + addSubview(tableView) + headerView.addSubview(titleView) + headerView.border = [.Bottom] + let title: TextViewLayout = TextViewLayout.init(NSAttributedString.initialize(string: L10n.termsOfServiceTitle, color: theme.colors.text, font: .medium(.title))) + title.measure(width: frameRect.width - 20) + titleView.update(title) + } + + override func layout() { + super.layout() + headerView.frame = NSMakeRect(0, 0, frame.width, 50) + tableView.frame = NSMakeRect(0, 60, frame.width, frame.height - 60) + titleView.center() + } + + func updateText(_ text: NSAttributedString, openBot:@escaping(String)->Void) { + tableView.removeAll() + let initialSize = NSMakeSize(380, tableView.frame.height) + let item = GeneralTextRowItem(initialSize, text: text, linkExecutor: TextViewInteractions(processURL: { url in + if let url = url as? String, !url.isEmpty { + if url.hasPrefix("@") { + openBot(url) + } else { + execute(inapp: .external(link: url, false)) + } + } + })) + _ = tableView.addItem(item: item) + + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} +class TermsModalController: ModalViewController { + + override func viewClass() -> AnyClass { + return TermsView.self + } + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(380, min(size.height - 70, genericView.tableView.listHeight + 70)), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(380, min(contentSize.height - 70, genericView.tableView.listHeight + 70)), animated: animated) + } + } + override var dynamicSize: Bool { + return true + } + + + override var handleAllEvents: Bool { + return true + } + + override var modalInteractions: ModalInteractions? { + let network = self.context.account.network + let terms = self.terms + let account = self.context.account + let accept:()->Void = { [weak self] in + guard let `self` = self else {return} + + _ = showModalProgress(signal: acceptTermsOfService(account: account, id: terms.id) |> deliverOnMainQueue, for: mainWindow).start(next: { [weak self] in + self?.close() + }) + if let botname = self.proceedBotAfterAgree { + _ = (resolvePeerByName(account: self.context.account, name: botname) |> deliverOnMainQueue).start(next: { [weak self] peerId in + guard let `self` = self else {return} + if let peerId = peerId { + self.context.sharedContext.bindings.rootNavigation().push(ChatController(context: self.context, chatLocation: .peer(peerId))) + } + }) + } + } + return ModalInteractions(acceptTitle: L10n.termsOfServiceAccept, accept: { + if let age = terms.ageConfirmation { + confirm(for: mainWindow, header: L10n.termsOfServiceTitle, information: L10n.termsOfServiceConfirmAge("\(age)"), okTitle: L10n.termsOfServiceAcceptConfirmAge, successHandler: { _ in + accept() + }) + } else { + accept() + } + }, cancelTitle: L10n.termsOfServiceDisagree, cancel: { + confirm(for: mainWindow, header: L10n.termsOfServiceTitle, information: L10n.termsOfServiceDisagreeText, okTitle: L10n.termsOfServiceDisagreeOK, successHandler: { _ in + confirm(for: mainWindow, header: L10n.termsOfServiceTitle, information: L10n.termsOfServiceDisagreeTextLast, okTitle: L10n.termsOfServiceDisagreeTextLastOK, successHandler: { _ in + _ = resetAccountDueTermsOfService(network: network).start() + }) + }) + }, drawBorder: true, height: 50, alignCancelLeft: true) + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillDisappear(animated) + modal?.interactions?.updateCancel { control in + control.set(color: theme.colors.redUI, for: .Normal) + } + } + + + private let context: AccountContext + private let terms: TermsOfServiceUpdate + private var proceedBotAfterAgree: String? = nil + init(_ context: AccountContext, terms: TermsOfServiceUpdate) { + self.context = context + self.terms = terms + super.init(frame: NSMakeRect(0, 0, 380, 380)) + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + + private var genericView: TermsView { + return self.view as! TermsView + } + + override func escapeKeyAction() -> KeyHandlerResult { + return .invoked + } + + deinit { + } + + + override var closable: Bool { + return false + } + + override func viewDidLoad() { + super.viewDidLoad() + let attributedString: NSMutableAttributedString = NSMutableAttributedString() + + _ = attributedString.append(string: terms.text, color: theme.colors.text, font: .normal(.text)) + + for entity in terms.entities { + switch entity.type { + case .Bold: + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.bold(.text), range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + case .Italic: + attributedString.addAttribute(NSAttributedString.Key.font, value: NSFont.italic(.text), range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + case let .TextUrl(url): + attributedString.addAttribute(NSAttributedString.Key.link, value: url, range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.colors.link, range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + case .Mention: + attributedString.addAttribute(NSAttributedString.Key.link, value: terms.text.nsstring.substring(with: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)), range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.colors.link, range: NSMakeRange(entity.range.lowerBound, entity.range.upperBound - entity.range.lowerBound)) + default: + break + } + } + + genericView.updateText(attributedString, openBot: { [weak self] botname in + guard let `self` = self else {return} + self.proceedBotAfterAgree = botname + self.show(toaster: ControllerToaster(text: L10n.termsOfServiceProceedBot(botname))) + }) + + + + updateSize(false) + readyOnce() + } + + +} diff --git a/Telegram-Mac/TextAndLabelItem.swift b/Telegram-Mac/TextAndLabelItem.swift index 8bacddc9fc..58424270f4 100644 --- a/Telegram-Mac/TextAndLabelItem.swift +++ b/Telegram-Mac/TextAndLabelItem.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit class TextAndLabelItem: GeneralRowItem { @@ -25,20 +26,70 @@ class TextAndLabelItem: GeneralRowItem { var textLayout:TextViewLayout let isTextSelectable:Bool let callback:()->Void - let account:Account - init(_ initialSize:NSSize, stableId:AnyHashable, label:String, text:String, account:Account, detectLinks:Bool = false, isTextSelectable:Bool = true, callback:@escaping ()->Void = {}, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil) { - self.account = account + let canCopy: Bool + + + var hasMore: Bool? = true { + didSet { + if hasMore == nil { + textLayout.maximumNumberOfLines = 0 + textLayout.cutout = nil + _ = makeSize(width, oldWidth: 0) + + if let table = self.table { + table.enumerateItems { item -> Bool in + item.table?.reloadData(row: item.index, animated: true) + return true + } + } + + } + } + } + + let moreLayout: TextViewLayout + + init(_ initialSize:NSSize, stableId:AnyHashable, label:String, labelColor: NSColor = theme.colors.accent, text:String, context: AccountContext, viewType: GeneralViewType = .legacy, detectLinks:Bool = false, isTextSelectable:Bool = true, callback:@escaping ()->Void = {}, openInfo:((PeerId, Bool, MessageId?, ChatInitialAction?)->Void)? = nil, hashtag:((String)->Void)? = nil, selectFullWord: Bool = false, canCopy: Bool = true) { self.callback = callback self.isTextSelectable = isTextSelectable - self.label = NSAttributedString.initialize(string: label, color: theme.colors.blueUI, font: .normal(FontSize.text)) + self.label = NSAttributedString.initialize(string: label, color: labelColor, font: .normal(FontSize.text)) let attr = NSMutableAttributedString() _ = attr.append(string: text.trimmed.fullTrimmed, color: theme.colors.text, font: .normal(.title)) if detectLinks { - attr.detectLinks(type: [.Links, .Hashtags, .Mentions], account: account, openInfo: openInfo, hashtag: hashtag) + attr.detectLinks(type: [.Links, .Hashtags, .Mentions], context: context, color: theme.colors.link, openInfo: openInfo, hashtag: hashtag, applyProxy: { settings in + applyExternalProxy(settings, accountManager: context.sharedContext.accountManager) + }) } - textLayout = TextViewLayout(attr) + self.canCopy = canCopy + + + textLayout = TextViewLayout(attr, maximumNumberOfLines: 3, alwaysStaticItems: !detectLinks) textLayout.interactions = globalLinkExecutor - super.init(initialSize,stableId: stableId, type: .none, action: callback, drawCustomSeparator: true) + textLayout.selectWholeText = !detectLinks + if selectFullWord { + textLayout.interactions.copy = { + copyToClipboard(text) + return true + } + } + + var showFull:(()->Void)? = nil + + let moreAttr = parseMarkdownIntoAttributedString(L10n.peerInfoShowMoreText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.title), textColor: theme.colors.text), bold: MarkdownAttributeSet(font: .bold(.title), textColor: theme.colors.text), link: MarkdownAttributeSet(font: .normal(.title), textColor: theme.colors.link), linkAttribute: { contents in + return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, { _ in + showFull?() + })) + })) + self.moreLayout = TextViewLayout(moreAttr) + self.moreLayout.interactions = globalLinkExecutor + + + self.moreLayout.measure(width: .greatestFiniteMagnitude) + super.init(initialSize,stableId: stableId, type: .none, viewType: viewType, action: callback, drawCustomSeparator: true) + + showFull = { [weak self] in + self?.hasMore = nil + } } override func viewClass() -> AnyClass { @@ -46,11 +97,21 @@ class TextAndLabelItem: GeneralRowItem { } var textWidth:CGFloat { - return width - inset.left - inset.right + switch viewType { + case .legacy: + return width - inset.left - inset.right + case let .modern(_, inner): + return blockWidth - inner.left - inner.right + } } override var height: CGFloat { - return labelsHeight + 20 + switch viewType { + case .legacy: + return labelsHeight + 20 + case let .modern(_, insets): + return labelsHeight + insets.top + insets.bottom - 4 + } } var labelsHeight:CGFloat { @@ -73,41 +134,73 @@ class TextAndLabelItem: GeneralRowItem { return (height - labelsHeight) / 2.0 } -// override func menuItems() -> Signal<[ContextMenuItem], Void> { -// return .single([ContextMenuItem(tr(.textCopy), handler: { [weak self] in -// if let strongSelf = self { -// copyToClipboard(strongSelf.textLayout.attributedString.string) -// } -// -// })]) -// } -// + override func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + if !canCopy { + return .single([]) + } else { + return .single([ContextMenuItem(L10n.textCopyLabel(self.label.string.components(separatedBy: " ").map{$0.capitalizingFirstLetter()}.joined(separator: " ")), handler: { [weak self] in + if let strongSelf = self { + copyToClipboard(strongSelf.textLayout.attributedString.string) + } + })]) + } + + } +// override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { + let result = super.makeSize(width, oldWidth: oldWidth) textLayout.measure(width: textWidth) + + if hasMore != nil { + hasMore = !textLayout.isPerfectSized + } + if hasMore == true { + textLayout.cutout = TextViewCutout(bottomRight: NSMakeSize(moreLayout.layoutSize.width + 10, 0)) + textLayout.measure(width: textWidth) + } + labelLayout = TextNode.layoutText(maybeNode: nil, label, nil, 1, .end, NSMakeSize(textWidth, .greatestFiniteMagnitude), nil, false, .left) - return super.makeSize(width, oldWidth: oldWidth) + return result } } class TextAndLabelRowView: GeneralRowView { - + private let containerView = GeneralRowContainerView(frame: NSZeroRect) private var labelView:TextView = TextView() - + private let moreView: TextView = TextView() override func draw(_ layer: CALayer, in ctx: CGContext) { - super.draw(layer, in: ctx) - if let item = item as? TextAndLabelItem, let label = item.labelLayout { - - label.1.draw(NSMakeRect(item.inset.left, item.labelY, label.0.size.width, label.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - if item.drawCustomSeparator { - ctx.setFillColor(theme.colors.border.cgColor) - ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + if let item = item as? TextAndLabelItem, let label = item.labelLayout, layer == containerView.layer { + switch item.viewType { + case .legacy: + label.1.draw(NSMakeRect(item.inset.left, item.labelY, label.0.size.width, label.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backdorColor) + if item.drawCustomSeparator { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left - item.inset.right, .borderSize)) + } + case let .modern(position, insets): + label.1.draw(NSMakeRect(insets.left, item.labelY, label.0.size.width, label.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backdorColor) + if position.border { + ctx.setFillColor(theme.colors.border.cgColor) + ctx.fill(NSMakeRect(insets.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - insets.left - insets.right, .borderSize)) + } } } } + override var backdorColor: NSColor { + return theme.colors.background + } + override func updateColors() { + if let item = item as? TextAndLabelItem { + self.labelView.backgroundColor = backdorColor + self.containerView.backgroundColor = backdorColor + self.background = item.viewType.rowBackground + } + } + override func mouseUp(with event: NSEvent) { if mouseInside() { if let item = item as? TextAndLabelItem { @@ -122,8 +215,11 @@ class TextAndLabelRowView: GeneralRowView { required init(frame frameRect: NSRect) { super.init(frame: frameRect) - self.addSubview(labelView) - + containerView.addSubview(labelView) + self.addSubview(self.containerView) + self.containerView.displayDelegate = self + self.containerView.userInteractionEnabled = false + containerView.addSubview(moreView) labelView.set(handler: { [weak self] _ in if let item = self?.item as? TextAndLabelItem { item.action() @@ -134,11 +230,28 @@ class TextAndLabelRowView: GeneralRowView { override func layout() { super.layout() if let item = item as? TextAndLabelItem { - if let _ = item.labelLayout { - labelView.setFrameOrigin(item.inset.left, item.textY) - } else { - labelView.centerY(x:item.inset.left) + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + + if let _ = item.labelLayout { + labelView.setFrameOrigin(item.inset.left, item.textY) + } else { + labelView.centerY(x:item.inset.left) + } + case let .modern(_, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + + if let _ = item.labelLayout { + labelView.setFrameOrigin(innerInsets.left, item.textY) + } else { + labelView.centerY(x: innerInsets.left) + } + + moreView.setFrameOrigin(NSMakePoint(containerView.frame.width - moreView.frame.width - innerInsets.right, containerView.frame.height - innerInsets.bottom - moreView.frame.height + 2)) } + self.containerView.setCorners(item.viewType.corners) + } } @@ -149,11 +262,14 @@ class TextAndLabelRowView: GeneralRowView { if let item = item as? TextAndLabelItem { // labelView.userInteractionEnabled = item.isTextSelectable - + labelView.userInteractionEnabled = item.canCopy labelView.isSelectable = item.isTextSelectable labelView.update(item.textLayout) - labelView.backgroundColor = theme.colors.background + + moreView.isHidden = item.hasMore != true + moreView.update(item.moreLayout) } + containerView.needsDisplay = true needsLayout = true } diff --git a/Telegram-Mac/TextUtils.swift b/Telegram-Mac/TextUtils.swift index 7ffc793066..365b467a36 100644 --- a/Telegram-Mac/TextUtils.swift +++ b/Telegram-Mac/TextUtils.swift @@ -7,45 +7,71 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit +import SwiftSignalKit -func pullText(from message:Message, attachEmoji: Bool = true) -> NSString { +enum MessageTextMediaViewType { + case emoji + case text + case none +} + +func pullText(from message:Message, mediaViewType: MessageTextMediaViewType = .emoji, messagesCount: Int = 1) -> NSString { var messageText: NSString = message.text.fixed.nsstring for media in message.media { switch media { case _ as TelegramMediaImage: if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let _ = message.autoremoveAttribute { - messageText = tr(.chatListServiceDestructingPhoto).nsstring + messageText = tr(L10n.chatListServiceDestructingPhoto).nsstring } else { - messageText = tr(.chatListPhoto).nsstring + messageText = L10n.chatListPhotoCountable(messagesCount).nsstring if !message.text.isEmpty { - messageText = ((attachEmoji ? "🖼 " : "") + message.text.fixed).nsstring + switch mediaViewType { + case .emoji: + messageText = ("🖼 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } } } - + case let dice as TelegramMediaDice: + messageText = dice.emoji.nsstring case let fileMedia as TelegramMediaFile: - if fileMedia.isSticker { - messageText = tr(.chatListSticker(fileMedia.stickerText?.fixed ?? "")).nsstring + if fileMedia.isStaticSticker || fileMedia.isAnimatedSticker { + messageText = L10n.chatListSticker(fileMedia.stickerText?.fixed ?? "").nsstring } else if fileMedia.isVoice { - messageText = tr(.chatListVoice).nsstring + messageText = L10n.chatListVoice.nsstring } else if fileMedia.isMusic { - messageText = (fileMedia.musicText.0 + " - " + fileMedia.musicText.1).nsstring + messageText = ("🎵 " + fileMedia.musicText.0 + " - " + fileMedia.musicText.1).nsstring } else if fileMedia.isInstantVideo { - messageText = tr(.chatListInstantVideo).nsstring + messageText = tr(L10n.chatListInstantVideo).nsstring } else if fileMedia.isVideo { if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let _ = message.autoremoveAttribute { - messageText = tr(.chatListServiceDestructingVideo).nsstring + messageText = tr(L10n.chatListServiceDestructingVideo).nsstring } else { if fileMedia.isAnimated { - messageText = tr(.chatListGIF).nsstring + messageText = L10n.chatListGIF.nsstring + if !message.text.fixed.isEmpty { + messageText = (L10n.chatListGIF + ", " + message.text.fixed).nsstring + } } else { - messageText = tr(.chatListVideo).nsstring - if !message.text.isEmpty { - messageText = ("📹 " + message.text.fixed).nsstring + messageText = L10n.chatListVideoCountable(messagesCount).nsstring + if !message.text.fixed.isEmpty { + switch mediaViewType { + case .emoji: + messageText = ("📹 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } } } } @@ -54,17 +80,59 @@ func pullText(from message:Message, attachEmoji: Bool = true) -> NSString { } else { messageText = fileMedia.fileName?.fixed.nsstring ?? "File" if !message.text.isEmpty { - messageText = ("📎 " + message.text.fixed).nsstring + switch mediaViewType { + case .emoji: + messageText = ("📎 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } } } case _ as TelegramMediaMap: - messageText = tr(.chatListMap).nsstring + messageText = tr(L10n.chatListMap).nsstring case _ as TelegramMediaContact: - messageText = tr(.chatListContact).nsstring + messageText = tr(L10n.chatListContact).nsstring case let game as TelegramMediaGame: messageText = "🎮 \(game.title)".nsstring case let invoice as TelegramMediaInvoice: messageText = invoice.title.nsstring + case let poll as TelegramMediaPoll: + messageText = "📊 \(poll.text)".nsstring + case let webpage as TelegramMediaWebpage: + if case let .Loaded(content) = webpage.content { + if let _ = content.image { + switch mediaViewType { + case .emoji: + messageText = ("🖼 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } + } else if let file = content.file { + if (file.isVideo && !file.isInstantVideo) { + switch mediaViewType { + case .emoji: + messageText = ("🖼 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } + } else if file.isGraphicFile { + switch mediaViewType { + case .emoji: + messageText = ("📹 " + message.text.fixed).nsstring + case .text: + messageText = message.text.fixed.nsstring + case .none: + break + } + } + } + } default: break } @@ -73,13 +141,13 @@ func pullText(from message:Message, attachEmoji: Bool = true) -> NSString { } -func chatListText(account:Account, for message:Message?, renderedPeer:RenderedPeer? = nil, embeddedState:PeerChatListEmbeddedInterfaceState? = nil) -> NSAttributedString { +func chatListText(account:Account, for message:Message?, messagesCount: Int = 1, renderedPeer:RenderedPeer? = nil, embeddedState:PeerChatListEmbeddedInterfaceState? = nil, folder: Bool = false, applyUserName: Bool = false) -> NSAttributedString { if let embeddedState = embeddedState as? ChatEmbeddedInterfaceState { let mutableAttributedText = NSMutableAttributedString() - _ = mutableAttributedText.append(string: tr(.chatListDraft), color: theme.colors.redUI, font: .normal(FontSize.text)) - _ = mutableAttributedText.append(string: " \(embeddedState.text)", color: theme.chatList.grayTextColor, font: .normal(FontSize.text)) - mutableAttributedText.setSelected(color: .white, range: mutableAttributedText.range) + _ = mutableAttributedText.append(string: L10n.chatListDraft, color: theme.colors.redUI, font: .normal(.text)) + _ = mutableAttributedText.append(string: " \(embeddedState.text.fullTrimmed.replacingOccurrences(of: "\n", with: " "))", color: theme.chatList.grayTextColor, font: .normal(.text)) + mutableAttributedText.setSelected(color: theme.colors.underSelectedColor, range: mutableAttributedText.range) return mutableAttributedText } @@ -88,22 +156,22 @@ func chatListText(account:Account, for message:Message?, renderedPeer:RenderedPe let subAttr = NSMutableAttributedString() switch peer.embeddedState { case .terminated: - _ = subAttr.append(string: tr(.chatListSecretChatTerminated), color: theme.chatList.grayTextColor, font: .normal(.text)) + _ = subAttr.append(string: L10n.chatListSecretChatTerminated, color: theme.chatList.grayTextColor, font: .normal(.text)) case .handshake: - _ = subAttr.append(string: tr(.chatListSecretChatExKeys), color: theme.chatList.grayTextColor, font: .normal(.text)) + _ = subAttr.append(string: L10n.chatListSecretChatExKeys, color: theme.chatList.grayTextColor, font: .normal(.text)) case .active: if message == nil { - let title:String = renderedPeer.chatMainPeer?.compactDisplayTitle ?? tr(.peerDeletedUser) + let title:String = renderedPeer.chatMainPeer?.displayTitle ?? L10n.peerDeletedUser switch peer.role { case .creator: - _ = subAttr.append(string: tr(.chatListSecretChatJoined(title)), color: theme.chatList.grayTextColor, font: .normal(.text)) + _ = subAttr.append(string: L10n.chatListSecretChatJoined(title), color: theme.chatList.grayTextColor, font: .normal(.text)) case .participant: - _ = subAttr.append(string: tr(.chatListSecretChatCreated(title)), color: theme.chatList.grayTextColor, font: .normal(.text)) + _ = subAttr.append(string: L10n.chatListSecretChatCreated(title), color: theme.chatList.grayTextColor, font: .normal(.text)) } } } - subAttr.setSelected(color: .white, range: subAttr.range) + subAttr.setSelected(color: theme.colors.underSelectedColor, range: subAttr.range) if subAttr.length > 0 { return subAttr } @@ -111,66 +179,97 @@ func chatListText(account:Account, for message:Message?, renderedPeer:RenderedPe } if let message = message { - + if message.text.isEmpty && message.media.isEmpty { let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.chatListUnsupportedMessage), color: theme.chatList.grayTextColor, font: .normal(.text)) - attr.setSelected(color: .white, range: attr.range) + _ = attr.append(string: L10n.chatListUnsupportedMessage, color: theme.chatList.grayTextColor, font: .normal(.text)) + attr.setSelected(color: theme.colors.underSelectedColor, range: attr.range) return attr } let peer = messageMainPeer(message) - let messageText: NSString = pullText(from: message) + var mediaViewType: MessageTextMediaViewType = .emoji + if !message.containsSecretMedia { + for media in message.media { + if let _ = media as? TelegramMediaImage { + mediaViewType = .text + } else if let file = media as? TelegramMediaFile { + if (file.isVideo && !file.isInstantVideo) || file.isGraphicFile { + mediaViewType = .text + } + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let _ = content.image { + mediaViewType = .text + } else if let file = content.file { + if (file.isVideo && !file.isInstantVideo) || file.isGraphicFile { + mediaViewType = .text + } + } + } + } + } + let messageText: NSString = pullText(from: message, mediaViewType: mediaViewType, messagesCount: messagesCount) + let attributedText: NSMutableAttributedString = NSMutableAttributedString() + if messageText.length > 0 { - var attributedText: NSMutableAttributedString - if let author = message.author as? TelegramUser, let peer = peer, peer as? TelegramUser == nil, !peer.isChannel { - let peerText: NSString = (author.id == account.peerId ? "\(tr(.chatListYou))\n" : author.compactDisplayTitle + "\n") as NSString - let mutableAttributedText = NSMutableAttributedString() + + if folder, let peer = peer { + _ = attributedText.append(string: peer.displayTitle + "\n", color: theme.chatList.peerTextColor, font: .normal(.text)) + } + + if let author = message.author as? TelegramUser, let peer = peer, peer as? TelegramUser == nil, !peer.isChannel, applyUserName { + var peerText: String = (author.id == account.peerId ? "\(L10n.chatListYou)" : author.displayTitle) - _ = mutableAttributedText.append(string: peerText as String, color: theme.chatList.peerTextColor, font: .normal(.text)) - _ = mutableAttributedText.append(string: messageText as String, color: theme.chatList.grayTextColor, font: .normal(.text)) - attributedText = mutableAttributedText; + peerText += (folder ? ": " : "\n") + _ = attributedText.append(string: peerText, color: theme.chatList.peerTextColor, font: .normal(.text)) + _ = attributedText.append(string: messageText as String, color: theme.chatList.grayTextColor, font: .normal(.text)) } else { - attributedText = NSAttributedString.initialize(string: messageText as String, color: theme.chatList.grayTextColor, font: NSFont.normal(FontSize.text)).mutableCopy() as! NSMutableAttributedString + _ = attributedText.append(string: messageText as String, color: theme.chatList.grayTextColor, font: .normal(.text)) } - attributedText.setSelected(color: .white,range: attributedText.range) - return attributedText + + attributedText.setSelected(color: theme.colors.underSelectedColor, range: attributedText.range) } else if message.media.first is TelegramMediaAction { - let attributedText: NSMutableAttributedString = NSMutableAttributedString() _ = attributedText.append(string: serviceMessageText(message, account:account), color: theme.chatList.grayTextColor, font: .normal(.text)) - attributedText.setSelected(color: .white,range: attributedText.range) - return attributedText + attributedText.setSelected(color: theme.colors.underSelectedColor, range: attributedText.range) } else if let media = message.media.first as? TelegramMediaExpiredContent { - let attributedText: NSMutableAttributedString = NSMutableAttributedString() let text:String switch media.data { case .image: - text = tr(.serviceMessageExpiredPhoto) + text = L10n.serviceMessageExpiredPhoto case .file: - text = tr(.serviceMessageExpiredFile) + text = L10n.serviceMessageExpiredVideo } _ = attributedText.append(string: text, color: theme.chatList.grayTextColor, font: .normal(.text)) - attributedText.setSelected(color: .white,range: attributedText.range) - return attributedText + attributedText.setSelected(color: theme.colors.underSelectedColor,range: attributedText.range) } + return attributedText + } return NSAttributedString() } -func serviceMessageText(_ message:Message, account:Account) -> String { +func serviceMessageText(_ message:Message, account:Account, isReplied: Bool = false) -> String { var authorName:String = "" - if let displayTitle = message.author?.compactDisplayTitle { + if let displayTitle = message.author?.displayTitle { if message.author?.id == account.peerId { - authorName = tr(.chatServiceYou) + authorName = tr(L10n.chatServiceYou) } else { authorName = displayTitle } } + if let media = message.media.first as? TelegramMediaExpiredContent { + switch media.data { + case .image: + return L10n.chatListPhoto + case .file: + return L10n.chatListVideo + } + } let authorId:PeerId? = message.author?.id @@ -179,77 +278,102 @@ func serviceMessageText(_ message:Message, account:Account) -> String { switch action.action { case let .addedMembers(peerIds: peerIds): if peerIds.first == authorId { - return tr(.chatServiceGroupAddedSelf(authorName)) + return L10n.chatServiceGroupAddedSelf(authorName) } else { - return tr(.chatServiceGroupAddedMembers(authorName, peerDisplayTitles(peerIds, message.peers))) + return L10n.chatServiceGroupAddedMembers(authorName, peerDebugDisplayTitles(peerIds, message.peers)) } + case .phoneNumberRequest: + return "phone number request" case .channelMigratedFromGroup: - return tr(.chatServiceGroupMigratedToSupergroup) + return "" case let .groupCreated(title: title): if peer.isChannel { - return tr(.chatServiceChannelCreated) + return L10n.chatServiceChannelCreated } else { - return tr(.chatServiceGroupCreated(authorName, title)) + return L10n.chatServiceGroupCreated(authorName, title) } case .groupMigratedToChannel: - return tr(.chatServiceGroupMigratedToSupergroup) + return "" case .historyCleared: return "" case .historyScreenshot: - return tr(.chatServiceGroupTookScreenshot(authorName)) + return L10n.chatServiceGroupTookScreenshot(authorName) case let .joinedByLink(inviter: peerId): if peerId == authorId { - return tr(.chatServiceGroupJoinedByLink(tr(.chatServiceYou))) + return L10n.chatServiceGroupJoinedByLink(tr(L10n.chatServiceYou)) } else { - return tr(.chatServiceGroupJoinedByLink(authorName)) + return L10n.chatServiceGroupJoinedByLink(authorName) } case let .messageAutoremoveTimeoutUpdated(seconds): if seconds > 0 { - return tr(.chatServiceSecretChatSetTimer(authorName, autoremoveLocalized(Int(seconds)))) + return L10n.chatServiceSecretChatSetTimer(authorName, autoremoveLocalized(Int(seconds))) } else { - return tr(.chatServiceSecretChatDisabledTimer(authorName)) + return L10n.chatServiceSecretChatDisabledTimer(authorName) } case let .photoUpdated(image: image): - if let _ = image { - return peer.isChannel ? tr(.chatServiceChannelUpdatedPhoto) : tr(.chatServiceGroupUpdatedPhoto(authorName)) + if let image = image { + let text: String + if image.videoRepresentations.isEmpty { + text = peer.isChannel ? L10n.chatServiceChannelUpdatedPhoto : L10n.chatServiceGroupUpdatedPhoto(authorName) + } else { + text = peer.isChannel ? L10n.chatServiceChannelUpdatedVideo : L10n.chatServiceGroupUpdatedVideo(authorName) + } + return text } else { - return peer.isChannel ? tr(.chatServiceChannelRemovedPhoto) : tr(.chatServiceGroupRemovedPhoto(authorName)) + return peer.isChannel ? L10n.chatServiceChannelRemovedPhoto : L10n.chatServiceGroupRemovedPhoto(authorName) } case .pinnedMessageUpdated: - return tr(.chatServicePinnedMessage) + if !isReplied { + var authorName:String = "" + if let displayTitle = message.author?.displayTitle { + authorName = displayTitle + if account.peerId == message.author?.id { + authorName = tr(L10n.chatServiceYou) + } + } + + var replyMessageText = "" + for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] { + replyMessageText = pullText(from: message) as String + } + } + return L10n.chatServiceGroupUpdatedPinnedMessage(authorName, replyMessageText.prefixWithDots(15)) + } else { + return L10n.chatServicePinnedMessage + } + case let .removedMembers(peerIds: peerIds): if peerIds.first == authorId { - return tr(.chatServiceGroupRemovedSelf(authorName)) + return L10n.chatServiceGroupRemovedSelf(authorName) } else { - return tr(.chatServiceGroupRemovedMembers(authorName, peerCompactDisplayTitles(peerIds, message.peers))) + return L10n.chatServiceGroupRemovedMembers(authorName, peerCompactDisplayTitles(peerIds, message.peers)) } case let .titleUpdated(title: title): - return peer.isChannel ? tr(.chatServiceChannelUpdatedTitle(title)) : tr(.chatServiceGroupUpdatedTitle(authorName, title)) - case let .phoneCall(callId: _, discardReason: reason, duration: duration): + return peer.isChannel ? L10n.chatServiceChannelUpdatedTitle(title) : L10n.chatServiceGroupUpdatedTitle(authorName, title) + case let .phoneCall(callId: _, discardReason: reason, duration: duration, _): if let duration = duration, duration > 0 { if message.author?.id == account.peerId { - return tr(.chatListServiceCallOutgoing(.stringForShortCallDurationSeconds(for: duration))) + return L10n.chatListServiceCallOutgoing(.stringForShortCallDurationSeconds(for: duration)) } else { - return tr(.chatListServiceCallIncoming(.stringForShortCallDurationSeconds(for: duration))) + return L10n.chatListServiceCallIncoming(.stringForShortCallDurationSeconds(for: duration)) } } if let reason = reason { + let outgoing = !message.flags.contains(.Incoming) + switch reason { case .busy: - return tr(.chatListServiceCallCancelled) + return outgoing ? L10n.chatListServiceCallCancelled : L10n.chatListServiceCallMissed case .disconnect: - return tr(.chatListServiceCallDisconnected) + return tr(L10n.chatListServiceCallMissed) case .hangup: - if message.author?.id == account.peerId { - return tr(.chatListServiceCallCancelled) - } else { - return tr(.chatListServiceCallMissed) - } + return outgoing ? L10n.chatListServiceCallCancelled : L10n.chatListServiceCallMissed case .missed: - return tr(.chatListServiceCallMissed) + return outgoing ? L10n.chatListServiceCallCancelled : L10n.chatListServiceCallMissed } } case let .gameScore(gameId: _, score: score): @@ -261,7 +385,7 @@ func serviceMessageText(_ message:Message, account:Account) -> String { } } } - var text = tr(.chatListServiceGameScored(Int(score), gameName)) + var text = L10n.chatListServiceGameScored1Countable(Int(score), gameName) if let peer = messageMainPeer(message) { if peer.isGroup || peer.isSupergroup { text = (message.author?.compactDisplayTitle ?? "") + " " + text @@ -269,15 +393,22 @@ func serviceMessageText(_ message:Message, account:Account) -> String { } return text case let .paymentSent(currency, totalAmount): - return tr(.chatListServicePaymentSent(TGCurrencyFormatter.shared().formatAmount(totalAmount, currency: currency))) + return L10n.chatListServicePaymentSent(TGCurrencyFormatter.shared().formatAmount(totalAmount, currency: currency)) case .unknown: break - case .customText(let text): + case .customText(let text, _): return text + case let .botDomainAccessGranted(domain): + return L10n.chatServiceBotPermissionAllowed(domain) + case let .botSentSecureValues(types): + let permissions = types.map({$0.rawValue}).joined(separator: ", ") + return L10n.chatServiceSecureIdAccessGranted(peer.displayTitle, permissions) + case .peerJoined: + return L10n.chatServicePeerJoinedTelegram(authorName) } } - return tr(.chatMessageUnsupported) + return tr(L10n.chatMessageUnsupported) } struct PeerStatusStringTheme { @@ -287,7 +418,7 @@ struct PeerStatusStringTheme { let statusColor:NSColor let highlightColor:NSColor let highlightIfActivity:Bool - init(titleFont:NSFont = .normal(.title), titleColor:NSColor = theme.colors.text, statusFont:NSFont = .normal(.short), statusColor:NSColor = theme.colors.grayText, highlightColor:NSColor = theme.colors.blueUI, highlightIfActivity:Bool = true) { + init(titleFont:NSFont = .normal(.title), titleColor:NSColor = theme.colors.text, statusFont:NSFont = .normal(.short), statusColor:NSColor = theme.colors.grayText, highlightColor:NSColor = theme.colors.accent, highlightIfActivity:Bool = true) { self.titleFont = titleFont self.titleColor = titleColor self.statusFont = statusFont @@ -306,6 +437,18 @@ struct PeerStatusStringResult : Equatable { self.status = status self.presence = presence } + + func withUpdatedTitle(_ string: String) -> PeerStatusStringResult { + let title = self.title.mutableCopy() as! NSMutableAttributedString + title.replaceCharacters(in: title.range, with: string) + return PeerStatusStringResult(title, self.status, presence: presence) + } + + func withUpdatedStatus(_ status: String) -> PeerStatusStringResult { + let status = self.status.mutableCopy() as! NSMutableAttributedString + status.replaceCharacters(in: status.range, with: status) + return PeerStatusStringResult(self.title, status, presence: presence) + } } func ==(lhs: PeerStatusStringResult, rhs: PeerStatusStringResult) -> Bool { @@ -323,24 +466,25 @@ func ==(lhs: PeerStatusStringResult, rhs: PeerStatusStringResult) -> Bool { return true } -func stringStatus(for peerView:PeerView, theme:PeerStatusStringTheme = PeerStatusStringTheme()) -> PeerStatusStringResult { +func stringStatus(for peerView:PeerView, context: AccountContext, theme:PeerStatusStringTheme = PeerStatusStringTheme(), onlineMemberCount: Int32? = nil, expanded: Bool = false) -> PeerStatusStringResult { if let peer = peerViewMainPeer(peerView) { - let title:NSAttributedString = .initialize(string: peer.displayTitle, color: theme.titleColor, font: theme.titleFont) if let user = peer as? TelegramUser { if user.phone == "42777" || user.phone == "42470" || user.phone == "4240004" { - return PeerStatusStringResult(title, .initialize(string: tr(.peerServiceNotifications), color: theme.statusColor, font: theme.statusFont)) + return PeerStatusStringResult(title, .initialize(string: L10n.peerServiceNotifications, color: theme.statusColor, font: theme.statusFont)) } - if let _ = user.botInfo { - return PeerStatusStringResult(title, .initialize(string: tr(.presenceBot), color: theme.statusColor, font: theme.statusFont)) + if user.flags.contains(.isSupport) { + return PeerStatusStringResult(title, .initialize(string: L10n.presenceSupport, color: theme.statusColor, font: theme.statusFont)) + } else if let _ = user.botInfo { + return PeerStatusStringResult(title, .initialize(string: L10n.presenceBot, color: theme.statusColor, font: theme.statusFont)) } else if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - let (string, activity, _) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp)) + let (string, activity, _) = stringAndActivityForUserPresence(presence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp), expanded: expanded) return PeerStatusStringResult(title, .initialize(string: string, color: activity && theme.highlightIfActivity ? theme.highlightColor : theme.statusColor, font: theme.statusFont), presence: presence) } else { - return PeerStatusStringResult(title, .initialize(string: tr(.peerStatusRecently), color: theme.statusColor, font: theme.statusFont)) + return PeerStatusStringResult(title, .initialize(string: L10n.peerStatusRecently, color: theme.statusColor, font: theme.statusFont)) } } else if let group = peer as? TelegramGroup { var onlineCount = 0 @@ -348,7 +492,7 @@ func stringStatus(for peerView:PeerView, theme:PeerStatusStringTheme = PeerStatu let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 for participant in participants.participants { if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence { - let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp)) + let relativeStatus = relativeUserPresenceStatus(presence, timeDifference: context.timeDifference, relativeTo: Int32(timestamp)) switch relativeStatus { case .online: onlineCount += 1 @@ -361,50 +505,46 @@ func stringStatus(for peerView:PeerView, theme:PeerStatusStringTheme = PeerStatu if onlineCount > 1 { let string = NSMutableAttributedString() - let _ = string.append(string: "\(tr(.peerStatusMemberCountable(group.participantCount))), ", color: theme.statusColor, font: theme.statusFont) - let _ = string.append(string: tr(.peerStatusMemberOnlineCountable(onlineCount)), color: theme.statusColor, font: theme.statusFont) + let _ = string.append(string: "\(L10n.peerStatusMemberCountable(group.participantCount).replacingOccurrences(of: "\(group.participantCount)", with: group.participantCount.formattedWithSeparator)), ", color: theme.statusColor, font: theme.statusFont) + let _ = string.append(string: L10n.peerStatusMemberOnlineCountable(onlineCount), color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) } else { - let string = NSAttributedString.initialize(string: tr(.peerStatusMemberCountable(group.participantCount)), color: theme.statusColor, font: theme.statusFont) + let string = NSAttributedString.initialize(string: L10n.peerStatusMemberCountable(group.participantCount).replacingOccurrences(of: "\(group.participantCount)", with: group.participantCount.formattedWithSeparator), color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) } } else if let channel = peer as? TelegramChannel { - var onlineCount = 0 + let onlineCount: Int = Int(onlineMemberCount ?? 0) if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { + - if let participants = cachedChannelData.topParticipants { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - for participant in participants.participants { - if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence { - let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp)) - switch relativeStatus { - case .online: - onlineCount += 1 - default: - break - } - } - } + let membersLocalized: String + if channel.isChannel { + membersLocalized = L10n.peerStatusSubscribersCountable(Int(memberCount)) + } else { + membersLocalized = L10n.peerStatusMemberCountable(Int(memberCount)) } - if onlineCount > 1, memberCount <= 200, case .group = channel.info { + + let countString = membersLocalized.replacingOccurrences(of: "\(memberCount)", with: memberCount.formattedWithSeparator) + if onlineCount > 1, case .group = channel.info { let string = NSMutableAttributedString() - let _ = string.append(string: "\(tr(.peerStatusMemberCountable(Int(memberCount)))), ", color: theme.statusColor, font: theme.statusFont) - let _ = string.append(string: tr(.peerStatusMemberOnlineCountable(onlineCount)), color: theme.statusColor, font: theme.statusFont) + let _ = string.append(string: "\(countString), ", color: theme.statusColor, font: theme.statusFont) + let _ = string.append(string: L10n.peerStatusMemberOnlineCountable(onlineCount), color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) } else { - let string = NSAttributedString.initialize(string: tr(.peerStatusMemberCountable(Int(memberCount))), color: theme.statusColor, font: theme.statusFont) + + let string = NSAttributedString.initialize(string: countString, color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) } } else { switch channel.info { case .group: - let string = NSAttributedString.initialize(string: tr(.peerStatusGroup), color: theme.statusColor, font: theme.statusFont) + let string = NSAttributedString.initialize(string: L10n.peerStatusGroup, color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) case .broadcast: - let string = NSAttributedString.initialize(string: tr(.peerStatusChannel), color: theme.statusColor, font: theme.statusFont) + let string = NSAttributedString.initialize(string: L10n.peerStatusChannel, color: theme.statusColor, font: theme.statusFont) return PeerStatusStringResult(title, string) } } @@ -417,19 +557,68 @@ func stringStatus(for peerView:PeerView, theme:PeerStatusStringTheme = PeerStatu func autoremoveLocalized(_ ttl: Int) -> String { var localized: String = "" if ttl <= 59 { - localized = tr(.timerSecondsCountable(ttl)) + localized = L10n.timerSecondsCountable(ttl) } else if ttl <= 3599 { - localized = tr(.timerMinutesCountable(ttl / 60)) + localized = L10n.timerMinutesCountable(ttl / 60) } else if ttl <= 86399 { - localized = tr(.timerHoursCountable(ttl / 60 / 60)) + localized = L10n.timerHoursCountable(ttl / 60 / 60) } else if ttl <= 604799 { - localized = tr(.timerDaysCountable(ttl / 60 / 60 / 24)) + localized = L10n.timerDaysCountable(ttl / 60 / 60 / 24) } else { - localized = tr(.timerWeeksCountable(ttl / 60 / 60 / 24 / 7)) + localized = L10n.timerWeeksCountable(ttl / 60 / 60 / 24 / 7) } return localized } +public func shortTimeIntervalString(value: Int32) -> String { + if value < 60 { + return L10n.messageTimerShortSeconds("\(max(1, value))") + } else if value < 60 * 60 { + return L10n.messageTimerShortMinutes("\(max(1, value / 60))") + } else if value < 60 * 60 * 24 { + return L10n.messageTimerShortHours("\(max(1, value / (60 * 60)))") + } else if value < 60 * 60 * 24 * 7 { + return L10n.messageTimerShortDays("\(max(1, value / (60 * 60 * 24)))") + } else { + return L10n.messageTimerShortWeeks("\(max(1, value / (60 * 60 * 24 * 7)))") + } +} + + +func slowModeTooltipText(_ timeout: Int32) -> String { + let minutes = timeout / 60 + let seconds = timeout % 60 + return L10n.channelSlowModeToolTip(minutes < 10 ? "0\(minutes)" : "\(minutes)", seconds < 10 ? "0\(seconds)" : "\(seconds)") +} +func showSlowModeTimeoutTooltip(_ slowMode: SlowMode, for view: NSView) { + if let errorText = slowMode.errorText { + if let validUntil = slowMode.validUntil { + tooltip(for: view, text: errorText, updateText: { f in + var timer:SwiftSignalKit.Timer? + timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: true, completion: { + + let timeout = (validUntil - Int32(Date().timeIntervalSince1970)) + + var result: Bool = false + if timeout > 0 { + result = f(slowModeTooltipText(timeout)) + } + if !result { + timer?.invalidate() + timer = nil + } + + }, queue: .mainQueue()) + + timer?.start() + }) + } else { + tooltip(for: view, text: errorText) + } + + } +} + let preCharacter = "`" let codeCharacter = "```" func parseTextEntities(_ message:String) -> (String, [MessageTextEntity]) { @@ -472,5 +661,21 @@ func parseTextEntities(_ message:String) -> (String, [MessageTextEntity]) { } - +func timeIntervalString( _ value: Int) -> String { + if value < 60 { + return tr(L10n.timerSecondsCountable(value)) + } else if value < 60 * 60 { + return tr(L10n.timerMinutesCountable(max(1, value / 60))) + } else if value < 60 * 60 * 24 { + return tr(L10n.timerHoursCountable(max(1, value / (60 * 60)))) + } else if value < 60 * 60 * 24 * 7 { + return tr(L10n.timerDaysCountable(max(1, value / (60 * 60 * 24)))) + } else if value < 60 * 60 * 24 * 30 { + return tr(L10n.timerWeeksCountable(max(1, value / (60 * 60 * 24 * 7)))) + } else if value < 60 * 60 * 24 * 360 { + return tr(L10n.timerMonthsCountable(max(1, value / (60 * 60 * 24 * 30)))) + } else { + return tr(L10n.timerYearsCountable(max(1, value / (60 * 60 * 24 * 365)))) + } +} diff --git a/Telegram-Mac/ThemeGridControllerItem.swift b/Telegram-Mac/ThemeGridControllerItem.swift new file mode 100644 index 0000000000..d33d2f2348 --- /dev/null +++ b/Telegram-Mac/ThemeGridControllerItem.swift @@ -0,0 +1,261 @@ +// +// ThemeGridControllerItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox + + +final class SettingsThemeWallpaperView: BackgroundView { + private var wallpaper: Wallpaper? + let imageView = TransformImageView() + private let fetchDisposable = MetaDisposable() + var delete: (() -> Void)? + private let label: TextView = TextView() + init() { + super.init(frame: NSZeroRect) + layer?.borderColor = theme.colors.border.cgColor + layer?.borderWidth = .borderSize + //addSubview(label) + self.addSubview(self.imageView) + label.isEventLess = true + label.userInteractionEnabled = false + label.isSelectable = false + let layout = TextViewLayout(.initialize(string: L10n.chatWallpaperEmpty, color: theme.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1) + layout.measure(width: .greatestFiniteMagnitude) + label.update(layout) + label.backgroundColor = theme.chatBackground + label.disableBackgroundDrawing = true + } + + deinit { + fetchDisposable.dispose() + } + + override func menu(for event: NSEvent) -> NSMenu? { + let menu = NSMenu(title: "") + if let wallpaper = self.wallpaper { + switch wallpaper { + case .file: + menu.addItem(ContextMenuItem(L10n.messageContextDelete, handler: { [weak self] in + self?.delete?() + })) + default: + break + } + } + + return menu + } + + override func layout() { + super.layout() + label.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required override init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + func setWallpaper(account: Account, wallpaper: Wallpaper, size: CGSize) { + self.imageView.frame = CGRect(origin: CGPoint(), size: size) + + + + self.wallpaper = wallpaper + switch wallpaper { + case .builtin: + self.label.isHidden = true + self.imageView.isHidden = false + + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: -1), representations: [], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(), boundingSize: size, intrinsicInsets: NSEdgeInsets()) + self.imageView.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor)) + + + self.imageView.setSignal(settingsBuiltinWallpaperImage(account: account, scale: backingScaleFactor), cacheImage: { [weak media] result in + if let media = media { + cacheMedia(result, media: media, arguments: arguments, scale: System.backingScale) + } + }) + + self.imageView.set(arguments: arguments) + + + case let .color(color): + self.imageView.isHidden = true + self.label.isHidden = true + self.backgroundMode = .color(color: NSColor(UInt32(color))) + // backgroundColor = NSColor(UInt32(color)) + case let .gradient(t, b, r): + self.imageView.isHidden = true + self.label.isHidden = true + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + self.backgroundMode = .gradient(top: top, bottom: bottom, rotation: r) + case let .image(representations, _): + self.label.isHidden = true + self.imageView.isHidden = false + self.imageView.setSignal(chatWallpaper(account: account, representations: representations, mode: .thumbnail, isPattern: false, autoFetchFullSize: true, scale: backingScaleFactor)) + self.imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: largestImageRepresentation(representations)!.dimensions.size.aspectFilled(size), boundingSize: size, intrinsicInsets: NSEdgeInsets(), emptyColor: nil)) + self.backgroundMode = .plain + fetchDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.wallpaper(wallpaper: nil, resource: largestImageRepresentation(representations)!.resource)).start()) + case let .file(slug, file, settings, isPattern): + self.label.isHidden = true + self.imageView.isHidden = false + var patternColor: TransformImageEmptyColor? = nil// = NSColor(rgb: 0xd6e2ee, alpha: 0.5) + + var representations:[TelegramMediaImageRepresentation] = [] +// representations.append(contentsOf: file.previewRepresentations) + if let dimensions = file.dimensions { + representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource)) + } else { + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(NSMakeSize(600, 600)), resource: file.resource)) + } + + let sz = largestImageRepresentation(representations)?.dimensions.size ?? size + + if isPattern { + var patternIntensity: CGFloat = 0.5 + if let intensity = settings.intensity { + patternIntensity = CGFloat(intensity) / 100.0 + } + if let color = settings.color, settings.bottomColor == nil { + patternColor = .color(NSColor(rgb: color, alpha: patternIntensity)) + } else if let t = settings.color, let b = settings.bottomColor { + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + patternColor = .gradient(top: top.withAlphaComponent(patternIntensity), bottom: bottom.withAlphaComponent(patternIntensity), rotation: settings.rotation) + } + } + + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: sz.aspectFilled(isPattern ? NSMakeSize(300, 300) : size), boundingSize: size, intrinsicInsets: NSEdgeInsets(), emptyColor: patternColor) + + + self.imageView.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor)) + + self.imageView.setSignal(chatWallpaper(account: account, representations: representations, file: file, mode: .thumbnail, isPattern: isPattern, autoFetchFullSize: true, scale: backingScaleFactor), clearInstantly: false, cacheImage: { result in + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + }) + + + self.imageView.set(arguments: arguments) + + + fetchDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: MediaResourceReference.wallpaper(wallpaper: .slug(slug), resource: largestImageRepresentation(representations)!.resource)).start()) + + self.backgroundMode = .plain + default: + self.backgroundMode = .plain + } + } + +} + +final class ThemeGridControllerItem: GridItem { + let account: Account + let wallpaper: Wallpaper + let telegramWallpaper: TelegramWallpaper? + let interaction: ThemeGridControllerInteraction + + let section: GridSection? = nil + let isSelected: Bool + init(account: Account, wallpaper: Wallpaper, telegramWallpaper: TelegramWallpaper?, interaction: ThemeGridControllerInteraction, isSelected: Bool) { + self.account = account + self.isSelected = isSelected + self.wallpaper = wallpaper + self.telegramWallpaper = telegramWallpaper + self.interaction = interaction + } + + + func node(layout: GridNodeLayout, gridNode: GridNode, cachedNode: GridItemNode?) -> GridItemNode { + let node = ThemeGridControllerItemNode(gridNode) + node.setup(account: self.account, wallpaper: self.wallpaper, telegramWallpaper: self.telegramWallpaper, interaction: self.interaction, isSelected: isSelected) + return node + } + + func update(node: GridItemNode) { + guard let node = node as? ThemeGridControllerItemNode else { + assertionFailure() + return + } + node.setup(account: self.account, wallpaper: self.wallpaper, telegramWallpaper: self.telegramWallpaper, interaction: self.interaction, isSelected: self.isSelected) + } +} + +final class ThemeGridControllerItemNode: GridItemNode { + private let wallpaperView: SettingsThemeWallpaperView + + private var currentState: (Account, Wallpaper, TelegramWallpaper?)? + private var interaction: ThemeGridControllerInteraction? + private let imageView: ImageView = ImageView() + override init(_ grid: GridNode) { + self.wallpaperView = SettingsThemeWallpaperView() + + super.init(grid) + self.addSubview(self.wallpaperView) + addSubview(imageView) + imageView.image = theme.icons.chatGroupToggleSelected + imageView.sizeToFit() + + wallpaperView.delete = { [weak self] in + if let (_, wallpaper, telegramWallapper) = self?.currentState { + if let telegramWallapper = telegramWallapper { + self?.interaction?.deleteWallpaper(wallpaper, telegramWallapper) + } + } + } + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + + func setup(account: Account, wallpaper: Wallpaper, telegramWallpaper: TelegramWallpaper?, interaction: ThemeGridControllerInteraction, isSelected: Bool) { + self.interaction = interaction + + if self.currentState == nil || self.currentState!.0 !== account || wallpaper != self.currentState!.1 { + self.currentState = (account, wallpaper, telegramWallpaper) + self.needsLayout = true + } + imageView.isHidden = !isSelected + } + + override func mouseUp(with event: NSEvent) { + if mouseInside() { + if let (_, wallpaper, telegramWallpaper) = self.currentState { + self.interaction?.openWallpaper(wallpaper, telegramWallpaper) + } + } + } + + override func layout() { + super.layout() + + let bounds = self.bounds + self.wallpaperView.frame = bounds + if let (account, wallpaper, _) = self.currentState { + self.wallpaperView.setWallpaper(account: account, wallpaper: wallpaper, size: bounds.size) + } + imageView.setFrameOrigin(frame.width - imageView.frame.width - 10, 10) + } +} diff --git a/Telegram-Mac/ThemeListRowItem.swift b/Telegram-Mac/ThemeListRowItem.swift new file mode 100644 index 0000000000..a0cb3b706b --- /dev/null +++ b/Telegram-Mac/ThemeListRowItem.swift @@ -0,0 +1,357 @@ +// +// ThemeListRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +private final class HorizontalThemeFirstItem : GeneralRowItem { + override var width: CGFloat { + return 10 + } + override var height: CGFloat { + return 10 + } + override func viewClass() -> AnyClass { + return HorizontalRowView.self + } +} + +private final class ThemeCachedItem { + let source: InstallThemeSource + init(source: InstallThemeSource) { + self.source = source + } +} + +private let cache:NSCache = NSCache() + +private final class HorizontalThemeItem : GeneralRowItem { + fileprivate let themeType: ThemeSource + fileprivate let titleLayout: TextViewLayout + fileprivate let selected: Bool + fileprivate let theme: TelegramPresentationTheme + fileprivate let context: AccountContext + fileprivate let togglePalette: (InstallThemeSource)->Void + fileprivate let menuItems: (ThemeSource)->[ContextMenuItem] + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, theme: TelegramPresentationTheme, themeType: ThemeSource, selected: Bool, togglePalette: @escaping(InstallThemeSource)->Void, menuItems: @escaping(ThemeSource)->[ContextMenuItem]) { + self.themeType = themeType + self.selected = selected + self.theme = theme + self.menuItems = menuItems + self.togglePalette = togglePalette + self.context = context + let attr: NSAttributedString + switch themeType { + case let .local(palette, _): + attr = .initialize(string: localizedString("AppearanceSettings.ColorTheme.\(palette.name)"), color: selected ? theme.colors.accent : theme.colors.text, font: selected ? .medium(12) : .normal(12)) + case let .cloud(cloud): + attr = .initialize(string: cloud.title, color: selected ? theme.colors.accent : theme.colors.text, font: selected ? .medium(12) : .normal(12)) + } + self.titleLayout = TextViewLayout(attr, maximumNumberOfLines: 1, truncationType: .end, alignment: .center, alwaysStaticItems: true) + self.titleLayout.measure(width: 80) + super.init(initialSize, height: 100, stableId: stableId) + } + + + override func viewClass() -> AnyClass { + return HorizontalThemeView.self + } + + override var width: CGFloat { + return 100 + } +} + +struct LocalPaletteWithReference { + let palette: ColorPalette + let cloud: TelegramTheme? + init(palette: ColorPalette, cloud: TelegramTheme?) { + self.palette = palette + self.cloud = cloud + } + func withAccentColor(_ color: PaletteAccentColor) -> LocalPaletteWithReference { + return LocalPaletteWithReference(palette: self.palette.withAccentColor(color), cloud: self.cloud) + } +} + +private final class HorizontalThemeView : HorizontalRowView { + private let containerView = View(frame: NSMakeRect(0, 26, 100, 74)) + private let holderView: View = View() + private let selectionView: View = View() + private let imageView = TransformImageView(frame: NSMakeRect(0, 0, 80, 60)) + private let nameView: TextView = TextView() + private let overlay: Control = Control() + private let progressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 20, 20)) + private let disposable = MetaDisposable() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(containerView) + nameView.userInteractionEnabled = false + nameView.isSelectable = false + containerView.addSubview(nameView) + containerView.addSubview(holderView) + containerView.addSubview(imageView) + containerView.addSubview(selectionView) + containerView.addSubview(overlay) + holderView.addSubview(progressIndicator) + selectionView.layer?.cornerRadius = 10 + selectionView.layer?.borderWidth = 2.5 + holderView.layer?.cornerRadius = 10 + } + + override func set(item: TableRowItem, animated: Bool) { + + + super.set(item: item, animated: animated) + + guard let item = item as? HorizontalThemeItem else { + return + } + + overlay.removeAllHandlers() + + var cachedData: InstallThemeSource? = cache.object(forKey: PhotoCacheKeyEntry.theme(item.themeType, item.theme.bubbled).stringValue)?.source + + overlay.set(handler: { [weak item] _ in + if let cachedData = cachedData { + item?.togglePalette(cachedData) + } + }, for: .Click) + + overlay.set(handler: { [weak item] control in + if let item = item, let event = NSApp.currentEvent { + ContextMenu.show(items: item.menuItems(item.themeType), view: control, event: event) + } + }, for: .RightDown) + + progressIndicator.progressColor = item.theme.colors.grayIcon + + let signal = themeAppearanceThumbAndData(context: item.context, bubbled: item.theme.bubbled, source: item.themeType) |> deliverOnMainQueue + + self.imageView.setSignal(signal: cachedThemeThumb(source: item.themeType, bubbled: item.theme.bubbled), clearInstantly: false) + + var animated: Bool = !self.imageView.hasImage + + switch item.themeType { + case .local: + progressIndicator.isHidden = true + animated = false + self.imageView.layer?.contentsGravity = .resize + case let .cloud(cloud): + progressIndicator.isHidden = self.imageView.hasImage + self.imageView.layer?.contentsGravity = cloud.file != nil ? .resize : .center + } + + disposable.set(signal.start(next: { [weak self] image, data in + self?.imageView.setSignal(signal: .single(image), clearInstantly: true, animate: animated) + self?.progressIndicator.isHidden = true + cacheThemeThumb(image, source: item.themeType, bubbled: item.theme.bubbled) + cache.setObject(ThemeCachedItem(source: data), forKey: PhotoCacheKeyEntry.theme(item.themeType, item.theme.bubbled).stringValue) + cachedData = data + })) + + + selectionView.layer?.borderWidth = item.selected ? 2 : 1 + + + nameView.update(item.titleLayout) + needsLayout = true + } + + override var backdorColor: NSColor { + guard let item = item as? HorizontalThemeItem else { + return theme.colors.background + } + return item.theme.colors.background + } + + override func updateColors() { + guard let item = item as? HorizontalThemeItem else { + return + } + backgroundColor = backdorColor + selectionView.layer?.borderColor = item.selected ? item.theme.colors.accentSelect.cgColor : item.theme.colors.border.cgColor + containerView.backgroundColor = backdorColor + switch item.themeType { + case .local: + holderView.backgroundColor = item.theme.colors.grayBackground + case let .cloud(cloud): + holderView.backgroundColor = cloud.file != nil ? item.theme.colors.grayBackground : item.theme.colors.background + } + containerView.backgroundColor = backdorColor + } + + override func layout() { + super.layout() + holderView.frame = NSMakeRect(10, 0, 80, 55) + selectionView.frame = NSMakeRect(10, 0, 80, 55) + imageView.frame = NSMakeRect(11, 1, 78, 53) + overlay.frame = NSMakeRect(10, 0, 80, 55) + nameView.centerX(y: containerView.frame.height - nameView.frame.height) + progressIndicator.center() + } + + + deinit { + disposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +class ThemeListRowItem: GeneralRowItem { + fileprivate let context: AccountContext + fileprivate let theme: TelegramPresentationTheme + fileprivate let cloudThemes:[TelegramTheme] + fileprivate let local:[LocalPaletteWithReference] + fileprivate let togglePalette: (InstallThemeSource)->Void + fileprivate let menuItems: (ThemeSource)->[ContextMenuItem] + fileprivate let selected: ThemeSource + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, theme: TelegramPresentationTheme, selected: ThemeSource, local:[LocalPaletteWithReference], cloudThemes:[TelegramTheme], viewType: GeneralViewType, togglePalette: @escaping(InstallThemeSource)->Void, menuItems: @escaping(ThemeSource)->[ContextMenuItem]) { + self.context = context + self.theme = theme + self.local = local + self.selected = selected + self.cloudThemes = cloudThemes + self.togglePalette = togglePalette + self.menuItems = menuItems + super.init(initialSize, height: 74 + viewType.innerInset.top + viewType.innerInset.bottom, stableId: stableId, viewType: viewType) + } + + override func viewClass() -> AnyClass { + return ThemeListRowView.self + } +} + + +private final class ThemeListRowView : TableRowView { + private var containerView = GeneralRowContainerView(frame: NSZeroRect) + private let borderView: View = View() + private let tableView = HorizontalTableView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.containerView.addSubview(self.tableView) + self.containerView.addSubview(self.borderView) + + + self.addSubview(containerView) + } + + + override func updateColors() { + guard let item = item as? ThemeListRowItem else { + return + } + self.containerView.backgroundColor = item.theme.colors.background + self.borderView.backgroundColor = item.theme.colors.border + self.backgroundColor = item.viewType.rowBackground + } + + override var backdorColor: NSColor { + guard let item = item as? ThemeListRowItem else { + return theme.colors.background + } + return item.theme.colors.background + } + + override func layout() { + super.layout() + guard let item = item as? ThemeListRowItem else { + return + } + + let innerInset = item.viewType.innerInset + + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + self.borderView.frame = NSMakeRect(innerInset.left, self.containerView.frame.height - .borderSize, self.containerView.frame.width - innerInset.left - innerInset.right, .borderSize) + + self.tableView.frame = NSMakeRect(0, innerInset.top, self.containerView.frame.width, self.containerView.frame.height - innerInset.bottom - innerInset.top) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item: TableRowItem, animated: Bool = false) { + + let previous: ThemeListRowItem? = self.item as? ThemeListRowItem + super.set(item: item, animated: animated) + + guard let item = item as? ThemeListRowItem else { + return + } + + self.tableView.getBackgroundColor = { + item.theme.colors.background + } + + borderView.isHidden = !item.viewType.hasBorder + + self.layout() + + if previous?.cloudThemes == item.cloudThemes && previous?.theme == item.theme && item.selected == previous?.selected { + return + } + + let reloadAnimated = animated && previous?.cloudThemes.count != item.cloudThemes.count + + tableView.beginTableUpdates() + tableView.removeAll(animation: reloadAnimated ? .effectFade : .none) + _ = tableView.addItem(item: HorizontalThemeFirstItem(tableView.frame.size), animation: reloadAnimated ? .effectFade : .none) + + let localPalettes:[LocalPaletteWithReference] = item.local + var scrollItem:HorizontalThemeItem? = nil + for palette in localPalettes { + let selected: Bool + switch item.selected { + case let .local(local, _): + selected = local.parent == palette.palette.parent + default: + selected = false + } + let item = HorizontalThemeItem(tableView.frame.size, stableId: palette.palette.name, context: item.context, theme: item.theme, themeType: .local(palette.palette, palette.cloud), selected: selected, togglePalette: item.togglePalette, menuItems: item.menuItems) + _ = tableView.addItem(item: item) + if item.selected && scrollItem == nil { + scrollItem = item + } + } + + for cloud in item.cloudThemes { + let selected: Bool + switch item.selected { + case let .cloud(theme): + selected = theme.id == cloud.id + default: + selected = false + } + let item = HorizontalThemeItem(tableView.frame.size, stableId: cloud.id, context: item.context, theme: item.theme, themeType: .cloud(cloud), selected: selected, togglePalette: item.togglePalette, menuItems: item.menuItems) + _ = tableView.addItem(item: item, animation: reloadAnimated ? .effectFade : .none) + if item.selected && scrollItem == nil { + scrollItem = item + } + } + + _ = tableView.addItem(item: HorizontalThemeFirstItem(tableView.frame.size), animation: reloadAnimated ? .effectFade : .none) + tableView.endTableUpdates() + + if let item = scrollItem { + self.tableView.scroll(to: .center(id: item.stableId, innerId: nil, animated: reloadAnimated, focus: .init(focus: false), inset: 0), true) + } + } +} diff --git a/Telegram-Mac/ThemePreviewModalController.swift b/Telegram-Mac/ThemePreviewModalController.swift new file mode 100644 index 0000000000..2b33de9fc7 --- /dev/null +++ b/Telegram-Mac/ThemePreviewModalController.swift @@ -0,0 +1,430 @@ +// +// ThemePreviewModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 27/08/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private final class ThemePreviewView : BackgroundView { + fileprivate let segmentControl = CatalinaStyledSegmentController(frame: NSMakeRect(0, 0, 290, 30)) + private let segmentContainer = View() + private let tableView: TableView = TableView(frame: NSZeroRect, isFlipped: false) + weak var controller: ModalViewController? + private let context: AccountContext + required init(frame frameRect: NSRect, context: AccountContext) { + self.context = context + super.init(frame: frameRect) + self.addSubview(tableView) + segmentContainer.addSubview(segmentControl.view) + self.addSubview(segmentContainer) + + + layout() + + + tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self else { + return + } + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + })) + + tableView.afterSetupItem = { [weak self] view, item in + guard let `self` = self else { + return + } + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + } + + + } + + override func layout() { + super.layout() + segmentContainer.frame = NSMakeRect(0, 0, frame.width, 50) + self.segmentControl.view.center() + tableView.frame = NSMakeRect(0, 50, frame.width, frame.height - 50) + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required override init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + fileprivate func addTableItems(_ context: AccountContext, theme: TelegramPresentationTheme) { + + self.tableView.getBackgroundColor = { + if theme.bubbled { + return .clear + } else { + return theme.chatBackground + } + } + + + + + segmentContainer.backgroundColor = theme.colors.background + segmentContainer.borderColor = theme.colors.border + segmentContainer.border = [.Bottom] + segmentControl.theme = CatalinaSegmentTheme(backgroundColor: theme.colors.listBackground, foregroundColor: theme.colors.background, activeTextColor: theme.colors.text, inactiveTextColor: theme.colors.listGrayText) + + tableView.removeAll() + tableView.updateLocalizationAndTheme(theme: theme) + tableView.backgroundColor = theme.colors.background + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: 10, stableId: 0, backgroundColor: .clear)) + + let chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context, disableSelectAbility: true) + + + chatInteraction.getGradientOffsetRect = { [weak self] in + guard let `self` = self else { + return .zero + } + let offset = self.tableView.scrollPosition().current.rect.origin + return CGRect(origin: offset, size: NSMakeSize(350, 400)) + } + + let fromUser1 = TelegramUser(id: PeerId(1), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName1, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + let fromUser2 = TelegramUser(id: PeerId(2), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName2, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + + + let firstMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 18 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreview1, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let firstEntry: ChatHistoryEntry = .MessageEntry(firstMessage, MessageIndex(firstMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + + let secondMessage = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 0), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 20 + 60*60*18, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser2, text: tr(L10n.appearanceSettingsChatPreview2), attributes: [ReplyMessageAttribute(messageId: firstMessage.id)], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary([firstMessage.id : firstMessage]), associatedMessageIds: []) + + let secondEntry: ChatHistoryEntry = .MessageEntry(secondMessage, MessageIndex(secondMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + let thridMessage = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreview3, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let thridEntry: ChatHistoryEntry = .MessageEntry(thridMessage, MessageIndex(thridMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + let item1 = ChatRowItem.item(frame.size, from: firstEntry, interaction: chatInteraction, theme: theme) + let item2 = ChatRowItem.item(frame.size, from: secondEntry, interaction: chatInteraction, theme: theme) + let item3 = ChatRowItem.item(frame.size, from: thridEntry, interaction: chatInteraction, theme: theme) + + + _ = item2.makeSize(frame.width, oldWidth: 0) + _ = item3.makeSize(frame.width, oldWidth: 0) + _ = item1.makeSize(frame.width, oldWidth: 0) + + + tableView.beginTableUpdates() + _ = tableView.addItem(item: item3) + _ = tableView.addItem(item: item2) + _ = tableView.addItem(item: item1) + tableView.endTableUpdates() + + + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + } + + override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + } + +} + +enum ThemePreviewSource { + case localTheme(TelegramPresentationTheme, name: String?) + case cloudTheme(TelegramTheme) +} + + +class ThemePreviewModalController: ModalViewController { + + private let context: AccountContext + private let source:ThemePreviewSource + private let disposable = MetaDisposable() + private var currentTheme: TelegramPresentationTheme = theme + private var fetchDisposable = MetaDisposable() + init(context: AccountContext, source: ThemePreviewSource) { + self.context = context + self.source = source + super.init(frame: NSMakeRect(0, 0, 350, 350)) + self.bar = .init(height: 0) + } + + deinit { + disposable.dispose() + fetchDisposable.dispose() + } + + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.controller = self + let context = self.context + + let updateChatMode:(Bool)->Void = { [weak self] bubbled in + guard let `self` = self else { + return + } + let newTheme = self.currentTheme.withUpdatedChatMode(bubbled).withUpdatedBackgroundSize(NSMakeSize(350, 350)) + self.currentTheme = newTheme + self.genericView.addTableItems(self.context, theme: newTheme) + self.genericView.backgroundMode = newTheme.controllerBackgroundMode + } + + self.genericView.segmentControl.add(segment: CatalinaSegmentedItem(title: L10n.appearanceSettingsChatViewBubbles, handler: { + updateChatMode(true) + })) + + self.genericView.segmentControl.add(segment: CatalinaSegmentedItem(title: L10n.appearanceSettingsChatViewClassic, handler: { + updateChatMode(false) + })) + + switch self.source { + case let .localTheme(theme, _): + self.readyOnce() + self.currentTheme = theme.withUpdatedChatMode(true) + genericView.addTableItems(self.context, theme: theme) + modal?.updateLocalizationAndTheme(theme: theme) + genericView.backgroundMode = theme.controllerBackgroundMode + case let .cloudTheme(theme): + if let settings = theme.settings { + let palette = settings.palette + let wallpaper: Wallpaper + let cloud = settings.wallpaper + if let cloud = cloud { + wallpaper = Wallpaper(cloud) + } else { + if settings.baseTheme == .classic { + wallpaper = .builtin + } else { + wallpaper = .none + } + } + self.disposable.set(showModalProgress(signal: moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wallpaper), for: context.window).start(next: { [weak self] wallpaper in + guard let `self` = self else { + return + } + self.readyOnce() + let newTheme = self.currentTheme + .withUpdatedColors(palette) + .withUpdatedWallpaper(ThemeWallpaper(wallpaper: wallpaper, associated: AssociatedWallpaper(cloud: cloud, wallpaper: wallpaper))) + .withUpdatedChatMode(true) + .withUpdatedBackgroundSize(WallpaperDimensions.aspectFilled(NSMakeSize(600, 600))) + self.currentTheme = newTheme + self.genericView.addTableItems(context, theme: newTheme) + self.modal?.updateLocalizationAndTheme(theme: newTheme) + self.genericView.backgroundMode = newTheme.controllerBackgroundMode + + })) + + } else if let file = theme.file { + let signal = loadCloudPaletteAndWallpaper(context: context, file: file) + disposable.set(showModalProgress(signal: signal |> deliverOnMainQueue, for: context.window).start(next: { [weak self] data in + guard let `self` = self else { + return + } + if let (palette, wallpaper, cloud) = data { + self.readyOnce() + let newTheme = self.currentTheme + .withUpdatedColors(palette) + .withUpdatedWallpaper(ThemeWallpaper(wallpaper: wallpaper, associated: AssociatedWallpaper(cloud: cloud, wallpaper: wallpaper))) + .withUpdatedChatMode(true) + self.currentTheme = newTheme + self.genericView.addTableItems(context, theme: newTheme) + self.modal?.updateLocalizationAndTheme(theme: newTheme) + self.genericView.backgroundMode = newTheme.controllerBackgroundMode + } else { + self.close() + alert(for: context.window, info: L10n.unknownError) + } + + })) + fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: AnyMediaReference.standalone(media: file), resource: file.resource)).start()) + } + } + + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + switch self.source { + case let .cloudTheme(theme): + + let count:Int32 = theme.installCount + + var countTitle = L10n.themePreviewUsesCountCountable(Int(count)) + countTitle = countTitle.replacingOccurrences(of: "\(count)", with: count.formattedWithSeparator) + + return (left: ModalHeaderData(image: currentTheme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: theme.title, subtitle: count > 0 ? countTitle : nil), right: ModalHeaderData(image: currentTheme.icons.modalShare, handler: { [weak self] in + self?.share() + })) + case let .localTheme(theme, name): + return (left: ModalHeaderData(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: name ?? localizedString("AppearanceSettings.ColorTheme.\(theme.colors.name)")), right: nil) + } + + } + + private func share() { + switch self.source { + case let .cloudTheme(theme): + showModal(with: ShareModalController(ShareLinkObject(self.context, link: "https://t.me/addtheme/\(theme.slug)")), for: self.context.window) + default: + break + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + private func saveAccent() { + + let context = self.context + let currentTheme = self.currentTheme + let colors = currentTheme.colors + + let cloudTheme: TelegramTheme? + switch self.source { + case let .cloudTheme(t): + cloudTheme = t + default: + cloudTheme = nil + } + _ = updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + var settings = settings + .withUpdatedPalette(colors) + .updateWallpaper { _ in + return currentTheme.wallpaper + } + .withUpdatedCloudTheme(cloudTheme) + .withUpdatedBubbled(currentTheme.bubbled) + + + let defaultTheme: DefaultTheme + + if let cloudTheme = cloudTheme { + defaultTheme = DefaultTheme(local: colors.parent, cloud: DefaultCloudTheme(cloud: cloudTheme, palette: colors, wallpaper: currentTheme.wallpaper.associated ?? AssociatedWallpaper(cloud: currentTheme.wallpaper.associated?.cloud, wallpaper: currentTheme.wallpaper.wallpaper))) + } else { + defaultTheme = DefaultTheme(local: colors.parent, cloud: nil) + } + + if colors.isDark { + settings = settings.withUpdatedDefaultDark(defaultTheme) + } else { + settings = settings.withUpdatedDefaultDay(defaultTheme) + } + settings = settings.withUpdatedDefaultIsDark(colors.isDark).saveDefaultAccent(color: PaletteAccentColor(colors.accent, (top: colors.bubbleBackgroundTop_outgoing, colors.bubbleBackgroundBottom_outgoing))).saveDefaultWallpaper().withSavedAssociatedTheme() + return settings + }).start() + + delay(0.1, closure: { [weak self] in + self?.close() + }) + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: L10n.modalSet, accept: { [weak self] in + self?.saveAccent() + }, drawBorder: true, singleButton: true) + } + + override var dynamicSize: Bool { + return true + } + + override func initializer() -> NSView { + return ThemePreviewView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), context: self.context) + } + + override func measure(size: NSSize) { + self.modal?.resize(with: NSMakeSize(350, 350), animated: false) + } + + private var genericView:ThemePreviewView { + return self.view as! ThemePreviewView + } + override func viewClass() -> AnyClass { + return ThemePreviewView.self + } +} + + +func paletteFromFile(context: AccountContext, file: TelegramMediaFile) -> ColorPalette? { + let path = context.account.postbox.mediaBox.resourcePath(file.resource) + + return importPalette(path) +} + +func loadCloudPaletteAndWallpaper(context: AccountContext, file: TelegramMediaFile) -> Signal<(ColorPalette, Wallpaper, TelegramWallpaper?)?, NoError> { + return context.account.postbox.mediaBox.resourceData(file.resource) + |> filter { $0.complete } + |> take(1) + |> map { importPalette($0.path) } + |> mapToSignal { palette -> Signal<(ColorPalette, Wallpaper, TelegramWallpaper?)?, NoError> in + if let palette = palette { + switch palette.wallpaper { + case .builtin: + return .single((palette, Wallpaper.builtin, nil)) + case .none: + return .single((palette, Wallpaper.none, nil)) + case let .color(color): + return .single((palette, Wallpaper.color(color.argb), nil)) + case let .url(url): + let link = inApp(for: url as NSString, context: context) + switch link { + case let .wallpaper(values): + switch values.preview { + case let .slug(slug, settings): + return getWallpaper(network: context.account.network, slug: slug) + |> mapToSignal { cloud in + return moveWallpaperToCache(postbox: context.account.postbox, wallpaper: Wallpaper(cloud).withUpdatedSettings(settings)) |> map { wallpaper in + return (palette, wallpaper, cloud) + } |> castError(GetWallpaperError.self) + } + |> `catch` { _ in + return .single((palette, .none, nil)) + } + default: + break + } + default: + break + } + return .single(nil) + } + } else { + return .single(nil) + } + } +} diff --git a/Telegram-Mac/ThemePreviewRowItem.swift b/Telegram-Mac/ThemePreviewRowItem.swift new file mode 100644 index 0000000000..fa07d02e1f --- /dev/null +++ b/Telegram-Mac/ThemePreviewRowItem.swift @@ -0,0 +1,176 @@ +// +// ThemePreviewRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +class ThemePreviewRowItem: GeneralRowItem { + + fileprivate let theme: TelegramPresentationTheme + fileprivate let items:[TableRowItem] + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, theme: TelegramPresentationTheme, viewType: GeneralViewType) { + self.theme = theme.withUpdatedBackgroundSize(WallpaperDimensions.aspectFilled(NSMakeSize(200, 200))) + + let chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context, disableSelectAbility: true) + + + + let fromUser1 = TelegramUser(id: PeerId(1), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName1, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + let fromUser2 = TelegramUser(id: PeerId(2), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName2, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + + + let firstMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 18 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreview1, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let firstEntry: ChatHistoryEntry = .MessageEntry(firstMessage, MessageIndex(firstMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + + let secondMessage = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 0), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 20 + 60*60*18, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser2, text: tr(L10n.appearanceSettingsChatPreview2), attributes: [ReplyMessageAttribute(messageId: firstMessage.id)], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary([firstMessage.id : firstMessage]), associatedMessageIds: []) + + let secondEntry: ChatHistoryEntry = .MessageEntry(secondMessage, MessageIndex(secondMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + let thridMessage = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: L10n.appearanceSettingsChatPreview3, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let thridEntry: ChatHistoryEntry = .MessageEntry(thridMessage, MessageIndex(thridMessage), true, theme.bubbled ? .bubble : .list, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + let item1 = ChatRowItem.item(initialSize, from: firstEntry, interaction: chatInteraction, theme: theme) + let item2 = ChatRowItem.item(initialSize, from: secondEntry, interaction: chatInteraction, theme: theme) + let item3 = ChatRowItem.item(initialSize, from: thridEntry, interaction: chatInteraction, theme: theme) + + + self.items = [item1, item2, item3] + + super.init(initialSize, stableId: stableId, viewType: viewType) + + chatInteraction.getGradientOffsetRect = { [weak self] in + guard let `self` = self else { + return .zero + } + return CGRect(origin: NSMakePoint(0, self.height), size: NSMakeSize(self.width, 400)) + } + + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + let itemWidth = self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right + for item in items { + _ = item.makeSize(itemWidth, oldWidth: 0) + } + return true + } + + override var instantlyResize: Bool { + return true + } + + override var height: CGFloat { + var height: CGFloat = self.viewType.innerInset.top + self.viewType.innerInset.bottom + + for item in self.items { + height += item.height + } + return height + } + + override func viewClass() -> AnyClass { + return ThemePreviewRowView.self + } + +} + +private final class ThemePreviewRowView : TableRowView { + private var containerView = GeneralRowContainerView(frame: NSZeroRect) + private let backgroundView: BackgroundView + private let borderView: View = View() + required init(frame frameRect: NSRect) { + backgroundView = BackgroundView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + self.containerView.addSubview(self.backgroundView) + self.containerView.addSubview(self.borderView) + self.addSubview(containerView) + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? ThemePreviewRowItem else { + return + } + + self.layout() + + self.backgroundView.removeAllSubviews() + + switch item.theme.backgroundMode { + case .background, .tiled: + borderView.isHidden = item.theme.bubbled + case .plain: + borderView.isHidden = false + case .gradient: + borderView.isHidden = item.theme.bubbled + case let .color(color): + borderView.isHidden = color != item.theme.colors.background + } + + var y: CGFloat = item.viewType.innerInset.top + for item in item.items { + let vz = item.viewClass() as! TableRowView.Type + let view = vz.init(frame:NSMakeRect(0, y, self.backgroundView.frame.width, item.height)) + view.set(item: item, animated: false) + self.backgroundView.addSubview(view) + + if let view = view as? ChatRowView { + view.updateBackground(animated: false, rotated: true) + } + + y += item.height + } + + + } + + override func updateColors() { + guard let item = item as? ThemePreviewRowItem else { + return + } + self.containerView.backgroundColor = background + self.backgroundView.backgroundMode = item.theme.bubbled ? item.theme.backgroundMode : .color(color: item.theme.colors.chatBackground) + self.borderView.backgroundColor = theme.colors.border + self.backgroundColor = item.viewType.rowBackground + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func layout() { + super.layout() + guard let item = item as? ThemePreviewRowItem else { + return + } + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + self.backgroundView.frame = self.containerView.bounds + self.borderView.frame = NSMakeRect(0, self.containerView.frame.height - .borderSize, self.containerView.frame.width, .borderSize) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/ThemeSettings.swift b/Telegram-Mac/ThemeSettings.swift index 320244aafb..76c0977215 100644 --- a/Telegram-Mac/ThemeSettings.swift +++ b/Telegram-Mac/ThemeSettings.swift @@ -6,183 +6,771 @@ // Copyright © 2017 Telegram. All rights reserved. // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore import TGUIKit +import SyncCore public enum PresentationThemeParsingError: Error { case generic } -private func parseColor(_ decoder: PostboxDecoder, _ key: String) -> NSColor { +private func parseColor(_ decoder: PostboxDecoder, _ key: String) -> NSColor? { if let value = decoder.decodeOptionalInt32ForKey(key) { return NSColor(argb: UInt32(bitPattern: value)) - } else { - return NSColor(0x000000) - } -} - -struct ThemePalleteSettings: PreferencesEntry, Equatable { - let background: NSColor - let text: NSColor - let grayText:NSColor - let link:NSColor - let blueUI:NSColor - let redUI:NSColor - let greenUI:NSColor - let blackTransparent:NSColor - let grayTransparent:NSColor - let grayUI:NSColor - let darkGrayText:NSColor - let blueText:NSColor - let blueSelect:NSColor - let selectText:NSColor - let blueFill:NSColor - let border:NSColor - let grayBackground:NSColor - let grayForeground:NSColor - let grayIcon:NSColor - let blueIcon:NSColor - let badgeMuted:NSColor - let badge:NSColor - let indicatorColor: NSColor - let selectMessage: NSColor - let dark: Bool + } + return nil +} + + +extension PaletteWallpaper { + var wallpaper: Wallpaper { + switch self { + case .none: + return .none + case .builtin: + return .builtin + case let .color(color): + return .color(color.argb) + default: + return .none + } + } +} + + +struct AssociatedWallpaper : PostboxCoding, Equatable { + let cloud: TelegramWallpaper? + let wallpaper: Wallpaper + init(decoder: PostboxDecoder) { + self.cloud = decoder.decodeObjectForKey("c", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper + self.wallpaper = decoder.decodeObjectForKey("w", decoder: { Wallpaper(decoder: $0) }) as! Wallpaper + } + + static func ==(lhs: AssociatedWallpaper, rhs: AssociatedWallpaper) -> Bool { + if let lhsCloud = lhs.cloud, let rhsCloud = rhs.cloud { + switch lhsCloud { + case let .file(id, accessHash, isCreator, isDefault, isPattern, isDark, slug, lhsFile, settings): + if case .file(id, accessHash, isCreator, isDefault, isPattern, isDark, slug, let rhsFile, settings) = rhsCloud { + return lhsFile.isSemanticallyEqual(to: rhsFile) && lhs.wallpaper == rhs.wallpaper + } else { + return lhsCloud == rhsCloud && lhs.wallpaper == rhs.wallpaper + } + default: + return lhsCloud == rhsCloud && lhs.wallpaper == rhs.wallpaper + } + } + return lhs.cloud == rhs.cloud && lhs.wallpaper == rhs.wallpaper + } + + init() { + self.cloud = nil + self.wallpaper = .none + } + init(cloud: TelegramWallpaper?, wallpaper: Wallpaper) { + self.cloud = cloud + self.wallpaper = wallpaper + } + + func encode(_ encoder: PostboxEncoder) { + if let cloud = cloud { + encoder.encodeObject(cloud, forKey: "c") + } else { + encoder.encodeNil(forKey: "c") + } + encoder.encodeObject(self.wallpaper, forKey: "w") + } + +} + +struct ThemeWallpaper : PostboxCoding, Equatable { + let wallpaper: Wallpaper + let associated: AssociatedWallpaper? + + init() { + self.wallpaper = .none + self.associated = nil + } + init(wallpaper: Wallpaper, associated: AssociatedWallpaper?) { + self.wallpaper = wallpaper + self.associated = associated + } + + init(decoder: PostboxDecoder) { + self.wallpaper = decoder.decodeObjectForKey("w", decoder: { Wallpaper(decoder: $0) }) as? Wallpaper ?? .none + self.associated = decoder.decodeObjectForKey("aw", decoder: { AssociatedWallpaper(decoder: $0) }) as? AssociatedWallpaper + } + + func encode(_ encoder: PostboxEncoder) { + if let associated = associated { + encoder.encodeObject(associated, forKey: "aw") + } else { + encoder.encodeNil(forKey: "aw") + } + encoder.encodeObject(self.wallpaper, forKey: "w") + } + + func withUpdatedWallpaper(_ wallpaper: Wallpaper) -> ThemeWallpaper { + return ThemeWallpaper(wallpaper: wallpaper, associated: self.associated) + } + func withUpdatedAssociated(_ associated: AssociatedWallpaper?) -> ThemeWallpaper { + return ThemeWallpaper(wallpaper: self.wallpaper, associated: associated) + } + + var paletteWallpaper: PaletteWallpaper { + switch self.wallpaper { + case let .file(slug, _, settings, isPattern): + var options: [String] = [] + if settings.blur { + options.append("mode=blur") + } + if isPattern { + if let pattern = settings.color { + var color = NSColor(argb: pattern).hexString.lowercased() + color = String(color[color.index(after: color.startIndex) ..< color.endIndex]) + options.append("bg_color=\(color)") + } + if let intensity = settings.intensity { + options.append("intensity=\(intensity)") + } + } + var optionsString = "" + if !options.isEmpty { + optionsString = "?\(options.joined(separator: "&"))" + } + return .url("https://t.me/bg/\(slug)\(optionsString)") + case .builtin: + return .builtin + default: + return .none + } + } + +} + +extension PaletteAccentColor { + static func initWith(decoder: PostboxDecoder) -> PaletteAccentColor { + let accent = NSColor(argb: UInt32(bitPattern: decoder.decodeInt32ForKey("c", orElse: 0))) + var messages: (top: NSColor, bottom: NSColor)? = nil + if let rawTop = decoder.decodeOptionalInt32ForKey("bt"), let rawBottom = decoder.decodeOptionalInt32ForKey("bb") { + messages = (top: NSColor(argb: UInt32(bitPattern: rawTop)), bottom: NSColor(argb: UInt32(bitPattern: rawBottom))) + } + return PaletteAccentColor(accent, messages) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(Int32(bitPattern: self.accent.argb), forKey: "c") + if let messages = self.messages { + encoder.encodeInt32(Int32(bitPattern: messages.top.argb), forKey: "bt") + encoder.encodeInt32(Int32(bitPattern: messages.bottom.argb), forKey: "bb") + } else { + encoder.encodeNil(forKey: "bt") + encoder.encodeNil(forKey: "bb") + } + } + +} + +extension ColorPalette { + func encode(_ encoder: PostboxEncoder) { + + encoder.encodeBool(self.isNative, forKey: "isNative") + encoder.encodeString(self.name, forKey: "name") + encoder.encodeString(self.copyright, forKey: "copyright") + encoder.encodeString(self.parent.rawValue, forKey: "parent") + encoder.encodeBool(self.isDark, forKey: "dark") + encoder.encodeBool(self.tinted, forKey: "tinted") + encoder.encodeString(self.wallpaper.toString, forKey: "pw") + encoder.encodeObjectArrayWithEncoder(self.accentList, forKey: "accentList_1", encoder: { value, encoder in + return value.encode(encoder) + }) + + for child in Mirror(reflecting: self).children { + if let label = child.label { + if let value = child.value as? NSColor { + var label = label + _ = label.removeFirst() + if label == "selectTextBubble_outgoing" { + var bp:Int = 0 + bp += 1 + } + NSColor.init(argb: value.argb).hexString + encoder.encodeInt32(Int32(bitPattern: value.argb), forKey: label) + } + } + } + + //encoder.encodeString(self.accentList.map { $0.hexString }.joined(separator: ","), forKey: "accentList") + } + + static func initWith(decoder: PostboxDecoder) -> ColorPalette { + let dark = decoder.decodeBoolForKey("dark", orElse: false) + let tinted = decoder.decodeBoolForKey("tinted", orElse: false) + + let parent: TelegramBuiltinTheme = TelegramBuiltinTheme(rawValue: decoder.decodeStringForKey("parent", orElse: TelegramBuiltinTheme.dayClassic.rawValue)) ?? (dark ? .nightAccent : .dayClassic) + let copyright = decoder.decodeStringForKey("copyright", orElse: "Telegram") + + let isNative = decoder.decodeBoolForKey("isNative", orElse: false) + let name = decoder.decodeStringForKey("name", orElse: "Default") + + let palette: ColorPalette = parent.palette + let pw = PaletteWallpaper(decoder.decodeStringForKey("pw", orElse: "none")) + + + + let accentList: [PaletteAccentColor] = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("accentList_1", decoder: { PaletteAccentColor.initWith(decoder: $0) })) ?? [] + + return ColorPalette(isNative: isNative, + isDark: dark, + tinted: tinted, + name: name, + parent: parent, + wallpaper: pw ?? palette.wallpaper, + copyright: copyright, + accentList: accentList, + basicAccent: parseColor(decoder, "basicAccent") ?? palette.basicAccent, + background: parseColor(decoder, "background") ?? palette.background, + text: parseColor(decoder, "text") ?? palette.text, + grayText: parseColor(decoder, "grayText") ?? palette.grayText, + link: parseColor(decoder, "link") ?? palette.link, + accent: parseColor(decoder, "accent") ?? palette.accent, + redUI: parseColor(decoder, "redUI") ?? palette.redUI, + greenUI: parseColor(decoder, "greenUI") ?? palette.greenUI, + blackTransparent: parseColor(decoder, "blackTransparent") ?? palette.blackTransparent, + grayTransparent: parseColor(decoder, "grayTransparent") ?? palette.grayTransparent, + grayUI: parseColor(decoder, "grayUI") ?? palette.grayUI, + darkGrayText: parseColor(decoder, "darkGrayText") ?? palette.darkGrayText, + accentSelect: parseColor(decoder, "accentSelect") ?? palette.accentSelect, + selectText: parseColor(decoder, "selectText") ?? palette.selectText, + border: parseColor(decoder, "border") ?? palette.border, + grayBackground: parseColor(decoder, "grayBackground") ?? palette.grayBackground, + grayForeground: parseColor(decoder, "grayForeground") ?? palette.grayForeground, + grayIcon: parseColor(decoder, "grayIcon") ?? palette.grayIcon, + accentIcon: parseColor(decoder, "accentIcon") ?? parseColor(decoder, "blueIcon") ?? palette.accentIcon, + badgeMuted: parseColor(decoder, "badgeMuted") ?? palette.badgeMuted, + badge: parseColor(decoder, "badge") ?? palette.badge, + indicatorColor: parseColor(decoder, "indicatorColor") ?? palette.indicatorColor, + selectMessage: parseColor(decoder, "selectMessage") ?? palette.selectMessage, + monospacedPre: parseColor(decoder, "monospacedPre") ?? palette.monospacedPre, + monospacedCode: parseColor(decoder, "monospacedCode") ?? palette.monospacedCode, + monospacedPreBubble_incoming: parseColor(decoder, "monospacedPreBubble_incoming") ?? palette.monospacedPreBubble_incoming, + monospacedPreBubble_outgoing: parseColor(decoder, "monospacedPreBubble_outgoing") ?? palette.monospacedPreBubble_outgoing, + monospacedCodeBubble_incoming: parseColor(decoder, "monospacedCodeBubble_incoming") ?? palette.monospacedCodeBubble_incoming, + monospacedCodeBubble_outgoing: parseColor(decoder, "monospacedCodeBubble_outgoing") ?? palette.monospacedCodeBubble_outgoing, + selectTextBubble_incoming: parseColor(decoder, "selectTextBubble_incoming") ?? palette.selectTextBubble_incoming, + selectTextBubble_outgoing: parseColor(decoder, "selectTextBubble_outgoing") ?? palette.selectTextBubble_outgoing, + bubbleBackground_incoming: parseColor(decoder, "bubbleBackground_incoming") ?? palette.bubbleBackground_incoming, + bubbleBackgroundTop_outgoing: parseColor(decoder, "bubbleBackgroundTop_outgoing") ?? palette.bubbleBackgroundTop_outgoing, + bubbleBackgroundBottom_outgoing: parseColor(decoder, "bubbleBackgroundBottom_outgoing") ?? palette.bubbleBackgroundTop_outgoing, + bubbleBorder_incoming: parseColor(decoder, "bubbleBorder_incoming") ?? palette.bubbleBorder_incoming, + bubbleBorder_outgoing: parseColor(decoder, "bubbleBorder_outgoing") ?? palette.bubbleBorder_outgoing, + grayTextBubble_incoming: parseColor(decoder, "grayTextBubble_incoming") ?? palette.grayTextBubble_incoming, + grayTextBubble_outgoing: parseColor(decoder, "grayTextBubble_outgoing") ?? palette.grayTextBubble_outgoing, + grayIconBubble_incoming: parseColor(decoder, "grayIconBubble_incoming") ?? palette.grayIconBubble_incoming, + grayIconBubble_outgoing: parseColor(decoder, "grayIconBubble_outgoing") ?? palette.grayIconBubble_outgoing, + accentIconBubble_incoming: parseColor(decoder, "accentIconBubble_incoming") ?? parseColor(decoder, "blueIconBubble_incoming") ?? palette.accentIconBubble_incoming, + accentIconBubble_outgoing: parseColor(decoder, "accentIconBubble_outgoing") ?? parseColor(decoder, "blueIconBubble_outgoing") ?? palette.accentIconBubble_outgoing, + linkBubble_incoming: parseColor(decoder, "linkBubble_incoming") ?? palette.linkBubble_incoming, + linkBubble_outgoing: parseColor(decoder, "linkBubble_outgoing") ?? palette.linkBubble_outgoing, + textBubble_incoming: parseColor(decoder, "textBubble_incoming") ?? palette.textBubble_incoming, + textBubble_outgoing: parseColor(decoder, "textBubble_outgoing") ?? palette.textBubble_outgoing, + selectMessageBubble: parseColor(decoder, "selectMessageBubble") ?? palette.selectMessageBubble, + fileActivityBackground: parseColor(decoder, "fileActivityBackground") ?? palette.fileActivityBackground, + fileActivityForeground: parseColor(decoder, "fileActivityForeground") ?? palette.fileActivityForeground, + fileActivityBackgroundBubble_incoming: parseColor(decoder, "fileActivityBackgroundBubble_incoming") ?? palette.fileActivityBackgroundBubble_incoming, + fileActivityBackgroundBubble_outgoing: parseColor(decoder, "fileActivityBackgroundBubble_outgoing") ?? palette.fileActivityBackgroundBubble_outgoing, + fileActivityForegroundBubble_incoming: parseColor(decoder, "fileActivityForegroundBubble_incoming") ?? palette.fileActivityForegroundBubble_incoming, + fileActivityForegroundBubble_outgoing: parseColor(decoder, "fileActivityForegroundBubble_outgoing") ?? palette.fileActivityForegroundBubble_outgoing, + waveformBackground: parseColor(decoder, "waveformBackground") ?? palette.waveformBackground, + waveformForeground: parseColor(decoder, "waveformForeground") ?? palette.waveformForeground, + waveformBackgroundBubble_incoming: parseColor(decoder, "waveformBackgroundBubble_incoming") ?? palette.waveformBackgroundBubble_incoming, + waveformBackgroundBubble_outgoing: parseColor(decoder, "waveformBackgroundBubble_outgoing") ?? palette.waveformBackgroundBubble_outgoing, + waveformForegroundBubble_incoming: parseColor(decoder, "waveformForegroundBubble_incoming") ?? palette.waveformForegroundBubble_incoming, + waveformForegroundBubble_outgoing: parseColor(decoder, "waveformForegroundBubble_outgoing") ?? palette.waveformForegroundBubble_outgoing, + webPreviewActivity: parseColor(decoder, "webPreviewActivity") ?? palette.webPreviewActivity, + webPreviewActivityBubble_incoming: parseColor(decoder, "webPreviewActivityBubble_incoming") ?? palette.webPreviewActivityBubble_incoming, + webPreviewActivityBubble_outgoing: parseColor(decoder, "webPreviewActivityBubble_outgoing") ?? palette.webPreviewActivityBubble_outgoing, + redBubble_incoming: parseColor(decoder, "redBubble_incoming") ?? palette.redBubble_incoming, + redBubble_outgoing: parseColor(decoder, "redBubble_outgoing") ?? palette.redBubble_outgoing, + greenBubble_incoming: parseColor(decoder, "greenBubble_incoming") ?? palette.greenBubble_incoming, + greenBubble_outgoing: parseColor(decoder, "greenBubble_outgoing") ?? palette.greenBubble_outgoing, + chatReplyTitle: parseColor(decoder, "chatReplyTitle") ?? palette.chatReplyTitle, + chatReplyTextEnabled: parseColor(decoder, "chatReplyTextEnabled") ?? palette.chatReplyTextEnabled, + chatReplyTextDisabled: parseColor(decoder, "chatReplyTextDisabled") ?? palette.chatReplyTextDisabled, + chatReplyTitleBubble_incoming: parseColor(decoder, "chatReplyTitleBubble_incoming") ?? palette.chatReplyTitleBubble_incoming, + chatReplyTitleBubble_outgoing: parseColor(decoder, "chatReplyTitleBubble_outgoing") ?? palette.chatReplyTitleBubble_outgoing, + chatReplyTextEnabledBubble_incoming: parseColor(decoder, "chatReplyTextEnabledBubble_incoming") ?? palette.chatReplyTextEnabledBubble_incoming, + chatReplyTextEnabledBubble_outgoing: parseColor(decoder, "chatReplyTextEnabledBubble_outgoing") ?? palette.chatReplyTextEnabledBubble_outgoing, + chatReplyTextDisabledBubble_incoming: parseColor(decoder, "chatReplyTextDisabledBubble_incoming") ?? palette.chatReplyTextDisabledBubble_incoming, + chatReplyTextDisabledBubble_outgoing: parseColor(decoder, "chatReplyTextDisabledBubble_outgoing") ?? palette.chatReplyTextDisabledBubble_outgoing, + groupPeerNameRed: parseColor(decoder, "groupPeerNameRed") ?? palette.groupPeerNameRed, + groupPeerNameOrange: parseColor(decoder, "groupPeerNameOrange") ?? palette.groupPeerNameOrange, + groupPeerNameViolet:parseColor(decoder, "groupPeerNameViolet") ?? palette.groupPeerNameViolet, + groupPeerNameGreen:parseColor(decoder, "groupPeerNameGreen") ?? palette.groupPeerNameGreen, + groupPeerNameCyan: parseColor(decoder, "groupPeerNameCyan") ?? palette.groupPeerNameCyan, + groupPeerNameLightBlue: parseColor(decoder, "groupPeerNameLightBlue") ?? palette.groupPeerNameLightBlue, + groupPeerNameBlue: parseColor(decoder, "groupPeerNameBlue") ?? palette.groupPeerNameBlue, + peerAvatarRedTop: parseColor(decoder, "peerAvatarRedTop") ?? palette.peerAvatarRedTop, + peerAvatarRedBottom: parseColor(decoder, "peerAvatarRedBottom") ?? palette.peerAvatarRedBottom, + peerAvatarOrangeTop: parseColor(decoder, "peerAvatarOrangeTop") ?? palette.peerAvatarOrangeTop, + peerAvatarOrangeBottom: parseColor(decoder, "peerAvatarOrangeBottom") ?? palette.peerAvatarOrangeBottom, + peerAvatarVioletTop: parseColor(decoder, "peerAvatarVioletTop") ?? palette.peerAvatarVioletTop, + peerAvatarVioletBottom: parseColor(decoder, "peerAvatarVioletBottom") ?? palette.peerAvatarVioletBottom, + peerAvatarGreenTop: parseColor(decoder, "peerAvatarGreenTop") ?? palette.peerAvatarGreenTop, + peerAvatarGreenBottom: parseColor(decoder, "peerAvatarGreenBottom") ?? palette.peerAvatarGreenBottom, + peerAvatarCyanTop: parseColor(decoder, "peerAvatarCyanTop") ?? palette.peerAvatarCyanTop, + peerAvatarCyanBottom: parseColor(decoder, "peerAvatarCyanBottom") ?? palette.peerAvatarCyanBottom, + peerAvatarBlueTop: parseColor(decoder, "peerAvatarBlueTop") ?? palette.peerAvatarBlueTop, + peerAvatarBlueBottom: parseColor(decoder, "peerAvatarBlueBottom") ?? palette.peerAvatarBlueBottom, + peerAvatarPinkTop: parseColor(decoder, "peerAvatarPinkTop") ?? palette.peerAvatarPinkTop, + peerAvatarPinkBottom: parseColor(decoder, "peerAvatarPinkBottom") ?? palette.peerAvatarPinkBottom, + bubbleBackgroundHighlight_incoming: parseColor(decoder, "bubbleBackgroundHighlight_incoming") ?? palette.bubbleBackgroundHighlight_incoming, + bubbleBackgroundHighlight_outgoing: parseColor(decoder, "bubbleBackgroundHighlight_outgoing") ?? palette.bubbleBackgroundHighlight_outgoing, + chatDateActive: parseColor(decoder, "chatDateActive") ?? palette.chatDateActive, + chatDateText: parseColor(decoder, "chatDateText") ?? palette.chatDateText, + revealAction_neutral1_background: parseColor(decoder, "revealAction_neutral1_background") ?? palette.revealAction_neutral1_background, + revealAction_neutral1_foreground: parseColor(decoder, "revealAction_neutral1_foreground") ?? palette.revealAction_neutral1_foreground, + revealAction_neutral2_background: parseColor(decoder, "revealAction_neutral2_background") ?? palette.revealAction_neutral2_background, + revealAction_neutral2_foreground: parseColor(decoder, "revealAction_neutral2_foreground") ?? palette.revealAction_neutral2_foreground, + revealAction_destructive_background: parseColor(decoder, "revealAction_destructive_background") ?? palette.revealAction_destructive_background, + revealAction_destructive_foreground: parseColor(decoder, "revealAction_destructive_foreground") ?? palette.revealAction_destructive_foreground, + revealAction_constructive_background: parseColor(decoder, "revealAction_constructive_background") ?? palette.revealAction_constructive_background, + revealAction_constructive_foreground: parseColor(decoder, "revealAction_constructive_foreground") ?? palette.revealAction_constructive_foreground, + revealAction_accent_background: parseColor(decoder, "revealAction_accent_background") ?? palette.revealAction_accent_background, + revealAction_accent_foreground: parseColor(decoder, "revealAction_accent_foreground") ?? palette.revealAction_accent_foreground, + revealAction_warning_background: parseColor(decoder, "revealAction_warning_background") ?? palette.revealAction_warning_background, + revealAction_warning_foreground: parseColor(decoder, "revealAction_warning_foreground") ?? palette.revealAction_warning_foreground, + revealAction_inactive_background: parseColor(decoder, "revealAction_inactive_background") ?? palette.revealAction_inactive_background, + revealAction_inactive_foreground: parseColor(decoder, "revealAction_inactive_foreground") ?? palette.revealAction_inactive_foreground, + chatBackground: parseColor(decoder, "chatBackground") ?? palette.chatBackground, + listBackground: parseColor(decoder, "listBackground") ?? palette.listBackground, + listGrayText: parseColor(decoder, "listGrayText") ?? palette.listGrayText, + grayHighlight: parseColor(decoder, "grayHighlight") ?? palette.grayHighlight, + focusAnimationColor: parseColor(decoder, "focusAnimationColor") ?? palette.focusAnimationColor + ) + } +} + +struct DefaultCloudTheme : Equatable, PostboxCoding { + let cloud: TelegramTheme + let palette: ColorPalette + let wallpaper: AssociatedWallpaper + init(cloud: TelegramTheme, palette: ColorPalette, wallpaper: AssociatedWallpaper) { + self.cloud = cloud + self.palette = palette + self.wallpaper = wallpaper + } + + init(decoder: PostboxDecoder) { + self.cloud = decoder.decodeObjectForKey("c", decoder: { TelegramTheme(decoder: $0) }) as! TelegramTheme + self.palette = decoder.decodeAnyObjectForKey("p", decoder: { ColorPalette.initWith(decoder: $0) }) as! ColorPalette + self.wallpaper = decoder.decodeObjectForKey("w", decoder: { AssociatedWallpaper(decoder: $0) }) as! AssociatedWallpaper + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.cloud, forKey: "c") + encoder.encodeObjectWithEncoder(self.palette, encoder: { self.palette.encode($0) }, forKey: "p") + encoder.encodeObject(self.wallpaper, forKey: "w") + } +} + + + +struct DefaultTheme : Equatable, PostboxCoding { + let local: TelegramBuiltinTheme + let cloud: DefaultCloudTheme? + init(local: TelegramBuiltinTheme, cloud: DefaultCloudTheme?) { + self.local = local + self.cloud = cloud + } + func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.local.rawValue, forKey: "dl_1") + if let cloud = cloud { + encoder.encodeObject(cloud, forKey: "dc") + } else { + encoder.encodeNil(forKey: "dc") + } + } + init(decoder: PostboxDecoder) { + self.local = TelegramBuiltinTheme(rawValue: decoder.decodeStringForKey("dl_1", orElse: TelegramBuiltinTheme.dayClassic.rawValue)) ?? .dayClassic + self.cloud = decoder.decodeObjectForKey("dc", decoder: { DefaultCloudTheme(decoder: $0) }) as? DefaultCloudTheme + } + func withUpdatedLocal(_ local: TelegramBuiltinTheme) -> DefaultTheme { + return DefaultTheme(local: local, cloud: self.cloud) + } + func updateCloud(_ f: (DefaultCloudTheme?)->DefaultCloudTheme?) -> DefaultTheme { + return DefaultTheme(local: self.local, cloud: f(self.cloud)) + } +} + +struct LocalWallapper : Equatable, PostboxCoding { + let name: TelegramBuiltinTheme + let cloud: TelegramTheme? + let wallpaper: AssociatedWallpaper + let associated: AssociatedWallpaper? + let accentColor: UInt32 + init(name: TelegramBuiltinTheme, accentColor: UInt32, wallpaper: AssociatedWallpaper, associated: AssociatedWallpaper?, cloud: TelegramTheme?) { + self.name = name + self.accentColor = accentColor + self.wallpaper = wallpaper + self.cloud = cloud + self.associated = associated + } + + func isEqual(to other: ColorPalette) -> Bool { + if self.name != other.parent { + return false + } + if self.accentColor != 0 { + return self.accentColor == other.accent.argb + } + return self.cloud == nil + } + + init(decoder: PostboxDecoder) { + self.name = TelegramBuiltinTheme(rawValue: decoder.decodeStringForKey("name", orElse: dayClassicPalette.name)) ?? .dayClassic + self.wallpaper = decoder.decodeObjectForKey("aw", decoder: { AssociatedWallpaper(decoder: $0) }) as! AssociatedWallpaper + self.associated = decoder.decodeObjectForKey("as", decoder: { AssociatedWallpaper(decoder: $0) }) as? AssociatedWallpaper + self.cloud = decoder.decodeObjectForKey("cloud", decoder: { TelegramTheme(decoder: $0) }) as? TelegramTheme + self.accentColor = UInt32(bitPattern: decoder.decodeInt32ForKey("ac", orElse: 0)) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.wallpaper, forKey: "aw") + encoder.encodeString(self.name.rawValue, forKey: "name") + if let cloud = cloud { + encoder.encodeObject(cloud, forKey: "cloud") + } else { + encoder.encodeNil(forKey: "cloud") + } + if let associated = self.associated { + encoder.encodeObject(associated, forKey: "as") + } else { + encoder.encodeNil(forKey: "as") + } + encoder.encodeInt32(Int32(bitPattern: self.accentColor), forKey: "ac") + } +} + +struct LocalAccentColor : Equatable, PostboxCoding { + let name: TelegramBuiltinTheme + let color: PaletteAccentColor + let cloud: TelegramTheme? + init(name: TelegramBuiltinTheme, color: PaletteAccentColor, cloud: TelegramTheme?) { + self.name = name + self.color = color + self.cloud = cloud + } + + init(decoder: PostboxDecoder) { + self.name = TelegramBuiltinTheme(rawValue: decoder.decodeStringForKey("name", orElse: dayClassicPalette.name)) ?? .dayClassic + if let hex = decoder.decodeOptionalStringForKey("color"), let color = NSColor(hexString: hex) { + self.color = PaletteAccentColor(color) + } else if let value = decoder.decodeAnyObjectForKey("pac", decoder: { PaletteAccentColor.initWith(decoder: $0) }) as? PaletteAccentColor { + self.color = value + } else { + self.color = PaletteAccentColor(self.name.palette.basicAccent) + } + self.cloud = decoder.decodeObjectForKey("cloud") as? TelegramTheme + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.name.rawValue, forKey: "name") + encoder.encodeObjectWithEncoder(self.color, encoder: self.color.encode, forKey: "pac") + if let cloud = self.cloud { + encoder.encodeObject(cloud, forKey: "cloud") + } else { + encoder.encodeNil(forKey: "cloud") + } + } +} + +struct ThemePaletteSettings: PreferencesEntry, Equatable { + let palette: ColorPalette + let bubbled: Bool + let defaultIsDark: Bool let fontSize: CGFloat + let defaultDark: DefaultTheme + let defaultDay: DefaultTheme + let associated:[DefaultTheme] + let wallpapers: [LocalWallapper] + let accents:[LocalAccentColor] + let wallpaper: ThemeWallpaper + let cloudTheme: TelegramTheme? - init(background:NSColor, text: NSColor, grayText: NSColor, link: NSColor, blueUI:NSColor, redUI:NSColor, greenUI:NSColor, blackTransparent:NSColor, grayTransparent:NSColor, grayUI:NSColor, darkGrayText:NSColor, blueText:NSColor, blueSelect:NSColor, selectText:NSColor, blueFill:NSColor, border:NSColor, grayBackground:NSColor, grayForeground:NSColor, grayIcon:NSColor, blueIcon:NSColor, badgeMuted:NSColor, badge:NSColor, indicatorColor: NSColor, selectMessage: NSColor, dark:Bool, fontSize: CGFloat) { - self.background = background - self.text = text - self.grayText = grayText - self.link = link - self.blueUI = blueUI - self.redUI = redUI - self.greenUI = greenUI - self.blackTransparent = blackTransparent - self.grayTransparent = grayTransparent - self.grayUI = grayUI - self.darkGrayText = darkGrayText - self.blueText = blueText - self.blueSelect = blueSelect - self.selectText = selectText - self.blueFill = blueFill - self.border = border - self.grayBackground = grayBackground - self.grayForeground = grayForeground - self.grayIcon = grayIcon - self.blueIcon = blueIcon - self.badgeMuted = badgeMuted - self.badge = badge - self.indicatorColor = indicatorColor - self.selectMessage = selectMessage - self.dark = dark + init(palette: ColorPalette, + bubbled: Bool, + fontSize: CGFloat, + wallpaper: ThemeWallpaper, + defaultDark: DefaultTheme, + defaultDay: DefaultTheme, + defaultIsDark: Bool, + wallpapers: [LocalWallapper], + accents: [LocalAccentColor], + cloudTheme: TelegramTheme?, + associated: [DefaultTheme]) { + + self.palette = palette + self.bubbled = bubbled self.fontSize = fontSize + self.wallpaper = wallpaper + self.defaultDark = defaultDark + self.defaultDay = defaultDay + self.cloudTheme = cloudTheme + self.wallpapers = wallpapers + self.accents = accents + self.defaultIsDark = defaultIsDark + self.associated = associated.filter({$0.cloud?.cloud.settings != nil}) } public func isEqual(to: PreferencesEntry) -> Bool { - if let to = to as? ThemePalleteSettings { + if let to = to as? ThemePaletteSettings { return self == to } else { return false } } init(decoder: PostboxDecoder) { - self.background = parseColor(decoder, "background") - self.text = parseColor(decoder, "text") - self.grayText = parseColor(decoder, "grayText") - self.link = parseColor(decoder, "link") - self.blueUI = parseColor(decoder, "blueUI") - self.redUI = parseColor(decoder, "redUI") - self.greenUI = parseColor(decoder, "greenUI") - self.blackTransparent = parseColor(decoder, "blackTransparent") - self.grayTransparent = parseColor(decoder, "grayTransparent") - self.grayUI = parseColor(decoder, "grayUI") - self.darkGrayText = parseColor(decoder, "darkGrayText") - self.blueText = parseColor(decoder, "blueText") - self.blueSelect = parseColor(decoder, "blueSelect") - self.selectText = parseColor(decoder, "selectText") - self.blueFill = parseColor(decoder, "blueFill") - self.border = parseColor(decoder, "border") - self.grayBackground = parseColor(decoder, "grayBackground") - self.grayForeground = parseColor(decoder, "grayForeground") - self.grayIcon = parseColor(decoder, "grayIcon") - self.blueIcon = parseColor(decoder, "blueIcon") - self.badgeMuted = parseColor(decoder, "badgeMuted") - self.badge = parseColor(decoder, "badge") - self.indicatorColor = parseColor(decoder, "indicatorColor") - self.selectMessage = parseColor(decoder, "selectMessage") - self.dark = decoder.decodeBoolForKey("dark", orElse: false) - self.fontSize = CGFloat(decoder.decodeDoubleForKey("fontSize", orElse: 13.0)) + + self.wallpaper = (decoder.decodeObjectForKey("wallpaper", decoder: { ThemeWallpaper(decoder: $0) }) as? ThemeWallpaper) ?? ThemeWallpaper() + self.palette = ColorPalette.initWith(decoder: decoder) + + self.bubbled = decoder.decodeBoolForKey("bubbled", orElse: false) + self.fontSize = CGFloat(decoder.decodeDoubleForKey("fontSize", orElse: 13)) + + let defDark = DefaultTheme(local: .nightAccent, cloud: nil) + let defDay = DefaultTheme(local: .dayClassic, cloud: nil) + + self.defaultDark = decoder.decodeObjectForKey("defaultDark_1", decoder: { DefaultTheme(decoder: $0) }) as? DefaultTheme ?? defDark + self.defaultDay = decoder.decodeObjectForKey("defaultDay_1", decoder: { DefaultTheme(decoder: $0) }) as? DefaultTheme ?? defDay + + self.cloudTheme = decoder.decodeObjectForKey("cloudTheme", decoder: { TelegramTheme(decoder: $0) }) as? TelegramTheme + + self.wallpapers = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("local_wallpapers", decoder: { LocalWallapper(decoder: $0) })) ?? [] + self.accents = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("local_accents", decoder: { LocalAccentColor(decoder: $0) })) ?? [] + + self.defaultIsDark = decoder.decodeBoolForKey("defaultIsDark", orElse: self.palette.isDark) + + self.associated = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("associated", decoder: { DefaultTheme(decoder: $0) })) ?? [] + } public func encode(_ encoder: PostboxEncoder) { - for child in Mirror(reflecting: self).children { - if let label = child.label { - if let value = child.value as? NSColor { - encoder.encodeInt32(Int32(bitPattern: value.argb), forKey: label) + + self.palette.encode(encoder) + encoder.encodeBool(bubbled, forKey: "bubbled") + encoder.encodeDouble(Double(fontSize), forKey: "fontSize") + encoder.encodeObject(wallpaper, forKey: "wallpaper") + + encoder.encodeObject(defaultDay, forKey: "defaultDay_1") + encoder.encodeObject(defaultDark, forKey: "defaultDark_1") + encoder.encodeObjectArray(self.wallpapers, forKey: "local_wallpapers") + encoder.encodeObjectArray(self.accents, forKey: "local_accents") + encoder.encodeObjectArray(self.associated, forKey: "associated") + + encoder.encodeBool(self.defaultIsDark, forKey: "defaultIsDark") + + + if let cloudTheme = self.cloudTheme { + encoder.encodeObject(cloudTheme, forKey: "cloudTheme") + } else { + encoder.encodeNil(forKey: "cloudTheme") + } + } + + func withUpdatedPalette(_ palette: ColorPalette) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + func withUpdatedBubbled(_ bubbled: Bool) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + func withUpdatedFontSize(_ fontSize: CGFloat) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + func updateWallpaper(_ f:(ThemeWallpaper)->ThemeWallpaper) -> ThemePaletteSettings { + let updated = f(self.wallpaper) + + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: updated, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + func saveDefaultWallpaper() -> ThemePaletteSettings { + var wallpapers = self.wallpapers + let local = LocalWallapper(name: self.palette.parent, accentColor: self.palette.accent.argb, wallpaper: AssociatedWallpaper(cloud: self.wallpaper.associated?.cloud, wallpaper: self.wallpaper.wallpaper), associated: self.wallpaper.associated, cloud: self.cloudTheme) + + if let cloud = cloudTheme { + if let index = wallpapers.firstIndex(where: { $0.cloud?.id == cloud.id }) { + wallpapers[index] = local + } else { + wallpapers.append(local) + } + } else { + if let index = wallpapers.firstIndex(where: { $0.isEqual(to: self.palette) }) { + wallpapers[index] = local + } else { + wallpapers.append(local) + } + } + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + func installDefaultWallpaper() -> ThemePaletteSettings { + + let wallpaper:ThemeWallpaper + if let cloud = self.cloudTheme { + let first = self.wallpapers.first(where: { $0.cloud?.id == cloud.id }) + wallpaper = ThemeWallpaper(wallpaper: first?.wallpaper.wallpaper ?? self.palette.wallpaper.wallpaper, associated: first?.associated) + } else { + let first = self.wallpapers.first(where: { $0.isEqual(to: self.palette) }) + wallpaper = ThemeWallpaper(wallpaper: first?.wallpaper.wallpaper ?? self.palette.wallpaper.wallpaper, associated: nil) + } + + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + func saveDefaultAccent(color: PaletteAccentColor) -> ThemePaletteSettings { + var accents = self.accents + let local = LocalAccentColor(name: self.palette.parent, color: color, cloud: self.cloudTheme) + if let index = accents.firstIndex(where: { $0.name == palette.parent && $0.cloud?.id == self.cloudTheme?.id }) { + accents[index] = local + } else { + accents.append(local) + } + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + + func installDefaultAccent() -> ThemePaletteSettings { + let accent: LocalAccentColor? = self.accents.first(where: { $0.name == self.palette.parent && $0.cloud?.id == self.cloudTheme?.id }) + var palette: ColorPalette = self.palette.withoutAccentColor() + if let accent = accent { + palette = palette.withAccentColor(accent.color) + } + return ThemePaletteSettings(palette: palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + + func withUpdatedDefaultDay(_ defaultDay: DefaultTheme) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: wallpaper, defaultDark: self.defaultDark, defaultDay: defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + func withUpdatedDefaultDark(_ defaultDark: DefaultTheme) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: wallpaper, defaultDark: defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + func withUpdatedDefaultIsDark(_ defaultIsDark: Bool) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: self.associated) + } + func withUpdatedCloudTheme(_ cloudTheme: TelegramTheme?) -> ThemePaletteSettings { + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: defaultDark, defaultDay: defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: cloudTheme, associated: self.associated) + } + + func withSavedAssociatedTheme() -> ThemePaletteSettings { + var associated = self.associated + if let cloudTheme = self.cloudTheme { + if cloudTheme.settings != nil { + let value = DefaultTheme(local: self.palette.parent, cloud: DefaultCloudTheme(cloud: cloudTheme, palette: self.palette, wallpaper: AssociatedWallpaper(cloud: self.wallpaper.associated?.cloud, wallpaper: self.wallpaper.wallpaper))) + if let index = associated.firstIndex(where: { $0.local == self.palette.parent }) { + associated[index] = value + } else { + associated.append(value) } + } + } else { + let value = DefaultTheme(local: self.palette.parent, cloud: nil) + if let index = associated.firstIndex(where: { $0.local == self.palette.parent }) { + associated[index] = value + } else { + associated.append(value) } } - encoder.encodeBool(dark, forKey: "dark") - encoder.encodeDouble(Double(fontSize), forKey: "fontSize") + return ThemePaletteSettings(palette: self.palette, bubbled: self.bubbled, fontSize: self.fontSize, wallpaper: self.wallpaper, defaultDark: self.defaultDark, defaultDay: self.defaultDay, defaultIsDark: self.defaultIsDark, wallpapers: self.wallpapers, accents: self.accents, cloudTheme: self.cloudTheme, associated: associated) } - static var defaultTheme: ThemePalleteSettings { - return ThemePalleteSettings(whitePallete, dark: false, fontSize: 13.0) - } -} - -func ==(lhs: ThemePalleteSettings, rhs: ThemePalleteSettings) -> Bool { - return lhs.background == rhs.background && - lhs.text == rhs.text && - lhs.grayText == rhs.grayText && - lhs.link == rhs.link && - lhs.blueUI == rhs.blueUI && - lhs.redUI == rhs.redUI && - lhs.greenUI == rhs.greenUI && - lhs.blackTransparent == rhs.blackTransparent && - lhs.grayTransparent == rhs.grayTransparent && - lhs.grayUI == rhs.grayUI && - lhs.darkGrayText == rhs.darkGrayText && - lhs.blueText == rhs.blueText && - lhs.blueSelect == rhs.blueSelect && - lhs.selectText == rhs.selectText && - lhs.blueFill == rhs.blueFill && - lhs.border == rhs.border && - lhs.grayBackground == rhs.grayBackground && - lhs.grayForeground == rhs.grayForeground && - lhs.grayIcon == rhs.grayIcon && - lhs.blueIcon == rhs.blueIcon && - lhs.badgeMuted == rhs.badgeMuted && - lhs.badge == rhs.badge && - lhs.indicatorColor == rhs.indicatorColor && - lhs.selectMessage == rhs.selectMessage && - lhs.dark == rhs.dark && - lhs.fontSize == rhs.fontSize -} - -extension ThemePalleteSettings { - init(_ pallete: ColorPallete, dark: Bool, fontSize: CGFloat) { - self.init(background: pallete.background, text: pallete.text, grayText: pallete.grayText, link: pallete.link, blueUI: pallete.blueUI, redUI: pallete.redUI, greenUI: pallete.greenUI, blackTransparent: pallete.blackTransparent, grayTransparent: pallete.grayTransparent, grayUI: pallete.grayUI, darkGrayText: pallete.darkGrayText, blueText: pallete.blueText, blueSelect: pallete.blueSelect, selectText: pallete.selectText, blueFill: pallete.blueFill, border: pallete.border, grayBackground: pallete.grayBackground, grayForeground: pallete.grayForeground, grayIcon: pallete.grayIcon, blueIcon: pallete.blueIcon, badgeMuted: pallete.badgeMuted, badge: pallete.badge, indicatorColor: pallete.indicatorColor, selectMessage: pallete.selectMessage, dark: dark, fontSize: fontSize) - } -} - -func updateThemeSettings(postbox: Postbox, pallete: ColorPallete, dark: Bool) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.themeSettings, { entry in - let current = entry as? ThemePalleteSettings - return ThemePalleteSettings(pallete, dark: dark, fontSize: current?.fontSize ?? 13.0) - }) + func withUpdatedToDefault(dark: Bool, onlyLocal: Bool = false) -> ThemePaletteSettings { + if dark { + if let cloud = self.defaultDark.cloud, !onlyLocal { + return self.withUpdatedPalette(cloud.palette) + .withUpdatedCloudTheme(cloud.cloud) + .installDefaultWallpaper() + } else { + return self.withUpdatedPalette(self.defaultDark.local.palette) + .withUpdatedCloudTheme(nil) + .installDefaultAccent() + .installDefaultWallpaper() + } + } else { + if let cloud = self.defaultDay.cloud, !onlyLocal { + return self.withUpdatedPalette(cloud.palette) + .withUpdatedCloudTheme(cloud.cloud) + .installDefaultWallpaper() + } else { + return self.withUpdatedPalette(self.defaultDay.local.palette) + .withUpdatedCloudTheme(nil) + .installDefaultAccent() + .installDefaultWallpaper() + } + } + } + + static var defaultTheme: ThemePaletteSettings { + let defDark = DefaultTheme(local: .nightAccent, cloud: nil) + let defDay = DefaultTheme(local: .dayClassic, cloud: nil) + return ThemePaletteSettings(palette: dayClassicPalette, bubbled: false, fontSize: 13, wallpaper: ThemeWallpaper(), defaultDark: defDark, defaultDay: defDay, defaultIsDark: false, wallpapers: [LocalWallapper(name: .dayClassic, accentColor: dayClassicPalette.accent.argb, wallpaper: AssociatedWallpaper(cloud: nil, wallpaper: .builtin), associated: nil, cloud: nil)], accents: [], cloudTheme: nil, associated: []) } } -func updateApplicationFontSize(postbox: Postbox, fontSize: CGFloat) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.themeSettings, { entry in - let current = entry as? ThemePalleteSettings ?? ThemePalleteSettings.defaultTheme - return ThemePalleteSettings(ColorPallete(current), dark: current.dark, fontSize: fontSize) +func ==(lhs: ThemePaletteSettings, rhs: ThemePaletteSettings) -> Bool { + if lhs.palette != rhs.palette { + return false + } + if lhs.fontSize != rhs.fontSize { + return false + } + if lhs.bubbled != rhs.bubbled { + return false + } + if lhs.wallpaper != rhs.wallpaper { + return false + } + if lhs.defaultDay != rhs.defaultDay { + return false + } + if lhs.defaultDark != rhs.defaultDark { + return false + } + if lhs.cloudTheme != rhs.cloudTheme { + return false + } + if lhs.wallpapers != rhs.wallpapers { + return false + } + if lhs.defaultIsDark != rhs.defaultIsDark { + return false + } + if lhs.associated != rhs.associated { + return false + } + return true +} + + +func themeSettingsView(accountManager: AccountManager)-> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.themeSettings]) |> map { $0.entries[ApplicationSharedPreferencesKeys.themeSettings] as? ThemePaletteSettings ?? ThemePaletteSettings.defaultTheme } +} + +func themeUnmodifiedSettings(accountManager: AccountManager)-> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.themeSettings]) |> map { $0.entries[ApplicationSharedPreferencesKeys.themeSettings] as? ThemePaletteSettings ?? ThemePaletteSettings.defaultTheme } +} + + +func updateThemeInteractivetly(accountManager: AccountManager, f:@escaping (ThemePaletteSettings)->ThemePaletteSettings)-> Signal { + var bp:Int = 0 + bp += 1 + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.themeSettings, { entry in + return f(entry as? ThemePaletteSettings ?? ThemePaletteSettings.defaultTheme) }) } } diff --git a/Telegram-Mac/ThumbUtils.swift b/Telegram-Mac/ThumbUtils.swift index e428b37bac..022a404380 100644 --- a/Telegram-Mac/ThumbUtils.swift +++ b/Telegram-Mac/ThumbUtils.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import TGUIKit private let extensionImageCache = Atomic<[String: CGImage]>(value: [:]) @@ -36,57 +36,57 @@ func generateExtensionImage(colors: (UInt32, UInt32), ext:String) -> CGImage? { return generateImage(CGSize(width: 42.0, height: 42.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) + context.round(CGRect(origin: CGPoint(), size: size), flags: [.left, .bottom, .right]) + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: -size.width / 2.0 + 1.0, y: -size.height / 2.0 + 1.0) -// -// let radius: CGFloat = 2.0 -// let cornerSize: CGFloat = 10.0 - let size = CGSize(width: 42.0, height: 42.0) - - context.setFillColor(NSColor(colors.0).cgColor) - // context.beginPath() - context.fillEllipse(in: NSMakeRect(0, 0, size.width - 2, size.height - 2)) -// context.move(to: CGPoint(x: 0.0, y: radius)) -// if !radius.isZero { -// context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: radius, y: 0.0), radius: radius) -// } -// context.addLine(to: CGPoint(x: size.width - cornerSize, y: 0.0)) -// context.addLine(to: CGPoint(x: size.width - cornerSize + cornerSize / 4.0, y: cornerSize - cornerSize / 4.0)) -// context.addLine(to: CGPoint(x: size.width, y: cornerSize)) -// context.addLine(to: CGPoint(x: size.width, y: size.height - radius)) -// if !radius.isZero { -// context.addArc(tangent1End: CGPoint(x: size.width, y: size.height), tangent2End: CGPoint(x: size.width - radius, y: size.height), radius: radius) -// } -// context.addLine(to: CGPoint(x: radius, y: size.height)) -// -// if !radius.isZero { -// context.addArc(tangent1End: CGPoint(x: 0.0, y: size.height), tangent2End: CGPoint(x: 0.0, y: size.height - radius), radius: radius) -// } -// context.closePath() -// context.fillPath() -// -// context.setFillColor(NSColor(colors.1).cgColor) -// context.beginPath() -// context.move(to: CGPoint(x: size.width - cornerSize, y: 0.0)) -// context.addLine(to: CGPoint(x: size.width, y: cornerSize)) -// context.addLine(to: CGPoint(x: size.width - cornerSize + radius, y: cornerSize)) -// -// if !radius.isZero { -// context.addArc(tangent1End: CGPoint(x: size.width - cornerSize, y: cornerSize), tangent2End: CGPoint(x: size.width - cornerSize, y: cornerSize - radius), radius: radius) -// } -// - // context.closePath() - // context.fillPath() - - - - let layout = TextViewLayout(.initialize(string: ext, color: .white, font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .middle) + + let radius: CGFloat = .cornerRadius + let cornerSize: CGFloat = 10.0 + + context.setFillColor(NSColor(rgb: colors.0).cgColor) + context.beginPath() + context.move(to: CGPoint(x: 0.0, y: radius)) + if !radius.isZero { + context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: radius, y: 0.0), radius: radius) + } + context.addLine(to: CGPoint(x: size.width - cornerSize, y: 0.0)) + context.addLine(to: CGPoint(x: size.width - cornerSize + cornerSize / 4.0, y: cornerSize - cornerSize / 4.0)) + context.addLine(to: CGPoint(x: size.width, y: cornerSize)) + context.addLine(to: CGPoint(x: size.width, y: size.height - radius)) + if !radius.isZero { + context.addArc(tangent1End: CGPoint(x: size.width, y: size.height), tangent2End: CGPoint(x: size.width - radius, y: size.height), radius: radius) + } + context.addLine(to: CGPoint(x: radius, y: size.height)) + + if !radius.isZero { + context.addArc(tangent1End: CGPoint(x: 0.0, y: size.height), tangent2End: CGPoint(x: 0.0, y: size.height - 5), radius: 5) + } + context.closePath() + context.fillPath() + + context.setFillColor(NSColor(rgb: colors.1).cgColor) + context.beginPath() + context.move(to: CGPoint(x: size.width - cornerSize, y: 0.0)) + context.addLine(to: CGPoint(x: size.width, y: cornerSize)) + context.addLine(to: CGPoint(x: size.width - cornerSize + radius, y: cornerSize)) + + if !radius.isZero { + context.addArc(tangent1End: CGPoint(x: size.width - cornerSize, y: cornerSize), tangent2End: CGPoint(x: size.width - cornerSize, y: cornerSize - radius), radius: radius) + } + + context.closePath() + context.fillPath() + + + + let layout = TextViewLayout(.initialize(string: ext, color: .white, font: .medium(.short)), maximumNumberOfLines: 1, truncationType: .middle) layout.measure(width: size.width - 4) if !layout.lines.isEmpty { let line = layout.lines[0] context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - context.textPosition = NSMakePoint(floorToScreenPixels((size.width - line.frame.width)/2.0) - 1, floorToScreenPixels((size.height )/2.0) + 4) + context.textPosition = NSMakePoint(floorToScreenPixels(System.backingScale, (size.width - line.frame.width)/2.0) - 1, floorToScreenPixels(System.backingScale, (size.height )/2.0) + 4) CTLineDraw(line.line, context) } @@ -94,20 +94,20 @@ func generateExtensionImage(colors: (UInt32, UInt32), ext:String) -> CGImage? { } -func generateMediaEmptyLinkThumb(color: NSColor, host:String) -> CGImage? { - return generateImage(CGSize(width: 50, height: 50), contextGenerator: { size, context in +func generateMediaEmptyLinkThumb(color: NSColor, textColor: NSColor, host:String) -> CGImage? { + return generateImage(CGSize(width: 40, height: 40), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) let host = host.isEmpty ? "L" : host - context.round(size, 25) + context.round(size, .cornerRadius) context.setFillColor(color.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) if !host.isEmpty { - let layout = TextViewLayout(.initialize(string: host, color: .white, font: .normal(.custom(16))), maximumNumberOfLines: 1, truncationType: .middle) + let layout = TextViewLayout(.initialize(string: host, color: textColor, font: .bold(.huge)), maximumNumberOfLines: 1, truncationType: .middle) layout.measure(width: size.width - 4) let line = layout.lines[0] context.textMatrix = CGAffineTransform(scaleX: 1.0, y: 1.0) - context.textPosition = NSMakePoint(floorToScreenPixels((size.width - line.frame.width)/2.0) , floorToScreenPixels((size.height - line.frame.width)/2.0)) + context.textPosition = NSMakePoint(floorToScreenPixels(System.backingScale, (size.width - line.frame.width)/2.0) , floorToScreenPixels(System.backingScale, (size.height - line.frame.width)/2.0)) CTLineDraw(line.line, context) } }) @@ -150,8 +150,8 @@ func capIcon(for text:NSAttributedString, size:NSSize = NSMakeSize(50, 50), corn let rect = CTLineGetBoundsWithOptions(line, [.excludeTypographicLeading]) - ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - ctx.textPosition = NSMakePoint(floorToScreenPixels((size.width - rect.width)/2.0), size.height - floorToScreenPixels((size.height - rect.height)/2.0) - 6 ) + ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: 1.0) + ctx.textPosition = NSMakePoint(floorToScreenPixels(System.backingScale, (size.width - rect.width)/2.0), floorToScreenPixels(System.backingScale, (size.height - rect.height)/2.0) + 6 ) CTLineDraw(line, ctx) @@ -181,6 +181,13 @@ let playerPauseThumb = generateImage(CGSize(width: 40, height: 40), contextGener }) +let stopFetchStreamableControl = generateImage(CGSize(width: 6, height: 6), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.round(size, 2) + context.setFillColor(NSColor.white.cgColor) + context.fill(NSMakeRect(0, 0, size.width, size.height)) +}) + public struct PreviewOptions: OptionSet { public var rawValue: UInt32 @@ -196,53 +203,48 @@ public struct PreviewOptions: OptionSet { public init(_ flags: PreviewOptions) { var rawValue: UInt32 = 0 - if flags.contains(PreviewOptions.image) { - rawValue |= PreviewOptions.image.rawValue - } - - if flags.contains(PreviewOptions.video) { - rawValue |= PreviewOptions.video.rawValue - } if flags.contains(PreviewOptions.file) { rawValue |= PreviewOptions.file.rawValue } - if flags.contains(PreviewOptions.mixed) { - rawValue |= PreviewOptions.mixed.rawValue + if flags.contains(PreviewOptions.media) { + rawValue |= PreviewOptions.media.rawValue } self.rawValue = rawValue } - public static let image = PreviewOptions(rawValue: 1) - public static let video = PreviewOptions(rawValue: 2) - public static let file = PreviewOptions(rawValue: 4) - public static let mixed = PreviewOptions(rawValue: 8) + public static let media = PreviewOptions(rawValue: 1) + public static let file = PreviewOptions(rawValue: 8) } func takeSenderOptions(for urls:[URL]) -> [PreviewOptions] { var options:[PreviewOptions] = [] for url in urls { - let mime = MIMEType(url.path.nsstring.pathExtension) - let isImage = mime.hasPrefix("image") && !mime.hasSuffix("gif") - let isVideo = mime.hasPrefix("video/mp4") - if isImage && !options.contains(.image) { - options.append(.image) - } - if isVideo && !options.contains(.video) { - options.append(.video) - } + let mime = MIMEType(url.path) - if !isImage && !isVideo { - if !options.contains(.file) { - options.append(.file) + if mime.hasPrefix("image") { + if let image = NSImage(contentsOf: url) { + if image.size.width / 10 > image.size.height || image.size.height < 40 { + continue + } else if image.size.height / 10 > image.size.width || image.size.width < 40 { + continue + } + } else { + continue } } - if options.count > 1 && (options.contains(.video) || options.contains(.image)) { - if !options.contains(.mixed) { - options.append(.mixed) + let media = mime.hasPrefix("image") || mime.hasSuffix("gif") || mime.hasPrefix("video/mp4") || mime.hasPrefix("video/mov") || mime.hasSuffix("m4v") + + if media { + if !options.contains(.media) { + options.append(.media) + } + } else { + if !options.contains(.file) { + options.append(.file) } } } @@ -375,15 +377,27 @@ private func generateUploadFileAnimatedImage(_ animationValue:CGFloat, backgroun // let round: CGFloat = 1.25 var dotsColor = NSColor(backgroundColor) context.setFillColor(dotsColor.cgColor) - context.fill(CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight)) + context.addPath(CGPath(roundedRect: CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight), cornerWidth: 2, cornerHeight: 2, transform: nil)) + context.closePath() + context.fillPath() + // context.fill(CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight)) dotsColor = NSColor(foregroundColor, 0.3) context.setFillColor(dotsColor.cgColor) - context.fill(CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight)) + + context.addPath(CGPath(roundedRect: CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight), cornerWidth: 2, cornerHeight: 2, transform: nil)) + context.closePath() + context.fillPath() + +// context.fill(CGRect(x: leftPadding, y: topPadding, width: progressWidth, height: progressHeight)) progress = interpolate(from: 0.0, to: progressWidth * 2.0, value: animationValue) dotsColor = NSColor(foregroundColor, 1.0) context.setFillColor(dotsColor.cgColor) context.setBlendMode(.sourceIn) - context.fill(CGRect(x: CGFloat(leftPadding - progressWidth + progress), y: topPadding, width: progressWidth, height: progressHeight)) + // context.fill(CGRect(x: CGFloat(leftPadding - progressWidth + progress), y: topPadding, width: progressWidth, height: progressHeight)) + context.addPath(CGPath(roundedRect: CGRect(x: CGFloat(leftPadding - progressWidth + progress), y: topPadding, width: progressWidth, height: progressHeight), cornerWidth: 2, cornerHeight: 2, transform: nil)) + context.closePath() + context.fillPath() + })! diff --git a/Telegram-Mac/TouchBarEmojiItemView.swift b/Telegram-Mac/TouchBarEmojiItemView.swift new file mode 100644 index 0000000000..e02090f831 --- /dev/null +++ b/Telegram-Mac/TouchBarEmojiItemView.swift @@ -0,0 +1,36 @@ +// +// TouchBarEmojiItemView.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +@available(OSX 10.12.2, *) +class TouchBarEmojiItemView: NSScrubberItemView { + private let textView: NSTextField = NSTextField() + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.backgroundColor = .clear + textView.font = .normal(30) + } + + func update(_ emoji: String) { + textView.stringValue = emoji + } + + override func layout() { + super.layout() + textView.setFrameSize(38, 40) + textView.center() + } + + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/TouchBarEmojiPicker.swift b/Telegram-Mac/TouchBarEmojiPicker.swift new file mode 100644 index 0000000000..d1cb4786da --- /dev/null +++ b/Telegram-Mac/TouchBarEmojiPicker.swift @@ -0,0 +1,172 @@ +// +// TouchBarEmojiPicker.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +@available(OSX 10.12.2, *) +class TGScrubber : NSScrubber { + private let leftShadow = ShadowView(frame: NSMakeRect(0, 0, 20, 40)) + private let rightShadow = ShadowView(frame: NSMakeRect(0, 0, 20, 40)) + init() { + super.init(frame: NSZeroRect) + leftShadow.shadowBackground = .black + leftShadow.direction = .horizontal(false) + addSubview(leftShadow) + + rightShadow.shadowBackground = .black + rightShadow.direction = .horizontal(true) + addSubview(rightShadow) + } + + + override func layout() { + super.layout() + leftShadow.frame = NSMakeRect(0, 0, 20, frame.height) + rightShadow.frame = NSMakeRect(frame.width - rightShadow.frame.width, 0, 20, frame.height) + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +enum TouchBarEmojiPickerEntry { + case header(TextViewLayout) + case emoji(String) +} + +@available(OSX 10.12.2, *) +fileprivate extension NSTouchBarItem.Identifier { + static let emoji = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.emoji") +} + +@available(OSX 10.12.2, *) +private extension NSTouchBar.CustomizationIdentifier { + static let emojiScrubber = NSTouchBar.CustomizationIdentifier("\(Bundle.main.bundleIdentifier!).touchBar.EmojiScrubber") +} + + +@available(OSX 10.12.2, *) +private class EmojiScrubberBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate { + + private static let emojiItemViewIdentifier = "EmojiItemViewIdentifier" + private static let headerItemViewIdentifier = "HeaderItemViewIdentifier" + + private let entries: [TouchBarEmojiPickerEntry] + private let selectedEmoji: (String)->Void + init(identifier: NSTouchBarItem.Identifier, selectedEmoji:@escaping(String)->Void, entries: [TouchBarEmojiPickerEntry]) { + self.entries = entries + self.selectedEmoji = selectedEmoji + super.init(identifier: identifier) + + let scrubber = TGScrubber() + scrubber.register(TouchBarEmojiItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: EmojiScrubberBarItem.emojiItemViewIdentifier)) + scrubber.register(TouchBarScrubberHeaderItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: EmojiScrubberBarItem.headerItemViewIdentifier)) + + scrubber.mode = .free + scrubber.selectionBackgroundStyle = .roundedBackground + scrubber.delegate = self + scrubber.dataSource = self + + self.view = scrubber + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + + + func numberOfItems(for scrubber: NSScrubber) -> Int { + return entries.count + } + + + func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let itemView: NSScrubberItemView + switch entries[index] { + case let .header(title): + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: EmojiScrubberBarItem.headerItemViewIdentifier), owner: nil) as! TouchBarScrubberHeaderItemView + view.update(title) + itemView = view + case let .emoji(emoji): + let view = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: EmojiScrubberBarItem.emojiItemViewIdentifier), owner: nil) as! TouchBarEmojiItemView + view.update(emoji) + itemView = view + } + + return itemView + } + + func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize { + switch entries[itemIndex] { + case let .header(layout): + return NSMakeSize(layout.layoutSize.width + 20, 30) + case .emoji: + return NSSize(width: 42, height: 30) + } + } + + + func scrubber(_ scrubber: NSScrubber, didSelectItemAt index: Int) { + switch entries[index] { + case let .emoji(emoji): + selectedEmoji(emoji) + default: + break + } + scrubber.selectedIndex = -1 + } +} + +@available(OSX 10.12.2, *) +class TouchBarEmojiPicker: NSTouchBar, NSTouchBarDelegate { + private let selectedEmoji: (String) -> Void + private let entries: [TouchBarEmojiPickerEntry] + init(recent: [String], segments: [EmojiSegment : [String]], selectedEmoji: @escaping(String) -> Void) { + var entries: [TouchBarEmojiPickerEntry] = [] + if !recent.isEmpty { + let layout = TextViewLayout(.initialize(string: L10n.touchBarRecentlyUsed, color: .grayText, font: .normal(.header))) + layout.measure(width: .greatestFiniteMagnitude) + entries.append(.header(layout)) + entries.append(contentsOf: recent.map {.emoji($0)}) + } + + for segment in segments.sorted(by: {$0.key < $1.key}) { + let layout = TextViewLayout(.initialize(string: segment.key.localizedString, color: .grayText, font: .normal(.header))) + layout.measure(width: .greatestFiniteMagnitude) + entries.append(.header(layout)) + entries.append(contentsOf: segment.value.map {.emoji($0)}) + } + + self.entries = entries + self.selectedEmoji = selectedEmoji + super.init() + delegate = self + customizationIdentifier = .emojiScrubber + defaultItemIdentifiers = [.emoji] + customizationAllowedItemIdentifiers = [.emoji] + } + + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .emoji: + let scrubberItem: NSCustomTouchBarItem = EmojiScrubberBarItem(identifier: identifier, selectedEmoji: selectedEmoji, entries: self.entries) + return scrubberItem + default: + return nil + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/TouchBarScrubberHeaderItemView.swift b/Telegram-Mac/TouchBarScrubberHeaderItemView.swift new file mode 100644 index 0000000000..4b5a3527e8 --- /dev/null +++ b/Telegram-Mac/TouchBarScrubberHeaderItemView.swift @@ -0,0 +1,37 @@ +// +// TouchBarStickerHeaderItemView.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +@available(OSX 10.12.2, *) +class TouchBarScrubberHeaderItemView: NSScrubberItemView { + private let textView: TextView = TextView() + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.backgroundColor = .clear + } + + func update(_ layout: TextViewLayout) { + textView.update(layout) + } + + override func layout() { + super.layout() + textView.centerX() + textView.centerY(addition: -1) + } + + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/Telegram-Mac/TouchBarStickerItemView.swift b/Telegram-Mac/TouchBarStickerItemView.swift new file mode 100644 index 0000000000..dce799b873 --- /dev/null +++ b/Telegram-Mac/TouchBarStickerItemView.swift @@ -0,0 +1,101 @@ +// +// TouchBarThumbailItemView.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import TelegramCore +import SyncCore +import TGUIKit + + + +@available(OSX 10.12.2, *) +class TouchBarStickerItemView: NSScrubberItemView { + private var animatedSticker:MediaAnimatedStickerView? + private var imageView: TransformImageView? + private let fetchDisposable = MetaDisposable() + private(set) var file: TelegramMediaFile? + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + +// let gesture = NSPressGestureRecognizer(target: self, action: #selector(pressGesture)) +// gesture.minimumPressDuration = 0.5 +// self.addGestureRecognizer(gesture) + } + + var quickPreview: QuickPreviewMedia? { + if let file = file { + let reference = file.stickerReference != nil ? FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file) : FileMediaReference.standalone(media: file) + if file.isAnimatedSticker { + return .file(reference, AnimatedStickerPreviewModalView.self) + } else { + return .file(reference, StickerPreviewModalView.self) + } + } + return nil + } + + + func update(context: AccountContext, file: TelegramMediaFile, animated: Bool) { + self.file = file + if file.isAnimatedSticker, animated { + self.imageView?.removeFromSuperview() + self.imageView = nil + + if self.animatedSticker == nil { + self.animatedSticker = MediaAnimatedStickerView(frame: NSZeroRect) + addSubview(self.animatedSticker!) + } + guard let animatedSticker = self.animatedSticker else { + return + } + animatedSticker.update(with: file, size: NSMakeSize(30, 30), context: context, parent: nil, table: nil, parameters: nil, animated: false, positionFlags: nil, approximateSynchronousValue: false) + } else { + self.animatedSticker?.removeFromSuperview() + self.animatedSticker = nil + if self.imageView == nil { + self.imageView = TransformImageView() + addSubview(self.imageView!) + } + guard let imageView = self.imageView else { + return + } + let dimensions = file.dimensions?.size ?? frame.size + let imageSize = NSMakeSize(30, 30) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFitted(imageSize), boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) + + imageView.setSignal(signal: cachedMedia(media: file, arguments: arguments, scale: backingScaleFactor), clearInstantly: true) + imageView.setSignal(chatMessageSticker(postbox: context.account.postbox, file: file, small: true, scale: backingScaleFactor, fetched: true), cacheImage: { result in + cacheMedia(result, media: file, arguments: arguments, scale: System.backingScale) + }) + imageView.set(arguments: arguments) + imageView.setFrameSize(imageSize) + } + + // fetchDisposable.set(fileInteractiveFetched(account: account, fileReference: FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file)).start()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + // layer?.backgroundColor = NSColor.controlColor.cgColor + } + + deinit { + fetchDisposable.dispose() + } + + override func layout() { + super.layout() + + imageView?.center() + animatedSticker?.center() + } +} diff --git a/Telegram-Mac/TouchBarThumbailItemView.swift b/Telegram-Mac/TouchBarThumbailItemView.swift new file mode 100644 index 0000000000..394399c300 --- /dev/null +++ b/Telegram-Mac/TouchBarThumbailItemView.swift @@ -0,0 +1,62 @@ +// +// TouchBarThumbailItemView.swift +// Telegram +// +// Created by Mikhail Filimonov on 14/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKitMac +import TelegramCoreMac +import TGUIKit + +@available(OSX 10.12.2, *) +class TouchBarStickerItemView: NSScrubberItemView { + + private let imageView: TransformImageView = TransformImageView() + + private let spinner: NSProgressIndicator + required override init(frame frameRect: NSRect) { + + spinner = NSProgressIndicator() + + super.init(frame: frameRect) + + spinner.isIndeterminate = true + spinner.style = .spinning + spinner.sizeToFit() + spinner.frame = bounds.insetBy(dx: (bounds.width - spinner.frame.width)/2, dy: (bounds.height - spinner.frame.height)/2) + spinner.isHidden = true + spinner.controlSize = .small + spinner.appearance = NSAppearance(named: NSAppearance.Name.vibrantDark) + spinner.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxXMargin] + + subviews = [imageView, spinner] + } + + func update(account: Account, file: TelegramMediaFile) { + let dimensions = file.dimensions ?? frame.size + let imageSize = NSMakeSize(30, 30) + imageView.setSignal(chatMessageSticker(account: account, fileReference: FileMediaReference.stickerPack(stickerPack: file.stickerReference!, media: file), type: .thumb, scale: backingScaleFactor)) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize:dimensions.aspectFitted(imageSize), boundingSize: imageSize, intrinsicInsets: NSEdgeInsets()) + imageView.set(arguments: arguments) + imageView.setFrameSize(imageSize) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + // layer?.backgroundColor = NSColor.controlColor.cgColor + } + + override func layout() { + super.layout() + + imageView.center() + spinner.sizeToFit() + spinner.frame = bounds.insetBy(dx: (bounds.width - spinner.frame.width)/2, dy: (bounds.height - spinner.frame.height)/2) + } +} diff --git a/Telegram-Mac/TransformImageView.swift b/Telegram-Mac/TransformImageView.swift index 024f45b060..122a03d377 100644 --- a/Telegram-Mac/TransformImageView.swift +++ b/Telegram-Mac/TransformImageView.swift @@ -7,23 +7,48 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit + +private let threadPool = ThreadPool(threadCount: 1, threadPriority: 0.1) + + + open class TransformImageView: NSView { - public var imageUpdated: (() -> Void)? - public var alphaTransitionOnFirstUpdate = false + public var imageUpdated: ((Any?) -> Void)? private let disposable = MetaDisposable() - private let cachedDisposable = MetaDisposable() public var animatesAlphaOnFirstTransition:Bool = false private let argumentsPromise = Promise() + private(set) var isFullyLoaded: Bool = false + public var ignoreFullyLoad:Bool = false private var first:Bool = true public init() { super.init(frame: NSZeroRect) self.wantsLayer = true self.layer?.disableActions() self.background = .clear + layerContentsRedrawPolicy = .never + } + + open override var isFlipped: Bool { + return true + } + + var image: CGImage? { + set { + layer?.contents = newValue + imageUpdated?(newValue) + } + get { + if let any = layer?.contents { + return any as! CGImage + } else { + return nil + } + } } required public override init(frame frameRect: NSRect) { @@ -31,6 +56,7 @@ open class TransformImageView: NSView { self.wantsLayer = true self.layer?.disableActions() self.background = .clear + layerContentsRedrawPolicy = .never } required public init?(coder aDecoder: NSCoder) { @@ -39,7 +65,6 @@ open class TransformImageView: NSView { deinit { self.disposable.dispose() - cachedDisposable.dispose() } @@ -47,56 +72,109 @@ open class TransformImageView: NSView { disposable.set(nil) } - public func setSignal(signal: Signal) { - self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] image in - self?.layer?.contents = image + public func setSignal(signal: Signal, clearInstantly: Bool = true, animate: Bool = false) { + self.disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] result in + + let hasImage = self?.image != nil + + if clearInstantly { + self?.image = result.image + } else if let image = result.image { + self?.image = image + } + if !hasImage && animate { + self?.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } else if animate { + self?.layer?.animateContents() + } + self?.isFullyLoaded = result.highQuality })) } - public func setSignal(account: Account, signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, clearInstantly: Bool = true, animate:Bool = false, cacheImage:(Signal) -> Signal = {_ in return .single(Void())}) { + open override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + } + + + public func setSignal(_ signal: Signal, clearInstantly: Bool = false, animate:Bool = false, synchronousLoad: Bool = false, cacheImage:@escaping(TransformImageResult) -> Void = { _ in } ) { if clearInstantly { - self.layer?.contents = nil + self.image = nil } - let result = combineLatest(signal, argumentsPromise.get() |> distinctUntilChanged) |> deliverOn(account.graphicsThreadPool) |> mapToThrottled { transform, arguments -> Signal in - return deferred { - return Signal.single(transform(arguments)?.generateImage()) - } + + if isFullyLoaded && !ignoreFullyLoad { + disposable.set(nil) + isFullyLoaded = false + return } - cachedDisposable.set(cacheImage(result).start()) + var combine = combineLatest(signal, argumentsPromise.get() |> distinctUntilChanged) - self.disposable.set((result |> deliverOnMainQueue).start(next: {[weak self] next in - + if !synchronousLoad { + combine = combine |> deliverOn(threadPool) + } + + let result = combine |> map { data, arguments -> TransformImageResult in + autoreleasepool { + let context = data.execute(arguments, data.data) + let image = context?.generateImage() + return TransformImageResult(image, context?.isHighQuality ?? false) + } + } |> deliverOnMainQueue + + self.disposable.set(result.start(next: { [weak self] result in if let strongSelf = self { - if strongSelf.layer?.contents == nil && strongSelf.animatesAlphaOnFirstTransition { + if strongSelf.image == nil && strongSelf.animatesAlphaOnFirstTransition { strongSelf.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) } - - self?.layer?.contents = next - + self?.image = result.image if !strongSelf.first && animate { self?.layer?.animateContents() } strongSelf.first = false + cacheImage(result) } - })) + + } + + + public var hasImage: Bool { + return image != nil } public func set(arguments:TransformImageArguments) ->Void { argumentsPromise.set(.single(arguments)) } + override open func copy() -> Any { let view = NSView() view.wantsLayer = true view.background = .clear - view.layer?.frame = NSMakeRect(0, visibleRect.minY == 0 ? 0 : visibleRect.height - frame.height, frame.width, frame.height) - view.layer?.contents = self.layer?.contents - view.layer?.masksToBounds = true + view.layer?.contents = self.image view.frame = self.visibleRect + view.layer?.masksToBounds = true + + + if bounds != visibleRect { + if let image = self.layer?.contents { + view.layer?.contents = generateImage(bounds.size, contextGenerator: { size, ctx in + ctx.clear(bounds) + ctx.setFillColor(.clear) + ctx.fill(bounds) + if visibleRect.minY != 0 { + ctx.clip(to: NSMakeRect(0, 0, bounds.width, bounds.height - ( bounds.height - visibleRect.height))) + } else { + ctx.clip(to: NSMakeRect(0, (bounds.height - visibleRect.height), bounds.width, bounds.height - ( bounds.height - visibleRect.height))) + } + ctx.draw(image as! CGImage, in: bounds) + }, opaque: false) + } + } + view.layer?.shouldRasterize = true view.layer?.rasterizationScale = backingScaleFactor + return view } diff --git a/Telegram-Mac/TransformOutgoingMessageMedia.swift b/Telegram-Mac/TransformOutgoingMessageMedia.swift index a3d1faa318..ad5010ecc6 100644 --- a/Telegram-Mac/TransformOutgoingMessageMedia.swift +++ b/Telegram-Mac/TransformOutgoingMessageMedia.swift @@ -7,16 +7,18 @@ // import Foundation -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit import TGUIKit +import SyncCore -public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, media: Media, opportunistic: Bool) -> Signal { - switch media { +public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, reference: AnyMediaReference, opportunistic: Bool) -> Signal { + switch reference.media { case let file as TelegramMediaFile: let signal = Signal<(MediaResourceData, String?), NoError> { subscriber in - let fetch = postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)).start() + let fetch = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: reference.resourceReference(file.resource), statsCategory: .file).start() //postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)).start() let dataSignal = resourceType(mimeType: file.mimeType) |> mapToSignal { ext in return postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true)) |> map { result in return (result, ext) @@ -43,25 +45,37 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me } return result - |> mapToSignal { data -> Signal in + |> mapToSignal { data -> Signal in if data.0.complete { return Signal { subscriber in + let resource = (file.resource as? LocalFileReferenceMediaResource) + var size = resource?.size + + if resource == nil { + size = Int32(data.0.size) + } + var thumbImage:CGImage? = nil let thumbedFile:String - if file.isVideo && file.isAnimated { - thumbedFile = data.0.path + ".mp4" + if let resource = resource { + thumbedFile = resource.localFilePath } else { - thumbedFile = data.0.path.appending(".\(file.fileName?.nsstring.pathExtension ?? data.1 ?? "txt")") + if file.isVideo && file.isAnimated { + thumbedFile = data.0.path + ".mp4" + } else { + thumbedFile = data.0.path.appending(".\(file.fileName?.nsstring.pathExtension ?? data.1 ?? "mp4")") + } } + try? FileManager.default.linkItem(atPath: data.0.path, toPath: thumbedFile) if file.mimeType.hasPrefix("image/") { if let thumbData = try? Data(contentsOf: URL(fileURLWithPath: thumbedFile)) { let options = NSMutableDictionary() - options.setValue(90 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) + options.setValue(320 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) if let imageSource = CGImageSourceCreateWithData(thumbData as CFData, nil) { @@ -73,28 +87,42 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me } else if file.mimeType.hasPrefix("video/") { let asset = AVAsset(url: URL(fileURLWithPath: thumbedFile)) let imageGenerator = AVAssetImageGenerator(asset: asset) - imageGenerator.maximumSize = CGSize(width: 90, height: 90) + imageGenerator.maximumSize = CGSize(width: 320, height: 320) imageGenerator.appliesPreferredTrackTransform = true thumbImage = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) } + if thumbedFile != resource?.localFilePath { + try? FileManager.default.removeItem(atPath: thumbedFile) + } - if let thumbImage = thumbImage { - let imageRep = NSBitmapImageRep(cgImage: thumbImage) - let options: [NSBitmapImageRep.PropertyKey: Any] = [.compressionFactor: 0.6] - let compressedData: Data? = imageRep.representation(using: .jpeg, properties: options) + if let image = thumbImage { - if let compressedData = compressedData { - let thumbnailResource = LocalFileMediaResource(fileId: arc4random64()) - postbox.mediaBox.storeResourceData(thumbnailResource.id, data: compressedData) - subscriber.putNext(file.withUpdatedSize(data.0.size).withUpdatedPreviewRepresentations([TelegramMediaImageRepresentation(dimensions: thumbImage.size, resource: thumbnailResource)])) + let options = NSMutableDictionary() + options.setValue(320 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + + let colorQuality: Float = 0.2 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + + let mutableData: CFMutableData = NSMutableData() as CFMutableData + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, options) { + CGImageDestinationSetProperties(colorDestination, nil) - return EmptyDisposable + CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let isSecretRelated = (file.previewRepresentations.first as? LocalFileMediaResource)?.isSecretRelated ?? false + let thumbnailResource = LocalFileMediaResource(fileId: arc4random64(), isSecretRelated: isSecretRelated) + postbox.mediaBox.storeResourceData(thumbnailResource.id, data: mutableData as Data) + subscriber.putNext(AnyMediaReference.standalone(media: file.withUpdatedSize(Int(size ?? 0)).withUpdatedPreviewRepresentations([TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: thumbnailResource)]))) + + return EmptyDisposable + } } - } + } - subscriber.putNext(file.withUpdatedSize(data.0.size)) + subscriber.putNext(AnyMediaReference.standalone(media: file.withUpdatedSize(Int(size ?? 0)))) subscriber.putCompletion() diff --git a/Telegram-Mac/Tuple.swift b/Telegram-Mac/Tuple.swift new file mode 100644 index 0000000000..4c2c48f40d --- /dev/null +++ b/Telegram-Mac/Tuple.swift @@ -0,0 +1,43 @@ +import Foundation + +public final class Tuple1 { + public let _0: T0 + + public init(_ _0: T0) { + self._0 = _0 + } +} + +public final class Tuple2 { + public let _0: T0 + public let _1: T1 + + public init(_ _0: T0, _ _1: T1) { + self._0 = _0 + self._1 = _1 + } +} + +public final class Tuple3 { + public let _0: T0 + public let _1: T1 + public let _2: T2 + + public init(_ _0: T0, _ _1: T1, _ _2: T2) { + self._0 = _0 + self._1 = _1 + self._2 = _2 + } +} + +public func Tuple(_ _0: T0) -> Tuple1 { + return Tuple1(_0) +} + +public func Tuple(_ _0: T0, _ _1: T1) -> Tuple2 { + return Tuple2(_0, _1) +} + +public func Tuple(_ _0: T0, _ _1: T1, _ _2: T2) -> Tuple3 { + return Tuple3(_0, _1, _2) +} diff --git a/Telegram-Mac/TwoStepVerification.swift b/Telegram-Mac/TwoStepVerification.swift index bf472caa29..8141415397 100644 --- a/Telegram-Mac/TwoStepVerification.swift +++ b/Telegram-Mac/TwoStepVerification.swift @@ -7,43 +7,95 @@ // import Cocoa -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac -import MtProtoKitMac - - - -func apiInputPeer(_ peer: Peer) -> Api.InputPeer? { - - switch peer { - case let user as TelegramUser where user.accessHash != nil: - return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash!) - case let group as TelegramGroup: - return Api.InputPeer.inputPeerChat(chatId: group.id.id) - case let channel as TelegramChannel: - if let accessHash = channel.accessHash { - return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash) - } else { - return nil - } - default: - return nil - } -} - -func apiInputChannel(_ peer: Peer) -> Api.InputChannel? { - if let channel = peer as? TelegramChannel, let accessHash = channel.accessHash { - return Api.InputChannel.inputChannel(channelId: channel.id.id, accessHash: accessHash) - } else { - return nil - } -} - -func apiInputUser(_ peer: Peer) -> Api.InputUser? { - if let user = peer as? TelegramUser, let accessHash = user.accessHash { - return Api.InputUser.inputUser(userId: user.id.id, accessHash: accessHash) - } else { - return nil - } -} +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + + + +//func apiInputPeer(_ peer: Peer) -> Api.InputPeer? { +// +// switch peer { +// case let user as TelegramUser where user.accessHash != nil: +// return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash!) +// case let group as TelegramGroup: +// return Api.InputPeer.inputPeerChat(chatId: group.id.id) +// case let channel as TelegramChannel: +// if let accessHash = channel.accessHash { +// return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash) +// } else { +// return nil +// } +// default: +// return nil +// } +//} +// +//func apiInputChannel(_ peer: Peer) -> Api.InputChannel? { +// if let channel = peer as? TelegramChannel, let accessHash = channel.accessHash { +// return Api.InputChannel.inputChannel(channelId: channel.id.id, accessHash: accessHash) +// } else { +// return nil +// } +//} +// +//func apiInputUser(_ peer: Peer) -> Api.InputUser? { +// if let user = peer as? TelegramUser, let accessHash = user.accessHash { +// return Api.InputUser.inputUser(userId: user.id.id, accessHash: accessHash) +// } else { +// return nil +// } +//} +// +// +// +//public func reportMessages(postbox: Postbox, network: Network, peerId: PeerId, messageIds: [MessageId], reason:ReportReason) -> Signal { +// return postbox.modify{ transaction -> Void in +// if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { +// // return Api.functions.messages. +// } +// } +//} + +//public func getCountryCode(network: Network)->Signal { +// return network.request(Api.functions.help.getNearestDc()) |> retryRequest |> map { value in +// switch value { +// case let .nearestDc(country, _, _): +// return country +// } +// } +//} + + + +//public func dropSecureId(network: Network, currentPassword: String) -> Signal { +// return twoStepAuthData(network) +// |> mapError { _ -> AuthorizationPasswordVerificationError in +// return .generic +// } +// |> mapToSignal { authData -> Signal in +// if let currentSalt = authData.currentSalt { +// var data = Data() +// data.append(currentSalt) +// data.append(currentPassword.data(using: .utf8, allowLossyConversion: true)!) +// data.append(currentSalt) +// currentPasswordHash = Buffer(data: sha256Digest(data)) +// } else { +// currentPasswordHash = Buffer(data: Data()) +// } +// +// let flags: Int32 = 1 << 1 +// +// let settings = network.request(Api.functions.account.getPasswordSettings(currentPasswordHash: currentPasswordHash), automaticFloodWait: false) |> mapError {_ in return AuthorizationPasswordVerificationError.generic} +// +// +// return settings |> mapToSignal { value -> Signal in +// switch value { +// case let .passwordSettings(email, secureSalt, _, _): +// return network.request(Api.functions.account.updatePasswordSettings(currentPasswordHash: currentPasswordHash, newSettings: Api.account.PasswordInputSettings.passwordInputSettings(flags: flags, newSalt: secureSalt, newPasswordHash: currentPasswordHash, hint: nil, email: email, newSecureSalt: secureSalt, newSecureSecret: nil, newSecureSecretId: nil)), automaticFloodWait: false) |> map {_ in} |> mapError {_ in return AuthorizationPasswordVerificationError.generic} +// } +// } +// } +//} + diff --git a/Telegram-Mac/TwoStepVerificationPasswordEntryController.swift b/Telegram-Mac/TwoStepVerificationPasswordEntryController.swift deleted file mode 100644 index 0df240e821..0000000000 --- a/Telegram-Mac/TwoStepVerificationPasswordEntryController.swift +++ /dev/null @@ -1,307 +0,0 @@ -// -// TwoStepVerificationPasswordEntryController.swift -// Telegram -// -// Created by keepcoder on 17/10/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import SwiftSignalKitMac -import TelegramCoreMac -import TGUIKit - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:TwoStepVerificationPasswordEntryControllerArguments) -> TableUpdateTransition { - - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -class TwoStepVerificationPasswordEntryController: TableViewController { - - fileprivate let mode: TwoStepVerificationPasswordEntryMode - fileprivate let result: Promise - fileprivate var nextAction:(()->Void)? - fileprivate let disposable = MetaDisposable() - init(account: Account, mode: TwoStepVerificationPasswordEntryMode, result: Promise) { - self.mode = mode - self.result = result - super.init(account) - } - - override func viewDidLoad() { - super.viewDidLoad() - - let account = self.account - let mode = self.mode - let result = self.result - - let initialStage: PasswordEntryStage - switch mode { - case .setup, .change: - initialStage = .entry(text: "") - case .setupEmail: - initialStage = .email(password: "", hint: "", text: "") - } - let initialState = TwoStepVerificationPasswordEntryControllerState(stage: initialStage, updating: false) - - let statePromise = ValuePromise(initialState, ignoreRepeated: true) - let stateValue = Atomic(value: initialState) - let updateState: ((TwoStepVerificationPasswordEntryControllerState) -> TwoStepVerificationPasswordEntryControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - - let actionsDisposable = DisposableSet() - - let updatePasswordDisposable = MetaDisposable() - actionsDisposable.add(updatePasswordDisposable) - - func checkPassword(_ skipEmail:Bool = false) { - var passwordHintEmail: (String, String, String)? - var invalidReentry = false - updateState { state in - if state.updating { - return state - } else { - switch state.stage { - case let .entry(text): - if text.isEmpty { - return state - } else { - return state.withUpdatedStage(.reentry(first: text, text: "")) - } - case let .reentry(first, text): - if text.isEmpty { - return state - } else if text != first { - invalidReentry = true - return state.withUpdatedStage(.entry(text: "")) - } else { - return state.withUpdatedStage(.hint(password: text, text: "")) - } - case let .hint(password, text): - switch mode { - case .setup: - return state.withUpdatedStage(.email(password: password, hint: text, text: "")) - case .change: - passwordHintEmail = (password, text, "") - return state.withUpdatedUpdating(true) - case .setupEmail: - preconditionFailure() - } - case let .email(password, hint, text): - passwordHintEmail = (password, hint, text) - return state.withUpdatedUpdating(true) - } - } - } - if let (password, hint, email) = passwordHintEmail { - switch mode { - case .setup, .change: - var currentPassword: String? - if case let .change(current) = mode { - currentPassword = current - } - updatePasswordDisposable.set((updateTwoStepVerificationPassword(account: account, currentPassword: currentPassword, updatedPassword: .password(password: password, hint: hint, email: skipEmail ? "" : email)) |> deliverOnMainQueue).start(next: { update in - updateState { - $0.withUpdatedUpdating(false) - } - switch update { - case let .password(password, pendingEmailPattern): - result.set(.single(TwoStepVerificationPasswordEntryResult(password: password, pendingEmailPattern: pendingEmailPattern))) - case .none: - break - } - }, error: { error in - updateState { - $0.withUpdatedUpdating(false) - } - let alertText: String - switch error { - case .generic: - alertText = tr(.twoStepAuthErrorGeneric) - case .invalidEmail: - alertText = tr(.twoStepAuthErrorInvalidEmail) - } - alert(for: mainWindow, header: appName, info: alertText) - })) - case let .setupEmail(password): - updatePasswordDisposable.set((updateTwoStepVerificationEmail(account: account, currentPassword: password, updatedEmail: email) |> deliverOnMainQueue).start(next: { update in - updateState { - $0.withUpdatedUpdating(false) - } - switch update { - case let .password(password, pendingEmailPattern): - result.set(.single(TwoStepVerificationPasswordEntryResult(password: password, pendingEmailPattern: pendingEmailPattern))) - case .none: - break - } - }, error: { error in - updateState { - $0.withUpdatedUpdating(false) - } - let alertText: String - switch error { - case .generic: - alertText = tr(.twoStepAuthErrorGeneric) - case .invalidEmail: - alertText = tr(.twoStepAuthErrorInvalidEmail) - } - alert(for: mainWindow, header: appName, info: alertText) - })) - } - } else if invalidReentry { - alert(for: mainWindow, header: appName, info: tr(.twoStepAuthErrorPasswordsDontMatch)) - } - } - - let arguments = TwoStepVerificationPasswordEntryControllerArguments(updateEntryText: { updatedText in - updateState { - $0.withUpdatedStage($0.stage.updateCurrentText(updatedText)) - } - }, next: { [weak self] in - - if (self?.rightBarView as? TextButtonBarView)?.button.isEnabled == false { - NSSound.beep() - return - } - - let value = stateValue.modify({$0}) - - if !value.updating { - switch value.stage { - case let .email(password: _, hint: _, text): - switch mode { - case .setupEmail: - checkPassword() - return - default: - break - } - if text.isEmpty { - confirm(for: mainWindow, with: appName, and: tr(.twoStepAuthEmailSkipAlert), successHandler: { _ in - checkPassword() - }) - } else { - checkPassword() - } - return - default: - break - } - } - - checkPassword() - }, skipEmail: { - confirm(for: mainWindow, with: appName, and: tr(.twoStepAuthEmailSkipAlert), successHandler: { _ in - checkPassword(true) - }) - }) - - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - - let signal = combineLatest(appearanceSignal, statePromise.get()) |> deliverOnMainQueue - |> map { appearance, state -> (TableUpdateTransition, Bool, String) in - - var nextEnabled = true - var title: String = "Password" - - switch state.stage { - case .entry: - title = tr(.twoStepAuthSetupPasswordTitle) - case .reentry: - title = tr(.twoStepAuthSetupPasswordTitle) - case .hint: - title = tr(.twoStepAuthSetupHintTitle) - case .email: - title = tr(.twoStepAuthSetupEmailTitle) - } - - if state.updating { - nextEnabled = false - } else { - switch state.stage { - case let .entry(text): - if text.isEmpty { - nextEnabled = false - } - case let.reentry(_, text): - if text.isEmpty { - nextEnabled = false - } - case .hint: - break - case .email(let text): - switch mode { - case .setupEmail: - nextEnabled = !text.text.isEmpty - default: - nextEnabled = true - } - } - - } - - let entries = twoStepVerificationPasswordEntryControllerEntries(state: state, mode: mode).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - - return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), nextEnabled, title) - } |> afterDisposed { - actionsDisposable.dispose() - } |> deliverOnMainQueue - - nextAction = arguments.next - - disposable.set(signal.start(next: { [weak self] transition, nextEnabled, title in - self?.genericView.merge(with: transition) - self?.readyOnce() - self?.setCenterTitle(title) - (self?.rightBarView as? TextButtonBarView)?.button.isEnabled = nextEnabled - })) - } - - deinit { - disposable.dispose() - } - - override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.composeNext)) - - button.button.set(handler: { [weak self] _ in - self?.nextAction?() - }, for: .Click) - - return button - } - - override func returnKeyAction() -> KeyHandlerResult { - nextAction?() - return .invoked - } - - override func firstResponder() -> NSResponder? { - if genericView.count > 1 { - if !(window?.firstResponder is NSTextView) { - return (genericView.viewNecessary(at: 1) as? GeneralInputRowView)?.firstResponder - } - } - return window?.firstResponder - } - - override func backKeyAction() -> KeyHandlerResult { - return .invokeNext - } - - override func becomeFirstResponder() -> Bool? { - return true - } - - override var removeAfterDisapper: Bool { - return true - } - -} diff --git a/Telegram-Mac/TwoStepVerificationResetController.swift b/Telegram-Mac/TwoStepVerificationResetController.swift deleted file mode 100644 index 35d6a239e1..0000000000 --- a/Telegram-Mac/TwoStepVerificationResetController.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// TwoStepVerificationResetController.swift -// Telegram -// -// Created by keepcoder on 18/10/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import TGUIKit - - - - -private func twoStepVerificationResetControllerEntries(state: TwoStepVerificationResetControllerState, emailPattern: String) -> [TwoStepVerificationResetEntry] { - var entries: [TwoStepVerificationResetEntry] = [] - - var sectionId:Int32 = 0 - entries.append(.section(sectionId)) - sectionId += 1 - - entries.append(.codeEntry(sectionId : sectionId, state.codeText)) - entries.append(.codeInfo(sectionId : sectionId, tr(.twoStepAuthRecoveryCodeHelp) + "\n\n[\(tr(.twoStepAuthRecoveryEmailUnavailable(emailPattern)))]()")) - return entries -} - - -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:TwoStepVerificationResetControllerArguments) -> TableUpdateTransition { - - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) - } - - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -class TwoStepVerificationResetController : TableViewController { - fileprivate let emailPattern: String - fileprivate let result: Promise - fileprivate let disposable = MetaDisposable() - fileprivate var nextAction:(()->Void)? - init(account: Account, emailPattern: String, result: Promise) { - self.emailPattern = emailPattern - self.result = result - super.init(account) - } - - override var defaultBarTitle: String { - return tr(.twoStepAuthRecoveryTitle) - } - - deinit { - disposable.dispose() - } - - override func viewDidLoad() { - let account = self.account - let result = self.result - let emailPattern = self.emailPattern - let initialSize = self.atomicSize - let initialState = TwoStepVerificationResetControllerState(codeText: "", checking: false) - - let statePromise = ValuePromise(initialState, ignoreRepeated: true) - let stateValue = Atomic(value: initialState) - let updateState: ((TwoStepVerificationResetControllerState) -> TwoStepVerificationResetControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - let actionsDisposable = DisposableSet() - - let resetPasswordDisposable = MetaDisposable() - actionsDisposable.add(resetPasswordDisposable) - - let checkCode: () -> Void = { [weak self] in - - if (self?.rightBarView as? TextButtonBarView)?.button.isEnabled == false { - NSSound.beep() - return - } - - var code: String? - updateState { state in - if state.checking || state.codeText.isEmpty { - return state - } else { - code = state.codeText - return state.withUpdatedChecking(true) - } - } - if let code = code { - resetPasswordDisposable.set((recoverTwoStepVerificationPassword(account: account, code: code) |> deliverOnMainQueue).start(error: { error in - updateState { - return $0.withUpdatedChecking(false) - } - let alertText: String - switch error { - case .generic: - alertText = tr(.twoStepAuthGenericError) - case .invalidCode: - alertText = tr(.twoStepAuthRecoveryCodeInvalid) - case .codeExpired: - alertText = tr(.twoStepAuthRecoveryCodeExpired) - case .limitExceeded: - alertText = tr(.twoStepAuthFloodError) - } - alert(for: mainWindow, header: appName, info: alertText) - - }, completed: { - updateState { - return $0.withUpdatedChecking(false) - } - result.set(.single(true)) - })) - } - } - - let arguments = TwoStepVerificationResetControllerArguments(updateEntryText: { updatedText in - updateState { - $0.withUpdatedCodeText(updatedText) - } - }, next: { - checkCode() - }, openEmailInaccessible: { - alert(for: mainWindow, info: tr(.twoStepAuthErrorHaventEmail)) - }) - - - self.nextAction = checkCode - - let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - - let signal = combineLatest(appearanceSignal, statePromise.get()) |> deliverOnMainQueue - |> map { appearance, state -> (TableUpdateTransition, Bool) in - - var nextEnabled = true - - - if state.checking { - nextEnabled = false - } else { - if state.codeText.isEmpty { - nextEnabled = false - } - } - let entries = twoStepVerificationResetControllerEntries(state: state, emailPattern: emailPattern).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - - return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), nextEnabled) - } |> afterDisposed { - actionsDisposable.dispose() - } - - disposable.set(signal.start(next: { [weak self] transition, enabled in - self?.genericView.merge(with: transition) - self?.readyOnce() - (self?.rightBarView as? TextButtonBarView)?.button.isEnabled = enabled - })) - } - - override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.composeNext)) - - button.button.set(handler: { [weak self] _ in - self?.nextAction?() - }, for: .Click) - - return button - } - - override func returnKeyAction() -> KeyHandlerResult { - nextAction?() - return .invoked - } - - override func firstResponder() -> NSResponder? { - if genericView.count > 1 { - return (genericView.viewNecessary(at: 1) as? GeneralInputRowView)?.firstResponder - } - return nil - } - - override func backKeyAction() -> KeyHandlerResult { - return .invokeNext - } - - override func becomeFirstResponder() -> Bool? { - return true - } - - override var removeAfterDisapper: Bool { - return true - } - -} - - - diff --git a/Telegram-Mac/TwoStepVerificationUnlockController.swift b/Telegram-Mac/TwoStepVerificationUnlockController.swift index cb7eecea70..7c1db00a74 100644 --- a/Telegram-Mac/TwoStepVerificationUnlockController.swift +++ b/Telegram-Mac/TwoStepVerificationUnlockController.swift @@ -8,485 +8,1395 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox -private func twoStepVerificationUnlockSettingsControllerEntries(state: TwoStepVerificationUnlockSettingsControllerState,data: TwoStepVerificationUnlockSettingsControllerData) -> [TwoStepVerificationUnlockSettingsEntry] { - var entries: [TwoStepVerificationUnlockSettingsEntry] = [] + + + +private struct TwoStepVerificationUnlockSettingsControllerState: Equatable { + let passwordText: String + let checking: Bool + let emailCode: String + let errors:[InputDataIdentifier : InputDataValueError] + let data: TwoStepVerificationUnlockSettingsControllerData + + init(passwordText: String, checking: Bool, emailCode: String, errors: [InputDataIdentifier : InputDataValueError], data: TwoStepVerificationUnlockSettingsControllerData) { + self.passwordText = passwordText + self.checking = checking + self.emailCode = emailCode + self.errors = errors + self.data = data + } + + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> TwoStepVerificationUnlockSettingsControllerState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return TwoStepVerificationUnlockSettingsControllerState(passwordText: self.passwordText, checking: self.checking, emailCode: self.emailCode, errors: errors, data: self.data) + } + + func withUpdatedPasswordText(_ passwordText: String) -> TwoStepVerificationUnlockSettingsControllerState { + return TwoStepVerificationUnlockSettingsControllerState(passwordText: passwordText, checking: self.checking, emailCode: self.emailCode, errors: self.errors, data: self.data) + } + func withUpdatedEmailCode(_ emailCode: String) -> TwoStepVerificationUnlockSettingsControllerState { + return TwoStepVerificationUnlockSettingsControllerState(passwordText: self.passwordText, checking: self.checking, emailCode: emailCode, errors: self.errors, data: self.data) + } + + func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationUnlockSettingsControllerState { + return TwoStepVerificationUnlockSettingsControllerState(passwordText: self.passwordText, checking: checking, emailCode: self.emailCode, errors: self.errors, data: self.data) + } + + func withUpdatedControllerData(_ data: TwoStepVerificationUnlockSettingsControllerData) -> TwoStepVerificationUnlockSettingsControllerState { + return TwoStepVerificationUnlockSettingsControllerState(passwordText: self.passwordText, checking: self.checking, emailCode: self.emailCode, errors: self.errors, data: data) + } +} + + +enum TwoStepVerificationUnlockSettingsControllerMode { + case access(TwoStepVeriticationAccessConfiguration?) + case manage(password: String, email: String, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool) +} + +private enum TwoStepVerificationUnlockSettingsControllerData : Equatable { + case access(configuration: TwoStepVeriticationAccessConfiguration?) + case manage(password: String, emailSet: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool) +} + + + +struct PendingEmailState : Equatable { + let password: String? + let email: TwoStepVerificationPendingEmail +} + + +private final class TwoStepVerificationPasswordEntryControllerArguments { + let updateEntryText: (String) -> Void + let next: () -> Void + let skipEmail:() ->Void + init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, skipEmail:@escaping()->Void) { + self.updateEntryText = updateEntryText + self.next = next + self.skipEmail = skipEmail + } +} + + + +enum PasswordEntryStage: Equatable { + case entry(text: String) + case reentry(first: String, text: String) + case hint(password: String, text: String) + case email(password: String, hint: String, text: String, change: Bool) + case code(text: String, codeLength: Int32?, pattern: String) + + func updateCurrentText(_ text: String) -> PasswordEntryStage { + switch self { + case .entry: + return .entry(text: text) + case let .reentry(first, _): + return .reentry(first: first, text: text) + case let .hint(password, _): + return .hint(password: password, text: text) + case let .email(password, hint, _, change): + return .email(password: password, hint: hint, text: text, change: change) + case let .code(_, codeLength, pattern): + return .code(text: text, codeLength: codeLength, pattern: pattern) + } + } + +} + +private struct TwoStepVerificationPasswordEntryControllerState: Equatable { + let stage: PasswordEntryStage + let updating: Bool + let errors: [InputDataIdentifier : InputDataValueError] + init(stage: PasswordEntryStage, updating: Bool, errors: [InputDataIdentifier : InputDataValueError]) { + self.stage = stage + self.updating = updating + self.errors = errors + } + + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> TwoStepVerificationPasswordEntryControllerState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return TwoStepVerificationPasswordEntryControllerState(stage: self.stage, updating: self.updating, errors: errors) + } + + func withUpdatedStage(_ stage: PasswordEntryStage) -> TwoStepVerificationPasswordEntryControllerState { + return TwoStepVerificationPasswordEntryControllerState(stage: stage, updating: self.updating, errors: self.errors) + } + + func withUpdatedUpdating(_ updating: Bool) -> TwoStepVerificationPasswordEntryControllerState { + return TwoStepVerificationPasswordEntryControllerState(stage: self.stage, updating: updating, errors: self.errors) + } +} + + +enum TwoStepVerificationPasswordEntryMode { + case setup + case change(current: String) + case setupEmail(password: String, change: Bool) + case enterCode(codeLength: Int32?, pattern: String) +} + + +enum TwoStepVeriticationAccessConfiguration : Equatable { + case notSet(pendingEmail: PendingEmailState?) + case set(hint: String, hasRecoveryEmail: Bool, hasSecureValues: Bool) + + init(configuration: TwoStepVerificationConfiguration, password: String?) { + switch configuration { + case let .notSet(pendingEmail): + self = .notSet(pendingEmail: pendingEmail.flatMap({ PendingEmailState(password: password, email: $0) })) + case let .set(hint, hasRecoveryEmail, _, hasSecureValues): + self = .set(hint: hint, hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues) + } + } +} + +enum SetupTwoStepVerificationStateUpdate { + case noPassword + case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?) + case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool) + case emailSet +} + + + + +final class TwoStepVerificationResetControllerArguments { + let updateEntryText: (String) -> Void + let next: () -> Void + let openEmailInaccessible: () -> Void + + init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, openEmailInaccessible: @escaping () -> Void) { + self.updateEntryText = updateEntryText + self.next = next + self.openEmailInaccessible = openEmailInaccessible + } +} + + +struct TwoStepVerificationResetControllerState: Equatable { + let codeText: String + let checking: Bool + + init(codeText: String, checking: Bool) { + self.codeText = codeText + self.checking = checking + } + + + func withUpdatedCodeText(_ codeText: String) -> TwoStepVerificationResetControllerState { + return TwoStepVerificationResetControllerState(codeText: codeText, checking: self.checking) + } + + func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationResetControllerState { + return TwoStepVerificationResetControllerState(codeText: self.codeText, checking: checking) + } +} + + + +private let _id_input_enter_pwd = InputDataIdentifier("input_password") +private let _id_change_pwd = InputDataIdentifier("change_pwd") +private let _id_remove_pwd = InputDataIdentifier("remove_pwd") +private let _id_setup_email = InputDataIdentifier("setup_email") +private let _id_enter_email_code = InputDataIdentifier("enter_email_code") +private let _id_set_password = InputDataIdentifier("set_password") +private let _id_input_enter_email_code = InputDataIdentifier("_id_input_enter_email_code") + +private func twoStepVerificationUnlockSettingsControllerEntries(state: TwoStepVerificationUnlockSettingsControllerState, forgotPassword:@escaping()->Void, abort:@escaping()-> Void) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] var sectionId:Int32 = 0 - entries.append(.section(sectionId)) + + entries.append(.sectionId(sectionId, type: .normal)) sectionId += 1 - switch data { + var index: Int32 = 0 + + + switch state.data { case let .access(configuration): if let configuration = configuration { switch configuration { - case let .notSet(pendingEmailPattern): - if pendingEmailPattern.isEmpty { - entries.append(.passwordSetup(sectionId: sectionId, tr(.twoStepAuthSetPassword))) - entries.append(.passwordSetupInfo(sectionId: sectionId, tr(.twoStepAuthSetPasswordHelp))) + case let .notSet(pendingEmail): + if let pendingEmail = pendingEmail { + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.emailCode), error: state.errors[_id_input_enter_email_code], identifier: _id_input_enter_email_code, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: pendingEmail.email.codeLength ?? 255)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .markdown(L10n.twoStepAuthConfirmationTextNew + "\n\n\(pendingEmail.email.pattern)\n\n[" + L10n.twoStepAuthConfirmationAbort + "]()", linkHandler: { url in + abort() + }), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + index += 1 + + } else { - entries.append(.pendingEmailInfo(sectionId: sectionId, tr(.twoStepAuthConfirmationText) + "\n\n\(pendingEmailPattern)\n\n[" + tr(.twoStepAuthConfirmationAbort) + "]()")) + entries.append(.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_set_password, data: InputDataGeneralData(name: L10n.twoStepAuthSetPassword, color: theme.colors.text, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthSetPasswordHelp), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 } case let .set(hint, _, _): - entries.append(.passwordEntry(sectionId: sectionId, tr(.twoStepAuthEnterPasswordPassword), state.passwordText)) + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.passwordText), error: state.errors[_id_input_enter_pwd], identifier: _id_input_enter_pwd, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthEnterPasswordPassword, filter: { $0 }, limit: 255)) + index += 1 if hint.isEmpty { - entries.append(.passwordEntryInfo(sectionId: sectionId, tr(.twoStepAuthEnterPasswordHelp) + "\n\n[" + tr(.twoStepAuthEnterPasswordForgot) + "](forgot)")) + entries.append(.desc(sectionId: sectionId, index: index, text: .markdown(L10n.twoStepAuthEnterPasswordHelp + "\n\n[" + L10n.twoStepAuthEnterPasswordForgot + "](forgot)", linkHandler: { link in + forgotPassword() + }), data: InputDataGeneralTextData(viewType: .textBottomItem))) } else { - entries.append(.passwordEntryInfo(sectionId: sectionId, tr(.twoStepAuthEnterPasswordHint(hint)) + "\n\n" + tr(.twoStepAuthEnterPasswordHelp) + "\n\n[" + tr(.twoStepAuthEnterPasswordForgot) + "](forgot)")) + entries.append(.desc(sectionId: sectionId, index: index, text: .markdown(L10n.twoStepAuthEnterPasswordHint(hint) + "\n\n" + L10n.twoStepAuthEnterPasswordHelp + "\n\n[" + L10n.twoStepAuthEnterPasswordForgot + "](forgot)", linkHandler: { link in + forgotPassword() + }), data: InputDataGeneralTextData(viewType: .textBottomItem))) } + index += 1 } + } else { + return [.loading] } - case let .manage(_, emailSet, pendingEmailPattern): - entries.append(.changePassword(sectionId: sectionId, tr(.twoStepAuthChangePassword))) - entries.append(.turnPasswordOff(sectionId: sectionId, tr(.twoStepAuthRemovePassword))) - entries.append(.setupRecoveryEmail(sectionId: sectionId, emailSet ? tr(.twoStepAuthChangeEmail) : tr(.twoStepAuthSetupEmail))) - if pendingEmailPattern.isEmpty { - entries.append(.passwordInfo(sectionId: sectionId, tr(.twoStepAuthGenericHelp))) + case let .manage(_, emailSet, pendingEmail, _): + + entries.append(.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_change_pwd, data: InputDataGeneralData(name: L10n.twoStepAuthChangePassword, color: theme.colors.text, icon: nil, type: .none, viewType: .firstItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_remove_pwd, data: InputDataGeneralData(name: L10n.twoStepAuthRemovePassword, color: theme.colors.text, icon: nil, type: .none, viewType: .innerItem, action: nil))) + index += 1 + entries.append(.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_setup_email, data: InputDataGeneralData(name: emailSet ? L10n.twoStepAuthChangeEmail : L10n.twoStepAuthSetupEmail, color: theme.colors.text, icon: nil, type: .none, viewType: .lastItem, action: nil))) + index += 1 + + + if let _ = pendingEmail { + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.general(sectionId: sectionId, index: index, value: .string(nil), error: nil, identifier: _id_enter_email_code, data: InputDataGeneralData(name: L10n.twoStepAuthEnterEmailCode, color: theme.colors.text, icon: nil, type: .none, viewType: .singleItem, action: nil))) + index += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthEmailSent), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + } else { - entries.append(.passwordInfo(sectionId: sectionId, tr(.twoStepAuthPendingEmailHelp(pendingEmailPattern)))) + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthGenericHelp), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 } + } + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + return entries } -fileprivate func prepareTransition(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], initialSize:NSSize, arguments:TwoStepVerificationUnlockSettingsControllerArguments) -> TableUpdateTransition { + + + +func twoStepVerificationUnlockController(context: AccountContext, mode: TwoStepVerificationUnlockSettingsControllerMode, presentController:@escaping((controller: ViewController, root:Bool, animated: Bool))->Void) -> InputDataController { + + let actionsDisposable = DisposableSet() + + + let checkDisposable = MetaDisposable() + actionsDisposable.add(checkDisposable) + + let setupDisposable = MetaDisposable() + actionsDisposable.add(setupDisposable) + + let setupResultDisposable = MetaDisposable() + actionsDisposable.add(setupResultDisposable) + - let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in - return entry.entry.item(arguments, initialSize: initialSize) + let data: TwoStepVerificationUnlockSettingsControllerData + + switch mode { + case let .access(configuration): + data = .access(configuration: configuration) + case let .manage(password, email, pendingEmail, hasSecureValues): + data = .manage(password: password, emailSet: !email.isEmpty, pendingEmail: pendingEmail, hasSecureValues: hasSecureValues) } + // + let initialState = TwoStepVerificationUnlockSettingsControllerState(passwordText: "", checking: false, emailCode: "", errors: [:], data: data) - return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) -} - -class TwoStepVerificationUnlockController: TableViewController { - - private let mode:TwoStepVerificationUnlockSettingsControllerMode - private var invokeNextAction:(()->Void)? - private let disposable = MetaDisposable() - private var removeOnDisappear: Bool = false - init(account: Account, mode: TwoStepVerificationUnlockSettingsControllerMode) { - self.mode = mode - super.init(account) + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((TwoStepVerificationUnlockSettingsControllerState) -> TwoStepVerificationUnlockSettingsControllerState) -> Void = { f in + statePromise.set(stateValue.modify (f)) } - override func viewDidLoad() { - super.viewDidLoad() - - let account = self.account - let mode = self.mode - - let initialState = TwoStepVerificationUnlockSettingsControllerState(passwordText: "", checking: false) - - let statePromise = ValuePromise(initialState, ignoreRepeated: true) - let stateValue = Atomic(value: initialState) - let updateState: ((TwoStepVerificationUnlockSettingsControllerState) -> TwoStepVerificationUnlockSettingsControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - var presentControllerImpl: ((ViewController) -> Void)? - - let actionsDisposable = DisposableSet() - - let checkDisposable = MetaDisposable() - actionsDisposable.add(checkDisposable) - - let setupDisposable = MetaDisposable() - actionsDisposable.add(setupDisposable) - - let setupResultDisposable = MetaDisposable() - actionsDisposable.add(setupResultDisposable) - - let dataPromise = Promise() - - switch mode { - case .access: - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: nil)) |> then(twoStepVerificationConfiguration(account: account) |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: $0) })) - case let .manage(password, email, pendingEmailPattern): - dataPromise.set(.single(.manage(password: password, emailSet: !email.isEmpty, pendingEmailPattern: pendingEmailPattern))) - } - - let arguments = TwoStepVerificationUnlockSettingsControllerArguments(updatePasswordText: { updatedText in + switch mode { + case .access: + actionsDisposable.add((twoStepVerificationConfiguration(account: context.account) |> map { TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVeriticationAccessConfiguration(configuration: $0, password: nil)) } |> deliverOnMainQueue).start(next: { data in updateState { - $0.withUpdatedPasswordText(updatedText) + $0.withUpdatedControllerData(data) } - }, openForgotPassword: { - setupDisposable.set((dataPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { data in - switch data { - case let .access(configuration): - if let configuration = configuration { - switch configuration { - case let .set(_, hasRecoveryEmail, _): - if hasRecoveryEmail { - updateState { - $0.withUpdatedChecking(true) - } - setupResultDisposable.set((requestTwoStepVerificationPasswordRecoveryCode(account: account) |> deliverOnMainQueue).start(next: { emailPattern in - updateState { - $0.withUpdatedChecking(false) - } - let result = Promise() - let controller = TwoStepVerificationResetController(account: account, emailPattern: emailPattern, result: result) - presentControllerImpl?(controller) - - setupDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] _ in - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationConfiguration.notSet(pendingEmailPattern: "")))) - controller?.dismiss() - })) - }, error: { _ in - updateState { - $0.withUpdatedChecking(false) - } - alert(for: mainWindow, info: tr(.twoStepAuthAnError)) - })) - } else { - alert(for: mainWindow, info: tr(.twoStepAuthErrorHaventEmail)) - } - case .notSet: - break + })) + default: + break + } + + + + let disablePassword: () -> InputDataValidation = { + return .fail(.doSomething { f in + + switch data { + case .access: + break + case let .manage(password, _, _, hasSecureValues): + + var text: String = L10n.twoStepAuthConfirmDisablePassword + if hasSecureValues { + text += "\n\n" + text += L10n.secureIdWarningDataLost + } + + confirm(for: mainWindow, information: text, successHandler: { result in + var disablePassword = false + updateState { state in + if state.checking { + return state + } else { + disablePassword = true + return state.withUpdatedChecking(true) } } - case .manage: + context.hasPassportSettings.set(.single(false)) + + if disablePassword { + let resetPassword = updateTwoStepVerificationPassword(network: context.account.network, currentPassword: password, updatedPassword: .none) |> deliverOnMainQueue + + setupDisposable.set(resetPassword.start(next: { value in + updateState { + $0.withUpdatedChecking(false) + } + context.resetTemporaryPwd() + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.notSet(pendingEmail: nil)), presentController: presentController), root: true, animated: true)) + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + }, error: { error in + alert(for: mainWindow, info: L10n.unknownError) + })) + } + }) + } + }) + } + + let checkEmailConfirmation: () -> InputDataValidation = { + + return .fail(.doSomething { f in + let data: TwoStepVerificationUnlockSettingsControllerData = stateValue.with { $0.data } + + var pendingEmailData: PendingEmailState? + switch data { + case let .access(configuration): + guard let configuration = configuration else { + return + } + switch configuration { + case let .notSet(pendingEmail): + pendingEmailData = pendingEmail + case .set: break } - })) - }, openSetupPassword: { - setupDisposable.set((dataPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { data in - switch data { - case let .access(configuration): - if let configuration = configuration { - switch configuration { - case .notSet: - let result = Promise() - let controller = TwoStepVerificationPasswordEntryController(account: account, mode: .setup, result: result) - presentControllerImpl?(controller) - setupResultDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] updatedPassword in - if let updatedPassword = updatedPassword { - if let pendingEmailPattern = updatedPassword.pendingEmailPattern { - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationConfiguration.notSet(pendingEmailPattern: pendingEmailPattern)))) - } else { - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.manage(password: updatedPassword.password, emailSet: false, pendingEmailPattern: ""))) - } - controller?.dismiss() - } - })) - case .set: - break - } - } - case let .manage(password, emailSet, pendingEmailPattern): - let result = Promise() - let controller = TwoStepVerificationPasswordEntryController(account: account, mode: .change(current: password), result: result) - presentControllerImpl?(controller) - setupResultDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] updatedPassword in - if let updatedPassword = updatedPassword { - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.manage(password: updatedPassword.password, emailSet: emailSet, pendingEmailPattern: pendingEmailPattern))) - controller?.dismiss() - } - })) + case let .manage(password, _, pendingEmail, _): + if let pendingEmail = pendingEmail { + pendingEmailData = PendingEmailState(password: password, email: pendingEmail) } - })) - }, openDisablePassword: { - - confirm(for: mainWindow, with: appName, and: tr(.twoStepAuthConfirmDisablePassword), successHandler: { _ in - var disablePassword = false + } + if let pendingEmail = pendingEmailData { + var code: String? updateState { state in - if state.checking { - return state - } else { - disablePassword = true + if !state.checking { + code = state.emailCode return state.withUpdatedChecking(true) } + return state } - if disablePassword { - setupDisposable.set((dataPromise.get() - |> take(1) - |> mapError { _ -> UpdateTwoStepVerificationPasswordError in return .generic } - |> mapToSignal { data -> Signal in - switch data { - case .access: - return .complete() - case let .manage(password, _, _): - return updateTwoStepVerificationPassword(account: account, currentPassword: password, updatedPassword: .none) - |> mapToSignal { _ -> Signal in - return .complete() - } + if let code = code { + setupDisposable.set((confirmTwoStepRecoveryEmail(network: context.account.network, code: code) + |> deliverOnMainQueue).start(error: { error in + updateState { state in + return state.withUpdatedChecking(false) + } + let text: String + switch error { + case .invalidEmail: + text = L10n.twoStepAuthEmailInvalid + case .invalidCode: + text = L10n.twoStepAuthEmailCodeInvalid + case .expired: + text = L10n.twoStepAuthEmailCodeExpired + case .flood: + text = L10n.twoStepAuthFloodError + case .generic: + text = L10n.unknownError } - } - |> deliverOnMainQueue).start(error: { _ in updateState { - $0.withUpdatedChecking(false) + $0.withUpdatedError(InputDataValueError(description: text, target: .data), for: _id_input_enter_email_code) } + f(.fail(.fields([_id_input_enter_email_code:.shake]))) }, completed: { - updateState { - $0.withUpdatedChecking(false) + switch data { + case .access: + if let password = pendingEmail.password { + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: "", pendingEmail: nil, hasSecureValues: false), presentController: presentController), root: true, animated: true)) + } else { + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.set(hint: "", hasRecoveryEmail: true, hasSecureValues: false)), presentController: presentController), root: true, animated: true)) + } + case let .manage(manage): + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: manage.password, email: "", pendingEmail: nil, hasSecureValues: manage.hasSecureValues), presentController: presentController), root: true, animated: true)) + } + + updateState { state in + return state.withUpdatedChecking(false).withUpdatedEmailCode("") } - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: "")))) })) } - }) - - }, openSetupEmail: { - setupDisposable.set((dataPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { data in - switch data { - case .access: - break - case let .manage(password, _, _): - let result = Promise() - let controller = TwoStepVerificationPasswordEntryController(account: account, mode: .setupEmail(password: password), result: result) - presentControllerImpl?(controller) - setupResultDisposable.set((result.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] updatedPassword in - if let updatedPassword = updatedPassword { - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.manage(password: updatedPassword.password, emailSet: true, pendingEmailPattern: updatedPassword.pendingEmailPattern ?? ""))) - controller?.dismiss() - } - })) - } - })) - }, openResetPendingEmail: { - updateState { state in - return state.withUpdatedChecking(true) } - setupDisposable.set((updateTwoStepVerificationPassword(account: account, currentPassword: nil, updatedPassword: .none) |> deliverOnMainQueue).start(next: { _ in - updateState { state in - return state.withUpdatedChecking(false) - } - dataPromise.set(.single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: .notSet(pendingEmailPattern: "")))) - }, error: { _ in - updateState { state in - return state.withUpdatedChecking(false) - } - })) + }) + } + + + + let validateAccessPassword:([InputDataIdentifier : InputDataValue]) -> InputDataValidation = { data in + var wasChecking: Bool = false + updateState { state in + wasChecking = state.checking + return state + } - let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) - let initialSize = self.atomicSize - - var nextAction:(()->Void)? = nil - - let shake:()->Void = { [weak self] in - (self?.firstResponder() as? NSTextView)?.shake() - (self?.firstResponder() as? NSTextView)?.selectAll(nil) - NSSound.beep() + updateState { state in + return state.withUpdatedChecking(!wasChecking) } - let signal = combineLatest(appearanceSignal, statePromise.get(), dataPromise.get() |> deliverOnMainQueue) - |> map { appearance, state, data -> (TableUpdateTransition, String, TwoStepVerificationUnlockSettingsControllerData) in - - - let entries = twoStepVerificationUnlockSettingsControllerEntries(state: state, data: data).map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} + if !wasChecking, let password = data[_id_input_enter_pwd]?.stringValue { + + return .fail(.doSomething(next: { f in - var title: String = tr(.twoStepAuthPasswordTitle) - switch data { - case let .access(configuration): - if let configuration = configuration { - if state.checking { - nextAction = nil - } else { - switch configuration { - case .notSet: - title = tr(.telegramTwoStepVerificationUnlockController) - case let .set(_, _, pendingEmailPattern): - title = tr(.twoStepAuthPasswordTitle) - nextAction = { - - var wasChecking = false - var password: String? - updateState { state in - wasChecking = state.checking - password = state.passwordText - return state.withUpdatedChecking(true) - } - - if let password = password, !wasChecking { - checkDisposable.set((requestTwoStepVerifiationSettings(account: account, password: password) |> deliverOnMainQueue).start(next: { settings in - updateState { - $0.withUpdatedChecking(false) - } - presentControllerImpl?(TwoStepVerificationUnlockController(account: account, mode: .manage(password: password, email: settings.email, pendingEmailPattern: pendingEmailPattern))) - }, error: { error in - updateState { - $0.withUpdatedChecking(false) - } - - switch error { - case .limitExceeded: - alert(for: mainWindow, info: tr(.twoStepAuthErrorLimitExceeded)) - case .invalidPassword: - shake() - //text = tr(.twoStepAuthErrorInvalidPassword) - case .generic: - alert(for: mainWindow, info: tr(.twoStepAuthErrorGeneric)) - } - - })) - } - } + checkDisposable.set((requestTwoStepVerifiationSettings(network: context.account.network, password: password) + |> mapToSignal { settings -> Signal<(TwoStepVerificationSettings, TwoStepVerificationPendingEmail?), AuthorizationPasswordVerificationError> in + return twoStepVerificationConfiguration(account: context.account) + |> mapError { _ -> AuthorizationPasswordVerificationError in + return .generic } + |> map { configuration in + var pendingEmail: TwoStepVerificationPendingEmail? + if case let .set(configuration) = configuration { + pendingEmail = configuration.pendingEmail + } + return (settings, pendingEmail) } } - case .manage: - title = tr(.telegramTwoStepVerificationUnlockController) - if state.checking { - nextAction = nil - } - } + |> deliverOnMainQueue).start(next: { settings, pendingEmail in + updateState { + $0.withUpdatedChecking(false) + } + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: settings.email, pendingEmail: pendingEmail, hasSecureValues: settings.secureSecret != nil), presentController: presentController), root: true, animated: true)) + f(.none) + }, error: { error in + let text: String + switch error { + case .limitExceeded: + text = L10n.twoStepAuthErrorLimitExceeded + case .invalidPassword: + text = L10n.twoStepAuthInvalidPasswordError + case .generic: + text = L10n.twoStepAuthErrorGeneric + } + updateState { + $0.withUpdatedChecking(false).withUpdatedError(InputDataValueError(description: text, target: .data), for: _id_input_enter_pwd) + } + + f(.fail(.fields([_id_input_enter_pwd : .shake]))) + + })) - return (prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments), title, data) - } |> afterDisposed { - actionsDisposable.dispose() - } |> deliverOnMainQueue - - self.invokeNextAction = { - nextAction?() + })) + + + } else { + checkDisposable.set(nil) } - - disposable.set(signal.start(next: { [weak self] transition, title, data in - self?.genericView.merge(with: transition) + + return .none + } + + let proccessEntryResult:(SetupTwoStepVerificationStateUpdate) -> Void = { update in + switch update { + case .noPassword: + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.notSet(pendingEmail: nil)), presentController: presentController), root: true, animated: true)) + case let .awaitingEmailConfirmation(password, pattern, codeLength): - switch mode { + let data = stateValue.with {$0.data} + + let hasSecureValues: Bool + + switch data { + case let .manage(_, _, _, _hasSecureValues): + hasSecureValues = _hasSecureValues case .access: - switch data { - case let .access(configuration): - self?.removeOnDisappear = false - if let configuration = configuration { - switch configuration { - case .notSet: - self?.rightBarView.isHidden = true - self?.removeOnDisappear = false - case .set: - self?.rightBarView.isHidden = false - self?.removeOnDisappear = true - } - } else { - self?.rightBarView.isHidden = true - } - case .manage: - self?.removeOnDisappear = false - self?.rightBarView.isHidden = true - } - case .manage: - self?.removeOnDisappear = false - self?.rightBarView.isHidden = true + hasSecureValues = false } - self?.setCenterTitle(title) - })) - - readyOnce() - - presentControllerImpl = { [weak self] controller in - self?.navigationController?.push(controller) - } - } - - override var removeAfterDisapper: Bool { - return removeOnDisappear + + let pendingEmail = TwoStepVerificationPendingEmail(pattern: pattern, codeLength: codeLength) + + let root = twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: "", pendingEmail: pendingEmail, hasSecureValues: hasSecureValues), presentController: presentController) + + presentController((controller: root, root: true, animated: false)) + + presentController((controller: twoStepVerificationPasswordEntryController(network: context.account.network, mode: .enterCode(codeLength: pendingEmail.codeLength, pattern: pendingEmail.pattern), initialStage: nil, result: { _ in + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: "email", pendingEmail: nil, hasSecureValues: hasSecureValues), presentController: presentController), root: true, animated: true)) + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + }, presentController: presentController), root: false, animated: true)) + + + case .emailSet: + let data = stateValue.with {$0.data} + + switch data { + case let .manage(password, _, _, hasSecureValues): + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: "email", pendingEmail: nil, hasSecureValues: hasSecureValues), presentController: presentController), root: true, animated: true)) + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + default: + break + } + case let .passwordSet(password, hasRecoveryEmail, hasSecureValues): + if let password = password { + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .manage(password: password, email: hasRecoveryEmail ? "email" : "", pendingEmail: nil, hasSecureValues: hasSecureValues), presentController: presentController), root: true, animated: true)) + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 1.0).start() + } else { + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.set(hint: "", hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues)), presentController: presentController), root: true, animated: true)) + } + } } - override func becomeFirstResponder() -> Bool? { - return true + let setupPassword:() -> InputDataValidation = { + let controller = twoStepVerificationPasswordEntryController(network: context.account.network, mode: .setup, initialStage: nil, result: proccessEntryResult, presentController: presentController) + presentController((controller: controller, root: false, animated: true)) + return .none } - override func firstResponder() -> NSResponder? { - if genericView.count > 1 { - if !(window?.firstResponder is NSTextView) { - return (genericView.viewNecessary(at: 1) as? GeneralInputRowView)?.firstResponder + let changePassword: (_ current: String) -> InputDataValidation = { current in + let controller = twoStepVerificationPasswordEntryController(network: context.account.network, mode: .change(current: current), initialStage: nil, result: proccessEntryResult, presentController: presentController) + presentController((controller: controller, root: false, animated: true)) + return .none + } + + let setupRecoveryEmail:() -> InputDataValidation = { + + let data = stateValue.with {$0.data} + + switch data { + case .access: + break + case let .manage(password, emailSet, _, _): + let controller = twoStepVerificationPasswordEntryController(network: context.account.network, mode: .setupEmail(password: password, change: emailSet), initialStage: nil, result: proccessEntryResult, presentController: presentController) + presentController((controller: controller, root: false, animated: true)) + } + + return .none + } + + let enterCode:() -> InputDataValidation = { + let data = stateValue.with {$0.data} + + switch data { + case .access: + break + case let .manage(_, _, pendingEmail, _): + if let pendingEmail = pendingEmail { + let controller = twoStepVerificationPasswordEntryController(network: context.account.network, mode: .enterCode(codeLength: pendingEmail.codeLength, pattern: pendingEmail.pattern), initialStage: nil, result: proccessEntryResult, presentController: presentController) + presentController((controller: controller, root: false, animated: true)) } } - return window?.firstResponder + + return .none } - override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.composeNext)) + let forgotPassword:() -> Void = { + + let data = stateValue.with {$0.data} + switch data { + case let .access(configuration): + if let configuration = configuration { + switch configuration { + case let .set(_, hasRecoveryEmail, _): + if hasRecoveryEmail { + updateState { state in + return state.withUpdatedChecking(true) + } + + setupResultDisposable.set((requestTwoStepVerificationPasswordRecoveryCode(network: context.account.network) + |> deliverOnMainQueue).start(next: { emailPattern in + + updateState { state in + return state.withUpdatedChecking(false) + } + + presentController((controller: twoStepVerificationResetPasswordController(context: context, emailPattern: emailPattern, success: { + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.notSet(pendingEmail: nil)), presentController: presentController), root: true, animated: true)) + }), root: false, animated: true)) + + }, error: { _ in + updateState { state in + return state.withUpdatedChecking(false) + } + alert(for: mainWindow, info: L10n.twoStepAuthAnError) + })) + } else { + alert(for: mainWindow, info: L10n.twoStepAuthErrorHaventEmail) + } + + default: + break + } + + } + case .manage: + break + } - button.button.set(handler: { [weak self] _ in - self?.invokeNextAction?() - }, for: .Click) + } + + let abort: () -> Void = { + updateState { $0.withUpdatedChecking(true) } + let resetPassword = updateTwoStepVerificationPassword(network: context.account.network, currentPassword: nil, updatedPassword: .none) |> deliverOnMainQueue - return button + setupDisposable.set(resetPassword.start(next: { value in + updateState { $0.withUpdatedChecking(false) } + presentController((controller: twoStepVerificationUnlockController(context: context, mode: .access(.notSet(pendingEmail: nil)), presentController: presentController), root: true, animated: true)) + }, error: { error in + alert(for: mainWindow, info: L10n.unknownError) + })) } - deinit { - disposable.dispose() + let signal: Signal<[InputDataEntry], NoError> = statePromise.get() |> map { state -> [InputDataEntry] in + return twoStepVerificationUnlockSettingsControllerEntries(state: state, forgotPassword: forgotPassword, abort: abort) + } + + + return InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0) }, title: L10n.privacySettingsTwoStepVerification, validateData: { validateData -> InputDataValidation in + + let data = stateValue.with {$0.data} + let loading = stateValue.with {$0.checking} + + if !loading { + switch mode { + case .access: + switch data { + case let .access(configuration): + if let configuration = configuration { + switch configuration { + case let .notSet(pendingEmail): + if let _ = pendingEmail { + return checkEmailConfirmation() + } else { + return setupPassword() + } + case .set: + return validateAccessPassword(validateData) + } + } + case .manage: + break + } + case let .manage(password, _, _, _): + if let _ = validateData[_id_remove_pwd] { + return disablePassword() + } else if let _ = validateData[_id_change_pwd] { + return changePassword(password) + } else if let _ = validateData[_id_setup_email] { + return setupRecoveryEmail() + } else if let _ = validateData[_id_enter_email_code] { + return enterCode() + } + + } + } else { + NSSound.beep() + } + + return .none + + }, updateDatas: { data in + if let password = data[_id_input_enter_pwd]?.stringValue { + updateState { state in + return state.withUpdatedPasswordText(password).withUpdatedError(nil, for: _id_input_enter_pwd) + } + } else if let code = data[_id_input_enter_email_code]?.stringValue { + updateState { state in + return state.withUpdatedEmailCode(code).withUpdatedError(nil, for: _id_input_enter_email_code) + } + } + return .none + }, afterDisappear: { + actionsDisposable.dispose() + }, updateDoneValue: { data in + return { f in + + let data = stateValue.with {$0.data} + + switch mode { + case .access: + switch data { + case let .access(configuration: configuration): + if let configuration = configuration { + switch configuration { + case let .notSet(pendingEmail): + if let _ = pendingEmail { + var checking: Bool = false + var codeEmpty: Bool = true + updateState { state in + checking = state.checking + codeEmpty = state.emailCode.isEmpty + return state + } + return f(checking ? .loading : codeEmpty ? .disabled(L10n.navigationDone) : .enabled(L10n.navigationDone)) + } else { + + } + case .set: + var checking: Bool = false + var pwdEmpty: Bool = true + updateState { state in + checking = state.checking + pwdEmpty = state.passwordText.isEmpty + return state + } + return f(checking ? .loading : pwdEmpty ? .disabled(L10n.navigationDone) : .enabled(L10n.navigationDone)) + } + } else { + return f(.invisible) + } + case .manage: + break + } + + default: + break + } + + var checking: Bool = false + updateState { state in + checking = state.checking + return state + } + return f(checking ? .loading : .invisible) + + } + }, removeAfterDisappear: false, hasDone: true, identifier: "tsv-unlock") +} + + + + + +private struct TwoStepVerificationResetState : Equatable { + let code: String + let checking: Bool + let emailPattern: String + let errors: [InputDataIdentifier : InputDataValueError] + init(emailPattern: String, code: String, checking: Bool, errors: [InputDataIdentifier : InputDataValueError] = [:]) { + self.code = code + self.checking = checking + self.emailPattern = emailPattern + self.errors = errors } - override func returnKeyAction() -> KeyHandlerResult { - invokeNextAction?() - return .invoked + func withUpdatedCode(_ code: String) -> TwoStepVerificationResetState { + return TwoStepVerificationResetState(emailPattern: self.emailPattern, code: code, checking: self.checking, errors: self.errors) + } + func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationResetState { + return TwoStepVerificationResetState(emailPattern: self.emailPattern, code: self.code, checking: checking, errors: self.errors) } + func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> TwoStepVerificationResetState { + var errors = self.errors + if let error = error { + errors[key] = error + } else { + errors.removeValue(forKey: key) + } + return TwoStepVerificationResetState(emailPattern: self.emailPattern, code: self.code, checking: checking, errors: errors) + } +} + +private let _id_input_recovery_code = InputDataIdentifier("_id_input_recovery_code") + +private func twoStepVerificationResetPasswordEntries( state: TwoStepVerificationResetState, unavailable: @escaping()-> Void) -> [InputDataEntry] { + + var entries: [InputDataEntry] = [] + + var sectionId: Int32 = 0 + var index: Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + entries.append(.input(sectionId: sectionId, index: index, value: .string(state.code), error: state.errors[_id_input_recovery_code], identifier: _id_input_recovery_code, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: 255)) + index += 1 + + let info = L10n.twoStepAuthRecoveryCodeHelp + "\n\n\(L10n.twoStepAuthRecoveryEmailUnavailableNew(state.emailPattern))" + + entries.append(.desc(sectionId: sectionId, index: index, text: .markdown(info, linkHandler: { _ in + unavailable() + }), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries } -/* - - var rightNavigationButton: ItemListNavigationButton? - var emptyStateItem: ItemListControllerEmptyStateItem? - let title: String - switch data { - case let .access(configuration): - title = presentationData.strings.TwoStepAuth_Title - if let configuration = configuration { - if state.checking { - rightNavigationButton = ItemListNavigationButton(title: "", style: .activity, enabled: true, action: {}) - } else { - switch configuration { - case .notSet: - break - case .set: - rightNavigationButton = ItemListNavigationButton(title: presentationData.strings.Common_Next, style: .bold, enabled: true, action: { - var wasChecking = false - var password: String? - updateState { state in - wasChecking = state.checking - password = state.passwordText - return state.withUpdatedChecking(true) - } - - if let password = password, !wasChecking { - checkDisposable.set((requestTwoStepVerifiationSettings(account: account, password: password) |> deliverOnMainQueue).start(next: { settings in - updateState { - $0.withUpdatedChecking(false) - } - - replaceControllerImpl?(twoStepVerificationUnlockSettingsController(account: account, mode: .manage(password: password, email: settings.email, pendingEmailPattern: ""))) - }, error: { error in - updateState { - $0.withUpdatedChecking(false) - } - - let text: String - switch error { - case .limitExceeded: - text = "You have entered invalid password too many times. Please try again later." - case .invalidPassword: - text = "Invalid password. Please try again." - case .generic: - text = "An error occured. Please try again later." - } - - presentControllerImpl?(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - })) - } - }) - } - } - } else { - emptyStateItem = ItemListLoadingIndicatorEmptyStateItem() - } - case .manage: - title = presentationData.strings.PrivacySettings_TwoStepAuth - if state.checking { - rightNavigationButton = ItemListNavigationButton(title: "", style: .activity, enabled: true, action: {}) - } - } - - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(entries: twoStepVerificationUnlockSettingsControllerEntries(presentationData: presentationData, state: state, data: data), style: .blocks, focusItemTag: TwoStepVerificationUnlockSettingsEntryTag.password, emptyStateItem: emptyStateItem, animateChanges: false) + + +private func twoStepVerificationResetPasswordController(context: AccountContext, emailPattern: String, success: @escaping()->Void) -> InputDataController { + + + + let initialState = TwoStepVerificationResetState(emailPattern: emailPattern, code: "", checking: false) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((TwoStepVerificationResetState) -> TwoStepVerificationResetState) -> Void = { f in + statePromise.set(stateValue.modify(f)) + } + + let resetDisposable = MetaDisposable() + + let signal: Signal<[InputDataEntry], NoError> = statePromise.get() |> map { state in + return twoStepVerificationResetPasswordEntries(state: state, unavailable: { + alert(for: mainWindow, info: L10n.twoStepAuthRecoveryFailed) + }) + } + + let checkRecoveryCode: (String) -> InputDataValidation = { code in + return .fail(.doSomething { f in + + updateState { + return $0.withUpdatedChecking(true) + } + + resetDisposable.set((recoverTwoStepVerificationPassword(network: context.account.network, code: code) |> deliverOnMainQueue).start(error: { error in + + let errorText: String + switch error { + case .generic: + errorText = L10n.twoStepAuthGenericError + case .invalidCode: + errorText = L10n.twoStepAuthRecoveryCodeInvalid + case .codeExpired: + errorText = L10n.twoStepAuthRecoveryCodeExpired + case .limitExceeded: + errorText = L10n.twoStepAuthFloodError + } + + updateState { + return $0.withUpdatedError(InputDataValueError(description: errorText, target: .data), for: _id_input_recovery_code).withUpdatedChecking(false) + } + + f(.fail(.fields([_id_input_recovery_code: .shake]))) + + }, completed: { + updateState { + return $0.withUpdatedChecking(false) + } + success() + })) + }) + } + + return InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0) }, title: L10n.twoStepAuthRecoveryTitle, validateData: { data in + + let code = stateValue.with {$0.code} + let loading = stateValue.with {$0.checking} + + if !loading { + return checkRecoveryCode(code) + } else { + NSSound.beep() + } + return .none + }, updateDatas: { data in + updateState { current in + return current.withUpdatedCode(data[_id_input_recovery_code]?.stringValue ?? current.code).withUpdatedError(nil, for: _id_input_recovery_code) + } + return .none + }, afterDisappear: { + resetDisposable.dispose() + }, updateDoneValue: { data in + return { f in + let code = stateValue.with {$0.code} + let loading = stateValue.with {$0.checking} + f(loading ? .loading : code.isEmpty ? .disabled(L10n.navigationDone) : .enabled(L10n.navigationDone)) + } + }, removeAfterDisappear: true, hasDone: true, identifier: "tsv-reset") +} + + + + +private let _id_input_entry_pwd = InputDataIdentifier("_id_input_entry_pwd") +private let _id_input_reentry_pwd = InputDataIdentifier("_id_input_reentry_pwd") +private let _id_input_entry_hint = InputDataIdentifier("_id_input_entry_hint") +private let _id_input_entry_email = InputDataIdentifier("_id_input_entry_email") +private let _id_input_entry_code = InputDataIdentifier("_id_input_entry_code") + +private func twoStepVerificationPasswordEntryControllerEntries(state: TwoStepVerificationPasswordEntryControllerState, mode: TwoStepVerificationPasswordEntryMode) -> [InputDataEntry] { + var entries: [InputDataEntry] = [] + + var sectionId:Int32 = 0 + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + var index: Int32 = 0 + + + switch state.stage { + case let .entry(text): + + let placeholder:String + switch mode { + case .change: + placeholder = L10n.twoStepAuthEnterPasswordPassword + default: + placeholder = L10n.twoStepAuthEnterPasswordPassword + } + + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: state.errors[_id_input_entry_pwd], identifier: _id_input_entry_pwd, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: placeholder, filter: { $0 }, limit: 255)) + index += 1 + + switch mode { + case .setup: + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthSetupPasswordDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + case .change: + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthChangePasswordDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + default: + break + } + + case let .reentry(_, text): + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: state.errors[_id_input_reentry_pwd], identifier: _id_input_reentry_pwd, mode: .secure, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthEnterPasswordPassword, filter: { $0 }, limit: 255)) + index += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthSetupPasswordConfirmPassword), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + case let .hint(_, text): + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: state.errors[_id_input_entry_hint], identifier: _id_input_entry_hint, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthSetupHintPlaceholder, filter: { $0 }, limit: 255)) + index += 1 + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthSetupHintDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) + index += 1 + case let .email(_, _, text, change): + + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: state.errors[_id_input_entry_email], identifier: _id_input_entry_email, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthEmail, filter: { $0 }, limit: 255)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(change ? L10n.twoStepAuthEmailHelpChange : L10n.twoStepAuthEmailHelp), data: InputDataGeneralTextData(viewType: .textBottomItem))) + case let .code(text, codeLength, pattern): + entries.append(.input(sectionId: sectionId, index: index, value: .string(text), error: state.errors[_id_input_entry_code], identifier: _id_input_entry_code, mode: .plain, data: InputDataRowData(viewType: .singleItem), placeholder: nil, inputPlaceholder: L10n.twoStepAuthRecoveryCode, filter: {String($0.unicodeScalars.filter { CharacterSet.decimalDigits.contains($0)})}, limit: codeLength ?? 255)) + index += 1 + + entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.twoStepAuthConfirmEmailCodeDesc(pattern)), data: InputDataGeneralTextData(detectBold: false, viewType: .textBottomItem))) + } + + entries.append(.sectionId(sectionId, type: .normal)) + sectionId += 1 + + return entries +} + + + +func twoStepVerificationPasswordEntryController(network: Network, mode: TwoStepVerificationPasswordEntryMode, initialStage: PasswordEntryStage?, result: @escaping(SetupTwoStepVerificationStateUpdate) -> Void, presentController: @escaping((controller: ViewController, root: Bool, animated: Bool)) -> Void) -> InputDataController { + + + var initialStage: PasswordEntryStage! = initialStage + if initialStage == nil { + switch mode { + case .setup, .change: + initialStage = .entry(text: "") + case let .setupEmail(password, change): + initialStage = .email(password: password, hint: "", text: "", change: change) + case let .enterCode(codeLength, pattern): + initialStage = .code(text: "", codeLength: codeLength, pattern: pattern) + } + } + + let initialState = TwoStepVerificationPasswordEntryControllerState(stage: initialStage, updating: false, errors: [:]) + + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) + let updateState: ((TwoStepVerificationPasswordEntryControllerState) -> TwoStepVerificationPasswordEntryControllerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + + let signal: Signal<[InputDataEntry], NoError> = statePromise.get() |> map { state in + return twoStepVerificationPasswordEntryControllerEntries(state: state, mode: mode) + } + + + let actionsDisposable = DisposableSet() + + let updatePasswordDisposable = MetaDisposable() + actionsDisposable.add(updatePasswordDisposable) - return (controllerState, (listState, arguments)) - */ + + func checkAndSaveState() -> InputDataValidation { + var passwordHintEmail: (String, String, String)? + var enterCode: String? + updateState { state in + if state.updating { + return state + } else { + switch state.stage { + case .entry: + break + case .reentry: + break + case let .hint(password, text): + switch mode { + case .change: + passwordHintEmail = (password, text, "") + default: + preconditionFailure() + } + case let .email(password, hint, text, _): + passwordHintEmail = (password, hint, text) + case let .code(text, _, _): + enterCode = text + } + } + return state + } + + + return .fail(.doSomething { f in + if let (password, hint, email) = passwordHintEmail { + + updateState { + $0.withUpdatedUpdating(true) + } + + switch mode { + case .setup, .change: + var currentPassword: String? + if case let .change(current) = mode { + currentPassword = current + } + + updatePasswordDisposable.set((updateTwoStepVerificationPassword(network: network, currentPassword: currentPassword, updatedPassword: .password(password: password, hint: hint, email: email)) |> deliverOnMainQueue).start(next: { update in + updateState { + $0.withUpdatedUpdating(false) + } + switch update { + case let .password(password, pendingEmail): + if let pendingEmail = pendingEmail { + result(.awaitingEmailConfirmation(password: password, pattern: email, codeLength: pendingEmail.codeLength)) + } else { + result(.passwordSet(password: password, hasRecoveryEmail: false, hasSecureValues: false)) + } + case .none: + break + } + }, error: { error in + updateState { + $0.withUpdatedUpdating(false) + } + switch error { + case .generic: + alert(for: mainWindow, info: L10n.twoStepAuthErrorGeneric) + case .invalidEmail: + updateState { + $0.withUpdatedError(InputDataValueError(description: L10n.twoStepAuthErrorInvalidEmail, target: .data), for: _id_input_entry_email) + } + f(.fail(.fields([_id_input_entry_email: .shake]))) + } + + })) + case let .setupEmail(password, _): + updatePasswordDisposable.set((updateTwoStepVerificationEmail(network: network, currentPassword: password, updatedEmail: email) |> deliverOnMainQueue).start(next: { update in + updateState { + $0.withUpdatedUpdating(false) + } + switch update { + case let .password(password, pendingEmail): + if let pendingEmail = pendingEmail { + result(.awaitingEmailConfirmation(password: password, pattern: email, codeLength: pendingEmail.codeLength)) + } else { + result(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false)) + } + case .none: + break + } + }, error: { error in + updateState { + $0.withUpdatedUpdating(false) + } + let errorText: String + switch error { + case .generic: + errorText = L10n.twoStepAuthErrorGeneric + case .invalidEmail: + errorText = L10n.twoStepAuthErrorInvalidEmail + } + updateState { + $0.withUpdatedError(InputDataValueError(description: errorText, target: .data), for: _id_input_entry_email) + } + f(.fail(.fields([_id_input_entry_email: .shake]))) + })) + case .enterCode: + fatalError() + } + } else if let code = enterCode { + updateState { + $0.withUpdatedUpdating(true) + } + updatePasswordDisposable.set((confirmTwoStepRecoveryEmail(network: network, code: code) |> deliverOnMainQueue).start(error: { error in + updateState { + $0.withUpdatedUpdating(false) + } + let errorText: String + switch error { + case .generic: + errorText = L10n.twoStepAuthGenericError + case .invalidCode: + errorText = L10n.twoStepAuthRecoveryCodeInvalid + case .expired: + errorText = L10n.twoStepAuthRecoveryCodeExpired + case .flood: + errorText = L10n.twoStepAuthFloodError + case .invalidEmail: + errorText = L10n.twoStepAuthErrorInvalidEmail + } + updateState { + $0.withUpdatedError(InputDataValueError(description: errorText, target: .data), for: _id_input_entry_code) + } + f(.fail(.fields([_id_input_entry_code: .shake]))) + + }, completed: { + updateState { + $0.withUpdatedUpdating(false) + } + result(.emailSet) + })) + } + }) + + + } + + + return InputDataController(dataSignal: signal |> map { InputDataSignalValue(entries: $0) }, title: "", validateData: { data -> InputDataValidation in + + var stage: PasswordEntryStage? + var allowPerform: Bool = true + + let loading = stateValue.with {$0.updating} + + if !loading { + return .fail(.doSomething { f in + var skipEmail: Bool = false + updateState { state in + var state = state + if state.updating { + return state + } else { + switch state.stage { + case let .entry(text): + if text.isEmpty { + return state + } else { + stage = .reentry(first: text, text: "") + } + case let .reentry(first, text): + if text.isEmpty { + + } else if text != first { + state = state.withUpdatedError(InputDataValueError(description: L10n.twoStepAuthSetupPasswordConfirmFailed, target: .data), for: _id_input_reentry_pwd) + f(.fail(.fields([_id_input_reentry_pwd : .shake]))) + } else { + stage = .hint(password: text, text: "") + } + case let .hint(password, text): + switch mode { + case .setup: + stage = .email(password: password, hint: text, text: "", change: false) + default: + break + } + case let .email(_, _, text, _): + if text.isEmpty { + skipEmail = true + } + case let .code(text, codeLength, _): + if text.isEmpty { + allowPerform = false + } else if let codeLength = codeLength, text.length != codeLength { + allowPerform = false + } else { + allowPerform = true + } + } + return state + } + } + if allowPerform { + if let stage = stage { + presentController((controller: twoStepVerificationPasswordEntryController(network: network, mode: mode, initialStage: stage, result: result, presentController: presentController), root: false, animated: true)) + } else { + if skipEmail { + confirm(for: mainWindow, information: L10n.twoStepAuthEmailSkipAlert, okTitle: L10n.twoStepAuthEmailSkip, successHandler: { _ in + f(checkAndSaveState()) + }) + } else { + f(checkAndSaveState()) + } + } + } + }) + } else { + NSSound.beep() + return .none + } + }, updateDatas: { data -> InputDataValidation in + + let previousCode: String? + switch stateValue.with ({ $0.stage }) { + case let .code(text, _, _): + previousCode = text + default: + previousCode = nil + } + + updateState { state in + switch state.stage { + case let .entry(text): + return state.withUpdatedStage(.entry(text: data[_id_input_entry_pwd]?.stringValue ?? text)) + case let .reentry(first, text): + return state.withUpdatedStage(.reentry(first: first, text: data[_id_input_reentry_pwd]?.stringValue ?? text)).withUpdatedError(nil, for: _id_input_reentry_pwd) + case let .hint(password, text): + return state.withUpdatedStage(.hint(password: password, text: data[_id_input_entry_hint]?.stringValue ?? text)).withUpdatedError(nil, for: _id_input_entry_hint) + case let .email(password, hint, text, change): + return state.withUpdatedStage(.email(password: password, hint: hint, text: data[_id_input_entry_email]?.stringValue ?? text, change: change)).withUpdatedError(nil, for: _id_input_entry_email) + case let .code(text, codeLength, pattern): + return state.withUpdatedStage(.code(text: data[_id_input_entry_code]?.stringValue ?? text, codeLength: codeLength, pattern: pattern)).withUpdatedError(nil, for: _id_input_entry_code) + } + } + + switch stateValue.with ({ $0.stage }) { + case let .code(text, codeLength, _): + if Int32(text.length) == codeLength, previousCode != text { + return checkAndSaveState() + } + default: + break + } + + return .none + }, afterDisappear: { + actionsDisposable.dispose() + }, updateDoneValue: { data in + return { f in + updateState { state in + + if state.updating { + f(.loading) + } else { + switch state.stage { + case let .entry(text): + if text.isEmpty { + f(.disabled(L10n.navigationNext)) + } else { + f(.enabled(L10n.navigationNext)) + } + case let .reentry(_, text): + if text.isEmpty { + f(.disabled(L10n.navigationNext)) + } else { + f(.enabled(L10n.navigationNext)) + } + case let .hint(_, text): + if text.isEmpty { + f(.enabled(L10n.twoStepAuthEmailSkip)) + } else { + f(.enabled(L10n.navigationNext)) + } + case let .email(_, _, text, _): + switch mode { + case .setupEmail: + f(text.isEmpty ? .disabled(L10n.navigationNext) : .enabled(L10n.navigationNext)) + default: + f(text.isEmpty ? .enabled(L10n.twoStepAuthEmailSkip) : .enabled(L10n.navigationNext)) + } + case let .code(text, codeLength, _): + if let codeLength = codeLength { + f(text.length < codeLength ? .disabled(L10n.navigationNext) : .enabled(L10n.navigationNext)) + } else { + f(text.isEmpty ? .disabled(L10n.navigationNext) : .enabled(L10n.navigationNext)) + } + } + } + return state + } + } + }, removeAfterDisappear: false, hasDone: true, identifier: "tsv-entry", afterTransaction: { controller in + var stage: PasswordEntryStage? + updateState { state in + stage = state.stage + return state + } + if let stage = stage { + var title: String = "" + + switch stage { + case .entry: + switch mode { + case .change: + title = L10n.twoStepAuthChangePassword + case .setup: + title = L10n.twoStepAuthSetupPasswordTitle + case .setupEmail: + title = L10n.twoStepAuthSetupPasswordTitle + case .enterCode: + preconditionFailure() + } + + case .reentry: + switch mode { + case .change: + title = L10n.twoStepAuthChangePassword + case .setup: + title = L10n.twoStepAuthSetupPasswordTitle + case .setupEmail: + title = L10n.twoStepAuthSetupPasswordTitle + case .enterCode: + preconditionFailure() + } + case .hint: + title = L10n.twoStepAuthSetupHintTitle + case .email: + title = L10n.twoStepAuthSetupEmailTitle + case .code: + title = L10n.twoStepAuthSetupEmailTitle + } + controller.setCenterTitle(title) + } + }) + +} diff --git a/Telegram-Mac/TwoStepVerificationUnlockStructures.swift b/Telegram-Mac/TwoStepVerificationUnlockStructures.swift deleted file mode 100644 index 7df352d454..0000000000 --- a/Telegram-Mac/TwoStepVerificationUnlockStructures.swift +++ /dev/null @@ -1,615 +0,0 @@ -// -// TwoStepVerificationUnlockStructures.swift -// Telegram -// -// Created by keepcoder on 16/10/2017. -// Copyright © 2017 Telegram. All rights reserved. -// - -import Cocoa -import TelegramCoreMac -import TGUIKit - - -final class TwoStepVerificationUnlockSettingsControllerArguments { - let updatePasswordText: (String) -> Void - let openForgotPassword: () -> Void - let openSetupPassword: () -> Void - let openDisablePassword: () -> Void - let openSetupEmail: () -> Void - let openResetPendingEmail: () -> Void - - init(updatePasswordText: @escaping (String) -> Void, openForgotPassword: @escaping () -> Void, openSetupPassword: @escaping () -> Void, openDisablePassword: @escaping () -> Void, openSetupEmail: @escaping () -> Void, openResetPendingEmail: @escaping () -> Void) { - self.updatePasswordText = updatePasswordText - self.openForgotPassword = openForgotPassword - self.openSetupPassword = openSetupPassword - self.openDisablePassword = openDisablePassword - self.openSetupEmail = openSetupEmail - self.openResetPendingEmail = openResetPendingEmail - } -} - -enum TwoStepVerificationUnlockSettingsSection: Int32 { - case password -} - - -enum TwoStepVerificationUnlockSettingsEntry: TableItemListNodeEntry { - case passwordEntry(sectionId: Int32, String, String) - case passwordEntryInfo(sectionId: Int32, String) - - case passwordSetup(sectionId: Int32, String) - case passwordSetupInfo(sectionId: Int32, String) - - case changePassword(sectionId: Int32, String) - case turnPasswordOff(sectionId: Int32, String) - case setupRecoveryEmail(sectionId: Int32, String) - case passwordInfo(sectionId: Int32, String) - - case pendingEmailInfo(sectionId: Int32, String) - case section(Int32) - - - var stableId: Int32 { - switch self { - case .passwordEntry: - return 0 - case .passwordEntryInfo: - return 1 - case .passwordSetup: - return 2 - case .passwordSetupInfo: - return 3 - case .changePassword: - return 4 - case .turnPasswordOff: - return 5 - case .setupRecoveryEmail: - return 6 - case .passwordInfo: - return 7 - case .pendingEmailInfo: - return 8 - case .section(let id): - return (id + 1) * 1000 - id - } - } - - static func ==(lhs: TwoStepVerificationUnlockSettingsEntry, rhs: TwoStepVerificationUnlockSettingsEntry) -> Bool { - switch lhs { - case let .passwordEntry(lhsSection, lhsText, lhsValue): - if case let .passwordEntry(rhsSection, rhsText, rhsValue) = rhs, lhsSection == rhsSection, lhsText == rhsText, lhsValue == rhsValue { - return true - } else { - return false - } - case let .passwordEntryInfo(lhsSection, lhsText): - if case let .passwordEntryInfo(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .passwordSetupInfo(lhsSection, lhsText): - if case let .passwordSetupInfo(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .setupRecoveryEmail(lhsSection, lhsText): - if case let .setupRecoveryEmail(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .passwordInfo(lhsSection, lhsText): - if case let .passwordInfo(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .pendingEmailInfo(lhsSection, lhsText): - if case let .pendingEmailInfo(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .passwordSetup(lhsSection, lhsText): - if case let .passwordSetup(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .changePassword(lhsSection, lhsText): - if case let .changePassword(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .turnPasswordOff(lhsSection, lhsText): - if case let .turnPasswordOff(rhsSection, rhsText) = rhs, lhsSection == rhsSection, lhsText == rhsText { - return true - } else { - return false - } - case let .section(section): - if case .section(section) = rhs { - return true - } else { - return false - } - } - } - - var index: Int32 { - switch self { - case let .changePassword(sectionId, _): - return (sectionId * 1000) + stableId - case let .passwordEntry(sectionId, _, _): - return (sectionId * 1000) + stableId - case let .passwordEntryInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .passwordSetup(sectionId, _): - return (sectionId * 1000) + stableId - case let .passwordSetupInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .turnPasswordOff(sectionId, _): - return (sectionId * 1000) + stableId - case let .setupRecoveryEmail(sectionId, _): - return (sectionId * 1000) + stableId - case let .passwordInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .pendingEmailInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .section(id): - return (id + 1) * 1000 - id - } - } - - static func <(lhs: TwoStepVerificationUnlockSettingsEntry, rhs: TwoStepVerificationUnlockSettingsEntry) -> Bool { - return lhs.index < rhs.index - } - - - func item(_ arguments: TwoStepVerificationUnlockSettingsControllerArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case let .passwordEntry(_, text, value): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.twoStepAuthEnterPasswordPassword), text: value, limit: INT32_MAX, textChangeHandler: { updatedText in - arguments.updatePasswordText(updatedText) - }, inputType: .secure) - case let .passwordEntryInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: { _ in - arguments.openForgotPassword() - }), inset: NSEdgeInsetsMake(5, 28, 5, 28)) - - case let .passwordSetup(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { - arguments.openSetupPassword() - }) - case let .passwordSetupInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: { _ in }), inset: NSEdgeInsetsMake(5, 28, 5, 28)) - case let .changePassword(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { - arguments.openSetupPassword() - }) - case let .turnPasswordOff(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { - arguments.openDisablePassword() - }) - case let .setupRecoveryEmail(_, text): - return GeneralInteractedRowItem(initialSize, stableId: stableId, name: text, type: .next, action: { - arguments.openSetupEmail() - }) - case let .passwordInfo(_, text): - return GeneralTextRowItem(initialSize, text: .plain(text), inset: NSEdgeInsetsMake(5, 28, 5, 28)) - case let .pendingEmailInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: {_ in - arguments.openResetPendingEmail() - }), inset: NSEdgeInsetsMake(5, 28, 5, 28)) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - } - } -} - -struct TwoStepVerificationUnlockSettingsControllerState: Equatable { - let passwordText: String - let checking: Bool - - init(passwordText: String, checking: Bool) { - self.passwordText = passwordText - self.checking = checking - } - - static func ==(lhs: TwoStepVerificationUnlockSettingsControllerState, rhs: TwoStepVerificationUnlockSettingsControllerState) -> Bool { - if lhs.passwordText != rhs.passwordText { - return false - } - if lhs.checking != rhs.checking { - return false - } - - return true - } - - func withUpdatedPasswordText(_ passwordText: String) -> TwoStepVerificationUnlockSettingsControllerState { - return TwoStepVerificationUnlockSettingsControllerState(passwordText: passwordText, checking: self.checking) - } - - func withUpdatedChecking(_ cheking: Bool) -> TwoStepVerificationUnlockSettingsControllerState { - return TwoStepVerificationUnlockSettingsControllerState(passwordText: self.passwordText, checking: cheking) - } -} - - -enum TwoStepVerificationUnlockSettingsControllerMode { - case access - case manage(password: String, email: String, pendingEmailPattern: String) -} - -enum TwoStepVerificationUnlockSettingsControllerData { - case access(configuration: TwoStepVerificationConfiguration?) - case manage(password: String, emailSet: Bool, pendingEmailPattern: String) -} - - - - - - -final class TwoStepVerificationPasswordEntryControllerArguments { - let updateEntryText: (String) -> Void - let next: () -> Void - let skipEmail:() ->Void - init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, skipEmail:@escaping()->Void) { - self.updateEntryText = updateEntryText - self.next = next - self.skipEmail = skipEmail - } -} - - - -enum TwoStepVerificationPasswordEntryEntry: TableItemListNodeEntry { - case passwordEntry(sectionId:Int32, String, String) - - case hintEntry(sectionId:Int32, String, String) - - case emailEntry(sectionId:Int32, String) - case emailInfo(sectionId:Int32, String) - case section(Int32) - - var stableId: Int32 { - switch self { - case .passwordEntry: - return 1 - case .hintEntry: - return 3 - case .emailEntry: - return 5 - case .emailInfo: - return 6 - case .section(let id): - return (id + 1) * 1000 - id - } - } - - var index: Int32 { - switch self { - case let .passwordEntry(sectionId, _, _): - return (sectionId * 1000) + stableId - case let .hintEntry(sectionId, _, _): - return (sectionId * 1000) + stableId - case let .emailEntry(sectionId, _): - return (sectionId * 1000) + stableId - case let .emailInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .section(id): - return (id + 1) * 1000 - id - } - } - - static func ==(lhs: TwoStepVerificationPasswordEntryEntry, rhs: TwoStepVerificationPasswordEntryEntry) -> Bool { - switch lhs { - case let .passwordEntry(sectionId, text, placeholder): - if case .passwordEntry(sectionId, text, placeholder) = rhs { - return true - } else { - return false - } - case let .hintEntry(sectionId, text, placeholder): - if case .hintEntry(sectionId, text, placeholder) = rhs { - return true - } else { - return false - } - case let .emailEntry(lhsSectionId, lhsText): - if case let .emailEntry(rhsSectionId, rhsText) = rhs, lhsSectionId == rhsSectionId, lhsText == rhsText { - return true - } else { - return false - } - case let .emailInfo(lhsSectionId, lhsText): - if case let .emailInfo(rhsSectionId, rhsText) = rhs, lhsSectionId == rhsSectionId, lhsText == rhsText { - return true - } else { - return false - } - case let .section(id): - if case .section(id) = rhs { - return true - } else { - return false - } - } - } - - static func <(lhs: TwoStepVerificationPasswordEntryEntry, rhs: TwoStepVerificationPasswordEntryEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(_ arguments: TwoStepVerificationPasswordEntryControllerArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case let .passwordEntry(_, text, placeholder): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: placeholder, text: text, limit: INT32_MAX, textChangeHandler: { updatedText in - arguments.updateEntryText(updatedText) - }, inputType: .secure) - case let .hintEntry(_, text, placeholder): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: placeholder, text: text, limit: 30, textChangeHandler: { updatedText in - arguments.updateEntryText(updatedText) - }, inputType: .plain) - case let .emailEntry(_, text): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.twoStepAuthEmail), text: text, limit: 40, textChangeHandler: { updatedText in - arguments.updateEntryText(updatedText) - }, inputType: .plain) - case let .emailInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: { _ in - arguments.skipEmail() - }), inset: NSEdgeInsetsMake(5, 28, 5, 28)) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - } - } -} - -enum PasswordEntryStage: Equatable { - case entry(text: String) - case reentry(first: String, text: String) - case hint(password: String, text: String) - case email(password: String, hint: String, text: String) - - func updateCurrentText(_ text: String) -> PasswordEntryStage { - switch self { - case .entry: - return .entry(text: text) - case let .reentry(first, _): - return .reentry(first: first, text: text) - case let .hint(password, _): - return .hint(password: password, text: text) - case let .email(password, hint, _): - return .email(password: password, hint: hint, text: text) - } - } - - static func ==(lhs: PasswordEntryStage, rhs: PasswordEntryStage) -> Bool { - switch lhs { - case let .entry(text): - if case .entry(text) = rhs { - return true - } else { - return false - } - case let .reentry(first, text): - if case .reentry(first, text) = rhs { - return true - } else { - return false - } - case let .hint(password, text): - if case .hint(password, text) = rhs { - return true - } else { - return false - } - case let .email(password, hint, text): - if case .email(password, hint, text) = rhs { - return true - } else { - return false - } - } - } -} - -struct TwoStepVerificationPasswordEntryControllerState: Equatable { - let stage: PasswordEntryStage - let updating: Bool - - init(stage: PasswordEntryStage, updating: Bool) { - self.stage = stage - self.updating = updating - } - - static func ==(lhs: TwoStepVerificationPasswordEntryControllerState, rhs: TwoStepVerificationPasswordEntryControllerState) -> Bool { - if lhs.stage != rhs.stage { - return false - } - if lhs.updating != rhs.updating { - return false - } - - return true - } - - func withUpdatedStage(_ stage: PasswordEntryStage) -> TwoStepVerificationPasswordEntryControllerState { - return TwoStepVerificationPasswordEntryControllerState(stage: stage, updating: self.updating) - } - - func withUpdatedUpdating(_ updating: Bool) -> TwoStepVerificationPasswordEntryControllerState { - return TwoStepVerificationPasswordEntryControllerState(stage: self.stage, updating: updating) - } -} - -func twoStepVerificationPasswordEntryControllerEntries(state: TwoStepVerificationPasswordEntryControllerState, mode: TwoStepVerificationPasswordEntryMode) -> [TwoStepVerificationPasswordEntryEntry] { - var entries: [TwoStepVerificationPasswordEntryEntry] = [] - - var sectionId:Int32 = 0 - - entries.append(.section(sectionId)) - sectionId += 1 - - switch state.stage { - case let .entry(text): - let placeholder:String - switch mode { - case .change: - placeholder = tr(.twoStepAuthSetupPasswordEnterPasswordNew) - default: - placeholder = tr(.twoStepAuthSetupPasswordEnterPassword) - } - entries.append(.passwordEntry(sectionId: sectionId, text, placeholder)) - case let .reentry(_, text): - entries.append(.passwordEntry(sectionId: sectionId, text, tr(.twoStepAuthSetupPasswordConfirmPassword))) - case let .hint(_, text): - entries.append(.hintEntry(sectionId: sectionId, text, tr(.twoStepAuthSetupHint))) - case let .email(_, _, text): - - var emailText = tr(.twoStepAuthEmailHelp) - switch mode { - case .setupEmail: - break - default: - emailText += "\n\n[\(tr(.twoStepAuthEmailSkip))]()" - } - entries.append(.emailEntry(sectionId: sectionId, text)) - entries.append(.emailInfo(sectionId: sectionId, emailText)) - } - - return entries -} - -enum TwoStepVerificationPasswordEntryMode { - case setup - case change(current: String) - case setupEmail(password: String) -} - -struct TwoStepVerificationPasswordEntryResult { - let password: String - let pendingEmailPattern: String? -} - - - - -final class TwoStepVerificationResetControllerArguments { - let updateEntryText: (String) -> Void - let next: () -> Void - let openEmailInaccessible: () -> Void - - init(updateEntryText: @escaping (String) -> Void, next: @escaping () -> Void, openEmailInaccessible: @escaping () -> Void) { - self.updateEntryText = updateEntryText - self.next = next - self.openEmailInaccessible = openEmailInaccessible - } -} - -enum TwoStepVerificationResetEntry: TableItemListNodeEntry { - case codeEntry(sectionId:Int32, String) - case codeInfo(sectionId:Int32, String) - case section(Int32) - - var stableId: Int32 { - switch self { - case .codeEntry: - return 0 - case .codeInfo: - return 1 - case .section(let id): - return (id + 1) * 1000 - id - } - } - - static func ==(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool { - switch lhs { - case let .codeEntry(sectionId, text): - if case .codeEntry(sectionId, text) = rhs { - return true - } else { - return false - } - case let .codeInfo(sectionId, text): - if case .codeInfo(sectionId, text) = rhs { - return true - } else { - return false - } - case .section(let id): - if case .section(id) = rhs { - return true - } else { - return false - } - } - } - - var index: Int32 { - switch self { - case let .codeInfo(sectionId, _): - return (sectionId * 1000) + stableId - case let .codeEntry(sectionId, _): - return (sectionId * 1000) + stableId - case let .section(id): - return (id + 1) * 1000 - id - } - } - - static func <(lhs: TwoStepVerificationResetEntry, rhs: TwoStepVerificationResetEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(_ arguments: TwoStepVerificationResetControllerArguments, initialSize: NSSize) -> TableRowItem { - switch self { - case let .codeEntry(_, text): - return GeneralInputRowItem(initialSize, stableId: stableId, placeholder: tr(.twoStepAuthRecoveryCode), text: text, limit: 6, textChangeHandler: { updatedText in - arguments.updateEntryText(updatedText) - }, textFilter: { text -> String in - return text.trimmingCharacters(in: CharacterSet.decimalDigits.inverted) - }) - case let .codeInfo(_, text): - return GeneralTextRowItem(initialSize, stableId: stableId, text: .markdown(text, linkHandler: { _ in - arguments.openEmailInaccessible() - })) - case .section: - return GeneralRowItem(initialSize, height: 20, stableId: stableId) - } - } -} - -struct TwoStepVerificationResetControllerState: Equatable { - let codeText: String - let checking: Bool - - init(codeText: String, checking: Bool) { - self.codeText = codeText - self.checking = checking - } - - static func ==(lhs: TwoStepVerificationResetControllerState, rhs: TwoStepVerificationResetControllerState) -> Bool { - if lhs.codeText != rhs.codeText { - return false - } - if lhs.checking != rhs.checking { - return false - } - - return true - } - - func withUpdatedCodeText(_ codeText: String) -> TwoStepVerificationResetControllerState { - return TwoStepVerificationResetControllerState(codeText: codeText, checking: self.checking) - } - - func withUpdatedChecking(_ checking: Bool) -> TwoStepVerificationResetControllerState { - return TwoStepVerificationResetControllerState(codeText: self.codeText, checking: checking) - } -} diff --git a/Telegram-Mac/UnauthorizedConfiguration.swift b/Telegram-Mac/UnauthorizedConfiguration.swift new file mode 100644 index 0000000000..e65c4ac8e1 --- /dev/null +++ b/Telegram-Mac/UnauthorizedConfiguration.swift @@ -0,0 +1,86 @@ +// +// QRLoginConfiguration.swift +// Telegram +// +// Created by Mikhail Filimonov on 26.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TelegramApi + +enum QRLoginType : String { + case primary = "primary" + case secondary = "secondary" + case disabled = "disabled" +} + struct UnauthorizedConfiguration { + static var defaultValue: UnauthorizedConfiguration { + return UnauthorizedConfiguration(qr: .disabled) + } + + let qr: QRLoginType + + fileprivate init(qr: QRLoginType) { + self.qr = qr + } + public static func with(appConfiguration: AppConfiguration) -> UnauthorizedConfiguration { + if let data = appConfiguration.data, let rawType = data["qr_login_code"] as? String, let qr = QRLoginType(rawValue: rawType) { + return UnauthorizedConfiguration(qr: qr) + } else { + return .defaultValue + } + } +} + + +func unauthorizedConfiguration(accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.appConfiguration]) |> mapToSignal { view in + if let appConfiguration = view.entries[ApplicationSharedPreferencesKeys.appConfiguration] as? AppConfiguration { + let configuration = UnauthorizedConfiguration.with(appConfiguration: appConfiguration) + return .single(configuration) + } else { + return .never() + } + } |> deliverOnMainQueue +} + +private func currentUnauthorizedAppConfiguration(transaction: AccountManagerModifier) -> AppConfiguration { + if let entry = transaction.getSharedData(ApplicationSharedPreferencesKeys.appConfiguration) as? AppConfiguration { + return entry + } else { + return AppConfiguration.defaultValue + } +} + +private func updateAppConfiguration(transaction: AccountManagerModifier, _ f: (AppConfiguration) -> AppConfiguration) { + let current = currentUnauthorizedAppConfiguration(transaction: transaction) + let updated = f(current) + transaction.updateSharedData(ApplicationSharedPreferencesKeys.appConfiguration, { _ in + return updated + }) +} + + +func managedAppConfigurationUpdates(accountManager: AccountManager, network: Network) -> Signal { + let poll = Signal { subscriber in + return (network.request(Api.functions.help.getAppConfig()) + |> retryRequest + |> mapToSignal { result -> Signal in + return accountManager.transaction { transaction -> Void in + if let data = JSON(apiJson: result) { + updateAppConfiguration(transaction: transaction, { configuration -> AppConfiguration in + var configuration = configuration + configuration.data = data + return configuration + }) + } + } + }).start() + } + return (poll |> then(.complete() |> suspendAwareDelay(12.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/Telegram-Mac/UndoOverlayHeaderView.swift b/Telegram-Mac/UndoOverlayHeaderView.swift new file mode 100644 index 0000000000..425c58ea0b --- /dev/null +++ b/Telegram-Mac/UndoOverlayHeaderView.swift @@ -0,0 +1,191 @@ +// +// UndoOverlayHeaderView.swift +// Telegram +// +// Created by Mikhail Filimonov on 09/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +class UndoOverlayHeaderView: NavigationHeaderView { + private let progress = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, lineWidth: 2, clockwise: false), twist: false, size: NSMakeSize(20, 20)) + private let manager: ChatUndoManager + private let disposable = MetaDisposable() + private var didSetReady: Bool = false + private var timer: SwiftSignalKit.Timer? + private var progressValue: Double = 0.0 + private var secondsUntilFinish: Int = 0 + private let undoButton = TitleButton() + private let textView = TextView() + private let durationContainer: View = View(frame: NSMakeRect(0, 0, 18, 18)) + init(_ header: NavigationHeader, manager: ChatUndoManager) { + self.manager = manager + super.init(header) + addSubview(progress) + addSubview(undoButton) + addSubview(durationContainer) + addSubview(textView) + undoButton.set(font: .medium(.title), for: .Normal) + undoButton.direction = .right + undoButton.autohighlight = false + border = [.Bottom] + progress.state = .ImpossibleFetching(progress: 0, force: true) + + updateDuration(value: 5, animated: false) + + disposable.set((manager.allStatuses() |> deliverOnMainQueue).start(next: { [weak self] statuses in + self?.update(statuses: statuses) + })) + + + undoButton.set(handler: { [weak self] _ in + self?.manager.cancelAll() + }, for: .Click) + + updateLocalizationAndTheme(theme: theme) + } + + var removeAnimationForNextTransition: Bool = true + + private func updateProgress(force: Bool) { + progress.state = .ImpossibleFetching(progress: Float(progressValue), force: force) + } + private func updateDuration(value: Int, animated: Bool) { + if self.secondsUntilFinish != value { + let reversed: Bool = self.secondsUntilFinish < value + self.self.secondsUntilFinish = value + + let textView = TextView() + let layout = TextViewLayout.init(.initialize(string: "\(value)", color: theme.colors.text, font: .medium(12))) + layout.measure(width: .greatestFiniteMagnitude) + + + if animated { + for view in durationContainer.subviews { + textView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false) + view._change(pos: NSMakePoint(view.frame.minX, reversed ? -view.frame.height : durationContainer.frame.height), animated: true, removeOnCompletion: false, duration: 0.2, completion: { [weak view] completed in + view?.removeFromSuperview() + }) + } + } else { + durationContainer.removeAllSubviews() + } + + textView.update(layout) + durationContainer.addSubview(textView) + textView.center() + + if animated { + textView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + textView.layer?.animatePosition(from: NSMakePoint(textView.frame.minX, reversed ? durationContainer.frame.height : -textView.frame.height), to: textView.frame.origin, duration: 0.2) + } + } + + } + + private func update(statuses: ChatUndoStatuses) { + + if statuses.hasProcessingActions { + + let newValue = 1.0 - min(1.0, max(0, statuses.secondsUntilFinish / statuses.maximumDuration)) + + let removeAnimationForNextTransition = self.removeAnimationForNextTransition + self.removeAnimationForNextTransition = false + + + timer?.invalidate() + + + timer = SwiftSignalKit.Timer(timeout: 0.016, repeat: true, completion: { [weak self] in + self?.progressValue = 1.0 - min(1.0, max(0, statuses.secondsUntilFinish / statuses.maximumDuration)) + self?.updateDuration(value: Int(round(max(1, statuses.secondsUntilFinish))), animated: true) + self?.updateProgress(force: true) + }, queue: Queue.mainQueue()) + + if progressValue > newValue { + delay(0.2, closure: { [weak self] in + self?.timer?.start() + }) + } else { + timer?.start() + } + + let layout = TextViewLayout(.initialize(string: statuses.activeDescription, color: theme.colors.text, font: .medium(.text)), maximumNumberOfLines: 10) + textView.update(layout) + + progressValue = min(max(newValue, 0), 1.0) + updateProgress(force: false) + updateDuration(value: Int(round(max(1, statuses.secondsUntilFinish))), animated: !removeAnimationForNextTransition) + + } else { + timer?.invalidate() + timer = nil + } + + + if !didSetReady { + self.ready.set(.single(true)) + didSetReady = true + } + + needsLayout = true + } + + deinit { + timer?.invalidate() + disposable.dispose() + } + + override func layout() { + super.layout() + progress.centerY(x: 28) + undoButton.centerY(x: frame.width - undoButton.frame.width - 20) + durationContainer.centerY(x: progress.frame.minX + 1, addition: -1) + + if let layout = textView.layout { + layout.measure(width: frame.width - (progress.frame.maxX + 18) - undoButton.frame.width - 20) + textView.update(layout) + } + textView.centerY(x: progress.frame.maxX + 18, addition: -1) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + + self.progress.theme = RadialProgressTheme(backgroundColor: .clear, foregroundColor: theme.colors.text, lineWidth: 2, clockwise: false) + + let attributed = textView.layout?.attributedString.mutableCopy() as? NSMutableAttributedString + if let attributed = attributed { + attributed.addAttribute(.foregroundColor, value: theme.colors.text, range: attributed.range) + self.textView.update(TextViewLayout(attributed, maximumNumberOfLines: 10)) + } + + self.borderColor = theme.colors.border + + undoButton.set(text: L10n.chatUndoManagerUndo, for: .Normal) + undoButton.set(image: theme.icons.chatUndoAction, for: .Normal) + undoButton.set(color: theme.colors.accent, for: .Normal) + + _ = undoButton.sizeToFit() + backgroundColor = theme.colors.background + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} diff --git a/Telegram-Mac/UndoTooltipController.swift b/Telegram-Mac/UndoTooltipController.swift new file mode 100644 index 0000000000..547d3553a8 --- /dev/null +++ b/Telegram-Mac/UndoTooltipController.swift @@ -0,0 +1,321 @@ +// +// UndoTooltipController.swift +// Telegram +// +// Created by Mikhail Filimonov on 26/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore + + + + final class UndoTooltipControl : NSObject { + var current: UndoTooltipController? + private let disposable = MetaDisposable() + private let context: AccountContext + init(context: AccountContext) { + self.context = context + super.init() + + let invocation: (NSEvent)-> KeyHandlerResult = { [weak self] _ in + self?.hideCurrentIfNeeded() + return .rejected + } + + self.context.window.set(mouseHandler: invocation, with: self, for: .leftMouseUp, priority: .supreme) + self.context.window.set(mouseHandler: invocation, with: self, for: .rightMouseUp, priority: .supreme) + self.context.window.set(mouseHandler: invocation, with: self, for: .rightMouseDown, priority: .supreme) + + + self.context.window.set(handler: { [weak self] in + self?.hideCurrentIfNeeded() + return .rejected + }, with: self, for: .All, priority: .supreme) + } + + var getYInset:()->CGFloat = { return 10 } { + didSet { + self.current?.getYInset = self.getYInset + } + } + + func add(controller: ViewController) { + let context = self.context + if self.current == nil { + let new = UndoTooltipController(context, controller: controller, undoManager: context.chatUndoManager) + new.getYInset = self.getYInset + new.show() + + self.current = new + + new.view.layer?.animatePosition(from: NSMakePoint(new.view.frame.minX, new.view.frame.maxY), to: new.view.frame.origin, duration: 0.25, timingFunction: .spring) + new.view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, timingFunction: .spring) + } + disposable.set((Signal.complete() |> delay(5.0, queue: .mainQueue())).start(completed: { [weak self] in + self?.hideCurrentIfNeeded() + })) + } + + private func hideCurrentIfNeeded(animated: Bool = true) { + if let current = self.current { + self.current = nil + if !current.cancelled { + context.chatUndoManager.invokeAll() + } + let view = current.view + if animated { + view.layer?.animatePosition(from: view.frame.origin, to: NSMakePoint(view.frame.minX, view.frame.maxY), duration: 0.25, timingFunction: .spring, removeOnCompletion: false) + view._change(opacity: 0, duration: 0.25, timingFunction: .spring, completion: { [weak view] completed in + view?.removeFromSuperview() + }) + } else { + view.removeFromSuperview() + } + } + } + + deinit { + disposable.dispose() + context.window.removeAllHandlers(for: self) + } +} + + +final class UndoTooltipView : NSVisualEffectView, AppearanceViewProtocol { + private let progress = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, lineWidth: 2, clockwise: false), twist: false, size: NSMakeSize(20, 20)) + private let textView: TextView = TextView() + private var undoButton: TitleButton = TitleButton() + + private let manager: ChatUndoManager + private let disposable = MetaDisposable() + private var didSetReady: Bool = false + private var timer: SwiftSignalKit.Timer? + private var progressValue: Double = 0.0 + private var secondsUntilFinish: Int = 0 + private let durationContainer: View = View(frame: NSMakeRect(0, 0, 18, 18)) + + + init(frame frameRect: NSRect, undoManager: ChatUndoManager, undo: @escaping()->Void) { + self.manager = undoManager + super.init(frame: frameRect) + self.wantsLayer = true + self.blendingMode = .withinWindow + self.material = .dark + + addSubview(progress) + addSubview(undoButton) + addSubview(durationContainer) + addSubview(textView) + + self.layer?.cornerRadius = 10.0 + + + + undoButton.set(font: .medium(.title), for: .Normal) + undoButton.direction = .right + undoButton.autohighlight = false + + textView.userInteractionEnabled = false + textView.isSelectable = false + textView.disableBackgroundDrawing = true + + progress.state = .ImpossibleFetching(progress: 0, force: true) + + updateDuration(value: 5, animated: false) + + disposable.set((manager.allStatuses() |> deliverOnMainQueue).start(next: { [weak self] statuses in + self?.update(statuses: statuses) + })) + + undoButton.set(handler: { _ in + undo() + }, for: .Down) + } + + var removeAnimationForNextTransition: Bool = true + + private func updateProgress(force: Bool) { + progress.state = .ImpossibleFetching(progress: Float(progressValue), force: force) + } + private func updateDuration(value: Int, animated: Bool) { + if self.secondsUntilFinish != value { + let reversed: Bool = self.secondsUntilFinish < value + self.self.secondsUntilFinish = value + + let textView = TextView() + let layout = TextViewLayout.init(.initialize(string: "\(value)", color: .white, font: .medium(12))) + layout.measure(width: .greatestFiniteMagnitude) + + + if animated { + for view in durationContainer.subviews { + textView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false) + view._change(pos: NSMakePoint(view.frame.minX, reversed ? -view.frame.height : durationContainer.frame.height), animated: true, removeOnCompletion: false, duration: 0.2, completion: { [weak view] completed in + view?.removeFromSuperview() + }) + } + } else { + durationContainer.removeAllSubviews() + } + + textView.update(layout) + durationContainer.addSubview(textView) + textView.center() + + if animated { + textView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + textView.layer?.animatePosition(from: NSMakePoint(textView.frame.minX, reversed ? durationContainer.frame.height : -textView.frame.height), to: textView.frame.origin, duration: 0.2) + } + } + + } + + private func update(statuses: ChatUndoStatuses) { + if statuses.hasProcessingActions { + + let newValue = 1.0 - min(1.0, max(0, statuses.secondsUntilFinish / statuses.maximumDuration)) + + let removeAnimationForNextTransition = self.removeAnimationForNextTransition + self.removeAnimationForNextTransition = false + + + timer?.invalidate() + + + timer = SwiftSignalKit.Timer(timeout: 0.016, repeat: true, completion: { [weak self] in + self?.progressValue = 1.0 - min(1.0, max(0, statuses.secondsUntilFinish / statuses.maximumDuration)) + self?.updateDuration(value: Int(round(max(1, statuses.secondsUntilFinish))), animated: true) + self?.updateProgress(force: true) + }, queue: Queue.mainQueue()) + + if progressValue > newValue { + delay(0.2, closure: { [weak self] in + self?.timer?.start() + }) + } else { + timer?.start() + } + + let layout = TextViewLayout(.initialize(string: statuses.activeDescription, color: .white, font: .medium(.text)), maximumNumberOfLines: 10) + textView.update(layout) + + progressValue = min(max(newValue, 0), 1.0) + updateProgress(force: false) + updateDuration(value: Int(round(max(1, statuses.secondsUntilFinish))), animated: !removeAnimationForNextTransition) + + } else { + timer?.invalidate() + timer = nil + } + + needsLayout = true + } + + deinit { + timer?.invalidate() + disposable.dispose() + } + + func updateLocalizationAndTheme(theme: PresentationTheme) { + + self.progress.theme = RadialProgressTheme(backgroundColor: .clear, foregroundColor: .white, lineWidth: 2, clockwise: false) + + let attributed = textView.layout?.attributedString.mutableCopy() as? NSMutableAttributedString + if let attributed = attributed { + attributed.addAttribute(.foregroundColor, value: NSColor.white, range: attributed.range) + self.textView.update(TextViewLayout(attributed, maximumNumberOfLines: 1)) + } + undoButton.set(text: L10n.chatUndoManagerUndo, for: .Normal) + undoButton.set(color: .white, for: .Normal) + + _ = undoButton.sizeToFit() + } + + + override var isFlipped: Bool { + return true + } + + override func layout() { + super.layout() + progress.centerY(x: 18) + undoButton.centerY(x: frame.width - undoButton.frame.width - 10) + durationContainer.centerY(x: progress.frame.minX + 1, addition: -1) + + if let layout = textView.layout { + layout.measure(width: frame.width - (progress.frame.maxX + 8) - undoButton.frame.width - 10) + textView.update(layout) + } + textView.centerY(x: progress.frame.maxX + 8, addition: -1) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required override init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +class UndoTooltipController: TelegramGenericViewController { + private let undoManager: ChatUndoManager + private weak var controller: ViewController? + private(set) var cancelled: Bool = false + init(_ context: AccountContext, controller: ViewController, undoManager: ChatUndoManager) { + self.undoManager = undoManager + self.controller = controller + super.init(context) + self.bar = .init(height: 0) + self._frameRect = NSMakeRect(0, 0, min(controller.frame.width - 20, 330), 40) + } + + override func viewDidLoad() { + super.viewDidLoad() + readyOnce() + } + override func initializer() -> UndoTooltipView { + return UndoTooltipView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), undoManager: undoManager, undo: { [weak self] in + if let `self` = self { + self.undoManager.cancelAll() + self.cancelled = true + } + }) + } + + + func show() { + + guard let controller = controller else { return } + loadViewIfNeeded() + controller.view.addSubview(self.view) + self.parentFrameDidChange(Notification(name: Notification.Name(""))) + NotificationCenter.default.addObserver(self, selector: #selector(parentFrameDidChange(_:)), name: NSView.frameDidChangeNotification, object: controller.view) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + genericView.updateLocalizationAndTheme(theme: theme) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + var getYInset:()->CGFloat = { return 10 } + + @objc private func parentFrameDidChange(_ notification:Notification) { + + guard let controller = controller else { return } + + self.view.isHidden = controller.frame.width < 100 + self.view.frame = NSMakeRect(0, 0, min(controller.frame.width - 20, 330), self.frame.height) + + self.view.centerX(y: controller.frame.height - self.frame.height - self.getYInset()) + } + +} diff --git a/Telegram-Mac/UniversalSoftwareVideoSource.swift b/Telegram-Mac/UniversalSoftwareVideoSource.swift new file mode 100644 index 0000000000..4fe3b28864 --- /dev/null +++ b/Telegram-Mac/UniversalSoftwareVideoSource.swift @@ -0,0 +1,393 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore + + +private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + + let data: Signal<(Data, Bool), NoError> + + let resourceSize: Int = context.size + let readCount = min(256 * 1024, Int(bufferSize)) + let requestRange: Range = context.readingOffset ..< (context.readingOffset + readCount) + + context.currentNumberOfReads += 1 + context.currentReadBytes += readCount + + let semaphore = DispatchSemaphore(value: 0) + data = context.mediaBox.resourceData(context.fileReference.media.resource, size: context.size, in: requestRange, mode: .partial) + let requiredDataIsNotLocallyAvailable = context.requiredDataIsNotLocallyAvailable + var fetchedData: Data? + let fetchDisposable = MetaDisposable() + let isInitialized = context.videoStream != nil + let mediaBox = context.mediaBox + let reference = context.fileReference.resourceReference(context.fileReference.media.resource) + let disposable = data.start(next: { result in + let (data, isComplete) = result + if data.count == readCount || isComplete { + fetchedData = data + semaphore.signal() + } else { + if isInitialized { + fetchDisposable.set(fetchedMediaResource(mediaBox: mediaBox, reference: reference, ranges: [(requestRange, .maximum)]).start()) + } + requiredDataIsNotLocallyAvailable?() + } + }) + let cancelDisposable = context.cancelRead.start(next: { value in + if value { + semaphore.signal() + } + }) + semaphore.wait() + + disposable.dispose() + cancelDisposable.dispose() + fetchDisposable.dispose() + + if let fetchedData = fetchedData { + fetchedData.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(buffer, bytes, fetchedData.count) + } + let fetchedCount = Int32(fetchedData.count) + context.readingOffset += Int(fetchedCount) + return fetchedCount + } else { + return 0 + } +} + +private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 { + let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() + if (whence & FFMPEG_AVSEEK_SIZE) != 0 { + return Int64(context.size) + } else { + context.readingOffset = Int(offset) + return offset + } +} + +private final class SoftwareVideoStream { + let index: Int + let fps: CMTime + let timebase: CMTime + let duration: CMTime + let decoder: FFMpegMediaVideoFrameDecoder + let rotationAngle: Double + let aspect: Double + + init(index: Int, fps: CMTime, timebase: CMTime, duration: CMTime, decoder: FFMpegMediaVideoFrameDecoder, rotationAngle: Double, aspect: Double) { + self.index = index + self.fps = fps + self.timebase = timebase + self.duration = duration + self.decoder = decoder + self.rotationAngle = rotationAngle + self.aspect = aspect + } +} + +private final class UniversalSoftwareVideoSourceImpl { + fileprivate let mediaBox: MediaBox + fileprivate let fileReference: FileMediaReference + fileprivate let size: Int + + fileprivate let state: ValuePromise + + fileprivate var avIoContext: FFMpegAVIOContext! + fileprivate var avFormatContext: FFMpegAVFormatContext! + fileprivate var videoStream: SoftwareVideoStream! + + fileprivate var readingOffset: Int = 0 + + fileprivate var cancelRead: Signal + fileprivate var requiredDataIsNotLocallyAvailable: (() -> Void)? + fileprivate var currentNumberOfReads: Int = 0 + fileprivate var currentReadBytes: Int = 0 + + init?(mediaBox: MediaBox, fileReference: FileMediaReference, state: ValuePromise, cancelInitialization: Signal) { + guard let size = fileReference.media.size else { + return nil + } + + self.mediaBox = mediaBox + self.fileReference = fileReference + self.size = size + + self.state = state + state.set(.initializing) + + self.cancelRead = cancelInitialization + + let ioBufferSize = 1 * 1024 + + guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback) else { + return nil + } + self.avIoContext = avIoContext + + let avFormatContext = FFMpegAVFormatContext() + avFormatContext.setIO(avIoContext) + + if !avFormatContext.openInput() { + return nil + } + + if !avFormatContext.findStreamInfo() { + return nil + } + + self.avFormatContext = avFormatContext + + var videoStream: SoftwareVideoStream? + + for streamIndexNumber in avFormatContext.streamIndices(for: FFMpegAVFormatStreamTypeVideo) { + let streamIndex = streamIndexNumber.int32Value + if avFormatContext.isAttachedPic(atStreamIndex: streamIndex) { + continue + } + + let codecId = avFormatContext.codecId(atStreamIndex: streamIndex) + + let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000)) + let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + + let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale) + + let metrics = avFormatContext.metricsForStream(at: streamIndex) + + let rotationAngle: Double = metrics.rotationAngle + let aspect = Double(metrics.width) / Double(metrics.height) + + if let codec = FFMpegAVCodec.find(forId: codecId) { + let codecContext = FFMpegAVCodecContext(codec: codec) + if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { + if codecContext.open() { + videoStream = SoftwareVideoStream(index: Int(streamIndex), fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + break + } + } + } + } + + if let videoStream = videoStream { + self.videoStream = videoStream + } else { + return nil + } + + state.set(.ready) + } + + private func readPacketInternal() -> FFMpegPacket? { + guard let avFormatContext = self.avFormatContext else { + return nil + } + + let packet = FFMpegPacket() + if avFormatContext.readFrame(into: packet) { + return packet + } else { + return nil + } + } + + func readDecodableFrame() -> (MediaTrackDecodableFrame?, Bool) { + var frames: [MediaTrackDecodableFrame] = [] + var endOfStream = false + + while frames.isEmpty { + if let packet = self.readPacketInternal() { + if let videoStream = videoStream, Int(packet.streamIndex) == videoStream.index { + let packetPts = packet.pts + + let pts = CMTimeMake(value: packetPts, timescale: videoStream.timebase.timescale) + let dts = CMTimeMake(value: packet.dts, timescale: videoStream.timebase.timescale) + + let duration: CMTime + + let frameDuration = packet.duration + if frameDuration != 0 { + duration = CMTimeMake(value: frameDuration * videoStream.timebase.value, timescale: videoStream.timebase.timescale) + } else { + duration = videoStream.fps + } + + let frame = MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration) + frames.append(frame) + } + } else { + endOfStream = true + break + } + } + + if endOfStream { + if let videoStream = self.videoStream { + videoStream.decoder.reset() + } + } + + return (frames.first, endOfStream) + } + + private func seek(timestamp: Double) { + if let stream = self.videoStream, let avFormatContext = self.avFormatContext { + let pts = CMTimeMakeWithSeconds(timestamp, preferredTimescale: stream.timebase.timescale) + avFormatContext.seekFrame(forStreamIndex: Int32(stream.index), pts: pts.value, positionOnKeyframe: true) + stream.decoder.reset() + } + } + + func readImage(at timestamp: Double) -> (CGImage?, CGFloat, CGFloat, Bool) { + guard let videoStream = self.videoStream, let _ = self.avFormatContext else { + return (nil, 0.0, 1.0, false) + } + + self.seek(timestamp: timestamp) + + self.currentNumberOfReads = 0 + self.currentReadBytes = 0 + for _ in 0 ..< 10 { + let (decodableFrame, loop) = self.readDecodableFrame() + if let decodableFrame = decodableFrame { + if let renderedFrame = videoStream.decoder.render(frame: decodableFrame) { + //print("Frame rendered in \(self.currentNumberOfReads) reads, \(self.currentReadBytes) bytes, total frames read: \(i + 1)") + return (renderedFrame, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop) + } + } + } + return (nil, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), true) + } +} + +private enum UniversalSoftwareVideoSourceState { + case initializing + case failed + case ready + case generatingFrame +} + +private final class UniversalSoftwareVideoSourceThreadParams: NSObject { + let mediaBox: MediaBox + let fileReference: FileMediaReference + let state: ValuePromise + let cancelInitialization: Signal + + init(mediaBox: MediaBox, fileReference: FileMediaReference, state: ValuePromise, cancelInitialization: Signal) { + self.mediaBox = mediaBox + self.fileReference = fileReference + self.state = state + self.cancelInitialization = cancelInitialization + } +} + +private final class UniversalSoftwareVideoSourceTakeFrameParams: NSObject { + let timestamp: Double + let completion: (CGImage?) -> Void + let cancel: Signal + let requiredDataIsNotLocallyAvailable: () -> Void + + init(timestamp: Double, completion: @escaping (CGImage?) -> Void, cancel: Signal, requiredDataIsNotLocallyAvailable: @escaping () -> Void) { + self.timestamp = timestamp + self.completion = completion + self.cancel = cancel + self.requiredDataIsNotLocallyAvailable = requiredDataIsNotLocallyAvailable + } +} + +private final class UniversalSoftwareVideoSourceThread: NSObject { + @objc static func entryPoint(_ params: UniversalSoftwareVideoSourceThreadParams) { + let runLoop = RunLoop.current + + let timer = Timer(fireAt: .distantFuture, interval: 0.0, target: UniversalSoftwareVideoSourceThread.self, selector: #selector(UniversalSoftwareVideoSourceThread.none), userInfo: nil, repeats: false) + runLoop.add(timer, forMode: .common) + + let source = UniversalSoftwareVideoSourceImpl(mediaBox: params.mediaBox, fileReference: params.fileReference, state: params.state, cancelInitialization: params.cancelInitialization) + Thread.current.threadDictionary["source"] = source + + while true { + runLoop.run(mode: .default, before: .distantFuture) + if Thread.current.threadDictionary["UniversalSoftwareVideoSourceThread_stop"] != nil { + break + } + } + + Thread.current.threadDictionary.removeObject(forKey: "source") + } + + @objc static func none() { + } + + @objc static func stop() { + Thread.current.threadDictionary["UniversalSoftwareVideoSourceThread_stop"] = "true" + } + + @objc static func takeFrame(_ params: UniversalSoftwareVideoSourceTakeFrameParams) { + guard let source = Thread.current.threadDictionary["source"] as? UniversalSoftwareVideoSourceImpl else { + params.completion(nil) + return + } + source.cancelRead = params.cancel + source.requiredDataIsNotLocallyAvailable = params.requiredDataIsNotLocallyAvailable + source.state.set(.generatingFrame) + let image = source.readImage(at: params.timestamp).0 + source.cancelRead = .single(false) + source.requiredDataIsNotLocallyAvailable = nil + source.state.set(.ready) + params.completion(image) + } +} + +enum UniversalSoftwareVideoSourceTakeFrameResult { + case waitingForData + case image(CGImage?) +} + +final class UniversalSoftwareVideoSource { + private let thread: Thread + private let stateValue: ValuePromise = ValuePromise(.initializing, ignoreRepeated: true) + private let cancelInitialization: ValuePromise = ValuePromise(false) + + var ready: Signal { + return self.stateValue.get() + |> map { value -> Bool in + switch value { + case .ready: + return true + default: + return false + } + } + } + + init(mediaBox: MediaBox, fileReference: FileMediaReference) { + self.thread = Thread(target: UniversalSoftwareVideoSourceThread.self, selector: #selector(UniversalSoftwareVideoSourceThread.entryPoint(_:)), object: UniversalSoftwareVideoSourceThreadParams(mediaBox: mediaBox, fileReference: fileReference, state: self.stateValue, cancelInitialization: self.cancelInitialization.get())) + self.thread.name = "UniversalSoftwareVideoSource" + self.thread.start() + } + + deinit { + UniversalSoftwareVideoSourceThread.self.perform(#selector(UniversalSoftwareVideoSourceThread.stop), on: self.thread, with: nil, waitUntilDone: false) + self.cancelInitialization.set(true) + } + + public func takeFrame(at timestamp: Double) -> Signal { + return Signal { subscriber in + let cancel = ValuePromise(false) + UniversalSoftwareVideoSourceThread.self.perform(#selector(UniversalSoftwareVideoSourceThread.takeFrame(_:)), on: self.thread, with: UniversalSoftwareVideoSourceTakeFrameParams(timestamp: timestamp, completion: { image in + subscriber.putNext(.image(image)) + subscriber.putCompletion() + }, cancel: cancel.get(), requiredDataIsNotLocallyAvailable: { + subscriber.putNext(.waitingForData) + }), waitUntilDone: false) + + return ActionDisposable { + cancel.set(true) + } + } + } +} diff --git a/Telegram-Mac/UpdateModalController.swift b/Telegram-Mac/UpdateModalController.swift new file mode 100644 index 0000000000..b250f45eba --- /dev/null +++ b/Telegram-Mac/UpdateModalController.swift @@ -0,0 +1,175 @@ +// +// UpdateModalController.swift +// Telegram +// +// Created by keepcoder on 10/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit + +private class UpdateTableItem : GeneralRowItem { + fileprivate let titleLayout: TextViewLayout + fileprivate let descLayout: TextViewLayout + + init(_ initialSize: NSSize) { + + titleLayout = TextViewLayout(.initialize(string: "Telegram 4.2", color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1) + titleLayout.measure(width: initialSize.width - 150) + + descLayout = TextViewLayout(.initialize(string: "You'll need to update Telegram to the latest version before you can use the app.", color: theme.colors.text, font: .normal(.text))) + descLayout.measure(width: initialSize.width - 50) + super.init(initialSize, height: 100 + descLayout.layoutSize.height, stableId: 0) + } + + override func viewClass() -> AnyClass { + return UpdateTableView.self + } +} + +private final class UpdateTableView : TableRowView { + private let titleView: TextView = TextView() + private let descView: TextView = TextView() + private let logoView: ImageView = ImageView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + logoView.image = theme.icons.confirmAppAccessoryIcon + logoView.setFrameSize(50,50) + addSubview(logoView) + addSubview(titleView) + addSubview(descView) + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? UpdateTableItem else {return} + + titleView.update(item.titleLayout) + descView.update(item.descLayout) + + needsLayout = true + } + + override func layout() { + super.layout() + logoView.setFrameOrigin(25, 10) + titleView.setFrameOrigin(logoView.frame.maxX + 10, floorToScreenPixels(backingScaleFactor, logoView.frame.minY + (logoView.frame.height - titleView.frame.height)/2)) + descView.setFrameOrigin(logoView.frame.minX, logoView.frame.maxY + 20) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class UpdateView : View { + private let headerView: View = View() + private let titleView = TextView() + let tableView = TableView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(headerView) + addSubview(tableView) + headerView.addSubview(titleView) + headerView.border = [.Bottom] + let title: TextViewLayout = TextViewLayout(.initialize(string: "Telegram Update", color: theme.colors.text, font: .medium(.title))) + title.measure(width: frameRect.width - 20) + titleView.update(title) + } + + func update() { + tableView.removeAll() + _ = tableView.addItem(item: UpdateTableItem(frame.size)) + } + + override func layout() { + super.layout() + headerView.frame = NSMakeRect(0, 0, frame.width, 50) + tableView.frame = NSMakeRect(0, 60, frame.width, frame.height - 60) + titleView.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +class UpdateModalController: ModalViewController { + + private let postbox: Postbox + private let network: Network + init(postbox: Postbox, network: Network) { + self.postbox = postbox + self.network = network + super.init(frame: NSMakeRect(0, 0, 320, 350)) + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: "Update Telegram", accept: { + #if APP_STORE + execute(inapp: inAppLink.external(link: "https://apps.apple.com/us/app/telegram/id747648890", false)) + #else + (NSApp.delegate as? AppDelegate)?.checkForUpdates("") + #endif + }, cancelTitle: L10n.modalCancel, cancel: { [weak self] in + self?.close() + }, drawBorder: true, height: 50, alignCancelLeft: true) + + } + + override func viewClass() -> AnyClass { + return UpdateView.self + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(320, min(size.height - 70, genericView.tableView.listHeight + 70)), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(320, min(contentSize.height - 70, genericView.tableView.listHeight + 70)), animated: animated) + } + } + + override var handleAllEvents: Bool { + return true + } + + private var genericView: UpdateView { + return self.view as! UpdateView + } + + override func escapeKeyAction() -> KeyHandlerResult { + return .invoked + } + + deinit { + } + + override func viewDidLoad() { + super.viewDidLoad() + readyOnce() + + genericView.update() + } + + override var dynamicSize: Bool { + return true + } + + override var closable: Bool { + return false + } + +} diff --git a/Telegram-Mac/UpdaterNotifySettings.swift b/Telegram-Mac/UpdaterNotifySettings.swift new file mode 100644 index 0000000000..b1f06d1a2f --- /dev/null +++ b/Telegram-Mac/UpdaterNotifySettings.swift @@ -0,0 +1,189 @@ +// +// LaunchSettings.swift +// Telegram +// +// Created by Mikhail Filimonov on 04/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import SyncCore +enum LaunchNavigation : PostboxCoding, Equatable { + case chat(PeerId, necessary: Bool) + case settings + + func encode(_ encoder: PostboxEncoder) { + switch self { + case let .chat(peerId, necessary): + encoder.encodeInt32(0, forKey: "t") + encoder.encodeInt32(peerId.namespace, forKey: "p.n") + encoder.encodeInt32(peerId.id, forKey: "p.id") + encoder.encodeBool(necessary, forKey: "n") + case .settings: + encoder.encodeInt32(1, forKey: "t") + } + } + + init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("t", orElse: 0) { + case 0: + let peerId = PeerId(namespace: decoder.decodeInt32ForKey("p.n", orElse: 0), id: decoder.decodeInt32ForKey("p.id", orElse: 0)) + self = .chat(peerId, necessary: decoder.decodeBoolForKey("n", orElse: false)) + case 1: + self = .settings + default: + fatalError() + } + } +} + +struct LaunchSettings: PreferencesEntry, Equatable { + + let navigation: LaunchNavigation? + let applyText: String? + let previousText: String? + let openAtLaunch: Bool + init(applyText: String?, previousText: String?, navigation: LaunchNavigation?, openAtLaunch: Bool) { + self.applyText = applyText + self.navigation = navigation + self.previousText = previousText + self.openAtLaunch = openAtLaunch + } + + init(decoder: PostboxDecoder) { + self.applyText = decoder.decodeOptionalStringForKey("at") + self.navigation = decoder.decodeObjectForKey("n1", decoder: { LaunchNavigation(decoder: $0) }) as? LaunchNavigation + self.previousText = decoder.decodeOptionalStringForKey("pt") + self.openAtLaunch = decoder.decodeBoolForKey("oat", orElse: true) + } + + func encode(_ encoder: PostboxEncoder) { + if let applyText = applyText { + encoder.encodeString(applyText, forKey: "at") + } else { + encoder.encodeNil(forKey: "at") + } + if let navigation = navigation { + encoder.encodeObject(navigation, forKey: "n1") + } else { + encoder.encodeNil(forKey: "n1") + } + if let previousText = previousText { + encoder.encodeString(previousText, forKey: "pt") + } else { + encoder.encodeNil(forKey: "pt") + } + encoder.encodeBool(self.openAtLaunch, forKey: "oat") + } + + func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? LaunchSettings { + return self == to + } else { + return false + } + } + + + func withUpdatedApplyText(_ applyText: String?) -> LaunchSettings { + return LaunchSettings(applyText: applyText, previousText: self.previousText, navigation: self.navigation, openAtLaunch: self.openAtLaunch) + } + func withUpdatedNavigation(_ navigation: LaunchNavigation?) -> LaunchSettings { + return LaunchSettings(applyText: self.applyText, previousText: self.previousText, navigation: navigation, openAtLaunch: self.openAtLaunch) + } + func withUpdatedPreviousText(_ previousText: String?) -> LaunchSettings { + return LaunchSettings(applyText: self.applyText, previousText: previousText, navigation: self.navigation, openAtLaunch: self.openAtLaunch) + } + func withUpdatedOpenAtLaunch(_ openAtLaunch: Bool) -> LaunchSettings { + return LaunchSettings(applyText: self.applyText, previousText: self.previousText, navigation: self.navigation, openAtLaunch: openAtLaunch) + } + + static var defaultSettings: LaunchSettings { + return LaunchSettings(applyText: nil, previousText: nil, navigation: nil, openAtLaunch: true) + } +} + + +/* + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.inAppNotificationSettings, { entry in + let currentSettings: InAppNotificationSettings + if let entry = entry as? InAppNotificationSettings { + currentSettings = entry + } else { + currentSettings = InAppNotificationSettings.defaultSettings + } + return f(currentSettings) + }) + } + */ + +func addAppUpdateText(_ postbox: Postbox, applyText: String?) -> Signal{ + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings, { pref in + let settings = pref as? LaunchSettings ?? LaunchSettings.defaultSettings + return settings.withUpdatedApplyText(applyText) + }) + } |> ignoreValues +} + + +func updateLaunchSettings(_ postbox: Postbox, _ f: @escaping(LaunchSettings)->LaunchSettings) -> Signal{ + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings, { pref in + let settings = pref as? LaunchSettings ?? LaunchSettings.defaultSettings + return f(settings) + }) + } |> ignoreValues |> deliverOnMainQueue +} + + +func appLaunchSettings(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> LaunchSettings in + return transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings) as? LaunchSettings ?? LaunchSettings.defaultSettings + } +} + + +func applyUpdateTextIfNeeded(_ postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> Void in + var applyText: String? + var previousText: String? + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings, { pref in + applyText = (pref as? LaunchSettings)?.applyText + previousText = (pref as? LaunchSettings)?.previousText + return (pref as? LaunchSettings)?.withUpdatedApplyText(nil) + }) + if let applyText = applyText { + var attributes: [MessageAttribute] = [] + + let index = applyText.firstIndex(of: "\n") + if let index = index { + let boldLine = MessageTextEntity(range: 0 ..< index.encodedOffset, type: .Bold) + attributes.append(TextEntitiesMessageAttribute(entities: [boldLine])) + + if let previousText = previousText, let prevIndex = previousText.firstIndex(of: "\n") { + let apply = String(applyText[index...]) + let previous = String(previousText[prevIndex...]) + if apply == previous { + return + } + } + } + + + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) + let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: Int32(Date().timeIntervalSince1970), flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: applyText, attributes: attributes, media: []) + _ = transaction.addMessages([message], location: .UpperHistoryBlock) + + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.launchSettings, { pref in + return (pref as? LaunchSettings)?.withUpdatedPreviousText(applyText) + }) + + } + } |> ignoreValues +} diff --git a/Telegram-Mac/UpgradedAccount.swift b/Telegram-Mac/UpgradedAccount.swift new file mode 100644 index 0000000000..c590ee578a --- /dev/null +++ b/Telegram-Mac/UpgradedAccount.swift @@ -0,0 +1,225 @@ +// +// UpgradedAccount.swift +// Telegram +// +// Created by Mikhail Filimonov on 08/03/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +private enum LegacyPreferencesKeyValues: Int32 { + case cacheStorageSettings = 1 + case localizationSettings = 2 + case proxySettings = 5 + + var key: ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: self.rawValue) + return key + } +} + +private enum UpgradedSharedDataKeyValues: Int32 { + case cacheStorageSettings = 2 + case localizationSettings = 3 + case proxySettings = 4 + + var key: ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: self.rawValue) + return key + } +} + + + +private enum LegacyApplicationSpecificPreferencesKeyValues: Int32 { + case inAppNotificationSettings = 0 + case baseAppSettings = 1 + case themeSettings = 22 + case autoNight = 26 + case additionalSettings = 15 + case voiceCallSettings = 34 + var key: ValueBoxKey { + return applicationSpecificPreferencesKey(self.rawValue) + } +} + +private enum UpgradedApplicationSpecificSharedDataKeyValues: Int32 { + case inAppNotificationSettings = 0 + case baseAppSettings = 1 + case themeSettings = 22 + case autoNight = 26 + case additionalSettings = 15 + case voiceCallSettings = 34 + var key: ValueBoxKey { + return applicationSpecificSharedDataKey(self.rawValue) + } +} + +private let preferencesKeyMapping: [LegacyPreferencesKeyValues: UpgradedSharedDataKeyValues] = [ + .cacheStorageSettings: .cacheStorageSettings, + .localizationSettings: .localizationSettings, + .proxySettings: .proxySettings +] + + + +private let applicationSpecificPreferencesKeyMapping: [LegacyApplicationSpecificPreferencesKeyValues: UpgradedApplicationSpecificSharedDataKeyValues] = [ + .inAppNotificationSettings: .inAppNotificationSettings, + .themeSettings: .themeSettings +] + +private func upgradedSharedDataValue(_ value: PreferencesEntry?) -> PreferencesEntry? { + return value +} + + +public func upgradedAccounts(accountManager: AccountManager, rootPath: String, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { + return accountManager.transaction { transaction -> (Int32?, AccountRecordId?) in + return (transaction.getVersion(), transaction.getCurrent()?.0) + } + |> mapToSignal { version, currentId -> Signal in + guard let version = version else { + return accountManager.transaction { transaction -> Void in + transaction.setVersion(4) + } + |> ignoreValues + |> mapToSignal { _ -> Signal in + return .complete() + } + } + var signal: Signal = .complete() + if version < 1 { + if let currentId = currentId { + let upgradePreferences = accountPreferenceEntries(rootPath: rootPath, id: currentId, keys: Set(preferencesKeyMapping.keys.map({ $0.key }) + applicationSpecificPreferencesKeyMapping.keys.map({ $0.key })), encryptionParameters: encryptionParameters) + |> mapToSignal { result -> Signal in + switch result { + case let .progress(progress): + return .single(progress) + case let .result(path, values): + return accountManager.transaction { transaction -> Void in + for (key, value) in values { + var upgradedKey: ValueBoxKey? + for (k, v) in preferencesKeyMapping { + if k.key == key { + upgradedKey = v.key + break + } + } + for (k, v) in applicationSpecificPreferencesKeyMapping { + if k.key == key { + upgradedKey = v.key + break + } + } + if let upgradedKey = upgradedKey { + transaction.updateSharedData(upgradedKey, { _ in + return upgradedSharedDataValue(value) + }) + } + } + + transaction.setVersion(1) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } + signal = signal |> then(upgradePreferences) + } else { + let upgradePreferences = accountManager.transaction { transaction -> Void in + transaction.setVersion(1) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + signal = signal |> then(upgradePreferences) + } + } + if version < 2 { + if let currentId = currentId { + let upgradeNotices = accountNoticeEntries(rootPath: rootPath, id: currentId, encryptionParameters: encryptionParameters) + |> mapToSignal { result -> Signal in + switch result { + case let .progress(progress): + return .single(progress) + case let .result(path, values): + return accountManager.transaction { transaction -> Void in + for (key, value) in values { + transaction.setNotice(NoticeEntryKey(namespace: ValueBoxKey(length: 0), key: key), value) + } + + transaction.setVersion(2) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } + signal = signal |> then(upgradeNotices) + } else { + let upgradeNotices = accountManager.transaction { transaction -> Void in + transaction.setVersion(2) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + signal = signal |> then(upgradeNotices) + } + + let upgradeSortOrder = accountManager.transaction { transaction -> Void in + var index: Int32 = 0 + for record in transaction.getRecords() { + transaction.updateRecord(record.id, { _ in + return AccountRecord(id: record.id, attributes: record.attributes + [AccountSortOrderAttribute(order: index)], temporarySessionId: record.temporarySessionId) + }) + index += 1 + } + } + |> mapToSignal { _ -> Signal in + return .complete() + } + signal = signal |> then(upgradeSortOrder) + } + if version < 3 { + if let currentId = currentId { + let upgradeAccessChallengeData = accountLegacyAccessChallengeData(rootPath: rootPath, id: currentId, encryptionParameters: encryptionParameters) + |> mapToSignal { result -> Signal in + switch result { + case let .progress(progress): + return .single(progress) + case let .result(accessChallengeData): + return accountManager.transaction { transaction -> Void in + if case .none = transaction.getAccessChallengeData() { + transaction.setAccessChallengeData(accessChallengeData) + } + + transaction.setVersion(3) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + } + } + signal = signal |> then(upgradeAccessChallengeData) + } else { + let upgradeAccessChallengeData = accountManager.transaction { transaction -> Void in + transaction.setVersion(3) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + signal = signal |> then(upgradeAccessChallengeData) + } + } + return signal + } +} diff --git a/Telegram-Mac/UserInfoEntries.swift b/Telegram-Mac/UserInfoEntries.swift index ea7e5a651f..29159981bc 100644 --- a/Telegram-Mac/UserInfoEntries.swift +++ b/Telegram-Mac/UserInfoEntries.swift @@ -7,9 +7,10 @@ // import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox import TGUIKit @@ -46,10 +47,11 @@ final class UserInfoState : PeerInfoState { let editingState: UserInfoEditingState? let savingData: Bool - init(editingState: UserInfoEditingState?, savingData: Bool) { self.editingState = editingState self.savingData = savingData + + } override init() { @@ -93,22 +95,71 @@ class UserInfoArguments : PeerInfoArguments { private let startSecretChatDisposable = MetaDisposable() private let updatePeerNameDisposable = MetaDisposable() private let deletePeerContactDisposable = MetaDisposable() + private let callDisposable = MetaDisposable() func shareContact() { - shareDisposable.set((account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let account = self?.account { - showModal(with: ShareModalController(ShareContactObject(account, user: peer as! TelegramUser)), for: mainWindow) + let context = self.context + let peer = context.account.postbox.peerView(id: peerId) |> take(1) |> map { + return peerViewMainPeer($0) + } |> deliverOnMainQueue + + + + shareDisposable.set(peer.start(next: { [weak self] peer in + if let context = self?.context, let peer = peer as? TelegramUser { + showModal(with: ShareModalController(ShareContactObject(context, user: peer)), for: context.window) } })) } + override init(context: AccountContext, peerId: PeerId, state: PeerInfoState, isAd: Bool, pushViewController: @escaping (ViewController) -> Void, pullNavigation: @escaping () -> NavigationViewController?, mediaController: @escaping()->PeerMediaController?) { + super.init(context: context, peerId: peerId, state: state, isAd: isAd, pushViewController: pushViewController, pullNavigation: pullNavigation, mediaController: mediaController) + + let updateState:((UserInfoState)->UserInfoState)->Void = { [weak self] f in + self?.updateState(f) + } + + } + + func shareMyInfo() { + + + let context = self.context + let peerId = self.peerId + + + let peer = context.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } |> deliverOnMainQueue + + _ = peer.start(next: { [weak self] peer in + if let peer = peer { + confirm(for: mainWindow, information: L10n.peerInfoConfirmShareInfo(peer.displayTitle), successHandler: { [weak self] _ in + let signal: Signal = context.account.postbox.loadedPeerWithId(context.peerId) |> map { $0 as! TelegramUser } |> mapToSignal { peer in + let signal = Sender.enqueue(message: EnqueueMessage.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: peer.phone ?? "", peerId: peer.id, vCardData: nil)), replyToMessageId: nil, localGroupingKey: nil), context: context, peerId: peerId) + return signal |> map { _ in} + } + self?.shareDisposable.set(showModalProgress(signal: signal, for: mainWindow).start()) + }) + } + }) + + + } + func addContact() { - shareDisposable.set(addContactPeerInteractively(account: account, peerId: peerId, phone: nil).start()) + let context = self.context + let peerView = context.account.postbox.peerView(id: self.peerId) |> take(1) |> deliverOnMainQueue + _ = peerView.start(next: { peerView in + if let peer = peerViewMainPeer(peerView) { + showModal(with: NewContactController(context: context, peerId: peer.id), for: context.window) + } + }) } - override func updateEditable(_ editable: Bool, peerView: PeerView) { + override func updateEditable(_ editable:Bool, peerView:PeerView, controller: PeerInfoController) -> Bool { - let account = self.account + let context = self.context let peerId = self.peerId let updateState:((UserInfoState)->UserInfoState)->Void = { [weak self] f in self?.updateState(f) @@ -132,10 +183,28 @@ class UserInfoArguments : PeerInfoArguments { } } - let updateNames: Signal + if let firstName = updateValues.firstName, firstName.isEmpty { + controller.genericView.item(stableId: IntPeerInfoEntryStableId(value: 1).hashValue)?.view?.shakeView() + return false + } + + + if updateValues.firstName != nil || updateValues.lastName != nil { + updateState { state in + return state.withUpdatedSavingData(true) + } + } else { + updateState { state in + return state.withUpdatedEditingState(nil) + } + } + + - if let firstName = updateValues.firstName, let lastName = updateValues.lastName { - updateNames = showModalProgress(signal: updateContactName(account: account, peerId: peerId, firstName: firstName, lastName: lastName) |> mapError {_ in} |> deliverOnMainQueue, for: mainWindow) + let updateNames: Signal + + if let firstName = updateValues.firstName { + updateNames = showModalProgress(signal: updateContactName(account: context.account, peerId: peerId, firstName: firstName, lastName: updateValues.lastName ?? "") |> deliverOnMainQueue, for: mainWindow) } else { updateNames = .complete() } @@ -152,37 +221,91 @@ class UserInfoArguments : PeerInfoArguments { } - + return true } + func sendMessage() { + self.peerChat(self.peerId) + } + func call() { + let context = self.context + let peer = context.account.postbox.peerView(id: peerId) |> take(1) |> map { + return peerViewMainPeer($0)?.id + } |> filter { $0 != nil } |> map { $0! } + + let call = peer |> mapToSignal { + phoneCall(account: context.account, sharedContext: context.sharedContext, peerId: $0) + } |> deliverOnMainQueue + + self.callDisposable.set(call.start(next: { result in + applyUIPCallResult(context.sharedContext, result) + })) + } - func startSecretChat() { + func botAddToGroup() { + let context = self.context + let peerId = self.peerId - let signal = account.postbox.modify { [weak self] modifier -> (Peer?, Account?) in - - if let peerId = self?.peerId, let peer = modifier.getPeer(peerId), let account = self?.account { - return (peer, account) + let result = selectModalPeers(context: context, title: L10n.selectPeersTitleSelectChat, behavior: SelectChatsBehavior(limit: 1), confirmation: { peerIds -> Signal in + if let peerId = peerIds.first { + return context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue |> mapToSignal { peer -> Signal in + return confirmSignal(for: context.window, information: L10n.confirmAddBotToGroup(peer.displayTitle)) + } + } + return .single(false) + }) |> deliverOnMainQueue |> filter {$0.first != nil} |> map {$0.first!} |> mapToSignal { groupId -> Signal in + if groupId.namespace == Namespaces.Peer.CloudGroup { + return showModalProgress(signal: addGroupMember(account: context.account, peerId: groupId, memberId: peerId), for: context.window) |> `catch` {_ in .complete()} |> map {groupId} } else { - return (nil, nil) + return showModalProgress(signal: context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: groupId, memberId: peerId), for: context.window) |> map { groupId } } + } + + _ = result.start(next: { [weak self] peerId in + self?.peerChat(peerId) + }) + } + func botShare(_ botName: String) { + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/\(botName)")), for: mainWindow) + } + func botSettings() { + _ = Sender.enqueue(input: ChatTextInputState(inputText: "/settings"), context: context, peerId: peerId, replyId: nil).start() + pullNavigation()?.back() + } + func botHelp() { + _ = Sender.enqueue(input: ChatTextInputState(inputText: "/help"), context: context, peerId: peerId, replyId: nil).start() + pullNavigation()?.back() + } + + func botPrivacy() { + _ = Sender.enqueue(input: ChatTextInputState(inputText: "/privacy"), context: context, peerId: peerId, replyId: nil).start() + pullNavigation()?.back() + } + + func startSecretChat() { + let context = self.context + let peerId = self.peerId + let signal = context.account.postbox.transaction { transaction -> Peer? in - } |> deliverOnMainQueue |> mapToSignal { peer, account -> Signal in - if let peer = peer, let account = account { - let confirm = confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmStartSecretChat(peer.displayTitle))) - return confirm |> filter {$0} |> mapToSignal { (_) -> Signal in - return showModalProgress(signal: createSecretChat(account: account, peerId: peer.id), for: mainWindow) |> mapError {_ in} - } - } else { - return .complete() + return transaction.getPeer(peerId) + + } |> deliverOnMainQueue |> mapToSignal { peer -> Signal in + if let peer = peer { + let confirm = confirmSignal(for: context.window, header: L10n.peerInfoConfirmSecretChatHeader, information: L10n.peerInfoConfirmStartSecretChat(peer.displayTitle), okTitle: L10n.peerInfoConfirmSecretChatOK) + return confirm |> filter {$0} |> mapToSignal { (_) -> Signal in + return showModalProgress(signal: createSecretChat(account: context.account, peerId: peer.id) |> `catch` { _ in return .complete()}, for: mainWindow) } - } |> deliverOnMainQueue + } else { + return .complete() + } + } |> deliverOnMainQueue startSecretChatDisposable.set(signal.start(next: { [weak self] peerId in if let strongSelf = self { - strongSelf.pushViewController(ChatController(account: strongSelf.account, peerId: peerId)) + strongSelf.pushViewController(ChatController(context: strongSelf.context, chatLocation: .peer(peerId))) } })) } @@ -203,28 +326,53 @@ class UserInfoArguments : PeerInfoArguments { } } - func updateBlocked(_ blocked:Bool) { - blockDisposable.set(requestUpdatePeerIsBlocked(account: account, peerId: peerId, isBlocked: blocked).start()) + func updateBlocked(peer: Peer,_ blocked:Bool, _ isBot: Bool) { + let context = self.context + if blocked { + let signal = showModalProgress(signal: context.blockedPeersContext.add(peerId: peer.id) |> deliverOnMainQueue, for: context.window) + blockDisposable.set(signal.start(error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }, completed: { + + })) + } else { + let signal = showModalProgress(signal: context.blockedPeersContext.remove(peerId: peer.id) |> deliverOnMainQueue, for: context.window) + blockDisposable.set(signal.start(error: { error in + switch error { + case .generic: + alert(for: context.window, info: L10n.unknownError) + } + }, completed: { + + })) + } + + if !blocked && isBot { + pushViewController(ChatController(context: context, chatLocation: .peer(peer.id), initialAction: ChatInitialAction.start(parameter: "", behavior: .automatic))) + } + } func deleteContact() { - let account = self.account + let context = self.context let peerId = self.peerId - deletePeerContactDisposable.set((confirmSignal(for: mainWindow, header: appName, information: tr(.peerInfoConfirmDeleteContact)) + deletePeerContactDisposable.set((confirmSignal(for: context.window, information: tr(L10n.peerInfoConfirmDeleteContact)) |> filter {$0} |> mapToSignal { _ in - showModalProgress(signal: deleteContactPeerInteractively(account: account, peerId: peerId) |> deliverOnMainQueue, for: mainWindow) + showModalProgress(signal: deleteContactPeerInteractively(account: context.account, peerId: peerId) |> deliverOnMainQueue, for: context.window) }).start(completed: { [weak self] in self?.pullNavigation()?.back() })) } func encryptionKey() { - pushViewController(SecretChatKeyViewController(account: account, peerId: peerId)) + pushViewController(SecretChatKeyViewController(context, peerId: peerId)) } - func groupInCommon() -> Void { - pushViewController(GroupsInCommonViewController(account: account, peerId: peerId)) + func groupInCommon(_ peerId: PeerId) -> Void { } deinit { @@ -233,6 +381,7 @@ class UserInfoArguments : PeerInfoArguments { startSecretChatDisposable.dispose() updatePeerNameDisposable.dispose() deletePeerContactDisposable.dispose() + callDisposable.dispose() } } @@ -240,24 +389,66 @@ class UserInfoArguments : PeerInfoArguments { enum UserInfoEntry: PeerInfoEntry { - case info(sectionId:Int, PeerView, editable:Bool) - case about(sectionId:Int, text: String) - case bio(sectionId:Int, text: String) - case phoneNumber(sectionId:Int, index: Int, value: PhoneNumberWithLabel) - case userName(sectionId:Int, value: String) - case sendMessage(sectionId:Int) - case shareContact(sectionId:Int) - case addContact(sectionId:Int) - case startSecretChat(sectionId:Int) - case sharedMedia(sectionId:Int) - case notifications(sectionId:Int, settings: PeerNotificationSettings?) - case groupInCommon(sectionId:Int, count:Int) - case block(sectionId:Int, Bool) - case deleteChat(sectionId: Int) - case deleteContact(sectionId: Int) - case encryptionKey(sectionId: Int) + case info(sectionId:Int, peerView: PeerView, editable:Bool, viewType: GeneralViewType) + case setFirstName(sectionId:Int, text: String, viewType: GeneralViewType) + case setLastName(sectionId:Int, text: String, viewType: GeneralViewType) + case about(sectionId:Int, text: String, viewType: GeneralViewType) + case bio(sectionId:Int, text: String, viewType: GeneralViewType) + case scam(sectionId:Int, text: String, viewType: GeneralViewType) + case phoneNumber(sectionId:Int, index: Int, value: PhoneNumberWithLabel, canCopy: Bool, viewType: GeneralViewType) + case userName(sectionId:Int, value: String, viewType: GeneralViewType) + case sendMessage(sectionId:Int, viewType: GeneralViewType) + case shareContact(sectionId:Int, viewType: GeneralViewType) + case shareMyInfo(sectionId:Int, viewType: GeneralViewType) + case addContact(sectionId:Int, viewType: GeneralViewType) + case botAddToGroup(sectionId: Int, viewType: GeneralViewType) + case botShare(sectionId: Int, name: String, viewType: GeneralViewType) + case botHelp(sectionId: Int, viewType: GeneralViewType) + case botSettings(sectionId: Int, viewType: GeneralViewType) + case botPrivacy(sectionId: Int, viewType: GeneralViewType) + case startSecretChat(sectionId:Int, viewType: GeneralViewType) + case sharedMedia(sectionId:Int, viewType: GeneralViewType) + case notifications(sectionId:Int, settings: PeerNotificationSettings?, viewType: GeneralViewType) + case groupInCommon(sectionId:Int, count:Int, peerId: PeerId, viewType: GeneralViewType) + case block(sectionId:Int, peer: Peer, blocked: Bool, isBot: Bool, viewType: GeneralViewType) + case deleteChat(sectionId: Int, viewType: GeneralViewType) + case deleteContact(sectionId: Int, viewType: GeneralViewType) + case encryptionKey(sectionId: Int, viewType: GeneralViewType) + case media(sectionId: Int, controller: PeerMediaController, isVisible: Bool, viewType: GeneralViewType) case section(sectionId:Int) + func withUpdatedViewType(_ viewType: GeneralViewType) -> UserInfoEntry { + switch self { + case let .info(sectionId, peerView, editable, _): return .info(sectionId: sectionId, peerView: peerView, editable: editable, viewType: viewType) + case let .setFirstName(sectionId, text, _): return .setFirstName(sectionId: sectionId, text: text, viewType: viewType) + case let .setLastName(sectionId, text, _): return .setLastName(sectionId: sectionId, text: text, viewType: viewType) + case let .about(sectionId, text, _): return .about(sectionId: sectionId, text: text, viewType: viewType) + case let .bio(sectionId, text, _): return .bio(sectionId: sectionId, text: text, viewType: viewType) + case let .scam(sectionId, text, _): return .scam(sectionId: sectionId, text: text, viewType: viewType) + case let .phoneNumber(sectionId, index, value, canCopy, _): return .phoneNumber(sectionId: sectionId, index: index, value: value, canCopy: canCopy, viewType: viewType) + case let .userName(sectionId, value: String, _): return .userName(sectionId: sectionId, value: String, viewType: viewType) + case let .sendMessage(sectionId, _): return .sendMessage(sectionId: sectionId, viewType: viewType) + case let .shareContact(sectionId, _): return .shareContact(sectionId: sectionId, viewType: viewType) + case let .shareMyInfo(sectionId, _): return .shareMyInfo(sectionId: sectionId, viewType: viewType) + case let .addContact(sectionId, _): return .addContact(sectionId: sectionId, viewType: viewType) + case let .botAddToGroup(sectionId, _): return .botAddToGroup(sectionId: sectionId, viewType: viewType) + case let .botShare(sectionId, name, _): return .botShare(sectionId: sectionId, name: name, viewType: viewType) + case let .botHelp(sectionId, _): return .botHelp(sectionId: sectionId, viewType: viewType) + case let .botSettings(sectionId, _): return .botSettings(sectionId: sectionId, viewType: viewType) + case let .botPrivacy(sectionId, _): return .botPrivacy(sectionId: sectionId, viewType: viewType) + case let .startSecretChat(sectionId, _): return .startSecretChat(sectionId: sectionId, viewType: viewType) + case let .sharedMedia(sectionId, _): return .sharedMedia(sectionId: sectionId, viewType: viewType) + case let .notifications(sectionId, settings, _): return .notifications(sectionId: sectionId, settings: settings, viewType: viewType) + case let .groupInCommon(sectionId, count, peerId, _): return .groupInCommon(sectionId: sectionId, count: count, peerId: peerId, viewType: viewType) + case let .block(sectionId, peer, blocked, isBot, _): return .block(sectionId: sectionId, peer: peer, blocked: blocked, isBot: isBot, viewType: viewType) + case let .deleteChat(sectionId, _): return .deleteChat(sectionId: sectionId, viewType: viewType) + case let .deleteContact(sectionId, _): return .deleteContact(sectionId: sectionId, viewType: viewType) + case let .encryptionKey(sectionId, _): return .encryptionKey(sectionId: sectionId, viewType: viewType) + case let .media(sectionId, controller, isVisible, _): return .media(sectionId: sectionId, controller: controller, isVisible: isVisible, viewType: viewType) + case .section: return self + } + } + var stableId: PeerInfoEntryStableId { return IntPeerInfoEntryStableId(value: self.stableIndex) } @@ -268,13 +459,16 @@ enum UserInfoEntry: PeerInfoEntry { } switch self { - case let .info(lhsSectionId, lhsPeerView, lhsEditable): + case let .info(lhsSectionId, lhsPeerView, lhsEditable, lhsViewType): switch entry { - case let .info(rhsSectionId, rhsPeerView, rhsEditable): + case let .info(rhsSectionId, rhsPeerView, rhsEditable, rhsViewType): if lhsSectionId != rhsSectionId { return false } + if lhsViewType != rhsViewType { + return false + } if lhsEditable != rhsEditable { return false @@ -282,19 +476,26 @@ enum UserInfoEntry: PeerInfoEntry { let lhsPeer = peerViewMainPeer(lhsPeerView) let lhsCachedData = lhsPeerView.cachedData - + let lhsNotificationSettings = lhsPeerView.notificationSettings + let rhsPeer = peerViewMainPeer(rhsPeerView) let rhsCachedData = rhsPeerView.cachedData - + let rhsNotificationSettings = rhsPeerView.notificationSettings if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { if !lhsPeer.isEqual(rhsPeer) { return false } - } else if (lhsPeer == nil) != (rhsPeer != nil) { + } else if (lhsPeer != nil) != (rhsPeer != nil) { return false } - + if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings { + if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) { + return false + } + } else if (lhsNotificationSettings == nil) != (rhsNotificationSettings == nil) { + return false + } if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData { if !lhsCachedData.isEqual(to: rhsCachedData) { @@ -307,115 +508,182 @@ enum UserInfoEntry: PeerInfoEntry { default: return false } - case let .about(sectionId, text): + case let .setFirstName(sectionId, text, viewType): switch entry { - case .about(sectionId, text): + case .setFirstName(sectionId, text, viewType): return true default: return false } - case let .bio(sectionId, text): + case let .setLastName(sectionId, text, viewType): switch entry { - case .bio(sectionId, text): + case .setLastName(sectionId, text, viewType): return true default: return false } - case let .phoneNumber(lhsSectionId, lhsIndex, lhsValue): + case let .about(sectionId, text, viewType): switch entry { - case let .phoneNumber(rhsSectionId, rhsIndex, rhsValue) where lhsIndex == rhsIndex && lhsValue == rhsValue && lhsSectionId == rhsSectionId: + case .about(sectionId, text, viewType): return true default: return false } - case let .userName(sectionId, value): + case let .bio(sectionId, text, viewType): switch entry { - case .userName(sectionId, value): + case .bio(sectionId, text, viewType): return true default: return false } - case let .sendMessage(sectionId): + case let .scam(sectionId, text, viewType): switch entry { - case .sendMessage(sectionId): + case .scam(sectionId, text, viewType): return true default: return false } - case let .shareContact(sectionId): + case let .phoneNumber(sectionid, index, value, canCopy, viewType): switch entry { - case .shareContact(sectionId): + case .phoneNumber(sectionid, index, value, canCopy, viewType): return true default: return false } - case let .addContact(sectionId): + case let .userName(sectionId, value, viewType): switch entry { - case .addContact(sectionId): + case .userName(sectionId, value, viewType): return true default: return false } - case let .startSecretChat(sectionId): + case let .sendMessage(sectionId, viewType): switch entry { - case .startSecretChat(sectionId): + case .sendMessage(sectionId, viewType): return true default: return false } - case let .sharedMedia(sectionId): + case let .botAddToGroup(sectionId, viewType): switch entry { - case .sharedMedia(sectionId): + case .botAddToGroup(sectionId, viewType): return true default: return false } - case let .notifications(lhsSectionId, lhsSettings): + case let .botShare(sectionId, botName, viewType): switch entry { - case let .notifications(rhsSectionId, rhsSettings): - if lhsSectionId != rhsSectionId { - return false - } + case .botShare(sectionId, botName, viewType): + return true + default: + return false + } + case let .botHelp(sectionId, viewType): + switch entry { + case .botHelp(sectionId, viewType): + return true + default: + return false + } + case let .botSettings(sectionId, viewType): + switch entry { + case .botSettings(sectionId, viewType): + return true + default: + return false + } + case let .botPrivacy(sectionId, viewType): + if case .botPrivacy(sectionId, viewType) = entry { + return true + } else { + return false + } + case let .shareContact(sectionId, viewType): + switch entry { + case .shareContact(sectionId, viewType): + return true + default: + return false + } + case let .shareMyInfo(sectionId, viewType): + switch entry { + case .shareMyInfo(sectionId, viewType): + return true + default: + return false + } + case let .addContact(sectionId, viewType): + switch entry { + case .addContact(sectionId, viewType): + return true + default: + return false + } + case let .startSecretChat(sectionId, viewType): + switch entry { + case .startSecretChat(sectionId, viewType): + return true + default: + return false + } + case let .sharedMedia(sectionId, viewType): + switch entry { + case .sharedMedia(sectionId, viewType): + return true + default: + return false + } + case let .notifications(sectionId, lhsSettings, viewType): + switch entry { + case .notifications(sectionId, let rhsSettings, viewType): if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings { return lhsSettings.isEqual(to: rhsSettings) } else if (lhsSettings != nil) != (rhsSettings != nil) { return false + } else { + return true } - return true default: return false } - case let .block(sectionId, isBlocked): + case let .block(sectionId, lhsPeer, isBlocked, isBot, viewType): + switch entry { + case .block(sectionId, let rhsPeer, isBlocked, isBot, viewType): + return lhsPeer.isEqual(rhsPeer) + default: + return false + } + case let .groupInCommon(sectionId, count, peerId, viewType): switch entry { - case .block(sectionId, isBlocked): + case .groupInCommon(sectionId, count, peerId, viewType): return true default: return false } - case let .groupInCommon(sectionId, count): + case let .deleteChat(sectionId, viewType): switch entry { - case .groupInCommon(sectionId, count): + case .deleteChat(sectionId, viewType): return true default: return false } - case let .deleteChat(sectionId): + case let .deleteContact(sectionId, viewType): switch entry { - case .deleteChat(sectionId): + case .deleteContact(sectionId, viewType): return true default: return false } - case let .deleteContact(sectionId): + case let .encryptionKey(sectionId, viewType): switch entry { - case .deleteContact(sectionId): + case .encryptionKey(sectionId, viewType): return true default: return false } - case let .encryptionKey(sectionId): + case let .media(sectionId, _, isVisible, viewType): switch entry { - case .encryptionKey(sectionId): + case .media(sectionId, _, isVisible, viewType): return true default: return false @@ -434,36 +702,56 @@ enum UserInfoEntry: PeerInfoEntry { switch self { case .info: return 0 - case .about: + case .setFirstName: return 1 - case .phoneNumber: + case .setLastName: return 2 - case .bio: + case .scam: return 3 - case .userName: + case .about: return 4 - case .sendMessage: + case .bio: return 5 - case .shareContact: + case .phoneNumber: return 6 - case .addContact: + case .userName: return 7 - case .startSecretChat: + case .sendMessage: return 8 - case .sharedMedia: + case .botAddToGroup: return 9 - case .notifications: + case .botShare: return 10 - case .encryptionKey: + case .botSettings: return 11 - case .groupInCommon: + case .botHelp: return 12 - case .block: + case .botPrivacy: return 13 - case .deleteChat: + case .shareContact: return 14 - case .deleteContact: + case .shareMyInfo: return 15 + case .addContact: + return 16 + case .startSecretChat: + return 17 + case .sharedMedia: + return 18 + case .notifications: + return 19 + case .encryptionKey: + return 20 + case .groupInCommon: + return 21 + case .block: + return 22 + case .deleteChat: + return 23 + case .deleteContact: + return 24 + case .media: + return 25 case let .section(id): return (id + 1) * 1000 - id } @@ -471,37 +759,57 @@ enum UserInfoEntry: PeerInfoEntry { private var sortIndex:Int { switch self { - case let .info(sectionId, _, _): + case let .info(sectionId, _, _, _): + return (sectionId * 1000) + stableIndex + case let .setFirstName(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .setLastName(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .about(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .bio(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .about(sectionId, _): + case let .phoneNumber(sectionId, _, _, _, _): return (sectionId * 1000) + stableIndex - case let .bio(sectionId, _): + case let .userName(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .phoneNumber(sectionId, _, _): + case let .scam(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .userName(sectionId, _): + case let .sendMessage(sectionId, _): return (sectionId * 1000) + stableIndex - case let .sendMessage(sectionId): + case let .botAddToGroup(sectionId, _): return (sectionId * 1000) + stableIndex - case let .shareContact(sectionId): + case let .botShare(sectionId, _, _): return (sectionId * 1000) + stableIndex - case let .addContact(sectionId): + case let .botSettings(sectionId, _): return (sectionId * 1000) + stableIndex - case let .startSecretChat(sectionId): + case let .botPrivacy(sectionId, _): return (sectionId * 1000) + stableIndex - case let .sharedMedia(sectionId): + case let .botHelp(sectionId, _): return (sectionId * 1000) + stableIndex - case let .groupInCommon(sectionId, _): + case let .shareContact(sectionId, _): return (sectionId * 1000) + stableIndex - case let .notifications(sectionId, _): + case let .shareMyInfo(sectionId, _): return (sectionId * 1000) + stableIndex - case let .encryptionKey(sectionId): + case let .addContact(sectionId, _): return (sectionId * 1000) + stableIndex - case let .block(sectionId, _): + case let .startSecretChat(sectionId, _): return (sectionId * 1000) + stableIndex - case let .deleteChat(sectionId): + case let .sharedMedia(sectionId, _): return (sectionId * 1000) + stableIndex - case let .deleteContact(sectionId): + case let .groupInCommon(sectionId, _, _, _): + return (sectionId * 1000) + stableIndex + case let .notifications(sectionId, _, _): + return (sectionId * 1000) + stableIndex + case let .encryptionKey(sectionId, _): + return (sectionId * 1000) + stableIndex + case let .block(sectionId, _, _, _, _): + return (sectionId * 1000) + stableIndex + case let .deleteChat(sectionId, _): + return (sectionId * 1000) + stableIndex + case let .deleteContact(sectionId, _): + return (sectionId * 1000) + stableIndex + case let .media(sectionId, _, _, _): return (sectionId * 1000) + stableIndex case let .section(id): return (id + 1) * 1000 - id @@ -514,7 +822,7 @@ enum UserInfoEntry: PeerInfoEntry { return false } - return self.sortIndex > other.sortIndex + return self.sortIndex < other.sortIndex } @@ -522,78 +830,114 @@ enum UserInfoEntry: PeerInfoEntry { func item( initialSize:NSSize, arguments:PeerInfoArguments) -> TableRowItem { let arguments = arguments as! UserInfoArguments - let state = arguments.state as! UserInfoState + var state:UserInfoState { + return arguments.state as! UserInfoState + } + switch self { - case let .info(_, peerView, editable): - return PeerInfoHeaderItem(initialSize, stableId:stableId.hashValue, account:arguments.account, peerView:peerView, editable: editable, updatingPhotoState: nil, firstNameEditableText: state.editingState?.editingFirstName, lastNameEditableText: state.editingState?.editingLastName, textChangeHandler: { firstName, lastName in - arguments.updateEditingNames(firstName: firstName, lastName: lastName) - }) - case let .about(_, text): - return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label:tr(.peerInfoAbout), text:text, account: arguments.account, detectLinks:true) - case let .bio(_, text): - return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label:tr(.peerInfoBio), text:text, account: arguments.account, detectLinks:false) - case let .phoneNumber(_, _, value): - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:value.label, text:formatPhoneNumber(value.number), account: arguments.account) - case let .userName(_, value): - return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:tr(.peerInfoUsername), text:"@\(value)", account: arguments.account) - case .sendMessage: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSendMessage), nameStyle: blueActionButton, type: .none, action: { + case let .info(_, peerView, editable, viewType): + return PeerInfoHeadItem(initialSize, stableId:stableId.hashValue, context: arguments.context, arguments: arguments, peerView: peerView, viewType: viewType, editing: editable) + case let .setFirstName(_, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoFirstNamePlaceholder, filter: { $0 }, updated: { + arguments.updateEditingNames(firstName: $0, lastName: state.editingState?.editingLastName) + }, limit: 255) + case let .setLastName(_, text, viewType): + return InputDataRowItem(initialSize, stableId: stableId.hashValue, mode: .plain, error: nil, viewType: viewType, currentText: text, placeholder: nil, inputPlaceholder: L10n.peerInfoLastNamePlaceholder, filter: { $0 }, updated: { + arguments.updateEditingNames(firstName: state.editingState?.editingFirstName, lastName: $0) + }, limit: 255) + case let .about(_, text, viewType): + return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label: L10n.peerInfoAbout, text:text, context: arguments.context, viewType: viewType, detectLinks:true, openInfo: { peerId, toChat, postId, _ in + if toChat { + arguments.peerChat(peerId, postId: postId) + } else { + arguments.peerInfo(peerId) + } + }, hashtag: arguments.context.sharedContext.bindings.globalSearch) + case let .bio(_, text, viewType): + return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label: L10n.peerInfoBio, text:text, context: arguments.context, viewType: viewType, detectLinks:false) + case let .phoneNumber(_, _, value, canCopy, viewType): + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label:value.label, text: value.number, context: arguments.context, viewType: viewType, canCopy: canCopy) + case let .userName(_, value, viewType): + return TextAndLabelItem(initialSize, stableId: stableId.hashValue, label: L10n.peerInfoUsername, text:"@\(value)", context: arguments.context, viewType: viewType) + case let .scam(_, text, viewType): + return TextAndLabelItem(initialSize, stableId:stableId.hashValue, label: L10n.peerInfoScam, labelColor: theme.colors.redUI, text: text, context: arguments.context, viewType: viewType, detectLinks:false) + case let .sendMessage(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSendMessage, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.peerChat(arguments.peerId) }) - case .shareContact: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoShareContact), nameStyle: blueActionButton, type: .none, action: { + case let .botAddToGroup(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoBotAddToGroup, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.botAddToGroup() + }) + case let .botShare(_, name, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoBotShare, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.botShare(name) + }) + case let .botSettings(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoBotSettings, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.botSettings() + }) + case let .botHelp(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoBotHelp, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.botHelp() + }) + case let .botPrivacy(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoBotPrivacy, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.botPrivacy() + }) + case let .shareContact(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoShareContact, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.shareContact() }) - case .addContact: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoAddContact), nameStyle: blueActionButton, type: .none, action: { + case let .shareMyInfo(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoShareMyInfo, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { + arguments.shareMyInfo() + }) + case let .addContact(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoAddContact, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.addContact() }) - case .startSecretChat: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoStartSecretChat), nameStyle: blueActionButton, type: .none, action: { + case let .startSecretChat(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoStartSecretChat, nameStyle: blueActionButton, type: .none, viewType: viewType, action: { arguments.startSecretChat() }) - case .sharedMedia: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoSharedMedia), type: .none, action: { + case let .sharedMedia(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoSharedMedia, type: .next, viewType: viewType, action: { arguments.sharedMedia() }) - case let .groupInCommon(sectionId: _, count: count): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoGroupsInCommon), type: .context(stateback: { () -> String in - return "\(count)" - }), action: { - arguments.groupInCommon() + case let .groupInCommon(sectionId: _, count, peerId, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoGroupsInCommon, type: .nextContext("\(count)"), viewType: viewType, action: { + arguments.groupInCommon(peerId) }) - case let .notifications(_, settings): + case let .notifications(_, settings, viewType): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoNotifications), type: .switchable(stateback: { () -> Bool in - - if let settings = settings as? TelegramPeerNotificationSettings, case .muted = settings.muteState { - return false - } else { - return true - } - - }), action: { + let settings = settings as? TelegramPeerNotificationSettings + let enabled = !(settings?.isMuted ?? false) + + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoNotifications, type: .switchable(enabled), viewType: viewType, action: { arguments.toggleNotifications() - }) - case .encryptionKey: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoEncryptionKey), type: .none, action: { + }, enabled: settings != nil) + case let .encryptionKey(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoEncryptionKey, type: .next, viewType: viewType, action: { arguments.encryptionKey() }) - case let .block(_, isBlocked): - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: !isBlocked ? tr(.peerInfoBlockUser) : tr(.peerInfoUnblockUser), nameStyle:redActionButton, type: .none, action: { - arguments.updateBlocked(!isBlocked) + case let .block(_, peer, isBlocked, isBot, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: isBot ? (!isBlocked ? L10n.peerInfoStopBot : L10n.peerInfoRestartBot) : (!isBlocked ? L10n.peerInfoBlockUser : L10n.peerInfoUnblockUser), nameStyle:redActionButton, type: .none, viewType: viewType, action: { + arguments.updateBlocked(peer: peer, !isBlocked, isBot) }) - case .deleteChat: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoDeleteSecretChat), nameStyle: redActionButton, type: .none, action: { + case let .deleteChat(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoDeleteSecretChat, nameStyle: redActionButton, type: .none, viewType: viewType, action: { arguments.delete() }) - case .deleteContact: - return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: tr(.peerInfoDeleteContact), nameStyle: redActionButton, type: .none, action: { + case let .deleteContact(_, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId.hashValue, name: L10n.peerInfoDeleteContact, nameStyle: redActionButton, type: .none, viewType: viewType, action: { arguments.deleteContact() }) + case let .media(_, controller, isVisible, viewType): + return PeerMediaBlockRowItem(initialSize, stableId: stableId.hashValue, controller: controller, isVisible: isVisible, viewType: viewType) case .section(_): - return GeneralRowItem(initialSize, height:20, stableId: stableId.hashValue) + return GeneralRowItem(initialSize, height: 30, stableId: stableId.hashValue, viewType: .separator) } } @@ -602,95 +946,112 @@ enum UserInfoEntry: PeerInfoEntry { -func userInfoEntries(view: PeerView, arguments: PeerInfoArguments) -> [PeerInfoEntry] { +func userInfoEntries(view: PeerView, arguments: PeerInfoArguments, mediaTabsData: PeerMediaTabsData) -> [PeerInfoEntry] { let arguments = arguments as! UserInfoArguments let state = arguments.state as! UserInfoState var entries: [PeerInfoEntry] = [] - var sectionId:Int = 1 + var sectionId:Int = 0 + entries.append(UserInfoEntry.section(sectionId: sectionId)) + sectionId += 1 + + + func applyBlock(_ block:[UserInfoEntry]) { + var block = block + for (i, item) in block.enumerated() { + block[i] = item.withUpdatedViewType(bestGeneralViewType(block, for: i)) + } + entries.append(contentsOf: block) + } + + var infoBlock: [UserInfoEntry] = [] + + let editing = state.editingState != nil && (view.peers[view.peerId] as? TelegramUser)?.botInfo == nil && view.peerIsContact + + infoBlock.append(.info(sectionId: sectionId, peerView: view, editable: editing, viewType: .singleItem)) + if editing { + infoBlock.append(.setFirstName(sectionId: sectionId, text: state.editingState?.editingFirstName ?? "", viewType: .singleItem)) + infoBlock.append(.setLastName(sectionId: sectionId, text: state.editingState?.editingLastName ?? "", viewType: .singleItem)) + } + + applyBlock(infoBlock) - entries.append(UserInfoEntry.info(sectionId: sectionId, view, editable: state.editingState != nil)) + entries.append(UserInfoEntry.section(sectionId: sectionId)) + sectionId += 1 if let peer = view.peers[view.peerId] { - if let cachedUserData = view.cachedData as? CachedUserData, state.editingState == nil { - if let about = cachedUserData.about, !about.isEmpty { - if peer.isBot { - entries.append(UserInfoEntry.about(sectionId: sectionId, text: about)) - } else { - entries.append(UserInfoEntry.bio(sectionId: sectionId, text: about)) - } - } - } if let user = peerViewMainPeer(view) as? TelegramUser { + var destructBlock:[UserInfoEntry] = [] + var infoBlock:[UserInfoEntry] = [] + if state.editingState == nil { + if user.isScam { + infoBlock.append(UserInfoEntry.scam(sectionId: sectionId, text: L10n.peerInfoScamWarning, viewType: .singleItem)) + } + if let cachedUserData = view.cachedData as? CachedUserData { + if let about = cachedUserData.about, !about.isEmpty, !user.isScam { + if peer.isBot { + infoBlock.append(UserInfoEntry.about(sectionId: sectionId, text: about, viewType: .singleItem)) + } else { + infoBlock.append(UserInfoEntry.bio(sectionId: sectionId, text: about, viewType: .singleItem)) + } + } + } + if let phoneNumber = user.phone, !phoneNumber.isEmpty { - entries.append(UserInfoEntry.phoneNumber(sectionId: sectionId, index: 0, value: PhoneNumberWithLabel(label: tr(.peerInfoPhone), number: phoneNumber))) + infoBlock.append(.phoneNumber(sectionId: sectionId, index: 0, value: PhoneNumberWithLabel(label: L10n.peerInfoPhone, number: formatPhoneNumber(phoneNumber)), canCopy: true, viewType: .singleItem)) + } else if view.peerIsContact { + infoBlock.append(.phoneNumber(sectionId: sectionId, index: 0, value: PhoneNumberWithLabel(label: L10n.peerInfoPhone, number: L10n.newContactPhoneHidden), canCopy: false, viewType: .singleItem)) } if let username = user.username, !username.isEmpty { - entries.append(UserInfoEntry.userName(sectionId: sectionId, value: username)) + infoBlock.append(.userName(sectionId: sectionId, value: username, viewType: .singleItem)) } - entries.append(UserInfoEntry.section(sectionId: sectionId)) - sectionId += 1 - if !(peer is TelegramSecretChat) { - entries.append(UserInfoEntry.sendMessage(sectionId: sectionId)) - if let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty { - if view.peerIsContact { - entries.append(UserInfoEntry.shareContact(sectionId: sectionId)) - } else { - entries.append(UserInfoEntry.addContact(sectionId: sectionId)) + if !user.isBot { + if !view.peerIsContact { + infoBlock.append(.addContact(sectionId: sectionId, viewType: .singleItem)) + if let cachedData = view.cachedData as? CachedUserData { + infoBlock.append(.block(sectionId: sectionId, peer: peer, blocked: cachedData.isBlocked, isBot: peer.isBot, viewType: .singleItem)) } } } - - if arguments.account.peerId != arguments.peerId, !(peer is TelegramSecretChat), let peer = peer as? TelegramUser, peer.botInfo == nil { - entries.append(UserInfoEntry.startSecretChat(sectionId: sectionId)) + if (peer is TelegramSecretChat) { + infoBlock.append(.encryptionKey(sectionId: sectionId, viewType: .singleItem)) } - entries.append(UserInfoEntry.section(sectionId: sectionId)) - sectionId += 1 - entries.append(UserInfoEntry.sharedMedia(sectionId: sectionId)) - } - - entries.append(UserInfoEntry.notifications(sectionId: sectionId, settings: view.notificationSettings)) - - if (peer is TelegramSecretChat) { - entries.append(UserInfoEntry.encryptionKey(sectionId: sectionId)) + applyBlock(infoBlock) } - if let cachedData = view.cachedData as? CachedUserData, arguments.account.peerId != arguments.peerId { - - if state.editingState == nil { - if cachedData.commonGroupCount > 0 { - entries.append(UserInfoEntry.groupInCommon(sectionId: sectionId, count: Int(cachedData.commonGroupCount))) + if let _ = view.cachedData as? CachedUserData, arguments.context.account.peerId != arguments.peerId { + if state.editingState != nil { + if peer is TelegramSecretChat { + destructBlock.append(.deleteChat(sectionId: sectionId, viewType: .singleItem)) + } + if view.peerIsContact { + destructBlock.append(.deleteContact(sectionId: sectionId, viewType: .singleItem)) } - entries.append(UserInfoEntry.section(sectionId: sectionId)) - sectionId += 1 - - entries.append(UserInfoEntry.block(sectionId: sectionId, cachedData.isBlocked)) - } else { - entries.append(UserInfoEntry.section(sectionId: sectionId)) - sectionId += 1 - entries.append(UserInfoEntry.deleteContact(sectionId: sectionId)) } - - + } - if peer is TelegramSecretChat { + applyBlock(destructBlock) + + + if mediaTabsData.loaded && !mediaTabsData.collections.isEmpty, let controller = arguments.mediaController() { + entries.append(UserInfoEntry.media(sectionId: sectionId, controller: controller, isVisible: state.editingState == nil, viewType: .singleItem)) + } else { entries.append(UserInfoEntry.section(sectionId: sectionId)) sectionId += 1 - - entries.append(UserInfoEntry.deleteChat(sectionId: sectionId)) } } } + return entries.sorted(by: { (p1, p2) -> Bool in return p1.isOrderedBefore(p2) }) diff --git a/Telegram-Mac/UsernameInputRowItem.swift b/Telegram-Mac/UsernameInputRowItem.swift index 7c81f142ab..b7c5f25572 100644 --- a/Telegram-Mac/UsernameInputRowItem.swift +++ b/Telegram-Mac/UsernameInputRowItem.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac +import SwiftSignalKit +import TelegramCore +import SyncCore class UsernameInputRowItem: GeneralInputRowItem { let status:AddressNameValidationStatus? let changeHandler:(String)->Void @@ -50,20 +51,30 @@ class UsernameInputRowView: GeneralInputRowView { indicator.isHidden = true } + + + override func updateColors() { + super.updateColors() + indicator.progressColor = theme.colors.grayText + } override func layout() { super.layout() if let item = item as? UsernameInputRowItem { - imageView.setFrameOrigin(textView.frame.maxX - imageView.frame.width, textView.frame.maxY - imageView.frame.height - item.insets.bottom) - indicator.setFrameOrigin(textView.frame.maxX - indicator.frame.width, textView.frame.maxY - indicator.frame.height - item.insets.bottom) + imageView.setFrameOrigin(textView.frame.maxX - imageView.frame.width, textView.frame.maxY - imageView.frame.height - item.insets.bottom - 5) + indicator.setFrameOrigin(textView.frame.maxX - indicator.frame.width, textView.frame.maxY - indicator.frame.height - item.insets.bottom - 5) if !imageView.isHidden || !indicator.isHidden { - textView.setFrameSize(frame.width - item.insets.right - 30, textView.frame.height) + textView.setFrameSize(frame.width - item.insets.right - item.insets.left, textView.frame.height) } else { - textView.setFrameSize(frame.width - item.insets.right, textView.frame.height) + textView.setFrameSize(frame.width - item.insets.right - item.insets.left, textView.frame.height) } } } + override func textViewDidReachedLimit(_ textView: Any) { + self.textView.shake() + } + override func textViewHeightChanged(_ height: CGFloat, animated: Bool) { super.textViewHeightChanged(height, animated: animated) imageView.change(pos: NSMakePoint(textView.frame.maxX - imageView.frame.width, textView.frame.maxY - imageView.frame.height), animated: animated) @@ -85,11 +96,19 @@ class UsernameInputRowView: GeneralInputRowView { indicator.isHidden = false indicator.animates = true imageView.isHidden = true - case .availability: - imageView.isHidden = false - imageView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) - indicator.isHidden = true - indicator.animates = false + case let .availability(status): + + switch status { + case .available: + imageView.isHidden = false + imageView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + indicator.isHidden = true + indicator.animates = false + default: + imageView.isHidden = true + indicator.isHidden = true + indicator.animates = false + } default: imageView.isHidden = true indicator.isHidden = true diff --git a/Telegram-Mac/UsernameSettingsViewController.swift b/Telegram-Mac/UsernameSettingsViewController.swift index 912a280090..afd40e7632 100644 --- a/Telegram-Mac/UsernameSettingsViewController.swift +++ b/Telegram-Mac/UsernameSettingsViewController.swift @@ -8,81 +8,72 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +fileprivate enum UsernameEntryId : Hashable { + case section(Int32) + case inputEntry + case stateEntry + case descEntry + +} -fileprivate enum UsernameEntries : Comparable, Identifiable { - case whiteSpace(Int64, CGFloat) - case inputEntry(placeholder:String, state:AddressNameAvailabilityState) - case stateEntry(text:String, color:NSColor) - case descEntry(String) +fileprivate enum UsernameEntry : Comparable, Identifiable { + case section(Int32) + case inputEntry(sectionId: Int32, placeholder:String, state:AddressNameAvailabilityState, viewType: GeneralViewType) + case stateEntry(sectionId:Int32, text:String, color:NSColor, viewType: GeneralViewType) + case descEntry(sectionId:Int32, text: String, viewType: GeneralViewType) + + var index:Int32 { + switch self { + case let .section(sectionId): + return (sectionId + 1) * 1000 - sectionId + case let .inputEntry(sectionId, _, _, _): + return (sectionId * 1000) + 1 + case let .stateEntry(sectionId, _, _, _): + return (sectionId * 1000) + 2 + case let .descEntry(sectionId, _, _): + return (sectionId * 1000) + 3 + } + } - var index:Int64 { + fileprivate var stableId:UsernameEntryId { switch self { - case let .whiteSpace(index, _): - return index + case let .section(index): + return .section(index) case .inputEntry: - return 1000 + return .inputEntry case .stateEntry: - return 2000 + return .stateEntry case .descEntry: - return 3000 + return .descEntry } } - - fileprivate var stableId:Int64 { - return index - } } -fileprivate func <(lhs:UsernameEntries, rhs:UsernameEntries) ->Bool { +fileprivate func <(lhs:UsernameEntry, rhs:UsernameEntry) ->Bool { return lhs.index < rhs.index } -fileprivate func ==(lhs:UsernameEntries, rhs:UsernameEntries) ->Bool { - switch lhs { - case let .whiteSpace(lhsIndex, lhsHeight): - if case let .whiteSpace(rhsIndex, rhsHeight) = rhs { - return lhsIndex == rhsIndex && lhsHeight == rhsHeight - } - return false - case let .inputEntry(lhsState): - if case let .inputEntry(rhsState) = rhs , lhsState.state == rhsState.state { - return true - } - return false - case let .stateEntry(lhsState): - if case let .stateEntry(rhsState) = rhs, lhsState == rhsState { - return true - } - return false - case let .descEntry(lhsDesc): - if case let .descEntry(rhsDesc) = rhs, lhsDesc == rhsDesc { - return true - } - return false - } -} - -fileprivate func prepareEntries(from:[AppearanceWrapperEntry], to:[AppearanceWrapperEntry], account:Account, initialSize:NSSize, animated:Bool, availability:ValuePromise) -> Signal { +fileprivate func prepareEntries(from:[AppearanceWrapperEntry], to:[AppearanceWrapperEntry], initialSize:NSSize, animated:Bool, availability:ValuePromise) -> Signal { return Signal { subscriber in let (removed, inserted, updated) = proccessEntriesWithoutReverse(from, right: to, { entry -> TableRowItem in switch entry.entry { - case let .whiteSpace(index, height): - return GeneralRowItem(initialSize, height: height, stableId: index) + case .section: + return GeneralRowItem(initialSize, height: 30, stableId: entry.stableId, viewType: .separator) case let .inputEntry(inputState): - - return UsernameInputRowItem(initialSize, stableId: entry.stableId, placeholder: inputState.placeholder, limit: 30, status: nil, text: inputState.state.username ?? "", changeHandler: { value in - availability.set(value) - }) - case let .stateEntry(state): - return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: NSAttributedString.initialize(string: state.text, color: state.color, font: .normal(.text)), alignment: .left, inset:NSEdgeInsets(left: 30.0, right: 30.0, top:6, bottom:4)) - case let .descEntry(desc): - return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: desc) + return InputDataRowItem(initialSize, stableId: entry.stableId, mode: .plain, error: nil, viewType: inputState.viewType, currentText: inputState.state.username ?? "", placeholder: nil, inputPlaceholder: inputState.placeholder, filter: { $0 }, updated: { value in + availability.set(value) + }, limit: 30) + case let .stateEntry(_, text, color, viewType): + return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: NSAttributedString.initialize(string: text, color: color, font: .normal(.text)), viewType: viewType) + case let .descEntry(_, text, viewType): + return GeneralTextRowItem(initialSize, stableId: entry.stableId, text: text, viewType: viewType) } }) @@ -107,11 +98,8 @@ class UsernameSettingsViewController: TableViewController { return true } - var doneButton:Button? { - if let button = rightBarView as? TextButtonBarView { - return button.button - } - return nil + var doneButton:Control? { + return rightBarView } override func backKeyAction() -> KeyHandlerResult { @@ -119,9 +107,9 @@ class UsernameSettingsViewController: TableViewController { } override func getRightBarViewOnce() -> BarView { - let button = TextButtonBarView(controller: self, text: tr(.usernameSettingsDone)) + let button = TextButtonBarView(controller: self, text: L10n.usernameSettingsDone) - button.button.set(handler: { [weak self] _ in + button.set(handler: { [weak self] _ in self?.saveUsername() }, for: .Click) @@ -131,8 +119,16 @@ class UsernameSettingsViewController: TableViewController { func saveUsername() { - if let item = genericView.item(stableId: Int64(1000)) as? UsernameInputRowItem, let window = window { - updateDisposable.set(showModalProgress(signal: updateAddressName(account: account, domain: .account, name: item.text) |> mapError({_ in}), for: window).start()) + if let item = genericView.item(stableId: AnyHashable(UsernameEntryId.inputEntry)) as? InputDataRowItem, let window = window { + updateDisposable.set(showModalProgress(signal: updateAddressName(account: context.account, domain: .account, name: item.currentText.string), for: window).start(error: { error in + switch error { + case .generic: + alert(for: mainWindow, info: L10n.unknownError) + } + }, completed: { [weak self] in + self?.navigationController?.back() + _ = showModalSuccess(for: mainWindow, icon: theme.icons.successModalProgress, delay: 0.5).start() + })) } } @@ -140,7 +136,7 @@ class UsernameSettingsViewController: TableViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.window?.set(handler: { [weak self] () -> KeyHandlerResult in - if let rightView = self?.rightBarView as? TextButtonBarView, rightView.button.isEnabled { + if let rightView = self?.rightBarView as? TextButtonBarView, rightView.isEnabled { self?.saveUsername() return .rejected } @@ -153,42 +149,39 @@ class UsernameSettingsViewController: TableViewController { - let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value:[]) - let entries:Promise<[UsernameEntries]> = Promise() + let previous:Atomic<[AppearanceWrapperEntry]> = Atomic(value:[]) + let entries:Promise<[UsernameEntry]> = Promise() let initialSize = self.atomicSize.modify({$0}) - let account = self.account + let context = self.context let availability = self.availability - var mutableItems:[UsernameEntries] = [.whiteSpace(0, 16), - .inputEntry(placeholder: tr(.usernameSettingsInputPlaceholder), state:.none(username: nil)), - .descEntry(tr(.usernameSettingsChangeDescription))] - username.set(account.viewTracker.peerView( account.peerId) |> deliverOnMainQueue |> mapToSignal { peerView -> Signal in - if let peer = peerView.peers[account.peerId] { + username.set(context.account.viewTracker.peerView( context.peerId) |> deliverOnMainQueue |> mapToSignal { peerView -> Signal in + if let peer = peerView.peers[context.peerId] { return .single(peer.username ?? "") } return .complete() }) - self.genericView.merge(with: combineLatest(entries.get(),username.get() |> distinctUntilChanged |> mapToSignal {username -> Signal in + self.genericView.merge(with: combineLatest(entries.get(),username.get() |> distinctUntilChanged |> mapToSignal {username -> Signal in availability.set(username) return .single(username) }, appearanceSignal) |> deliverOnMainQueue - |> mapToSignal { items, username, appearance -> Signal in + |> mapToSignal { items, username, appearance -> Signal in let items = items.map{AppearanceWrapperEntry(entry: $0, appearance: appearance)} - return prepareEntries(from: previous.swap(items), to: items, account: account, initialSize: initialSize, animated: true, availability:availability) + return prepareEntries(from: previous.swap(items), to: items, initialSize: initialSize, animated: true, availability:availability) }) let availabilityChecker = combineLatest(availability.get(), username.get() |> distinctUntilChanged) - |> mapToSignal { (value,username) -> Signal<(AddressNameAvailabilityState,String),Void> in + |> mapToSignal { (value,username) -> Signal<(AddressNameAvailabilityState,String), NoError> in if let error = checkAddressNameFormat(value) { return .single((AddressNameAvailabilityState.fail(username: value, formatError: error, availability: .available), username)) } else { - return .single((AddressNameAvailabilityState.progress(username: value), username)) |> then(addressNameAvailability(account: account, domain: .account, name: value) + return .single((AddressNameAvailabilityState.progress(username: value), username)) |> then(addressNameAvailability(account: context.account, domain: .account, name: value) |> map { availability -> (AddressNameAvailabilityState,String) in switch availability { case .available: @@ -202,48 +195,54 @@ class UsernameSettingsViewController: TableViewController { } } |> deliverOnMainQueue - |> mapToSignal { [weak self] (availability,address) -> Signal in - mutableItems[1] = .inputEntry(placeholder: tr(.usernameSettingsInputPlaceholder), state:availability) + |> mapToSignal { [weak self] (availability,address) -> Signal in + // var mutableItems:[UsernameEntry] = [.whiteSpace(0, 16), + // .inputEntry(placeholder: tr(L10n.usernameSettingsInputPlaceholder), state:.none(username: nil)), +// .descEntry(tr(L10n.usernameSettingsChangeDescription))] + + var items:[UsernameEntry] = [] + var sectionId: Int32 = 0 + + items.append(.section(sectionId)) + sectionId += 1 + + items.append(.inputEntry(sectionId: sectionId, placeholder: L10n.usernameSettingsInputPlaceholder, state: availability, viewType: .singleItem)) switch availability { case .none: - if case .stateEntry = mutableItems[2] { - mutableItems.remove(at: 2) - } self?.doneButton?.isEnabled = true break case .progress: self?.doneButton?.isEnabled = false break case let .success(username:username): - if case .stateEntry = mutableItems[2] { - mutableItems.remove(at: 2) - } if address != username { if username?.length != 0 { - mutableItems.insert(.stateEntry(text:tr(.usernameSettingsAvailable(username ?? "")), color: theme.colors.blueUI), at: 2) + items.append(.stateEntry(sectionId: sectionId, text: L10n.usernameSettingsAvailable(username ?? ""), color: theme.colors.accent, viewType: .textBottomItem)) } } self?.doneButton?.isEnabled = address != username case let .fail(fail): - if case .stateEntry = mutableItems[2] { - mutableItems.remove(at: 2) - } - let enabled = fail.username?.length == 0 && address.length != 0 - - let stateEntry:UsernameEntries + let stateEntry:UsernameEntry if let error = fail.formatError { - stateEntry = .stateEntry(text: error.description, color: theme.colors.redUI) + stateEntry = .stateEntry(sectionId: sectionId, text: error.description, color: theme.colors.redUI, viewType: .textBottomItem) } else { - stateEntry = .stateEntry(text: fail.availability.description, color: theme.colors.redUI) + stateEntry = .stateEntry(sectionId: sectionId, text: fail.availability.description(for: address), color: theme.colors.redUI, viewType: .textBottomItem) } if fail.username?.length != 0 { - mutableItems.insert(stateEntry, at: 2) + items.append(stateEntry) } self?.doneButton?.isEnabled = enabled } - entries.set(.single(mutableItems)) + + items.append(.descEntry(sectionId: sectionId, text: L10n.usernameSettingsChangeDescription, viewType: .textBottomItem)) + + + items.append(.section(sectionId)) + sectionId += 1 + + entries.set(.single(items)) self?.readyOnce() return .single(Void()) @@ -268,7 +267,7 @@ class UsernameSettingsViewController: TableViewController { } override func firstResponder() -> NSResponder? { - if let item = genericView.item(stableId: Int64(1000)), let view = genericView.viewNecessary(at: item.index) as? GeneralInputRowView { + if let view = genericView.item(at: 1).view as? InputDataRowView { return view.textView } return nil diff --git a/Telegram-Mac/VCardContactController.swift b/Telegram-Mac/VCardContactController.swift new file mode 100644 index 0000000000..bc947dca0c --- /dev/null +++ b/Telegram-Mac/VCardContactController.swift @@ -0,0 +1,258 @@ +// +// VCardContactController.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import Contacts +import SwiftSignalKit +// +//private class VCardContactView : View { +// let tableView: TableView = TableView(frame: NSZeroRect) +// private let title: TextView = TextView() +// private let separator : View = View() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// addSubview(title) +// addSubview(tableView) +// addSubview(separator) +// separator.backgroundColor = theme.colors.border +// +// self.title.update(TextViewLayout(.initialize(string: "Contact", color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1)) +// needsLayout = true +// } +// +// +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func layout() { +// super.layout() +// tableView.frame = NSMakeRect(0, 50, frame.width, frame.height - 50) +// title.layout?.measure(width: frame.width - 60) +// title.update(title.layout) +// title.centerX(y: floorToScreenPixels(backingScaleFactor, (50 - title.frame.height) / 2)) +// separator.frame = NSMakeRect(0, 49, frame.width, .borderSize) +// } +//} +// +//private final class VCardArguments { +// let account: Account +// init(account: Account) { +// self.account = account +// } +//} +// +//private func vCardEntries(vCard: CNContact, contact: TelegramMediaContact, arguments: VCardArguments) -> [InputDataEntry] { +// +// var entries: [InputDataEntry] = [] +// var sectionId:Int32 = 0 +// var index: Int32 = 0 +// +// func getLabel(_ key: String) -> String { +// +// switch key { +// case "_$!!$_": +// return L10n.contactInfoURLLabelHomepage +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelHome +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelWork +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelMobile +// case "_$!
!$_": +// return L10n.contactInfoPhoneLabelMain +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelHomeFax +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelWorkFax +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelPager +// case "_$!!$_": +// return L10n.contactInfoPhoneLabelOther +// default: +// return L10n.contactInfoPhoneLabelOther +// } +// } +// +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("header"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return VCardHeaderItem(initialSize, stableId: stableId, account: arguments.account, vCard: vCard, contact: contact) +// })) +// index += 1 +// +// for phoneNumber in vCard.phoneNumbers { +// if let label = phoneNumber.label { +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("phone_\(phoneNumber.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: getLabel(label), text: phoneNumber.value.stringValue, account: arguments.account) +// })) +// } +// index += 1 +// } +// +// for email in vCard.emailAddresses { +// if let label = email.label { +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("email_\(email.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: getLabel(label), text: email.value as String, account: arguments.account) +// })) +// } +// index += 1 +// } +// +// for address in vCard.urlAddresses { +// if let label = address.label { +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("url_\(address.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: getLabel(label), text: address.value as String, account: arguments.account) +// })) +// } +// index += 1 +// } +// +// for address in vCard.postalAddresses { +// if let label = address.label { +// let text: String = address.value.street + "\n" + address.value.city + "\n" + address.value.country +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("url_\(address.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: getLabel(label), text: text, account: arguments.account) +// })) +// } +// index += 1 +// } +// +// if let birthday = vCard.birthday { +// let date = Calendar.current.date(from: birthday)! +// +// let dateFormatter = DateFormatter() +// dateFormatter.dateStyle = .long +// +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("birthday"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: L10n.contactInfoBirthdayLabel, text: dateFormatter.string(from: date), account: arguments.account) +// })) +// index += 1 +// } +// +// for social in vCard.socialProfiles { +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("social_\(social.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: social.value.service, text: social.value.urlString, account: arguments.account) +// })) +// } +// +// for social in vCard.instantMessageAddresses { +// entries.append(InputDataEntry.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("instant_\(social.identifier)"), equatable: nil, item: { initialSize, stableId -> TableRowItem in +// return TextAndLabelItem(initialSize, stableId: stableId, label: social.value.service, text: social.value.username, account: arguments.account) +// })) +// } +// +// +// return entries +//} +// +// +//final class VCardModalController : ModalViewController { +// private let controller: NavigationViewController +// init(_ account: Account, vCard: CNContact, contact: TelegramMediaContact) { +// self.controller = VCardContactController(account, vCard: vCard, contact: contact) +// super.init(frame: controller._frameRect) +// } +// +// public override var handleEvents: Bool { +// return true +// } +// +// public override func firstResponder() -> NSResponder? { +// return controller.controller.firstResponder() +// } +// +// public override func returnKeyAction() -> KeyHandlerResult { +// return controller.controller.returnKeyAction() +// } +// +// public override var haveNextResponder: Bool { +// return true +// } +// +// public override func nextResponder() -> NSResponder? { +// return controller.controller.nextResponder() +// } +// +// var input: InputDataController { +// return controller.controller as! InputDataController +// } +// +// public override func viewDidLoad() { +// super.viewDidLoad() +// ready.set(controller.ready.get()) +// } +// +// override var view: NSView { +// if !controller.isLoaded() { +// controller.loadViewIfNeeded() +// viewDidLoad() +// } +// return controller.view +// } +// +// override var modalInteractions: ModalInteractions? { +// return ModalInteractions(acceptTitle: L10n.modalOK) +// } +// +// override func measure(size: NSSize) { +// self.modal?.resize(with:NSMakeSize(380, min(size.height - 70, input.genericView.listHeight + 70)), animated: false) +// } +// +// public func updateSize(_ animated: Bool) { +// if let contentSize = self.modal?.window.contentView?.frame.size { +// self.modal?.resize(with:NSMakeSize(380, min(contentSize.height - 70, input.genericView.listHeight + 70)), animated: animated) +// } +// } +// override var dynamicSize: Bool { +// return true +// } +// +//} +// +//private class VCardContactController: NavigationViewController { +// +//// override func viewClass() -> AnyClass { +//// return VCardContactView.self +//// } +// +// fileprivate let context: AccountContext +// fileprivate let vCard: CNContact +// fileprivate let contact: TelegramMediaContact +// fileprivate let input: InputDataController +// fileprivate let values: Promise<[InputDataEntry]> = Promise() +// init(_ account: Account, vCard: CNContact, contact: TelegramMediaContact) { +// self.account = account +// self.vCard = vCard +// self.contact = contact +// input = InputDataController(dataSignal: values.get() |> map {($0, true)}, title: L10n.contactInfoContactInfo, hasDone: false) +// super.init(input) +// self._frameRect = NSMakeRect(0, 0, 380, 500) +// } +// +// +// +// +// override func viewDidLoad() { +// super.viewDidLoad() +// ready.set(input.ready.get()) +// let arguments = VCardArguments(account: account) +// let vCard = self.vCard +// let contact = self.contact +// +// values.set(appearanceSignal |> deliverOnPrepareQueue |> map { _ in return vCardEntries(vCard: vCard, contact: contact, arguments: arguments)}) +// } +// +//// private var genericView:VCardContactView { +//// return self.view as! VCardContactView +//// } +// +//} diff --git a/Telegram-Mac/VCardHeaderItem.swift b/Telegram-Mac/VCardHeaderItem.swift new file mode 100644 index 0000000000..fcb23ad51a --- /dev/null +++ b/Telegram-Mac/VCardHeaderItem.swift @@ -0,0 +1,15 @@ +// +// VCardHeaderItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit +import Contacts diff --git a/Telegram-Mac/VCardLocationRowItem.swift b/Telegram-Mac/VCardLocationRowItem.swift new file mode 100644 index 0000000000..ddac34870a --- /dev/null +++ b/Telegram-Mac/VCardLocationRowItem.swift @@ -0,0 +1,85 @@ +// +// VCardLocationRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 20/07/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import Contacts +import TelegramCore +import SyncCore + +class VCardLocationRowItem: GeneralRowItem { + fileprivate let address: CNLabeledValue + fileprivate let textLayout: TextViewLayout + init(_ initialSize: NSSize, stableId: AnyHashable, address: CNLabeledValue, account: Account) { + self.address = address + let attr = NSMutableAttributedString() + + if let label = address.label { + _ = attr.append(string: label, color: theme.colors.accent, font: .normal(.text)) + _ = attr.append(string: "\n\n") + } + + _ = attr.append(string: address.value.street, color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: "\n", color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: address.value.city, color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: "\n", color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: address.value.country, color: theme.colors.text, font: .normal(.text)) + _ = attr.append(string: "\n", color: theme.colors.text, font: .normal(.text)) + + self.textLayout = TextViewLayout(attr) + super.init(initialSize, stableId: stableId) + + } + + override var height: CGFloat { + return max(textLayout.layoutSize.height, 80) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let result = super.makeSize(width, oldWidth: oldWidth) + textLayout.measure(width: width - 180) + return result + } + + override func viewClass() -> AnyClass { + return VCardLocationRowView.self + } + +} + +private final class VCardLocationRowView : TableRowView { + fileprivate let textView = TextView() + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + } + + override func updateColors() { + super.updateColors() + textView.backgroundColor = theme.colors.background + } + + override func layout() { + super.layout() + guard let item = item as? VCardLocationRowItem else { return } + + textView.centerY(x: item.inset.left) + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + guard let item = item as? VCardLocationRowItem else { return } + textView.update(item.textLayout) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Telegram-Mac/ValidateAddressNameInteractive.swift b/Telegram-Mac/ValidateAddressNameInteractive.swift index 75e7b49868..1fa8c3c74e 100644 --- a/Telegram-Mac/ValidateAddressNameInteractive.swift +++ b/Telegram-Mac/ValidateAddressNameInteractive.swift @@ -8,37 +8,16 @@ import Cocoa -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox enum AddressNameValidationStatus: Equatable { case checking case invalidFormat(AddressNameFormatError) case availability(AddressNameAvailability) - - static func ==(lhs: AddressNameValidationStatus, rhs: AddressNameValidationStatus) -> Bool { - switch lhs { - case .checking: - if case .checking = rhs { - return true - } else { - return false - } - case let .invalidFormat(error): - if case .invalidFormat(error) = rhs { - return true - } else { - return false - } - case let .availability(availability): - if case .availability(availability) = rhs { - return true - } else { - return false - } - } - } + } func validateAddressNameInteractive(account: Account, domain: AddressNameDomain, name: String) -> Signal { diff --git a/Telegram-Mac/ValuesSelectorModalController.swift b/Telegram-Mac/ValuesSelectorModalController.swift new file mode 100644 index 0000000000..ae269a8e0e --- /dev/null +++ b/Telegram-Mac/ValuesSelectorModalController.swift @@ -0,0 +1,287 @@ +// +// ValuesSelectorModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 21/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit + +private final class ValuesSelectorArguments where T : Equatable { + let selectItem:(ValuesSelectorValue)->Void + init(selectItem:@escaping(ValuesSelectorValue)->Void) { + self.selectItem = selectItem + } +} + +private enum ValuesSelectorEntry : TableItemListNodeEntry where T : Equatable { + case sectionId(sectionId: Int32) + case value(sectionId: Int32, index: Int32, value: ValuesSelectorValue, selected: Bool, viewType: GeneralViewType) + var stableId: Int32 { + switch self { + case let .value(_, index, _, _, _): + return index + case let .sectionId(sectionId): + return 1000 + sectionId + } + } + + var index: Int32 { + switch self { + case let .sectionId(sectionId): + return (sectionId + 1) * 1000 - sectionId + case let .value(sectionId, index, _, _, _): + return (sectionId * 1000) + index + } + } + + func item(_ arguments: ValuesSelectorArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case let .value(_, _, value, selected, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: value.localized, type: .none, viewType: viewType, action: { + arguments.selectItem(value) + }) + case .sectionId: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + } + } +} + +private func ==(lhs: ValuesSelectorEntry, rhs: ValuesSelectorEntry) -> Bool { + switch lhs { + case let .value(section, index, value, selected, viewType): + if case .value(section, index, value, selected, viewType) = rhs { + return true + } else { + return false + } + case let .sectionId(sectionId): + if case .sectionId(sectionId) = rhs { + return true + } else { + return false + } + } +} + +private func <(lhs: ValuesSelectorEntry, rhs: ValuesSelectorEntry) -> Bool { + return lhs.index < rhs.index +} + +private final class ValuesSelectorModalView : View { + let tableView: TableView = TableView(frame: NSZeroRect) + fileprivate let searchView: SearchView = SearchView(frame: NSZeroRect) + private let separator : View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(tableView) + addSubview(separator) + addSubview(searchView) + tableView.getBackgroundColor = { + return theme.colors.listBackground + } + separator.backgroundColor = theme.colors.border + } + + func hasSearch(_ hasSearch: Bool) { + searchView.isHidden = !hasSearch + separator.isHidden = !hasSearch + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + + let offset: CGFloat = searchView.isHidden ? 0 : 50 + + tableView.frame = NSMakeRect(0, offset, frame.width, frame.height - offset) + searchView.setFrameSize(NSMakeSize(frame.width - 20, 30)) + searchView.centerX(y: floorToScreenPixels(backingScaleFactor, (50 - searchView.frame.height) / 2)) + separator.frame = NSMakeRect(0, 49, frame.width, .borderSize) + } +} + +private final class ValuesSelectorState : Equatable where T : Equatable { + let selected: ValuesSelectorValue? + let values: [ValuesSelectorValue] + init(selected: ValuesSelectorValue? = nil, values: [ValuesSelectorValue] = []) { + self.selected = selected + self.values = values + } + + func withUpdatedSelected(_ selected: ValuesSelectorValue?) -> ValuesSelectorState { + return ValuesSelectorState(selected: selected, values: self.values) + } + func withUpdatedValues(_ values: [ValuesSelectorValue]) -> ValuesSelectorState { + return ValuesSelectorState(selected: self.selected, values: values) + } +} + +private func ==(lhs: ValuesSelectorState, rhs: ValuesSelectorState) -> Bool { + return lhs.selected == rhs.selected && lhs.values == rhs.values +} + +fileprivate func prepareTransition(left:[ValuesSelectorEntry], right: [ValuesSelectorEntry], initialSize:NSSize, arguments: ValuesSelectorArguments) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.item(arguments, initialSize: initialSize) + } + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: false) +} + +struct ValuesSelectorValue : Equatable where T : Equatable { + let localized: String + let value: T + init(localized: String, value: T) { + self.localized = localized + self.value = value + } +} + +func ==(lhs: ValuesSelectorValue, rhs: ValuesSelectorValue) -> Bool { + return lhs.value == rhs.value +} + +class ValuesSelectorModalController: ModalViewController where T : Equatable { + + + private func complete() { + if let selected = stateValue.modify({$0}).selected { + self.onComplete(selected) + } + close() + } + + override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return (left: ModalHeaderData(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: self.title), right: nil) + } + + + override func viewClass() -> AnyClass { + return ValuesSelectorModalView.self + } + + private let onComplete:(ValuesSelectorValue)->Void + private let disposable = MetaDisposable() + private let title: String + private let stateValue: Atomic> + init(values: [ValuesSelectorValue], selected: ValuesSelectorValue?, title: String, onComplete:@escaping(ValuesSelectorValue)->Void) { + self.stateValue = Atomic(value: ValuesSelectorState(selected: nil, values: values)) + self.onComplete = onComplete + self.title = title + super.init(frame: NSMakeRect(0, 0, 350, 100)) + self.bar = .init(height: 0) + } + + override func viewDidLoad() { + super.viewDidLoad() + + genericView.hasSearch(self.stateValue.with { $0.values.count > 10 }) + + let search:ValuePromise = ValuePromise(SearchState(state: .None, request: nil), ignoreRepeated: true) + + let searchInteractions = SearchInteractions({ s, _ in + search.set(s) + }, { s in + search.set(s) + }) + + + genericView.searchView.searchInteractions = searchInteractions + + let statePromise: ValuePromise> = ValuePromise(ignoreRepeated: true) + let stateValue = self.stateValue + let updateState:((ValuesSelectorState)->ValuesSelectorState) -> Void = { f in + statePromise.set(stateValue.modify(f)) + } + + updateState { current in + return current + } + + + let arguments = ValuesSelectorArguments(selectItem: { [weak self] selected in + updateState { current in + return current.withUpdatedSelected(selected) + } + self?.complete() + }) + + let initialSize = self.atomicSize + + let previous: Atomic<[ValuesSelectorEntry]> = Atomic(value: []) + + let signal: Signal = combineLatest(statePromise.get() |> deliverOnPrepareQueue, search.get() |> deliverOnPrepareQueue) |> map { state, search in + + var entries:[ValuesSelectorEntry] = [] + var index: Int32 = 0 + var sectionId: Int32 = 0 + + entries.append(.sectionId(sectionId: sectionId)) + sectionId += 1 + + let values = state.values.filter { value in + let result = value.localized.split(separator: " ").filter({$0.lowercased().hasPrefix(search.request.lowercased())}) + return search.request.isEmpty || !result.isEmpty + } + for value in values { + entries.append(ValuesSelectorEntry.value(sectionId: sectionId, index: index, value: value, selected: state.selected == value, viewType: bestGeneralViewType(values, for: value))) + index += 1 + } + entries.append(.sectionId(sectionId: sectionId)) + sectionId += 1 + + return prepareTransition(left: previous.swap(entries), right: entries, initialSize: initialSize.modify{$0}, arguments: arguments) + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] transition in + guard let `self` = self else {return} + self.genericView.tableView.merge(with: transition) + self.readyOnce() + })) + + } + + override func becomeFirstResponder() -> Bool? { + return false + } + + override func firstResponder() -> NSResponder? { + return genericView.searchView.isHidden ? nil : genericView.searchView.input + } + + private func updateSize(_ width: CGFloat, animated: Bool) { + if let contentSize = self.window?.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(width, min(contentSize.height - 150, genericView.tableView.listHeight + 50)), animated: animated) + } + } + + override func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 150, genericView.tableView.listHeight + self.stateValue.with { $0.values.count > 10 ? 50 : 0 })), animated: false) + } + + override func returnKeyAction() -> KeyHandlerResult { + complete() + return .invoked + } + + override var dynamicSize: Bool { + return true + } + + private var genericView:ValuesSelectorModalView { + return self.view as! ValuesSelectorModalView + } + + deinit { + disposable.dispose() + } + +} diff --git a/Telegram-Mac/VerticalTabsView.swift b/Telegram-Mac/VerticalTabsView.swift new file mode 100644 index 0000000000..5ed3fa7bcb --- /dev/null +++ b/Telegram-Mac/VerticalTabsView.swift @@ -0,0 +1,14 @@ +// +// VerticalTabsView.swift +// Telegram +// +// Created by Mikhail Filimonov on 18.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +class VerticalTabsView: View { + +} diff --git a/Telegram-Mac/VideoAvatarModalController.swift b/Telegram-Mac/VideoAvatarModalController.swift new file mode 100644 index 0000000000..1fb3b89737 --- /dev/null +++ b/Telegram-Mac/VideoAvatarModalController.swift @@ -0,0 +1,870 @@ +// +// VideoAvatarModalController.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import Postbox +import SyncCore +import AVKit +import SwiftSignalKit + + +private var magicNumber: CGFloat { + return 8 / 370 +} + +private final class VideoAvatarKeyFramePreviewView: Control { + private let imageView: ImageView = ImageView() + private let flash: View = View() + fileprivate var keyFrame: CGFloat? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(imageView) + addSubview(flash) + flash.backgroundColor = .white + flash.frame = bounds + imageView.frame = bounds + imageView.animates = true + layout() + } + + func update(with image: CGImage?, value: CGFloat?, animated: Bool, completion: @escaping(Bool)->Void) { + imageView.image = image + self.keyFrame = value + if animated { + flash.layer?.animateAlpha(from: 1, to: 0, duration: 0.8, timingFunction: .easeIn, removeOnCompletion: false, completion: { [weak self] completed in + self?.flash.removeFromSuperview() + completion(completed) + }) + } else { + flash.removeFromSuperview() + } + } + + + + override func layout() { + super.layout() + imageView.frame = bounds + layer?.cornerRadius = frame.width / 2 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class VideoAvatarModalView : View { + private var avPlayer: AVPlayerView + private var videoSize: NSSize = .zero + private let playerContainer: View = View() + private var keyFramePreview: VideoAvatarKeyFramePreviewView? + private var keyFrameDotView: View? + private let controls: View = View() + + fileprivate let ok: TitleButton = TitleButton() + fileprivate let cancel: TitleButton = TitleButton() + + fileprivate let scrubberView: VideoEditorScrubblerControl = VideoEditorScrubblerControl(frame: .zero) + fileprivate let selectionRectView: SelectionRectView + + + private let descView: TextView = TextView() + + required init(frame frameRect: NSRect) { + selectionRectView = SelectionRectView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + avPlayer = AVPlayerView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + playerContainer.addSubview(avPlayer) + avPlayer.controlsStyle = .none + + playerContainer.addSubview(selectionRectView) + controls.addSubview(scrubberView) + selectionRectView.isCircleCap = true + selectionRectView.dimensions = .square + + + // controls.border = [.Left, .Right] + // controls.borderColor = NSColor.black.withAlphaComponent(0.2) + //controls.backgroundColor = NSColor(0x303030) + //controls.layer?.cornerRadius = .cornerRadius + addSubview(playerContainer) + addSubview(controls) + + self.addSubview(ok) + self.addSubview(cancel) + + addSubview(descView) + + descView.userInteractionEnabled = false + descView.isSelectable = false + descView.disableBackgroundDrawing = true + + + cancel.set(background: .grayText, for: .Normal) + ok.set(background: .accent, for: .Normal) + + cancel.set(background: NSColor.grayText.withAlphaComponent(0.8), for: .Highlight) + ok.set(background: NSColor.accent.withAlphaComponent(0.8), for: .Highlight) + + + cancel.set(color: .white, for: .Normal) + cancel.set(text: L10n.videoAvatarButtonCancel, for: .Normal) + + + ok.set(color: .white, for: .Normal) + ok.set(text: L10n.videoAvatarButtonSet, for: .Normal) + + _ = cancel.sizeToFit(.zero, NSMakeSize(80, 20), thatFit: true) + _ = ok.sizeToFit(.zero, NSMakeSize(80, 20), thatFit: true) + + cancel.layer?.cornerRadius = .cornerRadius + ok.layer?.cornerRadius = .cornerRadius + + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.2) + shadow.shadowOffset = NSMakeSize(0, 2) + self.cancel.shadow = shadow + self.ok.shadow = shadow + + // cancel.set(image: NSImage(named: "Icon_VideoPlayer_Close")!.precomposed(.white), for: .Normal) + // ok.set(image: NSImage(named: "Icon_SaveEditedMessage")!.precomposed(.accent), for: .Normal) + + setFrameSize(frame.size) + layout() + + + } + + func updateKeyFrameImage(_ image: CGImage?) { + keyFramePreview?.update(with: image, value: keyFramePreview?.keyFrame, animated: false, completion: { _ in }) + } + + func setKeyFrame(value: CGFloat?, highRes: CGImage? = nil, lowRes: CGImage? = nil, animated: Bool, completion: @escaping(Bool)->Void = { _ in}, moveToCurrentKeyFrame: @escaping(CGFloat)->Void = { _ in }) -> Void { + if let keyFramePreview = self.keyFramePreview { + self.keyFramePreview = nil + keyFramePreview.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak keyFramePreview] _ in + keyFramePreview?.removeFromSuperview() + }) + } + if let keyFrameDotView = self.keyFrameDotView { + self.keyFrameDotView = nil + keyFrameDotView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak keyFrameDotView] _ in + keyFrameDotView?.removeFromSuperview() + }) + } + if let value = value { + let point = self.convert(selectionRectView.selectedRect.origin, from: selectionRectView) + let size = selectionRectView.selectedRect.size + let keyFramePreview = VideoAvatarKeyFramePreviewView(frame: CGRect(origin: point, size: size)) + + + keyFramePreview.set(handler: { _ in + moveToCurrentKeyFrame(value) + }, for: .Click) + + keyFramePreview.update(with: highRes, value: value, animated: animated, completion: { [weak self, weak keyFramePreview] completed in + + if !completed { + keyFramePreview?.removeFromSuperview() + completion(completed) + return + } + + guard let `self` = self, let keyFramePreview = keyFramePreview else { + return + } + + let keyFrameDotView = View() + + + self.addSubview(keyFrameDotView) + keyFrameDotView.backgroundColor = .white + keyFrameDotView.layer?.cornerRadius = 3 + + + let point = NSMakePoint(self.controls.frame.minX + self.scrubberView.frame.minX + value * self.scrubberView.frame.width - 15 + 2, self.controls.frame.maxY - self.scrubberView.frame.height - 30 - 14) + + keyFrameDotView.frame = NSMakeRect(self.controls.frame.minX + self.scrubberView.frame.minX + (value * self.scrubberView.frame.width) - 3 + 2, self.controls.frame.maxY - self.scrubberView.frame.height - 10, 6, 6) + + keyFramePreview.layer?.animateScale(from: 1, to: 30 / keyFramePreview.frame.width, duration: 0.23, removeOnCompletion: false) + keyFramePreview.layer?.animatePosition(from: keyFramePreview.frame.origin, to: point, duration: 0.3, removeOnCompletion: false, completion: { [weak self, weak keyFramePreview, weak keyFrameDotView] complete in + + keyFramePreview?.update(with: lowRes, value: value, animated: false, completion: { _ in }) + keyFramePreview?.frame = CGRect(origin: point, size: NSMakeSize(30, 30)) + keyFramePreview?.layer?.removeAllAnimations() + + self?.keyFrameDotView = keyFrameDotView + self?.keyFramePreview = keyFramePreview + + if !complete { + keyFrameDotView?.removeFromSuperview() + keyFramePreview?.removeFromSuperview() + } + + completion(complete) + }) + + + + keyFrameDotView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + }) + self.addSubview(keyFramePreview) + } + } + + var playerSize: NSSize { + return playerContainer.frame.size + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + private var localize: String? + + func update(_ player: AVPlayer, localize: String, size: NSSize) { + self.avPlayer.player = player + self.videoSize = size + self.localize = localize + setFrameSize(frame.size) + layout() + + let size = NSMakeSize(200, 200).aspectFitted(playerContainer.frame.size) + let rect = playerContainer.focus(size) + selectionRectView.minimumSize = size.aspectFitted(NSMakeSize(150, 150)) + selectionRectView.applyRect(rect, force: true, dimensions: .square) + } + + func play() { + self.avPlayer.player?.play() + + } + func stop() { + self.avPlayer.player?.pause() + self.avPlayer.player = nil + } + + override func setFrameSize(_ newSize: NSSize) { + let oldSize = self.frame.size + super.setFrameSize(newSize) + + let videoContainerSize = videoSize.aspectFitted(NSMakeSize(frame.width, frame.height - 200)) + let oldVideoContainerSize = playerContainer.frame.size + playerContainer.setFrameSize(videoContainerSize) + + + if oldSize != newSize, oldSize != NSZeroSize, inLiveResize { + let multiplier = NSMakeSize(videoContainerSize.width / oldVideoContainerSize.width, videoContainerSize.height / oldVideoContainerSize.height) + selectionRectView.applyRect(selectionRectView.selectedRect.apply(multiplier: multiplier)) + } + + avPlayer.frame = playerContainer.bounds + selectionRectView.frame = playerContainer.bounds + controls.setFrameSize(NSMakeSize(370, 44)) + scrubberView.setFrameSize(controls.frame.size) + + + + if let localize = localize { + let descLayout = TextViewLayout.init(.initialize(string: localize, color: .white, font: .normal(.text)), maximumNumberOfLines: 1) + descLayout.measure(width: frame.width) + descView.update(descLayout) + } + } + + override func layout() { + super.layout() + + playerContainer.centerX(y: floorToScreenPixels(backingScaleFactor, (frame.height - 184) - playerContainer.frame.height) / 2) + controls.centerX(y: frame.height - controls.frame.height - 100) + scrubberView.centerX(y: controls.frame.height - scrubberView.frame.height) + + ok.centerX(y: frame.height - ok.frame.height - 30, addition: 7 + ok.frame.width / 2) + cancel.centerX(y: frame.height - cancel.frame.height - 30, addition: -(7 + cancel.frame.width / 2)) + + descView.centerX(y: controls.frame.maxY + 15) + + + if let keyFramePreview = keyFramePreview, let keyFrameDotView = keyFrameDotView, let value = keyFramePreview.keyFrame { + let point = NSMakePoint(self.controls.frame.minX + self.scrubberView.frame.minX + value * self.scrubberView.frame.width - 15 + 2, self.controls.frame.maxY - self.scrubberView.frame.height - 30 - 14) + + keyFrameDotView.frame = NSMakeRect(self.controls.frame.minX + self.scrubberView.frame.minX + (value * self.scrubberView.frame.width) - 3 + 2, self.controls.frame.maxY - self.scrubberView.frame.height - 10, 6, 6) + keyFramePreview.frame = CGRect(origin: point, size: NSMakeSize(30, 30)) + + } + + } +} + + +enum VideoAvatarGeneratorState : Equatable { + case start(thumb: String) + case progress(Float) + case complete(thumb: String, video: String, keyFrame: Double?) + case error +} + + +class VideoAvatarModalController: ModalViewController { + private let context: AccountContext + fileprivate let videoSize: NSSize + fileprivate let player: AVPlayer + fileprivate let item: AVPlayerItem + fileprivate let asset: AVComposition + fileprivate let track: AVAssetTrack + fileprivate var appliedKeyFrame: CGFloat? = nil + + private let updateThumbsDisposable = MetaDisposable() + private let rectDisposable = MetaDisposable() + private let valuesDisposable = MetaDisposable() + private let keyFrameGeneratorDisposable = MetaDisposable() + + fileprivate let scrubberValues:Atomic = Atomic(value: VideoScrubberValues(movePos: 0, keyFrame: nil, leftTrim: 0, rightTrim: 1.0, minDist: 0, maxDist: 1, paused: true, suspended: false)) + fileprivate let _scrubberValuesSignal: ValuePromise = ValuePromise(ignoreRepeated: true) + var scrubberValuesSignal: Signal { + return _scrubberValuesSignal.get() |> deliverOnMainQueue + } + + fileprivate func updateValues(_ f: (VideoScrubberValues)->VideoScrubberValues) { + _scrubberValuesSignal.set(scrubberValues.modify(f)) + } + private var firstTime: Bool = true + private var timeObserverToken: Any? + + var completeState: Signal { + return state.get() + } + + private var state: Promise = Promise() + private let localize: String + init(context: AccountContext, asset: AVComposition, track: AVAssetTrack, localize: String) { + self.context = context + self.asset = asset + self.track = track + let size = track.naturalSize.applying(track.preferredTransform) + self.videoSize = NSMakeSize(abs(size.width), abs(size.height)) + self.item = AVPlayerItem(asset: asset) + + self.player = AVPlayer(playerItem: item) + + let videoComposition = AVMutableVideoComposition() + videoComposition.renderSize = videoSize + videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) + let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track) + let instruction = AVMutableVideoCompositionInstruction() + instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: self.asset.duration) + let transform1: CGAffineTransform = track.preferredTransform + transformer.setTransform(transform1, at: CMTime.zero) + instruction.layerInstructions = [transformer] + videoComposition.instructions = [instruction] + self.item.videoComposition = videoComposition + + self.localize = localize + super.init(frame: CGRect(origin: .zero, size: context.window.contentView!.frame.size - NSMakeSize(20, 20))) + self.bar = .init(height: 0) + } + + override open func measure(size: NSSize) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with: contentSize - NSMakeSize(20, 20), animated: false) + } + } + + func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with: contentSize - NSMakeSize(20, 20), animated: animated) + } + } + + + override var dynamicSize: Bool { + return true + } + + override var background: NSColor { + return .clear + } + + override var containerBackground: NSColor { + return .clear + } + override var isVisualEffectBackground: Bool { + return true + } + + override func viewClass() -> AnyClass { + return VideoAvatarModalView.self + } + + private var genericView: VideoAvatarModalView { + return self.view as! VideoAvatarModalView + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + player.pause() + + if let timeObserverToken = timeObserverToken { + player.removeTimeObserver(timeObserverToken) + self.timeObserverToken = nil + } + } + + override func returnKeyAction() -> KeyHandlerResult { + self.state.set(generateVideo(asset, composition: self.currentVideoComposition(), values: self.scrubberValues.with { $0 })) + close() + + return .invoked + } + + + private func currentVideoComposition() -> AVVideoComposition { + let size = self.videoSize + let naturalSize = self.asset.naturalSize + + enum Orientation { + case up, down, right, left + } + + func orientation(for track: AVAssetTrack) -> Orientation { + let t = track.preferredTransform + + if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) { + return .up + } else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) { + return .down + } else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) { + return .right + } else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) { + return .left + } else { + return .up + } + } + + func roundSize(_ numToRound: CGFloat) -> CGFloat { + let numToRound = Int(numToRound) + let remainder = numToRound % 16; + if (remainder == 0) { + return CGFloat(numToRound) + } + return CGFloat((numToRound - 16) + (16 - remainder)); + } + + let rotation: Orientation = orientation(for: track) + + var selectedRect = self.genericView.selectionRectView.selectedRect + let viewSize = self.genericView.playerSize + let coefficient = NSMakeSize(size.width / viewSize.width, size.height / viewSize.height) + + selectedRect = selectedRect.apply(multiplier: coefficient) + + selectedRect.size = NSMakeSize(min(selectedRect.width, selectedRect.height), min(selectedRect.width, selectedRect.height)) + + let videoComposition = AVMutableVideoComposition() + + + + videoComposition.renderSize = NSMakeSize(roundSize(selectedRect.width), roundSize(selectedRect.height)) + videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) + + let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track) + let instruction = AVMutableVideoCompositionInstruction() + + instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: self.asset.duration) + + let point = selectedRect.origin + var finalTransform: CGAffineTransform = CGAffineTransform.identity + + switch rotation { + case .down: + finalTransform = finalTransform + .translatedBy(x: -point.x, y: naturalSize.width - point.y) + .rotated(by: -.pi / 2) + case .left: + finalTransform = finalTransform + .translatedBy(x: naturalSize.width - point.x, y: naturalSize.height - point.y) + .rotated(by: .pi) + case .right: + finalTransform = finalTransform + .translatedBy(x: -point.x, y: -point.y) + .rotated(by: 0) + case .up: + finalTransform = finalTransform + .translatedBy(x: naturalSize.height - point.x, y: -point.y) + .rotated(by: .pi / 2) + } + + transformer.setTransform(finalTransform, at: CMTime.zero) + + instruction.layerInstructions = [transformer] + videoComposition.instructions = [instruction] + + return videoComposition + } + + + deinit { + rectDisposable.dispose() + updateThumbsDisposable.dispose() + valuesDisposable.dispose() + keyFrameGeneratorDisposable.dispose() + NotificationCenter.default.removeObserver(self.item) + + if let timeObserverToken = timeObserverToken { + player.removeTimeObserver(timeObserverToken) + self.timeObserverToken = nil + } + } + + private var generatedRect: NSRect? = nil + + private func updateUserInterface(_ firstTime: Bool) { + let size = NSMakeSize(genericView.scrubberView.frame.height, genericView.scrubberView.frame.height) + + let signal = generateVideoScrubberThumbs(for: asset, composition: currentVideoComposition(), size: size, count: Int(ceil(genericView.scrubberView.frame.width / size.width)), gradually: true, blur: true) + |> delay(0.2, queue: .concurrentDefaultQueue()) + + + let duration = CMTimeGetSeconds(asset.duration) + + let keyFrame = scrubberValues.with { $0.keyFrame } + + let keyFrameSignal: Signal + + if let keyFrame = keyFrame { + keyFrameSignal = generateVideoAvatarPreview(for: asset, composition: self.currentVideoComposition(), highSize: genericView.selectionRectView.selectedRect.size, lowSize: NSMakeSize(30, 30), at: Double(keyFrame) * duration) + |> delay(0.2, queue: .concurrentDefaultQueue()) + |> map { $0.0 } + } else { + keyFrameSignal = .single(nil) + } + + var selectedRect = self.genericView.selectionRectView.selectedRect + let viewSize = self.genericView.playerSize + let coefficient = NSMakeSize(size.width / viewSize.width, size.height / viewSize.height) + + selectedRect = selectedRect.apply(multiplier: coefficient) + + if generatedRect != selectedRect { + updateThumbsDisposable.set(combineLatest(queue: .mainQueue(), signal, keyFrameSignal).start(next: { [weak self] images, keyFrame in + self?.genericView.scrubberView.render(images.0, size: size) + self?.genericView.updateKeyFrameImage(keyFrame) + if self?.firstTime == true { + self?.firstTime = !images.1 + } + self?.generatedRect = selectedRect + })) + } + } + private func applyValuesToPlayer(_ values: VideoScrubberValues) { + if values.movePos > values.rightTrim - (magicNumber + (magicNumber / 2)), !values.paused { + play() + } + if values.paused { + player.rate = 0 + seekToNormal(values) + player.pause() + } else if player.rate == 0, !values.paused { + player.rate = 1 + play() + } + if let keyFrame = values.keyFrame, appliedKeyFrame != keyFrame { + self.runKeyFrameUpdater(keyFrame) + seekToNormal(values) + } else if appliedKeyFrame != nil && values.keyFrame == nil { + self.genericView.setKeyFrame(value: nil, animated: true) + self.appliedKeyFrame = nil + } + } + @discardableResult private func seekToNormal(_ values: VideoScrubberValues) -> CGFloat? { + let duration = CMTimeGetSeconds(asset.duration) + if values.suspended { + self.player.seek(to: CMTimeMakeWithSeconds(TimeInterval(values.movePos) * duration, preferredTimescale: 1000), toleranceBefore: .zero, toleranceAfter: .zero) + return values.keyFrame + } else { + self.player.seek(to: CMTimeMakeWithSeconds(TimeInterval(values.leftTrim + magicNumber) * duration, preferredTimescale: 1000), toleranceBefore: .zero, toleranceAfter: .zero) + return nil + } + } + + private func play() { + player.pause() + let duration = CMTimeGetSeconds(asset.duration) + + if let timeObserverToken = timeObserverToken { + player.removeTimeObserver(timeObserverToken) + self.timeObserverToken = nil + } + + _ = self.scrubberValues.modify { values in + let values = values.withUpdatedPaused(false) + if let result = self.seekToNormal(values) { + return values.withUpdatedMove(result) + } else { + return values.withUpdatedMove(values.leftTrim) + } + } + + let timeScale = CMTimeScale(NSEC_PER_SEC) + let time = CMTime(seconds: 0.016 * 2, preferredTimescale: timeScale) + + + timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] time in + self?.updateValues { current in + if !current.suspended { + return current.withUpdatedMove(CGFloat(CMTimeGetSeconds(time) / duration)) + } else { + return current + } + } + } + + + self.player.play() + } + + private func runKeyFrameUpdater(_ keyFrame: CGFloat) { + let duration = CMTimeGetSeconds(asset.duration) + + let size = genericView.selectionRectView.selectedRect.size + + let signal = generateVideoAvatarPreview(for: self.asset, composition: self.currentVideoComposition(), highSize: size, lowSize: NSMakeSize(30, 30), at: Double(keyFrame) * duration) + |> deliverOnMainQueue + + keyFrameGeneratorDisposable.set(signal.start(next: { [weak self] highRes, lowRes in + self?.genericView.setKeyFrame(value: keyFrame, highRes: highRes, lowRes: lowRes, animated: true, completion: { [weak self] completed in + if completed { + self?.updateValues { + $0.withUpdatedPaused(false) + .withUpdatedMove(keyFrame) + } + self?.updateValues { + $0.withUpdatedSuspended(false) + } + } else { + self?.updateValues { + $0.withUpdatedSuspended(false) + .withUpdatedPaused(false) + } + } + + }, moveToCurrentKeyFrame: { [weak self] keyFrame in + self?.updateValues { + $0.withUpdatedSuspended(true) + .withUpdatedMove(keyFrame) + } + self?.updateValues { [weak self] values in + self?.seekToNormal(values) + return values + } + self?.updateValues { + $0.withUpdatedSuspended(false) + } + }) + self?.appliedKeyFrame = keyFrame + })) + } + + override var closable: Bool { + return false + } + + override func viewDidLoad() { + super.viewDidLoad() + + + genericView.update(self.player, localize: self.localize, size: self.videoSize) + + genericView.cancel.set(handler: { [weak self] _ in + self?.close() + }, for: .Click) + + genericView.ok.set(handler: { [weak self] _ in + _ = self?.returnKeyAction() + }, for: .Click) + + let duration = CMTimeGetSeconds(asset.duration) + + let scrubberSize = genericView.scrubberView.frame.width + + let valueSec = (scrubberSize / CGFloat(duration)) / scrubberSize + + self.updateValues { values in + return values.withUpdatedMinDist(valueSec).withUpdatedMaxDist(valueSec * 10.0).withUpdatedrightTrim(min(1, valueSec * 10.0)) + } + + genericView.scrubberView.updateValues = { [weak self] values in + self?.updateValues { _ in + return values + } + } + + rectDisposable.set(genericView.selectionRectView.updatedRect.start(next: { [weak self] rect in + self?.genericView.selectionRectView.applyRect(rect, force: true, dimensions: .square) + self?.updateUserInterface(self?.firstTime ?? false) + })) + + valuesDisposable.set(self.scrubberValuesSignal.start(next: { [weak self] values in + self?.genericView.scrubberView.apply(values: values) + self?.applyValuesToPlayer(values) + })) + + NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.item, queue: .main) { [weak self] _ in + self?.play() + } + + play() + readyOnce() + } + +} + + + +func selectVideoAvatar(context: AccountContext, path: String, localize: String, signal:@escaping(Signal)->Void) { + let asset = AVURLAsset(url: URL(fileURLWithPath: path)) + let track = asset.tracks(withMediaType: .video).first + if let track = track { + let composition = AVMutableComposition() + guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else { + return + } + do { + try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: track, at: .zero) + let controller = VideoAvatarModalController(context: context, asset: composition, track: track, localize: localize) + showModal(with: controller, for: context.window) + signal(controller.completeState) + } catch { + + } + } +} + + +private func generateVideo(_ asset: AVComposition, composition: AVVideoComposition, values: VideoScrubberValues) -> Signal { + return Signal { subscriber in + + let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality)! + exportSession.outputFileType = .mp4 + exportSession.shouldOptimizeForNetworkUse = true + + let videoPath = NSTemporaryDirectory() + "\(arc4random()).mp4" + let thumbPath = NSTemporaryDirectory() + "\(arc4random()).jpg" + + + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.maximumSize = CGSize(width: 640, height: 640) + imageGenerator.appliesPreferredTrackTransform = true + imageGenerator.requestedTimeToleranceBefore = .zero + imageGenerator.requestedTimeToleranceAfter = .zero + + + imageGenerator.videoComposition = composition + let image = try? imageGenerator.copyCGImage(at: CMTimeMakeWithSeconds(Double(values.keyFrame ?? values.leftTrim) * asset.duration.seconds, preferredTimescale: 1000), actualTime: nil) + if let image = image { + let options = NSMutableDictionary() + options.setValue(640 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) + + let colorQuality: Float = 0.3 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + + let mutableData: CFMutableData = NSMutableData() as CFMutableData + let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, options)! + CGImageDestinationSetProperties(colorDestination, nil) + + CGImageDestinationAddImage(colorDestination, image, options as CFDictionary) + CGImageDestinationFinalize(colorDestination) + + try? (mutableData as Data).write(to: URL(fileURLWithPath: thumbPath)) + + subscriber.putNext(.start(thumb: thumbPath)) + + } + + exportSession.outputURL = URL(fileURLWithPath: videoPath) + + exportSession.videoComposition = composition + + + + let wholeDuration = CMTimeGetSeconds(asset.duration) + + let from = TimeInterval(values.leftTrim) * wholeDuration + let to = TimeInterval(values.rightTrim) * wholeDuration + + let start = CMTimeMakeWithSeconds(from, preferredTimescale: 1000) + let duration = CMTimeMakeWithSeconds(to - from, preferredTimescale: 1000) + + if #available(OSX 10.14, *) { + exportSession.fileLengthLimit = 2 * 1024 * 1024 + } + + let timer = SwiftSignalKit.Timer(timeout: 0.05, repeat: true, completion: { + subscriber.putNext(.progress(exportSession.progress)) + }, queue: .concurrentBackgroundQueue()) + + exportSession.timeRange = CMTimeRangeMake(start: start, duration: duration) + + + exportSession.exportAsynchronously(completionHandler: { [weak exportSession] in + + timer.invalidate() + + if let exportSession = exportSession, exportSession.status == .completed, exportSession.error == nil { + subscriber.putNext(.complete(thumb: thumbPath, video: videoPath, keyFrame: values.keyFrame != nil ? Double(values.keyFrame!) * asset.duration.seconds : nil)) + subscriber.putCompletion() + + } else { + subscriber.putNext(.error) + subscriber.putCompletion() + } + + }) + + + + timer.start() + + return ActionDisposable { + exportSession.cancelExport() + timer.invalidate() + } + } |> runOn(.concurrentBackgroundQueue()) +} + + + +/* + - (UIImageOrientation)getVideoOrientationFromAsset:(AVAsset *)asset + { + AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; + CGSize size = [videoTrack naturalSize]; + CGAffineTransform txf = [videoTrack preferredTransform]; + + if (size.width == txf.tx && size.height == txf.ty) + return UIImageOrientationLeft; //return UIInterfaceOrientationLandscapeLeft; + else if (txf.tx == 0 && txf.ty == 0) + return UIImageOrientationRight; //return UIInterfaceOrientationLandscapeRight; + else if (txf.tx == 0 && txf.ty == size.width) + return UIImageOrientationDown; //return UIInterfaceOrientationPortraitUpsideDown; + else + return UIImageOrientationUp; //return UIInterfaceOrientationPortrait; + } + */ diff --git a/Telegram-Mac/VideoCameraStructures.swift b/Telegram-Mac/VideoCameraStructures.swift index 81a7addbaa..acebc9d556 100644 --- a/Telegram-Mac/VideoCameraStructures.swift +++ b/Telegram-Mac/VideoCameraStructures.swift @@ -15,7 +15,7 @@ enum VideoCameraRecordingStatus : Equatable { case madeThumbnail(CGImage) case stoppingRecording case stopped(thumb: CGImage?) - case finishRecording(path:String, duration:Int, thumb: CGImage?) + case finishRecording(path:String, duration:Int, id: Int64?, thumb: CGImage?) } func ==(lhs: VideoCameraRecordingStatus, rhs: VideoCameraRecordingStatus) -> Bool { diff --git a/Telegram-Mac/VideoEditorScrubbler.swift b/Telegram-Mac/VideoEditorScrubbler.swift new file mode 100644 index 0000000000..7f76a4ff12 --- /dev/null +++ b/Telegram-Mac/VideoEditorScrubbler.swift @@ -0,0 +1,436 @@ +// +// VideoEditorScrubbler.swift +// Telegram +// +// Created by Mikhail Filimonov on 16/07/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +private final class ScrubberMoveView: Control { + private let view = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + view.isEventLess = true + addSubview(view) + view.layer?.cornerRadius = 2 + view.backgroundColor = .white + } + + + override func layout() { + super.layout() + view.frame = bounds + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ScrubberleftTrim: Control { + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + ctx.round(bounds, flags: [.left, .top, .bottom]) + ctx.setFillColor(NSColor.accent.cgColor) + ctx.fill(bounds) + } + + override func layout() { + super.layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ScrubberrightTrim: Control { + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + ctx.round(bounds, flags: [.right, .top, .bottom]) + ctx.setFillColor(NSColor.accent.cgColor) + ctx.fill(bounds) + } + + override func layout() { + super.layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +struct VideoScrubberValues : Equatable { + let movePos: CGFloat + let leftTrim: CGFloat + let rightTrim: CGFloat + let minDist: CGFloat + let maxDist: CGFloat + let paused: Bool + let keyFrame: CGFloat? + let suspended: Bool + init(movePos: CGFloat, keyFrame: CGFloat?, leftTrim: CGFloat, rightTrim: CGFloat, minDist: CGFloat, maxDist: CGFloat, paused: Bool, suspended: Bool) { + self.movePos = movePos + self.keyFrame = keyFrame + self.leftTrim = leftTrim + self.rightTrim = rightTrim + self.minDist = minDist + self.paused = paused + self.maxDist = maxDist + self.suspended = suspended + } + + func withUpdatedMove(_ movePos: CGFloat) -> VideoScrubberValues { + return VideoScrubberValues(movePos: min(max(0, movePos), 1), keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedleftTrim(_ leftTrim: CGFloat) -> VideoScrubberValues { + var keyFrame = self.keyFrame + if let frame = keyFrame, leftTrim > frame { + keyFrame = nil + } + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: min(max(0, leftTrim), 1), rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedrightTrim(_ rightTrim: CGFloat) -> VideoScrubberValues { + var keyFrame = self.keyFrame + if let frame = keyFrame, rightTrim < frame { + keyFrame = nil + } + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: min(max(0, rightTrim), 1), minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedMinDist(_ minDist: CGFloat) -> VideoScrubberValues { + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedMaxDist(_ maxDist: CGFloat) -> VideoScrubberValues { + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedPaused(_ paused: Bool) -> VideoScrubberValues { + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + + func withUpdatedKeyFrame(_ keyFrame: CGFloat?) -> VideoScrubberValues { + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } + func withUpdatedSuspended(_ suspended: Bool) -> VideoScrubberValues { + return VideoScrubberValues(movePos: movePos, keyFrame: keyFrame, leftTrim: leftTrim, rightTrim: rightTrim, minDist: minDist, maxDist: maxDist, paused: paused, suspended: suspended) + } +} + +class VideoEditorScrubblerControl : View, ViewDisplayDelegate { + private let scrubber: ScrubberMoveView + private var imageViewsContainer: View = View() + private let overlay: View = View() + private let distance = Control() + private let leftTrim: ScrubberleftTrim = ScrubberleftTrim(frame: .zero) + private let rightTrim: ScrubberrightTrim = ScrubberrightTrim(frame: .zero) + + private var values = VideoScrubberValues(movePos: 0, keyFrame: nil, leftTrim: 0, rightTrim: 1.0, minDist: 0, maxDist: 1, paused: false, suspended: false) + + var updateValues:((VideoScrubberValues)->Void)? = nil + + required init(frame frameRect: NSRect) { + scrubber = ScrubberMoveView(frame: NSMakeRect(0, 0, 4, frameRect.height)) + super.init(frame: frameRect) + addSubview(self.imageViewsContainer) + imageViewsContainer.layer?.cornerRadius = .cornerRadius + imageViewsContainer.backgroundColor = NSColor.black.withAlphaComponent(0.85) + addSubview(self.scrubber) + + overlay.isEventLess = true + overlay.displayDelegate = self + overlay.layer?.cornerRadius = .cornerRadius + addSubview(overlay) + + addSubview(leftTrim) + addSubview(rightTrim) + + addSubview(distance) + + let shadow = NSShadow() + shadow.shadowBlurRadius = 5 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.2) + shadow.shadowOffset = NSMakeSize(0, 2) + self.scrubber.shadow = shadow + + + let shadow1 = NSShadow() + shadow1.shadowBlurRadius = 5 + shadow1.shadowColor = NSColor.black.withAlphaComponent(0.4) + shadow1.shadowOffset = NSMakeSize(0, 2) + self.shadow = shadow1 + + + func possibleDrag(_ value: CGFloat) -> Bool { + return value >= 0 && value <= 1 + } + func checkDist(_ leftValue: CGFloat, _ rightValue: CGFloat, _ min: CGFloat, _ max: CGFloat) -> Bool { + return (leftValue - rightValue) >= min && (leftValue - rightValue) <= max + } + + var leftTrimStart: NSPoint? = nil + var rightTrimStart: NSPoint? = nil + var distanceStart: NSPoint? = nil + + + leftTrim.set(handler: { control in + leftTrimStart = control.window?.mouseLocationOutsideOfEventStream ?? nil + }, for: .Down) + + leftTrim.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + leftTrimStart = nil + self.updateValues?(self.values.withUpdatedPaused(false)) + }, for: .Up) + + + leftTrim.set(handler: { [weak self] control in + guard let `self` = self, let start = leftTrimStart, let current = control.window?.mouseLocationOutsideOfEventStream else { + return + } + + let difference = start - current + + let width = self.frame.width - control.frame.width + + let newValue = control.frame.origin - difference + + let percent = newValue.x / width + + if checkDist(self.values.rightTrim, percent, self.values.minDist, self.values.maxDist) { + if possibleDrag(percent) { + leftTrimStart = current + control.setFrameOrigin(newValue) + } + if percent != self.values.leftTrim { + self.updateValues?(self.values.withUpdatedleftTrim(percent).withUpdatedPaused(true)) + } + } + }, for: .MouseDragging) + + + rightTrim.set(handler: { control in + rightTrimStart = control.window?.mouseLocationOutsideOfEventStream ?? nil + }, for: .Down) + + rightTrim.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + rightTrimStart = nil + self.updateValues?(self.values.withUpdatedPaused(false)) + }, for: .Up) + + + rightTrim.set(handler: { [weak self] control in + guard let `self` = self, let start = rightTrimStart, let current = control.window?.mouseLocationOutsideOfEventStream else { + return + } + let difference = start - current + let width = self.frame.width - control.frame.width + + let newValue = control.frame.origin - difference + var percent = newValue.x / width + + if percent < 0 && self.values.rightTrim > 0 { + percent = 0 + } + if percent > 1 && self.values.rightTrim < 1 { + percent = 1 + } + + if checkDist(percent, self.values.leftTrim, self.values.minDist, self.values.maxDist) { + if possibleDrag(newValue.x / width) { + rightTrimStart = current + control.setFrameOrigin(newValue) + } + self.updateValues?(self.values.withUpdatedrightTrim(percent).withUpdatedPaused(true)) + } + }, for: .MouseDragging) + + + distance.set(handler: { control in + distanceStart = control.window?.mouseLocationOutsideOfEventStream ?? nil + }, for: .Down) + + + var chooseFrame: Bool = false + + distance.set(handler: { [weak self] control in + guard let `self` = self else { + return + } + if !self.values.paused { + let point = self.imageViewsContainer.convert(control.window?.mouseLocationOutsideOfEventStream ?? .zero, from: nil) + let keyFrame = min(1, max(0, (point.x - self.scrubber.frame.width / 2) / self.imageViewsContainer.frame.width)) + self.updateValues?(self.values.withUpdatedSuspended(true).withUpdatedPaused(true).withUpdatedMove(keyFrame)) + chooseFrame = true + } + + }, for: .LongMouseDown) + + + distance.set(handler: { [weak self] control in + guard let `self` = self else { + return + } + var values = self.values + let point = self.imageViewsContainer.convert(control.window?.mouseLocationOutsideOfEventStream ?? .zero, from: nil) + let keyFrame = min(values.rightTrim, max(values.leftTrim, (point.x - self.scrubber.frame.width / 2) / self.imageViewsContainer.frame.width)) + if !self.values.suspended { + if !values.paused { + let point = self.imageViewsContainer.convert(control.window?.mouseLocationOutsideOfEventStream ?? .zero, from: nil) + let keyFrame = min(1, max(0, (point.x - self.scrubber.frame.width / 2) / self.imageViewsContainer.frame.width)) + if keyFrame != values.keyFrame { + values = values.withUpdatedKeyFrame(keyFrame).withUpdatedMove(keyFrame).withUpdatedPaused(true).withUpdatedSuspended(true) + } + } else { + values = values.withUpdatedPaused(false) + } + } else if self.values.suspended && chooseFrame { + if keyFrame != values.keyFrame { + values = values + .withUpdatedKeyFrame(keyFrame) + .withUpdatedMove(keyFrame) + .withUpdatedPaused(true) + .withUpdatedSuspended(true) + } else { + values = values + .withUpdatedMove(keyFrame) + .withUpdatedPaused(false) + .withUpdatedSuspended(false) + } + } + self.updateValues?(values) + chooseFrame = false + distanceStart = nil + }, for: .Up) + + + distance.set(handler: { [weak self] control in + guard let `self` = self, let start = distanceStart, let current = control.window?.mouseLocationOutsideOfEventStream else { + return + } + + let difference = start - current + let value = difference.x / (self.frame.width - self.leftTrim.frame.width) + + if chooseFrame { + let updatedValue = self.values.movePos - value + if updatedValue >= self.values.leftTrim && updatedValue <= self.values.rightTrim { + let newValues = self.values.withUpdatedMove(updatedValue) + self.updateValues?(newValues) + distanceStart = current + } + } else { + var leftValue = self.values.leftTrim - value + var rightValue = self.values.rightTrim - value + if leftValue < 0 && self.values.leftTrim > 0 { + leftValue = 0 + } + if leftValue > 1 && self.values.leftTrim < 1 { + leftValue = 1 + } + if rightValue < 0 && self.values.rightTrim > 0 { + rightValue = 0 + } + if rightValue > 1 && self.values.rightTrim < 1 { + rightValue = 1 + } + if possibleDrag(leftValue) && possibleDrag(rightValue) && (leftValue != self.values.leftTrim || rightValue != self.values.rightTrim) { + distanceStart = current + let newValues = self.values.withUpdatedleftTrim(leftValue).withUpdatedrightTrim(rightValue).withUpdatedPaused(true) + self.updateValues?(newValues) + } + } + + + + + + }, for: .MouseDragging) + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + if layer == overlay.layer { + ctx.setFillColor(NSColor.black.withAlphaComponent(0.85).cgColor) + if values.leftTrim > 0 { + ctx.fill(NSMakeRect(0, 0, leftTrim.frame.maxX, imageViewsContainer.frame.height)) + } + if values.rightTrim < 1 { + ctx.fill(NSMakeRect(rightTrim.frame.minX, 0, imageViewsContainer.frame.width - self.rightTrim.frame.minX, imageViewsContainer.frame.height)) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func render(_ images: [CGImage], size: NSSize) { + + while imageViewsContainer.subviews.count > images.count { + imageViewsContainer.subviews.removeLast() + } + while imageViewsContainer.subviews.count < images.count { + let view = ImageView() + view.contentGravity = .resizeAspectFill + view.animates = true + imageViewsContainer.addSubview(view) + } + + var x: CGFloat = 0 + + + for (i, image) in images.enumerated() { + (imageViewsContainer.subviews[i] as? ImageView)?.image = image + (imageViewsContainer.subviews[i] as? ImageView)?.frame = NSMakeRect(x, 0, size.width, size.height) + x += size.width + } + } + + func apply(values: VideoScrubberValues) { + let previousPaused = self.values.paused + let previousSuspdended = self.values.suspended + self.values = values + if previousPaused != values.paused || values.suspended != previousSuspdended { + self.scrubber.change(opacity: values.paused && !values.suspended ? 0 : 1, animated: !values.paused) + } + needsLayout = true + overlay.needsDisplay = true + } + + override func layout() { + super.layout() + self.imageViewsContainer.setFrameSize(NSMakeSize(frame.width, frame.height - 4)) + self.imageViewsContainer.center() + + leftTrim.frame = NSMakeRect(values.leftTrim * (frame.width - 8), 2, 8, frame.height - 4) + rightTrim.frame = NSMakeRect(values.rightTrim * (frame.width - 8), 2, 8, frame.height - 4) + + + self.scrubber.frame = CGRect(origin: NSMakePoint(min(max(values.movePos * frame.width, leftTrim.frame.maxX), rightTrim.frame.minX - self.scrubber.frame.width), 0), size: NSMakeSize(4, frame.height)) + + self.distance.frame = NSMakeRect(leftTrim.frame.maxX, imageViewsContainer.frame.minY, imageViewsContainer.frame.width - leftTrim.frame.maxX - (imageViewsContainer.frame.width - rightTrim.frame.minX), imageViewsContainer.frame.height) + + var x: CGFloat = 0 + for view in imageViewsContainer.subviews { + view.setFrameOrigin(NSMakePoint(x, 0)) + x += view.frame.width + } + + overlay.frame = imageViewsContainer.frame + } +} diff --git a/Telegram-Mac/VideoEditorThumbs.swift b/Telegram-Mac/VideoEditorThumbs.swift new file mode 100644 index 0000000000..09e9ccd212 --- /dev/null +++ b/Telegram-Mac/VideoEditorThumbs.swift @@ -0,0 +1,100 @@ +// +// VideoEditorThumbs.swift +// Telegram +// +// Created by Mikhail Filimonov on 16/07/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import TGUIKit + +func generateVideoScrubberThumbs(for asset: AVComposition, composition: AVVideoComposition?, size: NSSize, count: Int, gradually: Bool, blur: Bool) -> Signal<([CGImage], Bool), NoError> { + return Signal { subscriber in + + var cancelled = false + + let videoDuration = asset.duration + + let generator = AVAssetImageGenerator(asset: asset) + + let size = size.multipliedByScreenScale() + + var frameForTimes = [NSValue]() + let sampleCounts = count + let totalTimeLength = Int(videoDuration.seconds * Double(videoDuration.timescale)) + let step = totalTimeLength / sampleCounts + + for i in 0 ..< sampleCounts { + let cmTime = CMTimeMake(value: Int64(i * step), timescale: Int32(videoDuration.timescale)) + frameForTimes.append(NSValue(time: cmTime)) + } + generator.appliesPreferredTrackTransform = true + generator.maximumSize = size + + generator.requestedTimeToleranceBefore = CMTime.zero + generator.requestedTimeToleranceAfter = CMTime.zero + generator.videoComposition = composition + + var images:[(image: CGImage, fake: Bool)] = [] + + var blurred: CGImage? + + generator.generateCGImagesAsynchronously(forTimes: frameForTimes, completionHandler: { (requestedTime, image, actualTime, result, error) in + if let image = image, result == .succeeded { + images.removeAll(where: { $0.fake }) + images.append((image: image, fake: false)) + if images.count < count, let image = images.first?.image, blur { + if blurred == nil { + let thumbnailContext = DrawingContext(size: size, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: size)) + } + telegramFastBlurMore(Int32(size.width), Int32(size.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + blurred = thumbnailContext.generateImage() + } + if let image = blurred { + while images.count < count { + images.append((image: image, fake: true)) + } + } + } + + } + if gradually { + subscriber.putNext((images.map { $0.image }, false)) + } + if images.filter({ !$0.fake }).count == frameForTimes.count, !cancelled { + subscriber.putNext((images.map { $0.image }, true)) + subscriber.putCompletion() + } + }) + return ActionDisposable { [weak generator] in + Queue.concurrentBackgroundQueue().async { + generator?.cancelAllCGImageGeneration() + cancelled = true + } + } + } |> runOn(.concurrentBackgroundQueue()) +} +func generateVideoAvatarPreview(for asset: AVComposition, composition: AVVideoComposition?, highSize: NSSize, lowSize: NSSize, at seconds: Double) -> Signal<(CGImage?, CGImage?), NoError> { + return Signal { subscriber in + let imageGenerator = AVAssetImageGenerator(asset: asset) + let highSize = highSize.multipliedByScreenScale() + imageGenerator.maximumSize = highSize + imageGenerator.appliesPreferredTrackTransform = true + imageGenerator.requestedTimeToleranceBefore = .zero + imageGenerator.requestedTimeToleranceAfter = .zero + imageGenerator.videoComposition = composition + let highRes = try? imageGenerator.copyCGImage(at: CMTimeMakeWithSeconds(seconds, preferredTimescale: 1000), actualTime: nil) + + + subscriber.putNext((highRes, highRes)) + subscriber.putCompletion() + + return ActionDisposable { + } + } |> runOn(.concurrentDefaultQueue()) +} diff --git a/Telegram-Mac/VideoPlayer.swift b/Telegram-Mac/VideoPlayer.swift new file mode 100644 index 0000000000..fa5fc50912 --- /dev/null +++ b/Telegram-Mac/VideoPlayer.swift @@ -0,0 +1,13 @@ +// +// VideoPlayer.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/05/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +class VideoPlayer: NSObject { + +} diff --git a/Telegram-Mac/VideoPlayerProxy.swift b/Telegram-Mac/VideoPlayerProxy.swift new file mode 100755 index 0000000000..99d27231f6 --- /dev/null +++ b/Telegram-Mac/VideoPlayerProxy.swift @@ -0,0 +1,117 @@ +import Foundation +import SwiftSignalKit +import AVFoundation + +private final class VideoPlayerProxyContext { + private let queue: Queue + + var updateVideoInHierarchy: ((Bool) -> Void)? + + var node: MediaPlayerView? { + didSet { + self.node?.takeFrameAndQueue = self.takeFrameAndQueue + self.node?.state = state + self.updateVideoInHierarchy?(node?.videoInHierarchy ?? false) + self.node?.updateVideoInHierarchy = { [weak self] value in + self?.updateVideoInHierarchy?(value) + } + } + } + + var takeFrameAndQueue: (Queue, () -> MediaTrackFrameResult)? { + didSet { + self.node?.takeFrameAndQueue = self.takeFrameAndQueue + } + } + + var state: (timebase: CMTimebase, requestFrames: Bool, rotationAngle: Double, aspect: Double)? { + didSet { + self.node?.state = self.state + } + } + + init(queue: Queue) { + self.queue = queue + } + + deinit { + assert(self.queue.isCurrent()) + } +} + +final class VideoPlayerProxy { + var takeFrameAndQueue: (Queue, () -> MediaTrackFrameResult)? { + didSet { + let updatedTakeFrameAndQueue = self.takeFrameAndQueue + self.withContext { context in + context?.takeFrameAndQueue = updatedTakeFrameAndQueue + } + } + } + + var state: (timebase: CMTimebase, requestFrames: Bool, rotationAngle: Double, aspect: Double)? { + didSet { + let updatedState = self.state + self.withContext { context in + context?.state = updatedState + } + } + } + + private let queue: Queue + private let contextQueue = Queue.mainQueue() + private var contextRef: Unmanaged? + + var visibility: Bool = false + var visibilityUpdated: ((Bool) -> Void)? + + init(queue: Queue) { + self.queue = queue + + self.contextQueue.async { + let context = VideoPlayerProxyContext(queue: self.contextQueue) + context.updateVideoInHierarchy = { [weak self] value in + queue.async { + if let strongSelf = self { + if strongSelf.visibility != value { + strongSelf.visibility = value + strongSelf.visibilityUpdated?(value) + } + } + } + } + self.contextRef = Unmanaged.passRetained(context) + } + } + + deinit { + let contextRef = self.contextRef + self.contextQueue.async { + if let contextRef = contextRef { + let context = contextRef.takeUnretainedValue() + context.state = nil + contextRef.release() + } + } + } + + private func withContext(_ f: @escaping (VideoPlayerProxyContext?) -> Void) { + self.contextQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + f(context) + } else { + f(nil) + } + } + } + + func attachNodeAndRelease(_ nodeRef: Unmanaged) { + self.withContext { context in + if let context = context { + context.node = nodeRef.takeUnretainedValue() + } + nodeRef.release() + } + } +} diff --git a/Telegram-Mac/VideoRecorderModalController.swift b/Telegram-Mac/VideoRecorderModalController.swift index 1421e53ea4..ecf74d1ad9 100644 --- a/Telegram-Mac/VideoRecorderModalController.swift +++ b/Telegram-Mac/VideoRecorderModalController.swift @@ -8,8 +8,9 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import SwiftSignalKit class VideoRecorderModalController: ModalViewController { @@ -30,7 +31,7 @@ class VideoRecorderModalController: ModalViewController { init(chatInteraction: ChatInteraction, pipeline: VideoRecorderPipeline) { self.chatInteraction = chatInteraction self.pipeline = pipeline - super.init(frame: NSMakeRect(0, 0, 210, 210)) + super.init(frame: NSMakeRect(0, 0, 240, 240)) bar = .init(height: 0) } @@ -38,16 +39,14 @@ class VideoRecorderModalController: ModalViewController { return NSTemporaryDirectory() + "video_last_thumbnail.jpg" } + private func saveThumbnail(_ thumb: CGImage) { - var blurred: CGImage = thumb - for _ in 0 ..< 10 { - blurred = blurred.blurred - } + let blurred: CGImage = thumb.blurred _ = blurred.saveToFile(pathForThumbnail) } override func initializer() -> NSView { - return VideoRecorderModalView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), thumbnail: .loadFromFile(pathForThumbnail)) + return VideoRecorderModalView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), thumbnail: .loadFromFile("")) } @@ -60,20 +59,13 @@ class VideoRecorderModalController: ModalViewController { disposable.set((pipeline.statePromise.get() |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { switch status { - case let .finishRecording(path, _, thumb): + case let .finishRecording(path, _, _, thumb): strongSelf.countdownDisposable.set(nil) strongSelf.genericView.updateForPreview(path, preview: thumb) strongSelf.pipeline.stopCapture() case .recording: strongSelf.genericView.didStartedRecording() - strongSelf.countdownDisposable.set(countdown(VideoRecorderPipeline.videoMessageMaxDuration, delay: 0.2).start(next: { [weak strongSelf] value in - strongSelf?.genericView.updateProgress(1.0 - Float(value) / Float(VideoRecorderPipeline.videoMessageMaxDuration)) - - if value <= 0 { - strongSelf?.stopAndMakeRecordedVideo() - } - - })) + strongSelf.runTimer() case .madeThumbnail(let thumb): strongSelf.saveThumbnail(thumb) case let .stopped(thumb): @@ -89,6 +81,23 @@ class VideoRecorderModalController: ModalViewController { } + private func runTimer() { + countdownDisposable.set((pipeline.powerAndDuration.get() |> deliverOnMainQueue).start(next: { [weak self] _, duration in + guard let `self` = self else {return} + self.genericView.updateProgress(Float(duration / VideoRecorderPipeline.videoMessageMaxDuration)) + + if duration >= VideoRecorderPipeline.videoMessageMaxDuration { + self.stopAndMakeRecordedVideo() + if let stateData = self.chatInteraction.presentation.recordingState?.data { + self.chatInteraction.mediaPromise.set(stateData) + } + self.chatInteraction.update({$0.withoutRecordingState()}) + self.close() + } + + })) + } + deinit { disposable.dispose() countdownDisposable.dispose() @@ -109,6 +118,9 @@ class VideoRecorderModalController: ModalViewController { override func escapeKeyAction() -> KeyHandlerResult { + close() + chatInteraction.presentation.recordingState?.stop() + chatInteraction.update({$0.withoutRecordingState()}) return .invoked } diff --git a/Telegram-Mac/VideoRecorderModalView.swift b/Telegram-Mac/VideoRecorderModalView.swift index 1df0c7a733..9a3cd57584 100644 --- a/Telegram-Mac/VideoRecorderModalView.swift +++ b/Telegram-Mac/VideoRecorderModalView.swift @@ -24,7 +24,7 @@ class VideoRecorderModalView: View { } init(frame frameRect: NSRect, thumbnail: CGImage?) { - progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: theme.colors.blueUI, icon: nil, iconInset: NSEdgeInsets(), lineWidth: 4), twist: false) + progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .clear, foregroundColor: theme.colors.accent, icon: nil, iconInset: NSEdgeInsets(), lineWidth: 4), twist: false) super.init(frame: frameRect) addSubview(shadowView) addSubview(captureContainer) @@ -63,7 +63,7 @@ class VideoRecorderModalView: View { } func updateForPreview(_ path:String? = nil, preview: CGImage?) -> Void { - previewPlayer.set(path: path) + previewPlayer.set(data: AVGifData.dataFrom(path)) placeholderView.image = preview previewPlayer.isHidden = path == nil @@ -84,13 +84,13 @@ class VideoRecorderModalView: View { } func updateProgress(_ progress: Float) { - progressView.state = .ImpossibleFetching(progress: progress, force: false) + progressView.state = .ImpossibleFetching(progress: progress, force: true) } func didStartedRecording() { - placeholderView.change(opacity: 0) captureLayer.opacity = 1 - captureLayer.animateAlpha(from: 0, to: 1, duration: 0.2) + placeholderView.change(opacity: 0.0, duration: 1.0) + } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/VideoRecorderPipeline.swift b/Telegram-Mac/VideoRecorderPipeline.swift index 9158faef5a..a27f2d2760 100644 --- a/Telegram-Mac/VideoRecorderPipeline.swift +++ b/Telegram-Mac/VideoRecorderPipeline.swift @@ -7,9 +7,10 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit import Accelerate -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit @@ -30,7 +31,8 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele } func movieRecorderDidFinishRecording(_ recorder: TGVideoCameraMovieRecorder!) { - status = .finishRecording(path: url.path, duration: resultDuration, thumb: thumbnail) + liveUploading?.fileDidChangedSize(true) + status = .finishRecording(path: url.path, duration: resultDuration, id: liveUploading?.id, thumb: thumbnail) } @@ -80,11 +82,12 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele private let startRecordAfterAudioBuffer:Atomic = Atomic(value: false) - static let videoMessageMaxDuration: Double = 59.6 + static let videoMessageMaxDuration: Double = 60 - - init(url:URL) { + private let liveUploading: PreUploadManager? + init(url:URL, liveUploading: PreUploadManager?) { self.url = url + self.liveUploading = liveUploading super.init() recorder = TGVideoCameraMovieRecorder(url: url, delegate: self, callbackQueue: VideoRecorderPipeline.queue.queue) @@ -204,7 +207,7 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer); - if self.skip.modify({min($0 + 1, 24)}) < 24 { + if self.skip.modify({min($0 + 1, 3)}) < 3 { return } @@ -235,7 +238,7 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele } } } - + liveUploading?.fileDidChangedSize(false) } @@ -258,6 +261,7 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele } recorder?.appendVideoPixelBuffer(renderedPixelBuffer, withPresentationTime: timestamp) } + } @@ -298,14 +302,19 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele func stop() { VideoRecorderPipeline.queue.async { + if case .finishRecording = self.status { + return + } self.status = .stoppingRecording self.videoOutput.setSampleBufferDelegate(nil, queue: nil) self.audioOutput.setSampleBufferDelegate(nil, queue: nil) let duration = self.recorder.videoDuration() - if !duration.isNaN { - self.resultDuration = Int(duration) + if !duration.isNaN && duration >= 0.5 { + self.resultDuration = Int(ceil(duration)) + self.recorder.finishRecording() + } else { + self.dispose() } - self.recorder.finishRecording() } } @@ -317,10 +326,13 @@ class VideoRecorderPipeline : NSObject, AVCaptureVideoDataOutputSampleBufferDele func dispose() { VideoRecorderPipeline.queue.async { + if case .finishRecording = self.status { + return + } self.status = .stopped(thumb: self.thumbnail) let duration = self.recorder.videoDuration() if !duration.isNaN { - self.resultDuration = Int(duration) + self.resultDuration = Int(ceil(duration)) } } } diff --git a/Telegram-Mac/Views.swift b/Telegram-Mac/Views.swift index e54d1be60c..78b8af4c30 100644 --- a/Telegram-Mac/Views.swift +++ b/Telegram-Mac/Views.swift @@ -9,7 +9,7 @@ import Cocoa import TGUIKit -class RestrictionWrappedView : View { +class RestrictionWrappedView : Control { let textView: TextView = TextView() let text:String required init(frame frameRect: NSRect) { @@ -21,13 +21,14 @@ class RestrictionWrappedView : View { super.init() addSubview(textView) textView.userInteractionEnabled = false - updateLocalizationAndTheme() + updateLocalizationAndTheme(theme: theme) } - override func updateLocalizationAndTheme() { + override func updateLocalizationAndTheme(theme: PresentationTheme) { self.backgroundColor = theme.colors.background - let layout = TextViewLayout(.initialize(string: text, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 2, alignment: .center) + let layout = TextViewLayout(.initialize(string: text, color: theme.colors.grayText, font: .normal(.text)), alignment: .center) textView.update(layout) + textView.backgroundColor = theme.colors.background } required init?(coder: NSCoder) { @@ -72,7 +73,7 @@ class VideoDurationView : View { ctx.fill(bounds) let f = focus(textNode.0.size) - textNode.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor) + textNode.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } required init?(coder: NSCoder) { @@ -83,3 +84,112 @@ class VideoDurationView : View { fatalError("init(frame:) has not been implemented") } } + +class CornerView : View { + + var positionFlags: LayoutPositionFlags? { + didSet { + needsLayout = true + } + } + + override var backgroundColor: NSColor { + didSet { + layer?.backgroundColor = .clear + } + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + + ctx.round(frame.size, .cornerRadius, positionFlags: positionFlags) + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(bounds) +// if let positionFlags = positionFlags { +// +// let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width +// let miny:CGFloat = 0, midy = frame.height/2.0, maxy = frame.height +// +// ctx.move(to: NSMakePoint(minx, midy)) +// +// var topLeftRadius: CGFloat = .cornerRadius +// var bottomLeftRadius: CGFloat = .cornerRadius +// var topRightRadius: CGFloat = .cornerRadius +// var bottomRightRadius: CGFloat = .cornerRadius +// +// +// if positionFlags.contains(.top) && positionFlags.contains(.left) { +// topLeftRadius = topLeftRadius * 3 + 2 +// } +// if positionFlags.contains(.top) && positionFlags.contains(.right) { +// topRightRadius = topRightRadius * 3 + 2 +// } +// if positionFlags.contains(.bottom) && positionFlags.contains(.left) { +// bottomLeftRadius = bottomLeftRadius * 3 + 2 +// } +// if positionFlags.contains(.bottom) && positionFlags.contains(.right) { +// bottomRightRadius = bottomRightRadius * 3 + 2 +// } +// +// ctx.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) +// ctx.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) +// ctx.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topLeftRadius) +// ctx.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topRightRadius) +// +// ctx.closePath() +// ctx.clip() +// } +// +// ctx.setFillColor(backgroundColor.cgColor) +// ctx.fill(bounds) +// + } + +} + + +class SearchTitleBarView : TitledBarView { + private var search:ImageButton = ImageButton() + init(controller: ViewController, title:NSAttributedString, handler:@escaping() ->Void) { + super.init(controller: controller, title) + search.set(handler: { _ in + handler() + }, for: .Click) + addSubview(search) + updateLocalizationAndTheme(theme: theme) + } + + func updateSearchVisibility(_ visible: Bool) { + if visible { + self.search.isHidden = false + } + search.change(opacity: visible ? 1 : 0, animated: true, completion: { [weak self] _ in + self?.search.isHidden = !visible + }) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + let theme = (theme as! TelegramPresentationTheme) + search.set(image: theme.icons.chatSearch, for: .Normal) + search.set(image: theme.icons.chatSearchActive, for: .Highlight) + + + _ = search.sizeToFit() + backgroundColor = theme.colors.background + needsLayout = true + } + + override func layout() { + super.layout() + search.centerY(x: frame.width - search.frame.width) + } + + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/VoiceCallSettings.swift b/Telegram-Mac/VoiceCallSettings.swift index 9cecca430d..f3bb4a6485 100644 --- a/Telegram-Mac/VoiceCallSettings.swift +++ b/Telegram-Mac/VoiceCallSettings.swift @@ -7,8 +7,10 @@ // import Cocoa -import PostboxMac -import SwiftSignalKitMac +import Postbox +import SwiftSignalKit +import TelegramCore +import SyncCore public enum VoiceCallDataSaving: Int32 { case never @@ -17,22 +19,42 @@ public enum VoiceCallDataSaving: Int32 { } public struct VoiceCallSettings: PreferencesEntry, Equatable { - public let dataSaving: VoiceCallDataSaving + + let inputDeviceId: String? + let outputDeviceId: String? + let muteSounds: Bool + public static var defaultSettings: VoiceCallSettings { - return VoiceCallSettings(dataSaving: .never) + return VoiceCallSettings(inputDeviceId: nil, outputDeviceId: nil, muteSounds: true) } - init(dataSaving: VoiceCallDataSaving) { - self.dataSaving = dataSaving + init(inputDeviceId: String?, outputDeviceId: String?, muteSounds: Bool) { + self.inputDeviceId = inputDeviceId + self.outputDeviceId = outputDeviceId + self.muteSounds = muteSounds } public init(decoder: PostboxDecoder) { - self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))! + self.inputDeviceId = decoder.decodeOptionalStringForKey("i") + self.outputDeviceId = decoder.decodeOptionalStringForKey("o") + self.muteSounds = decoder.decodeInt32ForKey("m", orElse: 1) == 1 } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds") + if let inputDeviceId = inputDeviceId { + encoder.encodeString(inputDeviceId, forKey: "i") + } else { + encoder.encodeNil(forKey: "i") + } + + if let outputDeviceId = outputDeviceId { + encoder.encodeString(outputDeviceId, forKey: "o") + } else { + encoder.encodeNil(forKey: "o") + } + + encoder.encodeInt32(muteSounds ? 1 : 0, forKey: "m") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -43,18 +65,23 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable { } } - public static func ==(lhs: VoiceCallSettings, rhs: VoiceCallSettings) -> Bool { - return lhs.dataSaving == rhs.dataSaving + + func withUpdatedInputDeviceId(_ inputDeviceId: String?) -> VoiceCallSettings { + return VoiceCallSettings(inputDeviceId: inputDeviceId, outputDeviceId: self.outputDeviceId, muteSounds: self.muteSounds) } - func withUpdatedDataSaving(_ dataSaving: VoiceCallDataSaving) -> VoiceCallSettings { - return VoiceCallSettings(dataSaving: dataSaving) + func withUpdatedOutputDeviceId(_ outputDeviceId: String?) -> VoiceCallSettings { + return VoiceCallSettings(inputDeviceId: self.inputDeviceId, outputDeviceId: outputDeviceId, muteSounds: self.muteSounds) + } + + func withUpdatedMuteSounds(_ muteSounds: Bool) -> VoiceCallSettings { + return VoiceCallSettings(inputDeviceId: self.inputDeviceId, outputDeviceId: self.outputDeviceId, muteSounds: muteSounds) } } -func updateVoiceCallSettingsSettingsInteractively(postbox: Postbox, _ f: @escaping (VoiceCallSettings) -> VoiceCallSettings) -> Signal { - return postbox.modify { modifier -> Void in - modifier.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.voiceCallSettings, { entry in +func updateVoiceCallSettingsSettingsInteractively(accountManager: AccountManager, _ f: @escaping (VoiceCallSettings) -> VoiceCallSettings) -> Signal { + return accountManager.transaction { transaction -> Void in + transaction.updateSharedData(ApplicationSharedPreferencesKeys.voiceCallSettings, { entry in let currentSettings: VoiceCallSettings if let entry = entry as? VoiceCallSettings { currentSettings = entry @@ -65,3 +92,10 @@ func updateVoiceCallSettingsSettingsInteractively(postbox: Postbox, _ f: @escapi }) } } + + +func voiceCallSettings(_ accountManager: AccountManager) -> Signal { + return accountManager.sharedData(keys: [ApplicationSharedPreferencesKeys.voiceCallSettings]) |> map { view in + return view.entries[ApplicationSharedPreferencesKeys.voiceCallSettings] as? VoiceCallSettings ?? VoiceCallSettings.defaultSettings + } +} diff --git a/Telegram-Mac/VoipDerivedState.swift b/Telegram-Mac/VoipDerivedState.swift new file mode 100644 index 0000000000..879b9910fa --- /dev/null +++ b/Telegram-Mac/VoipDerivedState.swift @@ -0,0 +1,53 @@ +// +// VoipDerivedState.swift +// Telegram +// +// Created by Mikhail Filimonov on 23/06/2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Foundation +import Postbox +import SwiftSignalKit + +public struct VoipDerivedState: Equatable, PreferencesEntry { + public var data: Data + + public static var `default`: VoipDerivedState { + return VoipDerivedState(data: Data()) + } + + public init(data: Data) { + self.data = data + } + + public init(decoder: PostboxDecoder) { + self.data = decoder.decodeDataForKey("data") ?? Data() + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeData(self.data, forKey: "data") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? VoipDerivedState { + return self == to + } else { + return false + } + } +} + +public func updateVoipDerivedStateInteractively(postbox: Postbox, _ f: @escaping (VoipDerivedState) -> VoipDerivedState) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.voipDerivedState, { entry in + let currentSettings: VoipDerivedState + if let entry = entry as? VoipDerivedState { + currentSettings = entry + } else { + currentSettings = .default + } + return f(currentSettings) + }) + } +} diff --git a/Telegram-Mac/WPArticleContentView.swift b/Telegram-Mac/WPArticleContentView.swift index fba9fb0108..d679a1e320 100644 --- a/Telegram-Mac/WPArticleContentView.swift +++ b/Telegram-Mac/WPArticleContentView.swift @@ -8,25 +8,45 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit class WPArticleContentView: WPContentView { private var durationView:VideoDurationView? private var progressIndicator:ProgressIndicator? private(set) var imageView:TransformImageView? + private(set) var gradientView: BackgroundView? private var playIcon:ImageView? private let openExternalDisposable:MetaDisposable = MetaDisposable() private let loadingStatusDisposable: MetaDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private let statusDisposable = MetaDisposable() + private var countAccessoryView: ChatMessageAccessoryView? + private var downloadIndicator: RadialProgressView? + + private var groupedContents: [ChatMediaContentView] = [] + private let groupedContentView: View = View() override var backgroundColor: NSColor { didSet { self.setNeedsDisplay() } } - + override func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let _ = imageView, let content = content as? WPArticleLayout, content.isFullImageSize, let image = content.content.image { + return (.image(ImageMediaReference.webPage(webPage: WebpageReference(content.webPage), media: image), ImagePreviewModalView.self), imageView) + } + return nil + } + + override func previewMediaIfPossible() -> Bool { + guard let window = self.kitWindow, let content = content as? WPArticleLayout, content.isFullImageSize, let table = content.table, let imageView = imageView, imageView._mouseInside(), playIcon == nil, !content.hasInstantPage else {return false} + _ = startModalPreviewHandle(table, window: window, context: content.context) + return true + } required public init() { super.init() @@ -35,6 +55,8 @@ class WPArticleContentView: WPContentView { deinit { openExternalDisposable.dispose() loadingStatusDisposable.dispose() + statusDisposable.dispose() + fetchDisposable.dispose() } override func viewDidMoveToSuperview() { @@ -56,17 +78,29 @@ class WPArticleContentView: WPContentView { fatalError("init(frame:) has not been implemented") } + override func updateMouse() { + super.updateMouse() + for content in groupedContentView.subviews.compactMap({$0 as? ChatMediaContentView}) { + content.updateMouse() + } + } + func open() { if let content = content?.content, let layout = self.content, let window = kitWindow { + + if layout.hasInstantPage { + showInstantPage(InstantPageViewController(layout.context, webPage: layout.parent.media[0] as! TelegramMediaWebpage, message: layout.parent.text)) + return + } + if ExternalVideoLoader.isPlayable(content) { - openExternalDisposable.set((sharedVideoLoader.status(for: content) |> deliverOnMainQueue).start(next: { (status) in if let status = status { switch status { case .fail: execute(inapp: .external(link: content.url, false)) case .loaded: - showChatGallery(account: layout.account, message: layout.parent, layout.table) + showChatGallery(context: layout.context, message: layout.parent, layout.table) default: break } @@ -77,19 +111,53 @@ class WPArticleContentView: WPContentView { return } if content.embedType == "iframe" { - showModal(with: WebpageModalController(content:content,account:layout.account), for: window) - } else if (content.type == "video" && content.type == "video/mp4") || content.type == "photo" { - showChatGallery(account: layout.account, message: layout.parent, layout.table) - } else { + showModal(with: WebpageModalController(content:content, context: layout.context), for: window) + } else if layout.isGalleryAssemble { + showChatGallery(context: layout.context, message: layout.parent, layout.table, type: .alone) + } else if let wallpaper = layout.wallpaper { + execute(inapp: wallpaper) + } else if let link = layout.themeLink { + execute(inapp: link) + } else if !content.url.isEmpty { execute(inapp: .external(link: content.url, false)) } } } + func fetch() { + if let layout = content as? WPArticleLayout { + if let _ = layout.wallpaper, let file = layout.content.file { + + fetchDisposable.set(fetchedMediaResource(mediaBox: layout.context.account.postbox.mediaBox, reference: MediaResourceReference.wallpaper(wallpaper: layout.wallpaperReference, resource: file.resource)).start()) + } else if let image = layout.content.image { + fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: layout.context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(layout.webPage), media: image)).start()) + } else if layout.isTheme, let file = layout.content.file { + fetchDisposable.set(fetchedMediaResource(mediaBox: layout.context.account.postbox.mediaBox, reference: MediaResourceReference.wallpaper(wallpaper: layout.wallpaperReference, resource: file.resource)).start()) + } + } + } + + func cancelFetching() { + if let layout = content as? WPArticleLayout { + if let _ = layout.wallpaper, let file = layout.content.file { + fileCancelInteractiveFetch(account: layout.context.account, file: file) + } else if let image = layout.content.image { + chatMessagePhotoCancelInteractiveFetch(account: layout.context.account, photo: image) + } else if layout.isTheme, let file = layout.content.file { + fileCancelInteractiveFetch(account: layout.context.account, file: file) + } + fetchDisposable.set(nil) + } + } + override func mouseUp(with event: NSEvent) { if let imageView = imageView, imageView._mouseInside(), event.clickCount == 1 { - open() + if let downloadProgressView = downloadIndicator { + downloadProgressView.fetchControls?.fetch() + } else { + open() + } } else { super.mouseUp(with: event) } @@ -97,9 +165,69 @@ class WPArticleContentView: WPContentView { + + override func update(with layout: WPLayout) { - + let newLayout = self.content?.content.displayUrl != layout.content.displayUrl if let layout = layout as? WPArticleLayout { + + let synchronousLoad = layout.approximateSynchronousValue + + if let groupLayout = layout.groupLayout { + addSubview(groupedContentView) + groupedContentView.setFrameSize(groupLayout.dimensions) + + if groupedContents.count > groupLayout.count { + let contentCount = groupedContents.count + let layoutCount = groupLayout.count + + for i in layoutCount ..< contentCount { + groupedContents[i].removeFromSuperview() + } + groupedContents = groupedContents.subarray(with: NSMakeRange(0, layoutCount)) + + for i in 0 ..< groupedContents.count { + if !groupedContents[i].isKind(of: groupLayout.contentNode(for: i)) { + let node = groupLayout.contentNode(for: i) + let view = node.init(frame:NSZeroRect) + replaceSubview(groupedContents[i], with: view) + groupedContents[i] = view + } + } + } else if groupedContents.count < groupLayout.count { + let contentCount = groupedContents.count + for i in contentCount ..< groupLayout.count { + let node = groupLayout.contentNode(for: i) + let view = node.init(frame:NSZeroRect) + groupedContents.append(view) + } + } + + for content in groupedContents { + groupedContentView.addSubview(content) + } + + assert(groupedContents.count == groupLayout.count) + + for i in 0 ..< groupLayout.count { + groupedContents[i].change(size: groupLayout.frame(at: i).size, animated: false) + let positionFlags: LayoutPositionFlags = groupLayout.position(at: i) + + + groupedContents[i].update(with: groupLayout.messages[i].media[0], size: groupLayout.frame(at: i).size, context: layout.context, parent: layout.parent.withUpdatedGroupingKey(groupLayout.messages[i].groupingKey), table: layout.table, parameters: layout.parameters[i], animated: false, positionFlags: positionFlags, approximateSynchronousValue: synchronousLoad) + + groupedContents[i].change(pos: groupLayout.frame(at: i).origin, animated: false) + } + + } else { + while !groupedContents.isEmpty { + groupedContents[0].removeFromSuperview() + groupedContents.removeFirst() + } + groupedContentView.removeFromSuperview() + } + + if ExternalVideoLoader.isPlayable(layout.content) { loadingStatusDisposable.set((sharedVideoLoader.status(for: layout.content) |> deliverOnMainQueue).start(next: { [weak self] status in if let status = status , let strongSelf = self { @@ -110,6 +238,7 @@ class WPArticleContentView: WPContentView { // self?.progressIndicator?.set(color: .white) strongSelf.imageView?.addSubview((strongSelf.progressIndicator)!) } + strongSelf.progressIndicator?.center() strongSelf.progressIndicator?.animates = true default: strongSelf.progressIndicator?.animates = false @@ -125,48 +254,196 @@ class WPArticleContentView: WPContentView { progressIndicator = nil } - - var updateImageSignal:Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - if self.content?.content.image != layout.content.image { - if let image = layout.content.image { - updateImageSignal = chatWebpageSnippetPhoto(account: layout.account, photo: image, scale: backingScaleFactor, small:layout.smallThumb) - - if imageView == nil { - imageView = TransformImageView() - imageView?.alphaTransitionOnFirstUpdate = true - self.addSubview(imageView!) - } - - if ExternalVideoLoader.isPlayable(layout.content) { - if playIcon == nil { - playIcon = ImageView() - imageView?.addSubview(playIcon!) + var image = layout.content.image + if layout.content.image == nil, let file = layout.content.file, let dimension = layout.imageSize { + var representations: [TelegramMediaImageRepresentation] = [] + representations.append(contentsOf: file.previewRepresentations) + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimension), resource: file.resource)) + image = TelegramMediaImage(imageId: file.id ?? MediaId(namespace: 0, id: arc4random64()), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: file.partialReference, flags: []) + + } + var updateImageSignal:Signal? + if let image = image { + if layout.wallpaper != nil || layout.isTheme { + let isPattern: Bool + if let settings = layout.content.themeSettings, let wallpaper = settings.wallpaper { + switch wallpaper { + case let .file(_, _, _, _, pattern, _, _, _, _): + isPattern = pattern + default: + isPattern = false } - playIcon?.image = ExternalVideoLoader.playIcon(layout.content) - playIcon?.sizeToFit() } else { - playIcon?.removeFromSuperview() - playIcon = nil + isPattern = layout.isPatternWallpaper } - - if let imageSize = layout.imageArguments?.imageSize { - imageView?.setSignal(signal: cachedMedia(media: image, size: imageSize, scale: backingScaleFactor)) - - if let updateImageSignal = updateImageSignal, imageView?.layer?.contents == nil { - imageView?.setSignal(account: layout.account, signal: updateImageSignal, cacheImage: { [weak self] signal in - if let strongSelf = self { - return cacheMedia(signal: signal, media: image, size: imageSize, scale: strongSelf.backingScaleFactor) + updateImageSignal = chatWallpaper(account: layout.context.account, representations: image.representations, file: layout.content.file, webpage: layout.webPage, mode: .thumbnail, isPattern: isPattern, autoFetchFullSize: true, scale: backingScaleFactor, isBlurred: false, synchronousLoad: false) + } else { + updateImageSignal = chatWebpageSnippetPhoto(account: layout.context.account, imageReference: ImageMediaReference.webPage(webPage: WebpageReference(layout.webPage), media: image), scale: backingScaleFactor, small: layout.smallThumb) + } + + if imageView == nil { + imageView = TransformImageView() + self.addSubview(imageView!) + } + + let closestRepresentation = image.representationForDisplayAtSize(PixelDimensions(1280, 1280)) + + if let closestRepresentation = closestRepresentation { + statusDisposable.set((layout.context.account.postbox.mediaBox.resourceStatus(closestRepresentation.resource, approximateSynchronousValue: synchronousLoad) |> deliverOnMainQueue).start(next: { [weak self] status in + + guard let `self` = self else {return} + + var initProgress: Bool = false + var state: RadialProgressState = .None + switch status { + case .Fetching: + state = .Fetching(progress: 0.3, force: false) + initProgress = true + case .Local: + state = .Fetching(progress: 1.0, force: false) + case .Remote: + initProgress = true + state = .Remote + } + if initProgress { + + self.playIcon?.removeFromSuperview() + self.playIcon = nil + + if self.downloadIndicator == nil { + self.downloadIndicator = RadialProgressView() + } + self.imageView?.addSubview(self.downloadIndicator!) + self.downloadIndicator!.center() + + } else { + + let playable = ExternalVideoLoader.isPlayable(layout.content) + if layout.isFullImageSize, let icon = ExternalVideoLoader.playIcon(layout.content) { + if self.playIcon == nil { + self.playIcon = ImageView() + self.imageView?.addSubview(self.playIcon!) + } + self.playIcon?.image = icon + self.playIcon?.sizeToFit() + } else { + self.playIcon?.removeFromSuperview() + self.playIcon = nil + } + + if let progressView = self.downloadIndicator { + progressView.state = state + + self.downloadIndicator = nil + if playable { + progressView.removeFromSuperview() } else { - return .complete() + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) } - }) + } } + + self.downloadIndicator?.fetchControls = FetchControls(fetch: { [weak self] in + switch status { + case .Remote: + self?.fetch() + case .Fetching: + self?.cancelFetching() + case .Local: + self?.open() + } + }) + + self.downloadIndicator?.state = state + self.needsLayout = true + + })) + } else { + statusDisposable.set(nil) + downloadIndicator?.removeFromSuperview() + downloadIndicator = nil + + } + + + + + if let arguments = layout.imageArguments, let imageView = imageView { + imageView.set(arguments: arguments) + imageView.setSignal(signal: cachedMedia(media: image, arguments: arguments, scale: backingScaleFactor), clearInstantly: newLayout) + + if let updateImageSignal = updateImageSignal, !imageView.isFullyLoaded { + imageView.setSignal(updateImageSignal, animate: true, cacheImage: { result in + cacheMedia(result, media: image, arguments: arguments, scale: System.backingScale) + }) } + } + + } else if let palette = layout.content.crossplatformPalette, let wallpaper = layout.content.crossplatformWallpaper, let settings = layout.content.themeSettings { + updateImageSignal = crossplatformPreview(account: layout.context.account, palette: palette, wallpaper: wallpaper, mode: .thumbnail) + + + if imageView == nil { + imageView = TransformImageView() + self.addSubview(imageView!) + } + + if let arguments = layout.imageArguments, let imageView = imageView { + imageView.set(arguments: arguments) + imageView.setSignal(signal: cachedMedia(media: settings, arguments: arguments, scale: backingScaleFactor), clearInstantly: newLayout) - } else { + if let updateImageSignal = updateImageSignal, !imageView.isFullyLoaded { + imageView.setSignal(updateImageSignal, animate: true, cacheImage: { result in + cacheMedia(result, media: settings, arguments: arguments, scale: System.backingScale) + }) + } + } + } else { + + var removeImageView: Bool = true + var removeGradientView: Bool = true + if let wallpaper = layout.wallpaper { + switch wallpaper { + case let .wallpaper(_, _, preview): + switch preview { + case let .color(color): + if imageView == nil { + imageView = TransformImageView() + self.addSubview(imageView!) + } + imageView?.layer?.cornerRadius = .cornerRadius + imageView?.background = color + removeImageView = false + case let .gradient(top, bottom, gradient): + if gradientView == nil { + gradientView = BackgroundView(frame: NSZeroRect) + self.addSubview(gradientView!) + } + gradientView?.layer?.cornerRadius = .cornerRadius + gradientView?.backgroundMode = .gradient(top: top, bottom: bottom, rotation: gradient) + removeImageView = true + removeGradientView = false + default: + break + } + default: + break + } + } + if removeImageView { imageView?.removeFromSuperview() imageView = nil } + if removeGradientView { + gradientView?.removeFromSuperview() + gradientView = nil + } + downloadIndicator?.removeFromSuperview() + downloadIndicator = nil } @@ -182,11 +459,25 @@ class WPArticleContentView: WPContentView { durationView?.removeFromSuperview() durationView = nil } - + + if let mediaCount = layout.mediaCount { + if countAccessoryView == nil { + countAccessoryView = ChatMessageAccessoryView(frame: NSZeroRect) + imageView?.addSubview(countAccessoryView!) + } + countAccessoryView?.updateText(L10n.chatWebpageMediaCount(1, mediaCount), maxWidth: 40, status: nil, isStreamable: false) + } else { + countAccessoryView?.removeFromSuperview() + countAccessoryView = nil + } } super.update(with: layout) + if let layout = layout as? WPArticleLayout, layout.isAutoDownloable { + fetch() + } + } override func layout() { @@ -200,16 +491,24 @@ class WPArticleContentView: WPContentView { playIcon?.isHidden = progressIndicator != nil + if groupedContentView.superview != nil { + var origin:NSPoint = NSZeroPoint + if let textLayout = layout.textLayout { + origin.y += textLayout.layoutSize.height + 6.0 + } + groupedContentView.setFrameOrigin(origin) + } + if let imageView = imageView { - - progressIndicator?.center() - if let arguments = layout.imageArguments { imageView.set(arguments: arguments) imageView.setFrameSize(arguments.boundingSize) } + progressIndicator?.center() + downloadIndicator?.center() + var origin:NSPoint = NSMakePoint(layout.contentRect.width - imageView.frame.width - 10, 0) if layout.textLayout?.cutout == nil { @@ -223,19 +522,35 @@ class WPArticleContentView: WPContentView { imageView.setFrameOrigin(origin.x, origin.y) playIcon?.center() + if let durationView = durationView { - durationView.setFrameOrigin(imageView.frame.width - durationView.frame.width - 10, 10) + durationView.setFrameOrigin(imageView.frame.width - durationView.frame.width - 10, imageView.frame.height - durationView.frame.height - 10) + } + if let countAccessoryView = countAccessoryView { + countAccessoryView.setFrameOrigin(imageView.frame.width - countAccessoryView.frame.width - 10, 10) } } + if let gradientView = gradientView { + if let arguments = layout.imageArguments { + gradientView.setFrameSize(arguments.boundingSize) + } + let origin:NSPoint = NSMakePoint(layout.contentRect.width - gradientView.frame.width, 0) + gradientView.setFrameOrigin(origin.x, origin.y) + } } } - override var interactionContentView:NSView { - return self.imageView ?? self + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + return !groupedContentView.subviews.isEmpty ? groupedContentView : self.imageView ?? self } - + override func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + if !groupedContents.isEmpty { + return groupedContentView.convert(point, from: nil) + } + return super.convertWindowPointToContent(point) + } } diff --git a/Telegram-Mac/WPArticleLayout.swift b/Telegram-Mac/WPArticleLayout.swift index 58fd479ae5..78ab485678 100644 --- a/Telegram-Mac/WPArticleLayout.swift +++ b/Telegram-Mac/WPArticleLayout.swift @@ -7,9 +7,12 @@ // import Cocoa -import TelegramCoreMac -import PostboxMac +import TelegramCore +import SyncCore +import Postbox import TGUIKit +import SwiftSignalKit + class WPArticleLayout: WPLayout { @@ -20,81 +23,258 @@ class WPArticleLayout: WPLayout { private(set) var duration:(TextNodeLayout, TextNode)? private let durationAttributed:NSAttributedString? - override init(with content: TelegramMediaWebpageLoadedContent, account:Account, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat) { + private let fetchDisposable = MetaDisposable() + private let downloadSettings: AutomaticMediaDownloadSettings + + private(set) var groupLayout: GroupedLayout? + private(set) var parameters:[ChatMediaLayoutParameters] = [] + + init(with content: TelegramMediaWebpageLoadedContent, context: AccountContext, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat, presentation: WPLayoutPresentation, approximateSynchronousValue: Bool, downloadSettings: AutomaticMediaDownloadSettings, autoplayMedia: AutoplayMediaPreferences) { if let duration = content.duration { self.durationAttributed = .initialize(string: String.durationTransformed(elapsed: duration), color: .white, font: .normal(.text)) } else { durationAttributed = nil } - super.init(with: content, account:account, chatInteraction: chatInteraction, parent:parent, fontSize: fontSize) + var content = content + if content.type == "telegram_theme" { + for attr in content.attributes { + switch attr { + case let .theme(theme): + for file in theme.files { + if file.mimeType == "application/x-tgtheme-macos", !file.previewRepresentations.isEmpty { + content = content.withUpdatedFile(file) + } + } + case .unsupported: + break + } + } + + } + + self.downloadSettings = downloadSettings + super.init(with: content, context: context, chatInteraction: chatInteraction, parent:parent, fontSize: fontSize, presentation: presentation, approximateSynchronousValue: approximateSynchronousValue) - if let image = content.image { + if let mediaCount = mediaCount, mediaCount > 1 { + var instantMedias = Array(instantPageMedias(for: parent.media[0] as! TelegramMediaWebpage).suffix(10)) + + if let file = content.file { + let page = InstantPageMedia(index: 0, media: file, webpage: parent.media[0] as! TelegramMediaWebpage, url: nil, caption: nil, credit: nil) + for i in 0 ..< instantMedias.count { + instantMedias[i] = instantMedias[i].withUpdatedIndex(i + 1) + } + instantMedias.insert(page, at: 0) + } else if let image = content.image { + let page = InstantPageMedia(index: 0, media: image, webpage: parent.media[0] as! TelegramMediaWebpage, url: nil, caption: nil, credit: nil) + for i in 0 ..< instantMedias.count { + instantMedias[i] = instantMedias[i].withUpdatedIndex(i + 1) + } + instantMedias.insert(page, at: 0) + } else { + for i in 0 ..< instantMedias.count { + instantMedias[i] = instantMedias[i].withUpdatedIndex(i) + } + } - if let dimensions = largestImageRepresentation(image.representations)?.dimensions { + var messages:[Message] = [] + let groupingKey = arc4random64() + for i in 0 ..< instantMedias.count { + let media = instantMedias[i].media + let message = parent.withUpdatedMedia([media]).withUpdatedStableId(arc4random()).withUpdatedId(MessageId(peerId: chatInteraction.peerId, namespace: Namespaces.Message.Local, id: MessageId.Id(i))).withUpdatedGroupingKey(groupingKey) + messages.append(message) + + weak var weakParameters:ChatMediaGalleryParameters? + + let parameters = ChatMediaGalleryParameters(showMedia: { [weak self] _ in + guard let `self` = self else {return} +// + showInstantViewGallery(context: context, medias: instantMedias, firstIndex: i, firstStableId: ChatHistoryEntryId.message(parent), parent: parent, self.table, weakParameters) + + }, showMessage: { [weak chatInteraction] _ in + chatInteraction?.focusMessageId(nil, parent.id, .center(id: 0, innerId: nil, animated: true, focus: .init(focus: true), inset: 0)) + }, isWebpage: chatInteraction.isLogInteraction, presentation: .make(for: message, account: context.account, renderType: presentation.renderType), media: media, automaticDownload: downloadSettings.isDownloable(message), autoplayMedia: autoplayMedia) + + weakParameters = parameters + + self.parameters.append(parameters) + } + groupLayout = GroupedLayout(messages) + } + + if let image = content.image, groupLayout == nil { + if let dimensions = largestImageRepresentation(image.representations)?.dimensions.size { imageSize = dimensions } - } + if let file = content.file, groupLayout == nil { + if let dimensions = file.dimensions?.size { + imageSize = dimensions + } else if isTheme { + imageSize = NSMakeSize(200, 200) + } + } else if isTheme { + imageSize = NSMakeSize(260, 260) + } + if let wallpaper = wallpaper { + switch wallpaper { + case let .wallpaper(_, _, preview): + switch preview { + case .color: + imageSize = NSMakeSize(150, 150) + case .gradient: + imageSize = NSMakeSize(200, 200) + default: + break + } + default: + break + } + } + + if ExternalVideoLoader.isPlayable(content) { + _ = sharedVideoLoader.fetch(for: content).start() + } + } + + var isAutoDownloable: Bool { + return downloadSettings.isDownloable(parent) + } + + deinit { + fetchDisposable.dispose() } private let mediaTypes:[String] = ["photo","video"] + private let fullSizeSites:[String] = ["instagram","twitter"] + + var isFullImageSize: Bool { + if content.type == "telegram_background" || content.type == "telegram_theme" { + return true + } + let website = content.websiteName?.lowercased() + if let type = content.type, mediaTypes.contains(type) || (fullSizeSites.contains(website ?? "") || content.instantPage != nil) || content.text == nil { + if let imageSize = imageSize { + if imageSize.width < 200 { + return false + } + } + return true + } + return content.text == nil || content.text!.trimmed.isEmpty + } override func measure(width: CGFloat) { - super.measure(width: width) - - var contentSize:NSSize = NSMakeSize(width, 0) - - if let imageSize = imageSize, let type = content.type, mediaTypes.contains(type) { - contrainedImageSize = imageSize.aspectFitted(NSMakeSize(min(width - insets.left, 320), 320)) - textLayout?.cutout = nil - smallThumb = false - contentSize.height += contrainedImageSize.height - if textLayout != nil { - contentSize.height += 6 + if oldWidth != width { + super.measure(width: width) + + let maxw = max(min(320, width - 50), 230) + + var contentSize:NSSize = NSMakeSize(width - insets.left, 0) + + if let groupLayout = groupLayout { + groupLayout.measure(NSMakeSize(max(contentSize.width, 260), maxw)) + + contentSize.height += groupLayout.dimensions.height + 6 + contentSize.width = max(groupLayout.dimensions.width, contentSize.width) } - } else { - if imageSize != nil { - contrainedImageSize = NSMakeSize(54, 54) - textLayout?.cutout = TextViewCutout(position: .TopRight, size: NSMakeSize(contrainedImageSize.width + 16, contrainedImageSize.height + 10)) + + var emptyColor: TransformImageEmptyColor? = nil// = NSColor(rgb: 0xd6e2ee, alpha: 0.5) + var isColor: Bool = false + if let wallpaper = wallpaper { + switch wallpaper { + case let .wallpaper(_, _, preview): + switch preview { + case let .slug(_, settings): + if settings.color != nil { + var patternIntensity: CGFloat = 0.5 + + let color = settings.color ?? NSColor(rgb: 0xd6e2ee, alpha: 0.5).argb + if let intensity = settings.intensity { + patternIntensity = CGFloat(intensity) / 100.0 + } + if let bottomColor = settings.bottomColor { + emptyColor = .gradient(top: NSColor(argb: color).withAlphaComponent(patternIntensity), bottom: NSColor(rgb: bottomColor).withAlphaComponent(patternIntensity), rotation: settings.rotation) + } else { + emptyColor = .color(NSColor(argb: color)) + } + } + case .color: + isColor = true + case .gradient: + isColor = true + } + default: + break + } } - } - - if let durationAttributed = durationAttributed { - duration = TextNode.layoutText(durationAttributed, nil, 1, .end, NSMakeSize(width, .greatestFiniteMagnitude), nil, false, .center) - } - - textLayout?.measure(width: width - insets.left) - - if let textLayout = textLayout { - contentSize.height += textLayout.layoutSize.height + if let imageSize = imageSize, isFullImageSize { + if isTheme { + contrainedImageSize = imageSize + } else { + contrainedImageSize = imageSize.fitted(NSMakeSize(min(width - insets.left, maxw), maxw)) + } + // if presentation.renderType == .bubble { + if isColor { + contrainedImageSize = imageSize + } else if !isTheme { + contrainedImageSize.width = max(contrainedImageSize.width, maxw) + } + // } + textLayout?.cutout = nil + smallThumb = false + contentSize.height += contrainedImageSize.height + contentSize.width = contrainedImageSize.width + if textLayout != nil { + contentSize.height += 6 + } + } else { + if let _ = imageSize { + contrainedImageSize = NSMakeSize(54, 54) + textLayout?.cutout = TextViewCutout(topRight: NSMakeSize(contrainedImageSize.width + 16, contrainedImageSize.height + 10)) + } + } - if textLayout.cutout != nil { + if let durationAttributed = durationAttributed { + duration = TextNode.layoutText(durationAttributed, nil, 1, .end, NSMakeSize(contentSize.width, .greatestFiniteMagnitude), nil, false, .center) + } + + + textLayout?.measure(width: contentSize.width) + + + + if let textLayout = textLayout { - contentSize.height = max(content.image != nil ? contrainedImageSize.height : 0,contentSize.height) - contentSize.width = min(max(textLayout.layoutSize.width, (siteName?.0.size.width ?? 0) + contrainedImageSize.width), width - insets.left) - } else if imageSize == nil { - contentSize.width = max(textLayout.layoutSize.width, (siteName?.0.size.width ?? 0)) + contentSize.height += textLayout.layoutSize.height + + if textLayout.cutout != nil { + contentSize.height = max(content.image != nil ? contrainedImageSize.height : 0,contentSize.height) + contentSize.width = min(max(textLayout.layoutSize.width, (siteName?.0.size.width ?? 0) + contrainedImageSize.width), width - insets.left) + } else if imageSize == nil { + contentSize.width = max(max(textLayout.layoutSize.width, groupLayout?.dimensions.width ?? 0), (siteName?.0.size.width ?? 0)) + } } - } - - if imageSize != nil { - let imageArguments = TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: contrainedImageSize, boundingSize: contrainedImageSize, intrinsicInsets: NSEdgeInsets()) - if imageArguments != self.imageArguments { - self.imageArguments = imageArguments + if let imageSize = imageSize { + + let imageArguments = TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: isTheme ? contrainedImageSize : imageSize.aspectFilled(NSMakeSize(maxw, maxw)), boundingSize: contrainedImageSize, intrinsicInsets: NSEdgeInsets(), resizeMode: .blurBackground, emptyColor: emptyColor) + + if imageArguments != self.imageArguments { + self.imageArguments = imageArguments + } + } else { + self.imageArguments = nil } - } else { - self.imageArguments = nil + + + + layout(with :contentSize) } - - - - layout(with :contentSize) } } diff --git a/Telegram-Mac/WPContentView.swift b/Telegram-Mac/WPContentView.swift index 1170cc85f6..7311aa41f0 100644 --- a/Telegram-Mac/WPContentView.swift +++ b/Telegram-Mac/WPContentView.swift @@ -8,12 +8,17 @@ import Cocoa import TGUIKit -import TelegramCoreMac +import TelegramCore +import SyncCore -class WPContentView: View, MultipleSelectable { +class WPContentView: View, MultipleSelectable, ModalPreviewRowViewProtocol { + func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + return nil + } + var header: String? { return nil } @@ -33,11 +38,23 @@ class WPContentView: View, MultipleSelectable { containerView.backgroundColor = backgroundColor for subview in containerView.subviews { - subview.background = backgroundColor + if !(subview is TransformImageView) { + subview.background = backgroundColor + } + } + if let content = content { + instantPageButton?.layer?.borderColor = content.presentation.activity.cgColor + instantPageButton?.set(color: content.presentation.activity, for: .Normal) + + if content.hasInstantPage { + instantPageButton?.set(image: content.presentation.ivIcon, for: .Normal) + instantPageButton?.set(image: content.presentation.ivIcon, for: .Highlight) + } else { + instantPageButton?.removeImage(for: .Normal) + instantPageButton?.removeImage(for: .Highlight) + } } - instantPageButton?.set(image: theme.icons.chatInstantView, for: .Normal) - instantPageButton?.layer?.borderColor = theme.colors.blueIcon.cgColor - instantPageButton?.set(color: theme.colors.blueIcon, for: .Normal) + setNeedsDisplay() } } @@ -46,19 +63,23 @@ class WPContentView: View, MultipleSelectable { return [textView] } + func previewMediaIfPossible() -> Bool { + return false + } + override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) - ctx.setFillColor(theme.colors.blueFill.cgColor) + guard let content = content else {return} + + ctx.setFillColor(content.presentation.activity.cgColor) let radius:CGFloat = 1.0 ctx.fill(NSMakeRect(0, radius, 2, layer.bounds.height - radius * 2)) ctx.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: radius + radius, height: radius + radius))) ctx.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: layer.bounds.height - radius * 2), size: CGSize(width: radius + radius, height: radius + radius))) - if let content = content { - if let siteName = content.siteName { - siteName.1.draw(NSMakeRect(content.insets.left, 0, siteName.0.size.width, siteName.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor) - } + if let siteName = content.siteName { + siteName.1.draw(NSMakeRect(content.insets.left, 0, siteName.0.size.width, siteName.0.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } } @@ -69,12 +90,17 @@ class WPContentView: View, MultipleSelectable { if !textView.isEqual(to: content.textLayout) { textView.update(content.textLayout) } - instantPageButton?.sizeToFit(NSZeroSize, NSMakeSize(content.contentRect.width, 30), thatFit: false) + textView.isHidden = content.textLayout == nil + _ = instantPageButton?.sizeToFit(NSZeroSize, NSMakeSize(content.contentRect.width, 30), thatFit: true) instantPageButton?.setFrameOrigin(0, content.contentRect.height - 30) } needsDisplay = true } + func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + return convert(point, from: nil) + } + required public override init() { super.init() super.addSubview(containerView) @@ -96,11 +122,16 @@ class WPContentView: View, MultipleSelectable { deinit { containerView.removeAllSubviews() } + + func updateMouse() { + + } func update(with layout:WPLayout) -> Void { self.content = layout - if layout.hasInstantPage { + + if layout.hasInstantPage || layout.isProxyConfig { if instantPageButton == nil { instantPageButton = TitleButton() @@ -110,18 +141,23 @@ class WPContentView: View, MultipleSelectable { addSubview(instantPageButton!) } - instantPageButton?.layer?.borderColor = theme.colors.blueIcon.cgColor - instantPageButton?.set(color: theme.colors.blueIcon, for: .Normal) - instantPageButton?.set(image: theme.icons.chatInstantView, for: .Normal) + instantPageButton?.layer?.borderColor = theme.colors.accentIcon.cgColor + + instantPageButton?.set(color: theme.colors.accentIcon, for: .Normal) + instantPageButton?.set(font: .medium(.title), for: .Normal) - instantPageButton?.set(background: theme.colors.background, for: .Normal) - instantPageButton?.set(text: tr(.chatInstantView), for: .Normal) - instantPageButton?.sizeToFit(NSZeroSize, NSMakeSize(layout.contentRect.width, 30), thatFit: false) + instantPageButton?.set(background: .clear, for: .Normal) + instantPageButton?.set(text: layout.isProxyConfig ? L10n.chatApplyProxy : L10n.chatInstantView, for: .Normal) + _ = instantPageButton?.sizeToFit(NSZeroSize, NSMakeSize(layout.contentRect.width, 30), thatFit: false) instantPageButton?.removeAllHandlers() instantPageButton?.set(handler : { [weak layout] _ in if let content = layout { - showInstantPage(InstantPageViewController(content.account, webPage: content.parent.media[0] as! TelegramMediaWebpage, message: content.parent.text)) + if content.hasInstantPage { + showInstantPage(InstantPageViewController(content.context, webPage: content.parent.media[0] as! TelegramMediaWebpage, message: content.parent.text)) + } else if let proxyConfig = content.proxyConfig { + applyExternalProxy(proxyConfig, accountManager: content.context.sharedContext.accountManager) + } } }, for: .Click) @@ -133,7 +169,7 @@ class WPContentView: View, MultipleSelectable { self.needsLayout = true } - var interactionContentView: NSView { + func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return self.containerView } diff --git a/Telegram-Mac/WPLayout.swift b/Telegram-Mac/WPLayout.swift index a20fce6521..2b1d36ebf9 100644 --- a/Telegram-Mac/WPLayout.swift +++ b/Telegram-Mac/WPLayout.swift @@ -8,13 +8,24 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac +import Postbox +import TelegramCore +import SyncCore + + +struct WPLayoutPresentation { + let text: NSColor + let activity: NSColor + let link: NSColor + let selectText: NSColor + let ivIcon: CGImage + let renderType: ChatItemRenderType +} class WPLayout: Equatable { let content:TelegramMediaWebpageLoadedContent let parent:Message - let account:Account + let context: AccountContext let fontSize:CGFloat weak var table:TableView? @@ -30,27 +41,76 @@ class WPLayout: Equatable { var insets: NSEdgeInsets = NSEdgeInsets(left:8.0, top:0.0) - init(with content:TelegramMediaWebpageLoadedContent, account:Account, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat) { + + var mediaCount: Int? { + if let instantPage = content.instantPage, isGalleryAssemble { + if let block = instantPage.blocks.filter({ value in + if case .slideshow = value { + return true + } else if case .collage = value { + return true + } else { + return false + } + }).last { + switch block { + case let .slideshow(items, _), let .collage(items , _): + if items.count == 1 { + return nil + } + return items.count + default: + break + } + + } + } + return nil + } + + var webPage: TelegramMediaWebpage { + if let game = parent.media.first as? TelegramMediaGame { + return TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent.init(url: "", displayUrl: "", hash: 0, type: "game", websiteName: game.title, title: nil, text: game.description, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: game.image, file: game.file, attributes: [], instantPage: nil))) + } + return parent.media.first as! TelegramMediaWebpage + } + + let presentation: WPLayoutPresentation + + private var _approximateSynchronousValue: Bool = false + var approximateSynchronousValue: Bool { + get { + let result = _approximateSynchronousValue + _approximateSynchronousValue = false + return result + } + } + + init(with content:TelegramMediaWebpageLoadedContent, context: AccountContext, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat, presentation: WPLayoutPresentation, approximateSynchronousValue: Bool) { self.content = content - self.account = account + self.context = context + self.presentation = presentation self.parent = parent self.fontSize = fontSize + self._approximateSynchronousValue = approximateSynchronousValue if let websiteName = content.websiteName { - _siteNameAttr = .initialize(string: websiteName, color: theme.colors.link, font: .medium(.text)) + let websiteName = content.type == "telegram_background" ? L10n.chatWPBackgroundTitle : websiteName + _siteNameAttr = .initialize(string: websiteName, color: presentation.activity, font: .medium(.text)) _nameNode = TextNode() } let attributedText:NSMutableAttributedString = NSMutableAttributedString() - if let title = content.title ?? content.author { - _ = attributedText.append(string: title, color: theme.colors.text, font: NSFont.medium(.custom(fontSize))) - if content.text != nil { + let text = content.type != "telegram_background" ? content.text?.trimmed : nil + if let title = content.title ?? content.author, content.type != "telegram_background" { + _ = attributedText.append(string: title, color: presentation.text, font: .medium(fontSize)) + if text != nil { _ = attributedText.append(string: "\n") } } - if let text = content.text { - _ = attributedText.append(string: text, color: theme.colors.text, font: NSFont.normal(.custom(fontSize))) + if let text = text { + _ = attributedText.append(string: text, color: presentation.text, font: .normal(fontSize)) } if attributedText.length > 0 { var p: ParsingType = [.Links] @@ -59,9 +119,38 @@ class WPLayout: Equatable { p = [.Links, .Mentions, .Hashtags] } - attributedText.detectLinks(type: p) - textLayout = TextViewLayout(attributedText, maximumNumberOfLines:10, truncationType: .end, cutout: nil) - textLayout?.interactions = TextViewInteractions(processURL: { link in + attributedText.detectLinks(type: p, color: presentation.link, dotInMention: wname == "instagram") + textLayout = TextViewLayout(attributedText, maximumNumberOfLines:10, truncationType: .end, cutout: nil, selectText: presentation.selectText, strokeLinks: presentation.renderType == .bubble, alwaysStaticItems: true) + + let interactions = globalLinkExecutor + interactions.resolveLink = { link in + if let link = link as? inAppLink { + if case .external(let url, _) = link { + switch wname { + case "instagram": + if url.hasPrefix("@") { + return "https://instagram.com/\(url.nsstring.substring(from: 1))" + } + if url.hasPrefix("#") { + return "https://instagram.com/explore/tags/\(url.nsstring.substring(from: 1))" + } + case "twitter": + if url.hasPrefix("@") { + return "https://twitter.com/\(url.nsstring.substring(from: 1))" + } + if url.hasPrefix("#") { + return "https://twitter.com/hashtag/\(url.nsstring.substring(from: 1))" + } + default: + break + } + } + return link.link + } + return nil + + } + interactions.processURL = { link in if let link = link as? inAppLink { var link = link if case .external(let url, _) = link { @@ -81,25 +170,78 @@ class WPLayout: Equatable { link = .external(link: "https://twitter.com/hashtag/\(url.nsstring.substring(from: 1))", false) } default: + link = inApp(for: url.nsstring, context: context, peerId: nil, openInfo: chatInteraction.openInfo, hashtag: nil, command: nil, applyProxy: nil, confirm: false) break } } execute(inapp: link) } - }) + } + + textLayout?.interactions = interactions } attributedText.fixUndefinedEmojies() } + var isGalleryAssemble: Bool { + // && content.instantPage != nil + if (content.type == "video" && content.type == "video/mp4") || content.type == "photo" || ((content.websiteName?.lowercased() == "instagram" || content.websiteName?.lowercased() == "twitter" || content.websiteName?.lowercased() == "telegram")) || content.text == nil { + return !content.url.isEmpty && content.type != "telegram_background" && content.type != "telegram_theme" + } + return content.type == "telegram_album" && content.type != "telegram_background" && content.type != "telegram_theme" + } + + var wallpaper: inAppLink? { + if content.type == "telegram_background" { + return inApp(for: content.url as NSString, context: context) + } + return nil + } + var isPatternWallpaper: Bool { + return content.file?.mimeType == "application/x-tgwallpattern" + } + + var wallpaperReference: WallpaperReference? { + if let wallpaper = wallpaper { + switch wallpaper { + case let .wallpaper(link, context, preview): + inner: switch preview { + case let .slug(slug, _): + return .slug(slug) + default: + break inner + } + default: + break + } + } + return nil + } + + var themeLink: inAppLink? { + if content.type == "telegram_theme" { + return inApp(for: content.url as NSString, context: context) + } + return nil + } + + var isTheme: Bool { + return content.type == "telegram_theme" && (content.file != nil || content.isCrossplatformTheme) + } + func viewClass() -> AnyClass { return WPArticleContentView.self } + private(set) var oldWidth:CGFloat = 0 func measure(width: CGFloat) { - siteName = TextNode.layoutText(maybeNode: _nameNode, _siteNameAttr, nil, 1, .end, NSMakeSize(width, 20), nil, false, .left) + if oldWidth != width { + self.oldWidth = width + siteName = TextNode.layoutText(maybeNode: _nameNode, _siteNameAttr, nil, 1, .end, NSMakeSize(width - 50, 20), nil, false, .left) + } if let siteName = siteName { insets.top = siteName.0.size.height + 2.0 @@ -108,13 +250,36 @@ class WPLayout: Equatable { } func layout(with size:NSSize) -> Void { - let size = NSMakeSize(max(size.width, hasInstantPage ? 160 : size.width) , size.height + (hasInstantPage ? 30 + 6 : 0)) + let size = NSMakeSize(max(size.width, hasInstantPage ? 160 : size.width) , size.height + (hasInstantPage ? 30 + 6 : 0) + (isProxyConfig ? 30 + 6 : 0)) self.contentRect = NSMakeRect(insets.left, insets.top, size.width, size.height) self.size = NSMakeSize(size.width + insets.left + insets.right, size.height + insets.top + insets.bottom) } var hasInstantPage: Bool { - return content.instantPage != nil + if let instantPage = content.instantPage { + if content.websiteName?.lowercased() == "instagram" || content.websiteName?.lowercased() == "twitter" || content.type == "telegram_album" { + return false + } + if instantPage.blocks.count == 3 { + switch instantPage.blocks[2] { + case let .collage(_, caption), let .slideshow(_, caption): + return !attributedStringForRichText(caption.text, styleStack: InstantPageTextStyleStack()).string.isEmpty + default: + break + } + } + + return true + } + return false + } + + var isProxyConfig: Bool { + return content.type == "proxy" + } + + var proxyConfig: ProxyServerSettings? { + return proxySettings(from: content.url).0 } } diff --git a/Telegram-Mac/WPMediaContentView.swift b/Telegram-Mac/WPMediaContentView.swift index c533776719..88b4904680 100644 --- a/Telegram-Mac/WPMediaContentView.swift +++ b/Telegram-Mac/WPMediaContentView.swift @@ -8,9 +8,51 @@ import Cocoa import TGUIKit +import TelegramCore +import SyncCore + class WPMediaContentView: WPContentView { - var contentNode:ChatMediaContentView? + private(set) var contentNode:ChatMediaContentView? + + + override func fileAtPoint(_ point: NSPoint) -> (QuickPreviewMedia, NSView?)? { + if let contentNode = contentNode { + if contentNode is ChatStickerContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, StickerPreviewModalView.self), contentNode) + } + } else if contentNode is ChatGIFContentView { + if let file = contentNode.media as? TelegramMediaFile { + let reference = contentNode.parent != nil ? FileMediaReference.message(message: MessageReference(contentNode.parent!), media: file) : FileMediaReference.standalone(media: file) + return (.file(reference, GifPreviewModalView.self), contentNode) + } + } else if contentNode is ChatInteractiveContentView { + if let image = contentNode.media as? TelegramMediaImage { + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } else if contentNode is ChatFileContentView { + if let file = contentNode.media as? TelegramMediaFile, file.isGraphicFile, let mediaId = file.id, let dimension = file.dimensions { + var representations: [TelegramMediaImageRepresentation] = [] + representations.append(contentsOf: file.previewRepresentations) + representations.append(TelegramMediaImageRepresentation(dimensions: dimension, resource: file.resource)) + let image = TelegramMediaImage(imageId: mediaId, representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: file.partialReference, flags: []) + let reference = contentNode.parent != nil ? ImageMediaReference.message(message: MessageReference(contentNode.parent!), media: image) : ImageMediaReference.standalone(media: image) + return (.image(reference, ImagePreviewModalView.self), contentNode) + } + } + } + + return nil + } + + override func previewMediaIfPossible() -> Bool { + guard let window = self.kitWindow, let content = content as? WPArticleLayout, content.isFullImageSize, let table = content.table, let contentNode = contentNode, contentNode.mouseInside() else {return false} + _ = startModalPreviewHandle(table, window: window, context: content.context) + return true + } override func draw(_ dirtyRect: NSRect) { @@ -36,10 +78,14 @@ class WPMediaContentView: WPContentView { self.addSubview(self.contentNode!) } - self.contentNode?.update(with: layout.media, size: layout.mediaSize, account: layout.account, parent:layout.parent, table:layout.table, parameters: layout.parameters) + self.contentNode?.update(with: layout.media, size: layout.mediaSize, context: layout.context, parent:layout.parent, table:layout.table, parameters: layout.parameters, approximateSynchronousValue: layout.approximateSynchronousValue) } } + override func updateMouse() { + contentNode?.updateMouse() + } + override func layout() { super.layout() if let content = content as? WPMediaLayout { @@ -47,8 +93,8 @@ class WPMediaContentView: WPContentView { } } - override var interactionContentView: NSView { - return contentNode?.interactionContentView ?? self + override func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + return contentNode?.interactionContentView(for: innerId, animateIn: animateIn) ?? self } } diff --git a/Telegram-Mac/WPMediaLayout.swift b/Telegram-Mac/WPMediaLayout.swift index 73407b0194..87d4c3b776 100644 --- a/Telegram-Mac/WPMediaLayout.swift +++ b/Telegram-Mac/WPMediaLayout.swift @@ -8,9 +8,10 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import PostboxMac -import TelegramCoreMac +import SwiftSignalKit +import Postbox +import TelegramCore +import SyncCore @@ -18,12 +19,15 @@ class WPMediaLayout: WPLayout { var mediaSize:NSSize = NSZeroSize private(set) var media:TelegramMediaFile - var parameters:ChatMediaLayoutParameters? - override init(with content: TelegramMediaWebpageLoadedContent, account: Account, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat) { - self.media = content.file! - super.init(with: content, account: account, chatInteraction: chatInteraction, parent:parent, fontSize: fontSize) + let parameters:ChatMediaLayoutParameters? + init(with content: TelegramMediaWebpageLoadedContent, context: AccountContext, chatInteraction:ChatInteraction, parent:Message, fontSize: CGFloat, presentation: WPLayoutPresentation, approximateSynchronousValue: Bool, downloadSettings: AutomaticMediaDownloadSettings, autoplayMedia: AutoplayMediaPreferences) { + self.media = content.file! + if let representations = content.image?.representations { + self.media = self.media.withUpdatedPreviewRepresentations(representations) + } + self.parameters = ChatMediaLayoutParameters.layout(for: content.file!, isWebpage: true, chatInteraction: chatInteraction, presentation: .make(for: parent, account: context.account, renderType: presentation.renderType), automaticDownload: downloadSettings.isDownloable(parent), isIncoming: parent.isIncoming(context.account, presentation.renderType == .bubble), autoplayMedia: autoplayMedia) + super.init(with: content, context: context, chatInteraction: chatInteraction, parent:parent, fontSize: fontSize, presentation: presentation, approximateSynchronousValue: approximateSynchronousValue) - self.parameters = ChatMediaLayoutParameters.layout(for: self.media, isWebpage: true, chatInteraction: chatInteraction) } override func measure(width: CGFloat) { @@ -42,10 +46,10 @@ class WPMediaLayout: WPLayout { parameters.name = TextNode.layoutText(maybeNode: parameters.nameNode, NSAttributedString.initialize(string: parameters.fileName , color: theme.colors.text, font: .medium(.text)), nil, 1, .middle, NSMakeSize(width - (parameters.hasThumb ? 80 : 50), 20), nil,false, .left) } + parameters?.makeLabelsForWidth(contentSize.width - 50) + if let parameters = parameters as? ChatMediaMusicLayoutParameters { - parameters.nameLayout.measure(width: contentSize.width - 50) - parameters.durationLayout.measure(width: contentSize.width - 50) - parameters.sizeLayout.measure(width: contentSize.width - 50) + contentSize.width = 50 + max(parameters.nameLayout.layoutSize.width, parameters.durationLayout.layoutSize.width) } layout(with: contentSize) @@ -53,7 +57,7 @@ class WPMediaLayout: WPLayout { } public func contentNode() -> ChatMediaContentView.Type { - return ChatLayoutUtils.contentNode(for: media) + return ChatLayoutUtils.contentNode(for: media) } override func viewClass() -> AnyClass { diff --git a/Telegram-Mac/Wallet24WordsItem.swift b/Telegram-Mac/Wallet24WordsItem.swift new file mode 100644 index 0000000000..fc8ba98759 --- /dev/null +++ b/Telegram-Mac/Wallet24WordsItem.swift @@ -0,0 +1,142 @@ +//// +//// Wallet24WordsItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +// +// +//class Wallet24WordsItem: GeneralRowItem { +// fileprivate let leftViewLayout: TextViewLayout +// fileprivate let rightViewLayout: TextViewLayout +// fileprivate let wordsList: String +// fileprivate let copy:(String)->Void +// init(_ initialSize: NSSize, stableId: AnyHashable, words: [String], viewType: GeneralViewType, copy:@escaping(String)->Void) { +// +// let left = words.prefix(12) +// let right = words.suffix(12) +// +// let leftAttributed: NSMutableAttributedString = NSMutableAttributedString() +// for (i, word) in left.enumerated() { +// _ = leftAttributed.append(string: "\(i + 1). ", color: theme.colors.grayText, font: .normal(.text)) +// _ = leftAttributed.append(string: word, color: theme.colors.text, font: .medium(.text)) +// if i != left.count - 1 { +// _ = leftAttributed.append(string: "\n", color: theme.colors.text, font: .normal(.text)) +// } +// } +// let rightAttributed: NSMutableAttributedString = NSMutableAttributedString() +// for (i, word) in right.enumerated() { +// _ = rightAttributed.append(string: "\(i + 1 + left.count). ", color: theme.colors.grayText, font: .normal(.text)) +// _ = rightAttributed.append(string: word, color: theme.colors.text, font: .medium(.text)) +// if i != right.count - 1 { +// _ = rightAttributed.append(string: "\n", color: theme.colors.text, font: .normal(.text)) +// } +// } +// +// self.leftViewLayout = TextViewLayout(leftAttributed, lineSpacing: 5, alwaysStaticItems: true) +// self.rightViewLayout = TextViewLayout(rightAttributed, lineSpacing: 5, alwaysStaticItems: true) +// +// self.leftViewLayout.measure(width: .greatestFiniteMagnitude) +// self.rightViewLayout.measure(width: .greatestFiniteMagnitude) +// +// self.wordsList = words.joined(separator: " ") +// self.copy = copy +// super.init(initialSize, height: max(self.leftViewLayout.layoutSize.height, self.rightViewLayout.layoutSize.height) + viewType.innerInset.top + viewType.innerInset.bottom, stableId: stableId, viewType: viewType) +// } +// +// override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// return true +// } +// +// override var blockWidth: CGFloat { +// return 280 +// } +// +// override func viewClass() -> AnyClass { +// return Wallet24WordsView.self +// } +//} +// +// +//private final class Wallet24WordsView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let leftTextView = TextView() +// private let rightTextView = TextView() +// private let wordsContainer = Control() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// leftTextView.isSelectable = false +// rightTextView.isSelectable = false +// leftTextView.userInteractionEnabled = false +// rightTextView.userInteractionEnabled = false +// +// wordsContainer.addSubview(leftTextView) +// wordsContainer.addSubview(rightTextView) +// self.addSubview(containerView) +// +// containerView.addSubview(wordsContainer) +// } +// +// override var backdorColor: NSColor { +// return theme.colors.background +// } +// +// override func updateColors() { +// guard let item = item as? Wallet24WordsItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.leftTextView.background = backdorColor +// self.rightTextView.background = backdorColor +// self.containerView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? Wallet24WordsItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// wordsContainer.setFrameSize(NSMakeSize(250, max(leftTextView.frame.height, rightTextView.frame.height))) +// +// wordsContainer.center() +// +// leftTextView.centerY(x: 0) +// rightTextView.centerY(x: wordsContainer.frame.width - rightTextView.frame.width) +// +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? Wallet24WordsItem else { +// return +// } +// let wordsList = item.wordsList +// let copy = item.copy +// wordsContainer.removeAllHandlers() +// +// wordsContainer.set(handler: { control in +// if let event = NSApp.currentEvent { +// ContextMenu.show(items: [ContextMenuItem(L10n.walletSplashSave24WordsCopy, handler: { +// copy(wordsList) +// })], view: control, event: event) +// } +// }, for: .RightDown) +// +// leftTextView.update(item.leftViewLayout) +// rightTextView.update(item.rightViewLayout) +// needsLayout = true +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/Telegram-Mac/WalletBalanceItem.swift b/Telegram-Mac/WalletBalanceItem.swift new file mode 100644 index 0000000000..3066741a50 --- /dev/null +++ b/Telegram-Mac/WalletBalanceItem.swift @@ -0,0 +1,363 @@ +//// +//// WalletBalanceItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 20/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import SwiftSignalKit +//import TelegramCore +//import SyncCore +//import WalletCore +// +//public enum RelativeTimestampFormatDay { +// case today +// case yesterday +//} +// +// +// +//private func stringForRelativeUpdateTime(day: RelativeTimestampFormatDay, hours: Int32, minutes: Int32) -> String { +// let dayString: String +// switch day { +// case .today: +// dayString = L10n.updatedTodayAt(stringForShortTimestamp(hours: hours, minutes: minutes)) +// case .yesterday: +// dayString = L10n.updatedYesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes)) +// } +// return dayString +//} +// +// +//private func lastUpdateTimestampString(statusTimestamp: Int32, relativeTo timestamp: Int32) -> String { +// let difference = timestamp - statusTimestamp +// let expanded = true +// if difference < 60 { +// return L10n.updatedJustNow +// } else if difference < 60 * 60 && !expanded { +// let minutes = difference / 60 +// return L10n.updatedMinutesAgoCountable(Int(minutes)) +// } else { +// var t: time_t = time_t(statusTimestamp) +// var timeinfo: tm = tm() +// localtime_r(&t, &timeinfo) +// +// var now: time_t = time_t(timestamp) +// var timeinfoNow: tm = tm() +// localtime_r(&now, &timeinfoNow) +// +// if timeinfo.tm_year != timeinfoNow.tm_year { +// return L10n.updatedAtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year)) +// } +// +// let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday +// if dayDifference == 0 || dayDifference == -1 { +// let day: RelativeTimestampFormatDay +// if dayDifference == 0 { +// if expanded { +// day = .today +// } else { +// let hours = difference / (60 * 60) +// return L10n.updatedHoursAgoCountable(Int(hours)) +// } +// } else { +// day = .yesterday +// } +// return stringForRelativeUpdateTime(day: day, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min) +// } else { +// return L10n.updatedAtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year)) +// } +// } +//} +// +// +//class WalletBalanceItem: GeneralRowItem { +// fileprivate let walletState: WalletState? +// fileprivate let receiveMoney:()->Void +// fileprivate let sendMoney:()->Void +// fileprivate let balanceLayout: TextViewLayout? +// fileprivate let updatedTimestamp: Int64? +// fileprivate let update:()->Void +// fileprivate let context: AccountContext +// fileprivate let syncProgress: Float +// init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, state: WalletState?, updatedTimestamp: Int64?, syncProgress: Float, viewType: GeneralViewType, receiveMoney:@escaping()->Void, sendMoney:@escaping()->Void, update:@escaping()->Void) { +// self.walletState = state +// self.context = context +// self.syncProgress = syncProgress +// self.receiveMoney = receiveMoney +// self.sendMoney = sendMoney +// self.update = update +// self.updatedTimestamp = updatedTimestamp +// if let walletState = walletState { +// let value: String +// if walletState.balance >= 0 { +// value = formatBalanceText(walletState.balance) +// } else { +// value = "0\(Formatter.withSeparator.decimalSeparator!)0" +// } +// let attributed: NSMutableAttributedString = NSMutableAttributedString() +// if let range = value.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(value[.. AnyClass { +// return WalletBalanceView.self +// } +//} +// +//private final class WalletBalanceView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let balanceView: TextView = TextView() +// private let updatedTimestampView = TextView() +// private let receiveButton = TitleButton() +// private let sendButton = TitleButton() +// private let reloadButton = ImageButton() +// private let crystalView: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) +// private let animator: ConstantDisplayLinkAnimator +// +// required init(frame frameRect: NSRect) { +// +// var updateImpl: (() -> Void)? +// self.animator = ConstantDisplayLinkAnimator(update: { +// updateImpl?() +// }) +// +// super.init(frame: frameRect) +// addSubview(containerView) +// containerView.addSubview(balanceView) +// containerView.addSubview(receiveButton) +// containerView.addSubview(sendButton) +// containerView.addSubview(updatedTimestampView) +// containerView.addSubview(reloadButton) +// containerView.addSubview(crystalView) +// receiveButton.layer?.cornerRadius = .cornerRadius +// sendButton.layer?.cornerRadius = .cornerRadius +// +// updatedTimestampView.userInteractionEnabled = false +// updatedTimestampView.isSelectable = false +// +// receiveButton.disableActions() +// sendButton.disableActions() +// +// updateImpl = { [weak self] in +// self?.updateAnimation() +// } +// +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override var backdorColor: NSColor { +// return theme.colors.background +// } +// +// override func updateColors() { +// guard let item = item as? WalletBalanceItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// +// updatedTimestampView.backgroundColor = backdorColor +// +// receiveButton.set(text: L10n.walletBalanceInfoReceive, for: .Normal) +// receiveButton.set(background: theme.colors.accent, for: .Normal) +// receiveButton.set(background: theme.colors.accent.withAlphaComponent(0.8), for: .Highlight) +// receiveButton.set(color: theme.colors.underSelectedColor, for: .Normal) +// receiveButton.set(font: .medium(.text), for: .Normal) +// receiveButton.set(image: theme.icons.wallet_receive, for: .Normal) +// receiveButton.set(image: theme.icons.wallet_receive, for: .Highlight) +// +// _ = receiveButton.sizeToFit() +// +// sendButton.set(text: L10n.walletBalanceInfoSend, for: .Normal) +// sendButton.set(background: theme.colors.accent, for: .Normal) +// sendButton.set(background: theme.colors.accent.withAlphaComponent(0.8), for: .Highlight) +// sendButton.set(color: theme.colors.underSelectedColor, for: .Normal) +// sendButton.set(font: .medium(.text), for: .Normal) +// sendButton.set(image: theme.icons.wallet_send, for: .Normal) +// sendButton.set(image: theme.icons.wallet_send, for: .Highlight) +// +// _ = sendButton.sizeToFit() +// +// reloadButton.set(image: theme.icons.wallet_update, for: .Normal) +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletBalanceItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// let midY = containerView.frame.midY - (40 - item.viewType.innerInset.bottom) / 2 +// balanceView.centerX(y: midY - balanceView.frame.height, addition: crystalView.frame.width / 2) +// crystalView.setFrameOrigin(NSMakePoint(balanceView.frame.minX - crystalView.frame.width, balanceView.frame.minY - 3)) +// updatedTimestampView.centerX(y: balanceView.frame.maxY - 3) +// +// let buttonWidth = sendButton.isHidden ? (item.blockWidth - item.viewType.innerInset.left - item.viewType.innerInset.right) : (item.blockWidth - item.viewType.innerInset.left * 3) / 2 +// +// receiveButton.setFrameSize(NSMakeSize(min(buttonWidth, 140), 40)) +// sendButton.setFrameSize(NSMakeSize(min(buttonWidth, 140), 40)) +// +// if sendButton.isHidden { +// receiveButton.centerX(y: containerView.frame.height - receiveButton.frame.height - item.viewType.innerInset.bottom - 20) +// } else { +// receiveButton.setFrameOrigin(NSMakePoint(containerView.frame.width / 2 - 5 - receiveButton.frame.width, containerView.frame.height - receiveButton.frame.height - 20)) +// sendButton.setFrameOrigin(NSMakePoint(containerView.frame.width / 2 + 5, containerView.frame.height - sendButton.frame.height - 20)) +// } +// reloadButton.setFrameSize(NSMakeSize(40, 40)) +// +// reloadButton.setFrameOrigin(NSMakePoint(containerView.frame.width - reloadButton.frame.width, 0)) +// } +// +// private var currentAngle: CGFloat = 0.0 +// private var currentExtraSpeed: CGFloat = 0.0 +// private var animateToZeroState: (Double, CGFloat)? +// private var currentTextIndex: Int = 0 +// +// private func updateAnimation() { +// guard let item = item as? WalletBalanceItem else { +// return +// } +// var speed: CGFloat = 0.0 +// var baseValue: CGFloat = 0.0 +// +// if item.updatedTimestamp == nil { +// speed = 0.01 +// self.animateToZeroState = nil +// } else { +// if self.currentExtraSpeed.isZero && self.animateToZeroState == nil && !self.currentAngle.isZero { +// self.animateToZeroState = (CACurrentMediaTime(), self.currentAngle) +// +// } +// } +// +// +// +// if let (startTime, startValue) = self.animateToZeroState { +// let endValue: CGFloat = floor(startValue) + 1.0 +// let duration: Double = Double(endValue - startValue) * 1.0 +// let timeDelta = (startTime + duration - CACurrentMediaTime()) +// let t: CGFloat = 1.0 - CGFloat(max(0.0, min(1.0, timeDelta / duration))) +// if t >= 1.0 - CGFloat.ulpOfOne { +// self.animateToZeroState = nil +// self.currentAngle = 0.0 +// } else { +// let bt = bezierPoint(0.23, 1.0, 0.32, 1.0, t) +// self.currentAngle = startValue * (1.0 - bt) + endValue * bt +// } +// } else { +// self.currentAngle += speed + self.currentExtraSpeed +// } +// self.currentExtraSpeed *= 0.97 +// if abs(self.currentExtraSpeed) < 0.0001 { +// self.currentExtraSpeed = 0.0 +// } +// +// self.reloadButton.layer?.anchorPoint = NSMakePoint(0.5, 0.5) +// self.reloadButton.layer?.position = NSMakePoint(containerView.frame.width - reloadButton.frame.width + 20, 20) +// self.reloadButton.layer?.transform = CATransform3DMakeRotation((baseValue + self.currentAngle) * CGFloat.pi * 2.0, 0.0, 0.0, 1.0) +// +// let updatedTimestampLayout: TextViewLayout +// +// if item.syncProgress < 1 { +// updatedTimestampLayout = TextViewLayout(.initialize(string: L10n.walletBalanceInfoSyncing("\(item.syncProgress * 100.0)"), color: theme.colors.grayText, font: .normal(12))) +// } else { +// if let updatedTimestamp = item.updatedTimestamp { +// updatedTimestampLayout = TextViewLayout(.initialize(string: lastUpdateTimestampString(statusTimestamp: Int32(updatedTimestamp), relativeTo: Int32(Date().timeIntervalSince1970)), color: theme.colors.grayText, font: .normal(12))) +// } else { +// let text: String +// if currentTextIndex <= 15 { +// text = L10n.walletBalanceInfoUpdating1 +// } else if currentTextIndex <= 30 { +// text = L10n.walletBalanceInfoUpdating2 +// } else { +// text = L10n.walletBalanceInfoUpdating3 +// } +// updatedTimestampLayout = TextViewLayout(.initialize(string: text, color: theme.colors.grayText, font: .normal(12))) +// +// currentTextIndex += 1 +// +// if currentTextIndex > 45 { +// currentTextIndex = 0 +// } +// } +// } +// +// +// updatedTimestampLayout.measure(width: .greatestFiniteMagnitude) +// self.updatedTimestampView.update(updatedTimestampLayout) +// if !self.currentExtraSpeed.isZero || !speed.isZero || self.animateToZeroState != nil { +// self.animator.isPaused = false +// } else { +// self.animator.isPaused = true +// self.currentTextIndex = 0 +// } +// } +// +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletBalanceItem else { +// return +// } +// +// crystalView.update(with: LocalAnimatedSticker.brilliant_static.file, size: NSMakeSize(44, 44), context: item.context, parent: nil, table: nil, parameters: LocalAnimatedSticker.brilliant_static.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: true) +// +// sendButton.isHidden = item.walletState?.balance == -1 +// +// receiveButton.removeAllHandlers() +// receiveButton.set(handler: { [weak item] _ in +// item?.receiveMoney() +// }, for: .Click) +// +// sendButton.removeAllHandlers() +// sendButton.set(handler: { [weak item] _ in +// item?.sendMoney() +// }, for: .Click) +// +// reloadButton.removeAllHandlers() +// reloadButton.set(handler: { [weak item] _ in +// item?.update() +// }, for: .Click) +// +// if item.updatedTimestamp == nil { +// updateAnimation() +// } +// +// balanceView.update(item.balanceLayout) +// +// let updatedTimestampLayout: TextViewLayout +// if let updatedTimestamp = item.updatedTimestamp { +// updatedTimestampLayout = TextViewLayout(.initialize(string: lastUpdateTimestampString(statusTimestamp: Int32(updatedTimestamp), relativeTo: Int32(Date().timeIntervalSince1970)), color: theme.colors.grayText, font: .normal(12))) +// } else { +// updatedTimestampLayout = TextViewLayout(.initialize(string: L10n.walletBalanceInfoUpdating3, color: theme.colors.grayText, font: .normal(12))) +// } +// updatedTimestampLayout.measure(width: .greatestFiniteMagnitude) +// updatedTimestampView.update(updatedTimestampLayout) +// needsLayout = true +// } +// +//} diff --git a/Telegram-Mac/WalletConfiguration.swift b/Telegram-Mac/WalletConfiguration.swift new file mode 100644 index 0000000000..457ba5a511 --- /dev/null +++ b/Telegram-Mac/WalletConfiguration.swift @@ -0,0 +1,53 @@ +// +// WalletConfiguration.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + + +public struct WalletConfiguration { + static var defaultValue: WalletConfiguration { + return WalletConfiguration(config: nil, blockchainName: nil, disableProxy: false) + } + + public let config: String? + public let blockchainName: String? + public let disableProxy: Bool + + fileprivate init(config: String?, blockchainName: String?, disableProxy: Bool) { + self.config = config + self.blockchainName = blockchainName + self.disableProxy = disableProxy + } + + public static func with(appConfiguration: AppConfiguration) -> WalletConfiguration { + if let data = appConfiguration.data, let config = data["wallet_config"] as? String, let blockchainName = data["wallet_blockchain_name"] as? String { + var disableProxy = false + if let value = data["wallet_disable_proxy"] as? String { + disableProxy = value != "0" + } else if let value = data["wallet_disable_proxy"] as? Int { + disableProxy = value != 0 + } + return WalletConfiguration(config: config, blockchainName: blockchainName, disableProxy: disableProxy) + } else { + return .defaultValue + } + } +} + + +func walletConfiguration(postbox: Postbox) -> Signal { + return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> map { view in + let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue + let configuration = WalletConfiguration.with(appConfiguration: appConfiguration) + return configuration + } |> deliverOnMainQueue +} diff --git a/Telegram-Mac/WalletCreateInvoiceController.swift b/Telegram-Mac/WalletCreateInvoiceController.swift new file mode 100644 index 0000000000..2f345ac46a --- /dev/null +++ b/Telegram-Mac/WalletCreateInvoiceController.swift @@ -0,0 +1,189 @@ +//// +//// WalletCreateInvoiceController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 08/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import SwiftSignalKit +//import TelegramCore +//import SyncCore +//import WalletCore +//private final class WalletInvoiceArguments { +// let context: AccountContext +// let copy:()->Void +// let share:()->Void +// init(context: AccountContext, copy: @escaping()->Void, share: @escaping()->Void) { +// self.context = context +// self.copy = copy +// self.share = share +// } +//} +// +//private func url(for state: WalletInvoiceState, address: String) -> String { +// var url = "ton://transfer/\(escape(with: address, addPercent: true))" +// let amount = amountValue(state.amount) +// let comment = state.comment +// if !comment.isEmpty || amount > 0 { +// url += "?" +// } +// if amount > 0 { +// url += "amount=\(amount)" +// } +// if !comment.isEmpty { +// if amount > 0 { +// url += "&" +// } +// url += "text=\(comment)" +// } +// return url +//} +// +//private struct WalletInvoiceState : Equatable { +// let amount: String +// let comment: String +// init(amount: String, comment: String) { +// self.amount = amount +// self.comment = comment +// } +// func withUpdatedAmount(_ amount: String) -> WalletInvoiceState { +// return WalletInvoiceState(amount: amount, comment: self.comment) +// } +// func withUpdatedComment(_ comment: String) -> WalletInvoiceState { +// return WalletInvoiceState(amount: self.amount, comment: comment) +// } +//} +//private let _id_amount = InputDataIdentifier("_id_amount") +//private let _id_comment = InputDataIdentifier("_id_comment") +//private let _id_copy = InputDataIdentifier("_id_copy") +//private let _id_share = InputDataIdentifier("_id_share") +//private let _id_invoice_url = InputDataIdentifier("_id_invoice_url") +//private func walletInvoiceEntries(state: WalletInvoiceState, address: String, arguments: WalletInvoiceArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// +// var sectionId:Int32 = 0 +// var index:Int32 = 0 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletCreateInvoiceAmoutTitle), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// +// entries.append(.input(sectionId: sectionId, index: index, value: .string(state.amount), error: nil, identifier: _id_amount, mode: .plain, data: .init(viewType: .firstItem, rightItem: nil, defaultText: nil, pasteFilter: { value in +// if isValidAmount(value) { +// return (true, value) +// } +// return (false, value) +// }), placeholder: nil, inputPlaceholder: L10n.walletCreateInvoiceAmoutPlaceholder, filter: { value in +// +// let set = CharacterSet(charactersIn: "0987654321.,\(Formatter.withSeparator.decimalSeparator!)") +// let value = value.trimmingCharacters(in: set.inverted) +// +// if !isValidAmount(value) { +// return state.amount +// } +// return value +// +// }, limit: 40)) +// index += 1 +// +// +// entries.append(.input(sectionId: sectionId, index: index, value: .string(state.comment), error: nil, identifier: _id_comment, mode: .plain, data: InputDataRowData(viewType: .lastItem), placeholder: nil, inputPlaceholder: L10n.walletCreateInvoiceCommentPlaceholder, filter: { current in +// if let data = current.data(using: .utf8) { +// let ncut = data.suffix(500) +// return String(data: ncut, encoding: .utf8)! +// } else { +// return current +// } +// }, limit: 500)) +// index += 1 +// +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletCreateInvoiceInvoiceTitle), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_invoice_url, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .firstItem, text: url(for: state, address: address), font: .normal(.text)) +// })) +// index += 1 +// +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_copy, data: InputDataGeneralData(name: L10n.walletCreateInvoiceCopyURL, color: theme.colors.accent, viewType: .innerItem, action: arguments.copy))) +// index += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_share, data: InputDataGeneralData(name: L10n.walletCreateInvoiceShareURL, color: theme.colors.accent, viewType: .lastItem, action: arguments.share))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletCreateInvoiceShareDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// return entries +//} +// +//func WalletInvoiceController(context: AccountContext, tonContext: TonContext, address: String) -> InputDataModalController { +// let initialState = WalletInvoiceState(amount: "", comment: "") +// let state: ValuePromise = ValuePromise(initialState) +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletInvoiceState)->WalletInvoiceState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// var getController:(()->InputDataController?)? = nil +// +// let arguments = WalletInvoiceArguments(context: context, copy: { +// copyToClipboard(url(for: stateValue.with { $0 }, address: address)) +// getController?()?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) +// }, share: { +// let urlValue = url(for: stateValue.with { $0 }, address: address) +// showModal(with: ShareModalController(ShareLinkObject(context, link: urlValue)), for: context.window) +// }) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return walletInvoiceEntries(state: state, address: address, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// var getModalController:(()->InputDataModalController?)? = nil +// +// +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletCreateInvoiceTitle) +// +// +// controller.updateDatas = { data in +// updateState { +// $0.withUpdatedAmount(formatAmountText(data[_id_amount]?.stringValue ?? "")) +// .withUpdatedComment(data[_id_comment]?.stringValue ?? "") +// } +// return .none +// } +// +// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +// getModalController?()?.close() +// }) +// +// getController = { [weak controller] in +// return controller +// } +// +// let modalController = InputDataModalController(controller, closeHandler: { f in +// f() +// }, size: NSMakeSize(350, 350)) +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// return modalController +//} diff --git a/Telegram-Mac/WalletImportWordsItem.swift b/Telegram-Mac/WalletImportWordsItem.swift new file mode 100644 index 0000000000..bf8b36b250 --- /dev/null +++ b/Telegram-Mac/WalletImportWordsItem.swift @@ -0,0 +1,272 @@ +//// +//// WalletImportWordsItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 22/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//private func _id_word(_ index:Int) -> InputDataIdentifier { +// return InputDataIdentifier("_id_word_\(index)") +//} +//class WalletImportWordsItem: GeneralRowItem { +// fileprivate let leftItems:[InputDataRowItem] +// fileprivate let rightItems:[InputDataRowItem] +// init(_ initialSize: NSSize, stableId: AnyHashable, words:[InputDataIdentifier: InputDataValue], viewType: GeneralViewType, update:@escaping(InputDataIdentifier, InputDataValue)->Void) { +// +// var left: [InputDataRowItem] = [] +// var right: [InputDataRowItem] = [] +// +// let insets = NSEdgeInsets(left: 5, right: 0, top: 12, bottom: 12) +// let arrayLeft:[Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] +// for index in arrayLeft { +// let item = InputDataRowItem(NSMakeSize(100, 40), stableId: _id_word(index), mode: .plain, error: nil, viewType: .modern(position: bestGeneralViewType(arrayLeft, for: index).position, insets: insets), currentText: words[_id_word(index)]?.stringValue ?? "", placeholder: nil, inputPlaceholder: "", defaultText: nil, rightItem: nil, insets: NSEdgeInsets(), filter: { $0 }, updated: { text in +// update(_id_word(index), .string(text)) +// }, pasteFilter: nil, limit: 8) +// left.append(item) +// } +// let arrayRight:[Int] = [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] +// for index in arrayRight { +// let item = InputDataRowItem(NSMakeSize(100, 40), stableId: _id_word(index), mode: .plain, error: nil, viewType: .modern(position: bestGeneralViewType(arrayRight, for: index).position, insets: insets), currentText: words[_id_word(index)]?.stringValue ?? "", placeholder: nil, inputPlaceholder: "", defaultText: nil, rightItem: nil, insets: NSEdgeInsets(), filter: { $0 }, updated: { text in +// update(_id_word(index), .string(text)) +// }, pasteFilter: nil, limit: 8) +// right.append(item) +// } +// self.leftItems = left +// self.rightItems = right +// +// super.init(initialSize, height: 40 * 12, stableId: stableId, type: .none, viewType: viewType) +// } +// +// override var blockWidth: CGFloat { +// return 280 +// } +// +// override var instantlyResize: Bool { +// return false +// } +// +// +// override func viewClass() -> AnyClass { +// return WalletImportWordsView.self +// } +//} +// +//private final class WalletImportWordsView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let leftHolderViews:[TextView] +// private let rightHolderViews:[TextView] +// private let leftSeparatorViews:[View] +// private let rightSeparatorViews:[View] +// +// private let leftViews:[InputDataRowView] +// private let rightViews:[InputDataRowView] +// private let viewsContainer = View() +// required init(frame frameRect: NSRect) { +// var left:[InputDataRowView] = [] +// var right:[InputDataRowView] = [] +// var leftHolders: [TextView] = [] +// var rightHolders: [TextView] = [] +// +// var leftSeparatorViews:[View] = [] +// var rightSeparatorViews:[View] = [] +// +// for _ in 1 ... 12 { +// left.append(InputDataRowView(frame: NSZeroRect)) +// leftHolders.append(TextView()) +// leftSeparatorViews.append(View()) +// } +// +// for _ in 13 ... 24 { +// right.append(InputDataRowView(frame: NSZeroRect)) +// rightHolders.append(TextView()) +// rightSeparatorViews.append(View()) +// } +// +// +// +// self.leftHolderViews = leftHolders +// self.rightHolderViews = rightHolders +// self.leftSeparatorViews = leftSeparatorViews +// self.rightSeparatorViews = rightSeparatorViews +// self.leftViews = left +// self.rightViews = right +// super.init(frame: frameRect) +// +// for view in leftViews { +// viewsContainer.addSubview(view) +// } +// for view in rightViews { +// viewsContainer.addSubview(view) +// } +// +// for view in leftHolderViews { +// viewsContainer.addSubview(view) +// } +// for view in rightHolderViews { +// viewsContainer.addSubview(view) +// } +// +// for view in leftSeparatorViews { +// viewsContainer.addSubview(view) +// } +// for view in rightSeparatorViews { +// viewsContainer.addSubview(view) +// } +// +// containerView.addSubview(viewsContainer) +// addSubview(containerView) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func layout() { +// super.layout() +// +// guard let item = item as? WalletImportWordsItem else { +// return +// } +// +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// viewsContainer.setFrameSize(NSMakeSize(item.viewType.innerInset.left + item.viewType.innerInset.right + 200 + 30 + 30 + 30, self.containerView.frame.height)) +// viewsContainer.center() +// +// var x: CGFloat = item.viewType.innerInset.left + 32 +// var y: CGFloat = 0 +// +// for (i, view) in leftViews.enumerated() { +// view.setFrameOrigin(NSMakePoint(x, y)) +// leftHolderViews[i].setFrameOrigin(NSMakePoint(x - leftHolderViews[i].frame.width, y + floorToScreenPixels(backingScaleFactor, (view.frame.height - leftHolderViews[i].frame.height) / 2))) +// y += view.frame.height +// } +// y = 0 +// x = item.viewType.innerInset.left + 100 + 30 + 30 + 10 +// +// for (i, view) in rightViews.enumerated() { +// view.setFrameOrigin(NSMakePoint(x, y)) +// rightHolderViews[i].setFrameOrigin(NSMakePoint(x - rightHolderViews[i].frame.width, y + floorToScreenPixels(backingScaleFactor, (view.frame.height - rightHolderViews[i].frame.height) / 2))) +// y += view.frame.height +// } +// +// } +// +// override var backdorColor: NSColor { +// return theme.colors.background +// } +// +// override func updateColors() { +// guard let item = item as? WalletImportWordsItem else { +// return +// } +// +// for view in leftSeparatorViews { +// view.backgroundColor = theme.colors.border +// } +// for view in rightSeparatorViews { +// view.backgroundColor = theme.colors.border +// } +// +// for (i, view) in leftHolderViews.enumerated() { +// let layout = TextViewLayout(.initialize(string: "\(i + 1):", color: theme.colors.grayText, font: .normal(.text))) +// layout.measure(width: .greatestFiniteMagnitude) +// view.update(layout) +// } +// +// for (i, view) in rightHolderViews.enumerated() { +// let layout = TextViewLayout(.initialize(string: "\(i + 13):", color: theme.colors.grayText, font: .normal(.text))) +// layout.measure(width: .greatestFiniteMagnitude) +// view.update(layout) +// } +// // self.viewsContainer.backgroundColor = .random +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletImportWordsItem else { +// return +// } +// +// for (i, view) in leftViews.enumerated() { +// view.setFrameSize(NSMakeSize(item.leftItems[i].blockWidth, item.leftItems[i].height)) +// view.set(item: item.leftItems[i], animated: animated) +// } +// +// for (i, view) in rightViews.enumerated() { +// view.setFrameSize(NSMakeSize(item.leftItems[i].blockWidth, item.rightItems[i].height)) +// view.set(item: item.rightItems[i], animated: animated) +// } +// +// +// +// needsLayout = true +// } +// +// override func shakeViewWithData(_ data: Any) { +// super.shakeViewWithData(data) +// if let data = data as? [InputDataIdentifier : InputDataValidationFailAction] { +// for (key, _) in data { +// let leftView = leftViews.first(where: { +// $0.item?.stableId.base as? InputDataIdentifier == key +// }) +// let rightView = rightViews.first(where: { +// $0.item?.stableId.base as? InputDataIdentifier == key +// }) +// +// leftView?.shakeView() +// rightView?.shakeView() +// } +// } +// } +// +// override var firstResponder: NSResponder? { +// for left in leftViews { +// if left.textView.inputView == window?.firstResponder { +// return left.textView.inputView +// } +// } +// for right in rightViews { +// if right.textView.inputView == window?.firstResponder { +// return right.textView.inputView +// } +// } +// return leftViews.first?.textView.inputView +// } +// +// override func hasFirstResponder() -> Bool { +// return true +// } +// +// override func nextResponder() -> NSResponder? { +// +// for (i, view) in leftViews.enumerated() { +// if view.textView.inputView == window?.firstResponder { +// if view != leftViews.last { +// return leftViews[i + 1].textView.inputView +// } +// } +// } +// if leftViews.last?.textView.inputView == window?.firstResponder { +// return rightViews.first?.textView.inputView +// } +// +// for (i, view) in rightViews.enumerated() { +// if view.textView.inputView == window?.firstResponder { +// if view != rightViews.last { +// return rightViews[i + 1].textView.inputView +// } +// } +// +// } +// +// return leftViews.first?.textView.inputView +// } +//} diff --git a/Telegram-Mac/WalletInfoController.swift b/Telegram-Mac/WalletInfoController.swift new file mode 100644 index 0000000000..c377d8f693 --- /dev/null +++ b/Telegram-Mac/WalletInfoController.swift @@ -0,0 +1,437 @@ +//// +//// WalletInfoController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import TGUIKit +//import WalletCore +// +//enum WalletInfoTransaction: Equatable { +// case completed(WalletTransaction) +// case pending(PendingWalletTransaction) +// +// var timestamp: Int64 { +// switch self { +// case let .completed(transaction): +// return transaction.timestamp +// case let .pending(transaction): +// return transaction.timestamp +// } +// } +// +// var transactionId: Int64 { +// switch self { +// case let .completed(transaction): +// return transaction.transactionId.lt +// case let .pending(transaction): +// return transaction.timestamp +// } +// } +//} +// +// +//private final class WalletInfoArguments { +// let context: AccountContext +// let openReceive:()->Void +// let openSend:()->Void +// let openTransaction:(WalletInfoTransaction)->Void +// let update:()->Void +// init(context: AccountContext, openReceive: @escaping()->Void, openSend: @escaping()->Void, openTransaction: @escaping(WalletInfoTransaction)->Void, update:@escaping()->Void) { +// self.context = context +// self.openReceive = openReceive +// self.openSend = openSend +// self.openTransaction = openTransaction +// self.update = update +// } +//} +// +//private struct WalletInfoState : Equatable { +// let walletState: WalletState? +// let updatedTimestamp: Int64? +// let previousTimestamp: Int64? +// let address: String +// let syncProgress: Float +// let isSynced: Bool +// let transactions:[WalletInfoTransaction] +// init(walletState: WalletState?, updatedTimestamp: Int64?, previousTimestamp: Int64?, address: String, syncProgress: Float, isSynced: Bool, transactions:[WalletInfoTransaction]) { +// self.walletState = walletState +// self.address = address +// self.isSynced = isSynced +// self.syncProgress = syncProgress +// self.updatedTimestamp = updatedTimestamp +// self.previousTimestamp = previousTimestamp +// self.transactions = transactions.sorted(by: { $0.timestamp > $1.timestamp }) +// } +// +// func withUpdatedWalletState(_ walletState: WalletState?) -> WalletInfoState { +// return WalletInfoState(walletState: walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: self.transactions) +// } +// func withUpdatedAddress(_ address: String) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: self.transactions) +// } +// +// func withUpdatedTimestamp(_ updatedTimestamp: Int64?) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: self.transactions) +// } +// func withUpdatedSyncProgress(_ syncProgress: Float) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: syncProgress, isSynced: self.isSynced, transactions: self.transactions) +// } +// func withUpdatedSynced(_ isSynced: Bool) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: isSynced, transactions: self.transactions) +// } +// func withUpdatedPreviousTimestamp(_ previousTimestamp: Int64?) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: self.transactions) +// } +// func withUpdatedTransactions(_ transactions: [WalletInfoTransaction]) -> WalletInfoState { +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: transactions) +// } +// +// func withAddedTransactions(_ transactions: [WalletInfoTransaction]) -> WalletInfoState { +// var updated = self.transactions +// var exists:Set = Set() +// for transaction in updated { +// switch transaction { +// case let .completed(transaction): +// exists.insert(transaction.transactionId) +// case .pending: +// break +// } +// } +// for transaction in transactions { +// switch transaction { +// case let .completed(transaction): +// if !exists.contains(transaction.transactionId) { +// updated.append(.completed(transaction)) +// } +// case .pending: +// break +// } +// } +// return WalletInfoState(walletState: self.walletState, updatedTimestamp: self.updatedTimestamp, previousTimestamp: self.previousTimestamp, address: self.address, syncProgress: self.syncProgress, isSynced: self.isSynced, transactions: updated) +// } +//} +// +//private let _id_balance = InputDataIdentifier("_id_balance") +//private let _id_created_address = InputDataIdentifier("_id_created_address") +//private func _id_transaction(_ id: Int64) -> InputDataIdentifier { +// return InputDataIdentifier("_id_transaction_\(id)") +//} +//private func _id_date(_ id:Int32) -> InputDataIdentifier { +// return InputDataIdentifier("_id_data_\(id)") +//} +//private func walletInfoEntries(_ state: WalletInfoState, arguments: WalletInfoArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// +// var sectionId: Int32 = 0 +// var index:Int32 = 0 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// if let _ = state.walletState { +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_balance, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return WalletBalanceItem(initialSize, stableId: stableId, context: arguments.context, state: state.walletState, updatedTimestamp: state.updatedTimestamp, syncProgress: state.syncProgress, viewType: .singleItem, receiveMoney: arguments.openReceive, sendMoney: arguments.openSend, update: arguments.update) +// })) +// index += 1 +// +// +// if state.transactions.isEmpty { +// +// if state.walletState?.balance == -1 { +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_created_address, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return WalletInfoCreatedItem(initialSize, stableId: stableId, context: arguments.context, address: state.address, viewType: .singleItem) +// })) +// index += 1 +// } +// +// } else { +// +// enum TransactionItem : Equatable { +// case transaction(WalletInfoTransaction) +// case date(Int32) +// } +// +// var items: [TransactionItem] = [] +// +// +// for (i, transaction) in state.transactions.enumerated() { +// let prev: WalletInfoTransaction? = i == 0 ? nil : state.transactions[i - 1] +// let next: WalletInfoTransaction? = i == state.transactions.count - 1 ? nil : state.transactions[i + 1] +// if prev == nil { +// let dateId = chatDateId(for: Int32(transaction.timestamp)) +// items.append(.date(Int32(dateId))) +// } +// +// items.append(.transaction(transaction)) +// +// if let next = next { +// let dateId = chatDateId(for: Int32(transaction.timestamp)) +// let nextDateId = chatDateId(for: Int32(next.timestamp)) +// +// if dateId != nextDateId { +// items.append(.date(Int32(nextDateId))) +// } +// } +// } +// +// +// var groupItems:[(TransactionItem, [TransactionItem])] = [] +// var current:[TransactionItem] = [] +// for item in items.reversed() { +// switch item { +// case .date: +// if !current.isEmpty { +// groupItems.append((item, current)) +// current.removeAll() +// } +// case .transaction: +// current.insert(item, at: 0) +// } +// } +// +// for group in groupItems.reversed() { +// switch group.0 { +// case let .date(timestamp): +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_date(timestamp), equatable: InputDataEquatable(timestamp), item: { initialSize, stableId in +// return WalletTransactionDateStickItem(initialSize, timestamp: timestamp, viewType: .firstItem) +// })) +// +// for item in group.1 { +// switch item { +// case let .transaction(transaction): +// struct E : Equatable { +// let transaction: WalletInfoTransaction +// let viewType: GeneralViewType +// } +// +// let value = E(transaction: transaction, viewType: bestGeneralViewType(group.1, for: item)) +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_transaction(transaction.transactionId), equatable: InputDataEquatable(value), item: { initialSize, stableId in +// return WalletInfoTransactionItem(initialSize, stableId: stableId, context: arguments.context, transaction: value.transaction, viewType: value.viewType, action: { +// arguments.openTransaction(transaction) +// }) +// })) +// index += 1 +// default: +// break +// } +// +// } +// +// default: +// break +// } +// } +// } +// } +// +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// return entries +//} +//@available(OSX 10.12, *) +//func WalletInfoController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo) -> InputDataController { +// +// let initialState = WalletInfoState(walletState: nil, updatedTimestamp: nil, previousTimestamp: nil, address: "", syncProgress: 0, isSynced: true, transactions: []) +// let state: ValuePromise = ValuePromise() +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletInfoState)->WalletInfoState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// let syncDisposable = MetaDisposable() +// +// var getController:(()->InputDataController?)? = nil +// +// +// let updateBalanceDisposable = MetaDisposable() +// let updateBalance:()->Void = { +// +// +// +// let signal = combineLatest(queue: .mainQueue(), getCombinedWalletState(storage: tonContext.storage, subject: .wallet(walletInfo), tonInstance: tonContext.instance), walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance) |> mapError { _ in return .generic }, TONKeychain.hasKeys(for: context.account) |> castError(GetCombinedWalletStateError.self)) +// +// let short = signal |> then(signal |> delay(3.3, queue: .mainQueue()) |> restart) +// +// updateBalanceDisposable.set(short.start(next: { state, address, hasKeys in +// var combinedState: CombinedWalletState? +// switch state { +// case let .cached(state): +// combinedState = state +// case let .updated(state): +// combinedState = state +// } +// +// if let combinedState = combinedState { +// var transactions:[WalletInfoTransaction] = [] +// transactions.append(contentsOf: combinedState.topTransactions.map { .completed($0) }) +// transactions.append(contentsOf: combinedState.pendingTransactions.map { .pending($0) }) +// +// var updatedTransactions: [WalletTransaction] = combinedState.topTransactions +// +// var existingIds = Set() +// for transaction in updatedTransactions { +// existingIds.insert(transaction.transactionId) +// } +// let current = stateValue.with { $0.transactions } +// +// for transaction in current { +// switch transaction { +// case let .completed(transaction): +// if !existingIds.contains(transaction.transactionId) { +// existingIds.insert(transaction.transactionId) +// updatedTransactions.append(transaction) +// } +// case .pending: +// break +// } +// } +// let list:[WalletInfoTransaction] = combinedState.pendingTransactions.map { .pending($0) } + updatedTransactions.map { .completed($0) } +// +// +// updateState { +// $0.withUpdatedTimestamp(combinedState.timestamp) +// .withUpdatedPreviousTimestamp(combinedState.timestamp) +// .withUpdatedWalletState(combinedState.walletState) +// .withUpdatedTransactions(list) +// .withUpdatedAddress(address) +// } +// } else { +// updateState { +// $0.withUpdatedAddress(address) +// } +// } +// if !hasKeys { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .unavailable)) +// } +// +// }, error: { error in +// if stateValue.with({$0.updatedTimestamp == nil}) { +// getController?()?.show(toaster: ControllerToaster(text: L10n.walletBalanceInfoRetrieveError)) +// } +// updateState { +// $0.withUpdatedTimestamp($0.previousTimestamp) +// } +// })) +// } +// +// syncDisposable.set(tonContext.instance.syncProgress.start(next: { value in +// updateState { +// $0.withUpdatedSyncProgress(value) +// } +// })) +// +// let transactionListDisposable = MetaDisposable() +// +// var loadMoreTransactions: Bool = true +// +// let loadTransactions:(WalletTransactionId?)->Void = { transactionId in +// if !loadMoreTransactions { +// return +// } +// loadMoreTransactions = false +// let signal = getWalletTransactions(address: stateValue.with { $0.address }, previousId: transactionId, tonInstance: tonContext.instance) |> deliverOnMainQueue +// transactionListDisposable.set(signal.start(next: { list in +// loadMoreTransactions = true +// updateState { +// $0.withAddedTransactions(list.map { .completed($0) }) +// } +// }, error: { error in +// +// })) +// } +// +// let invokeUpdate:()->Void = { +// if stateValue.with({ $0.updatedTimestamp != nil }) { +// updateState { +// $0.withUpdatedTimestamp(nil) +// } +// getController?()?.tableView.scroll(to: .up(true)) +// updateBalance() +// } +// } +// +// let arguments = WalletInfoArguments(context: context, openReceive: { +// showModal(with: WalletReceiveController(context: context, tonContext: tonContext, address: stateValue.with { $0.address }), for: context.window) +// }, openSend: { +// showModal(with: WalletSendController(context: context, tonContext: tonContext, walletInfo: walletInfo, walletState: stateValue.with { $0 }.walletState, updateWallet: invokeUpdate), for: context.window) +// }, openTransaction: { transaction in +// showModal(with: WalletTransactionPreviewController(context: context, tonContext: tonContext, walletInfo: walletInfo, transaction: transaction, walletState: stateValue.with { $0 }.walletState, updateWallet: invokeUpdate), for: context.window) +// }, update: invokeUpdate) +// +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { +// return walletInfoEntries($0, arguments: arguments) +// } |> map { +// return InputDataSignalValue(entries: $0, animated: true) +// } +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletBalanceInfoTitle, hasDone: false, identifier: "wallet-info") +// +// controller.onDeinit = { +// transactionListDisposable.dispose() +// updateBalanceDisposable.dispose() +// syncDisposable.dispose() +// } +// +// +// +// controller.didLoaded = { controller, _ in +// controller.tableView.setScrollHandler { position in +// switch position.direction { +// case .bottom: +// let lastTransactionId: WalletTransactionId? = stateValue.with { state in +// if let last = state.transactions.last { +// switch last { +// case let .completed(transaction): +// return transaction.transactionId +// +// case .pending: +// break +// } +// } +// return nil +// } +// +// loadTransactions(lastTransactionId) +// default: +// break +// } +// } +// controller.tableView.set(stickClass: WalletTransactionDateStickItem.self, handler: { item in +// +// }) +// } +// +// controller.customRightButton = { controller in +// let rightView = ImageBarView(controller: controller, theme.icons.wallet_settings) +// +// rightView.button.set(handler: { _ in +// showModal(with: WalletSettingsController(context: context, tonContext: tonContext, walletInfo: walletInfo), for: context.window) +// }, for: .Click) +// +// return rightView +// } +// +// getController = { [weak controller] in +// return controller +// } +// +// updateBalance() +// +// return controller +//} diff --git a/Telegram-Mac/WalletInfoCreatedItem.swift b/Telegram-Mac/WalletInfoCreatedItem.swift new file mode 100644 index 0000000000..f8da8aa525 --- /dev/null +++ b/Telegram-Mac/WalletInfoCreatedItem.swift @@ -0,0 +1,121 @@ +//// +//// WalletInfoCreatedItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 20/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import TelegramCore +//import SyncCore +// +// +//class WalletInfoCreatedItem: GeneralRowItem { +// fileprivate let addressLayout: TextViewLayout +// fileprivate let headerView: TextViewLayout +// fileprivate let yourWalletAddress: TextViewLayout +// fileprivate let context: AccountContext +// fileprivate let animation: TelegramMediaFile = LocalAnimatedSticker.chiken_born.file +// private var _h: CGFloat = 0 +// +// init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, address: String, viewType: GeneralViewType) { +// var addressString: String +// if address.count % 2 == 0 { +// addressString = String(address.prefix(address.count / 2) + "\n" + address.suffix(address.count / 2)) +// } else { +// addressString = address +// } +//// addressString = Array(addressString).map { String($0) }.joined(separator: " ") +// self.addressLayout = TextViewLayout(.initialize(string: addressString, color: theme.colors.listGrayText, font: .blockchain(.title)), alignment: .center) +// self.yourWalletAddress = TextViewLayout(.initialize(string: L10n.walletInfoWalletCreatedText, color: theme.colors.listGrayText, font: .normal(.title))) +// self.headerView = TextViewLayout(.initialize(string: L10n.walletInfoWalletCreatedHeader, color: theme.colors.listGrayText, font: .bold(22))) +// self.context = context +// super.init(initialSize, stableId: stableId, viewType: viewType) +// } +// +// override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// _ = self.addressLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// _ = self.yourWalletAddress.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// _ = self.headerView.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// var height = self.viewType.innerInset.top + addressLayout.layoutSize.height + self.viewType.innerInset.top + self.yourWalletAddress.layoutSize.height + self.viewType.innerInset.top + self.headerView.layoutSize.height + self.viewType.innerInset.bottom +// +// height += 150 + self.viewType.innerInset.top +// +// self._h = height +// return true +// } +// +// override var height: CGFloat { +// return _h +// } +// +// override func viewClass() -> AnyClass { +// return WalletInfoCreatedView.self +// } +//} +// +// +//private final class WalletInfoCreatedView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let headerView: TextView = TextView() +// private let descView: TextView = TextView() +// private let addressView: TextView = TextView() +// private let animationView: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) +// +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// addSubview(containerView) +// headerView.isSelectable = false +// descView.isSelectable = false +// containerView.addSubview(headerView) +// containerView.addSubview(descView) +// containerView.addSubview(addressView) +// containerView.addSubview(animationView) +// } +// required init?(coder: NSCoder) { +// super.init(coder: coder) +// } +// +// override var backdorColor: NSColor { +// return theme.colors.listBackground +// } +// +// override func updateColors() { +// guard let item = item as? WalletInfoCreatedItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletInfoCreatedItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// animationView.centerX(y: item.viewType.innerInset.top) +// headerView.centerX(y: animationView.frame.maxY + item.viewType.innerInset.top) +// descView.centerX(y: headerView.frame.maxY + item.viewType.innerInset.top) +// addressView.centerX(y: descView.frame.maxY + item.viewType.innerInset.top) +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletInfoCreatedItem else { +// return +// } +// +// addressView.update(item.addressLayout) +// descView.update(item.yourWalletAddress) +// headerView.update(item.headerView) +// +// animationView.update(with: item.animation, size: NSMakeSize(150, 150), context: item.context, parent: nil, table: item.table, parameters: nil, animated: animated, positionFlags: nil, approximateSynchronousValue: !animated) +// } +//} diff --git a/Telegram-Mac/WalletInfoTransactionItem.swift b/Telegram-Mac/WalletInfoTransactionItem.swift new file mode 100644 index 0000000000..aec4d0c03d --- /dev/null +++ b/Telegram-Mac/WalletInfoTransactionItem.swift @@ -0,0 +1,317 @@ +//// +//// WalletInfoTransactionItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 23/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import TGUIKit +// +// +//class WalletInfoTransactionItem: GeneralRowItem { +// fileprivate let transaction: WalletInfoTransaction +// fileprivate let titleLayout: TextViewLayout +// fileprivate let dateLayout: TextViewLayout +// fileprivate let addressLayout: TextViewLayout +// fileprivate let commentLayout: TextViewLayout? +// fileprivate let feeLayout: TextViewLayout? +// fileprivate let context: AccountContext +// init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, transaction: WalletInfoTransaction, viewType: GeneralViewType, action: @escaping()->Void) { +// self.transaction = transaction +// self.context = context +// let title: String +// let directionText: String +// let titleColor: NSColor +// +// let transferredValue: Int64 +// switch transaction { +// case let .completed(transaction): +// transferredValue = transaction.transferredValueWithoutFees +// case let .pending(transaction): +// transferredValue = -transaction.value +// } +// let address = stringForAddress(address: extractAddress(transaction)) +// var singleAddress: String? +// let comment = extractDescription(transaction) +// +// +// +// var text: String = "" +// if transferredValue <= 0 { +// +// title = "\(formatBalanceText(abs(transferredValue)))" +// titleColor = theme.colors.redUI +// +// switch transaction { +// case let .completed(transaction): +// if transaction.outMessages.isEmpty { +// directionText = "" +// text = L10n.walletTransactionEmptyTransaction +// } else { +// directionText = L10n.walletTransactionTo +// for message in transaction.outMessages { +// if !text.isEmpty { +// text.append("\n") +// } +// text.append(formatAddress(message.destination)) +// } +// } +// case let .pending(transaction): +// directionText = L10n.walletTransactionTo +// if !text.isEmpty { +// text.append("\n") +// } +// text.append(formatAddress(transaction.address)) +// } +// } else { +// title = "\(formatBalanceText(transferredValue))" +// titleColor = theme.colors.greenUI +// directionText = L10n.walletTransactionFrom +// switch transaction { +// case let .completed(transaction): +// if let inMessage = transaction.inMessage { +// text = formatAddress(inMessage.source) +// } else { +// text = "" +// } +// case .pending: +// text = "" +// } +// +// } +// +// switch transaction { +// case let .completed(transaction): +// let fees = transaction.otherFee + transaction.storageFee +// if fees > 0 { +// self.feeLayout = TextViewLayout(.initialize(string: L10n.walletBalanceInfoTransactionFees(formatBalanceText(fees)), color: theme.colors.grayText, font: .normal(.text))) +// } else { +// self.feeLayout = nil +// } +// default: +// self.feeLayout = nil +// } +// +// +// let date = Date(timeIntervalSince1970: TimeInterval(transaction.timestamp) - context.timeDifference) +// let formatter = DateFormatter() +// formatter.timeStyle = .short +// +// let dateText = formatter.string(from: date) +// +// let titleAttr = NSMutableAttributedString() +// +// +// if let range = title.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(title[.. Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// +// +// self.dateLayout.measure(width: .greatestFiniteMagnitude) +// self.titleLayout.measure(width: .greatestFiniteMagnitude) +// +// self.addressLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// self.commentLayout?.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// self.feeLayout?.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// +// return true +// } +// +// override func viewClass() -> AnyClass { +// return WalletInfoTransactionView.self +// } +// +//} +// +//private final class WalletInfoTransactionView: TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private var pendingView: SendingClockProgress? +// private let crystalView: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) +// private let borderView: View = View() +// private let titleView = TextView() +// private let dateView = TextView() +// private let addressView = TextView() +// private let commentsView = TextView() +// private let feeView = TextView() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// addSubview(self.containerView) +// +// self.containerView.addSubview(borderView) +// self.containerView.addSubview(titleView) +// self.containerView.addSubview(dateView) +// self.containerView.addSubview(addressView) +// self.containerView.addSubview(commentsView) +// self.containerView.addSubview(feeView) +// self.containerView.addSubview(crystalView) +// titleView.userInteractionEnabled = false +// dateView.userInteractionEnabled = false +// addressView.userInteractionEnabled = false +// commentsView.userInteractionEnabled = false +// feeView.userInteractionEnabled = false +// titleView.isSelectable = false +// dateView.isSelectable = false +// commentsView.isSelectable = false +// feeView.isSelectable = false +// self.containerView.set(handler: { [weak self] _ in +// if let item = self?.item as? GeneralRowItem { +// item.action() +// } +// }, for: .Click) +// +// containerView.set(handler: { [weak self] _ in +// self?.updateColors() +// }, for: .Highlight) +// +// containerView.set(handler: { [weak self] _ in +// self?.updateColors() +// }, for: .Hover) +// +// containerView.set(handler: { [weak self] _ in +// self?.updateColors() +// }, for: .Normal) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override var backdorColor: NSColor { +// return theme.colors.background +// } +// +// override func updateColors() { +// guard let item = item as? WalletInfoTransactionItem else { +// return +// } +// +// let highlighted = item.viewType.isPlainMode ? self.backdorColor : theme.colors.grayHighlight +// +// self.backgroundColor = item.viewType.rowBackground +// self.borderView.backgroundColor = theme.colors.border +// self.titleView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor +// self.dateView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor +// self.addressView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor +// self.commentsView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor +// self.feeView.backgroundColor = containerView.controlState == .Highlight ? highlighted : backdorColor +// containerView.set(background: self.backdorColor, for: .Normal) +// containerView.set(background: highlighted, for: .Highlight) +// +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletInfoTransactionItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// titleView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left + crystalView.frame.width, item.viewType.innerInset.top)) +// crystalView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, item.viewType.innerInset.top - 1)) +// dateView.setFrameOrigin(NSMakePoint(item.blockWidth - dateView.frame.width - item.viewType.innerInset.right, item.viewType.innerInset.top)) +// addressView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, titleView.frame.maxY + 4)) +// commentsView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, addressView.frame.maxY + 4)) +// +// if let pendingView = pendingView { +// pendingView.setFrameOrigin(NSMakePoint(item.blockWidth - pendingView.frame.width - item.viewType.innerInset.right - dateView.frame.width - 3, item.viewType.innerInset.top)) +// } +// +// let feeUpperView: NSView = commentsView.layout != nil ? commentsView : addressView +// feeView.setFrameOrigin(NSMakePoint(item.viewType.innerInset.left, feeUpperView.frame.maxY + 4)) +// +// borderView.frame = NSMakeRect(item.viewType.innerInset.left, self.containerView.frame.height - .borderSize, item.blockWidth - item.viewType.innerInset.left - item.viewType.innerInset.right, .borderSize) +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletInfoTransactionItem else { +// return +// } +// +// self.dateView.update(item.dateLayout) +// self.titleView.update(item.titleLayout) +// self.addressView.update(item.addressLayout) +// self.commentsView.update(item.commentLayout) +// self.feeView.update(item.feeLayout) +// borderView.isHidden = !item.viewType.hasBorder +// +// switch item.transaction { +// case .pending: +// if pendingView == nil { +// pendingView = SendingClockProgress() +// containerView.addSubview(pendingView!) +// } +// self.pendingView?.applyGray() +// case .completed: +// if let pendingView = self.pendingView { +// self.pendingView = nil +// if animated { +// pendingView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak pendingView] _ in +// pendingView?.removeFromSuperview() +// }) +// } else { +// pendingView.removeFromSuperview() +// } +// } +// } +// +// let parameters = ChatAnimatedStickerMediaLayoutParameters(playPolicy: .once, media: LocalAnimatedSticker.brilliant_static.file) +// +// crystalView.update(with: LocalAnimatedSticker.brilliant_static.file, size: NSMakeSize(16, 16), context: item.context, parent: nil, table: nil, parameters: parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: true) +// +// +// needsLayout = true +// } +// +//} diff --git a/Telegram-Mac/WalletIntroRowItem.swift b/Telegram-Mac/WalletIntroRowItem.swift new file mode 100644 index 0000000000..4d5bd55e10 --- /dev/null +++ b/Telegram-Mac/WalletIntroRowItem.swift @@ -0,0 +1,150 @@ +// +// WalletSplashRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 19/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCoreMac + +extension WalletSplashMode { + var splashAnimation: String { + switch self { + case .intro: + return "❤️" + default: + return "👍" + } + } + var title: String { + switch self { + case .intro: + return "Gram Wallet" + case .created: + return "Congratulations" + case .success: + return "Ready to go!" + case .restoreFailed: + return "Too Bad" + } + } + var desc: String { + switch self { + case .intro: + return "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries." + case .created: + return "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode." + case .success: + return "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers. " + case .restoreFailed: + return "Without the secret words, you can't'nrestore access to the wallet." + } + } +} + +class WalletSplashRowItem: GeneralRowItem { + fileprivate let mode:WalletSplashMode + fileprivate let descLayout: TextViewLayout + fileprivate let titleLayout: TextViewLayout + fileprivate let animation: TelegramMediaFile? + fileprivate let context: AccountContext + private var h: CGFloat = 0 + init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, mode: WalletSplashMode, animations: [String: TelegramMediaFile], viewType: GeneralViewType) { + self.mode = mode + self.context = context + self.animation = animations[mode.splashAnimation] + self.descLayout = TextViewLayout(.initialize(string: mode.desc, color: theme.colors.text, font: .normal(.text)), alignment: .center) + self.titleLayout = TextViewLayout(.initialize(string: mode.title, color: theme.colors.text, font: .medium(.huge)), alignment: .center) + + super.init(initialSize, stableId: stableId, viewType: viewType) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override var height: CGFloat { + return self.h + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { + _ = super.makeSize(width, oldWidth: oldWidth) + + self.descLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) + self.titleLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) + + self.h = self.viewType.innerInset.top + self.viewType.innerInset.bottom + self.descLayout.layoutSize.height + self.viewType.innerInset.top + self.titleLayout.layoutSize.height + self.viewType.innerInset.top + 150 + + return true + } + + override func viewClass() -> AnyClass { + return WalletIntroRowView.self + } +} + + +private final class WalletIntroRowView : TableRowView { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let titleView = TextView() + private let descView = TextView() + private let animationView: ChatMediaAnimatedStickerView = ChatMediaAnimatedStickerView(frame: NSZeroRect) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + containerView.addSubview(animationView) + containerView.addSubview(titleView) + containerView.addSubview(descView) + titleView.userInteractionEnabled = false + titleView.isSelectable = false + descView.userInteractionEnabled = false + descView.isSelectable = false + addSubview(containerView) + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + override func updateColors() { + guard let item = item as? WalletSplashRowItem else { + return + } + self.backgroundColor = item.viewType.rowBackground + self.titleView.background = backdorColor + self.descView.background = backdorColor + self.containerView.backgroundColor = backdorColor + } + + override func layout() { + super.layout() + guard let item = item as? WalletSplashRowItem else { + return + } + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(item.viewType.corners) + + animationView.centerX(y: item.viewType.innerInset.top) + titleView.centerX(y: animationView.frame.maxY + item.viewType.innerInset.top) + descView.centerX(y: titleView.frame.maxY + item.viewType.innerInset.top) + + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? WalletSplashRowItem else { + return + } + titleView.update(item.titleLayout) + descView.update(item.descLayout) + if let animation = item.animation { + animationView.update(with: animation, size: NSMakeSize(150, 150), context: item.context, parent: nil, table: item.table, parameters: nil, animated: animated, positionFlags: nil, approximateSynchronousValue: !animated) + } + + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Telegram-Mac/WalletKeychainController.swift b/Telegram-Mac/WalletKeychainController.swift new file mode 100644 index 0000000000..7d11855919 --- /dev/null +++ b/Telegram-Mac/WalletKeychainController.swift @@ -0,0 +1,14 @@ +//// +//// WalletKeychainController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 03/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +// +////func WalletKeychainController(context: AccountContext) -> InputDataModalController { +//// +////} diff --git a/Telegram-Mac/WalletPasscodeTimeout.swift b/Telegram-Mac/WalletPasscodeTimeout.swift new file mode 100644 index 0000000000..9ea38f848b --- /dev/null +++ b/Telegram-Mac/WalletPasscodeTimeout.swift @@ -0,0 +1,171 @@ +//// +//// WalletPasscodeTimeout.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 08/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import SwiftSignalKit +//import TelegramCore +//import SyncCore +//import Postbox +// +//enum WalletPasscodeTimeoutLevel : Int32, Equatable { +// case none = 0 +// case first = 1 +// case second = 2 +// case thrid = 3 +// case fourth = 4 +// case fifth = 5 +// case six = 6 +// case seven = 7 +// case eight = 8 +// case nine = 9 +// var timeout: Int32 { +// switch self { +// case .first: +// return 0 +// case .none: +// return 0 +// case .second: +// return 0 +// case .thrid: +// return 0 +// case .fourth: +// return 30 +// case .fifth: +// return 30 +// case .six: +// return 60 +// case .seven: +// return 60 * 5 +// case .eight: +// return 60 * 30 +// case .nine: +// return 60 * 60 +// } +// } +// var incremented: WalletPasscodeTimeoutLevel { +// let value = self.rawValue +// return WalletPasscodeTimeoutLevel(rawValue: value + 1) ?? .nine +// } +// +//} +// +//struct WalletPasscodeTimeout: PreferencesEntry, Equatable { +// let timeout: Int32 +// let level: WalletPasscodeTimeoutLevel +// static var defaultSettings: WalletPasscodeTimeout { +// return WalletPasscodeTimeout(timeout: 0, level: .none) +// } +// +// init(timeout: Int32, level: WalletPasscodeTimeoutLevel) { +// self.timeout = timeout +// self.level = level +// } +// +// init(decoder: PostboxDecoder) { +// self.timeout = decoder.decodeInt32ForKey("to", orElse: 0) +// self.level = WalletPasscodeTimeoutLevel(rawValue: decoder.decodeInt32ForKey("level", orElse: 0)) ?? .none +// } +// +// func encode(_ encoder: PostboxEncoder) { +// encoder.encodeInt32(self.timeout, forKey: "to") +// encoder.encodeInt32(self.level.rawValue, forKey: "level") +// } +// +// func isEqual(to: PreferencesEntry) -> Bool { +// if let to = to as? WalletPasscodeTimeout { +// return self == to +// } else { +// return false +// } +// } +// +// func withUpdatedTimeout(_ timeout: Int32) -> WalletPasscodeTimeout { +// return WalletPasscodeTimeout(timeout: timeout, level: self.level) +// } +// func withUpdatedLevel(_ level: WalletPasscodeTimeoutLevel) -> WalletPasscodeTimeout { +// return WalletPasscodeTimeout(timeout: self.timeout, level: level) +// } +//} +// +//func updateWalletTimeoutInteractively(postbox: Postbox, _ f: @escaping (WalletPasscodeTimeout) -> WalletPasscodeTimeout) -> Signal { +// return postbox.transaction { transaction -> Void in +// transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.walletPasscodeTimeout, { entry in +// let currentSettings: WalletPasscodeTimeout +// if let entry = entry as? WalletPasscodeTimeout { +// currentSettings = entry +// } else { +// currentSettings = WalletPasscodeTimeout.defaultSettings +// } +// return f(currentSettings) +// }) +// } +//} +// +// +// +//final class WalletPasscodeTimeoutContext { +// private(set) var timeout: WalletPasscodeTimeout = .defaultSettings { +// didSet { +// applyTimer() +// self.valuePromise.set(timeout.timeout) +// } +// } +// +// private var valuePromise:ValuePromise = ValuePromise(0, ignoreRepeated: true) +// +// var value: Signal { +// return self.valuePromise.get() +// } +// +// private let postbox: Postbox +// private let disposable = MetaDisposable() +// private let updateTimeoutDisposable = MetaDisposable() +// private let updateSettingsDisposable = MetaDisposable() +// init(postbox: Postbox) { +// self.postbox = postbox +// self.disposable.set((postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.walletPasscodeTimeout]) |> deliverOnMainQueue).start(next: { [weak self] view in +// self?.timeout = view.values[ApplicationSpecificPreferencesKeys.walletPasscodeTimeout] as? WalletPasscodeTimeout ?? WalletPasscodeTimeout.defaultSettings +// })) +// } +// +// private func applyTimer() { +// if timeout.timeout > 0 { +// let signal = updateWalletTimeoutInteractively(postbox: self.postbox, { +// $0.withUpdatedTimeout($0.timeout - 1) +// }) |> delay(1.0, queue: .concurrentDefaultQueue()) +// updateTimeoutDisposable.set(signal.start()) +// } else { +// updateTimeoutDisposable.set(nil) +// } +// } +// +// func incrementLevel() { +// updateSettingsDisposable.set(updateWalletTimeoutInteractively(postbox: self.postbox, { settings in +// return settings.withUpdatedLevel(settings.level.incremented).withUpdatedTimeout(settings.level.timeout) +// }).start()) +// } +// +// func disposeLevel() { +// updateSettingsDisposable.set(updateWalletTimeoutInteractively(postbox: self.postbox, { settings in +// return settings.withUpdatedLevel(.none).withUpdatedTimeout(0) +// }).start()) +// } +// +// +// func clear() { +// disposable.dispose() +// updateTimeoutDisposable.dispose() +// updateSettingsDisposable.dispose() +// } +// +// deinit { +// clear() +// } +//} +// +// diff --git a/Telegram-Mac/WalletProcessTransactionController.swift b/Telegram-Mac/WalletProcessTransactionController.swift new file mode 100644 index 0000000000..6312998f8b --- /dev/null +++ b/Telegram-Mac/WalletProcessTransactionController.swift @@ -0,0 +1,382 @@ +//// +//// WalletProcessTransactionController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 04/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import WalletCore +// +//private final class WalletTransactionArguments { +// let context: AccountContext +// let buttonAction: ()->Void +// init(context: AccountContext, buttonAction: @escaping()->Void) { +// self.context = context +// self.buttonAction = buttonAction +// } +//} +// +//enum WalletTransactionMode : Equatable { +// case none +// case passcode +// case sending +// case sent +// +// var title: String { +// switch self { +// case .passcode: +// return L10n.walletProcessTransactionPasscodeTitle +// case .sending: +// return L10n.walletSendSendingTitle +// default: +// return L10n.walletSendSentTitle +// } +// } +// +// var headerId:InputDataIdentifier { +// switch self { +// case .none: +// return InputDataIdentifier("_header_none") +// case .passcode: +// return InputDataIdentifier("_header_passcode") +// case .sending: +// return InputDataIdentifier("_header_sending") +// case .sent: +// return InputDataIdentifier("_header_sent") +// } +// } +// +// var text: String { +// switch self { +// case .passcode: +// return L10n.walletProcessTransactionPasscodeText +// case .sending: +// return L10n.walletSendSendingText +// default: +// return "" +// } +// } +//} +// +//private struct WalletTransactionState : Equatable { +// let amount: Int64 +// let randomId: Int64 +// let mode: WalletTransactionMode +// let passcode: InputDataValue? +// let passcodeError: InputDataValueError? +// init(amount: Int64, randomId: Int64, mode: WalletTransactionMode, passcode: InputDataValue?, passcodeError: InputDataValueError?) { +// self.amount = amount +// self.randomId = randomId +// self.mode = mode +// self.passcode = passcode +// self.passcodeError = passcodeError +// } +// +// func withUpdatedPasscode(_ passcode: InputDataValue?) -> WalletTransactionState { +// return WalletTransactionState(amount: self.amount, randomId: randomId, mode: self.mode, passcode: passcode, passcodeError: self.passcodeError) +// } +// func withUpdatedPasscodeError(_ passcodeError: InputDataValueError?) -> WalletTransactionState { +// return WalletTransactionState(amount: self.amount, randomId: randomId, mode: self.mode, passcode: self.passcode, passcodeError: passcodeError) +// } +// func withUpdatedMode(_ mode: WalletTransactionMode) -> WalletTransactionState { +// return WalletTransactionState(amount: self.amount, randomId: randomId, mode: mode, passcode: self.passcode, passcodeError: self.passcodeError) +// } +//} +//private let _id_header = InputDataIdentifier("_id_header") +//private let _id_passcode = InputDataIdentifier("_id_passcode") +//private let _id_button = InputDataIdentifier("_id_button") +// +//@available (OSX 10.12, *) +//private func entries(state: WalletTransactionState, arguments: WalletTransactionArguments) -> [InputDataEntry] { +// var entries: [InputDataEntry] = [] +// +// var sectionId: Int32 = 0 +// var index: Int32 = 0 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("topDynamic"), equatable: InputDataEquatable(arc4random()), item: { initialSize, stableId in +// return DynamicHeightRowItem(initialSize, stableId: stableId, side: .top) +// })) +// +// let animation: LocalAnimatedSticker? +// +// switch state.mode { +// case .passcode: +// animation = LocalAnimatedSticker.keychain +// case .sending: +// animation = LocalAnimatedSticker.fly_dollar +// case .sent: +// animation = LocalAnimatedSticker.gift +// case .none: +// animation = nil +// } +// +// let desc: String +// switch state.mode { +// case .passcode: +// if let error = state.passcodeError { +// desc = L10n.walletProcessTransactionPasscodeTextError(error.description) +// } else { +// desc = state.mode.text +// } +// case .sending: +// desc = state.mode.text +// case .sent: +// desc = L10n.walletSendSentText(formatBalanceText(state.amount)) +// case .none: +// desc = "" +// } +// +// let title: String +// switch state.mode { +// case .passcode: +// if let _ = state.passcodeError { +// title = L10n.walletProcessTransactionPasscodeTitleError +// } else { +// title = state.mode.title +// } +// default: +// title = state.mode.title +// } +// +// switch state.mode { +// case .none: +// break +// default: +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: state.mode.headerId, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return WalletSplashRowItem(initialSize, stableId: stableId, context: arguments.context, title: title, desc: desc, animation: animation, viewType: .modern(position: .inner, insets: NSEdgeInsets()), action: { _ in }) +// })) +// index += 1 +// } +// +// +// switch state.mode { +// case .passcode: +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.input(sectionId: sectionId, index: index, value: state.passcode ?? .none, error: nil, identifier: _id_passcode, mode: .secure, data: InputDataRowData(viewType: .singleItem, maxBlockWidth: 280), placeholder: nil, inputPlaceholder: L10n.walletProcessTransactionPasscodePlaceholder, filter: { $0 }, limit: 255)) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// // +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_button, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return WalletSplashButtonRowItem(initialSize, stableId: stableId, buttonText: L10n.walletSendProcessTranfer, subButtonText: nil, enabled: state.passcodeError == nil, viewType: .lastItem, subTextAction: { _ in }, action: arguments.buttonAction) +// })) +// index += 1 +// case .sending, .none: +// break +// +// case .sent: +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_button, equatable: InputDataEquatable(state), item: { initialSize, stableId in +// return WalletSplashButtonRowItem(initialSize, stableId: stableId, buttonText: L10n.walletSendSentViewMyWallet, subButtonText: nil, viewType: .lastItem, subTextAction: { _ in }, action: arguments.buttonAction) +// })) +// index += 1 +// } +// +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("bottomDynamic"), equatable: InputDataEquatable(arc4random()), item: { initialSize, stableId in +// return DynamicHeightRowItem(initialSize, stableId: stableId, side: .bottom) +// })) +// +// +// return entries +//} +// +//@available (OSX 10.12, *) +//func WalletProcessTransactionController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, amount: Int64, to address: String, comment: String, updateMode:@escaping(WalletTransactionMode)->Void, updateWallet:((Bool)->Void)? = nil) -> InputDataModalController { +// let initialState = WalletTransactionState(amount: amount, randomId: arc4random64(), mode: .passcode, passcode: nil, passcodeError: nil) +// let state: ValuePromise = ValuePromise(initialState) +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletTransactionState)->WalletTransactionState) -> Void = { f in +// let result = stateValue.modify(f) +// state.set(result) +// updateMode(result.mode) +// } +// +// let checkPasscode = MetaDisposable() +// let sendDisposable = MetaDisposable() +// let updateTimeout = MetaDisposable() +// +// var getController:(()->InputDataController?)? = nil +// +// let arguments = WalletTransactionArguments(context: context, buttonAction: { +// getController?()?.validateInputValues() +// }) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return entries(state: state, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// updateTimeout.set(context.walletPasscodeTimeoutContext.value.start(next: { timeout in +// updateState { current in +// if timeout > 0 { +// let minutes = timeout / 60 +// let seconds = timeout % 60 +// let string = String(format: "%@:%@", minutes < 10 ? "0\(minutes)" : "\(minutes)", seconds < 10 ? "0\(seconds)" : "\(seconds)") +// return current.withUpdatedPasscodeError(InputDataValueError.init(description: string, target: .data)) +// } else { +// return current.withUpdatedPasscodeError(nil) +// } +// } +// })) +// +// var getModalController:(()->InputDataModalController?)? = nil +// +// let controller = InputDataController(dataSignal: dataSignal, title: "", hasDone: false) +// +// func send(_ decryptedSecret: Data, _ state: WalletTransactionState, _ force: Bool) -> InputDataValidation { +// return .fail(.doSomething(next: { f in +// let signal = getServerWalletSalt(network: context.account.network) |> mapError { _ in +// return SendGramsFromWalletError.generic +// } |> mapToSignal { salt in +// sendGramsFromWallet(storage: tonContext.storage, tonInstance: tonContext.instance, walletInfo: walletInfo, decryptedSecret: decryptedSecret, localPassword: salt, toAddress: address, amount: state.amount, textMessage: comment.data(using: .utf8)!, forceIfDestinationNotInitialized: force, timeout: 0, randomId: state.randomId) +// } |> timeout(15.0, queue: .mainQueue(), alternate: .fail(.network)) |> deliverOnMainQueue +// +// sendDisposable.set(signal.start(error: { error in +// var errorText: String? +// switch error { +// case .destinationIsNotInitialized: +// confirm(for: context.window, header: L10n.walletSendErrorTitle, information: L10n.walletSendErrorDestinationIsNotInitialized, okTitle: L10n.walletSendSendAnyway, cancelTitle: "", thridTitle: L10n.modalCancel, successHandler: { result in +// switch result { +// case .basic: +// getController?()?.proccessValidation(send(decryptedSecret, state, true)) +// default: +// updateState { +// $0.withUpdatedMode(.none) +// } +// getModalController?()?.close() +// } +// }) +// case .invalidAddress: +// errorText = L10n.walletSendErrorInvalidAddress +// case .messageTooLong: +// errorText = L10n.unknownError +// case .network: +// errorText = L10n.walletSendErrorNetwork +// case .notEnoughFunds: +// errorText = L10n.walletSendErrorNotEnoughFundsText +// case .secretDecryptionFailed: +// errorText = L10n.walletSendErrorDecryptionFailed +// case .generic: +// errorText = L10n.unknownError +// } +// +// if let errorText = errorText { +// alert(for: context.window, header: L10n.walletSendErrorTitle, info: errorText) +// updateState { +// $0.withUpdatedMode(.none) +// } +// getModalController?()?.close() +// } +// }, completed: { +// updateState { +// $0.withUpdatedMode(.sent) +// } +// updateWallet?(false) +// })) +// +// })) +// } +// +// controller.validateData = { data in +// let state = stateValue.with { $0 } +// switch state.mode { +// case .passcode: +// return .fail(.doSomething(next: { f in +// if state.passcodeError == nil { +// if let passcode = state.passcode?.stringValue { +// let signal = TONKeychain.decryptedSecretKey(walletInfo.encryptedSecret, account: context.account, tonInstance: tonContext.instance, by: passcode) |> deliverOnMainQueue +// checkPasscode.set(signal.start(next: { data in +// if let data = data { +// updateState { +// $0.withUpdatedMode(.sending) +// } +// f(send(data, state, true)) +// context.walletPasscodeTimeoutContext.disposeLevel() +// } else { +// f(.fail(.fields([_id_passcode : .shake]))) +// context.walletPasscodeTimeoutContext.incrementLevel() +// } +// })) +// } else { +// f(.fail(.fields([_id_passcode : .shake]))) +// } +// } else { +// f(.none) +// } +// })) +// case .sent: +// updateWallet?(true) +// default: +// break +// } +// +// return .none +// } +// +// controller.updateDatas = { data in +// updateState { current in +// switch current.mode { +// case .passcode: +// return current.withUpdatedPasscode(data[_id_passcode]) +// default: +// return current +// } +// } +// return .none +// } +// +//// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +//// closeAllModals() +//// }) +// +// controller.onDeinit = { +// checkPasscode.dispose() +// sendDisposable.dispose() +// updateTimeout.dispose() +// } +// +// getController = { [weak controller] in +// return controller +// } +// +// let modalController = InputDataModalController(controller, closeHandler: { f in +// switch stateValue.with ({ $0.mode }) { +// case .passcode, .none: +// f() +// case .sent: +// f() +// closeAllModals() +// default: +// break +// } +// }, size: NSMakeSize(350, 350)) +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// modalController.isFullScreenImpl = { +// return true +// } +// modalController.dynamicSizeImpl = { +// return false +// } +// +// return modalController +//} diff --git a/Telegram-Mac/WalletReceiveController.swift b/Telegram-Mac/WalletReceiveController.swift new file mode 100644 index 0000000000..e21a287681 --- /dev/null +++ b/Telegram-Mac/WalletReceiveController.swift @@ -0,0 +1,137 @@ +//// +//// WalletReceiveController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 23/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import TGUIKit +//import WalletCore +// +//private final class WalletReceiveArguments { +// let context: AccountContext +// let copy:()->Void +// let share:()->Void +// let createInvoice: ()->Void +// init(context: AccountContext, copy: @escaping()->Void, share: @escaping()->Void, createInvoice: @escaping()->Void) { +// self.context = context +// self.copy = copy +// self.share = share +// self.createInvoice = createInvoice +// } +//} +// +//private struct WalletReceiveState : Equatable { +// let address: String +// init(address: String) { +// self.address = address +// } +//} +//private let _id_address = InputDataIdentifier("_id_address") +//private let _id_copy = InputDataIdentifier("_id_copy") +//private let _id_share = InputDataIdentifier("_id_share") +//private let _id_create_invoice = InputDataIdentifier("_id_create_invoice") +//private func walletReceiveEntries(state: WalletReceiveState, arguments: WalletReceiveArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// +// var sectionId:Int32 = 0 +// var index:Int32 = 0 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletReceiveYourWalletAddress), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_address, equatable: InputDataEquatable(state.address), item: { initialSize, stableId in +// let addressString: String +// if state.address.count % 2 == 0 { +// addressString = String(state.address.prefix(state.address.count / 2) + "\n" + state.address.suffix(state.address.count / 2)) +// } else { +// addressString = state.address +// } +// return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .firstItem, text: addressString, font: .code(.text)) +// })) +// index += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_copy, data: InputDataGeneralData(name: L10n.walletReceiveCopyWalletAddress, color: theme.colors.accent, type: .none, viewType: .innerItem, action: arguments.copy))) +// index += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_share, data: InputDataGeneralData(name: L10n.walletReceiveShareWalletAddress, color: theme.colors.accent, type: .none, viewType: .lastItem, action: arguments.share))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletReceiveShareWalletDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_create_invoice, data: InputDataGeneralData(name: L10n.walletReceiveCreateInvoice, color: theme.colors.accent, type: .none, viewType: .singleItem, action: arguments.createInvoice))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletReceiveCreateInvoiceDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// return entries +//} +// +//func WalletReceiveController(context: AccountContext, tonContext: TonContext, address: String) -> InputDataModalController { +// let initialState = WalletReceiveState(address: address) +// let state: ValuePromise = ValuePromise(initialState) +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletReceiveState)->WalletReceiveState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// var getController:(()->InputDataController?)? = nil +// +// let arguments = WalletReceiveArguments(context: context, copy: { +// copyToClipboard(address) +// getController?()?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) +// }, share: { +// showModal(with: ShareModalController(ShareLinkObject(context, link: "ton://transfer/\(escape(with: address, addPercent: true))")), for: context.window) +// }, createInvoice: { +// showModal(with: WalletInvoiceController(context: context, tonContext: tonContext, address: address), for: context.window) +// }) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return walletReceiveEntries(state: state, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// var getModalController:(()->InputDataModalController?)? = nil +// +// +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletReceiveTitle) +// +// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +// getModalController?()?.close() +// }) +// +// getController = { [weak controller] in +// return controller +// } +// +// let modalController = InputDataModalController(controller, closeHandler: { f in +// f() +// }, size: NSMakeSize(350, 350)) +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// +// return modalController +//} diff --git a/Telegram-Mac/WalletSendController.swift b/Telegram-Mac/WalletSendController.swift new file mode 100644 index 0000000000..135b419bd4 --- /dev/null +++ b/Telegram-Mac/WalletSendController.swift @@ -0,0 +1,366 @@ +//// +//// WalletSendController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 23/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import TGUIKit +//import WalletCore +// +// +//private final class WalletSendArguments { +// let context: AccountContext +// init(context: AccountContext) { +// self.context = context +// } +//} +// +//private struct WalletSendingDest : Equatable { +// let randomId: Int64 +// let recipient: String +// let comment: String +// let amount: Int64 +// init(randomId: Int64, amount: Int64, recipient: String, comment: String) { +// self.randomId = randomId +// self.recipient = recipient +// self.amount = amount +// self.comment = comment +// } +//} +// +// +//private struct WalletSendState : Equatable { +// let walletState: WalletState? +// let recipient: String +// let comment: String +// let amount: String +// let sendingState: WalletTransactionMode +// let address: String +// init(walletState: WalletState?, sendingState: WalletTransactionMode, address: String, recipient: String, comment: String, amount: String) { +// self.sendingState = sendingState +// self.walletState = walletState +// self.recipient = recipient +// self.comment = comment +// self.amount = amount +// self.address = address +// } +// func withUpdatedWalletState(_ walletState: WalletState?) -> WalletSendState { +// return WalletSendState(walletState: walletState, sendingState: self.sendingState, address: self.address, recipient: recipient, comment: self.comment, amount: self.amount) +// } +// func withUpdatedRecipient(_ recipient: String) -> WalletSendState { +// return WalletSendState(walletState: self.walletState, sendingState: self.sendingState, address: self.address, recipient: recipient, comment: self.comment, amount: self.amount) +// } +// func withUpdatedComment(_ comment: String) -> WalletSendState { +// return WalletSendState(walletState: self.walletState, sendingState: self.sendingState, address: self.address, recipient: self.recipient, comment: comment, amount: self.amount) +// } +// func withUpdatedAmount(_ amount: String) -> WalletSendState { +// return WalletSendState(walletState: self.walletState, sendingState: self.sendingState, address: self.address, recipient: self.recipient, comment: self.comment, amount: amount) +// } +// func withUpdatedAddress(_ address: String) -> WalletSendState { +// return WalletSendState(walletState: self.walletState, sendingState: self.sendingState, address: address, recipient: self.recipient, comment: self.comment, amount: self.amount) +// } +// func withUpdatedSendingState(_ sendingState: WalletTransactionMode) -> WalletSendState { +// return WalletSendState(walletState: self.walletState, sendingState: sendingState, address: self.address, recipient: self.recipient, comment: self.comment, amount: self.amount) +// } +//} +//private let _id_recipient = InputDataIdentifier("_id_recipient") +//private let _id_amount = InputDataIdentifier("_id_amount") +//private let _id_comment = InputDataIdentifier("_id_comment") +// +//private func WalletSendEntries(state: WalletSendState, arguments: WalletSendArguments) -> [InputDataEntry] { +// var entries: [InputDataEntry] = [] +// +// var sectionId: Int32 = 0 +// var index: Int32 = 0 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletSendRecipientHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// +// entries.append(.input(sectionId: sectionId, index: index, value: .string(state.recipient), error: nil, identifier: _id_recipient, mode: .plain, data: InputDataRowData(viewType: .singleItem, pasteFilter: { value in +// +// let value = value.trimmingCharacters(in: invalidAddressCharacters).replacingOccurrences(of: "\n", with: "") +// +// if isValidAddress(value) { +// return (true, value) +// } +// if let url = URL(string: value), let data = parseWalletUrl(url) { +// return (true, data.address) +// } +// return (false, value) +// }), placeholder: nil, inputPlaceholder: L10n.walletSendRecipientPlaceholder, filter: { value in +// return value.trimmingCharacters(in: invalidAddressCharacters) +// }, limit: Int32(walletAddressLength))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletSendRecipientDesc), data: InputDataGeneralTextData(viewType: .textBottomItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// +// let text: NSMutableAttributedString? +// if let balance = state.walletState?.balance { +// let color = balance > amountValue(state.amount) ? theme.colors.listGrayText : theme.colors.redUI +// var attr: String = "" +// let value = formatBalanceText(max(balance, 0)) +// if let range = value.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(value[..Void)? = nil) -> InputDataModalController { +// let initialState = WalletSendState(walletState: walletState, sendingState: .passcode, address: "", recipient: recipient, comment: comment, amount: formatAmountText(amount)) +// let state: ValuePromise = ValuePromise() +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletSendState)->WalletSendState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// let updateBalanceDisposable = MetaDisposable() +// let transferDisposable = MetaDisposable() +// +// let updateBalance:()->Void = { +// let signal = getCombinedWalletState(storage: tonContext.storage, subject: .wallet(walletInfo), tonInstance: tonContext.instance) +// +// let address = walletAddress(publicKey: walletInfo.publicKey, tonInstance: tonContext.instance) +// |> mapError { _ in +// return GetCombinedWalletStateError.generic +// } +// +// updateBalanceDisposable.set(combineLatest(queue: .mainQueue(), address, signal).start(next: { address, state in +// switch state { +// case let .cached(combinedState): +// if let combinedState = combinedState { +// updateState { +// $0.withUpdatedWalletState(combinedState.walletState) +// .withUpdatedAddress(address) +// } +// } else { +// updateState { +// $0.withUpdatedAddress(address) +// } +// } +// case let .updated(combinedState): +// updateState { +// $0.withUpdatedWalletState(combinedState.walletState) +// .withUpdatedAddress(address) +// } +// } +// }, error: { error in +// +// })) +// } +// +// var getController:(()->InputDataController?)? = nil +// +// let arguments = WalletSendArguments(context: context) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return WalletSendEntries(state: state, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// var getModalController:(()->InputDataModalController?)? = nil +// +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletSendTitle) +// +// controller.updateDatas = { data in +// updateState { +// $0.withUpdatedComment(data[_id_comment]?.stringValue ?? "") +// .withUpdatedRecipient(data[_id_recipient]?.stringValue ?? "") +// .withUpdatedAmount(formatAmountText(data[_id_amount]?.stringValue ?? "")) +// } +// return .none +// } +// +// +// +// +// let serverSaltValue = Promise() +// serverSaltValue.set(getServerWalletSalt(network: context.account.network) +// |> map(Optional.init) +// |> `catch` { _ -> Signal in +// return .single(nil) +// }) +// +// let validationDisposable = MetaDisposable() +// +// controller.validateData = { data in +// let state = stateValue.with { $0 } +// +// let addressIsValid = isValidAddress(state.recipient, exactLength: true) +// let amountIsValid = isValidAmount(state.amount) && amountValue(state.amount) <= state.walletState?.balance ?? 0 && amountValue(state.amount) > 0 +// +// if !addressIsValid { +// return .fail(.fields([_id_recipient : .shake])) +// } +// if !amountIsValid { +// return .fail(.fields([_id_amount : .shake])) +// } +// +// return .fail(.doSomething(next: { f in +// +// let feesSignal = showModalProgress(signal: verifySendGramsRequestAndEstimateFees(tonInstance: tonContext.instance, walletInfo: walletInfo, toAddress: state.recipient, amount: amountValue(state.amount), textMessage: state.comment.data(using: .utf8)!, timeout: 5), for: context.window) +// +// validationDisposable.set(feesSignal.start(next: { fees in +// +// let feeAmount = fees.inFwdFee + fees.storageFee + fees.gasFee + fees.fwdFee +// +// confirm(for: context.window, header: L10n.walletSendConfirmationHeader, information: L10n.walletSendConfirmationText(state.amount, state.recipient, formatBalanceText(feeAmount)), okTitle: L10n.walletSendConfirmationOK, successHandler: { _ in +// +// let state = stateValue.with { $0 } +// +// let invoke:()->Void = { +// +// let controller = WalletProcessTransactionController(context: context, tonContext: tonContext, walletInfo: walletInfo, amount: amountValue(state.amount), to: state.recipient, comment: state.comment, updateMode: { mode in +// updateState { $0.withUpdatedSendingState(mode) } +// }, updateWallet: { close in +// if close { +// getModalController?()?.close() +// } +// if let updateWallet = updateWallet { +// updateWallet() +// } else if close { +// context.sharedContext.bindings.rootNavigation().push(WalletInfoController(context: context, tonContext: tonContext, walletInfo: walletInfo)) +// } +// +// }) +// +// +// if let parentModal = getModalController?()?.modal { +// let modal = Modal(controller: controller, for: context.window, isOverlay: false, animationType: .scaleCenter, parentView: parentModal.containerView) +// modal.show() +// } +// } +// +// if state.recipient == state.address { +// confirm(for: context.window, header: L10n.walletSendConfirmTitle, information: L10n.walletSendSelfConfirmText, okTitle: L10n.walletSendSelfConfirmOK, successHandler: { _ in +// invoke() +// }) +// } else { +// invoke() +// } +// }) +// })) +// })) +// } +// +// let interactions = ModalInteractions(acceptTitle: L10n.modalSend, accept: { [weak controller] in +// controller?.validateInputValues() +// }, drawBorder: true, height: 50, singleButton: true) +// +// getController = { [weak controller] in +// return controller +// } +// +// controller.afterTransaction = { controller in +// interactions.updateDone { title in +// let addressIsValid = isValidAddress(stateValue.with { $0.recipient }, exactLength: true) +// let amountIsValid = isValidAmount(stateValue.with { $0.amount }) && amountValue(stateValue.with { $0.amount }) <= stateValue.with { $0.walletState?.balance ?? 0 } && amountValue(stateValue.with { $0.amount }) > 0 +// title.isEnabled = amountIsValid && addressIsValid +// } +// } +// +// controller.onDeinit = { +// transferDisposable.dispose() +// updateBalanceDisposable.dispose() +// validationDisposable.dispose() +// } +// +// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +// getModalController?()?.close() +// }) +// +// let modalController = InputDataModalController(controller, modalInteractions: interactions, closeHandler: { f in +// f() +// closeAllModals() +// }, size: NSMakeSize(350, 350)) +// +// +// modalController.closableImpl = { +// let value = stateValue.with { $0.sendingState } +// switch value { +// case .passcode, .sent: +// return true +// default: +// return false +// } +// } +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// updateBalance() +// +// return modalController +// +//} +// diff --git a/Telegram-Mac/WalletSendProccessingView.swift b/Telegram-Mac/WalletSendProccessingView.swift new file mode 100644 index 0000000000..0fb4e6f533 --- /dev/null +++ b/Telegram-Mac/WalletSendProccessingView.swift @@ -0,0 +1,245 @@ +//// +//// WalletSendProccessingController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 25/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import TelegramCore +//import SyncCore +// +//final class WalletSendProccessingView : Control { +// private let titleView: TextView = TextView() +// private let textView: TextView = TextView() +// private let containerView = View() +// private let animationView = MediaAnimatedStickerView(frame: NSZeroRect) +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// containerView.addSubview(textView) +// containerView.addSubview(titleView) +// containerView.addSubview(animationView) +// addSubview(containerView) +// updateLocalizationAndTheme(theme: theme) +// +// titleView.userInteractionEnabled = false +// titleView.isSelectable = false +// +// textView.userInteractionEnabled = false +// textView.isSelectable = false +// } +// +// func setup(context: AccountContext) { +// self.animationView.update(with: LocalAnimatedSticker.fly_dollar.file, size: NSMakeSize(200, 200), context: context, parent: nil, table: nil, parameters: LocalAnimatedSticker.fly_dollar.parameters, animated: false, positionFlags: nil, approximateSynchronousValue: true) +// +// needsLayout = true +// } +// +// override func updateLocalizationAndTheme(theme: PresentationTheme) { +// super.updateLocalizationAndTheme(theme: theme) +// self.backgroundColor = theme.colors.listBackground +// let titleLayout = TextViewLayout(.initialize(string: L10n.walletSendSendingTitle, color: theme.colors.text, font: .medium(22)), alignment: .center) +// titleLayout.measure(width: frame.width - 60) +// self.titleView.update(titleLayout) +// +// let textLayout = TextViewLayout(.initialize(string: L10n.walletSendSendingText, color: theme.colors.listGrayText, font: .normal(.text)), alignment: .center) +// textLayout.measure(width: frame.width - 60) +// self.textView.update(textLayout) +// +// self.titleView.backgroundColor = theme.colors.listBackground +// self.textView.backgroundColor = theme.colors.listBackground +// } +// +// override func layout() { +// super.layout() +// +// updateLocalizationAndTheme(theme: theme) +// +// containerView.frame = NSMakeRect(0, 0, frame.width - 60, animationView.frame.height + textView.frame.height + titleView.frame.height + 10) +// containerView.center() +// containerView.setFrameOrigin(NSMakePoint(containerView.frame.minX, containerView.frame.minY - 20)) +// animationView.centerX(y: 0) +// titleView.centerX(y: animationView.frame.maxY) +// textView.centerX(y: titleView.frame.maxY + 10) +// +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} +// +// +// +//final class WalletSentView : Control { +// private let titleView: TextView = TextView() +// private let textView: TextView = TextView() +// private let containerView = View() +// private let animationView = MediaAnimatedStickerView(frame: NSZeroRect) +// private let walletButton = TitleButton() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// containerView.addSubview(textView) +// containerView.addSubview(titleView) +// containerView.addSubview(animationView) +// +// addSubview(containerView) +// addSubview(walletButton) +// titleView.userInteractionEnabled = false +// titleView.isSelectable = false +// +// textView.userInteractionEnabled = false +// textView.isSelectable = false +// +// walletButton.layer?.cornerRadius = 10 +// } +// +// private var amount: String = "" +// +// +// func setup(context: AccountContext, amount: String, callback: @escaping()->Void) { +// self.amount = amount +// self.animationView.update(with: LocalAnimatedSticker.success.file, size: NSMakeSize(150, 150), context: context, parent: nil, table: nil, parameters: LocalAnimatedSticker.success.parameters, animated: false, positionFlags: nil, approximateSynchronousValue: true) +// +// walletButton.removeAllHandlers() +// +// walletButton.set(handler: { _ in +// callback() +// }, for: .Click) +// +// self.updateLocalizationAndTheme(theme: theme) +// } +// +// override func updateLocalizationAndTheme(theme: PresentationTheme) { +// super.updateLocalizationAndTheme(theme: theme) +// self.backgroundColor = theme.colors.listBackground +// let titleLayout = TextViewLayout(.initialize(string: L10n.walletSendSentTitle, color: theme.colors.text, font: .medium(22)), alignment: .center) +// titleLayout.measure(width: frame.width - 60) +// self.titleView.update(titleLayout) +// +// let attr = NSMutableAttributedString() +// _ = attr.append(string: L10n.walletSendSentText(self.amount), color: theme.colors.listGrayText, font: .normal(.text)) +// attr.detectBoldColorInString(with: .medium(.text)) +// let textLayout = TextViewLayout(attr, alignment: .center) +// textLayout.measure(width: frame.width - 60) +// self.textView.update(textLayout) +// +// self.titleView.backgroundColor = theme.colors.listBackground +// self.textView.backgroundColor = theme.colors.listBackground +// +// +// walletButton.set(background: theme.colors.accent, for: .Normal) +// walletButton.set(background: theme.colors.accent.withAlphaComponent(0.8), for: .Highlight) +// walletButton.set(text: L10n.walletSendSentViewMyWallet, for: .Normal) +// walletButton.set(color: theme.colors.underSelectedColor, for: .Normal) +// walletButton.set(font: .medium(.title), for: .Normal) +// +// +// +// needsLayout = true +// } +// +// override func layout() { +// super.layout() +// +// updateLocalizationAndTheme(theme: theme) +// +// containerView.frame = NSMakeRect(0, 0, frame.width - 60, animationView.frame.height + textView.frame.height + titleView.frame.height + 10) +// containerView.center() +// containerView.setFrameOrigin(NSMakePoint(containerView.frame.minX, containerView.frame.minY - 20)) +// animationView.centerX(y: 0) +// animationView.setFrameOrigin(NSMakePoint(animationView.frame.minX + 12, 0)) +// titleView.centerX(y: animationView.frame.maxY) +// textView.centerX(y: titleView.frame.maxY + 10) +// +// walletButton.setFrameSize(NSMakeSize(frame.width - 100, 40)) +// walletButton.centerX(y: frame.height - 25 - 40) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} +// +// +// +//final class WalletPasscodeView : Control { +// private let titleView: TextView = TextView() +// private let textView: TextView = TextView() +// private let containerView = View() +// private let animationView = MediaAnimatedStickerView(frame: NSZeroRect) +// private let inputView = InputDataRowView(frame: NSZeroRect) +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// containerView.addSubview(textView) +// containerView.addSubview(titleView) +// containerView.addSubview(animationView) +// addSubview(containerView) +// addSubview(inputView) +// titleView.userInteractionEnabled = false +// titleView.isSelectable = false +// +// textView.userInteractionEnabled = false +// textView.isSelectable = false +// +// } +// +// func setup(context: AccountContext, decryptedKey: @escaping(Data)->Void) { +// self.animationView.update(with: LocalAnimatedSticker.keychain.file, size: NSMakeSize(150, 150), context: context, parent: nil, table: nil, parameters: LocalAnimatedSticker.success.parameters, animated: false, positionFlags: nil, approximateSynchronousValue: true) +// +// let inputItem = InputDataRowItem(NSMakeSize(350, 40), stableId: 0, mode: .secure, error: nil, viewType: .singleItem, currentText: "", placeholder: nil, inputPlaceholder: "Passcode", filter: { $0 }, updated: { updated in +// +// }, limit: 255) +// +// inputView.set(item: inputItem, animated: true) +// +// self.updateLocalizationAndTheme(theme: theme) +// } +// +// override func updateLocalizationAndTheme(theme: PresentationTheme) { +// super.updateLocalizationAndTheme(theme: theme) +// self.backgroundColor = theme.colors.listBackground +// let titleLayout = TextViewLayout(.initialize(string: "Passcode", color: theme.colors.text, font: .medium(22)), alignment: .center) +// titleLayout.measure(width: frame.width - 60) +// self.titleView.update(titleLayout) +// +// let attr = NSMutableAttributedString() +// _ = attr.append(string: "Please enter your passcode for transafer Grams.", color: theme.colors.listGrayText, font: .normal(.text)) +// attr.detectBoldColorInString(with: .medium(.text)) +// let textLayout = TextViewLayout(attr, alignment: .center) +// textLayout.measure(width: frame.width - 60) +// self.textView.update(textLayout) +// +// self.titleView.backgroundColor = theme.colors.listBackground +// self.textView.backgroundColor = theme.colors.listBackground +// +// +// +// needsLayout = true +// } +// +// override func layout() { +// super.layout() +// +// updateLocalizationAndTheme(theme: theme) +// +// containerView.frame = NSMakeRect(0, 0, frame.width - 60, animationView.frame.height + textView.frame.height + titleView.frame.height + 10) +// containerView.center() +// containerView.setFrameOrigin(NSMakePoint(containerView.frame.minX, containerView.frame.minY - 20)) +// animationView.centerX(y: 0) +// animationView.setFrameOrigin(NSMakePoint(animationView.frame.minX + 12, 0)) +// titleView.centerX(y: animationView.frame.maxY) +// textView.centerX(y: titleView.frame.maxY + 10) +// +// inputView.setFrameSize(NSMakeSize(frame.width, 40)) +// inputView.centerX(y: frame.height - 25 - 40) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} +// +// diff --git a/Telegram-Mac/WalletSendSuccessController.swift b/Telegram-Mac/WalletSendSuccessController.swift new file mode 100644 index 0000000000..4917a3fd05 --- /dev/null +++ b/Telegram-Mac/WalletSendSuccessController.swift @@ -0,0 +1,13 @@ +//// +//// WalletSendSuccessController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 25/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +// +//func WalletSendSuccessController() { +// +//} diff --git a/Telegram-Mac/WalletSettingsController.swift b/Telegram-Mac/WalletSettingsController.swift new file mode 100644 index 0000000000..04bfc7ff9a --- /dev/null +++ b/Telegram-Mac/WalletSettingsController.swift @@ -0,0 +1,106 @@ +//// +//// WalletSettingsController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 01/10/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import Postbox +//import SwiftSignalKit +//import TGUIKit +//import WalletCore +//private final class WalletSettingsArguments { +// let context: AccountContext +// let deleteWallet:()->Void +// init(context: AccountContext, deleteWallet: @escaping()->Void) { +// self.context = context +// self.deleteWallet = deleteWallet +// } +//} +// +//private let _id_delete_wallet = InputDataIdentifier("_id_delete_wallet") +// +//private func walletSettingsEntries(arguments: WalletSettingsArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// var sectionId:Int32 = 0 +// var index:Int32 = 0 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_delete_wallet, data: InputDataGeneralData.init(name: L10n.walletSettingsDeleteWallet, color: theme.colors.redUI, viewType: .singleItem, action: arguments.deleteWallet))) +// index += 1 +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletSettingsDeleteWalletDesc), data: InputDataGeneralTextData(color: theme.colors.listGrayText, viewType: .textBottomItem))) +// index += 1 +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// +// return entries +//} +//@available(OSX 10.12, *) +//func WalletSettingsController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo) -> InputDataModalController { +// +// var getController:(()->InputDataController?)? = nil +// var getModalController:(()->InputDataModalController?)? = nil +// +// let arguments = WalletSettingsArguments(context: context, deleteWallet: { +// confirm(for: context.window, header: L10n.walletSettingsDeleteConfirmHeader, information: L10n.walletSettingsDeleteConfirmText, okTitle: L10n.walletSettingsDeleteConfirmOK, successHandler: { _ in +// +// +// +// +// +// let signals = combineLatest(TONKeychain.delete(account: context.account) |> castError(DeleteAllLocalWalletsDataError.self) |> ignoreValues, deleteAllLocalWalletsData(storage: tonContext.storage, tonInstance: tonContext.instance)) +// +// let _ = showModalProgress(signal: signals +// |> deliverOnMainQueue, for: context.window).start(error: { error in +// let text: String +// switch error { +// case .generic: +// text = L10n.unknownError +// } +// alert(for: context.window, info: text) +// }, completed: { +// getModalController?()?.close() +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .intro)) +// }) +// }) +// }) +// +// let signal:Signal<[InputDataEntry], NoError> = .single(walletSettingsEntries(arguments: arguments)) +// +// let dataSignal = signal |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletSettingsTitle, hasDone: false) +// +// +// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +// getModalController?()?.close() +// }) +// +// getController = { [weak controller] in +// return controller +// } +// +// let modalController = InputDataModalController(controller, closeHandler: { f in +// f() +// }, size: NSMakeSize(350, 350)) +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// +// return modalController +// +//} diff --git a/Telegram-Mac/WalletSplashButtonRowItem.swift b/Telegram-Mac/WalletSplashButtonRowItem.swift new file mode 100644 index 0000000000..e7dd2b96d1 --- /dev/null +++ b/Telegram-Mac/WalletSplashButtonRowItem.swift @@ -0,0 +1,125 @@ +//// +//// WalletSplashButtonRowItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import TelegramCore +//import SyncCore +// +// +// +//@available (OSX 10.12, *) +//class WalletSplashButtonRowItem: GeneralRowItem { +// fileprivate let subTextLayout: TextViewLayout? +// private var h: CGFloat = 0 +// fileprivate let buttonText: String +// init(_ initialSize: NSSize, stableId: AnyHashable, buttonText: String, subButtonText: String?, enabled: Bool = true, viewType: GeneralViewType, subTextAction:@escaping(String)->Void, action: @escaping()->Void) { +// self.buttonText = buttonText +// if let subText = subButtonText { +// let attributedText: NSMutableAttributedString = parseMarkdownIntoAttributedString(subText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(12.5), textColor: theme.colors.listGrayText), bold: MarkdownAttributeSet(font: .bold(12.5), textColor: theme.colors.listGrayText), link: MarkdownAttributeSet(font: .normal(12.5), textColor: theme.colors.link), linkAttribute: { contents in +// return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, subTextAction)) +// })).mutableCopy() as! NSMutableAttributedString +// +// self.subTextLayout = TextViewLayout(attributedText, alignment: .center) +// self.subTextLayout?.interactions = globalLinkExecutor +// } else { +// self.subTextLayout = nil +// } +// super.init(initialSize, stableId: stableId, viewType: viewType, action: action, enabled: enabled) +// } +// +// override var height: CGFloat { +// return self.h +// } +// +// override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// +// if let subTextLayout = subTextLayout { +// subTextLayout.measure(width: self.blockWidth - viewType.innerInset.left - viewType.innerInset.right) +// self.h = 40 + subTextLayout.layoutSize.height + self.viewType.innerInset.bottom +// } else { +// self.h = 40 +// } +// +// +// return true +// } +// +// override func viewClass() -> AnyClass { +// return WalletSplashButtonRowView.self +// } +//} +// +// +//@available (OSX 10.12, *) +//private final class WalletSplashButtonRowView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let descView = TextView() +// private let button = TitleButton() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// descView.isSelectable = false +// containerView.addSubview(descView) +// containerView.addSubview(button) +// addSubview(containerView) +// button.layer?.cornerRadius = 10 +// } +// +// override var backdorColor: NSColor { +// return theme.colors.listBackground +// } +// +// override func updateColors() { +// guard let item = item as? WalletSplashButtonRowItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.descView.background = backdorColor +// self.containerView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletSplashButtonRowItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// button.centerX(y: 0) +// descView.centerX(y: button.frame.maxY + item.viewType.innerInset.top) +// +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletSplashButtonRowItem else { +// return +// } +// +// button.removeAllHandlers() +// button.set(handler: { [weak item] _ in +// item?.action() +// }, for: .Click) +// +// descView.update(item.subTextLayout) +// button.set(font: .medium(.header), for: .Normal) +// button.set(color: theme.colors.underSelectedColor, for: .Normal) +// button.set(background: !item.enabled ? theme.colors.accent.withAlphaComponent(0.8) : theme.colors.accent, for: .Normal) +// button.set(background: theme.colors.accent.withAlphaComponent(0.8), for: .Highlight) +// button.set(text: item.buttonText, for: .Normal) +// _ = button.sizeToFit(NSZeroSize, NSMakeSize(280, 40), thatFit: true) +// needsLayout = true +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/Telegram-Mac/WalletSplashController.swift b/Telegram-Mac/WalletSplashController.swift new file mode 100644 index 0000000000..c02fe5414e --- /dev/null +++ b/Telegram-Mac/WalletSplashController.swift @@ -0,0 +1,580 @@ +//// +//// WalletSplashController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import Postbox +//import TGUIKit +//import WalletCore +// +//@available (OSX 10.12, *) +//enum WalletSplashMode : Equatable { +// case intro +// case createPasscode(TKKey, WalletInfo) +// case created(TKKey, WalletInfo, [String]) +// case save24Words(TKKey, WalletInfo, [String], TimeInterval) +// case success(WalletInfo) +// case testWords(TKKey, WalletInfo, [String], [Int]) +// case restoreFailed +// case importExist +// case unavailable +//} +// +//private final class WalletSplashArguments { +// let context: AccountContext +// let action:()->Void +// let copyWords:(String)->Void +// let openRestoreFailed:()->Void +// let openImport:()->Void +// let togglePasscodeMode:()->Void +// let updateImportWords:(InputDataIdentifier, InputDataValue)->Void +// let openTerms:()->Void +// let createNew:()->Void +// init(context: AccountContext, action: @escaping()->Void, copyWords: @escaping(String)->Void, openRestoreFailed: @escaping()->Void, openImport:@escaping()->Void, updateImportWords: @escaping(InputDataIdentifier, InputDataValue)->Void, togglePasscodeMode:@escaping()->Void, openTerms:@escaping()->Void, createNew:@escaping()->Void) { +// self.context = context +// self.action = action +// self.copyWords = copyWords +// self.openRestoreFailed = openRestoreFailed +// self.openImport = openImport +// self.updateImportWords = updateImportWords +// self.togglePasscodeMode = togglePasscodeMode +// self.openTerms = openTerms +// self.createNew = createNew +// } +//} +// +// +//@available (OSX 10.12, *) +//private struct WalletSplashState : Equatable { +// let mode: WalletSplashMode +// let wordsValues:[InputDataIdentifier: InputDataValue] +// let errors:[InputDataIdentifier : InputDataValueError] +// let passcodeState: InputDataInputMode +// init(mode: WalletSplashMode, wordsValues: [InputDataIdentifier: InputDataValue] = [:], errors: [InputDataIdentifier : InputDataValueError], passcodeState: InputDataInputMode) { +// self.mode = mode +// self.wordsValues = wordsValues +// self.errors = errors +// self.passcodeState = passcodeState +// } +// func withUpdatedWordsValue(for key: InputDataIdentifier, value: InputDataValue) -> WalletSplashState { +// var wordsValues = self.wordsValues +// wordsValues[key] = value +// return WalletSplashState(mode: self.mode, wordsValues: wordsValues, errors: self.errors, passcodeState: self.passcodeState) +// } +// func withUpdatedPasscodeState(passcodeState: InputDataInputMode) -> WalletSplashState { +// return WalletSplashState(mode: self.mode, wordsValues: self.wordsValues, errors: self.errors, passcodeState: passcodeState) +// } +// func withUpdatedError(_ error: InputDataValueError?, for key: InputDataIdentifier) -> WalletSplashState { +// var errors = self.errors +// if let error = error { +// errors[key] = error +// } else { +// errors.removeValue(forKey: key) +// } +// return WalletSplashState(mode: self.mode, wordsValues: self.wordsValues, errors: errors, passcodeState: self.passcodeState) +// } +//} +// +//private let _id_create_intro = InputDataIdentifier("_id_create_intro") +//private let _id_button = InputDataIdentifier("_id_button") +//private let _id_words = InputDataIdentifier("_id_words") +// +//private let _id_passcode_1 = InputDataIdentifier("_id_passcode_1") +//private let _id_passcode_2 = InputDataIdentifier("_id_passcode_2") +// +//private func _id_word(_ index:Int) -> InputDataIdentifier { +// return InputDataIdentifier("_id_word_\(index)") +//} +//@available (OSX 10.12, *) +//private func splashEntries(state: WalletSplashState, arguments: WalletSplashArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// +// var sectionId: Int32 = 0 +// var index: Int32 = 0 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("topDynamic"), equatable: InputDataEquatable(arc4random()), item: { initialSize, stableId in +// return DynamicHeightRowItem(initialSize, stableId: stableId, side: .top) +// })) +// +// +// +// let animation: LocalAnimatedSticker? +// switch state.mode { +// case .createPasscode: +// switch state.passcodeState { +// case .secure: +// animation = LocalAnimatedSticker.keychain +// case .plain: +// animation = LocalAnimatedSticker.keychain +// } +// default: +// animation = state.mode.animation +// } +// +// if animation == nil { +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// } +// +// struct WalletSplashEquatable : Equatable { +// let mode: WalletSplashMode +// let animation: LocalAnimatedSticker? +// } +// let splashEquatable = WalletSplashEquatable(mode: state.mode, animation: animation) +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_create_intro, equatable: InputDataEquatable(splashEquatable), item: { initialSize, stableId in +// return WalletSplashRowItem(initialSize, stableId: stableId, context: arguments.context, title: state.mode.title, desc: state.mode.desc, animation: animation, viewType: .modern(position: .inner, insets: NSEdgeInsets()), action: { action in +// switch action { +// case "HaventWords": +// arguments.openRestoreFailed() +// case "EnterWords": +// arguments.openImport() +// case "CreateNew": +// arguments.createNew() +// default: +// break +// } +// }) +// })) +// index += 1 +// +// switch state.mode { +// case let .save24Words(_, _, words, _): +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_words, equatable: InputDataEquatable(state.mode), item: { initialSize, stableId in +// return Wallet24WordsItem(initialSize, stableId: stableId, words: words, viewType: .singleItem, copy: arguments.copyWords) +// })) +// index += 1 +// case let .testWords(_, _, _, indexes): +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +//// for idx in indexes { +//// entries.append(.input(sectionId: sectionId, index: index, value: .none, error: nil, identifier: _id_passcode_1, mode: .plain, data: InputDataRowData(viewType: bestGeneralViewType(indexes, for: idx)), placeholder: nil, inputPlaceholder: "", filter: { $0 }, limit: 8)) +//// index += 1 +//// +//// } +// +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_words, equatable: InputDataEquatable(state.wordsValues), item: { initialSize, stableId in +// return WalletTestWordsItem(initialSize, stableId: stableId, indexes: indexes, words: state.wordsValues, viewType: .singleItem, update: arguments.updateImportWords) +// })) +// index += 1 +// case .importExist: +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_words, equatable: InputDataEquatable(state.wordsValues), item: { initialSize, stableId in +// return WalletImportWordsItem(initialSize, stableId: stableId, words: state.wordsValues, viewType: .singleItem, update: arguments.updateImportWords) +// })) +// index += 1 +// case .createPasscode: +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// let icon: CGImage +// switch state.passcodeState { +// case .secure: +// icon = theme.icons.wallet_passcode_visible +// case .plain: +// icon = theme.icons.wallet_passcode_hidden +// } +// +// entries.append(.input(sectionId: sectionId, index: index, value: state.wordsValues[_id_passcode_1] ?? .none, error: nil, identifier: _id_passcode_1, mode: state.passcodeState, data: InputDataRowData(viewType: .firstItem, rightItem: InputDataRightItem.action(icon, .custom(arguments.togglePasscodeMode)), maxBlockWidth: 280), placeholder: nil, inputPlaceholder: L10n.walletSplashCreatePasscodePlaceholder1, filter: { $0 }, limit: 255)) +// index += 1 +// +// entries.append(.input(sectionId: sectionId, index: index, value: state.wordsValues[_id_passcode_2] ?? .none, error: state.errors[_id_passcode_2], identifier: _id_passcode_2, mode: state.passcodeState, data: InputDataRowData(viewType: .lastItem, maxBlockWidth: 280), placeholder: nil, inputPlaceholder: L10n.walletSplashCreatePasscodePlaceholder2, filter: { $0 }, limit: 255)) +// index += 1 +// default: +// break +// } +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +//// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_button, equatable: InputDataEquatable(state.mode), item: { initialSize, stableId in +// return WalletSplashButtonRowItem(initialSize, stableId: stableId, buttonText: state.mode.buttonText, subButtonText: state.mode.subButtonText, viewType: .lastItem, subTextAction: { action in +// switch action { +// case "HaventWords": +// arguments.openRestoreFailed() +// case "EnterWords": +// arguments.openImport() +// case "Terms": +// arguments.openTerms() +// default: +// break +// } +// }, action: arguments.action) +// })) +// index += 1 +// +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: InputDataIdentifier("bottomDynamic"), equatable: InputDataEquatable(arc4random()), item: { initialSize, stableId in +// return DynamicHeightRowItem(initialSize, stableId: stableId, side: .bottom) +// })) +// +// return entries +//} +// +//@available(OSX 10.12, *) +//func WalletSplashController(context: AccountContext, tonContext: TonContext, mode: WalletSplashMode) -> InputDataController { +// +// let initialState = WalletSplashState(mode: mode, errors: [:], passcodeState: .secure) +// let state: ValuePromise = ValuePromise(initialState) +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletSplashState)->WalletSplashState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// var getController:(()->InputDataController?)? = nil +// +// let validateAction:(WalletSplashMode)->InputDataValidation = { mode in +// switch mode { +// case .intro, .restoreFailed: +// let signal = TONKeychain.initializePairAndSavePublic(for: context.account) +// let create = signal +// |> filter { $0 != nil } +// |> map { $0! } +// |> mapError { _ in +// return CreateWalletError.generic +// } +// |> mapToSignal { key in +// return getServerWalletSalt(network: context.account.network) +// |> mapError { _ in +// return CreateWalletError.generic +// } +// |> mapToSignal { salt in +// return createWallet(storage: tonContext.storage, tonInstance: tonContext.instance, keychain: tonContext.keychain, localPassword: salt) +// } +// |> map { data in +// return (key, data) +// } +// } +// +// _ = showModalProgress(signal: create, for: context.window).start(next: { keys, data in +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .created(keys, data.0, data.1))) +// }, error: { error in +// +// }) +// case let .createPasscode(keys, info): +// let values = stateValue.with { $0.wordsValues } +// var fails:[InputDataIdentifier: InputDataValidationFailAction] = [:] +// let passcode1 = values[_id_passcode_1]?.stringValue ?? "" +// let passcode2 = values[_id_passcode_2]?.stringValue ?? "" +// +// if passcode1.isEmpty { +// return .fail(.fields([_id_passcode_1 : .shake])) +// } +// if passcode2.isEmpty { +// return .fail(.fields([_id_passcode_2 : .shake])) +// } +// +// if passcode1 != passcode2 { +// fails[_id_passcode_1] = .shake +// fails[_id_passcode_2] = .shake +// } +// +// if fails.isEmpty { +// _ = showModalProgress(signal: TONKeychain.applyKeys(keys, account: context.account, tonInstance: tonContext.instance, password: passcode1), for: context.window).start(next: { success in +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .success(info))) +// }) +// } else { +// updateState { +// $0.withUpdatedError(InputDataValueError(description: L10n.walletSplashCreatePasscodeError, target: .data), for: _id_passcode_2) +// } +// return .fail(.fields(fails)) +// } +// +// case let .created(keys, info, words): +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .save24Words(keys, info, words, Date().timeIntervalSince1970))) +// case let .save24Words(keys, info, words, time): +// var indexes:[Int] = [] +// for _ in 0 ..< 3 { +// loop: while true { +// let index = Int(arc4random()) % (words.count - 1) + 1 +// if !indexes.contains(index) { +// indexes.append(index) +// break loop +// } +// } +// } +// if time + 30 > Date().timeIntervalSince1970 { +// confirm(for: context.window, header: L10n.walletSplashSave24WordsConfirmHeader, information: L10n.walletSplashSave24WordsConfirmText, okTitle: L10n.walletSplashSave24WordsConfirmOK, cancelTitle: "", thridTitle: L10n.walletSplashSave24WordsConfirmThrid, successHandler: { result in +// switch result { +// case .basic: +// getController?()?.show(toaster: ControllerToaster(text: L10n.walletSplashSave24WordsConfirmApoligies)) +// case .thrid: +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .testWords(keys, info, words, indexes.sorted()))) +// } +// }) +// } else { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .testWords(keys, info, words, indexes.sorted()))) +// } +// case let .testWords(keys, info, words, indexes): +// let values = stateValue.with { $0.wordsValues } +// var fails:[InputDataIdentifier: InputDataValidationFailAction] = [:] +// var instantFail:[InputDataIdentifier: InputDataValidationFailAction] = [:] +// for index in indexes { +// let value = values[_id_word(index)]?.stringValue ?? "" +// if value != words[index - 1] { +// fails[_id_word(index)] = .shake +// } +// if value.isEmpty || !walletPossibleWordList.contains(value) { +// instantFail[_id_word(index)] = .shake +// } +// } +// +// if !instantFail.isEmpty { +// return .fail(.fields([_id_words: .shakeWithData(instantFail)])) +// } +// +// if fails.isEmpty { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .createPasscode(keys, info))) +// } else { +// return .fail(.doSomething(next: { f in +// confirm(for: context.window, header: L10n.walletSplashTestWordsIncorrectHeader, information: L10n.walletSplashTestWordsIncorrectText, okTitle: L10n.walletSplashTestWordsIncorrectOK, cancelTitle: "", thridTitle: L10n.walletSplashTestWordsIncorrectThrid, successHandler: { result in +// switch result { +// case .thrid: +// context.sharedContext.bindings.rootNavigation().back() +// default: +// break +// } +// }) +// f(.fail(.fields([_id_words : .shakeWithData(fails)]))) +// })) +// } +// case .importExist: +// return .fail(.doSomething(next: { f in +// +// let values = stateValue.with { $0.wordsValues } +// var wordList:[String] = [] +// var fails:[InputDataIdentifier: InputDataValidationFailAction] = [:] +// for index in 1 ... 24 { +// let value = values[_id_word(index)]?.stringValue ?? "" +// if value.isEmpty || !walletPossibleWordList.contains(value) { +// fails[_id_word(index)] = .shake +// } else { +// wordList.append(value) +// } +// } +// if fails.isEmpty { +// let signal = TONKeychain.initializePairAndSavePublic(for: context.account) +// let create = signal +// |> filter { $0 != nil } +// |> map { $0! } +// |> mapError { _ in +// return ImportWalletError.generic +// } +// |> mapToSignal { key in +// return getServerWalletSalt(network: context.account.network) +// |> mapError { _ in +// return ImportWalletError.generic +// } +// |> mapToSignal { salt in +// return importWallet(storage: tonContext.storage, tonInstance: tonContext.instance, keychain: tonContext.keychain, wordList: wordList, localPassword: salt) +// } +// |> map { data in +// return (key, data) +// } +// } +// _ = showModalProgress(signal: create, for: context.window).start(next: { keys, info in +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .createPasscode(keys, info))) +// }, error: { error in +// switch error { +// case .generic: +// alert(for: context.window, header: L10n.walletSplashImportErrorTitle, info: L10n.walletSplashImportErrorText) +// } +// }) +// } else { +// f(.fail(.fields([_id_words : .shakeWithData(fails)]))) +// } +// +// })) +// case let .success(info): +// let signal = getCombinedWalletState(storage: tonContext.storage, subject: .wallet(info), tonInstance: tonContext.instance) +// |> filter { state in +// switch state { +// case let .cached(state): +// return state != nil +// case .updated: +// return true +// } +// } |> map { _ in +// return info +// } |> take(1) +// +// _ = showModalProgress(signal: signal, for: context.window).start(next: { info in +// context.sharedContext.bindings.rootNavigation().push(WalletInfoController(context: context, tonContext: tonContext, walletInfo: info)) +// }, error: { error in +// alert(for: context.window, info: L10n.unknownError) +// }) +// case .unavailable: +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .importExist)) +// } +// return .none +// } +// +// let arguments = WalletSplashArguments(context: context, action: { +// getController?()?.validateInputValues() +// }, copyWords: { words in +// confirm(for: context.window, header: L10n.walletSplashSave24WordsCopyHeader, information: L10n.walletSplashSave24WordsCopyText, successHandler: { _ in +// copyToClipboard(words) +// getController?()?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) +// }) +// }, openRestoreFailed: { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .restoreFailed)) +// }, openImport: { +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .importExist)) +// }, updateImportWords: { index, word in +// updateState { +// return $0.withUpdatedWordsValue(for: index, value: word) +// } +// }, togglePasscodeMode: { +// updateState { +// return $0.withUpdatedPasscodeState(passcodeState: $0.passcodeState == .secure ? .plain : .secure) +// } +// }, openTerms: { +// openFaq(context: context, dest: .walletTOS) +// }, createNew: { +// getController?()?.proccessValidation(validateAction(.intro)) +// }) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return splashEntries(state: state, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries, animated: true) +// } +// +// let controller = InputDataController(dataSignal: dataSignal, title: mode.header, validateData: { data in +// return validateAction(mode) +// }, updateDatas: { data in +// switch mode { +// case let .testWords(_, _, _, indexes): +// updateState { current in +// var current = current +// for index in indexes { +// if let value = data[_id_word(index)] { +// current = current.withUpdatedWordsValue(for: _id_word(index), value: value) +// } +// } +// return current +// } +// case .importExist: +// updateState { current in +// var current = current +// for index in 1 ... 24 { +// if let value = data[_id_word(index)] { +// current = current.withUpdatedWordsValue(for: _id_word(index), value: value) +// } +// } +// return current +// } +// case .createPasscode: +// updateState { current in +// var current = current +// current = current.withUpdatedWordsValue(for: _id_passcode_1, value: data[_id_passcode_1]!) +// current = current.withUpdatedWordsValue(for: _id_passcode_2, value: data[_id_passcode_2]!) +// current = current.withUpdatedError(nil, for: _id_passcode_1) +// current = current.withUpdatedError(nil, for: _id_passcode_2) +// return current +// } +// default: +// break +// } +// return .fail(.none) +// }, removeAfterDisappear: mode.removeAfterDisappear, hasDone: false, identifier: mode.identifier) +// +// controller.customRightButton = { controller in +// switch mode { +// case .intro: +// let barView = TextButtonBarView(controller: controller, text: L10n.walletSplashIntroImportExists, style: navigationButtonStyle, alignment: .Right) +// barView.set(handler: { _ in +// context.sharedContext.bindings.rootNavigation().push(WalletSplashController(context: context, tonContext: tonContext, mode: .importExist)) +// }, for: .Click) +// return barView +// default: +// return nil +// } +// } +// +// var first: Bool = true +// controller.afterTransaction = { controller in +// if first { +// controller.keyWindowUpdate(context.window.isKeyWindow, controller) +// first = false +// } +// } +// +// controller.keyWindowUpdate = { isKeyWindow, controller in +// switch mode { +// case .importExist: +// if isKeyWindow { +// if let parsed = parseClipboardSecureWords(), !parsed.isEmpty, parsed.count == 24 { +// let attr = NSAttributedString.initialize(string: L10n.walletSplashImportFromClipboard, color: theme.colors.link, font: .medium(.text)) +// controller.show(toaster: ControllerToaster(text: attr, action: { +// updateState { current in +// var current = current +// for (index, word) in parsed { +// current = current.withUpdatedWordsValue(for: _id_word(index), value: .string(word)) +// } +// return current +// } +// controller.tableView.scroll(to: .down(true)) +// }), for: 15, animated: true) +// } +// } +// default: +// break +// } +// } +// +// controller.ignoreRightBarHandler = true +// +// controller.hasBackSwipe = { +// switch mode { +// case .created, .save24Words: +// return false +// default: +// return true +// } +// } +// +// +// controller.backInvocation = { data, f in +// switch mode { +// case .created, .save24Words: +// confirm(for: context.window, header: L10n.walletSplashCloseConfirmHeader, information: L10n.walletSplashCloseConfirmText, okTitle: L10n.walletSplashCloseConfirmOK, cancelTitle: "", thridTitle: L10n.walletSplashCloseConfirmThrid, successHandler: { result in +// switch result { +// case .basic: +// f(false) +// case .thrid: +// f(true) +// } +// }) +// default: +// f(true) +// } +// } +// +// getController = { [weak controller] in +// return controller +// } +// return controller +//} diff --git a/Telegram-Mac/WalletSplashHelps.swift b/Telegram-Mac/WalletSplashHelps.swift new file mode 100644 index 0000000000..a886c7c1b8 --- /dev/null +++ b/Telegram-Mac/WalletSplashHelps.swift @@ -0,0 +1,2357 @@ +//// +//// WalletSplashHelps.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import Postbox +// +//@available (OSX 10.12, *) +//extension WalletSplashMode { +// +// var buttonText: String { +// switch self { +// case .intro: +// return L10n.walletSplashIntroButton +// case .created: +// return L10n.walletSplashCreatedButton +// case .save24Words: +// return L10n.walletSplashSave24WordsButton +// case .success: +// return L10n.walletSplashSuccessButton +// case .restoreFailed: +// return L10n.walletSplashRestoreFailedButton +// case .testWords: +// return L10n.walletSplashTestWordsButton +// case .importExist: +// return L10n.walletSplashImportButton +// case .createPasscode: +// return L10n.walletSplashCreatePasscodeButton +// case .unavailable: +// return L10n.walletSplashSecurityChangedButton +// } +// } +// +// var subButtonText: String? { +// switch self { +// case .intro: +// return L10n.walletSplashIntroTerms +// case .created: +// return nil +// case .save24Words: +// return nil +// case .success: +// return nil +// case .restoreFailed: +// return nil +// case .testWords: +// return nil +// case .importExist: +// return L10n.walletSplashImportButtonSub +// case .createPasscode: +// return nil +// case .unavailable: +// return nil +// } +// } +// +// var animationFile: TelegramMediaFile? { +// switch self { +// case .intro: +// return LocalAnimatedSticker.brilliant_loading.file +// case .success: +// return LocalAnimatedSticker.success.file +// case .save24Words: +// return LocalAnimatedSticker.write_words.file +// case .created: +// return LocalAnimatedSticker.gift.file +// case .restoreFailed: +// return LocalAnimatedSticker.sad.file +// case .testWords: +// return LocalAnimatedSticker.write_words.file +// case .importExist: +// return nil +// case .createPasscode: +// return LocalAnimatedSticker.keychain.file +// case .unavailable: +// return LocalAnimatedSticker.sad.file +// } +// } +// +// var animation: LocalAnimatedSticker? { +// switch self { +// case .intro: +// return LocalAnimatedSticker.brilliant_loading +// case .success: +// return LocalAnimatedSticker.success +// case .save24Words: +// return LocalAnimatedSticker.write_words +// case .created: +// return LocalAnimatedSticker.gift +// case .restoreFailed: +// return LocalAnimatedSticker.sad +// case .testWords: +// return LocalAnimatedSticker.smart_guy +// case .importExist: +// return nil +// case .createPasscode: +// return LocalAnimatedSticker.keychain +// case .unavailable: +// return LocalAnimatedSticker.sad +// } +// } +// +// var layoutParameters: ChatMediaLayoutParameters? { +// return nil +// } +// +// var title: String { +// switch self { +// case .intro: +// return L10n.walletSplashIntroTitle +// case .created: +// return L10n.walletSplashCreatedTitle +// case .success: +// return L10n.walletSplashSuccessTitle +// case .save24Words: +// return L10n.walletSplashSave24WordsTitle +// case .restoreFailed: +// return L10n.walletSplashRestoreFailedTitle +// case .testWords: +// return L10n.walletSplashTestWordsTitle +// case .importExist: +// return L10n.walletSplashImportTitle +// case .createPasscode: +// return L10n.walletSplashCreatePasscodeTitle +// case .unavailable: +// return L10n.walletSplashSecurityChangedTitle +// } +// } +// var desc: String { +// switch self { +// case .intro: +// return L10n.walletSplashIntroDesc +// case .created: +// return L10n.walletSplashCreatedDesc +// case .save24Words: +// return L10n.walletSplashSave24WordsDesc +// case .success: +// return L10n.walletSplashSuccessDesc +// case .restoreFailed: +// return L10n.walletSplashRestoreFailedDesc +// case let .testWords(_, _, _, indexes): +// return L10n.walletSplashTestWordsDesc(indexes[0], indexes[1], indexes[2]) +// case .importExist: +// return L10n.walletSplashImportDesc +// case .createPasscode: +// return L10n.walletSplashCreatePasscodeDesc +// case .unavailable: +// return L10n.walletSplashSecurityChangedDesc +// } +// } +// +// var identifier: String { +// switch self { +// case .created, .save24Words, .testWords, .success: +// return "wallet-create" +// case .createPasscode: +// return "wallet-create" +// default: +// return "wallet-splash" +// } +// } +// +// var header: String { +// switch self { +// case .intro: +// return L10n.walletSplashIntroHeader +// case .created: +// return L10n.walletSplashCreatedHeader +// case .save24Words: +// return L10n.walletSplashSave24WordsHeader +// case .success: +// return L10n.walletSplashSuccessHeader +// case .restoreFailed: +// return L10n.walletSplashRestoreFailedHeader +// case .testWords: +// return L10n.walletSplashTestWordsHeader +// case .importExist: +// return L10n.walletSplashImportHeader +// case .createPasscode: +// return L10n.walletSplashCreatePasscodeHeader +// case .unavailable: +// return L10n.walletSplashSecurityChangedHeader +// } +// } +// +// var removeAfterDisappear: Bool { +// switch self { +// case .intro: +// return true +// case .created: +// return true +// case .save24Words: +// return false +// case .testWords: +// return false +// case .success: +// return false +// case .restoreFailed: +// return true +// case .importExist: +// return true +// case .createPasscode: +// return true +// case .unavailable: +// return true +// } +// } +//} +// +// +//func parseClipboardSecureWords() -> [Int:String]? { +// if let text = NSPasteboard.general.string(forType: .string) { +// +// let numeric = CharacterSet(charactersIn: "0987654321") +// let alphabet = CharacterSet(charactersIn: "qwertyuiopasdfghjklzxcvbnm") +// +// +// var indexes:[Int] = [] +// for i in 0 ..< 24 { +// indexes.append(i + 1) +// } +// +// +// func parseList(_ words: [String]) -> [Int: String] { +// var index: Int = 1 +// var list:[Int: String] = words.reduce([:], { result, current in +// var result = result +// result[index] = current +// index += 1 +// return result +// }) +// +// main: for (baseIndex, word) in words.enumerated() { +// for c in indexes { +// let number = String(c) +// if word.hasPrefix(number + ".") || word.hasPrefix(number + ":") || word.hasPrefix(number + "-") { +// let new = String(word.suffix(word.length - (number.length + 1))) +// list[c] = new +// continue main +// } +// } +// list[baseIndex + 1] = String(word) +// } +// return list.filter { $0.key <= 24 }.filter { walletPossibleWordList.contains($0.value) } +// } +// +// +// let list = text.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter { !$0.isEmpty } +// +// var newList:[String] = Array(repeating: "", count: 24) +// +// var index:Int = 0 +// var i:Int = 0 +// main: while i < list.count { +// if index < newList.count { +// for c in indexes { +// let number = String(c) +// if list[i].hasPrefix(number), list[i] == number + "." || list[i] == number + ":" || list[i] == number + "-" { +// newList[index] = list[i] + list[i + 1] +// index += 1 +// i += 2 +// continue main +// } +// } +// newList[index] = list[i] +// index += 1 +// i += 1 +// } else { +// i += 1 +// } +// } +// return parseList(newList.filter { !$0.isEmpty }) +// } +// return nil +//} +// +//func formatBalanceText(_ value: Int64) -> String { +// var balanceText = "\(abs(value))" +// while balanceText.count < 10 { +// balanceText.insert("0", at: balanceText.startIndex) +// } +// balanceText.insert(Formatter.withSeparator.decimalSeparator.first!, at: balanceText.index(balanceText.endIndex, offsetBy: -9)) +// while true { +// if balanceText.hasSuffix("0") { +// if balanceText.hasSuffix("\(Formatter.withSeparator.decimalSeparator!)0") { +// break +// } else { +// balanceText.removeLast() +// } +// } else { +// break +// } +// } +// if value < 0 { +// balanceText.insert("-", at: balanceText.startIndex) +// } +// return balanceText +//} +// +// +// +// let walletPossibleWordList: [String] = [ +// "abandon", +// "ability", +// "able", +// "about", +// "above", +// "absent", +// "absorb", +// "abstract", +// "absurd", +// "abuse", +// "access", +// "accident", +// "account", +// "accuse", +// "achieve", +// "acid", +// "acoustic", +// "acquire", +// "across", +// "act", +// "action", +// "actor", +// "actress", +// "actual", +// "adapt", +// "add", +// "addict", +// "address", +// "adjust", +// "admit", +// "adult", +// "advance", +// "advice", +// "aerobic", +// "affair", +// "afford", +// "afraid", +// "again", +// "age", +// "agent", +// "agree", +// "ahead", +// "aim", +// "air", +// "airport", +// "aisle", +// "alarm", +// "album", +// "alcohol", +// "alert", +// "alien", +// "all", +// "alley", +// "allow", +// "almost", +// "alone", +// "alpha", +// "already", +// "also", +// "alter", +// "always", +// "amateur", +// "amazing", +// "among", +// "amount", +// "amused", +// "analyst", +// "anchor", +// "ancient", +// "anger", +// "angle", +// "angry", +// "animal", +// "ankle", +// "announce", +// "annual", +// "another", +// "answer", +// "antenna", +// "antique", +// "anxiety", +// "any", +// "apart", +// "apology", +// "appear", +// "apple", +// "approve", +// "april", +// "arch", +// "arctic", +// "area", +// "arena", +// "argue", +// "arm", +// "armed", +// "armor", +// "army", +// "around", +// "arrange", +// "arrest", +// "arrive", +// "arrow", +// "art", +// "artefact", +// "artist", +// "artwork", +// "ask", +// "aspect", +// "assault", +// "asset", +// "assist", +// "assume", +// "asthma", +// "athlete", +// "atom", +// "attack", +// "attend", +// "attitude", +// "attract", +// "auction", +// "audit", +// "august", +// "aunt", +// "author", +// "auto", +// "autumn", +// "average", +// "avocado", +// "avoid", +// "awake", +// "aware", +// "away", +// "awesome", +// "awful", +// "awkward", +// "axis", +// "baby", +// "bachelor", +// "bacon", +// "badge", +// "bag", +// "balance", +// "balcony", +// "ball", +// "bamboo", +// "banana", +// "banner", +// "bar", +// "barely", +// "bargain", +// "barrel", +// "base", +// "basic", +// "basket", +// "battle", +// "beach", +// "bean", +// "beauty", +// "because", +// "become", +// "beef", +// "before", +// "begin", +// "behave", +// "behind", +// "believe", +// "below", +// "belt", +// "bench", +// "benefit", +// "best", +// "betray", +// "better", +// "between", +// "beyond", +// "bicycle", +// "bid", +// "bike", +// "bind", +// "biology", +// "bird", +// "birth", +// "bitter", +// "black", +// "blade", +// "blame", +// "blanket", +// "blast", +// "bleak", +// "bless", +// "blind", +// "blood", +// "blossom", +// "blouse", +// "blue", +// "blur", +// "blush", +// "board", +// "boat", +// "body", +// "boil", +// "bomb", +// "bone", +// "bonus", +// "book", +// "boost", +// "border", +// "boring", +// "borrow", +// "boss", +// "bottom", +// "bounce", +// "box", +// "boy", +// "bracket", +// "brain", +// "brand", +// "brass", +// "brave", +// "bread", +// "breeze", +// "brick", +// "bridge", +// "brief", +// "bright", +// "bring", +// "brisk", +// "broccoli", +// "broken", +// "bronze", +// "broom", +// "brother", +// "brown", +// "brush", +// "bubble", +// "buddy", +// "budget", +// "buffalo", +// "build", +// "bulb", +// "bulk", +// "bullet", +// "bundle", +// "bunker", +// "burden", +// "burger", +// "burst", +// "bus", +// "business", +// "busy", +// "butter", +// "buyer", +// "buzz", +// "cabbage", +// "cabin", +// "cable", +// "cactus", +// "cage", +// "cake", +// "call", +// "calm", +// "camera", +// "camp", +// "can", +// "canal", +// "cancel", +// "candy", +// "cannon", +// "canoe", +// "canvas", +// "canyon", +// "capable", +// "capital", +// "captain", +// "car", +// "carbon", +// "card", +// "cargo", +// "carpet", +// "carry", +// "cart", +// "case", +// "cash", +// "casino", +// "castle", +// "casual", +// "cat", +// "catalog", +// "catch", +// "category", +// "cattle", +// "caught", +// "cause", +// "caution", +// "cave", +// "ceiling", +// "celery", +// "cement", +// "census", +// "century", +// "cereal", +// "certain", +// "chair", +// "chalk", +// "champion", +// "change", +// "chaos", +// "chapter", +// "charge", +// "chase", +// "chat", +// "cheap", +// "check", +// "cheese", +// "chef", +// "cherry", +// "chest", +// "chicken", +// "chief", +// "child", +// "chimney", +// "choice", +// "choose", +// "chronic", +// "chuckle", +// "chunk", +// "churn", +// "cigar", +// "cinnamon", +// "circle", +// "citizen", +// "city", +// "civil", +// "claim", +// "clap", +// "clarify", +// "claw", +// "clay", +// "clean", +// "clerk", +// "clever", +// "click", +// "client", +// "cliff", +// "climb", +// "clinic", +// "clip", +// "clock", +// "clog", +// "close", +// "cloth", +// "cloud", +// "clown", +// "club", +// "clump", +// "cluster", +// "clutch", +// "coach", +// "coast", +// "coconut", +// "code", +// "coffee", +// "coil", +// "coin", +// "collect", +// "color", +// "column", +// "combine", +// "come", +// "comfort", +// "comic", +// "common", +// "company", +// "concert", +// "conduct", +// "confirm", +// "congress", +// "connect", +// "consider", +// "control", +// "convince", +// "cook", +// "cool", +// "copper", +// "copy", +// "coral", +// "core", +// "corn", +// "correct", +// "cost", +// "cotton", +// "couch", +// "country", +// "couple", +// "course", +// "cousin", +// "cover", +// "coyote", +// "crack", +// "cradle", +// "craft", +// "cram", +// "crane", +// "crash", +// "crater", +// "crawl", +// "crazy", +// "cream", +// "credit", +// "creek", +// "crew", +// "cricket", +// "crime", +// "crisp", +// "critic", +// "crop", +// "cross", +// "crouch", +// "crowd", +// "crucial", +// "cruel", +// "cruise", +// "crumble", +// "crunch", +// "crush", +// "cry", +// "crystal", +// "cube", +// "culture", +// "cup", +// "cupboard", +// "curious", +// "current", +// "curtain", +// "curve", +// "cushion", +// "custom", +// "cute", +// "cycle", +// "dad", +// "damage", +// "damp", +// "dance", +// "danger", +// "daring", +// "dash", +// "daughter", +// "dawn", +// "day", +// "deal", +// "debate", +// "debris", +// "decade", +// "december", +// "decide", +// "decline", +// "decorate", +// "decrease", +// "deer", +// "defense", +// "define", +// "defy", +// "degree", +// "delay", +// "deliver", +// "demand", +// "demise", +// "denial", +// "dentist", +// "deny", +// "depart", +// "depend", +// "deposit", +// "depth", +// "deputy", +// "derive", +// "describe", +// "desert", +// "design", +// "desk", +// "despair", +// "destroy", +// "detail", +// "detect", +// "develop", +// "device", +// "devote", +// "diagram", +// "dial", +// "diamond", +// "diary", +// "dice", +// "diesel", +// "diet", +// "differ", +// "digital", +// "dignity", +// "dilemma", +// "dinner", +// "dinosaur", +// "direct", +// "dirt", +// "disagree", +// "discover", +// "disease", +// "dish", +// "dismiss", +// "disorder", +// "display", +// "distance", +// "divert", +// "divide", +// "divorce", +// "dizzy", +// "doctor", +// "document", +// "dog", +// "doll", +// "dolphin", +// "domain", +// "donate", +// "donkey", +// "donor", +// "door", +// "dose", +// "double", +// "dove", +// "draft", +// "dragon", +// "drama", +// "drastic", +// "draw", +// "dream", +// "dress", +// "drift", +// "drill", +// "drink", +// "drip", +// "drive", +// "drop", +// "drum", +// "dry", +// "duck", +// "dumb", +// "dune", +// "during", +// "dust", +// "dutch", +// "duty", +// "dwarf", +// "dynamic", +// "eager", +// "eagle", +// "early", +// "earn", +// "earth", +// "easily", +// "east", +// "easy", +// "echo", +// "ecology", +// "economy", +// "edge", +// "edit", +// "educate", +// "effort", +// "egg", +// "eight", +// "either", +// "elbow", +// "elder", +// "electric", +// "elegant", +// "element", +// "elephant", +// "elevator", +// "elite", +// "else", +// "embark", +// "embody", +// "embrace", +// "emerge", +// "emotion", +// "employ", +// "empower", +// "empty", +// "enable", +// "enact", +// "end", +// "endless", +// "endorse", +// "enemy", +// "energy", +// "enforce", +// "engage", +// "engine", +// "enhance", +// "enjoy", +// "enlist", +// "enough", +// "enrich", +// "enroll", +// "ensure", +// "enter", +// "entire", +// "entry", +// "envelope", +// "episode", +// "equal", +// "equip", +// "era", +// "erase", +// "erode", +// "erosion", +// "error", +// "erupt", +// "escape", +// "essay", +// "essence", +// "estate", +// "eternal", +// "ethics", +// "evidence", +// "evil", +// "evoke", +// "evolve", +// "exact", +// "example", +// "excess", +// "exchange", +// "excite", +// "exclude", +// "excuse", +// "execute", +// "exercise", +// "exhaust", +// "exhibit", +// "exile", +// "exist", +// "exit", +// "exotic", +// "expand", +// "expect", +// "expire", +// "explain", +// "expose", +// "express", +// "extend", +// "extra", +// "eye", +// "eyebrow", +// "fabric", +// "face", +// "faculty", +// "fade", +// "faint", +// "faith", +// "fall", +// "false", +// "fame", +// "family", +// "famous", +// "fan", +// "fancy", +// "fantasy", +// "farm", +// "fashion", +// "fat", +// "fatal", +// "father", +// "fatigue", +// "fault", +// "favorite", +// "feature", +// "february", +// "federal", +// "fee", +// "feed", +// "feel", +// "female", +// "fence", +// "festival", +// "fetch", +// "fever", +// "few", +// "fiber", +// "fiction", +// "field", +// "figure", +// "file", +// "film", +// "filter", +// "final", +// "find", +// "fine", +// "finger", +// "finish", +// "fire", +// "firm", +// "first", +// "fiscal", +// "fish", +// "fit", +// "fitness", +// "fix", +// "flag", +// "flame", +// "flash", +// "flat", +// "flavor", +// "flee", +// "flight", +// "flip", +// "float", +// "flock", +// "floor", +// "flower", +// "fluid", +// "flush", +// "fly", +// "foam", +// "focus", +// "fog", +// "foil", +// "fold", +// "follow", +// "food", +// "foot", +// "force", +// "forest", +// "forget", +// "fork", +// "fortune", +// "forum", +// "forward", +// "fossil", +// "foster", +// "found", +// "fox", +// "fragile", +// "frame", +// "frequent", +// "fresh", +// "friend", +// "fringe", +// "frog", +// "front", +// "frost", +// "frown", +// "frozen", +// "fruit", +// "fuel", +// "fun", +// "funny", +// "furnace", +// "fury", +// "future", +// "gadget", +// "gain", +// "galaxy", +// "gallery", +// "game", +// "gap", +// "garage", +// "garbage", +// "garden", +// "garlic", +// "garment", +// "gas", +// "gasp", +// "gate", +// "gather", +// "gauge", +// "gaze", +// "general", +// "genius", +// "genre", +// "gentle", +// "genuine", +// "gesture", +// "ghost", +// "giant", +// "gift", +// "giggle", +// "ginger", +// "giraffe", +// "girl", +// "give", +// "glad", +// "glance", +// "glare", +// "glass", +// "glide", +// "glimpse", +// "globe", +// "gloom", +// "glory", +// "glove", +// "glow", +// "glue", +// "goat", +// "goddess", +// "gold", +// "good", +// "goose", +// "gorilla", +// "gospel", +// "gossip", +// "govern", +// "gown", +// "grab", +// "grace", +// "grain", +// "grant", +// "grape", +// "grass", +// "gravity", +// "great", +// "green", +// "grid", +// "grief", +// "grit", +// "grocery", +// "group", +// "grow", +// "grunt", +// "guard", +// "guess", +// "guide", +// "guilt", +// "guitar", +// "gun", +// "gym", +// "habit", +// "hair", +// "half", +// "hammer", +// "hamster", +// "hand", +// "happy", +// "harbor", +// "hard", +// "harsh", +// "harvest", +// "hat", +// "have", +// "hawk", +// "hazard", +// "head", +// "health", +// "heart", +// "heavy", +// "hedgehog", +// "height", +// "hello", +// "helmet", +// "help", +// "hen", +// "hero", +// "hidden", +// "high", +// "hill", +// "hint", +// "hip", +// "hire", +// "history", +// "hobby", +// "hockey", +// "hold", +// "hole", +// "holiday", +// "hollow", +// "home", +// "honey", +// "hood", +// "hope", +// "horn", +// "horror", +// "horse", +// "hospital", +// "host", +// "hotel", +// "hour", +// "hover", +// "hub", +// "huge", +// "human", +// "humble", +// "humor", +// "hundred", +// "hungry", +// "hunt", +// "hurdle", +// "hurry", +// "hurt", +// "husband", +// "hybrid", +// "ice", +// "icon", +// "idea", +// "identify", +// "idle", +// "ignore", +// "ill", +// "illegal", +// "illness", +// "image", +// "imitate", +// "immense", +// "immune", +// "impact", +// "impose", +// "improve", +// "impulse", +// "inch", +// "include", +// "income", +// "increase", +// "index", +// "indicate", +// "indoor", +// "industry", +// "infant", +// "inflict", +// "inform", +// "inhale", +// "inherit", +// "initial", +// "inject", +// "injury", +// "inmate", +// "inner", +// "innocent", +// "input", +// "inquiry", +// "insane", +// "insect", +// "inside", +// "inspire", +// "install", +// "intact", +// "interest", +// "into", +// "invest", +// "invite", +// "involve", +// "iron", +// "island", +// "isolate", +// "issue", +// "item", +// "ivory", +// "jacket", +// "jaguar", +// "jar", +// "jazz", +// "jealous", +// "jeans", +// "jelly", +// "jewel", +// "job", +// "join", +// "joke", +// "journey", +// "joy", +// "judge", +// "juice", +// "jump", +// "jungle", +// "junior", +// "junk", +// "just", +// "kangaroo", +// "keen", +// "keep", +// "ketchup", +// "key", +// "kick", +// "kid", +// "kidney", +// "kind", +// "kingdom", +// "kiss", +// "kit", +// "kitchen", +// "kite", +// "kitten", +// "kiwi", +// "knee", +// "knife", +// "knock", +// "know", +// "lab", +// "label", +// "labor", +// "ladder", +// "lady", +// "lake", +// "lamp", +// "language", +// "laptop", +// "large", +// "later", +// "latin", +// "laugh", +// "laundry", +// "lava", +// "law", +// "lawn", +// "lawsuit", +// "layer", +// "lazy", +// "leader", +// "leaf", +// "learn", +// "leave", +// "lecture", +// "left", +// "leg", +// "legal", +// "legend", +// "leisure", +// "lemon", +// "lend", +// "length", +// "lens", +// "leopard", +// "lesson", +// "letter", +// "level", +// "liar", +// "liberty", +// "library", +// "license", +// "life", +// "lift", +// "light", +// "like", +// "limb", +// "limit", +// "link", +// "lion", +// "liquid", +// "list", +// "little", +// "live", +// "lizard", +// "load", +// "loan", +// "lobster", +// "local", +// "lock", +// "logic", +// "lonely", +// "long", +// "loop", +// "lottery", +// "loud", +// "lounge", +// "love", +// "loyal", +// "lucky", +// "luggage", +// "lumber", +// "lunar", +// "lunch", +// "luxury", +// "lyrics", +// "machine", +// "mad", +// "magic", +// "magnet", +// "maid", +// "mail", +// "main", +// "major", +// "make", +// "mammal", +// "man", +// "manage", +// "mandate", +// "mango", +// "mansion", +// "manual", +// "maple", +// "marble", +// "march", +// "margin", +// "marine", +// "market", +// "marriage", +// "mask", +// "mass", +// "master", +// "match", +// "material", +// "math", +// "matrix", +// "matter", +// "maximum", +// "maze", +// "meadow", +// "mean", +// "measure", +// "meat", +// "mechanic", +// "medal", +// "media", +// "melody", +// "melt", +// "member", +// "memory", +// "mention", +// "menu", +// "mercy", +// "merge", +// "merit", +// "merry", +// "mesh", +// "message", +// "metal", +// "method", +// "middle", +// "midnight", +// "milk", +// "million", +// "mimic", +// "mind", +// "minimum", +// "minor", +// "minute", +// "miracle", +// "mirror", +// "misery", +// "miss", +// "mistake", +// "mix", +// "mixed", +// "mixture", +// "mobile", +// "model", +// "modify", +// "mom", +// "moment", +// "monitor", +// "monkey", +// "monster", +// "month", +// "moon", +// "moral", +// "more", +// "morning", +// "mosquito", +// "mother", +// "motion", +// "motor", +// "mountain", +// "mouse", +// "move", +// "movie", +// "much", +// "muffin", +// "mule", +// "multiply", +// "muscle", +// "museum", +// "mushroom", +// "music", +// "must", +// "mutual", +// "myself", +// "mystery", +// "myth", +// "naive", +// "name", +// "napkin", +// "narrow", +// "nasty", +// "nation", +// "nature", +// "near", +// "neck", +// "need", +// "negative", +// "neglect", +// "neither", +// "nephew", +// "nerve", +// "nest", +// "net", +// "network", +// "neutral", +// "never", +// "news", +// "next", +// "nice", +// "night", +// "noble", +// "noise", +// "nominee", +// "noodle", +// "normal", +// "north", +// "nose", +// "notable", +// "note", +// "nothing", +// "notice", +// "novel", +// "now", +// "nuclear", +// "number", +// "nurse", +// "nut", +// "oak", +// "obey", +// "object", +// "oblige", +// "obscure", +// "observe", +// "obtain", +// "obvious", +// "occur", +// "ocean", +// "october", +// "odor", +// "off", +// "offer", +// "office", +// "often", +// "oil", +// "okay", +// "old", +// "olive", +// "olympic", +// "omit", +// "once", +// "one", +// "onion", +// "online", +// "only", +// "open", +// "opera", +// "opinion", +// "oppose", +// "option", +// "orange", +// "orbit", +// "orchard", +// "order", +// "ordinary", +// "organ", +// "orient", +// "original", +// "orphan", +// "ostrich", +// "other", +// "outdoor", +// "outer", +// "output", +// "outside", +// "oval", +// "oven", +// "over", +// "own", +// "owner", +// "oxygen", +// "oyster", +// "ozone", +// "pact", +// "paddle", +// "page", +// "pair", +// "palace", +// "palm", +// "panda", +// "panel", +// "panic", +// "panther", +// "paper", +// "parade", +// "parent", +// "park", +// "parrot", +// "party", +// "pass", +// "patch", +// "path", +// "patient", +// "patrol", +// "pattern", +// "pause", +// "pave", +// "payment", +// "peace", +// "peanut", +// "pear", +// "peasant", +// "pelican", +// "pen", +// "penalty", +// "pencil", +// "people", +// "pepper", +// "perfect", +// "permit", +// "person", +// "pet", +// "phone", +// "photo", +// "phrase", +// "physical", +// "piano", +// "picnic", +// "picture", +// "piece", +// "pig", +// "pigeon", +// "pill", +// "pilot", +// "pink", +// "pioneer", +// "pipe", +// "pistol", +// "pitch", +// "pizza", +// "place", +// "planet", +// "plastic", +// "plate", +// "play", +// "please", +// "pledge", +// "pluck", +// "plug", +// "plunge", +// "poem", +// "poet", +// "point", +// "polar", +// "pole", +// "police", +// "pond", +// "pony", +// "pool", +// "popular", +// "portion", +// "position", +// "possible", +// "post", +// "potato", +// "pottery", +// "poverty", +// "powder", +// "power", +// "practice", +// "praise", +// "predict", +// "prefer", +// "prepare", +// "present", +// "pretty", +// "prevent", +// "price", +// "pride", +// "primary", +// "print", +// "priority", +// "prison", +// "private", +// "prize", +// "problem", +// "process", +// "produce", +// "profit", +// "program", +// "project", +// "promote", +// "proof", +// "property", +// "prosper", +// "protect", +// "proud", +// "provide", +// "public", +// "pudding", +// "pull", +// "pulp", +// "pulse", +// "pumpkin", +// "punch", +// "pupil", +// "puppy", +// "purchase", +// "purity", +// "purpose", +// "purse", +// "push", +// "put", +// "puzzle", +// "pyramid", +// "quality", +// "quantum", +// "quarter", +// "question", +// "quick", +// "quit", +// "quiz", +// "quote", +// "rabbit", +// "raccoon", +// "race", +// "rack", +// "radar", +// "radio", +// "rail", +// "rain", +// "raise", +// "rally", +// "ramp", +// "ranch", +// "random", +// "range", +// "rapid", +// "rare", +// "rate", +// "rather", +// "raven", +// "raw", +// "razor", +// "ready", +// "real", +// "reason", +// "rebel", +// "rebuild", +// "recall", +// "receive", +// "recipe", +// "record", +// "recycle", +// "reduce", +// "reflect", +// "reform", +// "refuse", +// "region", +// "regret", +// "regular", +// "reject", +// "relax", +// "release", +// "relief", +// "rely", +// "remain", +// "remember", +// "remind", +// "remove", +// "render", +// "renew", +// "rent", +// "reopen", +// "repair", +// "repeat", +// "replace", +// "report", +// "require", +// "rescue", +// "resemble", +// "resist", +// "resource", +// "response", +// "result", +// "retire", +// "retreat", +// "return", +// "reunion", +// "reveal", +// "review", +// "reward", +// "rhythm", +// "rib", +// "ribbon", +// "rice", +// "rich", +// "ride", +// "ridge", +// "rifle", +// "right", +// "rigid", +// "ring", +// "riot", +// "ripple", +// "risk", +// "ritual", +// "rival", +// "river", +// "road", +// "roast", +// "robot", +// "robust", +// "rocket", +// "romance", +// "roof", +// "rookie", +// "room", +// "rose", +// "rotate", +// "rough", +// "round", +// "route", +// "royal", +// "rubber", +// "rude", +// "rug", +// "rule", +// "run", +// "runway", +// "rural", +// "sad", +// "saddle", +// "sadness", +// "safe", +// "sail", +// "salad", +// "salmon", +// "salon", +// "salt", +// "salute", +// "same", +// "sample", +// "sand", +// "satisfy", +// "satoshi", +// "sauce", +// "sausage", +// "save", +// "say", +// "scale", +// "scan", +// "scare", +// "scatter", +// "scene", +// "scheme", +// "school", +// "science", +// "scissors", +// "scorpion", +// "scout", +// "scrap", +// "screen", +// "script", +// "scrub", +// "sea", +// "search", +// "season", +// "seat", +// "second", +// "secret", +// "section", +// "security", +// "seed", +// "seek", +// "segment", +// "select", +// "sell", +// "seminar", +// "senior", +// "sense", +// "sentence", +// "series", +// "service", +// "session", +// "settle", +// "setup", +// "seven", +// "shadow", +// "shaft", +// "shallow", +// "share", +// "shed", +// "shell", +// "sheriff", +// "shield", +// "shift", +// "shine", +// "ship", +// "shiver", +// "shock", +// "shoe", +// "shoot", +// "shop", +// "short", +// "shoulder", +// "shove", +// "shrimp", +// "shrug", +// "shuffle", +// "shy", +// "sibling", +// "sick", +// "side", +// "siege", +// "sight", +// "sign", +// "silent", +// "silk", +// "silly", +// "silver", +// "similar", +// "simple", +// "since", +// "sing", +// "siren", +// "sister", +// "situate", +// "six", +// "size", +// "skate", +// "sketch", +// "ski", +// "skill", +// "skin", +// "skirt", +// "skull", +// "slab", +// "slam", +// "sleep", +// "slender", +// "slice", +// "slide", +// "slight", +// "slim", +// "slogan", +// "slot", +// "slow", +// "slush", +// "small", +// "smart", +// "smile", +// "smoke", +// "smooth", +// "snack", +// "snake", +// "snap", +// "sniff", +// "snow", +// "soap", +// "soccer", +// "social", +// "sock", +// "soda", +// "soft", +// "solar", +// "soldier", +// "solid", +// "solution", +// "solve", +// "someone", +// "song", +// "soon", +// "sorry", +// "sort", +// "soul", +// "sound", +// "soup", +// "source", +// "south", +// "space", +// "spare", +// "spatial", +// "spawn", +// "speak", +// "special", +// "speed", +// "spell", +// "spend", +// "sphere", +// "spice", +// "spider", +// "spike", +// "spin", +// "spirit", +// "split", +// "spoil", +// "sponsor", +// "spoon", +// "sport", +// "spot", +// "spray", +// "spread", +// "spring", +// "spy", +// "square", +// "squeeze", +// "squirrel", +// "stable", +// "stadium", +// "staff", +// "stage", +// "stairs", +// "stamp", +// "stand", +// "start", +// "state", +// "stay", +// "steak", +// "steel", +// "stem", +// "step", +// "stereo", +// "stick", +// "still", +// "sting", +// "stock", +// "stomach", +// "stone", +// "stool", +// "story", +// "stove", +// "strategy", +// "street", +// "strike", +// "strong", +// "struggle", +// "student", +// "stuff", +// "stumble", +// "style", +// "subject", +// "submit", +// "subway", +// "success", +// "such", +// "sudden", +// "suffer", +// "sugar", +// "suggest", +// "suit", +// "summer", +// "sun", +// "sunny", +// "sunset", +// "super", +// "supply", +// "supreme", +// "sure", +// "surface", +// "surge", +// "surprise", +// "surround", +// "survey", +// "suspect", +// "sustain", +// "swallow", +// "swamp", +// "swap", +// "swarm", +// "swear", +// "sweet", +// "swift", +// "swim", +// "swing", +// "switch", +// "sword", +// "symbol", +// "symptom", +// "syrup", +// "system", +// "table", +// "tackle", +// "tag", +// "tail", +// "talent", +// "talk", +// "tank", +// "tape", +// "target", +// "task", +// "taste", +// "tattoo", +// "taxi", +// "teach", +// "team", +// "tell", +// "ten", +// "tenant", +// "tennis", +// "tent", +// "term", +// "test", +// "text", +// "thank", +// "that", +// "theme", +// "then", +// "theory", +// "there", +// "they", +// "thing", +// "this", +// "thought", +// "three", +// "thrive", +// "throw", +// "thumb", +// "thunder", +// "ticket", +// "tide", +// "tiger", +// "tilt", +// "timber", +// "time", +// "tiny", +// "tip", +// "tired", +// "tissue", +// "title", +// "toast", +// "tobacco", +// "today", +// "toddler", +// "toe", +// "together", +// "toilet", +// "token", +// "tomato", +// "tomorrow", +// "tone", +// "tongue", +// "tonight", +// "tool", +// "tooth", +// "top", +// "topic", +// "topple", +// "torch", +// "tornado", +// "tortoise", +// "toss", +// "total", +// "tourist", +// "toward", +// "tower", +// "town", +// "toy", +// "track", +// "trade", +// "traffic", +// "tragic", +// "train", +// "transfer", +// "trap", +// "trash", +// "travel", +// "tray", +// "treat", +// "tree", +// "trend", +// "trial", +// "tribe", +// "trick", +// "trigger", +// "trim", +// "trip", +// "trophy", +// "trouble", +// "truck", +// "true", +// "truly", +// "trumpet", +// "trust", +// "truth", +// "try", +// "tube", +// "tuition", +// "tumble", +// "tuna", +// "tunnel", +// "turkey", +// "turn", +// "turtle", +// "twelve", +// "twenty", +// "twice", +// "twin", +// "twist", +// "two", +// "type", +// "typical", +// "ugly", +// "umbrella", +// "unable", +// "unaware", +// "uncle", +// "uncover", +// "under", +// "undo", +// "unfair", +// "unfold", +// "unhappy", +// "uniform", +// "unique", +// "unit", +// "universe", +// "unknown", +// "unlock", +// "until", +// "unusual", +// "unveil", +// "update", +// "upgrade", +// "uphold", +// "upon", +// "upper", +// "upset", +// "urban", +// "urge", +// "usage", +// "use", +// "used", +// "useful", +// "useless", +// "usual", +// "utility", +// "vacant", +// "vacuum", +// "vague", +// "valid", +// "valley", +// "valve", +// "van", +// "vanish", +// "vapor", +// "various", +// "vast", +// "vault", +// "vehicle", +// "velvet", +// "vendor", +// "venture", +// "venue", +// "verb", +// "verify", +// "version", +// "very", +// "vessel", +// "veteran", +// "viable", +// "vibrant", +// "vicious", +// "victory", +// "video", +// "view", +// "village", +// "vintage", +// "violin", +// "virtual", +// "virus", +// "visa", +// "visit", +// "visual", +// "vital", +// "vivid", +// "vocal", +// "voice", +// "void", +// "volcano", +// "volume", +// "vote", +// "voyage", +// "wage", +// "wagon", +// "wait", +// "walk", +// "wall", +// "walnut", +// "want", +// "warfare", +// "warm", +// "warrior", +// "wash", +// "wasp", +// "waste", +// "water", +// "wave", +// "way", +// "wealth", +// "weapon", +// "wear", +// "weasel", +// "weather", +// "web", +// "wedding", +// "weekend", +// "weird", +// "welcome", +// "west", +// "wet", +// "whale", +// "what", +// "wheat", +// "wheel", +// "when", +// "where", +// "whip", +// "whisper", +// "wide", +// "width", +// "wife", +// "wild", +// "will", +// "win", +// "window", +// "wine", +// "wing", +// "wink", +// "winner", +// "winter", +// "wire", +// "wisdom", +// "wise", +// "wish", +// "witness", +// "wolf", +// "woman", +// "wonder", +// "wood", +// "wool", +// "word", +// "work", +// "world", +// "worry", +// "worth", +// "wrap", +// "wreck", +// "wrestle", +// "wrist", +// "write", +// "wrong", +// "yard", +// "year", +// "yellow", +// "you", +// "young", +// "youth", +// "zebra", +// "zero", +// "zone", +// "zoo" +//] diff --git a/Telegram-Mac/WalletSplashRowItem.swift b/Telegram-Mac/WalletSplashRowItem.swift new file mode 100644 index 0000000000..c443058087 --- /dev/null +++ b/Telegram-Mac/WalletSplashRowItem.swift @@ -0,0 +1,139 @@ +//// +//// WalletSplashRowItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 19/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//import TelegramCore +//import SyncCore +// +// +//@available (OSX 10.12, *) +//class WalletSplashRowItem: GeneralRowItem { +// fileprivate let descLayout: TextViewLayout +// fileprivate let titleLayout: TextViewLayout +// fileprivate let animation: LocalAnimatedSticker? +// fileprivate let context: AccountContext +// private var h: CGFloat = 0 +// init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, title: String, desc: String, animation: LocalAnimatedSticker?, viewType: GeneralViewType, action:@escaping(String)->Void) { +// self.context = context +// self.animation = animation +// +// let attributedText: NSMutableAttributedString = parseMarkdownIntoAttributedString(desc, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.listGrayText), bold: MarkdownAttributeSet(font: .medium(.text), textColor: theme.colors.listGrayText), link: MarkdownAttributeSet(font: .normal(.text), textColor: theme.colors.link), linkAttribute: { contents in +// return (NSAttributedString.Key.link.rawValue, inAppLink.callback(contents, action)) +// })).mutableCopy() as! NSMutableAttributedString +// +// attributedText.detectBoldColorInString(with: .medium(.text)) +// +// self.descLayout = TextViewLayout(attributedText, alignment: .center) +// +// self.titleLayout = TextViewLayout(.initialize(string: title, color: theme.colors.text, font: .medium(22)), alignment: .center) +// +// +// self.descLayout.interactions = globalLinkExecutor +// +// super.init(initialSize, stableId: stableId, viewType: viewType) +// _ = makeSize(initialSize.width, oldWidth: 0) +// } +// +// override var height: CGFloat { +// return self.h +// } +// +// override func makeSize(_ width: CGFloat, oldWidth: CGFloat = 0) -> Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// +// self.descLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +// self.titleLayout.measure(width: self.blockWidth - self.viewType.innerInset.left - self.viewType.innerInset.right) +//// +// var height: CGFloat = self.descLayout.layoutSize.height + self.titleLayout.layoutSize.height + self.viewType.innerInset.top +// +// if let _ = self.animation { +// height += (20 + 140) +// } +// self.h = height +// +// return true +// } +// +// override func viewClass() -> AnyClass { +// return WalletIntroRowView.self +// } +//} +// +// +//@available (OSX 10.12, *) +//private final class WalletIntroRowView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let titleView = TextView() +// private let descView = TextView() +// private let animationView: MediaAnimatedStickerView = MediaAnimatedStickerView(frame: NSZeroRect) +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// containerView.addSubview(animationView) +// containerView.addSubview(titleView) +// containerView.addSubview(descView) +// titleView.isSelectable = false +// descView.isSelectable = false +// addSubview(containerView) +// } +// +// override var backdorColor: NSColor { +// return theme.colors.listBackground +// } +// +// override func updateColors() { +// guard let item = item as? WalletSplashRowItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.titleView.background = backdorColor +// self.descView.background = backdorColor +// self.containerView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletSplashRowItem else { +// return +// } +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// animationView.centerX(y: 0) +// if item.animation != nil { +// titleView.centerX(y: animationView.frame.maxY + 20) +// } else { +// titleView.centerX(y: 0) +// } +// descView.centerX(y: titleView.frame.maxY + item.viewType.innerInset.top) +// +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletSplashRowItem else { +// return +// } +// titleView.update(item.titleLayout) +// descView.update(item.descLayout) +// if let animation = item.animation?.file { +// animationView.update(with: animation, size: NSMakeSize(140, 140), context: item.context, parent: nil, table: item.table, parameters: item.animation?.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: !animated) +// } +// +// needsLayout = true +// } +// +// override var firstResponder: NSResponder? { +// return nil +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/Telegram-Mac/WalletTestWordsItem.swift b/Telegram-Mac/WalletTestWordsItem.swift new file mode 100644 index 0000000000..dce0945acc --- /dev/null +++ b/Telegram-Mac/WalletTestWordsItem.swift @@ -0,0 +1,173 @@ +//// +//// WalletTestWordsItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 23/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +//private func _id_word(_ index:Int) -> InputDataIdentifier { +// return InputDataIdentifier("_id_word_\(index)") +//} +//class WalletTestWordsItem: GeneralRowItem { +// fileprivate let items:[InputDataRowItem] +// fileprivate let indexes:[Int] +// init(_ initialSize: NSSize, stableId: AnyHashable, indexes:[Int], words:[InputDataIdentifier: InputDataValue], viewType: GeneralViewType, update:@escaping(InputDataIdentifier, InputDataValue)->Void) { +// self.indexes = indexes +// var items: [InputDataRowItem] = [] +// let insets = NSEdgeInsets(left: 5, right: 0, top: 12, bottom: 12) +// for index in indexes { +// let item = InputDataRowItem(NSMakeSize(240, 40), stableId: _id_word(index), mode: .plain, error: nil, viewType: .modern(position: bestGeneralViewType(indexes, for: index).position, insets: insets), currentText: words[_id_word(index)]?.stringValue ?? "", placeholder: nil, inputPlaceholder: "", defaultText: nil, rightItem: nil, insets: NSEdgeInsets(), filter: { $0 }, updated: { text in +// update(_id_word(index), .string(text)) +// }, pasteFilter: nil, limit: 8) +// items.append(item) +// } +// self.items = items +// +// super.init(initialSize, height: 40 * 3, stableId: stableId, type: .none, viewType: viewType) +// } +// +// override var blockWidth: CGFloat { +// return 280 +// } +// +// override var instantlyResize: Bool { +// return false +// } +// +// +// override func viewClass() -> AnyClass { +// return WalletTestWordsView.self +// } +//} +// +//private final class WalletTestWordsView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let holderViews:[TextView] +// +// private let views:[InputDataRowView] +// private let viewsContainer = View() +// required init(frame frameRect: NSRect) { +// var views:[InputDataRowView] = [] +// var holdersView: [TextView] = [] +// +// for _ in 0 ..< 3 { +// views.append(InputDataRowView(frame: NSZeroRect)) +// holdersView.append(TextView()) +// } +// self.views = views +// self.holderViews = holdersView +// super.init(frame: frameRect) +// +// for view in views { +// viewsContainer.addSubview(view) +// } +// for view in holderViews { +// viewsContainer.addSubview(view) +// } +// containerView.addSubview(viewsContainer) +// addSubview(containerView) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func layout() { +// super.layout() +// +// guard let item = item as? WalletTestWordsItem else { +// return +// } +// +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// viewsContainer.setFrameSize(NSMakeSize(item.blockWidth - item.viewType.innerInset.left - item.viewType.innerInset.right, self.containerView.frame.height)) +// viewsContainer.center() +// +// let x: CGFloat = item.viewType.innerInset.left +// var y: CGFloat = 0 +// +// for (i, view) in views.enumerated() { +// view.setFrameOrigin(NSMakePoint(x, y)) +// holderViews[i].setFrameOrigin(NSMakePoint(x - holderViews[i].frame.width, y + floorToScreenPixels(backingScaleFactor, (view.frame.height - holderViews[i].frame.height) / 2))) +// y += view.frame.height +// } +// } +// +// override var backdorColor: NSColor { +// return theme.colors.background +// } +// +// override func updateColors() { +// guard let item = item as? WalletTestWordsItem else { +// return +// } +// for view in views { +// view.updateColors() +// } +// +// for (i, index) in item.indexes.enumerated() { +// let layout = TextViewLayout(.initialize(string: "\(index):", color: theme.colors.grayText, font: .normal(.text))) +// layout.measure(width: .greatestFiniteMagnitude) +// holderViews[i].update(layout) +// } +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletTestWordsItem else { +// return +// } +// +// for (i, view) in views.enumerated() { +// view.setFrameSize(NSMakeSize(item.items[i].blockWidth, item.items[i].height)) +// view.set(item: item.items[i], animated: animated) +// } +// +// needsLayout = true +// } +// +// override func shakeViewWithData(_ data: Any) { +// super.shakeViewWithData(data) +// if let data = data as? [InputDataIdentifier : InputDataValidationFailAction] { +// for (key, _) in data { +// let view = views.first(where: { +// $0.item?.stableId.base as? InputDataIdentifier == key +// }) +// view?.shakeView() +// } +// } +// } +// +// override var firstResponder: NSResponder? { +// for view in views { +// if view.textView.inputView == window?.firstResponder { +// return view.textView.inputView +// } +// } +// return views.first?.textView.inputView +// } +// +// override func hasFirstResponder() -> Bool { +// return true +// } +// +// override func nextResponder() -> NSResponder? { +// for (i, view) in views.enumerated() { +// if view.textView.inputView == window?.firstResponder { +// if view != views.last { +// return views[i + 1].textView.inputView +// } +// } +// } +// return views.first?.textView.inputView +// } +//} +// diff --git a/Telegram-Mac/WalletTransactionDateStickItem.swift b/Telegram-Mac/WalletTransactionDateStickItem.swift new file mode 100644 index 0000000000..cf20f65f2d --- /dev/null +++ b/Telegram-Mac/WalletTransactionDateStickItem.swift @@ -0,0 +1,146 @@ +//// +//// WalletTransactionDateStickItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 25/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +// +// +//class WalletTransactionDateStickItem: TableStickItem { +// +// fileprivate let timestamp:Int32 +// let layout:TextViewLayout +// let viewType: GeneralViewType +// let inset: NSEdgeInsets +// init(_ initialSize:NSSize, timestamp: Int32, viewType: GeneralViewType) { +// self.timestamp = timestamp +// self.viewType = viewType +// self.inset = NSEdgeInsets(left: 30, right: 30) +// let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) +// +// var t: time_t = time_t(timestamp) +// var timeinfo: tm = tm() +// localtime_r(&t, &timeinfo) +// +// var now: time_t = time_t(nowTimestamp) +// var timeinfoNow: tm = tm() +// localtime_r(&now, &timeinfoNow) +// +// var text: String +// if timeinfo.tm_year == timeinfoNow.tm_year && timeinfo.tm_yday == timeinfoNow.tm_yday { +// text = L10n.dateToday +// } else { +// let dateFormatter = DateFormatter() +// dateFormatter.calendar = Calendar.autoupdatingCurrent +// //dateFormatter.timeZone = NSTimeZone.local +// dateFormatter.dateFormat = "dd MMMM"; +// if timeinfoNow.tm_year > timeinfo.tm_year && (timeinfoNow.tm_mon >= timeinfo.tm_mon || (timeinfoNow.tm_year - timeinfo.tm_year) >= 2) { +// dateFormatter.dateFormat = "dd MMMM yyyy"; +// } else if timeinfoNow.tm_year < timeinfo.tm_year { +// dateFormatter.dateFormat = "dd MMMM yyyy"; +// } +// text = dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(timestamp))) +// +// } +// self.layout = TextViewLayout(.initialize(string: text, color: theme.colors.listGrayText, font: .normal(.text)), maximumNumberOfLines: 1, truncationType: .end, alignment: .center) +// +// super.init(initialSize) +// self.layout.measure(width: .greatestFiniteMagnitude) +// } +// +// override var canBeAnchor: Bool { +// return false +// } +// +// required init(_ initialSize: NSSize) { +// self.timestamp = 0 +// self.viewType = .legacy +// self.layout = TextViewLayout(NSAttributedString()) +// self.inset = NSEdgeInsets(left: 30, right: 30) +// super.init(initialSize) +// } +// +// override func makeSize(_ width: CGFloat, oldWidth:CGFloat) -> Bool { +// let success = super.makeSize(width, oldWidth: oldWidth) +// return success +// } +// +// override var stableId: AnyHashable { +// return self.timestamp +// } +// +// override var height: CGFloat { +// return 30 +// } +// +// override func viewClass() -> AnyClass { +// return WalletTransactionDateStickView.self +// } +// +//} +// +// +//private final class WalletTransactionDateStickView : TableStickView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let textView = TextView() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// addSubview(self.containerView) +// containerView.addSubview(self.textView) +// self.textView.disableBackgroundDrawing = true +// self.textView.isSelectable = false +// self.textView.userInteractionEnabled = false +// } +// +// override var header: Bool { +// didSet { +// updateColors() +// } +// } +// +// +// override var backdorColor: NSColor { +// return theme.colors.listBackground.withAlphaComponent(0.8) +// } +// +// override func updateColors() { +// guard let item = item as? WalletTransactionDateStickItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletTransactionDateStickItem else { +// return +// } +// +// let blockWidth = min(600, frame.width - item.inset.left - item.inset.right) +// +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - blockWidth) / 2), item.inset.top, blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners([]) +// +// textView.centerY(x: item.viewType.innerInset.left) +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// +// guard let item = item as? WalletTransactionDateStickItem else { +// return +// } +// self.textView.update(item.layout) +// +// needsLayout = true +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/Telegram-Mac/WalletTransactionPreviewController.swift b/Telegram-Mac/WalletTransactionPreviewController.swift new file mode 100644 index 0000000000..ab307c4823 --- /dev/null +++ b/Telegram-Mac/WalletTransactionPreviewController.swift @@ -0,0 +1,286 @@ +//// +//// WalletTransactionPreviewController.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 24/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TelegramCore +//import SyncCore +//import SwiftSignalKit +//import TGUIKit +//import WalletCore +// +//private final class WalletTransactionPreviewArguments { +// let context: AccountContext +// let copy:(String)->Void +// let sendGrams:(String)->Void +// init(context: AccountContext, copy: @escaping(String)->Void, sendGrams: @escaping(String)->Void) { +// self.context = context +// self.copy = copy +// self.sendGrams = sendGrams +// } +//} +// +//private struct WalletTransactionPreviewState : Equatable { +// let transaction: WalletInfoTransaction +// init(transaction: WalletInfoTransaction) { +// self.transaction = transaction +// } +//} +//private let _id_value = InputDataIdentifier("_id_value") +//private let _id_comment = InputDataIdentifier("_id_comment") +//private let _id_address = InputDataIdentifier("_id_address") +//private let _id_fee_other = InputDataIdentifier("_id_fee_other") +//private let _id_fee_storage = InputDataIdentifier("_id_fee_storage") +// +// +//private let _id_copy = InputDataIdentifier("_id_copy") +//private let _id_send = InputDataIdentifier("_id_send") +// +// +// +//private var dayFormatterRelative: DateFormatter { +// let dateFormatter = DateFormatter() +// dateFormatter.locale = Locale(identifier: appAppearance.language.languageCode) +// // dateFormatter.timeZone = TimeZone(abbreviation: "UTC")! +// +// dateFormatter.dateStyle = .medium +// dateFormatter.timeStyle = .short +// dateFormatter.doesRelativeDateFormatting = true +// return dateFormatter +//} +// +//private func formatDay(_ date: Date) -> String { +// return dayFormatterRelative.string(from: date) +//} +// +//enum WalletTransactionAddress { +// case list([String]) +// case none +// case unknown +//} +// +// +//func stringForAddress(address: WalletTransactionAddress) -> String { +// switch address { +// case let .list(addresses): +// return addresses.map { formatAddress($0) }.joined(separator: "\n\n") +// case .none: +// return L10n.walletTransactionEmptyAddress +// case .unknown: +// return "" +// } +//} +// +//func extractAddress(_ walletTransaction: WalletInfoTransaction) -> WalletTransactionAddress { +// switch walletTransaction { +// case let .completed(walletTransaction): +// let transferredValue = walletTransaction.transferredValueWithoutFees +// if transferredValue <= 0 { +// if walletTransaction.outMessages.isEmpty { +// return .none +// } else { +// var addresses: [String] = [] +// for message in walletTransaction.outMessages { +// addresses.append(message.destination) +// } +// return .list(addresses) +// } +// } else { +// if let inMessage = walletTransaction.inMessage { +// return .list([inMessage.source]) +// } else { +// return .unknown +// } +// } +// return .none +// case let .pending(pending): +// return .list([pending.address]) +// } +//} +// +//func extractDescription(_ walletTransaction: WalletInfoTransaction) -> String { +// switch walletTransaction { +// case let .completed(walletTransaction): +// let transferredValue = walletTransaction.transferredValueWithoutFees +// var text = "" +// if transferredValue <= 0 { +// for message in walletTransaction.outMessages { +// if !text.isEmpty { +// text.append("\n\n") +// } +// text.append(message.textMessage) +// } +// } else { +// if let inMessage = walletTransaction.inMessage { +// text = inMessage.textMessage +// } +// } +// return text +// case let .pending(pending): +// return String(data: pending.comment, encoding: .utf8) ?? "" +// } +//} +// +// +// +// +//private func WalletTransactionPreviewEntries(state: WalletTransactionPreviewState, arguments: WalletTransactionPreviewArguments) -> [InputDataEntry] { +// var entries:[InputDataEntry] = [] +// +// var sectionId:Int32 = 0 +// var index:Int32 = 0 +// +// +// let title: String +// let transferredValue: Int64 +// switch state.transaction { +// case let .completed(transaction): +// transferredValue = transaction.transferredValueWithoutFees +// case let .pending(transaction): +// transferredValue = -transaction.value +// } +// let address = stringForAddress(address: extractAddress(state.transaction)) +// let comment = extractDescription(state.transaction) +// +// +// var color: NSColor +// let headerText: String +// if transferredValue <= 0 { +// title = "\(formatBalanceText(abs(transferredValue)))" +// headerText = L10n.walletTransactionPreviewRecipient +// color = theme.colors.redUI +// } else { +// headerText = L10n.walletTransactionPreviewSender +// color = theme.colors.greenUI +// title = "\(formatBalanceText(transferredValue))" +// } +// +// +// let timestamp: Int64 +// switch state.transaction { +// case let .completed(transaction): +// timestamp = transaction.timestamp +// case let .pending(transaction): +// timestamp = transaction.timestamp +// } +// +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_value, equatable: InputDataEquatable(state.transaction), item: { initialSize, stableId in +// var subText: String = "" +// if case let .completed(transaction) = state.transaction { +// if transaction.otherFee != 0 { +// subText += L10n.walletTransactionPreviewTransactionFee("-\(formatBalanceText(transaction.otherFee))") +// } +// if transaction.storageFee != 0 { +// if !subText.isEmpty { +// subText += "\n" +// } +// subText += L10n.walletTransactionPreviewStorageFee("-\(formatBalanceText(transaction.storageFee))") +// } +// } +// return WalletTransactionTextItem(initialSize, stableId: stableId, context: arguments.context, value: title, subText: subText, color: color, viewType: .modern(position: .single, insets: NSEdgeInsets(left: 12, right: 12, top: 30, bottom: 20))) +// })) +// index += 1 +// +// +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(headerText), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_address, equatable: InputDataEquatable(state.transaction), item: { initialSize, stableId in +// return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .firstItem, text: address, font: .blockchain(.text)) +// })) +// index += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .string(address), error: nil, identifier: _id_copy, data: InputDataGeneralData(name: L10n.walletTransactionPreviewCopyAddress, color: theme.colors.accent, viewType: .innerItem))) +// index += 1 +// +// entries.append(.general(sectionId: sectionId, index: index, value: .string(address), error: nil, identifier: _id_send, data: InputDataGeneralData(name: L10n.walletTransactionPreviewSendGrams, color: theme.colors.accent, viewType: .lastItem))) +// index += 1 +// +// +// +// if !comment.isEmpty { +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// +// entries.append(.desc(sectionId: sectionId, index: index, text: .plain(L10n.walletTransactionPreviewCommentHeader), data: InputDataGeneralTextData(viewType: .textTopItem))) +// index += 1 +// entries.append(.custom(sectionId: sectionId, index: index, value: .none, identifier: _id_comment, equatable: InputDataEquatable(state.transaction), item: { initialSize, stableId in +// return GeneralBlockTextRowItem(initialSize, stableId: stableId, viewType: .singleItem, text: comment, font: .normal(.text)) +// })) +// index += 1 +// } +// +// entries.append(.sectionId(sectionId, type: .normal)) +// sectionId += 1 +// +// return entries +//} +//@available(OSX 10.12, *) +//func WalletTransactionPreviewController(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, transaction: WalletInfoTransaction, walletState: WalletState? = nil, updateWallet:(()->Void)? = nil) -> InputDataModalController { +// let initialState = WalletTransactionPreviewState(transaction: transaction) +// let state: ValuePromise = ValuePromise(initialState) +// let stateValue: Atomic = Atomic(value: initialState) +// +// let updateState:((WalletTransactionPreviewState)->WalletTransactionPreviewState) -> Void = { f in +// state.set(stateValue.modify(f)) +// } +// +// var getController:(()->InputDataController?)? = nil +// +// let arguments = WalletTransactionPreviewArguments(context: context, copy: { address in +// copyToClipboard(address) +// getController?()?.show(toaster: ControllerToaster(text: L10n.shareLinkCopied)) +// +// }, sendGrams: { address in +// showModal(with: WalletSendController(context: context, tonContext: tonContext, walletInfo: walletInfo, walletState: walletState, recipient: address, updateWallet: updateWallet), for: context.window) +// }) +// +// let dataSignal = state.get() |> deliverOnPrepareQueue |> map { state in +// return WalletTransactionPreviewEntries(state: state, arguments: arguments) +// } |> map { entries in +// return InputDataSignalValue(entries: entries) +// } +// +// var getModalController:(()->InputDataModalController?)? = nil +// +// let controller = InputDataController(dataSignal: dataSignal, title: L10n.walletTransactionPreviewTitle, hasDone: false) +// +// controller.validateData = { data in +// if let address = data[_id_copy]?.stringValue { +// arguments.copy(address.replacingOccurrences(of: "\n", with: "")) +// } else if let address = data[_id_send]?.stringValue { +// arguments.sendGrams(address.replacingOccurrences(of: "\n", with: "")) +// } +// return .none +// } +// +// controller.leftModalHeader = ModalHeaderData(image: theme.icons.wallet_close, handler: { +// getModalController?()?.close() +// }) +// +// let date = formatDay(Date(timeIntervalSince1970: TimeInterval(transaction.timestamp) - arguments.context.timeDifference)) +// controller.centerModalHeader = ModalHeaderData(title: L10n.walletTransactionPreviewTitle, subtitle: date) +// +// getController = { [weak controller] in +// return controller +// } +// +// let modalController = InputDataModalController(controller, closeHandler: { f in +// f() +// }, size: NSMakeSize(350, 350)) +// +// getModalController = { [weak modalController] in +// return modalController +// } +// +// +// return modalController +//} diff --git a/Telegram-Mac/WalletTransactionTextItem.swift b/Telegram-Mac/WalletTransactionTextItem.swift new file mode 100644 index 0000000000..e84f8ca1a2 --- /dev/null +++ b/Telegram-Mac/WalletTransactionTextItem.swift @@ -0,0 +1,121 @@ +//// +//// GeneralBlockTextRowItem.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 23/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +//import TGUIKit +// +// +//final class WalletTransactionTextItem : GeneralRowItem { +// fileprivate let textLayout: TextViewLayout +// fileprivate let subTextLayout: TextViewLayout +// fileprivate let context: AccountContext +// init(_ initialSize: NSSize, stableId: AnyHashable, context: AccountContext, value: String, subText: String, color: NSColor, viewType: GeneralViewType) { +// self.context = context +// let attributed: NSMutableAttributedString = NSMutableAttributedString() +// if let range = value.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(value[.. Bool { +// _ = super.makeSize(width, oldWidth: oldWidth) +// +// textLayout.measure(width: blockWidth - viewType.innerInset.left - viewType.innerInset.right) +// subTextLayout.measure(width: blockWidth - viewType.innerInset.left - viewType.innerInset.right) +// return true +// } +// +// override var height: CGFloat { +// return textLayout.layoutSize.height + subTextLayout.layoutSize.height + viewType.innerInset.top + viewType.innerInset.bottom +// } +// +// override func viewClass() -> AnyClass { +// return WalletTransactionTextView.self +// } +// +//} +// +// +//private final class WalletTransactionTextView : TableRowView { +// private let containerView = GeneralRowContainerView(frame: NSZeroRect) +// private let crystalView = MediaAnimatedStickerView(frame: NSZeroRect) +// private let textView = TextView() +// private let subTextView = TextView() +// required init(frame frameRect: NSRect) { +// super.init(frame: frameRect) +// addSubview(self.containerView) +// self.containerView.addSubview(self.textView) +// self.containerView.addSubview(self.subTextView) +// self.containerView.addSubview(self.crystalView) +// subTextView.isSelectable = false +// subTextView.userInteractionEnabled = false +// +// self.textView.isSelectable = false +// self.textView.userInteractionEnabled = false +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override var backdorColor: NSColor { +// return theme.colors.listBackground +// } +// +// override func updateColors() { +// guard let item = item as? WalletTransactionTextItem else { +// return +// } +// self.backgroundColor = item.viewType.rowBackground +// self.containerView.backgroundColor = backdorColor +// self.textView.backgroundColor = backdorColor +// self.subTextView.backgroundColor = backdorColor +// } +// +// override func layout() { +// super.layout() +// guard let item = item as? WalletTransactionTextItem else { +// return +// } +// +// self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) +// self.containerView.setCorners(item.viewType.corners) +// +// textView.centerX(y: item.viewType.innerInset.top) +// +// crystalView.setFrameOrigin(NSMakePoint(textView.frame.minX - crystalView.frame.width, textView.frame.minY + 1)) +// +// subTextView.centerX(y: textView.frame.maxY - 8) +// +// } +// +// override func set(item: TableRowItem, animated: Bool = false) { +// super.set(item: item, animated: animated) +// guard let item = item as? WalletTransactionTextItem else { +// return +// } +// self.textView.update(item.textLayout) +// self.subTextView.update(item.subTextLayout) +// +// crystalView.update(with: LocalAnimatedSticker.brilliant_static.file, size: NSMakeSize(40, 40), context: item.context, parent: nil, table: nil, parameters: LocalAnimatedSticker.brilliant_static.parameters, animated: animated, positionFlags: nil, approximateSynchronousValue: true) +// +// needsLayout = true +// } +//} diff --git a/Telegram-Mac/WalletUtils.swift b/Telegram-Mac/WalletUtils.swift new file mode 100644 index 0000000000..7be71e42b8 --- /dev/null +++ b/Telegram-Mac/WalletUtils.swift @@ -0,0 +1,180 @@ +//// +//// WalletUtils.swift +//// Telegram +//// +//// Created by Mikhail Filimonov on 29/09/2019. +//// Copyright © 2019 Telegram. All rights reserved. +//// +// +//import Cocoa +// +//let walletAddressLength: Int = 48 +// +//func formatAddress(_ address: String) -> String { +// var address = address +// // address.insert("\n", at: address.index(address.startIndex, offsetBy: address.count / 2)) +// return address +//} +// +//func formatBalanceText(_ value: Int64, decimalSeparator: String = Formatter.withSeparator.decimalSeparator) -> String { +// var balanceText = "\(abs(value))" +// while balanceText.count < 10 { +// balanceText.insert("0", at: balanceText.startIndex) +// } +// balanceText.insert(contentsOf: decimalSeparator, at: balanceText.index(balanceText.endIndex, offsetBy: -9)) +// while true { +// if balanceText.hasSuffix("0") { +// if balanceText.hasSuffix("\(decimalSeparator)0") { +// break +// } else { +// balanceText.removeLast() +// } +// } else { +// break +// } +// } +// return balanceText +//} +// +//let invalidAmountCharacters = CharacterSet(charactersIn: "01234567890.,").inverted +//func isValidAmount(_ amount: String) -> Bool { +// let amount = normalizeArabicNumeralString(amount, type: .western) +// if amount.rangeOfCharacter(from: invalidAmountCharacters) != nil { +// return false +// } +// var hasDecimalSeparator = false +// var hasLeadingZero = false +// var index = 0 +// for c in amount { +// if c == "." || c == "," || c == Formatter.withSeparator.decimalSeparator.first { +// if !hasDecimalSeparator { +// hasDecimalSeparator = true +// } else { +// return false +// } +// } +// index += 1 +// } +// +// var decimalIndex: String.Index? +// if let index = amount.firstIndex(of: ".") { +// decimalIndex = index +// } else if let index = amount.firstIndex(of: ",") { +// decimalIndex = index +// } else if let index = amount.firstIndex(of: Formatter.withSeparator.decimalSeparator.first!) { +// decimalIndex = index +// } +// +// if let decimalIndex = decimalIndex, amount.distance(from: decimalIndex, to: amount.endIndex) > 10 { +// return false +// } +// +// let string = amount.replacingOccurrences(of: ",", with: Formatter.withSeparator.decimalSeparator) +// if let range = string.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(string[.. Int64 { +// let string = string.replacingOccurrences(of: ",", with: Formatter.withSeparator.decimalSeparator) +// if let range = string.range(of: Formatter.withSeparator.decimalSeparator) { +// let integralPart = String(string[.. maxIntegral { +// return 0 +// } +// return integral * 1000000000 +// } +// return 0 +//} +// +//func normalizedStringForGramsString(_ string: String, decimalSeparator: String = Formatter.withSeparator.decimalSeparator) -> String { +// return formatBalanceText(amountValue(string), decimalSeparator: decimalSeparator) +//} +// +//func formatAmountText(_ text: String, decimalSeparator: String = Formatter.withSeparator.decimalSeparator) -> String { +// var text = normalizeArabicNumeralString(text, type: .western) +// if text == "." || text == "," || text == Formatter.withSeparator.decimalSeparator { +// text = "0\(decimalSeparator)" +// } else if text == "0" { +// text = "0\(decimalSeparator)" +// } else if text.hasPrefix("0") && text.firstIndex(of: ".") == nil && text.firstIndex(of: ",") == nil && text.firstIndex(of: Formatter.withSeparator.decimalSeparator.first!) == nil { +// var trimmedText = text +// while trimmedText.first == "0" { +// trimmedText.removeFirst() +// } +// text = trimmedText +// } +// return text +//} +// +//let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted +// +//func isValidAddress(_ address: String, exactLength: Bool = false) -> Bool { +// if address.count > walletAddressLength || address.rangeOfCharacter(from: invalidAddressCharacters) != nil { +// return false +// } +// if exactLength && address.count != walletAddressLength { +// return false +// } +// return true +//} +// +// +// +//public enum ArabicNumeralStringType { +// case western +// case arabic +// case persian +//} +// +//public func normalizeArabicNumeralString(_ string: String, type: ArabicNumeralStringType) -> String { +// var string = string +// +// let numerals = [ +// ("0", "٠", "۰"), +// ("1", "١", "۱"), +// ("2", "٢", "۲"), +// ("3", "٣", "۳"), +// ("4", "٤", "۴"), +// ("5", "٥", "۵"), +// ("6", "٦", "۶"), +// ("7", "٧", "۷"), +// ("8", "٨", "۸"), +// ("9", "٩", "۹"), +// (",", "٫", "٫") +// ] +// for (western, arabic, persian) in numerals { +// switch type { +// case .western: +// string = string.replacingOccurrences(of: arabic, with: western) +// string = string.replacingOccurrences(of: persian, with: western) +// case .arabic: +// string = string.replacingOccurrences(of: western, with: arabic) +// string = string.replacingOccurrences(of: persian, with: arabic) +// case .persian: +// string = string.replacingOccurrences(of: western, with: persian) +// string = string.replacingOccurrences(of: arabic, with: persian) +// } +// +// } +// return string +//} diff --git a/Telegram-Mac/WallpaperColorPicker.swift b/Telegram-Mac/WallpaperColorPicker.swift new file mode 100644 index 0000000000..aeb8a1cbaf --- /dev/null +++ b/Telegram-Mac/WallpaperColorPicker.swift @@ -0,0 +1,449 @@ +// +// WallpaperColorPicker.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit + +private let shadowImage: CGImage = { + return generateImage(CGSize(width: 45.0, height: 45.0), opaque: false, scale: System.backingScale, rotatedContext: { size, context in + context.setBlendMode(.clear) + context.setFillColor(NSColor.clear.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.normal) + context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: NSColor(rgb: 0x000000, alpha: 0.5).cgColor) + context.setFillColor(NSColor(rgb: 0x000000, alpha: 0.5).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + .borderSize, dy: 3.0 + .borderSize)) + })! +}() + +private let smallShadowImage: CGImage = { + return generateImage(CGSize(width: 24.0, height: 24.0), opaque: false, scale: System.backingScale, rotatedContext: { size, context in + context.setBlendMode(.clear) + context.setFillColor(NSColor.clear.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.normal) + context.setShadow(offset: CGSize(width: 0.0, height: 1.5), blur: 4.5, color: NSColor(rgb: 0x000000, alpha: 0.65).cgColor) + context.setFillColor(NSColor(rgb: 0x000000, alpha: 0.5).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 3.0 + .borderSize, dy: 3.0 + .borderSize)) + })! +}() + +private let pointerImage: CGImage = { + return generateImage(CGSize(width: 12.0, height: 42.0), opaque: false, scale: System.backingScale, rotatedContext: { size, context in + context.setBlendMode(.clear) + context.setFillColor(NSColor.clear.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.normal) + + let lineWidth: CGFloat = 1.0 + context.setFillColor(NSColor.black.cgColor) + context.setStrokeColor(NSColor.white.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + + let pointerHeight: CGFloat = 6.0 + context.move(to: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - lineWidth / 2.0, y: lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width / 2.0, y: lineWidth / 2.0 + pointerHeight)) + context.closePath() + context.drawPath(using: .fillStroke) + + context.move(to: CGPoint(x: lineWidth / 2.0, y: size.height - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width / 2.0, y: size.height - lineWidth / 2.0 - pointerHeight)) + context.addLine(to: CGPoint(x: size.width - lineWidth / 2.0, y: size.height - lineWidth / 2.0)) + context.closePath() + context.drawPath(using: .fillStroke) + })! +}() + +private final class HSVParameter: NSObject { + let hue: CGFloat + let saturation: CGFloat + let value: CGFloat + + init(hue: CGFloat, saturation: CGFloat, value: CGFloat) { + self.hue = hue + self.saturation = saturation + self.value = value + super.init() + } +} + +private final class IntensitySliderParameter: NSObject { + let bordered: Bool + let min: HSVParameter + let max: HSVParameter + + init(bordered: Bool, min: HSVParameter, max: HSVParameter) { + self.bordered = bordered + self.min = min + self.max = max + super.init() + } +} + + +private final class WallpaperColorHueSaturationView: View { + var parameters: HSVParameter = HSVParameter(hue: 1.0, saturation: 1.0, value: 1.0) { + didSet { + self.setNeedsDisplay() + } + } + + var value: CGFloat = 1.0 { + didSet { + parameters = HSVParameter(hue: 1.0, saturation: 1.0, value: self.value) + } + } + + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func draw(_ layer: CALayer, in context: CGContext) { + + + let colorSpace = deviceColorSpace + + let colors = [NSColor(rgb: 0xff0000).cgColor, NSColor(rgb: 0xffff00).cgColor, NSColor(rgb: 0x00ff00).cgColor, NSColor(rgb: 0x00ffff).cgColor, NSColor(rgb: 0x0000ff).cgColor, NSColor(rgb: 0xff00ff).cgColor, NSColor(rgb: 0xff0000).cgColor] + var locations: [CGFloat] = [0.0, 0.16667, 0.33333, 0.5, 0.66667, 0.83334, 1.0] + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: bounds.width, y: 0.0), options: CGGradientDrawingOptions()) + + let overlayColors = [NSColor(rgb: 0xffffff, alpha: 0.0).cgColor, NSColor(rgb: 0xffffff).cgColor] + var overlayLocations: [CGFloat] = [0.0, 1.0] + let overlayGradient = CGGradient(colorsSpace: colorSpace, colors: overlayColors as CFArray, locations: &overlayLocations)! + context.drawLinearGradient(overlayGradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.height), options: CGGradientDrawingOptions()) + + context.setFillColor(NSColor(rgb: 0x000000, alpha: 1.0 - parameters.value).cgColor) + context.fill(bounds) + } +} + + +private final class WallpaperColorBrightnessView: View { + var hsv: (CGFloat, CGFloat, CGFloat) = (0.0, 1.0, 1.0) { + didSet { + self.setNeedsDisplay() + } + } + + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + var parameters:HSVParameter { + return HSVParameter(hue: self.hsv.0, saturation: self.hsv.1, value: self.hsv.2) + } + + + override func draw(_ layer: CALayer, in context: CGContext) { + let colorSpace = deviceColorSpace + + context.setFillColor(NSColor(white: parameters.value, alpha: 1.0).cgColor) + context.fill(bounds) + + + + let path = NSBezierPath(roundedRect: bounds, xRadius: bounds.height / 2.0, yRadius: bounds.height / 2.0) + context.addPath(path.cgPath) + context.setFillColor(NSColor.white.cgColor) + context.fillPath() + + let innerPath = NSBezierPath(roundedRect: bounds.insetBy(dx: 1.0, dy: 1.0), xRadius: bounds.height / 2.0, yRadius: bounds.height / 2.0) + context.addPath(innerPath.cgPath) + context.clip() + + let color = NSColor(hue: parameters.hue, saturation: parameters.saturation, brightness: 1.0, alpha: 1.0) + let colors = [color.cgColor, NSColor.black.cgColor] + var locations: [CGFloat] = [0.0, 1.0] + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: bounds.width, y: 0.0), options: CGGradientDrawingOptions()) + } + +} + + +private final class WallpaperColorKnobView: View { + var hsv: (CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 1.0) { + didSet { + if self.hsv != oldValue { + self.setNeedsDisplay() + } + } + } + + var parameters: HSVParameter { + return HSVParameter(hue: self.hsv.0, saturation: self.hsv.1, value: self.hsv.2) + } + + override init() { + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func draw(_ layer: CALayer, in context: CGContext) { + // if !isRasterizing { + context.setBlendMode(.copy) + context.setFillColor(NSColor.clear.cgColor) + context.fill(bounds) + // } + + let image = bounds.width > 30.0 ? shadowImage : smallShadowImage + context.draw(image, in: bounds) + + context.setBlendMode(.normal) + context.setFillColor(NSColor.white.cgColor) + context.fillEllipse(in: bounds.insetBy(dx: 3.0, dy: 3.0)) + + let color = NSColor(hue: parameters.hue, saturation: parameters.saturation, brightness: parameters.value, alpha: 1.0) + context.setFillColor(color.cgColor) + + let borderWidth: CGFloat = bounds.width > 30.0 ? 5.0 : 5.0 + context.fillEllipse(in: bounds.insetBy(dx: borderWidth - .borderSize, dy: borderWidth - .borderSize)) + } +} + +private enum PickerChangeValue { + case color + case brightness +} + + + +final class WallpaperColorPickerView: View { + private let brightnessView: WallpaperColorBrightnessView + private let brightnessKnobView: ImageView + private let colorView: WallpaperColorHueSaturationView + + + private let colorKnobView: WallpaperColorKnobView + + private var pickerValue: PickerChangeValue? + + var colorHSV: (CGFloat, CGFloat, CGFloat) = (0.0, 1.0, 1.0) + var color: NSColor { + get { + return NSColor(hue: self.colorHSV.0, saturation: self.colorHSV.1, brightness: self.colorHSV.2, alpha: 1.0) + } + set { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var value: CGFloat = 0.0 + + + newValue.getHue(&hue, saturation: &saturation, brightness: &value, alpha: nil) + let newHSV: (CGFloat, CGFloat, CGFloat) = (hue, saturation, value) + + if newHSV != self.colorHSV { + self.colorHSV = newHSV + self.update() + } + } + } + var colorChanged: ((NSColor) -> Void)? + var colorChangeEnded: ((NSColor) -> Void)? + + + + var adjustingPattern: Bool = false { + didSet { + let value = self.adjustingPattern + self.brightnessView.isHidden = value + self.brightnessKnobView.isHidden = value + self.needsLayout = true + } + } + + override init() { + self.brightnessView = WallpaperColorBrightnessView() + //self.brightnessView.hitTestSlop = NSEdgeInsetsMake(-16.0, -16.0, -16.0, -16.0) + self.brightnessKnobView = ImageView() + self.brightnessKnobView.image = pointerImage + self.colorView = WallpaperColorHueSaturationView() + // self.colorView.hitTestSlop = NSEdgeInsetsMake(-16.0, -16.0, -16.0, -16.0) + self.colorKnobView = WallpaperColorKnobView() + + + + super.init() + + self.backgroundColor = .white + + self.addSubview(self.brightnessView) + self.addSubview(self.colorView) + self.addSubview(self.colorKnobView) + self.addSubview(self.brightnessKnobView) + + let valueChanged: (CGFloat, Bool) -> Void = { [weak self] value, ended in + if let strongSelf = self { + let previousColor = strongSelf.color + strongSelf.colorHSV.2 = 1.0 - value + + if strongSelf.color != previousColor || ended { + strongSelf.update() + if ended { + strongSelf.colorChangeEnded?(strongSelf.color) + } else { + strongSelf.colorChanged?(strongSelf.color) + } + } + } + } + + self.update() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + + private func update() { + if self.adjustingPattern { + self.backgroundColor = .white + } else { + self.backgroundColor = NSColor(white: self.colorHSV.2, alpha: 1.0) + } + self.colorView.value = self.colorHSV.2 + self.brightnessView.hsv = self.colorHSV + self.colorKnobView.hsv = self.colorHSV + + } + + func updateKnobLayout(size: CGSize, panningColor: Bool) { + let knobSize = CGSize(width: 45.0, height: 45.0) + + let colorHeight = size.height - 40 + var colorKnobFrame = CGRect(x: -knobSize.width / 2.0 + size.width * self.colorHSV.0, y: -knobSize.height / 2.0 + (colorHeight * (1.0 - self.colorHSV.1)), width: knobSize.width, height: knobSize.height) + var origin = colorKnobFrame.origin + if !panningColor { + origin = CGPoint(x: max(0.0, min(origin.x, size.width - knobSize.width)), y: max(0.0, min(origin.y, colorHeight - knobSize.height))) + } else { + origin = origin.offsetBy(dx: 0.0, dy: -32.0) + } + colorKnobFrame.origin = origin + self.colorKnobView.frame = colorKnobFrame + + let inset: CGFloat = 42.0 + let brightnessKnobSize = CGSize(width: 12.0, height: 42.0) + let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.colorHSV.2), y: size.height - 46.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) + self.brightnessKnobView.frame = brightnessKnobFrame + } + + override func layout() { + super.layout() + let size = frame.size + let colorHeight = size.height - 40.0 + colorView.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: colorHeight) + + let inset: CGFloat = 42.0 + brightnessView.frame = CGRect(x: inset, y: size.height - 40, width: size.width - (inset * 2.0), height: 29.0) + + let slidersInset: CGFloat = 24.0 + + self.updateKnobLayout(size: size, panningColor: false) + } + + override func mouseDown(with event: NSEvent) { + + if brightnessView.mouseInside() || brightnessKnobView._mouseInside() { + pickerValue = .brightness + } else { + pickerValue = .color + } + + guard let pickerValue = pickerValue else { return } + let size = frame.size + let colorHeight = size.height - 40.0 + + switch pickerValue { + case .color: + let location = self.convert(event.locationInWindow, from: nil) + let newHue = max(0.0, min(1.0, location.x / size.width)) + let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) + self.colorHSV.0 = newHue + self.colorHSV.1 = newSaturation + case .brightness: + let location = brightnessView.convert(event.locationInWindow, from: nil) + let brightnessWidth: CGFloat = brightnessView.frame.width + let newValue = max(0.0, min(1.0, 1.0 - location.x / brightnessWidth)) + self.colorHSV.2 = newValue + } + self.updateKnobLayout(size: size, panningColor: false) + self.update() + self.colorChanged?(self.color) + } + + override func mouseDragged(with event: NSEvent) { + let previousColor = self.color + let size = frame.size + let colorHeight = size.height - 40.0 + + guard let pickerValue = pickerValue else { return } + + + switch pickerValue { + case .color: + var location = self.convert(event.locationInWindow, from: nil) + location.x = min(max(location.x, 1.0), frame.width) + + let newHue = max(0.0, min(1.0, location.x / size.width)) + let newSaturation = max(0.0, min(1.0, (1.0 - location.y / colorHeight))) + self.colorHSV.0 = newHue + self.colorHSV.1 = newSaturation + case .brightness: + let location = brightnessView.convert(event.locationInWindow, from: nil) + let brightnessWidth: CGFloat = brightnessView.frame.width + let newValue = max(0.0, min(1.0, 1.0 - location.x / brightnessWidth)) + self.colorHSV.2 = newValue + } + + + self.updateKnobLayout(size: size, panningColor: false) + + if self.color != previousColor { + self.update() + self.colorChanged?(self.color) + } + } + + override func mouseUp(with event: NSEvent) { + self.updateKnobLayout(size: frame.size, panningColor: false) + self.colorChanged?(self.color) + self.colorChangeEnded?(self.color) + pickerValue = nil + } +} diff --git a/Telegram-Mac/WallpaperPatternPreview.swift b/Telegram-Mac/WallpaperPatternPreview.swift new file mode 100644 index 0000000000..526480f5ff --- /dev/null +++ b/Telegram-Mac/WallpaperPatternPreview.swift @@ -0,0 +1,298 @@ +// +// WallpaperPatternPreview.swift +// Telegram +// +// Created by Mikhail Filimonov on 29/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + +private class WallpaperPatternView : Control { + private let backgroundView: BackgroundView + let imageView = TransformImageView() + let checkbox: ImageView = ImageView() + private let emptyTextView = TextView() + fileprivate var pattern: Wallpaper? + required init(frame frameRect: NSRect) { + backgroundView = BackgroundView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + + addSubview(backgroundView) + + + addSubview(imageView) + addSubview(checkbox) + checkbox.image = theme.icons.chatGroupToggleSelected + checkbox.sizeToFit() + self.layer?.cornerRadius = .cornerRadius + + emptyTextView.userInteractionEnabled = false + emptyTextView.isSelectable = false + } + + override func layout() { + super.layout() + imageView.frame = bounds + emptyTextView.center() + checkbox.setFrameOrigin(NSMakePoint(frame.width - checkbox.frame.width - 5, 5)) + backgroundView.frame = bounds + } + + func update(with pattern: Wallpaper?, isSelected: Bool, account: Account, color: [NSColor], rotation: Int32?) { + checkbox.isHidden = !isSelected + self.pattern = pattern + if color.count == 2 { + backgroundView.backgroundMode = .gradient(top: color[0], bottom: color[1], rotation: rotation) + } else { + backgroundView.backgroundMode = .color(color: color[0]) + } + + let layout = TextViewLayout(.initialize(string: L10n.chatWPPatternNone, color: color.first!.brightnessAdjustedColor, font: .normal(.title))) + layout.measure(width: 80) + emptyTextView.update(layout) + + if let pattern = pattern { + emptyTextView.isHidden = true + imageView.isHidden = false + + let emptyColor: TransformImageEmptyColor + if color.count == 2 { + emptyColor = .gradient(top: color.first!.withAlphaComponent(color.first!.alpha == 0 ? 0.5 : color.first!.alpha), bottom: color.last!.withAlphaComponent(color.last!.alpha == 0 ? 0.5 : color.last!.alpha), rotation: rotation) + } else { + emptyColor = .color(color.first!) + } + + imageView.set(arguments: TransformImageArguments(corners: ImageCorners(radius: .cornerRadius), imageSize: pattern.dimensions.aspectFilled(NSMakeSize(300, 300)), boundingSize: bounds.size, intrinsicInsets: NSEdgeInsets(), emptyColor: emptyColor)) + switch pattern { + case let .file(_, file, _, _): + var representations:[TelegramMediaImageRepresentation] = [] + if let dimensions = file.dimensions { + representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource)) + } else { + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 300, height: 300), resource: file.resource)) + } + imageView.setSignal(chatWallpaper(account: account, representations: representations, file: file, mode: .thumbnail, isPattern: true, autoFetchFullSize: true, scale: backingScaleFactor, isBlurred: false, synchronousLoad: false), animate: false, synchronousLoad: false) + default: + break + } + } else { + emptyTextView.isHidden = false + imageView.isHidden = true + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +final class WallpaperPatternPreviewView: View { + private let documentView: View = View() + private let scrollView = HorizontalScrollView() + private let sliderView = LinearProgressControl(progressHeight: 5) + private let intensityTextView = TextView() + private let intensityContainerView = View() + private let borderView = View() + var updateIntensity: ((Float) -> Void)? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(scrollView) + scrollView.documentView = documentView + backgroundColor = theme.colors.background + + scrollView.backgroundColor = theme.colors.grayBackground.withAlphaComponent(0.7) + + borderView.backgroundColor = theme.colors.border + sliderView.scrubberImage = theme.icons.videoPlayerSliderInteractor + sliderView.roundCorners = true + sliderView.alignment = .center + sliderView.containerBackground = NSColor.grayBackground.withAlphaComponent(0.2) + sliderView.style = ControlStyle(foregroundColor: theme.colors.accent, backgroundColor: .clear, highlightColor: theme.colors.grayForeground) + sliderView.set(progress: 0.8) + sliderView.userInteractionEnabled = true + sliderView.insets = NSEdgeInsetsMake(0, 4.5, 0, 4.5) + sliderView.containerBackground = theme.colors.grayForeground + sliderView.liveScrobbling = true + sliderView.onUserChanged = { [weak self] value in + guard let `self` = self else {return} + self.sliderView.set(progress: CGFloat(value)) + self.updateIntensity?(value) + } + + let layout = TextViewLayout(.initialize(string: L10n.chatWPIntensity, color: theme.colors.grayText, font: .normal(.text))) + layout.measure(width: .greatestFiniteMagnitude) + intensityTextView.update(layout) + + intensityContainerView.addSubview(sliderView) + intensityContainerView.addSubview(intensityTextView) + intensityTextView.userInteractionEnabled = false + intensityTextView.isSelectable = false + addSubview(intensityContainerView) + addSubview(borderView) + } + + func updateColor(_ color: [NSColor], rotation: Int32?, account: Account) { + self.color = color + self.rotation = rotation + for subview in self.documentView.subviews { + if let subview = (subview as? WallpaperPatternView) { + subview.update(with: subview.pattern, isSelected: !subview.checkbox.isHidden, account: account, color: color, rotation: rotation) + } + } + } + + fileprivate var color: [NSColor] = [NSColor(rgb: 0xd6e2ee, alpha: 0.5)] + fileprivate var rotation: Int32? = nil + + func updateSelected(_ pattern: Wallpaper?) { + + for subview in self.documentView.subviews { + if let subview = (subview as? WallpaperPatternView) { + if let pattern = pattern { + subview.checkbox.isHidden = subview.pattern == nil || subview.pattern?.isSemanticallyEqual(to: pattern) == false + } else { + subview.checkbox.isHidden = pattern != subview.pattern + } + } + } + + let selectedView = self.documentView.subviews.first { view -> Bool in + return !(view as! WallpaperPatternView).checkbox.isHidden + } + if let selectedView = selectedView { + scrollView.clipView.scroll(to: NSMakePoint(min(max(selectedView.frame.midX - frame.width / 2, 0), max(documentView.frame.width - frame.width, 0)), 0), animated: true) + } + + if let pattern = pattern { + intensityContainerView.isHidden = false + if let intensity = pattern.settings.intensity { + sliderView.set(progress: CGFloat(intensity) / 100.0) + } + } else { + intensityContainerView.isHidden = true + } + } + + func update(with patterns: [Wallpaper?], selected: Wallpaper?, account: Account, select: @escaping(Wallpaper?) -> Void) { + documentView.removeAllSubviews() + var x: CGFloat = 10 + for pattern in patterns { + let patternView = WallpaperPatternView(frame: NSMakeRect(x, 10, 80, 80)) + patternView.update(with: pattern, isSelected: pattern == selected, account: account, color: self.color, rotation: self.rotation) + patternView.set(handler: { [weak self] _ in + guard let `self` = self else {return} + select(pattern) + self.updateSelected(pattern) + }, for: .Click) + documentView.addSubview(patternView) + x += patternView.frame.width + 10 + } + documentView.setFrameSize(NSMakeSize(x, 100)) + + } + + override func layout() { + super.layout() + scrollView.frame = NSMakeRect(0, 0, frame.width, 100) + + intensityContainerView.setFrameSize(frame.width - 80, intensityTextView.frame.height + 12 + 3) + sliderView.setFrameSize(NSMakeSize(intensityContainerView.frame.width - 20, 12)) + intensityTextView.centerX(y: 0) + sliderView.centerX(y: intensityTextView.frame.height + 3) + + intensityContainerView.centerX(y: 110) + borderView.frame = NSMakeRect(0, 0, frame.width, .borderSize) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class WallpaperPatternPreviewController: GenericViewController { + private let disposable = MetaDisposable() + private let context: AccountContext + + var color: ([NSColor], Int32?) = ([NSColor(rgb: 0xd6e2ee, alpha: 0.5)], nil) { + didSet { + genericView.updateColor(color.0, rotation: color.1, account: context.account) + } + } + + + var selected:((Wallpaper?) -> Void)? + + var intensity: Int32? = nil { + didSet { + if oldValue != nil, oldValue != intensity { + self.selected?(pattern?.withUpdatedSettings(WallpaperSettings(color: pattern?.settings.color, intensity: intensity))) + } + } + } + + var pattern: Wallpaper? { + didSet { + let intensity = self.intensity ?? pattern?.settings.intensity + self.intensity = intensity + + if let pattern = pattern { + switch pattern { + case .file: + genericView.updateSelected(pattern) + default: + break + } + } + } + } + + + init(context: AccountContext) { + self.context = context + super.init() + } + + override func viewDidLoad() { + super.viewDidLoad() + + + genericView.updateIntensity = { [weak self] intensity in + guard let `self` = self else {return} + self.intensity = Int32(intensity * 100) + } + + let signal = telegramWallpapers(postbox: context.account.postbox, network: context.account.network) |> map { wallpapers -> [Wallpaper] in + return wallpapers.compactMap { wallpaper in + switch wallpaper { + case let .file(_, _, _, _, isPattern, _, _, _, _): + return isPattern ? Wallpaper(wallpaper) : nil + default: + return nil + } + } + } |> deliverOnMainQueue + + disposable.set(signal.start(next: { [weak self] patterns in + guard let `self` = self else {return} + self.genericView.update(with: [nil] + patterns, selected: nil, account: self.context.account, select: { [weak self] wallpaper in + self?.pattern = wallpaper + self?.selected?(wallpaper) + }) + self.pattern = patterns.first + })) + + } + + deinit { + disposable.dispose() + } + +} diff --git a/Telegram-Mac/WallpaperPreviewController.swift b/Telegram-Mac/WallpaperPreviewController.swift new file mode 100644 index 0000000000..33217a1220 --- /dev/null +++ b/Telegram-Mac/WallpaperPreviewController.swift @@ -0,0 +1,1713 @@ +// +// WallpaperPreviewController.swift +// Telegram +// +// Created by Mikhail Filimonov on 17/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox +import SyncCore +import CoreGraphics + +enum WallpaperPreviewMode : Equatable { + case plain + case blurred +} + +private func availableColors() -> [Int32] { + return [ + 0xffffff, + 0xd4dfea, + 0xb3cde1, + 0x6ab7ea, + 0x008dd0, + 0xd3e2da, + 0xc8e6c9, + 0xc5e1a5, + 0x61b06e, + 0xcdcfaf, + 0xa7a895, + 0x7c6f72, + 0xffd7ae, + 0xffb66d, + 0xde8751, + 0xefd5e0, + 0xdba1b9, + 0xffafaf, + 0xf16a60, + 0xe8bcea, + 0x9592ed, + 0xd9bc60, + 0xb17e49, + 0xd5cef7, + 0xdf506b, + 0x8bd2cc, + 0x3c847e, + 0x22612c, + 0x244d7c, + 0x3d3b85, + 0x65717d, + 0x18222d, + 0x000000 + ] +} + + + +extension Wallpaper { + var dimensions: NSSize { + switch self { + case let .file(_, file, _, _): + if let dimensions = file.dimensions { + return dimensions.size + } + return NSMakeSize(300, 300) + case let .image(representations, _): + let largest = largestImageRepresentation(representations) + return largest!.dimensions.size + case let .custom(representation, _): + return representation.dimensions.size + case .color: + return NSMakeSize(300, 300) + default: + return NSZeroSize + } + + } +} + +let WallpaperDimensions: NSSize = NSMakeSize(1040, 1580) + +private final class blurCheckbox : View { + + var isFullFilled: Bool = false { + didSet { + needsDisplay = true + } + } + + private(set) var isSelected: Bool = false + private var timer: SwiftSignalKit.Timer? + func set(isSelected: Bool, animated: Bool) { + self.isSelected = isSelected + if animated { + timer?.invalidate() + + let fps: CGFloat = 60 + + let tick = isSelected ? ((1 - animationProgress) / (fps * 0.2)) : -(animationProgress / (fps * 0.2)) + timer = SwiftSignalKit.Timer(timeout: 0.016, repeat: true, completion: { [weak self] in + guard let `self` = self else {return} + self.animationProgress += tick + + if self.animationProgress <= 0 || self.animationProgress >= 1 { + self.timer?.invalidate() + self.timer = nil + } + + }, queue: .mainQueue()) + + timer?.start() + } else { + animationProgress = isSelected ? 1.0 : 0.0 + } + } + + deinit { + timer?.invalidate() + } + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var animationProgress: CGFloat = 0.0 { + didSet { + needsDisplay = true + } + } + + override func draw(_ layer: CALayer, in context: CGContext) { + super.draw(layer, in: context) + + let borderWidth: CGFloat = 2.0 + + context.setStrokeColor(.white) + context.setLineWidth(borderWidth) + context.strokeEllipse(in: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0)) + + let progress: CGFloat = animationProgress + let diameter = bounds.width + let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0) + + + context.setFillColor(.white) + context.fillEllipse(in: bounds.insetBy(dx: (diameter - borderWidth) * (1.0 - animationProgress), dy: (diameter - borderWidth) * (1.0 - animationProgress))) + if !isFullFilled { + let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0)) + let s = CGPoint(x: center.x - 4.0, y: center.y + 1.0) + let p1 = CGPoint(x: 3.0, y: 3.0) + let p2 = CGPoint(x: 5.0, y: -6.0) + + if !firstSegment.isZero { + if firstSegment < 1.0 { + context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment)) + context.addLine(to: s) + } else { + let secondSegment = (progress - 0.33) * 1.5 + context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment)) + context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y)) + context.addLine(to: s) + } + } + + + context.setBlendMode(.clear) + context.setLineWidth(borderWidth) + context.setLineCap(.round) + context.setLineJoin(.round) + context.setMiterLimit(10.0) + + + context.strokePath() + } + + } +} + +final class ApplyblurCheckbox : View { + private let title:(TextNodeLayout,TextNode) + fileprivate let checkbox: blurCheckbox = blurCheckbox(frame: NSMakeRect(0, 0, 16, 16)) + + var isSelected: Bool { + get { + return checkbox.isSelected + } + set { + checkbox.set(isSelected: newValue, animated: false) + } + } + + required init(frame frameRect: NSRect, title: String) { + self.title = TextNode.layoutText(.initialize(string: title, color: .white, font: .medium(.text)), nil, 1, .end, NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude), nil, false, .left) + super.init(frame: frameRect) + addSubview(checkbox) + layer?.cornerRadius = .cornerRadius + setFrameSize(self.title.0.size.width + 10 + checkbox.frame.width + 10 + 10, frameRect.height) + } + + override func mouseDown(with event: NSEvent) { + checkbox.set(isSelected: !checkbox.isSelected, animated: false) + onChangedValue?(checkbox.isSelected) + } + var onChangedValue:((Bool)->Void)? + + override func layout() { + super.layout() + checkbox.centerY(x: 10) + } + + func update(by image: CGImage?) -> Void { + if let image = image { + let color = getAverageColor(NSImage(cgImage: image, size: image.backingSize)) + backgroundColor = color + } else { + backgroundColor = .blackTransparent + } + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + let rect = focus(title.0.size) + title.1.draw(NSMakeRect(frame.width - rect.width - 10, rect.minY, rect.width, rect.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: .clear) + } + + deinit { + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +private enum WallpaperPreviewState { + case color + case pattern + case normal +} + +private final class WallpaperAdditionColorView : View, TGModernGrowingDelegate { + func textViewHeightChanged(_ height: CGFloat, animated: Bool) { + + } + + func textViewEnterPressed(_ event: NSEvent) -> Bool { + return true + } + + func textViewTextDidChange(_ string: String) { + var filtered = String(string.unicodeScalars.filter {CharacterSet(charactersIn: "#0123456789abcdefABCDEF").contains($0)}).uppercased() + if string != filtered { + if filtered.isEmpty { + filtered = "#" + } else if filtered.first != "#" { + filtered = "#" + filtered + } + textView.setString(filtered) + } + if filtered.length == maxCharactersLimit(textView) { + let color = NSColor(hexString: filtered) + if let color = color { + colorChanged?(color) + } + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + background = theme.colors.background + textView.background = theme.colors.background + textView.textColor = theme.colors.text + } + + func textViewTextDidChangeSelectedRange(_ range: NSRange) { + + } + + func textViewDidPaste(_ pasteboard: NSPasteboard) -> Bool { + + let text = pasteboard.string(forType: .string) + if let text = text, let color = NSColor(hexString: text) { + defaultColor = color + } + return true + } + + func textViewSize(_ textView: TGModernGrowingTextView!) -> NSSize { + return textView.frame.size + } + + func textViewIsTypingEnabled() -> Bool { + return true + } + + func maxCharactersLimit(_ textView: TGModernGrowingTextView!) -> Int32 { + return 7 + } + + var defaultColor: NSColor = NSColor(hexString: "#FFFFFF")! { + didSet { + textView.setString(defaultColor.hexString) + colorBulb.backgroundColor = defaultColor + } + } + + var colorChanged: ((NSColor) -> Void)? = nil + + fileprivate let resetButton = ImageButton() + private let colorBulb: View = View(frame: NSMakeRect(0, 0, 14, 14)) + + let textView: TGModernGrowingTextView = TGModernGrowingTextView(frame: NSZeroRect, unscrollable: true) + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + + layer?.cornerRadius = frameRect.height / 2 + layer?.borderWidth = .borderSize + layer?.borderColor = theme.colors.border.cgColor + colorBulb.layer?.cornerRadius = 7 + textView.delegate = self + textView.setString("#") + textView.textFont = .normal(.text) + backgroundColor = theme.colors.background + textView.cursorColor = theme.colors.indicatorColor + resetButton.set(image: theme.icons.wallpaper_color_close, for: .Normal) + _ = resetButton.sizeToFit() + addSubview(resetButton) + addSubview(colorBulb) + + colorBulb.backgroundColor = defaultColor + textView.setBackgroundColor(theme.colors.background) + layout() + } + + override func change(size: NSSize, animated: Bool, _ save: Bool = true, removeOnCompletion: Bool = true, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = .easeOut, completion: ((Bool) -> Void)? = nil) { + super.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + resetButton.change(pos: NSMakePoint(frame.width - resetButton.frame.width - 5, resetButton.frame.minY), animated: animated, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction) + } + + override func layout() { + super.layout() + + colorBulb.centerY(x: 10) + textView.frame = NSMakeRect(colorBulb.frame.maxX + 3, 0, frame.width - resetButton.frame.width - 26, frame.height) + resetButton.centerY(x: frame.width - resetButton.frame.width - 5) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +enum WallpaperColorSelectMode : Equatable { + case single(NSColor) + case gradient(top: NSColor, bottom: NSColor, rotation: Int32?) +} +enum WallpaperResponder : Equatable { + case first + case second +} + +final class WallpaperColorPickerContainerView : View { + fileprivate let firstView:WallpaperAdditionColorView = WallpaperAdditionColorView(frame: NSMakeRect(0, 4, 200, 30)) + fileprivate var secondView:WallpaperAdditionColorView? + let colorPicker = WallpaperColorPickerView() + private let colorsContainer: View + fileprivate let addColorButton: ImageButton = ImageButton() + fileprivate let swapColors: ImageButton = ImageButton() + private(set) var mode: WallpaperColorSelectMode = .single(NSColor(hexString: "#ffffff")!) + + + required init(frame frameRect: NSRect) { + colorsContainer = View(frame: NSMakeRect(0, 0, frameRect.width, 38)) + super.init(frame: frameRect) + colorsContainer.addSubview(firstView) + colorsContainer.addSubview(addColorButton) + colorsContainer.addSubview(swapColors) + addSubview(colorPicker) + addSubview(colorsContainer) + swapColors.hideAnimated = true + updateLocalizationAndTheme(theme: theme) + + firstView.colorChanged = { [weak self] color in + guard let `self` = self else { return } + switch self.mode { + case .single: + self.colorChanged?(.single(color)) + case let .gradient(_, bottom, rotation): + self.colorChanged?(.gradient(top: color, bottom: bottom, rotation: rotation)) + } + } + firstView.resetButton.set(handler: { [weak self] _ in + if let secondView = self?.secondView { + self?.colorChanged?(.single(secondView.defaultColor)) + self?.colorPicker.colorChanged?(secondView.defaultColor) + } + }, for: .Click) + + swapColors.set(handler: { [weak self] _ in + guard let `self` = self, let secondView = self.secondView else { + return + } + let rotation: Int32? + switch self.mode { + case let .gradient(_, _, r): + if let r = r { + if r + 45 == 360 { + rotation = nil + } else { + rotation = r + 45 + } + } else { + rotation = 45 + } + default: + rotation = nil + } + + + self.colorChanged?(.gradient(top: self.firstView.defaultColor, bottom: secondView.defaultColor, rotation: rotation)) + switch self.currentResponder { + case .first: + self.colorPicker.colorChanged?(self.firstView.defaultColor) + case .second: + self.colorPicker.colorChanged?(secondView.defaultColor) + } + + }, for: .Click) + } + + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + let theme = theme as! TelegramPresentationTheme + colorsContainer.backgroundColor = theme.colors.grayBackground + colorsContainer.border = [.Top, .Bottom] + colorsContainer.borderColor = theme.colors.border + backgroundColor = theme.colors.background + + swapColors.set(image: theme.icons.wallpaper_color_rotate, for: .Normal) + _ = swapColors.sizeToFit() + + addColorButton.set(image: theme.icons.wallpaper_color_add, for: .Normal) + _ = addColorButton.sizeToFit() + } + + func updateMode(_ mode: WallpaperColorSelectMode, animated: Bool) { + if self.mode != mode { + self.mode = mode + switch mode { + case let .single(color): + firstView.defaultColor = color + firstView.resetButton.isHidden = true + self.swapColors.isHidden = true + if let secondView = secondView { + self.secondView = nil + if animated { + secondView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak secondView] _ in + secondView?.removeFromSuperview() + }) + secondView.layer?.animatePosition(from: secondView.frame.origin, to: NSMakePoint(colorsContainer.frame.width, secondView.frame.minY), removeOnCompletion: false) + } else { + secondView.removeFromSuperview() + } + } + case let .gradient(top, bottom, rotation): + firstView.defaultColor = top + firstView.resetButton.isHidden = false + self.swapColors.isHidden = false + if secondView == nil { + secondView = WallpaperAdditionColorView(frame: NSMakeRect(colorsContainer.frame.width, 4, 200, 30)) + secondView?.resetButton.isHidden = false + secondView?.resetButton.set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + self.colorChanged?(.single(self.firstView.defaultColor)) + self.colorPicker.colorChanged?(self.firstView.defaultColor) + }, for: .Click) + + colorsContainer.addSubview(secondView!) + window?.makeFirstResponder(secondView?.textView.inputView) + secondView!.colorChanged = { [weak self] color in + guard let `self` = self else { return } + switch self.mode { + case .single: + fatalError() + case let .gradient(top, _, rotation): + self.colorChanged?(.gradient(top: top, bottom: color, rotation: rotation)) + } + } + } + secondView?.defaultColor = bottom + } + updateFrame(animated: animated) + } + } + + var canUseGradient: Bool = false { + didSet { + addColorButton.isHidden = !self.canUseGradient + updateFrame(animated: false) + } + } + + var currentResponder: WallpaperResponder { + if window?.firstResponder == firstView.textView.inputView { + return .first + } + if window?.firstResponder == secondView?.textView.inputView { + return .second + } + return .first + } + + var colorChanged: ((WallpaperColorSelectMode) -> Void)? = nil + + private func updateFrame(animated: Bool) { + switch self.mode { + case .gradient: + firstView.change(size: NSMakeSize(floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.width - (30 + swapColors.frame.width)) / 2) , firstView.frame.height), animated: animated) + secondView!.change(size: NSMakeSize(floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.width - (30 + swapColors.frame.width)) / 2), secondView!.frame.height), animated: animated) + firstView.change(pos: NSMakePoint(10, floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.height - firstView.frame.height) / 2)), animated: animated) + secondView!.change(pos: NSMakePoint(firstView.frame.maxX + 10 + swapColors.frame.width, floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.height - secondView!.frame.height) / 2)), animated: animated) + addColorButton.isHidden = true + swapColors.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.width - swapColors.frame.width) / 2), floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.height - swapColors.frame.height) / 2)), animated: animated) + case .single: + addColorButton.centerY(x: colorsContainer.frame.width - addColorButton.frame.width - 10) + firstView.change(size: NSMakeSize(colorsContainer.frame.width - 20 - (canUseGradient ? addColorButton.frame.width + 10 : 0), firstView.frame.height), animated: animated) + firstView.change(pos: NSMakePoint(10, floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.height - firstView.frame.height) / 2)), animated: animated) + swapColors.change(pos: NSMakePoint(colorsContainer.frame.width, floorToScreenPixels(backingScaleFactor, (colorsContainer.frame.height - swapColors.frame.height) / 2)), animated: animated) + addColorButton.isHidden = !canUseGradient + } + } + + override func layout() { + colorPicker.frame = NSMakeRect(0, 38, frame.width, frame.height - 38) + colorsContainer.frame = NSMakeRect(0, 0, frame.width, 38) + self.updateFrame(animated: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +private final class WallpaperPreviewView: View { + private let updateStateDisposable = MetaDisposable() + private let backgroundView: BackgroundView = BackgroundView(frame: NSZeroRect) + private let imageView: TransformImageView = TransformImageView() + let magnifyView: MagnifyView + private let disposable = MetaDisposable() + private var progressView: RadialProgressView? + private let tableView: TableView + private let documentView: NSView + let colorPicker = WallpaperColorPickerContainerView(frame: NSZeroRect) + let blurCheckbox: ApplyblurCheckbox = ApplyblurCheckbox(frame: NSMakeRect(0, 0, 70, 28), title: L10n.wallpaperPreviewBlurred) + let patternCheckbox: ApplyblurCheckbox = ApplyblurCheckbox(frame: NSMakeRect(0, 0, 70, 28), title: L10n.chatWPPattern) + let colorCheckbox: ApplyblurCheckbox = ApplyblurCheckbox(frame: NSMakeRect(0, 0, 70, 28), title: L10n.chatWPColor) + let checkboxContainer: View = View() + let patternsController: WallpaperPatternPreviewController + private var previewState: WallpaperPreviewState = .normal + private var imageSize: NSSize = NSZeroSize + private let context: AccountContext + private(set) var wallpaper: Wallpaper { + didSet { + if oldValue != wallpaper { + let signal = Signal.complete() |> delay(0.05, queue: .mainQueue()) + updateStateDisposable.set(signal.start(completed: { [weak self] in + self?.updateState(synchronousLoad: false) + })) + } + } + } + + override func smartMagnify(with event: NSEvent) { + magnifyView.smartMagnify(with: event) + } + + override func magnify(with event: NSEvent) { + magnifyView.magnify(with: event) + } + + required init(frame frameRect: NSRect, context: AccountContext, wallpaper: Wallpaper) { + self.context = context + self.wallpaper = wallpaper + self.tableView = TableView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height), isFlipped: false, drawBorder: false) + self.magnifyView = MagnifyView(imageView, contentSize: frameRect.size) + self.documentView = tableView.documentView! + self.patternsController = WallpaperPatternPreviewController(context: context) + super.init(frame: frameRect) + addSubview(backgroundView) + addSubview(patternsController.view) + addSubview(magnifyView) + documentView.removeFromSuperview() + addSubview(documentView) + checkboxContainer.addSubview(blurCheckbox) + checkboxContainer.addSubview(patternCheckbox) + checkboxContainer.addSubview(colorCheckbox) + addSubview(checkboxContainer) + addSubview(colorPicker) + addSubview(patternsController.view) + imageView.layer?.contentsGravity = .resizeAspectFill + + colorPicker.canUseGradient = true + + colorPicker.addColorButton.set(handler: { [weak self] _ in + guard let `self` = self else { return } + switch self.colorPicker.mode { + case let .single(color): + switch self.wallpaper { + case let .file(_, _, settings, _): + self.wallpaper = self.wallpaper.withUpdatedSettings(WallpaperSettings(blur: settings.blur, motion: settings.motion, color: color.argb, bottomColor: color.darker(amount: 0.3).argb, intensity: settings.intensity, rotation: settings.rotation)) + default: + self.wallpaper = .gradient(color.argb, color.darker(amount: 0.3).argb, nil) + } + self.colorPicker.updateMode(.gradient(top: color, bottom: color.darker(amount: 0.5), rotation: nil), animated: true) + case .gradient: + fatalError() + } + }, for: .Click) + + patternCheckbox.checkbox.isFullFilled = true + colorCheckbox.checkbox.isFullFilled = true + + tableView.backgroundColor = .clear + tableView.layer?.backgroundColor = .clear + + addTableItems(context) + + imageView.imageUpdated = { [weak self] image in + self?.blurCheckbox.update(by: image != nil ? (image as! CGImage) : nil) + self?.colorCheckbox.update(by: image != nil ? (image as! CGImage) : nil) + self?.patternCheckbox.update(by: image != nil ? (image as! CGImage) : nil) + } + + blurCheckbox.onChangedValue = { [weak self] isSelected in + guard let `self` = self else { return } + self.wallpaper = self.wallpaper.withUpdatedBlurrred(isSelected) + } + + colorCheckbox.onChangedValue = { [weak self] isSelected in + guard let `self` = self else { return } + switch self.previewState { + case .color: + self.updateModifyState(.normal, animated: true) + default: + self.updateModifyState(.color, animated: true) + } + } + + patternCheckbox.onChangedValue = { [weak self] isSelected in + guard let `self` = self else { return } + switch self.previewState { + case .pattern: + self.updateModifyState(.normal, animated: true) + default: + self.updateModifyState(.pattern, animated: true) + } + } + + switch wallpaper { + case let .color(color): + colorPicker.colorPicker.color = NSColor(UInt32(color)) + colorPicker.updateMode(.single(colorPicker.colorPicker.color), animated: false) + patternsController.color = ([colorPicker.colorPicker.color], nil) + case let .file(_, _, settings, _): + colorPicker.colorPicker.color = settings.color != nil ? NSColor(UInt32(settings.color!)) : NSColor(hexString: "#ffffff")! + colorPicker.updateMode(.single(colorPicker.colorPicker.color), animated: false) + case let .gradient(t, b, r): + let top = NSColor(UInt32(t)) + let bottom = NSColor(UInt32(b)) + colorPicker.colorPicker.color = top + colorPicker.updateMode(.gradient(top: top, bottom: bottom, rotation: r), animated: false) + patternsController.color = ([top, bottom], r) + + default: + break + } + + colorPicker.colorPicker.colorChanged = { [weak self] color in + guard let `self` = self else {return} + switch self.colorPicker.mode { + case .single: + switch self.wallpaper { + case let .file(_, _, settings, _): + self.wallpaper = self.wallpaper.withUpdatedSettings(settings.withUpdatedColor(color.argb)) + self.colorPicker.updateMode(.single(color), animated: true) + default: + self.wallpaper = .color(color.rgb) + self.colorPicker.updateMode(.single(color), animated: true) + } + self.patternsController.color = ([color], nil) + case let .gradient(top, bottom, rotation): + switch self.colorPicker.currentResponder { + case .first: + switch self.wallpaper { + case let .file(_, _, settings, _): + self.wallpaper = self.wallpaper.withUpdatedSettings(WallpaperSettings(blur: settings.blur, motion: settings.motion, color: color.argb, bottomColor: bottom.argb, intensity: settings.intensity, rotation: rotation)) + default: + self.wallpaper = .gradient(color.argb, bottom.argb, rotation) + } + self.colorPicker.updateMode(.gradient(top: color, bottom: bottom, rotation: rotation), animated: true) + self.patternsController.color = ([color, bottom], rotation) + case .second: + switch self.wallpaper { + case let .file(_, _, settings, _): + self.wallpaper = self.wallpaper.withUpdatedSettings(WallpaperSettings(blur: settings.blur, motion: settings.motion, color: top.argb, bottomColor: color.argb, intensity: settings.intensity, rotation: rotation)) + default: + self.wallpaper = .gradient(top.argb, color.argb, rotation) + } + self.colorPicker.updateMode(.gradient(top: top, bottom: color, rotation: rotation), animated: true) + self.patternsController.color = ([top, color], rotation) + } + if let rotation = rotation { + if let layer = self.colorPicker.swapColors.layer, let animatorLayer = self.colorPicker.swapColors.animator().layer { + layer.position = CGPoint(x: layer.frame.midX, y: layer.frame.midY) + layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) + + NSAnimationContext.beginGrouping() + NSAnimationContext.current.allowsImplicitAnimation = true + animatorLayer.transform = CATransform3DMakeRotation(CGFloat.pi * CGFloat(rotation) / 180.0, 0, 0, 1) + NSAnimationContext.endGrouping() + + } + } + } + + } + + colorPicker.colorChanged = { [weak self] mode in + guard let `self` = self else {return} + switch mode { + case let .single(color): + self.patternsController.color = ([color], nil) + case let .gradient(top, bottom, rotation): + self.patternsController.color = ([top, bottom], rotation) + } + + self.colorPicker.updateMode(mode, animated: true) + } + + + + patternsController.selected = { [weak self] wallpaper in + guard let `self` = self else {return} + if let wallpaper = wallpaper { + switch self.wallpaper { + case let .color(color): + self.wallpaper = wallpaper.withUpdatedSettings(WallpaperSettings(color: color, intensity: self.patternsController.intensity)) + case let .gradient(t, b, r): + self.wallpaper = wallpaper.withUpdatedSettings(WallpaperSettings(color: NSColor(argb: t).withAlphaComponent(1.0).argb, bottomColor: NSColor(argb: b).withAlphaComponent(1.0).argb, intensity: self.patternsController.intensity, rotation: r)) + case let .file(_, _, settings, _): + self.wallpaper = wallpaper.withUpdatedSettings(WallpaperSettings(color: settings.color, bottomColor: settings.bottomColor, intensity: self.patternsController.intensity, rotation: settings.rotation)) + default: + break + } + } else { + switch self.wallpaper { + case .color: + break + case let .file(_, _, settings, _): + if let color = settings.color, settings.bottomColor == nil { + self.wallpaper = Wallpaper.color(color) + } else if let t = settings.color, let b = settings.bottomColor { + self.wallpaper = Wallpaper.gradient(t, b, nil) + } + default: + break + } + } + } + + + tableView.addScroll(listener: TableScrollListener(dispatchWhenVisibleRangeUpdated: false, { [weak self] position in + guard let `self` = self else { + return + } + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + })) + + self.layout() + updateState(synchronousLoad: true) + } + + private func addTableItems(_ context: AccountContext) { + + + switch wallpaper { + case .color: + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: 50, stableId: 0, backgroundColor: .clear)) + case .file(_, _, _, _): + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: 50, stableId: 0, backgroundColor: .clear)) + default: + _ = tableView.addItem(item: GeneralRowItem(frame.size, height: 50, stableId: 0, backgroundColor: .clear)) + } + + let chatInteraction = ChatInteraction(chatLocation: .peer(PeerId(0)), context: context, disableSelectAbility: true) + + chatInteraction.getGradientOffsetRect = { [weak self] in + guard let `self` = self else { + return .zero + } + return CGRect(origin: NSMakePoint(0, self.documentView.frame.height), size: self.documentView.frame.size) + } + + + let fromUser1 = TelegramUser(id: PeerId(1), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName1, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let fromUser2 = TelegramUser(id: PeerId(2), accessHash: nil, firstName: L10n.appearanceSettingsChatPreviewUserName2, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + + let firstText: String + let secondText: String + switch wallpaper { + case let .file(_, _, _, isPattern): + if isPattern { + firstText = L10n.chatWPColorFirstMessage + secondText = L10n.chatWPColorSecondMessage + } else { + firstText = L10n.chatWPFirstMessage + secondText = L10n.chatWPSecondMessage + } + case .image: + firstText = L10n.chatWPFirstMessage + secondText = L10n.chatWPSecondMessage + default: + firstText = L10n.chatWPColorFirstMessage + secondText = L10n.chatWPColorSecondMessage + } + + let firstMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 0), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 20 + 60*60*18, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser2, text: firstText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let firstEntry: ChatHistoryEntry = .MessageEntry(firstMessage, MessageIndex(firstMessage), true, .bubble, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + let secondMessage = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: fromUser1.id, namespace: 0, id: 1), globallyUniqueId: 0, groupingKey: 0, groupInfo: nil, timestamp: 60 * 22 + 60*60*18, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: fromUser1, text: secondText, attributes: [], media: [], peers:SimpleDictionary([fromUser2.id : fromUser2, fromUser1.id : fromUser1]) , associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + let secondEntry: ChatHistoryEntry = .MessageEntry(secondMessage, MessageIndex(secondMessage), true, .bubble, .Full(rank: nil), nil, ChatHistoryEntryData(nil, MessageEntryAdditionalData(), AutoplayMediaPreferences.defaultSettings)) + + + let item1 = ChatRowItem.item(frame.size, from: firstEntry, interaction: chatInteraction, theme: theme) + let item2 = ChatRowItem.item(frame.size, from: secondEntry, interaction: chatInteraction, theme: theme) + + + + _ = tableView.addItem(item: item2) + _ = tableView.addItem(item: item1) + + } + + var croppedRect: NSRect { + + let fittedSize = WallpaperDimensions.aspectFitted(imageSize) + let multiplier = NSMakeSize( imageSize.width / magnifyView.contentFrame.width, imageSize.height / magnifyView.contentFrame.height) + let magnifyRect = magnifyView.contentFrame.apply(multiplier: multiplier) + let fittedRect = NSMakeRect(abs(magnifyRect.minX), abs(magnifyRect.minY), fittedSize.width, fittedSize.height) + + return fittedRect.offsetBy(dx:(magnifyView.contentFrame.minX - magnifyView.contentFrameMagnified.minX) * multiplier.width, dy: (magnifyView.contentFrame.minY - magnifyView.contentFrameMagnified.minY) * multiplier.height) + } + + deinit { + updateStateDisposable.dispose() + disposable.dispose() + } + + override func layout() { + super.layout() + + var checkboxWidth: CGFloat = blurCheckbox.isHidden ? 0 : blurCheckbox.frame.width + if !patternCheckbox.isHidden { + checkboxWidth += (checkboxWidth != 0 ? 10 + patternCheckbox.frame.width : patternCheckbox.frame.width) + } + if !colorCheckbox.isHidden { + checkboxWidth += (checkboxWidth != 0 ? 10 + colorCheckbox.frame.width : colorCheckbox.frame.width) + } + + checkboxContainer.setFrameSize(NSMakeSize(checkboxWidth, 28)) + + var point: NSPoint = NSZeroPoint + + blurCheckbox.setFrameOrigin(point) + if !colorCheckbox.isHidden { + colorCheckbox.setFrameOrigin(point) + point.x += colorCheckbox.frame.width + } + if point.x != 0 { + point.x += 10 + } + patternCheckbox.setFrameOrigin(point) + switch self.wallpaper { + case .color, .gradient, .file: + backgroundView.frame = NSMakeRect(0, 0, frame.width, frame.height - colorPicker.frame.height) + default: + backgroundView.frame = bounds + } + + magnifyView.frame = bounds + switch wallpaper { + case let .file(_, _, _, isPattern): + if isPattern { + magnifyView.contentSize = frame.size + } else { + magnifyView.contentSize = imageSize.aspectFilled(frame.size) + } + default: + magnifyView.contentSize = imageSize.aspectFilled(frame.size) + } + tableView.frame = bounds + documentView.setFrameSize(NSMakeSize(frame.width, documentView.frame.height)) + + self.progressView?.center() + colorPicker.setFrameSize(NSMakeSize(frame.width, 168)) + patternsController.view.setFrameSize(NSMakeSize(frame.width, 168)) + + + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: false) + } + }) + + + switch self.previewState { + case .color: + backgroundView.setFrameSize(NSMakeSize(frame.width, frame.height - colorPicker.frame.height)) + default: + backgroundView.setFrameSize(NSMakeSize(frame.width, frame.height)) + } + + switch previewState { + case .color: + colorPicker.setFrameOrigin(NSMakePoint(0, frame.height - colorPicker.frame.height)) + documentView.setFrameOrigin(NSMakePoint(0, frame.height - colorPicker.frame.height - tableView.listHeight)) + checkboxContainer.setFrameOrigin(NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - colorPicker.frame.height - checkboxContainer.frame.height - 10)) + patternsController.view.setFrameOrigin(NSMakePoint(0, frame.height)) + case .normal: + checkboxContainer.setFrameOrigin(NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - checkboxContainer.frame.height - 10)) + documentView.setFrameOrigin(NSMakePoint(0, frame.height - tableView.listHeight)) + colorPicker.setFrameOrigin(NSMakePoint(0, frame.height)) + patternsController.view.setFrameOrigin(NSMakePoint(0, frame.height)) + case .pattern: + colorPicker.setFrameOrigin(NSMakePoint(0, frame.height)) + documentView.setFrameOrigin(NSMakePoint(0, frame.height - patternsController.view.frame.height - tableView.listHeight)) + checkboxContainer.setFrameOrigin(NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - patternsController.view.frame.height - checkboxContainer.frame.height - 10)) + patternsController.view.setFrameOrigin(NSMakePoint(0, frame.height - patternsController.view.frame.height)) + } + + } + + + func updateModifyState(_ state: WallpaperPreviewState, animated: Bool) { + + switch state { + case .color: + backgroundView.change(size: NSMakeSize(frame.width, frame.height - colorPicker.frame.height), animated: animated) + default: + backgroundView.change(size: NSMakeSize(frame.width, frame.height), animated: animated) + } + + self.previewState = state + switch state { + case .color: + patternCheckbox.isSelected = false + colorCheckbox.isSelected = true + colorPicker.change(pos: NSMakePoint(0, frame.height - colorPicker.frame.height), animated: animated) + documentView._change(pos: NSMakePoint(0, frame.height - colorPicker.frame.height - tableView.listHeight), animated: animated) + checkboxContainer.change(pos: NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - colorPicker.frame.height - checkboxContainer.frame.height - 10), animated: animated) + patternsController.view._change(pos: NSMakePoint(0, frame.height), animated: animated) + var rotation:Int32? = nil + + switch self.wallpaper { + case let .gradient(t, b, r): + colorPicker.updateMode(.gradient(top: NSColor(argb: t), bottom: NSColor(argb: b), rotation: r), animated: false) + rotation = r + case let .file(_, _, settings, _): + if let t = settings.color, let b = settings.bottomColor { + colorPicker.updateMode(.gradient(top: NSColor(argb: t), bottom: NSColor(argb: b), rotation: settings.rotation), animated: false) + rotation = settings.rotation + } else if let c = settings.color { + colorPicker.updateMode(.single(NSColor(argb: c)), animated: false) + } + case let .color(c): + colorPicker.updateMode(.single(NSColor(argb: c)), animated: false) + default: + break + } + + if let rotation = rotation { + if let layer = self.colorPicker.swapColors.layer { + layer.position = CGPoint(x: layer.frame.midX, y: layer.frame.midY) + layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) + layer.transform = CATransform3DMakeRotation(CGFloat.pi * CGFloat(rotation) / 180.0, 0, 0, 1) + } + } + + + updateBackground(wallpaper) + case .normal: + patternCheckbox.isSelected = false + colorCheckbox.isSelected = false + checkboxContainer.change(pos: NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - checkboxContainer.frame.height - 10), animated: animated) + documentView._change(pos: NSMakePoint(0, frame.height - tableView.listHeight), animated: animated) + colorPicker.change(pos: NSMakePoint(0, frame.height), animated: animated) + patternsController.view._change(pos: NSMakePoint(0, frame.height), animated: animated) + updateBackground(wallpaper) + case .pattern: + + if let selected = patternsController.pattern { + self.wallpaper = selected.withUpdatedSettings(self.wallpaper.settings) + } + +// if let pattern = patternsController.pattern { +// patternsController.pattern?.withUpdatedSettings(wallpaper.s) +// } else { +// patternsController.pattern = wallpaper +// } + patternCheckbox.isSelected = true + colorCheckbox.isSelected = false + colorPicker.change(pos: NSMakePoint(0, frame.height), animated: animated) + documentView._change(pos: NSMakePoint(0, frame.height - patternsController.view.frame.height - tableView.listHeight), animated: animated) + checkboxContainer.change(pos: NSMakePoint(focus(checkboxContainer.frame.size).minX, frame.height - patternsController.view.frame.height - checkboxContainer.frame.height - 10), animated: animated) + patternsController.view._change(pos: NSMakePoint(0, frame.height - patternsController.view.frame.height), animated: animated) + + + updateBackground(wallpaper) + + } + + self.tableView.enumerateVisibleViews(with: { view in + if let view = view as? ChatRowView { + view.updateBackground(animated: animated) + } + }) + } + + private func updateBackground(_ wallpaper: Wallpaper) { + switch wallpaper { + case .builtin: + backgroundView.backgroundMode = .plain + case let .color(color): + backgroundView.backgroundMode = .color(color: NSColor(UInt32(color))) + case let .gradient(t, b, r): + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + backgroundView.backgroundMode = .gradient(top: top, bottom: bottom, rotation: r) + case .image: + backgroundView.backgroundMode = .plain + case let .file(_, _, settings, isPattern): + if isPattern { + if let color = settings.color, settings.bottomColor == nil { + backgroundView.backgroundMode = .color(color: NSColor(UInt32(color))) + } else if let t = settings.color, let b = settings.bottomColor { + let top = NSColor(UInt32(t)) + let bottom = NSColor(UInt32(b)) + backgroundView.backgroundMode = .gradient(top: top, bottom: bottom, rotation: settings.rotation) + } + } else { + backgroundView.backgroundMode = .plain + } + default: + backgroundView.backgroundMode = .plain + } + } + + func updateState(synchronousLoad: Bool) { + let maximumSize: NSSize = WallpaperDimensions + var updatedStatusSignal: Signal? + + switch wallpaper { + case .builtin: + self.imageView.isHidden = false + blurCheckbox.isHidden = false + colorCheckbox.isHidden = true + patternCheckbox.isHidden = true + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: -1), representations: [], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(), boundingSize: CGSize(), intrinsicInsets: NSEdgeInsets()) + self.imageView.setSignal(signal: cachedMedia(media: media, arguments: arguments, scale: backingScaleFactor)) + self.imageView.setSignal(settingsBuiltinWallpaperImage(account: context.account, scale: backingScaleFactor)) + self.imageView.set(arguments: arguments) + case let .color(color): + self.imageView.isHidden = true + blurCheckbox.isHidden = true + colorCheckbox.isHidden = false + patternCheckbox.isHidden = false + let image = generateImage(NSMakeSize(1, 1), contextGenerator: { size, ctx in + ctx.setFillColor(NSColor(UInt32(color)).cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + }) + self.blurCheckbox.update(by: image) + self.colorCheckbox.update(by: image) + self.patternCheckbox.update(by: image) + case let .gradient(t, b, _): + self.imageView.isHidden = true + blurCheckbox.isHidden = true + colorCheckbox.isHidden = false + patternCheckbox.isHidden = false + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + let middle = top.blended(withFraction: 0.5, of: bottom)! + + let image = generateImage(NSMakeSize(1, 1), contextGenerator: { size, ctx in + ctx.setFillColor(middle.cgColor) + ctx.fill(NSMakeRect(0, 0, size.width, size.height)) + }) + self.blurCheckbox.update(by: image) + self.colorCheckbox.update(by: image) + self.patternCheckbox.update(by: image) + + case let .image(representations, settings): + self.imageView.isHidden = false + blurCheckbox.isHidden = false + colorCheckbox.isHidden = true + patternCheckbox.isHidden = true + let dimensions = largestImageRepresentation(representations)!.dimensions.size + let boundingSize = dimensions.fitted(maximumSize) + self.imageSize = dimensions + + self.imageView.setSignal(chatWallpaper(account: context.account, representations: representations, mode: .screen, isPattern: false, autoFetchFullSize: true, scale: backingScaleFactor, isBlurred: settings.blur, synchronousLoad: synchronousLoad), animate: true, synchronousLoad: synchronousLoad) + self.imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: boundingSize, boundingSize: WallpaperDimensions.aspectFilled(NSMakeSize(600, 600)), intrinsicInsets: NSEdgeInsets())) + + updatedStatusSignal = context.account.postbox.mediaBox.resourceStatus(largestImageRepresentation(representations)!.resource, approximateSynchronousValue: synchronousLoad) |> deliverOnMainQueue + magnifyView.maxMagnify = 3.0 + + case let .file(_, file, settings, isPattern): + self.imageView.isHidden = false + blurCheckbox.isHidden = isPattern + + colorCheckbox.isHidden = !isPattern + patternCheckbox.isHidden = !isPattern + var patternColor: TransformImageEmptyColor? = nil + + var representations:[TelegramMediaImageRepresentation] = [] + representations.append(contentsOf: file.previewRepresentations) + if let dimensions = file.dimensions { + representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource)) + } else { + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(maximumSize), resource: file.resource)) + } + + if isPattern { + var patternIntensity: CGFloat = 0.5 + if let intensity = settings.intensity { + patternIntensity = CGFloat(intensity) / 100.0 + } + if let color = settings.color, settings.bottomColor == nil { + patternColor = .color(NSColor(rgb: color, alpha: patternIntensity)) + } else if let t = settings.color, let b = settings.bottomColor { + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + patternColor = .gradient(top: top.withAlphaComponent(patternIntensity), bottom: bottom.withAlphaComponent(patternIntensity), rotation: settings.rotation) + } else { + patternColor = .color(NSColor(rgb: 0xd6e2ee, alpha: 0.5)) + } + } + + self.imageView.setSignal(chatWallpaper(account: context.account, representations: representations, file: file, mode: .thumbnail, isPattern: isPattern, autoFetchFullSize: true, scale: backingScaleFactor, isBlurred: settings.blur, synchronousLoad: synchronousLoad), animate: true, synchronousLoad: synchronousLoad) + + + magnifyView.maxMagnify = !isPattern ? 3.0 : 1.0 + + + + let dimensions = largestImageRepresentation(representations)!.dimensions.size + let boundingSize = dimensions.aspectFilled(frame.size) + self.imageSize = dimensions + + updatedStatusSignal = context.account.postbox.mediaBox.resourceStatus(largestImageRepresentation(representations)!.resource, approximateSynchronousValue: synchronousLoad) |> deliverOnMainQueue + self.imageView.set(arguments: TransformImageArguments(corners: ImageCorners(), imageSize: boundingSize, boundingSize: boundingSize, intrinsicInsets: NSEdgeInsets(), emptyColor: patternColor)) + default: + break + } + + updateBackground(self.wallpaper) + + + if let updatedStatusSignal = updatedStatusSignal { + disposable.set(updatedStatusSignal.start(next: { [weak self] status in + guard let `self` = self else { return } + switch status { + case let .Fetching(_, progress): + if self.progressView == nil { + self.progressView = RadialProgressView(theme: RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white), twist: true, size: NSMakeSize(40, 40)) + self.addSubview(self.progressView!) + self.progressView?.center() + } + self.progressView?.state = .ImpossibleFetching(progress: progress, force: false) + break + case .Local: + if let progressView = self.progressView { + progressView.state = .ImpossibleFetching(progress:1.0, force: false) + self.progressView = nil + progressView.layer?.animateAlpha(from: 1, to: 0, duration: 0.25, timingFunction: .linear, removeOnCompletion: false, completion: { [weak progressView] completed in + if completed { + progressView?.removeFromSuperview() + } + }) + } + + case .Remote: + break + } + })) + } else { + progressView?.removeFromSuperview() + progressView = nil + } + needsLayout = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +enum WallpaperSource { + case link(TelegramWallpaper) + case gallery(TelegramWallpaper) + case none +} + +private func cropWallpaperImage(_ image: CGImage, dimensions: NSSize, rect: NSRect, magnify: CGFloat, settings: WallpaperSettings?) -> CGImage { + let fittedSize = NSMakeSize(dimensions.width * magnify, dimensions.height * magnify)//WallpaperDimensions.aspectFitted(representation.dimensions) + + let image = generateImage(rect.size, contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + ctx.interpolationQuality = .high + ctx.setBlendMode(.normal) + let imageRect = NSMakeRect(-rect.minX, -rect.minY, fittedSize.width, fittedSize.height) + ctx.draw(image, in: imageRect) + }, opaque: false, scale: 1.0)! + + let fitted = WallpaperDimensions.aspectFitted(dimensions) + + return generateImage(fitted, contextGenerator: { size, ctx in + let imageRect = NSMakeRect(0, 0, fitted.width, fitted.height) + ctx.clear(imageRect) + if let settings = settings { + + var _patternColor: NSColor = NSColor(rgb: 0xd6e2ee, alpha: 0.5) + + var patternIntensity: CGFloat = 0.5 + if let color = settings.color { + if let intensity = settings.intensity { + patternIntensity = CGFloat(intensity) / 100.0 + } + _patternColor = NSColor(rgb: color, alpha: patternIntensity) + } + + let color = _patternColor.withAlphaComponent(1.0) + let intensity = _patternColor.alpha + + ctx.setBlendMode(.copy) + ctx.setFillColor(color.cgColor) + ctx.fill(imageRect) + + ctx.setBlendMode(.normal) + ctx.interpolationQuality = .high + + ctx.clip(to: imageRect, mask: image) + ctx.setFillColor(patternColor(for: color, intensity: intensity).cgColor) + ctx.fill(imageRect) + } else { + ctx.draw(image, in: imageRect) + } + + //ctx.draw(image, in: imageRect) + }, opaque: false, scale: 1.0)! + +} + +private func cropWallpaperIfNeeded(_ wallpaper: Wallpaper, account: Account, rect: NSRect, magnify: CGFloat = 1.0) -> Signal { + return Signal { subscriber in + + let disposable = MetaDisposable() + switch wallpaper { + case let .image(representations, _): + if let representation = largestImageRepresentation(representations), let resource = representation.resource as? LocalFileReferenceMediaResource { + if let image = NSImage(contentsOfFile: resource.localFilePath)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + + let fittedImage = cropWallpaperImage(image, dimensions: representation.dimensions.size, rect: rect, magnify: magnify, settings: nil) + + let options = NSMutableDictionary() + options.setValue(90 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + var result: [TelegramMediaImageRepresentation] = [] + let colorQuality: Float = 0.1 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + let mutableData: CFMutableData = NSMutableData() as CFMutableData + + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) { + CGImageDestinationAddImage(colorDestination, fittedImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let thumdResource = LocalFileMediaResource(fileId: arc4random64()) + account.postbox.mediaBox.storeResourceData(thumdResource.id, data: mutableData as Data) + result.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(fittedImage.backingSize.aspectFitted(NSMakeSize(90, 90))), resource: thumdResource)) + } + } + + let fittedDimensions = WallpaperDimensions.aspectFitted(representation.dimensions.size) + + disposable.set(putToTemp(image: NSImage(cgImage: fittedImage, size: fittedDimensions), compress: false).start(next: { path in + copyToClipboard(path) + let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) + result.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(fittedDimensions), resource: resource)) + + let wallpaper: Wallpaper = .image(result, settings: wallpaper.settings) + subscriber.putNext(wallpaper) + subscriber.putCompletion() + })) + } + } + case let .file(slug, file, settings, isPattern): + + let dimensions = file.dimensions?.size ?? WallpaperDimensions + if isPattern { + + let path = account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPatternWallpaperMaskRepresentation(size: nil)) + + if let image = NSImage.init(contentsOf: URL(fileURLWithPath: path)) { + let size = image.size.aspectFilled(WallpaperDimensions) + + let image = generateImage(size, contextGenerator: { size, ctx in + let imageRect = NSMakeRect(0, 0, size.width, size.height) + + let colors:[NSColor] + let color: NSColor + var intensity: CGFloat = 0.5 + + if let combinedColor = settings.color, settings.bottomColor == nil { + let combinedColor = NSColor(combinedColor) + if let i = settings.intensity { + intensity = CGFloat(i) / 100.0 + } + color = combinedColor.withAlphaComponent(1.0) + intensity = combinedColor.alpha + colors = [color] + } else if let t = settings.color, let b = settings.bottomColor { + let top = NSColor(argb: t) + let bottom = NSColor(argb: b) + color = top.withAlphaComponent(1.0) + if let i = settings.intensity { + intensity = CGFloat(i) / 100.0 + } + colors = [top, bottom].reversed().map { $0.withAlphaComponent(1.0) } + } else { + colors = [NSColor(rgb: 0xd6e2ee, alpha: 0.5)] + color = NSColor(rgb: 0xd6e2ee, alpha: 0.5) + } + + ctx.setBlendMode(.copy) + if colors.count == 1 { + ctx.setFillColor(color.cgColor) + ctx.fill(imageRect) + } else { + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + ctx.saveGState() + ctx.translateBy(x: imageRect.width / 2.0, y: imageRect.height / 2.0) + ctx.rotate(by: CGFloat(settings.rotation ?? 0) * CGFloat.pi / -180.0) + ctx.translateBy(x: -imageRect.width / 2.0, y: -imageRect.height / 2.0) + + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: imageRect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + ctx.restoreGState() + } + + + ctx.setBlendMode(.normal) + ctx.interpolationQuality = .medium + ctx.clip(to: imageRect, mask: image.cgImage(forProposedRect: nil, context: nil, hints: nil)!) + + if colors.count == 1 { + ctx.setFillColor(patternColor(for: color, intensity: intensity).cgColor) + ctx.fill(imageRect) + } else { + let gradientColors = colors.map { patternColor(for: $0, intensity: intensity).cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + ctx.translateBy(x: imageRect.width / 2.0, y: imageRect.height / 2.0) + ctx.rotate(by: CGFloat(settings.rotation ?? 0) * CGFloat.pi / -180.0) + ctx.translateBy(x: -imageRect.width / 2.0, y: -imageRect.height / 2.0) + + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: imageRect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + } + + })! + + disposable.set(putToTemp(image: NSImage(cgImage: image, size: size), compress: false).start(next: { path in + let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) + + var attributes = file.attributes + loop: for (i, attr) in attributes.enumerated() { + switch attr { + case .ImageSize: + attributes[i] = .ImageSize(size: PixelDimensions(size)) + break loop + default: + break + } + } + let wallpaper: Wallpaper = .file(slug: slug, file: file.withUpdatedResource(resource).withUpdatedAttributes(attributes), settings: settings, isPattern: isPattern) + subscriber.putNext(wallpaper) + subscriber.putCompletion() + })) + } + + subscriber.putNext(wallpaper.withUpdatedSettings(settings)) + subscriber.putCompletion() + } else { + if let path = account.postbox.mediaBox.completedResourcePath(file.resource), let image = NSImage(contentsOfFile: path)?.cgImage(forProposedRect: nil, context: nil, hints: nil) { + let fittedImage = cropWallpaperImage(image, dimensions: dimensions, rect: rect, magnify: magnify, settings: isPattern ? settings : nil) + + let options = NSMutableDictionary() + options.setValue(90 as NSNumber, forKey: kCGImageDestinationImageMaxPixelSize as String) + var result: [TelegramMediaImageRepresentation] = [] + let colorQuality: Float = 0.1 + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + let mutableData: CFMutableData = NSMutableData() as CFMutableData + + if let colorDestination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) { + CGImageDestinationAddImage(colorDestination, fittedImage, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + let thumdResource = LocalFileMediaResource(fileId: arc4random64()) + account.postbox.mediaBox.storeResourceData(thumdResource.id, data: mutableData as Data) + result.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(fittedImage.backingSize.aspectFitted(NSMakeSize(90, 90))), resource: thumdResource)) + } + } + + let fittedDimensions = WallpaperDimensions.aspectFitted(dimensions) + + disposable.set(putToTemp(image: NSImage(cgImage: fittedImage, size: fittedDimensions), compress: false).start(next: { path in + let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: arc4random64()) + + var attributes = file.attributes + loop: for (i, attr) in attributes.enumerated() { + switch attr { + case .ImageSize: + attributes[i] = .ImageSize(size: PixelDimensions(fittedDimensions)) + break loop + default: + break + } + } + + let wallpaper: Wallpaper = .file(slug: slug, file: file.withUpdatedPreviewRepresentations(result).withUpdatedResource(resource).withUpdatedAttributes(attributes), settings: settings, isPattern: isPattern) + subscriber.putNext(wallpaper) + subscriber.putCompletion() + })) + } + } + default: + subscriber.putNext(wallpaper) + subscriber.putCompletion() + } + + return ActionDisposable { + disposable.dispose() + } + } |> runOn(resourcesQueue) +} + + +class WallpaperPreviewController: ModalViewController { + + override func viewClass() -> AnyClass { + return WallpaperPreviewView.self + } + + override var handleAllEvents: Bool { + return false + } + + override func firstResponder() -> NSResponder? { + switch genericView.colorPicker.currentResponder { + case .first: + return genericView.colorPicker.firstView.textView.inputView + case .second: + return genericView.colorPicker.secondView?.textView.inputView + } + } + + private let wallpaper: Wallpaper + private let context: AccountContext + + let source: WallpaperSource + + init(_ context: AccountContext, wallpaper: Wallpaper, source: WallpaperSource) { + self.wallpaper = wallpaper.isSemanticallyEqual(to: theme.wallpaper.wallpaper) ? wallpaper.withUpdatedBlurrred(theme.wallpaper.wallpaper.isBlurred) : wallpaper + self.context = context + self.source = source + super.init(frame: NSMakeRect(0, 0, 380, 300)) + bar = .init(height: 0) + } + public override var modalHeader: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + let hasShare: Bool + switch self.wallpaper { + case .color, .gradient, .file: + hasShare = true + default: + hasShare = false + } + + return (left: ModalHeaderData.init(image: theme.icons.modalClose, handler: { [weak self] in + self?.close() + }), center: ModalHeaderData(title: L10n.wallpaperPreviewHeader), right: !hasShare ? nil : ModalHeaderData(image: theme.icons.modalShare, handler: { [weak self] in + self?.share() + })) + } + + private func share() { + //close() + + switch genericView.wallpaper { + case let .file(slug, _, settings, isPattern): + var options: [String] = [] + if settings.blur { + options.append("mode=blur") + } + + if isPattern { + if let pattern = settings.color { + var color = NSColor(argb: pattern).hexString.lowercased() + color = String(color[color.index(after: color.startIndex) ..< color.endIndex]) + var bg = "bg_color=\(color)" + if let bottomColor = settings.bottomColor { + var color = NSColor(argb: bottomColor).hexString.lowercased() + color = String(color[color.index(after: color.startIndex) ..< color.endIndex]) + bg = "\(bg)-\(color)" + } + options.append(bg) + } + if let intensity = settings.intensity { + options.append("intensity=\(intensity)") + } else { + options.append("intensity=\(50)") + } + if let r = settings.rotation { + options.append("rotation=\(r)") + } + } + + var optionsString = "" + if !options.isEmpty { + optionsString = "?\(options.joined(separator: "&"))" + } + + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/bg/\(slug)\(optionsString)")), for: context.window) + case let .color(color): + var color = NSColor(argb: color).hexString.lowercased() + color = String(color[color.index(after: color.startIndex) ..< color.endIndex]) + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/bg/\(color)")), for: context.window) + case let .gradient(t, b, r): + let top = NSColor(argb: t).hexString.lowercased() + let bottom = NSColor(argb: b).hexString.lowercased() + + let tcut = String(top[top.index(after: top.startIndex) ..< top.endIndex]) + let bcut = String(bottom[bottom.index(after: bottom.startIndex) ..< bottom.endIndex]) + + var rotation: String = "" + if let r = r { + rotation = "&rotation=\(r)" + } + + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/bg/\(tcut)-\(bcut)" + rotation)), for: context.window) + + + default: + break + } + + } + + override func viewDidLoad() { + super.viewDidLoad() + genericView.blurCheckbox.isSelected = wallpaper.isBlurred + + switch wallpaper { + case let .color(color): + genericView.patternsController.color = ([NSColor(argb: color)], nil) + case let .gradient(t, b, r): + genericView.patternsController.color = ([NSColor(argb: t), NSColor(argb: b)], r) + case let .file(_, _, settings, isPattern): + if isPattern { + var colors:[NSColor] = [] + if let t = settings.color { + colors.append(NSColor(argb: t)) + } + if let b = settings.bottomColor { + colors.append(NSColor(argb: b)) + } + if colors.isEmpty { + colors.append(NSColor(rgb: 0xd6e2ee, alpha: 0.5)) + } + genericView.patternsController.color = (colors, settings.rotation) + } + default: + break + } + + readyOnce() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + window?.removeAllHandlers(for: self) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.magnifyView.zoomOut() + return .invoked + }, with: self, for: .Minus, priority: .modal) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + self?.genericView.magnifyView.zoomIn() + return .invoked + }, with: self, for: .Equal, priority: .modal) + } + + + private func applyAndClose() { + let context = self.context + closeAllModals() + + let signal = cropWallpaperIfNeeded(genericView.wallpaper, account: context.account, rect: genericView.croppedRect, magnify: genericView.magnifyView.magnify) |> mapToSignal { wallpaper in + return moveWallpaperToCache(postbox: context.account.postbox, wallpaper: wallpaper) + } + + _ = showModalProgress(signal: signal, for: context.window).start(next: { wallpaper in + _ = (updateThemeInteractivetly(accountManager: context.sharedContext.accountManager, f: { settings in + return settings.updateWallpaper { $0.withUpdatedWallpaper(wallpaper) }.saveDefaultWallpaper().withSavedAssociatedTheme().withUpdatedBubbled(true) + }) |> deliverOnMainQueue).start(completed: { + var stats:[Signal] = [] + switch self.source { + case let .gallery(wallpaper): + stats = [installWallpaper(account: context.account, wallpaper: wallpaper)] + case let .link(wallpaper): + stats = [installWallpaper(account: context.account, wallpaper: wallpaper), saveWallpaper(account: context.account, wallpaper: wallpaper)] + case .none: + break + } + let _ = combineLatest(stats).start() + }) + }) + + } + + override var modalInteractions: ModalInteractions? { + return ModalInteractions(acceptTitle: L10n.wallpaperPreviewApply, accept: { [weak self] in + self?.applyAndClose() + }, drawBorder: true, height: 50, singleButton: true) + } + override func initializer() -> NSView { + return WallpaperPreviewView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), context: context, wallpaper: wallpaper); + } + + override var dynamicSize: Bool { + return true + } + + override func measure(size: NSSize) { + let chatSize = NSMakeSize(context.sharedContext.bindings.rootNavigation().frame.width, min(500, size.height - 150)) + let contentSize = WallpaperDimensions.aspectFitted(chatSize) + + self.modal?.resize(with: contentSize, animated: false) + } + + func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, contentSize.height - 150), animated: animated) + } + } + + private var genericView: WallpaperPreviewView { + return self.view as! WallpaperPreviewView + } + +} diff --git a/Telegram-Mac/Wallpapers.swift b/Telegram-Mac/Wallpapers.swift new file mode 100644 index 0000000000..6fc8fdb519 --- /dev/null +++ b/Telegram-Mac/Wallpapers.swift @@ -0,0 +1,147 @@ +// +// Wallpapers.swift +// Telegram +// +// Created by Mikhail Filimonov on 11/01/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +// +//public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { +// case none +// case builtin +// case color(Int32) +// case image([TelegramMediaImageRepresentation]) +// case custom(String) +// public init(decoder: PostboxDecoder) { +// switch decoder.decodeInt32ForKey("v", orElse: 0) { +// case 0: +// self = .builtin +// case 1: +// self = .color(decoder.decodeInt32ForKey("c", orElse: 0)) +// case 2: +// self = .image(decoder.decodeObjectArrayWithDecoderForKey("i")) +// case 3: +// self = .none +// case 4: +// self = .custom(decoder.decodeStringForKey("p", orElse: "")) +// default: +// assertionFailure() +// self = .none +// } +// } +// +// var hasWallpaper: Bool { +// switch self { +// case .none: +// return false +// case .color: +// return false +// default: +// return true +// } +// } +// +// public func encode(_ encoder: PostboxEncoder) { +// switch self { +// case .builtin: +// encoder.encodeInt32(0, forKey: "v") +// case let .color(color): +// encoder.encodeInt32(1, forKey: "v") +// encoder.encodeInt32(color, forKey: "c") +// case let .image(representations): +// encoder.encodeInt32(2, forKey: "v") +// encoder.encodeObjectArray(representations, forKey: "i") +// case .none: +// encoder.encodeInt32(3, forKey: "v") +// case let .custom(path): +// encoder.encodeInt32(4, forKey: "v") +// encoder.encodeString(path, forKey: "p") +// } +// } +// +// public static func ==(lhs: TelegramWallpaper, rhs: TelegramWallpaper) -> Bool { +// switch lhs { +// case .builtin: +// if case .builtin = rhs { +// return true +// } else { +// return false +// } +// case .none: +// if case .none = rhs { +// return true +// } else { +// return false +// } +// case let .color(color): +// if case .color(color) = rhs { +// return true +// } else { +// return false +// } +// case let .custom(path): +// if case .custom(path) = rhs { +// return true +// } else { +// return false +// } +// case let .image(lhsRepresentations): +// if case let .image(rhsRepresentations) = rhs, lhsRepresentations == rhsRepresentations { +// return true +// } else { +// return false +// } +// } +// } +//} +// +//func telegramWallpapers(account: Account) -> Signal<[TelegramWallpaper], NoError> { +// return account.postbox.transaction { transaction -> [TelegramWallpaper] in +// let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers) +// if items.count == 0 { +// return [.none, .builtin] +// } else { +// return items.map { $0.contents as! TelegramWallpaper } +// } +// } |> mapToSignal { list -> Signal<[TelegramWallpaper], NoError> in +// let remote = account.network.request(Api.functions.account.getWallPapers()) +// |> retryRequest +// |> mapToSignal { result -> Signal<[TelegramWallpaper], NoError> in +// var items: [TelegramWallpaper] = [] +// for item in result { +// switch item { +// case let .wallPaper(_, _, sizes, color): +// items.append(.image(telegramMediaImageRepresentationsFromApiSizes(sizes))) +// case let .wallPaperSolid(_, _, bgColor, color): +// items.append(.color(bgColor)) +// } +// } +// items.removeFirst() +// items.insert(.none, at: 0) +// items.insert(.builtin, at: 1) +// +// if items == list { +// return .complete() +// } else { +// return account.postbox.transaction { transaction -> [TelegramWallpaper] in +// var entries: [OrderedItemListEntry] = [] +// for item in items { +// var intValue = Int32(entries.count) +// let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) +// entries.append(OrderedItemListEntry(id: id, contents: item)) +// } +// transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers, items: entries) +// +// return items +// } +// } +// } +// return .single(list) |> then(remote) +// } +//} diff --git a/Telegram-Mac/WebAuthorizationRowItem.swift b/Telegram-Mac/WebAuthorizationRowItem.swift new file mode 100644 index 0000000000..27772096bc --- /dev/null +++ b/Telegram-Mac/WebAuthorizationRowItem.swift @@ -0,0 +1,178 @@ +// +// WebAuthorizationRowItem.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import TelegramCore +import SyncCore +import Postbox + +class WebAuthorizationRowItem: GeneralRowItem { + + fileprivate let account: Account + fileprivate let nameLayout: TextViewLayout + fileprivate let photo: AvatarNodeState + fileprivate let statusLayout: TextViewLayout + fileprivate let dateLayout: TextViewLayout + fileprivate let logoutInteraction:()->Void + init(_ initialSize: NSSize, stableId: AnyHashable, account: Account, authorization: WebAuthorization, peer: Peer, viewType: GeneralViewType, logout:@escaping()->Void) { + self.logoutInteraction = logout + self.account = account + self.photo = .PeerAvatar(peer, peer.displayLetters, peer.smallProfileImage, nil) + self.nameLayout = TextViewLayout(.initialize(string: peer.displayTitle, color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1) + let statusAttr = NSMutableAttributedString() + + _ = statusAttr.append(string: authorization.domain, color: theme.colors.text, font: .normal(.text)) + _ = statusAttr.append(string: ", ", color: theme.colors.grayText) + _ = statusAttr.append(string: authorization.browser, color: theme.colors.text, font: .normal(.text)) + _ = statusAttr.append(string: ", ", color: theme.colors.grayText) + _ = statusAttr.append(string: authorization.platform, color: theme.colors.text, font: .normal(.text)) + + _ = statusAttr.append(string: "\n") + + _ = statusAttr.append(string: authorization.ip, color: theme.colors.grayText, font: .normal(.text)) + _ = statusAttr.append(string: " ● ", color: theme.colors.grayText) + _ = statusAttr.append(string: authorization.region, color: theme.colors.grayText, font: .normal(.text)) + + self.statusLayout = TextViewLayout(statusAttr, maximumNumberOfLines: 2) + self.dateLayout = TextViewLayout(.initialize(string: DateUtils.string(forMessageListDate: authorization.dateActive), color: theme.colors.grayText, font: .normal(.text))) + super.init(initialSize, height: 80, stableId: stableId, viewType: viewType, inset: NSEdgeInsetsMake(0, 30, 0, 30)) + _ = makeSize(initialSize.width, oldWidth: 0) + } + + override func makeSize(_ width: CGFloat, oldWidth: CGFloat) -> Bool { + let success = super.makeSize(width, oldWidth: oldWidth) + dateLayout.measure(width: .greatestFiniteMagnitude) + nameLayout.measure(width: width - (inset.left + inset.right) - 20 - dateLayout.layoutSize.width) + statusLayout.measure(width: width - (inset.left + inset.right)) + + return success + } + + override func viewClass() -> AnyClass { + return WebAuthorizationRowView.self + } + +} + + +private class WebAuthorizationRowView : TableRowView, ViewDisplayDelegate { + private let containerView = GeneralRowContainerView(frame: NSZeroRect) + private let botNameView: TextView = TextView() + private let statusTextView: TextView = TextView() + private let dateView: TextView = TextView() + private let photoView: AvatarControl = AvatarControl(font: .avatar(8)) + private let logoutButton: TitleButton = TitleButton() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + botNameView.isSelectable = false + botNameView.userInteractionEnabled = false + containerView.addSubview(botNameView) + containerView.addSubview(statusTextView) + containerView.addSubview(dateView) + containerView.addSubview(photoView) + containerView.addSubview(logoutButton) + photoView.setFrameSize(16, 16) + + + addSubview(containerView) + + containerView.displayDelegate = self + + logoutButton.set(handler: { [weak self] _ in + guard let item = self?.item as? WebAuthorizationRowItem else {return} + item.logoutInteraction() + }, for: .Click) + } + + override func layout() { + super.layout() + guard let item = item as? WebAuthorizationRowItem else {return} + + switch item.viewType { + case .legacy: + self.containerView.frame = bounds + self.containerView.setCorners([]) + photoView.setFrameOrigin(item.inset.left, item.inset.top + 2) + botNameView.setFrameOrigin(photoView.frame.maxX + 4, item.inset.top) + statusTextView.setFrameOrigin(item.inset.left, botNameView.frame.maxY + 4) + dateView.setFrameOrigin(self.containerView.frame.width - item.inset.right - dateView.frame.width, item.inset.top) + logoutButton.setFrameOrigin(self.containerView.frame.width - logoutButton.frame.width - 25, self.containerView.frame.height - logoutButton.frame.height - 10) + case let .modern(position, innerInsets): + self.containerView.frame = NSMakeRect(floorToScreenPixels(backingScaleFactor, (frame.width - item.blockWidth) / 2), item.inset.top, item.blockWidth, frame.height - item.inset.bottom - item.inset.top) + self.containerView.setCorners(position.corners) + photoView.setFrameOrigin(innerInsets.left, innerInsets.top + 2) + botNameView.setFrameOrigin(photoView.frame.maxX + 4, innerInsets.top) + statusTextView.setFrameOrigin(innerInsets.left, botNameView.frame.maxY + 8) + dateView.setFrameOrigin(self.containerView.frame.width - innerInsets.right - dateView.frame.width, innerInsets.top) + logoutButton.setFrameOrigin(self.containerView.frame.width - logoutButton.frame.width - innerInsets.right + 4, self.containerView.frame.height - logoutButton.frame.height - 10) + } + + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + guard let item = item as? WebAuthorizationRowItem, layer == containerView.layer else {return} + + ctx.setFillColor(theme.colors.border.cgColor) + + switch item.viewType { + case .legacy: + ctx.fill(NSMakeRect(item.inset.left, frame.height - .borderSize, frame.width - item.inset.left, .borderSize)) + case let .modern(position, insets): + if position.border { + ctx.fill(NSMakeRect(insets.left, containerView.frame.height - .borderSize, containerView.frame.width - insets.left, .borderSize)) + } + } + + } + + override func updateColors() { + guard let item = item as? WebAuthorizationRowItem else {return} + logoutButton.set(background: backdorColor, for: .Normal) + botNameView.backgroundColor = backdorColor + statusTextView.backgroundColor = backdorColor + dateView.backgroundColor = backdorColor + containerView.backgroundColor = backdorColor + self.background = item.viewType.rowBackground + } + + override var backdorColor: NSColor { + return theme.colors.background + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + + guard let item = item as? WebAuthorizationRowItem else {return} + + switch item.viewType { + case .legacy: + containerView.setCorners([], animated: animated) + case let .modern(position, _): + containerView.setCorners(position.corners, animated: animated) + } + + self.photoView.setState(account: item.account, state: item.photo) + self.botNameView.update(item.nameLayout) + self.statusTextView.update(item.statusLayout) + self.dateView.update(item.dateLayout) + + logoutButton.set(color: theme.colors.accent, for: .Normal) + logoutButton.set(font: .medium(.text), for: .Normal) + logoutButton.set(text: L10n.webAuthorizationsLogout, for: .Normal) + _ = logoutButton.sizeToFit() + + needsLayout = true + } +} diff --git a/Telegram-Mac/WebGameViewController.swift b/Telegram-Mac/WebGameViewController.swift index 151c245279..3f1bae54ca 100644 --- a/Telegram-Mac/WebGameViewController.swift +++ b/Telegram-Mac/WebGameViewController.swift @@ -9,9 +9,26 @@ import Cocoa import WebKit import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox + +private class WeakGameScriptMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + + fileprivate var weakGames:[WeakReference] = [] @@ -24,7 +41,7 @@ fileprivate func game(forKey:String) -> WebGameViewController? { return nil } -class WebGameViewController: TelegramGenericViewController, WebFrameLoadDelegate { +class WebGameViewController: TelegramGenericViewController, WKUIDelegate { private let gameUrl:String private let peerId:PeerId @@ -34,11 +51,11 @@ class WebGameViewController: TelegramGenericViewController, WebFrameLoa private let messageId:MessageId fileprivate let uniqueId:String = "_\(arc4random())" private let loadMessageDisposable = MetaDisposable() - init(_ account:Account, _ peerId:PeerId, _ messageId:MessageId, _ gameUrl:String) { + init(_ context: AccountContext, _ peerId:PeerId, _ messageId:MessageId, _ gameUrl:String) { self.gameUrl = gameUrl self.peerId = peerId self.messageId = messageId - super.init(account) + super.init(context) weakGames.append(WeakReference(value: self)) } @@ -52,9 +69,9 @@ class WebGameViewController: TelegramGenericViewController, WebFrameLoa override func getRightBarViewOnce() -> BarView { let view = ImageBarView(controller: self, theme.icons.webgameShare) - let account = self.account - view.button.set(handler: {_ in - showModal(with: ShareModalController(ShareLinkObject(account, link: "https://t.me/gamebot")), for: mainWindow) + + view.button.set(handler: { [weak self] _ in + self?.share_game("") }, for: .Click) view.set(image: theme.icons.webgameShare, highlightImage: nil) return view @@ -62,13 +79,14 @@ class WebGameViewController: TelegramGenericViewController, WebFrameLoa override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - genericView.mainFrame.load(URLRequest(url: URL(string:"file://blank")!)) - genericView.mainFrame.stopLoading() + genericView.load(URLRequest(url: URL(string:"file://blank")!)) + genericView.stopLoading() } override func viewDidLoad() { super.viewDidLoad() - loadMessageDisposable.set((account.postbox.messageAtId(messageId) |> deliverOnMainQueue).start(next: { [weak self] message in + genericView.wantsLayer = true + loadMessageDisposable.set((context.account.postbox.messageAtId(messageId) |> deliverOnMainQueue).start(next: { [weak self] message in if let message = message, let game = message.media.first as? TelegramMediaGame, let peer = message.inlinePeer { self?.start(with: game, peer: peer) } @@ -82,36 +100,67 @@ class WebGameViewController: TelegramGenericViewController, WebFrameLoa self.centerBarView.status = .initialize(string: "@\(peer.addressName ?? "gamebot")", color: theme.colors.grayText, font: .normal(.text)) if let url = URL(string:gameUrl) { - genericView.mainFrame.load(URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 15)) + genericView.load(URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 15)) } - genericView.frameLoadDelegate = self; + + + + + readyOnce() } - func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) { - frame.windowObject.evaluateWebScript("TelegramWebviewProxy = { postEvent:function(eventType, eventData) {gameHandler(eventType,eventData)}}") - JSGlobalContextSetName(frame.globalContext, JSStringCreateWithCFString(uniqueId as CFString!)); - let funcName = JSStringCreateWithUTF8CString("gameHandler"); - let funcObj = JSObjectMakeFunctionWithCallback(frame.globalContext, funcName, { (ctx, function, thisObject, argumentCount, arguments, exception) in - if let arguments = arguments, argumentCount == 2 && JSValueGetType (ctx, arguments[0]) == kJSTypeString && JSValueGetType (ctx, arguments[1]) == kJSTypeString { - let eventType = JSStringCopyCFString(kCFAllocatorDefault,JSValueToStringCopy (ctx, arguments[0],exception)) - let data = JSStringCopyCFString(kCFAllocatorDefault,JSValueToStringCopy (ctx, arguments[1],exception)) - let uniqueId = JSStringCopyCFString(kCFAllocatorDefault,JSGlobalContextCopyName(JSContextGetGlobalContext(ctx))) - - if let eventType = eventType as String?, let data = data as String?, let uniqueId = uniqueId as String?, let controller = game(forKey: uniqueId) { - let selector = NSSelectorFromString(eventType + ":") - if controller.responds(to: selector) { - controller.perform(selector, with: data) - } - } + + override func initializer() -> WKWebView { + + let js = "var TelegramWebviewProxyProto = function() {}; " + + "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + + "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + + "}; " + + "var TelegramWebviewProxy = new TelegramWebviewProxyProto();" + + let configuration = WKWebViewConfiguration() + let userController = WKUserContentController() + + let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) + userController.addUserScript(userScript) + + userController.add(WeakGameScriptMessageHandler { [weak self] message in + if let strongSelf = self { + strongSelf.handleScriptMessage(message) } - return JSValueMakeNull(ctx); - }); - JSObjectSetProperty(sender.mainFrame.globalContext, JSContextGetGlobalObject(frame.globalContext), funcName, funcObj, JSPropertyAttributes(kJSPropertyAttributeNone), nil); - JSStringRelease(funcName); - + }, name: "performAction") + + + configuration.userContentController = userController + + return WKWebView(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height), configuration: configuration) + } + + private func handleScriptMessage(_ message: WKScriptMessage) { + guard let body = message.body as? [String: Any] else { + return + } + + guard let eventName = body["eventName"] as? String else { + return + } + + switch eventName { + case "share_game": + self.share_game("") + case "share_score": + self.share_score("") + case "game_over": + self.game_over("") + case "game_loaded": + self.game_loaded("") + default: + break + } } + @objc func game_loaded(_ data:String) { @@ -120,10 +169,17 @@ class WebGameViewController: TelegramGenericViewController, WebFrameLoa } @objc func share_game(_ data:String) { - showModal(with: ShareModalController(ShareLinkObject(account, link: "https://t.me/gamebot")), for: mainWindow) + showModal(with: ShareModalController(ShareLinkObject(context, link: "https://t.me/\(self.peer.addressName ?? "gamebot")" + "?game=\(self.media.name)")), for: mainWindow) } @objc func share_score(_ data:String) { - showModal(with: ShareModalController(ShareLinkObject(account, link: "https://t.me/gamebot")), for: mainWindow) + + let context = self.context + let messageId = self.messageId + + showModal(with: ShareModalController(ShareCallbackObject(context, callback: { peerIds in + let signals = peerIds.map { forwardGameWithScore(account: context.account, messageId: messageId, to: $0) } + return combineLatest(signals) |> map { _ in return } |> ignoreValues + })), for: context.window) } diff --git a/Telegram-Mac/WebSessionsController.swift b/Telegram-Mac/WebSessionsController.swift new file mode 100644 index 0000000000..0039afadea --- /dev/null +++ b/Telegram-Mac/WebSessionsController.swift @@ -0,0 +1,330 @@ +// +// WebSessionsController.swift +// Telegram +// +// Created by Mikhail Filimonov on 12/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit +import TGUIKit + + +private final class WebSessionArguments { + let context: AccountContext + let logoutSession:(WebAuthorization)->Void + let logoutAll:()->Void + init(context: AccountContext, logoutSession:@escaping(WebAuthorization)->Void, logoutAll:@escaping()->Void) { + self.context = context + self.logoutAll = logoutAll + self.logoutSession = logoutSession + } +} + +private enum WebSessionEntryStableId : Hashable { + + case logoutId + case descriptionId(Int32) + case sessionId(Int64) + case loadingId + case sectionId(Int32) + var hashValue: Int { + switch self { + case let .sectionId(id): + return Int(id) + case .logoutId: + return 0 + case .loadingId: + return 1 + case let .sessionId(id): + return Int(id) + case let .descriptionId(id): + return Int(id) + } + } +} + +private enum WebSessionEntry : TableItemListNodeEntry { + case logout(sectionId: Int32, index: Int32, viewType: GeneralViewType) + case description(sectionId: Int32, index: Int32, text: String, viewType: GeneralViewType) + case session(sectionId: Int32, index: Int32, authorization: WebAuthorization, peer: Peer, viewType: GeneralViewType) + case sectionId(Int32) + case loading + + var stableId:WebSessionEntryStableId { + switch self { + case let .sectionId(id): + return .sectionId(id) + case .logout: + return .logoutId + case .loading: + return .loadingId + case .description(_, let index, _, _): + return .descriptionId(index) + case .session(_, _, let authorization, _, _): + return .sessionId(authorization.hash) + } + } + + var index: Int32 { + switch self { + case let .logout(sectionId, index, _): + return (sectionId * 1000) + index + case .loading: + return 0 + case let .description(sectionId, index, _, _): + return (sectionId * 1000) + index + case let .session(sectionId, index, _, _, _): + return (sectionId * 1000) + index + case let .sectionId(sectionId): + return (sectionId * 1000) + sectionId + } + } + + + func item(_ arguments: WebSessionArguments, initialSize: NSSize) -> TableRowItem { + switch self { + case .sectionId: + return GeneralRowItem(initialSize, height: 30, stableId: stableId, viewType: .separator) + case let .description(_, _, text, viewType): + return GeneralTextRowItem(initialSize, stableId: stableId, text: text, viewType: viewType) + case let .logout(_, _, viewType): + return GeneralInteractedRowItem(initialSize, stableId: stableId, name: L10n.webAuthorizationsLogoutAll, nameStyle: ControlStyle(font: .normal(.title), foregroundColor: theme.colors.redUI), type: .none, viewType: viewType, action: { + arguments.logoutAll() + }) + case .loading: + return SearchEmptyRowItem(initialSize, stableId: stableId, isLoading: true) + case let .session(_, _, authorization, peer, viewType): + return WebAuthorizationRowItem(initialSize, stableId: stableId, account: arguments.context.account, authorization: authorization, peer: peer, viewType: viewType, logout: { + arguments.logoutSession(authorization) + }) + } + } +} + +private func ==(lhs: WebSessionEntry, rhs: WebSessionEntry) -> Bool { + switch lhs { + case .loading: + if case .loading = rhs { + return true + } else { + return false + } + case let .description(sectionId, index, text, viewType): + if case .description(sectionId, index, text, viewType) = rhs { + return true + } else { + return false + } + case let .logout(sectionId, index, viewType): + if case .logout(sectionId, index, viewType) = rhs { + return true + } else { + return false + } + case let .sectionId(sectionId): + if case .sectionId(sectionId) = rhs { + return true + } else { + return false + } + case let .session(lhsSectionId, lhsIndex, lhsAuthorization, lhsPeer, lhsViewType): + if case let .session(rhsSectionId, rhsIndex, rhsAuthorization, rhsPeer, rhsViewType) = rhs { + return lhsSectionId == rhsSectionId && lhsIndex == rhsIndex && lhsAuthorization == rhsAuthorization && lhsPeer.isEqual(rhsPeer) && lhsViewType == rhsViewType + } else { + return false + } + } +} +private func <(lhs: WebSessionEntry, rhs: WebSessionEntry) -> Bool { + return lhs.index < rhs.index +} + + +private struct WebSessionsControllerState: Equatable { + let removingSessionId: Int64? + let removedSessions: Set + init() { + self.removingSessionId = nil + self.removedSessions = [] + } + + init(removingSessionId: Int64?, removedSessions: Set) { + self.removingSessionId = removingSessionId + self.removedSessions = removedSessions + } + + static func ==(lhs: WebSessionsControllerState, rhs: WebSessionsControllerState) -> Bool { + if lhs.removingSessionId != rhs.removingSessionId { + return false + } + if lhs.removedSessions != rhs.removedSessions { + return false + } + + return true + } + + func withUpdatedRemovedSessionId(_ sessionId: Int64) -> WebSessionsControllerState { + var sessions = self.removedSessions + if sessions.contains(sessionId) { + sessions.remove(sessionId) + } else { + sessions.insert(sessionId) + } + return WebSessionsControllerState(removingSessionId: removingSessionId, removedSessions: sessions) + } + + func withUpdatedRemovingSessionId(_ removingSessionId: Int64?) -> WebSessionsControllerState { + return WebSessionsControllerState(removingSessionId: removingSessionId, removedSessions: self.removedSessions) + } + + func newState(from value: ([WebAuthorization], [PeerId : Peer])?) -> ([WebAuthorization], [PeerId : Peer])? { + if let value = value { + return (value.0.filter({!removedSessions.contains($0.hash)}), value.1) + } + return nil + } + +} + +private func prepareSessions(left:[AppearanceWrapperEntry], right: [AppearanceWrapperEntry], arguments: WebSessionArguments, initialSize: NSSize) -> TableUpdateTransition { + let (removed, inserted, updated) = proccessEntriesWithoutReverse(left, right: right) { entry -> TableRowItem in + return entry.entry.item(arguments, initialSize: initialSize) + } + + return TableUpdateTransition(deleted: removed, inserted: inserted, updated: updated, animated: true) +} + +private func webAuthorizationEntries(authorizations: [WebAuthorization]?, peers:[PeerId : Peer], state: WebSessionsControllerState) -> [WebSessionEntry] { + var entries: [WebSessionEntry] = [] + + if let authorizations = authorizations { + var sectionId:Int32 = 0 + entries.append(.sectionId(sectionId)) + sectionId += 1 + + var index: Int32 = 1 + + + entries.append(.logout(sectionId: sectionId, index: index, viewType: .singleItem)) + index += 1 + + entries.append(.description(sectionId: sectionId, index: index, text: L10n.webAuthorizationsLogoutAllDescription, viewType: .textBottomItem)) + index += 1 + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + + let authorizations = authorizations.filter {!state.removedSessions.contains($0.hash)} + + if authorizations.count > 0 { + entries.append(.description(sectionId: sectionId, index: index, text: L10n.webAuthorizationsLoggedInDescrpiption, viewType: .textTopItem)) + index += 1 + } + + for auth in authorizations { + if let peer = peers[auth.botId] { + entries.append(.session(sectionId: sectionId, index: index, authorization: auth, peer: peer, viewType: bestGeneralViewType(authorizations, for: auth))) + index += 1 + } + } + + entries.append(.sectionId(sectionId)) + sectionId += 1 + + } else { + entries.append(.loading) + } + + + return entries +} + +class WebSessionsController: TableViewController { + + private let disposable = MetaDisposable() + + override func viewDidLoad() { + super.viewDidLoad() + + let actionsDisposable = MetaDisposable() + + let state = Atomic(value: WebSessionsControllerState()) + + + + let stateValue = ValuePromise(WebSessionsControllerState(), ignoreRepeated: true) + + let updateState:((WebSessionsControllerState)->WebSessionsControllerState)->Void = { f -> Void in + stateValue.set(state.modify(f)) + } + + let network = context.account.network + + let arguments = WebSessionArguments(context: context, logoutSession: { session in + confirm(for: mainWindow, information: L10n.webAuthorizationsConfirmRevoke, successHandler: { result in + updateState { state in + return state.withUpdatedRemovingSessionId(session.hash) + } + + _ = showModalProgress(signal: terminateWebSession(network: network, hash: session.hash), for: mainWindow).start(next: { value in + updateState { state in + return state.withUpdatedRemovedSessionId(session.hash).withUpdatedRemovingSessionId(nil) + } + }) + }) + + }, logoutAll: { [weak self] in + confirm(for: mainWindow, information: L10n.webAuthorizationsConfirmRevokeAll, successHandler: { result in + self?.updated(nil) + self?.navigationController?.back() + _ = showModalProgress(signal: terminateAllWebSessions(network: network), for: mainWindow).start() + }) + + }) + let initialSize = self.atomicSize + + let previous: Atomic<[AppearanceWrapperEntry]> = Atomic(value: []) + + let signal = combineLatest((Signal<([WebAuthorization], [PeerId: Peer])?, NoError>.single(defaultValue) |> deliverOnPrepareQueue |> then (webSessions(network: network) |> map {Optional($0)} |> deliverOnPrepareQueue)), appearanceSignal |> deliverOnPrepareQueue, stateValue.get() |> deliverOnPrepareQueue) |> map { values, appearance, state -> (TableUpdateTransition, ([WebAuthorization], [PeerId: Peer])?, WebSessionsControllerState) in + let entries = webAuthorizationEntries(authorizations: values?.0, peers: values?.1 ?? [:], state: state).map({AppearanceWrapperEntry(entry: $0, appearance: appearance)}) + return (prepareSessions(left: previous.swap(entries), right: entries, arguments: arguments, initialSize: initialSize.modify{$0}), values, state) + + } |> deliverOnMainQueue |> afterDisposed { + actionsDisposable.dispose() + } + + disposable.set(signal.start(next: { [weak self] transition, values, state in + self?.genericView.merge(with: transition) + self?.readyOnce() + + let newValue = state.newState(from: values) + self?.updated(newValue) + + if newValue == nil || newValue?.0.isEmpty == true { + self?.navigationController?.back() + } + })) + + } + + deinit { + disposable.dispose() + } + + private let defaultValue: ([WebAuthorization], [PeerId : Peer])? + private let updated: (([WebAuthorization], [PeerId : Peer])?) -> Void + init(_ context: AccountContext, _ result: ([WebAuthorization], [PeerId : Peer])?, updated: @escaping(([WebAuthorization], [PeerId : Peer])?) -> Void) { + self.defaultValue = result + self.updated = updated + super.init(context) + + } + +} diff --git a/Telegram-Mac/WebpageModalController.swift b/Telegram-Mac/WebpageModalController.swift index 83a5556eea..9d72af5464 100644 --- a/Telegram-Mac/WebpageModalController.swift +++ b/Telegram-Mac/WebpageModalController.swift @@ -8,37 +8,51 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import SwiftSignalKitMac -import PostboxMac +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox import WebKit -class WebpageModalController: ModalViewController,WebFrameLoadDelegate { - private var webView:WebView! + + +class WebpageModalController: ModalViewController, WKNavigationDelegate { private var indicator:ProgressIndicator! private let content:TelegramMediaWebpageLoadedContent - private let account:Account + private let context:AccountContext + private let webview: WKWebView = WKWebView(frame: NSZeroRect) override func loadView() { super.loadView() - webView = WebView(frame: self.bounds) - webView.frameLoadDelegate = self - webView.wantsLayer = true - addSubview(webView) + webview.wantsLayer = true + webview.removeFromSuperview() + addSubview(webview) indicator = ProgressIndicator(frame: NSMakeRect(0,0,30,30)) addSubview(indicator) indicator.center() - webView.isHidden = true + webview.isHidden = true indicator.animates = true + + webview.navigationDelegate = self + // leakWebview() + if let embed = content.embedUrl, let url = URL(string: embed) { - webView.mainFrame.load(URLRequest(url: url)) + webview.load(URLRequest(url: url)) + readyOnce() } } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + webview.isHidden = false + webview.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + indicator.isHidden = true + indicator.animates = false + } + override var dynamicSize:Bool { return true } @@ -46,9 +60,10 @@ class WebpageModalController: ModalViewController,WebFrameLoadDelegate { override func measure(size: NSSize) { - if let embedSize = content.embedSize { + if let embedSize = content.embedSize?.size { let size = embedSize.aspectFitted(NSMakeSize(min(size.width - 100, 800), min(size.height - 100, 800))) - webView.setFrameSize(size) + webview.setFrameSize(size) + self.modal?.resize(with:size, animated: false) indicator.center() @@ -57,20 +72,20 @@ class WebpageModalController: ModalViewController,WebFrameLoadDelegate { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - webView.mainFrame.load(URLRequest(url: URL(string:"file://blank")!)) - webView.mainFrame.stopLoading() + webview.removeFromSuperview() + webview.stopLoading() + webview.loadHTMLString("", baseURL: nil) } - func webView(_ sender: WebView!, didFinishLoadFor frame: WebFrame!) { - webView.isHidden = false - webView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) - indicator.isHidden = true - indicator.animates = false + deinit { + var bp:Int = 0 + bp += 1 } - init(content:TelegramMediaWebpageLoadedContent, account:Account) { + + init(content:TelegramMediaWebpageLoadedContent, context: AccountContext) { self.content = content - self.account = account + self.context = context super.init(frame:NSMakeRect(0,0,350,270)) } diff --git a/Telegram-Mac/builtin-wallpaper-0.jpg b/Telegram-Mac/builtin-wallpaper-0.jpg new file mode 100644 index 0000000000..de7c315b47 Binary files /dev/null and b/Telegram-Mac/builtin-wallpaper-0.jpg differ diff --git a/Telegram-Mac/countries b/Telegram-Mac/countries new file mode 100644 index 0000000000..ff8d443b15 --- /dev/null +++ b/Telegram-Mac/countries @@ -0,0 +1,232 @@ +1876;JM;Jamaica;JAM +1869;KN;Saint Kitts & Nevis;KNA +1868;TT;Trinidad & Tobago;TTO +1784;VC;Saint Vincent & the Grenadines;VCT +1767;DM;Dominica;DMA +1758;LC;Saint Lucia;LCA +1721;SX;Sint Maarten;SXM +1684;AS;American Samoa;ASM +1671;GU;Guam;GUM +1670;MP;Northern Mariana Islands;MNP +1664;MS;Montserrat;MSR +1649;TC;Turks & Caicos Islands;TCA +1473;GD;Grenada;GRD +1441;BM;Bermuda;BMU +1345;KY;Cayman Islands;CYM +1340;VI;US Virgin Islands;VIR +1284;VG;British Virgin Islands;VGB +1268;AG;Antigua & Barbuda;ATG +1264;AI;Anguilla;AIA +1246;BB;Barbados;BRB +1242;BS;Bahamas;BHS +998;UZ;Uzbekistan;UZB +996;KG;Kyrgyzstan;KGZ +995;GE;Georgia;GEO +994;AZ;Azerbaijan;AZE +993;TM;Turkmenistan;TKM +992;TJ;Tajikistan;TJK +977;NP;Nepal;NPL +976;MN;Mongolia;MNG +975;BT;Bhutan;BTN +974;QA;Qatar;QAT +973;BH;Bahrain;BHR +972;IL;Israel;ISR +971;AE;United Arab Emirates;ARE +970;PS;Palestine;PSE +968;OM;Oman;OMN +967;YE;Yemen;YEM +966;SA;Saudi Arabia;SAU +965;KW;Kuwait;KWT +964;IQ;Iraq;IRQ +963;SY;Syrian Arab Republic;SYR +962;JO;Jordan;JOR +961;LB;Lebanon;LBN +960;MV;Maldives;MDV +886;TW;Taiwan;TWN +880;BD;Bangladesh;BGD +856;LA;Laos;LAO +855;KH;Cambodia;KHM +853;MO;Macau;MAC +852;HK;Hong Kong;HKG +850;KP;North Korea;PRK +692;MH;Marshall Islands;MHL +691;FM;Micronesia;FSM +690;TK;Tokelau;TKL +689;PF;French Polynesia;PYF +688;TV;Tuvalu;TUV +687;NC;New Caledonia;NCL +686;KI;Kiribati;KIR +685;WS;Samoa;WSM +683;NU;Niue;NIU +682;CK;Cook Islands;COK +681;WF;Wallis & Futuna;WLF +680;PW;Palau;PLW +679;FJ;Fiji;FJI +678;VU;Vanuatu;VUT +677;SB;Solomon Islands;SLB +676;TO;Tonga;TON +675;PG;Papua New Guinea;PNG +674;NR;Nauru;NRU +673;BN;Brunei Darussalam;BRN +672;NF;Norfolk Island;NFK +670;TL;Timor-Leste;TLS +599;BQ;Bonaire, Sint Eustatius & Saba;BES +599;CW;Curaçao;CUW +598;UY;Uruguay;URY +597;SR;Suriname;SUR +596;MQ;Martinique;MTQ +595;PY;Paraguay;PRY +594;GF;French Guiana;GUF +593;EC;Ecuador;ECU +592;GY;Guyana;GUY +591;BO;Bolivia;BOL +590;GP;Guadeloupe;GLP +509;HT;Haiti;HTI +508;PM;Saint Pierre & Miquelon;SPM +507;PA;Panama;PAN +506;CR;Costa Rica;CRI +505;NI;Nicaragua;NIC +504;HN;Honduras;HND +503;SV;El Salvador;SLV +502;GT;Guatemala;GTM +501;BZ;Belize;BLZ +500;FK;Falkland Islands;FLK +423;LI;Liechtenstein;LIE +421;SK;Slovakia;SVK +420;CZ;Czech Republic;CZE +383;XK;Kosovo;UNK +389;MK;Macedonia;MKD +387;BA;Bosnia & Herzegovina;BIH +386;SI;Slovenia;SVN +385;HR;Croatia;HRV +382;ME;Montenegro;MNE +381;RS;Serbia;SRB +380;UA;Ukraine;UKR +378;SM;San Marino;SMR +377;MC;Monaco;MCO +376;AD;Andorra;AND +375;BY;Belarus;BLR +374;AM;Armenia;ARM +373;MD;Moldova;MDA +372;EE;Estonia;EST +371;LV;Latvia;LVA +370;LT;Lithuania;LTU +359;BG;Bulgaria;BGR +358;FI;Finland;FIN +357;CY;Cyprus;CYP +356;MT;Malta;MLT +355;AL;Albania;ALB +354;IS;Iceland;ISL +353;IE;Ireland;IRL +352;LU;Luxembourg;LUX +351;PT;Portugal;PRT +350;GI;Gibraltar;GIB +299;GL;Greenland;GRL +298;FO;Faroe Islands;FRO +297;AW;Aruba;ABW +291;ER;Eritrea;ERI +290;SH;Saint Helena;SHN +269;KM;Comoros;COM +268;SZ;Swaziland;SWZ +267;BW;Botswana;BWA +266;LS;Lesotho;LSO +265;MW;Malawi;MWI +264;NA;Namibia;NAM +263;ZW;Zimbabwe;ZWE +262;RE;Réunion;REU +261;MG;Madagascar;MDG +260;ZM;Zambia;ZMB +258;MZ;Mozambique;MOZ +257;BI;Burundi;BDI +256;UG;Uganda;UGA +255;TZ;Tanzania;TZA +254;KE;Kenya;KEN +253;DJ;Djibouti;DJI +252;SO;Somalia;SOM +251;ET;Ethiopia;ETH +250;RW;Rwanda;RWA +249;SD;Sudan;SDN +248;SC;Seychelles;SYC +247;SH;Saint Helena;SHN +246;IO;Diego Garcia;DGA +245;GW;Guinea-Bissau;GNB +244;AO;Angola;AGO +243;CD;Congo (Dem. Rep.);COD +242;CG;Congo (Rep.);COG +241;GA;Gabon;GAB +240;GQ;Equatorial Guinea;GNQ +239;ST;São Tomé & Príncipe;STP +238;CV;Cape Verde;CPV +237;CM;Cameroon;CMR +236;CF;Central African Rep.;CAF +235;TD;Chad;TCD +234;NG;Nigeria;NGA +233;GH;Ghana;GHA +232;SL;Sierra Leone;SLE +231;LR;Liberia;LBR +230;MU;Mauritius;MUS +229;BJ;Benin;BEN +228;TG;Togo;TGO +227;NE;Niger;NER +226;BF;Burkina Faso;BFA +225;CI;Côte d`Ivoire;CIV +224;GN;Guinea;GIN +223;ML;Mali;MLI +222;MR;Mauritania;MRT +221;SN;Senegal;SEN +220;GM;Gambia;GMB +218;LY;Libya;LBY +216;TN;Tunisia;TUN +213;DZ;Algeria;DZA +212;MA;Morocco;MAR +211;SS;South Sudan;SSD +98;IR;Iran;IRN +95;MM;Myanmar;MMR +94;LK;Sri Lanka;LKA +93;AF;Afghanistan;AFG +92;PK;Pakistan;PAK +91;IN;India;IND +90;TR;Turkey;TUR +86;CN;China;CHN +84;VN;Vietnam;VNM +82;KR;South Korea;KOR +81;JP;Japan;JPN +66;TH;Thailand;THA +65;SG;Singapore;SGP +64;NZ;New Zealand;NZL +63;PH;Philippines;PHL +62;ID;Indonesia;IDN +61;AU;Australia;AUS +60;MY;Malaysia;MYS +58;VE;Venezuela;VEN +57;CO;Colombia;COL +56;CL;Chile;CHL +55;BR;Brazil;BRA +54;AR;Argentina;ARG +53;CU;Cuba;CUB +52;MX;Mexico;MEX +51;PE;Peru;PER +49;DE;Germany;DEU +48;PL;Poland;POL +47;NO;Norway;NOR +46;SE;Sweden;SWE +45;DK;Denmark;DNK +44;GB;United Kingdom;GBR,GBD,GBN,GBO,GBP,GBS +43;AT;Austria;AUT +41;CH;Switzerland;CHE +40;RO;Romania;ROM +39;IT;Italy;ITA +36;HU;Hungary;HUN +34;ES;Spain;ESP +33;FR;France;FRA +32;BE;Belgium;BEL +31;NL;Netherlands;NLD +30;GR;Greece;GRC +27;ZA;South Africa;ZAF +20;EG;Egypt;EGY +7;RU;Russian Federation;RUS +7;KZ;Kazakhstan;KAZ +1;US;USA;USA,UMI +1;PR;Puerto Rico;PRI +1;DO;Dominican Rep.;DOM +1;CA;Canada;CAN diff --git a/Telegram-Mac/de.lproj/MainMenu.xib b/Telegram-Mac/de.lproj/MainMenu.xib new file mode 100644 index 0000000000..57e428c68e --- /dev/null +++ b/Telegram-Mac/de.lproj/MainMenu.xib @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/emoji1014-1.txt b/Telegram-Mac/emoji1014-1.txt new file mode 100644 index 0000000000..8c9eb95899 --- /dev/null +++ b/Telegram-Mac/emoji1014-1.txt @@ -0,0 +1,15 @@ +😀 😁 😂 🤣 😃 😄 😅 😆 😉 😊 😋 😎 😍 😘 🥰 😗 😙 😚 ☺️ 🙂 🤗 🤩 🤔 🤨 😐 😑 😶 🙄 😏 😣 😥 😮 🤐 😯 😪 😫 😴 😌 😛 😜 😝 🤤 😒 😓 😔 😕 🙃 🤑 😲 ☹️ 🙁 😖 😞 😟 😤 😢 😭 😦 😧 😨 😩 🤯 😬 😰 😱 🥵 🥶 😳 🤪 😵 😡 😠 🤬 😷 🤒 🤕 🤢 🤮 🤧 😇 🤠 🤡 🥳 🥴 🥺 🤥 🤫 🤭 🧐 🤓 😈 👿 👹 👺 💀 👻 👽 🤖 💩 😺 😸 😹 😻 😼 😽 🙀 😿 😾 👶 👧 🧒 👦 👩 🧑 👨 👵 🧓 👴 👲 👳‍♀️ 👳‍♂️ 🧕 🧔 👱‍♂️ 👱‍♀️ 👨‍🦰 👩‍🦰 👨‍🦱 👩‍🦱 👨‍🦲 👩‍🦲 👨‍🦳 👩‍🦳 🦸‍♀️ 🦸‍♂️ 🦹‍♀️ 🦹‍♂️ 👮‍♀️ 👮‍♂️ 👷‍♀️ 👷‍♂️ 💂‍♀️ 💂‍♂️ 🕵️‍♀️ 🕵️‍♂️ 👩‍⚕️ 👨‍⚕️ 👩‍🌾 👨‍🌾 👩‍🍳 👨‍🍳 👩‍🎓 👨‍🎓 👩‍🎤 👨‍🎤 👩‍🏫 👨‍🏫 👩‍🏭 👨‍🏭 👩‍💻 👨‍💻 👩‍💼 👨‍💼 👩‍🔧 👨‍🔧 👩‍🔬 👨‍🔬 👩‍🎨 👨‍🎨 👩‍🚒 👨‍🚒 👩‍✈️ 👨‍✈️ 👩‍🚀 👨‍🚀 👩‍⚖️ 👨‍⚖️ 👰 🤵 👸 🤴 🤶 🎅 🧙‍♀️ 🧙‍♂️ 🧝‍♀️ 🧝‍♂️ 🧛‍♀️ 🧛‍♂️ 🧟‍♀️ 🧟‍♂️ 🧞‍♀️ 🧞‍♂️ 🧜‍♀️ 🧜‍♂️ 🧚‍♀️ 🧚‍♂️ 👼 🤰 🤱 🙇‍♀️ 🙇‍♂️ 💁‍♀️ 💁‍♂️ 🙅‍♀️ 🙅‍♂️ 🙆‍♀️ 🙆‍♂️ 🙋‍♀️ 🙋‍♂️ 🤦‍♀️ 🤦‍♂️ 🤷‍♀️ 🤷‍♂️ 🙎‍♀️ 🙎‍♂️ 🙍‍♀️ 🙍‍♂️ 💇‍♀️ 💇‍♂️ 💆‍♀️ 💆‍♂️ 🧖‍♀️ 🧖‍♂️ 💅 🤳 💃 🕺 👯‍♀️ 👯‍♂️ 🕴 🚶‍♀️ 🚶‍♂️ 🏃‍♀️ 🏃‍♂️ 👫 👭 👬 💑 👩‍❤️‍👩 👨‍❤️‍👨 💏 👩‍❤️‍💋‍👩 👨‍❤️‍💋‍👨 👪 👨‍👩‍👧 👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👩‍👩‍👦 👩‍👩‍👧 👩‍👩‍👧‍👦 👩‍👩‍👦‍👦 👩‍👩‍👧‍👧 👨‍👨‍👦 👨‍👨‍👧 👨‍👨‍👧‍👦 👨‍👨‍👦‍👦 👨‍👨‍👧‍👧 👩‍👦 👩‍👧 👩‍👧‍👦 👩‍👦‍👦 👩‍👧‍👧 👨‍👦 👨‍👧 👨‍👧‍👦 👨‍👦‍👦 👨‍👧‍👧 🤲 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤟 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🦵 🦶 🖕 ✍️ 🙏 💍 💄 💋 👄 👅 👂 👃 👣 👁 👀 🧠 🦴 🦷 🗣 👤 👥 + +🐶 🐱 🐭 🐹 🐰 🦊 🦝 🐻 🐼 🦘 🦡 🐨 🐯 🦁 🐮 🐷 🐽 🐸 🐵 🙈 🙉 🙊 🐒 🐔 🐧 🐦 🐤 🐣 🐥 🦆 🦢 🦅 🦉 🦚 🦜 🦇 🐺 🐗 🐴 🦄 🐝 🐛 🦋 🐌 🐚 🐞 🐜 🦗 🕷 🕸 🦂 🦟 🦠 🐢 🐍 🦎 🦖 🦕 🐙 🦑 🦐 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🐘 🦏 🦛 🐪 🐫 🦙 🦒 🐃 🐂 🐄 🐎 🐖 🐏 🐑 🐐 🦌 🐕 🐩 🐈 🐓 🦃 🕊 🐇 🐁 🐀 🐿 🦔 🐾 🐉 🐲 🌵 🎄 🌲 🌳 🌴 🌱 🌿 ☘️ 🍀 🎍 🎋 🍃 🍂 🍁 🍄 🌾 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻 🌞 🌝 🌛 🌜 🌚 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔 🌙 🌎 🌍 🌏 💫 ⭐️ 🌟 ✨ ⚡️ ☄️ 💥 🔥 🌪 🌈 ☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫 + +🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🍈 🍒 🍑 🍍 🥭 🥥 🥝 🍅 🍆 🥑 🥦 🥒 🥬 🌶 🌽 🥕 🥔 🍠 🥐 🍞 🥖 🥨 🥯 🧀 🥚 🍳 🥞 🥓 🥩 🍗 🍖 🌭 🍔 🍟 🍕 🥪 🥙 🌮 🌯 🥗 🥘 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🍤 🍙 🍚 🍘 🍥 🥮 🥠 🍢 🍡 🍧 🍨 🍦 🥧 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🧂 🍩 🍪 🌰 🥜 🍯 🥛 🍼 ☕️ 🍵 🥤 🍶 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🍾 🥄 🍴 🍽 🥣 🥡 🥢 + +⚽️ 🏀 🏈 ⚾️ 🥎 🏐 🏉 🎾 🥏 🎱 🏓 🏸 🥅 🏒 🏑 🥍 🏏 ⛳️ 🏹 🎣 🥊 🥋 🎽 ⛸ 🥌 🛷 🛹 🎿 ⛷ 🏂 🏋️‍♀️ 🏋🏻‍♀️ 🏋🏼‍♀️ 🏋🏽‍♀️ 🏋🏾‍♀️ 🏋🏿‍♀️ 🏋️‍♂️ 🏋🏻‍♂️ 🏋🏼‍♂️ 🏋🏽‍♂️ 🏋🏾‍♂️ 🏋🏿‍♂️ 🤼‍♀️ 🤼‍♂️ 🤸‍♀️ 🤸🏻‍♀️ 🤸🏼‍♀️ 🤸🏽‍♀️ 🤸🏾‍♀️ 🤸🏿‍♀️ 🤸‍♂️ 🤸🏻‍♂️ 🤸🏼‍♂️ 🤸🏽‍♂️ 🤸🏾‍♂️ 🤸🏿‍♂️ ⛹️‍♀️ ⛹🏻‍♀️ ⛹🏼‍♀️ ⛹🏽‍♀️ ⛹🏾‍♀️ ⛹🏿‍♀️ ⛹️‍♂️ ⛹🏻‍♂️ ⛹🏼‍♂️ ⛹🏽‍♂️ ⛹🏾‍♂️ ⛹🏿‍♂️ 🤺 🤾‍♀️ 🤾🏻‍♀️ 🤾🏼‍♀️ 🤾🏾‍♀️ 🤾🏾‍♀️ 🤾🏿‍♀️ 🤾‍♂️ 🤾🏻‍♂️ 🤾🏼‍♂️ 🤾🏽‍♂️ 🤾🏾‍♂️ 🤾🏿‍♂️ 🏌️‍♀️ 🏌🏻‍♀️ 🏌🏼‍♀️ 🏌🏽‍♀️ 🏌🏾‍♀️ 🏌🏿‍♀️ 🏌️‍♂️ 🏌🏻‍♂️ 🏌🏼‍♂️ 🏌🏽‍♂️ 🏌🏾‍♂️ 🏌🏿‍♂️ 🏇 🏇🏻 🏇🏼 🏇🏽 🏇🏾 🏇🏿 🧘‍♀️ 🧘🏻‍♀️ 🧘🏼‍♀️ 🧘🏽‍♀️ 🧘🏾‍♀️ 🧘🏿‍♀️ 🧘‍♂️ 🧘🏻‍♂️ 🧘🏼‍♂️ 🧘🏽‍♂️ 🧘🏾‍♂️ 🧘🏿‍♂️ 🏄‍♀️ 🏄🏻‍♀️ 🏄🏼‍♀️ 🏄🏽‍♀️ 🏄🏾‍♀️ 🏄🏿‍♀️ 🏄‍♂️ 🏄🏻‍♂️ 🏄🏼‍♂️ 🏄🏽‍♂️ 🏄🏾‍♂️ 🏄🏿‍♂️ 🏊‍♀️ 🏊🏻‍♀️ 🏊🏼‍♀️ 🏊🏽‍♀️ 🏊🏾‍♀️ 🏊🏿‍♀️ 🏊‍♂️ 🏊🏻‍♂️ 🏊🏼‍♂️ 🏊🏽‍♂️ 🏊🏾‍♂️ 🏊🏿‍♂️ 🤽‍♀️ 🤽🏻‍♀️ 🤽🏼‍♀️ 🤽🏽‍♀️ 🤽🏾‍♀️ 🤽🏿‍♀️ 🤽‍♂️ 🤽🏻‍♂️ 🤽🏼‍♂️ 🤽🏽‍♂️ 🤽🏾‍♂️ 🤽🏿‍♂️ 🚣‍♀️ 🚣🏻‍♀️ 🚣🏼‍♀️ 🚣🏽‍♀️ 🚣🏾‍♀️ 🚣🏿‍♀️ 🚣‍♂️ 🚣🏻‍♂️ 🚣🏼‍♂️ 🚣🏽‍♂️ 🚣🏾‍♂️ 🚣🏿‍♂️ 🧗‍♀️ 🧗🏻‍♀️ 🧗🏼‍♀️ 🧗🏽‍♀️ 🧗🏾‍♀️ 🧗🏿‍♀️ 🧗‍♂️ 🧗🏻‍♂️ 🧗🏼‍♂️ 🧗🏽‍♂️ 🧗🏾‍♂️ 🧗🏿‍♂️ 🚵‍♀️ 🚵🏻‍♀️ 🚵🏼‍♀️ 🚵🏽‍♀️ 🚵🏾‍♀️ 🚵🏿‍♀️ 🚵‍♂️ 🚵🏻‍♂️ 🚵🏼‍♂️ 🚵🏽‍♂️ 🚵🏾‍♂️ 🚵🏿‍♂️ 🚴‍♀️ 🚴🏻‍♀️ 🚴🏼‍♀️ 🚴🏽‍♀️ 🚴🏾‍♀️ 🚴🏿‍♀️ 🚴‍♂️ 🚴🏻‍♂️ 🚴🏼‍♂️ 🚴🏽‍♂️ 🚴🏾‍♂️ 🚴🏿‍♂️ 🏆 🥇 🥈 🥉 🏅 🎖 🏵 🎗 🎫 🎟 🎪 🤹‍♀️ 🤹🏻‍♀️ 🤹🏼‍♀️ 🤹🏽‍♀️ 🤹🏾‍♀️ 🤹🏿‍♀️ 🤹‍♂️ 🤹🏻‍♂️ 🤹🏼‍♂️ 🤹🏽‍♂️ 🤹🏾‍♂️ 🤹🏿‍♂️ 🎭 🎨 🎬 🎤 🎧 🎼 🎹 🥁 🎷 🎺 🎸 🎻 🎲 🧩 ♟ 🎯 🎳 🎮 🎰 + +🚗 🚕 🚙 🚌 🚎 🏎 🚓 🚑 🚒 🚐 🚚 🚛 🚜 🛴 🚲 🛵 🏍 🚨 🚔 🚍 🚘 🚖 🚡 🚠 🚟 🚃 🚋 🚞 🚝 🚄 🚅 🚈 🚂 🚆 🚇 🚊 🚉 ✈️ 🛫 🛬 🛩 💺 🛰 🚀 🛸 🚁 🛶 ⛵️ 🚤 🛥 🛳 ⛴ 🚢 ⚓️ ⛽️ 🚧 🚦 🚥 🚏 🗺 🗿 🗽 🗼 🏰 🏯 🏟 🎡 🎢 🎠 ⛲️ ⛱ 🏖 🏝 🏜 🌋 ⛰ 🏔 🗻 🏕 ⛺️ 🏠 🏡 🏘 🏚 🏗 🏭 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛 ⛪️ 🕌 🕍 🕋 ⛩ 🛤 🛣 🗾 🎑 🏞 🌅 🌄 🌠 🎇 🎆 🌇 🌆 🏙 🌃 🌌 🌉 🌁 + +⌚️ 📱 📲 💻 ⌨️ 🖥 🖨 🖱 🖲 🕹 🗜 💽 💾 💿 📀 📼 📷 📸 📹 🎥 📽 🎞 📞 ☎️ 📟 📠 📺 📻 🎙 🎚 🎛 ⏱ ⏲ ⏰ 🕰 ⌛️ ⏳ 📡 🔋 🔌 💡 🔦 🕯 🗑 🛢 💸 💵 💴 💶 💷 💰 💳 🧾 💎 ⚖️ 🔧 🔨 ⚒ 🛠 ⛏ 🔩 ⚙️ ⛓ 🔫 💣 🔪 🗡 ⚔️ 🛡 🚬 ⚰️ ⚱️ 🏺 🧭 🧱 🔮 🧿 🧸 📿 💈 ⚗️ 🔭 🧰 🧲 🧪 🧫 🧬 🧯 🔬 🕳 💊 💉 🌡 🚽 🚰 🚿 🛁 🛀 🛀🏻 🛀🏼 🛀🏽 🛀🏾 🛀🏿 🧴 🧵 🧶 🧷 🧹 🧺 🧻 🧼 🧽 🛎 🔑 🗝 🚪 🛋 🛏 🛌 🖼 🛍 🧳 🛒 🎁 🎈 🎏 🎀 🎊 🎉 🧨 🎎 🏮 🎐 🧧 ✉️ 📩 📨 📧 💌 📥 📤 📦 🏷 📪 📫 📬 📭 📮 📯 📜 📃 📄 📑 📊 📈 📉 🗒 🗓 📆 📅 📇 🗃 🗳 🗄 📋 📁 📂 🗂 🗞 📰 📓 📔 📒 📕 📗 📘 📙 📚 📖 🔖 🔗 📎 🖇 📐 📏 📌 📍 ✂️ 🖊 🖋 ✒️ 🖌 🖍 📝 ✏️ 🔍 🔎 🔏 🔐 🔒 🔓 + +❤️ 🧡 💛 💚 💙 💜 🖤 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 💟 ☮️ ✝️ ☪️ 🕉 ☸️ ✡️ 🔯 🕎 ☯️ ☦️ 🛐 ⛎ ♈️ ♉️ ♊️ ♋️ ♌️ ♍️ ♎️ ♏️ ♐️ ♑️ ♒️ ♓️ 🆔 ⚛️ 🉑 ☢️ ☣️ 📴 📳 🈶 🈚️ 🈸 🈺 🈷️ ✴️ 🆚 💮 🉐 ㊙️ ㊗️ 🈴 🈵 🈹 🈲 🅰️ 🅱️ 🆎 🆑 🅾️ 🆘 ❌ ⭕️ 🛑 ⛔️ 📛 🚫 💯 💢 ♨️ 🚷 🚯 🚳 🚱 🔞 📵 🚭 ❗️ ❕ ❓ ❔ ‼️ ⁉️ 🔅 🔆 〽️ ⚠️ 🚸 🔱 ⚜️ 🔰 ♻️ ✅ 🈯️ 💹 ❇️ ✳️ ❎ 🌐 💠 Ⓜ️ 🌀 💤 🏧 🚾 ♿️ 🅿️ 🈳 🈂️ 🛂 🛃 🛄 🛅 🚹 🚺 🚼 🚻 🚮 🎦 📶 🈁 🔣 ℹ️ 🔤 🔡 🔠 🆖 🆗 🆙 🆒 🆕 🆓 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 🔢 #️⃣ *️⃣ ⏏️ ▶️ ⏸ ⏯ ⏹ ⏺ ⏭ ⏮ ⏩ ⏪ ⏫ ⏬ ◀️ 🔼 🔽 ➡️ ⬅️ ⬆️ ⬇️ ↗️ ↘️ ↙️ ↖️ ↕️ ↔️ ↪️ ↩️ ⤴️ ⤵️ 🔀 🔁 🔂 🔄 🔃 🎵 🎶 ➕ ➖ ➗ ✖️ ♾ 💲 💱 ™️ ©️ ®️ 〰️ ➰ ➿ 🔚 🔙 🔛 🔝 🔜 ✔️ ☑️ 🔘 ⚪️ ⚫️ 🔴 🔵 🔺 🔻 🔸 🔹 🔶 🔷 🔳 🔲 ▪️ ▫️ ◾️ ◽️ ◼️ ◻️ ⬛️ ⬜️ 🔈 🔇 🔉 🔊 🔔 🔕 📣 📢 👁‍🗨 💬 💭 🗯 ♠️ ♣️ ♥️ ♦️ 🃏 🎴 🀄️ 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 + +🏳️ 🏴 🏁 🚩 🏳️‍🌈 🏴‍☠️ 🇦🇫 🇦🇽 🇦🇱 🇩🇿 🇦🇸 🇦🇩 🇦🇴 🇦🇮 🇦🇶 🇦🇬 🇦🇷 🇦🇲 🇦🇼 🇦🇺 🇦🇹 🇦🇿 🇧🇸 🇧🇭 🇧🇩 🇧🇧 🇧🇾 🇧🇪 🇧🇿 🇧🇯 🇧🇲 🇧🇹 🇧🇴 🇧🇦 🇧🇼 🇧🇷 🇮🇴 🇻🇬 🇧🇳 🇧🇬 🇧🇫 🇧🇮 🇰🇭 🇨🇲 🇨🇦 🇮🇨 🇨🇻 🇧🇶 🇰🇾 🇨🇫 🇹🇩 🇨🇱 🇨🇳 🇨🇽 🇨🇨 🇨🇴 🇰🇲 🇨🇬 🇨🇩 🇨🇰 🇨🇷 🇨🇮 🇭🇷 🇨🇺 🇨🇼 🇨🇾 🇨🇿 🇩🇰 🇩🇯 🇩🇲 🇩🇴 🇪🇨 🇪🇬 🇸🇻 🇬🇶 🇪🇷 🇪🇪 🇪🇹 🇪🇺 🇫🇰 🇫🇴 🇫🇯 🇫🇮 🇫🇷 🇬🇫 🇵🇫 🇹🇫 🇬🇦 🇬🇲 🇬🇪 🇩🇪 🇬🇭 🇬🇮 🇬🇷 🇬🇱 🇬🇩 🇬🇵 🇬🇺 🇬🇹 🇬🇬 🇬🇳 🇬🇼 🇬🇾 🇭🇹 🇭🇳 🇭🇰 🇭🇺 🇮🇸 🇮🇳 🇮🇩 🇮🇷 🇮🇶 🇮🇪 🇮🇲 🇮🇱 🇮🇹 🇯🇲 🇯🇵 🎌 🇯🇪 🇯🇴 🇰🇿 🇰🇪 🇰🇮 🇽🇰 🇰🇼 🇰🇬 🇱🇦 🇱🇻 🇱🇧 🇱🇸 🇱🇷 🇱🇾 🇱🇮 🇱🇹 🇱🇺 🇲🇴 🇲🇰 🇲🇬 🇲🇼 🇲🇾 🇲🇻 🇲🇱 🇲🇹 🇲🇭 🇲🇶 🇲🇷 🇲🇺 🇾🇹 🇲🇽 🇫🇲 🇲🇩 🇲🇨 🇲🇳 🇲🇪 🇲🇸 🇲🇦 🇲🇿 🇲🇲 🇳🇦 🇳🇷 🇳🇵 🇳🇱 🇳🇨 🇳🇿 🇳🇮 🇳🇪 🇳🇬 🇳🇺 🇳🇫 🇰🇵 🇲🇵 🇳🇴 🇴🇲 🇵🇰 🇵🇼 🇵🇸 🇵🇦 🇵🇬 🇵🇾 🇵🇪 🇵🇭 🇵🇳 🇵🇱 🇵🇹 🇵🇷 🇶🇦 🇷🇪 🇷🇴 🇷🇺 🇷🇼 🇼🇸 🇸🇲 🇸🇦 🇸🇳 🇷🇸 🇸🇨 🇸🇱 🇸🇬 🇸🇽 🇸🇰 🇸🇮 🇬🇸 🇸🇧 🇸🇴 🇿🇦 🇰🇷 🇸🇸 🇪🇸 🇱🇰 🇧🇱 🇸🇭 🇰🇳 🇱🇨 🇵🇲 🇻🇨 🇸🇩 🇸🇷 🇸🇿 🇸🇪 🇨🇭 🇸🇾 🇹🇼 🇹🇯 🇹🇿 🇹🇭 🇹🇱 🇹🇬 🇹🇰 🇹🇴 🇹🇹 🇹🇳 🇹🇷 🇹🇲 🇹🇨 🇹🇻 🇻🇮 🇺🇬 🇺🇦 🇦🇪 🇬🇧 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁳󠁣󠁴󠁿 🏴󠁧󠁢󠁷󠁬󠁳󠁿 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇺 🇻🇦 🇻🇪 🇻🇳 🇼🇫 🇪🇭 🇾🇪 🇿🇲 🇿🇼 diff --git a/Telegram-Mac/emoji14.txt b/Telegram-Mac/emoji14.txt new file mode 100644 index 0000000000..f86e81b477 Binary files /dev/null and b/Telegram-Mac/emoji14.txt differ diff --git a/Telegram-Mac/en.lproj/Localizable.strings b/Telegram-Mac/en.lproj/Localizable.strings index 2ad26b3e85..22470ec311 100644 Binary files a/Telegram-Mac/en.lproj/Localizable.strings and b/Telegram-Mac/en.lproj/Localizable.strings differ diff --git a/Telegram-Mac/es.lproj/MainMenu.xib b/Telegram-Mac/es.lproj/MainMenu.xib new file mode 100644 index 0000000000..57e428c68e --- /dev/null +++ b/Telegram-Mac/es.lproj/MainMenu.xib @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/instantPageWebEmbedView.swift b/Telegram-Mac/instantPageWebEmbedView.swift index df29001bd8..328f36449c 100644 --- a/Telegram-Mac/instantPageWebEmbedView.swift +++ b/Telegram-Mac/instantPageWebEmbedView.swift @@ -7,40 +7,120 @@ // import Cocoa -import TelegramCoreMac +import TelegramCore +import SyncCore import TGUIKit import WebKit -final class instantPageWebEmbedView: View, InstantPageView { +private final class InstantPageWebView : WKWebView { + + var enableScrolling: Bool = true + + override func scrollWheel(with event: NSEvent) { + if enableScrolling { + super.scrollWheel(with: event) + } else { + if event.scrollingDeltaX != 0 { + super.scrollWheel(with: event) + } else { + super.enclosingScrollView?.scrollWheel(with: event) + } + } + } +} + + +private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + +final class InstantPageWebEmbedView: View, InstantPageView { let url: String? let html: String? - private let webView: WebView - - init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool) { + private var webView: InstantPageWebView! + let updateWebEmbedHeight: (CGFloat) -> Void + init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool, updateWebEmbedHeight: @escaping(CGFloat) -> Void) { self.url = url self.html = html + self.updateWebEmbedHeight = updateWebEmbedHeight + super.init() - self.webView = WebView(frame: CGRect(origin: CGPoint(), size: frame.size)) + + + let js = "var TelegramWebviewProxyProto = function() {}; " + + "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + + "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + + "}; " + + "var TelegramWebviewProxy = new TelegramWebviewProxyProto();" + + let configuration = WKWebViewConfiguration() + let userController = WKUserContentController() + + let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) + userController.addUserScript(userScript) + + userController.add(WeakInstantPageWebEmbedNodeMessageHandler { [weak self] message in + if let strongSelf = self { + strongSelf.handleScriptMessage(message) + } + }, name: "performAction") + + configuration.userContentController = userController + + let webView = InstantPageWebView(frame: CGRect(origin: CGPoint(), size: frame.size), configuration: configuration) + - webView.background = theme.colors.background - super.init() if let html = html { - self.webView.mainFrame.loadHTMLString(html, baseURL: nil) + webView.loadHTMLString(html, baseURL: nil) } else if let url = url, let parsedUrl = URL(string: url) { var request = URLRequest(url: parsedUrl) - let referrer = "\(parsedUrl.scheme ?? "")://\(parsedUrl.host ?? "")" - request.setValue(referrer, forHTTPHeaderField: "Referer") - self.webView.mainFrame.load(request) + if let scheme = parsedUrl.scheme, let host = parsedUrl.host { + let referrer = "\(scheme)://\(host)" + request.setValue(referrer, forHTTPHeaderField: "Referer") + } + webView.load(request) } + self.webView = webView + webView.enableScrolling = enableScrolling addSubview(webView) } + private func handleScriptMessage(_ message: WKScriptMessage) { + guard let body = message.body as? [String: Any] else { + return + } + + guard let eventName = body["eventName"] as? String, let eventString = body["eventData"] as? String else { + return + } + + guard let eventData = eventString.data(using: .utf8) else { + return + } + + guard let dict = (try? JSONSerialization.jsonObject(with: eventData, options: [])) as? [String: Any] else { + return + } + + if eventName == "resize_frame", let height = dict["height"] as? Int { + self.updateWebEmbedHeight(CGFloat(height)) + } + } deinit { - webView.mainFrame.load(URLRequest(url: URL(string:"file://blank")!)) - webView.mainFrame.stopLoading() + } required init?(coder: NSCoder) { diff --git a/Telegram-Mac/it.lproj/MainMenu.xib b/Telegram-Mac/it.lproj/MainMenu.xib new file mode 100644 index 0000000000..57e428c68e --- /dev/null +++ b/Telegram-Mac/it.lproj/MainMenu.xib @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/lottie/anim_archive.json b/Telegram-Mac/lottie/anim_archive.json new file mode 100644 index 0000000000..832dacf3a3 --- /dev/null +++ b/Telegram-Mac/lottie/anim_archive.json @@ -0,0 +1 @@ +{"v":"5.5.1","fr":60,"ip":0,"op":30,"w":228,"h":228,"nm":"Archiveic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"box3","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[30]},{"t":20,"s":[0]}],"ix":10},"p":{"a":0,"k":[30.5,-39,0],"ix":2},"a":{"a":0,"k":[30.5,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":[[-1.1,0],[0,0],[0,-1.1],[0,0],[0,0],[0,0]],"o":[[0,0],[1.1,0],[0,0],[0,0],[0,0],[0,-1.1]],"v":[[-11,-3],[11,-3],[13,-1],[13,3],[-13,3],[-13,-1]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"box3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"box2","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-13.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[10,3],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":1,"ix":4},"nm":"Rectangle Outline 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.662744998932,0.662744998932,0.678430974483,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":[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":"Transform"}],"nm":"box2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"box1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114,93,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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":0,"s":[0,0,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":15,"s":[110,110,100]},{"t":20,"s":[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],[0,0],[1.1,0],[0,0],[0,1.1]],"o":[[0,0],[0,0],[0,1.1],[0,0],[-1.1,0],[0,0]],"v":[[-12,-9],[12,-9],[12,7],[10,9],[-10,9],[-12,7]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"box1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_delete.json b/Telegram-Mac/lottie/anim_delete.json new file mode 100644 index 0000000000..23b20fd472 --- /dev/null +++ b/Telegram-Mac/lottie/anim_delete.json @@ -0,0 +1 @@ +{"v":"5.1.2","fr":60,"ip":0,"op":3600,"w":228,"h":228,"nm":"delete","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"bin2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114,94,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-0p667_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,0.833]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_0p833"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-3.112,0],[0,0],[-0.269,3.1],[0,0]],"o":[[0,0],[0.269,3.1],[0,0],[3.113,0],[0,0],[0,0]],"v":[[-27,-34.5],[-21.476,29.02],[-15.499,34.5],[15.499,34.5],[21.476,29.02],[27,-34.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,1.057],[-1.884,32.041],[-1.107,0],[0,-1.057],[1.885,-32.041],[1.107,0]],"o":[[0,-0.038],[0.065,-1.105],[1.057,0],[0,0.038],[-0.065,1.105],[-1.058,0]],"v":[[8.586,23.086],[11.413,-25.032],[13.5,-27],[15.414,-25.086],[12.587,23.032],[10.5,25]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,1.105],[0,0],[-1.105,0],[0,-1.105],[0,0],[1.105,0]],"o":[[0,0],[0,-1.105],[1.105,0],[0,0],[0,1.105],[-1.105,0]],"v":[[-2,23],[-2,-25],[0,-27],[2,-25],[2,23],[0,25]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.065,1.105],[0,0],[-1.057,0],[-0.066,-1.105],[0,0],[1.057,0]],"o":[[0,0],[-0.004,-1.17],[1.107,0],[0,0],[0.004,1.17],[-1.107,0]],"v":[[-12.587,23.032],[-15.411,-24.973],[-13.5,-27],[-11.412,-25.032],[-8.589,22.973],[-10.5,25]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256.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":"Group 1","np":6,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"bin1 Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[45],"e":[0]},{"t":15}],"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":0,"s":[270,180,0],"e":[256,211.923,0],"to":[-2.33333325386047,5.32055473327637,0],"ti":[2.33333325386047,-5.32055473327637,0]},{"t":15}],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[100,100,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.542,-0.903],[0,0],[0,0],[0,-3.314],[0,0],[3.314,0],[0,0],[0,0],[1.054,0]],"o":[[-1.054,0],[0,0],[0,0],[-3.314,0],[0,0],[0,-3.314],[0,0],[0,0],[-0.542,-0.903],[0,0]],"v":[[-7.301,-6],[-9.874,-4.544],[-12.6,0],[-24,0],[-30,6],[30,6],[24,0],[12.6,0],[9.874,-4.544],[7.301,-6]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256],"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,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-130,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_group.json b/Telegram-Mac/lottie/anim_group.json new file mode 100644 index 0000000000..cc5a0437e1 --- /dev/null +++ b/Telegram-Mac/lottie/anim_group.json @@ -0,0 +1 @@ +{"v":"5.1.7","fr":60,"ip":0,"op":32,"w":228,"h":228,"nm":"group","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Oval 2 Copy 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[135.5,110.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":16,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":21,"s":[115,115,100],"e":[100,100,100]},{"t":31}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy 2","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Oval 2 Copy 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[93.5,110.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":4,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":14,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":19,"s":[115,115,100],"e":[100,100,100]},{"t":29}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy 3","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Oval 2 Copy","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[135.5,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":2,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":12,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":17,"s":[115,115,100],"e":[100,100,100]},{"t":27}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Oval 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[93.5,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_hide.json b/Telegram-Mac/lottie/anim_hide.json new file mode 100644 index 0000000000..b972f4c361 --- /dev/null +++ b/Telegram-Mac/lottie/anim_hide.json @@ -0,0 +1 @@ +{"v":"5.5.1","fr":60,"ip":0,"op":30,"w":228,"h":228,"nm":"Hide","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":[114,79.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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":0,"s":[0,0,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":15,"s":[110,110,100]},{"t":20,"s":[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":[[-11,0],[11,0]],"c":false},"ix":2},"nm":"Outline 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":2.2,"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":[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":"Transform"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path 2","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,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],[0,0],[0,0],[0.57,0],[0.06,-0.56],[0,0],[0,0],[0,0],[-0.56,0],[-0.07,0.56]],"o":[[0,0],[0,0],[0,0],[-0.06,-0.56],[-0.57,0],[0,0],[0,0],[0,0],[0.07,0.56],[0.56,0],[0,0]],"v":[[1.09,-3.5],[12,-3.5],[12,3.5],[1.1,3.5],[0,2.5],[-1.1,3.5],[-12,3.5],[-12,-3.5],[-1.09,-3.5],[0,-2.5],[1.09,-3.5]],"c":true},"ix":2},"nm":"Outline 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.741176470588,0.741176470588,0.760784313725,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":[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":"Transform"}],"nm":"Path 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path","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.667,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[0,-4,0],"to":[0,-0.167,0],"ti":[0,-1.167,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":15,"s":[0,-5,0],"to":[0,1.167,0],"ti":[0,-1.333,0]},{"t":20,"s":[0,3,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],[0.6,0],[0,0.61],[0,0],[0,0],[0.41,0.45],[-0.45,0.41],[0,0],[-0.42,-0.39],[0,0],[0.41,-0.45],[0.45,0.41]],"o":[[0,0],[0,0.61],[-0.61,0],[0,0],[0,0],[-0.44,0.41],[-0.41,-0.45],[0,0],[0.42,-0.39],[0,0],[0.44,0.41],[-0.41,0.45],[0,0]],"v":[[1.103,-8],[1.103,10.5],[0.003,11.6],[-1.097,10.5],[-1.097,-8],[-3.927,-5.4],[-5.477,-5.47],[-5.407,-7.02],[-0.747,-11.31],[0.743,-11.31],[5.413,-7.02],[5.473,-5.47],[3.923,-5.4]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_mute.json b/Telegram-Mac/lottie/anim_mute.json new file mode 100644 index 0000000000..a8d3b47c04 --- /dev/null +++ b/Telegram-Mac/lottie/anim_mute.json @@ -0,0 +1 @@ +{"v":"5.1.2","fr":60,"ip":0,"op":3600,"w":228,"h":228,"nm":"mute","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"un Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[117.875,88.875,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.863,0.863,-0.19]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p863_0p333_0","0p833_0p863_0p333_0","0p833_-0p19_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.365,0.365,0.476]},"n":["0p667_1_0p167_0p365","0p667_1_0p167_0p365","0p667_1_0p167_0p476"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":14,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"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":5,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[-33.992,-34]],"c":false}],"e":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[34,34]],"c":false}]},{"t":15}],"ix":2,"x":"var $bm_rt;\n$bm_rt = content('Group 1').content('Path 1').path;"},"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":6,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.584313750267,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":15,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256],"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":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"mute Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[111,88.781,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.863,0.863,-12.69]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p863_0p333_0","0p833_0p863_0p333_0","0p833_-12p69_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.365,0.365,5.476]},"n":["0p667_1_0p167_0p365","0p667_1_0p167_0p365","0p667_1_0p167_5p476"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":14,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.995,-0.904],[0,0],[0.746,0],[0,0],[0,-3.866],[0,0],[-3.866,0],[0,0],[-0.552,-0.503],[0,0],[-2.006,2.207],[0,1.343],[0,0],[2.982,0]],"o":[[0,0],[-0.552,0.502],[0,0],[-3.866,0],[0,0],[0,3.866],[0,0],[0.746,0],[0,0],[2.207,2.007],[0.903,-0.995],[0,0],[0,-2.982],[-1.343,0]],"v":[[16.967,-36.589],[-6.142,-15.581],[-8.16,-14.801],[-19,-14.801],[-26,-7.801],[-26,7.199],[-19,14.199],[-8.16,14.199],[-6.142,14.98],[16.967,35.987],[24.596,35.625],[26,31.992],[26,-32.594],[20.6,-37.994]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256.994],"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,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_pin.json b/Telegram-Mac/lottie/anim_pin.json new file mode 100644 index 0000000000..adb1491446 --- /dev/null +++ b/Telegram-Mac/lottie/anim_pin.json @@ -0,0 +1 @@ +{"v":"5.1.2","fr":60,"ip":0,"op":3600,"w":228,"h":228,"nm":"pinchat","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pin Outlines","sr":1,"ks":{"o":{"a":0,"k":99,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[113.656,86.516,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-10.015]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-10p015_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,5.508]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_5p508"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":24}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.682,-2.385],[0,0],[7.227,-5.009],[0.494,-0.402],[0.078,-0.079],[-1.265,-1.266],[0,0],[-0.087,-0.07],[-1.129,1.388],[-0.211,0.297],[2.361,8.281],[0,0],[3.76,3.76],[0,0],[2.035,0]],"o":[[0,0],[-8.176,-2.331],[-0.37,0.258],[-0.086,0.07],[-1.265,1.265],[0,0],[0.079,0.078],[1.388,1.129],[0.312,-0.384],[5.172,-7.273],[0,0],[4.348,-3.067],[0,0],[-1.701,-1.701],[-2.465,0]],"v":[[10.943,-36.491],[-1.24,-19.219],[-25.643,-15.202],[-26.94,-14.213],[-27.187,-13.99],[-27.187,-9.408],[8.801,26.579],[9.049,26.802],[13.607,26.332],[14.391,25.311],[18.607,0.628],[35.879,-11.556],[36.955,-23.925],[23.313,-37.567],[17.514,-40.1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-40.714,40.1],[-7.985,15.793],[-16.406,7.372]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256.714,257.1],"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":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_read.json b/Telegram-Mac/lottie/anim_read.json new file mode 100644 index 0000000000..de017a73ed --- /dev/null +++ b/Telegram-Mac/lottie/anim_read.json @@ -0,0 +1 @@ +{"v":"4.5.6","fr":60,"ip":0,"op":30,"w":228,"h":228,"ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Combined Shape","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[114,89.499,0]},"a":{"a":0,"k":[40,38.499,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167","0p667_0p667_0p167_0p167"],"t":0,"s":[0,0,100],"e":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":15,"s":[110,110,100],"e":[100,100,100]},{"t":20}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.39,0],[0,19.29],[22.09,0],[0,-19.28],[-9.1,-6.4],[2.59,-3.82],[-1.62,-0.65],[-4.27,2.3],[-1.31,-0.29]],"o":[[22.09,0],[0,-19.28],[-22.09,0],[0,11],[1.17,0.82],[-2.6,3.82],[1,0.4],[6.1,-3.28],[3.14,0.69]],"v":[[40,69.85],[80,34.92],[40,0],[0,34.92],[14.45,61.34],[14.1,70.53],[9.89,76.75],[21.07,75.05],[30.18,68.79]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"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,"mn":"ADBE Vector Group"}],"ip":0,"op":30,"st":0,"bm":0,"sr":1}]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_unarchive.json b/Telegram-Mac/lottie/anim_unarchive.json new file mode 100644 index 0000000000..8105c51653 --- /dev/null +++ b/Telegram-Mac/lottie/anim_unarchive.json @@ -0,0 +1 @@ +{"v":"5.5.1","fr":60,"ip":0,"op":60,"w":288,"h":288,"nm":"Unarchive","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"box2","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.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[0,-9,0],"to":[0,3.833,0],"ti":[0,-3.833,0]},{"t":15,"s":[0,14,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[10,3],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":1,"ix":4},"nm":"Rectangle Outline 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.662745098039,0.662745098039,0.678431372549,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":[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":"Transform"}],"nm":"box2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"box1","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,18,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":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[1.1,0],[0,0],[0,1.1]],"o":[[0,0],[0,0],[0,1.1],[0,0],[-1.1,0],[0,0]],"v":[[-12,-9],[12,-9],[12,7],[10,9],[-10,9],[-12,7]],"c":true}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0,0],[1.1,0],[0,0],[0,1.1]],"o":[[0,0],[0,0],[0,1.1],[0,0],[-1.1,0],[0,0]],"v":[[-12,-5.167],[12,-5.167],[12,7],[10,9],[-10,9],[-12,7]],"c":true}]}],"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"box1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path 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.067,"y":1},"o":{"x":0.936,"y":0},"t":0,"s":[0,28.621,0],"to":[0,-13.167,0],"ti":[0,13.167,0]},{"t":15,"s":[0,-50.379,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.1,0.09],[0,0],[-0.2,-0.2],[0,0],[0.19,-0.2],[0.13,0],[0,0],[0,0],[0.28,0],[0,0],[0,0.28],[0,0],[0,0],[0,0.28]],"o":[[0,0],[0.19,-0.2],[0,0],[0.19,0.19],[-0.09,0.09],[0,0],[0,0],[0,0.28],[0,0],[-0.27,0],[0,0],[0,0],[-0.28,0],[0,-0.13]],"v":[[-5.143,0.544],[-0.353,-4.246],[0.357,-4.246],[5.147,0.544],[5.147,1.254],[4.797,1.394],[1.997,1.394],[1.997,3.894],[1.497,4.394],[-1.503,4.394],[-2.003,3.894],[-2.003,1.394],[-4.793,1.394],[-5.293,0.894]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"Path 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"scale","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144,144,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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":0,"s":[0,0,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":15,"s":[110,110,100]},{"t":20,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_ungroup.json b/Telegram-Mac/lottie/anim_ungroup.json new file mode 100644 index 0000000000..0ad2298e46 --- /dev/null +++ b/Telegram-Mac/lottie/anim_ungroup.json @@ -0,0 +1 @@ +{"v":"5.1.7","fr":60,"ip":0,"op":32,"w":228,"h":228,"nm":"ungroup","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"un Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114.75,89.312,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":24}],"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":5,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[-33.992,-34]],"c":false}],"e":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[34,34]],"c":false}]},{"t":15}],"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":6,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"st","c":{"a":0,"k":[0.097999999102,0.57599995931,0.980000035903,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":15,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256],"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":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Oval 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[93.5,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Oval 2 Copy","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[135.5,68.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":2,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":12,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":17,"s":[115,115,100],"e":[100,100,100]},{"t":27}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Oval 2 Copy 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[93.5,110.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":4,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":14,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":19,"s":[115,115,100],"e":[100,100,100]},{"t":29}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy 3","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Oval 2 Copy 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[135.5,110.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[0,0,100],"e":[100,100,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":16,"s":[100,100,100],"e":[115,115,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]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":21,"s":[115,115,100],"e":[100,100,100]},{"t":31}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[33,33],"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":[1,1,1,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":"Oval 2 Copy 2","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":32,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_unmute.json b/Telegram-Mac/lottie/anim_unmute.json new file mode 100644 index 0000000000..f1059e9a2d --- /dev/null +++ b/Telegram-Mac/lottie/anim_unmute.json @@ -0,0 +1 @@ +{"v":"5.1.2","fr":60,"ip":0,"op":3600,"w":228,"h":228,"nm":"unmute","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"vol1 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"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":0,"s":[120,90,0],"e":[131,90,0],"to":[1.83333337306976,0,0],"ti":[-1.83333337306976,0,0]},{"t":10}],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.577,-0.531],[0.016,-0.789],[0,0],[-0.516,-0.561],[0,-5.477],[4.174,-4.561],[0.014,-0.704],[0,0],[-0.631,-0.578],[-1.119,1.223],[0,7.023],[5.163,5.605],[0.809,0]],"o":[[-0.627,0.576],[0,0],[0.015,0.706],[4.17,4.526],[0,5.479],[-0.513,0.56],[0,0],[0.016,0.793],[1.222,1.118],[5.16,-5.636],[0,-7.025],[-0.592,-0.641],[-0.726,0]],"v":[[-5.533,-19.371],[-6.5,-17.224],[-6.5,-17.104],[-5.707,-15.133],[0.5,-0.19],[-5.713,14.81],[-6.5,16.777],[-6.5,16.892],[-5.526,19.048],[-1.287,18.859],[6.5,-0.19],[-1.293,-19.198],[-3.501,-20.165]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[255.5,256.165],"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,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"vol2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"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":2,"s":[120,90,0],"e":[146,90,0],"to":[4.33333349227905,0,0],"ti":[-4.33333349227905,0,0]},{"t":12}],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":2,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":12,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":17,"s":[115,115,100],"e":[100,100,100]},{"t":27}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.56,-0.468],[-1.06,-1.273],[0,-8.078],[6.916,-8.351],[-1.276,-1.057],[-1.057,1.275],[0,9.591],[7.755,9.312],[0.861,0]],"o":[[-1.274,1.06],[6.912,8.299],[0,8.082],[-1.057,1.276],[1.276,1.056],[7.751,-9.362],[0,-9.592],[-0.593,-0.713],[-0.675,0]],"v":[[-7.235,-28.988],[-7.622,-24.763],[2.684,-0.221],[-7.627,24.404],[-7.229,28.628],[-3.005,28.23],[8.684,-0.221],[-3.011,-28.603],[-5.319,-29.684]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[255.316,256.684],"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,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"vol3 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"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":4,"s":[120,89.062,0],"e":[162,89.062,0],"to":[7,0,0],"ti":[-7,0,0]},{"t":14}],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":4,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":14,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":19,"s":[115,115,100],"e":[100,100,100]},{"t":29}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.563,-0.475],[-1.067,-1.267],[0,-12.342],[9.568,-11.435],[-1.27,-1.063],[-0.665,-0.01],[0,0],[-0.582,0.697],[0,13.826],[10.437,12.396],[0.855,0]],"o":[[-1.268,1.066],[9.564,11.36],[0,12.345],[-1.063,1.271],[0.549,0.459],[0,0],[0.842,-0.013],[10.432,-12.468],[0,-13.827],[-0.593,-0.705],[-0.683,0]],"v":[[-9.25,-39.794],[-9.613,-35.568],[4.682,-0.056],[-9.619,35.575],[-9.243,39.801],[-7.364,40.5],[-7.274,40.5],[-5.017,39.425],[10.682,-0.056],[-5.024,-39.432],[-7.319,-40.5]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[255.318,256.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":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"mute Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"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":0,"s":[100,88.781,0],"e":[85,88.781,0],"to":[-2.5,0,0],"ti":[2.5,0,0]},{"t":10}],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":25}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.995,-0.904],[0,0],[0.746,0],[0,0],[0,-3.866],[0,0],[-3.866,0],[0,0],[-0.552,-0.503],[0,0],[-2.006,2.207],[0,1.343],[0,0],[2.982,0]],"o":[[0,0],[-0.552,0.502],[0,0],[-3.866,0],[0,0],[0,3.866],[0,0],[0.746,0],[0,0],[2.207,2.007],[0.903,-0.995],[0,0],[0,-2.982],[-1.343,0]],"v":[[16.967,-36.589],[-6.142,-15.581],[-8.16,-14.801],[-19,-14.801],[-26,-7.801],[-26,7.199],[-19,14.199],[-8.16,14.199],[-6.142,14.98],[16.967,35.987],[24.596,35.625],[26,31.992],[26,-32.594],[20.6,-37.994]],"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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256.994],"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,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_unpin.json b/Telegram-Mac/lottie/anim_unpin.json new file mode 100644 index 0000000000..3a0750f7a8 --- /dev/null +++ b/Telegram-Mac/lottie/anim_unpin.json @@ -0,0 +1 @@ +{"v":"5.1.2","fr":60,"ip":0,"op":3600,"w":228,"h":228,"nm":"unpinpinchat","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"un Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[118,84.062,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.872,0.872,-11.778]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p872_0p333_0","0p833_0p872_0p333_0","0p833_-11p778_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.426,0.426,6.389]},"n":["0p667_1_0p167_0p426","0p667_1_0p167_0p426","0p667_1_0p167_6p389"],"t":10,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":24}],"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":5,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[-33.992,-34]],"c":false}],"e":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-34,-34],[34,34]],"c":false}]},{"t":15}],"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":6,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"st","c":{"a":0,"k":[0.097999999102,0.57599995931,0.980000035903,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":15,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[256,256],"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":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"pin Outlines","sr":1,"ks":{"o":{"a":0,"k":99,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[113.656,86.516,0],"ix":2},"a":{"a":0,"k":[256,256,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.859,0.859,-13.056]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p833_0p859_0p333_0","0p833_0p859_0p333_0","0p833_-13p056_0p333_0"],"t":0,"s":[0,0,100],"e":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.341,0.341,5.111]},"n":["0p667_1_0p167_0p341","0p667_1_0p167_0p341","0p667_1_0p167_5p111"],"t":11,"s":[100,100,100],"e":[115,115,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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[115,115,100],"e":[100,100,100]},{"t":24}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.682,-2.385],[0,0],[7.227,-5.009],[0.494,-0.402],[0.078,-0.079],[-1.265,-1.266],[0,0],[-0.087,-0.07],[-1.129,1.388],[-0.211,0.297],[2.361,8.281],[0,0],[3.76,3.76],[0,0],[2.035,0]],"o":[[0,0],[-8.176,-2.331],[-0.37,0.258],[-0.086,0.07],[-1.265,1.265],[0,0],[0.079,0.078],[1.388,1.129],[0.312,-0.384],[5.172,-7.273],[0,0],[4.348,-3.067],[0,0],[-1.701,-1.701],[-2.465,0]],"v":[[10.943,-36.491],[-1.24,-19.219],[-25.643,-15.202],[-26.94,-14.213],[-27.187,-13.99],[-27.187,-9.408],[8.801,26.579],[9.049,26.802],[13.607,26.332],[14.391,25.311],[18.607,0.628],[35.879,-11.556],[36.955,-23.925],[23.313,-37.567],[17.514,-40.1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-40.714,40.1],[-7.985,15.793],[-16.406,7.372]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 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,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[256.714,257.1],"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":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/anim_unread.json b/Telegram-Mac/lottie/anim_unread.json new file mode 100644 index 0000000000..b648c6da3f --- /dev/null +++ b/Telegram-Mac/lottie/anim_unread.json @@ -0,0 +1 @@ +{"v":"4.5.6","fr":60,"ip":0,"op":30,"w":228,"h":228,"ddd":0,"assets":[],"layers":[{"ddd":0,"ind":0,"ty":4,"nm":"Oval","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[144,60,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167","0p667_0p667_0p167_0p167"],"t":10,"s":[0,0,100],"e":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":19,"s":[110,110,100],"e":[100,100,100]},{"t":24}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"st","c":{"a":0,"k":[0.13,0.58,0.98,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":5.333},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"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":3,"mn":"ADBE Vector Group"}],"ip":0,"op":30,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":1,"ty":4,"nm":"Combined Shape","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[114,89.499,0]},"a":{"a":0,"k":[40,38.499,0]},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p667_1_0p167_0p167","0p667_1_0p167_0p167","0p667_0p667_0p167_0p167"],"t":0,"s":[0,0,100],"e":[110,110,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,0.667]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0.333]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_0p667_0p333_0p333"],"t":15,"s":[110,110,100],"e":[100,100,100]},{"t":20}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-3.39,0],[0,19.29],[22.09,0],[0,-19.28],[-9.1,-6.4],[2.59,-3.82],[-1.62,-0.65],[-4.27,2.3],[-1.31,-0.29]],"o":[[22.09,0],[0,-19.28],[-22.09,0],[0,11],[1.17,0.82],[-2.6,3.82],[1,0.4],[6.1,-3.28],[3.14,0.69]],"v":[[40,69.85],[80,34.92],[40,0],[0,34.92],[14.45,61.34],[14.1,70.53],[9.89,76.75],[21.07,75.05],[30.18,68.79]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"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,"mn":"ADBE Vector Group"}],"ip":0,"op":30,"st":0,"bm":0,"sr":1}]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/archiveAvatar.json b/Telegram-Mac/lottie/archiveAvatar.json new file mode 100644 index 0000000000..56ba375ec1 --- /dev/null +++ b/Telegram-Mac/lottie/archiveAvatar.json @@ -0,0 +1 @@ +{"v":"5.5.1","fr":60,"ip":0,"op":60,"w":288,"h":288,"nm":"ArchiveAvatar","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"box3","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":16,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[-10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[5]},{"t":36,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[144,84,0],"to":[0,0.932,0],"ti":[0,-2.239,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":12,"s":[144,59,0],"to":[0,0.817,0],"ti":[0,-1.967,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[144,84,0],"to":[0,0.838,0],"ti":[0,-0.449,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":22,"s":[144,69,0],"to":[0,1.429,0],"ti":[0,-2.755,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":28,"s":[144,84,0],"to":[0,1.416,0],"ti":[0,0.204,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[144,79,0],"to":[0,-0.33,0],"ti":[0,-0.384,0]},{"t":36,"s":[144,84,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":[[-1.1,0],[0,0],[0,-1.1],[0,0],[0,0],[0,0]],"o":[[0,0],[1.1,0],[0,0],[0,0],[0,0],[0,-1.1]],"v":[[-11,-3],[11,-3],[13,-1],[13,3],[-13,3],[-13,-1]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"box3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"box2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144,135,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[10,3],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":1,"ix":4},"nm":"Rectangle Outline 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.662745098039,0.662745098039,0.678431372549,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":[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":"Transform"}],"nm":"box2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"box1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[144,162,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],[0,0],[1.1,0],[0,0],[0,1.1]],"o":[[0,0],[0,0],[0,1.1],[0,0],[-1.1,0],[0,0]],"v":[[-12,-9],[12,-9],[12,7],[10,9],[-10,9],[-12,7]],"c":true},"ix":2},"nm":"Outline 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":[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":"Transform"}],"nm":"box1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/lottie/maccheck.json b/Telegram-Mac/lottie/maccheck.json new file mode 100644 index 0000000000..ab180a705b --- /dev/null +++ b/Telegram-Mac/lottie/maccheck.json @@ -0,0 +1 @@ +{"v":"5.5.1","fr":60,"ip":10,"op":35,"w":144,"h":144,"nm":"Artboard Copy 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,2.576,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],[0,0],[0.12,0.12],[0,0],[0,0]],"o":[[0,0],[0,0.01],[-0.12,0.12],[0,0],[0,0],[0,0]],"v":[[4.5,-3.429],[-1.29,3.321],[-1.3,3.341],[-1.73,3.341],[-4.5,0.571],[-4.5,0.571]],"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":[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":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.501],"y":[0]},"t":10,"s":[100]},{"t":20,"s":[0]}],"ix":1},"e":{"a":0,"k":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":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Oval2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[72,72,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"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,1.667]},"t":18,"s":[100,100,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":[110,110,100]},{"t":30,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","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,"ix":5},"lc":1,"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":[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":"Oval2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Oval1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[72,72,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"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":10,"s":[0,0,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":[110,110,100]},{"t":30,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.180391997099,0.650979995728,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":"Oval1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/mime-types.txt b/Telegram-Mac/mime-types.txt index d04913f1b4..aa8f0a77e4 100644 --- a/Telegram-Mac/mime-types.txt +++ b/Telegram-Mac/mime-types.txt @@ -19,6 +19,7 @@ aip:text/x-audiosoft-intra ani:application/x-navi-animation aos:application/x-nokia-9000-communicator-add-on-software aps:application/mime +tgs:application/x-tgsticker arc:application/octet-stream arj:application/arj arj:application/octet-stream @@ -247,6 +248,8 @@ m:text/plain m:text/x-m m1v:video/mpeg mp4:video/mp4 +mkv:video/mkv +m4v:video/m4v m2a:audio/mpeg m2v:video/mpeg m3u:audio/x-mpequrl @@ -287,7 +290,7 @@ mme:application/base64 mod:audio/mod mod:audio/x-mod moov:video/quicktime -mov:video/quicktime +mov:video/mov movie:video/x-sgi-movie mp2:audio/mpeg mp2:audio/x-mpeg @@ -559,7 +562,7 @@ wml:text/vnd.wap.wml wmlc:application/vnd.wap.wmlc wmls:text/vnd.wap.wmlscript wmlsc:application/vnd.wap.wmlscriptc -word:application/msword +doc:application/msword wp:application/wordperfect wp5:application/wordperfect wp5:application/wordperfect6.0 diff --git a/Telegram-Mac/nl.lproj/MainMenu.xib b/Telegram-Mac/nl.lproj/MainMenu.xib new file mode 100644 index 0000000000..57e428c68e --- /dev/null +++ b/Telegram-Mac/nl.lproj/MainMenu.xib @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/ocr_nn.bin b/Telegram-Mac/ocr_nn.bin new file mode 100755 index 0000000000..ccab89e554 Binary files /dev/null and b/Telegram-Mac/ocr_nn.bin differ diff --git a/Telegram-Mac/pt-BR.lproj/MainMenu.xib b/Telegram-Mac/pt-BR.lproj/MainMenu.xib new file mode 100644 index 0000000000..57e428c68e --- /dev/null +++ b/Telegram-Mac/pt-BR.lproj/MainMenu.xib @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram-Mac/ru.lproj/Localizable.strings b/Telegram-Mac/ru.lproj/Localizable.strings new file mode 100644 index 0000000000..ed60b89c47 Binary files /dev/null and b/Telegram-Mac/ru.lproj/Localizable.strings differ diff --git a/Telegram-Mac/ru.lproj/MainMenu.strings b/Telegram-Mac/ru.lproj/MainMenu.strings new file mode 100644 index 0000000000..8567b3ba83 --- /dev/null +++ b/Telegram-Mac/ru.lproj/MainMenu.strings @@ -0,0 +1,159 @@ + +/* Class = "NSMenuItem"; title = "TelegramMac"; ObjectID = "1Xt-HY-uBw"; */ +"1Xt-HY-uBw.title" = "TelegramMac"; + +/* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ +"2oI-Rn-ZJC.title" = "Transformations"; + +/* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ +"3IN-sU-3Bg.title" = "Spelling"; + +/* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ +"4J7-dP-txa.title" = "Enter Full Screen"; + +/* Class = "NSMenuItem"; title = "Quit Telegram"; ObjectID = "4sb-4s-VLi"; */ +"4sb-4s-VLi.title" = "Quit Telegram"; + +/* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ +"5QF-Oa-p0T.title" = "Edit"; + +/* Class = "NSMenuItem"; title = "About Telegram"; ObjectID = "5kV-Vb-QxS"; */ +"5kV-Vb-QxS.title" = "About Telegram"; + +/* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ +"6dh-zS-Vam.title" = "Redo"; + +/* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ +"78Y-hA-62v.title" = "Correct Spelling Automatically"; + +/* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ +"9ic-FL-obx.title" = "Substitutions"; + +/* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ +"9yt-4B-nSM.title" = "Smart Copy/Paste"; + +/* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ +"AYu-sK-qS6.title" = "Main Menu"; + +/* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ +"BOF-NM-1cW.title" = "Preferences…"; + +/* Class = "NSMenuItem"; title = "Hide Telegram"; ObjectID = "Cag-YX-WT6"; */ +"Cag-YX-WT6.title" = "Hide Telegram"; + +/* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ +"Dv1-io-Yv7.title" = "Spelling and Grammar"; + +/* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ +"FeM-D8-WVr.title" = "Substitutions"; + +/* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ +"H8h-7b-M4v.title" = "View"; + +/* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ +"HFQ-gK-NFA.title" = "Text Replacement"; + +/* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ +"HFo-cy-zxI.title" = "Show Spelling and Grammar"; + +/* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ +"HyV-fh-RgO.title" = "View"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ +"Kd2-mp-pUS.title" = "Show All"; + +/* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ +"LE2-aR-0XJ.title" = "Bring All to Front"; + +/* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ +"OY7-WF-poV.title" = "Minimize"; + +/* Class = "NSMenuItem"; title = "Hide"; ObjectID = "Olw-nP-bQN"; */ +"Olw-nP-bQN.title" = "Hide"; + +/* Class = "NSWindow"; title = "Telegram"; ObjectID = "QvC-M9-y7g"; */ +"QvC-M9-y7g.title" = "Telegram"; + +/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ +"R4o-n2-Eq4.title" = "Zoom"; + +/* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ +"Ruw-6m-B2m.title" = "Select All"; + +/* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ +"Td7-aD-5lo.title" = "Window"; + +/* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ +"UEZ-Bs-lqG.title" = "Capitalize"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ +"Vdr-fp-XzO.title" = "Hide Others"; + +/* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ +"W48-6f-4Dl.title" = "Edit"; + +/* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ +"WeT-3V-zwk.title" = "Paste and Match Style"; + +/* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ +"aUF-d1-5bR.title" = "Window"; + +/* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ +"c8a-y6-VQd.title" = "Transformations"; + +/* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ +"cwL-P1-jid.title" = "Smart Links"; + +/* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ +"d9M-CD-aMd.title" = "Make Lower Case"; + +/* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ +"dRJ-4n-Yzg.title" = "Undo"; + +/* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ +"gVA-U4-sdL.title" = "Paste"; + +/* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ +"hQb-2v-fYv.title" = "Smart Quotes"; + +/* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ +"hz2-CU-CR7.title" = "Check Document Now"; + +/* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ +"mK6-2p-4JG.title" = "Check Grammar With Spelling"; + +/* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ +"pa3-QI-u2k.title" = "Delete"; + +/* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ +"rbD-Rh-wIN.title" = "Check Spelling While Typing"; + +/* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ +"rgM-f4-ycn.title" = "Smart Dashes"; + +/* Class = "NSMenuItem"; title = "Quick Switcher"; ObjectID = "sZh-ct-GQS"; */ +"sZh-ct-GQS.title" = "Quick Switcher"; + +/* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ +"tRr-pd-1PS.title" = "Data Detectors"; + +/* Class = "NSMenu"; title = "TelegramMac"; ObjectID = "uQy-DD-JDr"; */ +"uQy-DD-JDr.title" = "TelegramMac"; + +/* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ +"uRl-iY-unG.title" = "Cut"; + +/* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ +"vmV-6d-7jI.title" = "Make Upper Case"; + +/* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ +"x3v-GG-iWU.title" = "Copy"; + +/* Class = "NSMenuItem"; title = "Check for Updates"; ObjectID = "xey-M7-XVy"; */ +"xey-M7-XVy.title" = "Check for Updates"; + +/* Class = "NSMenuItem"; title = "Telegram"; ObjectID = "yUb-j6-EX5"; */ +"yUb-j6-EX5.title" = "Telegram"; + +/* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ +"z6F-FW-3nz.title" = "Show Substitutions"; diff --git a/Telegram-Mac/sound_a.caf b/Telegram-Mac/sound_a.caf new file mode 100644 index 0000000000..0a9f875f20 Binary files /dev/null and b/Telegram-Mac/sound_a.caf differ diff --git a/Telegram-Mac/sounds/confetti.mp3 b/Telegram-Mac/sounds/confetti.mp3 new file mode 100644 index 0000000000..9b3ad7553c Binary files /dev/null and b/Telegram-Mac/sounds/confetti.mp3 differ diff --git a/Telegram-Mac/sounds/quiz-correct.mp3 b/Telegram-Mac/sounds/quiz-correct.mp3 new file mode 100644 index 0000000000..1458478169 Binary files /dev/null and b/Telegram-Mac/sounds/quiz-correct.mp3 differ diff --git a/Telegram-Mac/sounds/quiz-incorrect.mp3 b/Telegram-Mac/sounds/quiz-incorrect.mp3 new file mode 100644 index 0000000000..bfb30453df Binary files /dev/null and b/Telegram-Mac/sounds/quiz-incorrect.mp3 differ diff --git a/Telegram-Mac/tgs/brilliant_loading.tgs b/Telegram-Mac/tgs/brilliant_loading.tgs new file mode 100644 index 0000000000..f89875bf97 Binary files /dev/null and b/Telegram-Mac/tgs/brilliant_loading.tgs differ diff --git a/Telegram-Mac/tgs/brilliant_static.tgs b/Telegram-Mac/tgs/brilliant_static.tgs new file mode 100644 index 0000000000..63e8e22514 Binary files /dev/null and b/Telegram-Mac/tgs/brilliant_static.tgs differ diff --git a/Telegram-Mac/tgs/chiken_born.tgs b/Telegram-Mac/tgs/chiken_born.tgs new file mode 100644 index 0000000000..e4b8e01649 Binary files /dev/null and b/Telegram-Mac/tgs/chiken_born.tgs differ diff --git a/Telegram-Mac/tgs/dart_idle.tgs b/Telegram-Mac/tgs/dart_idle.tgs new file mode 100644 index 0000000000..ee3753cf7e Binary files /dev/null and b/Telegram-Mac/tgs/dart_idle.tgs differ diff --git a/Telegram-Mac/tgs/dice_idle.tgs b/Telegram-Mac/tgs/dice_idle.tgs new file mode 100644 index 0000000000..aad33bffe1 Binary files /dev/null and b/Telegram-Mac/tgs/dice_idle.tgs differ diff --git a/Telegram-Mac/tgs/fly_dollar.tgs b/Telegram-Mac/tgs/fly_dollar.tgs new file mode 100644 index 0000000000..9876c9658e Binary files /dev/null and b/Telegram-Mac/tgs/fly_dollar.tgs differ diff --git a/Telegram-Mac/tgs/folder.tgs b/Telegram-Mac/tgs/folder.tgs new file mode 100644 index 0000000000..41b820a74d Binary files /dev/null and b/Telegram-Mac/tgs/folder.tgs differ diff --git a/Telegram-Mac/tgs/folder_empty.tgs b/Telegram-Mac/tgs/folder_empty.tgs new file mode 100644 index 0000000000..999bdabbf4 Binary files /dev/null and b/Telegram-Mac/tgs/folder_empty.tgs differ diff --git a/Telegram-Mac/tgs/folder_new.tgs b/Telegram-Mac/tgs/folder_new.tgs new file mode 100644 index 0000000000..ee8755f78a Binary files /dev/null and b/Telegram-Mac/tgs/folder_new.tgs differ diff --git a/Telegram-Mac/tgs/gift.tgs b/Telegram-Mac/tgs/gift.tgs new file mode 100644 index 0000000000..4e87c321ed Binary files /dev/null and b/Telegram-Mac/tgs/gift.tgs differ diff --git a/Telegram-Mac/tgs/graph_loading.tgs b/Telegram-Mac/tgs/graph_loading.tgs new file mode 100644 index 0000000000..28b03df976 Binary files /dev/null and b/Telegram-Mac/tgs/graph_loading.tgs differ diff --git a/Telegram-Mac/tgs/keyboard_typing.tgs b/Telegram-Mac/tgs/keyboard_typing.tgs new file mode 100644 index 0000000000..c8e9940a40 Binary files /dev/null and b/Telegram-Mac/tgs/keyboard_typing.tgs differ diff --git a/Telegram-Mac/tgs/keychain.tgs b/Telegram-Mac/tgs/keychain.tgs new file mode 100644 index 0000000000..8538275214 Binary files /dev/null and b/Telegram-Mac/tgs/keychain.tgs differ diff --git a/Telegram-Mac/tgs/monkey_see.tgs b/Telegram-Mac/tgs/monkey_see.tgs new file mode 100644 index 0000000000..07cca60ece Binary files /dev/null and b/Telegram-Mac/tgs/monkey_see.tgs differ diff --git a/Telegram-Mac/tgs/monkey_unsee.tgs b/Telegram-Mac/tgs/monkey_unsee.tgs new file mode 100644 index 0000000000..f16eca5ada Binary files /dev/null and b/Telegram-Mac/tgs/monkey_unsee.tgs differ diff --git a/Telegram-Mac/tgs/sad_man.tgs b/Telegram-Mac/tgs/sad_man.tgs new file mode 100644 index 0000000000..9285160db5 Binary files /dev/null and b/Telegram-Mac/tgs/sad_man.tgs differ diff --git a/Telegram-Mac/tgs/smart_guy.tgs b/Telegram-Mac/tgs/smart_guy.tgs new file mode 100644 index 0000000000..9e1cf7701b Binary files /dev/null and b/Telegram-Mac/tgs/smart_guy.tgs differ diff --git a/Telegram-Mac/tgs/success_saved.tgs b/Telegram-Mac/tgs/success_saved.tgs new file mode 100644 index 0000000000..f66fe78be0 --- /dev/null +++ b/Telegram-Mac/tgs/success_saved.tgs @@ -0,0 +1 @@ +{"v":"5.5.2","fr":60,"ip":0,"op":60,"w":200,"h":140,"nm":"saved","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"luc12","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-43.5,75.344,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-60,"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":"luc12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"luc11","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.5,75.344,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":60,"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":"luc11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"luc10","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,87,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"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":"Преобразовать"}],"nm":"luc10","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"luc9","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75.344,43.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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":30,"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":"luc9","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"luc8","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[87,0,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[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":"luc8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"luc7","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75.344,-43.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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-30,"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":"luc7","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"luc6","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.5,-75.344,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-60,"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":"luc6","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.526],"y":[0.407]},"t":2,"s":[60]},{"t":5,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":2,"s":[60]},{"i":{"x":[0.201],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[60]},{"t":20,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"luc5","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-87,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"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":"Преобразовать"}],"nm":"luc5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"luc4","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-43.5,-75.344,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":60,"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":"luc4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"luc3","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.344,-43.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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":30,"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":"luc3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"luc2","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-87,0,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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[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":"luc2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"luc1","parent":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.344,43.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":[[2.5,0],[-2.5,0]],"c":false},"ix":2},"nm":"contour","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":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"stroke","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":[600,600],"ix":3},"r":{"a":0,"k":-30,"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":"luc1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[40]},{"t":20,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.697],"y":[0.848]},"o":{"x":[0.527],"y":[0.407]},"t":2,"s":[40]},{"t":5,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":-15,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"info1","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.5,3.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],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-28.5,0.5],[-9.5,19.5],[28.5,-19.5]],"c":false},"ix":2},"nm":"contour","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,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","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":"Преобразовать"}],"nm":"info1","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.833],"y":[0.833]},"o":{"x":[0.799],"y":[0]},"t":5,"s":[0]},{"t":15,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"trim","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":61,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Oval 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100,70,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.214,0.214,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.609,0.609,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[55,55,100]},{"t":30,"s":[50,50,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"contour","mn":"ADBE Vector Shape - Ellipse","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","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":"Oval","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram-Mac/tgs/swap_money.tgs b/Telegram-Mac/tgs/swap_money.tgs new file mode 100644 index 0000000000..74bbb0bf3d Binary files /dev/null and b/Telegram-Mac/tgs/swap_money.tgs differ diff --git a/Telegram-Mac/tgs/think_spectacular.tgs b/Telegram-Mac/tgs/think_spectacular.tgs new file mode 100644 index 0000000000..2dd3799596 Binary files /dev/null and b/Telegram-Mac/tgs/think_spectacular.tgs differ diff --git a/Telegram-Mac/tgs/wallet_success_created.tgs b/Telegram-Mac/tgs/wallet_success_created.tgs new file mode 100644 index 0000000000..456cc1e2fe Binary files /dev/null and b/Telegram-Mac/tgs/wallet_success_created.tgs differ diff --git a/Telegram-Mac/tgs/write_words.tgs b/Telegram-Mac/tgs/write_words.tgs new file mode 100644 index 0000000000..d0bd460e3f Binary files /dev/null and b/Telegram-Mac/tgs/write_words.tgs differ diff --git a/Telegram-Mac/uk.lproj/Localizable.strings b/Telegram-Mac/uk.lproj/Localizable.strings new file mode 100644 index 0000000000..ed60b89c47 Binary files /dev/null and b/Telegram-Mac/uk.lproj/Localizable.strings differ diff --git a/Telegram-Mac/uk.lproj/MainMenu.strings b/Telegram-Mac/uk.lproj/MainMenu.strings new file mode 100644 index 0000000000..8567b3ba83 --- /dev/null +++ b/Telegram-Mac/uk.lproj/MainMenu.strings @@ -0,0 +1,159 @@ + +/* Class = "NSMenuItem"; title = "TelegramMac"; ObjectID = "1Xt-HY-uBw"; */ +"1Xt-HY-uBw.title" = "TelegramMac"; + +/* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */ +"2oI-Rn-ZJC.title" = "Transformations"; + +/* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */ +"3IN-sU-3Bg.title" = "Spelling"; + +/* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */ +"4J7-dP-txa.title" = "Enter Full Screen"; + +/* Class = "NSMenuItem"; title = "Quit Telegram"; ObjectID = "4sb-4s-VLi"; */ +"4sb-4s-VLi.title" = "Quit Telegram"; + +/* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */ +"5QF-Oa-p0T.title" = "Edit"; + +/* Class = "NSMenuItem"; title = "About Telegram"; ObjectID = "5kV-Vb-QxS"; */ +"5kV-Vb-QxS.title" = "About Telegram"; + +/* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */ +"6dh-zS-Vam.title" = "Redo"; + +/* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */ +"78Y-hA-62v.title" = "Correct Spelling Automatically"; + +/* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */ +"9ic-FL-obx.title" = "Substitutions"; + +/* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */ +"9yt-4B-nSM.title" = "Smart Copy/Paste"; + +/* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ +"AYu-sK-qS6.title" = "Main Menu"; + +/* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ +"BOF-NM-1cW.title" = "Preferences…"; + +/* Class = "NSMenuItem"; title = "Hide Telegram"; ObjectID = "Cag-YX-WT6"; */ +"Cag-YX-WT6.title" = "Hide Telegram"; + +/* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */ +"Dv1-io-Yv7.title" = "Spelling and Grammar"; + +/* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */ +"FeM-D8-WVr.title" = "Substitutions"; + +/* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ +"H8h-7b-M4v.title" = "View"; + +/* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */ +"HFQ-gK-NFA.title" = "Text Replacement"; + +/* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */ +"HFo-cy-zxI.title" = "Show Spelling and Grammar"; + +/* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ +"HyV-fh-RgO.title" = "View"; + +/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ +"Kd2-mp-pUS.title" = "Show All"; + +/* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ +"LE2-aR-0XJ.title" = "Bring All to Front"; + +/* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ +"OY7-WF-poV.title" = "Minimize"; + +/* Class = "NSMenuItem"; title = "Hide"; ObjectID = "Olw-nP-bQN"; */ +"Olw-nP-bQN.title" = "Hide"; + +/* Class = "NSWindow"; title = "Telegram"; ObjectID = "QvC-M9-y7g"; */ +"QvC-M9-y7g.title" = "Telegram"; + +/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ +"R4o-n2-Eq4.title" = "Zoom"; + +/* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */ +"Ruw-6m-B2m.title" = "Select All"; + +/* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ +"Td7-aD-5lo.title" = "Window"; + +/* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */ +"UEZ-Bs-lqG.title" = "Capitalize"; + +/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ +"Vdr-fp-XzO.title" = "Hide Others"; + +/* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */ +"W48-6f-4Dl.title" = "Edit"; + +/* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */ +"WeT-3V-zwk.title" = "Paste and Match Style"; + +/* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ +"aUF-d1-5bR.title" = "Window"; + +/* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */ +"c8a-y6-VQd.title" = "Transformations"; + +/* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */ +"cwL-P1-jid.title" = "Smart Links"; + +/* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */ +"d9M-CD-aMd.title" = "Make Lower Case"; + +/* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */ +"dRJ-4n-Yzg.title" = "Undo"; + +/* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */ +"gVA-U4-sdL.title" = "Paste"; + +/* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */ +"hQb-2v-fYv.title" = "Smart Quotes"; + +/* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */ +"hz2-CU-CR7.title" = "Check Document Now"; + +/* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */ +"mK6-2p-4JG.title" = "Check Grammar With Spelling"; + +/* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */ +"pa3-QI-u2k.title" = "Delete"; + +/* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */ +"rbD-Rh-wIN.title" = "Check Spelling While Typing"; + +/* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */ +"rgM-f4-ycn.title" = "Smart Dashes"; + +/* Class = "NSMenuItem"; title = "Quick Switcher"; ObjectID = "sZh-ct-GQS"; */ +"sZh-ct-GQS.title" = "Quick Switcher"; + +/* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */ +"tRr-pd-1PS.title" = "Data Detectors"; + +/* Class = "NSMenu"; title = "TelegramMac"; ObjectID = "uQy-DD-JDr"; */ +"uQy-DD-JDr.title" = "TelegramMac"; + +/* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */ +"uRl-iY-unG.title" = "Cut"; + +/* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */ +"vmV-6d-7jI.title" = "Make Upper Case"; + +/* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */ +"x3v-GG-iWU.title" = "Copy"; + +/* Class = "NSMenuItem"; title = "Check for Updates"; ObjectID = "xey-M7-XVy"; */ +"xey-M7-XVy.title" = "Check for Updates"; + +/* Class = "NSMenuItem"; title = "Telegram"; ObjectID = "yUb-j6-EX5"; */ +"yUb-j6-EX5.title" = "Telegram"; + +/* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */ +"z6F-FW-3nz.title" = "Show Substitutions"; diff --git a/Telegram-Mac/webp.m b/Telegram-Mac/webp.m index ae6f2e35cb..4114f8535f 100755 --- a/Telegram-Mac/webp.m +++ b/Telegram-Mac/webp.m @@ -25,7 +25,12 @@ CGImageRef convertFromWebP(NSData *imgData) { void *targetMemory = malloc((int)(targetBytesPerRow * targetContextSize.height)); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_11_2) + colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3); + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; CGContextRef targetContext = CGBitmapContextCreate(targetMemory, (int)targetContextSize.width, (int)targetContextSize.height, 8, targetBytesPerRow, colorSpace, bitmapInfo); diff --git a/Telegram.xcodeproj/project.pbxproj b/Telegram.xcodeproj/project.pbxproj index 575988a638..07731d0fdc 100644 --- a/Telegram.xcodeproj/project.pbxproj +++ b/Telegram.xcodeproj/project.pbxproj @@ -7,13 +7,288 @@ objects = { /* Begin PBXBuildFile section */ + 9F0367F0227208E000456348 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0367EF227208E000456348 /* QRCode.swift */; }; + 9F0367F22272108800456348 /* ProxyQRCodeRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0367F12272108800456348 /* ProxyQRCodeRowItem.swift */; }; + 9F0367F72273260A00456348 /* UndoTooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0367F62273260A00456348 /* UndoTooltipController.swift */; }; + 9F0368012277091800456348 /* LAnimationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0368002277091800456348 /* LAnimationButton.swift */; }; + 9F03680F22771A9700456348 /* anim_unarchive.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680222771A9600456348 /* anim_unarchive.json */; }; + 9F03681022771A9700456348 /* anim_ungroup.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680322771A9600456348 /* anim_ungroup.json */; }; + 9F03681122771A9700456348 /* archiveAvatar.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680422771A9600456348 /* archiveAvatar.json */; }; + 9F03681222771A9700456348 /* anim_read.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680522771A9600456348 /* anim_read.json */; }; + 9F03681322771A9700456348 /* anim_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680622771A9600456348 /* anim_delete.json */; }; + 9F03681422771A9700456348 /* anim_unread.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680722771A9600456348 /* anim_unread.json */; }; + 9F03681522771A9700456348 /* anim_hide.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680822771A9600456348 /* anim_hide.json */; }; + 9F03681622771A9700456348 /* anim_mute.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680922771A9600456348 /* anim_mute.json */; }; + 9F03681722771A9700456348 /* anim_unpin.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680A22771A9600456348 /* anim_unpin.json */; }; + 9F03681822771A9700456348 /* anim_group.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680B22771A9600456348 /* anim_group.json */; }; + 9F03681922771A9700456348 /* anim_archive.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680C22771A9600456348 /* anim_archive.json */; }; + 9F03681A22771A9700456348 /* anim_unmute.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680D22771A9700456348 /* anim_unmute.json */; }; + 9F03681B22771A9700456348 /* anim_pin.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F03680E22771A9700456348 /* anim_pin.json */; }; + 9F0AE6872191D29D00A8B53A /* ContextSearchMessageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0AE6862191D29D00A8B53A /* ContextSearchMessageItem.swift */; }; + 9F0AE6B52199904400A8B53A /* sound_a.caf in Resources */ = {isa = PBXBuildFile; fileRef = 9F0AE6B42199904400A8B53A /* sound_a.caf */; }; + 9F0AE6BC2199BBB900A8B53A /* MediaPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0AE6BB2199BBB900A8B53A /* MediaPlayerView.swift */; }; + 9F0AE6BE2199BEBE00A8B53A /* SVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0AE6BD2199BEBE00A8B53A /* SVideoController.swift */; }; + 9F0AE6C02199CDB500A8B53A /* SVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0AE6BF2199CDB500A8B53A /* SVideoView.swift */; }; + 9F0B8F171FFB7F1A00073D3F /* AccentColorRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0B8F161FFB7F1A00073D3F /* AccentColorRowItem.swift */; }; + 9F0E6F78203ED1380086699C /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F0E6F77203ED1380086699C /* AppKit.framework */; }; + 9F0E6F7A203EFE870086699C /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0E6F79203EFE870086699C /* Preferences.swift */; }; + 9F0F8E82226DCD1C00A97F6A /* OpmizeDatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0F8E81226DCD1C00A97F6A /* OpmizeDatabaseView.swift */; }; + 9F10CE8A20611536002DD61A /* PassportHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE8920611536002DD61A /* PassportHeaderItem.swift */; }; + 9F10CE8E20617C36002DD61A /* PassportInsertPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE8D20617C36002DD61A /* PassportInsertPasswordItem.swift */; }; + 9F10CE922061BE19002DD61A /* InputDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE912061BE19002DD61A /* InputDataController.swift */; }; + 9F10CE942061C8C8002DD61A /* InputDataControllerEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE932061C8C8002DD61A /* InputDataControllerEntries.swift */; }; + 9F10CE962061C98E002DD61A /* InputDataRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE952061C98E002DD61A /* InputDataRowItem.swift */; }; + 9F10CE9820626B1B002DD61A /* PassportDocumentRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE9720626B1B002DD61A /* PassportDocumentRowItem.swift */; }; + 9F10CE9A206284F8002DD61A /* InputDataDateRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE99206284F8002DD61A /* InputDataDateRowItem.swift */; }; + 9F127E15210B1F540080D709 /* PeerMediaVoiceRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F127E14210B1F540080D709 /* PeerMediaVoiceRowItem.swift */; }; + 9F12D343209251CF0072928B /* EditAccountInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F12D342209251CF0072928B /* EditAccountInfoItem.swift */; }; + 9F13EE172100B05300562E53 /* VCardContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F13EE162100B05300562E53 /* VCardContactController.swift */; }; + 9F13EE1B2100BFCA00562E53 /* VCardHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F13EE1A2100BFCA00562E53 /* VCardHeaderItem.swift */; }; + 9F147F6F223014EB00D71BD1 /* PasscodeControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F147F6E223014EB00D71BD1 /* PasscodeControllers.swift */; }; + 9F147F7322314B5500D71BD1 /* AccountContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D422922240403007B68BB /* AccountContext.swift */; }; + 9F147F7422314B5500D71BD1 /* SharedAccountContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D422B222415C9007B68BB /* SharedAccountContext.swift */; }; + 9F147F7522314F2700D71BD1 /* GlobalBadgeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AC9C171E1E687E0085C7DE /* GlobalBadgeNode.swift */; }; + 9F147F7722314FBE00D71BD1 /* PanelUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226742E1DBE2CB3000BA9ED /* PanelUtils.swift */; }; + 9F147F782231515800D71BD1 /* UpdaterNotifySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3D5F8522085B2000CB0CAA /* UpdaterNotifySettings.swift */; }; + 9F147F79223151D900D71BD1 /* RenderedTotalUnreadCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EC947218B459A002B3C56 /* RenderedTotalUnreadCount.swift */; }; + 9F147F7A2231533700D71BD1 /* InAppNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25F71471E410DEE0046AF4E /* InAppNotificationSettings.swift */; }; + 9F147F7C2231543800D71BD1 /* PeerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F147F7B2231543800D71BD1 /* PeerUtils.swift */; }; + 9F147F7D2231543800D71BD1 /* PeerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F147F7B2231543800D71BD1 /* PeerUtils.swift */; }; + 9F147F7E22315EC200D71BD1 /* ManageSharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B74022227F492006610E4 /* ManageSharedAccountInfo.swift */; }; + 9F147F7F22315EC200D71BD1 /* SharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B74042227F4F2006610E4 /* SharedAccountInfo.swift */; }; + 9F14CBF32007DEB300F22DA9 /* ChatWallpaperModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F14CBF22007DEB300F22DA9 /* ChatWallpaperModalController.swift */; }; + 9F14CBF52007DFD400F22DA9 /* Wallpapers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F14CBF42007DFD400F22DA9 /* Wallpapers.swift */; }; + 9F153D1421F0C7F800B95D82 /* WallpaperPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F153D1321F0C7F800B95D82 /* WallpaperPreviewController.swift */; }; + 9F153D1621F3662700B95D82 /* StoredMessageFromSearchPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F153D1521F3662700B95D82 /* StoredMessageFromSearchPeer.swift */; }; + 9F1668B82007E3BC00DD39FB /* ThemeGridControllerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1668B72007E3BC00DD39FB /* ThemeGridControllerItem.swift */; }; + 9F1668C62008A97000DD39FB /* builtin-wallpaper-0.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9F1668C52008A97000DD39FB /* builtin-wallpaper-0.jpg */; }; + 9F1668C82008F30900DD39FB /* ChatBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1668C72008F30900DD39FB /* ChatBackgroundView.swift */; }; + 9F17E5B9212F173900C25A65 /* AutoNightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F17E5B8212F173900C25A65 /* AutoNightViewController.swift */; }; + 9F17E5BB212F191F00C25A65 /* AutoNightThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F17E5BA212F191F00C25A65 /* AutoNightThemePreferences.swift */; }; + 9F18908D2237B5A400665EF5 /* InputURLFormatterModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F18908C2237B5A400665EF5 /* InputURLFormatterModalController.swift */; }; + 9F18908F2237E95400665EF5 /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C250BA931E6E84880057CD96 /* VideoToolbox.framework */; }; + 9F1890932238F3DC00665EF5 /* DownloadedFilesPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1890922238F3DC00665EF5 /* DownloadedFilesPaths.swift */; }; + 9F18DD93206D8FFD00A2AAD0 /* SecureIdVerificationDocumentsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F18DD91206D8FFD00A2AAD0 /* SecureIdVerificationDocumentsContext.swift */; }; + 9F18DD94206D8FFD00A2AAD0 /* SecureIdVerificationDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F18DD92206D8FFD00A2AAD0 /* SecureIdVerificationDocument.swift */; }; + 9F1962D82101458C00FFF048 /* VCardLocationRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1962D72101458C00FFF048 /* VCardLocationRowItem.swift */; }; + 9F1AE5E420B6D7AA002A9D8D /* LocationPlaceSuggestionRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1AE5E320B6D7AA002A9D8D /* LocationPlaceSuggestionRowItem.swift */; }; + 9F1AE5E620B70328002A9D8D /* LocationSendCurrentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1AE5E520B70328002A9D8D /* LocationSendCurrentItem.swift */; }; + 9F1BABAE21E5ECE70075C03E /* ChatUndoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1BABAD21E5ECE70075C03E /* ChatUndoManager.swift */; }; + 9F1BABB021E60DCC0075C03E /* UndoOverlayHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1BABAF21E60DCC0075C03E /* UndoOverlayHeaderView.swift */; }; + 9F1BC1A9223FDE6D00F21815 /* InputSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1BC1A8223FDE6D00F21815 /* InputSources.swift */; }; + 9F1C279321D38A96003CD033 /* InstantPageScrollableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1C279221D38A96003CD033 /* InstantPageScrollableItem.swift */; }; + 9F21A7CF21C1552D0037784F /* InstantPageTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F21A7CE21C1552D0037784F /* InstantPageTheme.swift */; }; + 9F21A7D321C167000037784F /* InstantPageImageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F21A7D221C167000037784F /* InstantPageImageItem.swift */; }; + 9F21A7D521C16CB90037784F /* InstantPagePeerReferenceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F21A7D421C16CB90037784F /* InstantPagePeerReferenceItem.swift */; }; + 9F21A7DC21C290E00037784F /* InstantPageTableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F21A7DB21C290E00037784F /* InstantPageTableItem.swift */; }; + 9F21F65520B5A72800332C85 /* LocationModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F21F65420B5A72800332C85 /* LocationModalController.swift */; }; + 9F21F65D20B5A9C900332C85 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F21F65C20B5A9C900332C85 /* MapKit.framework */; }; + 9F262D5F21BFD5BC006817CD /* LocalizationPreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F262D5E21BFD5BC006817CD /* LocalizationPreviewModalController.swift */; }; + 9F27EFF920C6B8EE00682B76 /* PassportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F10CE8720610127002DD61A /* PassportController.swift */; }; + 9F291CA12264E57F00C66267 /* BuildConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F291C9F2264E57F00C66267 /* BuildConfig.m */; }; + 9F354E9C2270630A006F1D42 /* HapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F354E9B2270630A006F1D42 /* HapticEngine.swift */; }; + 9F3D5F6322044D8700CB0CAA /* AppUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3D5F6222044D8700CB0CAA /* AppUpdateViewController.swift */; }; + 9F3D5F8622085B2000CB0CAA /* UpdaterNotifySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3D5F8522085B2000CB0CAA /* UpdaterNotifySettings.swift */; }; + 9F3EAB3B20A5A1EC003FE7E3 /* NetworkUsageStatsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EAB3A20A5A1EC003FE7E3 /* NetworkUsageStatsController.swift */; }; + 9F3EAB4420A5ED2F003FE7E3 /* ocr.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EAB3E20A5ED2F003FE7E3 /* ocr.mm */; }; + 9F3EAB4520A5ED2F003FE7E3 /* ocr_nn.bin in Resources */ = {isa = PBXBuildFile; fileRef = 9F3EAB3F20A5ED2F003FE7E3 /* ocr_nn.bin */; }; + 9F3EAB4620A5ED2F003FE7E3 /* genann.c in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EAB4220A5ED2F003FE7E3 /* genann.c */; }; + 9F3EAB4720A5ED2F003FE7E3 /* fast-edge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EAB4320A5ED2F003FE7E3 /* fast-edge.cpp */; }; + 9F3EAB4B20A5F90B003FE7E3 /* TGPassportMRZ.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EAB4A20A5F90B003FE7E3 /* TGPassportMRZ.m */; }; + 9F4EC948218B459A002B3C56 /* RenderedTotalUnreadCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EC947218B459A002B3C56 /* RenderedTotalUnreadCount.swift */; }; + 9F4EEF7E21D3C3E3002C3B33 /* InstantPageDetailsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF7D21D3C3E3002C3B33 /* InstantPageDetailsItem.swift */; }; + 9F4EEF8021D3C76E002C3B33 /* InstantPageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF7F21D3C76E002C3B33 /* InstantPageContentView.swift */; }; + 9F4EEF8221D4F584002C3B33 /* ImageTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF8121D4F584002C3B33 /* ImageTransparency.swift */; }; + 9F4EEF8421D4F59C002C3B33 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F4EEF8321D4F59C002C3B33 /* Accelerate.framework */; }; + 9F4EEF8621D4FA68002C3B33 /* InstantPageArticleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF8521D4FA68002C3B33 /* InstantPageArticleItem.swift */; }; + 9F4EEF8821D515C5002C3B33 /* InstantPageStoredState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF8721D515C5002C3B33 /* InstantPageStoredState.swift */; }; + 9F52F51A2130286E006FC0B5 /* LocationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F52F5192130286E006FC0B5 /* LocationRequest.swift */; }; + 9F580BE520A0AA7B00F6D56C /* ChatRecorderOverlayWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F580BE420A0AA7B00F6D56C /* ChatRecorderOverlayWindow.swift */; }; + 9F62AE7E202D85B7007FB557 /* FetchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F62AE7D202D85B7007FB557 /* FetchManager.swift */; }; + 9F62AE81202D85E7007FB557 /* FetchManagerLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F62AE80202D85E7007FB557 /* FetchManagerLocation.swift */; }; + 9F62AE83202D8759007FB557 /* FetchMediaUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F62AE82202D8759007FB557 /* FetchMediaUtils.swift */; }; + 9F6314E121CAA0AB009FD379 /* NewPollController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6314E021CAA0AB009FD379 /* NewPollController.swift */; }; + 9F63152621D236CB009FD379 /* ForgotPasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F63152521D236CB009FD379 /* ForgotPasswordController.swift */; }; + 9F63152921D26892009FD379 /* CancelResetAccountController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F63152821D26892009FD379 /* CancelResetAccountController.swift */; }; + 9F6B54C821369B4000748FC1 /* GalleryModernControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6B54C721369B4000748FC1 /* GalleryModernControls.swift */; }; + 9F72973420B878B00067F815 /* MapResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F72973320B878B00067F815 /* MapResources.swift */; }; + 9F72974020BD9C6A0067F815 /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F72973F20BD9C6A0067F815 /* VideoPlayer.swift */; }; + 9F72974820C597800067F815 /* TermsModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F72974720C597800067F815 /* TermsModalController.swift */; }; + 9F77B3982211979B003B65B8 /* AutoplayPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F77B3972211979B003B65B8 /* AutoplayPreferences.swift */; }; + 9F77B3A6221C1DAC003B65B8 /* LogoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F77B3A5221C1DAC003B65B8 /* LogoutViewController.swift */; }; + 9F7943B020854E2F00FEDB81 /* ProxyListRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7943AF20854E2F00FEDB81 /* ProxyListRowItem.swift */; }; + 9F7943B220855DC200FEDB81 /* ProxyListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7943B120855DC200FEDB81 /* ProxyListController.swift */; }; + 9F7B5FCB22003DC70087D020 /* WallpaperColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B5FCA22003DC70087D020 /* WallpaperColorPicker.swift */; }; + 9F7B5FCD220099220087D020 /* WallpaperPatternPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B5FCC220099220087D020 /* WallpaperPatternPreview.swift */; }; + 9F7B74032227F492006610E4 /* ManageSharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B74022227F492006610E4 /* ManageSharedAccountInfo.swift */; }; + 9F7B74052227F4F2006610E4 /* SharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B74042227F4F2006610E4 /* SharedAccountInfo.swift */; }; + 9F7B740F2229618E006610E4 /* SharedWakeupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B740E2229618E006610E4 /* SharedWakeupManager.swift */; }; + 9F7B741122296FD7006610E4 /* SharedNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B741022296FD7006610E4 /* SharedNotificationManager.swift */; }; + 9F7D421F22203DB1007B68BB /* ChannelStatisticsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D421E22203DB1007B68BB /* ChannelStatisticsController.swift */; }; + 9F7D422A22240404007B68BB /* AccountContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D422922240403007B68BB /* AccountContext.swift */; }; + 9F7D422C222415C9007B68BB /* SharedAccountContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D422B222415C9007B68BB /* SharedAccountContext.swift */; }; + 9F88A134200FD425007B899E /* Wallpapers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F14CBF42007DFD400F22DA9 /* Wallpapers.swift */; }; + 9F8DF3C8209228B000AED104 /* EditAccountInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8DF3C7209228B000AED104 /* EditAccountInfoController.swift */; }; + 9F9206F020727AF30054E581 /* ChangePhoneNumberContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9206EF20727AF30054E581 /* ChangePhoneNumberContainerView.swift */; }; + 9F9483B1202AF816006E873D /* CrashHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9483B0202AF816006E873D /* CrashHandler.swift */; }; + 9F9B5EB222573E8A00728CDC /* FFMpegAVFormatContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EB122573E8A00728CDC /* FFMpegAVFormatContext.m */; }; + 9F9B5EB522573FA700728CDC /* FFMpegAVIOContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EB422573FA700728CDC /* FFMpegAVIOContext.m */; }; + 9F9B5EB82257402200728CDC /* FFMpegAVCodec.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EB72257402200728CDC /* FFMpegAVCodec.m */; }; + 9F9B5EBB2257408200728CDC /* FFMpegAVCodecContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EBA2257408200728CDC /* FFMpegAVCodecContext.m */; }; + 9F9B5EBF2257426500728CDC /* FFMpegAVFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EBE2257426500728CDC /* FFMpegAVFrame.m */; }; + 9F9B5EC22257443600728CDC /* FFMpegPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EC12257443600728CDC /* FFMpegPacket.m */; }; + 9FA0E52A20519E33001E5649 /* MP4Atom.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E52920519E33001E5649 /* MP4Atom.m */; }; + 9FA0E52D20519FCC001E5649 /* LiveUploadingHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E52C20519FCC001E5649 /* LiveUploadingHelper.m */; }; + 9FA0E5342051A41A001E5649 /* PreUploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E5332051A41A001E5649 /* PreUploadManager.swift */; }; + 9FA0E53B2052EDFF001E5649 /* HackUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E5392052EDFE001E5649 /* HackUtils.m */; }; + 9FA0E53D205693DA001E5649 /* WebSessionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E53C205693DA001E5649 /* WebSessionsController.swift */; }; + 9FA0E53F2056E159001E5649 /* WebAuthorizationRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA0E53E2056E159001E5649 /* WebAuthorizationRowItem.swift */; }; + 9FB14FBF209889A500688EF9 /* EDSunriseSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FB14FBE209889A500688EF9 /* EDSunriseSet.m */; }; + 9FB7CB6D221EB22700888EA9 /* CallSettingsModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB7CB6C221EB22700888EA9 /* CallSettingsModalController.swift */; }; + 9FBE0EE1201FBEFC0060FD1C /* DownloadSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBE0EE0201FBEFC0060FD1C /* DownloadSettingsViewController.swift */; }; + 9FC4DA9621DD0B35003E2A62 /* PeerChannelMemberCategoriesContextsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DA9521DD0B35003E2A62 /* PeerChannelMemberCategoriesContextsManager.swift */; }; + 9FC4DA9821DD0B86003E2A62 /* ChannelMemberCategoryListContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DA9721DD0B86003E2A62 /* ChannelMemberCategoryListContext.swift */; }; + 9FC4DA9A21DD0BE6003E2A62 /* CachedChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DA9921DD0BE6003E2A62 /* CachedChannelAdmins.swift */; }; + 9FC4DA9C21DD187C003E2A62 /* SearchPeerMembers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DA9B21DD187C003E2A62 /* SearchPeerMembers.swift */; }; + 9FC4DA9E21DD1C6C003E2A62 /* ImageCompression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DA9D21DD1C6C003E2A62 /* ImageCompression.swift */; }; + 9FC4DAA621DD1D6C003E2A62 /* libturbojpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FC4DAA221DD1D6C003E2A62 /* libturbojpeg.a */; }; + 9FC4DAA821DE0626003E2A62 /* ChannelPermissionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC4DAA721DE0626003E2A62 /* ChannelPermissionsController.swift */; }; + 9FC8AD9A2062A5610094F7B4 /* InputDataDataSelectorRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8AD992062A5610094F7B4 /* InputDataDataSelectorRowItem.swift */; }; + 9FC8AD9C2062AA630094F7B4 /* ValuesSelectorModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8AD9B2062AA630094F7B4 /* ValuesSelectorModalController.swift */; }; + 9FC8ADA02062D5E70094F7B4 /* PassportAcceptRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8AD9F2062D5E70094F7B4 /* PassportAcceptRowItem.swift */; }; + 9FC8ADA22062E2DF0094F7B4 /* PassportNewPhoneNumberRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8ADA12062E2DF0094F7B4 /* PassportNewPhoneNumberRowItem.swift */; }; + 9FC8ADA42063B6450094F7B4 /* PassportTwoStepVerificationIntroItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8ADA32063B6450094F7B4 /* PassportTwoStepVerificationIntroItem.swift */; }; + 9FC8ADA6206925F60094F7B4 /* PassportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8ADA5206925F60094F7B4 /* PassportWindowController.swift */; }; + 9FC8ADA8206A77E00094F7B4 /* countries in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8ADA7206A77E00094F7B4 /* countries */; }; + 9FDA713220E6456A001ED8ED /* ExternalMusicAlbumArtResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA713120E6456A001ED8ED /* ExternalMusicAlbumArtResources.swift */; }; + 9FDA713420E65D49001ED8ED /* PeerMediaPlayerAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA713320E65D49001ED8ED /* PeerMediaPlayerAnimationView.swift */; }; + 9FDA713B20EA9532001ED8ED /* ReadArticlesListPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA713A20EA9532001ED8ED /* ReadArticlesListPreferences.swift */; }; + 9FDA713F20EE2D49001ED8ED /* PopularPeersRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA713E20EE2D49001ED8ED /* PopularPeersRowItem.swift */; }; + 9FDD78D121C8F0CC00F1B4EF /* ChatPollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDD78D021C8F0CC00F1B4EF /* ChatPollItem.swift */; }; + 9FDE0A8F21AD41C2001546D7 /* emoji1014-1.txt in Resources */ = {isa = PBXBuildFile; fileRef = 9FDE0A8E21AD41C2001546D7 /* emoji1014-1.txt */; }; + 9FF1DEA6225B699D009512C9 /* SearchUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF1DEA5225B699D009512C9 /* SearchUtils.swift */; }; + 9FF32C7C21B7DF4800BF58B6 /* StickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF32C7B21B7DF4800BF58B6 /* StickerSettings.swift */; }; + 9FF5A1CB2232A2FF00BC1359 /* UpgradedAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF5A1CA2232A2FF00BC1359 /* UpgradedAccount.swift */; }; + 9FFAE4ED205A8C89000C028E /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4DC205A8C80000C028E /* FFMpegMediaPassthroughVideoFrameDecoder.swift */; }; + 9FFAE4EF205A8C89000C028E /* MediaFrameSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4DE205A8C81000C028E /* MediaFrameSource.swift */; }; + 9FFAE4F1205A8C89000C028E /* MediaTrackFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E0205A8C82000C028E /* MediaTrackFrame.swift */; }; + 9FFAE4F2205A8C89000C028E /* MediaTrackFrameBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E1205A8C83000C028E /* MediaTrackFrameBuffer.swift */; }; + 9FFAE4F3205A8C89000C028E /* MediaPlayerAudioRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E2205A8C83000C028E /* MediaPlayerAudioRenderer.swift */; }; + 9FFAE4F4205A8C89000C028E /* FFMpegMediaFrameSourceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E3205A8C84000C028E /* FFMpegMediaFrameSourceContext.swift */; }; + 9FFAE4F5205A8C89000C028E /* MediaTrackFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E4205A8C84000C028E /* MediaTrackFrameDecoder.swift */; }; + 9FFAE4F7205A8C89000C028E /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E6205A8C85000C028E /* FFMpegMediaFrameSourceContextHelpers.swift */; }; + 9FFAE4F8205A8C89000C028E /* FFMpegMediaFrameSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E7205A8C86000C028E /* FFMpegMediaFrameSource.swift */; }; + 9FFAE4F9205A8C89000C028E /* FFMpegAudioFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E8205A8C86000C028E /* FFMpegAudioFrameDecoder.swift */; }; + 9FFAE4FA205A8C89000C028E /* MediaPlaybackData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4E9205A8C87000C028E /* MediaPlaybackData.swift */; }; + 9FFAE4FB205A8C89000C028E /* MediaPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4EA205A8C87000C028E /* MediaPlayer.swift */; }; + 9FFAE4FC205A8C89000C028E /* MediaTrackDecodableFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4EB205A8C88000C028E /* MediaTrackDecodableFrame.swift */; }; + 9FFAE4FD205A8C89000C028E /* FFMpegMediaVideoFrameDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4EC205A8C88000C028E /* FFMpegMediaVideoFrameDecoder.swift */; }; + 9FFAE500205A9042000C028E /* FFMpegSwResample.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE4FF205A9042000C028E /* FFMpegSwResample.m */; }; + 9FFAE502205A9154000C028E /* ManagedAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE501205A9153000C028E /* ManagedAudioSession.swift */; }; + 9FFAE504205A916B000C028E /* VideoPlayerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE503205A916B000C028E /* VideoPlayerProxy.swift */; }; + 9FFAE506205A928C000C028E /* RingByteBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE505205A928C000C028E /* RingByteBuffer.swift */; }; + 9FFAE509205A92B9000C028E /* RingBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FFAE508205A92B9000C028E /* RingBuffer.m */; }; + 9FFAE50A205AA1FB000C028E /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F8FDF37205A7AE7001A7A77 /* libavutil.a */; }; + 9FFAE50B205AA1FB000C028E /* libavcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F8FDF38205A7AE7001A7A77 /* libavcodec.a */; }; + 9FFAE50C205AA1FB000C028E /* libavformat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F8FDF39205A7AE7001A7A77 /* libavformat.a */; }; + 9FFAE50D205AA1FB000C028E /* libswresample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F8FDF3A205A7AE7001A7A77 /* libswresample.a */; }; + 9FFAE50F205AB4A3000C028E /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FFAE50E205AB4A3000C028E /* libiconv.tbd */; }; + 9FFAE514205AB50C000C028E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FFAE513205AB50C000C028E /* libz.tbd */; }; + A7029EF8240E3A5400A89ABD /* ChatListFiltersHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7029EF7240E3A5400A89ABD /* ChatListFiltersHeaderItem.swift */; }; + A7029EFA240E3CDA00A89ABD /* folder.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A7029EF9240E3CCF00A89ABD /* folder.tgs */; }; + A71DC82B23858312000EEDE2 /* CoreSpotlight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A71DC82A23858311000EEDE2 /* CoreSpotlight.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + A71DC82D23858356000EEDE2 /* Spotlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71DC82C23858356000EEDE2 /* Spotlight.swift */; }; + A71DC82F2386AADF000EEDE2 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71DC82E2386AADF000EEDE2 /* Signature.swift */; }; + A71DC8322386C845000EEDE2 /* TelegramShare.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C232EA911E1D07E700C4D38C /* TelegramShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + A71DC8342386D512000EEDE2 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71DC82E2386AADF000EEDE2 /* Signature.swift */; }; + A71DC8352386D512000EEDE2 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BB2DAF1F8BDF6700520255 /* Config.swift */; }; + A72D7AE923C471A7005BAC59 /* PollResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72D7AE823C471A7005BAC59 /* PollResultController.swift */; }; + A7377E1B23ACC79100AD3ADD /* ChatNavigateFailed.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7377E1A23ACC79100AD3ADD /* ChatNavigateFailed.swift */; }; + A7393D352407CAE100CE44CA /* ChatMediaDice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7393D342407CAE100CE44CA /* ChatMediaDice.swift */; }; + A7393D372407CD7A00CE44CA /* ChatDiceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7393D362407CD7A00CE44CA /* ChatDiceContentView.swift */; }; + A7393D392407D0F100CE44CA /* GraphCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7393D382407D0F100CE44CA /* GraphCore.framework */; }; + A7393D422407FF1900CE44CA /* dice_idle.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A7393D412407FF0F00CE44CA /* dice_idle.tgs */; }; + A7393D442408F84300CE44CA /* DiceCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7393D432408F84300CE44CA /* DiceCache.swift */; }; + A7393D462409044C00CE44CA /* ChannelOverviewStatsRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7393D452409044C00CE44CA /* ChannelOverviewStatsRowItem.swift */; }; + A742CDCD240FB32F00C6B69B /* ChatListFilterRecommendedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A742CDCC240FB32F00C6B69B /* ChatListFilterRecommendedItem.swift */; }; + A742CE45241A517800C6B69B /* ChannelRecentPostRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A742CE44241A517800C6B69B /* ChannelRecentPostRowItem.swift */; }; + A74EB072237961A1005F55AE /* AppCenterCrashes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A74EB070237961A1005F55AE /* AppCenterCrashes.framework */; }; + A74EB073237961BF005F55AE /* AppCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A74EB06F237961A1005F55AE /* AppCenter.framework */; }; + A7565EAE23BE02AF0031EADE /* ChatGradientModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7565EAD23BE02AF0031EADE /* ChatGradientModel.swift */; }; + A766493F236D6BFD00163DF4 /* PasscodeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A766493E236D6BFD00163DF4 /* PasscodeSettings.swift */; }; + A7664940236D6BFD00163DF4 /* PasscodeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A766493E236D6BFD00163DF4 /* PasscodeSettings.swift */; }; + A767DD4123F2BB3200366F76 /* ShortcutListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767DD4023F2BB3200366F76 /* ShortcutListController.swift */; }; + A76C8A9A241F826900FDB071 /* GlobalSearchModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76C8A99241F826900FDB071 /* GlobalSearchModalController.swift */; }; + A76C8A9E2420FFE500FDB071 /* folder_empty.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A76C8A9D2420FFE400FDB071 /* folder_empty.tgs */; }; + A76C8AA02422132400FDB071 /* VerticalTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76C8A9F2422132400FDB071 /* VerticalTabsView.swift */; }; + A76C8AA224221D3000FDB071 /* graph_loading.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A76C8AA124221D2800FDB071 /* graph_loading.tgs */; }; + A76C8AA424221F5400FDB071 /* StatisticsLoadingRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76C8AA324221F5400FDB071 /* StatisticsLoadingRowItem.swift */; }; + A76C8AB3242366EC00FDB071 /* PeerMediaBlockRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76C8AB2242366EC00FDB071 /* PeerMediaBlockRowItem.swift */; }; + A778DC2A23C75F1100DD307B /* Confetti.swift in Sources */ = {isa = PBXBuildFile; fileRef = A778DC2923C75F1100DD307B /* Confetti.swift */; }; + A778DC2E23C77AD800DD307B /* confetti.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A778DC2D23C77AD800DD307B /* confetti.mp3 */; }; + A778DC3023C8985300DD307B /* SoundEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = A778DC2F23C8985300DD307B /* SoundEffects.swift */; }; + A778DC3323C8988300DD307B /* quiz-incorrect.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A778DC3123C8988100DD307B /* quiz-incorrect.mp3 */; }; + A778DC3423C8988300DD307B /* quiz-correct.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A778DC3223C8988300DD307B /* quiz-correct.mp3 */; }; + A778DC4223CD9F3C00DD307B /* SearchSettingsEmptyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A778DC4123CD9F3C00DD307B /* SearchSettingsEmptyItem.swift */; }; + A7831B1C2403CFDD0056AEAC /* ChannelStatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B1B2403CFDD0056AEAC /* ChannelStatsViewController.swift */; }; + A7831B2A24040B6B0056AEAC /* StatisticRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B2924040B6B0056AEAC /* StatisticRowItem.swift */; }; + A789E08423E05EAE00AEB34A /* ChatListFiltersListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789E08323E05EAE00AEB34A /* ChatListFiltersListController.swift */; }; + A789E09723E427CE00AEB34A /* ChatListFilterPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C1379123DB00D900803ED3 /* ChatListFilterPreferences.swift */; }; + A7918DB424093505002011CA /* GraphUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7918DB324093505002011CA /* GraphUI.framework */; }; + A7919135240D0869002011CA /* MurMurHash32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7919134240D0869002011CA /* MurMurHash32.framework */; }; + A7919138240D1077002011CA /* ChatListFilterPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7919137240D1077002011CA /* ChatListFilterPredicate.swift */; }; + A7B5031023B62E0400C9838E /* nanosvg.c in Sources */ = {isa = PBXBuildFile; fileRef = A7B5030E23B62E0000C9838E /* nanosvg.c */; }; + A7B5031323B62E1A00C9838E /* Svg.m in Sources */ = {isa = PBXBuildFile; fileRef = A7B5031123B62E1700C9838E /* Svg.m */; }; + A7B6DDD723ED935100B8E01C /* think_spectacular.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A7B6DDD623ED8FDF00B8E01C /* think_spectacular.tgs */; }; + A7C1377D23D1A62800803ED3 /* PeerEmptyHolderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C1377C23D1A62700803ED3 /* PeerEmptyHolderItem.swift */; }; + A7C1379223DB00D900803ED3 /* ChatListFilterPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C1379123DB00D900803ED3 /* ChatListFilterPreferences.swift */; }; + A7C1379E23DF21EA00803ED3 /* ChatListRevealItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C1379D23DF21EA00803ED3 /* ChatListRevealItem.swift */; }; + A7C41DB4235862BB00CF9402 /* PeerMediaPhotosController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C41DB3235862BB00CF9402 /* PeerMediaPhotosController.swift */; }; + A7C41DB623586DC100CF9402 /* PeerPhotosMonthItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C41DB523586DC100CF9402 /* PeerPhotosMonthItem.swift */; }; + A7C7215723FD45D300CE3F75 /* SaveModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C7215623FD45D300CE3F75 /* SaveModalController.swift */; }; + A7C7215923FD473E00CE3F75 /* success_saved.tgs in Resources */ = {isa = PBXBuildFile; fileRef = A7C7215823FD473D00CE3F75 /* success_saved.tgs */; }; + A7C7215B23FD4BAA00CE3F75 /* LottieLocalAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C7215A23FD4BAA00CE3F75 /* LottieLocalAnimations.swift */; }; + A7D08F48236AC9B6002DC240 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F0BCD9E2087BABC001D8D8A /* Sparkle.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + A7D08F4A236AC9BB002DC240 /* Sparkle.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 9F0BCD9E2087BABC001D8D8A /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D28205236C3C0B0000A9BF /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28204236C3C0B0000A9BF /* Postbox.framework */; }; + A7D28207236C3C0F0000A9BF /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28206236C3C0F0000A9BF /* SwiftSignalKit.framework */; }; + A7D28209236C3C150000A9BF /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28208236C3C150000A9BF /* TelegramCore.framework */; }; + A7D2820B236C3C1B0000A9BF /* MtProtoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2820A236C3C1B0000A9BF /* MtProtoKit.framework */; }; + A7D2820C236C3C2A0000A9BF /* MtProtoKit.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = A7D2820A236C3C1B0000A9BF /* MtProtoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D2820D236C3C2F0000A9BF /* TelegramCore.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = A7D28208236C3C150000A9BF /* TelegramCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D2820E236C3C330000A9BF /* SwiftSignalKit.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = A7D28206236C3C0F0000A9BF /* SwiftSignalKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D2820F236C3C3B0000A9BF /* Postbox.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = A7D28204236C3C0B0000A9BF /* Postbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D28211236C3D390000A9BF /* SyncCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28210236C3D390000A9BF /* SyncCore.framework */; }; + A7D28212236C3D3E0000A9BF /* SyncCore.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = A7D28210236C3D390000A9BF /* SyncCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + A7D28226236C50EE0000A9BF /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28204236C3C0B0000A9BF /* Postbox.framework */; }; + A7D28227236C50F20000A9BF /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28206236C3C0F0000A9BF /* SwiftSignalKit.framework */; }; + A7D28228236C50F60000A9BF /* SyncCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28210236C3D390000A9BF /* SyncCore.framework */; }; + A7D28229236C50FC0000A9BF /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28208236C3C150000A9BF /* TelegramCore.framework */; }; + A7D2822B236C51A50000A9BF /* OpenSSLEncryption.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2822A236C51A50000A9BF /* OpenSSLEncryption.framework */; }; + A7D2822D236C51F10000A9BF /* OpenSSLEncryption.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2822C236C51F10000A9BF /* OpenSSLEncryption.framework */; }; + A7D2822F236C549B0000A9BF /* SyncCoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D2822E236C549B0000A9BF /* SyncCoreExtension.swift */; }; + A7D28230236C549B0000A9BF /* SyncCoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D2822E236C549B0000A9BF /* SyncCoreExtension.swift */; }; + A7D28232236C57DD0000A9BF /* libphonenumber.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28231236C57DD0000A9BF /* libphonenumber.framework */; }; + A7D28237236C5B2C0000A9BF /* PhoneNumberUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D28236236C5B2C0000A9BF /* PhoneNumberUtils.swift */; }; + A7D28259236C70A50000A9BF /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2823B236C69070000A9BF /* SSignalKit.framework */; }; + A7DF1B6C237415AD00ACC01F /* Zip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7DF1B6B237415AD00ACC01F /* Zip.framework */; }; + A7ED5DAF236C7CE100040372 /* RLottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DAE236C7CE100040372 /* RLottie.framework */; }; + A7F282B2238D122900742C20 /* UnauthorizedConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F282B1238D122900742C20 /* UnauthorizedConfiguration.swift */; }; + A7F283002395168300742C20 /* SettingsSearchRecentQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F282FF2395168300742C20 /* SettingsSearchRecentQueries.swift */; }; + A7F283022395185B00742C20 /* SettingsSearchableItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F283012395185B00742C20 /* SettingsSearchableItems.swift */; }; + A7F283042395289B00742C20 /* AccountUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F283032395289B00742C20 /* AccountUtils.swift */; }; + A7F2830623954E1B00742C20 /* CachedFaqInstantPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F2830523954E1B00742C20 /* CachedFaqInstantPage.swift */; }; + A7F2830823954EF800742C20 /* CachedInstantPages.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F2830723954EF800742C20 /* CachedInstantPages.swift */; }; + A7F2830A2395570400742C20 /* SearchSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F283092395570400742C20 /* SearchSettingsController.swift */; }; + A7F2831923994B9700742C20 /* AutoplayPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F77B3972211979B003B65B8 /* AutoplayPreferences.swift */; }; + A7F2831B239A496400742C20 /* ContextShowPeersHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F2831A239A496400742C20 /* ContextShowPeersHolder.swift */; }; + A7F28337239A808500742C20 /* AudioAnimatedSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F28336239A808500742C20 /* AudioAnimatedSticker.swift */; }; C2016F3B1EAA4538003AF981 /* RecentPeerRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2016F3A1EAA4538003AF981 /* RecentPeerRowItem.swift */; }; C2016F681EAE0A68003AF981 /* PhoneCallWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2016F671EAE0A68003AF981 /* PhoneCallWindowController.swift */; }; C201C2321E3B2D1C0026C21E /* FastSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C201C2311E3B2D1C0026C21E /* FastSettings.swift */; }; C20232A81D81D189007C9ADE /* ChatController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232A71D81D189007C9ADE /* ChatController.swift */; }; C20232AA1D81D19C007C9ADE /* ChatRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232A91D81D19C007C9ADE /* ChatRowItem.swift */; }; C20232AC1D81D1AE007C9ADE /* ChatMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232AB1D81D1AE007C9ADE /* ChatMessageView.swift */; }; - C20320FF1F9769BA00143395 /* TwoStepVerificationResetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20320FE1F9769BA00143395 /* TwoStepVerificationResetController.swift */; }; C2057FA51EBBCEA7000423DC /* voip_busy.caf in Resources */ = {isa = PBXBuildFile; fileRef = C2CFCAB91EBB4A2200843F6A /* voip_busy.caf */; }; C2057FA61EBBCEA7000423DC /* voip_end.caf in Resources */ = {isa = PBXBuildFile; fileRef = C2CFCABA1EBB4A2200843F6A /* voip_end.caf */; }; C2057FA71EBBCEA7000423DC /* voip_fail.caf in Resources */ = {isa = PBXBuildFile; fileRef = C2CFCABB1EBB4A2200843F6A /* voip_fail.caf */; }; @@ -22,7 +297,6 @@ C205DB981EE71127003711DF /* ChannelAdminController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C205DB971EE71127003711DF /* ChannelAdminController.swift */; }; C205DB9D1EE88765003711DF /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = C205DB9C1EE88762003711DF /* Views.swift */; }; C205FEA61EB39DE400455808 /* SidebarCapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C205FEA51EB39DE400455808 /* SidebarCapViewController.swift */; }; - C2084F041F5D5C6F004713C4 /* ChatReplyPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2084F031F5D5C6F004713C4 /* ChatReplyPreviewController.swift */; }; C209C3731F262537009231FE /* emoji_suggestions_data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C209C36F1F262537009231FE /* emoji_suggestions_data.cpp */; }; C209C3741F262537009231FE /* emoji_suggestions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C209C3711F262537009231FE /* emoji_suggestions.cpp */; }; C209C3771F26271B009231FE /* EmojiSuggestionBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = C209C3761F26271B009231FE /* EmojiSuggestionBridge.mm */; }; @@ -30,15 +304,14 @@ C209C38D1F276D4C009231FE /* ChatSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C209C38C1F276D4C009231FE /* ChatSearchView.swift */; }; C20B8F3E1DFC52EE008A354E /* ChatEmptyPeerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20B8F3D1DFC52EE008A354E /* ChatEmptyPeerItem.swift */; }; C20B8F401DFD9999008A354E /* ChatListNothingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20B8F3F1DFD9999008A354E /* ChatListNothingItem.swift */; }; + C20CAD121FE291E300EFF8BF /* ChatBubbleAccessoryForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20CAD111FE291E200EFF8BF /* ChatBubbleAccessoryForward.swift */; }; + C20CAD151FE436E300EFF8BF /* SelectSizeRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20CAD141FE436E300EFF8BF /* SelectSizeRowItem.swift */; }; C20CB7291E60886F00C992AC /* LinkInvationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20CB7281E60886E00C992AC /* LinkInvationController.swift */; }; C20D5AB11DA996480042616A /* EBlockItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20D5AB01DA996480042616A /* EBlockItem.swift */; }; C20D5AB31DA9965B0042616A /* EBlockRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20D5AB21DA9965B0042616A /* EBlockRowView.swift */; }; C21074241E77F5DF006EE5EF /* dsa_pub_prod.pem in Resources */ = {isa = PBXBuildFile; fileRef = C21074231E77F5DF006EE5EF /* dsa_pub_prod.pem */; }; C210742C1E780CC1006EE5EF /* PhotoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C210742B1E780CC1006EE5EF /* PhotoCache.swift */; }; C210959A1E9FE04700E10BDB /* ChatVideoMessageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21095991E9FE04700E10BDB /* ChatVideoMessageContentView.swift */; }; - C21178001F16BB8300AC706D /* BioViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21177FF1F16BB8300AC706D /* BioViewController.swift */; }; - C21656D41EE4A83E0041A6BA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D098C71C1D7E175A007784E4 /* MainMenu.xib */; }; - C21656E81EE576FA0041A6BA /* ChatUserPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21656E71EE576F90041A6BA /* ChatUserPopover.swift */; }; C2167E4F1DC220D800F98E03 /* PeerMediaWebpageRowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E4E1DC220D800F98E03 /* PeerMediaWebpageRowContent.swift */; }; C2167E511DC220E900F98E03 /* PeerMediaMusicRowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E501DC220E900F98E03 /* PeerMediaMusicRowContent.swift */; }; C2167E531DC220F600F98E03 /* PeerMediaFileRowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E521DC220F600F98E03 /* PeerMediaFileRowContent.swift */; }; @@ -52,7 +325,6 @@ C218FF9A1F42030B00DD7D35 /* InstantPageChannelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C218FF991F42030B00DD7D35 /* InstantPageChannelItem.swift */; }; C218FF9C1F4204C400DD7D35 /* InstantPageChannelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C218FF9B1F4204C400DD7D35 /* InstantPageChannelView.swift */; }; C219E1D71D8869F20042F0C8 /* ChatHoleRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1D61D8869F20042F0C8 /* ChatHoleRowItem.swift */; }; - C219E1D91D886A160042F0C8 /* ChatHoleRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1D81D886A160042F0C8 /* ChatHoleRowView.swift */; }; C219E1DB1D8884290042F0C8 /* ChatHistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1DA1D8884290042F0C8 /* ChatHistoryEntry.swift */; }; C219E1E41D8AC8370042F0C8 /* ChatUnreadRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1E31D8AC8370042F0C8 /* ChatUnreadRowItem.swift */; }; C219E1E81D8AC90B0042F0C8 /* ChatUnreadRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1E71D8AC90B0042F0C8 /* ChatUnreadRowView.swift */; }; @@ -61,12 +333,13 @@ C21A48AE1F7CFBBE0095ADB1 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C21A48AD1F7CFBBE0095ADB1 /* OpenGL.framework */; }; C21A48B21F7D0D3F0095ADB1 /* VideoMessage.fsh in Resources */ = {isa = PBXBuildFile; fileRef = C21A48B11F7D0D3F0095ADB1 /* VideoMessage.fsh */; }; C21A48B41F7D0D6B0095ADB1 /* VideoMessage.vsh in Resources */ = {isa = PBXBuildFile; fileRef = C21A48B31F7D0D6B0095ADB1 /* VideoMessage.vsh */; }; - C21AAE341DB0F6BC007638C5 /* MediaTitleBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE331DB0F6BC007638C5 /* MediaTitleBarView.swift */; }; C21AAE361DB22CA5007638C5 /* AvatarLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE351DB22CA5007638C5 /* AvatarLayer.swift */; }; C21B24611ED9C39F00FC6CDA /* SuggestionLocalizationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21B24601ED9C39F00FC6CDA /* SuggestionLocalizationViewController.swift */; }; C21B24631EDADC8600FC6CDA /* MMMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21B24621EDADC8600FC6CDA /* MMMenuItem.swift */; }; C21B24651EDB115700FC6CDA /* StringPluralization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21B24641EDB115700FC6CDA /* StringPluralization.swift */; }; C21B24681EDB116B00FC6CDA /* NumberPluralizationForm.m in Sources */ = {isa = PBXBuildFile; fileRef = C21B24671EDB116B00FC6CDA /* NumberPluralizationForm.m */; }; + C21BE3AF1FD099AA00C1C849 /* DeveloperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21BE3AE1FD099AA00C1C849 /* DeveloperViewController.swift */; }; + C21BE3B11FD14CDB00C1C849 /* ParseAppearanceColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21BE3B01FD14CDB00C1C849 /* ParseAppearanceColors.swift */; }; C2203EA21DDE2AB8001E6AB6 /* ChatSelectText.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2203EA11DDE2AB8001E6AB6 /* ChatSelectText.swift */; }; C221ED541EA684BE00471C65 /* DataAndStorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C221ED531EA684BE00471C65 /* DataAndStorageViewController.swift */; }; C221ED561EA6877300471C65 /* GeneratedMediaStoreSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C221ED551EA6877300471C65 /* GeneratedMediaStoreSettings.swift */; }; @@ -75,27 +348,18 @@ C221ED5C1EA69AA300471C65 /* StorageUsageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C221ED5B1EA69AA300471C65 /* StorageUsageController.swift */; }; C221ED5E1EA6B36600471C65 /* ChatStorageManagmentModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C221ED5D1EA6B36600471C65 /* ChatStorageManagmentModalController.swift */; }; C22338451F823F8C004AD57C /* VideoCameraStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22338441F823F8C004AD57C /* VideoCameraStructures.swift */; }; + C224675B1FA8546200F03E27 /* GroupedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C224675A1FA8546200F03E27 /* GroupedLayout.swift */; }; + C224675D1FA884E300F03E27 /* ChatGroupedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C224675C1FA884E300F03E27 /* ChatGroupedItem.swift */; }; C224A72D1EB75A3100F43F3F /* ExMajorNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C224A72C1EB75A3100F43F3F /* ExMajorNavigationController.swift */; }; - C224F2921E43CD93002FF0B2 /* TelegramCoreMac.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C2FBC1D61DC61AFF0063A23B /* TelegramCoreMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C224F2931E43CDBE002FF0B2 /* SwiftSignalKitMac.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C2FBC1D81DC61B050063A23B /* SwiftSignalKitMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C224F2941E43CDC4002FF0B2 /* TGUIKit.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C22E06251D7F16C000A11C88 /* TGUIKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C224F2951E43CDD8002FF0B2 /* MtProtoKitMac.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C2FBC1DE1DC61B580063A23B /* MtProtoKitMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C224F2961E43CDDD002FF0B2 /* PostboxMac.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C234CA901D97E117003023F7 /* PostboxMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C225524B1F7BE7000007944D /* VideoRecorderModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225524A1F7BE7000007944D /* VideoRecorderModalController.swift */; }; C225524D1F7BE8E40007944D /* VideoRecorderModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225524C1F7BE8E40007944D /* VideoRecorderModalView.swift */; }; C225524F1F7C03B50007944D /* VideoRecorderPipeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225524E1F7C03B50007944D /* VideoRecorderPipeline.swift */; }; C2256D981DAB9D5A00494CF4 /* ChatHistoryViewForLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2256D971DAB9D5A00494CF4 /* ChatHistoryViewForLocation.swift */; }; - C226741F1DBCEAC2000BA9ED /* EStickerGridEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226741E1DBCEAC2000BA9ED /* EStickerGridEntries.swift */; }; - C22674211DBCECCC000BA9ED /* EStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674201DBCECCC000BA9ED /* EStickerGridItem.swift */; }; C226742B1DBE16B9000BA9ED /* CachedResourceRepresentations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226742A1DBE16B9000BA9ED /* CachedResourceRepresentations.swift */; }; C226742F1DBE2CB3000BA9ED /* PanelUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226742E1DBE2CB3000BA9ED /* PanelUtils.swift */; }; C22674311DBE9B50000BA9ED /* FetchCachedRepresentations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674301DBE9B50000BA9ED /* FetchCachedRepresentations.swift */; }; - C22674331DBF665A000BA9ED /* EStickerPackEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674321DBF665A000BA9ED /* EStickerPackEntries.swift */; }; - C22674351DBF6A85000BA9ED /* EStickerPackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674341DBF6A85000BA9ED /* EStickerPackItem.swift */; }; C22674381DC125C1000BA9ED /* PeerMediaCollectionInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674371DC125C1000BA9ED /* PeerMediaCollectionInterfaceState.swift */; }; - C226743A1DC1273E000BA9ED /* PeerMediaGridController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674391DC1273E000BA9ED /* PeerMediaGridController.swift */; }; - C226743F1DC12E4E000BA9ED /* GridMessageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226743E1DC12E4E000BA9ED /* GridMessageItem.swift */; }; - C22674411DC13165000BA9ED /* GridHoleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674401DC13165000BA9ED /* GridHoleItem.swift */; }; C22674451DC20664000BA9ED /* PeerMediaListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22674441DC20664000BA9ED /* PeerMediaListController.swift */; }; C2271D9C1DACC027001792B6 /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271D9B1DACC027001792B6 /* SearchController.swift */; }; C2271D9E1DACC796001792B6 /* ChatListMessageRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271D9D1DACC796001792B6 /* ChatListMessageRowItem.swift */; }; @@ -111,20 +375,16 @@ C2271DBB1DAE213D001792B6 /* ChannelInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DBA1DAE213D001792B6 /* ChannelInfoEntries.swift */; }; C2271DBF1DAE563D001792B6 /* PeerInfoHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DBE1DAE563D001792B6 /* PeerInfoHeaderItem.swift */; }; C2271DC11DAE583E001792B6 /* TextAndLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DC01DAE583E001792B6 /* TextAndLabelItem.swift */; }; - C2271DC41DAE5E46001792B6 /* PeerInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DC31DAE5E46001792B6 /* PeerInfoHeaderView.swift */; }; C2271DCA1DAED681001792B6 /* PresenceStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DC91DAED681001792B6 /* PresenceStrings.swift */; }; C2271DD21DAF6DF5001792B6 /* EmptyChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DD11DAF6DF5001792B6 /* EmptyChatViewController.swift */; }; C2271DD71DAF80D5001792B6 /* PeerMediaController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DD61DAF80D5001792B6 /* PeerMediaController.swift */; }; C2271F2B1DB3BEB60045E719 /* GlobalHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F2A1DB3BEB60045E719 /* GlobalHandlers.swift */; }; C2271F381DB4D0490045E719 /* EmojiViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F371DB4D0490045E719 /* EmojiViewController.swift */; }; - C2271F3A1DB4D0540045E719 /* EStickersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F391DB4D0540045E719 /* EStickersViewController.swift */; }; C2271F3C1DB4D0630045E719 /* GIFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F3B1DB4D0630045E719 /* GIFViewController.swift */; }; C2271F3E1DB4D4240045E719 /* ETabRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F3D1DB4D4240045E719 /* ETabRowItem.swift */; }; C2271F401DB4D5850045E719 /* ETabRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F3F1DB4D5850045E719 /* ETabRowView.swift */; }; C2271F471DB4FC130045E719 /* EStickItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F461DB4FC130045E719 /* EStickItem.swift */; }; C2271F491DB4FC220045E719 /* EStickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F481DB4FC220045E719 /* EStickView.swift */; }; - C2271F4F1D9C38F500424F7B /* SPopoverRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F4E1D9C38F500424F7B /* SPopoverRowItem.swift */; }; - C2271F511D9C392400424F7B /* SPopoverRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F501D9C392400424F7B /* SPopoverRowView.swift */; }; C2271F541D9D420B00424F7B /* ContactsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F531D9D420B00424F7B /* ContactsController.swift */; }; C2271F571D9D46BC00424F7B /* ShortPeerRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F561D9D46BC00424F7B /* ShortPeerRowItem.swift */; }; C2271F591D9D46CA00424F7B /* ShortPeerRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F581D9D46CA00424F7B /* ShortPeerRowView.swift */; }; @@ -142,10 +402,10 @@ C2303E731D9966BD00098E12 /* ChatInputActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E721D9966BD00098E12 /* ChatInputActionsView.swift */; }; C2303E781D997C3100098E12 /* ChatInputAttachView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E771D997C3100098E12 /* ChatInputAttachView.swift */; }; C2303E8A1D9A76D800098E12 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E891D9A76D800098E12 /* MainViewController.swift */; }; + C23044831F98F8B400977C51 /* MediaPreviewRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23044821F98F8B400977C51 /* MediaPreviewRowItem.swift */; }; C230B8EF1DD3358C0057F596 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B8EE1DD3358C0057F596 /* AccountViewController.swift */; }; C230B8F11DD348970057F596 /* AccountInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B8F01DD348970057F596 /* AccountInfoItem.swift */; }; C230B8F41DD368D40057F596 /* SelectPeersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B8F31DD368D40057F596 /* SelectPeersController.swift */; }; - C230B8F61DD371430057F596 /* SPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B8F51DD371430057F596 /* SPopoverViewController.swift */; }; C230B90F1DD383820057F596 /* ComposeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B90E1DD383820057F596 /* ComposeViewController.swift */; }; C230B9111DD3866A0057F596 /* ComposeActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B9101DD3866A0057F596 /* ComposeActions.swift */; }; C230B9131DD392EB0057F596 /* SearchRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B9121DD392EB0057F596 /* SearchRowItem.swift */; }; @@ -165,10 +425,6 @@ C232EA951E1D07E700C4D38C /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = C232EA941E1D07E700C4D38C /* icon.icns */; }; C232EA981E1D07E700C4D38C /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C232EA971E1D07E700C4D38C /* ShareViewController.swift */; }; C232EA9B1E1D07E700C4D38C /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C232EA991E1D07E700C4D38C /* ShareViewController.xib */; }; - C232EAA41E1D07FE00C4D38C /* MtProtoKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1DE1DC61B580063A23B /* MtProtoKitMac.framework */; }; - C232EAA51E1D07FE00C4D38C /* PostboxMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C234CA901D97E117003023F7 /* PostboxMac.framework */; }; - C232EAA61E1D07FE00C4D38C /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1D81DC61B050063A23B /* SwiftSignalKitMac.framework */; }; - C232EAA71E1D07FE00C4D38C /* TelegramCoreMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1D61DC61AFF0063A23B /* TelegramCoreMac.framework */; }; C232EAA81E1D07FE00C4D38C /* TGUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C22E06251D7F16C000A11C88 /* TGUIKit.framework */; }; C232EAB01E1D110500C4D38C /* SESelectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C232EAAF1E1D110500C4D38C /* SESelectController.swift */; }; C232EAB61E1D11CA00C4D38C /* InterfaceObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E5E1DC25C6900F98E03 /* InterfaceObserver.swift */; }; @@ -185,7 +441,6 @@ C234A7FB1ED7112400EBBECE /* LocalizableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234A7FA1ED7112400EBBECE /* LocalizableExtension.swift */; }; C234A7FE1ED725C300EBBECE /* LanguageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234A7FD1ED725C300EBBECE /* LanguageViewController.swift */; }; C234A8001ED73A3300EBBECE /* LanguageRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234A7FF1ED73A3300EBBECE /* LanguageRowItem.swift */; }; - C234CA911D97E117003023F7 /* PostboxMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C234CA901D97E117003023F7 /* PostboxMac.framework */; }; C234D4121EEDE6990017DC25 /* LoadingTableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234D4111EEDE6990017DC25 /* LoadingTableItem.swift */; }; C236ADDE1F7D318700E8C71A /* TGVideoCameraMovieRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = C236ADDC1F7D318600E8C71A /* TGVideoCameraMovieRecorder.m */; }; C2379D2A1DDCCBF10063AD30 /* ReplyMarkupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2379D291DDCCBF10063AD30 /* ReplyMarkupNode.swift */; }; @@ -200,24 +455,25 @@ C23BC37E1E9BB28F00D79F92 /* AddContactModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23BC37D1E9BB28F00D79F92 /* AddContactModalController.swift */; }; C23C5AE11E1136D1005903E1 /* GroupNameRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23C5AE01E1136D1005903E1 /* GroupNameRowItem.swift */; }; C23D0D7A1F1A609300AF5151 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */; }; - C23D0D7C1F1A649900AF5151 /* SFCompactRounded-Semibold.otf in Fonts */ = {isa = PBXBuildFile; fileRef = C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */; }; + C23D0D7C1F1A649900AF5151 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */; }; C23D0D7D1F1A692100AF5151 /* SFCompactRounded-Semibold.otf in CopyFiles */ = {isa = PBXBuildFile; fileRef = C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */; }; - C240E9521F96449E00F671FA /* TwoStepVerificationPasswordEntryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C240E9511F96449E00F671FA /* TwoStepVerificationPasswordEntryController.swift */; }; - C2412E071DA795D200588C14 /* GalleryControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2412E061DA795D200588C14 /* GalleryControls.swift */; }; + C23EEC891FCC47C1001371CD /* PeerMediaDateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23EEC881FCC47C1001371CD /* PeerMediaDateItem.swift */; }; + C241025D1FD5702D00DB8625 /* ChatMessageBubbleImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = C241025C1FD5702D00DB8625 /* ChatMessageBubbleImages.swift */; }; + C24102651FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = C24102641FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.m */; }; + C241026F1FD58EA900DB8625 /* SImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C241026E1FD58EA800DB8625 /* SImageView.swift */; }; C2423A541F2235080041907F /* InstantPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2423A531F2235080041907F /* InstantPageViewController.swift */; }; C246161B1ED33FFE0026D5BC /* InstantVideoPIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C246161A1ED33FFE0026D5BC /* InstantVideoPIP.swift */; }; + C246D6281FAB72D4004C17FA /* MediaGroupPreviewRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C246D6271FAB72D4004C17FA /* MediaGroupPreviewRowItem.swift */; }; C248BD211E6F09CC004B9106 /* ChatGameContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C248BD201E6F09CC004B9106 /* ChatGameContentView.swift */; }; C248BD221E705B62004B9106 /* PrivacyAndSecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24D9FC91E25033E002CD3F3 /* PrivacyAndSecurityViewController.swift */; }; C248BD241E706104004B9106 /* BlockedPeersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C248BD231E706104004B9106 /* BlockedPeersViewController.swift */; }; C248BD261E706A05004B9106 /* RecentSessionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C248BD251E706A05004B9106 /* RecentSessionsController.swift */; }; C248BD291E706DDA004B9106 /* RecentSessionRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C248BD281E706DDA004B9106 /* RecentSessionRowItem.swift */; }; - C24949121E5B704900D7ED5D /* AccountsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24949111E5B704900D7ED5D /* AccountsListViewController.swift */; }; C24949141E5B763F00D7ED5D /* ApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24949131E5B763F00D7ED5D /* ApplicationContext.swift */; }; C24BA3BD1E9D30F800E8970B /* DeleteSupergroupMessagesModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24BA3BC1E9D30F800E8970B /* DeleteSupergroupMessagesModalController.swift */; }; C24D9F911E1F8F85002CD3F3 /* MajorBackNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24D9F901E1F8F85002CD3F3 /* MajorBackNavigationBar.swift */; }; C24D9FC41E24FFF3002CD3F3 /* PasscodeLockController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24D9FC31E24FFF3002CD3F3 /* PasscodeLockController.swift */; }; C24D9FC71E2500AC002CD3F3 /* PasscodeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24D9FC61E2500AC002CD3F3 /* PasscodeSettingsViewController.swift */; }; - C24D9FDC1E267932002CD3F3 /* PreviewSenderItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24D9FDB1E267932002CD3F3 /* PreviewSenderItems.swift */; }; C24DAB861E08026C005EE404 /* MGalleryVideoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24DAB851E08026C005EE404 /* MGalleryVideoItem.swift */; }; C24DAB931E0828B6005EE404 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C24DAB921E0828B6005EE404 /* JavaScriptCore.framework */; }; C24DABA21E083185005EE404 /* YTVimeoExtractor.m in Sources */ = {isa = PBXBuildFile; fileRef = C24DAB9A1E083184005EE404 /* YTVimeoExtractor.m */; }; @@ -246,11 +502,9 @@ C250B0371DB7BB09004E9FBE /* mime-types.txt in Resources */ = {isa = PBXBuildFile; fileRef = C250B0361DB7BB09004E9FBE /* mime-types.txt */; }; C250B0391DB7BB2D004E9FBE /* MimeTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C250B0381DB7BB2D004E9FBE /* MimeTypes.swift */; }; C250BA8F1E6E1CDC0057CD96 /* ChatMessageThrottledProcessingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C250BA8E1E6E1CDC0057CD96 /* ChatMessageThrottledProcessingManager.swift */; }; - C25253271DF03F5700ADBC98 /* TGOpusAudioRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = C25253261DF03F5700ADBC98 /* TGOpusAudioRecorder.m */; }; + C251FB4C1FEDCC750035E5D7 /* ChatPresentationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C251FB4B1FEDCC750035E5D7 /* ChatPresentationUtils.swift */; }; C252532A1DF03F9600ADBC98 /* TGAudioWaveform.m in Sources */ = {isa = PBXBuildFile; fileRef = C25253291DF03F9600ADBC98 /* TGAudioWaveform.m */; }; C252532D1DF04AF500ADBC98 /* begin_record.caf in Resources */ = {isa = PBXBuildFile; fileRef = C252532C1DF0440300ADBC98 /* begin_record.caf */; }; - C2538E521E770B4600B21DF0 /* GroupAdminsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2538E511E770B4600B21DF0 /* GroupAdminsController.swift */; }; - C253A92D1D8EE1A600CDC850 /* ChatMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A92C1D8EE1A600CDC850 /* ChatMediaView.swift */; }; C253A9451D90303200CDC850 /* FastBlur.m in Sources */ = {isa = PBXBuildFile; fileRef = C253A9441D90303200CDC850 /* FastBlur.m */; }; C253A95C1D9165A400CDC850 /* libwebp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C253A95B1D9165A400CDC850 /* libwebp.a */; }; C253A95F1D9165CD00CDC850 /* webp.m in Sources */ = {isa = PBXBuildFile; fileRef = C253A95E1D9165CD00CDC850 /* webp.m */; }; @@ -269,13 +523,15 @@ C253E23A1DE4D3DB0022A29F /* ChatInterfaceInputContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253E2391DE4D3DB0022A29F /* ChatInterfaceInputContext.swift */; }; C253E23C1DE4D4080022A29F /* ChatInterfaceStateContextQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253E23B1DE4D4080022A29F /* ChatInterfaceStateContextQueries.swift */; }; C253E23E1DE61CB50022A29F /* ContextListRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253E23D1DE61CB50022A29F /* ContextListRowItem.swift */; }; + C256A9141FB9CBF10043D497 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C256A9131FB9CBF10043D497 /* LocalAuthentication.framework */; }; + C256A9161FB9E1490043D497 /* AdditionalSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C256A9151FB9E1490043D497 /* AdditionalSettings.swift */; }; C258D1B41F8D385700458478 /* PreHistorySettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C258D1B31F8D385700458478 /* PreHistorySettingsController.swift */; }; C258D1B61F8D3A0D00458478 /* PreHistoryControllerStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C258D1B51F8D3A0D00458478 /* PreHistoryControllerStructures.swift */; }; C25911371DF1A68200671E72 /* ChatInputRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25911361DF1A68200671E72 /* ChatInputRecordingView.swift */; }; C2593FBF1F7D242E00F6D2B1 /* TGPaintShader.m in Sources */ = {isa = PBXBuildFile; fileRef = C2593FBE1F7D241E00F6D2B1 /* TGPaintShader.m */; }; C259ED1C1DB8DC78008E6712 /* ChatNavigateScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C259ED1B1DB8DC78008E6712 /* ChatNavigateScroller.swift */; }; C259ED1E1DB956C1008E6712 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C259ED1D1DB956C1008E6712 /* QuartzCore.framework */; }; - C25BB1691F867FEE0089ED02 /* ChatVideoAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25BB1681F867FEE0089ED02 /* ChatVideoAccessoryView.swift */; }; + C25BB1691F867FEE0089ED02 /* ChatMessageAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25BB1681F867FEE0089ED02 /* ChatMessageAccessoryView.swift */; }; C25C132D1E8A404F00AE26A1 /* InstalledStickerPacksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C132C1E8A404F00AE26A1 /* InstalledStickerPacksController.swift */; }; C25C132F1E8A405E00AE26A1 /* ArchivedStickerPacksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C132E1E8A405E00AE26A1 /* ArchivedStickerPacksController.swift */; }; C25C13311E8A406D00AE26A1 /* FeaturedStickerPacksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25C13301E8A406D00AE26A1 /* FeaturedStickerPacksController.swift */; }; @@ -289,16 +545,16 @@ C26505971E041B91001954DC /* MGalleryGIFItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26505961E041B90001954DC /* MGalleryGIFItem.swift */; }; C26546CC1EA0AC3C00E3969A /* ChatVideoMessageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26546CB1EA0AC3C00E3969A /* ChatVideoMessageItem.swift */; }; C26A37EC1E5DE465006977AC /* ChannelAdminsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A37EB1E5DE464006977AC /* ChannelAdminsViewController.swift */; }; - C26A37EE1E5DE48F006977AC /* ChannelBlacklistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A37ED1E5DE48F006977AC /* ChannelBlacklistViewController.swift */; }; + C26A37EE1E5DE48F006977AC /* ChannelBlocklistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A37ED1E5DE48F006977AC /* ChannelBlocklistViewController.swift */; }; C26A71991DC9FA5100F69385 /* EditMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A71981DC9FA5100F69385 /* EditMessageModel.swift */; }; C26A719B1DC9FB3600F69385 /* InputPasteboardParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A719A1DC9FB3600F69385 /* InputPasteboardParser.swift */; }; C26D8A3C1E464944002FAA3F /* JoinLinkPreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26D8A3B1E464944002FAA3F /* JoinLinkPreviewModalController.swift */; }; C26E82D11E83EFFE0046DF2F /* TimeObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = C26E82D01E83EFFE0046DF2F /* TimeObserver.m */; }; C271EB901EB8E6A40034792D /* SelectivePrivacySettingsPeersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C271EB8F1EB8E6A40034792D /* SelectivePrivacySettingsPeersController.swift */; }; C271EB971EB916870034792D /* libtgvoip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C271EB961EB916870034792D /* libtgvoip.framework */; }; - C271EB9B1EB9DEF00034792D /* CallBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = C271EB9A1EB9DEF00034792D /* CallBridge.mm */; }; + C271EB9B1EB9DEF00034792D /* OngoingCallThreadLocalContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = C271EB9A1EB9DEF00034792D /* OngoingCallThreadLocalContext.mm */; }; C271EBA21EB9F04E0034792D /* TGCallUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = C271EBA11EB9F04E0034792D /* TGCallUtils.mm */; }; - C271EBE91EBA22FE0034792D /* TGCallConnectionDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = C271EBE81EBA22FE0034792D /* TGCallConnectionDescription.m */; }; + C271EBE91EBA22FE0034792D /* OngoingCallConnectionDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = C271EBE81EBA22FE0034792D /* OngoingCallConnectionDescription.m */; }; C271EBEB1EBA3BC90034792D /* PCallSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C271EBEA1EBA3BC90034792D /* PCallSession.swift */; }; C275932E1DF6E1CE00A0807A /* AboutModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C275932D1DF6E1CE00A0807A /* AboutModalController.swift */; }; C275E9EF1F8FCA4200D3D8C0 /* PhoneNumberIntroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C275E9EE1F8FCA4200D3D8C0 /* PhoneNumberIntroController.swift */; }; @@ -307,10 +563,6 @@ C276248C1D95AF7600FE5B2B /* ObjcUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C276248B1D95AF7600FE5B2B /* ObjcUtils.m */; }; C276248E1D95B4F300FE5B2B /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C276248D1D95B4F300FE5B2B /* Extensions.swift */; }; C276383E1E8A9A86009E7839 /* StickerSetTableRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C276383D1E8A9A86009E7839 /* StickerSetTableRowItem.swift */; }; - C276AFC71F74F2CF00DEDD8E /* Sparkle.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C231992D1EE006330011BEBE /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C276AFC81F74F2CF00DEDD8E /* HockeySDK.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = C2A71CD81DDA0FA300C69F73 /* HockeySDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C276AFC91F74F2D200DEDD8E /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C231992D1EE006330011BEBE /* Sparkle.framework */; }; - C276AFCA1F74F2D200DEDD8E /* HockeySDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2A71CD81DDA0FA300C69F73 /* HockeySDK.framework */; }; C2777B5B1DCE11A5008B69DD /* SendingClockProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2777B5A1DCE11A5008B69DD /* SendingClockProgress.swift */; }; C2777B601DCF4766008B69DD /* CoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2777B5F1DCF4766008B69DD /* CoreExtension.swift */; }; C2777B621DCFB4C9008B69DD /* ChatServiceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2777B611DCFB4C9008B69DD /* ChatServiceItem.swift */; }; @@ -325,20 +577,20 @@ C27AAFE91DE9DA61009B9629 /* CountryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27AAFE81DE9DA61009B9629 /* CountryManager.swift */; }; C27AAFED1DEB1D72009B9629 /* SignalUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27AAFEC1DEB1D72009B9629 /* SignalUtils.swift */; }; C27AAFEF1DEB2EA9009B9629 /* EmptyComposeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27AAFEE1DEB2EA9009B9629 /* EmptyComposeController.swift */; }; - C28149881EA7F22200BB933E /* PreparedChatHistoryViewTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28149871EA7F22200BB933E /* PreparedChatHistoryViewTransition.swift */; }; + C27BAC7A20CFCE68007A7508 /* PassportSettingsHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27BAC7920CFCE68007A7508 /* PassportSettingsHeaderItem.swift */; }; C281498A1EA7F44300BB933E /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28149891EA7F44300BB933E /* ListViewIntermediateState.swift */; }; C2844AD71DA907E8009308DC /* EntertainmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2844AD61DA907E8009308DC /* EntertainmentViewController.swift */; }; C2844AE01DA90C8A009308DC /* emoji.txt in Resources */ = {isa = PBXBuildFile; fileRef = C2844ADF1DA90C8A009308DC /* emoji.txt */; }; C28BAB271DF980DE0027CE3A /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28BAB261DF980DE0027CE3A /* AudioRecorder.swift */; }; C28BAB291DF981320027CE3A /* AudioWaveform.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28BAB281DF981320027CE3A /* AudioWaveform.swift */; }; C28BAB2C1DF9C2790027CE3A /* DateUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = C28BAB2B1DF9C2790027CE3A /* DateUtils.mm */; }; + C2905E1C207E4D9E00990AD7 /* InstantPageAudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2905E1B207E4D9E00990AD7 /* InstantPageAudioView.swift */; }; + C2905E1E207E545600990AD7 /* InstantPageAudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2905E1D207E545600990AD7 /* InstantPageAudioItem.swift */; }; C291942F1DCC6E2200359491 /* DeclareEncodables.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291942E1DCC6E2200359491 /* DeclareEncodables.swift */; }; C291E2731E8AFA2C00D397BA /* ShareApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291E2721E8AFA2C00D397BA /* ShareApplicationContext.swift */; }; C291E2741E8B051100D397BA /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA17801E2FD50A00887153 /* ImageUtils.swift */; }; - C291E2751E8B051900D397BA /* PhotoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C210742B1E780CC1006EE5EF /* PhotoCache.swift */; }; C29340F11F506C310074991E /* EmptyGroupstickerSearchRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F01F506C310074991E /* EmptyGroupstickerSearchRowItem.swift */; }; C295C65F1F75808600BA309D /* ChatAdditionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C295C65E1F75808600BA309D /* ChatAdditionController.swift */; }; - C29670791F0FAAC800884DA2 /* AppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29670781F0FAAC800884DA2 /* AppearanceViewController.swift */; }; C296707B1F0FBFB500884DA2 /* ThemeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C296707A1F0FBFB500884DA2 /* ThemeSettings.swift */; }; C296AF7F1D8D38E5001DBB59 /* ChatRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C296AF7E1D8D38E5001DBB59 /* ChatRowView.swift */; }; C296AF861D8DB178001DBB59 /* MediaUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C296AF851D8DB178001DBB59 /* MediaUtils.swift */; }; @@ -368,11 +620,10 @@ C29B5F4F1DC8F39A00D13E65 /* FWDNavigationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F4E1DC8F39A00D13E65 /* FWDNavigationAction.swift */; }; C29C3E5B1E421F1700193A7E /* ChatAccessoryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29C3E5A1E421F1700193A7E /* ChatAccessoryModel.swift */; }; C29C3E6F1E4352C100193A7E /* ContextStickerRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29C3E6E1E4352C100193A7E /* ContextStickerRowItem.swift */; }; - C29C3E711E43881500193A7E /* StickerPreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29C3E701E43881500193A7E /* StickerPreviewModalController.swift */; }; + C29C3E711E43881500193A7E /* ModalPreviewViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29C3E701E43881500193A7E /* ModalPreviewViews.swift */; }; C29C3E731E4397F300193A7E /* StickerPreviewHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29C3E721E4397F300193A7E /* StickerPreviewHandler.swift */; }; C29E0EE01F4DC43100C0C7A8 /* InstantViewAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29E0EDF1F4DC43100C0C7A8 /* InstantViewAppearance.swift */; }; C29E0EE21F4EFB5100C0C7A8 /* GroupStickerSetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29E0EE11F4EFB5100C0C7A8 /* GroupStickerSetController.swift */; }; - C29F4C761F45F58B00DBFC00 /* MIHSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = C29F4C751F45F58B00DBFC00 /* MIHSliderView.m */; }; C29F4C781F45FBFF00DBFC00 /* InstantPageSlideshowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29F4C771F45FBFF00DBFC00 /* InstantPageSlideshowItem.swift */; }; C29F4C7D1F47283600DBFC00 /* InstantPageBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29F4C7C1F47283600DBFC00 /* InstantPageBrowser.swift */; }; C2A1054B1E0163D500B01F48 /* GalleryPageController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A1054A1E0163D500B01F48 /* GalleryPageController.swift */; }; @@ -392,7 +643,6 @@ C2A71CE11DDB18FF00C69F73 /* ThumbUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CE01DDB18FF00C69F73 /* ThumbUtils.swift */; }; C2A71CE31DDB2EBD00C69F73 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CE21DDB2EBD00C69F73 /* GeneralSettingsViewController.swift */; }; C2A71CE71DDB2F8700C69F73 /* UsernameSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CE61DDB2F8700C69F73 /* UsernameSettingsViewController.swift */; }; - C2A71CE91DDB342100C69F73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CE81DDB342100C69F73 /* NotificationSettingsViewController.swift */; }; C2A71CED1DDB3C1000C69F73 /* ChatHeaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CEC1DDB3C1000C69F73 /* ChatHeaderController.swift */; }; C2A72D901DEC66F300C3B945 /* LoginErrorStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A72D8F1DEC66F300C3B945 /* LoginErrorStateView.swift */; }; C2A87DE81F4C6910002D3F73 /* InstantViewWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A87DE71F4C6910002D3F73 /* InstantViewWindow.swift */; }; @@ -403,7 +653,6 @@ C2AC9C181E1E687E0085C7DE /* GlobalBadgeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AC9C171E1E687E0085C7DE /* GlobalBadgeNode.swift */; }; C2AF01131F01543200D8AC1D /* ExportProxyModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AF01121F01543200D8AC1D /* ExportProxyModalController.swift */; }; C2AF011D1F03D4C600D8AC1D /* TGCallAesCtr.m in Sources */ = {isa = PBXBuildFile; fileRef = C2AF011C1F03D4C600D8AC1D /* TGCallAesCtr.m */; }; - C2AF3B821E5CD79200DFDD81 /* ConvertGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AF3B811E5CD79200DFDD81 /* ConvertGroupViewController.swift */; }; C2B0722E1DFEDE430082939D /* UsernameInputRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B0722D1DFEDE430082939D /* UsernameInputRowItem.swift */; }; C2B1A0EF1D9D94CE00ACB1DD /* SeparatorRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A0EE1D9D94CE00ACB1DD /* SeparatorRowItem.swift */; }; C2B1A0F11D9D94E400ACB1DD /* SeparatorRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A0F01D9D94E400ACB1DD /* SeparatorRowView.swift */; }; @@ -425,23 +674,23 @@ C2B9BE891EFC5E7000D6B96F /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B9BE881EFC5E7000D6B96F /* Appearance.swift */; }; C2BB120A1ED87C5A00BDE46A /* ControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BB12091ED87C5A00BDE46A /* ControllerExtension.swift */; }; C2BB2DB01F8BDF6700520255 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BB2DAF1F8BDF6700520255 /* Config.swift */; }; - C2C5C2051EF822B900AEA252 /* ProxySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C5C2041EF822B900AEA252 /* ProxySettingsViewController.swift */; }; - C2C738F11DD898DA00CE9D8A /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2C738F01DD898DA00CE9D8A /* AVKit.framework */; }; + C2C415E51FA33D1A00FF36F4 /* InputFormatterPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C415E41FA33D1A00FF36F4 /* InputFormatterPopover.swift */; }; C2C73A5B1EEAF3AE00DB8420 /* ChannelEventFilterModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C73A5A1EEAF3AE00DB8420 /* ChannelEventFilterModalController.swift */; }; C2C98FEF1E818FB5009CBDB7 /* ClearUserNotifies.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C98FEE1E818FB5009CBDB7 /* ClearUserNotifies.swift */; }; C2C9B92C1E80165D00380D79 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2C9B9281E8011B000380D79 /* IOKit.framework */; }; - C2C9B92D1E80166400380D79 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2C9B92A1E8011CD00380D79 /* Carbon.framework */; }; C2C9B9341E8016C400380D79 /* NSObject+SPInvocationGrabbing.m in Sources */ = {isa = PBXBuildFile; fileRef = C2C9B9331E8016C400380D79 /* NSObject+SPInvocationGrabbing.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C2C9B9371E8016D400380D79 /* SPMediaKeyTap.m in Sources */ = {isa = PBXBuildFile; fileRef = C2C9B9361E8016D400380D79 /* SPMediaKeyTap.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; C2CBCAC01D81528700142EC0 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCABF1D81528700142EC0 /* System.swift */; }; C2CBCAC51D81649E00142EC0 /* ChatListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCAC41D81649E00142EC0 /* ChatListRowView.swift */; }; + C2CE43E420E2CFE800656543 /* PlayerListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CE43E320E2CFE700656543 /* PlayerListController.swift */; }; + C2CE43E920F4F74F00656543 /* UpdateModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CE43E820F4F74F00656543 /* UpdateModalController.swift */; }; C2CFCABE1EBB4A4D00843F6A /* voip_connecting.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C2CFCABD1EBB4A4D00843F6A /* voip_connecting.mp3 */; }; C2CFCAC01EBB9C8100843F6A /* CallAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CFCABF1EBB9C8100843F6A /* CallAudioPlayer.swift */; }; C2D1839D1E4DBF7E001CE25A /* MGalleryPeerPhotoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D1839C1E4DBF7E001CE25A /* MGalleryPeerPhotoItem.swift */; }; C2D187EE1E28B9CB0038961D /* ShareInlineResultNavigationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D187ED1E28B9CB0038961D /* ShareInlineResultNavigationAction.swift */; }; C2D187F01E28C58C0038961D /* ContextSwitchPeerRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D187EF1E28C58C0038961D /* ContextSwitchPeerRowItem.swift */; }; C2D187F21E28D0840038961D /* ChatSwitchInlineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D187F11E28D0840038961D /* ChatSwitchInlineController.swift */; }; - C2D2CAED1E64579700939968 /* StickersPackPreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D2CAEC1E64579700939968 /* StickersPackPreviewModalController.swift */; }; + C2D2CAED1E64579700939968 /* StickerPackPreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D2CAEC1E64579700939968 /* StickerPackPreviewModalController.swift */; }; C2D2CAF01E64874600939968 /* StickerPackGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D2CAEF1E64874600939968 /* StickerPackGridItem.swift */; }; C2DDA04E1EC0C024003531BB /* opening.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C2DDA04D1EC0C024003531BB /* opening.mp3 */; }; C2DDA0501EC0C19A003531BB /* opening.m4a in Resources */ = {isa = PBXBuildFile; fileRef = C2DDA04F1EC0C19A003531BB /* opening.m4a */; }; @@ -458,7 +707,6 @@ C2DE5D3C1F3CAE120081EC1E /* InstantPageTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE5D3B1F3CAE120081EC1E /* InstantPageTextItem.swift */; }; C2DE5D3E1F3CAF2B0081EC1E /* InstantPageShapeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE5D3D1F3CAF2B0081EC1E /* InstantPageShapeItem.swift */; }; C2DE5D401F3CB0380081EC1E /* InstantPageTileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DE5D3F1F3CB0380081EC1E /* InstantPageTileView.swift */; }; - C2DEC87F1DECB8C800F6544A /* TelegramApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DEC87E1DECB8C800F6544A /* TelegramApplicationContext.swift */; }; C2DF47961DE71160003AA6C0 /* GIFContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DF47951DE71160003AA6C0 /* GIFContainerView.swift */; }; C2DF47BA1DE79574003AA6C0 /* bitwise.c in Sources */ = {isa = PBXBuildFile; fileRef = C2DF479B1DE79574003AA6C0 /* bitwise.c */; }; C2DF47BB1DE79574003AA6C0 /* framing.c in Sources */ = {isa = PBXBuildFile; fileRef = C2DF479C1DE79574003AA6C0 /* framing.c */; }; @@ -487,22 +735,21 @@ C2E0646B1ECF137300387BB8 /* ChatInvoiceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0646A1ECF137300387BB8 /* ChatInvoiceItem.swift */; }; C2E40A1B1E37ADAF0099AC7D /* PeerMediaEmptyRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E40A1A1E37ADAF0099AC7D /* PeerMediaEmptyRowItem.swift */; }; C2E52A0D1EB8C386009AF87D /* SelectivePrivacySettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E52A0C1EB8C385009AF87D /* SelectivePrivacySettingsController.swift */; }; + C2E6F3CF1F9F85260023653D /* ContextHashtagRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E6F3CE1F9F85260023653D /* ContextHashtagRowItem.swift */; }; C2E8694D1F43500D00BDD0A2 /* ChatNavigationMention.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E8694C1F43500D00BDD0A2 /* ChatNavigationMention.swift */; }; + C2E8BA071FB5EF4C00DEB5E2 /* GalleryThumbsControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E8BA061FB5EF4C00DEB5E2 /* GalleryThumbsControl.swift */; }; + C2E8BA0A1FB5F15900DEB5E2 /* GalleryThumbsControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E8BA091FB5F15900DEB5E2 /* GalleryThumbsControlView.swift */; }; C2EA177F1E2FD50000887153 /* TransformImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA177E1E2FD50000887153 /* TransformImageView.swift */; }; C2EA17811E2FD50A00887153 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA17801E2FD50A00887153 /* ImageUtils.swift */; }; - C2EA53471F751EF300C183F7 /* GalleryMessageEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2EA53461F751EF300C183F7 /* GalleryMessageEntry.swift */; }; + C2EBBEA11FB5CA94009AD8ED /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2EBBEA01FB5CA94009AD8ED /* CoreServices.framework */; }; C2F4ED1B1EC5AE1D005F2696 /* CallRatingModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F4ED1A1EC5AE1D005F2696 /* CallRatingModalViewController.swift */; }; C2F6190D1E844DCD007A051B /* TelegramAccountAuxiliaryMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F6190C1E844DCD007A051B /* TelegramAccountAuxiliaryMethods.swift */; }; C2F8923D1E3FA51000D98B2D /* PasteboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F8923C1E3FA51000D98B2D /* PasteboardUtils.swift */; }; C2F93A2D1F3C55C500BCD48F /* EmojiToleranceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F93A2C1F3C55C500BCD48F /* EmojiToleranceController.swift */; }; C2F952B01F8E1C840056E586 /* CachedAdminIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F952AF1F8E1C840056E586 /* CachedAdminIds.swift */; }; C2F9C4481F9500B4002B2CBF /* TwoStepVerificationUnlockController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F9C4471F9500B4002B2CBF /* TwoStepVerificationUnlockController.swift */; }; - C2F9C44A1F9500C3002B2CBF /* TwoStepVerificationUnlockStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F9C4491F9500C3002B2CBF /* TwoStepVerificationUnlockStructures.swift */; }; C2F9C44C1F95FE58002B2CBF /* Markdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F9C44B1F95FE58002B2CBF /* Markdown.swift */; }; C2FB2FAB1EBF73D00093C8BA /* RecentCallsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FB2FAA1EBF73CF0093C8BA /* RecentCallsViewController.swift */; }; - C2FBC1D71DC61AFF0063A23B /* TelegramCoreMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1D61DC61AFF0063A23B /* TelegramCoreMac.framework */; }; - C2FBC1D91DC61B050063A23B /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1D81DC61B050063A23B /* SwiftSignalKitMac.framework */; }; - C2FBC1DF1DC61B580063A23B /* MtProtoKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FBC1DE1DC61B580063A23B /* MtProtoKitMac.framework */; }; C2FBC1E71DC631980063A23B /* SPPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FBC1E61DC631980063A23B /* SPPreviewController.swift */; }; C2FD33E91E696A86008D13D4 /* GroupsInCommonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33E81E696A86008D13D4 /* GroupsInCommonViewController.swift */; }; C2FD33EE1E697B31008D13D4 /* TransformOutgoingMessageMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33ED1E697B31008D13D4 /* TransformOutgoingMessageMedia.swift */; }; @@ -511,17 +758,163 @@ C2FD33F81E6C1486008D13D4 /* SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33F51E6C1486008D13D4 /* SSKeychain.m */; }; C2FD33F91E6C1486008D13D4 /* SSKeychainQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33F71E6C1486008D13D4 /* SSKeychainQuery.m */; }; C2FD33FB1E6C169C008D13D4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2FD33FA1E6C169C008D13D4 /* Security.framework */; }; - C2FD34131E6C2503008D13D4 /* LegacyImportAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD34121E6C2503008D13D4 /* LegacyImportAuthorization.swift */; }; C2FD34151E6C9003008D13D4 /* BaseApplicationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD34141E6C9003008D13D4 /* BaseApplicationSettings.swift */; }; C2FD382E1DCA1FA3009DC28C /* PreviewSenderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD382D1DCA1FA3009DC28C /* PreviewSenderController.swift */; }; C2FD38321DCA215F009DC28C /* GeneralInputRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD38311DCA215F009DC28C /* GeneralInputRow.swift */; }; C2FF14601E532C0A007B7B14 /* SearchEmptyRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FF145F1E532C0A007B7B14 /* SearchEmptyRowItem.swift */; }; + D001E974243B1A0A009025F9 /* LeftSidebarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001E973243B1A0A009025F9 /* LeftSidebarController.swift */; }; + D001E976243B385E009025F9 /* FolderIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001E975243B385E009025F9 /* FolderIcons.swift */; }; + D001E978243B4639009025F9 /* LeftSidebarFolderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001E977243B4639009025F9 /* LeftSidebarFolderItem.swift */; }; + D004167522D37AD00000566B /* StickerPackPanelRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D004167422D37AD00000566B /* StickerPackPanelRowItem.swift */; }; + D004167722D4AD3B0000566B /* StickerPackItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D004167622D4AD3B0000566B /* StickerPackItems.swift */; }; + D004BD2B23153415009A54B1 /* ThemePreviewModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D004BD2A23153415009A54B1 /* ThemePreviewModalController.swift */; }; + D007AB9F234C8DCB0022C27F /* WalletPasscodeTimeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007AB9E234C8DCB0022C27F /* WalletPasscodeTimeout.swift */; }; + D007ABA1234CACE40022C27F /* WalletCreateInvoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007ABA0234CACE40022C27F /* WalletCreateInvoiceController.swift */; }; + D00C73B62302C196004B1E2B /* ChatScheduleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C73B52302C196004B1E2B /* ChatScheduleController.swift */; }; + D00CE4F72284D530008C1B4F /* BuildConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F291C9F2264E57F00C66267 /* BuildConfig.m */; }; + D00CE4F82284D5E3008C1B4F /* TransformOutgoingMessageMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33ED1E697B31008D13D4 /* TransformOutgoingMessageMedia.swift */; }; + D00CE50D2289C9B7008C1B4F /* MediaAnimatedStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CE50C2289C9B7008C1B4F /* MediaAnimatedStickerView.swift */; }; + D00CE50F2289CE87008C1B4F /* ChatAnimatedStickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00CE50E2289CE87008C1B4F /* ChatAnimatedStickerItem.swift */; }; + D014193C22AE939F008667CB /* ModalOptionSetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D014193B22AE939F008667CB /* ModalOptionSetController.swift */; }; + D014193E22AE9A90008667CB /* GeneralLineSeparatorRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D014193D22AE9A90008667CB /* GeneralLineSeparatorRowItem.swift */; }; + D014AA242316CE0700CE5362 /* NewThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D014AA232316CE0700CE5362 /* NewThemeController.swift */; }; + D014AA262317D07D00CE5362 /* EditThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D014AA252317D07D00CE5362 /* EditThemeController.swift */; }; + D017A3EB2334F5680086174B /* WalletBalanceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3EA2334F5680086174B /* WalletBalanceItem.swift */; }; + D017A3ED2335166B0086174B /* WalletInfoCreatedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3EC2335166B0086174B /* WalletInfoCreatedItem.swift */; }; + D017A3EF2337D1B90086174B /* WalletImportWordsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3EE2337D1B90086174B /* WalletImportWordsItem.swift */; }; + D017A3F12338DE1E0086174B /* WalletTestWordsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3F02338DE1E0086174B /* WalletTestWordsItem.swift */; }; + D017A3F523390D8C0086174B /* WalletInfoTransactionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3F423390D8C0086174B /* WalletInfoTransactionItem.swift */; }; + D017A3F9233929B10086174B /* WalletReceiveController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3F8233929B10086174B /* WalletReceiveController.swift */; }; + D017A3FB2339404D0086174B /* WalletTransactionTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3FA2339404D0086174B /* WalletTransactionTextItem.swift */; }; + D017A3FD233949B00086174B /* WalletSendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017A3FC233949B00086174B /* WalletSendController.swift */; }; + D0186731223807D200A77C45 /* ChatListEmptyRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0186730223807D200A77C45 /* ChatListEmptyRowItem.swift */; }; + D01C731722A9814C000DA008 /* InputPasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C731622A9814C000DA008 /* InputPasswordController.swift */; }; + D01CBE2D22A5384700F6A971 /* NotificationPreferencesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01CBE2C22A5384600F6A971 /* NotificationPreferencesController.swift */; }; + D01E1F0422B39A4800AD6DAE /* LottiePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E1F0322B39A4800AD6DAE /* LottiePlayer.swift */; }; + D0276B7D22BD6511003155D8 /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0276B7C22BD6511003155D8 /* DisplayLink.swift */; }; + D02BD7C0232D0FB800D1814A /* AppAppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BD7BF232D0FB800D1814A /* AppAppearanceViewController.swift */; }; + D02BD7C3232D14E800D1814A /* ThemePreviewRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BD7C2232D14E800D1814A /* ThemePreviewRowItem.swift */; }; + D02BD7C5232D204F00D1814A /* ThemeListRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BD7C4232D204F00D1814A /* ThemeListRowItem.swift */; }; + D02BD7C7232D4DB200D1814A /* AppearanceThumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BD7C6232D4DB200D1814A /* AppearanceThumbs.swift */; }; + D02F0A0022E8875800553411 /* SoftwareVideoSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F09FF22E8875800553411 /* SoftwareVideoSource.swift */; }; + D02F0A0222E88C6E00553411 /* MediaPlayerFramePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F0A0122E88C6E00553411 /* MediaPlayerFramePreview.swift */; }; + D02F0A0422E88C9900553411 /* UniversalSoftwareVideoSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F0A0322E88C9900553411 /* UniversalSoftwareVideoSource.swift */; }; + D0439A2023410D5400C1E6F9 /* WalletUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0439A1F23410D5400C1E6F9 /* WalletUtils.swift */; }; + D04D213F230D68B800609388 /* CustomAccentColorModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D213E230D68B800609388 /* CustomAccentColorModalController.swift */; }; + D04D2144230DB55B00609388 /* TelegramIconsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D2143230DB55B00609388 /* TelegramIconsTheme.swift */; }; + D04D2145230DB57300609388 /* TelegramIconsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D2143230DB55B00609388 /* TelegramIconsTheme.swift */; }; + D05051902342BA6700D93176 /* WalletSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050518F2342BA6700D93176 /* WalletSettingsController.swift */; }; + D0558D7C215141CF006B403D /* ChatInfoTouchbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0558D7B215141CF006B403D /* ChatInfoTouchbar.swift */; }; + D0558D852152B4D3006B403D /* GalleryTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0558D842152B4D3006B403D /* GalleryTouchBar.swift */; }; + D0558D872154047E006B403D /* GalleryTouchBarThumbItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0558D862154047E006B403D /* GalleryTouchBarThumbItemView.swift */; }; + D05F392322FB45450040F341 /* ScheduledMessageModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F392222FB45450040F341 /* ScheduledMessageModalController.swift */; }; + D0675D84217F1F27004900A7 /* ArchiverContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0675D83217F1F27004900A7 /* ArchiverContext.swift */; }; + D06ACAEF231AC265002DCD81 /* ChatMessageBubbleImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = C241025C1FD5702D00DB8625 /* ChatMessageBubbleImages.swift */; }; + D06ACAF0231AC3E5002DCD81 /* ParseAppearanceColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21BE3B01FD14CDB00C1C849 /* ParseAppearanceColors.swift */; }; + D06C7BB5247D6F4B00E67C3C /* SoftwareVideoLayerFrameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06C7BB4247D6F4B00E67C3C /* SoftwareVideoLayerFrameManager.swift */; }; + D06C7BB7247D6FA900E67C3C /* SampleBufferPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06C7BB6247D6FA900E67C3C /* SampleBufferPool.swift */; }; + D06C7BB9247E664900E67C3C /* SoftwareVideoThumbnailLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06C7BB8247E664900E67C3C /* SoftwareVideoThumbnailLayer.swift */; }; + D06C7BBB247FAE3E00E67C3C /* GifPlayerBufferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06C7BBA247FAE3E00E67C3C /* GifPlayerBufferView.swift */; }; + D06CD4F022F22F1100E444DF /* PhotoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C210742B1E780CC1006EE5EF /* PhotoCache.swift */; }; + D070DB8122D3638F008A0BBE /* StickersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D070DB8022D3638F008A0BBE /* StickersViewController.swift */; }; + D071E8A421496F38001B6024 /* ChatListTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8A321496F38001B6024 /* ChatListTouchBar.swift */; }; + D071E8A6214A805C001B6024 /* ChatTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8A5214A805C001B6024 /* ChatTouchBar.swift */; }; + D071E8A8214BBE21001B6024 /* ChatStickersTouchBarPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8A7214BBE21001B6024 /* ChatStickersTouchBarPopover.swift */; }; + D071E8AA214BC589001B6024 /* TouchBarStickerItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8A9214BC589001B6024 /* TouchBarStickerItemView.swift */; }; + D071E8AC214BE6E7001B6024 /* TouchBarScrubberHeaderItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8AB214BE6E7001B6024 /* TouchBarScrubberHeaderItemView.swift */; }; + D071E8B3214C15C7001B6024 /* TouchBarEmojiPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8B2214C15C7001B6024 /* TouchBarEmojiPicker.swift */; }; + D071E8B5214C1935001B6024 /* TouchBarEmojiItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071E8B4214C1935001B6024 /* TouchBarEmojiItemView.swift */; }; + D07450D5233B8D2900769D7F /* WalletTransactionDateStickItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07450D4233B8D2900769D7F /* WalletTransactionDateStickItem.swift */; }; + D07450D8233BB86900769D7F /* WalletSendProccessingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07450D7233BB86900769D7F /* WalletSendProccessingView.swift */; }; + D07450DA233BB92F00769D7F /* WalletSendSuccessController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07450D9233BB92F00769D7F /* WalletSendSuccessController.swift */; }; + D07450E6233D61B800769D7F /* brilliant_static.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450DC233D61B000769D7F /* brilliant_static.tgs */; }; + D07450E7233D61B800769D7F /* gift.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450DD233D61B100769D7F /* gift.tgs */; }; + D07450E8233D61B800769D7F /* brilliant_loading.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450DE233D61B200769D7F /* brilliant_loading.tgs */; }; + D07450E9233D61B800769D7F /* keyboard_typing.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450DF233D61B200769D7F /* keyboard_typing.tgs */; }; + D07450EA233D61B800769D7F /* write_words.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E0233D61B300769D7F /* write_words.tgs */; }; + D07450EB233D61B800769D7F /* fly_dollar.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E1233D61B400769D7F /* fly_dollar.tgs */; }; + D07450EC233D61B800769D7F /* swap_money.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E2233D61B500769D7F /* swap_money.tgs */; }; + D07450ED233D61B800769D7F /* chiken_born.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E3233D61B600769D7F /* chiken_born.tgs */; }; + D07450EE233D61B800769D7F /* keychain.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E4233D61B700769D7F /* keychain.tgs */; }; + D07450EF233D61B800769D7F /* smart_guy.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450E5233D61B800769D7F /* smart_guy.tgs */; }; + D07450F12340DC8200769D7F /* WalletConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07450F02340DC8200769D7F /* WalletConfiguration.swift */; }; + D07450F32340E89100769D7F /* sad_man.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450F22340E89000769D7F /* sad_man.tgs */; }; + D07450F52340F28D00769D7F /* wallet_success_created.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07450F42340F28D00769D7F /* wallet_success_created.tgs */; }; + D074A56524A1DE7700E92F8A /* OngoingCallContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074A56424A1DE7700E92F8A /* OngoingCallContext.swift */; }; + D074A56724A1DF5200E92F8A /* VoipDerivedState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074A56624A1DF5200E92F8A /* VoipDerivedState.swift */; }; + D074A56C24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.mm in Sources */ = {isa = PBXBuildFile; fileRef = D074A56A24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.mm */; }; + D076A07E248A5F890077BC0A /* GifPanelTabRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076A07D248A5F890077BC0A /* GifPanelTabRowItem.swift */; }; + D076F86D22959958004F895A /* InlineLoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F86C22959958004F895A /* InlineLoginController.swift */; }; + D076F8702295B24D004F895A /* InlineAuthOptionRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F86F2295B24D004F895A /* InlineAuthOptionRowItem.swift */; }; + D076F8872296CAB2004F895A /* ChannelDisscussionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F8862296CAB2004F895A /* ChannelDisscussionGroup.swift */; }; + D076F88D2296FD18004F895A /* DiscussionHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F88C2296FD18004F895A /* DiscussionHeaderItem.swift */; }; + D076F88F2297285F004F895A /* DiscussionSetModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F88E2297285F004F895A /* DiscussionSetModalController.swift */; }; + D076F891229823DA004F895A /* ChannelDiscussionInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F890229823DA004F895A /* ChannelDiscussionInputView.swift */; }; + D07B558223338AA300F076A8 /* WalletInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B558123338AA300F076A8 /* WalletInfoController.swift */; }; + D07B558423338B6B00F076A8 /* WalletSplashController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B558323338B6B00F076A8 /* WalletSplashController.swift */; }; + D07B55872333903C00F076A8 /* WalletSplashRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B55862333903C00F076A8 /* WalletSplashRowItem.swift */; }; + D07B55892333A41600F076A8 /* WalletSplashButtonRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B55882333A41600F076A8 /* WalletSplashButtonRowItem.swift */; }; + D07B558D2333E1FE00F076A8 /* Wallet24WordsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B558C2333E1FE00F076A8 /* Wallet24WordsItem.swift */; }; + D07B55912334108E00F076A8 /* WalletSplashHelps.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07B55902334108E00F076A8 /* WalletSplashHelps.swift */; }; + D07C6D7923464C8000468B1A /* WalletKeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07C6D7823464C8000468B1A /* WalletKeychainController.swift */; }; + D07C6D7B2346547600468B1A /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07C6D7A2346547600468B1A /* Keychain.swift */; }; + D07C6D7D234698C600468B1A /* DynamicHeightRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07C6D7C234698C600468B1A /* DynamicHeightRowItem.swift */; }; + D07C6D802346A43000468B1A /* monkey_unsee.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07C6D7E2346A42E00468B1A /* monkey_unsee.tgs */; }; + D07C6D812346A43000468B1A /* monkey_see.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D07C6D7F2346A42F00468B1A /* monkey_see.tgs */; }; + D07C6D832347701700468B1A /* WalletProcessTransactionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07C6D822347701700468B1A /* WalletProcessTransactionController.swift */; }; + D0830FC424127473006198E7 /* folder_new.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D0830FC324127468006198E7 /* folder_new.tgs */; }; + D08E5C9822C583CE007B1C09 /* Tuple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E5C9722C583CE007B1C09 /* Tuple.swift */; }; + D093474E242DCFC4000ECA88 /* PeerMediaGroupPeersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093474D242DCFC4000ECA88 /* PeerMediaGroupPeersController.swift */; }; D098C7191D7E175A007784E4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098C7181D7E175A007784E4 /* AppDelegate.swift */; }; D098C71B1D7E175A007784E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D098C71A1D7E175A007784E4 /* Assets.xcassets */; }; D098C71E1D7E175A007784E4 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D098C71C1D7E175A007784E4 /* MainMenu.xib */; }; D098C73A1D7E1C40007784E4 /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098C7391D7E1C40007784E4 /* AuthController.swift */; }; + D09D9DF1229C27A700378796 /* AnimatedStickerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09D9DF0229C27A700378796 /* AnimatedStickerUtils.swift */; }; + D09D9DF4229C289F00378796 /* GZip.m in Sources */ = {isa = PBXBuildFile; fileRef = D09D9DF3229C289F00378796 /* GZip.m */; }; + D0A2764E249C9D6B005E3C77 /* PeerPhotos.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2764D249C9D6B005E3C77 /* PeerPhotos.swift */; }; + D0A27650249CE588005E3C77 /* GroupsStatsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2764F249CE588005E3C77 /* GroupsStatsController.swift */; }; + D0A75F25244843D3001F84A0 /* EditImageCanvasController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A75F24244843D3001F84A0 /* EditImageCanvasController.swift */; }; + D0A75F2724486B6D001F84A0 /* EditImageCanvasColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A75F2624486B6D001F84A0 /* EditImageCanvasColorPicker.swift */; }; + D0A75F2A24487A1E001F84A0 /* EditImageCanvasControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A75F2924487A1E001F84A0 /* EditImageCanvasControls.swift */; }; + D0A75F2C2449B689001F84A0 /* dart_idle.tgs in Resources */ = {isa = PBXBuildFile; fileRef = D0A75F2B2449B67D001F84A0 /* dart_idle.tgs */; }; + D0B6D72A22AA65D3008E36FE /* NewContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B6D72922AA65D3008E36FE /* NewContactController.swift */; }; + D0BDD50123A38660002010A5 /* InactiveChannelsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDD50023A38660002010A5 /* InactiveChannelsController.swift */; }; + D0BEB98F21628C250055B718 /* EditImageModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB98E21628C250055B718 /* EditImageModalController.swift */; }; + D0BEB9952166AF270055B718 /* PeerMediaTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB9942166AF270055B718 /* PeerMediaTouchBar.swift */; }; + D0BEB998216BD8A70055B718 /* EditImageControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB997216BD8A70055B718 /* EditImageControls.swift */; }; + D0BEB99A216D10920055B718 /* MediaPreviewEditControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB999216D10920055B718 /* MediaPreviewEditControl.swift */; }; + D0C39EC3233A5D9B003CD402 /* WalletTransactionPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C39EC2233A5D9B003CD402 /* WalletTransactionPreviewController.swift */; }; + D0C39EC5233A6077003CD402 /* GeneralBlockTextRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C39EC4233A6077003CD402 /* GeneralBlockTextRowItem.swift */; }; + D0CBB0F52492974F00620C65 /* VideoAvatarModalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CBB0F42492974F00620C65 /* VideoAvatarModalController.swift */; }; + D0CC4ADE22BA5C930088F36D /* LottieBufferCompressor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CC4ADD22BA5C930088F36D /* LottieBufferCompressor.swift */; }; + D0CD3095221970F8005D23DD /* FFMpegRemuxer.m in Sources */ = {isa = PBXBuildFile; fileRef = D0CD3094221970F8005D23DD /* FFMpegRemuxer.m */; }; + D0CD309822197E62005D23DD /* FFMpegGlobals.m in Sources */ = {isa = PBXBuildFile; fileRef = D0CD309722197E62005D23DD /* FFMpegGlobals.m */; }; + D0D087E2243DF4F100E05317 /* ChatlistFilterVisibilityItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D087E1243DF4F100E05317 /* ChatlistFilterVisibilityItem.swift */; }; + D0D3CE6C23D465FA00864F3C /* PollResultStickItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D3CE6B23D465FA00864F3C /* PollResultStickItem.swift */; }; + D0D71386227ADE9400EC88B1 /* maccheck.json in Resources */ = {isa = PBXBuildFile; fileRef = D0D71385227ADE9400EC88B1 /* maccheck.json */; }; + D0D7520924C03CD60037D73A /* VideoEditorThumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D7520824C03CD60037D73A /* VideoEditorThumbs.swift */; }; + D0D7520B24C04FD60037D73A /* VideoEditorScrubbler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D7520A24C04FD60037D73A /* VideoEditorScrubbler.swift */; }; + D0D752C822F8A76100E2CB74 /* Geocoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D752C722F8A76100E2CB74 /* Geocoding.swift */; }; + D0D81AEE243E2D6F00CB9D20 /* ChatListFilterFolderIconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D81AED243E2D6F00CB9D20 /* ChatListFilterFolderIconController.swift */; }; + D0D81AF8243F69AD00CB9D20 /* PollTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D81AF7243F69AD00CB9D20 /* PollTimerView.swift */; }; + D0DD91FE246A8A380039D83D /* PeerMediaGifsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DD91FD246A8A380039D83D /* PeerMediaGifsController.swift */; }; + D0DDECEA2322FA0200E1B359 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F13EE182100B6DA00562E53 /* Contacts.framework */; }; + D0E52B3222FD66C4000C0306 /* MessageTimecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E52B3122FD66C4000C0306 /* MessageTimecode.swift */; }; + D0FCA7622434867400B72F18 /* PeerInfoHeadItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FCA7612434867400B72F18 /* PeerInfoHeadItem.swift */; }; + D0FFC4AC23E184BA0044D305 /* ChatListFilterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FFC4AB23E184BA0044D305 /* ChatListFilterController.swift */; }; + D0FFCEBE215A7CB700995AFE /* emoji14.txt in Resources */ = {isa = PBXBuildFile; fileRef = D0FFCEBD215A7CB700995AFE /* emoji14.txt */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + A71DC8302386C83E000EEDE2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D098C70D1D7E175A007784E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C232EA901E1D07E700C4D38C; + remoteInfo = TelegramShare; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ C20D5AB61DAA94500042616A /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; @@ -529,23 +922,24 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - C276AFC71F74F2CF00DEDD8E /* Sparkle.framework in Copy Files */, - C276AFC81F74F2CF00DEDD8E /* HockeySDK.framework in Copy Files */, - C224F2961E43CDDD002FF0B2 /* PostboxMac.framework in Copy Files */, - C224F2951E43CDD8002FF0B2 /* MtProtoKitMac.framework in Copy Files */, + A7D28212236C3D3E0000A9BF /* SyncCore.framework in Copy Files */, + A7D2820F236C3C3B0000A9BF /* Postbox.framework in Copy Files */, + A7D2820E236C3C330000A9BF /* SwiftSignalKit.framework in Copy Files */, + A7D2820D236C3C2F0000A9BF /* TelegramCore.framework in Copy Files */, + A7D2820C236C3C2A0000A9BF /* MtProtoKit.framework in Copy Files */, + A7D08F4A236AC9BB002DC240 /* Sparkle.framework in Copy Files */, C224F2941E43CDC4002FF0B2 /* TGUIKit.framework in Copy Files */, - C224F2931E43CDBE002FF0B2 /* SwiftSignalKitMac.framework in Copy Files */, - C224F2921E43CD93002FF0B2 /* TelegramCoreMac.framework in Copy Files */, ); name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; C232EA661E1D041B00C4D38C /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 13; files = ( + A71DC8322386C845000EEDE2 /* TelegramShare.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -566,14 +960,405 @@ dstPath = fonts; dstSubfolderSpec = 7; files = ( - C23D0D7C1F1A649900AF5151 /* SFCompactRounded-Semibold.otf in Fonts */, ); name = Fonts; runOnlyForDeploymentPostprocessing = 0; }; + C2786FF51FEC128F001FB044 /* Palettes */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = palettes; + dstSubfolderSpec = 7; + files = ( + ); + name = Palettes; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9F0367EF227208E000456348 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; + 9F0367F12272108800456348 /* ProxyQRCodeRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyQRCodeRowItem.swift; sourceTree = ""; }; + 9F0367F62273260A00456348 /* UndoTooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UndoTooltipController.swift; sourceTree = ""; }; + 9F0368002277091800456348 /* LAnimationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LAnimationButton.swift; sourceTree = ""; }; + 9F03680222771A9600456348 /* anim_unarchive.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_unarchive.json; sourceTree = ""; }; + 9F03680322771A9600456348 /* anim_ungroup.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_ungroup.json; sourceTree = ""; }; + 9F03680422771A9600456348 /* archiveAvatar.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = archiveAvatar.json; sourceTree = ""; }; + 9F03680522771A9600456348 /* anim_read.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_read.json; sourceTree = ""; }; + 9F03680622771A9600456348 /* anim_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_delete.json; sourceTree = ""; }; + 9F03680722771A9600456348 /* anim_unread.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_unread.json; sourceTree = ""; }; + 9F03680822771A9600456348 /* anim_hide.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_hide.json; sourceTree = ""; }; + 9F03680922771A9600456348 /* anim_mute.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_mute.json; sourceTree = ""; }; + 9F03680A22771A9600456348 /* anim_unpin.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_unpin.json; sourceTree = ""; }; + 9F03680B22771A9600456348 /* anim_group.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_group.json; sourceTree = ""; }; + 9F03680C22771A9600456348 /* anim_archive.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_archive.json; sourceTree = ""; }; + 9F03680D22771A9700456348 /* anim_unmute.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_unmute.json; sourceTree = ""; }; + 9F03680E22771A9700456348 /* anim_pin.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_pin.json; sourceTree = ""; }; + 9F0AE6862191D29D00A8B53A /* ContextSearchMessageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextSearchMessageItem.swift; sourceTree = ""; }; + 9F0AE6B42199904400A8B53A /* sound_a.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = sound_a.caf; sourceTree = ""; }; + 9F0AE6BB2199BBB900A8B53A /* MediaPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerView.swift; sourceTree = ""; }; + 9F0AE6BD2199BEBE00A8B53A /* SVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVideoController.swift; sourceTree = ""; }; + 9F0AE6BF2199CDB500A8B53A /* SVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVideoView.swift; sourceTree = ""; }; + 9F0B8F161FFB7F1A00073D3F /* AccentColorRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentColorRowItem.swift; sourceTree = ""; }; + 9F0BCD9C2087BA81001D8D8A /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F0BCD9E2087BABC001D8D8A /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F0E6F77203ED1380086699C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 9F0E6F79203EFE870086699C /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + 9F0F8E81226DCD1C00A97F6A /* OpmizeDatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpmizeDatabaseView.swift; sourceTree = ""; }; + 9F10CE8720610127002DD61A /* PassportController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportController.swift; sourceTree = ""; }; + 9F10CE8920611536002DD61A /* PassportHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportHeaderItem.swift; sourceTree = ""; }; + 9F10CE8D20617C36002DD61A /* PassportInsertPasswordItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportInsertPasswordItem.swift; sourceTree = ""; }; + 9F10CE912061BE19002DD61A /* InputDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDataController.swift; sourceTree = ""; }; + 9F10CE932061C8C8002DD61A /* InputDataControllerEntries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDataControllerEntries.swift; sourceTree = ""; }; + 9F10CE952061C98E002DD61A /* InputDataRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDataRowItem.swift; sourceTree = ""; }; + 9F10CE9720626B1B002DD61A /* PassportDocumentRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportDocumentRowItem.swift; sourceTree = ""; }; + 9F10CE99206284F8002DD61A /* InputDataDateRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDataDateRowItem.swift; sourceTree = ""; }; + 9F127E14210B1F540080D709 /* PeerMediaVoiceRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaVoiceRowItem.swift; sourceTree = ""; }; + 9F12D342209251CF0072928B /* EditAccountInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountInfoItem.swift; sourceTree = ""; }; + 9F13CCE22006719400ECF301 /* ru */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 9F13CCE3200671AF00ECF301 /* uk */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; + 9F13EE162100B05300562E53 /* VCardContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCardContactController.swift; sourceTree = ""; }; + 9F13EE182100B6DA00562E53 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; + 9F13EE1A2100BFCA00562E53 /* VCardHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCardHeaderItem.swift; sourceTree = ""; }; + 9F147F6E223014EB00D71BD1 /* PasscodeControllers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeControllers.swift; sourceTree = ""; }; + 9F147F7B2231543800D71BD1 /* PeerUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerUtils.swift; sourceTree = ""; }; + 9F14CBF22007DEB300F22DA9 /* ChatWallpaperModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWallpaperModalController.swift; sourceTree = ""; }; + 9F14CBF42007DFD400F22DA9 /* Wallpapers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallpapers.swift; sourceTree = ""; }; + 9F153D1321F0C7F800B95D82 /* WallpaperPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPreviewController.swift; sourceTree = ""; }; + 9F153D1521F3662700B95D82 /* StoredMessageFromSearchPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredMessageFromSearchPeer.swift; sourceTree = ""; }; + 9F1668B72007E3BC00DD39FB /* ThemeGridControllerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeGridControllerItem.swift; sourceTree = ""; }; + 9F1668C52008A97000DD39FB /* builtin-wallpaper-0.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "builtin-wallpaper-0.jpg"; sourceTree = ""; }; + 9F1668C72008F30900DD39FB /* ChatBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBackgroundView.swift; sourceTree = ""; }; + 9F17E5B8212F173900C25A65 /* AutoNightViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoNightViewController.swift; sourceTree = ""; }; + 9F17E5BA212F191F00C25A65 /* AutoNightThemePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoNightThemePreferences.swift; sourceTree = ""; }; + 9F18908C2237B5A400665EF5 /* InputURLFormatterModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputURLFormatterModalController.swift; sourceTree = ""; }; + 9F1890922238F3DC00665EF5 /* DownloadedFilesPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadedFilesPaths.swift; sourceTree = ""; }; + 9F18DD91206D8FFD00A2AAD0 /* SecureIdVerificationDocumentsContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureIdVerificationDocumentsContext.swift; sourceTree = ""; }; + 9F18DD92206D8FFD00A2AAD0 /* SecureIdVerificationDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureIdVerificationDocument.swift; sourceTree = ""; }; + 9F1962D72101458C00FFF048 /* VCardLocationRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCardLocationRowItem.swift; sourceTree = ""; }; + 9F1AE5E320B6D7AA002A9D8D /* LocationPlaceSuggestionRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPlaceSuggestionRowItem.swift; sourceTree = ""; }; + 9F1AE5E520B70328002A9D8D /* LocationSendCurrentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSendCurrentItem.swift; sourceTree = ""; }; + 9F1BABAD21E5ECE70075C03E /* ChatUndoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUndoManager.swift; sourceTree = ""; }; + 9F1BABAF21E60DCC0075C03E /* UndoOverlayHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UndoOverlayHeaderView.swift; sourceTree = ""; }; + 9F1BC1A8223FDE6D00F21815 /* InputSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSources.swift; sourceTree = ""; }; + 9F1C279221D38A96003CD033 /* InstantPageScrollableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageScrollableItem.swift; sourceTree = ""; }; + 9F21A7CE21C1552D0037784F /* InstantPageTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageTheme.swift; sourceTree = ""; }; + 9F21A7D221C167000037784F /* InstantPageImageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageImageItem.swift; sourceTree = ""; }; + 9F21A7D421C16CB90037784F /* InstantPagePeerReferenceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPagePeerReferenceItem.swift; sourceTree = ""; }; + 9F21A7DB21C290E00037784F /* InstantPageTableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageTableItem.swift; sourceTree = ""; }; + 9F21F65420B5A72800332C85 /* LocationModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationModalController.swift; sourceTree = ""; }; + 9F21F65C20B5A9C900332C85 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; + 9F262D5E21BFD5BC006817CD /* LocalizationPreviewModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationPreviewModalController.swift; sourceTree = ""; }; + 9F291C9F2264E57F00C66267 /* BuildConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BuildConfig.m; sourceTree = ""; }; + 9F291CA02264E57F00C66267 /* BuildConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuildConfig.h; sourceTree = ""; }; + 9F354E9B2270630A006F1D42 /* HapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticEngine.swift; sourceTree = ""; }; + 9F354E9D2270757E006F1D42 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F354E9F227076A6006F1D42 /* libLottie.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libLottie.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F354EA1227076EF006F1D42 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F354EA3227077BD006F1D42 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F3D5F6222044D8700CB0CAA /* AppUpdateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateViewController.swift; sourceTree = ""; }; + 9F3D5F8522085B2000CB0CAA /* UpdaterNotifySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterNotifySettings.swift; sourceTree = ""; }; + 9F3EAB3A20A5A1EC003FE7E3 /* NetworkUsageStatsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkUsageStatsController.swift; sourceTree = ""; }; + 9F3EAB3D20A5ED2F003FE7E3 /* genann.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = genann.h; sourceTree = ""; }; + 9F3EAB3E20A5ED2F003FE7E3 /* ocr.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ocr.mm; sourceTree = ""; }; + 9F3EAB3F20A5ED2F003FE7E3 /* ocr_nn.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = ocr_nn.bin; sourceTree = ""; }; + 9F3EAB4020A5ED2F003FE7E3 /* fast-edge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "fast-edge.h"; sourceTree = ""; }; + 9F3EAB4120A5ED2F003FE7E3 /* ocr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocr.h; sourceTree = ""; }; + 9F3EAB4220A5ED2F003FE7E3 /* genann.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = genann.c; sourceTree = ""; }; + 9F3EAB4320A5ED2F003FE7E3 /* fast-edge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "fast-edge.cpp"; sourceTree = ""; }; + 9F3EAB4920A5F90B003FE7E3 /* TGPassportMRZ.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGPassportMRZ.h; sourceTree = ""; }; + 9F3EAB4A20A5F90B003FE7E3 /* TGPassportMRZ.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGPassportMRZ.m; sourceTree = ""; }; + 9F4EC947218B459A002B3C56 /* RenderedTotalUnreadCount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RenderedTotalUnreadCount.swift; sourceTree = ""; }; + 9F4EEF7D21D3C3E3002C3B33 /* InstantPageDetailsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsItem.swift; sourceTree = ""; }; + 9F4EEF7F21D3C76E002C3B33 /* InstantPageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageContentView.swift; sourceTree = ""; }; + 9F4EEF8121D4F584002C3B33 /* ImageTransparency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransparency.swift; sourceTree = ""; }; + 9F4EEF8321D4F59C002C3B33 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 9F4EEF8521D4FA68002C3B33 /* InstantPageArticleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageArticleItem.swift; sourceTree = ""; }; + 9F4EEF8721D515C5002C3B33 /* InstantPageStoredState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageStoredState.swift; sourceTree = ""; }; + 9F52E74E2074C12B0048791C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 9F52E7502074C16D0048791C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MainMenu.strings; sourceTree = ""; }; + 9F52E7522074C1710048791C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/MainMenu.strings; sourceTree = ""; }; + 9F52F5192130286E006FC0B5 /* LocationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRequest.swift; sourceTree = ""; }; + 9F580BE420A0AA7B00F6D56C /* ChatRecorderOverlayWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRecorderOverlayWindow.swift; sourceTree = ""; }; + 9F62AE7D202D85B7007FB557 /* FetchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchManager.swift; sourceTree = ""; }; + 9F62AE80202D85E7007FB557 /* FetchManagerLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchManagerLocation.swift; sourceTree = ""; }; + 9F62AE82202D8759007FB557 /* FetchMediaUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchMediaUtils.swift; sourceTree = ""; }; + 9F6314E021CAA0AB009FD379 /* NewPollController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPollController.swift; sourceTree = ""; }; + 9F63152521D236CB009FD379 /* ForgotPasswordController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgotPasswordController.swift; sourceTree = ""; }; + 9F63152821D26892009FD379 /* CancelResetAccountController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelResetAccountController.swift; sourceTree = ""; }; + 9F6B54C721369B4000748FC1 /* GalleryModernControls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryModernControls.swift; sourceTree = ""; }; + 9F6FF3212248E298004364FE /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; + 9F72973320B878B00067F815 /* MapResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapResources.swift; sourceTree = ""; }; + 9F72973F20BD9C6A0067F815 /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; + 9F72974720C597800067F815 /* TermsModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsModalController.swift; sourceTree = ""; }; + 9F77B3972211979B003B65B8 /* AutoplayPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoplayPreferences.swift; sourceTree = ""; }; + 9F77B3A5221C1DAC003B65B8 /* LogoutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutViewController.swift; sourceTree = ""; }; + 9F7943AF20854E2F00FEDB81 /* ProxyListRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyListRowItem.swift; sourceTree = ""; }; + 9F7943B120855DC200FEDB81 /* ProxyListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyListController.swift; sourceTree = ""; }; + 9F7B5FCA22003DC70087D020 /* WallpaperColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperColorPicker.swift; sourceTree = ""; }; + 9F7B5FCC220099220087D020 /* WallpaperPatternPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperPatternPreview.swift; sourceTree = ""; }; + 9F7B74022227F492006610E4 /* ManageSharedAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageSharedAccountInfo.swift; sourceTree = ""; }; + 9F7B74042227F4F2006610E4 /* SharedAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccountInfo.swift; sourceTree = ""; }; + 9F7B740E2229618E006610E4 /* SharedWakeupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedWakeupManager.swift; sourceTree = ""; }; + 9F7B741022296FD7006610E4 /* SharedNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedNotificationManager.swift; sourceTree = ""; }; + 9F7D421E22203DB1007B68BB /* ChannelStatisticsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStatisticsController.swift; sourceTree = ""; }; + 9F7D422922240403007B68BB /* AccountContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountContext.swift; sourceTree = ""; }; + 9F7D422B222415C9007B68BB /* SharedAccountContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccountContext.swift; sourceTree = ""; }; + 9F8DF3C7209228B000AED104 /* EditAccountInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountInfoController.swift; sourceTree = ""; }; + 9F8FDED6205A7AE0001A7A77 /* hwcontext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hwcontext.h; sourceTree = ""; }; + 9F8FDED7205A7AE0001A7A77 /* time.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time.h; sourceTree = ""; }; + 9F8FDED8205A7AE0001A7A77 /* hwcontext_cuda.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hwcontext_cuda.h; sourceTree = ""; }; + 9F8FDED9205A7AE0001A7A77 /* intfloat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = intfloat.h; sourceTree = ""; }; + 9F8FDEDA205A7AE0001A7A77 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = ""; }; + 9F8FDEDB205A7AE0001A7A77 /* fifo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fifo.h; sourceTree = ""; }; + 9F8FDEDC205A7AE0001A7A77 /* blowfish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = blowfish.h; sourceTree = ""; }; + 9F8FDEDD205A7AE0001A7A77 /* replaygain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = replaygain.h; sourceTree = ""; }; + 9F8FDEDE205A7AE0001A7A77 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 9F8FDEDF205A7AE0001A7A77 /* murmur3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = murmur3.h; sourceTree = ""; }; + 9F8FDEE0205A7AE0001A7A77 /* stereo3d.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stereo3d.h; sourceTree = ""; }; + 9F8FDEE1205A7AE0001A7A77 /* samplefmt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = samplefmt.h; sourceTree = ""; }; + 9F8FDEE2205A7AE0001A7A77 /* pixdesc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixdesc.h; sourceTree = ""; }; + 9F8FDEE3205A7AE0001A7A77 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; + 9F8FDEE4205A7AE0001A7A77 /* rational.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rational.h; sourceTree = ""; }; + 9F8FDEE5205A7AE0001A7A77 /* sha.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha.h; sourceTree = ""; }; + 9F8FDEE6205A7AE0001A7A77 /* motion_vector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = motion_vector.h; sourceTree = ""; }; + 9F8FDEE7205A7AE0001A7A77 /* avconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avconfig.h; sourceTree = ""; }; + 9F8FDEE8205A7AE0001A7A77 /* lfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lfg.h; sourceTree = ""; }; + 9F8FDEE9205A7AE0001A7A77 /* avutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avutil.h; sourceTree = ""; }; + 9F8FDEEA205A7AE0001A7A77 /* xtea.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xtea.h; sourceTree = ""; }; + 9F8FDEEB205A7AE0001A7A77 /* crc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crc.h; sourceTree = ""; }; + 9F8FDEEC205A7AE0001A7A77 /* hwcontext_vdpau.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hwcontext_vdpau.h; sourceTree = ""; }; + 9F8FDEED205A7AE0001A7A77 /* frame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = frame.h; sourceTree = ""; }; + 9F8FDEEE205A7AE0001A7A77 /* file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file.h; sourceTree = ""; }; + 9F8FDEEF205A7AE0001A7A77 /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = ""; }; + 9F8FDEF0205A7AE0001A7A77 /* cast5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cast5.h; sourceTree = ""; }; + 9F8FDEF1205A7AE0001A7A77 /* hwcontext_vaapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hwcontext_vaapi.h; sourceTree = ""; }; + 9F8FDEF2205A7AE0001A7A77 /* ffversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ffversion.h; sourceTree = ""; }; + 9F8FDEF3205A7AE0001A7A77 /* audio_fifo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = audio_fifo.h; sourceTree = ""; }; + 9F8FDEF4205A7AE0001A7A77 /* tree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tree.h; sourceTree = ""; }; + 9F8FDEF5205A7AE0001A7A77 /* threadmessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = threadmessage.h; sourceTree = ""; }; + 9F8FDEF6205A7AE0001A7A77 /* attributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = attributes.h; sourceTree = ""; }; + 9F8FDEF7205A7AE0001A7A77 /* adler32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = adler32.h; sourceTree = ""; }; + 9F8FDEF8205A7AE0001A7A77 /* timecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timecode.h; sourceTree = ""; }; + 9F8FDEF9205A7AE0001A7A77 /* sha512.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha512.h; sourceTree = ""; }; + 9F8FDEFA205A7AE0001A7A77 /* hwcontext_dxva2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hwcontext_dxva2.h; sourceTree = ""; }; + 9F8FDEFB205A7AE0001A7A77 /* display.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = display.h; sourceTree = ""; }; + 9F8FDEFC205A7AE0001A7A77 /* buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = ""; }; + 9F8FDEFD205A7AE0001A7A77 /* camellia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = camellia.h; sourceTree = ""; }; + 9F8FDEFE205A7AE0001A7A77 /* pixelutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixelutils.h; sourceTree = ""; }; + 9F8FDEFF205A7AE0001A7A77 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; + 9F8FDF00205A7AE0001A7A77 /* hmac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hmac.h; sourceTree = ""; }; + 9F8FDF01205A7AE0001A7A77 /* eval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eval.h; sourceTree = ""; }; + 9F8FDF02205A7AE0001A7A77 /* dict.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dict.h; sourceTree = ""; }; + 9F8FDF03205A7AE0001A7A77 /* random_seed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = random_seed.h; sourceTree = ""; }; + 9F8FDF04205A7AE0001A7A77 /* opt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opt.h; sourceTree = ""; }; + 9F8FDF05205A7AE0001A7A77 /* mastering_display_metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mastering_display_metadata.h; sourceTree = ""; }; + 9F8FDF06205A7AE0001A7A77 /* log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log.h; sourceTree = ""; }; + 9F8FDF07205A7AE0001A7A77 /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = ""; }; + 9F8FDF08205A7AE0001A7A77 /* macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macros.h; sourceTree = ""; }; + 9F8FDF09205A7AE0001A7A77 /* bswap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bswap.h; sourceTree = ""; }; + 9F8FDF0A205A7AE0001A7A77 /* rc4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rc4.h; sourceTree = ""; }; + 9F8FDF0B205A7AE0001A7A77 /* tea.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tea.h; sourceTree = ""; }; + 9F8FDF0C205A7AE0001A7A77 /* cpu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cpu.h; sourceTree = ""; }; + 9F8FDF0D205A7AE0001A7A77 /* des.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = des.h; sourceTree = ""; }; + 9F8FDF0E205A7AE0001A7A77 /* channel_layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = channel_layout.h; sourceTree = ""; }; + 9F8FDF0F205A7AE0001A7A77 /* twofish.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = twofish.h; sourceTree = ""; }; + 9F8FDF10205A7AE0001A7A77 /* imgutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = imgutils.h; sourceTree = ""; }; + 9F8FDF11205A7AE0001A7A77 /* mem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mem.h; sourceTree = ""; }; + 9F8FDF12205A7AE0001A7A77 /* parseutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parseutils.h; sourceTree = ""; }; + 9F8FDF13205A7AE0001A7A77 /* ripemd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ripemd.h; sourceTree = ""; }; + 9F8FDF14205A7AE0001A7A77 /* bprint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bprint.h; sourceTree = ""; }; + 9F8FDF15205A7AE0001A7A77 /* pixfmt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixfmt.h; sourceTree = ""; }; + 9F8FDF16205A7AE0001A7A77 /* aes_ctr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes_ctr.h; sourceTree = ""; }; + 9F8FDF17205A7AE0001A7A77 /* timestamp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timestamp.h; sourceTree = ""; }; + 9F8FDF18205A7AE0001A7A77 /* downmix_info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = downmix_info.h; sourceTree = ""; }; + 9F8FDF19205A7AE0001A7A77 /* avassert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avassert.h; sourceTree = ""; }; + 9F8FDF1A205A7AE0001A7A77 /* hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hash.h; sourceTree = ""; }; + 9F8FDF1B205A7AE0001A7A77 /* mathematics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mathematics.h; sourceTree = ""; }; + 9F8FDF1C205A7AE0001A7A77 /* intreadwrite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = intreadwrite.h; sourceTree = ""; }; + 9F8FDF1D205A7AE0001A7A77 /* avstring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avstring.h; sourceTree = ""; }; + 9F8FDF1F205A7AE0001A7A77 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 9F8FDF20205A7AE0001A7A77 /* avio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avio.h; sourceTree = ""; }; + 9F8FDF21205A7AE0001A7A77 /* avformat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avformat.h; sourceTree = ""; }; + 9F8FDF23205A7AE0001A7A77 /* avcodec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avcodec.h; sourceTree = ""; }; + 9F8FDF24205A7AE0001A7A77 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 9F8FDF25205A7AE0001A7A77 /* vdpau.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vdpau.h; sourceTree = ""; }; + 9F8FDF26205A7AE0001A7A77 /* qsv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qsv.h; sourceTree = ""; }; + 9F8FDF27205A7AE0001A7A77 /* vaapi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vaapi.h; sourceTree = ""; }; + 9F8FDF28205A7AE0001A7A77 /* videotoolbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = videotoolbox.h; sourceTree = ""; }; + 9F8FDF29205A7AE0001A7A77 /* xvmc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xvmc.h; sourceTree = ""; }; + 9F8FDF2A205A7AE0001A7A77 /* d3d11va.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = d3d11va.h; sourceTree = ""; }; + 9F8FDF2B205A7AE0001A7A77 /* avfft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avfft.h; sourceTree = ""; }; + 9F8FDF2C205A7AE0001A7A77 /* vda.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vda.h; sourceTree = ""; }; + 9F8FDF2D205A7AE0001A7A77 /* jni.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jni.h; sourceTree = ""; }; + 9F8FDF2E205A7AE0001A7A77 /* dirac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dirac.h; sourceTree = ""; }; + 9F8FDF2F205A7AE0001A7A77 /* avdct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = avdct.h; sourceTree = ""; }; + 9F8FDF30205A7AE0001A7A77 /* vorbis_parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vorbis_parser.h; sourceTree = ""; }; + 9F8FDF31205A7AE0001A7A77 /* dxva2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dxva2.h; sourceTree = ""; }; + 9F8FDF32205A7AE0001A7A77 /* dv_profile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dv_profile.h; sourceTree = ""; }; + 9F8FDF34205A7AE0001A7A77 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 9F8FDF35205A7AE0001A7A77 /* swresample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = swresample.h; sourceTree = ""; }; + 9F8FDF37205A7AE7001A7A77 /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavutil.a; sourceTree = ""; }; + 9F8FDF38205A7AE7001A7A77 /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavcodec.a; sourceTree = ""; }; + 9F8FDF39205A7AE7001A7A77 /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libavformat.a; sourceTree = ""; }; + 9F8FDF3A205A7AE7001A7A77 /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libswresample.a; sourceTree = ""; }; + 9F9206EF20727AF30054E581 /* ChangePhoneNumberContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePhoneNumberContainerView.swift; sourceTree = ""; }; + 9F9483B0202AF816006E873D /* CrashHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashHandler.swift; sourceTree = ""; }; + 9F9B5EB022573E8A00728CDC /* FFMpegAVFormatContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVFormatContext.h; sourceTree = ""; }; + 9F9B5EB122573E8A00728CDC /* FFMpegAVFormatContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegAVFormatContext.m; sourceTree = ""; }; + 9F9B5EB322573FA700728CDC /* FFMpegAVIOContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVIOContext.h; sourceTree = ""; }; + 9F9B5EB422573FA700728CDC /* FFMpegAVIOContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegAVIOContext.m; sourceTree = ""; }; + 9F9B5EB62257402200728CDC /* FFMpegAVCodec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVCodec.h; sourceTree = ""; }; + 9F9B5EB72257402200728CDC /* FFMpegAVCodec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegAVCodec.m; sourceTree = ""; }; + 9F9B5EB92257408200728CDC /* FFMpegAVCodecContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVCodecContext.h; sourceTree = ""; }; + 9F9B5EBA2257408200728CDC /* FFMpegAVCodecContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegAVCodecContext.m; sourceTree = ""; }; + 9F9B5EBC225740F400728CDC /* FFMpegAVSampleFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVSampleFormat.h; sourceTree = ""; }; + 9F9B5EBD2257426500728CDC /* FFMpegAVFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegAVFrame.h; sourceTree = ""; }; + 9F9B5EBE2257426500728CDC /* FFMpegAVFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegAVFrame.m; sourceTree = ""; }; + 9F9B5EC02257443600728CDC /* FFMpegPacket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegPacket.h; sourceTree = ""; }; + 9F9B5EC12257443600728CDC /* FFMpegPacket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegPacket.m; sourceTree = ""; }; + 9FA0E52820519E33001E5649 /* MP4Atom.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MP4Atom.h; sourceTree = ""; }; + 9FA0E52920519E33001E5649 /* MP4Atom.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MP4Atom.m; sourceTree = ""; }; + 9FA0E52B20519FCC001E5649 /* LiveUploadingHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LiveUploadingHelper.h; sourceTree = ""; }; + 9FA0E52C20519FCC001E5649 /* LiveUploadingHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LiveUploadingHelper.m; sourceTree = ""; }; + 9FA0E5332051A41A001E5649 /* PreUploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreUploadManager.swift; sourceTree = ""; }; + 9FA0E5392052EDFE001E5649 /* HackUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HackUtils.m; sourceTree = ""; }; + 9FA0E53A2052EDFF001E5649 /* HackUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HackUtils.h; sourceTree = ""; }; + 9FA0E53C205693DA001E5649 /* WebSessionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSessionsController.swift; sourceTree = ""; }; + 9FA0E53E2056E159001E5649 /* WebAuthorizationRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthorizationRowItem.swift; sourceTree = ""; }; + 9FB14FBD2098896200688EF9 /* EDSunriseSet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EDSunriseSet.h; sourceTree = ""; }; + 9FB14FBE209889A500688EF9 /* EDSunriseSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EDSunriseSet.m; sourceTree = ""; }; + 9FB7CB6C221EB22700888EA9 /* CallSettingsModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettingsModalController.swift; sourceTree = ""; }; + 9FBE0EE0201FBEFC0060FD1C /* DownloadSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadSettingsViewController.swift; sourceTree = ""; }; + 9FC4DA9521DD0B35003E2A62 /* PeerChannelMemberCategoriesContextsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerChannelMemberCategoriesContextsManager.swift; sourceTree = ""; }; + 9FC4DA9721DD0B86003E2A62 /* ChannelMemberCategoryListContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMemberCategoryListContext.swift; sourceTree = ""; }; + 9FC4DA9921DD0BE6003E2A62 /* CachedChannelAdmins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedChannelAdmins.swift; sourceTree = ""; }; + 9FC4DA9B21DD187C003E2A62 /* SearchPeerMembers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPeerMembers.swift; sourceTree = ""; }; + 9FC4DA9D21DD1C6C003E2A62 /* ImageCompression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompression.swift; sourceTree = ""; }; + 9FC4DAA021DD1D6C003E2A62 /* jpeglib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jpeglib.h; sourceTree = ""; }; + 9FC4DAA121DD1D6C003E2A62 /* jerror.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jerror.h; sourceTree = ""; }; + 9FC4DAA221DD1D6C003E2A62 /* libturbojpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libturbojpeg.a; sourceTree = ""; }; + 9FC4DAA321DD1D6C003E2A62 /* turbojpeg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = turbojpeg.h; sourceTree = ""; }; + 9FC4DAA421DD1D6C003E2A62 /* jconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jconfig.h; sourceTree = ""; }; + 9FC4DAA521DD1D6C003E2A62 /* jmorecfg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jmorecfg.h; sourceTree = ""; }; + 9FC4DAA721DE0626003E2A62 /* ChannelPermissionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelPermissionsController.swift; sourceTree = ""; }; + 9FC8AD992062A5610094F7B4 /* InputDataDataSelectorRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDataDataSelectorRowItem.swift; sourceTree = ""; }; + 9FC8AD9B2062AA630094F7B4 /* ValuesSelectorModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValuesSelectorModalController.swift; sourceTree = ""; }; + 9FC8AD9F2062D5E70094F7B4 /* PassportAcceptRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportAcceptRowItem.swift; sourceTree = ""; }; + 9FC8ADA12062E2DF0094F7B4 /* PassportNewPhoneNumberRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportNewPhoneNumberRowItem.swift; sourceTree = ""; }; + 9FC8ADA32063B6450094F7B4 /* PassportTwoStepVerificationIntroItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportTwoStepVerificationIntroItem.swift; sourceTree = ""; }; + 9FC8ADA5206925F60094F7B4 /* PassportWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportWindowController.swift; sourceTree = ""; }; + 9FC8ADA7206A77E00094F7B4 /* countries */ = {isa = PBXFileReference; lastKnownFileType = text; path = countries; sourceTree = ""; }; + 9FDA713120E6456A001ED8ED /* ExternalMusicAlbumArtResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalMusicAlbumArtResources.swift; sourceTree = ""; }; + 9FDA713320E65D49001ED8ED /* PeerMediaPlayerAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaPlayerAnimationView.swift; sourceTree = ""; }; + 9FDA713A20EA9532001ED8ED /* ReadArticlesListPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadArticlesListPreferences.swift; sourceTree = ""; }; + 9FDA713E20EE2D49001ED8ED /* PopularPeersRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularPeersRowItem.swift; sourceTree = ""; }; + 9FDD78D021C8F0CC00F1B4EF /* ChatPollItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPollItem.swift; sourceTree = ""; }; + 9FDE0A8E21AD41C2001546D7 /* emoji1014-1.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "emoji1014-1.txt"; sourceTree = ""; }; + 9FF1DEA5225B699D009512C9 /* SearchUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchUtils.swift; sourceTree = ""; }; + 9FF32C7B21B7DF4800BF58B6 /* StickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerSettings.swift; sourceTree = ""; }; + 9FF5A1CA2232A2FF00BC1359 /* UpgradedAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradedAccount.swift; sourceTree = ""; }; + 9FFAE4DC205A8C80000C028E /* FFMpegMediaPassthroughVideoFrameDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaPassthroughVideoFrameDecoder.swift; sourceTree = ""; }; + 9FFAE4DE205A8C81000C028E /* MediaFrameSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaFrameSource.swift; sourceTree = ""; }; + 9FFAE4E0205A8C82000C028E /* MediaTrackFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackFrame.swift; sourceTree = ""; }; + 9FFAE4E1205A8C83000C028E /* MediaTrackFrameBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackFrameBuffer.swift; sourceTree = ""; }; + 9FFAE4E2205A8C83000C028E /* MediaPlayerAudioRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayerAudioRenderer.swift; sourceTree = ""; }; + 9FFAE4E3205A8C84000C028E /* FFMpegMediaFrameSourceContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSourceContext.swift; sourceTree = ""; }; + 9FFAE4E4205A8C84000C028E /* MediaTrackFrameDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackFrameDecoder.swift; sourceTree = ""; }; + 9FFAE4E6205A8C85000C028E /* FFMpegMediaFrameSourceContextHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSourceContextHelpers.swift; sourceTree = ""; }; + 9FFAE4E7205A8C86000C028E /* FFMpegMediaFrameSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSource.swift; sourceTree = ""; }; + 9FFAE4E8205A8C86000C028E /* FFMpegAudioFrameDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegAudioFrameDecoder.swift; sourceTree = ""; }; + 9FFAE4E9205A8C87000C028E /* MediaPlaybackData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlaybackData.swift; sourceTree = ""; }; + 9FFAE4EA205A8C87000C028E /* MediaPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayer.swift; sourceTree = ""; }; + 9FFAE4EB205A8C88000C028E /* MediaTrackDecodableFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTrackDecodableFrame.swift; sourceTree = ""; }; + 9FFAE4EC205A8C88000C028E /* FFMpegMediaVideoFrameDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaVideoFrameDecoder.swift; sourceTree = ""; }; + 9FFAE4FE205A9041000C028E /* FFMpegSwResample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FFMpegSwResample.h; sourceTree = ""; }; + 9FFAE4FF205A9042000C028E /* FFMpegSwResample.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FFMpegSwResample.m; sourceTree = ""; }; + 9FFAE501205A9153000C028E /* ManagedAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAudioSession.swift; sourceTree = ""; }; + 9FFAE503205A916B000C028E /* VideoPlayerProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerProxy.swift; sourceTree = ""; }; + 9FFAE505205A928C000C028E /* RingByteBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RingByteBuffer.swift; sourceTree = ""; }; + 9FFAE507205A92B9000C028E /* RingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RingBuffer.h; sourceTree = ""; }; + 9FFAE508205A92B9000C028E /* RingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RingBuffer.m; sourceTree = ""; }; + 9FFAE50E205AB4A3000C028E /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + 9FFAE510205AB4AB000C028E /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; + 9FFAE513205AB50C000C028E /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + A7029EF7240E3A5400A89ABD /* ChatListFiltersHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFiltersHeaderItem.swift; sourceTree = ""; }; + A7029EF9240E3CCF00A89ABD /* folder.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = folder.tgs; sourceTree = ""; }; + A71DC82A23858311000EEDE2 /* CoreSpotlight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSpotlight.framework; path = System/Library/Frameworks/CoreSpotlight.framework; sourceTree = SDKROOT; }; + A71DC82C23858356000EEDE2 /* Spotlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spotlight.swift; sourceTree = ""; }; + A71DC82E2386AADF000EEDE2 /* Signature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signature.swift; sourceTree = ""; }; + A72D7AE823C471A7005BAC59 /* PollResultController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultController.swift; sourceTree = ""; }; + A7377E1A23ACC79100AD3ADD /* ChatNavigateFailed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatNavigateFailed.swift; sourceTree = ""; }; + A7377E2023B5DCBA00AD3ADD /* SVGKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SVGKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7377E5B23B5E99500AD3ADD /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; }; + A7393D342407CAE100CE44CA /* ChatMediaDice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMediaDice.swift; sourceTree = ""; }; + A7393D362407CD7A00CE44CA /* ChatDiceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDiceContentView.swift; sourceTree = ""; }; + A7393D382407D0F100CE44CA /* GraphCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GraphCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7393D412407FF0F00CE44CA /* dice_idle.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = dice_idle.tgs; sourceTree = ""; }; + A7393D432408F84300CE44CA /* DiceCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiceCache.swift; sourceTree = ""; }; + A7393D452409044C00CE44CA /* ChannelOverviewStatsRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelOverviewStatsRowItem.swift; sourceTree = ""; }; + A742CDCC240FB32F00C6B69B /* ChatListFilterRecommendedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterRecommendedItem.swift; sourceTree = ""; }; + A742CE44241A517800C6B69B /* ChannelRecentPostRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRecentPostRowItem.swift; sourceTree = ""; }; + A74EB06F237961A1005F55AE /* AppCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppCenter.framework; path = submodules/AppCenter/AppCenter.framework; sourceTree = ""; }; + A74EB070237961A1005F55AE /* AppCenterCrashes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppCenterCrashes.framework; path = submodules/AppCenter/AppCenterCrashes.framework; sourceTree = ""; }; + A7565EAD23BE02AF0031EADE /* ChatGradientModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatGradientModel.swift; sourceTree = ""; }; + A766493E236D6BFD00163DF4 /* PasscodeSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeSettings.swift; sourceTree = ""; }; + A767DD4023F2BB3200366F76 /* ShortcutListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutListController.swift; sourceTree = ""; }; + A76C8A99241F826900FDB071 /* GlobalSearchModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchModalController.swift; sourceTree = ""; }; + A76C8A9D2420FFE400FDB071 /* folder_empty.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = folder_empty.tgs; sourceTree = ""; }; + A76C8A9F2422132400FDB071 /* VerticalTabsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalTabsView.swift; sourceTree = ""; }; + A76C8AA124221D2800FDB071 /* graph_loading.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = graph_loading.tgs; sourceTree = ""; }; + A76C8AA324221F5400FDB071 /* StatisticsLoadingRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsLoadingRowItem.swift; sourceTree = ""; }; + A76C8AB2242366EC00FDB071 /* PeerMediaBlockRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaBlockRowItem.swift; sourceTree = ""; }; + A778DC2923C75F1100DD307B /* Confetti.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confetti.swift; sourceTree = ""; }; + A778DC2D23C77AD800DD307B /* confetti.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = confetti.mp3; sourceTree = ""; }; + A778DC2F23C8985300DD307B /* SoundEffects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundEffects.swift; sourceTree = ""; }; + A778DC3123C8988100DD307B /* quiz-incorrect.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "quiz-incorrect.mp3"; sourceTree = ""; }; + A778DC3223C8988300DD307B /* quiz-correct.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "quiz-correct.mp3"; sourceTree = ""; }; + A778DC4123CD9F3C00DD307B /* SearchSettingsEmptyItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsEmptyItem.swift; sourceTree = ""; }; + A7831B172403CB420056AEAC /* Graph.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Graph.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7831B1B2403CFDD0056AEAC /* ChannelStatsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStatsViewController.swift; sourceTree = ""; }; + A7831B2924040B6B0056AEAC /* StatisticRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticRowItem.swift; sourceTree = ""; }; + A789E08323E05EAE00AEB34A /* ChatListFiltersListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFiltersListController.swift; sourceTree = ""; }; + A7918DB324093505002011CA /* GraphUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GraphUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7919134240D0869002011CA /* MurMurHash32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MurMurHash32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7919137240D1077002011CA /* ChatListFilterPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterPredicate.swift; sourceTree = ""; }; + A7B5030E23B62E0000C9838E /* nanosvg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nanosvg.c; sourceTree = ""; }; + A7B5030F23B62E0400C9838E /* nanosvg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nanosvg.h; sourceTree = ""; }; + A7B5031123B62E1700C9838E /* Svg.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Svg.m; sourceTree = ""; }; + A7B5031223B62E1A00C9838E /* Svg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Svg.h; sourceTree = ""; }; + A7B6DDD623ED8FDF00B8E01C /* think_spectacular.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = think_spectacular.tgs; sourceTree = ""; }; + A7C1377C23D1A62700803ED3 /* PeerEmptyHolderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerEmptyHolderItem.swift; sourceTree = ""; }; + A7C1379123DB00D900803ED3 /* ChatListFilterPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterPreferences.swift; sourceTree = ""; }; + A7C1379D23DF21EA00803ED3 /* ChatListRevealItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListRevealItem.swift; sourceTree = ""; }; + A7C41DB3235862BB00CF9402 /* PeerMediaPhotosController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaPhotosController.swift; sourceTree = ""; }; + A7C41DB523586DC100CF9402 /* PeerPhotosMonthItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerPhotosMonthItem.swift; sourceTree = ""; }; + A7C7215623FD45D300CE3F75 /* SaveModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveModalController.swift; sourceTree = ""; }; + A7C7215823FD473D00CE3F75 /* success_saved.tgs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = success_saved.tgs; sourceTree = ""; }; + A7C7215A23FD4BAA00CE3F75 /* LottieLocalAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieLocalAnimations.swift; sourceTree = ""; }; + A7D28204236C3C0B0000A9BF /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28206236C3C0F0000A9BF /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28208236C3C150000A9BF /* TelegramCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2820A236C3C1B0000A9BF /* MtProtoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28210236C3D390000A9BF /* SyncCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SyncCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28213236C3DAE0000A9BF /* WalletCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WalletCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2822A236C51A50000A9BF /* OpenSSLEncryption.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OpenSSLEncryption.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2822C236C51F10000A9BF /* OpenSSLEncryption.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OpenSSLEncryption.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2822E236C549B0000A9BF /* SyncCoreExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncCoreExtension.swift; sourceTree = ""; }; + A7D28231236C57DD0000A9BF /* libphonenumber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28236236C5B2C0000A9BF /* PhoneNumberUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberUtils.swift; sourceTree = ""; }; + A7D2823B236C69070000A9BF /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7DF1B6B237415AD00ACC01F /* Zip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Zip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7ED5DAE236C7CE100040372 /* RLottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RLottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7F282B1238D122900742C20 /* UnauthorizedConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnauthorizedConfiguration.swift; sourceTree = ""; }; + A7F282FF2395168300742C20 /* SettingsSearchRecentQueries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchRecentQueries.swift; sourceTree = ""; }; + A7F283012395185B00742C20 /* SettingsSearchableItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchableItems.swift; sourceTree = ""; }; + A7F283032395289B00742C20 /* AccountUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUtils.swift; sourceTree = ""; }; + A7F2830523954E1B00742C20 /* CachedFaqInstantPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedFaqInstantPage.swift; sourceTree = ""; }; + A7F2830723954EF800742C20 /* CachedInstantPages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedInstantPages.swift; sourceTree = ""; }; + A7F283092395570400742C20 /* SearchSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsController.swift; sourceTree = ""; }; + A7F2831A239A496400742C20 /* ContextShowPeersHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextShowPeersHolder.swift; sourceTree = ""; }; + A7F28336239A808500742C20 /* AudioAnimatedSticker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioAnimatedSticker.swift; sourceTree = ""; }; C2016F3A1EAA4538003AF981 /* RecentPeerRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentPeerRowItem.swift; sourceTree = ""; }; C2016F431EAA4967003AF981 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/ShareViewController.strings"; sourceTree = ""; }; C2016F451EAA4967003AF981 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; @@ -582,12 +1367,10 @@ C20232A71D81D189007C9ADE /* ChatController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatController.swift; sourceTree = ""; }; C20232A91D81D19C007C9ADE /* ChatRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRowItem.swift; sourceTree = ""; }; C20232AB1D81D1AE007C9ADE /* ChatMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageView.swift; sourceTree = ""; }; - C20320FE1F9769BA00143395 /* TwoStepVerificationResetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoStepVerificationResetController.swift; sourceTree = ""; }; C2057FA91EBC6A3C000423DC /* ChatCallRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCallRowItem.swift; sourceTree = ""; }; C205DB971EE71127003711DF /* ChannelAdminController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdminController.swift; sourceTree = ""; }; C205DB9C1EE88762003711DF /* Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = ""; }; C205FEA51EB39DE400455808 /* SidebarCapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarCapViewController.swift; sourceTree = ""; }; - C2084F031F5D5C6F004713C4 /* ChatReplyPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatReplyPreviewController.swift; sourceTree = ""; }; C209C36F1F262537009231FE /* emoji_suggestions_data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emoji_suggestions_data.cpp; sourceTree = ""; }; C209C3701F262537009231FE /* emoji_suggestions_data.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = emoji_suggestions_data.h; sourceTree = ""; }; C209C3711F262537009231FE /* emoji_suggestions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emoji_suggestions.cpp; sourceTree = ""; }; @@ -602,6 +1385,8 @@ C209C38C1F276D4C009231FE /* ChatSearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatSearchView.swift; sourceTree = ""; }; C20B8F3D1DFC52EE008A354E /* ChatEmptyPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmptyPeerItem.swift; sourceTree = ""; }; C20B8F3F1DFD9999008A354E /* ChatListNothingItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListNothingItem.swift; sourceTree = ""; }; + C20CAD111FE291E200EFF8BF /* ChatBubbleAccessoryForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBubbleAccessoryForward.swift; sourceTree = ""; }; + C20CAD141FE436E300EFF8BF /* SelectSizeRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectSizeRowItem.swift; sourceTree = ""; }; C20CB7281E60886E00C992AC /* LinkInvationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkInvationController.swift; sourceTree = ""; }; C20D5AB01DA996480042616A /* EBlockItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EBlockItem.swift; sourceTree = ""; }; C20D5AB21DA9965B0042616A /* EBlockRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EBlockRowView.swift; sourceTree = ""; }; @@ -610,18 +1395,16 @@ C21074261E77F7A3006EE5EF /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; C210742B1E780CC1006EE5EF /* PhotoCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCache.swift; sourceTree = ""; }; C21095991E9FE04700E10BDB /* ChatVideoMessageContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatVideoMessageContentView.swift; sourceTree = ""; }; - C21177FF1F16BB8300AC706D /* BioViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BioViewController.swift; sourceTree = ""; }; - C21656CA1EE1CC6F0041A6BA /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; - C21656CB1EE1CC730041A6BA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - C21656CC1EE1CC760041A6BA /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - C21656CD1EE1CC7C0041A6BA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - C21656CE1EE1CC830041A6BA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + C21656CA1EE1CC6F0041A6BA /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + C21656CB1EE1CC730041A6BA /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + C21656CC1EE1CC760041A6BA /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + C21656CD1EE1CC7C0041A6BA /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + C21656CE1EE1CC830041A6BA /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; C21656D31EE4A83C0041A6BA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainMenu.strings; sourceTree = ""; }; C21656D51EE4A8400041A6BA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = ""; }; C21656D61EE4A8430041A6BA /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/MainMenu.strings; sourceTree = ""; }; C21656D81EE4A8470041A6BA /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/MainMenu.strings; sourceTree = ""; }; C21656DA1EE4A8490041A6BA /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/MainMenu.strings"; sourceTree = ""; }; - C21656E71EE576F90041A6BA /* ChatUserPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUserPopover.swift; sourceTree = ""; }; C2167E4E1DC220D800F98E03 /* PeerMediaWebpageRowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaWebpageRowContent.swift; sourceTree = ""; }; C2167E501DC220E900F98E03 /* PeerMediaMusicRowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaMusicRowContent.swift; sourceTree = ""; }; C2167E521DC220F600F98E03 /* PeerMediaFileRowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaFileRowContent.swift; sourceTree = ""; }; @@ -635,7 +1418,6 @@ C218FF991F42030B00DD7D35 /* InstantPageChannelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageChannelItem.swift; sourceTree = ""; }; C218FF9B1F4204C400DD7D35 /* InstantPageChannelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageChannelView.swift; sourceTree = ""; }; C219E1D61D8869F20042F0C8 /* ChatHoleRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHoleRowItem.swift; sourceTree = ""; }; - C219E1D81D886A160042F0C8 /* ChatHoleRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHoleRowView.swift; sourceTree = ""; }; C219E1DA1D8884290042F0C8 /* ChatHistoryEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryEntry.swift; sourceTree = ""; }; C219E1E31D8AC8370042F0C8 /* ChatUnreadRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUnreadRowItem.swift; sourceTree = ""; }; C219E1E71D8AC90B0042F0C8 /* ChatUnreadRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUnreadRowView.swift; sourceTree = ""; }; @@ -644,13 +1426,14 @@ C21A48AD1F7CFBBE0095ADB1 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; C21A48B11F7D0D3F0095ADB1 /* VideoMessage.fsh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.glsl; path = VideoMessage.fsh; sourceTree = ""; }; C21A48B31F7D0D6B0095ADB1 /* VideoMessage.vsh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.glsl; path = VideoMessage.vsh; sourceTree = ""; }; - C21AAE331DB0F6BC007638C5 /* MediaTitleBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaTitleBarView.swift; sourceTree = ""; }; C21AAE351DB22CA5007638C5 /* AvatarLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarLayer.swift; sourceTree = ""; }; C21B24601ED9C39F00FC6CDA /* SuggestionLocalizationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestionLocalizationViewController.swift; sourceTree = ""; }; C21B24621EDADC8600FC6CDA /* MMMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MMMenuItem.swift; sourceTree = ""; }; C21B24641EDB115700FC6CDA /* StringPluralization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringPluralization.swift; sourceTree = ""; }; C21B24661EDB116B00FC6CDA /* NumberPluralizationForm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NumberPluralizationForm.h; sourceTree = ""; }; C21B24671EDB116B00FC6CDA /* NumberPluralizationForm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NumberPluralizationForm.m; sourceTree = ""; }; + C21BE3AE1FD099AA00C1C849 /* DeveloperViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperViewController.swift; sourceTree = ""; }; + C21BE3B01FD14CDB00C1C849 /* ParseAppearanceColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAppearanceColors.swift; sourceTree = ""; }; C2203E9F1DDE040F001E6AB6 /* libcommonCrypto.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcommonCrypto.tbd; path = usr/lib/system/libcommonCrypto.tbd; sourceTree = SDKROOT; }; C2203EA11DDE2AB8001E6AB6 /* ChatSelectText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatSelectText.swift; sourceTree = ""; }; C221ED531EA684BE00471C65 /* DataAndStorageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageViewController.swift; sourceTree = ""; }; @@ -660,22 +1443,17 @@ C221ED5B1EA69AA300471C65 /* StorageUsageController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageUsageController.swift; sourceTree = ""; }; C221ED5D1EA6B36600471C65 /* ChatStorageManagmentModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatStorageManagmentModalController.swift; sourceTree = ""; }; C22338441F823F8C004AD57C /* VideoCameraStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCameraStructures.swift; sourceTree = ""; }; + C224675A1FA8546200F03E27 /* GroupedLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupedLayout.swift; sourceTree = ""; }; + C224675C1FA884E300F03E27 /* ChatGroupedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatGroupedItem.swift; sourceTree = ""; }; C224A72C1EB75A3100F43F3F /* ExMajorNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExMajorNavigationController.swift; sourceTree = ""; }; C225524A1F7BE7000007944D /* VideoRecorderModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRecorderModalController.swift; sourceTree = ""; }; C225524C1F7BE8E40007944D /* VideoRecorderModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRecorderModalView.swift; sourceTree = ""; }; C225524E1F7C03B50007944D /* VideoRecorderPipeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRecorderPipeline.swift; sourceTree = ""; }; C2256D971DAB9D5A00494CF4 /* ChatHistoryViewForLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryViewForLocation.swift; sourceTree = ""; }; - C226741E1DBCEAC2000BA9ED /* EStickerGridEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EStickerGridEntries.swift; sourceTree = ""; }; - C22674201DBCECCC000BA9ED /* EStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EStickerGridItem.swift; sourceTree = ""; }; C226742A1DBE16B9000BA9ED /* CachedResourceRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedResourceRepresentations.swift; sourceTree = ""; }; C226742E1DBE2CB3000BA9ED /* PanelUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanelUtils.swift; sourceTree = ""; }; C22674301DBE9B50000BA9ED /* FetchCachedRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchCachedRepresentations.swift; sourceTree = ""; }; - C22674321DBF665A000BA9ED /* EStickerPackEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EStickerPackEntries.swift; sourceTree = ""; }; - C22674341DBF6A85000BA9ED /* EStickerPackItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EStickerPackItem.swift; sourceTree = ""; }; C22674371DC125C1000BA9ED /* PeerMediaCollectionInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaCollectionInterfaceState.swift; sourceTree = ""; }; - C22674391DC1273E000BA9ED /* PeerMediaGridController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaGridController.swift; sourceTree = ""; }; - C226743E1DC12E4E000BA9ED /* GridMessageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMessageItem.swift; sourceTree = ""; }; - C22674401DC13165000BA9ED /* GridHoleItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridHoleItem.swift; sourceTree = ""; }; C22674441DC20664000BA9ED /* PeerMediaListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaListController.swift; sourceTree = ""; }; C2271D9B1DACC027001792B6 /* SearchController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = ""; }; C2271D9D1DACC796001792B6 /* ChatListMessageRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListMessageRowItem.swift; sourceTree = ""; }; @@ -691,13 +1469,11 @@ C2271DBA1DAE213D001792B6 /* ChannelInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelInfoEntries.swift; sourceTree = ""; }; C2271DBE1DAE563D001792B6 /* PeerInfoHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoHeaderItem.swift; sourceTree = ""; }; C2271DC01DAE583E001792B6 /* TextAndLabelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextAndLabelItem.swift; sourceTree = ""; }; - C2271DC31DAE5E46001792B6 /* PeerInfoHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoHeaderView.swift; sourceTree = ""; }; C2271DC91DAED681001792B6 /* PresenceStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceStrings.swift; sourceTree = ""; }; C2271DD11DAF6DF5001792B6 /* EmptyChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyChatViewController.swift; sourceTree = ""; }; C2271DD61DAF80D5001792B6 /* PeerMediaController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaController.swift; sourceTree = ""; }; C2271F2A1DB3BEB60045E719 /* GlobalHandlers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalHandlers.swift; sourceTree = ""; }; C2271F371DB4D0490045E719 /* EmojiViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiViewController.swift; sourceTree = ""; }; - C2271F391DB4D0540045E719 /* EStickersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EStickersViewController.swift; sourceTree = ""; }; C2271F3B1DB4D0630045E719 /* GIFViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GIFViewController.swift; sourceTree = ""; }; C2271F3D1DB4D4240045E719 /* ETabRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ETabRowItem.swift; sourceTree = ""; }; C2271F3F1DB4D5850045E719 /* ETabRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ETabRowView.swift; sourceTree = ""; }; @@ -724,6 +1500,7 @@ C2303E721D9966BD00098E12 /* ChatInputActionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputActionsView.swift; sourceTree = ""; }; C2303E771D997C3100098E12 /* ChatInputAttachView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputAttachView.swift; sourceTree = ""; }; C2303E891D9A76D800098E12 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + C23044821F98F8B400977C51 /* MediaPreviewRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewRowItem.swift; sourceTree = ""; }; C230B8EE1DD3358C0057F596 /* AccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; C230B8F01DD348970057F596 /* AccountInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInfoItem.swift; sourceTree = ""; }; C230B8F31DD368D40057F596 /* SelectPeersController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectPeersController.swift; sourceTree = ""; }; @@ -758,7 +1535,6 @@ C234A7FA1ED7112400EBBECE /* LocalizableExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizableExtension.swift; sourceTree = ""; }; C234A7FD1ED725C300EBBECE /* LanguageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageViewController.swift; sourceTree = ""; }; C234A7FF1ED73A3300EBBECE /* LanguageRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageRowItem.swift; sourceTree = ""; }; - C234CA901D97E117003023F7 /* PostboxMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PostboxMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C234D4111EEDE6990017DC25 /* LoadingTableItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingTableItem.swift; sourceTree = ""; }; C236ADDC1F7D318600E8C71A /* TGVideoCameraMovieRecorder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TGVideoCameraMovieRecorder.m; sourceTree = ""; }; C236ADDD1F7D318700E8C71A /* TGVideoCameraMovieRecorder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TGVideoCameraMovieRecorder.h; sourceTree = ""; }; @@ -780,22 +1556,25 @@ C23CF9711E70BD0B0009CD06 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ShareViewController.strings; sourceTree = ""; }; C23CF9731E70BD0B0009CD06 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFCompactRounded-Semibold.otf"; sourceTree = ""; }; - C240E9511F96449E00F671FA /* TwoStepVerificationPasswordEntryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoStepVerificationPasswordEntryController.swift; sourceTree = ""; }; - C2412E061DA795D200588C14 /* GalleryControls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryControls.swift; sourceTree = ""; }; + C23EEC881FCC47C1001371CD /* PeerMediaDateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaDateItem.swift; sourceTree = ""; }; + C241025C1FD5702D00DB8625 /* ChatMessageBubbleImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageBubbleImages.swift; sourceTree = ""; }; + C24102631FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage+RHResizableImageAdditions.h"; sourceTree = ""; }; + C24102641FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+RHResizableImageAdditions.m"; sourceTree = ""; }; + C24102661FD587C400DB8625 /* RHARCSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RHARCSupport.h; sourceTree = ""; }; + C241026E1FD58EA800DB8625 /* SImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SImageView.swift; sourceTree = ""; }; C2423A531F2235080041907F /* InstantPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageViewController.swift; sourceTree = ""; }; C246161A1ED33FFE0026D5BC /* InstantVideoPIP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantVideoPIP.swift; sourceTree = ""; }; + C246D6271FAB72D4004C17FA /* MediaGroupPreviewRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGroupPreviewRowItem.swift; sourceTree = ""; }; C248BD201E6F09CC004B9106 /* ChatGameContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGameContentView.swift; sourceTree = ""; }; C248BD231E706104004B9106 /* BlockedPeersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockedPeersViewController.swift; sourceTree = ""; }; C248BD251E706A05004B9106 /* RecentSessionsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentSessionsController.swift; sourceTree = ""; }; C248BD281E706DDA004B9106 /* RecentSessionRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentSessionRowItem.swift; sourceTree = ""; }; - C24949111E5B704900D7ED5D /* AccountsListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsListViewController.swift; sourceTree = ""; }; C24949131E5B763F00D7ED5D /* ApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationContext.swift; sourceTree = ""; }; C24BA3BC1E9D30F800E8970B /* DeleteSupergroupMessagesModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSupergroupMessagesModalController.swift; sourceTree = ""; }; C24D9F901E1F8F85002CD3F3 /* MajorBackNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MajorBackNavigationBar.swift; sourceTree = ""; }; C24D9FC31E24FFF3002CD3F3 /* PasscodeLockController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockController.swift; sourceTree = ""; }; C24D9FC61E2500AC002CD3F3 /* PasscodeSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeSettingsViewController.swift; sourceTree = ""; }; C24D9FC91E25033E002CD3F3 /* PrivacyAndSecurityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyAndSecurityViewController.swift; sourceTree = ""; }; - C24D9FDB1E267932002CD3F3 /* PreviewSenderItems.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewSenderItems.swift; sourceTree = ""; }; C24DAB851E08026C005EE404 /* MGalleryVideoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MGalleryVideoItem.swift; sourceTree = ""; }; C24DAB921E0828B6005EE404 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; C24DAB981E083184005EE404 /* YTVimeoError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YTVimeoError.h; sourceTree = ""; }; @@ -839,13 +1618,11 @@ C250B0381DB7BB2D004E9FBE /* MimeTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MimeTypes.swift; sourceTree = ""; }; C250BA8E1E6E1CDC0057CD96 /* ChatMessageThrottledProcessingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageThrottledProcessingManager.swift; sourceTree = ""; }; C250BA931E6E84880057CD96 /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; - C25253251DF03F5700ADBC98 /* TGOpusAudioRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGOpusAudioRecorder.h; sourceTree = ""; }; - C25253261DF03F5700ADBC98 /* TGOpusAudioRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGOpusAudioRecorder.m; sourceTree = ""; }; + C251FB4B1FEDCC750035E5D7 /* ChatPresentationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPresentationUtils.swift; sourceTree = ""; }; + C251FB501FEE300E0035E5D7 /* Telegram.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Telegram.xcodeproj; sourceTree = ""; }; C25253281DF03F9600ADBC98 /* TGAudioWaveform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGAudioWaveform.h; sourceTree = ""; }; C25253291DF03F9600ADBC98 /* TGAudioWaveform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGAudioWaveform.m; sourceTree = ""; }; C252532C1DF0440300ADBC98 /* begin_record.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = begin_record.caf; sourceTree = ""; }; - C2538E511E770B4600B21DF0 /* GroupAdminsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupAdminsController.swift; sourceTree = ""; }; - C253A92C1D8EE1A600CDC850 /* ChatMediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaView.swift; sourceTree = ""; }; C253A9431D90303200CDC850 /* FastBlur.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastBlur.h; sourceTree = ""; }; C253A9441D90303200CDC850 /* FastBlur.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FastBlur.m; sourceTree = ""; }; C253A94A1D9032A000CDC850 /* Telegram-Mac-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Telegram-Mac-Bridging-Header.h"; sourceTree = ""; }; @@ -859,7 +1636,7 @@ C253A9621D91A90100CDC850 /* ChatFileMediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatFileMediaItem.swift; sourceTree = ""; }; C253A9661D92E3AE00CDC850 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; C253A96C1D92F69C00CDC850 /* ReplyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyModel.swift; sourceTree = ""; }; - C253A9711D92F9F100CDC850 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + C253A9711D92F9F100CDC850 /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; C253A9731D94182500CDC850 /* ChatRightView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRightView.swift; sourceTree = ""; }; C253E2251DE335C10022A29F /* ChatVoiceContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatVoiceContentView.swift; sourceTree = ""; }; C253E22D1DE33A7C0022A29F /* ChatMusicRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMusicRowItem.swift; sourceTree = ""; }; @@ -870,7 +1647,9 @@ C253E2391DE4D3DB0022A29F /* ChatInterfaceInputContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceInputContext.swift; sourceTree = ""; }; C253E23B1DE4D4080022A29F /* ChatInterfaceStateContextQueries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateContextQueries.swift; sourceTree = ""; }; C253E23D1DE61CB50022A29F /* ContextListRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextListRowItem.swift; sourceTree = ""; }; - C25692971EDD85FB009EE421 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; + C25692971EDD85FB009EE421 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + C256A9131FB9CBF10043D497 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; + C256A9151FB9E1490043D497 /* AdditionalSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalSettings.swift; sourceTree = ""; }; C258D1B31F8D385700458478 /* PreHistorySettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreHistorySettingsController.swift; sourceTree = ""; }; C258D1B51F8D3A0D00458478 /* PreHistoryControllerStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreHistoryControllerStructures.swift; sourceTree = ""; }; C25911361DF1A68200671E72 /* ChatInputRecordingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputRecordingView.swift; sourceTree = ""; }; @@ -878,7 +1657,7 @@ C2593FBE1F7D241E00F6D2B1 /* TGPaintShader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TGPaintShader.m; sourceTree = ""; }; C259ED1B1DB8DC78008E6712 /* ChatNavigateScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatNavigateScroller.swift; sourceTree = ""; }; C259ED1D1DB956C1008E6712 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - C25BB1681F867FEE0089ED02 /* ChatVideoAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatVideoAccessoryView.swift; sourceTree = ""; }; + C25BB1681F867FEE0089ED02 /* ChatMessageAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageAccessoryView.swift; sourceTree = ""; }; C25C132C1E8A404F00AE26A1 /* InstalledStickerPacksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstalledStickerPacksController.swift; sourceTree = ""; }; C25C132E1E8A405E00AE26A1 /* ArchivedStickerPacksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchivedStickerPacksController.swift; sourceTree = ""; }; C25C13301E8A406D00AE26A1 /* FeaturedStickerPacksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeaturedStickerPacksController.swift; sourceTree = ""; }; @@ -892,7 +1671,7 @@ C26505961E041B90001954DC /* MGalleryGIFItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MGalleryGIFItem.swift; sourceTree = ""; }; C26546CB1EA0AC3C00E3969A /* ChatVideoMessageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatVideoMessageItem.swift; sourceTree = ""; }; C26A37EB1E5DE464006977AC /* ChannelAdminsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdminsViewController.swift; sourceTree = ""; }; - C26A37ED1E5DE48F006977AC /* ChannelBlacklistViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelBlacklistViewController.swift; sourceTree = ""; }; + C26A37ED1E5DE48F006977AC /* ChannelBlocklistViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelBlocklistViewController.swift; sourceTree = ""; }; C26A71981DC9FA5100F69385 /* EditMessageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMessageModel.swift; sourceTree = ""; }; C26A719A1DC9FB3600F69385 /* InputPasteboardParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputPasteboardParser.swift; sourceTree = ""; }; C26D8A3B1E464944002FAA3F /* JoinLinkPreviewModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLinkPreviewModalController.swift; sourceTree = ""; }; @@ -900,12 +1679,12 @@ C26E82D01E83EFFE0046DF2F /* TimeObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeObserver.m; sourceTree = ""; }; C271EB8F1EB8E6A40034792D /* SelectivePrivacySettingsPeersController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectivePrivacySettingsPeersController.swift; sourceTree = ""; }; C271EB961EB916870034792D /* libtgvoip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libtgvoip.framework; path = "../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/libtgvoip.framework"; sourceTree = ""; }; - C271EB991EB9DEF00034792D /* CallBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallBridge.h; sourceTree = ""; }; - C271EB9A1EB9DEF00034792D /* CallBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CallBridge.mm; sourceTree = ""; }; + C271EB991EB9DEF00034792D /* OngoingCallThreadLocalContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OngoingCallThreadLocalContext.h; sourceTree = ""; }; + C271EB9A1EB9DEF00034792D /* OngoingCallThreadLocalContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OngoingCallThreadLocalContext.mm; sourceTree = ""; }; C271EBA01EB9F04E0034792D /* TGCallUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGCallUtils.h; sourceTree = ""; }; C271EBA11EB9F04E0034792D /* TGCallUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGCallUtils.mm; sourceTree = ""; }; - C271EBE71EBA22FE0034792D /* TGCallConnectionDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGCallConnectionDescription.h; sourceTree = ""; }; - C271EBE81EBA22FE0034792D /* TGCallConnectionDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGCallConnectionDescription.m; sourceTree = ""; }; + C271EBE71EBA22FE0034792D /* OngoingCallConnectionDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OngoingCallConnectionDescription.h; sourceTree = ""; }; + C271EBE81EBA22FE0034792D /* OngoingCallConnectionDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OngoingCallConnectionDescription.m; sourceTree = ""; }; C271EBEA1EBA3BC90034792D /* PCallSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PCallSession.swift; sourceTree = ""; }; C275932D1DF6E1CE00A0807A /* AboutModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutModalController.swift; sourceTree = ""; }; C275E9EE1F8FCA4200D3D8C0 /* PhoneNumberIntroController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberIntroController.swift; sourceTree = ""; }; @@ -930,7 +1709,7 @@ C27AAFE81DE9DA61009B9629 /* CountryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryManager.swift; sourceTree = ""; }; C27AAFEC1DEB1D72009B9629 /* SignalUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalUtils.swift; sourceTree = ""; }; C27AAFEE1DEB2EA9009B9629 /* EmptyComposeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyComposeController.swift; sourceTree = ""; }; - C28149871EA7F22200BB933E /* PreparedChatHistoryViewTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreparedChatHistoryViewTransition.swift; sourceTree = ""; }; + C27BAC7920CFCE68007A7508 /* PassportSettingsHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportSettingsHeaderItem.swift; sourceTree = ""; }; C28149891EA7F44300BB933E /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; C2844AD61DA907E8009308DC /* EntertainmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntertainmentViewController.swift; sourceTree = ""; }; C2844ADF1DA90C8A009308DC /* emoji.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emoji.txt; sourceTree = ""; }; @@ -938,11 +1717,12 @@ C28BAB281DF981320027CE3A /* AudioWaveform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioWaveform.swift; sourceTree = ""; }; C28BAB2A1DF9C2790027CE3A /* DateUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtils.h; sourceTree = ""; }; C28BAB2B1DF9C2790027CE3A /* DateUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DateUtils.mm; sourceTree = ""; }; + C2905E1B207E4D9E00990AD7 /* InstantPageAudioView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageAudioView.swift; sourceTree = ""; }; + C2905E1D207E545600990AD7 /* InstantPageAudioItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageAudioItem.swift; sourceTree = ""; }; C291942E1DCC6E2200359491 /* DeclareEncodables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeclareEncodables.swift; sourceTree = ""; }; C291E2721E8AFA2C00D397BA /* ShareApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareApplicationContext.swift; sourceTree = ""; }; C29340F01F506C310074991E /* EmptyGroupstickerSearchRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyGroupstickerSearchRowItem.swift; sourceTree = ""; }; C295C65E1F75808600BA309D /* ChatAdditionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAdditionController.swift; sourceTree = ""; }; - C29670781F0FAAC800884DA2 /* AppearanceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceViewController.swift; sourceTree = ""; }; C296707A1F0FBFB500884DA2 /* ThemeSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSettings.swift; sourceTree = ""; }; C296AF7E1D8D38E5001DBB59 /* ChatRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatRowView.swift; sourceTree = ""; }; C296AF851D8DB178001DBB59 /* MediaUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaUtils.swift; sourceTree = ""; }; @@ -963,12 +1743,10 @@ C29B5F4E1DC8F39A00D13E65 /* FWDNavigationAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FWDNavigationAction.swift; sourceTree = ""; }; C29C3E5A1E421F1700193A7E /* ChatAccessoryModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatAccessoryModel.swift; sourceTree = ""; }; C29C3E6E1E4352C100193A7E /* ContextStickerRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextStickerRowItem.swift; sourceTree = ""; }; - C29C3E701E43881500193A7E /* StickerPreviewModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPreviewModalController.swift; sourceTree = ""; }; + C29C3E701E43881500193A7E /* ModalPreviewViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalPreviewViews.swift; sourceTree = ""; }; C29C3E721E4397F300193A7E /* StickerPreviewHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPreviewHandler.swift; sourceTree = ""; }; C29E0EDF1F4DC43100C0C7A8 /* InstantViewAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantViewAppearance.swift; sourceTree = ""; }; C29E0EE11F4EFB5100C0C7A8 /* GroupStickerSetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupStickerSetController.swift; sourceTree = ""; }; - C29F4C741F45F58B00DBFC00 /* MIHSliderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIHSliderView.h; sourceTree = ""; }; - C29F4C751F45F58B00DBFC00 /* MIHSliderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIHSliderView.m; sourceTree = ""; }; C29F4C771F45FBFF00DBFC00 /* InstantPageSlideshowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageSlideshowItem.swift; sourceTree = ""; }; C29F4C7C1F47283600DBFC00 /* InstantPageBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageBrowser.swift; sourceTree = ""; }; C2A1054A1E0163D500B01F48 /* GalleryPageController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryPageController.swift; sourceTree = ""; }; @@ -986,11 +1764,9 @@ C2A506781DF59C9700971A93 /* PicturePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PicturePicker.swift; sourceTree = ""; }; C2A5067A1DF5BE6900971A93 /* GeneralTextRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralTextRowItem.swift; sourceTree = ""; }; C2A71CD61DD9EEDB00C69F73 /* WebpageModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebpageModalController.swift; sourceTree = ""; }; - C2A71CD81DDA0FA300C69F73 /* HockeySDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = HockeySDK.framework; sourceTree = ""; }; C2A71CE01DDB18FF00C69F73 /* ThumbUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbUtils.swift; sourceTree = ""; }; C2A71CE21DDB2EBD00C69F73 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = ""; }; C2A71CE61DDB2F8700C69F73 /* UsernameSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsernameSettingsViewController.swift; sourceTree = ""; }; - C2A71CE81DDB342100C69F73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = ""; }; C2A71CEC1DDB3C1000C69F73 /* ChatHeaderController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHeaderController.swift; sourceTree = ""; }; C2A72D8F1DEC66F300C3B945 /* LoginErrorStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginErrorStateView.swift; sourceTree = ""; }; C2A87DE71F4C6910002D3F73 /* InstantViewWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantViewWindow.swift; sourceTree = ""; }; @@ -1002,7 +1778,6 @@ C2AF01121F01543200D8AC1D /* ExportProxyModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportProxyModalController.swift; sourceTree = ""; }; C2AF011B1F03D4C600D8AC1D /* TGCallAesCtr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGCallAesCtr.h; sourceTree = ""; }; C2AF011C1F03D4C600D8AC1D /* TGCallAesCtr.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGCallAesCtr.m; sourceTree = ""; }; - C2AF3B811E5CD79200DFDD81 /* ConvertGroupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertGroupViewController.swift; sourceTree = ""; }; C2B0722D1DFEDE430082939D /* UsernameInputRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsernameInputRowItem.swift; sourceTree = ""; }; C2B1A0EE1D9D94CE00ACB1DD /* SeparatorRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorRowItem.swift; sourceTree = ""; }; C2B1A0F01D9D94E400ACB1DD /* SeparatorRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorRowView.swift; sourceTree = ""; }; @@ -1026,7 +1801,7 @@ C2BB12091ED87C5A00BDE46A /* ControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerExtension.swift; sourceTree = ""; }; C2BB2DAF1F8BDF6700520255 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; C2C030CC1DD5097400617711 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - C2C5C2041EF822B900AEA252 /* ProxySettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySettingsViewController.swift; sourceTree = ""; }; + C2C415E41FA33D1A00FF36F4 /* InputFormatterPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputFormatterPopover.swift; sourceTree = ""; }; C2C738F01DD898DA00CE9D8A /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; C2C73A5A1EEAF3AE00DB8420 /* ChannelEventFilterModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelEventFilterModalController.swift; sourceTree = ""; }; C2C98FEE1E818FB5009CBDB7 /* ClearUserNotifies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClearUserNotifies.swift; sourceTree = ""; }; @@ -1038,6 +1813,8 @@ C2C9B9361E8016D400380D79 /* SPMediaKeyTap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMediaKeyTap.m; sourceTree = ""; }; C2CBCABF1D81528700142EC0 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; C2CBCAC41D81649E00142EC0 /* ChatListRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListRowView.swift; sourceTree = ""; }; + C2CE43E320E2CFE700656543 /* PlayerListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerListController.swift; sourceTree = ""; }; + C2CE43E820F4F74F00656543 /* UpdateModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateModalController.swift; sourceTree = ""; }; C2CFCAB91EBB4A2200843F6A /* voip_busy.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = voip_busy.caf; sourceTree = ""; }; C2CFCABA1EBB4A2200843F6A /* voip_end.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = voip_end.caf; sourceTree = ""; }; C2CFCABB1EBB4A2200843F6A /* voip_fail.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = voip_fail.caf; sourceTree = ""; }; @@ -1048,7 +1825,7 @@ C2D187ED1E28B9CB0038961D /* ShareInlineResultNavigationAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareInlineResultNavigationAction.swift; sourceTree = ""; }; C2D187EF1E28C58C0038961D /* ContextSwitchPeerRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextSwitchPeerRowItem.swift; sourceTree = ""; }; C2D187F11E28D0840038961D /* ChatSwitchInlineController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatSwitchInlineController.swift; sourceTree = ""; }; - C2D2CAEC1E64579700939968 /* StickersPackPreviewModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickersPackPreviewModalController.swift; sourceTree = ""; }; + C2D2CAEC1E64579700939968 /* StickerPackPreviewModalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackPreviewModalController.swift; sourceTree = ""; }; C2D2CAEF1E64874600939968 /* StickerPackGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackGridItem.swift; sourceTree = ""; }; C2D70AF11F2BFB3700AE768E /* Telegram.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Telegram.xcodeproj; sourceTree = ""; }; C2DDA04D1EC0C024003531BB /* opening.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = opening.mp3; sourceTree = ""; }; @@ -1066,7 +1843,6 @@ C2DE5D3B1F3CAE120081EC1E /* InstantPageTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageTextItem.swift; sourceTree = ""; }; C2DE5D3D1F3CAF2B0081EC1E /* InstantPageShapeItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageShapeItem.swift; sourceTree = ""; }; C2DE5D3F1F3CB0380081EC1E /* InstantPageTileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageTileView.swift; sourceTree = ""; }; - C2DEC87E1DECB8C800F6544A /* TelegramApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramApplicationContext.swift; sourceTree = ""; }; C2DF47951DE71160003AA6C0 /* GIFContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GIFContainerView.swift; sourceTree = ""; }; C2DF47971DE79540003AA6C0 /* libopus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopus.a; path = "thrid-party/opus/lib/libopus.a"; sourceTree = ""; }; C2DF479B1DE79574003AA6C0 /* bitwise.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bitwise.c; sourceTree = ""; }; @@ -1115,22 +1891,21 @@ C2E0646A1ECF137300387BB8 /* ChatInvoiceItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInvoiceItem.swift; sourceTree = ""; }; C2E40A1A1E37ADAF0099AC7D /* PeerMediaEmptyRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMediaEmptyRowItem.swift; sourceTree = ""; }; C2E52A0C1EB8C385009AF87D /* SelectivePrivacySettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectivePrivacySettingsController.swift; sourceTree = ""; }; + C2E6F3CE1F9F85260023653D /* ContextHashtagRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextHashtagRowItem.swift; sourceTree = ""; }; C2E8694C1F43500D00BDD0A2 /* ChatNavigationMention.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatNavigationMention.swift; sourceTree = ""; }; + C2E8BA061FB5EF4C00DEB5E2 /* GalleryThumbsControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryThumbsControl.swift; sourceTree = ""; }; + C2E8BA091FB5F15900DEB5E2 /* GalleryThumbsControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryThumbsControlView.swift; sourceTree = ""; }; C2EA177E1E2FD50000887153 /* TransformImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformImageView.swift; sourceTree = ""; }; C2EA17801E2FD50A00887153 /* ImageUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageUtils.swift; sourceTree = ""; }; - C2EA53461F751EF300C183F7 /* GalleryMessageEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryMessageEntry.swift; sourceTree = ""; }; + C2EBBEA01FB5CA94009AD8ED /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; C2F4ED1A1EC5AE1D005F2696 /* CallRatingModalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRatingModalViewController.swift; sourceTree = ""; }; C2F6190C1E844DCD007A051B /* TelegramAccountAuxiliaryMethods.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramAccountAuxiliaryMethods.swift; sourceTree = ""; }; C2F8923C1E3FA51000D98B2D /* PasteboardUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasteboardUtils.swift; sourceTree = ""; }; C2F93A2C1F3C55C500BCD48F /* EmojiToleranceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiToleranceController.swift; sourceTree = ""; }; C2F952AF1F8E1C840056E586 /* CachedAdminIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedAdminIds.swift; sourceTree = ""; }; C2F9C4471F9500B4002B2CBF /* TwoStepVerificationUnlockController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoStepVerificationUnlockController.swift; sourceTree = ""; }; - C2F9C4491F9500C3002B2CBF /* TwoStepVerificationUnlockStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoStepVerificationUnlockStructures.swift; sourceTree = ""; }; C2F9C44B1F95FE58002B2CBF /* Markdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Markdown.swift; sourceTree = ""; }; C2FB2FAA1EBF73CF0093C8BA /* RecentCallsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentCallsViewController.swift; sourceTree = ""; }; - C2FBC1D61DC61AFF0063A23B /* TelegramCoreMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TelegramCoreMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C2FBC1D81DC61B050063A23B /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C2FBC1DE1DC61B580063A23B /* MtProtoKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MtProtoKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C2FBC1E61DC631980063A23B /* SPPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPPreviewController.swift; sourceTree = ""; }; C2FD33E81E696A86008D13D4 /* GroupsInCommonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsInCommonViewController.swift; sourceTree = ""; }; C2FD33ED1E697B31008D13D4 /* TransformOutgoingMessageMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformOutgoingMessageMedia.swift; sourceTree = ""; }; @@ -1141,17 +1916,153 @@ C2FD33F61E6C1486008D13D4 /* SSKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychainQuery.h; sourceTree = ""; }; C2FD33F71E6C1486008D13D4 /* SSKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainQuery.m; sourceTree = ""; }; C2FD33FA1E6C169C008D13D4 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - C2FD34121E6C2503008D13D4 /* LegacyImportAuthorization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyImportAuthorization.swift; sourceTree = ""; }; C2FD34141E6C9003008D13D4 /* BaseApplicationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseApplicationSettings.swift; sourceTree = ""; }; C2FD382D1DCA1FA3009DC28C /* PreviewSenderController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewSenderController.swift; sourceTree = ""; }; C2FD38311DCA215F009DC28C /* GeneralInputRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralInputRow.swift; sourceTree = ""; }; C2FF145F1E532C0A007B7B14 /* SearchEmptyRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchEmptyRowItem.swift; sourceTree = ""; }; + D001E973243B1A0A009025F9 /* LeftSidebarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftSidebarController.swift; sourceTree = ""; }; + D001E975243B385E009025F9 /* FolderIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderIcons.swift; sourceTree = ""; }; + D001E977243B4639009025F9 /* LeftSidebarFolderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftSidebarFolderItem.swift; sourceTree = ""; }; + D004167422D37AD00000566B /* StickerPackPanelRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackPanelRowItem.swift; sourceTree = ""; }; + D004167622D4AD3B0000566B /* StickerPackItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackItems.swift; sourceTree = ""; }; + D004BD2A23153415009A54B1 /* ThemePreviewModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreviewModalController.swift; sourceTree = ""; }; + D007AB9E234C8DCB0022C27F /* WalletPasscodeTimeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletPasscodeTimeout.swift; sourceTree = ""; }; + D007ABA0234CACE40022C27F /* WalletCreateInvoiceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletCreateInvoiceController.swift; sourceTree = ""; }; + D00C73B52302C196004B1E2B /* ChatScheduleController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScheduleController.swift; sourceTree = ""; }; + D00CA6502281BB0900FFACAD /* Telegram-Sandbox.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "Telegram-Sandbox.entitlements"; sourceTree = ""; }; + D00CE50C2289C9B7008C1B4F /* MediaAnimatedStickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaAnimatedStickerView.swift; sourceTree = ""; }; + D00CE50E2289CE87008C1B4F /* ChatAnimatedStickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAnimatedStickerItem.swift; sourceTree = ""; }; + D014193B22AE939F008667CB /* ModalOptionSetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalOptionSetController.swift; sourceTree = ""; }; + D014193D22AE9A90008667CB /* GeneralLineSeparatorRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralLineSeparatorRowItem.swift; sourceTree = ""; }; + D014AA232316CE0700CE5362 /* NewThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewThemeController.swift; sourceTree = ""; }; + D014AA252317D07D00CE5362 /* EditThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditThemeController.swift; sourceTree = ""; }; + D017A3EA2334F5680086174B /* WalletBalanceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletBalanceItem.swift; sourceTree = ""; }; + D017A3EC2335166B0086174B /* WalletInfoCreatedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInfoCreatedItem.swift; sourceTree = ""; }; + D017A3EE2337D1B90086174B /* WalletImportWordsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletImportWordsItem.swift; sourceTree = ""; }; + D017A3F02338DE1E0086174B /* WalletTestWordsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTestWordsItem.swift; sourceTree = ""; }; + D017A3F423390D8C0086174B /* WalletInfoTransactionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInfoTransactionItem.swift; sourceTree = ""; }; + D017A3F8233929B10086174B /* WalletReceiveController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletReceiveController.swift; sourceTree = ""; }; + D017A3FA2339404D0086174B /* WalletTransactionTextItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionTextItem.swift; sourceTree = ""; }; + D017A3FC233949B00086174B /* WalletSendController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendController.swift; sourceTree = ""; }; + D0186730223807D200A77C45 /* ChatListEmptyRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListEmptyRowItem.swift; sourceTree = ""; }; + D01C731622A9814C000DA008 /* InputPasswordController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputPasswordController.swift; sourceTree = ""; }; + D01CBE2C22A5384600F6A971 /* NotificationPreferencesController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferencesController.swift; sourceTree = ""; }; + D01E1EFF22B261A800AD6DAE /* Alpha.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Alpha.xcconfig; sourceTree = ""; }; + D01E1F0322B39A4800AD6DAE /* LottiePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottiePlayer.swift; sourceTree = ""; }; + D0276B7C22BD6511003155D8 /* DisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayLink.swift; sourceTree = ""; }; + D02BD7BF232D0FB800D1814A /* AppAppearanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearanceViewController.swift; sourceTree = ""; }; + D02BD7C2232D14E800D1814A /* ThemePreviewRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreviewRowItem.swift; sourceTree = ""; }; + D02BD7C4232D204F00D1814A /* ThemeListRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeListRowItem.swift; sourceTree = ""; }; + D02BD7C6232D4DB200D1814A /* AppearanceThumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceThumbs.swift; sourceTree = ""; }; + D02F09FF22E8875800553411 /* SoftwareVideoSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareVideoSource.swift; sourceTree = ""; }; + D02F0A0122E88C6E00553411 /* MediaPlayerFramePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerFramePreview.swift; sourceTree = ""; }; + D02F0A0322E88C9900553411 /* UniversalSoftwareVideoSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalSoftwareVideoSource.swift; sourceTree = ""; }; + D0439A1F23410D5400C1E6F9 /* WalletUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletUtils.swift; sourceTree = ""; }; + D04D213E230D68B800609388 /* CustomAccentColorModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAccentColorModalController.swift; sourceTree = ""; }; + D04D2143230DB55B00609388 /* TelegramIconsTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelegramIconsTheme.swift; sourceTree = ""; }; + D050518F2342BA6700D93176 /* WalletSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSettingsController.swift; sourceTree = ""; }; + D0558D7B215141CF006B403D /* ChatInfoTouchbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoTouchbar.swift; sourceTree = ""; }; + D0558D842152B4D3006B403D /* GalleryTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryTouchBar.swift; sourceTree = ""; }; + D0558D862154047E006B403D /* GalleryTouchBarThumbItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryTouchBarThumbItemView.swift; sourceTree = ""; }; + D05F392222FB45450040F341 /* ScheduledMessageModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduledMessageModalController.swift; sourceTree = ""; }; + D0675D6A217E20B9004900A7 /* Zip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Zip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0675D83217F1F27004900A7 /* ArchiverContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiverContext.swift; sourceTree = ""; }; + D06C7BB4247D6F4B00E67C3C /* SoftwareVideoLayerFrameManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareVideoLayerFrameManager.swift; sourceTree = ""; }; + D06C7BB6247D6FA900E67C3C /* SampleBufferPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleBufferPool.swift; sourceTree = ""; }; + D06C7BB8247E664900E67C3C /* SoftwareVideoThumbnailLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareVideoThumbnailLayer.swift; sourceTree = ""; }; + D06C7BBA247FAE3E00E67C3C /* GifPlayerBufferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifPlayerBufferView.swift; sourceTree = ""; }; + D070DB8022D3638F008A0BBE /* StickersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersViewController.swift; sourceTree = ""; }; + D071E8A321496F38001B6024 /* ChatListTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTouchBar.swift; sourceTree = ""; }; + D071E8A5214A805C001B6024 /* ChatTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTouchBar.swift; sourceTree = ""; }; + D071E8A7214BBE21001B6024 /* ChatStickersTouchBarPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStickersTouchBarPopover.swift; sourceTree = ""; }; + D071E8A9214BC589001B6024 /* TouchBarStickerItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarStickerItemView.swift; sourceTree = ""; }; + D071E8AB214BE6E7001B6024 /* TouchBarScrubberHeaderItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarScrubberHeaderItemView.swift; sourceTree = ""; }; + D071E8B2214C15C7001B6024 /* TouchBarEmojiPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarEmojiPicker.swift; sourceTree = ""; }; + D071E8B4214C1935001B6024 /* TouchBarEmojiItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarEmojiItemView.swift; sourceTree = ""; }; + D07450D4233B8D2900769D7F /* WalletTransactionDateStickItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionDateStickItem.swift; sourceTree = ""; }; + D07450D7233BB86900769D7F /* WalletSendProccessingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendProccessingView.swift; sourceTree = ""; }; + D07450D9233BB92F00769D7F /* WalletSendSuccessController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSendSuccessController.swift; sourceTree = ""; }; + D07450DC233D61B000769D7F /* brilliant_static.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = brilliant_static.tgs; sourceTree = ""; }; + D07450DD233D61B100769D7F /* gift.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = gift.tgs; sourceTree = ""; }; + D07450DE233D61B200769D7F /* brilliant_loading.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = brilliant_loading.tgs; sourceTree = ""; }; + D07450DF233D61B200769D7F /* keyboard_typing.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = keyboard_typing.tgs; sourceTree = ""; }; + D07450E0233D61B300769D7F /* write_words.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = write_words.tgs; sourceTree = ""; }; + D07450E1233D61B400769D7F /* fly_dollar.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = fly_dollar.tgs; sourceTree = ""; }; + D07450E2233D61B500769D7F /* swap_money.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = swap_money.tgs; sourceTree = ""; }; + D07450E3233D61B600769D7F /* chiken_born.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = chiken_born.tgs; sourceTree = ""; }; + D07450E4233D61B700769D7F /* keychain.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = keychain.tgs; sourceTree = ""; }; + D07450E5233D61B800769D7F /* smart_guy.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = smart_guy.tgs; sourceTree = ""; }; + D07450F02340DC8200769D7F /* WalletConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConfiguration.swift; sourceTree = ""; }; + D07450F22340E89000769D7F /* sad_man.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = sad_man.tgs; sourceTree = ""; }; + D07450F42340F28D00769D7F /* wallet_success_created.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = wallet_success_created.tgs; sourceTree = ""; }; + D074A56424A1DE7700E92F8A /* OngoingCallContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingCallContext.swift; sourceTree = ""; }; + D074A56624A1DF5200E92F8A /* VoipDerivedState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipDerivedState.swift; sourceTree = ""; }; + D074A56A24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = OngoingCallThreadLocalContextWebrtc.mm; sourceTree = ""; }; + D074A56B24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OngoingCallThreadLocalContextWebrtc.h; sourceTree = ""; }; + D076A07D248A5F890077BC0A /* GifPanelTabRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifPanelTabRowItem.swift; sourceTree = ""; }; + D076F86C22959958004F895A /* InlineLoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineLoginController.swift; sourceTree = ""; }; + D076F86F2295B24D004F895A /* InlineAuthOptionRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAuthOptionRowItem.swift; sourceTree = ""; }; + D076F8862296CAB2004F895A /* ChannelDisscussionGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelDisscussionGroup.swift; sourceTree = ""; }; + D076F88C2296FD18004F895A /* DiscussionHeaderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionHeaderItem.swift; sourceTree = ""; }; + D076F88E2297285F004F895A /* DiscussionSetModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionSetModalController.swift; sourceTree = ""; }; + D076F890229823DA004F895A /* ChannelDiscussionInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelDiscussionInputView.swift; sourceTree = ""; }; + D07B558123338AA300F076A8 /* WalletInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInfoController.swift; sourceTree = ""; }; + D07B558323338B6B00F076A8 /* WalletSplashController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSplashController.swift; sourceTree = ""; }; + D07B55862333903C00F076A8 /* WalletSplashRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSplashRowItem.swift; sourceTree = ""; }; + D07B55882333A41600F076A8 /* WalletSplashButtonRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSplashButtonRowItem.swift; sourceTree = ""; }; + D07B558C2333E1FE00F076A8 /* Wallet24WordsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet24WordsItem.swift; sourceTree = ""; }; + D07B55902334108E00F076A8 /* WalletSplashHelps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletSplashHelps.swift; sourceTree = ""; }; + D07C6D7823464C8000468B1A /* WalletKeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletKeychainController.swift; sourceTree = ""; }; + D07C6D7A2346547600468B1A /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; + D07C6D7C234698C600468B1A /* DynamicHeightRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicHeightRowItem.swift; sourceTree = ""; }; + D07C6D7E2346A42E00468B1A /* monkey_unsee.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = monkey_unsee.tgs; sourceTree = ""; }; + D07C6D7F2346A42F00468B1A /* monkey_see.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = monkey_see.tgs; sourceTree = ""; }; + D07C6D822347701700468B1A /* WalletProcessTransactionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletProcessTransactionController.swift; sourceTree = ""; }; + D0830FC324127468006198E7 /* folder_new.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = folder_new.tgs; sourceTree = ""; }; + D08E5C9722C583CE007B1C09 /* Tuple.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tuple.swift; sourceTree = ""; }; + D093474D242DCFC4000ECA88 /* PeerMediaGroupPeersController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaGroupPeersController.swift; sourceTree = ""; }; D098C7151D7E175A007784E4 /* Telegram.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Telegram.app; sourceTree = BUILT_PRODUCTS_DIR; }; D098C7181D7E175A007784E4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D098C71A1D7E175A007784E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D098C71F1D7E175A007784E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D098C7381D7E1A62007784E4 /* Telegram-Mac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Telegram-Mac.entitlements"; sourceTree = ""; }; D098C7391D7E1C40007784E4 /* AuthController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthController.swift; sourceTree = ""; }; + D09D9DF0229C27A700378796 /* AnimatedStickerUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerUtils.swift; sourceTree = ""; }; + D09D9DF2229C289F00378796 /* GZip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GZip.h; sourceTree = ""; }; + D09D9DF3229C289F00378796 /* GZip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GZip.m; sourceTree = ""; }; + D0A2764D249C9D6B005E3C77 /* PeerPhotos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerPhotos.swift; sourceTree = ""; }; + D0A2764F249CE588005E3C77 /* GroupsStatsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupsStatsController.swift; sourceTree = ""; }; + D0A75F24244843D3001F84A0 /* EditImageCanvasController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditImageCanvasController.swift; sourceTree = ""; }; + D0A75F2624486B6D001F84A0 /* EditImageCanvasColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditImageCanvasColorPicker.swift; sourceTree = ""; }; + D0A75F2924487A1E001F84A0 /* EditImageCanvasControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditImageCanvasControls.swift; sourceTree = ""; }; + D0A75F2B2449B67D001F84A0 /* dart_idle.tgs */ = {isa = PBXFileReference; lastKnownFileType = file; path = dart_idle.tgs; sourceTree = ""; }; + D0B6D72922AA65D3008E36FE /* NewContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewContactController.swift; sourceTree = ""; }; + D0BDD50023A38660002010A5 /* InactiveChannelsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InactiveChannelsController.swift; sourceTree = ""; }; + D0BEB98E21628C250055B718 /* EditImageModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditImageModalController.swift; sourceTree = ""; }; + D0BEB9942166AF270055B718 /* PeerMediaTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaTouchBar.swift; sourceTree = ""; }; + D0BEB997216BD8A70055B718 /* EditImageControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditImageControls.swift; sourceTree = ""; }; + D0BEB999216D10920055B718 /* MediaPreviewEditControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewEditControl.swift; sourceTree = ""; }; + D0C39EC2233A5D9B003CD402 /* WalletTransactionPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTransactionPreviewController.swift; sourceTree = ""; }; + D0C39EC4233A6077003CD402 /* GeneralBlockTextRowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralBlockTextRowItem.swift; sourceTree = ""; }; + D0CBB0F42492974F00620C65 /* VideoAvatarModalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAvatarModalController.swift; sourceTree = ""; }; + D0CC4ADD22BA5C930088F36D /* LottieBufferCompressor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieBufferCompressor.swift; sourceTree = ""; }; + D0CD3093221970F8005D23DD /* FFMpegRemuxer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FFMpegRemuxer.h; sourceTree = ""; }; + D0CD3094221970F8005D23DD /* FFMpegRemuxer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FFMpegRemuxer.m; sourceTree = ""; }; + D0CD309622197E62005D23DD /* FFMpegGlobals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FFMpegGlobals.h; sourceTree = ""; }; + D0CD309722197E62005D23DD /* FFMpegGlobals.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FFMpegGlobals.m; sourceTree = ""; }; + D0D087E1243DF4F100E05317 /* ChatlistFilterVisibilityItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatlistFilterVisibilityItem.swift; sourceTree = ""; }; + D0D3CE6B23D465FA00864F3C /* PollResultStickItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultStickItem.swift; sourceTree = ""; }; + D0D71385227ADE9400EC88B1 /* maccheck.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = maccheck.json; sourceTree = ""; }; + D0D7520824C03CD60037D73A /* VideoEditorThumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoEditorThumbs.swift; sourceTree = ""; }; + D0D7520A24C04FD60037D73A /* VideoEditorScrubbler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoEditorScrubbler.swift; sourceTree = ""; }; + D0D752C722F8A76100E2CB74 /* Geocoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geocoding.swift; sourceTree = ""; }; + D0D81AED243E2D6F00CB9D20 /* ChatListFilterFolderIconController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterFolderIconController.swift; sourceTree = ""; }; + D0D81AF7243F69AD00CB9D20 /* PollTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTimerView.swift; sourceTree = ""; }; + D0DD91FD246A8A380039D83D /* PeerMediaGifsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerMediaGifsController.swift; sourceTree = ""; }; + D0E52B3122FD66C4000C0306 /* MessageTimecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTimecode.swift; sourceTree = ""; }; + D0FCA7612434867400B72F18 /* PeerInfoHeadItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerInfoHeadItem.swift; sourceTree = ""; }; + D0FFC4AB23E184BA0044D305 /* ChatListFilterController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterController.swift; sourceTree = ""; }; + D0FFCEBD215A7CB700995AFE /* emoji14.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = emoji14.txt; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1159,10 +2070,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C232EAA41E1D07FE00C4D38C /* MtProtoKitMac.framework in Frameworks */, - C232EAA51E1D07FE00C4D38C /* PostboxMac.framework in Frameworks */, - C232EAA61E1D07FE00C4D38C /* SwiftSignalKitMac.framework in Frameworks */, - C232EAA71E1D07FE00C4D38C /* TelegramCoreMac.framework in Frameworks */, + A7D2822D236C51F10000A9BF /* OpenSSLEncryption.framework in Frameworks */, + A7D28229236C50FC0000A9BF /* TelegramCore.framework in Frameworks */, + A7D28228236C50F60000A9BF /* SyncCore.framework in Frameworks */, + A7D28227236C50F20000A9BF /* SwiftSignalKit.framework in Frameworks */, + A7D28226236C50EE0000A9BF /* Postbox.framework in Frameworks */, C232EAA81E1D07FE00C4D38C /* TGUIKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1171,26 +2083,49 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A7919135240D0869002011CA /* MurMurHash32.framework in Frameworks */, + A7918DB424093505002011CA /* GraphUI.framework in Frameworks */, + A7393D392407D0F100CE44CA /* GraphCore.framework in Frameworks */, + A71DC82B23858312000EEDE2 /* CoreSpotlight.framework in Frameworks */, + A74EB073237961BF005F55AE /* AppCenter.framework in Frameworks */, + A74EB072237961A1005F55AE /* AppCenterCrashes.framework in Frameworks */, + A7DF1B6C237415AD00ACC01F /* Zip.framework in Frameworks */, + A7ED5DAF236C7CE100040372 /* RLottie.framework in Frameworks */, + A7D28259236C70A50000A9BF /* SSignalKit.framework in Frameworks */, + A7D28232236C57DD0000A9BF /* libphonenumber.framework in Frameworks */, + A7D2822B236C51A50000A9BF /* OpenSSLEncryption.framework in Frameworks */, + A7D28211236C3D390000A9BF /* SyncCore.framework in Frameworks */, + A7D2820B236C3C1B0000A9BF /* MtProtoKit.framework in Frameworks */, + A7D28209236C3C150000A9BF /* TelegramCore.framework in Frameworks */, + A7D28207236C3C0F0000A9BF /* SwiftSignalKit.framework in Frameworks */, + A7D28205236C3C0B0000A9BF /* Postbox.framework in Frameworks */, + A7D08F48236AC9B6002DC240 /* Sparkle.framework in Frameworks */, + D0DDECEA2322FA0200E1B359 /* Contacts.framework in Frameworks */, + C259ED1E1DB956C1008E6712 /* QuartzCore.framework in Frameworks */, + C2FD33FB1E6C169C008D13D4 /* Security.framework in Frameworks */, + 9F18908F2237E95400665EF5 /* VideoToolbox.framework in Frameworks */, + 9F4EEF8421D4F59C002C3B33 /* Accelerate.framework in Frameworks */, + 9FFAE514205AB50C000C028E /* libz.tbd in Frameworks */, + 9FFAE50F205AB4A3000C028E /* libiconv.tbd in Frameworks */, + 9FFAE50A205AA1FB000C028E /* libavutil.a in Frameworks */, + 9FFAE50B205AA1FB000C028E /* libavcodec.a in Frameworks */, + 9FFAE50C205AA1FB000C028E /* libavformat.a in Frameworks */, + 9FFAE50D205AA1FB000C028E /* libswresample.a in Frameworks */, + 9F0E6F78203ED1380086699C /* AppKit.framework in Frameworks */, + C256A9141FB9CBF10043D497 /* LocalAuthentication.framework in Frameworks */, + C2EBBEA11FB5CA94009AD8ED /* CoreServices.framework in Frameworks */, C2A8E14B1F7D2690000FD5E3 /* CoreVideo.framework in Frameworks */, C2A8E1491F7D268B000FD5E3 /* CoreMedia.framework in Frameworks */, C21A48AE1F7CFBBE0095ADB1 /* OpenGL.framework in Frameworks */, - C276AFC91F74F2D200DEDD8E /* Sparkle.framework in Frameworks */, - C276AFCA1F74F2D200DEDD8E /* HockeySDK.framework in Frameworks */, C209C3881F262D48009231FE /* libstdc++.tbd in Frameworks */, C271EB971EB916870034792D /* libtgvoip.framework in Frameworks */, - C2C9B92D1E80166400380D79 /* Carbon.framework in Frameworks */, C2C9B92C1E80165D00380D79 /* IOKit.framework in Frameworks */, - C2FD33FB1E6C169C008D13D4 /* Security.framework in Frameworks */, C2FD33F01E697EC2008D13D4 /* QuickLook.framework in Frameworks */, + 9FC4DAA621DD1D6C003E2A62 /* libturbojpeg.a in Frameworks */, C24DAB931E0828B6005EE404 /* JavaScriptCore.framework in Frameworks */, - C2C738F11DD898DA00CE9D8A /* AVKit.framework in Frameworks */, - C2FBC1DF1DC61B580063A23B /* MtProtoKitMac.framework in Frameworks */, - C2FBC1D91DC61B050063A23B /* SwiftSignalKitMac.framework in Frameworks */, - C2FBC1D71DC61AFF0063A23B /* TelegramCoreMac.framework in Frameworks */, - C259ED1E1DB956C1008E6712 /* QuartzCore.framework in Frameworks */, + 9F21F65D20B5A9C900332C85 /* MapKit.framework in Frameworks */, C250B0351DB78A0A004E9FBE /* Quartz.framework in Frameworks */, C250B0331DB788B3004E9FBE /* Foundation.framework in Frameworks */, - C234CA911D97E117003023F7 /* PostboxMac.framework in Frameworks */, C253A9671D92E3AF00CDC850 /* AVFoundation.framework in Frameworks */, C2DF47BC1DE79574003AA6C0 /* libopus.a in Frameworks */, C22E06261D7F16C000A11C88 /* TGUIKit.framework in Frameworks */, @@ -1201,6 +2136,534 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9F0367E622707B6400456348 /* lottie */ = { + isa = PBXGroup; + children = ( + D0D71385227ADE9400EC88B1 /* maccheck.json */, + 9F03680C22771A9600456348 /* anim_archive.json */, + 9F03680622771A9600456348 /* anim_delete.json */, + 9F03680B22771A9600456348 /* anim_group.json */, + 9F03680822771A9600456348 /* anim_hide.json */, + 9F03680922771A9600456348 /* anim_mute.json */, + 9F03680E22771A9700456348 /* anim_pin.json */, + 9F03680522771A9600456348 /* anim_read.json */, + 9F03680222771A9600456348 /* anim_unarchive.json */, + 9F03680322771A9600456348 /* anim_ungroup.json */, + 9F03680D22771A9700456348 /* anim_unmute.json */, + 9F03680A22771A9600456348 /* anim_unpin.json */, + 9F03680722771A9600456348 /* anim_unread.json */, + 9F03680422771A9600456348 /* archiveAvatar.json */, + ); + path = lottie; + sourceTree = ""; + }; + 9F0AE6BA2199BBA400A8B53A /* media-player */ = { + isa = PBXGroup; + children = ( + 9F0AE6C12199CDB800A8B53A /* player */, + 9F0AE6BB2199BBB900A8B53A /* MediaPlayerView.swift */, + 9F0AE6BD2199BEBE00A8B53A /* SVideoController.swift */, + ); + name = "media-player"; + sourceTree = ""; + }; + 9F0AE6C12199CDB800A8B53A /* player */ = { + isa = PBXGroup; + children = ( + 9F0AE6BF2199CDB500A8B53A /* SVideoView.swift */, + ); + name = player; + sourceTree = ""; + }; + 9F10CE862060FFB7002DD61A /* secure-id */ = { + isa = PBXGroup; + children = ( + 9F10CE8720610127002DD61A /* PassportController.swift */, + 9F10CE8920611536002DD61A /* PassportHeaderItem.swift */, + 9F10CE8D20617C36002DD61A /* PassportInsertPasswordItem.swift */, + 9F10CE9720626B1B002DD61A /* PassportDocumentRowItem.swift */, + 9FC8AD9F2062D5E70094F7B4 /* PassportAcceptRowItem.swift */, + 9FC8ADA12062E2DF0094F7B4 /* PassportNewPhoneNumberRowItem.swift */, + 9FC8ADA32063B6450094F7B4 /* PassportTwoStepVerificationIntroItem.swift */, + 9F18DD92206D8FFD00A2AAD0 /* SecureIdVerificationDocument.swift */, + 9F18DD91206D8FFD00A2AAD0 /* SecureIdVerificationDocumentsContext.swift */, + 9FC8ADA5206925F60094F7B4 /* PassportWindowController.swift */, + C27BAC7920CFCE68007A7508 /* PassportSettingsHeaderItem.swift */, + ); + name = "secure-id"; + sourceTree = ""; + }; + 9F13EE152100AFDC00562E53 /* contacts */ = { + isa = PBXGroup; + children = ( + 9F13EE162100B05300562E53 /* VCardContactController.swift */, + 9F13EE1A2100BFCA00562E53 /* VCardHeaderItem.swift */, + 9F1962D72101458C00FFF048 /* VCardLocationRowItem.swift */, + ); + name = contacts; + sourceTree = ""; + }; + 9F1BABAC21E5EBFE0075C03E /* undo */ = { + isa = PBXGroup; + children = ( + 9F1BABAD21E5ECE70075C03E /* ChatUndoManager.swift */, + 9F1BABAF21E60DCC0075C03E /* UndoOverlayHeaderView.swift */, + ); + name = undo; + sourceTree = ""; + }; + 9F21F65320B5A41B00332C85 /* location */ = { + isa = PBXGroup; + children = ( + 9F21F65420B5A72800332C85 /* LocationModalController.swift */, + 9F1AE5E320B6D7AA002A9D8D /* LocationPlaceSuggestionRowItem.swift */, + 9F1AE5E520B70328002A9D8D /* LocationSendCurrentItem.swift */, + D0D752C722F8A76100E2CB74 /* Geocoding.swift */, + ); + name = location; + sourceTree = ""; + }; + 9F354E9A227062F3006F1D42 /* haptic */ = { + isa = PBXGroup; + children = ( + 9F354E9B2270630A006F1D42 /* HapticEngine.swift */, + ); + name = haptic; + sourceTree = ""; + }; + 9F3D5F6122044D3500CB0CAA /* updater */ = { + isa = PBXGroup; + children = ( + 9F3D5F6222044D8700CB0CAA /* AppUpdateViewController.swift */, + 9F3D5F8522085B2000CB0CAA /* UpdaterNotifySettings.swift */, + ); + name = updater; + sourceTree = ""; + }; + 9F3EAB3C20A5ED2F003FE7E3 /* ocr */ = { + isa = PBXGroup; + children = ( + 9F3EAB3D20A5ED2F003FE7E3 /* genann.h */, + 9F3EAB3E20A5ED2F003FE7E3 /* ocr.mm */, + 9F3EAB4020A5ED2F003FE7E3 /* fast-edge.h */, + 9F3EAB4120A5ED2F003FE7E3 /* ocr.h */, + 9F3EAB4220A5ED2F003FE7E3 /* genann.c */, + 9F3EAB4320A5ED2F003FE7E3 /* fast-edge.cpp */, + ); + path = ocr; + sourceTree = ""; + }; + 9F3EAB4820A5F90B003FE7E3 /* mrz */ = { + isa = PBXGroup; + children = ( + 9F3EAB4920A5F90B003FE7E3 /* TGPassportMRZ.h */, + 9F3EAB4A20A5F90B003FE7E3 /* TGPassportMRZ.m */, + ); + path = mrz; + sourceTree = ""; + }; + 9F62AE7F202D85CC007FB557 /* fetch */ = { + isa = PBXGroup; + children = ( + 9F62AE7D202D85B7007FB557 /* FetchManager.swift */, + 9F62AE80202D85E7007FB557 /* FetchManagerLocation.swift */, + 9F62AE82202D8759007FB557 /* FetchMediaUtils.swift */, + ); + name = fetch; + sourceTree = ""; + }; + 9F72973E20BD9C580067F815 /* video-player */ = { + isa = PBXGroup; + children = ( + 9F72973F20BD9C6A0067F815 /* VideoPlayer.swift */, + ); + name = "video-player"; + sourceTree = ""; + }; + 9F7943AE20854E1900FEDB81 /* proxy */ = { + isa = PBXGroup; + children = ( + 9F7943AF20854E2F00FEDB81 /* ProxyListRowItem.swift */, + 9F7943B120855DC200FEDB81 /* ProxyListController.swift */, + 9F0367F12272108800456348 /* ProxyQRCodeRowItem.swift */, + ); + name = proxy; + sourceTree = ""; + }; + 9F7B5FC922003D8C0087D020 /* wallpapers */ = { + isa = PBXGroup; + children = ( + 9F14CBF22007DEB300F22DA9 /* ChatWallpaperModalController.swift */, + 9F14CBF42007DFD400F22DA9 /* Wallpapers.swift */, + 9F1668B72007E3BC00DD39FB /* ThemeGridControllerItem.swift */, + 9F153D1321F0C7F800B95D82 /* WallpaperPreviewController.swift */, + 9F7B5FCA22003DC70087D020 /* WallpaperColorPicker.swift */, + 9F7B5FCC220099220087D020 /* WallpaperPatternPreview.swift */, + ); + name = wallpapers; + sourceTree = ""; + }; + 9F7D422822240235007B68BB /* account */ = { + isa = PBXGroup; + children = ( + 9F7D422922240403007B68BB /* AccountContext.swift */, + 9F7D422B222415C9007B68BB /* SharedAccountContext.swift */, + 9F7B74022227F492006610E4 /* ManageSharedAccountInfo.swift */, + 9F7B74042227F4F2006610E4 /* SharedAccountInfo.swift */, + 9F7B740E2229618E006610E4 /* SharedWakeupManager.swift */, + 9F7B741022296FD7006610E4 /* SharedNotificationManager.swift */, + 9FF5A1CA2232A2FF00BC1359 /* UpgradedAccount.swift */, + 9F1BC1A8223FDE6D00F21815 /* InputSources.swift */, + 9F0F8E81226DCD1C00A97F6A /* OpmizeDatabaseView.swift */, + A7393D432408F84300CE44CA /* DiceCache.swift */, + ); + name = account; + sourceTree = ""; + }; + 9F8FDED3205A7AD5001A7A77 /* ffmpeg */ = { + isa = PBXGroup; + children = ( + 9F8FDF36205A7AE7001A7A77 /* lib */, + 9F8FDED4205A7AE0001A7A77 /* include */, + ); + path = ffmpeg; + sourceTree = ""; + }; + 9F8FDED4205A7AE0001A7A77 /* include */ = { + isa = PBXGroup; + children = ( + 9F8FDED5205A7AE0001A7A77 /* libavutil */, + 9F8FDF1E205A7AE0001A7A77 /* libavformat */, + 9F8FDF22205A7AE0001A7A77 /* libavcodec */, + 9F8FDF33205A7AE0001A7A77 /* libswresample */, + ); + path = include; + sourceTree = ""; + }; + 9F8FDED5205A7AE0001A7A77 /* libavutil */ = { + isa = PBXGroup; + children = ( + 9F8FDED6205A7AE0001A7A77 /* hwcontext.h */, + 9F8FDED7205A7AE0001A7A77 /* time.h */, + 9F8FDED8205A7AE0001A7A77 /* hwcontext_cuda.h */, + 9F8FDED9205A7AE0001A7A77 /* intfloat.h */, + 9F8FDEDA205A7AE0001A7A77 /* error.h */, + 9F8FDEDB205A7AE0001A7A77 /* fifo.h */, + 9F8FDEDC205A7AE0001A7A77 /* blowfish.h */, + 9F8FDEDD205A7AE0001A7A77 /* replaygain.h */, + 9F8FDEDE205A7AE0001A7A77 /* version.h */, + 9F8FDEDF205A7AE0001A7A77 /* murmur3.h */, + 9F8FDEE0205A7AE0001A7A77 /* stereo3d.h */, + 9F8FDEE1205A7AE0001A7A77 /* samplefmt.h */, + 9F8FDEE2205A7AE0001A7A77 /* pixdesc.h */, + 9F8FDEE3205A7AE0001A7A77 /* base64.h */, + 9F8FDEE4205A7AE0001A7A77 /* rational.h */, + 9F8FDEE5205A7AE0001A7A77 /* sha.h */, + 9F8FDEE6205A7AE0001A7A77 /* motion_vector.h */, + 9F8FDEE7205A7AE0001A7A77 /* avconfig.h */, + 9F8FDEE8205A7AE0001A7A77 /* lfg.h */, + 9F8FDEE9205A7AE0001A7A77 /* avutil.h */, + 9F8FDEEA205A7AE0001A7A77 /* xtea.h */, + 9F8FDEEB205A7AE0001A7A77 /* crc.h */, + 9F8FDEEC205A7AE0001A7A77 /* hwcontext_vdpau.h */, + 9F8FDEED205A7AE0001A7A77 /* frame.h */, + 9F8FDEEE205A7AE0001A7A77 /* file.h */, + 9F8FDEEF205A7AE0001A7A77 /* md5.h */, + 9F8FDEF0205A7AE0001A7A77 /* cast5.h */, + 9F8FDEF1205A7AE0001A7A77 /* hwcontext_vaapi.h */, + 9F8FDEF2205A7AE0001A7A77 /* ffversion.h */, + 9F8FDEF3205A7AE0001A7A77 /* audio_fifo.h */, + 9F8FDEF4205A7AE0001A7A77 /* tree.h */, + 9F8FDEF5205A7AE0001A7A77 /* threadmessage.h */, + 9F8FDEF6205A7AE0001A7A77 /* attributes.h */, + 9F8FDEF7205A7AE0001A7A77 /* adler32.h */, + 9F8FDEF8205A7AE0001A7A77 /* timecode.h */, + 9F8FDEF9205A7AE0001A7A77 /* sha512.h */, + 9F8FDEFA205A7AE0001A7A77 /* hwcontext_dxva2.h */, + 9F8FDEFB205A7AE0001A7A77 /* display.h */, + 9F8FDEFC205A7AE0001A7A77 /* buffer.h */, + 9F8FDEFD205A7AE0001A7A77 /* camellia.h */, + 9F8FDEFE205A7AE0001A7A77 /* pixelutils.h */, + 9F8FDEFF205A7AE0001A7A77 /* common.h */, + 9F8FDF00205A7AE0001A7A77 /* hmac.h */, + 9F8FDF01205A7AE0001A7A77 /* eval.h */, + 9F8FDF02205A7AE0001A7A77 /* dict.h */, + 9F8FDF03205A7AE0001A7A77 /* random_seed.h */, + 9F8FDF04205A7AE0001A7A77 /* opt.h */, + 9F8FDF05205A7AE0001A7A77 /* mastering_display_metadata.h */, + 9F8FDF06205A7AE0001A7A77 /* log.h */, + 9F8FDF07205A7AE0001A7A77 /* aes.h */, + 9F8FDF08205A7AE0001A7A77 /* macros.h */, + 9F8FDF09205A7AE0001A7A77 /* bswap.h */, + 9F8FDF0A205A7AE0001A7A77 /* rc4.h */, + 9F8FDF0B205A7AE0001A7A77 /* tea.h */, + 9F8FDF0C205A7AE0001A7A77 /* cpu.h */, + 9F8FDF0D205A7AE0001A7A77 /* des.h */, + 9F8FDF0E205A7AE0001A7A77 /* channel_layout.h */, + 9F8FDF0F205A7AE0001A7A77 /* twofish.h */, + 9F8FDF10205A7AE0001A7A77 /* imgutils.h */, + 9F8FDF11205A7AE0001A7A77 /* mem.h */, + 9F8FDF12205A7AE0001A7A77 /* parseutils.h */, + 9F8FDF13205A7AE0001A7A77 /* ripemd.h */, + 9F8FDF14205A7AE0001A7A77 /* bprint.h */, + 9F8FDF15205A7AE0001A7A77 /* pixfmt.h */, + 9F8FDF16205A7AE0001A7A77 /* aes_ctr.h */, + 9F8FDF17205A7AE0001A7A77 /* timestamp.h */, + 9F8FDF18205A7AE0001A7A77 /* downmix_info.h */, + 9F8FDF19205A7AE0001A7A77 /* avassert.h */, + 9F8FDF1A205A7AE0001A7A77 /* hash.h */, + 9F8FDF1B205A7AE0001A7A77 /* mathematics.h */, + 9F8FDF1C205A7AE0001A7A77 /* intreadwrite.h */, + 9F8FDF1D205A7AE0001A7A77 /* avstring.h */, + ); + path = libavutil; + sourceTree = ""; + }; + 9F8FDF1E205A7AE0001A7A77 /* libavformat */ = { + isa = PBXGroup; + children = ( + 9F8FDF1F205A7AE0001A7A77 /* version.h */, + 9F8FDF20205A7AE0001A7A77 /* avio.h */, + 9F8FDF21205A7AE0001A7A77 /* avformat.h */, + ); + path = libavformat; + sourceTree = ""; + }; + 9F8FDF22205A7AE0001A7A77 /* libavcodec */ = { + isa = PBXGroup; + children = ( + 9F8FDF23205A7AE0001A7A77 /* avcodec.h */, + 9F8FDF24205A7AE0001A7A77 /* version.h */, + 9F8FDF25205A7AE0001A7A77 /* vdpau.h */, + 9F8FDF26205A7AE0001A7A77 /* qsv.h */, + 9F8FDF27205A7AE0001A7A77 /* vaapi.h */, + 9F8FDF28205A7AE0001A7A77 /* videotoolbox.h */, + 9F8FDF29205A7AE0001A7A77 /* xvmc.h */, + 9F8FDF2A205A7AE0001A7A77 /* d3d11va.h */, + 9F8FDF2B205A7AE0001A7A77 /* avfft.h */, + 9F8FDF2C205A7AE0001A7A77 /* vda.h */, + 9F8FDF2D205A7AE0001A7A77 /* jni.h */, + 9F8FDF2E205A7AE0001A7A77 /* dirac.h */, + 9F8FDF2F205A7AE0001A7A77 /* avdct.h */, + 9F8FDF30205A7AE0001A7A77 /* vorbis_parser.h */, + 9F8FDF31205A7AE0001A7A77 /* dxva2.h */, + 9F8FDF32205A7AE0001A7A77 /* dv_profile.h */, + ); + path = libavcodec; + sourceTree = ""; + }; + 9F8FDF33205A7AE0001A7A77 /* libswresample */ = { + isa = PBXGroup; + children = ( + 9F8FDF34205A7AE0001A7A77 /* version.h */, + 9F8FDF35205A7AE0001A7A77 /* swresample.h */, + ); + path = libswresample; + sourceTree = ""; + }; + 9F8FDF36205A7AE7001A7A77 /* lib */ = { + isa = PBXGroup; + children = ( + 9F8FDF37205A7AE7001A7A77 /* libavutil.a */, + 9F8FDF38205A7AE7001A7A77 /* libavcodec.a */, + 9F8FDF39205A7AE7001A7A77 /* libavformat.a */, + 9F8FDF3A205A7AE7001A7A77 /* libswresample.a */, + ); + path = lib; + sourceTree = ""; + }; + 9FA0E52720519DF8001E5649 /* Video Encoder */ = { + isa = PBXGroup; + children = ( + 9FA0E52820519E33001E5649 /* MP4Atom.h */, + 9FA0E52920519E33001E5649 /* MP4Atom.m */, + 9FA0E52B20519FCC001E5649 /* LiveUploadingHelper.h */, + 9FA0E52C20519FCC001E5649 /* LiveUploadingHelper.m */, + ); + path = "Video Encoder"; + sourceTree = ""; + }; + 9FC4DA9421DD0B21003E2A62 /* channel-members */ = { + isa = PBXGroup; + children = ( + 9FC4DA9521DD0B35003E2A62 /* PeerChannelMemberCategoriesContextsManager.swift */, + 9FC4DA9721DD0B86003E2A62 /* ChannelMemberCategoryListContext.swift */, + 9FC4DA9921DD0BE6003E2A62 /* CachedChannelAdmins.swift */, + 9FC4DAA721DE0626003E2A62 /* ChannelPermissionsController.swift */, + ); + name = "channel-members"; + sourceTree = ""; + }; + 9FC4DA9F21DD1D6C003E2A62 /* libjpeg-turbo */ = { + isa = PBXGroup; + children = ( + 9FC4DAA021DD1D6C003E2A62 /* jpeglib.h */, + 9FC4DAA121DD1D6C003E2A62 /* jerror.h */, + 9FC4DAA221DD1D6C003E2A62 /* libturbojpeg.a */, + 9FC4DAA321DD1D6C003E2A62 /* turbojpeg.h */, + 9FC4DAA421DD1D6C003E2A62 /* jconfig.h */, + 9FC4DAA521DD1D6C003E2A62 /* jmorecfg.h */, + ); + path = "libjpeg-turbo"; + sourceTree = ""; + }; + 9FDA713020E64541001ED8ED /* media */ = { + isa = PBXGroup; + children = ( + C296AF851D8DB178001DBB59 /* MediaUtils.swift */, + 9F72973320B878B00067F815 /* MapResources.swift */, + C2B6488F1EB25B1700BA574B /* MediaResources.swift */, + 9FDA713120E6456A001ED8ED /* ExternalMusicAlbumArtResources.swift */, + 9F4EEF8121D4F584002C3B33 /* ImageTransparency.swift */, + ); + name = media; + sourceTree = ""; + }; + 9FDA713720EA9101001ED8ED /* read-articles */ = { + isa = PBXGroup; + children = ( + ); + name = "read-articles"; + sourceTree = ""; + }; + 9FFAE4DB205A8C76000C028E /* modern */ = { + isa = PBXGroup; + children = ( + 9FFAE505205A928C000C028E /* RingByteBuffer.swift */, + 9FFAE503205A916B000C028E /* VideoPlayerProxy.swift */, + 9FFAE501205A9153000C028E /* ManagedAudioSession.swift */, + 9FFAE4E8205A8C86000C028E /* FFMpegAudioFrameDecoder.swift */, + 9FFAE4E7205A8C86000C028E /* FFMpegMediaFrameSource.swift */, + 9FFAE4E3205A8C84000C028E /* FFMpegMediaFrameSourceContext.swift */, + 9FFAE4E6205A8C85000C028E /* FFMpegMediaFrameSourceContextHelpers.swift */, + 9FFAE4DC205A8C80000C028E /* FFMpegMediaPassthroughVideoFrameDecoder.swift */, + 9FFAE4EC205A8C88000C028E /* FFMpegMediaVideoFrameDecoder.swift */, + 9FFAE4DE205A8C81000C028E /* MediaFrameSource.swift */, + 9FFAE4E9205A8C87000C028E /* MediaPlaybackData.swift */, + 9FFAE4EA205A8C87000C028E /* MediaPlayer.swift */, + 9FFAE4E2205A8C83000C028E /* MediaPlayerAudioRenderer.swift */, + 9FFAE4EB205A8C88000C028E /* MediaTrackDecodableFrame.swift */, + 9FFAE4E0205A8C82000C028E /* MediaTrackFrame.swift */, + 9FFAE4E1205A8C83000C028E /* MediaTrackFrameBuffer.swift */, + 9FFAE4E4205A8C84000C028E /* MediaTrackFrameDecoder.swift */, + 9F9B5EB022573E8A00728CDC /* FFMpegAVFormatContext.h */, + 9F9B5EB122573E8A00728CDC /* FFMpegAVFormatContext.m */, + 9F9B5EB322573FA700728CDC /* FFMpegAVIOContext.h */, + 9F9B5EB422573FA700728CDC /* FFMpegAVIOContext.m */, + 9F9B5EB62257402200728CDC /* FFMpegAVCodec.h */, + 9F9B5EB72257402200728CDC /* FFMpegAVCodec.m */, + 9F9B5EB92257408200728CDC /* FFMpegAVCodecContext.h */, + 9F9B5EBA2257408200728CDC /* FFMpegAVCodecContext.m */, + 9F9B5EBC225740F400728CDC /* FFMpegAVSampleFormat.h */, + 9F9B5EBD2257426500728CDC /* FFMpegAVFrame.h */, + 9F9B5EBE2257426500728CDC /* FFMpegAVFrame.m */, + 9F9B5EC02257443600728CDC /* FFMpegPacket.h */, + 9F9B5EC12257443600728CDC /* FFMpegPacket.m */, + 9FFAE4FE205A9041000C028E /* FFMpegSwResample.h */, + 9FFAE4FF205A9042000C028E /* FFMpegSwResample.m */, + D02F09FF22E8875800553411 /* SoftwareVideoSource.swift */, + D02F0A0122E88C6E00553411 /* MediaPlayerFramePreview.swift */, + D02F0A0322E88C9900553411 /* UniversalSoftwareVideoSource.swift */, + ); + name = modern; + sourceTree = ""; + }; + A71DC8332386D0C4000EEDE2 /* api-credentials */ = { + isa = PBXGroup; + children = ( + A71DC82E2386AADF000EEDE2 /* Signature.swift */, + C2BB2DAF1F8BDF6700520255 /* Config.swift */, + ); + name = "api-credentials"; + sourceTree = ""; + }; + A72D7AE723C4718C005BAC59 /* polls */ = { + isa = PBXGroup; + children = ( + A72D7AE823C471A7005BAC59 /* PollResultController.swift */, + D0D3CE6B23D465FA00864F3C /* PollResultStickItem.swift */, + ); + name = polls; + sourceTree = ""; + }; + A76C8AB124235F8900FDB071 /* modern */ = { + isa = PBXGroup; + children = ( + A76C8AB2242366EC00FDB071 /* PeerMediaBlockRowItem.swift */, + ); + name = modern; + sourceTree = ""; + }; + A778DC2C23C77ACB00DD307B /* sounds */ = { + isa = PBXGroup; + children = ( + A778DC3223C8988300DD307B /* quiz-correct.mp3 */, + A778DC3123C8988100DD307B /* quiz-incorrect.mp3 */, + A778DC2D23C77AD800DD307B /* confetti.mp3 */, + ); + path = sounds; + sourceTree = ""; + }; + A7831B1A2403CFD00056AEAC /* stats */ = { + isa = PBXGroup; + children = ( + A7831B1B2403CFDD0056AEAC /* ChannelStatsViewController.swift */, + A7831B2924040B6B0056AEAC /* StatisticRowItem.swift */, + A7393D452409044C00CE44CA /* ChannelOverviewStatsRowItem.swift */, + A742CE44241A517800C6B69B /* ChannelRecentPostRowItem.swift */, + A76C8AA324221F5400FDB071 /* StatisticsLoadingRowItem.swift */, + D0A2764F249CE588005E3C77 /* GroupsStatsController.swift */, + ); + name = stats; + sourceTree = ""; + }; + A789E08223E05E9A00AEB34A /* filters */ = { + isa = PBXGroup; + children = ( + A789E08323E05EAE00AEB34A /* ChatListFiltersListController.swift */, + D0FFC4AB23E184BA0044D305 /* ChatListFilterController.swift */, + A7919137240D1077002011CA /* ChatListFilterPredicate.swift */, + A7029EF7240E3A5400A89ABD /* ChatListFiltersHeaderItem.swift */, + A742CDCC240FB32F00C6B69B /* ChatListFilterRecommendedItem.swift */, + A76C8A9F2422132400FDB071 /* VerticalTabsView.swift */, + D0D087E1243DF4F100E05317 /* ChatlistFilterVisibilityItem.swift */, + D0D81AED243E2D6F00CB9D20 /* ChatListFilterFolderIconController.swift */, + ); + name = filters; + sourceTree = ""; + }; + A7B5030D23B62DF200C9838E /* svg */ = { + isa = PBXGroup; + children = ( + A7B5031223B62E1A00C9838E /* Svg.h */, + A7B5031123B62E1700C9838E /* Svg.m */, + A7B5030E23B62E0000C9838E /* nanosvg.c */, + A7B5030F23B62E0400C9838E /* nanosvg.h */, + ); + path = svg; + sourceTree = ""; + }; + A7C41DAF2358622F00CF9402 /* modern */ = { + isa = PBXGroup; + children = ( + A7C41DB3235862BB00CF9402 /* PeerMediaPhotosController.swift */, + A7C41DB523586DC100CF9402 /* PeerPhotosMonthItem.swift */, + ); + name = modern; + sourceTree = ""; + }; + A7F282FE2395166D00742C20 /* settings-search */ = { + isa = PBXGroup; + children = ( + A7F282FF2395168300742C20 /* SettingsSearchRecentQueries.swift */, + A7F283012395185B00742C20 /* SettingsSearchableItems.swift */, + A7F2830523954E1B00742C20 /* CachedFaqInstantPage.swift */, + A7F283092395570400742C20 /* SearchSettingsController.swift */, + A778DC4123CD9F3C00DD307B /* SearchSettingsEmptyItem.swift */, + ); + name = "settings-search"; + sourceTree = ""; + }; C2016F661EAE0A49003AF981 /* calls */ = { isa = PBXGroup; children = ( @@ -1210,6 +2673,8 @@ C2CFCABF1EBB9C8100843F6A /* CallAudioPlayer.swift */, C22A899E1EBCC2AA005B963D /* CallNavigationHeaderView.swift */, C2F4ED1A1EC5AE1D005F2696 /* CallRatingModalViewController.swift */, + D074A56424A1DE7700E92F8A /* OngoingCallContext.swift */, + D074A56624A1DF5200E92F8A /* VoipDerivedState.swift */, ); name = calls; sourceTree = ""; @@ -1217,6 +2682,9 @@ C20232A61D81D178007C9ADE /* chat */ = { isa = PBXGroup; children = ( + A72D7AE723C4718C005BAC59 /* polls */, + 9F13EE152100AFDC00562E53 /* contacts */, + C22467591FA8544200F03E27 /* photo-grouping */, C21656E61EE576650041A6BA /* popover */, C24616161ED33F850026D5BC /* instant-pip */, C2379D281DDCCBD00063AD30 /* bot */, @@ -1227,10 +2695,15 @@ C219E1E51D8AC83A0042F0C8 /* views */, C20232A71D81D189007C9ADE /* ChatController.swift */, C219E1DA1D8884290042F0C8 /* ChatHistoryEntry.swift */, + D05F392222FB45450040F341 /* ScheduledMessageModalController.swift */, C2256D971DAB9D5A00494CF4 /* ChatHistoryViewForLocation.swift */, C2D187F11E28D0840038961D /* ChatSwitchInlineController.swift */, C295C65E1F75808600BA309D /* ChatAdditionController.swift */, C2F952AF1F8E1C840056E586 /* CachedAdminIds.swift */, + 9F153D1521F3662700B95D82 /* StoredMessageFromSearchPeer.swift */, + D0B6D72922AA65D3008E36FE /* NewContactController.swift */, + D00C73B52302C196004B1E2B /* ChatScheduleController.swift */, + D0BDD50023A38660002010A5 /* InactiveChannelsController.swift */, ); name = chat; sourceTree = ""; @@ -1264,6 +2737,21 @@ name = emoji; sourceTree = ""; }; + C20CAD131FE436C600EFF8BF /* appearance */ = { + isa = PBXGroup; + children = ( + D02BD7C1232D14CC00D1814A /* items */, + D014AA272317DA0E00CE5362 /* themes */, + 9F7B5FC922003D8C0087D020 /* wallpapers */, + C20CAD141FE436E300EFF8BF /* SelectSizeRowItem.swift */, + 9F0B8F161FFB7F1A00073D3F /* AccentColorRowItem.swift */, + 9F17E5B8212F173900C25A65 /* AutoNightViewController.swift */, + D04D213E230D68B800609388 /* CustomAccentColorModalController.swift */, + D02BD7BF232D0FB800D1814A /* AppAppearanceViewController.swift */, + ); + name = appearance; + sourceTree = ""; + }; C210742A1E780CA8006EE5EF /* cache */ = { isa = PBXGroup; children = ( @@ -1275,7 +2763,6 @@ C21656E61EE576650041A6BA /* popover */ = { isa = PBXGroup; children = ( - C21656E71EE576F90041A6BA /* ChatUserPopover.swift */, ); name = popover; sourceTree = ""; @@ -1288,15 +2775,9 @@ C2167E521DC220F600F98E03 /* PeerMediaFileRowContent.swift */, C2167E561DC221E600F98E03 /* PeerMediaRowContent.swift */, C2E40A1A1E37ADAF0099AC7D /* PeerMediaEmptyRowItem.swift */, - ); - name = content; - sourceTree = ""; - }; - C2167E551DC2210600F98E03 /* content */ = { - isa = PBXGroup; - children = ( - C226743E1DC12E4E000BA9ED /* GridMessageItem.swift */, - C22674401DC13165000BA9ED /* GridHoleItem.swift */, + C23EEC881FCC47C1001371CD /* PeerMediaDateItem.swift */, + 9FDA713320E65D49001ED8ED /* PeerMediaPlayerAnimationView.swift */, + 9F127E14210B1F540080D709 /* PeerMediaVoiceRowItem.swift */, ); name = content; sourceTree = ""; @@ -1309,10 +2790,8 @@ C250B01D1DB67934004E9FBE /* webpages */, C296AF8D1D8EACD3001DBB59 /* content */, C20232AB1D81D1AE007C9ADE /* ChatMessageView.swift */, - C219E1D81D886A160042F0C8 /* ChatHoleRowView.swift */, C219E1E71D8AC90B0042F0C8 /* ChatUnreadRowView.swift */, C296AF7E1D8D38E5001DBB59 /* ChatRowView.swift */, - C253A92C1D8EE1A600CDC850 /* ChatMediaView.swift */, C253A9731D94182500CDC850 /* ChatRightView.swift */, C259ED1B1DB8DC78008E6712 /* ChatNavigateScroller.swift */, C2777B631DCFB559008B69DD /* ChatServiceRowView.swift */, @@ -1320,6 +2799,8 @@ C2A71CEC1DDB3C1000C69F73 /* ChatHeaderController.swift */, C2DF47E81DE892E3003AA6C0 /* ChatToasterView.swift */, C2E8694C1F43500D00BDD0A2 /* ChatNavigationMention.swift */, + A7377E1A23ACC79100AD3ADD /* ChatNavigateFailed.swift */, + A7565EAD23BE02AF0031EADE /* ChatGradientModel.swift */, ); name = views; sourceTree = ""; @@ -1344,20 +2825,44 @@ C26546CB1EA0AC3C00E3969A /* ChatVideoMessageItem.swift */, C2057FA91EBC6A3C000423DC /* ChatCallRowItem.swift */, C2E0646A1ECF137300387BB8 /* ChatInvoiceItem.swift */, + C224675C1FA884E300F03E27 /* ChatGroupedItem.swift */, + 9FDD78D021C8F0CC00F1B4EF /* ChatPollItem.swift */, + D00CE50E2289CE87008C1B4F /* ChatAnimatedStickerItem.swift */, + A7393D342407CAE100CE44CA /* ChatMediaDice.swift */, + D0D81AF7243F69AD00CB9D20 /* PollTimerView.swift */, ); name = items; sourceTree = ""; }; + C21BE3AD1FD0980200C1C849 /* developer */ = { + isa = PBXGroup; + children = ( + C21BE3AE1FD099AA00C1C849 /* DeveloperViewController.swift */, + C21BE3B01FD14CDB00C1C849 /* ParseAppearanceColors.swift */, + ); + name = developer; + sourceTree = ""; + }; C221ED521EA6849D00471C65 /* data-and-storage */ = { isa = PBXGroup; children = ( C221ED531EA684BE00471C65 /* DataAndStorageViewController.swift */, C221ED5B1EA69AA300471C65 /* StorageUsageController.swift */, C221ED5D1EA6B36600471C65 /* ChatStorageManagmentModalController.swift */, + 9FBE0EE0201FBEFC0060FD1C /* DownloadSettingsViewController.swift */, + 9F3EAB3A20A5A1EC003FE7E3 /* NetworkUsageStatsController.swift */, ); name = "data-and-storage"; sourceTree = ""; }; + C22467591FA8544200F03E27 /* photo-grouping */ = { + isa = PBXGroup; + children = ( + C224675A1FA8546200F03E27 /* GroupedLayout.swift */, + ); + name = "photo-grouping"; + sourceTree = ""; + }; C22552491F7BE6BB0007944D /* video-record */ = { isa = PBXGroup; children = ( @@ -1390,8 +2895,7 @@ C22674421DC2037C000BA9ED /* grid */ = { isa = PBXGroup; children = ( - C2167E551DC2210600F98E03 /* content */, - C22674391DC1273E000BA9ED /* PeerMediaGridController.swift */, + A7C41DAF2358622F00CF9402 /* modern */, ); name = grid; sourceTree = ""; @@ -1401,6 +2905,8 @@ children = ( C2167E541DC220F900F98E03 /* content */, C22674441DC20664000BA9ED /* PeerMediaListController.swift */, + D093474D242DCFC4000ECA88 /* PeerMediaGroupPeersController.swift */, + D0DD91FD246A8A380039D83D /* PeerMediaGifsController.swift */, ); name = list; sourceTree = ""; @@ -1411,6 +2917,7 @@ C2271D9B1DACC027001792B6 /* SearchController.swift */, C2271D9D1DACC796001792B6 /* ChatListMessageRowItem.swift */, C2271D9F1DACC7F7001792B6 /* ChatListMessageRowView.swift */, + 9FDA713E20EE2D49001ED8ED /* PopularPeersRowItem.swift */, ); name = search; sourceTree = ""; @@ -1418,6 +2925,7 @@ C2271DA81DAD867E001792B6 /* PeerInfo */ = { isa = PBXGroup; children = ( + A76C8AB124235F8900FDB071 /* modern */, C2AF3B801E5CD77800DFDD81 /* controllers */, C2271DC21DAE5E32001792B6 /* table */, C2271DA91DAD8716001792B6 /* PeerInfoController.swift */, @@ -1439,6 +2947,15 @@ C2271DB61DAE132C001792B6 /* GeneralInteractedRowView.swift */, C2FD38311DCA215F009DC28C /* GeneralInputRow.swift */, C2A5067A1DF5BE6900971A93 /* GeneralTextRowItem.swift */, + 9F10CE912061BE19002DD61A /* InputDataController.swift */, + 9F10CE932061C8C8002DD61A /* InputDataControllerEntries.swift */, + 9F10CE952061C98E002DD61A /* InputDataRowItem.swift */, + 9F10CE99206284F8002DD61A /* InputDataDateRowItem.swift */, + 9FC8AD992062A5610094F7B4 /* InputDataDataSelectorRowItem.swift */, + 9FC8AD9B2062AA630094F7B4 /* ValuesSelectorModalController.swift */, + D014193D22AE9A90008667CB /* GeneralLineSeparatorRowItem.swift */, + D0C39EC4233A6077003CD402 /* GeneralBlockTextRowItem.swift */, + D07C6D7C234698C600468B1A /* DynamicHeightRowItem.swift */, ); name = general; sourceTree = ""; @@ -1447,7 +2964,7 @@ isa = PBXGroup; children = ( C2271DBE1DAE563D001792B6 /* PeerInfoHeaderItem.swift */, - C2271DC31DAE5E46001792B6 /* PeerInfoHeaderView.swift */, + D0FCA7612434867400B72F18 /* PeerInfoHeadItem.swift */, ); name = table; sourceTree = ""; @@ -1455,6 +2972,9 @@ C2271DD01DAF6DD9001792B6 /* settings-controllers */ = { isa = PBXGroup; children = ( + 9F10CE862060FFB7002DD61A /* secure-id */, + C20CAD131FE436C600EFF8BF /* appearance */, + C21BE3AD1FD0980200C1C849 /* developer */, C275E9ED1F8FC99600D3D8C0 /* change-phone-number */, C2C5C2031EF8228C00AEA252 /* proxy */, C221ED521EA6849D00471C65 /* data-and-storage */, @@ -1465,9 +2985,8 @@ C2271DD11DAF6DF5001792B6 /* EmptyChatViewController.swift */, C2A71CE21DDB2EBD00C69F73 /* GeneralSettingsViewController.swift */, C2A71CE61DDB2F8700C69F73 /* UsernameSettingsViewController.swift */, - C2A71CE81DDB342100C69F73 /* NotificationSettingsViewController.swift */, - C24949111E5B704900D7ED5D /* AccountsListViewController.swift */, - C21177FF1F16BB8300AC706D /* BioViewController.swift */, + D01CBE2C22A5384600F6A971 /* NotificationPreferencesController.swift */, + A767DD4023F2BB3200366F76 /* ShortcutListController.swift */, ); name = "settings-controllers"; sourceTree = ""; @@ -1477,7 +2996,6 @@ children = ( C226743B1DC12742000BA9ED /* controllers */, C2271DD61DAF80D5001792B6 /* PeerMediaController.swift */, - C21AAE331DB0F6BC007638C5 /* MediaTitleBarView.swift */, C22674371DC125C1000BA9ED /* PeerMediaCollectionInterfaceState.swift */, ); name = "shared media"; @@ -1506,6 +3024,7 @@ C2FF145F1E532C0A007B7B14 /* SearchEmptyRowItem.swift */, C2016F3A1EAA4538003AF981 /* RecentPeerRowItem.swift */, C234D4111EEDE6990017DC25 /* LoadingTableItem.swift */, + A7C1377C23D1A62700803ED3 /* PeerEmptyHolderItem.swift */, ); name = "table-elements"; sourceTree = ""; @@ -1513,6 +3032,8 @@ C22B63531D967F3800085C19 /* external-objc */ = { isa = PBXGroup; children = ( + 9FB14FBD2098896200688EF9 /* EDSunriseSet.h */, + 9FB14FBE209889A500688EF9 /* EDSunriseSet.m */, C2B6488A1EB2533800BA574B /* TGGifConverter.h */, C2B6488B1EB2533800BA574B /* TGGifConverter.m */, C22B63541D967F4500085C19 /* TGInputTextTag.h */, @@ -1528,6 +3049,7 @@ C22B635A1D969D7E00085C19 /* input */ = { isa = PBXGroup; children = ( + 9F21F65320B5A41B00332C85 /* location */, C2777B5C1DCE48F4008B69DD /* panels */, C29B5F3F1DC7858100D13E65 /* context */, C22B635B1D96A14E00085C19 /* ChatInputView.swift */, @@ -1536,6 +3058,7 @@ C2B1A1261DA3D84900ACB1DD /* ChatInputAccessory.swift */, C26A719A1DC9FB3600F69385 /* InputPasteboardParser.swift */, C25911361DF1A68200671E72 /* ChatInputRecordingView.swift */, + D076F890229823DA004F895A /* ChannelDiscussionInputView.swift */, ); name = input; sourceTree = ""; @@ -1543,6 +3066,13 @@ C22E062F1D80439800A11C88 /* ui */ = { isa = PBXGroup; children = ( + D0CBB0F32492972A00620C65 /* Video-Avatar */, + 9F354E9A227062F3006F1D42 /* haptic */, + 9F3D5F6122044D3500CB0CAA /* updater */, + 9F0AE6BA2199BBA400A8B53A /* media-player */, + D071E89F21496D77001B6024 /* touchbar */, + 9F72973E20BD9C580067F815 /* video-player */, + C2A7592E1FB1C19E009FCF07 /* alert */, C22552491F7BE6BB0007944D /* video-record */, C235A47A1EFBDC6400D463FD /* appearance */, C205DB991EE800E9003711DF /* ui-controls */, @@ -1571,6 +3101,7 @@ C2271F551D9D468900424F7B /* table-elements */, C2303E881D9A767E00098E12 /* right */, C2303E871D9A766900098E12 /* left */, + 9F0368002277091800456348 /* LAnimationButton.swift */, ); name = ui; sourceTree = ""; @@ -1578,6 +3109,7 @@ C22E06301D8043A000A11C88 /* chatlist */ = { isa = PBXGroup; children = ( + A789E08223E05E9A00AEB34A /* filters */, C22E06311D8044CC00A11C88 /* ChatListController.swift */, C22E063C1D80929400A11C88 /* ChatListRowItem.swift */, C2CBCAC41D81649E00142EC0 /* ChatListRowView.swift */, @@ -1586,6 +3118,9 @@ C2A506761DF5664F00971A93 /* ForwardChatListController.swift */, C20B8F3F1DFD9999008A354E /* ChatListNothingItem.swift */, C2AC9C141E1E627F0085C7DE /* TabBadgeItem.swift */, + D0186730223807D200A77C45 /* ChatListEmptyRowItem.swift */, + 9F0367F62273260A00456348 /* UndoTooltipController.swift */, + A7C1379D23DF21EA00803ED3 /* ChatListRevealItem.swift */, ); name = chatlist; sourceTree = ""; @@ -1593,6 +3128,8 @@ C2303E871D9A766900098E12 /* left */ = { isa = PBXGroup; children = ( + D001E972243B19C4009025F9 /* folders-sidebar */, + A7F282FE2395166D00742C20 /* settings-search */, C2FB2FA91EBF73AE0093C8BA /* calls */, C230B8ED1DD335780057F596 /* account */, C2271D9A1DACC011001792B6 /* search */, @@ -1607,12 +3144,14 @@ C2303E881D9A767E00098E12 /* right */ = { isa = PBXGroup; children = ( + D0A2764C249C9C8E005E3C77 /* user-photos */, + 9F1BABAC21E5EBFE0075C03E /* undo */, + 9FC4DA9421DD0B21003E2A62 /* channel-members */, C2423A521F2234EA0041907F /* instantpage */, C29B5F4D1DC8F37100D13E65 /* navigation actions */, C2271DD01DAF6DD9001792B6 /* settings-controllers */, C2271DA81DAD867E001792B6 /* PeerInfo */, C20232A61D81D178007C9ADE /* chat */, - C29670781F0FAAC800884DA2 /* AppearanceViewController.swift */, ); name = right; sourceTree = ""; @@ -1620,8 +3159,13 @@ C230B8ED1DD335780057F596 /* account */ = { isa = PBXGroup; children = ( + D07B558023338A1400F076A8 /* wallet */, C230B8EE1DD3358C0057F596 /* AccountViewController.swift */, C230B8F01DD348970057F596 /* AccountInfoItem.swift */, + 9F8DF3C7209228B000AED104 /* EditAccountInfoController.swift */, + 9F12D342209251CF0072928B /* EditAccountInfoItem.swift */, + 9F77B3A5221C1DAC003B65B8 /* LogoutViewController.swift */, + A7F283032395289B00742C20 /* AccountUtils.swift */, ); name = account; sourceTree = ""; @@ -1672,6 +3216,13 @@ children = ( C230B9211DD4C24E0057F596 /* GIFPlayerView.swift */, C2DF47951DE71160003AA6C0 /* GIFContainerView.swift */, + D01E1F0322B39A4800AD6DAE /* LottiePlayer.swift */, + D0CC4ADD22BA5C930088F36D /* LottieBufferCompressor.swift */, + A7C7215A23FD4BAA00CE3F75 /* LottieLocalAnimations.swift */, + D06C7BB4247D6F4B00E67C3C /* SoftwareVideoLayerFrameManager.swift */, + D06C7BB6247D6FA900E67C3C /* SampleBufferPool.swift */, + D06C7BB8247E664900E67C3C /* SoftwareVideoThumbnailLayer.swift */, + D06C7BBA247FAE3E00E67C3C /* GifPlayerBufferView.swift */, ); name = video; sourceTree = ""; @@ -1752,6 +3303,9 @@ isa = PBXGroup; children = ( C2B9BE881EFC5E7000D6B96F /* Appearance.swift */, + D04D2143230DB55B00609388 /* TelegramIconsTheme.swift */, + A778DC2923C75F1100DD307B /* Confetti.swift */, + A778DC2F23C8985300DD307B /* SoundEffects.swift */, ); name = appearance; sourceTree = ""; @@ -1783,6 +3337,7 @@ C2423A521F2234EA0041907F /* instantpage */ = { isa = PBXGroup; children = ( + 9FDA713720EA9101001ED8ED /* read-articles */, C2423A531F2235080041907F /* InstantPageViewController.swift */, C2DE5D271F3CA5FE0081EC1E /* InstantPageItem.swift */, C2DE5D291F3CA69D0081EC1E /* InstantPageMedia.swift */, @@ -1806,6 +3361,18 @@ C29F4C771F45FBFF00DBFC00 /* InstantPageSlideshowItem.swift */, C29F4C7C1F47283600DBFC00 /* InstantPageBrowser.swift */, C2A87DE71F4C6910002D3F73 /* InstantViewWindow.swift */, + C2905E1B207E4D9E00990AD7 /* InstantPageAudioView.swift */, + C2905E1D207E545600990AD7 /* InstantPageAudioItem.swift */, + 9F21A7CE21C1552D0037784F /* InstantPageTheme.swift */, + 9F21A7D221C167000037784F /* InstantPageImageItem.swift */, + 9F21A7D421C16CB90037784F /* InstantPagePeerReferenceItem.swift */, + 9F21A7DB21C290E00037784F /* InstantPageTableItem.swift */, + 9F1C279221D38A96003CD033 /* InstantPageScrollableItem.swift */, + 9F4EEF7D21D3C3E3002C3B33 /* InstantPageDetailsItem.swift */, + 9F4EEF7F21D3C76E002C3B33 /* InstantPageContentView.swift */, + 9F4EEF8521D4FA68002C3B33 /* InstantPageArticleItem.swift */, + 9F4EEF8721D515C5002C3B33 /* InstantPageStoredState.swift */, + A7F2830723954EF800742C20 /* CachedInstantPages.swift */, ); name = instantpage; sourceTree = ""; @@ -1823,6 +3390,8 @@ children = ( C248BD251E706A05004B9106 /* RecentSessionsController.swift */, C248BD281E706DDA004B9106 /* RecentSessionRowItem.swift */, + 9FA0E53C205693DA001E5649 /* WebSessionsController.swift */, + 9FA0E53E2056E159001E5649 /* WebAuthorizationRowItem.swift */, ); name = sessions; sourceTree = ""; @@ -1831,6 +3400,7 @@ isa = PBXGroup; children = ( C24D9FC31E24FFF3002CD3F3 /* PasscodeLockController.swift */, + 9F147F6E223014EB00D71BD1 /* PasscodeControllers.swift */, ); name = passcode; sourceTree = ""; @@ -1846,11 +3416,12 @@ C24D9FC81E250328002CD3F3 /* privacy */ = { isa = PBXGroup; children = ( + C24D9FC91E25033E002CD3F3 /* PrivacyAndSecurityViewController.swift */, + 9F7943AE20854E1900FEDB81 /* proxy */, C2F9C4461F94FFC0002B2CBF /* two-step-verification */, C2E52A0B1EB8C367009AF87D /* selective-privacy */, C248BD271E706DBC004B9106 /* sessions */, C24D9FC51E250094002CD3F3 /* passcode */, - C24D9FC91E25033E002CD3F3 /* PrivacyAndSecurityViewController.swift */, C248BD231E706104004B9106 /* BlockedPeersViewController.swift */, ); name = privacy; @@ -1859,8 +3430,12 @@ C24D9FDA1E267550002CD3F3 /* preview-sender */ = { isa = PBXGroup; children = ( + D0BEB996216BD5F90055B718 /* editor */, C2FD382D1DCA1FA3009DC28C /* PreviewSenderController.swift */, - C24D9FDB1E267932002CD3F3 /* PreviewSenderItems.swift */, + C23044821F98F8B400977C51 /* MediaPreviewRowItem.swift */, + C246D6271FAB72D4004C17FA /* MediaGroupPreviewRowItem.swift */, + D0BEB999216D10920055B718 /* MediaPreviewEditControl.swift */, + D0675D83217F1F27004900A7 /* ArchiverContext.swift */, ); name = "preview-sender"; sourceTree = ""; @@ -1908,9 +3483,18 @@ C24DAB971E082FDF005EE404 /* objc */ = { isa = PBXGroup; children = ( + 9F291CA02264E57F00C66267 /* BuildConfig.h */, + 9F291C9F2264E57F00C66267 /* BuildConfig.m */, + D0CD3093221970F8005D23DD /* FFMpegRemuxer.h */, + D0CD3094221970F8005D23DD /* FFMpegRemuxer.m */, + 9F3EAB4820A5F90B003FE7E3 /* mrz */, + 9F3EAB3C20A5ED2F003FE7E3 /* ocr */, + 9FA0E53A2052EDFF001E5649 /* HackUtils.h */, + 9FA0E5392052EDFE001E5649 /* HackUtils.m */, + C24102661FD587C400DB8625 /* RHARCSupport.h */, + C24102631FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.h */, + C24102641FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.m */, C2A8E1451F7D2602000FD5E3 /* opengl */, - C29F4C741F45F58B00DBFC00 /* MIHSliderView.h */, - C29F4C751F45F58B00DBFC00 /* MIHSliderView.m */, C21B24661EDB116B00FC6CDA /* NumberPluralizationForm.h */, C21B24671EDB116B00FC6CDA /* NumberPluralizationForm.m */, C26E82CF1E83EFFE0046DF2F /* TimeObserver.h */, @@ -1919,12 +3503,14 @@ C24DAB951E082971005EE404 /* youtube */, C25253281DF03F9600ADBC98 /* TGAudioWaveform.h */, C25253291DF03F9600ADBC98 /* TGAudioWaveform.m */, - C25253251DF03F5700ADBC98 /* TGOpusAudioRecorder.h */, - C25253261DF03F5700ADBC98 /* TGOpusAudioRecorder.m */, C2DF47CC1DE79751003AA6C0 /* ATQueue.h */, C2DF47CD1DE79751003AA6C0 /* ATQueue.m */, C2DF47C91DE79719003AA6C0 /* TGDataItem.h */, C2DF47CA1DE79719003AA6C0 /* TGDataItem.m */, + D0CD309622197E62005D23DD /* FFMpegGlobals.h */, + D0CD309722197E62005D23DD /* FFMpegGlobals.m */, + D09D9DF2229C289F00378796 /* GZip.h */, + D09D9DF3229C289F00378796 /* GZip.m */, ); path = objc; sourceTree = ""; @@ -1966,9 +3552,9 @@ C2203EA11DDE2AB8001E6AB6 /* ChatSelectText.swift */, C29C3E721E4397F300193A7E /* StickerPreviewHandler.swift */, C250BA8E1E6E1CDC0057CD96 /* ChatMessageThrottledProcessingManager.swift */, - C28149871EA7F22200BB933E /* PreparedChatHistoryViewTransition.swift */, C28149891EA7F44300BB933E /* ListViewIntermediateState.swift */, - C2084F031F5D5C6F004713C4 /* ChatReplyPreviewController.swift */, + C241025C1FD5702D00DB8625 /* ChatMessageBubbleImages.swift */, + C251FB4B1FEDCC750035E5D7 /* ChatPresentationUtils.swift */, ); name = utils; sourceTree = ""; @@ -1981,13 +3567,26 @@ name = "quick-look"; sourceTree = ""; }; + C251FB511FEE300E0035E5D7 /* Products */ = { + isa = PBXGroup; + name = Products; + sourceTree = ""; + }; C252532B1DF043D800ADBC98 /* resources */ = { isa = PBXGroup; children = ( + A778DC2C23C77ACB00DD307B /* sounds */, + D07450DB233D617B00769D7F /* tgs */, + 9F0367E622707B6400456348 /* lottie */, + 9F0AE6B42199904400A8B53A /* sound_a.caf */, + 9F3EAB3F20A5ED2F003FE7E3 /* ocr_nn.bin */, + 9F1668C52008A97000DD39FB /* builtin-wallpaper-0.jpg */, + C2786FF21FEBF088001FB044 /* palettes */, C23D0D781F1A609300AF5151 /* SFCompactRounded-Semibold.otf */, C27A6F721ECF610C00C65577 /* currencies.json */, C2CFCAB81EBB4A1600843F6A /* voip */, C23BC3671E9AA03E00D79F92 /* emoji11.txt */, + D0FFCEBD215A7CB700995AFE /* emoji14.txt */, C21074231E77F5DF006EE5EF /* dsa_pub_prod.pem */, C232EA3C1E1C06EF00C4D38C /* dsa_pub.pem */, C252532C1DF0440300ADBC98 /* begin_record.caf */, @@ -2000,6 +3599,9 @@ C21074261E77F7A3006EE5EF /* Release.xcconfig */, C21A48B11F7D0D3F0095ADB1 /* VideoMessage.fsh */, C21A48B31F7D0D6B0095ADB1 /* VideoMessage.vsh */, + 9FC8ADA7206A77E00094F7B4 /* countries */, + 9FDE0A8E21AD41C2001546D7 /* emoji1014-1.txt */, + D01E1EFF22B261A800AD6DAE /* Alpha.xcconfig */, ); name = resources; sourceTree = ""; @@ -2007,6 +3609,10 @@ C253A9531D9164CE00CDC850 /* thrid-party */ = { isa = PBXGroup; children = ( + A7B5030D23B62DF200C9838E /* svg */, + 9FC4DA9F21DD1D6C003E2A62 /* libjpeg-turbo */, + 9F8FDED3205A7AD5001A7A77 /* ffmpeg */, + 9FA0E52720519DF8001E5649 /* Video Encoder */, C209C36E1F262520009231FE /* emoji */, C2C9B90B1E800F0C00380D79 /* media-key-tap */, C2FD33F31E6C1486008D13D4 /* sskeychain */, @@ -2059,6 +3665,7 @@ C253E22F1DE34FA20022A29F /* audio */ = { isa = PBXGroup; children = ( + 9FFAE4DB205A8C76000C028E /* modern */, C253E2301DE34FBB0022A29F /* AudioPlayerController.swift */, C253E2351DE398580022A29F /* AudioPlayer.swift */, C253E2371DE398980022A29F /* NativeAudioPlayer.swift */, @@ -2073,6 +3680,7 @@ isa = PBXGroup; children = ( C253E2331DE3776A0022A29F /* InlineAudioPlayerView.swift */, + C2CE43E320E2CFE700656543 /* PlayerListController.swift */, ); name = "inline-audio-player"; sourceTree = ""; @@ -2092,6 +3700,7 @@ isa = PBXGroup; children = ( C25F71471E410DEE0046AF4E /* InAppNotificationSettings.swift */, + 9F4EC947218B459A002B3C56 /* RenderedTotalUnreadCount.swift */, C25F71491E4110C50046AF4E /* ApplicationSpecificPreferencesKeys.swift */, C2FD34141E6C9003008D13D4 /* BaseApplicationSettings.swift */, C221ED551EA6877300471C65 /* GeneratedMediaStoreSettings.swift */, @@ -2100,6 +3709,16 @@ C296707A1F0FBFB500884DA2 /* ThemeSettings.swift */, C24FD40F1F20FBFB00A97196 /* RecentUsedEmoji.swift */, C29E0EDF1F4DC43100C0C7A8 /* InstantViewAppearance.swift */, + C256A9151FB9E1490043D497 /* AdditionalSettings.swift */, + 9FDA713A20EA9532001ED8ED /* ReadArticlesListPreferences.swift */, + 9F17E5BA212F191F00C25A65 /* AutoNightThemePreferences.swift */, + 9FF32C7B21B7DF4800BF58B6 /* StickerSettings.swift */, + 9F77B3972211979B003B65B8 /* AutoplayPreferences.swift */, + 9F1890922238F3DC00665EF5 /* DownloadedFilesPaths.swift */, + D07450F02340DC8200769D7F /* WalletConfiguration.swift */, + A766493E236D6BFD00163DF4 /* PasscodeSettings.swift */, + A7F282B1238D122900742C20 /* UnauthorizedConfiguration.swift */, + A7C1379123DB00D900803ED3 /* ChatListFilterPreferences.swift */, ); name = "inapp-settings"; sourceTree = ""; @@ -2127,12 +3746,13 @@ C271EB981EB9DEDA0034792D /* calls */ = { isa = PBXGroup; children = ( + D074A56924A1EB0D00E92F8A /* webrtc */, C271EBA01EB9F04E0034792D /* TGCallUtils.h */, C271EBA11EB9F04E0034792D /* TGCallUtils.mm */, - C271EB991EB9DEF00034792D /* CallBridge.h */, - C271EB9A1EB9DEF00034792D /* CallBridge.mm */, - C271EBE71EBA22FE0034792D /* TGCallConnectionDescription.h */, - C271EBE81EBA22FE0034792D /* TGCallConnectionDescription.m */, + C271EB991EB9DEF00034792D /* OngoingCallThreadLocalContext.h */, + C271EB9A1EB9DEF00034792D /* OngoingCallThreadLocalContext.mm */, + C271EBE71EBA22FE0034792D /* OngoingCallConnectionDescription.h */, + C271EBE81EBA22FE0034792D /* OngoingCallConnectionDescription.m */, C2AF011B1F03D4C600D8AC1D /* TGCallAesCtr.h */, C2AF011C1F03D4C600D8AC1D /* TGCallAesCtr.m */, ); @@ -2153,6 +3773,7 @@ C275E9EE1F8FCA4200D3D8C0 /* PhoneNumberIntroController.swift */, C275E9F41F8FEDEB00D3D8C0 /* PhoneNumberConfirmController.swift */, C275E9F61F90CDF900D3D8C0 /* PhoneNumberInputCodeController.swift */, + 9F9206EF20727AF30054E581 /* ChangePhoneNumberContainerView.swift */, ); name = "change-phone-number"; sourceTree = ""; @@ -2177,16 +3798,16 @@ isa = PBXGroup; children = ( C29B5F441DC7DA4B00D13E65 /* MessageActionsPanelView.swift */, + C2C415E41FA33D1A00FF36F4 /* InputFormatterPopover.swift */, ); name = panels; sourceTree = ""; }; - C279824C1E72C83500262BFD /* legacy */ = { + C2786FF21FEBF088001FB044 /* palettes */ = { isa = PBXGroup; children = ( - C2FD34121E6C2503008D13D4 /* LegacyImportAuthorization.swift */, ); - name = legacy; + name = palettes; sourceTree = ""; }; C27A8E971EC9D45C00C8E7E7 /* quick-switcher */ = { @@ -2208,6 +3829,7 @@ C2844AD51DA90775009308DC /* entertainment */ = { isa = PBXGroup; children = ( + D070DB7F22D36359008A0BBE /* modern-stickers */, C2F93A2B1F3C550A00BCD48F /* tolerance */, C22674361DC0E270000BA9ED /* table */, C2844ADA1DA907F7009308DC /* gifs */, @@ -2234,11 +3856,6 @@ C2844AD91DA907F2009308DC /* stickers */ = { isa = PBXGroup; children = ( - C2271F391DB4D0540045E719 /* EStickersViewController.swift */, - C22674201DBCECCC000BA9ED /* EStickerGridItem.swift */, - C226741E1DBCEAC2000BA9ED /* EStickerGridEntries.swift */, - C22674321DBF665A000BA9ED /* EStickerPackEntries.swift */, - C22674341DBF6A85000BA9ED /* EStickerPackItem.swift */, ); name = stickers; sourceTree = ""; @@ -2248,6 +3865,7 @@ children = ( C2271F3B1DB4D0630045E719 /* GIFViewController.swift */, C2B6A9641E8519FA00A441B7 /* RecentGIFRowItem.swift */, + D076A07D248A5F890077BC0A /* GifPanelTabRowItem.swift */, ); name = gifs; sourceTree = ""; @@ -2273,6 +3891,8 @@ C22EBCB91DFAB64F0034C435 /* ChatMapContentView.swift */, C248BD201E6F09CC004B9106 /* ChatGameContentView.swift */, C21095991E9FE04700E10BDB /* ChatVideoMessageContentView.swift */, + D00CE50C2289C9B7008C1B4F /* MediaAnimatedStickerView.swift */, + A7393D362407CD7A00CE44CA /* ChatDiceContentView.swift */, ); name = content; sourceTree = ""; @@ -2294,6 +3914,9 @@ C23B3AEA1E338ADC009C162C /* ContextMediaRowItem.swift */, C29C3E6E1E4352C100193A7E /* ContextStickerRowItem.swift */, C24FD40D1F20EE8B00A97196 /* ContextClueRowItem.swift */, + C2E6F3CE1F9F85260023653D /* ContextHashtagRowItem.swift */, + 9F0AE6862191D29D00A8B53A /* ContextSearchMessageItem.swift */, + A7F2831A239A496400742C20 /* ContextShowPeersHolder.swift */, ); name = context; sourceTree = ""; @@ -2331,7 +3954,9 @@ C2A506741DF438B900971A93 /* AudioWaveformView.swift */, C24D9F901E1F8F85002CD3F3 /* MajorBackNavigationBar.swift */, C209C38C1F276D4C009231FE /* ChatSearchView.swift */, - C25BB1681F867FEE0089ED02 /* ChatVideoAccessoryView.swift */, + C25BB1681F867FEE0089ED02 /* ChatMessageAccessoryView.swift */, + C20CAD111FE291E200EFF8BF /* ChatBubbleAccessoryForward.swift */, + 9F580BE420A0AA7B00F6D56C /* ChatRecorderOverlayWindow.swift */, ); name = elements; sourceTree = ""; @@ -2344,6 +3969,13 @@ name = views; sourceTree = ""; }; + C2A7592E1FB1C19E009FCF07 /* alert */ = { + isa = PBXGroup; + children = ( + ); + name = alert; + sourceTree = ""; + }; C2A8E1451F7D2602000FD5E3 /* opengl */ = { isa = PBXGroup; children = ( @@ -2368,21 +4000,23 @@ C2AF3B801E5CD77800DFDD81 /* controllers */ = { isa = PBXGroup; children = ( + A7831B1A2403CFD00056AEAC /* stats */, + D076F88B2296FCE5004F895A /* discussion */, C29340EF1F506BC80074991E /* groupstickers */, C230BEB81EE9AE5B0029586C /* logs */, - C2AF3B811E5CD79200DFDD81 /* ConvertGroupViewController.swift */, C26A37EB1E5DE464006977AC /* ChannelAdminsViewController.swift */, - C26A37ED1E5DE48F006977AC /* ChannelBlacklistViewController.swift */, + C26A37ED1E5DE48F006977AC /* ChannelBlocklistViewController.swift */, C2B1B11D1E5F151D00895E0D /* ChannelVisibilityController.swift */, C20CB7281E60886E00C992AC /* LinkInvationController.swift */, C22EE61D1E67506800334C38 /* ChannelMembersViewController.swift */, C2FD33E81E696A86008D13D4 /* GroupsInCommonViewController.swift */, - C2538E511E770B4600B21DF0 /* GroupAdminsController.swift */, C21795E21E795955006A2AA3 /* SecretChatKeyViewController.swift */, C205DB971EE71127003711DF /* ChannelAdminController.swift */, C230BEB31EE97B6F0029586C /* RestictedModalViewController.swift */, C258D1B31F8D385700458478 /* PreHistorySettingsController.swift */, C258D1B51F8D3A0D00458478 /* PreHistoryControllerStructures.swift */, + 9F7D421E22203DB1007B68BB /* ChannelStatisticsController.swift */, + D01C731622A9814C000DA008 /* InputPasswordController.swift */, ); name = controllers; sourceTree = ""; @@ -2412,6 +4046,7 @@ C2B1A1121D9FD2AE00ACB1DD /* ChatPresentationInterfaceState.swift */, C253E2391DE4D3DB0022A29F /* ChatInterfaceInputContext.swift */, C253E23B1DE4D4080022A29F /* ChatInterfaceStateContextQueries.swift */, + 9FC4DA9B21DD187C003E2A62 /* SearchPeerMembers.swift */, ); name = interface; sourceTree = ""; @@ -2442,11 +4077,12 @@ C2B1A1341DA6581500ACB1DD /* gallery */ = { isa = PBXGroup; children = ( + C2E8BA081FB5F13600DEB5E2 /* thumbs */, C26505911E02FC01001954DC /* items */, C2A1054A1E0163D500B01F48 /* GalleryPageController.swift */, + 9F6B54C721369B4000748FC1 /* GalleryModernControls.swift */, C2B1A1351DA6587100ACB1DD /* GalleryViewer.swift */, - C2412E061DA795D200588C14 /* GalleryControls.swift */, - C2EA53461F751EF300C183F7 /* GalleryMessageEntry.swift */, + A7C7215623FD45D300CE3F75 /* SaveModalController.swift */, ); name = gallery; sourceTree = ""; @@ -2471,6 +4107,7 @@ isa = PBXGroup; children = ( C2BB12091ED87C5A00BDE46A /* ControllerExtension.swift */, + 9F1668C72008F30900DD39FB /* ChatBackgroundView.swift */, ); name = "parent-controllers"; sourceTree = ""; @@ -2478,7 +4115,6 @@ C2C5C2031EF8228C00AEA252 /* proxy */ = { isa = PBXGroup; children = ( - C2C5C2041EF822B900AEA252 /* ProxySettingsViewController.swift */, C2AF01121F01543200D8AC1D /* ExportProxyModalController.swift */, ); name = proxy; @@ -2507,6 +4143,11 @@ C2CBCABE1D81526A00142EC0 /* utils */ = { isa = PBXGroup; children = ( + D08E5C9722C583CE007B1C09 /* Tuple.swift */, + 9FFAE507205A92B9000C028E /* RingBuffer.h */, + 9FFAE508205A92B9000C028E /* RingBuffer.m */, + 9F0E6F79203EFE870086699C /* Preferences.swift */, + 9F62AE7F202D85CC007FB557 /* fetch */, C234A7F91ED7104500EBBECE /* localization */, C271EB981EB9DEDA0034792D /* calls */, C210742A1E780CA8006EE5EF /* cache */, @@ -2522,7 +4163,6 @@ C253A9431D90303200CDC850 /* FastBlur.h */, C253A9441D90303200CDC850 /* FastBlur.m */, C2CBCABF1D81528700142EC0 /* System.swift */, - C296AF851D8DB178001DBB59 /* MediaUtils.swift */, C253A94A1D9032A000CDC850 /* Telegram-Mac-Bridging-Header.h */, C276248A1D95AF7600FE5B2B /* ObjcUtils.h */, C276248B1D95AF7600FE5B2B /* ObjcUtils.m */, @@ -2550,9 +4190,21 @@ C2C98FEE1E818FB5009CBDB7 /* ClearUserNotifies.swift */, C25C13321E8A433700AE26A1 /* TableUtils.swift */, C2B6488D1EB25A5300BA574B /* FetchVideoMediaResource.swift */, - C2B6488F1EB25B1700BA574B /* MediaResources.swift */, C21B24621EDADC8600FC6CDA /* MMMenuItem.swift */, C2F9C44B1F95FE58002B2CBF /* Markdown.swift */, + C241026E1FD58EA800DB8625 /* SImageView.swift */, + 9F9483B0202AF816006E873D /* CrashHandler.swift */, + 9FA0E5332051A41A001E5649 /* PreUploadManager.swift */, + 9F52F5192130286E006FC0B5 /* LocationRequest.swift */, + 9FC4DA9D21DD1C6C003E2A62 /* ImageCompression.swift */, + 9F147F7B2231543800D71BD1 /* PeerUtils.swift */, + 9FF1DEA5225B699D009512C9 /* SearchUtils.swift */, + 9F0367EF227208E000456348 /* QRCode.swift */, + D09D9DF0229C27A700378796 /* AnimatedStickerUtils.swift */, + D0276B7C22BD6511003155D8 /* DisplayLink.swift */, + D0E52B3122FD66C4000C0306 /* MessageTimecode.swift */, + A7D2822E236C549B0000A9BF /* SyncCoreExtension.swift */, + A7D28236236C5B2C0000A9BF /* PhoneNumberUtils.swift */, ); name = utils; sourceTree = ""; @@ -2574,7 +4226,7 @@ C2D2CAEE1E6486CE00939968 /* stickers */ = { isa = PBXGroup; children = ( - C2D2CAEC1E64579700939968 /* StickersPackPreviewModalController.swift */, + C2D2CAEC1E64579700939968 /* StickerPackPreviewModalController.swift */, C2D2CAEF1E64874600939968 /* StickerPackGridItem.swift */, ); name = stickers; @@ -2704,6 +4356,7 @@ C27AAFE71DE9DA51009B9629 /* utils */, C2DF47EC1DE9A18E003AA6C0 /* register */, C2DF47EB1DE9A18A003AA6C0 /* login */, + 9F63152521D236CB009FD379 /* ForgotPasswordController.swift */, ); name = auth; sourceTree = ""; @@ -2742,6 +4395,15 @@ name = "selective-privacy"; sourceTree = ""; }; + C2E8BA081FB5F13600DEB5E2 /* thumbs */ = { + isa = PBXGroup; + children = ( + C2E8BA061FB5EF4C00DEB5E2 /* GalleryThumbsControl.swift */, + C2E8BA091FB5F15900DEB5E2 /* GalleryThumbsControlView.swift */, + ); + name = thumbs; + sourceTree = ""; + }; C2EA177D1E2FD4F900887153 /* image */ = { isa = PBXGroup; children = ( @@ -2763,9 +4425,7 @@ isa = PBXGroup; children = ( C2F9C4471F9500B4002B2CBF /* TwoStepVerificationUnlockController.swift */, - C2F9C4491F9500C3002B2CBF /* TwoStepVerificationUnlockStructures.swift */, - C240E9511F96449E00F671FA /* TwoStepVerificationPasswordEntryController.swift */, - C20320FE1F9769BA00143395 /* TwoStepVerificationResetController.swift */, + 9F63152821D26892009FD379 /* CancelResetAccountController.swift */, ); name = "two-step-verification"; sourceTree = ""; @@ -2774,6 +4434,7 @@ isa = PBXGroup; children = ( C2FB2FAA1EBF73CF0093C8BA /* RecentCallsViewController.swift */, + 9FB7CB6C221EB22700888EA9 /* CallSettingsModalController.swift */, ); name = calls; sourceTree = ""; @@ -2781,6 +4442,7 @@ C2FBC1E51DC631620063A23B /* overlay */ = { isa = PBXGroup; children = ( + D076F86E2295B1F6004F895A /* inline-auth */, C2D2CAEE1E6486CE00939968 /* stickers */, C24D9FDA1E267550002CD3F3 /* preview-sender */, C230B8F71DD3714D0057F596 /* popover */, @@ -2788,10 +4450,16 @@ C2A71CD61DD9EEDB00C69F73 /* WebpageModalController.swift */, C29A65061E098B610071BCEF /* ShareModalController.swift */, C232E9F51E1437B100C4D38C /* SearchResultModalController.swift */, - C29C3E701E43881500193A7E /* StickerPreviewModalController.swift */, + C29C3E701E43881500193A7E /* ModalPreviewViews.swift */, C26D8A3B1E464944002FAA3F /* JoinLinkPreviewModalController.swift */, C22EE6181E66ECB200334C38 /* ReportReasonModalController.swift */, C24BA3BC1E9D30F800E8970B /* DeleteSupergroupMessagesModalController.swift */, + 9F72974720C597800067F815 /* TermsModalController.swift */, + C2CE43E820F4F74F00656543 /* UpdateModalController.swift */, + 9F262D5E21BFD5BC006817CD /* LocalizationPreviewModalController.swift */, + 9F6314E021CAA0AB009FD379 /* NewPollController.swift */, + 9F18908C2237B5A400665EF5 /* InputURLFormatterModalController.swift */, + D014193B22AE939F008667CB /* ModalOptionSetController.swift */, ); name = overlay; sourceTree = ""; @@ -2808,52 +4476,322 @@ path = objc/sskeychain; sourceTree = ""; }; - D098C70C1D7E175A007784E4 = { + D001E972243B19C4009025F9 /* folders-sidebar */ = { isa = PBXGroup; children = ( - D098C7171D7E175A007784E4 /* Telegram-Mac */, - C232EA921E1D07E700C4D38C /* TelegramShare */, - D098C7161D7E175A007784E4 /* Products */, - D098C7311D7E18CB007784E4 /* Frameworks */, + D001E973243B1A0A009025F9 /* LeftSidebarController.swift */, + D001E975243B385E009025F9 /* FolderIcons.swift */, + D001E977243B4639009025F9 /* LeftSidebarFolderItem.swift */, ); + name = "folders-sidebar"; sourceTree = ""; }; - D098C7161D7E175A007784E4 /* Products */ = { + D014AA272317DA0E00CE5362 /* themes */ = { isa = PBXGroup; children = ( - D098C7151D7E175A007784E4 /* Telegram.app */, - C232EA911E1D07E700C4D38C /* TelegramShare.appex */, + D004BD2A23153415009A54B1 /* ThemePreviewModalController.swift */, + D014AA232316CE0700CE5362 /* NewThemeController.swift */, + D014AA252317D07D00CE5362 /* EditThemeController.swift */, ); - name = Products; + name = themes; sourceTree = ""; }; - D098C7171D7E175A007784E4 /* Telegram-Mac */ = { + D017A3E92334F54D0086174B /* info */ = { isa = PBXGroup; children = ( - C279824C1E72C83500262BFD /* legacy */, - C25F71461E410DD50046AF4E /* inapp-settings */, - C253E22F1DE34FA20022A29F /* audio */, - C253A9531D9164CE00CDC850 /* thrid-party */, - C2CBCABE1D81526A00142EC0 /* utils */, - C22E062F1D80439800A11C88 /* ui */, - D098C7381D7E1A62007784E4 /* Telegram-Mac.entitlements */, - D098C7181D7E175A007784E4 /* AppDelegate.swift */, - C2F6190C1E844DCD007A051B /* TelegramAccountAuxiliaryMethods.swift */, - C2DEC87E1DECB8C800F6544A /* TelegramApplicationContext.swift */, - C24949131E5B763F00D7ED5D /* ApplicationContext.swift */, - C2BB2DAF1F8BDF6700520255 /* Config.swift */, - D098C71A1D7E175A007784E4 /* Assets.xcassets */, - D098C71C1D7E175A007784E4 /* MainMenu.xib */, - D098C71F1D7E175A007784E4 /* Info.plist */, - C253A9721D92F9F100CDC850 /* Localizable.strings */, - C252532B1DF043D800ADBC98 /* resources */, + D07450D3233B6ACF00769D7F /* items */, + D07B558123338AA300F076A8 /* WalletInfoController.swift */, + D017A3F8233929B10086174B /* WalletReceiveController.swift */, + D0C39EC2233A5D9B003CD402 /* WalletTransactionPreviewController.swift */, + D050518F2342BA6700D93176 /* WalletSettingsController.swift */, + D007ABA0234CACE40022C27F /* WalletCreateInvoiceController.swift */, ); - path = "Telegram-Mac"; + name = info; + sourceTree = ""; + }; + D02BD7C1232D14CC00D1814A /* items */ = { + isa = PBXGroup; + children = ( + D02BD7C2232D14E800D1814A /* ThemePreviewRowItem.swift */, + D02BD7C4232D204F00D1814A /* ThemeListRowItem.swift */, + D02BD7C6232D4DB200D1814A /* AppearanceThumbs.swift */, + ); + name = items; + sourceTree = ""; + }; + D0558D782151411F006B403D /* chat */ = { + isa = PBXGroup; + children = ( + D071E8A5214A805C001B6024 /* ChatTouchBar.swift */, + D071E8A7214BBE21001B6024 /* ChatStickersTouchBarPopover.swift */, + D0558D7B215141CF006B403D /* ChatInfoTouchbar.swift */, + ); + name = chat; + sourceTree = ""; + }; + D0558D792151412C006B403D /* stickers */ = { + isa = PBXGroup; + children = ( + D071E8A9214BC589001B6024 /* TouchBarStickerItemView.swift */, + ); + name = stickers; + sourceTree = ""; + }; + D0558D7A2151413A006B403D /* chat-list */ = { + isa = PBXGroup; + children = ( + D071E8A321496F38001B6024 /* ChatListTouchBar.swift */, + ); + name = "chat-list"; + sourceTree = ""; + }; + D0558D832152B4C8006B403D /* gallery */ = { + isa = PBXGroup; + children = ( + D0558D842152B4D3006B403D /* GalleryTouchBar.swift */, + D0558D862154047E006B403D /* GalleryTouchBarThumbItemView.swift */, + ); + name = gallery; + sourceTree = ""; + }; + D05F392122FB448E0040F341 /* experemental */ = { + isa = PBXGroup; + children = ( + A71DC82C23858356000EEDE2 /* Spotlight.swift */, + A7F28336239A808500742C20 /* AudioAnimatedSticker.swift */, + A76C8A99241F826900FDB071 /* GlobalSearchModalController.swift */, + ); + name = experemental; + sourceTree = ""; + }; + D070DB7F22D36359008A0BBE /* modern-stickers */ = { + isa = PBXGroup; + children = ( + D070DB8022D3638F008A0BBE /* StickersViewController.swift */, + D004167422D37AD00000566B /* StickerPackPanelRowItem.swift */, + D004167622D4AD3B0000566B /* StickerPackItems.swift */, + ); + name = "modern-stickers"; + sourceTree = ""; + }; + D071E89F21496D77001B6024 /* touchbar */ = { + isa = PBXGroup; + children = ( + D0BEB9932166AF000055B718 /* shared-media */, + D0558D832152B4C8006B403D /* gallery */, + D0558D7A2151413A006B403D /* chat-list */, + D0558D792151412C006B403D /* stickers */, + D0558D782151411F006B403D /* chat */, + D071E8B1214C15AE001B6024 /* Emoji Picker */, + D071E8AB214BE6E7001B6024 /* TouchBarScrubberHeaderItemView.swift */, + ); + name = touchbar; + sourceTree = ""; + }; + D071E8B1214C15AE001B6024 /* Emoji Picker */ = { + isa = PBXGroup; + children = ( + D071E8B2214C15C7001B6024 /* TouchBarEmojiPicker.swift */, + D071E8B4214C1935001B6024 /* TouchBarEmojiItemView.swift */, + ); + name = "Emoji Picker"; + sourceTree = ""; + }; + D07450D3233B6ACF00769D7F /* items */ = { + isa = PBXGroup; + children = ( + D017A3EA2334F5680086174B /* WalletBalanceItem.swift */, + D017A3EC2335166B0086174B /* WalletInfoCreatedItem.swift */, + D017A3F423390D8C0086174B /* WalletInfoTransactionItem.swift */, + D017A3FA2339404D0086174B /* WalletTransactionTextItem.swift */, + D07450D4233B8D2900769D7F /* WalletTransactionDateStickItem.swift */, + ); + name = items; + sourceTree = ""; + }; + D07450D6233BB84300769D7F /* send */ = { + isa = PBXGroup; + children = ( + D017A3FC233949B00086174B /* WalletSendController.swift */, + D07450D7233BB86900769D7F /* WalletSendProccessingView.swift */, + D07450D9233BB92F00769D7F /* WalletSendSuccessController.swift */, + D07C6D822347701700468B1A /* WalletProcessTransactionController.swift */, + D007AB9E234C8DCB0022C27F /* WalletPasscodeTimeout.swift */, + ); + name = send; + sourceTree = ""; + }; + D07450DB233D617B00769D7F /* tgs */ = { + isa = PBXGroup; + children = ( + D0A75F2B2449B67D001F84A0 /* dart_idle.tgs */, + A76C8AA124221D2800FDB071 /* graph_loading.tgs */, + A76C8A9D2420FFE400FDB071 /* folder_empty.tgs */, + D0830FC324127468006198E7 /* folder_new.tgs */, + A7029EF9240E3CCF00A89ABD /* folder.tgs */, + A7393D412407FF0F00CE44CA /* dice_idle.tgs */, + A7C7215823FD473D00CE3F75 /* success_saved.tgs */, + A7B6DDD623ED8FDF00B8E01C /* think_spectacular.tgs */, + D07C6D7F2346A42F00468B1A /* monkey_see.tgs */, + D07C6D7E2346A42E00468B1A /* monkey_unsee.tgs */, + D07450F42340F28D00769D7F /* wallet_success_created.tgs */, + D07450F22340E89000769D7F /* sad_man.tgs */, + D07450DE233D61B200769D7F /* brilliant_loading.tgs */, + D07450DC233D61B000769D7F /* brilliant_static.tgs */, + D07450E3233D61B600769D7F /* chiken_born.tgs */, + D07450E1233D61B400769D7F /* fly_dollar.tgs */, + D07450DD233D61B100769D7F /* gift.tgs */, + D07450E4233D61B700769D7F /* keychain.tgs */, + D07450DF233D61B200769D7F /* keyboard_typing.tgs */, + D07450E5233D61B800769D7F /* smart_guy.tgs */, + D07450E2233D61B500769D7F /* swap_money.tgs */, + D07450E0233D61B300769D7F /* write_words.tgs */, + ); + path = tgs; + sourceTree = ""; + }; + D074A56924A1EB0D00E92F8A /* webrtc */ = { + isa = PBXGroup; + children = ( + D074A56B24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.h */, + D074A56A24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.mm */, + ); + name = webrtc; + sourceTree = ""; + }; + D076F86E2295B1F6004F895A /* inline-auth */ = { + isa = PBXGroup; + children = ( + D076F86C22959958004F895A /* InlineLoginController.swift */, + D076F86F2295B24D004F895A /* InlineAuthOptionRowItem.swift */, + ); + name = "inline-auth"; + sourceTree = ""; + }; + D076F88B2296FCE5004F895A /* discussion */ = { + isa = PBXGroup; + children = ( + D076F8862296CAB2004F895A /* ChannelDisscussionGroup.swift */, + D076F88C2296FD18004F895A /* DiscussionHeaderItem.swift */, + D076F88E2297285F004F895A /* DiscussionSetModalController.swift */, + ); + name = discussion; + sourceTree = ""; + }; + D07B558023338A1400F076A8 /* wallet */ = { + isa = PBXGroup; + children = ( + D07450D6233BB84300769D7F /* send */, + D017A3E92334F54D0086174B /* info */, + D07B55852333901300F076A8 /* items */, + D07B558323338B6B00F076A8 /* WalletSplashController.swift */, + D07B55902334108E00F076A8 /* WalletSplashHelps.swift */, + D0439A1F23410D5400C1E6F9 /* WalletUtils.swift */, + D07C6D7823464C8000468B1A /* WalletKeychainController.swift */, + D07C6D7A2346547600468B1A /* Keychain.swift */, + ); + name = wallet; + sourceTree = ""; + }; + D07B55852333901300F076A8 /* items */ = { + isa = PBXGroup; + children = ( + D07B55862333903C00F076A8 /* WalletSplashRowItem.swift */, + D07B55882333A41600F076A8 /* WalletSplashButtonRowItem.swift */, + D07B558C2333E1FE00F076A8 /* Wallet24WordsItem.swift */, + D017A3EE2337D1B90086174B /* WalletImportWordsItem.swift */, + D017A3F02338DE1E0086174B /* WalletTestWordsItem.swift */, + ); + name = items; + sourceTree = ""; + }; + D098C70C1D7E175A007784E4 = { + isa = PBXGroup; + children = ( + D098C7171D7E175A007784E4 /* Telegram-Mac */, + C232EA921E1D07E700C4D38C /* TelegramShare */, + D098C7161D7E175A007784E4 /* Products */, + D098C7311D7E18CB007784E4 /* Frameworks */, + ); + sourceTree = ""; + }; + D098C7161D7E175A007784E4 /* Products */ = { + isa = PBXGroup; + children = ( + D098C7151D7E175A007784E4 /* Telegram.app */, + C232EA911E1D07E700C4D38C /* TelegramShare.appex */, + ); + name = Products; + sourceTree = ""; + }; + D098C7171D7E175A007784E4 /* Telegram-Mac */ = { + isa = PBXGroup; + children = ( + A71DC8332386D0C4000EEDE2 /* api-credentials */, + D05F392122FB448E0040F341 /* experemental */, + 9FDA713020E64541001ED8ED /* media */, + C25F71461E410DD50046AF4E /* inapp-settings */, + C253E22F1DE34FA20022A29F /* audio */, + C253A9531D9164CE00CDC850 /* thrid-party */, + C2CBCABE1D81526A00142EC0 /* utils */, + C22E062F1D80439800A11C88 /* ui */, + D098C7381D7E1A62007784E4 /* Telegram-Mac.entitlements */, + D00CA6502281BB0900FFACAD /* Telegram-Sandbox.entitlements */, + D098C7181D7E175A007784E4 /* AppDelegate.swift */, + C2F6190C1E844DCD007A051B /* TelegramAccountAuxiliaryMethods.swift */, + 9F7D422822240235007B68BB /* account */, + C24949131E5B763F00D7ED5D /* ApplicationContext.swift */, + D098C71A1D7E175A007784E4 /* Assets.xcassets */, + D098C71C1D7E175A007784E4 /* MainMenu.xib */, + D098C71F1D7E175A007784E4 /* Info.plist */, + C253A9721D92F9F100CDC850 /* Localizable.strings */, + C252532B1DF043D800ADBC98 /* resources */, + ); + path = "Telegram-Mac"; sourceTree = ""; }; D098C7311D7E18CB007784E4 /* Frameworks */ = { isa = PBXGroup; children = ( + A7919134240D0869002011CA /* MurMurHash32.framework */, + A7918DB324093505002011CA /* GraphUI.framework */, + A7393D382407D0F100CE44CA /* GraphCore.framework */, + A7831B172403CB420056AEAC /* Graph.framework */, + A7377E5B23B5E99500AD3ADD /* libxml2.tbd */, + A7377E2023B5DCBA00AD3ADD /* SVGKit.framework */, + A71DC82A23858311000EEDE2 /* CoreSpotlight.framework */, + A74EB06F237961A1005F55AE /* AppCenter.framework */, + A74EB070237961A1005F55AE /* AppCenterCrashes.framework */, + A7DF1B6B237415AD00ACC01F /* Zip.framework */, + A7ED5DAE236C7CE100040372 /* RLottie.framework */, + A7D2823B236C69070000A9BF /* SSignalKit.framework */, + A7D28231236C57DD0000A9BF /* libphonenumber.framework */, + A7D2822C236C51F10000A9BF /* OpenSSLEncryption.framework */, + A7D2822A236C51A50000A9BF /* OpenSSLEncryption.framework */, + A7D28213236C3DAE0000A9BF /* WalletCore.framework */, + A7D28210236C3D390000A9BF /* SyncCore.framework */, + A7D2820A236C3C1B0000A9BF /* MtProtoKit.framework */, + A7D28208236C3C150000A9BF /* TelegramCore.framework */, + A7D28206236C3C0F0000A9BF /* SwiftSignalKit.framework */, + A7D28204236C3C0B0000A9BF /* Postbox.framework */, + 9F354EA3227077BD006F1D42 /* Lottie.framework */, + 9F354EA1227076EF006F1D42 /* Lottie.framework */, + 9F354E9F227076A6006F1D42 /* libLottie.a */, + 9F354E9D2270757E006F1D42 /* Lottie.framework */, + 9F6FF3212248E298004364FE /* UserNotifications.framework */, + 9F4EEF8321D4F59C002C3B33 /* Accelerate.framework */, + D0675D6A217E20B9004900A7 /* Zip.framework */, + 9F13EE182100B6DA00562E53 /* Contacts.framework */, + 9F21F65C20B5A9C900332C85 /* MapKit.framework */, + 9F0BCD9E2087BABC001D8D8A /* Sparkle.framework */, + 9FFAE513205AB50C000C028E /* libz.tbd */, + 9FFAE510205AB4AB000C028E /* libbz2.tbd */, + 9FFAE50E205AB4A3000C028E /* libiconv.tbd */, + 9F0E6F77203ED1380086699C /* AppKit.framework */, + C251FB501FEE300E0035E5D7 /* Telegram.xcodeproj */, + C256A9131FB9CBF10043D497 /* LocalAuthentication.framework */, + 9F0BCD9C2087BA81001D8D8A /* Sparkle.framework */, + C2EBBEA01FB5CA94009AD8ED /* CoreServices.framework */, C2A8E14A1F7D2690000FD5E3 /* CoreVideo.framework */, C21A48AD1F7CFBBE0095ADB1 /* OpenGL.framework */, C209C37C1F2628D7009231FE /* libc++.tbd */, @@ -2872,22 +4810,63 @@ C2DF47971DE79540003AA6C0 /* libopus.a */, C2203E9F1DDE040F001E6AB6 /* libcommonCrypto.tbd */, C231992D1EE006330011BEBE /* Sparkle.framework */, - C2A71CD81DDA0FA300C69F73 /* HockeySDK.framework */, C2C738F01DD898DA00CE9D8A /* AVKit.framework */, C2C030CC1DD5097400617711 /* CoreMedia.framework */, - C2FBC1DE1DC61B580063A23B /* MtProtoKitMac.framework */, - C2FBC1D81DC61B050063A23B /* SwiftSignalKitMac.framework */, - C2FBC1D61DC61AFF0063A23B /* TelegramCoreMac.framework */, C259ED1D1DB956C1008E6712 /* QuartzCore.framework */, C250B0341DB78A0A004E9FBE /* Quartz.framework */, C250B0321DB788B3004E9FBE /* Foundation.framework */, - C234CA901D97E117003023F7 /* PostboxMac.framework */, C253A9661D92E3AE00CDC850 /* AVFoundation.framework */, C22E06251D7F16C000A11C88 /* TGUIKit.framework */, ); name = Frameworks; sourceTree = ""; }; + D0A2764C249C9C8E005E3C77 /* user-photos */ = { + isa = PBXGroup; + children = ( + D0A2764D249C9D6B005E3C77 /* PeerPhotos.swift */, + ); + name = "user-photos"; + sourceTree = ""; + }; + D0A75F2824487A05001F84A0 /* canvas */ = { + isa = PBXGroup; + children = ( + D0A75F24244843D3001F84A0 /* EditImageCanvasController.swift */, + D0A75F2624486B6D001F84A0 /* EditImageCanvasColorPicker.swift */, + D0A75F2924487A1E001F84A0 /* EditImageCanvasControls.swift */, + ); + name = canvas; + sourceTree = ""; + }; + D0BEB9932166AF000055B718 /* shared-media */ = { + isa = PBXGroup; + children = ( + D0BEB9942166AF270055B718 /* PeerMediaTouchBar.swift */, + ); + name = "shared-media"; + sourceTree = ""; + }; + D0BEB996216BD5F90055B718 /* editor */ = { + isa = PBXGroup; + children = ( + D0A75F2824487A05001F84A0 /* canvas */, + D0BEB98E21628C250055B718 /* EditImageModalController.swift */, + D0BEB997216BD8A70055B718 /* EditImageControls.swift */, + ); + name = editor; + sourceTree = ""; + }; + D0CBB0F32492972A00620C65 /* Video-Avatar */ = { + isa = PBXGroup; + children = ( + D0CBB0F42492974F00620C65 /* VideoAvatarModalController.swift */, + D0D7520824C03CD60037D73A /* VideoEditorThumbs.swift */, + D0D7520A24C04FD60037D73A /* VideoEditorScrubbler.swift */, + ); + name = "Video-Avatar"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2913,17 +4892,20 @@ isa = PBXNativeTarget; buildConfigurationList = D098C7221D7E175A007784E4 /* Build configuration list for PBXNativeTarget "Telegram" */; buildPhases = ( + 9F193BD82130B968001C0800 /* ShellScript */, D098C7121D7E175A007784E4 /* Frameworks */, C20D5AB61DAA94500042616A /* Copy Files */, D098C7111D7E175A007784E4 /* Sources */, D098C7131D7E175A007784E4 /* Resources */, C232EA661E1D041B00C4D38C /* Embed App Extensions */, C23D0D7B1F1A648600AF5151 /* Fonts */, + C2786FF51FEC128F001FB044 /* Palettes */, C2D85FD51F25FE5400EDEA15 /* ShellScript */, ); buildRules = ( ); dependencies = ( + A71DC8312386C83E000EEDE2 /* PBXTargetDependency */, ); name = Telegram; productName = "Telegram-Mac"; @@ -2936,8 +4918,9 @@ D098C70D1D7E175A007784E4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0810; + DefaultBuildSystemTypeForWorkspace = Latest; + LastSwiftUpdateCheck = 1030; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = Telegram; TargetAttributes = { C232EA901E1D07E700C4D38C = { @@ -2948,6 +4931,9 @@ com.apple.ApplicationGroups.Mac = { enabled = 1; }; + com.apple.HardenedRuntime = { + enabled = 0; + }; }; }; D098C7141D7E175A007784E4 = { @@ -2959,6 +4945,15 @@ com.apple.ApplicationGroups.Mac = { enabled = 1; }; + com.apple.HardenedRuntime = { + enabled = 1; + }; + com.apple.Keychain = { + enabled = 1; + }; + com.apple.Maps.Mac = { + enabled = 1; + }; com.apple.Sandbox = { enabled = 1; }; @@ -2971,6 +4966,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, es, @@ -2978,6 +4974,8 @@ it, nl, "pt-BR", + ru, + uk, ); mainGroup = D098C70C1D7E175A007784E4; productRefGroup = D098C7161D7E175A007784E4 /* Products */; @@ -2987,6 +4985,10 @@ ProductGroup = C2D70AF21F2BFB3700AE768E /* Products */; ProjectRef = C2D70AF11F2BFB3700AE768E /* Telegram.xcodeproj */; }, + { + ProductGroup = C251FB511FEE300E0035E5D7 /* Products */; + ProjectRef = C251FB501FEE300E0035E5D7 /* Telegram.xcodeproj */; + }, ); projectRoot = ""; targets = ( @@ -3003,7 +5005,6 @@ files = ( C299B2001F1E11AF00922882 /* Localizable.strings in Resources */, C232EA9B1E1D07E700C4D38C /* ShareViewController.xib in Resources */, - C21656D41EE4A83E0041A6BA /* MainMenu.xib in Resources */, C23D0D7A1F1A609300AF5151 /* SFCompactRounded-Semibold.otf in Resources */, C232EAC21E1D448C00C4D38C /* Assets.xcassets in Resources */, C232EA951E1D07E700C4D38C /* icon.icns in Resources */, @@ -3014,35 +5015,95 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D0A75F2C2449B689001F84A0 /* dart_idle.tgs in Resources */, + A76C8AA224221D3000FDB071 /* graph_loading.tgs in Resources */, + A76C8A9E2420FFE500FDB071 /* folder_empty.tgs in Resources */, + D0830FC424127473006198E7 /* folder_new.tgs in Resources */, + A7029EFA240E3CDA00A89ABD /* folder.tgs in Resources */, + A7393D422407FF1900CE44CA /* dice_idle.tgs in Resources */, + A7B6DDD723ED935100B8E01C /* think_spectacular.tgs in Resources */, + 9F1668C62008A97000DD39FB /* builtin-wallpaper-0.jpg in Resources */, C2057FA51EBBCEA7000423DC /* voip_busy.caf in Resources */, + 9F3EAB4520A5ED2F003FE7E3 /* ocr_nn.bin in Resources */, C2057FA61EBBCEA7000423DC /* voip_end.caf in Resources */, + D07450EB233D61B800769D7F /* fly_dollar.tgs in Resources */, C2057FA71EBBCEA7000423DC /* voip_fail.caf in Resources */, C2DDA04E1EC0C024003531BB /* opening.mp3 in Resources */, + 9F0AE6B52199904400A8B53A /* sound_a.caf in Resources */, + A778DC2E23C77AD800DD307B /* confetti.mp3 in Resources */, + D07450ED233D61B800769D7F /* chiken_born.tgs in Resources */, + 9F03681322771A9700456348 /* anim_delete.json in Resources */, + 9F03681822771A9700456348 /* anim_group.json in Resources */, + D07450E9233D61B800769D7F /* keyboard_typing.tgs in Resources */, + 9F03681422771A9700456348 /* anim_unread.json in Resources */, + 9FDE0A8F21AD41C2001546D7 /* emoji1014-1.txt in Resources */, + D07450F52340F28D00769D7F /* wallet_success_created.tgs in Resources */, + 9F03680F22771A9700456348 /* anim_unarchive.json in Resources */, C2E064651ECCB24000387BB8 /* NativeCallSettingsViewController.xib in Resources */, + 9F03681622771A9700456348 /* anim_mute.json in Resources */, C2057FA81EBBCEA7000423DC /* voip_ringback.caf in Resources */, C252532D1DF04AF500ADBC98 /* begin_record.caf in Resources */, + D07450E8233D61B800769D7F /* brilliant_loading.tgs in Resources */, + A7C7215923FD473E00CE3F75 /* success_saved.tgs in Resources */, C29B5F431DC7970400D13E65 /* sent.caf in Resources */, C27AAFE61DE9D2EE009B9629 /* PhoneCountries.txt in Resources */, C2DDA0501EC0C19A003531BB /* opening.m4a in Resources */, C250B0371DB7BB09004E9FBE /* mime-types.txt in Resources */, + D07450EA233D61B800769D7F /* write_words.tgs in Resources */, + 9F03681722771A9700456348 /* anim_unpin.json in Resources */, C253A9701D92F9F100CDC850 /* Localizable.strings in Resources */, + D07450E7233D61B800769D7F /* gift.tgs in Resources */, + 9F03681B22771A9700456348 /* anim_pin.json in Resources */, C232EA3D1E1C06EF00C4D38C /* dsa_pub.pem in Resources */, D098C71B1D7E175A007784E4 /* Assets.xcassets in Resources */, C2CFCABE1EBB4A4D00843F6A /* voip_connecting.mp3 in Resources */, + 9F03681222771A9700456348 /* anim_read.json in Resources */, + D07450F32340E89100769D7F /* sad_man.tgs in Resources */, + D07C6D802346A43000468B1A /* monkey_unsee.tgs in Resources */, D098C71E1D7E175A007784E4 /* MainMenu.xib in Resources */, + D07450EE233D61B800769D7F /* keychain.tgs in Resources */, + D07C6D812346A43000468B1A /* monkey_see.tgs in Resources */, + D0D71386227ADE9400EC88B1 /* maccheck.json in Resources */, C2844AE01DA90C8A009308DC /* emoji.txt in Resources */, + 9FC8ADA8206A77E00094F7B4 /* countries in Resources */, C21A48B21F7D0D3F0095ADB1 /* VideoMessage.fsh in Resources */, + D0FFCEBE215A7CB700995AFE /* emoji14.txt in Resources */, + 9F03681A22771A9700456348 /* anim_unmute.json in Resources */, C27A6F731ECF610C00C65577 /* currencies.json in Resources */, C250B01C1DB670B4004E9FBE /* url-schemes.txt in Resources */, + 9F03681122771A9700456348 /* archiveAvatar.json in Resources */, + D07450EF233D61B800769D7F /* smart_guy.tgs in Resources */, C23BC3681E9AA03E00D79F92 /* emoji11.txt in Resources */, + D07450E6233D61B800769D7F /* brilliant_static.tgs in Resources */, + 9F03681522771A9700456348 /* anim_hide.json in Resources */, C21A48B41F7D0D6B0095ADB1 /* VideoMessage.vsh in Resources */, + A778DC3423C8988300DD307B /* quiz-correct.mp3 in Resources */, + 9F03681022771A9700456348 /* anim_ungroup.json in Resources */, + A778DC3323C8988300DD307B /* quiz-incorrect.mp3 in Resources */, + D07450EC233D61B800769D7F /* swap_money.tgs in Resources */, C21074241E77F5DF006EE5EF /* dsa_pub_prod.pem in Resources */, + 9F03681922771A9700456348 /* anim_archive.json in Resources */, + C23D0D7C1F1A649900AF5151 /* SFCompactRounded-Semibold.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 9F193BD82130B968001C0800 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = " +"; + }; C2D85FD51F25FE5400EDEA15 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3054,7 +5115,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "sh ${PROJECT_DIR}/tools/bump_build_number.sh \"${PROJECT_DIR}/TelegramMac/${INFOPLIST_FILE}\"\nsh ${PROJECT_DIR}/tools/sync_share_version.sh \"${PROJECT_DIR}/${INFOPLIST_FILE}\" \"${PROJECT_DIR}/TelegramShare/Info.plist\"\n"; + shellScript = "sh ${PROJECT_DIR}/tools/bump_build_number.sh \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\nsh ${PROJECT_DIR}/tools/sync_share_version.sh \"${PROJECT_DIR}/${INFOPLIST_FILE}\" \"${PROJECT_DIR}/TelegramShare/Info.plist\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3063,12 +5124,34 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A789E09723E427CE00AEB34A /* ChatListFilterPreferences.swift in Sources */, + A7F2831923994B9700742C20 /* AutoplayPreferences.swift in Sources */, + A71DC8342386D512000EEDE2 /* Signature.swift in Sources */, + A71DC8352386D512000EEDE2 /* Config.swift in Sources */, + D06ACAF0231AC3E5002DCD81 /* ParseAppearanceColors.swift in Sources */, + D06ACAEF231AC265002DCD81 /* ChatMessageBubbleImages.swift in Sources */, + D04D2145230DB57300609388 /* TelegramIconsTheme.swift in Sources */, + D06CD4F022F22F1100E444DF /* PhotoCache.swift in Sources */, + D00CE4F82284D5E3008C1B4F /* TransformOutgoingMessageMedia.swift in Sources */, + D00CE4F72284D530008C1B4F /* BuildConfig.m in Sources */, + 9F147F7E22315EC200D71BD1 /* ManageSharedAccountInfo.swift in Sources */, + 9F147F7F22315EC200D71BD1 /* SharedAccountInfo.swift in Sources */, + 9F147F7A2231533700D71BD1 /* InAppNotificationSettings.swift in Sources */, + 9F147F79223151D900D71BD1 /* RenderedTotalUnreadCount.swift in Sources */, + 9F147F782231515800D71BD1 /* UpdaterNotifySettings.swift in Sources */, + 9F147F7722314FBE00D71BD1 /* PanelUtils.swift in Sources */, + 9F147F7522314F2700D71BD1 /* GlobalBadgeNode.swift in Sources */, + 9F147F7322314B5500D71BD1 /* AccountContext.swift in Sources */, + 9F147F7422314B5500D71BD1 /* SharedAccountContext.swift in Sources */, + 9F88A134200FD425007B899E /* Wallpapers.swift in Sources */, C299B1FF1F1E0B9F00922882 /* NumberPluralizationForm.m in Sources */, + 9F147F7D2231543800D71BD1 /* PeerUtils.swift in Sources */, C299B1FE1F1E0AB700922882 /* ApplicationSpecificPreferencesKeys.swift in Sources */, C299B1FD1F1E0A8E00922882 /* StringPluralization.swift in Sources */, C299B1FC1F1E0A3500922882 /* ObjcUtils.m in Sources */, C299B1FB1F1E097C00922882 /* ControllerExtension.swift in Sources */, C299B1F91F1E095800922882 /* Localizable.swift in Sources */, + A7D28230236C549B0000A9BF /* SyncCoreExtension.swift in Sources */, C299B1FA1F1E095800922882 /* LocalizableExtension.swift in Sources */, C299B1F81F1E08C900922882 /* ThumbUtils.swift in Sources */, C299B1F61F1E080300922882 /* ThemeSettings.swift in Sources */, @@ -3076,7 +5159,6 @@ C23BC3711E9ACD1700D79F92 /* SearchEmptyRowItem.swift in Sources */, C23BC36C1E9ACC0800D79F92 /* MimeTypes.swift in Sources */, C23BC3701E9ACCF900D79F92 /* SEPasslockController.swift in Sources */, - C291E2751E8B051900D397BA /* PhotoCache.swift in Sources */, C291E2741E8B051100D397BA /* ImageUtils.swift in Sources */, C232EABF1E1D13F500C4D38C /* ChatListNothingItem.swift in Sources */, C232EABC1E1D134C00C4D38C /* AvatarLayer.swift in Sources */, @@ -3085,6 +5167,7 @@ C291E2731E8AFA2C00D397BA /* ShareApplicationContext.swift in Sources */, C232EAB81E1D12A300C4D38C /* ShortPeerRowItem.swift in Sources */, C232EAB91E1D12A300C4D38C /* ShortPeerRowView.swift in Sources */, + A7664940236D6BFD00163DF4 /* PasscodeSettings.swift in Sources */, C232EAC91E1D536200C4D38C /* SEModalProgressView.swift in Sources */, C232EAB71E1D123800C4D38C /* System.swift in Sources */, C232EAB61E1D11CA00C4D38C /* InterfaceObserver.swift in Sources */, @@ -3098,84 +5181,151 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9F27EFF920C6B8EE00682B76 /* PassportController.swift in Sources */, C2593FBF1F7D242E00F6D2B1 /* TGPaintShader.m in Sources */, C23BC37C1E9B8F6F00D79F92 /* AddContactTableItem.swift in Sources */, C248BD221E705B62004B9106 /* PrivacyAndSecurityViewController.swift in Sources */, C2F952B01F8E1C840056E586 /* CachedAdminIds.swift in Sources */, C250B0211DB67DA6004E9FBE /* WPContentView.swift in Sources */, C2DF47CB1DE79719003AA6C0 /* TGDataItem.m in Sources */, - C2538E521E770B4600B21DF0 /* GroupAdminsController.swift in Sources */, + D07B558D2333E1FE00F076A8 /* Wallet24WordsItem.swift in Sources */, C22338451F823F8C004AD57C /* VideoCameraStructures.swift in Sources */, C27AAFED1DEB1D72009B9629 /* SignalUtils.swift in Sources */, C253A9631D91A90100CDC850 /* ChatFileMediaItem.swift in Sources */, C24FD4101F20FBFB00A97196 /* RecentUsedEmoji.swift in Sources */, + A76C8A9A241F826900FDB071 /* GlobalSearchModalController.swift in Sources */, C219E1D71D8869F20042F0C8 /* ChatHoleRowItem.swift in Sources */, + D07C6D832347701700468B1A /* WalletProcessTransactionController.swift in Sources */, C253A95F1D9165CD00CDC850 /* webp.m in Sources */, + 9F1668B82007E3BC00DD39FB /* ThemeGridControllerItem.swift in Sources */, + A76C8AA424221F5400FDB071 /* StatisticsLoadingRowItem.swift in Sources */, C2DE5D2C1F3CA6E80081EC1E /* InstantPageMediaItem.swift in Sources */, C252532A1DF03F9600ADBC98 /* TGAudioWaveform.m in Sources */, C218FF981F41FF2300DD7D35 /* InstantPageAnchorItem.swift in Sources */, + A7C41DB623586DC100CF9402 /* PeerPhotosMonthItem.swift in Sources */, + 9FFAE4EF205A8C89000C028E /* MediaFrameSource.swift in Sources */, + A7C7215B23FD4BAA00CE3F75 /* LottieLocalAnimations.swift in Sources */, C218FF941F3CC99200DD7D35 /* instantPageWebEmbedView.swift in Sources */, + D0186731223807D200A77C45 /* ChatListEmptyRowItem.swift in Sources */, + D02BD7C7232D4DB200D1814A /* AppearanceThumbs.swift in Sources */, C250B02B1DB76BA4004E9FBE /* WPMediaLayout.swift in Sources */, + 9F7943B020854E2F00FEDB81 /* ProxyListRowItem.swift in Sources */, + 9FF32C7C21B7DF4800BF58B6 /* StickerSettings.swift in Sources */, C2271DB91DAE2125001792B6 /* GroupInfoEntries.swift in Sources */, C2EA177F1E2FD50000887153 /* TransformImageView.swift in Sources */, C29C3E6F1E4352C100193A7E /* ContextStickerRowItem.swift in Sources */, + D0D087E2243DF4F100E05317 /* ChatlistFilterVisibilityItem.swift in Sources */, C2271F3E1DB4D4240045E719 /* ETabRowItem.swift in Sources */, C2DF47BD1DE79574003AA6C0 /* diag_range.c in Sources */, - C25BB1691F867FEE0089ED02 /* ChatVideoAccessoryView.swift in Sources */, + 9F7B5FCB22003DC70087D020 /* WallpaperColorPicker.swift in Sources */, + 9FFAE4F7205A8C89000C028E /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */, + A7393D352407CAE100CE44CA /* ChatMediaDice.swift in Sources */, + D0BEB9952166AF270055B718 /* PeerMediaTouchBar.swift in Sources */, + C25BB1691F867FEE0089ED02 /* ChatMessageAccessoryView.swift in Sources */, C2DF47BB1DE79574003AA6C0 /* framing.c in Sources */, - C29670791F0FAAC800884DA2 /* AppearanceViewController.swift in Sources */, + C21BE3AF1FD099AA00C1C849 /* DeveloperViewController.swift in Sources */, + D070DB8122D3638F008A0BBE /* StickersViewController.swift in Sources */, C2777B621DCFB4C9008B69DD /* ChatServiceItem.swift in Sources */, + 9FFAE4F9205A8C89000C028E /* FFMpegAudioFrameDecoder.swift in Sources */, C232EABE1E1D13A800C4D38C /* TextUtils.swift in Sources */, C253A9741D94182500CDC850 /* ChatRightView.swift in Sources */, + 9FDA713F20EE2D49001ED8ED /* PopularPeersRowItem.swift in Sources */, + 9F77B3A6221C1DAC003B65B8 /* LogoutViewController.swift in Sources */, C248BD211E6F09CC004B9106 /* ChatGameContentView.swift in Sources */, - C253A92D1D8EE1A600CDC850 /* ChatMediaView.swift in Sources */, + A7F28337239A808500742C20 /* AudioAnimatedSticker.swift in Sources */, + A7F2830623954E1B00742C20 /* CachedFaqInstantPage.swift in Sources */, C226742F1DBE2CB3000BA9ED /* PanelUtils.swift in Sources */, C2DF47C51DE79574003AA6C0 /* stream.c in Sources */, C271EBA21EB9F04E0034792D /* TGCallUtils.mm in Sources */, + C2CE43E920F4F74F00656543 /* UpdateModalController.swift in Sources */, C22EE6191E66ECB200334C38 /* ReportReasonModalController.swift in Sources */, C2B0722E1DFEDE430082939D /* UsernameInputRowItem.swift in Sources */, - C271EBE91EBA22FE0034792D /* TGCallConnectionDescription.m in Sources */, + C271EBE91EBA22FE0034792D /* OngoingCallConnectionDescription.m in Sources */, C23B3AEB1E338ADC009C162C /* ContextMediaRowItem.swift in Sources */, + 9F0368012277091800456348 /* LAnimationButton.swift in Sources */, C26505931E0301A5001954DC /* MGalleryPhotoItem.swift in Sources */, + D00CE50D2289C9B7008C1B4F /* MediaAnimatedStickerView.swift in Sources */, C205FEA61EB39DE400455808 /* SidebarCapViewController.swift in Sources */, C2271DAE1DAE1172001792B6 /* UserInfoEntries.swift in Sources */, + 9FFAE4F4205A8C89000C028E /* FFMpegMediaFrameSourceContext.swift in Sources */, + 9FFAE4F2205A8C89000C028E /* MediaTrackFrameBuffer.swift in Sources */, C230B9131DD392EB0057F596 /* SearchRowItem.swift in Sources */, C2F4ED1B1EC5AE1D005F2696 /* CallRatingModalViewController.swift in Sources */, C2DF47E91DE892E3003AA6C0 /* ChatToasterView.swift in Sources */, C2167E4F1DC220D800F98E03 /* PeerMediaWebpageRowContent.swift in Sources */, + D02F0A0022E8875800553411 /* SoftwareVideoSource.swift in Sources */, C2B1A10E1D9FD02400ACB1DD /* ChatInterfaceState.swift in Sources */, - C2412E071DA795D200588C14 /* GalleryControls.swift in Sources */, C26546CC1EA0AC3C00E3969A /* ChatVideoMessageItem.swift in Sources */, + 9FC4DAA821DE0626003E2A62 /* ChannelPermissionsController.swift in Sources */, C2E0646B1ECF137300387BB8 /* ChatInvoiceItem.swift in Sources */, C232234D1DE20E610078D738 /* ChatMessageDateHeader.swift in Sources */, + 9FA0E52A20519E33001E5649 /* MP4Atom.m in Sources */, + A76C8AB3242366EC00FDB071 /* PeerMediaBlockRowItem.swift in Sources */, C20D5AB31DA9965B0042616A /* EBlockRowView.swift in Sources */, + C21BE3B11FD14CDB00C1C849 /* ParseAppearanceColors.swift in Sources */, C2A71CE11DDB18FF00C69F73 /* ThumbUtils.swift in Sources */, + 9F72973420B878B00067F815 /* MapResources.swift in Sources */, + C23EEC891FCC47C1001371CD /* PeerMediaDateItem.swift in Sources */, C2F8923D1E3FA51000D98B2D /* PasteboardUtils.swift in Sources */, + D004BD2B23153415009A54B1 /* ThemePreviewModalController.swift in Sources */, + 9FFAE506205A928C000C028E /* RingByteBuffer.swift in Sources */, C24DABBE1E0834B7005EE404 /* XCDYouTubeLogger.m in Sources */, + 9F4EC948218B459A002B3C56 /* RenderedTotalUnreadCount.swift in Sources */, + C23044831F98F8B400977C51 /* MediaPreviewRowItem.swift in Sources */, + D014193C22AE939F008667CB /* ModalOptionSetController.swift in Sources */, C2DF47D31DE824FD003AA6C0 /* OpusObjcBridge.mm in Sources */, C2B4D0C81E36048000CBC4E6 /* ChatActivitiesModel.swift in Sources */, + 9F10CE962061C98E002DD61A /* InputDataRowItem.swift in Sources */, C258D1B41F8D385700458478 /* PreHistorySettingsController.swift in Sources */, C2271DA01DACC7F7001792B6 /* ChatListMessageRowView.swift in Sources */, C219E1DB1D8884290042F0C8 /* ChatHistoryEntry.swift in Sources */, + 9F0367F0227208E000456348 /* QRCode.swift in Sources */, + 9F291CA12264E57F00C66267 /* BuildConfig.m in Sources */, C2A71CD71DD9EEDB00C69F73 /* WebpageModalController.swift in Sources */, + 9FFAE4FB205A8C89000C028E /* MediaPlayer.swift in Sources */, + 9F127E15210B1F540080D709 /* PeerMediaVoiceRowItem.swift in Sources */, C230B90F1DD383820057F596 /* ComposeViewController.swift in Sources */, + 9F9B5EBF2257426500728CDC /* FFMpegAVFrame.m in Sources */, C22EBCB81DFAB36A0034C435 /* ChatMapRowItem.swift in Sources */, C2AC9C151E1E627F0085C7DE /* TabBadgeItem.swift in Sources */, C25FC7FB1D86FEA90041E303 /* ChatListHoleRowItem.swift in Sources */, C2271F471DB4FC130045E719 /* EStickItem.swift in Sources */, C2844AD71DA907E8009308DC /* EntertainmentViewController.swift in Sources */, + 9F17E5BB212F191F00C25A65 /* AutoNightThemePreferences.swift in Sources */, C2303E731D9966BD00098E12 /* ChatInputActionsView.swift in Sources */, + D07450DA233BB92F00769D7F /* WalletSendSuccessController.swift in Sources */, + A7C41DB4235862BB00CF9402 /* PeerMediaPhotosController.swift in Sources */, + 9F0F8E82226DCD1C00A97F6A /* OpmizeDatabaseView.swift in Sources */, + D0E52B3222FD66C4000C0306 /* MessageTimecode.swift in Sources */, + 9F0367F72273260A00456348 /* UndoTooltipController.swift in Sources */, + D0A75F25244843D3001F84A0 /* EditImageCanvasController.swift in Sources */, + 9F21A7D321C167000037784F /* InstantPageImageItem.swift in Sources */, + 9F12D343209251CF0072928B /* EditAccountInfoItem.swift in Sources */, C210959A1E9FE04700E10BDB /* ChatVideoMessageContentView.swift in Sources */, - C2DEC87F1DECB8C800F6544A /* TelegramApplicationContext.swift in Sources */, + C20CAD151FE436E300EFF8BF /* SelectSizeRowItem.swift in Sources */, + D01C731722A9814C000DA008 /* InputPasswordController.swift in Sources */, + 9F10CE922061BE19002DD61A /* InputDataController.swift in Sources */, + 9F10CE9820626B1B002DD61A /* PassportDocumentRowItem.swift in Sources */, C29B5F451DC7DA4B00D13E65 /* MessageActionsPanelView.swift in Sources */, + C20CAD121FE291E300EFF8BF /* ChatBubbleAccessoryForward.swift in Sources */, + 9F72974820C597800067F815 /* TermsModalController.swift in Sources */, + 9F13EE1B2100BFCA00562E53 /* VCardHeaderItem.swift in Sources */, C218FF9C1F4204C400DD7D35 /* InstantPageChannelView.swift in Sources */, + D07B55912334108E00F076A8 /* WalletSplashHelps.swift in Sources */, + D07C6D7D234698C600468B1A /* DynamicHeightRowItem.swift in Sources */, C2271DC11DAE583E001792B6 /* TextAndLabelItem.swift in Sources */, C218FF9A1F42030B00DD7D35 /* InstantPageChannelItem.swift in Sources */, + D01CBE2D22A5384700F6A971 /* NotificationPreferencesController.swift in Sources */, C2D187F21E28D0840038961D /* ChatSwitchInlineController.swift in Sources */, C2A12FE61E0C503900EC2239 /* ChatContactRowItem.swift in Sources */, + D014AA242316CE0700CE5362 /* NewThemeController.swift in Sources */, C29B5F3E1DC74DB400D13E65 /* SenderController.swift in Sources */, + 9FC8ADA6206925F60094F7B4 /* PassportWindowController.swift in Sources */, + A7393D372407CD7A00CE44CA /* ChatDiceContentView.swift in Sources */, C2271D9E1DACC796001792B6 /* ChatListMessageRowItem.swift in Sources */, + 9F9B5EBB2257408200728CDC /* FFMpegAVCodecContext.m in Sources */, C2DF47DA1DE82653003AA6C0 /* NSObject+TGLock.m in Sources */, + 9FC4DA9621DD0B35003E2A62 /* PeerChannelMemberCategoriesContextsManager.swift in Sources */, C250B0271DB69694004E9FBE /* WPArticleLayout.swift in Sources */, C232EA131E16552900C4D38C /* WebGameViewController.swift in Sources */, C2AF01131F01543200D8AC1D /* ExportProxyModalController.swift in Sources */, @@ -3183,128 +5333,204 @@ C2DF47C01DE79574003AA6C0 /* picture.c in Sources */, C20B8F3E1DFC52EE008A354E /* ChatEmptyPeerItem.swift in Sources */, C2016F681EAE0A68003AF981 /* PhoneCallWindowController.swift in Sources */, + 9FDA713220E6456A001ED8ED /* ExternalMusicAlbumArtResources.swift in Sources */, C210742C1E780CC1006EE5EF /* PhotoCache.swift in Sources */, C2256D981DAB9D5A00494CF4 /* ChatHistoryViewForLocation.swift in Sources */, C20CB7291E60886F00C992AC /* LinkInvationController.swift in Sources */, C253E2381DE398980022A29F /* NativeAudioPlayer.swift in Sources */, C221ED561EA6877300471C65 /* GeneratedMediaStoreSettings.swift in Sources */, C296AF861D8DB178001DBB59 /* MediaUtils.swift in Sources */, + 9FA0E52D20519FCC001E5649 /* LiveUploadingHelper.m in Sources */, C22EE61E1E67506800334C38 /* ChannelMembersViewController.swift in Sources */, - C240E9521F96449E00F671FA /* TwoStepVerificationPasswordEntryController.swift in Sources */, C26A719B1DC9FB3600F69385 /* InputPasteboardParser.swift in Sources */, + D02BD7C3232D14E800D1814A /* ThemePreviewRowItem.swift in Sources */, C230B8F41DD368D40057F596 /* SelectPeersController.swift in Sources */, C250B0391DB7BB2D004E9FBE /* MimeTypes.swift in Sources */, + 9FFAE500205A9042000C028E /* FFMpegSwResample.m in Sources */, + D017A3ED2335166B0086174B /* WalletInfoCreatedItem.swift in Sources */, C250BA8F1E6E1CDC0057CD96 /* ChatMessageThrottledProcessingManager.swift in Sources */, C271EBEB1EBA3BC90034792D /* PCallSession.swift in Sources */, + 9F3D5F8622085B2000CB0CAA /* UpdaterNotifySettings.swift in Sources */, C24DABC11E0834B7005EE404 /* XCDYouTubeVideoOperation.m in Sources */, + D0BEB998216BD8A70055B718 /* EditImageControls.swift in Sources */, C29F4C7D1F47283600DBFC00 /* InstantPageBrowser.swift in Sources */, + D017A3EF2337D1B90086174B /* WalletImportWordsItem.swift in Sources */, C25FC7FD1D86FEE00041E303 /* ChatListHoleRowView.swift in Sources */, + D07450D8233BB86900769D7F /* WalletSendProccessingView.swift in Sources */, C2A506791DF59C9700971A93 /* PicturePicker.swift in Sources */, C225524D1F7BE8E40007944D /* VideoRecorderModalView.swift in Sources */, C2D1839D1E4DBF7E001CE25A /* MGalleryPeerPhotoItem.swift in Sources */, - C2D2CAED1E64579700939968 /* StickersPackPreviewModalController.swift in Sources */, + 9F63152921D26892009FD379 /* CancelResetAccountController.swift in Sources */, + C2D2CAED1E64579700939968 /* StickerPackPreviewModalController.swift in Sources */, C22B63581D967F4500085C19 /* TGInputTextTag.m in Sources */, C2DE5D341F3CA94A0081EC1E /* InstantPageMediaView.swift in Sources */, + A7F283042395289B00742C20 /* AccountUtils.swift in Sources */, + A7F283022395185B00742C20 /* SettingsSearchableItems.swift in Sources */, + D0A75F2724486B6D001F84A0 /* EditImageCanvasColorPicker.swift in Sources */, C232EA0E1E155E9400C4D38C /* PeersListController.swift in Sources */, + C2C415E51FA33D1A00FF36F4 /* InputFormatterPopover.swift in Sources */, + 9F0AE6C02199CDB500A8B53A /* SVideoView.swift in Sources */, C232EA101E156DE100C4D38C /* ContextCommandRowItem.swift in Sources */, - C20320FF1F9769BA00143395 /* TwoStepVerificationResetController.swift in Sources */, + C24102651FD5877E00DB8625 /* NSImage+RHResizableImageAdditions.m in Sources */, + D0BEB99A216D10920055B718 /* MediaPreviewEditControl.swift in Sources */, C234A7FB1ED7112400EBBECE /* LocalizableExtension.swift in Sources */, + 9FDA713420E65D49001ED8ED /* PeerMediaPlayerAnimationView.swift in Sources */, C225524B1F7BE7000007944D /* VideoRecorderModalController.swift in Sources */, + A778DC3023C8985300DD307B /* SoundEffects.swift in Sources */, C253E2361DE398580022A29F /* AudioPlayer.swift in Sources */, C2B648901EB25B1700BA574B /* MediaResources.swift in Sources */, C219E1E81D8AC90B0042F0C8 /* ChatUnreadRowView.swift in Sources */, C2D2CAF01E64874600939968 /* StickerPackGridItem.swift in Sources */, C2379D2A1DDCCBF10063AD30 /* ReplyMarkupNode.swift in Sources */, + D076F86D22959958004F895A /* InlineLoginController.swift in Sources */, C21B24611ED9C39F00FC6CDA /* SuggestionLocalizationViewController.swift in Sources */, C275E9EF1F8FCA4200D3D8C0 /* PhoneNumberIntroController.swift in Sources */, C275E9F71F90CDF900D3D8C0 /* PhoneNumberInputCodeController.swift in Sources */, - C28149881EA7F22200BB933E /* PreparedChatHistoryViewTransition.swift in Sources */, + 9FC8ADA02062D5E70094F7B4 /* PassportAcceptRowItem.swift in Sources */, + D071E8AA214BC589001B6024 /* TouchBarStickerItemView.swift in Sources */, + 9F4EEF8821D515C5002C3B33 /* InstantPageStoredState.swift in Sources */, C20B8F401DFD9999008A354E /* ChatListNothingItem.swift in Sources */, + D014AA262317D07D00CE5362 /* EditThemeController.swift in Sources */, C259ED1C1DB8DC78008E6712 /* ChatNavigateScroller.swift in Sources */, C29A65031E08396F0071BCEF /* MGalleryExternalVideoItem.swift in Sources */, C24DABBF1E0834B7005EE404 /* XCDYouTubePlayerScript.m in Sources */, C24DABC01E0834B7005EE404 /* XCDYouTubeVideo.m in Sources */, C2B1A1131D9FD2AE00ACB1DD /* ChatPresentationInterfaceState.swift in Sources */, C2DE5D321F3CA8370081EC1E /* InstantPageTile.swift in Sources */, + A7919138240D1077002011CA /* ChatListFilterPredicate.swift in Sources */, C28BAB291DF981320027CE3A /* AudioWaveform.swift in Sources */, C22EBCBA1DFAB64F0034C435 /* ChatMapContentView.swift in Sources */, C234A7FE1ED725C300EBBECE /* LanguageViewController.swift in Sources */, C2E40A1B1E37ADAF0099AC7D /* PeerMediaEmptyRowItem.swift in Sources */, + D07B55892333A41600F076A8 /* WalletSplashButtonRowItem.swift in Sources */, + D07C6D7B2346547600468B1A /* Keychain.swift in Sources */, + 9F9206F020727AF30054E581 /* ChangePhoneNumberContainerView.swift in Sources */, C239BE951E61F15600C2C453 /* LoginOthersAccountModal.swift in Sources */, + D07450F12340DC8200769D7F /* WalletConfiguration.swift in Sources */, C2B6488C1EB2533800BA574B /* TGGifConverter.m in Sources */, - C2EA53471F751EF300C183F7 /* GalleryMessageEntry.swift in Sources */, - C22674411DC13165000BA9ED /* GridHoleItem.swift in Sources */, C250B01A1DB66B44004E9FBE /* InAppLinks.swift in Sources */, + 9FF1DEA6225B699D009512C9 /* SearchUtils.swift in Sources */, + A7C1379E23DF21EA00803ED3 /* ChatListRevealItem.swift in Sources */, C22A899F1EBCC2AA005B963D /* CallNavigationHeaderView.swift in Sources */, C27A6F711ECF604A00C65577 /* TGCurrencyFormatter.m in Sources */, + A742CDCD240FB32F00C6B69B /* ChatListFilterRecommendedItem.swift in Sources */, C2A506771DF5664F00971A93 /* ForwardChatListController.swift in Sources */, C20232AA1D81D19C007C9ADE /* ChatRowItem.swift in Sources */, - C2271F3A1DB4D0540045E719 /* EStickersViewController.swift in Sources */, + 9FA0E53B2052EDFF001E5649 /* HackUtils.m in Sources */, + 9FC4DA9E21DD1C6C003E2A62 /* ImageCompression.swift in Sources */, C2271DAC1DAE1116001792B6 /* PeerInfoEntries.swift in Sources */, C25C132D1E8A404F00AE26A1 /* InstalledStickerPacksController.swift in Sources */, + 9F10CE9A206284F8002DD61A /* InputDataDateRowItem.swift in Sources */, C24DABA41E083185005EE404 /* YTVimeoURLParser.m in Sources */, + D0FFC4AC23E184BA0044D305 /* ChatListFilterController.swift in Sources */, C22674311DBE9B50000BA9ED /* FetchCachedRepresentations.swift in Sources */, C25C13311E8A406D00AE26A1 /* FeaturedStickerPacksController.swift in Sources */, + D0A2764E249C9D6B005E3C77 /* PeerPhotos.swift in Sources */, + 9FC8ADA22062E2DF0094F7B4 /* PassportNewPhoneNumberRowItem.swift in Sources */, + 9F0E6F7A203EFE870086699C /* Preferences.swift in Sources */, + 9F0AE6BE2199BEBE00A8B53A /* SVideoController.swift in Sources */, + 9F580BE520A0AA7B00F6D56C /* ChatRecorderOverlayWindow.swift in Sources */, C2016F3B1EAA4538003AF981 /* RecentPeerRowItem.swift in Sources */, + 9FDD78D121C8F0CC00F1B4EF /* ChatPollItem.swift in Sources */, C25F71481E410DEE0046AF4E /* InAppNotificationSettings.swift in Sources */, C250B0311DB78869004E9FBE /* QuickLookPreview.swift in Sources */, + D0BDD50123A38660002010A5 /* InactiveChannelsController.swift in Sources */, C2DE5D3A1F3CAD990081EC1E /* InstantPageLayout.swift in Sources */, + 9F153D1621F3662700B95D82 /* StoredMessageFromSearchPeer.swift in Sources */, + 9F7D422A22240404007B68BB /* AccountContext.swift in Sources */, + 9F21A7CF21C1552D0037784F /* InstantPageTheme.swift in Sources */, C21B24681EDB116B00FC6CDA /* NumberPluralizationForm.m in Sources */, C2EA17811E2FD50A00887153 /* ImageUtils.swift in Sources */, + 9F147F7C2231543800D71BD1 /* PeerUtils.swift in Sources */, C219E1E41D8AC8370042F0C8 /* ChatUnreadRowItem.swift in Sources */, + D02F0A0422E88C9900553411 /* UniversalSoftwareVideoSource.swift in Sources */, C296AF7F1D8D38E5001DBB59 /* ChatRowView.swift in Sources */, C2F9C4481F9500B4002B2CBF /* TwoStepVerificationUnlockController.swift in Sources */, C2B6A9651E8519FA00A441B7 /* RecentGIFRowItem.swift in Sources */, + C2E8BA071FB5EF4C00DEB5E2 /* GalleryThumbsControl.swift in Sources */, C2A1054B1E0163D500B01F48 /* GalleryPageController.swift in Sources */, + D0D752C822F8A76100E2CB74 /* Geocoding.swift in Sources */, C271EB901EB8E6A40034792D /* SelectivePrivacySettingsPeersController.swift in Sources */, + D017A3FB2339404D0086174B /* WalletTransactionTextItem.swift in Sources */, C209C3731F262537009231FE /* emoji_suggestions_data.cpp in Sources */, - C2FD34131E6C2503008D13D4 /* LegacyImportAuthorization.swift in Sources */, + D0BEB98F21628C250055B718 /* EditImageModalController.swift in Sources */, C28BAB271DF980DE0027CE3A /* AudioRecorder.swift in Sources */, C21B24651EDB115700FC6CDA /* StringPluralization.swift in Sources */, C248BD291E706DDA004B9106 /* RecentSessionRowItem.swift in Sources */, + 9F77B3982211979B003B65B8 /* AutoplayPreferences.swift in Sources */, C2DF47BE1DE79574003AA6C0 /* opus_header.c in Sources */, + A778DC2A23C75F1100DD307B /* Confetti.swift in Sources */, C2B9BE891EFC5E7000D6B96F /* Appearance.swift in Sources */, + C256A9161FB9E1490043D497 /* AdditionalSettings.swift in Sources */, C29F4C781F45FBFF00DBFC00 /* InstantPageSlideshowItem.swift in Sources */, C2DF47D01DE82475003AA6C0 /* OpusAudioPlayer.swift in Sources */, C2B1A1111D9FD0A100ACB1DD /* ChatInterfaceInteraction.swift in Sources */, C2B1A0EF1D9D94CE00ACB1DD /* SeparatorRowItem.swift in Sources */, C29340F11F506C310074991E /* EmptyGroupstickerSearchRowItem.swift in Sources */, + 9F9B5EB522573FA700728CDC /* FFMpegAVIOContext.m in Sources */, + D09D9DF4229C289F00378796 /* GZip.m in Sources */, C2FD33F21E69CF6D008D13D4 /* ChatUrlPreviewModel.swift in Sources */, C225524F1F7C03B50007944D /* VideoRecorderPipeline.swift in Sources */, + D001E976243B385E009025F9 /* FolderIcons.swift in Sources */, C2B648891EB0C48600BA574B /* PIPVideoWindow.swift in Sources */, + 9F3EAB4B20A5F90B003FE7E3 /* TGPassportMRZ.m in Sources */, C2B6488E1EB25A5400BA574B /* FetchVideoMediaResource.swift in Sources */, + D06C7BB7247D6FA900E67C3C /* SampleBufferPool.swift in Sources */, C2A72D901DEC66F300C3B945 /* LoginErrorStateView.swift in Sources */, + 9F0AE6872191D29D00A8B53A /* ContextSearchMessageItem.swift in Sources */, + A7565EAE23BE02AF0031EADE /* ChatGradientModel.swift in Sources */, + 9F0AE6BC2199BBB900A8B53A /* MediaPlayerView.swift in Sources */, + 9F7D421F22203DB1007B68BB /* ChannelStatisticsController.swift in Sources */, C2271F491DB4FC220045E719 /* EStickView.swift in Sources */, + 9F153D1421F0C7F800B95D82 /* WallpaperPreviewController.swift in Sources */, + 9F6B54C821369B4000748FC1 /* GalleryModernControls.swift in Sources */, C2271F541D9D420B00424F7B /* ContactsController.swift in Sources */, - C24D9FDC1E267932002CD3F3 /* PreviewSenderItems.swift in Sources */, - C2271F511D9C392400424F7B /* SPopoverRowView.swift in Sources */, C258D1B61F8D3A0D00458478 /* PreHistoryControllerStructures.swift in Sources */, C2C98FEF1E818FB5009CBDB7 /* ClearUserNotifies.swift in Sources */, C2B1B1201E5F170A00895E0D /* ValidateAddressNameInteractive.swift in Sources */, + A7831B1C2403CFDD0056AEAC /* ChannelStatsViewController.swift in Sources */, + A7C1377D23D1A62800803ED3 /* PeerEmptyHolderItem.swift in Sources */, + A778DC4223CD9F3C00DD307B /* SearchSettingsEmptyItem.swift in Sources */, C2DE5D381F3CAD010081EC1E /* InstantPageTextStyleStack.swift in Sources */, C29E0EE21F4EFB5100C0C7A8 /* GroupStickerSetController.swift in Sources */, C29B5F4B1DC8ED5900D13E65 /* ForwardPanelModel.swift in Sources */, + 9FFAE4F3205A8C89000C028E /* MediaPlayerAudioRenderer.swift in Sources */, + D0D81AEE243E2D6F00CB9D20 /* ChatListFilterFolderIconController.swift in Sources */, + D0D7520B24C04FD60037D73A /* VideoEditorScrubbler.swift in Sources */, + D074A56524A1DE7700E92F8A /* OngoingCallContext.swift in Sources */, C25911371DF1A68200671E72 /* ChatInputRecordingView.swift in Sources */, C2271DB31DAE127A001792B6 /* GeneralRowView.swift in Sources */, C2167E511DC220E900F98E03 /* PeerMediaMusicRowContent.swift in Sources */, + 9F62AE83202D8759007FB557 /* FetchMediaUtils.swift in Sources */, C2A506751DF438B900971A93 /* AudioWaveformView.swift in Sources */, C2271DB51DAE131C001792B6 /* GeneralInteractedRowItem.swift in Sources */, + 9FDA713B20EA9532001ED8ED /* ReadArticlesListPreferences.swift in Sources */, + D093474E242DCFC4000ECA88 /* PeerMediaGroupPeersController.swift in Sources */, + D07B558223338AA300F076A8 /* WalletInfoController.swift in Sources */, C25C13331E8A433700AE26A1 /* TableUtils.swift in Sources */, C24949141E5B763F00D7ED5D /* ApplicationContext.swift in Sources */, C219E1F61D8C09130042F0C8 /* ChatMessageItem.swift in Sources */, C209C3741F262537009231FE /* emoji_suggestions.cpp in Sources */, C2A71CE31DDB2EBD00C69F73 /* GeneralSettingsViewController.swift in Sources */, + A7C1379223DB00D900803ED3 /* ChatListFilterPreferences.swift in Sources */, C24DABBD1E0834B7005EE404 /* XCDYouTubeClient.m in Sources */, C2271D9C1DACC027001792B6 /* SearchController.swift in Sources */, + D071E8B3214C15C7001B6024 /* TouchBarEmojiPicker.swift in Sources */, C234A8001ED73A3300EBBECE /* LanguageRowItem.swift in Sources */, - C226743F1DC12E4E000BA9ED /* GridMessageItem.swift in Sources */, + D02F0A0222E88C6E00553411 /* MediaPlayerFramePreview.swift in Sources */, + 9F7B5FCD220099220087D020 /* WallpaperPatternPreview.swift in Sources */, C250B02E1DB7805E004E9FBE /* ChatLayoutUtils.swift in Sources */, + D0FCA7622434867400B72F18 /* PeerInfoHeadItem.swift in Sources */, C2271DBF1DAE563D001792B6 /* PeerInfoHeaderItem.swift in Sources */, + A7F2831B239A496400742C20 /* ContextShowPeersHolder.swift in Sources */, C2A71CED1DDB3C1000C69F73 /* ChatHeaderController.swift in Sources */, - C2C5C2051EF822B900AEA252 /* ProxySettingsViewController.swift in Sources */, C2203EA21DDE2AB8001E6AB6 /* ChatSelectText.swift in Sources */, + 9F4EEF8021D3C76E002C3B33 /* InstantPageContentView.swift in Sources */, + D074A56C24A1EB4000E92F8A /* OngoingCallThreadLocalContextWebrtc.mm in Sources */, + 9FFAE504205A916B000C028E /* VideoPlayerProxy.swift in Sources */, C2CBCAC01D81528700142EC0 /* System.swift in Sources */, C221ED5E1EA6B36600471C65 /* ChatStorageManagmentModalController.swift in Sources */, + 9FB14FBF209889A500688EF9 /* EDSunriseSet.m in Sources */, C21AAE361DB22CA5007638C5 /* AvatarLayer.swift in Sources */, C2DF47E01DE846AB003AA6C0 /* ChatAudioContentView.swift in Sources */, C29B5F4F1DC8F39A00D13E65 /* FWDNavigationAction.swift in Sources */, @@ -3313,108 +5539,197 @@ C2FD33E91E696A86008D13D4 /* GroupsInCommonViewController.swift in Sources */, C21795E31E795955006A2AA3 /* SecretChatKeyViewController.swift in Sources */, C2DF47C21DE79574003AA6C0 /* info.c in Sources */, + C2CE43E420E2CFE800656543 /* PlayerListController.swift in Sources */, + 9F21A7DC21C290E00037784F /* InstantPageTableItem.swift in Sources */, D098C73A1D7E1C40007784E4 /* AuthController.swift in Sources */, + C2905E1C207E4D9E00990AD7 /* InstantPageAudioView.swift in Sources */, + D014193E22AE9A90008667CB /* GeneralLineSeparatorRowItem.swift in Sources */, C29A65011E0837A70071BCEF /* XCDYouTubeVideoWebpage.m in Sources */, C26505971E041B91001954DC /* MGalleryGIFItem.swift in Sources */, - C2A71CE91DDB342100C69F73 /* NotificationSettingsViewController.swift in Sources */, + 9FF5A1CB2232A2FF00BC1359 /* UpgradedAccount.swift in Sources */, + 9FFAE4FD205A8C89000C028E /* FFMpegMediaVideoFrameDecoder.swift in Sources */, + 9FA0E5342051A41A001E5649 /* PreUploadManager.swift in Sources */, + D076F88D2296FD18004F895A /* DiscussionHeaderItem.swift in Sources */, + D0A27650249CE588005E3C77 /* GroupsStatsController.swift in Sources */, C2CFCAC01EBB9C8100843F6A /* CallAudioPlayer.swift in Sources */, C230B9241DD4C78E0057F596 /* ChatGIFMediaItem.swift in Sources */, C230B9161DD39E780057F596 /* CreateGroupViewController.swift in Sources */, C276383E1E8A9A86009E7839 /* StickerSetTableRowItem.swift in Sources */, + A7393D442408F84300CE44CA /* DiceCache.swift in Sources */, + D0B6D72A22AA65D3008E36FE /* NewContactController.swift in Sources */, + D076F88F2297285F004F895A /* DiscussionSetModalController.swift in Sources */, C2DF47CE1DE79751003AA6C0 /* ATQueue.m in Sources */, C2C9B9341E8016C400380D79 /* NSObject+SPInvocationGrabbing.m in Sources */, + D07450D5233B8D2900769D7F /* WalletTransactionDateStickItem.swift in Sources */, C224A72D1EB75A3100F43F3F /* ExMajorNavigationController.swift in Sources */, - C271EB9B1EB9DEF00034792D /* CallBridge.mm in Sources */, + C271EB9B1EB9DEF00034792D /* OngoingCallThreadLocalContext.mm in Sources */, + A7831B2A24040B6B0056AEAC /* StatisticRowItem.swift in Sources */, C2B1B1261E5F840B00895E0D /* PeerInfoUtils.swift in Sources */, C2D187F01E28C58C0038961D /* ContextSwitchPeerRowItem.swift in Sources */, C24DABA21E083185005EE404 /* YTVimeoExtractor.m in Sources */, + D017A3F523390D8C0086174B /* WalletInfoTransactionItem.swift in Sources */, + 9F9B5EB222573E8A00728CDC /* FFMpegAVFormatContext.m in Sources */, C26A71991DC9FA5100F69385 /* EditMessageModel.swift in Sources */, + D007AB9F234C8DCB0022C27F /* WalletPasscodeTimeout.swift in Sources */, C2AF011D1F03D4C600D8AC1D /* TGCallAesCtr.m in Sources */, C250B0231DB6927C004E9FBE /* WPArticleContentView.swift in Sources */, + 9FFAE509205A92B9000C028E /* RingBuffer.m in Sources */, C2271F401DB4D5850045E719 /* ETabRowView.swift in Sources */, C23C5AE11E1136D1005903E1 /* GroupNameRowItem.swift in Sources */, + D04D2144230DB55B00609388 /* TelegramIconsTheme.swift in Sources */, C25C132F1E8A405E00AE26A1 /* ArchivedStickerPacksController.swift in Sources */, - C29F4C761F45F58B00DBFC00 /* MIHSliderView.m in Sources */, + D01E1F0422B39A4800AD6DAE /* LottiePlayer.swift in Sources */, C2DF47C31DE79574003AA6C0 /* internal.c in Sources */, + D004167522D37AD00000566B /* StickerPackPanelRowItem.swift in Sources */, C295C65F1F75808600BA309D /* ChatAdditionController.swift in Sources */, + D001E974243B1A0A009025F9 /* LeftSidebarController.swift in Sources */, C2271F571D9D46BC00424F7B /* ShortPeerRowItem.swift in Sources */, C291942F1DCC6E2200359491 /* DeclareEncodables.swift in Sources */, + A7D2822F236C549B0000A9BF /* SyncCoreExtension.swift in Sources */, C2A315BB1E2E697200D89000 /* TwoStepVerification.swift in Sources */, C296AF881D8DB2A7001DBB59 /* ChatMediaContentView.swift in Sources */, C2DE5D401F3CB0380081EC1E /* InstantPageTileView.swift in Sources */, C2271DBB1DAE213D001792B6 /* ChannelInfoEntries.swift in Sources */, + 9FC8AD9C2062AA630094F7B4 /* ValuesSelectorModalController.swift in Sources */, + A7393D462409044C00CE44CA /* ChannelOverviewStatsRowItem.swift in Sources */, C221ED581EA687B600471C65 /* AutomaticMediaDownloadCategoryPeers.swift in Sources */, + 9F1BABB021E60DCC0075C03E /* UndoOverlayHeaderView.swift in Sources */, C24BA3BD1E9D30F800E8970B /* DeleteSupergroupMessagesModalController.swift in Sources */, C20232AC1D81D1AE007C9ADE /* ChatMessageView.swift in Sources */, C209C3771F26271B009231FE /* EmojiSuggestionBridge.mm in Sources */, + D071E8B5214C1935001B6024 /* TouchBarEmojiItemView.swift in Sources */, C250B0291DB769EE004E9FBE /* WPMediaContentView.swift in Sources */, + D071E8A8214BBE21001B6024 /* ChatStickersTouchBarPopover.swift in Sources */, C2B1A0F11D9D94E400ACB1DD /* SeparatorRowView.swift in Sources */, C232E9F61E1437B100C4D38C /* SearchResultModalController.swift in Sources */, + 9F21A7D521C16CB90037784F /* InstantPagePeerReferenceItem.swift in Sources */, + A7D28237236C5B2C0000A9BF /* PhoneNumberUtils.swift in Sources */, C2A16E551E117BD000585A85 /* CreateChannelViewController.swift in Sources */, + 9F7D422C222415C9007B68BB /* SharedAccountContext.swift in Sources */, + 9F1AE5E420B6D7AA002A9D8D /* LocationPlaceSuggestionRowItem.swift in Sources */, + A789E08423E05EAE00AEB34A /* ChatListFiltersListController.swift in Sources */, + 9FFAE4ED205A8C89000C028E /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */, + D0C39EC3233A5D9B003CD402 /* WalletTransactionPreviewController.swift in Sources */, C2D187EE1E28B9CB0038961D /* ShareInlineResultNavigationAction.swift in Sources */, C226742B1DBE16B9000BA9ED /* CachedResourceRepresentations.swift in Sources */, + 9F354E9C2270630A006F1D42 /* HapticEngine.swift in Sources */, C2167E571DC221E600F98E03 /* PeerMediaRowContent.swift in Sources */, + D08E5C9822C583CE007B1C09 /* Tuple.swift in Sources */, + D0DD91FE246A8A380039D83D /* PeerMediaGifsController.swift in Sources */, + D0675D84217F1F27004900A7 /* ArchiverContext.swift in Sources */, C248BD261E706A05004B9106 /* RecentSessionsController.swift in Sources */, + 9FA0E53D205693DA001E5649 /* WebSessionsController.swift in Sources */, C2DF47BA1DE79574003AA6C0 /* bitwise.c in Sources */, C250B01F1DB67B42004E9FBE /* WPLayout.swift in Sources */, + D0D7520924C03CD60037D73A /* VideoEditorThumbs.swift in Sources */, + 9F0367F22272108800456348 /* ProxyQRCodeRowItem.swift in Sources */, D098C7191D7E175A007784E4 /* AppDelegate.swift in Sources */, C2C73A5B1EEAF3AE00DB8420 /* ChannelEventFilterModalController.swift in Sources */, + 9F9B5EB82257402200728CDC /* FFMpegAVCodec.m in Sources */, C2271DD21DAF6DF5001792B6 /* EmptyChatViewController.swift in Sources */, C23BC37E1E9BB28F00D79F92 /* AddContactModalController.swift in Sources */, + 9F3EAB4420A5ED2F003FE7E3 /* ocr.mm in Sources */, C2BB120A1ED87C5A00BDE46A /* ControllerExtension.swift in Sources */, + A7B5031323B62E1A00C9838E /* Svg.m in Sources */, + A767DD4123F2BB3200366F76 /* ShortcutListController.swift in Sources */, + 9F62AE81202D85E7007FB557 /* FetchManagerLocation.swift in Sources */, C230B91F1DD4BEBC0057F596 /* ChatGIFContentView.swift in Sources */, C276248C1D95AF7600FE5B2B /* ObjcUtils.m in Sources */, + 9F1668C82008F30900DD39FB /* ChatBackgroundView.swift in Sources */, + 9FA0E53F2056E159001E5649 /* WebAuthorizationRowItem.swift in Sources */, + D0276B7D22BD6511003155D8 /* DisplayLink.swift in Sources */, + 9F0B8F171FFB7F1A00073D3F /* AccentColorRowItem.swift in Sources */, C2DF47EE1DE9A1F3003AA6C0 /* LoginViewController.swift in Sources */, C2DE5D301F3CA7C00081EC1E /* InstantPageLinkSelectionView.swift in Sources */, + A742CE45241A517800C6B69B /* ChannelRecentPostRowItem.swift in Sources */, + D0CD309822197E62005D23DD /* FFMpegGlobals.m in Sources */, + D017A3F12338DE1E0086174B /* WalletTestWordsItem.swift in Sources */, C2271DCA1DAED681001792B6 /* PresenceStrings.swift in Sources */, + D02BD7C5232D204F00D1814A /* ThemeListRowItem.swift in Sources */, + C224675D1FA884E300F03E27 /* ChatGroupedItem.swift in Sources */, C2271DD71DAF80D5001792B6 /* PeerMediaController.swift in Sources */, + 9F1BABAE21E5ECE70075C03E /* ChatUndoManager.swift in Sources */, + A7F2830823954EF800742C20 /* CachedInstantPages.swift in Sources */, C2DF47E41DE84A67003AA6C0 /* ChatVoiceRowItem.swift in Sources */, C28BAB2C1DF9C2790027CE3A /* DateUtils.mm in Sources */, C2A5067B1DF5BE6900971A93 /* GeneralTextRowItem.swift in Sources */, + D017A3EB2334F5680086174B /* WalletBalanceItem.swift in Sources */, C2A8E1481F7D2612000FD5E3 /* TGVideoCameraGLRenderer.m in Sources */, + D05F392322FB45450040F341 /* ScheduledMessageModalController.swift in Sources */, C209C38D1F276D4C009231FE /* ChatSearchView.swift in Sources */, C2777B601DCF4766008B69DD /* CoreExtension.swift in Sources */, + A7029EF8240E3A5400A89ABD /* ChatListFiltersHeaderItem.swift in Sources */, C2A16E3E1E11674F00585A85 /* CreateGroupSignals.swift in Sources */, + D06C7BB9247E664900E67C3C /* SoftwareVideoThumbnailLayer.swift in Sources */, C2C9B9371E8016D400380D79 /* SPMediaKeyTap.m in Sources */, C21B24631EDADC8600FC6CDA /* MMMenuItem.swift in Sources */, C2A71CE71DDB2F8700C69F73 /* UsernameSettingsViewController.swift in Sources */, C2E064641ECCB24000387BB8 /* NativeCallSettingsViewController.swift in Sources */, - C21AAE341DB0F6BC007638C5 /* MediaTitleBarView.swift in Sources */, + 9F3EAB4620A5ED2F003FE7E3 /* genann.c in Sources */, + D00CE50F2289CE87008C1B4F /* ChatAnimatedStickerItem.swift in Sources */, + D07C6D7923464C8000468B1A /* WalletKeychainController.swift in Sources */, C230BEB41EE97B6F0029586C /* RestictedModalViewController.swift in Sources */, C22E063D1D80929400A11C88 /* ChatListRowItem.swift in Sources */, C20D5AB11DA996480042616A /* EBlockItem.swift in Sources */, + 9F17E5B9212F173900C25A65 /* AutoNightViewController.swift in Sources */, C2167E531DC220F600F98E03 /* PeerMediaFileRowContent.swift in Sources */, C22674451DC20664000BA9ED /* PeerMediaListController.swift in Sources */, + D076A07E248A5F890077BC0A /* GifPanelTabRowItem.swift in Sources */, + 9FB7CB6D221EB22700888EA9 /* CallSettingsModalController.swift in Sources */, + 9FFAE4F8205A8C89000C028E /* FFMpegMediaFrameSource.swift in Sources */, C230B8F11DD348970057F596 /* AccountInfoItem.swift in Sources */, C2FBC1E71DC631980063A23B /* SPPreviewController.swift in Sources */, + D017A3FD233949B00086174B /* WalletSendController.swift in Sources */, C230B8EF1DD3358C0057F596 /* AccountViewController.swift in Sources */, + 9F4EEF8621D4FA68002C3B33 /* InstantPageArticleItem.swift in Sources */, + 9F18DD94206D8FFD00A2AAD0 /* SecureIdVerificationDocument.swift in Sources */, + 9F7B741122296FD7006610E4 /* SharedNotificationManager.swift in Sources */, C25F714A1E4110C50046AF4E /* ApplicationSpecificPreferencesKeys.swift in Sources */, - C226743A1DC1273E000BA9ED /* PeerMediaGridController.swift in Sources */, + 9F7B740F2229618E006610E4 /* SharedWakeupManager.swift in Sources */, C24DABA31E083185005EE404 /* YTVimeoExtractorOperation.m in Sources */, + 9F10CE8E20617C36002DD61A /* PassportInsertPasswordItem.swift in Sources */, + D071E8AC214BE6E7001B6024 /* TouchBarScrubberHeaderItemView.swift in Sources */, C253E23E1DE61CB50022A29F /* ContextListRowItem.swift in Sources */, C2271DB11DAE126B001792B6 /* GeneralRowItem.swift in Sources */, C2A87DE81F4C6910002D3F73 /* InstantViewWindow.swift in Sources */, + 9F21F65520B5A72800332C85 /* LocationModalController.swift in Sources */, C2777B681DD20DD2008B69DD /* ChatTitleBarView.swift in Sources */, C246161B1ED33FFE0026D5BC /* InstantVideoPIP.swift in Sources */, + D0CBB0F52492974F00620C65 /* VideoAvatarModalController.swift in Sources */, + 9FFAE4F5205A8C89000C028E /* MediaTrackFrameDecoder.swift in Sources */, + D0D81AF8243F69AD00CB9D20 /* PollTimerView.swift in Sources */, C26A37EC1E5DE465006977AC /* ChannelAdminsViewController.swift in Sources */, C29C3E5B1E421F1700193A7E /* ChatAccessoryModel.swift in Sources */, C2BB2DB01F8BDF6700520255 /* Config.swift in Sources */, + 9FC4DA9A21DD0BE6003E2A62 /* CachedChannelAdmins.swift in Sources */, + 9F4EEF7E21D3C3E3002C3B33 /* InstantPageDetailsItem.swift in Sources */, C2A315B11E2E656100D89000 /* CalendarMonthController.swift in Sources */, C275932E1DF6E1CE00A0807A /* AboutModalController.swift in Sources */, + 9F1C279321D38A96003CD033 /* InstantPageScrollableItem.swift in Sources */, C2271F591D9D46CA00424F7B /* ShortPeerRowView.swift in Sources */, + C251FB4C1FEDCC750035E5D7 /* ChatPresentationUtils.swift in Sources */, C29B5F411DC7859800D13E65 /* InputContextHelper.swift in Sources */, + 9F7B74052227F4F2006610E4 /* SharedAccountInfo.swift in Sources */, C24FD40E1F20EE8B00A97196 /* ContextClueRowItem.swift in Sources */, + D007ABA1234CACE40022C27F /* WalletCreateInvoiceController.swift in Sources */, C29A65051E0845B80071BCEF /* ExternalVideoLoader.swift in Sources */, + 9FFAE4FA205A8C89000C028E /* MediaPlaybackData.swift in Sources */, C2FD33EE1E697B31008D13D4 /* TransformOutgoingMessageMedia.swift in Sources */, + D0558D852152B4D3006B403D /* GalleryTouchBar.swift in Sources */, C253A9451D90303200CDC850 /* FastBlur.m in Sources */, + D0D3CE6C23D465FA00864F3C /* PollResultStickItem.swift in Sources */, C2057FAA1EBC6A3C000423DC /* ChatCallRowItem.swift in Sources */, C253E23A1DE4D3DB0022A29F /* ChatInterfaceInputContext.swift in Sources */, C24D9FC41E24FFF3002CD3F3 /* PasscodeLockController.swift in Sources */, + 9F10CE942061C8C8002DD61A /* InputDataControllerEntries.swift in Sources */, + A71DC82F2386AADF000EEDE2 /* Signature.swift in Sources */, + D02BD7C0232D0FB800D1814A /* AppAppearanceViewController.swift in Sources */, C2A315AF1E2E64BE00D89000 /* CalendarController.swift in Sources */, C296AF8A1D8E9F94001DBB59 /* ChatInteractiveContentView.swift in Sources */, C2271F2B1DB3BEB60045E719 /* GlobalHandlers.swift in Sources */, + C224675B1FA8546200F03E27 /* GroupedLayout.swift in Sources */, C230BEBA1EE9AEB80029586C /* ChannelEventLogItem.swift in Sources */, - C26A37EE1E5DE48F006977AC /* ChannelBlacklistViewController.swift in Sources */, + C26A37EE1E5DE48F006977AC /* ChannelBlocklistViewController.swift in Sources */, C205DB981EE71127003711DF /* ChannelAdminController.swift in Sources */, C22674381DC125C1000BA9ED /* PeerMediaCollectionInterfaceState.swift in Sources */, C2271F3C1DB4D0630045E719 /* GIFViewController.swift in Sources */, @@ -3422,105 +5737,173 @@ C2303E781D997C3100098E12 /* ChatInputAttachView.swift in Sources */, C296707B1F0FBFB500884DA2 /* ThemeSettings.swift in Sources */, C248BD241E706104004B9106 /* BlockedPeersViewController.swift in Sources */, + 9F9B5EC22257443600728CDC /* FFMpegPacket.m in Sources */, + D0558D7C215141CF006B403D /* ChatInfoTouchbar.swift in Sources */, + D0558D872154047E006B403D /* GalleryTouchBarThumbItemView.swift in Sources */, C230B9111DD3866A0057F596 /* ComposeActions.swift in Sources */, + 9F3D5F6322044D8700CB0CAA /* AppUpdateViewController.swift in Sources */, C24DAB861E08026C005EE404 /* MGalleryVideoItem.swift in Sources */, + 9F9483B1202AF816006E873D /* CrashHandler.swift in Sources */, C2DE5D3C1F3CAE120081EC1E /* InstantPageTextItem.swift in Sources */, + D0A75F2A24487A1E001F84A0 /* EditImageCanvasControls.swift in Sources */, C221ED5A1EA6896400471C65 /* VoiceCallSettings.swift in Sources */, C253E23C1DE4D4080022A29F /* ChatInterfaceStateContextQueries.swift in Sources */, C253E22E1DE33A7C0022A29F /* ChatMusicRowItem.swift in Sources */, + D09D9DF1229C27A700378796 /* AnimatedStickerUtils.swift in Sources */, + 9F7943B220855DC200FEDB81 /* ProxyListController.swift in Sources */, C296AF8C1D8EA7F3001DBB59 /* ChatFileContentView.swift in Sources */, C219E1FC1D8D35FA0042F0C8 /* ChatMediaItem.swift in Sources */, - C21178001F16BB8300AC706D /* BioViewController.swift in Sources */, C234D4121EEDE6990017DC25 /* LoadingTableItem.swift in Sources */, - C230B8F61DD371430057F596 /* SPopoverViewController.swift in Sources */, - C22674331DBF665A000BA9ED /* EStickerPackEntries.swift in Sources */, + 9F1962D82101458C00FFF048 /* VCardLocationRowItem.swift in Sources */, + 9FC8ADA42063B6450094F7B4 /* PassportTwoStepVerificationIntroItem.swift in Sources */, + 9F4EEF8221D4F584002C3B33 /* ImageTransparency.swift in Sources */, C2FD33F81E6C1486008D13D4 /* SSKeychain.m in Sources */, C230B9221DD4C24E0057F596 /* GIFPlayerView.swift in Sources */, C2CBCAC51D81649E00142EC0 /* ChatListRowView.swift in Sources */, C29E0EE01F4DC43100C0C7A8 /* InstantViewAppearance.swift in Sources */, C20232A81D81D189007C9ADE /* ChatController.swift in Sources */, + D017A3F9233929B10086174B /* WalletReceiveController.swift in Sources */, + D00C73B62302C196004B1E2B /* ChatScheduleController.swift in Sources */, + D0C39EC5233A6077003CD402 /* GeneralBlockTextRowItem.swift in Sources */, C26E82D11E83EFFE0046DF2F /* TimeObserver.m in Sources */, C253A96D1D92F69C00CDC850 /* ReplyModel.swift in Sources */, + D076F891229823DA004F895A /* ChannelDiscussionInputView.swift in Sources */, + 9F72974020BD9C6A0067F815 /* VideoPlayer.swift in Sources */, + A7F283002395168300742C20 /* SettingsSearchRecentQueries.swift in Sources */, + D0439A2023410D5400C1E6F9 /* WalletUtils.swift in Sources */, + 9F10CE8A20611536002DD61A /* PassportHeaderItem.swift in Sources */, C29C3E731E4397F300193A7E /* StickerPreviewHandler.swift in Sources */, + D04D213F230D68B800609388 /* CustomAccentColorModalController.swift in Sources */, C29A65071E098B610071BCEF /* ShareModalController.swift in Sources */, + D07B55872333903C00F076A8 /* WalletSplashRowItem.swift in Sources */, + C2E8BA0A1FB5F15900DEB5E2 /* GalleryThumbsControlView.swift in Sources */, C2DF47BF1DE79574003AA6C0 /* opusenc.m in Sources */, C2DF47E21DE846B8003AA6C0 /* ChatMusicContentView.swift in Sources */, C2271F381DB4D0490045E719 /* EmojiViewController.swift in Sources */, + D071E8A421496F38001B6024 /* ChatListTouchBar.swift in Sources */, + A7C7215723FD45D300CE3F75 /* SaveModalController.swift in Sources */, + 9F147F6F223014EB00D71BD1 /* PasscodeControllers.swift in Sources */, C2271DAA1DAD8716001792B6 /* PeerInfoController.swift in Sources */, + D001E978243B4639009025F9 /* LeftSidebarFolderItem.swift in Sources */, C22B635C1D96A14E00085C19 /* ChatInputView.swift in Sources */, + D076F8872296CAB2004F895A /* ChannelDisscussionGroup.swift in Sources */, + A7F2830A2395570400742C20 /* SearchSettingsController.swift in Sources */, C253E2341DE3776A0022A29F /* InlineAudioPlayerView.swift in Sources */, C26D8A3C1E464944002FAA3F /* JoinLinkPreviewModalController.swift in Sources */, C2E52A0D1EB8C386009AF87D /* SelectivePrivacySettingsController.swift in Sources */, - C21656E81EE576FA0041A6BA /* ChatUserPopover.swift in Sources */, + 9F13EE172100B05300562E53 /* VCardContactController.swift in Sources */, + 9FFAE502205A9154000C028E /* ManagedAudioSession.swift in Sources */, C2AC9C181E1E687E0085C7DE /* GlobalBadgeNode.swift in Sources */, C275E9F51F8FEDEB00D3D8C0 /* PhoneNumberConfirmController.swift in Sources */, C2DE5D281F3CA5FE0081EC1E /* InstantPageItem.swift in Sources */, - C2084F041F5D5C6F004713C4 /* ChatReplyPreviewController.swift in Sources */, - C226741F1DBCEAC2000BA9ED /* EStickerGridEntries.swift in Sources */, C2FD33F91E6C1486008D13D4 /* SSKeychainQuery.m in Sources */, C2DF47961DE71160003AA6C0 /* GIFContainerView.swift in Sources */, - C2271F4F1D9C38F500424F7B /* SPopoverRowItem.swift in Sources */, C2FD34151E6C9003008D13D4 /* BaseApplicationSettings.swift in Sources */, + C27BAC7A20CFCE68007A7508 /* PassportSettingsHeaderItem.swift in Sources */, + D076F8702295B24D004F895A /* InlineAuthOptionRowItem.swift in Sources */, C253E2261DE335C10022A29F /* ChatVoiceContentView.swift in Sources */, + A7377E1B23ACC79100AD3ADD /* ChatNavigateFailed.swift in Sources */, C2271DB71DAE132C001792B6 /* GeneralInteractedRowView.swift in Sources */, C218FF921F3CC8C700DD7D35 /* InstantPageWebEmbedItem.swift in Sources */, C2F9C44C1F95FE58002B2CBF /* Markdown.swift in Sources */, - C2F9C44A1F9500C3002B2CBF /* TwoStepVerificationUnlockStructures.swift in Sources */, + 9F62AE7E202D85B7007FB557 /* FetchManager.swift in Sources */, C2777B661DCFC649008B69DD /* Localizable.swift in Sources */, C24D9F911E1F8F85002CD3F3 /* MajorBackNavigationBar.swift in Sources */, C2A16E411E1167DA00585A85 /* ChannelIntroViewController.swift in Sources */, C201C2321E3B2D1C0026C21E /* FastSettings.swift in Sources */, C2A315B41E2E65C600D89000 /* CalendarUtils.m in Sources */, + A72D7AE923C471A7005BAC59 /* PollResultController.swift in Sources */, C2DE5D361F3CACCF0081EC1E /* InstantPageLayoutSpacings.swift in Sources */, C2423A541F2235080041907F /* InstantPageViewController.swift in Sources */, C221ED541EA684BE00471C65 /* DataAndStorageViewController.swift in Sources */, - C2AF3B821E5CD79200DFDD81 /* ConvertGroupViewController.swift in Sources */, + 9F3EAB3B20A5A1EC003FE7E3 /* NetworkUsageStatsController.swift in Sources */, + A766493F236D6BFD00163DF4 /* PasscodeSettings.swift in Sources */, + 9FBE0EE1201FBEFC0060FD1C /* DownloadSettingsViewController.swift in Sources */, + 9F52F51A2130286E006FC0B5 /* LocationRequest.swift in Sources */, C2B1B11E1E5F151D00895E0D /* ChannelVisibilityController.swift in Sources */, + A71DC82D23858356000EEDE2 /* Spotlight.swift in Sources */, C205DB9D1EE88765003711DF /* Views.swift in Sources */, + D07B558423338B6B00F076A8 /* WalletSplashController.swift in Sources */, C27AAFE91DE9DA61009B9629 /* CountryManager.swift in Sources */, C253A9611D917ACD00CDC850 /* ChatStickerContentView.swift in Sources */, + A7B5031023B62E0400C9838E /* nanosvg.c in Sources */, + C2E6F3CF1F9F85260023653D /* ContextHashtagRowItem.swift in Sources */, C276248E1D95B4F300FE5B2B /* Extensions.swift in Sources */, C27AAFEF1DEB2EA9009B9629 /* EmptyComposeController.swift in Sources */, + 9F1890932238F3DC00665EF5 /* DownloadedFilesPaths.swift in Sources */, C22B63591D967F4500085C19 /* TGModernGrowingTextView.m in Sources */, C2B4D0C11E34E07100CBC4E6 /* PrettyGridUtils.swift in Sources */, C27A8E991EC9D48000C8E7E7 /* QuickSwitcherModalController.swift in Sources */, C230BEB21EE97B290029586C /* ChannelEventLogController.swift in Sources */, C2777B5B1DCE11A5008B69DD /* SendingClockProgress.swift in Sources */, + C2905E1E207E545600990AD7 /* InstantPageAudioItem.swift in Sources */, + D074A56724A1DF5200E92F8A /* VoipDerivedState.swift in Sources */, + 9FFAE4FC205A8C89000C028E /* MediaTrackDecodableFrame.swift in Sources */, C2F93A2D1F3C55C500BCD48F /* EmojiToleranceController.swift in Sources */, + D071E8A6214A805C001B6024 /* ChatTouchBar.swift in Sources */, C2B1A1271DA3D84900ACB1DD /* ChatInputAccessory.swift in Sources */, + A76C8AA02422132400FDB071 /* VerticalTabsView.swift in Sources */, C218FF961F3DAD7200DD7D35 /* InstantPageSelectText.swift in Sources */, - C25253271DF03F5700ADBC98 /* TGOpusAudioRecorder.m in Sources */, C236ADDE1F7D318700E8C71A /* TGVideoCameraMovieRecorder.m in Sources */, C2DE5D3E1F3CAF2B0081EC1E /* InstantPageShapeItem.swift in Sources */, + 9F18908D2237B5A400665EF5 /* InputURLFormatterModalController.swift in Sources */, C221ED5C1EA69AA300471C65 /* StorageUsageController.swift in Sources */, + D05051902342BA6700D93176 /* WalletSettingsController.swift in Sources */, C2FD382E1DCA1FA3009DC28C /* PreviewSenderController.swift in Sources */, C281498A1EA7F44300BB933E /* ListViewIntermediateState.swift in Sources */, + 9F14CBF32007DEB300F22DA9 /* ChatWallpaperModalController.swift in Sources */, + 9F262D5F21BFD5BC006817CD /* LocalizationPreviewModalController.swift in Sources */, C2FD38321DCA215F009DC28C /* GeneralInputRow.swift in Sources */, C2FF14601E532C0A007B7B14 /* SearchEmptyRowItem.swift in Sources */, C2167E5F1DC25C6900F98E03 /* InterfaceObserver.swift in Sources */, - C29C3E711E43881500193A7E /* StickerPreviewModalController.swift in Sources */, + C29C3E711E43881500193A7E /* ModalPreviewViews.swift in Sources */, + 9F7B74032227F492006610E4 /* ManageSharedAccountInfo.swift in Sources */, + D0CD3095221970F8005D23DD /* FFMpegRemuxer.m in Sources */, + A7F282B2238D122900742C20 /* UnauthorizedConfiguration.swift in Sources */, + 9FC4DA9C21DD187C003E2A62 /* SearchPeerMembers.swift in Sources */, C22E06321D8044CC00A11C88 /* ChatListController.swift in Sources */, + C246D6281FAB72D4004C17FA /* MediaGroupPreviewRowItem.swift in Sources */, + 9F18DD93206D8FFD00A2AAD0 /* SecureIdVerificationDocumentsContext.swift in Sources */, + 9F8DF3C8209228B000AED104 /* EditAccountInfoController.swift in Sources */, + C241025D1FD5702D00DB8625 /* ChatMessageBubbleImages.swift in Sources */, + 9FC8AD9A2062A5610094F7B4 /* InputDataDataSelectorRowItem.swift in Sources */, + D004167722D4AD3B0000566B /* StickerPackItems.swift in Sources */, + 9F14CBF52007DFD400F22DA9 /* Wallpapers.swift in Sources */, C2DF47C11DE79574003AA6C0 /* wav_io.c in Sources */, - C24949121E5B704900D7ED5D /* AccountsListViewController.swift in Sources */, + D06C7BB5247D6F4B00E67C3C /* SoftwareVideoLayerFrameManager.swift in Sources */, + D0CC4ADE22BA5C930088F36D /* LottieBufferCompressor.swift in Sources */, + C241026F1FD58EA900DB8625 /* SImageView.swift in Sources */, + D06C7BBB247FAE3E00E67C3C /* GifPlayerBufferView.swift in Sources */, C24D9FC71E2500AC002CD3F3 /* PasscodeSettingsViewController.swift in Sources */, + 9F3EAB4720A5ED2F003FE7E3 /* fast-edge.cpp in Sources */, + 9F6314E121CAA0AB009FD379 /* NewPollController.swift in Sources */, + 9FFAE4F1205A8C89000C028E /* MediaTrackFrame.swift in Sources */, C2DE5D2A1F3CA69D0081EC1E /* InstantPageMedia.swift in Sources */, C2DE5D2E1F3CA72E0081EC1E /* InstantPageView.swift in Sources */, C2FB2FAB1EBF73D00093C8BA /* RecentCallsViewController.swift in Sources */, - C22674211DBCECCC000BA9ED /* EStickerGridItem.swift in Sources */, - C219E1D91D886A160042F0C8 /* ChatHoleRowView.swift in Sources */, C26505951E0301B4001954DC /* MGalleryItem.swift in Sources */, C2777B641DCFB559008B69DD /* ChatServiceRowView.swift in Sources */, C2303E8A1D9A76D800098E12 /* MainViewController.swift in Sources */, - C22674351DBF6A85000BA9ED /* EStickerPackItem.swift in Sources */, - C2271DC41DAE5E46001792B6 /* PeerInfoHeaderView.swift in Sources */, + 9F1BC1A9223FDE6D00F21815 /* InputSources.swift in Sources */, C2E8694D1F43500D00BDD0A2 /* ChatNavigationMention.swift in Sources */, + 9F63152621D236CB009FD379 /* ForgotPasswordController.swift in Sources */, C2B1A1361DA6587100ACB1DD /* GalleryViewer.swift in Sources */, + 9FC4DA9821DD0B86003E2A62 /* ChannelMemberCategoryListContext.swift in Sources */, + 9F1AE5E620B70328002A9D8D /* LocationSendCurrentItem.swift in Sources */, C253E2311DE34FBB0022A29F /* AudioPlayerController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + A71DC8312386C83E000EEDE2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C232EA901E1D07E700C4D38C /* TelegramShare */; + targetProxy = A71DC8302386C83E000EEDE2 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ C232EA991E1D07E700C4D38C /* ShareViewController.xib */ = { isa = PBXVariantGroup; @@ -3557,6 +5940,8 @@ C21656CC1EE1CC760041A6BA /* it */, C21656CD1EE1CC7C0041A6BA /* de */, C21656CE1EE1CC830041A6BA /* es */, + 9F13CCE22006719400ECF301 /* ru */, + 9F13CCE3200671AF00ECF301 /* uk */, ); name = Localizable.strings; sourceTree = ""; @@ -3570,6 +5955,9 @@ C21656D61EE4A8430041A6BA /* it */, C21656D81EE4A8470041A6BA /* nl */, C21656DA1EE4A8490041A6BA /* pt-BR */, + 9F52E74E2074C12B0048791C /* Base */, + 9F52E7502074C16D0048791C /* ru */, + 9F52E7522074C1710048791C /* uk */, ); name = MainMenu.xib; sourceTree = ""; @@ -3577,7 +5965,7 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - C232EA251E1BC1A100C4D38C /* Release AppStore */ = { + A71DC8392386D6C2000EEDE2 /* Github */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -3613,34 +6001,51 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; - name = "Release AppStore"; + name = Github; }; - C232EA261E1BC1A100C4D38C /* Release AppStore */ = { + A71DC83A2386D6C2000EEDE2 /* Github */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C21074261E77F7A3006EE5EF /* Release.xcconfig */; + baseConfigurationReference = C21074251E77F79A006EE5EF /* Beta.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = NO; CLANG_WARN_SUSPICIOUS_MOVE = NO; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Mac.entitlements"; - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 6N38VWS5BX; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "GITHUB=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", ); - GCC_PREPROCESSOR_DEFINITIONS = "BETA=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; @@ -3648,101 +6053,285 @@ HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/thrid-party/ogg", "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", ); INFOPLIST_FILE = "Telegram-Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Telegram-Mac/libwebp/lib", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", "$(SDKROOT)/usr/lib/system", "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", ); MACOSX_DEPLOYMENT_TARGET = 10.11; + ONLY_ACTIVE_ARCH = YES; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D GITHUB"; + PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; + SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; + A71DC83B2386D6C2000EEDE2 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", + "$(PROJECT_DIR)", + ); + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = "SHARE=1"; + INFOPLIST_FILE = TelegramShare/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; OTHER_CODE_SIGN_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-D DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; + SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; + }; + name = Github; + }; + C232EA251E1BC1A100C4D38C /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = ReleaseAppStore; + }; + C232EA261E1BC1A100C4D38C /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C21074261E77F7A3006EE5EF /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_USE_OPTIMIZATION_PROFILE = NO; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = NO; + CLANG_WARN_SUSPICIOUS_MOVE = NO; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Sandbox.entitlements"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "APP_STORE=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", + TGVOIP_NO_OSX_PRIVATE_API, + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; + HEADERMAP_USES_VFS = YES; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/thrid-party/ogg", + "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", + ); + INFOPLIST_FILE = "Telegram-Mac/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", + "$(SDKROOT)/usr/lib/system", + "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D APP_STORE"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - C232EAA11E1D07E700C4D38C /* Debug Hockeyapp */ = { + C232EAA11E1D07E700C4D38C /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", "$(PROJECT_DIR)", ); + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "SHARE=1", + ); INFOPLIST_FILE = TelegramShare/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-D BETA"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - C232EAA21E1D07E700C4D38C /* Debug AppStore */ = { + C232EAA21E1D07E700C4D38C /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; - CODE_SIGN_IDENTITY = ""; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", "$(PROJECT_DIR)", ); + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = "SHARE=1"; INFOPLIST_FILE = TelegramShare/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - C232EAA31E1D07E700C4D38C /* Release AppStore */ = { + C232EAA31E1D07E700C4D38C /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", "$(PROJECT_DIR)", ); + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = "SHARE=1"; INFOPLIST_FILE = TelegramShare/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = ""; OTHER_SWIFT_FLAGS = "-D APP_STORE"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - C248BD1D1E6ECB24004B9106 /* Release Hockeyapp */ = { + C248BD1D1E6ECB24004B9106 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -3778,20 +6367,20 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - C248BD1E1E6ECB24004B9106 /* Release Hockeyapp */ = { + C248BD1E1E6ECB24004B9106 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = C21074261E77F7A3006EE5EF /* Release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = NO; @@ -3799,13 +6388,26 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Mac.entitlements"; CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "STABLE=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", ); - GCC_PREPROCESSOR_DEFINITIONS = "APP_STORE=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; @@ -3813,50 +6415,67 @@ HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/thrid-party/ogg", "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", ); INFOPLIST_FILE = "Telegram-Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Telegram-Mac/libwebp/lib", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", "$(SDKROOT)/usr/lib/system", "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", ); MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D STABLE"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - C248BD1F1E6ECB24004B9106 /* Release Hockeyapp */ = { + C248BD1F1E6ECB24004B9106 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", "$(PROJECT_DIR)", ); + GCC_PREPROCESSOR_DEFINITIONS = "SHARE=1"; INFOPLIST_FILE = TelegramShare/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-D STABLE"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D098C7201D7E175A007784E4 /* Debug Hockeyapp */ = { + D01E1EC922B2608F00AD6DAE /* HockeyappMacAlpha */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -3898,16 +6517,178 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; - name = "Debug Hockeyapp"; + name = HockeyappMacAlpha; }; - D098C7211D7E175A007784E4 /* Debug AppStore */ = { + D01E1ECA22B2608F00AD6DAE /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D01E1EFF22B261A800AD6DAE /* Alpha.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = NO; + CLANG_WARN_SUSPICIOUS_MOVE = NO; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Mac.entitlements"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "ALPHA=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; + HEADERMAP_USES_VFS = YES; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/thrid-party/ogg", + "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", + ); + INFOPLIST_FILE = "Telegram-Mac/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", + "$(SDKROOT)/usr/lib/system", + "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", + ); + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D ALPHA"; + PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; + SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + D01E1ECB22B2608F00AD6DAE /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = TelegramShare/TelegramShare.entitlements; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/submodules/libtgvoip/build/Debug-iphoneos", + "$(PROJECT_DIR)", + ); + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "SHARE=1", + ); + INFOPLIST_FILE = TelegramShare/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_SWIFT_FLAGS = "-D ALPHA"; + PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram.TelegramShare; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SHARE; + SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; + SWIFT_VERSION = 4.2; + VALID_ARCHS = x86_64; + }; + name = HockeyappMacAlpha; + }; + D098C7201D7E175A007784E4 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = DebugHockeyapp; + }; + D098C7211D7E175A007784E4 /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -3943,20 +6724,21 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D098C7231D7E175A007784E4 /* Debug Hockeyapp */ = { + D098C7231D7E175A007784E4 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; baseConfigurationReference = C21074251E77F79A006EE5EF /* Beta.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = NO; @@ -3964,16 +6746,27 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Mac.entitlements"; CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", "BETA=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; @@ -3982,47 +6775,74 @@ HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/thrid-party/ogg", "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", ); INFOPLIST_FILE = "Telegram-Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Telegram-Mac/libwebp/lib", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", "$(SDKROOT)/usr/lib/system", "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", ); MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D BETA"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D098C7241D7E175A007784E4 /* Debug AppStore */ = { + D098C7241D7E175A007784E4 /* DebugAppStore */ = { isa = XCBuildConfiguration; baseConfigurationReference = C21074251E77F79A006EE5EF /* Beta.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_ENABLE_MODULES = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = NO; CLANG_WARN_SUSPICIOUS_MOVE = NO; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "Telegram-Mac/Telegram-Mac.entitlements"; - CODE_SIGN_IDENTITY = ""; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6N38VWS5BX; + ENABLE_BITCODE = NO; + ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + "$(PROJECT_DIR)/HockeySDK-Mac", + "$(PROJECT_DIR)/submodules", + "$(PROJECT_DIR)/submodules/AppCenter", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "TGVOIP_USE_CUSTOM_CRYPTO=1", ); - GCC_PREPROCESSOR_DEFINITIONS = "STABLE=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = NO; GCC_WARN_ABOUT_POINTER_SIGNEDNESS = NO; @@ -4030,24 +6850,38 @@ HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/thrid-party/ogg", "$(PROJECT_DIR)/submodules/libtgvoip", + "$(PROJECT_DIR)/thrid-party", + "$(PROJECT_DIR)/thrid-party/ffmpeg/include/", + "$(PROJECT_DIR)/thrid-party/rlottie/**", ); INFOPLIST_FILE = "Telegram-Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Telegram-Mac/libwebp/lib", + "$(PROJECT_DIR)/thrid-party/libwebp/lib", "$(SDKROOT)/usr/lib/system", "$(PROJECT_DIR)/thrid-party/opus/lib", + "$(PROJECT_DIR)/thrid-party/ffmpeg/lib", + "$(PROJECT_DIR)/thrid-party/libjpeg-turbo", + "$(PROJECT_DIR)", ); MACOSX_DEPLOYMENT_TARGET = 10.11; - OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D BETA"; + ONLY_ACTIVE_ARCH = YES; + OTHER_CODE_SIGN_FLAGS = "--deep"; + OTHER_LDFLAGS = "-ObjC"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = ru.keepcoder.Telegram; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_ENFORCE_EXCLUSIVE_ACCESS = off; SWIFT_OBJC_BRIDGING_HEADER = "Telegram-Mac/Telegram-Mac-Bridging-Header.h"; - SWIFT_VERSION = 4.0; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 5.0; }; - name = "Debug AppStore"; + name = DebugAppStore; }; /* End XCBuildConfiguration section */ @@ -4055,35 +6889,41 @@ C232EAA01E1D07E700C4D38C /* Build configuration list for PBXNativeTarget "TelegramShare" */ = { isa = XCConfigurationList; buildConfigurations = ( - C232EAA11E1D07E700C4D38C /* Debug Hockeyapp */, - C232EAA21E1D07E700C4D38C /* Debug AppStore */, - C248BD1F1E6ECB24004B9106 /* Release Hockeyapp */, - C232EAA31E1D07E700C4D38C /* Release AppStore */, + C232EAA11E1D07E700C4D38C /* DebugHockeyapp */, + D01E1ECB22B2608F00AD6DAE /* HockeyappMacAlpha */, + C232EAA21E1D07E700C4D38C /* DebugAppStore */, + A71DC83B2386D6C2000EEDE2 /* Github */, + C248BD1F1E6ECB24004B9106 /* ReleaseHockeyapp */, + C232EAA31E1D07E700C4D38C /* ReleaseAppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Debug AppStore"; + defaultConfigurationName = DebugAppStore; }; D098C7101D7E175A007784E4 /* Build configuration list for PBXProject "Telegram" */ = { isa = XCConfigurationList; buildConfigurations = ( - D098C7201D7E175A007784E4 /* Debug Hockeyapp */, - D098C7211D7E175A007784E4 /* Debug AppStore */, - C248BD1D1E6ECB24004B9106 /* Release Hockeyapp */, - C232EA251E1BC1A100C4D38C /* Release AppStore */, + D098C7201D7E175A007784E4 /* DebugHockeyapp */, + D01E1EC922B2608F00AD6DAE /* HockeyappMacAlpha */, + D098C7211D7E175A007784E4 /* DebugAppStore */, + A71DC8392386D6C2000EEDE2 /* Github */, + C248BD1D1E6ECB24004B9106 /* ReleaseHockeyapp */, + C232EA251E1BC1A100C4D38C /* ReleaseAppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Debug AppStore"; + defaultConfigurationName = DebugAppStore; }; D098C7221D7E175A007784E4 /* Build configuration list for PBXNativeTarget "Telegram" */ = { isa = XCConfigurationList; buildConfigurations = ( - D098C7231D7E175A007784E4 /* Debug Hockeyapp */, - D098C7241D7E175A007784E4 /* Debug AppStore */, - C248BD1E1E6ECB24004B9106 /* Release Hockeyapp */, - C232EA261E1BC1A100C4D38C /* Release AppStore */, + D098C7231D7E175A007784E4 /* DebugHockeyapp */, + D01E1ECA22B2608F00AD6DAE /* HockeyappMacAlpha */, + D098C7241D7E175A007784E4 /* DebugAppStore */, + A71DC83A2386D6C2000EEDE2 /* Github */, + C248BD1E1E6ECB24004B9106 /* ReleaseHockeyapp */, + C232EA261E1BC1A100C4D38C /* ReleaseAppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Debug AppStore"; + defaultConfigurationName = DebugAppStore; }; /* End XCConfigurationList section */ }; diff --git a/Telegram.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Telegram.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/Telegram.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Telegram.xcodeproj/xcshareddata/xcschemes/AppStore.xcscheme b/Telegram.xcodeproj/xcshareddata/xcschemes/AppStore.xcscheme new file mode 100644 index 0000000000..0d6a2425ba --- /dev/null +++ b/Telegram.xcodeproj/xcshareddata/xcschemes/AppStore.xcscheme @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcshareddata/xcschemes/Github.xcscheme b/Telegram.xcodeproj/xcshareddata/xcschemes/Github.xcscheme new file mode 100644 index 0000000000..90661d9947 --- /dev/null +++ b/Telegram.xcodeproj/xcshareddata/xcschemes/Github.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcshareddata/xcschemes/Hockeyapp.xcscheme b/Telegram.xcodeproj/xcshareddata/xcschemes/Hockeyapp.xcscheme new file mode 100644 index 0000000000..7370c7ed51 --- /dev/null +++ b/Telegram.xcodeproj/xcshareddata/xcschemes/Hockeyapp.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcshareddata/xcschemes/HockeyappAlpha.xcscheme b/Telegram.xcodeproj/xcshareddata/xcschemes/HockeyappAlpha.xcscheme new file mode 100644 index 0000000000..ebd3bb97a2 --- /dev/null +++ b/Telegram.xcodeproj/xcshareddata/xcschemes/HockeyappAlpha.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcshareddata/xcschemes/Release.xcscheme b/Telegram.xcodeproj/xcshareddata/xcschemes/Release.xcscheme new file mode 100644 index 0000000000..1aa7799d82 --- /dev/null +++ b/Telegram.xcodeproj/xcshareddata/xcschemes/Release.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/Telegram.xcscheme b/Telegram.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/Telegram.xcscheme old mode 100644 new mode 100755 index a4517fc909..fe5b2f6800 --- a/Telegram.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/Telegram.xcscheme +++ b/Telegram.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/Telegram.xcscheme @@ -26,7 +26,6 @@ buildConfiguration = "Debug Hockeyapp" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" shouldUseLaunchSchemeArgsEnv = "YES"> @@ -43,10 +42,9 @@ SchemeUserState AppStore.xcscheme + + orderHint + 4 + + AppStore.xcscheme_^#shared#^_ orderHint 14 Hockeyapp.xcscheme + + orderHint + 2 + + Hockeyapp.xcscheme_^#shared#^_ orderHint 12 + HockeyappAlpha.xcscheme_^#shared#^_ + + orderHint + 16 + Release.xcscheme + + orderHint + 3 + + Release.xcscheme_^#shared#^_ orderHint 13 @@ -24,10 +44,25 @@ orderHint 1 + Telegram.xcscheme_^#shared#^_ + + orderHint + 11 + + TelegramShare 2.xcscheme_^#shared#^_ + + orderHint + 22 + TelegramShare.xcscheme orderHint - 15 + 5 + + TelegramShare.xcscheme_^#shared#^_ + + orderHint + 24 SuppressBuildableAutocreation @@ -35,22 +70,22 @@ C232EA561E1D041A00C4D38C primary - + C232EA741E1D058700C4D38C primary - + C232EA901E1D07E700C4D38C primary - + D098C7141D7E175A007784E4 primary - + diff --git a/Telegram.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/Telegram.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..557d1054d1 --- /dev/null +++ b/Telegram.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,61 @@ + + + + + SchemeUserState + + AppStore.xcscheme_^#shared#^_ + + orderHint + 4 + + Github.xcscheme_^#shared#^_ + + orderHint + 29 + + Hockeyapp.xcscheme_^#shared#^_ + + orderHint + 2 + + HockeyappAlpha.xcscheme_^#shared#^_ + + orderHint + 3 + + Release.xcscheme_^#shared#^_ + + isShown + + orderHint + 5 + + Spotlight.xcscheme_^#shared#^_ + + orderHint + 29 + + Telegram.xcscheme + + orderHint + 1 + + TelegramShare.xcscheme_^#shared#^_ + + isShown + + orderHint + 36 + + + SuppressBuildableAutocreation + + D098C7141D7E175A007784E4 + + primary + + + + + diff --git a/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/AppStore.xcscheme b/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/AppStore.xcscheme new file mode 100644 index 0000000000..c1d2575aad --- /dev/null +++ b/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/AppStore.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TelegramShare.xcscheme b/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TelegramShare.xcscheme new file mode 100644 index 0000000000..e96e18ddc6 --- /dev/null +++ b/Telegram.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TelegramShare.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TelegramShare/Base.lproj/ShareViewController.xib b/TelegramShare/Base.lproj/ShareViewController.xib index c84fefa2da..ceec19abe2 100644 --- a/TelegramShare/Base.lproj/ShareViewController.xib +++ b/TelegramShare/Base.lproj/ShareViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -15,7 +15,7 @@ - + diff --git a/TelegramShare/Info.plist b/TelegramShare/Info.plist index 83f33aafb8..4b2bb09255 100644 --- a/TelegramShare/Info.plist +++ b/TelegramShare/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 3.5.2 + 6.3 CFBundleVersion - 107976 + 203623 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension diff --git a/TelegramShare/SEModalProgressView.swift b/TelegramShare/SEModalProgressView.swift index 2c87233f33..c7916c823d 100644 --- a/TelegramShare/SEModalProgressView.swift +++ b/TelegramShare/SEModalProgressView.swift @@ -25,21 +25,21 @@ class SEModalProgressView: View { addSubview(containerView) self.backgroundColor = theme.colors.blackTransparent self.containerView.backgroundColor = theme.colors.grayBackground - let layout = TextViewLayout(.initialize(string: tr(.shareExtensionShare), color: theme.colors.text, font: .normal(.title))) + let layout = TextViewLayout(.initialize(string: tr(L10n.shareExtensionShare), color: theme.colors.text, font: .normal(.title))) layout.measure(width: .greatestFiniteMagnitude) header.update(layout) header.backgroundColor = theme.colors.grayBackground containerView.setFrameSize(250, 80) containerView.layer?.cornerRadius = .cornerRadius - progress.style = ControlStyle(foregroundColor: theme.colors.blueUI, backgroundColor: theme.colors.grayBackground) + progress.style = ControlStyle(foregroundColor: theme.colors.accent, backgroundColor: theme.colors.grayBackground) progress.setFrameSize(250, 4) cancel.set(font: .medium(.title), for: .Normal) - cancel.set(color: theme.colors.blueUI, for: .Normal) - cancel.set(text: tr(.shareExtensionCancel), for: .Normal) - cancel.sizeToFit() + cancel.set(color: theme.colors.accent, for: .Normal) + cancel.set(text: tr(L10n.shareExtensionCancel), for: .Normal) + _ = cancel.sizeToFit() cancel.set(handler: { [weak self] _ in self?.cancelImpl?() diff --git a/TelegramShare/SEPasslockController.swift b/TelegramShare/SEPasslockController.swift index f1f38504b8..1c2e6097bc 100644 --- a/TelegramShare/SEPasslockController.swift +++ b/TelegramShare/SEPasslockController.swift @@ -19,41 +19,37 @@ import TGUIKit import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit + -enum PasscodeViewState { - case login -} private class PasscodeLockView : Control, NSTextFieldDelegate { - fileprivate let photoView:AvatarControl = AvatarControl(font: .avatar(.custom(23))) fileprivate let nameView:TextView = TextView() fileprivate let input:NSSecureTextField private let nextButton:TitleButton = TitleButton() - private var state:PasscodeViewState? fileprivate var cancel:ImageButton = ImageButton() fileprivate let value:ValuePromise = ValuePromise(ignoreRepeated: false) required init(frame frameRect: NSRect) { input = NSSecureTextField(frame: NSZeroRect) + input.stringValue = "" super.init(frame: frameRect) - photoView.setFrameSize(NSMakeSize(80, 80)) self.backgroundColor = theme.colors.background - nextButton.set(color: theme.colors.blueUI, for: .Normal) + nextButton.set(color: theme.colors.accent, for: .Normal) nextButton.set(font: .normal(.title), for: .Normal) - nextButton.set(text: tr(.shareExtensionPasscodeNext), for: .Normal) - nextButton.sizeToFit() + nextButton.set(text: tr(L10n.shareExtensionPasscodeNext), for: .Normal) + _ = nextButton.sizeToFit() cancel.set(image: theme.icons.chatInlineDismiss, for: .Normal) - cancel.sizeToFit() + _ = cancel.sizeToFit() nameView.backgroundColor = theme.colors.background - addSubview(photoView) addSubview(nameView) addSubview(input) addSubview(nextButton) @@ -66,7 +62,7 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { input.delegate = self let attr = NSMutableAttributedString() - _ = attr.append(string: tr(.shareExtensionPasscodePlaceholder), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) + _ = attr.append(string: tr(L10n.shareExtensionPasscodePlaceholder), color: theme.colors.grayText, font: NSFont.normal(FontSize.text)) attr.setAlignment(.center, range: attr.range) input.placeholderAttributedString = attr input.backgroundColor = theme.colors.background @@ -93,22 +89,15 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { value.set(input.stringValue) } - func update(with state:PasscodeViewState, account:Account, peer:Peer) { - self.state = state - - photoView.setPeer(account: account, peer: peer) - let layout = TextViewLayout(.initialize(string:peer.displayTitle, color: theme.colors.text, font:.normal(.title))) + func update() { + let layout = TextViewLayout(.initialize(string: L10n.passlockEnterYourPasscode, color: theme.colors.text, font:.normal(.title))) layout.measure(width: frame.width - 40) nameView.update(layout) needsLayout = true - changeInput(state) } - fileprivate func changeInput(_ state:PasscodeViewState) { - - } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) @@ -119,10 +108,9 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { override func layout() { super.layout() - photoView.center() - photoView.setFrameOrigin(photoView.frame.minX, photoView.frame.minY - floorToScreenPixels((20 + input.frame.height + 60)/2.0) - 20) + nameView.center() + nameView.centerX(y: nameView.frame.minY - floorToScreenPixels(backingScaleFactor, (20 + input.frame.height + 60)/2.0) - 20) input.setFrameSize(200, input.frame.height) - nameView.centerX(y: photoView.frame.maxY + 20) input.centerX(y: nameView.frame.minY + 30 + 20) input.setFrameOrigin(input.frame.minX, input.frame.minY) setNeedsDisplayLayer() @@ -138,34 +126,26 @@ private class PasscodeLockView : Control, NSTextFieldDelegate { } class SEPasslockController: ModalViewController { - private let account:Account - private var state: PasscodeViewState { - didSet { - self.genericView.changeInput(state) - } - } + private let context: SharedAccountContext + private let disposable:MetaDisposable = MetaDisposable() private let valueDisposable = MetaDisposable() private let logoutDisposable = MetaDisposable() private var passcodeValues:[String] = [] private let _doneValue:Promise = Promise() - var doneValue:Signal { + var doneValue:Signal { return _doneValue.get() } private let cancelImpl:()->Void - init(_ account:Account, _ state: PasscodeViewState, cancelImpl:@escaping()->Void) { - self.account = account - self.state = state + init(_ context: SharedAccountContext, cancelImpl:@escaping()->Void) { + self.context = context self.cancelImpl = cancelImpl super.init(frame: NSMakeRect(0, 0, 340, 310)) } override var isFullScreen: Bool { - switch state { - case .login: - return true - } + return true } private var genericView:PasscodeLockView { @@ -173,14 +153,11 @@ class SEPasslockController: ModalViewController { } private func checkNextValue(_ passcode: String, _ current:String?) { - switch state { - case .login: - if current == passcode { - _doneValue.set(.single(true)) - close() - } else { - genericView.input.shake() - } + if current == passcode { + _doneValue.set(.single(true)) + close() + } else { + genericView.input.shake() } } @@ -190,13 +167,14 @@ class SEPasslockController: ModalViewController { genericView.cancel.set(handler: { [weak self] _ in self?.cancelImpl() }, for: .Click) + valueDisposable.set((genericView.value.get() |> mapToSignal { [weak self] value in if let strongSelf = self { - return strongSelf.account.postbox.modify { modifier -> (String, String?) in - switch modifier.getAccessChallengeData() { + return strongSelf.context.accountManager.transaction { transaction -> (String, String?) in + switch transaction.getAccessChallengeData() { case .none: return (value, nil) - case let .plaintextPassword(passcode, _, _), let .numericalPassword(passcode, _, _): + case let .plaintextPassword(passcode), let .numericalPassword(passcode): return (value, passcode) } } @@ -206,12 +184,8 @@ class SEPasslockController: ModalViewController { self?.checkNextValue(value, current) })) - disposable.set((account.postbox.loadedPeerWithId(account.peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.genericView.update(with: strongSelf.state, account: strongSelf.account, peer: peer) - strongSelf.readyOnce() - } - })) + genericView.update() + readyOnce() } diff --git a/TelegramShare/SESelectController.swift b/TelegramShare/SESelectController.swift index 814260658b..89d29f01f7 100644 --- a/TelegramShare/SESelectController.swift +++ b/TelegramShare/SESelectController.swift @@ -8,34 +8,92 @@ import Cocoa import TGUIKit -import SwiftSignalKitMac -import TelegramCoreMac -import PostboxMac +import SwiftSignalKit +import TelegramCore +import SyncCore +import Postbox +class SelectAccountView: Control { + + init(_ accounts: [AccountWithInfo], primary: AccountRecordId, switchAccount: @escaping(AccountRecordId) -> Void, frame: NSRect) { + super.init(frame: frame) + backgroundColor = NSColor.black.withAlphaComponent(0.85) + + if let current = accounts.first(where: {$0.account.id == primary}) { + + let currentControl = AvatarControl(font: .avatar(12)) + currentControl.frame = NSMakeRect(frame.width - 30 - 10, 10, 30, 30) + currentControl.setPeer(account: current.account, peer: current.peer) + addSubview(currentControl) + + + var y: CGFloat = currentControl.frame.maxY + 10 + for current in accounts { + if current.account.id != primary { + let container = Button() + + container.autohighlight = true + + container.backgroundColor = .white + let nameView = TextView() + nameView.userInteractionEnabled = false + nameView.isSelectable = false + + let layout = TextViewLayout(.initialize(string: current.peer.compactDisplayTitle, color: .text, font: .medium(.text)), maximumNumberOfLines: 1) + layout.measure(width: 150) + + nameView.background = .white + nameView.update(layout) + + let control = AvatarControl(font: .avatar(12)) + control.setFrameSize(30, 30) + control.setPeer(account: current.account, peer: current.peer) + control.userInteractionEnabled = false + + container.addSubview(control) + + container.addSubview(nameView) + + container.setFrameSize(NSMakeSize(5 + nameView.frame.width + 5 + control.frame.width, 30)) + container.layer?.cornerRadius = container.frame.height / 2 + + container.frame = NSMakeRect(frame.width - container.frame.width - 10, 10, container.frame.width, container.frame.height) + + control.centerY(x: container.frame.width - control.frame.width) + nameView.centerY(x: 5) -extension Peer { - - var canSendMessage: Bool { - if let channel = self as? TelegramChannel { - if case .broadcast(_) = channel.info { - return channel.hasAdminRights(.canPostMessages) - } else if case .group(_) = channel.info { - return !channel.hasBannedRights(.banSendMessages) - } - } else if let group = self as? TelegramGroup { - return group.membership == .Member - } else if let secret = self as? TelegramSecretChat { - switch secret.embeddedState { - case .terminated: - return false - case .handshake: - return false - default: - return true + addSubview(container) + + container.set(handler: { [weak self] _ in + self?.change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, timingFunction: .spring, completion: { _ in + switchAccount(current.account.id) + }) + + }, for: .Click) + + container._change(pos: NSMakePoint(container.frame.minX, y), animated: true, timingFunction: .spring) + container.layer?.animateAlpha(from: 0, to: 1, duration: 0.2, timingFunction: .spring) + y += container.frame.height + 10 + } } + + set(handler: { [weak self] _ in + self?.change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, timingFunction: .spring, completion: { [weak self] completed in + self?.removeFromSuperview() + }) + }, for: .SingleClick) } - return true + } + + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } @@ -45,34 +103,72 @@ class ShareModalView : View { let tableView:TableView = TableView() let acceptView:TitleButton = TitleButton() let cancelView:TitleButton = TitleButton() + private var photoView: AvatarControl? + private var control: Control = Control() let borderView:View = View() required init(frame frameRect: NSRect) { super.init(frame: frameRect) self.backgroundColor = theme.colors.background borderView.backgroundColor = theme.colors.border - acceptView.style = ControlStyle(font: .medium(.text),foregroundColor: theme.colors.blueUI) - acceptView.set(text: tr(.shareExtensionShare), for: .Normal) - acceptView.sizeToFit() + acceptView.style = ControlStyle(font: .medium(.text),foregroundColor: theme.colors.accent) + acceptView.set(text: L10n.shareExtensionShare, for: .Normal) + _ = acceptView.sizeToFit() - cancelView.style = ControlStyle(font:.medium(.text),foregroundColor: theme.colors.blueUI) - cancelView.set(text: tr(.shareExtensionCancel), for: .Normal) - cancelView.sizeToFit() + cancelView.style = ControlStyle(font:.medium(.text),foregroundColor: theme.colors.accent) + cancelView.set(text: L10n.shareExtensionCancel, for: .Normal) + _ = cancelView.sizeToFit() addSubview(acceptView) addSubview(cancelView) addSubview(searchView) addSubview(tableView) addSubview(borderView) + addSubview(control) + } + + func updateWithAccounts(_ accounts: (primary: AccountRecordId?, accounts: [AccountWithInfo]), context: AccountContext) -> Void { + if accounts.accounts.count > 1, let primary = accounts.primary { + if photoView == nil { + photoView = AvatarControl(font: .avatar(12)) + photoView?.setFrameSize(NSMakeSize(30, 30)) + addSubview(photoView!) + } + if let account = accounts.accounts.first(where: {$0.account.id == primary}) { + photoView?.setPeer(account: account.account, peer: account.peer) + } + photoView?.removeAllHandlers() + + + + photoView?.set(handler: { [weak self] _ in + guard let `self` = self else {return} + let view = SelectAccountView(accounts.accounts, primary: primary, switchAccount: { recordId in + context.sharedContext.switchToAccount(id: recordId, action: nil) + }, frame: self.bounds) + self.addSubview(view) + view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + }, for: .Click) + } else { + photoView?.removeFromSuperview() + photoView = nil + } + needsLayout = true } override func layout() { super.layout() - searchView.frame = NSMakeRect(10, 10, frame.width - 20, 30) + if let photoView = photoView { + photoView.setFrameOrigin(frame.width - photoView.frame.width - 10, 10) + searchView.frame = NSMakeRect(10, 10, frame.width - 20 - photoView.frame.width - 10, 30) + } else { + searchView.frame = NSMakeRect(10, 10, frame.width - 20, 30) + } + control.frame = NSMakeRect(frame.width - 30 - 30, 10, 30, 30) tableView.frame = NSMakeRect(0, 50, frame.width, frame.height - 50 - 40) borderView.frame = NSMakeRect(0, tableView.frame.maxY, frame.width, .borderSize) - acceptView.setFrameOrigin(frame.width - acceptView.frame.width - 30, floorToScreenPixels(tableView.frame.maxY + (40 - acceptView.frame.height) / 2.0)) - cancelView.setFrameOrigin(acceptView.frame.minX - cancelView.frame.width - 30, floorToScreenPixels(tableView.frame.maxY + (40 - cancelView.frame.height) / 2.0)) + acceptView.setFrameOrigin(frame.width - acceptView.frame.width - 30, floorToScreenPixels(backingScaleFactor, tableView.frame.maxY + (40 - acceptView.frame.height) / 2.0)) + cancelView.setFrameOrigin(acceptView.frame.minX - cancelView.frame.width - 30, floorToScreenPixels(backingScaleFactor, tableView.frame.maxY + (40 - cancelView.frame.height) / 2.0)) } required init?(coder: NSCoder) { @@ -83,28 +179,28 @@ class ShareModalView : View { class ShareObject { - let account:Account - let context:NSExtensionContext - init(_ account:Account, _ context:NSExtensionContext) { - self.account = account + let context: AccountContext + let shareContext:NSExtensionContext + init(_ context: AccountContext, _ shareContext:NSExtensionContext) { self.context = context + self.shareContext = shareContext } private let progressView = SEModalProgressView() func perform(to entries:[PeerId], view: NSView) { - var signals:[Signal] = [] + var signals:[Signal] = [] var needWaitAsync = false var k:Int = 0 - let total = context.inputItems.reduce(0) { (current, item) -> Int in + let total = shareContext.inputItems.reduce(0) { (current, item) -> Int in if let item = item as? NSExtensionItem { if let _ = item.attributedContentText?.string { return current + 1 - } else if let attachments = item.attachments as? [NSItemProvider] { + } else if let attachments = item.attachments { return current + attachments.count } } @@ -128,7 +224,7 @@ class ShareObject { self.progressView.set(progress: CGFloat(min(progress / Float(total), 1))) }, completed: { - self.context.completeRequest(returningItems: nil, completionHandler: nil) + self.shareContext.completeRequest(returningItems: nil, completionHandler: nil) }) self.progressView.cancelImpl = { @@ -141,13 +237,13 @@ class ShareObject { } for peerId in entries { - for j in 0 ..< context.inputItems.count { - if let item = context.inputItems[j] as? NSExtensionItem { + for j in 0 ..< shareContext.inputItems.count { + if let item = shareContext.inputItems[j] as? NSExtensionItem { if let text = item.attributedContentText?.string { signals.append(sendText(text, to:peerId)) k += 1 requestIfNeeded() - } else if let attachments = item.attachments as? [NSItemProvider] { + } else if let attachments = item.attachments { for i in 0 ..< attachments.count { attachments[i].loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (coding, error) in @@ -157,10 +253,20 @@ class ShareObject { } else { signals.append(self.sendMedia(url, to:peerId)) } + k += 1 + requestIfNeeded() } - k += 1 - requestIfNeeded() + }) + if k != total { + attachments[i].loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { (coding, error) in + if let data = (coding as? NSImage)?.tiffRepresentation { + signals.append(self.sendMedia(nil, data, to:peerId)) + k += 1 + requestIfNeeded() + } + }) + } } } } @@ -169,19 +275,31 @@ class ShareObject { } - private func sendText(_ text:String, to peerId:PeerId) -> Signal { - return Signal.single(0) |> then(standaloneSendMessage(account: self.account, peerId: peerId, text: text, attributes: [], media: nil, replyToMessageId: nil) |> mapError {_ in} |> map {_ in return 1}) + private func sendText(_ text:String, to peerId:PeerId) -> Signal { + return Signal.single(0) |> then(standaloneSendMessage(account: context.account, peerId: peerId, text: text, attributes: [], media: nil, replyToMessageId: nil) |> `catch` {_ in return .complete()} |> map {_ in return 1}) } - private let queue:Queue = Queue(name: "proccessShareFilesQueue", target: nil) + private let queue:Queue = Queue(name: "proccessShareFilesQueue") - private func prepareMedia(_ path: URL) -> Signal { + private func prepareMedia(_ path: URL?, _ pasteData: Data? = nil) -> Signal { return Signal { subscriber in - if let data = try? Data(contentsOf: path) { + + let data = pasteData ?? (path != nil ? try? Data(contentsOf: path!) : nil) + + if let data = data { + var forceImage: Bool = false + if let _ = NSImage(data: data) { + if let path = path { + let mimeType = MIMEType(path.path) + if mimeType.hasPrefix("image/") && !mimeType.hasSuffix("gif") { + forceImage = true + } + } else { + forceImage = true + } + } - let mimeType = MIMEType(path.absoluteString.nsstring.pathExtension.lowercased()) - if mimeType.hasPrefix("image/") && !mimeType.hasSuffix("gif") { - + if forceImage { let options = NSMutableDictionary() options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailWithTransform as String) options.setValue(1280 as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) @@ -189,18 +307,25 @@ class ShareObject { if let imageSource = CGImageSourceCreateWithData(data as CFData, nil) { let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) - if let image = image { - let imageRep = NSBitmapImageRep(cgImage: image) - let options: [NSBitmapImageRep.PropertyKey: Any] = [.compressionFactor: 0.83] - let data = imageRep.representation(using: .jpeg, properties: options) - if let data = data { + if let image = image, let data = NSImage(cgImage: image, size: image.backingSize).tiffRepresentation(using: .jpeg, factor: 0.83) { + let imageRep = NSBitmapImageRep(data: data) + if let data = imageRep?.representation(using: .jpeg, properties: [:]) { subscriber.putNext(StandaloneMedia.image(data)) } } } } else { - subscriber.putNext(StandaloneMedia.file(data: data, mimeType: mimeType, attributes: [])) + var mimeType: String = "application/octet-stream" + let fileName: String + if let path = path { + mimeType = MIMEType(path.path) + fileName = path.path.nsstring.lastPathComponent + } else { + fileName = "Unnamed.file" + } + + subscriber.putNext(StandaloneMedia.file(data: data, mimeType: mimeType, attributes: [.FileName(fileName: fileName)])) } } @@ -212,15 +337,15 @@ class ShareObject { - private func sendMedia(_ path:URL, to peerId:PeerId) -> Signal { - return Signal.single(0) |> then(prepareMedia(path) |> mapToSignal { media -> Signal in - return standaloneSendMessage(account: self.account, peerId: peerId, text: "", attributes: [], media: media, replyToMessageId: nil) |> mapError {_ in} + private func sendMedia(_ path:URL?, _ data: Data? = nil, to peerId:PeerId) -> Signal { + return Signal.single(0) |> then(prepareMedia(path, data) |> mapToSignal { media -> Signal in + return standaloneSendMessage(account: self.context.account, peerId: peerId, text: "", attributes: [], media: media, replyToMessageId: nil) |> `catch` {_ in return .complete()} }) } func cancel() { let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) - context.cancelRequest(withError: cancelError) + shareContext.cancelRequest(withError: cancelError) } } @@ -302,22 +427,19 @@ func ==(lhs:SelectablePeersEntry, rhs:SelectablePeersEntry) -> Bool { -fileprivate func prepareEntries(from:[SelectablePeersEntry]?, to:[SelectablePeersEntry], account:Account, initialSize:NSSize, animated:Bool, selectInteraction:SelectPeerInteraction) -> Signal,Void> { +fileprivate func prepareEntries(from:[SelectablePeersEntry]?, to:[SelectablePeersEntry], account:Account, initialSize:NSSize, animated:Bool, selectInteraction:SelectPeerInteraction) -> Signal, NoError> { return Signal {subscriber in - let (deleted,inserted,updated) = proccessEntries(from, right: to, { (entry) -> TableRowItem in - + let (deleted,inserted,updated) = proccessEntries(from, right: to, { entry -> TableRowItem in switch entry { case let .plain(peer, _): - return ShortPeerRowItem(initialSize, peer: peer, account:account, height:40, photoSize:NSMakeSize(30,30), inset:NSEdgeInsets(left: 10, right:10), interactionType:.selectable(selectInteraction)) + return ShortPeerRowItem(initialSize, peer: peer, account:account, height:40, photoSize:NSMakeSize(30,30), isLookSavedMessage: true, inset:NSEdgeInsets(left: 10, right:10), interactionType:.selectable(selectInteraction)) case .emptySearch: return SearchEmptyRowItem(initialSize, stableId: SelectablePeersEntryStableId.emptySearch) } - - }) - let transition = TableEntriesTransition<[SelectablePeersEntry]>(deleted: deleted, inserted: inserted, updated:updated, entries:to, animated:animated, state: animated ? .none(nil) : .saveVisible(.lower)) + let transition = TableEntriesTransition<[SelectablePeersEntry]>(deleted: deleted, inserted: inserted, updated:updated, entries:to, animated:animated, state: .none(nil)) subscriber.putNext(transition) subscriber.putCompletion() @@ -346,7 +468,7 @@ class SESelectController: GenericViewController, Notifable { private let search:ValuePromise = ValuePromise(ignoreRepeated: true) private let inSearchSelected:Atomic<[PeerId]> = Atomic(value:[]) private let disposable:MetaDisposable = MetaDisposable() - + private let accountsDisposable = MetaDisposable() func notify(with value: Any, oldValue: Any, animated: Bool) { if let value = value as? SelectPeerPresentation, let oldValue = oldValue as? SelectPeerPresentation { @@ -361,6 +483,8 @@ class SESelectController: GenericViewController, Notifable { } } + + func isEqual(to other: Notifable) -> Bool { return false } @@ -368,23 +492,29 @@ class SESelectController: GenericViewController, Notifable { override func viewDidLoad() { super.viewDidLoad() + let context = self.share.context + + accountsDisposable.set((self.share.context.sharedContext.activeAccountsWithInfo |> deliverOnMainQueue).start(next: { [weak self] accounts in + self?.genericView.updateWithAccounts(accounts, context: context) + })) + search.set(SearchState(state: .None, request: nil)) let previous:Atomic<[SelectablePeersEntry]?> = Atomic(value: nil) let initialSize = self.atomicSize.modify({$0}) - let account = share.account + let account = share.context.account let table = genericView.tableView let selectInteraction = self.selectInteractions selectInteraction.add(observer: self) - let list:Signal,Void> = search.get() |> distinctUntilChanged |> mapToSignal { [weak self] search -> Signal,Void> in + let list:Signal, NoError> = search.get() |> distinctUntilChanged |> mapToSignal { [weak self] search -> Signal, NoError> in if search.state == .None { - let signal:Signal<(ChatListView,ViewUpdateType),Void> = account.viewTracker.tailChatListView(count: 100) + let signal:Signal<(ChatListView,ViewUpdateType), NoError> = account.viewTracker.tailChatListView(groupId: .root, count: 100) |> take(1) - return signal |> deliverOn(prepareQueue) |> mapToQueue { [weak self] (value) -> Signal, Void> in + return combineLatest(signal, account.postbox.loadedPeerWithId(account.peerId)) |> deliverOn(prepareQueue) |> mapToQueue { [weak self] value, mainPeer -> Signal, NoError> in if let strongSelf = self { var entries:[SelectablePeersEntry] = [] @@ -393,9 +523,12 @@ class SESelectController: GenericViewController, Notifable { var fromPeers:[PeerId:Peer] = [:] var contains:[PeerId:Peer] = [:] + entries.append(.plain(mainPeer, ChatListIndex.init(pinningIndex: 0, messageIndex: MessageIndex.absoluteUpperBound()))) + contains[mainPeer.id] = mainPeer + for entry in value.0.entries { switch entry { - case let .MessageEntry(id, _, _, _, _, renderedPeer, _): + case let .MessageEntry(id, _, _, _, _, renderedPeer, _, _, _, _): if let peer = renderedPeer.chatMainPeer { if !fromSetIds.contains(peer.id), contains[peer.id] == nil { if peer.canSendMessage { @@ -422,41 +555,77 @@ class SESelectController: GenericViewController, Notifable { } entries.sort(by: <) - return prepareEntries(from: previous.modify({$0}), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) |> deliverOnMainQueue + return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) } return .never() } } else { - return ( search.request.isEmpty ? recentPeers(account: account) : account.postbox.searchPeers(query: search.request.lowercased()) |> map { - return $0.flatMap({$0.chatMainPeer}).filter({!($0 is TelegramSecretChat)}) }) |> deliverOn(prepareQueue) |> mapToSignal { peers -> Signal, Void> in + + let signal: Signal<([Peer], Peer), NoError> + + if search.request.isEmpty { + signal = combineLatest(recentPeers(account: account) |> map { recent -> [Peer] in + switch recent { + case .disabled: + return [] + case let .peers(peers): + return peers + } + }, account.postbox.loadedPeerWithId(account.peerId)) + |> deliverOn(prepareQueue) + } else { + let foundLocalPeers = account.postbox.searchPeers(query: search.request.lowercased()) |> map {$0.compactMap { $0.chatMainPeer} } + + let foundRemotePeers:Signal<[Peer], NoError> = .single([]) |> then ( searchPeers(account: account, query: search.request.lowercased()) |> map { $0.map{$0.peer} + $1.map{$0.peer} } ) + + signal = combineLatest(combineLatest(foundLocalPeers, foundRemotePeers) |> map {$0 + $1}, account.postbox.loadedPeerWithId(account.peerId)) + + } + + let assignSavedMessages:Bool + if search.request.isEmpty { + assignSavedMessages = true + } else if L10n.peerSavedMessages.lowercased().hasPrefix(search.request.lowercased()) || "Saved Messages".lowercased().hasPrefix(search.request.lowercased()) { + assignSavedMessages = true + } else { + assignSavedMessages = false + } + + + return signal |> mapToSignal { peers, mainPeer in var entries:[SelectablePeersEntry] = [] var i:Int32 = Int32.max + + var contains: Set = Set() + if assignSavedMessages { + entries.append(.plain(mainPeer, ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex.absoluteUpperBound()))) + contains.insert(mainPeer.id) + } + + for peer in peers { - if peer.canSendMessage { + if peer.canSendMessage, !contains.contains(peer.id) { let index = MessageIndex(id: MessageId(peerId: peer.id, namespace: 1, id: i), timestamp: i) entries.append(.plain(peer, ChatListIndex(pinningIndex: nil, messageIndex: index))) + contains.insert(peer.id) i -= 1 } } - if entries.isEmpty { - entries.append(.emptySearch) - } entries.sort(by: <) - return prepareEntries(from: previous.modify({$0}), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) |> deliverOnMainQueue + return prepareEntries(from: previous.swap(entries), to: entries, account: account, initialSize: initialSize, animated: true, selectInteraction:selectInteraction) } + } - } - disposable.set(list.start(next: { [weak self] (transition) in + disposable.set((list |> deliverOnMainQueue).start(next: { [weak self] (transition) in table.resetScrollNotifies() - _ = previous.swap(transition.entries) table.merge(with:transition) self?.readyOnce() })) - self.genericView.searchView.searchInteractions = SearchInteractions({ state in + self.genericView.searchView.searchInteractions = SearchInteractions({ state, _ in self.search.set(SearchState(state: state.state, request: state.request)) }, { state in self.search.set(SearchState(state: state.state, request: state.request)) @@ -505,6 +674,7 @@ class SESelectController: GenericViewController, Notifable { deinit { disposable.dispose() + accountsDisposable.dispose() } } diff --git a/TelegramShare/SEUnauthorizedViewController.swift b/TelegramShare/SEUnauthorizedViewController.swift index e36ff3552e..dce4ce9a2c 100644 --- a/TelegramShare/SEUnauthorizedViewController.swift +++ b/TelegramShare/SEUnauthorizedViewController.swift @@ -21,10 +21,10 @@ class SEUnauthorizedView : View { imageView.sizeToFit() self.backgroundColor = theme.colors.background cancel.set(font: .medium(.title), for: .Normal) - cancel.set(color: theme.colors.blueUI, for: .Normal) - cancel.set(text: tr(.shareExtensionUnauthorizedOK), for: .Normal) + cancel.set(color: theme.colors.accent, for: .Normal) + cancel.set(text: tr(L10n.shareExtensionUnauthorizedOK), for: .Normal) - let layout = TextViewLayout(.initialize(string: tr(.shareExtensionUnauthorizedDescription), color: theme.colors.text, font: .normal(.text)), alignment: .center) + let layout = TextViewLayout(.initialize(string: tr(L10n.shareExtensionUnauthorizedDescription), color: theme.colors.text, font: .normal(.text)), alignment: .center) textView.backgroundColor = theme.colors.background textView.update(layout) diff --git a/TelegramShare/ShareApplicationContext.swift b/TelegramShare/ShareApplicationContext.swift index 5b9321d19f..4294e2a282 100644 --- a/TelegramShare/ShareApplicationContext.swift +++ b/TelegramShare/ShareApplicationContext.swift @@ -8,77 +8,32 @@ import Cocoa import TGUIKit -import TelegramCoreMac -import PostboxMac -import SwiftSignalKitMac +import TelegramCore +import SyncCore +import Postbox +import SwiftSignalKit -private let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in +let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in return nil }, fetchResource: { account, resource, range, _ in return nil +}, fetchResourceMediaReferenceHash: { resource in + return .single(nil) +}, prepareSecretThumbnailData: { _ in + return nil }) -func applicationContext(accountManager: AccountManager, appGroupPath: String, extensionContext: NSExtensionContext) -> Signal { - - return currentAccount(networkArguments: NetworkInitializationArguments(apiId: 2834, languagesCategory: "macos"), supplementary: true, manager: accountManager, appGroupPath: appGroupPath, testingEnvironment: false, auxiliaryMethods: telegramAccountAuxiliaryMethods) |> mapToSignal { result -> Signal in - if let result = result { - switch result { - case .unauthorized(let account): - return account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings]) |> take(1) |> deliverOnMainQueue |> map { value in - return .unauthorized(UnauthorizedApplicationContext(account: account, context: extensionContext, localization: value.values[PreferencesKeys.localizationSettings] as? LocalizationSettings, theme: value.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings)) - } - case let .authorized(account): - let paslock:Signal = account.postbox.modify { modifier -> PostboxAccessChallengeData in - return modifier.getAccessChallengeData() - } |> deliverOnMainQueue - - return paslock |> mapToSignal { access -> Signal in - let promise:Promise = Promise() - let auth: Signal = combineLatest(promise.get(), account.postbox.preferencesView(keys: [PreferencesKeys.localizationSettings, ApplicationSpecificPreferencesKeys.themeSettings]) |> take(1)) |> deliverOnMainQueue |> map { _, value in - return .authorized(AuthorizedApplicationContext(account: account, context: extensionContext, localization: value.values[PreferencesKeys.localizationSettings] as? LocalizationSettings, theme: value.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings)) - } - switch access { - case .none: - promise.set(.single(Void())) - return auth - default: - return account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.themeSettings, PreferencesKeys.localizationSettings]) |> take(1) |> deliverOnMainQueue |> map { value in - return .postboxAccess(PasscodeAccessContext(promise: promise, account: account, context: extensionContext, localization: value.values[PreferencesKeys.localizationSettings] as? LocalizationSettings, theme: value.values[ApplicationSpecificPreferencesKeys.themeSettings] as? ThemePalleteSettings)) - } |> then(auth) - - } - } - default: - return .complete() - } - - } - - return .single(nil) - } |> deliverOnMainQueue - -} - - final class UnauthorizedApplicationContext { let account: UnauthorizedAccount let rootController: SEUnauthorizedViewController - init( account: UnauthorizedAccount, context: NSExtensionContext, localization:LocalizationSettings?, theme:ThemePalleteSettings?) { + init( account: UnauthorizedAccount, context: NSExtensionContext) { self.account = account - if let localization = localization { - applyShareUILocalization(localization) - } - if let themeSettings = theme { - updateTheme(with: themeSettings) - } else { - setDefaultTheme(for: nil) - } - + self.rootController = SEUnauthorizedViewController(cancelImpl: { let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) context.cancelRequest(withError: cancelError) @@ -88,75 +43,13 @@ final class UnauthorizedApplicationContext { } class AuthorizedApplicationContext { - let account: Account + let context: AccountContext let rootController: SESelectController - init(account: Account, context: NSExtensionContext, localization:LocalizationSettings?, theme:ThemePalleteSettings?) { - self.account = account - - if let localization = localization { - applyShareUILocalization(localization) - } - - if let themeSettings = theme { - updateTheme(with: themeSettings) - } else { - setDefaultTheme() - } + init(context: AccountContext, shareContext: NSExtensionContext) { + self.context = context - self.rootController = SESelectController(ShareObject(account, context)) - account.network.shouldKeepConnection.set(.single(true)) + self.rootController = SESelectController(ShareObject(context, shareContext)) + context.account.network.shouldKeepConnection.set(.single(true)) } } -class PasscodeAccessContext { - let account: Account - let promise:Promise - let rootController: SEPasslockController - init(promise:Promise, account: Account, context:NSExtensionContext, localization:LocalizationSettings?, theme:ThemePalleteSettings?) { - self.account = account - self.promise = promise - if let localization = localization { - applyShareUILocalization(localization) - } - if let themeSettings = theme { - updateTheme(with: themeSettings) - } else { - setDefaultTheme() - } - - self.rootController = SEPasslockController(account, .login, cancelImpl: { - let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) - context.cancelRequest(withError: cancelError) - }) - promise.set(rootController.doneValue |> filter {$0} |> map {_ in}) - } -} - -enum ShareApplicationContext { - case unauthorized(UnauthorizedApplicationContext) - case authorized(AuthorizedApplicationContext) - case postboxAccess(PasscodeAccessContext) - - func showRoot(for window:Window) { - if let content = window.contentView { - switch self { - case let .postboxAccess(context): - showModal(with: context.rootController, for: window) - default: - content.addSubview(rootView) - rootView.frame = content.bounds - } - } - } - - var rootView: NSView { - switch self { - case let .unauthorized(context): - return context.rootController.view - case let .authorized(context): - return context.rootController.view - case let .postboxAccess(context): - return context.rootController.view - } - } -} diff --git a/TelegramShare/ShareViewController.swift b/TelegramShare/ShareViewController.swift index 3bff0efc6c..e8b5f5268c 100644 --- a/TelegramShare/ShareViewController.swift +++ b/TelegramShare/ShareViewController.swift @@ -8,54 +8,169 @@ import Cocoa import TGUIKit -import PostboxMac -import TelegramCoreMac -import SwiftSignalKitMac - +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import SyncCore +import OpenSSLEncryption class ShareViewController: NSViewController { override var nibName: NSNib.Name? { - return NSNib.Name(rawValue: "ShareViewController") + return "ShareViewController" } - private let accountManagerPromise = Promise() - private var contextValue: ShareApplicationContext? - private let context = Promise() + + + + private var contextValue: AuthorizedApplicationContext? + private let context = Promise() private let contextDisposable = MetaDisposable() + private var passlock: SEPasslockController? = nil - override func loadView() { - super.loadView() + override func viewDidLoad() { + super.viewDidLoad() - declareEncodable(ThemePalleteSettings.self, f: { ThemePalleteSettings(decoder: $0) }) - - let appGroupName = "6N38VWS5BX.ru.keepcoder.Telegram" + + + declareEncodable(ThemePaletteSettings.self, f: { ThemePaletteSettings(decoder: $0) }) + declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) + + + let appGroupName = ApiEnvironment.group guard let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) else { return } + let rootPath = containerUrl.path + + let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: Bundle.main.bundleIdentifier!) + let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: true, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) + + + + let accountManager = AccountManager(basePath: containerUrl.path + "/accounts-metadata") + let networkArguments = NetworkInitializationArguments(apiId: ApiEnvironment.apiId, apiHash: ApiEnvironment.apiHash, languagesCategory: ApiEnvironment.language, appVersion: ApiEnvironment.version, voipMaxLayer: 90, voipVersions: [], appData: .single(ApiEnvironment.appData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()) + + let sharedContext = SharedAccountContext(accountManager: accountManager, networkArguments: networkArguments, rootPath: rootPath, encryptionParameters: encryptionParameters, displayUpgradeProgress: { _ in }) + let logger = Logger(basePath: containerUrl.path + "/sharelogs") logger.logToConsole = false logger.logToFile = false Logger.setSharedLogger(logger) + + + let themeSemaphore = DispatchSemaphore(value: 0) + var themeSettings: ThemePaletteSettings = ThemePaletteSettings.defaultTheme + _ = (themeSettingsView(accountManager: accountManager) |> take(1)).start(next: { settings in + themeSettings = settings + themeSemaphore.signal() + }) + themeSemaphore.wait() + + var localization: LocalizationSettings? = nil + let localizationSemaphore = DispatchSemaphore(value: 0) + _ = (accountManager.transaction { transaction in + localization = transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings + localizationSemaphore.signal() + }).start() + localizationSemaphore.wait() + + if let localization = localization { + applyShareUILocalization(localization) + } + + updateTheme(with: themeSettings) + let extensionContext = self.extensionContext! - self.accountManagerPromise.set(accountManager(basePath: containerUrl.path + "/accounts-metadata")) - self.context.set(self.accountManagerPromise.get() |> deliverOnMainQueue |> mapToSignal { accountManager -> Signal in - return applicationContext(accountManager: accountManager, appGroupPath: containerUrl.path, extensionContext: extensionContext) + initializeAccountManagement() + + let rawAccounts = sharedContext.activeAccounts + |> map { _, accounts, _ -> [Account] in + return accounts.map({ $0.1 }) + } + let _ = (sharedAccountInfos(accountManager: sharedContext.accountManager, accounts: rawAccounts) + |> deliverOn(Queue())).start(next: { infos in + storeAccountsData(rootPath: rootPath, accounts: infos) }) - self.contextDisposable.set(self.context.get().start(next: { context in - assert(Queue.mainQueue().isCurrent()) - self.contextValue = context - self.view.removeAllSubviews() - if let rootView = context?.rootView { - rootView.frame = self.view.bounds - self.view.addSubview(rootView) + + var access: PostboxAccessChallengeData = .none + let accessSemaphore = DispatchSemaphore(value: 0) + _ = (accountManager.transaction { transaction in + access = transaction.getAccessChallengeData() + accessSemaphore.signal() + }).start() + accessSemaphore.wait() + + switch access { + case .numericalPassword, .plaintextPassword: + let passlock = SEPasslockController(sharedContext, cancelImpl: { + let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + extensionContext.cancelRequest(withError: cancelError) + }) + self.passlock = passlock + + let passlockView = passlock.view + _ = (passlock.doneValue |> filter { $0 } |> take(1)).start(next: { [weak passlockView] _ in + passlockView?._change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, timingFunction: .spring, completion: { _ in + passlockView?.removeFromSuperview() + self.passlock = nil + }) + }) + passlock.view.frame = self.view.bounds + self.view.addSubview(passlock.view) + default: + break + } + + let readyDisposable = MetaDisposable() + _ = (self.context.get() |> mapToSignal { context -> Signal in + return .single(context) + + } |> deliverOnMainQueue).start(next: { context in + assert(Queue.mainQueue().isCurrent()) + + if let context = context { + context.rootController.view.frame = self.view.bounds + + readyDisposable.set((context.rootController.ready.get() |> take(1)).start(next: { [weak context] _ in + guard let context = context else { return } + if let contextValue = self.contextValue { + contextValue.rootController.view.removeFromSuperview() + } + self.contextValue = context + self.view.addSubview(context.rootController.view, positioned: .below, relativeTo: self.view.subviews.first) + + })) + } + }) + + + self.context.set(sharedContext.activeAccounts + |> map { primary, _, _ -> Account? in + return primary } - })) + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs !== rhs { + return false + } + return true + }) + |> map { account in + if let account = account { + let context = AccountContext(sharedContext: sharedContext, window: Window(contentRect: NSZeroRect, styleMask: [], backing: NSWindow.BackingStoreType.buffered, defer: true), account: account) + return AuthorizedApplicationContext(context: context, shareContext: extensionContext) + + } else { + return nil + } + }) } + } diff --git a/TelegramShare/TelegramShare.entitlements b/TelegramShare/TelegramShare.entitlements index 1e99545733..c0559cf67f 100644 --- a/TelegramShare/TelegramShare.entitlements +++ b/TelegramShare/TelegramShare.entitlements @@ -6,7 +6,8 @@ com.apple.security.application-groups - $(TeamIdentifierPrefix)ru.keepcoder.Telegram + 6N38VWS5BX.ru.keepcoder.Telegram.TelegramShare + 6N38VWS5BX.ru.keepcoder.Telegram com.apple.security.files.user-selected.read-only diff --git a/buildbox/appdiff.py b/buildbox/appdiff.py new file mode 100644 index 0000000000..f5faa3d1a8 --- /dev/null +++ b/buildbox/appdiff.py @@ -0,0 +1,275 @@ +import sys +import os +import glob +import tempfile +import re +import filecmp +import subprocess + + +def get_file_list(dir): + result_files = [] + result_dirs = [] + for root, dirs, files in os.walk(dir, topdown=False): + for name in files: + result_files.append(os.path.relpath(os.path.join(root, name), dir)) + for name in dirs: + result_dirs.append(os.path.relpath(os.path.join(root, name), dir)) + return set(result_dirs), set(result_files) + + +def remove_codesign_dirs(dirs): + result = set() + for dir in dirs: + if dir == '_CodeSignature': + continue + if re.match('PlugIns/.*\\.appex/SC_Info', dir): + continue + if re.match('Frameworks/.*\\.framework/SC_Info', dir): + continue + result.add(dir) + return result + + +def remove_codesign_files(files): + result = set() + for f in files: + if f == 'Contents/embedded.provisionprofile': + continue + if re.match('Contents/.*/.*\\.appex/embedded.provisionprofile', f): + continue + if re.match('Contents/_CodeSignature/CodeResources', f): + continue + if f == 'Contents/CodeResources': + continue + if re.match('Contents/PlugIns/.*\\.appex/Contents/_CodeSignature', f): + continue + if re.match('Contents/Frameworks/.*\\.framework/Versions/A/_CodeSignature', f): + continue + result.add(f) + return result + + + +def remove_plugin_files(files): + result = set() + excluded = set() + for f in files: + if False and re.match('PlugIns/.*', f): + excluded.add(f) + else: + result.add(f) + return (result, excluded) + + +def remove_asset_files(files): + result = set() + excluded = set() + for f in files: + if re.match('.*\\.car', f): + excluded.add(f) + else: + result.add(f) + return (result, excluded) + + +def remove_nib_files(files): + result = set() + excluded = set() + for f in files: + result.add(f) + return (result, excluded) + + +def diff_dirs(app1, dir1, app2, dir2): + only_in_app1 = dir1.difference(dir2) + only_in_app2 = dir2.difference(dir1) + if len(only_in_app1) == 0 and len(only_in_app2) == 0: + return + print('Directory structure doesn\'t match in ' + app1 + ' and ' + app2) + if len(only_in_app1) != 0: + print('Directories not present in ' + app2) + for dir in only_in_app1: + print(' ' + dir) + if len(only_in_app2) != 0: + print('Directories not present in ' + app1) + for dir in only_in_app2: + print(' ' + dir) + + sys.exit(1) + + +def is_binary(file): + out = os.popen('file "' + file + '"').read() + if out.find('Mach-O') == -1: + return False + return True + + +def is_xcconfig(file): + if re.match('.*\\.xcconfig', file): + return True + else: + return False + + +def diff_binaries(tempdir, self_base_path, file1, file2): + diff_app = tempdir + '/main' + if not os.path.isfile(diff_app): + if not os.path.isfile(self_base_path + '/main.cpp'): + print('Could not find ' + self_base_path + '/main.cpp') + sys.exit(1) + subprocess.call(['clang', self_base_path + '/main.cpp', '-lc++', '-o', diff_app]) + if not os.path.isfile(diff_app): + print('Could not compile ' + self_base_path + '/main.cpp') + sys.exit(1) + + result = os.popen(diff_app + ' ' + file1 + ' ' + file2).read().strip() + if result == 'Encrypted': + return 'binary_encrypted' + elif result == 'Equal': + return 'equal' + elif result == 'Not Equal': + return 'not_equal' + else: + print('Unexpected data from binary diff code: ' + result) + sys.exit(1) + + +def is_plist(file1): + if file1.find('.plist') == -1: + return False + return True + + +def diff_plists(file1, file2): + remove_properties = ['UISupportedDevices', 'DTAppStoreToolsBuild', 'MinimumOSVersion', 'BuildMachineOSBuild'] + + clean1_properties = '' + clean2_properties = '' + + with open(os.devnull, 'w') as devnull: + for property in remove_properties: + if not subprocess.call(['plutil', '-extract', property, 'xml1', '-o', '-', file1], stderr=devnull, stdout=devnull): + clean1_properties += ' | plutil -remove ' + property + ' -r -o - -- -' + if not subprocess.call(['plutil', '-extract', property, 'xml1', '-o', '-', file2], stderr=devnull, stdout=devnull): + clean2_properties += ' | plutil -remove ' + property + ' -r -o - -- -' + + data1 = os.popen('plutil -convert xml1 "' + file1 + '" -o -' + clean1_properties).read() + data2 = os.popen('plutil -convert xml1 "' + file2 + '" -o -' + clean2_properties).read() + + if data1 == data2: + return 'equal' + else: + with open('lhs.plist', 'wb') as f: + f.write(str.encode(data1)) + with open('rhs.plist', 'wb') as f: + f.write(str.encode(data2)) + sys.exit(1) + return 'not_equal' + + +def diff_xcconfigs(file1, file2): + with open(file1, 'rb') as f: + data1 = f.read().strip() + with open(file2, 'rb') as f: + data2 = f.read().strip() + if data1 != data2: + return 'not_equal' + return 'equal' + + +def diff_files(app1, files1, app2, files2): + only_in_app1 = files1.difference(files2) + only_in_app2 = files2.difference(files1) + if len(only_in_app1) == 0 and len(only_in_app2) == 0: + return + if len(only_in_app1) != 0: + print('Files not present in ' + app2) + for f in only_in_app1: + print(' ' + f) + if len(only_in_app2) != 0: + print('Files not present in ' + app1) + for f in only_in_app2: + print(' ' + f) + + sys.exit(1) + + +def base_app_dir(path): + return path + + +def diff_file(tempdir, self_base_path, path1, path2): + if is_plist(path1): + return diff_plists(path1, path2) + elif is_binary(path1): + return diff_binaries(tempdir, self_base_path, path1, path2) + elif is_xcconfig(path1): + return diff_xcconfigs(path1, path2) + else: + if filecmp.cmp(path1, path2): + return 'equal' + return 'not_equal' + + +def appdiff(self_base_path, app1, app2): + tempdir = tempfile.mkdtemp() + + app1_dir = app1 + app2_dir = app2 + + (app1_dirs, app1_files) = get_file_list(base_app_dir(app1_dir)) + (app2_dirs, app2_files) = get_file_list(base_app_dir(app2_dir)) + + clean_app1_dirs = remove_codesign_dirs(app1_dirs) + clean_app2_dirs = remove_codesign_dirs(app2_dirs) + + clean_app1_files = remove_codesign_files(app1_files) + clean_app2_files = remove_codesign_files(app2_files) + + diff_dirs(app1, clean_app1_dirs, app2, clean_app2_dirs) + diff_files(app1, clean_app1_files, app2, clean_app2_files) + + clean_app1_files, plugin_app1_files = remove_plugin_files(clean_app1_files) + clean_app2_files, plugin_app2_files = remove_plugin_files(clean_app2_files) + + clean_app1_files, plugin_app1_files = remove_asset_files(clean_app1_files) + clean_app2_files, plugin_app2_files = remove_asset_files(clean_app2_files) + + clean_app1_files, nib_app1_files = remove_nib_files(clean_app1_files) + clean_app2_files, nib_app2_files = remove_nib_files(clean_app2_files) + + different_files = [] + encrypted_files = [] + for relative_file_path in clean_app1_files: + file_result = diff_file(tempdir, self_base_path, base_app_dir(app1_dir) + '/' + relative_file_path, base_app_dir(app2_dir) + '/' + relative_file_path) + if file_result == 'equal': + pass + elif file_result == 'binary_encrypted': + encrypted_files.append(relative_file_path) + else: + different_files.append(relative_file_path) + + if len(different_files) != 0: + print('Different files in ' + app1 + ' and ' + app2) + for relative_file_path in different_files: + print(' ' + relative_file_path) + else: + if len(encrypted_files) != 0: + print(' Excluded files that couldn\'t be checked due to being encrypted:') + for relative_file_path in encrypted_files: + print(' ' + relative_file_path) + if len(plugin_app1_files) != 0: + print(' APPs contain PlugIns directory with app extensions. Extensions can\'t currently be checked.') + if len(nib_app1_files) != 0: + print(' APPs contain .nib (compiled Interface Builder) files that are compiled by the App Store and can\'t currently be checked:') + for relative_file_path in nib_app1_files: + print(' ' + relative_file_path) + + +if len(sys.argv) != 3: + print('Usage: appdiff app1 app2') + sys.exit(1) + +appdiff(os.path.dirname(sys.argv[0]), sys.argv[1], sys.argv[2]) diff --git a/buildbox/build-vm.sh b/buildbox/build-vm.sh new file mode 100644 index 0000000000..6dac5233ae --- /dev/null +++ b/buildbox/build-vm.sh @@ -0,0 +1,10 @@ +#!/bin/sh + + +export PATH="$PATH:$HOME/.fastlane/bin" + +BUILD_CONFIGURATION=$1 + +cd ~/build +fastlane $BUILD_CONFIGURATION +tar cf "./output/Telegram.tar" -C "./output" . diff --git a/buildbox/build.sh b/buildbox/build.sh new file mode 100644 index 0000000000..a73d045c6e --- /dev/null +++ b/buildbox/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -x +set -e + + +MACOS_VERSION="10.15" +XCODE_VERSION="10.3" +GUEST_SHELL="bash" + +VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)" + +BUILD_MACHINE="macOS"; + + +BUILDBOX_DIR="buildbox" +BUILD_CONFIGURATION="$1" + +rm -rf "$HOME/build-$BUILD_CONFIGURATION" +mkdir -p "$HOME/build-$BUILD_CONFIGURATION" + +PROCESS_ID="$$" +VM_NAME="$VM_BASE_NAME-$(openssl rand -hex 10)-build-telegram-$PROCESS_ID" + +prlctl clone "$VM_BASE_NAME" --linked --name "$VM_NAME" +prlctl start "$VM_NAME" + + +rm -f "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" +tar cf "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" --exclude "$BUILDBOX_DIR" --exclude ".git" --exclude "./submodules/telegram-ios/.git" --exclude "./submodules/rlottie/.git" --exclude "./submodules/Sparkle/.git" --exclude "./submodules/ton/.git" --exclude "./submodules/Zip/.git" --exclude "./submodules/libtgvoip/.git" --exclude "build" "." + + + +while [ 1 ]; do + TEST_IP=$(prlctl exec "$VM_NAME" "ifconfig | grep inet | grep broadcast | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1 | tr '\n' '\0'" || echo "") + if [ ! -z "$TEST_IP" ]; then + RESPONSE=$(ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$TEST_IP" -o ServerAliveInterval=60 -t "echo -n 1") + if [ "$RESPONSE" == "1" ]; then + VM_IP="$TEST_IP" + break + fi + fi +sleep 1 +done +# +ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "rm -rf build/" +ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "mkdir -p build;" +scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" telegram@"$VM_IP":build + +ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "tar -xf build/Telegram.tar -C ./build" +ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "mkdir -p build/buildbox" +scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BUILDBOX_DIR/build-vm.sh" telegram@"$VM_IP":build/buildbox +ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "$GUEST_SHELL -l build/buildbox/build-vm.sh $BUILD_CONFIGURATION" || true + +scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr telegram@"$VM_IP":build/output/Telegram.tar "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" + + +tar -xf "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" -C "$HOME/build-$BUILD_CONFIGURATION" +rm -f "$HOME/build-$BUILD_CONFIGURATION/Telegram.tar" + +prlctl stop "$VM_NAME" --kill +prlctl delete "$VM_NAME" + diff --git a/buildbox/cleanup-telegram-build-vms.sh b/buildbox/cleanup-telegram-build-vms.sh new file mode 100644 index 0000000000..6e79d27856 --- /dev/null +++ b/buildbox/cleanup-telegram-build-vms.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +case "$(uname -s)" in +Linux*) BUILD_MACHINE=linux;; +Darwin*) BUILD_MACHINE=macOS;; +*) BUILD_MACHINE="" +esac + +function list_include_item { +local list="$1" +local item="$2" +if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then +result=0 +else +result=1 +fi +return $result +} + +function clean_once { +RUNNING_PIDS=$(pgrep -f buildbox/build-telegram.sh) + +if [ "$BUILD_MACHINE" == "linux" ]; then +virsh list --all --name | grep build-telegram | while read vm ; do +VM_PID=$(echo $vm | egrep -o 'build-telegram-[0-9]+' | egrep -o '[0-9]+') +if [ ! -z "$VM_PID" ] && [ ! -z "$vm" ]; then +if `list_include_item "$RUNNING_PIDS" "$VM_PID"` ; then +echo "$vm:$VM_PID is still valid" +else +virsh destroy "$vm" || true +virsh undefine "$vm" --remove-all-storage --nvram || true +fi +else +echo "Can't parse VM string $vm" +fi +done +elif [ "$BUILD_MACHINE" == "macOS" ]; then +prlctl list -a | grep build-telegram | while read vm ; do +VM_PID=$(echo $vm | grep -Eo 'build-telegram-\d+' | grep -Eo '\d+') +VM_UUID=$(echo $vm | grep -Eo '\{(\d|[a-f]|-)*\}') +if [ ! -z "$VM_PID" ] && [ ! -z "$VM_UUID" ]; then +if `list_include_item "$RUNNING_PIDS" "$VM_PID"` ; then +echo "$VM_UUID:$VM_PID is still valid" +else +prlctl stop "$VM_UUID" --kill || true +prlctl delete "$VM_UUID" || true +fi +else +echo "Can't parse VM string $vm" +fi +done +else +echo "Unknown build machine $(uname -s)" +fi +} + +if [ "$1" == "loop" ]; then +while [ 1 ]; do +clean_once +sleep 10 +done +else +clean_once +fi diff --git a/buildbox/internal.sh b/buildbox/internal.sh new file mode 100644 index 0000000000..566a0d8936 --- /dev/null +++ b/buildbox/internal.sh @@ -0,0 +1,12 @@ +#/bin/sh +set -x +set -e + +export PATH="$PATH:$PWD/../deploy" +export PATH="$PATH:$HOME/.fastlane/bin" + +tag="$1" +sh ./buildbox/cleanup-telegram-build-vms.sh +sh ./buildbox/build.sh $tag +sh deploy-$tag.sh ~/build-$tag $PWD Telegram.app ~/.credentials/dsa-$tag +rm -rf ~/build-$tag diff --git a/buildbox/main.cpp b/buildbox/main.cpp new file mode 100644 index 0000000000..99c513eda5 --- /dev/null +++ b/buildbox/main.cpp @@ -0,0 +1,262 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t funcSwap32(uint32_t input) { + return OSSwapBigToHostInt32(input); +} + +static uint32_t funcNoSwap32(uint32_t input) { + return OSSwapLittleToHostInt32(input); +} + +static bool cleanArch(std::vector &archData, bool &isEncrypted) { + uint32_t (*swap32)(uint32_t) = funcNoSwap32; + + uint32_t offset = 0; + + const struct mach_header* header = (struct mach_header*)(archData.data() + offset); + + switch (header->magic) { + case MH_CIGAM: + swap32 = funcSwap32; + case MH_MAGIC: + offset += sizeof(struct mach_header); + break; + case MH_CIGAM_64: + swap32 = funcSwap32; + case MH_MAGIC_64: + offset += sizeof(struct mach_header_64); + break; + default: + return nullptr; + } + + uint32_t commandCount = swap32(header->ncmds); + + for (uint32_t i = 0; i < commandCount; i++) { + const struct load_command* loadCommand = (const struct load_command*)(archData.data() + offset); + uint32_t commandSize = swap32(loadCommand->cmdsize); + + uint32_t commandType = swap32(loadCommand->cmd); + if (commandType == LC_CODE_SIGNATURE) { + const struct linkedit_data_command *dataCommand = (const struct linkedit_data_command *)(archData.data() + offset); + uint32_t dataOffset = swap32(dataCommand->dataoff); + uint32_t dataSize = swap32(dataCommand->datasize); + + // account for different signature size + memset(archData.data() + offset + offsetof(linkedit_data_command, datasize), 0, sizeof(uint32_t)); + + // remove signature + archData.erase(archData.begin() + dataOffset, archData.begin() + dataOffset + dataSize); + } else if (commandType == LC_SEGMENT_64) { + const struct segment_command_64 *segmentCommand = (const struct segment_command_64 *)(archData.data() + offset); + std::string segmentName = std::string(segmentCommand->segname); + if (segmentName == "__LINKEDIT") { + // account for different signature size + memset(archData.data() + offset + offsetof(segment_command_64, vmsize), 0, sizeof(uint32_t)); + // account for different file size because of signatures + memset(archData.data() + offset + offsetof(segment_command_64, filesize), 0, sizeof(uint32_t)); + } + } else if (commandType == LC_ID_DYLIB) { + // account for dylib timestamp + memset(archData.data() + offset + offsetof(dylib_command, dylib) + offsetof(struct dylib, timestamp), 0, sizeof(uint32_t)); + } else if (commandType == LC_UUID) { + // account for dylib uuid + memset(archData.data() + offset + offsetof(uuid_command, uuid), 0, 16); + } else if (commandType == LC_ENCRYPTION_INFO_64) { + const struct encryption_info_command_64 *encryptionInfoCommand = (const struct encryption_info_command_64 *)(archData.data() + offset); + if (encryptionInfoCommand->cryptid != 0) { + isEncrypted = true; + } + } + + offset += commandSize; + } + + return true; +} + +static std::vector parseFat(std::vector const &fileData) { + size_t offset = 0; + + const struct fat_header *fatHeader = (const struct fat_header *)fileData.data(); + offset += sizeof(*fatHeader); + + size_t initialOffset = offset; + + uint32_t archCount = OSSwapBigToHostInt32(fatHeader->nfat_arch); + + for (uint32_t i = 0; i < archCount; i++) { + const struct fat_arch *arch = (const struct fat_arch *)(fileData.data() + offset); + offset += sizeof(*arch); + + uint32_t archOffset = OSSwapBigToHostInt32(arch->offset); + uint32_t archSize = OSSwapBigToHostInt32(arch->size); + cpu_type_t cputype = OSSwapBigToHostInt32(arch->cputype); + + if (cputype == CPU_TYPE_ARM64) { + std::vector archData; + archData.resize(archSize); + memcpy(archData.data(), fileData.data() + archOffset, archSize); + return archData; + } + } + + offset = initialOffset; + + for (uint32_t i = 0; i < archCount; i++) { + const struct fat_arch *arch = (const struct fat_arch *)(fileData.data() + offset); + offset += sizeof(*arch); + + uint32_t archOffset = OSSwapBigToHostInt32(arch->offset); + uint32_t archSize = OSSwapBigToHostInt32(arch->size); + cpu_type_t cputype = OSSwapBigToHostInt32(arch->cputype); + cpu_type_t cpusubtype = OSSwapBigToHostInt32(arch->cpusubtype); + + if (cputype == CPU_TYPE_ARM && cpusubtype == CPU_SUBTYPE_ARM_V7K) { + std::vector archData; + archData.resize(archSize); + memcpy(archData.data(), fileData.data() + archOffset, archSize); + return archData; + } + } + + return std::vector(); +} + +static std::vector parseMachO(std::vector const &fileData) { + const uint32_t *magic = (const uint32_t *)fileData.data(); + + if (*magic == FAT_CIGAM || *magic == FAT_MAGIC) { + return parseFat(fileData); + } else { + return fileData; + } +} + +static std::vector readFile(std::string const &file) { + int fd = open(file.c_str(), O_RDONLY); + + if (fd == -1) { + return std::vector(); + } + + struct stat st; + fstat(fd, &st); + + std::vector fileData; + fileData.resize((size_t)st.st_size); + read(fd, fileData.data(), (size_t)st.st_size); + close(fd); + + return fileData; +} + +static void writeDataToFile(std::vector const &data, std::string const &path) { + int fd = open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + return; + } + + write(fd, data.data(), data.size()); + + close(fd); +} + +static std::vector stripSwiftSymbols(std::string const &file) { + std::string command; + command += "xcrun strip -ST -o /dev/stdout \""; + command += file; + command += "\" 2> /dev/null"; + + uint8_t buffer[128]; + std::vector result; + FILE *pipe = popen(command.c_str(), "r"); + if (!pipe) { + throw std::runtime_error("popen() failed!"); + } + while (true) { + size_t readBytes = fread(buffer, 1, 128, pipe); + if (readBytes <= 0) { + break; + } + result.insert(result.end(), buffer, buffer + readBytes); + } + pclose(pipe); + + return result; +} + +static bool endsWith(std::string const &mainStr, std::string const &toMatch) { + if(mainStr.size() >= toMatch.size() && mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch) == 0) { + return true; + } else { + return false; + } +} + +int main(int argc, const char *argv[]) { + if (argc != 3) { + printf("Usage: machofilediff file1 file2\n"); + return 1; + } + + std::string file1 = argv[1]; + std::string file2 = argv[2]; + + std::vector fileData1; + if (endsWith(file1, ".dylib")) { + fileData1 = stripSwiftSymbols(file1); + } else { + fileData1 = readFile(file1); + } + + std::vector fileData2; + if (endsWith(file2, ".dylib")) { + fileData2 = stripSwiftSymbols(file2); + } else { + fileData2 = readFile(file2); + } + + std::vector arch1 = parseMachO(fileData1); + if (arch1.size() == 0) { + printf("Couldn't parse %s\n", file1.c_str()); + return 1; + } + + std::vector arch2 = parseMachO(fileData2); + if (arch2.size() == 0) { + printf("Couldn't parse %s\n", file2.c_str()); + return 1; + } + + bool arch1Encrypted = false; + bool arch2Encrypted = false; + cleanArch(arch1, arch1Encrypted); + cleanArch(arch2, arch2Encrypted); + + if (arch1 == arch2) { + printf("Equal\n"); + return 0; + } else { + if (arch1Encrypted || arch2Encrypted) { + printf("Encrypted\n"); + } else { + printf("Not Equal\n"); + } + + return 1; + } + + return 0; +} diff --git a/buildbox/sync-toolbox.sh b/buildbox/sync-toolbox.sh new file mode 100644 index 0000000000..73f7316af3 --- /dev/null +++ b/buildbox/sync-toolbox.sh @@ -0,0 +1,17 @@ +#/bin/sh +set -x +set -e + +export PATH="$PATH:$HOME/.credentials" +source variables.sh +MAIN_REPOSITORY=$PWD +cd .. +if [ ! -d "./deploy" ]; then + mkdir -p ./deploy + git clone "$deploy_repository" deploy +else + cd ./deploy + git reset --hard + git pull origin master + cd .. +fi diff --git a/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.pbxproj b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..27a0d5ffa2 --- /dev/null +++ b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.pbxproj @@ -0,0 +1,634 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7918FEB240CF659002011CA /* CryptoUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918FE9240CF659002011CA /* CryptoUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FF9240CF6C8002011CA /* Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918FF8240CF6C8002011CA /* Crypto.m */; }; + A7918FFC240CF6D9002011CA /* Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918FFB240CF6D9002011CA /* Crypto.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7918FE6240CF659002011CA /* CryptoUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CryptoUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918FE9240CF659002011CA /* CryptoUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoUtils.h; sourceTree = ""; }; + A7918FEA240CF659002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7918FF8240CF6C8002011CA /* Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Crypto.m; path = "../../../../submodules/telegram-ios/submodules/CryptoUtils/Sources/Crypto.m"; sourceTree = ""; }; + A7918FFB240CF6D9002011CA /* Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Crypto.h; path = "../../../../submodules/telegram-ios/submodules/CryptoUtils/PublicHeaders/CryptoUtils/Crypto.h"; sourceTree = ""; }; + A791903A240CF9DB002011CA /* MtProtoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7918FE3240CF659002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7918FDC240CF659002011CA = { + isa = PBXGroup; + children = ( + A7918FE8240CF659002011CA /* CryptoUtils */, + A7918FE7240CF659002011CA /* Products */, + A7919039240CF9DB002011CA /* Frameworks */, + ); + sourceTree = ""; + }; + A7918FE7240CF659002011CA /* Products */ = { + isa = PBXGroup; + children = ( + A7918FE6240CF659002011CA /* CryptoUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7918FE8240CF659002011CA /* CryptoUtils */ = { + isa = PBXGroup; + children = ( + A7918FFA240CF6CF002011CA /* Headers */, + A7918FF7240CF6BD002011CA /* Sources */, + A7918FE9240CF659002011CA /* CryptoUtils.h */, + A7918FEA240CF659002011CA /* Info.plist */, + ); + path = CryptoUtils; + sourceTree = ""; + }; + A7918FF7240CF6BD002011CA /* Sources */ = { + isa = PBXGroup; + children = ( + A7918FF8240CF6C8002011CA /* Crypto.m */, + ); + path = Sources; + sourceTree = ""; + }; + A7918FFA240CF6CF002011CA /* Headers */ = { + isa = PBXGroup; + children = ( + A7918FFB240CF6D9002011CA /* Crypto.h */, + ); + path = Headers; + sourceTree = ""; + }; + A7919039240CF9DB002011CA /* Frameworks */ = { + isa = PBXGroup; + children = ( + A791903A240CF9DB002011CA /* MtProtoKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7918FE1240CF659002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918FEB240CF659002011CA /* CryptoUtils.h in Headers */, + A7918FFC240CF6D9002011CA /* Crypto.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7918FE5240CF659002011CA /* CryptoUtils */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7918FEE240CF659002011CA /* Build configuration list for PBXNativeTarget "CryptoUtils" */; + buildPhases = ( + A7918FE1240CF659002011CA /* Headers */, + A7918FE2240CF659002011CA /* Sources */, + A7918FE3240CF659002011CA /* Frameworks */, + A7918FE4240CF659002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CryptoUtils; + productName = CryptoUtils; + productReference = A7918FE6240CF659002011CA /* CryptoUtils.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7918FDD240CF659002011CA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7918FE5240CF659002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A7918FE0240CF659002011CA /* Build configuration list for PBXProject "CryptoUtils" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7918FDC240CF659002011CA; + productRefGroup = A7918FE7240CF659002011CA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7918FE5240CF659002011CA /* CryptoUtils */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7918FE4240CF659002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7918FE2240CF659002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918FF9240CF6C8002011CA /* Crypto.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7918FEC240CF659002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7918FED240CF659002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7918FEF240CF659002011CA /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = CryptoUtils/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.CryptoUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A7918FF0240CF659002011CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = CryptoUtils/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.CryptoUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A7918FF1240CF691002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7918FF2240CF691002011CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = CryptoUtils/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.CryptoUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A7918FF3240CF69A002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7918FF4240CF69A002011CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = CryptoUtils/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.CryptoUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7918FF5240CF6A7002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7918FF6240CF6A7002011CA /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = CryptoUtils/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.CryptoUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7918FE0240CF659002011CA /* Build configuration list for PBXProject "CryptoUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918FEC240CF659002011CA /* DebugAppStore */, + A7918FF3240CF69A002011CA /* HockeyappMacAlpha */, + A7918FF1240CF691002011CA /* DebugHockeyapp */, + A7918FED240CF659002011CA /* ReleaseAppStore */, + A7918FF5240CF6A7002011CA /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; + A7918FEE240CF659002011CA /* Build configuration list for PBXNativeTarget "CryptoUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918FEF240CF659002011CA /* DebugAppStore */, + A7918FF4240CF69A002011CA /* HockeyappMacAlpha */, + A7918FF2240CF691002011CA /* DebugHockeyapp */, + A7918FF0240CF659002011CA /* ReleaseAppStore */, + A7918FF6240CF6A7002011CA /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7918FDD240CF659002011CA /* Project object */; +} diff --git a/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..84c209f5ea --- /dev/null +++ b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/CryptoUtils/CryptoUtils.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/CryptoUtils/CryptoUtils/CryptoUtils.h b/core-xprojects/CryptoUtils/CryptoUtils/CryptoUtils.h new file mode 100644 index 0000000000..76b67bce53 --- /dev/null +++ b/core-xprojects/CryptoUtils/CryptoUtils/CryptoUtils.h @@ -0,0 +1,19 @@ +// +// CryptoUtils.h +// CryptoUtils +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for CryptoUtils. +FOUNDATION_EXPORT double CryptoUtilsVersionNumber; + +//! Project version string for CryptoUtils. +FOUNDATION_EXPORT const unsigned char CryptoUtilsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import diff --git a/core-xprojects/CryptoUtils/CryptoUtils/Info.plist b/core-xprojects/CryptoUtils/CryptoUtils/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/CryptoUtils/CryptoUtils/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProviderHeader.h b/core-xprojects/EncryptionProviderMac/EncryptionProviderHeader.h new file mode 100644 index 0000000000..9401626d52 --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProviderHeader.h @@ -0,0 +1,19 @@ +// +// EncryptionProvider.h +// EncryptionProvider +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for EncryptionProvider. +FOUNDATION_EXPORT double EncryptionProviderVersionNumber; + +//! Project version string for EncryptionProvider. +FOUNDATION_EXPORT const unsigned char EncryptionProviderVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.pbxproj b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d10a0ddd98 --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1015 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A790A30C236B072F000451B5 /* EncryptionProviderHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = A790A2A0236AFA72000451B5 /* EncryptionProviderHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DB6240CED79002011CA /* EncryptionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DB5240CED78002011CA /* EncryptionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A72D452A236AF6770052FA81 /* EncryptionProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EncryptionProvider.h; sourceTree = ""; }; + A72D4534236AF6F40052FA81 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + A790A29E236AFA72000451B5 /* EncryptionProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = EncryptionProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A2A0236AFA72000451B5 /* EncryptionProviderHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EncryptionProviderHeader.h; sourceTree = ""; }; + A790A2A1236AFA72000451B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7918DB5240CED78002011CA /* EncryptionProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EncryptionProvider.h; path = "../../submodules/telegram-ios/submodules/EncryptionProvider/PublicHeaders/EncryptionProvider/EncryptionProvider.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A790A29B236AFA72000451B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A72D451E236AF6770052FA81 = { + isa = PBXGroup; + children = ( + A7918DB5240CED78002011CA /* EncryptionProvider.h */, + A72D4529236AF6770052FA81 /* Sources */, + A790A29F236AFA72000451B5 /* EncryptionProvider */, + A72D4528236AF6770052FA81 /* Products */, + A72D4533236AF6F40052FA81 /* Frameworks */, + ); + sourceTree = ""; + }; + A72D4528236AF6770052FA81 /* Products */ = { + isa = PBXGroup; + children = ( + A790A29E236AFA72000451B5 /* EncryptionProvider.framework */, + ); + name = Products; + sourceTree = ""; + }; + A72D4529236AF6770052FA81 /* Sources */ = { + isa = PBXGroup; + children = ( + A72D452A236AF6770052FA81 /* EncryptionProvider.h */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/EncryptionProvider/Sources"; + sourceTree = ""; + }; + A72D4533236AF6F40052FA81 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A72D4534236AF6F40052FA81 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A790A29F236AFA72000451B5 /* EncryptionProvider */ = { + isa = PBXGroup; + children = ( + A790A2A0236AFA72000451B5 /* EncryptionProviderHeader.h */, + A790A2A1236AFA72000451B5 /* Info.plist */, + ); + name = EncryptionProvider; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A790A299236AFA72000451B5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DB6240CED79002011CA /* EncryptionProvider.h in Headers */, + A790A30C236B072F000451B5 /* EncryptionProviderHeader.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A790A29D236AFA72000451B5 /* EncryptionProvider */ = { + isa = PBXNativeTarget; + buildConfigurationList = A790A2AF236AFA72000451B5 /* Build configuration list for PBXNativeTarget "EncryptionProvider" */; + buildPhases = ( + A790A299236AFA72000451B5 /* Headers */, + A790A29A236AFA72000451B5 /* Sources */, + A790A29B236AFA72000451B5 /* Frameworks */, + A790A29C236AFA72000451B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = EncryptionProvider; + productName = EncryptionProvider; + productReference = A790A29E236AFA72000451B5 /* EncryptionProvider.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A72D451F236AF6770052FA81 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A790A29D236AFA72000451B5 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A72D4522236AF6770052FA81 /* Build configuration list for PBXProject "EncryptionProvider_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A72D451E236AF6770052FA81; + productRefGroup = A72D4528236AF6770052FA81 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A790A29D236AFA72000451B5 /* EncryptionProvider */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A790A29C236AFA72000451B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A790A29A236AFA72000451B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A72D4536236AF7540052FA81 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = DebugHockeyapp; + }; + A72D4538236AF75E0052FA81 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = HockeyappMacAlpha; + }; + A72D453A236AF7670052FA81 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = DebugAppStore; + }; + A72D453E236AF7780052FA81 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = ReleaseAppStore; + }; + A72D4540236AF77E0052FA81 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = ReleaseHockeyapp; + }; + A790A2B1236AFA72000451B5 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A790A2B2236AFA72000451B5 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A790A2B3236AFA72000451B5 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A790A2B5236AFA72000451B5 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A790A2B6236AFA72000451B5 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7F282D0238EAB0500742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Github; + }; + A7F282D1238EAB0500742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.EncryptionProvider; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A72D4522236AF6770052FA81 /* Build configuration list for PBXProject "EncryptionProvider_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A72D4536236AF7540052FA81 /* DebugHockeyapp */, + A72D4538236AF75E0052FA81 /* HockeyappMacAlpha */, + A72D453A236AF7670052FA81 /* DebugAppStore */, + A7F282D0238EAB0500742C20 /* Github */, + A72D453E236AF7780052FA81 /* ReleaseAppStore */, + A72D4540236AF77E0052FA81 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + A790A2AF236AFA72000451B5 /* Build configuration list for PBXNativeTarget "EncryptionProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A2B1236AFA72000451B5 /* DebugHockeyapp */, + A790A2B2236AFA72000451B5 /* HockeyappMacAlpha */, + A790A2B3236AFA72000451B5 /* DebugAppStore */, + A7F282D1238EAB0500742C20 /* Github */, + A790A2B5236AFA72000451B5 /* ReleaseAppStore */, + A790A2B6236AFA72000451B5 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A72D451F236AF6770052FA81 /* Project object */; +} diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..61bd5ca3da --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..9550516b36 Binary files /dev/null and b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/EncryptionProvider.xcscheme b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/EncryptionProvider.xcscheme new file mode 100644 index 0000000000..834be1769c --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/EncryptionProvider.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..d5f14b9577 --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/EncryptionProvider_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,29 @@ + + + + + SchemeUserState + + EncryptionProvider.xcscheme + + isShown + + orderHint + 22 + + EncryptionProviderTests.xcscheme_^#shared#^_ + + orderHint + 1 + + + SuppressBuildableAutocreation + + A790A29D236AFA72000451B5 + + primary + + + + + diff --git a/core-xprojects/EncryptionProviderMac/Info.plist b/core-xprojects/EncryptionProviderMac/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/EncryptionProviderMac/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/GraphCore/Graph/Info.plist b/core-xprojects/GraphCore/Graph/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/GraphCore/Graph/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/GraphCore/GraphCore.xcodeproj/project.pbxproj b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b5c95d6fdd --- /dev/null +++ b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.pbxproj @@ -0,0 +1,908 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + D0A9B733241CEFAC007DF938 /* HorizontalScalesRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6F9241CEFAC007DF938 /* HorizontalScalesRenderer.swift */; }; + D0A9B734241CEFAC007DF938 /* BarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6FA241CEFAC007DF938 /* BarChartRenderer.swift */; }; + D0A9B735241CEFAC007DF938 /* LinesChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6FB241CEFAC007DF938 /* LinesChartRenderer.swift */; }; + D0A9B736241CEFAC007DF938 /* PecentChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6FC241CEFAC007DF938 /* PecentChartRenderer.swift */; }; + D0A9B737241CEFAC007DF938 /* PerformanceRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6FD241CEFAC007DF938 /* PerformanceRenderer.swift */; }; + D0A9B738241CEFAC007DF938 /* VerticalLinesRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B6FE241CEFAC007DF938 /* VerticalLinesRenderer.swift */; }; + D0A9B73A241CEFAC007DF938 /* VerticalScalesRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B700241CEFAC007DF938 /* VerticalScalesRenderer.swift */; }; + D0A9B73B241CEFAC007DF938 /* BaseChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B701241CEFAC007DF938 /* BaseChartRenderer.swift */; }; + D0A9B73C241CEFAC007DF938 /* PieChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B702241CEFAC007DF938 /* PieChartRenderer.swift */; }; + D0A9B73D241CEFAC007DF938 /* ChartDetailsRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B703241CEFAC007DF938 /* ChartDetailsRenderer.swift */; }; + D0A9B73E241CEFAC007DF938 /* PercentPieAnimationRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B704241CEFAC007DF938 /* PercentPieAnimationRenderer.swift */; }; + D0A9B73F241CEFAC007DF938 /* PieChartComponentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B707241CEFAC007DF938 /* PieChartComponentController.swift */; }; + D0A9B740241CEFAC007DF938 /* PercentPieChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B708241CEFAC007DF938 /* PercentPieChartController.swift */; }; + D0A9B741241CEFAC007DF938 /* PercentChartComponentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B709241CEFAC007DF938 /* PercentChartComponentController.swift */; }; + D0A9B742241CEFAC007DF938 /* BaseChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B70A241CEFAC007DF938 /* BaseChartController.swift */; }; + D0A9B743241CEFAC007DF938 /* BarsComponentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B70C241CEFAC007DF938 /* BarsComponentController.swift */; }; + D0A9B744241CEFAC007DF938 /* StackedBarsChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B70D241CEFAC007DF938 /* StackedBarsChartController.swift */; }; + D0A9B745241CEFAC007DF938 /* LinesComponentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B70E241CEFAC007DF938 /* LinesComponentController.swift */; }; + D0A9B746241CEFAC007DF938 /* DailyBarsChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B70F241CEFAC007DF938 /* DailyBarsChartController.swift */; }; + D0A9B747241CEFAC007DF938 /* StepBarsChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B710241CEFAC007DF938 /* StepBarsChartController.swift */; }; + D0A9B749241CEFAC007DF938 /* GeneralChartComponentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B712241CEFAC007DF938 /* GeneralChartComponentController.swift */; }; + D0A9B74A241CEFAC007DF938 /* GeneralLinesChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B714241CEFAC007DF938 /* GeneralLinesChartController.swift */; }; + D0A9B74B241CEFAC007DF938 /* TwoAxisLinesChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B715241CEFAC007DF938 /* TwoAxisLinesChartController.swift */; }; + D0A9B74C241CEFAC007DF938 /* BaseLinesChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B716241CEFAC007DF938 /* BaseLinesChartController.swift */; }; + D0A9B74D241CEFAC007DF938 /* ColorMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B718241CEFAC007DF938 /* ColorMode.swift */; }; + D0A9B74E241CEFAC007DF938 /* ChartLineData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B719241CEFAC007DF938 /* ChartLineData.swift */; }; + D0A9B74F241CEFAC007DF938 /* LinesSelectionLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B71A241CEFAC007DF938 /* LinesSelectionLabel.swift */; }; + D0A9B750241CEFAC007DF938 /* LinesChartLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B71B241CEFAC007DF938 /* LinesChartLabel.swift */; }; + D0A9B751241CEFAC007DF938 /* TimeInterval+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B71D241CEFAC007DF938 /* TimeInterval+Utils.swift */; }; + D0A9B752241CEFAC007DF938 /* AnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B71E241CEFAC007DF938 /* AnimationController.swift */; }; + D0A9B753241CEFAC007DF938 /* TextUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B71F241CEFAC007DF938 /* TextUtils.swift */; }; + D0A9B754241CEFAC007DF938 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B720241CEFAC007DF938 /* UIView+Extensions.swift */; }; + D0A9B755241CEFAC007DF938 /* UIColor+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B721241CEFAC007DF938 /* UIColor+Utils.swift */; }; + D0A9B756241CEFAC007DF938 /* DisplayLinkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B722241CEFAC007DF938 /* DisplayLinkService.swift */; }; + D0A9B757241CEFAC007DF938 /* GlobalHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B723241CEFAC007DF938 /* GlobalHelpers.swift */; }; + D0A9B758241CEFAC007DF938 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B724241CEFAC007DF938 /* TimeZone.swift */; }; + D0A9B759241CEFAC007DF938 /* ScalesNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B725241CEFAC007DF938 /* ScalesNumberFormatter.swift */; }; + D0A9B75A241CEFAC007DF938 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B726241CEFAC007DF938 /* CGPoint+Extensions.swift */; }; + D0A9B75B241CEFAC007DF938 /* Array+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B727241CEFAC007DF938 /* Array+Utils.swift */; }; + D0A9B75C241CEFAC007DF938 /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B728241CEFAC007DF938 /* UIImage+Utils.swift */; }; + D0A9B75D241CEFAC007DF938 /* ClosedRange+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B729241CEFAC007DF938 /* ClosedRange+Utils.swift */; }; + D0A9B75E241CEFAC007DF938 /* CGFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B72A241CEFAC007DF938 /* CGFloat.swift */; }; + D0A9B75F241CEFAC007DF938 /* NumberFormatter+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B72B241CEFAC007DF938 /* NumberFormatter+Utils.swift */; }; + D0A9B760241CEFAC007DF938 /* GraphCore.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A9B72C241CEFAC007DF938 /* GraphCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0A9B761241CEFAC007DF938 /* ChartVisibilityItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B72E241CEFAC007DF938 /* ChartVisibilityItem.swift */; }; + D0A9B762241CEFAC007DF938 /* ChartsDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B72F241CEFAC007DF938 /* ChartsDataManager.swift */; }; + D0A9B763241CEFAC007DF938 /* Convert.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B730241CEFAC007DF938 /* Convert.swift */; }; + D0A9B764241CEFAC007DF938 /* ChartsCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B731241CEFAC007DF938 /* ChartsCollection.swift */; }; + D0A9B765241CEFAC007DF938 /* ChartsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A9B732241CEFAC007DF938 /* ChartsError.swift */; }; + D0DEF74B242BBCE200A34A30 /* LineBulletsRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DEF74A242BBCE200A34A30 /* LineBulletsRenderer.swift */; }; + D0DEF74C242BBDBE00A34A30 /* TwoAxisStepBarsChartController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DEF746242BB79900A34A30 /* TwoAxisStepBarsChartController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7831B152403BFE30056AEAC /* TGUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TGUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A789E0C223E841B600AEB34A /* GraphCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GraphCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A789E0C623E841B600AEB34A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0A9B6F9241CEFAC007DF938 /* HorizontalScalesRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalScalesRenderer.swift; sourceTree = ""; }; + D0A9B6FA241CEFAC007DF938 /* BarChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartRenderer.swift; sourceTree = ""; }; + D0A9B6FB241CEFAC007DF938 /* LinesChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinesChartRenderer.swift; sourceTree = ""; }; + D0A9B6FC241CEFAC007DF938 /* PecentChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PecentChartRenderer.swift; sourceTree = ""; }; + D0A9B6FD241CEFAC007DF938 /* PerformanceRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceRenderer.swift; sourceTree = ""; }; + D0A9B6FE241CEFAC007DF938 /* VerticalLinesRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalLinesRenderer.swift; sourceTree = ""; }; + D0A9B6FF241CEFAC007DF938 /* LineBulletsRenerer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBulletsRenerer.swift; sourceTree = ""; }; + D0A9B700241CEFAC007DF938 /* VerticalScalesRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalScalesRenderer.swift; sourceTree = ""; }; + D0A9B701241CEFAC007DF938 /* BaseChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseChartRenderer.swift; sourceTree = ""; }; + D0A9B702241CEFAC007DF938 /* PieChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartRenderer.swift; sourceTree = ""; }; + D0A9B703241CEFAC007DF938 /* ChartDetailsRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDetailsRenderer.swift; sourceTree = ""; }; + D0A9B704241CEFAC007DF938 /* PercentPieAnimationRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PercentPieAnimationRenderer.swift; sourceTree = ""; }; + D0A9B707241CEFAC007DF938 /* PieChartComponentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartComponentController.swift; sourceTree = ""; }; + D0A9B708241CEFAC007DF938 /* PercentPieChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PercentPieChartController.swift; sourceTree = ""; }; + D0A9B709241CEFAC007DF938 /* PercentChartComponentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PercentChartComponentController.swift; sourceTree = ""; }; + D0A9B70A241CEFAC007DF938 /* BaseChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseChartController.swift; sourceTree = ""; }; + D0A9B70C241CEFAC007DF938 /* BarsComponentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsComponentController.swift; sourceTree = ""; }; + D0A9B70D241CEFAC007DF938 /* StackedBarsChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackedBarsChartController.swift; sourceTree = ""; }; + D0A9B70E241CEFAC007DF938 /* LinesComponentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinesComponentController.swift; sourceTree = ""; }; + D0A9B70F241CEFAC007DF938 /* DailyBarsChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DailyBarsChartController.swift; sourceTree = ""; }; + D0A9B710241CEFAC007DF938 /* StepBarsChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepBarsChartController.swift; sourceTree = ""; }; + D0A9B712241CEFAC007DF938 /* GeneralChartComponentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralChartComponentController.swift; sourceTree = ""; }; + D0A9B714241CEFAC007DF938 /* GeneralLinesChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralLinesChartController.swift; sourceTree = ""; }; + D0A9B715241CEFAC007DF938 /* TwoAxisLinesChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoAxisLinesChartController.swift; sourceTree = ""; }; + D0A9B716241CEFAC007DF938 /* BaseLinesChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseLinesChartController.swift; sourceTree = ""; }; + D0A9B718241CEFAC007DF938 /* ColorMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorMode.swift; sourceTree = ""; }; + D0A9B719241CEFAC007DF938 /* ChartLineData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLineData.swift; sourceTree = ""; }; + D0A9B71A241CEFAC007DF938 /* LinesSelectionLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinesSelectionLabel.swift; sourceTree = ""; }; + D0A9B71B241CEFAC007DF938 /* LinesChartLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinesChartLabel.swift; sourceTree = ""; }; + D0A9B71D241CEFAC007DF938 /* TimeInterval+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utils.swift"; sourceTree = ""; }; + D0A9B71E241CEFAC007DF938 /* AnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationController.swift; sourceTree = ""; }; + D0A9B71F241CEFAC007DF938 /* TextUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextUtils.swift; sourceTree = ""; }; + D0A9B720241CEFAC007DF938 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; + D0A9B721241CEFAC007DF938 /* UIColor+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Utils.swift"; sourceTree = ""; }; + D0A9B722241CEFAC007DF938 /* DisplayLinkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkService.swift; sourceTree = ""; }; + D0A9B723241CEFAC007DF938 /* GlobalHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalHelpers.swift; sourceTree = ""; }; + D0A9B724241CEFAC007DF938 /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; + D0A9B725241CEFAC007DF938 /* ScalesNumberFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScalesNumberFormatter.swift; sourceTree = ""; }; + D0A9B726241CEFAC007DF938 /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; + D0A9B727241CEFAC007DF938 /* Array+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Utils.swift"; sourceTree = ""; }; + D0A9B728241CEFAC007DF938 /* UIImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; + D0A9B729241CEFAC007DF938 /* ClosedRange+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ClosedRange+Utils.swift"; sourceTree = ""; }; + D0A9B72A241CEFAC007DF938 /* CGFloat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGFloat.swift; sourceTree = ""; }; + D0A9B72B241CEFAC007DF938 /* NumberFormatter+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+Utils.swift"; sourceTree = ""; }; + D0A9B72C241CEFAC007DF938 /* GraphCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GraphCore.h; sourceTree = ""; }; + D0A9B72E241CEFAC007DF938 /* ChartVisibilityItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartVisibilityItem.swift; sourceTree = ""; }; + D0A9B72F241CEFAC007DF938 /* ChartsDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartsDataManager.swift; sourceTree = ""; }; + D0A9B730241CEFAC007DF938 /* Convert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Convert.swift; sourceTree = ""; }; + D0A9B731241CEFAC007DF938 /* ChartsCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartsCollection.swift; sourceTree = ""; }; + D0A9B732241CEFAC007DF938 /* ChartsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartsError.swift; sourceTree = ""; }; + D0DEF746242BB79900A34A30 /* TwoAxisStepBarsChartController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoAxisStepBarsChartController.swift; sourceTree = ""; }; + D0DEF74A242BBCE200A34A30 /* LineBulletsRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineBulletsRenderer.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A789E0BF23E841B600AEB34A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7831B142403BFE30056AEAC /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7831B152403BFE30056AEAC /* TGUIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A789E0B823E841B600AEB34A = { + isa = PBXGroup; + children = ( + A789E0C423E841B600AEB34A /* Graph */, + A789E0C323E841B600AEB34A /* Products */, + A7831B142403BFE30056AEAC /* Frameworks */, + ); + sourceTree = ""; + }; + A789E0C323E841B600AEB34A /* Products */ = { + isa = PBXGroup; + children = ( + A789E0C223E841B600AEB34A /* GraphCore.framework */, + ); + name = Products; + sourceTree = ""; + }; + A789E0C423E841B600AEB34A /* Graph */ = { + isa = PBXGroup; + children = ( + D0A9B6F6241CEFAC007DF938 /* Sources */, + A789E0C623E841B600AEB34A /* Info.plist */, + ); + path = Graph; + sourceTree = ""; + }; + D0A9B6F6241CEFAC007DF938 /* Sources */ = { + isa = PBXGroup; + children = ( + D0A9B6F7241CEFAC007DF938 /* Charts */, + D0A9B717241CEFAC007DF938 /* Models */, + D0A9B71C241CEFAC007DF938 /* Helpers */, + D0A9B72C241CEFAC007DF938 /* GraphCore.h */, + D0A9B72D241CEFAC007DF938 /* Charts Reader */, + ); + name = Sources; + path = "../../../submodules/telegram-ios/submodules/GraphCore/Sources"; + sourceTree = ""; + }; + D0A9B6F7241CEFAC007DF938 /* Charts */ = { + isa = PBXGroup; + children = ( + D0A9B6F8241CEFAC007DF938 /* Renderes */, + D0A9B705241CEFAC007DF938 /* Controllers */, + ); + path = Charts; + sourceTree = ""; + }; + D0A9B6F8241CEFAC007DF938 /* Renderes */ = { + isa = PBXGroup; + children = ( + D0DEF74A242BBCE200A34A30 /* LineBulletsRenderer.swift */, + D0A9B6F9241CEFAC007DF938 /* HorizontalScalesRenderer.swift */, + D0A9B6FA241CEFAC007DF938 /* BarChartRenderer.swift */, + D0A9B6FB241CEFAC007DF938 /* LinesChartRenderer.swift */, + D0A9B6FC241CEFAC007DF938 /* PecentChartRenderer.swift */, + D0A9B6FD241CEFAC007DF938 /* PerformanceRenderer.swift */, + D0A9B6FE241CEFAC007DF938 /* VerticalLinesRenderer.swift */, + D0A9B6FF241CEFAC007DF938 /* LineBulletsRenerer.swift */, + D0A9B700241CEFAC007DF938 /* VerticalScalesRenderer.swift */, + D0A9B701241CEFAC007DF938 /* BaseChartRenderer.swift */, + D0A9B702241CEFAC007DF938 /* PieChartRenderer.swift */, + D0A9B703241CEFAC007DF938 /* ChartDetailsRenderer.swift */, + D0A9B704241CEFAC007DF938 /* PercentPieAnimationRenderer.swift */, + ); + path = Renderes; + sourceTree = ""; + }; + D0A9B705241CEFAC007DF938 /* Controllers */ = { + isa = PBXGroup; + children = ( + D0A9B706241CEFAC007DF938 /* Percent And Pie */, + D0A9B70A241CEFAC007DF938 /* BaseChartController.swift */, + D0A9B70B241CEFAC007DF938 /* Stacked Bars */, + D0A9B712241CEFAC007DF938 /* GeneralChartComponentController.swift */, + D0A9B713241CEFAC007DF938 /* Lines */, + ); + path = Controllers; + sourceTree = ""; + }; + D0A9B706241CEFAC007DF938 /* Percent And Pie */ = { + isa = PBXGroup; + children = ( + D0A9B707241CEFAC007DF938 /* PieChartComponentController.swift */, + D0A9B708241CEFAC007DF938 /* PercentPieChartController.swift */, + D0A9B709241CEFAC007DF938 /* PercentChartComponentController.swift */, + ); + path = "Percent And Pie"; + sourceTree = ""; + }; + D0A9B70B241CEFAC007DF938 /* Stacked Bars */ = { + isa = PBXGroup; + children = ( + D0DEF746242BB79900A34A30 /* TwoAxisStepBarsChartController.swift */, + D0A9B70C241CEFAC007DF938 /* BarsComponentController.swift */, + D0A9B70D241CEFAC007DF938 /* StackedBarsChartController.swift */, + D0A9B70E241CEFAC007DF938 /* LinesComponentController.swift */, + D0A9B70F241CEFAC007DF938 /* DailyBarsChartController.swift */, + D0A9B710241CEFAC007DF938 /* StepBarsChartController.swift */, + ); + path = "Stacked Bars"; + sourceTree = ""; + }; + D0A9B713241CEFAC007DF938 /* Lines */ = { + isa = PBXGroup; + children = ( + D0A9B714241CEFAC007DF938 /* GeneralLinesChartController.swift */, + D0A9B715241CEFAC007DF938 /* TwoAxisLinesChartController.swift */, + D0A9B716241CEFAC007DF938 /* BaseLinesChartController.swift */, + ); + path = Lines; + sourceTree = ""; + }; + D0A9B717241CEFAC007DF938 /* Models */ = { + isa = PBXGroup; + children = ( + D0A9B718241CEFAC007DF938 /* ColorMode.swift */, + D0A9B719241CEFAC007DF938 /* ChartLineData.swift */, + D0A9B71A241CEFAC007DF938 /* LinesSelectionLabel.swift */, + D0A9B71B241CEFAC007DF938 /* LinesChartLabel.swift */, + ); + path = Models; + sourceTree = ""; + }; + D0A9B71C241CEFAC007DF938 /* Helpers */ = { + isa = PBXGroup; + children = ( + D0A9B71D241CEFAC007DF938 /* TimeInterval+Utils.swift */, + D0A9B71E241CEFAC007DF938 /* AnimationController.swift */, + D0A9B71F241CEFAC007DF938 /* TextUtils.swift */, + D0A9B720241CEFAC007DF938 /* UIView+Extensions.swift */, + D0A9B721241CEFAC007DF938 /* UIColor+Utils.swift */, + D0A9B722241CEFAC007DF938 /* DisplayLinkService.swift */, + D0A9B723241CEFAC007DF938 /* GlobalHelpers.swift */, + D0A9B724241CEFAC007DF938 /* TimeZone.swift */, + D0A9B725241CEFAC007DF938 /* ScalesNumberFormatter.swift */, + D0A9B726241CEFAC007DF938 /* CGPoint+Extensions.swift */, + D0A9B727241CEFAC007DF938 /* Array+Utils.swift */, + D0A9B728241CEFAC007DF938 /* UIImage+Utils.swift */, + D0A9B729241CEFAC007DF938 /* ClosedRange+Utils.swift */, + D0A9B72A241CEFAC007DF938 /* CGFloat.swift */, + D0A9B72B241CEFAC007DF938 /* NumberFormatter+Utils.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + D0A9B72D241CEFAC007DF938 /* Charts Reader */ = { + isa = PBXGroup; + children = ( + D0A9B72E241CEFAC007DF938 /* ChartVisibilityItem.swift */, + D0A9B72F241CEFAC007DF938 /* ChartsDataManager.swift */, + D0A9B730241CEFAC007DF938 /* Convert.swift */, + D0A9B731241CEFAC007DF938 /* ChartsCollection.swift */, + D0A9B732241CEFAC007DF938 /* ChartsError.swift */, + ); + path = "Charts Reader"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A789E0BD23E841B600AEB34A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D0A9B760241CEFAC007DF938 /* GraphCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A789E0C123E841B600AEB34A /* GraphCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = A789E0CA23E841B600AEB34A /* Build configuration list for PBXNativeTarget "GraphCore" */; + buildPhases = ( + A789E0BD23E841B600AEB34A /* Headers */, + A789E0BE23E841B600AEB34A /* Sources */, + A789E0BF23E841B600AEB34A /* Frameworks */, + A789E0C023E841B600AEB34A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GraphCore; + productName = Graph; + productReference = A789E0C223E841B600AEB34A /* GraphCore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A789E0B923E841B600AEB34A /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A789E0C123E841B600AEB34A = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A789E0BC23E841B600AEB34A /* Build configuration list for PBXProject "GraphCore" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A789E0B823E841B600AEB34A; + productRefGroup = A789E0C323E841B600AEB34A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A789E0C123E841B600AEB34A /* GraphCore */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A789E0C023E841B600AEB34A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A789E0BE23E841B600AEB34A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0A9B756241CEFAC007DF938 /* DisplayLinkService.swift in Sources */, + D0A9B759241CEFAC007DF938 /* ScalesNumberFormatter.swift in Sources */, + D0A9B744241CEFAC007DF938 /* StackedBarsChartController.swift in Sources */, + D0A9B745241CEFAC007DF938 /* LinesComponentController.swift in Sources */, + D0A9B737241CEFAC007DF938 /* PerformanceRenderer.swift in Sources */, + D0A9B75D241CEFAC007DF938 /* ClosedRange+Utils.swift in Sources */, + D0A9B736241CEFAC007DF938 /* PecentChartRenderer.swift in Sources */, + D0A9B73E241CEFAC007DF938 /* PercentPieAnimationRenderer.swift in Sources */, + D0A9B754241CEFAC007DF938 /* UIView+Extensions.swift in Sources */, + D0A9B749241CEFAC007DF938 /* GeneralChartComponentController.swift in Sources */, + D0A9B73C241CEFAC007DF938 /* PieChartRenderer.swift in Sources */, + D0A9B75C241CEFAC007DF938 /* UIImage+Utils.swift in Sources */, + D0A9B758241CEFAC007DF938 /* TimeZone.swift in Sources */, + D0A9B74D241CEFAC007DF938 /* ColorMode.swift in Sources */, + D0A9B738241CEFAC007DF938 /* VerticalLinesRenderer.swift in Sources */, + D0A9B74A241CEFAC007DF938 /* GeneralLinesChartController.swift in Sources */, + D0A9B742241CEFAC007DF938 /* BaseChartController.swift in Sources */, + D0A9B757241CEFAC007DF938 /* GlobalHelpers.swift in Sources */, + D0A9B73A241CEFAC007DF938 /* VerticalScalesRenderer.swift in Sources */, + D0A9B750241CEFAC007DF938 /* LinesChartLabel.swift in Sources */, + D0A9B75B241CEFAC007DF938 /* Array+Utils.swift in Sources */, + D0A9B75F241CEFAC007DF938 /* NumberFormatter+Utils.swift in Sources */, + D0A9B741241CEFAC007DF938 /* PercentChartComponentController.swift in Sources */, + D0A9B751241CEFAC007DF938 /* TimeInterval+Utils.swift in Sources */, + D0A9B763241CEFAC007DF938 /* Convert.swift in Sources */, + D0DEF74B242BBCE200A34A30 /* LineBulletsRenderer.swift in Sources */, + D0A9B75E241CEFAC007DF938 /* CGFloat.swift in Sources */, + D0A9B74E241CEFAC007DF938 /* ChartLineData.swift in Sources */, + D0A9B753241CEFAC007DF938 /* TextUtils.swift in Sources */, + D0A9B73B241CEFAC007DF938 /* BaseChartRenderer.swift in Sources */, + D0A9B755241CEFAC007DF938 /* UIColor+Utils.swift in Sources */, + D0A9B73F241CEFAC007DF938 /* PieChartComponentController.swift in Sources */, + D0A9B75A241CEFAC007DF938 /* CGPoint+Extensions.swift in Sources */, + D0A9B74B241CEFAC007DF938 /* TwoAxisLinesChartController.swift in Sources */, + D0A9B733241CEFAC007DF938 /* HorizontalScalesRenderer.swift in Sources */, + D0A9B762241CEFAC007DF938 /* ChartsDataManager.swift in Sources */, + D0A9B74F241CEFAC007DF938 /* LinesSelectionLabel.swift in Sources */, + D0A9B734241CEFAC007DF938 /* BarChartRenderer.swift in Sources */, + D0A9B752241CEFAC007DF938 /* AnimationController.swift in Sources */, + D0A9B735241CEFAC007DF938 /* LinesChartRenderer.swift in Sources */, + D0A9B764241CEFAC007DF938 /* ChartsCollection.swift in Sources */, + D0A9B761241CEFAC007DF938 /* ChartVisibilityItem.swift in Sources */, + D0A9B765241CEFAC007DF938 /* ChartsError.swift in Sources */, + D0A9B73D241CEFAC007DF938 /* ChartDetailsRenderer.swift in Sources */, + D0A9B740241CEFAC007DF938 /* PercentPieChartController.swift in Sources */, + D0A9B746241CEFAC007DF938 /* DailyBarsChartController.swift in Sources */, + D0A9B747241CEFAC007DF938 /* StepBarsChartController.swift in Sources */, + D0A9B743241CEFAC007DF938 /* BarsComponentController.swift in Sources */, + D0A9B74C241CEFAC007DF938 /* BaseLinesChartController.swift in Sources */, + D0DEF74C242BBDBE00A34A30 /* TwoAxisStepBarsChartController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7393CB92406859300CE44CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7393CBA2406859300CE44CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = Graph/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Graph; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + A7393CBC2406859800CE44CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7393CBD2406859800CE44CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = Graph/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Graph; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + A7393CBF240685A600CE44CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7393CC0240685A600CE44CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Graph/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Graph; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + A789E0C823E841B600AEB34A /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A789E0C923E841B600AEB34A /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A789E0CB23E841B600AEB34A /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Graph/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Graph; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + A789E0CC23E841B600AEB34A /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Graph/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Graph; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A789E0BC23E841B600AEB34A /* Build configuration list for PBXProject "GraphCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A789E0C823E841B600AEB34A /* DebugAppStore */, + A7393CBC2406859800CE44CA /* HockeyappMacAlpha */, + A7393CB92406859300CE44CA /* DebugHockeyapp */, + A789E0C923E841B600AEB34A /* ReleaseHockeyapp */, + A7393CBF240685A600CE44CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A789E0CA23E841B600AEB34A /* Build configuration list for PBXNativeTarget "GraphCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A789E0CB23E841B600AEB34A /* DebugAppStore */, + A7393CBD2406859800CE44CA /* HockeyappMacAlpha */, + A7393CBA2406859300CE44CA /* DebugHockeyapp */, + A789E0CC23E841B600AEB34A /* ReleaseHockeyapp */, + A7393CC0240685A600CE44CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A789E0B923E841B600AEB34A /* Project object */; +} diff --git a/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..8f31ea705e --- /dev/null +++ b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/GraphCore/GraphCore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit/Info.plist b/core-xprojects/MtProtoKit/MtProtoKit/Info.plist new file mode 100644 index 0000000000..fb8e650960 --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2015 Telegram. All rights reserved. + NSPrincipalClass + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit/MtProtoKit.h b/core-xprojects/MtProtoKit/MtProtoKit/MtProtoKit.h new file mode 100644 index 0000000000..2e53cf3f6f --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit/MtProtoKit.h @@ -0,0 +1,77 @@ +// +// MtProtoKit.h +// MtProtoKit +// +// Created by Peter on 01/05/15. +// Copyright (c) 2015 Telegram. All rights reserved. +// + +#import + +//! Project version number for MtProtoKit. +FOUNDATION_EXPORT double MtProtoKitVersionNumber; + +//! Project version string for MtProtoKit. +FOUNDATION_EXPORT const unsigned char MtProtoKitVersionString[]; + +#ifndef MtProtoKitFramework +# define MtProtoKitFramework 1 +#endif + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..a56e0c1b8d --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1508 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A790A319236B0F6C000451B5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A318236B0F6C000451B5 /* Foundation.framework */; }; + A790A31B236B1199000451B5 /* EncryptionProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A31A236B1199000451B5 /* EncryptionProvider.framework */; }; + A7918EE8240CF48A002011CA /* MTOutputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E6B240CF482002011CA /* MTOutputStream.m */; }; + A7918EE9240CF48A002011CA /* MTServerDhInnerDataMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E6C240CF483002011CA /* MTServerDhInnerDataMessage.m */; }; + A7918EEA240CF48A002011CA /* MTMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E6D240CF483002011CA /* MTMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EEB240CF48A002011CA /* MTDiscoverDatacenterAddressAction.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E6E240CF483002011CA /* MTDiscoverDatacenterAddressAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EEC240CF48A002011CA /* MTTimeFixContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E6F240CF483002011CA /* MTTimeFixContext.m */; }; + A7918EED240CF48A002011CA /* MTDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E70240CF483002011CA /* MTDisposable.m */; }; + A7918EEE240CF48A002011CA /* MTDropRpcResultMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E71240CF483002011CA /* MTDropRpcResultMessage.m */; }; + A7918EEF240CF48A002011CA /* PingFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E72240CF483002011CA /* PingFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EF0240CF48A002011CA /* MTGzip.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E73240CF483002011CA /* MTGzip.m */; }; + A7918EF1240CF48A002011CA /* MTPingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E74240CF483002011CA /* MTPingMessage.m */; }; + A7918EF2240CF48A002011CA /* MTMsgResendReqMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E75240CF483002011CA /* MTMsgResendReqMessage.m */; }; + A7918EF3240CF48A002011CA /* MTSetClientDhParamsResponseMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E76240CF483002011CA /* MTSetClientDhParamsResponseMessage.m */; }; + A7918EF4240CF48A002011CA /* PingFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E77240CF483002011CA /* PingFoundation.m */; }; + A7918EF5240CF48A002011CA /* MTResPqMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E78240CF483002011CA /* MTResPqMessage.m */; }; + A7918EF6240CF48A002011CA /* MTInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E79240CF483002011CA /* MTInputStream.m */; }; + A7918EF7240CF48A002011CA /* MTOutgoingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E7A240CF483002011CA /* MTOutgoingMessage.m */; }; + A7918EF8240CF48A002011CA /* MTBufferReader.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E7B240CF483002011CA /* MTBufferReader.m */; }; + A7918EF9240CF48A002011CA /* MTTransportSchemeStats.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E7C240CF483002011CA /* MTTransportSchemeStats.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EFA240CF48A002011CA /* MTTcpConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E7D240CF484002011CA /* MTTcpConnection.m */; }; + A7918EFB240CF48A002011CA /* MTTransportSchemeStats.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E7E240CF484002011CA /* MTTransportSchemeStats.m */; }; + A7918EFC240CF48A002011CA /* MTRpcError.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E7F240CF484002011CA /* MTRpcError.m */; }; + A7918EFD240CF48A002011CA /* MTSetClientDhParamsResponseMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E80240CF484002011CA /* MTSetClientDhParamsResponseMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EFE240CF48A002011CA /* MTTcpConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E81240CF484002011CA /* MTTcpConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918EFF240CF48A002011CA /* MTDatacenterTransferAuthAction.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E82240CF484002011CA /* MTDatacenterTransferAuthAction.m */; }; + A7918F00240CF48A002011CA /* MTMsgResendReqMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E83240CF484002011CA /* MTMsgResendReqMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F01240CF48A002011CA /* MTDatacenterAuthInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E84240CF484002011CA /* MTDatacenterAuthInfo.m */; }; + A7918F02240CF48A002011CA /* MTTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E85240CF484002011CA /* MTTransport.m */; }; + A7918F03240CF48A002011CA /* MTServerDhParamsMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E86240CF484002011CA /* MTServerDhParamsMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F04240CF48A002011CA /* MTPongMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E87240CF484002011CA /* MTPongMessage.m */; }; + A7918F05240CF48A002011CA /* MTDiscoverDatacenterAddressAction.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E88240CF484002011CA /* MTDiscoverDatacenterAddressAction.m */; }; + A7918F06240CF48A002011CA /* MTDropRpcResultMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E89240CF484002011CA /* MTDropRpcResultMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F07240CF48A002011CA /* MTBufferReader.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E8A240CF485002011CA /* MTBufferReader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F08240CF48A002011CA /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E8B240CF485002011CA /* GCDAsyncSocket.m */; }; + A7918F09240CF48A002011CA /* MTBag.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E8C240CF485002011CA /* MTBag.m */; }; + A7918F0A240CF48A002011CA /* MTRpcResultMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E8D240CF485002011CA /* MTRpcResultMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F0B240CF48A002011CA /* MTTime.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E8E240CF485002011CA /* MTTime.m */; }; + A7918F0C240CF48A002011CA /* MTRequestErrorContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E8F240CF485002011CA /* MTRequestErrorContext.m */; }; + A7918F0D240CF48A002011CA /* MTBackupAddressSignals.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E90240CF485002011CA /* MTBackupAddressSignals.m */; }; + A7918F0E240CF48A002011CA /* MTDiscoverConnectionSignals.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E91240CF485002011CA /* MTDiscoverConnectionSignals.m */; }; + A7918F0F240CF48A002011CA /* MTMsgAllInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E92240CF485002011CA /* MTMsgAllInfoMessage.m */; }; + A7918F10240CF48A002011CA /* MTConnectionProbing.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E93240CF485002011CA /* MTConnectionProbing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F11240CF48A002011CA /* MTPingMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E94240CF485002011CA /* MTPingMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F12240CF48A002011CA /* MTBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E95240CF485002011CA /* MTBuffer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F13240CF48A002011CA /* MTFutureSaltsMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E96240CF485002011CA /* MTFutureSaltsMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F14240CF48A002011CA /* MTTimeSyncMessageService.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E97240CF485002011CA /* MTTimeSyncMessageService.m */; }; + A7918F15240CF48A002011CA /* MTContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E98240CF485002011CA /* MTContext.m */; }; + A7918F16240CF48A002011CA /* MTSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E99240CF485002011CA /* MTSubscriber.m */; }; + A7918F17240CF48A002011CA /* MTDatacenterAuthAction.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E9A240CF485002011CA /* MTDatacenterAuthAction.m */; }; + A7918F18240CF48A002011CA /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E9B240CF486002011CA /* AFURLConnectionOperation.m */; }; + A7918F19240CF48A002011CA /* MTResPqMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E9C240CF486002011CA /* MTResPqMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F1A240CF48A002011CA /* MTEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E9D240CF486002011CA /* MTEncryption.m */; }; + A7918F1B240CF48A002011CA /* MTMsgsStateReqMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E9E240CF486002011CA /* MTMsgsStateReqMessage.m */; }; + A7918F1C240CF48A002011CA /* MTRpcResultMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E9F240CF486002011CA /* MTRpcResultMessage.m */; }; + A7918F1D240CF48A002011CA /* MTBindingTempAuthKeyContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EA0240CF486002011CA /* MTBindingTempAuthKeyContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F1E240CF48A002011CA /* MTNewSessionCreatedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA1240CF486002011CA /* MTNewSessionCreatedMessage.m */; }; + A7918F1F240CF48A002011CA /* MTMessageTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA2240CF486002011CA /* MTMessageTransaction.m */; }; + A7918F20240CF48A002011CA /* MTRsa.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA3240CF486002011CA /* MTRsa.m */; }; + A7918F21240CF48A002011CA /* MTExportedAuthorizationData.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA4240CF486002011CA /* MTExportedAuthorizationData.m */; }; + A7918F22240CF48A002011CA /* MTPongMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EA5240CF486002011CA /* MTPongMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F23240CF48A002011CA /* MTInternalMessageParser.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EA6240CF486002011CA /* MTInternalMessageParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F24240CF48A002011CA /* MTTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA7240CF486002011CA /* MTTimer.m */; }; + A7918F25240CF48A002011CA /* MTIncomingMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EA8240CF486002011CA /* MTIncomingMessage.m */; }; + A7918F26240CF48A002011CA /* MTMsgAllInfoMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EA9240CF486002011CA /* MTMsgAllInfoMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F27240CF48A002011CA /* MTHttpRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EAA240CF486002011CA /* MTHttpRequestOperation.m */; }; + A7918F28240CF48A002011CA /* MTProxyConnectivity.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EAB240CF487002011CA /* MTProxyConnectivity.m */; }; + A7918F29240CF48A002011CA /* MTTransportTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EAC240CF487002011CA /* MTTransportTransaction.m */; }; + A7918F2A240CF48A002011CA /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EAD240CF487002011CA /* AFHTTPRequestOperation.m */; }; + A7918F2B240CF48A002011CA /* MTMsgsStateReqMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EAE240CF487002011CA /* MTMsgsStateReqMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F2C240CF48A002011CA /* MTDestroySessionResponseMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EAF240CF487002011CA /* MTDestroySessionResponseMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F2D240CF48A002011CA /* MTRequestContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB0240CF487002011CA /* MTRequestContext.m */; }; + A7918F2E240CF48A002011CA /* MTBadMsgNotificationMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB1240CF487002011CA /* MTBadMsgNotificationMessage.m */; }; + A7918F2F240CF48A002011CA /* MTApiEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB2240CF487002011CA /* MTApiEnvironment.m */; }; + A7918F30240CF48A002011CA /* MTTcpConnectionBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB3240CF487002011CA /* MTTcpConnectionBehaviour.m */; }; + A7918F31240CF48A002011CA /* MTMessageEncryptionKey.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB4240CF487002011CA /* MTMessageEncryptionKey.m */; }; + A7918F32240CF48A002011CA /* MTTcpTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB5240CF487002011CA /* MTTcpTransport.m */; }; + A7918F33240CF48A002011CA /* MTDatacenterAuthMessageService.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB6240CF487002011CA /* MTDatacenterAuthMessageService.m */; }; + A7918F34240CF48A002011CA /* MTFutureSaltsMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EB7240CF487002011CA /* MTFutureSaltsMessage.m */; }; + A7918F35240CF48A002011CA /* MTMsgContainerMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EB8240CF487002011CA /* MTMsgContainerMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F36240CF48A002011CA /* MTMsgDetailedInfoMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EB9240CF487002011CA /* MTMsgDetailedInfoMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F37240CF48A002011CA /* MTServerDhInnerDataMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EBA240CF487002011CA /* MTServerDhInnerDataMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F38240CF48A002011CA /* MTInternalMessageParser.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EBB240CF487002011CA /* MTInternalMessageParser.m */; }; + A7918F39240CF48A002011CA /* MTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EBC240CF487002011CA /* MTLogging.m */; }; + A7918F3A240CF48A002011CA /* MTBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EBD240CF488002011CA /* MTBuffer.m */; }; + A7918F3B240CF48A002011CA /* MTRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EBE240CF488002011CA /* MTRequest.m */; }; + A7918F3C240CF48A002011CA /* MTRequestMessageService.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EBF240CF488002011CA /* MTRequestMessageService.m */; }; + A7918F3D240CF48A002011CA /* MTDatacenterAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC0240CF488002011CA /* MTDatacenterAddress.m */; }; + A7918F3E240CF48A002011CA /* MTBadMsgNotificationMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EC1240CF488002011CA /* MTBadMsgNotificationMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F3F240CF48A002011CA /* MTNetworkUsageCalculationInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC2240CF488002011CA /* MTNetworkUsageCalculationInfo.m */; }; + A7918F40240CF48A002011CA /* MTNetworkUsageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC3240CF488002011CA /* MTNetworkUsageManager.m */; }; + A7918F41240CF48A002011CA /* MTConnectionProbing.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC4240CF488002011CA /* MTConnectionProbing.m */; }; + A7918F42240CF48A002011CA /* MTDatacenterVerificationData.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC5240CF488002011CA /* MTDatacenterVerificationData.m */; }; + A7918F43240CF48A002011CA /* MTDestroySessionResponseMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC6240CF488002011CA /* MTDestroySessionResponseMessage.m */; }; + A7918F44240CF48A002011CA /* MTBindingTempAuthKeyContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC7240CF488002011CA /* MTBindingTempAuthKeyContext.m */; }; + A7918F45240CF48A002011CA /* MTQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC8240CF488002011CA /* MTQueue.m */; }; + A7918F46240CF48A002011CA /* MTServerDhParamsMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EC9240CF488002011CA /* MTServerDhParamsMessage.m */; }; + A7918F47240CF48A002011CA /* MTMsgsStateInfoMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918ECA240CF488002011CA /* MTMsgsStateInfoMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F48240CF48A002011CA /* MTMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ECB240CF488002011CA /* MTMessage.m */; }; + A7918F49240CF48A002011CA /* MTDatacenterAddressSet.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ECC240CF488002011CA /* MTDatacenterAddressSet.m */; }; + A7918F4A240CF48A002011CA /* MTMsgsStateInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ECD240CF488002011CA /* MTMsgsStateInfoMessage.m */; }; + A7918F4B240CF48A002011CA /* MTSessionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ECE240CF489002011CA /* MTSessionInfo.m */; }; + A7918F4C240CF48A002011CA /* MTDNS.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ECF240CF489002011CA /* MTDNS.m */; }; + A7918F4D240CF48A002011CA /* MTMsgsAckMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED0240CF489002011CA /* MTMsgsAckMessage.m */; }; + A7918F4E240CF48A002011CA /* MTTransportScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED1240CF489002011CA /* MTTransportScheme.m */; }; + A7918F4F240CF48A002011CA /* MTNetworkAvailability.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED2240CF489002011CA /* MTNetworkAvailability.m */; }; + A7918F50240CF48A002011CA /* MTDropResponseContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED3240CF489002011CA /* MTDropResponseContext.m */; }; + A7918F51240CF48A002011CA /* MTAes.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918ED4240CF489002011CA /* MTAes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F52240CF48A002011CA /* MTKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED5240CF489002011CA /* MTKeychain.m */; }; + A7918F53240CF48A002011CA /* MTDatacenterSaltInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED6240CF489002011CA /* MTDatacenterSaltInfo.m */; }; + A7918F54240CF48A002011CA /* MTDNS.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918ED7240CF489002011CA /* MTDNS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F55240CF48A002011CA /* MTMsgContainerMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED8240CF489002011CA /* MTMsgContainerMessage.m */; }; + A7918F56240CF48A002011CA /* MTAtomic.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918ED9240CF489002011CA /* MTAtomic.m */; }; + A7918F57240CF48A002011CA /* MTDiscoverConnectionSignals.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EDA240CF489002011CA /* MTDiscoverConnectionSignals.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F58240CF48A002011CA /* MTNewSessionCreatedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EDB240CF489002011CA /* MTNewSessionCreatedMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F59240CF48A002011CA /* MTResendMessageService.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EDC240CF489002011CA /* MTResendMessageService.m */; }; + A7918F5A240CF48A002011CA /* MTMsgDetailedInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EDD240CF489002011CA /* MTMsgDetailedInfoMessage.m */; }; + A7918F5B240CF48A002011CA /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EDE240CF489002011CA /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F5C240CF48A002011CA /* MTFileBasedKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EDF240CF48A002011CA /* MTFileBasedKeychain.m */; }; + A7918F5D240CF48A002011CA /* MTAes.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EE0240CF48A002011CA /* MTAes.m */; }; + A7918F5E240CF48A002011CA /* MTTcpConnectionBehaviour.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EE1240CF48A002011CA /* MTTcpConnectionBehaviour.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F5F240CF48A002011CA /* MTSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EE2240CF48A002011CA /* MTSignal.m */; }; + A7918F60240CF48A002011CA /* MTRsa.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EE3240CF48A002011CA /* MTRsa.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F61240CF48A002011CA /* MTPreparedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EE4240CF48A002011CA /* MTPreparedMessage.m */; }; + A7918F62240CF48A002011CA /* MTDatacenterAddressListData.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EE5240CF48A002011CA /* MTDatacenterAddressListData.m */; }; + A7918F63240CF48A002011CA /* MTMsgsAckMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918EE6240CF48A002011CA /* MTMsgsAckMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918F64240CF48A002011CA /* MTProto.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918EE7240CF48A002011CA /* MTProto.m */; }; + A7918FA1240CF4AE002011CA /* MTDatacenterAuthInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F66240CF4AB002011CA /* MTDatacenterAuthInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA2240CF4AE002011CA /* MTSessionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F67240CF4AB002011CA /* MTSessionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA3240CF4AE002011CA /* MTMessageService.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F68240CF4AB002011CA /* MTMessageService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA4240CF4AE002011CA /* MTLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F69240CF4AB002011CA /* MTLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA5240CF4AE002011CA /* MTDatacenterAddressSet.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6A240CF4AB002011CA /* MTDatacenterAddressSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA6240CF4AE002011CA /* MTHttpRequestOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6B240CF4AB002011CA /* MTHttpRequestOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA7240CF4AE002011CA /* MTDatacenterAuthAction.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6C240CF4AB002011CA /* MTDatacenterAuthAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA8240CF4AE002011CA /* MTNetworkUsageCalculationInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6D240CF4AB002011CA /* MTNetworkUsageCalculationInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FA9240CF4AE002011CA /* MTSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6E240CF4AB002011CA /* MTSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAA240CF4AE002011CA /* MTTimeSyncMessageService.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F6F240CF4AB002011CA /* MTTimeSyncMessageService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAB240CF4AE002011CA /* MTDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F70240CF4AB002011CA /* MTDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAC240CF4AE002011CA /* MTBackupAddressSignals.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F71240CF4AB002011CA /* MTBackupAddressSignals.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAD240CF4AE002011CA /* MTEncryption.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F72240CF4AB002011CA /* MTEncryption.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAE240CF4AE002011CA /* MTTransportScheme.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F73240CF4AB002011CA /* MTTransportScheme.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FAF240CF4AE002011CA /* MTAtomic.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F74240CF4AB002011CA /* MTAtomic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB0240CF4AE002011CA /* MTNetworkUsageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F75240CF4AB002011CA /* MTNetworkUsageManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB1240CF4AE002011CA /* MTRequestMessageService.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F76240CF4AC002011CA /* MTRequestMessageService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB2240CF4AE002011CA /* MTIncomingMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F77240CF4AC002011CA /* MTIncomingMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB3240CF4AE002011CA /* MTSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F78240CF4AC002011CA /* MTSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB4240CF4AE002011CA /* MTRequestContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F79240CF4AC002011CA /* MTRequestContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB5240CF4AE002011CA /* MTDatacenterAddressListData.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7A240CF4AC002011CA /* MTDatacenterAddressListData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB6240CF4AE002011CA /* MTApiEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7B240CF4AC002011CA /* MTApiEnvironment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB7240CF4AE002011CA /* MTKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7C240CF4AC002011CA /* MTKeychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB8240CF4AE002011CA /* MTDatacenterAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7D240CF4AC002011CA /* MTDatacenterAddress.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FB9240CF4AE002011CA /* AFHTTPRequestOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7E240CF4AC002011CA /* AFHTTPRequestOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBA240CF4AF002011CA /* MTSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F7F240CF4AC002011CA /* MTSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBB240CF4AF002011CA /* MTProto.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F80240CF4AC002011CA /* MTProto.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBC240CF4AF002011CA /* MTTcpTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F81240CF4AC002011CA /* MTTcpTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBD240CF4AF002011CA /* MTDatacenterTransferAuthAction.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F82240CF4AC002011CA /* MTDatacenterTransferAuthAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBE240CF4AF002011CA /* MTTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F83240CF4AC002011CA /* MTTransport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FBF240CF4AF002011CA /* MTFileBasedKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F84240CF4AD002011CA /* MTFileBasedKeychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC0240CF4AF002011CA /* MTContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F85240CF4AD002011CA /* MTContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC1240CF4AF002011CA /* MTBag.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F86240CF4AD002011CA /* MTBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC2240CF4AF002011CA /* MTTransportTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F87240CF4AD002011CA /* MTTransportTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC3240CF4AF002011CA /* MTTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F88240CF4AD002011CA /* MTTimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC4240CF4AF002011CA /* MTMessageEncryptionKey.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F89240CF4AD002011CA /* MTMessageEncryptionKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC5240CF4AF002011CA /* MTInternalId.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8A240CF4AD002011CA /* MTInternalId.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC6240CF4AF002011CA /* MTProxyConnectivity.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8B240CF4AD002011CA /* MTProxyConnectivity.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC7240CF4AF002011CA /* MTQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8C240CF4AD002011CA /* MTQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC8240CF4AF002011CA /* AFURLConnectionOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8D240CF4AD002011CA /* AFURLConnectionOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FC9240CF4AF002011CA /* MTRequestErrorContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8E240CF4AD002011CA /* MTRequestErrorContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCA240CF4AF002011CA /* MTDatacenterSaltInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F8F240CF4AD002011CA /* MTDatacenterSaltInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCB240CF4AF002011CA /* MTOutgoingMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F90240CF4AD002011CA /* MTOutgoingMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCC240CF4AF002011CA /* MTTimeFixContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F91240CF4AE002011CA /* MTTimeFixContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCD240CF4AF002011CA /* MTOutputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F92240CF4AE002011CA /* MTOutputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCE240CF4AF002011CA /* MTMessageTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F93240CF4AE002011CA /* MTMessageTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FCF240CF4AF002011CA /* MTGzip.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F94240CF4AE002011CA /* MTGzip.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD1240CF4AF002011CA /* MTRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F96240CF4AE002011CA /* MTRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD2240CF4AF002011CA /* MTTime.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F97240CF4AE002011CA /* MTTime.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD3240CF4AF002011CA /* MTPreparedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F98240CF4AE002011CA /* MTPreparedMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD4240CF4AF002011CA /* MTNetworkAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F99240CF4AE002011CA /* MTNetworkAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD5240CF4AF002011CA /* MTRpcError.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9A240CF4AE002011CA /* MTRpcError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD6240CF4AF002011CA /* MTResendMessageService.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9B240CF4AE002011CA /* MTResendMessageService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD7240CF4AF002011CA /* MTDropResponseContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9C240CF4AE002011CA /* MTDropResponseContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD8240CF4AF002011CA /* MTDatacenterAuthMessageService.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9D240CF4AE002011CA /* MTDatacenterAuthMessageService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FD9240CF4AF002011CA /* MTDatacenterVerificationData.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9E240CF4AE002011CA /* MTDatacenterVerificationData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FDA240CF4AF002011CA /* MTExportedAuthorizationData.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918F9F240CF4AE002011CA /* MTExportedAuthorizationData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918FDB240CF4AF002011CA /* MTInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918FA0240CF4AE002011CA /* MTInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D038789E2332500A00DB441C /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D038789D2332500A00DB441C /* libc++.tbd */; }; + D03878A02332503300DB441C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D038789F2332503300DB441C /* Security.framework */; }; + D03878A22332503E00DB441C /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03878A12332503E00DB441C /* SystemConfiguration.framework */; }; + D03878A42332504600DB441C /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03878A32332504600DB441C /* CFNetwork.framework */; }; + D079AB9C1AF39B8000076F59 /* MtProtoKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D079AB9B1AF39B8000076F59 /* MtProtoKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0B4187B1D7E04CF004562A4 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B4187A1D7E04CF004562A4 /* libz.tbd */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A790A318236B0F6C000451B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + A790A31A236B1199000451B5 /* EncryptionProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = EncryptionProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918DD6240CEF8A002011CA /* MurMurHash32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MurMurHash32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918E6B240CF482002011CA /* MTOutputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTOutputStream.m; path = ../Sources/MTOutputStream.m; sourceTree = ""; }; + A7918E6C240CF483002011CA /* MTServerDhInnerDataMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTServerDhInnerDataMessage.m; path = ../Sources/MTServerDhInnerDataMessage.m; sourceTree = ""; }; + A7918E6D240CF483002011CA /* MTMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMessage.h; path = ../Sources/MTMessage.h; sourceTree = ""; }; + A7918E6E240CF483002011CA /* MTDiscoverDatacenterAddressAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDiscoverDatacenterAddressAction.h; path = ../Sources/MTDiscoverDatacenterAddressAction.h; sourceTree = ""; }; + A7918E6F240CF483002011CA /* MTTimeFixContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTimeFixContext.m; path = ../Sources/MTTimeFixContext.m; sourceTree = ""; }; + A7918E70240CF483002011CA /* MTDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDisposable.m; path = ../Sources/MTDisposable.m; sourceTree = ""; }; + A7918E71240CF483002011CA /* MTDropRpcResultMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDropRpcResultMessage.m; path = ../Sources/MTDropRpcResultMessage.m; sourceTree = ""; }; + A7918E72240CF483002011CA /* PingFoundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PingFoundation.h; path = ../Sources/PingFoundation.h; sourceTree = ""; }; + A7918E73240CF483002011CA /* MTGzip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTGzip.m; path = ../Sources/MTGzip.m; sourceTree = ""; }; + A7918E74240CF483002011CA /* MTPingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTPingMessage.m; path = ../Sources/MTPingMessage.m; sourceTree = ""; }; + A7918E75240CF483002011CA /* MTMsgResendReqMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgResendReqMessage.m; path = ../Sources/MTMsgResendReqMessage.m; sourceTree = ""; }; + A7918E76240CF483002011CA /* MTSetClientDhParamsResponseMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTSetClientDhParamsResponseMessage.m; path = ../Sources/MTSetClientDhParamsResponseMessage.m; sourceTree = ""; }; + A7918E77240CF483002011CA /* PingFoundation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PingFoundation.m; path = ../Sources/PingFoundation.m; sourceTree = ""; }; + A7918E78240CF483002011CA /* MTResPqMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTResPqMessage.m; path = ../Sources/MTResPqMessage.m; sourceTree = ""; }; + A7918E79240CF483002011CA /* MTInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTInputStream.m; path = ../Sources/MTInputStream.m; sourceTree = ""; }; + A7918E7A240CF483002011CA /* MTOutgoingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTOutgoingMessage.m; path = ../Sources/MTOutgoingMessage.m; sourceTree = ""; }; + A7918E7B240CF483002011CA /* MTBufferReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBufferReader.m; path = ../Sources/MTBufferReader.m; sourceTree = ""; }; + A7918E7C240CF483002011CA /* MTTransportSchemeStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTransportSchemeStats.h; path = ../Sources/MTTransportSchemeStats.h; sourceTree = ""; }; + A7918E7D240CF484002011CA /* MTTcpConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTcpConnection.m; path = ../Sources/MTTcpConnection.m; sourceTree = ""; }; + A7918E7E240CF484002011CA /* MTTransportSchemeStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTransportSchemeStats.m; path = ../Sources/MTTransportSchemeStats.m; sourceTree = ""; }; + A7918E7F240CF484002011CA /* MTRpcError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRpcError.m; path = ../Sources/MTRpcError.m; sourceTree = ""; }; + A7918E80240CF484002011CA /* MTSetClientDhParamsResponseMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTSetClientDhParamsResponseMessage.h; path = ../Sources/MTSetClientDhParamsResponseMessage.h; sourceTree = ""; }; + A7918E81240CF484002011CA /* MTTcpConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTcpConnection.h; path = ../Sources/MTTcpConnection.h; sourceTree = ""; }; + A7918E82240CF484002011CA /* MTDatacenterTransferAuthAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterTransferAuthAction.m; path = ../Sources/MTDatacenterTransferAuthAction.m; sourceTree = ""; }; + A7918E83240CF484002011CA /* MTMsgResendReqMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgResendReqMessage.h; path = ../Sources/MTMsgResendReqMessage.h; sourceTree = ""; }; + A7918E84240CF484002011CA /* MTDatacenterAuthInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAuthInfo.m; path = ../Sources/MTDatacenterAuthInfo.m; sourceTree = ""; }; + A7918E85240CF484002011CA /* MTTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTransport.m; path = ../Sources/MTTransport.m; sourceTree = ""; }; + A7918E86240CF484002011CA /* MTServerDhParamsMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTServerDhParamsMessage.h; path = ../Sources/MTServerDhParamsMessage.h; sourceTree = ""; }; + A7918E87240CF484002011CA /* MTPongMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTPongMessage.m; path = ../Sources/MTPongMessage.m; sourceTree = ""; }; + A7918E88240CF484002011CA /* MTDiscoverDatacenterAddressAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDiscoverDatacenterAddressAction.m; path = ../Sources/MTDiscoverDatacenterAddressAction.m; sourceTree = ""; }; + A7918E89240CF484002011CA /* MTDropRpcResultMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDropRpcResultMessage.h; path = ../Sources/MTDropRpcResultMessage.h; sourceTree = ""; }; + A7918E8A240CF485002011CA /* MTBufferReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBufferReader.h; path = ../Sources/MTBufferReader.h; sourceTree = ""; }; + A7918E8B240CF485002011CA /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = ../Sources/GCDAsyncSocket.m; sourceTree = ""; }; + A7918E8C240CF485002011CA /* MTBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBag.m; path = ../Sources/MTBag.m; sourceTree = ""; }; + A7918E8D240CF485002011CA /* MTRpcResultMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRpcResultMessage.h; path = ../Sources/MTRpcResultMessage.h; sourceTree = ""; }; + A7918E8E240CF485002011CA /* MTTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTime.m; path = ../Sources/MTTime.m; sourceTree = ""; }; + A7918E8F240CF485002011CA /* MTRequestErrorContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRequestErrorContext.m; path = ../Sources/MTRequestErrorContext.m; sourceTree = ""; }; + A7918E90240CF485002011CA /* MTBackupAddressSignals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBackupAddressSignals.m; path = ../Sources/MTBackupAddressSignals.m; sourceTree = ""; }; + A7918E91240CF485002011CA /* MTDiscoverConnectionSignals.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDiscoverConnectionSignals.m; path = ../Sources/MTDiscoverConnectionSignals.m; sourceTree = ""; }; + A7918E92240CF485002011CA /* MTMsgAllInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgAllInfoMessage.m; path = ../Sources/MTMsgAllInfoMessage.m; sourceTree = ""; }; + A7918E93240CF485002011CA /* MTConnectionProbing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTConnectionProbing.h; path = ../Sources/MTConnectionProbing.h; sourceTree = ""; }; + A7918E94240CF485002011CA /* MTPingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTPingMessage.h; path = ../Sources/MTPingMessage.h; sourceTree = ""; }; + A7918E95240CF485002011CA /* MTBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBuffer.h; path = ../Sources/MTBuffer.h; sourceTree = ""; }; + A7918E96240CF485002011CA /* MTFutureSaltsMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTFutureSaltsMessage.h; path = ../Sources/MTFutureSaltsMessage.h; sourceTree = ""; }; + A7918E97240CF485002011CA /* MTTimeSyncMessageService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTimeSyncMessageService.m; path = ../Sources/MTTimeSyncMessageService.m; sourceTree = ""; }; + A7918E98240CF485002011CA /* MTContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTContext.m; path = ../Sources/MTContext.m; sourceTree = ""; }; + A7918E99240CF485002011CA /* MTSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTSubscriber.m; path = ../Sources/MTSubscriber.m; sourceTree = ""; }; + A7918E9A240CF485002011CA /* MTDatacenterAuthAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAuthAction.m; path = ../Sources/MTDatacenterAuthAction.m; sourceTree = ""; }; + A7918E9B240CF486002011CA /* AFURLConnectionOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFURLConnectionOperation.m; path = ../Sources/AFURLConnectionOperation.m; sourceTree = ""; }; + A7918E9C240CF486002011CA /* MTResPqMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTResPqMessage.h; path = ../Sources/MTResPqMessage.h; sourceTree = ""; }; + A7918E9D240CF486002011CA /* MTEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTEncryption.m; path = ../Sources/MTEncryption.m; sourceTree = ""; }; + A7918E9E240CF486002011CA /* MTMsgsStateReqMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgsStateReqMessage.m; path = ../Sources/MTMsgsStateReqMessage.m; sourceTree = ""; }; + A7918E9F240CF486002011CA /* MTRpcResultMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRpcResultMessage.m; path = ../Sources/MTRpcResultMessage.m; sourceTree = ""; }; + A7918EA0240CF486002011CA /* MTBindingTempAuthKeyContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBindingTempAuthKeyContext.h; path = ../Sources/MTBindingTempAuthKeyContext.h; sourceTree = ""; }; + A7918EA1240CF486002011CA /* MTNewSessionCreatedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTNewSessionCreatedMessage.m; path = ../Sources/MTNewSessionCreatedMessage.m; sourceTree = ""; }; + A7918EA2240CF486002011CA /* MTMessageTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMessageTransaction.m; path = ../Sources/MTMessageTransaction.m; sourceTree = ""; }; + A7918EA3240CF486002011CA /* MTRsa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRsa.m; path = ../Sources/MTRsa.m; sourceTree = ""; }; + A7918EA4240CF486002011CA /* MTExportedAuthorizationData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTExportedAuthorizationData.m; path = ../Sources/MTExportedAuthorizationData.m; sourceTree = ""; }; + A7918EA5240CF486002011CA /* MTPongMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTPongMessage.h; path = ../Sources/MTPongMessage.h; sourceTree = ""; }; + A7918EA6240CF486002011CA /* MTInternalMessageParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTInternalMessageParser.h; path = ../Sources/MTInternalMessageParser.h; sourceTree = ""; }; + A7918EA7240CF486002011CA /* MTTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTimer.m; path = ../Sources/MTTimer.m; sourceTree = ""; }; + A7918EA8240CF486002011CA /* MTIncomingMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTIncomingMessage.m; path = ../Sources/MTIncomingMessage.m; sourceTree = ""; }; + A7918EA9240CF486002011CA /* MTMsgAllInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgAllInfoMessage.h; path = ../Sources/MTMsgAllInfoMessage.h; sourceTree = ""; }; + A7918EAA240CF486002011CA /* MTHttpRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTHttpRequestOperation.m; path = ../Sources/MTHttpRequestOperation.m; sourceTree = ""; }; + A7918EAB240CF487002011CA /* MTProxyConnectivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTProxyConnectivity.m; path = ../Sources/MTProxyConnectivity.m; sourceTree = ""; }; + A7918EAC240CF487002011CA /* MTTransportTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTransportTransaction.m; path = ../Sources/MTTransportTransaction.m; sourceTree = ""; }; + A7918EAD240CF487002011CA /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFHTTPRequestOperation.m; path = ../Sources/AFHTTPRequestOperation.m; sourceTree = ""; }; + A7918EAE240CF487002011CA /* MTMsgsStateReqMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgsStateReqMessage.h; path = ../Sources/MTMsgsStateReqMessage.h; sourceTree = ""; }; + A7918EAF240CF487002011CA /* MTDestroySessionResponseMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDestroySessionResponseMessage.h; path = ../Sources/MTDestroySessionResponseMessage.h; sourceTree = ""; }; + A7918EB0240CF487002011CA /* MTRequestContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRequestContext.m; path = ../Sources/MTRequestContext.m; sourceTree = ""; }; + A7918EB1240CF487002011CA /* MTBadMsgNotificationMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBadMsgNotificationMessage.m; path = ../Sources/MTBadMsgNotificationMessage.m; sourceTree = ""; }; + A7918EB2240CF487002011CA /* MTApiEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTApiEnvironment.m; path = ../Sources/MTApiEnvironment.m; sourceTree = ""; }; + A7918EB3240CF487002011CA /* MTTcpConnectionBehaviour.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTcpConnectionBehaviour.m; path = ../Sources/MTTcpConnectionBehaviour.m; sourceTree = ""; }; + A7918EB4240CF487002011CA /* MTMessageEncryptionKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMessageEncryptionKey.m; path = ../Sources/MTMessageEncryptionKey.m; sourceTree = ""; }; + A7918EB5240CF487002011CA /* MTTcpTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTcpTransport.m; path = ../Sources/MTTcpTransport.m; sourceTree = ""; }; + A7918EB6240CF487002011CA /* MTDatacenterAuthMessageService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAuthMessageService.m; path = ../Sources/MTDatacenterAuthMessageService.m; sourceTree = ""; }; + A7918EB7240CF487002011CA /* MTFutureSaltsMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTFutureSaltsMessage.m; path = ../Sources/MTFutureSaltsMessage.m; sourceTree = ""; }; + A7918EB8240CF487002011CA /* MTMsgContainerMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgContainerMessage.h; path = ../Sources/MTMsgContainerMessage.h; sourceTree = ""; }; + A7918EB9240CF487002011CA /* MTMsgDetailedInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgDetailedInfoMessage.h; path = ../Sources/MTMsgDetailedInfoMessage.h; sourceTree = ""; }; + A7918EBA240CF487002011CA /* MTServerDhInnerDataMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTServerDhInnerDataMessage.h; path = ../Sources/MTServerDhInnerDataMessage.h; sourceTree = ""; }; + A7918EBB240CF487002011CA /* MTInternalMessageParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTInternalMessageParser.m; path = ../Sources/MTInternalMessageParser.m; sourceTree = ""; }; + A7918EBC240CF487002011CA /* MTLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTLogging.m; path = ../Sources/MTLogging.m; sourceTree = ""; }; + A7918EBD240CF488002011CA /* MTBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBuffer.m; path = ../Sources/MTBuffer.m; sourceTree = ""; }; + A7918EBE240CF488002011CA /* MTRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRequest.m; path = ../Sources/MTRequest.m; sourceTree = ""; }; + A7918EBF240CF488002011CA /* MTRequestMessageService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTRequestMessageService.m; path = ../Sources/MTRequestMessageService.m; sourceTree = ""; }; + A7918EC0240CF488002011CA /* MTDatacenterAddress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAddress.m; path = ../Sources/MTDatacenterAddress.m; sourceTree = ""; }; + A7918EC1240CF488002011CA /* MTBadMsgNotificationMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBadMsgNotificationMessage.h; path = ../Sources/MTBadMsgNotificationMessage.h; sourceTree = ""; }; + A7918EC2240CF488002011CA /* MTNetworkUsageCalculationInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTNetworkUsageCalculationInfo.m; path = ../Sources/MTNetworkUsageCalculationInfo.m; sourceTree = ""; }; + A7918EC3240CF488002011CA /* MTNetworkUsageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTNetworkUsageManager.m; path = ../Sources/MTNetworkUsageManager.m; sourceTree = ""; }; + A7918EC4240CF488002011CA /* MTConnectionProbing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTConnectionProbing.m; path = ../Sources/MTConnectionProbing.m; sourceTree = ""; }; + A7918EC5240CF488002011CA /* MTDatacenterVerificationData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterVerificationData.m; path = ../Sources/MTDatacenterVerificationData.m; sourceTree = ""; }; + A7918EC6240CF488002011CA /* MTDestroySessionResponseMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDestroySessionResponseMessage.m; path = ../Sources/MTDestroySessionResponseMessage.m; sourceTree = ""; }; + A7918EC7240CF488002011CA /* MTBindingTempAuthKeyContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTBindingTempAuthKeyContext.m; path = ../Sources/MTBindingTempAuthKeyContext.m; sourceTree = ""; }; + A7918EC8240CF488002011CA /* MTQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTQueue.m; path = ../Sources/MTQueue.m; sourceTree = ""; }; + A7918EC9240CF488002011CA /* MTServerDhParamsMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTServerDhParamsMessage.m; path = ../Sources/MTServerDhParamsMessage.m; sourceTree = ""; }; + A7918ECA240CF488002011CA /* MTMsgsStateInfoMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgsStateInfoMessage.h; path = ../Sources/MTMsgsStateInfoMessage.h; sourceTree = ""; }; + A7918ECB240CF488002011CA /* MTMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMessage.m; path = ../Sources/MTMessage.m; sourceTree = ""; }; + A7918ECC240CF488002011CA /* MTDatacenterAddressSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAddressSet.m; path = ../Sources/MTDatacenterAddressSet.m; sourceTree = ""; }; + A7918ECD240CF488002011CA /* MTMsgsStateInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgsStateInfoMessage.m; path = ../Sources/MTMsgsStateInfoMessage.m; sourceTree = ""; }; + A7918ECE240CF489002011CA /* MTSessionInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTSessionInfo.m; path = ../Sources/MTSessionInfo.m; sourceTree = ""; }; + A7918ECF240CF489002011CA /* MTDNS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDNS.m; path = ../Sources/MTDNS.m; sourceTree = ""; }; + A7918ED0240CF489002011CA /* MTMsgsAckMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgsAckMessage.m; path = ../Sources/MTMsgsAckMessage.m; sourceTree = ""; }; + A7918ED1240CF489002011CA /* MTTransportScheme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTTransportScheme.m; path = ../Sources/MTTransportScheme.m; sourceTree = ""; }; + A7918ED2240CF489002011CA /* MTNetworkAvailability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTNetworkAvailability.m; path = ../Sources/MTNetworkAvailability.m; sourceTree = ""; }; + A7918ED3240CF489002011CA /* MTDropResponseContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDropResponseContext.m; path = ../Sources/MTDropResponseContext.m; sourceTree = ""; }; + A7918ED4240CF489002011CA /* MTAes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTAes.h; path = ../Sources/MTAes.h; sourceTree = ""; }; + A7918ED5240CF489002011CA /* MTKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTKeychain.m; path = ../Sources/MTKeychain.m; sourceTree = ""; }; + A7918ED6240CF489002011CA /* MTDatacenterSaltInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterSaltInfo.m; path = ../Sources/MTDatacenterSaltInfo.m; sourceTree = ""; }; + A7918ED7240CF489002011CA /* MTDNS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDNS.h; path = ../Sources/MTDNS.h; sourceTree = ""; }; + A7918ED8240CF489002011CA /* MTMsgContainerMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgContainerMessage.m; path = ../Sources/MTMsgContainerMessage.m; sourceTree = ""; }; + A7918ED9240CF489002011CA /* MTAtomic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTAtomic.m; path = ../Sources/MTAtomic.m; sourceTree = ""; }; + A7918EDA240CF489002011CA /* MTDiscoverConnectionSignals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDiscoverConnectionSignals.h; path = ../Sources/MTDiscoverConnectionSignals.h; sourceTree = ""; }; + A7918EDB240CF489002011CA /* MTNewSessionCreatedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTNewSessionCreatedMessage.h; path = ../Sources/MTNewSessionCreatedMessage.h; sourceTree = ""; }; + A7918EDC240CF489002011CA /* MTResendMessageService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTResendMessageService.m; path = ../Sources/MTResendMessageService.m; sourceTree = ""; }; + A7918EDD240CF489002011CA /* MTMsgDetailedInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTMsgDetailedInfoMessage.m; path = ../Sources/MTMsgDetailedInfoMessage.m; sourceTree = ""; }; + A7918EDE240CF489002011CA /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = ../Sources/GCDAsyncSocket.h; sourceTree = ""; }; + A7918EDF240CF48A002011CA /* MTFileBasedKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTFileBasedKeychain.m; path = ../Sources/MTFileBasedKeychain.m; sourceTree = ""; }; + A7918EE0240CF48A002011CA /* MTAes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTAes.m; path = ../Sources/MTAes.m; sourceTree = ""; }; + A7918EE1240CF48A002011CA /* MTTcpConnectionBehaviour.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTcpConnectionBehaviour.h; path = ../Sources/MTTcpConnectionBehaviour.h; sourceTree = ""; }; + A7918EE2240CF48A002011CA /* MTSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTSignal.m; path = ../Sources/MTSignal.m; sourceTree = ""; }; + A7918EE3240CF48A002011CA /* MTRsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRsa.h; path = ../Sources/MTRsa.h; sourceTree = ""; }; + A7918EE4240CF48A002011CA /* MTPreparedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTPreparedMessage.m; path = ../Sources/MTPreparedMessage.m; sourceTree = ""; }; + A7918EE5240CF48A002011CA /* MTDatacenterAddressListData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTDatacenterAddressListData.m; path = ../Sources/MTDatacenterAddressListData.m; sourceTree = ""; }; + A7918EE6240CF48A002011CA /* MTMsgsAckMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMsgsAckMessage.h; path = ../Sources/MTMsgsAckMessage.h; sourceTree = ""; }; + A7918EE7240CF48A002011CA /* MTProto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTProto.m; path = ../Sources/MTProto.m; sourceTree = ""; }; + A7918F66240CF4AB002011CA /* MTDatacenterAuthInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAuthInfo.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h; sourceTree = ""; }; + A7918F67240CF4AB002011CA /* MTSessionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTSessionInfo.h; path = ../PublicHeaders/MtProtoKit/MTSessionInfo.h; sourceTree = ""; }; + A7918F68240CF4AB002011CA /* MTMessageService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMessageService.h; path = ../PublicHeaders/MtProtoKit/MTMessageService.h; sourceTree = ""; }; + A7918F69240CF4AB002011CA /* MTLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLogging.h; path = ../PublicHeaders/MtProtoKit/MTLogging.h; sourceTree = ""; }; + A7918F6A240CF4AB002011CA /* MTDatacenterAddressSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAddressSet.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAddressSet.h; sourceTree = ""; }; + A7918F6B240CF4AB002011CA /* MTHttpRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTHttpRequestOperation.h; path = ../PublicHeaders/MtProtoKit/MTHttpRequestOperation.h; sourceTree = ""; }; + A7918F6C240CF4AB002011CA /* MTDatacenterAuthAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAuthAction.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h; sourceTree = ""; }; + A7918F6D240CF4AB002011CA /* MTNetworkUsageCalculationInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTNetworkUsageCalculationInfo.h; path = ../PublicHeaders/MtProtoKit/MTNetworkUsageCalculationInfo.h; sourceTree = ""; }; + A7918F6E240CF4AB002011CA /* MTSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTSubscriber.h; path = ../PublicHeaders/MtProtoKit/MTSubscriber.h; sourceTree = ""; }; + A7918F6F240CF4AB002011CA /* MTTimeSyncMessageService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTimeSyncMessageService.h; path = ../PublicHeaders/MtProtoKit/MTTimeSyncMessageService.h; sourceTree = ""; }; + A7918F70240CF4AB002011CA /* MTDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDisposable.h; path = ../PublicHeaders/MtProtoKit/MTDisposable.h; sourceTree = ""; }; + A7918F71240CF4AB002011CA /* MTBackupAddressSignals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBackupAddressSignals.h; path = ../PublicHeaders/MtProtoKit/MTBackupAddressSignals.h; sourceTree = ""; }; + A7918F72240CF4AB002011CA /* MTEncryption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTEncryption.h; path = ../PublicHeaders/MtProtoKit/MTEncryption.h; sourceTree = ""; }; + A7918F73240CF4AB002011CA /* MTTransportScheme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTransportScheme.h; path = ../PublicHeaders/MtProtoKit/MTTransportScheme.h; sourceTree = ""; }; + A7918F74240CF4AB002011CA /* MTAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTAtomic.h; path = ../PublicHeaders/MtProtoKit/MTAtomic.h; sourceTree = ""; }; + A7918F75240CF4AB002011CA /* MTNetworkUsageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTNetworkUsageManager.h; path = ../PublicHeaders/MtProtoKit/MTNetworkUsageManager.h; sourceTree = ""; }; + A7918F76240CF4AC002011CA /* MTRequestMessageService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRequestMessageService.h; path = ../PublicHeaders/MtProtoKit/MTRequestMessageService.h; sourceTree = ""; }; + A7918F77240CF4AC002011CA /* MTIncomingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTIncomingMessage.h; path = ../PublicHeaders/MtProtoKit/MTIncomingMessage.h; sourceTree = ""; }; + A7918F78240CF4AC002011CA /* MTSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTSerialization.h; path = ../PublicHeaders/MtProtoKit/MTSerialization.h; sourceTree = ""; }; + A7918F79240CF4AC002011CA /* MTRequestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRequestContext.h; path = ../PublicHeaders/MtProtoKit/MTRequestContext.h; sourceTree = ""; }; + A7918F7A240CF4AC002011CA /* MTDatacenterAddressListData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAddressListData.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAddressListData.h; sourceTree = ""; }; + A7918F7B240CF4AC002011CA /* MTApiEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTApiEnvironment.h; path = ../PublicHeaders/MtProtoKit/MTApiEnvironment.h; sourceTree = ""; }; + A7918F7C240CF4AC002011CA /* MTKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTKeychain.h; path = ../PublicHeaders/MtProtoKit/MTKeychain.h; sourceTree = ""; }; + A7918F7D240CF4AC002011CA /* MTDatacenterAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAddress.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAddress.h; sourceTree = ""; }; + A7918F7E240CF4AC002011CA /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFHTTPRequestOperation.h; path = ../PublicHeaders/MtProtoKit/AFHTTPRequestOperation.h; sourceTree = ""; }; + A7918F7F240CF4AC002011CA /* MTSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTSignal.h; path = ../PublicHeaders/MtProtoKit/MTSignal.h; sourceTree = ""; }; + A7918F80240CF4AC002011CA /* MTProto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTProto.h; path = ../PublicHeaders/MtProtoKit/MTProto.h; sourceTree = ""; }; + A7918F81240CF4AC002011CA /* MTTcpTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTcpTransport.h; path = ../PublicHeaders/MtProtoKit/MTTcpTransport.h; sourceTree = ""; }; + A7918F82240CF4AC002011CA /* MTDatacenterTransferAuthAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterTransferAuthAction.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterTransferAuthAction.h; sourceTree = ""; }; + A7918F83240CF4AC002011CA /* MTTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTransport.h; path = ../PublicHeaders/MtProtoKit/MTTransport.h; sourceTree = ""; }; + A7918F84240CF4AD002011CA /* MTFileBasedKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTFileBasedKeychain.h; path = ../PublicHeaders/MtProtoKit/MTFileBasedKeychain.h; sourceTree = ""; }; + A7918F85240CF4AD002011CA /* MTContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTContext.h; path = ../PublicHeaders/MtProtoKit/MTContext.h; sourceTree = ""; }; + A7918F86240CF4AD002011CA /* MTBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTBag.h; path = ../PublicHeaders/MtProtoKit/MTBag.h; sourceTree = ""; }; + A7918F87240CF4AD002011CA /* MTTransportTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTransportTransaction.h; path = ../PublicHeaders/MtProtoKit/MTTransportTransaction.h; sourceTree = ""; }; + A7918F88240CF4AD002011CA /* MTTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTimer.h; path = ../PublicHeaders/MtProtoKit/MTTimer.h; sourceTree = ""; }; + A7918F89240CF4AD002011CA /* MTMessageEncryptionKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMessageEncryptionKey.h; path = ../PublicHeaders/MtProtoKit/MTMessageEncryptionKey.h; sourceTree = ""; }; + A7918F8A240CF4AD002011CA /* MTInternalId.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTInternalId.h; path = ../PublicHeaders/MtProtoKit/MTInternalId.h; sourceTree = ""; }; + A7918F8B240CF4AD002011CA /* MTProxyConnectivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTProxyConnectivity.h; path = ../PublicHeaders/MtProtoKit/MTProxyConnectivity.h; sourceTree = ""; }; + A7918F8C240CF4AD002011CA /* MTQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTQueue.h; path = ../PublicHeaders/MtProtoKit/MTQueue.h; sourceTree = ""; }; + A7918F8D240CF4AD002011CA /* AFURLConnectionOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFURLConnectionOperation.h; path = ../PublicHeaders/MtProtoKit/AFURLConnectionOperation.h; sourceTree = ""; }; + A7918F8E240CF4AD002011CA /* MTRequestErrorContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRequestErrorContext.h; path = ../PublicHeaders/MtProtoKit/MTRequestErrorContext.h; sourceTree = ""; }; + A7918F8F240CF4AD002011CA /* MTDatacenterSaltInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterSaltInfo.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterSaltInfo.h; sourceTree = ""; }; + A7918F90240CF4AD002011CA /* MTOutgoingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTOutgoingMessage.h; path = ../PublicHeaders/MtProtoKit/MTOutgoingMessage.h; sourceTree = ""; }; + A7918F91240CF4AE002011CA /* MTTimeFixContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTimeFixContext.h; path = ../PublicHeaders/MtProtoKit/MTTimeFixContext.h; sourceTree = ""; }; + A7918F92240CF4AE002011CA /* MTOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTOutputStream.h; path = ../PublicHeaders/MtProtoKit/MTOutputStream.h; sourceTree = ""; }; + A7918F93240CF4AE002011CA /* MTMessageTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTMessageTransaction.h; path = ../PublicHeaders/MtProtoKit/MTMessageTransaction.h; sourceTree = ""; }; + A7918F94240CF4AE002011CA /* MTGzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTGzip.h; path = ../PublicHeaders/MtProtoKit/MTGzip.h; sourceTree = ""; }; + A7918F96240CF4AE002011CA /* MTRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRequest.h; path = ../PublicHeaders/MtProtoKit/MTRequest.h; sourceTree = ""; }; + A7918F97240CF4AE002011CA /* MTTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTTime.h; path = ../PublicHeaders/MtProtoKit/MTTime.h; sourceTree = ""; }; + A7918F98240CF4AE002011CA /* MTPreparedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTPreparedMessage.h; path = ../PublicHeaders/MtProtoKit/MTPreparedMessage.h; sourceTree = ""; }; + A7918F99240CF4AE002011CA /* MTNetworkAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTNetworkAvailability.h; path = ../PublicHeaders/MtProtoKit/MTNetworkAvailability.h; sourceTree = ""; }; + A7918F9A240CF4AE002011CA /* MTRpcError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRpcError.h; path = ../PublicHeaders/MtProtoKit/MTRpcError.h; sourceTree = ""; }; + A7918F9B240CF4AE002011CA /* MTResendMessageService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTResendMessageService.h; path = ../PublicHeaders/MtProtoKit/MTResendMessageService.h; sourceTree = ""; }; + A7918F9C240CF4AE002011CA /* MTDropResponseContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDropResponseContext.h; path = ../PublicHeaders/MtProtoKit/MTDropResponseContext.h; sourceTree = ""; }; + A7918F9D240CF4AE002011CA /* MTDatacenterAuthMessageService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterAuthMessageService.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterAuthMessageService.h; sourceTree = ""; }; + A7918F9E240CF4AE002011CA /* MTDatacenterVerificationData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTDatacenterVerificationData.h; path = ../PublicHeaders/MtProtoKit/MTDatacenterVerificationData.h; sourceTree = ""; }; + A7918F9F240CF4AE002011CA /* MTExportedAuthorizationData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTExportedAuthorizationData.h; path = ../PublicHeaders/MtProtoKit/MTExportedAuthorizationData.h; sourceTree = ""; }; + A7918FA0240CF4AE002011CA /* MTInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTInputStream.h; path = ../PublicHeaders/MtProtoKit/MTInputStream.h; sourceTree = ""; }; + A7D281E5236C26400000A9BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D00354711C173CD0006610DA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + D00354731C173CD9006610DA /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + D038789D2332500A00DB441C /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; }; + D038789F2332503300DB441C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + D03878A12332503E00DB441C /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + D03878A32332504600DB441C /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + D05A831718AFB3F9007F1076 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D063A2F918B14AB500C65116 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/lib/libcrypto.dylib; sourceTree = DEVELOPER_DIR; }; + D079AB971AF39B8000076F59 /* MtProtoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D079AB9B1AF39B8000076F59 /* MtProtoKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MtProtoKit.h; sourceTree = ""; }; + D0B4187A1D7E04CF004562A4 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; + D0CAF2EB1D75F4520011F558 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + D0CAF2EE1D75F4E20011F558 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + D0CAF2F01D75F4EA0011F558 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + D0CD990F1D75C16100F41187 /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = openssl/iOS/libcrypto.a; sourceTree = ""; }; + D0D1A0711ADDE2FC007D9ED6 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D079AB931AF39B8000076F59 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A31B236B1199000451B5 /* EncryptionProvider.framework in Frameworks */, + A790A319236B0F6C000451B5 /* Foundation.framework in Frameworks */, + D03878A42332504600DB441C /* CFNetwork.framework in Frameworks */, + D03878A22332503E00DB441C /* SystemConfiguration.framework in Frameworks */, + D03878A02332503300DB441C /* Security.framework in Frameworks */, + D038789E2332500A00DB441C /* libc++.tbd in Frameworks */, + D0B4187B1D7E04CF004562A4 /* libz.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7918E6A240CF467002011CA /* Source */ = { + isa = PBXGroup; + children = ( + A7918EAD240CF487002011CA /* AFHTTPRequestOperation.m */, + A7918E9B240CF486002011CA /* AFURLConnectionOperation.m */, + A7918EDE240CF489002011CA /* GCDAsyncSocket.h */, + A7918E8B240CF485002011CA /* GCDAsyncSocket.m */, + A7918ED4240CF489002011CA /* MTAes.h */, + A7918EE0240CF48A002011CA /* MTAes.m */, + A7918EB2240CF487002011CA /* MTApiEnvironment.m */, + A7918ED9240CF489002011CA /* MTAtomic.m */, + A7918E90240CF485002011CA /* MTBackupAddressSignals.m */, + A7918EC1240CF488002011CA /* MTBadMsgNotificationMessage.h */, + A7918EB1240CF487002011CA /* MTBadMsgNotificationMessage.m */, + A7918E8C240CF485002011CA /* MTBag.m */, + A7918EA0240CF486002011CA /* MTBindingTempAuthKeyContext.h */, + A7918EC7240CF488002011CA /* MTBindingTempAuthKeyContext.m */, + A7918E95240CF485002011CA /* MTBuffer.h */, + A7918EBD240CF488002011CA /* MTBuffer.m */, + A7918E8A240CF485002011CA /* MTBufferReader.h */, + A7918E7B240CF483002011CA /* MTBufferReader.m */, + A7918E93240CF485002011CA /* MTConnectionProbing.h */, + A7918EC4240CF488002011CA /* MTConnectionProbing.m */, + A7918E98240CF485002011CA /* MTContext.m */, + A7918EC0240CF488002011CA /* MTDatacenterAddress.m */, + A7918EE5240CF48A002011CA /* MTDatacenterAddressListData.m */, + A7918ECC240CF488002011CA /* MTDatacenterAddressSet.m */, + A7918E9A240CF485002011CA /* MTDatacenterAuthAction.m */, + A7918E84240CF484002011CA /* MTDatacenterAuthInfo.m */, + A7918EB6240CF487002011CA /* MTDatacenterAuthMessageService.m */, + A7918ED6240CF489002011CA /* MTDatacenterSaltInfo.m */, + A7918E82240CF484002011CA /* MTDatacenterTransferAuthAction.m */, + A7918EC5240CF488002011CA /* MTDatacenterVerificationData.m */, + A7918EAF240CF487002011CA /* MTDestroySessionResponseMessage.h */, + A7918EC6240CF488002011CA /* MTDestroySessionResponseMessage.m */, + A7918EDA240CF489002011CA /* MTDiscoverConnectionSignals.h */, + A7918E91240CF485002011CA /* MTDiscoverConnectionSignals.m */, + A7918E6E240CF483002011CA /* MTDiscoverDatacenterAddressAction.h */, + A7918E88240CF484002011CA /* MTDiscoverDatacenterAddressAction.m */, + A7918E70240CF483002011CA /* MTDisposable.m */, + A7918ED7240CF489002011CA /* MTDNS.h */, + A7918ECF240CF489002011CA /* MTDNS.m */, + A7918ED3240CF489002011CA /* MTDropResponseContext.m */, + A7918E89240CF484002011CA /* MTDropRpcResultMessage.h */, + A7918E71240CF483002011CA /* MTDropRpcResultMessage.m */, + A7918E9D240CF486002011CA /* MTEncryption.m */, + A7918EA4240CF486002011CA /* MTExportedAuthorizationData.m */, + A7918EDF240CF48A002011CA /* MTFileBasedKeychain.m */, + A7918E96240CF485002011CA /* MTFutureSaltsMessage.h */, + A7918EB7240CF487002011CA /* MTFutureSaltsMessage.m */, + A7918E73240CF483002011CA /* MTGzip.m */, + A7918EAA240CF486002011CA /* MTHttpRequestOperation.m */, + A7918EA8240CF486002011CA /* MTIncomingMessage.m */, + A7918E79240CF483002011CA /* MTInputStream.m */, + A7918EA6240CF486002011CA /* MTInternalMessageParser.h */, + A7918EBB240CF487002011CA /* MTInternalMessageParser.m */, + A7918ED5240CF489002011CA /* MTKeychain.m */, + A7918EBC240CF487002011CA /* MTLogging.m */, + A7918E6D240CF483002011CA /* MTMessage.h */, + A7918ECB240CF488002011CA /* MTMessage.m */, + A7918EB4240CF487002011CA /* MTMessageEncryptionKey.m */, + A7918EA2240CF486002011CA /* MTMessageTransaction.m */, + A7918EA9240CF486002011CA /* MTMsgAllInfoMessage.h */, + A7918E92240CF485002011CA /* MTMsgAllInfoMessage.m */, + A7918EB8240CF487002011CA /* MTMsgContainerMessage.h */, + A7918ED8240CF489002011CA /* MTMsgContainerMessage.m */, + A7918EB9240CF487002011CA /* MTMsgDetailedInfoMessage.h */, + A7918EDD240CF489002011CA /* MTMsgDetailedInfoMessage.m */, + A7918E83240CF484002011CA /* MTMsgResendReqMessage.h */, + A7918E75240CF483002011CA /* MTMsgResendReqMessage.m */, + A7918EE6240CF48A002011CA /* MTMsgsAckMessage.h */, + A7918ED0240CF489002011CA /* MTMsgsAckMessage.m */, + A7918ECA240CF488002011CA /* MTMsgsStateInfoMessage.h */, + A7918ECD240CF488002011CA /* MTMsgsStateInfoMessage.m */, + A7918EAE240CF487002011CA /* MTMsgsStateReqMessage.h */, + A7918E9E240CF486002011CA /* MTMsgsStateReqMessage.m */, + A7918ED2240CF489002011CA /* MTNetworkAvailability.m */, + A7918EC2240CF488002011CA /* MTNetworkUsageCalculationInfo.m */, + A7918EC3240CF488002011CA /* MTNetworkUsageManager.m */, + A7918EDB240CF489002011CA /* MTNewSessionCreatedMessage.h */, + A7918EA1240CF486002011CA /* MTNewSessionCreatedMessage.m */, + A7918E7A240CF483002011CA /* MTOutgoingMessage.m */, + A7918E6B240CF482002011CA /* MTOutputStream.m */, + A7918E94240CF485002011CA /* MTPingMessage.h */, + A7918E74240CF483002011CA /* MTPingMessage.m */, + A7918EA5240CF486002011CA /* MTPongMessage.h */, + A7918E87240CF484002011CA /* MTPongMessage.m */, + A7918EE4240CF48A002011CA /* MTPreparedMessage.m */, + A7918EE7240CF48A002011CA /* MTProto.m */, + A7918EAB240CF487002011CA /* MTProxyConnectivity.m */, + A7918EC8240CF488002011CA /* MTQueue.m */, + A7918EBE240CF488002011CA /* MTRequest.m */, + A7918EB0240CF487002011CA /* MTRequestContext.m */, + A7918E8F240CF485002011CA /* MTRequestErrorContext.m */, + A7918EBF240CF488002011CA /* MTRequestMessageService.m */, + A7918EDC240CF489002011CA /* MTResendMessageService.m */, + A7918E9C240CF486002011CA /* MTResPqMessage.h */, + A7918E78240CF483002011CA /* MTResPqMessage.m */, + A7918E7F240CF484002011CA /* MTRpcError.m */, + A7918E8D240CF485002011CA /* MTRpcResultMessage.h */, + A7918E9F240CF486002011CA /* MTRpcResultMessage.m */, + A7918EE3240CF48A002011CA /* MTRsa.h */, + A7918EA3240CF486002011CA /* MTRsa.m */, + A7918EBA240CF487002011CA /* MTServerDhInnerDataMessage.h */, + A7918E6C240CF483002011CA /* MTServerDhInnerDataMessage.m */, + A7918E86240CF484002011CA /* MTServerDhParamsMessage.h */, + A7918EC9240CF488002011CA /* MTServerDhParamsMessage.m */, + A7918ECE240CF489002011CA /* MTSessionInfo.m */, + A7918E80240CF484002011CA /* MTSetClientDhParamsResponseMessage.h */, + A7918E76240CF483002011CA /* MTSetClientDhParamsResponseMessage.m */, + A7918EE2240CF48A002011CA /* MTSignal.m */, + A7918E99240CF485002011CA /* MTSubscriber.m */, + A7918E81240CF484002011CA /* MTTcpConnection.h */, + A7918E7D240CF484002011CA /* MTTcpConnection.m */, + A7918EE1240CF48A002011CA /* MTTcpConnectionBehaviour.h */, + A7918EB3240CF487002011CA /* MTTcpConnectionBehaviour.m */, + A7918EB5240CF487002011CA /* MTTcpTransport.m */, + A7918E8E240CF485002011CA /* MTTime.m */, + A7918E6F240CF483002011CA /* MTTimeFixContext.m */, + A7918EA7240CF486002011CA /* MTTimer.m */, + A7918E97240CF485002011CA /* MTTimeSyncMessageService.m */, + A7918E85240CF484002011CA /* MTTransport.m */, + A7918ED1240CF489002011CA /* MTTransportScheme.m */, + A7918E7C240CF483002011CA /* MTTransportSchemeStats.h */, + A7918E7E240CF484002011CA /* MTTransportSchemeStats.m */, + A7918EAC240CF487002011CA /* MTTransportTransaction.m */, + A7918E72240CF483002011CA /* PingFoundation.h */, + A7918E77240CF483002011CA /* PingFoundation.m */, + ); + path = Source; + sourceTree = ""; + }; + A7918F65240CF48F002011CA /* Headers */ = { + isa = PBXGroup; + children = ( + A7918F7E240CF4AC002011CA /* AFHTTPRequestOperation.h */, + A7918F8D240CF4AD002011CA /* AFURLConnectionOperation.h */, + A7918F7B240CF4AC002011CA /* MTApiEnvironment.h */, + A7918F74240CF4AB002011CA /* MTAtomic.h */, + A7918F71240CF4AB002011CA /* MTBackupAddressSignals.h */, + A7918F86240CF4AD002011CA /* MTBag.h */, + A7918F85240CF4AD002011CA /* MTContext.h */, + A7918F7D240CF4AC002011CA /* MTDatacenterAddress.h */, + A7918F7A240CF4AC002011CA /* MTDatacenterAddressListData.h */, + A7918F6A240CF4AB002011CA /* MTDatacenterAddressSet.h */, + A7918F6C240CF4AB002011CA /* MTDatacenterAuthAction.h */, + A7918F66240CF4AB002011CA /* MTDatacenterAuthInfo.h */, + A7918F9D240CF4AE002011CA /* MTDatacenterAuthMessageService.h */, + A7918F8F240CF4AD002011CA /* MTDatacenterSaltInfo.h */, + A7918F82240CF4AC002011CA /* MTDatacenterTransferAuthAction.h */, + A7918F9E240CF4AE002011CA /* MTDatacenterVerificationData.h */, + A7918F70240CF4AB002011CA /* MTDisposable.h */, + A7918F9C240CF4AE002011CA /* MTDropResponseContext.h */, + A7918F72240CF4AB002011CA /* MTEncryption.h */, + A7918F9F240CF4AE002011CA /* MTExportedAuthorizationData.h */, + A7918F84240CF4AD002011CA /* MTFileBasedKeychain.h */, + A7918F94240CF4AE002011CA /* MTGzip.h */, + A7918F6B240CF4AB002011CA /* MTHttpRequestOperation.h */, + A7918F77240CF4AC002011CA /* MTIncomingMessage.h */, + A7918FA0240CF4AE002011CA /* MTInputStream.h */, + A7918F8A240CF4AD002011CA /* MTInternalId.h */, + A7918F7C240CF4AC002011CA /* MTKeychain.h */, + A7918F69240CF4AB002011CA /* MTLogging.h */, + A7918F89240CF4AD002011CA /* MTMessageEncryptionKey.h */, + A7918F68240CF4AB002011CA /* MTMessageService.h */, + A7918F93240CF4AE002011CA /* MTMessageTransaction.h */, + A7918F99240CF4AE002011CA /* MTNetworkAvailability.h */, + A7918F6D240CF4AB002011CA /* MTNetworkUsageCalculationInfo.h */, + A7918F75240CF4AB002011CA /* MTNetworkUsageManager.h */, + A7918F90240CF4AD002011CA /* MTOutgoingMessage.h */, + A7918F92240CF4AE002011CA /* MTOutputStream.h */, + A7918F98240CF4AE002011CA /* MTPreparedMessage.h */, + A7918F80240CF4AC002011CA /* MTProto.h */, + A7918F8B240CF4AD002011CA /* MTProxyConnectivity.h */, + A7918F8C240CF4AD002011CA /* MTQueue.h */, + A7918F96240CF4AE002011CA /* MTRequest.h */, + A7918F79240CF4AC002011CA /* MTRequestContext.h */, + A7918F8E240CF4AD002011CA /* MTRequestErrorContext.h */, + A7918F76240CF4AC002011CA /* MTRequestMessageService.h */, + A7918F9B240CF4AE002011CA /* MTResendMessageService.h */, + A7918F9A240CF4AE002011CA /* MTRpcError.h */, + A7918F78240CF4AC002011CA /* MTSerialization.h */, + A7918F67240CF4AB002011CA /* MTSessionInfo.h */, + A7918F7F240CF4AC002011CA /* MTSignal.h */, + A7918F6E240CF4AB002011CA /* MTSubscriber.h */, + A7918F81240CF4AC002011CA /* MTTcpTransport.h */, + A7918F97240CF4AE002011CA /* MTTime.h */, + A7918F91240CF4AE002011CA /* MTTimeFixContext.h */, + A7918F88240CF4AD002011CA /* MTTimer.h */, + A7918F6F240CF4AB002011CA /* MTTimeSyncMessageService.h */, + A7918F83240CF4AC002011CA /* MTTransport.h */, + A7918F73240CF4AB002011CA /* MTTransportScheme.h */, + A7918F87240CF4AD002011CA /* MTTransportTransaction.h */, + ); + path = Headers; + sourceTree = ""; + }; + D05A830918AFB3F9007F1076 = { + isa = PBXGroup; + children = ( + D05A84E718AFF0EE007F1076 /* Third Party */, + D05A849B18AFCA3D007F1076 /* MtProtoKit */, + D079AB981AF39B8000076F59 /* MtProtoKit */, + D05A831618AFB3F9007F1076 /* Frameworks */, + D05A831518AFB3F9007F1076 /* Products */, + ); + sourceTree = ""; + }; + D05A831518AFB3F9007F1076 /* Products */ = { + isa = PBXGroup; + children = ( + D079AB971AF39B8000076F59 /* MtProtoKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + D05A831618AFB3F9007F1076 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7918DD6240CEF8A002011CA /* MurMurHash32.framework */, + A790A31A236B1199000451B5 /* EncryptionProvider.framework */, + A790A318236B0F6C000451B5 /* Foundation.framework */, + D03878A32332504600DB441C /* CFNetwork.framework */, + D03878A12332503E00DB441C /* SystemConfiguration.framework */, + D038789F2332503300DB441C /* Security.framework */, + D038789D2332500A00DB441C /* libc++.tbd */, + D0B4187A1D7E04CF004562A4 /* libz.tbd */, + D0CAF2F01D75F4EA0011F558 /* CFNetwork.framework */, + D0CAF2EE1D75F4E20011F558 /* UIKit.framework */, + D0CAF2EB1D75F4520011F558 /* Security.framework */, + D0CD990F1D75C16100F41187 /* libcrypto.a */, + D00354731C173CD9006610DA /* SystemConfiguration.framework */, + D00354711C173CD0006610DA /* libz.tbd */, + D0D1A0711ADDE2FC007D9ED6 /* libz.dylib */, + D063A2F918B14AB500C65116 /* libcrypto.dylib */, + D05A831718AFB3F9007F1076 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D05A849B18AFCA3D007F1076 /* MtProtoKit */ = { + isa = PBXGroup; + children = ( + A7918F65240CF48F002011CA /* Headers */, + A7918E6A240CF467002011CA /* Source */, + ); + name = MtProtoKit; + path = "../../submodules/telegram-ios/submodules/MtProtoKit"; + sourceTree = ""; + }; + D05A84E718AFF0EE007F1076 /* Third Party */ = { + isa = PBXGroup; + children = ( + ); + name = "Third Party"; + path = "../../submodules/telegram-ios/submodules/MtProtoKit/thirdparty"; + sourceTree = ""; + }; + D079AB981AF39B8000076F59 /* MtProtoKit */ = { + isa = PBXGroup; + children = ( + A7D281E5236C26400000A9BF /* Info.plist */, + D079AB9B1AF39B8000076F59 /* MtProtoKit.h */, + ); + path = MtProtoKit; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D079AB941AF39B8000076F59 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918FA7240CF4AE002011CA /* MTDatacenterAuthAction.h in Headers */, + A7918F2B240CF48A002011CA /* MTMsgsStateReqMessage.h in Headers */, + A7918F37240CF48A002011CA /* MTServerDhInnerDataMessage.h in Headers */, + A7918FCB240CF4AF002011CA /* MTOutgoingMessage.h in Headers */, + A7918F22240CF48A002011CA /* MTPongMessage.h in Headers */, + A7918F60240CF48A002011CA /* MTRsa.h in Headers */, + A7918F12240CF48A002011CA /* MTBuffer.h in Headers */, + A7918FA9240CF4AE002011CA /* MTSubscriber.h in Headers */, + A7918FD8240CF4AF002011CA /* MTDatacenterAuthMessageService.h in Headers */, + A7918F0A240CF48A002011CA /* MTRpcResultMessage.h in Headers */, + A7918F5E240CF48A002011CA /* MTTcpConnectionBehaviour.h in Headers */, + A7918F26240CF48A002011CA /* MTMsgAllInfoMessage.h in Headers */, + A7918FB1240CF4AE002011CA /* MTRequestMessageService.h in Headers */, + A7918FC8240CF4AF002011CA /* AFURLConnectionOperation.h in Headers */, + A7918FD7240CF4AF002011CA /* MTDropResponseContext.h in Headers */, + A7918FBA240CF4AF002011CA /* MTSignal.h in Headers */, + A7918FCE240CF4AF002011CA /* MTMessageTransaction.h in Headers */, + A7918FBF240CF4AF002011CA /* MTFileBasedKeychain.h in Headers */, + A7918FAD240CF4AE002011CA /* MTEncryption.h in Headers */, + A7918F36240CF48A002011CA /* MTMsgDetailedInfoMessage.h in Headers */, + A7918FAE240CF4AE002011CA /* MTTransportScheme.h in Headers */, + A7918FA6240CF4AE002011CA /* MTHttpRequestOperation.h in Headers */, + A7918FC6240CF4AF002011CA /* MTProxyConnectivity.h in Headers */, + A7918FAC240CF4AE002011CA /* MTBackupAddressSignals.h in Headers */, + A7918FC9240CF4AF002011CA /* MTRequestErrorContext.h in Headers */, + A7918FC1240CF4AF002011CA /* MTBag.h in Headers */, + A7918FAB240CF4AE002011CA /* MTDisposable.h in Headers */, + A7918F23240CF48A002011CA /* MTInternalMessageParser.h in Headers */, + D079AB9C1AF39B8000076F59 /* MtProtoKit.h in Headers */, + A7918F54240CF48A002011CA /* MTDNS.h in Headers */, + A7918EEA240CF48A002011CA /* MTMessage.h in Headers */, + A7918F13240CF48A002011CA /* MTFutureSaltsMessage.h in Headers */, + A7918F1D240CF48A002011CA /* MTBindingTempAuthKeyContext.h in Headers */, + A7918FB2240CF4AE002011CA /* MTIncomingMessage.h in Headers */, + A7918FB9240CF4AE002011CA /* AFHTTPRequestOperation.h in Headers */, + A7918FD2240CF4AF002011CA /* MTTime.h in Headers */, + A7918F63240CF48A002011CA /* MTMsgsAckMessage.h in Headers */, + A7918FB7240CF4AE002011CA /* MTKeychain.h in Headers */, + A7918FB5240CF4AE002011CA /* MTDatacenterAddressListData.h in Headers */, + A7918FB6240CF4AE002011CA /* MTApiEnvironment.h in Headers */, + A7918FCC240CF4AF002011CA /* MTTimeFixContext.h in Headers */, + A7918FCF240CF4AF002011CA /* MTGzip.h in Headers */, + A7918F11240CF48A002011CA /* MTPingMessage.h in Headers */, + A7918FD6240CF4AF002011CA /* MTResendMessageService.h in Headers */, + A7918F5B240CF48A002011CA /* GCDAsyncSocket.h in Headers */, + A7918FD4240CF4AF002011CA /* MTNetworkAvailability.h in Headers */, + A7918FC2240CF4AF002011CA /* MTTransportTransaction.h in Headers */, + A7918FA2240CF4AE002011CA /* MTSessionInfo.h in Headers */, + A7918EEB240CF48A002011CA /* MTDiscoverDatacenterAddressAction.h in Headers */, + A7918FC3240CF4AF002011CA /* MTTimer.h in Headers */, + A7918FC5240CF4AF002011CA /* MTInternalId.h in Headers */, + A7918FD1240CF4AF002011CA /* MTRequest.h in Headers */, + A7918FD5240CF4AF002011CA /* MTRpcError.h in Headers */, + A7918FBB240CF4AF002011CA /* MTProto.h in Headers */, + A7918FBE240CF4AF002011CA /* MTTransport.h in Headers */, + A7918F10240CF48A002011CA /* MTConnectionProbing.h in Headers */, + A7918FB8240CF4AE002011CA /* MTDatacenterAddress.h in Headers */, + A7918FBD240CF4AF002011CA /* MTDatacenterTransferAuthAction.h in Headers */, + A7918FB0240CF4AE002011CA /* MTNetworkUsageManager.h in Headers */, + A7918F47240CF48A002011CA /* MTMsgsStateInfoMessage.h in Headers */, + A7918FD3240CF4AF002011CA /* MTPreparedMessage.h in Headers */, + A7918F57240CF48A002011CA /* MTDiscoverConnectionSignals.h in Headers */, + A7918FC0240CF4AF002011CA /* MTContext.h in Headers */, + A7918F35240CF48A002011CA /* MTMsgContainerMessage.h in Headers */, + A7918FC4240CF4AF002011CA /* MTMessageEncryptionKey.h in Headers */, + A7918F2C240CF48A002011CA /* MTDestroySessionResponseMessage.h in Headers */, + A7918F3E240CF48A002011CA /* MTBadMsgNotificationMessage.h in Headers */, + A7918F00240CF48A002011CA /* MTMsgResendReqMessage.h in Headers */, + A7918EFE240CF48A002011CA /* MTTcpConnection.h in Headers */, + A7918FC7240CF4AF002011CA /* MTQueue.h in Headers */, + A7918F51240CF48A002011CA /* MTAes.h in Headers */, + A7918FA3240CF4AE002011CA /* MTMessageService.h in Headers */, + A7918FA8240CF4AE002011CA /* MTNetworkUsageCalculationInfo.h in Headers */, + A7918F03240CF48A002011CA /* MTServerDhParamsMessage.h in Headers */, + A7918FD9240CF4AF002011CA /* MTDatacenterVerificationData.h in Headers */, + A7918EF9240CF48A002011CA /* MTTransportSchemeStats.h in Headers */, + A7918F06240CF48A002011CA /* MTDropRpcResultMessage.h in Headers */, + A7918FDA240CF4AF002011CA /* MTExportedAuthorizationData.h in Headers */, + A7918FA4240CF4AE002011CA /* MTLogging.h in Headers */, + A7918FAF240CF4AE002011CA /* MTAtomic.h in Headers */, + A7918FCA240CF4AF002011CA /* MTDatacenterSaltInfo.h in Headers */, + A7918FB3240CF4AE002011CA /* MTSerialization.h in Headers */, + A7918F07240CF48A002011CA /* MTBufferReader.h in Headers */, + A7918FDB240CF4AF002011CA /* MTInputStream.h in Headers */, + A7918FA5240CF4AE002011CA /* MTDatacenterAddressSet.h in Headers */, + A7918EFD240CF48A002011CA /* MTSetClientDhParamsResponseMessage.h in Headers */, + A7918EEF240CF48A002011CA /* PingFoundation.h in Headers */, + A7918F58240CF48A002011CA /* MTNewSessionCreatedMessage.h in Headers */, + A7918FBC240CF4AF002011CA /* MTTcpTransport.h in Headers */, + A7918FCD240CF4AF002011CA /* MTOutputStream.h in Headers */, + A7918FA1240CF4AE002011CA /* MTDatacenterAuthInfo.h in Headers */, + A7918F19240CF48A002011CA /* MTResPqMessage.h in Headers */, + A7918FAA240CF4AE002011CA /* MTTimeSyncMessageService.h in Headers */, + A7918FB4240CF4AE002011CA /* MTRequestContext.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D079AB961AF39B8000076F59 /* MtProtoKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = D079ABAE1AF39B8000076F59 /* Build configuration list for PBXNativeTarget "MtProtoKit" */; + buildPhases = ( + D079AB921AF39B8000076F59 /* Sources */, + D079AB931AF39B8000076F59 /* Frameworks */, + D079AB941AF39B8000076F59 /* Headers */, + D079AB951AF39B8000076F59 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MtProtoKit; + productName = MtProtoKitMac; + productReference = D079AB971AF39B8000076F59 /* MtProtoKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D05A830A18AFB3F9007F1076 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + D079AB961AF39B8000076F59 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 1010; + }; + }; + }; + buildConfigurationList = D05A830D18AFB3F9007F1076 /* Build configuration list for PBXProject "MtProtoKit_Xcode" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = D05A830918AFB3F9007F1076; + productRefGroup = D05A831518AFB3F9007F1076 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D079AB961AF39B8000076F59 /* MtProtoKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D079AB951AF39B8000076F59 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D079AB921AF39B8000076F59 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918F5D240CF48A002011CA /* MTAes.m in Sources */, + A7918F3D240CF48A002011CA /* MTDatacenterAddress.m in Sources */, + A7918EF5240CF48A002011CA /* MTResPqMessage.m in Sources */, + A7918F4C240CF48A002011CA /* MTDNS.m in Sources */, + A7918EF8240CF48A002011CA /* MTBufferReader.m in Sources */, + A7918F27240CF48A002011CA /* MTHttpRequestOperation.m in Sources */, + A7918F1F240CF48A002011CA /* MTMessageTransaction.m in Sources */, + A7918F32240CF48A002011CA /* MTTcpTransport.m in Sources */, + A7918EF2240CF48A002011CA /* MTMsgResendReqMessage.m in Sources */, + A7918F31240CF48A002011CA /* MTMessageEncryptionKey.m in Sources */, + A7918F4E240CF48A002011CA /* MTTransportScheme.m in Sources */, + A7918F0B240CF48A002011CA /* MTTime.m in Sources */, + A7918F3A240CF48A002011CA /* MTBuffer.m in Sources */, + A7918EED240CF48A002011CA /* MTDisposable.m in Sources */, + A7918F0E240CF48A002011CA /* MTDiscoverConnectionSignals.m in Sources */, + A7918EFA240CF48A002011CA /* MTTcpConnection.m in Sources */, + A7918F2F240CF48A002011CA /* MTApiEnvironment.m in Sources */, + A7918F1C240CF48A002011CA /* MTRpcResultMessage.m in Sources */, + A7918F1A240CF48A002011CA /* MTEncryption.m in Sources */, + A7918F24240CF48A002011CA /* MTTimer.m in Sources */, + A7918EFB240CF48A002011CA /* MTTransportSchemeStats.m in Sources */, + A7918F2A240CF48A002011CA /* AFHTTPRequestOperation.m in Sources */, + A7918F02240CF48A002011CA /* MTTransport.m in Sources */, + A7918F1E240CF48A002011CA /* MTNewSessionCreatedMessage.m in Sources */, + A7918F59240CF48A002011CA /* MTResendMessageService.m in Sources */, + A7918EFC240CF48A002011CA /* MTRpcError.m in Sources */, + A7918F5C240CF48A002011CA /* MTFileBasedKeychain.m in Sources */, + A7918EF3240CF48A002011CA /* MTSetClientDhParamsResponseMessage.m in Sources */, + A7918F05240CF48A002011CA /* MTDiscoverDatacenterAddressAction.m in Sources */, + A7918F4A240CF48A002011CA /* MTMsgsStateInfoMessage.m in Sources */, + A7918EF7240CF48A002011CA /* MTOutgoingMessage.m in Sources */, + A7918F28240CF48A002011CA /* MTProxyConnectivity.m in Sources */, + A7918EEC240CF48A002011CA /* MTTimeFixContext.m in Sources */, + A7918F01240CF48A002011CA /* MTDatacenterAuthInfo.m in Sources */, + A7918F08240CF48A002011CA /* GCDAsyncSocket.m in Sources */, + A7918F25240CF48A002011CA /* MTIncomingMessage.m in Sources */, + A7918EEE240CF48A002011CA /* MTDropRpcResultMessage.m in Sources */, + A7918F15240CF48A002011CA /* MTContext.m in Sources */, + A7918F49240CF48A002011CA /* MTDatacenterAddressSet.m in Sources */, + A7918F0D240CF48A002011CA /* MTBackupAddressSignals.m in Sources */, + A7918F39240CF48A002011CA /* MTLogging.m in Sources */, + A7918F2D240CF48A002011CA /* MTRequestContext.m in Sources */, + A7918F46240CF48A002011CA /* MTServerDhParamsMessage.m in Sources */, + A7918F50240CF48A002011CA /* MTDropResponseContext.m in Sources */, + A7918F48240CF48A002011CA /* MTMessage.m in Sources */, + A7918F04240CF48A002011CA /* MTPongMessage.m in Sources */, + A7918F56240CF48A002011CA /* MTAtomic.m in Sources */, + A7918F61240CF48A002011CA /* MTPreparedMessage.m in Sources */, + A7918F17240CF48A002011CA /* MTDatacenterAuthAction.m in Sources */, + A7918EE9240CF48A002011CA /* MTServerDhInnerDataMessage.m in Sources */, + A7918F5F240CF48A002011CA /* MTSignal.m in Sources */, + A7918F4F240CF48A002011CA /* MTNetworkAvailability.m in Sources */, + A7918EF0240CF48A002011CA /* MTGzip.m in Sources */, + A7918EE8240CF48A002011CA /* MTOutputStream.m in Sources */, + A7918F5A240CF48A002011CA /* MTMsgDetailedInfoMessage.m in Sources */, + A7918F14240CF48A002011CA /* MTTimeSyncMessageService.m in Sources */, + A7918F21240CF48A002011CA /* MTExportedAuthorizationData.m in Sources */, + A7918EF4240CF48A002011CA /* PingFoundation.m in Sources */, + A7918F33240CF48A002011CA /* MTDatacenterAuthMessageService.m in Sources */, + A7918F52240CF48A002011CA /* MTKeychain.m in Sources */, + A7918F4D240CF48A002011CA /* MTMsgsAckMessage.m in Sources */, + A7918F64240CF48A002011CA /* MTProto.m in Sources */, + A7918F18240CF48A002011CA /* AFURLConnectionOperation.m in Sources */, + A7918F0C240CF48A002011CA /* MTRequestErrorContext.m in Sources */, + A7918F40240CF48A002011CA /* MTNetworkUsageManager.m in Sources */, + A7918F44240CF48A002011CA /* MTBindingTempAuthKeyContext.m in Sources */, + A7918F62240CF48A002011CA /* MTDatacenterAddressListData.m in Sources */, + A7918F29240CF48A002011CA /* MTTransportTransaction.m in Sources */, + A7918F38240CF48A002011CA /* MTInternalMessageParser.m in Sources */, + A7918F0F240CF48A002011CA /* MTMsgAllInfoMessage.m in Sources */, + A7918F53240CF48A002011CA /* MTDatacenterSaltInfo.m in Sources */, + A7918F41240CF48A002011CA /* MTConnectionProbing.m in Sources */, + A7918F2E240CF48A002011CA /* MTBadMsgNotificationMessage.m in Sources */, + A7918F55240CF48A002011CA /* MTMsgContainerMessage.m in Sources */, + A7918F20240CF48A002011CA /* MTRsa.m in Sources */, + A7918EF1240CF48A002011CA /* MTPingMessage.m in Sources */, + A7918F09240CF48A002011CA /* MTBag.m in Sources */, + A7918F42240CF48A002011CA /* MTDatacenterVerificationData.m in Sources */, + A7918F4B240CF48A002011CA /* MTSessionInfo.m in Sources */, + A7918F3C240CF48A002011CA /* MTRequestMessageService.m in Sources */, + A7918F45240CF48A002011CA /* MTQueue.m in Sources */, + A7918F43240CF48A002011CA /* MTDestroySessionResponseMessage.m in Sources */, + A7918F3B240CF48A002011CA /* MTRequest.m in Sources */, + A7918F3F240CF48A002011CA /* MTNetworkUsageCalculationInfo.m in Sources */, + A7918EFF240CF48A002011CA /* MTDatacenterTransferAuthAction.m in Sources */, + A7918F30240CF48A002011CA /* MTTcpConnectionBehaviour.m in Sources */, + A7918F16240CF48A002011CA /* MTSubscriber.m in Sources */, + A7918EF6240CF48A002011CA /* MTInputStream.m in Sources */, + A7918F1B240CF48A002011CA /* MTMsgsStateReqMessage.m in Sources */, + A7918F34240CF48A002011CA /* MTFutureSaltsMessage.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282E7238EAB7100742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + }; + name = Github; + }; + A7F282E8238EAB7100742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "BETA=1", + ); + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + D0364D4722B3E35B002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + }; + name = HockeyappMacAlpha; + }; + D0364D4922B3E35B002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "BETA=1", + ); + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D079FD101F06BE440038FADE /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + }; + name = DebugHockeyapp; + }; + D079FD121F06BE440038FADE /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "BETA=1", + ); + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D079FD161F06BE4D0038FADE /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseAppStore; + }; + D079FD181F06BE4D0038FADE /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = APPSTORE; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D079FD1C1F06BE540038FADE /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseHockeyapp; + }; + D079FD1E1F06BE540038FADE /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = STABLE; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D0DB57B01E5C4B470071854C /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + MACOSX_DEPLOYMENT_TARGET = 10.11; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + }; + name = DebugAppStore; + }; + D0DB57B21E5C4B470071854C /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ""; + FRAMEWORK_VERSION = A; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "BETA=1", + ); + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = MtProtoKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ""; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_CFLAGS = "-DMtProtoKitMacFramework=1"; + PRODUCT_BUNDLE_IDENTIFIER = "org.telegram.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D05A830D18AFB3F9007F1076 /* Build configuration list for PBXProject "MtProtoKit_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D079FD101F06BE440038FADE /* DebugHockeyapp */, + D0364D4722B3E35B002A6EF0 /* HockeyappMacAlpha */, + D0DB57B01E5C4B470071854C /* DebugAppStore */, + A7F282E7238EAB7100742C20 /* Github */, + D079FD161F06BE4D0038FADE /* ReleaseAppStore */, + D079FD1C1F06BE540038FADE /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + D079ABAE1AF39B8000076F59 /* Build configuration list for PBXNativeTarget "MtProtoKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D079FD121F06BE440038FADE /* DebugHockeyapp */, + D0364D4922B3E35B002A6EF0 /* HockeyappMacAlpha */, + D0DB57B21E5C4B470071854C /* DebugAppStore */, + A7F282E8238EAB7100742C20 /* Github */, + D079FD181F06BE4D0038FADE /* ReleaseAppStore */, + D079FD1E1F06BE540038FADE /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D05A830A18AFB3F9007F1076 /* Project object */; +} diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..02630510de --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..5568046175 Binary files /dev/null and b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/MtProtoKit.xcscheme b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/MtProtoKit.xcscheme new file mode 100644 index 0000000000..ec2bbd3dab --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/MtProtoKit.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..064640e4e1 --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + MtProtoKit.xcscheme_^#shared#^_ + + orderHint + 33 + + MtProtoKitDynamic.xcscheme_^#shared#^_ + + orderHint + 35 + + MtProtoKitMac.xcscheme_^#shared#^_ + + orderHint + 34 + + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/MtProtoKit.xcscheme b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/MtProtoKit.xcscheme new file mode 100644 index 0000000000..8b16bd31c9 --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/MtProtoKit.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..b8b3b794c3 --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,21 @@ + + + + + SchemeUserState + + MtProtoKit.xcscheme + + isShown + + orderHint + 12 + + MtProtoKitDynamic.xcscheme_^#shared#^_ + + orderHint + 2 + + + + diff --git a/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..a43f65e87d --- /dev/null +++ b/core-xprojects/MtProtoKit/MtProtoKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + MtProtoKit.xcscheme_^#shared#^_ + + orderHint + 39 + + MtProtoKitDynamic.xcscheme_^#shared#^_ + + orderHint + 40 + + MtProtoKitMac.xcscheme_^#shared#^_ + + orderHint + 41 + + + + diff --git a/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.pbxproj b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d36eab7add --- /dev/null +++ b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.pbxproj @@ -0,0 +1,608 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7918DCC240CEEBD002011CA /* MurMurHash32Public.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DCA240CEEBD002011CA /* MurMurHash32Public.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DD3240CEEFC002011CA /* MurMurHash32.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DD2240CEEFC002011CA /* MurMurHash32.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DD5240CEF0D002011CA /* MurMurHash32.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918DD4240CEF0D002011CA /* MurMurHash32.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7918DC7240CEEBD002011CA /* MurMurHash32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MurMurHash32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918DCA240CEEBD002011CA /* MurMurHash32Public.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MurMurHash32Public.h; sourceTree = ""; }; + A7918DCB240CEEBD002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7918DD2240CEEFC002011CA /* MurMurHash32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MurMurHash32.h; path = "../../submodules/telegram-ios/submodules/MurMurHash32/PublicHeaders/MurMurHash32/MurMurHash32.h"; sourceTree = ""; }; + A7918DD4240CEF0D002011CA /* MurMurHash32.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MurMurHash32.m; path = "../../submodules/telegram-ios/submodules/MurMurHash32/Sources/MurMurHash32.m"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7918DC4240CEEBD002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7918DBD240CEEBD002011CA = { + isa = PBXGroup; + children = ( + A7918DD4240CEF0D002011CA /* MurMurHash32.m */, + A7918DD2240CEEFC002011CA /* MurMurHash32.h */, + A7918DC9240CEEBD002011CA /* MurMurHash32 */, + A7918DC8240CEEBD002011CA /* Products */, + ); + sourceTree = ""; + }; + A7918DC8240CEEBD002011CA /* Products */ = { + isa = PBXGroup; + children = ( + A7918DC7240CEEBD002011CA /* MurMurHash32.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7918DC9240CEEBD002011CA /* MurMurHash32 */ = { + isa = PBXGroup; + children = ( + A7918DCA240CEEBD002011CA /* MurMurHash32Public.h */, + A7918DCB240CEEBD002011CA /* Info.plist */, + ); + path = MurMurHash32; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7918DC2240CEEBD002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DCC240CEEBD002011CA /* MurMurHash32Public.h in Headers */, + A7918DD3240CEEFC002011CA /* MurMurHash32.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7918DC6240CEEBD002011CA /* MurMurHash32 */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7918DCF240CEEBD002011CA /* Build configuration list for PBXNativeTarget "MurMurHash32" */; + buildPhases = ( + A7918DC2240CEEBD002011CA /* Headers */, + A7918DC3240CEEBD002011CA /* Sources */, + A7918DC4240CEEBD002011CA /* Frameworks */, + A7918DC5240CEEBD002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MurMurHash32; + productName = MurMurHash32; + productReference = A7918DC7240CEEBD002011CA /* MurMurHash32.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7918DBE240CEEBD002011CA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7918DC6240CEEBD002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A7918DC1240CEEBD002011CA /* Build configuration list for PBXProject "MurMurHash32" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7918DBD240CEEBD002011CA; + productRefGroup = A7918DC8240CEEBD002011CA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7918DC6240CEEBD002011CA /* MurMurHash32 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7918DC5240CEEBD002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7918DC3240CEEBD002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DD5240CEF0D002011CA /* MurMurHash32.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7918DCD240CEEBD002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7918DCE240CEEBD002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7918DD0240CEEBD002011CA /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MurMurHash32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MurMurHash32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A7918DD1240CEEBD002011CA /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MurMurHash32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MurMurHash32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A7918DD8240CEFA5002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7918DD9240CEFA5002011CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MurMurHash32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MurMurHash32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A7918DDA240CEFAB002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7918DDB240CEFAB002011CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MurMurHash32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MurMurHash32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7918DDC240CEFC1002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7918DDD240CEFC1002011CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MurMurHash32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.MurMurHash32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7918DC1240CEEBD002011CA /* Build configuration list for PBXProject "MurMurHash32" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918DCD240CEEBD002011CA /* DebugAppStore */, + A7918DDA240CEFAB002011CA /* HockeyappMacAlpha */, + A7918DD8240CEFA5002011CA /* DebugHockeyapp */, + A7918DCE240CEEBD002011CA /* ReleaseHockeyapp */, + A7918DDC240CEFC1002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A7918DCF240CEEBD002011CA /* Build configuration list for PBXNativeTarget "MurMurHash32" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918DD0240CEEBD002011CA /* DebugAppStore */, + A7918DDB240CEFAB002011CA /* HockeyappMacAlpha */, + A7918DD9240CEFA5002011CA /* DebugHockeyapp */, + A7918DD1240CEEBD002011CA /* ReleaseHockeyapp */, + A7918DDD240CEFC1002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7918DBE240CEEBD002011CA /* Project object */; +} diff --git a/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..80145310c2 --- /dev/null +++ b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/MurMurHash32/MurMurHash32.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/MurMurHash32/MurMurHash32/Info.plist b/core-xprojects/MurMurHash32/MurMurHash32/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/MurMurHash32/MurMurHash32/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/MurMurHash32/MurMurHash32/MurMurHash32Public.h b/core-xprojects/MurMurHash32/MurMurHash32/MurMurHash32Public.h new file mode 100644 index 0000000000..5b8ae4cf7b --- /dev/null +++ b/core-xprojects/MurMurHash32/MurMurHash32/MurMurHash32Public.h @@ -0,0 +1,20 @@ +// +// MurMurHash32.h +// MurMurHash32 +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for MurMurHash32. +FOUNDATION_EXPORT double MurMurHash32VersionNumber; + +//! Project version string for MurMurHash32. +FOUNDATION_EXPORT const unsigned char MurMurHash32VersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.pbxproj b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..cb3b14d8af --- /dev/null +++ b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.pbxproj @@ -0,0 +1,631 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A791900C240CF76A002011CA /* NetworkLoggingPublic.h in Headers */ = {isa = PBXBuildFile; fileRef = A791900A240CF76A002011CA /* NetworkLoggingPublic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919014240CF7E3002011CA /* NetworkLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919013240CF7E3002011CA /* NetworkLogging.m */; }; + A7919017240CF7F0002011CA /* NetworkLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919016240CF7F0002011CA /* NetworkLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919133240CFF3F002011CA /* MtProtoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7919132240CFF3F002011CA /* MtProtoKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7919007240CF76A002011CA /* NetworkLogging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NetworkLogging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A791900A240CF76A002011CA /* NetworkLoggingPublic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkLoggingPublic.h; sourceTree = ""; }; + A791900B240CF76A002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7919013240CF7E3002011CA /* NetworkLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetworkLogging.m; path = "../../../../submodules/telegram-ios/submodules/NetworkLogging/Sources/NetworkLogging.m"; sourceTree = ""; }; + A7919016240CF7F0002011CA /* NetworkLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetworkLogging.h; path = "../../../../submodules/telegram-ios/submodules/NetworkLogging/PublicHeaders/NetworkLogging/NetworkLogging.h"; sourceTree = ""; }; + A7919132240CFF3F002011CA /* MtProtoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7919004240CF76A002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919133240CFF3F002011CA /* MtProtoKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7918FFD240CF76A002011CA = { + isa = PBXGroup; + children = ( + A7919009240CF76A002011CA /* NetworkLogging */, + A7919008240CF76A002011CA /* Products */, + A7919131240CFF3F002011CA /* Frameworks */, + ); + sourceTree = ""; + }; + A7919008240CF76A002011CA /* Products */ = { + isa = PBXGroup; + children = ( + A7919007240CF76A002011CA /* NetworkLogging.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7919009240CF76A002011CA /* NetworkLogging */ = { + isa = PBXGroup; + children = ( + A7919015240CF7E5002011CA /* Headers */, + A7919012240CF7CD002011CA /* Sources */, + A791900A240CF76A002011CA /* NetworkLoggingPublic.h */, + A791900B240CF76A002011CA /* Info.plist */, + ); + path = NetworkLogging; + sourceTree = ""; + }; + A7919012240CF7CD002011CA /* Sources */ = { + isa = PBXGroup; + children = ( + A7919013240CF7E3002011CA /* NetworkLogging.m */, + ); + path = Sources; + sourceTree = ""; + }; + A7919015240CF7E5002011CA /* Headers */ = { + isa = PBXGroup; + children = ( + A7919016240CF7F0002011CA /* NetworkLogging.h */, + ); + path = Headers; + sourceTree = ""; + }; + A7919131240CFF3F002011CA /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7919132240CFF3F002011CA /* MtProtoKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7919002240CF76A002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A791900C240CF76A002011CA /* NetworkLoggingPublic.h in Headers */, + A7919017240CF7F0002011CA /* NetworkLogging.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7919006240CF76A002011CA /* NetworkLogging */ = { + isa = PBXNativeTarget; + buildConfigurationList = A791900F240CF76A002011CA /* Build configuration list for PBXNativeTarget "NetworkLogging" */; + buildPhases = ( + A7919002240CF76A002011CA /* Headers */, + A7919003240CF76A002011CA /* Sources */, + A7919004240CF76A002011CA /* Frameworks */, + A7919005240CF76A002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NetworkLogging; + productName = NetworkLogging; + productReference = A7919007240CF76A002011CA /* NetworkLogging.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7918FFE240CF76A002011CA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7919006240CF76A002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A7919001240CF76A002011CA /* Build configuration list for PBXProject "NetworkLogging" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7918FFD240CF76A002011CA; + productRefGroup = A7919008240CF76A002011CA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7919006240CF76A002011CA /* NetworkLogging */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7919005240CF76A002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7919003240CF76A002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919014240CF7E3002011CA /* NetworkLogging.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A791900D240CF76A002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A791900E240CF76A002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7919010240CF76A002011CA /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = NetworkLogging/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.NetworkLogging; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A7919011240CF76A002011CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = NetworkLogging/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.NetworkLogging; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A7919042240CFA70002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7919043240CFA70002011CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = NetworkLogging/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.NetworkLogging; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A7919044240CFA76002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7919045240CFA76002011CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = NetworkLogging/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.NetworkLogging; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7919046240CFA86002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7919047240CFA86002011CA /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = NetworkLogging/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.NetworkLogging; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7919001240CF76A002011CA /* Build configuration list for PBXProject "NetworkLogging" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A791900D240CF76A002011CA /* DebugAppStore */, + A7919044240CFA76002011CA /* HockeyappMacAlpha */, + A7919042240CFA70002011CA /* DebugHockeyapp */, + A791900E240CF76A002011CA /* ReleaseAppStore */, + A7919046240CFA86002011CA /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; + A791900F240CF76A002011CA /* Build configuration list for PBXNativeTarget "NetworkLogging" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7919010240CF76A002011CA /* DebugAppStore */, + A7919045240CFA76002011CA /* HockeyappMacAlpha */, + A7919043240CFA70002011CA /* DebugHockeyapp */, + A7919011240CF76A002011CA /* ReleaseAppStore */, + A7919047240CFA86002011CA /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7918FFE240CF76A002011CA /* Project object */; +} diff --git a/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..57d5f7e94e --- /dev/null +++ b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/NetworkLogging/NetworkLogging.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/NetworkLogging/NetworkLogging/Info.plist b/core-xprojects/NetworkLogging/NetworkLogging/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/NetworkLogging/NetworkLogging/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/NetworkLogging/NetworkLogging/NetworkLoggingPublic.h b/core-xprojects/NetworkLogging/NetworkLogging/NetworkLoggingPublic.h new file mode 100644 index 0000000000..efa455195b --- /dev/null +++ b/core-xprojects/NetworkLogging/NetworkLogging/NetworkLoggingPublic.h @@ -0,0 +1,20 @@ +// +// NetworkLogging.h +// NetworkLogging +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for NetworkLogging. +FOUNDATION_EXPORT double NetworkLoggingVersionNumber; + +//! Project version string for NetworkLogging. +FOUNDATION_EXPORT const unsigned char NetworkLoggingVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/Info.plist b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/OpenSSLEncryption.h b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/OpenSSLEncryption.h new file mode 100644 index 0000000000..91d79d32b6 --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption/OpenSSLEncryption.h @@ -0,0 +1,20 @@ +// +// OpenSSLEncryption.h +// OpenSSLEncryption +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for OpenSSLEncryption. +FOUNDATION_EXPORT double OpenSSLEncryptionVersionNumber; + +//! Project version string for OpenSSLEncryption. +FOUNDATION_EXPORT const unsigned char OpenSSLEncryptionVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.pbxproj b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..92916de8eb --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,732 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A790A2DD236AFCEF000451B5 /* OpenSSLEncryption.h in Headers */ = {isa = PBXBuildFile; fileRef = A790A2CF236AFCEF000451B5 /* OpenSSLEncryption.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A790A2E9236AFE3D000451B5 /* OpenSSLEncryptionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A790A2E7236AFE3D000451B5 /* OpenSSLEncryptionProvider.m */; }; + A790A2ED236AFE7C000451B5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A2EC236AFE7C000451B5 /* Foundation.framework */; }; + A790A302236B027F000451B5 /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A301236B027F000451B5 /* libcrypto.a */; }; + A790A30F236B097D000451B5 /* EncryptionProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A2FF236B0188000451B5 /* EncryptionProvider.framework */; }; + A791912C240CFDB6002011CA /* OpenSSLEncryptionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = A791912B240CFDB6002011CA /* OpenSSLEncryptionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A790A2CC236AFCEF000451B5 /* OpenSSLEncryption.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OpenSSLEncryption.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A2CF236AFCEF000451B5 /* OpenSSLEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpenSSLEncryption.h; sourceTree = ""; }; + A790A2D0236AFCEF000451B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A790A2E7236AFE3D000451B5 /* OpenSSLEncryptionProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpenSSLEncryptionProvider.m; sourceTree = ""; }; + A790A2EC236AFE7C000451B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + A790A2FF236B0188000451B5 /* EncryptionProvider.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = EncryptionProvider.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A301236B027F000451B5 /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../../thrid-party/openssl/lib/libcrypto.a"; sourceTree = ""; }; + A791912B240CFDB6002011CA /* OpenSSLEncryptionProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OpenSSLEncryptionProvider.h; path = ../PublicHeaders/OpenSSLEncryptionProvider/OpenSSLEncryptionProvider.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A790A2C9236AFCEF000451B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A30F236B097D000451B5 /* EncryptionProvider.framework in Frameworks */, + A790A302236B027F000451B5 /* libcrypto.a in Frameworks */, + A790A2ED236AFE7C000451B5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A790A2C2236AFCEF000451B5 = { + isa = PBXGroup; + children = ( + A791912E240CFE50002011CA /* Headers */, + A790A2E6236AFDFB000451B5 /* Sources */, + A790A2CE236AFCEF000451B5 /* OpenSSLEncryption */, + A790A2CD236AFCEF000451B5 /* Products */, + A790A2EB236AFE7C000451B5 /* Frameworks */, + ); + sourceTree = ""; + }; + A790A2CD236AFCEF000451B5 /* Products */ = { + isa = PBXGroup; + children = ( + A790A2CC236AFCEF000451B5 /* OpenSSLEncryption.framework */, + ); + name = Products; + sourceTree = ""; + }; + A790A2CE236AFCEF000451B5 /* OpenSSLEncryption */ = { + isa = PBXGroup; + children = ( + A790A2CF236AFCEF000451B5 /* OpenSSLEncryption.h */, + A790A2D0236AFCEF000451B5 /* Info.plist */, + ); + path = OpenSSLEncryption; + sourceTree = ""; + }; + A790A2E6236AFDFB000451B5 /* Sources */ = { + isa = PBXGroup; + children = ( + A791912B240CFDB6002011CA /* OpenSSLEncryptionProvider.h */, + A790A2E7236AFE3D000451B5 /* OpenSSLEncryptionProvider.m */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/Sources"; + sourceTree = ""; + }; + A790A2EB236AFE7C000451B5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A790A301236B027F000451B5 /* libcrypto.a */, + A790A2FF236B0188000451B5 /* EncryptionProvider.framework */, + A790A2EC236AFE7C000451B5 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A791912E240CFE50002011CA /* Headers */ = { + isa = PBXGroup; + children = ( + ); + path = Headers; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A790A2C7236AFCEF000451B5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A2DD236AFCEF000451B5 /* OpenSSLEncryption.h in Headers */, + A791912C240CFDB6002011CA /* OpenSSLEncryptionProvider.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A790A2CB236AFCEF000451B5 /* OpenSSLEncryption */ = { + isa = PBXNativeTarget; + buildConfigurationList = A790A2E0236AFCEF000451B5 /* Build configuration list for PBXNativeTarget "OpenSSLEncryption" */; + buildPhases = ( + A790A2C7236AFCEF000451B5 /* Headers */, + A790A2C8236AFCEF000451B5 /* Sources */, + A790A2C9236AFCEF000451B5 /* Frameworks */, + A790A2CA236AFCEF000451B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OpenSSLEncryption; + productName = OpenSSLEncryption; + productReference = A790A2CC236AFCEF000451B5 /* OpenSSLEncryption.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A790A2C3236AFCEF000451B5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A790A2CB236AFCEF000451B5 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A790A2C6236AFCEF000451B5 /* Build configuration list for PBXProject "OpenSSLEncryption_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A790A2C2236AFCEF000451B5; + productRefGroup = A790A2CD236AFCEF000451B5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A790A2CB236AFCEF000451B5 /* OpenSSLEncryption */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A790A2CA236AFCEF000451B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A790A2C8236AFCEF000451B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A2E9236AFE3D000451B5 /* OpenSSLEncryptionProvider.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A790A2F1236B00F1000451B5 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A790A2F2236B00F1000451B5 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A790A2F3236B00F8000451B5 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A790A2F4236B00F8000451B5 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A790A2F5236B00FD000451B5 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A790A2F6236B00FD000451B5 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A790A2F9236B010A000451B5 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A790A2FA236B010A000451B5 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A790A2FB236B0110000451B5 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A790A2FC236B0110000451B5 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A7F282CE238EAAFD00742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282CF238EAAFD00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = ( + "$(PROJECT_DIR)/../../thrid-party/openssl/", + "$(PROJECT_DIR)/../../submodules/telegram-ios/submodules/OpenSSLEncryptionProvider/PublicHeaders", + ); + INFOPLIST_FILE = OpenSSLEncryption/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../thrid-party/openssl/lib"; + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.OpenSSLEncryption; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A790A2C6236AFCEF000451B5 /* Build configuration list for PBXProject "OpenSSLEncryption_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A2F1236B00F1000451B5 /* DebugHockeyapp */, + A790A2F3236B00F8000451B5 /* DebugAppStore */, + A7F282CE238EAAFD00742C20 /* Github */, + A790A2F5236B00FD000451B5 /* HockeyappMacAlpha */, + A790A2F9236B010A000451B5 /* ReleaseAppStore */, + A790A2FB236B0110000451B5 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + A790A2E0236AFCEF000451B5 /* Build configuration list for PBXNativeTarget "OpenSSLEncryption" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A2F2236B00F1000451B5 /* DebugHockeyapp */, + A790A2F4236B00F8000451B5 /* DebugAppStore */, + A7F282CF238EAAFD00742C20 /* Github */, + A790A2F6236B00FD000451B5 /* HockeyappMacAlpha */, + A790A2FA236B010A000451B5 /* ReleaseAppStore */, + A790A2FC236B0110000451B5 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A790A2C3236AFCEF000451B5 /* Project object */; +} diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..20bdb16ff2 --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..676152aac0 Binary files /dev/null and b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/OpenSSLEncryption.xcscheme b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/OpenSSLEncryption.xcscheme new file mode 100644 index 0000000000..0a27458bf4 --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/OpenSSLEncryption.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..5ec3213f29 --- /dev/null +++ b/core-xprojects/OpenSSLEncryption/OpenSSLEncryption_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + OpenSSLEncryption.xcscheme + + isShown + + orderHint + 26 + + + SuppressBuildableAutocreation + + A790A2CB236AFCEF000451B5 + + primary + + + + + diff --git a/core-xprojects/Postbox/HistoryTagInfoView.swift b/core-xprojects/Postbox/HistoryTagInfoView.swift new file mode 100644 index 0000000000..4b145600b2 --- /dev/null +++ b/core-xprojects/Postbox/HistoryTagInfoView.swift @@ -0,0 +1,76 @@ +import Foundation + +final class MutableHistoryTagInfoView: MutablePostboxView { + fileprivate let peerId: PeerId + fileprivate let tag: MessageTags + + fileprivate var currentIndex: MessageIndex? + + init(postbox: Postbox, peerId: PeerId, tag: MessageTags) { + self.peerId = peerId + self.tag = tag + for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: self.peerId) { + if let index = postbox.messageHistoryTagsTable.latestIndex(tag: self.tag, peerId: self.peerId, namespace: namespace) { + self.currentIndex = index + break + } + } + } + + func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { + if let operations = transaction.currentOperationsByPeerId[self.peerId] { + var updated = false + var refresh = false + for operation in operations { + switch operation { + case let .InsertMessage(message): + if self.currentIndex == nil { + if message.tags.contains(self.tag) { + self.currentIndex = message.index + updated = true + } + } + case let .Remove(indicesAndTags): + if self.currentIndex != nil { + for (index, tags) in indicesAndTags { + if tags.contains(self.tag) { + if index == self.currentIndex { + self.currentIndex = nil + updated = true + refresh = true + } + } + } + } + default: + break + } + } + + if refresh { + for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: self.peerId) { + if let index = postbox.messageHistoryTagsTable.latestIndex(tag: self.tag, peerId: self.peerId, namespace: namespace) { + self.currentIndex = index + break + } + } + } + + return updated + } else { + return false + } + } + + func immutableView() -> PostboxView { + return HistoryTagInfoView(self) + } +} + +public final class HistoryTagInfoView: PostboxView { + public let isEmpty: Bool + + init(_ view: MutableHistoryTagInfoView) { + self.isEmpty = view.currentIndex == nil + } +} diff --git a/TGUIKit/TGUIKit/Info.plist b/core-xprojects/Postbox/Postbox/Info.plist similarity index 100% rename from TGUIKit/TGUIKit/Info.plist rename to core-xprojects/Postbox/Postbox/Info.plist diff --git a/core-xprojects/Postbox/Postbox/Postbox.h b/core-xprojects/Postbox/Postbox/Postbox.h new file mode 100644 index 0000000000..6fc344bb31 --- /dev/null +++ b/core-xprojects/Postbox/Postbox/Postbox.h @@ -0,0 +1,18 @@ +// +// PostboxMac.h +// PostboxMac +// +// Created by Peter on 9/5/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +#import + +//! Project version number for PostboxMac. +FOUNDATION_EXPORT double PostboxVersionNumber; + +//! Project version string for PostboxMac. +FOUNDATION_EXPORT const unsigned char PostboxVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.pbxproj b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..299b70fb6c --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1602 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9F291C9B2264CFCC00C66267 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 9F5B6E762011449F00C58B2A /* PostboxLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */; }; + A701F925236B3350002ABF81 /* sqlcipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F924236B3350002ABF81 /* sqlcipher.framework */; }; + A701F92C236C1B66002ABF81 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F92B236C1B66002ABF81 /* SwiftSignalKit.framework */; }; + A701F935236C1DC4002ABF81 /* Crc32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F934236C1DC4002ABF81 /* Crc32.framework */; }; + A7377E1923ACAE3A00AD3ADD /* FailedMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7377E1823ACAE3A00AD3ADD /* FailedMessagesView.swift */; }; + A754972423A3A5540091F293 /* MessageHistoryFailedTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A754972323A3A5540091F293 /* MessageHistoryFailedTable.swift */; }; + A767DD4323F2F75800366F76 /* HistoryTagInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767DD4223F2F75800366F76 /* HistoryTagInfoView.swift */; }; + A7918DDF240CEFFA002011CA /* MurMurHash32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7918DDE240CEFFA002011CA /* MurMurHash32.framework */; }; + A7918E69240CF2A3002011CA /* StringTransliteration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7918E68240CF2A3002011CA /* StringTransliteration.framework */; }; + C20EB2A31F7179DC00DD3A57 /* PeerNotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3EBB1F68261B0069BC90 /* PeerNotificationSettingsView.swift */; }; + C25B56FE1F431C3300581D02 /* MessageHistoryTagsSummaryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D070479F1F3CE16500F6A8D4 /* MessageHistoryTagsSummaryTable.swift */; }; + C2A315BC1E2E730400D89000 /* PeerOperationLogTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD23261E194D1C00A7089A /* PeerOperationLogTable.swift */; }; + C2A315BD1E2E732000D89000 /* PeerOperationLogMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD23281E196B6400A7089A /* PeerOperationLogMetadataTable.swift */; }; + C2A315BE1E2E733900D89000 /* PeerMergedOperationLogIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D010B6191E1E463900C3E282 /* PeerMergedOperationLogIndexTable.swift */; }; + C2A315BF1E2E733900D89000 /* PeerMergedOperationLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F019FC1E1DA0CC00F05AB3 /* PeerMergedOperationLogView.swift */; }; + C2AC9C131E1E5D200085C7DE /* UnreadMessageCountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A18D661E16874D004C6734 /* UnreadMessageCountsView.swift */; }; + D000CADB22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */; }; + D001388720BD942B007C9721 /* PostboxUpgrade_16to17.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */; }; + D00C7CD51E365C4E0080C3D5 /* PeerChatListInclusion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD31E365C4E0080C3D5 /* PeerChatListInclusion.swift */; }; + D00C7CE41E37861C0080C3D5 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CE21E37861C0080C3D5 /* MessageView.swift */; }; + D0105D6C218362F2007C04A7 /* TempBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0105D6A218362F2007C04A7 /* TempBox.swift */; }; + D0119CAE20C9E7A100895300 /* PostboxUpgrade_17to18.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0119CAC20C9E7A100895300 /* PostboxUpgrade_17to18.swift */; }; + D015E05D2263BB3B00CB9E8A /* PostboxUpgrade_22to23.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E05B2263BB3B00CB9E8A /* PostboxUpgrade_22to23.swift */; }; + D015E0612265D42400CB9E8A /* GroupMessageStatsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E05F2265D42400CB9E8A /* GroupMessageStatsTable.swift */; }; + D018BE56218B9AA900C02DDC /* TimeBasedCleanup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018BE54218B9AA900C02DDC /* TimeBasedCleanup.swift */; }; + D01BAA561ED1D70C00295217 /* ManagedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01BAA541ED1D70C00295217 /* ManagedFile.swift */; }; + D01C7EDF1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */; }; + D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */; }; + D01E79E92248F661005237FE /* PostboxUpgrade_19to20.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E79E72248F661005237FE /* PostboxUpgrade_19to20.swift */; }; + D021FC272024B83700C34AB7 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021FC252024B83700C34AB7 /* FileSize.swift */; }; + D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */; }; + D03387532242E32B007A2CE4 /* PostboxUpgrade_18to19.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03387512242E32A007A2CE4 /* PostboxUpgrade_18to19.swift */; }; + D037178C20D923CA004773C8 /* CachedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D037178A20D923CA004773C8 /* CachedItemView.swift */; }; + D039FB1C21714D9800BD1BAD /* PeerPresencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D039FB1A21714D9800BD1BAD /* PeerPresencesView.swift */; }; + D040CA6522665370007123CE /* PostboxUpgrade_23to24.swift in Sources */ = {isa = PBXBuildFile; fileRef = D040CA6322665370007123CE /* PostboxUpgrade_23to24.swift */; }; + D0439B402289F6300067E026 /* AccountManagerAtomicState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0439B3E2289F6300067E026 /* AccountManagerAtomicState.swift */; }; + D04614312004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D046142F2004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift */; }; + D04614342004F2CC00EC0EF2 /* LocalMessageTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */; }; + D048B33E203C838500038D05 /* PostboxUpgrade_15to16.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B33C203C838500038D05 /* PostboxUpgrade_15to16.swift */; }; + D048B4A920A5CBE400C79D31 /* AdditionalChatListItemsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B4A720A5CBE400C79D31 /* AdditionalChatListItemsTable.swift */; }; + D048B4B020A5EEAE00C79D31 /* AdditionalChatListItemsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B4AE20A5EEAE00C79D31 /* AdditionalChatListItemsView.swift */; }; + D049EAF11E44D9B900A2CD3A /* PostboxStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAEF1E44D9B900A2CD3A /* PostboxStateView.swift */; }; + D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CE1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift */; }; + D050F2671E4A5B5A00988324 /* TimestampBasedMessageAttributesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AAD1B11E3266B100D5B9DE /* TimestampBasedMessageAttributesTable.swift */; }; + D050F2681E4A5B5A00988324 /* TimestampBasedMessageAttributesIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AAD1B01E3266B100D5B9DE /* TimestampBasedMessageAttributesIndexTable.swift */; }; + D050F2691E4A5B5A00988324 /* MultiplePeersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386B1E3FCEE50044D6FE /* MultiplePeersView.swift */; }; + D0575AE41E9ECBB2006F2541 /* AccessChallengeDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AE21E9ECBB2006F2541 /* AccessChallengeDataView.swift */; }; + D05D8B32218F1D3D0064586F /* AccountSharedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B30218F1D3D0064586F /* AccountSharedData.swift */; }; + D05D8B35218F1EBB0064586F /* AccountManagerSharedDataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B33218F1EBB0064586F /* AccountManagerSharedDataTable.swift */; }; + D0633D05225B98B1003DD95F /* AdditionalMessageHistoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633D03225B98B1003DD95F /* AdditionalMessageHistoryViewData.swift */; }; + D0633D08225B98FC003DD95F /* MessageHistoryViewEntryAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633D06225B98FC003DD95F /* MessageHistoryViewEntryAttributes.swift */; }; + D0633D0B225BAD66003DD95F /* MessageHistoryAnchorIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633D09225BAD66003DD95F /* MessageHistoryAnchorIndex.swift */; }; + D0633D0E225BC9EF003DD95F /* MessageHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633D0C225BC9EF003DD95F /* MessageHistoryViewState.swift */; }; + D06CA12D227715E70094E707 /* PeerNotificationSettingsBehaviorTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06CA12B227715E70094E707 /* PeerNotificationSettingsBehaviorTable.swift */; }; + D06CA130227720910094E707 /* PeerNotificationSettingsBehaviorIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06CA12E227720910094E707 /* PeerNotificationSettingsBehaviorIndexTable.swift */; }; + D06CA13322772DE40094E707 /* PeerNotificationSettingsBehaviorTimestampView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06CA13122772DE40094E707 /* PeerNotificationSettingsBehaviorTimestampView.swift */; }; + D06ECFC620B796DC00C576C2 /* NoticeEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06ECFC420B796DC00C576C2 /* NoticeEntryView.swift */; }; + D07047A31F3CE58200F6A8D4 /* PendingMessageActionsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047A11F3CE58200F6A8D4 /* PendingMessageActionsTable.swift */; }; + D07047A61F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047A41F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift */; }; + D07047A91F3DA8D700F6A8D4 /* PendingMessageActionsMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047A71F3DA8D700F6A8D4 /* PendingMessageActionsMetadataTable.swift */; }; + D07047AC1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047AA1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift */; }; + D07047B21F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B01F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift */; }; + D073CE741DCBF3B4007511FD /* Peer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7831B28AE0900A402D9 /* Peer.swift */; }; + D073CE751DCBF3B4007511FD /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A79D1B28B50400A402D9 /* Message.swift */; }; + D073CE761DCBF3B4007511FD /* IntermediateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D510F81D63BCC200A97B8A /* IntermediateMessage.swift */; }; + D073CE771DCBF3B4007511FD /* Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7A11B28B7DC00A402D9 /* Media.swift */; }; + D073CE791DCBF3B4007511FD /* ChatListHole.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044CA2D1C618373002160FF /* ChatListHole.swift */; }; + D073CE7A1DCBF3B4007511FD /* PeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C674CB1CBB14A700183765 /* PeerReadState.swift */; }; + D073CE7B1DCBF3B4007511FD /* PeerNameIndexRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0079F6A1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift */; }; + D073CE7C1DCBF3B4007511FD /* CachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F91DA540F0006A2A60 /* CachedPeerData.swift */; }; + D073CE7D1DCBF3B4007511FD /* PeerPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844501DAC04FE005F29E1 /* PeerPresence.swift */; }; + D073CE7E1DCBF3B4007511FD /* PeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */; }; + D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */; }; + D073CE801DCBF3B4007511FD /* PeerChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */; }; + D073CE9F1DCBF3C1007511FD /* InitialMessageHistoryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */; }; + D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */; }; + D079FCE71F06A31C0038FADE /* NoticeTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D079FCE51F06A3170038FADE /* NoticeTable.swift */; }; + D07E7B46224E227100BB053B /* PostboxUpgrade_20to21.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07E7B44224E227100BB053B /* PostboxUpgrade_20to21.swift */; }; + D07E7B49224E562C00BB053B /* PostboxUpgrade_21to22.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07E7B47224E562C00BB053B /* PostboxUpgrade_21to22.swift */; }; + D0830FC62416397A006198E7 /* ChatListViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0830FC52416397A006198E7 /* ChatListViewState.swift */; }; + D08775011E3E3D9F00A97350 /* PreferencesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08774FF1E3E3D9F00A97350 /* PreferencesTable.swift */; }; + D08775041E3E3E7400A97350 /* PreferencesEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08775021E3E3E7400A97350 /* PreferencesEntry.swift */; }; + D08775071E3E3F2100A97350 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08775051E3E3F2100A97350 /* PreferencesView.swift */; }; + D087C20D1E43C11C00D686F8 /* OrderedItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087C20B1E43C11C00D686F8 /* OrderedItemListView.swift */; }; + D08B61C122A75C21000A46A8 /* Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B61BF22A75C21000A46A8 /* Hash.swift */; }; + D0943AF91FDAC540001522CC /* ChatLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943AF71FDAC53F001522CC /* ChatLocation.swift */; }; + D0943B031FDB01D8001522CC /* PostboxUpgrade_14to15.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0943B011FDB01D8001522CC /* PostboxUpgrade_14to15.swift */; }; + D098C6F21D7E1201007784E4 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098C6F01D7E11E9007784E4 /* Database.swift */; }; + D0A352F61F5488E9001423DC /* InvalidatedMessageHistoryTagsSummaryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A352F41F5488E9001423DC /* InvalidatedMessageHistoryTagsSummaryTable.swift */; }; + D0A352F91F549D95001423DC /* InvalidatedMessageHistoryTagSummariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A352F71F549D95001423DC /* InvalidatedMessageHistoryTagSummariesView.swift */; }; + D0AA55141FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */; }; + D0AAD1B61E32673C00D5B9DE /* TimestampBasedMessageAttributesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AAD1B41E32673C00D5B9DE /* TimestampBasedMessageAttributesView.swift */; }; + D0B166EB1F9D142A00976B40 /* MessageOfInterestHolesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B166E91F9D142A00976B40 /* MessageOfInterestHolesView.swift */; }; + D0B167211F9EAAA900976B40 /* OrderedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1671F1F9EAAA900976B40 /* OrderedList.swift */; }; + D0B2F75E204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F75C204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift */; }; + D0B36A5D23E173CF00745212 /* AllChatListHolesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B36A5C23E173CF00745212 /* AllChatListHolesView.swift */; }; + D0B418171D7DFAF3004562A4 /* Postbox.h in Headers */ = {isa = PBXBuildFile; fileRef = D0B418151D7DFAF3004562A4 /* Postbox.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0B418221D7DFE0C004562A4 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7871B28AE9C00A402D9 /* Coding.swift */; }; + D0B418231D7DFE0C004562A4 /* SimpleDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E8741C5A334100037222 /* SimpleDictionary.swift */; }; + D0B418241D7DFE0C004562A4 /* SimpleSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9DA381C65782500855278 /* SimpleSet.swift */; }; + D0B418251D7DFE0C004562A4 /* RedBlackTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CEFB31D2AD8BE0015D3BC /* RedBlackTree.swift */; }; + D0B418261D7DFE0C004562A4 /* MappedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D949F41D35353900740E02 /* MappedFile.swift */; }; + D0B418271D7DFE0C004562A4 /* IpcPipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D510FF1D64A58900A97B8A /* IpcPipe.swift */; }; + D0B418301D7DFE16004562A4 /* ValueBoxKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */; }; + D0B418311D7DFE16004562A4 /* ValueBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9B1B822DB4009994B2 /* ValueBox.swift */; }; + D0B418321D7DFE16004562A4 /* SqliteValueBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */; }; + D0B418481D7DFE20004562A4 /* MessageHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */; }; + D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E86A1C59719800037222 /* ChatListView.swift */; }; + D0B4184A1D7DFE20004562A4 /* UnsentMessageHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */; }; + D0B4184B1D7DFE20004562A4 /* SynchronizePeerReadStatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */; }; + D0B4184C1D7DFE20004562A4 /* ContactPeerIdsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0079F621D5A242500A27A2C /* ContactPeerIdsView.swift */; }; + D0B4184D1D7DFE20004562A4 /* ContactPeersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0079F641D5A457A00A27A2C /* ContactPeersView.swift */; }; + D0B4184E1D7DFE20004562A4 /* MessageHistoryHolesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B8B1D65D488002C78E7 /* MessageHistoryHolesView.swift */; }; + D0B4184F1D7DFE20004562A4 /* ChatListHolesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B8D1D65D49C002C78E7 /* ChatListHolesView.swift */; }; + D0B418501D7DFE20004562A4 /* UnsentMessageIndicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B8F1D65D4AB002C78E7 /* UnsentMessageIndicesView.swift */; }; + D0B418571D7DFE29004562A4 /* Postbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E3A7811B28ADD000A402D9 /* Postbox.swift */; }; + D0B418581D7DFE29004562A4 /* SeedConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044CA291C617D39002160FF /* SeedConfiguration.swift */; }; + D0B418591D7DFE29004562A4 /* PostboxTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D510F31D63BA8400A97B8A /* PostboxTransaction.swift */; }; + D0B4185A1D7DFE29004562A4 /* ViewTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */; }; + D0B844031DAB91A7005F29E1 /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C8E1D81A350008AEB01 /* PeerView.swift */; }; + D0B844051DAB91B5005F29E1 /* MediaBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D055BD321B7D3D2D00F06C0A /* MediaBox.swift */; }; + D0B844061DAB91B5005F29E1 /* MediaResourceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */; }; + D0B844071DAB91B5005F29E1 /* MediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */; }; + D0BC38731E409E670044D6FE /* RenderedPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38711E409E670044D6FE /* RenderedPeer.swift */; }; + D0BE3035206026C800FBE6D8 /* MessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE3033206026C800FBE6D8 /* MessagesView.swift */; }; + D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE38381E7C1FD4000079AF /* ItemCollectionInfoView.swift */; }; + D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF621E54B2FA00BD963D /* AccountManager.swift */; }; + D0BEAF681E54B33900BD963D /* AccountRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF661E54B33900BD963D /* AccountRecord.swift */; }; + D0BEAF6B1E54B5FB00BD963D /* AccountManagerMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF691E54B5FB00BD963D /* AccountManagerMetadataTable.swift */; }; + D0BEAF6E1E54B77900BD963D /* AccountManagerRecordTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF6C1E54B77900BD963D /* AccountManagerRecordTable.swift */; }; + D0BEAF711E54BC1E00BD963D /* AccountRecordsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */; }; + D0BFE51E22AFD5AF00143D08 /* MutableBasicPeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BFE51C22AFD5AF00143D08 /* MutableBasicPeerView.swift */; }; + D0C0B5AC1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */; }; + D0C26D7F1FE3FA4E004ABF18 /* PinnedItemId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D7D1FE3FA4E004ABF18 /* PinnedItemId.swift */; }; + D0C27B461F4B598200A4E170 /* CachedPeerDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B441F4B598200A4E170 /* CachedPeerDataView.swift */; }; + D0CA8E4622720064008A74C3 /* InvalidatedGroupMessageStatsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA8E4422720064008A74C3 /* InvalidatedGroupMessageStatsTable.swift */; }; + D0CA8E49227208FE008A74C3 /* SynchronizeGroupMessageStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA8E47227208FE008A74C3 /* SynchronizeGroupMessageStatsView.swift */; }; + D0CA8E4F2272130B008A74C3 /* PostboxUpgrade_24to25.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA8E4D2272130B008A74C3 /* PostboxUpgrade_24to25.swift */; }; + D0CCD6202231CE5400EE1E08 /* PeerGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CCD61E2231CE5100EE1E08 /* PeerGroup.swift */; }; + D0CCD6292232887100EE1E08 /* MessageHistoryHoleIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CCD6272232887100EE1E08 /* MessageHistoryHoleIndexTable.swift */; }; + D0CE8CF41F70249400AA2DB0 /* PostboxUpgrade_13to14.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CF21F70249400AA2DB0 /* PostboxUpgrade_13to14.swift */; }; + D0CE8CF71F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CF51F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift */; }; + D0DA1D301F70419D0034E892 /* PendingPeerNotificationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA1D2E1F70419D0034E892 /* PendingPeerNotificationSettingsView.swift */; }; + D0DA44491E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */; }; + D0E119A1229834BC008CAE3A /* MutablePeerChatInclusionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1199F229834BC008CAE3A /* MutablePeerChatInclusionView.swift */; }; + D0E1D30D1ECA1F5500FCEEF1 /* GlobalMessageHistoryTagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1D30B1ECA1F5500FCEEF1 /* GlobalMessageHistoryTagsTable.swift */; }; + D0E1D3101ECA53F900FCEEF1 /* GlobalMessageTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E1D30E1ECA53F900FCEEF1 /* GlobalMessageTagsView.swift */; }; + D0E23DE31E808A9400B9B6D2 /* ItemCollectionIdsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E23DE11E808A9400B9B6D2 /* ItemCollectionIdsView.swift */; }; + D0F02CE01E99223E0065DEE2 /* Upgrades.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CDE1E99223D0065DEE2 /* Upgrades.swift */; }; + D0F02CE31E9922F50065DEE2 /* PostboxUpgrade_12to13.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE11E9922F50065DEE2 /* PostboxUpgrade_12to13.swift */; }; + D0F53BF41E794C6700117362 /* PeerChatStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F53BF21E794C6700117362 /* PeerChatStateView.swift */; }; + D0F7B1C01E045C62007EB8A5 /* StringIndexTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C01E0079CB00071108 /* StringIndexTokens.swift */; }; + D0F7B1C31E045C6A007EB8A5 /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCF71C73561C0097A291 /* Table.swift */; }; + D0F7B1C41E045C6A007EB8A5 /* GlobalMessageIdsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E8721C5A1EE500037222 /* GlobalMessageIdsTable.swift */; }; + D0F7B1C51E045C6A007EB8A5 /* MessageHistoryMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044CA2B1C617E2D002160FF /* MessageHistoryMetadataTable.swift */; }; + D0F7B1C61E045C6A007EB8A5 /* MessageHistoryIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C713B1C51283C00779C0F /* MessageHistoryIndexTable.swift */; }; + D0F7B1C71E045C6A007EB8A5 /* MessageHistoryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C713D1C512EA500779C0F /* MessageHistoryTable.swift */; }; + D0F7B1C81E045C6A007EB8A5 /* MessageHistoryOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D510F51D63BBE100A97B8A /* MessageHistoryOperation.swift */; }; + D0F7B1C91E045C6A007EB8A5 /* MessageHistoryTagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00EED1D1C81F28D00341DFF /* MessageHistoryTagsTable.swift */; }; + D0F7B1CA1E045C6A007EB8A5 /* MessageHistoryUnsentTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */; }; + D0F7B1CB1E045C6A007EB8A5 /* ChatListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E8641C58CB7F00037222 /* ChatListTable.swift */; }; + D0F7B1CC1E045C6A007EB8A5 /* ChatListIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E8661C58D08900037222 /* ChatListIndexTable.swift */; }; + D0F7B1CD1E045C6A007EB8A5 /* MessageMediaTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E85A1C565EBB00037222 /* MessageMediaTable.swift */; }; + D0F7B1CF1E045C6A007EB8A5 /* MetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E86C1C5A0E5D00037222 /* MetadataTable.swift */; }; + D0F7B1D01E045C6A007EB8A5 /* KeychainTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E86E1C5A0E7600037222 /* KeychainTable.swift */; }; + D0F7B1D11E045C6A007EB8A5 /* PeerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F9E8701C5A0E9B00037222 /* PeerTable.swift */; }; + D0F7B1D21E045C6A007EB8A5 /* PeerNotificationSettingsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */; }; + D0F7B1D31E045C6A007EB8A5 /* CachedPeerDataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */; }; + D0F7B1D41E045C6A007EB8A5 /* PeerPresenceTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */; }; + D0F7B1D51E045C6A007EB8A5 /* PeerChatStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */; }; + D0F7B1D61E045C6A007EB8A5 /* ContactTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0079F591D592E8B00A27A2C /* ContactTable.swift */; }; + D0F7B1D71E045C6A007EB8A5 /* MessageHistoryReadStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */; }; + D0F7B1D81E045C6A007EB8A5 /* MessageHistorySynchronizeReadStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F7D9A1CBEC390008765C9 /* MessageHistorySynchronizeReadStateTable.swift */; }; + D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09ADF0B1D2EB83500C8208D /* OrderStatisticTable.swift */; }; + D0F7B1DA1E045C6A007EB8A5 /* RatingTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08D451E1D5D2CA700A7428A /* RatingTable.swift */; }; + D0F7B1DB1E045C6A007EB8A5 /* ItemCollectionInfoTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */; }; + D0F7B1DC1E045C6A007EB8A5 /* ItemCollectionItemTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */; }; + D0F7B1DD1E045C6A007EB8A5 /* PeerChatInterfaceStateTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */; }; + D0F7B1DE1E045C6A007EB8A5 /* PeerChatTopIndexableMessageIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB311DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift */; }; + D0F7B1DF1E045C6A007EB8A5 /* ItemCacheMetaTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC731DDE1EB9008148FA /* ItemCacheMetaTable.swift */; }; + D0F7B1E01E045C6A007EB8A5 /* ItemCacheTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC711DDE1CDC008148FA /* ItemCacheTable.swift */; }; + D0F7B1E11E045C6A007EB8A5 /* ReverseIndexReferenceTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C21E008F7300071108 /* ReverseIndexReferenceTable.swift */; }; + D0F7B1E21E045C6A007EB8A5 /* PeerNameIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C41E00B23F00071108 /* PeerNameIndexTable.swift */; }; + D0F82CFE1E4345D7007E499C /* OrderedItemListTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F82CFC1E4345D7007E499C /* OrderedItemListTable.swift */; }; + D0F82D0D1E439FCC007E499C /* OrderedItemListEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F82D0B1E439FCC007E499C /* OrderedItemListEntry.swift */; }; + D0F82D101E43A024007E499C /* OrderedItemListIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F82D0E1E43A024007E499C /* OrderedItemListIndexTable.swift */; }; + D0FA0AC81E77F0A2005BB9B7 /* ItemCollectionInfosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */; }; + D0FA0ACB1E780A26005BB9B7 /* PostboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */; }; + D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; }; + D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A701F8ED236B2E54002ABF81 /* crc32mac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = crc32mac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F8EF236B2E5A002ABF81 /* libphonenumbermac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumbermac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F8F1236B2E61002ABF81 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F924236B3350002ABF81 /* sqlcipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = sqlcipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F92B236C1B66002ABF81 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F92D236C1D02002ABF81 /* crc32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = crc32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F934236C1DC4002ABF81 /* Crc32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Crc32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A72D451C236AF37F0052FA81 /* Postbox.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Postbox.xcconfig; path = "../../submodules/telegram-ios/submodules/Postbox/Sources/Config.xcconfig/Postbox.xcconfig"; sourceTree = ""; }; + A7377E1823ACAE3A00AD3ADD /* FailedMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessagesView.swift; sourceTree = ""; }; + A754972323A3A5540091F293 /* MessageHistoryFailedTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryFailedTable.swift; sourceTree = ""; }; + A767DD4223F2F75800366F76 /* HistoryTagInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryTagInfoView.swift; sourceTree = ""; }; + A7918DDE240CEFFA002011CA /* MurMurHash32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MurMurHash32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918E68240CF2A3002011CA /* StringTransliteration.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StringTransliteration.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccountMediaManager.swift; sourceTree = ""; }; + D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_16to17.swift; sourceTree = ""; }; + D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryView.swift; sourceTree = ""; }; + D0079F591D592E8B00A27A2C /* ContactTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactTable.swift; sourceTree = ""; }; + D0079F621D5A242500A27A2C /* ContactPeerIdsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactPeerIdsView.swift; sourceTree = ""; }; + D0079F641D5A457A00A27A2C /* ContactPeersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactPeersView.swift; sourceTree = ""; }; + D0079F6A1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNameIndexRepresentation.swift; sourceTree = ""; }; + D00C7CD31E365C4E0080C3D5 /* PeerChatListInclusion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatListInclusion.swift; sourceTree = ""; }; + D00C7CE21E37861C0080C3D5 /* MessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; + D00EED1D1C81F28D00341DFF /* MessageHistoryTagsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTagsTable.swift; sourceTree = ""; }; + D0105D6A218362F2007C04A7 /* TempBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempBox.swift; sourceTree = ""; }; + D010B6191E1E463900C3E282 /* PeerMergedOperationLogIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMergedOperationLogIndexTable.swift; sourceTree = ""; }; + D0119CAC20C9E7A100895300 /* PostboxUpgrade_17to18.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_17to18.swift; sourceTree = ""; }; + D015E05B2263BB3B00CB9E8A /* PostboxUpgrade_22to23.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_22to23.swift; sourceTree = ""; }; + D015E05F2265D42400CB9E8A /* GroupMessageStatsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMessageStatsTable.swift; sourceTree = ""; }; + D018BE54218B9AA900C02DDC /* TimeBasedCleanup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeBasedCleanup.swift; sourceTree = ""; }; + D019B1CE1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageGloballyUniqueIdTable.swift; sourceTree = ""; }; + D01BAA541ED1D70C00295217 /* ManagedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedFile.swift; sourceTree = ""; }; + D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTextIndexTable.swift; sourceTree = ""; }; + D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnorderedItemListTable.swift; sourceTree = ""; }; + D01E79E72248F661005237FE /* PostboxUpgrade_19to20.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_19to20.swift; sourceTree = ""; }; + D01F7D9A1CBEC390008765C9 /* MessageHistorySynchronizeReadStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistorySynchronizeReadStateTable.swift; sourceTree = ""; }; + D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePeerReadStatesView.swift; sourceTree = ""; }; + D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollection.swift; sourceTree = ""; }; + D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfoTable.swift; sourceTree = ""; }; + D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionItemTable.swift; sourceTree = ""; }; + D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionsView.swift; sourceTree = ""; }; + D021FC252024B83700C34AB7 /* FileSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSize.swift; sourceTree = ""; }; + D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceTable.swift; sourceTree = ""; }; + D03120F91DA540F0006A2A60 /* CachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerData.swift; sourceTree = ""; }; + D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettings.swift; sourceTree = ""; }; + D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerDataTable.swift; sourceTree = ""; }; + D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsTable.swift; sourceTree = ""; }; + D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteInterface.swift; sourceTree = ""; }; + D03387512242E32A007A2CE4 /* PostboxUpgrade_18to19.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_18to19.swift; sourceTree = ""; }; + D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryUnsentTable.swift; sourceTree = ""; }; + D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsentMessageHistoryView.swift; sourceTree = ""; }; + D037178A20D923CA004773C8 /* CachedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedItemView.swift; sourceTree = ""; }; + D039FB1A21714D9800BD1BAD /* PeerPresencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerPresencesView.swift; sourceTree = ""; }; + D03BCCF71C73561C0097A291 /* Table.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = ""; }; + D03E45562305C7C90049C28B /* sqlcipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = sqlcipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E457A2305CD000049C28B /* Crc32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Crc32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E457C2305CD090049C28B /* Crc32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Crc32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E457E2305CD130049C28B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D03E462B2306E01C0049C28B /* sqlciphermac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = sqlciphermac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E463C2306E0F60049C28B /* crc32mac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = crc32mac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D040CA6322665370007123CE /* PostboxUpgrade_23to24.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_23to24.swift; sourceTree = ""; }; + D0439B3E2289F6300067E026 /* AccountManagerAtomicState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerAtomicState.swift; sourceTree = ""; }; + D044CA291C617D39002160FF /* SeedConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedConfiguration.swift; sourceTree = ""; }; + D044CA2B1C617E2D002160FF /* MessageHistoryMetadataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryMetadataTable.swift; sourceTree = ""; }; + D044CA2D1C618373002160FF /* ChatListHole.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListHole.swift; sourceTree = ""; }; + D044E1611B2AD667001EE087 /* MurMurHash32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MurMurHash32.h; sourceTree = ""; }; + D044E1621B2AD677001EE087 /* MurMurHash32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MurMurHash32.m; sourceTree = ""; }; + D046142F2004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMessageHistoryTagsTable.swift; sourceTree = ""; }; + D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMessageTagsView.swift; sourceTree = ""; }; + D048B33C203C838500038D05 /* PostboxUpgrade_15to16.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_15to16.swift; sourceTree = ""; }; + D048B4A720A5CBE400C79D31 /* AdditionalChatListItemsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalChatListItemsTable.swift; sourceTree = ""; }; + D048B4AE20A5EEAE00C79D31 /* AdditionalChatListItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalChatListItemsView.swift; sourceTree = ""; }; + D049EAEF1E44D9B900A2CD3A /* PostboxStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxStateView.swift; sourceTree = ""; }; + D055BD321B7D3D2D00F06C0A /* MediaBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaBox.swift; sourceTree = ""; }; + D0575AE21E9ECBB2006F2541 /* AccessChallengeDataView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessChallengeDataView.swift; sourceTree = ""; }; + D05D8B30218F1D3D0064586F /* AccountSharedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSharedData.swift; sourceTree = ""; }; + D05D8B33218F1EBB0064586F /* AccountManagerSharedDataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerSharedDataTable.swift; sourceTree = ""; }; + D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResourceStatus.swift; sourceTree = ""; }; + D0633D03225B98B1003DD95F /* AdditionalMessageHistoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalMessageHistoryViewData.swift; sourceTree = ""; }; + D0633D06225B98FC003DD95F /* MessageHistoryViewEntryAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHistoryViewEntryAttributes.swift; sourceTree = ""; }; + D0633D09225BAD66003DD95F /* MessageHistoryAnchorIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHistoryAnchorIndex.swift; sourceTree = ""; }; + D0633D0C225BC9EF003DD95F /* MessageHistoryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHistoryViewState.swift; sourceTree = ""; }; + D06CA12B227715E70094E707 /* PeerNotificationSettingsBehaviorTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsBehaviorTable.swift; sourceTree = ""; }; + D06CA12E227720910094E707 /* PeerNotificationSettingsBehaviorIndexTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsBehaviorIndexTable.swift; sourceTree = ""; }; + D06CA13122772DE40094E707 /* PeerNotificationSettingsBehaviorTimestampView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsBehaviorTimestampView.swift; sourceTree = ""; }; + D06ECFC420B796DC00C576C2 /* NoticeEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeEntryView.swift; sourceTree = ""; }; + D070479F1F3CE16500F6A8D4 /* MessageHistoryTagsSummaryTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTagsSummaryTable.swift; sourceTree = ""; }; + D07047A11F3CE58200F6A8D4 /* PendingMessageActionsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageActionsTable.swift; sourceTree = ""; }; + D07047A41F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTagSummaryView.swift; sourceTree = ""; }; + D07047A71F3DA8D700F6A8D4 /* PendingMessageActionsMetadataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageActionsMetadataTable.swift; sourceTree = ""; }; + D07047AA1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageActionsView.swift; sourceTree = ""; }; + D07047B01F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageActionsSummaryView.swift; sourceTree = ""; }; + D07827C01E0079CB00071108 /* StringIndexTokens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringIndexTokens.swift; sourceTree = ""; }; + D07827C21E008F7300071108 /* ReverseIndexReferenceTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseIndexReferenceTable.swift; sourceTree = ""; }; + D07827C41E00B23F00071108 /* PeerNameIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNameIndexTable.swift; sourceTree = ""; }; + D079FCE51F06A3170038FADE /* NoticeTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoticeTable.swift; sourceTree = ""; }; + D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatInterfaceState.swift; sourceTree = ""; }; + D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatInterfaceStateTable.swift; sourceTree = ""; }; + D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialMessageHistoryData.swift; sourceTree = ""; }; + D07E7B44224E227100BB053B /* PostboxUpgrade_20to21.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_20to21.swift; sourceTree = ""; }; + D07E7B47224E562C00BB053B /* PostboxUpgrade_21to22.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_21to22.swift; sourceTree = ""; }; + D0830FC52416397A006198E7 /* ChatListViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListViewState.swift; sourceTree = ""; }; + D08774FF1E3E3D9F00A97350 /* PreferencesTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesTable.swift; sourceTree = ""; }; + D08775021E3E3E7400A97350 /* PreferencesEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesEntry.swift; sourceTree = ""; }; + D08775051E3E3F2100A97350 /* PreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; + D087C20B1E43C11C00D686F8 /* OrderedItemListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderedItemListView.swift; sourceTree = ""; }; + D08B61BF22A75C21000A46A8 /* Hash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hash.swift; sourceTree = ""; }; + D08C713B1C51283C00779C0F /* MessageHistoryIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryIndexTable.swift; sourceTree = ""; }; + D08C713D1C512EA500779C0F /* MessageHistoryTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryTable.swift; sourceTree = ""; }; + D08CEFB31D2AD8BE0015D3BC /* RedBlackTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedBlackTree.swift; sourceTree = ""; }; + D08D451E1D5D2CA700A7428A /* RatingTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingTable.swift; sourceTree = ""; }; + D0943AF71FDAC53F001522CC /* ChatLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLocation.swift; sourceTree = ""; }; + D0943B011FDB01D8001522CC /* PostboxUpgrade_14to15.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_14to15.swift; sourceTree = ""; }; + D0977F9B1B822DB4009994B2 /* ValueBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueBox.swift; sourceTree = ""; }; + D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueBoxKey.swift; sourceTree = ""; }; + D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteValueBox.swift; sourceTree = ""; }; + D098C6F01D7E11E9007784E4 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; + D09ADF0B1D2EB83500C8208D /* OrderStatisticTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatisticTable.swift; sourceTree = ""; }; + D0A18D661E16874D004C6734 /* UnreadMessageCountsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadMessageCountsView.swift; sourceTree = ""; }; + D0A352F41F5488E9001423DC /* InvalidatedMessageHistoryTagsSummaryTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidatedMessageHistoryTagsSummaryTable.swift; sourceTree = ""; }; + D0A352F71F549D95001423DC /* InvalidatedMessageHistoryTagSummariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidatedMessageHistoryTagSummariesView.swift; sourceTree = ""; }; + D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinarySearch.swift; sourceTree = ""; }; + D0AAD1B01E3266B100D5B9DE /* TimestampBasedMessageAttributesIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampBasedMessageAttributesIndexTable.swift; sourceTree = ""; }; + D0AAD1B11E3266B100D5B9DE /* TimestampBasedMessageAttributesTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampBasedMessageAttributesTable.swift; sourceTree = ""; }; + D0AAD1B41E32673C00D5B9DE /* TimestampBasedMessageAttributesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampBasedMessageAttributesView.swift; sourceTree = ""; }; + D0AB0B8B1D65D488002C78E7 /* MessageHistoryHolesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryHolesView.swift; sourceTree = ""; }; + D0AB0B8D1D65D49C002C78E7 /* ChatListHolesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListHolesView.swift; sourceTree = ""; }; + D0AB0B8F1D65D4AB002C78E7 /* UnsentMessageIndicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsentMessageIndicesView.swift; sourceTree = ""; }; + D0AD23261E194D1C00A7089A /* PeerOperationLogTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerOperationLogTable.swift; sourceTree = ""; }; + D0AD23281E196B6400A7089A /* PeerOperationLogMetadataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerOperationLogMetadataTable.swift; sourceTree = ""; }; + D0AE3EBB1F68261B0069BC90 /* PeerNotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerNotificationSettingsView.swift; sourceTree = ""; }; + D0B166E91F9D142A00976B40 /* MessageOfInterestHolesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageOfInterestHolesView.swift; sourceTree = ""; }; + D0B1671F1F9EAAA900976B40 /* OrderedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedList.swift; sourceTree = ""; }; + D0B2F75C204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactImportInfoTable.swift; sourceTree = ""; }; + D0B36A5C23E173CF00745212 /* AllChatListHolesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AllChatListHolesView.swift; path = "../../submodules/telegram-ios/submodules/Postbox/Sources/AllChatListHolesView.swift"; sourceTree = ""; }; + D0B418131D7DFAF2004562A4 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B418151D7DFAF3004562A4 /* Postbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Postbox.h; sourceTree = ""; }; + D0B418161D7DFAF3004562A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0B418601D7DFE95004562A4 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKitMac.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug/SwiftSignalKitMac.framework"; sourceTree = ""; }; + D0B844501DAC04FE005F29E1 /* PeerPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresence.swift; sourceTree = ""; }; + D0BC386B1E3FCEE50044D6FE /* MultiplePeersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiplePeersView.swift; sourceTree = ""; }; + D0BC38711E409E670044D6FE /* RenderedPeer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RenderedPeer.swift; sourceTree = ""; }; + D0BE3033206026C800FBE6D8 /* MessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesView.swift; sourceTree = ""; }; + D0BE38381E7C1FD4000079AF /* ItemCollectionInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfoView.swift; sourceTree = ""; }; + D0BEAF621E54B2FA00BD963D /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D0BEAF661E54B33900BD963D /* AccountRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRecord.swift; sourceTree = ""; }; + D0BEAF691E54B5FB00BD963D /* AccountManagerMetadataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManagerMetadataTable.swift; sourceTree = ""; }; + D0BEAF6C1E54B77900BD963D /* AccountManagerRecordTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManagerRecordTable.swift; sourceTree = ""; }; + D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRecordsView.swift; sourceTree = ""; }; + D0BFE51C22AFD5AF00143D08 /* MutableBasicPeerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableBasicPeerView.swift; sourceTree = ""; }; + D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseAssociatedPeerTable.swift; sourceTree = ""; }; + D0C26D7D1FE3FA4E004ABF18 /* PinnedItemId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedItemId.swift; sourceTree = ""; }; + D0C27B441F4B598200A4E170 /* CachedPeerDataView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerDataView.swift; sourceTree = ""; }; + D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryReadStateTable.swift; sourceTree = ""; }; + D0C674CB1CBB14A700183765 /* PeerReadState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerReadState.swift; sourceTree = ""; }; + D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatStateTable.swift; sourceTree = ""; }; + D0C9DA381C65782500855278 /* SimpleSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleSet.swift; sourceTree = ""; }; + D0CA8E4422720064008A74C3 /* InvalidatedGroupMessageStatsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidatedGroupMessageStatsTable.swift; sourceTree = ""; }; + D0CA8E47227208FE008A74C3 /* SynchronizeGroupMessageStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeGroupMessageStatsView.swift; sourceTree = ""; }; + D0CA8E4D2272130B008A74C3 /* PostboxUpgrade_24to25.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_24to25.swift; sourceTree = ""; }; + D0CCD61E2231CE5100EE1E08 /* PeerGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerGroup.swift; sourceTree = ""; }; + D0CCD6272232887100EE1E08 /* MessageHistoryHoleIndexTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHistoryHoleIndexTable.swift; sourceTree = ""; }; + D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResource.swift; sourceTree = ""; }; + D0CE8CF21F70249400AA2DB0 /* PostboxUpgrade_13to14.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_13to14.swift; sourceTree = ""; }; + D0CE8CF51F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingPeerNotificationSettingsIndexTable.swift; sourceTree = ""; }; + D0D510F31D63BA8400A97B8A /* PostboxTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxTransaction.swift; sourceTree = ""; }; + D0D510F51D63BBE100A97B8A /* MessageHistoryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryOperation.swift; sourceTree = ""; }; + D0D510F81D63BCC200A97B8A /* IntermediateMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntermediateMessage.swift; sourceTree = ""; }; + D0D510FF1D64A58900A97B8A /* IpcPipe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IpcPipe.swift; sourceTree = ""; }; + D0D511011D64D73D00A97B8A /* IpcNotifier.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IpcNotifier.mm; sourceTree = ""; }; + D0D511031D64D75200A97B8A /* IpcNotifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IpcNotifier.h; sourceTree = ""; }; + D0D949F41D35353900740E02 /* MappedFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedFile.swift; sourceTree = ""; }; + D0DA1D2E1F70419D0034E892 /* PendingPeerNotificationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingPeerNotificationSettingsView.swift; sourceTree = ""; }; + D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxAccess.swift; sourceTree = ""; }; + D0DF0C8E1D81A350008AEB01 /* PeerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = ""; }; + D0E1199F229834BC008CAE3A /* MutablePeerChatInclusionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutablePeerChatInclusionView.swift; sourceTree = ""; }; + D0E1D30B1ECA1F5500FCEEF1 /* GlobalMessageHistoryTagsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalMessageHistoryTagsTable.swift; sourceTree = ""; }; + D0E1D30E1ECA53F900FCEEF1 /* GlobalMessageTagsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalMessageTagsView.swift; sourceTree = ""; }; + D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewTracker.swift; sourceTree = ""; }; + D0E23DE11E808A9400B9B6D2 /* ItemCollectionIdsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionIdsView.swift; sourceTree = ""; }; + D0E3A74E1B28A7E300A402D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0E3A74F1B28A7E300A402D9 /* Postbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Postbox.h; sourceTree = ""; }; + D0E3A7811B28ADD000A402D9 /* Postbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Postbox.swift; sourceTree = ""; }; + D0E3A7831B28AE0900A402D9 /* Peer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peer.swift; sourceTree = ""; }; + D0E3A7871B28AE9C00A402D9 /* Coding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; + D0E3A79D1B28B50400A402D9 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + D0E3A7A11B28B7DC00A402D9 /* Media.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Media.swift; sourceTree = ""; }; + D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxLogging.swift; sourceTree = ""; }; + D0F019FC1E1DA0CC00F05AB3 /* PeerMergedOperationLogView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMergedOperationLogView.swift; sourceTree = ""; }; + D0F02CDE1E99223D0065DEE2 /* Upgrades.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Upgrades.swift; sourceTree = ""; }; + D0F02CE11E9922F50065DEE2 /* PostboxUpgrade_12to13.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_12to13.swift; sourceTree = ""; }; + D0F3CC711DDE1CDC008148FA /* ItemCacheTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCacheTable.swift; sourceTree = ""; }; + D0F3CC731DDE1EB9008148FA /* ItemCacheMetaTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCacheMetaTable.swift; sourceTree = ""; }; + D0F53BF21E794C6700117362 /* PeerChatStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatStateView.swift; sourceTree = ""; }; + D0F7AB311DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerChatTopIndexableMessageIds.swift; sourceTree = ""; }; + D0F82CFC1E4345D7007E499C /* OrderedItemListTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderedItemListTable.swift; sourceTree = ""; }; + D0F82D0B1E439FCC007E499C /* OrderedItemListEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderedItemListEntry.swift; sourceTree = ""; }; + D0F82D0E1E43A024007E499C /* OrderedItemListIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderedItemListIndexTable.swift; sourceTree = ""; }; + D0F9E85A1C565EBB00037222 /* MessageMediaTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageMediaTable.swift; sourceTree = ""; }; + D0F9E8641C58CB7F00037222 /* ChatListTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListTable.swift; sourceTree = ""; }; + D0F9E8661C58D08900037222 /* ChatListIndexTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListIndexTable.swift; sourceTree = ""; }; + D0F9E86A1C59719800037222 /* ChatListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; + D0F9E86C1C5A0E5D00037222 /* MetadataTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetadataTable.swift; sourceTree = ""; }; + D0F9E86E1C5A0E7600037222 /* KeychainTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainTable.swift; sourceTree = ""; }; + D0F9E8701C5A0E9B00037222 /* PeerTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerTable.swift; sourceTree = ""; }; + D0F9E8721C5A1EE500037222 /* GlobalMessageIdsTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalMessageIdsTable.swift; sourceTree = ""; }; + D0F9E8741C5A334100037222 /* SimpleDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleDictionary.swift; sourceTree = ""; }; + D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfosView.swift; sourceTree = ""; }; + D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxView.swift; sourceTree = ""; }; + D0FA0ACC1E781067005BB9B7 /* Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = ""; }; + D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaBoxFile.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D0B4180F1D7DFAF2004562A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918E69240CF2A3002011CA /* StringTransliteration.framework in Frameworks */, + A7918DDF240CEFFA002011CA /* MurMurHash32.framework in Frameworks */, + A701F935236C1DC4002ABF81 /* Crc32.framework in Frameworks */, + A701F92C236C1B66002ABF81 /* SwiftSignalKit.framework in Frameworks */, + A701F925236B3350002ABF81 /* sqlcipher.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D0105D69218362D9007C04A7 /* Temp Box */ = { + isa = PBXGroup; + children = ( + D0105D6A218362F2007C04A7 /* TempBox.swift */, + ); + name = "Temp Box"; + sourceTree = ""; + }; + D05F09981C9CAC1100BB6F96 /* Media Box */ = { + isa = PBXGroup; + children = ( + D055BD321B7D3D2D00F06C0A /* MediaBox.swift */, + D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */, + D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */, + D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */, + D018BE54218B9AA900C02DDC /* TimeBasedCleanup.swift */, + ); + name = "Media Box"; + sourceTree = ""; + }; + D0633D02225B988E003DD95F /* Peer History */ = { + isa = PBXGroup; + children = ( + D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */, + D0633D03225B98B1003DD95F /* AdditionalMessageHistoryViewData.swift */, + D0633D06225B98FC003DD95F /* MessageHistoryViewEntryAttributes.swift */, + D0633D09225BAD66003DD95F /* MessageHistoryAnchorIndex.swift */, + D0633D0C225BC9EF003DD95F /* MessageHistoryViewState.swift */, + ); + name = "Peer History"; + sourceTree = ""; + }; + D07515FC1B2C44A200AE42E0 /* thirdparty */ = { + isa = PBXGroup; + children = ( + D098C6F01D7E11E9007784E4 /* Database.swift */, + ); + name = thirdparty; + sourceTree = ""; + }; + D0B418141D7DFAF3004562A4 /* Postbox */ = { + isa = PBXGroup; + children = ( + D0B418151D7DFAF3004562A4 /* Postbox.h */, + D0B418161D7DFAF3004562A4 /* Info.plist */, + ); + path = Postbox; + sourceTree = ""; + }; + D0B4185F1D7DFE95004562A4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7918E68240CF2A3002011CA /* StringTransliteration.framework */, + A7918DDE240CEFFA002011CA /* MurMurHash32.framework */, + A701F934236C1DC4002ABF81 /* Crc32.framework */, + A701F92D236C1D02002ABF81 /* crc32.framework */, + A701F92B236C1B66002ABF81 /* SwiftSignalKit.framework */, + A701F924236B3350002ABF81 /* sqlcipher.framework */, + A701F8F1236B2E61002ABF81 /* SwiftSignalKitMac.framework */, + A701F8EF236B2E5A002ABF81 /* libphonenumbermac.framework */, + A701F8ED236B2E54002ABF81 /* crc32mac.framework */, + D03E463C2306E0F60049C28B /* crc32mac.framework */, + D03E462B2306E01C0049C28B /* sqlciphermac.framework */, + D03E457E2305CD130049C28B /* Foundation.framework */, + D03E457C2305CD090049C28B /* Crc32.framework */, + D03E457A2305CD000049C28B /* Crc32.framework */, + D03E45562305C7C90049C28B /* sqlcipher.framework */, + D0B418601D7DFE95004562A4 /* SwiftSignalKitMac.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D0BEAF651E54B32B00BD963D /* Account Manager */ = { + isa = PBXGroup; + children = ( + D0BEAF621E54B2FA00BD963D /* AccountManager.swift */, + D0BEAF661E54B33900BD963D /* AccountRecord.swift */, + D0BEAF691E54B5FB00BD963D /* AccountManagerMetadataTable.swift */, + D0BEAF6C1E54B77900BD963D /* AccountManagerRecordTable.swift */, + D05D8B33218F1EBB0064586F /* AccountManagerSharedDataTable.swift */, + D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */, + D05D8B30218F1D3D0064586F /* AccountSharedData.swift */, + D0575AE21E9ECBB2006F2541 /* AccessChallengeDataView.swift */, + D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */, + D06ECFC420B796DC00C576C2 /* NoticeEntryView.swift */, + D0439B3E2289F6300067E026 /* AccountManagerAtomicState.swift */, + ); + name = "Account Manager"; + sourceTree = ""; + }; + D0DA443F1E4C7834005FDCA7 /* Upgrade */ = { + isa = PBXGroup; + children = ( + D0F02CDE1E99223D0065DEE2 /* Upgrades.swift */, + D0F02CE11E9922F50065DEE2 /* PostboxUpgrade_12to13.swift */, + D0CE8CF21F70249400AA2DB0 /* PostboxUpgrade_13to14.swift */, + D0943B011FDB01D8001522CC /* PostboxUpgrade_14to15.swift */, + D048B33C203C838500038D05 /* PostboxUpgrade_15to16.swift */, + D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */, + D0119CAC20C9E7A100895300 /* PostboxUpgrade_17to18.swift */, + D03387512242E32A007A2CE4 /* PostboxUpgrade_18to19.swift */, + D01E79E72248F661005237FE /* PostboxUpgrade_19to20.swift */, + D07E7B44224E227100BB053B /* PostboxUpgrade_20to21.swift */, + D07E7B47224E562C00BB053B /* PostboxUpgrade_21to22.swift */, + D015E05B2263BB3B00CB9E8A /* PostboxUpgrade_22to23.swift */, + D040CA6322665370007123CE /* PostboxUpgrade_23to24.swift */, + D0CA8E4D2272130B008A74C3 /* PostboxUpgrade_24to25.swift */, + ); + name = Upgrade; + sourceTree = ""; + }; + D0E1DE161C5EB06000C7826E /* Tables */ = { + isa = PBXGroup; + children = ( + A754972323A3A5540091F293 /* MessageHistoryFailedTable.swift */, + D03BCCF71C73561C0097A291 /* Table.swift */, + D019B1CE1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift */, + D0F9E8721C5A1EE500037222 /* GlobalMessageIdsTable.swift */, + D044CA2B1C617E2D002160FF /* MessageHistoryMetadataTable.swift */, + D08C713B1C51283C00779C0F /* MessageHistoryIndexTable.swift */, + D0CCD6272232887100EE1E08 /* MessageHistoryHoleIndexTable.swift */, + D08C713D1C512EA500779C0F /* MessageHistoryTable.swift */, + D0D510F51D63BBE100A97B8A /* MessageHistoryOperation.swift */, + D00EED1D1C81F28D00341DFF /* MessageHistoryTagsTable.swift */, + D070479F1F3CE16500F6A8D4 /* MessageHistoryTagsSummaryTable.swift */, + D0A352F41F5488E9001423DC /* InvalidatedMessageHistoryTagsSummaryTable.swift */, + D01C7EDD1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift */, + D07047A11F3CE58200F6A8D4 /* PendingMessageActionsTable.swift */, + D07047A71F3DA8D700F6A8D4 /* PendingMessageActionsMetadataTable.swift */, + D0E1D30B1ECA1F5500FCEEF1 /* GlobalMessageHistoryTagsTable.swift */, + D033A6F61C73D512006A2EAB /* MessageHistoryUnsentTable.swift */, + D0F9E8641C58CB7F00037222 /* ChatListTable.swift */, + D0F9E8661C58D08900037222 /* ChatListIndexTable.swift */, + D048B4A720A5CBE400C79D31 /* AdditionalChatListItemsTable.swift */, + D0F9E85A1C565EBB00037222 /* MessageMediaTable.swift */, + D0F9E86C1C5A0E5D00037222 /* MetadataTable.swift */, + D0F9E86E1C5A0E7600037222 /* KeychainTable.swift */, + D0F9E8701C5A0E9B00037222 /* PeerTable.swift */, + D03120FF1DA579A0006A2A60 /* PeerNotificationSettingsTable.swift */, + D06CA12B227715E70094E707 /* PeerNotificationSettingsBehaviorTable.swift */, + D06CA12E227720910094E707 /* PeerNotificationSettingsBehaviorIndexTable.swift */, + D0CE8CF51F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift */, + D03120FD1DA562E9006A2A60 /* CachedPeerDataTable.swift */, + D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */, + D0C735271C864DF300BB3149 /* PeerChatStateTable.swift */, + D0079F591D592E8B00A27A2C /* ContactTable.swift */, + D0C674C71CBB11C600183765 /* MessageHistoryReadStateTable.swift */, + D01F7D9A1CBEC390008765C9 /* MessageHistorySynchronizeReadStateTable.swift */, + D0CA8E4422720064008A74C3 /* InvalidatedGroupMessageStatsTable.swift */, + D09ADF0B1D2EB83500C8208D /* OrderStatisticTable.swift */, + D08D451E1D5D2CA700A7428A /* RatingTable.swift */, + D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */, + D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */, + D07CFF821DCA909100761F81 /* PeerChatInterfaceStateTable.swift */, + D0F7AB311DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift */, + D0F3CC731DDE1EB9008148FA /* ItemCacheMetaTable.swift */, + D0F3CC711DDE1CDC008148FA /* ItemCacheTable.swift */, + D07827C21E008F7300071108 /* ReverseIndexReferenceTable.swift */, + D07827C41E00B23F00071108 /* PeerNameIndexTable.swift */, + D0C0B5AA1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift */, + D0AD23261E194D1C00A7089A /* PeerOperationLogTable.swift */, + D010B6191E1E463900C3E282 /* PeerMergedOperationLogIndexTable.swift */, + D0AD23281E196B6400A7089A /* PeerOperationLogMetadataTable.swift */, + D0AAD1B11E3266B100D5B9DE /* TimestampBasedMessageAttributesTable.swift */, + D0AAD1B01E3266B100D5B9DE /* TimestampBasedMessageAttributesIndexTable.swift */, + D08774FF1E3E3D9F00A97350 /* PreferencesTable.swift */, + D0F82CFC1E4345D7007E499C /* OrderedItemListTable.swift */, + D0F82D0E1E43A024007E499C /* OrderedItemListIndexTable.swift */, + D01C7F061EFC1ED3008305F1 /* UnorderedItemListTable.swift */, + D079FCE51F06A3170038FADE /* NoticeTable.swift */, + D046142F2004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift */, + D0B2F75C204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift */, + D015E05F2265D42400CB9E8A /* GroupMessageStatsTable.swift */, + ); + name = Tables; + sourceTree = ""; + }; + D0E1DE171C5EB06B00C7826E /* Objects */ = { + isa = PBXGroup; + children = ( + D0E3A7831B28AE0900A402D9 /* Peer.swift */, + D0E3A79D1B28B50400A402D9 /* Message.swift */, + D0D510F81D63BCC200A97B8A /* IntermediateMessage.swift */, + D0E3A7A11B28B7DC00A402D9 /* Media.swift */, + D044CA2D1C618373002160FF /* ChatListHole.swift */, + D0C674CB1CBB14A700183765 /* PeerReadState.swift */, + D0079F6A1D5B3AAB00A27A2C /* PeerNameIndexRepresentation.swift */, + D03120F91DA540F0006A2A60 /* CachedPeerData.swift */, + D0B844501DAC04FE005F29E1 /* PeerPresence.swift */, + D03120FB1DA55427006A2A60 /* PeerNotificationSettings.swift */, + D021E0D31DB4FAE100C6B04F /* ItemCollection.swift */, + D07CFF801DCA765D00761F81 /* PeerChatInterfaceState.swift */, + D00C7CD31E365C4E0080C3D5 /* PeerChatListInclusion.swift */, + D08775021E3E3E7400A97350 /* PreferencesEntry.swift */, + D0BC38711E409E670044D6FE /* RenderedPeer.swift */, + D0F82D0B1E439FCC007E499C /* OrderedItemListEntry.swift */, + D0943AF71FDAC53F001522CC /* ChatLocation.swift */, + D0C26D7D1FE3FA4E004ABF18 /* PinnedItemId.swift */, + D0CCD61E2231CE5100EE1E08 /* PeerGroup.swift */, + ); + name = Objects; + sourceTree = ""; + }; + D0E1DE181C5EB09300C7826E /* Utils */ = { + isa = PBXGroup; + children = ( + D0E3A7871B28AE9C00A402D9 /* Coding.swift */, + D0F9E8741C5A334100037222 /* SimpleDictionary.swift */, + D0C9DA381C65782500855278 /* SimpleSet.swift */, + D08CEFB31D2AD8BE0015D3BC /* RedBlackTree.swift */, + D0D949F41D35353900740E02 /* MappedFile.swift */, + D0D510FF1D64A58900A97B8A /* IpcPipe.swift */, + D07827C01E0079CB00071108 /* StringIndexTokens.swift */, + D03229ED1E6B33FD0000AF9C /* SqliteInterface.swift */, + D01BAA541ED1D70C00295217 /* ManagedFile.swift */, + D0B1671F1F9EAAA900976B40 /* OrderedList.swift */, + D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */, + D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */, + D021FC252024B83700C34AB7 /* FileSize.swift */, + D08B61BF22A75C21000A46A8 /* Hash.swift */, + ); + name = Utils; + sourceTree = ""; + }; + D0E1DE191C5EB0B500C7826E /* Value Box */ = { + isa = PBXGroup; + children = ( + D0977F9D1B8234DF009994B2 /* ValueBoxKey.swift */, + D0977F9B1B822DB4009994B2 /* ValueBox.swift */, + D0977F9F1B8244D7009994B2 /* SqliteValueBox.swift */, + ); + name = "Value Box"; + sourceTree = ""; + }; + D0E1DE1A1C5EB0DB00C7826E /* Views */ = { + isa = PBXGroup; + children = ( + D0633D02225B988E003DD95F /* Peer History */, + D049EAEF1E44D9B900A2CD3A /* PostboxStateView.swift */, + D07CFF841DCA99C400761F81 /* InitialMessageHistoryData.swift */, + D0F9E86A1C59719800037222 /* ChatListView.swift */, + D033A6F81C73E440006A2EAB /* UnsentMessageHistoryView.swift */, + D01F7D9C1CBF8586008765C9 /* SynchronizePeerReadStatesView.swift */, + D0079F621D5A242500A27A2C /* ContactPeerIdsView.swift */, + D0079F641D5A457A00A27A2C /* ContactPeersView.swift */, + D0AB0B8B1D65D488002C78E7 /* MessageHistoryHolesView.swift */, + D0AB0B8D1D65D49C002C78E7 /* ChatListHolesView.swift */, + D0AB0B8F1D65D4AB002C78E7 /* UnsentMessageIndicesView.swift */, + D0DF0C8E1D81A350008AEB01 /* PeerView.swift */, + D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */, + D0830FC52416397A006198E7 /* ChatListViewState.swift */, + D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */, + D0E23DE11E808A9400B9B6D2 /* ItemCollectionIdsView.swift */, + D0BE38381E7C1FD4000079AF /* ItemCollectionInfoView.swift */, + D0A18D661E16874D004C6734 /* UnreadMessageCountsView.swift */, + D0F019FC1E1DA0CC00F05AB3 /* PeerMergedOperationLogView.swift */, + D0AAD1B41E32673C00D5B9DE /* TimestampBasedMessageAttributesView.swift */, + D00C7CE21E37861C0080C3D5 /* MessageView.swift */, + D08775051E3E3F2100A97350 /* PreferencesView.swift */, + D0BC386B1E3FCEE50044D6FE /* MultiplePeersView.swift */, + D087C20B1E43C11C00D686F8 /* OrderedItemListView.swift */, + D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */, + D0FA0ACC1E781067005BB9B7 /* Views.swift */, + D0F53BF21E794C6700117362 /* PeerChatStateView.swift */, + D0E1D30E1ECA53F900FCEEF1 /* GlobalMessageTagsView.swift */, + D07047A41F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift */, + D07047AA1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift */, + D0A352F71F549D95001423DC /* InvalidatedMessageHistoryTagSummariesView.swift */, + D07047B01F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift */, + D0C27B441F4B598200A4E170 /* CachedPeerDataView.swift */, + D0AE3EBB1F68261B0069BC90 /* PeerNotificationSettingsView.swift */, + D0DA1D2E1F70419D0034E892 /* PendingPeerNotificationSettingsView.swift */, + D0B166E91F9D142A00976B40 /* MessageOfInterestHolesView.swift */, + D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */, + D0BE3033206026C800FBE6D8 /* MessagesView.swift */, + D048B4AE20A5EEAE00C79D31 /* AdditionalChatListItemsView.swift */, + D037178A20D923CA004773C8 /* CachedItemView.swift */, + D039FB1A21714D9800BD1BAD /* PeerPresencesView.swift */, + D0CA8E47227208FE008A74C3 /* SynchronizeGroupMessageStatsView.swift */, + D06CA13122772DE40094E707 /* PeerNotificationSettingsBehaviorTimestampView.swift */, + D0E1199F229834BC008CAE3A /* MutablePeerChatInclusionView.swift */, + D0BFE51C22AFD5AF00143D08 /* MutableBasicPeerView.swift */, + A7377E1823ACAE3A00AD3ADD /* FailedMessagesView.swift */, + ); + name = Views; + sourceTree = ""; + }; + D0E3A7401B28A7E300A402D9 = { + isa = PBXGroup; + children = ( + A767DD4223F2F75800366F76 /* HistoryTagInfoView.swift */, + D0B36A5C23E173CF00745212 /* AllChatListHolesView.swift */, + A72D451C236AF37F0052FA81 /* Postbox.xcconfig */, + D0E3A74C1B28A7E300A402D9 /* Sources */, + D0B418141D7DFAF3004562A4 /* Postbox */, + D0E3A74B1B28A7E300A402D9 /* Products */, + D0B4185F1D7DFE95004562A4 /* Frameworks */, + ); + sourceTree = ""; + }; + D0E3A74B1B28A7E300A402D9 /* Products */ = { + isa = PBXGroup; + children = ( + D0B418131D7DFAF2004562A4 /* Postbox.framework */, + ); + name = Products; + sourceTree = ""; + }; + D0E3A74C1B28A7E300A402D9 /* Sources */ = { + isa = PBXGroup; + children = ( + D0DA443F1E4C7834005FDCA7 /* Upgrade */, + D07515FC1B2C44A200AE42E0 /* thirdparty */, + D0E1DE181C5EB09300C7826E /* Utils */, + D0E1DE171C5EB06B00C7826E /* Objects */, + D0E1DE191C5EB0B500C7826E /* Value Box */, + D0E1DE161C5EB06000C7826E /* Tables */, + D0E1DE1A1C5EB0DB00C7826E /* Views */, + D0105D69218362D9007C04A7 /* Temp Box */, + D05F09981C9CAC1100BB6F96 /* Media Box */, + D0E3A7811B28ADD000A402D9 /* Postbox.swift */, + D0DA44471E4C7D1E005FDCA7 /* PostboxAccess.swift */, + D044CA291C617D39002160FF /* SeedConfiguration.swift */, + D0D510F31D63BA8400A97B8A /* PostboxTransaction.swift */, + D0E1DE141C5E1C6900C7826E /* ViewTracker.swift */, + D0BEAF651E54B32B00BD963D /* Account Manager */, + D0E3A74D1B28A7E300A402D9 /* Supporting Files */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/Postbox/Sources"; + sourceTree = ""; + }; + D0E3A74D1B28A7E300A402D9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D044E1611B2AD667001EE087 /* MurMurHash32.h */, + D044E1621B2AD677001EE087 /* MurMurHash32.m */, + D0E3A74F1B28A7E300A402D9 /* Postbox.h */, + D0E3A74E1B28A7E300A402D9 /* Info.plist */, + D0D511031D64D75200A97B8A /* IpcNotifier.h */, + D0D511011D64D73D00A97B8A /* IpcNotifier.mm */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D0B418101D7DFAF2004562A4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D0B418171D7DFAF3004562A4 /* Postbox.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D0B418121D7DFAF2004562A4 /* Postbox */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0B4181B1D7DFAF3004562A4 /* Build configuration list for PBXNativeTarget "Postbox" */; + buildPhases = ( + D0B4180E1D7DFAF2004562A4 /* Sources */, + D0B4180F1D7DFAF2004562A4 /* Frameworks */, + D0B418101D7DFAF2004562A4 /* Headers */, + D0B418111D7DFAF2004562A4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Postbox; + productName = PostboxMac; + productReference = D0B418131D7DFAF2004562A4 /* Postbox.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D0E3A7411B28A7E300A402D9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftMigration = 0700; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + D0B418121D7DFAF2004562A4 = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = D0E3A7441B28A7E300A402D9 /* Build configuration list for PBXProject "Postbox_Xcode" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = D0E3A7401B28A7E300A402D9; + productRefGroup = D0E3A74B1B28A7E300A402D9 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0B418121D7DFAF2004562A4 /* Postbox */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0B418111D7DFAF2004562A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0B4180E1D7DFAF2004562A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9F291C9B2264CFCC00C66267 /* (null) in Sources */, + D0B36A5D23E173CF00745212 /* AllChatListHolesView.swift in Sources */, + 9F5B6E762011449F00C58B2A /* PostboxLogging.swift in Sources */, + C20EB2A31F7179DC00DD3A57 /* PeerNotificationSettingsView.swift in Sources */, + C25B56FE1F431C3300581D02 /* MessageHistoryTagsSummaryTable.swift in Sources */, + D0633D08225B98FC003DD95F /* MessageHistoryViewEntryAttributes.swift in Sources */, + D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */, + D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */, + D0AA55141FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */, + D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */, + D050F2671E4A5B5A00988324 /* TimestampBasedMessageAttributesTable.swift in Sources */, + D050F2681E4A5B5A00988324 /* TimestampBasedMessageAttributesIndexTable.swift in Sources */, + D050F2691E4A5B5A00988324 /* MultiplePeersView.swift in Sources */, + C2A315BE1E2E733900D89000 /* PeerMergedOperationLogIndexTable.swift in Sources */, + D0633D05225B98B1003DD95F /* AdditionalMessageHistoryViewData.swift in Sources */, + C2A315BF1E2E733900D89000 /* PeerMergedOperationLogView.swift in Sources */, + C2A315BD1E2E732000D89000 /* PeerOperationLogMetadataTable.swift in Sources */, + D000CADB22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */, + D0575AE41E9ECBB2006F2541 /* AccessChallengeDataView.swift in Sources */, + C2A315BC1E2E730400D89000 /* PeerOperationLogTable.swift in Sources */, + D0B2F75E204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift in Sources */, + D0C26D7F1FE3FA4E004ABF18 /* PinnedItemId.swift in Sources */, + C2AC9C131E1E5D200085C7DE /* UnreadMessageCountsView.swift in Sources */, + D0B418481D7DFE20004562A4 /* MessageHistoryView.swift in Sources */, + D0F7B1DF1E045C6A007EB8A5 /* ItemCacheMetaTable.swift in Sources */, + D0F7B1C41E045C6A007EB8A5 /* GlobalMessageIdsTable.swift in Sources */, + D073CE7E1DCBF3B4007511FD /* PeerNotificationSettings.swift in Sources */, + D0F7B1DC1E045C6A007EB8A5 /* ItemCollectionItemTable.swift in Sources */, + D0F7B1C71E045C6A007EB8A5 /* MessageHistoryTable.swift in Sources */, + D0B418571D7DFE29004562A4 /* Postbox.swift in Sources */, + D0B418251D7DFE0C004562A4 /* RedBlackTree.swift in Sources */, + D07047A31F3CE58200F6A8D4 /* PendingMessageActionsTable.swift in Sources */, + D04614342004F2CC00EC0EF2 /* LocalMessageTagsView.swift in Sources */, + D0CCD6202231CE5400EE1E08 /* PeerGroup.swift in Sources */, + D0BEAF681E54B33900BD963D /* AccountRecord.swift in Sources */, + D05D8B35218F1EBB0064586F /* AccountManagerSharedDataTable.swift in Sources */, + D06CA13322772DE40094E707 /* PeerNotificationSettingsBehaviorTimestampView.swift in Sources */, + D0F7B1C61E045C6A007EB8A5 /* MessageHistoryIndexTable.swift in Sources */, + D0F7B1CD1E045C6A007EB8A5 /* MessageMediaTable.swift in Sources */, + D0B844071DAB91B5005F29E1 /* MediaResource.swift in Sources */, + D0F7B1D71E045C6A007EB8A5 /* MessageHistoryReadStateTable.swift in Sources */, + D0B418261D7DFE0C004562A4 /* MappedFile.swift in Sources */, + D048B4A920A5CBE400C79D31 /* AdditionalChatListItemsTable.swift in Sources */, + D039FB1C21714D9800BD1BAD /* PeerPresencesView.swift in Sources */, + D073CE771DCBF3B4007511FD /* Media.swift in Sources */, + D037178C20D923CA004773C8 /* CachedItemView.swift in Sources */, + D0CA8E4622720064008A74C3 /* InvalidatedGroupMessageStatsTable.swift in Sources */, + D0A352F91F549D95001423DC /* InvalidatedMessageHistoryTagSummariesView.swift in Sources */, + D0CA8E49227208FE008A74C3 /* SynchronizeGroupMessageStatsView.swift in Sources */, + A767DD4323F2F75800366F76 /* HistoryTagInfoView.swift in Sources */, + D018BE56218B9AA900C02DDC /* TimeBasedCleanup.swift in Sources */, + D07E7B46224E227100BB053B /* PostboxUpgrade_20to21.swift in Sources */, + D073CE791DCBF3B4007511FD /* ChatListHole.swift in Sources */, + D0F53BF41E794C6700117362 /* PeerChatStateView.swift in Sources */, + D06CA130227720910094E707 /* PeerNotificationSettingsBehaviorIndexTable.swift in Sources */, + D0F82D0D1E439FCC007E499C /* OrderedItemListEntry.swift in Sources */, + D0B418591D7DFE29004562A4 /* PostboxTransaction.swift in Sources */, + D0F7B1D21E045C6A007EB8A5 /* PeerNotificationSettingsTable.swift in Sources */, + D0E23DE31E808A9400B9B6D2 /* ItemCollectionIdsView.swift in Sources */, + D0119CAE20C9E7A100895300 /* PostboxUpgrade_17to18.swift in Sources */, + D0F7B1C91E045C6A007EB8A5 /* MessageHistoryTagsTable.swift in Sources */, + D0B418271D7DFE0C004562A4 /* IpcPipe.swift in Sources */, + D0FA0ACB1E780A26005BB9B7 /* PostboxView.swift in Sources */, + D0B4184D1D7DFE20004562A4 /* ContactPeersView.swift in Sources */, + D00C7CE41E37861C0080C3D5 /* MessageView.swift in Sources */, + D08775011E3E3D9F00A97350 /* PreferencesTable.swift in Sources */, + D0830FC62416397A006198E7 /* ChatListViewState.swift in Sources */, + D0F7B1CC1E045C6A007EB8A5 /* ChatListIndexTable.swift in Sources */, + D07047A61F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift in Sources */, + D0F7B1D31E045C6A007EB8A5 /* CachedPeerDataTable.swift in Sources */, + D079FCE71F06A31C0038FADE /* NoticeTable.swift in Sources */, + D0F7B1D51E045C6A007EB8A5 /* PeerChatStateTable.swift in Sources */, + D0F82CFE1E4345D7007E499C /* OrderedItemListTable.swift in Sources */, + D0F7B1C51E045C6A007EB8A5 /* MessageHistoryMetadataTable.swift in Sources */, + D0CE8CF71F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift in Sources */, + D0E1D3101ECA53F900FCEEF1 /* GlobalMessageTagsView.swift in Sources */, + D08775071E3E3F2100A97350 /* PreferencesView.swift in Sources */, + D049EAF11E44D9B900A2CD3A /* PostboxStateView.swift in Sources */, + D0AAD1B61E32673C00D5B9DE /* TimestampBasedMessageAttributesView.swift in Sources */, + D0F7B1C31E045C6A007EB8A5 /* Table.swift in Sources */, + D06ECFC620B796DC00C576C2 /* NoticeEntryView.swift in Sources */, + D0F82D101E43A024007E499C /* OrderedItemListIndexTable.swift in Sources */, + D0F7B1CF1E045C6A007EB8A5 /* MetadataTable.swift in Sources */, + D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */, + D0B418501D7DFE20004562A4 /* UnsentMessageIndicesView.swift in Sources */, + D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */, + D015E0612265D42400CB9E8A /* GroupMessageStatsTable.swift in Sources */, + D021FC272024B83700C34AB7 /* FileSize.swift in Sources */, + D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */, + D07047B21F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift in Sources */, + D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */, + D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */, + D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */, + A7377E1923ACAE3A00AD3ADD /* FailedMessagesView.swift in Sources */, + D0439B402289F6300067E026 /* AccountManagerAtomicState.swift in Sources */, + D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */, + D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */, + D048B33E203C838500038D05 /* PostboxUpgrade_15to16.swift in Sources */, + D0F7B1D61E045C6A007EB8A5 /* ContactTable.swift in Sources */, + D0943B031FDB01D8001522CC /* PostboxUpgrade_14to15.swift in Sources */, + D0F7B1D11E045C6A007EB8A5 /* PeerTable.swift in Sources */, + D04614312004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift in Sources */, + D0F7B1DD1E045C6A007EB8A5 /* PeerChatInterfaceStateTable.swift in Sources */, + D098C6F21D7E1201007784E4 /* Database.swift in Sources */, + D07E7B49224E562C00BB053B /* PostboxUpgrade_21to22.swift in Sources */, + D0BFE51E22AFD5AF00143D08 /* MutableBasicPeerView.swift in Sources */, + D0B844061DAB91B5005F29E1 /* MediaResourceStatus.swift in Sources */, + D0F7B1DB1E045C6A007EB8A5 /* ItemCollectionInfoTable.swift in Sources */, + D0F7B1D41E045C6A007EB8A5 /* PeerPresenceTable.swift in Sources */, + D073CE761DCBF3B4007511FD /* IntermediateMessage.swift in Sources */, + D0FA0AC81E77F0A2005BB9B7 /* ItemCollectionInfosView.swift in Sources */, + D0B4185A1D7DFE29004562A4 /* ViewTracker.swift in Sources */, + D0F7B1E21E045C6A007EB8A5 /* PeerNameIndexTable.swift in Sources */, + D0E1D30D1ECA1F5500FCEEF1 /* GlobalMessageHistoryTagsTable.swift in Sources */, + D06CA12D227715E70094E707 /* PeerNotificationSettingsBehaviorTable.swift in Sources */, + D073CE7C1DCBF3B4007511FD /* CachedPeerData.swift in Sources */, + D0E119A1229834BC008CAE3A /* MutablePeerChatInclusionView.swift in Sources */, + D0F7B1DA1E045C6A007EB8A5 /* RatingTable.swift in Sources */, + D0BEAF711E54BC1E00BD963D /* AccountRecordsView.swift in Sources */, + D073CE9F1DCBF3C1007511FD /* InitialMessageHistoryData.swift in Sources */, + A754972423A3A5540091F293 /* MessageHistoryFailedTable.swift in Sources */, + D0A352F61F5488E9001423DC /* InvalidatedMessageHistoryTagsSummaryTable.swift in Sources */, + D0BC38731E409E670044D6FE /* RenderedPeer.swift in Sources */, + D0F7B1DE1E045C6A007EB8A5 /* PeerChatTopIndexableMessageIds.swift in Sources */, + D0B4184A1D7DFE20004562A4 /* UnsentMessageHistoryView.swift in Sources */, + D0B418231D7DFE0C004562A4 /* SimpleDictionary.swift in Sources */, + D0F7B1E01E045C6A007EB8A5 /* ItemCacheTable.swift in Sources */, + D07047A91F3DA8D700F6A8D4 /* PendingMessageActionsMetadataTable.swift in Sources */, + D0DA1D301F70419D0034E892 /* PendingPeerNotificationSettingsView.swift in Sources */, + D0F7B1E11E045C6A007EB8A5 /* ReverseIndexReferenceTable.swift in Sources */, + D0CCD6292232887100EE1E08 /* MessageHistoryHoleIndexTable.swift in Sources */, + D0B418241D7DFE0C004562A4 /* SimpleSet.swift in Sources */, + D0CA8E4F2272130B008A74C3 /* PostboxUpgrade_24to25.swift in Sources */, + D073CE7B1DCBF3B4007511FD /* PeerNameIndexRepresentation.swift in Sources */, + D073CE7D1DCBF3B4007511FD /* PeerPresence.swift in Sources */, + D048B4B020A5EEAE00C79D31 /* AdditionalChatListItemsView.swift in Sources */, + D0BE3035206026C800FBE6D8 /* MessagesView.swift in Sources */, + D0B4184C1D7DFE20004562A4 /* ContactPeerIdsView.swift in Sources */, + D0DA44491E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */, + D0B167211F9EAAA900976B40 /* OrderedList.swift in Sources */, + D0CE8CF41F70249400AA2DB0 /* PostboxUpgrade_13to14.swift in Sources */, + D073CE751DCBF3B4007511FD /* Message.swift in Sources */, + D0943AF91FDAC540001522CC /* ChatLocation.swift in Sources */, + D087C20D1E43C11C00D686F8 /* OrderedItemListView.swift in Sources */, + D00C7CD51E365C4E0080C3D5 /* PeerChatListInclusion.swift in Sources */, + D0F7B1CB1E045C6A007EB8A5 /* ChatListTable.swift in Sources */, + D0B166EB1F9D142A00976B40 /* MessageOfInterestHolesView.swift in Sources */, + D0F7B1C01E045C62007EB8A5 /* StringIndexTokens.swift in Sources */, + D07047AC1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift in Sources */, + D0F7B1CA1E045C6A007EB8A5 /* MessageHistoryUnsentTable.swift in Sources */, + D03387532242E32B007A2CE4 /* PostboxUpgrade_18to19.swift in Sources */, + D01BAA561ED1D70C00295217 /* ManagedFile.swift in Sources */, + D0BEAF6B1E54B5FB00BD963D /* AccountManagerMetadataTable.swift in Sources */, + D0F02CE01E99223E0065DEE2 /* Upgrades.swift in Sources */, + D0F7B1D01E045C6A007EB8A5 /* KeychainTable.swift in Sources */, + D0BEAF6E1E54B77900BD963D /* AccountManagerRecordTable.swift in Sources */, + D0B4184F1D7DFE20004562A4 /* ChatListHolesView.swift in Sources */, + D01E79E92248F661005237FE /* PostboxUpgrade_19to20.swift in Sources */, + D0105D6C218362F2007C04A7 /* TempBox.swift in Sources */, + D0F7B1D81E045C6A007EB8A5 /* MessageHistorySynchronizeReadStateTable.swift in Sources */, + D0B418581D7DFE29004562A4 /* SeedConfiguration.swift in Sources */, + D0B418311D7DFE16004562A4 /* ValueBox.swift in Sources */, + D0633D0E225BC9EF003DD95F /* MessageHistoryViewState.swift in Sources */, + D08775041E3E3E7400A97350 /* PreferencesEntry.swift in Sources */, + D0B4184E1D7DFE20004562A4 /* MessageHistoryHolesView.swift in Sources */, + D0C0B5AC1EE1AB08000F4D2C /* ReverseAssociatedPeerTable.swift in Sources */, + D0633D0B225BAD66003DD95F /* MessageHistoryAnchorIndex.swift in Sources */, + D015E05D2263BB3B00CB9E8A /* PostboxUpgrade_22to23.swift in Sources */, + D0B844051DAB91B5005F29E1 /* MediaBox.swift in Sources */, + D0B418321D7DFE16004562A4 /* SqliteValueBox.swift in Sources */, + D040CA6522665370007123CE /* PostboxUpgrade_23to24.swift in Sources */, + D073CE741DCBF3B4007511FD /* Peer.swift in Sources */, + D073CE7A1DCBF3B4007511FD /* PeerReadState.swift in Sources */, + D0F02CE31E9922F50065DEE2 /* PostboxUpgrade_12to13.swift in Sources */, + D0C27B461F4B598200A4E170 /* CachedPeerDataView.swift in Sources */, + D073CE801DCBF3B4007511FD /* PeerChatInterfaceState.swift in Sources */, + D0F7B1C81E045C6A007EB8A5 /* MessageHistoryOperation.swift in Sources */, + D0B844031DAB91A7005F29E1 /* PeerView.swift in Sources */, + D0B418301D7DFE16004562A4 /* ValueBoxKey.swift in Sources */, + D001388720BD942B007C9721 /* PostboxUpgrade_16to17.swift in Sources */, + D08B61C122A75C21000A46A8 /* Hash.swift in Sources */, + D0B418221D7DFE0C004562A4 /* Coding.swift in Sources */, + D0B4184B1D7DFE20004562A4 /* SynchronizePeerReadStatesView.swift in Sources */, + D01C7EDF1EF73F71008305F1 /* MessageHistoryTextIndexTable.swift in Sources */, + D05D8B32218F1D3D0064586F /* AccountSharedData.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282D8238EAB3C00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282D9238EAB3C00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; + C22069C81E8EB4BF00E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + C22069CB1E8EB4BF00E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + D0364D5122B3E385002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0364D5422B3E385002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + D079FD0C1F06BE070038FADE /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D079FD0F1F06BE070038FADE /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + D086A5711CC0116A00F08284 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0B418181D7DFAF3004562A4 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + D0B4181A1D7DFAF3004562A4 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Postbox/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.PostboxMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + D0E3A75E1B28A7E300A402D9 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D0B4181B1D7DFAF3004562A4 /* Build configuration list for PBXNativeTarget "Postbox" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0B418181D7DFAF3004562A4 /* DebugHockeyapp */, + D0364D5422B3E385002A6EF0 /* HockeyappMacAlpha */, + D079FD0F1F06BE070038FADE /* DebugAppStore */, + A7F282D9238EAB3C00742C20 /* Github */, + C22069CB1E8EB4BF00E82730 /* ReleaseHockeyapp */, + D0B4181A1D7DFAF3004562A4 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + D0E3A7441B28A7E300A402D9 /* Build configuration list for PBXProject "Postbox_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0E3A75E1B28A7E300A402D9 /* DebugHockeyapp */, + D0364D5122B3E385002A6EF0 /* HockeyappMacAlpha */, + D079FD0C1F06BE070038FADE /* DebugAppStore */, + A7F282D8238EAB3C00742C20 /* Github */, + C22069C81E8EB4BF00E82730 /* ReleaseHockeyapp */, + D086A5711CC0116A00F08284 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D0E3A7411B28A7E300A402D9 /* Project object */; +} diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..088de55706 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..1f36f24a42 Binary files /dev/null and b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Postbox.xcscheme b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Postbox.xcscheme new file mode 100644 index 0000000000..2a2d0f2587 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Postbox.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxMac.xcscheme b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxMac.xcscheme new file mode 100644 index 0000000000..136c3391e9 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxMac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxTests.xcscheme b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxTests.xcscheme new file mode 100644 index 0000000000..83d158d854 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/PostboxTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..32c3a46156 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + Postbox.xcscheme_^#shared#^_ + + orderHint + 20 + + PostboxMac.xcscheme_^#shared#^_ + + orderHint + 37 + + PostboxTests.xcscheme_^#shared#^_ + + orderHint + 21 + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/Postbox.xcscheme b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/Postbox.xcscheme new file mode 100644 index 0000000000..2a2d0f2587 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/Postbox.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..bdbf484420 --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + Postbox.xcscheme + + isShown + + orderHint + 20 + + + SuppressBuildableAutocreation + + D0B418121D7DFAF2004562A4 + + primary + + + + + diff --git a/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..e737be8e3e --- /dev/null +++ b/core-xprojects/Postbox/Postbox_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + Postbox.xcscheme_^#shared#^_ + + orderHint + 33 + + PostboxMac.xcscheme_^#shared#^_ + + orderHint + 43 + + PostboxTests.xcscheme_^#shared#^_ + + orderHint + 34 + + + + diff --git a/core-xprojects/Reachability/Reachability.xcodeproj/project.pbxproj b/core-xprojects/Reachability/Reachability.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..693adcc796 --- /dev/null +++ b/core-xprojects/Reachability/Reachability.xcodeproj/project.pbxproj @@ -0,0 +1,625 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7919027240CF904002011CA /* ReachabilityPublic.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919025240CF904002011CA /* ReachabilityPublic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919030240CF91D002011CA /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = A791902F240CF91D002011CA /* Reachability.m */; }; + A7919032240CF925002011CA /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919031240CF925002011CA /* Reachability.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7919022240CF904002011CA /* Reachability.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7919025240CF904002011CA /* ReachabilityPublic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReachabilityPublic.h; sourceTree = ""; }; + A7919026240CF904002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A791902F240CF91D002011CA /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = "../../../../submodules/telegram-ios/submodules/Reachability/Sources/Reachability.m"; sourceTree = ""; }; + A7919031240CF925002011CA /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = "../../../../submodules/telegram-ios/submodules/Reachability/PublicHeaders/Reachability/Reachability.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A791901F240CF904002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7919018240CF904002011CA = { + isa = PBXGroup; + children = ( + A7919024240CF904002011CA /* Reachability */, + A7919023240CF904002011CA /* Products */, + ); + sourceTree = ""; + }; + A7919023240CF904002011CA /* Products */ = { + isa = PBXGroup; + children = ( + A7919022240CF904002011CA /* Reachability.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7919024240CF904002011CA /* Reachability */ = { + isa = PBXGroup; + children = ( + A791902E240CF913002011CA /* Headers */, + A791902D240CF90B002011CA /* Sources */, + A7919025240CF904002011CA /* ReachabilityPublic.h */, + A7919026240CF904002011CA /* Info.plist */, + ); + path = Reachability; + sourceTree = ""; + }; + A791902D240CF90B002011CA /* Sources */ = { + isa = PBXGroup; + children = ( + A791902F240CF91D002011CA /* Reachability.m */, + ); + path = Sources; + sourceTree = ""; + }; + A791902E240CF913002011CA /* Headers */ = { + isa = PBXGroup; + children = ( + A7919031240CF925002011CA /* Reachability.h */, + ); + name = Headers; + path = "New Group1"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A791901D240CF904002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919027240CF904002011CA /* ReachabilityPublic.h in Headers */, + A7919032240CF925002011CA /* Reachability.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7919021240CF904002011CA /* Reachability */ = { + isa = PBXNativeTarget; + buildConfigurationList = A791902A240CF904002011CA /* Build configuration list for PBXNativeTarget "Reachability" */; + buildPhases = ( + A791901D240CF904002011CA /* Headers */, + A791901E240CF904002011CA /* Sources */, + A791901F240CF904002011CA /* Frameworks */, + A7919020240CF904002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Reachability; + productName = Reachability; + productReference = A7919022240CF904002011CA /* Reachability.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7919019240CF904002011CA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7919021240CF904002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A791901C240CF904002011CA /* Build configuration list for PBXProject "Reachability" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7919018240CF904002011CA; + productRefGroup = A7919023240CF904002011CA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7919021240CF904002011CA /* Reachability */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7919020240CF904002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A791901E240CF904002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919030240CF91D002011CA /* Reachability.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7919028240CF904002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7919029240CF904002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A791902B240CF904002011CA /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Reachability/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Reachability; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A791902C240CF904002011CA /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Reachability/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Reachability; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A791903C240CFA4D002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A791903D240CFA4D002011CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Reachability/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Reachability; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A791903E240CFA55002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A791903F240CFA55002011CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Reachability/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Reachability; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7919040240CFA5D002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7919041240CFA5D002011CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = Reachability/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Reachability; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A791901C240CF904002011CA /* Build configuration list for PBXProject "Reachability" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7919028240CF904002011CA /* DebugAppStore */, + A791903E240CFA55002011CA /* HockeyappMacAlpha */, + A791903C240CFA4D002011CA /* DebugHockeyapp */, + A7919029240CF904002011CA /* ReleaseHockeyapp */, + A7919040240CFA5D002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A791902A240CF904002011CA /* Build configuration list for PBXNativeTarget "Reachability" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A791902B240CF904002011CA /* DebugAppStore */, + A791903F240CFA55002011CA /* HockeyappMacAlpha */, + A791903D240CFA4D002011CA /* DebugHockeyapp */, + A791902C240CF904002011CA /* ReleaseHockeyapp */, + A7919041240CFA5D002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7919019240CF904002011CA /* Project object */; +} diff --git a/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..87066cc83f --- /dev/null +++ b/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/Reachability/Reachability.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/Reachability/Reachability/Info.plist b/core-xprojects/Reachability/Reachability/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/Reachability/Reachability/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/Reachability/Reachability/ReachabilityPublic.h b/core-xprojects/Reachability/Reachability/ReachabilityPublic.h new file mode 100644 index 0000000000..220a6f7e8b --- /dev/null +++ b/core-xprojects/Reachability/Reachability/ReachabilityPublic.h @@ -0,0 +1,19 @@ +// +// Reachability.h +// Reachability +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for Reachability. +FOUNDATION_EXPORT double ReachabilityVersionNumber; + +//! Project version string for Reachability. +FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..e92a325b59 --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1597 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A791908B240CFCE4002011CA /* STimer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919055240CFCE1002011CA /* STimer.h */; }; + A791908C240CFCE4002011CA /* SSignal+Pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919056240CFCE1002011CA /* SSignal+Pipe.h */; }; + A791908D240CFCE4002011CA /* SDisposableSet.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919057240CFCE1002011CA /* SDisposableSet.h */; }; + A791908E240CFCE4002011CA /* SSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919058240CFCE1002011CA /* SSignal.h */; }; + A791908F240CFCE4002011CA /* SSignal+Accumulate.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919059240CFCE1002011CA /* SSignal+Accumulate.h */; }; + A7919091240CFCE4002011CA /* SThreadPoolQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905B240CFCE1002011CA /* SThreadPoolQueue.h */; }; + A7919092240CFCE4002011CA /* SSignal+Meta.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905C240CFCE1002011CA /* SSignal+Meta.h */; }; + A7919093240CFCE4002011CA /* SVariable.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905D240CFCE1002011CA /* SVariable.h */; }; + A7919095240CFCE4002011CA /* SThreadPoolTask.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905F240CFCE1002011CA /* SThreadPoolTask.h */; }; + A7919098240CFCE4002011CA /* SSignal+SideEffects.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919062240CFCE1002011CA /* SSignal+SideEffects.h */; }; + A791909A240CFCE4002011CA /* SSignalKit.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919064240CFCE2002011CA /* SSignalKit.h */; }; + A791909D240CFCE4002011CA /* SSignal+Dispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919067240CFCE2002011CA /* SSignal+Dispatch.h */; }; + A791909E240CFCE4002011CA /* SSignal+Combine.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919068240CFCE2002011CA /* SSignal+Combine.h */; }; + A79190A4240CFCE4002011CA /* SMetaDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A791906E240CFCE2002011CA /* SMetaDisposable.h */; }; + A79190A7240CFCE4002011CA /* SBlockDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919071240CFCE2002011CA /* SBlockDisposable.h */; }; + A79190A8240CFCE4002011CA /* SAtomic.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919072240CFCE2002011CA /* SAtomic.h */; }; + A79190AA240CFCE4002011CA /* SSignal+Multicast.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919074240CFCE2002011CA /* SSignal+Multicast.h */; }; + A79190AD240CFCE4002011CA /* SSignal+Single.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919077240CFCE2002011CA /* SSignal+Single.h */; }; + A79190AE240CFCE4002011CA /* SDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919078240CFCE2002011CA /* SDisposable.h */; }; + A79190AF240CFCE4002011CA /* SSignal+Mapping.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919079240CFCE3002011CA /* SSignal+Mapping.h */; }; + A79190B0240CFCE4002011CA /* SBag.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907A240CFCE3002011CA /* SBag.h */; }; + A79190B3240CFCE4002011CA /* SQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907D240CFCE3002011CA /* SQueue.h */; }; + A79190B4240CFCE4002011CA /* SSignal+Timing.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907E240CFCE3002011CA /* SSignal+Timing.h */; }; + A79190B6240CFCE4002011CA /* SSignal+Catch.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919080240CFCE3002011CA /* SSignal+Catch.h */; }; + A79190B7240CFCE4002011CA /* SSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919081240CFCE3002011CA /* SSubscriber.h */; }; + A79190BA240CFCE4002011CA /* SMulticastSignalManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919084240CFCE3002011CA /* SMulticastSignalManager.h */; }; + A79190BC240CFCE4002011CA /* SThreadPool.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919086240CFCE4002011CA /* SThreadPool.h */; }; + A79190BD240CFCE4002011CA /* SSignal+Take.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919087240CFCE4002011CA /* SSignal+Take.h */; }; + A79190BF240CFD14002011CA /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E18240CF16A002011CA /* Atomic.swift */; }; + A79190C0240CFD14002011CA /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E22240CF16B002011CA /* Bag.swift */; }; + A79190C1240CFD14002011CA /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E23240CF16B002011CA /* Disposable.swift */; }; + A79190C2240CFD14002011CA /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E20240CF16A002011CA /* Lock.swift */; }; + A79190C3240CFD14002011CA /* Multicast.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E2E240CF16B002011CA /* Multicast.swift */; }; + A79190C4240CFD14002011CA /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1C240CF16A002011CA /* Promise.swift */; }; + A79190C5240CFD14002011CA /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E2B240CF16B002011CA /* Queue.swift */; }; + A79190C6240CFD14002011CA /* QueueLocalObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1B240CF16A002011CA /* QueueLocalObject.swift */; }; + A79190C7240CFD14002011CA /* Signal_Catch.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1A240CF16A002011CA /* Signal_Catch.swift */; }; + A79190C8240CFD14002011CA /* Signal_Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E24240CF16B002011CA /* Signal_Combine.swift */; }; + A79190C9240CFD14002011CA /* Signal_Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E15240CF16A002011CA /* Signal_Dispatch.swift */; }; + A79190CA240CFD14002011CA /* Signal_Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E17240CF16A002011CA /* Signal_Loop.swift */; }; + A79190CB240CFD14002011CA /* Signal_Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E16240CF16A002011CA /* Signal_Mapping.swift */; }; + A79190CC240CFD14002011CA /* Signal_Materialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E2A240CF16B002011CA /* Signal_Materialize.swift */; }; + A79190CD240CFD14002011CA /* Signal_Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E28240CF16B002011CA /* Signal_Merge.swift */; }; + A79190CE240CFD14002011CA /* Signal_Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E2D240CF16B002011CA /* Signal_Meta.swift */; }; + A79190CF240CFD14002011CA /* Signal_Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E27240CF16B002011CA /* Signal_Reduce.swift */; }; + A79190D0240CFD14002011CA /* Signal_SideEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E19240CF16A002011CA /* Signal_SideEffects.swift */; }; + A79190D1240CFD14002011CA /* Signal_Single.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E2C240CF16B002011CA /* Signal_Single.swift */; }; + A79190D2240CFD14002011CA /* Signal_Take.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1E240CF16A002011CA /* Signal_Take.swift */; }; + A79190D3240CFD14002011CA /* Signal_Timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E25240CF16B002011CA /* Signal_Timing.swift */; }; + A79190D4240CFD14002011CA /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E26240CF16B002011CA /* Signal.swift */; }; + A79190D5240CFD14002011CA /* Subscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1D240CF16A002011CA /* Subscriber.swift */; }; + A79190D6240CFD14002011CA /* ThreadPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E21240CF16B002011CA /* ThreadPool.swift */; }; + A79190D7240CFD14002011CA /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E29240CF16B002011CA /* Timer.swift */; }; + A79190D8240CFD14002011CA /* ValuePipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7918E1F240CF16A002011CA /* ValuePipe.swift */; }; + A79190DA240CFD22002011CA /* SAtomic.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919082240CFCE3002011CA /* SAtomic.m */; }; + A79190DC240CFD22002011CA /* SBag.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919061240CFCE1002011CA /* SBag.m */; }; + A79190DE240CFD22002011CA /* SBlockDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919065240CFCE2002011CA /* SBlockDisposable.m */; }; + A79190E0240CFD22002011CA /* SDisposableSet.h in Sources */ = {isa = PBXBuildFile; fileRef = A7919057240CFCE1002011CA /* SDisposableSet.h */; }; + A79190E1240CFD22002011CA /* SDisposableSet.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919053240CFCE1002011CA /* SDisposableSet.m */; }; + A79190E3240CFD22002011CA /* SMetaDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = A791907F240CFCE3002011CA /* SMetaDisposable.m */; }; + A79190E5240CFD22002011CA /* SMulticastSignalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919088240CFCE4002011CA /* SMulticastSignalManager.m */; }; + A79190E7240CFD22002011CA /* SQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919070240CFCE2002011CA /* SQueue.m */; }; + A79190E9240CFD22002011CA /* SSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = A791906C240CFCE2002011CA /* SSignal.m */; }; + A79190EB240CFD22002011CA /* SSignal+Accumulate.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919060240CFCE1002011CA /* SSignal+Accumulate.m */; }; + A79190ED240CFD22002011CA /* SSignal+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919054240CFCE1002011CA /* SSignal+Catch.m */; }; + A79190EF240CFD22002011CA /* SSignal+Combine.m in Sources */ = {isa = PBXBuildFile; fileRef = A791906B240CFCE2002011CA /* SSignal+Combine.m */; }; + A79190F1240CFD22002011CA /* SSignal+Dispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919076240CFCE2002011CA /* SSignal+Dispatch.m */; }; + A79190F3240CFD22002011CA /* SSignal+Mapping.m in Sources */ = {isa = PBXBuildFile; fileRef = A791907C240CFCE3002011CA /* SSignal+Mapping.m */; }; + A79190F5240CFD22002011CA /* SSignal+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = A791906F240CFCE2002011CA /* SSignal+Meta.m */; }; + A79190F7240CFD22002011CA /* SSignal+Multicast.m in Sources */ = {isa = PBXBuildFile; fileRef = A791906A240CFCE2002011CA /* SSignal+Multicast.m */; }; + A79190F9240CFD22002011CA /* SSignal+Pipe.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919073240CFCE2002011CA /* SSignal+Pipe.m */; }; + A79190FB240CFD22002011CA /* SSignal+SideEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919069240CFCE2002011CA /* SSignal+SideEffects.m */; }; + A79190FD240CFD22002011CA /* SSignal+Single.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919066240CFCE2002011CA /* SSignal+Single.m */; }; + A79190FF240CFD22002011CA /* SSignal+Take.m in Sources */ = {isa = PBXBuildFile; fileRef = A791905E240CFCE1002011CA /* SSignal+Take.m */; }; + A7919101240CFD22002011CA /* SSignal+Timing.m in Sources */ = {isa = PBXBuildFile; fileRef = A791907B240CFCE3002011CA /* SSignal+Timing.m */; }; + A7919104240CFD22002011CA /* SSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919063240CFCE2002011CA /* SSubscriber.m */; }; + A7919106240CFD22002011CA /* SThreadPool.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919085240CFCE3002011CA /* SThreadPool.m */; }; + A7919108240CFD22002011CA /* SThreadPoolQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919083240CFCE3002011CA /* SThreadPoolQueue.m */; }; + A791910A240CFD22002011CA /* SThreadPoolTask.m in Sources */ = {isa = PBXBuildFile; fileRef = A7919075240CFCE2002011CA /* SThreadPoolTask.m */; }; + A791910C240CFD22002011CA /* STimer.m in Sources */ = {isa = PBXBuildFile; fileRef = A791906D240CFCE2002011CA /* STimer.m */; }; + A791910E240CFD22002011CA /* SVariable.m in Sources */ = {isa = PBXBuildFile; fileRef = A791905A240CFCE1002011CA /* SVariable.m */; }; + A791910F240CFD60002011CA /* SAtomic.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919072240CFCE2002011CA /* SAtomic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919110240CFD60002011CA /* SBag.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907A240CFCE3002011CA /* SBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919111240CFD60002011CA /* SBlockDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919071240CFCE2002011CA /* SBlockDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919112240CFD60002011CA /* SDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919078240CFCE2002011CA /* SDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919113240CFD60002011CA /* SDisposableSet.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919057240CFCE1002011CA /* SDisposableSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919114240CFD60002011CA /* SMetaDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = A791906E240CFCE2002011CA /* SMetaDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919115240CFD60002011CA /* SMulticastSignalManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919084240CFCE3002011CA /* SMulticastSignalManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919116240CFD60002011CA /* SQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907D240CFCE3002011CA /* SQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919117240CFD60002011CA /* SSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919058240CFCE1002011CA /* SSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919118240CFD60002011CA /* SSignal+Accumulate.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919059240CFCE1002011CA /* SSignal+Accumulate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919119240CFD60002011CA /* SSignal+Catch.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919080240CFCE3002011CA /* SSignal+Catch.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911A240CFD60002011CA /* SSignal+Combine.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919068240CFCE2002011CA /* SSignal+Combine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911B240CFD60002011CA /* SSignal+Dispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919067240CFCE2002011CA /* SSignal+Dispatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911C240CFD60002011CA /* SSignal+Mapping.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919079240CFCE3002011CA /* SSignal+Mapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911D240CFD60002011CA /* SSignal+Meta.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905C240CFCE1002011CA /* SSignal+Meta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911E240CFD60002011CA /* SSignal+Multicast.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919074240CFCE2002011CA /* SSignal+Multicast.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791911F240CFD60002011CA /* SSignal+Pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919056240CFCE1002011CA /* SSignal+Pipe.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919120240CFD60002011CA /* SSignal+SideEffects.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919062240CFCE1002011CA /* SSignal+SideEffects.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919121240CFD60002011CA /* SSignal+Single.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919077240CFCE2002011CA /* SSignal+Single.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919122240CFD60002011CA /* SSignal+Take.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919087240CFCE4002011CA /* SSignal+Take.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919123240CFD60002011CA /* SSignal+Timing.h in Headers */ = {isa = PBXBuildFile; fileRef = A791907E240CFCE3002011CA /* SSignal+Timing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919124240CFD60002011CA /* SSignalKit.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919064240CFCE2002011CA /* SSignalKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919125240CFD60002011CA /* SSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919081240CFCE3002011CA /* SSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919126240CFD60002011CA /* SThreadPool.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919086240CFCE4002011CA /* SThreadPool.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919127240CFD60002011CA /* SThreadPoolQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905B240CFCE1002011CA /* SThreadPoolQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919128240CFD60002011CA /* SThreadPoolTask.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905F240CFCE1002011CA /* SThreadPoolTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919129240CFD60002011CA /* STimer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919055240CFCE1002011CA /* STimer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791912A240CFD60002011CA /* SVariable.h in Headers */ = {isa = PBXBuildFile; fileRef = A791905D240CFCE1002011CA /* SVariable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0B417F11D7DFA63004562A4 /* SwiftSignalKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D0B417EF1D7DFA63004562A4 /* SwiftSignalKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A790A321236B17DF000451B5 /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A37C236B1C44000451B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + A7918E15240CF16A002011CA /* Signal_Dispatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Dispatch.swift; path = Source/Signal_Dispatch.swift; sourceTree = ""; }; + A7918E16240CF16A002011CA /* Signal_Mapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Mapping.swift; path = Source/Signal_Mapping.swift; sourceTree = ""; }; + A7918E17240CF16A002011CA /* Signal_Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Loop.swift; path = Source/Signal_Loop.swift; sourceTree = ""; }; + A7918E18240CF16A002011CA /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Atomic.swift; path = Source/Atomic.swift; sourceTree = ""; }; + A7918E19240CF16A002011CA /* Signal_SideEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_SideEffects.swift; path = Source/Signal_SideEffects.swift; sourceTree = ""; }; + A7918E1A240CF16A002011CA /* Signal_Catch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Catch.swift; path = Source/Signal_Catch.swift; sourceTree = ""; }; + A7918E1B240CF16A002011CA /* QueueLocalObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QueueLocalObject.swift; path = Source/QueueLocalObject.swift; sourceTree = ""; }; + A7918E1C240CF16A002011CA /* Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Promise.swift; path = Source/Promise.swift; sourceTree = ""; }; + A7918E1D240CF16A002011CA /* Subscriber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Subscriber.swift; path = Source/Subscriber.swift; sourceTree = ""; }; + A7918E1E240CF16A002011CA /* Signal_Take.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Take.swift; path = Source/Signal_Take.swift; sourceTree = ""; }; + A7918E1F240CF16A002011CA /* ValuePipe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ValuePipe.swift; path = Source/ValuePipe.swift; sourceTree = ""; }; + A7918E20240CF16A002011CA /* Lock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Lock.swift; path = Source/Lock.swift; sourceTree = ""; }; + A7918E21240CF16B002011CA /* ThreadPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadPool.swift; path = Source/ThreadPool.swift; sourceTree = ""; }; + A7918E22240CF16B002011CA /* Bag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bag.swift; path = Source/Bag.swift; sourceTree = ""; }; + A7918E23240CF16B002011CA /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Disposable.swift; path = Source/Disposable.swift; sourceTree = ""; }; + A7918E24240CF16B002011CA /* Signal_Combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Combine.swift; path = Source/Signal_Combine.swift; sourceTree = ""; }; + A7918E25240CF16B002011CA /* Signal_Timing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Timing.swift; path = Source/Signal_Timing.swift; sourceTree = ""; }; + A7918E26240CF16B002011CA /* Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal.swift; path = Source/Signal.swift; sourceTree = ""; }; + A7918E27240CF16B002011CA /* Signal_Reduce.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Reduce.swift; path = Source/Signal_Reduce.swift; sourceTree = ""; }; + A7918E28240CF16B002011CA /* Signal_Merge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Merge.swift; path = Source/Signal_Merge.swift; sourceTree = ""; }; + A7918E29240CF16B002011CA /* Timer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Timer.swift; path = Source/Timer.swift; sourceTree = ""; }; + A7918E2A240CF16B002011CA /* Signal_Materialize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Materialize.swift; path = Source/Signal_Materialize.swift; sourceTree = ""; }; + A7918E2B240CF16B002011CA /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Source/Queue.swift; sourceTree = ""; }; + A7918E2C240CF16B002011CA /* Signal_Single.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Single.swift; path = Source/Signal_Single.swift; sourceTree = ""; }; + A7918E2D240CF16B002011CA /* Signal_Meta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Signal_Meta.swift; path = Source/Signal_Meta.swift; sourceTree = ""; }; + A7918E2E240CF16B002011CA /* Multicast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Multicast.swift; path = Source/Multicast.swift; sourceTree = ""; }; + A7919053240CFCE1002011CA /* SDisposableSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDisposableSet.m; path = Source/SSignalKit/SDisposableSet.m; sourceTree = ""; }; + A7919054240CFCE1002011CA /* SSignal+Catch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Catch.m"; path = "Source/SSignalKit/SSignal+Catch.m"; sourceTree = ""; }; + A7919055240CFCE1002011CA /* STimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STimer.h; path = Source/SSignalKit/STimer.h; sourceTree = ""; }; + A7919056240CFCE1002011CA /* SSignal+Pipe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Pipe.h"; path = "Source/SSignalKit/SSignal+Pipe.h"; sourceTree = ""; }; + A7919057240CFCE1002011CA /* SDisposableSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDisposableSet.h; path = Source/SSignalKit/SDisposableSet.h; sourceTree = ""; }; + A7919058240CFCE1002011CA /* SSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSignal.h; path = Source/SSignalKit/SSignal.h; sourceTree = ""; }; + A7919059240CFCE1002011CA /* SSignal+Accumulate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Accumulate.h"; path = "Source/SSignalKit/SSignal+Accumulate.h"; sourceTree = ""; }; + A791905A240CFCE1002011CA /* SVariable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SVariable.m; path = Source/SSignalKit/SVariable.m; sourceTree = ""; }; + A791905B240CFCE1002011CA /* SThreadPoolQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SThreadPoolQueue.h; path = Source/SSignalKit/SThreadPoolQueue.h; sourceTree = ""; }; + A791905C240CFCE1002011CA /* SSignal+Meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Meta.h"; path = "Source/SSignalKit/SSignal+Meta.h"; sourceTree = ""; }; + A791905D240CFCE1002011CA /* SVariable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SVariable.h; path = Source/SSignalKit/SVariable.h; sourceTree = ""; }; + A791905E240CFCE1002011CA /* SSignal+Take.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Take.m"; path = "Source/SSignalKit/SSignal+Take.m"; sourceTree = ""; }; + A791905F240CFCE1002011CA /* SThreadPoolTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SThreadPoolTask.h; path = Source/SSignalKit/SThreadPoolTask.h; sourceTree = ""; }; + A7919060240CFCE1002011CA /* SSignal+Accumulate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Accumulate.m"; path = "Source/SSignalKit/SSignal+Accumulate.m"; sourceTree = ""; }; + A7919061240CFCE1002011CA /* SBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBag.m; path = Source/SSignalKit/SBag.m; sourceTree = ""; }; + A7919062240CFCE1002011CA /* SSignal+SideEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+SideEffects.h"; path = "Source/SSignalKit/SSignal+SideEffects.h"; sourceTree = ""; }; + A7919063240CFCE2002011CA /* SSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SSubscriber.m; path = Source/SSignalKit/SSubscriber.m; sourceTree = ""; }; + A7919064240CFCE2002011CA /* SSignalKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSignalKit.h; path = Source/SSignalKit/SSignalKit.h; sourceTree = ""; }; + A7919065240CFCE2002011CA /* SBlockDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBlockDisposable.m; path = Source/SSignalKit/SBlockDisposable.m; sourceTree = ""; }; + A7919066240CFCE2002011CA /* SSignal+Single.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Single.m"; path = "Source/SSignalKit/SSignal+Single.m"; sourceTree = ""; }; + A7919067240CFCE2002011CA /* SSignal+Dispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Dispatch.h"; path = "Source/SSignalKit/SSignal+Dispatch.h"; sourceTree = ""; }; + A7919068240CFCE2002011CA /* SSignal+Combine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Combine.h"; path = "Source/SSignalKit/SSignal+Combine.h"; sourceTree = ""; }; + A7919069240CFCE2002011CA /* SSignal+SideEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+SideEffects.m"; path = "Source/SSignalKit/SSignal+SideEffects.m"; sourceTree = ""; }; + A791906A240CFCE2002011CA /* SSignal+Multicast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Multicast.m"; path = "Source/SSignalKit/SSignal+Multicast.m"; sourceTree = ""; }; + A791906B240CFCE2002011CA /* SSignal+Combine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Combine.m"; path = "Source/SSignalKit/SSignal+Combine.m"; sourceTree = ""; }; + A791906C240CFCE2002011CA /* SSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SSignal.m; path = Source/SSignalKit/SSignal.m; sourceTree = ""; }; + A791906D240CFCE2002011CA /* STimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = STimer.m; path = Source/SSignalKit/STimer.m; sourceTree = ""; }; + A791906E240CFCE2002011CA /* SMetaDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SMetaDisposable.h; path = Source/SSignalKit/SMetaDisposable.h; sourceTree = ""; }; + A791906F240CFCE2002011CA /* SSignal+Meta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Meta.m"; path = "Source/SSignalKit/SSignal+Meta.m"; sourceTree = ""; }; + A7919070240CFCE2002011CA /* SQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SQueue.m; path = Source/SSignalKit/SQueue.m; sourceTree = ""; }; + A7919071240CFCE2002011CA /* SBlockDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBlockDisposable.h; path = Source/SSignalKit/SBlockDisposable.h; sourceTree = ""; }; + A7919072240CFCE2002011CA /* SAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SAtomic.h; path = Source/SSignalKit/SAtomic.h; sourceTree = ""; }; + A7919073240CFCE2002011CA /* SSignal+Pipe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Pipe.m"; path = "Source/SSignalKit/SSignal+Pipe.m"; sourceTree = ""; }; + A7919074240CFCE2002011CA /* SSignal+Multicast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Multicast.h"; path = "Source/SSignalKit/SSignal+Multicast.h"; sourceTree = ""; }; + A7919075240CFCE2002011CA /* SThreadPoolTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SThreadPoolTask.m; path = Source/SSignalKit/SThreadPoolTask.m; sourceTree = ""; }; + A7919076240CFCE2002011CA /* SSignal+Dispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Dispatch.m"; path = "Source/SSignalKit/SSignal+Dispatch.m"; sourceTree = ""; }; + A7919077240CFCE2002011CA /* SSignal+Single.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Single.h"; path = "Source/SSignalKit/SSignal+Single.h"; sourceTree = ""; }; + A7919078240CFCE2002011CA /* SDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDisposable.h; path = Source/SSignalKit/SDisposable.h; sourceTree = ""; }; + A7919079240CFCE3002011CA /* SSignal+Mapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Mapping.h"; path = "Source/SSignalKit/SSignal+Mapping.h"; sourceTree = ""; }; + A791907A240CFCE3002011CA /* SBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBag.h; path = Source/SSignalKit/SBag.h; sourceTree = ""; }; + A791907B240CFCE3002011CA /* SSignal+Timing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Timing.m"; path = "Source/SSignalKit/SSignal+Timing.m"; sourceTree = ""; }; + A791907C240CFCE3002011CA /* SSignal+Mapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SSignal+Mapping.m"; path = "Source/SSignalKit/SSignal+Mapping.m"; sourceTree = ""; }; + A791907D240CFCE3002011CA /* SQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQueue.h; path = Source/SSignalKit/SQueue.h; sourceTree = ""; }; + A791907E240CFCE3002011CA /* SSignal+Timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Timing.h"; path = "Source/SSignalKit/SSignal+Timing.h"; sourceTree = ""; }; + A791907F240CFCE3002011CA /* SMetaDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SMetaDisposable.m; path = Source/SSignalKit/SMetaDisposable.m; sourceTree = ""; }; + A7919080240CFCE3002011CA /* SSignal+Catch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Catch.h"; path = "Source/SSignalKit/SSignal+Catch.h"; sourceTree = ""; }; + A7919081240CFCE3002011CA /* SSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SSubscriber.h; path = Source/SSignalKit/SSubscriber.h; sourceTree = ""; }; + A7919082240CFCE3002011CA /* SAtomic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SAtomic.m; path = Source/SSignalKit/SAtomic.m; sourceTree = ""; }; + A7919083240CFCE3002011CA /* SThreadPoolQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SThreadPoolQueue.m; path = Source/SSignalKit/SThreadPoolQueue.m; sourceTree = ""; }; + A7919084240CFCE3002011CA /* SMulticastSignalManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SMulticastSignalManager.h; path = Source/SSignalKit/SMulticastSignalManager.h; sourceTree = ""; }; + A7919085240CFCE3002011CA /* SThreadPool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SThreadPool.m; path = Source/SSignalKit/SThreadPool.m; sourceTree = ""; }; + A7919086240CFCE4002011CA /* SThreadPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SThreadPool.h; path = Source/SSignalKit/SThreadPool.h; sourceTree = ""; }; + A7919087240CFCE4002011CA /* SSignal+Take.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SSignal+Take.h"; path = "Source/SSignalKit/SSignal+Take.h"; sourceTree = ""; }; + A7919088240CFCE4002011CA /* SMulticastSignalManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SMulticastSignalManager.m; path = Source/SSignalKit/SMulticastSignalManager.m; sourceTree = ""; }; + D0085B251B282B9800EAF753 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0445DDC1A7C2CA500267924 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0B417ED1D7DFA63004562A4 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B417EF1D7DFA63004562A4 /* SwiftSignalKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftSignalKit.h; sourceTree = ""; }; + D0B417F01D7DFA63004562A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A790A31E236B17DF000451B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0B417E91D7DFA63004562A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A790A37B236B1C43000451B5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A790A37C236B1C44000451B5 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A7918E14240CF163002011CA /* Sources */ = { + isa = PBXGroup; + children = ( + A7918E18240CF16A002011CA /* Atomic.swift */, + A7918E22240CF16B002011CA /* Bag.swift */, + A7918E23240CF16B002011CA /* Disposable.swift */, + A7918E20240CF16A002011CA /* Lock.swift */, + A7918E2E240CF16B002011CA /* Multicast.swift */, + A7918E1C240CF16A002011CA /* Promise.swift */, + A7918E2B240CF16B002011CA /* Queue.swift */, + A7918E1B240CF16A002011CA /* QueueLocalObject.swift */, + A7918E1A240CF16A002011CA /* Signal_Catch.swift */, + A7918E24240CF16B002011CA /* Signal_Combine.swift */, + A7918E15240CF16A002011CA /* Signal_Dispatch.swift */, + A7918E17240CF16A002011CA /* Signal_Loop.swift */, + A7918E16240CF16A002011CA /* Signal_Mapping.swift */, + A7918E2A240CF16B002011CA /* Signal_Materialize.swift */, + A7918E28240CF16B002011CA /* Signal_Merge.swift */, + A7918E2D240CF16B002011CA /* Signal_Meta.swift */, + A7918E27240CF16B002011CA /* Signal_Reduce.swift */, + A7918E19240CF16A002011CA /* Signal_SideEffects.swift */, + A7918E2C240CF16B002011CA /* Signal_Single.swift */, + A7918E1E240CF16A002011CA /* Signal_Take.swift */, + A7918E25240CF16B002011CA /* Signal_Timing.swift */, + A7918E26240CF16B002011CA /* Signal.swift */, + A7918E1D240CF16A002011CA /* Subscriber.swift */, + A7918E21240CF16B002011CA /* ThreadPool.swift */, + A7918E29240CF16B002011CA /* Timer.swift */, + A7918E1F240CF16A002011CA /* ValuePipe.swift */, + ); + name = Sources; + sourceTree = ""; + }; + D0085B231B282B9800EAF753 /* SwiftSignalKit */ = { + isa = PBXGroup; + children = ( + A7918E14240CF163002011CA /* Sources */, + D0085B241B282B9800EAF753 /* Supporting Files */, + ); + name = SwiftSignalKit; + path = "../../submodules/telegram-ios/submodules/SSignalKit/SwiftSignalKit"; + sourceTree = ""; + }; + D0085B241B282B9800EAF753 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D0085B251B282B9800EAF753 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D0445DCE1A7C2CA500267924 = { + isa = PBXGroup; + children = ( + D0445DDA1A7C2CA500267924 /* SSignalKit */, + D0085B231B282B9800EAF753 /* SwiftSignalKit */, + D0B417EE1D7DFA63004562A4 /* SwiftSignalKit */, + D0445DD91A7C2CA500267924 /* Products */, + A790A37B236B1C43000451B5 /* Frameworks */, + ); + sourceTree = ""; + }; + D0445DD91A7C2CA500267924 /* Products */ = { + isa = PBXGroup; + children = ( + D0B417ED1D7DFA63004562A4 /* SwiftSignalKit.framework */, + A790A321236B17DF000451B5 /* SSignalKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + D0445DDA1A7C2CA500267924 /* SSignalKit */ = { + isa = PBXGroup; + children = ( + A7919072240CFCE2002011CA /* SAtomic.h */, + A7919082240CFCE3002011CA /* SAtomic.m */, + A791907A240CFCE3002011CA /* SBag.h */, + A7919061240CFCE1002011CA /* SBag.m */, + A7919071240CFCE2002011CA /* SBlockDisposable.h */, + A7919065240CFCE2002011CA /* SBlockDisposable.m */, + A7919078240CFCE2002011CA /* SDisposable.h */, + A7919057240CFCE1002011CA /* SDisposableSet.h */, + A7919053240CFCE1002011CA /* SDisposableSet.m */, + A791906E240CFCE2002011CA /* SMetaDisposable.h */, + A791907F240CFCE3002011CA /* SMetaDisposable.m */, + A7919084240CFCE3002011CA /* SMulticastSignalManager.h */, + A7919088240CFCE4002011CA /* SMulticastSignalManager.m */, + A791907D240CFCE3002011CA /* SQueue.h */, + A7919070240CFCE2002011CA /* SQueue.m */, + A7919058240CFCE1002011CA /* SSignal.h */, + A791906C240CFCE2002011CA /* SSignal.m */, + A7919059240CFCE1002011CA /* SSignal+Accumulate.h */, + A7919060240CFCE1002011CA /* SSignal+Accumulate.m */, + A7919080240CFCE3002011CA /* SSignal+Catch.h */, + A7919054240CFCE1002011CA /* SSignal+Catch.m */, + A7919068240CFCE2002011CA /* SSignal+Combine.h */, + A791906B240CFCE2002011CA /* SSignal+Combine.m */, + A7919067240CFCE2002011CA /* SSignal+Dispatch.h */, + A7919076240CFCE2002011CA /* SSignal+Dispatch.m */, + A7919079240CFCE3002011CA /* SSignal+Mapping.h */, + A791907C240CFCE3002011CA /* SSignal+Mapping.m */, + A791905C240CFCE1002011CA /* SSignal+Meta.h */, + A791906F240CFCE2002011CA /* SSignal+Meta.m */, + A7919074240CFCE2002011CA /* SSignal+Multicast.h */, + A791906A240CFCE2002011CA /* SSignal+Multicast.m */, + A7919056240CFCE1002011CA /* SSignal+Pipe.h */, + A7919073240CFCE2002011CA /* SSignal+Pipe.m */, + A7919062240CFCE1002011CA /* SSignal+SideEffects.h */, + A7919069240CFCE2002011CA /* SSignal+SideEffects.m */, + A7919077240CFCE2002011CA /* SSignal+Single.h */, + A7919066240CFCE2002011CA /* SSignal+Single.m */, + A7919087240CFCE4002011CA /* SSignal+Take.h */, + A791905E240CFCE1002011CA /* SSignal+Take.m */, + A791907E240CFCE3002011CA /* SSignal+Timing.h */, + A791907B240CFCE3002011CA /* SSignal+Timing.m */, + A7919064240CFCE2002011CA /* SSignalKit.h */, + A7919081240CFCE3002011CA /* SSubscriber.h */, + A7919063240CFCE2002011CA /* SSubscriber.m */, + A7919086240CFCE4002011CA /* SThreadPool.h */, + A7919085240CFCE3002011CA /* SThreadPool.m */, + A791905B240CFCE1002011CA /* SThreadPoolQueue.h */, + A7919083240CFCE3002011CA /* SThreadPoolQueue.m */, + A791905F240CFCE1002011CA /* SThreadPoolTask.h */, + A7919075240CFCE2002011CA /* SThreadPoolTask.m */, + A7919055240CFCE1002011CA /* STimer.h */, + A791906D240CFCE2002011CA /* STimer.m */, + A791905D240CFCE1002011CA /* SVariable.h */, + A791905A240CFCE1002011CA /* SVariable.m */, + D0445DDB1A7C2CA500267924 /* Supporting Files */, + ); + name = SSignalKit; + path = "../../submodules/telegram-ios/submodules/SSignalKit/SSignalKit"; + sourceTree = ""; + }; + D0445DDB1A7C2CA500267924 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D0445DDC1A7C2CA500267924 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D0B417EE1D7DFA63004562A4 /* SwiftSignalKit */ = { + isa = PBXGroup; + children = ( + D0B417EF1D7DFA63004562A4 /* SwiftSignalKit.h */, + D0B417F01D7DFA63004562A4 /* Info.plist */, + ); + path = SwiftSignalKit; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A790A31C236B17DF000451B5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A791910F240CFD60002011CA /* SAtomic.h in Headers */, + A7919110240CFD60002011CA /* SBag.h in Headers */, + A7919111240CFD60002011CA /* SBlockDisposable.h in Headers */, + A7919112240CFD60002011CA /* SDisposable.h in Headers */, + A7919113240CFD60002011CA /* SDisposableSet.h in Headers */, + A7919114240CFD60002011CA /* SMetaDisposable.h in Headers */, + A7919115240CFD60002011CA /* SMulticastSignalManager.h in Headers */, + A7919116240CFD60002011CA /* SQueue.h in Headers */, + A7919117240CFD60002011CA /* SSignal.h in Headers */, + A7919118240CFD60002011CA /* SSignal+Accumulate.h in Headers */, + A7919119240CFD60002011CA /* SSignal+Catch.h in Headers */, + A791911A240CFD60002011CA /* SSignal+Combine.h in Headers */, + A791911B240CFD60002011CA /* SSignal+Dispatch.h in Headers */, + A791911C240CFD60002011CA /* SSignal+Mapping.h in Headers */, + A791911D240CFD60002011CA /* SSignal+Meta.h in Headers */, + A791911E240CFD60002011CA /* SSignal+Multicast.h in Headers */, + A791911F240CFD60002011CA /* SSignal+Pipe.h in Headers */, + A7919120240CFD60002011CA /* SSignal+SideEffects.h in Headers */, + A7919121240CFD60002011CA /* SSignal+Single.h in Headers */, + A7919122240CFD60002011CA /* SSignal+Take.h in Headers */, + A7919123240CFD60002011CA /* SSignal+Timing.h in Headers */, + A7919124240CFD60002011CA /* SSignalKit.h in Headers */, + A7919125240CFD60002011CA /* SSubscriber.h in Headers */, + A7919126240CFD60002011CA /* SThreadPool.h in Headers */, + A7919127240CFD60002011CA /* SThreadPoolQueue.h in Headers */, + A7919128240CFD60002011CA /* SThreadPoolTask.h in Headers */, + A7919129240CFD60002011CA /* STimer.h in Headers */, + A791912A240CFD60002011CA /* SVariable.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0B417EA1D7DFA63004562A4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919093240CFCE4002011CA /* SVariable.h in Headers */, + A79190AF240CFCE4002011CA /* SSignal+Mapping.h in Headers */, + A79190B6240CFCE4002011CA /* SSignal+Catch.h in Headers */, + A79190B4240CFCE4002011CA /* SSignal+Timing.h in Headers */, + A79190B7240CFCE4002011CA /* SSubscriber.h in Headers */, + A791908B240CFCE4002011CA /* STimer.h in Headers */, + A7919098240CFCE4002011CA /* SSignal+SideEffects.h in Headers */, + A7919091240CFCE4002011CA /* SThreadPoolQueue.h in Headers */, + A7919095240CFCE4002011CA /* SThreadPoolTask.h in Headers */, + A79190BA240CFCE4002011CA /* SMulticastSignalManager.h in Headers */, + A79190B3240CFCE4002011CA /* SQueue.h in Headers */, + A791908C240CFCE4002011CA /* SSignal+Pipe.h in Headers */, + A791909E240CFCE4002011CA /* SSignal+Combine.h in Headers */, + A791908E240CFCE4002011CA /* SSignal.h in Headers */, + A79190B0240CFCE4002011CA /* SBag.h in Headers */, + A79190BD240CFCE4002011CA /* SSignal+Take.h in Headers */, + A79190AD240CFCE4002011CA /* SSignal+Single.h in Headers */, + D0B417F11D7DFA63004562A4 /* SwiftSignalKit.h in Headers */, + A79190A8240CFCE4002011CA /* SAtomic.h in Headers */, + A79190A4240CFCE4002011CA /* SMetaDisposable.h in Headers */, + A791908F240CFCE4002011CA /* SSignal+Accumulate.h in Headers */, + A791909D240CFCE4002011CA /* SSignal+Dispatch.h in Headers */, + A79190AA240CFCE4002011CA /* SSignal+Multicast.h in Headers */, + A79190A7240CFCE4002011CA /* SBlockDisposable.h in Headers */, + A79190AE240CFCE4002011CA /* SDisposable.h in Headers */, + A79190BC240CFCE4002011CA /* SThreadPool.h in Headers */, + A791908D240CFCE4002011CA /* SDisposableSet.h in Headers */, + A7919092240CFCE4002011CA /* SSignal+Meta.h in Headers */, + A791909A240CFCE4002011CA /* SSignalKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A790A320236B17DF000451B5 /* SSignalKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = A790A332236B17DF000451B5 /* Build configuration list for PBXNativeTarget "SSignalKit" */; + buildPhases = ( + A790A31C236B17DF000451B5 /* Headers */, + A790A31D236B17DF000451B5 /* Sources */, + A790A31E236B17DF000451B5 /* Frameworks */, + A790A31F236B17DF000451B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SSignalKit; + productName = SSignalKit; + productReference = A790A321236B17DF000451B5 /* SSignalKit.framework */; + productType = "com.apple.product-type.framework"; + }; + D0B417EC1D7DFA63004562A4 /* SwiftSignalKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0B417F21D7DFA63004562A4 /* Build configuration list for PBXNativeTarget "SwiftSignalKit" */; + buildPhases = ( + D0B417E81D7DFA63004562A4 /* Sources */, + D0B417E91D7DFA63004562A4 /* Frameworks */, + D0B417EA1D7DFA63004562A4 /* Headers */, + D0B417EB1D7DFA63004562A4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SwiftSignalKit; + productName = SwiftSignalKitMac; + productReference = D0B417ED1D7DFA63004562A4 /* SwiftSignalKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D0445DCF1A7C2CA500267924 /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastSwiftMigration = 0700; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A790A320236B17DF000451B5 = { + CreatedOnToolsVersion = 10.3; + DevelopmentTeam = 6N38VWS5BX; + ProvisioningStyle = Automatic; + }; + D0B417EC1D7DFA63004562A4 = { + CreatedOnToolsVersion = 8.0; + LastSwiftMigration = 1030; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = D0445DD21A7C2CA500267924 /* Build configuration list for PBXProject "SSignalKit_Xcode" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = D0445DCE1A7C2CA500267924; + productRefGroup = D0445DD91A7C2CA500267924 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0B417EC1D7DFA63004562A4 /* SwiftSignalKit */, + A790A320236B17DF000451B5 /* SSignalKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A790A31F236B17DF000451B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0B417EB1D7DFA63004562A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A790A31D236B17DF000451B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A79190DA240CFD22002011CA /* SAtomic.m in Sources */, + A79190DC240CFD22002011CA /* SBag.m in Sources */, + A79190DE240CFD22002011CA /* SBlockDisposable.m in Sources */, + A79190E0240CFD22002011CA /* SDisposableSet.h in Sources */, + A79190E1240CFD22002011CA /* SDisposableSet.m in Sources */, + A79190E3240CFD22002011CA /* SMetaDisposable.m in Sources */, + A79190E5240CFD22002011CA /* SMulticastSignalManager.m in Sources */, + A79190E7240CFD22002011CA /* SQueue.m in Sources */, + A79190E9240CFD22002011CA /* SSignal.m in Sources */, + A79190EB240CFD22002011CA /* SSignal+Accumulate.m in Sources */, + A79190ED240CFD22002011CA /* SSignal+Catch.m in Sources */, + A79190EF240CFD22002011CA /* SSignal+Combine.m in Sources */, + A79190F1240CFD22002011CA /* SSignal+Dispatch.m in Sources */, + A79190F3240CFD22002011CA /* SSignal+Mapping.m in Sources */, + A79190F5240CFD22002011CA /* SSignal+Meta.m in Sources */, + A79190F7240CFD22002011CA /* SSignal+Multicast.m in Sources */, + A79190F9240CFD22002011CA /* SSignal+Pipe.m in Sources */, + A79190FB240CFD22002011CA /* SSignal+SideEffects.m in Sources */, + A79190FD240CFD22002011CA /* SSignal+Single.m in Sources */, + A79190FF240CFD22002011CA /* SSignal+Take.m in Sources */, + A7919101240CFD22002011CA /* SSignal+Timing.m in Sources */, + A7919104240CFD22002011CA /* SSubscriber.m in Sources */, + A7919106240CFD22002011CA /* SThreadPool.m in Sources */, + A7919108240CFD22002011CA /* SThreadPoolQueue.m in Sources */, + A791910A240CFD22002011CA /* SThreadPoolTask.m in Sources */, + A791910C240CFD22002011CA /* STimer.m in Sources */, + A791910E240CFD22002011CA /* SVariable.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0B417E81D7DFA63004562A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A79190BF240CFD14002011CA /* Atomic.swift in Sources */, + A79190C0240CFD14002011CA /* Bag.swift in Sources */, + A79190C1240CFD14002011CA /* Disposable.swift in Sources */, + A79190C2240CFD14002011CA /* Lock.swift in Sources */, + A79190C3240CFD14002011CA /* Multicast.swift in Sources */, + A79190C4240CFD14002011CA /* Promise.swift in Sources */, + A79190C5240CFD14002011CA /* Queue.swift in Sources */, + A79190C6240CFD14002011CA /* QueueLocalObject.swift in Sources */, + A79190C7240CFD14002011CA /* Signal_Catch.swift in Sources */, + A79190C8240CFD14002011CA /* Signal_Combine.swift in Sources */, + A79190C9240CFD14002011CA /* Signal_Dispatch.swift in Sources */, + A79190CA240CFD14002011CA /* Signal_Loop.swift in Sources */, + A79190CB240CFD14002011CA /* Signal_Mapping.swift in Sources */, + A79190CC240CFD14002011CA /* Signal_Materialize.swift in Sources */, + A79190CD240CFD14002011CA /* Signal_Merge.swift in Sources */, + A79190CE240CFD14002011CA /* Signal_Meta.swift in Sources */, + A79190CF240CFD14002011CA /* Signal_Reduce.swift in Sources */, + A79190D0240CFD14002011CA /* Signal_SideEffects.swift in Sources */, + A79190D1240CFD14002011CA /* Signal_Single.swift in Sources */, + A79190D2240CFD14002011CA /* Signal_Take.swift in Sources */, + A79190D3240CFD14002011CA /* Signal_Timing.swift in Sources */, + A79190D4240CFD14002011CA /* Signal.swift in Sources */, + A79190D5240CFD14002011CA /* Subscriber.swift in Sources */, + A79190D6240CFD14002011CA /* ThreadPool.swift in Sources */, + A79190D7240CFD14002011CA /* Timer.swift in Sources */, + A79190D8240CFD14002011CA /* ValuePipe.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A790A333236B17DF000451B5 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A790A334236B17DF000451B5 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A790A336236B17DF000451B5 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A790A338236B17DF000451B5 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A790A33A236B17DF000451B5 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7F282DC238EAB4B00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282DD238EAB4B00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; + A7F282DE238EAB4B00742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SSignalKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + D0364D5522B3E38E002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0364D5A22B3E38E002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + D0445DEC1A7C2CA500267924 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D0445DED1A7C2CA500267924 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D086A5741CC0117500F08284 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0B417F31D7DFA63004562A4 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + D0B417F41D7DFA63004562A4 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + D0B417F51D7DFA63004562A4 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + D0DB57B61E5C4B7A0071854C /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0DB57BB1E5C4B7A0071854C /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SwiftSignalKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.SwiftSignalKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A790A332236B17DF000451B5 /* Build configuration list for PBXNativeTarget "SSignalKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A333236B17DF000451B5 /* DebugHockeyapp */, + A790A334236B17DF000451B5 /* HockeyappMacAlpha */, + A790A336236B17DF000451B5 /* DebugAppStore */, + A7F282DE238EAB4B00742C20 /* Github */, + A790A338236B17DF000451B5 /* ReleaseHockeyapp */, + A790A33A236B17DF000451B5 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + D0445DD21A7C2CA500267924 /* Build configuration list for PBXProject "SSignalKit_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0445DEC1A7C2CA500267924 /* DebugHockeyapp */, + D0364D5522B3E38E002A6EF0 /* HockeyappMacAlpha */, + D0DB57B61E5C4B7A0071854C /* DebugAppStore */, + A7F282DC238EAB4B00742C20 /* Github */, + D0445DED1A7C2CA500267924 /* ReleaseHockeyapp */, + D086A5741CC0117500F08284 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + D0B417F21D7DFA63004562A4 /* Build configuration list for PBXNativeTarget "SwiftSignalKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0B417F31D7DFA63004562A4 /* DebugHockeyapp */, + D0364D5A22B3E38E002A6EF0 /* HockeyappMacAlpha */, + D0DB57BB1E5C4B7A0071854C /* DebugAppStore */, + A7F282DD238EAB4B00742C20 /* Github */, + D0B417F41D7DFA63004562A4 /* ReleaseHockeyapp */, + D0B417F51D7DFA63004562A4 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D0445DCF1A7C2CA500267924 /* Project object */; +} diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..2141ad337b --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..45f08369bd Binary files /dev/null and b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SSignalKit.xcscheme b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SSignalKit.xcscheme new file mode 100644 index 0000000000..b09b5d14f1 --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SSignalKit.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme new file mode 100644 index 0000000000..7684a4160e --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKitMac.xcscheme b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKitMac.xcscheme new file mode 100644 index 0000000000..38e04aebfa --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/SwiftSignalKitMac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..1995169846 --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + SSignalKit.xcscheme_^#shared#^_ + + orderHint + 38 + + SwiftSignalKit.xcscheme_^#shared#^_ + + orderHint + 39 + + SwiftSignalKitMac.xcscheme_^#shared#^_ + + orderHint + 40 + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SSignalKit.xcscheme b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SSignalKit.xcscheme new file mode 100644 index 0000000000..1cea9528d0 --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SSignalKit.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme new file mode 100644 index 0000000000..38e04aebfa --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SwiftSignalKit.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..9f43b65949 --- /dev/null +++ b/core-xprojects/SSignalKit/SSignalKit_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + SSignalKit.xcscheme_^#shared#^_ + + orderHint + 44 + + SwiftSignalKit.xcscheme_^#shared#^_ + + orderHint + 45 + + SwiftSignalKitMac.xcscheme_^#shared#^_ + + orderHint + 46 + + + + diff --git a/core-xprojects/SSignalKit/SwiftSignalKit/Info.plist b/core-xprojects/SSignalKit/SwiftSignalKit/Info.plist new file mode 100644 index 0000000000..ab41b55afe --- /dev/null +++ b/core-xprojects/SSignalKit/SwiftSignalKit/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 Telegram. All rights reserved. + NSPrincipalClass + + + diff --git a/core-xprojects/SSignalKit/SwiftSignalKit/SwiftSignalKit.h b/core-xprojects/SSignalKit/SwiftSignalKit/SwiftSignalKit.h new file mode 100644 index 0000000000..fe79ca3a9e --- /dev/null +++ b/core-xprojects/SSignalKit/SwiftSignalKit/SwiftSignalKit.h @@ -0,0 +1,19 @@ +// +// SwiftSignalKitMac.h +// SwiftSignalKitMac +// +// Created by Peter on 9/5/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +#import + +//! Project version number for SwiftSignalKitMac. +FOUNDATION_EXPORT double SwiftSignalKitVersionNumber; + +//! Project version string for SwiftSignalKitMac. +FOUNDATION_EXPORT const unsigned char SwiftSignalKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.pbxproj b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ce55fc36c1 --- /dev/null +++ b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.pbxproj @@ -0,0 +1,608 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7918E58240CF21B002011CA /* StringTransliterationPublic.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E56240CF21B002011CA /* StringTransliterationPublic.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918E5F240CF228002011CA /* StringTransliteration.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918E5E240CF228002011CA /* StringTransliteration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918E61240CF230002011CA /* StringTransliteration.m in Sources */ = {isa = PBXBuildFile; fileRef = A7918E60240CF230002011CA /* StringTransliteration.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7918E53240CF21B002011CA /* StringTransliteration.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StringTransliteration.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918E56240CF21B002011CA /* StringTransliterationPublic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StringTransliterationPublic.h; sourceTree = ""; }; + A7918E57240CF21B002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7918E5E240CF228002011CA /* StringTransliteration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringTransliteration.h; path = "../../../submodules/telegram-ios/submodules/StringTransliteration/PublicHeaders/StringTransliteration/StringTransliteration.h"; sourceTree = ""; }; + A7918E60240CF230002011CA /* StringTransliteration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StringTransliteration.m; path = "../../../submodules/telegram-ios/submodules/StringTransliteration/Sources/StringTransliteration.m"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7918E50240CF21B002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7918E49240CF21B002011CA = { + isa = PBXGroup; + children = ( + A7918E55240CF21B002011CA /* StringTransliteration */, + A7918E54240CF21B002011CA /* Products */, + ); + sourceTree = ""; + }; + A7918E54240CF21B002011CA /* Products */ = { + isa = PBXGroup; + children = ( + A7918E53240CF21B002011CA /* StringTransliteration.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7918E55240CF21B002011CA /* StringTransliteration */ = { + isa = PBXGroup; + children = ( + A7918E60240CF230002011CA /* StringTransliteration.m */, + A7918E5E240CF228002011CA /* StringTransliteration.h */, + A7918E56240CF21B002011CA /* StringTransliterationPublic.h */, + A7918E57240CF21B002011CA /* Info.plist */, + ); + path = StringTransliteration; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7918E4E240CF21B002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918E58240CF21B002011CA /* StringTransliterationPublic.h in Headers */, + A7918E5F240CF228002011CA /* StringTransliteration.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7918E52240CF21B002011CA /* StringTransliteration */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7918E5B240CF21B002011CA /* Build configuration list for PBXNativeTarget "StringTransliteration" */; + buildPhases = ( + A7918E4E240CF21B002011CA /* Headers */, + A7918E4F240CF21B002011CA /* Sources */, + A7918E50240CF21B002011CA /* Frameworks */, + A7918E51240CF21B002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StringTransliteration; + productName = StringTransliteration; + productReference = A7918E53240CF21B002011CA /* StringTransliteration.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7918E4A240CF21B002011CA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7918E52240CF21B002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A7918E4D240CF21B002011CA /* Build configuration list for PBXProject "StringTransliteration" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7918E49240CF21B002011CA; + productRefGroup = A7918E54240CF21B002011CA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7918E52240CF21B002011CA /* StringTransliteration */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7918E51240CF21B002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7918E4F240CF21B002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918E61240CF230002011CA /* StringTransliteration.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7918E59240CF21B002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7918E5A240CF21B002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7918E5C240CF21B002011CA /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = StringTransliteration/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.StringTransliteration; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A7918E5D240CF21B002011CA /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = StringTransliteration/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.StringTransliteration; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A7918E62240CF269002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7918E63240CF269002011CA /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = StringTransliteration/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.StringTransliteration; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A7918E64240CF26D002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7918E65240CF26D002011CA /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = StringTransliteration/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.StringTransliteration; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7918E66240CF275002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7918E67240CF275002011CA /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = StringTransliteration/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.StringTransliteration; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7918E4D240CF21B002011CA /* Build configuration list for PBXProject "StringTransliteration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918E59240CF21B002011CA /* DebugHockeyapp */, + A7918E64240CF26D002011CA /* HockeyappMacAlpha */, + A7918E62240CF269002011CA /* DebugAppStore */, + A7918E5A240CF21B002011CA /* ReleaseHockeyapp */, + A7918E66240CF275002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A7918E5B240CF21B002011CA /* Build configuration list for PBXNativeTarget "StringTransliteration" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918E5C240CF21B002011CA /* DebugHockeyapp */, + A7918E65240CF26D002011CA /* HockeyappMacAlpha */, + A7918E63240CF269002011CA /* DebugAppStore */, + A7918E5D240CF21B002011CA /* ReleaseHockeyapp */, + A7918E67240CF275002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7918E4A240CF21B002011CA /* Project object */; +} diff --git a/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..ab496dd063 --- /dev/null +++ b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/StringTransliteration/StringTransliteration.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/StringTransliteration/StringTransliteration/Info.plist b/core-xprojects/StringTransliteration/StringTransliteration/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/core-xprojects/StringTransliteration/StringTransliteration/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/core-xprojects/StringTransliteration/StringTransliteration/StringTransliterationPublic.h b/core-xprojects/StringTransliteration/StringTransliteration/StringTransliterationPublic.h new file mode 100644 index 0000000000..def56d9905 --- /dev/null +++ b/core-xprojects/StringTransliteration/StringTransliteration/StringTransliterationPublic.h @@ -0,0 +1,20 @@ +// +// StringTransliteration.h +// StringTransliteration +// +// Created by Mikhail Filimonov on 02.03.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for StringTransliteration. +FOUNDATION_EXPORT double StringTransliterationVersionNumber; + +//! Project version string for StringTransliteration. +FOUNDATION_EXPORT const unsigned char StringTransliterationVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/core-xprojects/SyncCore/SyncCore/Info.plist b/core-xprojects/SyncCore/SyncCore/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/SyncCore/SyncCore/SyncCore.h b/core-xprojects/SyncCore/SyncCore/SyncCore.h new file mode 100644 index 0000000000..8517b20b16 --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore/SyncCore.h @@ -0,0 +1,19 @@ +// +// SyncCore.h +// SyncCore +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for SyncCore. +FOUNDATION_EXPORT double SyncCoreVersionNumber; + +//! Project version string for SyncCore. +FOUNDATION_EXPORT const unsigned char SyncCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.pbxproj b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ce8a06113a --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1288 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A701F7BA236B295F002ABF81 /* SyncCore.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F7B8236B295F002ABF81 /* SyncCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A701F856236B2A11002ABF81 /* TelegramGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7C9236B2A11002ABF81 /* TelegramGroup.swift */; }; + A701F857236B2A11002ABF81 /* OutgoingContentInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CA236B2A11002ABF81 /* OutgoingContentInfoMessageAttribute.swift */; }; + A701F858236B2A11002ABF81 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CB236B2A11002ABF81 /* TelegramPeerNotificationSettings.swift */; }; + A701F859236B2A11002ABF81 /* EmojiKeywordCollectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CC236B2A11002ABF81 /* EmojiKeywordCollectionInfo.swift */; }; + A701F85A236B2A11002ABF81 /* OutgoingMessageInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CD236B2A11002ABF81 /* OutgoingMessageInfoAttribute.swift */; }; + A701F85B236B2A11002ABF81 /* TelegramChatBannedRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CE236B2A11002ABF81 /* TelegramChatBannedRights.swift */; }; + A701F85C236B2A11002ABF81 /* TelegramWallpaper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7CF236B2A11002ABF81 /* TelegramWallpaper.swift */; }; + A701F85D236B2A11002ABF81 /* AccountBackupDataAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D0236B2A11002ABF81 /* AccountBackupDataAttribute.swift */; }; + A701F85E236B2A11002ABF81 /* LoggingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D1236B2A11002ABF81 /* LoggingSettings.swift */; }; + A701F85F236B2A11002ABF81 /* TelegramUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D2236B2A11002ABF81 /* TelegramUser.swift */; }; + A701F860236B2A11002ABF81 /* CachedStickerQueryResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D3236B2A11002ABF81 /* CachedStickerQueryResult.swift */; }; + A701F861236B2A11002ABF81 /* LimitsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D4236B2A11002ABF81 /* LimitsConfiguration.swift */; }; + A701F862236B2A11002ABF81 /* OutgoingScheduleInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D5236B2A11002ABF81 /* OutgoingScheduleInfoMessageAttribute.swift */; }; + A701F863236B2A11002ABF81 /* SecretChatOutgoingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D6236B2A11002ABF81 /* SecretChatOutgoingOperation.swift */; }; + A701F864236B2A11002ABF81 /* SecretChatIncomingDecryptedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D7236B2A11002ABF81 /* SecretChatIncomingDecryptedOperation.swift */; }; + A701F865236B2A11002ABF81 /* TelegramMediaUnsupported.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D8236B2A11002ABF81 /* TelegramMediaUnsupported.swift */; }; + A701F866236B2A11002ABF81 /* Namespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7D9236B2A11002ABF81 /* Namespaces.swift */; }; + A701F867236B2A11002ABF81 /* ThemeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DA236B2A11002ABF81 /* ThemeSettings.swift */; }; + A701F868236B2A11002ABF81 /* CachedLocalizationInfos.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DB236B2A11002ABF81 /* CachedLocalizationInfos.swift */; }; + A701F869236B2A11002ABF81 /* BotInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DC236B2A11002ABF81 /* BotInfo.swift */; }; + A701F86A236B2A11002ABF81 /* CachedResolvedByNamePeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DD236B2A11002ABF81 /* CachedResolvedByNamePeer.swift */; }; + A701F86B236B2A11002ABF81 /* CachedGroupParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DE236B2A11002ABF81 /* CachedGroupParticipants.swift */; }; + A701F86C236B2A11002ABF81 /* ViewCountMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7DF236B2A11002ABF81 /* ViewCountMessageAttribute.swift */; }; + A701F86D236B2A11002ABF81 /* AccountEnvironmentAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E0236B2A11002ABF81 /* AccountEnvironmentAttribute.swift */; }; + A701F86E236B2A11002ABF81 /* TelegramDeviceContactImportedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E1236B2A11002ABF81 /* TelegramDeviceContactImportedData.swift */; }; + A701F86F236B2A11002ABF81 /* SynchronizeEmojiKeywordsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E2236B2A11002ABF81 /* SynchronizeEmojiKeywordsOperation.swift */; }; + A701F870236B2A11002ABF81 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E3236B2A11002ABF81 /* Localization.swift */; }; + A701F871236B2A11002ABF81 /* CachedThemesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E4236B2A11002ABF81 /* CachedThemesConfiguration.swift */; }; + A701F872236B2A11002ABF81 /* StickerPackCollectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E5236B2A11002ABF81 /* StickerPackCollectionInfo.swift */; }; + A701F873236B2A11002ABF81 /* ImportableDeviceContactData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E6236B2A11002ABF81 /* ImportableDeviceContactData.swift */; }; + A701F874236B2A11002ABF81 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E7236B2A11002ABF81 /* AppConfiguration.swift */; }; + A701F875236B2A11002ABF81 /* SendScheduledMessageImmediatelyAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E8236B2A11002ABF81 /* SendScheduledMessageImmediatelyAction.swift */; }; + A701F876236B2A11002ABF81 /* SynchronizeAppLogEventsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7E9236B2A11002ABF81 /* SynchronizeAppLogEventsOperation.swift */; }; + A701F877236B2A11002ABF81 /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7EA236B2A11002ABF81 /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */; }; + A701F878236B2A11002ABF81 /* ProxySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7EB236B2A11002ABF81 /* ProxySettings.swift */; }; + A701F879236B2A11002ABF81 /* TextEntitiesMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7EC236B2A11002ABF81 /* TextEntitiesMessageAttribute.swift */; }; + A701F87A236B2A11002ABF81 /* AccountSortOrderAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7ED236B2A11002ABF81 /* AccountSortOrderAttribute.swift */; }; + A701F87B236B2A11002ABF81 /* TelegramMediaWebpage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7EE236B2A11002ABF81 /* TelegramMediaWebpage.swift */; }; + A701F87C236B2A11002ABF81 /* AutoremoveTimeoutMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7EF236B2A11002ABF81 /* AutoremoveTimeoutMessageAttribute.swift */; }; + A701F87D236B2A11002ABF81 /* UpdateMessageReactionsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F0236B2A11002ABF81 /* UpdateMessageReactionsAction.swift */; }; + A701F87E236B2A11002ABF81 /* TelegramMediaWebFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F1236B2A11002ABF81 /* TelegramMediaWebFile.swift */; }; + A701F87F236B2A11002ABF81 /* FeaturedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F2236B2A11002ABF81 /* FeaturedStickerPack.swift */; }; + A701F880236B2A11002ABF81 /* EditedMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F3236B2A11002ABF81 /* EditedMessageAttribute.swift */; }; + A701F881236B2A11002ABF81 /* RecentPeerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F4236B2A11002ABF81 /* RecentPeerItem.swift */; }; + A701F882236B2A11002ABF81 /* ArchivedStickerPacksInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F5236B2A11002ABF81 /* ArchivedStickerPacksInfo.swift */; }; + A701F883236B2A11002ABF81 /* RestrictedContentMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F6236B2A11002ABF81 /* RestrictedContentMessageAttribute.swift */; }; + A701F884236B2A11002ABF81 /* TelegramMediaGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F7236B2A11002ABF81 /* TelegramMediaGame.swift */; }; + A701F885236B2A11002ABF81 /* ForwardSourceInfoAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F8236B2A11002ABF81 /* ForwardSourceInfoAttribute.swift */; }; + A701F886236B2A11002ABF81 /* TelegramSecretChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7F9236B2A11002ABF81 /* TelegramSecretChat.swift */; }; + A701F887236B2A11002ABF81 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FA236B2A11002ABF81 /* NotificationInfoMessageAttribute.swift */; }; + A701F888236B2A11002ABF81 /* CachedChannelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FB236B2A11002ABF81 /* CachedChannelData.swift */; }; + A701F889236B2A11002ABF81 /* TelegramMediaPoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FC236B2A11002ABF81 /* TelegramMediaPoll.swift */; }; + A701F88A236B2A11002ABF81 /* ConsumableContentMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FD236B2A11002ABF81 /* ConsumableContentMessageAttribute.swift */; }; + A701F88B236B2A11002ABF81 /* RemoteStorageConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FE236B2A11002ABF81 /* RemoteStorageConfiguration.swift */; }; + A701F88C236B2A11002ABF81 /* PeerAccessHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F7FF236B2A11002ABF81 /* PeerAccessHash.swift */; }; + A701F88D236B2A11002ABF81 /* LocalizationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F800236B2A11002ABF81 /* LocalizationInfo.swift */; }; + A701F88E236B2A11002ABF81 /* CachedGroupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F801236B2A11002ABF81 /* CachedGroupData.swift */; }; + A701F88F236B2A11002ABF81 /* InlineBotMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F802236B2A11002ABF81 /* InlineBotMessageAttribute.swift */; }; + A701F890236B2A11002ABF81 /* AuthorSignatureMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F803236B2A11002ABF81 /* AuthorSignatureMessageAttribute.swift */; }; + A701F891236B2A11002ABF81 /* ChannelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F804236B2A11002ABF81 /* ChannelState.swift */; }; + A701F892236B2A11002ABF81 /* TelegramMediaImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F805236B2A11002ABF81 /* TelegramMediaImage.swift */; }; + A701F893236B2A11002ABF81 /* ReplyMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F806236B2A11002ABF81 /* ReplyMessageAttribute.swift */; }; + A701F894236B2A11002ABF81 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F807236B2A11002ABF81 /* ReactionsMessageAttribute.swift */; }; + A701F895236B2A11002ABF81 /* TemporaryTwoStepPasswordToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F808236B2A11002ABF81 /* TemporaryTwoStepPasswordToken.swift */; }; + A701F896236B2A11002ABF81 /* TelegramTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F809236B2A11002ABF81 /* TelegramTheme.swift */; }; + A701F897236B2A11002ABF81 /* ConsumePersonalMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80A236B2A11002ABF81 /* ConsumePersonalMessageAction.swift */; }; + A701F898236B2A11002ABF81 /* SavedStickerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80B236B2A11002ABF81 /* SavedStickerItem.swift */; }; + A701F899236B2A11002ABF81 /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80C236B2A11002ABF81 /* CacheStorageSettings.swift */; }; + A701F89A236B2A11002ABF81 /* UnauthorizedAccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80D236B2A11002ABF81 /* UnauthorizedAccountState.swift */; }; + A701F89B236B2A11002ABF81 /* TelegramMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80E236B2A11002ABF81 /* TelegramMediaResource.swift */; }; + A701F89C236B2A11002ABF81 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F80F236B2A11002ABF81 /* JSON.swift */; }; + A701F89D236B2A11002ABF81 /* NetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F810236B2A11002ABF81 /* NetworkSettings.swift */; }; + A701F89E236B2A11002ABF81 /* TelegramMediaInvoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F811236B2A11002ABF81 /* TelegramMediaInvoice.swift */; }; + A701F89F236B2A11002ABF81 /* SuggestedLocalizationEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F812236B2A11002ABF81 /* SuggestedLocalizationEntry.swift */; }; + A701F8A0236B2A11002ABF81 /* LocalizationListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F813236B2A11002ABF81 /* LocalizationListState.swift */; }; + A701F8A1236B2A11002ABF81 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F814236B2A11002ABF81 /* SynchronizeSavedStickersOperation.swift */; }; + A701F8A2236B2A11002ABF81 /* SynchronizeableChatInputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F815236B2A11002ABF81 /* SynchronizeableChatInputState.swift */; }; + A701F8A3236B2A11002ABF81 /* SecretChatSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F816236B2A11002ABF81 /* SecretChatSettings.swift */; }; + A701F8A4236B2A12002ABF81 /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F817236B2A11002ABF81 /* SecretChatState.swift */; }; + A701F8A5236B2A12002ABF81 /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F818236B2A11002ABF81 /* ContentPrivacySettings.swift */; }; + A701F8A6236B2A12002ABF81 /* CachedWallpapersConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F819236B2A11002ABF81 /* CachedWallpapersConfiguration.swift */; }; + A701F8A7236B2A12002ABF81 /* VoipConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81A236B2A11002ABF81 /* VoipConfiguration.swift */; }; + A701F8A8236B2A12002ABF81 /* OutgoingChatContextResultMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81B236B2A11002ABF81 /* OutgoingChatContextResultMessageAttribute.swift */; }; + A701F8A9236B2A12002ABF81 /* PeerGroupMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81C236B2A11002ABF81 /* PeerGroupMessageStateVersionAttribute.swift */; }; + A701F8AA236B2A12002ABF81 /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81D236B2A11002ABF81 /* ChannelMessageStateVersionAttribute.swift */; }; + A701F8AB236B2A12002ABF81 /* SourceReferenceMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81E236B2A11002ABF81 /* SourceReferenceMessageAttribute.swift */; }; + A701F8AC236B2A12002ABF81 /* AutodownloadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F81F236B2A11002ABF81 /* AutodownloadSettings.swift */; }; + A701F8AD236B2A12002ABF81 /* SecureIdFileReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F820236B2A11002ABF81 /* SecureIdFileReference.swift */; }; + A701F8AE236B2A12002ABF81 /* TelegramMediaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F821236B2A11002ABF81 /* TelegramMediaAction.swift */; }; + A701F8AF236B2A12002ABF81 /* TelegramChatAdminRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F822236B2A11002ABF81 /* TelegramChatAdminRights.swift */; }; + A701F8B0236B2A12002ABF81 /* StandaloneAccountTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F823236B2A11002ABF81 /* StandaloneAccountTransaction.swift */; }; + A701F8B1236B2A12002ABF81 /* TelegramMediaMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F824236B2A11002ABF81 /* TelegramMediaMap.swift */; }; + A701F8B2236B2A12002ABF81 /* SuggestedLocalizationUpdatesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F825236B2A11002ABF81 /* SuggestedLocalizationUpdatesOperation.swift */; }; + A701F8B3236B2A12002ABF81 /* SecretChatKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F826236B2A11002ABF81 /* SecretChatKeychain.swift */; }; + A701F8B4236B2A12002ABF81 /* SecretChatEncryptionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F827236B2A11002ABF81 /* SecretChatEncryptionConfig.swift */; }; + A701F8B5236B2A12002ABF81 /* PeerStatusSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F828236B2A11002ABF81 /* PeerStatusSettings.swift */; }; + A701F8B6236B2A12002ABF81 /* SecureFileMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F829236B2A11002ABF81 /* SecureFileMediaResource.swift */; }; + A701F8B7236B2A12002ABF81 /* SearchBotsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82A236B2A11002ABF81 /* SearchBotsConfiguration.swift */; }; + A701F8B8236B2A12002ABF81 /* PeerAccessRestrictionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82B236B2A11002ABF81 /* PeerAccessRestrictionInfo.swift */; }; + A701F8B9236B2A12002ABF81 /* TelegramMediaContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82C236B2A11002ABF81 /* TelegramMediaContact.swift */; }; + A701F8BA236B2A12002ABF81 /* SynchronizeSavedGifsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82D236B2A11002ABF81 /* SynchronizeSavedGifsOperation.swift */; }; + A701F8BB236B2A12002ABF81 /* ContentRequiresValidationMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82E236B2A11002ABF81 /* ContentRequiresValidationMessageAttribute.swift */; }; + A701F8BC236B2A12002ABF81 /* WalletCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F82F236B2A11002ABF81 /* WalletCollection.swift */; }; + A701F8BD236B2A12002ABF81 /* SecretChatFileReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F830236B2A11002ABF81 /* SecretChatFileReference.swift */; }; + A701F8BE236B2A12002ABF81 /* EmojiKeywordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F831236B2A11002ABF81 /* EmojiKeywordItem.swift */; }; + A701F8BF236B2A12002ABF81 /* CachedRecentPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F832236B2A11002ABF81 /* CachedRecentPeers.swift */; }; + A701F8C0236B2A12002ABF81 /* ContactsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F833236B2A11002ABF81 /* ContactsSettings.swift */; }; + A701F8C1236B2A12002ABF81 /* SynchronizeRecentlyUsedMediaOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F834236B2A11002ABF81 /* SynchronizeRecentlyUsedMediaOperation.swift */; }; + A701F8C2236B2A12002ABF81 /* ExportedInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F835236B2A11002ABF81 /* ExportedInvitation.swift */; }; + A701F8C3236B2A12002ABF81 /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F836236B2A11002ABF81 /* SynchronizeChatInputStateOperation.swift */; }; + A701F8C4236B2A12002ABF81 /* SynchronizeGroupedPeersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F837236B2A11002ABF81 /* SynchronizeGroupedPeersOperation.swift */; }; + A701F8C5236B2A12002ABF81 /* SecretChatIncomingEncryptedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F838236B2A11002ABF81 /* SecretChatIncomingEncryptedOperation.swift */; }; + A701F8C6236B2A12002ABF81 /* LoggedOutAccountAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F839236B2A11002ABF81 /* LoggedOutAccountAttribute.swift */; }; + A701F8C7236B2A12002ABF81 /* SynchronizePinnedChatsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83A236B2A11002ABF81 /* SynchronizePinnedChatsOperation.swift */; }; + A701F8C8236B2A12002ABF81 /* ConsumablePersonalMentionMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83B236B2A11002ABF81 /* ConsumablePersonalMentionMessageAttribute.swift */; }; + A701F8C9236B2A12002ABF81 /* LocalizationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83C236B2A11002ABF81 /* LocalizationSettings.swift */; }; + A701F8CA236B2A12002ABF81 /* MediaReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83D236B2A11002ABF81 /* MediaReference.swift */; }; + A701F8CB236B2A12002ABF81 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83E236B2A11002ABF81 /* SynchronizeConsumeMessageContentsOperation.swift */; }; + A701F8CC236B2A12002ABF81 /* StickerPackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F83F236B2A11002ABF81 /* StickerPackItem.swift */; }; + A701F8CD236B2A12002ABF81 /* CloudChatRemoveMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F840236B2A11002ABF81 /* CloudChatRemoveMessagesOperation.swift */; }; + A701F8CE236B2A12002ABF81 /* RichText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F841236B2A11002ABF81 /* RichText.swift */; }; + A701F8CF236B2A12002ABF81 /* WasScheduledMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F842236B2A11002ABF81 /* WasScheduledMessageAttribute.swift */; }; + A701F8D0236B2A12002ABF81 /* RecentHashtagItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F843236B2A11002ABF81 /* RecentHashtagItem.swift */; }; + A701F8D1236B2A12002ABF81 /* RecentMediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F844236B2A11002ABF81 /* RecentMediaItem.swift */; }; + A701F8D2236B2A12002ABF81 /* CachedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F845236B2A11002ABF81 /* CachedStickerPack.swift */; }; + A701F8D3236B2A12002ABF81 /* InstantPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F846236B2A11002ABF81 /* InstantPage.swift */; }; + A701F8D4236B2A12002ABF81 /* AppChangelogState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F847236B2A11002ABF81 /* AppChangelogState.swift */; }; + A701F8D5236B2A12002ABF81 /* CloudFileMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F848236B2A11002ABF81 /* CloudFileMediaResource.swift */; }; + A701F8D6236B2A12002ABF81 /* CachedSecureIdConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F849236B2A11002ABF81 /* CachedSecureIdConfiguration.swift */; }; + A701F8D7236B2A12002ABF81 /* TelegramChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84A236B2A11002ABF81 /* TelegramChannel.swift */; }; + A701F8D8236B2A12002ABF81 /* TelegramMediaExpiredContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84B236B2A11002ABF81 /* TelegramMediaExpiredContent.swift */; }; + A701F8D9236B2A12002ABF81 /* RegularChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84C236B2A11002ABF81 /* RegularChatState.swift */; }; + A701F8DA236B2A12002ABF81 /* CachedUserData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84D236B2A11002ABF81 /* CachedUserData.swift */; }; + A701F8DB236B2A12002ABF81 /* SecretFileEncryptionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84E236B2A11002ABF81 /* SecretFileEncryptionKey.swift */; }; + A701F8DC236B2A12002ABF81 /* GlobalNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F84F236B2A11002ABF81 /* GlobalNotificationSettings.swift */; }; + A701F8DD236B2A12002ABF81 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F850236B2A11002ABF81 /* TelegramUserPresence.swift */; }; + A701F8DE236B2A12002ABF81 /* TelegramMediaFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F851236B2A11002ABF81 /* TelegramMediaFile.swift */; }; + A701F8DF236B2A12002ABF81 /* PeerReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F852236B2A11002ABF81 /* PeerReference.swift */; }; + A701F8E0236B2A12002ABF81 /* ReplyMarkupMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F853236B2A11002ABF81 /* ReplyMarkupMessageAttribute.swift */; }; + A701F8E1236B2A12002ABF81 /* AuthorizedAccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F854236B2A11002ABF81 /* AuthorizedAccountState.swift */; }; + A701F8E2236B2A12002ABF81 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F855236B2A11002ABF81 /* SynchronizeInstalledStickerPacksOperations.swift */; }; + A701F8EC236B2E30002ABF81 /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F8EB236B2E30002ABF81 /* Postbox.framework */; }; + A701F92A236C1AF7002ABF81 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F929236C1AF7002ABF81 /* SwiftSignalKit.framework */; }; + A7393D2D2407A14800CE44CA /* TelegramMediaDice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7393D2C2407A14800CE44CA /* TelegramMediaDice.swift */; }; + A754972623A3A5860091F293 /* EmbeddedMediaStickersMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = A754972523A3A5860091F293 /* EmbeddedMediaStickersMessageAttribute.swift */; }; + A7D28201236C379D0000A9BF /* PixelDimensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D28200236C379D0000A9BF /* PixelDimensions.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A701F7B5236B295F002ABF81 /* SyncCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SyncCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F7B8236B295F002ABF81 /* SyncCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SyncCore.h; sourceTree = ""; }; + A701F7B9236B295F002ABF81 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A701F7C9236B2A11002ABF81 /* TelegramGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramGroup.swift; sourceTree = ""; }; + A701F7CA236B2A11002ABF81 /* OutgoingContentInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingContentInfoMessageAttribute.swift; sourceTree = ""; }; + A701F7CB236B2A11002ABF81 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = ""; }; + A701F7CC236B2A11002ABF81 /* EmojiKeywordCollectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiKeywordCollectionInfo.swift; sourceTree = ""; }; + A701F7CD236B2A11002ABF81 /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = ""; }; + A701F7CE236B2A11002ABF81 /* TelegramChatBannedRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChatBannedRights.swift; sourceTree = ""; }; + A701F7CF236B2A11002ABF81 /* TelegramWallpaper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramWallpaper.swift; sourceTree = ""; }; + A701F7D0236B2A11002ABF81 /* AccountBackupDataAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountBackupDataAttribute.swift; sourceTree = ""; }; + A701F7D1236B2A11002ABF81 /* LoggingSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingSettings.swift; sourceTree = ""; }; + A701F7D2236B2A11002ABF81 /* TelegramUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUser.swift; sourceTree = ""; }; + A701F7D3236B2A11002ABF81 /* CachedStickerQueryResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedStickerQueryResult.swift; sourceTree = ""; }; + A701F7D4236B2A11002ABF81 /* LimitsConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LimitsConfiguration.swift; sourceTree = ""; }; + A701F7D5236B2A11002ABF81 /* OutgoingScheduleInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingScheduleInfoMessageAttribute.swift; sourceTree = ""; }; + A701F7D6236B2A11002ABF81 /* SecretChatOutgoingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatOutgoingOperation.swift; sourceTree = ""; }; + A701F7D7236B2A11002ABF81 /* SecretChatIncomingDecryptedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatIncomingDecryptedOperation.swift; sourceTree = ""; }; + A701F7D8236B2A11002ABF81 /* TelegramMediaUnsupported.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaUnsupported.swift; sourceTree = ""; }; + A701F7D9236B2A11002ABF81 /* Namespaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Namespaces.swift; sourceTree = ""; }; + A701F7DA236B2A11002ABF81 /* ThemeSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSettings.swift; sourceTree = ""; }; + A701F7DB236B2A11002ABF81 /* CachedLocalizationInfos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedLocalizationInfos.swift; sourceTree = ""; }; + A701F7DC236B2A11002ABF81 /* BotInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotInfo.swift; sourceTree = ""; }; + A701F7DD236B2A11002ABF81 /* CachedResolvedByNamePeer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedResolvedByNamePeer.swift; sourceTree = ""; }; + A701F7DE236B2A11002ABF81 /* CachedGroupParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedGroupParticipants.swift; sourceTree = ""; }; + A701F7DF236B2A11002ABF81 /* ViewCountMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewCountMessageAttribute.swift; sourceTree = ""; }; + A701F7E0236B2A11002ABF81 /* AccountEnvironmentAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountEnvironmentAttribute.swift; sourceTree = ""; }; + A701F7E1236B2A11002ABF81 /* TelegramDeviceContactImportedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramDeviceContactImportedData.swift; sourceTree = ""; }; + A701F7E2236B2A11002ABF81 /* SynchronizeEmojiKeywordsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeEmojiKeywordsOperation.swift; sourceTree = ""; }; + A701F7E3236B2A11002ABF81 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + A701F7E4236B2A11002ABF81 /* CachedThemesConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedThemesConfiguration.swift; sourceTree = ""; }; + A701F7E5236B2A11002ABF81 /* StickerPackCollectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackCollectionInfo.swift; sourceTree = ""; }; + A701F7E6236B2A11002ABF81 /* ImportableDeviceContactData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableDeviceContactData.swift; sourceTree = ""; }; + A701F7E7236B2A11002ABF81 /* AppConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; + A701F7E8236B2A11002ABF81 /* SendScheduledMessageImmediatelyAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendScheduledMessageImmediatelyAction.swift; sourceTree = ""; }; + A701F7E9236B2A11002ABF81 /* SynchronizeAppLogEventsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeAppLogEventsOperation.swift; sourceTree = ""; }; + A701F7EA236B2A11002ABF81 /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeMarkAllUnseenPersonalMessagesOperation.swift; sourceTree = ""; }; + A701F7EB236B2A11002ABF81 /* ProxySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySettings.swift; sourceTree = ""; }; + A701F7EC236B2A11002ABF81 /* TextEntitiesMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntitiesMessageAttribute.swift; sourceTree = ""; }; + A701F7ED236B2A11002ABF81 /* AccountSortOrderAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountSortOrderAttribute.swift; sourceTree = ""; }; + A701F7EE236B2A11002ABF81 /* TelegramMediaWebpage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaWebpage.swift; sourceTree = ""; }; + A701F7EF236B2A11002ABF81 /* AutoremoveTimeoutMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoremoveTimeoutMessageAttribute.swift; sourceTree = ""; }; + A701F7F0236B2A11002ABF81 /* UpdateMessageReactionsAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateMessageReactionsAction.swift; sourceTree = ""; }; + A701F7F1236B2A11002ABF81 /* TelegramMediaWebFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaWebFile.swift; sourceTree = ""; }; + A701F7F2236B2A11002ABF81 /* FeaturedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeaturedStickerPack.swift; sourceTree = ""; }; + A701F7F3236B2A11002ABF81 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; + A701F7F4236B2A11002ABF81 /* RecentPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentPeerItem.swift; sourceTree = ""; }; + A701F7F5236B2A11002ABF81 /* ArchivedStickerPacksInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchivedStickerPacksInfo.swift; sourceTree = ""; }; + A701F7F6236B2A11002ABF81 /* RestrictedContentMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestrictedContentMessageAttribute.swift; sourceTree = ""; }; + A701F7F7236B2A11002ABF81 /* TelegramMediaGame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaGame.swift; sourceTree = ""; }; + A701F7F8236B2A11002ABF81 /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = ""; }; + A701F7F9236B2A11002ABF81 /* TelegramSecretChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramSecretChat.swift; sourceTree = ""; }; + A701F7FA236B2A11002ABF81 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = ""; }; + A701F7FB236B2A11002ABF81 /* CachedChannelData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedChannelData.swift; sourceTree = ""; }; + A701F7FC236B2A11002ABF81 /* TelegramMediaPoll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaPoll.swift; sourceTree = ""; }; + A701F7FD236B2A11002ABF81 /* ConsumableContentMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumableContentMessageAttribute.swift; sourceTree = ""; }; + A701F7FE236B2A11002ABF81 /* RemoteStorageConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteStorageConfiguration.swift; sourceTree = ""; }; + A701F7FF236B2A11002ABF81 /* PeerAccessHash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAccessHash.swift; sourceTree = ""; }; + A701F800236B2A11002ABF81 /* LocalizationInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationInfo.swift; sourceTree = ""; }; + A701F801236B2A11002ABF81 /* CachedGroupData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedGroupData.swift; sourceTree = ""; }; + A701F802236B2A11002ABF81 /* InlineBotMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InlineBotMessageAttribute.swift; sourceTree = ""; }; + A701F803236B2A11002ABF81 /* AuthorSignatureMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorSignatureMessageAttribute.swift; sourceTree = ""; }; + A701F804236B2A11002ABF81 /* ChannelState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelState.swift; sourceTree = ""; }; + A701F805236B2A11002ABF81 /* TelegramMediaImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaImage.swift; sourceTree = ""; }; + A701F806236B2A11002ABF81 /* ReplyMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMessageAttribute.swift; sourceTree = ""; }; + A701F807236B2A11002ABF81 /* ReactionsMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMessageAttribute.swift; sourceTree = ""; }; + A701F808236B2A11002ABF81 /* TemporaryTwoStepPasswordToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemporaryTwoStepPasswordToken.swift; sourceTree = ""; }; + A701F809236B2A11002ABF81 /* TelegramTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramTheme.swift; sourceTree = ""; }; + A701F80A236B2A11002ABF81 /* ConsumePersonalMessageAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumePersonalMessageAction.swift; sourceTree = ""; }; + A701F80B236B2A11002ABF81 /* SavedStickerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavedStickerItem.swift; sourceTree = ""; }; + A701F80C236B2A11002ABF81 /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = ""; }; + A701F80D236B2A11002ABF81 /* UnauthorizedAccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnauthorizedAccountState.swift; sourceTree = ""; }; + A701F80E236B2A11002ABF81 /* TelegramMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaResource.swift; sourceTree = ""; }; + A701F80F236B2A11002ABF81 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; + A701F810236B2A11002ABF81 /* NetworkSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkSettings.swift; sourceTree = ""; }; + A701F811236B2A11002ABF81 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = ""; }; + A701F812236B2A11002ABF81 /* SuggestedLocalizationEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestedLocalizationEntry.swift; sourceTree = ""; }; + A701F813236B2A11002ABF81 /* LocalizationListState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationListState.swift; sourceTree = ""; }; + A701F814236B2A11002ABF81 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = ""; }; + A701F815236B2A11002ABF81 /* SynchronizeableChatInputState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeableChatInputState.swift; sourceTree = ""; }; + A701F816236B2A11002ABF81 /* SecretChatSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatSettings.swift; sourceTree = ""; }; + A701F817236B2A11002ABF81 /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = ""; }; + A701F818236B2A11002ABF81 /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = ""; }; + A701F819236B2A11002ABF81 /* CachedWallpapersConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedWallpapersConfiguration.swift; sourceTree = ""; }; + A701F81A236B2A11002ABF81 /* VoipConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipConfiguration.swift; sourceTree = ""; }; + A701F81B236B2A11002ABF81 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = ""; }; + A701F81C236B2A11002ABF81 /* PeerGroupMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerGroupMessageStateVersionAttribute.swift; sourceTree = ""; }; + A701F81D236B2A11002ABF81 /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = ""; }; + A701F81E236B2A11002ABF81 /* SourceReferenceMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceReferenceMessageAttribute.swift; sourceTree = ""; }; + A701F81F236B2A11002ABF81 /* AutodownloadSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutodownloadSettings.swift; sourceTree = ""; }; + A701F820236B2A11002ABF81 /* SecureIdFileReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureIdFileReference.swift; sourceTree = ""; }; + A701F821236B2A11002ABF81 /* TelegramMediaAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaAction.swift; sourceTree = ""; }; + A701F822236B2A11002ABF81 /* TelegramChatAdminRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChatAdminRights.swift; sourceTree = ""; }; + A701F823236B2A11002ABF81 /* StandaloneAccountTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandaloneAccountTransaction.swift; sourceTree = ""; }; + A701F824236B2A11002ABF81 /* TelegramMediaMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaMap.swift; sourceTree = ""; }; + A701F825236B2A11002ABF81 /* SuggestedLocalizationUpdatesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestedLocalizationUpdatesOperation.swift; sourceTree = ""; }; + A701F826236B2A11002ABF81 /* SecretChatKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatKeychain.swift; sourceTree = ""; }; + A701F827236B2A11002ABF81 /* SecretChatEncryptionConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatEncryptionConfig.swift; sourceTree = ""; }; + A701F828236B2A11002ABF81 /* PeerStatusSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerStatusSettings.swift; sourceTree = ""; }; + A701F829236B2A11002ABF81 /* SecureFileMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureFileMediaResource.swift; sourceTree = ""; }; + A701F82A236B2A11002ABF81 /* SearchBotsConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBotsConfiguration.swift; sourceTree = ""; }; + A701F82B236B2A11002ABF81 /* PeerAccessRestrictionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAccessRestrictionInfo.swift; sourceTree = ""; }; + A701F82C236B2A11002ABF81 /* TelegramMediaContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaContact.swift; sourceTree = ""; }; + A701F82D236B2A11002ABF81 /* SynchronizeSavedGifsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedGifsOperation.swift; sourceTree = ""; }; + A701F82E236B2A11002ABF81 /* ContentRequiresValidationMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentRequiresValidationMessageAttribute.swift; sourceTree = ""; }; + A701F82F236B2A11002ABF81 /* WalletCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletCollection.swift; sourceTree = ""; }; + A701F830236B2A11002ABF81 /* SecretChatFileReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatFileReference.swift; sourceTree = ""; }; + A701F831236B2A11002ABF81 /* EmojiKeywordItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiKeywordItem.swift; sourceTree = ""; }; + A701F832236B2A11002ABF81 /* CachedRecentPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedRecentPeers.swift; sourceTree = ""; }; + A701F833236B2A11002ABF81 /* ContactsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsSettings.swift; sourceTree = ""; }; + A701F834236B2A11002ABF81 /* SynchronizeRecentlyUsedMediaOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeRecentlyUsedMediaOperation.swift; sourceTree = ""; }; + A701F835236B2A11002ABF81 /* ExportedInvitation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportedInvitation.swift; sourceTree = ""; }; + A701F836236B2A11002ABF81 /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = ""; }; + A701F837236B2A11002ABF81 /* SynchronizeGroupedPeersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeGroupedPeersOperation.swift; sourceTree = ""; }; + A701F838236B2A11002ABF81 /* SecretChatIncomingEncryptedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatIncomingEncryptedOperation.swift; sourceTree = ""; }; + A701F839236B2A11002ABF81 /* LoggedOutAccountAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutAccountAttribute.swift; sourceTree = ""; }; + A701F83A236B2A11002ABF81 /* SynchronizePinnedChatsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePinnedChatsOperation.swift; sourceTree = ""; }; + A701F83B236B2A11002ABF81 /* ConsumablePersonalMentionMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumablePersonalMentionMessageAttribute.swift; sourceTree = ""; }; + A701F83C236B2A11002ABF81 /* LocalizationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationSettings.swift; sourceTree = ""; }; + A701F83D236B2A11002ABF81 /* MediaReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaReference.swift; sourceTree = ""; }; + A701F83E236B2A11002ABF81 /* SynchronizeConsumeMessageContentsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeConsumeMessageContentsOperation.swift; sourceTree = ""; }; + A701F83F236B2A11002ABF81 /* StickerPackItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackItem.swift; sourceTree = ""; }; + A701F840236B2A11002ABF81 /* CloudChatRemoveMessagesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudChatRemoveMessagesOperation.swift; sourceTree = ""; }; + A701F841236B2A11002ABF81 /* RichText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RichText.swift; sourceTree = ""; }; + A701F842236B2A11002ABF81 /* WasScheduledMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WasScheduledMessageAttribute.swift; sourceTree = ""; }; + A701F843236B2A11002ABF81 /* RecentHashtagItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentHashtagItem.swift; sourceTree = ""; }; + A701F844236B2A11002ABF81 /* RecentMediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentMediaItem.swift; sourceTree = ""; }; + A701F845236B2A11002ABF81 /* CachedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedStickerPack.swift; sourceTree = ""; }; + A701F846236B2A11002ABF81 /* InstantPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPage.swift; sourceTree = ""; }; + A701F847236B2A11002ABF81 /* AppChangelogState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppChangelogState.swift; sourceTree = ""; }; + A701F848236B2A11002ABF81 /* CloudFileMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudFileMediaResource.swift; sourceTree = ""; }; + A701F849236B2A11002ABF81 /* CachedSecureIdConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedSecureIdConfiguration.swift; sourceTree = ""; }; + A701F84A236B2A11002ABF81 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = ""; }; + A701F84B236B2A11002ABF81 /* TelegramMediaExpiredContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaExpiredContent.swift; sourceTree = ""; }; + A701F84C236B2A11002ABF81 /* RegularChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegularChatState.swift; sourceTree = ""; }; + A701F84D236B2A11002ABF81 /* CachedUserData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedUserData.swift; sourceTree = ""; }; + A701F84E236B2A11002ABF81 /* SecretFileEncryptionKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretFileEncryptionKey.swift; sourceTree = ""; }; + A701F84F236B2A11002ABF81 /* GlobalNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalNotificationSettings.swift; sourceTree = ""; }; + A701F850236B2A11002ABF81 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = ""; }; + A701F851236B2A11002ABF81 /* TelegramMediaFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaFile.swift; sourceTree = ""; }; + A701F852236B2A11002ABF81 /* PeerReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerReference.swift; sourceTree = ""; }; + A701F853236B2A11002ABF81 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; + A701F854236B2A11002ABF81 /* AuthorizedAccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizedAccountState.swift; sourceTree = ""; }; + A701F855236B2A11002ABF81 /* SynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperations.swift; sourceTree = ""; }; + A701F8E4236B2A71002ABF81 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F8E6236B2A78002ABF81 /* PostboxMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PostboxMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F8EB236B2E30002ABF81 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F929236C1AF7002ABF81 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7393D2C2407A14800CE44CA /* TelegramMediaDice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaDice.swift; sourceTree = ""; }; + A754972523A3A5860091F293 /* EmbeddedMediaStickersMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedMediaStickersMessageAttribute.swift; sourceTree = ""; }; + A7D28200236C379D0000A9BF /* PixelDimensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelDimensions.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A701F7B2236B295F002ABF81 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F92A236C1AF7002ABF81 /* SwiftSignalKit.framework in Frameworks */, + A701F8EC236B2E30002ABF81 /* Postbox.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A701F7AB236B295F002ABF81 = { + isa = PBXGroup; + children = ( + A701F7C8236B2A11002ABF81 /* Sources */, + A701F7B7236B295F002ABF81 /* SyncCore */, + A701F7B6236B295F002ABF81 /* Products */, + A701F8E3236B2A71002ABF81 /* Frameworks */, + ); + sourceTree = ""; + }; + A701F7B6236B295F002ABF81 /* Products */ = { + isa = PBXGroup; + children = ( + A701F7B5236B295F002ABF81 /* SyncCore.framework */, + ); + name = Products; + sourceTree = ""; + }; + A701F7B7236B295F002ABF81 /* SyncCore */ = { + isa = PBXGroup; + children = ( + A701F7B8236B295F002ABF81 /* SyncCore.h */, + A701F7B9236B295F002ABF81 /* Info.plist */, + ); + path = SyncCore; + sourceTree = ""; + }; + A701F7C8236B2A11002ABF81 /* Sources */ = { + isa = PBXGroup; + children = ( + A7393D2C2407A14800CE44CA /* TelegramMediaDice.swift */, + A754972523A3A5860091F293 /* EmbeddedMediaStickersMessageAttribute.swift */, + A701F7C9236B2A11002ABF81 /* TelegramGroup.swift */, + A701F7CA236B2A11002ABF81 /* OutgoingContentInfoMessageAttribute.swift */, + A701F7CB236B2A11002ABF81 /* TelegramPeerNotificationSettings.swift */, + A701F7CC236B2A11002ABF81 /* EmojiKeywordCollectionInfo.swift */, + A701F7CD236B2A11002ABF81 /* OutgoingMessageInfoAttribute.swift */, + A701F7CE236B2A11002ABF81 /* TelegramChatBannedRights.swift */, + A701F7CF236B2A11002ABF81 /* TelegramWallpaper.swift */, + A701F7D0236B2A11002ABF81 /* AccountBackupDataAttribute.swift */, + A701F7D1236B2A11002ABF81 /* LoggingSettings.swift */, + A701F7D2236B2A11002ABF81 /* TelegramUser.swift */, + A701F7D3236B2A11002ABF81 /* CachedStickerQueryResult.swift */, + A701F7D4236B2A11002ABF81 /* LimitsConfiguration.swift */, + A701F7D5236B2A11002ABF81 /* OutgoingScheduleInfoMessageAttribute.swift */, + A701F7D6236B2A11002ABF81 /* SecretChatOutgoingOperation.swift */, + A701F7D7236B2A11002ABF81 /* SecretChatIncomingDecryptedOperation.swift */, + A701F7D8236B2A11002ABF81 /* TelegramMediaUnsupported.swift */, + A701F7D9236B2A11002ABF81 /* Namespaces.swift */, + A701F7DA236B2A11002ABF81 /* ThemeSettings.swift */, + A701F7DB236B2A11002ABF81 /* CachedLocalizationInfos.swift */, + A701F7DC236B2A11002ABF81 /* BotInfo.swift */, + A701F7DD236B2A11002ABF81 /* CachedResolvedByNamePeer.swift */, + A701F7DE236B2A11002ABF81 /* CachedGroupParticipants.swift */, + A701F7DF236B2A11002ABF81 /* ViewCountMessageAttribute.swift */, + A701F7E0236B2A11002ABF81 /* AccountEnvironmentAttribute.swift */, + A701F7E1236B2A11002ABF81 /* TelegramDeviceContactImportedData.swift */, + A701F7E2236B2A11002ABF81 /* SynchronizeEmojiKeywordsOperation.swift */, + A701F7E3236B2A11002ABF81 /* Localization.swift */, + A701F7E4236B2A11002ABF81 /* CachedThemesConfiguration.swift */, + A701F7E5236B2A11002ABF81 /* StickerPackCollectionInfo.swift */, + A701F7E6236B2A11002ABF81 /* ImportableDeviceContactData.swift */, + A701F7E7236B2A11002ABF81 /* AppConfiguration.swift */, + A701F7E8236B2A11002ABF81 /* SendScheduledMessageImmediatelyAction.swift */, + A701F7E9236B2A11002ABF81 /* SynchronizeAppLogEventsOperation.swift */, + A701F7EA236B2A11002ABF81 /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */, + A701F7EB236B2A11002ABF81 /* ProxySettings.swift */, + A701F7EC236B2A11002ABF81 /* TextEntitiesMessageAttribute.swift */, + A701F7ED236B2A11002ABF81 /* AccountSortOrderAttribute.swift */, + A701F7EE236B2A11002ABF81 /* TelegramMediaWebpage.swift */, + A701F7EF236B2A11002ABF81 /* AutoremoveTimeoutMessageAttribute.swift */, + A701F7F0236B2A11002ABF81 /* UpdateMessageReactionsAction.swift */, + A701F7F1236B2A11002ABF81 /* TelegramMediaWebFile.swift */, + A701F7F2236B2A11002ABF81 /* FeaturedStickerPack.swift */, + A701F7F3236B2A11002ABF81 /* EditedMessageAttribute.swift */, + A701F7F4236B2A11002ABF81 /* RecentPeerItem.swift */, + A701F7F5236B2A11002ABF81 /* ArchivedStickerPacksInfo.swift */, + A701F7F6236B2A11002ABF81 /* RestrictedContentMessageAttribute.swift */, + A701F7F7236B2A11002ABF81 /* TelegramMediaGame.swift */, + A701F7F8236B2A11002ABF81 /* ForwardSourceInfoAttribute.swift */, + A701F7F9236B2A11002ABF81 /* TelegramSecretChat.swift */, + A701F7FA236B2A11002ABF81 /* NotificationInfoMessageAttribute.swift */, + A701F7FB236B2A11002ABF81 /* CachedChannelData.swift */, + A701F7FC236B2A11002ABF81 /* TelegramMediaPoll.swift */, + A701F7FD236B2A11002ABF81 /* ConsumableContentMessageAttribute.swift */, + A701F7FE236B2A11002ABF81 /* RemoteStorageConfiguration.swift */, + A701F7FF236B2A11002ABF81 /* PeerAccessHash.swift */, + A701F800236B2A11002ABF81 /* LocalizationInfo.swift */, + A701F801236B2A11002ABF81 /* CachedGroupData.swift */, + A701F802236B2A11002ABF81 /* InlineBotMessageAttribute.swift */, + A701F803236B2A11002ABF81 /* AuthorSignatureMessageAttribute.swift */, + A701F804236B2A11002ABF81 /* ChannelState.swift */, + A701F805236B2A11002ABF81 /* TelegramMediaImage.swift */, + A701F806236B2A11002ABF81 /* ReplyMessageAttribute.swift */, + A701F807236B2A11002ABF81 /* ReactionsMessageAttribute.swift */, + A701F808236B2A11002ABF81 /* TemporaryTwoStepPasswordToken.swift */, + A701F809236B2A11002ABF81 /* TelegramTheme.swift */, + A701F80A236B2A11002ABF81 /* ConsumePersonalMessageAction.swift */, + A701F80B236B2A11002ABF81 /* SavedStickerItem.swift */, + A701F80C236B2A11002ABF81 /* CacheStorageSettings.swift */, + A701F80D236B2A11002ABF81 /* UnauthorizedAccountState.swift */, + A701F80E236B2A11002ABF81 /* TelegramMediaResource.swift */, + A701F80F236B2A11002ABF81 /* JSON.swift */, + A701F810236B2A11002ABF81 /* NetworkSettings.swift */, + A701F811236B2A11002ABF81 /* TelegramMediaInvoice.swift */, + A701F812236B2A11002ABF81 /* SuggestedLocalizationEntry.swift */, + A701F813236B2A11002ABF81 /* LocalizationListState.swift */, + A701F814236B2A11002ABF81 /* SynchronizeSavedStickersOperation.swift */, + A701F815236B2A11002ABF81 /* SynchronizeableChatInputState.swift */, + A701F816236B2A11002ABF81 /* SecretChatSettings.swift */, + A701F817236B2A11002ABF81 /* SecretChatState.swift */, + A701F818236B2A11002ABF81 /* ContentPrivacySettings.swift */, + A701F819236B2A11002ABF81 /* CachedWallpapersConfiguration.swift */, + A701F81A236B2A11002ABF81 /* VoipConfiguration.swift */, + A701F81B236B2A11002ABF81 /* OutgoingChatContextResultMessageAttribute.swift */, + A701F81C236B2A11002ABF81 /* PeerGroupMessageStateVersionAttribute.swift */, + A701F81D236B2A11002ABF81 /* ChannelMessageStateVersionAttribute.swift */, + A701F81E236B2A11002ABF81 /* SourceReferenceMessageAttribute.swift */, + A701F81F236B2A11002ABF81 /* AutodownloadSettings.swift */, + A701F820236B2A11002ABF81 /* SecureIdFileReference.swift */, + A701F821236B2A11002ABF81 /* TelegramMediaAction.swift */, + A701F822236B2A11002ABF81 /* TelegramChatAdminRights.swift */, + A701F823236B2A11002ABF81 /* StandaloneAccountTransaction.swift */, + A701F824236B2A11002ABF81 /* TelegramMediaMap.swift */, + A701F825236B2A11002ABF81 /* SuggestedLocalizationUpdatesOperation.swift */, + A701F826236B2A11002ABF81 /* SecretChatKeychain.swift */, + A701F827236B2A11002ABF81 /* SecretChatEncryptionConfig.swift */, + A701F828236B2A11002ABF81 /* PeerStatusSettings.swift */, + A701F829236B2A11002ABF81 /* SecureFileMediaResource.swift */, + A701F82A236B2A11002ABF81 /* SearchBotsConfiguration.swift */, + A701F82B236B2A11002ABF81 /* PeerAccessRestrictionInfo.swift */, + A701F82C236B2A11002ABF81 /* TelegramMediaContact.swift */, + A701F82D236B2A11002ABF81 /* SynchronizeSavedGifsOperation.swift */, + A701F82E236B2A11002ABF81 /* ContentRequiresValidationMessageAttribute.swift */, + A701F82F236B2A11002ABF81 /* WalletCollection.swift */, + A701F830236B2A11002ABF81 /* SecretChatFileReference.swift */, + A701F831236B2A11002ABF81 /* EmojiKeywordItem.swift */, + A701F832236B2A11002ABF81 /* CachedRecentPeers.swift */, + A701F833236B2A11002ABF81 /* ContactsSettings.swift */, + A701F834236B2A11002ABF81 /* SynchronizeRecentlyUsedMediaOperation.swift */, + A701F835236B2A11002ABF81 /* ExportedInvitation.swift */, + A701F836236B2A11002ABF81 /* SynchronizeChatInputStateOperation.swift */, + A701F837236B2A11002ABF81 /* SynchronizeGroupedPeersOperation.swift */, + A701F838236B2A11002ABF81 /* SecretChatIncomingEncryptedOperation.swift */, + A701F839236B2A11002ABF81 /* LoggedOutAccountAttribute.swift */, + A701F83A236B2A11002ABF81 /* SynchronizePinnedChatsOperation.swift */, + A701F83B236B2A11002ABF81 /* ConsumablePersonalMentionMessageAttribute.swift */, + A701F83C236B2A11002ABF81 /* LocalizationSettings.swift */, + A701F83D236B2A11002ABF81 /* MediaReference.swift */, + A701F83E236B2A11002ABF81 /* SynchronizeConsumeMessageContentsOperation.swift */, + A701F83F236B2A11002ABF81 /* StickerPackItem.swift */, + A701F840236B2A11002ABF81 /* CloudChatRemoveMessagesOperation.swift */, + A701F841236B2A11002ABF81 /* RichText.swift */, + A701F842236B2A11002ABF81 /* WasScheduledMessageAttribute.swift */, + A701F843236B2A11002ABF81 /* RecentHashtagItem.swift */, + A701F844236B2A11002ABF81 /* RecentMediaItem.swift */, + A701F845236B2A11002ABF81 /* CachedStickerPack.swift */, + A701F846236B2A11002ABF81 /* InstantPage.swift */, + A701F847236B2A11002ABF81 /* AppChangelogState.swift */, + A701F848236B2A11002ABF81 /* CloudFileMediaResource.swift */, + A701F849236B2A11002ABF81 /* CachedSecureIdConfiguration.swift */, + A701F84A236B2A11002ABF81 /* TelegramChannel.swift */, + A701F84B236B2A11002ABF81 /* TelegramMediaExpiredContent.swift */, + A701F84C236B2A11002ABF81 /* RegularChatState.swift */, + A701F84D236B2A11002ABF81 /* CachedUserData.swift */, + A701F84E236B2A11002ABF81 /* SecretFileEncryptionKey.swift */, + A701F84F236B2A11002ABF81 /* GlobalNotificationSettings.swift */, + A701F850236B2A11002ABF81 /* TelegramUserPresence.swift */, + A701F851236B2A11002ABF81 /* TelegramMediaFile.swift */, + A701F852236B2A11002ABF81 /* PeerReference.swift */, + A701F853236B2A11002ABF81 /* ReplyMarkupMessageAttribute.swift */, + A701F854236B2A11002ABF81 /* AuthorizedAccountState.swift */, + A701F855236B2A11002ABF81 /* SynchronizeInstalledStickerPacksOperations.swift */, + A7D28200236C379D0000A9BF /* PixelDimensions.swift */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/SyncCore/Sources"; + sourceTree = ""; + }; + A701F8E3236B2A71002ABF81 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A701F929236C1AF7002ABF81 /* SwiftSignalKit.framework */, + A701F8EB236B2E30002ABF81 /* Postbox.framework */, + A701F8E6236B2A78002ABF81 /* PostboxMac.framework */, + A701F8E4236B2A71002ABF81 /* SwiftSignalKitMac.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A701F7B0236B295F002ABF81 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F7BA236B295F002ABF81 /* SyncCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A701F7B4236B295F002ABF81 /* SyncCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = A701F7BD236B295F002ABF81 /* Build configuration list for PBXNativeTarget "SyncCore" */; + buildPhases = ( + A701F7B0236B295F002ABF81 /* Headers */, + A701F7B1236B295F002ABF81 /* Sources */, + A701F7B2236B295F002ABF81 /* Frameworks */, + A701F7B3236B295F002ABF81 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SyncCore; + productName = SyncCore; + productReference = A701F7B5236B295F002ABF81 /* SyncCore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A701F7AC236B295F002ABF81 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A701F7B4236B295F002ABF81 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A701F7AF236B295F002ABF81 /* Build configuration list for PBXProject "SyncCore_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A701F7AB236B295F002ABF81; + productRefGroup = A701F7B6236B295F002ABF81 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A701F7B4236B295F002ABF81 /* SyncCore */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A701F7B3236B295F002ABF81 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A701F7B1236B295F002ABF81 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F879236B2A11002ABF81 /* TextEntitiesMessageAttribute.swift in Sources */, + A701F8BB236B2A12002ABF81 /* ContentRequiresValidationMessageAttribute.swift in Sources */, + A701F87B236B2A11002ABF81 /* TelegramMediaWebpage.swift in Sources */, + A701F8A1236B2A11002ABF81 /* SynchronizeSavedStickersOperation.swift in Sources */, + A701F85D236B2A11002ABF81 /* AccountBackupDataAttribute.swift in Sources */, + A701F895236B2A11002ABF81 /* TemporaryTwoStepPasswordToken.swift in Sources */, + A701F882236B2A11002ABF81 /* ArchivedStickerPacksInfo.swift in Sources */, + A701F8C8236B2A12002ABF81 /* ConsumablePersonalMentionMessageAttribute.swift in Sources */, + A701F8DE236B2A12002ABF81 /* TelegramMediaFile.swift in Sources */, + A701F8C0236B2A12002ABF81 /* ContactsSettings.swift in Sources */, + A754972623A3A5860091F293 /* EmbeddedMediaStickersMessageAttribute.swift in Sources */, + A701F88B236B2A11002ABF81 /* RemoteStorageConfiguration.swift in Sources */, + A701F859236B2A11002ABF81 /* EmojiKeywordCollectionInfo.swift in Sources */, + A701F88D236B2A11002ABF81 /* LocalizationInfo.swift in Sources */, + A701F861236B2A11002ABF81 /* LimitsConfiguration.swift in Sources */, + A701F87A236B2A11002ABF81 /* AccountSortOrderAttribute.swift in Sources */, + A701F8A2236B2A11002ABF81 /* SynchronizeableChatInputState.swift in Sources */, + A701F865236B2A11002ABF81 /* TelegramMediaUnsupported.swift in Sources */, + A701F8D6236B2A12002ABF81 /* CachedSecureIdConfiguration.swift in Sources */, + A701F868236B2A11002ABF81 /* CachedLocalizationInfos.swift in Sources */, + A701F85E236B2A11002ABF81 /* LoggingSettings.swift in Sources */, + A701F8CC236B2A12002ABF81 /* StickerPackItem.swift in Sources */, + A701F8DC236B2A12002ABF81 /* GlobalNotificationSettings.swift in Sources */, + A701F86C236B2A11002ABF81 /* ViewCountMessageAttribute.swift in Sources */, + A701F8B1236B2A12002ABF81 /* TelegramMediaMap.swift in Sources */, + A701F8D0236B2A12002ABF81 /* RecentHashtagItem.swift in Sources */, + A701F8D5236B2A12002ABF81 /* CloudFileMediaResource.swift in Sources */, + A701F880236B2A11002ABF81 /* EditedMessageAttribute.swift in Sources */, + A701F899236B2A11002ABF81 /* CacheStorageSettings.swift in Sources */, + A701F86F236B2A11002ABF81 /* SynchronizeEmojiKeywordsOperation.swift in Sources */, + A701F8C9236B2A12002ABF81 /* LocalizationSettings.swift in Sources */, + A701F86A236B2A11002ABF81 /* CachedResolvedByNamePeer.swift in Sources */, + A701F8DF236B2A12002ABF81 /* PeerReference.swift in Sources */, + A701F8BF236B2A12002ABF81 /* CachedRecentPeers.swift in Sources */, + A701F864236B2A11002ABF81 /* SecretChatIncomingDecryptedOperation.swift in Sources */, + A701F890236B2A11002ABF81 /* AuthorSignatureMessageAttribute.swift in Sources */, + A701F887236B2A11002ABF81 /* NotificationInfoMessageAttribute.swift in Sources */, + A701F872236B2A11002ABF81 /* StickerPackCollectionInfo.swift in Sources */, + A701F88C236B2A11002ABF81 /* PeerAccessHash.swift in Sources */, + A701F85B236B2A11002ABF81 /* TelegramChatBannedRights.swift in Sources */, + A701F8A3236B2A11002ABF81 /* SecretChatSettings.swift in Sources */, + A701F8BD236B2A12002ABF81 /* SecretChatFileReference.swift in Sources */, + A701F8B7236B2A12002ABF81 /* SearchBotsConfiguration.swift in Sources */, + A701F86B236B2A11002ABF81 /* CachedGroupParticipants.swift in Sources */, + A701F8CA236B2A12002ABF81 /* MediaReference.swift in Sources */, + A701F8B9236B2A12002ABF81 /* TelegramMediaContact.swift in Sources */, + A701F89A236B2A11002ABF81 /* UnauthorizedAccountState.swift in Sources */, + A701F885236B2A11002ABF81 /* ForwardSourceInfoAttribute.swift in Sources */, + A701F88A236B2A11002ABF81 /* ConsumableContentMessageAttribute.swift in Sources */, + A701F857236B2A11002ABF81 /* OutgoingContentInfoMessageAttribute.swift in Sources */, + A701F8A6236B2A12002ABF81 /* CachedWallpapersConfiguration.swift in Sources */, + A701F88F236B2A11002ABF81 /* InlineBotMessageAttribute.swift in Sources */, + A701F883236B2A11002ABF81 /* RestrictedContentMessageAttribute.swift in Sources */, + A701F8C1236B2A12002ABF81 /* SynchronizeRecentlyUsedMediaOperation.swift in Sources */, + A701F8E2236B2A12002ABF81 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */, + A701F8AC236B2A12002ABF81 /* AutodownloadSettings.swift in Sources */, + A701F85A236B2A11002ABF81 /* OutgoingMessageInfoAttribute.swift in Sources */, + A701F858236B2A11002ABF81 /* TelegramPeerNotificationSettings.swift in Sources */, + A701F888236B2A11002ABF81 /* CachedChannelData.swift in Sources */, + A701F8D1236B2A12002ABF81 /* RecentMediaItem.swift in Sources */, + A701F8AE236B2A12002ABF81 /* TelegramMediaAction.swift in Sources */, + A701F8AA236B2A12002ABF81 /* ChannelMessageStateVersionAttribute.swift in Sources */, + A701F8CE236B2A12002ABF81 /* RichText.swift in Sources */, + A701F8C6236B2A12002ABF81 /* LoggedOutAccountAttribute.swift in Sources */, + A701F897236B2A11002ABF81 /* ConsumePersonalMessageAction.swift in Sources */, + A701F89D236B2A11002ABF81 /* NetworkSettings.swift in Sources */, + A701F8BC236B2A12002ABF81 /* WalletCollection.swift in Sources */, + A701F86E236B2A11002ABF81 /* TelegramDeviceContactImportedData.swift in Sources */, + A701F8C7236B2A12002ABF81 /* SynchronizePinnedChatsOperation.swift in Sources */, + A701F8A0236B2A11002ABF81 /* LocalizationListState.swift in Sources */, + A701F8D4236B2A12002ABF81 /* AppChangelogState.swift in Sources */, + A701F896236B2A11002ABF81 /* TelegramTheme.swift in Sources */, + A701F860236B2A11002ABF81 /* CachedStickerQueryResult.swift in Sources */, + A701F8AB236B2A12002ABF81 /* SourceReferenceMessageAttribute.swift in Sources */, + A701F863236B2A11002ABF81 /* SecretChatOutgoingOperation.swift in Sources */, + A701F8B4236B2A12002ABF81 /* SecretChatEncryptionConfig.swift in Sources */, + A701F886236B2A11002ABF81 /* TelegramSecretChat.swift in Sources */, + A701F8CB236B2A12002ABF81 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, + A701F8DD236B2A12002ABF81 /* TelegramUserPresence.swift in Sources */, + A701F8AD236B2A12002ABF81 /* SecureIdFileReference.swift in Sources */, + A701F8BE236B2A12002ABF81 /* EmojiKeywordItem.swift in Sources */, + A701F8C5236B2A12002ABF81 /* SecretChatIncomingEncryptedOperation.swift in Sources */, + A701F878236B2A11002ABF81 /* ProxySettings.swift in Sources */, + A701F889236B2A11002ABF81 /* TelegramMediaPoll.swift in Sources */, + A701F87D236B2A11002ABF81 /* UpdateMessageReactionsAction.swift in Sources */, + A701F884236B2A11002ABF81 /* TelegramMediaGame.swift in Sources */, + A701F8E1236B2A12002ABF81 /* AuthorizedAccountState.swift in Sources */, + A701F8A7236B2A12002ABF81 /* VoipConfiguration.swift in Sources */, + A701F874236B2A11002ABF81 /* AppConfiguration.swift in Sources */, + A701F88E236B2A11002ABF81 /* CachedGroupData.swift in Sources */, + A701F89C236B2A11002ABF81 /* JSON.swift in Sources */, + A701F869236B2A11002ABF81 /* BotInfo.swift in Sources */, + A701F8A9236B2A12002ABF81 /* PeerGroupMessageStateVersionAttribute.swift in Sources */, + A701F8D7236B2A12002ABF81 /* TelegramChannel.swift in Sources */, + A701F8B6236B2A12002ABF81 /* SecureFileMediaResource.swift in Sources */, + A701F89B236B2A11002ABF81 /* TelegramMediaResource.swift in Sources */, + A701F89F236B2A11002ABF81 /* SuggestedLocalizationEntry.swift in Sources */, + A701F867236B2A11002ABF81 /* ThemeSettings.swift in Sources */, + A701F891236B2A11002ABF81 /* ChannelState.swift in Sources */, + A701F894236B2A11002ABF81 /* ReactionsMessageAttribute.swift in Sources */, + A701F85F236B2A11002ABF81 /* TelegramUser.swift in Sources */, + A701F893236B2A11002ABF81 /* ReplyMessageAttribute.swift in Sources */, + A701F877236B2A11002ABF81 /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */, + A701F8D9236B2A12002ABF81 /* RegularChatState.swift in Sources */, + A701F8CF236B2A12002ABF81 /* WasScheduledMessageAttribute.swift in Sources */, + A701F866236B2A11002ABF81 /* Namespaces.swift in Sources */, + A701F8B0236B2A12002ABF81 /* StandaloneAccountTransaction.swift in Sources */, + A701F875236B2A11002ABF81 /* SendScheduledMessageImmediatelyAction.swift in Sources */, + A701F87C236B2A11002ABF81 /* AutoremoveTimeoutMessageAttribute.swift in Sources */, + A701F8B2236B2A12002ABF81 /* SuggestedLocalizationUpdatesOperation.swift in Sources */, + A701F876236B2A11002ABF81 /* SynchronizeAppLogEventsOperation.swift in Sources */, + A701F8B8236B2A12002ABF81 /* PeerAccessRestrictionInfo.swift in Sources */, + A701F8A5236B2A12002ABF81 /* ContentPrivacySettings.swift in Sources */, + A701F8E0236B2A12002ABF81 /* ReplyMarkupMessageAttribute.swift in Sources */, + A701F862236B2A11002ABF81 /* OutgoingScheduleInfoMessageAttribute.swift in Sources */, + A701F881236B2A11002ABF81 /* RecentPeerItem.swift in Sources */, + A7D28201236C379D0000A9BF /* PixelDimensions.swift in Sources */, + A701F870236B2A11002ABF81 /* Localization.swift in Sources */, + A701F873236B2A11002ABF81 /* ImportableDeviceContactData.swift in Sources */, + A701F871236B2A11002ABF81 /* CachedThemesConfiguration.swift in Sources */, + A7393D2D2407A14800CE44CA /* TelegramMediaDice.swift in Sources */, + A701F892236B2A11002ABF81 /* TelegramMediaImage.swift in Sources */, + A701F8CD236B2A12002ABF81 /* CloudChatRemoveMessagesOperation.swift in Sources */, + A701F8D2236B2A12002ABF81 /* CachedStickerPack.swift in Sources */, + A701F8B5236B2A12002ABF81 /* PeerStatusSettings.swift in Sources */, + A701F87E236B2A11002ABF81 /* TelegramMediaWebFile.swift in Sources */, + A701F8AF236B2A12002ABF81 /* TelegramChatAdminRights.swift in Sources */, + A701F8A8236B2A12002ABF81 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, + A701F87F236B2A11002ABF81 /* FeaturedStickerPack.swift in Sources */, + A701F8DA236B2A12002ABF81 /* CachedUserData.swift in Sources */, + A701F8B3236B2A12002ABF81 /* SecretChatKeychain.swift in Sources */, + A701F8C3236B2A12002ABF81 /* SynchronizeChatInputStateOperation.swift in Sources */, + A701F8D3236B2A12002ABF81 /* InstantPage.swift in Sources */, + A701F8C2236B2A12002ABF81 /* ExportedInvitation.swift in Sources */, + A701F8DB236B2A12002ABF81 /* SecretFileEncryptionKey.swift in Sources */, + A701F8D8236B2A12002ABF81 /* TelegramMediaExpiredContent.swift in Sources */, + A701F8C4236B2A12002ABF81 /* SynchronizeGroupedPeersOperation.swift in Sources */, + A701F856236B2A11002ABF81 /* TelegramGroup.swift in Sources */, + A701F8BA236B2A12002ABF81 /* SynchronizeSavedGifsOperation.swift in Sources */, + A701F898236B2A11002ABF81 /* SavedStickerItem.swift in Sources */, + A701F86D236B2A11002ABF81 /* AccountEnvironmentAttribute.swift in Sources */, + A701F8A4236B2A12002ABF81 /* SecretChatState.swift in Sources */, + A701F85C236B2A11002ABF81 /* TelegramWallpaper.swift in Sources */, + A701F89E236B2A11002ABF81 /* TelegramMediaInvoice.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A701F7BB236B295F002ABF81 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A701F7BC236B295F002ABF81 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A701F7BE236B295F002ABF81 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + A701F7BF236B295F002ABF81 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + A701F7C2236B29CC002ABF81 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A701F7C3236B29CC002ABF81 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + A701F7C4236B29E0002ABF81 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A701F7C5236B29E0002ABF81 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + A701F7C6236B29E6002ABF81 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A701F7C7236B29E6002ABF81 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + A7F282D6238EAB3500742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282D7238EAB3500742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SyncCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SyncCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A701F7AF236B295F002ABF81 /* Build configuration list for PBXProject "SyncCore_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F7BB236B295F002ABF81 /* DebugAppStore */, + A7F282D6238EAB3500742C20 /* Github */, + A701F7BC236B295F002ABF81 /* HockeyappMacAlpha */, + A701F7C2236B29CC002ABF81 /* ReleaseHockeyapp */, + A701F7C4236B29E0002ABF81 /* ReleaseAppStore */, + A701F7C6236B29E6002ABF81 /* DebugHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = HockeyappMacAlpha; + }; + A701F7BD236B295F002ABF81 /* Build configuration list for PBXNativeTarget "SyncCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F7BE236B295F002ABF81 /* DebugAppStore */, + A7F282D7238EAB3500742C20 /* Github */, + A701F7BF236B295F002ABF81 /* HockeyappMacAlpha */, + A701F7C3236B29CC002ABF81 /* ReleaseHockeyapp */, + A701F7C5236B29E0002ABF81 /* ReleaseAppStore */, + A701F7C7236B29E6002ABF81 /* DebugHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = HockeyappMacAlpha; + }; +/* End XCConfigurationList section */ + }; + rootObject = A701F7AC236B295F002ABF81 /* Project object */; +} diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..e4dcf5bbf7 --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..34bea761a6 Binary files /dev/null and b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SyncCore.xcscheme b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SyncCore.xcscheme new file mode 100644 index 0000000000..92997fc42c --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/SyncCore.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..7e75fc770f --- /dev/null +++ b/core-xprojects/SyncCore/SyncCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + SyncCore.xcscheme + + isShown + + orderHint + 25 + + + SuppressBuildableAutocreation + + A701F7B4236B295F002ABF81 + + primary + + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi/Info.plist b/core-xprojects/TelegramApi/TelegramApi/Info.plist new file mode 100644 index 0000000000..e1fe4cfb7b --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi/Info.plist @@ -0,0 +1,22 @@ + + + + + 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/core-xprojects/TelegramApi/TelegramApi/TelegramApi.h b/core-xprojects/TelegramApi/TelegramApi/TelegramApi.h new file mode 100644 index 0000000000..64322dc581 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi/TelegramApi.h @@ -0,0 +1,19 @@ +// +// TelegramApi.h +// TelegramApi +// +// Created by Peter on 6/16/19. +// Copyright © 2019 Telegram LLP. All rights reserved. +// + +#import + +//! Project version number for TelegramApi. +FOUNDATION_EXPORT double TelegramApiVersionNumber; + +//! Project version string for TelegramApi. +FOUNDATION_EXPORT const unsigned char TelegramApiVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.pbxproj b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..39b7d85432 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A701F93A236C210C002ABF81 /* TelegramApi.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F939236C210C002ABF81 /* TelegramApi.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A701F93B236C21AE002ABF81 /* SecretApiLayer8.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035734522B5C9BF00F0920D /* SecretApiLayer8.swift */; }; + A701F93C236C21AE002ABF81 /* SecretApiLayer46.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035734422B5C9BF00F0920D /* SecretApiLayer46.swift */; }; + A701F93D236C21AE002ABF81 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035734622B5C9BF00F0920D /* SecretApiLayer73.swift */; }; + A701F93E236C21AE002ABF81 /* SecretApiLayer101.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E9601A22C2BE4900B13673 /* SecretApiLayer101.swift */; }; + A701F93F236C21AE002ABF81 /* DeserializeFunctionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733C22B5C39100F0920D /* DeserializeFunctionResponse.swift */; }; + A701F940236C21AE002ABF81 /* TelegramApiLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733A22B5C31400F0920D /* TelegramApiLogger.swift */; }; + A701F941236C21AE002ABF81 /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733822B5C2E200F0920D /* Buffer.swift */; }; + A701F942236C21AE002ABF81 /* Api0.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733022B5C29900F0920D /* Api0.swift */; }; + A701F943236C21AE002ABF81 /* Api1.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733222B5C29900F0920D /* Api1.swift */; }; + A701F944236C21AE002ABF81 /* Api2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733122B5C29900F0920D /* Api2.swift */; }; + A701F945236C21AE002ABF81 /* Api3.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035733322B5C29900F0920D /* Api3.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 09E9601A22C2BE4900B13673 /* SecretApiLayer101.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer101.swift; sourceTree = ""; }; + A701F937236C1FFB002ABF81 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A701F939236C210C002ABF81 /* TelegramApi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TelegramApi.h; sourceTree = ""; }; + D035733022B5C29900F0920D /* Api0.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api0.swift; sourceTree = ""; }; + D035733122B5C29900F0920D /* Api2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api2.swift; sourceTree = ""; }; + D035733222B5C29900F0920D /* Api1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api1.swift; sourceTree = ""; }; + D035733322B5C29900F0920D /* Api3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api3.swift; sourceTree = ""; }; + D035733822B5C2E200F0920D /* Buffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buffer.swift; sourceTree = ""; }; + D035733A22B5C31400F0920D /* TelegramApiLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelegramApiLogger.swift; sourceTree = ""; }; + D035733C22B5C39100F0920D /* DeserializeFunctionResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeserializeFunctionResponse.swift; sourceTree = ""; }; + D035734422B5C9BF00F0920D /* SecretApiLayer46.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer46.swift; sourceTree = ""; }; + D035734522B5C9BF00F0920D /* SecretApiLayer8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer8.swift; sourceTree = ""; }; + D035734622B5C9BF00F0920D /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = ""; }; + D0CC4AD922BA46F30088F36D /* TelegramApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TelegramApi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D0CC4AD022BA46F30088F36D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A701F936236C1FFB002ABF81 /* TelegramApi */ = { + isa = PBXGroup; + children = ( + A701F939236C210C002ABF81 /* TelegramApi.h */, + A701F937236C1FFB002ABF81 /* Info.plist */, + ); + path = TelegramApi; + sourceTree = SOURCE_ROOT; + }; + D035731522B5C1FC00F0920D /* TelegramApi */ = { + isa = PBXGroup; + children = ( + A701F936236C1FFB002ABF81 /* TelegramApi */, + D035732122B5C1FC00F0920D /* Sources */, + D035732022B5C1FC00F0920D /* Products */, + ); + name = TelegramApi; + sourceTree = ""; + }; + D035732022B5C1FC00F0920D /* Products */ = { + isa = PBXGroup; + children = ( + D0CC4AD922BA46F30088F36D /* TelegramApi.framework */, + ); + name = Products; + sourceTree = ""; + }; + D035732122B5C1FC00F0920D /* Sources */ = { + isa = PBXGroup; + children = ( + D035734522B5C9BF00F0920D /* SecretApiLayer8.swift */, + D035734422B5C9BF00F0920D /* SecretApiLayer46.swift */, + D035734622B5C9BF00F0920D /* SecretApiLayer73.swift */, + 09E9601A22C2BE4900B13673 /* SecretApiLayer101.swift */, + D035733C22B5C39100F0920D /* DeserializeFunctionResponse.swift */, + D035733A22B5C31400F0920D /* TelegramApiLogger.swift */, + D035733822B5C2E200F0920D /* Buffer.swift */, + D035733022B5C29900F0920D /* Api0.swift */, + D035733222B5C29900F0920D /* Api1.swift */, + D035733122B5C29900F0920D /* Api2.swift */, + D035733322B5C29900F0920D /* Api3.swift */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/TelegramApi/Sources"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D0CC4AC322BA46F30088F36D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F93A236C210C002ABF81 /* TelegramApi.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D0CC4AC222BA46F30088F36D /* TelegramApi */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0CC4AD222BA46F30088F36D /* Build configuration list for PBXNativeTarget "TelegramApi" */; + buildPhases = ( + D0CC4AC322BA46F30088F36D /* Headers */, + D0CC4AC522BA46F30088F36D /* Sources */, + D0CC4AD022BA46F30088F36D /* Frameworks */, + D0CC4AD122BA46F30088F36D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TelegramApi; + productName = TelegramApi; + productReference = D0CC4AD922BA46F30088F36D /* TelegramApi.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D035731622B5C1FC00F0920D /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "Telegram LLP"; + }; + buildConfigurationList = D035731922B5C1FC00F0920D /* Build configuration list for PBXProject "TelegramApi_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D035731522B5C1FC00F0920D /* TelegramApi */; + productRefGroup = D035732022B5C1FC00F0920D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0CC4AC222BA46F30088F36D /* TelegramApi */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0CC4AD122BA46F30088F36D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0CC4AC522BA46F30088F36D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F93B236C21AE002ABF81 /* SecretApiLayer8.swift in Sources */, + A701F93C236C21AE002ABF81 /* SecretApiLayer46.swift in Sources */, + A701F93D236C21AE002ABF81 /* SecretApiLayer73.swift in Sources */, + A701F93E236C21AE002ABF81 /* SecretApiLayer101.swift in Sources */, + A701F93F236C21AE002ABF81 /* DeserializeFunctionResponse.swift in Sources */, + A701F940236C21AE002ABF81 /* TelegramApiLogger.swift in Sources */, + A701F941236C21AE002ABF81 /* Buffer.swift in Sources */, + A701F942236C21AE002ABF81 /* Api0.swift in Sources */, + A701F943236C21AE002ABF81 /* Api1.swift in Sources */, + A701F944236C21AE002ABF81 /* Api2.swift in Sources */, + A701F945236C21AE002ABF81 /* Api3.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282E3238EAB6200742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282E4238EAB6200742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Github; + }; + D0276B8422C17FAA003155D8 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0276B8622C17FAA003155D8 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseAppStore; + }; + D0276B8722C17FB2003155D8 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D0276B8922C17FB2003155D8 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseHockeyapp; + }; + D035732522B5C1FC00F0920D /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D0CC4AA522BA44AD0088F36D /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0CC4AA722BA44B70088F36D /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0CC4AD322BA46F30088F36D /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugHockeyapp; + }; + D0CC4AD422BA46F30088F36D /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = HockeyappMacAlpha; + }; + D0CC4AD522BA46F30088F36D /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TelegramApi/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramApi; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D035731922B5C1FC00F0920D /* Build configuration list for PBXProject "TelegramApi_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D035732522B5C1FC00F0920D /* DebugHockeyapp */, + D0CC4AA722BA44B70088F36D /* HockeyappMacAlpha */, + D0CC4AA522BA44AD0088F36D /* DebugAppStore */, + A7F282E3238EAB6200742C20 /* Github */, + D0276B8422C17FAA003155D8 /* ReleaseAppStore */, + D0276B8722C17FB2003155D8 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + D0CC4AD222BA46F30088F36D /* Build configuration list for PBXNativeTarget "TelegramApi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0CC4AD322BA46F30088F36D /* DebugHockeyapp */, + D0CC4AD422BA46F30088F36D /* HockeyappMacAlpha */, + D0CC4AD522BA46F30088F36D /* DebugAppStore */, + A7F282E4238EAB6200742C20 /* Github */, + D0276B8622C17FAA003155D8 /* ReleaseAppStore */, + D0276B8922C17FB2003155D8 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D035731622B5C1FC00F0920D /* Project object */; +} diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..94b2795e22 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..8134c0d9ba Binary files /dev/null and b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApi.xcscheme b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApi.xcscheme new file mode 100644 index 0000000000..8274d109b7 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApi.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApiMac.xcscheme b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApiMac.xcscheme new file mode 100644 index 0000000000..8274d109b7 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramApiMac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..60d336eeb1 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + TelegramApi.xcscheme_^#shared#^_ + + orderHint + 30 + + TelegramApiMac.xcscheme_^#shared#^_ + + orderHint + 31 + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramApi.xcscheme b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramApi.xcscheme new file mode 100644 index 0000000000..49a8e70e5b --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramApi.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..3daa2b2e73 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + TelegramApi.xcscheme + + isShown + + orderHint + 15 + + + SuppressBuildableAutocreation + + D0CC4AC222BA46F30088F36D + + primary + + + + + diff --git a/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..eb013ad372 --- /dev/null +++ b/core-xprojects/TelegramApi/TelegramApi_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + TelegramApi.xcscheme_^#shared#^_ + + orderHint + 36 + + TelegramApiMac.xcscheme_^#shared#^_ + + orderHint + 37 + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore/Info.plist b/core-xprojects/TelegramCore/TelegramCore/Info.plist new file mode 100644 index 0000000000..6c6dba7bb1 --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 Peter. All rights reserved. + NSPrincipalClass + + + diff --git a/core-xprojects/TelegramCore/TelegramCore/TelegramCore.h b/core-xprojects/TelegramCore/TelegramCore/TelegramCore.h new file mode 100644 index 0000000000..f6bcd8339e --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore/TelegramCore.h @@ -0,0 +1,18 @@ +// +// TelegramCoreMac.h +// TelegramCoreMac +// +// Created by Peter on 9/5/16. +// Copyright © 2016 Peter. All rights reserved. +// + +#import + +//! Project version number for TelegramCoreMac. +FOUNDATION_EXPORT double TelegramCoreVersionNumber; + +//! Project version string for TelegramCoreMac. +FOUNDATION_EXPORT const unsigned char TelegramCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..38b3b72157 --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,2441 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9F06831121A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */; }; + 9F10CE8C20613CDB002DD61A /* TelegramDeviceContactImportInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F7732052DEF700D3BFB9 /* TelegramDeviceContactImportInfo.swift */; }; + 9F153D1021E8E0A200B95D82 /* Wallpaper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900555521E4A96D0030924C /* Wallpaper.swift */; }; + 9F1BC1AB2244CFED00F21815 /* EmojiKeywords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093857AA2243D88C00EB6A54 /* EmojiKeywords.swift */; }; + 9F1BC1AC2244CFED00F21815 /* SynchronizeEmojiKeywordsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093857A72243D87900EB6A54 /* SynchronizeEmojiKeywordsOperation.swift */; }; + 9F1BC1AD2244CFED00F21815 /* ManagedSynchronizeEmojiKeywordsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093857A62243D87800EB6A54 /* ManagedSynchronizeEmojiKeywordsOperations.swift */; }; + 9F4EEF9B21DCF66F002C3B33 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66C21B5C56F00245FD9 /* JSON.swift */; }; + 9F4EEF9C21DCF66F002C3B33 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66E21B6147600245FD9 /* AppConfiguration.swift */; }; + 9F4EEF9E21DCF6E7002C3B33 /* ManagedVoipConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */; }; + 9F4EEF9F21DCF6E7002C3B33 /* ManagedAppConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */; }; + 9F4EEFA021DCF6E7002C3B33 /* SynchronizeAppLogEventsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */; }; + 9F4EEFA121DCF6E7002C3B33 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */; }; + 9F7D42262223FF49007B68BB /* AutodownloadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */; }; + 9F7D42272223FF49007B68BB /* ManagedAutodownloadSettingsUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */; }; + 9FAA268820D457A300D26CF3 /* RemoteStorageConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA188120D3D2B1001AEE19 /* RemoteStorageConfiguration.swift */; }; + 9FC8ADA9206BBD000094F7B4 /* SaveSecureIdValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D805206539D000BC3599 /* SaveSecureIdValue.swift */; }; + 9FC8ADAC206BC00A0094F7B4 /* RecentWebSessions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8ADAA206BBFF10094F7B4 /* RecentWebSessions.swift */; }; + A71DC83D2387CC4C000EEDE2 /* UnauthorizedAccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71DC83C2387CC4C000EEDE2 /* UnauthorizedAccountStateManager.swift */; }; + A71DC83F2387CE10000EEDE2 /* AuthTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71DC83E2387CE10000EEDE2 /* AuthTransfer.swift */; }; + A754972823A3A8C80091F293 /* InactiveChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A754972723A3A8C80091F293 /* InactiveChannels.swift */; }; + A767DD4723F2FBD000366F76 /* ChatListFiltering.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767DD4623F2FBCF00366F76 /* ChatListFiltering.swift */; }; + A7919034240CF9C7002011CA /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7919033240CF9C7002011CA /* Reachability.framework */; }; + A7919036240CF9CC002011CA /* CryptoUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7919035240CF9CC002011CA /* CryptoUtils.framework */; }; + A7919038240CF9D1002011CA /* NetworkLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7919037240CF9D1002011CA /* NetworkLogging.framework */; }; + A7919136240D08A6002011CA /* TogglePeerChatPinned.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */; }; + A7D281DE236C25500000A9BF /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D281DD236C25500000A9BF /* SwiftSignalKit.framework */; }; + A7D281E2236C25710000A9BF /* Postbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D281E1236C25710000A9BF /* Postbox.framework */; }; + A7D281E4236C257C0000A9BF /* TelegramApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D281E3236C257C0000A9BF /* TelegramApi.framework */; }; + A7D281EA236C27030000A9BF /* MtProtoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D281E9236C27030000A9BF /* MtProtoKit.framework */; }; + A7D281EC236C2FAD0000A9BF /* SyncCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D281EB236C2FAD0000A9BF /* SyncCore.framework */; }; + A7D281FB236C34670000A9BF /* SynchronizeInstalledStickerPacksOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D281FA236C34660000A9BF /* SynchronizeInstalledStickerPacksOperation.swift */; }; + A7D281FD236C35E30000A9BF /* TelegramMediaWebFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D281FC236C35E30000A9BF /* TelegramMediaWebFile.swift */; }; + A7F2833D239E4EE300742C20 /* ContentSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F2833C239E4EE300742C20 /* ContentSettings.swift */; }; + C205FEA91EB3B75900455808 /* ExportMessageLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C205FEA71EB3B75900455808 /* ExportMessageLink.swift */; }; + C22EE61C1E67418000334C38 /* ToggleChannelSignatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22EE61A1E67418000334C38 /* ToggleChannelSignatures.swift */; }; + C230BEB71EE9A3760029586C /* ChannelAdminEventLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230BEB51EE9A3760029586C /* ChannelAdminEventLogs.swift */; }; + C2366C841E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */; }; + C2366C871E4F403C0097CCFF /* AddressNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C851E4F403C0097CCFF /* AddressNames.swift */; }; + C2366C8A1E4F40480097CCFF /* SupportPeerId.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C881E4F40480097CCFF /* SupportPeerId.swift */; }; + C239BE981E62F0D200C2C453 /* LoadMessagesIfNecessary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */; }; + C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C239BE9B1E630CA700C2C453 /* UpdatePinnedMessage.swift */; }; + C23BC3881E9BE3CB00D79F92 /* ImportContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */; }; + C251D7441E65E50500283EDE /* StickerSetInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C251D7421E65E50500283EDE /* StickerSetInstallation.swift */; }; + C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; }; + C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; }; + C28D3CF120D3DAA30027F4D6 /* DeepLinkInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28D3CEF20D3DA900027F4D6 /* DeepLinkInfo.swift */; }; + C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */; }; + C2A315C01E2E776A00D89000 /* RequestStartBot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01749581E1092BC0057C89A /* RequestStartBot.swift */; }; + C2E0646E1ECF171E00387BB8 /* TelegramMediaWebDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0646C1ECF171D00387BB8 /* TelegramMediaWebDocument.swift */; }; + C2F4ED1E1EC60064005F2696 /* RateCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F4ED1C1EC60064005F2696 /* RateCall.swift */; }; + C2FD33E21E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33E01E680E9E008D13D4 /* RequestUserPhotos.swift */; }; + C2FD33E51E687BF1008D13D4 /* PeerPhotoUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33E31E687BF1008D13D4 /* PeerPhotoUpdater.swift */; }; + C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33EA1E696C78008D13D4 /* GroupsInCommon.swift */; }; + D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CFF1D62255C00955575 /* ChannelState.swift */; }; + D001F3EA1E128A1C007A8C60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; + D001F3EB1E128A1C007A8C60 /* EnqueueMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D001D62255C00955575 /* EnqueueMessage.swift */; }; + D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D011D62255C00955575 /* Holes.swift */; }; + D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495D1E118F790057C89A /* AccountStateManager.swift */; }; + D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495F1E118FC30057C89A /* AccountIntermediateState.swift */; }; + D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */; }; + D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D051D62255C00955575 /* UpdateGroup.swift */; }; + D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D061D62255C00955575 /* UpdateMessageService.swift */; }; + D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */; }; + D001F3F51E128A1C007A8C60 /* PendingMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */; }; + D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */; }; + D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */; }; + D00422D421677F4500719B67 /* ManagedAccountPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00422D221677F4500719B67 /* ManagedAccountPresence.swift */; }; + D00BDA1A1EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */; }; + D00BDA1D1EE5952A00C64C5E /* TelegramChannelBannedRights.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00BDA1B1EE5952A00C64C5E /* TelegramChannelBannedRights.swift */; }; + D00C7CCD1E3620C30080C3D5 /* CachedChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CCB1E3620C30080C3D5 /* CachedChannelParticipants.swift */; }; + D00C7CE11E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */; }; + D00C7CEC1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */; }; + D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */; }; + D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */; }; + D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */; }; + D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */; }; + D00D97CB1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00D97C91E32917C00E5C2B6 /* PeerInputActivityManager.swift */; }; + D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DBBD61E64E41100DB5485 /* CreateSecretChat.swift */; }; + D00DBBDB1E64E67E00DB5485 /* UpdateSecretChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DBBD91E64E67E00DB5485 /* UpdateSecretChat.swift */; }; + D0119CB120CA9EA800895300 /* MarkAllChatsAsRead.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0119CAF20CA9EA800895300 /* MarkAllChatsAsRead.swift */; }; + D013630A208F6E2800EB3653 /* SecureIdValueContentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0136308208F3B0900EB3653 /* SecureIdValueContentError.swift */; }; + D014193922AE6B85008667CB /* ChannelOwnershipTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778222A9862100CD99F5 /* ChannelOwnershipTransfer.swift */; }; + D014193A22AE6B85008667CB /* PeersNearby.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E778F22AAABC600CD99F5 /* PeersNearby.swift */; }; + D015E00F225CA61100CB9E8A /* FindChannelById.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E00D225CA61100CB9E8A /* FindChannelById.swift */; }; + D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */; }; + D018D3381E648ACF00C5E089 /* ChannelCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* ChannelCreation.swift */; }; + D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; }; + D018EE062045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CheckPeerChatServiceActions.swift */; }; + D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; }; + D01A21A71F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */; }; + D01A21AA1F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */; }; + D01B264A23324CF900A6448B /* Wallets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B264823324CF800A6448B /* Wallets.swift */; }; + D01C06B81FBBA269001561AB /* CanSendMessagesToPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C06B61FBBA269001561AB /* CanSendMessagesToPeer.swift */; }; + D01C7ED41EF5DF83008305F1 /* LimitsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7ED21EF5DF83008305F1 /* LimitsConfiguration.swift */; }; + D01C7ED71EF5E468008305F1 /* ProxySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7ED51EF5E468008305F1 /* ProxySettings.swift */; }; + D01C7F051EFC1C49008305F1 /* DeviceContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C7F031EFC1C49008305F1 /* DeviceContact.swift */; }; + D01D6BFA1E42A718006151C6 /* SearchStickers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D6BF81E42A713006151C6 /* SearchStickers.swift */; }; + D020F00722F19C8F00BE699A /* ManagedAnimatedEmojiUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0925903622F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift */; }; + D021E7E92306EC03002F8BD1 /* ScheduledMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FC986C22FD99D400915E37 /* ScheduledMessages.swift */; }; + D021E7EA2306EC03002F8BD1 /* ValidateAddressNameInteractive.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E416B2304D5B30049C28B /* ValidateAddressNameInteractive.swift */; }; + D0223A991EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */; }; + D02395D71F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */; }; + D023E67921540624008C27D1 /* UpdateMessageMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023E67721540624008C27D1 /* UpdateMessageMedia.swift */; }; + D026099F20C695AF006C34AC /* Wallpapers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D026099D20C695AF006C34AC /* Wallpapers.swift */; }; + D02ABC7C1E30058F00CAE539 /* DeleteMessagesInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */; }; + D02ABC7F1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC7D1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift */; }; + D02ABC821E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC801E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift */; }; + D02D60A8206BA5F900FEFE1E /* SecureIdValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60A6206BA5F900FEFE1E /* SecureIdValue.swift */; }; + D02D60AC206BA64100FEFE1E /* VerifySecureIdValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */; }; + D02DADC22139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; }; + D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */; }; + D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */; }; + D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; }; + D0338744223BD532007A2CE4 /* InitializeAccountAfterLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */; }; + D033FEB11E61EB0200644997 /* PeerContactSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEAF1E61EB0200644997 /* PeerContactSettings.swift */; }; + D033FEB41E61F3C000644997 /* ReportPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEB21E61F3C000644997 /* ReportPeer.swift */; }; + D033FEB71E61F3F900644997 /* BlockedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEB51E61F3F900644997 /* BlockedPeers.swift */; }; + D03413F1231323CE00B555F3 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B5230FBB2B005C2E08 /* Theme.swift */; }; + D03413F3231325B300B555F3 /* Themes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B3230FB70B005C2E08 /* Themes.swift */; }; + D0380DBB204EF306000414AB /* MessageMediaPreuploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0380DB9204EF306000414AB /* MessageMediaPreuploadManager.swift */; }; + D03C53671DAD5CA9004C17B3 /* ApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CDA1D62245F00955575 /* ApiUtils.swift */; }; + D03C53681DAD5CA9004C17B3 /* PeerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CD81D62245B00955575 /* PeerUtils.swift */; }; + D03C53691DAD5CA9004C17B3 /* PeerAccessRestrictionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */; }; + D03C536A1DAD5CA9004C17B3 /* TelegramUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CD41D62245300955575 /* TelegramUser.swift */; }; + D03C536B1DAD5CA9004C17B3 /* TelegramGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CD51D62245300955575 /* TelegramGroup.swift */; }; + D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */; }; + D03C536D1DAD5CA9004C17B3 /* ApiGroupOrChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B417C01D7DCEEF004562A4 /* ApiGroupOrChannel.swift */; }; + D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003702A1DA42586004308D3 /* PhoneNumber.swift */; }; + D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8438B1DA7CF50005F29E1 /* BotInfo.swift */; }; + D03C53701DAD5CA9004C17B3 /* ExportedInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843881DA7AB96005F29E1 /* ExportedInvitation.swift */; }; + D03C53711DAD5CA9004C17B3 /* CachedGroupParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8438D1DA7D296005F29E1 /* CachedGroupParticipants.swift */; }; + D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843841DA6EDC4005F29E1 /* CachedChannelData.swift */; }; + D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; }; + D03DC9111F82E344001D584C /* AccountStateReset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03DC90F1F82E344001D584C /* AccountStateReset.swift */; }; + D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F41E535464008C24B4 /* AddPeerMember.swift */; }; + D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */; }; + D042C6841E8D9DF800C863B0 /* Unixtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = D042C6821E8D9DF800C863B0 /* Unixtime.swift */; }; + D0439B5E228ECB270067E026 /* RequestPhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0439B5C228ECB270067E026 /* RequestPhoneNumber.swift */; }; + D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */; }; + D0448C921E251F96005A61A7 /* SecretChatEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */; }; + D0448CA01E27F5EB005A61A7 /* Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448C9E1E27F5EB005A61A7 /* Random.swift */; }; + D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448CA11E291B14005A61A7 /* FetchSecretFileResource.swift */; }; + D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0448CA41E29215A005A61A7 /* MediaResourceApiUtils.swift */; }; + D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04554A521B43440007A6DD9 /* CancelAccountReset.swift */; }; + D0467D0C20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */; }; + D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */; }; + D048B4AD20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */; }; + D049EAD91E43DAD200A2CD3A /* ManagedRecentStickers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAD71E43DAD200A2CD3A /* ManagedRecentStickers.swift */; }; + D049EAEC1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */; }; + D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAF41E44DF3300A2CD3A /* AccountState.swift */; }; + D04CAA5B1E83310D0047E51F /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04CAA591E83310D0047E51F /* MD5.swift */; }; + D04D21372306EC9A00609388 /* MacInternalUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D21362306EC9A00609388 /* MacInternalUpdater.swift */; }; + D04D8FF5209A4B0700865719 /* NetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D8FF3209A4B0700865719 /* NetworkSettings.swift */; }; + D050F2521E4A59C200988324 /* JoinLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2501E4A59C200988324 /* JoinLink.swift */; }; + D050F2611E4A5AE700988324 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27A11E394D8B0022A4C0 /* PrivacySettings.swift */; }; + D050F2621E4A5AE700988324 /* GlobalNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */; }; + D050F2641E4A5AEB00988324 /* ManagedSynchronizePinnedChatsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */; }; + D050F26A1E4A5B6D00988324 /* ManagedGlobalNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08774FB1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift */; }; + D050F26B1E4A5B6D00988324 /* ApplyMaxReadIndexInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AAD1A91E32638500D5B9DE /* ApplyMaxReadIndexInteractively.swift */; }; + D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */; }; + D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */; }; + D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38741E40A7F70044D6FE /* RemovePeerChat.swift */; }; + D051DB15215EC5A300F30F92 /* AppChangelogState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D051DB13215EC5A300F30F92 /* AppChangelogState.swift */; }; + D051DB18215ECC4D00F30F92 /* AppChangelog.swift in Sources */ = {isa = PBXBuildFile; fileRef = D051DB16215ECC4D00F30F92 /* AppChangelog.swift */; }; + D0528E5B1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */; }; + D0528E611E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */; }; + D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */; }; + D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */; }; + D0529D2521A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0529D2321A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift */; }; + D0529D2821A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0529D2621A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift */; }; + D05452081E7B5093006EEF19 /* LoadedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05452061E7B5093006EEF19 /* LoadedStickerPack.swift */; }; + D054648C2073854A002ECC1E /* SecureIdPersonalDetailsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054648A2073854A002ECC1E /* SecureIdPersonalDetailsValue.swift */; }; + D054648F20738626002ECC1E /* SecureIdDriversLicenseValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054648D20738626002ECC1E /* SecureIdDriversLicenseValue.swift */; }; + D054649220738653002ECC1E /* SecureIdIDCardValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054649020738653002ECC1E /* SecureIdIDCardValue.swift */; }; + D0546495207386D7002ECC1E /* SecureIdUtilityBillValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0546493207386D7002ECC1E /* SecureIdUtilityBillValue.swift */; }; + D05464982073872C002ECC1E /* SecureIdBankStatementValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05464962073872C002ECC1E /* SecureIdBankStatementValue.swift */; }; + D054649B20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054649920738760002ECC1E /* SecureIdRentalAgreementValue.swift */; }; + D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */; }; + D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */; }; + D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */; }; + D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */; }; + D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */; }; + D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */; }; + D05A32E21E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */; }; + D05A32E51E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */; }; + D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32E61E6F0B5C002760B4 /* RecentAccountSession.swift */; }; + D05D8B382192F8AF0064586F /* LocalizationListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05D8B362192F8AF0064586F /* LocalizationListState.swift */; }; + D05FDC3922CA45070060BFE3 /* AppUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EC0DE822C6825D00E7185B /* AppUpdate.swift */; }; + D0613FCB1E60440600202CDB /* InvitationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0613FC91E60440600202CDB /* InvitationLinks.swift */; }; + D0613FD01E60520700202CDB /* ChannelMembers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0613FCE1E60520700202CDB /* ChannelMembers.swift */; }; + D0613FD81E606B3B00202CDB /* ConvertGroupToSupergroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0613FD61E606B3B00202CDB /* ConvertGroupToSupergroup.swift */; }; + D0633CD32253A528003DD95F /* ChatOnlineMembers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633CD12253A528003DD95F /* ChatOnlineMembers.swift */; }; + D0633CDC2253C0D3003DD95F /* CloudMediaResourceParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0633CDA2253C0D3003DD95F /* CloudMediaResourceParameters.swift */; }; + D0642EFA1F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */; }; + D06CA13622772EB20094E707 /* ManagedNotificationSettingsBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06CA13422772EB20094E707 /* ManagedNotificationSettingsBehaviors.swift */; }; + D06ECFC920B810D300C576C2 /* TermsOfService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06ECFC720B810D300C576C2 /* TermsOfService.swift */; }; + D07047B81F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */; }; + D072F358231542740009E66F /* MessageReactionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D072F356231542740009E66F /* MessageReactionList.swift */; }; + D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CE31D62249F00955575 /* TextEntitiesMessageAttribute.swift */; }; + D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; }; + D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */; }; + D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; }; + D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */; }; + D076F88A2296D8F6004F895A /* ManageChannelDiscussionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D076F8882296D8E9004F895A /* ManageChannelDiscussionGroup.swift */; }; + D07E4140208A769D00FCA8F0 /* ProxyServersStatuses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07E413E208A769D00FCA8F0 /* ProxyServersStatuses.swift */; }; + D081E10B217F5ADE003CD921 /* LocalizationPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081E109217F5ADE003CD921 /* LocalizationPreview.swift */; }; + D0830FC2241254FD006198E7 /* BankCards.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0830FC1241254FD006198E7 /* BankCards.swift */; }; + D0879BC922F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */; }; + D08984F32114B97400918162 /* ClearCloudDrafts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F12114B97400918162 /* ClearCloudDrafts.swift */; }; + D08984F621187ECA00918162 /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984F421187ECA00918162 /* NetworkType.swift */; }; + D08CAA811ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7F1ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift */; }; + D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA861ED81DD40000FDA8 /* LocalizationInfo.swift */; }; + D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */; }; + D08F4A6A1E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; }; + D093D7EF206413F600BC3599 /* SecureIdDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */; }; + D093D7F620641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */; }; + D093D7FA20641AA500BC3599 /* SecureIdEmailValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */; }; + D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */; }; + D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; }; + D099E223229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; }; + D0A2764B249C9989005E3C77 /* PeerStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2764A249C9989005E3C77 /* PeerStatistics.swift */; }; + D0A3E448214802C7008ACEF6 /* VoipConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A3E446214802C7008ACEF6 /* VoipConfiguration.swift */; }; + D0A472B71F4CBE8B00E0EEDA /* LoadedPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A472B51F4CBE8B00E0EEDA /* LoadedPeer.swift */; }; + D0A89990217A37A000759EE6 /* NotificationAutolockReportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A8998E217A37A000759EE6 /* NotificationAutolockReportManager.swift */; }; + D0AAD1B91E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AAD1B71E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift */; }; + D0AB262721C2F991008F6685 /* TelegramMediaPoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB262521C2F991008F6685 /* TelegramMediaPoll.swift */; }; + D0AB262C21C3CE80008F6685 /* Polls.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB262A21C3CE80008F6685 /* Polls.swift */; }; + D0AD02E41FFFA14800C1DCFF /* PeerLiveLocationsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD02E21FFFA14800C1DCFF /* PeerLiveLocationsContext.swift */; }; + D0ADF912212B00DD00310BBC /* SecureIdConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ADF910212B00DD00310BBC /* SecureIdConfiguration.swift */; }; + D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AF32211FAC95C20097362B /* StandaloneUploadedMedia.swift */; }; + D0AF32321FACEDEC0097362B /* CoreSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AF32301FACEDEC0097362B /* CoreSettings.swift */; }; + D0B1671E1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */; }; + D0B167241F9F972E00976B40 /* LoggingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B167221F9F972E00976B40 /* LoggingSettings.swift */; }; + D0B4186B1D7E03D5004562A4 /* TelegramCore.h in Headers */ = {isa = PBXBuildFile; fileRef = D0B418691D7E03D5004562A4 /* TelegramCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0B4188E1D7E0578004562A4 /* StoreMessage_Telegram.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CDF1D62249100955575 /* StoreMessage_Telegram.swift */; }; + D0B418941D7E0580004562A4 /* TelegramMediaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CEC1D62250800955575 /* TelegramMediaAction.swift */; }; + D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CEE1D62250800955575 /* TelegramMediaFile.swift */; }; + D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CEF1D62250800955575 /* TelegramMediaImage.swift */; }; + D0B418991D7E0580004562A4 /* TelegramMediaMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CF11D62250800955575 /* TelegramMediaMap.swift */; }; + D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CF31D62250800955575 /* TelegramMediaWebpage.swift */; }; + D0B418A61D7E0592004562A4 /* CloudFileMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */; }; + D0B418A71D7E0592004562A4 /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D391D6319E200955575 /* Fetch.swift */; }; + D0B418AA1D7E0597004562A4 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D561D631A6900955575 /* Download.swift */; }; + D0B418AB1D7E0597004562A4 /* MultipartFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D571D631A6900955575 /* MultipartFetch.swift */; }; + D0B418AC1D7E0597004562A4 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D581D631A6900955575 /* Network.swift */; }; + D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D591D631A6900955575 /* Serialization.swift */; }; + D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D6C1D631AA300955575 /* ContactManagement.swift */; }; + D0B8440D1DAB91CD005F29E1 /* ImageRepresentationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */; }; + D0B8440E1DAB91CD005F29E1 /* MessageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */; }; + D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CB81D62233400955575 /* Either.swift */; }; + D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CBC1D62234300955575 /* Regex.swift */; }; + D0B844121DAB91CD005F29E1 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CBE1D62234A00955575 /* Log.swift */; }; + D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CC01D62235000955575 /* StringFormat.swift */; }; + D0B844431DAB91FD005F29E1 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D611D631A8B00955575 /* Account.swift */; }; + D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D631D631A8B00955575 /* AccountViewTracker.swift */; }; + D0B844461DAB91FD005F29E1 /* RecentPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D641D631A8B00955575 /* RecentPeers.swift */; }; + D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B911D65E9FA002C78E7 /* ManagedServiceViews.swift */; }; + D0B844481DAB91FD005F29E1 /* ManagedMessageHistoryHoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B931D662ECE002C78E7 /* ManagedMessageHistoryHoles.swift */; }; + D0B844491DAB91FD005F29E1 /* ManagedChatListHoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B951D662F0B002C78E7 /* ManagedChatListHoles.swift */; }; + D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB0B991D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift */; }; + D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843861DA6F705005F29E1 /* UpdateCachedPeerData.swift */; }; + D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; }; + D0BE303B20619EE800FBE6D8 /* SecureIdForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE303920619EE800FBE6D8 /* SecureIdForm.swift */; }; + D0BE303E2061A29100FBE6D8 /* RequestSecureIdForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE303C2061A29100FBE6D8 /* RequestSecureIdForm.swift */; }; + D0BE304C20627D9800FBE6D8 /* AccessSecureId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE304A20627D9800FBE6D8 /* AccessSecureId.swift */; }; + D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5C1E54941B00BD963D /* Authorization.swift */; }; + D0BEAF611E54ACF900BD963D /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */; }; + D0C0B58B1ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */; }; + D0C0B58E1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */; }; + D0C26D671FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */; }; + D0C26D6A1FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */; }; + D0C26D6D1FE286C3004ABF18 /* FetchChatList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */; }; + D0C27B401F4B51D000A4E170 /* CachedStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */; }; + D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B411F4B58C000A4E170 /* PeerSpecificStickerPack.swift */; }; + D0C44B621FC616E200227BE0 /* SearchGroupMembers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */; }; + D0C48F3D1E8142EF0075317D /* LoadedPeerFromMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C48F3B1E8142EF0075317D /* LoadedPeerFromMessage.swift */; }; + D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C50E331E93A86600F62E39 /* CallSessionManager.swift */; }; + D0CA3F85207391560042D2B6 /* SecureIdPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA3F83207391560042D2B6 /* SecureIdPadding.swift */; }; + D0CA8E4C227209C4008A74C3 /* ManagedSynchronizeGroupMessageStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA8E4A227209C4008A74C3 /* ManagedSynchronizeGroupMessageStats.swift */; }; + D0CB09AE24ADEFC9008CD050 /* Suggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CB09AD24ADEFC9008CD050 /* Suggestions.swift */; }; + D0D376E722DCCFD600FA7D7C /* SlowMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D376E522DCCFD600FA7D7C /* SlowMode.swift */; }; + D0D3CE4023BB3F1100864F3C /* PendingUpdateMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D3CE3F23BB3F1100864F3C /* PendingUpdateMessageManager.swift */; }; + D0D3CE4423BB3FD200864F3C /* ChatUpdatingMessageMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D3CE4323BB3FD200864F3C /* ChatUpdatingMessageMedia.swift */; }; + D0D748031E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D748011E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift */; }; + D0DA1D331F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA1D311F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift */; }; + D0DB7F041F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */; }; + D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */; }; + D0DC35521DE36908000195EB /* ChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354F1DE36900000195EB /* ChatContextResult.swift */; }; + D0DEF73F242A41DB00A34A30 /* AccountStateManagementUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DEF73E242A41DB00A34A30 /* AccountStateManagementUtils.swift */; }; + D0DFD5E01FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */; }; + D0E23DDB1E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E23DD91E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift */; }; + D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E23DDE1E8082A400B9B6D2 /* ArchivedStickerPacks.swift */; }; + D0E305A81E5B5CBE00D7A3A2 /* PeerAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E305A61E5B5CBE00D7A3A2 /* PeerAdmins.swift */; }; + D0E305AB1E5BA02D00D7A3A2 /* ChannelBlacklist.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E305A91E5BA02D00D7A3A2 /* ChannelBlacklist.swift */; }; + D0E35A141DE4C69C00BC6096 /* FetchHttpResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */; }; + D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */; }; + D0E412D8206A866B00BEE4A2 /* UploadSecureIdFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412D6206A866B00BEE4A2 /* UploadSecureIdFile.swift */; }; + D0E412DD206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412DB206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift */; }; + D0E412E2206AB24700BEE4A2 /* SecureFileMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412E0206AB24700BEE4A2 /* SecureFileMediaResource.swift */; }; + D0E412E8206ABC7500BEE4A2 /* EncryptedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412E6206ABC7500BEE4A2 /* EncryptedMediaResource.swift */; }; + D0E412EB206AD18E00BEE4A2 /* DecryptedResourceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412E9206AD18E00BEE4A2 /* DecryptedResourceData.swift */; }; + D0E412EF206AF65500BEE4A2 /* GrantSecureIdAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412ED206AF65500BEE4A2 /* GrantSecureIdAccess.swift */; }; + D0E412F2206B9BB700BEE4A2 /* SecureIdPassportValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412F0206B9BB700BEE4A2 /* SecureIdPassportValue.swift */; }; + D0E412F5206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E412F3206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift */; }; + D0E41302206B9E6E00BEE4A2 /* SecureIdAddressValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E41300206B9E6E00BEE4A2 /* SecureIdAddressValue.swift */; }; + D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; }; + D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; }; + D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; }; + D0EC559B2101ED0800D1992C /* DeleteMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC55992101ED0800D1992C /* DeleteMessages.swift */; }; + D0EE7FC220986BF400981319 /* SecureIdInternalPassportValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EE7FC020986BF400981319 /* SecureIdInternalPassportValue.swift */; }; + D0EE7FC520986C5300981319 /* SecureIdPassportRegistrationValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EE7FC320986C5300981319 /* SecureIdPassportRegistrationValue.swift */; }; + D0EE7FC82098853100981319 /* SecureIdTemporaryRegistrationValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EE7FC62098853100981319 /* SecureIdTemporaryRegistrationValue.swift */; }; + D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; }; + D0F19F6720E6621000EEC860 /* MultiplexedRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F19F6520E6620D00EEC860 /* MultiplexedRequestManager.swift */; }; + D0F3A8A01E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; }; + D0F3A8A31E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */; }; + D0F3A8A91E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */; }; + D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D711D631ABA00955575 /* SearchMessages.swift */; }; + D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; }; + D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; }; + D0F53BEA1E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */; }; + D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */; }; + D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827CA1E02F5B200071108 /* RichText.swift */; }; + D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C81E02F59C00071108 /* InstantPage.swift */; }; + D0F7B1E71E045C87007EB8A5 /* JoinChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */; }; + D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */; }; + D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; }; + D0F7B1EA1E045C87007EB8A5 /* ChangePeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */; }; + D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */; }; + D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; }; + D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; }; + D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; }; + D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; }; + D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */; }; + D0FA8B991E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */; }; + D0FA8B9F1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8B9D1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift */; }; + D0FA8BA21E1F99E1001E855B /* SecretChatFileReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8BA01E1F99E1001E855B /* SecretChatFileReference.swift */; }; + D0FA8BAB1E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */; }; + D0FA8BAE1E1FD6E2001E855B /* MemoryBufferExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8BAC1E1FD6E2001E855B /* MemoryBufferExtensions.swift */; }; + D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8BAF1E1FEC7E001E855B /* SecretChatEncryptionConfig.swift */; }; + D0FA8BB41E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8BB21E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0900555521E4A96D0030924C /* Wallpaper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallpaper.swift; sourceTree = ""; }; + 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedVoipConfigurationUpdates.swift; sourceTree = ""; }; + 090E778222A9862100CD99F5 /* ChannelOwnershipTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelOwnershipTransfer.swift; sourceTree = ""; }; + 090E778F22AAABC600CD99F5 /* PeersNearby.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeersNearby.swift; sourceTree = ""; }; + 0925903622F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAnimatedEmojiUpdates.swift; sourceTree = ""; }; + 093857A62243D87800EB6A54 /* ManagedSynchronizeEmojiKeywordsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeEmojiKeywordsOperations.swift; sourceTree = ""; }; + 093857A72243D87900EB6A54 /* SynchronizeEmojiKeywordsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeEmojiKeywordsOperation.swift; sourceTree = ""; }; + 093857AA2243D88C00EB6A54 /* EmojiKeywords.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiKeywords.swift; sourceTree = ""; }; + 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAppConfigurationUpdates.swift; sourceTree = ""; }; + 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeAppLogEventsOperation.swift; sourceTree = ""; }; + 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeAppLogEventsOperations.swift; sourceTree = ""; }; + 0962E66C21B5C56F00245FD9 /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; + 0962E66E21B6147600245FD9 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; + 0962E67421B6437600245FD9 /* SplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTest.swift; sourceTree = ""; }; + 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBotsConfiguration.swift; sourceTree = ""; }; + 09B4A9B3230FB70B005C2E08 /* Themes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Themes.swift; sourceTree = ""; }; + 09B4A9B5230FBB2B005C2E08 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 09EC0DE822C6825D00E7185B /* AppUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdate.swift; sourceTree = ""; }; + 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadSettings.swift; sourceTree = ""; }; + 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAutodownloadSettingsUpdates.swift; sourceTree = ""; }; + 09FC986A22FD882200915E37 /* OutgoingScheduleInfoMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingScheduleInfoMessageAttribute.swift; sourceTree = ""; }; + 09FC986C22FD99D400915E37 /* ScheduledMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduledMessages.swift; sourceTree = ""; }; + 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExceptionsList.swift; sourceTree = ""; }; + 9FC8ADAA206BBFF10094F7B4 /* RecentWebSessions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentWebSessions.swift; sourceTree = ""; }; + A71DC83C2387CC4C000EEDE2 /* UnauthorizedAccountStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnauthorizedAccountStateManager.swift; sourceTree = ""; }; + A71DC83E2387CE10000EEDE2 /* AuthTransfer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthTransfer.swift; sourceTree = ""; }; + A754972723A3A8C80091F293 /* InactiveChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InactiveChannels.swift; sourceTree = ""; }; + A767DD4623F2FBCF00366F76 /* ChatListFiltering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListFiltering.swift; sourceTree = ""; }; + A7919033240CF9C7002011CA /* Reachability.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7919035240CF9CC002011CA /* CryptoUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CryptoUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7919037240CF9D1002011CA /* NetworkLogging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = NetworkLogging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281D7236C25370000A9BF /* libphonenumber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281D9236C253C0000A9BF /* TelegramApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramApi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281DB236C25440000A9BF /* MtProtoKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281DD236C25500000A9BF /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281DF236C25580000A9BF /* libphonenumber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281E1236C25710000A9BF /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281E3236C257C0000A9BF /* TelegramApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramApi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281E9236C27030000A9BF /* MtProtoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281EB236C2FAD0000A9BF /* SyncCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SyncCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D281FA236C34660000A9BF /* SynchronizeInstalledStickerPacksOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperation.swift; sourceTree = ""; }; + A7D281FC236C35E30000A9BF /* TelegramMediaWebFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaWebFile.swift; sourceTree = ""; }; + A7F2833C239E4EE300742C20 /* ContentSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentSettings.swift; sourceTree = ""; }; + C205FEA71EB3B75900455808 /* ExportMessageLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportMessageLink.swift; sourceTree = ""; }; + C210DD611FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceReferenceMessageAttribute.swift; sourceTree = ""; }; + C22EE61A1E67418000334C38 /* ToggleChannelSignatures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleChannelSignatures.swift; sourceTree = ""; }; + C230BEB51EE9A3760029586C /* ChannelAdminEventLogs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogs.swift; sourceTree = ""; }; + C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupReturnAndLeft.swift; sourceTree = ""; }; + C2366C851E4F403C0097CCFF /* AddressNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressNames.swift; sourceTree = ""; }; + C2366C881E4F40480097CCFF /* SupportPeerId.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportPeerId.swift; sourceTree = ""; }; + C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMessagesIfNecessary.swift; sourceTree = ""; }; + C239BE9B1E630CA700C2C453 /* UpdatePinnedMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePinnedMessage.swift; sourceTree = ""; }; + C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportContact.swift; sourceTree = ""; }; + C251D7421E65E50500283EDE /* StickerSetInstallation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerSetInstallation.swift; sourceTree = ""; }; + C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = ""; }; + C28D3CEF20D3DA900027F4D6 /* DeepLinkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkInfo.swift; sourceTree = ""; }; + C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroupSpecificStickerset.swift; sourceTree = ""; }; + C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = ""; }; + C2E0646C1ECF171D00387BB8 /* TelegramMediaWebDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaWebDocument.swift; sourceTree = ""; }; + C2F4ED1C1EC60064005F2696 /* RateCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RateCall.swift; sourceTree = ""; }; + C2FD33E01E680E9E008D13D4 /* RequestUserPhotos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestUserPhotos.swift; sourceTree = ""; }; + C2FD33E31E687BF1008D13D4 /* PeerPhotoUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPhotoUpdater.swift; sourceTree = ""; }; + C2FD33EA1E696C78008D13D4 /* GroupsInCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsInCommon.swift; sourceTree = ""; }; + D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; + D00422D221677F4500719B67 /* ManagedAccountPresence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAccountPresence.swift; sourceTree = ""; }; + D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannelAdminRights.swift; sourceTree = ""; }; + D00BDA1B1EE5952A00C64C5E /* TelegramChannelBannedRights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannelBannedRights.swift; sourceTree = ""; }; + D00C7CCB1E3620C30080C3D5 /* CachedChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedChannelParticipants.swift; sourceTree = ""; }; + D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkMessageContentAsConsumedInteractively.swift; sourceTree = ""; }; + D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSecretChatMessageAutoremoveTimeoutInteractively.swift; sourceTree = ""; }; + D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeleramMediaUnsupported.swift; sourceTree = ""; }; + D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaGame.swift; sourceTree = ""; }; + D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumableContentMessageAttribute.swift; sourceTree = ""; }; + D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeConsumeMessageContentsOperations.swift; sourceTree = ""; }; + D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeConsumeMessageContentsOperation.swift; sourceTree = ""; }; + D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInputActivity.swift; sourceTree = ""; }; + D00D97C91E32917C00E5C2B6 /* PeerInputActivityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInputActivityManager.swift; sourceTree = ""; }; + D00DBBD61E64E41100DB5485 /* CreateSecretChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateSecretChat.swift; sourceTree = ""; }; + D00DBBD91E64E67E00DB5485 /* UpdateSecretChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateSecretChat.swift; sourceTree = ""; }; + D0119CAF20CA9EA800895300 /* MarkAllChatsAsRead.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkAllChatsAsRead.swift; sourceTree = ""; }; + D0136308208F3B0900EB3653 /* SecureIdValueContentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdValueContentError.swift; sourceTree = ""; }; + D015E00D225CA61100CB9E8A /* FindChannelById.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindChannelById.swift; sourceTree = ""; }; + D01749581E1092BC0057C89A /* RequestStartBot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestStartBot.swift; sourceTree = ""; }; + D017495D1E118F790057C89A /* AccountStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManager.swift; sourceTree = ""; }; + D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = ""; }; + D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = ""; }; + D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmTwoStepRecoveryEmail.swift; sourceTree = ""; }; + D018D3361E648ACF00C5E089 /* ChannelCreation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelCreation.swift; sourceTree = ""; }; + D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = ""; }; + D018EE042045E95000CBB130 /* CheckPeerChatServiceActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPeerChatServiceActions.swift; sourceTree = ""; }; + D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatRekeySession.swift; sourceTree = ""; }; + D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = ""; }; + D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedStickersOperations.swift; sourceTree = ""; }; + D01A21AB1F38D10E00DDA104 /* SavedStickerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavedStickerItem.swift; sourceTree = ""; }; + D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMessageActionCallback.swift; sourceTree = ""; }; + D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestEditMessage.swift; sourceTree = ""; }; + D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyUpdateMessage.swift; sourceTree = ""; }; + D01B264823324CF800A6448B /* Wallets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallets.swift; sourceTree = ""; }; + D01B27A11E394D8B0022A4C0 /* PrivacySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = ""; }; + D01C06B61FBBA269001561AB /* CanSendMessagesToPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanSendMessagesToPeer.swift; sourceTree = ""; }; + D01C7ED21EF5DF83008305F1 /* LimitsConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LimitsConfiguration.swift; sourceTree = ""; }; + D01C7ED51EF5E468008305F1 /* ProxySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxySettings.swift; sourceTree = ""; }; + D01C7F031EFC1C49008305F1 /* DeviceContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceContact.swift; sourceTree = ""; }; + D01D6BF81E42A713006151C6 /* SearchStickers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchStickers.swift; sourceTree = ""; }; + D0208AF32306E92B00A23503 /* libphonenumbermac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumbermac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D021E0DE1DB539FC00C6B04F /* StickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPack.swift; sourceTree = ""; }; + D021E0E11DB5401A00C6B04F /* StickerManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManagement.swift; sourceTree = ""; }; + D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResourceNetworkStatsTag.swift; sourceTree = ""; }; + D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelHistoryAvailabilitySettings.swift; sourceTree = ""; }; + D023E67721540624008C27D1 /* UpdateMessageMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageMedia.swift; sourceTree = ""; }; + D026099D20C695AF006C34AC /* Wallpapers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallpapers.swift; sourceTree = ""; }; + D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteMessagesInteractively.swift; sourceTree = ""; }; + D02ABC7D1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudChatRemoveMessagesOperation.swift; sourceTree = ""; }; + D02ABC801E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedCloudChatRemoveMessagesOperations.swift; sourceTree = ""; }; + D02B198F21FB1D520094A764 /* RegisterNotificationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterNotificationToken.swift; sourceTree = ""; }; + D02D60A6206BA5F900FEFE1E /* SecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdValue.swift; sourceTree = ""; }; + D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifySecureIdValue.swift; sourceTree = ""; }; + D02DADC02139A1FC00116225 /* ContactSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSyncManager.swift; sourceTree = ""; }; + D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = ""; }; + D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactions.swift; sourceTree = ""; }; + D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMessageAttribute.swift; sourceTree = ""; }; + D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchedMediaResource.swift; sourceTree = ""; }; + D033873F223BD48B007A2CE4 /* ContactsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsSettings.swift; sourceTree = ""; }; + D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeAccountAfterLogin.swift; sourceTree = ""; }; + D033FEAF1E61EB0200644997 /* PeerContactSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerContactSettings.swift; sourceTree = ""; }; + D033FEB21E61F3C000644997 /* ReportPeer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportPeer.swift; sourceTree = ""; }; + D033FEB51E61F3F900644997 /* BlockedPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockedPeers.swift; sourceTree = ""; }; + D035732E22B5C24F00F0920D /* TelegramApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramApi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0380DB9204EF306000414AB /* MessageMediaPreuploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageMediaPreuploadManager.swift; sourceTree = ""; }; + D03B0CB81D62233400955575 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; + D03B0CBC1D62234300955575 /* Regex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regex.swift; sourceTree = ""; }; + D03B0CBE1D62234A00955575 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; + D03B0CC01D62235000955575 /* StringFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringFormat.swift; sourceTree = ""; }; + D03B0CD41D62245300955575 /* TelegramUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUser.swift; sourceTree = ""; }; + D03B0CD51D62245300955575 /* TelegramGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramGroup.swift; sourceTree = ""; }; + D03B0CD81D62245B00955575 /* PeerUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerUtils.swift; sourceTree = ""; }; + D03B0CDA1D62245F00955575 /* ApiUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiUtils.swift; sourceTree = ""; }; + D03B0CDF1D62249100955575 /* StoreMessage_Telegram.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreMessage_Telegram.swift; sourceTree = ""; }; + D03B0CE11D62249B00955575 /* InlineBotMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InlineBotMessageAttribute.swift; sourceTree = ""; }; + D03B0CE31D62249F00955575 /* TextEntitiesMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntitiesMessageAttribute.swift; sourceTree = ""; }; + D03B0CE51D6224A700955575 /* ReplyMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMessageAttribute.swift; sourceTree = ""; }; + D03B0CE71D6224AD00955575 /* ViewCountMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewCountMessageAttribute.swift; sourceTree = ""; }; + D03B0CEC1D62250800955575 /* TelegramMediaAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaAction.swift; sourceTree = ""; }; + D03B0CED1D62250800955575 /* TelegramMediaContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaContact.swift; sourceTree = ""; }; + D03B0CEE1D62250800955575 /* TelegramMediaFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaFile.swift; sourceTree = ""; }; + D03B0CEF1D62250800955575 /* TelegramMediaImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaImage.swift; sourceTree = ""; }; + D03B0CF11D62250800955575 /* TelegramMediaMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaMap.swift; sourceTree = ""; }; + D03B0CF31D62250800955575 /* TelegramMediaWebpage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaWebpage.swift; sourceTree = ""; }; + D03B0CFF1D62255C00955575 /* ChannelState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelState.swift; sourceTree = ""; }; + D03B0D001D62255C00955575 /* EnqueueMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnqueueMessage.swift; sourceTree = ""; }; + D03B0D011D62255C00955575 /* Holes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Holes.swift; sourceTree = ""; }; + D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizePeerReadState.swift; sourceTree = ""; }; + D03B0D051D62255C00955575 /* UpdateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroup.swift; sourceTree = ""; }; + D03B0D061D62255C00955575 /* UpdateMessageService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateMessageService.swift; sourceTree = ""; }; + D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesApiUtils.swift; sourceTree = ""; }; + D03B0D391D6319E200955575 /* Fetch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetch.swift; sourceTree = ""; }; + D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudFileMediaResource.swift; sourceTree = ""; }; + D03B0D561D631A6900955575 /* Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Download.swift; sourceTree = ""; }; + D03B0D571D631A6900955575 /* MultipartFetch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFetch.swift; sourceTree = ""; }; + D03B0D581D631A6900955575 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + D03B0D591D631A6900955575 /* Serialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Serialization.swift; sourceTree = ""; }; + D03B0D611D631A8B00955575 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; + D03B0D631D631A8B00955575 /* AccountViewTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountViewTracker.swift; sourceTree = ""; }; + D03B0D641D631A8B00955575 /* RecentPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentPeers.swift; sourceTree = ""; }; + D03B0D6C1D631AA300955575 /* ContactManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactManagement.swift; sourceTree = ""; }; + D03B0D711D631ABA00955575 /* SearchMessages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchMessages.swift; sourceTree = ""; }; + D03B0E571D631EB900955575 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + D03B0E591D63215200955575 /* TelegramCore.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = TelegramCore.xcconfig; path = "../../submodules/telegram-ios/submodules/TelegramCore/Sources/Config/TelegramCore.xcconfig"; sourceTree = ""; }; + D03B0E5D1D6327F600955575 /* SSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03B0E5F1D6327FF00955575 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + D03B0E611D63281A00955575 /* libavcodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavcodec.a; path = "third-party/FFmpeg-iOS/lib/libavcodec.a"; sourceTree = ""; }; + D03B0E621D63281A00955575 /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavformat.a; path = "third-party/FFmpeg-iOS/lib/libavformat.a"; sourceTree = ""; }; + D03B0E631D63281A00955575 /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = "third-party/FFmpeg-iOS/lib/libavutil.a"; sourceTree = ""; }; + D03B0E641D63281A00955575 /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswresample.a; path = "third-party/FFmpeg-iOS/lib/libswresample.a"; sourceTree = ""; }; + D03B0E691D63283000955575 /* libwebp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwebp.a; path = "third-party/libwebp/lib/libwebp.a"; sourceTree = ""; }; + D03B0E6B1D63283C00955575 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartUpload.swift; sourceTree = ""; }; + D03DC90F1F82E344001D584C /* AccountStateReset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountStateReset.swift; sourceTree = ""; }; + D03E3D27230447960049C28B /* RestrictedContentMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestrictedContentMessageAttribute.swift; sourceTree = ""; }; + D03E416B2304D5B30049C28B /* ValidateAddressNameInteractive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidateAddressNameInteractive.swift; sourceTree = ""; }; + D03E45D02305D34C0049C28B /* libphonenumber_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumber_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E45D32305D44A0049C28B /* libphonenumber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libphonenumber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D041E3F41E535464008C24B4 /* AddPeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddPeerMember.swift; sourceTree = ""; }; + D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePeerMember.swift; sourceTree = ""; }; + D042C6821E8D9DF800C863B0 /* Unixtime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unixtime.swift; sourceTree = ""; }; + D0439B5C228ECB270067E026 /* RequestPhoneNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPhoneNumber.swift; sourceTree = ""; }; + D0439B5F228EDE430067E026 /* ContentRequiresValidationMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentRequiresValidationMessageAttribute.swift; sourceTree = ""; }; + D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessSecretChatIncomingDecryptedOperations.swift; sourceTree = ""; }; + D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatEncryption.swift; sourceTree = ""; }; + D0448C9E1E27F5EB005A61A7 /* Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Random.swift; sourceTree = ""; }; + D0448CA11E291B14005A61A7 /* FetchSecretFileResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchSecretFileResource.swift; sourceTree = ""; }; + D0448CA41E29215A005A61A7 /* MediaResourceApiUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResourceApiUtils.swift; sourceTree = ""; }; + D04554A521B43440007A6DD9 /* CancelAccountReset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelAccountReset.swift; sourceTree = ""; }; + D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingContentInfoMessageAttribute.swift; sourceTree = ""; }; + D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeMarkAllUnseenPersonalMessagesOperation.swift; sourceTree = ""; }; + D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift; sourceTree = ""; }; + D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedProxyInfoUpdates.swift; sourceTree = ""; }; + D049EAD41E43D98500A2CD3A /* RecentMediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentMediaItem.swift; sourceTree = ""; }; + D049EAD71E43DAD200A2CD3A /* ManagedRecentStickers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedRecentStickers.swift; sourceTree = ""; }; + D049EAE71E44B67100A2CD3A /* RecentPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentPeerItem.swift; sourceTree = ""; }; + D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentlySearchedPeerIds.swift; sourceTree = ""; }; + D049EAF41E44DF3300A2CD3A /* AccountState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountState.swift; sourceTree = ""; }; + D04CAA591E83310D0047E51F /* MD5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = ""; }; + D04D21362306EC9A00609388 /* MacInternalUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacInternalUpdater.swift; sourceTree = ""; }; + D04D213B230AC35A00609388 /* WasScheduledMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WasScheduledMessageAttribute.swift; sourceTree = ""; }; + D04D8FF3209A4B0700865719 /* NetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettings.swift; sourceTree = ""; }; + D050F2501E4A59C200988324 /* JoinLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinLink.swift; sourceTree = ""; }; + D051DB13215EC5A300F30F92 /* AppChangelogState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppChangelogState.swift; sourceTree = ""; }; + D051DB16215ECC4D00F30F92 /* AppChangelog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppChangelog.swift; sourceTree = ""; }; + D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalInputActivities.swift; sourceTree = ""; }; + D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleMessageView.swift; sourceTree = ""; }; + D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateContactName.swift; sourceTree = ""; }; + D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebpagePreview.swift; sourceTree = ""; }; + D0529D2321A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeRecentlyUsedMediaOperations.swift; sourceTree = ""; }; + D0529D2621A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeRecentlyUsedMediaOperations.swift; sourceTree = ""; }; + D053B4171F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorSignatureMessageAttribute.swift; sourceTree = ""; }; + D053B41A1F18DEF500E2D58A /* TelegramMediaExpiredContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaExpiredContent.swift; sourceTree = ""; }; + D05452061E7B5093006EEF19 /* LoadedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedStickerPack.swift; sourceTree = ""; }; + D054648A2073854A002ECC1E /* SecureIdPersonalDetailsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPersonalDetailsValue.swift; sourceTree = ""; }; + D054648D20738626002ECC1E /* SecureIdDriversLicenseValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDriversLicenseValue.swift; sourceTree = ""; }; + D054649020738653002ECC1E /* SecureIdIDCardValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdIDCardValue.swift; sourceTree = ""; }; + D0546493207386D7002ECC1E /* SecureIdUtilityBillValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdUtilityBillValue.swift; sourceTree = ""; }; + D05464962073872C002ECC1E /* SecureIdBankStatementValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdBankStatementValue.swift; sourceTree = ""; }; + D054649920738760002ECC1E /* SecureIdRentalAgreementValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdRentalAgreementValue.swift; sourceTree = ""; }; + D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerInfo.swift; sourceTree = ""; }; + D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = ""; }; + D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedGifsOperation.swift; sourceTree = ""; }; + D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedGifsOperations.swift; sourceTree = ""; }; + D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccount.swift; sourceTree = ""; }; + D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandaloneSendMessage.swift; sourceTree = ""; }; + D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatedAccountPrivacySettings.swift; sourceTree = ""; }; + D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentAccountSessions.swift; sourceTree = ""; }; + D05A32E61E6F0B5C002760B4 /* RecentAccountSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentAccountSession.swift; sourceTree = ""; }; + D05D8B362192F8AF0064586F /* LocalizationListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationListState.swift; sourceTree = ""; }; + D0613FC91E60440600202CDB /* InvitationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvitationLinks.swift; sourceTree = ""; }; + D0613FCE1E60520700202CDB /* ChannelMembers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMembers.swift; sourceTree = ""; }; + D0613FD61E606B3B00202CDB /* ConvertGroupToSupergroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertGroupToSupergroup.swift; sourceTree = ""; }; + D0633CD12253A528003DD95F /* ChatOnlineMembers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatOnlineMembers.swift; sourceTree = ""; }; + D0633CDA2253C0D3003DD95F /* CloudMediaResourceParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudMediaResourceParameters.swift; sourceTree = ""; }; + D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarliestUnseenPersonalMentionMessage.swift; sourceTree = ""; }; + D06706641D512ADB00DED3E3 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D06706651D512ADB00DED3E3 /* Display.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D06706671D512ADB00DED3E3 /* Postbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D06706681D512ADB00DED3E3 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D067066E1D512AEB00DED3E3 /* MtProtoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MtProtoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D06CA13422772EB20094E707 /* ManagedNotificationSettingsBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedNotificationSettingsBehaviors.swift; sourceTree = ""; }; + D06ECFC720B810D300C576C2 /* TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfService.swift; sourceTree = ""; }; + D07047B31F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsumablePersonalMentionMessageAttribute.swift; sourceTree = ""; }; + D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConsumePersonalMessagesActions.swift; sourceTree = ""; }; + D072F356231542740009E66F /* MessageReactionList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactionList.swift; sourceTree = ""; }; + D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = ""; }; + D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = ""; }; + D0750C8F22B2FD8300BE5F6E /* PeerAccessHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerAccessHash.swift; sourceTree = ""; }; + D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentForm.swift; sourceTree = ""; }; + D076F8882296D8E9004F895A /* ManageChannelDiscussionGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageChannelDiscussionGroup.swift; sourceTree = ""; }; + D07827BA1E00451F00071108 /* SearchPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPeers.swift; sourceTree = ""; }; + D07827C81E02F59C00071108 /* InstantPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPage.swift; sourceTree = ""; }; + D07827CA1E02F5B200071108 /* RichText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RichText.swift; sourceTree = ""; }; + D07E413E208A769D00FCA8F0 /* ProxyServersStatuses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyServersStatuses.swift; sourceTree = ""; }; + D081E109217F5ADE003CD921 /* LocalizationPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationPreview.swift; sourceTree = ""; }; + D0830FC1241254FD006198E7 /* BankCards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankCards.swift; sourceTree = ""; }; + D08774FB1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedGlobalNotificationSettings.swift; sourceTree = ""; }; + D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalNotificationSettings.swift; sourceTree = ""; }; + D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRepresentationWithReference.swift; sourceTree = ""; }; + D08984F12114B97400918162 /* ClearCloudDrafts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearCloudDrafts.swift; sourceTree = ""; }; + D08984F421187ECA00918162 /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; + D08CAA7C1ED77EE90000FDA8 /* LocalizationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationSettings.swift; sourceTree = ""; }; + D08CAA7F1ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestedLocalizationEntry.swift; sourceTree = ""; }; + D08CAA831ED8164B0000FDA8 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + D08CAA861ED81DD40000FDA8 /* LocalizationInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationInfo.swift; sourceTree = ""; }; + D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizations.swift; sourceTree = ""; }; + D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperations.swift; sourceTree = ""; }; + D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeInstalledStickerPacksOperations.swift; sourceTree = ""; }; + D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDataTypes.swift; sourceTree = ""; }; + D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPhoneValue.swift; sourceTree = ""; }; + D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdEmailValue.swift; sourceTree = ""; }; + D093D805206539D000BC3599 /* SaveSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSecureIdValue.swift; sourceTree = ""; }; + D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsContext.swift; sourceTree = ""; }; + D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = ""; }; + D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewStateValidation.swift; sourceTree = ""; }; + D099E221229420D600561B75 /* BlockedPeersContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedPeersContext.swift; sourceTree = ""; }; + D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = ""; }; + D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = ""; }; + D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAccessRestrictionInfo.swift; sourceTree = ""; }; + D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageManager.swift; sourceTree = ""; }; + D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingMessageUploadedContent.swift; sourceTree = ""; }; + D0A2764A249C9989005E3C77 /* PeerStatistics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerStatistics.swift; sourceTree = ""; }; + D0A3E446214802C7008ACEF6 /* VoipConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipConfiguration.swift; sourceTree = ""; }; + D0A472B51F4CBE8B00E0EEDA /* LoadedPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadedPeer.swift; sourceTree = ""; }; + D0A8998E217A37A000759EE6 /* NotificationAutolockReportManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAutolockReportManager.swift; sourceTree = ""; }; + D0AAD1A71E32602500D5B9DE /* AutoremoveTimeoutMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoremoveTimeoutMessageAttribute.swift; sourceTree = ""; }; + D0AAD1A91E32638500D5B9DE /* ApplyMaxReadIndexInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyMaxReadIndexInteractively.swift; sourceTree = ""; }; + D0AAD1B71E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAutoremoveMessageOperations.swift; sourceTree = ""; }; + D0AB0B911D65E9FA002C78E7 /* ManagedServiceViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedServiceViews.swift; sourceTree = ""; }; + D0AB0B931D662ECE002C78E7 /* ManagedMessageHistoryHoles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedMessageHistoryHoles.swift; sourceTree = ""; }; + D0AB0B951D662F0B002C78E7 /* ManagedChatListHoles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedChatListHoles.swift; sourceTree = ""; }; + D0AB0B991D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizePeerReadStates.swift; sourceTree = ""; }; + D0AB262521C2F991008F6685 /* TelegramMediaPoll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelegramMediaPoll.swift; sourceTree = ""; }; + D0AB262A21C3CE80008F6685 /* Polls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Polls.swift; sourceTree = ""; }; + D0AC49491D7097A400AA55DA /* SSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0AD02E21FFFA14800C1DCFF /* PeerLiveLocationsContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerLiveLocationsContext.swift; sourceTree = ""; }; + D0ADF910212B00DD00310BBC /* SecureIdConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdConfiguration.swift; sourceTree = ""; }; + D0AF32211FAC95C20097362B /* StandaloneUploadedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandaloneUploadedMedia.swift; sourceTree = ""; }; + D0AF32301FACEDEC0097362B /* CoreSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSettings.swift; sourceTree = ""; }; + D0AF32341FAE8C6B0097362B /* MultipeerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerManager.swift; sourceTree = ""; }; + D0AF32371FAE8C910097362B /* MultipeerConnectivity.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultipeerConnectivity.framework; path = System/Library/Frameworks/MultipeerConnectivity.framework; sourceTree = SDKROOT; }; + D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistoryPreloadManager.swift; sourceTree = ""; }; + D0B167221F9F972E00976B40 /* LoggingSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingSettings.swift; sourceTree = ""; }; + D0B2F7732052DEF700D3BFB9 /* TelegramDeviceContactImportInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelegramDeviceContactImportInfo.swift; sourceTree = ""; }; + D0B417C01D7DCEEF004562A4 /* ApiGroupOrChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiGroupOrChannel.swift; sourceTree = ""; }; + D0B418671D7E03D5004562A4 /* TelegramCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TelegramCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B418691D7E03D5004562A4 /* TelegramCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TelegramCore.h; sourceTree = ""; }; + D0B4186A1D7E03D5004562A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0B418701D7E0409004562A4 /* PostboxMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PostboxMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B418711D7E0409004562A4 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B4187E1D7E054E004562A4 /* MtProtoKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MtProtoKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0B843841DA6EDC4005F29E1 /* CachedChannelData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedChannelData.swift; sourceTree = ""; }; + D0B843861DA6F705005F29E1 /* UpdateCachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateCachedPeerData.swift; sourceTree = ""; }; + D0B843881DA7AB96005F29E1 /* ExportedInvitation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportedInvitation.swift; sourceTree = ""; }; + D0B8438B1DA7CF50005F29E1 /* BotInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotInfo.swift; sourceTree = ""; }; + D0B8438D1DA7D296005F29E1 /* CachedGroupParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedGroupParticipants.swift; sourceTree = ""; }; + D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePeerNotificationSettings.swift; sourceTree = ""; }; + D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = ""; }; + D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyUsedHashtags.swift; sourceTree = ""; }; + D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelParticipants.swift; sourceTree = ""; }; + D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = ""; }; + D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeers.swift; sourceTree = ""; }; + D0BC38741E40A7F70044D6FE /* RemovePeerChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePeerChat.swift; sourceTree = ""; }; + D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizePinnedChatsOperations.swift; sourceTree = ""; }; + D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TogglePeerChatPinned.swift; sourceTree = ""; }; + D0BE303920619EE800FBE6D8 /* SecureIdForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdForm.swift; sourceTree = ""; }; + D0BE303C2061A29100FBE6D8 /* RequestSecureIdForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSecureIdForm.swift; sourceTree = ""; }; + D0BE304A20627D9800FBE6D8 /* AccessSecureId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessSecureId.swift; sourceTree = ""; }; + D0BEAF5C1E54941B00BD963D /* Authorization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = ""; }; + D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedLocalizationUpdatesOperations.swift; sourceTree = ""; }; + D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeLocalizationUpdatesOperation.swift; sourceTree = ""; }; + D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeGroupedPeersOperation.swift; sourceTree = ""; }; + D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeGroupedPeersOperations.swift; sourceTree = ""; }; + D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchChatList.swift; sourceTree = ""; }; + D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedStickerPack.swift; sourceTree = ""; }; + D0C27B411F4B58C000A4E170 /* PeerSpecificStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerSpecificStickerPack.swift; sourceTree = ""; }; + D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchGroupMembers.swift; sourceTree = ""; }; + D0C48F381E8138DF0075317D /* ArchivedStickerPacksInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchivedStickerPacksInfo.swift; sourceTree = ""; }; + D0C48F3B1E8142EF0075317D /* LoadedPeerFromMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadedPeerFromMessage.swift; sourceTree = ""; }; + D0C50E331E93A86600F62E39 /* CallSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallSessionManager.swift; sourceTree = ""; }; + D0CA3F83207391560042D2B6 /* SecureIdPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPadding.swift; sourceTree = ""; }; + D0CA8E4A227209C4008A74C3 /* ManagedSynchronizeGroupMessageStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeGroupMessageStats.swift; sourceTree = ""; }; + D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MtProtoKitDynamic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0CB09AD24ADEFC9008CD050 /* Suggestions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Suggestions.swift; sourceTree = ""; }; + D0CC4AA322BA44960088F36D /* TelegramApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramApi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0CC4ADB22BA47280088F36D /* TelegramApiMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramApiMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0D376E522DCCFD600FA7D7C /* SlowMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlowMode.swift; sourceTree = ""; }; + D0D3CE3F23BB3F1100864F3C /* PendingUpdateMessageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingUpdateMessageManager.swift; sourceTree = ""; }; + D0D3CE4323BB3FD200864F3C /* ChatUpdatingMessageMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUpdatingMessageMedia.swift; sourceTree = ""; }; + D0D748011E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackInteractiveOperations.swift; sourceTree = ""; }; + D0DA1D311F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPendingPeerNotificationSettings.swift; sourceTree = ""; }; + D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstallInteractiveReadMessagesAction.swift; sourceTree = ""; }; + D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestChatContextResults.swift; sourceTree = ""; }; + D0DC354F1DE36900000195EB /* ChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatContextResult.swift; sourceTree = ""; }; + D0DEF73E242A41DB00A34A30 /* AccountStateManagementUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManagementUtils.swift; sourceTree = ""; }; + D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinChannel.swift; sourceTree = ""; }; + D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRepresentationsUtils.swift; sourceTree = ""; }; + D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageUtils.swift; sourceTree = ""; }; + D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerParticipants.swift; sourceTree = ""; }; + D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedSentMediaReferences.swift; sourceTree = ""; }; + D0E23DD41E8042F500B9B6D2 /* FeaturedStickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeaturedStickerPack.swift; sourceTree = ""; }; + D0E23DD91E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift; sourceTree = ""; }; + D0E23DDE1E8082A400B9B6D2 /* ArchivedStickerPacks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchivedStickerPacks.swift; sourceTree = ""; }; + D0E305A61E5B5CBE00D7A3A2 /* PeerAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAdmins.swift; sourceTree = ""; }; + D0E305A91E5BA02D00D7A3A2 /* ChannelBlacklist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelBlacklist.swift; sourceTree = ""; }; + D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchHttpResource.swift; sourceTree = ""; }; + D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageWithChatContextResult.swift; sourceTree = ""; }; + D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = ""; }; + D0E412D6206A866B00BEE4A2 /* UploadSecureIdFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadSecureIdFile.swift; sourceTree = ""; }; + D0E412DB206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdValueAccessContext.swift; sourceTree = ""; }; + D0E412E0206AB24700BEE4A2 /* SecureFileMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFileMediaResource.swift; sourceTree = ""; }; + D0E412E6206ABC7500BEE4A2 /* EncryptedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedMediaResource.swift; sourceTree = ""; }; + D0E412E9206AD18E00BEE4A2 /* DecryptedResourceData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptedResourceData.swift; sourceTree = ""; }; + D0E412ED206AF65500BEE4A2 /* GrantSecureIdAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrantSecureIdAccess.swift; sourceTree = ""; }; + D0E412F0206B9BB700BEE4A2 /* SecureIdPassportValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPassportValue.swift; sourceTree = ""; }; + D0E412F3206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdVerificationDocumentReference.swift; sourceTree = ""; }; + D0E41300206B9E6E00BEE4A2 /* SecureIdAddressValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdAddressValue.swift; sourceTree = ""; }; + D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = ""; }; + D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogContext.swift; sourceTree = ""; }; + D0E8B8B22044706300605593 /* ForwardGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardGame.swift; sourceTree = ""; }; + D0EA188120D3D2B1001AEE19 /* RemoteStorageConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStorageConfiguration.swift; sourceTree = ""; }; + D0EC55992101ED0800D1992C /* DeleteMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteMessages.swift; sourceTree = ""; }; + D0EE7FC020986BF400981319 /* SecureIdInternalPassportValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdInternalPassportValue.swift; sourceTree = ""; }; + D0EE7FC320986C5300981319 /* SecureIdPassportRegistrationValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPassportRegistrationValue.swift; sourceTree = ""; }; + D0EE7FC62098853100981319 /* SecureIdTemporaryRegistrationValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdTemporaryRegistrationValue.swift; sourceTree = ""; }; + D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConfigurationUpdates.swift; sourceTree = ""; }; + D0F19F6520E6620D00EEC860 /* MultiplexedRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiplexedRequestManager.swift; sourceTree = ""; }; + D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = ""; }; + D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = ""; }; + D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeerChatInterfaceState.swift; sourceTree = ""; }; + D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolvePeerByName.swift; sourceTree = ""; }; + D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeAccountPhoneNumber.swift; sourceTree = ""; }; + D0F760D722202FE20074F7E5 /* ChannelStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStats.swift; sourceTree = ""; }; + D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; + D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; + D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = ""; }; + D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = ""; }; + D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = ""; }; + D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = ""; }; + D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = ""; }; + D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatOutgoingOperation.swift; sourceTree = ""; }; + D0FA8B9D1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatIncomingEncryptedOperation.swift; sourceTree = ""; }; + D0FA8BA01E1F99E1001E855B /* SecretChatFileReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatFileReference.swift; sourceTree = ""; }; + D0FA8BA31E1FA341001E855B /* SecretChatKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatKeychain.swift; sourceTree = ""; }; + D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSecretChatOutgoingOperations.swift; sourceTree = ""; }; + D0FA8BAC1E1FD6E2001E855B /* MemoryBufferExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryBufferExtensions.swift; sourceTree = ""; }; + D0FA8BAF1E1FEC7E001E855B /* SecretChatEncryptionConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatEncryptionConfig.swift; sourceTree = ""; }; + D0FA8BB21E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessSecretChatIncomingEncryptedOperations.swift; sourceTree = ""; }; + D0FA8BB81E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatIncomingDecryptedOperation.swift; sourceTree = ""; }; + D0FC195A2020D1CA00FEDBB2 /* PeerGroupMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerGroupMessageStateVersionAttribute.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D0B418631D7E03D5004562A4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919038240CF9D1002011CA /* NetworkLogging.framework in Frameworks */, + A7919036240CF9CC002011CA /* CryptoUtils.framework in Frameworks */, + A7919034240CF9C7002011CA /* Reachability.framework in Frameworks */, + A7D281EC236C2FAD0000A9BF /* SyncCore.framework in Frameworks */, + A7D281EA236C27030000A9BF /* MtProtoKit.framework in Frameworks */, + A7D281E4236C257C0000A9BF /* TelegramApi.framework in Frameworks */, + A7D281E2236C25710000A9BF /* Postbox.framework in Frameworks */, + A7D281DE236C25500000A9BF /* SwiftSignalKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7D281ED236C327B0000A9BF /* thrid-party */ = { + isa = PBXGroup; + children = ( + ); + path = "thrid-party"; + sourceTree = ""; + }; + D01B264723324CE600A6448B /* Wallet */ = { + isa = PBXGroup; + children = ( + D01B264823324CF800A6448B /* Wallets.swift */, + ); + name = Wallet; + sourceTree = ""; + }; + D01B27A01E394D7B0022A4C0 /* Settings */ = { + isa = PBXGroup; + children = ( + 0900555521E4A96D0030924C /* Wallpaper.swift */, + D01B27A11E394D8B0022A4C0 /* PrivacySettings.swift */, + D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */, + D05A32E61E6F0B5C002760B4 /* RecentAccountSession.swift */, + D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */, + D08CAA7C1ED77EE90000FDA8 /* LocalizationSettings.swift */, + D01C7ED21EF5DF83008305F1 /* LimitsConfiguration.swift */, + D0A3E446214802C7008ACEF6 /* VoipConfiguration.swift */, + D0EA188120D3D2B1001AEE19 /* RemoteStorageConfiguration.swift */, + D01C7ED51EF5E468008305F1 /* ProxySettings.swift */, + D04D8FF3209A4B0700865719 /* NetworkSettings.swift */, + D0B167221F9F972E00976B40 /* LoggingSettings.swift */, + D0AF32301FACEDEC0097362B /* CoreSettings.swift */, + D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */, + D051DB13215EC5A300F30F92 /* AppChangelogState.swift */, + 0962E66E21B6147600245FD9 /* AppConfiguration.swift */, + 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */, + 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */, + 09B4A9B5230FBB2B005C2E08 /* Theme.swift */, + ); + name = Settings; + sourceTree = ""; + }; + D021E0DD1DB539E800C6B04F /* Item Collections */ = { + isa = PBXGroup; + children = ( + D021E0DE1DB539FC00C6B04F /* StickerPack.swift */, + D049EAD41E43D98500A2CD3A /* RecentMediaItem.swift */, + D01A21AB1F38D10E00DDA104 /* SavedStickerItem.swift */, + D049EAE71E44B67100A2CD3A /* RecentPeerItem.swift */, + D0E23DD41E8042F500B9B6D2 /* FeaturedStickerPack.swift */, + D0C48F381E8138DF0075317D /* ArchivedStickerPacksInfo.swift */, + ); + name = "Item Collections"; + sourceTree = ""; + }; + D021E0E01DB5400200C6B04F /* Sticker Management */ = { + isa = PBXGroup; + children = ( + A7D281FA236C34660000A9BF /* SynchronizeInstalledStickerPacksOperation.swift */, + D021E0E11DB5401A00C6B04F /* StickerManagement.swift */, + D01D6BF81E42A713006151C6 /* SearchStickers.swift */, + D049EAD71E43DAD200A2CD3A /* ManagedRecentStickers.swift */, + C251D7421E65E50500283EDE /* StickerSetInstallation.swift */, + D0D748011E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift */, + D05452061E7B5093006EEF19 /* LoadedStickerPack.swift */, + D0E23DDE1E8082A400B9B6D2 /* ArchivedStickerPacks.swift */, + D0C27B3E1F4B51D000A4E170 /* CachedStickerPack.swift */, + D0C27B411F4B58C000A4E170 /* PeerSpecificStickerPack.swift */, + C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */, + ); + name = "Sticker Management"; + sourceTree = ""; + }; + D03B0CB71D62232000955575 /* Utils */ = { + isa = PBXGroup; + children = ( + A7D281FC236C35E30000A9BF /* TelegramMediaWebFile.swift */, + D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */, + D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */, + D03B0CB81D62233400955575 /* Either.swift */, + D03B0CBC1D62234300955575 /* Regex.swift */, + D03B0CBE1D62234A00955575 /* Log.swift */, + D03B0CC01D62235000955575 /* StringFormat.swift */, + D0FA8BAC1E1FD6E2001E855B /* MemoryBufferExtensions.swift */, + D0448C9E1E27F5EB005A61A7 /* Random.swift */, + D04CAA591E83310D0047E51F /* MD5.swift */, + D042C6821E8D9DF800C863B0 /* Unixtime.swift */, + D01C7F031EFC1C49008305F1 /* DeviceContact.swift */, + D01C06B61FBBA269001561AB /* CanSendMessagesToPeer.swift */, + D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */, + D0E412E9206AD18E00BEE4A2 /* DecryptedResourceData.swift */, + C28D3CEF20D3DA900027F4D6 /* DeepLinkInfo.swift */, + 0962E66C21B5C56F00245FD9 /* JSON.swift */, + 0962E67421B6437600245FD9 /* SplitTest.swift */, + ); + name = Utils; + sourceTree = ""; + }; + D03B0CCF1D62242200955575 /* Objects */ = { + isa = PBXGroup; + children = ( + D03E5E0A1E55E0220029569A /* Accounts */, + D03B0CD01D62242C00955575 /* Peers */, + D03B0CD11D62242F00955575 /* Messages */, + D0FA8B961E1E952D001E855B /* Secret Chats */, + D021E0DD1DB539E800C6B04F /* Item Collections */, + D01B27A01E394D7B0022A4C0 /* Settings */, + D08CAA821ED816290000FDA8 /* Localization */, + ); + name = Objects; + sourceTree = ""; + }; + D03B0CD01D62242C00955575 /* Peers */ = { + isa = PBXGroup; + children = ( + D03B0CDA1D62245F00955575 /* ApiUtils.swift */, + D03B0CD81D62245B00955575 /* PeerUtils.swift */, + D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */, + D0750C8F22B2FD8300BE5F6E /* PeerAccessHash.swift */, + D03B0CD41D62245300955575 /* TelegramUser.swift */, + D03B0CD51D62245300955575 /* TelegramGroup.swift */, + D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */, + D0B417C01D7DCEEF004562A4 /* ApiGroupOrChannel.swift */, + D003702A1DA42586004308D3 /* PhoneNumber.swift */, + D0B8438B1DA7CF50005F29E1 /* BotInfo.swift */, + D0B843881DA7AB96005F29E1 /* ExportedInvitation.swift */, + D0B8438D1DA7D296005F29E1 /* CachedGroupParticipants.swift */, + D00C7CCB1E3620C30080C3D5 /* CachedChannelParticipants.swift */, + D0B843841DA6EDC4005F29E1 /* CachedChannelData.swift */, + D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */, + D00D97C61E32901700E5C2B6 /* PeerInputActivity.swift */, + D033FEAF1E61EB0200644997 /* PeerContactSettings.swift */, + D00BDA181EE593D600C64C5E /* TelegramChannelAdminRights.swift */, + D00BDA1B1EE5952A00C64C5E /* TelegramChannelBannedRights.swift */, + ); + name = Peers; + sourceTree = ""; + }; + D03B0CD11D62242F00955575 /* Messages */ = { + isa = PBXGroup; + children = ( + D03B0CDF1D62249100955575 /* StoreMessage_Telegram.swift */, + D03B0CDC1D62247800955575 /* Attributes */, + D03B0CDD1D62247D00955575 /* Media */, + ); + name = Messages; + sourceTree = ""; + }; + D03B0CDC1D62247800955575 /* Attributes */ = { + isa = PBXGroup; + children = ( + D0AAD1A71E32602500D5B9DE /* AutoremoveTimeoutMessageAttribute.swift */, + D03B0CE71D6224AD00955575 /* ViewCountMessageAttribute.swift */, + D03B0CE51D6224A700955575 /* ReplyMessageAttribute.swift */, + D03B0CE31D62249F00955575 /* TextEntitiesMessageAttribute.swift */, + D03B0CE11D62249B00955575 /* InlineBotMessageAttribute.swift */, + D053B4171F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift */, + D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */, + D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */, + D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */, + D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */, + D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */, + D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */, + D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */, + D07047B31F3DF1FE00F6A8D4 /* ConsumablePersonalMentionMessageAttribute.swift */, + D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */, + D0FC195A2020D1CA00FEDBB2 /* PeerGroupMessageStateVersionAttribute.swift */, + C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */, + C210DD611FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift */, + D0439B5F228EDE430067E026 /* ContentRequiresValidationMessageAttribute.swift */, + 09FC986A22FD882200915E37 /* OutgoingScheduleInfoMessageAttribute.swift */, + D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */, + D03E3D27230447960049C28B /* RestrictedContentMessageAttribute.swift */, + D04D213B230AC35A00609388 /* WasScheduledMessageAttribute.swift */, + ); + name = Attributes; + sourceTree = ""; + }; + D03B0CDD1D62247D00955575 /* Media */ = { + isa = PBXGroup; + children = ( + D00D34381E6EC9520057B307 /* TeleramMediaUnsupported.swift */, + D03B0CEC1D62250800955575 /* TelegramMediaAction.swift */, + D03B0CED1D62250800955575 /* TelegramMediaContact.swift */, + D03B0CEE1D62250800955575 /* TelegramMediaFile.swift */, + D03B0CEF1D62250800955575 /* TelegramMediaImage.swift */, + D03B0CF11D62250800955575 /* TelegramMediaMap.swift */, + D03B0CF31D62250800955575 /* TelegramMediaWebpage.swift */, + D00D343B1E6EC9770057B307 /* TelegramMediaGame.swift */, + D053B41A1F18DEF500E2D58A /* TelegramMediaExpiredContent.swift */, + D07827CA1E02F5B200071108 /* RichText.swift */, + D07827C81E02F59C00071108 /* InstantPage.swift */, + C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */, + C2E0646C1ECF171D00387BB8 /* TelegramMediaWebDocument.swift */, + D0AB262521C2F991008F6685 /* TelegramMediaPoll.swift */, + ); + name = Media; + sourceTree = ""; + }; + D03B0CFE1D62252200955575 /* State */ = { + isa = PBXGroup; + children = ( + D0DEF73E242A41DB00A34A30 /* AccountStateManagementUtils.swift */, + D03B0CFF1D62255C00955575 /* ChannelState.swift */, + D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */, + D03B0D001D62255C00955575 /* EnqueueMessage.swift */, + D03B0D011D62255C00955575 /* Holes.swift */, + D017495D1E118F790057C89A /* AccountStateManager.swift */, + D017495F1E118FC30057C89A /* AccountIntermediateState.swift */, + D03B0D051D62255C00955575 /* UpdateGroup.swift */, + D03DC90F1F82E344001D584C /* AccountStateReset.swift */, + D03B0D041D62255C00955575 /* SynchronizePeerReadState.swift */, + D03B0D061D62255C00955575 /* UpdateMessageService.swift */, + D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */, + D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */, + D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */, + D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */, + D02ABC7D1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift */, + D02ABC801E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift */, + D0AAD1B71E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift */, + D00D97C91E32917C00E5C2B6 /* PeerInputActivityManager.swift */, + D0AB0B991D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift */, + D0BC38761E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift */, + D00DBBD91E64E67E00DB5485 /* UpdateSecretChat.swift */, + D00D34441E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift */, + D00D34411E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift */, + D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */, + D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */, + D0E23DD91E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift */, + D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */, + D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */, + D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */, + D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */, + D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */, + D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */, + D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */, + D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */, + D0C0B58C1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift */, + D0C0B5891ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift */, + D07047B61F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift */, + D0C26D651FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift */, + D0C26D681FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift */, + D0AF32211FAC95C20097362B /* StandaloneUploadedMedia.swift */, + D0380DB9204EF306000414AB /* MessageMediaPreuploadManager.swift */, + D048B4AB20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift */, + D0467D0A20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift */, + D0467D1420D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift */, + D00422D221677F4500719B67 /* ManagedAccountPresence.swift */, + D0A8998E217A37A000759EE6 /* NotificationAutolockReportManager.swift */, + 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */, + D0529D2321A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift */, + D0529D2621A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift */, + 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */, + 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */, + 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */, + 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */, + 093857A72243D87900EB6A54 /* SynchronizeEmojiKeywordsOperation.swift */, + 093857A62243D87800EB6A54 /* ManagedSynchronizeEmojiKeywordsOperations.swift */, + D0CA8E4A227209C4008A74C3 /* ManagedSynchronizeGroupMessageStats.swift */, + D06CA13422772EB20094E707 /* ManagedNotificationSettingsBehaviors.swift */, + 0925903622F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift */, + ); + name = State; + sourceTree = ""; + }; + D03B0D111D62256B00955575 /* Media */ = { + isa = PBXGroup; + children = ( + D03B0D121D62257600955575 /* Resources */, + D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */, + D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */, + ); + name = Media; + sourceTree = ""; + }; + D03B0D121D62257600955575 /* Resources */ = { + isa = PBXGroup; + children = ( + D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */, + D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */, + D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */, + D03B0D391D6319E200955575 /* Fetch.swift */, + D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */, + D0448CA11E291B14005A61A7 /* FetchSecretFileResource.swift */, + D0448CA41E29215A005A61A7 /* MediaResourceApiUtils.swift */, + D0E412E6206ABC7500BEE4A2 /* EncryptedMediaResource.swift */, + D0E412E0206AB24700BEE4A2 /* SecureFileMediaResource.swift */, + D0633CDA2253C0D3003DD95F /* CloudMediaResourceParameters.swift */, + ); + name = Resources; + sourceTree = ""; + }; + D03B0D531D631A4400955575 /* Network */ = { + isa = PBXGroup; + children = ( + D03B0D561D631A6900955575 /* Download.swift */, + D03B0D571D631A6900955575 /* MultipartFetch.swift */, + D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */, + D03B0D581D631A6900955575 /* Network.swift */, + D03B0D591D631A6900955575 /* Serialization.swift */, + D0F19F6520E6620D00EEC860 /* MultiplexedRequestManager.swift */, + D08984F421187ECA00918162 /* NetworkType.swift */, + ); + name = Network; + sourceTree = ""; + }; + D03B0D601D631A7200955575 /* Account */ = { + isa = PBXGroup; + children = ( + A7F2833C239E4EE300742C20 /* ContentSettings.swift */, + A71DC83E2387CE10000EEDE2 /* AuthTransfer.swift */, + A71DC83C2387CC4C000EEDE2 /* UnauthorizedAccountStateManager.swift */, + D03B0D611D631A8B00955575 /* Account.swift */, + D049EAF41E44DF3300A2CD3A /* AccountState.swift */, + D03B0D631D631A8B00955575 /* AccountViewTracker.swift */, + D03B0D641D631A8B00955575 /* RecentPeers.swift */, + D0AB0B911D65E9FA002C78E7 /* ManagedServiceViews.swift */, + D0AB0B931D662ECE002C78E7 /* ManagedMessageHistoryHoles.swift */, + D0AB0B951D662F0B002C78E7 /* ManagedChatListHoles.swift */, + D0B843861DA6F705005F29E1 /* UpdateCachedPeerData.swift */, + D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */, + D08774FB1E3E39F600A97350 /* ManagedGlobalNotificationSettings.swift */, + D0BEAF5C1E54941B00BD963D /* Authorization.swift */, + D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */, + D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */, + D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */, + D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */, + D06ECFC720B810D300C576C2 /* TermsOfService.swift */, + D051DB16215ECC4D00F30F92 /* AppChangelog.swift */, + D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */, + D02B198F21FB1D520094A764 /* RegisterNotificationToken.swift */, + D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */, + D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */, + 09EC0DE822C6825D00E7185B /* AppUpdate.swift */, + ); + name = Account; + sourceTree = ""; + }; + D03B0D691D631A9200955575 /* Contacts */ = { + isa = PBXGroup; + children = ( + D03B0D6C1D631AA300955575 /* ContactManagement.swift */, + D0B2F7732052DEF700D3BFB9 /* TelegramDeviceContactImportInfo.swift */, + D02DADC02139A1FC00116225 /* ContactSyncManager.swift */, + D0439B5C228ECB270067E026 /* RequestPhoneNumber.swift */, + ); + name = Contacts; + sourceTree = ""; + }; + D03B0D6E1D631AA900955575 /* Messages */ = { + isa = PBXGroup; + children = ( + D0D3CE4323BB3FD200864F3C /* ChatUpdatingMessageMedia.swift */, + D0D3CE3F23BB3F1100864F3C /* PendingUpdateMessageManager.swift */, + D03B0D711D631ABA00955575 /* SearchMessages.swift */, + D0EC55992101ED0800D1992C /* DeleteMessages.swift */, + D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */, + D0AB262A21C3CE80008F6685 /* Polls.swift */, + D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */, + D072F356231542740009E66F /* MessageReactionList.swift */, + D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */, + D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */, + D0DC354F1DE36900000195EB /* ChatContextResult.swift */, + D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */, + D01749581E1092BC0057C89A /* RequestStartBot.swift */, + D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */, + D0AAD1A91E32638500D5B9DE /* ApplyMaxReadIndexInteractively.swift */, + D00C7CDF1E3785700080C3D5 /* MarkMessageContentAsConsumedInteractively.swift */, + C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */, + D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */, + D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */, + D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */, + D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */, + D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */, + D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */, + D0E8B8B22044706300605593 /* ForwardGame.swift */, + D0119CAF20CA9EA800895300 /* MarkAllChatsAsRead.swift */, + D023E67721540624008C27D1 /* UpdateMessageMedia.swift */, + 09FC986C22FD99D400915E37 /* ScheduledMessages.swift */, + ); + name = Messages; + sourceTree = ""; + }; + D03E5E0A1E55E0220029569A /* Accounts */ = { + isa = PBXGroup; + children = ( + ); + name = Accounts; + sourceTree = ""; + }; + D04D21352306EC8700609388 /* Mac Internal Updater */ = { + isa = PBXGroup; + children = ( + D04D21362306EC9A00609388 /* MacInternalUpdater.swift */, + ); + name = "Mac Internal Updater"; + sourceTree = ""; + }; + D05A32DF1E6F096B002760B4 /* Settings */ = { + isa = PBXGroup; + children = ( + D0CB09AD24ADEFC9008CD050 /* Suggestions.swift */, + D026099D20C695AF006C34AC /* Wallpapers.swift */, + D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */, + D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */, + D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */, + D0F53BE81E784A4800117362 /* ChangeAccountPhoneNumber.swift */, + 9FC8ADAA206BBFF10094F7B4 /* RecentWebSessions.swift */, + D07E413E208A769D00FCA8F0 /* ProxyServersStatuses.swift */, + D08984F12114B97400918162 /* ClearCloudDrafts.swift */, + D04554A521B43440007A6DD9 /* CancelAccountReset.swift */, + D033873F223BD48B007A2CE4 /* ContactsSettings.swift */, + D099E221229420D600561B75 /* BlockedPeersContext.swift */, + D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */, + 09B4A9B3230FB70B005C2E08 /* Themes.swift */, + ); + name = Settings; + sourceTree = ""; + }; + D06706631D512ADA00DED3E3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7919037240CF9D1002011CA /* NetworkLogging.framework */, + A7919035240CF9CC002011CA /* CryptoUtils.framework */, + A7919033240CF9C7002011CA /* Reachability.framework */, + A7D281EB236C2FAD0000A9BF /* SyncCore.framework */, + A7D281E9236C27030000A9BF /* MtProtoKit.framework */, + A7D281E3236C257C0000A9BF /* TelegramApi.framework */, + A7D281E1236C25710000A9BF /* Postbox.framework */, + A7D281DF236C25580000A9BF /* libphonenumber.framework */, + A7D281DD236C25500000A9BF /* SwiftSignalKit.framework */, + A7D281DB236C25440000A9BF /* MtProtoKitMac.framework */, + A7D281D9236C253C0000A9BF /* TelegramApi.framework */, + A7D281D7236C25370000A9BF /* libphonenumber.framework */, + D0208AF32306E92B00A23503 /* libphonenumbermac.framework */, + D03E45D32305D44A0049C28B /* libphonenumber.framework */, + D03E45D02305D34C0049C28B /* libphonenumber_iOS.framework */, + D0CC4ADB22BA47280088F36D /* TelegramApiMac.framework */, + D0CC4AA322BA44960088F36D /* TelegramApi.framework */, + D035732E22B5C24F00F0920D /* TelegramApi.framework */, + D0AF32371FAE8C910097362B /* MultipeerConnectivity.framework */, + D0B4187E1D7E054E004562A4 /* MtProtoKitMac.framework */, + D0B418701D7E0409004562A4 /* PostboxMac.framework */, + D0B418711D7E0409004562A4 /* SwiftSignalKitMac.framework */, + D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */, + D0AC49491D7097A400AA55DA /* SSignalKit.framework */, + D03B0E6B1D63283C00955575 /* libiconv.tbd */, + D03B0E691D63283000955575 /* libwebp.a */, + D03B0E611D63281A00955575 /* libavcodec.a */, + D03B0E621D63281A00955575 /* libavformat.a */, + D03B0E631D63281A00955575 /* libavutil.a */, + D03B0E641D63281A00955575 /* libswresample.a */, + D03B0E5F1D6327FF00955575 /* libz.tbd */, + D03B0E5D1D6327F600955575 /* SSignalKit.framework */, + D03B0E571D631EB900955575 /* CoreMedia.framework */, + D067066E1D512AEB00DED3E3 /* MtProtoKit.framework */, + D06706641D512ADB00DED3E3 /* AsyncDisplayKit.framework */, + D06706651D512ADB00DED3E3 /* Display.framework */, + D06706671D512ADB00DED3E3 /* Postbox.framework */, + D06706681D512ADB00DED3E3 /* SwiftSignalKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D0754D281EEE10D800884F6E /* Bot Payments */ = { + isa = PBXGroup; + children = ( + D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */, + ); + name = "Bot Payments"; + sourceTree = ""; + }; + D08CAA821ED816290000FDA8 /* Localization */ = { + isa = PBXGroup; + children = ( + D08CAA7F1ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift */, + D08CAA831ED8164B0000FDA8 /* Localization.swift */, + D08CAA861ED81DD40000FDA8 /* LocalizationInfo.swift */, + D05D8B362192F8AF0064586F /* LocalizationListState.swift */, + 093857AA2243D88C00EB6A54 /* EmojiKeywords.swift */, + ); + name = Localization; + sourceTree = ""; + }; + D08CAA8A1ED81EA10000FDA8 /* Localization */ = { + isa = PBXGroup; + children = ( + D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */, + D081E109217F5ADE003CD921 /* LocalizationPreview.swift */, + ); + name = Localization; + sourceTree = ""; + }; + D093D7E82064135300BC3599 /* Values */ = { + isa = PBXGroup; + children = ( + D02D60A6206BA5F900FEFE1E /* SecureIdValue.swift */, + D0E412DB206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift */, + D093D7ED206413F600BC3599 /* SecureIdDataTypes.swift */, + D0E412F3206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift */, + D054648A2073854A002ECC1E /* SecureIdPersonalDetailsValue.swift */, + D0EE7FC020986BF400981319 /* SecureIdInternalPassportValue.swift */, + D0E412F0206B9BB700BEE4A2 /* SecureIdPassportValue.swift */, + D054648D20738626002ECC1E /* SecureIdDriversLicenseValue.swift */, + D054649020738653002ECC1E /* SecureIdIDCardValue.swift */, + D0E41300206B9E6E00BEE4A2 /* SecureIdAddressValue.swift */, + D0546493207386D7002ECC1E /* SecureIdUtilityBillValue.swift */, + D05464962073872C002ECC1E /* SecureIdBankStatementValue.swift */, + D054649920738760002ECC1E /* SecureIdRentalAgreementValue.swift */, + D093D7F420641A4900BC3599 /* SecureIdPhoneValue.swift */, + D093D7F820641AA500BC3599 /* SecureIdEmailValue.swift */, + D0EE7FC320986C5300981319 /* SecureIdPassportRegistrationValue.swift */, + D0EE7FC62098853100981319 /* SecureIdTemporaryRegistrationValue.swift */, + ); + name = Values; + sourceTree = ""; + }; + D09D8BF71D4FAB1D0081DBEC = { + isa = PBXGroup; + children = ( + A7D281ED236C327B0000A9BF /* thrid-party */, + D03B0E591D63215200955575 /* TelegramCore.xcconfig */, + D09D8C031D4FAB1D0081DBEC /* Sources */, + D0B418681D7E03D5004562A4 /* TelegramCore */, + D09D8C021D4FAB1D0081DBEC /* Products */, + D06706631D512ADA00DED3E3 /* Frameworks */, + ); + sourceTree = ""; + }; + D09D8C021D4FAB1D0081DBEC /* Products */ = { + isa = PBXGroup; + children = ( + D0B418671D7E03D5004562A4 /* TelegramCore.framework */, + ); + name = Products; + sourceTree = ""; + }; + D09D8C031D4FAB1D0081DBEC /* Sources */ = { + isa = PBXGroup; + children = ( + D01B264723324CE600A6448B /* Wallet */, + D04D21352306EC8700609388 /* Mac Internal Updater */, + D03B0CB71D62232000955575 /* Utils */, + D03B0CCF1D62242200955575 /* Objects */, + D03B0CFE1D62252200955575 /* State */, + D03B0D111D62256B00955575 /* Media */, + D03B0D531D631A4400955575 /* Network */, + D03B0D601D631A7200955575 /* Account */, + D03B0D691D631A9200955575 /* Contacts */, + D03B0D6E1D631AA900955575 /* Messages */, + D0DF0C881D819C5F008AEB01 /* Peers */, + D0754D281EEE10D800884F6E /* Bot Payments */, + D0BE303820619E9E00FBE6D8 /* Secure ID */, + D0C50E2F1E93A83B00F62E39 /* Calls */, + D021E0E01DB5400200C6B04F /* Sticker Management */, + D05A32DF1E6F096B002760B4 /* Settings */, + D08CAA8A1ED81EA10000FDA8 /* Localization */, + D0AD02E61FFFA15C00C1DCFF /* Live Location */, + D0AF32331FAE8C540097362B /* Multipeer */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/TelegramCore/Sources"; + sourceTree = ""; + }; + D0AD02E61FFFA15C00C1DCFF /* Live Location */ = { + isa = PBXGroup; + children = ( + D0AD02E21FFFA14800C1DCFF /* PeerLiveLocationsContext.swift */, + ); + name = "Live Location"; + sourceTree = ""; + }; + D0AF32331FAE8C540097362B /* Multipeer */ = { + isa = PBXGroup; + children = ( + D0AF32341FAE8C6B0097362B /* MultipeerManager.swift */, + ); + name = Multipeer; + sourceTree = ""; + }; + D0B418681D7E03D5004562A4 /* TelegramCore */ = { + isa = PBXGroup; + children = ( + D0B418691D7E03D5004562A4 /* TelegramCore.h */, + D0B4186A1D7E03D5004562A4 /* Info.plist */, + ); + path = TelegramCore; + sourceTree = ""; + }; + D0BE303820619E9E00FBE6D8 /* Secure ID */ = { + isa = PBXGroup; + children = ( + D0CA3F82207391450042D2B6 /* Utils */, + D0BE303C2061A29100FBE6D8 /* RequestSecureIdForm.swift */, + D0ADF910212B00DD00310BBC /* SecureIdConfiguration.swift */, + D0BE304A20627D9800FBE6D8 /* AccessSecureId.swift */, + D0BE303920619EE800FBE6D8 /* SecureIdForm.swift */, + D093D805206539D000BC3599 /* SaveSecureIdValue.swift */, + D0E412D6206A866B00BEE4A2 /* UploadSecureIdFile.swift */, + D0E412ED206AF65500BEE4A2 /* GrantSecureIdAccess.swift */, + D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */, + D0136308208F3B0900EB3653 /* SecureIdValueContentError.swift */, + D093D7E82064135300BC3599 /* Values */, + ); + name = "Secure ID"; + sourceTree = ""; + }; + D0C50E2F1E93A83B00F62E39 /* Calls */ = { + isa = PBXGroup; + children = ( + D0C50E331E93A86600F62E39 /* CallSessionManager.swift */, + C2F4ED1C1EC60064005F2696 /* RateCall.swift */, + ); + name = Calls; + sourceTree = ""; + }; + D0CA3F82207391450042D2B6 /* Utils */ = { + isa = PBXGroup; + children = ( + D0CA3F83207391560042D2B6 /* SecureIdPadding.swift */, + ); + name = Utils; + sourceTree = ""; + }; + D0DF0C881D819C5F008AEB01 /* Peers */ = { + isa = PBXGroup; + children = ( + D0A2764A249C9989005E3C77 /* PeerStatistics.swift */, + D0830FC1241254FD006198E7 /* BankCards.swift */, + A767DD4623F2FBCF00366F76 /* ChatListFiltering.swift */, + D0C26D6B1FE286C3004ABF18 /* FetchChatList.swift */, + D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */, + D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */, + D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */, + D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */, + D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */, + D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */, + D07827BA1E00451F00071108 /* SearchPeers.swift */, + D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */, + D018D3361E648ACF00C5E089 /* ChannelCreation.swift */, + D00DBBD61E64E41100DB5485 /* CreateSecretChat.swift */, + D0BC38741E40A7F70044D6FE /* RemovePeerChat.swift */, + D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */, + D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */, + D050F2501E4A59C200988324 /* JoinLink.swift */, + C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */, + C2366C851E4F403C0097CCFF /* AddressNames.swift */, + D0613FC91E60440600202CDB /* InvitationLinks.swift */, + C2366C881E4F40480097CCFF /* SupportPeerId.swift */, + D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */, + D041E3F41E535464008C24B4 /* AddPeerMember.swift */, + D041E3F71E535A88008C24B4 /* RemovePeerMember.swift */, + D0561DE21E5737FC00E6B9E9 /* UpdatePeerInfo.swift */, + D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */, + D0613FCE1E60520700202CDB /* ChannelMembers.swift */, + D0E305A91E5BA02D00D7A3A2 /* ChannelBlacklist.swift */, + D0E305A61E5B5CBE00D7A3A2 /* PeerAdmins.swift */, + D0613FD61E606B3B00202CDB /* ConvertGroupToSupergroup.swift */, + D033FEB21E61F3C000644997 /* ReportPeer.swift */, + D033FEB51E61F3F900644997 /* BlockedPeers.swift */, + C239BE9B1E630CA700C2C453 /* UpdatePinnedMessage.swift */, + D0528E641E65C82400E2FEF5 /* UpdateContactName.swift */, + C22EE61A1E67418000334C38 /* ToggleChannelSignatures.swift */, + C2FD33E01E680E9E008D13D4 /* RequestUserPhotos.swift */, + C2FD33E31E687BF1008D13D4 /* PeerPhotoUpdater.swift */, + C2FD33EA1E696C78008D13D4 /* GroupsInCommon.swift */, + D0C48F3B1E8142EF0075317D /* LoadedPeerFromMessage.swift */, + D0F3A8A71E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift */, + C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */, + C205FEA71EB3B75900455808 /* ExportMessageLink.swift */, + C230BEB51EE9A3760029586C /* ChannelAdminEventLogs.swift */, + D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */, + D0A472B51F4CBE8B00E0EEDA /* LoadedPeer.swift */, + D0DA1D311F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift */, + D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */, + D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */, + D018EE042045E95000CBB130 /* CheckPeerChatServiceActions.swift */, + 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */, + D0F760D722202FE20074F7E5 /* ChannelStats.swift */, + D0633CD12253A528003DD95F /* ChatOnlineMembers.swift */, + D015E00D225CA61100CB9E8A /* FindChannelById.swift */, + D076F8882296D8E9004F895A /* ManageChannelDiscussionGroup.swift */, + 090E778222A9862100CD99F5 /* ChannelOwnershipTransfer.swift */, + 090E778F22AAABC600CD99F5 /* PeersNearby.swift */, + D0D376E522DCCFD600FA7D7C /* SlowMode.swift */, + D03E416B2304D5B30049C28B /* ValidateAddressNameInteractive.swift */, + A754972723A3A8C80091F293 /* InactiveChannels.swift */, + ); + name = Peers; + sourceTree = ""; + }; + D0FA8B961E1E952D001E855B /* Secret Chats */ = { + isa = PBXGroup; + children = ( + D0FA8BA31E1FA341001E855B /* SecretChatKeychain.swift */, + D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */, + D0FA8BAF1E1FEC7E001E855B /* SecretChatEncryptionConfig.swift */, + D0448C901E251F96005A61A7 /* SecretChatEncryption.swift */, + D0FA8B9D1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift */, + D0FA8BB81E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift */, + D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */, + D0FA8BA01E1F99E1001E855B /* SecretChatFileReference.swift */, + D0FA8BB21E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift */, + D0448C8D1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift */, + D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */, + D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */, + D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */, + D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */, + ); + name = "Secret Chats"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D0B418641D7E03D5004562A4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D0B4186B1D7E03D5004562A4 /* TelegramCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D0B418661D7E03D5004562A4 /* TelegramCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0B4186C1D7E03D5004562A4 /* Build configuration list for PBXNativeTarget "TelegramCore" */; + buildPhases = ( + D0B418621D7E03D5004562A4 /* Sources */, + D0B418631D7E03D5004562A4 /* Frameworks */, + D0B418641D7E03D5004562A4 /* Headers */, + D0B418651D7E03D5004562A4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TelegramCore; + productName = TelegramCoreMac; + productReference = D0B418671D7E03D5004562A4 /* TelegramCore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D09D8BF81D4FAB1D0081DBEC /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = Peter; + TargetAttributes = { + D0B418661D7E03D5004562A4 = { + CreatedOnToolsVersion = 8.0; + LastSwiftMigration = 1030; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = D09D8BFB1D4FAB1D0081DBEC /* Build configuration list for PBXProject "TelegramCore_Xcode" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = D09D8BF71D4FAB1D0081DBEC; + productRefGroup = D09D8C021D4FAB1D0081DBEC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0B418661D7E03D5004562A4 /* TelegramCore */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0B418651D7E03D5004562A4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0B418621D7E03D5004562A4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7919136240D08A6002011CA /* TogglePeerChatPinned.swift in Sources */, + D03413F3231325B300B555F3 /* Themes.swift in Sources */, + D03413F1231323CE00B555F3 /* Theme.swift in Sources */, + D021E7E92306EC03002F8BD1 /* ScheduledMessages.swift in Sources */, + D021E7EA2306EC03002F8BD1 /* ValidateAddressNameInteractive.swift in Sources */, + D020F00722F19C8F00BE699A /* ManagedAnimatedEmojiUpdates.swift in Sources */, + D05FDC3922CA45070060BFE3 /* AppUpdate.swift in Sources */, + D014193922AE6B85008667CB /* ChannelOwnershipTransfer.swift in Sources */, + D014193A22AE6B85008667CB /* PeersNearby.swift in Sources */, + D076F88A2296D8F6004F895A /* ManageChannelDiscussionGroup.swift in Sources */, + 9F1BC1AB2244CFED00F21815 /* EmojiKeywords.swift in Sources */, + 9F1BC1AC2244CFED00F21815 /* SynchronizeEmojiKeywordsOperation.swift in Sources */, + 9F1BC1AD2244CFED00F21815 /* ManagedSynchronizeEmojiKeywordsOperations.swift in Sources */, + 9F7D42262223FF49007B68BB /* AutodownloadSettings.swift in Sources */, + 9F7D42272223FF49007B68BB /* ManagedAutodownloadSettingsUpdates.swift in Sources */, + 9F153D1021E8E0A200B95D82 /* Wallpaper.swift in Sources */, + 9F4EEF9E21DCF6E7002C3B33 /* ManagedVoipConfigurationUpdates.swift in Sources */, + 9F4EEF9F21DCF6E7002C3B33 /* ManagedAppConfigurationUpdates.swift in Sources */, + 9F4EEFA021DCF6E7002C3B33 /* SynchronizeAppLogEventsOperation.swift in Sources */, + 9F4EEFA121DCF6E7002C3B33 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */, + D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */, + 9F4EEF9B21DCF66F002C3B33 /* JSON.swift in Sources */, + 9F4EEF9C21DCF66F002C3B33 /* AppConfiguration.swift in Sources */, + 9FAA268820D457A300D26CF3 /* RemoteStorageConfiguration.swift in Sources */, + C28D3CF120D3DAA30027F4D6 /* DeepLinkInfo.swift in Sources */, + 9FC8ADAC206BC00A0094F7B4 /* RecentWebSessions.swift in Sources */, + 9FC8ADA9206BBD000094F7B4 /* SaveSecureIdValue.swift in Sources */, + 9F10CE8C20613CDB002DD61A /* TelegramDeviceContactImportInfo.swift in Sources */, + C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */, + C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */, + D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */, + D0830FC2241254FD006198E7 /* BankCards.swift in Sources */, + C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */, + C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */, + C239BE981E62F0D200C2C453 /* LoadMessagesIfNecessary.swift in Sources */, + D01C7ED41EF5DF83008305F1 /* LimitsConfiguration.swift in Sources */, + D051DB15215EC5A300F30F92 /* AppChangelogState.swift in Sources */, + C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */, + D00D343D1E6EC9770057B307 /* TelegramMediaGame.swift in Sources */, + D0AB262721C2F991008F6685 /* TelegramMediaPoll.swift in Sources */, + D0C26D6A1FE02402004ABF18 /* ManagedSynchronizeGroupedPeersOperations.swift in Sources */, + D01C7F051EFC1C49008305F1 /* DeviceContact.swift in Sources */, + D050F26A1E4A5B6D00988324 /* ManagedGlobalNotificationSettings.swift in Sources */, + D050F26B1E4A5B6D00988324 /* ApplyMaxReadIndexInteractively.swift in Sources */, + D033FEB11E61EB0200644997 /* PeerContactSettings.swift in Sources */, + D050F26C1E4A5B6D00988324 /* UpdatePeers.swift in Sources */, + D08984F621187ECA00918162 /* NetworkType.swift in Sources */, + D050F26D1E4A5B6D00988324 /* CreateGroup.swift in Sources */, + D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */, + D00D34461E6EDD420057B307 /* SynchronizeConsumeMessageContentsOperation.swift in Sources */, + D0E412E8206ABC7500BEE4A2 /* EncryptedMediaResource.swift in Sources */, + D050F26E1E4A5B6D00988324 /* RemovePeerChat.swift in Sources */, + D0613FD81E606B3B00202CDB /* ConvertGroupToSupergroup.swift in Sources */, + D0DB7F041F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift in Sources */, + D01D6BFA1E42A718006151C6 /* SearchStickers.swift in Sources */, + D0D748031E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift in Sources */, + D0B167241F9F972E00976B40 /* LoggingSettings.swift in Sources */, + D0E41302206B9E6E00BEE4A2 /* SecureIdAddressValue.swift in Sources */, + C2A315C01E2E776A00D89000 /* RequestStartBot.swift in Sources */, + D0642EFA1F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */, + D0F7B1EA1E045C87007EB8A5 /* ChangePeerNotificationSettings.swift in Sources */, + D08984F32114B97400918162 /* ClearCloudDrafts.swift in Sources */, + D0B418A71D7E0592004562A4 /* Fetch.swift in Sources */, + D02ABC7C1E30058F00CAE539 /* DeleteMessagesInteractively.swift in Sources */, + D03DC9111F82E344001D584C /* AccountStateReset.swift in Sources */, + D0B1671E1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift in Sources */, + D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */, + D0DEF73F242A41DB00A34A30 /* AccountStateManagementUtils.swift in Sources */, + D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */, + D02D60AC206BA64100FEFE1E /* VerifySecureIdValue.swift in Sources */, + D0119CB120CA9EA800895300 /* MarkAllChatsAsRead.swift in Sources */, + D050F2521E4A59C200988324 /* JoinLink.swift in Sources */, + D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */, + D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */, + D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */, + D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */, + D0E412EF206AF65500BEE4A2 /* GrantSecureIdAccess.swift in Sources */, + C22EE61C1E67418000334C38 /* ToggleChannelSignatures.swift in Sources */, + D0B418AC1D7E0597004562A4 /* Network.swift in Sources */, + D04CAA5B1E83310D0047E51F /* MD5.swift in Sources */, + D0E305AB1E5BA02D00D7A3A2 /* ChannelBlacklist.swift in Sources */, + D00D97CB1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */, + D0B844491DAB91FD005F29E1 /* ManagedChatListHoles.swift in Sources */, + D03C53711DAD5CA9004C17B3 /* CachedGroupParticipants.swift in Sources */, + D07047B81F3DF2CD00F6A8D4 /* ManagedConsumePersonalMessagesActions.swift in Sources */, + C2366C841E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */, + C2E0646E1ECF171E00387BB8 /* TelegramMediaWebDocument.swift in Sources */, + D03C53671DAD5CA9004C17B3 /* ApiUtils.swift in Sources */, + D0AAD1B91E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift in Sources */, + D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */, + D0338744223BD532007A2CE4 /* InitializeAccountAfterLogin.swift in Sources */, + D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */, + D018D3381E648ACF00C5E089 /* ChannelCreation.swift in Sources */, + D0F53BEA1E784A4800117362 /* ChangeAccountPhoneNumber.swift in Sources */, + D001F3EE1E128A1C007A8C60 /* AccountStateManager.swift in Sources */, + C205FEA91EB3B75900455808 /* ExportMessageLink.swift in Sources */, + D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */, + D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */, + A71DC83D2387CC4C000EEDE2 /* UnauthorizedAccountStateManager.swift in Sources */, + D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */, + D0FA8B9F1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, + D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */, + D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */, + D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */, + C230BEB71EE9A3760029586C /* ChannelAdminEventLogs.swift in Sources */, + D00DBBDB1E64E67E00DB5485 /* UpdateSecretChat.swift in Sources */, + D0BE303E2061A29100FBE6D8 /* RequestSecureIdForm.swift in Sources */, + D0448C921E251F96005A61A7 /* SecretChatEncryption.swift in Sources */, + D00BDA1D1EE5952A00C64C5E /* TelegramChannelBannedRights.swift in Sources */, + D0E35A141DE4C69C00BC6096 /* FetchHttpResource.swift in Sources */, + D0B8440D1DAB91CD005F29E1 /* ImageRepresentationsUtils.swift in Sources */, + D03C536A1DAD5CA9004C17B3 /* TelegramUser.swift in Sources */, + D0D3CE4023BB3F1100864F3C /* PendingUpdateMessageManager.swift in Sources */, + D001F3EA1E128A1C007A8C60 /* TelegramPeerNotificationSettings.swift in Sources */, + D0E412F5206B9BDC00BEE4A2 /* SecureIdVerificationDocumentReference.swift in Sources */, + A754972823A3A8C80091F293 /* InactiveChannels.swift in Sources */, + D0467D0C20D7F1E60055C28F /* SynchronizeMarkAllUnseenPersonalMessagesOperation.swift in Sources */, + C23BC3881E9BE3CB00D79F92 /* ImportContact.swift in Sources */, + D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */, + D0E412F2206B9BB700BEE4A2 /* SecureIdPassportValue.swift in Sources */, + D001F3EB1E128A1C007A8C60 /* EnqueueMessage.swift in Sources */, + D01A21A71F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */, + D00C7CEC1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift in Sources */, + D048B4AD20A5DA4300C79D31 /* ManagedProxyInfoUpdates.swift in Sources */, + D0B844481DAB91FD005F29E1 /* ManagedMessageHistoryHoles.swift in Sources */, + D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */, + D049EAEC1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift in Sources */, + D0F19F6720E6621000EEC860 /* MultiplexedRequestManager.swift in Sources */, + D0879BC922F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift in Sources */, + D02D60A8206BA5F900FEFE1E /* SecureIdValue.swift in Sources */, + D0FA8B991E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */, + D0BEAF611E54ACF900BD963D /* AccountManager.swift in Sources */, + D0CA3F85207391560042D2B6 /* SecureIdPadding.swift in Sources */, + D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */, + D093D7EF206413F600BC3599 /* SecureIdDataTypes.swift in Sources */, + A71DC83F2387CE10000EEDE2 /* AuthTransfer.swift in Sources */, + D06CA13622772EB20094E707 /* ManagedNotificationSettingsBehaviors.swift in Sources */, + D0C44B621FC616E200227BE0 /* SearchGroupMembers.swift in Sources */, + D00BDA1A1EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */, + D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, + D026099F20C695AF006C34AC /* Wallpapers.swift in Sources */, + D099E223229420D600561B75 /* BlockedPeersContext.swift in Sources */, + D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */, + D0E412EB206AD18E00BEE4A2 /* DecryptedResourceData.swift in Sources */, + D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */, + D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */, + D0A3E448214802C7008ACEF6 /* VoipConfiguration.swift in Sources */, + D018EE062045E95000CBB130 /* CheckPeerChatServiceActions.swift in Sources */, + D081E10B217F5ADE003CD921 /* LocalizationPreview.swift in Sources */, + D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */, + D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */, + D0C0B58E1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift in Sources */, + D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */, + D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */, + D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */, + D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */, + A767DD4723F2FBD000366F76 /* ChatListFiltering.swift in Sources */, + D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */, + D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */, + D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */, + D00C7CE11E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */, + D0E23DDB1E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift in Sources */, + D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */, + D093D7F620641A4900BC3599 /* SecureIdPhoneValue.swift in Sources */, + D0C26D6D1FE286C3004ABF18 /* FetchChatList.swift in Sources */, + D093D7FA20641AA500BC3599 /* SecureIdEmailValue.swift in Sources */, + D05A32E21E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */, + D0613FD01E60520700202CDB /* ChannelMembers.swift in Sources */, + D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */, + C2366C8A1E4F40480097CCFF /* SupportPeerId.swift in Sources */, + D08CAA811ED80ED20000FDA8 /* SuggestedLocalizationEntry.swift in Sources */, + D0DA1D331F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */, + D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */, + D0C48F3D1E8142EF0075317D /* LoadedPeerFromMessage.swift in Sources */, + D0F3A8A01E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */, + D07E4140208A769D00FCA8F0 /* ProxyServersStatuses.swift in Sources */, + D049EAD91E43DAD200A2CD3A /* ManagedRecentStickers.swift in Sources */, + D0EE7FC220986BF400981319 /* SecureIdInternalPassportValue.swift in Sources */, + D0B418A61D7E0592004562A4 /* CloudFileMediaResource.swift in Sources */, + D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */, + D03C536D1DAD5CA9004C17B3 /* ApiGroupOrChannel.swift in Sources */, + D051DB18215ECC4D00F30F92 /* AppChangelog.swift in Sources */, + D01C06B81FBBA269001561AB /* CanSendMessagesToPeer.swift in Sources */, + A7D281FD236C35E30000A9BF /* TelegramMediaWebFile.swift in Sources */, + D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, + D02ABC821E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift in Sources */, + C2FD33E51E687BF1008D13D4 /* PeerPhotoUpdater.swift in Sources */, + D0BE304C20627D9800FBE6D8 /* AccessSecureId.swift in Sources */, + D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */, + C2F4ED1E1EC60064005F2696 /* RateCall.swift in Sources */, + D0B844121DAB91CD005F29E1 /* Log.swift in Sources */, + D0AF32321FACEDEC0097362B /* CoreSettings.swift in Sources */, + D08F4A6A1E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */, + D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */, + D0529D2521A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift in Sources */, + D06ECFC920B810D300C576C2 /* TermsOfService.swift in Sources */, + 9F06831121A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */, + D0AB262C21C3CE80008F6685 /* Polls.swift in Sources */, + D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */, + D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */, + D05452081E7B5093006EEF19 /* LoadedStickerPack.swift in Sources */, + D0561DE41E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, + D042C6841E8D9DF800C863B0 /* Unixtime.swift in Sources */, + D0DC35521DE36908000195EB /* ChatContextResult.swift in Sources */, + D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, + D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */, + D033FEB71E61F3F900644997 /* BlockedPeers.swift in Sources */, + D0E412D8206A866B00BEE4A2 /* UploadSecureIdFile.swift in Sources */, + D050F2611E4A5AE700988324 /* PrivacySettings.swift in Sources */, + D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */, + D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */, + D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */, + D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */, + D013630A208F6E2800EB3653 /* SecureIdValueContentError.swift in Sources */, + D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */, + D02DADC22139A1FC00116225 /* ContactSyncManager.swift in Sources */, + D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */, + D0223A991EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */, + D00C7CCD1E3620C30080C3D5 /* CachedChannelParticipants.swift in Sources */, + D0A472B71F4CBE8B00E0EEDA /* LoadedPeer.swift in Sources */, + D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */, + D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */, + D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */, + D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */, + D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */, + D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */, + D03C53701DAD5CA9004C17B3 /* ExportedInvitation.swift in Sources */, + D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */, + D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */, + D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */, + D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */, + D072F358231542740009E66F /* MessageReactionList.swift in Sources */, + D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */, + D0B418AA1D7E0597004562A4 /* Download.swift in Sources */, + D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */, + D0D376E722DCCFD600FA7D7C /* SlowMode.swift in Sources */, + D015E00F225CA61100CB9E8A /* FindChannelById.swift in Sources */, + D04D8FF5209A4B0700865719 /* NetworkSettings.swift in Sources */, + D05464982073872C002ECC1E /* SecureIdBankStatementValue.swift in Sources */, + D0CB09AE24ADEFC9008CD050 /* Suggestions.swift in Sources */, + D0B4188E1D7E0578004562A4 /* StoreMessage_Telegram.swift in Sources */, + D0B844461DAB91FD005F29E1 /* RecentPeers.swift in Sources */, + D0E412E2206AB24700BEE4A2 /* SecureFileMediaResource.swift in Sources */, + D0439B5E228ECB270067E026 /* RequestPhoneNumber.swift in Sources */, + D03C53681DAD5CA9004C17B3 /* PeerUtils.swift in Sources */, + D0BE303B20619EE800FBE6D8 /* SecureIdForm.swift in Sources */, + D0380DBB204EF306000414AB /* MessageMediaPreuploadManager.swift in Sources */, + D0A2764B249C9989005E3C77 /* PeerStatistics.swift in Sources */, + D050F2621E4A5AE700988324 /* GlobalNotificationSettings.swift in Sources */, + D05D8B382192F8AF0064586F /* LocalizationListState.swift in Sources */, + D0DFD5E01FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift in Sources */, + D0B418991D7E0580004562A4 /* TelegramMediaMap.swift in Sources */, + D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */, + D054649B20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */, + D0561DEB1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */, + D0AD02E41FFFA14800C1DCFF /* PeerLiveLocationsContext.swift in Sources */, + D0613FCB1E60440600202CDB /* InvitationLinks.swift in Sources */, + D0633CD32253A528003DD95F /* ChatOnlineMembers.swift in Sources */, + D0B844471DAB91FD005F29E1 /* ManagedServiceViews.swift in Sources */, + D0F3A8A91E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */, + D03C53691DAD5CA9004C17B3 /* PeerAccessRestrictionInfo.swift in Sources */, + D0D3CE4423BB3FD200864F3C /* ChatUpdatingMessageMedia.swift in Sources */, + C2FD33E21E680E9E008D13D4 /* RequestUserPhotos.swift in Sources */, + A7D281FB236C34670000A9BF /* SynchronizeInstalledStickerPacksOperation.swift in Sources */, + D0B8440E1DAB91CD005F29E1 /* MessageUtils.swift in Sources */, + D0FA8BAB1E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift in Sources */, + D0C26D671FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */, + D03C536B1DAD5CA9004C17B3 /* TelegramGroup.swift in Sources */, + D0E412DD206A99AE00BEE4A2 /* SecureIdValueAccessContext.swift in Sources */, + D0B418941D7E0580004562A4 /* TelegramMediaAction.swift in Sources */, + D0EC559B2101ED0800D1992C /* DeleteMessages.swift in Sources */, + D0B418AB1D7E0597004562A4 /* MultipartFetch.swift in Sources */, + D01A21AA1F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift in Sources */, + D0546495207386D7002ECC1E /* SecureIdUtilityBillValue.swift in Sources */, + D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */, + D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */, + D05A32E51E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */, + D02395D71F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift in Sources */, + D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */, + D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */, + D0ADF912212B00DD00310BBC /* SecureIdConfiguration.swift in Sources */, + D0A89990217A37A000759EE6 /* NotificationAutolockReportManager.swift in Sources */, + D054648C2073854A002ECC1E /* SecureIdPersonalDetailsValue.swift in Sources */, + D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */, + D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */, + D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */, + A7F2833D239E4EE300742C20 /* ContentSettings.swift in Sources */, + D033FEB41E61F3C000644997 /* ReportPeer.swift in Sources */, + D0FA8BAE1E1FD6E2001E855B /* MemoryBufferExtensions.swift in Sources */, + D01B264A23324CF900A6448B /* Wallets.swift in Sources */, + D0FA8BB41E201B02001E855B /* ProcessSecretChatIncomingEncryptedOperations.swift in Sources */, + D0F3A8A31E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift in Sources */, + D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */, + D0448CA61E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, + D0633CDC2253C0D3003DD95F /* CloudMediaResourceParameters.swift in Sources */, + D001F3F11E128A1C007A8C60 /* SynchronizePeerReadState.swift in Sources */, + D054648F20738626002ECC1E /* SecureIdDriversLicenseValue.swift in Sources */, + D0529D2821A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift in Sources */, + D050F2641E4A5AEB00988324 /* ManagedSynchronizePinnedChatsOperations.swift in Sources */, + D04D21372306EC9A00609388 /* MacInternalUpdater.swift in Sources */, + D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */, + D0528E661E65C82400E2FEF5 /* UpdateContactName.swift in Sources */, + D0EE7FC82098853100981319 /* SecureIdTemporaryRegistrationValue.swift in Sources */, + D00422D421677F4500719B67 /* ManagedAccountPresence.swift in Sources */, + D023E67921540624008C27D1 /* UpdateMessageMedia.swift in Sources */, + D0F7B1E81E045C87007EB8A5 /* PeerParticipants.swift in Sources */, + D0EE7FC520986C5300981319 /* SecureIdPassportRegistrationValue.swift in Sources */, + D0C0B58B1ED9DA6B000F4D2C /* ManagedLocalizationUpdatesOperations.swift in Sources */, + D001F3F51E128A1C007A8C60 /* PendingMessageManager.swift in Sources */, + D0C27B401F4B51D000A4E170 /* CachedStickerPack.swift in Sources */, + D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */, + D01C7ED71EF5E468008305F1 /* ProxySettings.swift in Sources */, + C2366C871E4F403C0097CCFF /* AddressNames.swift in Sources */, + D02ABC7F1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */, + D0528E611E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */, + D0CA8E4C227209C4008A74C3 /* ManagedSynchronizeGroupMessageStats.swift in Sources */, + D0528E5B1E658B3600E2FEF5 /* ManagedLocalInputActivities.swift in Sources */, + D0F7B1E71E045C87007EB8A5 /* JoinChannel.swift in Sources */, + D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */, + D0FA8BA21E1F99E1001E855B /* SecretChatFileReference.swift in Sources */, + D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */, + D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, + D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */, + D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */, + D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */, + D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */, + D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */, + D0E305A81E5B5CBE00D7A3A2 /* PeerAdmins.swift in Sources */, + D0B844431DAB91FD005F29E1 /* Account.swift in Sources */, + D054649220738653002ECC1E /* SecureIdIDCardValue.swift in Sources */, + D0448CA01E27F5EB005A61A7 /* Random.swift in Sources */, + C251D7441E65E50500283EDE /* StickerSetInstallation.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282E1238EAB5B00742C20 /* Github */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282E2238EAB5B00742C20 /* Github */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; + C22069BE1E8EB4A200E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + C22069C11E8EB4A200E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + D0364D4D22B3E37C002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0364D5022B3E37C002A6EF0 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + D06706551D51162400DED3E3 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D09D8C131D4FAB1D0081DBEC /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D09D8C141D4FAB1D0081DBEC /* DebugAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + 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 = gnu99; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0B4186D1D7E03D5004562A4 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + D0B4186E1D7E03D5004562A4 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + OTHER_SWIFT_FLAGS = "-DDEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + D0B4186F1D7E03D5004562A4 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D03B0E591D63215200955575 /* TelegramCore.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TelegramCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_PRIVATE_FILE = "$(SRCROOT)/../../submodules/telegram-ios/submodules/TelegramCore/Sources/module.private.modulemap"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramCore; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D09D8BFB1D4FAB1D0081DBEC /* Build configuration list for PBXProject "TelegramCore_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D09D8C131D4FAB1D0081DBEC /* DebugHockeyapp */, + D0364D4D22B3E37C002A6EF0 /* HockeyappMacAlpha */, + D09D8C141D4FAB1D0081DBEC /* DebugAppStore */, + A7F282E1238EAB5B00742C20 /* Github */, + C22069BE1E8EB4A200E82730 /* ReleaseHockeyapp */, + D06706551D51162400DED3E3 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugAppStore; + }; + D0B4186C1D7E03D5004562A4 /* Build configuration list for PBXNativeTarget "TelegramCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0B4186D1D7E03D5004562A4 /* DebugHockeyapp */, + D0364D5022B3E37C002A6EF0 /* HockeyappMacAlpha */, + D0B4186E1D7E03D5004562A4 /* DebugAppStore */, + A7F282E2238EAB5B00742C20 /* Github */, + C22069C11E8EB4A200E82730 /* ReleaseHockeyapp */, + D0B4186F1D7E03D5004562A4 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugAppStore; + }; +/* End XCConfigurationList section */ + }; + rootObject = D09D8BF81D4FAB1D0081DBEC /* Project object */; +} diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..48eb199cdd --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..f648f7bf5d Binary files /dev/null and b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCore.xcscheme b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCore.xcscheme new file mode 100644 index 0000000000..7c9bc46b78 --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCore.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCoreMac.xcscheme b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCoreMac.xcscheme new file mode 100644 index 0000000000..b655b1740f --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/TelegramCoreMac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..249f7c30c4 --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + TelegramCore.xcscheme_^#shared#^_ + + orderHint + 23 + + TelegramCoreMac.xcscheme_^#shared#^_ + + orderHint + 36 + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramCore.xcscheme b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramCore.xcscheme new file mode 100644 index 0000000000..b655b1740f --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TelegramCore.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..fd584fd197 --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,16 @@ + + + + + SchemeUserState + + TelegramCore.xcscheme + + isShown + + orderHint + 8 + + + + diff --git a/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..c051690e6f --- /dev/null +++ b/core-xprojects/TelegramCore/TelegramCore_Xcode.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + TelegramCore.xcscheme_^#shared#^_ + + orderHint + 35 + + TelegramCoreMac.xcscheme_^#shared#^_ + + orderHint + 42 + + + + diff --git a/core-xprojects/TonBinding/TonBinding/Info.plist b/core-xprojects/TonBinding/TonBinding/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/TonBinding/TonBinding/TonBinding.h b/core-xprojects/TonBinding/TonBinding/TonBinding.h new file mode 100644 index 0000000000..f29f81feb1 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding/TonBinding.h @@ -0,0 +1,20 @@ +// +// TonBinding.h +// TonBinding +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for TonBindings. +FOUNDATION_EXPORT double TonBindingVersionNumber; + +//! Project version string for TonBindings. +FOUNDATION_EXPORT const unsigned char TonBindingVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/core-xprojects/TonBinding/TonBinding/TonBinding.xcconfig b/core-xprojects/TonBinding/TonBinding/TonBinding.xcconfig new file mode 100644 index 0000000000..a8de815b49 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding/TonBinding.xcconfig @@ -0,0 +1,10 @@ +// +// TonBinding.xcconfig +// TonBinding +// +// Created by Mikhail Filimonov on 06.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 diff --git a/core-xprojects/TonBinding/TonBinding/build.sh b/core-xprojects/TonBinding/TonBinding/build.sh new file mode 100644 index 0000000000..059d7d39a0 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding/build.sh @@ -0,0 +1,63 @@ +#/bin/sh + +set -x +set -e + +OUT_DIR="$1" +SOURCE_DIR="$2" +openssl_base_path="$3" +if [ -z "$openssl_base_path" ]; then +echo "Usage: sh build.sh path/to/openssl" +exit 1 +fi + +if [ ! -d "$openssl_base_path" ]; then +echo "$openssl_base_path not found" +exit 1 +fi + +if [ -d "$OUT_DIR/build/out" ] +then +exit 0 +fi + +td_path="$SOURCE_DIR" + +mkdir -p "$OUT_DIR" +mkdir -p "$OUT_DIR/build" +cd "$OUT_DIR/build" + +platforms="macOS" +for platform in $platforms; do +install="install-${platform}" +build="build-${platform}" +openssl_path="$openssl_base_path" +echo "OpenSSL path = ${openssl_path}" +openssl_crypto_library="${openssl_path}/lib/libcrypto.a" +options="$options -DOPENSSL_FOUND=1" +options="$options -DOPENSSL_CRYPTO_LIBRARY=${openssl_crypto_library}" +options="$options -DOPENSSL_INCLUDE_DIR=${openssl_path}/include" +options="$options -DOPENSSL_LIBRARIES=${openssl_crypto_library}" +options="$options -DTON_ONLY_TONLIB=ON" +options="$options -DCMAKE_BUILD_TYPE=Release" +options="$options -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11" +options="$options -DTON_ARCH=" +rm -rf $build +mkdir -p $build +mkdir -p $install +cd $build +cmake $td_path $options -DCMAKE_INSTALL_PREFIX=../${install} ${SOURCE_DIR} -GNinja +ninja install || exit +cd .. +done +mkdir -p $platform + +mkdir -p "out" +cp -r "install-macOS/include" "out/" +mkdir -p "out/lib" + +for f in install-macOS/lib/*.a; do +lib_name=$(basename "$f") +lipo -create "install-macOS/lib/$lib_name" -o "out/lib/$lib_name" +done + diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.pbxproj b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..7ce46e65bf --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,860 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A790A3A3236B1E36000451B5 /* TonBinding.h in Headers */ = {isa = PBXBuildFile; fileRef = A790A3A1236B1E36000451B5 /* TonBinding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A790A405236B1FC1000451B5 /* TON.mm in Sources */ = {isa = PBXBuildFile; fileRef = A790A403236B1FC1000451B5 /* TON.mm */; }; + A790A406236B1FC1000451B5 /* TON.h in Headers */ = {isa = PBXBuildFile; fileRef = A790A404236B1FC1000451B5 /* TON.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7DF1B452372EDEE00ACC01F /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7DF1B442372EDEE00ACC01F /* SSignalKit.framework */; }; + A7DF1B4A23730B3100ACC01F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2821F236C50700000A9BF /* libz.tbd */; }; + A7DF1B4B23730B6A00ACC01F /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A407236B2013000451B5 /* libc++.tbd */; }; + A7DF1B4C23730CE500ACC01F /* libadnllite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E4236B1F6A000451B5 /* libadnllite.a */; }; + A7DF1B4D23730CE500ACC01F /* libcrc32c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3EC236B1F6B000451B5 /* libcrc32c.a */; }; + A7DF1B4E23730CE500ACC01F /* libkeys.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E8236B1F6A000451B5 /* libkeys.a */; }; + A7DF1B4F23730CE500ACC01F /* liblite-client-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E3236B1F6A000451B5 /* liblite-client-common.a */; }; + A7DF1B5023730CE500ACC01F /* libsmc-envelope.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E2236B1F6A000451B5 /* libsmc-envelope.a */; }; + A7DF1B5123730CE500ACC01F /* libtdactor.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3EF236B1F6B000451B5 /* libtdactor.a */; }; + A7DF1B5223730CE500ACC01F /* libtddb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E9236B1F6B000451B5 /* libtddb.a */; }; + A7DF1B5323730CE500ACC01F /* libtdnet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3F0236B1F6B000451B5 /* libtdnet.a */; }; + A7DF1B5423730CE500ACC01F /* libtdutils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E1236B1F6A000451B5 /* libtdutils.a */; }; + A7DF1B5523730CE500ACC01F /* libtl_api.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E5236B1F6A000451B5 /* libtl_api.a */; }; + A7DF1B5623730CE500ACC01F /* libtl_lite_api.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E7236B1F6A000451B5 /* libtl_lite_api.a */; }; + A7DF1B5723730CE500ACC01F /* libtl_tonlib_api.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E0236B1F6A000451B5 /* libtl_tonlib_api.a */; }; + A7DF1B5823730CE500ACC01F /* libtl-lite-utils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3ED236B1F6B000451B5 /* libtl-lite-utils.a */; }; + A7DF1B5923730CE500ACC01F /* libtl-utils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3E6236B1F6A000451B5 /* libtl-utils.a */; }; + A7DF1B5A23730CE500ACC01F /* libton_block.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3EA236B1F6B000451B5 /* libton_block.a */; }; + A7DF1B5B23730CE500ACC01F /* libton_crypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3EB236B1F6B000451B5 /* libton_crypto.a */; }; + A7DF1B5C23730CE500ACC01F /* libtonlib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A790A3EE236B1F6B000451B5 /* libtonlib.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A790A39E236B1E36000451B5 /* TonBinding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TonBinding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3A1236B1E36000451B5 /* TonBinding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TonBinding.h; sourceTree = ""; }; + A790A3A2236B1E36000451B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A790A3E0236B1F6A000451B5 /* libtl_tonlib_api.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtl_tonlib_api.a; path = "ton-lib/build/out/lib/libtl_tonlib_api.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E1236B1F6A000451B5 /* libtdutils.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtdutils.a; path = "ton-lib/build/out/lib/libtdutils.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E2236B1F6A000451B5 /* libsmc-envelope.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libsmc-envelope.a"; path = "ton-lib/build/out/lib/libsmc-envelope.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E3236B1F6A000451B5 /* liblite-client-common.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "liblite-client-common.a"; path = "ton-lib/build/out/lib/liblite-client-common.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E4236B1F6A000451B5 /* libadnllite.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libadnllite.a; path = "ton-lib/build/out/lib/libadnllite.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E5236B1F6A000451B5 /* libtl_api.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtl_api.a; path = "ton-lib/build/out/lib/libtl_api.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E6236B1F6A000451B5 /* libtl-utils.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libtl-utils.a"; path = "ton-lib/build/out/lib/libtl-utils.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E7236B1F6A000451B5 /* libtl_lite_api.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtl_lite_api.a; path = "ton-lib/build/out/lib/libtl_lite_api.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E8236B1F6A000451B5 /* libkeys.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libkeys.a; path = "ton-lib/build/out/lib/libkeys.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3E9236B1F6B000451B5 /* libtddb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtddb.a; path = "ton-lib/build/out/lib/libtddb.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3EA236B1F6B000451B5 /* libton_block.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libton_block.a; path = "ton-lib/build/out/lib/libton_block.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3EB236B1F6B000451B5 /* libton_crypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libton_crypto.a; path = "ton-lib/build/out/lib/libton_crypto.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3EC236B1F6B000451B5 /* libcrc32c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrc32c.a; path = "ton-lib/build/out/lib/libcrc32c.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3ED236B1F6B000451B5 /* libtl-lite-utils.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libtl-lite-utils.a"; path = "ton-lib/build/out/lib/libtl-lite-utils.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3EE236B1F6B000451B5 /* libtonlib.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtonlib.a; path = "ton-lib/build/out/lib/libtonlib.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3EF236B1F6B000451B5 /* libtdactor.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtdactor.a; path = "ton-lib/build/out/lib/libtdactor.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A790A3F0236B1F6B000451B5 /* libtdnet.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtdnet.a; path = "ton-lib/build/out/lib/libtdnet.a"; sourceTree = ""; }; + A790A403236B1FC1000451B5 /* TON.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TON.mm; sourceTree = ""; }; + A790A404236B1FC1000451B5 /* TON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TON.h; sourceTree = ""; }; + A790A407236B2013000451B5 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + A790A409236B2019000451B5 /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2821F236C50700000A9BF /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + A7DF1B442372EDEE00ACC01F /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TonBinding.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A790A39B236B1E36000451B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7DF1B4B23730B6A00ACC01F /* libc++.tbd in Frameworks */, + A7DF1B4A23730B3100ACC01F /* libz.tbd in Frameworks */, + A7DF1B452372EDEE00ACC01F /* SSignalKit.framework in Frameworks */, + A7DF1B4C23730CE500ACC01F /* libadnllite.a in Frameworks */, + A7DF1B4D23730CE500ACC01F /* libcrc32c.a in Frameworks */, + A7DF1B4E23730CE500ACC01F /* libkeys.a in Frameworks */, + A7DF1B4F23730CE500ACC01F /* liblite-client-common.a in Frameworks */, + A7DF1B5023730CE500ACC01F /* libsmc-envelope.a in Frameworks */, + A7DF1B5123730CE500ACC01F /* libtdactor.a in Frameworks */, + A7DF1B5223730CE500ACC01F /* libtddb.a in Frameworks */, + A7DF1B5323730CE500ACC01F /* libtdnet.a in Frameworks */, + A7DF1B5423730CE500ACC01F /* libtdutils.a in Frameworks */, + A7DF1B5523730CE500ACC01F /* libtl_api.a in Frameworks */, + A7DF1B5623730CE500ACC01F /* libtl_lite_api.a in Frameworks */, + A7DF1B5723730CE500ACC01F /* libtl_tonlib_api.a in Frameworks */, + A7DF1B5823730CE500ACC01F /* libtl-lite-utils.a in Frameworks */, + A7DF1B5923730CE500ACC01F /* libtl-utils.a in Frameworks */, + A7DF1B5A23730CE500ACC01F /* libton_block.a in Frameworks */, + A7DF1B5B23730CE500ACC01F /* libton_crypto.a in Frameworks */, + A7DF1B5C23730CE500ACC01F /* libtonlib.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A790A394236B1E36000451B5 = { + isa = PBXGroup; + children = ( + A790A402236B1FC1000451B5 /* Sources */, + A790A3A0236B1E36000451B5 /* TonBinding */, + A790A39F236B1E36000451B5 /* Products */, + A790A3DF236B1F6A000451B5 /* Frameworks */, + ); + sourceTree = ""; + }; + A790A39F236B1E36000451B5 /* Products */ = { + isa = PBXGroup; + children = ( + A790A39E236B1E36000451B5 /* TonBinding.framework */, + ); + name = Products; + sourceTree = ""; + }; + A790A3A0236B1E36000451B5 /* TonBinding */ = { + isa = PBXGroup; + children = ( + A790A3A1236B1E36000451B5 /* TonBinding.h */, + A790A3A2236B1E36000451B5 /* Info.plist */, + A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */, + ); + path = TonBinding; + sourceTree = ""; + }; + A790A3DF236B1F6A000451B5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7DF1B442372EDEE00ACC01F /* SSignalKit.framework */, + A7D2821F236C50700000A9BF /* libz.tbd */, + A790A409236B2019000451B5 /* SSignalKit.framework */, + A790A407236B2013000451B5 /* libc++.tbd */, + A790A3E4236B1F6A000451B5 /* libadnllite.a */, + A790A3EC236B1F6B000451B5 /* libcrc32c.a */, + A790A3E8236B1F6A000451B5 /* libkeys.a */, + A790A3E3236B1F6A000451B5 /* liblite-client-common.a */, + A790A3E2236B1F6A000451B5 /* libsmc-envelope.a */, + A790A3EF236B1F6B000451B5 /* libtdactor.a */, + A790A3E9236B1F6B000451B5 /* libtddb.a */, + A790A3F0236B1F6B000451B5 /* libtdnet.a */, + A790A3E1236B1F6A000451B5 /* libtdutils.a */, + A790A3E5236B1F6A000451B5 /* libtl_api.a */, + A790A3E7236B1F6A000451B5 /* libtl_lite_api.a */, + A790A3E0236B1F6A000451B5 /* libtl_tonlib_api.a */, + A790A3ED236B1F6B000451B5 /* libtl-lite-utils.a */, + A790A3E6236B1F6A000451B5 /* libtl-utils.a */, + A790A3EA236B1F6B000451B5 /* libton_block.a */, + A790A3EB236B1F6B000451B5 /* libton_crypto.a */, + A790A3EE236B1F6B000451B5 /* libtonlib.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + A790A402236B1FC1000451B5 /* Sources */ = { + isa = PBXGroup; + children = ( + A790A403236B1FC1000451B5 /* TON.mm */, + A790A404236B1FC1000451B5 /* TON.h */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/TonBinding/Sources"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A790A399236B1E36000451B5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A406236B1FC1000451B5 /* TON.h in Headers */, + A790A3A3236B1E36000451B5 /* TonBinding.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A790A39D236B1E36000451B5 /* TonBinding */ = { + isa = PBXNativeTarget; + buildConfigurationList = A790A3A6236B1E36000451B5 /* Build configuration list for PBXNativeTarget "TonBinding" */; + buildPhases = ( + A7DF1B432372D6AF00ACC01F /* Run Script */, + A790A39B236B1E36000451B5 /* Frameworks */, + A790A399236B1E36000451B5 /* Headers */, + A790A39A236B1E36000451B5 /* Sources */, + A790A39C236B1E36000451B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TonBinding; + productName = TonBindings; + productReference = A790A39E236B1E36000451B5 /* TonBinding.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A790A395236B1E36000451B5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A790A39D236B1E36000451B5 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A790A398236B1E36000451B5 /* Build configuration list for PBXProject "TonBinding_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A790A394236B1E36000451B5; + productRefGroup = A790A39F236B1E36000451B5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A790A39D236B1E36000451B5 /* TonBinding */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A790A39C236B1E36000451B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A7DF1B432372D6AF00ACC01F /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nsh ./TonBinding/build.sh ${BUILT_PRODUCTS_DIR}/ton-lib ${PROJECT_DIR}/../../submodules/ton /usr/local/Cellar/openssl@1.1/1.1.1d\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A790A39A236B1E36000451B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A790A405236B1FC1000451B5 /* TON.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A790A3A5236B1E36000451B5 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A790A3A8236B1E36000451B5 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A790A3A9236B1E57000451B5 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A790A3AA236B1E57000451B5 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A790A3AD236B1E63000451B5 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A790A3AE236B1E63000451B5 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A790A3B1236B1E70000451B5 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A790A3B2236B1E70000451B5 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A790A3B5236B1E7E000451B5 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A790A3B6236B1E7E000451B5 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A7F282D4238EAB2C00742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_LIBTOOLFLAGS = ""; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282D5238EAB2C00742C20 /* Github */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7DF1B472372F52A00ACC01F /* TonBinding.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + HEADER_SEARCH_PATHS = "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/include/"; + INFOPLIST_FILE = TonBinding/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "${BUILT_PRODUCTS_DIR}/ton-lib/build/out/lib", + "$(PROJECT_DIR)/ton-lib/build/out/lib", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TonBinding; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A790A398236B1E36000451B5 /* Build configuration list for PBXProject "TonBinding_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A3A5236B1E36000451B5 /* DebugHockeyapp */, + A790A3A9236B1E57000451B5 /* HockeyappMacAlpha */, + A790A3AD236B1E63000451B5 /* DebugAppStore */, + A7F282D4238EAB2C00742C20 /* Github */, + A790A3B1236B1E70000451B5 /* ReleaseHockeyapp */, + A790A3B5236B1E7E000451B5 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + A790A3A6236B1E36000451B5 /* Build configuration list for PBXNativeTarget "TonBinding" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A790A3A8236B1E36000451B5 /* DebugHockeyapp */, + A790A3AA236B1E57000451B5 /* HockeyappMacAlpha */, + A790A3AE236B1E63000451B5 /* DebugAppStore */, + A7F282D5238EAB2C00742C20 /* Github */, + A790A3B2236B1E70000451B5 /* ReleaseHockeyapp */, + A790A3B6236B1E7E000451B5 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A790A395236B1E36000451B5 /* Project object */; +} diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..e48b38aa53 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..2e894ce4c5 Binary files /dev/null and b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TonBinding.xcscheme b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TonBinding.xcscheme new file mode 100644 index 0000000000..bfc414f4c0 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TonBinding.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..db5cb0ee14 --- /dev/null +++ b/core-xprojects/TonBinding/TonBinding_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + TonBinding.xcscheme + + isShown + + orderHint + 13 + + + SuppressBuildableAutocreation + + A790A39D236B1E36000451B5 + + primary + + + + + diff --git a/core-xprojects/WalletCore/WalletCore/Info.plist b/core-xprojects/WalletCore/WalletCore/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/WalletCore/WalletCore/WalletCore.h b/core-xprojects/WalletCore/WalletCore/WalletCore.h new file mode 100644 index 0000000000..05d9f8acf2 --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore/WalletCore.h @@ -0,0 +1,19 @@ +// +// WalletCore.h +// WalletCore +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for WalletCore. +FOUNDATION_EXPORT double WalletCoreVersionNumber; + +//! Project version string for WalletCore. +FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.pbxproj b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..9c5a2e3314 --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,750 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A701F783236B25E0002ABF81 /* WalletCore.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F781236B25E0002ABF81 /* WalletCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A701F78B236B25F6002ABF81 /* WalletCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701F78A236B25F6002ABF81 /* WalletCore.swift */; }; + A701F7A7236B27CF002ABF81 /* TonBinding.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A701F7A6236B27CF002ABF81 /* TonBinding.framework */; }; + A7D28217236C3DDF0000A9BF /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28216236C3DDF0000A9BF /* SwiftSignalKit.framework */; }; + A7D28219236C3EC80000A9BF /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28218236C3EC80000A9BF /* libcrypto.a */; }; + A7D2821B236C3EEB0000A9BF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D2821A236C3EEB0000A9BF /* libc++.tbd */; }; + A7D28225236C509A0000A9BF /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28224236C509A0000A9BF /* libz.tbd */; }; + A7D2823A236C68810000A9BF /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28239236C68810000A9BF /* SSignalKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A701F77E236B25E0002ABF81 /* WalletCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WalletCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F781236B25E0002ABF81 /* WalletCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WalletCore.h; sourceTree = ""; }; + A701F782236B25E0002ABF81 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A701F78A236B25F6002ABF81 /* WalletCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletCore.swift; sourceTree = ""; }; + A701F79D236B268E002ABF81 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F79F236B2692002ABF81 /* TonBindings.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TonBindings.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F7A4236B27CB002ABF81 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F7A6236B27CF002ABF81 /* TonBinding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TonBinding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28216236C3DDF0000A9BF /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D28218236C3EC80000A9BF /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../../thrid-party/openssl/lib/libcrypto.a"; sourceTree = ""; }; + A7D2821A236C3EEB0000A9BF /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + A7D28224236C509A0000A9BF /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + A7D28239236C68810000A9BF /* SSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A701F77B236B25E0002ABF81 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7D2823A236C68810000A9BF /* SSignalKit.framework in Frameworks */, + A7D28225236C509A0000A9BF /* libz.tbd in Frameworks */, + A7D2821B236C3EEB0000A9BF /* libc++.tbd in Frameworks */, + A7D28217236C3DDF0000A9BF /* SwiftSignalKit.framework in Frameworks */, + A701F7A7236B27CF002ABF81 /* TonBinding.framework in Frameworks */, + A7D28219236C3EC80000A9BF /* libcrypto.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A701F774236B25E0002ABF81 = { + isa = PBXGroup; + children = ( + A701F789236B25F6002ABF81 /* Sources */, + A701F780236B25E0002ABF81 /* WalletCore */, + A701F77F236B25E0002ABF81 /* Products */, + A701F79C236B268E002ABF81 /* Frameworks */, + ); + sourceTree = ""; + }; + A701F77F236B25E0002ABF81 /* Products */ = { + isa = PBXGroup; + children = ( + A701F77E236B25E0002ABF81 /* WalletCore.framework */, + ); + name = Products; + sourceTree = ""; + }; + A701F780236B25E0002ABF81 /* WalletCore */ = { + isa = PBXGroup; + children = ( + A701F781236B25E0002ABF81 /* WalletCore.h */, + A701F782236B25E0002ABF81 /* Info.plist */, + ); + path = WalletCore; + sourceTree = ""; + }; + A701F789236B25F6002ABF81 /* Sources */ = { + isa = PBXGroup; + children = ( + A701F78A236B25F6002ABF81 /* WalletCore.swift */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/WalletCore/Sources"; + sourceTree = ""; + }; + A701F79C236B268E002ABF81 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7D28239236C68810000A9BF /* SSignalKit.framework */, + A7D28224236C509A0000A9BF /* libz.tbd */, + A7D2821A236C3EEB0000A9BF /* libc++.tbd */, + A7D28218236C3EC80000A9BF /* libcrypto.a */, + A7D28216236C3DDF0000A9BF /* SwiftSignalKit.framework */, + A701F7A6236B27CF002ABF81 /* TonBinding.framework */, + A701F7A4236B27CB002ABF81 /* SwiftSignalKitMac.framework */, + A701F79F236B2692002ABF81 /* TonBindings.framework */, + A701F79D236B268E002ABF81 /* SwiftSignalKitMac.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A701F779236B25E0002ABF81 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F783236B25E0002ABF81 /* WalletCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A701F77D236B25E0002ABF81 /* WalletCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = A701F786236B25E0002ABF81 /* Build configuration list for PBXNativeTarget "WalletCore" */; + buildPhases = ( + A701F779236B25E0002ABF81 /* Headers */, + A701F77A236B25E0002ABF81 /* Sources */, + A701F77B236B25E0002ABF81 /* Frameworks */, + A701F77C236B25E0002ABF81 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WalletCore; + productName = WalletCore; + productReference = A701F77E236B25E0002ABF81 /* WalletCore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A701F775236B25E0002ABF81 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A701F77D236B25E0002ABF81 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A701F778236B25E0002ABF81 /* Build configuration list for PBXProject "WalletCore_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A701F774236B25E0002ABF81; + productRefGroup = A701F77F236B25E0002ABF81 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A701F77D236B25E0002ABF81 /* WalletCore */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A701F77C236B25E0002ABF81 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A701F77A236B25E0002ABF81 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F78B236B25F6002ABF81 /* WalletCore.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A701F785236B25E0002ABF81 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A701F788236B25E0002ABF81 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + A701F78C236B261D002ABF81 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A701F78D236B261D002ABF81 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; + A701F790236B2628002ABF81 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A701F791236B2628002ABF81 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + A701F794236B2634002ABF81 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A701F795236B2634002ABF81 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + A701F798236B2643002ABF81 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A701F799236B2643002ABF81 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + A7F282D2238EAB0D00742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282D3238EAB0D00742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + HEADER_SEARCH_PATHS = "../../thrid-party/openssl/"; + INFOPLIST_FILE = WalletCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = "../../thrid-party/openssl/lib"; + MACH_O_TYPE = mh_dylib; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WalletCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A701F778236B25E0002ABF81 /* Build configuration list for PBXProject "WalletCore_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F785236B25E0002ABF81 /* DebugHockeyapp */, + A701F78C236B261D002ABF81 /* HockeyappMacAlpha */, + A701F790236B2628002ABF81 /* DebugAppStore */, + A7F282D2238EAB0D00742C20 /* Github */, + A701F794236B2634002ABF81 /* ReleaseHockeyapp */, + A701F798236B2643002ABF81 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + A701F786236B25E0002ABF81 /* Build configuration list for PBXNativeTarget "WalletCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F788236B25E0002ABF81 /* DebugHockeyapp */, + A701F78D236B261D002ABF81 /* HockeyappMacAlpha */, + A701F791236B2628002ABF81 /* DebugAppStore */, + A7F282D3238EAB0D00742C20 /* Github */, + A701F795236B2634002ABF81 /* ReleaseHockeyapp */, + A701F799236B2643002ABF81 /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A701F775236B25E0002ABF81 /* Project object */; +} diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..ed2c0d4a44 --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..c48a1f5498 Binary files /dev/null and b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/WalletCore.xcscheme b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/WalletCore.xcscheme new file mode 100644 index 0000000000..000486df3a --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/WalletCore.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..629350be50 --- /dev/null +++ b/core-xprojects/WalletCore/WalletCore_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + WalletCore.xcscheme + + isShown + + orderHint + 21 + + + SuppressBuildableAutocreation + + A701F77D236B25E0002ABF81 + + primary + + + + + diff --git a/core-xprojects/crc32/Crc32/Crc32 b/core-xprojects/crc32/Crc32/Crc32 new file mode 100644 index 0000000000..b9b4f486c8 --- /dev/null +++ b/core-xprojects/crc32/Crc32/Crc32 @@ -0,0 +1,9 @@ +#import + +//! Project version number for crc32mac. +FOUNDATION_EXPORT double crc32VersionNumber; + +//! Project version string for crc32mac. +FOUNDATION_EXPORT const unsigned char crc32VersionString[]; + +uint32_t Crc32(const void *bytes, int length); diff --git a/core-xprojects/crc32/Crc32/Crc32.h b/core-xprojects/crc32/Crc32/Crc32.h new file mode 100644 index 0000000000..f890c2c16a --- /dev/null +++ b/core-xprojects/crc32/Crc32/Crc32.h @@ -0,0 +1,9 @@ +#import + +//! Project version number for crc32mac. +FOUNDATION_EXPORT double Crc32VersionNumber; + +//! Project version string for crc32mac. +FOUNDATION_EXPORT const unsigned char Crc32VersionString[]; + +uint32_t Crc32(const void *bytes, int length); diff --git a/core-xprojects/crc32/Crc32/Info.plist b/core-xprojects/crc32/Crc32/Info.plist new file mode 100644 index 0000000000..5371a6e108 --- /dev/null +++ b/core-xprojects/crc32/Crc32/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram Messenger LLP. All rights reserved. + + diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.pbxproj b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..16ca587570 --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1043 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A701F930236C1D9E002ABF81 /* Crc32.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F92F236C1D9E002ABF81 /* Crc32.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D03E463E2306E22E0049C28B /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45732305CCD20049C28B /* Crc32.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A701F92F236C1D9E002ABF81 /* Crc32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crc32.h; sourceTree = ""; }; + D03E45672305CC310049C28B /* Crc32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Crc32.h; sourceTree = ""; }; + D03E45732305CCD20049C28B /* Crc32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Crc32.m; sourceTree = ""; }; + D03E45762305CCEB0049C28B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D03E45782305CCF00049C28B /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + D03E46322306E0BB0049C28B /* Crc32.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Crc32.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E46352306E0BB0049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D03E462F2306E0BB0049C28B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D03E455A2305CC310049C28B = { + isa = PBXGroup; + children = ( + D03E45662305CC310049C28B /* Sources */, + D03E46332306E0BB0049C28B /* Crc32 */, + D03E45652305CC310049C28B /* Products */, + D03E45752305CCEB0049C28B /* Frameworks */, + ); + sourceTree = ""; + }; + D03E45652305CC310049C28B /* Products */ = { + isa = PBXGroup; + children = ( + D03E46322306E0BB0049C28B /* Crc32.framework */, + ); + name = Products; + sourceTree = ""; + }; + D03E45662305CC310049C28B /* Sources */ = { + isa = PBXGroup; + children = ( + D03E45732305CCD20049C28B /* Crc32.m */, + D03E45672305CC310049C28B /* Crc32.h */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/Crc32/Sources"; + sourceTree = ""; + }; + D03E45752305CCEB0049C28B /* Frameworks */ = { + isa = PBXGroup; + children = ( + D03E45782305CCF00049C28B /* libz.tbd */, + D03E45762305CCEB0049C28B /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D03E46332306E0BB0049C28B /* Crc32 */ = { + isa = PBXGroup; + children = ( + A701F92F236C1D9E002ABF81 /* Crc32.h */, + D03E46352306E0BB0049C28B /* Info.plist */, + ); + path = Crc32; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D03E462D2306E0BB0049C28B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F930236C1D9E002ABF81 /* Crc32.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D03E46312306E0BB0049C28B /* Crc32 */ = { + isa = PBXNativeTarget; + buildConfigurationList = D03E463B2306E0BB0049C28B /* Build configuration list for PBXNativeTarget "Crc32" */; + buildPhases = ( + D03E462D2306E0BB0049C28B /* Headers */, + D03E462E2306E0BB0049C28B /* Sources */, + D03E462F2306E0BB0049C28B /* Frameworks */, + D03E46302306E0BB0049C28B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Crc32; + productName = crc32mac; + productReference = D03E46322306E0BB0049C28B /* Crc32.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D03E455B2305CC310049C28B /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = "Telegram Messenger LLP"; + TargetAttributes = { + D03E46312306E0BB0049C28B = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = D03E455E2305CC310049C28B /* Build configuration list for PBXProject "Crc32_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D03E455A2305CC310049C28B; + productRefGroup = D03E45652305CC310049C28B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D03E46312306E0BB0049C28B /* Crc32 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D03E46302306E0BB0049C28B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D03E462E2306E0BB0049C28B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D03E463E2306E22E0049C28B /* Crc32.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282DF238EAB5400742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282E0238EAB5400742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + D0208AAC2306E7EB00A23503 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0208AAE2306E7EB00A23503 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0208AAF2306E7F700A23503 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0208AB12306E7F700A23503 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0208AB22306E7FD00A23503 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0208AB42306E7FD00A23503 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0208AB52306E80300A23503 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D0208AB72306E80300A23503 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D03E456F2305CC4E0049C28B /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D03E46382306E0BB0049C28B /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = crc32/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.crc32; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D03E455E2305CC310049C28B /* Build configuration list for PBXProject "Crc32_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D03E456F2305CC4E0049C28B /* DebugHockeyapp */, + D0208AAF2306E7F700A23503 /* DebugAppStore */, + A7F282DF238EAB5400742C20 /* Github */, + D0208AAC2306E7EB00A23503 /* HockeyappMacAlpha */, + D0208AB22306E7FD00A23503 /* ReleaseAppStore */, + D0208AB52306E80300A23503 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + D03E463B2306E0BB0049C28B /* Build configuration list for PBXNativeTarget "Crc32" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D03E46382306E0BB0049C28B /* DebugHockeyapp */, + D0208AB12306E7F700A23503 /* DebugAppStore */, + A7F282E0238EAB5400742C20 /* Github */, + D0208AAE2306E7EB00A23503 /* HockeyappMacAlpha */, + D0208AB42306E7FD00A23503 /* ReleaseAppStore */, + D0208AB72306E80300A23503 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D03E455B2305CC310049C28B /* Project object */; +} diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..37cfdeee1b Binary files /dev/null and b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Crc32.xcscheme b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Crc32.xcscheme new file mode 100644 index 0000000000..422caaa163 --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/Crc32.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/crc32mac.xcscheme b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/crc32mac.xcscheme new file mode 100644 index 0000000000..422caaa163 --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/crc32mac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..ac50275bec --- /dev/null +++ b/core-xprojects/crc32/Crc32_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + Crc32.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + D03E46312306E0BB0049C28B + + primary + + + + + diff --git a/core-xprojects/libphonenumber/libphonenumber/Info.plist b/core-xprojects/libphonenumber/libphonenumber/Info.plist new file mode 100644 index 0000000000..5371a6e108 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram Messenger LLP. All rights reserved. + + diff --git a/core-xprojects/libphonenumber/libphonenumber/libphonenumber.h b/core-xprojects/libphonenumber/libphonenumber/libphonenumber.h new file mode 100644 index 0000000000..798e6a43ec --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber/libphonenumber.h @@ -0,0 +1,12 @@ +#import + +//! Project version number for libphonenumber_Mac. +FOUNDATION_EXPORT double libphonenumber_VersionNumber; + +//! Project version string for libphonenumber_Mac. +FOUNDATION_EXPORT const unsigned char libphonenumber_VersionString[]; + +#import +#import +#import +#import diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.pbxproj b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..b4efa6db8c --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,832 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A791904E240CFC49002011CA /* NBPhoneNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = A7919049240CFC49002011CA /* NBPhoneNumber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A791904F240CFC49002011CA /* NBPhoneNumberUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = A791904A240CFC49002011CA /* NBPhoneNumberUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919051240CFC49002011CA /* NBAsYouTypeFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = A791904C240CFC49002011CA /* NBAsYouTypeFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7919052240CFC49002011CA /* NBPhoneNumberDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = A791904D240CFC49002011CA /* NBPhoneNumberDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0208ABF2306E85800A23503 /* NBMetadataCoreTest.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E459B2305D1EF0049C28B /* NBMetadataCoreTest.h */; }; + D0208AC02306E85800A23503 /* NBPhoneMetaDataGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45A22305D1EF0049C28B /* NBPhoneMetaDataGenerator.h */; }; + D0208AC42306E85800A23503 /* libphonenumber.h in Headers */ = {isa = PBXBuildFile; fileRef = D0208AB92306E84F00A23503 /* libphonenumber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0208AC52306E85800A23503 /* NBMetadataCoreMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45A02305D1EF0049C28B /* NBMetadataCoreMapper.h */; }; + D0208AC92306E85800A23503 /* NBMetadataCore.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E459E2305D1EF0049C28B /* NBMetadataCore.h */; }; + D0208ACA2306E85800A23503 /* NBPhoneMetaData.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45B02305D1F10049C28B /* NBPhoneMetaData.h */; }; + D0208ACB2306E85800A23503 /* NBPhoneNumberDesc.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45A72305D1F00049C28B /* NBPhoneNumberDesc.h */; }; + D0208ACC2306E85800A23503 /* NBMetadataHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E459A2305D1EF0049C28B /* NBMetadataHelper.h */; }; + D0208ACD2306E85800A23503 /* NBMetadataCoreTestMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E45A82305D1F00049C28B /* NBMetadataCoreTestMapper.h */; }; + D0208ACF2306E85800A23503 /* NBMetadataCoreMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45A42305D1F00049C28B /* NBMetadataCoreMapper.m */; }; + D0208AD02306E85800A23503 /* NBMetadataCoreTestMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45A32305D1F00049C28B /* NBMetadataCoreTestMapper.m */; }; + D0208AD12306E85800A23503 /* NBPhoneNumberDefines.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45A12305D1EF0049C28B /* NBPhoneNumberDefines.m */; }; + D0208AD22306E85800A23503 /* NBNumberFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45AE2305D1F10049C28B /* NBNumberFormat.m */; }; + D0208AD32306E85800A23503 /* NBAsYouTypeFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45B12305D1F10049C28B /* NBAsYouTypeFormatter.m */; }; + D0208AD42306E85800A23503 /* NBMetadataCore.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45A92305D1F00049C28B /* NBMetadataCore.m */; }; + D0208AD52306E85800A23503 /* NBPhoneNumberDesc.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E459F2305D1EF0049C28B /* NBPhoneNumberDesc.m */; }; + D0208AD62306E85800A23503 /* NBPhoneNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45AD2305D1F10049C28B /* NBPhoneNumber.m */; }; + D0208AD72306E85800A23503 /* NBPhoneMetaData.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45AF2305D1F10049C28B /* NBPhoneMetaData.m */; }; + D0208AD82306E85800A23503 /* NBMetadataCoreTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45B22305D1F10049C28B /* NBMetadataCoreTest.m */; }; + D0208AD92306E85800A23503 /* NBPhoneMetaDataGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45AB2305D1F10049C28B /* NBPhoneMetaDataGenerator.m */; }; + D0208ADA2306E85800A23503 /* NBMetadataHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45AC2305D1F10049C28B /* NBMetadataHelper.m */; }; + D0208ADB2306E85800A23503 /* NBPhoneNumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E45A52305D1F00049C28B /* NBPhoneNumberUtil.m */; }; + D0208ADD2306E85800A23503 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03E45CE2305D32D0049C28B /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7919049240CFC49002011CA /* NBPhoneNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NBPhoneNumber.h; path = "../../../submodules/telegram-ios/submodules/libphonenumber/PublicHeaders/libphonenumber/NBPhoneNumber.h"; sourceTree = ""; }; + A791904A240CFC49002011CA /* NBPhoneNumberUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NBPhoneNumberUtil.h; path = "../../../submodules/telegram-ios/submodules/libphonenumber/PublicHeaders/libphonenumber/NBPhoneNumberUtil.h"; sourceTree = ""; }; + A791904C240CFC49002011CA /* NBAsYouTypeFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NBAsYouTypeFormatter.h; path = "../../../submodules/telegram-ios/submodules/libphonenumber/PublicHeaders/libphonenumber/NBAsYouTypeFormatter.h"; sourceTree = ""; }; + A791904D240CFC49002011CA /* NBPhoneNumberDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NBPhoneNumberDefines.h; path = "../../../submodules/telegram-ios/submodules/libphonenumber/PublicHeaders/libphonenumber/NBPhoneNumberDefines.h"; sourceTree = ""; }; + D0208AB92306E84F00A23503 /* libphonenumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libphonenumber.h; sourceTree = ""; }; + D0208ABA2306E84F00A23503 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D0208AE52306E85800A23503 /* libphonenumber.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libphonenumber.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D03E45992305D1EF0049C28B /* NBNumberFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBNumberFormat.h; sourceTree = ""; }; + D03E459A2305D1EF0049C28B /* NBMetadataHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBMetadataHelper.h; sourceTree = ""; }; + D03E459B2305D1EF0049C28B /* NBMetadataCoreTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBMetadataCoreTest.h; sourceTree = ""; }; + D03E459E2305D1EF0049C28B /* NBMetadataCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBMetadataCore.h; sourceTree = ""; }; + D03E459F2305D1EF0049C28B /* NBPhoneNumberDesc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneNumberDesc.m; sourceTree = ""; }; + D03E45A02305D1EF0049C28B /* NBMetadataCoreMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBMetadataCoreMapper.h; sourceTree = ""; }; + D03E45A12305D1EF0049C28B /* NBPhoneNumberDefines.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneNumberDefines.m; sourceTree = ""; }; + D03E45A22305D1EF0049C28B /* NBPhoneMetaDataGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBPhoneMetaDataGenerator.h; sourceTree = ""; }; + D03E45A32305D1F00049C28B /* NBMetadataCoreTestMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBMetadataCoreTestMapper.m; sourceTree = ""; }; + D03E45A42305D1F00049C28B /* NBMetadataCoreMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBMetadataCoreMapper.m; sourceTree = ""; }; + D03E45A52305D1F00049C28B /* NBPhoneNumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneNumberUtil.m; sourceTree = ""; }; + D03E45A72305D1F00049C28B /* NBPhoneNumberDesc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBPhoneNumberDesc.h; sourceTree = ""; }; + D03E45A82305D1F00049C28B /* NBMetadataCoreTestMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBMetadataCoreTestMapper.h; sourceTree = ""; }; + D03E45A92305D1F00049C28B /* NBMetadataCore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBMetadataCore.m; sourceTree = ""; }; + D03E45AB2305D1F10049C28B /* NBPhoneMetaDataGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneMetaDataGenerator.m; sourceTree = ""; }; + D03E45AC2305D1F10049C28B /* NBMetadataHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBMetadataHelper.m; sourceTree = ""; }; + D03E45AD2305D1F10049C28B /* NBPhoneNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneNumber.m; sourceTree = ""; }; + D03E45AE2305D1F10049C28B /* NBNumberFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBNumberFormat.m; sourceTree = ""; }; + D03E45AF2305D1F10049C28B /* NBPhoneMetaData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneMetaData.m; sourceTree = ""; }; + D03E45B02305D1F10049C28B /* NBPhoneMetaData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NBPhoneMetaData.h; sourceTree = ""; }; + D03E45B12305D1F10049C28B /* NBAsYouTypeFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBAsYouTypeFormatter.m; sourceTree = ""; }; + D03E45B22305D1F10049C28B /* NBMetadataCoreTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBMetadataCoreTest.m; sourceTree = ""; }; + D03E45CE2305D32D0049C28B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D0208ADC2306E85800A23503 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D0208ADD2306E85800A23503 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7919048240CFC40002011CA /* headers */ = { + isa = PBXGroup; + children = ( + A791904C240CFC49002011CA /* NBAsYouTypeFormatter.h */, + A7919049240CFC49002011CA /* NBPhoneNumber.h */, + A791904D240CFC49002011CA /* NBPhoneNumberDefines.h */, + A791904A240CFC49002011CA /* NBPhoneNumberUtil.h */, + ); + path = headers; + sourceTree = ""; + }; + D0208AB82306E84F00A23503 /* libphonenumber */ = { + isa = PBXGroup; + children = ( + D0208AB92306E84F00A23503 /* libphonenumber.h */, + D0208ABA2306E84F00A23503 /* Info.plist */, + ); + path = libphonenumber; + sourceTree = ""; + }; + D03E45802305CE830049C28B = { + isa = PBXGroup; + children = ( + A7919048240CFC40002011CA /* headers */, + D0208AB82306E84F00A23503 /* libphonenumber */, + D03E458C2305CE840049C28B /* Sources */, + D03E458B2305CE830049C28B /* Products */, + D03E45CD2305D32D0049C28B /* Frameworks */, + ); + sourceTree = ""; + }; + D03E458B2305CE830049C28B /* Products */ = { + isa = PBXGroup; + children = ( + D0208AE52306E85800A23503 /* libphonenumber.framework */, + ); + name = Products; + sourceTree = ""; + }; + D03E458C2305CE840049C28B /* Sources */ = { + isa = PBXGroup; + children = ( + D03E45B12305D1F10049C28B /* NBAsYouTypeFormatter.m */, + D03E459E2305D1EF0049C28B /* NBMetadataCore.h */, + D03E45A92305D1F00049C28B /* NBMetadataCore.m */, + D03E45A02305D1EF0049C28B /* NBMetadataCoreMapper.h */, + D03E45A42305D1F00049C28B /* NBMetadataCoreMapper.m */, + D03E459B2305D1EF0049C28B /* NBMetadataCoreTest.h */, + D03E45B22305D1F10049C28B /* NBMetadataCoreTest.m */, + D03E45A82305D1F00049C28B /* NBMetadataCoreTestMapper.h */, + D03E45A32305D1F00049C28B /* NBMetadataCoreTestMapper.m */, + D03E459A2305D1EF0049C28B /* NBMetadataHelper.h */, + D03E45AC2305D1F10049C28B /* NBMetadataHelper.m */, + D03E45992305D1EF0049C28B /* NBNumberFormat.h */, + D03E45AE2305D1F10049C28B /* NBNumberFormat.m */, + D03E45B02305D1F10049C28B /* NBPhoneMetaData.h */, + D03E45AF2305D1F10049C28B /* NBPhoneMetaData.m */, + D03E45A22305D1EF0049C28B /* NBPhoneMetaDataGenerator.h */, + D03E45AB2305D1F10049C28B /* NBPhoneMetaDataGenerator.m */, + D03E45AD2305D1F10049C28B /* NBPhoneNumber.m */, + D03E45A12305D1EF0049C28B /* NBPhoneNumberDefines.m */, + D03E45A72305D1F00049C28B /* NBPhoneNumberDesc.h */, + D03E459F2305D1EF0049C28B /* NBPhoneNumberDesc.m */, + D03E45A52305D1F00049C28B /* NBPhoneNumberUtil.m */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/libphonenumber/Sources"; + sourceTree = ""; + }; + D03E45CD2305D32D0049C28B /* Frameworks */ = { + isa = PBXGroup; + children = ( + D03E45CE2305D32D0049C28B /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D0208ABE2306E85800A23503 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D0208AC42306E85800A23503 /* libphonenumber.h in Headers */, + A791904E240CFC49002011CA /* NBPhoneNumber.h in Headers */, + A7919051240CFC49002011CA /* NBAsYouTypeFormatter.h in Headers */, + A791904F240CFC49002011CA /* NBPhoneNumberUtil.h in Headers */, + A7919052240CFC49002011CA /* NBPhoneNumberDefines.h in Headers */, + D0208ABF2306E85800A23503 /* NBMetadataCoreTest.h in Headers */, + D0208AC02306E85800A23503 /* NBPhoneMetaDataGenerator.h in Headers */, + D0208AC52306E85800A23503 /* NBMetadataCoreMapper.h in Headers */, + D0208AC92306E85800A23503 /* NBMetadataCore.h in Headers */, + D0208ACA2306E85800A23503 /* NBPhoneMetaData.h in Headers */, + D0208ACB2306E85800A23503 /* NBPhoneNumberDesc.h in Headers */, + D0208ACC2306E85800A23503 /* NBMetadataHelper.h in Headers */, + D0208ACD2306E85800A23503 /* NBMetadataCoreTestMapper.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D0208ABD2306E85800A23503 /* libphonenumber */ = { + isa = PBXNativeTarget; + buildConfigurationList = D0208AE02306E85800A23503 /* Build configuration list for PBXNativeTarget "libphonenumber" */; + buildPhases = ( + D0208ABE2306E85800A23503 /* Headers */, + D0208ACE2306E85800A23503 /* Sources */, + D0208ADC2306E85800A23503 /* Frameworks */, + D0208ADE2306E85800A23503 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libphonenumber; + productName = "libphonenumber-iOS"; + productReference = D0208AE52306E85800A23503 /* libphonenumber.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D03E45812305CE830049C28B /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = "Telegram Messenger LLP"; + }; + buildConfigurationList = D03E45842305CE830049C28B /* Build configuration list for PBXProject "libphonenumber_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D03E45802305CE830049C28B; + productRefGroup = D03E458B2305CE830049C28B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0208ABD2306E85800A23503 /* libphonenumber */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0208ADE2306E85800A23503 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0208ACE2306E85800A23503 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0208ACF2306E85800A23503 /* NBMetadataCoreMapper.m in Sources */, + D0208AD02306E85800A23503 /* NBMetadataCoreTestMapper.m in Sources */, + D0208AD12306E85800A23503 /* NBPhoneNumberDefines.m in Sources */, + D0208AD22306E85800A23503 /* NBNumberFormat.m in Sources */, + D0208AD32306E85800A23503 /* NBAsYouTypeFormatter.m in Sources */, + D0208AD42306E85800A23503 /* NBMetadataCore.m in Sources */, + D0208AD52306E85800A23503 /* NBPhoneNumberDesc.m in Sources */, + D0208AD62306E85800A23503 /* NBPhoneNumber.m in Sources */, + D0208AD72306E85800A23503 /* NBPhoneMetaData.m in Sources */, + D0208AD82306E85800A23503 /* NBMetadataCoreTest.m in Sources */, + D0208AD92306E85800A23503 /* NBPhoneMetaDataGenerator.m in Sources */, + D0208ADA2306E85800A23503 /* NBMetadataHelper.m in Sources */, + D0208ADB2306E85800A23503 /* NBPhoneNumberUtil.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282E5238EAB6800742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282E6238EAB6800742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Github; + }; + D0208AE22306E85800A23503 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugHockeyapp; + }; + D0208AE72306E86800A23503 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + D0208AE92306E86800A23503 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugAppStore; + }; + D0208AEA2306E87100A23503 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D0208AEC2306E87100A23503 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = HockeyappMacAlpha; + }; + D0208AED2306E87700A23503 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + D0208AEF2306E87700A23503 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseAppStore; + }; + D0208AF02306E87E00A23503 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + D0208AF22306E87E00A23503 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = libphonenumber/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.libphonenumbermac; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseHockeyapp; + }; + D03E45952305CE9A0049C28B /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "iPhone Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D0208AE02306E85800A23503 /* Build configuration list for PBXNativeTarget "libphonenumber" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D0208AE22306E85800A23503 /* DebugHockeyapp */, + D0208AEC2306E87100A23503 /* HockeyappMacAlpha */, + D0208AE92306E86800A23503 /* DebugAppStore */, + A7F282E6238EAB6800742C20 /* Github */, + D0208AEF2306E87700A23503 /* ReleaseAppStore */, + D0208AF22306E87E00A23503 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; + D03E45842305CE830049C28B /* Build configuration list for PBXProject "libphonenumber_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D03E45952305CE9A0049C28B /* DebugHockeyapp */, + D0208AEA2306E87100A23503 /* HockeyappMacAlpha */, + D0208AE72306E86800A23503 /* DebugAppStore */, + A7F282E5238EAB6800742C20 /* Github */, + D0208AED2306E87700A23503 /* ReleaseAppStore */, + D0208AF02306E87E00A23503 /* ReleaseHockeyapp */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = D03E45812305CE830049C28B /* Project object */; +} diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..0630bef85d Binary files /dev/null and b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumber.xcscheme b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumber.xcscheme new file mode 100644 index 0000000000..04c54132b1 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumber.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumbermac.xcscheme b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumbermac.xcscheme new file mode 100644 index 0000000000..04c54132b1 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/libphonenumbermac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/libphonenumber.xcscheme b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/libphonenumber.xcscheme new file mode 100644 index 0000000000..04c54132b1 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/libphonenumber.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..a067a50209 --- /dev/null +++ b/core-xprojects/libphonenumber/libphonenumber_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,16 @@ + + + + + SchemeUserState + + libphonenumber.xcscheme + + isShown + + orderHint + 7 + + + + diff --git a/core-xprojects/sqlcipher/sqlcipher/Info.plist b/core-xprojects/sqlcipher/sqlcipher/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/core-xprojects/sqlcipher/sqlcipher/sqlcipher.h b/core-xprojects/sqlcipher/sqlcipher/sqlcipher.h new file mode 100644 index 0000000000..195799384d --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher/sqlcipher.h @@ -0,0 +1,23 @@ +// +// sqlcipher.h +// sqlcipher +// +// Created by Mikhail Filimonov on 31.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for sqlcipher. +FOUNDATION_EXPORT double sqlcipherVersionNumber; + +//! Project version string for sqlcipher. +FOUNDATION_EXPORT const unsigned char sqlcipherVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import +#import +#import + diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.pbxproj b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..f585d03ee9 --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,768 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A701F902236B2EF8002ABF81 /* sqlcipher.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F900236B2EF8002ABF81 /* sqlcipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A701F911236B2F01002ABF81 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = A701F909236B2F01002ABF81 /* sqlite3.c */; }; + A701F912236B2F01002ABF81 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F90A236B2F01002ABF81 /* SQLite-Bridging.h */; }; + A701F918236B2F01002ABF81 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = A701F910236B2F01002ABF81 /* fts3_tokenizer.h */; }; + A7918DBA240CEDE5002011CA /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DB7240CEDE5002011CA /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DBB240CEDE5002011CA /* sqlite3ext.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DB8240CEDE5002011CA /* sqlite3ext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DBC240CEDE5002011CA /* sqlcipher_config.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DB9240CEDE5002011CA /* sqlcipher_config.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A701F8FD236B2EF8002ABF81 /* sqlcipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlcipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A701F900236B2EF8002ABF81 /* sqlcipher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sqlcipher.h; sourceTree = ""; }; + A701F901236B2EF8002ABF81 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A701F909236B2F01002ABF81 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sqlite3.c; sourceTree = ""; }; + A701F90A236B2F01002ABF81 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + A701F90B236B2F01002ABF81 /* sqlcipher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlcipher.h; sourceTree = ""; }; + A701F90C236B2F01002ABF81 /* sqlcipher_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlcipher_config.h; sourceTree = ""; }; + A701F90D236B2F01002ABF81 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3.h; sourceTree = ""; }; + A701F90E236B2F01002ABF81 /* sqlite3ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3ext.h; sourceTree = ""; }; + A701F90F236B2F01002ABF81 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + A701F910236B2F01002ABF81 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; + A701F922236B3172002ABF81 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; + A7918DB7240CEDE5002011CA /* sqlite3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = "../../submodules/telegram-ios/submodules/sqlcipher/PublicHeaders/sqlcipher/sqlite3.h"; sourceTree = ""; }; + A7918DB8240CEDE5002011CA /* sqlite3ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sqlite3ext.h; path = "../../submodules/telegram-ios/submodules/sqlcipher/PublicHeaders/sqlcipher/sqlite3ext.h"; sourceTree = ""; }; + A7918DB9240CEDE5002011CA /* sqlcipher_config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sqlcipher_config.h; path = "../../submodules/telegram-ios/submodules/sqlcipher/PublicHeaders/sqlcipher/sqlcipher_config.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A701F8FA236B2EF8002ABF81 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A701F8F3236B2EF8002ABF81 = { + isa = PBXGroup; + children = ( + A7918DB9240CEDE5002011CA /* sqlcipher_config.h */, + A7918DB7240CEDE5002011CA /* sqlite3.h */, + A7918DB8240CEDE5002011CA /* sqlite3ext.h */, + A701F908236B2F01002ABF81 /* Sources */, + A701F8FF236B2EF8002ABF81 /* sqlcipher */, + A701F8FE236B2EF8002ABF81 /* Products */, + A701F921236B3171002ABF81 /* Frameworks */, + ); + sourceTree = ""; + }; + A701F8FE236B2EF8002ABF81 /* Products */ = { + isa = PBXGroup; + children = ( + A701F8FD236B2EF8002ABF81 /* sqlcipher.framework */, + ); + name = Products; + sourceTree = ""; + }; + A701F8FF236B2EF8002ABF81 /* sqlcipher */ = { + isa = PBXGroup; + children = ( + A701F900236B2EF8002ABF81 /* sqlcipher.h */, + A701F901236B2EF8002ABF81 /* Info.plist */, + ); + path = sqlcipher; + sourceTree = ""; + }; + A701F908236B2F01002ABF81 /* Sources */ = { + isa = PBXGroup; + children = ( + A701F909236B2F01002ABF81 /* sqlite3.c */, + A701F90A236B2F01002ABF81 /* SQLite-Bridging.h */, + A701F90B236B2F01002ABF81 /* sqlcipher.h */, + A701F90C236B2F01002ABF81 /* sqlcipher_config.h */, + A701F90D236B2F01002ABF81 /* sqlite3.h */, + A701F90E236B2F01002ABF81 /* sqlite3ext.h */, + A701F90F236B2F01002ABF81 /* SQLite-Bridging.m */, + A701F910236B2F01002ABF81 /* fts3_tokenizer.h */, + ); + name = Sources; + path = "../../submodules/telegram-ios/submodules/sqlcipher/Sources"; + sourceTree = ""; + }; + A701F921236B3171002ABF81 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A701F922236B3172002ABF81 /* libsqlite3.tbd */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A701F8F8236B2EF8002ABF81 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DBA240CEDE5002011CA /* sqlite3.h in Headers */, + A7918DBB240CEDE5002011CA /* sqlite3ext.h in Headers */, + A7918DBC240CEDE5002011CA /* sqlcipher_config.h in Headers */, + A701F918236B2F01002ABF81 /* fts3_tokenizer.h in Headers */, + A701F912236B2F01002ABF81 /* SQLite-Bridging.h in Headers */, + A701F902236B2EF8002ABF81 /* sqlcipher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A701F8FC236B2EF8002ABF81 /* sqlcipher */ = { + isa = PBXNativeTarget; + buildConfigurationList = A701F905236B2EF8002ABF81 /* Build configuration list for PBXNativeTarget "sqlcipher" */; + buildPhases = ( + A701F8F8236B2EF8002ABF81 /* Headers */, + A701F8F9236B2EF8002ABF81 /* Sources */, + A701F8FA236B2EF8002ABF81 /* Frameworks */, + A701F8FB236B2EF8002ABF81 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = sqlcipher; + productName = sqlcipher; + productReference = A701F8FD236B2EF8002ABF81 /* sqlcipher.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A701F8F4236B2EF8002ABF81 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A701F8FC236B2EF8002ABF81 = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A701F8F7236B2EF8002ABF81 /* Build configuration list for PBXProject "sqlcipher_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A701F8F3236B2EF8002ABF81; + productRefGroup = A701F8FE236B2EF8002ABF81 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A701F8FC236B2EF8002ABF81 /* sqlcipher */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A701F8FB236B2EF8002ABF81 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A701F8F9236B2EF8002ABF81 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A701F911236B2F01002ABF81 /* sqlite3.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A701F903236B2EF8002ABF81 /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A701F904236B2EF8002ABF81 /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A701F906236B2EF8002ABF81 /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A701F907236B2EF8002ABF81 /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A701F91A236B2F84002ABF81 /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A701F91B236B2F84002ABF81 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A701F91C236B2F89002ABF81 /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A701F91D236B2F89002ABF81 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A701F91E236B2F90002ABF81 /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A701F91F236B2F90002ABF81 /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7F282DA238EAB4300742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282DB238EAB4300742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREPROCESSOR_DEFINITIONS = ""; + INFOPLIST_FILE = sqlcipher/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC=1", + "-DSQLCIPHER_CRYPTO_CC=1", + "-DSQLITE_ENABLE_FTS5", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DNDEBUG", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.sqlcipher; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A701F8F7236B2EF8002ABF81 /* Build configuration list for PBXProject "sqlcipher_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F903236B2EF8002ABF81 /* DebugAppStore */, + A7F282DA238EAB4300742C20 /* Github */, + A701F904236B2EF8002ABF81 /* ReleaseAppStore */, + A701F91A236B2F84002ABF81 /* ReleaseHockeyapp */, + A701F91C236B2F89002ABF81 /* DebugHockeyapp */, + A701F91E236B2F90002ABF81 /* HockeyappMacAlpha */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; + A701F905236B2EF8002ABF81 /* Build configuration list for PBXNativeTarget "sqlcipher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A701F906236B2EF8002ABF81 /* DebugAppStore */, + A7F282DB238EAB4300742C20 /* Github */, + A701F907236B2EF8002ABF81 /* ReleaseAppStore */, + A701F91B236B2F84002ABF81 /* ReleaseHockeyapp */, + A701F91D236B2F89002ABF81 /* DebugHockeyapp */, + A701F91F236B2F90002ABF81 /* HockeyappMacAlpha */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStore; + }; +/* End XCConfigurationList section */ + }; + rootObject = A701F8F4236B2EF8002ABF81 /* Project object */; +} diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..9f308709d3 --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..83ccf0d58e Binary files /dev/null and b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/sqlcipher.xcscheme b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/sqlcipher.xcscheme new file mode 100644 index 0000000000..5d56c02aed --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/sqlcipher.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..2466286721 --- /dev/null +++ b/core-xprojects/sqlcipher/sqlcipher_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + sqlcipher.xcscheme + + isShown + + orderHint + 11 + + + SuppressBuildableAutocreation + + A701F8FC236B2EF8002ABF81 + + primary + + + + + diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000000..47b148803c --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,64 @@ +# Customise this file, documentation can be found here: +# https://github.com/fastlane/fastlane/tree/master/fastlane/docs +# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md +# can also be listed using the `fastlane actions` command + +# Change the syntax highlighting to Ruby +# All lines starting with a # are ignored when running `fastlane` + +# If you want to automatically update fastlane if a new version is available: +# update_fastlane + +# This is the minimum version number required. +# Update this, if you use features of a newer version +fastlane_version "2.3.1" + +default_platform :mac + +# Fastfile actions accept additional configuration, but +# don't worry, fastlane will prompt you for required +# info which you can add here later + +lane :beta do + sh("rm -rf ./output") + sh("mkdir ./output") + gym( + scheme: "Hockeyapp", + clean: true, + silent: true, + output_directory: "./output" + ) +end + +lane :alpha do + sh("rm -rf ./output") + sh("mkdir ./output") + gym( + scheme: "HockeyappAlpha", + clean: true, + silent: true, + output_directory: "./output" + ) +end + +lane :appstore do + sh("rm -rf ./output") + sh("mkdir ./output") + gym( + scheme: "AppStore", + clean: true, + silent: true, + output_directory: "./output" + ) +end + +lane :release do + sh("rm -rf ./output") + sh("mkdir ./output") + gym( + scheme: "Release", + clean: true, + silent: true, + output_directory: "./output" + ) +end diff --git a/images/mas_badge.png b/images/mas_badge.png new file mode 100644 index 0000000000..cf1aa85b98 Binary files /dev/null and b/images/mas_badge.png differ diff --git a/images/tg.png b/images/tg.png new file mode 100644 index 0000000000..30b2ebf4b2 Binary files /dev/null and b/images/tg.png differ diff --git a/submodules/AppCenter/AppCenter.framework/AppCenter b/submodules/AppCenter/AppCenter.framework/AppCenter new file mode 100644 index 0000000000..82fead5f89 Binary files /dev/null and b/submodules/AppCenter/AppCenter.framework/AppCenter differ diff --git a/submodules/AppCenter/AppCenter.framework/Headers/AppCenter.h b/submodules/AppCenter/AppCenter.framework/Headers/AppCenter.h new file mode 100644 index 0000000000..6ff9d8882c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/AppCenter.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" +#import "MSAppCenter.h" +#import "MSAppCenterErrors.h" +#import "MSChannelGroupProtocol.h" +#import "MSChannelProtocol.h" +#import "MSConstants.h" +#import "MSCustomProperties.h" +#import "MSDevice.h" +#import "MSEnable.h" +#import "MSLog.h" +#import "MSLogWithProperties.h" +#import "MSLogger.h" +#import "MSService.h" +#import "MSServiceAbstract.h" +#import "MSWrapperLogger.h" +#import "MSWrapperSdk.h" diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenter.framework/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenter.h b/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenter.h new file mode 100644 index 0000000000..c6ceb9ca90 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenter.h @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +@class MSWrapperSdk; + +#if !TARGET_OS_TV +@class MSCustomProperties; +#endif + +@interface MSAppCenter : NSObject + +/** + * Returns the singleton instance of MSAppCenter. + */ ++ (instancetype)sharedInstance; + +/** + * Configure the SDK with an application secret. + * + * @param appSecret A unique and secret key used to identify the application. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configureWithAppSecret:(NSString *)appSecret; + +/** + * Configure the SDK. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configure; + +/** + * Configure the SDK with an application secret and an array of services to start. + * + * @param appSecret A unique and secret key used to identify the application. + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)start:(NSString *)appSecret withServices:(NSArray *)services; + +/** + * Start the SDK with an array of services. + * + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)startWithServices:(NSArray *)services; + +/** + * Start a service. + * + * @param service A service to start. + * + * @discussion This may be called only once per service per application process lifetime. + */ ++ (void)startService:(Class)service; + +/** + * Configure the SDK with an array of services to start from a library. This will not start the service at application level, it will enable + * the service only for the library. + * + * @param services Array of services to start. + */ ++ (void)startFromLibraryWithServices:(NSArray *)services; + +/** + * Check whether the SDK has already been configured or not. + * + * @return YES if configured, NO otherwise. + */ ++ (BOOL)isConfigured; + +/** + * Check whether app is running in App Center Test Cloud. + * + * @return true if running in App Center Test Cloud, false otherwise. + */ ++ (BOOL)isRunningInAppCenterTestCloud; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl Base URL to use for backend communication. + */ ++ (void)setLogUrl:(NSString *)logUrl; + +/** + * Enable or disable the SDK as a whole. In addition to AppCenter resources, it will also enable or disable all registered services. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled YES to enable, NO to disable. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Check whether the SDK is enabled or not as a whole. + * + * @return YES if enabled, NO otherwise. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +/** + * Get log level. + * + * @return Log level. + */ ++ (MSLogLevel)logLevel; + +/** + * Set log level. + * + * @param logLevel The log level. + */ ++ (void)setLogLevel:(MSLogLevel)logLevel; + +/** + * Set log level handler. + * + * @param logHandler Handler. + */ ++ (void)setLogHandler:(MSLogHandler)logHandler; + +/** + * Set wrapper SDK information to use when building device properties. This is intended in case you are building a SDK that uses the App + * Center SDK under the hood, e.g. our Xamarin SDK or ReactNative SDk. + * + * @param wrapperSdk Wrapper SDK information. + */ ++ (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk; + +#if !TARGET_OS_TV +/** + * Set the custom properties. + * + * @param customProperties Custom properties object. + */ ++ (void)setCustomProperties:(MSCustomProperties *)customProperties; +#endif + +/** + * Check whether the application delegate forwarder is enabled or not. + * + * @return YES if enabled, NO otherwise. + * + * @discussion The application delegate forwarder forwards messages that target your application delegate methods via swizzling to the SDK. + * It simplifies the SDK integration but may not be suitable to any situations. For + * instance it should be disabled if you or one of your third party SDK is doing message forwarding on the application delegate. Message + * forwarding usually implies the implementation of @see NSObject#forwardingTargetForSelector: or @see NSObject#forwardInvocation: methods. + * To disable the application delegate forwarder just add the `AppCenterAppDelegateForwarderEnabled` tag to your Info .plist file and set it + * to `0`. Then you will have to forward any application delegate needed by the SDK manually. + */ ++ (BOOL)isAppDelegateForwarderEnabled; + +/** + * Get unique installation identifier. + * + * @return Unique installation identifier. + */ ++ (NSUUID *)installId; + +/** + * Detect if a debugger is attached to the app process. This is only invoked once on app startup and can not detect + * if the debugger is being attached during runtime! + * + * @return BOOL if the debugger is attached. + */ ++ (BOOL)isDebuggerAttached; + +/** + * Get the current version of AppCenter SDK. + * + * @return The current version of AppCenter SDK. + */ ++ (NSString *)sdkVersion; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. This method is only intended for + * applications. + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored. + * + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. This parameter can be null. + * + * @discussion This only sets the maximum size of the database, but App Center modules might store additional data. + * The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + */ ++ (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(void (^)(BOOL))completionHandler; + +/** + * Set the user identifier. + * + * @param userId User identifier. + * + * @discussion Set the user identifier for logs sent for the default target token when the secret passed in @c + * MSAppCenter:start:withServices: contains "target={targetToken}". + * + * For App Center backend the user identifier maximum length is 256 characters. + * + * AppCenter must be configured or started before this API can be used. + */ ++ (void)setUserId:(NSString *)userId; + +/** + * Set country code to use when building device properties. + * + * @param countryCode The two-letter ISO country code. @see https://www.iso.org/obp/ui/#search for more information. + */ ++ (void)setCountryCode:(NSString *)countryCode; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenterErrors.h b/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenterErrors.h new file mode 100644 index 0000000000..e57cf9217f --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSAppCenterErrors.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_APP_CENTER_ERRORS_H +#define MS_APP_CENTER_ERRORS_H + +#import + +#define MS_APP_CENTER_BASE_DOMAIN @"com.Microsoft.AppCenter." + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Domain + +static NSString *const kMSACErrorDomain = MS_APP_CENTER_BASE_DOMAIN @"ErrorDomain"; + +#pragma mark - General + +// Error codes. +NS_ENUM(NSInteger){MSACLogInvalidContainerErrorCode = 1, MSACCanceledErrorCode = 2, MSACDisabledErrorCode = 3}; + +// Error descriptions. +static NSString const *kMSACLogInvalidContainerErrorDesc = @"Invalid log container."; +static NSString const *kMSACCanceledErrorDesc = @"The operation was canceled."; +static NSString const *kMSACDisabledErrorDesc = @"The service is disabled."; + +#pragma mark - Connection + +// Error codes. +NS_ENUM(NSInteger){MSACConnectionPausedErrorCode = 100, MSACConnectionHttpErrorCode = 101}; + +// Error descriptions. +static NSString const *kMSACConnectionHttpErrorDesc = @"An HTTP error occured."; +static NSString const *kMSACConnectionPausedErrorDesc = @"Canceled, connection paused with log deletion."; + +// Error user info keys. +static NSString const *kMSACConnectionHttpCodeErrorKey = @"MSACConnectionHttpCode"; + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSChannelGroupProtocol.h b/submodules/AppCenter/AppCenter.framework/Headers/MSChannelGroupProtocol.h new file mode 100644 index 0000000000..b786ff018a --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSChannelGroupProtocol.h @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_GROUP_PROTOCOL_H +#define MS_CHANNEL_GROUP_PROTOCOL_H + +#import + +#import "MSChannelProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSChannelUnitConfiguration; + +@protocol MSIngestionProtocol; +@protocol MSChannelUnitProtocol; + +/** + * `MSChannelGroupProtocol` represents a kind of channel that contains constituent MSChannelUnit objects. When an operation from the + * `MSChannelProtocol` is performed on the group, that operation should be propagated to its constituent MSChannelUnit objects. + */ +@protocol MSChannelGroupProtocol + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration; + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * @param ingestion The alternative ingestion object + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration + withIngestion:(nullable id)ingestion; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl base URL to use for backend communication. + */ +- (void)setLogUrl:(NSString *)logUrl; + +/** + * Set the app secret. + * + * @param appSecret The app secret. + */ +- (void)setAppSecret:(NSString *)appSecret; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. + * + * @discussion The default maximum database size is 10485760 bytes (10 MiB). + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 24576 bytes (24 KiB) will be ignored. + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. + */ +- (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler; + +/** + * Return a channel unit instance for the given groupId. + * + * @param groupId The group ID for a channel unit. + * + * @return A channel unit instance or `nil`. + */ +- (id)channelUnitForGroupId:(NSString *)groupId; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSChannelProtocol.h b/submodules/AppCenter/AppCenter.framework/Headers/MSChannelProtocol.h new file mode 100644 index 0000000000..4c50baaba7 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSChannelProtocol.h @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_PROTOCOL_H +#define MS_CHANNEL_PROTOCOL_H + +#import + +#import "MSEnable.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MSChannelDelegate; + +/** + * `MSChannelProtocol` contains the essential operations of a channel. Channels are broadly responsible for enqueuing logs to be sent to the + * backend and/or stored on disk. + */ +@protocol MSChannelProtocol + +/** + * Add delegate. + * + * @param delegate delegate. + */ +- (void)addDelegate:(id)delegate; + +/** + * Remove delegate. + * + * @param delegate delegate. + */ +- (void)removeDelegate:(id)delegate; + +/** + * Pause operations, logs will be stored but not sent. + * + * @param identifyingObject Object used to identify the pause request. + * + * @discussion A paused channel doesn't forward logs to the ingestion. The identifying object used to pause the channel can be any unique + * object. The same identifying object must be used to call resume. For simplicity if the caller is the one owning the channel then @c self + * can be used as identifying object. + * + * @see resumeWithIdentifyingObject: + */ +- (void)pauseWithIdentifyingObject:(id)identifyingObject; + +/** + * Resume operations, logs can be sent again. + * + * @param identifyingObject Object used to passed to the pause method. + * + * @discussion The channel only resume when all the outstanding identifying objects have been resumed. + * + * @see pauseWithIdentifyingObject: + */ +- (void)resumeWithIdentifyingObject:(id)identifyingObject; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSConstants.h b/submodules/AppCenter/AppCenter.framework/Headers/MSConstants.h new file mode 100644 index 0000000000..047c9f7112 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSConstants.h @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * Log Levels + */ +typedef NS_ENUM(NSUInteger, MSLogLevel) { + + /** + * Logging will be very chatty + */ + MSLogLevelVerbose = 2, + + /** + * Debug information will be logged + */ + MSLogLevelDebug = 3, + + /** + * Information will be logged + */ + MSLogLevelInfo = 4, + + /** + * Errors and warnings will be logged + */ + MSLogLevelWarning = 5, + + /** + * Errors will be logged + */ + MSLogLevelError = 6, + + /** + * Only critical errors will be logged + */ + MSLogLevelAssert = 7, + + /** + * Logging is disabled + */ + MSLogLevelNone = 99 +}; + +typedef NSString * (^MSLogMessageProvider)(void); +typedef void (^MSLogHandler)(MSLogMessageProvider messageProvider, MSLogLevel logLevel, NSString *tag, const char *file, + const char *function, uint line); + +/** + * Channel priorities, check the kMSPriorityCount if you add a new value. + * The order matters here! Values NEED to range from low priority to high priority. + */ +typedef NS_ENUM(NSInteger, MSPriority) { MSPriorityBackground, MSPriorityDefault, MSPriorityHigh }; +static short const kMSPriorityCount = MSPriorityHigh + 1; + +/** + * The priority by which the modules are initialized. + * MSPriorityMax is reserved for only 1 module and this needs to be Crashes. + * Crashes needs to be initialized first to catch crashes in our other SDK Modules (which will hopefully never happen) and to avoid losing + * any log at crash time. + */ +typedef NS_ENUM(NSInteger, MSInitializationPriority) { + MSInitializationPriorityDefault = 500, + MSInitializationPriorityHigh = 750, + MSInitializationPriorityMax = 999 +}; + +/** + * Enum with the different HTTP status codes. + */ +typedef NS_ENUM(NSInteger, MSHTTPCodesNo) { + + // Invalid + MSHTTPCodesNo0XXInvalidUnknown = 0, + + // Informational + MSHTTPCodesNo1XXInformationalUnknown = 1, + MSHTTPCodesNo100Continue = 100, + MSHTTPCodesNo101SwitchingProtocols = 101, + MSHTTPCodesNo102Processing = 102, + + // Success + MSHTTPCodesNo2XXSuccessUnknown = 2, + MSHTTPCodesNo200OK = 200, + MSHTTPCodesNo201Created = 201, + MSHTTPCodesNo202Accepted = 202, + MSHTTPCodesNo203NonAuthoritativeInformation = 203, + MSHTTPCodesNo204NoContent = 204, + MSHTTPCodesNo205ResetContent = 205, + MSHTTPCodesNo206PartialContent = 206, + MSHTTPCodesNo207MultiStatus = 207, + MSHTTPCodesNo208AlreadyReported = 208, + MSHTTPCodesNo209IMUsed = 209, + + // Redirection + MSHTTPCodesNo3XXSuccessUnknown = 3, + MSHTTPCodesNo300MultipleChoices = 300, + MSHTTPCodesNo301MovedPermanently = 301, + MSHTTPCodesNo302Found = 302, + MSHTTPCodesNo303SeeOther = 303, + MSHTTPCodesNo304NotModified = 304, + MSHTTPCodesNo305UseProxy = 305, + MSHTTPCodesNo306SwitchProxy = 306, + MSHTTPCodesNo307TemporaryRedirect = 307, + MSHTTPCodesNo308PermanentRedirect = 308, + + // Client error + MSHTTPCodesNo4XXSuccessUnknown = 4, + MSHTTPCodesNo400BadRequest = 400, + MSHTTPCodesNo401Unauthorised = 401, + MSHTTPCodesNo402PaymentRequired = 402, + MSHTTPCodesNo403Forbidden = 403, + MSHTTPCodesNo404NotFound = 404, + MSHTTPCodesNo405MethodNotAllowed = 405, + MSHTTPCodesNo406NotAcceptable = 406, + MSHTTPCodesNo407ProxyAuthenticationRequired = 407, + MSHTTPCodesNo408RequestTimeout = 408, + MSHTTPCodesNo409Conflict = 409, + MSHTTPCodesNo410Gone = 410, + MSHTTPCodesNo411LengthRequired = 411, + MSHTTPCodesNo412PreconditionFailed = 412, + MSHTTPCodesNo413RequestEntityTooLarge = 413, + MSHTTPCodesNo414RequestURITooLong = 414, + MSHTTPCodesNo415UnsupportedMediaType = 415, + MSHTTPCodesNo416RequestedRangeNotSatisfiable = 416, + MSHTTPCodesNo417ExpectationFailed = 417, + MSHTTPCodesNo418IamATeapot = 418, + MSHTTPCodesNo419AuthenticationTimeout = 419, + MSHTTPCodesNo420MethodFailureSpringFramework = 420, + MSHTTPCodesNo420EnhanceYourCalmTwitter = 4200, + MSHTTPCodesNo422UnprocessableEntity = 422, + MSHTTPCodesNo423Locked = 423, + MSHTTPCodesNo424FailedDependency = 424, + MSHTTPCodesNo424MethodFailureWebDaw = 4240, + MSHTTPCodesNo425UnorderedCollection = 425, + MSHTTPCodesNo426UpgradeRequired = 426, + MSHTTPCodesNo428PreconditionRequired = 428, + MSHTTPCodesNo429TooManyRequests = 429, + MSHTTPCodesNo431RequestHeaderFieldsTooLarge = 431, + MSHTTPCodesNo444NoResponseNginx = 444, + MSHTTPCodesNo449RetryWithMicrosoft = 449, + MSHTTPCodesNo450BlockedByWindowsParentalControls = 450, + MSHTTPCodesNo451RedirectMicrosoft = 451, + MSHTTPCodesNo451UnavailableForLegalReasons = 4510, + MSHTTPCodesNo494RequestHeaderTooLargeNginx = 494, + MSHTTPCodesNo495CertErrorNginx = 495, + MSHTTPCodesNo496NoCertNginx = 496, + MSHTTPCodesNo497HTTPToHTTPSNginx = 497, + MSHTTPCodesNo499ClientClosedRequestNginx = 499, + + // Server error + MSHTTPCodesNo5XXSuccessUnknown = 5, + MSHTTPCodesNo500InternalServerError = 500, + MSHTTPCodesNo501NotImplemented = 501, + MSHTTPCodesNo502BadGateway = 502, + MSHTTPCodesNo503ServiceUnavailable = 503, + MSHTTPCodesNo504GatewayTimeout = 504, + MSHTTPCodesNo505HTTPVersionNotSupported = 505, + MSHTTPCodesNo506VariantAlsoNegotiates = 506, + MSHTTPCodesNo507InsufficientStorage = 507, + MSHTTPCodesNo508LoopDetected = 508, + MSHTTPCodesNo509BandwidthLimitExceeded = 509, + MSHTTPCodesNo510NotExtended = 510, + MSHTTPCodesNo511NetworkAuthenticationRequired = 511, + MSHTTPCodesNo522ConnectionTimedOut = 522, + MSHTTPCodesNo598NetworkReadTimeoutErrorUnknown = 598, + MSHTTPCodesNo599NetworkConnectTimeoutErrorUnknown = 599 +}; diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSCustomProperties.h b/submodules/AppCenter/AppCenter.framework/Headers/MSCustomProperties.h new file mode 100644 index 0000000000..34b202e258 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSCustomProperties.h @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CUSTOM_PROPERTIES_H +#define MS_CUSTOM_PROPERTIES_H + +#import + +/** + * Custom properties builder. + * Collects multiple properties to send in one log. + */ +@interface MSCustomProperties : NSObject + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setString:(NSString *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setNumber:(NSNumber *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setBool:(BOOL)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key; + +/** + * Clear the property for the specified key. + * + * @param key Key whose mapping is to be cleared. + * + * @return This instance. + */ +- (instancetype)clearPropertyForKey:(NSString *)key; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSDevice.h b/submodules/AppCenter/AppCenter.framework/Headers/MSDevice.h new file mode 100644 index 0000000000..bca52140f0 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSDevice.h @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_DEVICE_H +#define MS_DEVICE_H + +#import + +#import "MSWrapperSdk.h" + +@interface MSDevice : MSWrapperSdk + +/* + * Name of the SDK. Consists of the name of the SDK and the platform, e.g. "appcenter.ios", "appcenter.android" + */ +@property(nonatomic, copy, readonly) NSString *sdkName; + +/* + * Version of the SDK in semver format, e.g. "1.2.0" or "0.12.3-alpha.1". + */ +@property(nonatomic, copy, readonly) NSString *sdkVersion; + +/* + * Device model (example: iPad2,3). + */ +@property(nonatomic, copy, readonly) NSString *model; + +/* + * Device manufacturer (example: HTC). + */ +@property(nonatomic, copy, readonly) NSString *oemName; + +/* + * OS name (example: iOS). + */ +@property(nonatomic, copy, readonly) NSString *osName; + +/* + * OS version (example: 9.3.0). + */ +@property(nonatomic, copy, readonly) NSString *osVersion; + +/* + * OS build code (example: LMY47X). [optional] + */ +@property(nonatomic, copy, readonly) NSString *osBuild; + +/* + * API level when applicable like in Android (example: 15). [optional] + */ +@property(nonatomic, copy, readonly) NSNumber *osApiLevel; + +/* + * Language code (example: en_US). + */ +@property(nonatomic, copy, readonly) NSString *locale; + +/* + * The offset in minutes from UTC for the device time zone, including daylight savings time. + */ +@property(nonatomic, readonly, strong) NSNumber *timeZoneOffset; + +/* + * Screen size of the device in pixels (example: 640x480). + */ +@property(nonatomic, copy, readonly) NSString *screenSize; + +/* + * Application version name, e.g. 1.1.0 + */ +@property(nonatomic, copy, readonly) NSString *appVersion; + +/* + * Carrier name (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierName; + +/* + * Carrier country code (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierCountry; + +/* + * The app's build number, e.g. 42. + */ +@property(nonatomic, copy, readonly) NSString *appBuild; + +/* + * The bundle identifier, package identifier, or namespace, depending on what the individual plattforms use, .e.g com.microsoft.example. + * [optional] + */ +@property(nonatomic, copy, readonly) NSString *appNamespace; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSEnable.h b/submodules/AppCenter/AppCenter.framework/Headers/MSEnable.h new file mode 100644 index 0000000000..11e2f60579 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSEnable.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ENABLE_H +#define MS_ENABLE_H + +#import + +/** + * Protocol to define an instance that can be enabled/disabled. + */ +@protocol MSEnable + +@required + +/** + * Enable/disable this instance and delete data on disabled state. + * + * @param isEnabled A boolean value set to YES to enable the instance or NO to disable it. + * @param deleteData A boolean value set to YES to delete data or NO to keep it. + */ +- (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSLog.h b/submodules/AppCenter/AppCenter.framework/Headers/MSLog.h new file mode 100644 index 0000000000..07f1e574b2 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSLog.h @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_H +#define MS_LOG_H + +#import + +@class MSDevice; + +@protocol MSLog + +/** + * Log type. + */ +@property(nonatomic, copy) NSString *type; + +/** + * Log timestamp. + */ +@property(nonatomic, strong) NSDate *timestamp; + +/** + * A session identifier is used to correlate logs together. A session is an abstract concept in the API and is not necessarily an analytics + * session, it can be used to only track crashes. + */ +@property(nonatomic, copy) NSString *sid; + +/** + * Optional distribution group ID value. + */ +@property(nonatomic, copy) NSString *distributionGroupId; + +/** + * Optional user identifier. + */ +@property(nonatomic, copy) NSString *userId; + +/** + * Device properties associated to this log. + */ +@property(nonatomic, strong) MSDevice *device; + +/** + * Transient object tag. For example, a log can be tagged with a transmission target. We do this currently to prevent properties being + * applied retroactively to previous logs by comparing their tags. + */ +@property(nonatomic, strong) NSObject *tag; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +/** + * Adds a transmission target token that this log should be sent to. + * + * @param token The transmission target token. + */ +- (void)addTransmissionTargetToken:(NSString *)token; + +/** + * Gets all transmission target tokens that this log should be sent to. + * + * @returns Collection of transmission target tokens that this log should be sent to. + */ +- (NSSet *)transmissionTargetTokens; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSLogWithProperties.h b/submodules/AppCenter/AppCenter.framework/Headers/MSLogWithProperties.h new file mode 100644 index 0000000000..7a3b372da5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSLogWithProperties.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_WITH_PROPERTIES_H +#define MS_LOG_WITH_PROPERTIES_H + +#import + +#import "MSAbstractLog.h" + +@interface MSLogWithProperties : MSAbstractLog + +/** + * Additional key/value pair parameters. [optional] + */ +@property(nonatomic, strong) NSDictionary *properties; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSLogger.h b/submodules/AppCenter/AppCenter.framework/Headers/MSLogger.h new file mode 100644 index 0000000000..7eb5c3bebb --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSLogger.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +#define MSLog(_level, _tag, _message) \ + [MSLogger logMessage:_message level:_level tag:_tag file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__] +#define MSLogAssert(tag, format, ...) \ + MSLog(MSLogLevelAssert, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogError(tag, format, ...) \ + MSLog(MSLogLevelError, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogWarning(tag, format, ...) \ + MSLog(MSLogLevelWarning, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogInfo(tag, format, ...) \ + MSLog(MSLogLevelInfo, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogDebug(tag, format, ...) \ + MSLog(MSLogLevelDebug, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogVerbose(tag, format, ...) \ + MSLog(MSLogLevelVerbose, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) + +@interface MSLogger : NSObject + ++ (void)logMessage:(MSLogMessageProvider)messageProvider + level:(MSLogLevel)loglevel + tag:(NSString *)tag + file:(const char *)file + function:(const char *)function + line:(uint)line; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSService.h b/submodules/AppCenter/AppCenter.framework/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenter.framework/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperLogger.h b/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperLogger.h new file mode 100644 index 0000000000..1e17ff1324 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperLogger.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +/** + * This is a utility for producing App Center style log messages. It is only intended for use by App Center services and wrapper SDKs of App + * Center. + */ +@interface MSWrapperLogger : NSObject + ++ (void)MSWrapperLog:(MSLogMessageProvider)message tag:(NSString *)tag level:(MSLogLevel)level; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperSdk.h b/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperSdk.h new file mode 100644 index 0000000000..647ba7b0fe --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Headers/MSWrapperSdk.h @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_WRAPPER_SDK_H +#define MS_WRAPPER_SDK_H + +#import + +@interface MSWrapperSdk : NSObject + +/* + * Version of the wrapper SDK. When the SDK is embedding another base SDK (for example Xamarin.Android wraps Android), the Xamarin specific + * version is populated into this field while sdkVersion refers to the original Android SDK. [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkVersion; + +/* + * Name of the wrapper SDK (examples: Xamarin, Cordova). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkName; + +/* + * Version of the wrapper technology framework (Xamarin runtime version or ReactNative or Cordova etc...). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperRuntimeVersion; + +/* + * Label that is used to identify application code 'version' released via Live Update beacon running on device. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateReleaseLabel; + +/* + * Identifier of environment that current application release belongs to, deployment key then maps to environment like Production, Staging. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateDeploymentKey; + +/* + * Hash of all files (ReactNative or Cordova) deployed to device via LiveUpdate beacon. Helps identify the Release version on device or need + * to download updates in future + */ +@property(nonatomic, copy, readonly) NSString *liveUpdatePackageHash; + +- (instancetype)initWithWrapperSdkVersion:(NSString *)wrapperSdkVersion + wrapperSdkName:(NSString *)wrapperSdkName + wrapperRuntimeVersion:(NSString *)wrapperRuntimeVersion + liveUpdateReleaseLabel:(NSString *)liveUpdateReleaseLabel + liveUpdateDeploymentKey:(NSString *)liveUpdateDeploymentKey + liveUpdatePackageHash:(NSString *)liveUpdatePackageHash; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Modules/module.modulemap b/submodules/AppCenter/AppCenter.framework/Modules/module.modulemap new file mode 100644 index 0000000000..32c35bdba8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Modules/module.modulemap @@ -0,0 +1,12 @@ +framework module AppCenter { + umbrella header "AppCenter.h" + + export * + module * { export * } + + link framework "Foundation" + link framework "SystemConfiguration" + link framework "AppKit" + link "sqlite3" + link "z" +} diff --git a/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSChannelDelegate.h b/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSChannelDelegate.h new file mode 100644 index 0000000000..f1985b23a8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSChannelDelegate.h @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants+Flags.h" + +@protocol MSChannelUnitProtocol; +@protocol MSChannelGroupProtocol; +@protocol MSChannelProtocol; +@protocol MSLog; + +@protocol MSChannelDelegate + +@optional + +/** + * A callback that is called when a channel unit is added to the channel group. + * + * @param channelGroup The channel group. + * @param channel The newly added channel. + */ +- (void)channelGroup:(id)channelGroup didAddChannelUnit:(id)channel; + +/** + * A callback that is called when a log is just enqueued. Delegates may want to prepare the log a little more before further processing. + * + * @param log The log to prepare. + */ +- (void)channel:(id)channel prepareLog:(id)log; + +/** + * A callback that is called after a log is definitely prepared. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + * @param flags Options for the log. + */ +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags; + +/** + * A callback that is called after a log completed the enqueueing process whether it was successful or not. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + */ +- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId; + +/** + * Callback method that will be called before each log will be send to the server. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel willSendLog:(id)log; + +/** + * Callback method that will be called in case the SDK was able to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel didSucceedSendingLog:(id)log; + +/** + * Callback method that will be called in case the SDK was unable to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + * @param error The error that occured. + */ +- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error; + +/** + * A callback that is called when setEnabled has been invoked. + * + * @param channel The channel. + * @param isEnabled The boolean that indicates enabled. + * @param deletedData The boolean that indicates deleting data on disabled. + */ +- (void)channel:(id)channel didSetEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deletedData; + +/** + * A callback that is called when pause has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to pause the channel. + */ +- (void)channel:(id)channel didPauseWithIdentifyingObject:(id)identifyingObject; + +/** + * A callback that is called when resume has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to resume the channel. + */ +- (void)channel:(id)channel didResumeWithIdentifyingObject:(id)identifyingObject; + +/** + * Callback method that will determine if a log should be filtered out from the usual processing pipeline. If any delegate returns true, the + * log is filtered. + * + * @param channelUnit The channel unit that is going to send the log. + * @param log The log to be filtered or not. + * + * @return `true` if the log should be filtered out. + */ +- (BOOL)channelUnit:(id)channelUnit shouldFilterLog:(id)log; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSConstants+Flags.h b/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSConstants+Flags.h new file mode 100644 index 0000000000..0634d16513 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/PrivateHeaders/MSConstants+Flags.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +typedef NS_OPTIONS(NSUInteger, MSFlags) { + MSFlagsNone = (0 << 0), // => 00000000 + MSFlagsNormal = (1 << 0), // => 00000001 + MSFlagsCritical = (1 << 1), // => 00000010 + MSFlagsPersistenceNormal DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsNormal") = MSFlagsNormal, + MSFlagsPersistenceCritical DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsCritical") = MSFlagsCritical, + MSFlagsDefault = MSFlagsNormal +}; diff --git a/submodules/AppCenter/AppCenter.framework/Resources/Info.plist b/submodules/AppCenter/AppCenter.framework/Resources/Info.plist new file mode 100644 index 0000000000..7db23d565d --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenter + CFBundleIdentifier + com.microsoft.appcenter + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenter + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/AppCenter b/submodules/AppCenter/AppCenter.framework/Versions/A/AppCenter new file mode 100644 index 0000000000..82fead5f89 Binary files /dev/null and b/submodules/AppCenter/AppCenter.framework/Versions/A/AppCenter differ diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/AppCenter.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/AppCenter.h new file mode 100644 index 0000000000..6ff9d8882c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/AppCenter.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" +#import "MSAppCenter.h" +#import "MSAppCenterErrors.h" +#import "MSChannelGroupProtocol.h" +#import "MSChannelProtocol.h" +#import "MSConstants.h" +#import "MSCustomProperties.h" +#import "MSDevice.h" +#import "MSEnable.h" +#import "MSLog.h" +#import "MSLogWithProperties.h" +#import "MSLogger.h" +#import "MSService.h" +#import "MSServiceAbstract.h" +#import "MSWrapperLogger.h" +#import "MSWrapperSdk.h" diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenter.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenter.h new file mode 100644 index 0000000000..c6ceb9ca90 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenter.h @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +@class MSWrapperSdk; + +#if !TARGET_OS_TV +@class MSCustomProperties; +#endif + +@interface MSAppCenter : NSObject + +/** + * Returns the singleton instance of MSAppCenter. + */ ++ (instancetype)sharedInstance; + +/** + * Configure the SDK with an application secret. + * + * @param appSecret A unique and secret key used to identify the application. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configureWithAppSecret:(NSString *)appSecret; + +/** + * Configure the SDK. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configure; + +/** + * Configure the SDK with an application secret and an array of services to start. + * + * @param appSecret A unique and secret key used to identify the application. + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)start:(NSString *)appSecret withServices:(NSArray *)services; + +/** + * Start the SDK with an array of services. + * + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)startWithServices:(NSArray *)services; + +/** + * Start a service. + * + * @param service A service to start. + * + * @discussion This may be called only once per service per application process lifetime. + */ ++ (void)startService:(Class)service; + +/** + * Configure the SDK with an array of services to start from a library. This will not start the service at application level, it will enable + * the service only for the library. + * + * @param services Array of services to start. + */ ++ (void)startFromLibraryWithServices:(NSArray *)services; + +/** + * Check whether the SDK has already been configured or not. + * + * @return YES if configured, NO otherwise. + */ ++ (BOOL)isConfigured; + +/** + * Check whether app is running in App Center Test Cloud. + * + * @return true if running in App Center Test Cloud, false otherwise. + */ ++ (BOOL)isRunningInAppCenterTestCloud; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl Base URL to use for backend communication. + */ ++ (void)setLogUrl:(NSString *)logUrl; + +/** + * Enable or disable the SDK as a whole. In addition to AppCenter resources, it will also enable or disable all registered services. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled YES to enable, NO to disable. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Check whether the SDK is enabled or not as a whole. + * + * @return YES if enabled, NO otherwise. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +/** + * Get log level. + * + * @return Log level. + */ ++ (MSLogLevel)logLevel; + +/** + * Set log level. + * + * @param logLevel The log level. + */ ++ (void)setLogLevel:(MSLogLevel)logLevel; + +/** + * Set log level handler. + * + * @param logHandler Handler. + */ ++ (void)setLogHandler:(MSLogHandler)logHandler; + +/** + * Set wrapper SDK information to use when building device properties. This is intended in case you are building a SDK that uses the App + * Center SDK under the hood, e.g. our Xamarin SDK or ReactNative SDk. + * + * @param wrapperSdk Wrapper SDK information. + */ ++ (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk; + +#if !TARGET_OS_TV +/** + * Set the custom properties. + * + * @param customProperties Custom properties object. + */ ++ (void)setCustomProperties:(MSCustomProperties *)customProperties; +#endif + +/** + * Check whether the application delegate forwarder is enabled or not. + * + * @return YES if enabled, NO otherwise. + * + * @discussion The application delegate forwarder forwards messages that target your application delegate methods via swizzling to the SDK. + * It simplifies the SDK integration but may not be suitable to any situations. For + * instance it should be disabled if you or one of your third party SDK is doing message forwarding on the application delegate. Message + * forwarding usually implies the implementation of @see NSObject#forwardingTargetForSelector: or @see NSObject#forwardInvocation: methods. + * To disable the application delegate forwarder just add the `AppCenterAppDelegateForwarderEnabled` tag to your Info .plist file and set it + * to `0`. Then you will have to forward any application delegate needed by the SDK manually. + */ ++ (BOOL)isAppDelegateForwarderEnabled; + +/** + * Get unique installation identifier. + * + * @return Unique installation identifier. + */ ++ (NSUUID *)installId; + +/** + * Detect if a debugger is attached to the app process. This is only invoked once on app startup and can not detect + * if the debugger is being attached during runtime! + * + * @return BOOL if the debugger is attached. + */ ++ (BOOL)isDebuggerAttached; + +/** + * Get the current version of AppCenter SDK. + * + * @return The current version of AppCenter SDK. + */ ++ (NSString *)sdkVersion; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. This method is only intended for + * applications. + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored. + * + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. This parameter can be null. + * + * @discussion This only sets the maximum size of the database, but App Center modules might store additional data. + * The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + */ ++ (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(void (^)(BOOL))completionHandler; + +/** + * Set the user identifier. + * + * @param userId User identifier. + * + * @discussion Set the user identifier for logs sent for the default target token when the secret passed in @c + * MSAppCenter:start:withServices: contains "target={targetToken}". + * + * For App Center backend the user identifier maximum length is 256 characters. + * + * AppCenter must be configured or started before this API can be used. + */ ++ (void)setUserId:(NSString *)userId; + +/** + * Set country code to use when building device properties. + * + * @param countryCode The two-letter ISO country code. @see https://www.iso.org/obp/ui/#search for more information. + */ ++ (void)setCountryCode:(NSString *)countryCode; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenterErrors.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenterErrors.h new file mode 100644 index 0000000000..e57cf9217f --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSAppCenterErrors.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_APP_CENTER_ERRORS_H +#define MS_APP_CENTER_ERRORS_H + +#import + +#define MS_APP_CENTER_BASE_DOMAIN @"com.Microsoft.AppCenter." + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Domain + +static NSString *const kMSACErrorDomain = MS_APP_CENTER_BASE_DOMAIN @"ErrorDomain"; + +#pragma mark - General + +// Error codes. +NS_ENUM(NSInteger){MSACLogInvalidContainerErrorCode = 1, MSACCanceledErrorCode = 2, MSACDisabledErrorCode = 3}; + +// Error descriptions. +static NSString const *kMSACLogInvalidContainerErrorDesc = @"Invalid log container."; +static NSString const *kMSACCanceledErrorDesc = @"The operation was canceled."; +static NSString const *kMSACDisabledErrorDesc = @"The service is disabled."; + +#pragma mark - Connection + +// Error codes. +NS_ENUM(NSInteger){MSACConnectionPausedErrorCode = 100, MSACConnectionHttpErrorCode = 101}; + +// Error descriptions. +static NSString const *kMSACConnectionHttpErrorDesc = @"An HTTP error occured."; +static NSString const *kMSACConnectionPausedErrorDesc = @"Canceled, connection paused with log deletion."; + +// Error user info keys. +static NSString const *kMSACConnectionHttpCodeErrorKey = @"MSACConnectionHttpCode"; + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelGroupProtocol.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelGroupProtocol.h new file mode 100644 index 0000000000..b786ff018a --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelGroupProtocol.h @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_GROUP_PROTOCOL_H +#define MS_CHANNEL_GROUP_PROTOCOL_H + +#import + +#import "MSChannelProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSChannelUnitConfiguration; + +@protocol MSIngestionProtocol; +@protocol MSChannelUnitProtocol; + +/** + * `MSChannelGroupProtocol` represents a kind of channel that contains constituent MSChannelUnit objects. When an operation from the + * `MSChannelProtocol` is performed on the group, that operation should be propagated to its constituent MSChannelUnit objects. + */ +@protocol MSChannelGroupProtocol + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration; + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * @param ingestion The alternative ingestion object + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration + withIngestion:(nullable id)ingestion; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl base URL to use for backend communication. + */ +- (void)setLogUrl:(NSString *)logUrl; + +/** + * Set the app secret. + * + * @param appSecret The app secret. + */ +- (void)setAppSecret:(NSString *)appSecret; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. + * + * @discussion The default maximum database size is 10485760 bytes (10 MiB). + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 24576 bytes (24 KiB) will be ignored. + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. + */ +- (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler; + +/** + * Return a channel unit instance for the given groupId. + * + * @param groupId The group ID for a channel unit. + * + * @return A channel unit instance or `nil`. + */ +- (id)channelUnitForGroupId:(NSString *)groupId; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelProtocol.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelProtocol.h new file mode 100644 index 0000000000..4c50baaba7 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSChannelProtocol.h @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_PROTOCOL_H +#define MS_CHANNEL_PROTOCOL_H + +#import + +#import "MSEnable.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MSChannelDelegate; + +/** + * `MSChannelProtocol` contains the essential operations of a channel. Channels are broadly responsible for enqueuing logs to be sent to the + * backend and/or stored on disk. + */ +@protocol MSChannelProtocol + +/** + * Add delegate. + * + * @param delegate delegate. + */ +- (void)addDelegate:(id)delegate; + +/** + * Remove delegate. + * + * @param delegate delegate. + */ +- (void)removeDelegate:(id)delegate; + +/** + * Pause operations, logs will be stored but not sent. + * + * @param identifyingObject Object used to identify the pause request. + * + * @discussion A paused channel doesn't forward logs to the ingestion. The identifying object used to pause the channel can be any unique + * object. The same identifying object must be used to call resume. For simplicity if the caller is the one owning the channel then @c self + * can be used as identifying object. + * + * @see resumeWithIdentifyingObject: + */ +- (void)pauseWithIdentifyingObject:(id)identifyingObject; + +/** + * Resume operations, logs can be sent again. + * + * @param identifyingObject Object used to passed to the pause method. + * + * @discussion The channel only resume when all the outstanding identifying objects have been resumed. + * + * @see pauseWithIdentifyingObject: + */ +- (void)resumeWithIdentifyingObject:(id)identifyingObject; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSConstants.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSConstants.h new file mode 100644 index 0000000000..047c9f7112 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSConstants.h @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * Log Levels + */ +typedef NS_ENUM(NSUInteger, MSLogLevel) { + + /** + * Logging will be very chatty + */ + MSLogLevelVerbose = 2, + + /** + * Debug information will be logged + */ + MSLogLevelDebug = 3, + + /** + * Information will be logged + */ + MSLogLevelInfo = 4, + + /** + * Errors and warnings will be logged + */ + MSLogLevelWarning = 5, + + /** + * Errors will be logged + */ + MSLogLevelError = 6, + + /** + * Only critical errors will be logged + */ + MSLogLevelAssert = 7, + + /** + * Logging is disabled + */ + MSLogLevelNone = 99 +}; + +typedef NSString * (^MSLogMessageProvider)(void); +typedef void (^MSLogHandler)(MSLogMessageProvider messageProvider, MSLogLevel logLevel, NSString *tag, const char *file, + const char *function, uint line); + +/** + * Channel priorities, check the kMSPriorityCount if you add a new value. + * The order matters here! Values NEED to range from low priority to high priority. + */ +typedef NS_ENUM(NSInteger, MSPriority) { MSPriorityBackground, MSPriorityDefault, MSPriorityHigh }; +static short const kMSPriorityCount = MSPriorityHigh + 1; + +/** + * The priority by which the modules are initialized. + * MSPriorityMax is reserved for only 1 module and this needs to be Crashes. + * Crashes needs to be initialized first to catch crashes in our other SDK Modules (which will hopefully never happen) and to avoid losing + * any log at crash time. + */ +typedef NS_ENUM(NSInteger, MSInitializationPriority) { + MSInitializationPriorityDefault = 500, + MSInitializationPriorityHigh = 750, + MSInitializationPriorityMax = 999 +}; + +/** + * Enum with the different HTTP status codes. + */ +typedef NS_ENUM(NSInteger, MSHTTPCodesNo) { + + // Invalid + MSHTTPCodesNo0XXInvalidUnknown = 0, + + // Informational + MSHTTPCodesNo1XXInformationalUnknown = 1, + MSHTTPCodesNo100Continue = 100, + MSHTTPCodesNo101SwitchingProtocols = 101, + MSHTTPCodesNo102Processing = 102, + + // Success + MSHTTPCodesNo2XXSuccessUnknown = 2, + MSHTTPCodesNo200OK = 200, + MSHTTPCodesNo201Created = 201, + MSHTTPCodesNo202Accepted = 202, + MSHTTPCodesNo203NonAuthoritativeInformation = 203, + MSHTTPCodesNo204NoContent = 204, + MSHTTPCodesNo205ResetContent = 205, + MSHTTPCodesNo206PartialContent = 206, + MSHTTPCodesNo207MultiStatus = 207, + MSHTTPCodesNo208AlreadyReported = 208, + MSHTTPCodesNo209IMUsed = 209, + + // Redirection + MSHTTPCodesNo3XXSuccessUnknown = 3, + MSHTTPCodesNo300MultipleChoices = 300, + MSHTTPCodesNo301MovedPermanently = 301, + MSHTTPCodesNo302Found = 302, + MSHTTPCodesNo303SeeOther = 303, + MSHTTPCodesNo304NotModified = 304, + MSHTTPCodesNo305UseProxy = 305, + MSHTTPCodesNo306SwitchProxy = 306, + MSHTTPCodesNo307TemporaryRedirect = 307, + MSHTTPCodesNo308PermanentRedirect = 308, + + // Client error + MSHTTPCodesNo4XXSuccessUnknown = 4, + MSHTTPCodesNo400BadRequest = 400, + MSHTTPCodesNo401Unauthorised = 401, + MSHTTPCodesNo402PaymentRequired = 402, + MSHTTPCodesNo403Forbidden = 403, + MSHTTPCodesNo404NotFound = 404, + MSHTTPCodesNo405MethodNotAllowed = 405, + MSHTTPCodesNo406NotAcceptable = 406, + MSHTTPCodesNo407ProxyAuthenticationRequired = 407, + MSHTTPCodesNo408RequestTimeout = 408, + MSHTTPCodesNo409Conflict = 409, + MSHTTPCodesNo410Gone = 410, + MSHTTPCodesNo411LengthRequired = 411, + MSHTTPCodesNo412PreconditionFailed = 412, + MSHTTPCodesNo413RequestEntityTooLarge = 413, + MSHTTPCodesNo414RequestURITooLong = 414, + MSHTTPCodesNo415UnsupportedMediaType = 415, + MSHTTPCodesNo416RequestedRangeNotSatisfiable = 416, + MSHTTPCodesNo417ExpectationFailed = 417, + MSHTTPCodesNo418IamATeapot = 418, + MSHTTPCodesNo419AuthenticationTimeout = 419, + MSHTTPCodesNo420MethodFailureSpringFramework = 420, + MSHTTPCodesNo420EnhanceYourCalmTwitter = 4200, + MSHTTPCodesNo422UnprocessableEntity = 422, + MSHTTPCodesNo423Locked = 423, + MSHTTPCodesNo424FailedDependency = 424, + MSHTTPCodesNo424MethodFailureWebDaw = 4240, + MSHTTPCodesNo425UnorderedCollection = 425, + MSHTTPCodesNo426UpgradeRequired = 426, + MSHTTPCodesNo428PreconditionRequired = 428, + MSHTTPCodesNo429TooManyRequests = 429, + MSHTTPCodesNo431RequestHeaderFieldsTooLarge = 431, + MSHTTPCodesNo444NoResponseNginx = 444, + MSHTTPCodesNo449RetryWithMicrosoft = 449, + MSHTTPCodesNo450BlockedByWindowsParentalControls = 450, + MSHTTPCodesNo451RedirectMicrosoft = 451, + MSHTTPCodesNo451UnavailableForLegalReasons = 4510, + MSHTTPCodesNo494RequestHeaderTooLargeNginx = 494, + MSHTTPCodesNo495CertErrorNginx = 495, + MSHTTPCodesNo496NoCertNginx = 496, + MSHTTPCodesNo497HTTPToHTTPSNginx = 497, + MSHTTPCodesNo499ClientClosedRequestNginx = 499, + + // Server error + MSHTTPCodesNo5XXSuccessUnknown = 5, + MSHTTPCodesNo500InternalServerError = 500, + MSHTTPCodesNo501NotImplemented = 501, + MSHTTPCodesNo502BadGateway = 502, + MSHTTPCodesNo503ServiceUnavailable = 503, + MSHTTPCodesNo504GatewayTimeout = 504, + MSHTTPCodesNo505HTTPVersionNotSupported = 505, + MSHTTPCodesNo506VariantAlsoNegotiates = 506, + MSHTTPCodesNo507InsufficientStorage = 507, + MSHTTPCodesNo508LoopDetected = 508, + MSHTTPCodesNo509BandwidthLimitExceeded = 509, + MSHTTPCodesNo510NotExtended = 510, + MSHTTPCodesNo511NetworkAuthenticationRequired = 511, + MSHTTPCodesNo522ConnectionTimedOut = 522, + MSHTTPCodesNo598NetworkReadTimeoutErrorUnknown = 598, + MSHTTPCodesNo599NetworkConnectTimeoutErrorUnknown = 599 +}; diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSCustomProperties.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSCustomProperties.h new file mode 100644 index 0000000000..34b202e258 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSCustomProperties.h @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CUSTOM_PROPERTIES_H +#define MS_CUSTOM_PROPERTIES_H + +#import + +/** + * Custom properties builder. + * Collects multiple properties to send in one log. + */ +@interface MSCustomProperties : NSObject + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setString:(NSString *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setNumber:(NSNumber *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setBool:(BOOL)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key; + +/** + * Clear the property for the specified key. + * + * @param key Key whose mapping is to be cleared. + * + * @return This instance. + */ +- (instancetype)clearPropertyForKey:(NSString *)key; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSDevice.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSDevice.h new file mode 100644 index 0000000000..bca52140f0 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSDevice.h @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_DEVICE_H +#define MS_DEVICE_H + +#import + +#import "MSWrapperSdk.h" + +@interface MSDevice : MSWrapperSdk + +/* + * Name of the SDK. Consists of the name of the SDK and the platform, e.g. "appcenter.ios", "appcenter.android" + */ +@property(nonatomic, copy, readonly) NSString *sdkName; + +/* + * Version of the SDK in semver format, e.g. "1.2.0" or "0.12.3-alpha.1". + */ +@property(nonatomic, copy, readonly) NSString *sdkVersion; + +/* + * Device model (example: iPad2,3). + */ +@property(nonatomic, copy, readonly) NSString *model; + +/* + * Device manufacturer (example: HTC). + */ +@property(nonatomic, copy, readonly) NSString *oemName; + +/* + * OS name (example: iOS). + */ +@property(nonatomic, copy, readonly) NSString *osName; + +/* + * OS version (example: 9.3.0). + */ +@property(nonatomic, copy, readonly) NSString *osVersion; + +/* + * OS build code (example: LMY47X). [optional] + */ +@property(nonatomic, copy, readonly) NSString *osBuild; + +/* + * API level when applicable like in Android (example: 15). [optional] + */ +@property(nonatomic, copy, readonly) NSNumber *osApiLevel; + +/* + * Language code (example: en_US). + */ +@property(nonatomic, copy, readonly) NSString *locale; + +/* + * The offset in minutes from UTC for the device time zone, including daylight savings time. + */ +@property(nonatomic, readonly, strong) NSNumber *timeZoneOffset; + +/* + * Screen size of the device in pixels (example: 640x480). + */ +@property(nonatomic, copy, readonly) NSString *screenSize; + +/* + * Application version name, e.g. 1.1.0 + */ +@property(nonatomic, copy, readonly) NSString *appVersion; + +/* + * Carrier name (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierName; + +/* + * Carrier country code (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierCountry; + +/* + * The app's build number, e.g. 42. + */ +@property(nonatomic, copy, readonly) NSString *appBuild; + +/* + * The bundle identifier, package identifier, or namespace, depending on what the individual plattforms use, .e.g com.microsoft.example. + * [optional] + */ +@property(nonatomic, copy, readonly) NSString *appNamespace; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSEnable.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSEnable.h new file mode 100644 index 0000000000..11e2f60579 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSEnable.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ENABLE_H +#define MS_ENABLE_H + +#import + +/** + * Protocol to define an instance that can be enabled/disabled. + */ +@protocol MSEnable + +@required + +/** + * Enable/disable this instance and delete data on disabled state. + * + * @param isEnabled A boolean value set to YES to enable the instance or NO to disable it. + * @param deleteData A boolean value set to YES to delete data or NO to keep it. + */ +- (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLog.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLog.h new file mode 100644 index 0000000000..07f1e574b2 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLog.h @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_H +#define MS_LOG_H + +#import + +@class MSDevice; + +@protocol MSLog + +/** + * Log type. + */ +@property(nonatomic, copy) NSString *type; + +/** + * Log timestamp. + */ +@property(nonatomic, strong) NSDate *timestamp; + +/** + * A session identifier is used to correlate logs together. A session is an abstract concept in the API and is not necessarily an analytics + * session, it can be used to only track crashes. + */ +@property(nonatomic, copy) NSString *sid; + +/** + * Optional distribution group ID value. + */ +@property(nonatomic, copy) NSString *distributionGroupId; + +/** + * Optional user identifier. + */ +@property(nonatomic, copy) NSString *userId; + +/** + * Device properties associated to this log. + */ +@property(nonatomic, strong) MSDevice *device; + +/** + * Transient object tag. For example, a log can be tagged with a transmission target. We do this currently to prevent properties being + * applied retroactively to previous logs by comparing their tags. + */ +@property(nonatomic, strong) NSObject *tag; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +/** + * Adds a transmission target token that this log should be sent to. + * + * @param token The transmission target token. + */ +- (void)addTransmissionTargetToken:(NSString *)token; + +/** + * Gets all transmission target tokens that this log should be sent to. + * + * @returns Collection of transmission target tokens that this log should be sent to. + */ +- (NSSet *)transmissionTargetTokens; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogWithProperties.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogWithProperties.h new file mode 100644 index 0000000000..7a3b372da5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogWithProperties.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_WITH_PROPERTIES_H +#define MS_LOG_WITH_PROPERTIES_H + +#import + +#import "MSAbstractLog.h" + +@interface MSLogWithProperties : MSAbstractLog + +/** + * Additional key/value pair parameters. [optional] + */ +@property(nonatomic, strong) NSDictionary *properties; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogger.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogger.h new file mode 100644 index 0000000000..7eb5c3bebb --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSLogger.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +#define MSLog(_level, _tag, _message) \ + [MSLogger logMessage:_message level:_level tag:_tag file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__] +#define MSLogAssert(tag, format, ...) \ + MSLog(MSLogLevelAssert, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogError(tag, format, ...) \ + MSLog(MSLogLevelError, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogWarning(tag, format, ...) \ + MSLog(MSLogLevelWarning, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogInfo(tag, format, ...) \ + MSLog(MSLogLevelInfo, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogDebug(tag, format, ...) \ + MSLog(MSLogLevelDebug, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogVerbose(tag, format, ...) \ + MSLog(MSLogLevelVerbose, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) + +@interface MSLogger : NSObject + ++ (void)logMessage:(MSLogMessageProvider)messageProvider + level:(MSLogLevel)loglevel + tag:(NSString *)tag + file:(const char *)file + function:(const char *)function + line:(uint)line; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSService.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperLogger.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperLogger.h new file mode 100644 index 0000000000..1e17ff1324 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperLogger.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +/** + * This is a utility for producing App Center style log messages. It is only intended for use by App Center services and wrapper SDKs of App + * Center. + */ +@interface MSWrapperLogger : NSObject + ++ (void)MSWrapperLog:(MSLogMessageProvider)message tag:(NSString *)tag level:(MSLogLevel)level; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperSdk.h b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperSdk.h new file mode 100644 index 0000000000..647ba7b0fe --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Headers/MSWrapperSdk.h @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_WRAPPER_SDK_H +#define MS_WRAPPER_SDK_H + +#import + +@interface MSWrapperSdk : NSObject + +/* + * Version of the wrapper SDK. When the SDK is embedding another base SDK (for example Xamarin.Android wraps Android), the Xamarin specific + * version is populated into this field while sdkVersion refers to the original Android SDK. [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkVersion; + +/* + * Name of the wrapper SDK (examples: Xamarin, Cordova). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkName; + +/* + * Version of the wrapper technology framework (Xamarin runtime version or ReactNative or Cordova etc...). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperRuntimeVersion; + +/* + * Label that is used to identify application code 'version' released via Live Update beacon running on device. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateReleaseLabel; + +/* + * Identifier of environment that current application release belongs to, deployment key then maps to environment like Production, Staging. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateDeploymentKey; + +/* + * Hash of all files (ReactNative or Cordova) deployed to device via LiveUpdate beacon. Helps identify the Release version on device or need + * to download updates in future + */ +@property(nonatomic, copy, readonly) NSString *liveUpdatePackageHash; + +- (instancetype)initWithWrapperSdkVersion:(NSString *)wrapperSdkVersion + wrapperSdkName:(NSString *)wrapperSdkName + wrapperRuntimeVersion:(NSString *)wrapperRuntimeVersion + liveUpdateReleaseLabel:(NSString *)liveUpdateReleaseLabel + liveUpdateDeploymentKey:(NSString *)liveUpdateDeploymentKey + liveUpdatePackageHash:(NSString *)liveUpdatePackageHash; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Modules/module.modulemap b/submodules/AppCenter/AppCenter.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000000..32c35bdba8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,12 @@ +framework module AppCenter { + umbrella header "AppCenter.h" + + export * + module * { export * } + + link framework "Foundation" + link framework "SystemConfiguration" + link framework "AppKit" + link "sqlite3" + link "z" +} diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSChannelDelegate.h b/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSChannelDelegate.h new file mode 100644 index 0000000000..f1985b23a8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSChannelDelegate.h @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants+Flags.h" + +@protocol MSChannelUnitProtocol; +@protocol MSChannelGroupProtocol; +@protocol MSChannelProtocol; +@protocol MSLog; + +@protocol MSChannelDelegate + +@optional + +/** + * A callback that is called when a channel unit is added to the channel group. + * + * @param channelGroup The channel group. + * @param channel The newly added channel. + */ +- (void)channelGroup:(id)channelGroup didAddChannelUnit:(id)channel; + +/** + * A callback that is called when a log is just enqueued. Delegates may want to prepare the log a little more before further processing. + * + * @param log The log to prepare. + */ +- (void)channel:(id)channel prepareLog:(id)log; + +/** + * A callback that is called after a log is definitely prepared. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + * @param flags Options for the log. + */ +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags; + +/** + * A callback that is called after a log completed the enqueueing process whether it was successful or not. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + */ +- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId; + +/** + * Callback method that will be called before each log will be send to the server. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel willSendLog:(id)log; + +/** + * Callback method that will be called in case the SDK was able to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel didSucceedSendingLog:(id)log; + +/** + * Callback method that will be called in case the SDK was unable to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + * @param error The error that occured. + */ +- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error; + +/** + * A callback that is called when setEnabled has been invoked. + * + * @param channel The channel. + * @param isEnabled The boolean that indicates enabled. + * @param deletedData The boolean that indicates deleting data on disabled. + */ +- (void)channel:(id)channel didSetEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deletedData; + +/** + * A callback that is called when pause has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to pause the channel. + */ +- (void)channel:(id)channel didPauseWithIdentifyingObject:(id)identifyingObject; + +/** + * A callback that is called when resume has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to resume the channel. + */ +- (void)channel:(id)channel didResumeWithIdentifyingObject:(id)identifyingObject; + +/** + * Callback method that will determine if a log should be filtered out from the usual processing pipeline. If any delegate returns true, the + * log is filtered. + * + * @param channelUnit The channel unit that is going to send the log. + * @param log The log to be filtered or not. + * + * @return `true` if the log should be filtered out. + */ +- (BOOL)channelUnit:(id)channelUnit shouldFilterLog:(id)log; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSConstants+Flags.h b/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSConstants+Flags.h new file mode 100644 index 0000000000..0634d16513 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/PrivateHeaders/MSConstants+Flags.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +typedef NS_OPTIONS(NSUInteger, MSFlags) { + MSFlagsNone = (0 << 0), // => 00000000 + MSFlagsNormal = (1 << 0), // => 00000001 + MSFlagsCritical = (1 << 1), // => 00000010 + MSFlagsPersistenceNormal DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsNormal") = MSFlagsNormal, + MSFlagsPersistenceCritical DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsCritical") = MSFlagsCritical, + MSFlagsDefault = MSFlagsNormal +}; diff --git a/submodules/AppCenter/AppCenter.framework/Versions/A/Resources/Info.plist b/submodules/AppCenter/AppCenter.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000000..7db23d565d --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenter + CFBundleIdentifier + com.microsoft.appcenter + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenter + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/AppCenter b/submodules/AppCenter/AppCenter.framework/Versions/Current/AppCenter new file mode 100644 index 0000000000..82fead5f89 Binary files /dev/null and b/submodules/AppCenter/AppCenter.framework/Versions/Current/AppCenter differ diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/AppCenter.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/AppCenter.h new file mode 100644 index 0000000000..6ff9d8882c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/AppCenter.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" +#import "MSAppCenter.h" +#import "MSAppCenterErrors.h" +#import "MSChannelGroupProtocol.h" +#import "MSChannelProtocol.h" +#import "MSConstants.h" +#import "MSCustomProperties.h" +#import "MSDevice.h" +#import "MSEnable.h" +#import "MSLog.h" +#import "MSLogWithProperties.h" +#import "MSLogger.h" +#import "MSService.h" +#import "MSServiceAbstract.h" +#import "MSWrapperLogger.h" +#import "MSWrapperSdk.h" diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenter.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenter.h new file mode 100644 index 0000000000..c6ceb9ca90 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenter.h @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +@class MSWrapperSdk; + +#if !TARGET_OS_TV +@class MSCustomProperties; +#endif + +@interface MSAppCenter : NSObject + +/** + * Returns the singleton instance of MSAppCenter. + */ ++ (instancetype)sharedInstance; + +/** + * Configure the SDK with an application secret. + * + * @param appSecret A unique and secret key used to identify the application. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configureWithAppSecret:(NSString *)appSecret; + +/** + * Configure the SDK. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)configure; + +/** + * Configure the SDK with an application secret and an array of services to start. + * + * @param appSecret A unique and secret key used to identify the application. + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)start:(NSString *)appSecret withServices:(NSArray *)services; + +/** + * Start the SDK with an array of services. + * + * @param services Array of services to start. + * + * @discussion This may be called only once per application process lifetime. + */ ++ (void)startWithServices:(NSArray *)services; + +/** + * Start a service. + * + * @param service A service to start. + * + * @discussion This may be called only once per service per application process lifetime. + */ ++ (void)startService:(Class)service; + +/** + * Configure the SDK with an array of services to start from a library. This will not start the service at application level, it will enable + * the service only for the library. + * + * @param services Array of services to start. + */ ++ (void)startFromLibraryWithServices:(NSArray *)services; + +/** + * Check whether the SDK has already been configured or not. + * + * @return YES if configured, NO otherwise. + */ ++ (BOOL)isConfigured; + +/** + * Check whether app is running in App Center Test Cloud. + * + * @return true if running in App Center Test Cloud, false otherwise. + */ ++ (BOOL)isRunningInAppCenterTestCloud; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl Base URL to use for backend communication. + */ ++ (void)setLogUrl:(NSString *)logUrl; + +/** + * Enable or disable the SDK as a whole. In addition to AppCenter resources, it will also enable or disable all registered services. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled YES to enable, NO to disable. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Check whether the SDK is enabled or not as a whole. + * + * @return YES if enabled, NO otherwise. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +/** + * Get log level. + * + * @return Log level. + */ ++ (MSLogLevel)logLevel; + +/** + * Set log level. + * + * @param logLevel The log level. + */ ++ (void)setLogLevel:(MSLogLevel)logLevel; + +/** + * Set log level handler. + * + * @param logHandler Handler. + */ ++ (void)setLogHandler:(MSLogHandler)logHandler; + +/** + * Set wrapper SDK information to use when building device properties. This is intended in case you are building a SDK that uses the App + * Center SDK under the hood, e.g. our Xamarin SDK or ReactNative SDk. + * + * @param wrapperSdk Wrapper SDK information. + */ ++ (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk; + +#if !TARGET_OS_TV +/** + * Set the custom properties. + * + * @param customProperties Custom properties object. + */ ++ (void)setCustomProperties:(MSCustomProperties *)customProperties; +#endif + +/** + * Check whether the application delegate forwarder is enabled or not. + * + * @return YES if enabled, NO otherwise. + * + * @discussion The application delegate forwarder forwards messages that target your application delegate methods via swizzling to the SDK. + * It simplifies the SDK integration but may not be suitable to any situations. For + * instance it should be disabled if you or one of your third party SDK is doing message forwarding on the application delegate. Message + * forwarding usually implies the implementation of @see NSObject#forwardingTargetForSelector: or @see NSObject#forwardInvocation: methods. + * To disable the application delegate forwarder just add the `AppCenterAppDelegateForwarderEnabled` tag to your Info .plist file and set it + * to `0`. Then you will have to forward any application delegate needed by the SDK manually. + */ ++ (BOOL)isAppDelegateForwarderEnabled; + +/** + * Get unique installation identifier. + * + * @return Unique installation identifier. + */ ++ (NSUUID *)installId; + +/** + * Detect if a debugger is attached to the app process. This is only invoked once on app startup and can not detect + * if the debugger is being attached during runtime! + * + * @return BOOL if the debugger is attached. + */ ++ (BOOL)isDebuggerAttached; + +/** + * Get the current version of AppCenter SDK. + * + * @return The current version of AppCenter SDK. + */ ++ (NSString *)sdkVersion; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. This method is only intended for + * applications. + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored. + * + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. This parameter can be null. + * + * @discussion This only sets the maximum size of the database, but App Center modules might store additional data. + * The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB). + */ ++ (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(void (^)(BOOL))completionHandler; + +/** + * Set the user identifier. + * + * @param userId User identifier. + * + * @discussion Set the user identifier for logs sent for the default target token when the secret passed in @c + * MSAppCenter:start:withServices: contains "target={targetToken}". + * + * For App Center backend the user identifier maximum length is 256 characters. + * + * AppCenter must be configured or started before this API can be used. + */ ++ (void)setUserId:(NSString *)userId; + +/** + * Set country code to use when building device properties. + * + * @param countryCode The two-letter ISO country code. @see https://www.iso.org/obp/ui/#search for more information. + */ ++ (void)setCountryCode:(NSString *)countryCode; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenterErrors.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenterErrors.h new file mode 100644 index 0000000000..e57cf9217f --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSAppCenterErrors.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_APP_CENTER_ERRORS_H +#define MS_APP_CENTER_ERRORS_H + +#import + +#define MS_APP_CENTER_BASE_DOMAIN @"com.Microsoft.AppCenter." + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Domain + +static NSString *const kMSACErrorDomain = MS_APP_CENTER_BASE_DOMAIN @"ErrorDomain"; + +#pragma mark - General + +// Error codes. +NS_ENUM(NSInteger){MSACLogInvalidContainerErrorCode = 1, MSACCanceledErrorCode = 2, MSACDisabledErrorCode = 3}; + +// Error descriptions. +static NSString const *kMSACLogInvalidContainerErrorDesc = @"Invalid log container."; +static NSString const *kMSACCanceledErrorDesc = @"The operation was canceled."; +static NSString const *kMSACDisabledErrorDesc = @"The service is disabled."; + +#pragma mark - Connection + +// Error codes. +NS_ENUM(NSInteger){MSACConnectionPausedErrorCode = 100, MSACConnectionHttpErrorCode = 101}; + +// Error descriptions. +static NSString const *kMSACConnectionHttpErrorDesc = @"An HTTP error occured."; +static NSString const *kMSACConnectionPausedErrorDesc = @"Canceled, connection paused with log deletion."; + +// Error user info keys. +static NSString const *kMSACConnectionHttpCodeErrorKey = @"MSACConnectionHttpCode"; + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelGroupProtocol.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelGroupProtocol.h new file mode 100644 index 0000000000..b786ff018a --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelGroupProtocol.h @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_GROUP_PROTOCOL_H +#define MS_CHANNEL_GROUP_PROTOCOL_H + +#import + +#import "MSChannelProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSChannelUnitConfiguration; + +@protocol MSIngestionProtocol; +@protocol MSChannelUnitProtocol; + +/** + * `MSChannelGroupProtocol` represents a kind of channel that contains constituent MSChannelUnit objects. When an operation from the + * `MSChannelProtocol` is performed on the group, that operation should be propagated to its constituent MSChannelUnit objects. + */ +@protocol MSChannelGroupProtocol + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration; + +/** + * Initialize a channel unit with the given configuration. + * + * @param configuration channel configuration. + * @param ingestion The alternative ingestion object + * + * @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs. + */ +- (id)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration + withIngestion:(nullable id)ingestion; + +/** + * Change the base URL (schema + authority + port only) used to communicate with the backend. + * + * @param logUrl base URL to use for backend communication. + */ +- (void)setLogUrl:(NSString *)logUrl; + +/** + * Set the app secret. + * + * @param appSecret The app secret. + */ +- (void)setAppSecret:(NSString *)appSecret; + +/** + * Set the maximum size of the internal storage. This method must be called before App Center is started. + * + * @discussion The default maximum database size is 10485760 bytes (10 MiB). + * + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 24576 bytes (24 KiB) will be ignored. + * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size + * is successful, and `NO` otherwise. + */ +- (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler; + +/** + * Return a channel unit instance for the given groupId. + * + * @param groupId The group ID for a channel unit. + * + * @return A channel unit instance or `nil`. + */ +- (id)channelUnitForGroupId:(NSString *)groupId; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelProtocol.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelProtocol.h new file mode 100644 index 0000000000..4c50baaba7 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSChannelProtocol.h @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CHANNEL_PROTOCOL_H +#define MS_CHANNEL_PROTOCOL_H + +#import + +#import "MSEnable.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MSChannelDelegate; + +/** + * `MSChannelProtocol` contains the essential operations of a channel. Channels are broadly responsible for enqueuing logs to be sent to the + * backend and/or stored on disk. + */ +@protocol MSChannelProtocol + +/** + * Add delegate. + * + * @param delegate delegate. + */ +- (void)addDelegate:(id)delegate; + +/** + * Remove delegate. + * + * @param delegate delegate. + */ +- (void)removeDelegate:(id)delegate; + +/** + * Pause operations, logs will be stored but not sent. + * + * @param identifyingObject Object used to identify the pause request. + * + * @discussion A paused channel doesn't forward logs to the ingestion. The identifying object used to pause the channel can be any unique + * object. The same identifying object must be used to call resume. For simplicity if the caller is the one owning the channel then @c self + * can be used as identifying object. + * + * @see resumeWithIdentifyingObject: + */ +- (void)pauseWithIdentifyingObject:(id)identifyingObject; + +/** + * Resume operations, logs can be sent again. + * + * @param identifyingObject Object used to passed to the pause method. + * + * @discussion The channel only resume when all the outstanding identifying objects have been resumed. + * + * @see pauseWithIdentifyingObject: + */ +- (void)resumeWithIdentifyingObject:(id)identifyingObject; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSConstants.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSConstants.h new file mode 100644 index 0000000000..047c9f7112 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSConstants.h @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * Log Levels + */ +typedef NS_ENUM(NSUInteger, MSLogLevel) { + + /** + * Logging will be very chatty + */ + MSLogLevelVerbose = 2, + + /** + * Debug information will be logged + */ + MSLogLevelDebug = 3, + + /** + * Information will be logged + */ + MSLogLevelInfo = 4, + + /** + * Errors and warnings will be logged + */ + MSLogLevelWarning = 5, + + /** + * Errors will be logged + */ + MSLogLevelError = 6, + + /** + * Only critical errors will be logged + */ + MSLogLevelAssert = 7, + + /** + * Logging is disabled + */ + MSLogLevelNone = 99 +}; + +typedef NSString * (^MSLogMessageProvider)(void); +typedef void (^MSLogHandler)(MSLogMessageProvider messageProvider, MSLogLevel logLevel, NSString *tag, const char *file, + const char *function, uint line); + +/** + * Channel priorities, check the kMSPriorityCount if you add a new value. + * The order matters here! Values NEED to range from low priority to high priority. + */ +typedef NS_ENUM(NSInteger, MSPriority) { MSPriorityBackground, MSPriorityDefault, MSPriorityHigh }; +static short const kMSPriorityCount = MSPriorityHigh + 1; + +/** + * The priority by which the modules are initialized. + * MSPriorityMax is reserved for only 1 module and this needs to be Crashes. + * Crashes needs to be initialized first to catch crashes in our other SDK Modules (which will hopefully never happen) and to avoid losing + * any log at crash time. + */ +typedef NS_ENUM(NSInteger, MSInitializationPriority) { + MSInitializationPriorityDefault = 500, + MSInitializationPriorityHigh = 750, + MSInitializationPriorityMax = 999 +}; + +/** + * Enum with the different HTTP status codes. + */ +typedef NS_ENUM(NSInteger, MSHTTPCodesNo) { + + // Invalid + MSHTTPCodesNo0XXInvalidUnknown = 0, + + // Informational + MSHTTPCodesNo1XXInformationalUnknown = 1, + MSHTTPCodesNo100Continue = 100, + MSHTTPCodesNo101SwitchingProtocols = 101, + MSHTTPCodesNo102Processing = 102, + + // Success + MSHTTPCodesNo2XXSuccessUnknown = 2, + MSHTTPCodesNo200OK = 200, + MSHTTPCodesNo201Created = 201, + MSHTTPCodesNo202Accepted = 202, + MSHTTPCodesNo203NonAuthoritativeInformation = 203, + MSHTTPCodesNo204NoContent = 204, + MSHTTPCodesNo205ResetContent = 205, + MSHTTPCodesNo206PartialContent = 206, + MSHTTPCodesNo207MultiStatus = 207, + MSHTTPCodesNo208AlreadyReported = 208, + MSHTTPCodesNo209IMUsed = 209, + + // Redirection + MSHTTPCodesNo3XXSuccessUnknown = 3, + MSHTTPCodesNo300MultipleChoices = 300, + MSHTTPCodesNo301MovedPermanently = 301, + MSHTTPCodesNo302Found = 302, + MSHTTPCodesNo303SeeOther = 303, + MSHTTPCodesNo304NotModified = 304, + MSHTTPCodesNo305UseProxy = 305, + MSHTTPCodesNo306SwitchProxy = 306, + MSHTTPCodesNo307TemporaryRedirect = 307, + MSHTTPCodesNo308PermanentRedirect = 308, + + // Client error + MSHTTPCodesNo4XXSuccessUnknown = 4, + MSHTTPCodesNo400BadRequest = 400, + MSHTTPCodesNo401Unauthorised = 401, + MSHTTPCodesNo402PaymentRequired = 402, + MSHTTPCodesNo403Forbidden = 403, + MSHTTPCodesNo404NotFound = 404, + MSHTTPCodesNo405MethodNotAllowed = 405, + MSHTTPCodesNo406NotAcceptable = 406, + MSHTTPCodesNo407ProxyAuthenticationRequired = 407, + MSHTTPCodesNo408RequestTimeout = 408, + MSHTTPCodesNo409Conflict = 409, + MSHTTPCodesNo410Gone = 410, + MSHTTPCodesNo411LengthRequired = 411, + MSHTTPCodesNo412PreconditionFailed = 412, + MSHTTPCodesNo413RequestEntityTooLarge = 413, + MSHTTPCodesNo414RequestURITooLong = 414, + MSHTTPCodesNo415UnsupportedMediaType = 415, + MSHTTPCodesNo416RequestedRangeNotSatisfiable = 416, + MSHTTPCodesNo417ExpectationFailed = 417, + MSHTTPCodesNo418IamATeapot = 418, + MSHTTPCodesNo419AuthenticationTimeout = 419, + MSHTTPCodesNo420MethodFailureSpringFramework = 420, + MSHTTPCodesNo420EnhanceYourCalmTwitter = 4200, + MSHTTPCodesNo422UnprocessableEntity = 422, + MSHTTPCodesNo423Locked = 423, + MSHTTPCodesNo424FailedDependency = 424, + MSHTTPCodesNo424MethodFailureWebDaw = 4240, + MSHTTPCodesNo425UnorderedCollection = 425, + MSHTTPCodesNo426UpgradeRequired = 426, + MSHTTPCodesNo428PreconditionRequired = 428, + MSHTTPCodesNo429TooManyRequests = 429, + MSHTTPCodesNo431RequestHeaderFieldsTooLarge = 431, + MSHTTPCodesNo444NoResponseNginx = 444, + MSHTTPCodesNo449RetryWithMicrosoft = 449, + MSHTTPCodesNo450BlockedByWindowsParentalControls = 450, + MSHTTPCodesNo451RedirectMicrosoft = 451, + MSHTTPCodesNo451UnavailableForLegalReasons = 4510, + MSHTTPCodesNo494RequestHeaderTooLargeNginx = 494, + MSHTTPCodesNo495CertErrorNginx = 495, + MSHTTPCodesNo496NoCertNginx = 496, + MSHTTPCodesNo497HTTPToHTTPSNginx = 497, + MSHTTPCodesNo499ClientClosedRequestNginx = 499, + + // Server error + MSHTTPCodesNo5XXSuccessUnknown = 5, + MSHTTPCodesNo500InternalServerError = 500, + MSHTTPCodesNo501NotImplemented = 501, + MSHTTPCodesNo502BadGateway = 502, + MSHTTPCodesNo503ServiceUnavailable = 503, + MSHTTPCodesNo504GatewayTimeout = 504, + MSHTTPCodesNo505HTTPVersionNotSupported = 505, + MSHTTPCodesNo506VariantAlsoNegotiates = 506, + MSHTTPCodesNo507InsufficientStorage = 507, + MSHTTPCodesNo508LoopDetected = 508, + MSHTTPCodesNo509BandwidthLimitExceeded = 509, + MSHTTPCodesNo510NotExtended = 510, + MSHTTPCodesNo511NetworkAuthenticationRequired = 511, + MSHTTPCodesNo522ConnectionTimedOut = 522, + MSHTTPCodesNo598NetworkReadTimeoutErrorUnknown = 598, + MSHTTPCodesNo599NetworkConnectTimeoutErrorUnknown = 599 +}; diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSCustomProperties.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSCustomProperties.h new file mode 100644 index 0000000000..34b202e258 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSCustomProperties.h @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_CUSTOM_PROPERTIES_H +#define MS_CUSTOM_PROPERTIES_H + +#import + +/** + * Custom properties builder. + * Collects multiple properties to send in one log. + */ +@interface MSCustomProperties : NSObject + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setString:(NSString *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setNumber:(NSNumber *)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setBool:(BOOL)value forKey:(NSString *)key; + +/** + * Set the specified property value with the specified key. + * If the properties previously contained a property for the key, the old value is replaced. + * + * @param key Key with which the specified value is to be set. + * @param value Value to be set with the specified key. + * + * @return This instance. + */ +- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key; + +/** + * Clear the property for the specified key. + * + * @param key Key whose mapping is to be cleared. + * + * @return This instance. + */ +- (instancetype)clearPropertyForKey:(NSString *)key; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSDevice.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSDevice.h new file mode 100644 index 0000000000..bca52140f0 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSDevice.h @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_DEVICE_H +#define MS_DEVICE_H + +#import + +#import "MSWrapperSdk.h" + +@interface MSDevice : MSWrapperSdk + +/* + * Name of the SDK. Consists of the name of the SDK and the platform, e.g. "appcenter.ios", "appcenter.android" + */ +@property(nonatomic, copy, readonly) NSString *sdkName; + +/* + * Version of the SDK in semver format, e.g. "1.2.0" or "0.12.3-alpha.1". + */ +@property(nonatomic, copy, readonly) NSString *sdkVersion; + +/* + * Device model (example: iPad2,3). + */ +@property(nonatomic, copy, readonly) NSString *model; + +/* + * Device manufacturer (example: HTC). + */ +@property(nonatomic, copy, readonly) NSString *oemName; + +/* + * OS name (example: iOS). + */ +@property(nonatomic, copy, readonly) NSString *osName; + +/* + * OS version (example: 9.3.0). + */ +@property(nonatomic, copy, readonly) NSString *osVersion; + +/* + * OS build code (example: LMY47X). [optional] + */ +@property(nonatomic, copy, readonly) NSString *osBuild; + +/* + * API level when applicable like in Android (example: 15). [optional] + */ +@property(nonatomic, copy, readonly) NSNumber *osApiLevel; + +/* + * Language code (example: en_US). + */ +@property(nonatomic, copy, readonly) NSString *locale; + +/* + * The offset in minutes from UTC for the device time zone, including daylight savings time. + */ +@property(nonatomic, readonly, strong) NSNumber *timeZoneOffset; + +/* + * Screen size of the device in pixels (example: 640x480). + */ +@property(nonatomic, copy, readonly) NSString *screenSize; + +/* + * Application version name, e.g. 1.1.0 + */ +@property(nonatomic, copy, readonly) NSString *appVersion; + +/* + * Carrier name (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierName; + +/* + * Carrier country code (for mobile devices). [optional] + */ +@property(nonatomic, copy, readonly) NSString *carrierCountry; + +/* + * The app's build number, e.g. 42. + */ +@property(nonatomic, copy, readonly) NSString *appBuild; + +/* + * The bundle identifier, package identifier, or namespace, depending on what the individual plattforms use, .e.g com.microsoft.example. + * [optional] + */ +@property(nonatomic, copy, readonly) NSString *appNamespace; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSEnable.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSEnable.h new file mode 100644 index 0000000000..11e2f60579 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSEnable.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ENABLE_H +#define MS_ENABLE_H + +#import + +/** + * Protocol to define an instance that can be enabled/disabled. + */ +@protocol MSEnable + +@required + +/** + * Enable/disable this instance and delete data on disabled state. + * + * @param isEnabled A boolean value set to YES to enable the instance or NO to disable it. + * @param deleteData A boolean value set to YES to delete data or NO to keep it. + */ +- (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLog.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLog.h new file mode 100644 index 0000000000..07f1e574b2 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLog.h @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_H +#define MS_LOG_H + +#import + +@class MSDevice; + +@protocol MSLog + +/** + * Log type. + */ +@property(nonatomic, copy) NSString *type; + +/** + * Log timestamp. + */ +@property(nonatomic, strong) NSDate *timestamp; + +/** + * A session identifier is used to correlate logs together. A session is an abstract concept in the API and is not necessarily an analytics + * session, it can be used to only track crashes. + */ +@property(nonatomic, copy) NSString *sid; + +/** + * Optional distribution group ID value. + */ +@property(nonatomic, copy) NSString *distributionGroupId; + +/** + * Optional user identifier. + */ +@property(nonatomic, copy) NSString *userId; + +/** + * Device properties associated to this log. + */ +@property(nonatomic, strong) MSDevice *device; + +/** + * Transient object tag. For example, a log can be tagged with a transmission target. We do this currently to prevent properties being + * applied retroactively to previous logs by comparing their tags. + */ +@property(nonatomic, strong) NSObject *tag; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +/** + * Adds a transmission target token that this log should be sent to. + * + * @param token The transmission target token. + */ +- (void)addTransmissionTargetToken:(NSString *)token; + +/** + * Gets all transmission target tokens that this log should be sent to. + * + * @returns Collection of transmission target tokens that this log should be sent to. + */ +- (NSSet *)transmissionTargetTokens; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogWithProperties.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogWithProperties.h new file mode 100644 index 0000000000..7a3b372da5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogWithProperties.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_LOG_WITH_PROPERTIES_H +#define MS_LOG_WITH_PROPERTIES_H + +#import + +#import "MSAbstractLog.h" + +@interface MSLogWithProperties : MSAbstractLog + +/** + * Additional key/value pair parameters. [optional] + */ +@property(nonatomic, strong) NSDictionary *properties; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogger.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogger.h new file mode 100644 index 0000000000..7eb5c3bebb --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSLogger.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +#define MSLog(_level, _tag, _message) \ + [MSLogger logMessage:_message level:_level tag:_tag file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__] +#define MSLogAssert(tag, format, ...) \ + MSLog(MSLogLevelAssert, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogError(tag, format, ...) \ + MSLog(MSLogLevelError, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogWarning(tag, format, ...) \ + MSLog(MSLogLevelWarning, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogInfo(tag, format, ...) \ + MSLog(MSLogLevelInfo, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogDebug(tag, format, ...) \ + MSLog(MSLogLevelDebug, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) +#define MSLogVerbose(tag, format, ...) \ + MSLog(MSLogLevelVerbose, tag, (^{ \ + return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \ + })) + +@interface MSLogger : NSObject + ++ (void)logMessage:(MSLogMessageProvider)messageProvider + level:(MSLogLevel)loglevel + tag:(NSString *)tag + file:(const char *)file + function:(const char *)function + line:(uint)line; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSService.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperLogger.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperLogger.h new file mode 100644 index 0000000000..1e17ff1324 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperLogger.h @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants.h" + +/** + * This is a utility for producing App Center style log messages. It is only intended for use by App Center services and wrapper SDKs of App + * Center. + */ +@interface MSWrapperLogger : NSObject + ++ (void)MSWrapperLog:(MSLogMessageProvider)message tag:(NSString *)tag level:(MSLogLevel)level; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperSdk.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperSdk.h new file mode 100644 index 0000000000..647ba7b0fe --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Headers/MSWrapperSdk.h @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_WRAPPER_SDK_H +#define MS_WRAPPER_SDK_H + +#import + +@interface MSWrapperSdk : NSObject + +/* + * Version of the wrapper SDK. When the SDK is embedding another base SDK (for example Xamarin.Android wraps Android), the Xamarin specific + * version is populated into this field while sdkVersion refers to the original Android SDK. [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkVersion; + +/* + * Name of the wrapper SDK (examples: Xamarin, Cordova). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperSdkName; + +/* + * Version of the wrapper technology framework (Xamarin runtime version or ReactNative or Cordova etc...). [optional] + */ +@property(nonatomic, copy, readonly) NSString *wrapperRuntimeVersion; + +/* + * Label that is used to identify application code 'version' released via Live Update beacon running on device. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateReleaseLabel; + +/* + * Identifier of environment that current application release belongs to, deployment key then maps to environment like Production, Staging. + */ +@property(nonatomic, copy, readonly) NSString *liveUpdateDeploymentKey; + +/* + * Hash of all files (ReactNative or Cordova) deployed to device via LiveUpdate beacon. Helps identify the Release version on device or need + * to download updates in future + */ +@property(nonatomic, copy, readonly) NSString *liveUpdatePackageHash; + +- (instancetype)initWithWrapperSdkVersion:(NSString *)wrapperSdkVersion + wrapperSdkName:(NSString *)wrapperSdkName + wrapperRuntimeVersion:(NSString *)wrapperRuntimeVersion + liveUpdateReleaseLabel:(NSString *)liveUpdateReleaseLabel + liveUpdateDeploymentKey:(NSString *)liveUpdateDeploymentKey + liveUpdatePackageHash:(NSString *)liveUpdatePackageHash; + +/** + * Checks if the object's values are valid. + * + * @return YES, if the object is valid. + */ +- (BOOL)isValid; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Modules/module.modulemap b/submodules/AppCenter/AppCenter.framework/Versions/Current/Modules/module.modulemap new file mode 100644 index 0000000000..32c35bdba8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Modules/module.modulemap @@ -0,0 +1,12 @@ +framework module AppCenter { + umbrella header "AppCenter.h" + + export * + module * { export * } + + link framework "Foundation" + link framework "SystemConfiguration" + link framework "AppKit" + link "sqlite3" + link "z" +} diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSChannelDelegate.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSChannelDelegate.h new file mode 100644 index 0000000000..f1985b23a8 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSChannelDelegate.h @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSConstants+Flags.h" + +@protocol MSChannelUnitProtocol; +@protocol MSChannelGroupProtocol; +@protocol MSChannelProtocol; +@protocol MSLog; + +@protocol MSChannelDelegate + +@optional + +/** + * A callback that is called when a channel unit is added to the channel group. + * + * @param channelGroup The channel group. + * @param channel The newly added channel. + */ +- (void)channelGroup:(id)channelGroup didAddChannelUnit:(id)channel; + +/** + * A callback that is called when a log is just enqueued. Delegates may want to prepare the log a little more before further processing. + * + * @param log The log to prepare. + */ +- (void)channel:(id)channel prepareLog:(id)log; + +/** + * A callback that is called after a log is definitely prepared. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + * @param flags Options for the log. + */ +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags; + +/** + * A callback that is called after a log completed the enqueueing process whether it was successful or not. + * + * @param log The log. + * @param internalId An internal Id to keep track of logs. + */ +- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId; + +/** + * Callback method that will be called before each log will be send to the server. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel willSendLog:(id)log; + +/** + * Callback method that will be called in case the SDK was able to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + */ +- (void)channel:(id)channel didSucceedSendingLog:(id)log; + +/** + * Callback method that will be called in case the SDK was unable to send a log. + * + * @param channel The channel object. + * @param log The log to be sent. + * @param error The error that occured. + */ +- (void)channel:(id)channel didFailSendingLog:(id)log withError:(NSError *)error; + +/** + * A callback that is called when setEnabled has been invoked. + * + * @param channel The channel. + * @param isEnabled The boolean that indicates enabled. + * @param deletedData The boolean that indicates deleting data on disabled. + */ +- (void)channel:(id)channel didSetEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deletedData; + +/** + * A callback that is called when pause has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to pause the channel. + */ +- (void)channel:(id)channel didPauseWithIdentifyingObject:(id)identifyingObject; + +/** + * A callback that is called when resume has been invoked. + * + * @param channel The channel. + * @param identifyingObject The identifying object used to resume the channel. + */ +- (void)channel:(id)channel didResumeWithIdentifyingObject:(id)identifyingObject; + +/** + * Callback method that will determine if a log should be filtered out from the usual processing pipeline. If any delegate returns true, the + * log is filtered. + * + * @param channelUnit The channel unit that is going to send the log. + * @param log The log to be filtered or not. + * + * @return `true` if the log should be filtered out. + */ +- (BOOL)channelUnit:(id)channelUnit shouldFilterLog:(id)log; + +@end diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSConstants+Flags.h b/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSConstants+Flags.h new file mode 100644 index 0000000000..0634d16513 --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/PrivateHeaders/MSConstants+Flags.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +typedef NS_OPTIONS(NSUInteger, MSFlags) { + MSFlagsNone = (0 << 0), // => 00000000 + MSFlagsNormal = (1 << 0), // => 00000001 + MSFlagsCritical = (1 << 1), // => 00000010 + MSFlagsPersistenceNormal DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsNormal") = MSFlagsNormal, + MSFlagsPersistenceCritical DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsCritical") = MSFlagsCritical, + MSFlagsDefault = MSFlagsNormal +}; diff --git a/submodules/AppCenter/AppCenter.framework/Versions/Current/Resources/Info.plist b/submodules/AppCenter/AppCenter.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000000..7db23d565d --- /dev/null +++ b/submodules/AppCenter/AppCenter.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenter + CFBundleIdentifier + com.microsoft.appcenter + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenter + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/AppCenter/AppCenterCrashes.framework/AppCenterCrashes b/submodules/AppCenter/AppCenterCrashes.framework/AppCenterCrashes new file mode 100644 index 0000000000..8e7eef40d5 Binary files /dev/null and b/submodules/AppCenter/AppCenterCrashes.framework/AppCenterCrashes differ diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/AppCenterCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/AppCenterCrashes.h new file mode 100644 index 0000000000..d2920b3f15 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/AppCenterCrashes.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" +#import "MSCrashes.h" +#import "MSCrashesDelegate.h" +#import "MSErrorAttachmentLog+Utility.h" +#import "MSErrorAttachmentLog.h" +#import "MSWrapperCrashesHelper.h" diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashHandlerSetupDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashHandlerSetupDelegate.h new file mode 100644 index 0000000000..0a214be043 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashHandlerSetupDelegate.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * This is required for Wrapper SDKs that need to provide custom behavior surrounding the setup of crash handlers. + */ +@protocol MSCrashHandlerSetupDelegate + +@optional + +/** + * Callback method that will be called immediately before crash handlers are set up. + */ +- (void)willSetUpCrashHandlers; + +/** + * Callback method that will be called immediately after crash handlers are set up. + */ +- (void)didSetUpCrashHandlers; + +/** + * Callback method that gets a value indicating whether the SDK should enable an uncaught exception handler. + * + * @return YES if SDK should enable uncaught exception handler, otherwise NO. + * + * @discussion Do not register an UncaughtExceptionHandler for Xamarin as we rely on the Xamarin runtime to report NSExceptions. Registering + * our own UncaughtExceptionHandler will cause the Xamarin debugger to not work properly (it will not stop for NSExceptions). + */ +- (BOOL)shouldEnableUncaughtExceptionHandler; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashes.h new file mode 100644 index 0000000000..9e4ae86daa --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashes.h @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorReport.h" +#import "MSServiceAbstract.h" + +@class MSCrashesDelegate; + +/** + * Custom block that handles the alert that prompts the user whether crash reports need to be processed or not. + * + * @return Returns YES to discard crash reports, otherwise NO. + */ +typedef BOOL (^MSUserConfirmationHandler)(NSArray *_Nonnull errorReports); + +/** + * Error Logging status. + */ +typedef NS_ENUM(NSUInteger, MSErrorLogSetting) { + + /** + * Crash reporting is disabled. + */ + MSErrorLogSettingDisabled = 0, + + /** + * User is asked each time before sending error logs. + */ + MSErrorLogSettingAlwaysAsk = 1, + + /** + * Each error log is send automatically. + */ + MSErrorLogSettingAutoSend = 2 +}; + +/** + * Crash Manager alert user input. + */ +typedef NS_ENUM(NSUInteger, MSUserConfirmation) { + + /** + * User chose not to send the crash report. + */ + MSUserConfirmationDontSend = 0, + + /** + * User wants the crash report to be sent. + */ + MSUserConfirmationSend = 1, + + /** + * User wants to send all error logs. + */ + MSUserConfirmationAlways = 2 +}; + +@protocol MSCrashesDelegate; + +@interface MSCrashes : MSServiceAbstract + +///----------------------------------------------------------------------------- +/// @name Testing Crashes Feature +///----------------------------------------------------------------------------- + +/** + * Lets the app crash for easy testing of the SDK. + * + * The best way to use this is to trigger the crash with a button action. + * + * Make sure not to let the app crash in `applicationDidFinishLaunching` or any other startup method! Since otherwise the app would crash + * before the SDK could process it. + * + * Note that our SDK provides support for handling crashes that happen early on startup. Check the documentation for more information on how + * to use this. + * + * If the SDK detects an App Store environment, it will _NOT_ cause the app to crash! + */ ++ (void)generateTestCrash; + +///----------------------------------------------------------------------------- +/// @name Helpers +///----------------------------------------------------------------------------- + +/** + * Check if the app has crashed in the last session. + * + * @return Returns YES is the app has crashed in the last session. + */ ++ (BOOL)hasCrashedInLastSession; + +/** + * Check if the app received memory warning in the last session. + * + * @return Returns YES is the app received memory warning in the last session. + */ ++ (BOOL)hasReceivedMemoryWarningInLastSession; + +/** + * Provides details about the crash that occurred in the last app session + */ ++ (nullable MSErrorReport *)lastSessionCrashReport; + +///----------------------------------------------------------------------------- +/// @name Configuration +///----------------------------------------------------------------------------- + +#if !TARGET_OS_TV +/** + * Disable the Mach exception server. + * + * By default, the SDK uses the Mach exception handler to catch fatal signals, e.g. stack overflows, via a Mach exception server. If you + * want to disable the Mach exception handler, you should call this method _BEFORE_ starting the SDK. Your typical setup code would look + * like this: + * + * `[MSCrashes disableMachExceptionHandler]`; + * `[MSAppCenter start:@"YOUR_APP_ID" withServices:@[[MSCrashes class]]];` + * + * or if you are using Swift: + * + * `MSCrashes.disableMachExceptionHandler()` + * `MSAppCenter.start("YOUR_APP_ID", withServices: [MSAnalytics.self, MSCrashes.self])` + * + * tvOS does not support the Mach exception handler, thus crashes that are caused by stack overflows cannot be detected. As a result, + * disabling the Mach exception server is not available in the tvOS SDK. + * + * @discussion It can be useful to disable the Mach exception handler when you are debugging the Crashes service while developing, + * especially when you attach the debugger to your application after launch. + */ ++ (void)disableMachExceptionHandler; +#endif + +/** + * Set the delegate + * Defines the class that implements the optional protocol `MSCrashesDelegate`. + * + * @see MSCrashesDelegate + */ ++ (void)setDelegate:(_Nullable id)delegate; + +/** + * Set a user confirmation handler that is invoked right before processing crash reports to determine whether sending crash reports or not. + * + * @param userConfirmationHandler A handler for user confirmation. + * + * @see MSUserConfirmationHandler + */ ++ (void)setUserConfirmationHandler:(_Nullable MSUserConfirmationHandler)userConfirmationHandler; + +/** + * Notify SDK with a confirmation to handle the crash report. + * + * @param userConfirmation A user confirmation. + * + * @see MSUserConfirmation. + */ ++ (void)notifyWithUserConfirmation:(MSUserConfirmation)userConfirmation; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashesDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashesDelegate.h new file mode 100644 index 0000000000..ecc249f041 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSCrashesDelegate.h @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSCrashes; +@class MSErrorReport; +@class MSErrorAttachmentLog; + +@protocol MSCrashesDelegate + +@optional + +/** + * Callback method that will be called before processing errors. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Crashes will send logs to the server or discard/delete logs based on this method's return value. + */ +- (BOOL)crashes:(MSCrashes *)crashes shouldProcessErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called before each error will be send to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Use this callback to display custom UI while crashes are sent to the server. + */ +- (void)crashes:(MSCrashes *)crashes willSendErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center sent. + * + * @discussion Use this method to hide your custom UI. + */ +- (void)crashes:(MSCrashes *)crashes didSucceedSendingErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center tried to send. + * @param error The error that occurred. + */ +- (void)crashes:(MSCrashes *)crashes didFailSendingErrorReport:(MSErrorReport *)errorReport withError:(NSError *)error; + +/** + * Method to get the attachments associated to an error report. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport associated with the returned attachments. + * + * @return The attachments associated with the given error report or nil if the error report doesn't have any attachments. + * + * @discussion Implement this method if you want attachments to the given error report. + */ +- (NSArray *)attachmentsWithCrashes:(MSCrashes *)crashes forErrorReport:(MSErrorReport *)errorReport; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog+Utility.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog+Utility.h new file mode 100644 index 0000000000..585c353c53 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog+Utility.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorAttachmentLog.h" + +// Exporting symbols for category. +extern NSString *MSMSErrorLogAttachmentLogUtilityCategory; + +@interface MSErrorAttachmentLog (Utility) + +/** + * Create an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithText:(NSString *)text filename:(NSString *)filename; + +/** + * Create an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as NSData. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithBinary:(NSData *)data filename:(NSString *)filename contentType:(NSString *)contentType; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog.h new file mode 100644 index 0000000000..3ffc3c71e1 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorAttachmentLog.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" + +/** + * Error attachment log. + */ +@interface MSErrorAttachmentLog : MSAbstractLog + +/** + * Content type (text/plain for text). + */ +@property(nonatomic, copy) NSString *contentType; + +/** + * File name. + */ +@property(nonatomic, copy) NSString *filename; + +/** + * The attachment data. + */ +@property(nonatomic, copy) NSData *data; + +/** + * Initialize an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as `NSData`. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentBinary:(NSData *)data contentType:(NSString *)contentType; + +/** + * Initialize an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentText:(NSString *)text; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorReport.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorReport.h new file mode 100644 index 0000000000..cf524a269a --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSErrorReport.h @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSDevice; + +@interface MSErrorReport : NSObject + +/** + * UUID for the crash report. + */ +@property(nonatomic, copy, readonly) NSString *incidentIdentifier; + +/** + * UUID for the app installation on the device. + */ +@property(nonatomic, copy, readonly) NSString *reporterKey; + +/** + * Signal that caused the crash. + */ +@property(nonatomic, copy, readonly) NSString *signal; + +/** + * Exception name that triggered the crash, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionName; + +/** + * Exception reason, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionReason; + +/** + * Date and time the app started, nil if unknown. + */ +@property(nonatomic, readonly, strong) NSDate *appStartTime; + +/** + * Date and time the error occurred, nil if unknown + */ +@property(nonatomic, readonly, strong) NSDate *appErrorTime; + +/** + * Device information of the app when it crashed. + */ +@property(nonatomic, readonly, strong) MSDevice *device; + +/** + * Identifier of the app process that crashed. + */ +@property(nonatomic, readonly, assign) NSUInteger appProcessIdentifier; + +/** + * Indicates if the app was killed while being in foreground from the iOS. + * + * This can happen if it consumed too much memory or the watchdog killed the app because it took too long to startup or blocks the main + * thread for too long, or other reasons. See Apple documentation: + * https://developer.apple.com/library/ios/qa/qa1693/_index.html. + * + * @return YES if the details represent an app kill instead of a crash. + * + * @see `[MSCrashes didReceiveMemoryWarningInLastSession]` + */ +- (BOOL)isAppKill; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSService.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSWrapperCrashesHelper.h b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSWrapperCrashesHelper.h new file mode 100644 index 0000000000..d380ed4423 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Headers/MSWrapperCrashesHelper.h @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSErrorReport; +@class MSErrorAttachmentLog; +@class MSException; + +/** + * This general class allows wrappers to supplement the Crashes SDK with their own behavior. + */ +@interface MSWrapperCrashesHelper : NSObject + +/** + * Sets the crash handler setup delegate. + * + * @param delegate The delegate to set. + */ ++ (void)setCrashHandlerSetupDelegate:(id)delegate; + +/** + * Gets the crash handler setup delegate. + * + * @return The delegate being used by Crashes. + */ ++ (id)getCrashHandlerSetupDelegate; + +/** + * Enables or disables automatic crash processing. + * + * @param automaticProcessing Passing NO causes SDK not to send reports immediately, even if "Always Send" is true. + */ ++ (void)setAutomaticProcessing:(BOOL)automaticProcessing; + +/** + * Gets a list of unprocessed crash reports. Will block until the service starts. + * + * @return An array of unprocessed error reports. + */ ++ (NSArray *)unprocessedCrashReports; + +/** + * Resumes processing for a given subset of the unprocessed reports. + * + * @param filteredIds An array containing the errorId/incidentIdentifier of each report that should be sent. + * + * @return YES if should "Always Send" is true. + */ ++ (BOOL)sendCrashReportsOrAwaitUserConfirmationForFilteredIds:(NSArray *)filteredIds; + +/** + * Sends error attachments for a particular error report. + * + * @param errorAttachments An array of error attachments that should be sent. + * @param incidentIdentifier The identifier of the error report that the attachments will be associated with. + */ ++ (void)sendErrorAttachments:(NSArray *)errorAttachments withIncidentIdentifier:(NSString *)incidentIdentifier; + +/** + * Track handled exception directly as model form. + * This API is used by wrapper SDKs. + * + * @param exception model form exception. + * @param properties dictionary of properties. + * @param attachments a list of attachments. + * + * @return handled error ID. + */ ++ (NSString *)trackModelException:(MSException *)exception + withProperties:(nullable NSDictionary *)properties + withAttachments:(nullable NSArray *)attachments; + +/** + * Get a generic error report representation for an handled exception. + * This API is used by wrapper SDKs. + * + * @param errorID handled error ID. + * + * @return an error report. + */ ++ (MSErrorReport *)buildHandledErrorReportWithErrorID:(NSString *)errorID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Modules/module.modulemap b/submodules/AppCenter/AppCenterCrashes.framework/Modules/module.modulemap new file mode 100644 index 0000000000..58d5076e5f --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Modules/module.modulemap @@ -0,0 +1,10 @@ +framework module AppCenterCrashes { + umbrella header "AppCenterCrashes.h" + + export * + module * { export * } + + link framework "Foundation" + link "c++" + link "z" +} diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Resources/Info.plist b/submodules/AppCenter/AppCenterCrashes.framework/Resources/Info.plist new file mode 100644 index 0000000000..347b70ea86 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenterCrashes + CFBundleIdentifier + com.microsoft.appcenter.crashes + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenterCrashes + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/AppCenterCrashes b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/AppCenterCrashes new file mode 100644 index 0000000000..8e7eef40d5 Binary files /dev/null and b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/AppCenterCrashes differ diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/AppCenterCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/AppCenterCrashes.h new file mode 100644 index 0000000000..d2920b3f15 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/AppCenterCrashes.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" +#import "MSCrashes.h" +#import "MSCrashesDelegate.h" +#import "MSErrorAttachmentLog+Utility.h" +#import "MSErrorAttachmentLog.h" +#import "MSWrapperCrashesHelper.h" diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashHandlerSetupDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashHandlerSetupDelegate.h new file mode 100644 index 0000000000..0a214be043 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashHandlerSetupDelegate.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * This is required for Wrapper SDKs that need to provide custom behavior surrounding the setup of crash handlers. + */ +@protocol MSCrashHandlerSetupDelegate + +@optional + +/** + * Callback method that will be called immediately before crash handlers are set up. + */ +- (void)willSetUpCrashHandlers; + +/** + * Callback method that will be called immediately after crash handlers are set up. + */ +- (void)didSetUpCrashHandlers; + +/** + * Callback method that gets a value indicating whether the SDK should enable an uncaught exception handler. + * + * @return YES if SDK should enable uncaught exception handler, otherwise NO. + * + * @discussion Do not register an UncaughtExceptionHandler for Xamarin as we rely on the Xamarin runtime to report NSExceptions. Registering + * our own UncaughtExceptionHandler will cause the Xamarin debugger to not work properly (it will not stop for NSExceptions). + */ +- (BOOL)shouldEnableUncaughtExceptionHandler; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashes.h new file mode 100644 index 0000000000..9e4ae86daa --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashes.h @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorReport.h" +#import "MSServiceAbstract.h" + +@class MSCrashesDelegate; + +/** + * Custom block that handles the alert that prompts the user whether crash reports need to be processed or not. + * + * @return Returns YES to discard crash reports, otherwise NO. + */ +typedef BOOL (^MSUserConfirmationHandler)(NSArray *_Nonnull errorReports); + +/** + * Error Logging status. + */ +typedef NS_ENUM(NSUInteger, MSErrorLogSetting) { + + /** + * Crash reporting is disabled. + */ + MSErrorLogSettingDisabled = 0, + + /** + * User is asked each time before sending error logs. + */ + MSErrorLogSettingAlwaysAsk = 1, + + /** + * Each error log is send automatically. + */ + MSErrorLogSettingAutoSend = 2 +}; + +/** + * Crash Manager alert user input. + */ +typedef NS_ENUM(NSUInteger, MSUserConfirmation) { + + /** + * User chose not to send the crash report. + */ + MSUserConfirmationDontSend = 0, + + /** + * User wants the crash report to be sent. + */ + MSUserConfirmationSend = 1, + + /** + * User wants to send all error logs. + */ + MSUserConfirmationAlways = 2 +}; + +@protocol MSCrashesDelegate; + +@interface MSCrashes : MSServiceAbstract + +///----------------------------------------------------------------------------- +/// @name Testing Crashes Feature +///----------------------------------------------------------------------------- + +/** + * Lets the app crash for easy testing of the SDK. + * + * The best way to use this is to trigger the crash with a button action. + * + * Make sure not to let the app crash in `applicationDidFinishLaunching` or any other startup method! Since otherwise the app would crash + * before the SDK could process it. + * + * Note that our SDK provides support for handling crashes that happen early on startup. Check the documentation for more information on how + * to use this. + * + * If the SDK detects an App Store environment, it will _NOT_ cause the app to crash! + */ ++ (void)generateTestCrash; + +///----------------------------------------------------------------------------- +/// @name Helpers +///----------------------------------------------------------------------------- + +/** + * Check if the app has crashed in the last session. + * + * @return Returns YES is the app has crashed in the last session. + */ ++ (BOOL)hasCrashedInLastSession; + +/** + * Check if the app received memory warning in the last session. + * + * @return Returns YES is the app received memory warning in the last session. + */ ++ (BOOL)hasReceivedMemoryWarningInLastSession; + +/** + * Provides details about the crash that occurred in the last app session + */ ++ (nullable MSErrorReport *)lastSessionCrashReport; + +///----------------------------------------------------------------------------- +/// @name Configuration +///----------------------------------------------------------------------------- + +#if !TARGET_OS_TV +/** + * Disable the Mach exception server. + * + * By default, the SDK uses the Mach exception handler to catch fatal signals, e.g. stack overflows, via a Mach exception server. If you + * want to disable the Mach exception handler, you should call this method _BEFORE_ starting the SDK. Your typical setup code would look + * like this: + * + * `[MSCrashes disableMachExceptionHandler]`; + * `[MSAppCenter start:@"YOUR_APP_ID" withServices:@[[MSCrashes class]]];` + * + * or if you are using Swift: + * + * `MSCrashes.disableMachExceptionHandler()` + * `MSAppCenter.start("YOUR_APP_ID", withServices: [MSAnalytics.self, MSCrashes.self])` + * + * tvOS does not support the Mach exception handler, thus crashes that are caused by stack overflows cannot be detected. As a result, + * disabling the Mach exception server is not available in the tvOS SDK. + * + * @discussion It can be useful to disable the Mach exception handler when you are debugging the Crashes service while developing, + * especially when you attach the debugger to your application after launch. + */ ++ (void)disableMachExceptionHandler; +#endif + +/** + * Set the delegate + * Defines the class that implements the optional protocol `MSCrashesDelegate`. + * + * @see MSCrashesDelegate + */ ++ (void)setDelegate:(_Nullable id)delegate; + +/** + * Set a user confirmation handler that is invoked right before processing crash reports to determine whether sending crash reports or not. + * + * @param userConfirmationHandler A handler for user confirmation. + * + * @see MSUserConfirmationHandler + */ ++ (void)setUserConfirmationHandler:(_Nullable MSUserConfirmationHandler)userConfirmationHandler; + +/** + * Notify SDK with a confirmation to handle the crash report. + * + * @param userConfirmation A user confirmation. + * + * @see MSUserConfirmation. + */ ++ (void)notifyWithUserConfirmation:(MSUserConfirmation)userConfirmation; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashesDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashesDelegate.h new file mode 100644 index 0000000000..ecc249f041 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSCrashesDelegate.h @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSCrashes; +@class MSErrorReport; +@class MSErrorAttachmentLog; + +@protocol MSCrashesDelegate + +@optional + +/** + * Callback method that will be called before processing errors. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Crashes will send logs to the server or discard/delete logs based on this method's return value. + */ +- (BOOL)crashes:(MSCrashes *)crashes shouldProcessErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called before each error will be send to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Use this callback to display custom UI while crashes are sent to the server. + */ +- (void)crashes:(MSCrashes *)crashes willSendErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center sent. + * + * @discussion Use this method to hide your custom UI. + */ +- (void)crashes:(MSCrashes *)crashes didSucceedSendingErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center tried to send. + * @param error The error that occurred. + */ +- (void)crashes:(MSCrashes *)crashes didFailSendingErrorReport:(MSErrorReport *)errorReport withError:(NSError *)error; + +/** + * Method to get the attachments associated to an error report. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport associated with the returned attachments. + * + * @return The attachments associated with the given error report or nil if the error report doesn't have any attachments. + * + * @discussion Implement this method if you want attachments to the given error report. + */ +- (NSArray *)attachmentsWithCrashes:(MSCrashes *)crashes forErrorReport:(MSErrorReport *)errorReport; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog+Utility.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog+Utility.h new file mode 100644 index 0000000000..585c353c53 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog+Utility.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorAttachmentLog.h" + +// Exporting symbols for category. +extern NSString *MSMSErrorLogAttachmentLogUtilityCategory; + +@interface MSErrorAttachmentLog (Utility) + +/** + * Create an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithText:(NSString *)text filename:(NSString *)filename; + +/** + * Create an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as NSData. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithBinary:(NSData *)data filename:(NSString *)filename contentType:(NSString *)contentType; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog.h new file mode 100644 index 0000000000..3ffc3c71e1 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorAttachmentLog.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" + +/** + * Error attachment log. + */ +@interface MSErrorAttachmentLog : MSAbstractLog + +/** + * Content type (text/plain for text). + */ +@property(nonatomic, copy) NSString *contentType; + +/** + * File name. + */ +@property(nonatomic, copy) NSString *filename; + +/** + * The attachment data. + */ +@property(nonatomic, copy) NSData *data; + +/** + * Initialize an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as `NSData`. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentBinary:(NSData *)data contentType:(NSString *)contentType; + +/** + * Initialize an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentText:(NSString *)text; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorReport.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorReport.h new file mode 100644 index 0000000000..cf524a269a --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSErrorReport.h @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSDevice; + +@interface MSErrorReport : NSObject + +/** + * UUID for the crash report. + */ +@property(nonatomic, copy, readonly) NSString *incidentIdentifier; + +/** + * UUID for the app installation on the device. + */ +@property(nonatomic, copy, readonly) NSString *reporterKey; + +/** + * Signal that caused the crash. + */ +@property(nonatomic, copy, readonly) NSString *signal; + +/** + * Exception name that triggered the crash, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionName; + +/** + * Exception reason, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionReason; + +/** + * Date and time the app started, nil if unknown. + */ +@property(nonatomic, readonly, strong) NSDate *appStartTime; + +/** + * Date and time the error occurred, nil if unknown + */ +@property(nonatomic, readonly, strong) NSDate *appErrorTime; + +/** + * Device information of the app when it crashed. + */ +@property(nonatomic, readonly, strong) MSDevice *device; + +/** + * Identifier of the app process that crashed. + */ +@property(nonatomic, readonly, assign) NSUInteger appProcessIdentifier; + +/** + * Indicates if the app was killed while being in foreground from the iOS. + * + * This can happen if it consumed too much memory or the watchdog killed the app because it took too long to startup or blocks the main + * thread for too long, or other reasons. See Apple documentation: + * https://developer.apple.com/library/ios/qa/qa1693/_index.html. + * + * @return YES if the details represent an app kill instead of a crash. + * + * @see `[MSCrashes didReceiveMemoryWarningInLastSession]` + */ +- (BOOL)isAppKill; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSService.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSWrapperCrashesHelper.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSWrapperCrashesHelper.h new file mode 100644 index 0000000000..d380ed4423 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Headers/MSWrapperCrashesHelper.h @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSErrorReport; +@class MSErrorAttachmentLog; +@class MSException; + +/** + * This general class allows wrappers to supplement the Crashes SDK with their own behavior. + */ +@interface MSWrapperCrashesHelper : NSObject + +/** + * Sets the crash handler setup delegate. + * + * @param delegate The delegate to set. + */ ++ (void)setCrashHandlerSetupDelegate:(id)delegate; + +/** + * Gets the crash handler setup delegate. + * + * @return The delegate being used by Crashes. + */ ++ (id)getCrashHandlerSetupDelegate; + +/** + * Enables or disables automatic crash processing. + * + * @param automaticProcessing Passing NO causes SDK not to send reports immediately, even if "Always Send" is true. + */ ++ (void)setAutomaticProcessing:(BOOL)automaticProcessing; + +/** + * Gets a list of unprocessed crash reports. Will block until the service starts. + * + * @return An array of unprocessed error reports. + */ ++ (NSArray *)unprocessedCrashReports; + +/** + * Resumes processing for a given subset of the unprocessed reports. + * + * @param filteredIds An array containing the errorId/incidentIdentifier of each report that should be sent. + * + * @return YES if should "Always Send" is true. + */ ++ (BOOL)sendCrashReportsOrAwaitUserConfirmationForFilteredIds:(NSArray *)filteredIds; + +/** + * Sends error attachments for a particular error report. + * + * @param errorAttachments An array of error attachments that should be sent. + * @param incidentIdentifier The identifier of the error report that the attachments will be associated with. + */ ++ (void)sendErrorAttachments:(NSArray *)errorAttachments withIncidentIdentifier:(NSString *)incidentIdentifier; + +/** + * Track handled exception directly as model form. + * This API is used by wrapper SDKs. + * + * @param exception model form exception. + * @param properties dictionary of properties. + * @param attachments a list of attachments. + * + * @return handled error ID. + */ ++ (NSString *)trackModelException:(MSException *)exception + withProperties:(nullable NSDictionary *)properties + withAttachments:(nullable NSArray *)attachments; + +/** + * Get a generic error report representation for an handled exception. + * This API is used by wrapper SDKs. + * + * @param errorID handled error ID. + * + * @return an error report. + */ ++ (MSErrorReport *)buildHandledErrorReportWithErrorID:(NSString *)errorID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Modules/module.modulemap b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000000..58d5076e5f --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,10 @@ +framework module AppCenterCrashes { + umbrella header "AppCenterCrashes.h" + + export * + module * { export * } + + link framework "Foundation" + link "c++" + link "z" +} diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Resources/Info.plist b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000000..347b70ea86 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenterCrashes + CFBundleIdentifier + com.microsoft.appcenter.crashes + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenterCrashes + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/AppCenterCrashes b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/AppCenterCrashes new file mode 100644 index 0000000000..8e7eef40d5 Binary files /dev/null and b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/AppCenterCrashes differ diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/AppCenterCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/AppCenterCrashes.h new file mode 100644 index 0000000000..d2920b3f15 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/AppCenterCrashes.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" +#import "MSCrashes.h" +#import "MSCrashesDelegate.h" +#import "MSErrorAttachmentLog+Utility.h" +#import "MSErrorAttachmentLog.h" +#import "MSWrapperCrashesHelper.h" diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSAbstractLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSAbstractLog.h new file mode 100644 index 0000000000..76d026853c --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSAbstractLog.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_ABSTRACT_LOG_H +#define MS_ABSTRACT_LOG_H + +#import + +@interface MSAbstractLog : NSObject + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashHandlerSetupDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashHandlerSetupDelegate.h new file mode 100644 index 0000000000..0a214be043 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashHandlerSetupDelegate.h @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +/** + * This is required for Wrapper SDKs that need to provide custom behavior surrounding the setup of crash handlers. + */ +@protocol MSCrashHandlerSetupDelegate + +@optional + +/** + * Callback method that will be called immediately before crash handlers are set up. + */ +- (void)willSetUpCrashHandlers; + +/** + * Callback method that will be called immediately after crash handlers are set up. + */ +- (void)didSetUpCrashHandlers; + +/** + * Callback method that gets a value indicating whether the SDK should enable an uncaught exception handler. + * + * @return YES if SDK should enable uncaught exception handler, otherwise NO. + * + * @discussion Do not register an UncaughtExceptionHandler for Xamarin as we rely on the Xamarin runtime to report NSExceptions. Registering + * our own UncaughtExceptionHandler will cause the Xamarin debugger to not work properly (it will not stop for NSExceptions). + */ +- (BOOL)shouldEnableUncaughtExceptionHandler; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashes.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashes.h new file mode 100644 index 0000000000..9e4ae86daa --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashes.h @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorReport.h" +#import "MSServiceAbstract.h" + +@class MSCrashesDelegate; + +/** + * Custom block that handles the alert that prompts the user whether crash reports need to be processed or not. + * + * @return Returns YES to discard crash reports, otherwise NO. + */ +typedef BOOL (^MSUserConfirmationHandler)(NSArray *_Nonnull errorReports); + +/** + * Error Logging status. + */ +typedef NS_ENUM(NSUInteger, MSErrorLogSetting) { + + /** + * Crash reporting is disabled. + */ + MSErrorLogSettingDisabled = 0, + + /** + * User is asked each time before sending error logs. + */ + MSErrorLogSettingAlwaysAsk = 1, + + /** + * Each error log is send automatically. + */ + MSErrorLogSettingAutoSend = 2 +}; + +/** + * Crash Manager alert user input. + */ +typedef NS_ENUM(NSUInteger, MSUserConfirmation) { + + /** + * User chose not to send the crash report. + */ + MSUserConfirmationDontSend = 0, + + /** + * User wants the crash report to be sent. + */ + MSUserConfirmationSend = 1, + + /** + * User wants to send all error logs. + */ + MSUserConfirmationAlways = 2 +}; + +@protocol MSCrashesDelegate; + +@interface MSCrashes : MSServiceAbstract + +///----------------------------------------------------------------------------- +/// @name Testing Crashes Feature +///----------------------------------------------------------------------------- + +/** + * Lets the app crash for easy testing of the SDK. + * + * The best way to use this is to trigger the crash with a button action. + * + * Make sure not to let the app crash in `applicationDidFinishLaunching` or any other startup method! Since otherwise the app would crash + * before the SDK could process it. + * + * Note that our SDK provides support for handling crashes that happen early on startup. Check the documentation for more information on how + * to use this. + * + * If the SDK detects an App Store environment, it will _NOT_ cause the app to crash! + */ ++ (void)generateTestCrash; + +///----------------------------------------------------------------------------- +/// @name Helpers +///----------------------------------------------------------------------------- + +/** + * Check if the app has crashed in the last session. + * + * @return Returns YES is the app has crashed in the last session. + */ ++ (BOOL)hasCrashedInLastSession; + +/** + * Check if the app received memory warning in the last session. + * + * @return Returns YES is the app received memory warning in the last session. + */ ++ (BOOL)hasReceivedMemoryWarningInLastSession; + +/** + * Provides details about the crash that occurred in the last app session + */ ++ (nullable MSErrorReport *)lastSessionCrashReport; + +///----------------------------------------------------------------------------- +/// @name Configuration +///----------------------------------------------------------------------------- + +#if !TARGET_OS_TV +/** + * Disable the Mach exception server. + * + * By default, the SDK uses the Mach exception handler to catch fatal signals, e.g. stack overflows, via a Mach exception server. If you + * want to disable the Mach exception handler, you should call this method _BEFORE_ starting the SDK. Your typical setup code would look + * like this: + * + * `[MSCrashes disableMachExceptionHandler]`; + * `[MSAppCenter start:@"YOUR_APP_ID" withServices:@[[MSCrashes class]]];` + * + * or if you are using Swift: + * + * `MSCrashes.disableMachExceptionHandler()` + * `MSAppCenter.start("YOUR_APP_ID", withServices: [MSAnalytics.self, MSCrashes.self])` + * + * tvOS does not support the Mach exception handler, thus crashes that are caused by stack overflows cannot be detected. As a result, + * disabling the Mach exception server is not available in the tvOS SDK. + * + * @discussion It can be useful to disable the Mach exception handler when you are debugging the Crashes service while developing, + * especially when you attach the debugger to your application after launch. + */ ++ (void)disableMachExceptionHandler; +#endif + +/** + * Set the delegate + * Defines the class that implements the optional protocol `MSCrashesDelegate`. + * + * @see MSCrashesDelegate + */ ++ (void)setDelegate:(_Nullable id)delegate; + +/** + * Set a user confirmation handler that is invoked right before processing crash reports to determine whether sending crash reports or not. + * + * @param userConfirmationHandler A handler for user confirmation. + * + * @see MSUserConfirmationHandler + */ ++ (void)setUserConfirmationHandler:(_Nullable MSUserConfirmationHandler)userConfirmationHandler; + +/** + * Notify SDK with a confirmation to handle the crash report. + * + * @param userConfirmation A user confirmation. + * + * @see MSUserConfirmation. + */ ++ (void)notifyWithUserConfirmation:(MSUserConfirmation)userConfirmation; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashesDelegate.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashesDelegate.h new file mode 100644 index 0000000000..ecc249f041 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSCrashesDelegate.h @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSCrashes; +@class MSErrorReport; +@class MSErrorAttachmentLog; + +@protocol MSCrashesDelegate + +@optional + +/** + * Callback method that will be called before processing errors. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Crashes will send logs to the server or discard/delete logs based on this method's return value. + */ +- (BOOL)crashes:(MSCrashes *)crashes shouldProcessErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called before each error will be send to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that will be sent. + * + * @discussion Use this callback to display custom UI while crashes are sent to the server. + */ +- (void)crashes:(MSCrashes *)crashes willSendErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center sent. + * + * @discussion Use this method to hide your custom UI. + */ +- (void)crashes:(MSCrashes *)crashes didSucceedSendingErrorReport:(MSErrorReport *)errorReport; + +/** + * Callback method that will be called in case the SDK was unable to send an error report to the server. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport that App Center tried to send. + * @param error The error that occurred. + */ +- (void)crashes:(MSCrashes *)crashes didFailSendingErrorReport:(MSErrorReport *)errorReport withError:(NSError *)error; + +/** + * Method to get the attachments associated to an error report. + * + * @param crashes The instance of MSCrashes. + * @param errorReport The errorReport associated with the returned attachments. + * + * @return The attachments associated with the given error report or nil if the error report doesn't have any attachments. + * + * @discussion Implement this method if you want attachments to the given error report. + */ +- (NSArray *)attachmentsWithCrashes:(MSCrashes *)crashes forErrorReport:(MSErrorReport *)errorReport; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog+Utility.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog+Utility.h new file mode 100644 index 0000000000..585c353c53 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog+Utility.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import "MSErrorAttachmentLog.h" + +// Exporting symbols for category. +extern NSString *MSMSErrorLogAttachmentLogUtilityCategory; + +@interface MSErrorAttachmentLog (Utility) + +/** + * Create an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithText:(NSString *)text filename:(NSString *)filename; + +/** + * Create an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as NSData. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ ++ (MSErrorAttachmentLog *)attachmentWithBinary:(NSData *)data filename:(NSString *)filename contentType:(NSString *)contentType; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog.h new file mode 100644 index 0000000000..3ffc3c71e1 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorAttachmentLog.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSAbstractLog.h" + +/** + * Error attachment log. + */ +@interface MSErrorAttachmentLog : MSAbstractLog + +/** + * Content type (text/plain for text). + */ +@property(nonatomic, copy) NSString *contentType; + +/** + * File name. + */ +@property(nonatomic, copy) NSString *filename; + +/** + * The attachment data. + */ +@property(nonatomic, copy) NSData *data; + +/** + * Initialize an attachment with a given filename and `NSData` object. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param data The attachment data as `NSData`. + * @param contentType The content type of your data as MIME type. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentBinary:(NSData *)data contentType:(NSString *)contentType; + +/** + * Initialize an attachment with a given filename and text. + * + * @param filename The filename the attachment should get. If nil will get an automatically generated filename. + * @param text The attachment text. + * + * @return An instance of `MSErrorAttachmentLog`. + */ +- (instancetype)initWithFilename:(NSString *)filename attachmentText:(NSString *)text; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorReport.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorReport.h new file mode 100644 index 0000000000..cf524a269a --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSErrorReport.h @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +@class MSDevice; + +@interface MSErrorReport : NSObject + +/** + * UUID for the crash report. + */ +@property(nonatomic, copy, readonly) NSString *incidentIdentifier; + +/** + * UUID for the app installation on the device. + */ +@property(nonatomic, copy, readonly) NSString *reporterKey; + +/** + * Signal that caused the crash. + */ +@property(nonatomic, copy, readonly) NSString *signal; + +/** + * Exception name that triggered the crash, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionName; + +/** + * Exception reason, nil if the crash was not caused by an exception. + */ +@property(nonatomic, copy, readonly) NSString *exceptionReason; + +/** + * Date and time the app started, nil if unknown. + */ +@property(nonatomic, readonly, strong) NSDate *appStartTime; + +/** + * Date and time the error occurred, nil if unknown + */ +@property(nonatomic, readonly, strong) NSDate *appErrorTime; + +/** + * Device information of the app when it crashed. + */ +@property(nonatomic, readonly, strong) MSDevice *device; + +/** + * Identifier of the app process that crashed. + */ +@property(nonatomic, readonly, assign) NSUInteger appProcessIdentifier; + +/** + * Indicates if the app was killed while being in foreground from the iOS. + * + * This can happen if it consumed too much memory or the watchdog killed the app because it took too long to startup or blocks the main + * thread for too long, or other reasons. See Apple documentation: + * https://developer.apple.com/library/ios/qa/qa1693/_index.html. + * + * @return YES if the details represent an app kill instead of a crash. + * + * @see `[MSCrashes didReceiveMemoryWarningInLastSession]` + */ +- (BOOL)isAppKill; + +@end diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSService.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSService.h new file mode 100644 index 0000000000..59997b8233 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSService.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_H +#define MS_SERVICE_H + +#import + +/** + * Protocol declaring service logic. + */ +@protocol MSService + +/** + * Enable or disable this service. + * The state is persisted in the device's storage across application launches. + * + * @param isEnabled Whether this service is enabled or not. + * + * @see isEnabled + */ ++ (void)setEnabled:(BOOL)isEnabled; + +/** + * Indicates whether this service is enabled. + * + * @return `YES` if this service is enabled, `NO` if it is not. + * + * @see setEnabled: + */ ++ (BOOL)isEnabled; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSServiceAbstract.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSServiceAbstract.h new file mode 100644 index 0000000000..3e5e8decf5 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSServiceAbstract.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef MS_SERVICE_ABSTRACT_H +#define MS_SERVICE_ABSTRACT_H + +#import + +#import "MSService.h" + +@protocol MSChannelGroupProtocol; + +/** + * Abstraction of services common logic. + * This class is intended to be subclassed only not instantiated directly. + */ +@interface MSServiceAbstract : NSObject + +/** + * The flag indicates whether the service is started from application or not. + */ +@property(nonatomic, assign) BOOL startedFromApplication; + +/** + * Start this service with a channel group. Also sets the flag that indicates that a service has been started. + * + * @param channelGroup channel group used to persist and send logs. + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + * @param fromApplication indicates whether the service started from an application or not. + */ +- (void)startWithChannelGroup:(id)channelGroup + appSecret:(NSString *)appSecret + transmissionTargetToken:(NSString *)token + fromApplication:(BOOL)fromApplication; + +/** + * Update configuration when the service requires to start again. This method should only be called if the service is started from libraries + * and then is being started from an application. + * + * @param appSecret app secret for the SDK. + * @param token default transmission target token for this service. + */ +- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token; + +/** + * Checks if the service needs the application secret. + * + * @return `YES` if the application secret is required, `NO` otherwise. + */ +- (BOOL)isAppSecretRequired; + +@end + +#endif diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSWrapperCrashesHelper.h b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSWrapperCrashesHelper.h new file mode 100644 index 0000000000..d380ed4423 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Headers/MSWrapperCrashesHelper.h @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#import + +#import "MSCrashHandlerSetupDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MSErrorReport; +@class MSErrorAttachmentLog; +@class MSException; + +/** + * This general class allows wrappers to supplement the Crashes SDK with their own behavior. + */ +@interface MSWrapperCrashesHelper : NSObject + +/** + * Sets the crash handler setup delegate. + * + * @param delegate The delegate to set. + */ ++ (void)setCrashHandlerSetupDelegate:(id)delegate; + +/** + * Gets the crash handler setup delegate. + * + * @return The delegate being used by Crashes. + */ ++ (id)getCrashHandlerSetupDelegate; + +/** + * Enables or disables automatic crash processing. + * + * @param automaticProcessing Passing NO causes SDK not to send reports immediately, even if "Always Send" is true. + */ ++ (void)setAutomaticProcessing:(BOOL)automaticProcessing; + +/** + * Gets a list of unprocessed crash reports. Will block until the service starts. + * + * @return An array of unprocessed error reports. + */ ++ (NSArray *)unprocessedCrashReports; + +/** + * Resumes processing for a given subset of the unprocessed reports. + * + * @param filteredIds An array containing the errorId/incidentIdentifier of each report that should be sent. + * + * @return YES if should "Always Send" is true. + */ ++ (BOOL)sendCrashReportsOrAwaitUserConfirmationForFilteredIds:(NSArray *)filteredIds; + +/** + * Sends error attachments for a particular error report. + * + * @param errorAttachments An array of error attachments that should be sent. + * @param incidentIdentifier The identifier of the error report that the attachments will be associated with. + */ ++ (void)sendErrorAttachments:(NSArray *)errorAttachments withIncidentIdentifier:(NSString *)incidentIdentifier; + +/** + * Track handled exception directly as model form. + * This API is used by wrapper SDKs. + * + * @param exception model form exception. + * @param properties dictionary of properties. + * @param attachments a list of attachments. + * + * @return handled error ID. + */ ++ (NSString *)trackModelException:(MSException *)exception + withProperties:(nullable NSDictionary *)properties + withAttachments:(nullable NSArray *)attachments; + +/** + * Get a generic error report representation for an handled exception. + * This API is used by wrapper SDKs. + * + * @param errorID handled error ID. + * + * @return an error report. + */ ++ (MSErrorReport *)buildHandledErrorReportWithErrorID:(NSString *)errorID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Modules/module.modulemap b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Modules/module.modulemap new file mode 100644 index 0000000000..58d5076e5f --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Modules/module.modulemap @@ -0,0 +1,10 @@ +framework module AppCenterCrashes { + umbrella header "AppCenterCrashes.h" + + export * + module * { export * } + + link framework "Foundation" + link "c++" + link "z" +} diff --git a/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Resources/Info.plist b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Resources/Info.plist new file mode 100644 index 0000000000..347b70ea86 --- /dev/null +++ b/submodules/AppCenter/AppCenterCrashes.framework/Versions/Current/Resources/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 18G103 + CFBundleDevelopmentRegion + English + CFBundleExecutable + AppCenterCrashes + CFBundleIdentifier + com.microsoft.appcenter.crashes + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppCenterCrashes + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 10B61 + DTPlatformVersion + GM + DTSDKBuild + 18B71 + DTSDKName + macosx10.14 + DTXcode + 1010 + DTXcodeBuild + 10B61 + + diff --git a/submodules/GraphUI/Graph.xcodeproj/project.pbxproj b/submodules/GraphUI/Graph.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..4998639607 --- /dev/null +++ b/submodules/GraphUI/Graph.xcodeproj/project.pbxproj @@ -0,0 +1,974 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7918DA22409349F002011CA /* GraphUI.h in Headers */ = {isa = PBXBuildFile; fileRef = A7918DA02409349F002011CA /* GraphUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7918DA9240934F4002011CA /* ChartDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C7216923FD850100CE3F75 /* ChartDetailsView.swift */; }; + A7918DAA240934F4002011CA /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C7216523FD850100CE3F75 /* ChartView.swift */; }; + A7918DAB240934F4002011CA /* ChartStackSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B212403E0140056AEAC /* ChartStackSection.swift */; }; + A7918DAC240934F4002011CA /* ChartVisibilityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B232403E1830056AEAC /* ChartVisibilityItemView.swift */; }; + A7918DAD240934F4002011CA /* ChartVisibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B252403E2B30056AEAC /* ChartVisibilityView.swift */; }; + A7918DAE240934F4002011CA /* RangeChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7831B272403E3F90056AEAC /* RangeChartView.swift */; }; + A7918DAF240934F4002011CA /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789E6F223E8427000AEB34A /* UIView+Extensions.swift */; }; + A7918DB0240934F4002011CA /* UIImageView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789E6F623E8427000AEB34A /* UIImageView+Utils.swift */; }; + A7918DB1240934F4002011CA /* UIImage+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789E6FC23E8427000AEB34A /* UIImage+Utils.swift */; }; + A7918DB2240934F4002011CA /* UILabel+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789E6FF23E8427000AEB34A /* UILabel+Utils.swift */; }; + A791913C240D3822002011CA /* GraphCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7393CB72406765300CE44CA /* GraphCore.framework */; }; + D008C5A624093BDA003617EA /* TGUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7831B152403BFE30056AEAC /* TGUIKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7393CB72406765300CE44CA /* GraphCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GraphCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7831B152403BFE30056AEAC /* TGUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TGUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7831B212403E0140056AEAC /* ChartStackSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartStackSection.swift; sourceTree = ""; }; + A7831B232403E1830056AEAC /* ChartVisibilityItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartVisibilityItemView.swift; sourceTree = ""; }; + A7831B252403E2B30056AEAC /* ChartVisibilityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartVisibilityView.swift; sourceTree = ""; }; + A7831B272403E3F90056AEAC /* RangeChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeChartView.swift; sourceTree = ""; }; + A789E0C523E841B600AEB34A /* Graph.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Graph.h; sourceTree = ""; }; + A789E0C623E841B600AEB34A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A789E6F223E8427000AEB34A /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; + A789E6F623E8427000AEB34A /* UIImageView+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Utils.swift"; sourceTree = ""; }; + A789E6FC23E8427000AEB34A /* UIImage+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Utils.swift"; sourceTree = ""; }; + A789E6FF23E8427000AEB34A /* UILabel+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+Utils.swift"; sourceTree = ""; }; + A7918D9E2409349F002011CA /* GraphUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GraphUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7918DA02409349F002011CA /* GraphUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphUI.h; sourceTree = ""; }; + A7918DA12409349F002011CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7C7216523FD850100CE3F75 /* ChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = ""; }; + A7C7216923FD850100CE3F75 /* ChartDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDetailsView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7918D9B2409349F002011CA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A791913C240D3822002011CA /* GraphCore.framework in Frameworks */, + D008C5A624093BDA003617EA /* TGUIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7831B142403BFE30056AEAC /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7393CB72406765300CE44CA /* GraphCore.framework */, + A7831B152403BFE30056AEAC /* TGUIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A789E0B823E841B600AEB34A = { + isa = PBXGroup; + children = ( + A789E0C423E841B600AEB34A /* Graph */, + A7918D9F2409349F002011CA /* GraphUI */, + A789E0C323E841B600AEB34A /* Products */, + A7831B142403BFE30056AEAC /* Frameworks */, + ); + sourceTree = ""; + }; + A789E0C323E841B600AEB34A /* Products */ = { + isa = PBXGroup; + children = ( + A7918D9E2409349F002011CA /* GraphUI.framework */, + ); + name = Products; + sourceTree = ""; + }; + A789E0C423E841B600AEB34A /* Graph */ = { + isa = PBXGroup; + children = ( + A789E0CD23E8425200AEB34A /* src */, + A789E0C523E841B600AEB34A /* Graph.h */, + A789E0C623E841B600AEB34A /* Info.plist */, + ); + path = Graph; + sourceTree = ""; + }; + A789E0CD23E8425200AEB34A /* src */ = { + isa = PBXGroup; + children = ( + A789E0D823E8426E00AEB34A /* Charts */, + A789E6EF23E8427000AEB34A /* Helpers */, + ); + path = src; + sourceTree = ""; + }; + A789E0D823E8426E00AEB34A /* Charts */ = { + isa = PBXGroup; + children = ( + A7C7216423FD84F900CE3F75 /* views */, + ); + path = Charts; + sourceTree = ""; + }; + A789E6EF23E8427000AEB34A /* Helpers */ = { + isa = PBXGroup; + children = ( + A789E6F223E8427000AEB34A /* UIView+Extensions.swift */, + A789E6F623E8427000AEB34A /* UIImageView+Utils.swift */, + A789E6FC23E8427000AEB34A /* UIImage+Utils.swift */, + A789E6FF23E8427000AEB34A /* UILabel+Utils.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + A7918D9F2409349F002011CA /* GraphUI */ = { + isa = PBXGroup; + children = ( + A7918DA02409349F002011CA /* GraphUI.h */, + A7918DA12409349F002011CA /* Info.plist */, + ); + path = GraphUI; + sourceTree = ""; + }; + A7C7216423FD84F900CE3F75 /* views */ = { + isa = PBXGroup; + children = ( + A7C7216923FD850100CE3F75 /* ChartDetailsView.swift */, + A7C7216523FD850100CE3F75 /* ChartView.swift */, + A7831B212403E0140056AEAC /* ChartStackSection.swift */, + A7831B232403E1830056AEAC /* ChartVisibilityItemView.swift */, + A7831B252403E2B30056AEAC /* ChartVisibilityView.swift */, + A7831B272403E3F90056AEAC /* RangeChartView.swift */, + ); + path = views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7918D992409349F002011CA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DA22409349F002011CA /* GraphUI.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7918D9D2409349F002011CA /* GraphUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7918DA32409349F002011CA /* Build configuration list for PBXNativeTarget "GraphUI" */; + buildPhases = ( + A7918D992409349F002011CA /* Headers */, + A7918D9A2409349F002011CA /* Sources */, + A7918D9B2409349F002011CA /* Frameworks */, + A7918D9C2409349F002011CA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GraphUI; + productName = GraphUI; + productReference = A7918D9E2409349F002011CA /* GraphUI.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A789E0B923E841B600AEB34A /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7918D9D2409349F002011CA = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A789E0BC23E841B600AEB34A /* Build configuration list for PBXProject "Graph" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A789E0B823E841B600AEB34A; + productRefGroup = A789E0C323E841B600AEB34A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7918D9D2409349F002011CA /* GraphUI */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7918D9C2409349F002011CA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7918D9A2409349F002011CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7918DA9240934F4002011CA /* ChartDetailsView.swift in Sources */, + A7918DAA240934F4002011CA /* ChartView.swift in Sources */, + A7918DAB240934F4002011CA /* ChartStackSection.swift in Sources */, + A7918DAC240934F4002011CA /* ChartVisibilityItemView.swift in Sources */, + A7918DAD240934F4002011CA /* ChartVisibilityView.swift in Sources */, + A7918DAE240934F4002011CA /* RangeChartView.swift in Sources */, + A7918DAF240934F4002011CA /* UIView+Extensions.swift in Sources */, + A7918DB0240934F4002011CA /* UIImageView+Utils.swift in Sources */, + A7918DB1240934F4002011CA /* UIImage+Utils.swift in Sources */, + A7918DB2240934F4002011CA /* UILabel+Utils.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7393D472409320000CE44CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7393D492409320B00CE44CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7393D4B2409321200CE44CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A789E0C823E841B600AEB34A /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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 = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A789E0C923E841B600AEB34A /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7918DA42409349F002011CA /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + INFOPLIST_FILE = GraphUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.GraphUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7918DA52409349F002011CA /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; + 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; + INFOPLIST_FILE = GraphUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.GraphUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7918DA62409349F002011CA /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ""; + 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; + INFOPLIST_FILE = GraphUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.GraphUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.2; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7918DA72409349F002011CA /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = GraphUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.GraphUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.2; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7918DA82409349F002011CA /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + 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; + INFOPLIST_FILE = GraphUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.GraphUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.2; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A789E0BC23E841B600AEB34A /* Build configuration list for PBXProject "Graph" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A789E0C823E841B600AEB34A /* DebugAppStore */, + A7393D4B2409321200CE44CA /* DebugHockeyapp */, + A7393D492409320B00CE44CA /* HockeyappMacAlpha */, + A789E0C923E841B600AEB34A /* ReleaseHockeyapp */, + A7393D472409320000CE44CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A7918DA32409349F002011CA /* Build configuration list for PBXNativeTarget "GraphUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7918DA42409349F002011CA /* DebugAppStore */, + A7918DA52409349F002011CA /* DebugHockeyapp */, + A7918DA62409349F002011CA /* HockeyappMacAlpha */, + A7918DA72409349F002011CA /* ReleaseHockeyapp */, + A7918DA82409349F002011CA /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A789E0B923E841B600AEB34A /* Project object */; +} diff --git a/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..8f31ea705e --- /dev/null +++ b/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/submodules/GraphUI/Graph.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/submodules/GraphUI/Graph/Graph.h b/submodules/GraphUI/Graph/Graph.h new file mode 100644 index 0000000000..e4b99a3ca3 --- /dev/null +++ b/submodules/GraphUI/Graph/Graph.h @@ -0,0 +1,19 @@ +// +// Graph.h +// Graph +// +// Created by Mikhail Filimonov on 03.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for Graph. +FOUNDATION_EXPORT double GraphVersionNumber; + +//! Project version string for Graph. +FOUNDATION_EXPORT const unsigned char GraphVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/submodules/GraphUI/Graph/Info.plist b/submodules/GraphUI/Graph/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/submodules/GraphUI/Graph/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/submodules/GraphUI/Graph/src/Charts/views/ChartDetailsView.swift b/submodules/GraphUI/Graph/src/Charts/views/ChartDetailsView.swift new file mode 100644 index 0000000000..cfb1c6dca1 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/ChartDetailsView.swift @@ -0,0 +1,289 @@ +// +// ChartDetailsView.swift +// GraphTest +// +// Created by Andrew Solovey on 14/03/2019. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +private let cornerRadius: CGFloat = 5 +private let verticalMargins: CGFloat = 8 +private var labelHeight: CGFloat = 18 +private var margin: CGFloat = 10 +private var prefixLabelWidth: CGFloat = 35 +private var valueLabelWidth: CGFloat = 65 + + +class ChartDetailsView: Control { + + + override var alphaValue: CGFloat { + didSet { + self.isHidden = self.alphaValue == 0 + } + } + + override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + } + + let titleLabel = TransparentTextField() + let arrowView = TransparentImageView() + + var prefixViews: [TransparentTextField] = [] + var labelsViews: [TransparentTextField] = [] + var valuesViews: [TransparentTextField] = [] + + private var viewModel: ChartDetailsViewModel? + private var theme: ChartTheme = .defaultDayTheme + + + required init(frame: CGRect) { + super.init(frame: frame) + + layer?.cornerRadius = cornerRadius + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.allowsDefaultTighteningForTruncation = true + titleLabel.drawsBackground = false + titleLabel.lineBreakMode = .byTruncatingTail + titleLabel.font = NSFont.systemFont(ofSize: 12, weight: .bold) + arrowView.image = NSImage.arrowRight + arrowView.imageScaling = .scaleAxesIndependently + addSubview(titleLabel) + addSubview(arrowView) + + set(handler: { [weak self] _ in + self?.viewModel?.tapAction?() + }, for: .Click) + + } + + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup(viewModel: ChartDetailsViewModel, animated: Bool) { + self.viewModel = viewModel + + titleLabel.setText(viewModel.title, animated: animated) + titleLabel.setVisible(!viewModel.title.isEmpty, animated: animated) + arrowView.setVisible(viewModel.showArrow, animated: animated) + + let textLabelWidth = max(TitleButton.size(with: viewModel.title, font: NSFont.systemFont(ofSize: 12, weight: .bold)).width, 60) + + let viewWidth = intrinsicContentSize.width + + let width: CGFloat = margin * 2 + (viewModel.showPrefixes ? (prefixLabelWidth + margin) : 0) + textLabelWidth + valueLabelWidth + var y: CGFloat = verticalMargins + + if (!viewModel.title.isEmpty || viewModel.showArrow) { + titleLabel.frame = CGRect(x: margin, y: y, width: width, height: labelHeight) + arrowView.frame = CGRect(x: viewWidth - 6 - margin, y: margin, width: 6, height: 10) + y += labelHeight + } + let labelsCount: Int = viewModel.values.count + ((viewModel.totalValue == nil) ? 0 : 1) + + setLabelsCount(array: &prefixViews, + count: viewModel.showPrefixes ? labelsCount : 0, + font: NSFont.systemFont(ofSize: 12, weight: .bold)) + setLabelsCount(array: &labelsViews, + count: labelsCount, + font: NSFont.systemFont(ofSize: 12, weight: .regular), + textAlignment: .left) + setLabelsCount(array: &valuesViews, + count: labelsCount, + font: NSFont.systemFont(ofSize: 12, weight: .bold)) + + View.perform(animated: animated, animations: { + for (index, value) in viewModel.values.enumerated() { + var x: CGFloat = margin + if viewModel.showPrefixes { + let prefixLabel = self.prefixViews[index] + prefixLabel.textColor = self.theme.chartDetailsTextColor + prefixLabel.setText(value.prefix, animated: false) + prefixLabel.frame = CGRect(x: x, y: y, width: prefixLabelWidth, height: labelHeight) + x += prefixLabelWidth + margin + prefixLabel.alphaValue = value.visible ? 1 : 0 + } + let titleLabel = self.labelsViews[index] + + let textLabelWidth = max(TitleButton.size(with: value.title, font: NSFont.systemFont(ofSize: 12, weight: .regular)).width, 60) + + titleLabel.setTextColor(self.theme.chartDetailsTextColor, animated: false) + titleLabel.setText(value.title, animated: false) + titleLabel.frame = CGRect(x: x, y: y, width: textLabelWidth, height: labelHeight) + titleLabel.alphaValue = value.visible ? 1 : 0 + x += textLabelWidth + + let valueLabel = self.valuesViews[index] + valueLabel.setTextColor(value.color, animated: false) + valueLabel.setText(value.value, animated: false) + valueLabel.frame = CGRect(x: viewWidth - valueLabelWidth - margin, y: y, width: valueLabelWidth, height: labelHeight) + valueLabel.alphaValue = value.visible ? 1 : 0 + + if value.visible { + y += labelHeight + } + } + if let value = viewModel.totalValue { + var x: CGFloat = margin + if viewModel.showPrefixes { + let prefixLabel = self.prefixViews[viewModel.values.count] + prefixLabel.textColor = self.theme.chartDetailsTextColor + prefixLabel.setText(value.prefix, animated: false) + prefixLabel.frame = CGRect(x: x, y: y, width: prefixLabelWidth, height: labelHeight) + prefixLabel.alphaValue = value.visible ? 1 : 0 + x += prefixLabelWidth + margin + } + let titleLabel = self.labelsViews[viewModel.values.count] + titleLabel.setTextColor(self.theme.chartDetailsTextColor, animated: false) + titleLabel.setText(value.title, animated: false) + + let textLabelWidth = max(TitleButton.size(with: viewModel.title, font: NSFont.systemFont(ofSize: 12, weight: .regular)).width, 60) + + titleLabel.frame = CGRect(x: x, y: y, width: textLabelWidth, height: labelHeight) + titleLabel.alphaValue = value.visible ? 1 : 0 + + let valueLabel = self.valuesViews[viewModel.values.count] + valueLabel.setTextColor(self.theme.chartDetailsTextColor, animated: false) + valueLabel.setText(value.value, animated: false) + valueLabel.frame = CGRect(x: self.bounds.width - valueLabelWidth - margin, y: y, width: valueLabelWidth, height: labelHeight) + valueLabel.alphaValue = value.visible ? 1 : 0 + } + }) + } + + override var intrinsicContentSize: CGSize { + if let viewModel = viewModel { + let height = ((!viewModel.title.isEmpty || viewModel.showArrow) ? labelHeight : 0) + + (CGFloat(viewModel.values.filter({ $0.visible }).count) * labelHeight) + + (viewModel.totalValue?.visible == true ? labelHeight : 0) + + verticalMargins * 2 + + var textLabelWidth = max(TitleButton.size(with: viewModel.title, font: NSFont.systemFont(ofSize: 12, weight: .bold)).width, 60) + + let maxValue = viewModel.values.map { value -> CGFloat in + return TitleButton.size(with: value.title, font: NSFont.systemFont(ofSize: 12, weight: .regular)).width + }.max() ?? 0 + + textLabelWidth = max(maxValue, textLabelWidth) + + let width: CGFloat = margin * 2 + + (viewModel.showPrefixes ? (prefixLabelWidth + margin) : 0) + + textLabelWidth + + valueLabelWidth + + return CGSize(width: width, + height: height) + } else { + return CGSize(width: 140, + height: labelHeight + verticalMargins) + } + } + + func setLabelsCount(array: inout [TransparentTextField], + count: Int, + font: NSFont, + textAlignment: NSTextAlignment = .right) { + while array.count > count { + let subview = array.removeLast() + subview.removeFromSuperview() + } + while array.count < count { + let label = TransparentTextField() + label.isEditable = false + label.isBordered = false + label.drawsBackground = false + // label.allowsDefaultTighteningForTruncation = true + label.drawsBackground = false + label.lineBreakMode = .byTruncatingTail + + label.font = font + // label.adjustsFontSizeToFitWidth = true + // label.minimumScaleFactor = 0.5 + label.alignment = textAlignment + addSubview(label) + array.append(label) + } + } +} + +extension ChartDetailsView: ChartThemeContainer { + func apply(theme: ChartTheme, strings: ChartStrings, animated: Bool) { + self.theme = theme + self.titleLabel.setTextColor(theme.chartDetailsTextColor, animated: animated) + if let viewModel = self.viewModel { + self.setup(viewModel: viewModel, animated: animated) + } + View.perform(animated: animated) { + if #available(OSX 10.14, *) { + self.arrowView.contentTintColor = theme.chartDetailsArrowColor + } else { + // Fallback on earlier versions + } + self.backgroundColor = theme.chartDetailsViewColor + } + } +} + +// MARK: UIStackView+removeAllArrangedSubviews +public extension NSStackView { + func setLabelsCount(_ count: Int, + font: NSFont, + huggingPriority: NSLayoutConstraint.Priority, + textAlignment: NSTextAlignment = .right) { + while arrangedSubviews.count > count { + let subview = arrangedSubviews.last! + removeArrangedSubview(subview) + subview.removeFromSuperview() + } + while arrangedSubviews.count < count { + let label = TransparentTextField() + label.isEditable = false + label.isBordered = false + label.drawsBackground = false + //label.allowsDefaultTighteningForTruncation = true + label.drawsBackground = false + label.lineBreakMode = .byTruncatingTail + label.font = font + label.alignment = textAlignment + label.setContentHuggingPriority(huggingPriority, for: .horizontal) + label.setContentHuggingPriority(huggingPriority, for: .vertical) + label.setContentCompressionResistancePriority(NSLayoutConstraint.Priority(rawValue: 999), for: .horizontal) + label.setContentCompressionResistancePriority(NSLayoutConstraint.Priority(rawValue: 999), for: .vertical) + addArrangedSubview(label) + } + } + + func label(at index: Int) -> NSTextField { + return arrangedSubviews[index] as! TransparentTextField + } + + func removeAllArrangedSubviews() { + for subview in arrangedSubviews { + removeArrangedSubview(subview) + subview.removeFromSuperview() + } + } +} + +// MARK: UIStackView+addArrangedSubviews +public extension NSStackView { + func addArrangedSubviews(_ views: [View]) { + views.forEach({ addArrangedSubview($0) }) + } +} + +// MARK: UIStackView+insertArrangedSubviews +public extension NSStackView { + func insertArrangedSubviews(_ views: [View], at index: Int) { + views.reversed().forEach({ insertArrangedSubview($0, at: index) }) + } +} diff --git a/submodules/GraphUI/Graph/src/Charts/views/ChartStackSection.swift b/submodules/GraphUI/Graph/src/Charts/views/ChartStackSection.swift new file mode 100644 index 0000000000..52853eaaeb --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/ChartStackSection.swift @@ -0,0 +1,229 @@ +// +// ChartStackSection.swift +// Graph +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +private enum Constants { + static let chartViewHeightFraction: CGFloat = 0.55 +} + +public class ChartStackSection: View, ChartThemeContainer { + var chartView: ChartView + var rangeView: RangeChartView + var visibilityView: ChartVisibilityView + var sectionContainerView: View + var separators: [View] = [] + + var headerLabel: NSTextField! + var titleLabel: NSTextField! + var backButton: TitleButton! + + var controller: BaseChartController! + + override init() { + sectionContainerView = View() + chartView = ChartView(frame: NSZeroRect) + rangeView = RangeChartView(frame: NSZeroRect) + visibilityView = ChartVisibilityView() + headerLabel = NSTextField() + titleLabel = NSTextField() + backButton = TitleButton() + + super.init(frame: CGRect()) + + self.addSubview(sectionContainerView) + sectionContainerView.addSubview(chartView) + sectionContainerView.addSubview(visibilityView) + sectionContainerView.addSubview(rangeView) + + headerLabel.font = NSFont.systemFont(ofSize: 14, weight: .regular) + titleLabel.font = NSFont.systemFont(ofSize: 14, weight: .bold) + // visibilityView.clipsToBounds = true + // backButton.isExclusiveTouch = true + + backButton.setVisible(false, animated: false) + + headerLabel.font = NSFont.systemFont(ofSize: 14, weight: .regular) + titleLabel.font = NSFont.systemFont(ofSize: 14, weight: .bold) + titleLabel.alignment = .center + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.drawsBackground = false + // visibilityView.clipsToBounds = true + // backButton.isExclusiveTouch = true + + + addSubview(titleLabel) + addSubview(backButton) + + backButton.direction = .left + _ = backButton.sizeToFit() + backButton.set(handler: { [weak self] _ in + self?.didTapBackButton() + }, for: .Click) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + public func apply(theme: ChartTheme, strings: ChartStrings, animated: Bool) { + View.perform(animated: animated && self.isVisibleInWindow) { + self.backgroundColor = theme.chartBackgroundColor + + self.sectionContainerView.backgroundColor = theme.chartBackgroundColor + self.rangeView.backgroundColor = theme.chartBackgroundColor + self.visibilityView.backgroundColor = theme.chartBackgroundColor + // self.backButton.tintColor = theme.actionButtonColor + self.backButton.set(color: theme.actionButtonColor, for: .Normal) + + self.backButton.set(text: "Zoom Out", for: .Normal) + _ = self.backButton.sizeToFit() + + for separator in self.separators { + separator.backgroundColor = theme.chartStrongLinesColor + } + } + + if rangeView.isVisibleInWindow || chartView.isVisibleInWindow { + chartView.loadDetailsViewIfNeeded() + chartView.apply(theme: theme, strings: strings, animated: animated && chartView.isVisibleInWindow) + controller.apply(theme: theme, strings: strings, animated: animated) + rangeView.apply(theme: theme, strings: strings, animated: animated && rangeView.isVisibleInWindow) + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval.random(in: 0...0.1)) { + self.chartView.loadDetailsViewIfNeeded() + self.controller.apply(theme: theme, strings: strings, animated: false) + self.chartView.apply(theme: theme, strings: strings, animated: false) + self.rangeView.apply(theme: theme, strings: strings, animated: false) + } + + } + + self.titleLabel.setTextColor(theme.chartTitleColor, animated: animated && titleLabel.isVisibleInWindow) + self.headerLabel.setTextColor(theme.chartTitleColor, animated: animated && headerLabel.isVisibleInWindow) + + needsLayout = true + } + + func didTapBackButton() { + controller.didTapZoomOut() + } + + func setBackButtonVisible(_ visible: Bool, animated: Bool) { + + backButton.setVisible(visible, animated: animated) + layoutIfNeeded(animated: animated) + } + + func updateToolViews(animated: Bool) { + rangeView.setRange(controller.currentChartHorizontalRangeFraction, animated: animated) + rangeView.setRangePaging(enabled: controller.isChartRangePagingEnabled, + minimumSize: controller.minimumSelectedChartRange) + visibilityView.setVisible(controller.drawChartVisibity, animated: animated) + if controller.drawChartVisibity { + visibilityView.items = controller.actualChartsCollection.chartValues.map { value in + return ChartVisibilityItem(title: value.name, color: value.color) + } + visibilityView.setItemsSelection(controller.actualChartVisibility) + visibilityView.needsLayout = true + visibilityView.layoutIfNeeded(animated: animated) + } + } + + override public func layout() { + super.layout() + + let bounds = self.bounds + self.titleLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: bounds.width, height: 30)) + self.backButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: self.backButton.frame.size) + + self.sectionContainerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: frame.height)) + self.chartView.frame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: 250.0)) + self.rangeView.frame = CGRect(origin: CGPoint(x: 0.0, y: 250.0), size: CGSize(width: bounds.width, height: 48.0)) + let visibilityHeight = ChartVisibilityItem.generateItemsFrames(for: bounds.width, items: self.visibilityView.items).last?.maxY ?? 0 + self.visibilityView.frame = CGRect(origin: CGPoint(x: 0.0, y: 308.0), size: CGSize(width: bounds.width, height: visibilityHeight)) + } + + public func setup(controller: BaseChartController, title: String) { + self.controller = controller + self.headerLabel.setText(title, animated: false) + + // Chart + chartView.renderers = controller.mainChartRenderers + chartView.userDidSelectCoordinateClosure = { [unowned self] point in + self.controller.chartInteractionDidBegin(point: point) + } + chartView.userDidDeselectCoordinateClosure = { [unowned self] in + self.controller.cancelChartInteraction() + } + + controller.cartViewBounds = { [unowned self] in + return self.chartView.bounds + } + controller.chartFrame = { [unowned self] in + return self.chartView.chartFrame + } + controller.setDetailsViewModel = { [unowned self] viewModel, animated, _ in + self.chartView.setDetailsViewModel(viewModel: viewModel, animated: animated) + + self.chartView.userWantZoomIfPossible = { + viewModel.tapAction?() + } + } + controller.setDetailsChartVisibleClosure = { [unowned self] visible, animated in + self.chartView.setDetailsChartVisible(visible, animated: animated) + } + controller.setDetailsViewPositionClosure = { [unowned self] position in + self.chartView.detailsViewPosition = position + } + controller.setChartTitleClosure = { [unowned self] title, animated in + self.titleLabel.setText(title, animated: animated) + } + controller.setBackButtonVisibilityClosure = { [unowned self] visible, animated in + self.setBackButtonVisible(visible, animated: animated) + } + controller.refreshChartToolsClosure = { [unowned self] animated in + self.updateToolViews(animated: animated) + } + + // Range view + rangeView.chartView.renderers = controller.navigationRenderers + rangeView.rangeDidChangeClosure = { range in + controller.updateChartRange(range) + } + rangeView.touchedOutsideClosure = { + controller.cancelChartInteraction() + } + controller.chartRangeUpdatedClosure = { [unowned self] (range, animated) in + self.rangeView.setRange(range, animated: animated) + } + controller.chartRangePagingClosure = { [unowned self] (isEnabled, pageSize) in + self.rangeView.setRangePaging(enabled: isEnabled, minimumSize: pageSize) + } + + // Visibility view + visibilityView.selectionCallbackClosure = { [unowned self] visibility in + self.controller.updateChartsVisibility(visibility: visibility, animated: true) + } + + controller.initializeChart() + updateToolViews(animated: false) + + } + + public func updateMouse() { + self.chartView.updateMouse() + } +} diff --git a/submodules/GraphUI/Graph/src/Charts/views/ChartView.swift b/submodules/GraphUI/Graph/src/Charts/views/ChartView.swift new file mode 100644 index 0000000000..f14adfb61b --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/ChartView.swift @@ -0,0 +1,180 @@ +// +// ChartView.swift +// GraphTest +// +// Created by Andrei Salavei on 4/7/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +class ChartView: Control { + required init(frame: CGRect) { + super.init(frame: frame) + + setupView() + + set(handler: { [weak self] _ in + self?.userWantZoomIfPossible?() + }, for: .Click) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + setupView() + } + + var chartInsets: NSEdgeInsets = NSEdgeInsets(top: 40, left: 16, bottom: 35, right: 16) { + didSet { + setNeedsDisplay() + } + } + + var renderers: [ChartViewRenderer] = [] { + willSet { + renderers.forEach { $0.containerViews.removeAll(where: { $0 == self }) } + } + didSet { + renderers.forEach { $0.containerViews.append(self) } + setNeedsDisplay() + } + } + + var chartFrame: CGRect { + let chartBound = self.bounds + return CGRect(x: chartInsets.left, + y: chartInsets.top, + width: max(1, chartBound.width - chartInsets.left - chartInsets.right), + height: max(1, chartBound.height - chartInsets.top - chartInsets.bottom)) + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + let chartBounds = self.bounds + let chartFrame = self.chartFrame + + for renderer in renderers { + renderer.render(context: ctx, bounds: chartBounds, chartFrame: chartFrame) + } + } + + var userDidSelectCoordinateClosure: ((CGPoint) -> Void)? + var userDidDeselectCoordinateClosure: (() -> Void)? + + var userWantZoomIfPossible: (() -> Void)? + + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + updateMouse() + } + + func updateMouse() { + if let event = window?.currentEvent { + let point = convert(event.locationInWindow, from: nil) + + let fractionPoint = CGPoint(x: (point.x - chartFrame.origin.x) / chartFrame.width, + y: (point.y - chartFrame.origin.y) / chartFrame.height) + + if NSPointInRect(point, frame) { + userDidSelectCoordinateClosure?(fractionPoint) + } else { + userDidDeselectCoordinateClosure?() + } + } + } + + override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateMouse() + } + override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateMouse() + } + override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateMouse() + } + + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + updateMouse() + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + //updateMouse() + } + + // MARK: Details View + + private var detailsView: ChartDetailsView! + private var maxDetailsViewWidth: CGFloat = 0 + func loadDetailsViewIfNeeded() { + if detailsView == nil { + let detailsView = ChartDetailsView(frame: bounds) + addSubview(detailsView) + detailsView.alphaValue = 0 + self.detailsView = detailsView + } + } + + private var detailsTableTopOffset: CGFloat = 5 + private var detailsTableLeftOffset: CGFloat = 8 + private var isDetailsViewVisible: Bool = false + + var detailsViewPosition: CGFloat = 0 { + didSet { + loadDetailsViewIfNeeded() + let detailsViewSize = detailsView.intrinsicContentSize + maxDetailsViewWidth = max(maxDetailsViewWidth, detailsViewSize.width) + if maxDetailsViewWidth + detailsTableLeftOffset > detailsViewPosition { + detailsView.frame = CGRect(x: floorToScreenPixels(System.backingScale, min(detailsViewPosition + detailsTableLeftOffset, bounds.width - maxDetailsViewWidth)), + y: floorToScreenPixels(System.backingScale, chartInsets.top + detailsTableTopOffset), + width: maxDetailsViewWidth, + height: detailsViewSize.height) + } else { + detailsView.frame = CGRect(x: floorToScreenPixels(System.backingScale, detailsViewPosition - maxDetailsViewWidth - detailsTableLeftOffset), + y: floorToScreenPixels(System.backingScale, chartInsets.top + detailsTableTopOffset), + width: maxDetailsViewWidth, + height: detailsViewSize.height) + } + } + } + + func setDetailsChartVisible(_ visible: Bool, animated: Bool) { + guard isDetailsViewVisible != visible else { + return + } + isDetailsViewVisible = visible + loadDetailsViewIfNeeded() + detailsView.setVisible(visible, animated: animated) + if !visible { + maxDetailsViewWidth = 0 + } + } + + func setDetailsViewModel(viewModel: ChartDetailsViewModel, animated: Bool) { + loadDetailsViewIfNeeded() + detailsView.setup(viewModel: viewModel, animated: animated) + View.perform(animated: animated, animations: { + let position = self.detailsViewPosition + self.detailsViewPosition = position + }) + } + + func setupView() { + backgroundColor = .clear + layer?.drawsAsynchronously = true + } +} + + +extension ChartView: ChartThemeContainer { + func apply(theme: ChartTheme, strings: ChartStrings, animated: Bool) { + detailsView?.apply(theme: theme, strings: strings, animated: animated && (detailsView?.isVisibleInWindow ?? false)) + } +} diff --git a/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityItemView.swift b/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityItemView.swift new file mode 100644 index 0000000000..f065dbafdb --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityItemView.swift @@ -0,0 +1,93 @@ +// +// ChartVisibilityItemView.swift +// Graph +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +class ChartVisibilityItemView: View { + static let textFont = NSFont.systemFont(ofSize: 14, weight: .medium) + + let checkButton: TitleButton = TitleButton() + + required init(frame: CGRect) { + super.init(frame: frame) + + setupView() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + + func setupView() { + checkButton._thatFit = true + checkButton.frame = bounds + checkButton.set(font: ChartVisibilityItemView.textFont, for: .Normal) + checkButton.layer?.cornerRadius = 6 + checkButton.layer?.masksToBounds = true + checkButton.set(handler: { [weak self] _ in + self?.didTapButton() + }, for: .Click) + addSubview(checkButton) + } + + var tapClosure: (() -> Void)? + var longTapClosure: (() -> Void)? + + private func updateStyle(animated: Bool) { + guard let item = item else { + return + } + View.perform(animated: animated, animations: { + if self.isChecked { + self.checkButton.set(color: .white, for: .Normal) + self.checkButton.backgroundColor = item.color + self.checkButton.layer?.borderColor = nil + self.checkButton.layer?.borderWidth = 0 + self.checkButton.set(text: "✓ " + item.title, for: .Normal) + } else { + self.checkButton.backgroundColor = .clear + self.checkButton.layer?.borderColor = item.color.cgColor + self.checkButton.layer?.borderWidth = 1 + self.checkButton.set(color: item.color, for: .Normal) + self.checkButton.set(text: item.title, for: .Normal) + } + + }) + } + + override func layout() { + super.layout() + + checkButton.frame = bounds + } + + @objc private func didTapButton() { + tapClosure?() + } + + @objc private func didRecognizedLongPress(recognizer: NSGestureRecognizer) { + if recognizer.state == .began { + longTapClosure?() + } + } + + var item: ChartVisibilityItem? = nil { + didSet { + updateStyle(animated: false) + } + } + + private(set) var isChecked: Bool = true + func setChecked(isChecked: Bool, animated: Bool) { + self.isChecked = isChecked + updateStyle(animated: true) + } +} diff --git a/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityView.swift b/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityView.swift new file mode 100644 index 0000000000..420bc55099 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/ChartVisibilityView.swift @@ -0,0 +1,133 @@ +// +// ChartVisibilityView.swift +// Graph +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +private enum Constants { + static let itemHeight: CGFloat = 30 + static let itemSpacing: CGFloat = 8 + static let labelTextApproxInsets: CGFloat = 40 + static let insets = NSEdgeInsets(top: 0, left: 16, bottom: 16, right: 16) +} + + +class ChartVisibilityView: View { + var items: [ChartVisibilityItem] = [] { + didSet { + selectedItems = items.map { _ in true } + while selectionViews.count > selectedItems.count { + selectionViews.last?.removeFromSuperview() + selectionViews.removeLast() + } + while selectionViews.count < selectedItems.count { + let view = ChartVisibilityItemView(frame: bounds) + addSubview(view) + selectionViews.append(view) + } + + assert(selectionViews.count == items.count) + + for (index, item) in items.enumerated() { + let view = selectionViews[index] + view.item = item + view.tapClosure = { [weak self] in + guard let self = self else { return } + let selected = self.selectedItems.filter { $0 } + if selected.count == 1, self.selectedItems[index] { + self.selectionViews[index].shake() + } else { + self.setItemSelected(!self.selectedItems[index], at: index, animated: true) + self.notifyItemSelection() + } + } + + view.longTapClosure = { [weak self] in + guard let self = self else { return } + let hasSelectedItem = self.selectedItems.enumerated().contains(where: { $0.element && $0.offset != index }) + if hasSelectedItem { + for (itemIndex, _) in self.items.enumerated() { + self.setItemSelected(itemIndex == index, at: itemIndex, animated: true) + } + } else { + for (itemIndex, _) in self.items.enumerated() { + self.setItemSelected(true, at: itemIndex, animated: true) + } + } + self.notifyItemSelection() + } + } + } + } + + private (set) var selectedItems: [Bool] = [] + var isExpanded: Bool = true { + didSet { + invalidateIntrinsicContentSize() + // setNeedsUpdateConstraints() + } + } + + private var selectionViews: [ChartVisibilityItemView] = [] + + var selectionCallbackClosure: (([Bool]) -> Void)? + + func setItemSelected(_ selected: Bool, at index: Int, animated: Bool) { + self.selectedItems[index] = selected + self.selectionViews[index].setChecked(isChecked: selected, animated: animated) + } + + func setItemsSelection(_ selection: [Bool]) { + assert(selection.count == items.count) + self.selectedItems = selection + for (index, selected) in self.selectedItems.enumerated() { + selectionViews[index].setChecked(isChecked: selected, animated: false) + } + } + + override func removeFromSuperview() { + super.removeFromSuperview() + } + + private func notifyItemSelection() { + selectionCallbackClosure?(selectedItems) + } + + override func layout() { + super.layout() + + updateFrames() + } + + override var alphaValue: CGFloat { + didSet { + if self.alphaValue == 0 { + var bp:Int = 0 + bp += 1 + } + } + } + + private func updateFrames() { + let frames = ChartVisibilityItem.generateItemsFrames(for: frame.width, items: self.items) + for (index, frame) in frames.enumerated() { + selectionViews[index].frame = frame + } + } + +} + +extension ChartVisibilityView: ChartThemeContainer { + func apply(theme: ChartTheme, strings: ChartStrings, animated: Bool) { + View.perform(animated: animated) { + self.backgroundColor = theme.chartBackgroundColor + // self.tintColor = theme.descriptionActionColor + } + } +} diff --git a/submodules/GraphUI/Graph/src/Charts/views/RangeChartView.swift b/submodules/GraphUI/Graph/src/Charts/views/RangeChartView.swift new file mode 100644 index 0000000000..c8bb195399 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Charts/views/RangeChartView.swift @@ -0,0 +1,298 @@ +// +// RangeChartView.swift +// Graph +// +// Created by Mikhail Filimonov on 24.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore +private enum Constants { + static let cropIndocatorLineWidth: CGFloat = 1 + static let markerSelectionRange: CGFloat = 25 + static let defaultMinimumRangeDistance: CGFloat = 0.05 + static let titntAreaWidth: CGFloat = 10 + static let horizontalContentMargin: CGFloat = 16 + static let cornerRadius: CGFloat = 5 +} + +class RangeChartView: Control { + private enum Marker { + case lower + case upper + case center + } + public var lowerBound: CGFloat = 0 { + didSet { + needsLayout = true + } + } + public var upperBound: CGFloat = 1 { + didSet { + needsLayout = true + } + } + public var selectionColor: NSColor = .blue + public var defaultColor: NSColor = .lightGray + + public var minimumRangeDistance: CGFloat = Constants.defaultMinimumRangeDistance + + private let lowerBoundTintView = View() + private let upperBoundTintView = View() + private let cropFrameView = TransparentImageView() + + private var selectedMarker: Marker? + private var selectedMarkerHorizontalOffet: CGFloat = 0 + private var isBoundCropHighlighted: Bool = false + private var isRangePagingEnabled: Bool = false + + public let chartView = ChartView(frame: NSZeroRect) + + var layoutMargins: NSEdgeInsets + + required init(frame: CGRect) { + layoutMargins = NSEdgeInsets(top: Constants.cropIndocatorLineWidth, + left: Constants.horizontalContentMargin, + bottom: Constants.cropIndocatorLineWidth, + right: Constants.horizontalContentMargin) + super.init(frame: frame) + + self.setup() + } + + func setup() { +// isMultipleTouchEnabled = false + + chartView.chartInsets = .init() + chartView.backgroundColor = .clear + cropFrameView.wantsLayer = true + addSubview(chartView) + lowerBoundTintView.isEventLess = true + upperBoundTintView.isEventLess = true + addSubview(lowerBoundTintView) + addSubview(upperBoundTintView) + addSubview(cropFrameView) + + cropFrameView.isEnabled = false + + cropFrameView.imageScaling = .scaleAxesIndependently + + cropFrameView.layer?.contentsGravity = .resize + + //cropFrameView.isUserInteractionEnabled = false + chartView.userInteractionEnabled = false + chartView.isEventLess = true + lowerBoundTintView.userInteractionEnabled = false + upperBoundTintView.userInteractionEnabled = false + + chartView.layer?.cornerRadius = 5 + upperBoundTintView.layer?.cornerRadius = 5 + lowerBoundTintView.layer?.cornerRadius = 5 + + chartView.layer?.masksToBounds = true + upperBoundTintView.layer?.masksToBounds = true + lowerBoundTintView.layer?.masksToBounds = true + + layoutViews() + } + + + public var rangeDidChangeClosure: ((ClosedRange) -> Void)? + public var touchedOutsideClosure: (() -> Void)? + + required init?(coder aDecoder: NSCoder) { + fatalError("not supported") + } + + func setRangePaging(enabled: Bool, minimumSize: CGFloat) { + isRangePagingEnabled = enabled + minimumRangeDistance = minimumSize + } + + func setRange(_ range: ClosedRange, animated: Bool) { + View.perform(animated: animated) { + self.lowerBound = range.lowerBound + self.upperBound = range.upperBound + self.needsLayout = true + } + } + + override func layout() { + super.layout() + + layoutViews() + } + + override var isEnabled: Bool { + get { + return super.isEnabled + } + set { + if newValue == false { + selectedMarker = nil + } + super.isEnabled = newValue + } + } + + // MARK: - Touches + + override func mouseDown(with event: NSEvent) { + + super.mouseDown(with: event) + + guard isEnabled else { return } + + let point = self.convert(event.locationInWindow, from: nil) + + if abs(locationInView(for: upperBound) - point.x + Constants.markerSelectionRange / 2) < Constants.markerSelectionRange { + selectedMarker = .upper + selectedMarkerHorizontalOffet = point.x - locationInView(for: upperBound) + isBoundCropHighlighted = true + } else if abs(locationInView(for: lowerBound) - point.x - Constants.markerSelectionRange / 2) < Constants.markerSelectionRange { + selectedMarker = .lower + selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound) + isBoundCropHighlighted = true + } else if point.x > locationInView(for: lowerBound) && point.x < locationInView(for: upperBound) { + selectedMarker = .center + selectedMarkerHorizontalOffet = point.x - locationInView(for: lowerBound) + isBoundCropHighlighted = true + } else { + selectedMarker = nil + return + } + + // sendActions(for: .touchDown) + } + + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + + guard let selectedMarker = selectedMarker else { return } + let point = self.convert(event.locationInWindow, from: nil) + + let horizontalPosition = point.x - selectedMarkerHorizontalOffet + let fraction = fractionFor(offsetX: horizontalPosition) + updateMarkerOffset(selectedMarker, fraction: fraction) + + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + guard isEnabled else { return } + + guard let selectedMarker = selectedMarker else { + touchedOutsideClosure?() + return + } + let point = self.convert(event.locationInWindow, from: nil) + + let horizontalPosition = point.x - selectedMarkerHorizontalOffet + let fraction = fractionFor(offsetX: horizontalPosition) + updateMarkerOffset(selectedMarker, fraction: fraction) + + self.selectedMarker = nil + self.isBoundCropHighlighted = false + + rangeDidChangeClosure?(lowerBound...upperBound) + + } +} + +private extension RangeChartView { + var contentFrame: CGRect { + return CGRect(x: layoutMargins.right, + y: layoutMargins.top, + width: (bounds.width - layoutMargins.right - layoutMargins.left), + height: bounds.height - layoutMargins.top - layoutMargins.bottom) + } + + func locationInView(for fraction: CGFloat) -> CGFloat { + return contentFrame.minX + contentFrame.width * fraction + } + + func locationInView(for fraction: Double) -> CGFloat { + return locationInView(for: CGFloat(fraction)) + } + + func fractionFor(offsetX: CGFloat) -> CGFloat { + guard contentFrame.width > 0 else { + return 0 + } + + return crop(0, CGFloat((offsetX - contentFrame.minX ) / contentFrame.width), 1) + } + + private func updateMarkerOffset(_ marker: Marker, fraction: CGFloat, notifyDelegate: Bool = true) { + let fractionToCount: CGFloat + if isRangePagingEnabled { + guard let minValue = stride(from: CGFloat(0.0), through: CGFloat(1.0), by: minimumRangeDistance).min(by: { abs($0 - fraction) < abs($1 - fraction) }) else { return } + fractionToCount = minValue + } else { + fractionToCount = fraction + } + + switch marker { + case .lower: + lowerBound = min(fractionToCount, upperBound - minimumRangeDistance) + case .upper: + upperBound = max(fractionToCount, lowerBound + minimumRangeDistance) + case .center: + let distance = upperBound - lowerBound + lowerBound = max(0, min(fractionToCount, 1 - distance)) + upperBound = lowerBound + distance + } + if notifyDelegate { + rangeDidChangeClosure?(lowerBound...upperBound) + } + self.layoutIfNeeded(animated: true) + } + + // MARK: - Layout + + func layoutViews() { + cropFrameView.frame = CGRect(x: locationInView(for: lowerBound), + y: contentFrame.minY - Constants.cropIndocatorLineWidth, + width: locationInView(for: upperBound) - locationInView(for: lowerBound), + height: contentFrame.height + Constants.cropIndocatorLineWidth * 2) + + if chartView.frame != contentFrame { + chartView.frame = contentFrame + } + + lowerBoundTintView.frame = CGRect(x: contentFrame.minX, + y: contentFrame.minY, + width: max(0, locationInView(for: lowerBound) - contentFrame.minX + Constants.titntAreaWidth), + height: contentFrame.height) + + upperBoundTintView.frame = CGRect(x: locationInView(for: upperBound) - Constants.titntAreaWidth, + y: contentFrame.minY, + width: max(0, contentFrame.maxX - locationInView(for: upperBound) + Constants.titntAreaWidth), + height: contentFrame.height) + } +} + +extension RangeChartView: ChartThemeContainer { + func apply(theme: ChartTheme, strings: ChartStrings, animated: Bool) { + let closure = { + self.lowerBoundTintView.backgroundColor = theme.rangeViewTintColor + self.upperBoundTintView.backgroundColor = theme.rangeViewTintColor + } + + let rangeCropImage = theme.rangeCropImage + rangeCropImage?.resizingMode = .stretch + rangeCropImage?.capInsets = NSEdgeInsets(top: 15, left: 15, bottom: 15, right: 15) + + self.cropFrameView.setImage(rangeCropImage, animated: animated) + + // self.chartView.apply(theme: theme, animated: animated) + + if animated { + View.perform(animated: true, animations: closure) + } else { + closure() + } + } +} diff --git a/submodules/GraphUI/Graph/src/Helpers/UIImage+Utils.swift b/submodules/GraphUI/Graph/src/Helpers/UIImage+Utils.swift new file mode 100644 index 0000000000..b99235eae2 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Helpers/UIImage+Utils.swift @@ -0,0 +1,33 @@ +// +// NSImage+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/8/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + + +extension NSImage { + static let arrowRight = NSImage(named: "arrow_right") + static let arrowLeft = NSImage(named: "arrow_left") + + public convenience init?(color: NSColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + + + let image = generateImage(size, contextGenerator: { size, ctx in + ctx.clear(rect) + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + }) + + guard let cgImage = image else { return nil } + self.init(cgImage: cgImage, size: size) + } + + +} diff --git a/submodules/GraphUI/Graph/src/Helpers/UIImageView+Utils.swift b/submodules/GraphUI/Graph/src/Helpers/UIImageView+Utils.swift new file mode 100644 index 0000000000..97ce674e30 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Helpers/UIImageView+Utils.swift @@ -0,0 +1,37 @@ +// +// NSImageView+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/9/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +extension NSImageView { + func setImage(_ image: NSImage?, animated: Bool) { + if self.image != image { + if animated { + let animation = CATransition() + animation.timingFunction = CAMediaTimingFunction.init(name: .linear) + animation.type = .fade + animation.duration = 0.2 + self.layer?.add(animation, forKey: "kCATransitionImageFade") + } + self.image = image + } + } + +} + +class TransparentImageView : NSImageView { + +} + +extension TransparentImageView { + open override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } +} diff --git a/submodules/GraphUI/Graph/src/Helpers/UILabel+Utils.swift b/submodules/GraphUI/Graph/src/Helpers/UILabel+Utils.swift new file mode 100644 index 0000000000..4db7785be6 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Helpers/UILabel+Utils.swift @@ -0,0 +1,50 @@ +// +// UILabel+Utils.swift +// GraphTest +// +// Created by Andrei Salavei on 4/9/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + +extension NSTextField { + func setTextColor(_ color: NSColor, animated: Bool) { + if self.textColor != color { + if animated { + let animation = CATransition() + animation.timingFunction = CAMediaTimingFunction.init(name: .linear) + animation.type = .fade + animation.duration = 0.2 + self.layer?.add(animation, forKey: "kCATransitionColorFade") + } + self.textColor = color + } + } + + func setText(_ title: String?, animated: Bool) { + if self.stringValue != title { + if animated { + let animation = CATransition() + animation.timingFunction = CAMediaTimingFunction.init(name: .linear) + animation.type = .fade + animation.duration = 0.2 + self.layer?.add(animation, forKey: "kCATransitionTextFade") + } + self.stringValue = title ?? "" + } + } +} + + +class TransparentTextField : NSTextField { + +} + +extension TransparentTextField { + open override func hitTest(_ point: NSPoint) -> NSView? { + return nil + } +} diff --git a/submodules/GraphUI/Graph/src/Helpers/UIView+Extensions.swift b/submodules/GraphUI/Graph/src/Helpers/UIView+Extensions.swift new file mode 100644 index 0000000000..b721f0e510 --- /dev/null +++ b/submodules/GraphUI/Graph/src/Helpers/UIView+Extensions.swift @@ -0,0 +1,64 @@ +// +// View+Extensions.swift +// GraphTest +// +// Created by Andrei Salavei on 4/10/19. +// Copyright © 2019 Andrei Salavei. All rights reserved. +// + +import Cocoa +import TGUIKit +import GraphCore + + + +extension NSView { + static let oneDevicePixel: CGFloat = (1.0 / max(2, min(1, 2.0))) +} + +// MARK: View+Animation +public extension NSView { + func bringToFront() { + if var subviews = superview?.subviews, let index = subviews.firstIndex(where: { $0 === self }) { + subviews.append(subviews.remove(at: index)) + superview?.subviews = subviews + } + } + + func layoutIfNeeded(animated: Bool) { + View.perform(animated: animated) { + self.needsLayout = true + } + } + + func setVisible(_ visible: Bool, animated: Bool) { + let updatedAlpha: CGFloat = visible ? 1 : 0 + if self.alphaValue != updatedAlpha { + View.perform(animated: animated) { + self.alphaValue = updatedAlpha + } + } + } + + static func perform(animated: Bool, animations: @escaping () -> Void) { + perform(animated: animated, animations: animations, completion: { _ in }) + } + + static func perform(animated: Bool, animations: @escaping () -> Void, completion: @escaping (Bool) -> Void) { + if animated { + NSAnimationContext.runAnimationGroup({ _ in + animations() + }, completionHandler: { + completion(true) + }) + //View.animate(withDuration: .defaultDuration, delay: 0, animations: animations, completion: completion) + } else { + animations() + completion(true) + } + } + + var isVisibleInWindow: Bool { + return visibleRect.height > 0 + } +} diff --git a/submodules/GraphUI/GraphUI/GraphUI.h b/submodules/GraphUI/GraphUI/GraphUI.h new file mode 100644 index 0000000000..5778716f2f --- /dev/null +++ b/submodules/GraphUI/GraphUI/GraphUI.h @@ -0,0 +1,19 @@ +// +// GraphUI.h +// GraphUI +// +// Created by Mikhail Filimonov on 28.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +#import + +//! Project version number for GraphUI. +FOUNDATION_EXPORT double GraphUIVersionNumber; + +//! Project version string for GraphUI. +FOUNDATION_EXPORT const unsigned char GraphUIVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/submodules/GraphUI/GraphUI/Info.plist b/submodules/GraphUI/GraphUI/Info.plist new file mode 100644 index 0000000000..861c829fcb --- /dev/null +++ b/submodules/GraphUI/GraphUI/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2020 Telegram. All rights reserved. + + diff --git a/submodules/MtProtoKit b/submodules/MtProtoKit deleted file mode 160000 index f644f24f2a..0000000000 --- a/submodules/MtProtoKit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f644f24f2accdab1fc630feaef7b649def036f9f diff --git a/submodules/Postbox b/submodules/Postbox deleted file mode 160000 index be316a4db1..0000000000 --- a/submodules/Postbox +++ /dev/null @@ -1 +0,0 @@ -Subproject commit be316a4db13758b4c467af7a5e3c868ab9383929 diff --git a/submodules/RLottie_Xcode/RLottie/Info.plist b/submodules/RLottie_Xcode/RLottie/Info.plist new file mode 100644 index 0000000000..0c4389ac7e --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie/Info.plist @@ -0,0 +1,24 @@ + + + + + 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) + NSHumanReadableCopyright + Copyright © 2019 Telegram. All rights reserved. + + diff --git a/submodules/RLottie_Xcode/RLottie/RLottie.h b/submodules/RLottie_Xcode/RLottie/RLottie.h new file mode 100644 index 0000000000..3eab069e01 --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie/RLottie.h @@ -0,0 +1,20 @@ +// +// RLottie.h +// RLottie +// +// Created by Mikhail Filimonov on 01.11.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +//! Project version number for RLottie. +FOUNDATION_EXPORT double RLottieVersionNumber; + +//! Project version string for RLottie. +FOUNDATION_EXPORT const unsigned char RLottieVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + +#import diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.pbxproj b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..24d78c72d4 --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,1213 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A7D2826F236C77FA0000A9BF /* RLottie.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2826D236C77FA0000A9BF /* RLottie.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7D28280236C78DF0000A9BF /* RLottieBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7D2827E236C78DF0000A9BF /* RLottieBridge.mm */; }; + A7D28281236C78DF0000A9BF /* RLottieBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2827F236C78DF0000A9BF /* RLottieBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A7D282A2236C79C40000A9BF /* lottieanimation_capi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A1236C79C40000A9BF /* lottieanimation_capi.cpp */; }; + A7D282B1236C79D70000A9BF /* lottieloader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A4236C79D60000A9BF /* lottieloader.cpp */; }; + A7D282B2236C79D70000A9BF /* lottieproxymodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A5236C79D60000A9BF /* lottieproxymodel.cpp */; }; + A7D282B3236C79D70000A9BF /* lottiekeypath.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A6236C79D70000A9BF /* lottiekeypath.cpp */; }; + A7D282B4236C79D70000A9BF /* lottiemodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A7236C79D70000A9BF /* lottiemodel.cpp */; }; + A7D282B5236C79D70000A9BF /* lottieanimation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282A8236C79D70000A9BF /* lottieanimation.cpp */; }; + A7D282B6236C79D70000A9BF /* lottieparser.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282A9236C79D70000A9BF /* lottieparser.h */; }; + A7D282B7236C79D70000A9BF /* lottiemodel.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282AA236C79D70000A9BF /* lottiemodel.h */; }; + A7D282B8236C79D70000A9BF /* lottiekeypath.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282AB236C79D70000A9BF /* lottiekeypath.h */; }; + A7D282B9236C79D70000A9BF /* lottieitem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282AC236C79D70000A9BF /* lottieitem.cpp */; }; + A7D282BA236C79D70000A9BF /* lottieitem.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282AD236C79D70000A9BF /* lottieitem.h */; }; + A7D282BB236C79D70000A9BF /* lottieparser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D282AE236C79D70000A9BF /* lottieparser.cpp */; }; + A7D282BC236C79D70000A9BF /* lottieloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282AF236C79D70000A9BF /* lottieloader.h */; }; + A7D282BD236C79D70000A9BF /* lottieproxymodel.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282B0236C79D70000A9BF /* lottieproxymodel.h */; }; + A7D282E6236C79F60000A9BF /* prettywriter.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282BF236C79F50000A9BF /* prettywriter.h */; }; + A7D282E7236C79F60000A9BF /* ieee754.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C1236C79F50000A9BF /* ieee754.h */; }; + A7D282E8236C79F60000A9BF /* strtod.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C2236C79F50000A9BF /* strtod.h */; }; + A7D282E9236C79F60000A9BF /* swap.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C3236C79F50000A9BF /* swap.h */; }; + A7D282EA236C79F60000A9BF /* regex.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C4236C79F50000A9BF /* regex.h */; }; + A7D282EB236C79F60000A9BF /* diyfp.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C5236C79F50000A9BF /* diyfp.h */; }; + A7D282EC236C79F60000A9BF /* biginteger.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C6236C79F50000A9BF /* biginteger.h */; }; + A7D282ED236C79F60000A9BF /* strfunc.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C7236C79F50000A9BF /* strfunc.h */; }; + A7D282EE236C79F60000A9BF /* itoa.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C8236C79F50000A9BF /* itoa.h */; }; + A7D282EF236C79F60000A9BF /* stack.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282C9236C79F50000A9BF /* stack.h */; }; + A7D282F0236C79F60000A9BF /* dtoa.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CA236C79F50000A9BF /* dtoa.h */; }; + A7D282F1236C79F60000A9BF /* meta.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CB236C79F50000A9BF /* meta.h */; }; + A7D282F2236C79F60000A9BF /* pow10.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CC236C79F50000A9BF /* pow10.h */; }; + A7D282F3236C79F60000A9BF /* filewritestream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CD236C79F50000A9BF /* filewritestream.h */; }; + A7D282F4236C79F60000A9BF /* allocators.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CE236C79F50000A9BF /* allocators.h */; }; + A7D282F5236C79F60000A9BF /* reader.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282CF236C79F50000A9BF /* reader.h */; }; + A7D282F6236C79F60000A9BF /* schema.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D0236C79F50000A9BF /* schema.h */; }; + A7D282F7236C79F60000A9BF /* writer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D1236C79F50000A9BF /* writer.h */; }; + A7D282FA236C79F60000A9BF /* filereadstream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D5236C79F50000A9BF /* filereadstream.h */; }; + A7D282FB236C79F60000A9BF /* encodedstream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D6236C79F50000A9BF /* encodedstream.h */; }; + A7D282FC236C79F60000A9BF /* document.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D7236C79F50000A9BF /* document.h */; }; + A7D282FD236C79F60000A9BF /* memorystream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D8236C79F50000A9BF /* memorystream.h */; }; + A7D282FE236C79F60000A9BF /* cursorstreamwrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282D9236C79F50000A9BF /* cursorstreamwrapper.h */; }; + A7D282FF236C79F60000A9BF /* encodings.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DA236C79F60000A9BF /* encodings.h */; }; + A7D28300236C79F60000A9BF /* fwd.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DB236C79F60000A9BF /* fwd.h */; }; + A7D28301236C79F60000A9BF /* memorybuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DC236C79F60000A9BF /* memorybuffer.h */; }; + A7D28302236C79F60000A9BF /* istreamwrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DD236C79F60000A9BF /* istreamwrapper.h */; }; + A7D28303236C79F60000A9BF /* ostreamwrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DE236C79F60000A9BF /* ostreamwrapper.h */; }; + A7D28304236C79F60000A9BF /* stream.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282DF236C79F60000A9BF /* stream.h */; }; + A7D28305236C79F60000A9BF /* error.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282E1236C79F60000A9BF /* error.h */; }; + A7D28306236C79F60000A9BF /* en.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282E2236C79F60000A9BF /* en.h */; }; + A7D28307236C79F60000A9BF /* rapidjson.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282E3236C79F60000A9BF /* rapidjson.h */; }; + A7D28308236C79F60000A9BF /* stringbuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282E4236C79F60000A9BF /* stringbuffer.h */; }; + A7D28309236C79F60000A9BF /* pointer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D282E5236C79F60000A9BF /* pointer.h */; }; + A7D28337236C7A270000A9BF /* vpath.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2830B236C7A250000A9BF /* vpath.h */; }; + A7D28338236C7A270000A9BF /* vraster.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2830C236C7A250000A9BF /* vraster.h */; }; + A7D28339236C7A270000A9BF /* vmatrix.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2830D236C7A250000A9BF /* vmatrix.h */; }; + A7D2833A236C7A270000A9BF /* vdrawhelper_neon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2830E236C7A250000A9BF /* vdrawhelper_neon.cpp */; }; + A7D2833B236C7A270000A9BF /* vpainter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2830F236C7A250000A9BF /* vpainter.cpp */; }; + A7D2833C236C7A270000A9BF /* vbitmap.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28310236C7A250000A9BF /* vbitmap.h */; }; + A7D2833D236C7A270000A9BF /* vrect.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28311236C7A250000A9BF /* vrect.h */; }; + A7D2833E236C7A270000A9BF /* vinterpolator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28312236C7A250000A9BF /* vinterpolator.cpp */; }; + A7D2833F236C7A270000A9BF /* vpathmesure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28313236C7A250000A9BF /* vpathmesure.cpp */; }; + A7D28340236C7A270000A9BF /* vdasher.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28314236C7A250000A9BF /* vdasher.h */; }; + A7D28341236C7A270000A9BF /* vcowptr.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28315236C7A250000A9BF /* vcowptr.h */; }; + A7D28342236C7A270000A9BF /* vrect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28316236C7A250000A9BF /* vrect.cpp */; }; + A7D28343236C7A270000A9BF /* vbitmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28317236C7A250000A9BF /* vbitmap.cpp */; }; + A7D28344236C7A270000A9BF /* vpainter.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28318236C7A250000A9BF /* vpainter.h */; }; + A7D28345236C7A270000A9BF /* velapsedtimer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28319236C7A250000A9BF /* velapsedtimer.h */; }; + A7D28346236C7A270000A9BF /* vdebug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2831A236C7A260000A9BF /* vdebug.cpp */; }; + A7D28347236C7A270000A9BF /* vdrawhelper_sse2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2831B236C7A260000A9BF /* vdrawhelper_sse2.cpp */; }; + A7D28348236C7A270000A9BF /* vdrawhelper.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2831C236C7A260000A9BF /* vdrawhelper.h */; }; + A7D28349236C7A270000A9BF /* vimageloader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2831D236C7A260000A9BF /* vimageloader.cpp */; }; + A7D2834A236C7A270000A9BF /* vdrawable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2831E236C7A260000A9BF /* vdrawable.cpp */; }; + A7D2834B236C7A270000A9BF /* vmatrix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2831F236C7A260000A9BF /* vmatrix.cpp */; }; + A7D2834C236C7A270000A9BF /* vraster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28320236C7A260000A9BF /* vraster.cpp */; }; + A7D2834D236C7A270000A9BF /* vline.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28321236C7A260000A9BF /* vline.h */; }; + A7D2834E236C7A270000A9BF /* vdebug.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28322236C7A260000A9BF /* vdebug.h */; }; + A7D2834F236C7A270000A9BF /* vdrawable.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28323236C7A260000A9BF /* vdrawable.h */; }; + A7D28350236C7A270000A9BF /* vpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28324236C7A260000A9BF /* vpoint.h */; }; + A7D28351236C7A270000A9BF /* vbezier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28325236C7A260000A9BF /* vbezier.cpp */; }; + A7D28352236C7A270000A9BF /* vpathmesure.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28326236C7A260000A9BF /* vpathmesure.h */; }; + A7D28353236C7A270000A9BF /* velapsedtimer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28327236C7A260000A9BF /* velapsedtimer.cpp */; }; + A7D28354236C7A270000A9BF /* vrle.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28328236C7A260000A9BF /* vrle.h */; }; + A7D28355236C7A270000A9BF /* vglobal.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28329236C7A260000A9BF /* vglobal.h */; }; + A7D28356236C7A270000A9BF /* vbrush.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2832A236C7A260000A9BF /* vbrush.cpp */; }; + A7D28357236C7A270000A9BF /* vrle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2832B236C7A260000A9BF /* vrle.cpp */; }; + A7D28358236C7A270000A9BF /* vpath.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2832C236C7A260000A9BF /* vpath.cpp */; }; + A7D28359236C7A270000A9BF /* vtaskqueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2832D236C7A260000A9BF /* vtaskqueue.h */; }; + A7D2835A236C7A270000A9BF /* vbrush.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2832E236C7A260000A9BF /* vbrush.h */; }; + A7D2835B236C7A270000A9BF /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2832F236C7A260000A9BF /* config.h */; }; + A7D2835C236C7A270000A9BF /* vinterpolator.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28330236C7A270000A9BF /* vinterpolator.h */; }; + A7D2835D236C7A270000A9BF /* vcompositionfunctions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28331236C7A270000A9BF /* vcompositionfunctions.cpp */; }; + A7D2835E236C7A270000A9BF /* vdrawhelper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28332236C7A270000A9BF /* vdrawhelper.cpp */; }; + A7D2835F236C7A270000A9BF /* vstackallocator.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28333236C7A270000A9BF /* vstackallocator.h */; }; + A7D28360236C7A270000A9BF /* vdasher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28334236C7A270000A9BF /* vdasher.cpp */; }; + A7D28361236C7A270000A9BF /* vbezier.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28335236C7A270000A9BF /* vbezier.h */; }; + A7D28362236C7A270000A9BF /* vimageloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28336236C7A270000A9BF /* vimageloader.h */; }; + A7D2836B236C7A380000A9BF /* v_ft_types.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28364236C7A380000A9BF /* v_ft_types.h */; }; + A7D2836C236C7A380000A9BF /* v_ft_stroker.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28365236C7A380000A9BF /* v_ft_stroker.h */; }; + A7D2836D236C7A380000A9BF /* v_ft_raster.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28366236C7A380000A9BF /* v_ft_raster.h */; }; + A7D2836E236C7A380000A9BF /* v_ft_math.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28367236C7A380000A9BF /* v_ft_math.h */; }; + A7D2836F236C7A380000A9BF /* v_ft_math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28368236C7A380000A9BF /* v_ft_math.cpp */; }; + A7D28370236C7A380000A9BF /* v_ft_stroker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28369236C7A380000A9BF /* v_ft_stroker.cpp */; }; + A7D28371236C7A380000A9BF /* v_ft_raster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D2836A236C7A380000A9BF /* v_ft_raster.cpp */; }; + A7D28375236C7A8E0000A9BF /* vregion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A7D28373236C7A8E0000A9BF /* vregion.cpp */; }; + A7D28376236C7A8E0000A9BF /* vregion.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D28374236C7A8E0000A9BF /* vregion.h */; }; + A7D28380236C7AC90000A9BF /* rlottie.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2837D236C7AC90000A9BF /* rlottie.h */; }; + A7D28381236C7AC90000A9BF /* rlottiecommon.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2837E236C7AC90000A9BF /* rlottiecommon.h */; }; + A7D28382236C7AC90000A9BF /* rlottie_capi.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D2837F236C7AC90000A9BF /* rlottie_capi.h */; }; + A7ED5DB2236C7D5500040372 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DB1236C7D5500040372 /* QuartzCore.framework */; }; + A7ED5DB4236C7D6300040372 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DB3236C7D6300040372 /* Accelerate.framework */; }; + A7ED5DB6236C7D6B00040372 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DB5236C7D6B00040372 /* CoreMedia.framework */; }; + A7ED5DB8236C7D8000040372 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DB7236C7D8000040372 /* AppKit.framework */; }; + A7ED5DBA236C7D9100040372 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7ED5DB9236C7D9100040372 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A7D2826A236C77FA0000A9BF /* RLottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RLottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D2826D236C77FA0000A9BF /* RLottie.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RLottie.h; sourceTree = ""; }; + A7D2826E236C77FA0000A9BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A7D2827E236C78DF0000A9BF /* RLottieBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RLottieBridge.mm; sourceTree = ""; }; + A7D2827F236C78DF0000A9BF /* RLottieBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLottieBridge.h; sourceTree = ""; }; + A7D282A1236C79C40000A9BF /* lottieanimation_capi.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieanimation_capi.cpp; path = ../../../../rlottie/src/binding/c/lottieanimation_capi.cpp; sourceTree = ""; }; + A7D282A4236C79D60000A9BF /* lottieloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieloader.cpp; path = ../../../rlottie/src/lottie/lottieloader.cpp; sourceTree = ""; }; + A7D282A5236C79D60000A9BF /* lottieproxymodel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieproxymodel.cpp; path = ../../../rlottie/src/lottie/lottieproxymodel.cpp; sourceTree = ""; }; + A7D282A6236C79D70000A9BF /* lottiekeypath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottiekeypath.cpp; path = ../../../rlottie/src/lottie/lottiekeypath.cpp; sourceTree = ""; }; + A7D282A7236C79D70000A9BF /* lottiemodel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottiemodel.cpp; path = ../../../rlottie/src/lottie/lottiemodel.cpp; sourceTree = ""; }; + A7D282A8236C79D70000A9BF /* lottieanimation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieanimation.cpp; path = ../../../rlottie/src/lottie/lottieanimation.cpp; sourceTree = ""; }; + A7D282A9236C79D70000A9BF /* lottieparser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottieparser.h; path = ../../../rlottie/src/lottie/lottieparser.h; sourceTree = ""; }; + A7D282AA236C79D70000A9BF /* lottiemodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottiemodel.h; path = ../../../rlottie/src/lottie/lottiemodel.h; sourceTree = ""; }; + A7D282AB236C79D70000A9BF /* lottiekeypath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottiekeypath.h; path = ../../../rlottie/src/lottie/lottiekeypath.h; sourceTree = ""; }; + A7D282AC236C79D70000A9BF /* lottieitem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieitem.cpp; path = ../../../rlottie/src/lottie/lottieitem.cpp; sourceTree = ""; }; + A7D282AD236C79D70000A9BF /* lottieitem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottieitem.h; path = ../../../rlottie/src/lottie/lottieitem.h; sourceTree = ""; }; + A7D282AE236C79D70000A9BF /* lottieparser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lottieparser.cpp; path = ../../../rlottie/src/lottie/lottieparser.cpp; sourceTree = ""; }; + A7D282AF236C79D70000A9BF /* lottieloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottieloader.h; path = ../../../rlottie/src/lottie/lottieloader.h; sourceTree = ""; }; + A7D282B0236C79D70000A9BF /* lottieproxymodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lottieproxymodel.h; path = ../../../rlottie/src/lottie/lottieproxymodel.h; sourceTree = ""; }; + A7D282BF236C79F50000A9BF /* prettywriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = prettywriter.h; path = ../../../../rlottie/src/lottie/rapidjson/prettywriter.h; sourceTree = ""; }; + A7D282C1236C79F50000A9BF /* ieee754.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ieee754.h; sourceTree = ""; }; + A7D282C2236C79F50000A9BF /* strtod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strtod.h; sourceTree = ""; }; + A7D282C3236C79F50000A9BF /* swap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = swap.h; sourceTree = ""; }; + A7D282C4236C79F50000A9BF /* regex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = regex.h; sourceTree = ""; }; + A7D282C5236C79F50000A9BF /* diyfp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = diyfp.h; sourceTree = ""; }; + A7D282C6236C79F50000A9BF /* biginteger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = biginteger.h; sourceTree = ""; }; + A7D282C7236C79F50000A9BF /* strfunc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strfunc.h; sourceTree = ""; }; + A7D282C8236C79F50000A9BF /* itoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = itoa.h; sourceTree = ""; }; + A7D282C9236C79F50000A9BF /* stack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stack.h; sourceTree = ""; }; + A7D282CA236C79F50000A9BF /* dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dtoa.h; sourceTree = ""; }; + A7D282CB236C79F50000A9BF /* meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = meta.h; sourceTree = ""; }; + A7D282CC236C79F50000A9BF /* pow10.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pow10.h; sourceTree = ""; }; + A7D282CD236C79F50000A9BF /* filewritestream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = filewritestream.h; path = ../../../../rlottie/src/lottie/rapidjson/filewritestream.h; sourceTree = ""; }; + A7D282CE236C79F50000A9BF /* allocators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = allocators.h; path = ../../../../rlottie/src/lottie/rapidjson/allocators.h; sourceTree = ""; }; + A7D282CF236C79F50000A9BF /* reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = reader.h; path = ../../../../rlottie/src/lottie/rapidjson/reader.h; sourceTree = ""; }; + A7D282D0236C79F50000A9BF /* schema.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = schema.h; path = ../../../../rlottie/src/lottie/rapidjson/schema.h; sourceTree = ""; }; + A7D282D1236C79F50000A9BF /* writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = writer.h; path = ../../../../rlottie/src/lottie/rapidjson/writer.h; sourceTree = ""; }; + A7D282D5236C79F50000A9BF /* filereadstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = filereadstream.h; path = ../../../../rlottie/src/lottie/rapidjson/filereadstream.h; sourceTree = ""; }; + A7D282D6236C79F50000A9BF /* encodedstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = encodedstream.h; path = ../../../../rlottie/src/lottie/rapidjson/encodedstream.h; sourceTree = ""; }; + A7D282D7236C79F50000A9BF /* document.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = document.h; path = ../../../../rlottie/src/lottie/rapidjson/document.h; sourceTree = ""; }; + A7D282D8236C79F50000A9BF /* memorystream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memorystream.h; path = ../../../../rlottie/src/lottie/rapidjson/memorystream.h; sourceTree = ""; }; + A7D282D9236C79F50000A9BF /* cursorstreamwrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cursorstreamwrapper.h; path = ../../../../rlottie/src/lottie/rapidjson/cursorstreamwrapper.h; sourceTree = ""; }; + A7D282DA236C79F60000A9BF /* encodings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = encodings.h; path = ../../../../rlottie/src/lottie/rapidjson/encodings.h; sourceTree = ""; }; + A7D282DB236C79F60000A9BF /* fwd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fwd.h; path = ../../../../rlottie/src/lottie/rapidjson/fwd.h; sourceTree = ""; }; + A7D282DC236C79F60000A9BF /* memorybuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memorybuffer.h; path = ../../../../rlottie/src/lottie/rapidjson/memorybuffer.h; sourceTree = ""; }; + A7D282DD236C79F60000A9BF /* istreamwrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = istreamwrapper.h; path = ../../../../rlottie/src/lottie/rapidjson/istreamwrapper.h; sourceTree = ""; }; + A7D282DE236C79F60000A9BF /* ostreamwrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ostreamwrapper.h; path = ../../../../rlottie/src/lottie/rapidjson/ostreamwrapper.h; sourceTree = ""; }; + A7D282DF236C79F60000A9BF /* stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stream.h; path = ../../../../rlottie/src/lottie/rapidjson/stream.h; sourceTree = ""; }; + A7D282E1236C79F60000A9BF /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = ""; }; + A7D282E2236C79F60000A9BF /* en.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = en.h; sourceTree = ""; }; + A7D282E3236C79F60000A9BF /* rapidjson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rapidjson.h; path = ../../../../rlottie/src/lottie/rapidjson/rapidjson.h; sourceTree = ""; }; + A7D282E4236C79F60000A9BF /* stringbuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stringbuffer.h; path = ../../../../rlottie/src/lottie/rapidjson/stringbuffer.h; sourceTree = ""; }; + A7D282E5236C79F60000A9BF /* pointer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pointer.h; path = ../../../../rlottie/src/lottie/rapidjson/pointer.h; sourceTree = ""; }; + A7D2830B236C7A250000A9BF /* vpath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vpath.h; path = ../../../rlottie/src/vector/vpath.h; sourceTree = ""; }; + A7D2830C236C7A250000A9BF /* vraster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vraster.h; path = ../../../rlottie/src/vector/vraster.h; sourceTree = ""; }; + A7D2830D236C7A250000A9BF /* vmatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vmatrix.h; path = ../../../rlottie/src/vector/vmatrix.h; sourceTree = ""; }; + A7D2830E236C7A250000A9BF /* vdrawhelper_neon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdrawhelper_neon.cpp; path = ../../../rlottie/src/vector/vdrawhelper_neon.cpp; sourceTree = ""; }; + A7D2830F236C7A250000A9BF /* vpainter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vpainter.cpp; path = ../../../rlottie/src/vector/vpainter.cpp; sourceTree = ""; }; + A7D28310236C7A250000A9BF /* vbitmap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vbitmap.h; path = ../../../rlottie/src/vector/vbitmap.h; sourceTree = ""; }; + A7D28311236C7A250000A9BF /* vrect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vrect.h; path = ../../../rlottie/src/vector/vrect.h; sourceTree = ""; }; + A7D28312236C7A250000A9BF /* vinterpolator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vinterpolator.cpp; path = ../../../rlottie/src/vector/vinterpolator.cpp; sourceTree = ""; }; + A7D28313236C7A250000A9BF /* vpathmesure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vpathmesure.cpp; path = ../../../rlottie/src/vector/vpathmesure.cpp; sourceTree = ""; }; + A7D28314236C7A250000A9BF /* vdasher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vdasher.h; path = ../../../rlottie/src/vector/vdasher.h; sourceTree = ""; }; + A7D28315236C7A250000A9BF /* vcowptr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vcowptr.h; path = ../../../rlottie/src/vector/vcowptr.h; sourceTree = ""; }; + A7D28316236C7A250000A9BF /* vrect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vrect.cpp; path = ../../../rlottie/src/vector/vrect.cpp; sourceTree = ""; }; + A7D28317236C7A250000A9BF /* vbitmap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vbitmap.cpp; path = ../../../rlottie/src/vector/vbitmap.cpp; sourceTree = ""; }; + A7D28318236C7A250000A9BF /* vpainter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vpainter.h; path = ../../../rlottie/src/vector/vpainter.h; sourceTree = ""; }; + A7D28319236C7A250000A9BF /* velapsedtimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = velapsedtimer.h; path = ../../../rlottie/src/vector/velapsedtimer.h; sourceTree = ""; }; + A7D2831A236C7A260000A9BF /* vdebug.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdebug.cpp; path = ../../../rlottie/src/vector/vdebug.cpp; sourceTree = ""; }; + A7D2831B236C7A260000A9BF /* vdrawhelper_sse2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdrawhelper_sse2.cpp; path = ../../../rlottie/src/vector/vdrawhelper_sse2.cpp; sourceTree = ""; }; + A7D2831C236C7A260000A9BF /* vdrawhelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vdrawhelper.h; path = ../../../rlottie/src/vector/vdrawhelper.h; sourceTree = ""; }; + A7D2831D236C7A260000A9BF /* vimageloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vimageloader.cpp; path = ../../../rlottie/src/vector/vimageloader.cpp; sourceTree = ""; }; + A7D2831E236C7A260000A9BF /* vdrawable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdrawable.cpp; path = ../../../rlottie/src/vector/vdrawable.cpp; sourceTree = ""; }; + A7D2831F236C7A260000A9BF /* vmatrix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vmatrix.cpp; path = ../../../rlottie/src/vector/vmatrix.cpp; sourceTree = ""; }; + A7D28320236C7A260000A9BF /* vraster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vraster.cpp; path = ../../../rlottie/src/vector/vraster.cpp; sourceTree = ""; }; + A7D28321236C7A260000A9BF /* vline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vline.h; path = ../../../rlottie/src/vector/vline.h; sourceTree = ""; }; + A7D28322236C7A260000A9BF /* vdebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vdebug.h; path = ../../../rlottie/src/vector/vdebug.h; sourceTree = ""; }; + A7D28323236C7A260000A9BF /* vdrawable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vdrawable.h; path = ../../../rlottie/src/vector/vdrawable.h; sourceTree = ""; }; + A7D28324236C7A260000A9BF /* vpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vpoint.h; path = ../../../rlottie/src/vector/vpoint.h; sourceTree = ""; }; + A7D28325236C7A260000A9BF /* vbezier.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vbezier.cpp; path = ../../../rlottie/src/vector/vbezier.cpp; sourceTree = ""; }; + A7D28326236C7A260000A9BF /* vpathmesure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vpathmesure.h; path = ../../../rlottie/src/vector/vpathmesure.h; sourceTree = ""; }; + A7D28327236C7A260000A9BF /* velapsedtimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = velapsedtimer.cpp; path = ../../../rlottie/src/vector/velapsedtimer.cpp; sourceTree = ""; }; + A7D28328236C7A260000A9BF /* vrle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vrle.h; path = ../../../rlottie/src/vector/vrle.h; sourceTree = ""; }; + A7D28329236C7A260000A9BF /* vglobal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vglobal.h; path = ../../../rlottie/src/vector/vglobal.h; sourceTree = ""; }; + A7D2832A236C7A260000A9BF /* vbrush.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vbrush.cpp; path = ../../../rlottie/src/vector/vbrush.cpp; sourceTree = ""; }; + A7D2832B236C7A260000A9BF /* vrle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vrle.cpp; path = ../../../rlottie/src/vector/vrle.cpp; sourceTree = ""; }; + A7D2832C236C7A260000A9BF /* vpath.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vpath.cpp; path = ../../../rlottie/src/vector/vpath.cpp; sourceTree = ""; }; + A7D2832D236C7A260000A9BF /* vtaskqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vtaskqueue.h; path = ../../../rlottie/src/vector/vtaskqueue.h; sourceTree = ""; }; + A7D2832E236C7A260000A9BF /* vbrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vbrush.h; path = ../../../rlottie/src/vector/vbrush.h; sourceTree = ""; }; + A7D2832F236C7A260000A9BF /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = ../../../rlottie/src/vector/config.h; sourceTree = ""; }; + A7D28330236C7A270000A9BF /* vinterpolator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vinterpolator.h; path = ../../../rlottie/src/vector/vinterpolator.h; sourceTree = ""; }; + A7D28331236C7A270000A9BF /* vcompositionfunctions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vcompositionfunctions.cpp; path = ../../../rlottie/src/vector/vcompositionfunctions.cpp; sourceTree = ""; }; + A7D28332236C7A270000A9BF /* vdrawhelper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdrawhelper.cpp; path = ../../../rlottie/src/vector/vdrawhelper.cpp; sourceTree = ""; }; + A7D28333236C7A270000A9BF /* vstackallocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vstackallocator.h; path = ../../../rlottie/src/vector/vstackallocator.h; sourceTree = ""; }; + A7D28334236C7A270000A9BF /* vdasher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vdasher.cpp; path = ../../../rlottie/src/vector/vdasher.cpp; sourceTree = ""; }; + A7D28335236C7A270000A9BF /* vbezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vbezier.h; path = ../../../rlottie/src/vector/vbezier.h; sourceTree = ""; }; + A7D28336236C7A270000A9BF /* vimageloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vimageloader.h; path = ../../../rlottie/src/vector/vimageloader.h; sourceTree = ""; }; + A7D28364236C7A380000A9BF /* v_ft_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = v_ft_types.h; path = ../../../../rlottie/src/vector/freetype/v_ft_types.h; sourceTree = ""; }; + A7D28365236C7A380000A9BF /* v_ft_stroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = v_ft_stroker.h; path = ../../../../rlottie/src/vector/freetype/v_ft_stroker.h; sourceTree = ""; }; + A7D28366236C7A380000A9BF /* v_ft_raster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = v_ft_raster.h; path = ../../../../rlottie/src/vector/freetype/v_ft_raster.h; sourceTree = ""; }; + A7D28367236C7A380000A9BF /* v_ft_math.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = v_ft_math.h; path = ../../../../rlottie/src/vector/freetype/v_ft_math.h; sourceTree = ""; }; + A7D28368236C7A380000A9BF /* v_ft_math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = v_ft_math.cpp; path = ../../../../rlottie/src/vector/freetype/v_ft_math.cpp; sourceTree = ""; }; + A7D28369236C7A380000A9BF /* v_ft_stroker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = v_ft_stroker.cpp; path = ../../../../rlottie/src/vector/freetype/v_ft_stroker.cpp; sourceTree = ""; }; + A7D2836A236C7A380000A9BF /* v_ft_raster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = v_ft_raster.cpp; path = ../../../../rlottie/src/vector/freetype/v_ft_raster.cpp; sourceTree = ""; }; + A7D28373236C7A8E0000A9BF /* vregion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vregion.cpp; path = ../../../../rlottie/src/vector/pixman/vregion.cpp; sourceTree = ""; }; + A7D28374236C7A8E0000A9BF /* vregion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vregion.h; path = ../../../../rlottie/src/vector/pixman/vregion.h; sourceTree = ""; }; + A7D2837D236C7AC90000A9BF /* rlottie.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rlottie.h; path = ../../rlottie/inc/rlottie.h; sourceTree = ""; }; + A7D2837E236C7AC90000A9BF /* rlottiecommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rlottiecommon.h; path = ../../rlottie/inc/rlottiecommon.h; sourceTree = ""; }; + A7D2837F236C7AC90000A9BF /* rlottie_capi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rlottie_capi.h; path = ../../rlottie/inc/rlottie_capi.h; sourceTree = ""; }; + A7ED5DB1236C7D5500040372 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + A7ED5DB3236C7D6300040372 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + A7ED5DB5236C7D6B00040372 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + A7ED5DB7236C7D8000040372 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + A7ED5DB9236C7D9100040372 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A7D28267236C77FA0000A9BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7ED5DBA236C7D9100040372 /* Foundation.framework in Frameworks */, + A7ED5DB8236C7D8000040372 /* AppKit.framework in Frameworks */, + A7ED5DB6236C7D6B00040372 /* CoreMedia.framework in Frameworks */, + A7ED5DB4236C7D6300040372 /* Accelerate.framework in Frameworks */, + A7ED5DB2236C7D5500040372 /* QuartzCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A7D28260236C77FA0000A9BF = { + isa = PBXGroup; + children = ( + A7D2837C236C7AC20000A9BF /* include */, + A7D28282236C793E0000A9BF /* src */, + A7D2827D236C78B00000A9BF /* Sources */, + A7D2826C236C77FA0000A9BF /* RLottie */, + A7D2826B236C77FA0000A9BF /* Products */, + A7ED5DB0236C7D5400040372 /* Frameworks */, + ); + sourceTree = ""; + }; + A7D2826B236C77FA0000A9BF /* Products */ = { + isa = PBXGroup; + children = ( + A7D2826A236C77FA0000A9BF /* RLottie.framework */, + ); + name = Products; + sourceTree = ""; + }; + A7D2826C236C77FA0000A9BF /* RLottie */ = { + isa = PBXGroup; + children = ( + A7D2826D236C77FA0000A9BF /* RLottie.h */, + A7D2826E236C77FA0000A9BF /* Info.plist */, + ); + path = RLottie; + sourceTree = ""; + }; + A7D2827D236C78B00000A9BF /* Sources */ = { + isa = PBXGroup; + children = ( + A7D2827F236C78DF0000A9BF /* RLottieBridge.h */, + A7D2827E236C78DF0000A9BF /* RLottieBridge.mm */, + ); + path = Sources; + sourceTree = ""; + }; + A7D28282236C793E0000A9BF /* src */ = { + isa = PBXGroup; + children = ( + A7D2830A236C7A110000A9BF /* vector */, + A7D282A3236C79CC0000A9BF /* lottie */, + A7D2829F236C79B30000A9BF /* binding */, + ); + path = src; + sourceTree = ""; + }; + A7D2829F236C79B30000A9BF /* binding */ = { + isa = PBXGroup; + children = ( + A7D282A0236C79BB0000A9BF /* c */, + ); + path = binding; + sourceTree = ""; + }; + A7D282A0236C79BB0000A9BF /* c */ = { + isa = PBXGroup; + children = ( + A7D282A1236C79C40000A9BF /* lottieanimation_capi.cpp */, + ); + path = c; + sourceTree = ""; + }; + A7D282A3236C79CC0000A9BF /* lottie */ = { + isa = PBXGroup; + children = ( + A7D282BE236C79D90000A9BF /* rapidjson */, + A7D282A8236C79D70000A9BF /* lottieanimation.cpp */, + A7D282AC236C79D70000A9BF /* lottieitem.cpp */, + A7D282AD236C79D70000A9BF /* lottieitem.h */, + A7D282A6236C79D70000A9BF /* lottiekeypath.cpp */, + A7D282AB236C79D70000A9BF /* lottiekeypath.h */, + A7D282A4236C79D60000A9BF /* lottieloader.cpp */, + A7D282AF236C79D70000A9BF /* lottieloader.h */, + A7D282A7236C79D70000A9BF /* lottiemodel.cpp */, + A7D282AA236C79D70000A9BF /* lottiemodel.h */, + A7D282AE236C79D70000A9BF /* lottieparser.cpp */, + A7D282A9236C79D70000A9BF /* lottieparser.h */, + A7D282A5236C79D60000A9BF /* lottieproxymodel.cpp */, + A7D282B0236C79D70000A9BF /* lottieproxymodel.h */, + ); + path = lottie; + sourceTree = ""; + }; + A7D282BE236C79D90000A9BF /* rapidjson */ = { + isa = PBXGroup; + children = ( + A7D282CE236C79F50000A9BF /* allocators.h */, + A7D282D9236C79F50000A9BF /* cursorstreamwrapper.h */, + A7D282D7236C79F50000A9BF /* document.h */, + A7D282D6236C79F50000A9BF /* encodedstream.h */, + A7D282DA236C79F60000A9BF /* encodings.h */, + A7D282E0236C79F60000A9BF /* error */, + A7D282D5236C79F50000A9BF /* filereadstream.h */, + A7D282CD236C79F50000A9BF /* filewritestream.h */, + A7D282DB236C79F60000A9BF /* fwd.h */, + A7D282C0236C79F50000A9BF /* internal */, + A7D282DD236C79F60000A9BF /* istreamwrapper.h */, + A7D282DC236C79F60000A9BF /* memorybuffer.h */, + A7D282D8236C79F50000A9BF /* memorystream.h */, + A7D282DE236C79F60000A9BF /* ostreamwrapper.h */, + A7D282E5236C79F60000A9BF /* pointer.h */, + A7D282BF236C79F50000A9BF /* prettywriter.h */, + A7D282E3236C79F60000A9BF /* rapidjson.h */, + A7D282CF236C79F50000A9BF /* reader.h */, + A7D282D0236C79F50000A9BF /* schema.h */, + A7D282DF236C79F60000A9BF /* stream.h */, + A7D282E4236C79F60000A9BF /* stringbuffer.h */, + A7D282D1236C79F50000A9BF /* writer.h */, + ); + path = rapidjson; + sourceTree = ""; + }; + A7D282C0236C79F50000A9BF /* internal */ = { + isa = PBXGroup; + children = ( + A7D282C1236C79F50000A9BF /* ieee754.h */, + A7D282C2236C79F50000A9BF /* strtod.h */, + A7D282C3236C79F50000A9BF /* swap.h */, + A7D282C4236C79F50000A9BF /* regex.h */, + A7D282C5236C79F50000A9BF /* diyfp.h */, + A7D282C6236C79F50000A9BF /* biginteger.h */, + A7D282C7236C79F50000A9BF /* strfunc.h */, + A7D282C8236C79F50000A9BF /* itoa.h */, + A7D282C9236C79F50000A9BF /* stack.h */, + A7D282CA236C79F50000A9BF /* dtoa.h */, + A7D282CB236C79F50000A9BF /* meta.h */, + A7D282CC236C79F50000A9BF /* pow10.h */, + ); + name = internal; + path = ../../../../rlottie/src/lottie/rapidjson/internal; + sourceTree = ""; + }; + A7D282E0236C79F60000A9BF /* error */ = { + isa = PBXGroup; + children = ( + A7D282E1236C79F60000A9BF /* error.h */, + A7D282E2236C79F60000A9BF /* en.h */, + ); + name = error; + path = ../../../../rlottie/src/lottie/rapidjson/error; + sourceTree = ""; + }; + A7D2830A236C7A110000A9BF /* vector */ = { + isa = PBXGroup; + children = ( + A7D28372236C7A400000A9BF /* pixman */, + A7D28363236C7A2F0000A9BF /* freetype */, + A7D2832F236C7A260000A9BF /* config.h */, + A7D28325236C7A260000A9BF /* vbezier.cpp */, + A7D28335236C7A270000A9BF /* vbezier.h */, + A7D28317236C7A250000A9BF /* vbitmap.cpp */, + A7D28310236C7A250000A9BF /* vbitmap.h */, + A7D2832A236C7A260000A9BF /* vbrush.cpp */, + A7D2832E236C7A260000A9BF /* vbrush.h */, + A7D28331236C7A270000A9BF /* vcompositionfunctions.cpp */, + A7D28315236C7A250000A9BF /* vcowptr.h */, + A7D28334236C7A270000A9BF /* vdasher.cpp */, + A7D28314236C7A250000A9BF /* vdasher.h */, + A7D2831A236C7A260000A9BF /* vdebug.cpp */, + A7D28322236C7A260000A9BF /* vdebug.h */, + A7D2831E236C7A260000A9BF /* vdrawable.cpp */, + A7D28323236C7A260000A9BF /* vdrawable.h */, + A7D2830E236C7A250000A9BF /* vdrawhelper_neon.cpp */, + A7D2831B236C7A260000A9BF /* vdrawhelper_sse2.cpp */, + A7D28332236C7A270000A9BF /* vdrawhelper.cpp */, + A7D2831C236C7A260000A9BF /* vdrawhelper.h */, + A7D28327236C7A260000A9BF /* velapsedtimer.cpp */, + A7D28319236C7A250000A9BF /* velapsedtimer.h */, + A7D28329236C7A260000A9BF /* vglobal.h */, + A7D2831D236C7A260000A9BF /* vimageloader.cpp */, + A7D28336236C7A270000A9BF /* vimageloader.h */, + A7D28312236C7A250000A9BF /* vinterpolator.cpp */, + A7D28330236C7A270000A9BF /* vinterpolator.h */, + A7D28321236C7A260000A9BF /* vline.h */, + A7D2831F236C7A260000A9BF /* vmatrix.cpp */, + A7D2830D236C7A250000A9BF /* vmatrix.h */, + A7D2830F236C7A250000A9BF /* vpainter.cpp */, + A7D28318236C7A250000A9BF /* vpainter.h */, + A7D2832C236C7A260000A9BF /* vpath.cpp */, + A7D2830B236C7A250000A9BF /* vpath.h */, + A7D28313236C7A250000A9BF /* vpathmesure.cpp */, + A7D28326236C7A260000A9BF /* vpathmesure.h */, + A7D28324236C7A260000A9BF /* vpoint.h */, + A7D28320236C7A260000A9BF /* vraster.cpp */, + A7D2830C236C7A250000A9BF /* vraster.h */, + A7D28316236C7A250000A9BF /* vrect.cpp */, + A7D28311236C7A250000A9BF /* vrect.h */, + A7D2832B236C7A260000A9BF /* vrle.cpp */, + A7D28328236C7A260000A9BF /* vrle.h */, + A7D28333236C7A270000A9BF /* vstackallocator.h */, + A7D2832D236C7A260000A9BF /* vtaskqueue.h */, + ); + path = vector; + sourceTree = ""; + }; + A7D28363236C7A2F0000A9BF /* freetype */ = { + isa = PBXGroup; + children = ( + A7D28368236C7A380000A9BF /* v_ft_math.cpp */, + A7D28367236C7A380000A9BF /* v_ft_math.h */, + A7D2836A236C7A380000A9BF /* v_ft_raster.cpp */, + A7D28366236C7A380000A9BF /* v_ft_raster.h */, + A7D28369236C7A380000A9BF /* v_ft_stroker.cpp */, + A7D28365236C7A380000A9BF /* v_ft_stroker.h */, + A7D28364236C7A380000A9BF /* v_ft_types.h */, + ); + path = freetype; + sourceTree = ""; + }; + A7D28372236C7A400000A9BF /* pixman */ = { + isa = PBXGroup; + children = ( + A7D28373236C7A8E0000A9BF /* vregion.cpp */, + A7D28374236C7A8E0000A9BF /* vregion.h */, + ); + path = pixman; + sourceTree = ""; + }; + A7D2837C236C7AC20000A9BF /* include */ = { + isa = PBXGroup; + children = ( + A7D2837F236C7AC90000A9BF /* rlottie_capi.h */, + A7D2837D236C7AC90000A9BF /* rlottie.h */, + A7D2837E236C7AC90000A9BF /* rlottiecommon.h */, + ); + path = include; + sourceTree = ""; + }; + A7ED5DB0236C7D5400040372 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7ED5DB9236C7D9100040372 /* Foundation.framework */, + A7ED5DB7236C7D8000040372 /* AppKit.framework */, + A7ED5DB5236C7D6B00040372 /* CoreMedia.framework */, + A7ED5DB3236C7D6300040372 /* Accelerate.framework */, + A7ED5DB1236C7D5500040372 /* QuartzCore.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A7D28265236C77FA0000A9BF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A7D282B6236C79D70000A9BF /* lottieparser.h in Headers */, + A7D28281236C78DF0000A9BF /* RLottieBridge.h in Headers */, + A7D282FE236C79F60000A9BF /* cursorstreamwrapper.h in Headers */, + A7D282FB236C79F60000A9BF /* encodedstream.h in Headers */, + A7D282FC236C79F60000A9BF /* document.h in Headers */, + A7D282B7236C79D70000A9BF /* lottiemodel.h in Headers */, + A7D28339236C7A270000A9BF /* vmatrix.h in Headers */, + A7D282BA236C79D70000A9BF /* lottieitem.h in Headers */, + A7D282E7236C79F60000A9BF /* ieee754.h in Headers */, + A7D28302236C79F60000A9BF /* istreamwrapper.h in Headers */, + A7D282EE236C79F60000A9BF /* itoa.h in Headers */, + A7D282F6236C79F60000A9BF /* schema.h in Headers */, + A7D282ED236C79F60000A9BF /* strfunc.h in Headers */, + A7D282FD236C79F60000A9BF /* memorystream.h in Headers */, + A7D28381236C7AC90000A9BF /* rlottiecommon.h in Headers */, + A7D28338236C7A270000A9BF /* vraster.h in Headers */, + A7D2835A236C7A270000A9BF /* vbrush.h in Headers */, + A7D282F3236C79F60000A9BF /* filewritestream.h in Headers */, + A7D282FA236C79F60000A9BF /* filereadstream.h in Headers */, + A7D28341236C7A270000A9BF /* vcowptr.h in Headers */, + A7D28355236C7A270000A9BF /* vglobal.h in Headers */, + A7D2835B236C7A270000A9BF /* config.h in Headers */, + A7D2826F236C77FA0000A9BF /* RLottie.h in Headers */, + A7D2836D236C7A380000A9BF /* v_ft_raster.h in Headers */, + A7D282E6236C79F60000A9BF /* prettywriter.h in Headers */, + A7D282EA236C79F60000A9BF /* regex.h in Headers */, + A7D282FF236C79F60000A9BF /* encodings.h in Headers */, + A7D282EF236C79F60000A9BF /* stack.h in Headers */, + A7D28309236C79F60000A9BF /* pointer.h in Headers */, + A7D28301236C79F60000A9BF /* memorybuffer.h in Headers */, + A7D28354236C7A270000A9BF /* vrle.h in Headers */, + A7D28352236C7A270000A9BF /* vpathmesure.h in Headers */, + A7D2836B236C7A380000A9BF /* v_ft_types.h in Headers */, + A7D28380236C7AC90000A9BF /* rlottie.h in Headers */, + A7D282EB236C79F60000A9BF /* diyfp.h in Headers */, + A7D282E8236C79F60000A9BF /* strtod.h in Headers */, + A7D28359236C7A270000A9BF /* vtaskqueue.h in Headers */, + A7D282E9236C79F60000A9BF /* swap.h in Headers */, + A7D2835F236C7A270000A9BF /* vstackallocator.h in Headers */, + A7D282BD236C79D70000A9BF /* lottieproxymodel.h in Headers */, + A7D28362236C7A270000A9BF /* vimageloader.h in Headers */, + A7D28348236C7A270000A9BF /* vdrawhelper.h in Headers */, + A7D28382236C7AC90000A9BF /* rlottie_capi.h in Headers */, + A7D282B8236C79D70000A9BF /* lottiekeypath.h in Headers */, + A7D28303236C79F60000A9BF /* ostreamwrapper.h in Headers */, + A7D2833D236C7A270000A9BF /* vrect.h in Headers */, + A7D28307236C79F60000A9BF /* rapidjson.h in Headers */, + A7D28306236C79F60000A9BF /* en.h in Headers */, + A7D28361236C7A270000A9BF /* vbezier.h in Headers */, + A7D282F4236C79F60000A9BF /* allocators.h in Headers */, + A7D282F5236C79F60000A9BF /* reader.h in Headers */, + A7D2833C236C7A270000A9BF /* vbitmap.h in Headers */, + A7D282F2236C79F60000A9BF /* pow10.h in Headers */, + A7D28376236C7A8E0000A9BF /* vregion.h in Headers */, + A7D282F1236C79F60000A9BF /* meta.h in Headers */, + A7D282BC236C79D70000A9BF /* lottieloader.h in Headers */, + A7D2836C236C7A380000A9BF /* v_ft_stroker.h in Headers */, + A7D2834D236C7A270000A9BF /* vline.h in Headers */, + A7D28344236C7A270000A9BF /* vpainter.h in Headers */, + A7D28340236C7A270000A9BF /* vdasher.h in Headers */, + A7D28304236C79F60000A9BF /* stream.h in Headers */, + A7D28300236C79F60000A9BF /* fwd.h in Headers */, + A7D28308236C79F60000A9BF /* stringbuffer.h in Headers */, + A7D282EC236C79F60000A9BF /* biginteger.h in Headers */, + A7D282F7236C79F60000A9BF /* writer.h in Headers */, + A7D28350236C7A270000A9BF /* vpoint.h in Headers */, + A7D2835C236C7A270000A9BF /* vinterpolator.h in Headers */, + A7D2834F236C7A270000A9BF /* vdrawable.h in Headers */, + A7D2836E236C7A380000A9BF /* v_ft_math.h in Headers */, + A7D282F0236C79F60000A9BF /* dtoa.h in Headers */, + A7D2834E236C7A270000A9BF /* vdebug.h in Headers */, + A7D28337236C7A270000A9BF /* vpath.h in Headers */, + A7D28345236C7A270000A9BF /* velapsedtimer.h in Headers */, + A7D28305236C79F60000A9BF /* error.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A7D28269236C77FA0000A9BF /* RLottie */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7D28272236C77FA0000A9BF /* Build configuration list for PBXNativeTarget "RLottie" */; + buildPhases = ( + A7D28265236C77FA0000A9BF /* Headers */, + A7D28266236C77FA0000A9BF /* Sources */, + A7D28267236C77FA0000A9BF /* Frameworks */, + A7D28268236C77FA0000A9BF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RLottie; + productName = RLottie; + productReference = A7D2826A236C77FA0000A9BF /* RLottie.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A7D28261236C77FA0000A9BF /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + A7D28269236C77FA0000A9BF = { + CreatedOnToolsVersion = 10.3; + }; + }; + }; + buildConfigurationList = A7D28264236C77FA0000A9BF /* Build configuration list for PBXProject "RLottie_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A7D28260236C77FA0000A9BF; + productRefGroup = A7D2826B236C77FA0000A9BF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A7D28269236C77FA0000A9BF /* RLottie */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A7D28268236C77FA0000A9BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A7D28266236C77FA0000A9BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7D282B4236C79D70000A9BF /* lottiemodel.cpp in Sources */, + A7D28351236C7A270000A9BF /* vbezier.cpp in Sources */, + A7D2834C236C7A270000A9BF /* vraster.cpp in Sources */, + A7D2833B236C7A270000A9BF /* vpainter.cpp in Sources */, + A7D2834A236C7A270000A9BF /* vdrawable.cpp in Sources */, + A7D282B1236C79D70000A9BF /* lottieloader.cpp in Sources */, + A7D2833A236C7A270000A9BF /* vdrawhelper_neon.cpp in Sources */, + A7D28358236C7A270000A9BF /* vpath.cpp in Sources */, + A7D28343236C7A270000A9BF /* vbitmap.cpp in Sources */, + A7D2835D236C7A270000A9BF /* vcompositionfunctions.cpp in Sources */, + A7D282B5236C79D70000A9BF /* lottieanimation.cpp in Sources */, + A7D2833F236C7A270000A9BF /* vpathmesure.cpp in Sources */, + A7D2836F236C7A380000A9BF /* v_ft_math.cpp in Sources */, + A7D28347236C7A270000A9BF /* vdrawhelper_sse2.cpp in Sources */, + A7D28353236C7A270000A9BF /* velapsedtimer.cpp in Sources */, + A7D282B3236C79D70000A9BF /* lottiekeypath.cpp in Sources */, + A7D28375236C7A8E0000A9BF /* vregion.cpp in Sources */, + A7D28357236C7A270000A9BF /* vrle.cpp in Sources */, + A7D28356236C7A270000A9BF /* vbrush.cpp in Sources */, + A7D28346236C7A270000A9BF /* vdebug.cpp in Sources */, + A7D2834B236C7A270000A9BF /* vmatrix.cpp in Sources */, + A7D282B2236C79D70000A9BF /* lottieproxymodel.cpp in Sources */, + A7D2835E236C7A270000A9BF /* vdrawhelper.cpp in Sources */, + A7D28370236C7A380000A9BF /* v_ft_stroker.cpp in Sources */, + A7D28360236C7A270000A9BF /* vdasher.cpp in Sources */, + A7D28349236C7A270000A9BF /* vimageloader.cpp in Sources */, + A7D2833E236C7A270000A9BF /* vinterpolator.cpp in Sources */, + A7D282A2236C79C40000A9BF /* lottieanimation_capi.cpp in Sources */, + A7D28342236C7A270000A9BF /* vrect.cpp in Sources */, + A7D282BB236C79D70000A9BF /* lottieparser.cpp in Sources */, + A7D28280236C78DF0000A9BF /* RLottieBridge.mm in Sources */, + A7D282B9236C79D70000A9BF /* lottieitem.cpp in Sources */, + A7D28371236C7A380000A9BF /* v_ft_raster.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7D28271236C77FA0000A9BF /* ReleaseHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + A7D28274236C77FA0000A9BF /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseHockeyapp; + }; + A7D28275236C78800000A9BF /* DebugHockeyapp */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + A7D28276236C78800000A9BF /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugHockeyapp; + }; + A7D28277236C78870000A9BF /* ReleaseAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + A7D28278236C78870000A9BF /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = ReleaseAppStore; + }; + A7D28279236C78950000A9BF /* HockeyappMacAlpha */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + A7D2827A236C78950000A9BF /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = HockeyappMacAlpha; + }; + A7D2827B236C78A30000A9BF /* DebugAppStore */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + A7D2827C236C78A30000A9BF /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = DebugAppStore; + }; + A7F282E9238EAB7900742C20 /* Github */ = { + 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_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; + CODE_SIGN_IDENTITY = "Mac Developer"; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282EA238EAB7900742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = RLottie/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.RLottie; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + }; + name = Github; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A7D28264236C77FA0000A9BF /* Build configuration list for PBXProject "RLottie_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7D28271236C77FA0000A9BF /* ReleaseHockeyapp */, + A7D28275236C78800000A9BF /* DebugHockeyapp */, + A7D28277236C78870000A9BF /* ReleaseAppStore */, + A7D28279236C78950000A9BF /* HockeyappMacAlpha */, + A7D2827B236C78A30000A9BF /* DebugAppStore */, + A7F282E9238EAB7900742C20 /* Github */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; + A7D28272236C77FA0000A9BF /* Build configuration list for PBXNativeTarget "RLottie" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7D28274236C77FA0000A9BF /* ReleaseHockeyapp */, + A7D28276236C78800000A9BF /* DebugHockeyapp */, + A7D28278236C78870000A9BF /* ReleaseAppStore */, + A7D2827A236C78950000A9BF /* HockeyappMacAlpha */, + A7D2827C236C78A30000A9BF /* DebugAppStore */, + A7F282EA238EAB7900742C20 /* Github */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseHockeyapp; + }; +/* End XCConfigurationList section */ + }; + rootObject = A7D28261236C77FA0000A9BF /* Project object */; +} diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..6aaba923ec --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000..66acd493c0 Binary files /dev/null and b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/project.xcworkspace/xcuserdata/overtake.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/RLottie.xcscheme b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/RLottie.xcscheme new file mode 100644 index 0000000000..8f83d4773a --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/RLottie.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..1bd468b816 --- /dev/null +++ b/submodules/RLottie_Xcode/RLottie_Xcode.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + RLottie.xcscheme + + isShown + + orderHint + 23 + + + SuppressBuildableAutocreation + + A7D28269236C77FA0000A9BF + + primary + + + + + diff --git a/submodules/RLottie_Xcode/Sources/RLottieBridge.h b/submodules/RLottie_Xcode/Sources/RLottieBridge.h new file mode 100644 index 0000000000..469af956b6 --- /dev/null +++ b/submodules/RLottie_Xcode/Sources/RLottieBridge.h @@ -0,0 +1,27 @@ +// +// RLottie.h +// Telegram +// +// Created by Mikhail Filimonov on 15/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import +#import +#import +NS_ASSUME_NONNULL_BEGIN + +@interface RLottieBridge : NSObject +-(id __nullable)initWithJson:(NSString *)string key:(NSString *)cachedKey; +-(CGImageRef)renderFrame:(int)frame width:(size_t)w height:(size_t)h; +-(CMSampleBufferRef)renderSampleBufferFrame:(int)frame timebase:(CMTimebaseRef)timebase width:(size_t)w height:(size_t)h fps:(size_t)fps; +-(CVPixelBufferRef)renderPixelBufferFrame:(int)frame width:(size_t)w height:(size_t)h; +-(NSData *)renderDataFrame:(int)frame width:(size_t)w height:(size_t)h ; +-(void)renderFrameWithIndex:(int32_t)index into:(uint8_t * _Nonnull)buffer width:(int32_t)width height:(int32_t)height; +-(int)startFrame; +-(int)endFrame; +-(int)fps; +-(void)setColor:(NSColor *)color forKeyPath:(NSString *)keyPath; +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/RLottie_Xcode/Sources/RLottieBridge.mm b/submodules/RLottie_Xcode/Sources/RLottieBridge.mm new file mode 100644 index 0000000000..9cb157d2bf --- /dev/null +++ b/submodules/RLottie_Xcode/Sources/RLottieBridge.mm @@ -0,0 +1,239 @@ +// +// RLottie.m +// Telegram +// +// Created by Mikhail Filimonov on 15/06/2019. +// Copyright © 2019 Telegram. All rights reserved. +// +#import +#import "RLottieBridge.h" +#import +#import +#import +//#include +#include "rlottie.h" +#import + +@interface RLottieBridge () +{ + std::unique_ptr player; +} +@end + +@implementation RLottieBridge + +-(id __nullable)initWithJson:(NSString *)string key:(NSString *)cachedKey { + if (self = [super init]) { + std::string json = std::string([string UTF8String]); + std::string key = std::string([cachedKey UTF8String]); + self->player = rlottie::Animation::loadFromData(json, key); + + if (self->player == nullptr) { + return nil; + } + + + // self->animationBuffer = std::unique_ptr(new uint32_t[w * h]); + // self->surface = + + //self->surface = rlottie::Surface + } + return self; +} + +-(void)dealloc { + player.reset(); +} + +-(int)startFrame { + return 0; +} +-(int)endFrame { + return self->player->totalFrame(); +} +-(int)fps { + return self->player->frameRate(); +} + +-(CGImageRef)renderFrame:(int)frame width:(size_t)w height:(size_t)h { + + auto animationBuffer = std::unique_ptr(new uint32_t[w * h]); + rlottie::Surface surface(animationBuffer.get(), w, h, w * 4); + player->renderSync(frame, surface); + + NSMutableData *data = [[NSMutableData alloc] initWithLength:w * h * 4]; + memset((uint8_t *)data.bytes + w * h * 4, 255, w * h); + + vImage_Buffer inputBuffer; + inputBuffer.width = w; + inputBuffer.height = h; + inputBuffer.rowBytes = w * 4; + inputBuffer.data = (uint8_t *)data.bytes; + memcpy(inputBuffer.data, (void *)animationBuffer.get(), w * h * 4); + + const uint8_t map[4] = { 3, 2, 1, 0 }; + // vImagePermuteChannels_ARGB8888(&inputBuffer, &inputBuffer, map, kvImageNoFlags); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_11_2) + colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceDisplayP3); + + CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); + CGImageRef image = CGImageCreate(w, h, 8, 32, w * 4, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, false, kCGRenderingIntentDefault); + CFRelease(dataProvider); + CFRelease(colorSpace); + animationBuffer.reset(); + return image; + +} + + +-(CMSampleBufferRef)renderSampleBufferFrame:(int)frame timebase:(CMTimebaseRef)timebase width:(size_t)w height:(size_t)h fps:(size_t)fps { + +// NSMutableDictionary *ioSurfaceProperties = [NSMutableDictionary dictionary]; +// [ioSurfaceProperties setValue:@(YES) forKey:@"IOSurfaceIsGlobal"]; +// +// NSMutableDictionary *options = [NSMutableDictionary dictionary]; +// +// [options setValue:ioSurfaceProperties forKey:(NSString *)kCVPixelBufferIOSurfacePropertiesKey]; +////(__bridge CFDictionaryRef)options + CVPixelBufferRef pixelBuffer = NULL; + CVPixelBufferCreate(kCFAllocatorDefault, w, h, kCVPixelFormatType_32ARGB, NULL, &pixelBuffer); + + + auto animationBuffer = std::unique_ptr(new uint32_t[w * h]); + rlottie::Surface surface(animationBuffer.get(), w, h, w * 4); + player->renderSync(frame, surface); + + + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + + void *base = CVPixelBufferGetBaseAddress(pixelBuffer); + + vImage_Buffer inputBuffer; + inputBuffer.width = w; + inputBuffer.height = h; + inputBuffer.rowBytes = w * 4; + inputBuffer.data = (void *)animationBuffer.get(); + + const uint8_t map[4] = { 3, 2, 1, 0 }; + vImagePermuteChannels_ARGB8888(&inputBuffer, &inputBuffer, map, kvImageNoFlags); + + memcpy(base, inputBuffer.data, w * h * 4); + + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); + + + CMTime currentTime = CMTimebaseGetTime(timebase); + + CMSampleTimingInfo info = CMSampleTimingInfo(); + info.duration = CMTimeMake(1 / fps, currentTime.timescale); + info.presentationTimeStamp = CMTimeAdd(CMTimeMake((1 / fps) * frame, currentTime.timescale), currentTime); //CMTimeMake((1 / 30) * frame, currentTime.timescale); + + CMFormatDescriptionRef formatDesc; + CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &formatDesc); + + + CMSampleBufferRef sampleBuffer; + CMSampleBufferCreateReadyWithImageBuffer(NULL, pixelBuffer, formatDesc, &info, &sampleBuffer); + + + CFRelease(formatDesc); + CFRelease(pixelBuffer); + animationBuffer.reset(); + return sampleBuffer; +} + + +-(CVPixelBufferRef)renderPixelBufferFrame:(int)frame width:(size_t)w height:(size_t)h { + + CVPixelBufferRef pixelBuffer = NULL; + + NSMutableDictionary *options = [NSMutableDictionary dictionary]; + + // [options setValue:(NSString *)kCVImageBufferChromaSubsampling_420 forKey:(NSString *)kCVImageBufferChromaSubsamplingKey]; + + size_t width = w;//MAX(320, w); + size_t height = h;//MAX(320, h); + + CVPixelBufferCreate(kCFAllocatorDefault, w, h, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)options, &pixelBuffer); + + auto animationBuffer = std::unique_ptr(new uint32_t[width * height]); + rlottie::Surface surface(animationBuffer.get(), width, height, width * 4); + player->renderSync(frame, surface); + + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + + void *base = CVPixelBufferGetBaseAddress(pixelBuffer); + +// NSMutableDictionary *ioSurfaceProperties = [NSMutableDictionary dictionary]; +// [ioSurfaceProperties setValue:@(YES) forKey:@"IOSurfaceIsGlobal"]; +// +// +// vImage_Buffer inputBuffer; +// inputBuffer.width = width; +// inputBuffer.height = height; +// inputBuffer.rowBytes = width * 4; +// inputBuffer.data = (void *)animationBuffer.get(); +// +// +// size_t scaledBytesPerRow = w * 4; +// void *scaledData = malloc(w * scaledBytesPerRow); +// vImage_Buffer scaledvImageBuffer = { +// .data = scaledData, +// .height = (vImagePixelCount)w, +// .width = (vImagePixelCount)h, +// .rowBytes = w * 4 +// }; +// +// vImageScale_ARGB8888(&inputBuffer, &scaledvImageBuffer, nil, 0); +// + memcpy(base, animationBuffer.get(), w * h * 4); + + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); + + animationBuffer.reset(); + return pixelBuffer; +} + +// // +// const uint8_t map[4] = { 0, 1, 2, 3 }; +// vImagePermuteChannels_ARGB8888(&inputBuffer, &inputBuffer, map, kvImageNoFlags); +//vImagePremultiplyData_ARGB8888(&inputBuffer, &inputBuffer, kvImageNoFlags); +//func vImagePremultiplyData_ARGB8888(UnsafePointer, UnsafePointer, vImage_Flags) + + +-(NSData *)renderDataFrame:(int)frame width:(size_t)w height:(size_t)h { + auto animationBuffer = std::unique_ptr(new uint32_t[w * h]); + rlottie::Surface surface(animationBuffer.get(), w, h, w * 4); + player->renderSync(frame, surface); + NSData *data = [[NSData alloc] init]; + memcpy(animationBuffer.get(), data.bytes, w * h * 4); + animationBuffer.reset(); + return data; +} + +- (void)renderFrameWithIndex:(int32_t)index into:(uint8_t * _Nonnull)buffer width:(int32_t)width height:(int32_t)height { + rlottie::Surface surface((uint32_t *)buffer, width, height, width * 4); + player->renderSync(index, surface); +} + +-(void)setColor:(NSColor *)color forKeyPath:(NSString *)keyPath { + player->setValue(keyPath.UTF8String, rlottie::Color(color.redComponent, color.greenComponent, color.blueComponent)); + player->setValue(keyPath.UTF8String, rlottie::Color(color.redComponent, color.greenComponent, color.blueComponent)); +} + +@end + + + + + +//vImageUnpremultiplyData_ARGB8888(&inputBuffer, &inputBuffer, kvImageNoFlags); +// CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); +//CVPixelBufferLockBaseAddress(pixelBuffer, 0); +//vImageBuffer_CopyToCVPixelBuffer(&inputBuffer, &format, pixelBuffer, NULL, NULL, kvImageNoFlags); +// var timebase:CMTimebase? = nil +// CMTimebaseCreateWithMasterClock( allocator: kCFAllocatorDefault, masterClock: CMClockGetHostTimeClock(), timebaseOut: &timebase ) +// CMTimebaseSetRate(timebase!, rate: 1.0) diff --git a/submodules/Signals b/submodules/Signals deleted file mode 160000 index f274dbca86..0000000000 --- a/submodules/Signals +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f274dbca868638d585994025e11e460c3726bd21 diff --git a/submodules/Sparkle b/submodules/Sparkle new file mode 160000 index 0000000000..f9e912289a --- /dev/null +++ b/submodules/Sparkle @@ -0,0 +1 @@ +Subproject commit f9e912289a5a15f5e9797e08d6cd875bb65c4a0c diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/project.pbxproj b/submodules/TGUIKit/TGUIKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..70b01e45d9 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/project.pbxproj @@ -0,0 +1,1277 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 9F1C279521D391BF003CD033 /* HorizontalScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1C279421D391BF003CD033 /* HorizontalScrollView.swift */; }; + 9F4EEF9A21DCD260002C3B33 /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4EEF9921DCD260002C3B33 /* SliderView.swift */; }; + 9F6281292037389200B2D3C9 /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6281282037389200B2D3C9 /* CheckBox.swift */; }; + 9F9B5EAF2255FEDA00728CDC /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9B5EAE2255FEDA00728CDC /* TooltipController.swift */; }; + 9FC76CAD20DBDDA30050CE3B /* SegmentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC76CAC20DBDDA30050CE3B /* SegmentController.swift */; }; + A789ECFE23EAE99100AEB34A /* ScrollableSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789ECFD23EAE99100AEB34A /* ScrollableSegmentView.swift */; }; + A789ED0023EAFDE600AEB34A /* MergeLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = A789ECFF23EAFDE600AEB34A /* MergeLists.swift */; }; + A7C41DB92359E69400CF9402 /* CatalinaStyledSegmentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C41DB82359E69400CF9402 /* CatalinaStyledSegmentController.swift */; }; + A7D28203236C3B5D0000A9BF /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D28202236C3B5D0000A9BF /* SwiftSignalKit.framework */; }; + A7D49478235F226C003E9A25 /* SpinningProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D49477235F226C003E9A25 /* SpinningProgressView.swift */; }; + C20232B61D845B0B007C9ADE /* TextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232B51D845B0B007C9ADE /* TextNode.swift */; }; + C20232B81D85525C007C9ADE /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20232B71D85525C007C9ADE /* ViewUtils.swift */; }; + C21178021F17E78E00AC706D /* TimableProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21178011F17E78E00AC706D /* TimableProgressView.swift */; }; + C2167E5D1DC2534300F98E03 /* SelectingControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E5C1DC2534300F98E03 /* SelectingControl.swift */; }; + C2167E611DC356FD00F98E03 /* LinearProgressControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */; }; + C219E1DD1D8978960042F0C8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C219E1DC1D8978960042F0C8 /* QuartzCore.framework */; }; + C219E1E01D8A71820042F0C8 /* SImageLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */; }; + C219E1E21D8A93050042F0C8 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1E11D8A93050042F0C8 /* TextView.swift */; }; + C219E1EB1D8AD1470042F0C8 /* NavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */; }; + C219E1ED1D8ADEE00042F0C8 /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */; }; + C219E1EF1D8AE3310042F0C8 /* AnimationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */; }; + C219E1F11D8AFD140042F0C8 /* AnimationBlockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */; }; + C219E1F31D8B02FA0042F0C8 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */; }; + C219E1F81D8C14930042F0C8 /* BarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F71D8C14930042F0C8 /* BarView.swift */; }; + C219E1FA1D8C316C0042F0C8 /* TitledBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */; }; + C21AAE381DB233F7007638C5 /* UIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE371DB233F7007638C5 /* UIUtils.swift */; }; + C21AAE3A1DB2398B007638C5 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */; }; + C21AAE3C1DB239ED007638C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C21AAE3B1DB239ED007638C5 /* Foundation.framework */; }; + C224A72B1EB7581500F43F3F /* MajorNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */; }; + C226741B1DBCD6E8000BA9ED /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226741A1DBCD6E8000BA9ED /* GridNode.swift */; }; + C226741D1DBCDBA8000BA9ED /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C226741C1DBCDBA8000BA9ED /* ContainableController.swift */; }; + C2271DA61DAD16B4001792B6 /* BadgeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DA51DAD16B4001792B6 /* BadgeNode.swift */; }; + C2271DC81DAEAAF9001792B6 /* SwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DC71DAEAAF9001792B6 /* SwitchView.swift */; }; + C2271DD41DAF766A001792B6 /* WeakReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DD31DAF766A001792B6 /* WeakReference.swift */; }; + C2271DED1DAFE4D0001792B6 /* TransformImageArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */; }; + C2271F331DB4CEB30045E719 /* HorizontalTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */; }; + C2271F361DB4CF270045E719 /* HorizontalRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F351DB4CF270045E719 /* HorizontalRowView.swift */; }; + C2271F431DB4EE2F0045E719 /* TableStickItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F421DB4EE2F0045E719 /* TableStickItem.swift */; }; + C2271F451DB4EE3C0045E719 /* TableStickView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2271F441DB4EE3C0045E719 /* TableStickView.swift */; }; + C22E062E1D7F3CC000A11C88 /* TGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E062D1D7F3CC000A11C88 /* TGColor.swift */; }; + C22E06351D804C8200A11C88 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06341D804C8200A11C88 /* TableView.swift */; }; + C22E06371D804D3B00A11C88 /* TableRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06361D804D3B00A11C88 /* TableRowItem.swift */; }; + C22E06391D804DCD00A11C88 /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E06381D804DCD00A11C88 /* ScrollView.swift */; }; + C22E063B1D8067A000A11C88 /* TableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22E063A1D8067A000A11C88 /* TableRowView.swift */; }; + C22E06451D80967E00A11C88 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C22E06441D80967E00A11C88 /* CoreText.framework */; }; + C2303E6F1D9950E000098E12 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E6E1D9950E000098E12 /* Button.swift */; }; + C2303E711D9956C400098E12 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E701D9956C400098E12 /* Style.swift */; }; + C2303E761D996E6300098E12 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E751D996E6300098E12 /* ImageButton.swift */; }; + C2303E7A1D9987FE00098E12 /* Popover.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E791D9987FE00098E12 /* Popover.swift */; }; + C2303E7C1D99880B00098E12 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E7B1D99880B00098E12 /* Modal.swift */; }; + C2303E7F1D99BEAE00098E12 /* OverlayControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */; }; + C2303E821D9A692B00098E12 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E811D9A692B00098E12 /* TabBarController.swift */; }; + C2303E841D9A69BD00098E12 /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E831D9A69BD00098E12 /* TabBarView.swift */; }; + C2303E861D9A6C2A00098E12 /* TabItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E851D9A6C2A00098E12 /* TabItem.swift */; }; + C2303E8C1D9A8B3000098E12 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2303E8B1D9A8B3000098E12 /* SearchView.swift */; }; + C230B9181DD3A6350057F596 /* ProgressModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C230B9171DD3A6350057F596 /* ProgressModal.swift */; }; + C232EA151E165AEF00C4D38C /* ImageBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C232EA141E165AEF00C4D38C /* ImageBarView.swift */; }; + C234CA8E1D97DF26003023F7 /* DrawingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA8B1D97DF26003023F7 /* DrawingContext.swift */; }; + C234CA8F1D97DF26003023F7 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA8C1D97DF26003023F7 /* Image.swift */; }; + C234CA971D981F00003023F7 /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = C234CA961D981F00003023F7 /* Control.swift */; }; + C2449A151F0E490C00DF5650 /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */; }; + C24A37161F324A36004ADE5C /* ShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24A37151F324A36004ADE5C /* ShadowView.swift */; }; + C24A371A1F338406004ADE5C /* SectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C24A37191F338406004ADE5C /* SectionViewController.swift */; }; + C253A9521D9147A400CDC850 /* Layer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9511D9147A400CDC850 /* Layer.swift */; }; + C253A9651D91B24100CDC850 /* TextViewLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9641D91B24100CDC850 /* TextViewLabel.swift */; }; + C253A9761D9430A900CDC850 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C253A9751D9430A900CDC850 /* ImageView.swift */; }; + C25FC7F71D86DC370041E303 /* TGClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25FC7F61D86DC370041E303 /* TGClipView.swift */; }; + C25FC7F91D86E53A0041E303 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C25FC7F81D86E53A0041E303 /* CoreVideo.framework */; }; + C26505901E02EBEE001954DC /* MagnifyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265058F1E02EBEE001954DC /* MagnifyView.swift */; }; + C28BAB0D1DF8550E0027CE3A /* WindowSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */; }; + C28CFC7D1F387A7F00C55596 /* TokenizedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */; }; + C291ED991DB35BC5008C6B2A /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291ED981DB35BC5008C6B2A /* Window.swift */; }; + C291ED9B1DB361ED008C6B2A /* KeyboardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */; }; + C296AF841D8D9D7D001DBB59 /* RadialProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */; }; + C29B5F3C1DC66DAC00D13E65 /* DraggingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */; }; + C29B5F471DC8CA2B00D13E65 /* NavigationModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */; }; + C29B5F491DC8CA3C00D13E65 /* NavigationModalAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */; }; + C2A71CEB1DDB382F00C69F73 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */; }; + C2B1A1181DA1673300ACB1DD /* TableAnimationInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */; }; + C2B1A11E1DA26BA400ACB1DD /* TransactionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */; }; + C2B1A1251DA2F3DC00ACB1DD /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */; }; + C2B1A1291DA3DDED00ACB1DD /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1281DA3DDED00ACB1DD /* Node.swift */; }; + C2B1A12C1DA50CC600ACB1DD /* TitleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */; }; + C2B1A12E1DA5148B00ACB1DD /* TextButtonBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */; }; + C2B1A1301DA53E7D00ACB1DD /* BackNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */; }; + C2B1A1321DA57E8300ACB1DD /* EditableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */; }; + C2B9BE861EFC373D00D6B96F /* PresentationTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */; }; + C2B9BE871EFC373D00D6B96F /* PresentationResourceCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */; }; + C2CBCABB1D80AD1F00142EC0 /* TGFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */; }; + C2CBCABD1D814D4B00142EC0 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCABC1D814D4B00142EC0 /* System.swift */; }; + C2CBCAC31D81595900142EC0 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2CBCAC21D81595900142EC0 /* Extensions.swift */; }; + C2E0DEAC1D7EF51C00EF1C8D /* TGUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C2E0DEB71D7EF58200EF1C8D /* TGSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */; }; + C2E0DEB91D7EF7F300EF1C8D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */; }; + C2E0DEBB1D7F05AB00EF1C8D /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E0DEBA1D7F05AB00EF1C8D /* View.swift */; }; + C2E8694F1F44481400BDD0A2 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E8694E1F44481400BDD0A2 /* GridItem.swift */; }; + C2E869511F4449A700BDD0A2 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E869501F4449A700BDD0A2 /* GridItemNode.swift */; }; + C2FD33E71E6952A8008D13D4 /* RadialProgressContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */; }; + D04D214C230ED78300609388 /* SPopoverRowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D214A230ED78300609388 /* SPopoverRowItem.swift */; }; + D04D214D230ED78300609388 /* SPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04D214B230ED78300609388 /* SPopoverViewController.swift */; }; + D050518E2342A75D00D93176 /* DisplayLinkAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050518D2342A75D00D93176 /* DisplayLinkAnimator.swift */; }; + D0558D8221528DF5006B403D /* ModalTouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0558D8121528DF5006B403D /* ModalTouchBar.swift */; }; + D05F392022FB37920040F341 /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F391F22FB37920040F341 /* DatePicker.swift */; }; + D0BEB9922163C1F00055B718 /* SelectionRectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB9912163C1F00055B718 /* SelectionRectView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 9F1C279421D391BF003CD033 /* HorizontalScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalScrollView.swift; sourceTree = ""; }; + 9F4EEF9921DCD260002C3B33 /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = ""; }; + 9F6281282037389200B2D3C9 /* CheckBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = ""; }; + 9F9B5EAE2255FEDA00728CDC /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; + 9FC76CAC20DBDDA30050CE3B /* SegmentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentController.swift; sourceTree = ""; }; + A789ECFD23EAE99100AEB34A /* ScrollableSegmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableSegmentView.swift; sourceTree = ""; }; + A789ECFF23EAFDE600AEB34A /* MergeLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergeLists.swift; sourceTree = ""; }; + A7C41DB82359E69400CF9402 /* CatalinaStyledSegmentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalinaStyledSegmentController.swift; sourceTree = ""; }; + A7D28202236C3B5D0000A9BF /* SwiftSignalKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A7D49477235F226C003E9A25 /* SpinningProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinningProgressView.swift; sourceTree = ""; }; + C20232B51D845B0B007C9ADE /* TextNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextNode.swift; sourceTree = ""; }; + C20232B71D85525C007C9ADE /* ViewUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewUtils.swift; sourceTree = ""; }; + C21178011F17E78E00AC706D /* TimableProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimableProgressView.swift; sourceTree = ""; }; + C2167E5C1DC2534300F98E03 /* SelectingControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectingControl.swift; sourceTree = ""; }; + C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearProgressControl.swift; sourceTree = ""; }; + C219E1D41D87F4A00042F0C8 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKitMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/SwiftSignalKitMac.framework"; sourceTree = ""; }; + C219E1DC1D8978960042F0C8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SImageLayer.swift; sourceTree = ""; }; + C219E1E11D8A93050042F0C8 /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; + C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationViewController.swift; sourceTree = ""; }; + C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; + C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationStyle.swift; sourceTree = ""; }; + C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationBlockDelegate.swift; sourceTree = ""; }; + C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; + C219E1F71D8C14930042F0C8 /* BarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarView.swift; sourceTree = ""; }; + C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitledBarView.swift; sourceTree = ""; }; + C21AAE371DB233F7007638C5 /* UIUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIUtils.swift; sourceTree = ""; }; + C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + C21AAE3B1DB239ED007638C5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MajorNavigationController.swift; sourceTree = ""; }; + C226741A1DBCD6E8000BA9ED /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; + C226741C1DBCDBA8000BA9ED /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; + C2271DA51DAD16B4001792B6 /* BadgeNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeNode.swift; sourceTree = ""; }; + C2271DC71DAEAAF9001792B6 /* SwitchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchView.swift; sourceTree = ""; }; + C2271DD31DAF766A001792B6 /* WeakReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakReference.swift; sourceTree = ""; }; + C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformImageArguments.swift; sourceTree = ""; }; + C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalTableView.swift; sourceTree = ""; }; + C2271F351DB4CF270045E719 /* HorizontalRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalRowView.swift; sourceTree = ""; }; + C2271F421DB4EE2F0045E719 /* TableStickItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableStickItem.swift; sourceTree = ""; }; + C2271F441DB4EE3C0045E719 /* TableStickView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableStickView.swift; sourceTree = ""; }; + C22E062D1D7F3CC000A11C88 /* TGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGColor.swift; sourceTree = ""; }; + C22E06341D804C8200A11C88 /* TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = ""; }; + C22E06361D804D3B00A11C88 /* TableRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowItem.swift; sourceTree = ""; }; + C22E06381D804DCD00A11C88 /* ScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = ""; }; + C22E063A1D8067A000A11C88 /* TableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowView.swift; sourceTree = ""; }; + C22E06441D80967E00A11C88 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; + C2303E6E1D9950E000098E12 /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + C2303E701D9956C400098E12 /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Style.swift; sourceTree = ""; }; + C2303E751D996E6300098E12 /* ImageButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageButton.swift; sourceTree = ""; }; + C2303E791D9987FE00098E12 /* Popover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Popover.swift; sourceTree = ""; }; + C2303E7B1D99880B00098E12 /* Modal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; + C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayControl.swift; sourceTree = ""; }; + C2303E811D9A692B00098E12 /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; + C2303E831D9A69BD00098E12 /* TabBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = ""; }; + C2303E851D9A6C2A00098E12 /* TabItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabItem.swift; sourceTree = ""; }; + C2303E8B1D9A8B3000098E12 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + C230B9171DD3A6350057F596 /* ProgressModal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressModal.swift; sourceTree = ""; }; + C232EA141E165AEF00C4D38C /* ImageBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageBarView.swift; sourceTree = ""; }; + C234CA881D97DEB6003023F7 /* TGLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TGLibrary.framework; path = ../TGLibrary/build/Debug/TGLibrary.framework; sourceTree = ""; }; + C234CA8B1D97DF26003023F7 /* DrawingContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawingContext.swift; sourceTree = ""; }; + C234CA8C1D97DF26003023F7 /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; + C234CA921D97E151003023F7 /* PostboxMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PostboxMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/PostboxMac.framework"; sourceTree = ""; }; + C234CA941D97E163003023F7 /* TelegramCoreMac.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TelegramCoreMac.framework; path = "../../../Library/Developer/Xcode/DerivedData/Telegram-Mac-hikohhgxyaqnbcboyjbphnuqswbk/Build/Products/Debug/TelegramCoreMac.framework"; sourceTree = ""; }; + C234CA961D981F00003023F7 /* Control.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = ""; }; + C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = ""; }; + C24A37151F324A36004ADE5C /* ShadowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowView.swift; sourceTree = ""; }; + C24A37191F338406004ADE5C /* SectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionViewController.swift; sourceTree = ""; }; + C253A9511D9147A400CDC850 /* Layer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layer.swift; sourceTree = ""; }; + C253A9641D91B24100CDC850 /* TextViewLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewLabel.swift; sourceTree = ""; }; + C253A9751D9430A900CDC850 /* ImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = ""; }; + C25FC7F61D86DC370041E303 /* TGClipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGClipView.swift; sourceTree = ""; }; + C25FC7F81D86E53A0041E303 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; + C265058F1E02EBEE001954DC /* MagnifyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagnifyView.swift; sourceTree = ""; }; + C27624881D95AD7300FE5B2B /* ObjcExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjcExtension.framework; path = ../ObjcExtension/build/Debug/ObjcExtension.framework; sourceTree = ""; }; + C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowSaver.swift; sourceTree = ""; }; + C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizedView.swift; sourceTree = ""; }; + C291ED981DB35BC5008C6B2A /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; + C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardUtils.swift; sourceTree = ""; }; + C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadialProgressView.swift; sourceTree = ""; }; + C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DraggingView.swift; sourceTree = ""; }; + C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModalView.swift; sourceTree = ""; }; + C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModalAction.swift; sourceTree = ""; }; + C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; + C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableAnimationInterface.swift; sourceTree = ""; }; + C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHandler.swift; sourceTree = ""; }; + C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; + C2B1A1281DA3DDED00ACB1DD /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; + C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleButton.swift; sourceTree = ""; }; + C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextButtonBarView.swift; sourceTree = ""; }; + C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackNavigationBar.swift; sourceTree = ""; }; + C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditableViewController.swift; sourceTree = ""; }; + C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationTheme.swift; sourceTree = ""; }; + C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationResourceCache.swift; sourceTree = ""; }; + C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGFont.swift; sourceTree = ""; }; + C2CBCABC1D814D4B00142EC0 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; + C2CBCAC21D81595900142EC0 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TGUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TGUIKit.h; sourceTree = ""; }; + C2E0DEAB1D7EF51C00EF1C8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TGSplitView.swift; sourceTree = ""; }; + C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C2E0DEBA1D7F05AB00EF1C8D /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; + C2E8694E1F44481400BDD0A2 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; + C2E869501F4449A700BDD0A2 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; + C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadialProgressContainerView.swift; sourceTree = ""; }; + D04D214A230ED78300609388 /* SPopoverRowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPopoverRowItem.swift; sourceTree = ""; }; + D04D214B230ED78300609388 /* SPopoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPopoverViewController.swift; sourceTree = ""; }; + D050518D2342A75D00D93176 /* DisplayLinkAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayLinkAnimator.swift; sourceTree = ""; }; + D0558D8121528DF5006B403D /* ModalTouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalTouchBar.swift; sourceTree = ""; }; + D05F391F22FB37920040F341 /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; + D0BEB9912163C1F00055B718 /* SelectionRectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionRectView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C2E0DEA31D7EF51C00EF1C8D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A7D28203236C3B5D0000A9BF /* SwiftSignalKit.framework in Frameworks */, + C21AAE3C1DB239ED007638C5 /* Foundation.framework in Frameworks */, + C219E1DD1D8978960042F0C8 /* QuartzCore.framework in Frameworks */, + C25FC7F91D86E53A0041E303 /* CoreVideo.framework in Frameworks */, + C22E06451D80967E00A11C88 /* CoreText.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9FC76CAB20DBDD370050CE3B /* segment */ = { + isa = PBXGroup; + children = ( + A7C41DB72359E66800CF9402 /* catalina */, + 9FC76CAC20DBDDA30050CE3B /* SegmentController.swift */, + ); + name = segment; + sourceTree = ""; + }; + A789ECFC23EAE97800AEB34A /* scrollable-segment */ = { + isa = PBXGroup; + children = ( + A789ECFD23EAE99100AEB34A /* ScrollableSegmentView.swift */, + ); + name = "scrollable-segment"; + sourceTree = ""; + }; + A7C41DB72359E66800CF9402 /* catalina */ = { + isa = PBXGroup; + children = ( + A7C41DB82359E69400CF9402 /* CatalinaStyledSegmentController.swift */, + ); + name = catalina; + sourceTree = ""; + }; + C219E1DE1D8A71680042F0C8 /* layers */ = { + isa = PBXGroup; + children = ( + C219E1DF1D8A71820042F0C8 /* SImageLayer.swift */, + C253A9511D9147A400CDC850 /* Layer.swift */, + ); + name = layers; + sourceTree = ""; + }; + C219E1E91D8AD1330042F0C8 /* navigation */ = { + isa = PBXGroup; + children = ( + D0558D8021528DA0006B403D /* touchbar */, + C24A37181F3383DF004ADE5C /* SectionController */, + C219E1F41D8BEA1F0042F0C8 /* bar */, + C219E1EA1D8AD1470042F0C8 /* NavigationViewController.swift */, + C224A72A1EB7581500F43F3F /* MajorNavigationController.swift */, + C2E0DEB81D7EF7F300EF1C8D /* ViewController.swift */, + C2B1A1311DA57E8300ACB1DD /* EditableViewController.swift */, + C2A71CEA1DDB382F00C69F73 /* TableViewController.swift */, + ); + name = navigation; + sourceTree = ""; + }; + C219E1F41D8BEA1F0042F0C8 /* bar */ = { + isa = PBXGroup; + children = ( + C219E1EC1D8ADEE00042F0C8 /* NavigationBarView.swift */, + C219E1F71D8C14930042F0C8 /* BarView.swift */, + C219E1F91D8C316C0042F0C8 /* TitledBarView.swift */, + C2B1A12D1DA5148B00ACB1DD /* TextButtonBarView.swift */, + C2B1A12F1DA53E7D00ACB1DD /* BackNavigationBar.swift */, + C232EA141E165AEF00C4D38C /* ImageBarView.swift */, + ); + name = bar; + sourceTree = ""; + }; + C22674191DBCD6CA000BA9ED /* gridnode */ = { + isa = PBXGroup; + children = ( + C226741A1DBCD6E8000BA9ED /* GridNode.swift */, + C2E8694E1F44481400BDD0A2 /* GridItem.swift */, + C2E869501F4449A700BDD0A2 /* GridItemNode.swift */, + ); + name = gridnode; + sourceTree = ""; + }; + C2271DA71DAD1728001792B6 /* nodes */ = { + isa = PBXGroup; + children = ( + C2271DA51DAD16B4001792B6 /* BadgeNode.swift */, + ); + name = nodes; + sourceTree = ""; + }; + C2271F341DB4CF040045E719 /* horizontal */ = { + isa = PBXGroup; + children = ( + C2271F321DB4CEB30045E719 /* HorizontalTableView.swift */, + C2271F351DB4CF270045E719 /* HorizontalRowView.swift */, + 9F1C279421D391BF003CD033 /* HorizontalScrollView.swift */, + ); + name = horizontal; + sourceTree = ""; + }; + C2271F411DB4EE170045E719 /* stick */ = { + isa = PBXGroup; + children = ( + C2271F421DB4EE2F0045E719 /* TableStickItem.swift */, + C2271F441DB4EE3C0045E719 /* TableStickView.swift */, + ); + name = stick; + sourceTree = ""; + }; + C22E062A1D7F3C2700A11C88 /* utils */ = { + isa = PBXGroup; + children = ( + A789ECFF23EAFDE600AEB34A /* MergeLists.swift */, + C2271DEC1DAFE4D0001792B6 /* TransformImageArguments.swift */, + C2CBCAC11D81593B00142EC0 /* extensions */, + C22E062D1D7F3CC000A11C88 /* TGColor.swift */, + C2CBCABA1D80AD1F00142EC0 /* TGFont.swift */, + C2CBCABC1D814D4B00142EC0 /* System.swift */, + C219E1EE1D8AE3310042F0C8 /* AnimationStyle.swift */, + C219E1F01D8AFD140042F0C8 /* AnimationBlockDelegate.swift */, + C219E1F21D8B02FA0042F0C8 /* CAAnimationUtils.swift */, + C2303E701D9956C400098E12 /* Style.swift */, + C2B1A11D1DA26BA400ACB1DD /* TransactionHandler.swift */, + C2271DD31DAF766A001792B6 /* WeakReference.swift */, + C21AAE371DB233F7007638C5 /* UIUtils.swift */, + C21AAE391DB2398B007638C5 /* DisplayLinkDispatcher.swift */, + C291ED9A1DB361ED008C6B2A /* KeyboardUtils.swift */, + C226741C1DBCDBA8000BA9ED /* ContainableController.swift */, + C28BAB0C1DF8550E0027CE3A /* WindowSaver.swift */, + D050518D2342A75D00D93176 /* DisplayLinkAnimator.swift */, + ); + name = utils; + sourceTree = ""; + }; + C22E06331D804C5800A11C88 /* table */ = { + isa = PBXGroup; + children = ( + C22674191DBCD6CA000BA9ED /* gridnode */, + C2271F411DB4EE170045E719 /* stick */, + C2271F341DB4CF040045E719 /* horizontal */, + C22E06341D804C8200A11C88 /* TableView.swift */, + C22E06361D804D3B00A11C88 /* TableRowItem.swift */, + C22E063A1D8067A000A11C88 /* TableRowView.swift */, + C2B1A1171DA1673200ACB1DD /* TableAnimationInterface.swift */, + ); + name = table; + sourceTree = ""; + }; + C22E06431D80967E00A11C88 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7D28202236C3B5D0000A9BF /* SwiftSignalKit.framework */, + C21AAE3B1DB239ED007638C5 /* Foundation.framework */, + C234CA941D97E163003023F7 /* TelegramCoreMac.framework */, + C234CA921D97E151003023F7 /* PostboxMac.framework */, + C234CA881D97DEB6003023F7 /* TGLibrary.framework */, + C27624881D95AD7300FE5B2B /* ObjcExtension.framework */, + C219E1DC1D8978960042F0C8 /* QuartzCore.framework */, + C219E1D41D87F4A00042F0C8 /* SwiftSignalKitMac.framework */, + C25FC7F81D86E53A0041E303 /* CoreVideo.framework */, + C22E06441D80967E00A11C88 /* CoreText.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C2303E7D1D99881500098E12 /* overlay */ = { + isa = PBXGroup; + children = ( + D04D214A230ED78300609388 /* SPopoverRowItem.swift */, + D04D214B230ED78300609388 /* SPopoverViewController.swift */, + C2B1A1241DA2F3DC00ACB1DD /* ContextMenu.swift */, + C2303E791D9987FE00098E12 /* Popover.swift */, + C2303E7B1D99880B00098E12 /* Modal.swift */, + C29B5F461DC8CA2B00D13E65 /* NavigationModalView.swift */, + C29B5F481DC8CA3C00D13E65 /* NavigationModalAction.swift */, + C230B9171DD3A6350057F596 /* ProgressModal.swift */, + 9F9B5EAE2255FEDA00728CDC /* TooltipController.swift */, + ); + name = overlay; + sourceTree = ""; + }; + C2303E801D9A691300098E12 /* tabbar */ = { + isa = PBXGroup; + children = ( + C2303E811D9A692B00098E12 /* TabBarController.swift */, + C2303E831D9A69BD00098E12 /* TabBarView.swift */, + C2303E851D9A6C2A00098E12 /* TabItem.swift */, + ); + name = tabbar; + sourceTree = ""; + }; + C2449A0F1F0E475200DF5650 /* thrid-party */ = { + isa = PBXGroup; + children = ( + ); + name = "thrid-party"; + sourceTree = ""; + }; + C24A37181F3383DF004ADE5C /* SectionController */ = { + isa = PBXGroup; + children = ( + C24A37191F338406004ADE5C /* SectionViewController.swift */, + ); + name = SectionController; + sourceTree = ""; + }; + C2B9BE831EFC373200D6B96F /* presentation */ = { + isa = PBXGroup; + children = ( + C2B9BE841EFC373D00D6B96F /* PresentationTheme.swift */, + C2B9BE851EFC373D00D6B96F /* PresentationResourceCache.swift */, + ); + name = presentation; + sourceTree = ""; + }; + C2CBCAC11D81593B00142EC0 /* extensions */ = { + isa = PBXGroup; + children = ( + C234CA8B1D97DF26003023F7 /* DrawingContext.swift */, + C234CA8C1D97DF26003023F7 /* Image.swift */, + C2CBCAC21D81595900142EC0 /* Extensions.swift */, + ); + name = extensions; + sourceTree = ""; + }; + C2E0DE9D1D7EF51C00EF1C8D = { + isa = PBXGroup; + children = ( + C2E0DEA91D7EF51C00EF1C8D /* TGUIKit */, + C2E0DEA81D7EF51C00EF1C8D /* Products */, + C22E06431D80967E00A11C88 /* Frameworks */, + ); + sourceTree = ""; + }; + C2E0DEA81D7EF51C00EF1C8D /* Products */ = { + isa = PBXGroup; + children = ( + C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + C2E0DEA91D7EF51C00EF1C8D /* TGUIKit */ = { + isa = PBXGroup; + children = ( + A789ECFC23EAE97800AEB34A /* scrollable-segment */, + D05F391E22FB377E0040F341 /* date */, + 9FC76CAB20DBDD370050CE3B /* segment */, + C2449A0F1F0E475200DF5650 /* thrid-party */, + C2B9BE831EFC373200D6B96F /* presentation */, + C2271DA71DAD1728001792B6 /* nodes */, + C2303E801D9A691300098E12 /* tabbar */, + C2303E7D1D99881500098E12 /* overlay */, + C219E1E91D8AD1330042F0C8 /* navigation */, + C22E06331D804C5800A11C88 /* table */, + C22E062A1D7F3C2700A11C88 /* utils */, + C2E0DEBC1D7F071300EF1C8D /* view */, + C2E0DEB41D7EF53900EF1C8D /* split-view */, + C2E0DEAA1D7EF51C00EF1C8D /* TGUIKit.h */, + C2E0DEAB1D7EF51C00EF1C8D /* Info.plist */, + ); + path = TGUIKit; + sourceTree = ""; + }; + C2E0DEB41D7EF53900EF1C8D /* split-view */ = { + isa = PBXGroup; + children = ( + C2E0DEB61D7EF58200EF1C8D /* TGSplitView.swift */, + ); + name = "split-view"; + sourceTree = ""; + }; + C2E0DEBC1D7F071300EF1C8D /* view */ = { + isa = PBXGroup; + children = ( + C219E1DE1D8A71680042F0C8 /* layers */, + C296AF831D8D9D7D001DBB59 /* RadialProgressView.swift */, + C219E1E11D8A93050042F0C8 /* TextView.swift */, + C2E0DEBA1D7F05AB00EF1C8D /* View.swift */, + C22E06381D804DCD00A11C88 /* ScrollView.swift */, + C20232B51D845B0B007C9ADE /* TextNode.swift */, + C20232B71D85525C007C9ADE /* ViewUtils.swift */, + C25FC7F61D86DC370041E303 /* TGClipView.swift */, + C253A9641D91B24100CDC850 /* TextViewLabel.swift */, + C253A9751D9430A900CDC850 /* ImageView.swift */, + C234CA961D981F00003023F7 /* Control.swift */, + C2303E6E1D9950E000098E12 /* Button.swift */, + C2303E751D996E6300098E12 /* ImageButton.swift */, + C2303E7E1D99BEAE00098E12 /* OverlayControl.swift */, + C2303E8B1D9A8B3000098E12 /* SearchView.swift */, + C2B1A1281DA3DDED00ACB1DD /* Node.swift */, + C2B1A12B1DA50CC600ACB1DD /* TitleButton.swift */, + C2271DC71DAEAAF9001792B6 /* SwitchView.swift */, + C291ED981DB35BC5008C6B2A /* Window.swift */, + C2167E601DC356FD00F98E03 /* LinearProgressControl.swift */, + C2167E5C1DC2534300F98E03 /* SelectingControl.swift */, + C29B5F3B1DC66DAC00D13E65 /* DraggingView.swift */, + C265058F1E02EBEE001954DC /* MagnifyView.swift */, + C2FD33E61E6952A8008D13D4 /* RadialProgressContainerView.swift */, + C2449A141F0E490C00DF5650 /* ProgressIndicator.swift */, + C21178011F17E78E00AC706D /* TimableProgressView.swift */, + C24A37151F324A36004ADE5C /* ShadowView.swift */, + C28CFC7C1F387A7F00C55596 /* TokenizedView.swift */, + 9F6281282037389200B2D3C9 /* CheckBox.swift */, + D0BEB9912163C1F00055B718 /* SelectionRectView.swift */, + 9F4EEF9921DCD260002C3B33 /* SliderView.swift */, + A7D49477235F226C003E9A25 /* SpinningProgressView.swift */, + ); + name = view; + sourceTree = ""; + }; + D0558D8021528DA0006B403D /* touchbar */ = { + isa = PBXGroup; + children = ( + D0558D8121528DF5006B403D /* ModalTouchBar.swift */, + ); + name = touchbar; + sourceTree = ""; + }; + D05F391E22FB377E0040F341 /* date */ = { + isa = PBXGroup; + children = ( + D05F391F22FB37920040F341 /* DatePicker.swift */, + ); + name = date; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + C2E0DEA41D7EF51C00EF1C8D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C2E0DEAC1D7EF51C00EF1C8D /* TGUIKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C2E0DEA61D7EF51C00EF1C8D /* TGUIKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = C2E0DEAF1D7EF51C00EF1C8D /* Build configuration list for PBXNativeTarget "TGUIKit" */; + buildPhases = ( + C2E0DEA21D7EF51C00EF1C8D /* Sources */, + C2E0DEA31D7EF51C00EF1C8D /* Frameworks */, + C2E0DEA41D7EF51C00EF1C8D /* Headers */, + C2E0DEA51D7EF51C00EF1C8D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TGUIKit; + productName = TGUIKit; + productReference = C2E0DEA71D7EF51C00EF1C8D /* TGUIKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C2E0DE9E1D7EF51C00EF1C8D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + C2E0DEA61D7EF51C00EF1C8D = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6N38VWS5BX; + LastSwiftMigration = 0830; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = C2E0DEA11D7EF51C00EF1C8D /* Build configuration list for PBXProject "TGUIKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = C2E0DE9D1D7EF51C00EF1C8D; + productRefGroup = C2E0DEA81D7EF51C00EF1C8D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C2E0DEA61D7EF51C00EF1C8D /* TGUIKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C2E0DEA51D7EF51C00EF1C8D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C2E0DEA21D7EF51C00EF1C8D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C219E1F81D8C14930042F0C8 /* BarView.swift in Sources */, + C2303E861D9A6C2A00098E12 /* TabItem.swift in Sources */, + C219E1EF1D8AE3310042F0C8 /* AnimationStyle.swift in Sources */, + A7D49478235F226C003E9A25 /* SpinningProgressView.swift in Sources */, + C2271DA61DAD16B4001792B6 /* BadgeNode.swift in Sources */, + C234CA8F1D97DF26003023F7 /* Image.swift in Sources */, + C2B1A1181DA1673300ACB1DD /* TableAnimationInterface.swift in Sources */, + C2271F331DB4CEB30045E719 /* HorizontalTableView.swift in Sources */, + A789ED0023EAFDE600AEB34A /* MergeLists.swift in Sources */, + C2E0DEBB1D7F05AB00EF1C8D /* View.swift in Sources */, + C2271DD41DAF766A001792B6 /* WeakReference.swift in Sources */, + C22E06351D804C8200A11C88 /* TableView.swift in Sources */, + C232EA151E165AEF00C4D38C /* ImageBarView.swift in Sources */, + C2167E611DC356FD00F98E03 /* LinearProgressControl.swift in Sources */, + C22E062E1D7F3CC000A11C88 /* TGColor.swift in Sources */, + C2271DED1DAFE4D0001792B6 /* TransformImageArguments.swift in Sources */, + C2E0DEB71D7EF58200EF1C8D /* TGSplitView.swift in Sources */, + C2B1A1291DA3DDED00ACB1DD /* Node.swift in Sources */, + C2B9BE861EFC373D00D6B96F /* PresentationTheme.swift in Sources */, + C234CA8E1D97DF26003023F7 /* DrawingContext.swift in Sources */, + C219E1E01D8A71820042F0C8 /* SImageLayer.swift in Sources */, + C28CFC7D1F387A7F00C55596 /* TokenizedView.swift in Sources */, + C2B1A11E1DA26BA400ACB1DD /* TransactionHandler.swift in Sources */, + C2303E8C1D9A8B3000098E12 /* SearchView.swift in Sources */, + C234CA971D981F00003023F7 /* Control.swift in Sources */, + C2303E6F1D9950E000098E12 /* Button.swift in Sources */, + D0BEB9922163C1F00055B718 /* SelectionRectView.swift in Sources */, + 9FC76CAD20DBDDA30050CE3B /* SegmentController.swift in Sources */, + C21AAE381DB233F7007638C5 /* UIUtils.swift in Sources */, + C20232B61D845B0B007C9ADE /* TextNode.swift in Sources */, + C2FD33E71E6952A8008D13D4 /* RadialProgressContainerView.swift in Sources */, + C219E1F31D8B02FA0042F0C8 /* CAAnimationUtils.swift in Sources */, + C291ED991DB35BC5008C6B2A /* Window.swift in Sources */, + C296AF841D8D9D7D001DBB59 /* RadialProgressView.swift in Sources */, + C24A371A1F338406004ADE5C /* SectionViewController.swift in Sources */, + C219E1EB1D8AD1470042F0C8 /* NavigationViewController.swift in Sources */, + C253A9761D9430A900CDC850 /* ImageView.swift in Sources */, + C24A37161F324A36004ADE5C /* ShadowView.swift in Sources */, + C2B1A1321DA57E8300ACB1DD /* EditableViewController.swift in Sources */, + C2167E5D1DC2534300F98E03 /* SelectingControl.swift in Sources */, + 9F4EEF9A21DCD260002C3B33 /* SliderView.swift in Sources */, + C230B9181DD3A6350057F596 /* ProgressModal.swift in Sources */, + C21178021F17E78E00AC706D /* TimableProgressView.swift in Sources */, + C2E0DEB91D7EF7F300EF1C8D /* ViewController.swift in Sources */, + A789ECFE23EAE99100AEB34A /* ScrollableSegmentView.swift in Sources */, + C2303E841D9A69BD00098E12 /* TabBarView.swift in Sources */, + C22E063B1D8067A000A11C88 /* TableRowView.swift in Sources */, + C2303E7A1D9987FE00098E12 /* Popover.swift in Sources */, + 9F9B5EAF2255FEDA00728CDC /* TooltipController.swift in Sources */, + C29B5F3C1DC66DAC00D13E65 /* DraggingView.swift in Sources */, + 9F6281292037389200B2D3C9 /* CheckBox.swift in Sources */, + C226741D1DBCDBA8000BA9ED /* ContainableController.swift in Sources */, + C253A9651D91B24100CDC850 /* TextViewLabel.swift in Sources */, + D050518E2342A75D00D93176 /* DisplayLinkAnimator.swift in Sources */, + C219E1FA1D8C316C0042F0C8 /* TitledBarView.swift in Sources */, + C28BAB0D1DF8550E0027CE3A /* WindowSaver.swift in Sources */, + C2303E7C1D99880B00098E12 /* Modal.swift in Sources */, + C219E1E21D8A93050042F0C8 /* TextView.swift in Sources */, + C2449A151F0E490C00DF5650 /* ProgressIndicator.swift in Sources */, + C21AAE3A1DB2398B007638C5 /* DisplayLinkDispatcher.swift in Sources */, + C2271F431DB4EE2F0045E719 /* TableStickItem.swift in Sources */, + C2E8694F1F44481400BDD0A2 /* GridItem.swift in Sources */, + C2B9BE871EFC373D00D6B96F /* PresentationResourceCache.swift in Sources */, + C2271F451DB4EE3C0045E719 /* TableStickView.swift in Sources */, + D0558D8221528DF5006B403D /* ModalTouchBar.swift in Sources */, + C2E869511F4449A700BDD0A2 /* GridItemNode.swift in Sources */, + C25FC7F71D86DC370041E303 /* TGClipView.swift in Sources */, + C2CBCAC31D81595900142EC0 /* Extensions.swift in Sources */, + C219E1ED1D8ADEE00042F0C8 /* NavigationBarView.swift in Sources */, + C2303E821D9A692B00098E12 /* TabBarController.swift in Sources */, + D04D214C230ED78300609388 /* SPopoverRowItem.swift in Sources */, + C2CBCABB1D80AD1F00142EC0 /* TGFont.swift in Sources */, + C2B1A12E1DA5148B00ACB1DD /* TextButtonBarView.swift in Sources */, + C20232B81D85525C007C9ADE /* ViewUtils.swift in Sources */, + C2271F361DB4CF270045E719 /* HorizontalRowView.swift in Sources */, + 9F1C279521D391BF003CD033 /* HorizontalScrollView.swift in Sources */, + A7C41DB92359E69400CF9402 /* CatalinaStyledSegmentController.swift in Sources */, + D04D214D230ED78300609388 /* SPopoverViewController.swift in Sources */, + C22E06391D804DCD00A11C88 /* ScrollView.swift in Sources */, + C219E1F11D8AFD140042F0C8 /* AnimationBlockDelegate.swift in Sources */, + C253A9521D9147A400CDC850 /* Layer.swift in Sources */, + C22E06371D804D3B00A11C88 /* TableRowItem.swift in Sources */, + C2303E711D9956C400098E12 /* Style.swift in Sources */, + C2271DC81DAEAAF9001792B6 /* SwitchView.swift in Sources */, + C2B1A1251DA2F3DC00ACB1DD /* ContextMenu.swift in Sources */, + C224A72B1EB7581500F43F3F /* MajorNavigationController.swift in Sources */, + C2B1A1301DA53E7D00ACB1DD /* BackNavigationBar.swift in Sources */, + D05F392022FB37920040F341 /* DatePicker.swift in Sources */, + C2303E761D996E6300098E12 /* ImageButton.swift in Sources */, + C226741B1DBCD6E8000BA9ED /* GridNode.swift in Sources */, + C2CBCABD1D814D4B00142EC0 /* System.swift in Sources */, + C2303E7F1D99BEAE00098E12 /* OverlayControl.swift in Sources */, + C29B5F471DC8CA2B00D13E65 /* NavigationModalView.swift in Sources */, + C26505901E02EBEE001954DC /* MagnifyView.swift in Sources */, + C2B1A12C1DA50CC600ACB1DD /* TitleButton.swift in Sources */, + C291ED9B1DB361ED008C6B2A /* KeyboardUtils.swift in Sources */, + C29B5F491DC8CA3C00D13E65 /* NavigationModalAction.swift in Sources */, + C2A71CEB1DDB382F00C69F73 /* TableViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A7F282EB238EAB8000742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Github; + }; + A7F282EC238EAB8000742C20 /* Github */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Github; + }; + C22069D21E8EB4D700E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + 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 = gnu99; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyapp; + }; + C22069D31E8EB4D700E82730 /* ReleaseHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + }; + name = ReleaseHockeyapp; + }; + C232EA391E1BCFDE00C4D38C /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + 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 = gnu99; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStore; + }; + C232EA3A1E1BCFDE00C4D38C /* ReleaseAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + }; + name = ReleaseAppStore; + }; + C2E0DEAD1D7EF51C00EF1C8D /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + C2E0DEAE1D7EF51C00EF1C8D /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStore; + }; + C2E0DEB01D7EF51C00EF1C8D /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = DebugHockeyapp; + }; + C2E0DEB11D7EF51C00EF1C8D /* DebugAppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = singlefile; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = DebugAppStore; + }; + D01E1ED822B260DD00AD6DAE /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = HockeyappMacAlpha; + }; + D01E1ED922B260DD00AD6DAE /* HockeyappMacAlpha */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6N38VWS5BX; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_VERSION = A; + GCC_OPTIMIZATION_LEVEL = s; + INFOPLIST_FILE = TGUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TGUIKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = HockeyappMacAlpha; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C2E0DEA11D7EF51C00EF1C8D /* Build configuration list for PBXProject "TGUIKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2E0DEAD1D7EF51C00EF1C8D /* DebugHockeyapp */, + D01E1ED822B260DD00AD6DAE /* HockeyappMacAlpha */, + C2E0DEAE1D7EF51C00EF1C8D /* DebugAppStore */, + A7F282EB238EAB8000742C20 /* Github */, + C22069D21E8EB4D700E82730 /* ReleaseHockeyapp */, + C232EA391E1BCFDE00C4D38C /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugAppStore; + }; + C2E0DEAF1D7EF51C00EF1C8D /* Build configuration list for PBXNativeTarget "TGUIKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C2E0DEB01D7EF51C00EF1C8D /* DebugHockeyapp */, + D01E1ED922B260DD00AD6DAE /* HockeyappMacAlpha */, + C2E0DEB11D7EF51C00EF1C8D /* DebugAppStore */, + A7F282EC238EAB8000742C20 /* Github */, + C22069D31E8EB4D700E82730 /* ReleaseHockeyapp */, + C232EA3A1E1BCFDE00C4D38C /* ReleaseAppStore */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugAppStore; + }; +/* End XCConfigurationList section */ + }; + rootObject = C2E0DE9E1D7EF51C00EF1C8D /* Project object */; +} diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..98bcdfad93 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/keepcoder.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,24 @@ + + + + + SchemeUserState + + TGUIKit.xcscheme_^#shared#^_ + + isShown + + orderHint + 29 + + + SuppressBuildableAutocreation + + C2E0DEA61D7EF51C00EF1C8D + + primary + + + + + diff --git a/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/TGUIKit.xcscheme b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/TGUIKit.xcscheme similarity index 100% rename from TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/TGUIKit.xcscheme rename to submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/TGUIKit.xcscheme diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100755 index 0000000000..0572fe6ea9 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/mikhailfilimonov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,29 @@ + + + + + SchemeUserState + + TGUIKit.xcscheme + + isShown + + orderHint + 0 + + TGUIKit.xcscheme_^#shared#^_ + + orderHint + 25 + + + SuppressBuildableAutocreation + + C2E0DEA61D7EF51C00EF1C8D + + primary + + + + + diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TGUIKit.xcscheme b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TGUIKit.xcscheme new file mode 100644 index 0000000000..fe24613d0d --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/TGUIKit.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..c906fcf309 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/overtake.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,16 @@ + + + + + SchemeUserState + + TGUIKit.xcscheme + + isShown + + orderHint + 18 + + + + diff --git a/TGUIKit/TGUIKit.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 100% rename from TGUIKit/TGUIKit.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist rename to submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TGUIKit.xcscheme b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TGUIKit.xcscheme new file mode 100644 index 0000000000..8886764abe --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/TGUIKit.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..0db52b36c7 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit.xcodeproj/xcuserdata/telegram.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,29 @@ + + + + + SchemeUserState + + TGUIKit.xcscheme + + isShown + + orderHint + 2 + + TGUIKit.xcscheme_^#shared#^_ + + orderHint + 28 + + + SuppressBuildableAutocreation + + C2E0DEA61D7EF51C00EF1C8D + + primary + + + + + diff --git a/TGUIKit/TGUIKit/AnimationBlockDelegate.swift b/submodules/TGUIKit/TGUIKit/AnimationBlockDelegate.swift similarity index 100% rename from TGUIKit/TGUIKit/AnimationBlockDelegate.swift rename to submodules/TGUIKit/TGUIKit/AnimationBlockDelegate.swift diff --git a/submodules/TGUIKit/TGUIKit/AnimationStyle.swift b/submodules/TGUIKit/TGUIKit/AnimationStyle.swift new file mode 100644 index 0000000000..d489b21dca --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/AnimationStyle.swift @@ -0,0 +1,19 @@ +// +// AnimationStyle.swift +// TGUIKit +// +// Created by keepcoder on 15/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public struct AnimationStyle { + + public let duration:CFTimeInterval + public let function:CAMediaTimingFunctionName + public init(duration: CFTimeInterval, function: CAMediaTimingFunctionName) { + self.duration = duration + self.function = function + } +} diff --git a/submodules/TGUIKit/TGUIKit/BackNavigationBar.swift b/submodules/TGUIKit/TGUIKit/BackNavigationBar.swift new file mode 100644 index 0000000000..8caff79d00 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/BackNavigationBar.swift @@ -0,0 +1,61 @@ +// +// BackNavigationBar.swift +// TGUIKit +// +// Created by keepcoder on 05/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +open class BackNavigationBar: TextButtonBarView { + + + public init(_ controller:ViewController, canBeEmpty: Bool = false) { + let backSettings = controller.backSettings() + super.init(controller: controller, text: backSettings.0, style: navigationButtonStyle, alignment: .Left, canBeEmpty: canBeEmpty) + + if let image = backSettings.1 { + set(image: image, for: .Normal) + } + set(handler: { [weak self] _ in + self?.controller?.executeReturn() + }, for: .Up) + + requestUpdate() + } + + override var isFitted: Bool { + return super.isFitted + } + + public func requestUpdate() { + let backSettings = controller?.backSettings() ?? ("",nil) + set(text: backSettings.0, for: .Normal) + if let image = backSettings.1 { + set(image: image, for: .Normal) + } else { + removeImage(for: .Normal) + } + style = navigationButtonStyle + needsLayout = true + } + + open override func layout() { + super.layout() + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} diff --git a/TGUIKit/TGUIKit/BadgeNode.swift b/submodules/TGUIKit/TGUIKit/BadgeNode.swift similarity index 84% rename from TGUIKit/TGUIKit/BadgeNode.swift rename to submodules/TGUIKit/TGUIKit/BadgeNode.swift index e4d25768d6..cd892039e4 100644 --- a/TGUIKit/TGUIKit/BadgeNode.swift +++ b/submodules/TGUIKit/TGUIKit/BadgeNode.swift @@ -32,18 +32,20 @@ public class BadgeNode: Node { ctx.fill(layer.bounds) let focus = view.focus(textLayout.0.size) - textLayout.1.draw(focus, in: ctx, backingScaleFactor: view.backingScaleFactor) + textLayout.1.draw(focus, in: ctx, backingScaleFactor: view.backingScaleFactor, backgroundColor: view.backgroundColor) } } + public var additionSize: NSSize = NSMakeSize(8, 7) + public init(_ attributedString:NSAttributedString, _ fillColor:NSColor) { textLayout = TextNode.layoutText(maybeNode: nil, attributedString, nil, 1, .middle, NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude), nil, false, .left) self.fillColor = fillColor super.init() - size = NSMakeSize(textLayout.0.size.width + 8, textLayout.0.size.height + 7) + size = NSMakeSize(textLayout.0.size.width + additionSize.width, textLayout.0.size.height + additionSize.height) size = NSMakeSize(max(size.height,size.width), size.height) } diff --git a/submodules/TGUIKit/TGUIKit/BarView.swift b/submodules/TGUIKit/TGUIKit/BarView.swift new file mode 100644 index 0000000000..a68105360c --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/BarView.swift @@ -0,0 +1,78 @@ +// +// BarView.swift +// TGUIKit +// +// Created by keepcoder on 16/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +open class BarView: Control { + + + public var clickHandler:()->Void = {} + + public var minWidth:CGFloat = 20 + public private(set) weak var controller: ViewController? + + + override open func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + } + + + var isFitted: Bool { + return true + } + + func fit(to maxWidth: CGFloat) -> CGFloat { + return frame.width + } + + open override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + self.setNeedsDisplay() + } + + public init(_ width:CGFloat = 20, controller: ViewController) { + self.minWidth = width + self.controller = controller + super.init(frame: NSMakeRect(0, 0, minWidth, 50)) + animates = false + overlayInitEvent() + } + + open override var isHidden: Bool { + didSet { + if !isHidden { + // layer?.opacity = 1 + } + } + } + + override open func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + set(background: .clear, for: .Normal) + backgroundColor = .clear + } + + + func overlayInitEvent() -> Void { + set(handler: { [weak self] control in + self?.clickHandler() + }, for: .Click) + updateLocalizationAndTheme(theme: presentation) + } + + required public init(frame frameRect: NSRect) { + super.init(frame:frameRect) + overlayInitEvent() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/TGUIKit/TGUIKit/Button.swift b/submodules/TGUIKit/TGUIKit/Button.swift similarity index 84% rename from TGUIKit/TGUIKit/Button.swift rename to submodules/TGUIKit/TGUIKit/Button.swift index 761df8d53a..6fb0b5687b 100644 --- a/TGUIKit/TGUIKit/Button.swift +++ b/submodules/TGUIKit/TGUIKit/Button.swift @@ -12,9 +12,12 @@ import Cocoa open class Button: Control { + public var contextObject: Any? + public var autohighlight:Bool = true public var highlightHovered:Bool = false - + public var _thatFit: Bool = false + private var stateBackground:[ControlState:NSColor] = [:] open override func draw(_ layer: CALayer, in ctx: CGContext) { @@ -27,6 +30,11 @@ open class Button: Control { } + open override func cursorUpdate(with event: NSEvent) { + //super.cursorUpdate(with: event) + NSCursor.arrow.set() + } + // public func set(backgroundColor:NSColor, for state:ControlState) -> Void { // stateBackground[state] = backgroundColor // apply(state: self.controlState) @@ -38,8 +46,7 @@ open class Button: Control { } func prepare() -> Void { - - + layer?.removeAllAnimations() } override public func apply(state:ControlState) -> Void { @@ -64,8 +71,9 @@ open class Button: Control { fatalError("init(coder:) has not been implemented") } - public func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) { - + public func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) -> Bool { + self._thatFit = thatFit + return true } open override func viewDidChangeBackingProperties() { diff --git a/submodules/TGUIKit/TGUIKit/CAAnimationUtils.swift b/submodules/TGUIKit/TGUIKit/CAAnimationUtils.swift new file mode 100644 index 0000000000..648ca05340 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/CAAnimationUtils.swift @@ -0,0 +1,466 @@ +// +// CAAnimationUtils.swift +// TGUIKit +// +// Created by keepcoder on 15/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +@objc public class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { + var completion: ((Bool) -> Void)? + + public init(completion: ((Bool) -> Void)?) { + self.completion = completion + + super.init() + } + + @objc public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + if let completion = self.completion { + completion(flag) + } + } +} + +private let completionKey = "CAAnimationUtils_completion" + +public extension CAMediaTimingFunctionName { + public static var spring: CAMediaTimingFunctionName { + return CAMediaTimingFunctionName(rawValue: "CAAnimationUtilsSpringCurve") + } +} + +public extension CAAnimation { + public var completion: ((Bool) -> Void)? { + get { + if let delegate = self.delegate as? CALayerAnimationDelegate { + return delegate.completion + } else { + return nil + } + } set(value) { + if let delegate = self.delegate as? CALayerAnimationDelegate { + delegate.completion = value + } else { + self.delegate = CALayerAnimationDelegate(completion: value) + } + } + } +} + +public func makeSpringAnimation(_ path:String) -> CABasicAnimation { + if #available(OSX 10.11, *) { + let springAnimation:CASpringAnimation = CASpringAnimation(keyPath: path) + springAnimation.mass = 3.0; + springAnimation.stiffness = 1000.0; + springAnimation.damping = 500.0; + springAnimation.initialVelocity = 0.0; + springAnimation.duration = 0.5;//springAnimation.settlingDuration; + springAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + return springAnimation; + } else { + let anim:CABasicAnimation = CABasicAnimation(keyPath: path) + anim.duration = 0.2 + anim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) + + return anim + } + +} + +public func makeSpringBounceAnimation(_ path:String, _ initialVelocity:CGFloat, _ damping: CGFloat = 88.0) -> CABasicAnimation { + if #available(OSX 10.11, *) { + let springAnimation:CASpringAnimation = CASpringAnimation(keyPath: path) + springAnimation.mass = 5.0 + springAnimation.stiffness = 900.0 + springAnimation.damping = damping + springAnimation.initialVelocity = initialVelocity + springAnimation.duration = springAnimation.settlingDuration + springAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + return springAnimation; + } else { + let anim:CABasicAnimation = CABasicAnimation(keyPath: path) + anim.duration = 0.2 + anim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) + + return anim + } + +} + + +public extension CALayer { + func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: CAMediaTimingFunctionName, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil, forKey: String? = nil) { + if timingFunction == CAMediaTimingFunctionName.spring { + let animation = makeSpringAnimation(keyPath) + animation.fromValue = from + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = CAMediaTimingFillMode.forwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let k = Float(1.0) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + self.add(animation, forKey: keyPath) + } else { + let k = Float(1.0) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + animation.speed = speed + animation.isAdditive = additive + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.add(animation, forKey: forKey ?? keyPath) + } + } + + func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: CAMediaTimingFunctionName, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + let k = Float(1.0) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + animation.speed = speed + animation.isAdditive = true + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.add(animation, forKey: key) + } + + func animateScaleSpring(from: CGFloat, to: CGFloat, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, bounce: Bool = true, completion: ((Bool) -> Void)? = nil) { + let animation = bounce ? makeSpringBounceAnimation("transform", initialVelocity) : makeSpringAnimation("transform") + + var fr = CATransform3DIdentity + fr = CATransform3DTranslate(fr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + fr = CATransform3DScale(fr, from, from, 1) + fr = CATransform3DTranslate(fr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + + animation.fromValue = NSValue(caTransform3D: fr) + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let speed: Float = 1.0 + + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + var tr = CATransform3DIdentity + tr = CATransform3DTranslate(tr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + tr = CATransform3DScale(tr, to, to, 1) + tr = CATransform3DTranslate(tr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + animation.toValue = NSValue(caTransform3D: tr) + + + self.add(animation, forKey: "transform") + } + + func animateScaleCenter(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, timingFunction: CAMediaTimingFunctionName = .easeInEaseOut, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = CABasicAnimation(keyPath: "transform") + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + + var fr = CATransform3DIdentity + fr = CATransform3DTranslate(fr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + fr = CATransform3DScale(fr, from, from, 1) + fr = CATransform3DTranslate(fr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + + animation.fromValue = NSValue(caTransform3D: fr) + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + + + animation.duration = duration + animation.isAdditive = additive + + var tr = CATransform3DIdentity + tr = CATransform3DTranslate(tr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + tr = CATransform3DScale(tr, to, to, 1) + tr = CATransform3DTranslate(tr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + animation.toValue = NSValue(caTransform3D: tr) + + + self.add(animation, forKey: "transform") + } + + func animateRotateCenter(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + + + let animation = makeSpringAnimation("transform") + + var fr = CATransform3DIdentity + fr = CATransform3DTranslate(fr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + fr = CATransform3DRotate(fr, from * CGFloat.pi / 180, 0, 0, 1.0) + fr = CATransform3DTranslate(fr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + + animation.fromValue = NSValue(caTransform3D: fr) + + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let speed: Float = 1.0 + + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + var tr = CATransform3DIdentity + tr = CATransform3DTranslate(tr, floorToScreenPixels(System.backingScale, frame.width / 2), floorToScreenPixels(System.backingScale, frame.height / 2), 0) + tr = CATransform3DRotate(fr, to * CGFloat.pi / 180, 0, 0, 1.0) + tr = CATransform3DTranslate(tr, -floorToScreenPixels(System.backingScale, frame.width / 2), -floorToScreenPixels(System.backingScale, frame.height / 2), 0) + animation.toValue = NSValue(caTransform3D: tr) + + + self.add(animation, forKey: "transform") + +// let animation = CABasicAnimation(keyPath: "transform") +// animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) +// var fr = CATransform3DIdentity +// fr = CATransform3DTranslate(fr, floorToScreenPixels(backingScaleFactor, frame.width / 2), floorToScreenPixels(backingScaleFactor, frame.height / 2), 0) +// fr = CATransform3DRotate(fr, from * CGFloat.pi / 180, 0, 0, 1.0) +// fr = CATransform3DTranslate(fr, -floorToScreenPixels(backingScaleFactor, frame.width / 2), -floorToScreenPixels(backingScaleFactor, frame.height / 2), 0) +// +// animation.fromValue = NSValue(caTransform3D: fr) +// animation.isRemovedOnCompletion = removeOnCompletion +// // animation.fillMode = kCAFillModeForwards +// if let completion = completion { +// animation.delegate = CALayerAnimationDelegate(completion: completion) +// } +// +// let speed: Float = 1.0 +// +// +// animation.speed = speed * Float(animation.duration / duration) +// animation.isAdditive = additive +// +// var tr = CATransform3DIdentity +// // tr = CATransform3DTranslate(tr, floorToScreenPixels(backingScaleFactor, frame.width / 2), floorToScreenPixels(backingScaleFactor, frame.height / 2), 0) +// tr = CATransform3DRotate(fr, to * CGFloat.pi / 180, 0, 0, 1.0) +// //tr = CATransform3DTranslate(tr, -floorToScreenPixels(backingScaleFactor, frame.width / 2), -floorToScreenPixels(backingScaleFactor, frame.height / 2), 0) +// animation.toValue = NSValue(caTransform3D: tr) +// +// +// self.add(animation, forKey: "transform") + } + + + func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + } + + func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation: CABasicAnimation + if #available(iOS 9.0, *) { + animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) + } else { + animation = makeSpringAnimation(keyPath) + } + animation.fromValue = from + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = .forwards + + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let k = Float(1) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + self.add(animation, forKey: keyPath) + } + + func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + } + + func animateScaleX(from: CGFloat, to: CGFloat, duration: Double, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.x", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + } + + func animateScaleY(from: CGFloat, to: CGFloat, duration: Double, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.y", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + } + + func animatePosition(from: NSPoint, to: NSPoint, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to { + if let completion = completion { + completion(true) + } + return + } + self.animate(from: NSValue(point: from), to: NSValue(point: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + } + + func animateBounds(from: NSRect, to: NSRect, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, removeOnCompletion: Bool = true, additive: Bool = false, forKey: String? = nil, completion: ((Bool) -> Void)? = nil) { + if from == to { + if let completion = completion { + completion(true) + } + return + } + self.animate(from: NSValue(rect: from), to: NSValue(rect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion, forKey: forKey) + } + + func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { + self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: CAMediaTimingFunctionName.easeOut, duration: duration, removeOnCompletion: true) + } + + func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: CAMediaTimingFunctionName, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to { + if let completion = completion { + completion(true) + } + return + } + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: nil) + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + } + + func shake(_ duration:CFTimeInterval, from:NSPoint, to:NSPoint) { + let animation = CABasicAnimation(keyPath: "position") + animation.duration = duration; + animation.repeatCount = 4 + animation.autoreverses = true + animation.isRemovedOnCompletion = true + + animation.fromValue = NSValue(point: from) + animation.toValue = NSValue(point: to) + + self.add(animation, forKey: "position") + } + + + + /* + + (CAAnimation *)shakeWithDuration:(float)duration fromValue:(CGPoint)fromValue toValue:(CGPoint)toValue { + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; + animation.duration = duration; + animation.repeatCount = 4; + animation.autoreverses = YES; + animation.removedOnCompletion = YES; + NSValue *fromValueValue = [NSValue value:&fromValue withObjCType:@encode(CGPoint)]; + NSValue *toValueValue = [NSValue value:&toValue withObjCType:@encode(CGPoint)]; + + animation.fromValue = fromValueValue; + animation.toValue = toValueValue; + return animation; + } + */ +} + + +struct ViewportItemSpring { + let stiffness: CGFloat + let damping: CGFloat + let mass: CGFloat + var velocity: CGFloat = 0.0 + + init(stiffness: CGFloat, damping: CGFloat, mass: CGFloat) { + self.stiffness = stiffness + self.damping = damping + self.mass = mass + } +} + +private func a(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 1.0 - 3.0 * a2 + 3.0 * a1 +} + +private func b(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a2 - 6.0 * a1 +} + +private func c(_ a1: CGFloat) -> CGFloat +{ + return 3.0 * a1 +} + +private func calcBezier(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return ((a(a1, a2)*t + b(a1, a2))*t + c(a1)) * t +} + +private func calcSlope(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1) +} + +private func getTForX(_ x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { + var t = x + var i = 0 + while i < 4 { + let currentSlope = calcSlope(t, x1, x2) + if currentSlope == 0.0 { + return t + } else { + let currentX = calcBezier(t, x1, x2) - x + t -= currentX / currentSlope + } + + i += 1 + } + + return t +} + +public func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat +{ + var value = calcBezier(getTForX(x, x1, x2), y1, y2) + if value >= 0.997 { + value = 1.0 + } + return value +} diff --git a/submodules/TGUIKit/TGUIKit/CatalinaStyledSegmentController.swift b/submodules/TGUIKit/TGUIKit/CatalinaStyledSegmentController.swift new file mode 100644 index 0000000000..95e6fc6177 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/CatalinaStyledSegmentController.swift @@ -0,0 +1,340 @@ +// +// CatalinaStyledSegmentController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 18.10.2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +// +// SegmentController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 21/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +public class CatalinaSegmentedItem { + let title:String + let handler: ()->Void + public init(title:String, handler:@escaping()->Void) { + self.title = title + self.handler = handler + } +} + + +public struct CatalinaSegmentTheme { + let backgroundColor: NSColor + let foregroundColor: NSColor + let activeTextColor: NSColor + let inactiveTextColor: NSColor + public init(backgroundColor: NSColor, foregroundColor: NSColor, activeTextColor: NSColor, inactiveTextColor: NSColor) { + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.activeTextColor = activeTextColor + self.inactiveTextColor = inactiveTextColor + } +} + +private final class CatalinaSegmentItemView : Control { + let item: CatalinaSegmentedItem + private let inactiveTextView: TextView = TextView() + private let activeTextView: TextView = TextView() + private let separatorView = View() + init(item: CatalinaSegmentedItem, theme: CatalinaSegmentTheme, select:@escaping()->Void) { + self.item = item + self.theme = theme + super.init(frame: NSZeroRect) + addSubview(activeTextView) + addSubview(inactiveTextView) + addSubview(separatorView) + activeTextView.userInteractionEnabled = false + activeTextView.isSelectable = false + + inactiveTextView.userInteractionEnabled = false + inactiveTextView.isSelectable = false + + + set(handler: { _ in + select() + }, for: .SingleClick) + + set(handler: { [weak self] _ in + self?.inactiveTextView.layer?.animateAlpha(from: 1, to: 0.8, duration: 0.2, removeOnCompletion: false) + }, for: .Down) + + set(handler: { [weak self] _ in + self?.inactiveTextView.layer?.animateAlpha(from: 0.8, to: 1.0, duration: 0.2, removeOnCompletion: true) + }, for: .Up) + } + + + var theme: CatalinaSegmentTheme { + didSet { + update() + needsLayout = true + } + } + + private func update() { + + activeTextView.disableBackgroundDrawing = true + inactiveTextView.disableBackgroundDrawing = true + + let activeLayout = TextViewLayout(.initialize(string: item.title, color: theme.activeTextColor, font: .medium(.text)), maximumNumberOfLines: 1, alwaysStaticItems: true) + activeLayout.measure(width: frame.width - 6) + + let inactiveLayout = TextViewLayout(.initialize(string: item.title, color: theme.inactiveTextColor, font: .normal(.text)), maximumNumberOfLines: 1, alwaysStaticItems: true) + inactiveLayout.measure(width: frame.width - 6) + + activeTextView.update(activeLayout) + inactiveTextView.update(inactiveLayout) + + separatorView.backgroundColor = theme.inactiveTextColor + + } + + func set(_ selected: Bool, hasSepator: Bool, animated: Bool) { + separatorView.change(opacity: hasSepator ? 0.4 : 0, animated: animated) + activeTextView.change(opacity: selected ? 1 : 0, animated: animated) + inactiveTextView.change(opacity: !selected ? 1 : 0, animated: animated) + +// activeTextView.backgroundColor = !selected ? theme.backgroundColor : theme.foregroundColor +// inactiveTextView.backgroundColor = !selected ? theme.backgroundColor : theme.foregroundColor + + // userInteractionEnabled = !selected + isSelected = selected + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func layout() { + super.layout() + update() + separatorView.frame = NSMakeRect(frame.width - .borderSize, 8, .borderSize, frame.height - 16) + activeTextView.center() + inactiveTextView.center() + + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + } +} + +private class CatalinaSegmentedControlView: View { + private let button: Control = Control() + private let itemsContainerView = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layer?.cornerRadius = 10.0 + addSubview(button) + addSubview(itemsContainerView) + button.layer?.cornerRadius = 8 + + button.set(handler: { [weak self] control in + control.layer?.animateScaleCenter(from: 1, to: 0.95, duration: 0.2, removeOnCompletion: false) + guard let `self` = self else { + return + } + let itemView = self.itemsContainerView.subviews.compactMap { $0 as? CatalinaSegmentItemView }.first(where: { $0.isSelected }) + itemView?.layer?.animateScaleCenter(from: 1, to: 0.95, duration: 0.2, removeOnCompletion: false) + + }, for: .Down) + + button.set(handler: { [weak self] control in + control.layer?.animateScaleCenter(from: 0.95, to: 1.0, duration: 0.2, removeOnCompletion: true) + guard let `self` = self else { + return + } + let itemView = self.itemsContainerView.subviews.compactMap { $0 as? CatalinaSegmentItemView }.first(where: { $0.isSelected }) + itemView?.layer?.animateScaleCenter(from: 0.95, to: 1.0, duration: 0.2, removeOnCompletion: true) + }, for: .Up) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public var theme: CatalinaSegmentTheme = CatalinaSegmentTheme(backgroundColor: presentation.colors.listBackground, foregroundColor: presentation.colors.background, activeTextColor: presentation.colors.text, inactiveTextColor: presentation.colors.listGrayText) { + didSet { + for subview in itemsContainerView.subviews.compactMap({ $0 as? CatalinaSegmentItemView}) { + subview.theme = theme + } + update() + } + } + + private func update() { + self.backgroundColor = theme.backgroundColor + itemsContainerView.backgroundColor = .clear + + button.backgroundColor = theme.foregroundColor + needsLayout = true + } + + func update(items: [CatalinaSegmentedItem], selected: Int, animated: Bool, select: @escaping(Int, Bool)->Void) -> Void { + itemsContainerView.removeAllSubviews() + for i in 0 ..< items.count { + let view = CatalinaSegmentItemView(item: items[i], theme: theme, select: { select(i, true) }) + var hasSeparator: Bool = false + if i != (items.count - 1) && selected != i { + hasSeparator = true + if selected > 0 { + if i == selected - 1 { + hasSeparator = false + } + } + } + view.set(selected == i, hasSepator: hasSeparator, animated: animated) + itemsContainerView.addSubview(view) + } + + needsLayout = true + } + + func set(selected: Int, animated: Bool) { + let items = self.itemsContainerView.subviews.compactMap { $0 as? CatalinaSegmentItemView } + for (i, subview) in items.enumerated() { + var hasSeparator: Bool = false + if i != (items.count - 1) && selected != i { + hasSeparator = true + if selected > 0 { + if i == selected - 1 { + hasSeparator = false + } + } + } + subview.set(selected == i, hasSepator: hasSeparator, animated: animated) + if selected == i { + button.change(pos: NSMakePoint(max(2, min(subview.frame.minX + 2, frame.width - subview.frame.width - 2)), 2), animated: animated) + } + } + needsLayout = true + } + + override func layout() { + super.layout() + + let items = self.itemsContainerView.subviews.compactMap { $0 as? CatalinaSegmentItemView } + + guard !items.isEmpty else {return} + + self.itemsContainerView.frame = bounds + + + var x: CGFloat = 0 + let itemWidth = floor(frame.width / CGFloat(items.count)) + for view in items { + view.frame = NSMakeRect(x, 0, itemWidth, frame.height) + + if view.isSelected { + button.frame = NSMakeRect(max(2, min(x + 2, frame.width - itemWidth - 2)), 2, itemWidth, frame.height - 4) + } + + x += itemWidth + } + } + +} + + +public class CatalinaStyledSegmentController: ViewController { + + private var items: [CatalinaSegmentedItem] = [] + private var selected: Int = 0 + + private var genericView: CatalinaSegmentedControlView { + return self.view as! CatalinaSegmentedControlView + } + + public override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + bar = .init(height: 0) + } + + public var theme: CatalinaSegmentTheme { + get { + return genericView.theme + } + set { + genericView.theme = newValue + } + } + + public override func viewDidLoad() { + super.viewDidLoad() + readyOnce() + } + + private func select(_ index: Int, animated: Bool) { + self.selected = index + items[index].handler() + genericView.set(selected: index, animated: animated) + } + + public func add(segment: CatalinaSegmentedItem) -> Void { + items.append(segment) + genericView.update(items: items, selected: selected, animated: false, select: { [weak self] index, animated in + self?.select(index, animated: animated) + }) + } + public func segment(at index:Int) -> CatalinaSegmentedItem { + return items[index] + } + public func replace(segment: CatalinaSegmentedItem, at index:Int) -> Void { + items[index] = segment + genericView.update(items: items, selected: selected, animated: false, select: { [weak self] index, animated in + self?.select(index, animated: animated) + }) + } + public func insert(segment: CatalinaSegmentedItem, at index: Int) -> Void { + items.insert(segment, at: index) + genericView.update(items: items, selected: selected, animated: false, select: { [weak self] index, animated in + self?.select(index, animated: animated) + }) + } + public func remove(at index: Int) -> Void { + items.remove(at: index) + genericView.update(items: items, selected: selected, animated: false, select: { [weak self] index, animated in + self?.select(index, animated: animated) + }) + } + + public func set(selected index: Int, animated: Bool = false) -> Void { + selected = index + genericView.set(selected: index, animated: animated) + } + + public func selectNext(animated: Bool) { + var index = self.selected + index += 1 + if index == self.items.count { + index = 0 + } + self.select(index, animated: animated) + } + + public func removeAll() -> Void { + selected = 0 + items.removeAll() + genericView.update(items: items, selected: selected, animated: false, select: { [weak self] index, animated in + self?.select(index, animated: animated) + }) + } + + override public func viewClass() -> AnyClass { + return CatalinaSegmentedControlView.self + } +} diff --git a/submodules/TGUIKit/TGUIKit/CheckBox.swift b/submodules/TGUIKit/TGUIKit/CheckBox.swift new file mode 100644 index 0000000000..686920d067 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/CheckBox.swift @@ -0,0 +1,76 @@ +// +// CheckBox.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 16/02/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +public class CheckBox: Control { + private let textView: TextView = TextView() + private let selectedImage: CGImage + private let unselectedImage: CGImage + private let imageView:ImageView = ImageView() + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + public init(selectedImage: CGImage, unselectedImage: CGImage) { + self.selectedImage = selectedImage + self.unselectedImage = unselectedImage + super.init(frame: NSZeroRect) + textView.userInteractionEnabled = false + textView.isSelectable = false + textView.isEventLess = true + + addSubview(textView) + addSubview(imageView) + isSelected = false + } + + public override var isSelected: Bool { + didSet { + imageView.image = isSelected ? selectedImage : unselectedImage + imageView.sizeToFit() + needsLayout = true + } + } + + public override func layout() { + imageView.centerY(x: 0) + textView.centerY(x: imageView.frame.maxX + 10) + } + + override public func send(event: ControlEvent) { + switch event { + case .Click: + self.isSelected = !isSelected + default: + break + } + super.send(event: event) + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func update(with text: String, maxWidth: CGFloat) { + let textLayout = TextViewLayout(.initialize(string: text, color: presentation.colors.text, font: .normal(.text)), maximumNumberOfLines: 1) + textLayout.measure(width: maxWidth - (imageView.frame.width + 10)) + textView.update(textLayout) + needsLayout = true + setFrameSize(NSMakeSize(textLayout.layoutSize.width + (imageView.frame.width + 10), max(imageView.frame.height, textLayout.layoutSize.height))) + } + + + override public func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + // Drawing code here. + } + +} diff --git a/TGUIKit/TGUIKit/ContainableController.swift b/submodules/TGUIKit/TGUIKit/ContainableController.swift similarity index 92% rename from TGUIKit/TGUIKit/ContainableController.swift rename to submodules/TGUIKit/TGUIKit/ContainableController.swift index 71f9bf0162..bea7441958 100644 --- a/TGUIKit/TGUIKit/ContainableController.swift +++ b/submodules/TGUIKit/TGUIKit/ContainableController.swift @@ -14,12 +14,12 @@ public enum ContainedViewLayoutTransitionCurve { } public extension ContainedViewLayoutTransitionCurve { - var timingFunction: String { + var timingFunction: CAMediaTimingFunctionName { switch self { case .easeInOut: - return kCAMediaTimingFunctionEaseInEaseOut + return CAMediaTimingFunctionName.easeInEaseOut case .spring: - return kCAMediaTimingFunctionSpring + return CAMediaTimingFunctionName.spring } } } diff --git a/submodules/TGUIKit/TGUIKit/ContextMenu.swift b/submodules/TGUIKit/TGUIKit/ContextMenu.swift new file mode 100644 index 0000000000..1bc3c0b50c --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ContextMenu.swift @@ -0,0 +1,107 @@ +// +// ContextMenu.swift +// TGUIKit +// +// Created by keepcoder on 03/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + +public class ContextSeparatorItem : ContextMenuItem { + public init() { + super.init("", handler: {}, image: nil) + } + + public override var isSeparatorItem: Bool { + return true + } + + required public init(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public class ContextMenuItem : NSMenuItem { + + let handler:()->Void + private let dynamicTitle:(()->String)? + + + public init(_ title:String, handler:@escaping()->Void = {}, image:NSImage? = nil, dynamicTitle:(()->String)? = nil, state: NSControl.StateValue? = nil) { + self.handler = handler + self.dynamicTitle = dynamicTitle + super.init(title: title, action: nil, keyEquivalent: "") + + self.title = title + self.action = #selector(click) + self.target = self + self.isEnabled = true + self.image = image + if let state = state { + self.state = state + } + } + + public override var title: String { + get { + return self.dynamicTitle?() ?? super.title + } + set { + super.title = newValue + } + } + + required public init(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func click() -> Void { + handler() + } + +} + +public final class ContextMenu : NSMenu, NSMenuDelegate { + + var onShow:(ContextMenu)->Void = {(ContextMenu) in} + var onClose:()->Void = {() in} + + weak var view:NSView? + + public static func show(items:[ContextMenuItem], view:NSView, event:NSEvent, onShow:@escaping(ContextMenu)->Void = {_ in}, onClose:@escaping()->Void = {}) -> Void { + + let menu = ContextMenu.init() + menu.onShow = onShow + menu.onClose = onClose + menu.view = view + + for item in items { + menu.addItem(item) + } + + menu.delegate = menu + + NSMenu.popUpContextMenu(menu, with: event, for: view) + } + + + public func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { + return true + } + + public func menuWillOpen(_ menu: NSMenu) { + onShow(self) + } + + public func menuDidClose(_ menu: NSMenu) { + onClose() + } + +} + + + + + diff --git a/submodules/TGUIKit/TGUIKit/Control.swift b/submodules/TGUIKit/TGUIKit/Control.swift new file mode 100644 index 0000000000..8619381eee --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Control.swift @@ -0,0 +1,535 @@ +// +// Control.swift +// TGUIKit +// +// Created by keepcoder on 25/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +public enum ControlState { + case Normal + case Hover + case Highlight + case Other +} + +public enum ControlEvent { + case Down + case Up + case Click + case SingleClick + case RightClick + case RightDown + case MouseDragging + case LongMouseDown + case LongMouseUp + case LongOver +} + +private let longHandleDisposable = MetaDisposable() +private let longOverHandleDisposable = MetaDisposable() + + +internal struct ControlEventHandler : Hashable { + static func == (lhs: ControlEventHandler, rhs: ControlEventHandler) -> Bool { + return lhs.identifier == rhs.identifier + } + + let identifier: UInt32 + let handler:(Control)->Void + let event:ControlEvent + let `internal`: Bool + + func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } +} +internal struct ControlStateHandler : Hashable { + static func == (lhs: ControlStateHandler, rhs: ControlStateHandler) -> Bool { + return lhs.identifier == rhs.identifier + } + let identifier: UInt32 + let handler:(Control)->Void + let state:ControlState + let `internal`: Bool + + func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } +} + +open class Control: View { + + public internal(set) weak var popover: Popover? + + open var isEnabled:Bool = true { + didSet { + if isEnabled != oldValue { + apply(state: controlState) + } + } + } + open var hideAnimated:Bool = false + + + public var appTooltip: String? + + public var isSelected:Bool { + didSet { + if isSelected != oldValue { + apply(state: isSelected ? .Highlight : self.controlState) + } + updateState() + } + } + + open var animationStyle:AnimationStyle = AnimationStyle(duration:0.3, function:CAMediaTimingFunctionName.spring) + + var trackingArea:NSTrackingArea? + + + + private var handlers:[ControlEventHandler] = [] + private var stateHandlers:[ControlStateHandler] = [] + + private(set) internal var backgroundState:[ControlState:NSColor] = [:] + private var mouseMovedInside: Bool = true + private var longInvoked: Bool = false + public var handleLongEvent: Bool = true + open override var backgroundColor: NSColor { + get{ + return self.style.backgroundColor + } + set { + if self.style.backgroundColor != newValue { + self.style.backgroundColor = newValue + self.setNeedsDisplayLayer() + } + } + } + + public var style:ControlStyle = ControlStyle() { + didSet { + if style != oldValue { + apply(style:style) + } + } + } + + open override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + apply(state: self.controlState) + updateTrackingAreas() + } + + public var controlState:ControlState = .Normal { + didSet { + if oldValue != controlState { + apply(state: isSelected ? .Highlight : controlState) + + for value in stateHandlers { + if value.state == controlState { + value.handler(self) + } + } + + if let tp = appTooltip, controlState == .Hover { + tooltip(for: self, text: tp) + } + } + } + } + + public func apply(state:ControlState) -> Void { + let state:ControlState = self.isSelected ? .Highlight : state + if state == .Highlight, (NSEvent.pressedMouseButtons & (1 << 0)) == 0 { + self.mouseIsDown = false + self.updateState() + return + } + if isEnabled { + if let color = backgroundState[state] { + self.layer?.backgroundColor = color.cgColor + } else { + self.layer?.backgroundColor = backgroundState[.Normal]?.cgColor ?? self.backgroundColor.cgColor + } + } else { + self.layer?.backgroundColor = backgroundState[.Normal]?.cgColor ?? self.backgroundColor.cgColor + } + + if animates { + self.layer?.animateBackground() + } + } + + private var mouseIsDown:Bool = false + + open override func updateTrackingAreas() { + super.updateTrackingAreas(); + + if let trackingArea = trackingArea { + self.removeTrackingArea(trackingArea) + } + + trackingArea = nil + + if let _ = window { + let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.activeInKeyWindow, NSTrackingArea.Options.inVisibleRect] + self.trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) + + self.addTrackingArea(self.trackingArea!) + } + + } + + open override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + open override func viewDidMoveToSuperview() { + super.viewDidMoveToSuperview() + updateTrackingAreas() + } + + deinit { + if let trackingArea = self.trackingArea { + self.removeTrackingArea(trackingArea) + } + // longHandleDisposable.dispose() + // longOverHandleDisposable.dispose() + } + + public var controlIsHidden: Bool { + return super.isHidden || layer!.opacity < Float(1.0) + } + + open override var isHidden: Bool { + get { + return super.isHidden + } + set { + if newValue != super.isHidden { + if hideAnimated { + if !newValue { + super.isHidden = newValue + } + self.layer?.opacity = newValue ? 0.0 : 1.0 + self.layer?.animateAlpha(from: newValue ? 1.0 : 0.0, to: newValue ? 0.0 : 1.0, duration: 0.2, completion:{[weak self] (completed) in + if completed { + self?.updateHiddenState(newValue) + } + }) + } else { + updateHiddenState(newValue) + } + } + } + } + + public func forceHide() -> Void { + super.isHidden = true + self.layer?.removeAllAnimations() + } + + private func updateHiddenState(_ value:Bool) -> Void { + super.isHidden = value + } + + + public var canHighlight: Bool = true + + @discardableResult public func set(handler:@escaping (Control) -> Void, for event:ControlEvent) -> UInt32 { + return set(handler: handler, for: event, internal: false) + } + + @discardableResult public func set(handler:@escaping (Control) -> Void, for state:ControlState) -> UInt32 { + return set(handler: handler, for: state, internal: false) + } + + @discardableResult internal func set(handler:@escaping (Control) -> Void, for event:ControlEvent, internal: Bool) -> UInt32 { + let new = ControlEventHandler(identifier: arc4random(), handler: handler, event: event, internal: `internal`) + handlers.append(new) + return new.identifier + } + + @discardableResult internal func set(handler:@escaping (Control) -> Void, for state:ControlState, internal: Bool) -> UInt32 { + let new = ControlStateHandler(identifier: arc4random(), handler: handler, state: state, internal: `internal`) + stateHandlers.append(new) + return new.identifier + } + + public func set(background:NSColor, for state:ControlState) -> Void { + backgroundState[state] = background + apply(state: self.controlState) + self.setNeedsDisplayLayer() + } + + public func removeLastHandler() -> ((Control)->Void)? { + var last: ControlEventHandler? + for handler in handlers.reversed() { + if !handler.internal { + last = handler + break + } + } + if let last = last { + self.handlers.removeAll(where: { last.identifier == $0.identifier }) + return last.handler + } + return nil + } + + public func removeLastStateHandler() -> Void { + + var last: ControlStateHandler? + for handler in stateHandlers.reversed() { + if !handler.internal { + last = handler + break + } + } + if let last = last { + self.stateHandlers.removeAll(where: { last.identifier == $0.identifier }) + } + } + + public func removeStateHandler(_ identifier: UInt32) -> Void { + self.stateHandlers.removeAll(where: { identifier == $0.identifier }) + } + + public func removeHandler(_ identifier: UInt32) -> Void { + self.handlers.removeAll(where: { identifier == $0.identifier }) + } + + public func removeAllStateHandler() -> Void { + self.stateHandlers.removeAll(where: { !$0.internal }) + + } + + public func removeAllHandlers() ->Void { + self.handlers.removeAll(where: { !$0.internal }) + } + + + override open func mouseDown(with event: NSEvent) { + mouseIsDown = true + longInvoked = false + longOverHandleDisposable.set(nil) + + if event.modifierFlags.contains(.control) { + for handler in handlers { + if handler.event == .RightDown { + handler.handler(self) + } + } + super.mouseDown(with: event) + return + } + + if userInteractionEnabled, mouseInside() { + updateState() + send(event: .Down) + let point = event.locationInWindow + if handleLongEvent { + let disposable = (Signal.single(Void()) |> delay(0.35, queue: Queue.mainQueue())).start(next: { [weak self] in + if let inside = self?.mouseInside(), inside, let wPoint = self?.window?.mouseLocationOutsideOfEventStream, NSPointInRect(point, NSMakeRect(wPoint.x - 2, wPoint.y - 2, 4, 4)) { + self?.longInvoked = true + self?.send(event: .LongMouseDown) + } + }) + + longHandleDisposable.set(disposable) + } else { + longHandleDisposable.set(nil) + } + + + } else { + super.mouseDown(with: event) + } + } + + override open func mouseUp(with event: NSEvent) { + longHandleDisposable.set(nil) + longOverHandleDisposable.set(nil) + mouseIsDown = false + + if userInteractionEnabled && !event.modifierFlags.contains(.control) { + if isEnabled && layer!.opacity > 0 { + send(event: .Up) + + if longInvoked { + send(event: .LongMouseUp) + } + + if mouseInside() && !longInvoked { + if event.clickCount == 1 { + send(event: .SingleClick) + } + send(event: .Click) + } + } + + updateState() + + } else { + super.mouseUp(with: event) + } + } + + func performSuperMouseUp(_ event: NSEvent) { + super.mouseUp(with: event) + } + func performSuperMouseDown(_ event: NSEvent) { + super.mouseDown(with: event) + } + + + public func send(event:ControlEvent) -> Void { + for value in handlers { + if value.event == event { + value.handler(self) + } + } + + } + + override open func mouseMoved(with event: NSEvent) { + updateState() + if userInteractionEnabled { + + } else { + super.mouseMoved(with: event) + } + } + + + open override func rightMouseDown(with event: NSEvent) { + if userInteractionEnabled { + updateState() + send(event: .RightDown) + super.rightMouseDown(with: event) + } else { + super.rightMouseDown(with: event) + } + } + + + public func updateState() -> Void { + if mouseInside(), !inLiveResize { + if mouseIsDown && canHighlight { + self.controlState = .Highlight + } else if mouseMovedInside { + self.controlState = .Hover + } else { + self.controlState = .Normal + } + } else { + self.controlState = .Normal + } + + } + + public var continuesAction: Bool = false + + override open func mouseEntered(with event: NSEvent) { + updateState() + if userInteractionEnabled { + + let disposable = (Signal.single(Void()) |> delay(0.3, queue: Queue.mainQueue())).start(next: { [weak self] in + if let strongSelf = self, strongSelf.mouseInside(), strongSelf.controlState == .Hover { + strongSelf.send(event: .LongOver) + } + }) + longOverHandleDisposable.set(disposable) + + } else { + super.mouseEntered(with: event) + } + } + + + override open func mouseExited(with event: NSEvent) { + updateState() + longOverHandleDisposable.set(nil) + if userInteractionEnabled { + } else { + super.mouseExited(with: event) + } + } + + + open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + return NSDragOperation.generic + } + + + + override open func mouseDragged(with event: NSEvent) { + if userInteractionEnabled { + send(event: .MouseDragging) + updateState() + } else { + super.mouseDragged(with: event) + } + } + + func apply(style:ControlStyle) -> Void { + set(background: style.backgroundColor, for: .Normal) + self.backgroundColor = style.backgroundColor + if self.animates { + self.layer?.animateBackground() + } + self.setNeedsDisplayLayer() + } + + + + required public init(frame frameRect: NSRect) { + self.isSelected = false + super.init(frame: frameRect) + animates = false +// layer?.disableActions() + guard #available(OSX 10.12, *) else { + layer?.opacity = 0.99 + return + } + + + + //self.wantsLayer = true + //self.layer?.isOpaque = true + } + + public override init() { + self.isSelected = false + super.init(frame: NSZeroRect) + animates = false + layer?.disableActions() + + guard #available(OSX 10.12, *) else { + layer?.opacity = 0.99 + return + } + + + + //self.wantsLayer = true + //self.layer?.isOpaque = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func becomeFirstResponder() -> Bool { + if let window = kitWindow { + return window.makeFirstResponder(self) + } + return false + } + +} diff --git a/submodules/TGUIKit/TGUIKit/DatePicker.swift b/submodules/TGUIKit/TGUIKit/DatePicker.swift new file mode 100644 index 0000000000..284e4aff8f --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/DatePicker.swift @@ -0,0 +1,467 @@ +// +// DatePicker.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 07/08/2019. +// Copyminutes © 2019 Telegram. All minutess reserved. +// + +import Cocoa + +public final class DatePickerOption : Equatable where T: Equatable { + public let name: String + public let value:T + public init(name: String, value: T) { + self.name = name + self.value = value + } + + public static func ==(lhs: DatePickerOption, rhs: DatePickerOption) -> Bool { + return lhs.name == rhs.name && rhs.value == rhs.value + } + +} + +public final class DatePickerData { + +} + + + +public class DatePicker: Control where T: Equatable { + + private let selectedText = TextView() + private let borderView = View() + private let activeBorderView = View() + + public var selected: DatePickerOption { + didSet { + if oldValue != selected { + self.updateSelected(animated: true) + } + } + } + + public var font: NSFont = .normal(.text) + + public init(selected: DatePickerOption) { + self.selected = selected + super.init(frame: NSZeroRect) + self.selectedText.userInteractionEnabled = false + self.selectedText.isEventLess = true + self.selectedText.isSelectable = false + + self.addSubview(self.selectedText) + self.addSubview(self.borderView) + self.addSubview(self.activeBorderView) + self.updateLocalizationAndTheme(theme: presentation) + } + + public override func layout() { + super.layout() + self.updateSelected(animated: false) + self.borderView.frame = bounds + self.activeBorderView.frame = bounds + self.selectedText.center() + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.backgroundColor = presentation.colors.grayBackground + self.layer?.cornerRadius = .cornerRadius + + self.borderView.layer?.borderWidth = .borderSize + self.borderView.layer?.borderColor = presentation.colors.border.cgColor + self.borderView.layer?.cornerRadius = .cornerRadius + + self.activeBorderView.layer?.borderWidth = .borderSize + self.activeBorderView.layer?.borderColor = presentation.colors.accentIcon.cgColor + self.activeBorderView.layer?.cornerRadius = .cornerRadius + self.updateSelected(animated: false) + needsLayout = true + } + + + public override func updateState() { + super.updateState() + self.updateSelected(animated: true) + needsLayout = true + } + + private func updateSelected(animated: Bool) { + let layout = TextViewLayout(.initialize(string: self.selected.name, color: presentation.colors.text, font: self.font), maximumNumberOfLines: 1, alwaysStaticItems: true) + layout.measure(width: frame.width - 8) + self.selectedText.update(layout) + if controlState == .Highlight || isSelected { + self.borderView.change(opacity: 0, animated: animated) + self.activeBorderView.change(opacity: 1, animated: animated) + } else { + self.borderView.change(opacity: 1, animated: animated) + self.activeBorderView.change(opacity: 0, animated: animated) + } + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override public func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + // Drawing code here. + } + +} + + + + +public struct TimePickerOption : Equatable { + public let hours: Int32 + public let minutes: Int32 + public let seconds: Int32 + public init(hours: Int32, minutes: Int32, seconds: Int32) { + self.hours = hours + self.minutes = minutes + self.seconds = seconds + } +} + +private final class TimeOptionView: View { + + private var textNode:(TextNodeLayout, TextNode)? + private var textNodeSelected:(TextNodeLayout, TextNode)? + + private var isFirst: Bool = false + + var keyDown:((Int32, Bool)->Void)? = nil + var next:((Bool)->Void)? = nil + + private func updateLayout() { + + let value: String = self.value < 10 ? "0\(self.value)" : "\(self.value)" + + textNode = TextNode.layoutText(.initialize(string: value, color: presentation.colors.text, font: .normal(.text)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .center) + textNodeSelected = TextNode.layoutText(.initialize(string: value, color: presentation.colors.underSelectedColor, font: .normal(.text)), nil, 1, .end, NSMakeSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude), nil, false, .center) + + setFrameSize(NSMakeSize(textNode!.0.size.width + 6, textNode!.0.size.height + 4)) + + needsDisplay = true + } + + var value: Int32 = 0 { + didSet { + updateLayout() + } + } + init(value: Int32) { + self.value = value + super.init(frame: NSZeroRect) + updateLayout() + } + + override func keyDown(with event: NSEvent) { + guard let keyCode = KeyboardKey(rawValue: event.keyCode) else { + return + } + switch keyCode { + case .Zero, .Keypad0: + self.keyDown?(0, isFirst) + case .One, .Keypad1: + self.keyDown?(1, isFirst) + case .Two, .Keypad2: + self.keyDown?(2, isFirst) + case .Three, .Keypad3: + self.keyDown?(3, isFirst) + case .Four, .Keypad4: + self.keyDown?(4, isFirst) + case .Five, .Keypad5: + self.keyDown?(5, isFirst) + case .Six, .Keypad6: + self.keyDown?(6, isFirst) + case .Seven, .Keypad7: + self.keyDown?(7, isFirst) + case .Eight, .Keypad8: + self.keyDown?(8, isFirst) + case .Nine, .Keypad9: + self.keyDown?(9, isFirst) + case .Tab, .RightArrow: + self.next?(true) + case .LeftArrow: + self.next?(false) + case .Escape: + self.window?.makeFirstResponder(nil) + default: + break + } + isFirst = false + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override var acceptsFirstResponder: Bool { + return true + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + ctx.round(frame.size, 4) + + if window?.firstResponder == self { + ctx.setFillColor(presentation.colors.accentSelect.cgColor) + ctx.fill(bounds) + if let textNode = textNodeSelected { + textNode.1.draw(focus(textNode.0.size), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: .clear) + } + } else { + ctx.setFillColor(presentation.colors.grayBackground.cgColor) + ctx.fill(bounds) + if let textNode = self.textNode { + textNode.1.draw(focus(textNode.0.size), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: .clear) + } + } + + } + + override func becomeFirstResponder() -> Bool { + needsDisplay = true + isFirst = true + return super.becomeFirstResponder() + } + override func resignFirstResponder() -> Bool { + needsDisplay = true + isFirst = false + return super.resignFirstResponder() + } +} + +public class TimePicker: Control { + + public var update:((TimePickerOption)->Bool)? + + private let hoursView:TimeOptionView + private let minutesView:TimeOptionView + private let secondsView:TimeOptionView + private let separatorView1: TextView = TextView() + private let separatorView2: TextView = TextView() + private let borderView = View() + + public var selected: TimePickerOption { + didSet { + if oldValue != selected { + self.hoursView.value = selected.hours + self.minutesView.value = selected.minutes + self.secondsView.value = selected.seconds + needsLayout = true + self.updateSelected(animated: true) + } + } + } + + public var font: NSFont = .normal(.text) + + public init(selected: TimePickerOption) { + self.selected = selected + self.hoursView = TimeOptionView(value: selected.hours) + self.minutesView = TimeOptionView(value: selected.minutes) + self.secondsView = TimeOptionView(value: selected.seconds) + super.init(frame: NSZeroRect) + + + separatorView1.userInteractionEnabled = false + separatorView1.isSelectable = false + separatorView1.isEventLess = true + + separatorView2.userInteractionEnabled = false + separatorView2.isSelectable = false + separatorView2.isEventLess = true + + self.addSubview(self.separatorView1) + self.addSubview(self.separatorView2) + self.addSubview(self.borderView) + self.addSubview(self.hoursView) + self.addSubview(self.minutesView) + self.addSubview(self.secondsView) + self.updateLocalizationAndTheme(theme: presentation) + + hoursView.keyDown = { [weak self] value, isFirst in + guard let selected = self?.selected else { + return + } + var updatedValue = value + if isFirst { + updatedValue = value + } else { + if selected.hours > 0, selected.hours < 10 { + updatedValue = min(Int32("\(selected.hours)\(updatedValue)")!, 23) + self?.switchToRight() + } + } + + let new = TimePickerOption(hours: updatedValue, minutes: selected.minutes, seconds: selected.seconds) + if let result = self?.update?(new), result { + self?.selected = new + } else { + self?.shake() + } + } + minutesView.keyDown = { [weak self] value, isFirst in + guard let selected = self?.selected else { + return + } + var updatedValue = value + if isFirst { + updatedValue = value + } else { + if selected.minutes > 0, selected.minutes < 10 { + updatedValue = min(Int32("\(selected.minutes)\(updatedValue)")!, 59) + self?.switchToRight() + } + } + let new = TimePickerOption(hours: selected.hours, minutes: updatedValue, seconds: selected.seconds) + self?.selected = new + if let result = self?.update?(new), result { + self?.selected = new + } else { + self?.shake() + } + } + secondsView.keyDown = { [weak self] value, isFirst in + guard let selected = self?.selected else { + return + } + var updatedValue = value + if isFirst { + updatedValue = value + } else { + if selected.seconds > 0, selected.seconds < 10 { + updatedValue = min(Int32("\(selected.seconds)\(updatedValue)")!, 59) + self?.switchToRight() + } + } + let new = TimePickerOption(hours: selected.hours, minutes: selected.minutes, seconds: updatedValue) + self?.selected = new + if let result = self?.update?(new), result { + self?.selected = new + } else { + self?.shake() + } + } + minutesView.next = { [weak self] toRight in + if !toRight { + self?.switchToLeft() + } else { + self?.switchToRight() + } + } + hoursView.next = { [weak self] toRight in + if !toRight { + self?.switchToLeft() + } else { + self?.switchToRight() + } + } + secondsView.next = { [weak self] toRight in + if !toRight { + self?.switchToLeft() + } else { + self?.switchToRight() + } + } + } + + func switchToRight() { + if self.window?.firstResponder == self.hoursView { + self.window?.makeFirstResponder(self.minutesView) + } else if self.window?.firstResponder == self.minutesView { + self.window?.makeFirstResponder(self.secondsView) + } else { + self.window?.makeFirstResponder(self.hoursView) + } + } + func switchToLeft() { + if self.window?.firstResponder == self.secondsView { + self.window?.makeFirstResponder(self.minutesView) + } else if self.window?.firstResponder == self.minutesView { + self.window?.makeFirstResponder(self.hoursView) + } else { + self.window?.makeFirstResponder(self.secondsView) + } + } + + public var firstResponder: NSResponder? { + if self.window?.firstResponder == self.secondsView { + return self.secondsView + } else if self.window?.firstResponder == self.minutesView { + return self.minutesView + } else { + return self.hoursView + } + } + + public override func layout() { + super.layout() + + minutesView.center() + separatorView1.centerY(x: minutesView.frame.minX - separatorView1.frame.width - 2) + hoursView.centerY(x: minutesView.frame.minX - hoursView.frame.width - separatorView1.frame.width - 5) + separatorView2.centerY(x: minutesView.frame.maxX + 3) + secondsView.centerY(x: minutesView.frame.maxX + separatorView1.frame.width + 5) + self.borderView.frame = bounds + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.backgroundColor = presentation.colors.grayBackground + self.layer?.cornerRadius = .cornerRadius + + self.borderView.layer?.borderWidth = .borderSize + self.borderView.layer?.borderColor = presentation.colors.border.cgColor + self.borderView.layer?.cornerRadius = .cornerRadius + + let layout = TextViewLayout(.initialize(string: ":", color: presentation.colors.grayText, font: .normal(.text))) + layout.measure(width: .greatestFiniteMagnitude) + separatorView1.update(layout) + + separatorView2.update(layout) + + needsLayout = true + } + + + public override func updateState() { + super.updateState() + self.updateSelected(animated: true) + needsLayout = true + } + + private func updateSelected(animated: Bool) { + + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override public func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + } + +} diff --git a/submodules/TGUIKit/TGUIKit/DisplayLinkAnimator.swift b/submodules/TGUIKit/TGUIKit/DisplayLinkAnimator.swift new file mode 100644 index 0000000000..1ac1e18df2 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/DisplayLinkAnimator.swift @@ -0,0 +1,118 @@ +// +// DisplayLinkAnimator.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 30/09/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +private final class DisplayLinkTarget: NSObject { + private let f: () -> Void + + init(_ f: @escaping () -> Void) { + self.f = f + } + + @objc func event() { + self.f() + } +} + +public final class DisplayLinkAnimator { + private var displayLink: SwiftSignalKit.Timer! + private let duration: Double + private let fromValue: CGFloat + private let toValue: CGFloat + private let startTime: Double + private let update: (CGFloat) -> Void + private let completion: () -> Void + private var completed = false + + public init(duration: Double, from fromValue: CGFloat, to toValue: CGFloat, update: @escaping (CGFloat) -> Void, completion: @escaping () -> Void) { + self.duration = duration + self.fromValue = fromValue + self.toValue = toValue + self.update = update + self.completion = completion + + self.startTime = CACurrentMediaTime() + + self.displayLink = SwiftSignalKit.Timer.init(timeout: 0.016, repeat: true, completion: { [weak self] in + self?.tick() + }, queue: .mainQueue()) + + self.displayLink.start() + } + + deinit { + self.displayLink.invalidate() + } + + public func invalidate() { + self.displayLink.invalidate() + } + + @objc private func tick() { + if self.completed { + return + } + let timestamp = CACurrentMediaTime() + var t = (timestamp - self.startTime) / self.duration + t = max(0.0, t) + t = min(1.0, t) + self.update(self.fromValue * CGFloat(1 - t) + self.toValue * CGFloat(t)) + if abs(t - 1.0) < Double.ulpOfOne { + self.completed = true + self.displayLink.invalidate() + self.completion() + } + } +} + +public final class ConstantDisplayLinkAnimator { + private var displayLink: SwiftSignalKit.Timer? + private let update: () -> Void + private var completed = false + private let fps: TimeInterval + + public var isPaused: Bool = true { + didSet { + if self.isPaused != oldValue { + if self.isPaused { + self.displayLink?.invalidate() + } else { + + self.displayLink = SwiftSignalKit.Timer(timeout: 1 / fps, repeat: true, completion: { [weak self] in + self?.tick() + }, queue: .mainQueue()) + + self.displayLink?.start() + } + } + } + } + + public init(update: @escaping () -> Void, fps: TimeInterval = 60) { + self.update = update + self.fps = fps + } + + deinit { + self.displayLink?.invalidate() + } + + public func invalidate() { + self.displayLink?.invalidate() + } + + @objc private func tick() { + if self.completed { + return + } + self.update() + } +} + diff --git a/TGUIKit/TGUIKit/DisplayLinkDispatcher.swift b/submodules/TGUIKit/TGUIKit/DisplayLinkDispatcher.swift similarity index 100% rename from TGUIKit/TGUIKit/DisplayLinkDispatcher.swift rename to submodules/TGUIKit/TGUIKit/DisplayLinkDispatcher.swift diff --git a/submodules/TGUIKit/TGUIKit/DraggingView.swift b/submodules/TGUIKit/TGUIKit/DraggingView.swift new file mode 100644 index 0000000000..d2b8edb650 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/DraggingView.swift @@ -0,0 +1,193 @@ +// +// DragController.swift +// Telegram-Mac +// +// Created by keepcoder on 30/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public class DragItem { + let title:String + let desc:String + let handler:()->Void + + let attr:NSAttributedString + + public init(title:String, desc:String,handler:@escaping()->Void) { + self.title = title + self.desc = desc + self.handler = handler + + let attr:NSMutableAttributedString = NSMutableAttributedString() + _ = attr.append(string: title, color: presentation.colors.grayText, font: .normal(.huge)) + _ = attr.append(string: "\n") + + _ = attr.append(string: desc, color: presentation.colors.text, font: .medium(16.0)) + + self.attr = attr.copy() as! NSAttributedString + } +} + +class DragView : OverlayControl { + var item:DragItem + + var textView:TextView = TextView() + + init(item:DragItem) { + self.item = item + super.init(frame: NSZeroRect) + addSubview(textView) + textView.backgroundColor = presentation.colors.background + self.layer?.cornerRadius = .cornerRadius + self.layer?.borderWidth = 2.0 + self.layer?.backgroundColor = presentation.colors.background.cgColor + self.layer?.borderColor = presentation.colors.border.cgColor + self.backgroundColor = presentation.colors.background +// self.set(handler: { control in +// control.layer?.borderColor = presentation.colors.accent.cgColor +// control.layer?.animateBorder() +// }, for: .Hover) +// +// self.set(handler: { control in +// control.layer?.borderColor = presentation.colors.border.cgColor +// control.layer?.animateBorder() +// }, for: .Normal) + + } + + public override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + override func layout() { + super.layout() + let layout:TextViewLayout = TextViewLayout(item.attr, maximumNumberOfLines: 2, truncationType: .middle, alignment:.center) + layout.measure(width: frame.width - 20) + + textView.update(layout) + textView.center() + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +public class DraggingView: SplitView { + + var container:Control = Control() + + public weak var controller:ViewController? + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + container.backgroundColor = .clear + self.registerForDraggedTypes([.string, .tiff, .kUrl, .kFilenames]) + } + + override public func performDragOperation(_ sender: NSDraggingInfo) -> Bool { + + for itemView in container.subviews as! [DragView] { + if itemView.mouseInside() { + itemView.item.handler() + return true + } + } + + return false + } + + + + func layoutItems(with items:[DragItem]) { + container.removeAllSubviews() + + + + let itemSize = NSMakeSize(frame.width - 10, ceil((frame.height - 10 - (5 * (CGFloat(items.count) - 1))) / CGFloat(items.count))) + + var y:CGFloat = 5 + for item in items { + let view:DragView = DragView(item:item) + view.frame = NSMakeRect(5, y, itemSize.width, itemSize.height) + container.addSubview(view) + y += itemSize.height + 5 + + } + + + } + + public override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + private func updateItems() { + for itemView in container.subviews as! [DragView] { + if itemView.mouseInside() { + itemView.layer?.borderColor = presentation.colors.accent.cgColor + itemView.layer?.animateBorder() + } else { + itemView.layer?.borderColor = presentation.colors.border.cgColor + itemView.layer?.animateBorder() + } + } + } + + public override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation { + updateItems() + + return .copy + } + + override public func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { + if let items = controller?.draggingItems(for: sender.draggingPasteboard), items.count > 0, !sender.draggingSourceOperationMask.isEmpty { + + container.frame = bounds + + if container.superview == nil { + layoutItems(with: items) + addSubview(container) + } + container.layer?.removeAllAnimations() + container.layer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + + controller?.draggingEntered() + updateItems() + } + + return .copy + } + + override public func draggingExited(_ sender: NSDraggingInfo?) { + + controller?.draggingExited() + + container.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion:false, completion:{[weak self] (completed) in + if completed { + self?.container.removeFromSuperview() + } + }) + } + + public override func draggingEnded(_ sender: NSDraggingInfo?) { + draggingExited(sender) + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/DrawingContext.swift b/submodules/TGUIKit/TGUIKit/DrawingContext.swift new file mode 100644 index 0000000000..59afa1e562 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/DrawingContext.swift @@ -0,0 +1,412 @@ +// +// DrawingContext.swift +// TGLibrary +// +// Created by keepcoder on 18/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + +public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false, scale: CGFloat = 2.0) -> CGImage? { + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) + + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) + else { + return nil + } + + context.scaleBy(x: scale, y: scale) + + contextGenerator(size, context) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return image +} + +public func generateImageMask(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, scale: CGFloat = 2.0) -> CGImage? { + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue) + + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: bitmapInfo.rawValue) + else { + return nil + } + + context.scaleBy(x: scale, y: scale) + + contextGenerator(size, context) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return image +} + +public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? = 2.0, rotatedContext: (CGSize, CGContext) -> Void) -> CGImage? { + let selectedScale = scale ?? System.backingScale + let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) + + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { + return nil + } + + context.scaleBy(x: selectedScale, y: selectedScale) + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + rotatedContext(size, context) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return image +} + + + +public let deviceColorSpace: CGColorSpace = { + if #available(OSX 10.11.2, *) { + if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) { + return colorSpace + } else { + return CGColorSpaceCreateDeviceRGB() + } + } else { + return CGColorSpaceCreateDeviceRGB() + } +}() + + + +public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> CGImage? { + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = 4 * Int(scaledSize.width) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self) + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + pixelGenerator(scaledSize, bytes) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return image +} + + +public enum DrawingContextBltMode { + case Alpha +} +private var allocCounter: Int = 0 +private var deallocCounter: Int = 0 + +public class DrawingContext { + public let size: CGSize + public let scale: CGFloat + public let scaledSize: CGSize + public let bytesPerRow: Int + private let bitmapInfo: CGBitmapInfo + public let length: Int + + public let bytes: UnsafeMutableRawPointer + private let checkDealloc: Bool + let provider: CGDataProvider? + + private var _context: CGContext? + public private(set) var isHighQuality: Bool = true + + public func withContext(isHighQuality: Bool = true, _ f: (CGContext) -> ()) { + + self.isHighQuality = isHighQuality + if self._context == nil { + if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { + c.scaleBy(x: scale, y: scale) + self._context = c + } + } + + if let _context = self._context { + f(_context) + } + } + + deinit { + if checkDealloc { + deallocCounter += 1 + NSLog("deallocated: \(deallocCounter)") + } + } + + public func withFlippedContext(isHighQuality: Bool = true, horizontal: Bool = false, vertical: Bool = false, _ f: (CGContext) -> ()) { + self.isHighQuality = isHighQuality + + if self._context == nil { + if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { + c.scaleBy(x: scale, y: scale) + self._context = c + } + } + + if let _context = self._context { + _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scaleBy(x: horizontal ? -1.0 : 1.0, y: vertical ? -1.0 : 1.0) + _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) + + f(_context) + + _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scaleBy(x: horizontal ? -1.0 : 1.0, y: vertical ? -1.0 : 1.0) + _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) + } + } + + public init(size: CGSize, scale: CGFloat, clear: Bool = false, checkDealloc: Bool = false) { + self.size = NSMakeSize(max(size.width, 1), max(size.height, 1)) + self.scale = scale + self.checkDealloc = checkDealloc + self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + + + if checkDealloc { + allocCounter += 1 + NSLog("allocCounter: \(allocCounter)") + } + + self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + self.length = bytesPerRow * Int(scaledSize.height) + + self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + + self.bytes = malloc(length)! + if clear { + memset(self.bytes, 0, self.length) + } + self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + } + + public func generateImage() -> CGImage? { + if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) { + return image + } else { + return nil + } + } + + public func colorAt(_ point: CGPoint) -> NSColor { + let x = Int(point.x * self.scale) + let y = Int(point.y * self.scale) + if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { + let srcLine = self.bytes.advanced(by: y * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) + let pixel = srcLine + x + let colorValue = pixel.pointee + return NSColor(UInt32(colorValue)) + } else { + return NSColor.clear + } + } + + public func blt(_ other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { + if abs(other.scale - self.scale) < CGFloat.ulpOfOne { + let srcX = 0 + var srcY = 0 + let dstX = Int(at.x * self.scale) + var dstY = Int(at.y * self.scale) + + let width = min(Int(self.size.width * self.scale) - dstX, Int(other.size.width * self.scale)) + let height = min(Int(self.size.height * self.scale) - dstY, Int(other.size.height * self.scale)) + + let maxDstX = dstX + width + let maxDstY = dstY + height + + switch mode { + case .Alpha: + while dstY < maxDstY { + let srcLine = other.bytes.advanced(by: max(0, srcY) * other.bytesPerRow).assumingMemoryBound(to: UInt32.self) + let dstLine = self.bytes.advanced(by: max(0, dstY) * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) + + var dx = dstX + var sx = srcX + while dx < maxDstX { + let srcPixel = srcLine + sx + let dstPixel = dstLine + dx + + let baseColor = dstPixel.pointee + let baseAlpha = (baseColor >> 24) & 0xff + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + let alpha = min(baseAlpha, srcPixel.pointee >> 24) + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + dstPixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b + + dx += 1 + sx += 1 + } + + dstY += 1 + srcY += 1 + } + } + } + } +} + +public enum ParsingError: Error { + case Generic +} + +public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { + let begin = index + var seenPoint = false + while index <= end { + let c = index.pointee + index = index.successor() + + if c == 46 { // . + if seenPoint { + throw ParsingError.Generic + } else { + seenPoint = true + } + } else if c == separator { + break + } else if !((c >= 48 && c <= 57) || c == 45 || c == 101 || c == 69) { + throw ParsingError.Generic + } + } + + if index == begin { + throw ParsingError.Generic + } + + if let value = NSString(bytes: UnsafeRawPointer(begin), length: index - begin, encoding: String.Encoding.utf8.rawValue)?.floatValue { + return CGFloat(value) + } else { + throw ParsingError.Generic + } +} +public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove: Bool = false) throws { + var index: UnsafePointer = path.utf8Start + let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount) + while index < end { + let c = index.pointee + index = index.successor() + + if c == 77 { // M + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Move to \(x), \(y)") + context.move(to: CGPoint(x: x, y: y)) + } else if c == 76 { // L + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Line to \(x), \(y)") + context.addLine(to: CGPoint(x: x, y: y)) + + if strokeOnMove { + context.strokePath() + context.move(to: CGPoint(x: x, y: y)) + } + } else if c == 67 { // C + let x1 = try readCGFloat(&index, end: end, separator: 44) + let y1 = try readCGFloat(&index, end: end, separator: 32) + let x2 = try readCGFloat(&index, end: end, separator: 44) + let y2 = try readCGFloat(&index, end: end, separator: 32) + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + context.addCurve(to: CGPoint(x: x, y: y), control1: CGPoint(x: x1, y: y1), control2: CGPoint(x: x2, y: y2)) + + //print("Line to \(x), \(y)") + if strokeOnMove { + context.strokePath() + context.move(to: CGPoint(x: x, y: y)) + } + } else if c == 90 { // Z + if index != end && index.pointee != 32 { + throw ParsingError.Generic + } + + //CGContextClosePath(context) + context.fillPath() + //CGContextBeginPath(context) + //print("Close") + } else if c == 83 { // S + if index != end && index.pointee != 32 { + throw ParsingError.Generic + } + + //CGContextClosePath(context) + context.strokePath() + //CGContextBeginPath(context) + //print("Close") + } else if c == 32 { // space + continue + } else { + throw ParsingError.Generic + } + } +} diff --git a/TGUIKit/TGUIKit/EditableViewController.swift b/submodules/TGUIKit/TGUIKit/EditableViewController.swift similarity index 100% rename from TGUIKit/TGUIKit/EditableViewController.swift rename to submodules/TGUIKit/TGUIKit/EditableViewController.swift diff --git a/submodules/TGUIKit/TGUIKit/Extensions.swift b/submodules/TGUIKit/TGUIKit/Extensions.swift new file mode 100644 index 0000000000..3e8195a42e --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Extensions.swift @@ -0,0 +1,2023 @@ +// +// Extensions.swift +// TGUIKit +// +// Created by keepcoder on 08/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Foundation + + +public extension NSAttributedString { + + func CTSize(_ width:CGFloat, framesetter:CTFramesetter?) -> (CTFramesetter,NSSize) { + + var fs = framesetter + + if fs == nil { + fs = CTFramesetterCreateWithAttributedString(self); + } + + var textSize:CGSize = CTFramesetterSuggestFrameSizeWithConstraints(fs!, CFRangeMake(0,self.length), nil, NSMakeSize(width, CGFloat.greatestFiniteMagnitude), nil); + + textSize.width = ceil(textSize.width) + textSize.height = ceil(textSize.height) + + return (fs!,textSize); + + } + + var trimmed: NSAttributedString { + + let string:NSMutableAttributedString = self.mutableCopy() as! NSMutableAttributedString + + while true { + let range = string.string.nsstring.range(of: "\u{2028}") + if range.location != NSNotFound { + string.replaceCharacters(in: range, with: "\n") + } else { + break + } + } + while true { + let range = string.string.nsstring.range(of: "\u{fffc}", options: .literal) + if range.location != NSNotFound { + string.replaceCharacters(in: range, with: "\n") + } else { + break + } + } + var range = string.string.nsstring.rangeOfCharacter(from: NSCharacterSet.whitespacesAndNewlines) + while !string.string.isEmpty, range.location == 0 { + string.replaceCharacters(in: NSMakeRange(0, 1), with: "") + range = string.string.nsstring.rangeOfCharacter(from: NSCharacterSet.whitespacesAndNewlines) + } + while !string.string.isEmpty, string.string.rangeOfCharacter(from: NSCharacterSet.whitespacesAndNewlines, options: [], range: string.string.index(string.string.endIndex, offsetBy: -1) ..< string.string.endIndex) != nil { + string.replaceCharacters(in: NSMakeRange(string.string.length - 1, 1), with: "") + } + + return string + } + + + var range:NSRange { + return NSMakeRange(0, self.length) + } + + func trimRange(_ range:NSRange) -> NSRange { + let loc:Int = min(range.location,self.length) + let length:Int = min(range.length, self.length - loc) + return NSMakeRange(loc, length) + } + + static func initialize(string:String?, color:NSColor? = nil, font:NSFont? = nil, coreText:Bool = true) -> NSAttributedString { + let attr:NSMutableAttributedString = NSMutableAttributedString() + _ = attr.append(string: string, color: color, font: font, coreText: true) + + return attr.copy() as! NSAttributedString + } + public convenience init(string: String, font: NSFont? = nil, textColor: NSColor = NSColor.black, paragraphAlignment: NSTextAlignment? = nil) { + var attributes: [NSAttributedString.Key: AnyObject] = [:] + if let font = font { + attributes[.font] = font + } + attributes[.foregroundColor] = textColor + if let paragraphAlignment = paragraphAlignment { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = paragraphAlignment + attributes[.paragraphStyle] = paragraphStyle + } + self.init(string: string, attributes: attributes) + } + + +} + +public extension String { + + + static func prettySized(with size:Int, afterDot: Int8 = 1, removeToken: Bool = false) -> String { + var converted:Double = Double(size) + var factor:Int = 0 + + let tokens:[String] = ["B", "KB", "MB", "GB", "TB"] + + while converted >= 1024.0 { + converted /= 1024.0 + factor += 1 + } + + if factor == 0 { + //converted = 0 + } + //factor = Swift.max(1,factor) + + if ceil(converted) - converted != 0.0 || removeToken { + if removeToken { + return String(format: "%.\(afterDot)f", converted) + } else { + return String(format: "%.\(afterDot)f%@", converted, tokens[factor]) + } + } else { + if removeToken { + return String(format: "%.0f", converted) + } else { + return String(format: "%.0f%@", converted, tokens[factor]) + } + } + + } + + var trimmed:String { + + var string:String = self + string = string.replacingOccurrences(of: "\u{2028}", with: "\n") + while !string.isEmpty, let index = string.rangeOfCharacter(from: NSCharacterSet.whitespacesAndNewlines), index.lowerBound == string.startIndex { + string = String(string[index.upperBound.. 2 { +// copy = String(copy[.. NSRange { + + if(string == nil) { + return NSMakeRange(0, 0) + } + + let slength:Int = self.length + + + var range:NSRange + + self.append(NSAttributedString(string: string!)) + let nlength:Int = self.length - slength + range = NSMakeRange(self.length - nlength, nlength) + + if let c = color { + self.addAttribute(NSAttributedString.Key.foregroundColor, value: c, range:range ) + } + + if let f = font { + if coreText { + self.setCTFont(font: f, range: range) + } + self.setFont(font: f, range: range) + } + + + return range + + } + + func fixEmojiesFont(_ fontSize: CGFloat) { + let nsString = self.string.nsstring + for i in 0 ..< min(nsString.length, 300) { + let sub = nsString.substring(with: NSMakeRange(i, 1)) + if sub.containsOnlyEmoji, let font = NSFont(name: "AppleColorEmoji", size: fontSize) { + self.addAttribute(.font, value: font, range: NSMakeRange(i, 1)) + } + } + } + + func add(link:Any, for range:NSRange, color: NSColor = presentation.colors.link) { + self.addAttribute(NSAttributedString.Key.link, value: link, range: range) + self.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range) + } + + func setCTFont(font:NSFont, range:NSRange) -> Void { + self.addAttribute(NSAttributedString.Key(kCTFontAttributeName as String), value: CTFontCreateWithFontDescriptor(font.fontDescriptor, 0, nil), range: range) + } + + func setSelected(color:NSColor,range:NSRange) -> Void { + self.addAttribute(.selectedColor, value: color, range: range) + } + + + func setFont(font:NSFont, range:NSRange) -> Void { + self.addAttribute(NSAttributedString.Key.font, value: font, range: range) + } + +} + + +public extension CALayer { + + func disableActions() -> Void { + + self.actions = ["onOrderIn":NSNull(),"sublayers":NSNull(),"bounds":NSNull(),"frame":NSNull(), "background":NSNull(), "position":NSNull(),"contents":NSNull(),"backgroundColor":NSNull(),"border":NSNull(), "shadowOffset": NSNull()] + removeAllAnimations() + } + + + func animateBackground() ->Void { + let animation = CABasicAnimation(keyPath: "backgroundColor") + animation.duration = 0.2 + self.add(animation, forKey: "backgroundColor") + } + + + + func animateBorder() ->Void { + let animation = CABasicAnimation(keyPath: "borderWidth") + animation.duration = 0.2 + self.add(animation, forKey: "borderWidth") + } + + func animateContents() ->Void { + let animation = CABasicAnimation(keyPath: "contents") + animation.duration = 0.2 + self.add(animation, forKey: "contents") + } + +} + +public extension String { + + var nsstring:NSString { + return self as NSString + } + + var length:Int { + return self.nsstring.length + } +} + + +public extension NSView { + + var snapshot: NSImage { + guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { return NSImage() } + cacheDisplay(in: bounds, to: bitmapRep) + let image = NSImage() + image.addRepresentation(bitmapRep) + bitmapRep.size = bounds.size + return NSImage(data: dataWithPDF(inside: bounds))! + } + + var subviewsSize: NSSize { + var size: NSSize = NSZeroSize + for subview in subviews { + size.width += subview.frame.width + size.height += subview.frame.height + } + return size + } + private func isInSuperclassView(_ superclass: AnyClass, view: NSView) -> Bool { + if view.isKind(of: superclass) { + return true + } else if let view = view.superview { + return isInSuperclassView(superclass, view: view) + } else { + return false + } + } + func isInSuperclassView(_ superclass: AnyClass) -> Bool { + return isInSuperclassView(superclass, view: self) + } + + func _mouseInside() -> Bool { + if let window = self.window { + var location:NSPoint = window.mouseLocationOutsideOfEventStream + location = self.convert(location, from: nil) + + if let view = window.contentView!.hitTest(window.mouseLocationOutsideOfEventStream) { + if let view = view as? View { + if view.isEventLess { + return NSPointInRect(location, self.bounds) + } + } else if let view = view as? ImageView, view.isEventLess { + return NSPointInRect(location, self.bounds) + } + if view == self { + return NSPointInRect(location, self.bounds) + } else { + var s = view.superview + if let view = view as? NSTableView { + let somePoint = view.convert(window.mouseLocationOutsideOfEventStream, from: nil) + let row = view.row(at: somePoint) + if row >= 0 { + let someView = view.rowView(atRow: row, makeIfNecessary: false) + if let someView = someView { + let hit = someView.hitTest(someView.convert(window.mouseLocationOutsideOfEventStream, from: nil)) + return hit == self + } + } + } + while let sv = s { + if sv == self { + return NSPointInRect(location, self.bounds) + } + s = sv.superview + } + } + } else { + var bp:Int = 0 + bp += 1 + } + + } + return false + } + + var backingScaleFactor: CGFloat { + if let window = window { + return window.backingScaleFactor + } else { + return System.backingScale + } + } + + func removeAllSubviews() -> Void { + var filtered = self.subviews.filter { view -> Bool in + if let view = view as? View { + return !view.noWayToRemoveFromSuperview + } else { + return true + } + } + while (filtered.count > 0) { + filtered.removeFirst().removeFromSuperview() + } + } + + func isInnerView(_ view:NSView?) -> Bool { + var inner = false + for i in 0 ..< subviews.count { + inner = subviews[i] == view + if !inner && !subviews[i].subviews.isEmpty { + inner = subviews[i].isInnerView(view) + } + if inner { + break + } + } + return inner + } + + func setFrameSize(_ width:CGFloat, _ height:CGFloat) { + self.setFrameSize(NSMakeSize(width, height)) + } + + func setFrameOrigin(_ x:CGFloat, _ y:CGFloat) { + self.setFrameOrigin(NSMakePoint(x, y)) + } + + var background:NSColor { + get { + if let view = self as? View { + return view.backgroundColor + } + if let backgroundColor = layer?.backgroundColor { + return NSColor(cgColor: backgroundColor) ?? .white + } + return .white + } + set { + if let view = self as? View { + view.backgroundColor = newValue + } else { + self.layer?.backgroundColor = newValue.cgColor + } + } + } + + func centerX(_ superView:NSView? = nil, y:CGFloat? = nil, addition: CGFloat = 0) -> Void { + + var x:CGFloat = 0 + + if let sv = superView { + x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) + } else if let sv = self.superview { + x = CGFloat(roundf(Float((sv.frame.width - frame.width)/2.0))) + } + + + self.setFrameOrigin(NSMakePoint(x + addition, y == nil ? NSMinY(self.frame) : y!)) + } + + func focus(_ size:NSSize) -> NSRect { + var x:CGFloat = 0 + var y:CGFloat = 0 + + x = CGFloat(round((frame.width - size.width)/2.0)) + y = CGFloat(round((frame.height - size.height)/2.0)) + + + return NSMakeRect(x, y, size.width, size.height) + } + + func focus(_ size:NSSize, inset:NSEdgeInsets) -> NSRect { + let x:CGFloat = CGFloat(round((frame.width - size.width + (inset.left + inset.right))/2.0)) + let y:CGFloat = CGFloat(round((frame.height - size.height + (inset.top + inset.bottom))/2.0)) + return NSMakeRect(x, y, size.width, size.height) + } + + func centerY(_ superView:NSView? = nil, x:CGFloat? = nil, addition: CGFloat = 0) -> Void { + + var y:CGFloat = 0 + + if let sv = superView { + y = CGFloat(round((sv.frame.height - frame.height)/2.0)) + } else if let sv = self.superview { + y = CGFloat(round((sv.frame.height - frame.height)/2.0)) + } + + self.setFrameOrigin(NSMakePoint(x ?? frame.minX, y + addition)) + } + + + func center(_ superView:NSView? = nil) -> Void { + + var x:CGFloat = 0 + var y:CGFloat = 0 + + if let sv = superView { + x = CGFloat(round((sv.frame.width - frame.width)/2.0)) + y = CGFloat(round((sv.frame.height - frame.height)/2.0)) + } else if let sv = self.superview { + x = CGFloat(round((sv.frame.width - frame.width)/2.0)) + y = CGFloat(round((sv.frame.height - frame.height)/2.0)) + } + + self.setFrameOrigin(NSMakePoint(x, y)) + + } + + + func _change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, additive: Bool = false, forceAnimateIfHasAnimation: Bool = false, completion:((Bool)->Void)? = nil) -> Void { + if animated || (forceAnimateIfHasAnimation && self.layer?.animation(forKey:"position") != nil) { + + var presentX = NSMinX(self.frame) + var presentY = NSMinY(self.frame) + let presentation:CALayer? = self.layer?.presentation() + if let presentation = presentation, let _ = self.layer?.animation(forKey:"position") { + presentY = presentation.frame.minY + presentX = presentation.frame.minX + } + self.layer?.animatePosition(from: NSMakePoint(presentX, presentY), to: position, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + + } else { + self.layer?.removeAnimation(forKey: "position") + } + if save { + self.setFrameOrigin(position) + if let completion = completion, !animated { + completion(true) + } + } + + } + + func shake(beep: Bool = true) { + let a:CGFloat = 3 + if let layer = layer { + self.layer?.shake(0.04, from:NSMakePoint(-a + layer.position.x,layer.position.y), to:NSMakePoint(a + layer.position.x, layer.position.y)) + } + if beep { + NSSound.beep() + } + } + + func _change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + if animated { + var presentBounds:NSRect = self.layer?.bounds ?? self.bounds + let presentation = self.layer?.presentation() + if let presentation = presentation, self.layer?.animation(forKey:"bounds") != nil { + presentBounds.size.width = NSWidth(presentation.bounds) + presentBounds.size.height = NSHeight(presentation.bounds) + } + self.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, 0, size.width, size.height), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) + + + } else { + self.layer?.removeAnimation(forKey: "bounds") + } + if save { + self.frame = NSMakeRect(NSMinX(self.frame), NSMinY(self.frame), size.width, size.height) + } + } + + func _changeBounds(from: NSRect, to: NSRect, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + + + if save { + self.bounds = to + } + + if from == to { + completion?(true) + return + } + + if animated { + self.layer?.animateBounds(from: from, to: to, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) + + } else { + self.layer?.removeAnimation(forKey: "bounds") + } + + if !animated { + completion?(true) + } + } + + func _change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + if animated { + if let layer = self.layer { + var opacity:CGFloat = CGFloat(layer.opacity) + if let presentation = self.layer?.presentation(), self.layer?.animation(forKey:"opacity") != nil { + opacity = CGFloat(presentation.opacity) + } + + layer.animateAlpha(from: opacity, to: to, duration:duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, completion: completion) + } + + + } else { + layer?.removeAnimation(forKey: "opacity") + } + if save { + self.layer?.opacity = Float(to) + if let completion = completion, !animated { + completion(true) + } + } + } + + func disableHierarchyInteraction() -> Void { + for sub in self.subviews { + if let sub = sub as? View, sub.interactionStateForRestore == nil { + sub.interactionStateForRestore = sub.userInteractionEnabled + sub.userInteractionEnabled = false + } + sub.disableHierarchyInteraction() + } + } + func restoreHierarchyInteraction() -> Void { + for sub in self.subviews { + if let sub = sub as? View, let resporeState = sub.interactionStateForRestore { + sub.userInteractionEnabled = resporeState + sub.interactionStateForRestore = nil + } else if let sub = sub as? TableRowView, let resporeState = sub.interactionStateForRestore { + sub.userInteractionEnabled = resporeState + sub.interactionStateForRestore = nil + } + sub.restoreHierarchyInteraction() + } + } + + func restoreHierarchyDynamicContent() -> Void { + for sub in self.subviews { + if let sub = sub as? View, let resporeState = sub.dynamicContentStateForRestore { + sub.isDynamicContentLocked = resporeState + sub.dynamicContentStateForRestore = nil + } else if let sub = sub as? TableRowView, let resporeState = sub.dynamicContentStateForRestore { + sub.isDynamicContentLocked = resporeState + sub.dynamicContentStateForRestore = nil + } + sub.restoreHierarchyDynamicContent() + } + } + + func disableHierarchyDynamicContent() -> Void { + for sub in self.subviews { + if let sub = sub as? View, sub.interactionStateForRestore == nil { + sub.dynamicContentStateForRestore = sub.isDynamicContentLocked + sub.isDynamicContentLocked = true + } else if let sub = sub as? TableRowView, sub.interactionStateForRestore == nil { + sub.dynamicContentStateForRestore = sub.isDynamicContentLocked + sub.isDynamicContentLocked = true + } + sub.disableHierarchyDynamicContent() + } + } + + + +} + + + + +public extension NSTableView.AnimationOptions { + static var none: NSTableView.AnimationOptions { get { + return NSTableView.AnimationOptions(rawValue: 0) + } + } + +} + +public extension CGSize { + func fitted(_ size: CGSize) -> CGSize { + var fittedSize = self + if fittedSize.width > size.width { + fittedSize = CGSize(width: size.width, height: ceil((fittedSize.height * size.width / max(fittedSize.width, 1.0)))) + } + if fittedSize.height > size.height { + fittedSize = CGSize(width: ceil((fittedSize.width * size.height / max(fittedSize.height, 1.0))), height: size.height) + } + return fittedSize + } + + func fit(_ maxSize: CGSize) -> CGSize { + var size = self + if self.width < 1.0 { + return CGSize() + } + if self.height < 1.0 { + return CGSize() + } + + if size.width > maxSize.width { + size.height = floor((size.height * maxSize.width / size.width)); + size.width = maxSize.width; + } + if size.height > maxSize.height { + size.width = floor((size.width * maxSize.height / size.height)); + size.height = maxSize.height; + } + return size; + } + + func cropped(_ size: CGSize) -> CGSize { + return CGSize(width: min(size.width, self.width), height: min(size.height, self.height)) + } + + func fittedToArea(_ area: CGFloat) -> CGSize { + if self.height < 1.0 || self.width < 1.0 { + return CGSize() + } + let aspect = self.width / self.height + let height = sqrt(area / aspect) + let width = aspect * height + return CGSize(width: ceil(width), height: ceil(height)) + } + + func aspectFilled(_ size: CGSize) -> CGSize { + let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) + return CGSize(width: ceil(self.width * scale), height: ceil(self.height * scale)) + } + func fittedToWidthOrSmaller(_ width: CGFloat) -> CGSize { + let scale = min(1.0, width / max(1.0, self.width)) + return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + } + + func aspectFitted(_ size: CGSize) -> CGSize { + let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) + return CGSize(width: ceil(self.width * scale), height: ceil(self.height * scale)) + } + + func multipliedByScreenScale() -> CGSize { + let scale:CGFloat = System.backingScale + return CGSize(width: self.width * scale, height: self.height * scale) + } + + func dividedByScreenScale() -> CGSize { + let scale:CGFloat = System.backingScale + return CGSize(width: self.width / scale, height: self.height / scale) + } +} + +public extension NSImage { + + func precomposed(_ color:NSColor? = nil, bottomColor: NSColor? = nil, flipVertical:Bool = false, flipHorizontal:Bool = false) -> CGImage { + + let drawContext:DrawingContext = DrawingContext(size: self.size, scale: 2.0, clear: true) + + + let make:(CGContext) -> Void = { [weak self] ctx in + + guard let image = self else { return } + + let rect = NSMakeRect(0, 0, drawContext.size.width, drawContext.size.height) + ctx.interpolationQuality = .high + ctx.clear(rect) + + var imageRect:CGRect = NSMakeRect(0, 0, image.size.width, image.size.height) + + let cimage = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) + + if let color = color { + ctx.clip(to: rect, mask: cimage!) + if let bottomColor = bottomColor { + let colors = [color, bottomColor] + let rect = NSMakeRect(0, 0, rect.width, rect.height) + let gradientColors = colors.reversed().map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: rect.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + } else { + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + } + + } else { + ctx.draw(cimage!, in: imageRect) + } + + } + + drawContext.withFlippedContext(horizontal: flipHorizontal, vertical: flipVertical, make) + + + + + return drawContext.generateImage()! + +// var image:NSImage = self.copy() as! NSImage +// if let color = color { +// image.lockFocus() +// color.set() +// var imageRect = NSMakeRect(0, 0, image.size.width * 2.0, image.size.height * 2.0) +// NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop) +// image.unlockFocus() +// } + + // return roundImage(image.tiffRepresentation!, self.size, cornerRadius: 0, reversed:reversed)! + } + +} + +public extension CGRect { + var topLeft: CGPoint { + return self.origin + } + + var topRight: CGPoint { + return CGPoint(x: self.maxX, y: self.minY) + } + + var bottomLeft: CGPoint { + return CGPoint(x: self.minX, y: self.maxY) + } + + var bottomRight: CGPoint { + return CGPoint(x: self.maxX, y: self.maxY) + } + + var center: CGPoint { + return CGPoint(x: self.midX, y: self.midY) + } + func focus(_ size:NSSize) -> NSRect { + var x:CGFloat = 0 + var y:CGFloat = 0 + + x = CGFloat(round((self.width - size.width)/2.0)) + y = CGFloat(round((self.height - size.height)/2.0)) + + + return NSMakeRect(x, y, size.width, size.height) + } +} + +public extension CGPoint { + func offsetBy(dx: CGFloat, dy: CGFloat) -> CGPoint { + return CGPoint(x: self.x + dx, y: self.y + dy) + } +} + + +public enum ImageOrientation { + case up + case down + case left + case right + case upMirrored + case downMirrored + case leftMirrored + case rightMirrored +} + + +public extension CGImage { + + var backingSize:NSSize { + return NSMakeSize(CGFloat(width) / 2.0, CGFloat(height) / 2.0) + } + + var size:NSSize { + return NSMakeSize(CGFloat(width), CGFloat(height)) + } + + var systemSize:NSSize { + return NSMakeSize(CGFloat(width) / System.backingScale, CGFloat(height) / System.backingScale) + } + + var backingBounds: NSRect { + return NSMakeRect(0, 0, backingSize.width, backingSize.height) + } + + var scale:CGFloat { + return 2.0 + } + + var _NSImage: NSImage { + return NSImage(cgImage: self, size: backingSize) + } + + + func createMatchingBackingDataWithImage(orienation: ImageOrientation) -> CGImage? + { + var orientedImage: CGImage? + let imageRef = self + let originalWidth = imageRef.width + let originalHeight = imageRef.height + + + + + var degreesToRotate: Double + var swapWidthHeight: Bool + var mirrored: Bool + switch orienation { + case .up: + degreesToRotate = 0.0 + swapWidthHeight = false + mirrored = false + break + case .upMirrored: + degreesToRotate = 0.0 + swapWidthHeight = false + mirrored = true + break + case .right: + degreesToRotate = 90.0 + swapWidthHeight = true + mirrored = false + break + case .rightMirrored: + degreesToRotate = 90.0 + swapWidthHeight = true + mirrored = true + break + case .down: + degreesToRotate = 180.0 + swapWidthHeight = false + mirrored = false + break + case .downMirrored: + degreesToRotate = 180.0 + swapWidthHeight = false + mirrored = true + break + case .left: + degreesToRotate = -90.0 + swapWidthHeight = true + mirrored = false + break + case .leftMirrored: + degreesToRotate = -90.0 + swapWidthHeight = true + mirrored = true + break + } + let radians = degreesToRotate * Double.pi / 180.0 + + var width: Int + var height: Int + if swapWidthHeight { + width = originalHeight + height = originalWidth + } else { + width = originalWidth + height = originalHeight + } + + let bytesPerRow = (4 * Int(swapWidthHeight ? imageRef.height : imageRef.width) + 15) & (~15) + let bitsPerComponent = 8 + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + + + let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) + contextRef?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0) + if mirrored { + contextRef?.scaleBy(x: -1.0, y: 1.0) + } + contextRef?.rotate(by: CGFloat(radians)) + if swapWidthHeight { + contextRef?.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0) + } else { + contextRef?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0) + } + contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight))) + orientedImage = contextRef?.makeImage() + + return orientedImage + } +} + +extension Array { + static func fromCFArray(records : CFArray?) -> Array? { + var result: [Element]? + if let records = records { + for i in 0.. CGPath { + + let path = CGMutablePath() + + let minx:CGFloat = 0, midx = frame.width/2.0, maxx = frame.width + let miny:CGFloat = frame.height, midy = frame.height/2.0, maxy: CGFloat = 0 + + path.move(to: NSMakePoint(minx, midy)) + + var topLeftRadius: CGFloat = 0 + var bottomLeftRadius: CGFloat = 0 + var topRightRadius: CGFloat = 0 + var bottomRightRadius: CGFloat = 0 + + + if rectCorner.contains(.topLeft) { + topLeftRadius = cornerRadius + } + if rectCorner.contains(.topRight) { + topRightRadius = cornerRadius + } + if rectCorner.contains(.bottomLeft) { + bottomLeftRadius = cornerRadius + } + if rectCorner.contains(.bottomRight) { + bottomRightRadius = cornerRadius + } + + + + path.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: bottomLeftRadius) + path.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: bottomRightRadius) + path.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: topRightRadius) + path.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: topLeftRadius) + + if rectCorner.contains(.topLeft) { + path.move(to: NSMakePoint(minx, cornerRadius)) + } else { + path.move(to: NSMakePoint(minx, maxy)) + } + + + path.addLine(to: NSMakePoint(minx, midy)) + + //cgPath.closePath() + return path + //cgPath.clip() + } +} + + + +public extension NSRange { + var min:Int { + return self.location + } + var max:Int { + return self.location + self.length + } + func indexIn(_ index: Int) -> Bool { + return NSLocationInRange(index, self) + } +} + +public extension NSBezierPath { + var cgPath: CGPath { + let path = CGMutablePath() + var points = [CGPoint](repeating: .zero, count: 3) + for i in 0 ..< self.elementCount { + let type = self.element(at: i, associatedPoints: &points) + switch type { + case .moveTo: path.move(to: points[0]) + case .lineTo: path.addLine(to: points[0]) + case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1]) + case .closePath: path.closeSubpath() + } + } + return path + } + +} + + +public extension NSRect { + + func apply(multiplier: NSSize) -> NSRect { + return NSMakeRect(round(minX * multiplier.width), round(minY * multiplier.height), round(width * multiplier.width), round(height * multiplier.height)) + } + + func rotate90Degress(parentSize: NSSize) -> NSRect { + + + + let width: CGFloat = parentSize.width + let height: CGFloat = parentSize.height + + + let transform = NSAffineTransform() + + // transform.translateX(by: 0, yBy: height) + + transform.rotate(byDegrees: 90) + transform.translateX(by: 0, yBy: -height) + + + //transform.scaleX(by: 1, yBy: -1) + + let path = NSBezierPath() + path.appendRect(NSMakeRect(0, 0, width, height)) + path.appendRect(self) + + let newPath = transform.transform(path) + + var rect = NSMakeRect(0, 0, self.height, self.width) + for i in 5 ..< newPath.elementCount - 2 { + var points = [NSPoint](repeating: NSZeroPoint, count: 1) + + switch newPath.element(at: i, associatedPoints: &points) { + case .moveTo: + let point = points[0] + rect.origin.x = (height - point.x) + rect.origin.y = (width - point.y - self.width) + case .lineTo: + break + case .curveTo: + break + case .closePath: + break + } + } + + + + return rect + } +} + +public extension NSEdgeInsets { + + public init(left:CGFloat = 0, right:CGFloat = 0, top:CGFloat = 0, bottom:CGFloat = 0) { + self.init(top: top, left: left, bottom: bottom, right: right) + } +} + +public extension NSColor { + + convenience init?(hexString: String) { + let scanner = Scanner(string: hexString.prefix(7)) + if hexString.hasPrefix("#") { + scanner.scanLocation = 1 + } + var num: UInt32 = 0 + var alpha: CGFloat = 1.0 + let checkSet = CharacterSet(charactersIn: "#0987654321abcdef") + for char in hexString.lowercased().unicodeScalars { + if !checkSet.contains(char) { + return nil + } + } + if scanner.scanHexInt32(&num), hexString.length >= 7 && hexString.length <= 9 { + if hexString.length == 9 { + let scanner = Scanner(string: hexString) + scanner.scanLocation = 7 + var intAlpha: UInt32 = 0 + scanner.scanHexInt32(&intAlpha) + alpha = CGFloat(intAlpha) / 255 + } + self.init(num, alpha) + } else { + return nil + } + } + + + convenience init(_ rgbValue:UInt32, _ alpha:CGFloat = 1.0) { + let r: CGFloat = ((CGFloat)((rgbValue & 0xFF0000) >> 16)) + let g: CGFloat = ((CGFloat)((rgbValue & 0xFF00) >> 8)) + let b: CGFloat = ((CGFloat)(rgbValue & 0xFF)) + self.init(srgbRed: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha) + // self.init(deviceRed: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha) + } + + var hexString: String { + // Get the red, green, and blue components of the color + var r :CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + + + let color = self.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + + + var rInt, gInt, bInt, aInt: Int + var rHex, gHex, bHex: String + + var hexColor: String + + color.getRed(&r, green: &g, blue: &b, alpha: &a) + + // println("R: \(r) G: \(g) B:\(b) A:\(a)") + + // Convert the components to numbers (unsigned decimal integer) between 0 and 255 + rInt = Int(round(r * 255.0)) + gInt = Int(round(g * 255.0)) + bInt = Int(round(b * 255.0)) + + // Convert the numbers to hex strings + rHex = rInt == 0 ? "00" : NSString(format:"%2X", rInt) as String + gHex = gInt == 0 ? "00" : NSString(format:"%2X", gInt) as String + bHex = bInt == 0 ? "00" : NSString(format:"%2X", bInt) as String + + rHex = rHex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + gHex = gHex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + bHex = bHex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + + if rHex.length == 1 { + rHex = "0\(rHex)" + } + if gHex.length == 1 { + gHex = "0\(gHex)" + } + if bHex.length == 1 { + bHex = "0\(bHex)" + } + + hexColor = rHex + gHex + bHex + if a < 1 { + return "#" + hexColor + ":\(String(format: "%.2f", Double(a * 100 / 100)))" + } else { + return "#" + hexColor + } + } + +} + +public extension Int { + + func prettyFormatter(_ n: Int, iteration: Int) -> String { + let keys = ["K", "M", "B", "T"] + let d = Double((n / 100)) / 10.0 + let isRound:Bool = (Int(d) * 10) % 10 == 0 + if d < 1000 { + if d == 1 { + return "\(Int(d))\(keys[iteration])" + } else { + var result = "\((d > 99.9 || isRound || (!isRound && d > 9.99)) ? d * 10 / 10 : d)" + if result.hasSuffix(".0") { + result = result.prefix(result.count - 2) + } + return result + "\(keys[iteration])" + } + } + else { + return self.prettyFormatter(Int(d), iteration: iteration + 1) + } + } + + var prettyNumber:String { + if self < 1000 { + return "\(self)" + } + + return self.prettyFormatter(self, iteration: 0).replacingOccurrences(of: ".", with: Locale.current.decimalSeparator ?? ".") + } + var separatedNumber: String { + if self < 1000 { + return "\(self)" + } + let string = "\(self)" + + let length: Int = string.length + var result:String = "" + var index:Int = 0 + while index < length { + let modulo = length % 3 + if index == 0 && modulo != 0 { + result = string.nsstring.substring(with: NSMakeRange(index, modulo)) + index += modulo + } else { + let count:Int = 3 + let value = string.nsstring.substring(with: NSMakeRange(index, count)) + if index == 0 { + result = value + } else { + result += " " + value + } + index += count + } + } + return result + } +} + + +public extension NSProgressIndicator { + func set(color:NSColor) { + let color = color.usingColorSpace(NSColorSpace.sRGB) + + let colorPoly = CIFilter(name: "CIColorPolynomial") + if let colorPoly = colorPoly, let color = color { + colorPoly.setDefaults() + let redVector = CIVector(x: color.redComponent, y: 0, z: 0, w: 0) + let greenVector = CIVector(x: color.greenComponent, y: 0, z: 0, w: 0) + let blueVector = CIVector(x: color.blueComponent, y: 0, z: 0, w: 0) + + colorPoly.setValue(redVector, forKey: "inputRedCoefficients") + colorPoly.setValue(greenVector, forKey: "inputGreenCoefficients") + colorPoly.setValue(blueVector, forKey: "inputBlueCoefficients") + self.contentFilters = [colorPoly] + } + } +} + +public extension String { + func prefix(_ by:Int) -> String { + if let index = index(startIndex, offsetBy: by, limitedBy: endIndex) { + return String(self[.. String { + if let index = index(startIndex, offsetBy: by, limitedBy: endIndex) { + var new = String(self[.. String { + if let index = index(startIndex, offsetBy: by, limitedBy: endIndex) { + return String(self[index.. String { + let h = elapsed / 3600 + let m = (elapsed / 60) % 60 + let s = elapsed % 60 + + if h > 0 { + return String.init(format: "%d:%02d:%02d", h, m, s) + } else { + return String.init(format: "%02d:%02d", m, s) + } + } + + +} + +public extension NSTextField { + func setSelectionRange(_ range: NSRange) { + textView?.setSelectedRange(range) + } + + var selectedRange: NSRange { + if let textView = textView { + return textView.selectedRange + } + return NSMakeRange(0, 0) + } + + func setCursorToEnd() { + self.setSelectionRange(NSRange(location: self.stringValue.length, length: 0)) + } + + func setCursorToStart() { + self.setSelectionRange(NSRange(location: 0, length: 0)) + } + + var textView:NSTextView? { + let textView = (self.window?.fieldEditor(true, for: self) as? NSTextView) + textView?.backgroundColor = .clear + textView?.drawsBackground = true + return textView + } +} + +public extension NSTextView { + func selectAllText() { + setSelectedRange(NSMakeRange(0, self.string.length)) + } + + func appendText(_ text: String) -> Void { + let inputText = self.attributedString().mutableCopy() as! NSMutableAttributedString + + if selectedRange.upperBound - selectedRange.lowerBound > 0 { + inputText.replaceCharacters(in: NSMakeRange(selectedRange.lowerBound, selectedRange.upperBound - selectedRange.lowerBound), with: NSAttributedString(string: text)) + } else { + inputText.insert(NSAttributedString(string: text), at: selectedRange.lowerBound) + } + self.string = inputText.string + } +} + + + +public extension String { + var emojiSkinToneModifiers: [String] { + return [ "🏻", "🏼", "🏽", "🏾", "🏿" ] + } + + var emojiVisibleLength: Int { + var count = 0 + enumerateSubstrings(in: startIndex.. String { + switch nsstring.length { + case 5: + return nsstring.substring(to: 2) + modifier + nsstring.substring(from: 2) + default: + return self + modifier + } + } + + var emojiSkin: String { + if self.length < 2 { + return "" + } + + for modifier in emojiSkinToneModifiers { + if let range = self.range(of: modifier) { + return String(self[range]) + } + } + return "" + } + + var basicEmoji: (String, String?) { + let fitzCodes: [UInt32] = [ + 0x1f3fb, + 0x1f3fc, + 0x1f3fd, + 0x1f3fe, + 0x1f3ff + ] + + var string = "" + var fitzModifier: String? + for scalar in self.unicodeScalars { + if fitzCodes.contains(scalar.value) { + fitzModifier = String(scalar) + continue + } + string.unicodeScalars.append(scalar) + if scalar.value == 0x2764, self.unicodeScalars.count > 1, self.emojis.count == 1 { + break + } + } + return (string, fitzModifier) + } + + + + var canHaveSkinToneModifier: Bool { + if self.isEmpty { + return false + } + + let modified = self.emojiUnmodified + self.emojiSkinToneModifiers[0] + return modified.glyphCount == 1 + } + + var glyphCount: Int { + + let richText = NSAttributedString(string: self) + let line = CTLineCreateWithAttributedString(richText) + return CTLineGetGlyphCount(line) + } + + var isSingleEmoji: Bool { + return glyphCount == 1 && containsEmoji + } + + var containsEmoji: Bool { + return unicodeScalars.first(where: { $0.isEmoji }) != nil + } + + var containsOnlyEmoji: Bool { + guard !self.isEmpty else { + return false + } + if self.unicodeScalars.first == UnicodeScalar.ZeroWidthJoiner || self.unicodeScalars.first == UnicodeScalar.VariationSelector { + return false + } + + var nextShouldBeVariationSelector = false + for scalar in self.unicodeScalars { + if nextShouldBeVariationSelector { + if scalar == UnicodeScalar.VariationSelector { + nextShouldBeVariationSelector = false + continue + } else { + return false + } + } + if !scalar.isEmoji && scalar.maybeEmoji { + nextShouldBeVariationSelector = true + } + else if !scalar.isEmoji && scalar != UnicodeScalar.ZeroWidthJoiner { + return false + } + } + return !nextShouldBeVariationSelector + + } + + + var emojiString: String { + + return emojiScalars.map { String($0) }.reduce("", +) + } + + var emojis: [String] { + var emojis: [String] = [] + self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, options: .byComposedCharacterSequences) { substring, _, _, _ in + if let substring = substring { + emojis.append(substring) + } + } + return emojis + } + + + fileprivate var emojiScalars: [UnicodeScalar] { + var chars: [UnicodeScalar] = [] + var previous: UnicodeScalar? + for cur in unicodeScalars { + if let previous = previous, previous != UnicodeScalar.ZeroWidthJoiner && previous != UnicodeScalar.VariationSelector, cur.isEmoji { + chars.append(previous) + chars.append(cur) + + } else if cur.isEmoji { + chars.append(cur) + } + + previous = cur + } + + return chars + } + + var normalizedEmoji: String { + var string = "" + + var nextShouldBeVariationSelector = false + for scalar in self.unicodeScalars { + if nextShouldBeVariationSelector { + if scalar != UnicodeScalar.VariationSelector { + string.unicodeScalars.append(UnicodeScalar.VariationSelector) + } + nextShouldBeVariationSelector = false + } + string.unicodeScalars.append(scalar) + if !scalar.isEmoji && scalar.maybeEmoji { + nextShouldBeVariationSelector = true + } + } + + if nextShouldBeVariationSelector { + string.unicodeScalars.append(UnicodeScalar.VariationSelector) + } + + return string + } + + + var strippedEmoji: (String) { + var string = "" + for scalar in self.unicodeScalars { + if scalar.value != 0xfe0f { + string.unicodeScalars.append(scalar) + } + } + return string + } + +} + +public extension UnicodeScalar { + var isEmoji: Bool { + switch self.value { + case 0x1F600...0x1F64F, 0x1F300...0x1F5FF, 0x1F680...0x1F6FF, 0x1F1E6...0x1F1FF, 0xE0020...0xE007F, 0xFE00...0xFE0F, 0x1F900...0x1F9FF, 0x1F018...0x1F0F5, 0x1F200...0x1F270, 65024...65039, 9100...9300, 8400...8447, 0x1F004, 0x1F18E, 0x1F191...0x1F19A, 0x1F5E8: + return true + case 0x2603, 0x265F, 0x267E, 0x2692, 0x26C4, 0x26C8, 0x26CE, 0x26CF, 0x26D1...0x26D3, 0x26E9, 0x26F0...0x26F9, 0x2705, 0x270A, 0x270B, 0x2728, 0x274E, 0x2753...0x2755, 0x274C, 0x2795...0x2797, 0x27B0, 0x27BF: + return true + default: + return false + } + } + + var maybeEmoji: Bool { + switch self.value { + case 0x2A, 0x23, 0x30...0x39, 0xA9, 0xAE: + return true + case 0x2600...0x26FF, 0x2700...0x27BF, 0x1F100...0x1F1FF: + return true + case 0x203C, 0x2049, 0x2122, 0x2194...0x2199, 0x21A9, 0x21AA, 0x2139, 0x2328, 0x231A, 0x231B, 0x24C2, 0x25AA, 0x25AB, 0x25B6, 0x25FB...0x25FE, 0x25C0, 0x2934, 0x2935, 0x2B05...0x2B07, 0x2B1B...0x2B1E, 0x2B50, 0x2B55, 0x3030, 0x3297, 0x3299: + return true + default: + return false + } + } + + static var ZeroWidthJoiner = UnicodeScalar(0x200D)! + static var VariationSelector = UnicodeScalar(0xFE0F)! +} + + + + + +public extension Sequence where Iterator.Element: Hashable { + var uniqueElements: [Iterator.Element] { + return self.reduce([], { current, value in + if current.contains(value) { + return current + } else { + return current + [value] + } + }) + } +} +public extension Sequence where Iterator.Element: Equatable { + var uniqueElements: [Iterator.Element] { + return self.reduce([], { current, value in + if current.contains(value) { + return current + } else { + return current + [value] + } + }) +// return self.reduce([]){ +// uniqueElements, element in +// +// uniqueElements.contains(element) +// ? uniqueElements +// : uniqueElements + [element] +// } + } +} +public extension String { + func capitalizingFirstLetter() -> String { + return prefix(1).uppercased() + dropFirst() + } + + mutating func capitalizeFirstLetter() { + self = self.capitalizingFirstLetter() + } +} + + + +class GestureUtils{ + /** + * To avoid duplicate code we could extract the content of this method into an GestureUtils method. Return nil if there isn't 2 touches and set the array only if != nil + */ + static func twoFingersTouches(_ view:NSView, _ event:NSEvent)->[String:NSTouch]?{ + var twoFingersTouches:[String:NSTouch]? = nil//NSMutualDictionary was used before and didn't require casting id to string, revert if side-effects manifest + let touches:Set = event.touches(matching:NSTouch.Phase.any, in: view)//touchesMatchingPhase:NSTouchPhaseAny inView:self + if(touches.count == 2){ + twoFingersTouches = [String:NSTouch]() + for touch in touches { + twoFingersTouches!["\((touch).identity)"] = touch/*assigns each touch to the identity of the same touch*///was [ setObject: forKey:]; + } + } + return twoFingersTouches + } + /** + * Detects 2 finger (left/right) swipe gesture + * NOTE: either of 3 enums is returned: .leftSwipe, .rightSwipe .none + * TODO: also make up and down swipe detectors, and do more research into how this could be done easier. Maybe you even have some clues in the notes about gestures etc. + * Conceptually: + * 1. Record 2 .began touchEvents + * 2. Record 2 .ended touchEvents + * 3. Measure the distance between .began and .ended and assert if it is within threshold + */ + static func swipe(_ view:NSView, _ event:NSEvent, _ beginningTouches:[String:NSTouch]) -> SwipeType{ + let endingTouches:Set = event.touches(matching: NSTouch.Phase.ended, in: view) + if endingTouches.count == 2 { + var magnitudesX:[CGFloat] = []/*magnitude definition: the great size or extent of something.*/ + var magnitudesY:[CGFloat] = []/*magnitude definition: the great size or extent of something.*/ + for endingTouch in endingTouches { + guard let beginningTouch:NSTouch = beginningTouches["\(endingTouch.identity)"] else {continue} + + let magnitudeX:CGFloat = endingTouch.normalizedPosition.x - beginningTouch.normalizedPosition.x + magnitudesX.append(magnitudeX) + + let magnitudeY:CGFloat = endingTouch.normalizedPosition.y - beginningTouch.normalizedPosition.y + magnitudesX.append(magnitudeY) + } + + let kSwipeMinimumLength:CGFloat = 0.1 + + + var sumX:CGFloat = 0 + for magnitudeX in magnitudesX { + sumX += magnitudeX + } + + var sumY:CGFloat = 0 + for magnitudeY in magnitudesY { + sumY += magnitudeY + } + + let absoluteSumY:CGFloat = abs(sumY) + if (absoluteSumY > kSwipeMinimumLength) {return .none} + + let absoluteSumX:CGFloat = abs(sumX)/*force value to be positive*/ + if (absoluteSumX < kSwipeMinimumLength) {return .none}/*Assert if the absolute sum is long enough to be considered a complete gesture*/ + if (sumX > 0){ + return .right + }else /*if(sum < 0)*/{ + return .left + } + } + return .none/*no swipe direction detected*/ + } +} +public extension NSView { + + func widthConstraint(relation: NSLayoutConstraint.Relation, + size: CGFloat) -> NSLayoutConstraint { + return NSLayoutConstraint(item: self, + attribute: .width, + relatedBy: relation, + toItem: nil, + attribute: .width, + multiplier: 1.0, + constant: size) + } + + func addWidthConstraint(relation: NSLayoutConstraint.Relation = .equal, + size: CGFloat) { + addConstraint(widthConstraint(relation: relation, + size: size)) + } + +} + +public extension NSWindow { + var bounds: NSRect { + return NSMakeRect(0, 0, frame.width, frame.height) + } +} + + + +public extension String { + var isDirectory: Bool { + var isDir: ObjCBool = false + _ = FileManager.default.fileExists(atPath: self, isDirectory: &isDir) + return isDir.boolValue + } +} + + + +public extension Formatter { + static let withSeparator: NumberFormatter = { + let formatter = NumberFormatter() + formatter.locale = NSLocale.current + formatter.numberStyle = .decimal + return formatter + }() +} + +public extension BinaryInteger { + var formattedWithSeparator: String { + return Formatter.withSeparator.string(for: self) ?? "" + } +} + + +public extension String { + var persistentHashValue: UInt64 { + var result = UInt64 (5381) + let buf = [UInt8](self.utf8) + for b in buf { + result = 127 * (result & 0x00ffffffffffffff) + UInt64(b) + } + return result + } +} +extension NSEdgeInsets : Equatable { + public static func ==(lhs: NSEdgeInsets, rhs: NSEdgeInsets) -> Bool { + return lhs.left == rhs.left && lhs.right == rhs.right && lhs.bottom == rhs.bottom && lhs.top == rhs.top + } +} + + diff --git a/TGUIKit/TGUIKit/GridItem.swift b/submodules/TGUIKit/TGUIKit/GridItem.swift similarity index 84% rename from TGUIKit/TGUIKit/GridItem.swift rename to submodules/TGUIKit/TGUIKit/GridItem.swift index f8bdcc81ff..4adbbad5d5 100644 --- a/TGUIKit/TGUIKit/GridItem.swift +++ b/submodules/TGUIKit/TGUIKit/GridItem.swift @@ -18,7 +18,7 @@ public protocol GridSection { public protocol GridItem { var section: GridSection? { get } - func node(layout: GridNodeLayout, gridNode: GridNode) -> GridItemNode + func node(layout: GridNodeLayout, gridNode: GridNode, cachedNode: GridItemNode?) -> GridItemNode func update(node: GridItemNode) var aspectRatio: CGFloat { get } } diff --git a/TGUIKit/TGUIKit/GridItemNode.swift b/submodules/TGUIKit/TGUIKit/GridItemNode.swift similarity index 94% rename from TGUIKit/TGUIKit/GridItemNode.swift rename to submodules/TGUIKit/TGUIKit/GridItemNode.swift index 2755c232fd..e12107f921 100644 --- a/TGUIKit/TGUIKit/GridItemNode.swift +++ b/submodules/TGUIKit/TGUIKit/GridItemNode.swift @@ -9,7 +9,7 @@ import Cocoa -open class GridItemNode: ImageButton { +open class GridItemNode: Control { open var stableId:AnyHashable { return 0 diff --git a/TGUIKit/TGUIKit/GridNode.swift b/submodules/TGUIKit/TGUIKit/GridNode.swift similarity index 91% rename from TGUIKit/TGUIKit/GridNode.swift rename to submodules/TGUIKit/TGUIKit/GridNode.swift index b91f9b26d0..a71a6bd578 100644 --- a/TGUIKit/TGUIKit/GridNode.swift +++ b/submodules/TGUIKit/TGUIKit/GridNode.swift @@ -1,5 +1,5 @@ import Cocoa - +import AVFoundation public struct GridNodeInsertItem { public let index: Int @@ -250,14 +250,16 @@ private struct WrappedGridItemNode: Hashable { } } -open class GridNode: ScrollView, InteractionContentViewProtocol { +open class GridNode: ScrollView, InteractionContentViewProtocol, AppearanceViewProtocol { + + private var gridLayout = GridNodeLayout(size: CGSize(), insets: NSEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), lineSpacing: 0.0)) private var firstIndexInSectionOffset: Int = 0 private var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] private var sectionNodes: [WrappedGridSection: View] = [:] private var itemLayout = GridNodeItemLayout(contentSize: CGSize(), items: [], sections: []) - + private var cachedNodes:[GridItemNode] = [] private var applyingContentOffset = false public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? @@ -284,6 +286,16 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { fatalError("init(coder:) has not been implemented") } + public func updateLocalizationAndTheme(theme: PresentationTheme) { + guard let documentView = documentView else {return} + layer?.backgroundColor = presentation.colors.background.cgColor + for view in documentView.subviews { + if let view = view as? AppearanceViewProtocol { + view.updateLocalizationAndTheme(theme: theme) + } + } + } + public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout && (transaction.updateFirstIndexInSectionOffset == nil || transaction.updateFirstIndexInSectionOffset == self.firstIndexInSectionOffset)) { if let presentationLayoutUpdated = self.presentationLayoutUpdated { @@ -384,6 +396,33 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, itemTransition: transaction.itemTransition, completion: completion) } + var rows: Int { + return items.count / inRowCount + } + + var inRowCount: Int { + var count: Int = 0 + if let range = displayedItemRange().visibleRange { + let y: CGFloat? = itemNodes[range.lowerBound]?.frame.minY + for item in itemNodes { + if item.value.frame.minY == y { + count += 1 + } + } + } else { + return 1 + } + + return count + } + + private var previousScroll:ScrollPosition? + public var scrollHandler:(_ scrollPosition:ScrollPosition) ->Void = {_ in} { + didSet { + previousScroll = nil + } + } + open override func viewDidMoveToSuperview() { if superview != nil { @@ -392,6 +431,41 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { if !strongSelf.applyingContentOffset { strongSelf.applyPresentaionLayoutTransition(strongSelf.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, itemTransition: .immediate, completion: { _ in }) } + + let reqCount = 1 + + if let range = strongSelf.displayedItemRange().visibleRange { + let range = NSMakeRange(range.lowerBound / strongSelf.inRowCount, range.upperBound / strongSelf.inRowCount - range.lowerBound / strongSelf.inRowCount) + let scroll = strongSelf.scrollPosition() + + if (!strongSelf.clipView.isAnimateScrolling) { + + if(scroll.current.rect != strongSelf.previousScroll?.rect) { + + switch(scroll.current.direction) { + case .top: + if(range.location <= reqCount) { + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + case .bottom: + if(strongSelf.rows - (range.location + range.length) <= reqCount) { + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + case .none: + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + } + + } + } + strongSelf.reflectScrolledClipView(strongSelf.contentView) + } }) @@ -400,6 +474,8 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } } + + open override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) } @@ -433,14 +509,14 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { switch gridLayout.type { case let .fixed(itemSize, lineSpacing): - let s = floorToScreenPixels(gridLayout.size.width/floor(gridLayout.size.width/itemSize.width)) - let itemSize = NSMakeSize(s, s) + // let s = floorToScreenPixels(backingScaleFactor, gridLayout.size.width/floor(gridLayout.size.width/itemSize.width)) + // let itemSize = NSMakeSize(s, s) let itemsInRow = Int(gridLayout.size.width / itemSize.width) let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width let remainingWidth = max(0.0, gridLayout.size.width - itemsInRowWidth) - let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + let itemSpacing = floorToScreenPixels(backingScaleFactor, remainingWidth / CGFloat(itemsInRow + 1)) var incrementedCurrentRow = false var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) @@ -777,13 +853,12 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { self.documentView?.setFrameSize(presentationLayoutTransition.layout.contentSize) self.contentView.contentInsets = presentationLayoutTransition.layout.layout.insets - if !documentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) || self.bounds.size != presentationLayoutTransition.layout.layout.size { //self.scrollView.contentOffset = presentationLayoutTransition.layout.contentOffset self.contentView.bounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: self.contentView.bounds.size) - reflectScrolledClipView(contentView) } + reflectScrolledClipView(contentView) applyingContentOffset = false let lowestSectionNode: View? = self.lowestSectionNode() @@ -797,7 +872,11 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { itemNode.frame = item.frame } } else { - let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, gridNode: self) + let cachedNode = !cachedNodes.isEmpty ? cachedNodes.removeFirst() : nil + + let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, gridNode: self, cachedNode: cachedNode) + + itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) } @@ -821,6 +900,7 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { if let sectionNode = self.sectionNodes[wrappedSection] { sectionNode.frame = sectionFrame + document.addSubview(sectionNode) } else { let sectionNode = section.section.node() sectionNode.frame = sectionFrame @@ -884,12 +964,12 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } if let offset = offset { - let timingFunction: String + let timingFunction: CAMediaTimingFunctionName switch curve { case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut + timingFunction = CAMediaTimingFunctionName.easeInEaseOut case .spring: - timingFunction = kCAMediaTimingFunctionSpring + timingFunction = CAMediaTimingFunctionName.spring } for (index, itemNode) in self.itemNodes where existingItemIndices.contains(index) { @@ -957,12 +1037,12 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } } } else if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = itemTransition { - let timingFunction: String + let timingFunction: CAMediaTimingFunctionName switch curve { case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut + timingFunction = CAMediaTimingFunctionName.easeInEaseOut case .spring: - timingFunction = kCAMediaTimingFunctionSpring + timingFunction = CAMediaTimingFunctionName.spring } for index in self.itemNodes.keys { @@ -970,8 +1050,8 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { if !existingItemIndices.contains(index) { if let _ = previousItemFrames[WrappedGridItemNode(node: itemNode)] { self.removeItemNodeWithIndex(index, removeNode: false) - itemNode.layer!.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false) - itemNode.layer!.animateScale(from: 1.0, to: 0.1, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode.layer!.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeIn, removeOnCompletion: false) + itemNode.layer!.animateScale(from: 1.0, to: 0.1, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeIn, removeOnCompletion: false, completion: { [weak itemNode] _ in itemNode?.removeFromSuperview() }) } else { @@ -980,19 +1060,20 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { itemNode.layer!.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: itemNode.layer!.position, duration: duration, timingFunction: timingFunction) } else { - itemNode.layer!.animateAlpha(from: 0.0, to: 1.0, duration: 0.12, timingFunction: kCAMediaTimingFunctionEaseIn) + itemNode.layer!.animateAlpha(from: 0.0, to: 1.0, duration: 0.12, timingFunction: CAMediaTimingFunctionName.easeIn) itemNode.layer!.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) } } for itemNode in removedNodes { if let _ = previousItemFrames[WrappedGridItemNode(node: itemNode)] { - itemNode.layer!.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false) - itemNode.layer!.animateScale(from: 1.0, to: 0.1, duration: 0.18, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode.layer!.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, timingFunction: CAMediaTimingFunctionName.easeIn, removeOnCompletion: false) + itemNode.layer!.animateScale(from: 1.0, to: 0.1, duration: 0.18, timingFunction: CAMediaTimingFunctionName.easeIn, removeOnCompletion: false, completion: { [weak itemNode] _ in itemNode?.removeFromSuperview() }) } else { itemNode.removeFromSuperview() + cachedNodes.append(itemNode) } } @@ -1010,7 +1091,7 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { sectionNode.layer!.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: sectionNode.layer!.position, duration: duration, timingFunction: timingFunction) } else { - sectionNode.layer!.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn) + sectionNode.layer!.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeIn) } } } else { @@ -1028,6 +1109,7 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { for itemNode in removedNodes { itemNode.removeFromSuperview() + cachedNodes.append(itemNode) } } @@ -1098,6 +1180,7 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { if let itemNode = self.itemNodes.removeValue(forKey: index) { if removeNode { itemNode.removeFromSuperview() + cachedNodes.append(itemNode) } } } @@ -1110,6 +1193,13 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } } + public var itemsCount: Int { + return self.items.count + } + public var isEmpty: Bool { + return self.items.isEmpty + } + private func updateItemNodeVisibilititesAndScrolling() { let visibleRect = self.contentView.bounds let isScrolling = self.clipView.isScrolling @@ -1131,7 +1221,7 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { } - public func contentInteractionView(for stableId: AnyHashable) -> NSView? { + public func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { for (_, node) in itemNodes { if node.stableId == stableId { return node @@ -1140,6 +1230,21 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { return nil; } + public func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + + } + + public func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + + } + public func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + return nil + } + + public func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + + } + public func forEachRow(_ f: ([View]) -> Void) { var row: [View] = [] var previousMinY: CGFloat? @@ -1182,9 +1287,6 @@ open class GridNode: ScrollView, InteractionContentViewProtocol { applyPresentaionLayoutTransition(generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, itemTransition: .immediate, completion: { _ in }) } - public var isEmpty: Bool { - return items.isEmpty - } } diff --git a/submodules/TGUIKit/TGUIKit/HorizontalRowView.swift b/submodules/TGUIKit/TGUIKit/HorizontalRowView.swift new file mode 100644 index 0000000000..0f6fb241b4 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/HorizontalRowView.swift @@ -0,0 +1,58 @@ +// +// HorizontalRowView.swift +// TGUIKit +// +// Created by keepcoder on 17/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +open class HorizontalRowView: TableRowView { + + private var container:View = View() + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + super.addSubview(container) + + container.frame = NSMakeRect(0, 0, frame.height, frame.width) + container.frameCenterRotation = 90 + } + + + + open override func set(item: TableRowItem, animated: Bool) { + super.set(item: item, animated: animated) + container.backgroundColor = backdorColor + } + + + + open override func addSubview(_ view: NSView) { + container.addSubview(view) + } + + deinit { + container.removeAllSubviews() + } + + open override func layout() { + super.layout() + container.frame = NSMakeRect(frame.size.width, 0, frame.size.width, frame.size.height) + } + + open override func setFrameSize(_ newSize: NSSize) { + guard let item = self.item else { + super.setFrameSize(newSize) + return + } + super.setFrameSize(NSMakeSize(item.width == 0 ? newSize.width : item.width, newSize.height)) + // container.setFrameSize(newSize) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/HorizontalScrollView.swift b/submodules/TGUIKit/TGUIKit/HorizontalScrollView.swift new file mode 100644 index 0000000000..26cbc3959f --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/HorizontalScrollView.swift @@ -0,0 +1,52 @@ +// +// HorizontalScrollView.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 26/12/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +// +//public class HorizontalScrollView: ScrollView { +// +// public override func scrollWheel(with event: NSEvent) { +// +// var scrollPoint = contentView.bounds.origin +// let isInverted: Bool = System.isScrollInverted +// if event.scrollingDeltaY != 0 { +// if isInverted { +// scrollPoint.y += -event.scrollingDeltaY +// } else { +// scrollPoint.y -= event.scrollingDeltaY +// } +// } +// +// if event.scrollingDeltaX != 0 { +// if !isInverted { +// scrollPoint.y += -event.scrollingDeltaX +// } else { +// scrollPoint.y -= event.scrollingDeltaX +// } +// } +// +// clipView.scroll(to: scrollPoint) +// +// +// } +// +// +// open override var hasVerticalScroller: Bool { +// get { +// return false +// } +// set { +// super.hasVerticalScroller = newValue +// } +// } +// +// required public init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +//} diff --git a/submodules/TGUIKit/TGUIKit/HorizontalTableView.swift b/submodules/TGUIKit/TGUIKit/HorizontalTableView.swift new file mode 100644 index 0000000000..15d6699d4d --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/HorizontalTableView.swift @@ -0,0 +1,127 @@ +// +// HorizontalTableView.swift +// TGUIKit +// +// Created by keepcoder on 17/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public class HorizontalTableView: TableView { + + public override init(frame frameRect: NSRect, isFlipped: Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { + super.init(frame: frameRect, isFlipped: isFlipped, bottomInset: bottomInset, drawBorder: drawBorder) + // [[self.scrollView verticalScroller] setControlSize:NSSmallControlSize]; + //self.verticalScroller?.controlSize = NSControlSize.small + self.rotate(byDegrees: 270) + + self.clipView.border = [] + self.tableView.border = [] + } + + public override func scrollWheel(with event: NSEvent) { + + var scrollPoint = contentView.bounds.origin + let isInverted: Bool = System.isScrollInverted + + if event.scrollingDeltaY != 0 { + if isInverted { + scrollPoint.y += -event.scrollingDeltaY + } else { + scrollPoint.y -= event.scrollingDeltaY + } + } + + if event.scrollingDeltaX != 0 { + if !isInverted { + scrollPoint.y += -event.scrollingDeltaX + } else { + scrollPoint.y -= event.scrollingDeltaX + } + } + + scrollPoint.y = max(0, min(scrollPoint.y, listHeight - clipView.bounds.height)) + clipView.scroll(to: scrollPoint) + + + } + + + open override var hasVerticalScroller: Bool { + get { + return false + } + set { + super.hasVerticalScroller = newValue + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func rowView(item:TableRowItem) -> TableRowView { + let identifier:String = item.identifier + + if let resortView = self.resortController?.resortView { + if resortView.item?.stableId == item.stableId { + return resortView + } + } + var view: NSView? = item.isUniqueView ? nil : self.tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier), owner: self.tableView) + + if(view == nil) { + let vz = item.viewClass() as! TableRowView.Type + + view = vz.init(frame:NSMakeRect(0, 0, item.height, frame.height)) + + view?.identifier = NSUserInterfaceItemIdentifier(rawValue: identifier) + + } + + return view as! TableRowView; + } + + public override func viewNecessary(at row:Int, makeIfNecessary: Bool = false) -> TableRowView? { + if row < 0 || row >= count { + return nil + } + if let resortView = self.resortController?.resortView { + if resortView.item?.stableId == self.item(at: row).stableId { + return resortView + } + } + return self.tableView.rowView(atRow: row, makeIfNecessary: makeIfNecessary) as? TableRowView + } + +} + + +open class HorizontalScrollView : ScrollView { + override open func scrollWheel(with event: NSEvent) { + + var scrollPoint = contentView.bounds.origin + let isInverted: Bool = System.isScrollInverted + if event.scrollingDeltaY != 0 { + if isInverted { + scrollPoint.x += -event.scrollingDeltaY + } else { + scrollPoint.x -= event.scrollingDeltaY + } + } + if event.scrollingDeltaX != 0 { + if !isInverted { + scrollPoint.x += -event.scrollingDeltaX + } else { + scrollPoint.x -= event.scrollingDeltaX + } + } + if documentView!.frame.width > frame.width { + scrollPoint.x = min(max(0, scrollPoint.x), documentView!.frame.width - frame.width) + clipView.scroll(to: scrollPoint) + } else { + superview?.scrollWheel(with: event) + } + } +} diff --git a/submodules/TGUIKit/TGUIKit/Image.swift b/submodules/TGUIKit/TGUIKit/Image.swift new file mode 100644 index 0000000000..c480ecf987 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Image.swift @@ -0,0 +1,70 @@ +import Foundation +import ImageIO +import Accelerate + + + + +public func roundImage(_ data:Data, _ s:NSSize, cornerRadius:CGFloat = -1, reversed:Bool = false, scale:CGFloat = 1.0) -> CGImage? { + return autoreleasepool { + let image:CGImageSource? = CGImageSourceCreateWithData(data as CFData, nil) + + let size = NSMakeSize(s.width * scale, s.height * scale) + + let context:CGContext? = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: Int(4*size.width), space: NSColorSpace.genericRGB.cgColorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) + + if let ctx = context { + if let img = image { + let cimage = CGImageSourceCreateImageAtIndex(img, 0, nil) + if let c = cimage { + + if cornerRadius == -1 { + var startAngle: Float = Float(2 * Double.pi) + var endAngle: Float = 0.0 + let radius:Float = Float(size.width/2.0) + let center = NSMakePoint(size.width/2.0, size.height/2.0) + + startAngle = startAngle - Float(Double.pi / 2) + endAngle = endAngle - Float(Double.pi / 2) + ctx.addArc(center: center, radius: CGFloat(radius), startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: false) + } else if cornerRadius > 0 { + + let minx:CGFloat = 0, midx = size.width/2.0, maxx = size.width + let miny:CGFloat = 0, midy = size.height/2.0, maxy = size.height + + + ctx.move(to: NSMakePoint(minx, midy)) + ctx.addArc(tangent1End: NSMakePoint(minx, miny), tangent2End: NSMakePoint(midx, miny), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(maxx, miny), tangent2End: NSMakePoint(maxx, midy), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(maxx, maxy), tangent2End: NSMakePoint(midx, maxy), radius: cornerRadius) + ctx.addArc(tangent1End: NSMakePoint(minx, maxy), tangent2End: NSMakePoint(minx, midy), radius: cornerRadius) + + } + + if cornerRadius > 0 || cornerRadius == -1 { + ctx.closePath() + ctx.clip() + } + + if reversed { + ctx.translateBy(x: size.width/2.0, y: size.height/2.0) + ctx.scaleBy(x: 1.0, y: -1.0) + ctx.translateBy(x: -(size.width/2.0), y: -(size.height/2.0)) + + } + ctx.draw(c, in: NSMakeRect(0, 0, size.width, size.height)) + + + return ctx.makeImage() + + } + + } + } + return nil + } +} + + + + diff --git a/TGUIKit/TGUIKit/ImageBarView.swift b/submodules/TGUIKit/TGUIKit/ImageBarView.swift similarity index 90% rename from TGUIKit/TGUIKit/ImageBarView.swift rename to submodules/TGUIKit/TGUIKit/ImageBarView.swift index 85b19dae82..267871d78c 100644 --- a/TGUIKit/TGUIKit/ImageBarView.swift +++ b/submodules/TGUIKit/TGUIKit/ImageBarView.swift @@ -17,12 +17,13 @@ public class ImageBarView: BarView { if let highlight = highlightImage { button.set(image: highlight, for: .Highlight) } - button.sizeToFit() - self.needsLayout = true + _ = button.sizeToFit() + setFrameSize(NSMakeSize(max(60, image.backingSize.width), frame.height)) } + public override func layout() { super.layout() button.center() diff --git a/submodules/TGUIKit/TGUIKit/ImageButton.swift b/submodules/TGUIKit/TGUIKit/ImageButton.swift new file mode 100644 index 0000000000..09c5e17377 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ImageButton.swift @@ -0,0 +1,232 @@ +// +// ImageButton.swift +// TGUIKit +// +// Created by keepcoder on 26/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public enum ButtonHoverPolicy { + case none + case enlarge(value: CGFloat) +} + +public enum ButtonBackgroundCornerRadius { + case none + case appSpecific + case half +} + +public enum ImageButtonAnimationPolicy { + case animateContents + case replaceScale +} + +open class ImageButton: Button { + + internal private(set) var imageView:ImageView = ImageView() + internal let additionBackgroundView: View = View() + + private var additionStateBackground:[ControlState:NSColor] = [:] + private var cornerRadius:[ControlState : ButtonBackgroundCornerRadius] = [:] + + private var hoverAdditionPolicy:[ControlState : ButtonHoverPolicy] = [:] + + private var additionBackgroundMultiplier:[ControlState: CGFloat] = [:] + + private var images:[ControlState:CGImage] = [:] + + private var backgroundImage:[ControlState:CGImage] = [:] + + + public func removeImage(for state:ControlState) { + images.removeValue(forKey: state) + apply(state: self.controlState) + + } + + public func setImageContentGravity(_ gravity: CALayerContentsGravity) { + imageView.contentGravity = gravity + } + + public func set(image:CGImage, for state:ControlState) -> Void { + images[state] = image + apply(state: self.controlState) + } + + open override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + } + + override func prepare() { + super.prepare() + imageView.animates = true + additionBackgroundView.isEventLess = true + //imageView.isEventLess = true + self.addSubview(additionBackgroundView) + self.addSubview(imageView) + } + + public func set(additionBackgroundColor:NSColor, for state:ControlState) -> Void { + additionStateBackground[state] = additionBackgroundColor + apply(state: self.controlState) + } + public func set(additionBackgroundMultiplier: CGFloat, for state:ControlState) -> Void { + self.additionBackgroundMultiplier[state] = additionBackgroundMultiplier + apply(state: self.controlState) + } + + + public func set(cornerRadius: ButtonBackgroundCornerRadius, for state:ControlState) -> Void { + self.cornerRadius[state] = cornerRadius + apply(state: self.controlState) + } + public func set(hoverAdditionPolicy: ButtonHoverPolicy, for state:ControlState) -> Void { + self.hoverAdditionPolicy[state] = hoverAdditionPolicy + apply(state: self.controlState) + } + + + + + public override var animates: Bool { + didSet { + imageView.animates = animates + } + } + + private var previousState: ControlState? + + override public func apply(state: ControlState) { + let previous = self.previousState + let state:ControlState = self.isSelected ? .Highlight : state + super.apply(state: state) + self.previousState = state + + let updated: CGImage? + + if let image = images[state] { + updated = image + } else if state == .Highlight && autohighlight, let image = images[.Normal] { + updated = style.highlight(image: image) + } else if state == .Hover && highlightHovered, let image = images[.Normal] { + updated = style.highlight(image: image) + } else { + updated = images[.Normal] + } + + if imageView.image != updated { + self.imageView.image = updated + } + + + if let policy = self.hoverAdditionPolicy[state], previous != state { + switch policy { + case .none: + break + case let .enlarge(value): + let current = additionBackgroundView.layer?.presentation()?.value(forKeyPath: "transform.scale") as? CGFloat ?? 1.0 + additionBackgroundView.layer?.animateScaleSpring(from: current, to: value, duration: 0.35, removeOnCompletion: false) + } + } + + if let color = self.additionStateBackground[state] ?? self.additionStateBackground[.Normal] { + additionBackgroundView.backgroundColor = color + } else { + additionBackgroundView.backgroundColor = .clear + } + + updateLayout() + + if let cornerRadius = self.cornerRadius[state] { + switch cornerRadius { + case .none: + self.layer?.cornerRadius = 0 + self.additionBackgroundView.layer?.cornerRadius = 0 + case .appSpecific: + self.layer?.cornerRadius = .cornerRadius + self.additionBackgroundView.layer?.cornerRadius = .cornerRadius + case .half: + self.layer?.cornerRadius = max(frame.width, frame.height) / 2 + self.additionBackgroundView.layer?.cornerRadius = max(additionBackgroundView.frame.width, additionBackgroundView.frame.height) / 2 + } + } + + } + + public func applyAnimation(from: CGImage, to: CGImage, animation: ImageButtonAnimationPolicy) { + switch animation { + case .animateContents: + self.imageView.image = to + case .replaceScale: + let imageView = self.imageView + imageView.image = from + let newImageView = ImageView() + self.imageView = newImageView + newImageView.image = to + newImageView.sizeToFit() + addSubview(newImageView) + newImageView.center() + + + imageView.layer?.animateScaleCenter(from: 1, to: 0.1, duration: 0.25, removeOnCompletion: false, completion: { [weak imageView] _ in + imageView?.removeFromSuperview() + }) + imageView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false) + + newImageView.layer?.animateScaleCenter(from: 0.1, to: 1, duration: 0.25, removeOnCompletion: true) + } + } + + public func disableActions() { + animates = false + self.layer?.disableActions() + layer?.removeAllAnimations() + imageView.animates = false + imageView.layer?.disableActions() + } + + open override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + } + + override public func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) -> Bool { + _ = super.sizeToFit(addition, maxSize, thatFit: thatFit) + + if let image = images[.Normal] { + var size = image.backingSize + + if maxSize.width > 0 || maxSize.height > 0 { + size = maxSize + } + + size.width += addition.width + size.height += addition.height + self.setFrameSize(size) + } + return true + } + + public override func updateLayout() { + if let image = images[controlState] { + switch imageView.contentGravity { + case .resize, .resizeAspectFill: + imageView.setFrameSize(frame.size) + default: + imageView.setFrameSize(image.backingSize) + } + } + imageView.center() + + if let multiplier = additionBackgroundMultiplier[controlState] { + additionBackgroundView.setFrameSize(NSMakeSize(floorToScreenPixels(backingScaleFactor, frame.width * multiplier), floorToScreenPixels(backingScaleFactor, frame.height * multiplier))) + } else { + additionBackgroundView.setFrameSize(frame.size) + } + + additionBackgroundView.center() + } + +} diff --git a/submodules/TGUIKit/TGUIKit/ImageView.swift b/submodules/TGUIKit/TGUIKit/ImageView.swift new file mode 100644 index 0000000000..10e7645f9a --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ImageView.swift @@ -0,0 +1,93 @@ +// +// ImageView.swift +// TGUIKit +// +// Created by keepcoder on 22/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + +public enum ImageViewTransition { + case `default` + case modern +} + +open class ImageView: NSView { + + + public var isEventLess: Bool = false + + public var animationTransition: ImageViewTransition = .default + open var animates:Bool = false + + open var image:CGImage? { + didSet { + let wasImage = self.layer?.contents != nil + self.layer?.contents = image + if animates { + if !wasImage { + self.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } else { + animate() + } + } + } + } + + open var contentGravity: CALayerContentsGravity = .center { + didSet { + layer?.contentsGravity = contentGravity + } + } + + open func sizeToFit() { + if let image = self.image { + setFrameSize(image.backingSize) + } + } + + open override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + override public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.wantsLayer = true + layerContentsRedrawPolicy = .never + } + init() { + super.init(frame: .zero) + self.wantsLayer = true + layerContentsRedrawPolicy = .never + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open func animate() -> Void { + let animation = CABasicAnimation(keyPath: "contents") + animation.duration = 0.2 + self.layer?.add(animation, forKey: "contents") + } + + override open func viewDidChangeBackingProperties() { + if let window = self.window { + self.layer?.contentsScale = window.backingScaleFactor + } + } + + open func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) -> Void { + super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + open func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + open func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + +} diff --git a/submodules/TGUIKit/TGUIKit/Info.plist b/submodules/TGUIKit/TGUIKit/Info.plist new file mode 100644 index 0000000000..6cdd879cca --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2016 Telegram. All rights reserved. + UIAppFonts + + SFCompactRounded-Semibold.otf + + NSPrincipalClass + + + diff --git a/TGUIKit/TGUIKit/KeyboardUtils.swift b/submodules/TGUIKit/TGUIKit/KeyboardUtils.swift similarity index 100% rename from TGUIKit/TGUIKit/KeyboardUtils.swift rename to submodules/TGUIKit/TGUIKit/KeyboardUtils.swift diff --git a/TGUIKit/TGUIKit/Layer.swift b/submodules/TGUIKit/TGUIKit/Layer.swift similarity index 87% rename from TGUIKit/TGUIKit/Layer.swift rename to submodules/TGUIKit/TGUIKit/Layer.swift index 626ef1c452..a08975b365 100644 --- a/TGUIKit/TGUIKit/Layer.swift +++ b/submodules/TGUIKit/TGUIKit/Layer.swift @@ -17,9 +17,7 @@ public class Layer: CALayer { public override func didChangeValue(forKey key: String) { super.didChangeValue(forKey: key) - if s != self.superlayer { - self.layerMoved(to: self.superlayer) - } + self.layerMoved(to: self.superlayer) s = self.superlayer diff --git a/submodules/TGUIKit/TGUIKit/LinearProgressControl.swift b/submodules/TGUIKit/TGUIKit/LinearProgressControl.swift new file mode 100644 index 0000000000..d813a8c99e --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/LinearProgressControl.swift @@ -0,0 +1,404 @@ +// +// LinearProgressControl.swift +// TGUIKit +// +// Created by keepcoder on 28/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public enum LinearProgressAlignment { + case bottom + case center + case top +} + +public class LinearProgressControl: Control { + + public var hasMinumimVisibility: Bool = false + + private var progressView:View! + private var fetchingView:View! + + private var fetchingViewRanges:[View] = [] + + private var containerView:View! + + private var _progress:CGFloat = 0 + private var progress:CGFloat { + get { + return scrubblingTempState ?? _progress + } + set { + _progress = newValue + } + } + + private var fetchingProgress: CGFloat = 0 + private var fetchingProgressRanges: [Range] = [] + + public var progressHeight:CGFloat + public var onUserChanged:((Float)->Void)? + + public var onLiveScrobbling:((Float?)->Void)? + + public var startScrobbling:(()->Void)? + public var endScrobbling:(()->Void)? + + public var insets: NSEdgeInsets = NSEdgeInsets() { + didSet { + needsLayout = true + } + } + public var alignment: LinearProgressAlignment = .bottom + public var liveScrobbling: Bool = true + + private var scrubber: ImageButton? = nil + + private(set) public var scrubblingTempState: CGFloat? { + didSet { + needsLayout = true + self.progressView.layer?.removeAllAnimations() + self.scrubber?.layer?.removeAllAnimations() + } + } + + public var cornerRadius: CGFloat = 0 { + didSet { + self.progressView.layer?.cornerRadius = cornerRadius + self.layer?.cornerRadius = cornerRadius + } + } + + public var scrubberImage: CGImage? { + didSet { + if let scrubberImage = scrubberImage { + if scrubber == nil { + scrubber = ImageButton() + scrubber?.userInteractionEnabled = false + scrubber?.autohighlight = false + scrubber!.set(image: scrubberImage, for: .Normal) + _ = scrubber!.sizeToFit() + addSubview(scrubber!) + } else { + scrubber!.set(image: scrubberImage, for: .Normal) + _ = scrubber!.sizeToFit() + } + needsLayout = true + } else { + scrubber?.removeFromSuperview() + scrubber = nil + } + } + } + public var roundCorners: Bool = false { + didSet { + containerView.layer?.cornerRadius = roundCorners ? containerView.frame.height / 2 : 0 + progressView.layer?.cornerRadius = roundCorners ? progressView.frame.height / 2 : 0 + fetchingView.layer?.cornerRadius = roundCorners ? fetchingView.frame.height / 2 : 0 + + for view in fetchingViewRanges { + view.layer?.cornerRadius = roundCorners ? view.frame.height / 2 : 0 + } + } + } + + public var currentValue: CGFloat { + return progress + } + + + public override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + if let onUserChanged = onUserChanged, isEnabled { + let location = containerView.convert(event.locationInWindow, from: nil) + // if location.x >= 0 && location.x <= frame.width { + let progress = min(max(Float(max(location.x, 0) / containerView.frame.width), 0), 1) + if liveScrobbling { + onUserChanged(progress) + } else { + self.scrubblingTempState = CGFloat(progress) + self.onLiveScrobbling?(progress) + } + // } + } + } + + + public override func mouseDown(with event: NSEvent) { + scrubblingTempState = nil + self.startScrobbling?() + if let _ = onUserChanged, !liveScrobbling, isEnabled { + let location = containerView.convert(event.locationInWindow, from: nil) + let progress = min(max(CGFloat(max(location.x, 0) / containerView.frame.width), 0), 1) + self.scrubblingTempState = progress + self.onLiveScrobbling?(nil) + // } + } else if !userInteractionEnabled { + //super.mouseDown(with: event) + } + } + + public var hasTemporaryState: Bool { + return scrubblingTempState != nil + } + + public override func mouseUp(with event: NSEvent) { + scrubblingTempState = nil + self.endScrobbling?() + if let onUserChanged = onUserChanged, isEnabled { + let location = containerView.convert(event.locationInWindow, from: nil) + let progress = min(max(Float(max(location.x, 0) / containerView.frame.width), 0), 1) + onUserChanged(progress) + self.onLiveScrobbling?(nil) + } + } + + public var interactiveValue:Float { + if let window = window { + let location = containerView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + return Float(location.x / containerView.frame.width) + } + return 0 + } + + open override func updateTrackingAreas() { + super.updateTrackingAreas(); + + + if let trackingArea = trackingArea { + self.removeTrackingArea(trackingArea) + } + + trackingArea = nil + + if let _ = window { + let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.enabledDuringMouseDrag, NSTrackingArea.Options.activeInKeyWindow,NSTrackingArea.Options.inVisibleRect] + self.trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) + + self.addTrackingArea(self.trackingArea!) + } + } + + deinit { + if let trackingArea = self.trackingArea { + self.removeTrackingArea(trackingArea) + } + } + + public override var style: ControlStyle { + set { + self.progressView.layer?.backgroundColor = newValue.foregroundColor.cgColor + super.style = newValue + } + get { + return super.style + } + } + + public var containerBackground: NSColor = .clear { + didSet { + containerView.backgroundColor = containerBackground + } + } + + public var fetchingColor: NSColor = presentation.colors.grayTransparent { + didSet { + self.fetchingView.layer?.backgroundColor = fetchingColor.cgColor + for fetchView in fetchingViewRanges { + fetchView.backgroundColor = fetchingColor + } + } + } + + + private func preparedAnimation(keyPath: String, from: NSValue, to: NSValue, duration: Double, beginTime: Double?, offset: Double, speed: Float, repeatForever: Bool = false) -> CAAnimation { + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.fillMode = .both + animation.speed = speed + animation.timeOffset = offset + animation.isAdditive = false + if let beginTime = beginTime { + animation.beginTime = beginTime + } + return animation + } + + public func set(progress:CGFloat, animated:Bool, duration: Double, beginTime: Double?, offset: Double, speed: Float, repeatForever: Bool = false) { + let progress:CGFloat = progress.isNaN ? 1 : progress + self.progress = progress + let size = NSMakeSize(floorToScreenPixels(backingScaleFactor, max(containerView.frame.width * self.progress, hasMinumimVisibility ? progressHeight : 0)), progressHeight) + + + progressView.centerY(x: 0) + + let fromBounds = NSMakeRect(0, progressView.frame.minY, 0, size.height) + let toBounds = NSMakeRect(0, progressView.frame.minY, containerView.frame.width, size.height) + + if animated, scrubblingTempState == nil { + progressView.layer?.add(preparedAnimation(keyPath: "bounds", from: NSValue(rect: fromBounds), to: NSValue(rect: toBounds), duration: duration, beginTime: beginTime, offset: offset, speed: speed), forKey: "bounds") + if let scrubber = scrubber { + scrubber.layer?.add(preparedAnimation(keyPath: "position", from: NSValue(point: NSMakePoint(containerView.frame.minX - scrubber.frame.width / 2, scrubber.frame.minY)), to: NSValue(point: NSMakePoint(containerView.frame.maxX - scrubber.frame.width / 2, scrubber.frame.minY)), duration: duration, beginTime: beginTime, offset: offset, speed: speed), forKey: "position") + } + } else { + progressView.layer?.removeAllAnimations() + set(progress: progress) + } + + updateFetchingRanges(animated) + } + + public override var frame: NSRect { + didSet { + layout() + } + } + public func set(progress:CGFloat, animated:Bool = false, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = .linear, bounce: Bool = false) { + let progress:CGFloat = progress.isNaN ? 1 : progress + self.progress = progress + let size = NSMakeSize(floorToScreenPixels(backingScaleFactor, max(containerView.frame.width * self.progress, hasMinumimVisibility ? progressHeight : 0)), progressHeight) + + progressView.change(size: size, animated: animated, duration: duration, timingFunction: timingFunction) + if let scrubber = scrubber { + scrubber.change(pos: NSMakePoint(containerView.frame.minX + size.width - scrubber.frame.width / 2, scrubber.frame.minY), animated: animated, timingFunction: timingFunction) + } + progressView.centerY(x: 0) + + updateFetchingRanges(animated) + } + + public func set(fetchingProgress: CGFloat, animated:Bool = false, duration: Double = 0.2) { + let fetchingProgress:CGFloat = fetchingProgress.isNaN ? 1 : fetchingProgress + self.fetchingProgress = fetchingProgress + let size = NSMakeSize(floorToScreenPixels(backingScaleFactor, containerView.frame.width * fetchingProgress), progressHeight) + fetchingView.change(size: size, animated: animated, duration: duration, timingFunction: .linear) + + fetchingView.centerY(x: 0) + } + + public func set(fetchingProgressRanges: [Range], animated: Bool = true) { + self.fetchingProgressRanges = fetchingProgressRanges + updateFetchingRanges(animated) + } + + private func updateFetchingRanges(_ animated: Bool) { + let fetchingProgressRanges = self.fetchingProgressRanges.filter({$0.contains(self.currentValue)}) + + if self.fetchingViewRanges.count == fetchingProgressRanges.count { + + } else if self.fetchingViewRanges.count > fetchingProgressRanges.count { + while self.fetchingViewRanges.count != fetchingProgressRanges.count { + self.fetchingViewRanges.removeLast().removeFromSuperview() + } + } else if self.fetchingViewRanges.count < fetchingProgressRanges.count { + while self.fetchingViewRanges.count != fetchingProgressRanges.count { + let view = View(frame: NSMakeRect(0, 0, 0, progressHeight)) + view.backgroundColor = fetchingColor + self.fetchingViewRanges.append(view) + containerView.addSubview(self.fetchingViewRanges.last!, positioned: .below, relativeTo: progressView) + } + } + + for i in 0 ..< fetchingProgressRanges.count { + let range = fetchingProgressRanges[i] + let view = self.fetchingViewRanges[i] + let width = (range.upperBound - range.lowerBound) * containerView.frame.width + view.change(size: NSMakeSize(width, progressHeight), animated: animated, duration: 0.2, timingFunction: .linear) + view.setFrameOrigin(range.lowerBound * containerView.frame.width, floorToScreenPixels(backingScaleFactor, (containerView.frame.height - progressHeight) / 2)) + view.layer?.cornerRadius = roundCorners ? view.frame.height / 2 : 0 + } + } + + + + public init(progressHeight:CGFloat = 4) { + self.progressHeight = progressHeight + super.init(frame: NSMakeRect(0, 0, 0, progressHeight)) + + initialize() + } + + public override func layout() { + super.layout() + + switch alignment { + case .bottom: + containerView.frame = NSMakeRect(insets.left, frame.height - progressHeight, frame.width - insets.left - insets.right, progressHeight) + case .top: + containerView.frame = NSMakeRect(insets.left, 0, frame.width - insets.left - insets.right, progressHeight) + case .center: + containerView.frame = NSMakeRect(insets.left, floorToScreenPixels(backingScaleFactor, (frame.height - progressHeight) / 2), frame.width - insets.left - insets.right, progressHeight) + } + + let size = NSMakeSize(floorToScreenPixels(backingScaleFactor, max(containerView.frame.width * self.progress, hasMinumimVisibility ? progressHeight : 0)), progressHeight) + progressView.setFrameSize(size) + + + if let scrubber = scrubber { + scrubber.centerY(x: containerView.frame.minX + size.width - scrubber.frame.width / 2) + } + + } + + + private func initialize() { + + containerView = View(frame:NSMakeRect(0, 0, 0, progressHeight)) + addSubview(containerView) + + + fetchingView = View(frame:NSMakeRect(0, 0, 0, progressHeight)) + fetchingView.backgroundColor = style.foregroundColor + containerView.addSubview(fetchingView) + + progressView = View(frame:NSMakeRect(0, 0, 0, progressHeight)) + progressView.backgroundColor = style.foregroundColor + containerView.addSubview(progressView) + + userInteractionEnabled = false + + + progressView.isEventLess = true + containerView.isEventLess = true + fetchingView.isEventLess = true + } + + + private func updateCursor() { + if mouseInside() && onUserChanged != nil, style.highlightColor != .clear, isEnabled { + set(background: style.highlightColor.withAlphaComponent(0.2), for: .Hover) + } else { + set(background: style.backgroundColor, for: .Hover) + } + } + + public override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateCursor() + } + + public override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + updateCursor() + } + + public override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateCursor() + } + + required public init(frame frameRect: NSRect) { + self.progressHeight = frameRect.height + super.init(frame:frameRect) + initialize() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/MagnifyView.swift b/submodules/TGUIKit/TGUIKit/MagnifyView.swift new file mode 100644 index 0000000000..266e2b2652 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/MagnifyView.swift @@ -0,0 +1,289 @@ +// +// MagnifyView.swift +// TGUIKit +// +// Created by keepcoder on 15/12/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +open class MagnifyView : NSView { + + public private(set) var magnify:CGFloat = 1.0 { + didSet { + } + } + public var maxMagnify:CGFloat = 8.0 + public var minMagnify:CGFloat = 1.0 + + private let smartUpdater:Promise = Promise() + + public var smartUpdaterValue: Signal { + return smartUpdater.get() |> distinctUntilChanged + } + + fileprivate let magnifyUpdater:Promise = Promise(1) + + public var magnifyUpdaterValue:Signal { + return magnifyUpdater.get() |> distinctUntilChanged + } + + private var mov_start:NSPoint = NSZeroPoint + private var mov_content_start:NSPoint = NSZeroPoint + + + public private(set) var contentView:NSView + let containerView:NSView = NSView() + open var contentSize:NSSize = NSZeroSize { + didSet { + if abs(oldValue.width - contentSize.width) > 1 || abs(oldValue.height - contentSize.height) > 1 { + contentView.frame = focus(magnifiedSize) + } + } + } + + + public var contentFrame: NSRect { + return contentView.frame.apply(multiplier: NSMakeSize(1 / magnify, 1 / magnify)) + } + + public var contentFrameMagnified: NSRect { + return contentView.frame + } + + private var magnifiedSize:NSSize { + return NSMakeSize(floorToScreenPixels(backingScaleFactor, contentSize.width * magnify), floorToScreenPixels(backingScaleFactor, contentSize.height * magnify)) + } + + public func swapView(_ newView: NSView) { + self.contentView.removeFromSuperview() + newView.removeFromSuperview() + self.contentView = newView + containerView.addSubview(newView) + resetMagnify() + } + + public init(_ contentView:NSView, contentSize:NSSize) { + self.contentView = contentView + contentView.setFrameSize(contentSize) + self.contentSize = contentSize + contentView.wantsLayer = true + super.init(frame: NSZeroRect) + wantsLayer = true + containerView.wantsLayer = true + addSubview(containerView) + containerView.addSubview(contentView) + containerView.autoresizesSubviews = false + contentView.background = .clear + background = .clear + smartUpdater.set(.single(contentSize)) + } + + public func resetMagnify() { + magnify = 1.0 + contentView.setFrameSize(magnifiedSize) + contentView.center() + } + + public func focusContentView() { + contentView.center() + } + + public func zoomIn() { + add(magnify: 0.5, for: NSMakePoint(containerView.frame.width/2, containerView.frame.height/2), animated: true) + magnifyUpdater.set(.single(magnify) |> delay(0.2, queue: .mainQueue())) + } + + public func zoomOut() { + add(magnify: -0.5, for: NSMakePoint(containerView.frame.width/2, containerView.frame.height/2), animated: true) + magnifyUpdater.set(.single(magnify) |> delay(0.2, queue: .mainQueue())) + } + + open override func layout() { + super.layout() + containerView.setFrameSize(frame.size) + contentView.center() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func magnify(with event: NSEvent) { + // super.magnify(with: event) + + add(magnify: event.magnification, for: containerView.convert(event.locationInWindow, from: nil)) + + if event.phase == .ended { + smartUpdater.set(.single(magnifiedSize) |> delay(0.3, queue: Queue.mainQueue())) + magnifyUpdater.set(.single(magnify) |> delay(0.2, queue: .mainQueue())) + } else if event.phase == .began { + smartUpdater.set(smartUpdater.get()) + } + } + + override open func smartMagnify(with event: NSEvent) { + // super.smartMagnify(with: event) + addSmart(for: containerView.convert(event.locationInWindow, from: nil)) + smartUpdater.set(.single(magnifiedSize) |> delay(0.2, queue: Queue.mainQueue())) + magnifyUpdater.set(.single(magnify) |> delay(0.2, queue: .mainQueue())) + } + + func addSmart(for location:NSPoint) { + var minFactor:CGFloat = min(max(floor(frame.size.width / magnifiedSize.width), floor(frame.size.height / magnifiedSize.height)),2.0) + if magnify > 1.0 { + minFactor = 1 - magnify + } + add(magnify: minFactor, for: location, animated: true) + } + + open func add(magnify:CGFloat, for location:NSPoint, animated:Bool = false) { + self.magnify += magnify + self.magnify = min(max(minMagnify,self.magnify),maxMagnify) + let point = magnifyOrigin( for: location, from:contentView.frame, factor: magnify) + + //contentView.change(pos: point, animated: animated) + // contentView.change(size: magnifiedSize, animated: animated) + // content.layer?.animateScaleCenter(from: <#T##CGFloat#>, to: <#T##CGFloat#>, duration: <#T##Double#>) + //content.anch + + + + let content = animated ? contentView.animator() : contentView + content.frame = NSMakeRect(point.x, point.y, magnifiedSize.width, magnifiedSize.height) + + + } + + func magnifyOrigin(for location:NSPoint, from past:NSRect, factor:CGFloat) -> NSPoint { + + var point:NSPoint = past.origin + let focused = focus(magnifiedSize).origin + if NSPointInRect(location, contentView.frame) { + if magnifiedSize.width < frame.width { + point.x = focused.x + } else { + point.x -= (magnifiedSize.width - past.width) * ((location.x - past.minX) / past.width) + point = adjust(with: point, adjustX: true, adjustY: false) + + } + if magnifiedSize.height < frame.height { + point.y = focused.y + } else { + point.y -= (magnifiedSize.height - past.height) * ((location.y - past.minY) / past.height) + point = adjust(with: point, adjustX: false, adjustY: true) + } + + } else { + point = focused + } + return point + } + + override open func mouseDown(with theEvent: NSEvent) { + self.mov_start = convert(theEvent.locationInWindow, from: nil) + self.mov_content_start = contentView.frame.origin + } + + override open func mouseUp(with theEvent: NSEvent) { + self.mov_start = NSZeroPoint + self.mov_content_start = NSZeroPoint + super.mouseUp(with: theEvent) + } + + override open func mouseDragged(with theEvent: NSEvent) { + super.mouseDragged(with: theEvent) + if (mov_start.x == 0 || mov_start.y == 0) || (frame.width > magnifiedSize.width && frame.height > magnifiedSize.height) { + return + } + var current = convert(theEvent.locationInWindow, from: nil) + current = NSMakePoint(current.x - mov_start.x, current.y - mov_start.y) + + let adjust = self.adjust(with: NSMakePoint(mov_content_start.x + current.x, mov_content_start.y + current.y)) + + var point = contentView.frame.origin + if magnifiedSize.width > frame.width { + point.x = adjust.x + } + if magnifiedSize.height > frame.height { + point.y = adjust.y + } + + contentView.setFrameOrigin(point) + + + } + + private func adjust(with point:NSPoint, adjustX: Bool = true, adjustY: Bool = true) -> NSPoint { + var point = point + if adjustX { + point.x = floorToScreenPixels(backingScaleFactor, max(min(0, point.x), point.x + (frame.width - (point.x + magnifiedSize.width)))) + } + if adjustY { + point.y = floorToScreenPixels(backingScaleFactor, max(min(0, point.y), point.y + (frame.height - (point.y + magnifiedSize.height)))) + } + return point + } + + override open func scrollWheel(with event: NSEvent) { + + if magnify == minMagnify { + super.scrollWheel(with: event) + //return + } + + if event.type == .smartMagnify || event.type == .magnify { + return + } + + + let content_f = contentView.frame.origin + if (content_f.x == 0 && event.scrollingDeltaX > 0) || (content_f.x == (frame.width - magnifiedSize.width) && event.scrollingDeltaX < 0) { + // super.scrollWheel(with: event) + // return + } + + if (content_f.y == 0 && event.scrollingDeltaY < 0) || (content_f.y == (frame.height - magnifiedSize.height) && event.scrollingDeltaY > 0) { + // super.scrollWheel(with: event) + // return + } + + var point = content_f + let adjust = self.adjust(with: NSMakePoint(content_f.x + event.scrollingDeltaX, content_f.y + -event.scrollingDeltaY)) + if event.scrollingDeltaX != 0 && magnifiedSize.width > frame.width { + point.x = adjust.x + } + if event.scrollingDeltaY != 0 && magnifiedSize.height > frame.height { + point.y = adjust.y + } + if point.equalTo(content_f) { + // super.scrollWheel(with: event) + return + } + + contentView.setFrameOrigin(point) + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + + open func mouseInside() -> Bool { + return super._mouseInside() + } + + + public var mouseInContent:Bool { + if let window = window { + let point = window.mouseLocationOutsideOfEventStream + return NSPointInRect(convert(point, from: nil), contentView.frame) + } + return false + } + + +} + diff --git a/submodules/TGUIKit/TGUIKit/MajorNavigationController.swift b/submodules/TGUIKit/TGUIKit/MajorNavigationController.swift new file mode 100644 index 0000000000..fc69511e69 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/MajorNavigationController.swift @@ -0,0 +1,383 @@ +// +// SingleChatNavigationController.swift +// Telegram-Mac +// +// Created by keepcoder on 13/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + + +public protocol MajorControllerListener : class { + func navigationWillShowMajorController(_ controller:ViewController); +} + +open class MajorNavigationController: NavigationViewController, SplitViewDelegate { + + public var alwaysAnimate: Bool = false + private var majorClass:AnyClass + private var defaultEmpty:ViewController + private var listeners:[WeakReference] = [] + + private let container:GenericViewController = GenericViewController() + + override var containerView:BackgroundView { + get { + return container.genericView + } + set { + super.containerView = newValue + } + } + + + + open override func loadView() { + super.loadView() + + genericView.setProportion(proportion: SplitProportion(min:380, max: .greatestFiniteMagnitude), state: .single) + + controller._frameRect = bounds + controller.viewWillAppear(false) + controller.navigationController = self + + containerView.addSubview(navigationBar) + containerView.frame = bounds + navigationBar.frame = NSMakeRect(0, 0, containerView.frame.width, controller.bar.height) + controller.view.frame = NSMakeRect(0, controller.bar.height , containerView.frame.width, containerView.frame.height - controller.bar.height) + + navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: .none, animationStyle: controller.animationStyle, liveSwiping: false) + + containerView.addSubview(controller.view) + Queue.mainQueue().justDispatch { + self.controller.viewDidAppear(false) + } + + containerView.customHandler.layout = { [weak self] view in + guard let `self` = self else { + return + } + self.navigationBar.frame = NSMakeRect(self.navigationBar.frame.minX, self.navigationBar.frame.minY, self.controller.frame.width, self.navigationBar.frame.height) + self.navigationRightBorder.frame = NSMakeRect(view.frame.width - .borderSize, 0, .borderSize, self.navigationBar.frame.height) + } + } + + public func closeSidebar() { + genericView.removeProportion(state: .dual) + genericView.setProportion(proportion: SplitProportion(min:380, max: .greatestFiniteMagnitude), state: .single) + genericView.layout() + + viewDidChangedNavigationLayout(.single) + } + + override var containerSize: NSSize { + switch genericView.state { + case .dual: + return NSMakeSize(frame.width - 350, frame.height) + default: + return super.containerSize + } + } + + open override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + + self.genericView.setFrameSize(size) + //_ = atomicSize.swap(size) + // self.genericView.frame = NSMakeRect(0, barInset, <#T##w: CGFloat##CGFloat#>, <#T##h: CGFloat##CGFloat#>) + } + + public init(_ majorClass:AnyClass, _ empty:ViewController, _ window: Window) { + self.majorClass = majorClass + self.defaultEmpty = empty + container.bar = .init(height: 0) + assert(majorClass is ViewController.Type) + + super.init(empty, window) + } + + open override func currentControllerDidChange() { + if let view = view as? DraggingView { + view.controller = controller + } + for listener in listeners { + listener.value?.navigationWillChangeController() + } + } + + open override func viewDidLoad() { + //super.viewDidLoad() + + genericView.delegate = self + genericView.layout() + } + + public func splitViewDidNeedSwapToLayout(state: SplitViewState) { + genericView.removeAllControllers(); + + switch state { + case .dual: + genericView.addController(controller: container, proportion: SplitProportion(min: 800, max: .greatestFiniteMagnitude)) + if let sidebar = sidebar { + genericView.addController(controller: sidebar, proportion: SplitProportion(min:350, max: 350)) + } + case .single: + genericView.addController(controller: container, proportion: SplitProportion(min: 800, max: .greatestFiniteMagnitude)) + default: + break + } + controller.viewDidChangedNavigationLayout(state) + viewDidResized(self.frame.size) + } + + public func splitViewDidNeedMinimisize(controller: ViewController) { + + } + + public func splitViewDidNeedFullsize(controller: ViewController) { + + } + + public func splitViewIsCanMinimisize() -> Bool { + return false; + } + + public func splitViewDrawBorder() -> Bool { + return true + } + + open override func viewClass() ->AnyClass { + return DraggingView.self + } + + public var genericView:SplitView { + return view as! SplitView + } + + override open func push(_ controller: ViewController, _ animated: Bool = true, style:ViewControllerStyle? = nil) { + + assertOnMainThread() + + if controller.abolishWhenNavigationSame, controller.className == self.controller.className { + return + } + + controller.navigationController = self + + + if genericView.nextLayout == .dual { + controller.loadViewIfNeeded(NSMakeRect(0, 0, genericView.frame.width - 350, genericView.frame.height)) + } else { + controller.loadViewIfNeeded(genericView.bounds) + } + + self.genericView.update() + + self.controller.ableToNextController(controller, { [weak self] controller, result in + if result { + self?.pushDisposable.set((controller.ready.get() |> deliverOnMainQueue |> take(1)).start(next: {[weak self] _ in + if let strongSelf = self { + + + let isMajorController = controller.className == NSStringFromClass(strongSelf.majorClass) + let removeAnimateFlag = strongSelf.stackCount == 2 && isMajorController && !strongSelf.alwaysAnimate + + if isMajorController { + for controller in strongSelf.stack { + controller.didRemovedFromStack() + } + strongSelf.stack.removeAll() + + strongSelf.stack.append(strongSelf.empty) + } + + if let index = strongSelf.stack.firstIndex(of: controller) { + strongSelf.stack.remove(at: index) + } + + strongSelf.stack.append(controller) + + let anim = animated && (!isMajorController || strongSelf.controller != strongSelf.defaultEmpty) && !removeAnimateFlag + + let newStyle:ViewControllerStyle + if let style = style { + newStyle = style + } else { + newStyle = anim ? .push : .none + } + + CATransaction.begin() + strongSelf.show(controller, newStyle) + CATransaction.commit() + } + })) + } + }) + + + } + + + open override func back(animated:Bool = true, forceAnimated: Bool = false, animationStyle: ViewControllerStyle = .pop) -> Void { + if !isLocked, let last = stack.last, last.invokeNavigationBack() { + if stackCount > 1 { + let ncontroller = stack[stackCount - 2] + let removeAnimateFlag = ((ncontroller == defaultEmpty || !animated) && !alwaysAnimate) && !forceAnimated + last.didRemovedFromStack() + stack.removeLast() + + show(ncontroller, removeAnimateFlag ? .none : animationStyle) + } else { + doSomethingOnEmptyBack?() + } + } + + } + + + + + public func removeExceptMajor() { + let index = stack.firstIndex(where: { current in + return current.className == NSStringFromClass(self.majorClass) + }) + if let index = index { + while stack.count > index { + stack.removeLast() + } + } else { + while stack.count > 1 { + stack.removeLast() + } + } + } + + open override var responderPriority: HandlerPriority { + return empty.responderPriority + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.window?.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.escapeKeyAction() + } + return .rejected + }, with: self, for: .Escape, priority: self.responderPriority) + + self.window?.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.returnKeyAction() + } + return .rejected + }, with: self, for: .Return, priority: self.responderPriority) + + + self.window?.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.backKeyAction() + } + return .rejected + }, with: self, for: .LeftArrow, priority: self.responderPriority) + + self.window?.set(handler: { [weak self] in + if let strongSelf = self { + return strongSelf.nextKeyAction() + } + return .rejected + }, with: self, for: .RightArrow, priority: self.responderPriority) + + + + } + + + open override var canSwipeBack: Bool { + return (self.genericView.state == .single || self.stackCount > 2) + } + + + + open override func viewWillDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.window?.removeAllHandlers(for: self) + self.window?.removeAllHandlers(for: self.containerView) + } + + open override func backKeyAction() -> KeyHandlerResult { + let status:KeyHandlerResult = stackCount > 1 ? .invoked : .rejected + if isLocked { + return .invoked + } + let cInvoke = self.controller.backKeyAction() + + if cInvoke == .invokeNext { + return .invokeNext + } else if cInvoke == .invoked { + return .invoked + } + self.back() + return status + } + + open override func nextKeyAction() -> KeyHandlerResult { + if isLocked { + return .invoked + } + return self.controller.nextKeyAction() + } + + + open override func escapeKeyAction() -> KeyHandlerResult { + let status:KeyHandlerResult = stackCount > 1 ? .invoked : .rejected + if isLocked { + return .invoked + } + let cInvoke = self.controller.escapeKeyAction() + + if cInvoke == .invokeNext { + return .invokeNext + } else if cInvoke == .invoked { + return .invoked + } + self.back() + return status + } + + open override func returnKeyAction() -> KeyHandlerResult { + let status:KeyHandlerResult = .rejected + + let cInvoke = self.controller.returnKeyAction() + + if cInvoke == .invokeNext { + return .invokeNext + } else if cInvoke == .invoked { + return .invoked + } + return status + } + + public func add(listener:WeakReference) -> Void { + let index = listeners.firstIndex(where: { (weakView) -> Bool in + return listener.value == weakView.value + }) + if index == nil { + listeners.append(listener) + } + } + + public func remove(listener:WeakReference) -> Void { + + let index = listeners.firstIndex(where: { (weakView) -> Bool in + return listener.value == weakView.value + }) + + if let index = index { + listeners.remove(at: index) + } + } + +} diff --git a/submodules/TGUIKit/TGUIKit/MergeLists.swift b/submodules/TGUIKit/TGUIKit/MergeLists.swift new file mode 100644 index 0000000000..7c1af1ad85 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/MergeLists.swift @@ -0,0 +1,397 @@ +import Foundation + +public protocol Identifiable { + associatedtype T: Hashable, Equatable + var stableId: T { get } +} + +public func mergeListsStable(leftList: [T], rightList: [T]) -> ([Int], [(Int, T, Int?)]) where T: Comparable, T: Equatable, T: Identifiable { + var removeIndices: [Int] = [] + var insertItems: [(Int, T, Int?)] = [] + + var currentList = leftList + + var i = 0 + var j = 0 + while true { + let left: T? = i < currentList.count ? currentList[i] : nil + let right: T? = j < rightList.count ? rightList[j] : nil + + if let left = left, let right = right { + if left == right { + i += 1 + j += 1 + } else if left < right { + removeIndices.append(i) + i += 1 + } else { + j += 1 + } + } else if let _ = left { + removeIndices.append(i) + i += 1 + } else if let _ = right { + j += 1 + } else { + break + } + } + + for index in removeIndices.reversed() { + currentList.remove(at: index) + } + + var previousIndices: [T.T: Int] = [:] + i = 0 + for left in leftList { + previousIndices[left.stableId] = i + i += 1 + } + + i = 0 + j = 0 + while true { + let left: T? = i < currentList.count ? currentList[i] : nil + let right: T? = j < rightList.count ? rightList[j] : nil + + if let left = left, let right = right { + if left == right { + i += 1 + j += 1 + } else if left > right { + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + i += 1 + j += 1 + } else { + i += 1 + } + } else if let _ = left { + i += 1 + } else if let right = right { + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + i += 1 + j += 1 + } else { + break + } + } + + assert(currentList == rightList, "currentList == rightList") + + return (removeIndices, insertItems) +} + +public func mergeListsStableWithUpdates(leftList: [T], rightList: [T], allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { + var removeIndices: [Int] = [] + var insertItems: [(Int, T, Int?)] = [] + var updatedIndices: [(Int, T, Int)] = [] + + + var currentList = leftList + + var i = 0 + var previousIndices: [T.T: Int] = [:] + for left in leftList { + previousIndices[left.stableId] = i + i += 1 + } + + i = 0 + var j = 0 + while true { + let left: T? = i < currentList.count ? currentList[i] : nil + let right: T? = j < rightList.count ? rightList[j] : nil + + if let left = left, let right = right { + if left.stableId == right.stableId && (left != right || allUpdated) { + updatedIndices.append((i, right, previousIndices[left.stableId]!)) + i += 1 + j += 1 + } else { + if left == right && !allUpdated { + i += 1 + j += 1 + } else if left < right { + removeIndices.append(i) + i += 1 + } else if !(left > right) { + removeIndices.append(i) + i += 1 + } else { + j += 1 + } + } + } else if let _ = left { + removeIndices.append(i) + i += 1 + } else if let _ = right { + j += 1 + } else { + break + } + } + + //print("remove:\n\(removeIndices)") + + for index in removeIndices.reversed() { + currentList.remove(at: index) + for i in 0 ..< updatedIndices.count { + if updatedIndices[i].0 >= index { + updatedIndices[i].0 -= 1 + } + } + } + + /*print("\n current after removes:\n") + m = 0 + for right in currentList { + print("\(m): \(right.stableId)") + m += 1 + } + + print("update:\n\(updatedIndices.map({ "\($0.0), \($0.1.stableId) (was \($0.2)))" }))")*/ + + i = 0 + j = 0 + var k = 0 + while true { + let left: T? + + //print("i=\(i), j=\(j), k=\(k)") + + if k < updatedIndices.count && updatedIndices[k].0 < i { + //print("updated[k=\(k)]=\(updatedIndices[k].0) right { + //print("\(left.stableId)>\(right.stableId)") + //print("insert \(right.stableId) at \(i)") + //print("i++, j++") + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + if k < updatedIndices.count { + for l in k ..< updatedIndices.count { + updatedIndices[l] = (updatedIndices[l].0 + 1, updatedIndices[l].1, updatedIndices[l].2) + } + } + + i += 1 + j += 1 + } else { + //print("\(left.stableId)<\(right.stableId)") + //print("i++") + i += 1 + } + } else if let _ = left { + //print("\(left!.stableId)>nil") + //print("i++") + i += 1 + } else if let right = right { + //print("nil<\(right.stableId)") + //print("insert \(right.stableId) at \(i)") + //print("i++") + //print("j++") + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + + if k < updatedIndices.count { + for l in k ..< updatedIndices.count { + updatedIndices[l] = (updatedIndices[l].0 + 1, updatedIndices[l].1, updatedIndices[l].2) + } + } + + i += 1 + j += 1 + } else { + break + } + } + + for (index, item, _) in updatedIndices { + currentList[index] = item + } + + + + return (removeIndices, insertItems, updatedIndices) +} + + + + +public func mergeListsStableWithUpdatesReversed(leftList: [T], rightList: [T], allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { + var removeIndices: [Int] = [] + var insertItems: [(Int, T, Int?)] = [] + var updatedIndices: [(Int, T, Int)] = [] + + #if (arch(i386) || arch(x86_64)) && os(iOS) + var existingStableIds: [T.T: T] = [:] + for item in leftList { + if let _ = existingStableIds[item.stableId] { + assertionFailure() + } else { + existingStableIds[item.stableId] = item + } + } + existingStableIds.removeAll() + for item in rightList { + if let other = existingStableIds[item.stableId] { + print("\(other) has the same stableId as \(item): \(item.stableId)") + assertionFailure() + } else { + existingStableIds[item.stableId] = item + } + } + #endif + + var currentList = leftList + + var i = 0 + var previousIndices: [T.T: Int] = [:] + for left in leftList { + previousIndices[left.stableId] = i + i += 1 + } + + i = 0 + var j = 0 + while true { + let left: T? = i < currentList.count ? currentList[i] : nil + let right: T? = j < rightList.count ? rightList[j] : nil + + if let left = left, let right = right { + if left.stableId == right.stableId && (left != right || allUpdated) { + updatedIndices.append((i, right, previousIndices[left.stableId]!)) + i += 1 + j += 1 + } else { + if left == right && !allUpdated { + i += 1 + j += 1 + } else if left > right { + removeIndices.append(i) + i += 1 + } else if !(left < right) { + removeIndices.append(i) + i += 1 + } else { + j += 1 + } + } + } else if let _ = left { + removeIndices.append(i) + i += 1 + } else if let _ = right { + j += 1 + } else { + break + } + } + + //print("remove:\n\(removeIndices)") + + for index in removeIndices.reversed() { + currentList.remove(at: index) + for i in 0 ..< updatedIndices.count { + if updatedIndices[i].0 >= index { + updatedIndices[i].0 -= 1 + } + } + } + + i = 0 + j = 0 + var k = 0 + while true { + let left: T? + + if k < updatedIndices.count && updatedIndices[k].0 < i { + k += 1 + } + + if k < updatedIndices.count { + if updatedIndices[k].0 == i { + left = updatedIndices[k].1 + } else { + left = i < currentList.count ? currentList[i] : nil + } + } else { + left = i < currentList.count ? currentList[i] : nil + } + + let right: T? = j < rightList.count ? rightList[j] : nil + + if let left = left, let right = right { + if left == right { + i += 1 + j += 1 + } else if left < right { + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + if k < updatedIndices.count { + for l in k ..< updatedIndices.count { + updatedIndices[l] = (updatedIndices[l].0 + 1, updatedIndices[l].1, updatedIndices[l].2) + } + } + + i += 1 + j += 1 + } else { + i += 1 + } + } else if let _ = left { + i += 1 + } else if let right = right { + let previousIndex = previousIndices[right.stableId] + insertItems.append((i, right, previousIndex)) + currentList.insert(right, at: i) + + if k < updatedIndices.count { + for l in k ..< updatedIndices.count { + updatedIndices[l] = (updatedIndices[l].0 + 1, updatedIndices[l].1, updatedIndices[l].2) + } + } + + i += 1 + j += 1 + } else { + break + } + } + + for (index, item, _) in updatedIndices { + currentList[index] = item + } + + assert(currentList == rightList, "currentList == rightList") + + return (removeIndices, insertItems, updatedIndices) +} + diff --git a/submodules/TGUIKit/TGUIKit/Modal.swift b/submodules/TGUIKit/TGUIKit/Modal.swift new file mode 100644 index 0000000000..4900d76201 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Modal.swift @@ -0,0 +1,932 @@ +// +// Modal.swift +// TGUIKit +// +// Created by keepcoder on 26/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +private class ModalBackground : Control { + var isOverlay: Bool = false + var canRedirectScroll: Bool = false + fileprivate override func scrollWheel(with event: NSEvent) { + if canRedirectScroll { + super.scrollWheel(with: event) + } + } + override func cursorUpdate(with event: NSEvent) { + NSCursor.arrow.set() + } + + override func mouseMoved(with event: NSEvent) { + + } + + override func mouseEntered(with event: NSEvent) { + + } + override func mouseExited(with event: NSEvent) { + + } + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return false + } + + deinit { + var bp:Int = 0 + bp += 1 + } +} + +private var activeModals:[WeakReference] = [] + +public class ModalInteractions { + let accept:(()->Void)? + let cancel:(()->Void)? + let acceptTitle:String + let cancelTitle:String? + let drawBorder:Bool + let height:CGFloat + var enables:((Bool)->Void)? = nil + let alignCancelLeft: Bool + + var doneUpdatable:(((TitleButton)->Void)->Void)? = nil + var cancelUpdatable:(((TitleButton)->Void)->Void)? = nil + let singleButton: Bool + public init(acceptTitle:String, accept:(()->Void)? = nil, cancelTitle:String? = nil, cancel:(()->Void)? = nil, drawBorder:Bool = false, height:CGFloat = 50, alignCancelLeft: Bool = false, singleButton: Bool = false) { + self.drawBorder = drawBorder + self.accept = accept + self.cancel = cancel + self.acceptTitle = acceptTitle + self.cancelTitle = cancelTitle + self.height = height + self.alignCancelLeft = alignCancelLeft + self.singleButton = singleButton + } + + public func updateEnables(_ enable:Bool) -> Void { + if let enables = enables { + enables(enable) + } + } + + public func updateDone(_ f:@escaping (TitleButton) -> Void) -> Void { + doneUpdatable?(f) + } + public func updateCancel(_ f:@escaping(TitleButton) -> Void) -> Void { + cancelUpdatable?(f) + } + +} + +private class ModalInteractionsContainer : View { + let acceptView:TitleButton + let cancelView:TitleButton? + let interactions:ModalInteractions + let borderView:View? + + + + override func mouseUp(with event: NSEvent) { + + } + override func mouseDown(with event: NSEvent) { + + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + acceptView.style = ControlStyle(font:.medium(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) + cancelView?.style = ControlStyle(font:.medium(.text), foregroundColor: theme.colors.accent, backgroundColor: theme.colors.background) + borderView?.backgroundColor = theme.colors.border + backgroundColor = theme.colors.background + + if interactions.singleButton { + acceptView.set(background: theme.colors.background, for: .Normal) + acceptView.set(background: theme.colors.grayForeground.withAlphaComponent(0.25), for: .Highlight) + } else { + acceptView.set(background: theme.colors.background, for: .Normal) + } + } + + init(interactions:ModalInteractions, modal:Modal) { + self.interactions = interactions + acceptView = TitleButton() + acceptView.style = ControlStyle(font:.medium(.text), foregroundColor: presentation.colors.accent, backgroundColor: presentation.colors.background) + acceptView.set(text: interactions.acceptTitle, for: .Normal) + acceptView.disableActions() + _ = acceptView.sizeToFit(NSZeroSize, NSMakeSize(0, interactions.height - 10), thatFit: true) + if let cancelTitle = interactions.cancelTitle { + cancelView = TitleButton() + cancelView?.style = ControlStyle(font:.medium(.text), foregroundColor: presentation.colors.accent, backgroundColor: presentation.colors.background) + cancelView?.set(text: cancelTitle, for: .Normal) + _ = cancelView?.sizeToFit(NSZeroSize, NSMakeSize(0, interactions.height - 10), thatFit: true) + + } else { + cancelView = nil + } + if interactions.singleButton { + acceptView.set(background: presentation.colors.background, for: .Normal) + acceptView.set(background: presentation.colors.grayForeground.withAlphaComponent(0.25), for: .Highlight) + } else { + acceptView.set(background: presentation.colors.background, for: .Normal) + } + + if interactions.drawBorder { + borderView = View() + borderView?.backgroundColor = presentation.colors.border + } else { + borderView = nil + } + + + + super.init() + self.backgroundColor = presentation.colors.background + if let cancel = interactions.cancel { + cancelView?.set(handler: { _ in + cancel() + }, for: .Click) + } else { + cancelView?.set(handler: { [weak modal] _ in + modal?.controller?.close() + }, for: .Click) + } + + if let accept = interactions.accept { + acceptView.set(handler: { _ in + accept() + }, for: .Click) + } else { + acceptView.set(handler: { [weak modal] _ in + modal?.controller?.close() + }, for: .Click) + + } + + addSubview(acceptView) + if let cancelView = cancelView { + addSubview(cancelView) + } + if let borderView = borderView { + addSubview(borderView) + } + + interactions.enables = { [weak self] enable in + self?.acceptView.isEnabled = enable + self?.acceptView.apply(state: .Normal) + } + + interactions.doneUpdatable = { [weak self] f in + if let strongSelf = self { + f(strongSelf.acceptView) + } + self?.updateDone() + } + interactions.cancelUpdatable = { [weak self] f in + if let strongSelf = self, let cancelView = strongSelf.cancelView { + f(cancelView) + } + self?.updateCancel() + } + + + } + + public func updateDone() { + _ = acceptView.sizeToFit(NSZeroSize, NSMakeSize(0, interactions.height - 10), thatFit: true) + needsLayout = true + } + + public func updateCancel() { + _ = cancelView?.sizeToFit(NSZeroSize, NSMakeSize(0, interactions.height - 10), thatFit: true) + needsLayout = true + } + public func updateThrid(_ text:String) { + acceptView.set(text: text, for: .Normal) + _ = acceptView.sizeToFit(NSZeroSize, NSMakeSize(0, interactions.height - 10), thatFit: true) + + needsLayout = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + fileprivate override func layout() { + super.layout() + + if self.interactions.singleButton { + acceptView.frame = bounds + } else { + acceptView.centerY(x:frame.width - acceptView.frame.width - 30) + if let cancelView = cancelView { + if interactions.alignCancelLeft { + cancelView.centerY(x: 30) + } else { + cancelView.centerY(x:acceptView.frame.minX - cancelView.frame.width - 30) + } + } + } + + + borderView?.frame = NSMakeRect(0, 0, frame.width, .borderSize) + } + + + +} + + +private final class ModalHeaderView: View { + let titleView: TextView = TextView() + private var subtitleView: TextView? + var leftButton: ImageButton? + var rightButton: ImageButton? + weak var controller:ModalViewController? + required init(frame frameRect: NSRect, data: (left: ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)) { + super.init(frame: frameRect) + + titleView.update(TextViewLayout(.initialize(string: data.center?.title, color: presentation.colors.text, font: .medium(.title)), maximumNumberOfLines: 1)) + titleView.userInteractionEnabled = false + titleView.isSelectable = false + border = [.Bottom] + + if let subtitle = data.center?.subtitle { + subtitleView = TextView() + subtitleView!.update(TextViewLayout(.initialize(string: subtitle, color: presentation.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1)) + subtitleView!.userInteractionEnabled = false + subtitleView!.isSelectable = false + addSubview(subtitleView!) + } + + if let right = data.right { + rightButton = ImageButton() + if let image = right.image { + rightButton?.set(image: image, for: .Normal) + } + rightButton?.set(handler: { _ in + right.handler?() + }, for: .Click) + + _ = rightButton?.sizeToFit() + addSubview(rightButton!) + } + + if let left = data.left { + leftButton = ImageButton() + if let image = left.image { + leftButton?.set(image: image, for: .Normal) + } + leftButton?.set(handler: { _ in + left.handler?() + }, for: .Click) + + _ = leftButton?.sizeToFit() + addSubview(leftButton!) + } + + addSubview(titleView) + } + + override func layout() { + super.layout() + var additionalSize: CGFloat = 0 + if let rightButton = rightButton { + additionalSize += rightButton.frame.width * 2 + rightButton.centerY(x: frame.width - rightButton.frame.width - 20) + } + + if let leftButton = leftButton { + additionalSize += leftButton.frame.width * 2 + leftButton.centerY(x: 20) + } + + titleView.layout?.measure(width: frame.width - 40 - additionalSize) + titleView.update(titleView.layout) + + subtitleView?.layout?.measure(width: frame.width - 40 - additionalSize) + subtitleView?.update(subtitleView?.layout) + + if let subtitleView = subtitleView { + let center = frame.midY + titleView.centerX(y: center - titleView.frame.height - 1) + subtitleView.centerX(y: center + 1) + } else { + titleView.center() + } + + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + guard let controller = controller else { + return + } + + background = theme.colors.background + borderColor = theme.colors.border + + let header = controller.modalHeader + if let header = header { + titleView.update(TextViewLayout(.initialize(string: header.center?.title, color: theme.colors.text, font: .medium(.title)), maximumNumberOfLines: 1)) + subtitleView?.update(TextViewLayout(.initialize(string: header.center?.subtitle, color: theme.colors.grayText, font: .normal(.text)), maximumNumberOfLines: 1)) + + if let image = header.right?.image { + rightButton?.set(image: image, for: .Normal) + } + if let image = header.left?.image { + leftButton?.set(image: image, for: .Normal) + } + } + needsLayout = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +private class ModalContainerView: View { + + + override func mouseMoved(with event: NSEvent) { + + } + + override func mouseEntered(with event: NSEvent) { + + } + override func mouseExited(with event: NSEvent) { + + } + fileprivate override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + } + + fileprivate override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + } +} + +extension Modal : ObservableViewDelegate { + +} + +public class Modal: NSObject { + private let visualEffectView: NSVisualEffectView? + fileprivate let background:ModalBackground + fileprivate var controller:ModalViewController? + private var container:ModalContainerView! + public let window:Window + private let disposable:MetaDisposable = MetaDisposable() + private var interactionsView:ModalInteractionsContainer? + private var headerView:ModalHeaderView? + + public let interactions:ModalInteractions? + fileprivate let animated: Bool + private let isOverlay: Bool + private let animationType: ModalAnimationType + private let parentView: NSView? + public init(controller:ModalViewController, for window:Window, animated: Bool = true, isOverlay: Bool, animationType: ModalAnimationType, parentView: NSView? = nil) { + self.parentView = parentView + self.animationType = animationType + self.controller = controller + self.window = window + self.animated = animated + self.isOverlay = isOverlay + background = ModalBackground() + background.isOverlay = isOverlay + background.canRedirectScroll = controller.redirectMouseAfterClosing + background.backgroundColor = controller.background + background.layer?.disableActions() + self.interactions = controller.modalInteractions + if controller.isVisualEffectBackground { + self.visualEffectView = NSVisualEffectView(frame: NSZeroRect) + self.visualEffectView!.material = .ultraDark + self.visualEffectView!.blendingMode = .withinWindow + self.visualEffectView!.state = .active + self.visualEffectView?.wantsLayer = true + } else { + self.visualEffectView = nil + } + super.init() + controller.modal = self + if let interactions = interactions { + interactionsView = ModalInteractionsContainer(interactions: interactions, modal:self) + interactionsView?.frame = NSMakeRect(0, controller.bounds.height, controller.bounds.width, interactions.height) + } + if let header = controller.modalHeader { + headerView = ModalHeaderView(frame: NSMakeRect(0, 0, controller.bounds.width, 50), data: header) + headerView?.backgroundColor = controller.headerBackground + headerView?.controller = controller + } + + if controller.isFullScreen { + controller._frameRect = topView.bounds + } + + container = ModalContainerView(frame: containerRect) + container.autoresizingMask = [] + container.autoresizesSubviews = true + container.layer?.cornerRadius = 10 + container.layer?.shouldRasterize = true + container.layer?.rasterizationScale = CGFloat(System.backingScale) + container.backgroundColor = controller.containerBackground + + if !controller.contentBelowBackground { + container.addSubview(controller.view) + } else { + controller.loadViewIfNeeded() + } + + if let headerView = headerView { + container.addSubview(headerView) + } + + if let interactionsView = interactionsView { + container.addSubview(interactionsView) + } + + + background.addSubview(container) + + background.userInteractionEnabled = controller.handleEvents + + if controller.handleEvents { + window.set(responder: { [weak controller] () -> NSResponder? in + return controller?.firstResponder() + }, with: self, priority: controller.responderPriority) + + if controller.handleAllEvents { + window.set(handler: { [weak controller] () -> KeyHandlerResult in + if let controller = controller, controller.redirectMouseAfterClosing { + controller.close() + return .rejected + } + return .invokeNext + }, with: self, for: .All, priority: controller.responderPriority) + } + + window.set(escape: {[weak self] () -> KeyHandlerResult in + if self?.controller?.escapeKeyAction() == .rejected { + self?.controller?.close() + } + return .invoked + }, with: self, priority: controller.responderPriority) + + window.set(handler: { [weak self] () -> KeyHandlerResult in + if let controller = self?.controller { + return controller.returnKeyAction() + } + return .invokeNext + }, with: self, for: .Return, priority: controller.responderPriority) + } + + var isDown: Bool = false + + background.set(handler: { [weak self] control in + guard let controller = self?.controller, let `self` = self else { return } + + if control.mouseInside() && !controller.view._mouseInside() && !self.container.mouseInside() { + isDown = true + } + + if controller.redirectMouseAfterClosing, let event = NSApp.currentEvent { + control.performSuperMouseDown(event) + } + }, for: .Down) + + background.set(handler: { [weak self] control in + guard let controller = self?.controller, let `self` = self else { return } + if controller.closable, !controller.view._mouseInside() && !self.container.mouseInside(), isDown { + controller.close() + } + if controller.redirectMouseAfterClosing, let event = NSApp.currentEvent { + control.performSuperMouseUp(event) + } + + isDown = false + + }, for: .Click) + + if controller.dynamicSize { + background.customHandler.size = { [weak self] (size) in + self?.controller?.measure(size: size) + } + } + + activeModals.append(WeakReference(value: self)) + } + + private var topView: NSView { + if let parentView = self.parentView { + return parentView + } else { + return self.window.contentView! + } + } + + public var containerView: NSView { + return self.container + } + + func observableView(_ view: NSView, didAddSubview: NSView) { + if isOverlay { + var subviews = self.window.contentView!.subviews + if let index = subviews.firstIndex(of: self.background) { + subviews.remove(at: index) + subviews.append(self.background) + } + self.window.contentView?.subviews = subviews + } + } + + func observableview(_ view: NSView, willRemoveSubview: NSView) { + + } + + public func resize(with size:NSSize, animated:Bool = true) { + let focus:NSRect + + var headerOffset: CGFloat = 0 + if let headerView = headerView { + headerOffset += headerView.frame.height + headerView.setFrameSize(size.width, headerView.frame.height) + } + + if let interactions = controller?.modalInteractions { + focus = background.focus(NSMakeSize(size.width, size.height + interactions.height + headerOffset)) + interactionsView?.change(pos: NSMakePoint(0, size.height + headerOffset), animated: animated) + interactionsView?.setFrameSize(NSMakeSize(size.width, interactions.height)) + } else { + focus = background.focus(NSMakeSize(size.width, size.height + headerOffset)) + } + + + if focus != container.frame { + CATransaction.begin() + container.change(size: focus.size, animated: animated) + container.change(pos: focus.origin, animated: animated) + + controller?.view._change(size: size, animated: animated) + controller?.view._change(pos: NSMakePoint(0, headerOffset), animated: animated) + CATransaction.commit() + } + controller?.didResizeView(size, animated: animated) + + } + + public func updateLocalizationAndTheme(theme: PresentationTheme) { + self.interactionsView?.updateLocalizationAndTheme(theme: theme) + self.headerView?.updateLocalizationAndTheme(theme: theme) + } + + private var containerRect:NSRect { + if let controller = controller { + var containerRect = controller.bounds + if let interactions = controller.modalInteractions { + containerRect.size.height += interactions.height + } + if let headerView = headerView { + containerRect.size.height += headerView.frame.height + } + return containerRect + } + return NSZeroRect + } + + public func close(_ callAcceptInteraction:Bool = false, animationType: ModalAnimationCloseBehaviour = .common) ->Void { + window.removeAllHandlers(for: self) + controller?.viewWillDisappear(true) + + for i in stride(from: activeModals.count - 1, to: -1, by: -1) { + if activeModals[i].value == self { + activeModals.remove(at: i) + break + } + } + + let animateBackground = !unhideModalIfNeeded() || self.controller?.containerBackground == .clear + + if callAcceptInteraction, let interactionsView = interactionsView { + interactionsView.interactions.accept?() + } + let background: NSView + if let visualEffectView = self.visualEffectView { + background = visualEffectView + } else { + background = self.background + } + if let controller = controller, controller.contentBelowBackground { + controller.view._change(opacity: 0, animated: true, removeOnCompletion: false, duration: 0.2, timingFunction: .spring, completion: { [weak self] _ in + self?.controller?.view.removeFromSuperview() + }) + } + + if animateBackground { + background.layer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: {[weak self, weak background] complete in + if let stongSelf = self { + background?.removeFromSuperview() + stongSelf.controller?.view.removeFromSuperview() + stongSelf.controller?.viewDidDisappear(true) + stongSelf.controller?.modal = nil + stongSelf.controller = nil + } + }) + } else if let lastActive = activeModals.last?.value { + background.removeFromSuperview() + self.controller?.view.removeFromSuperview() + self.controller?.viewDidDisappear(true) + self.controller?.modal = nil + self.controller = nil + lastActive.containerView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + + + switch animationType { + case .common: + break + case let .scaleToRect(newRect): + let view = self.container! + let oldRect = self.container.frame + view.layer?.animatePosition(from: oldRect.origin, to: newRect.origin, duration: 0.25, timingFunction: .spring, removeOnCompletion: false) + view.layer?.animateScaleX(from: 1, to: newRect.width / oldRect.width, duration: 0.25, timingFunction: .spring, removeOnCompletion: false) + view.layer?.animateScaleY(from: 1, to: newRect.height / oldRect.height, duration: 0.25, timingFunction: .spring, removeOnCompletion: false) + } + } + + private var subview: NSView? + + public func removeSubview(animated: Bool) { + if let subview = self.subview { + if animated { + subview.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak subview] _ in + subview?.removeFromSuperview() + }) + } else { + subview.removeFromSuperview() + } + } + self.subview = nil + } + + public func addSubview(_ view: NSView, animated: Bool) { + if let subview = self.subview { + if animated { + subview.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak subview] _ in + subview?.removeFromSuperview() + }) + } else { + subview.removeFromSuperview() + } + } + + view.frame = container.bounds + self.container.addSubview(view) + if animated { + view.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + self.subview = view + } + + deinit { + disposable.dispose() + (window.contentView as? ObervableView)?.remove(listener: self) + for i in stride(from: activeModals.count - 1, to: -1, by: -1) { + if activeModals[i].value == self { + activeModals.remove(at: i) + break + } + } + + } + + static func topModalController(_ window: Window) -> ModalViewController? { + for i in stride(from: activeModals.count - 1, to: -1, by: -1) { + if let modal = activeModals[i].value, modal.window === window { + return modal.controller + } + } + return nil + } + + public func show() -> Void { + // if let view + if let controller = controller { + disposable.set((controller.ready.get() |> take(1)).start(next: { [weak self, weak controller] ready in + if let strongSelf = self, let controller = controller { + let view = strongSelf.topView + if controller.contentBelowBackground { + view.addSubview(controller.view) + controller.view.center() + if strongSelf.animated { + controller.view.layer?.animateAlpha(from: 0.1, to: 1, duration: 0.4, timingFunction: .spring) + } + } + strongSelf.controller?.viewWillAppear(true) + strongSelf.visualEffectView?.frame = view.bounds + strongSelf.background.frame = view.bounds + if controller.isFullScreen { + strongSelf.container.frame = view.bounds + } else { + strongSelf.container.center() + } + strongSelf.background.background = controller.isFullScreen ? controller.containerBackground : controller.background + if strongSelf.animated { + if case .alpha = strongSelf.animationType { + } else { + strongSelf.container.layer?.animateAlpha(from: 0.1, to: 1.0, duration: 0.15, timingFunction: .spring) + } + if !controller.isFullScreen { + switch strongSelf.animationType { + case .bottomToCenter: + let origin = strongSelf.container.frame.origin + strongSelf.container.layer?.animatePosition(from: NSMakePoint(origin.x, origin.y + 100), to: origin, timingFunction: .spring) + case .scaleCenter: + strongSelf.container.layer?.animateScaleSpring(from: 0.7, to: 1.0, duration: 0.2, bounce: false) + case let .scaleFrom(oldRect): + let view = strongSelf.container! + let newRect = view.frame + view.layer?.animateAlpha(from: 0.1, to: 1.0, duration: 0.15, timingFunction: .spring) + view.layer?.animatePosition(from: oldRect.origin, to: newRect.origin, duration: 0.3, timingFunction: .spring) + view.layer?.animateScaleX(from: oldRect.width / newRect.width, to: 1, duration: 0.3, timingFunction: .spring) + view.layer?.animateScaleY(from: oldRect.height / newRect.height, to: 1, duration: 0.3, timingFunction: .spring) + case .alpha: + view.layer?.animateAlpha(from: 1.0, to: 1.0, duration: 0.15, timingFunction: .spring) + } + } + } + strongSelf.visualEffectView?.autoresizingMask = [.width,.height] + strongSelf.background.autoresizingMask = [.width,.height] + strongSelf.background.customHandler.layout = { [weak strongSelf] view in + strongSelf?.container.center() + } + + if controller.isFullScreen { + strongSelf.background.customHandler.size = { [weak strongSelf] size in + strongSelf?.container.setFrameSize(size) + } + } + + var belowView: NSView? + + for subview in view.subviews.reversed() { + if let subview = subview as? ModalBackground { + if subview.isOverlay { + belowView = subview + + } + } + } + + let background: NSView + if let visualEffectView = strongSelf.visualEffectView { + background = visualEffectView + visualEffectView.addSubview(strongSelf.background) + } else { + background = strongSelf.background + } + + if let belowView = belowView { + view.addSubview(background, positioned: .below, relativeTo: belowView) + } else { + view.addSubview(background) + } + if let value = strongSelf.controller?.becomeFirstResponder() { + if value { + _ = strongSelf.window.makeFirstResponder(strongSelf.controller?.firstResponder()) + } else { + _ = strongSelf.window.makeFirstResponder(nil) + } + } + let animatedBackground = strongSelf.animated && !hideBelowModalsIfNeeded(except: strongSelf) + + if animatedBackground { + strongSelf.background.layer?.animateAlpha(from: 0, to: 1, duration: 0.15, completion:{[weak strongSelf] (completed) in + strongSelf?.controller?.viewDidAppear(true) + }) + } else { + strongSelf.controller?.viewDidAppear(false) + } + + } + })) + } + + } + +} + +public func hasModals() -> Bool { + + for i in stride(from: activeModals.count - 1, to: -1, by: -1) { + if activeModals[i].value == nil { + activeModals.remove(at: i) + } + } + + return !activeModals.isEmpty +} + +public func hasModals(_ window: Window) -> Bool { + + for i in stride(from: activeModals.count - 1, to: -1, by: -1) { + if activeModals[i].value == nil { + activeModals.remove(at: i) + } + } + + return !activeModals.filter { $0.value?.window === window}.isEmpty +} + + +public func closeAllModals() { + for modal in activeModals { + if let controller = modal.value?.controller, controller.closable { + modal.value?.close() + } + } +} + +public enum ModalAnimationType { + case bottomToCenter + case scaleCenter + case scaleFrom(NSRect) + case alpha +} +public enum ModalAnimationCloseBehaviour { + case common + case scaleToRect(NSRect) +} + +public func showModal(with controller:ModalViewController, for window:Window, isOverlay: Bool = false, animated: Bool = true, animationType: ModalAnimationType = .bottomToCenter) -> Void { + assert(controller.modal == nil) + for weakModal in activeModals { + if weakModal.value?.controller?.className == controller.className, weakModal.value?.controller?.shouldCloseAllTheSameModals == true { + weakModal.value?.close() + } + } + + controller.modal = Modal(controller: controller, for: window, animated: animated, isOverlay: isOverlay, animationType: animationType) + if #available(OSX 10.12.2, *) { + window.touchBar = nil + } + controller.modal?.show() +} + +public func closeModal(_ type: ModalViewController.Type) -> Void { + for i in stride(from: activeModals.count - 1, to: -1 , by: -1) { + let weakModal = activeModals[i] + if let controller = weakModal.value?.controller, controller.isKind(of: type) { + weakModal.value?.close() + } + } +} + +public func showModal(with controller: NavigationViewController, for window:Window, isOverlay: Bool = false, animated: Bool = true, animationType: ModalAnimationType = .bottomToCenter) -> Void { + assert(controller.modal == nil) + for weakModal in activeModals { + if weakModal.value?.controller?.className == controller.className { + weakModal.value?.close() + } + } + + controller.modal = Modal(controller: ModalController(controller), for: window, animated: animated, isOverlay: isOverlay, animationType: animationType) + controller.modal?.show() + +} + + +private func hideBelowModalsIfNeeded(except: Modal) -> Bool { +// var hided: Bool = false +// if let exceptController = except.controller, exceptController.containerBackground != .clear { +// for modal in activeModals { +// if modal.value != except, let controller = modal.value?.controller, !controller.isFullScreen { +// modal.value?.background.isHidden = true +// hided = true +// } +// } +// } +// +// return hided + return false +} + +private func unhideModalIfNeeded() -> Bool { +// activeModals.last?.value?.background.isHidden = false + return false +} diff --git a/submodules/TGUIKit/TGUIKit/ModalTouchBar.swift b/submodules/TGUIKit/TGUIKit/ModalTouchBar.swift new file mode 100644 index 0000000000..2aaf5ab968 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ModalTouchBar.swift @@ -0,0 +1,80 @@ +// +// ModalTouchBar.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 19/09/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +@available(OSX 10.12.2, *) +private extension NSTouchBarItem.Identifier { + static let modalOK = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.modal.OK") + static let modalCancel = NSTouchBarItem.Identifier("\(Bundle.main.bundleIdentifier!).touchBar.modal.Cancel") + +} + +@available(OSX 10.12.2, *) +class ModalTouchBar: NSTouchBar, NSTouchBarDelegate { + private let interactions: ModalInteractions + private let modal:Modal + init(_ interactions: ModalInteractions, modal: Modal) { + self.interactions = interactions + self.modal = modal + super.init() + self.delegate = self + + var items: [NSTouchBarItem.Identifier] = [] + items.append(.flexibleSpace) + if let _ = interactions.cancelTitle { + items.append(.modalCancel) + } + + items.append(.modalOK) + items.append(.flexibleSpace) + self.defaultItemIdentifiers = items + } + + @objc private func modalOKAction() { + if let accept = interactions.accept { + accept() + } else { + modal.close() + } + } + + @objc private func modalCancelAction() { + if let cancel = interactions.cancel { + cancel() + } else { + modal.close() + } + } + + func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { + switch identifier { + case .modalOK: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: interactions.acceptTitle, target: self, action: #selector(modalOKAction)) + button.addWidthConstraint(size: 200) + item.view = button + item.customizationLabel = button.title + return item + case .modalCancel: + let item = NSCustomTouchBarItem(identifier: identifier) + let button = NSButton(title: interactions.cancelTitle!, target: self, action: #selector(modalCancelAction)) + button.addWidthConstraint(size: 200) + item.view = button + item.customizationLabel = button.title + return item + default: + return nil + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/NavigationBarView.swift b/submodules/TGUIKit/TGUIKit/NavigationBarView.swift new file mode 100644 index 0000000000..9930617693 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/NavigationBarView.swift @@ -0,0 +1,457 @@ +// +// NavigationBarView.swift +// TGUIKit +// +// Created by keepcoder on 15/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public enum NavigationBarSwapAnimation { + case none + case crossfade +} + +public struct NavigationBarStyle { + public let height:CGFloat + public let enableBorder:Bool + public init(height:CGFloat, enableBorder:Bool = true) { + self.height = height + self.enableBorder = enableBorder + } + + public var has: Bool { + return height > 0 + } +} + +public class NavigationBarView: View { + + private var bottomBorder:View = View() + + private var leftView:BarView = BarView(frame: NSZeroRect) + private var centerView:BarView = BarView(frame: NSZeroRect) + private var rightView:BarView = BarView(frame: NSZeroRect) + + override init() { + super.init() + // self.autoresizingMask = [.width] + updateLocalizationAndTheme(theme: presentation) + } + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + // self.autoresizingMask = [.width] + updateLocalizationAndTheme(theme: presentation) + } + + override public func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + bottomBorder.backgroundColor = presentation.colors.border + backgroundColor = presentation.colors.background + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + public override func draw(_ layer: CALayer, in ctx: CGContext) { + + super.draw(layer, in: ctx) +// ctx.setFillColor(NSColor.white.cgColor) +// ctx.fill(self.bounds) +// +// ctx.setFillColor(theme.colors.border.cgColor) +// ctx.fill(NSMakeRect(0, NSHeight(self.frame) - .borderSize, NSWidth(self.frame), .borderSize)) + } + + override public func layout() { + super.layout() + self.bottomBorder.setNeedsDisplay() + self.bottomBorder.frame = NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize) + + self.layout(left: leftView, center: centerView, right: rightView) + + } + + override public func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + + // guard let window = window as? Window, !window.inLiveSwiping else {return} + + } + + public override var mouseDownCanMoveWindow: Bool { + return true + } + + + func layout(left: BarView, center: BarView, right: BarView, force: Bool = false) -> Void { + + // + + if frame.height > 0 { + //proportions = 50 / 25 / 25 + + let leftWidth = left.isFitted ? left.frame.width : left.fit(to: (right.frame.width == right.minWidth ? frame.width / 3 : frame.width / 4)) + let rightWidth = right.isFitted ? right.frame.width : right.fit(to: (left.frame.width == left.minWidth ? frame.width / 3 : frame.width / 4)) + + left.frame = NSMakeRect(0, 0, leftWidth, frame.height - .borderSize); + center.frame = NSMakeRect(left.frame.maxX, 0, frame.width - (leftWidth + rightWidth), frame.height - .borderSize); + right.frame = NSMakeRect(center.frame.maxX, 0, rightWidth, frame.height - .borderSize); + } + } + + // ! PUSH ! + // left from center + // right cross fade + // center from right + + // ! POP ! + // old left -> new center + // old center -> right + // old right -> fade + + @objc func viewFrameChanged(_ notification:Notification) { + guard let window = window as? Window, !window.inLiveSwiping else {return} + layout(left: leftView, center: centerView, right: rightView) + } + + func startMoveViews(left:BarView, center:BarView, right:BarView, direction: SwipeDirection) { + addSubview(left) + addSubview(center) + addSubview(right) + + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: leftView) + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: centerView) + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: rightView) + + + var nLeft_from:CGFloat = 0, nRight_from:CGFloat = 0, nCenter_from:CGFloat = 0 + + switch direction { + case .right: + + nLeft_from = round(frame.width - left.frame.width)/2.0 + nCenter_from = left.frame.width + right.frame.width + nRight_from = right.frame.minX + + case .left: + nLeft_from = 0 + nCenter_from = 0 + nRight_from = right.frame.minX + + case .none: + break + } + + left.setFrameOrigin(nLeft_from, left.frame.minY) + center.setFrameOrigin(nCenter_from, center.frame.minY) + right.setFrameOrigin(nRight_from, right.frame.minY) + + left.setNeedsDisplay() + center.setNeedsDisplay() + right.setNeedsDisplay() + + + left.layer?.opacity = 0 + center.layer?.opacity = 0 + right.layer?.opacity = 0 + + layout(left: left, center: center, right: right) + + } + + func moveViews(left:BarView, center:BarView, right:BarView, direction: SwipeDirection, percent: CGFloat, animationStyle:AnimationStyle? = nil) { + + var pLeft_to:CGFloat = 0, pRight_to:CGFloat = 0, pCenter_to:CGFloat = 0 + var nLeft_to:CGFloat = 0, nRight_to:CGFloat = 0, nCenter_to:CGFloat = 0 + + switch direction { + case .right: + + + //center + nLeft_to = round(frame.width - left.frame.width)/2.0 - (round(frame.width - left.frame.width)/2.0) * percent + nCenter_to = left.frame.maxX + right.frame.width - (right.frame.width * percent) + nRight_to = left.frame.width + center.frame.width + + pLeft_to = self.leftView.frame.minX + pCenter_to = self.leftView.frame.width * (1.0 - percent) + pRight_to = self.leftView.frame.width + self.centerView.frame.width + + break + case .left: + + nLeft_to = 0 + nCenter_to = left.frame.maxX * percent + nRight_to = left.frame.width + center.frame.width + + pLeft_to = self.leftView.frame.minX + pCenter_to = self.leftView.frame.width + self.centerView.frame.width * percent + pRight_to = self.leftView.frame.width + self.centerView.frame.width + break + case .none: + break + } + + + + left.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, nLeft_to), left.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear, completion: { [weak left] completed in + if completed && animationStyle != nil { + left?.removeFromSuperview() + } + }) + center.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, nCenter_to), center.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear, completion: { [weak center] completed in + if completed && animationStyle != nil { + center?.removeFromSuperview() + } + }) + right.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, nRight_to), right.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear, completion: { [weak right] completed in + if completed && animationStyle != nil { + right?.removeFromSuperview() + } + }) + + + self.leftView.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, pLeft_to), self.leftView.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + self.centerView.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, pCenter_to), self.centerView.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + self.rightView.change(pos: NSMakePoint(floorToScreenPixels(backingScaleFactor, pRight_to), self.rightView.frame.minY), animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + + + + left.change(opacity: percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + center.change(opacity: percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + right.change(opacity: percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + + self.leftView.change(opacity: 1.0 - percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + self.centerView.change(opacity: 1.0 - percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + self.rightView.change(opacity: 1.0 - percent, animated: animationStyle != nil, duration: animationStyle?.duration ?? 0, timingFunction: animationStyle?.function ?? .linear) + + } + + public func switchViews(left:BarView, center:BarView, right:BarView, controller:ViewController, style:ViewControllerStyle, animationStyle:AnimationStyle, liveSwiping: Bool) { + + // var animationStyle = AnimationStyle.init(duration: 3.0, function: animationStyle.function) + + + + if !liveSwiping { + layout(left: left, center: center, right: right) + } + + + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: leftView) + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: centerView) + NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: rightView) + + self.bottomBorder.isHidden = !controller.bar.enableBorder + if style != .none { + + + + CATransaction.begin() + + if !liveSwiping { + self.addSubview(left) + self.addSubview(center) + self.addSubview(right) + } + self.addSubview(bottomBorder) + + + left.setNeedsDisplay() + center.setNeedsDisplay() + right.setNeedsDisplay() + + let pLeft = self.leftView + let pCenter = self.centerView + let pRight = self.rightView + + if !liveSwiping { + left.layer?.opacity = 0 + center.layer?.opacity = 0 + right.layer?.opacity = 0 + } + + pLeft.updateLocalizationAndTheme(theme: presentation) + pCenter.updateLocalizationAndTheme(theme: presentation) + pRight.updateLocalizationAndTheme(theme: presentation) + + self.leftView = left + self.centerView = center + self.rightView = right + + var pLeft_from:CGFloat = 0,pRight_from:CGFloat = 0, pCenter_from:CGFloat = 0, pLeft_to:CGFloat = 0, pRight_to:CGFloat = 0, pCenter_to:CGFloat = 0 + var nLeft_from:CGFloat = 0, nRight_from:CGFloat = 0, nCenter_from:CGFloat = 0, nLeft_to:CGFloat = 0, nRight_to:CGFloat = 0, nCenter_to:CGFloat = 0 + + switch style { + case .push: + + //left + pLeft_from = liveSwiping ? pLeft.frame.minX : 0 + pLeft_to = 0 + nLeft_from = liveSwiping ? left.frame.minX : round(frame.width - left.frame.width)/2.0 + nLeft_to = 0 + + //center + pCenter_from = liveSwiping ? pCenter.frame.minX : pLeft.frame.width + pCenter_to = 0 + + nCenter_from = liveSwiping ? center.frame.minX : left.frame.width + center.frame.width + nCenter_to = left.frame.width + + //right + pRight_from = right.frame.minX + pRight_to = right.frame.minX + nRight_from = right.frame.minX + nRight_to = right.frame.minX + + break + case .pop: + + //left + pLeft_from = liveSwiping ? pLeft.frame.minX : 0 + pLeft_to = 0 + nLeft_from = liveSwiping ? left.frame.minX : 0 + nLeft_to = 0 + + //center + pCenter_from = liveSwiping ? pCenter.frame.minX : center.frame.minX + pCenter_to = left.frame.width + center.frame.width + nCenter_from = liveSwiping ? center.frame.minX : 0 + nCenter_to = left.frame.maxX + + //right + pRight_from = liveSwiping ? pRight.frame.minX : right.frame.minX + pRight_to = right.frame.minX + nRight_from = right.frame.minX + nRight_to = right.frame.minX + + + break + case .none: + break + } + + + + left.setFrameOrigin(nLeft_from, left.frame.minY) + center.setFrameOrigin(nCenter_from, center.frame.minY) + right.setFrameOrigin(nRight_from, right.frame.minY) + + pLeft.setFrameOrigin(pLeft_from, left.frame.minY) + // pCenter.setFrameOrigin(pCenter_from, pCenter.frame.minY) + pRight.setFrameOrigin(pRight_from, pRight.frame.minY) + + +// + // old + pLeft.change(opacity: 0.0, duration: animationStyle.duration, timingFunction: animationStyle.function, completion:{ [weak pLeft] completed in + if completed { + pLeft?.removeFromSuperview() + } + }) + pLeft.change(pos: NSMakePoint(pLeft_to, pLeft.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + pCenter.change(opacity: 0.0, duration: animationStyle.duration, timingFunction: animationStyle.function, completion:{ [weak pCenter] completed in + if completed { + pCenter?.removeFromSuperview() + } + }) + // pCenter.change(pos: NSMakePoint(pCenter_to, pCenter.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + + pRight.change(opacity: 0.0, duration: animationStyle.duration, timingFunction: animationStyle.function, completion:{ [weak pRight] completed in + if completed { + pRight?.removeFromSuperview() + } + }) + pRight.change(pos: NSMakePoint(pRight_to, pRight.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + // new + if !left.isHidden { + left.change(opacity: 1.0, duration: animationStyle.duration, timingFunction: animationStyle.function) + } + left.change(pos: NSMakePoint(nLeft_to, left.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + + if !center.isHidden { + center.change(opacity: 1.0, duration: animationStyle.duration, timingFunction: animationStyle.function) + } + + center.change(pos: NSMakePoint(nCenter_to, center.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + + if !right.isHidden { + right.change(opacity: 1.0, duration: animationStyle.duration, timingFunction: animationStyle.function) + } + right.change(pos: NSMakePoint(nRight_to, right.frame.minY), animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function) + + + + CATransaction.commit() + + } else { + self.removeAllSubviews() + self.addSubview(left) + self.addSubview(center) + self.addSubview(right) + + self.leftView = left + self.centerView = center + self.rightView = right + + self.addSubview(bottomBorder) + } + + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: left) + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: center) + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: right) + + } + + private func applyAnimation(_ animation: NavigationBarSwapAnimation, from fromView: BarView, to toView: BarView) { + + toView.frame = fromView.frame + + switch animation { + case .none: + toView.layer?.opacity = 1.0 + self.addSubview(toView, positioned: .below, relativeTo: fromView) + fromView.removeFromSuperview() + toView.layer?.removeAllAnimations() + fromView.layer?.removeAllAnimations() + case .crossfade: + self.addSubview(toView, positioned: .below, relativeTo: fromView) + toView.layer?.opacity = 1.0 + toView.layer?.removeAllAnimations() + toView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + fromView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak fromView] _ in + fromView?.removeFromSuperview() + fromView?.layer?.removeAllAnimations() + }) + } + } + + public func switchLeftView(_ barView: BarView, animation: NavigationBarSwapAnimation) { + if self.leftView != barView { + applyAnimation(animation, from: self.leftView, to: barView) + self.leftView = barView + } + } + public func switchCenterView(_ barView: BarView, animation: NavigationBarSwapAnimation) { + if self.centerView != barView { + applyAnimation(animation, from: self.centerView, to: barView) + self.centerView = barView + } + } + public func switchRightView(_ barView: BarView, animation: NavigationBarSwapAnimation) { + if self.rightView != barView { + applyAnimation(animation, from: self.rightView, to: barView) + self.rightView = barView + } + } +} diff --git a/TGUIKit/TGUIKit/NavigationModalAction.swift b/submodules/TGUIKit/TGUIKit/NavigationModalAction.swift similarity index 100% rename from TGUIKit/TGUIKit/NavigationModalAction.swift rename to submodules/TGUIKit/TGUIKit/NavigationModalAction.swift diff --git a/TGUIKit/TGUIKit/NavigationModalView.swift b/submodules/TGUIKit/TGUIKit/NavigationModalView.swift similarity index 81% rename from TGUIKit/TGUIKit/NavigationModalView.swift rename to submodules/TGUIKit/TGUIKit/NavigationModalView.swift index ec24763eb8..11ba8baec0 100644 --- a/TGUIKit/TGUIKit/NavigationModalView.swift +++ b/submodules/TGUIKit/TGUIKit/NavigationModalView.swift @@ -12,7 +12,7 @@ class NavigationModalView: Control { private var textNode:TextNode = TextNode() private var attributeString:NSAttributedString - + let action:NavigationModalAction weak var viewController:NavigationViewController? @@ -22,7 +22,7 @@ class NavigationModalView: Control { self.action = action let attr:NSMutableAttributedString = NSMutableAttributedString() - _ = attr.append(string: action.reason, color: presentation.colors.text, font: .normal(.custom(20))) + _ = attr.append(string: action.reason, color: presentation.colors.text, font: .normal(20.0)) _ = attr.append(string: "\n") _ = attr.append(string: action.desc, color: presentation.colors.grayText, font: .normal(.text)) @@ -30,11 +30,10 @@ class NavigationModalView: Control { self.viewController = viewController - // viewController.navigationController?.lock = true + // viewController.navigationController?.lock = true super.init() self.autoresizingMask = [.width, .height] - self.backgroundColor = presentation.colors.background.withAlphaComponent(0.75) //NSColor(0xffffff,0.8) set(handler: { control in let control = control as! NavigationModalView @@ -44,15 +43,16 @@ class NavigationModalView: Control { }, for: .Click) } - override func updateLocalizationAndTheme() { - super.updateLocalizationAndTheme() + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) let attr:NSMutableAttributedString = NSMutableAttributedString() - _ = attr.append(string: action.reason, color: presentation.colors.text, font: .normal(.custom(20))) + + _ = attr.append(string: action.reason, color: presentation.colors.text, font: .normal(20.0)) _ = attr.append(string: "\n") _ = attr.append(string: action.desc, color: presentation.colors.grayText, font: .normal(.text)) self.attributeString = attr.copy() as! NSAttributedString - self.backgroundColor = presentation.colors.background.withAlphaComponent(0.75) + self.backgroundColor = .clear needsDisplay = true } @@ -61,18 +61,21 @@ class NavigationModalView: Control { } override func draw(_ layer: CALayer, in ctx: CGContext) { + + ctx.setFillColor(presentation.colors.background.withAlphaComponent(0.9).cgColor) + ctx.fill(layer.frame) super.draw(layer, in: ctx) let node = TextNode.layoutText(maybeNode: textNode, attributeString, nil, 3, .end, NSMakeSize(frame.width - 40, frame.height), nil, false, .center) let f = focus(node.0.size) - node.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor) + node.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } func close() { - kitWindow?.remove(object: self, for: .Escape) + kitWindow?.removeAllHandlers(for: self) self.lock = true self.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion:false, completion:{[weak self] (completed) in @@ -81,7 +84,7 @@ class NavigationModalView: Control { } override func removeFromSuperview() { - kitWindow?.remove(object: self, for: .Escape) + kitWindow?.removeAllHandlers(for: self) super.removeFromSuperview() } @@ -92,6 +95,9 @@ class NavigationModalView: Control { self?.close() return .invoked }, with: self, priority: .high) + self.kitWindow?.set(responder: { () -> NSResponder? in + return nil + }, with: self, priority: .modal) } else { // self.viewController?.navigationController?.lock = false } @@ -113,3 +119,4 @@ class NavigationModalView: Control { } + diff --git a/submodules/TGUIKit/TGUIKit/NavigationViewController.swift b/submodules/TGUIKit/TGUIKit/NavigationViewController.swift new file mode 100644 index 0000000000..fae0895e1e --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/NavigationViewController.swift @@ -0,0 +1,1238 @@ +// +// NavigationViewController.swift +// TGUIKit +// +// Created by keepcoder on 15/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +public enum ViewControllerStyle { + case push; + case pop; + case none; +} + + + +open class NavigationHeaderView : View { + public private(set) weak var header:NavigationHeader? + public let ready:Promise = Promise() + public init(_ header:NavigationHeader) { + self.header = header + super.init(frame: NSMakeRect(0, 0, 0, header.height)) + self.autoresizingMask = [.width] + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +open class NavigationHeader { + fileprivate var additionalHeader:NavigationHeader? + public let height:CGFloat + let initializer:(NavigationHeader)->NavigationHeaderView + weak var navigation:NavigationViewController? + fileprivate var _view:NavigationHeaderView? + fileprivate let disposable:MetaDisposable = MetaDisposable() + fileprivate var isShown:Bool = false + public var needShown:Bool = false + public init(_ height:CGFloat, initializer:@escaping(NavigationHeader)->NavigationHeaderView) { + self.height = height + self.initializer = initializer + } + + public var view:NavigationHeaderView { + if _view == nil { + _view = initializer(self) + } + return _view! + } + + deinit { + disposable.dispose() + } + + open func show(_ animated:Bool) { + assert(navigation != nil) + needShown = true + if isShown { + return + } + + isShown = true + if let navigation = navigation { + let view = self.view + let height = self.height + view.frame = NSMakeRect(0, navigation.controller.bar.height - height, navigation.containerView.frame.width, height) + + disposable.set((view.ready.get() |> filter {$0} |> take(1)).start(next: { [weak navigation, weak self, weak view] (ready) in + if let navigation = navigation, let view = view { + let contentInset = navigation.controller.bar.height + height + + navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) + + var inset:CGFloat = navigation.controller.bar.height + + if let additionalHeader = self?.additionalHeader, additionalHeader.needShown { + inset += additionalHeader.height + } + CATransaction.begin() + let completion = navigation.controller.navigationHeaderDidNoticeAnimation(height, 0, animated) + + let animator = animated ? view.animator() : view + animator.setFrameOrigin(NSMakePoint(0, inset)) + + NSAnimationContext.runAnimationGroup({ ctx in + navigation.controller.updateFrame(NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset), animated: animated) + }, completionHandler: { + completion() + }) + // navigation.controller.view.needsLayout = true + + CATransaction.commit() + } + })) + } + + } + + open func hide(_ animated:Bool) { + assert(navigation != nil) + if !isShown { + return + } + needShown = false + isShown = false + + if let navigation = navigation { + CATransaction.begin() + let completion = navigation.controller.navigationHeaderDidNoticeAnimation(0, height, animated) + if animated { + NSAnimationContext.runAnimationGroup({ ctx in + let animator = animated ? view.animator() : view + animator.setFrameOrigin(NSMakePoint(0, navigation.controller.bar.height - height)) + + }, completionHandler: { [weak view, weak self] in + view?.removeFromSuperview() + self?._view = nil + completion() + }) + } else { + view.removeFromSuperview() + _view = nil + } + CATransaction.commit() + var inset:CGFloat = navigation.controller.bar.height + + if let additionalHeader = additionalHeader, additionalHeader.needShown { + inset += additionalHeader.height + } + navigation.controller.updateFrame(CGRect(origin: NSMakePoint(0, inset), size: NSMakeSize(navigation.controller.frame.width, navigation.frame.height - inset)), animated: animated) + } + + } +} + +public class CallNavigationHeader : NavigationHeader { + fileprivate weak var simpleHeader:NavigationHeader? + public override func show(_ animated:Bool) { + assert(navigation != nil) + needShown = true + if isShown { + return + } + isShown = true + if let navigation = navigation { + let view = self.view + let height = self.height + view.frame = NSMakeRect(0, 0, navigation.containerView.frame.width, height) + + disposable.set((view.ready.get() |> take(1)).start(next: { [weak navigation, weak view] (ready) in + if let navigation = navigation, let view = view { + let contentInset = navigation.controller.bar.height + height + navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) + + let navigationBar = animated ? navigation.navigationBar.animator() : navigation.navigationBar + + navigationBar.setFrameOrigin(NSMakePoint(0, height)) + + let simple = animated ? self.simpleHeader?.view.animator() : self.simpleHeader?.view + simple?.setFrameOrigin(NSMakePoint(0, height + navigation.controller.bar.height)) + + let headerView = animated ? view.animator() : view + + headerView.setFrameOrigin(NSMakePoint(0, 0)) + navigation.controller.updateFrame(NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset), animated: animated) + + } + })) + } + + } + + public override func hide(_ animated:Bool) { + assert(navigation != nil) + if !isShown { + return + } + needShown = false + isShown = false + + if let navigation = navigation { + if animated { + NSAnimationContext.runAnimationGroup({ ctx in + view.animator().setFrameOrigin(NSMakePoint(0, -height)) + }, completionHandler: { [weak view, weak self] in + view?.removeFromSuperview() + self?._view = nil + }) + } else { + view.removeFromSuperview() + _view = nil + } + + if let header = simpleHeader, header.needShown { + let headerView = animated ? header.view.animator() : header.view + headerView.setFrameOrigin(NSMakePoint(0, navigation.controller.bar.height)) + } + + let navigationBar = animated ? navigation.navigationBar.animator() : navigation.navigationBar + navigationBar.setFrameOrigin(.zero) + navigation.controller.updateFrame(NSMakeRect(0, navigation.controller.bar.height, navigation.controller.frame.width, navigation.frame.height - navigation.controller.bar.height), animated: animated) + } + + } +} + + +public class UndoNavigationHeader : NavigationHeader { + fileprivate weak var simpleHeader:NavigationHeader? + public override func show(_ animated:Bool) { + assert(navigation != nil) + needShown = true + if isShown { + return + } + isShown = true + if let navigation = navigation { + let view = self.view + let height = self.height + view.frame = NSMakeRect(0, -height, navigation.containerView.frame.width, height) + + disposable.set((view.ready.get() |> take(1)).start(next: { [weak navigation, weak view] (ready) in + if let navigation = navigation, let view = view { + let contentInset = navigation.controller.bar.height > 0 ? height : 0 + navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) + CATransaction.begin() + if navigation.navigationBar.layer?.animation(forKey: "position") == nil { + navigation.navigationBar.change(pos: NSMakePoint(navigation.navigationBar.frame.minX, height), animated: animated) + } + + self.simpleHeader?.view.change(pos: NSMakePoint(0, height + navigation.controller.bar.height), animated: animated) + let completion = navigation.controller.navigationUndoHeaderDidNoticeAnimation(height, 0, animated) + + view.change(pos: NSMakePoint(0, 0), animated: animated, completion: { [weak navigation] completed in + if let navigation = navigation, completed { + navigation.controller.view.frame = NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset) + navigation.controller.view.needsLayout = true + completion() + } + }) + CATransaction.commit() + } + })) + } + + } + + public override func hide(_ animated:Bool) { + assert(navigation != nil) + if !isShown { + return + } + needShown = false + isShown = false + + if let navigation = navigation { + CATransaction.begin() + + let completion = navigation.controller.navigationUndoHeaderDidNoticeAnimation(0, height, animated) + + if animated { + view.change(pos: NSMakePoint(0, -height), animated: animated, removeOnCompletion: false, completion: { [weak self] completed in + if completed { + self?._view?.removeFromSuperview() + self?._view = nil + completion() + } + }) + } else { + view.removeFromSuperview() + _view = nil + completion() + } + + if let header = simpleHeader, header.needShown { + header.view.change(pos: NSMakePoint(0, navigation.controller.bar.height), animated: animated) + } + + navigation.navigationBar.change(pos: NSZeroPoint, animated: animated) + navigation.controller.view.frame = NSMakeRect(0, navigation.controller.bar.height, navigation.controller.frame.width, navigation.frame.height - navigation.controller.bar.height) + navigation.controller.view.needsLayout = true + CATransaction.commit() + } + + } +} + +enum NavigationShadowDirection { + case left, right +} + +final class NavigationShadowView : View { + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + } + + var direction: NavigationShadowDirection = .left { + didSet { + needsDisplay = true + } + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + ctx.clear(NSMakeRect(0, 0, frame.width, frame.height)) + ctx.setStrokeColor(presentation.colors.background.cgColor) + ctx.setShadow(offset: CGSize.zero, blur: 8, color: .black) + ctx.setBlendMode(.multiply) + ctx.strokeLineSegments(between: [CGPoint(x: bounds.width, y: 0), CGPoint(x: bounds.width, y: bounds.height)]) + +// let colorSpace = CGColorSpaceCreateDeviceRGB() +// let array = [NSColor.clear.cgColor, NSColor.black.withAlphaComponent(0.2).cgColor] +// let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: array), locations: nil)! +// +// ctx.drawLinearGradient(gradient, start: NSMakePoint(0, 0), end: CGPoint(x: frame.width, y: 0), options: CGGradientDrawingOptions()) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +open class NavigationViewController: ViewController, CALayerDelegate,CAAnimationDelegate { + + public var applyAppearOnLoad: Bool = true + + public private(set) var modalAction:NavigationModalAction? + let shadowView:NavigationShadowView = NavigationShadowView(frame: NSMakeRect(0, 0, 20, 0)) + let navigationRightBorder: View = View() + var stack:[ViewController] = [ViewController]() + var lock:Bool = false { + didSet { + var bp:Int = 0 + bp += 1 + } + } + + public var hasBarRightBorder: Bool = false { + didSet { + navigationRightBorder.isHidden = !hasBarRightBorder + navigationRightBorder.backgroundColor = presentation.colors.border + } + } + + private let _window: Window + + open override var window: Window? { + return _window + } + + public var empty:ViewController { + didSet { + empty.navigationController = self + + let prev = self.stack.last + self.stack.remove(at: 0) + self.stack.insert(empty, at: 0) + + + var controllerInset:CGFloat = 0 + + if let header = header, header.needShown { + controllerInset += header.height + } + if let header = callHeader, header.needShown { + controllerInset += header.height + } else if let header = undoHeader, header.needShown { + controllerInset += header.height + } + + empty.loadViewIfNeeded(NSMakeRect(0, controllerInset, self.bounds.width, self.bounds.height - controllerInset)) + + if prev == oldValue { + controller = empty + oldValue.removeFromSuperview() + containerView.addSubview(empty.view) + + if let header = header, header.needShown { + header.view.removeFromSuperview() + containerView.addSubview(header.view) + } + if let header = callHeader, header.needShown { + header.view.removeFromSuperview() + containerView.addSubview(header.view) + } + if let header = undoHeader, header.needShown { + header.view.removeFromSuperview() + containerView.addSubview(header.view) + } + } + + empty.view.frame = NSMakeRect(0, controllerInset, self.bounds.width, self.bounds.height - controllerInset - bar.height) + + + } + } + + open var isLocked:Bool { + return lock + } + + public private(set) var controller:ViewController { + didSet { + currentControllerDidChange() + } + } + + func _setController(_ controller:ViewController) { + self.controller = controller + } + + private(set) public var navigationBar:NavigationBarView = NavigationBarView() + + let pushDisposable:MetaDisposable = MetaDisposable() + let popDisposable:MetaDisposable = MetaDisposable() + + private(set) public var header:NavigationHeader? + private(set) public var callHeader:CallNavigationHeader? + private(set) public var undoHeader: UndoNavigationHeader? + var containerView:BackgroundView = BackgroundView(frame: NSZeroRect) + + public var backgroundMode: TableBackgroundMode { + get { + return containerView.backgroundMode + } + set { + containerView.backgroundMode = newValue + } + } + + + public func set(header:NavigationHeader?) { + self.header?.hide(false) + header?.navigation = self + header?.additionalHeader = callHeader + callHeader?.simpleHeader = header + self.header = header + } + + public func set(callHeader:CallNavigationHeader?) { + self.callHeader?.hide(false) + callHeader?.navigation = self + header?.additionalHeader = callHeader + callHeader?.simpleHeader = header + self.callHeader = callHeader + } + + public func set(undoHeader:UndoNavigationHeader?) { + self.undoHeader?.hide(false) + undoHeader?.navigation = self + header?.additionalHeader = undoHeader + undoHeader?.simpleHeader = header + self.undoHeader = undoHeader + } + + open override func loadView() { + super.loadView(); + viewDidLoad() + } + + open override func viewDidLoad() { + super.viewDidLoad() + + containerView.frame = bounds + //self.view.autoresizesSubviews = true + //containerView.autoresizesSubviews = true + //containerView.autoresizingMask = [.width, .height] + self.view.addSubview(containerView, positioned: .below, relativeTo: self.view.subviews.first) + controller._frameRect = bounds + if self.applyAppearOnLoad { + controller.viewWillAppear(false) + } + controller.navigationController = self + + containerView.addSubview(navigationBar) + + navigationBar.frame = NSMakeRect(0, 0, NSWidth(containerView.frame), controller.bar.height) + controller.view.frame = NSMakeRect(0, controller.bar.height , NSWidth(containerView.frame), NSHeight(containerView.frame) - controller.bar.height) + + navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: .none, animationStyle: controller.animationStyle, liveSwiping: false) + + containerView.addSubview(controller.view) + + self.view.addSubview(navigationRightBorder) + navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, 50) + + if self.applyAppearOnLoad { + Queue.mainQueue().justDispatch { + self.controller.viewDidAppear(false) + } + } + + + } + + var barInset: CGFloat { + var barInset:CGFloat = 0 + if let header = callHeader, header.needShown { + barInset += header.height + } else if let header = undoHeader, header.needShown { + barInset += header.height + } + return barInset + } + + public func swapNavigationBar(leftView: BarView?, centerView: BarView?, rightView: BarView?, animation: NavigationBarSwapAnimation) { + + navigationBar.frame = NSMakeRect(0, self.navigationBar.frame.minY, containerView.frame.width, controller.bar.height) + + if let leftView = leftView { + navigationBar.switchLeftView(leftView, animation: animation) + } + if let centerView = centerView { + navigationBar.switchCenterView(centerView, animation: animation) + } + if let rightView = rightView { + navigationBar.switchRightView(rightView, animation: animation) + } + } + + var containerSize: NSSize { + return frame.size + } + + open override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + containerView.frame = NSMakeRect(0, 0, containerSize.width, containerSize.height) + navigationBar.frame = NSMakeRect(0, navigationBar.frame.minY, containerSize.width, controller.bar.height) + navigationRightBorder.frame = NSMakeRect(size.width - .borderSize, 0, .borderSize, navigationBar.frame.height) + + if let header = callHeader { + header.view.setFrameSize(NSMakeSize(containerSize.width, header.height)) + } + if let header = header { + header.view.setFrameSize(NSMakeSize(containerSize.width, header.height)) + } + if let header = undoHeader { + header.view.setFrameSize(NSMakeSize(containerSize.width, header.height)) + } + + if controller.isLoaded() { + controller.frame = NSMakeRect(0, barInset + controller.bar.height, containerSize.width, containerSize.height - barInset - controller.bar.height) + } + } + + public func cancelCurrentController() { + pushDisposable.set(nil) + } + + open override var canBecomeResponder: Bool { + return false + } + open func currentControllerDidChange() { + + } + + public override var backgroundColor: NSColor { + set { + self.view.background = newValue + navigationBar.backgroundColor = newValue + } + get { + return self.view.background + } + } + + public init(_ empty:ViewController, _ window: Window) { + self.empty = empty + self.controller = empty + self._window = window + self.stack.append(controller) + + super.init() + bar = .init(height: 0) + } + + public var stackCount:Int { + return stack.count + } + + deinit { + self.popDisposable.dispose() + self.pushDisposable.dispose() + while !stack.isEmpty { + let value = stack.removeFirst() + value.removeFromSuperview() + } + } + + public func stackInsert(_ controller:ViewController, at: Int) -> Void { + controller.navigationController = self + controller.loadViewIfNeeded(self.bounds) + stack.insert(controller, at: at) + } + + open func push(_ controller:ViewController, _ animated:Bool = true, style: ViewControllerStyle? = nil) -> Void { + +// if isLocked { +// return +// } + + if controller.abolishWhenNavigationSame, controller.className == self.controller.className { + return + } + + controller.navigationController = self + controller.loadViewIfNeeded(self.bounds) + self.pushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in + if let strongSelf = self { + controller.navigationController = strongSelf + + if let index = strongSelf.stack.firstIndex(of: controller) { + strongSelf.stack.remove(at: index) + } + + strongSelf.stack.append(controller) + + let newStyle:ViewControllerStyle + if let style = style { + newStyle = style + } else { + newStyle = animated && strongSelf.stack.count > 1 ? .push : .none + } + CATransaction.begin() + strongSelf.show(controller, newStyle) + CATransaction.commit() + } + })) + } + + + func show(_ controller:ViewController,_ style:ViewControllerStyle) -> Void { + lock = true + + + guard let window = self.window else {return} + if style == .none { + window.abortSwiping() + } + + + let previous:ViewController = self.controller; + controller.navigationController = self + + if(previous === controller && stackCount > 1) { + previous.viewWillDisappear(false) + previous.viewDidDisappear(false) + + controller.viewWillAppear(false) + controller.viewDidAppear(false) + _ = controller.becomeFirstResponder() + lock = false + + navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, navigationBar.frame.height) + + return; + } + + + + var contentInset = controller.bar.height + + var barInset:CGFloat = 0 + if let header = callHeader, header.needShown { + header.view.frame = NSMakeRect(0, 0, containerView.frame.width, header.height) + contentInset += header.height + barInset += header.height + } else if let header = undoHeader, header.needShown { + header.view.frame = NSMakeRect(0, 0, containerView.frame.width, header.height) + contentInset += header.height + barInset += header.height + } + + + let animatePosBar: Bool = controller.bar.height != previous.bar.height && style != .none + self.navigationBar.frame = NSMakeRect(0, barInset, containerView.frame.width, animatePosBar && controller.bar.height == 0 ? previous.bar.height : controller.bar.height) + + + + if let header = header, header.needShown { + header.view.frame = NSMakeRect(0, contentInset, containerView.frame.width, header.height) + containerView.addSubview(header.view, positioned: .below, relativeTo: self.navigationBar) + contentInset += header.height + } + + controller.view.removeFromSuperview() + + let popInteractiveInset: CGFloat? = window.inLiveSwiping ? controller.frame.minX : nil + + controller.view.frame = NSMakeRect(0, contentInset , NSWidth(containerView.frame), NSHeight(containerView.frame) - contentInset) + if #available(OSX 10.12, *) { + + } else { + controller.view.needsLayout = true + } + + + let reloadHeaders = { [weak self] in + if let header = self?.header, header.needShown { + header.view.removeFromSuperview() + self?.containerView.addSubview(header.view, positioned: .above, relativeTo: controller.view) + } + + if let header = self?.callHeader, header.needShown { + header.view.removeFromSuperview() + self?.containerView.addSubview(header.view, positioned: .below, relativeTo: self?.navigationBar) + } + if let header = self?.undoHeader, header.needShown { + header.view.removeFromSuperview() + self?.containerView.addSubview(header.view, positioned: .below, relativeTo: self?.navigationBar) + } + } + + var pfrom:CGFloat = 0, pto:CGFloat = 0, nto:CGFloat = 0, nfrom:CGFloat = 0; + + var sto: CGFloat = 0 + + switch style { + case .push: + + controller.view.disableHierarchyDynamicContent() + + previous.viewWillDisappear(true); + controller.viewWillAppear(true); + + nfrom = popInteractiveInset != nil ? popInteractiveInset! : frame.width + nto = 0 + pfrom = previous.view.frame.minX + pto = -round(NSWidth(self.frame)/3.0) + containerView.addSubview(controller.view, positioned: .above, relativeTo: previous.view) + + sto = -shadowView.frame.width + if controller.isOpaque { + addShadowView(.right, updateOrigin: shadowView.superview == nil) + } + + if animatePosBar { + navigationBar.layer?.animatePosition(from: NSMakePoint(nfrom, 0), to: NSMakePoint(nto, 0), duration: previous.animationStyle.duration, timingFunction: .spring) + } + + navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + + case .pop: + + controller.view.disableHierarchyDynamicContent() + + previous.viewWillDisappear(true); + controller.viewWillAppear(true); + + nfrom = popInteractiveInset != nil ? popInteractiveInset! : -round(frame.width/3.0) + nto = 0 + pfrom = previous.view.frame.minX + pto = frame.width + previous.view.setFrameOrigin(NSMakePoint(pto, previous.frame.minY)) + containerView.addSubview(controller.view, positioned: .below, relativeTo: previous.view) + + + if animatePosBar { + navigationBar.layer?.animatePosition(from: NSMakePoint(pfrom, barInset), to: NSMakePoint(pto, barInset), duration: previous.animationStyle.duration, timingFunction: .spring, removeOnCompletion: false, completion: { [weak controller, weak navigationBar] completed in + if let controller = controller, completed { + navigationBar?.frame = NSMakeRect(0, barInset, controller.frame.width, controller.bar.height) + navigationBar?.layer?.removeAllAnimations() + } + + }) + } + + navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + + + sto = frame.width + if controller.isOpaque { + addShadowView(.left, updateOrigin: shadowView.superview == nil) + } + case .none: + + + previous.viewWillDisappear(false); + previous.view.removeFromSuperview() + containerView.addSubview(controller.view) + + self.controller = controller + + controller.viewWillAppear(false); + previous.viewDidDisappear(false); + controller.viewDidAppear(false); + _ = controller.becomeFirstResponder(); + + self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: style, animationStyle: controller.animationStyle, liveSwiping: false) + lock = false + + navigationBar.removeFromSuperview() + navigationBar.frame = NSMakeRect(0, barInset, controller.frame.width, controller.bar.height) + containerView.addSubview(navigationBar) + + + + reloadHeaders() + + navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, navigationBar.frame.height) + + + return // without animations + } + + self.controller = controller + + let prevBackgroundView = containerView.copy() as! NSView + let nextBackgroundView = containerView.copy() as! NSView + + if !previous.isOpaque { + previous.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: previous.view.subviews.first) + prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -previous.view.frame.minY)) + } + if !controller.isOpaque { + controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: controller.view.subviews.first) + nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -controller.view.frame.minY)) + } + + + + if previous.removeAfterDisapper, let index = stack.firstIndex(of: previous) { + self.stack.remove(at: index) + } + + navigationBar.removeFromSuperview() + containerView.addSubview(navigationBar) + reloadHeaders() + + + + + + + CATransaction.begin() + + shadowView.change(opacity: shadowView.direction == .left ? 0.2 : 1, animated: true, duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function) + + shadowView.change(pos: NSMakePoint(sto, shadowView.frame.minY), animated: true, duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function, completion: { [weak self] completed in + if completed { + self?.shadowView.removeFromSuperview() + } + }) + if !animatePosBar || (animatePosBar && style == .push) { + self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: animatePosBar ? .none : style, animationStyle: controller.animationStyle, liveSwiping: window.inLiveSwiping) + } + + + previous.view.layer?.animate(from: pfrom as NSNumber, to: pto as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.spring, duration: previous.animationStyle.duration, removeOnCompletion: false, additive: false, completion: { [weak prevBackgroundView] completed in + if completed { + previous.view.removeFromSuperview() + previous.view.layer?.removeAnimation(forKey: "position.x") + previous.viewDidDisappear(true); + prevBackgroundView?.removeFromSuperview() + } + }); + + + controller.view.layer?.animate(from: nfrom as NSNumber, to: nto as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.spring, duration: controller.animationStyle.duration, removeOnCompletion: true, additive: false, completion: { [weak nextBackgroundView, weak self] completed in + guard let `self` = self else { return } + if completed { + controller.viewDidAppear(true); + _ = controller.becomeFirstResponder() + nextBackgroundView?.removeFromSuperview() + } + self.navigationRightBorder.frame = NSMakeRect(self.frame.width - .borderSize, 0, .borderSize, controller.bar.height) + self.lock = false + controller.view.restoreHierarchyDynamicContent() + + }); + + + CATransaction.commit() + + } + + public func first(_ f:(ViewController)->Bool) -> ViewController? { + return self.stack.first(where: f) + } + + func addShadowView(_ shadowDirection: NavigationShadowDirection, updateOrigin: Bool = true) { + if updateOrigin { + shadowView.layer?.opacity = shadowDirection == .left ? 1.0 : 0.0 + } + shadowView.layer?.removeAllAnimations() + shadowView.direction = shadowDirection + shadowView.frame = NSMakeRect(updateOrigin ? (shadowDirection == .left ? -shadowView.frame.width : containerView.frame.width) : shadowView.frame.minX, 0, shadowView.frame.width, containerView.frame.height) + containerView.addSubview(shadowView, positioned: .below, relativeTo: navigationBar) + } + + open override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + navigationBar.updateLocalizationAndTheme(theme: theme) + callHeader?.view.updateLocalizationAndTheme(theme: theme) + header?.view.updateLocalizationAndTheme(theme: theme) + undoHeader?.view.updateLocalizationAndTheme(theme: theme) + navigationRightBorder.backgroundColor = presentation.colors.border + + for controller in self.stack { + if controller != self.controller, controller.isLoaded() { + controller.leftBarView.updateLocalizationAndTheme(theme: theme) + controller.centerBarView.updateLocalizationAndTheme(theme: theme) + controller.rightBarView.updateLocalizationAndTheme(theme: theme) + } + } + } + + + open func back(animated:Bool = true, forceAnimated: Bool = false, animationStyle: ViewControllerStyle = .pop) -> Void { + if !isLocked, let last = stack.last, last.invokeNavigationBack() { + if stackCount > 1 { + let controller = stack[stackCount - 2] + last.didRemovedFromStack() + stack.removeLast() + show(controller, animated || forceAnimated ? animationStyle : .none) + } else { + doSomethingOnEmptyBack?() + } + } + } + + + public var doSomethingOnEmptyBack: (()->Void)? = nil + + + + public func to( index:Int? = nil) -> Void { + if stackCount > 1, let index = index { + if index < 0 { + gotoEmpty(false) + } else { + let controller = stack[index] + let range = min(max(1, index + 1), stackCount) ..< stackCount + for i in range.lowerBound ..< range.upperBound { + stack[i].didRemovedFromStack() + } + stack.removeSubrange(range) + show(controller, .none) + } + } + } + + public func gotoEmpty(_ animated:Bool = true) -> Void { + if controller != empty { + let range = 1 ..< stackCount - 1 + for i in range.lowerBound ..< range.upperBound { + stack[i].didRemovedFromStack() + } + stack.removeSubrange(1 ..< stackCount - 1) + show(empty, animated ? .pop : .none) + } + } + + public func close(animated:Bool = true) ->Void { + if stackCount > 1 && !isLocked { + let controller = stack[0] + while stack.count != 1 { + stack.removeLast().didRemovedFromStack() + } + show(controller, animated ? .pop : .none) + } + } + + public func set(modalAction:NavigationModalAction, _ showView:Bool = true) { + self.modalAction?.view?.removeFromSuperview() + self.modalAction = modalAction + modalAction.navigation = self + if showView { + let actionView = NavigationModalView(action: modalAction, viewController: self) + modalAction.view = actionView + actionView.frame = bounds + view.addSubview(actionView) + } + } + + public func removeUntil(_ controllerType: ViewController.Type) { + let index = stack.firstIndex(where: { current in + return current.className == NSStringFromClass(controllerType) + }) + if let index = index { + while stack.count - 1 > index { + let controller = stack.removeLast() + controller.didRemovedFromStack() + } + } else { + while stack.count > 1 { + let controller = stack.removeLast() + controller.didRemovedFromStack() + } + } + } + + public func removeAll() { + stack.removeAll() + } + + public func removeModalAction() { + self.modalAction?.view?.removeFromSuperview() + self.modalAction = nil + } + + public func enumerateControllers(_ f:(ViewController, Int)->Bool) { + for i in stride(from: stack.count - 1, to: -1, by: -1) { + if f(stack[i], i) { + break + } + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if controller.redirectUserInterfaceCalls { + controller.viewWillAppear(animated) + } + } + + + open var previousController: ViewController? { + if stackCount > 1 { + return stack[stackCount - 2] + } + return nil + } + + open var canSwipeBack: Bool { + return self.stackCount > 1 // (self.genericView.state == .single || self.stackCount > 2) + } + + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.window?.add(swipe: { [weak self] direction, animated -> SwipeHandlerResult in + guard let `self` = self, self.controller.view.layer?.animationKeys() == nil, let window = self.window else {return .failed} + + if let view = window.contentView!.hitTest(window.contentView!.convert(window.mouseLocationOutsideOfEventStream, from: nil))?.superview { + if view is HorizontalRowView || view.superview is HorizontalRowView { + return .failed + } + } + if hasPopover(window) { + return .failed + } + + switch direction { + case let .left(state): + switch state { + case .start: + + guard let previous = self.previousController, self.controller.supportSwipes, self.stackCount > 1 && !self.isLocked, self.canSwipeBack else {return .failed} + + previous.view.frame = NSMakeRect(0, previous.bar.height, self.frame.width, self.frame.height - previous.bar.height) + + self.containerView.addSubview(previous.view, positioned: .below, relativeTo: self.controller.view) + + + let prevBackgroundView = self.containerView.copy() as! NSView + let nextBackgroundView = self.containerView.copy() as! NSView + + if !previous.isOpaque { + previous.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: previous.view.subviews.first) + prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -previous.view.frame.minY)) + } + if !self.controller.isOpaque { + self.controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: self.controller.view.subviews.first) + nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -self.controller.view.frame.minY)) + } + + self.addShadowView(.left) + if previous.bar.has { + self.navigationBar.startMoveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction) + } + self.lock = true + return .success(previous) + case let .swiping(delta, previous): + + let nPosition = min(max(0, delta), self.containerView.frame.width) + self.controller.view._change(pos: NSMakePoint(nPosition, self.controller.view.frame.minY), animated: false) + let previousStart = -round(NSWidth(self.containerView.frame)/3.0) + previous.view._change(pos: NSMakePoint(min(previousStart + delta / 3.0, 0), previous.view.frame.minY), animated: false) + + self.shadowView.setFrameOrigin(nPosition - self.shadowView.frame.width, self.shadowView.frame.minY) + self.shadowView.layer?.opacity = min(1.0 - Float(nPosition / self.containerView.frame.width) + 0.2, 1.0) + + if previous.bar.has { + self.navigationBar.moveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction, percent: nPosition / self.containerView.frame.width) + } else { + self.navigationBar.setFrameOrigin(nPosition, self.navigationBar.frame.minY) + } + return .deltaUpdated(available: nPosition) + + case let .success(_, controller): + self.lock = false + + controller.removeBackgroundCap() + self.controller.removeBackgroundCap() + + self.back(forceAnimated: true) + case let .failed(_, previous): + // CATransaction.begin() + let animationStyle = previous.animationStyle + self.controller.view._change(pos: NSMakePoint(0, self.controller.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) + self.containerView.subviews[1]._change(pos: NSMakePoint(-round(self.containerView.frame.width / 3), self.containerView.subviews[1].frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self, weak previous] completed in + if completed { + self?.containerView.subviews[1].removeFromSuperview() + self?.controller.removeBackgroundCap() + previous?.removeBackgroundCap() + } + }) + self.shadowView.change(pos: NSMakePoint(-self.shadowView.frame.width, self.shadowView.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] completed in + self?.shadowView.removeFromSuperview() + }) + self.shadowView.change(opacity: 1, animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) + if previous.bar.has { + self.navigationBar.moveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction, percent: 0, animationStyle: animationStyle) + } else { + self.navigationBar.change(pos: NSMakePoint(0, self.navigationBar.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) + } + self.lock = false + // CATransaction.commit() + } + case let .right(state): + + switch state { + case .start: + guard let new = self.controller.rightSwipeController, !self.isLocked else {return .failed} + new._frameRect = self.containerView.bounds + new.view.setFrameOrigin(NSMakePoint(self.containerView.frame.width, self.controller.frame.minY)) + + + let prevBackgroundView = self.containerView.copy() as! NSView + let nextBackgroundView = self.containerView.copy() as! NSView + + if !new.isOpaque { + new.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: new.view.subviews.first) + prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -new.view.frame.minY)) + } + if !self.controller.isOpaque { + self.controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: self.controller.view.subviews.first) + nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -self.controller.view.frame.minY)) + } + + self.containerView.addSubview(new.view, positioned: .above, relativeTo: self.controller.view) + self.addShadowView(.right) + self.navigationBar.startMoveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction) + self.lock = true + return .success(new) + case let .swiping(delta, new): + let delta = min(max(0, delta), self.containerView.frame.width) + + let nPosition = self.containerView.frame.width - delta + // NSLog("\(nPosition)") + new.view._change(pos: NSMakePoint(nPosition, new.frame.minY), animated: false) + + self.controller.view._change(pos: NSMakePoint(min(-delta / 3.0, 0), self.controller.view.frame.minY), animated: false) + + self.shadowView.setFrameOrigin(nPosition - self.shadowView.frame.width, self.shadowView.frame.minY) + self.shadowView.layer?.opacity = min(1.0 - Float(nPosition / self.containerView.frame.width) + 0.2, 1.0) + + self.navigationBar.moveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction, percent: delta / self.containerView.frame.width) + + return .deltaUpdated(available: delta) + case let .success(_, controller): + self.lock = false + + controller.removeBackgroundCap() + self.controller.removeBackgroundCap() + + self.push(controller, true, style: .push) + case let .failed(_, new): + // CATransaction.begin() + let animationStyle = new.animationStyle + var _new:ViewController? = new + + + _new?.view._change(pos: NSMakePoint(self.containerView.frame.width, self.controller.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) + self.containerView.subviews[1]._change(pos: NSMakePoint(0, self.containerView.subviews[1].frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak new, weak self] completed in + self?.controller.removeBackgroundCap() + new?.view.removeFromSuperview() + _new = nil + }) + self.shadowView.change(pos: NSMakePoint(self.containerView.frame.width, self.shadowView.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] completed in + self?.shadowView.removeFromSuperview() + }) + self.shadowView.change(opacity: 1, duration: animationStyle.duration, timingFunction: animationStyle.function) + self.navigationBar.moveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction, percent: 0, animationStyle: animationStyle) + self.lock = false + // CATransaction.commit() + } + default: + break + } + + return .nothing + }, with: self.containerView, identifier: "\(self.description)") + + if controller.redirectUserInterfaceCalls { + controller.viewDidAppear(animated) + } + } + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.window?.removeAllHandlers(for: self) + if controller.redirectUserInterfaceCalls { + controller.viewWillDisappear(animated) + } + } + + open override func scrollup(force: Bool = false) { + super.scrollup() + if controller.redirectUserInterfaceCalls { + controller.scrollup() + } + } + + open override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if controller.redirectUserInterfaceCalls { + controller.viewDidDisappear(animated) + } + } + + open override func focusSearch(animated: Bool) { + super.focusSearch(animated: animated) + if controller.redirectUserInterfaceCalls { + controller.focusSearch(animated: animated) + } + } +} diff --git a/TGUIKit/TGUIKit/Node.swift b/submodules/TGUIKit/TGUIKit/Node.swift similarity index 91% rename from TGUIKit/TGUIKit/Node.swift rename to submodules/TGUIKit/TGUIKit/Node.swift index faa63f2716..ad16f3ede5 100644 --- a/TGUIKit/TGUIKit/Node.swift +++ b/submodules/TGUIKit/TGUIKit/Node.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit open class Node: NSObject, ViewDisplayDelegate { @@ -15,7 +15,7 @@ open class Node: NSObject, ViewDisplayDelegate { open var backgroundColor:NSColor? { - return self.view?.background + return self.view?.backgroundColor } private let _strongView:View? @@ -53,6 +53,9 @@ open class Node: NSObject, ViewDisplayDelegate { self.view = view } + open func update() { + + } open func setNeedDisplay() -> Void { @@ -69,8 +72,7 @@ open class Node: NSObject, ViewDisplayDelegate { } open func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(backgroundColor!.cgColor) - ctx.fill(layer.bounds) + } diff --git a/TGUIKit/TGUIKit/OverlayControl.swift b/submodules/TGUIKit/TGUIKit/OverlayControl.swift similarity index 98% rename from TGUIKit/TGUIKit/OverlayControl.swift rename to submodules/TGUIKit/TGUIKit/OverlayControl.swift index cdfcf484bd..3d4dabfb13 100644 --- a/TGUIKit/TGUIKit/OverlayControl.swift +++ b/submodules/TGUIKit/TGUIKit/OverlayControl.swift @@ -24,7 +24,7 @@ open class OverlayControl: Control { trackingArea = nil if let _ = window { - let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.activeAlways] + let options:NSTrackingArea.Options = [NSTrackingArea.Options.cursorUpdate, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.mouseMoved, NSTrackingArea.Options.activeInKeyWindow] self.trackingArea = NSTrackingArea.init(rect: self.bounds, options: options, owner: self, userInfo: nil) self.addTrackingArea(self.trackingArea!) diff --git a/submodules/TGUIKit/TGUIKit/Popover.swift b/submodules/TGUIKit/TGUIKit/Popover.swift new file mode 100644 index 0000000000..aa7cdc54cd --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Popover.swift @@ -0,0 +1,437 @@ +// +// Popover.swift +// TGUIKit +// +// Created by keepcoder on 26/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// +import Cocoa +import SwiftSignalKit + +class PopoverBackground: Control { + +} + +private struct PopoverFrameValue { + let inset: NSPoint + let edge: NSRectEdge? + let contentRect: NSRect + weak var control: Control? + +} + +open class Popover: NSObject { + + private weak var window:Window? + + private var disposable:MetaDisposable = MetaDisposable() + + public var animates:Bool = true + + public var controller:ViewController? + + private weak var control:Control? + + public var isShown:Bool = false + + private var controlStateIdentifiers: [UInt32] = [] + + public var overlay:OverlayControl! + private var background:PopoverBackground = PopoverBackground(frame: NSZeroRect) + + public var animationStyle:AnimationStyle = AnimationStyle(duration:0.2, function:CAMediaTimingFunctionName.spring) + + var readyDisposable:MetaDisposable = MetaDisposable() + private let `static`: Bool + required public init(controller:ViewController, static: Bool) { + self.controller = controller +// self.background.layer?.shadowOpacity = 0.4 + self.background.layer?.rasterizationScale = CGFloat(System.backingScale) + self.background.layer?.shouldRasterize = true + self.background.layer?.isOpaque = false + self.background.layer?.cornerRadius = 10 + + let shadow = NSShadow() + shadow.shadowBlurRadius = 4 + shadow.shadowColor = NSColor.black.withAlphaComponent(0.3) + shadow.shadowOffset = NSMakeSize(0, 0) + self.background.shadow = shadow + + self.static = `static` + super.init() + + background.popover = self + } + + @objc func windowDidResized(_ notification: NSNotification) { + hide() + } + + private var frameValue: PopoverFrameValue! + private func updatePopoverFrame() { + if let parentView = window?.contentView, let control = frameValue.control, let controller = controller { + var point:NSPoint = control.convert(NSMakePoint(0, 0), to: parentView) + + + if let edge = frameValue.edge { + + switch edge { + case .maxX: + point.x -= controller.frame.width + case .maxY: + point.y -= controller.frame.height + background.flip = true + case .minX: + point.x -= (controller.frame.width - control.frame.width) + point.y -= controller.frame.height + background.flip = true + default: + fatalError("Not Implemented") + } + + + } + + if frameValue.inset.x != 0 { + point.x += (frameValue.inset.x) + + } + if frameValue.inset.y != 0 { + point.y += frameValue.inset.y + } + + + var rect = controller.bounds + if !NSIsEmptyRect(frameValue.contentRect) { + rect = frameValue.contentRect + } + + + point.x = min(max(5, point.x), (parentView.frame.width - rect.width - 12) - 5) + point.y = min(max(5, point.y), (parentView.frame.height - rect.height - 12) - 5) + + background.frame = NSMakeRect(round(point.x), round(point.y), rect.width + 14, rect.height + 14) + } + } + + + open func show(for control:Control, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, contentRect:NSRect = NSMakeRect(7, 7, 0, 0), delayBeforeShown: Double = 0.2) -> Void { + + control.popover = self + + if let controller = controller, let parentView = control.window?.contentView { + + frameValue = PopoverFrameValue(inset: inset, edge: edge, contentRect: contentRect, control: control) + + controller.loadViewIfNeeded() + controller.viewWillAppear(animates) + + self.window = control.kitWindow + + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResized(_:)), name: NSWindow.didResizeNotification, object: window) + + var signal = controller.ready.get() |> filter {$0} |> take(1) + if control.controlState == .Hover && delayBeforeShown > 0.0 { + signal = signal |> delay(delayBeforeShown, queue: Queue.mainQueue()) + } + self.readyDisposable.set(signal.start(next: { [weak self, weak controller, weak parentView] ready in + if let parentView = parentView { + for subview in parentView.subviews { + if let view = subview as? PopoverBackground, view.popover?.isShown == true, view.popover?.controller?.isAutoclosePopover == true { + view.popover?.hide(false) + } + } + } + + if let strongSelf = self, let controller = controller, let parentView = parentView, (strongSelf.inside() || (control.controlState == .Hover || control.controlState == .Highlight || strongSelf.static) || !control.userInteractionEnabled), control.window != nil, control.visibleRect != NSZeroRect { + + control.isSelected = true + + strongSelf.window?.set(escape: { [weak strongSelf] () -> KeyHandlerResult in + strongSelf?.hide() + return .invoked + }, with: strongSelf, priority: .modal) + + strongSelf.window?.set(handler: { () -> KeyHandlerResult in + return .invokeNext + }, with: strongSelf, for: .All) + + strongSelf.window?.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + if let strongSelf = self, !strongSelf.inside() && !control.mouseInside() { + strongSelf.hide() + } + return .invokeNext + }, with: strongSelf, for: .leftMouseUp, priority: .high) + + + strongSelf.window?.set(mouseHandler: { [weak self] (_) -> KeyHandlerResult in + if let strongSelf = self, !strongSelf.inside() && !control.mouseInside() { + strongSelf.hide() + } + return .invokeNext + }, with: strongSelf, for: .scrollWheel, priority: .high) + + strongSelf.control = control + strongSelf.background.flip = false + + strongSelf.updatePopoverFrame() + strongSelf.background.backgroundColor = .clear + strongSelf.background.layer?.cornerRadius = 10 + + strongSelf.overlay = OverlayControl(frame: NSMakeRect(contentRect.minX, contentRect.minY, controller.frame.width , controller.frame.height )) + strongSelf.overlay.backgroundColor = presentation.colors.background + strongSelf.overlay.layer?.cornerRadius = 10 + strongSelf.overlay.layer?.opacity = 0.99 + + + let bg = View(frame: NSMakeRect(strongSelf.overlay.frame.minX + 2, strongSelf.overlay.frame.minY + 2, strongSelf.overlay.frame.width - 4, strongSelf.overlay.frame.height - 4)) + bg.layer?.cornerRadius = 10 + bg.backgroundColor = presentation.colors.background + + strongSelf.background.addSubview(bg) + + strongSelf.background.addSubview(strongSelf.overlay) + + controller.view.setFrameOrigin(NSMakePoint(0, 0)) + + + strongSelf.overlay.addSubview(controller.view) + + parentView.addSubview(strongSelf.background) + + + let result = controller.becomeFirstResponder() + + strongSelf.isShown = true + + if let _ = strongSelf.overlay { + if strongSelf.animates { + + var once:Bool = false + + strongSelf.background.layer?.animateAlpha(from: 0, to: 1, duration: 0.1, completion: { [weak controller] (comple) in + if let strongSelf = self, !once { + once = true + controller?.viewDidAppear(strongSelf.animates) + if result == true { + _ = strongSelf.window?.makeFirstResponder(controller?.firstResponder()) + } else if result == false { + _ = strongSelf.window?.makeFirstResponder(nil) + } + } + + }) + + } else { + controller.viewDidAppear(strongSelf.animates) + } + + let nHandler:(Control) -> Void = { [weak strongSelf] control in + if let strongSelf = strongSelf { + let s = Signal.single(Void()) |> delay(0.2, queue: Queue.mainQueue()) |> then(Signal.single(Void()) |> delay(0.2, queue: Queue.mainQueue()) |> restart) + + strongSelf.disposable.set(s.start(next: { [weak strongSelf] () in + if let strongSelf = strongSelf { + if !strongSelf.inside() && !control.mouseInside() { + if (NSEvent.pressedMouseButtons & (1 << 0)) == 0 { + strongSelf.hide() + } + } + } + + })) + } + + + } + + var first: Bool = true + + strongSelf.window?.set(mouseHandler: { [weak strongSelf, weak control] _ -> KeyHandlerResult in + if let strongSelf = strongSelf, first, let control = control, !strongSelf.static { + if !strongSelf.inside() && !control.mouseInside() { + first = false + nHandler(control) + } + } + return .invokeNext + }, with: strongSelf, for: .mouseMoved, priority: .high) + + strongSelf.window?.set(mouseHandler: { [weak strongSelf] event -> KeyHandlerResult in + if let strongSelf = strongSelf, !strongSelf.inside() && (!control.mouseInside() || control.continuesAction) { + strongSelf.hide() + return .invokeNext + } else { + return .rejected + } + }, with: strongSelf, for: .leftMouseDown, priority: .high) + + strongSelf.window?.set(responder: { [weak controller] () -> NSResponder? in + return controller?.firstResponder() + }, with: self, priority: .high, ignoreKeys: [.Return, .Delete]) + + let hHandler:(Control) -> Void = { [weak strongSelf] _ in + + strongSelf?.disposable.set(nil) + + } + + strongSelf.background.set(handler: nHandler, for: .Normal) + strongSelf.background.set(handler: hHandler, for: .Hover) + + + strongSelf.controlStateIdentifiers = [control.set(handler: nHandler, for: .Normal), control.set(handler: hHandler, for: .Hover)] + + } + } else if let strongSelf = self { + controller?.viewWillDisappear(false) + controller?.viewDidDisappear(false) + controller?.didRemovedFromStack() + controller?.popover = nil + strongSelf.controller = nil + strongSelf.window?.removeAllHandlers(for: strongSelf) + strongSelf.window?.remove(object: strongSelf, for: .All) + } + + })) + + } + + } + + + public func addSubview(_ subview: View) -> Void { + self.background.addSubview(subview) + } + + func inside() -> Bool { + + // return true + + if let window = control?.window { + let g:NSPoint = NSEvent.mouseLocation + let w:NSPoint = window.contentView!.convert(window.convertFromScreen(NSMakeRect(g.x, g.y, 1, 1)).origin, from: nil) + //if w.x > background.frame.minX && background + return NSPointInRect(w, background.frame) + } + return false + } + + + deinit { + self.disposable.dispose() + self.readyDisposable.dispose() + window?.remove(object: self, for: .All) + background.removeFromSuperview() + NotificationCenter.default.removeObserver(self) + } + + public func hide(_ removeHandlers:Bool = true) -> Void { + + self.disposable.set(nil) + self.readyDisposable.set(nil) + + + if !isShown { + return + } + + isShown = false + control?.isSelected = false + window?.removeAllHandlers(for: self) + window?.remove(object: self, for: .All) + + overlay?.removeLastStateHandler() + overlay?.removeLastStateHandler() + + if removeHandlers { + for id in controlStateIdentifiers { + control?.removeStateHandler(id) + } + } + + controller?.viewWillDisappear(true) + if animates { + background.change(opacity: 0, animated: animates, duration: 0.15, completion: { [weak self] complete in + guard let `self` = self else {return} + if self === self.controller?.popover { + self.controller?.viewDidDisappear(true) + self.controller?.didRemovedFromStack() + self.controller?.popover = nil + } + self.controller = nil + self.background.removeFromSuperview() + }) +// for sub in background.subviews { +// +// sub._change(opacity: 0, animated: true, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] complete in +// if let strongSelf = self, !once { +// once = true +// +// } +// }) +// +// } + } else { + controller?.viewDidDisappear(false) + controller?.didRemovedFromStack() + controller?.popover = nil + controller = nil + background.removeFromSuperview() + } + } + +} + +public func hasPopover(_ window:Window) -> Bool { + if !window.sheets.isEmpty { + return true + } + for subview in window.contentView!.subviews { + if let subview = subview as? PopoverBackground, let popover = subview.popover { + return popover.isShown + } + } + return false +} + +public func closeAllPopovers(for window: Window) { + + while hasPopover(window) { + + while !window.sheets.isEmpty { + window.sheets.last?.orderOut(nil) + } + + for subview in window.contentView!.subviews { + if let subview = subview as? PopoverBackground, let popover = subview.popover { + popover.hide() + } + } + } + +} + +public func showPopover(for control:Control, with controller:ViewController, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, delayBeforeShown: Double = 0.015, static: Bool = false ) -> Void { + if let _ = control.window as? Window { + if let popover = controller.popover { + if popover.isShown { + return + } + } + + // if let event = NSApp.currentEvent, event.type == .gesture { + + // } + + controller.popover = (controller.popoverClass as! Popover.Type).init(controller: controller, static: `static`) + + if let popover = controller.popover { + popover.show(for: control, edge: edge, inset: inset, delayBeforeShown: delayBeforeShown) + } + } +} + + + + diff --git a/TGUIKit/TGUIKit/PresentationResourceCache.swift b/submodules/TGUIKit/TGUIKit/PresentationResourceCache.swift similarity index 85% rename from TGUIKit/TGUIKit/PresentationResourceCache.swift rename to submodules/TGUIKit/TGUIKit/PresentationResourceCache.swift index 7b2aaeb740..03ae9c3fd4 100644 --- a/TGUIKit/TGUIKit/PresentationResourceCache.swift +++ b/submodules/TGUIKit/TGUIKit/PresentationResourceCache.swift @@ -1,12 +1,12 @@ import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit private final class PresentationsResourceCacheHolder { var images: [Int32: CGImage] = [:] } private final class PresentationsResourceAnyCacheHolder { - var objects: [Int32: AnyObject] = [:] + var objects: [Int32: Any] = [:] } public final class PresentationsResourceCache { @@ -33,8 +33,8 @@ public final class PresentationsResourceCache { } } - public func object(_ key: Int32, _ generate: () -> AnyObject) -> AnyObject { - let result = self.objectCache.with { holder -> AnyObject? in + public func object(_ key: Int32, _ generate: () -> Any) -> Any { + let result = self.objectCache.with { holder -> Any? in return holder.objects[key] } if let result = result { diff --git a/submodules/TGUIKit/TGUIKit/PresentationTheme.swift b/submodules/TGUIKit/TGUIKit/PresentationTheme.swift new file mode 100644 index 0000000000..fccdaeb61e --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/PresentationTheme.swift @@ -0,0 +1,2578 @@ +// +// PresentationTheme.swift +// Telegram +// +// Created by keepcoder on 22/06/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +// + +/* + colorItems.append(.preset(PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0x5a9e29, bubbleColors: (0xdcf8c6, nil), wallpaper: patternWallpaper("R3j69wKskFIBAAAAoUdXWCKMzCM", 0xede6dd, nil, 50, nil)))) + colorItems.append(.preset(PresentationThemeAccentColor(index: 106, baseColor: .preset, accentColor: 0xf55783, bubbleColors: (0xd6f5ff, nil), wallpaper: patternWallpaper("p-pXcflrmFIBAAAAvXYQk-mCwZU", 0xfce3ec, nil, 40, nil)))) + colorItems.append(.preset(PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x7e5fe5, bubbleColors: (0xf5e2ff, nil), wallpaper: patternWallpaper("nQcFYJe1mFIBAAAAcI95wtIK0fk", 0xfcccf4, 0xae85f0, 54, nil)))) + colorItems.append(.preset(PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0xff5fa9, bubbleColors: (0xfff4d7, nil), wallpaper: patternWallpaper("51nnTjx8mFIBAAAAaFGJsMIvWkk", 0xf6b594, 0xebf6cd, 46, 45)))) + colorItems.append(.preset(PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0x199972, bubbleColors: (0xfffec7, nil), wallpaper: patternWallpaper("fqv01SQemVIBAAAApND8LDRUhRU", 0xc1e7cb, nil, 50, nil)))) + colorItems.append(.preset(PresentationThemeAccentColor(index: 105, baseColor: .preset, accentColor: 0x009eee, bubbleColors: (0x94fff9, 0xccffc7), wallpaper: patternWallpaper("p-pXcflrmFIBAAAAvXYQk-mCwZU", 0xffbca6, 0xff63bd, 57, 225)))) + */ + + + +public struct SearchTheme { + public let backgroundColor: NSColor + public let searchImage:CGImage + public let clearImage:CGImage + public let placeholder:()->String + public let textColor: NSColor + public let placeholderColor: NSColor + public init(_ backgroundColor: NSColor, _ searchImage:CGImage, _ clearImage:CGImage, _ placeholder:@escaping()->String, _ textColor: NSColor, _ placeholderColor: NSColor) { + self.backgroundColor = backgroundColor + self.searchImage = searchImage + self.clearImage = clearImage + self.placeholder = placeholder + self.textColor = textColor + self.placeholderColor = placeholderColor + } +} + +public enum PaletteWallpaper : Equatable { + case none + case url(String) + case builtin + case color(NSColor) + public init?(_ string: String) { + switch string { + case "none": + self = .none + case "builtin": + self = .builtin + default: + if string.hasPrefix("t.me/bg/") || string.hasPrefix("https://t.me/bg/") || string.hasPrefix("http://t.me/bg/") { + self = .url(string) + } else if let color = NSColor(hexString: string) { + self = .color(color) + } else { + return nil + } + } + } + + public var toString: String { + switch self { + case .builtin: + return "builtin" + case .none: + return "none" + case let .color(color): + return color.hexString + case let .url(string): + return string + } + } +} + +public func ==(lhs: ColorPalette, rhs: ColorPalette) -> Bool { + + + if lhs.isNative != rhs.isNative { + return false + } + if lhs.isDark != rhs.isDark { + return false + } + if lhs.tinted != rhs.tinted { + return false + } + if lhs.name != rhs.name { + return false + } + if lhs.copyright != rhs.copyright { + return false + } + if lhs.accentList != rhs.accentList { + return false + } + if lhs.parent != rhs.parent { + return false + } + if lhs.wallpaper != rhs.wallpaper { + return false + } + + let lhsMirror = Mirror(reflecting: lhs).superclassMirror ?? Mirror(reflecting: lhs) + let rhsMirror = Mirror(reflecting: rhs).superclassMirror ?? Mirror(reflecting: rhs) + + for (i, lhsChildren) in lhsMirror.children.enumerated() { + let lhsValue = lhsChildren.value as? NSColor + let rhsValue = Array(rhsMirror.children)[i].value as? NSColor + if let lhsValue = lhsValue, let rhsValue = rhsValue { + if lhsValue.argb != rhsValue.argb { + return false + } + } + } + + return true +} + +public struct PaletteAccentColor : Equatable { + public static func == (lhs: PaletteAccentColor, rhs: PaletteAccentColor) -> Bool { + return lhs.accent.argb == rhs.accent.argb && lhs.messages?.top.argb == rhs.messages?.top.argb && lhs.messages?.bottom.argb == rhs.messages?.bottom.argb + } + + public let accent: NSColor + public let messages: (top: NSColor, bottom: NSColor)? + public init(_ accent: NSColor, _ messages: (top: NSColor, bottom: NSColor)? = nil) { + self.accent = accent.withAlphaComponent(1.0) + self.messages = messages.map { (top: $0.top.withAlphaComponent(1.0), bottom: $0.bottom.withAlphaComponent(1.0)) } + } +} + +public class ColorPalette : Equatable { + + public let isNative: Bool + public let isDark: Bool + public let tinted: Bool + public let name: String + public let copyright:String + public let accentList:[PaletteAccentColor] + public let parent: TelegramBuiltinTheme + public let wallpaper: PaletteWallpaper + + private let _basicAccent: NSColor + public var basicAccent: NSColor { + return self._basicAccent + } + private let _background: NSColor + public var background: NSColor { + return self._background + } + private let _text: NSColor + public var text: NSColor { + return self._text + } + private let _grayText:NSColor + public var grayText: NSColor { + return self._grayText + } + private let _link:NSColor + public var link: NSColor { + return self._link + } + private let _accent:NSColor + public var accent: NSColor { + return self._accent + } + private let _redUI:NSColor + public var redUI: NSColor { + return self._redUI + } + private let _greenUI:NSColor + public var greenUI: NSColor { + return self._greenUI + } + private let _blackTransparent:NSColor + public var blackTransparent: NSColor { + return self._blackTransparent + } + private let _grayTransparent:NSColor + public var grayTransparent: NSColor { + return self._grayTransparent + } + private let _grayUI:NSColor + public var grayUI: NSColor { + return self._grayUI + } + private let _darkGrayText:NSColor + public var darkGrayText: NSColor { + return self._darkGrayText + } + + private let _accentSelect:NSColor + public var accentSelect: NSColor { + return self._accentSelect + } + private let _selectText:NSColor + public var selectText: NSColor { + return self._selectText + } + private let _border:NSColor + public var border: NSColor { + return self._border + } + private let _grayBackground:NSColor + public var grayBackground: NSColor { + return self._grayBackground + } + private let _grayForeground:NSColor + public var grayForeground: NSColor { + return self._grayForeground + } + private let _grayIcon:NSColor + public var grayIcon: NSColor { + return self._grayIcon + } + private let _accentIcon:NSColor + public var accentIcon: NSColor { + return self._accentIcon + } + private let _badgeMuted:NSColor + public var badgeMuted: NSColor { + return self._badgeMuted + } + private let _badge:NSColor + public var badge: NSColor { + return self._badge + } + private let _indicatorColor: NSColor + public var indicatorColor: NSColor { + return self._indicatorColor + } + private let _selectMessage: NSColor + public var selectMessage: NSColor { + return self._selectMessage + } + private let _monospacedPre: NSColor + public var monospacedPre: NSColor { + return self._monospacedPre + } + private let _monospacedCode: NSColor + public var monospacedCode: NSColor { + return self._monospacedCode + } + private let _monospacedPreBubble_incoming: NSColor + public var monospacedPreBubble_incoming: NSColor { + return self._monospacedPreBubble_incoming + } + private let _monospacedPreBubble_outgoing: NSColor + public var monospacedPreBubble_outgoing: NSColor { + return self._monospacedPreBubble_outgoing + } + private let _monospacedCodeBubble_incoming: NSColor + public var monospacedCodeBubble_incoming: NSColor { + return self._monospacedCodeBubble_incoming + } + private let _monospacedCodeBubble_outgoing: NSColor + public var monospacedCodeBubble_outgoing: NSColor { + return self._monospacedCodeBubble_outgoing + } + private let _selectTextBubble_incoming: NSColor + public var selectTextBubble_incoming: NSColor { + return self._selectTextBubble_incoming + } + private let _selectTextBubble_outgoing: NSColor + public var selectTextBubble_outgoing: NSColor { + return self._selectTextBubble_outgoing + } + private let _bubbleBackground_incoming: NSColor + public var bubbleBackground_incoming: NSColor { + return self._bubbleBackground_incoming + } + private let _bubbleBackgroundTop_outgoing: NSColor + public var bubbleBackgroundTop_outgoing: NSColor { + return self._bubbleBackgroundTop_outgoing + } + private let _bubbleBackgroundBottom_outgoing: NSColor + public var bubbleBackgroundBottom_outgoing: NSColor { + return self._bubbleBackgroundBottom_outgoing + } + + private let _bubbleBorder_incoming: NSColor + public var bubbleBorder_incoming: NSColor { + return self._bubbleBorder_incoming + } + private let _bubbleBorder_outgoing: NSColor + public var bubbleBorder_outgoing: NSColor { + return self._bubbleBorder_outgoing + } + private let _grayTextBubble_incoming: NSColor + public var grayTextBubble_incoming: NSColor { + return self._grayTextBubble_incoming + } + private let _grayTextBubble_outgoing: NSColor + public var grayTextBubble_outgoing: NSColor { + return self._grayTextBubble_outgoing + } + private let _grayIconBubble_incoming: NSColor + public var grayIconBubble_incoming: NSColor { + return self._grayIconBubble_incoming + } + private let _grayIconBubble_outgoing: NSColor + public var grayIconBubble_outgoing: NSColor { + return self._grayIconBubble_outgoing + } + private let _accentIconBubble_incoming: NSColor + public var accentIconBubble_incoming: NSColor { + return self._accentIconBubble_incoming + } + private let _accentIconBubble_outgoing: NSColor + public var accentIconBubble_outgoing: NSColor { + return self._accentIconBubble_outgoing + } + private let _linkBubble_incoming: NSColor + public var linkBubble_incoming: NSColor { + return self._linkBubble_incoming + } + private let _linkBubble_outgoing: NSColor + public var linkBubble_outgoing: NSColor { + return self._linkBubble_outgoing + } + private let _textBubble_incoming: NSColor + public var textBubble_incoming: NSColor { + return self._textBubble_incoming + } + private let _textBubble_outgoing: NSColor + public var textBubble_outgoing: NSColor { + return self._textBubble_outgoing + } + private let _selectMessageBubble: NSColor + public var selectMessageBubble: NSColor { + return self._selectMessageBubble + } + private let _fileActivityBackground: NSColor + public var fileActivityBackground: NSColor { + return self._fileActivityBackground + } + private let _fileActivityForeground: NSColor + public var fileActivityForeground: NSColor { + return self._fileActivityForeground + } + private let _fileActivityBackgroundBubble_incoming: NSColor + public var fileActivityBackgroundBubble_incoming: NSColor { + return self._fileActivityBackgroundBubble_incoming + } + private let _fileActivityBackgroundBubble_outgoing: NSColor + public var fileActivityBackgroundBubble_outgoing: NSColor { + return self._fileActivityBackgroundBubble_outgoing + } + private let _fileActivityForegroundBubble_incoming: NSColor + public var fileActivityForegroundBubble_incoming: NSColor { + return self._fileActivityForegroundBubble_incoming + } + private let _fileActivityForegroundBubble_outgoing: NSColor + public var fileActivityForegroundBubble_outgoing: NSColor { + return self._fileActivityForegroundBubble_outgoing + } + private let _waveformBackground: NSColor + public var waveformBackground: NSColor { + return self._waveformBackground + } + private let _waveformForeground: NSColor + public var waveformForeground: NSColor { + return self._waveformForeground + } + private let _waveformBackgroundBubble_incoming: NSColor + public var waveformBackgroundBubble_incoming: NSColor { + return self._waveformBackgroundBubble_incoming + } + private let _waveformBackgroundBubble_outgoing: NSColor + public var waveformBackgroundBubble_outgoing: NSColor { + return self._waveformBackgroundBubble_outgoing + } + private let _waveformForegroundBubble_incoming: NSColor + public var waveformForegroundBubble_incoming: NSColor { + return self._waveformForegroundBubble_incoming + } + private let _waveformForegroundBubble_outgoing: NSColor + public var waveformForegroundBubble_outgoing: NSColor { + return self._waveformForegroundBubble_outgoing + } + private let _webPreviewActivity: NSColor + public var webPreviewActivity: NSColor { + return self._webPreviewActivity + } + private let _webPreviewActivityBubble_incoming: NSColor + public var webPreviewActivityBubble_incoming: NSColor { + return self._webPreviewActivityBubble_incoming + } + private let _webPreviewActivityBubble_outgoing: NSColor + public var webPreviewActivityBubble_outgoing: NSColor { + return self._webPreviewActivityBubble_outgoing + } + private let _redBubble_incoming:NSColor + public var redBubble_incoming: NSColor { + return self._redBubble_incoming + } + private let _redBubble_outgoing:NSColor + public var redBubble_outgoing: NSColor { + return self._redBubble_outgoing + } + private let _greenBubble_incoming:NSColor + public var greenBubble_incoming: NSColor { + return self._greenBubble_incoming + } + private let _greenBubble_outgoing:NSColor + public var greenBubble_outgoing: NSColor { + return self._greenBubble_outgoing + } + private let _chatReplyTitle: NSColor + public var chatReplyTitle: NSColor { + return self._chatReplyTitle + } + private let _chatReplyTextEnabled: NSColor + public var chatReplyTextEnabled: NSColor { + return self._chatReplyTextEnabled + } + private let _chatReplyTextDisabled: NSColor + public var chatReplyTextDisabled: NSColor { + return self._chatReplyTextDisabled + } + private let _chatReplyTitleBubble_incoming: NSColor + public var chatReplyTitleBubble_incoming: NSColor { + return self._chatReplyTitleBubble_incoming + } + private let _chatReplyTitleBubble_outgoing: NSColor + public var chatReplyTitleBubble_outgoing: NSColor { + return self._chatReplyTitleBubble_outgoing + } + private let _chatReplyTextEnabledBubble_incoming: NSColor + public var chatReplyTextEnabledBubble_incoming: NSColor { + return self._chatReplyTextEnabledBubble_incoming + } + private let _chatReplyTextEnabledBubble_outgoing: NSColor + public var chatReplyTextEnabledBubble_outgoing: NSColor { + return self._chatReplyTextEnabledBubble_outgoing + } + private let _chatReplyTextDisabledBubble_incoming: NSColor + public var chatReplyTextDisabledBubble_incoming: NSColor { + return self._chatReplyTextDisabledBubble_incoming + } + private let _chatReplyTextDisabledBubble_outgoing: NSColor + public var chatReplyTextDisabledBubble_outgoing: NSColor { + return self._chatReplyTextDisabledBubble_outgoing + } + private let _groupPeerNameRed:NSColor + public var groupPeerNameRed: NSColor { + return self._groupPeerNameRed + } + private let _groupPeerNameOrange:NSColor + public var groupPeerNameOrange: NSColor { + return self._groupPeerNameOrange + } + private let _groupPeerNameViolet:NSColor + public var groupPeerNameViolet: NSColor { + return self._groupPeerNameViolet + } + private let _groupPeerNameGreen:NSColor + public var groupPeerNameGreen: NSColor { + return self._groupPeerNameGreen + } + private let _groupPeerNameCyan:NSColor + public var groupPeerNameCyan: NSColor { + return self._groupPeerNameCyan + } + private let _groupPeerNameLightBlue:NSColor + public var groupPeerNameLightBlue: NSColor { + return self._groupPeerNameLightBlue + } + private let _groupPeerNameBlue:NSColor + public var groupPeerNameBlue: NSColor { + return self._groupPeerNameBlue + } + private let _peerAvatarRedTop: NSColor + public var peerAvatarRedTop: NSColor { + return self._peerAvatarRedTop + } + private let _peerAvatarRedBottom: NSColor + public var peerAvatarRedBottom: NSColor { + return self._peerAvatarRedBottom + } + private let _peerAvatarOrangeTop: NSColor + public var peerAvatarOrangeTop: NSColor { + return self._peerAvatarOrangeTop + } + private let _peerAvatarOrangeBottom: NSColor + public var peerAvatarOrangeBottom: NSColor { + return self._peerAvatarOrangeBottom + } + private let _peerAvatarVioletTop: NSColor + public var peerAvatarVioletTop: NSColor { + return self._peerAvatarVioletTop + } + private let _peerAvatarVioletBottom: NSColor + public var peerAvatarVioletBottom: NSColor { + return self._peerAvatarVioletBottom + } + private let _peerAvatarGreenTop: NSColor + public var peerAvatarGreenTop: NSColor { + return self._peerAvatarGreenTop + } + private let _peerAvatarGreenBottom: NSColor + public var peerAvatarGreenBottom: NSColor { + return self._peerAvatarGreenBottom + } + private let _peerAvatarCyanTop: NSColor + public var peerAvatarCyanTop: NSColor { + return self._peerAvatarCyanTop + } + private let _peerAvatarCyanBottom: NSColor + public var peerAvatarCyanBottom: NSColor { + return self._peerAvatarCyanBottom + } + private let _peerAvatarBlueTop: NSColor + public var peerAvatarBlueTop: NSColor { + return self._peerAvatarBlueTop + } + private let _peerAvatarBlueBottom: NSColor + public var peerAvatarBlueBottom: NSColor { + return self._peerAvatarBlueBottom + } + private let _peerAvatarPinkTop: NSColor + public var peerAvatarPinkTop: NSColor { + return self._peerAvatarPinkTop + } + private let _peerAvatarPinkBottom: NSColor + public var peerAvatarPinkBottom: NSColor { + return self._peerAvatarPinkBottom + } + private let _bubbleBackgroundHighlight_incoming: NSColor + public var bubbleBackgroundHighlight_incoming: NSColor { + return self._bubbleBackgroundHighlight_incoming + } + private let _bubbleBackgroundHighlight_outgoing: NSColor + public var bubbleBackgroundHighlight_outgoing: NSColor { + return self._bubbleBackgroundHighlight_outgoing + } + private let _chatDateActive: NSColor + public var chatDateActive: NSColor { + return self._chatDateActive + } + private let _chatDateText: NSColor + public var chatDateText: NSColor { + return self._chatDateText + } + private let _revealAction_neutral1_background: NSColor + public var revealAction_neutral1_background: NSColor { + return self._revealAction_neutral1_background + } + private let _revealAction_neutral1_foreground: NSColor + public var revealAction_neutral1_foreground: NSColor { + return self._revealAction_neutral1_foreground + } + private let _revealAction_neutral2_background: NSColor + public var revealAction_neutral2_background: NSColor { + return self._revealAction_neutral2_background + } + private let _revealAction_neutral2_foreground: NSColor + public var revealAction_neutral2_foreground: NSColor { + return self._revealAction_neutral2_foreground + } + private let _revealAction_destructive_background: NSColor + public var revealAction_destructive_background: NSColor { + return self._revealAction_destructive_background + } + private let _revealAction_destructive_foreground: NSColor + public var revealAction_destructive_foreground: NSColor { + return self._revealAction_destructive_foreground + } + private let _revealAction_constructive_background: NSColor + public var revealAction_constructive_background: NSColor { + return self._revealAction_constructive_background + } + private let _revealAction_constructive_foreground: NSColor + public var revealAction_constructive_foreground: NSColor { + return self._revealAction_constructive_foreground + } + private let _revealAction_accent_background: NSColor + public var revealAction_accent_background: NSColor { + return self._revealAction_accent_background + } + private let _revealAction_accent_foreground: NSColor + public var revealAction_accent_foreground: NSColor { + return self._revealAction_accent_foreground + } + private let _revealAction_warning_background: NSColor + public var revealAction_warning_background: NSColor { + return self._revealAction_warning_background + } + private let _revealAction_warning_foreground: NSColor + public var revealAction_warning_foreground: NSColor { + return self._revealAction_warning_foreground + } + private let _revealAction_inactive_background: NSColor + public var revealAction_inactive_background: NSColor { + return self._revealAction_inactive_background + } + private let _revealAction_inactive_foreground: NSColor + public var revealAction_inactive_foreground: NSColor { + return self._revealAction_inactive_foreground + } + private let _chatBackground: NSColor + public var chatBackground: NSColor { + return self._chatBackground + } + private let _listBackground: NSColor + public var listBackground: NSColor { + return self._listBackground + } + private let _listGrayText: NSColor + public var listGrayText: NSColor { + return self._listGrayText + } + private let _grayHighlight: NSColor + public var grayHighlight: NSColor { + return self._grayHighlight + } + + private let _focusAnimationColor: NSColor + public var focusAnimationColor: NSColor { + return self._focusAnimationColor + } + + + + public var underSelectedColor: NSColor { + if basicAccent != accent { + return accent.lightness > 0.8 ? NSColor(0x000000) : NSColor(0xffffff) + } else { + return NSColor(0xffffff) + } + } + public var hasAccent: Bool { + return basicAccent != accent + } + + public func peerColors(_ index: Int) -> (top: NSColor, bottom: NSColor) { + let colors: [(top: NSColor, bottom: NSColor)] = [ + (peerAvatarRedTop, peerAvatarRedBottom), + (peerAvatarOrangeTop, peerAvatarOrangeBottom), + (peerAvatarVioletTop, peerAvatarVioletBottom), + (peerAvatarGreenTop, peerAvatarGreenBottom), + (peerAvatarCyanTop, peerAvatarCyanBottom), + (peerAvatarBlueTop, peerAvatarBlueBottom), + (peerAvatarPinkTop, peerAvatarPinkBottom) + ] + + return colors[index] + } + + public var toString: String { + var string: String = "" + + string += "isDark = \(self.isDark ? 1 : 0)\n" + string += "tinted = \(self.tinted ? 1 : 0)\n" + string += "name = \(self.name)\n" + string += "//Fallback for parameters which didn't define. Available values: day, dayClassic, dark, nightAccent\n" + string += "parent = \(self.parent.rawValue)\n" + string += "copyright = \(self.copyright)\n" +// string += "accentList = \(self.accentList.map{$0.hexString}.joined(separator: ","))\n" + for prop in self.listProperties() { + if let color = self.colorFromStringVariable(prop) { + var prop = prop + _ = prop.removeFirst() + if prop == "chatBackground" { + string += "//Parameter is usually using for minimalistic chat mode, but also works as fallback for bubbles if wallpaper doesn't installed\n" + } + string += "\(prop) = \(color.hexString.lowercased())\n" + } + } + string += "//Parameter only affects bubble chat mode. Available values: none, builtin, hexColor or url to cloud backgound like a t.me/bg/%slug%\n" + string += "wallpaper = \(wallpaper.toString)\n" + return string + } + + public required init(isNative: Bool, isDark: Bool, + tinted: Bool, + name: String, + parent: TelegramBuiltinTheme, + wallpaper: PaletteWallpaper, + copyright: String, + accentList: [PaletteAccentColor], + basicAccent: NSColor, + background:NSColor, + text: NSColor, + grayText: NSColor, + link: NSColor, + accent:NSColor, + redUI:NSColor, + greenUI:NSColor, + blackTransparent:NSColor, + grayTransparent:NSColor, + grayUI:NSColor, + darkGrayText:NSColor, + accentSelect:NSColor, + selectText:NSColor, + border:NSColor, + grayBackground:NSColor, + grayForeground:NSColor, + grayIcon:NSColor, + accentIcon:NSColor, + badgeMuted:NSColor, + badge:NSColor, + indicatorColor: NSColor, + selectMessage: NSColor, + monospacedPre: NSColor, + monospacedCode: NSColor, + monospacedPreBubble_incoming: NSColor, + monospacedPreBubble_outgoing: NSColor, + monospacedCodeBubble_incoming: NSColor, + monospacedCodeBubble_outgoing: NSColor, + selectTextBubble_incoming: NSColor, + selectTextBubble_outgoing: NSColor, + bubbleBackground_incoming: NSColor, + bubbleBackgroundTop_outgoing: NSColor, + bubbleBackgroundBottom_outgoing: NSColor, + bubbleBorder_incoming: NSColor, + bubbleBorder_outgoing: NSColor, + grayTextBubble_incoming: NSColor, + grayTextBubble_outgoing: NSColor, + grayIconBubble_incoming: NSColor, + grayIconBubble_outgoing: NSColor, + accentIconBubble_incoming: NSColor, + accentIconBubble_outgoing: NSColor, + linkBubble_incoming: NSColor, + linkBubble_outgoing: NSColor, + textBubble_incoming: NSColor, + textBubble_outgoing: NSColor, + selectMessageBubble: NSColor, + fileActivityBackground: NSColor, + fileActivityForeground: NSColor, + fileActivityBackgroundBubble_incoming: NSColor, + fileActivityBackgroundBubble_outgoing: NSColor, + fileActivityForegroundBubble_incoming: NSColor, + fileActivityForegroundBubble_outgoing: NSColor, + waveformBackground: NSColor, + waveformForeground: NSColor, + waveformBackgroundBubble_incoming: NSColor, + waveformBackgroundBubble_outgoing: NSColor, + waveformForegroundBubble_incoming: NSColor, + waveformForegroundBubble_outgoing: NSColor, + webPreviewActivity: NSColor, + webPreviewActivityBubble_incoming: NSColor, + webPreviewActivityBubble_outgoing: NSColor, + redBubble_incoming:NSColor, + redBubble_outgoing:NSColor, + greenBubble_incoming:NSColor, + greenBubble_outgoing:NSColor, + chatReplyTitle: NSColor, + chatReplyTextEnabled: NSColor, + chatReplyTextDisabled: NSColor, + chatReplyTitleBubble_incoming: NSColor, + chatReplyTitleBubble_outgoing: NSColor, + chatReplyTextEnabledBubble_incoming: NSColor, + chatReplyTextEnabledBubble_outgoing: NSColor, + chatReplyTextDisabledBubble_incoming: NSColor, + chatReplyTextDisabledBubble_outgoing: NSColor, + groupPeerNameRed:NSColor, + groupPeerNameOrange:NSColor, + groupPeerNameViolet:NSColor, + groupPeerNameGreen:NSColor, + groupPeerNameCyan:NSColor, + groupPeerNameLightBlue:NSColor, + groupPeerNameBlue:NSColor, + peerAvatarRedTop: NSColor, + peerAvatarRedBottom: NSColor, + peerAvatarOrangeTop: NSColor, + peerAvatarOrangeBottom: NSColor, + peerAvatarVioletTop: NSColor, + peerAvatarVioletBottom: NSColor, + peerAvatarGreenTop: NSColor, + peerAvatarGreenBottom: NSColor, + peerAvatarCyanTop: NSColor, + peerAvatarCyanBottom: NSColor, + peerAvatarBlueTop: NSColor, + peerAvatarBlueBottom: NSColor, + peerAvatarPinkTop: NSColor, + peerAvatarPinkBottom: NSColor, + bubbleBackgroundHighlight_incoming: NSColor, + bubbleBackgroundHighlight_outgoing: NSColor, + chatDateActive: NSColor, + chatDateText: NSColor, + revealAction_neutral1_background: NSColor, + revealAction_neutral1_foreground: NSColor, + revealAction_neutral2_background: NSColor, + revealAction_neutral2_foreground: NSColor, + revealAction_destructive_background: NSColor, + revealAction_destructive_foreground: NSColor, + revealAction_constructive_background: NSColor, + revealAction_constructive_foreground: NSColor, + revealAction_accent_background: NSColor, + revealAction_accent_foreground: NSColor, + revealAction_warning_background: NSColor, + revealAction_warning_foreground: NSColor, + revealAction_inactive_background: NSColor, + revealAction_inactive_foreground: NSColor, + chatBackground: NSColor, + listBackground: NSColor, + listGrayText: NSColor, + grayHighlight: NSColor, + focusAnimationColor: NSColor) { + + let background: NSColor = background.withAlphaComponent(1.0) + let grayBackground: NSColor = grayBackground.withAlphaComponent(1.0) + let grayForeground: NSColor = grayForeground.withAlphaComponent(1.0) + var text: NSColor = text.withAlphaComponent(1.0) + var link: NSColor = link.withAlphaComponent(1.0) + var grayText: NSColor = grayText.withAlphaComponent(1.0) + var accent: NSColor = accent.withAlphaComponent(1) + var accentIcon: NSColor = accentIcon + var grayIcon: NSColor = grayIcon + var accentSelect: NSColor = accentSelect + var textBubble_incoming: NSColor = textBubble_incoming.withAlphaComponent(1.0) + var textBubble_outgoing: NSColor = textBubble_outgoing.withAlphaComponent(1.0) + var grayTextBubble_incoming: NSColor = grayTextBubble_incoming.withAlphaComponent(1.0) + var grayTextBubble_outgoing: NSColor = grayTextBubble_outgoing.withAlphaComponent(1.0) + var grayIconBubble_incoming: NSColor = grayIconBubble_incoming + var grayIconBubble_outgoing: NSColor = grayIconBubble_outgoing + var accentIconBubble_incoming: NSColor = accentIconBubble_incoming + var accentIconBubble_outgoing: NSColor = accentIconBubble_outgoing + + let bubbleBackground_incoming = bubbleBackground_incoming.withAlphaComponent(1.0) + let bubbleBackgroundTop_outgoing = bubbleBackgroundTop_outgoing.withAlphaComponent(1.0) + let bubbleBackgroundBottom_outgoing = bubbleBackgroundBottom_outgoing.withAlphaComponent(1.0) + let linkBubble_incoming = linkBubble_incoming.withAlphaComponent(1.0) + let linkBubble_outgoing = linkBubble_outgoing.withAlphaComponent(1.0) + + + let bubbleBackground_outgoing = bubbleBackgroundTop_outgoing.blended(withFraction: 0.5, of: bubbleBackgroundBottom_outgoing)! + + let chatBackground = chatBackground.withAlphaComponent(1.0) + + if link.isTooCloseHSV(to: background) { + link = background.brightnessAdjustedColor + } + if text.isTooCloseHSV(to: background) { + text = background.brightnessAdjustedColor + } + if accent.isTooCloseHSV(to: background) { + accent = background.brightnessAdjustedColor + } + if accentIcon.isTooCloseHSV(to: background) { + accentIcon = background.brightnessAdjustedColor + } + if grayIcon.isTooCloseHSV(to: background) { + grayIcon = background.brightnessAdjustedColor + } + if grayText.isTooCloseHSV(to: background) { + grayText = background.brightnessAdjustedColor + } + if accentSelect.isTooCloseHSV(to: background) { + accentSelect = background.brightnessAdjustedColor + } + if textBubble_incoming.isTooCloseHSV(to: bubbleBackground_incoming) { + textBubble_incoming = bubbleBackground_incoming.brightnessAdjustedColor + } + if textBubble_outgoing.isTooCloseHSV(to: bubbleBackground_outgoing) { + textBubble_outgoing = bubbleBackground_outgoing.brightnessAdjustedColor + } + if grayTextBubble_incoming.isTooCloseHSV(to: bubbleBackground_incoming) { + grayTextBubble_incoming = bubbleBackground_incoming.brightnessAdjustedColor + } + if grayTextBubble_outgoing.isTooCloseHSV(to: bubbleBackground_outgoing) { + grayTextBubble_outgoing = bubbleBackground_outgoing.brightnessAdjustedColor + } + if grayIconBubble_incoming.isTooCloseHSV(to: bubbleBackground_incoming) { + grayIconBubble_incoming = bubbleBackground_incoming.brightnessAdjustedColor + } + if grayIconBubble_outgoing.isTooCloseHSV(to: bubbleBackground_outgoing) { + grayIconBubble_outgoing = bubbleBackground_outgoing.brightnessAdjustedColor + } + if accentIconBubble_incoming.isTooCloseHSV(to: bubbleBackground_incoming) { + accentIconBubble_incoming = bubbleBackground_incoming.brightnessAdjustedColor + } + if accentIconBubble_outgoing.isTooCloseHSV(to: bubbleBackground_outgoing) { + accentIconBubble_outgoing = bubbleBackground_outgoing.brightnessAdjustedColor + } + self.isNative = isNative + self.parent = parent + self.copyright = copyright + self.isDark = isDark + self.tinted = tinted + self.name = name + self.accentList = accentList + self._basicAccent = basicAccent.withAlphaComponent(max(0.6, basicAccent.alpha)) + self._background = background.withAlphaComponent(max(0.6, background.alpha)) + self._text = text.withAlphaComponent(max(0.6, text.alpha)) + self._grayText = grayText.withAlphaComponent(max(0.6, grayText.alpha)) + self._link = link.withAlphaComponent(max(0.6, link.alpha)) + self._accent = accent.withAlphaComponent(max(0.6, accent.alpha)) + self._redUI = redUI.withAlphaComponent(max(0.6, redUI.alpha)) + self._greenUI = greenUI.withAlphaComponent(max(0.6, greenUI.alpha)) + self._blackTransparent = blackTransparent.withAlphaComponent(max(0.6, blackTransparent.alpha)) + self._grayTransparent = grayTransparent.withAlphaComponent(max(0.6, grayTransparent.alpha)) + self._grayUI = grayUI.withAlphaComponent(max(0.6, grayUI.alpha)) + self._darkGrayText = darkGrayText.withAlphaComponent(max(0.6, darkGrayText.alpha)) + self._accentSelect = accentSelect.withAlphaComponent(max(0.6, accentSelect.alpha)) + self._selectText = selectText.withAlphaComponent(max(0.6, selectText.alpha)) + self._border = border.withAlphaComponent(max(0.6, border.alpha)) + self._grayBackground = grayBackground.withAlphaComponent(max(0.6, grayBackground.alpha)) + self._grayForeground = grayForeground.withAlphaComponent(max(0.6, grayForeground.alpha)) + self._grayIcon = grayIcon.withAlphaComponent(max(0.6, grayIcon.alpha)) + self._accentIcon = accentIcon.withAlphaComponent(max(0.6, accentIcon.alpha)) + self._badgeMuted = badgeMuted.withAlphaComponent(max(0.6, badgeMuted.alpha)) + self._badge = badge.withAlphaComponent(max(0.6, badge.alpha)) + self._indicatorColor = indicatorColor.withAlphaComponent(max(0.6, indicatorColor.alpha)) + self._selectMessage = selectMessage.withAlphaComponent(max(0.6, selectMessage.alpha)) + + self._monospacedPre = monospacedPre.withAlphaComponent(max(0.6, monospacedPre.alpha)) + self._monospacedCode = monospacedCode.withAlphaComponent(max(0.6, monospacedCode.alpha)) + self._monospacedPreBubble_incoming = monospacedPreBubble_incoming.withAlphaComponent(max(0.6, monospacedPreBubble_incoming.alpha)) + self._monospacedPreBubble_outgoing = monospacedPreBubble_outgoing.withAlphaComponent(max(0.6, monospacedPreBubble_outgoing.alpha)) + self._monospacedCodeBubble_incoming = monospacedCodeBubble_incoming.withAlphaComponent(max(0.6, monospacedCodeBubble_incoming.alpha)) + self._monospacedCodeBubble_outgoing = monospacedCodeBubble_outgoing.withAlphaComponent(max(0.6, monospacedCodeBubble_outgoing.alpha)) + self._selectTextBubble_incoming = selectTextBubble_incoming.withAlphaComponent(max(0.6, selectTextBubble_incoming.alpha)) + self._selectTextBubble_outgoing = selectTextBubble_outgoing.withAlphaComponent(max(0.6, selectTextBubble_outgoing.alpha)) + self._bubbleBackground_incoming = bubbleBackground_incoming.withAlphaComponent(max(0.6, bubbleBackground_incoming.alpha)) + self._bubbleBackgroundTop_outgoing = bubbleBackgroundTop_outgoing.withAlphaComponent(max(1.0, bubbleBackgroundTop_outgoing.alpha)) + self._bubbleBackgroundBottom_outgoing = bubbleBackgroundBottom_outgoing.withAlphaComponent(max(1.0, bubbleBackgroundBottom_outgoing.alpha)) + self._bubbleBorder_incoming = bubbleBorder_incoming.withAlphaComponent(max(0.6, bubbleBorder_incoming.alpha)) + self._bubbleBorder_outgoing = bubbleBorder_outgoing.withAlphaComponent(max(0.6, bubbleBorder_outgoing.alpha)) + self._grayTextBubble_incoming = grayTextBubble_incoming.withAlphaComponent(max(0.6, grayTextBubble_incoming.alpha)) + self._grayTextBubble_outgoing = grayTextBubble_outgoing.withAlphaComponent(max(0.6, grayTextBubble_outgoing.alpha)) + self._grayIconBubble_incoming = grayIconBubble_incoming.withAlphaComponent(max(0.6, grayIconBubble_incoming.alpha)) + self._grayIconBubble_outgoing = grayIconBubble_outgoing.withAlphaComponent(max(0.6, grayIconBubble_outgoing.alpha)) + self._accentIconBubble_incoming = accentIconBubble_incoming.withAlphaComponent(max(0.6, accentIconBubble_incoming.alpha)) + self._accentIconBubble_outgoing = accentIconBubble_outgoing.withAlphaComponent(max(0.6, accentIconBubble_outgoing.alpha)) + self._linkBubble_incoming = linkBubble_incoming.withAlphaComponent(max(0.6, linkBubble_incoming.alpha)) + self._linkBubble_outgoing = linkBubble_outgoing.withAlphaComponent(max(0.6, linkBubble_outgoing.alpha)) + self._textBubble_incoming = textBubble_incoming.withAlphaComponent(max(0.6, textBubble_incoming.alpha)) + self._textBubble_outgoing = textBubble_outgoing.withAlphaComponent(max(0.6, textBubble_outgoing.alpha)) + self._selectMessageBubble = selectMessageBubble.withAlphaComponent(max(0.6, selectMessageBubble.alpha)) + self._fileActivityBackground = fileActivityBackground.withAlphaComponent(max(0.6, fileActivityBackground.alpha)) + self._fileActivityForeground = fileActivityForeground.withAlphaComponent(max(0.6, fileActivityForeground.alpha)) + self._fileActivityBackgroundBubble_incoming = fileActivityBackgroundBubble_incoming.withAlphaComponent(max(0.6, fileActivityBackgroundBubble_incoming.alpha)) + self._fileActivityBackgroundBubble_outgoing = fileActivityBackgroundBubble_outgoing.withAlphaComponent(max(0.6, fileActivityBackgroundBubble_outgoing.alpha)) + self._fileActivityForegroundBubble_incoming = fileActivityForegroundBubble_incoming.withAlphaComponent(max(0.6, fileActivityForegroundBubble_incoming.alpha)) + self._fileActivityForegroundBubble_outgoing = fileActivityForegroundBubble_outgoing.withAlphaComponent(max(0.6, fileActivityForegroundBubble_outgoing.alpha)) + self._waveformBackground = waveformBackground.withAlphaComponent(max(0.6, waveformBackground.alpha)) + self._waveformForeground = waveformForeground.withAlphaComponent(max(0.6, waveformForeground.alpha)) + self._waveformBackgroundBubble_incoming = waveformBackgroundBubble_incoming.withAlphaComponent(max(0.6, waveformBackgroundBubble_incoming.alpha)) + self._waveformBackgroundBubble_outgoing = waveformBackgroundBubble_outgoing.withAlphaComponent(max(0.6, waveformBackgroundBubble_outgoing.alpha)) + self._waveformForegroundBubble_incoming = waveformForegroundBubble_incoming.withAlphaComponent(max(0.6, waveformForegroundBubble_incoming.alpha)) + self._waveformForegroundBubble_outgoing = waveformForegroundBubble_outgoing.withAlphaComponent(max(0.6, waveformForegroundBubble_outgoing.alpha)) + self._webPreviewActivity = webPreviewActivity.withAlphaComponent(max(0.6, webPreviewActivity.alpha)) + self._webPreviewActivityBubble_incoming = webPreviewActivityBubble_incoming.withAlphaComponent(max(0.6, webPreviewActivityBubble_incoming.alpha)) + self._webPreviewActivityBubble_outgoing = webPreviewActivityBubble_outgoing.withAlphaComponent(max(0.6, webPreviewActivityBubble_outgoing.alpha)) + self._redBubble_incoming = redBubble_incoming.withAlphaComponent(max(0.6, redBubble_incoming.alpha)) + self._redBubble_outgoing = redBubble_outgoing.withAlphaComponent(max(0.6, redBubble_outgoing.alpha)) + self._greenBubble_incoming = greenBubble_incoming.withAlphaComponent(max(0.6, greenBubble_incoming.alpha)) + self._greenBubble_outgoing = greenBubble_outgoing.withAlphaComponent(max(0.6, greenBubble_outgoing.alpha)) + self._chatReplyTitle = chatReplyTitle.withAlphaComponent(max(0.6, chatReplyTitle.alpha)) + self._chatReplyTextEnabled = chatReplyTextEnabled.withAlphaComponent(max(0.6, chatReplyTextEnabled.alpha)) + self._chatReplyTextDisabled = chatReplyTextDisabled.withAlphaComponent(max(0.6, chatReplyTextDisabled.alpha)) + self._chatReplyTitleBubble_incoming = chatReplyTitleBubble_incoming.withAlphaComponent(max(0.6, chatReplyTitleBubble_incoming.alpha)) + self._chatReplyTitleBubble_outgoing = chatReplyTitleBubble_outgoing.withAlphaComponent(max(0.6, chatReplyTitleBubble_outgoing.alpha)) + self._chatReplyTextEnabledBubble_incoming = chatReplyTextEnabledBubble_incoming.withAlphaComponent(max(0.6, chatReplyTextEnabledBubble_incoming.alpha)) + self._chatReplyTextEnabledBubble_outgoing = chatReplyTextEnabledBubble_outgoing.withAlphaComponent(max(0.6, chatReplyTextEnabledBubble_outgoing.alpha)) + self._chatReplyTextDisabledBubble_incoming = chatReplyTextDisabledBubble_incoming.withAlphaComponent(max(0.6, chatReplyTextDisabledBubble_incoming.alpha)) + self._chatReplyTextDisabledBubble_outgoing = chatReplyTextDisabledBubble_outgoing.withAlphaComponent(max(0.6, chatReplyTextDisabledBubble_outgoing.alpha)) + self._groupPeerNameRed = groupPeerNameRed.withAlphaComponent(max(0.6, groupPeerNameRed.alpha)) + self._groupPeerNameOrange = groupPeerNameOrange.withAlphaComponent(max(0.6, groupPeerNameOrange.alpha)) + self._groupPeerNameViolet = groupPeerNameViolet.withAlphaComponent(max(0.6, groupPeerNameViolet.alpha)) + self._groupPeerNameGreen = groupPeerNameGreen.withAlphaComponent(max(0.6, groupPeerNameGreen.alpha)) + self._groupPeerNameCyan = groupPeerNameCyan.withAlphaComponent(max(0.6, groupPeerNameCyan.alpha)) + self._groupPeerNameLightBlue = groupPeerNameLightBlue.withAlphaComponent(max(0.6, groupPeerNameLightBlue.alpha)) + self._groupPeerNameBlue = groupPeerNameBlue.withAlphaComponent(max(0.6, groupPeerNameBlue.alpha)) + + self._peerAvatarRedTop = peerAvatarRedTop.withAlphaComponent(max(0.6, peerAvatarRedTop.alpha)) + self._peerAvatarRedBottom = peerAvatarRedBottom.withAlphaComponent(max(0.6, peerAvatarRedBottom.alpha)) + self._peerAvatarOrangeTop = peerAvatarOrangeTop.withAlphaComponent(max(0.6, peerAvatarOrangeTop.alpha)) + self._peerAvatarOrangeBottom = peerAvatarOrangeBottom.withAlphaComponent(max(0.6, peerAvatarOrangeBottom.alpha)) + self._peerAvatarVioletTop = peerAvatarVioletTop.withAlphaComponent(max(0.6, peerAvatarVioletTop.alpha)) + self._peerAvatarVioletBottom = peerAvatarVioletBottom.withAlphaComponent(max(0.6, peerAvatarVioletBottom.alpha)) + self._peerAvatarGreenTop = peerAvatarGreenTop.withAlphaComponent(max(0.6, peerAvatarGreenTop.alpha)) + self._peerAvatarGreenBottom = peerAvatarGreenBottom.withAlphaComponent(max(0.6, peerAvatarGreenBottom.alpha)) + self._peerAvatarCyanTop = peerAvatarCyanTop.withAlphaComponent(max(0.6, peerAvatarCyanTop.alpha)) + self._peerAvatarCyanBottom = peerAvatarCyanBottom.withAlphaComponent(max(0.6, peerAvatarCyanBottom.alpha)) + self._peerAvatarBlueTop = peerAvatarBlueTop.withAlphaComponent(max(0.6, peerAvatarBlueTop.alpha)) + self._peerAvatarBlueBottom = peerAvatarBlueBottom.withAlphaComponent(max(0.6, peerAvatarBlueBottom.alpha)) + self._peerAvatarPinkTop = peerAvatarPinkTop.withAlphaComponent(max(0.6, peerAvatarPinkTop.alpha)) + self._peerAvatarPinkBottom = peerAvatarPinkBottom.withAlphaComponent(max(0.6, peerAvatarPinkBottom.alpha)) + self._bubbleBackgroundHighlight_incoming = bubbleBackgroundHighlight_incoming.withAlphaComponent(max(0.6, bubbleBackgroundHighlight_incoming.alpha)) + self._bubbleBackgroundHighlight_outgoing = bubbleBackgroundHighlight_outgoing.withAlphaComponent(max(0.6, bubbleBackgroundHighlight_outgoing.alpha)) + self._chatDateActive = chatDateActive.withAlphaComponent(max(0.6, chatDateActive.alpha)) + self._chatDateText = chatDateText.withAlphaComponent(max(0.6, chatDateText.alpha)) + + self._revealAction_neutral1_background = revealAction_neutral1_background.withAlphaComponent(max(0.6, revealAction_neutral1_background.alpha)) + self._revealAction_neutral1_foreground = revealAction_neutral1_foreground.withAlphaComponent(max(0.6, revealAction_neutral1_foreground.alpha)) + self._revealAction_neutral2_background = revealAction_neutral2_background.withAlphaComponent(max(0.6, revealAction_neutral2_background.alpha)) + self._revealAction_neutral2_foreground = revealAction_neutral2_foreground.withAlphaComponent(max(0.6, revealAction_neutral2_foreground.alpha)) + self._revealAction_destructive_background = revealAction_destructive_background.withAlphaComponent(max(0.6, revealAction_destructive_background.alpha)) + self._revealAction_destructive_foreground = revealAction_destructive_foreground.withAlphaComponent(max(0.6, revealAction_destructive_foreground.alpha)) + self._revealAction_constructive_background = revealAction_constructive_background.withAlphaComponent(max(0.6, revealAction_constructive_background.alpha)) + self._revealAction_constructive_foreground = revealAction_constructive_foreground.withAlphaComponent(max(0.6, revealAction_constructive_foreground.alpha)) + self._revealAction_accent_background = revealAction_accent_background.withAlphaComponent(max(0.6, revealAction_accent_background.alpha)) + self._revealAction_accent_foreground = revealAction_accent_foreground.withAlphaComponent(max(0.6, revealAction_accent_foreground.alpha)) + self._revealAction_warning_background = revealAction_warning_background.withAlphaComponent(max(0.6, revealAction_warning_background.alpha)) + self._revealAction_warning_foreground = revealAction_warning_foreground.withAlphaComponent(max(0.6, revealAction_warning_foreground.alpha)) + self._revealAction_inactive_background = revealAction_inactive_background.withAlphaComponent(max(0.6, revealAction_inactive_background.alpha)) + self._revealAction_inactive_foreground = revealAction_inactive_foreground.withAlphaComponent(max(0.6, revealAction_inactive_foreground.alpha)) + + self._chatBackground = chatBackground.withAlphaComponent(max(0.6, chatBackground.alpha)) + self.wallpaper = wallpaper + self._listBackground = listBackground + self._listGrayText = listGrayText + self._grayHighlight = grayHighlight + self._focusAnimationColor = focusAnimationColor + } + + public func listProperties(reflect: Mirror? = nil) -> [String] { + let mirror = reflect ?? Mirror(reflecting: self) + + return mirror.children.enumerated().filter({$0.element.label != nil}).map({$0.element.label!}) + } + + public func colorFromStringVariable(_ string: String) -> NSColor? { + let mirror = Mirror(reflecting: self) + for (_, value) in mirror.children.enumerated() { + if value.label == string { + return value.value as? NSColor + } + } + return nil + } + + public func withoutAccentColor() -> ColorPalette { + switch self.name { + case whitePalette.name: + return whitePalette + case "Night Blue": + return nightAccentPalette + case nightAccentPalette.name: + return nightAccentPalette + case darkPalette.name: + return darkPalette + case dayClassicPalette.name: + return dayClassicPalette + case systemPalette.name: + return systemPalette + case "Mojave": + return darkPalette + default: + return self + } + } + + public func withUpdatedName(_ name: String) -> ColorPalette { + return ColorPalette(isNative: self.isNative, isDark: isDark, + tinted: tinted, + name: name, + parent: parent, + wallpaper: wallpaper, + copyright: copyright, + accentList: accentList, + basicAccent: basicAccent, + background: background, + text: text, + grayText: grayText, + link: link, + accent: accent, + redUI: redUI, + greenUI: greenUI, + blackTransparent: blackTransparent, + grayTransparent: grayTransparent, + grayUI: grayUI, + darkGrayText: darkGrayText, + accentSelect: accentSelect, + selectText: selectText, + border: border, + grayBackground: grayBackground, + grayForeground: grayForeground, + grayIcon: grayIcon, + accentIcon: accentIcon, + badgeMuted: badgeMuted, + badge: badge, + indicatorColor: indicatorColor, + selectMessage: selectMessage, + monospacedPre: monospacedPre, + monospacedCode: monospacedCode, + monospacedPreBubble_incoming: monospacedPreBubble_incoming, + monospacedPreBubble_outgoing: monospacedPreBubble_outgoing, + monospacedCodeBubble_incoming: monospacedCodeBubble_incoming, + monospacedCodeBubble_outgoing: monospacedCodeBubble_outgoing, + selectTextBubble_incoming: selectTextBubble_incoming, + selectTextBubble_outgoing: selectTextBubble_outgoing, + bubbleBackground_incoming: bubbleBackground_incoming, + bubbleBackgroundTop_outgoing: bubbleBackgroundTop_outgoing, + bubbleBackgroundBottom_outgoing: bubbleBackgroundBottom_outgoing, + bubbleBorder_incoming: bubbleBorder_incoming, + bubbleBorder_outgoing: bubbleBorder_outgoing, + grayTextBubble_incoming: grayTextBubble_incoming, + grayTextBubble_outgoing: grayTextBubble_outgoing, + grayIconBubble_incoming: grayIconBubble_incoming, + grayIconBubble_outgoing: grayIconBubble_outgoing, + accentIconBubble_incoming: accentIconBubble_incoming, + accentIconBubble_outgoing: accentIconBubble_outgoing, + linkBubble_incoming: linkBubble_incoming, + linkBubble_outgoing: linkBubble_outgoing, + textBubble_incoming: textBubble_incoming, + textBubble_outgoing: textBubble_outgoing, + selectMessageBubble: selectMessageBubble, + fileActivityBackground: fileActivityBackground, + fileActivityForeground: fileActivityForeground, + fileActivityBackgroundBubble_incoming: fileActivityBackgroundBubble_incoming, + fileActivityBackgroundBubble_outgoing: fileActivityBackgroundBubble_outgoing, + fileActivityForegroundBubble_incoming: fileActivityForegroundBubble_incoming, + fileActivityForegroundBubble_outgoing: fileActivityForegroundBubble_outgoing, + waveformBackground: waveformBackground, + waveformForeground: waveformForeground, + waveformBackgroundBubble_incoming: waveformBackgroundBubble_incoming, + waveformBackgroundBubble_outgoing: waveformBackgroundBubble_outgoing, + waveformForegroundBubble_incoming: waveformForegroundBubble_incoming, + waveformForegroundBubble_outgoing: waveformForegroundBubble_outgoing, + webPreviewActivity: webPreviewActivity, + webPreviewActivityBubble_incoming: webPreviewActivityBubble_incoming, + webPreviewActivityBubble_outgoing: webPreviewActivityBubble_outgoing, + redBubble_incoming: redBubble_incoming, + redBubble_outgoing: redBubble_outgoing, + greenBubble_incoming: greenBubble_incoming, + greenBubble_outgoing: greenBubble_outgoing, + chatReplyTitle: chatReplyTitle, + chatReplyTextEnabled: chatReplyTextEnabled, + chatReplyTextDisabled: chatReplyTextDisabled, + chatReplyTitleBubble_incoming: chatReplyTitleBubble_incoming, + chatReplyTitleBubble_outgoing: chatReplyTitleBubble_outgoing, + chatReplyTextEnabledBubble_incoming: chatReplyTextEnabledBubble_incoming, + chatReplyTextEnabledBubble_outgoing: chatReplyTextEnabledBubble_outgoing, + chatReplyTextDisabledBubble_incoming: chatReplyTextDisabledBubble_incoming, + chatReplyTextDisabledBubble_outgoing: chatReplyTextDisabledBubble_outgoing, + groupPeerNameRed: groupPeerNameRed, + groupPeerNameOrange: groupPeerNameOrange, + groupPeerNameViolet: groupPeerNameViolet, + groupPeerNameGreen: groupPeerNameGreen, + groupPeerNameCyan: groupPeerNameCyan, + groupPeerNameLightBlue: groupPeerNameLightBlue, + groupPeerNameBlue: groupPeerNameBlue, + peerAvatarRedTop: peerAvatarRedTop, + peerAvatarRedBottom: peerAvatarRedBottom, + peerAvatarOrangeTop: peerAvatarOrangeTop, + peerAvatarOrangeBottom: peerAvatarOrangeBottom, + peerAvatarVioletTop: peerAvatarVioletTop, + peerAvatarVioletBottom: peerAvatarVioletBottom, + peerAvatarGreenTop: peerAvatarGreenTop, + peerAvatarGreenBottom: peerAvatarGreenBottom, + peerAvatarCyanTop: peerAvatarCyanTop, + peerAvatarCyanBottom: peerAvatarCyanBottom, + peerAvatarBlueTop: peerAvatarBlueTop, + peerAvatarBlueBottom: peerAvatarBlueBottom, + peerAvatarPinkTop: peerAvatarPinkTop, + peerAvatarPinkBottom: peerAvatarPinkBottom, + bubbleBackgroundHighlight_incoming: bubbleBackgroundHighlight_incoming, + bubbleBackgroundHighlight_outgoing: bubbleBackgroundHighlight_outgoing, + chatDateActive: chatDateActive, + chatDateText: chatDateText, + revealAction_neutral1_background: revealAction_neutral1_background, + revealAction_neutral1_foreground: revealAction_neutral1_foreground, + revealAction_neutral2_background: revealAction_neutral2_background, + revealAction_neutral2_foreground: revealAction_neutral2_foreground, + revealAction_destructive_background: revealAction_destructive_background, + revealAction_destructive_foreground: revealAction_destructive_foreground, + revealAction_constructive_background: revealAction_constructive_background, + revealAction_constructive_foreground: revealAction_constructive_foreground, + revealAction_accent_background: revealAction_accent_background, + revealAction_accent_foreground: revealAction_accent_foreground, + revealAction_warning_background: revealAction_warning_background, + revealAction_warning_foreground: revealAction_warning_foreground, + revealAction_inactive_background: revealAction_inactive_background, + revealAction_inactive_foreground: revealAction_inactive_foreground, + chatBackground: chatBackground, + listBackground: listBackground, + listGrayText: listGrayText, + grayHighlight: grayHighlight, + focusAnimationColor: focusAnimationColor) + } + + public func withUpdatedWallpaper(_ wallpaper: PaletteWallpaper) -> ColorPalette { + return ColorPalette(isNative: self.isNative, isDark: isDark, + tinted: tinted, + name: name, + parent: parent, + wallpaper: wallpaper, + copyright: copyright, + accentList: accentList, + basicAccent: basicAccent, + background: background, + text: text, + grayText: grayText, + link: link, + accent: accent, + redUI: redUI, + greenUI: greenUI, + blackTransparent: blackTransparent, + grayTransparent: grayTransparent, + grayUI: grayUI, + darkGrayText: darkGrayText, + accentSelect: accentSelect, + selectText: selectText, + border: border, + grayBackground: grayBackground, + grayForeground: grayForeground, + grayIcon: grayIcon, + accentIcon: accentIcon, + badgeMuted: badgeMuted, + badge: badge, + indicatorColor: indicatorColor, + selectMessage: selectMessage, + monospacedPre: monospacedPre, + monospacedCode: monospacedCode, + monospacedPreBubble_incoming: monospacedPreBubble_incoming, + monospacedPreBubble_outgoing: monospacedPreBubble_outgoing, + monospacedCodeBubble_incoming: monospacedCodeBubble_incoming, + monospacedCodeBubble_outgoing: monospacedCodeBubble_outgoing, + selectTextBubble_incoming: selectTextBubble_incoming, + selectTextBubble_outgoing: selectTextBubble_outgoing, + bubbleBackground_incoming: bubbleBackground_incoming, + bubbleBackgroundTop_outgoing: bubbleBackgroundTop_outgoing, + bubbleBackgroundBottom_outgoing: bubbleBackgroundBottom_outgoing, + bubbleBorder_incoming: bubbleBorder_incoming, + bubbleBorder_outgoing: bubbleBorder_outgoing, + grayTextBubble_incoming: grayTextBubble_incoming, + grayTextBubble_outgoing: grayTextBubble_outgoing, + grayIconBubble_incoming: grayIconBubble_incoming, + grayIconBubble_outgoing: grayIconBubble_outgoing, + accentIconBubble_incoming: accentIconBubble_incoming, + accentIconBubble_outgoing: accentIconBubble_outgoing, + linkBubble_incoming: linkBubble_incoming, + linkBubble_outgoing: linkBubble_outgoing, + textBubble_incoming: textBubble_incoming, + textBubble_outgoing: textBubble_outgoing, + selectMessageBubble: selectMessageBubble, + fileActivityBackground: fileActivityBackground, + fileActivityForeground: fileActivityForeground, + fileActivityBackgroundBubble_incoming: fileActivityBackgroundBubble_incoming, + fileActivityBackgroundBubble_outgoing: fileActivityBackgroundBubble_outgoing, + fileActivityForegroundBubble_incoming: fileActivityForegroundBubble_incoming, + fileActivityForegroundBubble_outgoing: fileActivityForegroundBubble_outgoing, + waveformBackground: waveformBackground, + waveformForeground: waveformForeground, + waveformBackgroundBubble_incoming: waveformBackgroundBubble_incoming, + waveformBackgroundBubble_outgoing: waveformBackgroundBubble_outgoing, + waveformForegroundBubble_incoming: waveformForegroundBubble_incoming, + waveformForegroundBubble_outgoing: waveformForegroundBubble_outgoing, + webPreviewActivity: webPreviewActivity, + webPreviewActivityBubble_incoming: webPreviewActivityBubble_incoming, + webPreviewActivityBubble_outgoing: webPreviewActivityBubble_outgoing, + redBubble_incoming: redBubble_incoming, + redBubble_outgoing: redBubble_outgoing, + greenBubble_incoming: greenBubble_incoming, + greenBubble_outgoing: greenBubble_outgoing, + chatReplyTitle: chatReplyTitle, + chatReplyTextEnabled: chatReplyTextEnabled, + chatReplyTextDisabled: chatReplyTextDisabled, + chatReplyTitleBubble_incoming: chatReplyTitleBubble_incoming, + chatReplyTitleBubble_outgoing: chatReplyTitleBubble_outgoing, + chatReplyTextEnabledBubble_incoming: chatReplyTextEnabledBubble_incoming, + chatReplyTextEnabledBubble_outgoing: chatReplyTextEnabledBubble_outgoing, + chatReplyTextDisabledBubble_incoming: chatReplyTextDisabledBubble_incoming, + chatReplyTextDisabledBubble_outgoing: chatReplyTextDisabledBubble_outgoing, + groupPeerNameRed: groupPeerNameRed, + groupPeerNameOrange: groupPeerNameOrange, + groupPeerNameViolet: groupPeerNameViolet, + groupPeerNameGreen: groupPeerNameGreen, + groupPeerNameCyan: groupPeerNameCyan, + groupPeerNameLightBlue: groupPeerNameLightBlue, + groupPeerNameBlue: groupPeerNameBlue, + peerAvatarRedTop: peerAvatarRedTop, + peerAvatarRedBottom: peerAvatarRedBottom, + peerAvatarOrangeTop: peerAvatarOrangeTop, + peerAvatarOrangeBottom: peerAvatarOrangeBottom, + peerAvatarVioletTop: peerAvatarVioletTop, + peerAvatarVioletBottom: peerAvatarVioletBottom, + peerAvatarGreenTop: peerAvatarGreenTop, + peerAvatarGreenBottom: peerAvatarGreenBottom, + peerAvatarCyanTop: peerAvatarCyanTop, + peerAvatarCyanBottom: peerAvatarCyanBottom, + peerAvatarBlueTop: peerAvatarBlueTop, + peerAvatarBlueBottom: peerAvatarBlueBottom, + peerAvatarPinkTop: peerAvatarPinkTop, + peerAvatarPinkBottom: peerAvatarPinkBottom, + bubbleBackgroundHighlight_incoming: bubbleBackgroundHighlight_incoming, + bubbleBackgroundHighlight_outgoing: bubbleBackgroundHighlight_outgoing, + chatDateActive: chatDateActive, + chatDateText: chatDateText, + revealAction_neutral1_background: revealAction_neutral1_background, + revealAction_neutral1_foreground: revealAction_neutral1_foreground, + revealAction_neutral2_background: revealAction_neutral2_background, + revealAction_neutral2_foreground: revealAction_neutral2_foreground, + revealAction_destructive_background: revealAction_destructive_background, + revealAction_destructive_foreground: revealAction_destructive_foreground, + revealAction_constructive_background: revealAction_constructive_background, + revealAction_constructive_foreground: revealAction_constructive_foreground, + revealAction_accent_background: revealAction_accent_background, + revealAction_accent_foreground: revealAction_accent_foreground, + revealAction_warning_background: revealAction_warning_background, + revealAction_warning_foreground: revealAction_warning_foreground, + revealAction_inactive_background: revealAction_inactive_background, + revealAction_inactive_foreground: revealAction_inactive_foreground, + chatBackground: chatBackground, + listBackground: listBackground, + listGrayText: listGrayText, + grayHighlight: grayHighlight, + focusAnimationColor: focusAnimationColor) + } + + public func withAccentColor(_ color: PaletteAccentColor, disableTint: Bool = false) -> ColorPalette { + + var accentColor = color.accent + let hsv = color.accent.hsv + accentColor = NSColor(hue: hsv.0, saturation: hsv.1, brightness: max(hsv.2, 0.18), alpha: 1.0) + + + + + var background = self.background + var border = self.border + var grayBackground = self.grayBackground + var grayForeground = self.grayForeground + var bubbleBackground_incoming = self.bubbleBackground_incoming + var bubbleBorder_incoming = self.bubbleBorder_incoming + var bubbleBorder_outgoing = self.bubbleBorder_outgoing + var bubbleBackgroundHighlight_incoming = self.bubbleBackgroundHighlight_incoming + var bubbleBackgroundHighlight_outgoing = self.bubbleBackgroundHighlight_outgoing + var chatBackground = self.chatBackground + var listBackground = self.listBackground + var selectMessage = self.selectMessage + var grayHighlight = self.grayHighlight + let link = color.accent + + if tinted && !disableTint { + background = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.25) + border = accentColor.withMultiplied(hue: 1.024, saturation: 0.585, brightness: 0.3) + grayForeground = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) + grayBackground = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18) + bubbleBackground_incoming = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.28) + bubbleBorder_incoming = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.38) + bubbleBackgroundHighlight_incoming = accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.38) + chatBackground = accentColor.withMultiplied(hue: 1.024, saturation: 0.570, brightness: 0.14) + selectMessage = accentColor.withMultiplied(hue: 1.024, saturation: 0.570, brightness: 0.3) + listBackground = accentColor.withMultiplied(hue: 1.024, saturation: 0.572, brightness: 0.16) + grayHighlight = background.darker(amount: 0.08) + } + + + var lightnessColor = color.messages?.top ?? color.accent + + if color.messages == nil { + switch parent { + case .dayClassic: + let hsb = accentColor.hsb + lightnessColor = NSColor(hue: hsb.0, saturation: (hsb.1 > 0.0 && hsb.2 > 0.0) ? 0.14 : 0.0, brightness: 0.79 + hsb.2 * 0.21, alpha: 1.0) + default: + break + } + } else if let messages = color.messages { + lightnessColor = messages.top.blended(withFraction: 0.5, of: messages.bottom)! + } + + + + let bubbleBackgroundTop_outgoing = color.messages?.top ?? lightnessColor + let bubbleBackgroundBottom_outgoing = color.messages?.bottom ?? lightnessColor + + bubbleBackgroundHighlight_outgoing = lightnessColor.darker(amount: 0.1) + + + + + var textBubble_outgoing = self.textBubble_outgoing + var webPreviewActivityBubble_outgoing = self.webPreviewActivityBubble_outgoing + var monospacedPreBubble_outgoing = self.monospacedPreBubble_outgoing + var monospacedCodeBubble_outgoing = self.monospacedCodeBubble_outgoing + var grayTextBubble_outgoing = self.grayTextBubble_outgoing + var grayIconBubble_outgoing = self.grayIconBubble_outgoing + var accentIconBubble_outgoing = self.accentIconBubble_outgoing + var fileActivityForegroundBubble_outgoing = self.fileActivityForegroundBubble_outgoing + var fileActivityBackgroundBubble_outgoing = self.fileActivityBackgroundBubble_outgoing + var linkBubble_outgoing = self.linkBubble_outgoing + var chatReplyTextEnabledBubble_outgoing = self.chatReplyTextEnabledBubble_outgoing + var chatReplyTextDisabledBubble_outgoing = self.chatReplyTextDisabledBubble_outgoing + var chatReplyTitleBubble_outgoing = self.chatReplyTitleBubble_outgoing + + var waveformForegroundBubble_outgoing = self.waveformForegroundBubble_outgoing + var waveformBackgroundBubble_outgoing = self.waveformBackgroundBubble_outgoing + let waveformForegroundBubble_incoming = color.accent + let waveformBackgroundBubble_incoming = color.accent.withAlphaComponent(0.6) + + + let fileActivityForegroundBubble_incoming = NSColor(0xffffff) + let fileActivityBackgroundBubble_incoming = color.accent + + + var selectTextBubble_outgoing = self.selectTextBubble_outgoing + + + bubbleBorder_outgoing = lightnessColor.withAlphaComponent(0.7) + + if lightnessColor.lightness > 0.75 { + let hueFactor: CGFloat = 0.75 + let saturationFactor: CGFloat = 1.1 + let outgoingPrimaryTextColor = NSColor(rgb: 0x000000) + let outgoingSecondaryTextColor = lightnessColor.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554 * saturationFactor, brightness: 0.549).withAlphaComponent(0.8) + bubbleBackgroundHighlight_outgoing = lightnessColor.withMultiplied(hue: 1.024, saturation: 0.9, brightness: 0.9) + + textBubble_outgoing = outgoingPrimaryTextColor + webPreviewActivityBubble_outgoing = outgoingSecondaryTextColor.withAlphaComponent(1.0) + monospacedPreBubble_outgoing = outgoingPrimaryTextColor + monospacedCodeBubble_outgoing = outgoingPrimaryTextColor + grayTextBubble_outgoing = outgoingSecondaryTextColor + grayIconBubble_outgoing = outgoingSecondaryTextColor + accentIconBubble_outgoing = outgoingSecondaryTextColor + fileActivityForegroundBubble_outgoing = NSColor(0xffffff) + fileActivityBackgroundBubble_outgoing = outgoingSecondaryTextColor.withAlphaComponent(1.0) + linkBubble_outgoing = outgoingSecondaryTextColor + chatReplyTextEnabledBubble_outgoing = outgoingPrimaryTextColor + chatReplyTextDisabledBubble_outgoing = outgoingSecondaryTextColor + chatReplyTitleBubble_outgoing = outgoingSecondaryTextColor + + waveformBackgroundBubble_outgoing = lightnessColor.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554 * saturationFactor, brightness: 0.549).withAlphaComponent(0.4) + waveformForegroundBubble_outgoing = lightnessColor.withMultiplied(hue: 1.344 * hueFactor, saturation: 4.554 * saturationFactor, brightness: 0.549).withAlphaComponent(0.8) + + selectTextBubble_outgoing = lightnessColor.lighter(amount: 0.2) + + + } else { + + waveformBackgroundBubble_outgoing = NSColor(0xffffff, 0) + waveformForegroundBubble_outgoing = NSColor(0xffffff, 0) + + textBubble_outgoing = NSColor(0xffffff) + bubbleBackgroundHighlight_outgoing = lightnessColor.withMultiplied(hue: 1.024, saturation: 0.9, brightness: 0.9) + webPreviewActivityBubble_outgoing = NSColor(0xffffff) + monospacedPreBubble_outgoing = NSColor(0xffffff) + monospacedCodeBubble_outgoing = NSColor(0xffffff) + grayTextBubble_outgoing = textBubble_outgoing.withMultiplied(hue: 0.956, saturation: 0.17, brightness: 1.0) + grayIconBubble_outgoing = textBubble_outgoing.withMultiplied(hue: 0.956, saturation: 0.17, brightness: 1.0) + accentIconBubble_outgoing = textBubble_outgoing + fileActivityForegroundBubble_outgoing = color.accent + fileActivityBackgroundBubble_outgoing = textBubble_outgoing + linkBubble_outgoing = textBubble_outgoing + chatReplyTextEnabledBubble_outgoing = textBubble_outgoing.withMultiplied(hue: 0.956, saturation: 0.17, brightness: 1.0) + chatReplyTextDisabledBubble_outgoing = textBubble_outgoing.withMultiplied(hue: 0.956, saturation: 0.17, brightness: 1.0) + chatReplyTitleBubble_outgoing = textBubble_outgoing.withMultiplied(hue: 0.956, saturation: 0.17, brightness: 1.0) + + } + + + + let chatReplyTitleBubble_incoming = color.accent + + return ColorPalette(isNative: self.isNative, isDark: isDark, + tinted: tinted, + name: name, + parent: parent, + wallpaper: wallpaper, + copyright: copyright, + accentList: accentList, + basicAccent: basicAccent, + background: background, + text: text, + grayText: grayText, + link: link, + accent: color.accent, + redUI: redUI, + greenUI: greenUI, + blackTransparent: blackTransparent, + grayTransparent: grayTransparent, + grayUI: grayUI, + darkGrayText: darkGrayText, + accentSelect: color.accent, + selectText: selectText, + border: border, + grayBackground: grayBackground, + grayForeground: grayForeground, + grayIcon: grayIcon, + accentIcon: color.accent, + badgeMuted: badgeMuted, + badge: color.accent, + indicatorColor: indicatorColor, + selectMessage: selectMessage, + monospacedPre: monospacedPre, + monospacedCode: monospacedCode, + monospacedPreBubble_incoming: monospacedPreBubble_incoming, + monospacedPreBubble_outgoing: monospacedPreBubble_outgoing, + monospacedCodeBubble_incoming: monospacedCodeBubble_incoming, + monospacedCodeBubble_outgoing: monospacedCodeBubble_outgoing, + selectTextBubble_incoming: selectTextBubble_incoming, + selectTextBubble_outgoing: selectTextBubble_outgoing, + bubbleBackground_incoming: bubbleBackground_incoming, + bubbleBackgroundTop_outgoing: bubbleBackgroundTop_outgoing, + bubbleBackgroundBottom_outgoing: bubbleBackgroundBottom_outgoing, + bubbleBorder_incoming: bubbleBorder_incoming, + bubbleBorder_outgoing: bubbleBorder_outgoing, + grayTextBubble_incoming: grayTextBubble_incoming, + grayTextBubble_outgoing: grayTextBubble_outgoing, + grayIconBubble_incoming: grayIconBubble_incoming, + grayIconBubble_outgoing: grayIconBubble_outgoing, + accentIconBubble_incoming: accentIconBubble_incoming, + accentIconBubble_outgoing: accentIconBubble_outgoing, + linkBubble_incoming: linkBubble_incoming, + linkBubble_outgoing: linkBubble_outgoing, + textBubble_incoming: textBubble_incoming, + textBubble_outgoing: textBubble_outgoing, + selectMessageBubble: selectMessageBubble, + fileActivityBackground: color.accent, + fileActivityForeground: fileActivityForeground, + fileActivityBackgroundBubble_incoming: fileActivityBackgroundBubble_incoming, + fileActivityBackgroundBubble_outgoing: fileActivityBackgroundBubble_outgoing, + fileActivityForegroundBubble_incoming: fileActivityForegroundBubble_incoming, + fileActivityForegroundBubble_outgoing: fileActivityForegroundBubble_outgoing, + waveformBackground: waveformBackground, + waveformForeground: color.accent, + waveformBackgroundBubble_incoming: waveformBackgroundBubble_incoming, + waveformBackgroundBubble_outgoing: waveformBackgroundBubble_outgoing, + waveformForegroundBubble_incoming: waveformForegroundBubble_incoming, + waveformForegroundBubble_outgoing: waveformForegroundBubble_outgoing, + webPreviewActivity: color.accent, + webPreviewActivityBubble_incoming: webPreviewActivityBubble_incoming, + webPreviewActivityBubble_outgoing: webPreviewActivityBubble_outgoing, + redBubble_incoming: redBubble_incoming, + redBubble_outgoing: redBubble_outgoing, + greenBubble_incoming: greenBubble_incoming, + greenBubble_outgoing: greenBubble_outgoing, + chatReplyTitle: color.accent, + chatReplyTextEnabled: chatReplyTextEnabled, + chatReplyTextDisabled: chatReplyTextDisabled, + chatReplyTitleBubble_incoming: chatReplyTitleBubble_incoming, + chatReplyTitleBubble_outgoing: chatReplyTitleBubble_outgoing, + chatReplyTextEnabledBubble_incoming: chatReplyTextEnabledBubble_incoming, + chatReplyTextEnabledBubble_outgoing: chatReplyTextEnabledBubble_outgoing, + chatReplyTextDisabledBubble_incoming: chatReplyTextDisabledBubble_incoming, + chatReplyTextDisabledBubble_outgoing: chatReplyTextDisabledBubble_outgoing, + groupPeerNameRed: groupPeerNameRed, + groupPeerNameOrange: groupPeerNameOrange, + groupPeerNameViolet: groupPeerNameViolet, + groupPeerNameGreen: groupPeerNameGreen, + groupPeerNameCyan: groupPeerNameCyan, + groupPeerNameLightBlue: groupPeerNameLightBlue, + groupPeerNameBlue: groupPeerNameBlue, + peerAvatarRedTop: peerAvatarRedTop, + peerAvatarRedBottom: peerAvatarRedBottom, + peerAvatarOrangeTop: peerAvatarOrangeTop, + peerAvatarOrangeBottom: peerAvatarOrangeBottom, + peerAvatarVioletTop: peerAvatarVioletTop, + peerAvatarVioletBottom: peerAvatarVioletBottom, + peerAvatarGreenTop: peerAvatarGreenTop, + peerAvatarGreenBottom: peerAvatarGreenBottom, + peerAvatarCyanTop: peerAvatarCyanTop, + peerAvatarCyanBottom: peerAvatarCyanBottom, + peerAvatarBlueTop: peerAvatarBlueTop, + peerAvatarBlueBottom: peerAvatarBlueBottom, + peerAvatarPinkTop: peerAvatarPinkTop, + peerAvatarPinkBottom: peerAvatarPinkBottom, + bubbleBackgroundHighlight_incoming: bubbleBackgroundHighlight_incoming, + bubbleBackgroundHighlight_outgoing: bubbleBackgroundHighlight_outgoing, + chatDateActive: chatDateActive, + chatDateText: chatDateText, + revealAction_neutral1_background: revealAction_neutral1_background, + revealAction_neutral1_foreground: revealAction_neutral1_foreground, + revealAction_neutral2_background: revealAction_neutral2_background, + revealAction_neutral2_foreground: revealAction_neutral2_foreground, + revealAction_destructive_background: revealAction_destructive_background, + revealAction_destructive_foreground: revealAction_destructive_foreground, + revealAction_constructive_background: revealAction_constructive_background, + revealAction_constructive_foreground: revealAction_constructive_foreground, + revealAction_accent_background: revealAction_accent_background, + revealAction_accent_foreground: revealAction_accent_foreground, + revealAction_warning_background: revealAction_warning_background, + revealAction_warning_foreground: revealAction_warning_foreground, + revealAction_inactive_background: revealAction_inactive_background, + revealAction_inactive_foreground: revealAction_inactive_foreground, + chatBackground: chatBackground, + listBackground: listBackground, + listGrayText: listGrayText, + grayHighlight: grayHighlight, + focusAnimationColor: focusAnimationColor) + } +} + + + +open class PresentationTheme : Equatable { + + public let colors:ColorPalette + public let search: SearchTheme + + public let resourceCache = PresentationsResourceCache() + + public init(colors: ColorPalette, search: SearchTheme) { + self.colors = colors + self.search = search + } + + static var current: PresentationTheme { + return presentation + } + + + public static func ==(lhs: PresentationTheme, rhs: PresentationTheme) -> Bool { + return lhs === rhs + } + + // public func image(_ key: Int32, _ generate: (PresentationTheme) -> CGImage?) -> CGImage? { + // return self.resourceCache.image(key, self, generate) + // } + // + // public func object(_ key: Int32, _ generate: (PresentationTheme) -> AnyObject?) -> AnyObject? { + // return self.resourceCache.object(key, self, generate) + // } +} + + +public var navigationButtonStyle:ControlStyle { + return ControlStyle(font: .medium(.title), foregroundColor: presentation.colors.accent, backgroundColor: presentation.colors.background, highlightColor: presentation.colors.accent) +} +public var switchViewAppearance: SwitchViewAppearance { + return SwitchViewAppearance(backgroundColor: presentation.colors.background, stateOnColor: presentation.colors.accent, stateOffColor: presentation.colors.grayForeground, disabledColor: presentation.colors.grayTransparent, borderColor: presentation.colors.border) +} + +public enum TelegramBuiltinTheme : String { + case day = "day" + case dayClassic = "dayClassic" + case dark = "dark" + case nightAccent = "nightAccent" + case system = "system" + + public init?(rawValue: String) { + switch rawValue { + case "Day": + self = .day + case "day": + self = .day + case "Day Classic": + self = .dayClassic + case "dayClassic": + self = .dayClassic + case "Dark": + self = .dark + case "dark": + self = .dark + case "Tinted Blue": + self = .nightAccent + case "Night Blue": + self = .nightAccent + case "nightAccent": + self = .nightAccent + case "System": + self = .system + case "system": + self = .system + default: + return nil + } + } + + public var palette: ColorPalette { + switch self { + case .day: + return whitePalette + case .dark: + return darkPalette + case .dayClassic: + return dayClassicPalette + case .system: + return systemPalette + case .nightAccent: + return nightAccentPalette + } + } +} + + + +//0xE3EDF4 +public let whitePalette = ColorPalette(isNative: true, isDark: false, + tinted: false, + name: "day", + parent: .day, + wallpaper: .none, + copyright: "Telegram", + accentList: [PaletteAccentColor(NSColor(0x2481cc)), + PaletteAccentColor(NSColor(0xf83b4c)), + PaletteAccentColor(NSColor(0xff7519)), + PaletteAccentColor(NSColor(0xeba239)), + PaletteAccentColor(NSColor(0x29b327)), + PaletteAccentColor(NSColor(0x00c2ed)), + PaletteAccentColor(NSColor(0x7748ff)), + PaletteAccentColor(NSColor(0xff5da2))], + basicAccent: NSColor(0x2481cc), + background: NSColor(0xffffff), + text: NSColor(0x000000), + grayText: NSColor(0x999999), + link: NSColor(0x2481cc), + accent: NSColor(0x2481cc), + redUI: NSColor(0xff3b30), + greenUI:NSColor(0x63DA6E), + blackTransparent: NSColor(0x000000, 0.6), + grayTransparent: NSColor(0xf4f4f4, 0.4), + grayUI: NSColor(0xFaFaFa), + darkGrayText:NSColor(0x333333), + accentSelect:NSColor(0x4c91c7), + selectText:NSColor(0xeaeaea), + border:NSColor(0xeaeaea), + grayBackground:NSColor(0xf4f4f4), + grayForeground:NSColor(0xe4e4e4), + grayIcon:NSColor(0x9e9e9e), + accentIcon:NSColor(0x2481cc), + badgeMuted:NSColor(0xd7d7d7), + badge:NSColor(0x4ba3e2), + indicatorColor: NSColor(0x464a57), + selectMessage: NSColor(0xeaeaea), + monospacedPre: NSColor(0x000000), + monospacedCode: NSColor(0xff3b30), + monospacedPreBubble_incoming: NSColor(0xff3b30), + monospacedPreBubble_outgoing: NSColor(0xffffff), + monospacedCodeBubble_incoming: NSColor(0xff3b30), + monospacedCodeBubble_outgoing: NSColor(0xffffff), + selectTextBubble_incoming: NSColor(0xCCDDEA), + selectTextBubble_outgoing: NSColor(0x6DA8D6), + bubbleBackground_incoming: NSColor(0xF4F4F4), + bubbleBackgroundTop_outgoing: NSColor(0x4c91c7),//0x007ee5 + bubbleBackgroundBottom_outgoing: NSColor(0x4c91c7),//0x007ee5 + bubbleBorder_incoming: NSColor(0xeaeaea), + bubbleBorder_outgoing: NSColor(0x4c91c7), + grayTextBubble_incoming: NSColor(0x999999), + grayTextBubble_outgoing: NSColor(0xEFFAFF, 0.8), + grayIconBubble_incoming: NSColor(0x999999), + grayIconBubble_outgoing: NSColor(0xEFFAFF, 0.8), + accentIconBubble_incoming: NSColor(0x999999), + accentIconBubble_outgoing: NSColor(0xEFFAFF, 0.8), + linkBubble_incoming: NSColor(0x2481cc), + linkBubble_outgoing: NSColor(0xffffff), + textBubble_incoming: NSColor(0x000000), + textBubble_outgoing: NSColor(0xffffff), + selectMessageBubble: NSColor(0xEDF4F9, 0.6), + fileActivityBackground: NSColor(0x4ba3e2), + fileActivityForeground: NSColor(0xffffff), + fileActivityBackgroundBubble_incoming: NSColor(0x4ba3e2), + fileActivityBackgroundBubble_outgoing: NSColor(0xffffff), + fileActivityForegroundBubble_incoming: NSColor(0xffffff), + fileActivityForegroundBubble_outgoing: NSColor(0x4c91c7), + waveformBackground: NSColor(0x9e9e9e, 0.7), + waveformForeground: NSColor(0x4ba3e2), + waveformBackgroundBubble_incoming: NSColor(0x999999), + waveformBackgroundBubble_outgoing: NSColor(0xffffff), + waveformForegroundBubble_incoming: NSColor(0x4ba3e2), + waveformForegroundBubble_outgoing: NSColor(0xEFFAFF), + webPreviewActivity: NSColor(0x2481cc), + webPreviewActivityBubble_incoming: NSColor(0x2481cc), + webPreviewActivityBubble_outgoing: NSColor(0xffffff), + redBubble_incoming:NSColor(0xff3b30), + redBubble_outgoing:NSColor(0xff3b30), + greenBubble_incoming:NSColor(0x63DA6E), + greenBubble_outgoing:NSColor(0x63DA6E), + chatReplyTitle: NSColor(0x2481cc), + chatReplyTextEnabled: NSColor(0x000000), + chatReplyTextDisabled: NSColor(0x999999), + chatReplyTitleBubble_incoming: NSColor(0x2481cc), + chatReplyTitleBubble_outgoing: NSColor(0xffffff), + chatReplyTextEnabledBubble_incoming: NSColor(0x000000), + chatReplyTextEnabledBubble_outgoing: NSColor(0xffffff), + chatReplyTextDisabledBubble_incoming: NSColor(0x999999), + chatReplyTextDisabledBubble_outgoing: NSColor(0xEFFAFF, 0.8), + groupPeerNameRed:NSColor(0xfc5c51), + groupPeerNameOrange:NSColor(0xfa790f), + groupPeerNameViolet:NSColor(0x895dd5), + groupPeerNameGreen:NSColor(0x0fb297), + groupPeerNameCyan:NSColor(0x00c1a6), + groupPeerNameLightBlue:NSColor(0x3ca5ec), + groupPeerNameBlue:NSColor(0x3d72ed), + peerAvatarRedTop: NSColor(0xff885e), + peerAvatarRedBottom: NSColor(0xff516a), + peerAvatarOrangeTop: NSColor(0xffcd6a), + peerAvatarOrangeBottom: NSColor(0xffa85c), + peerAvatarVioletTop: NSColor(0x82b1ff), + peerAvatarVioletBottom: NSColor(0x665fff), + peerAvatarGreenTop: NSColor(0xa0de7e), + peerAvatarGreenBottom: NSColor(0x54cb68), + peerAvatarCyanTop: NSColor(0x53edd6), + peerAvatarCyanBottom: NSColor(0x28c9b7), + peerAvatarBlueTop: NSColor(0x72d5fd), + peerAvatarBlueBottom: NSColor(0x2a9ef1), + peerAvatarPinkTop: NSColor(0xe0a2f3), + peerAvatarPinkBottom: NSColor(0xd669ed), + bubbleBackgroundHighlight_incoming: NSColor(0xeaeaea), + bubbleBackgroundHighlight_outgoing: NSColor(0x4b7bad), + chatDateActive: NSColor(0xffffff, 1.0), + chatDateText: NSColor(0x333333), + revealAction_neutral1_background: NSColor(0x4892f2), + revealAction_neutral1_foreground: NSColor(0xffffff), + revealAction_neutral2_background: NSColor(0xf09a37), + revealAction_neutral2_foreground: NSColor(0xffffff), + revealAction_destructive_background: NSColor(0xff3824), + revealAction_destructive_foreground: NSColor(0xffffff), + revealAction_constructive_background: NSColor(0x00c900), + revealAction_constructive_foreground: NSColor(0xffffff), + revealAction_accent_background: NSColor(0x2481cc), + revealAction_accent_foreground: NSColor(0xffffff), + revealAction_warning_background: NSColor(0xff9500), + revealAction_warning_foreground: NSColor(0xffffff), + revealAction_inactive_background: NSColor(0xbcbcc3), + revealAction_inactive_foreground: NSColor(0xffffff), + chatBackground: NSColor(0xffffff), + listBackground: NSColor(0xefeff3), + listGrayText: NSColor(0x6D6D71), + grayHighlight: NSColor(0xF8F8F8), + focusAnimationColor: NSColor(0x68A8E2) +) + + + +/* + colors[0] = NSColor(0xfc5c51); // red + colors[1] = NSColor(0xfa790f); // orange + colors[2] = NSColor(0x895dd5); // violet + colors[3] = NSColor(0x0fb297); // green + colors[4] = NSColor(0x00c1a6); // cyan + colors[5] = NSColor(0x3ca5ec); // light blue + colors[6] = NSColor(0x3d72ed); // blue + */ + +public let nightAccentPalette = ColorPalette(isNative: true, isDark: true, + tinted: true, + name:"nightAccent", + parent: .nightAccent, + wallpaper: .none, + copyright: "Telegram", + accentList: [PaletteAccentColor(NSColor(0x2ea6ff)), + PaletteAccentColor(NSColor(0xf83b4c)), + PaletteAccentColor(NSColor(0xff7519)), + PaletteAccentColor(NSColor(0xeba239)), + PaletteAccentColor(NSColor(0x29b327)), + PaletteAccentColor(NSColor(0x00c2ed)), + PaletteAccentColor(NSColor(0x7748ff)), + PaletteAccentColor(NSColor(0xff5da2))], + basicAccent: NSColor(0x2ea6ff), + background: NSColor(0x18222d), + text: NSColor(0xffffff), + grayText: NSColor(0xb1c3d5), + link: NSColor(0x62bcf9), + accent: NSColor(0x2ea6ff), + redUI: NSColor(0xef5b5b), + greenUI: NSColor(0x49ad51), + blackTransparent: NSColor(0x000000, 0.6), + grayTransparent: NSColor(0x2f313d, 0.5), + grayUI: NSColor(0x18222d), + darkGrayText: NSColor(0xb1c3d5), + accentSelect: NSColor(0x3d6a97), + selectText: NSColor(0x3e6b9b), + border: NSColor(0x213040), + grayBackground: NSColor(0x213040), + grayForeground: NSColor(0x213040), + grayIcon: NSColor(0xb1c3d5), + accentIcon: NSColor(0x2ea6ff), + badgeMuted: NSColor(0xb1c3d5), + badge: NSColor(0x2ea6ff), + indicatorColor: NSColor(0xffffff), + selectMessage: NSColor(0x0F161E), + monospacedPre: NSColor(0x2ea6ff), + monospacedCode: NSColor(0x2ea6ff), + monospacedPreBubble_incoming: NSColor(0xffffff), + monospacedPreBubble_outgoing: NSColor(0xffffff), + monospacedCodeBubble_incoming: NSColor(0xffffff), + monospacedCodeBubble_outgoing: NSColor(0xffffff), + selectTextBubble_incoming: NSColor(0x3e6b9b), + selectTextBubble_outgoing: NSColor(0x355a80), + bubbleBackground_incoming: NSColor(0x213040), + bubbleBackgroundTop_outgoing: NSColor(0x3d6a97), + bubbleBackgroundBottom_outgoing: NSColor(0x3d6a97), + bubbleBorder_incoming: NSColor(0x213040), + bubbleBorder_outgoing: NSColor(0x3d6a97), + grayTextBubble_incoming: NSColor(0xb1c3d5), + grayTextBubble_outgoing: NSColor(0xEFFAFF, 0.8), + grayIconBubble_incoming: NSColor(0xb1c3d5), + grayIconBubble_outgoing: NSColor(0xb1c3d5), + accentIconBubble_incoming: NSColor(0xb1c3d5), + accentIconBubble_outgoing: NSColor(0xEFFAFF, 0.8), + linkBubble_incoming: NSColor(0x62bcf9), + linkBubble_outgoing: NSColor(0x62bcf9), + textBubble_incoming: NSColor(0xffffff), + textBubble_outgoing: NSColor(0xffffff), + selectMessageBubble: NSColor(0x213040), + fileActivityBackground: NSColor(0x2ea6ff, 0.8), + fileActivityForeground: NSColor(0xffffff), + fileActivityBackgroundBubble_incoming: NSColor(0xb1c3d5), + fileActivityBackgroundBubble_outgoing: NSColor(0xffffff), + fileActivityForegroundBubble_incoming: NSColor(0x213040), + fileActivityForegroundBubble_outgoing: NSColor(0x3d6a97), + waveformBackground: NSColor(0xb1c3d5, 0.7), + waveformForeground: NSColor(0x2ea6ff, 0.8), + waveformBackgroundBubble_incoming: NSColor(0xb1c3d5), + waveformBackgroundBubble_outgoing: NSColor(0xb1c3d5), + waveformForegroundBubble_incoming: NSColor(0xffffff), + waveformForegroundBubble_outgoing: NSColor(0xffffff), + webPreviewActivity: NSColor(0x62bcf9), + webPreviewActivityBubble_incoming: NSColor(0xffffff), + webPreviewActivityBubble_outgoing: NSColor(0xffffff), + redBubble_incoming: NSColor(0xef5b5b), + redBubble_outgoing: NSColor(0xef5b5b), + greenBubble_incoming: NSColor(0x49ad51), + greenBubble_outgoing: NSColor(0x49ad51), + chatReplyTitle: NSColor(0x62bcf9), + chatReplyTextEnabled: NSColor(0xffffff), + chatReplyTextDisabled: NSColor(0xb1c3d5), + chatReplyTitleBubble_incoming: NSColor(0xffffff), + chatReplyTitleBubble_outgoing: NSColor(0xffffff), + chatReplyTextEnabledBubble_incoming: NSColor(0xffffff), + chatReplyTextEnabledBubble_outgoing: NSColor(0xffffff), + chatReplyTextDisabledBubble_incoming: NSColor(0xb1c3d5), + chatReplyTextDisabledBubble_outgoing: NSColor(0xb1c3d5), + groupPeerNameRed: NSColor(0xff8e86), + groupPeerNameOrange: NSColor(0xffa357), + groupPeerNameViolet: NSColor(0xbf9aff), + groupPeerNameGreen: NSColor(0x4dd6bf), + groupPeerNameCyan: NSColor(0x45e8d1), + groupPeerNameLightBlue: NSColor(0x7ac9ff), + groupPeerNameBlue: NSColor(0x7aa2ff), + peerAvatarRedTop: NSColor(0xffac8e), + peerAvatarRedBottom: NSColor(0xff8597), + peerAvatarOrangeTop: NSColor(0xffdc97), + peerAvatarOrangeBottom: NSColor(0xffc28d), + peerAvatarVioletTop: NSColor(0xa8c8ff), + peerAvatarVioletBottom: NSColor(0x948fff), + peerAvatarGreenTop: NSColor(0xcdffb2), + peerAvatarGreenBottom: NSColor(0x90f4a0), + peerAvatarCyanTop: NSColor(0x8bffee), + peerAvatarCyanBottom: NSColor(0x6af1e2), + peerAvatarBlueTop: NSColor(0x9de3ff), + peerAvatarBlueBottom: NSColor(0x6cc2ff), + peerAvatarPinkTop: NSColor(0xf1c4ff), + peerAvatarPinkBottom: NSColor(0xee9cff), + bubbleBackgroundHighlight_incoming: NSColor(0x2D3A49), + bubbleBackgroundHighlight_outgoing: NSColor(0x5079A1), + chatDateActive: NSColor(0x18222d), + chatDateText: NSColor(0xb1c3d5), + revealAction_neutral1_background: NSColor(0x007cd6), + revealAction_neutral1_foreground: NSColor(0xffffff), + revealAction_neutral2_background: NSColor(0xcd7800), + revealAction_neutral2_foreground: NSColor(0xffffff), + revealAction_destructive_background: NSColor(0xc70c0c), + revealAction_destructive_foreground: NSColor(0xffffff), + revealAction_constructive_background: NSColor(0x08a723), + revealAction_constructive_foreground: NSColor(0xffffff), + revealAction_accent_background: NSColor(0x007cd6), + revealAction_accent_foreground: NSColor(0xffffff), + revealAction_warning_background: NSColor(0xcd7800), + revealAction_warning_foreground: NSColor(0xffffff), + revealAction_inactive_background: NSColor(0x26384c), + revealAction_inactive_foreground: NSColor(0xffffff), + chatBackground: NSColor(0x18222d), + listBackground: NSColor(0x131415), + listGrayText: NSColor(0xb1c3d5), + grayHighlight: NSColor(0x18222d).darker(amount: 0.08), + focusAnimationColor: NSColor(0x68A8E2) +) +public let dayClassicPalette = ColorPalette(isNative: true, + isDark: false, + tinted: false, + name:"dayClassic", + parent: .dayClassic, + wallpaper: .builtin, + copyright: "Telegram", + accentList: [PaletteAccentColor(NSColor(0x2481cc), (top: NSColor(0xdcf8c6), bottom: NSColor(0xdcf8c6))), + PaletteAccentColor(NSColor(0x5a9e29), (top: NSColor(0xdcf8c6), bottom: NSColor(0xdcf8c6))), + PaletteAccentColor(NSColor(0xf55783), (top: NSColor(0xd6f5ff), bottom: NSColor(0xd6f5ff))), + PaletteAccentColor(NSColor(0x7e5fe5), (top: NSColor(0xf5e2ff), bottom: NSColor(0xf5e2ff))), + PaletteAccentColor(NSColor(0xff5fa9), (top: NSColor(0xfff4d7), bottom: NSColor(0xfff4d7))), + PaletteAccentColor(NSColor(0x199972), (top: NSColor(0xfffec7), bottom: NSColor(0xfffec7))), + PaletteAccentColor(NSColor(0x009eee), (top: NSColor(0x94fff9), bottom: NSColor(0x94fff9)))], + basicAccent: NSColor(0x2481cc), + background: NSColor(0xffffff), + text: NSColor(0x000000), + grayText: NSColor(0x999999), + link: NSColor(0x2481cc), + accent: NSColor(0x2481cc), + redUI: NSColor(0xff3b30), + greenUI: NSColor(0x63DA6E), + blackTransparent: NSColor(0x000000,0.6), + grayTransparent: NSColor(0xf4f4f4,0.4), + grayUI: NSColor(0xFaFaFa), + darkGrayText: NSColor(0x333333), + accentSelect: NSColor(0x4c91c7), + selectText: NSColor(0xeaeaea), + border: NSColor(0xeaeaea), + grayBackground: NSColor(0xf4f4f4), + grayForeground: NSColor(0xe4e4e4), + grayIcon: NSColor(0x9e9e9e), + accentIcon: NSColor(0x2481cc), + badgeMuted: NSColor(0xd7d7d7), + badge: NSColor(0x4ba3e2), + indicatorColor: NSColor(0x464a57), + selectMessage: NSColor(0xeaeaea), + monospacedPre: NSColor(0x000000), + monospacedCode: NSColor(0xff3b30), + monospacedPreBubble_incoming: NSColor(0xff3b30), + monospacedPreBubble_outgoing: NSColor(0x000000), + monospacedCodeBubble_incoming: NSColor(0xff3b30), + monospacedCodeBubble_outgoing: NSColor(0x000000), + selectTextBubble_incoming: NSColor(0xCCDDEA), + selectTextBubble_outgoing: NSColor(0xCCDDEA), + bubbleBackground_incoming: NSColor(0xffffff), + bubbleBackgroundTop_outgoing: NSColor(0xE1FFC7), + bubbleBackgroundBottom_outgoing: NSColor(0xE1FFC7), + bubbleBorder_incoming: NSColor(0x86A9C9,0.5), + bubbleBorder_outgoing: NSColor(0x86A9C9,0.5), + grayTextBubble_incoming: NSColor(0x999999), + grayTextBubble_outgoing: NSColor(0x008c09,0.8), + grayIconBubble_incoming: NSColor(0x999999), + grayIconBubble_outgoing: NSColor(0x008c09,0.8), + accentIconBubble_incoming: NSColor(0x999999), + accentIconBubble_outgoing: NSColor(0x008c09,0.8), + linkBubble_incoming: NSColor(0x2481cc), + linkBubble_outgoing: NSColor(0x004bad), + textBubble_incoming: NSColor(0x000000), + textBubble_outgoing: NSColor(0x000000), + selectMessageBubble: NSColor(0xEDF4F9), + fileActivityBackground: NSColor(0x4ba3e2), + fileActivityForeground: NSColor(0xffffff), + fileActivityBackgroundBubble_incoming: NSColor(0x3ca7fe), + fileActivityBackgroundBubble_outgoing: NSColor(0x00a700), + fileActivityForegroundBubble_incoming: NSColor(0xffffff), + fileActivityForegroundBubble_outgoing: NSColor(0xffffff), + waveformBackground: NSColor(0x9e9e9e,0.7), + waveformForeground: NSColor(0x4ba3e2), + waveformBackgroundBubble_incoming: NSColor(0x3ca7fe,0.6), + waveformBackgroundBubble_outgoing: NSColor(0x00a700,0.6), + waveformForegroundBubble_incoming: NSColor(0x3ca7fe), + waveformForegroundBubble_outgoing: NSColor(0x00a700), + webPreviewActivity: NSColor(0x2481cc), + webPreviewActivityBubble_incoming: NSColor(0x2481cc), + webPreviewActivityBubble_outgoing: NSColor(0x00a700), + redBubble_incoming: NSColor(0xff3b30), + redBubble_outgoing: NSColor(0xff3b30), + greenBubble_incoming: NSColor(0x63DA6E), + greenBubble_outgoing: NSColor(0x63DA6E), + chatReplyTitle: NSColor(0x2481cc), + chatReplyTextEnabled: NSColor(0x000000), + chatReplyTextDisabled: NSColor(0x999999), + chatReplyTitleBubble_incoming: NSColor(0x2481cc), + chatReplyTitleBubble_outgoing: NSColor(0x00a700), + chatReplyTextEnabledBubble_incoming: NSColor(0x000000), + chatReplyTextEnabledBubble_outgoing: NSColor(0x000000), + chatReplyTextDisabledBubble_incoming: NSColor(0x999999), + chatReplyTextDisabledBubble_outgoing: NSColor(0x008c09,0.8), + groupPeerNameRed: NSColor(0xfc5c51), + groupPeerNameOrange: NSColor(0xfa790f), + groupPeerNameViolet: NSColor(0x895dd5), + groupPeerNameGreen: NSColor(0x0fb297), + groupPeerNameCyan: NSColor(0x00c1a6), + groupPeerNameLightBlue: NSColor(0x3ca5ec), + groupPeerNameBlue: NSColor(0x3d72ed), + peerAvatarRedTop: NSColor(0xff885e), + peerAvatarRedBottom: NSColor(0xff516a), + peerAvatarOrangeTop: NSColor(0xffcd6a), + peerAvatarOrangeBottom: NSColor(0xffa85c), + peerAvatarVioletTop: NSColor(0x82b1ff), + peerAvatarVioletBottom: NSColor(0x665fff), + peerAvatarGreenTop: NSColor(0xa0de7e), + peerAvatarGreenBottom: NSColor(0x54cb68), + peerAvatarCyanTop: NSColor(0x53edd6), + peerAvatarCyanBottom: NSColor(0x28c9b7), + peerAvatarBlueTop: NSColor(0x72d5fd), + peerAvatarBlueBottom: NSColor(0x2a9ef1), + peerAvatarPinkTop: NSColor(0xe0a2f3), + peerAvatarPinkBottom: NSColor(0xd669ed), + bubbleBackgroundHighlight_incoming: NSColor(0xd9f4ff), + bubbleBackgroundHighlight_outgoing: NSColor(0xc8ffa6), + chatDateActive: NSColor(0xffffff, 1.0), + chatDateText: NSColor(0x999999), + revealAction_neutral1_background: NSColor(0x4892f2), + revealAction_neutral1_foreground: NSColor(0xffffff), + revealAction_neutral2_background: NSColor(0xf09a37), + revealAction_neutral2_foreground: NSColor(0xffffff), + revealAction_destructive_background: NSColor(0xff3824), + revealAction_destructive_foreground: NSColor(0xffffff), + revealAction_constructive_background: NSColor(0x00c900), + revealAction_constructive_foreground: NSColor(0xffffff), + revealAction_accent_background: NSColor(0x2481cc), + revealAction_accent_foreground: NSColor(0xffffff), + revealAction_warning_background: NSColor(0xff9500), + revealAction_warning_foreground: NSColor(0xffffff), + revealAction_inactive_background: NSColor(0xbcbcc3), + revealAction_inactive_foreground: NSColor(0xffffff), + chatBackground: NSColor(0xffffff), + listBackground: NSColor(0xefeff3), + listGrayText: NSColor(0x6D6D71), + grayHighlight: NSColor(0xF8F8F8), + focusAnimationColor: NSColor(0x68A8E2) +) + +public let darkPalette = ColorPalette(isNative: true, isDark:true, + tinted: false, + name:"Dark", + parent: .dark, + wallpaper: .none, + copyright: "Telegram", + accentList: [PaletteAccentColor(NSColor(0x04afc8)), + PaletteAccentColor(NSColor(0xf83b4c)), + PaletteAccentColor(NSColor(0xff7519)), + PaletteAccentColor(NSColor(0xeba239)), + PaletteAccentColor(NSColor(0x29b327)), + PaletteAccentColor(NSColor(0x00c2ed)), + PaletteAccentColor(NSColor(0x7748ff)), + PaletteAccentColor(NSColor(0xff5da2))], + basicAccent: NSColor(0x04afc8), + background: NSColor(0x292b36), + text: NSColor(0xe9e9e9), + grayText: NSColor(0x8699a3), + link: NSColor(0x04afc8), + accent: NSColor(0x04afc8), + redUI: NSColor(0xec6657), + greenUI : NSColor(0x49ad51), + blackTransparent: NSColor(0x000000, 0.6), + grayTransparent: NSColor(0x2f313d, 0.5), + grayUI: NSColor(0x292b36), + darkGrayText : NSColor(0x8699a3), + accentSelect: NSColor(0x20889a), + selectText: NSColor(0x8699a3), + border: NSColor(0x464a57), + grayBackground : NSColor(0x464a57), + grayForeground : NSColor(0x3d414d), + grayIcon: NSColor(0x8699a3), + accentIcon: NSColor(0x04afc8), + badgeMuted: NSColor(0x8699a3), + badge: NSColor(0x04afc8), + indicatorColor: NSColor(0xffffff), + selectMessage: NSColor(0x3d414d), + monospacedPre: NSColor(0xffffff), + monospacedCode: NSColor(0xff3b30), + monospacedPreBubble_incoming: NSColor(0xffffff), + monospacedPreBubble_outgoing: NSColor(0xffffff), + monospacedCodeBubble_incoming: NSColor(0xec6657), + monospacedCodeBubble_outgoing: NSColor(0xffffff), + selectTextBubble_incoming: NSColor(0x8699a3), + selectTextBubble_outgoing: NSColor(0x8699a3), + bubbleBackground_incoming: NSColor(0x3d414d), + bubbleBackgroundTop_outgoing: NSColor(0x20889a), + bubbleBackgroundBottom_outgoing: NSColor(0x20889a), + bubbleBorder_incoming: NSColor(0x464a57), + bubbleBorder_outgoing: NSColor(0x20889a), + grayTextBubble_incoming: NSColor(0x8699a3), + grayTextBubble_outgoing: NSColor(0xa0d5dd), + grayIconBubble_incoming: NSColor(0x8699a3), + grayIconBubble_outgoing: NSColor(0xa0d5dd), + accentIconBubble_incoming: NSColor(0x8699a3), + accentIconBubble_outgoing: NSColor(0xa0d5dd), + linkBubble_incoming: NSColor(0x04afc8), + linkBubble_outgoing: NSColor(0xffffff), + textBubble_incoming: NSColor(0xe9e9e9), + textBubble_outgoing: NSColor(0xffffff), + selectMessageBubble: NSColor(0x3d414d), + fileActivityBackground: NSColor(0x04afc8), + fileActivityForeground: NSColor(0xffffff), + fileActivityBackgroundBubble_incoming: NSColor(0x04afc8), + fileActivityBackgroundBubble_outgoing: NSColor(0xffffff), + fileActivityForegroundBubble_incoming: NSColor(0xffffff), + fileActivityForegroundBubble_outgoing: NSColor(0x20889a), + waveformBackground: NSColor(0x8699a3, 0.7), + waveformForeground: NSColor(0x04afc8), + waveformBackgroundBubble_incoming: NSColor(0x8699a3), + waveformBackgroundBubble_outgoing: NSColor(0xa0d5dd), + waveformForegroundBubble_incoming: NSColor(0x04afc8), + waveformForegroundBubble_outgoing: NSColor(0xffffff), + webPreviewActivity : NSColor(0x04afc8), + webPreviewActivityBubble_incoming : NSColor(0x04afc8), + webPreviewActivityBubble_outgoing : NSColor(0xffffff), + redBubble_incoming : NSColor(0xec6657), + redBubble_outgoing : NSColor(0xec6657), + greenBubble_incoming : NSColor(0x49ad51), + greenBubble_outgoing : NSColor(0x49ad51), + chatReplyTitle: NSColor(0x04afc8), + chatReplyTextEnabled: NSColor(0xe9e9e9), + chatReplyTextDisabled: NSColor(0x8699a3), + chatReplyTitleBubble_incoming: NSColor(0xffffff), + chatReplyTitleBubble_outgoing: NSColor(0xffffff), + chatReplyTextEnabledBubble_incoming: NSColor(0xffffff), + chatReplyTextEnabledBubble_outgoing: NSColor(0xffffff), + chatReplyTextDisabledBubble_incoming: NSColor(0x8699a3), + chatReplyTextDisabledBubble_outgoing: NSColor(0xa0d5dd), + groupPeerNameRed : NSColor(0xfc5c51), + groupPeerNameOrange : NSColor(0xfa790f), + groupPeerNameViolet : NSColor(0x895dd5), + groupPeerNameGreen : NSColor(0x0fb297), + groupPeerNameCyan : NSColor(0x00c1a6), + groupPeerNameLightBlue : NSColor(0x3ca5ec), + groupPeerNameBlue : NSColor(0x3d72ed), + peerAvatarRedTop: NSColor(0xff885e), + peerAvatarRedBottom: NSColor(0xff516a), + peerAvatarOrangeTop : NSColor(0xffcd6a), + peerAvatarOrangeBottom : NSColor(0xffa85c), + peerAvatarVioletTop : NSColor(0x82b1ff), + peerAvatarVioletBottom : NSColor(0x665fff), + peerAvatarGreenTop : NSColor(0xa0de7e), + peerAvatarGreenBottom : NSColor(0x54cb68), + peerAvatarCyanTop : NSColor(0x53edd6), + peerAvatarCyanBottom : NSColor(0x28c9b7), + peerAvatarBlueTop : NSColor(0x72d5fd), + peerAvatarBlueBottom : NSColor(0x2a9ef1), + peerAvatarPinkTop : NSColor(0xe0a2f3), + peerAvatarPinkBottom : NSColor(0xd669ed), + bubbleBackgroundHighlight_incoming : NSColor(0x525768), + bubbleBackgroundHighlight_outgoing : NSColor(0x387080), + chatDateActive : NSColor(0x292b36), + chatDateText : NSColor(0x8699a3), + revealAction_neutral1_background: NSColor(0x666666), + revealAction_neutral1_foreground: NSColor(0xffffff), + revealAction_neutral2_background: NSColor(0xcd7800), + revealAction_neutral2_foreground: NSColor(0xffffff), + revealAction_destructive_background: NSColor(0xc70c0c), + revealAction_destructive_foreground: NSColor(0xffffff), + revealAction_constructive_background: NSColor(0x08a723), + revealAction_constructive_foreground: NSColor(0xffffff), + revealAction_accent_background: NSColor(0x666666), + revealAction_accent_foreground: NSColor(0xffffff), + revealAction_warning_background: NSColor(0xcd7800), + revealAction_warning_foreground: NSColor(0xffffff), + revealAction_inactive_background: NSColor(0x666666), + revealAction_inactive_foreground: NSColor(0xffffff), + chatBackground: NSColor(0x292b36), + listBackground: NSColor(0x131415), + listGrayText: NSColor(0x8699a3), + grayHighlight: NSColor(0x292b36).darker(amount: 0.08), + focusAnimationColor: NSColor(0x68A8E2) +) + +@available(macOS 10.14, *) +private final class MojavePalette : ColorPalette { + + private var underPageBackgroundColor: NSColor { + return NSColor(0x282828) + } + private var windowBackgroundColor: NSColor { + return NSColor(0x323232) + } + private var controlAccentColor: NSColor { + return NSColor.controlAccentColor.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + private var selectedTextBackgroundColor: NSColor { + return controlAccentColor.darker() + } + private var secondaryLabelColor: NSColor { + return NSColor(0xffffff, 0.55) + } + private var systemRed: NSColor { + return NSColor(0xFF453A) + } + private var systemGreen: NSColor { + return NSColor(0x32D74B) + } + private var controlBackgroundColor: NSColor { + return NSColor(0x1E1E1E) + } + private var separatorColor: NSColor { + return NSColor(0x3d3d3d) + } + private var textColor: NSColor { + return NSColor(0xffffff, 0.85) + } + private var disabledControlTextColor: NSColor { + return NSColor(0xffffff, 0.25) + } + private var unemphasizedSelectedTextBackgroundColor: NSColor { + return NSColor(0x464646) + } + + override var background: NSColor { + return underPageBackgroundColor + } + override var grayBackground: NSColor { + return windowBackgroundColor + } + override var link: NSColor { + return controlAccentColor + } + override var selectText: NSColor { + return selectedTextBackgroundColor + } + override var accent: NSColor { + return controlAccentColor + } + override var accentIcon: NSColor { + return controlAccentColor + } + override var grayIcon: NSColor { + return secondaryLabelColor + } + override var redUI: NSColor { + return systemRed + } + override var greenUI: NSColor { + return systemGreen + } + override var text: NSColor { + return textColor + } + override var grayText: NSColor { + return secondaryLabelColor + } + override var listGrayText: NSColor { + return secondaryLabelColor.darker(amount: 0.1) + } + override var listBackground: NSColor { + return controlBackgroundColor.darker(amount: 0.05) + } + override var chatBackground: NSColor { + return controlBackgroundColor + } + override var border: NSColor { + return separatorColor + } + override var accentSelect: NSColor { + return controlAccentColor.darker(amount: 0.2) + } + override var webPreviewActivity: NSColor { + return controlAccentColor + } + override var fileActivityBackground: NSColor { + return controlAccentColor + } + override var fileActivityForeground: NSColor { + return underSelectedColor + } + override var waveformForeground: NSColor { + return controlAccentColor + } + override var chatReplyTitle: NSColor { + return controlAccentColor + } + override var chatReplyTextEnabled: NSColor { + return textColor + } + override var chatReplyTextDisabled: NSColor { + return disabledControlTextColor + } + override var chatReplyTextDisabledBubble_outgoing: NSColor { + return NSColor.white.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var groupPeerNameRed: NSColor { + return NSColor.systemRed.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var groupPeerNameBlue: NSColor { + return NSColor.systemBlue.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var groupPeerNameGreen: NSColor { + return NSColor.systemGreen.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var groupPeerNameOrange: NSColor { + return NSColor.systemOrange.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var bubbleBackgroundTop_outgoing: NSColor { + return controlAccentColor.darker(amount: 0.2) + } + override var bubbleBackgroundBottom_outgoing: NSColor { + return controlAccentColor.darker(amount: 0.2) + } + override var bubbleBorder_outgoing: NSColor { + return controlAccentColor.darker(amount: 0.2) + } + override var bubbleBackground_incoming: NSColor { + return windowBackgroundColor + } + override var bubbleBackgroundHighlight_incoming: NSColor { + return windowBackgroundColor.lighter(amount: 0.2) + } + override var bubbleBackgroundHighlight_outgoing: NSColor { + return controlAccentColor + } + override var linkBubble_outgoing: NSColor { + return NSColor.white.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + } + override var selectTextBubble_incoming: NSColor { + return selectedTextBackgroundColor + } + override var selectTextBubble_outgoing: NSColor { + return unemphasizedSelectedTextBackgroundColor + } + override var linkBubble_incoming: NSColor { + return controlAccentColor + } + override var waveformForegroundBubble_outgoing: NSColor { + return grayIconBubble_outgoing + } + override var fileActivityForegroundBubble_incoming: NSColor { + return windowBackgroundColor + } + override var fileActivityForegroundBubble_outgoing: NSColor { + return controlAccentColor + } +} + +public let systemPalette: ColorPalette = { + + let initializer: ColorPalette.Type + if #available(macOS 10.14, *) { + initializer = MojavePalette.self + } else { + initializer = ColorPalette.self + } + let palette = initializer.init(isNative: true, isDark: true, + tinted: false, + name: "system", + parent: .system, + wallpaper: .none, + copyright: "Telegram", + accentList: [], + basicAccent: NSColor(0x2ea6ff), + background: NSColor(0x292a2f), + text: NSColor(0xffffff), + grayText: NSColor(0xb1c3d5), + link: NSColor(0x2ea6ff), + accent: NSColor(0x2ea6ff), + redUI: NSColor(0xef5b5b), + greenUI: NSColor(0x49ad51), + blackTransparent: NSColor(0x000000, 0.6), + grayTransparent: NSColor(0x3e464c, 0.5), + grayUI: NSColor(0x292A2F), + darkGrayText: NSColor(0xb1c3d5), + accentSelect: NSColor(0x3d6a97), + selectText: NSColor(0x3e6b9b), + border: NSColor(0x3d474f), + grayBackground: NSColor(0x3e464c), + grayForeground: NSColor(0x3e464c), + grayIcon: NSColor(0xb1c3d5), + accentIcon: NSColor(0x2ea6ff), + badgeMuted: NSColor(0xb1c3d5), + badge: NSColor(0x2ea6ff), + indicatorColor: NSColor(0xffffff), + selectMessage: NSColor(0x42444a), + monospacedPre: NSColor(0xffffff), + monospacedCode: NSColor(0xffffff), + monospacedPreBubble_incoming: NSColor(0xffffff), + monospacedPreBubble_outgoing: NSColor(0xffffff), + monospacedCodeBubble_incoming: NSColor(0xffffff), + monospacedCodeBubble_outgoing: NSColor(0xffffff), + selectTextBubble_incoming: NSColor(0x3e6b9b), + selectTextBubble_outgoing: NSColor(0x355a80), + bubbleBackground_incoming: NSColor(0x4e5058), + bubbleBackgroundTop_outgoing: NSColor(0x3d6a97), + bubbleBackgroundBottom_outgoing: NSColor(0x3d6a97), + bubbleBorder_incoming: NSColor(0x4e5058), + bubbleBorder_outgoing: NSColor(0x3d6a97), + grayTextBubble_incoming: NSColor(0xb1c3d5), + grayTextBubble_outgoing: NSColor(0xEFFAFF, 0.8), + grayIconBubble_incoming: NSColor(0xb1c3d5), + grayIconBubble_outgoing: NSColor(0xb1c3d5), + accentIconBubble_incoming: NSColor(0xb1c3d5), + accentIconBubble_outgoing: NSColor(0xEFFAFF, 0.8), + linkBubble_incoming: NSColor(0x2ea6ff), + linkBubble_outgoing: NSColor(0x2ea6ff), + textBubble_incoming: NSColor(0xffffff), + textBubble_outgoing: NSColor(0xffffff), + selectMessageBubble: NSColor(0x4e5058), + fileActivityBackground: NSColor(0x2ea6ff, 0.8), + fileActivityForeground: NSColor(0xffffff), + fileActivityBackgroundBubble_incoming: NSColor(0xb1c3d5), + fileActivityBackgroundBubble_outgoing: NSColor(0xffffff), + fileActivityForegroundBubble_incoming: NSColor(0x4e5058), + fileActivityForegroundBubble_outgoing: NSColor(0x3d6a97), + waveformBackground: NSColor(0xb1c3d5, 0.7), + waveformForeground: NSColor(0x2ea6ff, 0.8), + waveformBackgroundBubble_incoming: NSColor(0xb1c3d5), + waveformBackgroundBubble_outgoing: NSColor(0xb1c3d5), + waveformForegroundBubble_incoming: NSColor(0xffffff), + waveformForegroundBubble_outgoing: NSColor(0xffffff), + webPreviewActivity: NSColor(0x2ea6ff), + webPreviewActivityBubble_incoming: NSColor(0xffffff), + webPreviewActivityBubble_outgoing: NSColor(0xffffff), + redBubble_incoming: NSColor(0xef5b5b), + redBubble_outgoing: NSColor(0xef5b5b), + greenBubble_incoming: NSColor(0x49ad51), + greenBubble_outgoing: NSColor(0x49ad51), + chatReplyTitle: NSColor(0x2ea6ff), + chatReplyTextEnabled: NSColor(0xffffff), + chatReplyTextDisabled: NSColor(0xb1c3d5), + chatReplyTitleBubble_incoming: NSColor(0xffffff), + chatReplyTitleBubble_outgoing: NSColor(0xffffff), + chatReplyTextEnabledBubble_incoming: NSColor(0xffffff), + chatReplyTextEnabledBubble_outgoing: NSColor(0xffffff), + chatReplyTextDisabledBubble_incoming: NSColor(0xb1c3d5), + chatReplyTextDisabledBubble_outgoing: NSColor(0xb1c3d5), + groupPeerNameRed: NSColor(0xff8e86), + groupPeerNameOrange: NSColor(0xffa357), + groupPeerNameViolet: NSColor(0xbf9aff), + groupPeerNameGreen: NSColor(0x9ccfc3), + groupPeerNameCyan: NSColor(0x45e8d1), + groupPeerNameLightBlue: NSColor(0x7ac9ff), + groupPeerNameBlue: NSColor(0x7aa2ff), + peerAvatarRedTop: NSColor(0xffac8e), + peerAvatarRedBottom: NSColor(0xff8597), + peerAvatarOrangeTop: NSColor(0xffdc97), + peerAvatarOrangeBottom: NSColor(0xffc28d), + peerAvatarVioletTop: NSColor(0xa8c8ff), + peerAvatarVioletBottom: NSColor(0x948fff), + peerAvatarGreenTop: NSColor(0xcdffb2), + peerAvatarGreenBottom: NSColor(0x90f4a0), + peerAvatarCyanTop: NSColor(0x8bffee), + peerAvatarCyanBottom: NSColor(0x6af1e2), + peerAvatarBlueTop: NSColor(0x9de3ff), + peerAvatarBlueBottom: NSColor(0x6cc2ff), + peerAvatarPinkTop: NSColor(0xf1c4ff), + peerAvatarPinkBottom: NSColor(0xee9cff), + bubbleBackgroundHighlight_incoming: NSColor(0x42444a), + bubbleBackgroundHighlight_outgoing: NSColor(0x5079a1), + chatDateActive: NSColor(0x292A2F), + chatDateText: NSColor(0xb1c3d5), + revealAction_neutral1_background: NSColor(0x666666), + revealAction_neutral1_foreground: NSColor(0xffffff), + revealAction_neutral2_background: NSColor(0xcd7800), + revealAction_neutral2_foreground: NSColor(0xffffff), + revealAction_destructive_background: NSColor(0xc70c0c), + revealAction_destructive_foreground: NSColor(0xffffff), + revealAction_constructive_background: NSColor(0x08a723), + revealAction_constructive_foreground: NSColor(0xffffff), + revealAction_accent_background: NSColor(0x666666), + revealAction_accent_foreground: NSColor(0xffffff), + revealAction_warning_background: NSColor(0xcd7800), + revealAction_warning_foreground: NSColor(0xffffff), + revealAction_inactive_background: NSColor(0x666666), + revealAction_inactive_foreground: NSColor(0xffffff), + chatBackground: NSColor(0x292a2f), + listBackground: NSColor(0x131415), + listGrayText: NSColor(0xb1c3d5), + grayHighlight: NSColor(0x292a2f).darker(amount: 0.08), + focusAnimationColor: NSColor(0x68A8E2) + ) + + + + return palette +}() + + +/* + public let darkPalette = ColorPalette(background: NSColor(0x282e33), text: NSColor(0xe9e9e9), grayText: NSColor(0x999999), link: NSColor(0x20eeda), accent: NSColor(0x20eeda), redUI: NSColor(0xec6657), greenUI:NSColor(0x63DA6E), blackTransparent: NSColor(0x000000, 0.6), grayTransparent: NSColor(0xf4f4f4, 0.4), grayUI: NSColor(0xFaFaFa), darkGrayText:NSColor(0x333333), blueText:NSColor(0x009687), accentSelect:NSColor(0x009687), selectText:NSColor(0xeaeaea), blueFill: NSColor(0x20eeda), border: NSColor(0x3d444b), grayBackground:NSColor(0x3d444b), grayForeground:NSColor(0xe4e4e4), grayIcon:NSColor(0x757676), accentIcon: NSColor(0x20eeda), badgeMuted:NSColor(0xd7d7d7), badge:NSColor(0x4ba3e2), indicatorColor: NSColor(0xffffff)) + */ + + +private var _theme:Atomic = Atomic(value: whiteTheme) + +public let whiteTheme = PresentationTheme(colors: whitePalette, search: SearchTheme(.grayBackground, #imageLiteral(resourceName: "Icon_SearchField").precomposed(), #imageLiteral(resourceName: "Icon_SearchClear").precomposed(), {localizedString("SearchField.Search")}, .text, .grayText)) + + + +public var presentation:PresentationTheme { + return _theme.modify {$0} +} + +public func updateTheme(_ theme:PresentationTheme) { + assertOnMainThread() + _ = _theme.swap(theme) +} + + +public extension ColorPalette { + var appearance: NSAppearance { + switch parent { + case .system: + if #available(macOS 10.14, *) { + return NSAppearance(named: NSAppearance.Name.darkAqua)! + } else { + return NSAppearance(named: NSAppearance.Name.vibrantDark)! + } + default: + if #available(macOS 10.14, *) { + return isDark ? NSAppearance(named: NSAppearance.Name.darkAqua)! : NSAppearance(named: NSAppearance.Name.aqua)! + } else { + return isDark ? NSAppearance(named: NSAppearance.Name.vibrantDark)! : NSAppearance(named: NSAppearance.Name.aqua)! + } + } + } +} +public extension PresentationTheme { + var appearance: NSAppearance { + return colors.appearance + } +} diff --git a/submodules/TGUIKit/TGUIKit/ProgressIndicator.swift b/submodules/TGUIKit/TGUIKit/ProgressIndicator.swift new file mode 100644 index 0000000000..0e4922dc4d --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ProgressIndicator.swift @@ -0,0 +1,136 @@ +// +// ProgressIndicator.swift +// TGUIKit +// +// Created by keepcoder on 06/07/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa + + +private class ProgressLayer : CALayer { + fileprivate var progressColor: NSColor? = nil + fileprivate func update(_ hasAnimation: Bool) { + if hasAnimation { + var fromValue: Float = 0 + + if let layer = presentation(), let from = layer.value(forKeyPath: "transform.rotation.z") as? Float { + fromValue = from + } + let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + basicAnimation.duration = 0.8 + basicAnimation.fromValue = fromValue + basicAnimation.isRemovedOnCompletion = false + basicAnimation.toValue = Double.pi * 2.0 + basicAnimation.repeatCount = Float.infinity + basicAnimation.timingFunction = CAMediaTimingFunction(name: .linear) + add(basicAnimation, forKey: "progressRotation") + } else { + removeAnimation(forKey: "progressRotation") + } + } + + fileprivate var lineWidth: CGFloat = 2 { + didSet { + setNeedsDisplay() + } + } + + override func draw(in ctx: CGContext) { + + ctx.setStrokeColor((progressColor ?? PresentationTheme.current.colors.indicatorColor).cgColor) + + let startAngle = 2.0 * (CGFloat.pi) * 0.8 - CGFloat.pi / 2 + let endAngle = -(CGFloat.pi / 2) + + let diameter = floorToScreenPixels(System.backingScale, frame.height) + + let pathDiameter = diameter - lineWidth - lineWidth * 2 + ctx.addArc(center: NSMakePoint(diameter / 2.0, floorToScreenPixels(System.backingScale, diameter / 2.0)), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: true) + + ctx.setLineWidth(lineWidth); + ctx.setLineCap(.round); + ctx.strokePath() + } +} + +public class ProgressIndicator : Control { + + public var alwaysAnimate: Bool = false + + public var progressColor: NSColor? = nil { + didSet { + indicator.color = progressColor ?? presentation.colors.text + indicator.setNeedsDisplay() + } + } + public var innerInset: CGFloat = 0 { + didSet { + needsLayout = true + // indicator.lineWidth = lineWidth + } + } + private let indicator: SpinningProgressView = SpinningProgressView(frame: NSZeroRect) + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + indicator.frame = bounds + self.addSubview(indicator) + } + + public override func setFrameSize(_ newSize: NSSize) { + let prev = self.frame + super.setFrameSize(newSize) + if prev != self.frame { + updateWantsAnimation() + } + } + + + public override func layout() { + super.layout() + indicator.setFrameSize(NSMakeSize(frame.width - innerInset, frame.height - innerInset)) + indicator.center() + } + + public override init() { + super.init(frame: NSMakeRect(0, 0, 20, 20)) + indicator.frame = bounds + self.addSubview(indicator) + } + + public override func viewDidMoveToSuperview() { + updateWantsAnimation() + } + + public override func viewDidMoveToWindow() { + updateWantsAnimation() + } + + public override func viewDidHide() { + updateWantsAnimation() + } + + public override func viewDidUnhide() { + updateWantsAnimation() + } + + private func updateWantsAnimation() { + if (window != nil && !isHidden) || alwaysAnimate { + indicator.startAnimation() + } else { + indicator.stopAnimation() + } + } + + + override public func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + diff --git a/submodules/TGUIKit/TGUIKit/ProgressModal.swift b/submodules/TGUIKit/TGUIKit/ProgressModal.swift new file mode 100644 index 0000000000..231f8cdad5 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ProgressModal.swift @@ -0,0 +1,262 @@ +// +// ProgressModal.swift +// TGUIKit +// +// Created by keepcoder on 09/11/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +private final class ProgressModalView : NSVisualEffectView { + private let progressView = ProgressIndicator(frame: NSMakeRect(0, 0, 32, 32)) + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.wantsLayer = true + self.layer?.cornerRadius = 10.0 + self.autoresizingMask = [] + self.autoresizesSubviews = false + self.addSubview(self.progressView) + self.material = presentation.colors.isDark ? .dark : .light + self.blendingMode = .withinWindow + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(NSMakeSize(80, 80)) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layout() { + super.layout() + self.progressView.center() + self.center() + } +} + +class ProgressModalController: ModalViewController { + + private var progressView:ProgressIndicator? + override var background: NSColor { + return .clear + } + + override var contentBelowBackground: Bool { + return true + } + + override func becomeFirstResponder() -> Bool? { + return nil + } + + override func close(animationType: ModalAnimationCloseBehaviour = .common) { + super.close(animationType: animationType) + disposable.dispose() + } + + + override var containerBackground: NSColor { + return .clear + } + + override func viewClass() -> AnyClass { + return ProgressModalView.self + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + + override func viewDidLoad() { + super.viewDidLoad() + readyOnce() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + deinit { + disposable.dispose() + } + + fileprivate let disposable: Disposable + + init(_ disposable: Disposable) { + self.disposable = disposable + super.init(frame:NSMakeRect(0,0,80,80)) + self.bar = .init(height: 0) + } + + +} + + +private final class SuccessModalView : NSVisualEffectView { + private let imageView:ImageView = ImageView() + private let textView: TextView = TextView() + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + addSubview(imageView) + self.wantsLayer = true + self.layer?.cornerRadius = 10.0 + self.autoresizingMask = [] + self.autoresizesSubviews = false + self.material = presentation.colors.isDark ? .dark : .light + self.blendingMode = .withinWindow + } + + + + override func layout() { + super.layout() + + if !textView.isHidden { + imageView.centerY(x: 20) + textView.centerY(x: imageView.frame.maxX + 20) + } else { + imageView.center() + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateIcon(icon: CGImage, text: TextViewLayout?) { + imageView.image = icon + imageView.sizeToFit() + textView.isSelectable = false + textView.isHidden = text == nil + textView.update(text) + needsLayout = true + } +} + +class SuccessModalController : ModalViewController { + override var background: NSColor { + return .clear + } + + override var contentBelowBackground: Bool { + return true + } + + override var containerBackground: NSColor { + return .clear + } + + override func viewClass() -> AnyClass { + return SuccessModalView.self + } + private var genericView: SuccessModalView { + return self.view as! SuccessModalView + } + + override var redirectMouseAfterClosing: Bool { + return true + } + + override func viewDidResized(_ size: NSSize) { + super.viewDidResized(size) + } + + override func viewDidLoad() { + super.viewDidLoad() + genericView.updateIcon(icon: icon, text: text) + readyOnce() + } + +// override var handleEvents: Bool { +// return false +// } +// + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + private let icon: CGImage + private let text: TextViewLayout? + private let _backgroundColor: NSColor + init(_ icon: CGImage, text: TextViewLayout? = nil, background: NSColor) { + self.icon = icon + self.text = text + self._backgroundColor = background + super.init(frame:NSMakeRect(0, 0, text != nil ? 100 + text!.layoutSize.width : 80, text != nil ? max(icon.backingSize.height + 20, text!.layoutSize.height + 20) : 80)) + self.bar = .init(height: 0) + } +} + +public func showModalProgress(signal:Signal, for window:Window, disposeAfterComplete: Bool = true) -> Signal { + return Signal { subscriber in + + var signal = signal |> deliverOnMainQueue + let beforeDisposable:DisposableSet = DisposableSet() + + let modal = ProgressModalController(beforeDisposable) + let beforeModal:Signal = .single(Void()) |> delay(0.25, queue: Queue.mainQueue()) + + + beforeDisposable.add(beforeModal.start(completed: { + showModal(with: modal, for: window, animationType: .scaleCenter) + })) + + signal = signal |> afterDisposed { + modal.close() + } + + beforeDisposable.add(signal.start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + if disposeAfterComplete { + beforeDisposable.dispose() + } + modal.close() + }, completed: { + subscriber.putCompletion() + if disposeAfterComplete { + beforeDisposable.dispose() + } + modal.close() + })) + + return beforeDisposable + } +} + +public func showModalSuccess(for window: Window, icon: CGImage, text: TextViewLayout? = nil, background: NSColor = presentation.colors.background, delay _delay: Double) -> Signal { + + let modal = SuccessModalController(icon, text: text, background: background) + + return Signal({ _ -> Disposable in + showModal(with: modal, for: window, animationType: .scaleCenter) + return ActionDisposable { + modal.close() + } + }) |> timeout(_delay, queue: Queue.mainQueue(), alternate: Signal({ _ -> Disposable in + modal.close() + return EmptyDisposable + })) +} diff --git a/TGUIKit/TGUIKit/RadialProgressContainerView.swift b/submodules/TGUIKit/TGUIKit/RadialProgressContainerView.swift similarity index 95% rename from TGUIKit/TGUIKit/RadialProgressContainerView.swift rename to submodules/TGUIKit/TGUIKit/RadialProgressContainerView.swift index ac15c07a6e..f421ac2c28 100644 --- a/TGUIKit/TGUIKit/RadialProgressContainerView.swift +++ b/submodules/TGUIKit/TGUIKit/RadialProgressContainerView.swift @@ -10,7 +10,7 @@ import Cocoa public class RadialProgressContainerView: View { public let progress:RadialProgressView - private let proggressBackground:View = View() + public let proggressBackground:View = View() override public func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) } diff --git a/submodules/TGUIKit/TGUIKit/RadialProgressView.swift b/submodules/TGUIKit/TGUIKit/RadialProgressView.swift new file mode 100644 index 0000000000..d8c2ad8952 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/RadialProgressView.swift @@ -0,0 +1,570 @@ +// +// RadialProgressLayer.swift +// TGUIKit +// +// Created by keepcoder on 17/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +private func progressInteractiveThumb(backgroundColor: NSColor, foregroundColor: NSColor) -> CGImage { + + let context = DrawingContext(size: NSMakeSize(40, 40), scale: 1.0, clear: true) + + context.withContext { (ctx) in + + ctx.round(context.size, context.size.height/2.0) + ctx.setFillColor(backgroundColor.cgColor) + + let image = #imageLiteral(resourceName: "Icon_MessageFile").precomposed(foregroundColor) + + ctx.fill(NSMakeRect(0, 0, context.size.width, context.size.height)) + ctx.draw(image, in: NSMakeRect(floorToScreenPixels(System.backingScale, (context.size.width - image.backingSize.width) / 2.0), floorToScreenPixels(System.backingScale, (context.size.height - image.backingSize.height) / 2.0), image.backingSize.width, image.backingSize.height)) + + } + + return context.generateImage()! + +} + +public struct FetchControls { + public let fetch: () -> Void + public init(fetch:@escaping()->Void) { + self.fetch = fetch + } +} + + +private class RadialProgressParameters: NSObject { + let theme: RadialProgressTheme + let diameter: CGFloat + let twist: Bool + let state: RadialProgressState + let clockwise: Bool + init(theme: RadialProgressTheme, diameter: CGFloat, state: RadialProgressState, twist: Bool = true) { + self.theme = theme + self.diameter = diameter + self.state = state + self.twist = twist + self.clockwise = theme.clockwise + super.init() + } +} + +public struct RadialProgressTheme : Equatable { + public let backgroundColor: NSColor + public let foregroundColor: NSColor + public let icon: CGImage? + public let cancelFetchingIcon: CGImage? + public let iconInset:NSEdgeInsets + public let diameter:CGFloat? + public let lineWidth: CGFloat + public let clockwise: Bool + public init(backgroundColor:NSColor, foregroundColor:NSColor, icon:CGImage? = nil, cancelFetchingIcon: CGImage? = nil, iconInset:NSEdgeInsets = NSEdgeInsets(), diameter: CGFloat? = nil, lineWidth: CGFloat = 2, clockwise: Bool = true) { + self.iconInset = iconInset + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.icon = icon + self.cancelFetchingIcon = cancelFetchingIcon + self.diameter = diameter + self.lineWidth = lineWidth + self.clockwise = clockwise + } +} + +public func ==(lhs:RadialProgressTheme, rhs:RadialProgressTheme) -> Bool { + return lhs.backgroundColor == rhs.backgroundColor && lhs.foregroundColor == rhs.foregroundColor && ((lhs.icon == nil) == (rhs.icon == nil)) +} + +public enum RadialProgressState: Equatable { + case None + case Remote + case Fetching(progress: Float, force: Bool) + case ImpossibleFetching(progress: Float, force: Bool) + case Play + case Icon(image:CGImage, mode:CGBlendMode) + case Success +} + +public func ==(lhs:RadialProgressState, rhs:RadialProgressState) -> Bool { + switch lhs { + case .None: + if case .None = rhs { + return true + } else { + return false + } + case .Remote: + if case .Remote = rhs { + return true + } else { + return false + } + case .Success: + if case .Success = rhs { + return true + } else { + return false + } + case .Play: + if case .Play = rhs { + return true + } else { + return false + } + case let .Fetching(lhsProgress): + if case let .Fetching(rhsProgress) = rhs, lhsProgress == rhsProgress { + return true + } else { + return false + } + case let .ImpossibleFetching(lhsProgress): + if case let .ImpossibleFetching(rhsProgress) = rhs, lhsProgress == rhsProgress { + return true + } else { + return false + } + case .Icon: + if case .Icon = rhs { + return true + } else { + return false + } + } +} + + +private class RadialProgressOverlayLayer: CALayer { + var theme: RadialProgressTheme + let twist: Bool + private var timer: SwiftSignalKit.Timer? + private var _progress: Float = 0 + private var progress: Float = 0 + var parameters:RadialProgressParameters { + return RadialProgressParameters(theme: self.theme, diameter: theme.diameter ?? frame.width, state: self.state, twist: twist) + } + + + var state: RadialProgressState = .None { + didSet { + switch state { + case .None, .Play, .Remote, .Icon, .Success: + self.progress = 0 + self._progress = 0 + mayAnimate(false) + case let .Fetching(progress, f), let .ImpossibleFetching(progress, f): + self.progress = twist ? max(progress, 0.05) : progress + if f { + _progress = progress + } + mayAnimate(true) + } + let fps: Float = 60 + let difference = progress - _progress + let tick: Float = Float(difference / (fps * 0.2)) + + let clockwise = theme.clockwise + + if (clockwise && difference > 0) || (!clockwise && difference != 0) { + timer = SwiftSignalKit.Timer(timeout: TimeInterval(1 / fps), repeat: true, completion: { [weak self] in + if let strongSelf = self { + strongSelf._progress += tick + strongSelf.setNeedsDisplay() + if strongSelf._progress == strongSelf.progress || strongSelf._progress < 0 || (strongSelf._progress >= 1 && !clockwise) || (strongSelf._progress > strongSelf.progress && clockwise) { + strongSelf.stopAnimation() + } + } + }, queue: Queue.mainQueue()) + timer?.start() + } else { + stopAnimation() + _progress = progress + } + + self.setNeedsDisplay() + } + } + + func stopAnimation() { + timer?.invalidate() + timer = nil + self.setNeedsDisplay() + } + + init(theme: RadialProgressTheme, twist: Bool) { + self.theme = theme + self.twist = twist + super.init() + + } + + + override func removeAllAnimations() { + super.removeAllAnimations() + } + + override init(layer: Any) { + let layer = layer as! RadialProgressOverlayLayer + self.theme = layer.theme + self.twist = layer.twist + super.init(layer: layer) + } + + fileprivate override func draw(in ctx: CGContext) { + ctx.setStrokeColor(theme.foregroundColor.cgColor) + let startAngle = 2.0 * (CGFloat.pi) * CGFloat(_progress) - CGFloat.pi / 2 + let endAngle = -(CGFloat.pi / 2) + + let pathDiameter = !twist ? parameters.diameter - parameters.theme.lineWidth : parameters.diameter - parameters.theme.lineWidth - parameters.theme.lineWidth * parameters.theme.lineWidth + ctx.addArc(center: NSMakePoint(parameters.diameter / 2.0, floorToScreenPixels(System.backingScale, parameters.diameter / 2.0)), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise: parameters.clockwise) + + ctx.setLineWidth(parameters.theme.lineWidth); + ctx.setLineCap(.round); + ctx.strokePath() + } + + + fileprivate func mayAnimate(_ animate: Bool) { + + + if animate, parameters.twist { + let fromValue: Float = 0 + + if animation(forKey: "progressRotation") != nil { + return + } + removeAllAnimations() + CATransaction.begin() + + let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) + basicAnimation.duration = 2.0 + basicAnimation.fromValue = NSNumber(value: fromValue) + basicAnimation.toValue = NSNumber(value: Float.pi * 2.0) + basicAnimation.repeatCount = .infinity + basicAnimation.isRemovedOnCompletion = false + + basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + self.add(basicAnimation, forKey: "progressRotation") + CATransaction.commit() + } else { + removeAllAnimations() + } + } + + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +public class RadialProgressView: Control { + + + public var fetchControls:FetchControls? { + didSet { + self.removeAllHandlers() + if let fetchControls = fetchControls { + set(handler: { _ in + fetchControls.fetch() + }, for: .Click) + } + } + } + + public var theme:RadialProgressTheme { + didSet { + overlay.theme = theme + self.setNeedsDisplay() + } + } + private let overlay: RadialProgressOverlayLayer + private var parameters:RadialProgressParameters { + return RadialProgressParameters(theme: self.theme, diameter: NSWidth(self.frame), state: self.state) + } + + + + + public var state: RadialProgressState = .None { + didSet { + self.overlay.state = self.state + if case .Fetching = state { + if self.overlay.superlayer == nil { + self.layer?.addSublayer(self.overlay) + } + } else if case .ImpossibleFetching = state { + if self.overlay.superlayer == nil { + self.layer?.addSublayer(self.overlay) + } + } else { + if self.overlay.superlayer != nil { + self.overlay.removeFromSuperlayer() + } + } + switch oldValue { + case .Fetching: + switch self.state { + case .Fetching: + break + default: + self.setNeedsDisplay() + } + case .ImpossibleFetching: + switch self.state { + case .ImpossibleFetching: + break + default: + self.setNeedsDisplay() + } + case .Remote: + switch self.state { + case .Remote: + break + default: + self.setNeedsDisplay() + } + case .None: + switch self.state { + case .None: + break + default: + self.setNeedsDisplay() + } + case .Play: + switch self.state { + case .Play: + break + default: + self.setNeedsDisplay() + } + case .Success: + switch self.state { + case .Success: + break + default: + self.setNeedsDisplay() + } + case .Icon: + switch self.state { + case .Icon: + self.setNeedsDisplay() + default: + self.setNeedsDisplay() + } + } + + } + } + + public override func viewDidMoveToSuperview() { + overlay.mayAnimate(superview != nil) + } + + + + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override public var frame: CGRect { + get { + return super.frame + } set (value) { + let redraw = value.size != self.frame.size + super.frame = value + + if redraw { + self.overlay.frame = CGRect(origin: CGPoint(), size: value.size) + self.setNeedsDisplay() + self.overlay.setNeedsDisplay() + } + } + } + + public init(theme: RadialProgressTheme = RadialProgressTheme(backgroundColor: .blackTransparent, foregroundColor: .white, icon: nil), twist: Bool = true, size: NSSize = NSMakeSize(40, 40)) { + self.theme = theme + self.overlay = RadialProgressOverlayLayer(theme: theme, twist: twist) + super.init() + self.overlay.contentsScale = backingScaleFactor + self.frame = NSMakeRect(0, 0, size.width, size.height) + + } + + public override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + // overlay.mayAnimate(superview != nil && window != nil) + } + + + + + public override func draw(_ layer: CALayer, in context: CGContext) { + context.setFillColor(parameters.theme.backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: parameters.diameter, height: parameters.diameter))) + + switch parameters.state { + case .None: + break + case .Success: + let diameter = bounds.size.width + + let progress: CGFloat = 1.0 + + var pathLineWidth: CGFloat = 2.0 + var pathDiameter: CGFloat = diameter - pathLineWidth + + if (abs(diameter - 37.0) < 0.1) { + pathLineWidth = 2.5 + pathDiameter = diameter - pathLineWidth * 2.0 - 1.5 + } else if (abs(diameter - 32.0) < 0.1) { + pathLineWidth = 2.0 + pathDiameter = diameter - pathLineWidth * 2.0 - 1.5 + } else { + pathLineWidth = 2.5 + pathDiameter = diameter - pathLineWidth * 2.0 - 1.5 + } + + let center = CGPoint(x: diameter / 2.0, y: diameter / 2.0) + + context.setStrokeColor(parameters.theme.foregroundColor.cgColor) + context.setLineWidth(pathLineWidth) + context.setLineCap(.round) + context.setLineJoin(.round) + context.setMiterLimit(10.0) + + let firstSegment: CGFloat = max(0.0, min(1.0, progress * 3.0)) + + var s = CGPoint(x: center.x - 10.0, y: center.y + 1.0) + var p1 = CGPoint(x: 7.0, y: 7.0) + var p2 = CGPoint(x: 15.0, y: -16.0) + + if diameter < 36.0 { + s = CGPoint(x: center.x - 7.0, y: center.y + 1.0) + p1 = CGPoint(x: 4.5, y: 4.5) + p2 = CGPoint(x: 10.0, y: -11.0) + } + + if !firstSegment.isZero { + if firstSegment < 1.0 { + context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment)) + context.addLine(to: s) + } else { + let secondSegment = (progress - 0.33) * 1.5 + context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment)) + context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y)) + context.addLine(to: s) + } + } + context.strokePath() + case .Fetching: + if let icon = parameters.theme.cancelFetchingIcon { + var f = focus(icon.backingSize) + f.origin.x += parameters.theme.iconInset.left + f.origin.x -= parameters.theme.iconInset.right + f.origin.y += parameters.theme.iconInset.top + f.origin.y -= parameters.theme.iconInset.bottom + context.draw(icon, in: f) + } else { + context.setStrokeColor(parameters.theme.foregroundColor.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + + + let crossSize: CGFloat = parameters.diameter < 40 ? 9 : 14.0 + + context.move(to: CGPoint(x: parameters.diameter / 2.0 - crossSize / 2.0, y: parameters.diameter / 2.0 - crossSize / 2.0)) + context.addLine(to: CGPoint(x: parameters.diameter / 2.0 + crossSize / 2.0, y: parameters.diameter / 2.0 + crossSize / 2.0)) + context.strokePath() + context.move(to: CGPoint(x: parameters.diameter / 2.0 + crossSize / 2.0, y: parameters.diameter / 2.0 - crossSize / 2.0)) + context.addLine(to: CGPoint(x: parameters.diameter / 2.0 - crossSize / 2.0, y: parameters.diameter / 2.0 + crossSize / 2.0)) + context.strokePath() + + + } + + case .Remote: + let color = parameters.theme.foregroundColor + let diameter = layer.frame.height + + context.setStrokeColor(color.cgColor) + var lineWidth: CGFloat = 2.0 + if diameter < 24.0 { + lineWidth = 1.3 + } + context.setLineWidth(lineWidth) + context.setLineCap(.round) + context.setLineJoin(.round) + + let factor = diameter / 50.0 + + let arrowHeadSize: CGFloat = 15.0 * factor + let arrowLength: CGFloat = 18.0 * factor + let arrowHeadOffset: CGFloat = 1.0 * factor + + context.move(to: CGPoint(x: diameter / 2.0, y: diameter / 2.0 - arrowLength / 2.0 + arrowHeadOffset)) + context.addLine(to: CGPoint(x: diameter / 2.0, y: diameter / 2.0 + arrowLength / 2.0 - 1.0 + arrowHeadOffset)) + context.strokePath() + + context.move(to: CGPoint(x: diameter / 2.0 - arrowHeadSize / 2.0, y: diameter / 2.0 + arrowLength / 2.0 - arrowHeadSize / 2.0 + arrowHeadOffset)) + context.addLine(to: CGPoint(x: diameter / 2.0, y: diameter / 2.0 + arrowLength / 2.0 + arrowHeadOffset)) + context.addLine(to: CGPoint(x: diameter / 2.0 + arrowHeadSize / 2.0, y: diameter / 2.0 + arrowLength / 2.0 - arrowHeadSize / 2.0 + arrowHeadOffset)) + context.strokePath() + case .Play: + let color = parameters.theme.foregroundColor + let diameter = layer.frame.height + context.setFillColor(color.cgColor) + + let factor = diameter / 50.0 + + let size = CGSize(width: 15.0, height: 18.0) + context.translateBy(x: (diameter - size.width) / 2.0 + 1.5, y: (diameter - size.height) / 2.0) + if (diameter < 40.0) { + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: factor, y: factor) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + } + let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ") + context.fillPath() + if (diameter < 40.0) { + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + } + context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0) + case .ImpossibleFetching: + break + case let .Icon(image: icon, mode:blendMode): + var f = focus(icon.backingSize) + f.origin.x += parameters.theme.iconInset.left + f.origin.x -= parameters.theme.iconInset.right + f.origin.y += parameters.theme.iconInset.top + f.origin.y -= parameters.theme.iconInset.bottom + context.setBlendMode(blendMode) + context.draw(icon, in: f) + } + + } + + public override func copy() -> Any { + let view = NSView() + view.wantsLayer = true + view.frame = self.frame + view.layer?.contents = progressInteractiveThumb(backgroundColor: parameters.theme.backgroundColor, foregroundColor: parameters.theme.foregroundColor) + return view + + } + + public override func apply(state: ControlState) { + + } + +} diff --git a/TGUIKit/TGUIKit/SImageLayer.swift b/submodules/TGUIKit/TGUIKit/SImageLayer.swift similarity index 98% rename from TGUIKit/TGUIKit/SImageLayer.swift rename to submodules/TGUIKit/TGUIKit/SImageLayer.swift index 9d642b966b..35f2ab7319 100644 --- a/TGUIKit/TGUIKit/SImageLayer.swift +++ b/submodules/TGUIKit/TGUIKit/SImageLayer.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit open class SImageLayer: CALayer { private let disposable = MetaDisposable() diff --git a/submodules/TGUIKit/TGUIKit/SPopoverRowItem.swift b/submodules/TGUIKit/TGUIKit/SPopoverRowItem.swift new file mode 100644 index 0000000000..a9003544ff --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SPopoverRowItem.swift @@ -0,0 +1,214 @@ +// +// SPopoverRowItem.swift +// Telegram-Mac +// +// Created by keepcoder on 28/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +class SPopoverRowItem: TableRowItem { + private let _height: CGFloat + override var height: CGFloat { + return _height + } + + private var unique:Int64 + + override var stableId: AnyHashable { + return unique + } + + let iStyle:ControlStyle = ControlStyle(backgroundColor: presentation.colors.accentSelect, highlightColor: presentation.colors.underSelectedColor) + + + // data + let image:CGImage? + let title:TextViewLayout + let activeTitle: TextViewLayout + let clickHandler:() -> Void + + override func viewClass() -> AnyClass { + return SPopoverRowView.self + } + let alignAsImage: Bool + let additionView: SPopoverAdditionItemView? + init(_ initialSize:NSSize, height: CGFloat, image:CGImage? = nil, alignAsImage: Bool, title:String, textColor: NSColor, additionView: SPopoverAdditionItemView? = nil, clickHandler:@escaping() ->Void = {}) { + self.image = image + self._height = height + self.alignAsImage = alignAsImage + self.title = TextViewLayout(.initialize(string: title, color: textColor, font: .normal(.title)), maximumNumberOfLines: 1) + self.activeTitle = TextViewLayout(.initialize(string: title, color: presentation.colors.underSelectedColor, font: .normal(.title)), maximumNumberOfLines: 1) + self.additionView = additionView + self.title.measure(width: 200) + self.activeTitle.measure(width: 200) + self.clickHandler = clickHandler + unique = Int64(arc4random()) + super.init(initialSize) + } + var itemWidth: CGFloat { + return self.title.layoutSize.width + (additionView != nil ? 40 : 0) + } +} + + +private class SPopoverRowView: TableRowView { + + var image:ImageView = ImageView() + + var overlay:OverlayControl = OverlayControl() + + var text:TextView = TextView(); + + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.addSubview(overlay) + overlay.addSubview(image) + + overlay.addSubview(text) + text.isSelectable = false + text.userInteractionEnabled = false + + overlay.set(handler: {[weak self] (state) in + self?.overlay.backgroundColor = presentation.colors.accentSelect + if let item = self?.item as? SPopoverRowItem { + if let image = item.image { + self?.image.image = item.iStyle.highlight(image: image) + } + self?.text.backgroundColor = presentation.colors.accentSelect + self?.text.update(item.activeTitle) + item.additionView?.updateIsSelected?(self?.mouseInside() ?? false) + } + }, for: .Hover) + + overlay.set(handler: {[weak self] (state) in + self?.overlay.backgroundColor = presentation.colors.background + if let item = self?.item as? SPopoverRowItem { + self?.image.image = item.image + self?.text.backgroundColor = presentation.colors.background + self?.text.update(item.title) + item.additionView?.updateIsSelected?(self?.mouseInside() ?? false) + } + }, for: .Normal) + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + overlay.setFrameSize(newSize) + } + + override func layout() { + super.layout() + if let item = item as? SPopoverRowItem { + if item.image != nil || item.alignAsImage { + text.centerY(self, x: 45) + } else { + text.center() + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func updateMouse() { + overlay.updateState() + } + + override func set(item:TableRowItem, animated:Bool = false) { + super.set(item: item, animated: animated) + + overlay.removeAllHandlers() + overlay.backgroundColor = presentation.colors.background + text.backgroundColor = presentation.colors.background + if let item = item as? SPopoverRowItem { + + if let view = item.additionView { + overlay.addSubview(view.view) + view.view.setFrameOrigin(NSMakePoint(frame.width - 34, 10)) + } + + image.image = item.image + overlay.removeAllHandlers() + overlay.set(handler: {_ in + item.clickHandler() + }, for: .Click) + image.sizeToFit() + image.centerY(self, x: floorToScreenPixels(backingScaleFactor, (45 - image.frame.width) / 2)) + + text.update(item.title) + + if item.image != nil || item.alignAsImage { + text.centerY(self, x: 45) + } else { + text.center() + } + } + + } + +} + + +public final class SPopoverSeparatorItem : TableRowItem { + + let drawBorder: Bool + + override public var stableId: AnyHashable { + return arc4random() + } + + override public init(_ initialSize: NSSize) { + self.drawBorder = true + super.init(initialSize) + } + public init(_ drawBorder: Bool = true) { + self.drawBorder = drawBorder + super.init(NSZeroSize) + } + + override public func viewClass() -> AnyClass { + return SPopoverSeparatorView.self + } + + override public var height: CGFloat { + return drawBorder ? 10 : 1 + } +} + + +private final class SPopoverSeparatorView : TableRowView { + private let separator: View = View() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(separator) + } + override func updateColors() { + super.updateColors() + separator.backgroundColor = presentation.colors.border + } + + override func set(item: TableRowItem, animated: Bool = false) { + super.set(item: item, animated: animated) + + guard let item = item as? SPopoverSeparatorItem else { + return + } + // separator.isHidden = !item.drawBorder + } + + override func layout() { + super.layout() + separator.setFrameSize(NSMakeSize(frame.width, .borderSize)) + separator.center() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/submodules/TGUIKit/TGUIKit/SPopoverViewController.swift b/submodules/TGUIKit/TGUIKit/SPopoverViewController.swift new file mode 100644 index 0000000000..8b96504c5d --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SPopoverViewController.swift @@ -0,0 +1,153 @@ +// +// SPopoverViewController.swift +// Telegram-Mac +// +// Created by keepcoder on 09/11/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +public struct SPopoverAdditionItemView { + let context: Any? + let view: NSView + let updateIsSelected:((Bool)->Void)? + public init(context: Any?, view: NSView, updateIsSelected:((Bool)->Void)? = nil) { + self.context = context + self.view = view + self.updateIsSelected = updateIsSelected + } +} + +public struct SPopoverItem : Equatable { + let title:String + let image:CGImage? + let textColor: NSColor + let height: CGFloat + let handler:()->Void + let isSeparator: Bool + let drawSeparatorBorder: Bool + let additionView: SPopoverAdditionItemView? + + public init(_ title:String, _ handler:@escaping ()->Void, _ image:CGImage? = nil, _ textColor: NSColor = presentation.colors.text, height: CGFloat = 40.0, isSeparator: Bool = false, drawSeparatorBorder: Bool = true, additionView: SPopoverAdditionItemView? = nil) { + self.title = title + self.image = image + self.textColor = textColor + self.handler = handler + self.height = height + self.isSeparator = false + self.additionView = additionView + self.drawSeparatorBorder = drawSeparatorBorder + } + + public init(_ drawSeparatorBorder: Bool = true) { + self.title = "" + self.image = nil + self.textColor = presentation.colors.text + self.handler = {} + self.height = 10 + self.isSeparator = true + self.drawSeparatorBorder = drawSeparatorBorder + self.additionView = nil + } + + public static func ==(lhs: SPopoverItem, rhs: SPopoverItem) -> Bool { + return lhs.title == rhs.title && lhs.textColor == rhs.textColor + } +} + + + +public class SPopoverViewController: GenericViewController { + private let items:[TableRowItem] + private let disposable = MetaDisposable() + public override func viewDidLoad() { + super.viewDidLoad() + + genericView.insert(items: items) + genericView.needUpdateVisibleAfterScroll = true + genericView.reloadData() + + readyOnce() + } + + public init(items:[SPopoverItem], visibility:Int = 4, handlerDelay: Double = 0.15, headerItems: [TableRowItem] = []) { + weak var controller:SPopoverViewController? + let alignAsImage = !items.filter({$0.image != nil}).isEmpty + let items = items.map { item -> TableRowItem in + if item.isSeparator { + return SPopoverSeparatorItem(item.drawSeparatorBorder) + } else { + return SPopoverRowItem(NSZeroSize, height: item.height, image: item.image, alignAsImage: alignAsImage, title: item.title, textColor: item.textColor, additionView: item.additionView, clickHandler: { + Queue.mainQueue().justDispatch { + controller?.popover?.hide() + + if handlerDelay == 0 { + item.handler() + } else { + _ = (Signal.single(Void()) |> delay(handlerDelay, queue: Queue.mainQueue())).start(next: { + item.handler() + }) + } + } + }) + } + } + + + let width: CGFloat = items.isEmpty ? 200 : items.compactMap({ $0 as? SPopoverRowItem }).max(by: {$0.itemWidth < $1.itemWidth})!.itemWidth + + for item in headerItems { + _ = item.makeSize(width + 48 + 18) + } + + + + self.items = headerItems + (headerItems.isEmpty ? [] : [SPopoverSeparatorItem(false)]) + items + + var height: CGFloat = 0 + for (i, item) in self.items.enumerated() { + if i < visibility { + height += item.height + } else { + height += item.height / 2 + break + } + } + + // let height = min(visibility * 40 + 20, items.count * 40) + super.init(frame: NSMakeRect(0, 0, width + 45 + 18, CGFloat(height))) + bar = .init(height: 0) + controller = self + } + + deinit { + disposable.dispose() + } + + + public override func viewWillAppear(_ animated: Bool) { + + } + + +} + + + + + +//public func presntContextMenu(for event: NSEvent, items: [SPopoverItem]) -> Void { +// +// +// let controller = SPopoverViewController(items: items, visibility: Int.max, handlerDelay: 0) +// +// let window = Window(contentRect: NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, controller.frame.width, controller.frame.height), styleMask: [], backing: .buffered, defer: true) +// window.contentView = controller.view +// window.backgroundColor = .clear +// event.window?.addChildWindow(window, ordered: .above) +// window.makeKeyAndOrderFront(nil) +// +//} diff --git a/submodules/TGUIKit/TGUIKit/ScrollView.swift b/submodules/TGUIKit/TGUIKit/ScrollView.swift new file mode 100644 index 0000000000..f13d761b40 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ScrollView.swift @@ -0,0 +1,296 @@ +// +// ScrollView.swift +// TGUIKit +// +// Created by keepcoder on 07/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import Foundation +public enum ScrollDirection { + case top; + case bottom; + case none; +} + + + +public struct ScrollPosition : Equatable { + public private(set) var rect:NSRect + public private(set) var visibleRows: NSRange + public private(set) var direction:ScrollDirection + public init(_ rect:NSRect = NSZeroRect, _ direction:ScrollDirection = .none, _ visibleRows: NSRange = NSMakeRange(NSNotFound, 0)) { + self.rect = rect + self.visibleRows = visibleRows + self.direction = direction + } +} + +public func ==(lhs:ScrollPosition, rhs:ScrollPosition) -> Bool { + return NSEqualRects(lhs.rect, rhs.rect) && lhs.direction == rhs.direction && NSEqualRanges(lhs.visibleRows, rhs.visibleRows) +} + +open class ScrollView: NSScrollView{ + private var currentpos:ScrollPosition = ScrollPosition() + public var deltaCorner:Int64 = 60 + + public var applyExternalScroll:((NSEvent)->Bool)? = nil + + override public static var isCompatibleWithResponsiveScrolling: Bool { + return true + } + + public func scrollPosition(_ visibleRange: NSRange = NSMakeRange(NSNotFound, 0)) -> (current: ScrollPosition, previous: ScrollPosition) { + + let rect = NSMakeRect(contentView.bounds.minX, contentView.bounds.maxY,contentView.documentRect.width, contentView.documentRect.height) + + var d:ScrollDirection = .none + + + if abs(currentpos.rect.minY - rect.minY) < 5 { + return (currentpos, currentpos) + } + + if(currentpos.rect.minY > rect.minY) { + d = .top + } else if(currentpos.rect.minY < rect.minY) { + d = .bottom + } + + let n = ScrollPosition(rect, d, visibleRange) + let previous = currentpos + currentpos = n + return (n, previous) + } + + func updateScroll(_ visibleRange: NSRange = NSMakeRange(NSNotFound, 0)) -> Void { + self.currentpos = ScrollPosition(NSMakeRect(contentView.bounds.minX, contentView.bounds.maxY,contentView.documentRect.width, contentView.documentRect.height), .none, visibleRange) + } + + public var documentOffset:NSPoint { + return NSMakePoint(NSMinX(self.contentView.bounds), NSMinY(self.contentView.bounds)) + } + + open override func knowsPageRange(_ range: NSRangePointer) -> Bool { + return super.knowsPageRange(range) + } + + public var documentSize:NSSize { + return self.contentView.documentRect.size; + } +// +// +// open override func draw(_ dirtyRect: NSRect) { +// +// } + + + public var clipView:TGClipView { + return self.contentView as! TGClipView + } + + + override public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + + + + // self.layer?.delegate = self +// self.canDrawSubviewsIntoLayer = true +// self.layer?.drawsAsynchronously = System.drawAsync + + // self.contentView.wantsLayer = true + // self.contentView.layerContentsRedrawPolicy = .onSetNeedsDisplay + // self.contentView.layer?.drawsAsynchronously = System.drawAsync + + // self.layerContentsRedrawPolicy = .never; + //self.layer?.isOpaque = true + + let clipView = TGClipView(frame:self.contentView.frame) + self.contentView = clipView; + + drawsBackground = false + layerContentsRedrawPolicy = .never + + self.hasHorizontalScroller = false + self.horizontalScrollElasticity = .none + self.verticalScroller?.scrollerStyle = .overlay + autoresizingMask = [] + self.wantsLayer = true; + layer?.backgroundColor = presentation.colors.background.cgColor + // verticalScrollElasticity = .automatic + //allowsMagnification = true + //self.hasVerticalScroller = false + + // self.scrollerStyle = .overlay + + } + + open override func draw(_ dirtyRect: NSRect) { + + } + + + open override func scrollWheel(with event: NSEvent) { + guard let window = window as? Window else { + super.scrollWheel(with: event) + return + } + + if let applyExternalScroll = self.applyExternalScroll, applyExternalScroll(event) { + return + } + + if !window.inLiveSwiping, super.responds(to: #selector(scrollWheel(with:))) { + super.scrollWheel(with: event) + } +// + } + + open override func setNeedsDisplay(_ invalidRect: NSRect) { + + } + + open override var scrollerStyle: NSScroller.Style { + set { + super.scrollerStyle = .overlay + } + get { + return .overlay + } + } + + +// +// open override var hasVerticalScroller: Bool { +// get { +// return true +// } +// set { +// super.hasVerticalScroller = newValue +// } +// } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + assertOnMainThread() + } + + +// override open func scrollWheel(with event: NSEvent) { +// NSLog("\(event)") +// var scrollPoint = self.contentView.bounds.origin +// // var isInverted = CBool(UserDefaults.standard.object(forKey: "com.apple.swipescrolldirection")!) +//// if !isInverted { +//// scrollPoint.x += (event.scrollingDeltaY() + event.scrollingDeltaX()) +//// } +//// else { +// scrollPoint.y -= (event.scrollingDeltaY + event.scrollingDeltaY) +// // } +// self.clipView.scroll(to: scrollPoint) +// } + + let dynamic:CGFloat = 100.0 + +// open override func scrollWheel(with event: NSEvent) { +// +// if deltaCorner > 0 { +// var origin = clipView.bounds.origin +// +// deltaCorner = max(Int64(floorToScreenPixels(backingScaleFactor, frame.height / 6.0)),40) +// +// +// +// let deltaScrollY = min(max(Int64(event.scrollingDeltaY),-deltaCorner),deltaCorner) +// +// +// // NSLog("\(event.deltaY)") +// +// if let cgEvent = event.cgEvent?.copy() { +// +// +// +// // cgEvent.setDoubleValueField(.scrollWheelEventDeltaAxis1, value: Double(min(max(-4,event.deltaY),4))) +// +// +// //if delta == deltaCorner || delta == -deltaCorner || delta == 0 { +// cgEvent.setIntegerValueField(.scrollWheelEventScrollCount, value: min(1,cgEvent.getIntegerValueField(.scrollWheelEventScrollCount))) +// // } +// cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: deltaScrollY) +// // if event.scrollingDeltaY > 0 { +// // +// // } else { +// // cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: ) +// // } +// +// // NSLog("\(cgEvent.getIntegerValueField(.scrollWheelEventScrollCount)) == \(delta)") +// +// // cgEvent.setIntegerValueField(.scrollWheelEventPointDeltaAxis1, value: 10) +// // cgEvent.setIntegerValueField(.scrollWheelEventScrollCount, value: Int64(delta)) +// +// let newEvent = NSEvent(cgEvent: cgEvent)! +// +// super.scrollWheel(with: newEvent) +// +// +// } else { +// //NSLog("\(cgEvent.getIntegerValueField(.scrollWheelEventScrollCount))") +// +// super.scrollWheel(with: event) +// } +// +// +// if origin == clipView.bounds.origin, abs(deltaScrollY) >= deltaCorner +// { +// +// if let documentView = documentView, !(self is HorizontalTableView) { +// +// if frame.minY < origin.y - frame.height - 50 { +// if origin.y > documentView.frame.maxY + dynamic { +// clipView.scroll(to: NSMakePoint(origin.x, documentView.frame.minY)) +// } +// +// if origin.y < documentView.frame.height { +// if documentView.isFlipped { +// if origin.y < documentView.frame.height - (frame.height + frame.minY) { +// origin.y -= CGFloat(deltaScrollY) +// clipView.scroll(to: origin) +// reflectScrolledClipView(clipView) +// } +// } else { +// if origin.y + frame.height < documentView.frame.height { +// origin.y += CGFloat(deltaScrollY) +// clipView.scroll(to: origin) +// reflectScrolledClipView(clipView) +// } +// +// } +// } +// } else if origin.y < -dynamic { +// clipView.scroll(to: NSMakePoint(origin.x, 0)) +// } +// } +// } +// } else { +// super.scrollWheel(with: event) +// } +// } + + + public func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) -> Void { + super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + public func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + public func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + } + +} diff --git a/submodules/TGUIKit/TGUIKit/ScrollableSegmentController.swift b/submodules/TGUIKit/TGUIKit/ScrollableSegmentController.swift new file mode 100644 index 0000000000..f552321e5b --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ScrollableSegmentController.swift @@ -0,0 +1,13 @@ +// +// ScrollableSegmentController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 05.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa + +class ScrollableSegmentView: View { + +} diff --git a/submodules/TGUIKit/TGUIKit/ScrollableSegmentView.swift b/submodules/TGUIKit/TGUIKit/ScrollableSegmentView.swift new file mode 100644 index 0000000000..1c471f9d80 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ScrollableSegmentView.swift @@ -0,0 +1,752 @@ +// +// ScrollableSegmentController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 05.02.2020. +// Copyright © 2020 Telegram. All rights reserved. +// + +import Cocoa + + + + +public protocol _HasCustomUIEquatableRepresentation { + func _toCustomUIEquatable() -> UIEquatable? +} + +internal protocol _UIEquatableBox { + var _typeID: ObjectIdentifier { get } + func _unbox() -> T? + + func _isEqual(to: _UIEquatableBox) -> Bool? + + var _base: Any { get } + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool +} + +internal struct _ConcreteEquatableBox : _UIEquatableBox { + internal var _baseEquatable: Base + + internal init(_ base: Base) { + self._baseEquatable = base + } + + + internal var _typeID: ObjectIdentifier { + return ObjectIdentifier(type(of: self)) + } + + internal func _unbox() -> T? { + return (self as _UIEquatableBox as? _ConcreteEquatableBox)?._baseEquatable + } + + internal func _isEqual(to rhs: _UIEquatableBox) -> Bool? { + if let rhs: Base = rhs._unbox() { + return _baseEquatable == rhs + } + return nil + } + + internal var _base: Any { + return _baseEquatable + } + + internal + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool { + guard let value = _baseEquatable as? T else { return false } + result.initialize(to: value) + return true + } +} + + +public struct UIEquatable { + internal var _box: _UIEquatableBox + internal var _usedCustomRepresentation: Bool + + + public init(_ base: H) { + if let customRepresentation = + (base as? _HasCustomUIEquatableRepresentation)?._toCustomUIEquatable() { + self = customRepresentation + self._usedCustomRepresentation = true + return + } + + self._box = _ConcreteEquatableBox(base) + self._usedCustomRepresentation = false + } + + internal init(_usingDefaultRepresentationOf base: H) { + self._box = _ConcreteEquatableBox(base) + self._usedCustomRepresentation = false + } + + public var base: Any { + return _box._base + } + internal + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool { + // Attempt the downcast. + if _box._downCastConditional(into: result) { return true } + + + + return false + } +} + +extension UIEquatable : Equatable { + public static func == (lhs: UIEquatable, rhs: UIEquatable) -> Bool { + if let result = lhs._box._isEqual(to: rhs._box) { return result } + + return false + } +} + +extension UIEquatable : CustomStringConvertible { + public var description: String { + return String(describing: base) + } +} + +extension UIEquatable : CustomDebugStringConvertible { + public var debugDescription: String { + return "UIEquatable(" + String(reflecting: base) + ")" + } +} + +extension UIEquatable : CustomReflectable { + public var customMirror: Mirror { + return Mirror( + self, + children: ["value": base]) + } +} + +public final class ScrollableSegmentItem : Equatable, Comparable, Identifiable { + let title: String + let selected: Bool + let theme: ScrollableSegmentTheme + let insets: NSEdgeInsets + let icon: CGImage? + let equatable: UIEquatable? + public let index: Int + public let uniqueId: Int32 + + public init(title: String, index: Int, uniqueId: Int32, selected: Bool, insets: NSEdgeInsets, icon: CGImage?, theme: ScrollableSegmentTheme, equatable: UIEquatable?) { + self.title = title + self.index = index + self.uniqueId = uniqueId + self.selected = selected + self.theme = theme + self.insets = insets + self.icon = icon + self.equatable = equatable + } + + public var stableId: Int32 { + return uniqueId + } + public static func <(lhs: ScrollableSegmentItem, rhs: ScrollableSegmentItem) -> Bool { + return lhs.index < rhs.index + } + public static func ==(lhs: ScrollableSegmentItem, rhs: ScrollableSegmentItem) -> Bool { + return lhs.index == rhs.index && lhs.title == rhs.title && lhs.uniqueId == rhs.uniqueId && lhs.selected == rhs.selected && lhs.theme == rhs.theme && lhs.insets == rhs.insets && lhs.equatable == rhs.equatable + } + + fileprivate var view:SegmentItemView? +} + +private func buildText(for item: ScrollableSegmentItem) -> TextViewLayout { + let layout = TextViewLayout.init(.initialize(string: item.title, color: item.selected ? item.theme.activeText : item.theme.inactiveText, font: item.theme.textFont)) + layout.measure(width: .greatestFiniteMagnitude) + return layout +} + +private final class SegmentItemView : Control { + private(set) var item: ScrollableSegmentItem + private var textLayout: TextViewLayout + private var imageView: ImageView? + private let textView = TextView() + private let callback: (ScrollableSegmentItem)->Void + private let menuItems:(ScrollableSegmentItem)->[ContextMenuItem] + private let startDraggingIfNeeded: (ScrollableSegmentItem, Control)->Void + init(item: ScrollableSegmentItem, theme: ScrollableSegmentTheme, callback: @escaping(ScrollableSegmentItem)->Void, startDraggingIfNeeded: @escaping(ScrollableSegmentItem, Control)->Void, menuItems:@escaping(ScrollableSegmentItem)->[ContextMenuItem]) { + self.item = item + self.callback = callback + self.menuItems = menuItems + self.textLayout = buildText(for: item) + self.startDraggingIfNeeded = startDraggingIfNeeded + super.init(frame: NSZeroRect) + self.handleLongEvent = true + addSubview(textView) + textView.userInteractionEnabled = false + textView.isSelectable = false + textView.disableBackgroundDrawing = true + textView.isEventLess = true + self.updateItem(item, theme: theme, animated: false) + + updateHandlers() + } + + private func updateHandlers() { + set(handler: { [weak self] _ in + if self?.item.selected == false { + self?.textView.change(opacity: 0.8, animated: true) + self?.imageView?.change(opacity: 0.8, animated: true) + } + }, for: .Highlight) + + set(handler: { [weak self] _ in + self?.textView.change(opacity: 1.0, animated: true) + self?.imageView?.change(opacity: 1.0, animated: true) + }, for: .Normal) + + set(handler: { [weak self] _ in + self?.textView.change(opacity: 1.0, animated: true) + self?.imageView?.change(opacity: 1.0, animated: true) + }, for: .Hover) + + + set(handler: { [weak self] _ in + guard let `self` = self else { + return + } + self.callback(self.item) + }, for: .Click) + + + set(handler: { [weak self] control in + guard let `self` = self else { + return + } + self.startDraggingIfNeeded(self.item, control) + }, for: .LongMouseDown) + + set(handler: { [weak self] control in + guard let `self` = self else { + return + } + if let event = NSApp.currentEvent { + ContextMenu.show(items: self.menuItems(self.item), view: control, event: event) + } + + }, for: .RightDown) + } + + override func scrollWheel(with event: NSEvent) { + super.scrollWheel(with: event) + } + + var size: NSSize { + var width: CGFloat = self.textLayout.layoutSize.width + item.insets.left + item.insets.right + if let imageView = imageView { + width += 5 + imageView.frame.width + } + if self.textLayout.layoutSize.width == 0 { + width -= (5 + (item.insets.left + item.insets.right) / 2) + } + return NSMakeSize(width, frame.height) + } + + func updateItem(_ item: ScrollableSegmentItem, theme: ScrollableSegmentTheme, animated: Bool) { + self.item = item + self.textLayout = buildText(for: item) + textView.update(self.textLayout) + if let image = item.icon { + if imageView == nil { + imageView = ImageView() + addSubview(imageView!) + if animated { + // imageView?.layer?.animateAlpha(from: 0, to: 1, duration: duration) + } + } + imageView?.image = image + imageView?.sizeToFit() + } else { + if animated, let imageView = imageView { + self.imageView = nil + imageView.removeFromSuperview() + +// imageView.layer?.animateAlpha(from: 1, to: 0, duration: duration, removeOnCompletion: false, completion: { [weak imageView] _ in +// imageView?.removeFromSuperview() +// }) + } else { + imageView?.removeFromSuperview() + imageView = nil + } + + } + + change(size: size, animated: animated, duration: duration) + self.backgroundColor = .clear + textView.backgroundColor = .clear + needsLayout = true + } + + override func layout() { + super.layout() + if textView.frame.width > 0 { + textView.centerY(x: item.insets.left) + textView.setFrameOrigin(NSMakePoint(textView.frame.minX, textView.frame.minY - item.insets.bottom + item.insets.top)) + } else { + textView.centerY(x: 0) + } + if let imageView = imageView { + if textView.frame.width > 0 { + imageView.centerY(x: textView.frame.maxX + 5) + } else { + imageView.center() + } + imageView.setFrameOrigin(NSMakePoint(imageView.frame.minX, imageView.frame.minY - (item.insets.bottom + item.insets.top) + 1)) + } + + } + + required init(frame frameRect: NSRect) { + fatalError("init(coder:) has not been implemented") + } + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class SelectorView : View { + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + layerContentsRedrawPolicy = .duringViewResize + } + var theme: ScrollableSegmentTheme? = nil { + didSet { + needsDisplay = true + } + } + + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + ctx.round(frame.size, 2.0) + if let theme = self.theme { + ctx.setFillColor(theme.selector.cgColor) + ctx.fill(bounds) + } + } +} + +public struct ScrollableSegmentTheme : Equatable { + let background: NSColor + let border: NSColor + let selector: NSColor + let inactiveText: NSColor + let activeText: NSColor + let textFont: NSFont + public init(background: NSColor, border: NSColor, selector: NSColor, inactiveText: NSColor, activeText: NSColor, textFont: NSFont) { + self.border = border + self.selector = selector + self.inactiveText = inactiveText + self.activeText = activeText + self.textFont = textFont + self.background = background + } +} + +private let duration: Double = 0.2 + + +private class Scroll : ScrollView { + override func scrollWheel(with event: NSEvent) { + + var scrollPoint = contentView.bounds.origin + let isInverted: Bool = System.isScrollInverted + if event.scrollingDeltaY != 0 { + if isInverted { + scrollPoint.x += -event.scrollingDeltaY + } else { + scrollPoint.x -= event.scrollingDeltaY + } + } + if event.scrollingDeltaX != 0 { + if !isInverted { + scrollPoint.x += -event.scrollingDeltaX + } else { + scrollPoint.x -= event.scrollingDeltaX + } + } + if documentView!.frame.width > frame.width { + scrollPoint.x = min(max(0, scrollPoint.x), documentView!.frame.width - frame.width) + clipView.scroll(to: scrollPoint) + } else { + superview?.scrollWheel(with: event) + } + } +} + +private struct ResortData { + let point: NSPoint + let item: ScrollableSegmentItem + let view: Control + let viewPoint: NSPoint +} + +public class ScrollableSegmentView: View { + public let scrollView:ScrollView = Scroll() + + private let selectorView: SelectorView = SelectorView(frame: NSZeroRect) + private let borderView = View() + + private let documentView = View() + private var items: [ScrollableSegmentItem] = [] + private var selected:Int = 0 + + public var menuItems:((ScrollableSegmentItem)->[ContextMenuItem])? + + + public var fitToWidth: Bool = false + + public var didChangeSelectedItem:((ScrollableSegmentItem)->Void)? + + public var theme: ScrollableSegmentTheme = ScrollableSegmentTheme(background: presentation.colors.background, border: presentation.colors.border, selector: presentation.colors.accent, inactiveText: presentation.colors.grayText, activeText: presentation.colors.accent, textFont: .medium(.title)) + { + didSet { + if theme != oldValue { + redraw() + } + } + } + + public var resortRange: NSRange? = nil + public var resortHandler: ((Int, Int)->Void)? + public override func scrollWheel(with event: NSEvent) { + super.scrollWheel(with: event) + } + + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(scrollView) + addSubview(borderView) + addSubview(selectorView) + scrollView.documentView = documentView + + + +// scrollView.backgroundColor = .clear +// scrollView.background = .clear +// +// documentView.backgroundColor = .clear +// + NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: scrollView.clipView, queue: OperationQueue.main, using: { [weak self] notification in + self?.needsLayout = true + }) + layout() + redraw() + + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + public override func mouseUp(with event: NSEvent) { + if fitToWidth, documentView.frame.width <= frame.width, mouseInside() { + let insetBetween: CGFloat = self.insetBetween + let point = documentView.convert(event.locationInWindow, from: nil) + + for item in self.items { + if let view = item.view { + + if point.x >= view.frame.minX - (item == self.items.first ? insetBetween : insetBetween / 2) && point.x <= view.frame.maxX + (item == self.items.last ? insetBetween : insetBetween / 2) { + self.didChangeSelectedItem?(item) + return + } + + } + } + } + super.mouseUp(with: event) + } + + public override func updateLocalizationAndTheme(theme presentation: PresentationTheme) { + self.theme = ScrollableSegmentTheme(background: presentation.colors.background, border: presentation.colors.border, selector: presentation.colors.accent, inactiveText: presentation.colors.grayText, activeText: presentation.colors.accent, textFont: .normal(.title)) + } + + private var selectorFrame: CGRect { + + let selectedItem = self.items.first(where: { $0.selected }) + + var x: CGFloat = 0 + var width: CGFloat = frame.width + if let selectedItem = selectedItem, let view = selectedItem.view { + let point: NSPoint + if scrollView.clipView.isAnimateScrolling { + point = NSMakePoint(min(max(view.frame.midX - frame.width / 2, 0), max(documentView.frame.width - frame.width, 0)), 0) + } else { + point = scrollView.documentOffset + } + + + x = view.frame.minX + selectedItem.insets.left - point.x + width = view.size.width - selectedItem.insets.left - selectedItem.insets.right + } + + return CGRect(origin: NSMakePoint(x, frame.height - 6 / 2), size: CGSize(width: width, height: 6)) + } + + + private func moveSelector(_ animated: Bool = false) { + self.selectorView.change(pos: selectorFrame.origin, animated: animated) + self.selectorView.change(size: selectorFrame.size, animated: animated) + } + + private var insetBetween: CGFloat { + var insetBetween: CGFloat = 0 + + if fitToWidth, items.count > 1 { + let itemsWidth = items.reduce(CGFloat(0), { current, value in + return current + (value.view?.size.width ?? 0) + }) + if itemsWidth < frame.width { + insetBetween = floor((frame.width - itemsWidth) / CGFloat(items.count + 1)) + } + } + return insetBetween + } + + public override func layout() { + super.layout() + scrollView.frame = bounds + borderView.frame = NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize) + for item in self.items { + if let view = item.view { + view.setFrameSize(NSMakeSize(view.size.width, frame.height)) + } + } + + let insetBetween: CGFloat = self.insetBetween + + + var x: CGFloat = insetBetween + + for item in self.items { + if let view = item.view { + view.setFrameOrigin(NSMakePoint(x, 0)) + x += view.size.width + insetBetween + } + } + documentView.frame = CGRect(origin: .zero, size: NSMakeSize(max(x, frame.width), frame.height)) + selectorView.frame = selectorFrame + } + + public func updateItems(_ items:[ScrollableSegmentItem], animated: Bool, autoscroll: Bool = true) -> Void { + assertOnMainThread() + + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: self.items, rightList: items) + for rdx in deleteIndices.reversed() { + self.removeItem(at: rdx, animated: animated) + } + for (idx, item, _) in indicesAndItems { + self.insertItem(item, theme: self.theme, at: idx, animated: animated, callback: { [weak self] item in + self?.didChangeSelectedItem?(item) + }, menuItems: { [weak self] item in + guard let menuItems = self?.menuItems else { + return [] + } + return menuItems(item) + }) + } + for (idx, item, _) in updateIndices { + self.updateItem(item, theme: self.theme, at: idx, animated: animated) + } + if !deleteIndices.isEmpty || !indicesAndItems.isEmpty || !updateIndices.isEmpty { + layoutItems(animated: animated) + } + + + if let selectedItem = items.first(where: { $0.selected }), let selectedView = selectedItem.view { + let point = NSMakePoint(min(max(selectedView.frame.midX - frame.width / 2, 0), max(documentView.frame.width - frame.width, 0)), 0) + if point != scrollView.documentOffset, frame != .zero { + scrollView.clipView.scroll(to: point, animated: animated) + } + } + moveSelector(animated) + } + + public func scrollToSelected(animated: Bool) { + if let selectedItem = items.first(where: { $0.selected }), let selectedView = selectedItem.view { + let point = NSMakePoint(min(max(selectedView.frame.midX - frame.width / 2, 0), max(documentView.frame.width - frame.width, 0)), 0) + if point != scrollView.documentOffset, frame != .zero { + scrollView.clipView.scroll(to: point, animated: true) + } + } + } + + private func removeItem(at index: Int, animated: Bool) { + let view = self.items[index].view + self.items[index].view = nil + if animated, let view = view { + view.layer?.animateAlpha(from: 1, to: 0, duration: duration, removeOnCompletion: false, completion: { [weak view] _ in + view?.removeFromSuperview() + }) + } else { + view?.removeFromSuperview() + } + + self.items.remove(at: index) + } + private func updateItem(_ item: ScrollableSegmentItem, theme: ScrollableSegmentTheme, at index: Int, animated: Bool) { + item.view = self.items[index].view + item.view?.updateItem(item, theme: theme, animated: animated) + + self.items[index] = item + } + private func insertItem(_ item: ScrollableSegmentItem, theme: ScrollableSegmentTheme, at index: Int, animated: Bool, callback: @escaping(ScrollableSegmentItem)->Void, menuItems: @escaping(ScrollableSegmentItem)->[ContextMenuItem]) { + let view = SegmentItemView(item: item, theme: theme, callback: callback, startDraggingIfNeeded: { [weak self] item, view in + self?.startDraggingIfNeeded(item, view) + }, menuItems: menuItems) + view.setFrameSize(NSMakeSize(view.frame.width, frame.height)) + item.view = view + + var subviews = self.documentView.subviews + + subviews.insert(view, at: index) + self.documentView.subviews = subviews + self.items.insert(item, at: index) + + for (i, item) in self.items.enumerated() { + if i == index - 1, let v = item.view { + view.setFrameOrigin(NSMakePoint(v.frame.maxX, 0)) + } + } + } + + private var initialResortData: ResortData? + + private func startDraggingIfNeeded(_ item: ScrollableSegmentItem, _ view: Control) { + if let range = self.resortRange, let window = self.window { + if range.indexIn(item.index) { + initialResortData = ResortData(point: self.convert(window.mouseLocationOutsideOfEventStream, from: nil), item: item, view: view, viewPoint: view.frame.origin) + + view.set(handler: { [weak self] _ in + self?.applyLiveResort() + }, for: .MouseDragging) + + view.set(handler: { [weak self] _ in + self?.applyResort() + }, for: .LongMouseUp) + } + } + } + + public func contains(_ id: Int32) -> Bool { + return self.items.first(where: { $0.uniqueId == id }) != nil + } + + private func applyResort() { + if let data = initialResortData { + _ = data.view.removeLastHandler() + _ = data.view.removeLastHandler() + layoutItems(animated: true, completion: { [weak self] in + if let bestIndex = self?.bestIndex { + self?.resortHandler?(data.item.index, bestIndex) + } + self?.bestIndex = nil + self?.initialResortData = nil + }) + } + } + private var bestIndex: Int? = nil + private func applyLiveResort() { + if let data = initialResortData, let resortRange = resortRange, let window = self.window { + let currentPoint = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + let updatePoint = currentPoint.offsetBy(dx: -data.point.x, dy: -data.point.y) + data.view.setFrameOrigin(data.viewPoint.offsetBy(dx: updatePoint.x.rounded(), dy: 0)) + selectorView.frame = selectorFrame + + var bestIndex: Int = items.count - 1 + + var x: CGFloat = 0 + for (i, item) in self.items.enumerated() { + if let view = item.view { + x += view.frame.width + } + if x > currentPoint.x { + bestIndex = i + break + } + } + + bestIndex = max(resortRange.location, bestIndex) + if bestIndex != self.bestIndex { + self.bestIndex = bestIndex + x = 0 + for (i, item) in self.items.enumerated() { + if let view = item.view { + if i == bestIndex, bestIndex <= data.item.index { + x += data.view.frame.width + } + if item != data.item { + view.change(pos: NSMakePoint(x, view.frame.minY), animated: true) + x += view.frame.width + } + if i == bestIndex, bestIndex > data.item.index { + x += data.view.frame.width + } + } + } + selectorView.change(pos: selectorFrame.origin, animated: true) + } + if let event = NSApp.currentEvent { + scrollView.contentView.autoscroll(with: event) + } + } + } + + + private func layoutItems(animated: Bool, completion: (()->Void)? = nil) { + var x: CGFloat = 0 + var items = self.items + if let data = initialResortData, let bestIndex = self.bestIndex, bestIndex != data.item.index { + items.insert(items.remove(at: data.item.index), at: bestIndex) + } + + let insetBetween: CGFloat = self.insetBetween + + + x += insetBetween + for (i, item) in items.enumerated() { + if let view = item.view { + view._change(pos: NSMakePoint(x, 0), animated: animated, completion: { [weak self] completed in + if i == self?.bestIndex { + completion?() + } + }) + x += view.size.width + insetBetween + } + } + + documentView.change(size: NSMakeSize(max(x, frame.width), frame.height), animated: animated) + selectorView.change(pos: selectorFrame.origin, animated: animated) + } + + private func redraw() { + selectorView.theme = self.theme + borderView.backgroundColor = self.theme.border + backgroundColor = self.theme.background + scrollView.background = self.theme.background + for item in self.items { + item.view?.updateItem(item, theme: self.theme, animated: false) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/submodules/TGUIKit/TGUIKit/SearchView.swift b/submodules/TGUIKit/TGUIKit/SearchView.swift new file mode 100644 index 0000000000..df001f3fab --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SearchView.swift @@ -0,0 +1,574 @@ +// +// SearchView.swift +// TGUIKit +// +// Created by keepcoder on 27/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +public class SearchTextField: NSTextView { + + @available(OSX 10.12.2, *) + override public func makeTouchBar() -> NSTouchBar? { + return viewEnableTouchBar ? super.makeTouchBar() : nil + } + + override public func resignFirstResponder() -> Bool { + self.delegate?.textDidEndEditing?(Notification(name: NSControl.textDidChangeNotification)) + return super.resignFirstResponder() + } + + override public func becomeFirstResponder() -> Bool { + self.delegate?.textDidBeginEditing?(Notification(name: NSControl.textDidChangeNotification)) + return super.becomeFirstResponder() + } + + override public func paste(_ sender: Any?) { + + let text = NSPasteboard.general.string(forType: .string)?.nsstring + if let text = text { + var modified = text.replacingOccurrences(of: "\n", with: " ") + modified = text.replacingOccurrences(of: "\n", with: " ") + appendText(modified) + self.delegate?.textDidChange?(Notification(name: NSControl.textDidChangeNotification)) + } + } + +} + +public enum SearchFieldState { + case None; + case Focus; +} + +public struct SearchState : Equatable { + public let state:SearchFieldState + public let request:String + public let responder: Bool + public init(state:SearchFieldState, request:String?, responder: Bool = false) { + self.state = state + self.request = request ?? "" + self.responder = responder + } +} + +public final class SearchInteractions { + public let stateModified:(SearchState, Bool) -> Void + public let textModified:(SearchState) -> Void + public let responderModified:(SearchState) -> Void + public init(_ state:@escaping(SearchState, Bool)->Void, _ text:@escaping(SearchState)->Void, responderModified:@escaping(SearchState) -> Void = {_ in}) { + self.stateModified = state + self.textModified = text + self.responderModified = responderModified + } +} + +open class SearchView: OverlayControl, NSTextViewDelegate { + + public private(set) var state:SearchFieldState = .None + + + + private(set) public var input:NSTextView = SearchTextField() + + private var lock:Bool = false + + private let clear:ImageButton = ImageButton() + private let search:ImageView = ImageView() + private let progressIndicator:ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 18, 18)) + private let placeholder:TextViewLabel = TextViewLabel() + + private let animateContainer:View = View() + + public let inset:CGFloat = 10 + public let leftInset:CGFloat = 10.0 + + public var searchInteractions:SearchInteractions? + + private let _searchValue:ValuePromise = ValuePromise(SearchState(state: .None, request: nil), ignoreRepeated: true) + + public var searchValue: Signal { + return _searchValue.get() + } + public var shouldUpdateTouchBarItemIdentifiers: (()->[Any])? + + + private let inputContainer = View() + + public var isLoading:Bool = false { + didSet { + if oldValue != isLoading { + self.updateLoading() + needsLayout = true + } + } + } + + override open func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + + inputContainer.backgroundColor = .clear + input.textColor = presentation.search.textColor + input.backgroundColor = presentation.colors.background + placeholder.attributedString = .initialize(string: presentation.search.placeholder(), color: presentation.search.placeholderColor, font: .normal(.text)) + placeholder.backgroundColor = presentation.search.backgroundColor + self.backgroundColor = presentation.search.backgroundColor + placeholder.sizeToFit() + search.frame = NSMakeRect(0, 0, presentation.search.searchImage.backingSize.width, presentation.search.searchImage.backingSize.height) + search.image = presentation.search.searchImage + animateContainer.setFrameSize(NSMakeSize(placeholder.frame.width + placeholderTextInset, max(21, search.frame.height))) + + clear.set(image: presentation.search.clearImage, for: .Normal) + _ = clear.sizeToFit() + + placeholder.centerY(x: placeholderTextInset, addition: -1) + search.centerY(addition: -1) + input.insertionPointColor = presentation.search.textColor + progressIndicator.progressColor = presentation.colors.text + needsLayout = true + + } + + open var startTextInset: CGFloat { + return search.frame.width + inset / 2 + } + + open var placeholderTextInset: CGFloat { + return startTextInset + } + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self.backgroundColor = .grayBackground + self.layer?.cornerRadius = 10 + if #available(OSX 10.12.2, *) { + input.allowsCharacterPickerTouchBarItem = false + } + progressIndicator.isHidden = true +// progressIndicator.numberOfLines = 8 +// progressIndicator.innerMargin = 3; +// progressIndicator.widthOfLine = 3; +// progressIndicator.lengthOfLine = 6; + // input.isBordered = false + // input.isBezeled = false + input.focusRingType = .none + input.frame = self.bounds + input.autoresizingMask = [.width, .height] + input.backgroundColor = NSColor.clear + input.delegate = self + input.isRichText = false + + input.textContainer?.widthTracksTextView = true + input.textContainer?.heightTracksTextView = false + + // input.maxSize = NSMakeSize(100, .greatestFiniteMagnitude) + input.isHorizontallyResizable = false + input.isVerticallyResizable = false + + + //input.placeholderAttributedString = NSAttributedString.initialize(string: localizedString("SearchField.Search"), color: .grayText, font: .normal(.text), coreText: false) + + input.font = .normal(.text) + input.textColor = .text + input.isHidden = true + input.drawsBackground = false + + animateContainer.backgroundColor = .clear + + placeholder.sizeToFit() + animateContainer.addSubview(placeholder) + + animateContainer.addSubview(search) + + self.animateContainer.setFrameSize(NSMakeSize(NSWidth(placeholder.frame) + search.frame.width + inset / 2, max(21, search.frame.height))) + + placeholder.centerY(x: NSWidth(search.frame) + inset / 2, addition: -1) + search.centerY(addition: -1) + + inputContainer.addSubview(input) + addSubview(animateContainer) + addSubview(inputContainer) + inputContainer.backgroundColor = .clear + clear.backgroundColor = .clear + + + clear.set(handler: { [weak self] _ in + self?.cancelSearch() + }, for: .Click) + + addSubview(clear) + + clear.isHidden = true + + animateContainer.center() + + self.set(handler: {[weak self] (event) in + if let strongSelf = self { + strongSelf.change(state: .Focus , true) + } + }, for: .Click) + + updateLocalizationAndTheme(theme: presentation) + + + + progressIndicator.set(handler: { [weak self] _ in + self?.cancelSearch() + }, for: .Click) + + } + + @available(OSX 10.12.2, *) + public func textView(_ textView: NSTextView, shouldUpdateTouchBarItemIdentifiers identifiers: [NSTouchBarItem.Identifier]) -> [NSTouchBarItem.Identifier] { + return self.shouldUpdateTouchBarItemIdentifiers?() as? [NSTouchBarItem.Identifier] ?? identifiers + } + + open func cancelSearch() { + if self.query.isEmpty { + self.change(state: .None, true) + } else { + self.setString("") + } + } + + open func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool { + if let trimmed = replacementString?.trimmed, trimmed.isEmpty, affectedCharRange.min == 0 && affectedCharRange.max == 0, textView.string.isEmpty { + return false + } + if replacementString == "\n" { + return false + } + return true + } + + + + open func textDidChange(_ notification: Notification) { + + let trimmed = input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")) + if trimmed != input.string { + self.setString(trimmed) + return + } + + let value = SearchState(state: state, request: trimmed, responder: self.input == window?.firstResponder) + searchInteractions?.textModified(value) + _searchValue.set(value) + + + let pHidden = !input.string.isEmpty + if placeholder.isHidden != pHidden { + placeholder.isHidden = pHidden + } + + needsLayout = true + + let iHidden = !(state == .Focus && !input.string.isEmpty) + if input.isHidden != iHidden { + // input.isHidden = iHidden + window?.makeFirstResponder(input) + } + } + + open override func mouseUp(with event: NSEvent) { + if isLoading { + let point = convert(event.locationInWindow, from: nil) + if NSPointInRect(point, progressIndicator.frame) { + setString("") + } else { + super.mouseUp(with: event) + } + } else { + super.mouseUp(with: event) + } + } + + + public func textViewDidChangeSelection(_ notification: Notification) { + if let storage = input.textStorage { + let size = storage.size() + + let inputInset = placeholderTextInset + 8 + + let defWidth = frame.width - inputInset - inset - clear.frame.width - 10 + // input.sizeToFit() + input.setFrameSize(max(size.width + 10, defWidth), input.frame.height) + // inputContainer.setFrameSize(inputContainer.frame.width, input.frame.height) + if let layout = input.layoutManager, !input.string.isEmpty { + let index = max(0, input.selectedRange().max - 1) + let point = layout.location(forGlyphAt: layout.glyphIndexForCharacter(at: index)) + + let additionalInset: CGFloat + if index + 2 < input.string.length { + let nextPoint = layout.location(forGlyphAt: layout.glyphIndexForCharacter(at: index + 2)) + additionalInset = nextPoint.x - point.x + } else { + additionalInset = 8 + } + + if defWidth < size.width && point.x > defWidth { + input.setFrameOrigin(floorToScreenPixels(backingScaleFactor, defWidth - point.x - additionalInset), input.frame.minY) + if input.frame.maxX < inputContainer.frame.width { + input.setFrameOrigin(inputContainer.frame.width - input.frame.width + 4, input.frame.minY) + } + } else { + input.setFrameOrigin(0, input.frame.minY) + } + } else { + input.setFrameOrigin(0, input.frame.minY) + } + needsLayout = true + } + } + + open func textDidEndEditing(_ notification: Notification) { + didResignResponder() + } + + open func textDidBeginEditing(_ notification: Notification) { + didBecomeResponder() + } + + open var isEmpty: Bool { + return query.isEmpty + } + + open func didResignResponder() { + let value = SearchState(state: state, request: self.query, responder: false) + searchInteractions?.responderModified(value) + _searchValue.set(value) + if isEmpty { + change(state: .None, true) + } + + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + + open func didBecomeResponder() { + let value = SearchState(state: state, request: self.query, responder: true) + searchInteractions?.responderModified(SearchState(state: state, request: self.query, responder: true)) + _searchValue.set(value) + + change(state: .Focus, true) + + self.kitWindow?.set(escape: {[weak self] () -> KeyHandlerResult in + if let strongSelf = self { + strongSelf.setString("") + return strongSelf.changeResponder() ? .invoked : .rejected + } + return .rejected + + }, with: self, priority: .modal) + + self.kitWindow?.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.state == .Focus { + return .invokeNext + } + return .rejected + }, with: self, for: .RightArrow, priority: .modal) + + self.kitWindow?.set(handler: { [weak self] () -> KeyHandlerResult in + if self?.state == .Focus { + return .invokeNext + } + return .rejected + }, with: self, for: .LeftArrow, priority: .modal) + + self.kitWindow?.set(responder: {[weak self] () -> NSResponder? in + return self?.input + }, with: self, priority: .modal) + } + + + open override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + } + + + + open func change(state:SearchFieldState, _ animated:Bool) -> Void { + + if state != self.state && !lock { + self.state = state + + let text = input.string.trimmingCharacters(in: CharacterSet(charactersIn: "\n\r")) + let value = SearchState(state: state, request: state == .None ? nil : text, responder: self.input == window?.firstResponder) + searchInteractions?.stateModified(value, animated) + + _searchValue.set(value) + + lock = true + + if state == .Focus { + + window?.makeFirstResponder(input) + + let inputInset = placeholderTextInset + 8 + + let fromX:CGFloat = animateContainer.frame.minX + animateContainer.centerY(x: leftInset) + + inputContainer.frame = NSMakeRect(inputInset, animateContainer.frame.minY + 2, frame.width - inputInset - inset - clear.frame.width - 6, animateContainer.frame.height) + input.frame = inputContainer.bounds + + input.isHidden = false + + if animated { + + inputContainer.layer?.animate(from: fromX as NSNumber, to: inputContainer.frame.minX as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration) + + animateContainer.layer?.animate(from: fromX as NSNumber, to: leftInset as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion: true, additive: false, completion: {[weak self] (complete) in + self?.input.isHidden = false + if self?.window?.firstResponder != self?.input { + self?.window?.makeFirstResponder(self?.input) + } + self?.lock = false + }) + } else { + self.input.isHidden = false + self.window?.makeFirstResponder(self.input) + self.lock = false + } + + + clear.isHidden = false + clear.layer?.opacity = 1.0 + if animated { + clear.layer?.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration) + } + } + + if state == .None { + + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + + self.input.isHidden = true + self.input.string = "" + self.window?.makeFirstResponder(nil) + self.placeholder.isHidden = false + + animateContainer.center() + if animated { + animateContainer.layer?.animate(from: leftInset as NSNumber, to: NSMinX(animateContainer.frame) as NSNumber, keyPath: "position.x", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion: true) + + clear.layer?.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "opacity", timingFunction: animationStyle.function, duration: animationStyle.duration, removeOnCompletion:true, additive:false, completion: {[weak self] (complete) in + self?.clear.isHidden = true + self?.lock = false + }) + } else { + clear.isHidden = true + lock = false + } + + clear.layer?.opacity = 0.0 + } + updateLoading() + self.needsLayout = true + } + + } + + open override func viewWillMove(toWindow newWindow: NSWindow?) { + if newWindow == nil { + if isEmpty { + change(state: .None, false) + } + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + } + + + func updateLoading() { + if isLoading && state == .Focus { + if progressIndicator.superview == nil { + addSubview(progressIndicator) + } + progressIndicator.isHidden = false + clear.isHidden = true + rightAccessory.isHidden = true + progressIndicator.animates = true + } else { + progressIndicator.animates = false + progressIndicator.removeFromSuperview() + progressIndicator.isHidden = true + clear.isHidden = self.state == .None || !clearVisibility + rightAccessory.isHidden = self.state == .None + } + if window?.firstResponder == input { + window?.makeFirstResponder(input) + } + } + private var clearVisibility: Bool = true + + public func updateClearVisibility(_ visible: Bool) { + clearVisibility = visible + clear.isHidden = !visible || isLoading + } + + open var rightAccessory: NSView { + return clear + } + + + open override func layout() { + super.layout() + switch state { + case .None: + animateContainer.center() + case .Focus: + animateContainer.centerY(x: leftInset) + } + placeholder.centerY(addition: -1) + clear.centerY(x: frame.width - inset - clear.frame.width) + progressIndicator.centerY(x: frame.width - inset - progressIndicator.frame.width + 2) + inputContainer.setFrameSize(NSMakeSize(frame.width - (inset * 3) - (clear.frame.width * 2), inputContainer.frame.height)) + inputContainer.setFrameOrigin(placeholderTextInset + 8, inputContainer.frame.minY) + search.centerY(addition: -1) + } + + public func changeResponder(_ animated:Bool = true) -> Bool { + if state == .Focus { + cancelSearch() + } else { + change(state: .Focus, animated) + } + return true + } + + deinit { + self.kitWindow?.removeAllHandlers(for: self) + self.kitWindow?.removeObserver(for: self) + } + + public var query:String { + return self.input.string + } + + open override func change(size: NSSize, animated: Bool = true, _ save: Bool = true, removeOnCompletion: Bool = false, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + super.change(size: size, animated: animated, save, duration: duration, timingFunction: timingFunction) + clear.change(pos: NSMakePoint(frame.width - inset - clear.frame.width, clear.frame.minY), animated: animated) + } + + + public func setString(_ string:String) { + self.input.string = string + textDidChange(Notification(name: NSText.didChangeNotification)) + needsLayout = true + } + + public func cancel(_ animated:Bool) -> Void { + change(state: .None, animated) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/SectionViewController.swift b/submodules/TGUIKit/TGUIKit/SectionViewController.swift new file mode 100644 index 0000000000..837ee0b6ed --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SectionViewController.swift @@ -0,0 +1,392 @@ +// +// SectionViewController.swift +// TGUIKit +// +// Created by keepcoder on 03/08/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +private final class SectionControllerArguments { + let select:(Int)->Void + init(select:@escaping(Int)->Void) { + self.select = select + } +} + +public class SectionControllerView : View { + fileprivate let header: View = View() + fileprivate let selector:View = View() + fileprivate let container: View = View() + private weak var current: ViewController? + + public var selectorIndex:Int = 0 { + didSet { + if selectorIndex != oldValue { + var index:Int = 0 + for hContainer in header.subviews { + for t in hContainer.subviews { + if let t = t as? TextView { + t.update(TextViewLayout(.initialize(string: t.layout?.attributedString.string, color: selectorIndex == index ? presentation.colors.accent : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle)) + } + + } + index += 1 + } + } + } + } + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(header) + addSubview(container) + addSubview(selector) + updateLocalizationAndTheme(theme: presentation) + needsLayout = true + } + + fileprivate func layout(sections: [SectionControllerItem], selected: Int, hasHeaderView: Bool, arguments: SectionControllerArguments) { + header.removeAllSubviews() + header.isHidden = !hasHeaderView + self.selectorIndex = selected + for i in 0 ..< sections.count { + let section = sections[i] + let headerContainer = Control(frame: NSMakeRect(0, 0, 0, 50)) + let title: TextView = TextView() + title.isSelectable = false + title.userInteractionEnabled = false + title.backgroundColor = presentation.colors.background + title.update(TextViewLayout(.initialize(string: section.title(), color: i == selected ? presentation.colors.accent : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle)) + headerContainer.addSubview(title) + header.addSubview(headerContainer) + headerContainer.border = [.Bottom] + headerContainer.set(handler: { _ in + arguments.select(i) + }, for: .Click) + } + needsLayout = true + } + + + fileprivate func select(controller: ViewController, index: Int, animated: Bool, notifyApper: Bool = true) { + let previousIndex = self.selectorIndex + let previous = self.current + self.current = controller + selectorIndex = index + + controller.view.frame = container.bounds + if notifyApper { + previous?.viewWillDisappear(animated) + } + + let duration: Double = 0.2 + + container.addSubview(controller.view) + + if animated { + CATransaction.begin() + let container = header.subviews[index] + selector.change(pos: NSMakePoint(container.frame.minX, selector.frame.minY), animated: animated, duration: duration, timingFunction: .spring) + + + let pto: NSPoint + let nfrom: NSPoint + + + if previousIndex < index { + pto = NSMakePoint(-container.frame.width, 0) + nfrom = NSMakePoint(container.frame.width, 0) + } else { + pto = NSMakePoint(container.frame.width, 0) + nfrom = NSMakePoint(-container.frame.width, 0) + } + + previous?.view._change(pos: pto, animated: animated, duration: duration, timingFunction: CAMediaTimingFunctionName.spring, completion: { [weak previous, weak controller] complete in + if complete { + previous?.view.removeFromSuperview() + previous?.viewDidDisappear(animated) + controller?.viewDidAppear(animated) + } + }) + controller.view.layer?.animatePosition(from: nfrom, to: NSZeroPoint, duration: duration, timingFunction: CAMediaTimingFunctionName.spring) + CATransaction.commit() + } else { + container.removeAllSubviews() + previous?.viewDidDisappear(animated) + container.addSubview(controller.view) + controller.viewDidAppear(animated) + } + needsLayout = true + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.backgroundColor = presentation.colors.background + selector.backgroundColor = presentation.colors.accent + container.backgroundColor = presentation.colors.background + var index:Int = 0 + for hContainer in header.subviews { + hContainer.background = presentation.colors.background + for t in hContainer.subviews { + if let t = t as? TextView { + let layout = TextViewLayout(.initialize(string: t.layout?.attributedString.string, color: selectorIndex == index ? presentation.colors.accent : presentation.colors.grayText, font: .medium(.title)), maximumNumberOfLines: 1, truncationType: .middle) + layout.measure(width: hContainer.frame.width - 20) + t.update(layout) + t.center() + } + + t.background = presentation.colors.background + } + index += 1 + } + } + + public override func layout() { + super.layout() + header.setFrameSize(NSMakeSize(frame.width, header.isHidden ? 0 : 50.0)) + + let width = floorToScreenPixels(backingScaleFactor, frame.width / CGFloat(max(header.subviews.count, 3))) + + selector.frame = NSMakeRect(CGFloat(selectorIndex) * width, header.frame.height - .borderSize, width, .borderSize) + container.frame = NSMakeRect(0, header.frame.maxY, frame.width, frame.height - header.frame.height) + container.subviews.first?.frame = container.bounds + + var x:CGFloat = 0 + for i in 0 ..< header.subviews.count { + let hContainer = header.subviews[i] + let width = i == header.subviews.count - 1 ? frame.width - x : width + hContainer.frame = NSMakeRect(x, 0, width, hContainer.frame.height) + if let textView = hContainer.subviews.first as? TextView { + textView.layout?.measure(width: width - 10) + textView.update(textView.layout) + textView.center() + } + x += width + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public class SectionControllerItem { + let title: ()->String + let controller: ViewController + public init(title: @escaping()->String, controller: ViewController) { + self.title = title + self.controller = controller + } +} + + +public class SectionViewController: GenericViewController { + + private var sections:[SectionControllerItem] = [] + public var selectedSection:SectionControllerItem + public private(set) var selectedIndex: Int = -1 + private let disposable = MetaDisposable() + + public var selectionUpdateHandler:((Int)->Void)? + + public func addSection(_ section: SectionControllerItem) { + sections.append(section) + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + let arguments = SectionControllerArguments { [weak self] index in + self?.select(index, true) + } + genericView.layout(sections: sections, selected: selectedIndex, hasHeaderView: self.hasHeaderView, arguments: arguments) + genericView.updateLocalizationAndTheme(theme: theme) + } + + deinit { + disposable.dispose() + } + + public func select(_ index:Int, _ animated: Bool, notifyApper: Bool = true) { + if selectedIndex != index || !animated { + selectedSection = sections[index] + sections[index].controller._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) + sections[index].controller.loadViewIfNeeded() + let controller = sections[index].controller + selectedIndex = index + selectionUpdateHandler?(index) + if notifyApper { + sections[index].controller.viewWillAppear(animated) + } + disposable.set((sections[index].controller.ready.get() |> filter {$0} |> take(1)).start(next: { [weak self, weak controller] ready in + if let strongSelf = self, let controller = controller { + strongSelf.genericView.select(controller: controller, index: index, animated: animated, notifyApper: notifyApper) + } + })) + } + } + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + selectedSection.controller._frameRect = NSMakeRect(0, 0, frame.width, frame.height - 50) + selectedSection.controller.viewWillAppear(animated) + self.ready.set(sections[selectedIndex].controller.ready.get()) + } + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + selectedSection.controller.viewWillDisappear(animated) + window?.remove(object: self, for: .Tab) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + selectedSection.controller.viewDidAppear(animated) + + window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + if !self.sections.isEmpty { + var index:Int = self.selectedIndex + if index == self.sections.count - 1 { + index = 0 + } else { + index += 1 + } + self.select(index, true) + } + + return .invoked + }, with: self, for: .Tab, priority: .high) + + + window?.add(swipe: { [weak self] direction, _ -> SwipeHandlerResult in + guard let `self` = self, !self.sections.isEmpty else {return .nothing} + + if !self.selectedSection.controller.supportSwipes { + return .nothing + } + + switch direction { + case let .left(state): + + switch state { + case .start: + if self.selectedIndex > 0 { + let new = self.sections[self.selectedIndex - 1].controller + new._frameRect = self.genericView.container.bounds + new.view.frame = self.genericView.container.bounds + new.viewWillAppear(false) + self.genericView.container.addSubview(new.view, positioned: .below, relativeTo: self.selectedSection.controller.view) + + return .success(new) + } + case let .swiping(delta, controller): + + let delta = min(max(0, delta), controller.frame.width) + + // self.genericView.selector.change(pos: NSMakePoint(container.frame.minX, genericView.selector.frame.minY), animated: animated, timingFunction: CAMediaTimingFunctionName.spring) + + let selectorFrame = self.genericView.header.subviews[self.selectedIndex].frame + + self.genericView.selector.setFrameOrigin(NSMakePoint(selectorFrame.minX - selectorFrame.width * (delta / controller.frame.width), self.genericView.selector.frame.minY)) + self.selectedSection.controller.frame = NSMakeRect(delta, self.selectedSection.controller.frame.minY, self.selectedSection.controller.frame.width, self.selectedSection.controller.frame.height) + controller.frame = NSMakeRect(delta - controller.frame.width, controller.frame.minY, controller.frame.width, controller.frame.height) + return .deltaUpdated(available: delta) + case let .success(_, controller): + controller.view._change(pos: NSMakePoint(0, controller.frame.minY), animated: true) + let selectorFrame = self.genericView.header.subviews[self.selectedIndex - 1].frame + self.genericView.selector.change(pos: NSMakePoint(selectorFrame.minX, self.genericView.selector.frame.minY), animated: animated) + self.selectedSection.controller.view._change(pos: NSMakePoint(self.selectedSection.controller.frame.width, self.selectedSection.controller.frame.minY), animated: true, completion: { [weak self] completed in + if completed, let index = self?.sections.lastIndex(where: {$0.controller == controller}) { + self?.select(index, false, notifyApper: false) + } + }) + case let .failed(_, controller): + controller.view._change(pos: NSMakePoint(-controller.frame.width, controller.frame.minY), animated: true) + let selectorFrame = self.genericView.header.subviews[self.selectedIndex].frame + self.genericView.selector.change(pos: NSMakePoint(selectorFrame.minX, self.genericView.selector.frame.minY), animated: animated) + self.selectedSection.controller.view._change(pos: NSMakePoint(0, self.selectedSection.controller.frame.minY), animated: true, completion: { [weak controller] completed in + if completed { + controller?.removeFromSuperview() + } + }) + } + + break + case let .right(state): + switch state { + case .start: + if self.selectedIndex < self.sections.count - 1 { + let new = self.sections[self.selectedIndex + 1].controller + new._frameRect = self.genericView.container.bounds + new.view.frame = self.genericView.container.bounds + new.viewWillAppear(false) + self.genericView.container.addSubview(new.view, positioned: .below, relativeTo: self.selectedSection.controller.view) + return .success(new) + } + case let .swiping(delta, controller): + + let delta = min(max(0, delta), controller.frame.width) + + + let selectorFrame = self.genericView.header.subviews[self.selectedIndex].frame + self.genericView.selector.setFrameOrigin(NSMakePoint(selectorFrame.minX + selectorFrame.width * ( delta / controller.frame.width), self.genericView.selector.frame.minY)) + + self.selectedSection.controller.frame = NSMakeRect(-delta, self.selectedSection.controller.frame.minY, self.selectedSection.controller.frame.width, self.selectedSection.controller.frame.height) + controller.frame = NSMakeRect(controller.frame.width - delta, controller.frame.minY, controller.frame.width, controller.frame.height) + return .deltaUpdated(available: delta) + case let .success(_, controller): + controller.view._change(pos: NSMakePoint(0, controller.frame.minY), animated: true) + let selectorFrame = self.genericView.header.subviews[self.selectedIndex + 1].frame + self.genericView.selector.change(pos: NSMakePoint(selectorFrame.minX, self.genericView.selector.frame.minY), animated: true) + self.selectedSection.controller.view._change(pos: NSMakePoint(-self.selectedSection.controller.frame.width, self.selectedSection.controller.frame.minY), animated: true, completion: { [weak self] completed in + if completed, let index = self?.sections.lastIndex(where: {$0.controller == controller}) { + self?.select(index, false, notifyApper: false) + } + }) + case let .failed(_, controller): + controller.view._change(pos: NSMakePoint(controller.frame.width, controller.frame.minY), animated: true) + let selectorFrame = self.genericView.header.subviews[self.selectedIndex].frame + self.genericView.selector.change(pos: NSMakePoint(selectorFrame.minX, self.genericView.selector.frame.minY), animated: true) + self.selectedSection.controller.view._change(pos: NSMakePoint(0, self.selectedSection.controller.frame.minY), animated: true, completion: { [weak controller] completed in + if completed { + controller?.removeFromSuperview() + } + }) + } + case .none: + break + } + + return .nothing + + }, with: self.genericView, identifier: SwipeIdentifier("section-swipe")) + + } + override public func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + selectedSection.controller.viewDidDisappear(animated) + } + + public override func viewDidLoad() { + super.viewDidLoad() + + let arguments = SectionControllerArguments { [weak self] index in + self?.select(index, true) + } + genericView.layout(sections: sections, selected: selectedIndex, hasHeaderView: self.hasHeaderView, arguments: arguments) + select(selectedIndex, false) + } + + private let hasHeaderView: Bool + + public init(sections: [SectionControllerItem], selected: Int = 0, hasHeaderView: Bool = true) { + assert(!sections.isEmpty) + self.sections = sections + self.selectedSection = sections[selected] + self.selectedIndex = selected + self.hasHeaderView = hasHeaderView + super.init() + bar = .init(height: 0) + } + +} diff --git a/submodules/TGUIKit/TGUIKit/SegmentController.swift b/submodules/TGUIKit/TGUIKit/SegmentController.swift new file mode 100644 index 0000000000..f96a8595bd --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SegmentController.swift @@ -0,0 +1,225 @@ +// +// SegmentController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 21/06/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa + +public class SegmentedItem { + let title:String + let handler: ()->Void + public init(title:String, handler:@escaping()->Void) { + self.title = title + self.handler = handler + } +} + +private enum SegmentItemPosition { + case left + case right + case inner +} + +public struct SegmentTheme { + let backgroundColor: NSColor + let foregroundColor: NSColor + let textColor: NSColor + public init(backgroundColor: NSColor, foregroundColor: NSColor, textColor: NSColor) { + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.textColor = textColor + } +} + +private final class SegmentItemView : Control { + let item: SegmentedItem + let position: SegmentItemPosition + let selected: Bool + init(item: SegmentedItem, selected: Bool, theme: SegmentTheme, position: SegmentItemPosition, select:@escaping()->Void) { + self.item = item + self.theme = theme + self.position = position + self.selected = selected + super.init(frame: NSZeroRect) + + set(handler: { _ in + select() + }, for: .SingleClick) + } + + var theme: SegmentTheme { + didSet { + needsDisplay = true + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + override func layout() { + super.layout() + needsDisplay = true + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + + + switch position { + case .left: + ctx.round(bounds, flags: [.bottom, .top, .left]) + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(bounds) + + ctx.round(NSMakeRect(.borderSize , .borderSize, bounds.width - (.borderSize / 2), bounds.height - .borderSize), flags: [.bottom, .top, .left]) + ctx.setFillColor(theme.backgroundColor.cgColor) + ctx.fill(bounds) + case .right: + ctx.round(bounds, flags: [.bottom, .top, .right]) + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(bounds) + + ctx.round(NSMakeRect(.borderSize / 2, .borderSize, bounds.width - .borderSize , bounds.height - .borderSize ), flags: [.bottom, .top, .right]) + ctx.setFillColor(theme.backgroundColor.cgColor) + ctx.fill(bounds) + case .inner: + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, .borderSize / 2, frame.height)) + + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(NSMakeRect(0, 0, frame.width, .borderSize)) + + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(NSMakeRect(frame.width - .borderSize / 2, 0, .borderSize / 2, frame.height)) + + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize)) + } + + + + if selected { + ctx.setFillColor(theme.foregroundColor.cgColor) + ctx.fill(bounds) + } + + let text = TextNode.layoutText(.initialize(string: item.title, color: selected ? theme.backgroundColor : theme.foregroundColor, font: .normal(12)), selected ? theme.foregroundColor : theme.backgroundColor, 1, .end, NSMakeSize(frame.width - 10, frame.height), nil, false, .center) + + let f = focus(text.0.size) + text.1.draw(f, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: selected ? theme.foregroundColor : theme.backgroundColor) + } +} + +private class SegmentedControlView: View { + + public var theme: SegmentTheme = SegmentTheme(backgroundColor: presentation.colors.background, foregroundColor: presentation.colors.accent, textColor: presentation.colors.accent) { + didSet { + for subview in subviews.compactMap({ $0 as? SegmentItemView}) { + subview.theme = theme + } + } + } + + func update(items: [SegmentedItem], selected: Int, select: @escaping(Int)->Void) -> Void { + self.removeAllSubviews() + for i in 0 ..< items.count { + let view = SegmentItemView(item: items[i], selected: selected == i, theme: theme, position: i == 0 ? .left : i == items.count - 1 ? .right : .inner, select: { select(i) }) + addSubview(view) + } + + needsLayout = true + } + + override func layout() { + super.layout() + guard !subviews.isEmpty else {return} + + var x: CGFloat = 0 + let itemWidth = floor(frame.width / CGFloat(subviews.count)) + for view in subviews { + view.frame = NSMakeRect(x, 0, itemWidth, frame.height) + x += itemWidth + } + } + +} + + +public class SegmentController: ViewController { + + private var items: [SegmentedItem] = [] + private var selected: Int = 0 + + private var genericView: SegmentedControlView { + return self.view as! SegmentedControlView + } + + public override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + bar = .init(height: 0) + } + + public var theme: SegmentTheme { + get { + return genericView.theme + } + set { + genericView.theme = newValue + } + } + + public override func viewDidLoad() { + super.viewDidLoad() + readyOnce() + } + + private func select(_ index: Int) { + self.selected = index + items[index].handler() + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + + public func add(segment: SegmentedItem) -> Void { + items.append(segment) + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + public func segment(at index:Int) -> SegmentedItem { + return items[index] + } + public func replace(segment: SegmentedItem, at index:Int) -> Void { + items[index] = segment + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + public func insert(segment: SegmentedItem, at index: Int) -> Void { + items.insert(segment, at: index) + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + public func remove(at index: Int) -> Void { + items.remove(at: index) + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + + public func set(selected index: Int) -> Void { + selected = index + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + + public func removeAll() -> Void { + selected = 0 + items.removeAll() + genericView.update(items: items, selected: selected, select: { [weak self] index in self?.select(index)}) + } + + override public func viewClass() -> AnyClass { + return SegmentedControlView.self + } +} diff --git a/TGUIKit/TGUIKit/SelectingControl.swift b/submodules/TGUIKit/TGUIKit/SelectingControl.swift similarity index 84% rename from TGUIKit/TGUIKit/SelectingControl.swift rename to submodules/TGUIKit/TGUIKit/SelectingControl.swift index c7408f6a20..2501025e50 100644 --- a/TGUIKit/TGUIKit/SelectingControl.swift +++ b/submodules/TGUIKit/TGUIKit/SelectingControl.swift @@ -16,17 +16,18 @@ public class SelectingControl: Control { private var unselectedImage:CGImage private var selectedImage:CGImage - public init(unselectedImage:CGImage, selectedImage:CGImage) { + public init(unselectedImage:CGImage, selectedImage:CGImage, selected: Bool = false) { self.unselectedImage = unselectedImage self.selectedImage = selectedImage - imageView.image = unselectedImage + imageView.image = selected ? selectedImage : unselectedImage super.init(frame:NSMakeRect(0, 0, max(unselectedImage.backingSize.width ,selectedImage.backingSize.width ), max(unselectedImage.backingSize.height,selectedImage.backingSize.height ))) userInteractionEnabled = false - + self.isSelected = selected addSubview(imageView) } + public override func layout() { super.layout() imageView.setFrameSize(unselectedImage.backingSize) @@ -35,6 +36,7 @@ public class SelectingControl: Control { public func set(selected:Bool, animated:Bool = false) { if selected != isSelected { + self.isSelected = selected imageView.image = selected ? selectedImage : unselectedImage if animated { @@ -51,10 +53,8 @@ public class SelectingControl: Control { fatalError("init(frame:) has not been implemented") } - override public func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - // Drawing code here. + public override func draw(_ layer: CALayer, in ctx: CGContext) { + } } diff --git a/submodules/TGUIKit/TGUIKit/SelectionRectView.swift b/submodules/TGUIKit/TGUIKit/SelectionRectView.swift new file mode 100644 index 0000000000..6ff258f804 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SelectionRectView.swift @@ -0,0 +1,391 @@ +// +// SelectionRectView.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 02/10/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +public enum SelectionRectDimensions { + case none + case original + case square + case x2_3 + case x3_5 + case x3_4 + case x4_5 + case x5_7 + case x9_16 + case x16_9 + public var description: String { + switch self { + case .none: + return localizedString("SelectAreaControl.Dimension.None") + case .original: + return localizedString("SelectAreaControl.Dimension.Original") + case .square: + return localizedString("SelectAreaControl.Dimension.Square") + case .x2_3: + return "2x3" + case .x3_5: + return "3x5" + case .x3_4: + return "3x4" + case .x4_5: + return "4x5" + case .x5_7: + return "5x7" + case .x9_16: + return "9x16" + case .x16_9: + return "16x9" + } + } + + func aspectRation(_ size: NSSize) -> CGFloat { + switch self { + case .none: + return 0 + case .original: + return size.height / size.width + case .square: + return 1 + case .x2_3: + return 3 / 2 + case .x3_5: + return 5 / 3 + case .x3_4: + return 4 / 3 + case .x4_5: + return 5 / 4 + case .x5_7: + return 7 / 5 + case .x9_16: + return 16 / 9 + case .x16_9: + return 9 / 16 + } + } + + fileprivate func applyRect(_ rect: NSRect, for corner: SelectingCorner?, areaSize: NSSize, force: Bool = false) -> NSRect { + let aspectRatio = self.aspectRation(areaSize) + var newCropRect = rect + if aspectRatio > 0, corner != nil || force { + newCropRect.size.height = rect.width * aspectRatio + } else { + return rect + } + + let currentCenter = NSMakePoint(rect.midX, rect.midY) + + if (newCropRect.size.height > areaSize.height) + { + newCropRect.size.height = areaSize.height; + newCropRect.size.width = newCropRect.height / aspectRatio; + } + + if let corner = corner { + switch corner { + case .topLeft, .topRight: + newCropRect.origin.y += (rect.height - newCropRect.height) + default: + break + } + } else { + newCropRect.origin.x = currentCenter.x - newCropRect.width / 2; + newCropRect.origin.y = currentCenter.y - newCropRect.height / 2; + } + + + +// if newCropRect.maxY > areaSize.height { +// newCropRect.origin.x = areaSize.width - newCropRect.width; +// } +// +// if (newCropRect.maxY > areaSize.height) { +// newCropRect.origin.y = areaSize.height - newCropRect.size.height; +// } +// +// + + return newCropRect + } + + public static var all: [SelectionRectDimensions] { + return [.original, .square, .x2_3, .x3_5, .x3_4, .x4_5, .x5_7, .x9_16, .x16_9] + } +} + +public enum SelectingCorner { + case topLeft + case topRight + case bottomLeft + case bottomRight +} + +private func generateCorner(_ corner: SelectingCorner, _ color: NSColor) -> CGImage { + return generateImage(NSMakeSize(20, 20), contextGenerator: { size, ctx in + ctx.clear(NSMakeRect(0, 0, size.width, size.height)) + + ctx.setFillColor(color.cgColor) + switch corner { + case .bottomLeft: + ctx.fill(NSMakeRect(0, 0, 2, size.height)) + ctx.fill(NSMakeRect(0, 0, size.width, 2)) + case .bottomRight: + ctx.fill(NSMakeRect(size.width - 2, 0, 2, size.height)) + ctx.fill(NSMakeRect(0, 0, size.width, 2)) + case .topLeft: + ctx.fill(NSMakeRect(0, 0, 2, size.height)) + ctx.fill(NSMakeRect(0, size.height - 2, size.width, 2)) + case .topRight: + ctx.fill(NSMakeRect(size.width - 2, 0, 2, size.height)) + ctx.fill(NSMakeRect(0, size.height - 2, size.width, 2)) + + } + })! +} + +public func generateSelectionAreaCorners(_ color: NSColor) -> (topLeft: CGImage, topRight: CGImage, bottomLeft: CGImage, bottomRight: CGImage) { + return (topLeft: generateCorner(.topLeft, color), topRight: generateCorner(.topRight, color), bottomLeft: generateCorner(.bottomLeft, color), bottomRight: generateCorner(.bottomRight, color)) +} + +public class SelectionRectView: View { + + private let topLeftCorner: ImageButton = ImageButton() + private let topRightCorner: ImageButton = ImageButton() + private let bottomLeftCorner: ImageButton = ImageButton() + private let bottomRightCorner: ImageButton = ImageButton() + + public var minimumSize: NSSize = NSMakeSize(40, 40) + public var isCircleCap: Bool = false + private var _updatedRect: ValuePromise = ValuePromise(ignoreRepeated: true) + public var updatedRect: Signal { + return _updatedRect.get() + } + + public var dimensions: SelectionRectDimensions = .none { + didSet { + if oldValue != dimensions && selectedRect != NSZeroRect { + // applyRect(selectedRect, force: true) + } + } + } + + private var startSelectedRect: NSPoint? = nil + private var moveSelectedRect: NSPoint? = nil + + public private(set) var selectedRect: NSRect = NSZeroRect { + didSet { + updateRectLayout() + _updatedRect.set(selectedRect) + } + } + + public var topLeftPosition: NSPoint { + return topLeftCorner.frame.origin + } + public var topRightPosition: NSPoint { + return topRightCorner.frame.origin + } + public var bottomLeftPosition: NSPoint { + return bottomLeftCorner.frame.origin + } + public var bottomRightPosition: NSPoint { + return bottomRightCorner.frame.origin + } + + public var isWholeSelected: Bool { + let rect = NSMakeRect(0, 0, frame.width, frame.height) + return rect == selectedRect + } + + private var highlighedRect: NSRect { + return NSMakeRect(max(1, selectedRect.minX), max(1, selectedRect.minY), min(selectedRect.width, frame.width - 2), min(selectedRect.height, frame.height - 2)) + } + + private func updateRectLayout() { + topLeftCorner.setFrameOrigin(highlighedRect.minX - 2, highlighedRect.minY - 2) + topRightCorner.setFrameOrigin(highlighedRect.maxX - topRightCorner.frame.width + 2, highlighedRect.minY - 2) + + bottomLeftCorner.setFrameOrigin(highlighedRect.minX - 2, highlighedRect.maxY - bottomLeftCorner.frame.height + 2) + bottomRightCorner.setFrameOrigin(highlighedRect.maxX - topRightCorner.frame.width + 2, highlighedRect.maxY - bottomRightCorner.frame.height + 2) + needsDisplay = true + } + + private var currentCorner: SelectingCorner? = nil + + public override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + currentCorner = nil + guard let window = window else {return} + var point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + point = NSMakePoint(ceil(point.x), ceil(point.y)) + if NSPointInRect(point, topLeftCorner.frame) { + currentCorner = .topLeft + } else if NSPointInRect(point, topRightCorner.frame) { + currentCorner = .topRight + } else if NSPointInRect(point, bottomLeftCorner.frame) { + currentCorner = .bottomLeft + } else if NSPointInRect(point, bottomRightCorner.frame) { + currentCorner = .bottomRight + } else if NSPointInRect(point, selectedRect) { + startSelectedRect = selectedRect.origin + moveSelectedRect = point + } else { + moveSelectedRect = nil + startSelectedRect = nil + } + } + + public override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + let point = convert(event.locationInWindow, from: nil) + if event.clickCount == 2, NSPointInRect(point, selectedRect) { + applyRect(bounds) + } + moveSelectedRect = nil + startSelectedRect = nil + currentCorner = nil + } + + + public override func mouseDragged(with event: NSEvent) { + guard let window = window else {return} + let point = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if let currentCorner = currentCorner { + applyDragging(currentCorner) + } else { + if let current = moveSelectedRect, let selectedOriginPoint = startSelectedRect { + let new = NSMakePoint(current.x - ceil(point.x), current.y - ceil(point.y)) + applyRect(NSMakeRect(selectedOriginPoint.x - new.x, selectedOriginPoint.y - new.y, selectedRect.width, selectedRect.height)) + } else { + super.mouseDragged(with: event) + } + } + } + + + public override func layout() { + super.layout() + needsDisplay = true + updateRectLayout() + } + + + public var inDragging: Bool { + return currentCorner != nil || moveSelectedRect != nil + } + + public func applyRect(_ newRect: NSRect, force: Bool = false, dimensions: SelectionRectDimensions = .none) { + self.dimensions = dimensions + selectedRect = dimensions.applyRect(NSMakeRect(min(max(0, newRect.minX), frame.width - newRect.width), min(max(0, newRect.minY), frame.height - newRect.height), max(min(newRect.width, frame.width), minimumSize.width), max(min(newRect.height, frame.height), minimumSize.height)), for: self.currentCorner, areaSize: frame.size, force: force) + } + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(topLeftCorner) + addSubview(topRightCorner) + addSubview(bottomLeftCorner) + addSubview(bottomRightCorner) + + + topLeftCorner.autohighlight = false + topRightCorner.autohighlight = false + bottomLeftCorner.autohighlight = false + bottomRightCorner.autohighlight = false + + topLeftCorner.set(image: generateCorner(.topLeft, .white), for: .Normal) + topRightCorner.set(image: generateCorner(.topRight, .white), for: .Normal) + bottomLeftCorner.set(image: generateCorner(.bottomLeft, .white), for: .Normal) + bottomRightCorner.set(image: generateCorner(.bottomRight, .white), for: .Normal) + + topLeftCorner.setFrameSize(NSMakeSize(20, 20)) + topRightCorner.setFrameSize(NSMakeSize(20, 20)) + bottomLeftCorner.setFrameSize(NSMakeSize(20, 20)) + bottomRightCorner.setFrameSize(NSMakeSize(20, 20)) + + topLeftCorner.userInteractionEnabled = false + topRightCorner.userInteractionEnabled = false + bottomLeftCorner.userInteractionEnabled = false + bottomRightCorner.userInteractionEnabled = false + + +// topLeftCorner.set(handler: { [weak self] control in +// self?.applyDragging(.topLeft) +// }, for: .MouseDragging) +// +// topRightCorner.set(handler: { [weak self] control in +// self?.applyDragging(.topRight) +// }, for: .MouseDragging) +// +// bottomLeftCorner.set(handler: { [weak self] control in +// self?.applyDragging(.bottomLeft) +// }, for: .MouseDragging) +// +// bottomRightCorner.set(handler: { [weak self] control in +// self?.applyDragging(.bottomRight) +// }, for: .MouseDragging) + } + + private func applyDragging(_ corner: SelectingCorner) { + moveSelectedRect = nil + startSelectedRect = nil + + guard let window = window else {return} + var current = self.convert(window.mouseLocationOutsideOfEventStream, from: nil) + current = NSMakePoint(min(max(0, ceil(current.x)), frame.width), min(max(0, ceil(current.y)), frame.height)) + + +// guard NSPointInRect(current, frame) else { +// return +// } + let newRect: NSRect + switch corner { + case .topLeft: + newRect = NSMakeRect(current.x, current.y, (frame.width - current.x) - (frame.width - selectedRect.maxX), (frame.height - current.y) - (frame.height - selectedRect.maxY)) + case .topRight: + newRect = NSMakeRect(selectedRect.minX, current.y, frame.width - selectedRect.minX - (frame.width - current.x), (frame.height - current.y) - (frame.height - selectedRect.maxY)) + case .bottomLeft: + newRect = NSMakeRect(current.x, selectedRect.minY, (frame.width - current.x) - (frame.width - selectedRect.maxX), frame.height - selectedRect.minY - (frame.height - current.y)) + case .bottomRight: + newRect = NSMakeRect(selectedRect.minX, selectedRect.minY, frame.width - selectedRect.minX - (frame.width - current.x), frame.height - selectedRect.minY - (frame.height - current.y)) + } + + applyRect(newRect) + + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + public override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + ctx.setFillColor(NSColor.black.withAlphaComponent(0.8).cgColor) + + let rect = NSMakeRect(max(1, selectedRect.minX), max(1, selectedRect.minY), min(selectedRect.width, frame.width - 2), min(selectedRect.height, frame.height - 2)) + + ctx.setBlendMode(.normal) + ctx.fill(bounds) + + ctx.setBlendMode(.clear) + if isCircleCap { + ctx.fillEllipse(in: rect) + } else { + ctx.fill(rect) + ctx.setBlendMode(.normal) + ctx.setStrokeColor(.white) + ctx.setLineWidth(1) + ctx.stroke(rect) + } + } + +} diff --git a/submodules/TGUIKit/TGUIKit/ShadowView.swift b/submodules/TGUIKit/TGUIKit/ShadowView.swift new file mode 100644 index 0000000000..a6b74935d0 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ShadowView.swift @@ -0,0 +1,38 @@ +// +// ShadowView.swift +// TGUIKit +// +// Created by keepcoder on 02/08/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa + +public enum ShadowDirection { + case horizontal(Bool) + case vertical(Bool) +} +public class ShadowView: View { + public var direction: ShadowDirection = .vertical(true) + public var shadowBackground: NSColor = .white { + didSet { + needsDisplay = true + } + } + override public func draw(_ layer: CALayer, in ctx: CGContext) { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: [shadowBackground.withAlphaComponent(0).cgColor, shadowBackground.cgColor]), locations: nil)! + + switch direction { + case .vertical: + ctx.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: layer.bounds.height), options: CGGradientDrawingOptions()) + case let .horizontal(reversed): + if reversed { + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: layer.bounds.height), end: CGPoint(x: layer.bounds.width, y: layer.bounds.height), options: CGGradientDrawingOptions()) + } else { + ctx.drawLinearGradient(gradient, start: CGPoint(x: layer.bounds.width, y: layer.bounds.height), end: CGPoint(x: 0, y: layer.bounds.height), options: CGGradientDrawingOptions()) + } + } + } + +} diff --git a/submodules/TGUIKit/TGUIKit/SliderView.swift b/submodules/TGUIKit/TGUIKit/SliderView.swift new file mode 100644 index 0000000000..7c5967b62d --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SliderView.swift @@ -0,0 +1,291 @@ +// +// SliderView.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 02/01/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa + +public enum SliderTransitionStyle { + case fade + case pushVertical + case pushHorizontalFromLeft + case pushHorizontalFromRight +} + +private let kDefaultScheduledTransitionTimeInterval: TimeInterval = 4.0 +private let kDefaultTransitionAnimationDuration: TimeInterval = 0.6 +public class SliderView: View { + + public private(set) var indexOfDisplayedSlide: Int = 0 + public var displayedSlide: NSView? + private var transitionTimer: Timer? + private let contentView = View(frame: NSZeroRect) + private let dotsControl = SliderDotsControl(frame: NSZeroRect) + private var scrollDeltaX: CGFloat = 0 + private var scrollDeltaY: CGFloat = 0 + + + private var slides: [NSView] = [] + private var scheduledTransitionInterval: TimeInterval = kDefaultScheduledTransitionTimeInterval { + didSet { + if self.scheduledTransition { + _prepareTransitionTimer() + } + } + } + + public var transitionAnimationDuration: TimeInterval = kDefaultTransitionAnimationDuration + public var repeatingScheduledTransition: Bool = true + + public var transitionStyle: SliderTransitionStyle = .fade + + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + self._prepareView() + scheduledTransition = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + func setScheduledTransition(_ scheduledTransition: Bool) { + if scheduledTransition { + _prepareTransitionTimer() + } else { + transitionTimer?.invalidate() + transitionTimer = nil + } + } + + var scheduledTransition: Bool { + get { + return transitionTimer != nil + } set { + if newValue { + _prepareTransitionTimer() + } else { + transitionTimer?.invalidate() + transitionTimer = nil + } + } + } + + + override public func layout() { + super.layout() + contentView.frame = bounds + } + + @objc fileprivate func _prepareView() { + acceptsTouchEvents = true + addSubview(contentView) + + dotsControl.sliderView = self + addSubview(dotsControl, positioned: .above, relativeTo: contentView) + + } + @objc fileprivate func _prepareTransitionTimer() { + transitionTimer?.invalidate() + transitionTimer = Timer.scheduledTimer(timeInterval: scheduledTransitionInterval, target: self, selector: #selector(self._handleTimerTick(_:)), userInfo: nil, repeats: true) + } + + @objc fileprivate func _prepareTransition(to index: Int) { + let duration: CFTimeInterval = transitionAnimationDuration * 0.3 + let transition = CATransition() + transition.duration = duration + transition.timingFunction = CAMediaTimingFunction(name: .easeOut) + switch self.transitionStyle { + case .fade: + transition.type = .fade + case .pushHorizontalFromLeft: + transition.type = .push + transition.subtype = .fromLeft + case .pushHorizontalFromRight: + transition.type = .push + transition.subtype = .fromRight + case .pushVertical: + transition.type = .moveIn + transition.subtype = .fromTop + } + contentView.animations = ["subviews" : transition] + } + @objc fileprivate func _handleTimerTick(_ userInfo: Any) { + if slides.count > 0 && (repeatingScheduledTransition || indexOfDisplayedSlide + 1 < slides.count) { + displaySlide(at: (indexOfDisplayedSlide + 1) % slides.count) + } + } + @objc fileprivate func _dotViewSelected(_ dotView: NSView) { + if scheduledTransition { + _prepareTransitionTimer() + } + if dotView.tag >= 0 && dotView.tag < slides.count { + transitionStyle = dotView.tag > dotsControl.indexOfHighlightedDot ? .pushHorizontalFromRight : .pushHorizontalFromLeft + displaySlide(at: dotView.tag) + } + } + + func displaySlide(at aIndex: Int) { + + if aIndex < 0 { + return + } + + dotsControl.indexOfHighlightedDot = aIndex + + let slideToDisplay = slides[aIndex] + if slideToDisplay == displayedSlide { + return + } + slideToDisplay.frame = bounds + slideToDisplay.autoresizingMask = [.width, .height] + + if displayedSlide == nil { + contentView.addSubview(slideToDisplay) + displayedSlide = slideToDisplay + indexOfDisplayedSlide = aIndex + return + } + + NSAnimationContext.beginGrouping() + _prepareTransition(to: aIndex) + if let displayedSlide = displayedSlide { + contentView.animator().replaceSubview(displayedSlide, with: slideToDisplay) + } + NSAnimationContext.endGrouping() + + displayedSlide = slideToDisplay + indexOfDisplayedSlide = aIndex + } + + public func addSlide(_ aSlide: NSView?) { + if let aSlide = aSlide { + slides.append(aSlide) + } + + dotsControl.dotsCount = slides.count + + if slides.count == 1 { + displaySlide(at: 0) + } + } + + public func removeSlide(_ aSlide: NSView?) { + slides.removeAll(where: { element in element == aSlide }) + if indexOfDisplayedSlide >= slides.count { + indexOfDisplayedSlide = 0 + } + dotsControl.dotsCount = slides.count + } + + + override public func scrollWheel(with theEvent: NSEvent) { + + + if theEvent.phase == .began { + scrollDeltaX = 0 + scrollDeltaY = 0 + if theEvent.scrollingDeltaY != 0 { + super.scrollWheel(with: theEvent) + } + } else if theEvent.phase == .changed { + scrollDeltaX += theEvent.scrollingDeltaX + scrollDeltaY += theEvent.scrollingDeltaY + if scrollDeltaX == 0 { + super.scrollWheel(with: theEvent) + } + } else if theEvent.phase == .ended { + + NSLog("\(scrollDeltaX)") + + + if scrollDeltaX > 50 { + transitionStyle = .pushHorizontalFromLeft + displaySlide(at: (indexOfDisplayedSlide - 1) % slides.count) + if scheduledTransition { + _prepareTransitionTimer() + } + } else if scrollDeltaX < -50 { + transitionStyle = .pushHorizontalFromRight + displaySlide(at: (indexOfDisplayedSlide + 1) % slides.count) + if scheduledTransition { + _prepareTransitionTimer() + } + } + } else if theEvent.phase == .cancelled { + scrollDeltaX = 0 + scrollDeltaY = 0 + } else { + super.scrollWheel(with: theEvent) + } + } + + +} + +private let highlightedDotImage = generateImage(NSMakeSize(6, 6), contextGenerator: { size, context in + context.clear(NSMakeRect(0,0, size.width, size.height)) + context.setFillColor(.white) + context.fillEllipse(in: NSMakeRect(0,0, size.width, size.height)) +})! + +private let normalDotImage = generateImage(NSMakeSize(6, 6), contextGenerator: { size, context in + context.clear(NSMakeRect(0,0, size.width, size.height)) + context.setFillColor(NSColor.white.withAlphaComponent(0.5).cgColor) + context.fillEllipse(in: NSMakeRect(0,0, size.width, size.height)) +})! + +private let kDotImageSize: CGFloat = 15.0 +private let kSpaceBetweenDotCenters: CGFloat = 12 +private let kDotContainerY: CGFloat = 8.0 + + +private final class SliderDotsControl : NSView { + var dotsCount: Int = 0 { + didSet { + removeAllSubviews() + + for currentDotIndex in 0 ..< self.dotsCount { + let dotView = NSButton(frame: NSMakeRect(CGFloat(currentDotIndex) * kSpaceBetweenDotCenters, 0.0, kDotImageSize, kDotImageSize)) + dotView.image = (indexOfHighlightedDot == currentDotIndex ? NSImage(cgImage: highlightedDotImage, size: highlightedDotImage.backingSize) : NSImage(cgImage: normalDotImage, size: normalDotImage.backingSize)) + dotView.alternateImage = dotView.image + dotView.setButtonType(.momentaryChange) + dotView.isBordered = false + dotView.tag = currentDotIndex + dotView.target = sliderView + dotView.action = #selector(SliderView._dotViewSelected(_:)) + + addSubview(dotView) + } + if let sliderView = sliderView { + self.frame = NSMakeRect((sliderView.bounds.size.width - (CGFloat(dotsCount) * kSpaceBetweenDotCenters + kDotImageSize) + kSpaceBetweenDotCenters) / 2, sliderView.frame.height - kDotContainerY - kDotImageSize, CGFloat(dotsCount) * kSpaceBetweenDotCenters + kDotImageSize, kDotImageSize); + } + } + } + + var indexOfHighlightedDot: Int = 0 { + didSet { + for subview in subviews { + if let subview = subview as? NSButton { + subview.image = (indexOfHighlightedDot == subview.tag ? NSImage(cgImage: highlightedDotImage, size: highlightedDotImage.backingSize) : NSImage(cgImage: normalDotImage, size: normalDotImage.backingSize)) + } + } + } + } + + fileprivate weak var sliderView: SliderView? + required override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + wantsLayer = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/submodules/TGUIKit/TGUIKit/SpinningProgressView.swift b/submodules/TGUIKit/TGUIKit/SpinningProgressView.swift new file mode 100644 index 0000000000..61c16fe584 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/SpinningProgressView.swift @@ -0,0 +1,150 @@ +// +// SpinningProgressView.swift +// TGUIKit +// + +import Cocoa +import SwiftSignalKit + +private let alphaWhenStopped: CGFloat = 0.15 +private let fadeMultiplier: CGFloat = 0.85 +private var numberOfFins: Int = 12 +private let fadeOutTime: TimeInterval = 0.7 + +public class SpinningProgressView: View { + + + public var color: NSColor = presentation.colors.text + public var currentValue: CGFloat = 0.0 + public var maxValue: CGFloat = 0.0 + + + private var currentPosition: Int + private var finColors: [NSColor] = Array(repeating: presentation.colors.text, count: numberOfFins) + private var isAnimating:Bool + private var animationTimer: SwiftSignalKit.Timer? = nil + private var isFadingOut: Bool + private var fadeOutStartTime: Date = Date() + + + public required init(frame frameRect: NSRect) { + currentPosition = 0 + isAnimating = false + isFadingOut = false + color = presentation.colors.text + currentValue = 0.0 + maxValue = 100.0 + super.init(frame: frameRect) + } + + public func setColor(_ value: NSColor) { + if color != value { + color = value + for i in 0 ..< numberOfFins { + let alpha = alphaValue(forPosition: i) + finColors[i] = color.withAlphaComponent(alpha) + } + needsDisplay = true + } + } + + func alphaValue(forPosition position: Int) -> CGFloat { + var normalValue = pow(fadeMultiplier, CGFloat((position + currentPosition) % numberOfFins)) + if isFadingOut { + let timeSinceStop = -fadeOutStartTime.timeIntervalSinceNow + normalValue *= CGFloat(fadeOutTime - timeSinceStop) + } + return normalValue + } + + public func stopAnimation() { + isFadingOut = true + fadeOutStartTime = Date() + } + public func startAnimation() { + self.actuallyStartAnimation() + } + + private func actuallyStopAnimation() { + isAnimating = false + isFadingOut = false + + self.animationTimer?.invalidate() + self.animationTimer = nil + + self.needsDisplay = true + } + + private func actuallyStartAnimation() { + self.actuallyStopAnimation() + + isAnimating = true + isFadingOut = false + + currentPosition = 0 + + animationTimer = SwiftSignalKit.Timer(timeout: 0.04, repeat: true, completion: { [weak self] in + self?.updateTimer() + }, queue: .mainQueue()) + + animationTimer?.start() + + self.updateTimer() + } + + private func updateTimer() { + + let minAlpha = alphaWhenStopped + + for i in 0 ..< numberOfFins { + let newAlpha = max(alphaValue(forPosition: numberOfFins - i), minAlpha) + self.finColors[i] = color.withAlphaComponent(newAlpha) + } + + if isFadingOut { + if fadeOutStartTime.timeIntervalSinceNow < -fadeOutTime { + self.actuallyStopAnimation() + } + } + + self.needsDisplay = true + + if !isFadingOut { + self.currentPosition = currentPosition + 1 % numberOfFins + } + } + + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ layer: CALayer, in ctx: CGContext) { + let size = bounds.size + let length = min(size.height, size.width) + + ctx.translateBy(x: size.width / 2, y: size.height / 2) + autoreleasepool { + let path = CGMutablePath() + let lineWidth = 0.0859375 * length + let lineStart = 0.234375 * length + let lineEnd = 0.421875 * length + path.move(to: NSPoint(x: 0, y: lineStart)) + path.addLine(to: NSPoint(x: 0, y: lineEnd)) + for i in 0 ..< numberOfFins { + let c = isAnimating ? finColors[i] : color.withAlphaComponent(alphaWhenStopped) + ctx.setLineWidth(lineWidth) + ctx.setLineCap(.round) + ctx.setLineJoin(.round) + ctx.setStrokeColor(c.cgColor) + ctx.addPath(path) + ctx.closePath() + ctx.strokePath() + ctx.rotate(by: 2 * .pi / CGFloat(numberOfFins)) + } + } + + } + +} diff --git a/TGUIKit/TGUIKit/Style.swift b/submodules/TGUIKit/TGUIKit/Style.swift similarity index 97% rename from TGUIKit/TGUIKit/Style.swift rename to submodules/TGUIKit/TGUIKit/Style.swift index 803f9886b8..71e55f8311 100644 --- a/TGUIKit/TGUIKit/Style.swift +++ b/submodules/TGUIKit/TGUIKit/Style.swift @@ -16,7 +16,7 @@ public struct ControlStyle: Equatable { private var _highlightColor: NSColor? public var highlightColor:NSColor { - return _highlightColor ?? presentation.colors.blueUI + return _highlightColor ?? presentation.colors.accent } public func highlight(image:CGImage) -> CGImage { diff --git a/TGUIKit/TGUIKit/SwitchView.swift b/submodules/TGUIKit/TGUIKit/SwitchView.swift similarity index 86% rename from TGUIKit/TGUIKit/SwitchView.swift rename to submodules/TGUIKit/TGUIKit/SwitchView.swift index e50f07d72b..a4ad19e7d6 100644 --- a/TGUIKit/TGUIKit/SwitchView.swift +++ b/submodules/TGUIKit/TGUIKit/SwitchView.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit public struct SwitchViewAppearance { let backgroundColor: NSColor @@ -28,6 +28,7 @@ public struct SwitchViewAppearance { public class SwitchView: Control { private let disposable = MetaDisposable() + public var autoswitch: Bool = true public var presentation: SwitchViewAppearance = switchViewAppearance { didSet { let animates = self.animates @@ -81,7 +82,7 @@ public class SwitchView: Control { self.set(handler: { [weak self] control in if let strongSelf = self { - strongSelf.disposable.set((Signal.single(Void()) |> delay(0.1, queue: Queue.mainQueue())).start(next: { [weak strongSelf] in + strongSelf.disposable.set((Signal.single(Void()) |> delay(0.15, queue: Queue.mainQueue())).start(next: { [weak strongSelf] in if let strongSelf = strongSelf, let stateChanged = strongSelf.stateChanged, strongSelf.isEnabled { stateChanged() } @@ -90,7 +91,9 @@ public class SwitchView: Control { let control = control as! SwitchView let animates = control.animates control.animates = true - control.isOn = !control.isOn + if strongSelf.autoswitch { + control.isOn = !control.isOn + } control.animates = animates } @@ -111,11 +114,11 @@ public class SwitchView: Control { CATransaction.begin() if animates { - buble.animateFrame(from: buble.frame, to: bubleRect, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring) + buble.animateFrame(from: buble.frame, to: bubleRect, duration: 0.2, timingFunction: CAMediaTimingFunctionName.spring) } backgroundLayer.backgroundColor = isEnabled ? ( isOn ? presentation.stateOnColor.cgColor : presentation.stateOffColor.cgColor ) : presentation.disabledColor.cgColor - backgroundLayer.borderWidth = isOn ? 0.0 : 1.0 - buble.borderWidth = isOn ? 0.0 : 1.0 + backgroundLayer.borderWidth = isOn ? 0.0 : 0.0 + buble.borderWidth = isOn ? 0.0 : 0.0 buble.backgroundColor = presentation.backgroundColor.cgColor if animates { @@ -135,8 +138,8 @@ public class SwitchView: Control { var bubleRect:NSRect { - let w = frame.height - (isOn ? 2.0 : 0.0) - return NSMakeRect(isOn ? frame.width - w - (isOn ? 1 : 0): 0, isOn ? 1.0 : 0.0, w, w) + let w = frame.height - (isOn ? 2.0 : 2.0) + return NSMakeRect(isOn ? frame.width - w - (isOn ? 1 : 1) : 1, isOn ? 1.0 : 1.0, w, w) } @@ -146,12 +149,12 @@ public class SwitchView: Control { backgroundLayer.frame = NSMakeRect(0, 0, frame.width, frame.height) backgroundLayer.cornerRadius = frame.height / 2.0 - backgroundLayer.borderWidth = isOn ? 0.0 : 1.0 + backgroundLayer.borderWidth = isOn ? 0.0 : 0.0 backgroundLayer.borderColor = presentation.borderColor.cgColor buble.frame = bubleRect buble.cornerRadius = bubleRect.height/2.0 - buble.borderWidth = isOn ? 0.0 : 1.0 + buble.borderWidth = isOn ? 0.0 : 0.0 buble.borderColor = presentation.borderColor.cgColor } diff --git a/TGUIKit/TGUIKit/System.swift b/submodules/TGUIKit/TGUIKit/System.swift similarity index 76% rename from TGUIKit/TGUIKit/System.swift rename to submodules/TGUIKit/TGUIKit/System.swift index 7e45fd79de..4453b43395 100644 --- a/TGUIKit/TGUIKit/System.swift +++ b/submodules/TGUIKit/TGUIKit/System.swift @@ -7,22 +7,41 @@ // import Cocoa +import SwiftSignalKit public struct System { + public static var scaleFactor: Atomic = Atomic(value: 2.0) + public static var isRetina:Bool { get { - return NSScreen.main?.backingScaleFactor == 2.0 + return scaleFactor.modify({$0}) >= 2.0 } } public static var backingScale:CGFloat { - return CGFloat(NSScreen.main?.backingScaleFactor ?? 2.0) + return CGFloat(scaleFactor.modify({$0})) } public static var drawAsync:Bool { return false } + + public static var isScrollInverted: Bool { + if UserDefaults.standard.value(forKey: "com.apple.swipescrolldirection") != nil { + return UserDefaults.standard.bool(forKey: "com.apple.swipescrolldirection") + } else { + return true + } + } + + public static var supportsTransparentFontDrawing: Bool { + if #available(OSX 10.15, *) { + return true + } else { + return System.backingScale > 1.0 + } + } } diff --git a/TGUIKit/TGUIKit/TGClipView.swift b/submodules/TGUIKit/TGUIKit/TGClipView.swift similarity index 76% rename from TGUIKit/TGUIKit/TGClipView.swift rename to submodules/TGUIKit/TGUIKit/TGClipView.swift index 9668c381bb..4e1edd2c92 100644 --- a/TGUIKit/TGUIKit/TGClipView.swift +++ b/submodules/TGUIKit/TGUIKit/TGClipView.swift @@ -8,14 +8,38 @@ import Cocoa import CoreVideo -import SwiftSignalKitMac +import SwiftSignalKit public class TGClipView: NSClipView,CALayerDelegate { - var border:BorderType? + var border:BorderType? { + didSet { + self.layerContentsRedrawPolicy = .onSetNeedsDisplay + super.needsDisplay = true + self.layerContentsRedrawPolicy = .never + } + } var displayLink:CVDisplayLink? var shouldAnimateOriginChange:Bool = false var destinationOrigin:NSPoint? + + var backgroundMode: TableBackgroundMode = .plain { + didSet { + needsDisplay = true + } + } + + public override var needsDisplay: Bool { + set { + //self.layerContentsRedrawPolicy = .onSetNeedsDisplay + super.needsDisplay = needsDisplay + // self.layerContentsRedrawPolicy = .never + } + get { + return super.needsDisplay + } + } + weak var containingScrollView:NSScrollView? { if let scroll = self.enclosingScrollView { @@ -30,7 +54,7 @@ public class TGClipView: NSClipView,CALayerDelegate { } var scrollCompletion:((_ success:Bool) ->Void)? - public var decelerationRate:CGFloat = 0.78 + public var decelerationRate:CGFloat = 0.8 var isScrolling: Bool { @@ -43,14 +67,28 @@ public class TGClipView: NSClipView,CALayerDelegate { override init(frame frameRect: NSRect) { super.init(frame: frameRect) - self.wantsLayer = true - //self.canDrawSubviewsIntoLayer = true + //self.wantsLayer = true + backgroundColor = .clear + self.layerContentsRedrawPolicy = .never // self.layer?.drawsAsynchronously = System.drawAsync - self.layer?.delegate = self + //self.layer?.delegate = self createDisplayLink() } + override public static var isCompatibleWithResponsiveScrolling: Bool { + return true + } + + public override var backgroundColor: NSColor { + set { + super.backgroundColor = .clear + } + get { + return .clear + } + } + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -64,8 +102,12 @@ public class TGClipView: NSClipView,CALayerDelegate { // } public func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.setFillColor(presentation.colors.background.cgColor) - ctx.fill(self.bounds) + // ctx.clear(bounds) + + + // ctx.setFillColor(NSColor.clear.cgColor) + // ctx.fill(bounds) + if let border = border { @@ -81,7 +123,7 @@ public class TGClipView: NSClipView,CALayerDelegate { ctx.fill(NSMakeRect(0, 0, .borderSize, NSHeight(self.frame))) } if border.contains(.Right) { - ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize + 2, NSHeight(self.frame))) + ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize, NSHeight(self.frame))) } } @@ -109,6 +151,7 @@ public class TGClipView: NSClipView,CALayerDelegate { deinit { endScroll() + NotificationCenter.default.removeObserver(self) } @@ -129,6 +172,9 @@ public class TGClipView: NSClipView,CALayerDelegate { return true } } + if layer?.animation(forKey: "bounds") != nil { + return true + } return false } @@ -180,21 +226,25 @@ public class TGClipView: NSClipView,CALayerDelegate { if ((fabs(o.x - lastOrigin.x) < 0.1 && fabs(o.y - lastOrigin.y) < 0.1)) { if destination.x == o.x && destination.y == o.y { self.endScroll() - + super.scroll(to: o) handleCompletionIfNeeded(withSuccess: true) } else { _ = destination.x - o.x let ydif = destination.y - o.y - + let xdif = destination.x - o.x + if ydif != 0 { let incY = abs(ydif) - abs(ydif + 1) o.y -= incY } - + if xdif != 0 { + let incX = abs(xdif) - abs(xdif + 1) + o.x -= incX + } + super.scroll(to: o) } - super.scroll(to: o) } } @@ -237,10 +287,12 @@ public class TGClipView: NSClipView,CALayerDelegate { public func scroll(to point: NSPoint, animated:Bool, completion: @escaping (Bool) -> Void = {_ in}) { - - + self.shouldAnimateOriginChange = animated self.scrollCompletion = completion + if animated { + beginScroll() + } if animated && abs(bounds.minY - point.y) > frame.height { let y:CGFloat if bounds.minY < point.y { @@ -254,28 +306,42 @@ public class TGClipView: NSClipView,CALayerDelegate { self.scroll(to: point) } - + public func justScroll(to newOrigin:NSPoint) { + super.scroll(to: newOrigin) + } + override public func scroll(to newOrigin:NSPoint) -> Void { - if (self.shouldAnimateOriginChange) { self.shouldAnimateOriginChange = false; self.destinationOrigin = newOrigin; self.beginScroll() } else { - self.destinationOrigin = newOrigin; - self.endScroll() - super.scroll(to: newOrigin) - Queue.mainQueue().justDispatch { - self.handleCompletionIfNeeded(withSuccess: true) + if !isAnimateScrolling { + self.destinationOrigin = newOrigin; + self.endScroll() + super.scroll(to: newOrigin) + Queue.mainQueue().justDispatch { + self.handleCompletionIfNeeded(withSuccess: true) + } } } } + public override var bounds: NSRect { + set { + super.bounds = newValue + } + get { + return super.bounds + } + } + func handleCompletionIfNeeded(withSuccess success: Bool) { if self.scrollCompletion != nil { + // super.scroll(to: bounds.origin) self.scrollCompletion!(success) self.scrollCompletion = nil } diff --git a/submodules/TGUIKit/TGUIKit/TGColor.swift b/submodules/TGUIKit/TGUIKit/TGColor.swift new file mode 100644 index 0000000000..9b2ef98ecc --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TGColor.swift @@ -0,0 +1,299 @@ +// +// Color.swift +// TGUIKit +// +// Created by keepcoder on 06/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Foundation + +public extension NSColor { + + static func ==(lhs: NSColor, rhs: NSColor) -> Bool { + return lhs.argb == rhs.argb + } + + static func colorFromRGB(rgbValue:UInt32) ->NSColor { + return NSColor.init(srgbRed: ((CGFloat)((rgbValue & 0xFF0000) >> 16))/255.0, green: ((CGFloat)((rgbValue & 0xFF00) >> 8))/255.0, blue: ((CGFloat)(rgbValue & 0xFF))/255.0, alpha: 1.0) + } + + static func colorFromRGB(rgbValue:UInt32, alpha:CGFloat) ->NSColor { + return NSColor.init(srgbRed: ((CGFloat)((rgbValue & 0xFF0000) >> 16))/255.0, green: ((CGFloat)((rgbValue & 0xFF00) >> 8))/255.0, blue: ((CGFloat)(rgbValue & 0xFF))/255.0, alpha:alpha) + } + + var alpha: CGFloat { + var alpha: CGFloat = 0 + self.getHue(nil, saturation: nil, brightness: nil, alpha: &alpha) + return alpha + } + + var hsv: (CGFloat, CGFloat, CGFloat) { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var value: CGFloat = 0.0 + self.getHue(&hue, saturation: &saturation, brightness: &value, alpha: nil) + return (hue, saturation, value) + } + + func isTooCloseHSV(to color: NSColor) -> Bool { + let hsv1 = abs(self.hsv.0) + abs(self.hsv.1) + abs(self.hsv.2) + let hsv2 = abs(color.hsv.0) + abs(color.hsv.1) + abs(color.hsv.2) + + let dif = abs(hsv1 - hsv2) + return dif < 0.005 + } + + var lightness: CGFloat { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + self.getRed(&red, green: &green, blue: &blue, alpha: nil) + return 0.2126 * red + 0.7152 * green + 0.0722 * blue + } + + var hsb: (CGFloat, CGFloat, CGFloat) { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil) + return (hue, saturation, brightness) + } + + + var brightnessAdjustedColor: NSColor{ + if lightness > 0.7 { + return NSColor(0x000000) + } else { + return NSColor(0xffffff) + } + var components = self.cgColor.components + let alpha = components?.last + components?.removeLast() + let color = CGFloat(1-(components?.max())! >= 0.5 ? 1.0 : 0.0) + return NSColor(red: color, green: color, blue: color, alpha: alpha!) + } + + func withMultipliedBrightnessBy(_ factor: CGFloat) -> NSColor { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return NSColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha) + } + + func withMultiplied(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) -> NSColor { + var hueValue: CGFloat = 0.0 + var saturationValue: CGFloat = 0.0 + var brightnessValue: CGFloat = 0.0 + var alphaValue: CGFloat = 0.0 + self.getHue(&hueValue, saturation: &saturationValue, brightness: &brightnessValue, alpha: &alphaValue) + + return NSColor(hue: max(0.0, min(1.0, hueValue * hue)), saturation: max(0.0, min(1.0, saturationValue * saturation)), brightness: max(0.0, min(1.0, brightnessValue * brightness)), alpha: alphaValue) + } + + + static var link:NSColor { + return .colorFromRGB(rgbValue: 0x2481cc) + } + + static var accent:NSColor { + return .colorFromRGB(rgbValue: 0x2481cc) + } + + static var redUI:NSColor { + return colorFromRGB(rgbValue: 0xff3b30) + } + + static var greenUI:NSColor { + return colorFromRGB(rgbValue: 0x63DA6E) + } + + static var blackTransparent:NSColor { + return colorFromRGB(rgbValue: 0x000000, alpha: 0.6) + } + + static var grayTransparent:NSColor { + return colorFromRGB(rgbValue: 0xf4f4f4, alpha: 0.4) + } + + static var grayUI:NSColor { + return colorFromRGB(rgbValue: 0xFaFaFa, alpha: 1.0) + } + + static var darkGrayText:NSColor { + return NSColor(0x333333) + } + + static var text:NSColor { + return NSColor.black + } + + + static var blueText:NSColor { + get { + return colorFromRGB(rgbValue: 0x4ba3e2) + } + } + + static var accentSelect:NSColor { + get { + return colorFromRGB(rgbValue: 0x4c91c7) + } + } + + + func lighter(amount : CGFloat = 0.15) -> NSColor { + return hueColorWithBrightnessAmount(1 + amount) + } + + func darker(amount : CGFloat = 0.15) -> NSColor { + return hueColorWithBrightnessAmount(1 - amount) + } + + private func hueColorWithBrightnessAmount(_ amount: CGFloat) -> NSColor { + var hue : CGFloat = 0 + var saturation : CGFloat = 0 + var brightness : CGFloat = 0 + var alpha : CGFloat = 0 + + getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + return NSColor( hue: hue, + saturation: saturation, + brightness: brightness * amount, + alpha: alpha ) + } + + + static var selectText:NSColor { + get { + return colorFromRGB(rgbValue: 0xeaeaea, alpha:1.0) + } + } + + static var random:NSColor { + get { + return colorFromRGB(rgbValue: arc4random_uniform(16000000)) + } + } + + static var blueFill:NSColor { + get { + return colorFromRGB(rgbValue: 0x4ba3e2) + } + } + + + static var border:NSColor { + get { + return colorFromRGB(rgbValue: 0xeaeaea) + } + } + + + + static var grayBackground:NSColor { + get { + return colorFromRGB(rgbValue: 0xf4f4f4) + } + } + + static var grayForeground:NSColor { + get { + return colorFromRGB(rgbValue: 0xe4e4e4) + } + } + + + + static var grayIcon:NSColor { + get { + return colorFromRGB(rgbValue: 0x9e9e9e) + } + } + + + static var accentIcon:NSColor { + get { + return colorFromRGB(rgbValue: 0x0f8fe4) + } + } + + static var badgeMuted:NSColor { + get { + return colorFromRGB(rgbValue: 0xd7d7d7) + } + } + + static var badge:NSColor { + get { + return .blueFill + } + } + + static var grayText:NSColor { + get { + return colorFromRGB(rgbValue: 0x999999) + } + } +} + +public extension NSColor { + convenience init(rgb: UInt32) { + self.init(deviceRed: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) + } + + convenience init(rgb: UInt32, alpha: CGFloat) { + self.init(deviceRed: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: alpha) + } + + convenience init(argb: UInt32) { + self.init(deviceRed: CGFloat((argb >> 16) & 0xff) / 255.0, green: CGFloat((argb >> 8) & 0xff) / 255.0, blue: CGFloat(argb & 0xff) / 255.0, alpha: CGFloat((argb >> 24) & 0xff) / 255.0) + } + + var argb: UInt32 { + + let color = self.usingColorSpaceName(NSColorSpaceName.deviceRGB)! + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + } + + var rgb: UInt32 { + + let color = self.usingColorSpaceName(NSColorSpaceName.deviceRGB) + if let color = color { + let red: CGFloat = color.redComponent + let green: CGFloat = color.greenComponent + let blue: CGFloat = color.blueComponent + + return (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + } + return 0x000000 + } +} + +public extension CGFloat { + + + public static var cornerRadius:CGFloat { + return 5 + } + + public static var borderSize:CGFloat { + get { + return 1 + } + } + + + +} + + diff --git a/submodules/TGUIKit/TGUIKit/TGFont.swift b/submodules/TGUIKit/TGUIKit/TGFont.swift new file mode 100644 index 0000000000..aaa19b40f4 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TGFont.swift @@ -0,0 +1,146 @@ +// +// TGFont.swift +// TGUIKit +// +// Created by keepcoder on 07/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + +public func systemFont(_ size:CGFloat) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.regular) + } else { + return NSFont.init(name: "HelveticaNeue", size: size)! + } +} + +public func systemMediumFont(_ size:CGFloat) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.semibold) + } else { + return NSFont.init(name: "HelveticaNeue-Medium", size: size)! + } + +} + +public func systemBoldFont(_ size:CGFloat) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.bold) + } else { + return NSFont.init(name: "HelveticaNeue-Bold", size: size)! + } +} + +public extension NSFont { + static func normal(_ size:FontSize) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.regular) + } else { + return NSFont(name: "HelveticaNeue", size: size)! + } + } + + static func light(_ size:FontSize) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.light) + } else { + return NSFont(name: "HelveticaNeue", size: size)! + } + } + static func ultraLight(_ size:FontSize) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.ultraLight) + } else { + return NSFont(name: "HelveticaNeue", size: size)! + } + } + + static func italic(_ size: FontSize) -> NSFont { + return NSFontManager.shared.convert(.normal(size), toHaveTrait: .italicFontMask) + } + + static func boldItalic(_ size: FontSize) -> NSFont { + return NSFontManager.shared.convert(.normal(size), toHaveTrait: [.italicFontMask, .boldFontMask]) + } + + static func avatar(_ size: FontSize) -> NSFont { + if let font = NSFont(name: ".SFCompactRounded-Semibold", size: size) { + return font + } else { + return .systemFont(ofSize: size, weight: .heavy) + } + } + + static func medium(_ size:FontSize) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.semibold) + } else { + return NSFont(name: "HelveticaNeue-Medium", size: size)! + } + + } + + static func bold(_ size:FontSize) ->NSFont { + + if #available(OSX 10.11, *) { + return NSFont.systemFont(ofSize: size, weight: NSFont.Weight.bold) + } else { + return NSFont(name: "HelveticaNeue-Bold", size: size)! + } + } + + static func code(_ size:FontSize) ->NSFont { + return NSFont(name: "Menlo-Regular", size: size) ?? NSFont.systemFont(ofSize: size) + } + static func blockchain(_ size: FontSize)->NSFont { + return NSFont(name: "PT Mono", size: size) ?? NSFont.systemFont(ofSize: size) + } +} + +public typealias FontSize = CGFloat + +public extension FontSize { + static let small: CGFloat = 11.0 + static let short: CGFloat = 12.0 + static let text: CGFloat = 13.0 + static let title: CGFloat = 14.0 + static let header: CGFloat = 15.0 + static let huge: CGFloat = 18.0 +} + + + + +public struct TGFont { + + public static var shortSize:CGFloat { + return 12 + } + + public static var textSize:CGFloat { + return 13 + } + + public static var headerSize:CGFloat { + return 15 + } + + public static var titleSize:CGFloat { + return 14 + } + + public static var hugeSize:CGFloat { + return 18 + } + +} diff --git a/submodules/TGUIKit/TGUIKit/TGSplitView.swift b/submodules/TGUIKit/TGUIKit/TGSplitView.swift new file mode 100644 index 0000000000..130e796761 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TGSplitView.swift @@ -0,0 +1,474 @@ +// +// SplitView.swift +// TGUIKit +// +// Created by keepcoder on 06/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Foundation +import SwiftSignalKit +fileprivate class SplitMinimisizeView : Control { + + private var startPoint:NSPoint = NSZeroPoint + private var startDragging: NSPoint = .zero + private var acceptAllDrags: Bool = false + weak var splitView:SplitView? + override init() { + super.init() + userInteractionEnabled = false + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + fileprivate override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + checkCursor() + } + + + func checkCursor() { + if let splitView = splitView { + if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize { + if mouseInside() || (NSEvent.pressedMouseButtons & (1 << 0)) != 0 { + if let cursor = splitView.delegate?.splitResizeCursor(at: self.frame.origin.offsetBy(dx: 5, dy: 0)) { + cursor.set() + } else { + if splitView.state == .minimisize { + NSCursor.resizeRight.set() + } else { + NSCursor.resizeLeft.set() + } + } + + } else { + NSCursor.arrow.set() + } + } + } + } + + fileprivate override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + checkCursor() + } + + override func cursorUpdate(with event: NSEvent) { + super.cursorUpdate(with: event) + checkCursor() + } + + + fileprivate override func mouseExited(with event: NSEvent) { + super.mouseExited(with: event) + checkCursor() + } + + fileprivate override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + + if let splitView = splitView { + if let minimisize = splitView.delegate?.splitViewIsCanMinimisize(), minimisize { + checkCursor() + + let current = splitView.convert(event.locationInWindow, from: nil) + + if startDragging == .zero { + startDragging = current + acceptAllDrags = false + } + + if abs(startDragging.x - current.x) > frame.width / 2 || abs(startDragging.y - current.y) > frame.width / 2 || acceptAllDrags { + acceptAllDrags = true + splitView.delegate?.splitViewShouldResize(at: current) + + if current.x <= 100, splitView.state != .minimisize { + splitView.needMinimisize() + startPoint = current + } else if current.x >= 100, splitView.state == .minimisize { + splitView.needFullsize() + startPoint = current + } else { + splitView.resize(to: current) + } + } + } + + } + } + + private func notifyTableEndResize(in view: NSView) { + for view in view.subviews { + if let view = view as? TableView { + view.layoutItems() + } else { + notifyTableEndResize(in: view) + } + } + } + + override func rightMouseUp(with event: NSEvent) { + super.rightMouseUp(with: event) + startPoint = .zero + startDragging = .zero + acceptAllDrags = false + } + + fileprivate override func mouseUp(with event: NSEvent) { + startPoint = .zero + startDragging = .zero + acceptAllDrags = false + if let splitView = splitView { + notifyTableEndResize(in: splitView) + } + } + + fileprivate override func mouseDown(with event: NSEvent) { + if let splitView = splitView { + startPoint = splitView.convert(event.locationInWindow, from: nil) + } + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + if let splitView = splitView { + if let drawBorder = splitView.delegate?.splitViewDrawBorder(), drawBorder { + ctx.setFillColor(presentation.colors.border.cgColor) + ctx.fill(NSMakeRect(floorToScreenPixels(backingScaleFactor, frame.width / 2), 0, .borderSize, frame.height)) + } + } + } +} + +public struct SplitProportion { + var min:CGFloat = 0; + var max:CGFloat = 0; + + public init(min:CGFloat, max:CGFloat) { + self.min = min; + self.max = max; + } +} + +public enum SplitViewState : Int { + case none = -1; + case single = 0; + case dual = 1; + case triple = 2; + case minimisize = 3 +} + + +public protocol SplitViewDelegate : class { + func splitViewDidNeedSwapToLayout(state:SplitViewState) -> Void + func splitViewDidNeedMinimisize(controller:ViewController) -> Void + func splitViewDidNeedFullsize(controller:ViewController) -> Void + func splitViewIsCanMinimisize() -> Bool + func splitViewDrawBorder() -> Bool + + func splitViewShouldResize(at point: NSPoint) -> Void + + func splitResizeCursor(at point: NSPoint) -> NSCursor? +} + +public extension SplitViewDelegate { + func splitViewShouldResize(at point: NSPoint) -> Void { + + } + func splitResizeCursor(at point: NSPoint) -> NSCursor? { + return nil + } +} + + +public class SplitView : View { + + private let minimisizeOverlay:SplitMinimisizeView = SplitMinimisizeView() + private let container:View + private var forceNotice:Bool = false + public var state: SplitViewState = .none { + didSet { + let notify:Bool = state != oldValue; + // assert(notify); + if(notify) { + self.delegate?.splitViewDidNeedSwapToLayout(state: state); + } + if state != .none { + if state == .dual || state == .minimisize { + if let _ = container.subviews.first { + if minimisizeOverlay.superview == nil { + addSubview(minimisizeOverlay) + } + } + } else { + minimisizeOverlay.removeFromSuperview() + } + } else { + minimisizeOverlay.removeFromSuperview() + } + } + } + + public var mustMinimisize: Bool = false + + public var canChangeState:Bool = true; + public weak var delegate:SplitViewDelegate? + + + private var _proportions:[Int:SplitProportion] = [Int:SplitProportion]() + private var _startSize:[Int:NSSize] = [Int:NSSize]() + fileprivate var _controllers:[ViewController] = [ViewController]() + private var _issingle:Bool? + private var _layoutProportions:[SplitViewState:SplitProportion] = [SplitViewState:SplitProportion]() + + private var _splitIdx:Int? + + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + } + + required public init(frame frameRect: NSRect) { + container = View(frame: NSMakeRect(0,0,frameRect.width, frameRect.height)) + super.init(frame: frameRect); + self.autoresizesSubviews = true + self.autoresizingMask = [.width, .height] + container.autoresizesSubviews = false + container.autoresizingMask = [.width, .height] + addSubview(container) + minimisizeOverlay.splitView = self + + } + + public override var backgroundColor: NSColor { + didSet { + container.backgroundColor = backgroundColor + minimisizeOverlay.needsDisplay = true + } + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + public func addController(controller:ViewController, proportion:SplitProportion) ->Void { + controller._frameRect = NSMakeRect(0, 0, proportion.min, frame.height) + container.addSubview(controller.view); + controller.viewWillAppear(false) + _controllers.append(controller); + _startSize.updateValue(controller.view.frame.size, forKey: controller.internalId); + _proportions.updateValue(proportion, forKey: controller.internalId) + controller.viewDidAppear(false) + } + + func removeController(controller:ViewController) -> Void { + + controller.viewWillDisappear(false) + let idx = _controllers.firstIndex(of: controller)!; + container.subviews[idx].removeFromSuperview(); + _controllers.remove(at: idx); + _startSize.removeValue(forKey: controller.internalId); + _proportions.removeValue(forKey: controller.internalId); + + controller.viewDidDisappear(false) + } + + public func removeAllControllers() -> Void { + + var copy:[ViewController] = [] + + + for controller in _controllers { + copy.append(controller) + } + + for controller in copy { + controller.viewWillDisappear(false) + } + + container.removeAllSubviews(); + _controllers.removeAll(); + _startSize.removeAll(); + _proportions.removeAll(); + + for controller in copy { + controller.viewDidDisappear(false) + } + } + + public func setProportion(proportion:SplitProportion, state:SplitViewState) -> Void { + _layoutProportions[state] = proportion; + } + + public func removeProportion(state:SplitViewState) -> Void { + _layoutProportions.removeValue(forKey: state); + if(_controllers.count > state.rawValue) { + _controllers.remove(at: state.rawValue) + } + } + + public func updateStartSize(size:NSSize, controller:ViewController) -> Void { + _startSize[controller.internalId] = size; + _proportions[controller.internalId] = SplitProportion(min:size.width, max:size.height); + needsLayout = true + } + + public func update(_ forceNotice:Bool = false) -> Void { + Queue.mainQueue().justDispatch { + self.forceNotice = forceNotice + self.needsLayout = true + } + } + + public var nextLayout: SplitViewState { + + let single:SplitProportion! = _layoutProportions[.single] + let dual:SplitProportion! = _layoutProportions[.dual] + let triple:SplitProportion! = _layoutProportions[.triple] + + if acceptLayout(prop: single) && canChangeState && !mustMinimisize { + if frame.width < single.max { + if self.state != .single { + return .single; + } + } else if acceptLayout(prop: dual) { + if acceptLayout(prop: triple) { + if frame.width >= dual.min && frame.width <= dual.max { + if state != .dual { + return .dual; + } + } else if state != .triple { + return .triple; + } + } else { + if state != .dual && frame.width >= dual.min { + return .dual; + } + } + + } + } else if mustMinimisize, self.state != .minimisize { + return .minimisize + } + return self.state + } + + public override func layout() { + super.layout() + + //assert(!_controllers.isEmpty) + + let single:SplitProportion! = _layoutProportions[.single] + let dual:SplitProportion! = _layoutProportions[.dual] + let triple:SplitProportion! = _layoutProportions[.triple] + + + + if acceptLayout(prop: single) && canChangeState && !mustMinimisize { + if frame.width < single.max { + if self.state != .single { + self.state = .single; + } + } else if acceptLayout(prop: dual) { + if acceptLayout(prop: triple) { + if frame.width >= dual.min && frame.width <= dual.max { + if state != .dual { + state = .dual; + } + } else if state != .triple { + self.state = .triple; + } + } else { + if state != .dual && frame.width >= dual.min { + self.state = .dual; + } + } + + } + } else if mustMinimisize, self.state != .minimisize { + self.state = .minimisize + } + + if forceNotice { + forceNotice = false + self.delegate?.splitViewDidNeedSwapToLayout(state: state) + } + + var x:CGFloat = 0; + + for (index, obj) in _controllers.enumerated() { + + let proportion:SplitProportion = _proportions[obj.internalId]!; + let startSize:NSSize = _startSize[obj.internalId]!; + var size:NSSize = NSMakeSize(x, frame.height); + var _min:CGFloat = startSize.width; + _min = proportion.min; + if(proportion.max == CGFloat.greatestFiniteMagnitude && index != _controllers.count-1) { + var m2:CGFloat = 0; + for i:Int in index + 1 ..< _controllers.count - index { + let split:ViewController = _controllers[i]; + let proportion:SplitProportion = _proportions[split.internalId]!; + m2+=proportion.min; + } + _min = frame.width - x - m2; + } + if index < _controllers.count - 1, state != .minimisize { + _min = min(_min, frame.width - 350) + } + if(index == _controllers.count - 1) { + _min = frame.width - x; + } + + size = NSMakeSize(x + _min > frame.width ? (frame.width - x) : _min, frame.height); + let rect:NSRect = NSMakeRect(x, 0, size.width, size.height); + + if(!NSEqualRects(rect, obj.view.frame)) { + obj.view.frame = rect; + } + + x+=size.width; + + } + + //assert(state != .none) + if state != .none { + if state == .dual || state == .minimisize { + if let first = container.subviews.first { + minimisizeOverlay.frame = NSMakeRect(first.frame.maxX - 5, 0, 10, frame.height) + } + + } + } + } + + + + public func needFullsize() { + self.state = .none + self.needsLayout = true + } + + public func needMinimisize() { + self.state = .minimisize + self.needsLayout = true + + } + + func resize(to point: NSPoint) { + //NSLog("\(point)") + } + + + func acceptLayout(prop:SplitProportion!) -> Bool { + return prop != nil ? (prop!.min > 0 && prop!.max > 0) : false; + } + +} diff --git a/TGUIKit/TGUIKit/TGUIKit.h b/submodules/TGUIKit/TGUIKit/TGUIKit.h similarity index 100% rename from TGUIKit/TGUIKit/TGUIKit.h rename to submodules/TGUIKit/TGUIKit/TGUIKit.h diff --git a/submodules/TGUIKit/TGUIKit/TabBarController.swift b/submodules/TGUIKit/TGUIKit/TabBarController.swift new file mode 100644 index 0000000000..6b68997bcc --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TabBarController.swift @@ -0,0 +1,159 @@ +// +// TabBarController.swift +// TGUIKit +// +// Created by keepcoder on 27/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +private class TabBarViewController : View { + let tabView:TabBarView + + + required init(frame frameRect: NSRect) { + tabView = TabBarView(frame: NSMakeRect(0, frameRect.height - 50, frameRect.width, 50)) + super.init(frame: frameRect) + addSubview(tabView) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.background = presentation.colors.background + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + func updateFrame(_ frame: NSRect, animated: Bool) { + for subview in subviews { + if let subview = subview as? TabBarView { + (animated ? subview.animator() : subview).frame = NSMakeRect(0, frame.height - 50, frame.width, 50) + } else { + if tabView.isHidden { + (animated ? subview.animator() : subview).frame = bounds + } else { + (animated ? subview.animator() : subview).frame = NSMakeRect(0, 0, frame.width, frame.height - tabView.frame.height) + } + } + } + } + + override func layout() { + super.layout() + updateFrame(frame, animated: false) + } +} + +public class TabBarController: ViewController, TabViewDelegate { + + + public var didChangedIndex:(Int)->Void = {_ in} + + public weak var current:ViewController? { + didSet { + current?.navigationController = self.navigationController + } + } + + private var genericView:TabBarViewController { + return view as! TabBarViewController + } + + public override func viewClass() -> AnyClass { + return TabBarViewController.self + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + genericView.tabView.enumerateItems({ item in + if item.controller.isLoaded() { + item.controller.updateLocalizationAndTheme(theme: theme) + } + return false + }) + } + + public override func loadView() { + super.loadView() + genericView.tabView.delegate = self + genericView.autoresizingMask = [] + } + + public func didChange(selected item: TabItem, index: Int) { + + if current != item.controller { + if let current = current { + _ = current.window?.makeFirstResponder(nil) + current.viewWillDisappear(false) + current.view.removeFromSuperview() + current.viewDidDisappear(false) + } + item.controller._frameRect = NSMakeRect(0, 0, bounds.width, bounds.height - genericView.tabView.frame.height) + item.controller.view.frame = item.controller._frameRect + item.controller.viewWillAppear(false) + view.addSubview(item.controller.view, positioned: .below, relativeTo: genericView.tabView) + item.controller.viewDidAppear(false) + current = item.controller + didChangedIndex(index) + } + } + + public func scrollup() { + current?.scrollup() + } + + public func control(for index: Int) -> Control { + return self.genericView.tabView.control(for: index) + } + + public func hideTabView(_ hide:Bool) { + genericView.tabView.isHidden = hide + current?.view.frame = hide ? bounds : NSMakeRect(0, 0, bounds.width, bounds.height - genericView.tabView.frame.height) + + } + + public override func updateFrame(_ frame: NSRect, animated: Bool) { + super.updateFrame(frame, animated: animated) + self.genericView.updateFrame(frame, animated: animated) + } + + public func select(index:Int) -> Void { + if index != self.genericView.tabView.selectedIndex { + genericView.tabView.setSelectedIndex(index, respondToDelegate: true, animated: false) + } + } + + public var count: Int { + return genericView.tabView.count + } + + public func add(tab:TabItem) -> Void { + genericView.tabView.addTab(tab) + } + public func tab(at index:Int) -> TabItem { + return genericView.tabView.tab(at: index) + } + public func replace(tab: TabItem, at index:Int) -> Void { + genericView.tabView.replaceTab(tab, at: index) + } + public func insert(tab: TabItem, at index: Int) -> Void { + genericView.tabView.insertTab(tab, at: index) + } + public func remove(at index: Int) -> Void { + genericView.tabView.removeTab(at: index) + } + public var isEmpty:Bool { + return genericView.tabView.isEmpty + } + public func showTooltip(text: String, for index: Int) -> Void { + genericView.tabView.showTooltip(text: text, for: index) + } + +} diff --git a/submodules/TGUIKit/TGUIKit/TabBarView.swift b/submodules/TGUIKit/TGUIKit/TabBarView.swift new file mode 100644 index 0000000000..896cdec97c --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TabBarView.swift @@ -0,0 +1,223 @@ +// +// TabBarView.swift +// TGUIKit +// +// Created by keepcoder on 27/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + +public protocol TabViewDelegate : class { + func didChange(selected item:TabItem, index:Int) + func scrollup() +} + +public class TabBarView: View { + + private var tabs:[TabItem] = [] + public private(set) var selectedIndex:Int = 0 + + public weak var delegate:TabViewDelegate? + + + required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + autoresizesSubviews = false + autoresizingMask = [] + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + + ctx.setFillColor(presentation.colors.border.cgColor) + ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) + ctx.fill(NSMakeRect(0, 0, frame.width, .borderSize)) + + } + + func control(for index: Int) -> Control { + return subviews[index] as! Control + } + + func addTab(_ tab: TabItem) { + self.tabs.append(tab) + self.redraw() + } + func replaceTab(_ tab: TabItem, at index:Int) { + self.tabs.remove(at: index) + self.tabs.insert(tab, at: index) + + let subview = self.subviews[index].subviews.first?.subviews.first as? ImageView + subview?.animates = true + subview?.image = self.selectedIndex == index ? tab.selectedImage : tab.image + subview?.animates = false + (self.subviews[index] as? Control)?.backgroundColor = presentation.colors.background + (self.subviews[index].subviews.first as? View)?.backgroundColor = presentation.colors.background + + if let subView = tab.subNode?.view { + while self.subviews[index].subviews.count > 1 { + self.subviews[index].subviews.last?.removeFromSuperview() + } + self.subviews[index].addSubview(subView) + tab.subNode?.update() + } + } + + func insertTab(_ tab: TabItem, at index: Int) { + self.tabs.insert(tab, at: index) + if selectedIndex >= index { + selectedIndex += 1 + } + self.redraw() + } + + func removeTab(_ tab: TabItem) { + self.tabs.remove(at: self.tabs.firstIndex(of: tab)!) + self.redraw() + } + + func enumerateItems(_ f:(TabItem)->Bool) { + for item in tabs { + if f(item) { + break + } + } + } + + func removeTab(at index: Int) { + self.tabs.remove(at: index) + if selectedIndex >= index { + selectedIndex -= 1 + } + self.redraw() + } + public var isEmpty:Bool { + return tabs.isEmpty + } + public var count:Int { + return tabs.count + } + public func tab(at index:Int) -> TabItem { + return self.tabs[index] + } + public func showTooltip(text: String, for index: Int) -> Void { + tooltip(for: self.subviews[index], text: text) + } + + func redraw() { + let width = NSWidth(self.bounds) + let height = NSHeight(self.bounds) - .borderSize + let defWidth = width / CGFloat(self.tabs.count) + self.removeAllSubviews() + var xOffset:CGFloat = 0 + + + for i in 0 ..< tabs.count { + let tab = tabs[i] + let itemWidth = defWidth + let view = Control(frame: NSMakeRect(xOffset, .borderSize, itemWidth, height)) + view.backgroundColor = presentation.colors.background + let container = View(frame: view.bounds) + view.set(handler: { [weak self] control in + self?.tabs[i].longHoverHandler?(control) + }, for: .RightDown) + + view.set(handler: { [weak self] control in + if let strongSelf = self { + if strongSelf.selectedIndex == i { + strongSelf.delegate?.scrollup() + } else { + strongSelf.setSelectedIndex(i, respondToDelegate:true, animated: true) + } + } + }, for: .Down) + view.autoresizingMask = [.minXMargin, .maxXMargin, .width] + view.autoresizesSubviews = true + let imageView = tab.makeView() + tab.setSelected(false, for: imageView, animated: false) + container.addSubview(imageView) + container.backgroundColor = presentation.colors.background + container.setFrameSize(NSMakeSize(NSWidth(imageView.frame), NSHeight(container.frame))) + view.addSubview(container) + + if let subView = tab.subNode?.view { + view.addSubview(subView) + tab.subNode?.update() + } + + imageView.center() + container.center() + + self.addSubview(view) + xOffset += itemWidth + } + + self.setSelectedIndex(self.selectedIndex, respondToDelegate: false, animated: false) + setFrameSize(frame.size) + } + + public override func updateLocalizationAndTheme(theme: PresentationTheme) { + for subview in subviews { + subview.background = presentation.colors.background + for container in subview.subviews { + //container.background = presentation.colors.background + } + } + self.backgroundColor = presentation.colors.background + needsDisplay = true + super.updateLocalizationAndTheme(theme: theme) + } + + + override public func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + // if previous != newSize.width { + let width = NSWidth(self.bounds) + let height = NSHeight(self.bounds) - .borderSize + let defWidth = floorToScreenPixels(backingScaleFactor, width / CGFloat( max(1, self.tabs.count) )) + var xOffset:CGFloat = 0 + + var idx:Int = 0 + + for subview in subviews { + let w = idx == subviews.count - 1 ? defWidth - .borderSize : defWidth + let child = subview.subviews[0] + subview.frame = NSMakeRect(xOffset, .borderSize, w, height) + child.center() + xOffset += w + + idx += 1 + } + // } + } + + + public func setSelectedIndex(_ selectedIndex: Int, respondToDelegate: Bool, animated: Bool) { + if selectedIndex > self.tabs.count || self.tabs.count == 0 { + return + } + let deselectItem = self.tabs[self.selectedIndex] + let deselectView = self.subviews[self.selectedIndex] + + deselectItem.setSelected(false, for: deselectView.subviews[0].subviews[0], animated: animated) + self.selectedIndex = selectedIndex + let selectItem = self.tabs[self.selectedIndex] + let selectView = self.subviews[self.selectedIndex] + + selectItem.setSelected(true, for: selectView.subviews[0].subviews[0], animated: animated) + if respondToDelegate { + self.delegate?.didChange(selected: selectItem, index: selectedIndex) + } + + } + + + + +} diff --git a/submodules/TGUIKit/TGUIKit/TabItem.swift b/submodules/TGUIKit/TGUIKit/TabItem.swift new file mode 100644 index 0000000000..9905d13d51 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TabItem.swift @@ -0,0 +1,40 @@ +// +// TabItem.swift +// TGUIKit +// +// Created by keepcoder on 27/09/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + + + +open class TabItem: NSObject { + let image: CGImage + let selectedImage: CGImage + public let controller:ViewController + public let subNode:Node? + public let longHoverHandler:((Control)->Void)? + public init(image: CGImage, selectedImage: CGImage, controller:ViewController, subNode:Node? = nil, longHoverHandler:((Control)->Void)? = nil) { + self.image = image + self.longHoverHandler = longHoverHandler + self.selectedImage = selectedImage + self.controller = controller + self.subNode = subNode + super.init() + } + + open func withUpdatedImages(_ image: CGImage, _ selectedImage: CGImage) -> TabItem { + return TabItem(image: image, selectedImage: selectedImage, controller: self.controller, subNode: self.subNode, longHoverHandler: self.longHoverHandler) + } + + open func makeView() -> NSView { + return ImageView(frame: NSMakeRect(0, 0, image.backingSize.width, image.backingSize.height)) + } + + open func setSelected(_ selected: Bool, for view: NSView, animated: Bool) { + (view as? ImageView)?.image = selected ? selectedImage : image + + } +} diff --git a/submodules/TGUIKit/TGUIKit/TableAnimationInterface.swift b/submodules/TGUIKit/TGUIKit/TableAnimationInterface.swift new file mode 100644 index 0000000000..faefc3e036 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TableAnimationInterface.swift @@ -0,0 +1,122 @@ + +// +// TableAnimationInterface.swift +// TGUIKit +// +// Created by keepcoder on 02/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +open class TableAnimationInterface: NSObject { + + public let scrollBelow:Bool + public let saveIfAbove:Bool + public init(_ scrollBelow:Bool = true, _ saveIfAbove:Bool = true) { + self.scrollBelow = scrollBelow + self.saveIfAbove = saveIfAbove + } + + public func animate(table:TableView, documentOffset: NSPoint, added:[TableRowItem], removed:[TableRowItem]) -> Void { + + var height:CGFloat = 0 + + + + + let contentView = table.contentView + let bounds = contentView.bounds + + var scrollBelow = self.scrollBelow || (bounds.minY - height) < 0 + var checkBelowAfter: Bool = false + + if scrollBelow { + contentView.scroll(to: NSMakePoint(0, min(0, documentOffset.y))) + contentView.layer?.removeAllAnimations() + } else { + checkBelowAfter = true + } + + var range:NSRange = table.visibleRows(height) + + let added = added.filter { item in + if item.index < range.location || item.index > range.location + range.length { + return false + } else { + return true + } + } + + let removed = removed.filter { item in + if item.index < range.location || item.index > range.location + range.length { + return false + } else { + return true + } + } + + for item in added { + height += item.height + } + + for item in removed { + height -= item.height + } + + range = table.visibleRows(height) + + if added.isEmpty && removed.isEmpty { + return + } + + + scrollBelow = scrollBelow || (checkBelowAfter && (bounds.minY - height) < 0) + + if height - bounds.height < table.frame.height || bounds.minY > height, scrollBelow { + + contentView.scroll(to: NSMakePoint(0, min(0, documentOffset.y))) + + if range.length >= added[0].index { + for idx in added[0].index ..< range.length { + + if let view = table.viewNecessary(at: idx), let layer = view.layer { + + var inset = (layer.frame.minY - height); + if let presentLayer = layer.presentation(), presentLayer.animation(forKey: "position") != nil { + inset = presentLayer.position.y + } + + // if layer.presentation()?.animation(forKey: "position") == nil { + layer.animatePosition(from: NSMakePoint(0, inset), to: NSMakePoint(0, layer.position.y), duration: 0.2, timingFunction: .easeOut) + // } + + /* + if layer.presentation()?.animation(forKey: "position") == nil { + layer.animatePosition(from: NSMakePoint(0, -height), to: NSZeroPoint, duration: 0.2, timingFunction: .easeOut, additive: true) + } + */ + + for item in added { + if item.index == idx { + //layer.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + + } + } + } + + } else if !scrollBelow { + contentView.bounds = NSMakeRect(0, bounds.minY + height, contentView.bounds.width, contentView.bounds.height) + table.reflectScrolledClipView(contentView) + } + + } + + public func scroll(table:TableView, from:NSRect, to:NSRect) -> Void { + table.contentView.layer?.animateBounds(from: from, to: to, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut) + } + + +} diff --git a/submodules/TGUIKit/TGUIKit/TableRowItem.swift b/submodules/TGUIKit/TGUIKit/TableRowItem.swift new file mode 100644 index 0000000000..2f94248e93 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TableRowItem.swift @@ -0,0 +1,184 @@ +// +// TableRowItem.swift +// TGUIKit +// +// Created by keepcoder on 07/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + +open class TableRowItem: NSObject { + public weak var table:TableView? { + didSet { + tableViewDidUpdated() + } + } + public let initialSize:NSSize + + open func tableViewDidUpdated() { + + } + + open var canBeAnchor: Bool { + return true + } + + open var isUniqueView: Bool { + return false + } + + open var animatable:Bool { + return true + } + + open var instantlyResize:Bool { + return false + } + open var reloadOnTableHeightChanged: Bool { + return false + } + + open private(set) var height:CGFloat = 60; + + + public var size:NSSize { + return NSMakeSize(width, height) + } + + public var oldWidth:CGFloat = 0 + + open var width:CGFloat { + return oldWidth + } + + open var stableId:AnyHashable { + return 0 + } + + open func copyAndUpdate(animated: Bool) { + + } + + open var index:Int { + if let _index = _index { + return _index + } else if let table = table, let index = table.index(of:self) { + return index + } else { + return -1 + } + } + + + internal(set) var _index:Int? = nil + + public init(_ initialSize:NSSize) { + self.initialSize = initialSize + } + + open func prepare(_ selected:Bool) { + + } + + open var isVisible: Bool { + if let table = table { + let visible = table.visibleRows() + return visible.indexIn(index) + } + return false + } + + + open func menuItems(in location: NSPoint) -> Signal<[ContextMenuItem], NoError> { + return .single([]) + } + + public func redraw(animated: Bool = false, options: NSTableView.AnimationOptions = .effectFade, presentAsNew: Bool = false)->Void { + if index != -1 { + table?.reloadData(row: index, animated: animated, options: options, presentAsNew: presentAsNew) + } + } + + public var isSelected:Bool { + if let table = table { + return table.isSelected(self) + } else { + return false + } + } + public var isHighlighted: Bool { + if let table = table { + return table.isHighlighted(self) + } else { + return false + } + } + + open var isLast: Bool { + return table?.lastItem == self + } + + open func canMultiselectTextIn(_ location: NSPoint) -> Bool { + if let view = view { + return view.canMultiselectTextIn(location) + } + return false + } + + open var identifier:String { + return NSStringFromClass(viewClass()) + } + + open func viewClass() ->AnyClass { + return TableRowView.self; + } + + open var layoutSize: NSSize { + return NSZeroSize + } + + open var view: TableRowView? { + assertOnMainThread() + if let table = table { + return table.viewNecessary(at: index) + } + return nil + } + + open var viewNecessary: TableRowView? { + assertOnMainThread() + if let table = table { + return table.viewNecessary(at: index, makeIfNecessary: true) + } + return nil + } + + open func makeSize(_ width:CGFloat = CGFloat.greatestFiniteMagnitude, oldWidth:CGFloat = 0) -> Bool { + self.oldWidth = width + return true; + } + + public private(set) var _isAutohidden: Bool = false + public var isAutohidden: Bool { + return _isAutohidden + } + public func hideItem(animated: Bool, reload: Bool = true, options: NSTableView.AnimationOptions = .slideUp, presentAsNew: Bool = true) { + _isAutohidden = true + if reload { + redraw(animated: animated, options: options, presentAsNew: presentAsNew) + } + } + public func unhideItem(animated: Bool, reload: Bool = true, options: NSTableView.AnimationOptions = .slideDown, presentAsNew: Bool = true) { + _isAutohidden = false + NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .drawCompleted) + if reload { + redraw(animated: animated, options: .slideDown, presentAsNew: true) + } + } + + internal var heightValue: CGFloat { + return _isAutohidden ? 1.0 : self.height + } +} diff --git a/submodules/TGUIKit/TGUIKit/TableRowView.swift b/submodules/TGUIKit/TGUIKit/TableRowView.swift new file mode 100644 index 0000000000..24bcb77066 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TableRowView.swift @@ -0,0 +1,462 @@ +// +// TableRowView.swift +// TGUIKit +// +// Created by keepcoder on 07/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import AVFoundation +import Foundation + +open class TableRowView: NSTableRowView, CALayerDelegate { + + private var animatedView: RowAnimateView? + + public internal(set) var isResorting: Bool = false { + didSet { + updateIsResorting() + } + } + + var dynamicContentStateForRestore:Bool? = nil + var interactionStateForRestore:Bool? = nil + + public var isDynamicContentLocked:Bool = false { + didSet { + if isDynamicContentLocked != oldValue { + viewDidUpdatedDynamicContent() + } + } + } + public var userInteractionEnabled:Bool = true { + didSet { + if userInteractionEnabled != oldValue { + viewDidUpdatedInteractivity() + } + } + } + + open func viewDidUpdatedInteractivity() { + + } + open func viewDidUpdatedDynamicContent() { + + } + + + open private(set) weak var item:TableRowItem? + private let menuDisposable = MetaDisposable() + // var selected:Bool? + + open var border:BorderType? + public var animates:Bool = true + + public private(set) var contextMenu:ContextMenu? + + + required public override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + // self.layer = (self.layerClass() as! CALayer.Type).init() + self.wantsLayer = true + backgroundColor = .clear + self.layerContentsRedrawPolicy = .never + autoresizingMask = [] + // self.layer?.delegate = self + autoresizesSubviews = false + // canDrawSubviewsIntoLayer = true + pressureConfiguration = NSPressureConfiguration(pressureBehavior: .primaryDeepClick) + appearance = presentation.appearance + self.canDrawSubviewsIntoLayer = true + } + + + + open func updateColors() { + self.layer?.backgroundColor = backdorColor.cgColor + } + + open override func smartMagnify(with event: NSEvent) { + super.smartMagnify(with: event) + } + + open func layerClass() ->AnyClass { + return CALayer.self; + } + + open var backdorColor: NSColor { + return presentation.colors.background + } + + open var isSelect: Bool { + return item?.isSelected ?? false + } + + open var isHighlighted: Bool { + return item?.isHighlighted ?? false + } + + open override func draw(_ dirtyRect: NSRect) { + var bp:Int = 0 + bp += 1 + } + open func updateIsResorting() { + + } + + + open func draw(_ layer: CALayer, in ctx: CGContext) { +// ctx.setFillColor(backdorColor.cgColor) +// ctx.fill(layer.bounds) + // layer.draw(in: ctx) + + if let border = border { + + ctx.setFillColor(presentation.colors.border.cgColor) + + if border.contains(.Top) { + ctx.fill(NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize)) + } + if border.contains(.Bottom) { + ctx.fill(NSMakeRect(0, 0, frame.width, .borderSize)) + } + if border.contains(.Left) { + ctx.fill(NSMakeRect(0, 0, .borderSize, frame.height)) + } + if border.contains(.Right) { + ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) + } + + } + + } + + open func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { + return self + } + open func interactionControllerDidFinishAnimation(interactive: Bool, innerId: AnyHashable) { + + } + open func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { + + } + open func videoTimebase(for innerId: AnyHashable) -> CMTimebase? { + return nil + } + open func applyTimebase(for innerId: AnyHashable, timebase: CMTimebase?) { + + } + + open func hasFirstResponder() -> Bool { + return false + } + + open func nextResponder() -> NSResponder? { + return nil + } + + open var firstResponder:NSResponder? { + return self + } + + open override func mouseMoved(with event: NSEvent) { + super.mouseMoved(with: event) + updateMouse() + } + open override func mouseEntered(with event: NSEvent) { + super.mouseEntered(with: event) + updateMouse() + } + open override func mouseExited(with event: NSEvent) { + super.mouseMoved(with: event) + updateMouse() + } + + open override func mouseDown(with event: NSEvent) { + if event.modifierFlags.contains(.control) && event.clickCount == 1 { + showContextMenu(event) + } else { + if event.clickCount == 2 { + doubleClick(in: convert(event.locationInWindow, from: nil)) + return + } + super.mouseDown(with: event) + } + } + + private var lastPressureEventStage = 0 + + open override func pressureChange(with event: NSEvent) { + super.pressureChange(with: event) + if event.stage == 2 && lastPressureEventStage < 2 { + forceClick(in: convert(event.locationInWindow, from: nil)) + } + lastPressureEventStage = event.stage + } + + open override func rightMouseDown(with event: NSEvent) { + super.rightMouseDown(with: event) + if mouseInside() { + showContextMenu(event) + } + } + + open func doubleClick(in location:NSPoint) -> Void { + + } + + open func forceClick(in location: NSPoint) { + + } + + open func canMultiselectTextIn(_ location: NSPoint) -> Bool { + return true + } + + open func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { + return convert(point, from: nil) + } + + open func showContextMenu(_ event:NSEvent) -> Void { + + menuDisposable.set(nil) + contextMenu = nil + + if let item = item { + menuDisposable.set((item.menuItems(in: convertWindowPointToContent(event.locationInWindow)) |> deliverOnMainQueue |> take(1)).start(next: { [weak self] items in + if let strongSelf = self { + let menu = ContextMenu() + + +// presntContextMenu(for: event, items: items.compactMap({ item in +// if !(item is ContextSeparatorItem) { +// return SPopoverItem(item.title, item.handler) +// } else { +// return nil +// } +// })) + +// let window = Window(contentRect: NSMakeRect(event.locationInWindow.x + 100, event.locationInWindow.y, 100, 300), styleMask: [], backing: .buffered, defer: true) +// window.contentView?.wantsLayer = true +// window.contentView?.background = .random +// event.window?.addChildWindow(window, ordered: .above) + + menu.onShow = { [weak strongSelf] menu in + strongSelf?.contextMenu = menu + strongSelf?.onShowContextMenu() + } + menu.delegate = menu + menu.onClose = { [weak strongSelf] in + strongSelf?.contextMenu = nil + strongSelf?.onCloseContextMenu() + } + for item in items { + menu.addItem(item) + } + + menu.delegate = menu + + RunLoop.current.add(Timer.scheduledTimer(timeInterval: 0, target: strongSelf, selector: #selector(strongSelf.openPanelInRunLoop), userInfo: (event, menu), repeats: false), forMode: RunLoop.Mode.modalPanel) + + +// if #available(OSX 10.12, *) { +// RunLoop.current.perform(inModes: [.modalPanel], block: { +// NSMenu.popUpContextMenu(menu, with: event, for: strongSelf) +// }) +// } else { +// // Fallback on earlier versions +// } + + } + + })) + } + + + } + + @objc private func openPanelInRunLoop(_ timer:Foundation.Timer) { + if let (event, menu) = timer.userInfo as? (NSEvent, NSMenu) { + NSMenu.popUpContextMenu(menu, with: event, for: self) + } + } + + open override func menu(for event: NSEvent) -> NSMenu? { + return NSMenu() + } + + + + + open func onShowContextMenu() ->Void { + self.layer?.setNeedsDisplay() + } + + open func onCloseContextMenu() ->Void { + self.layer?.setNeedsDisplay() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open func updateMouse() { + + } + + public var isInsertionAnimated:Bool { + if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { + return true + } + return false + } + + public var rect:NSRect { + if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { + let rect = NSMakeRect(layer.position.x, layer.position.y, frame.width, frame.height) + return rect + } + return frame + } + + open override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + guard #available(OSX 10.12, *) else { + needsLayout = true + return + } + } + + open override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + guard #available(OSX 10.12, *) else { + needsLayout = true + return + } + } + + open override func viewDidMoveToSuperview() { + if superview != nil { + guard #available(OSX 10.12, *) else { + needsLayout = true + return + } + } + } + + open override func layout() { + super.layout() + } + + public func notifySubviewsToLayout(_ subview:NSView) -> Void { + for sub in subview.subviews { + sub.needsLayout = true + } + } + + + open override var needsLayout: Bool { + set { + super.needsLayout = newValue + if newValue { + guard #available(OSX 10.12, *) else { + layout() + notifySubviewsToLayout(self) + return + } + } + } + get { + return super.needsLayout + } + } + + deinit { + menuDisposable.dispose() + } + + open var mouseInsideField: Bool { + return false + } + + open override func copy() -> Any { + let view:View = View(frame:bounds) + view.backgroundColor = self.backdorColor + return view + } + + open func onRemove(_ animation: NSTableView.AnimationOptions) { + + } + + open func onInsert(_ animation: NSTableView.AnimationOptions) { + + } + + open func set(item:TableRowItem, animated:Bool = false) -> Void { + self.item = item; + updateColors() + } + + open func focusAnimation(_ innerId: AnyHashable?) { + + if animatedView == nil { + self.animatedView = RowAnimateView(frame: bounds) + self.animatedView?.isEventLess = true + self.addSubview(animatedView!) + animatedView?.backgroundColor = presentation.colors.focusAnimationColor + animatedView?.layer?.opacity = 0 + + } + animatedView?.stableId = item?.stableId + + + let animation: CABasicAnimation = makeSpringAnimation("opacity") + + animation.fromValue = animatedView?.layer?.presentation()?.opacity ?? 0 + animation.toValue = 0.5 + animation.autoreverses = true + animation.isRemovedOnCompletion = true + animation.fillMode = CAMediaTimingFillMode.forwards + + animation.delegate = CALayerAnimationDelegate(completion: { [weak self] completed in + if completed { + self?.animatedView?.removeFromSuperview() + self?.animatedView = nil + } + }) + animation.isAdditive = false + + animatedView?.layer?.add(animation, forKey: "opacity") + } + + open var interactableView: NSView { + return self + } + + open func shakeView() { + + } + + open func shakeViewWithData(_ data: Any) { + + } + + open func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) -> Void { + super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + open func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + open func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + open func mouseInside() -> Bool { + return super._mouseInside() + } + +} diff --git a/TGUIKit/TGUIKit/TableStickItem.swift b/submodules/TGUIKit/TGUIKit/TableStickItem.swift similarity index 91% rename from TGUIKit/TGUIKit/TableStickItem.swift rename to submodules/TGUIKit/TGUIKit/TableStickItem.swift index 2aa67f677c..55e3c4b463 100644 --- a/TGUIKit/TGUIKit/TableStickItem.swift +++ b/submodules/TGUIKit/TGUIKit/TableStickItem.swift @@ -15,6 +15,9 @@ open class TableStickItem: TableRowItem { return _stableId } + open var singletonItem: Bool { + return false + } open var headerHeight: CGFloat { return height diff --git a/TGUIKit/TGUIKit/TableStickView.swift b/submodules/TGUIKit/TGUIKit/TableStickView.swift similarity index 89% rename from TGUIKit/TGUIKit/TableStickView.swift rename to submodules/TGUIKit/TGUIKit/TableStickView.swift index 14b9c801d4..ad815e4809 100644 --- a/TGUIKit/TGUIKit/TableStickView.swift +++ b/submodules/TGUIKit/TGUIKit/TableStickView.swift @@ -18,6 +18,10 @@ open class TableStickView: TableRowView { } } + open var isAlwaysUp: Bool { + return false + } + open func updateIsVisible(_ visible: Bool, animated: Bool) { } diff --git a/submodules/TGUIKit/TGUIKit/TableView.swift b/submodules/TGUIKit/TGUIKit/TableView.swift new file mode 100644 index 0000000000..486a91e058 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TableView.swift @@ -0,0 +1,3066 @@ +// +// TableView.swift +// TGUIKit +// +// Created by keepcoder on 07/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit +import AVFoundation + +public enum TableSeparator { + case bottom; + case top; + case right; + case left; + case none; +} + +struct RevealAction { + + +} + +public class RowAnimateView: View { + public var stableId:AnyHashable? +} + +public final class RevealTableItemController : ViewController { + public let item: TableRowItem + public init(item: TableRowItem) { + self.item = item + super.init() + } +} + +public protocol RevealTableView { + var additionalRevealDelta: CGFloat { get } + var containerX: CGFloat { get } + var rightRevealWidth: CGFloat { get } + var leftRevealWidth: CGFloat { get } + var endRevealState:SwipeDirection? { get set } + + var width: CGFloat { get } + + func initRevealState() + + func moveReveal(delta: CGFloat) + func completeReveal(direction: SwipeDirection) +} + +public enum TableBackgroundMode { + case plain + case color(color: NSColor) + case gradient(top: NSColor, bottom: NSColor, rotation: Int32?) + case background(image: NSImage) + case tiled(image: NSImage) + + public var hasWallpapaer: Bool { + switch self { + case .plain: + return false + case let .color(color): + return color != presentation.colors.chatBackground + default: + return true + } + } +} + +public class TableResortController { + fileprivate let startTimeout: Double + fileprivate var resortRow: Int? + internal fileprivate(set) var resortView: TableRowView? { + didSet { + if resortView == nil { + oldValue?.isResorting = false + } else { + resortView?.isResorting = true + } + } + } + fileprivate var inResorting: Bool = false + fileprivate var startLocation: NSPoint = NSZeroPoint + fileprivate var startRowLocation: NSPoint = NSZeroPoint + + fileprivate var currentHoleIndex: Int? + fileprivate var prevHoleIndex: Int? + + public var resortRange: NSRange + fileprivate let start:(Int)->Void + fileprivate let resort:(Int)->Void + fileprivate let complete:(Int, Int)->Void + fileprivate let updateItems:(TableRowView?, [TableRowItem])->Void + public init(resortRange: NSRange, startTimeout: Double = 0.3, start:@escaping(Int)->Void, resort:@escaping(Int)->Void, complete:@escaping(Int, Int)->Void, updateItems:@escaping(TableRowView?, [TableRowItem])->Void = { _, _ in }) { + self.resortRange = resortRange + self.startTimeout = startTimeout + self.start = start + self.resort = resort + self.complete = complete + self.updateItems = updateItems + } + + func clear() { + resortView = nil + resortRow = nil + startLocation = NSZeroPoint + startRowLocation = NSZeroPoint + } + + var isResorting: Bool { + return resortView != nil + } + + func canResort(_ row: Int) -> Bool { + return resortRange.indexIn(row) + } +} + + + +public class UpdateTransition { + public let inserted:[(Int,T)] + public let updated:[(Int,T)] + public let deleted:[Int] + public let animateVisibleOnly: Bool + public init(deleted:[Int], inserted:[(Int,T)], updated:[(Int,T)], animateVisibleOnly: Bool = true) { + self.inserted = inserted + self.updated = updated + self.deleted = deleted + self.animateVisibleOnly = animateVisibleOnly + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + public var isEmpty:Bool { + return inserted.isEmpty && updated.isEmpty && deleted.isEmpty + } + + public var description: String { + return "inserted: \(inserted.count), updated:\(updated.count), deleted:\(deleted.count)" + } +} +public struct TableSearchVisibleData { + let cancelImage: CGImage? + let cancel:()->Void + let updateState: (SearchState)->Void + public init(cancelImage: CGImage? = nil, cancel: @escaping()->Void, updateState: @escaping(SearchState)->Void) { + self.cancelImage = cancelImage + self.cancel = cancel + self.updateState = updateState + } +} + +public enum TableSearchViewState : Equatable { + case none + case visible(TableSearchVisibleData) + + public static func ==(lhs: TableSearchViewState, rhs: TableSearchViewState) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false + } + case .visible: + if case .visible = rhs { + return true + } else { + return false + } + } + } +} + +public class TableUpdateTransition : UpdateTransition { + public let state:TableScrollState + public let animated:Bool + public let grouping:Bool + public let searchState: TableSearchViewState? + public init(deleted:[Int], inserted:[(Int,TableRowItem)], updated:[(Int,TableRowItem)], animated:Bool = false, state:TableScrollState = .none(nil), grouping:Bool = true, animateVisibleOnly: Bool = true, searchState: TableSearchViewState? = nil) { + self.animated = animated + self.state = state + self.grouping = grouping + self.searchState = searchState + super.init(deleted: deleted, inserted: inserted, updated: updated, animateVisibleOnly: animateVisibleOnly) + } + public override var description: String { + return "inserted: \(inserted.count), updated:\(updated.count), deleted:\(deleted.count), state: \(state), animated: \(animated)" + } + deinit { + var bp:Int = 0 + bp += 1 + } +} + +public final class TableEntriesTransition : TableUpdateTransition { + public let entries:T + public init(deleted:[Int], inserted:[(Int,TableRowItem)], updated:[(Int,TableRowItem)], entries:T, animated:Bool = false, state:TableScrollState = .none(nil), grouping:Bool = true) { + self.entries = entries + super.init(deleted: deleted, inserted: inserted, updated: updated, animated:animated, state: state, grouping:grouping) + } +} + +public protocol TableViewDelegate : class { + + func selectionDidChange(row:Int, item:TableRowItem, byClick:Bool, isNew:Bool) -> Void; + func selectionWillChange(row:Int, item:TableRowItem, byClick:Bool) -> Bool; + func isSelectable(row:Int, item:TableRowItem) -> Bool; + + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? +} + +extension TableViewDelegate { + func findGroupStableId(for stableId: AnyHashable) -> AnyHashable? { + return nil + } +} + +public enum TableSavingSide : Equatable { + case lower + case upper + case aroundIndex(AnyHashable) +} + +public func ==(lhs: TableSavingSide, rhs: TableSavingSide) -> Bool { + switch lhs { + case .lower: + if case .lower = rhs { + return true + } else { + return false + } + case .upper: + if case .upper = rhs { + return true + } else { + return false + } + case let .aroundIndex(id): + if case .aroundIndex(id) = rhs { + return true + } else { + return false + } + } +} + +public struct TableScrollFocus : Equatable { + public static func == (lhs: TableScrollFocus, rhs: TableScrollFocus) -> Bool { + return lhs.focus == rhs.focus + } + let focus:Bool + let action:((NSView)->Void)? + public init(focus: Bool, action: ((NSView)->Void)? = nil) { + self.focus = focus + self.action = action + } + + +} + +public enum TableScrollState :Equatable { + case top(id: AnyHashable, innerId: AnyHashable?, animated: Bool, focus: TableScrollFocus, inset: CGFloat); // stableId, animated, focus, inset + case bottom(id: AnyHashable, innerId: AnyHashable?, animated: Bool, focus: TableScrollFocus, inset: CGFloat); // stableId, animated, focus, inset + case center(id: AnyHashable, innerId: AnyHashable?, animated: Bool, focus: TableScrollFocus, inset: CGFloat); // stableId, animated, focus, inset + case saveVisible(TableSavingSide) + case none(TableAnimationInterface?); + case down(Bool); + case up(Bool); + case upOffset(Bool, CGFloat); +} + +public extension TableScrollState { + + func swap(to stableId:AnyHashable, innerId: AnyHashable? = nil) -> TableScrollState { + switch self { + case let .top(_, _, animated, focus, inset): + return .top(id: stableId, innerId: innerId, animated: animated, focus: focus, inset: inset) + case let .bottom(_, _, animated, focus, inset): + return .bottom(id: stableId, innerId: innerId, animated: animated, focus: focus, inset: inset) + case let .center(_, _, animated, focus, inset): + return .center(id: stableId, innerId: innerId, animated: animated, focus: focus, inset: inset) + default: + return self + } + } + + var animated: Bool { + switch self { + case let .top(_, _, animated, _, _): + return animated + case let .bottom(_, _, animated, _, _): + return animated + case let .center(_, _, animated, _, _): + return animated + case .down(let animated): + return animated + case .up(let animated): + return animated + case let .upOffset(animated, _): + return animated + default: + return false + } + } +} + + +protocol SelectDelegate : class { + func selectRow(index:Int) -> Void; +} + +private final class TableSearchView : View { + let searchView = SearchView(frame: NSZeroRect) + private var cancelButton:ImageButton? + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(searchView) + background = presentation.colors.background + border = [.Bottom] + searchView.frame = NSMakeRect(10, 10, frame.width - 20, frame.height - 20) + } + + func applySearchResponder() { + // _ = window?.makeFirstResponder(searchView.input) + searchView.layout() + _ = window?.makeFirstResponder(searchView.input) + searchView.change(state: .Focus, false) + } + + func updateDatas(_ datas: TableSearchVisibleData) { + if let cancelImage = datas.cancelImage { + if self.cancelButton == nil { + self.cancelButton = ImageButton() + self.addSubview(cancelButton!) + } + cancelButton!.removeAllHandlers() + cancelButton!.set(image: cancelImage, for: .Normal) + cancelButton!.set(handler: { _ in + datas.cancel() + }, for: .Click) + _ = cancelButton!.sizeToFit() + } else { + cancelButton?.removeFromSuperview() + cancelButton = nil + } + + + searchView.searchInteractions = SearchInteractions({ state, _ in + datas.updateState(state) + }, { state in + datas.updateState(state) + }) + + needsLayout = true + } + + override func layout() { + super.layout() + if let cancelButton = self.cancelButton { + cancelButton.centerY(x: frame.width - 10 - cancelButton.frame.width) + searchView.frame = NSMakeRect(10, 10, frame.width - cancelButton.frame.width - 30, frame.height - 20) + } else { + searchView.frame = NSMakeRect(10, 10, frame.width - 20, frame.height - 20) + } + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class TGFlipableTableView : NSTableView, CALayerDelegate { + + var bottomInset:CGFloat = 0 + private let longDisposable = MetaDisposable() + public var flip:Bool = true + + public weak var sdelegate:SelectDelegate? + weak var table:TableView? + var border:BorderType? + + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + backgroundColor = .clear + self.autoresizesSubviews = false + // canDrawSubviewsIntoLayer = true + usesAlternatingRowBackgroundColors = false + layerContentsRedrawPolicy = .never + } + + override func becomeFirstResponder() -> Bool { + return false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override var isFlipped: Bool { + return flip + } + + override func draw(_ dirtyRect: NSRect) { + + } + override var isOpaque: Bool { + return false + } + + + override func hitTest(_ point: NSPoint) -> NSView? { + return super.hitTest(point) + } + + override func addSubview(_ view: NSView) { + super.addSubview(view) + } + + func draw(_ layer: CALayer, in ctx: CGContext) { + + + + if let border = border { + + ctx.setFillColor(presentation.colors.border.cgColor) + + if border.contains(.Top) { + ctx.fill(NSMakeRect(0, NSHeight(self.frame) - .borderSize, NSWidth(self.frame), .borderSize)) + } + if border.contains(.Bottom) { + ctx.fill(NSMakeRect(0, 0, NSWidth(self.frame), .borderSize)) + } + if border.contains(.Left) { + ctx.fill(NSMakeRect(0, 0, .borderSize, NSHeight(self.frame))) + } + if border.contains(.Right) { + ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize, NSHeight(self.frame))) + } + + } + } + + private var beforeRange: NSRange = NSMakeRange(NSNotFound, 0) + private var offsetOfStartItem: NSPoint = .zero + private var mouseDown: Bool = false + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + + if let resortController = table?.resortController, beforeRange.length > 0, mouseDown { + if resortController.resortRange.indexIn(beforeRange.location) { + let point = self.convert(event.locationInWindow, from: nil) + let afterRange = self.rows(in: NSMakeRect(point.x, point.y, 1, 1)) + if afterRange != beforeRange { + self.table?.startResorting(beforeRange, point.offsetBy(dx: -offsetOfStartItem.x, dy: -offsetOfStartItem.y)) + } + } + } + } + + override func mouseDown(with event: NSEvent) { + if event.clickCount == 1 { + mouseDown = true + let point = self.convert(event.locationInWindow, from: nil) + let beforeRange = self.rows(in: NSMakeRect(point.x, point.y, 1, 1)) + self.beforeRange = beforeRange + if beforeRange.length > 0 { + if let resortController = table?.resortController{ + if resortController.resortRange.indexIn(beforeRange.location) { + self.offsetOfStartItem = point + } else if let table = table, !table.alwaysOpenRowsOnMouseUp { + sdelegate?.selectRow(index: beforeRange.location) + } + } else if let table = table, !table.alwaysOpenRowsOnMouseUp { + sdelegate?.selectRow(index: beforeRange.location) + } + } + } + } + + override func mouseUp(with event: NSEvent) { + longDisposable.set(nil) + let point = self.convert(event.locationInWindow, from: nil) + let range = self.rows(in: NSMakeRect(point.x, point.y, 1, 1)); + if range.length > 0, let table = table, mouseDown { + mouseDown = false + if let controller = self.table?.resortController { + if !controller.resortRange.indexIn(range.location) { + if controller.resortRow == nil, beforeRange.location == range.location && table.alwaysOpenRowsOnMouseUp { + sdelegate?.selectRow(index: range.location) + } + return + } + if range.length > 0, beforeRange.location == range.location { + sdelegate?.selectRow(index: range.location) + } + } else if table.alwaysOpenRowsOnMouseUp, beforeRange.location == range.location { + sdelegate?.selectRow(index: range.location) + } + } + mouseDown = false + } + + + deinit { + longDisposable.dispose() + } + + override func setFrameSize(_ newSize: NSSize) { + let oldWidth: CGFloat = frame.width + let oldHeight: CGFloat = frame.height + + if newSize.width > 0 || newSize.height > 0 { + super.setFrameSize(newSize) + + if oldWidth != frame.width, newSize.width > 0 && newSize.height > 0 { + if let table = table { + table.layoutIfNeeded(with: table.visibleRows(), oldWidth: oldWidth) + } + } else if oldHeight != frame.height { + table?.reloadHeightItems() + } + } + } + + + + var liveWidth:CGFloat = 0 + + override func viewWillStartLiveResize() { + super.viewWillStartLiveResize() + liveWidth = frame.width + } + + + override func viewDidEndLiveResize() { + super.viewDidEndLiveResize() + if liveWidth != frame.width { + liveWidth = frame.width + table?.layoutItems() + } + } + + override func layout() { + super.layout() + } + + + + +} + +public protocol InteractionContentViewProtocol : class { + func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? + func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) + func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) + func videoTimebase(for stableId: AnyHashable) -> CMTimebase? + func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) + +} + +public class TableScrollListener : NSObject { + fileprivate let uniqueId:UInt32 = arc4random() + public var handler:(ScrollPosition)->Void + fileprivate let dispatchWhenVisibleRangeUpdated: Bool + fileprivate var first: Bool = true + public init(dispatchWhenVisibleRangeUpdated: Bool = true, _ handler:@escaping(ScrollPosition)->Void) { + self.dispatchWhenVisibleRangeUpdated = dispatchWhenVisibleRangeUpdated + self.handler = handler + } + +} + +public struct TableAutohide { + public private(set) weak var item: TableRowItem? + let hideUntilOverscroll: Bool + let hideHandler:(Bool)->Void + public init(item: TableRowItem?, hideUntilOverscroll: Bool = false, hideHandler:@escaping(Bool)->Void = { _ in }) { + self.item = item + self.hideUntilOverscroll = hideUntilOverscroll + self.hideHandler = hideHandler + } +} + +open class TableView: ScrollView, NSTableViewDelegate,NSTableViewDataSource,SelectDelegate,InteractionContentViewProtocol, AppearanceViewProtocol { + + private var searchView: TableSearchView? + private var rightBorder: View? = nil + public var separator:TableSeparator = .none + + public var getBackgroundColor:()->NSColor = { presentation.colors.background } { + didSet { + if super.layer?.backgroundColor != .clear { + super.layer?.backgroundColor = self.getBackgroundColor().cgColor + } + self.needsDisplay = true + + } + } + + + var list:[TableRowItem] = [TableRowItem](); + var tableView:TGFlipableTableView + + public var view: NSTableView { + return tableView + } + + weak public var delegate:TableViewDelegate? + private var trackingArea:NSTrackingArea? + private var listhash:[AnyHashable:TableRowItem] = [AnyHashable:TableRowItem](); + + private let mergePromise:Promise = Promise() + private let mergeDisposable:MetaDisposable = MetaDisposable() + + public var resortController: TableResortController? { + didSet { + + } + } + + public var selectedhash:AnyHashable? = nil + public var highlitedHash:AnyHashable? = nil + + + private var updating:Bool = false + + private var previousScroll:ScrollPosition? + public var needUpdateVisibleAfterScroll:Bool = false + private var scrollHandler:(_ scrollPosition:ScrollPosition) ->Void = {_ in} + + private var backgroundView: ImageView? + + private var scrollListeners:[TableScrollListener] = [] + + public var alwaysOpenRowsOnMouseUp: Bool = true + + public var autohide: TableAutohide? + + public var emptyChecker: (([TableRowItem]) -> Bool)? = nil + + public var updatedItems:(([TableRowItem])->Void)? { + didSet { + updatedItems?(self.list) + } + } + + public var beforeSetupItem:((TableRowView, TableRowItem)->Void)? + public var afterSetupItem:((TableRowView, TableRowItem)->Void)? + + private var nextScrollEventIsAnimated: Bool = false + + public var emptyItem:TableRowItem? { + didSet { + emptyItem?.table = self + updateEmpties() + } + } + private var emptyView:TableRowView? + + public func addScroll(listener:TableScrollListener) { + var found: Bool = false + for enumerate in scrollListeners { + if enumerate.uniqueId == listener.uniqueId { + found = true + break + } + } + if !found { + scrollListeners.append(listener) + } + } + + + public var bottomInset:CGFloat = 0 { + didSet { + tableView.bottomInset = bottomInset + } + } + + open override func viewDidChangeBackingProperties() { + + } + + open func updateLocalizationAndTheme(theme: PresentationTheme) { + if super.layer?.backgroundColor != .clear { + super.layer?.backgroundColor = self.getBackgroundColor().cgColor + } + stickView?.updateColors() + rightBorder?.backgroundColor = theme.colors.border + //tableView.background = .clear + // super.layer?.backgroundColor = .clear + self.needsDisplay = true + // tableView.needsDisplay = true + // clipView.needsDisplay = true + } + + + public func removeScroll(listener:TableScrollListener) { + var index:Int = 0 + var found:Bool = false + for enumerate in scrollListeners { + if enumerate.uniqueId == listener.uniqueId { + found = true + break + } + index += 1 + } + + if found { + scrollListeners.remove(at: index) + } + + } + + public var count:Int { + get { + return self.list.count + } + } + +// open override func setNeedsDisplay(_ invalidRect: NSRect) { +// +// } + + open override var isFlipped: Bool { + return true + } + + public override init(frame frameRect: NSRect) { + self.tableView = TGFlipableTableView(frame:frameRect) + self.tableView.wantsLayer = true + self.tableView.autoresizesSubviews = false + super.init(frame: frameRect) + + updateAfterInitialize(isFlipped:true, drawBorder: false) + } + + public init(frame frameRect: NSRect, isFlipped:Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { + self.tableView = TGFlipableTableView(frame:frameRect) + self.tableView.wantsLayer = true + self.tableView.autoresizesSubviews = false + super.init(frame: frameRect) + updateAfterInitialize(isFlipped: isFlipped, drawBorder: drawBorder) + } + + public convenience init() { + self.init(frame: NSZeroRect) + } + + public var border:BorderType? { + didSet { + self.clipView.border = border + self.tableView.border = border + + if border == [.Right] { + if rightBorder == nil { + rightBorder = View() + rightBorder?.backgroundColor = presentation.colors.border + addSubview(rightBorder!) + needsLayout = true + } + } else { + rightBorder?.removeFromSuperview() + rightBorder = nil + } + } + } + + open override var backgroundColor: NSColor { + didSet { +// documentView?.background = backgroundColor +// contentView.background = backgroundColor +// self.clipView.backgroundColor = backgroundColor +// self.clipView.needsDisplay = true +// documentView?.needsDisplay = true + } + } + + public func setIsFlipped(_ flipped: Bool) { + self.tableView.flip = flipped + } + + public func updateAfterInitialize(isFlipped:Bool = true, bottomInset:CGFloat = 0, drawBorder: Bool = false) { + + self.tableView.flip = isFlipped + + clipView.copiesOnScroll = true + + // self.scrollsDynamically = true + // self.verticalLineScroll = 0 + //self.verticalScrollElasticity = .none + self.autoresizesSubviews = false + + self.tableView.table = self + + self.bottomInset = bottomInset + self.tableView.bottomInset = bottomInset + + if drawBorder { + self.clipView.border = BorderType([.Right]) + self.tableView.border = BorderType([.Right]) + } + + self.hasVerticalScroller = true; + + self.documentView = self.tableView; + self.autoresizesSubviews = true; + self.autoresizingMask = [.width, .height] + + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.sdelegate = self + self.tableView.allowsColumnReordering = false + self.tableView.headerView = nil; + self.tableView.intercellSpacing = NSMakeSize(0, 0) + + let tableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "column")) + tableColumn.width = frame.width + + self.tableView.addTableColumn(tableColumn) + + + mergeDisposable.set(mergePromise.get().start(next: { [weak self] (transition) in + self?.merge(with: transition) + })) + + } + + private func findBackgroundControllerView(view: NSView) -> BackgroundView? { + if let superview = view.superview { + for subview in superview.subviews { + if let subview = subview as? BackgroundView { + return subview + } else { + if let superview = subview.superview { + if let result = findBackgroundControllerView(view: superview) { + return result + } + } + } + } + } + return nil + } + + private var findBackgroundControllerView: BackgroundView? { + return self.findBackgroundControllerView(view: self) + } + + open override func layout() { + super.layout() + if let emptyView = emptyView, let superview = superview { + emptyView.frame = findBackgroundControllerView?.bounds ?? bounds + emptyView.centerX(y: superview.frame.height - emptyView.frame.height) + } + if let searchView = searchView { + searchView.setFrameSize(NSMakeSize(frame.width, searchView.frame.height)) + } + + if needsLayouItemsOnNextTransition { + layoutItems() + } + if let rightBorder = rightBorder { + rightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) + } + + if let stickView = stickView { + stickView.frame = NSMakeRect(stickView.frame.minX, stickView.frame.minY, frame.width, stickView.frame.height) + } + } + + + func layoutIfNeeded(with range:NSRange, oldWidth:CGFloat) { + + + let visibleItems = self.visibleItems() + + for i in range.min ..< range.max { + let item = self.item(at: i) + let before = item.heightValue + let updated = item.makeSize(tableView.frame.width, oldWidth: oldWidth) + let after = item.heightValue + if (before != after && updated) || item.instantlyResize { + reloadData(row: i, animated: false) + noteHeightOfRow(i, false) + } + } + if !tableView.inLiveResize && oldWidth != 0 { + saveScrollState(visibleItems) + } + } + + private var liveScrollStartPosition: NSPoint? + + public var _scrollWillStartLiveScrolling:(()->Void)? + public var _scrollDidLiveScrolling:(()->Void)? + public var _scrollDidEndLiveScrolling:(()->Void)? + + open func scrollWillStartLiveScrolling() { + liveScrollStartPosition = documentOffset + _scrollWillStartLiveScrolling?() + } + private var liveScrollStack:[CGFloat] = [] + open func scrollDidLiveScrolling() { + + liveScrollStack.append(documentOffset.y) + if documentOffset.y < -10, let liveScrollStartPosition = liveScrollStartPosition, let autohide = self.autohide, let item = autohide.item { + + if liveScrollStartPosition.y <= 0 && liveScrollStack.max() ?? 0 <= item.height / 2 { + if item.isAutohidden { + item.unhideItem(animated: true) + autohide.hideHandler(false) + liveScrollStack.removeAll() + } + } + } + _scrollDidLiveScrolling?() + } + + + public var updateScrollPoint:((NSPoint)->NSPoint)? = nil + + open override func scroll(_ clipView: NSClipView, to point: NSPoint) { + var point = point + if let updateScrollPoint = updateScrollPoint { + point = updateScrollPoint(point) + } + clipView.scroll(to: point) + } + + open func scrollDidChangedBounds() { + if let autohide = autohide, let item = autohide.item, autohide.hideUntilOverscroll, let _ = liveScrollStartPosition { + let rect = self.rectOf(item: item) + + if !item.isAutohidden, documentOffset.y >= rect.maxY { + item.hideItem(animated: false, reload: false) + + liveScrollStartPosition = nil + liveScrollStack.removeAll() + self.merge(with: TableUpdateTransition(deleted: [item.index], inserted: [(item.index, item)], updated: [], animated: false, state: .saveVisible(.upper))) + + autohide.hideHandler(true) + } + } + } + + open func scrollDidEndLiveScrolling() { + if let autohide = self.autohide { + if let autohideItem = autohide.item { + let rect = self.rectOf(item: autohideItem) + if (documentOffset.y > (rect.minY + (rect.height / 2))) && documentOffset.y < rect.maxY { +// scroll(to: .top(id: autohideItem.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: autohideItem.height), completion: { [weak self] _ in +// self?.liveScrollStartPosition = nil +// }) + } else if documentOffset.y > 0 && documentOffset.y < rect.maxY { +// scroll(to: .top(id: autohideItem.stableId, innerId: nil, animated: true, focus: .init(focus: false), inset: 0), completion: { [weak self] _ in +// self?.liveScrollStartPosition = nil +// }) + } else { + liveScrollStartPosition = nil + } + } else { + liveScrollStartPosition = nil + } + } else { + liveScrollStartPosition = nil + } + liveScrollStack.removeAll() + _scrollDidEndLiveScrolling?() + } + + open override func viewDidMoveToSuperview() { + if superview != nil { + let clipView = self.contentView + + NotificationCenter.default.addObserver(forName: NSScrollView.didEndLiveScrollNotification, object: self, queue: nil, using: { [weak self] notification in + self?.scrollDidEndLiveScrolling() + }) + + NotificationCenter.default.addObserver(forName: NSScrollView.willStartLiveScrollNotification, object: self, queue: nil, using: { [weak self] notification in + self?.scrollWillStartLiveScrolling() + }) + + NotificationCenter.default.addObserver(forName: NSScrollView.didLiveScrollNotification, object: self, queue: nil, using: { [weak self] notification in + self?.scrollDidLiveScrolling() + }) + + NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main, using: { [weak self] notification in + Queue.mainQueue().justDispatch { [weak self] in + self?.scrollDidChangedBounds() + } + if let strongSelf = self { + let reqCount = strongSelf.count / 6 + + strongSelf.updateStickAfterScroll(strongSelf.nextScrollEventIsAnimated) + strongSelf.nextScrollEventIsAnimated = false + let scroll = strongSelf.scrollPosition(strongSelf.visibleRows()) + + if (!strongSelf.updating && !strongSelf.clipView.isAnimateScrolling) { + + let range = scroll.current.visibleRows + + if(scroll.current.direction != strongSelf.previousScroll?.direction && scroll.current.rect != strongSelf.previousScroll?.rect) { + + switch(scroll.current.direction) { + case .top: + if(range.location <= reqCount) { + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + case .bottom: + if(strongSelf.count - (range.location + range.length) <= reqCount) { + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + case .none: + strongSelf.scrollHandler(scroll.current) + strongSelf.previousScroll = scroll.current + + } + } + + } + for listener in strongSelf.scrollListeners { + if !listener.dispatchWhenVisibleRangeUpdated || listener.first || !NSEqualRanges(scroll.current.visibleRows, scroll.previous.visibleRows) { + listener.handler(scroll.current) + listener.first = false + } + } + + if strongSelf.needUpdateVisibleAfterScroll { + let range = strongSelf.visibleRows() + for i in range.location ..< range.location + range.length { + if let view = strongSelf.viewNecessary(at: i) { + view.updateMouse() + } + } + } + } + }) + } else { + NotificationCenter.default.removeObserver(self) + } + } + + + private var stickClass:AnyClass? + private var stickView:TableStickView? + + public var p_stickView: NSView? { + return stickView + } + + private var stickItem:TableStickItem? { + didSet { + if stickItem != oldValue { + if let stickHandler = stickHandler { + stickHandler(stickItem) + } + } + } + } + private var stickHandler:((TableStickItem?)->Void)? + private var firstTime: Bool = false + public func set(stickClass:AnyClass?, visible: Bool = true, handler:@escaping(TableStickItem?)->Void) { + self.stickClass = stickClass + self.stickHandler = handler + self.firstTime = true + if let stickClass = stickClass as? TableStickItem.Type { + if stickView == nil { + let stickItem:TableStickItem = stickClass.init(frame.size) + + self.stickItem = stickItem + if visible { + let vz = stickItem.viewClass() as! TableStickView.Type + stickView = vz.init(frame:NSMakeRect(0, 0, NSWidth(self.frame), stickItem.heightValue)) + stickView!.header = true + stickView!.set(item: stickItem, animated: false) + // tableView.addSubview(stickView!) + } + } + + updateStickAfterScroll(false) + + } else { + stickView?.removeFromSuperview() + stickView = nil + stickItem = nil + } + + } + + func optionalItem(at:Int) -> TableRowItem? { + return at < count && at >= 0 ? self.item(at: at) : nil + } + + private var needsLayouItemsOnNextTransition:Bool = false + + public func layouItemsOnNextTransition() { + needsLayouItemsOnNextTransition = true + } + public func layoutItems() { + + let visibleItems = self.visibleItems() + + beginTableUpdates() + enumerateItems { item in + if item.width != frame.width { + _ = item.makeSize(frame.width, oldWidth: item.width) + reloadData(row: item.index, animated: false) + NSAnimationContext.current.duration = 0.0 + NSAnimationContext.current.timingFunction = nil + tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: item.index)) + } + return true + } + + endTableUpdates() + + saveScrollState(visibleItems) + + needsLayouItemsOnNextTransition = false + } + + private func saveScrollState(_ visibleItems: [(TableRowItem,CGFloat,CGFloat)]) -> Void { + //, clipView.bounds.minY > 0 + if !visibleItems.isEmpty, documentOffset.y > 0 { + var nrect:NSRect = NSZeroRect + + let strideTo:StrideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) + + for i in strideTo { + let visible = visibleItems[i] + if let item = self.item(stableId: visible.0.stableId) { + + nrect = rectOf(item: item) + + if let view = viewNecessary(at: i) { + if view.isInsertionAnimated { + break + } + } + + let y:CGFloat + if !tableView.isFlipped { + y = nrect.minY - (frame.height - visible.1) + nrect.height + } else { + y = nrect.minY - visible.1 + } + + self.contentView.scroll(to: NSMakePoint(0, y)) + // reflectScrolledClipView(clipView) + // flashScrollers() + break + } + } + } + } + + private let stickTimeoutDisposable = MetaDisposable() + private var previousStickMinY: CGFloat? = nil + public func updateStickAfterScroll(_ animated: Bool) -> Void { + let range = self.visibleRows() + + if let stickClass = stickClass, !updating { + // if documentSize.height > frame.height { + + let flipped = tableView.isFlipped + + + var index:Int = flipped ? range.location : range.location + range.length - 1 + + + + let scrollInset = self.documentOffset.y + (flipped ? 0 : frame.height) + var item:TableRowItem? = optionalItem(at: index) + + if !flipped { + while let s = item, !s.isKind(of: stickClass) { + index += 1 + item = self.optionalItem(at: index) + } + } else { + while let s = item, !s.isKind(of: stickClass) { + index -= 1 + item = self.optionalItem(at: index) + } + } + + + if item == nil && !flipped { + index = range.location + range.length + while item == nil && index < count { + if let s = self.optionalItem(at: index), s.isKind(of: stickClass) { + item = s + } + index += 1 + } + } + + + if let someItem = item as? TableStickItem { + var currentStick:TableStickItem? + + if !flipped { + for index in stride(from: someItem.index - 1, to: -1, by: -1) { + let item = self.optionalItem(at: index) + if let item = item, item.isKind(of: stickClass) { + currentStick = item as? TableStickItem + break + } + } + } else { + for index in someItem.index + 1 ..< count { + let item = self.optionalItem(at: index) + if let item = item, item.isKind(of: stickClass) { + currentStick = item as? TableStickItem + break + } + } + } + + if someItem.singletonItem { + currentStick = someItem + } + + if stickView?.item != item { + stickView?.set(item: someItem, animated: animated) + stickView?.updateIsVisible(!firstTime, animated: animated) + } + + if let item = stickItem { + if let view = (viewNecessary(at: item.index) as? TableStickView) { + view.updateIsVisible((!firstTime || !view.header), animated: animated) + } + } + + stickItem = currentStick ?? someItem + + if let stickView = stickView { + let scrollerIndex = subviews.firstIndex(where: { $0 is NSScroller }) + let stickViewIndex = subviews.firstIndex(where: { $0 is TableStickView }) + if let scrollerIndex = scrollerIndex, stickViewIndex == nil || stickViewIndex! > scrollerIndex { + addSubview(stickView, positioned: .below, relativeTo: subviews[scrollerIndex]) + } + } + + stickView?.setFrameSize(tableView.frame.width, someItem.heightValue) + let itemRect:NSRect = someItem.view?.visibleRect ?? NSZeroRect + + if let item = stickItem, item.isKind(of: stickClass), let stickView = stickView { + let rect:NSRect = tableView.rect(ofRow: item.index) + let dif:CGFloat + if currentStick != nil { + dif = min(scrollInset - rect.maxY, item.heightValue) + } else { + dif = item.heightValue + } + var yTopOffset:CGFloat + if !flipped { + yTopOffset = min((scrollInset - rect.maxY) - rect.height, 0) + } else { + yTopOffset = min(-(rect.height + (scrollInset - rect.minY)), 0) + } + if yTopOffset <= -rect.height { + yTopOffset = 0 + } + if scrollInset <= 0, item.singletonItem { + yTopOffset = abs(scrollInset) + } + stickView.change(pos: NSMakePoint(0, yTopOffset), animated: animated) + stickView.header = abs(dif) <= item.heightValue + + if !firstTime { + let rows:[Int] = [tableView.row(at: NSMakePoint(0, min(scrollInset - stickView.frame.height, documentSize.height - stickView.frame.height))), tableView.row(at: NSMakePoint(0, scrollInset))] + var applied: Bool = false + for row in rows { + let row = min(max(0, row), list.count - 1) + if let dateItem = self.item(at: row) as? TableStickItem, let view = dateItem.view as? TableStickView { +// NSLog("\(yTopOffset < 0 && documentOffset.y > 0), \(yTopOffset)") + view.updateIsVisible(yTopOffset < 0 , animated: false) + applied = true + } + } + if !applied { + self.enumerateViews(with: { view in + (view as? TableStickView)?.updateIsVisible(true, animated: false) + return true + }) + } + + } + + + if previousStickMinY == nil { + previousStickMinY = documentOffset.y + } + + + + if previousStickMinY != documentOffset.y { + stickView.isHidden = false + previousStickMinY = documentOffset.y + if !animated || stickView.layer?.opacity != 0 { + stickView.updateIsVisible(true, animated: true) + firstTime = false + } + } + + if tableView.isFlipped { + stickView.isHidden = documentOffset.y <= 0 && !item.singletonItem// && !stickView.isAlwaysUp + } else { + stickView.isHidden = documentSize.height <= frame.height + } + + stickTimeoutDisposable.set((Signal.single(Void()) |> delay(2.0, queue: Queue.mainQueue())).start(next: { [weak stickView] in + + if itemRect.height == 0, let stickView = stickView { + stickView.updateIsVisible(false, animated: true) + } + })) + + } + + } else { + if index == -1 { + if animated, let stickView = self.stickView { + stickView.change(pos: NSMakePoint(0, -stickView.frame.height), animated: animated, removeOnCompletion: false, completion: { [weak stickView] _ in + stickView?.removeFromSuperview() + }) + } else { + stickView?.removeFromSuperview() + } + } else { + stickView?.setFrameOrigin(0, 0) + stickView?.header = true + } + + self.enumerateViews(with: { view in + (view as? TableStickView)?.updateIsVisible(true, animated: false) + return true + }) + } + + // } + } + } + + + public func resetScrollNotifies() ->Void { + self.previousScroll = nil + updateScroll() + } + + public func scrollUp(offset: CGFloat = 30.0) { + self.clipView.scroll(to: NSMakePoint(0, min(clipView.bounds.minY + offset, clipView.bounds.maxY)), animated: true) + self.reflectScrolledClipView(clipView) + } + + + public func scrollDown(offset: CGFloat = 30.0) { + self.clipView.scroll(to: NSMakePoint(0, max(clipView.bounds.minY - offset, 0)), animated: true) + self.reflectScrolledClipView(clipView) + } + + public func notifyScrollHandlers() -> Void { + let scroll = scrollPosition(visibleRows()).current + for listener in scrollListeners { + listener.handler(scroll) + } + } + + public var topVisibleRow:Int? { + let visible = visibleItems() + if !isFlipped { + return visible.first?.0.index + } else { + return visible.last?.0.index + } + } + + public var bottomVisibleRow:Int? { + let visible = visibleItems() + if isFlipped { + return visible.first?.0.index + } else { + return visible.last?.0.index + } + } + + open override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin); + self.updateTrackingAreas(); + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func selectedItem() -> TableRowItem? { + + let hash = selectedhash + if let hash = hash { + return self.item(stableId:hash) + } + return nil + } + + public func isSelected(_ item:TableRowItem) ->Bool { + return selectedhash == item.stableId + } + + public func highlightedItem() -> TableRowItem? { + + let hash = highlitedHash + if let hash = hash { + return self.item(stableId: hash) + } + return nil + } + + public func isHighlighted(_ item:TableRowItem) ->Bool { + return highlitedHash == item.stableId + } + + public func item(stableId:AnyHashable) -> TableRowItem? { + return self.listhash[stableId]; + } + + public func index(of:TableRowItem) -> Int? { + if let it = self.listhash[of.stableId] { + return self.list.firstIndex(of: it) + } + return nil + } + + public func index(hash:AnyHashable) -> Int? { + + if let it = self.listhash[hash] { + return it.index + } + + return nil + } + + fileprivate func startResorting(_ range: NSRange, _ offset: CGPoint) { + guard let window = _window else {return} + + let point = tableView.convert(window.mouseLocationOutsideOfEventStream, from: nil) + if range.length > 0, let controller = resortController, controller.canResort(range.location), let view = viewNecessary(at: range.location) { + controller.resortRow = range.location + controller.currentHoleIndex = range.location + controller.resortView = view + controller.startLocation = NSMakePoint(round(point.y), round(point.y)) + controller.startRowLocation = view.frame.origin.offsetBy(dx: 0, dy: round(offset.y)) + controller.start(range.location) + + view.frame = convert(view.frame, from: view.superview) + + + + addSubview(view) + view.isHidden = false + window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + guard let controller = self?.resortController, controller.isResorting else {return .rejected} + self?.stopResorting() + return .invoked + }, with: self, for: .leftMouseUp, priority: .modal) + + var first: Bool = true + + window.set(mouseHandler: { [weak self] event -> KeyHandlerResult in + if let controller = self?.resortController, let view = controller.resortView, let `self` = self { + + self.contentView.autoscroll(with: event) + + var point = self.tableView.convert(event.locationInWindow, from: nil) + point.x = 0 + let difference = (controller.startLocation.y - point.y) + + if view.superview != self { + view.frame = self.convert(view.frame, from: view.superview) + view.set(item: self.item(at: range.location), animated: false) + controller.resortView = view + self.addSubview(view) + } + view.isHidden = false + + var newPoint = NSMakePoint(view.frame.minX, max(controller.startRowLocation.y - difference, 0)) + newPoint.y -= self.documentOffset.y //self.convert(newPoint, from: self.tableView) + newPoint = NSMakePoint(round(newPoint.x), round(newPoint.y)) + if first { + view.layer?.animatePosition(from: NSMakePoint(0, view.frame.minY - newPoint.y), to: .zero, duration: 0.2, timingFunction: .spring, removeOnCompletion: true, additive: true) + } + view.setFrameOrigin(newPoint) + first = false + self.updateMovableItem(point) + return .invoked + } else { + return .rejected + } + }, with: self, for: .leftMouseDragged, priority: .modal) + + + } + } + + + fileprivate func stopResorting() { + if let controller = resortController, let current = controller.currentHoleIndex, let start = controller.resortRow { + + + NSAnimationContext.runAnimationGroup({ ctx in + if controller.resortRange.location != NSNotFound { + var y: CGFloat = 0 + for i in 0 ..< controller.resortRange.location { + y += self.list[i].heightValue + } + for i in controller.resortRange.location ..< controller.resortRange.max { + if let resortView = controller.resortView { + if i == current { + if current > start { + y -= (resortView.frame.height - item(at: i).heightValue) + } + y -= self.documentOffset.y + let point = NSMakePoint(resortView.frame.minX, y) + //convert(, from: tableView) + resortView.animator().setFrameOrigin(point) + y = 0 + break + } + y += item(at: i).heightValue + } + } + } + }, completionHandler: { + let view = controller.resortView + controller.clear() + if let view = view { + view.frame = self.tableView.convert(view.frame, from: view.superview) + self.tableView.addSubview(view) + } + if controller.resortRange.location != NSNotFound { + controller.complete(start, current) + } + }) + + + _window?.remove(object: self, for: .leftMouseUp) + } + } + + + + + private var maxResortHeight: CGFloat { + guard let controller = resortController else {return 0} + var height: CGFloat = 0 + for i in 0 ..< controller.resortRange.max { + height += item(at: i).heightValue + } + return height + } + + + private func moveHole(at fromIndex: Int, to toIndex: Int, animated: Bool) { + var y: CGFloat = 0 + + + guard let controller = resortController, let resortRow = controller.resortRow, let resortView = controller.resortView else {return} + if controller.resortRange.location == NSNotFound { + self.stopResorting() + return + } + for i in 0 ..< controller.resortRange.location { + y += self.list[i].heightValue + } + + if toIndex > resortRow { + + y = maxResortHeight + + for i in stride(from: controller.resortRange.max - 1, to: -1, by: -1) { + let view = viewNecessary(at: i, makeIfNecessary: false) + if i == toIndex { + y -= resortView.frame.height// view.frame.height + } + if view != controller.resortView { + y -= self.item(at: i).heightValue + view?.animator().setFrameOrigin(0, y) + } + } + } else { + for i in controller.resortRange.location ..< controller.resortRange.max { + let view = viewNecessary(at: i, makeIfNecessary: false) + if i == toIndex { + y += resortView.frame.height + } + if view != controller.resortView { + view?.animator().setFrameOrigin(0, y) + y += self.item(at: i).heightValue + } + } + } + + + } + + private func updateMovableItem(_ point: NSPoint) { + + + guard let controller = resortController else {return} + + let row = min(max(tableView.row(at: point), controller.resortRange.location), controller.resortRange.max - 1) + controller.prevHoleIndex = controller.currentHoleIndex + controller.currentHoleIndex = row + if controller.prevHoleIndex != controller.currentHoleIndex { + moveHole(at: controller.prevHoleIndex!, to: controller.currentHoleIndex!, animated: true) + controller.updateItems(controller.resortView, self.list.filter { controller.canResort($0.index) }) + } + } + + + private var _window:Window? { + return window as? Window + } + + public func insert(item:TableRowItem, at:Int = 0, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Bool { + assert(self.item(stableId:item.stableId) == nil, "inserting existing row inTable: \(self.item(stableId:item.stableId)!.className), new: \(item.className), stableId: \(item.stableId)") + self.listhash[item.stableId] = item; + let at = min(at, list.count) + self.list.insert(item, at: at); + item.table = self; + item._index = at + let animation = animation != .none ? item.animatable ? animation : .none : .none + NSAnimationContext.current.duration = animation != .none ? 0.2 : 0.0 + NSAnimationContext.current.timingFunction = animation == .none ? nil : CAMediaTimingFunction(name: .easeOut) + if(redraw) { + self.tableView.insertRows(at: IndexSet(integer: at), withAnimation: animation) + self.tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: at)) + } + + return true; + + } + + public func addItem(item:TableRowItem, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Bool { + return self.insert(item: item, at: self.count, redraw: redraw, animation:animation) + } + + public func insert(items:[TableRowItem], at:Int = 0, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { + + + var current:Int = 0; + for item in items { + + if(self.insert(item: item, at: at + current, redraw: false)) { + current += 1; + } + + } + + if(current != 0 && redraw) { + self.tableView.insertRows(at: IndexSet(integersIn: at ..< current + at), withAnimation: animation) + self.tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integersIn: at ..< current + at)) + + } + + } + + + public var firstItem:TableRowItem? { + return self.list.first + } + + public var lastItem:TableRowItem? { + return self.list.last + } + + public func noteHeightOfRow(_ row:Int, _ animated:Bool = true) { + if !animated { + NSAnimationContext.current.duration = 0 + NSAnimationContext.current.timingFunction = nil + } + tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) + } + + + + public func reloadData(row:Int, animated:Bool = false, options: NSTableView.AnimationOptions = .effectFade, presentAsNew: Bool = false) -> Void { + if let view = self.viewNecessary(at: row) { + let item = self.item(at: row) + if view.isKind(of: item.viewClass()) && !presentAsNew { + if view.frame.height != item.heightValue { + NSAnimationContext.current.duration = animated ? 0.2 : 0.0 + NSAnimationContext.current.timingFunction = CAMediaTimingFunction(name: .easeOut) + tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) + } + + let height:CGFloat = item.heightValue + let width:CGFloat = self is HorizontalTableView ? item.width : frame.width + + + view.change(size: NSMakeSize(width, height), animated: animated) + view.set(item: item, animated: animated) + } else { + self.tableView.removeRows(at: IndexSet(integer: row), withAnimation: !animated ? .none : options) + self.tableView.insertRows(at: IndexSet(integer: row), withAnimation: !animated ? .none : options) + } + } else { + NSAnimationContext.current.duration = 0.0 + NSAnimationContext.current.timingFunction = nil + tableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: row)) + } + //self.moveItem(from: row, to: row) + } + + fileprivate func reloadHeightItems() { + self.enumerateItems { item -> Bool in + if item.reloadOnTableHeightChanged { + self.reloadData(row: item.index) + } + return true + } + } + + public func moveItem(from:Int, to:Int, changeItem:TableRowItem? = nil, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { + + + var item:TableRowItem = self.item(at:from); + let animation: NSTableView.AnimationOptions = animation != .none ? item.animatable ? animation : .none : .none + NSAnimationContext.current.duration = animation != .none ? NSAnimationContext.current.duration : 0.0 + NSAnimationContext.current.timingFunction = animation != .none ? CAMediaTimingFunction(name: .easeOut) : nil + + if let change = changeItem { + assert(change.stableId == item.stableId) + change.table = self + self.listhash.removeValue(forKey: item.stableId) + self.listhash[change.stableId] = change + item = change + } + + self.list.remove(at: from); + + self.list.insert(item, at: to); + + item._index = to + + if(redraw) { + + if from == to { + self.reloadData(row: to) + } else { + self.tableView.removeRows(at: IndexSet(integer:from), withAnimation: from == to ? .none : animation) + self.tableView.insertRows(at: IndexSet(integer:to), withAnimation: from == to ? .none : animation) + } + + } + + } + + public func beginUpdates() -> Void { + updating = true + updateScroll(visibleRows()) + self.previousScroll = nil + // CATransaction.begin() + } + + public func endUpdates() -> Void { + updating = false + updateScroll(visibleRows()) + self.previousScroll = nil + + // CATransaction.commit() + } + + public func rectOf(item:TableRowItem) -> NSRect { + return self.tableView.rect(ofRow: item.index) + } + + public func rectOf(index:Int) -> NSRect { + return self.tableView.rect(ofRow: index) + } + + public func remove(at:Int, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { + if at < count { + let item = self.list.remove(at: at); + self.listhash.removeValue(forKey: item.stableId) + + viewNecessary(at: at)?.onRemove(animation) + + item._index = nil + + let animation: NSTableView.AnimationOptions = animation != .none ? item.animatable ? animation : .none : .none + NSAnimationContext.current.duration = animation == .none ? 0.0 : 0.2 + NSAnimationContext.current.timingFunction = animation == .none ? nil : CAMediaTimingFunction(name: .easeOut) + + if(redraw) { + self.tableView.removeRows(at: IndexSet(integer:at), withAnimation: animation != .none ? .effectFade : .none) + } + } + } + + public func remove(range:Range, redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { + + for i in range.lowerBound ..< range.upperBound { + remove(at: i, redraw: false) + } + + if(redraw) { + self.tableView.removeRows(at: IndexSet(integersIn:range), withAnimation: animation != .none ? .effectFade : .none) + } + } + + + + public func removeAll(redraw:Bool = true, animation:NSTableView.AnimationOptions = .none) -> Void { + let count:Int = self.count; + self.list.removeAll() + self.listhash.removeAll() + + if(redraw) { + self.tableView.removeRows(at: IndexSet(integersIn: 0 ..< count), withAnimation: animation != .none ? .effectFade : .none) + } + self.tableView.removeAllSubviews() + } + + public func selectNext(_ scroll:Bool = true, _ animated:Bool = false, turnDirection: Bool = true) -> Void { + var previousInset: CGFloat = 0 + if let hash = selectedhash { + let selectedItem = self.item(stableId: hash) + if let selectedItem = selectedItem { + previousInset = -selectedItem.heightValue + var selectedIndex = self.index(of: selectedItem)! + selectedIndex += 1 + + if selectedIndex == count { + if turnDirection { + selectedIndex = 0 + } else { + selectedIndex = count - 1 + } + } + if let delegate = delegate { + let sIndex = selectedIndex + for i in sIndex ..< list.count { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false) { + selectedIndex = i + break + } + } + } + + + _ = select(item: item(at: selectedIndex)) + } + + + } else { + if let delegate = delegate { + for item in list { + if delegate.selectionWillChange(row: item.index, item: item, byClick: false) { + _ = self.select(item: item) + break + } + } + } + + } + if let hash = selectedhash, scroll { + self.scroll(to: .top(id: hash, innerId: nil, animated: animated, focus: .init(focus: false), inset: previousInset), inset: NSEdgeInsets(), true) + } + } + + public func selectPrev(_ scroll:Bool = true, _ animated:Bool = false, turnDirection: Bool = true) -> Void { + var previousInset: CGFloat = 0 + if let hash = selectedhash { + let selectedItem = self.item(stableId: hash) + if let selectedItem = selectedItem { + previousInset = selectedItem.heightValue + + var selectedIndex = self.index(of: selectedItem)! + selectedIndex -= 1 + + if selectedIndex == -1 { + if turnDirection { + selectedIndex = count - 1 + } else { + selectedIndex = 0 + } + } + + if let delegate = delegate { + let sIndex = selectedIndex + for i in stride(from: sIndex, to: -1, by: -1) { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false) { + selectedIndex = i + break + } + } + } + + + _ = select(item: item(at: selectedIndex)) + } + + + } else { + if let delegate = delegate { + for i in stride(from: list.count - 1, to: -1, by: -1) { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false) { + _ = self.select(item: item(at: i)) + break + } + } + } + + } + + if let hash = selectedhash, scroll { + self.scroll(to: .bottom(id: hash, innerId: nil, animated: animated, focus: .init(focus: false), inset: previousInset), inset: NSEdgeInsets(), true) + } + } + + public func highlightNext(_ scroll:Bool = true, _ animated:Bool = false, turnDirection: Bool = true) -> Void { + var previousInset: CGFloat = 0 + if let hash = highlitedHash { + let highlighteditem = self.item(stableId: hash) + + if let highlighteditem = highlighteditem { + previousInset = -highlighteditem.heightValue + var selectedIndex = self.index(of: highlighteditem)! + selectedIndex += 1 + + if selectedIndex == count { + if turnDirection { + selectedIndex = 0 + } else { + selectedIndex = count - 1 + } + } + if let delegate = delegate { + let sIndex = selectedIndex + for i in sIndex ..< list.count { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false), selectedItem()?.index != i { + selectedIndex = i + break + } + } + } + + _ = highlight(item: item(at: selectedIndex)) + } + + + } else { + if let delegate = delegate { + for item in list { + if delegate.selectionWillChange(row: item.index, item: item, byClick: false), selectedItem()?.index != item.index { + _ = self.highlight(item: item) + break + } + } + } + + } + if let hash = highlitedHash, scroll { + self.scroll(to: .top(id: hash, innerId: nil, animated: animated, focus: .init(focus: false), inset: previousInset), inset: NSEdgeInsets(), true) + } + } + + public func highlightPrev(_ scroll:Bool = true, _ animated:Bool = false, turnDirection: Bool = true) -> Void { + var previousInset: CGFloat = 0 + + if let hash = highlitedHash { + let highlightedItem = self.item(stableId: hash) + if let highlightedItem = highlightedItem { + previousInset = highlightedItem.heightValue + var selectedIndex = self.index(of: highlightedItem)! + selectedIndex -= 1 + + if selectedIndex == -1 { + if turnDirection { + selectedIndex = count - 1 + } else { + selectedIndex = 0 + } + } + + if let delegate = delegate { + let sIndex = selectedIndex + for i in stride(from: sIndex, to: -1, by: -1) { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false), selectedItem()?.index != i { + selectedIndex = i + break + } + } + } + + _ = highlight(item: item(at: selectedIndex)) + } + + + } else { + if let delegate = delegate { + for i in stride(from: list.count - 1, to: -1, by: -1) { + if delegate.selectionWillChange(row: i, item: item(at: i), byClick: false), selectedItem()?.index != i { + _ = self.highlight(item: item(at: i)) + break + } + } + } + + } + + if let hash = highlitedHash, scroll { + self.scroll(to: .bottom(id: hash, innerId: nil, animated: animated, focus: .init(focus: false), inset: previousInset), inset: NSEdgeInsets(), true) + } + } + + + public var isEmpty:Bool { + + if let emptyChecker = emptyChecker { + return emptyChecker(self.list) + } + + return self.list.isEmpty || (!tableView.isFlipped && list.count == 1) + } + + public func reloadData() -> Void { + self.tableView.reloadData() + } + + public func item(at:Int) -> TableRowItem { + return self.list[at] + } + + public func visibleRows(_ insetHeight:CGFloat = 0) -> NSRange { + return self.tableView.rows(in: NSMakeRect(self.tableView.visibleRect.minX, self.tableView.visibleRect.minY, self.tableView.visibleRect.width, self.tableView.visibleRect.height + insetHeight)) + } + + public var listHeight:CGFloat { + var height:CGFloat = 0 + for item in list { + height += item.heightValue + } + return height + } + + public func row(at point:NSPoint) -> Int { + return tableView.row(at: NSMakePoint(point.x, point.y - bottomInset)) + } + + public func viewNecessary(at row:Int, makeIfNecessary: Bool = false) -> TableRowView? { + if row < 0 || row >= count { + if row == -1000 { + return emptyView + } + return nil + } + if let resortView = self.resortController?.resortView { + if resortView.item?.stableId == self.item(at: row).stableId { + return resortView + } + } + return self.tableView.rowView(atRow: row, makeIfNecessary: makeIfNecessary) as? TableRowView + } + + + public func select(item:TableRowItem, notify:Bool = true, byClick:Bool = false) -> Bool { + + if let delegate = delegate, delegate.isSelectable(row: item.index, item: item) { + if(self.item(stableId:item.stableId) != nil) { + if !notify || delegate.selectionWillChange(row: item.index, item: item, byClick: byClick) { + let new = item.stableId != selectedhash + if new { + self.cancelSelection(); + } + self.selectedhash = item.stableId + self.cancelHighlight() + item.prepare(true) + self.reloadData(row:item.index) + if notify { + delegate.selectionDidChange(row: item.index, item: item, byClick:byClick, isNew:new) + } + return true; + } + } + } + return false; + + } + + public func highlight(item:TableRowItem, notify:Bool = true, byClick:Bool = false) -> Bool { + + if let delegate = delegate, delegate.isSelectable(row: item.index, item: item) { + if(self.item(stableId:item.stableId) != nil) { + if !notify || delegate.selectionWillChange(row: item.index, item: item, byClick: byClick) { + let new = item.stableId != selectedhash + if new { + self.cancelHighlight(); + } + highlitedHash = item.stableId + item.prepare(true) + self.reloadData(row:item.index) + return true; + } + } + } + return false; + + } + + public func changeSelection(stableId:AnyHashable?) { + if let stableId = stableId { + if let item = self.item(stableId: stableId) { + _ = self.select(item:item, notify:false) + } else { + cancelSelection() + self.selectedhash = stableId + } + } else { + cancelSelection() + } + } + + public func cancelSelection() -> Void { + if let hash = selectedhash { + if let item = self.item(stableId: hash) { + item.prepare(false) + selectedhash = nil + self.reloadData(row:item.index) + } else { + selectedhash = nil + } + } + + } + + public func cancelHighlight() -> Void { + if let hash = highlitedHash { + if let item = self.item(stableId: hash) { + item.prepare(false) + highlitedHash = nil + self.reloadData(row: item.index) + } else { + highlitedHash = nil + } + } + + } + + + func rowView(item:TableRowItem) -> TableRowView { + let identifier:String = item.identifier + + if let resortView = self.resortController?.resortView { + if resortView.item?.stableId == item.stableId { + return resortView + } + } + + var view: NSView? = item.isUniqueView ? nil : self.tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier), owner: self.tableView) + + if(view == nil) { + if let item = item as? TableStickItem, item.singletonItem { + view = TableRowView(frame: NSMakeRect(0, 0, frame.width, item.heightValue)) + } else { + view = makeView(at: item.index) + } + view?.identifier = NSUserInterfaceItemIdentifier(rawValue: identifier) + } + if view!.frame.height != item.heightValue { + view?.setFrameSize(NSMakeSize(frame.width, item.heightValue)) + } + return view as! TableRowView; + } + + private func makeView(at index: Int) -> TableRowView { + let item = self.item(at: index) + let vz = item.viewClass() as! TableRowView.Type + let view = vz.init(frame:NSMakeRect(0, 0, frame.width, item.heightValue)) + return view + } + + public func numberOfRows(in tableView: NSTableView) -> Int { + return self.count; + } + + public func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + return max(self.item(at: row).heightValue, 1) + } + + public func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { + return false; + } + + public func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + return nil + } + + + + public func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { + let item: TableRowItem = self.item(at: row); + + let view: TableRowView = self.rowView(item: item); + + self.beforeSetupItem?(view, item) + + view.set(item: item, animated: false) + + self.afterSetupItem?(view, item) + + + return view + } + + + func visibleItems() -> [(TableRowItem,CGFloat,CGFloat)] { // item, top offset, bottom offset + + var list:[(TableRowItem,CGFloat,CGFloat)] = [] + + let visible = visibleRows() + + for i in visible.min ..< visible.max { + let item = self.item(at: i) + let rect = rectOf(index: i) + if !tableView.isFlipped { + let top = frame.height - (rect.minY - documentOffset.y) - rect.height + let bottom = (rect.minY - documentOffset.y) + list.append((item,top,bottom)) + } else { + let top = rect.minY - documentOffset.y + let bottom = frame.height - (rect.minY - documentOffset.y) - rect.height + list.append((item,top,bottom)) + //fatalError("not supported") + } + + // list.append(item,) + } + + + return list; + + } + + func itemRects() -> [(TableRowItem, NSRect, Int)] { + var ilist:[(TableRowItem,NSRect,Int)] = [(TableRowItem,NSRect,Int)]() + + for i in 0 ..< self.list.count { + ilist.append((item(at: i),self.rectOf(index: i), i)) + + } + + return ilist; + + } + + public func beginTableUpdates() { + self.tableView.beginUpdates() + } + + public func endTableUpdates() { + self.tableView.endUpdates() + } + + public func stopMerge() { + mergeDisposable.set(nil) + mergePromise.set(.single(TableUpdateTransition(deleted: [], inserted: [], updated: []))) + } + + public func startMerge() { + mergeDisposable.set((mergePromise.get() |> deliverOnMainQueue).start(next: { [weak self] transition in + self?.merge(with: transition) + })) + } + + public func merge(with transition:Signal) { + mergePromise.set(transition |> deliverOnMainQueue) + } + + public var isBoundsAnimated: Bool { + return contentView.layer?.animation(forKey: "bounds") != nil + } + + private var first:Bool = true + + private var queuedTransitions: [TableUpdateTransition] = [] + private var areSuspended = false + private var isAlreadyEnqued: Bool = false + private func enqueueTransitions() { + + guard !isAlreadyEnqued else { + return + } + + isAlreadyEnqued = true + while !queuedTransitions.isEmpty { + if !isSetTransitionToQueue() && !updating { + self.merge(with: queuedTransitions.removeFirst(), forceApply: true) + } else { + break + } + } + isAlreadyEnqued = false + } + + private func isSetTransitionToQueue() -> Bool { + return areSuspended + } + + public func merge(with transition:TableUpdateTransition) -> Void { + self.merge(with: transition, forceApply: false) + enqueueTransitions() + } + + private func merge(with transition:TableUpdateTransition, forceApply: Bool) -> Void { + + assertOnMainThread() + assert(!updating) + + + if isSetTransitionToQueue() || (!self.queuedTransitions.isEmpty && !forceApply) { + self.queuedTransitions.append(transition) + return + } + + let oldEmpty = self.isEmpty + + self.beginUpdates() + + let documentOffset = self.documentOffset + + let visibleItems = self.visibleItems() + let visibleRange = self.visibleRows() + if transition.grouping && !transition.isEmpty { + self.tableView.beginUpdates() + } + //CATransaction.begin() + + + for (i, item) in list.enumerated() { + item._index = nil + } + + var inserted:[(TableRowItem, NSTableView.AnimationOptions)] = [] + var removed:[TableRowItem] = [] + + + + for rdx in transition.deleted.reversed() { + let effect:NSTableView.AnimationOptions + if case let .none(interface) = transition.state, interface != nil { + effect = (visibleRange.indexIn(rdx) || !transition.animateVisibleOnly) ? .effectFade : .none + } else { + effect = transition.animated && (visibleRange.indexIn(rdx) || !transition.animateVisibleOnly) ? .effectFade : .none + } + if rdx < visibleRange.location { + removed.append(item(at: rdx)) + } + self.remove(at: rdx, redraw: true, animation:effect) + } + + //NSAnimationContext.current.duration = transition.animated ? 0.2 : 0.0 + + + for (idx,item) in transition.inserted { + let effect:NSTableView.AnimationOptions = (visibleRange.indexIn(idx - 1) || !transition.animateVisibleOnly) && transition.animated ? .effectFade : .none + _ = self.insert(item: item, at:idx, redraw: true, animation: effect) + if item.animatable { + inserted.append((item, effect)) + } + } + + + for (index,item) in transition.updated { + let animated:Bool + if case .none = transition.state { + animated = visibleRange.indexIn(index) || !transition.animateVisibleOnly + } else { + animated = false + } + replace(item:item, at:index, animated: animated) + } + + + for (i, item) in list.enumerated() { + item._index = i + } + + //CATransaction.commit() + if transition.grouping && !transition.isEmpty { + self.tableView.endUpdates() + } + self.clipView.justScroll(to: documentOffset) + + + + for inserted in inserted { + inserted.0.view?.onInsert(inserted.1) + } + + + let state: TableScrollState + + if case .none = transition.state, !transition.deleted.isEmpty || !transition.inserted.isEmpty { + let isSomeOfItemVisible = !inserted.filter({$0.0.isVisible}).isEmpty || !removed.filter({$0.isVisible}).isEmpty + if isSomeOfItemVisible { + state = transition.state + } else { + state = transition.state + // state = .saveVisible(.upper) + } + } else { + state = transition.state + } + + // NSLog("listHeight: \(listHeight), scroll: \(state)") + // self.tableView.beginUpdates() + + func saveVisible(_ side: TableSavingSide) { + var nrect:NSRect = NSZeroRect + + let strideTo:StrideTo + + var aroundIndex: AnyHashable? + + if !tableView.isFlipped { + switch side { + case .lower: + strideTo = stride(from: 0, to: visibleItems.count, by: 1) + case .upper: + strideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) + case .aroundIndex(let index): + aroundIndex = index + strideTo = stride(from: 0, to: visibleItems.count, by: 1) + } + } else { + switch side { + case .upper: + strideTo = stride(from: visibleItems.count - 1, to: -1, by: -1) + case .lower: + strideTo = stride(from: 0, to: visibleItems.count, by: 1) + case .aroundIndex(let index): + aroundIndex = index + strideTo = stride(from: 0, to: visibleItems.count, by: 1) + } + } + + + for i in strideTo { + let visible = visibleItems[i] + + if let aroundIndex = aroundIndex { + if aroundIndex != visible.0.stableId { + continue + } + } + if let item = self.item(stableId: visible.0.stableId) { + + if !item.canBeAnchor { + continue + } + + nrect = rectOf(item: item) + + if let view = viewNecessary(at: i) { + if view.isInsertionAnimated { + break + } + } + + let y:CGFloat + + if !tableView.isFlipped { + y = nrect.minY - (frame.height - visible.1) + nrect.height + } else { + y = nrect.minY - visible.1 + } + self.clipView.scroll(to: NSMakePoint(0, y), animated: false) + + //reflectScrolledClipView(clipView) +// tile() + //self.contentView.bounds = NSMakeRect(0, y, 0, contentView.bounds.height) + //self.display(visi) + // reflectScrolledClipView(clipView) + + // let assertRect = rectOf(item: item) + // let top = frame.height - (assertRect.minY - documentOffset.y) - assertRect.height + + // assert(visible.1 == top) + + // tableView.tile() + // tableView.display() + break + } + } + } + switch state { + case let .none(animation): + // print("scroll do nothing") + animation?.animate(table:self, documentOffset: documentOffset, added: inserted.map{ $0.0 }, removed:removed) + if let animation = animation, !animation.scrollBelow, !transition.isEmpty, contentView.bounds.minY > 0 { + saveVisible(.upper) + } + case .bottom, .top, .center: + self.scroll(to: transition.state) + case .up, .down, .upOffset: + self.scroll(to: transition.state) + case let .saveVisible(side): + saveVisible(side) + + break + } + //reflectScrolledClipView(clipView) + // self.tableView.endUpdates() + self.endUpdates() + + + self.updatedItems?(self.list) + +// for subview in self.tableView.subviews.reversed() { +// if self.tableView.row(for: subview) == -1 { +// subview.removeFromSuperview() +// } +// } + + if oldEmpty != isEmpty || first { + updateEmpties(animated: !first) + } + + if let searchState = transition.searchState { + if self.searchView == nil { + self.searchView = TableSearchView(frame: NSMakeRect(0, -50, frame.width, 50)) + addSubview(self.searchView!) + } + guard let searchView = self.searchView else { + return + } + switch searchState { + case .none: + searchView.change(pos: NSMakePoint(0, -searchView.frame.height), animated: true) + searchView.searchView.cancel(true) + case let .visible(data): + searchView.change(pos: NSZeroPoint, animated: true) + searchView.applySearchResponder() + searchView.updateDatas(data) + } + } else { + self.searchView?.removeFromSuperview() + self.searchView = nil + } + + first = false + performScrollEvent(transition.animated) + updateStickAfterScroll(transition.animated) + } + + public func updateEmpties(animated: Bool = false) { + if let emptyItem = emptyItem { + if isEmpty { + if let empt = emptyView, !empt.isKind(of: emptyItem.viewClass()) || empt.item != emptyItem { + emptyView?.removeFromSuperview() + emptyView = nil + } + if emptyView == nil { + let vz = emptyItem.viewClass() as! TableRowView.Type + emptyView = vz.init(frame:bounds) + emptyView?.identifier = identifier + if animated, let emptyView = emptyView { + emptyView.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + } + + if emptyView?.superview == nil { + addSubview(emptyView!) + } + + if let emptyView = emptyView, let superview = superview { + emptyView.frame = findBackgroundControllerView?.bounds ?? bounds + emptyView.centerX(y: superview.frame.height - emptyView.frame.height) + + if animated { + emptyView.layer?.animatePosition(from: emptyView.frame.origin.offsetBy(dx: 0, dy: 25), to: emptyView.frame.origin) + } + } + + + emptyView?.set(item: emptyItem) + emptyView?.needsLayout = true + + tableView._change(opacity: 0, animated: animated) + } else { + if let emptyView = emptyView { + self.emptyView = nil + if animated { + emptyView.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak emptyView] completed in + emptyView?.removeFromSuperview() + }) + if animated { + emptyView.layer?.animatePosition(from: emptyView.frame.origin, to: emptyView.frame.origin.offsetBy(dx: 0, dy: 25), removeOnCompletion: false) + } + } else { + emptyView.removeFromSuperview() + } + } + tableView._change(opacity: 1, animated: animated) + } + } + + } + + + public func replace(item:TableRowItem, at index:Int, animated:Bool) { + if index < count { + listhash.removeValue(forKey: list[index].stableId) + list[index] = item + listhash[item.stableId] = item + item.table = self + item._index = index + reloadData(row: index, animated: animated) + } + } + + public func contentInteractionView(for stableId: AnyHashable, animateIn: Bool) -> NSView? { + var item = self.item(stableId: stableId) + + if item == nil { + if let groupStableId = delegate?.findGroupStableId(for: stableId) { + item = self.item(stableId: groupStableId) + } + } + + if let item = item { + let view = viewNecessary(at:item.index) + if let view = view, !NSIsEmptyRect(view.visibleRect) { + return view.interactionContentView(for: stableId, animateIn: animateIn) + } + + } + + return nil + } + + public func interactionControllerDidFinishAnimation(interactive: Bool, for stableId: AnyHashable) { + var item = self.item(stableId: stableId) + + if item == nil { + if let groupStableId = delegate?.findGroupStableId(for: stableId) { + item = self.item(stableId: groupStableId) + } + } + + if let item = item { + let view = viewNecessary(at:item.index) + if let view = view, !NSIsEmptyRect(view.visibleRect) { + view.interactionControllerDidFinishAnimation(interactive: interactive, innerId: stableId) + } + } + } + + public func addAccesoryOnCopiedView(for stableId: AnyHashable, view: NSView) { + var item = self.item(stableId: stableId) + + if item == nil { + if let groupStableId = delegate?.findGroupStableId(for: stableId) { + item = self.item(stableId: groupStableId) + } + } + + if let item = item { + let rowView = viewNecessary(at:item.index) + if let rowView = rowView, !NSIsEmptyRect(view.visibleRect) { + rowView.addAccesoryOnCopiedView(innerId: stableId, view: view) + } + } + } + + public func videoTimebase(for stableId: AnyHashable) -> CMTimebase? { + var item = self.item(stableId: stableId) + + if item == nil { + if let groupStableId = delegate?.findGroupStableId(for: stableId) { + item = self.item(stableId: groupStableId) + } + } + + if let item = item { + let view = viewNecessary(at:item.index) + if let view = view, !NSIsEmptyRect(view.visibleRect) { + return view.videoTimebase(for: stableId) + } + + } + + return nil + } + + public func applyTimebase(for stableId: AnyHashable, timebase: CMTimebase?) { + var item = self.item(stableId: stableId) + + if item == nil { + if let groupStableId = delegate?.findGroupStableId(for: stableId) { + item = self.item(stableId: groupStableId) + } + } + + if let item = item { + let view = viewNecessary(at:item.index) + if let view = view, !NSIsEmptyRect(view.visibleRect) { + view.applyTimebase(for: stableId, timebase: timebase) + } + } + + } + + func selectRow(index: Int) { + if self.count > index { + _ = self.select(item: self.item(at: index), byClick:true) + } + } + + public override func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion:((Bool)->Void)? = nil) { + + + if animated { + + if !tableView.isFlipped { + + CATransaction.begin() + var presentBounds:NSRect = self.layer?.bounds ?? self.bounds + let presentation = self.layer?.presentation() + if let presentation = presentation, self.layer?.animation(forKey:"bounds") != nil { + presentBounds = presentation.bounds + } + + self.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, self.bounds.minY, size.width, size.height), duration: duration, timingFunction: timingFunction) + let y = (size.height - presentBounds.height) + + presentBounds = contentView.layer?.bounds ?? contentView.bounds + if let presentation = contentView.layer?.presentation(), contentView.layer?.animation(forKey:"bounds") != nil { + presentBounds = presentation.bounds + } + + if y > 0 { + presentBounds.origin.y -= y + presentBounds.size.height += y + } else { + presentBounds.origin.y += y + presentBounds.size.height -= y + } + + contentView.layer?.animateBounds(from: presentBounds, to: NSMakeRect(0, contentView.bounds.minY, size.width, size.height), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, forKey: "bounds", completion: { completed in + completion?(completed) + }) + CATransaction.commit() + } else { + super.change(size: size, animated: animated, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + return + } + } + self.setFrameSize(size) + self.updateStickAfterScroll(animated) + } + + + + public func scroll(to state:TableScrollState, inset:NSEdgeInsets = NSEdgeInsets(), timingFunction: CAMediaTimingFunctionName = .spring, _ toVisible:Bool = false, ignoreLayerAnimation: Bool = false, completion: @escaping(Bool)->Void = { _ in }) { + // if let index = self.index(of: item) { + + var rowRect:NSRect = bounds + + var item:TableRowItem? + var animate:Bool = false + var focus: TableScrollFocus = .init(focus: false) + var relativeInset: CGFloat = 0 + var innerId: AnyHashable? = nil + switch state { + case let .center(stableId, _innerId, _animate, _focus, _inset): + item = self.item(stableId: stableId) + animate = _animate + relativeInset = _inset + focus = _focus + innerId = _innerId + case let .bottom(stableId, _innerId, _animate, _focus, _inset): + item = self.item(stableId: stableId) + animate = _animate + relativeInset = _inset + focus = _focus + innerId = _innerId + case let .top(stableId, _innerId, _animate, _focus, _inset): + item = self.item(stableId: stableId) + animate = _animate + relativeInset = _inset + focus = _focus + innerId = _innerId + case let .down(_animate): + animate = _animate + if !tableView.isFlipped { + rowRect.origin = NSZeroPoint + } else { + rowRect.origin = NSMakePoint(0, max(0,documentSize.height - frame.height)) + } + case let .up(_animate): + animate = _animate + if !tableView.isFlipped { + rowRect.origin = NSMakePoint(0, max(documentSize.height,frame.height)) + } else { + rowRect.origin = NSZeroPoint + } + case let .upOffset(_animate, offset): + animate = _animate + if !tableView.isFlipped { + rowRect.origin = NSMakePoint(0, max(documentSize.height,frame.height)) + } else { + rowRect.origin = NSZeroPoint + } + relativeInset = offset + default: + fatalError("for scroll to item, you can use only .top, center, .bottom enumeration") + } + + let bottomInset = self.bottomInset != 0 ? (self.bottomInset) : 0 + let height:CGFloat = self is HorizontalTableView ? frame.width : frame.height + + if let item = item { + rowRect = self.rectOf(item: item) + var state = state + if case let .center(id, innerId, animated, focus, inset) = state, rowRect.height > frame.height { + state = .top(id: id, innerId: innerId, animated: animated, focus: focus, inset: inset) + } + switch state { + case .bottom: + if tableView.isFlipped { + rowRect.origin.y -= (height - rowRect.height) - bottomInset + } + case .top: + // break + if !tableView.isFlipped { + rowRect.origin.y -= (height - rowRect.height) - bottomInset + } + case .center: + if !tableView.isFlipped { + rowRect.origin.y -= floorToScreenPixels(backingScaleFactor, (height - rowRect.height) / 2.0) - bottomInset + } else { + + if rowRect.maxY > height/2.0 { + rowRect.origin.y -= floorToScreenPixels(backingScaleFactor, (height - rowRect.height) / 2.0) - bottomInset + } else { + rowRect.origin.y = 0 + } + + + // fatalError("not implemented") + } + + default: + fatalError("not implemented") + } + + if toVisible { + let view = self.viewNecessary(at: item.index) + if let view = view, view.visibleRect.height == item.heightValue { + if focus.focus { + view.focusAnimation(innerId) + focus.action?(view.interactableView) + } + completion(true) + return + } + } + } + rowRect.origin.y = round(min(max(rowRect.minY + relativeInset, 0), documentSize.height - height) + inset.top) + if clipView.bounds.minY != rowRect.minY { + + var applied = false + let scrollListener = TableScrollListener({ [weak self, weak item] position in + if let item = item, !applied { + if let view = self?.viewNecessary(at: item.index), view.visibleRect.height > 10 { + applied = true + if focus.focus { + view.focusAnimation(innerId) + focus.action?(view.interactableView) + } + } + } + }) + + addScroll(listener: scrollListener) + + let bounds = NSMakeRect(0, rowRect.minY, clipView.bounds.width, clipView.bounds.height) + + + let getEdgeInset:()->CGFloat = { + if bounds.minY > self.clipView.bounds.minY { + return height + } else { + return -height + } + } + + let shouldSuspend: Bool + switch state { + case .down, .up: + shouldSuspend = false + default: + shouldSuspend = true + } + +// clipView.scroll(to: bounds.origin, animated: animate, completion: { [weak self] _ in +// self?.removeScroll(listener: scrollListener) +// }) + + if abs(bounds.minY - clipView.bounds.minY) < height || ignoreLayerAnimation { + if animate { + areSuspended = shouldSuspend + clipView.scroll(to: bounds.origin, animated: animate, completion: { [weak self] completed in + if let `self` = self { + scrollListener.handler(self.scrollPosition().current) + self.removeScroll(listener: scrollListener) + completion(completed) + self.areSuspended = false + self.enqueueTransitions() + } + + }) + } else { + self.contentView.scroll(to: bounds.origin) + reflectScrolledClipView(clipView) + removeScroll(listener: scrollListener) + } + + } else { + + areSuspended = shouldSuspend + let edgeRect:NSRect = NSMakeRect(clipView.bounds.minX, bounds.minY - getEdgeInset() - frame.minY, clipView.bounds.width, clipView.bounds.height) + clipView._changeBounds(from: edgeRect, to: bounds, animated: animate, duration: 0.4, timingFunction: timingFunction, completion: { [weak self] completed in + self?.removeScroll(listener: scrollListener) + completion(completed) + self?.areSuspended = false + self?.enqueueTransitions() + }) + + } + } else { + if let item = item, focus.focus { + if let view = viewNecessary(at: item.index) { + view.focusAnimation(innerId) + focus.action?(view.interactableView) + } + } + completion(true) + } + + } + + open override func setFrameSize(_ newSize: NSSize) { + let visible = visibleItems() + let oldWidth = frame.width + super.setFrameSize(newSize) + + + //updateStickAfterScroll(false) + if oldWidth != newSize.width, !inLiveResize && newSize.width > 0 && newSize.height > 0 { + saveScrollState(visible) + } + } + + public func setScrollHandler(_ handler: @escaping (_ scrollPosition:ScrollPosition) ->Void) -> Void { + + scrollHandler = handler + + } + + + public func enumerateItems(with callback:(TableRowItem)->Bool) { + for item in list { + if !callback(item) { + break + } + } + } + + public func enumerateItems(reversed: Bool = false, with callback:(TableRowItem)->Bool) { + if reversed { + for item in list.reversed() { + if !callback(item) { + break + } + } + } else { + for item in list { + if !callback(item) { + break + } + } + } + } + + public func enumerateVisibleItems(reversed: Bool = false, with callback:(TableRowItem)->Bool) { + let visible = visibleRows() + + if reversed { + for i in stride(from: visible.location + visible.length - 1, to: visible.location - 1, by: -1) { + if !callback(list[i]) { + break + } + } + } else { + for i in visible.location ..< visible.location + visible.length { + if !callback(list[i]) { + break + } + } + } + + } + + public func enumerateViews(with callback:(TableRowView)->Bool) { + for index in 0 ..< list.count { + if let view = viewNecessary(at: index) { + if !callback(view) { + break + } + } + } + } + + public func enumerateVisibleViews(with callback:(TableRowView)->Void, force: Bool = false) { + let visibleRows = self.visibleRows() + for index in visibleRows.location ..< visibleRows.location + visibleRows.length { + if let view = viewNecessary(at: index, makeIfNecessary: force) { + callback(view) + } + } + } + + public func performScrollEvent(_ animated: Bool = false) -> Void { + self.nextScrollEventIsAnimated = animated + self.updateScroll(visibleRows()) + //NotificationCenter.default.post(name: NSView.boundsDidChangeNotification, object: self.contentView) + } + + deinit { + mergeDisposable.dispose() + stickTimeoutDisposable.dispose() + } + + + +} diff --git a/TGUIKit/TGUIKit/TableViewController.swift b/submodules/TGUIKit/TGUIKit/TableViewController.swift similarity index 86% rename from TGUIKit/TGUIKit/TableViewController.swift rename to submodules/TGUIKit/TGUIKit/TableViewController.swift index bfe9d79b54..9e04c1cb43 100644 --- a/TGUIKit/TGUIKit/TableViewController.swift +++ b/submodules/TGUIKit/TGUIKit/TableViewController.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit diff --git a/submodules/TGUIKit/TGUIKit/TextButtonBarView.swift b/submodules/TGUIKit/TGUIKit/TextButtonBarView.swift new file mode 100644 index 0000000000..8f65c4c344 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TextButtonBarView.swift @@ -0,0 +1,176 @@ +// +// TextButtonBarView.swift +// TGUIKit +// +// Created by keepcoder on 05/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public enum TextBarAligment { + case Left + case Right + case Center +} + +open class TextButtonBarView: BarView { + + private let button:TitleButton = TitleButton() + private let progressIndicator: ProgressIndicator = ProgressIndicator(frame: NSMakeRect(0, 0, 25, 25)) + public var alignment:TextBarAligment = .Center + private var _isFitted: Bool + private let canBeEmpty: Bool + public init(controller: ViewController, text:String, style:ControlStyle = navigationButtonStyle, alignment:TextBarAligment = .Center, canBeEmpty: Bool = false) { + + self.canBeEmpty = canBeEmpty + + button.userInteractionEnabled = false + button.set(font: navigationButtonStyle.font, for: .Normal) + button.set(color: navigationButtonStyle.foregroundColor, for: .Normal) + button.set(text: text, for: .Normal) + button.disableActions() + + + + _isFitted = false + super.init(controller: controller) + + self.alignment = alignment + button.style = style + + progressIndicator.isHidden = true + + self.addSubview(button) + self.addSubview(progressIndicator) + + } + + public var direction: TitleButtonImageDirection = .left { + didSet { + button.direction = direction + } + } + + public func set(image:CGImage, for state:ControlState) -> Void { + button.set(image: image, for: state) + _isFitted = false + (superview as? NavigationBarView)?.viewFrameChanged(Notification(name: NSView.frameDidChangeNotification)) + } + + public func set(color:NSColor, for state:ControlState) -> Void { + button.set(color: color, for: state) + } + + public func set(font:NSFont, for state:ControlState) -> Void { + button.set(font: font, for: state) + _isFitted = false + (superview as? NavigationBarView)?.viewFrameChanged(Notification(name: NSView.frameDidChangeNotification)) + } + + public func set(text:String, for state:ControlState) -> Void { + button.set(text: text, for: state) + _isFitted = false + (superview as? NavigationBarView)?.viewFrameChanged(Notification(name: NSView.frameDidChangeNotification)) + } + + public func removeImage(for state:ControlState) { + button.removeImage(for: state) + } + + override var isFitted: Bool { + return _isFitted + } + + + + override func fit(to maxWidth: CGFloat) -> CGFloat { + if button.isEmpty && canBeEmpty { + _isFitted = true + return self.minWidth + } else { + var width: CGFloat = 20 + switch alignment { + case .Center: + _isFitted = button.sizeToFit(NSZeroSize,NSMakeSize(maxWidth, frame.height), thatFit: false) + width += button.frame.width + 16 + //button.center() + case .Left: + _isFitted = button.sizeToFit(NSZeroSize,NSMakeSize(maxWidth, frame.height)) + width += button.frame.width + case .Right: + _isFitted = button.sizeToFit(NSZeroSize,NSMakeSize(maxWidth - 20, frame.height), thatFit: false) + width += max(button.frame.width + 16, minWidth) + let f = focus(button.frame.size) + button.setFrameOrigin(NSMakePoint(frame.width - button.frame.width - 16, f.minY)) + } + return width + } + + + } + public override var style: ControlStyle { + didSet { + //button.set(color: style.foregroundColor, for: .Normal) + + button.set(font: style.font, for: .Normal) + button.style = style + } + } + + open override var isEnabled: Bool { + didSet { + button.isEnabled = isEnabled + } + } + + + open var isLoading: Bool = false { + didSet { + button.isHidden = isLoading + progressIndicator.isHidden = !isLoading + needsLayout = true + } + } + + override open func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + button.set(color: navigationButtonStyle.foregroundColor, for: .Normal) + button.set(background: presentation.colors.background, for: .Normal) + } + + open override func layout() { + super.layout() + if button.isEmpty && canBeEmpty { + button.frame = bounds + button.updateLayout() + } else { + switch alignment { + case .Center: + button.center() + progressIndicator.center() + case .Left: + let f = focus(button.frame.size) + button.setFrameOrigin(16, floorToScreenPixels(backingScaleFactor, f.minY)) + progressIndicator.center() + case .Right: + let f = focus(button.frame.size) + button.setFrameOrigin(NSMakePoint(frame.width - button.frame.width - 16, floorToScreenPixels(backingScaleFactor, f.minY))) + progressIndicator.center() + } + } + + } + + + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + +} diff --git a/TGUIKit/TGUIKit/TextNode.swift b/submodules/TGUIKit/TGUIKit/TextNode.swift similarity index 86% rename from TGUIKit/TGUIKit/TextNode.swift rename to submodules/TGUIKit/TGUIKit/TextNode.swift index 8d139b4da4..ae1fe0b86d 100644 --- a/TGUIKit/TGUIKit/TextNode.swift +++ b/submodules/TGUIKit/TGUIKit/TextNode.swift @@ -28,6 +28,10 @@ public enum TextNodeCutoutPosition { public struct TextNodeCutout: Equatable { public let position: TextNodeCutoutPosition public let size: NSSize + public init(position: TextNodeCutoutPosition, size: NSSize) { + self.position = position + self.size = size + } } public func ==(lhs: TextNodeCutout, rhs: TextNodeCutout) -> Bool { @@ -82,8 +86,13 @@ public class TextNode: NSObject { super.init() } + deinit { + var bp:Int = 0 + bp += 1 + } - private class func getlayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: NSColor?, constrainedSize: NSSize, cutout: TextNodeCutout?, selected:Bool, alignment:NSTextAlignment) -> TextNodeLayout { + + private class func getlayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: NSColor?, constrainedSize: NSSize, cutout: TextNodeCutout?, selected:Bool, alignment:NSTextAlignment, lineSpacing: CGFloat? = nil) -> TextNodeLayout { var attr = attributedString let isPerfectSized = false @@ -93,7 +102,7 @@ public class TextNode: NSObject { let c:NSMutableAttributedString = a.mutableCopy() as! NSMutableAttributedString if let color = c.attribute(.selectedColor, at: 0, effectiveRange: nil) { - c.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: c.range) + c.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: c.range) } attr = c @@ -108,7 +117,7 @@ public class TextNode: NSObject { let font: CTFont if attributedString.length != 0 { - if let stringFont = attributedString.attribute(NSAttributedStringKey(kCTFontAttributeName as String), at: 0, effectiveRange: nil) { + if let stringFont = attributedString.attribute(NSAttributedString.Key(kCTFontAttributeName as String), at: 0, effectiveRange: nil) { font = stringFont as! CTFont } else { font = defaultFont @@ -119,7 +128,7 @@ public class TextNode: NSObject { let fontAscent = CTFontGetAscent(font) let fontDescent = CTFontGetDescent(font) - let fontLineHeight = floor(fontAscent + fontDescent) + let fontLineHeight = floor(fontAscent + fontDescent) + (lineSpacing ?? 0) let fontLineSpacing = floor(fontLineHeight * 0.12) var lines: [TextNodeLine] = [] @@ -186,9 +195,9 @@ public class TextNode: NSObject { if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedSize.width) { coreTextLine = originalLine } else { - var truncationTokenAttributes: [NSAttributedStringKey : Any] = [:] - truncationTokenAttributes[NSAttributedStringKey(kCTFontAttributeName as String)] = font - truncationTokenAttributes[NSAttributedStringKey(kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber + var truncationTokenAttributes: [NSAttributedString.Key : Any] = [:] + truncationTokenAttributes[NSAttributedString.Key(kCTFontAttributeName as String)] = font + truncationTokenAttributes[NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber let tokenString = "\u{2026}" let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) @@ -240,14 +249,36 @@ public class TextNode: NSObject { } - open func draw(_ dirtyRect: NSRect, in ctx: CGContext, backingScaleFactor: CGFloat) { - + open func draw(_ dirtyRect: NSRect, in ctx: CGContext, backingScaleFactor: CGFloat, backgroundColor: NSColor) { + + if !System.supportsTransparentFontDrawing { + if backingScaleFactor == 1.0 { + ctx.setFillColor(backgroundColor.cgColor) + ctx.fill(dirtyRect) + } + + ctx.setAllowsAntialiasing(true) + ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) + ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setShouldSubpixelPositionFonts(true) + } else { + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setShouldSubpixelPositionFonts(true) + + ctx.setAllowsAntialiasing(true) + ctx.setShouldAntialias(true) + + ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) + ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + } + + //let contextPtr = NSGraphicsContext.current()?.graphicsPort let context:CGContext = ctx //unsafeBitCast(contextPtr, to: CGContext.self) - ctx.setAllowsAntialiasing(true) - ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) - ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + // ctx.setAllowsFontSmoothing(true) @@ -259,7 +290,7 @@ public class TextNode: NSObject { if #available(OSX 10.11, *) { } else { - context.setBlendMode(.hardLight) + // context.setBlendMode(.hardLight) } if let layout = self.currentLayout { @@ -276,7 +307,10 @@ public class TextNode: NSObject { let penOffset = CGFloat( CTLineGetPenOffsetForFlush(line.line, penFlush, Double(dirtyRect.width))) - context.textPosition = CGPoint(x: penOffset + NSMinX(dirtyRect), y: line.frame.origin.y + NSMinY(dirtyRect)) + context.textPosition = CGPoint(x: penOffset + dirtyRect.minX + line.frame.minX, y: line.frame.minY + dirtyRect.minY) + + + CTLineDraw(line.line, context) } @@ -290,7 +324,7 @@ public class TextNode: NSObject { - open class func layoutText(maybeNode:TextNode? = nil, _ attributedString: NSAttributedString?, _ backgroundColor: NSColor?, _ maximumNumberOfLines: Int, _ truncationType: CTLineTruncationType, _ constrainedSize: NSSize, _ cutout: TextNodeCutout?,_ selected:Bool, _ alignment:NSTextAlignment) -> (TextNodeLayout, TextNode) { + open class func layoutText(maybeNode:TextNode? = nil, _ attributedString: NSAttributedString?, _ backgroundColor: NSColor?, _ maximumNumberOfLines: Int, _ truncationType: CTLineTruncationType, _ constrainedSize: NSSize, _ cutout: TextNodeCutout?,_ selected:Bool, _ alignment:NSTextAlignment, _ lineSpacing: CGFloat? = nil) -> (TextNodeLayout, TextNode) { let existingLayout: TextNodeLayout? = maybeNode?.currentLayout @@ -309,10 +343,10 @@ public class TextNode: NSObject { if stringMatch { layout = existingLayout } else { - layout = TextNode.getlayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, cutout: cutout,selected:selected, alignment:alignment) + layout = TextNode.getlayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, cutout: cutout,selected:selected, alignment: alignment, lineSpacing: lineSpacing) } } else { - layout = TextNode.getlayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, cutout: cutout,selected:selected, alignment:alignment) + layout = TextNode.getlayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, cutout: cutout,selected:selected, alignment: alignment, lineSpacing: lineSpacing) } let node = maybeNode ?? TextNode() diff --git a/submodules/TGUIKit/TGUIKit/TextView.swift b/submodules/TGUIKit/TGUIKit/TextView.swift new file mode 100644 index 0000000000..3b375a698f --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TextView.swift @@ -0,0 +1,1721 @@ +// +// TextView.swift +// TGUIKit +// +// Created by keepcoder on 15/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +public enum LinkType { + case plain + case email + case username + case hashtag + case command + case stickerPack + case inviteLink + case code +} + +public func isValidEmail(_ checkString:String) -> Bool { + let emailRegex = ".+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2}[A-Za-z]*" + let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegex) + return emailTest.evaluate(with: checkString) +} + +private enum CornerType { + case topLeft + case topRight + case bottomLeft + case bottomRight +} + +public extension NSAttributedString.Key { + static let hexColorMark = NSAttributedString.Key("TextViewHexColorMarkAttribute") + static let hexColorMarkDimensions = NSAttributedString.Key("TextViewHexColorMarkAttributeDimensions") + +} + +private func drawFullCorner(context: CGContext, color: NSColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func drawConnectingCorner(context: CGContext, color: NSColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(NSColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(NSColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(NSColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(NSColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func generateRectsImage(color: NSColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, CGImage?) { + if rects.isEmpty { + return (CGPoint(), nil) + } + + var topLeft = rects[0].origin + var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY) + for i in 1 ..< rects.count { + topLeft.x = min(topLeft.x, rects[i].origin.x) + topLeft.y = min(topLeft.y, rects[i].origin.y) + bottomRight.x = max(bottomRight.x, rects[i].maxX) + bottomRight.y = max(bottomRight.y, rects[i].maxY) + } + + topLeft.x -= inset + topLeft.y -= inset + bottomRight.x += inset + bottomRight.y += inset + + return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + + context.setBlendMode(.copy) + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset) + context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) + } + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + + var previous: CGRect? + if i != 0 { + previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + var next: CGRect? + if i != rects.count - 1 { + next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + if let previous = previous { + if previous.contains(rect.topLeft) { + if abs(rect.topLeft.x - previous.minX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + } + if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.topRight.x - previous.maxX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + + if let next = next { + if next.contains(rect.bottomLeft) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + } + if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } + })) + +} + + + +public final class TextViewInteractions { + public var processURL:(Any)->Void // link, isPresent + public var copy:(()->Bool)? + public var menuItems:((LinkType?)->Signal<[ContextMenuItem], NoError>)? + public var isDomainLink:(Any, String?)->Bool + public var makeLinkType:((Any, String))->LinkType + public var localizeLinkCopy:(LinkType)-> String + public var resolveLink:(Any)->String? + public var copyAttributedString:(NSAttributedString)->Bool + public var copyToClipboard:((String)->Void)? + public init(processURL:@escaping (Any)->Void = {_ in}, copy:(()-> Bool)? = nil, menuItems:((LinkType?)->Signal<[ContextMenuItem], NoError>)? = nil, isDomainLink:@escaping(Any, String?)->Bool = {_, _ in return true}, makeLinkType:@escaping((Any, String)) -> LinkType = {_ in return .plain}, localizeLinkCopy:@escaping(LinkType)-> String = {_ in return localizedString("Text.Copy")}, resolveLink: @escaping(Any)->String? = { _ in return nil }, copyAttributedString: @escaping(NSAttributedString)->Bool = { _ in return false}, copyToClipboard: ((String)->Void)? = nil) { + self.processURL = processURL + self.copy = copy + self.menuItems = menuItems + self.isDomainLink = isDomainLink + self.makeLinkType = makeLinkType + self.localizeLinkCopy = localizeLinkCopy + self.resolveLink = resolveLink + self.copyAttributedString = copyAttributedString + self.copyToClipboard = copyToClipboard + } +} + +struct TextViewStrikethrough { + let color: NSColor + let frame: NSRect + init(color: NSColor, frame: NSRect) { + self.color = color + self.frame = frame + } +} + +public final class TextViewLine { + public let line: CTLine + public let frame: NSRect + public let range: NSRange + public var penFlush: CGFloat + let isBlocked: Bool + let strikethrough:[TextViewStrikethrough] + init(line: CTLine, frame: CGRect, range: NSRange, penFlush: CGFloat, isBlocked: Bool = false, strikethrough: [TextViewStrikethrough] = []) { + self.line = line + self.frame = frame + self.range = range + self.penFlush = penFlush + self.isBlocked = isBlocked + self.strikethrough = strikethrough + } + +} + + +public enum TextViewCutoutPosition { + case TopLeft + case TopRight + case BottomRight +} + +public struct TextViewCutout: Equatable { + public var topLeft: CGSize? + public var topRight: CGSize? + public var bottomRight: CGSize? + + public init(topLeft: CGSize? = nil, topRight: CGSize? = nil, bottomRight: CGSize? = nil) { + self.topLeft = topLeft + self.topRight = topRight + self.bottomRight = bottomRight + } +} + +private let defaultFont:NSFont = .normal(.text) + +public final class TextViewLayout : Equatable { + public var mayItems: Bool = true + public var selectWholeText: Bool = false + public fileprivate(set) var attributedString:NSAttributedString + public fileprivate(set) var constrainedWidth:CGFloat = 0 + public var interactions:TextViewInteractions = TextViewInteractions() + public var selectedRange:TextSelectedRange + public var additionalSelections:[TextSelectedRange] = [] + public var penFlush:CGFloat + public var insets:NSSize = NSZeroSize + public fileprivate(set) var lines:[TextViewLine] = [] + public fileprivate(set) var isPerfectSized:Bool = true + public var maximumNumberOfLines:Int32 + public let truncationType:CTLineTruncationType + public var cutout:TextViewCutout? + public var mayBlocked: Bool = true + fileprivate var blockImage:(CGPoint, CGImage?) = (CGPoint(), nil) + + public fileprivate(set) var lineSpacing:CGFloat? + + public private(set) var layoutSize:NSSize = NSZeroSize + public private(set) var perfectSize:NSSize = NSZeroSize + public var alwaysStaticItems: Bool + fileprivate var selectText: NSColor + public var strokeLinks: Bool + fileprivate var strokeRects: [(NSRect, NSColor)] = [] + fileprivate var hexColorsRect: [(NSRect, NSColor, String)] = [] + fileprivate var toolTipRects:[NSRect] = [] + private let disableTooltips: Bool + fileprivate var isBigEmoji: Bool = false + public init(_ attributedString:NSAttributedString, constrainedWidth:CGFloat = 0, maximumNumberOfLines:Int32 = INT32_MAX, truncationType: CTLineTruncationType = .end, cutout:TextViewCutout? = nil, alignment:NSTextAlignment = .left, lineSpacing:CGFloat? = nil, selectText: NSColor = presentation.colors.selectText, strokeLinks: Bool = false, alwaysStaticItems: Bool = false, disableTooltips: Bool = true) { + self.truncationType = truncationType + self.maximumNumberOfLines = maximumNumberOfLines + self.cutout = cutout + self.disableTooltips = disableTooltips + self.attributedString = attributedString + self.constrainedWidth = constrainedWidth + self.selectText = selectText + self.alwaysStaticItems = alwaysStaticItems + self.selectedRange = TextSelectedRange(color: selectText) + self.strokeLinks = strokeLinks + switch alignment { + case .center: + penFlush = 0.5 + case .right: + penFlush = 1.0 + default: + penFlush = 0.0 + } + self.lineSpacing = lineSpacing + } + + public func dropLayoutSize() { + self.layoutSize = .zero + } + + func calculateLayout(isBigEmoji: Bool = false) -> Void { + self.isBigEmoji = isBigEmoji + isPerfectSized = true + + let font: CTFont + if attributedString.length != 0 { + if let stringFont = attributedString.attribute(NSAttributedString.Key(kCTFontAttributeName as String), at: 0, effectiveRange: nil) { + font = stringFont as! CTFont + } else { + font = defaultFont + } + } else { + font = defaultFont + } + + self.lines.removeAll() + + let fontAscent = CTFontGetAscent(font) + let fontDescent = CTFontGetDescent(font) + + let fontLineHeight = floor(fontAscent + (isBigEmoji ? fontDescent / 2 : fontDescent)) + (lineSpacing ?? 0) + + var monospacedRects:[NSRect] = [] + + var fontLineSpacing:CGFloat = floor(fontLineHeight * 0.12) + + + var maybeTypesetter: CTTypesetter? + maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) + + let typesetter = maybeTypesetter! + + var lastLineCharacterIndex: CFIndex = 0 + var layoutSize = NSSize() + + var cutoutEnabled = false + var cutoutMinY: CGFloat = 0.0 + var cutoutMaxY: CGFloat = 0.0 + var cutoutWidth: CGFloat = 0.0 + var cutoutOffset: CGFloat = 0.0 + + + var bottomCutoutEnabled = false + var bottomCutoutSize = CGSize() + + + + + if let topLeft = cutout?.topLeft { + cutoutMinY = -fontLineSpacing + cutoutMaxY = topLeft.height + fontLineSpacing + cutoutWidth = topLeft.width + cutoutOffset = cutoutWidth + cutoutEnabled = true + } else if let topRight = cutout?.topRight { + cutoutMinY = -fontLineSpacing + cutoutMaxY = topRight.height + fontLineSpacing + cutoutWidth = topRight.width + cutoutEnabled = true + } + if let bottomRight = cutout?.bottomRight { + bottomCutoutSize = bottomRight + bottomCutoutEnabled = true + } + + + var first = true + var breakInset: CGFloat = 0 + var isWasPreformatted: Bool = false + while true { + var strikethroughs: [TextViewStrikethrough] = [] + + var lineConstrainedWidth = constrainedWidth + var lineOriginY: CGFloat = 0 + + var lineCutoutOffset: CGFloat = 0.0 + var lineAdditionalWidth: CGFloat = 0.0 + + var isPreformattedLine: CGFloat? = nil + + fontLineSpacing = isBigEmoji ? 0 : floor(fontLineHeight * 0.12) + + + if attributedString.length > 0, let space = (attributedString.attribute(.preformattedPre, at: min(lastLineCharacterIndex, attributedString.length - 1), effectiveRange: nil) as? NSNumber), mayBlocked { + + + + + breakInset = CGFloat(space.floatValue * 2) + lineCutoutOffset += CGFloat(space.floatValue) + lineAdditionalWidth += breakInset + + lineOriginY += CGFloat(space.floatValue/2) + + if !isWasPreformatted && !first { + lineOriginY += CGFloat(space.floatValue) + fontLineSpacing = CGFloat(space.floatValue) - fontLineSpacing + } else { + if isWasPreformatted || first { + fontLineSpacing = -CGFloat(space.floatValue/2) + lineOriginY -= (CGFloat(space.floatValue + space.floatValue/2)) + } + } + + isPreformattedLine = CGFloat(space.floatValue) + isWasPreformatted = true + } else { + + if isWasPreformatted && !first { + lineOriginY -= (2 - fontLineSpacing) + } + + isWasPreformatted = false + } + + lineOriginY += floor(layoutSize.height + fontLineHeight - fontLineSpacing * 2.0) + + if !first { + lineOriginY += fontLineSpacing + } + + if cutoutEnabled { + if lineOriginY < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY { + lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth) + lineCutoutOffset = cutoutOffset + lineAdditionalWidth = cutoutWidth + } + } + + + + let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth - breakInset)) + let lineRange = CFRange(location: lastLineCharacterIndex, length: lineCharacterCount) + + + var lineHeight = fontLineHeight + + let lineString = attributedString.attributedSubstring(from: NSMakeRange(lastLineCharacterIndex, lineCharacterCount)) + if lineString.string.containsEmoji, !isBigEmoji { + lineHeight += floor(fontDescent) + if first { + lineOriginY += floor(fontDescent) + } + } + + if maximumNumberOfLines != 0 && lines.count == (Int(maximumNumberOfLines) - 1) && lineCharacterCount > 0 { + if first { + first = false + } else { + layoutSize.height += fontLineSpacing + } + + let coreTextLine: CTLine + + let originalLine = CTTypesetterCreateLineWithOffset(typesetter, CFRange(location: lastLineCharacterIndex, length: attributedString.length - lastLineCharacterIndex), 0.0) + + + + if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedWidth) { + coreTextLine = originalLine + } else { + var truncationTokenAttributes: [NSAttributedString.Key : Any] = [:] + truncationTokenAttributes[NSAttributedString.Key(kCTFontAttributeName as String)] = font + truncationTokenAttributes[NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber + let tokenString = "\u{2026}" + let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) + let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) + + + var lineConstrainedWidth = constrainedWidth + if bottomCutoutEnabled { + lineConstrainedWidth -= bottomCutoutSize.width + } + + + coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(lineConstrainedWidth), truncationType, truncationToken) ?? truncationToken + isPerfectSized = false + } + + + let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) + let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: lineHeight) + layoutSize.height += lineHeight + fontLineSpacing + layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextViewStrikethrough(color: presentation.colors.text, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } + } + + + lines.append(TextViewLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), penFlush: self.penFlush, isBlocked: isWasPreformatted)) + + break + } else { + if lineCharacterCount > 0 { + + + if first { + first = false + } else { + layoutSize.height += fontLineSpacing + } + + let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastLineCharacterIndex, lineCharacterCount), 100.0) + + + let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) + let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY - (isBigEmoji ? fontDescent / 3 : 0), width: lineWidth, height: lineHeight) + layoutSize.height += lineHeight + layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + + if let space = lineString.attribute(.preformattedPre, at: 0, effectiveRange: nil) as? NSNumber, mayBlocked { + + layoutSize.width = self.constrainedWidth + let preformattedSpace = CGFloat(space.floatValue) * 2 + + monospacedRects.append(NSMakeRect(0, lineFrame.minY - lineFrame.height, layoutSize.width, lineFrame.height + preformattedSpace)) + } + + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextViewStrikethrough(color: presentation.colors.text, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } + } + + lines.append(TextViewLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), penFlush: self.penFlush, isBlocked: isWasPreformatted, strikethrough: strikethroughs)) + lastLineCharacterIndex += lineCharacterCount + } else { + if !lines.isEmpty { + layoutSize.height += fontLineSpacing + } + break + } + } + + if mayBlocked { + if let isPreformattedLine = isPreformattedLine { + layoutSize.height += isPreformattedLine * 2 + if lastLineCharacterIndex == attributedString.length { + layoutSize.height += isPreformattedLine/2 + } + // fontLineSpacing = isPreformattedLine + } + } + + } + + if mayBlocked { + let sortedIndices = (0 ..< monospacedRects.count).sorted(by: { monospacedRects[$0].width > monospacedRects[$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(monospacedRects[index + j].width - monospacedRects[index].width) < 40.0 { + monospacedRects[index + j].size.width = max(monospacedRects[index + j].width, monospacedRects[index].width) + } + } + } + } + + self.blockImage = generateRectsImage(color: presentation.colors.grayBackground, rects: monospacedRects, inset: 0, outerRadius: .cornerRadius, innerRadius: .cornerRadius) + } + + + + //self.monospacedStrokeImage = generateRectsImage(color: presentation.colors.border, rects: monospacedRects, inset: 0, outerRadius: .cornerRadius, innerRadius: .cornerRadius) + + + self.layoutSize = layoutSize + } + + public func generateAutoBlock(backgroundColor: NSColor) { + + var rects = self.lines.map({$0.frame}) + + if !rects.isEmpty { + let sortedIndices = (0 ..< rects.count).sorted(by: { rects[$0].width > rects[$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(rects[index + j].width - rects[index].width) < 40.0 { + rects[index + j].size.width = max(rects[index + j].width, rects[index].width) + } + } + } + } + + for i in 0 ..< rects.count { + let height = rects[i].size.height + 7 + rects[i] = rects[i].insetBy(dx: 0, dy: floor((rects[i].height - height) / 2.0)) + rects[i].size.height = height + + rects[i].origin.x = floor((layoutSize.width - rects[i].width) / 2.0) + rects[i].size.width += 20 + } + + self.blockImage = generateRectsImage(color: backgroundColor, rects: rects, inset: 0, outerRadius: rects[0].height / 2, innerRadius: .cornerRadius) + self.blockImage.0 = NSMakePoint(0, 0) + + layoutSize.width += 20 + lines[0] = TextViewLine(line: lines[0].line, frame: lines[0].frame.offsetBy(dx: 0, dy: 2), range: lines[0].range, penFlush: self.penFlush) + layoutSize.height = rects.last!.maxY + } + + } + + public func selectNextChar() { + var range = selectedRange.range + + switch selectedRange.cursorAlignment { + case let .min(cursorAlignment), let .max(cursorAlignment): + if range.min >= cursorAlignment { + range.length += 1 + } else { + range.location += 1 + if range.length > 1 { + range.length -= 1 + } + } + } + let location = min(max(0, range.location), attributedString.length) + let length = max(min(range.length, attributedString.length - location), 0) + selectedRange.range = NSMakeRange(location, length) + } + + public func selectPrevChar() { + var range = selectedRange.range + + switch selectedRange.cursorAlignment { + case let .min(cursorAlignment), let .max(cursorAlignment): + if range.location >= cursorAlignment { + if range.length > 1 { + range.length -= 1 + } else { + range.location -= 1 + } + } else { + if range.location > 0 { + range.location -= 1 + range.length += 1 + } + } + } + let location = min(max(0, range.location), attributedString.length) + let length = max(min(range.length, attributedString.length - location), 0) + selectedRange.range = NSMakeRange(location, length) + } + + public func measure(width: CGFloat = 0, isBigEmoji: Bool = false) -> Void { + + if width != 0 { + constrainedWidth = width + } + + toolTipRects.removeAll() + + calculateLayout(isBigEmoji: isBigEmoji) + + strokeRects.removeAll() + + attributedString.enumerateAttribute(NSAttributedString.Key.link, in: attributedString.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { value, range, stop in + if let value = value { + if interactions.isDomainLink(value, attributedString.attributedSubstring(from: range).string) && strokeLinks { + for line in lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + let rightOffset: CGFloat = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + + let color: NSColor = attributedString.attribute(NSAttributedString.Key.foregroundColor, at: range.location, effectiveRange: nil) as? NSColor ?? presentation.colors.link + let rect = NSMakeRect(line.frame.minX + leftOffset, line.frame.minY + 1, rightOffset - leftOffset, 1.0) + strokeRects.append((rect, color)) + if !disableTooltips, interactions.resolveLink(value) != attributedString.string.nsstring.substring(with: range) { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + let rightOffset: CGFloat = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + + toolTipRects.append(NSMakeRect(line.frame.minX + leftOffset, line.frame.minY - line.frame.height, rightOffset - leftOffset, line.frame.height)) + } + } + } + } + if !disableTooltips, interactions.resolveLink(value) != attributedString.string.nsstring.substring(with: range) { + for line in lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + let rightOffset: CGFloat = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + + toolTipRects.append(NSMakeRect(line.frame.minX + leftOffset, line.frame.minY - line.frame.height, rightOffset - leftOffset, line.frame.height)) + } + } + } + } + + }) + hexColorsRect.removeAll() + attributedString.enumerateAttribute(NSAttributedString.Key.hexColorMark, in: attributedString.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { value, range, stop in + if let color = value as? NSColor, let size = attributedString.attribute(.hexColorMarkDimensions, at: range.location, effectiveRange: nil) as? NSSize { + for line in lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0 + if lineRange.location != line.range.location { + leftOffset += floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + let rect = NSMakeRect(line.frame.minX + leftOffset + 10, line.frame.minY - (size.height - 7) + 2, size.width - 8, size.height - 8) + hexColorsRect.append((rect, color, attributedString.attributedSubstring(from: range).string)) + } + } + } + + }) + + attributedString.enumerateAttribute(NSAttributedString.Key.underlineStyle, in: attributedString.range, options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { value, range, stop in + if let _ = value { + for line in lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + let rightOffset: CGFloat = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + + + let color: NSColor = attributedString.attribute(NSAttributedString.Key.foregroundColor, at: range.location, effectiveRange: nil) as? NSColor ?? presentation.colors.text + let rect = NSMakeRect(line.frame.minX + leftOffset, line.frame.minY + 1, rightOffset - leftOffset, 1.0) + strokeRects.append((rect, color)) + } + } + } + + }) + } + + public func clearSelect() { + self.selectedRange.range = NSMakeRange(NSNotFound, 0) + } + + public func selectedRange(startPoint:NSPoint, currentPoint:NSPoint) -> NSRange { + + var selectedRange:NSRange = NSMakeRange(NSNotFound, 0) + + if (currentPoint.x != -1 && currentPoint.y != -1 && !lines.isEmpty && startPoint.x != -1 && startPoint.y != -1) { + + + let startSelectLineIndex = findIndex(location: startPoint) + let currentSelectLineIndex = findIndex(location: currentPoint) + + + let dif = abs(startSelectLineIndex - currentSelectLineIndex) + let isReversed = currentSelectLineIndex < startSelectLineIndex + var i = startSelectLineIndex + while isReversed ? i >= currentSelectLineIndex : i <= currentSelectLineIndex { + let line = lines[i].line + let lineRange = CTLineGetStringRange(line) + var startIndex: CFIndex = CTLineGetStringIndexForPosition(line, startPoint) + var endIndex: CFIndex = CTLineGetStringIndexForPosition(line, currentPoint) + if dif > 0 { + if i != currentSelectLineIndex { + endIndex = (lineRange.length + lineRange.location) + } + if i != startSelectLineIndex { + startIndex = lineRange.location + } + if isReversed { + if i == startSelectLineIndex { + endIndex = startIndex + startIndex = lineRange.location + } + if i == currentSelectLineIndex { + startIndex = endIndex + endIndex = (lineRange.length + lineRange.location) + } + } + } + if startIndex > endIndex { + startIndex = endIndex + startIndex + endIndex = startIndex - endIndex + startIndex = startIndex - endIndex + } + if abs(Int(startIndex) - Int(endIndex)) > 0 && (selectedRange.location == NSNotFound || selectedRange.location > startIndex) { + selectedRange.location = startIndex + } + selectedRange.length += (endIndex - startIndex) + i += isReversed ? -1 : 1 + } + } + return selectedRange + } + + + public func findIndex(location:NSPoint) -> Int { + + if location.y == .greatestFiniteMagnitude { + return lines.count - 1 + } else if location.y == 0 { + return 0 + } + //var previous:NSRect = lines[0].frame + for idx in 0 ..< lines.count { + if isCurrentLine(pos: location, index: idx) { + return idx + } + } + + return location.y <= layoutSize.height ? 0 : (lines.count - 1) + + } + + public func inSelectedRange(_ location:NSPoint) -> Bool { + let index = findCharacterIndex(at: location) + return selectedRange.range.indexIn(index) + } + + public func isCurrentLine(pos:NSPoint, index:Int) -> Bool { + + let line = lines[index] + var rect = line.frame + + var ascent:CGFloat = 0 + var descent:CGFloat = 0 + var leading:CGFloat = 0 + + CTLineGetTypographicBounds(line.line, &ascent, &descent, &leading) + + rect.origin.y = rect.minY - rect.height + ceil(descent - leading) + rect.size.height += ceil(descent - leading) + + if line.isBlocked { + rect.size.height += 2 + } + + return (pos.y > rect.minY) && pos.y < rect.maxY + + } + + fileprivate func color(at point: NSPoint) -> (NSColor, String)? { + + for value in self.hexColorsRect { + if NSPointInRect(point, value.0) { + return (value.1, value.2) + } + } + return nil + } + + public func link(at point:NSPoint) -> (Any, LinkType, NSRange, NSRect)? { + + let index = findIndex(location: point) + + guard index != -1, !lines.isEmpty else { + return nil + } + + let line = lines[index] + var ascent:CGFloat = 0 + var descent:CGFloat = 0 + var leading:CGFloat = 0 + + let width:CGFloat = CGFloat(CTLineGetTypographicBounds(line.line, &ascent, &descent, &leading)); + + var point = point + + //point.x -= floorToScreenPixels(System.backingScale, (frame.width - line.frame.width) / 2) + + +// var penOffset = CGFloat( CTLineGetPenOffsetForFlush(line.line, line.penFlush, Double(frame.width))) + line.frame.minX +// if layout.penFlush == 0.5, line.penFlush != 0.5 { +// penOffset = startPosition.x +// } else if layout.penFlush == 0.0 { +// penOffset = startPosition.x +// } + + point.x -= ((layoutSize.width - line.frame.width) * line.penFlush) + + + if width > point.x, point.x >= 0 { + var pos = CTLineGetStringIndexForPosition(line.line, point); + pos = min(max(0,pos),attributedString.length - 1) + var range:NSRange = NSMakeRange(NSNotFound, 0) + let attrs = attributedString.attributes(at: pos, effectiveRange: &range) + + let link:Any? = attrs[NSAttributedString.Key.link] + if let link = link { + let startOffset = CTLineGetOffsetForStringIndex(line.line, range.location, nil); + let endOffset = CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil); + return (link, interactions.makeLinkType((link, attributedString.attributedSubstring(from: range).string)), range, NSMakeRect(startOffset, line.frame.minY, endOffset - startOffset, ceil(ascent + ceil(descent) + leading))) + } + } + return nil + } + + func findCharacterIndex(at point:NSPoint) -> Int { + let index = findIndex(location: point) + + guard index != -1 else { + return -1 + } + + let line = lines[index] + let width:CGFloat = CGFloat(CTLineGetTypographicBounds(line.line, nil, nil, nil)); + if width > point.x { + let charIndex = Int(CTLineGetStringIndexForPosition(line.line, point)) + return charIndex == attributedString.length ? charIndex - 1 : charIndex + } + return -1 + } + + public func offset(for index: Int) -> CGFloat? { + let line = self.lines.first(where: { + $0.range.indexIn(index) + }) + if let line = line { + return CTLineGetOffsetForStringIndex(line.line, index, nil) + } + return nil + } + + public func selectAll(at point:NSPoint) -> Void { + + let startIndex = findCharacterIndex(at: point) + if startIndex == -1 { + return + } + + var blockRange: NSRange = NSMakeRange(NSNotFound, 0) + if let _ = attributedString.attribute(.preformattedPre, at: startIndex, effectiveRange: &blockRange) { + self.selectedRange = TextSelectedRange(range: blockRange, color: selectText, def: true) + } else { + self.selectedRange = TextSelectedRange(range: NSMakeRange(0,attributedString.length), color: selectText, def: true) + } + + } + + public func selectWord(at point:NSPoint) -> Void { + + if selectWholeText { + self.selectedRange = TextSelectedRange(range: attributedString.range, color: selectText, def: true) + return + } + + let startIndex = findCharacterIndex(at: point) + if startIndex == -1 { + return + } + var prev = startIndex + var next = startIndex + var range = NSMakeRange(startIndex, 1) + let char:NSString = attributedString.string.nsstring.substring(with: range) as NSString + var effectiveRange:NSRange = NSMakeRange(NSNotFound, 0) + let check = attributedString.attribute(NSAttributedString.Key.link, at: range.location, effectiveRange: &effectiveRange) + if check != nil && effectiveRange.location != NSNotFound { + self.selectedRange = TextSelectedRange(range: effectiveRange, color: selectText, def: true) + return + } + if char == "" { + self.selectedRange = TextSelectedRange(color: selectText) + return + } + let tidyChar = char.trimmingCharacters(in: NSCharacterSet.alphanumerics) + let valid:Bool = tidyChar == "" || tidyChar == "_" || tidyChar == "\u{FFFD}" + let string:NSString = attributedString.string.nsstring + while valid { + let prevChar = string.substring(with: NSMakeRange(prev, 1)) + let nextChar = string.substring(with: NSMakeRange(next, 1)) + let tidyPrev = prevChar.trimmingCharacters(in: NSCharacterSet.alphanumerics) + let tidyNext = nextChar.trimmingCharacters(in: NSCharacterSet.alphanumerics) + var prevValid:Bool = tidyPrev == "" || tidyPrev == "_" || tidyPrev == "\u{FFFD}" + var nextValid:Bool = tidyNext == "" || tidyNext == "_" || tidyNext == "\u{FFFD}" + if (prevValid && prev > 0) { + prev -= 1 + } + if(nextValid && next < string.length - 1) { + next += 1 + } + range.location = prevValid ? prev : prev + 1; + range.length = next - range.location; + if prev == 0 { + prevValid = false + } + if(next == string.length - 1) { + nextValid = false + let nextChar = string.substring(with: NSMakeRange(next, 1)) + let nextTidy = nextChar.trimmingCharacters(in: NSCharacterSet.alphanumerics) + if nextTidy == "" || nextTidy == "_" || nextTidy == "\u{FFFD}" { + range.length += 1 + } + } + if !prevValid && !nextValid { + break + } + if prev == 0 && !nextValid { + break + } + } + + self.selectedRange = TextSelectedRange(range: NSMakeRange(max(range.location, 0), min(max(range.length, 0), string.length)), color: selectText, def: true) + } + + +} + +public func ==(lhs:TextViewLayout, rhs:TextViewLayout) -> Bool { + return lhs.constrainedWidth == rhs.constrainedWidth && lhs.attributedString.isEqual(to: rhs.attributedString) && lhs.selectedRange == rhs.selectedRange && lhs.maximumNumberOfLines == rhs.maximumNumberOfLines && lhs.cutout == rhs.cutout && lhs.truncationType == rhs.truncationType && lhs.constrainedWidth == rhs.constrainedWidth +} + +public enum CursorSelectAlignment { + case min(Int) + case max(Int) +} + +public struct TextSelectedRange: Equatable { + + + public var range:NSRange = NSMakeRange(NSNotFound, 0) { + didSet { + var bp:Int = 0 + bp += 1 + } + } + public var color:NSColor = presentation.colors.selectText + public var def:Bool = true + + public init(range: NSRange = NSMakeRange(NSNotFound, 0), color: NSColor = presentation.colors.selectText, def: Bool = true, cursorAlignment: CursorSelectAlignment = .min(0)) { + self.range = range + self.color = color + self.def = def + self.cursorAlignment = cursorAlignment + } + + public var cursorAlignment: CursorSelectAlignment = .min(0) + + public var hasSelectText:Bool { + return range.location != NSNotFound + } +} + +public func ==(lhs:TextSelectedRange, rhs:TextSelectedRange) -> Bool { + return lhs.def == rhs.def && lhs.range.location == rhs.range.location && lhs.range.length == rhs.range.length && lhs.color.hexString == rhs.color.hexString +} + +//private extension TextView : NSMenuDelegate { +// +//} + +public class TextView: Control, NSViewToolTipOwner { + + + public func view(_ view: NSView, stringForToolTip tag: NSView.ToolTipTag, point: NSPoint, userData data: UnsafeMutableRawPointer?) -> String { + + guard let layout = self.layout else { return "" } + + if let link = layout.link(at: point), let resolved = layout.interactions.resolveLink(link.0)?.removingPercentEncoding { + return resolved.prefixWithDots(70) + } + + return "" + + } + + + private let menuDisposable = MetaDisposable() + + private(set) public var layout:TextViewLayout? + + private var beginSelect:NSPoint = NSZeroPoint + private var endSelect:NSPoint = NSZeroPoint + + public var canBeResponder:Bool = true + + + public var isSelectable:Bool = true { + didSet { + if oldValue != isSelectable { + self.setNeedsDisplayLayer() + } + } + } + + + + public override init() { + super.init(); + layer?.disableActions() + self.style = ControlStyle(backgroundColor: .clear) + +// wantsLayer = false +// self.layer?.delegate = nil + } + + public override var isFlipped: Bool { + return true + } + + public required init(frame frameRect: NSRect) { + super.init(frame:frameRect) + layer?.disableActions() + self.style = ControlStyle(backgroundColor: .clear) + + + +// wantsLayer = false +// self.layer?.delegate = nil + // self.layer?.drawsAsynchronously = System.drawAsync + } + + public var disableBackgroundDrawing: Bool = false + + public override func draw(_ layer: CALayer, in ctx: CGContext) { + //backgroundColor = .random + super.draw(layer, in: ctx) + + if let layout = layout { + + + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setShouldSubpixelPositionFonts(true) + + if !System.supportsTransparentFontDrawing { + ctx.setAllowsAntialiasing(true) + + ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) + ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + + if backingScaleFactor == 1.0 && !disableBackgroundDrawing { + ctx.setFillColor(backgroundColor.cgColor) + for line in layout.lines { + ctx.fill(NSMakeRect(0, line.frame.minY - line.frame.height - 2, line.frame.width, line.frame.height + 6)) + } + } + } else { + + ctx.setAllowsAntialiasing(true) + ctx.setShouldAntialias(true) + + ctx.setAllowsFontSmoothing(backingScaleFactor == 1.0) + ctx.setShouldSmoothFonts(backingScaleFactor == 1.0) + } + + + + if let image = layout.blockImage.1 { + ctx.draw(image, in: NSMakeRect(layout.blockImage.0.x, layout.blockImage.0.y, image.backingSize.width, image.backingSize.height)) + } + + + var ranges:[(TextSelectedRange, Bool)] = [(layout.selectedRange, true)] + ranges += layout.additionalSelections.map { ($0, false) } + + for range in ranges { + if range.0.range.location != NSNotFound && (range.1 && isSelectable || !range.1) { + + var lessRange = range.0.range + + var lines:[TextViewLine] = layout.lines + + let beginIndex:Int = 0 + let endIndex:Int = layout.lines.count - 1 + + + let isReversed = endIndex < beginIndex + + var i:Int = beginIndex + + while isReversed ? i >= endIndex : i <= endIndex { + + + let line = lines[i].line + var rect:NSRect = lines[i].frame + let lineRange = CTLineGetStringRange(line) + + var beginLineIndex:CFIndex = 0 + var endLineIndex:CFIndex = 0 + + if (lineRange.location + lineRange.length >= lessRange.location) && lessRange.length > 0 { + beginLineIndex = lessRange.location + let max = lineRange.length + lineRange.location + let maxSelect = max - beginLineIndex + + let selectLength = min(maxSelect,lessRange.length) + + lessRange.length-=selectLength + lessRange.location+=selectLength + + endLineIndex = beginLineIndex + selectLength + + var ascent:CGFloat = 0 + var descent:CGFloat = 0 + var leading:CGFloat = 0 + + var width:CGFloat = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading)); + + let startOffset = CTLineGetOffsetForStringIndex(line, beginLineIndex, nil); + let endOffset = CTLineGetOffsetForStringIndex(line, endLineIndex, nil); + + width = endOffset - startOffset; + + + if beginLineIndex == -1 { + beginLineIndex = 0 + } else if beginLineIndex >= layout.attributedString.length { + beginLineIndex = layout.attributedString.length - 1 + } + + let blockValue:CGFloat = layout.mayBlocked ? CGFloat((layout.attributedString.attribute(.preformattedPre, at: beginLineIndex, effectiveRange: nil) as? NSNumber)?.floatValue ?? 0) : 0 + + + + rect.size.width = width - blockValue / 2 + + rect.origin.x = startOffset + blockValue + rect.origin.y = rect.minY - rect.height + blockValue / 2 + rect.size.height += ceil(descent - leading) + let color:NSColor = window?.isKeyWindow == true || !range.1 ? range.0.color : NSColor.lightGray + + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + } + + i += isReversed ? -1 : 1 + + } + + } + } + + + + let textMatrix = ctx.textMatrix + let textPosition = ctx.textPosition + let startPosition = focus(layout.layoutSize).origin + + + ctx.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + + + for i in 0 ..< layout.lines.count { + let line = layout.lines[i] + + var penOffset = CGFloat( CTLineGetPenOffsetForFlush(line.line, line.penFlush, Double(frame.width))) + line.frame.minX + if layout.penFlush == 0.5, line.penFlush != 0.5 { + penOffset = startPosition.x + } else if layout.penFlush == 0.0 { + penOffset = startPosition.x + } + var additionY: CGFloat = 0 + if layout.isBigEmoji { + additionY -= 4 + } + + ctx.textPosition = CGPoint(x: penOffset, y: startPosition.y + line.frame.minY + additionY) + + CTLineDraw(line.line, ctx) + + if !line.strikethrough.isEmpty { + for strikethrough in line.strikethrough { + let frame = strikethrough.frame.offsetBy(dx: penOffset, dy: startPosition.y + line.frame.minY) + ctx.setFillColor(strikethrough.color.cgColor) + ctx.fill(CGRect(x: frame.minX, y: frame.minY - 5, width: frame.width, height: 1.0)) + } + } + } + + ctx.textMatrix = textMatrix + ctx.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) + + for stroke in layout.strokeRects { + ctx.setFillColor(stroke.1.cgColor) + ctx.fill(stroke.0) + } + + for hexColor in layout.hexColorsRect { + ctx.setFillColor(hexColor.1.cgColor) + ctx.fill(hexColor.0) + } + + + } + + } + + + public override func rightMouseDown(with event: NSEvent) { + if let layout = layout, userInteractionEnabled, layout.mayItems { + let location = convert(event.locationInWindow, from: nil) + if (!layout.selectedRange.hasSelectText || !layout.inSelectedRange(location)) && (!layout.alwaysStaticItems || layout.link(at: location) != nil) { + layout.selectWord(at : location) + } + self.setNeedsDisplayLayer() + if (layout.selectedRange.hasSelectText && isSelectable) || !layout.alwaysStaticItems { + let link = layout.link(at: convert(event.locationInWindow, from: nil)) + + if let menuItems = layout.interactions.menuItems?(link?.1) { + menuDisposable.set((menuItems |> deliverOnMainQueue).start(next:{ [weak self] items in + if let strongSelf = self { + let menu = NSMenu() + for item in items { + menu.addItem(item) + } + RunLoop.current.add(Timer.scheduledTimer(timeInterval: 0, target: strongSelf, selector: #selector(strongSelf.openPanelInRunLoop), userInfo: (event, menu), repeats: false), forMode: RunLoop.Mode.modalPanel) + } + })) + } else { + let link = layout.link(at: location) + let resolved: String? = link != nil ? layout.interactions.resolveLink(link!.0) : nil + let menu = NSMenu() + let copy = ContextMenuItem(link?.1 != nil ? layout.interactions.localizeLinkCopy(link!.1) : localizedString("Text.Copy"), handler: { [weak self] in + guard let `self` = self else {return} + if let resolved = resolved { + let pb = NSPasteboard.general + pb.clearContents() + pb.declareTypes([.string], owner: self) + pb.setString(resolved, forType: .string) + } else { + self.copy(self) + } + }) + // let copy = NSMenuItem(title: , action: #selector(copy(_:)), keyEquivalent: "") + menu.addItem(copy) + RunLoop.current.add(Timer.scheduledTimer(timeInterval: 0, target: self, selector: #selector(self.openPanelInRunLoop), userInfo: (event, menu), repeats: false), forMode: RunLoop.Mode.modalPanel) + } + } else { + layout.selectedRange.range = NSMakeRange(NSNotFound, 0) + needsDisplay = true + super.rightMouseDown(with: event) + } + } else { + super.rightMouseDown(with: event) + } + } + + @objc private func openPanelInRunLoop(_ timer:Foundation.Timer) { + if let (event, menu) = timer.userInfo as? (NSEvent, NSMenu) { + NSMenu.popUpContextMenu(menu, with: event, for: self) + // menu.delegate = self + } + } + + + public func menuDidClose(_ menu: NSMenu) { + + } + + /* + var view: NSTextView? = (self.window?.fieldEditor(true, forObject: self) as? NSTextView) + view?.isEditable = false + view?.isSelectable = true + view?.string = layout.attributedString.string + view?.selectedRange = NSRange(location: 0, length: view?.string?.length) + NSMenu.popUpContextMenu(view?.menu(for: event), with: event, for: view) + */ + + public override func menu(for event: NSEvent) -> NSMenu? { + + return nil + } + + deinit { + menuDisposable.dispose() + NotificationCenter.default.removeObserver(self) + } + + public func isEqual(to layout:TextViewLayout) -> Bool { + return self.layout == layout + } + + // + + public func update(_ layout:TextViewLayout?, origin:NSPoint? = nil) -> Void { + self.layout = layout + + + + if let layout = layout { + self.set(selectedRange: layout.selectedRange.range, display: false) + let point:NSPoint + if let origin = origin { + point = origin + } else { + point = frame.origin + } + self.frame = NSMakeRect(point.x, point.y, layout.layoutSize.width + layout.insets.width, layout.layoutSize.height + layout.insets.height) + + removeAllToolTips() + for rect in layout.toolTipRects { + addToolTip(rect, owner: self, userData: nil) + } + + } else { + self.set(selectedRange: NSMakeRange(NSNotFound, 0), display: false) + self.frame = NSZeroRect + } + + + + + self.setNeedsDisplayLayer() + } + + public func set(layout:TextViewLayout?) { + self.layout = layout + self.setNeedsDisplayLayer() + } + + func set(selectedRange range:NSRange, display:Bool = true) -> Void { + + if let layout = layout { + layout.selectedRange = TextSelectedRange(range:range, color: layout.selectText, def:true) + } + + beginSelect = NSMakePoint(-1, -1) + endSelect = NSMakePoint(-1, -1) + + + if display { + self.setNeedsDisplayLayer() + } + + } + + public override func mouseDown(with event: NSEvent) { + + if event.modifierFlags.contains(.control) { + rightMouseDown(with: event) + return + } + + if isSelectable && !event.modifierFlags.contains(.shift) { + self.window?.makeFirstResponder(nil) + } + if !userInteractionEnabled { + super.mouseDown(with: event) + } + else if let layout = layout { + let point = self.convert(event.locationInWindow, from: nil) + let index = layout.findIndex(location: point) + if point.x > layout.lines[index].frame.maxX { + superview?.mouseDown(with: event) + } + } + + _mouseDown(with: event) + + } + + + public override func viewWillMove(toWindow newWindow: NSWindow?) { + if let newWindow = newWindow { + NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: newWindow) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: newWindow) + } else { + NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.removeObserver(self, name: NSWindow.didResignKeyNotification, object: window) + } + } + + + @objc open func windowDidBecomeKey() { + needsDisplay = true + } + + @objc open func windowDidResignKey() { + needsDisplay = true + } + + private var locationInWindow:NSPoint? = nil + + func _mouseDown(with event: NSEvent) -> Void { + + self.locationInWindow = event.locationInWindow + + if !isSelectable || !userInteractionEnabled || event.modifierFlags.contains(.shift) { + super.mouseDown(with: event) + return + } + + _ = self.becomeFirstResponder() + + set(selectedRange: NSMakeRange(NSNotFound, 0), display: false) + self.beginSelect = self.convert(event.locationInWindow, from: nil) + + self.setNeedsDisplayLayer() + + } + + public override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + checkCursor(event) + _mouseDragged(with: event) + } + + func _mouseDragged(with event: NSEvent) -> Void { + if !isSelectable || !userInteractionEnabled { + return + } + if let locationInWindow = self.locationInWindow { + let old = (ceil(locationInWindow.x), ceil(locationInWindow.y)) + let new = (ceil(event.locationInWindow.x), round(event.locationInWindow.y)) + if abs(old.0 - new.0) <= 1 && abs(old.1 - new.1) <= 1 { + return + } + } + + endSelect = self.convert(event.locationInWindow, from: nil) + if let layout = layout { + layout.selectedRange.range = layout.selectedRange(startPoint: beginSelect, currentPoint: endSelect) + layout.selectedRange.cursorAlignment = beginSelect.x > endSelect.x ? .min(layout.selectedRange.range.max) : .max(layout.selectedRange.range.min) + } + self.setNeedsDisplayLayer() + } + + + public override func mouseEntered(with event: NSEvent) { + if userInteractionEnabled { + checkCursor(event) + } else { + super.mouseEntered(with: event) + } + + } + + public override func mouseExited(with event: NSEvent) { + if userInteractionEnabled { + checkCursor(event) + } else { + super.mouseExited(with: event) + } + } + + public override func mouseMoved(with event: NSEvent) { + if userInteractionEnabled { + checkCursor(event) + } else { + super.mouseMoved(with: event) + } + } + + + + public override func mouseUp(with event: NSEvent) { + + self.locationInWindow = nil + + if let layout = layout, userInteractionEnabled { + let point = self.convert(event.locationInWindow, from: nil) + if event.clickCount == 3, isSelectable { + layout.selectAll(at: point) + layout.selectedRange.cursorAlignment = .max(layout.selectedRange.range.min) + } else if isSelectable, event.clickCount == 2 || (event.type == .rightMouseUp && !layout.selectedRange.hasSelectText) { + layout.selectWord(at : point) + layout.selectedRange.cursorAlignment = .max(layout.selectedRange.range.min) + } else if !layout.selectedRange.hasSelectText || !isSelectable && (event.clickCount == 1 || !isSelectable) { + if let color = layout.color(at: point), let copyToClipboard = layout.interactions.copyToClipboard { + copyToClipboard(color.0.hexString.lowercased()) + } else if let (link, _, _, _) = layout.link(at: point) { + if event.clickCount == 1 { + layout.interactions.processURL(link) + } + } else { + super.mouseUp(with: event) + } + } else if layout.selectedRange.hasSelectText && event.clickCount == 1 && event.modifierFlags.contains(.shift) { + var range = layout.selectedRange.range + let index = layout.findCharacterIndex(at: point) + if index < range.min { + range.length += (range.location - index) + range.location = index + } else if index > range.max { + range.length = (index - range.location) + } + layout.selectedRange.range = range + } else { + super.mouseUp(with: event) + } + setNeedsDisplay() + } else { + super.mouseUp(with: event) + } + + self.beginSelect = NSMakePoint(-1, -1) + } + public override func cursorUpdate(with event: NSEvent) { + if userInteractionEnabled { + checkCursor(event) + } else { + super.cursorUpdate(with: event) + } + } + + func checkCursor(_ event:NSEvent) -> Void { + + let location = self.convert(event.locationInWindow, from: nil) + + if self.isMousePoint(location , in: self.visibleRect) && mouseInside() && userInteractionEnabled { + if layout?.color(at: location) != nil { + NSCursor.pointingHand.set() + } else if let layout = layout, let (_, _, _, _) = layout.link(at: location) { + NSCursor.pointingHand.set() + } else if isSelectable { + NSCursor.iBeam.set() + } else { + NSCursor.arrow.set() + } + } else { + NSCursor.arrow.set() + } + } + + + + + public override func becomeFirstResponder() -> Bool { + if canBeResponder { + if let window = self.window { + return window.makeFirstResponder(self) + } + } + + + return false + } + +// public override var isOpaque: Bool { +// return false +// } + + public override func resignFirstResponder() -> Bool { + _resignFirstResponder() + return super.resignFirstResponder() + } + + func _resignFirstResponder() -> Void { + self.set(selectedRange: NSMakeRange(NSNotFound, 0)) + } + + public override func responds(to aSelector: Selector!) -> Bool { + + if NSStringFromSelector(aSelector) == "copy:" { + return self.layout?.selectedRange.range.location != NSNotFound + } + + return super.responds(to: aSelector) + } + + @objc public func copy(_ sender:Any) -> Void { + if let layout = layout { + if let copy = layout.interactions.copy { + if !copy() && layout.selectedRange.range.location != NSNotFound { + if !layout.interactions.copyAttributedString(layout.attributedString.attributedSubstring(from: layout.selectedRange.range)) { + let pb = NSPasteboard.general + pb.clearContents() + pb.declareTypes([.string], owner: self) + pb.setString(layout.attributedString.string.nsstring.substring(with: layout.selectedRange.range), forType: .string) + } + } + } else if layout.selectedRange.range.location != NSNotFound { + if !layout.interactions.copyAttributedString(layout.attributedString.attributedSubstring(from: layout.selectedRange.range)) { + let pb = NSPasteboard.general + pb.clearContents() + pb.declareTypes([.string], owner: self) + pb.setString(layout.attributedString.string.nsstring.substring(with: layout.selectedRange.range), forType: .string) + } + } + } + } + + @objc func paste(_ sender:Any) { + + } + + public override func removeFromSuperview() { + super.removeFromSuperview() + } + + + public func updateWithNewWidth(_ width: CGFloat) { + let layout = self.layout + layout?.measure(width: width) + self.update(layout) + } + + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/TGUIKit/TGUIKit/TextViewLabel.swift b/submodules/TGUIKit/TGUIKit/TextViewLabel.swift similarity index 89% rename from TGUIKit/TGUIKit/TextViewLabel.swift rename to submodules/TGUIKit/TGUIKit/TextViewLabel.swift index afa4e49850..a95c0ab0c1 100644 --- a/TGUIKit/TGUIKit/TextViewLabel.swift +++ b/submodules/TGUIKit/TGUIKit/TextViewLabel.swift @@ -24,7 +24,7 @@ open class TextViewLabel: View { public var linesCount:Int = 1 public var autosize:Bool = false public var inset:NSEdgeInsets = NSEdgeInsets() - + public var alignment: NSTextAlignment = .left public var attributedString:NSAttributedString? { didSet { if attributedString != oldValue { @@ -45,15 +45,16 @@ open class TextViewLabel: View { if let text = text { let focus = self.focus(text.0.size) - text.1.draw(focus, in: ctx, backingScaleFactor: backingScaleFactor) + text.1.draw(focus, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) } } public func sizeToFit() -> Void { self.update(attr: self.attributedString, size: NSMakeSize(CGFloat.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitude)) if let text = text { - self.frame = NSMakeRect(frame.minX, frame.minY, text.0.size.width, text.0.size.height) + self.frame = NSMakeRect(frame.minX, frame.minY, text.0.size.width + 4, text.0.size.height) } + } public func sizeTo() -> Void { @@ -64,7 +65,7 @@ open class TextViewLabel: View { func update(attr:NSAttributedString?, size:NSSize) -> Void { if let attr = attr { - text = TextNode.layoutText(maybeNode: node, attr, nil, linesCount, .end, size, nil,false, .left) + text = TextNode.layoutText(maybeNode: nil, attr, nil, linesCount, .end, size, nil,false, alignment) } else { text = nil } @@ -74,7 +75,7 @@ open class TextViewLabel: View { open override func layout() { super.layout() if autosize { - text = TextNode.layoutText(maybeNode: node, attributedString, nil, linesCount, .end, NSMakeSize(frame.width - inset.left - inset.right, frame.height), nil,false, .left) + text = TextNode.layoutText(maybeNode: node, attributedString, nil, linesCount, .end, NSMakeSize(frame.width - inset.left - inset.right, frame.height), nil,false, alignment) self.setNeedsDisplay() } } diff --git a/TGUIKit/TGUIKit/TimableProgressView.swift b/submodules/TGUIKit/TGUIKit/TimableProgressView.swift similarity index 87% rename from TGUIKit/TGUIKit/TimableProgressView.swift rename to submodules/TGUIKit/TGUIKit/TimableProgressView.swift index 43b104a3a1..dc1e3a3351 100644 --- a/TGUIKit/TGUIKit/TimableProgressView.swift +++ b/submodules/TGUIKit/TGUIKit/TimableProgressView.swift @@ -7,7 +7,7 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit public final class TimableProgressTheme { let backgroundColor: NSColor @@ -48,7 +48,7 @@ public class TimableProgressView: View { } private var _progress: Double = 100 - private var timer: SwiftSignalKitMac.Timer? + private var timer: SwiftSignalKit.Timer? public func startAnimation() { timer?.invalidate() @@ -56,7 +56,7 @@ public class TimableProgressView: View { let fps: TimeInterval = 60 let difference = Double(progress) - _progress let tick: Double = Double(difference / (fps * duration)) - timer = SwiftSignalKitMac.Timer(timeout: 1 / fps, repeat: true, completion: { [weak self] in + timer = SwiftSignalKit.Timer(timeout: 1 / fps, repeat: true, completion: { [weak self] in if let strongSelf = self { strongSelf._progress += tick strongSelf.needsDisplay = true @@ -86,12 +86,12 @@ public class TimableProgressView: View { public override func draw(_ layer: CALayer, in ctx: CGContext) { - ctx.round(frame.size, floorToScreenPixels(frame.width/2)) + ctx.round(frame.size, floorToScreenPixels(backingScaleFactor, frame.width/2)) ctx.setFillColor(theme.backgroundColor.cgColor) ctx.fill(bounds) - let center = NSMakePoint(floorToScreenPixels(frame.width/2), floorToScreenPixels(frame.height/2)) + let center = NSMakePoint(frame.width/2, frame.height/2) let startAngle: CGFloat = 90 @@ -99,7 +99,7 @@ public class TimableProgressView: View { ctx.scaleBy(x: 1.0, y: -1.0) ctx.translateBy(x: -frame.width / 2.0, y: -frame.height / 2.0) - let radius: CGFloat = floorToScreenPixels(frame.width/2) - theme.outer + let radius: CGFloat = frame.width/2 - theme.outer let angle = CGFloat(_progress / 100 * 360) if theme.border { @@ -122,9 +122,9 @@ public class TimableProgressView: View { } - public init(_ theme: TimableProgressTheme = TimableProgressTheme()) { + public init(theme: TimableProgressTheme = TimableProgressTheme(), size: NSSize = NSMakeSize(40, 40)) { self.theme = theme - super.init(frame: NSMakeRect(0, 0, 40, 40)) + super.init(frame: CGRect(origin: .zero, size: size)) } required public init(frame frameRect: NSRect) { diff --git a/submodules/TGUIKit/TGUIKit/TitleButton.swift b/submodules/TGUIKit/TGUIKit/TitleButton.swift new file mode 100644 index 0000000000..fdeb21b109 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TitleButton.swift @@ -0,0 +1,291 @@ +// +// TitleButton.swift +// TGUIKit +// +// Created by keepcoder on 05/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public enum TitleButtonImageDirection { + case left + case right + case top +} + +class TextLayerExt: CATextLayer { + + override func draw(in ctx: CGContext) { + ctx.setAllowsFontSubpixelPositioning(true) + ctx.setShouldSubpixelPositionFonts(true) + ctx.setAllowsAntialiasing(true) + ctx.setShouldAntialias(true) + ctx.setAllowsFontSmoothing(System.backingScale == 1.0) + ctx.setShouldSmoothFonts(System.backingScale == 1.0) + + super.draw(in: ctx) + } + +} + + +public class TitleButton: ImageButton { + + private var text:TextLayerExt = TextLayerExt() + + private var stateText:[ControlState:String] = [:] + private var stateColor:[ControlState:NSColor] = [:] + private var stateFont:[ControlState:NSFont] = [:] + + public var autoSizeToFit: Bool = true + + public var direction: TitleButtonImageDirection = .left { + didSet { + if direction != oldValue { + updateLayout() + } + } + } + + public override init() { + super.init() + } + + public func set(text:String, for state:ControlState) -> Void { + stateText[state] = text + apply(state: self.controlState) + if autoSizeToFit { + _ = sizeToFit(NSZeroSize, self.frame.size, thatFit: _thatFit) + } + + } + + public func set(color:NSColor, for state:ControlState) -> Void { + stateColor[state] = color + apply(state: self.controlState) + } + + public func set(font:NSFont, for state:ControlState) -> Void { + stateFont[state] = font + apply(state: self.controlState) + } + + override public func apply(state: ControlState) { + let state:ControlState = self.isSelected ? .Highlight : state + super.apply(state: state) + + if let stateText = stateText[state] { + text.string = stateText + } else { + text.string = stateText[.Normal] + } + + if isEnabled { + if let stateColor = stateColor[state] { + text.foregroundColor = stateColor.cgColor + } else if let stateColor = stateColor[.Normal] { + text.foregroundColor = stateColor.cgColor + } else { + text.foregroundColor = style.foregroundColor.cgColor + } + + } else { + text.foregroundColor = presentation.colors.grayText.cgColor + } + + text.backgroundColor = .clear + + if let stateFont = stateFont[state] { + text.font = stateFont.fontName as CFTypeRef + text.fontSize = stateFont.pointSize + } else if let stateFont = stateFont[.Normal] { + text.font = stateFont.fontName as CFTypeRef + text.fontSize = stateFont.pointSize + } else { + text.font = style.font.fontName as CFTypeRef + text.fontSize = style.font.pointSize + } + + } + + public var isEmpty: Bool { + if let string = text.string as? String { + return string.isEmpty + } else { + return true + } + } + + public override func sizeToFit(_ addition: NSSize = NSZeroSize, _ maxSize:NSSize = NSZeroSize, thatFit:Bool = false) -> Bool { + _ = super.sizeToFit(addition, maxSize, thatFit: thatFit) + + var font:NSFont? + if let fontName = self.text.font as? String { + font = NSFont(name: fontName, size: text.fontSize) + } else if let _font = self.text.font as? NSFont { + font = NSFont(name: _font.fontName, size: text.fontSize) + } + font = font ?? .normal(text.fontSize) + let size:NSSize = TitleButton.size(with: self.text.string as! String?, font: font) + + var msize:NSSize = size + + if maxSize.width < size.width { + if let image = imageView.image, direction != .top { + msize.width += (image.backingSize.width + (12)) // max size + } + } + + var maxWidth:CGFloat = !thatFit || maxSize.width == 0 ? ( maxSize.width > 0 ? maxSize.width : msize.width ) : maxSize.width + + + + var textSize:CGFloat = maxWidth + + if let image = imageView.image, direction != .top { + + textSize = min(maxWidth,size.width) + let iwidth:CGFloat = (image.backingSize.width + (12)) + + if textSize == maxWidth { + textSize -= iwidth + } else { + textSize = (maxWidth - size.width) >= iwidth ? size.width : maxWidth - iwidth + maxWidth = textSize + iwidth + } + } else { + maxWidth = min(size.width, textSize) + textSize = min(size.width, textSize) + } + + if thatFit && maxSize.width > 0 { + maxWidth = maxSize.width + } else { + maxWidth += addition.width + } + + + self.text.frame = NSMakeRect(0, 0, textSize, size.height) + + + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: maxWidth, height: max(size.height,maxSize.height) + addition.height) + return frame.width >= maxWidth + } + + public override func updateLayout() { + super.updateLayout() + + let textFocus:NSRect = focus(self.text.frame.size) + if let _ = imageView.image { + if let string = self.text.string as? String, !string.isEmpty { + let imageFocus:NSRect = focus(self.imageView.frame.size) + switch direction { + case .left: + self.imageView.frame = NSMakeRect(round((self.frame.width - textFocus.width - imageFocus.width)/2.0 - 4), imageFocus.minY, imageFocus.width, imageFocus.height) + self.text.frame = NSMakeRect(imageView.frame.maxX + 4, textFocus.minY, textFocus.width, textFocus.height) + case .right: + self.imageView.frame = NSMakeRect(round(frame.width - imageFocus.width - 4), imageFocus.minY, imageFocus.width, imageFocus.height) + self.text.frame = NSMakeRect(0, textFocus.minY, textFocus.width, textFocus.height) + case .top: + self.imageView.frame = NSMakeRect(imageFocus.minX, imageFocus.minY - textFocus.height / 2 - 2, imageFocus.width, imageFocus.height) + self.text.frame = NSMakeRect(textFocus.minX, self.imageView.frame.maxY, textFocus.width, textFocus.height) + } + } else { + self.imageView.center() + } + + + } else { + self.text.frame = textFocus + } + + } + + + public static func size(with string: String?, font: NSFont?) -> NSSize { + if font == nil || string == nil { + return NSZeroSize + } + let attributedString:NSAttributedString = NSAttributedString.initialize(string: string, font: font, coreText: true) + let layout = TextViewLayout(attributedString) + layout.measure(width: .greatestFiniteMagnitude) + var size:NSSize = layout.layoutSize + size.width = ceil(size.width) + (size.width == 0 ? 0 : 10) + size.height = ceil(size.height) + return size + } + + + public override var style: ControlStyle { + set { + super.style = newValue + apply(state: self.controlState) +// +// self.set(color: style.foregroundColor, for: .Normal) +// self.set(color: style.highlightColor, for: .Highlight) +// self.set(font: style.font, for: .Normal) +// self.backgroundColor = style.backgroundColor + } + get { + return super.style + } + } + + override func prepare() { + super.prepare() + text.truncationMode = .end; + text.alignmentMode = .center; + self.layer?.addSublayer(text) + + text.actions = ["bounds":NSNull(),"position":NSNull()] + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + } + + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + disableActions() + } + + public override var backgroundColor: NSColor { + set { + super.backgroundColor = newValue + self.text.backgroundColor = newValue.cgColor + } + get { + return super.backgroundColor + } + } + + public override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + if let screen = NSScreen.main { + self.text.contentsScale = screen.backingScaleFactor + } + + } + + public override func disableActions() { + super.disableActions() + + self.text.disableActions() + self.layer?.disableActions() + } + + public override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + } + + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public var textSize: NSSize { + return self.text.frame.size + } + +} diff --git a/submodules/TGUIKit/TGUIKit/TitledBarView.swift b/submodules/TGUIKit/TGUIKit/TitledBarView.swift new file mode 100644 index 0000000000..9f83dd7d74 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TitledBarView.swift @@ -0,0 +1,195 @@ +// +// TitledBarView.swift +// TGUIKit +// +// Created by keepcoder on 16/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +private class TitledContainerView : View { + + private var statusNode:TextNode = TextNode() + private var titleNode:TextNode = TextNode() + var titleImage:(CGImage, TitleBarImageSide)? { + didSet { + self.setNeedsDisplay() + } + } + + var inset:CGFloat = 50 + + var text:NSAttributedString? { + didSet { + if text != oldValue { + self.setNeedsDisplay() + } + } + } + + var status:NSAttributedString? { + didSet { + if status != oldValue { + self.setNeedsDisplay() + } + } + } + + var hiddenStatus:Bool = false { + didSet { + self.setNeedsDisplay() + } + } + + var textInset:CGFloat? = nil { + didSet { + self.setNeedsDisplay() + } + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + backgroundColor = presentation.colors.background + } + + fileprivate override func draw(_ layer: CALayer, in ctx: CGContext) { + + + + if let text = text, let superview = superview?.superview { + + var additionalInset: CGFloat = 0 + if let (image,_) = titleImage { + additionalInset += image.backingSize.width + 5 + } + + let (textLayout, textApply) = TextNode.layoutText(maybeNode: titleNode, text, nil, 1, .end, NSMakeSize(frame.width - inset - additionalInset, frame.height), nil,false, .left) + var tY = focus(textLayout.size).minY + + if let status = status { + + let (statusLayout, statusApply) = TextNode.layoutText(maybeNode: statusNode, status, nil, 1, .end, NSMakeSize(frame.width - inset - additionalInset, frame.height), nil,false, .left) + + let t = textLayout.size.height + statusLayout.size.height + 2.0 + tY = floorToScreenPixels(backingScaleFactor, (frame.height - t) / 2.0) + + let sY = tY + textLayout.size.height + 2.0 + if !hiddenStatus { + let point = convert( NSMakePoint(floorToScreenPixels(backingScaleFactor, (superview.frame.width - statusLayout.size.width)/2.0), tY), from: superview) + + statusApply.draw(NSMakeRect(textInset == nil ? point.x : textInset!, sY, statusLayout.size.width, statusLayout.size.height), in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } + + let point = convert( NSMakePoint(floorToScreenPixels(backingScaleFactor, (superview.frame.width - textLayout.size.width)/2.0), tY), from: superview) + var textRect = NSMakeRect(min(max(textInset == nil ? point.x : textInset!, 0), frame.width - textLayout.size.width), point.y, textLayout.size.width, textLayout.size.height) + + if let (titleImage, side) = titleImage { + switch side { + case .left: + ctx.draw(titleImage, in: NSMakeRect(textInset == nil ? textRect.minX - titleImage.backingSize.width : textInset!, tY + 4, titleImage.backingSize.width, titleImage.backingSize.height)) + textRect.origin.x += floorToScreenPixels(backingScaleFactor, titleImage.backingSize.width) + 4 + case .right: + ctx.draw(titleImage, in: NSMakeRect(textRect.maxX + 3, tY + 1, titleImage.backingSize.width, titleImage.backingSize.height)) + } + } + + textApply.draw(textRect, in: ctx, backingScaleFactor: backingScaleFactor, backgroundColor: backgroundColor) + } + } +} + +public enum TitleBarImageSide { + case left + case right +} + +open class TitledBarView: BarView { + + public var titleImage:(CGImage, TitleBarImageSide)? { + didSet { + _containerView.titleImage = titleImage + } + } + + open override var backgroundColor: NSColor { + didSet { + containerView.backgroundColor = .clear + } + } + + public var text:NSAttributedString? { + didSet { + if text != oldValue { + _containerView.inset = inset + _containerView.text = text + } + } + } + + public var status:NSAttributedString? { + didSet { + if status != oldValue { + _containerView.inset = inset + _containerView.status = status + } + } + } + + private let _containerView:TitledContainerView = TitledContainerView() + public var containerView:View { + return _containerView + } + + public var hiddenStatus:Bool = false { + didSet { + _containerView.hiddenStatus = hiddenStatus + } + } + + open var inset:CGFloat { + return 50 + } + + public var textInset:CGFloat? { + didSet { + _containerView.textInset = textInset + } + } + + open override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + containerView.setFrameSize(newSize) + containerView.setNeedsDisplay() + } + public init(controller: ViewController, _ text:NSAttributedString? = nil, _ status:NSAttributedString? = nil, textInset:CGFloat? = nil) { + self.text = text + self.status = status + self.textInset = textInset + super.init(controller: controller) + addSubview(containerView) + _containerView.text = text + _containerView.status = status + _containerView.textInset = textInset + } + + open override func draw(_ dirtyRect: NSRect) { + + } + + deinit { + var bp:Int = 0 + bp += 1 + } + + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/submodules/TGUIKit/TGUIKit/TokenizedView.swift b/submodules/TGUIKit/TGUIKit/TokenizedView.swift new file mode 100644 index 0000000000..aa2ddb9637 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TokenizedView.swift @@ -0,0 +1,463 @@ +// +// TokenizedView.swift +// TGUIKit +// +// Created by keepcoder on 07/08/2017. +// Copyright © 2017 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +public struct SearchToken : Equatable { + public let name:String + public let uniqueId:Int64 + public init(name:String, uniqueId: Int64) { + self.name = name + self.uniqueId = uniqueId + } +} + +public func ==(lhs:SearchToken, rhs: SearchToken) -> Bool { + return lhs.name == rhs.name && lhs.uniqueId == rhs.uniqueId +} + +private class TokenView : Control { + fileprivate let token:SearchToken + private var isFailed: Bool = false + private let dismiss: ImageButton = ImageButton() + private let nameView: TextView = TextView() + fileprivate var immediatlyPaste: Bool = true + override var isSelected: Bool { + didSet { + updateLocalizationAndTheme(theme: presentation) + } + } + + init(_ token: SearchToken, maxSize: NSSize, onDismiss:@escaping()->Void, onSelect: @escaping()->Void) { + self.token = token + super.init() + self.layer?.cornerRadius = 6 + let layout = TextViewLayout(.initialize(string: token.name, color: .white, font: .normal(.title)), maximumNumberOfLines: 1) + layout.measure(width: maxSize.width - 30) + self.nameView.update(layout) + + nameView.userInteractionEnabled = false + nameView.isSelectable = false + + setFrameSize(NSMakeSize(layout.layoutSize.width + 30, maxSize.height)) + dismiss.autohighlight = false + updateLocalizationAndTheme(theme: presentation) + needsLayout = true + addSubview(nameView) + addSubview(dismiss) + dismiss.set(handler: { _ in + onDismiss() + }, for: .Click) + set(handler: { _ in + onSelect() + }, for: .Click) + } + + fileprivate func setFailed(_ isFailed: Bool, animated: Bool) { + self.isFailed = isFailed + self.updateLocalizationAndTheme(theme: presentation) + if isFailed { + self.shake() + } + } + + fileprivate var isPerfectSized: Bool { + return nameView.layout?.isPerfectSized ?? false + } + + override func change(size: NSSize, animated: Bool = true, _ save: Bool = true, removeOnCompletion: Bool = false, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + nameView.layout?.measure(width: size.width - 30) + + let size = NSMakeSize(min(((nameView.layout?.layoutSize.width ?? 0) + 30), size.width), size.height) + + super.change(size: size, animated: animated, save, duration: duration, timingFunction: timingFunction) + + let point = focus(dismiss.frame.size) + dismiss.change(pos: NSMakePoint(frame.width - 5 - dismiss.frame.width, point.minY), animated: animated) + + nameView.update(nameView.layout) + } + + override func layout() { + super.layout() + nameView.centerY(x: 5) + nameView.setFrameOrigin(5, nameView.frame.minY - 1) + dismiss.centerY(x: frame.width - 5 - dismiss.frame.width) + } + + var _backgroundColor: NSColor { + + var r:CGFloat = 0, g:CGFloat = 0, b:CGFloat = 0, a:CGFloat = 0 + + var redSelected = presentation.colors.redUI + + redSelected.getRed(&r, green: &g, blue: &b, alpha: &a) + redSelected = NSColor(red: min(r + 0.1, 1.0), green: min(g + 0.1, 1.0), blue: min(b + 0.1, 1.0), alpha: a) + + return isSelected ? (isFailed ? redSelected : presentation.colors.accentSelect) : (isFailed ? presentation.colors.redUI : presentation.colors.accent) + } + + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + dismiss.set(image: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(NSColor.white.withAlphaComponent(0.7)), for: .Normal) + dismiss.set(image: #imageLiteral(resourceName: "Icon_SearchClear").precomposed(NSColor.white), for: .Highlight) + _ = dismiss.sizeToFit() + + nameView.backgroundColor = .clear + self.background = _backgroundColor + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + required public init(frame frameRect: NSRect) { + fatalError("init(frame:) has not been implemented") + } +} + +public protocol TokenizedProtocol : class { + func tokenizedViewDidChangedHeight(_ view: TokenizedView, height: CGFloat, animated: Bool) +} + +public class TokenizedView: ScrollView, AppearanceViewProtocol, NSTextViewDelegate { + private var tokens:[SearchToken] = [] + private var failedTokens:[Int64] = [] + private let container: View = View() + private let input:SearchTextField = SearchTextField() + private(set) public var state: SearchFieldState = .None { + didSet { + stateValue.set(state) + } + } + public let stateValue: ValuePromise = ValuePromise(.None, ignoreRepeated: true) + private var selectedIndex: Int? = nil { + didSet { + for view in container.subviews { + if let view = view as? TokenView { + view.isSelected = selectedIndex != nil && view.token == tokens[selectedIndex!] + } + } + } + } + + + private let _tokensUpdater:Promise<[SearchToken]> = Promise([]) + public var tokensUpdater:Signal<[SearchToken], NoError> { + return _tokensUpdater.get() + } + + private let _textUpdater:ValuePromise = ValuePromise("", ignoreRepeated: true) + public var textUpdater:Signal { + return _textUpdater.get() + } + + public weak var delegate: TokenizedProtocol? = nil + private let placeholder: TextView = TextView() + + public func addTokens(tokens: [SearchToken], animated: Bool) -> Void { + self.tokens.append(contentsOf: tokens) + + for token in tokens { + let view = TokenView(token, maxSize: NSMakeSize(80, 22), onDismiss: { [weak self] in + self?.removeTokens(uniqueIds: [token.uniqueId], animated: true) + }, onSelect: { [weak self] in + self?.selectedIndex = self?.tokens.firstIndex(of: token) + }) + container.addSubview(view) + } + _tokensUpdater.set(.single(self.tokens)) + input.string = "" + textDidChange(Notification(name: NSText.didChangeNotification)) + (contentView as? TGClipView)?.scroll(to: NSMakePoint(0, max(0, container.frame.height - frame.height)), animated: animated) + } + + public func removeTokens(uniqueIds: [Int64], animated: Bool) { + for uniqueId in uniqueIds { + var index:Int? = nil + for i in 0 ..< tokens.count { + if tokens[i].uniqueId == uniqueId { + index = i + break + } + } + if let index = index { + tokens.remove(at: index) + for view in container.subviews { + if let view = view as? TokenView { + if view.token.uniqueId == uniqueId { + view.change(opacity: 0, animated: animated, completion: { [weak view] completed in + if completed { + view?.removeFromSuperview() + } + }) + + } + } + } + } + } + layoutContainer(animated: animated) + _tokensUpdater.set(.single(tokens)) + } + + private func layoutContainer(animated: Bool) { + CATransaction.begin() + + let mainw = frame.width + let between = NSMakePoint(5, 4) + var point: NSPoint = between + var extraLine: Bool = false + let count = container.subviews.count + for i in 0 ..< count { + let subview = container.subviews[i] + let next = container.subviews[min(i + 1, count - 1)] + if let token = subview as? TokenView, token.layer?.opacity != 0 { + token.change(pos: point, animated: token.immediatlyPaste ? false : animated) + if animated, token.immediatlyPaste { + token.layer?.animateAlpha(from: 0, to: 1, duration: 0.2) + } + //token.change(size: NSMakeSize(100, token.frame.height), animated: animated) + token.immediatlyPaste = false + + point.x += subview.frame.width + between.x + + let dif = mainw - (point.x + (i == count - 1 ? mainw/3 : next.frame.width) + between.x) + if dif < between.x { + // if !token.isPerfectSized { + // token.change(size: NSMakeSize(frame.width - startPointX - between.x, token.frame.height), animated: animated) + // } + point.x = between.x + point.y += token.frame.height + between.y + } + + } + if subview == container.subviews.last { + if mainw - point.x > mainw/3 { + extraLine = true + } + } + } + + input.frame = NSMakeRect(point.x, point.y + 3, mainw - point.x - between.x, 16) + placeholder.change(pos: NSMakePoint(point.x + 6, point.y + 3), animated: animated) + placeholder.change(opacity: tokens.isEmpty ? 1.0 : 0.0, animated: animated) + let contentHeight = max(point.y + between.y + (extraLine ? 22 : 0), 30) + container.change(size: NSMakeSize(container.frame.width, contentHeight), animated: animated) + + let height = min(contentHeight, 108) + if height != frame.height { + + _change(size: NSMakeSize(mainw, height), animated: animated) + delegate?.tokenizedViewDidChangedHeight(self, height: height, animated: animated) + } + + CATransaction.commit() + } + + public func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + let range = input.selectedRange() + + if commandSelector == #selector(insertNewline(_:)) { + return true + } + if range.location == 0 { + if commandSelector == #selector(moveLeft(_:)) { + if let index = selectedIndex { + selectedIndex = max(index - 1, 0) + } else { + selectedIndex = tokens.count - 1 + } + return true + } else if commandSelector == #selector(moveRight(_:)) { + if let index = selectedIndex { + if index + 1 == tokens.count { + selectedIndex = nil + input.setSelectedRange(NSMakeRange(input.string.length, 0)) + } else { + selectedIndex = index + 1 + } + return true + } + } + + if commandSelector == #selector(deleteBackward(_:)) { + if let selectedIndex = selectedIndex { + removeTokens(uniqueIds: [tokens[selectedIndex].uniqueId], animated: true) + if selectedIndex != tokens.count { + self.selectedIndex = min(selectedIndex, tokens.count - 1) + } else { + self.selectedIndex = nil + input.setSelectedRange(NSMakeRange(input.string.length, 0)) + } + + return true + } else { + if !tokens.isEmpty { + self.selectedIndex = tokens.count - 1 + return true + } + } + + } + } + + + return false + } + + open func textDidChange(_ notification: Notification) { + + let pHidden = !input.string.isEmpty + if placeholder.isHidden != pHidden { + placeholder.isHidden = pHidden + } + if self.window?.firstResponder == self.input { + self.state = .Focus + } + _textUpdater.set(input.string) + selectedIndex = nil + } + + + + public func textDidEndEditing(_ notification: Notification) { + self.didResignResponder() + } + + public func textDidBeginEditing(_ notification: Notification) { + //self.didBecomeResponder() + } + + public override var needsLayout: Bool { + set { + super.needsLayout = false + } + get { + return super.needsLayout + } + } + + public override func layout() { + super.layout() + //layoutContainer(animated: false) + } + + + private let localizationFunc: (String)->String + private let placeholderKey: String + required public init(frame frameRect: NSRect, localizationFunc: @escaping(String)->String, placeholderKey:String) { + self.localizationFunc = localizationFunc + self.placeholderKey = placeholderKey + super.init(frame: frameRect) + + hasVerticalScroller = true + container.frame = bounds + container.autoresizingMask = [.width] + contentView.documentView = container + + input.focusRingType = .none + input.backgroundColor = NSColor.clear + input.delegate = self + input.isRichText = false + + input.textContainer?.widthTracksTextView = true + input.textContainer?.heightTracksTextView = false + + input.isHorizontallyResizable = false + input.isVerticallyResizable = false + + + placeholder.set(handler: { [weak self] _ in + self?.window?.makeFirstResponder(self?.responder) + }, for: .Click) + + input.font = .normal(.text) + container.addSubview(input) + container.addSubview(placeholder) + container.layer?.cornerRadius = 10 + wantsLayer = true + self.layer?.cornerRadius = 10 + self.layer?.backgroundColor = presentation.colors.grayBackground.cgColor + updateLocalizationAndTheme(theme: presentation) + } + + public func markAsFailed(_ ids:[Int64], animated: Bool) { + self.failedTokens = ids + self.updateFailed(animated) + } + + public func addTooltip(for id: Int64, text: String) { + for token in self.container.subviews { + if let token = token as? TokenView, token.token.uniqueId == id { + tooltip(for: token, text: text) + break + } + } + } + + public func removeAllFailed(animated: Bool) { + self.failedTokens = [] + self.updateFailed(animated) + } + + private func updateFailed(_ animated: Bool) { + + for token in self.container.subviews { + if let token = token as? TokenView { + token.setFailed(failedTokens.contains(token.token.uniqueId), animated: animated) + } + } + + } + + open func didResignResponder() { + state = .None + } + + open func didBecomeResponder() { + state = .Focus + + } + + public var query: String { + return input.string + } + + override public func becomeFirstResponder() -> Bool { + window?.makeFirstResponder(input) + return true + } + + public var responder: NSResponder? { + return input + } + + public func updateLocalizationAndTheme(theme: PresentationTheme) { + background = presentation.colors.background + contentView.background = presentation.colors.background + self.container.backgroundColor = presentation.colors.grayBackground + input.textColor = presentation.colors.text + input.insertionPointColor = presentation.search.textColor + let placeholderLayout = TextViewLayout(.initialize(string: localizedString(placeholderKey), color: presentation.colors.grayText, font: .normal(.title)), maximumNumberOfLines: 1) + placeholderLayout.measure(width: .greatestFiniteMagnitude) + placeholder.update(placeholderLayout) + placeholder.backgroundColor = presentation.colors.grayBackground + placeholder.isSelectable = false + layoutContainer(animated: false) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + +} diff --git a/submodules/TGUIKit/TGUIKit/TooltipController.swift b/submodules/TGUIKit/TGUIKit/TooltipController.swift new file mode 100644 index 0000000000..e6cf0a0649 --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/TooltipController.swift @@ -0,0 +1,300 @@ +// +// TooltipController.swift +// TGUIKit +// +// Created by Mikhail Filimonov on 04/04/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +import Cocoa +import SwiftSignalKit + + +private final class TooltipView: View { + let textView = TextView() + private let textContainer = View() + let cornerView = ImageView() + + var button: TitleButton? + + var didRemoveFromWindow:(()->Void)? + weak var view: NSView? { + didSet { + oldValue?.removeObserver(self, forKeyPath: "window") + if let view = view { + view.addObserver(self, forKeyPath: "window", options: .new, context: nil) + } + } + } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if self.view?.window == nil { + self.didRemoveFromWindow?() + } + } + + var cornerX: CGFloat? = nil { + didSet { + needsLayout = true + } + } + + + func move(corner cornerX: CGFloat, animated: Bool) { + self.cornerX = cornerX + + let point: NSPoint + if frame.width > 44 { + point = NSMakePoint(max(min(cornerX - cornerView.frame.width / 2, frame.width - cornerView.frame.width - .cornerRadius), .cornerRadius), textContainer.frame.maxY) + } else { + let center = self.focus(cornerView.frame.size) + point = NSMakePoint(center.minX, textContainer.frame.maxY) + } + cornerView.change(pos: point, animated: animated) + } + + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + textContainer.backgroundColor = .black + textContainer.addSubview(textView) + addSubview(textContainer) + addSubview(cornerView) + cornerView.animates = false + cornerView.image = generateImage(NSMakeSize(30, 10), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(NSColor.black.cgColor) + context.scaleBy(x: 0.333, y: 0.333) + let _ = try? drawSvgPath(context, path: "M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ") + context.fillPath() + })! + + cornerView.sizeToFit() + textView.disableBackgroundDrawing = true + textView.isSelectable = false + textContainer.layer?.cornerRadius = .cornerRadius + } + + func update(text: NSAttributedString, button: (String, ()->Void)?, maxWidth: CGFloat, interactions: TextViewInteractions, animated: Bool) { + + if let buttonData = button { + let button: TitleButton + if self.button == nil { + button = TitleButton() + self.button = button + textContainer.addSubview(button) + } else { + button = self.button! + } + button.removeAllHandlers() + button.set(text: buttonData.0, for: .Normal) + button.set(font: .medium(.title), for: .Normal) + button.set(color: .accent, for: .Normal) + _ = button.sizeToFit() + button.set(handler: { _ in + buttonData.1() + }, for: .Click) + } else { + self.button?.removeFromSuperview() + self.button = nil + } + + let layout = TextViewLayout(text, alignment: .left, alwaysStaticItems: true) + layout.measure(width: maxWidth - (self.button != nil ? self.button!.frame.width : 0)) + textView.update(layout) + textContainer.change(size: NSMakeSize(max(40, layout.layoutSize.width + 18 + (self.button != nil ? self.button!.frame.width : 0)), max(layout.layoutSize.height + 8, button != nil ? 40 : 0)), animated: animated) + change(size: NSMakeSize(textContainer.frame.width, textContainer.frame.height + 14), animated: animated) + needsLayout = true + + layout.interactions = interactions + + + } + + override func layout() { + super.layout() + textContainer.centerX(y: 0) + if let button = button { + textView.centerY(x: 7) + button.centerY(x: textView.frame.maxX + 5) + } else { + textView.center() + } + if let cornerX = cornerX, frame.width > 44 { + cornerView.setFrameOrigin(max(min(cornerX - cornerView.frame.width / 2, frame.width - cornerView.frame.width - .cornerRadius), .cornerRadius), textContainer.frame.maxY) + } else { + cornerView.centerX(y: textContainer.frame.maxY) + } + } + + deinit { + DispatchQueue.main.async { + removeShownAnimation = false + } + view?.removeObserver(self, forKeyPath: "window") + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class TooltipController: ViewController { + + + +} + +private var removeShownAnimation:Bool = false + + +private let delayDisposable = MetaDisposable() +private var shouldRemoveTooltip: Bool = true +public func tooltip(for view: NSView, text: String, attributedText: NSAttributedString? = nil, interactions: TextViewInteractions = TextViewInteractions(), button: (String, ()->Void)? = nil, maxWidth: CGFloat = 350, autoCorner: Bool = true, offset: NSPoint = .zero, timeout: Double = 3.0, updateText: @escaping(@escaping(String)->Bool)->Void = { _ in }) -> Void { + guard let window = view.window as? Window else { return } + + if view.visibleRect.height != view.frame.height { + return + } + + let tooltip: TooltipView + let isExists: Bool + if let exists = window.contentView?.subviews.first(where: { $0 is TooltipView }) as? TooltipView { + tooltip = exists + isExists = true + shouldRemoveTooltip = false + } else { + tooltip = TooltipView(frame: NSZeroRect) + isExists = false + shouldRemoveTooltip = (NSEvent.pressedMouseButtons & (1 << 0)) == 0 + } + + tooltip.view = view + + + window.contentView?.addSubview(tooltip) + + let location = window.mouseLocationOutsideOfEventStream + + let text = attributedText ?? NSAttributedString.initialize(string: text, color: .white, font: .medium(.text)) + + updateText() { [weak tooltip] text in + tooltip?.update(text: .initialize(string: text, color: .white, font: .medium(.text)), button: button, maxWidth: maxWidth, interactions: interactions, animated: false) + return tooltip != nil + } + + tooltip.update(text: text, button: button, maxWidth: maxWidth, interactions: interactions, animated: isExists) + + + + var removeTooltip:(Bool) -> Void = { _ in + + } + + let updatePosition:(Bool)->Void = { [weak tooltip, weak view] animated in + if let tooltip = tooltip, let view = view { + var point = view.convert(NSZeroPoint, to: nil) + point.y += offset.y + let pos = NSMakePoint(min(max(point.x - (tooltip.frame.width - view.frame.width) / 2, 10), window.frame.width - tooltip.frame.width - 10), point.y) + if view.visibleRect.height != view.frame.height { + removeTooltip(true) + } else { + tooltip.change(pos: pos, animated: isExists || animated) + } + } + + } + + updatePosition(isExists) + + + if autoCorner { + let point = tooltip.convert(NSMakePoint(view.frame.width / 2, 0), from: view) + tooltip.move(corner: point.x, animated: isExists) + } else { + let mousePoint = tooltip.convert(location, from: nil) + tooltip.move(corner: mousePoint.x, animated: isExists) + } + + + let scroll = view.enclosingScrollView as? TableView + + scroll?.addScroll(listener: TableScrollListener.init(dispatchWhenVisibleRangeUpdated: false, { _ in + updatePosition(true) + })) + + if !isExists && !removeShownAnimation { + CATransaction.begin() + tooltip.layer?.animateAlpha(from: 0.3, to: 1, duration: 0.2) + tooltip.layer?.animatePosition(from: NSMakePoint(tooltip.frame.minX, tooltip.frame.minY - 5), to: tooltip.frame.origin) + CATransaction.commit() + } + removeTooltip = { [weak tooltip] animated in + guard let tooltip = tooltip else { return } + if animated { + CATransaction.begin() + tooltip.layer?.animatePosition(from: tooltip.frame.origin, to: NSMakePoint(tooltip.frame.minX, tooltip.frame.minY - 5), removeOnCompletion: false) + //tooltip.change(pos: NSMakePoint(tooltip.frame.minX, tooltip.frame.minY - 5), animated: true) + tooltip.layer?.animateAlpha(from: 1, to: 0, duration: 0.2, removeOnCompletion: false, completion: { [weak tooltip] _ in + if let tooltip = tooltip { + tooltip.removeFromSuperview() + window.removeAllHandlers(for: tooltip) + } + }) +// tooltip.change(opacity: 0, true, removeOnCompletion: false, duration: 0.2, completion: { [weak tooltip] _ in +// if let tooltip = tooltip { +// tooltip.removeFromSuperview() +// window.removeAllHandlers(for: tooltip) +// } +// }) + CATransaction.commit() + } else { + removeShownAnimation = true + tooltip.removeFromSuperview() + window.removeAllHandlers(for: tooltip) + } + } + + + + tooltip.didRemoveFromWindow = { + removeTooltip(true) + } + + delayDisposable.set((Signal.complete() |> delay(timeout, queue: .mainQueue())).start(completed: { + removeTooltip(true) + })) + + + window.set(mouseHandler: { _ -> KeyHandlerResult in + DispatchQueue.main.async { + if shouldRemoveTooltip { + removeTooltip(true) + } + shouldRemoveTooltip = !isExists + } + return .rejected + }, with: tooltip, for: .leftMouseUp, priority: .supreme) + + + + window.set(mouseHandler: { _ -> KeyHandlerResult in + removeTooltip(false) + return .rejected + }, with: tooltip, for: .scrollWheel, priority: .supreme) + + + window.set(handler: { () -> KeyHandlerResult in + removeTooltip(false) + return .rejected + }, with: tooltip, for: .All, priority: .supreme) + + + +} + +public func removeAllTooltips(_ window: Window) { + for subview in window.contentView!.subviews.reversed() { + if subview is TooltipView { + subview.removeFromSuperview() + } + } +} diff --git a/TGUIKit/TGUIKit/TransactionHandler.swift b/submodules/TGUIKit/TGUIKit/TransactionHandler.swift similarity index 100% rename from TGUIKit/TGUIKit/TransactionHandler.swift rename to submodules/TGUIKit/TGUIKit/TransactionHandler.swift diff --git a/TGUIKit/TGUIKit/TransformImageArguments.swift b/submodules/TGUIKit/TGUIKit/TransformImageArguments.swift similarity index 76% rename from TGUIKit/TGUIKit/TransformImageArguments.swift rename to submodules/TGUIKit/TGUIKit/TransformImageArguments.swift index 3b15bad257..58897e0ae5 100644 --- a/TGUIKit/TGUIKit/TransformImageArguments.swift +++ b/submodules/TGUIKit/TGUIKit/TransformImageArguments.swift @@ -8,6 +8,9 @@ import Cocoa + + + public enum ImageCorner: Equatable { case Corner(CGFloat) case Tail(CGFloat) @@ -21,7 +24,7 @@ public enum ImageCorner: Equatable { } } - var corner:CGFloat { + public var corner:CGFloat { switch self { case let .Corner(corner): return corner @@ -86,16 +89,31 @@ public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool { return lhs.topLeft == rhs.topLeft && lhs.topRight == rhs.topRight && lhs.bottomLeft == rhs.bottomLeft && lhs.bottomRight == rhs.bottomRight } +public enum TransformImageResizeMode { + case fill(NSColor) + case blurBackground + case none + case fillTransparent + case imageColor(NSColor) +} + +public enum TransformImageEmptyColor : Equatable { + case color(NSColor) + case gradient(top: NSColor, bottom: NSColor, rotation: Int32?) +} + public struct TransformImageArguments: Equatable { public let corners: ImageCorners public let imageSize: NSSize public let boundingSize: NSSize public let intrinsicInsets: NSEdgeInsets - + public let resizeMode: TransformImageResizeMode + public let emptyColor: TransformImageEmptyColor? + public let scale: CGFloat public var drawingSize: CGSize { let cornersExtendedEdges = self.corners.extendedEdges - return CGSize(width: self.boundingSize.width + cornersExtendedEdges.left + cornersExtendedEdges.right + self.intrinsicInsets.left + self.intrinsicInsets.right, height: self.boundingSize.height + cornersExtendedEdges.top + cornersExtendedEdges.bottom + self.intrinsicInsets.top + self.intrinsicInsets.bottom) + return CGSize(width: max(self.boundingSize.width + cornersExtendedEdges.left + cornersExtendedEdges.right + self.intrinsicInsets.left + self.intrinsicInsets.right, 1), height: max(self.boundingSize.height + cornersExtendedEdges.top + cornersExtendedEdges.bottom + self.intrinsicInsets.top + self.intrinsicInsets.bottom, 1)) } public var drawingRect: CGRect { @@ -108,17 +126,20 @@ public struct TransformImageArguments: Equatable { return NSEdgeInsets(top: cornersExtendedEdges.top + self.intrinsicInsets.top, left: cornersExtendedEdges.left + self.intrinsicInsets.left, bottom: cornersExtendedEdges.bottom + self.intrinsicInsets.bottom, right: cornersExtendedEdges.right + self.intrinsicInsets.right) } - public init(corners:ImageCorners, imageSize:NSSize, boundingSize:NSSize, intrinsicInsets:NSEdgeInsets) { + public init(corners:ImageCorners, imageSize:NSSize, boundingSize:NSSize, intrinsicInsets:NSEdgeInsets, resizeMode: TransformImageResizeMode = .none, emptyColor: TransformImageEmptyColor? = nil, scale: CGFloat = System.backingScale) { self.corners = corners let min = corners.topLeft.corner + corners.topRight.corner self.imageSize = NSMakeSize(max(imageSize.width, min), max(imageSize.height, min)) self.boundingSize = NSMakeSize(max(boundingSize.width, min), max(boundingSize.height, min)) self.intrinsicInsets = intrinsicInsets + self.resizeMode = resizeMode + self.emptyColor = emptyColor + self.scale = scale } } public func ==(lhs: TransformImageArguments, rhs: TransformImageArguments) -> Bool { - return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners + return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners && lhs.emptyColor == rhs.emptyColor && lhs.scale == rhs.scale } diff --git a/TGUIKit/TGUIKit/UIUtils.swift b/submodules/TGUIKit/TGUIKit/UIUtils.swift similarity index 76% rename from TGUIKit/TGUIKit/UIUtils.swift rename to submodules/TGUIKit/TGUIKit/UIUtils.swift index 781da8854e..991863b57a 100644 --- a/TGUIKit/TGUIKit/UIUtils.swift +++ b/submodules/TGUIKit/TGUIKit/UIUtils.swift @@ -9,8 +9,8 @@ import Cocoa -public func floorToScreenPixels(_ value: CGFloat) -> CGFloat { - let scale = NSScreen.main?.backingScaleFactor ?? 1.0 +public func floorToScreenPixels(_ scaleFactor: CGFloat, _ value: CGFloat) -> CGFloat { + let scale = scaleFactor//NSScreen.main?.backingScaleFactor ?? 1.0 return floor(value * scale) / scale } diff --git a/submodules/TGUIKit/TGUIKit/View.swift b/submodules/TGUIKit/TGUIKit/View.swift new file mode 100644 index 0000000000..bcb99a341a --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/View.swift @@ -0,0 +1,436 @@ +// +// View.swift +// TGUIKit +// +// Created by keepcoder on 06/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Foundation +import SwiftSignalKit +public let kUIKitAnimationBackground = "UIKitAnimationBackground" + +public protocol AppearanceViewProtocol { + func updateLocalizationAndTheme(theme: PresentationTheme) +} + +class ViewLayer : CALayer { + override init(layer: Any) { + super.init(layer: layer) + } + + override open class func needsDisplay(forKey:String) -> Bool { + if forKey == kUIKitAnimationBackground { + return true + } + return false + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public struct BorderType: OptionSet { + public var rawValue: UInt32 + + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public init(_ flags: BorderType) { + var rawValue: UInt32 = 0 + + if flags.contains(BorderType.Top) { + rawValue |= BorderType.Top.rawValue + } + + if flags.contains(BorderType.Bottom) { + rawValue |= BorderType.Bottom.rawValue + } + + if flags.contains(BorderType.Left) { + rawValue |= BorderType.Left.rawValue + } + + if flags.contains(BorderType.Right) { + rawValue |= BorderType.Right.rawValue + } + + self.rawValue = rawValue + } + + public static let Top = BorderType(rawValue: 1) + public static let Bottom = BorderType(rawValue: 2) + public static let Left = BorderType(rawValue: 4) + public static let Right = BorderType(rawValue: 8) +} + +public protocol ViewDisplayDelegate : class { + func draw(_ layer: CALayer, in ctx: CGContext); +} + +public class CustomViewHandlers { + public var size:((NSSize) ->Void)? + public var origin:((NSPoint) ->Void)? + public var layout:((NSView) ->Void)? + + deinit { + var bp:Int = 0 + bp += 1 + } +} + +public var viewEnableTouchBar: Bool = true + + + +open class View : NSView, CALayerDelegate, AppearanceViewProtocol { + + + public var noWayToRemoveFromSuperview: Bool = false + + public static let chagedEffectiveAppearance: NSNotification.Name = NSNotification.Name(rawValue: "ViewChagedEffectiveAppearanceNotification") + + public var userInteractionEnabled:Bool = true { + didSet { + if userInteractionEnabled != oldValue { + viewDidUpdatedInteractivity() + } + } + } + var dynamicContentStateForRestore:Bool? = nil + var interactionStateForRestore:Bool? = nil + + public var isDynamicContentLocked:Bool = false { + didSet { + if isDynamicContentLocked != oldValue { + viewDidUpdatedDynamicContent() + } + } + } + + public var borderColor: NSColor? { + didSet { + if oldValue != self.borderColor { + setNeedsDisplay() + } + } + } + + public var animates:Bool = false + + open var isEventLess: Bool = false + + public weak var displayDelegate:ViewDisplayDelegate? + + public var _customHandler:CustomViewHandlers? + + public var customHandler:CustomViewHandlers { + if _customHandler == nil { + _customHandler = CustomViewHandlers() + } + return _customHandler! + } + public var viewDidChangedEffectiveAppearance:(()->Void)? = nil + + + + open override func viewDidChangeEffectiveAppearance() { + self.viewDidChangedEffectiveAppearance?() + } + + open var backgroundColor:NSColor = .clear { + didSet { + if oldValue != self.backgroundColor { + layer?.backgroundColor = self.backgroundColor.cgColor + setNeedsDisplay() + } + } + } +// +// @available(OSX 10.14, *) +// open override func viewDidChangeEffectiveAppearance() { +// super.viewDidChangeEffectiveAppearance() +// NotificationCenter.default.post(name: View.chagedEffectiveAppearance, object: self) +// } + + open func viewDidUpdatedInteractivity() { + + } + open func viewDidUpdatedDynamicContent() { + + } + + @available(OSX 10.12.2, *) + open override func makeTouchBar() -> NSTouchBar? { + return viewEnableTouchBar ? super.makeTouchBar() : nil + } + + public var flip:Bool = true + + open var border:BorderType? + + + open override func layout() { + super.layout() + _customHandler?.layout?(self) + } + + open func draw(_ layer: CALayer, in ctx: CGContext) { + + + if let displayDelegate = displayDelegate { + displayDelegate.draw(layer, in: ctx) + } else { + // layer.backgroundColor = backgroundColor.cgColor + // layer.backgroundColor = self.backgroundColor.cgColor + + // ctx.setShadow(offset: NSMakeSize(5.0, 5.0), blur: 0.0, color: .shadow.cgColor) + + // ctx.setFillColor(self.backgroundColor.cgColor) + // ctx.fill(layer.bounds) + + if let border = border { + if let borderColor = borderColor { + ctx.setFillColor(borderColor.cgColor) + } else { + ctx.setFillColor(presentation.colors.border.cgColor) + } + + if border.contains(.Top) { + ctx.fill(NSMakeRect(0, !self.isFlipped ? NSHeight(self.frame) - .borderSize : 0, NSWidth(self.frame), .borderSize)) + } + if border.contains(.Bottom) { + ctx.fill(NSMakeRect(0, self.isFlipped ? NSHeight(self.frame) - .borderSize : 0, NSWidth(self.frame), .borderSize)) + } + if border.contains(.Left) { + ctx.fill(NSMakeRect(0, 0, .borderSize, NSHeight(self.frame))) + } + if border.contains(.Right) { + ctx.fill(NSMakeRect(NSWidth(self.frame) - .borderSize, 0, .borderSize, NSHeight(self.frame))) + } + + } + } + } + + public func setNeedsDisplay() -> Void { + self.layer?.setNeedsDisplay() + assertOnMainThread() + } + + + + open override var isFlipped: Bool { + return flip + } + + + + public init() { + super.init(frame: NSZeroRect) + assertOnMainThread() + self.autoresizesSubviews = false + self.wantsLayer = true + acceptsTouchEvents = true + self.layerContentsRedrawPolicy = .onSetNeedsDisplay + layer?.disableActions() + layer?.backgroundColor = backgroundColor.cgColor + // self.layer?.delegate = self + // self.layer?.isOpaque = false + // self.autoresizesSubviews = false + // self.layerContentsRedrawPolicy = .onSetNeedsDisplay + // self.layer?.drawsAsynchronously = System.drawAsync + } + + override required public init(frame frameRect: NSRect) { + super.init(frame: frameRect) + assertOnMainThread() + acceptsTouchEvents = true + self.wantsLayer = true + self.autoresizesSubviews = false + layer?.disableActions() + layer?.backgroundColor = backgroundColor.cgColor + // self.layer?.delegate = self + // self.layer?.isOpaque = false + self.layerContentsRedrawPolicy = .onSetNeedsDisplay + // self.layer?.drawsAsynchronously = System.drawAsync + } + +// open override var wantsDefaultClipping: Bool { +// return false +// } + + open override var translatesAutoresizingMaskIntoConstraints: Bool { + get { + return true + } + set { + + } + } + + open func mouseInside() -> Bool { + return super._mouseInside() + } + + open override func hitTest(_ point: NSPoint) -> NSView? { + if isEventLess { + return nil + } else { + return super.hitTest(point) + } + } + + open func change(pos position: NSPoint, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = .easeOut, completion:((Bool)->Void)? = nil) -> Void { + super._change(pos: position, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + + open func change(size: NSSize, animated: Bool, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = .easeOut, completion:((Bool)->Void)? = nil) { + super._change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + } + open func change(opacity to: CGFloat, animated: Bool = true, _ save:Bool = true, removeOnCompletion: Bool = true, duration:Double = 0.2, timingFunction: CAMediaTimingFunctionName = .easeOut, completion:((Bool)->Void)? = nil) { + super._change(opacity: to, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + + } + + open override func swipe(with event: NSEvent) { + super.swipe(with: event) + } + + open override func beginGesture(with event: NSEvent) { + super.beginGesture(with: event) + } + + open func updateLocalizationAndTheme(theme: PresentationTheme) { + for subview in subviews { + if let subview = subview as? AppearanceViewProtocol { + subview.updateLocalizationAndTheme(theme: theme) + } + } + } + + open override func viewDidMoveToSuperview() { + if superview != nil { + guard #available(OSX 10.12, *) else { + // self.needsLayout = true + return + } + } + } + open override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + + open override func setFrameSize(_ newSize: NSSize) { + super.setFrameSize(newSize) + _customHandler?.size?(newSize) + + guard #available(OSX 10.12, *) else { + self.needsLayout = true + return + } + } + + public func notifySubviewsToLayout(_ subview:NSView) -> Void { + for sub in subview.subviews { + sub.needsLayout = true + } + } + + open override var needsLayout: Bool { + set { + super.needsLayout = newValue + if newValue { + + guard #available(OSX 10.12, *) else { + layout() + notifySubviewsToLayout(self) + return + } + + } + } + get { + return super.needsLayout + } + } + + + @objc func layoutInRunLoop() { + layout() + } + + open override func setFrameOrigin(_ newOrigin: NSPoint) { + super.setFrameOrigin(newOrigin) + _customHandler?.origin?(newOrigin) + guard #available(OSX 10.12, *) else { + self.needsLayout = true + return + } + } + + deinit { + assertOnMainThread() + } + + + + open var responder:NSResponder? { + return self + } + + open func setNeedsDisplayLayer() -> Void { + self.layer?.setNeedsDisplay() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func mouseDown(with event: NSEvent) { + // self.window?.makeFirstResponder(nil) + super.mouseDown(with: event) + } + + + open override func draw(_ dirtyRect: NSRect) { + + } + + public var hasVisibleModal:Bool { + if let contentView = self.window?.contentView { + for subview in contentView.subviews { + if subview is PopoverBackground { + return true + } + } + } + + + return false + } + + open override func copy() -> Any { + let copy:View = View(frame:bounds) + copy.layer?.contents = self.layer?.contents + return copy + } + + + open override func removeFromSuperview() { + super.removeFromSuperview() + } + + + open var kitWindow: Window? { + return super.window as? Window + } + + +} diff --git a/submodules/TGUIKit/TGUIKit/ViewController.swift b/submodules/TGUIKit/TGUIKit/ViewController.swift new file mode 100644 index 0000000000..2ec6f9a5df --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/ViewController.swift @@ -0,0 +1,1058 @@ +// +// ViewController.swift +// TGUIKit +// +// Created by keepcoder on 06/09/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Foundation +import SwiftSignalKit + +public final class BackgroundGradientView : View { + public var values:(top: NSColor, bottom: NSColor, rotation: Int32?)? { + didSet { + needsDisplay = true + } + } + + public required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + noWayToRemoveFromSuperview = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override public var isFlipped: Bool { + return false + } + + override public func layout() { + super.layout() + let values = self.values + self.values = values + } + + override public func draw(_ layer: CALayer, in ctx: CGContext) { + super.draw(layer, in: ctx) + if let values = self.values { + + let colors = [values.top, values.bottom].reversed() + + let gradientColors = colors.map { $0.cgColor } as CFArray + let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0) + + var locations: [CGFloat] = [] + for i in 0 ..< colors.count { + locations.append(delta * CGFloat(i)) + } + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + ctx.saveGState() + ctx.translateBy(x: frame.width / 2.0, y: frame.height / 2.0) + ctx.rotate(by: CGFloat(values.rotation ?? 0) * CGFloat.pi / -180.0) + ctx.translateBy(x: -frame.width / 2.0, y: -frame.height / 2.0) + ctx.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: frame.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]) + ctx.restoreGState() + } + + } +} + + +open class BackgroundView: ImageView { + + public var _customHandler:CustomViewHandlers? + + public var customHandler:CustomViewHandlers { + if _customHandler == nil { + _customHandler = CustomViewHandlers() + } + return _customHandler! + } + + private let gradient: BackgroundGradientView + + public override init(frame frameRect: NSRect) { + gradient = BackgroundGradientView(frame: NSMakeRect(0, 0, frameRect.width, frameRect.height)) + super.init(frame: frameRect) + addSubview(gradient) + autoresizesSubviews = false +// gradient.actions = [:] +// +// gradient.bounds = NSMakeRect(0, 0, max(bounds.width, bounds.height), max(bounds.width, bounds.height)) +// gradient.anchorPoint = NSMakePoint(0.5, 0.5) +// gradient.position = NSMakePoint(bounds.width / 2, bounds.height / 2) +// layer?.addSublayer(gradient) + // self.layer?.disableActions() + self.layer?.contentsGravity = .resizeAspectFill + } + + open override func change(size: NSSize, animated: Bool, _ save: Bool = true, removeOnCompletion: Bool = true, duration: Double = 0.2, timingFunction: CAMediaTimingFunctionName = CAMediaTimingFunctionName.easeOut, completion: ((Bool) -> Void)? = nil) { + super.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction, completion: completion) + gradient.change(size: size, animated: animated, save, removeOnCompletion: removeOnCompletion, duration: duration, timingFunction: timingFunction) + } + + override init() { + fatalError("not supported") + } + + open override func layout() { + super.layout() + gradient.frame = bounds + _customHandler?.layout?(self) +// gradient.bounds = NSMakeRect(0, 0, max(frame.width, frame.height) * 2, max(frame.width, frame.height) * 2) +// gradient.position = NSMakePoint(frame.width / 2, frame.height / 2) + } + + + open override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + } + + open override var isFlipped: Bool { + return true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open var backgroundMode:TableBackgroundMode = .plain { + didSet { + switch backgroundMode { + case let .background(image): + layer?.backgroundColor = .clear + layer?.contents = image + gradient.isHidden = true + case let .color(color): + layer?.backgroundColor = color.withAlphaComponent(1.0).cgColor + layer?.contents = nil + gradient.values = nil + case let .gradient(top, bottom, rotation): + gradient.values = (top: top.withAlphaComponent(1.0), bottom: bottom.withAlphaComponent(1.0), rotation: rotation) + layer?.contents = nil + gradient.isHidden = false + default: + gradient.isHidden = true + gradient.values = nil + layer?.backgroundColor = presentation.colors.background.cgColor + layer?.contents = nil + } + } + } + + override open func copy() -> Any { + let view = BackgroundView(frame: self.bounds) + view.backgroundMode = self.backgroundMode + return view + } +} + + + +class ControllerToasterView : Control { + + private weak var toaster:ControllerToaster? + private let textView:TextView = TextView() + required init(frame frameRect: NSRect) { + super.init(frame: frameRect) + addSubview(textView) + textView.isSelectable = false + textView.userInteractionEnabled = false + self.autoresizingMask = [.width] + self.border = [.Bottom] + updateLocalizationAndTheme(theme: presentation) + } + override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + self.backgroundColor = theme.colors.background + self.textView.backgroundColor = theme.colors.background + } + + + func update(with toaster:ControllerToaster) { + self.toaster = toaster + } + + override func layout() { + super.layout() + if let toaster = toaster { + toaster.text.measure(width: frame.width - 40) + textView.update(toaster.text) + textView.center() + } + self.setNeedsDisplayLayer() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public class ControllerToaster { + let text:TextViewLayout + var view:ControllerToasterView? + let disposable:MetaDisposable = MetaDisposable() + private let action:(()->Void)? + private var height:CGFloat { + return max(30, self.text.layoutSize.height + 10) + } + public init(text:NSAttributedString, action:(()->Void)? = nil) { + self.action = action + self.text = TextViewLayout(text, maximumNumberOfLines: 3, truncationType: .middle, alignment: .center) + } + + public init(text:String, action:(()->Void)? = nil) { + self.action = action + self.text = TextViewLayout(NSAttributedString.initialize(string: text, color: presentation.colors.text, font: .medium(.text)), maximumNumberOfLines: 3, truncationType: .middle, alignment: .center) + } + + func show(for controller:ViewController, timeout:Double, animated:Bool) { + assert(view == nil) + text.measure(width: controller.frame.width - 40) + view = ControllerToasterView(frame: NSMakeRect(0, 0, controller.frame.width, height)) + view?.update(with: self) + + if let action = self.action { + view?.set(handler: { [weak self] _ in + action() + self?.hide(true) + }, for: .Click) + } + + controller.addSubview(view!) + + if animated { + view?.layer?.animatePosition(from: NSMakePoint(0, -height - controller.bar.height), to: NSZeroPoint, duration: 0.2) + } + + let signal:Signal = .single(Void()) |> delay(timeout, queue: Queue.mainQueue()) + disposable.set(signal.start(next:{ [weak self] in + self?.hide(true) + })) + } + + func hide(_ animated:Bool) { + if animated { + view?.layer?.animatePosition(from: NSZeroPoint, to: NSMakePoint(0, -height), duration: 0.2, removeOnCompletion:false, completion:{ [weak self] (completed) in + self?.view?.removeFromSuperview() + self?.view = nil + }) + } else { + view?.removeFromSuperview() + view = nil + disposable.dispose() + } + } + + deinit { + let view = self.view + view?.layer?.animatePosition(from: NSZeroPoint, to: NSMakePoint(0, -height), duration: 0.2, removeOnCompletion:false, completion:{ (completed) in + view?.removeFromSuperview() + }) + disposable.dispose() + } + +} + +open class ViewController : NSObject { + fileprivate var _view:NSView? + public var _frameRect:NSRect + + private var toaster:ControllerToaster? + + public var atomicSize:Atomic = Atomic(value:NSZeroSize) + + weak open var navigationController:NavigationViewController? { + didSet { + if navigationController != oldValue { + updateNavigation(navigationController) + } + } + } + + public var noticeResizeWhenLoaded: Bool = true + + public var animationStyle:AnimationStyle = AnimationStyle(duration:0.4, function:CAMediaTimingFunctionName.spring) + public var bar:NavigationBarStyle = NavigationBarStyle(height:50) + + public var leftBarView:BarView! + public var centerBarView:TitledBarView! + public var rightBarView:BarView! + + public var popover:Popover? + open var modal:Modal? + + private var widthOnDisappear: CGFloat? = nil + + public var ableToNextController:(ViewController, @escaping(ViewController, Bool)->Void)->Void = { controller, f in + f(controller, true) + } + + private let _ready = Promise() + open var ready: Promise { + return self._ready + } + public var didSetReady:Bool = false + + public let isKeyWindow:Promise = Promise(false) + + open var view:NSView { + get { + if(_view == nil) { + loadView(); + } + + return _view!; + } + + } + + open var redirectUserInterfaceCalls: Bool { + return false + } + + public var backgroundColor: NSColor { + set { + self.view.background = newValue + } + get { + return self.view.background + } + } + + open var enableBack:Bool { + return false + } + + open var isAutoclosePopover: Bool { + return true + } + + open func executeReturn() -> Void { + self.navigationController?.back() + } + + open func updateNavigation(_ navigation:NavigationViewController?) { + + } + + open var rightSwipeController: ViewController? { + return nil + } + + open func navigationWillChangeController() { + + } + + open var sidebar:ViewController? { + return nil + } + + open var sidebarWidth:CGFloat { + return 350 + } + + open var supportSwipes: Bool { + return true + } + + public private(set) var internalId:Int = 0; + + public override init() { + _frameRect = NSZeroRect + self.internalId = Int(arc4random()); + super.init() + } + + public init(frame frameRect:NSRect) { + _frameRect = frameRect; + self.internalId = Int(arc4random()); + } + + open func readyOnce() -> Void { + if !didSetReady { + didSetReady = true + ready.set(.single(true)) + } + } + + open func updateLocalizationAndTheme(theme: PresentationTheme) { + (view as? AppearanceViewProtocol)?.updateLocalizationAndTheme(theme: theme) + self.navigationController?.updateLocalizationAndTheme(theme: theme) + } + + open func loadView() -> Void { + if(_view == nil) { + + leftBarView = getLeftBarViewOnce() + centerBarView = getCenterBarViewOnce() + rightBarView = getRightBarViewOnce() + + let vz = viewClass() as! NSView.Type + _view = vz.init(frame: _frameRect); + _view?.autoresizingMask = [.width,.height] + + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) + + _ = atomicSize.swap(_view!.frame.size) + } + } + + open func navigationHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + return {} + } + + open func navigationUndoHeaderDidNoticeAnimation(_ current: CGFloat, _ previous: CGFloat, _ animated: Bool) -> ()->Void { + return {} + } + + @available(OSX 10.12.2, *) + open func makeTouchBar() -> NSTouchBar? { + return nil//window?.firstResponder?.makeTouchBar() + } + + @available(OSX 10.12.2, *) + @objc public var touchBar: NSTouchBar? { + return window?.touchBar + } + @available(OSX 10.12.2, *) + open func layoutTouchBar() { + + } + + + open func requestUpdateBackBar() { + if isLoaded(), let leftBarView = leftBarView as? BackNavigationBar { + leftBarView.requestUpdate() + } + self.leftBarView.style = navigationButtonStyle + } + + open func requestUpdateCenterBar() { + setCenterTitle(defaultBarTitle) + } + + open func dismiss() { + if navigationController?.controller == self { + navigationController?.back() + } + } + + open func requestUpdateRightBar() { + (self.rightBarView as? TextButtonBarView)?.style = navigationButtonStyle + self.rightBarView.style = navigationButtonStyle + } + + + @objc func viewFrameChanged(_ notification:Notification) { + if atomicSize.with({ $0 != frame.size}) { + viewDidResized(frame.size) + } + } + + public func updateBackgroundColor(_ backgroundMode: TableBackgroundMode) { + switch backgroundMode { + case .background, .gradient: + backgroundColor = .clear + case let .color(color): + backgroundColor = color + default: + backgroundColor = presentation.colors.background + } + } + + open func updateController() { + + } + + open func viewDidResized(_ size:NSSize) { + _ = atomicSize.swap(size) + } + + open func draggingExited() { + + } + open func draggingEntered() { + + } + + open func focusSearch(animated: Bool) { + + } + + open func invokeNavigationBack() -> Bool { + return true + } + + open func updateFrame(_ frame: NSRect, animated: Bool) { + if isLoaded() { + (animated ? self.view.animator() : self.view).frame = frame + } + } + + open func getLeftBarViewOnce() -> BarView { + return enableBack ? BackNavigationBar(self) : BarView(controller: self) + } + + open var defaultBarTitle:String { + return localizedString(self.className) + } + + open func getCenterBarViewOnce() -> TitledBarView { + return TitledBarView(controller: self, .initialize(string: defaultBarTitle, color: presentation.colors.text, font: .medium(.title))) + } + + public func setCenterTitle(_ text:String) { + self.centerBarView.text = .initialize(string: text, color: presentation.colors.text, font: .medium(.title)) + } + + open func getRightBarViewOnce() -> BarView { + return BarView(controller: self) + } + + open var abolishWhenNavigationSame: Bool { + return false + } + + open func viewClass() ->AnyClass { + return View.self + } + + open func draggingItems(for pasteboard:NSPasteboard) -> [DragItem] { + return [] + } + + public func loadViewIfNeeded(_ frame:NSRect = NSZeroRect) -> Void { + + guard _view != nil else { + if !NSIsEmptyRect(frame) { + _frameRect = frame + } + self.loadView() + + return + } + } + + open func viewDidLoad() -> Void { + if noticeResizeWhenLoaded { + viewDidResized(view.frame.size) + } + } + + + + open func viewWillAppear(_ animated:Bool) -> Void { + + } + + open func viewDidChangedNavigationLayout(_ state: SplitViewState) -> Void { + + } + + deinit { + self.window?.removeObserver(for: self) + window?.removeAllHandlers(for: self) + NotificationCenter.default.removeObserver(self) + assertOnMainThread() + } + + + open func viewWillDisappear(_ animated:Bool) -> Void { + if #available(OSX 10.12.2, *) { + window?.touchBar = nil + } + widthOnDisappear = frame.width + //assert(self.window != nil) + if canBecomeResponder { + self.window?.removeObserver(for: self) + } + if haveNextResponder { + self.window?.remove(object: self, for: .Tab) + } + NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.removeObserver(self, name: NSWindow.didResignKeyNotification, object: window) + isKeyWindow.set(.single(false)) + } + + public func isLoaded() -> Bool { + return _view != nil + } + + open func viewDidAppear(_ animated:Bool) -> Void { + //assert(self.window != nil) + if #available(OSX 10.12.2, *) { + // DispatchQueue.main.async { [weak self] in + self.window?.touchBar = self.window?.makeTouchBar() + // } + } + if haveNextResponder { + self.window?.set(handler: { [weak self] () -> KeyHandlerResult in + guard let `self` = self else {return .rejected} + + _ = self.window?.makeFirstResponder(self.nextResponder()) + + return .invoked + }, with: self, for: .Tab, priority: responderPriority) + } + + if canBecomeResponder { + self.window?.set(responder: {[weak self] () -> NSResponder? in + return self?.firstResponder() + }, with: self, priority: responderPriority) + + if let become = becomeFirstResponder(), become == true { + self.window?.applyResponderIfNeeded() + } else { + _ = self.window?.makeFirstResponder(self.window?.firstResponder) + } + } + + NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeKey), name: NSWindow.didBecomeKeyNotification, object: window) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidResignKey), name: NSWindow.didResignKeyNotification, object: window) + if let window = window { + isKeyWindow.set(.single(window.isKeyWindow)) + } + + func findTableView(in view: NSView) -> Void { + for subview in view.subviews { + if subview is NSTableView { + if !subview.inLiveResize { + subview.viewDidEndLiveResize() + } + } else if !subview.subviews.isEmpty { + findTableView(in: subview) + } + } + } + if let widthOnDisappear = widthOnDisappear, frame.width != widthOnDisappear { + findTableView(in: view) + } + } + + + + @objc open func windowDidBecomeKey() { + isKeyWindow.set(.single(true)) + if #available(OSX 10.12.2, *) { + window?.touchBar = window?.makeTouchBar() + } + } + + @objc open func windowDidResignKey() { + isKeyWindow.set(.single(false)) + if #available(OSX 10.12.2, *) { + window?.touchBar = nil + } + } + + open var canBecomeResponder: Bool { + return true + } + + open var removeAfterDisapper:Bool { + return false + } + + open func escapeKeyAction() -> KeyHandlerResult { + return .rejected + } + + open func backKeyAction() -> KeyHandlerResult { + if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift), let textView = window?.firstResponder as? TextView, let layout = textView.layout, layout.selectedRange.range.max != 0 { + _ = layout.selectPrevChar() + textView.needsDisplay = true + return .invoked + } + return .rejected + } + + open func nextKeyAction() -> KeyHandlerResult { + if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift), let textView = window?.firstResponder as? TextView, let layout = textView.layout, layout.selectedRange.range.max != 0 { + _ = layout.selectNextChar() + textView.needsDisplay = true + return .invoked + } + return .invokeNext + } + + open func returnKeyAction() -> KeyHandlerResult { + return .rejected + } + + open func didRemovedFromStack() -> Void { + + } + + open func viewDidDisappear(_ animated:Bool) -> Void { + + } + + open func scrollup(force: Bool = false) { + + } + + open func becomeFirstResponder() -> Bool? { + + return false + } + + open var window:Window? { + return _view?.window as? Window + } + + open func firstResponder() -> NSResponder? { + return nil + } + + open func nextResponder() -> NSResponder? { + return nil + } + + open var haveNextResponder: Bool { + return false + } + + open var responderPriority:HandlerPriority { + return .low + } + + + + public var frame:NSRect { + get { + return isLoaded() ? self.view.frame : _frameRect + } + set { + self.view.frame = newValue + } + } + public var bounds:NSRect { + return isLoaded() ? self.view.bounds : NSMakeRect(0, 0, _frameRect.width, _frameRect.height - bar.height) + } + + open var isOpaque: Bool { + return true + } + + func removeBackgroundCap() { + for subview in view.subviews.reversed() { + if subview is BackgroundView { + subview.removeFromSuperview() + } + } + } + + public func addSubview(_ subview:NSView) -> Void { + self.view.addSubview(subview) + } + + public func removeFromSuperview() ->Void { + if isLoaded() { + self.view.removeFromSuperview() + } + } + + + open func backSettings() -> (String,CGImage?) { + return (localizedString("Navigation.back"),#imageLiteral(resourceName: "Icon_NavigationBack").precomposed(presentation.colors.accentIcon)) + } + + open var popoverClass:AnyClass { + return Popover.self + } + + open func show(for control:Control, edge:NSRectEdge? = nil, inset:NSPoint = NSZeroPoint, static: Bool = false) -> Void { + if popover == nil { + self.popover = (self.popoverClass as! Popover.Type).init(controller: self, static: `static`) + } + + if let popover = popover { + popover.show(for: control, edge: edge, inset: inset) + } + } + + open func closePopover() -> Void { + self.popover?.hide() + } + + open func invokeNavigation(action:NavigationModalAction) { + _ = (self.ready.get() |> take(1) |> deliverOnMainQueue).start(next: { (ready) in + action.close() + }) + } + + public func removeToaster() { + if let toaster = self.toaster { + toaster.hide(true) + } + } + + public func show(toaster:ControllerToaster, for delay:Double = 3.0, animated:Bool = true) { + assert(isLoaded()) + if let toaster = self.toaster { + toaster.hide(true) + } + + self.toaster = toaster + toaster.show(for: self, timeout: delay, animated: animated) + + } +} + + +open class GenericViewController : ViewController where T:NSView { + public var genericView:T { + return super.view as! T + } + + open override func updateLocalizationAndTheme(theme: PresentationTheme) { + super.updateLocalizationAndTheme(theme: theme) + genericView.background = presentation.colors.background + } + + override open func loadView() -> Void { + if(_view == nil) { + + leftBarView = getLeftBarViewOnce() + centerBarView = getCenterBarViewOnce() + rightBarView = getRightBarViewOnce() + + _view = initializer() + _view?.wantsLayer = true + _view?.autoresizingMask = [.width,.height] + + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) + + _ = atomicSize.swap(_view!.frame.size) + } + viewDidLoad() + } + + + + open func initializer() -> T { + let vz = T.self as NSView.Type + //controller.bar.height + return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height)) as! T + } + +} + +public struct ModalHeaderData { + public let title: String? + public let subtitle: String? + public let image: CGImage? + public let handler: (()-> Void)? + public init(title: String? = nil, subtitle: String? = nil, image: CGImage? = nil, handler: (()->Void)? = nil) { + self.title = title + self.image = image + self.subtitle = subtitle + self.handler = handler + } +} + +open class ModalViewController : ViewController { + + open var closable:Bool { + return true + } + + // use this only for modal progress. This is made specially for nsvisualeffect support. + open var contentBelowBackground: Bool { + return false + } + + open var shouldCloseAllTheSameModals: Bool { + return true + } + + private var temporaryTouchBar: Any? + + @available(OSX 10.12.2, *) + open override func makeTouchBar() -> NSTouchBar? { + guard let modal = modal, let interactions = modal.interactions else { + if temporaryTouchBar == nil { + temporaryTouchBar = NSTouchBar() + } + return temporaryTouchBar as? NSTouchBar + } + if temporaryTouchBar == nil { + temporaryTouchBar = ModalTouchBar(interactions, modal: modal) + } + return temporaryTouchBar as? NSTouchBar + } + + open var hasOwnTouchbar: Bool { + return true + } + + open var background:NSColor { + return NSColor(0x000000, 0.6) + } + + open func didResizeView(_ size: NSSize, animated: Bool) -> Void { + + } + + open var isVisualEffectBackground: Bool { + return false + } + + open var isFullScreen:Bool { + return false + } + + open var redirectMouseAfterClosing: Bool { + return false + } + + open var containerBackground: NSColor { + return presentation.colors.background + } + open var headerBackground: NSColor { + return presentation.colors.background + } + + open var dynamicSize:Bool { + return false + } + + open override func becomeFirstResponder() -> Bool? { + return true + } + + open func measure(size:NSSize) { + + } + + open var modalInteractions:ModalInteractions? { + return nil + } + open var modalHeader: (left:ModalHeaderData?, center: ModalHeaderData?, right: ModalHeaderData?)? { + return nil + } + + open override var responderPriority: HandlerPriority { + return .modal + } + + open override func firstResponder() -> NSResponder? { + return self.view + } + + open func close(animationType: ModalAnimationCloseBehaviour = .common) { + modal?.close(animationType: animationType) + } + + open var handleEvents:Bool { + return true + } + + open var handleAllEvents: Bool { + return true + } + + override open func loadView() -> Void { + if(_view == nil) { + + _view = initializer() + _view?.autoresizingMask = [.width,.height] + + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) + + _ = atomicSize.swap(_view!.frame.size) + } + viewDidLoad() + } + + open func initializer() -> NSView { + let vz = viewClass() as! NSView.Type + return vz.init(frame: NSMakeRect(_frameRect.minX, _frameRect.minY, _frameRect.width, _frameRect.height - bar.height)); + } + +} + +open class ModalController : ModalViewController { + private let controller: NavigationViewController + public init(_ controller: NavigationViewController) { + self.controller = controller + super.init(frame: controller._frameRect) + } + + open override var handleEvents: Bool { + return true + } + + + open override func firstResponder() -> NSResponder? { + return controller.controller.firstResponder() + } + + open override func returnKeyAction() -> KeyHandlerResult { + return controller.controller.returnKeyAction() + } + + open override func escapeKeyAction() -> KeyHandlerResult { + return controller.controller.escapeKeyAction() + } + + open override var haveNextResponder: Bool { + return true + } + + open override func nextResponder() -> NSResponder? { + return controller.controller.nextResponder() + } + + open override func viewDidLoad() { + super.viewDidLoad() + ready.set(controller.controller.ready.get()) + } + + open override func becomeFirstResponder() -> Bool? { + return nil + } + + + open override func loadView() { + self._view = controller.view + NotificationCenter.default.addObserver(self, selector: #selector(viewFrameChanged(_:)), name: NSView.frameDidChangeNotification, object: _view!) + + _ = atomicSize.swap(_view!.frame.size) + viewDidLoad() + } +} + +open class TableModalViewController : ModalViewController { + override open var dynamicSize: Bool { + return true + } + + override open func measure(size: NSSize) { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(size.height - 120, genericView.listHeight)), animated: false) + } + + public func updateSize(_ animated: Bool) { + if let contentSize = self.modal?.window.contentView?.frame.size { + self.modal?.resize(with:NSMakeSize(genericView.frame.width, min(contentSize.height - 120, genericView.listHeight)), animated: animated) + } + } + + override open func viewClass() -> AnyClass { + return TableView.self + } + + public var genericView:TableView { + return self.view as! TableView + } + + + +} + + diff --git a/TGUIKit/TGUIKit/ViewUtils.swift b/submodules/TGUIKit/TGUIKit/ViewUtils.swift similarity index 100% rename from TGUIKit/TGUIKit/ViewUtils.swift rename to submodules/TGUIKit/TGUIKit/ViewUtils.swift diff --git a/TGUIKit/TGUIKit/WeakReference.swift b/submodules/TGUIKit/TGUIKit/WeakReference.swift similarity index 100% rename from TGUIKit/TGUIKit/WeakReference.swift rename to submodules/TGUIKit/TGUIKit/WeakReference.swift diff --git a/submodules/TGUIKit/TGUIKit/Window.swift b/submodules/TGUIKit/TGUIKit/Window.swift new file mode 100644 index 0000000000..932545f0ad --- /dev/null +++ b/submodules/TGUIKit/TGUIKit/Window.swift @@ -0,0 +1,870 @@ +// +// Window.swift +// TGUIKit +// +// Created by keepcoder on 16/10/2016. +// Copyright © 2016 Telegram. All rights reserved. +// + +import Cocoa + +public class ObervableView: NSView { + private var listeners:[WeakReference] = [] + + func add(listener: NSObject) { + listeners.append(WeakReference(value: listener)) + } + + func remove(listener: NSObject) { + let index = listeners.firstIndex(where: { (weakValue) -> Bool in + return listener == weakValue.value + }) + if let index = index { + listeners.remove(at: index) + } + } + override public func didAddSubview(_ subview: NSView) { + super.didAddSubview(subview) + for listener in listeners { + if let listener = listener.value as? ObservableViewDelegate { + listener.observableView(self, didAddSubview: subview) + } + } + } + + override public func willRemoveSubview(_ subview: NSView) { + super.willRemoveSubview(subview) + for listener in listeners { + if let listener = listener.value as? ObservableViewDelegate { + listener.observableview(self, willRemoveSubview: subview) + } + } + } + +} + +protocol ObservableViewDelegate : class { + func observableView(_ view: NSView, didAddSubview: NSView) + func observableview(_ view: NSView, willRemoveSubview: NSView) +} + +public enum HandlerPriority: Int, Comparable { + case low = 0 + case medium = 1 + case high = 2 + case modal = 3 + case supreme = 4 +} + +public func <(lhs: HandlerPriority, rhs: HandlerPriority) -> Bool { + return lhs.rawValue < rhs.rawValue +} + +class KeyHandler : Comparable { + let handler:()->KeyHandlerResult + let object:WeakReference + let priority:HandlerPriority + let modifierFlags:NSEvent.ModifierFlags? + let date: TimeInterval + init(_ handler:@escaping()->KeyHandlerResult, _ object:NSObject?, _ priority:HandlerPriority, _ flags:NSEvent.ModifierFlags?) { + self.handler = handler + self.object = WeakReference(value: object) + self.priority = priority + self.modifierFlags = flags + self.date = Date().timeIntervalSince1970 + } +} +func ==(lhs: KeyHandler, rhs: KeyHandler) -> Bool { + return lhs.priority == rhs.priority +} +func <(lhs: KeyHandler, rhs: KeyHandler) -> Bool { + if lhs.priority == rhs.priority { + return lhs.date < rhs.date + } + return lhs.priority < rhs.priority +} + +public enum SwipeType{ + case left,right,none +} + +public enum SwipeState { + case start(controller: ViewController) + case swiping(delta: CGFloat, controller: ViewController) + case success(delta: CGFloat, controller: ViewController) + case failed(delta: CGFloat, controller: ViewController) + + public var delta: CGFloat { + switch self { + case let .swiping(delta, _), let .success(delta, _), let .failed(delta, _): + return delta + default: + return 0 + } + } + public func withAlwaysSuccess() -> SwipeState { + switch self { + case let .failed(delta, controller): + return .success(delta: delta, controller: controller) + default: + return self + } + } + public func withAlwaysFailed() -> SwipeState { + switch self { + case let .success(delta, controller): + return .failed(delta: delta, controller: controller) + default: + return self + } + } + var controller: ViewController { + switch self { + case let .start(controller): + return controller + case let .success(_, controller), let .failed(_, controller), let .swiping(_, controller): + return controller + } + } +} + +public enum SwipeDirection { + case left(SwipeState) + case right(SwipeState) + case none + + func withAdditionalDelta(_ deltaX: CGFloat, _ force: Bool = false) -> SwipeDirection { + switch self { + case let .left(state): + switch state { + case let .swiping(delta, controller): + return .left(.swiping(delta: force ? deltaX : delta + deltaX, controller: controller)) + case let .start(controller): + return .left(.swiping(delta: 0, controller: controller)) + default: + return self + } + case let .right(state): + switch state { + case let .swiping(delta, controller): + return .right(.swiping(delta: force ? deltaX : delta - deltaX, controller: controller)) + case let .start(controller): + return .right(.swiping(delta: 0, controller: controller)) + default: + return self + } + default: + return self + } + } + + public var delta: CGFloat { + switch self { + case let .left(state), let .right(state): + return state.delta + case .none: + return 0 + } + } + + func withUpdatedSuccessOrFail(_ width: CGFloat) -> SwipeDirection { + switch self { + case let .left(state): + return .left(abs(state.delta) > width / 4 ? .success(delta: state.delta, controller: state.controller) : .failed(delta: state.delta, controller: state.controller)) + case let .right(state): + return .right(abs(state.delta) > width / 4 ? .success(delta: state.delta, controller: state.controller) : .failed(delta: state.delta, controller: state.controller)) + default: + return self + } + } +} + +public enum SwipeHandlerResult { + case success(ViewController) + case failed + case nothing + case deltaUpdated(available: CGFloat) +} + +class SwipeHandler : Comparable { + let handler:(SwipeDirection, Bool)->SwipeHandlerResult + let object:WeakReference + let priority:HandlerPriority + + init(_ handler:@escaping(SwipeDirection, Bool)->SwipeHandlerResult, _ object:NSView, _ priority:HandlerPriority) { + self.handler = handler + self.object = WeakReference(value: object) + self.priority = priority + } +} +func ==(lhs: SwipeHandler, rhs: SwipeHandler) -> Bool { + return lhs.priority == rhs.priority +} +func <(lhs: SwipeHandler, rhs: SwipeHandler) -> Bool { + return lhs.priority < rhs.priority +} + + +class ResponderObserver : Comparable { + let handler:()->NSResponder? + let ignoreKeys:[KeyboardKey] + let object:WeakReference + let priority:HandlerPriority + let date: TimeInterval + init(_ handler:@escaping()->NSResponder?, _ object:NSObject?, _ priority:HandlerPriority, _ ignoreKeys: [KeyboardKey]) { + self.handler = handler + self.ignoreKeys = ignoreKeys + self.object = WeakReference(value: object) + self.priority = priority + self.date = Date().timeIntervalSince1970 + } +} +func ==(lhs: ResponderObserver, rhs: ResponderObserver) -> Bool { + return lhs.priority == rhs.priority +} +func <(lhs: ResponderObserver, rhs: ResponderObserver) -> Bool { + if lhs.priority == rhs.priority { + return lhs.date < rhs.date + } + return lhs.priority < rhs.priority +} + +class MouseObserver : Comparable { + let handler:(NSEvent)->KeyHandlerResult + let object:WeakReference + let priority:HandlerPriority + let type:NSEvent.EventType? + let date: TimeInterval + init(_ handler:@escaping(NSEvent)->KeyHandlerResult, _ object:NSObject?, _ priority:HandlerPriority, _ type:NSEvent.EventType) { + self.handler = handler + self.object = WeakReference(value: object) + self.priority = priority + self.type = type + self.date = Date().timeIntervalSince1970 + } +} +func ==(lhs: MouseObserver, rhs: MouseObserver) -> Bool { + return lhs.priority == rhs.priority +} +func <(lhs: MouseObserver, rhs: MouseObserver) -> Bool { + if lhs.priority == rhs.priority { + return lhs.date < rhs.date + } + return lhs.priority < rhs.priority +} + +public typealias SwipeIdentifier = String + +public enum KeyHandlerResult { + case invoked // invoke and return + case rejected // can invoke next priority event + case invokeNext // invoke and send global event +} + + +open class Window: NSWindow { + public var name: String = "TGUIKit.Window" + private var keyHandlers:[KeyboardKey:[KeyHandler]] = [:] + private var swipeHandlers:[SwipeIdentifier: SwipeHandler] = [:] + private var swipeState:[SwipeIdentifier: SwipeDirection] = [:] + + private var responsders:[ResponderObserver] = [] + private var mouseHandlers:[UInt:[MouseObserver]] = [:] + private var swipePoints:[NSPoint] = [] + private var saver:WindowSaver? + public var initFromSaver:Bool = false + public var copyhandler:(()->Void)? = nil + public var closeInterceptor:(()->Bool)? = nil + public var orderOutHandler:(()->Void)? = nil + public weak var rootViewController: ViewController? + public var firstResponderFilter:(NSResponder?)->NSResponder? = { $0 } + + public func set(responder:@escaping() -> NSResponder?, with object:NSObject?, priority:HandlerPriority, ignoreKeys: [KeyboardKey] = []) { + responsders.append(ResponderObserver(responder, object, priority, ignoreKeys + [.Escape, .LeftArrow, .RightArrow, .Tab, .UpArrow, .DownArrow])) + } + + public func removeObserver(for object:NSObject) { + var copy:[ResponderObserver] = [] + for observer in responsders { + copy.append(observer) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value == object || copy[i].object.value == nil { + responsders.remove(at: i) + } + } + } + + + public func set(handler:@escaping() -> KeyHandlerResult, with object:NSObject, for key:KeyboardKey, priority:HandlerPriority = .low, modifierFlags:NSEvent.ModifierFlags? = nil) -> Void { + var handlers:[KeyHandler]? = keyHandlers[key] + if handlers == nil { + handlers = [] + keyHandlers[key] = handlers + } + + keyHandlers[key]?.append(KeyHandler(handler, object, priority, modifierFlags)) + + if key == .Return { + set(handler: handler, with: self, for: .KeypadEnter, priority: priority, modifierFlags: modifierFlags) + } + + } + + public func add(swipe handler:@escaping(SwipeDirection, Bool) -> SwipeHandlerResult, with object:NSView, identifier: SwipeIdentifier, priority:HandlerPriority = .low) -> Void { + swipeHandlers[identifier] = SwipeHandler(handler, object, priority) + } + + + public func removeAllHandlers(for object:NSObject) { + + var newKeyHandlers:[KeyboardKey:[KeyHandler]] = [:] + for (key, handlers) in keyHandlers { + newKeyHandlers[key] = handlers + } + + for (key, handlers) in keyHandlers { + var copy:[KeyHandler] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value === object { + newKeyHandlers[key]?.remove(at: i) + } + } + } + self.keyHandlers = newKeyHandlers + + var newMouseHandlers:[UInt:[MouseObserver]] = [:] + for (key, handlers) in mouseHandlers { + newMouseHandlers[key] = handlers + } + + for (key, handlers) in mouseHandlers { + var copy:[MouseObserver] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value === object { + newMouseHandlers[key]?.remove(at: i) + } + } + } + self.mouseHandlers = newMouseHandlers + + self.swipeHandlers = self.swipeHandlers.filter { key, value in + return value.object.value !== object && value.object.value != nil + } + self.responsders = responsders.filter { + $0.object.value !== object + } + } + + public func remove(object:NSObject, for key:KeyboardKey, modifierFlags: NSEvent.ModifierFlags? = nil, forceCheckFlags: Bool = false) { + let handlers = keyHandlers[key] + if let handlers = handlers { + var copy:[KeyHandler] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if (copy[i].object.value == object || copy[i].object.value == nil) && ((forceCheckFlags || modifierFlags == nil) && modifierFlags == copy[i].modifierFlags) { + keyHandlers[key]?.remove(at: i) + } + } + } + if key == .Return { + self.remove(object: object, for: .KeypadEnter, modifierFlags: modifierFlags, forceCheckFlags: forceCheckFlags) + } + } + + private func cleanUndefinedHandlers() { + var newKeyHandlers:[KeyboardKey:[KeyHandler]] = [:] + for (key, handlers) in keyHandlers { + newKeyHandlers[key] = handlers + } + + for (key, handlers) in keyHandlers { + var copy:[KeyHandler] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value == nil { + newKeyHandlers[key]?.remove(at: i) + } + } + } + self.keyHandlers = newKeyHandlers + + var newMouseHandlers:[UInt:[MouseObserver]] = [:] + for (key, handlers) in mouseHandlers { + newMouseHandlers[key] = handlers + } + + for (key, handlers) in mouseHandlers { + var copy:[MouseObserver] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value == nil { + newMouseHandlers[key]?.remove(at: i) + } + } + } + self.mouseHandlers = newMouseHandlers + + self.swipeHandlers = self.swipeHandlers.filter { key, value in + return value.object.value != nil + } + + } + + public func set(mouseHandler:@escaping(NSEvent) -> KeyHandlerResult, with object:NSObject, for type:NSEvent.EventType, priority:HandlerPriority = .low) -> Void { + var handlers:[MouseObserver]? = mouseHandlers[type.rawValue] + if handlers == nil { + handlers = [] + mouseHandlers[type.rawValue] = handlers + } + mouseHandlers[type.rawValue]?.append(MouseObserver(mouseHandler, object, priority, type)) + } + + public func remove(object:NSObject, for type:NSEvent.EventType) { + let handlers = mouseHandlers[type.rawValue] + if let handlers = handlers { + var copy:[MouseObserver] = [] + for handle in handlers { + copy.append(handle) + } + for i in stride(from: copy.count - 1, to: -1, by: -1) { + if copy[i].object.value == object || copy[i].object.value == nil { + mouseHandlers[type.rawValue]?.remove(at: i) + } + } + } + } + + + public func applyResponderIfNeeded(_ event: NSEvent? = nil) ->Void { + let sorted = responsders.sorted(by: >) + if let event = event, event.modifierFlags.contains(.option) + || event.modifierFlags.contains(.control) { + return + } + for observer in sorted { + if let event = event, let code = KeyboardKey(rawValue: event.keyCode), observer.ignoreKeys.contains(code) { + continue + } + if let responder = observer.handler() { + if self.firstResponder != responder { + let _ = self.resignFirstResponder() + if responder.responds(to: NSSelectorFromString("window")) { + let window:NSWindow? = responder.value(forKey: "window") as? NSWindow + if window != self { + continue + } + } + _ = self.makeFirstResponder(responder) + if let responder = responder as? NSTextField { + responder.setCursorToEnd() + } + } + break + } + } + } + + @available(OSX 10.12.2, *) + open override func makeTouchBar() -> NSTouchBar? { + if !sheets.isEmpty { + for sheet in sheets.reversed() { + if let sheet = sheet as? Window { + if hasModals(sheet) { + return Modal.topModalController(self)?.makeTouchBar() ?? sheet.makeTouchBar() + } + } + return sheet.makeTouchBar() + } + } + if let topModal = Modal.topModalController(self) { + if topModal.hasOwnTouchbar { + return topModal.makeTouchBar() ?? super.makeTouchBar() + } + } + return self.rootViewController?.makeTouchBar() ?? super.makeTouchBar() + } + + open override func makeFirstResponder(_ responder: NSResponder?) -> Bool { + if let responder = responder, responder.responds(to: NSSelectorFromString("window")) { + let window:NSWindow? = responder.value(forKey: "window") as? NSWindow + if window != self { + return false + } + } + return super.makeFirstResponder(self.firstResponderFilter(responder)) + } + + open override func becomeFirstResponder() -> Bool { + return false + } + + public func sendKeyEvent(_ key: KeyboardKey, modifierFlags: NSEvent.ModifierFlags) { + guard let event = NSEvent.keyEvent(with: .keyDown, location: mouseLocationOutsideOfEventStream, modifierFlags: modifierFlags, timestamp: Date().timeIntervalSince1970, windowNumber: windowNumber, context: graphicsContext, characters: "", charactersIgnoringModifiers: "", isARepeat: false, keyCode: key.rawValue) else {return} + + sendEvent(event) + } + + open override func makeKeyAndOrderFront(_ sender: Any?) { + super.makeKeyAndOrderFront(sender) + } + open override func orderOut(_ sender: Any?) { + super.orderOut(sender) + orderOutHandler?() + } + + public func enumerateAllSubviews(callback: (NSView) -> Void) { + if let contentView = contentView { + enumerateAllSubviews(callback: callback, superview: contentView) + } + } + private func enumerateAllSubviews(callback: (NSView) -> Void, superview: NSView) { + for view in superview.subviews { + callback(view) + if !view.subviews.isEmpty { + enumerateAllSubviews(callback: callback, superview: view) + } + } + } + + open override func close() { + if let closeInterceptor = closeInterceptor, closeInterceptor() { + return + } + if isReleasedWhenClosed { + super.close() + } else { + super.close() + } + } + + private func scrollDeltaXAfterInvertion(_ value: CGFloat) -> CGFloat { + var deltaX: CGFloat = 0 + let isInverted: Bool = System.isScrollInverted + + if !isInverted { + deltaX = -value + } else { + deltaX = value + } + return deltaX + } + + private func startSwiping(_ event: NSEvent) { + if event.scrollingDeltaY == 0 && event.scrollingDeltaX != 0 { + for (key, swipe) in swipeHandlers.sorted(by: { $0.value.priority > $1.value.priority }) { + if let view = swipe.object.value, view._mouseInside() { + if scrollDeltaXAfterInvertion(event.scrollingDeltaX) > 0 { + let result = swipe.handler(.left(.start(controller: ViewController())), true) + switch result { + case let .success(controller): + swipeState[key] = .left(.start(controller: controller)) + break + default: + break + } + } else { + let result = swipe.handler(.right(.start(controller: ViewController())), true) + switch result { + case let .success(controller): + swipeState[key] = .right(.start(controller: controller)) + break + default: + break + } + } + } + } + } + + } + private func stopSwiping(_ event: NSEvent) { + for (key, swipe) in swipeState { + if let handler = swipeHandlers[key], let view = handler.object.value { + _ = handler.handler(swipe.withUpdatedSuccessOrFail(view.frame.width), true) + } + } + swipeState.removeAll() + } + + + private func proccessSwiping(_ event: NSEvent) -> Void { + let copy = self.swipeState + for (key, swipe) in copy { + if let handler = swipeHandlers[key], let value = handler.object.value, value._mouseInside() { + let deltaX: CGFloat = scrollDeltaXAfterInvertion(event.scrollingDeltaX) + + let newState = swipe.withAdditionalDelta(deltaX) + let result = handler.handler(newState, true) + switch result { + case let .deltaUpdated(available): + swipeState[key] = swipe.withAdditionalDelta(available, true) + default: + swipeState[key] = newState + } + break + } + } + } + + public func abortSwiping() -> Void { + let copy = self.swipeState + for (key, swipe) in copy { + switch swipe { + case let .left(state): + let swipe: SwipeDirection = .left(.failed(delta: state.delta, controller: state.controller)) + if let handler = swipeHandlers[key] { + _ = handler.handler(swipe, false) + } + self.swipeState.removeValue(forKey: key) + case let .right(state): + let swipe: SwipeDirection = .right(.failed(delta: state.delta, controller: state.controller)) + if let handler = swipeHandlers[key] { + _ = handler.handler(swipe, false) + } + default: + break + } + } + self.swipeState.removeAll() + } + + public var inLiveSwiping: Bool { + return !swipeState.isEmpty + } + + @objc public func pasteToFirstResponder(_ sender: Any) { + + applyResponderIfNeeded() + + if let firstResponder = firstResponder, firstResponder.responds(to: NSSelectorFromString("paste:")) { + firstResponder.performSelector(onMainThread: NSSelectorFromString("paste:"), with: sender, waitUntilDone: false) + } + } + + @objc public func copyFromFirstResponder(_ sender: Any) { + if let copyhandler = copyhandler { + copyhandler() + } else { + if let firstResponder = firstResponder, firstResponder.responds(to: NSSelectorFromString("copy:")) { + firstResponder.performSelector(onMainThread: NSSelectorFromString("copy:"), with: sender, waitUntilDone: false) + } + } + + } + + + open override func sendEvent(_ event: NSEvent) { + + // let testEvent = NSEvent.EventType.init(rawValue: 36)! + // + +// if event.type.rawValue == 29 { +// NSLog("\(event.modifierFlags.rawValue)") +// var bp:Int = 0 +// bp += 1 +// } + + let eventType = event.type + if sheets.isEmpty { + if eventType == .keyDown { + + applyResponderIfNeeded(event) + + cleanUndefinedHandlers() + + if let globalHandlers = keyHandlers[.All]?.sorted(by: >), let keyCode = KeyboardKey(rawValue:event.keyCode) { + if let handle = keyHandlers[keyCode]?.sorted(by: >).first { + for globalHandler in globalHandlers { + if globalHandler.priority > handle.priority { + if (handle.modifierFlags == nil || event.modifierFlags.contains(handle.modifierFlags!)) { + switch globalHandler.handler() { + case .invoked: + return + case .rejected: + break + case .invokeNext: + super.sendEvent(event) + return + } + } + } + } + } + } + + if let keyCode = KeyboardKey(rawValue:event.keyCode), let handlers = keyHandlers[keyCode]?.sorted(by: >) { + loop: for handle in handlers { + + if (handle.modifierFlags == nil || event.modifierFlags.contains(handle.modifierFlags!)) { + + switch handle.handler() { + case .invoked: + return + case .rejected: + continue + case .invokeNext: + break loop + } + + } + } + } + } else if eventType == .scrollWheel, !swipeHandlers.isEmpty { + + + if let handlers = mouseHandlers[eventType.rawValue] { + let sorted = handlers.sorted(by: >) + loop: for handle in sorted { + switch handle.handler(event) { + case .invoked: + return + case .rejected: + continue + case .invokeNext: + break loop + } + } + } + + switch event.phase { + case NSEvent.Phase.began: + startSwiping(event) + case NSEvent.Phase.stationary: + break + case NSEvent.Phase.changed: + proccessSwiping(event) + case NSEvent.Phase.ended: + stopSwiping(event) + case NSEvent.Phase.cancelled: + stopSwiping(event) + case NSEvent.Phase.mayBegin: + break + default: + break + } + + } else if let handlers = mouseHandlers[eventType.rawValue] { + let sorted = handlers.sorted(by: >) + loop: for handle in sorted { + switch handle.handler(event) { + case .invoked: + return + case .rejected: + continue + case .invokeNext: + break loop + } + } + } + if eventType == .rightMouseDown { + if !swipeState.isEmpty { + stopSwiping(event) + } + } + super.sendEvent(event) + } else { + //super.sendEvent(event) + } + + + } + + open override func swipe(with event: NSEvent) { + super.swipe(with: event) + } + + // public func set(copy handler:@escaping()->Void) -> (()-> Void,NSEventModifierFlags?)? { + // return self.set(handler: handler, for: .C, priority:.low, modifierFlags: [.command]) + // } + // + // public func set(paste handler:@escaping()->Void) -> (()-> Void,NSEventModifierFlags?)? { + // return self.set(handler: handler, for: .V, modifierFlags: [.command]) + // } + + public func set(escape handler:@escaping() -> KeyHandlerResult, with object:NSObject, priority:HandlerPriority = .low, modifierFlags:NSEvent.ModifierFlags? = nil) -> Void { + set(handler: handler, with: object, for: .Escape, priority:priority, modifierFlags:modifierFlags) + } + + open override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + } + + + open override var canBecomeKey: Bool { + return true + } + + public var isFullScreen: Bool { + return styleMask.contains(.fullScreen) + } + + public func initSaver() { + self.initFromSaver = true + self.saver = .find(for: self) + if let saver = saver { + self.setFrame(saver.rect, display: true) + if saver.isFullScreen { + toggleFullScreen(self) + } + } + } + + open override func toggleFullScreen(_ sender: Any?) { + CATransaction.begin() + super.toggleFullScreen(sender) + CATransaction.commit() + saver?.isFullScreen = isFullScreen + } + + @objc func windowDidNeedSaveState(_ notification: Notification) { + guard let saver = saver, !saver.isFullScreen && !isFullScreen else { + self.saver?.save() + return + } + saver.rect = frame + saver.save() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + public func windowImageShot() -> CGImage? { + return CGWindowListCreateImage(CGRect.null, [.optionIncludingWindow], CGWindowID(windowNumber), [.boundsIgnoreFraming]) + } + + + + public override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing bufferingType: NSWindow.BackingStoreType, defer flag: Bool) { + super.init(contentRect: contentRect, styleMask: style, backing: bufferingType, defer: flag) + + self.acceptsMouseMovedEvents = true + + isOpaque = true + + self.contentView?.acceptsTouchEvents = true + NotificationCenter.default.addObserver(self, selector: #selector(windowDidNeedSaveState(_:)), name: NSWindow.didMoveNotification, object: self) + NotificationCenter.default.addObserver(self, selector: #selector(windowDidNeedSaveState(_:)), name: NSWindow.didResizeNotification, object: self) + + + + } + + public static var statusBarHeight: CGFloat { + return 22 + } + +} diff --git a/TGUIKit/TGUIKit/WindowSaver.swift b/submodules/TGUIKit/TGUIKit/WindowSaver.swift similarity index 83% rename from TGUIKit/TGUIKit/WindowSaver.swift rename to submodules/TGUIKit/TGUIKit/WindowSaver.swift index 34a1980416..cdc70f1a79 100644 --- a/TGUIKit/TGUIKit/WindowSaver.swift +++ b/submodules/TGUIKit/TGUIKit/WindowSaver.swift @@ -7,24 +7,28 @@ // import Cocoa -import SwiftSignalKitMac +import SwiftSignalKit public class WindowSaver : NSObject, NSCoding { var rect:NSRect let requiredName:String + var isFullScreen: Bool private let disposable:MetaDisposable = MetaDisposable() - init(name: String, rect:NSRect) { + init(name: String, rect:NSRect, isFullScreen: Bool) { self.rect = rect self.requiredName = name + self.isFullScreen = isFullScreen } required public init?(coder aDecoder: NSCoder) { self.rect = aDecoder.decodeRect(forKey: "rect") self.requiredName = aDecoder.decodeObject(forKey: "name") as! String + self.isFullScreen = aDecoder.decodeBool(forKey: "isFullScreen") } public func encode(with aCoder: NSCoder) { aCoder.encode(rect, forKey: "rect") aCoder.encode(self.requiredName, forKey: "name") + aCoder.encode(self.isFullScreen, forKey: "isFullScreen") } static public func find(for window:Window) -> WindowSaver { @@ -35,13 +39,13 @@ public class WindowSaver : NSObject, NSCoding { archiver = NSKeyedUnarchiver.unarchiveObject(with: data) as? WindowSaver } if archiver == nil { - archiver = WindowSaver(name: window.name, rect: window.frame) + archiver = WindowSaver(name: window.name, rect: window.frame, isFullScreen: window.isFullScreen) } return archiver! } public func save() { - let single:Signal = .single(Void()) |> delay(1.5, queue: Queue.mainQueue()) + let single:Signal = .single(Void()) |> delay(0.5, queue: Queue.mainQueue()) disposable.set(single.start(next: { [weak self] in if let strongSelf = self { diff --git a/submodules/TelegramCore b/submodules/TelegramCore deleted file mode 160000 index dbc32ce11f..0000000000 --- a/submodules/TelegramCore +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbc32ce11f303c157843d075f24dc71c644ea156 diff --git a/submodules/Zip b/submodules/Zip new file mode 160000 index 0000000000..ea98154bbd --- /dev/null +++ b/submodules/Zip @@ -0,0 +1 @@ +Subproject commit ea98154bbd09ad03a88207dfde9758a12d48019a diff --git a/submodules/libtgvoip b/submodules/libtgvoip index 620da00fb6..f411b66226 160000 --- a/submodules/libtgvoip +++ b/submodules/libtgvoip @@ -1 +1 @@ -Subproject commit 620da00fb699deeb81bda34a9b57519ab4810b08 +Subproject commit f411b66226c17c2bcb553c1471f6185f08c2c6fc diff --git a/submodules/rlottie b/submodules/rlottie new file mode 160000 index 0000000000..598f54078b --- /dev/null +++ b/submodules/rlottie @@ -0,0 +1 @@ +Subproject commit 598f54078b5a7c029a85bea8a710760e986ac536 diff --git a/submodules/telegram-ios b/submodules/telegram-ios new file mode 160000 index 0000000000..17cd0a81ee --- /dev/null +++ b/submodules/telegram-ios @@ -0,0 +1 @@ +Subproject commit 17cd0a81ee7993d3709fa3fd544ea36bad4f9ab3 diff --git a/swiftgen.sh b/swiftgen.sh deleted file mode 100644 index 651c9e2931..0000000000 --- a/swiftgen.sh +++ /dev/null @@ -1 +0,0 @@ -swiftgen strings -t swift3 --output ./Telegram-Mac/Localizable.swift ./Telegram-Mac/en.lproj/Localizable.strings diff --git a/thrid-party/Video Encoder/LiveUploadingHelper.h b/thrid-party/Video Encoder/LiveUploadingHelper.h new file mode 100644 index 0000000000..3ad064b049 --- /dev/null +++ b/thrid-party/Video Encoder/LiveUploadingHelper.h @@ -0,0 +1,13 @@ +// +// LiveUploadingHelper.h +// Telegram +// +// Created by Mikhail Filimonov on 08/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +#import + +@interface LiveUploadingHelper : NSObject + +@end diff --git a/thrid-party/Video Encoder/LiveUploadingHelper.m b/thrid-party/Video Encoder/LiveUploadingHelper.m new file mode 100644 index 0000000000..0fe20bc404 --- /dev/null +++ b/thrid-party/Video Encoder/LiveUploadingHelper.m @@ -0,0 +1,50 @@ +// +// LiveUploadingHelper.m +// Telegram +// +// Created by Mikhail Filimonov on 08/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +#import "LiveUploadingHelper.h" +#import "MP4Atom.h" +#import + + +@implementation LiveUploadingHelper ++ (void)readFileURL:(NSURL *)fileURL processBlock:(void (^)(NSFileHandle *, struct stat, MP4Atom *))processBlock +{ + NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:fileURL.path]; + struct stat s; + fstat([file fileDescriptor], &s); + + MP4Atom *fileAtom = [MP4Atom atomAt:0 size:(int)s.st_size type:(OSType)('file') inFile:file]; + MP4Atom *mdatAtom = [LiveUploadingHelper _findMdat:fileAtom]; + if (mdatAtom != nil && processBlock != nil) + processBlock(file, s, mdatAtom); + + [file closeFile]; +} + ++ (MP4Atom *)_findMdat:(MP4Atom *)atom +{ + if (atom == nil) + return nil; + + if (atom.type == (OSType)'mdat') + return atom; + + while (true) + { + MP4Atom *child = [atom nextChild]; + if (child == nil) + break; + + MP4Atom *result = [self _findMdat:child]; + if (result != nil) + return result; + } + + return nil; +} +@end diff --git a/thrid-party/Video Encoder/MP4Atom.h b/thrid-party/Video Encoder/MP4Atom.h new file mode 100644 index 0000000000..a816554d88 --- /dev/null +++ b/thrid-party/Video Encoder/MP4Atom.h @@ -0,0 +1,31 @@ +// +// MP4Atom.h +// Telegram +// +// Created by Mikhail Filimonov on 08/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +#import + +@interface MP4Atom : NSObject + +{ +@public + NSFileHandle* _file; + int64_t _offset; + int64_t _length; + OSType _type; + int64_t _nextChild; +} +@property (nonatomic) OSType type; +@property (nonatomic) int64_t length; + ++ (MP4Atom*) atomAt:(int64_t) offset size:(int) length type:(OSType) fourcc inFile:(NSFileHandle*) handle; +- (BOOL) init:(int64_t) offset size:(int) length type:(OSType) fourcc inFile:(NSFileHandle*) handle; +- (NSData*) readAt:(int64_t) offset size:(int) length; +- (BOOL) setChildOffset:(int64_t) offset; +- (MP4Atom*) nextChild; +- (MP4Atom*) childOfType:(OSType) fourcc startAt:(int64_t) offset; + +@end diff --git a/thrid-party/Video Encoder/MP4Atom.m b/thrid-party/Video Encoder/MP4Atom.m new file mode 100644 index 0000000000..4ed9e906a5 --- /dev/null +++ b/thrid-party/Video Encoder/MP4Atom.m @@ -0,0 +1,108 @@ +// +// MP4Atom.m +// Telegram +// +// Created by Mikhail Filimonov on 08/03/2018. +// Copyright © 2018 Telegram. All rights reserved. +// + +#import "MP4Atom.h" + + +#import "MP4Atom.h" + +static unsigned int to_host(unsigned char* p) +{ + return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; +} + +@implementation MP4Atom + ++ (MP4Atom*) atomAt:(int64_t) offset size:(int) length type:(OSType) fourcc inFile:(NSFileHandle*) handle +{ + MP4Atom* atom = [MP4Atom alloc]; + if (![atom init:offset size:length type:fourcc inFile:handle]) + { + return nil; + } + return atom; +} + +- (BOOL) init:(int64_t) offset size:(int) length type:(OSType) fourcc inFile:(NSFileHandle*) handle +{ + _file = handle; + _offset = offset; + _length = length; + _type = fourcc; + _nextChild = 0; + + return YES; +} + +- (NSData *)readAt:(int64_t)offset size:(int)length +{ + [_file seekToFileOffset:_offset + offset]; + return [_file readDataOfLength:length]; +} + +- (BOOL)setChildOffset:(int64_t) offset +{ + _nextChild = offset; + return YES; +} + +- (MP4Atom*) nextChild +{ + if (_nextChild <= (_length - 8)) + { + [_file seekToFileOffset:_offset + _nextChild]; + NSData *data = [_file readDataOfLength:8]; + if (data == nil || data.length == 0) + return nil; + + int cHeader = 8; + unsigned char* p = (unsigned char*) [data bytes]; + int64_t len = to_host(p); + OSType fourcc = to_host(p + 4); + if (len == 1) + { + // 64-bit extended length + cHeader+= 8; + data = [_file readDataOfLength:8]; + p = (unsigned char*) [data bytes]; + len = to_host(p); + len = (len << 32) + to_host(p + 4); + } + else if (len == 0) + { + // whole remaining parent space + len = _length - _nextChild; + } + if (fourcc == (OSType)('uuid')) + { + cHeader += 16; + } + if ((len < 0) || ((len + _nextChild) > _length)) + { + return nil; + } + int64_t offset = _nextChild + cHeader; + _nextChild += len; + len -= cHeader; + return [MP4Atom atomAt:offset+_offset size:(int)len type:fourcc inFile:_file]; + } + return nil; +} + +- (MP4Atom*) childOfType:(OSType) fourcc startAt:(int64_t) offset +{ + [self setChildOffset:offset]; + MP4Atom* child = nil; + do { + child = [self nextChild]; + } while ((child != nil) && (child.type != fourcc)); + return child; +} + +@end + diff --git a/thrid-party/ffmpeg/include/libavcodec/avcodec.h b/thrid-party/ffmpeg/include/libavcodec/avcodec.h new file mode 100644 index 0000000000..18c3e3ea1e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/avcodec.h @@ -0,0 +1,6425 @@ +/* + * copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_AVCODEC_H +#define AVCODEC_AVCODEC_H + +/** + * @file + * @ingroup libavc + * Libavcodec external API header + */ + +#include +#include "libavutil/samplefmt.h" +#include "libavutil/attributes.h" +#include "libavutil/avutil.h" +#include "libavutil/buffer.h" +#include "libavutil/cpu.h" +#include "libavutil/channel_layout.h" +#include "libavutil/dict.h" +#include "libavutil/frame.h" +#include "libavutil/log.h" +#include "libavutil/pixfmt.h" +#include "libavutil/rational.h" + +#include "version.h" + +/** + * @defgroup libavc libavcodec + * Encoding/Decoding Library + * + * @{ + * + * @defgroup lavc_decoding Decoding + * @{ + * @} + * + * @defgroup lavc_encoding Encoding + * @{ + * @} + * + * @defgroup lavc_codec Codecs + * @{ + * @defgroup lavc_codec_native Native Codecs + * @{ + * @} + * @defgroup lavc_codec_wrappers External library wrappers + * @{ + * @} + * @defgroup lavc_codec_hwaccel Hardware Accelerators bridge + * @{ + * @} + * @} + * @defgroup lavc_internal Internal + * @{ + * @} + * @} + */ + +/** + * @ingroup libavc + * @defgroup lavc_encdec send/receive encoding and decoding API overview + * @{ + * + * The avcodec_send_packet()/avcodec_receive_frame()/avcodec_send_frame()/ + * avcodec_receive_packet() functions provide an encode/decode API, which + * decouples input and output. + * + * The API is very similar for encoding/decoding and audio/video, and works as + * follows: + * - Set up and open the AVCodecContext as usual. + * - Send valid input: + * - For decoding, call avcodec_send_packet() to give the decoder raw + * compressed data in an AVPacket. + * - For encoding, call avcodec_send_frame() to give the encoder an AVFrame + * containing uncompressed audio or video. + * In both cases, it is recommended that AVPackets and AVFrames are + * refcounted, or libavcodec might have to copy the input data. (libavformat + * always returns refcounted AVPackets, and av_frame_get_buffer() allocates + * refcounted AVFrames.) + * - Receive output in a loop. Periodically call one of the avcodec_receive_*() + * functions and process their output: + * - For decoding, call avcodec_receive_frame(). On success, it will return + * an AVFrame containing uncompressed audio or video data. + * - For encoding, call avcodec_receive_packet(). On success, it will return + * an AVPacket with a compressed frame. + * Repeat this call until it returns AVERROR(EAGAIN) or an error. The + * AVERROR(EAGAIN) return value means that new input data is required to + * return new output. In this case, continue with sending input. For each + * input frame/packet, the codec will typically return 1 output frame/packet, + * but it can also be 0 or more than 1. + * + * At the beginning of decoding or encoding, the codec might accept multiple + * input frames/packets without returning a frame, until its internal buffers + * are filled. This situation is handled transparently if you follow the steps + * outlined above. + * + * In theory, sending input can result in EAGAIN - this should happen only if + * not all output was received. You can use this to structure alternative decode + * or encode loops other than the one suggested above. For example, you could + * try sending new input on each iteration, and try to receive output if that + * returns EAGAIN. + * + * End of stream situations. These require "flushing" (aka draining) the codec, + * as the codec might buffer multiple frames or packets internally for + * performance or out of necessity (consider B-frames). + * This is handled as follows: + * - Instead of valid input, send NULL to the avcodec_send_packet() (decoding) + * or avcodec_send_frame() (encoding) functions. This will enter draining + * mode. + * - Call avcodec_receive_frame() (decoding) or avcodec_receive_packet() + * (encoding) in a loop until AVERROR_EOF is returned. The functions will + * not return AVERROR(EAGAIN), unless you forgot to enter draining mode. + * - Before decoding can be resumed again, the codec has to be reset with + * avcodec_flush_buffers(). + * + * Using the API as outlined above is highly recommended. But it is also + * possible to call functions outside of this rigid schema. For example, you can + * call avcodec_send_packet() repeatedly without calling + * avcodec_receive_frame(). In this case, avcodec_send_packet() will succeed + * until the codec's internal buffer has been filled up (which is typically of + * size 1 per output frame, after initial input), and then reject input with + * AVERROR(EAGAIN). Once it starts rejecting input, you have no choice but to + * read at least some output. + * + * Not all codecs will follow a rigid and predictable dataflow; the only + * guarantee is that an AVERROR(EAGAIN) return value on a send/receive call on + * one end implies that a receive/send call on the other end will succeed, or + * at least will not fail with AVERROR(EAGAIN). In general, no codec will + * permit unlimited buffering of input or output. + * + * This API replaces the following legacy functions: + * - avcodec_decode_video2() and avcodec_decode_audio4(): + * Use avcodec_send_packet() to feed input to the decoder, then use + * avcodec_receive_frame() to receive decoded frames after each packet. + * Unlike with the old video decoding API, multiple frames might result from + * a packet. For audio, splitting the input packet into frames by partially + * decoding packets becomes transparent to the API user. You never need to + * feed an AVPacket to the API twice (unless it is rejected with AVERROR(EAGAIN) - then + * no data was read from the packet). + * Additionally, sending a flush/draining packet is required only once. + * - avcodec_encode_video2()/avcodec_encode_audio2(): + * Use avcodec_send_frame() to feed input to the encoder, then use + * avcodec_receive_packet() to receive encoded packets. + * Providing user-allocated buffers for avcodec_receive_packet() is not + * possible. + * - The new API does not handle subtitles yet. + * + * Mixing new and old function calls on the same AVCodecContext is not allowed, + * and will result in undefined behavior. + * + * Some codecs might require using the new API; using the old API will return + * an error when calling it. All codecs support the new API. + * + * A codec is not allowed to return AVERROR(EAGAIN) for both sending and receiving. This + * would be an invalid state, which could put the codec user into an endless + * loop. The API has no concept of time either: it cannot happen that trying to + * do avcodec_send_packet() results in AVERROR(EAGAIN), but a repeated call 1 second + * later accepts the packet (with no other receive/flush API calls involved). + * The API is a strict state machine, and the passage of time is not supposed + * to influence it. Some timing-dependent behavior might still be deemed + * acceptable in certain cases. But it must never result in both send/receive + * returning EAGAIN at the same time at any point. It must also absolutely be + * avoided that the current state is "unstable" and can "flip-flop" between + * the send/receive APIs allowing progress. For example, it's not allowed that + * the codec randomly decides that it actually wants to consume a packet now + * instead of returning a frame, after it just returned AVERROR(EAGAIN) on an + * avcodec_send_packet() call. + * @} + */ + +/** + * @defgroup lavc_core Core functions/structures. + * @ingroup libavc + * + * Basic definitions, functions for querying libavcodec capabilities, + * allocating core structures, etc. + * @{ + */ + + +/** + * Identify the syntax and semantics of the bitstream. + * The principle is roughly: + * Two decoders with the same ID can decode the same streams. + * Two encoders with the same ID can encode compatible streams. + * There may be slight deviations from the principle due to implementation + * details. + * + * If you add a codec ID to this list, add it so that + * 1. no value of an existing codec ID changes (that would break ABI), + * 2. it is as close as possible to similar codecs + * + * After adding new codec IDs, do not forget to add an entry to the codec + * descriptor list and bump libavcodec minor version. + */ +enum AVCodecID { + AV_CODEC_ID_NONE, + + /* video codecs */ + AV_CODEC_ID_MPEG1VIDEO, + AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding +#if FF_API_XVMC + AV_CODEC_ID_MPEG2VIDEO_XVMC, +#endif /* FF_API_XVMC */ + AV_CODEC_ID_H261, + AV_CODEC_ID_H263, + AV_CODEC_ID_RV10, + AV_CODEC_ID_RV20, + AV_CODEC_ID_MJPEG, + AV_CODEC_ID_MJPEGB, + AV_CODEC_ID_LJPEG, + AV_CODEC_ID_SP5X, + AV_CODEC_ID_JPEGLS, + AV_CODEC_ID_MPEG4, + AV_CODEC_ID_RAWVIDEO, + AV_CODEC_ID_MSMPEG4V1, + AV_CODEC_ID_MSMPEG4V2, + AV_CODEC_ID_MSMPEG4V3, + AV_CODEC_ID_WMV1, + AV_CODEC_ID_WMV2, + AV_CODEC_ID_H263P, + AV_CODEC_ID_H263I, + AV_CODEC_ID_FLV1, + AV_CODEC_ID_SVQ1, + AV_CODEC_ID_SVQ3, + AV_CODEC_ID_DVVIDEO, + AV_CODEC_ID_HUFFYUV, + AV_CODEC_ID_CYUV, + AV_CODEC_ID_H264, + AV_CODEC_ID_INDEO3, + AV_CODEC_ID_VP3, + AV_CODEC_ID_THEORA, + AV_CODEC_ID_ASV1, + AV_CODEC_ID_ASV2, + AV_CODEC_ID_FFV1, + AV_CODEC_ID_4XM, + AV_CODEC_ID_VCR1, + AV_CODEC_ID_CLJR, + AV_CODEC_ID_MDEC, + AV_CODEC_ID_ROQ, + AV_CODEC_ID_INTERPLAY_VIDEO, + AV_CODEC_ID_XAN_WC3, + AV_CODEC_ID_XAN_WC4, + AV_CODEC_ID_RPZA, + AV_CODEC_ID_CINEPAK, + AV_CODEC_ID_WS_VQA, + AV_CODEC_ID_MSRLE, + AV_CODEC_ID_MSVIDEO1, + AV_CODEC_ID_IDCIN, + AV_CODEC_ID_8BPS, + AV_CODEC_ID_SMC, + AV_CODEC_ID_FLIC, + AV_CODEC_ID_TRUEMOTION1, + AV_CODEC_ID_VMDVIDEO, + AV_CODEC_ID_MSZH, + AV_CODEC_ID_ZLIB, + AV_CODEC_ID_QTRLE, + AV_CODEC_ID_TSCC, + AV_CODEC_ID_ULTI, + AV_CODEC_ID_QDRAW, + AV_CODEC_ID_VIXL, + AV_CODEC_ID_QPEG, + AV_CODEC_ID_PNG, + AV_CODEC_ID_PPM, + AV_CODEC_ID_PBM, + AV_CODEC_ID_PGM, + AV_CODEC_ID_PGMYUV, + AV_CODEC_ID_PAM, + AV_CODEC_ID_FFVHUFF, + AV_CODEC_ID_RV30, + AV_CODEC_ID_RV40, + AV_CODEC_ID_VC1, + AV_CODEC_ID_WMV3, + AV_CODEC_ID_LOCO, + AV_CODEC_ID_WNV1, + AV_CODEC_ID_AASC, + AV_CODEC_ID_INDEO2, + AV_CODEC_ID_FRAPS, + AV_CODEC_ID_TRUEMOTION2, + AV_CODEC_ID_BMP, + AV_CODEC_ID_CSCD, + AV_CODEC_ID_MMVIDEO, + AV_CODEC_ID_ZMBV, + AV_CODEC_ID_AVS, + AV_CODEC_ID_SMACKVIDEO, + AV_CODEC_ID_NUV, + AV_CODEC_ID_KMVC, + AV_CODEC_ID_FLASHSV, + AV_CODEC_ID_CAVS, + AV_CODEC_ID_JPEG2000, + AV_CODEC_ID_VMNC, + AV_CODEC_ID_VP5, + AV_CODEC_ID_VP6, + AV_CODEC_ID_VP6F, + AV_CODEC_ID_TARGA, + AV_CODEC_ID_DSICINVIDEO, + AV_CODEC_ID_TIERTEXSEQVIDEO, + AV_CODEC_ID_TIFF, + AV_CODEC_ID_GIF, + AV_CODEC_ID_DXA, + AV_CODEC_ID_DNXHD, + AV_CODEC_ID_THP, + AV_CODEC_ID_SGI, + AV_CODEC_ID_C93, + AV_CODEC_ID_BETHSOFTVID, + AV_CODEC_ID_PTX, + AV_CODEC_ID_TXD, + AV_CODEC_ID_VP6A, + AV_CODEC_ID_AMV, + AV_CODEC_ID_VB, + AV_CODEC_ID_PCX, + AV_CODEC_ID_SUNRAST, + AV_CODEC_ID_INDEO4, + AV_CODEC_ID_INDEO5, + AV_CODEC_ID_MIMIC, + AV_CODEC_ID_RL2, + AV_CODEC_ID_ESCAPE124, + AV_CODEC_ID_DIRAC, + AV_CODEC_ID_BFI, + AV_CODEC_ID_CMV, + AV_CODEC_ID_MOTIONPIXELS, + AV_CODEC_ID_TGV, + AV_CODEC_ID_TGQ, + AV_CODEC_ID_TQI, + AV_CODEC_ID_AURA, + AV_CODEC_ID_AURA2, + AV_CODEC_ID_V210X, + AV_CODEC_ID_TMV, + AV_CODEC_ID_V210, + AV_CODEC_ID_DPX, + AV_CODEC_ID_MAD, + AV_CODEC_ID_FRWU, + AV_CODEC_ID_FLASHSV2, + AV_CODEC_ID_CDGRAPHICS, + AV_CODEC_ID_R210, + AV_CODEC_ID_ANM, + AV_CODEC_ID_BINKVIDEO, + AV_CODEC_ID_IFF_ILBM, +#define AV_CODEC_ID_IFF_BYTERUN1 AV_CODEC_ID_IFF_ILBM + AV_CODEC_ID_KGV1, + AV_CODEC_ID_YOP, + AV_CODEC_ID_VP8, + AV_CODEC_ID_PICTOR, + AV_CODEC_ID_ANSI, + AV_CODEC_ID_A64_MULTI, + AV_CODEC_ID_A64_MULTI5, + AV_CODEC_ID_R10K, + AV_CODEC_ID_MXPEG, + AV_CODEC_ID_LAGARITH, + AV_CODEC_ID_PRORES, + AV_CODEC_ID_JV, + AV_CODEC_ID_DFA, + AV_CODEC_ID_WMV3IMAGE, + AV_CODEC_ID_VC1IMAGE, + AV_CODEC_ID_UTVIDEO, + AV_CODEC_ID_BMV_VIDEO, + AV_CODEC_ID_VBLE, + AV_CODEC_ID_DXTORY, + AV_CODEC_ID_V410, + AV_CODEC_ID_XWD, + AV_CODEC_ID_CDXL, + AV_CODEC_ID_XBM, + AV_CODEC_ID_ZEROCODEC, + AV_CODEC_ID_MSS1, + AV_CODEC_ID_MSA1, + AV_CODEC_ID_TSCC2, + AV_CODEC_ID_MTS2, + AV_CODEC_ID_CLLC, + AV_CODEC_ID_MSS2, + AV_CODEC_ID_VP9, + AV_CODEC_ID_AIC, + AV_CODEC_ID_ESCAPE130, + AV_CODEC_ID_G2M, + AV_CODEC_ID_WEBP, + AV_CODEC_ID_HNM4_VIDEO, + AV_CODEC_ID_HEVC, +#define AV_CODEC_ID_H265 AV_CODEC_ID_HEVC + AV_CODEC_ID_FIC, + AV_CODEC_ID_ALIAS_PIX, + AV_CODEC_ID_BRENDER_PIX, + AV_CODEC_ID_PAF_VIDEO, + AV_CODEC_ID_EXR, + AV_CODEC_ID_VP7, + AV_CODEC_ID_SANM, + AV_CODEC_ID_SGIRLE, + AV_CODEC_ID_MVC1, + AV_CODEC_ID_MVC2, + AV_CODEC_ID_HQX, + AV_CODEC_ID_TDSC, + AV_CODEC_ID_HQ_HQA, + AV_CODEC_ID_HAP, + AV_CODEC_ID_DDS, + AV_CODEC_ID_DXV, + AV_CODEC_ID_SCREENPRESSO, + AV_CODEC_ID_RSCC, + + AV_CODEC_ID_Y41P = 0x8000, + AV_CODEC_ID_AVRP, + AV_CODEC_ID_012V, + AV_CODEC_ID_AVUI, + AV_CODEC_ID_AYUV, + AV_CODEC_ID_TARGA_Y216, + AV_CODEC_ID_V308, + AV_CODEC_ID_V408, + AV_CODEC_ID_YUV4, + AV_CODEC_ID_AVRN, + AV_CODEC_ID_CPIA, + AV_CODEC_ID_XFACE, + AV_CODEC_ID_SNOW, + AV_CODEC_ID_SMVJPEG, + AV_CODEC_ID_APNG, + AV_CODEC_ID_DAALA, + AV_CODEC_ID_CFHD, + AV_CODEC_ID_TRUEMOTION2RT, + AV_CODEC_ID_M101, + AV_CODEC_ID_MAGICYUV, + AV_CODEC_ID_SHEERVIDEO, + AV_CODEC_ID_YLC, + AV_CODEC_ID_PSD, + AV_CODEC_ID_PIXLET, + AV_CODEC_ID_SPEEDHQ, + AV_CODEC_ID_FMVC, + AV_CODEC_ID_SCPR, + AV_CODEC_ID_CLEARVIDEO, + AV_CODEC_ID_XPM, + AV_CODEC_ID_AV1, + AV_CODEC_ID_BITPACKED, + AV_CODEC_ID_MSCC, + AV_CODEC_ID_SRGC, + AV_CODEC_ID_SVG, + AV_CODEC_ID_GDV, + AV_CODEC_ID_FITS, + + /* various PCM "codecs" */ + AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs + AV_CODEC_ID_PCM_S16LE = 0x10000, + AV_CODEC_ID_PCM_S16BE, + AV_CODEC_ID_PCM_U16LE, + AV_CODEC_ID_PCM_U16BE, + AV_CODEC_ID_PCM_S8, + AV_CODEC_ID_PCM_U8, + AV_CODEC_ID_PCM_MULAW, + AV_CODEC_ID_PCM_ALAW, + AV_CODEC_ID_PCM_S32LE, + AV_CODEC_ID_PCM_S32BE, + AV_CODEC_ID_PCM_U32LE, + AV_CODEC_ID_PCM_U32BE, + AV_CODEC_ID_PCM_S24LE, + AV_CODEC_ID_PCM_S24BE, + AV_CODEC_ID_PCM_U24LE, + AV_CODEC_ID_PCM_U24BE, + AV_CODEC_ID_PCM_S24DAUD, + AV_CODEC_ID_PCM_ZORK, + AV_CODEC_ID_PCM_S16LE_PLANAR, + AV_CODEC_ID_PCM_DVD, + AV_CODEC_ID_PCM_F32BE, + AV_CODEC_ID_PCM_F32LE, + AV_CODEC_ID_PCM_F64BE, + AV_CODEC_ID_PCM_F64LE, + AV_CODEC_ID_PCM_BLURAY, + AV_CODEC_ID_PCM_LXF, + AV_CODEC_ID_S302M, + AV_CODEC_ID_PCM_S8_PLANAR, + AV_CODEC_ID_PCM_S24LE_PLANAR, + AV_CODEC_ID_PCM_S32LE_PLANAR, + AV_CODEC_ID_PCM_S16BE_PLANAR, + + AV_CODEC_ID_PCM_S64LE = 0x10800, + AV_CODEC_ID_PCM_S64BE, + AV_CODEC_ID_PCM_F16LE, + AV_CODEC_ID_PCM_F24LE, + + /* various ADPCM codecs */ + AV_CODEC_ID_ADPCM_IMA_QT = 0x11000, + AV_CODEC_ID_ADPCM_IMA_WAV, + AV_CODEC_ID_ADPCM_IMA_DK3, + AV_CODEC_ID_ADPCM_IMA_DK4, + AV_CODEC_ID_ADPCM_IMA_WS, + AV_CODEC_ID_ADPCM_IMA_SMJPEG, + AV_CODEC_ID_ADPCM_MS, + AV_CODEC_ID_ADPCM_4XM, + AV_CODEC_ID_ADPCM_XA, + AV_CODEC_ID_ADPCM_ADX, + AV_CODEC_ID_ADPCM_EA, + AV_CODEC_ID_ADPCM_G726, + AV_CODEC_ID_ADPCM_CT, + AV_CODEC_ID_ADPCM_SWF, + AV_CODEC_ID_ADPCM_YAMAHA, + AV_CODEC_ID_ADPCM_SBPRO_4, + AV_CODEC_ID_ADPCM_SBPRO_3, + AV_CODEC_ID_ADPCM_SBPRO_2, + AV_CODEC_ID_ADPCM_THP, + AV_CODEC_ID_ADPCM_IMA_AMV, + AV_CODEC_ID_ADPCM_EA_R1, + AV_CODEC_ID_ADPCM_EA_R3, + AV_CODEC_ID_ADPCM_EA_R2, + AV_CODEC_ID_ADPCM_IMA_EA_SEAD, + AV_CODEC_ID_ADPCM_IMA_EA_EACS, + AV_CODEC_ID_ADPCM_EA_XAS, + AV_CODEC_ID_ADPCM_EA_MAXIS_XA, + AV_CODEC_ID_ADPCM_IMA_ISS, + AV_CODEC_ID_ADPCM_G722, + AV_CODEC_ID_ADPCM_IMA_APC, + AV_CODEC_ID_ADPCM_VIMA, +#if FF_API_VIMA_DECODER + AV_CODEC_ID_VIMA = AV_CODEC_ID_ADPCM_VIMA, +#endif + + AV_CODEC_ID_ADPCM_AFC = 0x11800, + AV_CODEC_ID_ADPCM_IMA_OKI, + AV_CODEC_ID_ADPCM_DTK, + AV_CODEC_ID_ADPCM_IMA_RAD, + AV_CODEC_ID_ADPCM_G726LE, + AV_CODEC_ID_ADPCM_THP_LE, + AV_CODEC_ID_ADPCM_PSX, + AV_CODEC_ID_ADPCM_AICA, + AV_CODEC_ID_ADPCM_IMA_DAT4, + AV_CODEC_ID_ADPCM_MTAF, + + /* AMR */ + AV_CODEC_ID_AMR_NB = 0x12000, + AV_CODEC_ID_AMR_WB, + + /* RealAudio codecs*/ + AV_CODEC_ID_RA_144 = 0x13000, + AV_CODEC_ID_RA_288, + + /* various DPCM codecs */ + AV_CODEC_ID_ROQ_DPCM = 0x14000, + AV_CODEC_ID_INTERPLAY_DPCM, + AV_CODEC_ID_XAN_DPCM, + AV_CODEC_ID_SOL_DPCM, + + AV_CODEC_ID_SDX2_DPCM = 0x14800, + AV_CODEC_ID_GREMLIN_DPCM, + + /* audio codecs */ + AV_CODEC_ID_MP2 = 0x15000, + AV_CODEC_ID_MP3, ///< preferred ID for decoding MPEG audio layer 1, 2 or 3 + AV_CODEC_ID_AAC, + AV_CODEC_ID_AC3, + AV_CODEC_ID_DTS, + AV_CODEC_ID_VORBIS, + AV_CODEC_ID_DVAUDIO, + AV_CODEC_ID_WMAV1, + AV_CODEC_ID_WMAV2, + AV_CODEC_ID_MACE3, + AV_CODEC_ID_MACE6, + AV_CODEC_ID_VMDAUDIO, + AV_CODEC_ID_FLAC, + AV_CODEC_ID_MP3ADU, + AV_CODEC_ID_MP3ON4, + AV_CODEC_ID_SHORTEN, + AV_CODEC_ID_ALAC, + AV_CODEC_ID_WESTWOOD_SND1, + AV_CODEC_ID_GSM, ///< as in Berlin toast format + AV_CODEC_ID_QDM2, + AV_CODEC_ID_COOK, + AV_CODEC_ID_TRUESPEECH, + AV_CODEC_ID_TTA, + AV_CODEC_ID_SMACKAUDIO, + AV_CODEC_ID_QCELP, + AV_CODEC_ID_WAVPACK, + AV_CODEC_ID_DSICINAUDIO, + AV_CODEC_ID_IMC, + AV_CODEC_ID_MUSEPACK7, + AV_CODEC_ID_MLP, + AV_CODEC_ID_GSM_MS, /* as found in WAV */ + AV_CODEC_ID_ATRAC3, +#if FF_API_VOXWARE + AV_CODEC_ID_VOXWARE, +#endif + AV_CODEC_ID_APE, + AV_CODEC_ID_NELLYMOSER, + AV_CODEC_ID_MUSEPACK8, + AV_CODEC_ID_SPEEX, + AV_CODEC_ID_WMAVOICE, + AV_CODEC_ID_WMAPRO, + AV_CODEC_ID_WMALOSSLESS, + AV_CODEC_ID_ATRAC3P, + AV_CODEC_ID_EAC3, + AV_CODEC_ID_SIPR, + AV_CODEC_ID_MP1, + AV_CODEC_ID_TWINVQ, + AV_CODEC_ID_TRUEHD, + AV_CODEC_ID_MP4ALS, + AV_CODEC_ID_ATRAC1, + AV_CODEC_ID_BINKAUDIO_RDFT, + AV_CODEC_ID_BINKAUDIO_DCT, + AV_CODEC_ID_AAC_LATM, + AV_CODEC_ID_QDMC, + AV_CODEC_ID_CELT, + AV_CODEC_ID_G723_1, + AV_CODEC_ID_G729, + AV_CODEC_ID_8SVX_EXP, + AV_CODEC_ID_8SVX_FIB, + AV_CODEC_ID_BMV_AUDIO, + AV_CODEC_ID_RALF, + AV_CODEC_ID_IAC, + AV_CODEC_ID_ILBC, + AV_CODEC_ID_OPUS, + AV_CODEC_ID_COMFORT_NOISE, + AV_CODEC_ID_TAK, + AV_CODEC_ID_METASOUND, + AV_CODEC_ID_PAF_AUDIO, + AV_CODEC_ID_ON2AVC, + AV_CODEC_ID_DSS_SP, + + AV_CODEC_ID_FFWAVESYNTH = 0x15800, + AV_CODEC_ID_SONIC, + AV_CODEC_ID_SONIC_LS, + AV_CODEC_ID_EVRC, + AV_CODEC_ID_SMV, + AV_CODEC_ID_DSD_LSBF, + AV_CODEC_ID_DSD_MSBF, + AV_CODEC_ID_DSD_LSBF_PLANAR, + AV_CODEC_ID_DSD_MSBF_PLANAR, + AV_CODEC_ID_4GV, + AV_CODEC_ID_INTERPLAY_ACM, + AV_CODEC_ID_XMA1, + AV_CODEC_ID_XMA2, + AV_CODEC_ID_DST, + AV_CODEC_ID_ATRAC3AL, + AV_CODEC_ID_ATRAC3PAL, + AV_CODEC_ID_DOLBY_E, + + /* subtitle codecs */ + AV_CODEC_ID_FIRST_SUBTITLE = 0x17000, ///< A dummy ID pointing at the start of subtitle codecs. + AV_CODEC_ID_DVD_SUBTITLE = 0x17000, + AV_CODEC_ID_DVB_SUBTITLE, + AV_CODEC_ID_TEXT, ///< raw UTF-8 text + AV_CODEC_ID_XSUB, + AV_CODEC_ID_SSA, + AV_CODEC_ID_MOV_TEXT, + AV_CODEC_ID_HDMV_PGS_SUBTITLE, + AV_CODEC_ID_DVB_TELETEXT, + AV_CODEC_ID_SRT, + + AV_CODEC_ID_MICRODVD = 0x17800, + AV_CODEC_ID_EIA_608, + AV_CODEC_ID_JACOSUB, + AV_CODEC_ID_SAMI, + AV_CODEC_ID_REALTEXT, + AV_CODEC_ID_STL, + AV_CODEC_ID_SUBVIEWER1, + AV_CODEC_ID_SUBVIEWER, + AV_CODEC_ID_SUBRIP, + AV_CODEC_ID_WEBVTT, + AV_CODEC_ID_MPL2, + AV_CODEC_ID_VPLAYER, + AV_CODEC_ID_PJS, + AV_CODEC_ID_ASS, + AV_CODEC_ID_HDMV_TEXT_SUBTITLE, + + /* other specific kind of codecs (generally used for attachments) */ + AV_CODEC_ID_FIRST_UNKNOWN = 0x18000, ///< A dummy ID pointing at the start of various fake codecs. + AV_CODEC_ID_TTF = 0x18000, + + AV_CODEC_ID_SCTE_35, ///< Contain timestamp estimated through PCR of program stream. + AV_CODEC_ID_BINTEXT = 0x18800, + AV_CODEC_ID_XBIN, + AV_CODEC_ID_IDF, + AV_CODEC_ID_OTF, + AV_CODEC_ID_SMPTE_KLV, + AV_CODEC_ID_DVD_NAV, + AV_CODEC_ID_TIMED_ID3, + AV_CODEC_ID_BIN_DATA, + + + AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it + + AV_CODEC_ID_MPEG2TS = 0x20000, /**< _FAKE_ codec to indicate a raw MPEG-2 TS + * stream (only used by libavformat) */ + AV_CODEC_ID_MPEG4SYSTEMS = 0x20001, /**< _FAKE_ codec to indicate a MPEG-4 Systems + * stream (only used by libavformat) */ + AV_CODEC_ID_FFMETADATA = 0x21000, ///< Dummy codec for streams containing only metadata information. + AV_CODEC_ID_WRAPPED_AVFRAME = 0x21001, ///< Passthrough codec, AVFrames wrapped in AVPacket +}; + +/** + * This struct describes the properties of a single codec described by an + * AVCodecID. + * @see avcodec_descriptor_get() + */ +typedef struct AVCodecDescriptor { + enum AVCodecID id; + enum AVMediaType type; + /** + * Name of the codec described by this descriptor. It is non-empty and + * unique for each codec descriptor. It should contain alphanumeric + * characters and '_' only. + */ + const char *name; + /** + * A more descriptive name for this codec. May be NULL. + */ + const char *long_name; + /** + * Codec properties, a combination of AV_CODEC_PROP_* flags. + */ + int props; + /** + * MIME type(s) associated with the codec. + * May be NULL; if not, a NULL-terminated array of MIME types. + * The first item is always non-NULL and is the preferred MIME type. + */ + const char *const *mime_types; + /** + * If non-NULL, an array of profiles recognized for this codec. + * Terminated with FF_PROFILE_UNKNOWN. + */ + const struct AVProfile *profiles; +} AVCodecDescriptor; + +/** + * Codec uses only intra compression. + * Video and audio codecs only. + */ +#define AV_CODEC_PROP_INTRA_ONLY (1 << 0) +/** + * Codec supports lossy compression. Audio and video codecs only. + * @note a codec may support both lossy and lossless + * compression modes + */ +#define AV_CODEC_PROP_LOSSY (1 << 1) +/** + * Codec supports lossless compression. Audio and video codecs only. + */ +#define AV_CODEC_PROP_LOSSLESS (1 << 2) +/** + * Codec supports frame reordering. That is, the coded order (the order in which + * the encoded packets are output by the encoders / stored / input to the + * decoders) may be different from the presentation order of the corresponding + * frames. + * + * For codecs that do not have this property set, PTS and DTS should always be + * equal. + */ +#define AV_CODEC_PROP_REORDER (1 << 3) +/** + * Subtitle codec is bitmap based + * Decoded AVSubtitle data can be read from the AVSubtitleRect->pict field. + */ +#define AV_CODEC_PROP_BITMAP_SUB (1 << 16) +/** + * Subtitle codec is text based. + * Decoded AVSubtitle data can be read from the AVSubtitleRect->ass field. + */ +#define AV_CODEC_PROP_TEXT_SUB (1 << 17) + +/** + * @ingroup lavc_decoding + * Required number of additionally allocated bytes at the end of the input bitstream for decoding. + * This is mainly needed because some optimized bitstream readers read + * 32 or 64 bit at once and could read over the end.
+ * Note: If the first 23 bits of the additional bytes are not 0, then damaged + * MPEG bitstreams could cause overread and segfault. + */ +#define AV_INPUT_BUFFER_PADDING_SIZE 32 + +/** + * @ingroup lavc_encoding + * minimum encoding buffer size + * Used to avoid some checks during header writing. + */ +#define AV_INPUT_BUFFER_MIN_SIZE 16384 + +#if FF_API_WITHOUT_PREFIX +/** + * @deprecated use AV_INPUT_BUFFER_PADDING_SIZE instead + */ +#define FF_INPUT_BUFFER_PADDING_SIZE 32 + +/** + * @deprecated use AV_INPUT_BUFFER_MIN_SIZE instead + */ +#define FF_MIN_BUFFER_SIZE 16384 +#endif /* FF_API_WITHOUT_PREFIX */ + +/** + * @ingroup lavc_encoding + * motion estimation type. + * @deprecated use codec private option instead + */ +#if FF_API_MOTION_EST +enum Motion_Est_ID { + ME_ZERO = 1, ///< no search, that is use 0,0 vector whenever one is needed + ME_FULL, + ME_LOG, + ME_PHODS, + ME_EPZS, ///< enhanced predictive zonal search + ME_X1, ///< reserved for experiments + ME_HEX, ///< hexagon based search + ME_UMH, ///< uneven multi-hexagon search + ME_TESA, ///< transformed exhaustive search algorithm + ME_ITER=50, ///< iterative search +}; +#endif + +/** + * @ingroup lavc_decoding + */ +enum AVDiscard{ + /* We leave some space between them for extensions (drop some + * keyframes for intra-only or drop just some bidir frames). */ + AVDISCARD_NONE =-16, ///< discard nothing + AVDISCARD_DEFAULT = 0, ///< discard useless packets like 0 size packets in avi + AVDISCARD_NONREF = 8, ///< discard all non reference + AVDISCARD_BIDIR = 16, ///< discard all bidirectional frames + AVDISCARD_NONINTRA= 24, ///< discard all non intra frames + AVDISCARD_NONKEY = 32, ///< discard all frames except keyframes + AVDISCARD_ALL = 48, ///< discard all +}; + +enum AVAudioServiceType { + AV_AUDIO_SERVICE_TYPE_MAIN = 0, + AV_AUDIO_SERVICE_TYPE_EFFECTS = 1, + AV_AUDIO_SERVICE_TYPE_VISUALLY_IMPAIRED = 2, + AV_AUDIO_SERVICE_TYPE_HEARING_IMPAIRED = 3, + AV_AUDIO_SERVICE_TYPE_DIALOGUE = 4, + AV_AUDIO_SERVICE_TYPE_COMMENTARY = 5, + AV_AUDIO_SERVICE_TYPE_EMERGENCY = 6, + AV_AUDIO_SERVICE_TYPE_VOICE_OVER = 7, + AV_AUDIO_SERVICE_TYPE_KARAOKE = 8, + AV_AUDIO_SERVICE_TYPE_NB , ///< Not part of ABI +}; + +/** + * @ingroup lavc_encoding + */ +typedef struct RcOverride{ + int start_frame; + int end_frame; + int qscale; // If this is 0 then quality_factor will be used instead. + float quality_factor; +} RcOverride; + +#if FF_API_MAX_BFRAMES +/** + * @deprecated there is no libavcodec-wide limit on the number of B-frames + */ +#define FF_MAX_B_FRAMES 16 +#endif + +/* encoding support + These flags can be passed in AVCodecContext.flags before initialization. + Note: Not everything is supported yet. +*/ + +/** + * Allow decoders to produce frames with data planes that are not aligned + * to CPU requirements (e.g. due to cropping). + */ +#define AV_CODEC_FLAG_UNALIGNED (1 << 0) +/** + * Use fixed qscale. + */ +#define AV_CODEC_FLAG_QSCALE (1 << 1) +/** + * 4 MV per MB allowed / advanced prediction for H.263. + */ +#define AV_CODEC_FLAG_4MV (1 << 2) +/** + * Output even those frames that might be corrupted. + */ +#define AV_CODEC_FLAG_OUTPUT_CORRUPT (1 << 3) +/** + * Use qpel MC. + */ +#define AV_CODEC_FLAG_QPEL (1 << 4) +/** + * Use internal 2pass ratecontrol in first pass mode. + */ +#define AV_CODEC_FLAG_PASS1 (1 << 9) +/** + * Use internal 2pass ratecontrol in second pass mode. + */ +#define AV_CODEC_FLAG_PASS2 (1 << 10) +/** + * loop filter. + */ +#define AV_CODEC_FLAG_LOOP_FILTER (1 << 11) +/** + * Only decode/encode grayscale. + */ +#define AV_CODEC_FLAG_GRAY (1 << 13) +/** + * error[?] variables will be set during encoding. + */ +#define AV_CODEC_FLAG_PSNR (1 << 15) +/** + * Input bitstream might be truncated at a random location + * instead of only at frame boundaries. + */ +#define AV_CODEC_FLAG_TRUNCATED (1 << 16) +/** + * Use interlaced DCT. + */ +#define AV_CODEC_FLAG_INTERLACED_DCT (1 << 18) +/** + * Force low delay. + */ +#define AV_CODEC_FLAG_LOW_DELAY (1 << 19) +/** + * Place global headers in extradata instead of every keyframe. + */ +#define AV_CODEC_FLAG_GLOBAL_HEADER (1 << 22) +/** + * Use only bitexact stuff (except (I)DCT). + */ +#define AV_CODEC_FLAG_BITEXACT (1 << 23) +/* Fx : Flag for H.263+ extra options */ +/** + * H.263 advanced intra coding / MPEG-4 AC prediction + */ +#define AV_CODEC_FLAG_AC_PRED (1 << 24) +/** + * interlaced motion estimation + */ +#define AV_CODEC_FLAG_INTERLACED_ME (1 << 29) +#define AV_CODEC_FLAG_CLOSED_GOP (1U << 31) + +/** + * Allow non spec compliant speedup tricks. + */ +#define AV_CODEC_FLAG2_FAST (1 << 0) +/** + * Skip bitstream encoding. + */ +#define AV_CODEC_FLAG2_NO_OUTPUT (1 << 2) +/** + * Place global headers at every keyframe instead of in extradata. + */ +#define AV_CODEC_FLAG2_LOCAL_HEADER (1 << 3) + +/** + * timecode is in drop frame format. DEPRECATED!!!! + */ +#define AV_CODEC_FLAG2_DROP_FRAME_TIMECODE (1 << 13) + +/** + * Input bitstream might be truncated at a packet boundaries + * instead of only at frame boundaries. + */ +#define AV_CODEC_FLAG2_CHUNKS (1 << 15) +/** + * Discard cropping information from SPS. + */ +#define AV_CODEC_FLAG2_IGNORE_CROP (1 << 16) + +/** + * Show all frames before the first keyframe + */ +#define AV_CODEC_FLAG2_SHOW_ALL (1 << 22) +/** + * Export motion vectors through frame side data + */ +#define AV_CODEC_FLAG2_EXPORT_MVS (1 << 28) +/** + * Do not skip samples and export skip information as frame side data + */ +#define AV_CODEC_FLAG2_SKIP_MANUAL (1 << 29) +/** + * Do not reset ASS ReadOrder field on flush (subtitles decoding) + */ +#define AV_CODEC_FLAG2_RO_FLUSH_NOOP (1 << 30) + +/* Unsupported options : + * Syntax Arithmetic coding (SAC) + * Reference Picture Selection + * Independent Segment Decoding */ +/* /Fx */ +/* codec capabilities */ + +/** + * Decoder can use draw_horiz_band callback. + */ +#define AV_CODEC_CAP_DRAW_HORIZ_BAND (1 << 0) +/** + * Codec uses get_buffer() for allocating buffers and supports custom allocators. + * If not set, it might not use get_buffer() at all or use operations that + * assume the buffer was allocated by avcodec_default_get_buffer. + */ +#define AV_CODEC_CAP_DR1 (1 << 1) +#define AV_CODEC_CAP_TRUNCATED (1 << 3) +/** + * Encoder or decoder requires flushing with NULL input at the end in order to + * give the complete and correct output. + * + * NOTE: If this flag is not set, the codec is guaranteed to never be fed with + * with NULL data. The user can still send NULL data to the public encode + * or decode function, but libavcodec will not pass it along to the codec + * unless this flag is set. + * + * Decoders: + * The decoder has a non-zero delay and needs to be fed with avpkt->data=NULL, + * avpkt->size=0 at the end to get the delayed data until the decoder no longer + * returns frames. + * + * Encoders: + * The encoder needs to be fed with NULL data at the end of encoding until the + * encoder no longer returns data. + * + * NOTE: For encoders implementing the AVCodec.encode2() function, setting this + * flag also means that the encoder must set the pts and duration for + * each output packet. If this flag is not set, the pts and duration will + * be determined by libavcodec from the input frame. + */ +#define AV_CODEC_CAP_DELAY (1 << 5) +/** + * Codec can be fed a final frame with a smaller size. + * This can be used to prevent truncation of the last audio samples. + */ +#define AV_CODEC_CAP_SMALL_LAST_FRAME (1 << 6) + +#if FF_API_CAP_VDPAU +/** + * Codec can export data for HW decoding (VDPAU). + */ +#define AV_CODEC_CAP_HWACCEL_VDPAU (1 << 7) +#endif + +/** + * Codec can output multiple frames per AVPacket + * Normally demuxers return one frame at a time, demuxers which do not do + * are connected to a parser to split what they return into proper frames. + * This flag is reserved to the very rare category of codecs which have a + * bitstream that cannot be split into frames without timeconsuming + * operations like full decoding. Demuxers carrying such bitstreams thus + * may return multiple frames in a packet. This has many disadvantages like + * prohibiting stream copy in many cases thus it should only be considered + * as a last resort. + */ +#define AV_CODEC_CAP_SUBFRAMES (1 << 8) +/** + * Codec is experimental and is thus avoided in favor of non experimental + * encoders + */ +#define AV_CODEC_CAP_EXPERIMENTAL (1 << 9) +/** + * Codec should fill in channel configuration and samplerate instead of container + */ +#define AV_CODEC_CAP_CHANNEL_CONF (1 << 10) +/** + * Codec supports frame-level multithreading. + */ +#define AV_CODEC_CAP_FRAME_THREADS (1 << 12) +/** + * Codec supports slice-based (or partition-based) multithreading. + */ +#define AV_CODEC_CAP_SLICE_THREADS (1 << 13) +/** + * Codec supports changed parameters at any point. + */ +#define AV_CODEC_CAP_PARAM_CHANGE (1 << 14) +/** + * Codec supports avctx->thread_count == 0 (auto). + */ +#define AV_CODEC_CAP_AUTO_THREADS (1 << 15) +/** + * Audio encoder supports receiving a different number of samples in each call. + */ +#define AV_CODEC_CAP_VARIABLE_FRAME_SIZE (1 << 16) +/** + * Decoder is not a preferred choice for probing. + * This indicates that the decoder is not a good choice for probing. + * It could for example be an expensive to spin up hardware decoder, + * or it could simply not provide a lot of useful information about + * the stream. + * A decoder marked with this flag should only be used as last resort + * choice for probing. + */ +#define AV_CODEC_CAP_AVOID_PROBING (1 << 17) +/** + * Codec is intra only. + */ +#define AV_CODEC_CAP_INTRA_ONLY 0x40000000 +/** + * Codec is lossless. + */ +#define AV_CODEC_CAP_LOSSLESS 0x80000000 + + +#if FF_API_WITHOUT_PREFIX +/** + * Allow decoders to produce frames with data planes that are not aligned + * to CPU requirements (e.g. due to cropping). + */ +#define CODEC_FLAG_UNALIGNED AV_CODEC_FLAG_UNALIGNED +#define CODEC_FLAG_QSCALE AV_CODEC_FLAG_QSCALE +#define CODEC_FLAG_4MV AV_CODEC_FLAG_4MV +#define CODEC_FLAG_OUTPUT_CORRUPT AV_CODEC_FLAG_OUTPUT_CORRUPT +#define CODEC_FLAG_QPEL AV_CODEC_FLAG_QPEL +#if FF_API_GMC +/** + * @deprecated use the "gmc" private option of the libxvid encoder + */ +#define CODEC_FLAG_GMC 0x0020 ///< Use GMC. +#endif +#if FF_API_MV0 +/** + * @deprecated use the flag "mv0" in the "mpv_flags" private option of the + * mpegvideo encoders + */ +#define CODEC_FLAG_MV0 0x0040 +#endif +#if FF_API_INPUT_PRESERVED +/** + * @deprecated passing reference-counted frames to the encoders replaces this + * flag + */ +#define CODEC_FLAG_INPUT_PRESERVED 0x0100 +#endif +#define CODEC_FLAG_PASS1 AV_CODEC_FLAG_PASS1 +#define CODEC_FLAG_PASS2 AV_CODEC_FLAG_PASS2 +#define CODEC_FLAG_GRAY AV_CODEC_FLAG_GRAY +#if FF_API_EMU_EDGE +/** + * @deprecated edges are not used/required anymore. I.e. this flag is now always + * set. + */ +#define CODEC_FLAG_EMU_EDGE 0x4000 +#endif +#define CODEC_FLAG_PSNR AV_CODEC_FLAG_PSNR +#define CODEC_FLAG_TRUNCATED AV_CODEC_FLAG_TRUNCATED + +#if FF_API_NORMALIZE_AQP +/** + * @deprecated use the flag "naq" in the "mpv_flags" private option of the + * mpegvideo encoders + */ +#define CODEC_FLAG_NORMALIZE_AQP 0x00020000 +#endif +#define CODEC_FLAG_INTERLACED_DCT AV_CODEC_FLAG_INTERLACED_DCT +#define CODEC_FLAG_LOW_DELAY AV_CODEC_FLAG_LOW_DELAY +#define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER +#define CODEC_FLAG_BITEXACT AV_CODEC_FLAG_BITEXACT +#define CODEC_FLAG_AC_PRED AV_CODEC_FLAG_AC_PRED +#define CODEC_FLAG_LOOP_FILTER AV_CODEC_FLAG_LOOP_FILTER +#define CODEC_FLAG_INTERLACED_ME AV_CODEC_FLAG_INTERLACED_ME +#define CODEC_FLAG_CLOSED_GOP AV_CODEC_FLAG_CLOSED_GOP +#define CODEC_FLAG2_FAST AV_CODEC_FLAG2_FAST +#define CODEC_FLAG2_NO_OUTPUT AV_CODEC_FLAG2_NO_OUTPUT +#define CODEC_FLAG2_LOCAL_HEADER AV_CODEC_FLAG2_LOCAL_HEADER +#define CODEC_FLAG2_DROP_FRAME_TIMECODE AV_CODEC_FLAG2_DROP_FRAME_TIMECODE +#define CODEC_FLAG2_IGNORE_CROP AV_CODEC_FLAG2_IGNORE_CROP + +#define CODEC_FLAG2_CHUNKS AV_CODEC_FLAG2_CHUNKS +#define CODEC_FLAG2_SHOW_ALL AV_CODEC_FLAG2_SHOW_ALL +#define CODEC_FLAG2_EXPORT_MVS AV_CODEC_FLAG2_EXPORT_MVS +#define CODEC_FLAG2_SKIP_MANUAL AV_CODEC_FLAG2_SKIP_MANUAL + +/* Unsupported options : + * Syntax Arithmetic coding (SAC) + * Reference Picture Selection + * Independent Segment Decoding */ +/* /Fx */ +/* codec capabilities */ + +#define CODEC_CAP_DRAW_HORIZ_BAND AV_CODEC_CAP_DRAW_HORIZ_BAND ///< Decoder can use draw_horiz_band callback. +/** + * Codec uses get_buffer() for allocating buffers and supports custom allocators. + * If not set, it might not use get_buffer() at all or use operations that + * assume the buffer was allocated by avcodec_default_get_buffer. + */ +#define CODEC_CAP_DR1 AV_CODEC_CAP_DR1 +#define CODEC_CAP_TRUNCATED AV_CODEC_CAP_TRUNCATED +#if FF_API_XVMC +/* Codec can export data for HW decoding. This flag indicates that + * the codec would call get_format() with list that might contain HW accelerated + * pixel formats (XvMC, VDPAU, VAAPI, etc). The application can pick any of them + * including raw image format. + * The application can use the passed context to determine bitstream version, + * chroma format, resolution etc. + */ +#define CODEC_CAP_HWACCEL 0x0010 +#endif /* FF_API_XVMC */ +/** + * Encoder or decoder requires flushing with NULL input at the end in order to + * give the complete and correct output. + * + * NOTE: If this flag is not set, the codec is guaranteed to never be fed with + * with NULL data. The user can still send NULL data to the public encode + * or decode function, but libavcodec will not pass it along to the codec + * unless this flag is set. + * + * Decoders: + * The decoder has a non-zero delay and needs to be fed with avpkt->data=NULL, + * avpkt->size=0 at the end to get the delayed data until the decoder no longer + * returns frames. + * + * Encoders: + * The encoder needs to be fed with NULL data at the end of encoding until the + * encoder no longer returns data. + * + * NOTE: For encoders implementing the AVCodec.encode2() function, setting this + * flag also means that the encoder must set the pts and duration for + * each output packet. If this flag is not set, the pts and duration will + * be determined by libavcodec from the input frame. + */ +#define CODEC_CAP_DELAY AV_CODEC_CAP_DELAY +/** + * Codec can be fed a final frame with a smaller size. + * This can be used to prevent truncation of the last audio samples. + */ +#define CODEC_CAP_SMALL_LAST_FRAME AV_CODEC_CAP_SMALL_LAST_FRAME +#if FF_API_CAP_VDPAU +/** + * Codec can export data for HW decoding (VDPAU). + */ +#define CODEC_CAP_HWACCEL_VDPAU AV_CODEC_CAP_HWACCEL_VDPAU +#endif +/** + * Codec can output multiple frames per AVPacket + * Normally demuxers return one frame at a time, demuxers which do not do + * are connected to a parser to split what they return into proper frames. + * This flag is reserved to the very rare category of codecs which have a + * bitstream that cannot be split into frames without timeconsuming + * operations like full decoding. Demuxers carrying such bitstreams thus + * may return multiple frames in a packet. This has many disadvantages like + * prohibiting stream copy in many cases thus it should only be considered + * as a last resort. + */ +#define CODEC_CAP_SUBFRAMES AV_CODEC_CAP_SUBFRAMES +/** + * Codec is experimental and is thus avoided in favor of non experimental + * encoders + */ +#define CODEC_CAP_EXPERIMENTAL AV_CODEC_CAP_EXPERIMENTAL +/** + * Codec should fill in channel configuration and samplerate instead of container + */ +#define CODEC_CAP_CHANNEL_CONF AV_CODEC_CAP_CHANNEL_CONF +#if FF_API_NEG_LINESIZES +/** + * @deprecated no codecs use this capability + */ +#define CODEC_CAP_NEG_LINESIZES 0x0800 +#endif +/** + * Codec supports frame-level multithreading. + */ +#define CODEC_CAP_FRAME_THREADS AV_CODEC_CAP_FRAME_THREADS +/** + * Codec supports slice-based (or partition-based) multithreading. + */ +#define CODEC_CAP_SLICE_THREADS AV_CODEC_CAP_SLICE_THREADS +/** + * Codec supports changed parameters at any point. + */ +#define CODEC_CAP_PARAM_CHANGE AV_CODEC_CAP_PARAM_CHANGE +/** + * Codec supports avctx->thread_count == 0 (auto). + */ +#define CODEC_CAP_AUTO_THREADS AV_CODEC_CAP_AUTO_THREADS +/** + * Audio encoder supports receiving a different number of samples in each call. + */ +#define CODEC_CAP_VARIABLE_FRAME_SIZE AV_CODEC_CAP_VARIABLE_FRAME_SIZE +/** + * Codec is intra only. + */ +#define CODEC_CAP_INTRA_ONLY AV_CODEC_CAP_INTRA_ONLY +/** + * Codec is lossless. + */ +#define CODEC_CAP_LOSSLESS AV_CODEC_CAP_LOSSLESS + +/** + * HWAccel is experimental and is thus avoided in favor of non experimental + * codecs + */ +#define HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200 +#endif /* FF_API_WITHOUT_PREFIX */ + +#if FF_API_MB_TYPE +//The following defines may change, don't expect compatibility if you use them. +#define MB_TYPE_INTRA4x4 0x0001 +#define MB_TYPE_INTRA16x16 0x0002 //FIXME H.264-specific +#define MB_TYPE_INTRA_PCM 0x0004 //FIXME H.264-specific +#define MB_TYPE_16x16 0x0008 +#define MB_TYPE_16x8 0x0010 +#define MB_TYPE_8x16 0x0020 +#define MB_TYPE_8x8 0x0040 +#define MB_TYPE_INTERLACED 0x0080 +#define MB_TYPE_DIRECT2 0x0100 //FIXME +#define MB_TYPE_ACPRED 0x0200 +#define MB_TYPE_GMC 0x0400 +#define MB_TYPE_SKIP 0x0800 +#define MB_TYPE_P0L0 0x1000 +#define MB_TYPE_P1L0 0x2000 +#define MB_TYPE_P0L1 0x4000 +#define MB_TYPE_P1L1 0x8000 +#define MB_TYPE_L0 (MB_TYPE_P0L0 | MB_TYPE_P1L0) +#define MB_TYPE_L1 (MB_TYPE_P0L1 | MB_TYPE_P1L1) +#define MB_TYPE_L0L1 (MB_TYPE_L0 | MB_TYPE_L1) +#define MB_TYPE_QUANT 0x00010000 +#define MB_TYPE_CBP 0x00020000 +// Note bits 24-31 are reserved for codec specific use (H.264 ref0, MPEG-1 0mv, ...) +#endif + +/** + * Pan Scan area. + * This specifies the area which should be displayed. + * Note there may be multiple such areas for one frame. + */ +typedef struct AVPanScan{ + /** + * id + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + int id; + + /** + * width and height in 1/16 pel + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + int width; + int height; + + /** + * position of the top left corner in 1/16 pel for up to 3 fields/frames + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + int16_t position[3][2]; +}AVPanScan; + +/** + * This structure describes the bitrate properties of an encoded bitstream. It + * roughly corresponds to a subset the VBV parameters for MPEG-2 or HRD + * parameters for H.264/HEVC. + */ +typedef struct AVCPBProperties { + /** + * Maximum bitrate of the stream, in bits per second. + * Zero if unknown or unspecified. + */ + int max_bitrate; + /** + * Minimum bitrate of the stream, in bits per second. + * Zero if unknown or unspecified. + */ + int min_bitrate; + /** + * Average bitrate of the stream, in bits per second. + * Zero if unknown or unspecified. + */ + int avg_bitrate; + + /** + * The size of the buffer to which the ratecontrol is applied, in bits. + * Zero if unknown or unspecified. + */ + int buffer_size; + + /** + * The delay between the time the packet this structure is associated with + * is received and the time when it should be decoded, in periods of a 27MHz + * clock. + * + * UINT64_MAX when unknown or unspecified. + */ + uint64_t vbv_delay; +} AVCPBProperties; + +#if FF_API_QSCALE_TYPE +#define FF_QSCALE_TYPE_MPEG1 0 +#define FF_QSCALE_TYPE_MPEG2 1 +#define FF_QSCALE_TYPE_H264 2 +#define FF_QSCALE_TYPE_VP56 3 +#endif + +/** + * The decoder will keep a reference to the frame and may reuse it later. + */ +#define AV_GET_BUFFER_FLAG_REF (1 << 0) + +/** + * @defgroup lavc_packet AVPacket + * + * Types and functions for working with AVPacket. + * @{ + */ +enum AVPacketSideDataType { + /** + * An AV_PKT_DATA_PALETTE side data packet contains exactly AVPALETTE_SIZE + * bytes worth of palette. This side data signals that a new palette is + * present. + */ + AV_PKT_DATA_PALETTE, + + /** + * The AV_PKT_DATA_NEW_EXTRADATA is used to notify the codec or the format + * that the extradata buffer was changed and the receiving side should + * act upon it appropriately. The new extradata is embedded in the side + * data buffer and should be immediately used for processing the current + * frame or packet. + */ + AV_PKT_DATA_NEW_EXTRADATA, + + /** + * An AV_PKT_DATA_PARAM_CHANGE side data packet is laid out as follows: + * @code + * u32le param_flags + * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT) + * s32le channel_count + * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT) + * u64le channel_layout + * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE) + * s32le sample_rate + * if (param_flags & AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS) + * s32le width + * s32le height + * @endcode + */ + AV_PKT_DATA_PARAM_CHANGE, + + /** + * An AV_PKT_DATA_H263_MB_INFO side data packet contains a number of + * structures with info about macroblocks relevant to splitting the + * packet into smaller packets on macroblock edges (e.g. as for RFC 2190). + * That is, it does not necessarily contain info about all macroblocks, + * as long as the distance between macroblocks in the info is smaller + * than the target payload size. + * Each MB info structure is 12 bytes, and is laid out as follows: + * @code + * u32le bit offset from the start of the packet + * u8 current quantizer at the start of the macroblock + * u8 GOB number + * u16le macroblock address within the GOB + * u8 horizontal MV predictor + * u8 vertical MV predictor + * u8 horizontal MV predictor for block number 3 + * u8 vertical MV predictor for block number 3 + * @endcode + */ + AV_PKT_DATA_H263_MB_INFO, + + /** + * This side data should be associated with an audio stream and contains + * ReplayGain information in form of the AVReplayGain struct. + */ + AV_PKT_DATA_REPLAYGAIN, + + /** + * This side data contains a 3x3 transformation matrix describing an affine + * transformation that needs to be applied to the decoded video frames for + * correct presentation. + * + * See libavutil/display.h for a detailed description of the data. + */ + AV_PKT_DATA_DISPLAYMATRIX, + + /** + * This side data should be associated with a video stream and contains + * Stereoscopic 3D information in form of the AVStereo3D struct. + */ + AV_PKT_DATA_STEREO3D, + + /** + * This side data should be associated with an audio stream and corresponds + * to enum AVAudioServiceType. + */ + AV_PKT_DATA_AUDIO_SERVICE_TYPE, + + /** + * This side data contains quality related information from the encoder. + * @code + * u32le quality factor of the compressed frame. Allowed range is between 1 (good) and FF_LAMBDA_MAX (bad). + * u8 picture type + * u8 error count + * u16 reserved + * u64le[error count] sum of squared differences between encoder in and output + * @endcode + */ + AV_PKT_DATA_QUALITY_STATS, + + /** + * This side data contains an integer value representing the stream index + * of a "fallback" track. A fallback track indicates an alternate + * track to use when the current track can not be decoded for some reason. + * e.g. no decoder available for codec. + */ + AV_PKT_DATA_FALLBACK_TRACK, + + /** + * This side data corresponds to the AVCPBProperties struct. + */ + AV_PKT_DATA_CPB_PROPERTIES, + + /** + * Recommmends skipping the specified number of samples + * @code + * u32le number of samples to skip from start of this packet + * u32le number of samples to skip from end of this packet + * u8 reason for start skip + * u8 reason for end skip (0=padding silence, 1=convergence) + * @endcode + */ + AV_PKT_DATA_SKIP_SAMPLES=70, + + /** + * An AV_PKT_DATA_JP_DUALMONO side data packet indicates that + * the packet may contain "dual mono" audio specific to Japanese DTV + * and if it is true, recommends only the selected channel to be used. + * @code + * u8 selected channels (0=mail/left, 1=sub/right, 2=both) + * @endcode + */ + AV_PKT_DATA_JP_DUALMONO, + + /** + * A list of zero terminated key/value strings. There is no end marker for + * the list, so it is required to rely on the side data size to stop. + */ + AV_PKT_DATA_STRINGS_METADATA, + + /** + * Subtitle event position + * @code + * u32le x1 + * u32le y1 + * u32le x2 + * u32le y2 + * @endcode + */ + AV_PKT_DATA_SUBTITLE_POSITION, + + /** + * Data found in BlockAdditional element of matroska container. There is + * no end marker for the data, so it is required to rely on the side data + * size to recognize the end. 8 byte id (as found in BlockAddId) followed + * by data. + */ + AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, + + /** + * The optional first identifier line of a WebVTT cue. + */ + AV_PKT_DATA_WEBVTT_IDENTIFIER, + + /** + * The optional settings (rendering instructions) that immediately + * follow the timestamp specifier of a WebVTT cue. + */ + AV_PKT_DATA_WEBVTT_SETTINGS, + + /** + * A list of zero terminated key/value strings. There is no end marker for + * the list, so it is required to rely on the side data size to stop. This + * side data includes updated metadata which appeared in the stream. + */ + AV_PKT_DATA_METADATA_UPDATE, + + /** + * MPEGTS stream ID, this is required to pass the stream ID + * information from the demuxer to the corresponding muxer. + */ + AV_PKT_DATA_MPEGTS_STREAM_ID, + + /** + * Mastering display metadata (based on SMPTE-2086:2014). This metadata + * should be associated with a video stream and contains data in the form + * of the AVMasteringDisplayMetadata struct. + */ + AV_PKT_DATA_MASTERING_DISPLAY_METADATA, + + /** + * This side data should be associated with a video stream and corresponds + * to the AVSphericalMapping structure. + */ + AV_PKT_DATA_SPHERICAL, + + /** + * Content light level (based on CTA-861.3). This metadata should be + * associated with a video stream and contains data in the form of the + * AVContentLightMetadata struct. + */ + AV_PKT_DATA_CONTENT_LIGHT_LEVEL, + + /** + * ATSC A53 Part 4 Closed Captions. This metadata should be associated with + * a video stream. A53 CC bitstream is stored as uint8_t in AVPacketSideData.data. + * The number of bytes of CC data is AVPacketSideData.size. + */ + AV_PKT_DATA_A53_CC, + + /** + * The number of side data elements (in fact a bit more than it). + * This is not part of the public API/ABI in the sense that it may + * change when new side data types are added. + * This must stay the last enum value. + * If its value becomes huge, some code using it + * needs to be updated as it assumes it to be smaller than other limits. + */ + AV_PKT_DATA_NB +}; + +#define AV_PKT_DATA_QUALITY_FACTOR AV_PKT_DATA_QUALITY_STATS //DEPRECATED + +typedef struct AVPacketSideData { + uint8_t *data; + int size; + enum AVPacketSideDataType type; +} AVPacketSideData; + +/** + * This structure stores compressed data. It is typically exported by demuxers + * and then passed as input to decoders, or received as output from encoders and + * then passed to muxers. + * + * For video, it should typically contain one compressed frame. For audio it may + * contain several compressed frames. Encoders are allowed to output empty + * packets, with no compressed data, containing only side data + * (e.g. to update some stream parameters at the end of encoding). + * + * AVPacket is one of the few structs in FFmpeg, whose size is a part of public + * ABI. Thus it may be allocated on stack and no new fields can be added to it + * without libavcodec and libavformat major bump. + * + * The semantics of data ownership depends on the buf field. + * If it is set, the packet data is dynamically allocated and is + * valid indefinitely until a call to av_packet_unref() reduces the + * reference count to 0. + * + * If the buf field is not set av_packet_ref() would make a copy instead + * of increasing the reference count. + * + * The side data is always allocated with av_malloc(), copied by + * av_packet_ref() and freed by av_packet_unref(). + * + * @see av_packet_ref + * @see av_packet_unref + */ +typedef struct AVPacket { + /** + * A reference to the reference-counted buffer where the packet data is + * stored. + * May be NULL, then the packet data is not reference-counted. + */ + AVBufferRef *buf; + /** + * Presentation timestamp in AVStream->time_base units; the time at which + * the decompressed packet will be presented to the user. + * Can be AV_NOPTS_VALUE if it is not stored in the file. + * pts MUST be larger or equal to dts as presentation cannot happen before + * decompression, unless one wants to view hex dumps. Some formats misuse + * the terms dts and pts/cts to mean something different. Such timestamps + * must be converted to true pts/dts before they are stored in AVPacket. + */ + int64_t pts; + /** + * Decompression timestamp in AVStream->time_base units; the time at which + * the packet is decompressed. + * Can be AV_NOPTS_VALUE if it is not stored in the file. + */ + int64_t dts; + uint8_t *data; + int size; + int stream_index; + /** + * A combination of AV_PKT_FLAG values + */ + int flags; + /** + * Additional packet data that can be provided by the container. + * Packet can contain several types of side information. + */ + AVPacketSideData *side_data; + int side_data_elems; + + /** + * Duration of this packet in AVStream->time_base units, 0 if unknown. + * Equals next_pts - this_pts in presentation order. + */ + int64_t duration; + + int64_t pos; ///< byte position in stream, -1 if unknown + +#if FF_API_CONVERGENCE_DURATION + /** + * @deprecated Same as the duration field, but as int64_t. This was required + * for Matroska subtitles, whose duration values could overflow when the + * duration field was still an int. + */ + attribute_deprecated + int64_t convergence_duration; +#endif +} AVPacket; +#define AV_PKT_FLAG_KEY 0x0001 ///< The packet contains a keyframe +#define AV_PKT_FLAG_CORRUPT 0x0002 ///< The packet content is corrupted +/** + * Flag is used to discard packets which are required to maintain valid + * decoder state but are not required for output and should be dropped + * after decoding. + **/ +#define AV_PKT_FLAG_DISCARD 0x0004 +/** + * The packet comes from a trusted source. + * + * Otherwise-unsafe constructs such as arbitrary pointers to data + * outside the packet may be followed. + */ +#define AV_PKT_FLAG_TRUSTED 0x0008 + +enum AVSideDataParamChangeFlags { + AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_COUNT = 0x0001, + AV_SIDE_DATA_PARAM_CHANGE_CHANNEL_LAYOUT = 0x0002, + AV_SIDE_DATA_PARAM_CHANGE_SAMPLE_RATE = 0x0004, + AV_SIDE_DATA_PARAM_CHANGE_DIMENSIONS = 0x0008, +}; +/** + * @} + */ + +struct AVCodecInternal; + +enum AVFieldOrder { + AV_FIELD_UNKNOWN, + AV_FIELD_PROGRESSIVE, + AV_FIELD_TT, //< Top coded_first, top displayed first + AV_FIELD_BB, //< Bottom coded first, bottom displayed first + AV_FIELD_TB, //< Top coded first, bottom displayed first + AV_FIELD_BT, //< Bottom coded first, top displayed first +}; + +/** + * main external API structure. + * New fields can be added to the end with minor version bumps. + * Removal, reordering and changes to existing fields require a major + * version bump. + * You can use AVOptions (av_opt* / av_set/get*()) to access these fields from user + * applications. + * The name string for AVOptions options matches the associated command line + * parameter name and can be found in libavcodec/options_table.h + * The AVOption/command line parameter names differ in some cases from the C + * structure field names for historic reasons or brevity. + * sizeof(AVCodecContext) must not be used outside libav*. + */ +typedef struct AVCodecContext { + /** + * information on struct for av_log + * - set by avcodec_alloc_context3 + */ + const AVClass *av_class; + int log_level_offset; + + enum AVMediaType codec_type; /* see AVMEDIA_TYPE_xxx */ + const struct AVCodec *codec; +#if FF_API_CODEC_NAME + /** + * @deprecated this field is not used for anything in libavcodec + */ + attribute_deprecated + char codec_name[32]; +#endif + enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */ + + /** + * fourcc (LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). + * This is used to work around some encoder bugs. + * A demuxer should set this to what is stored in the field used to identify the codec. + * If there are multiple such fields in a container then the demuxer should choose the one + * which maximizes the information about the used codec. + * If the codec tag field in a container is larger than 32 bits then the demuxer should + * remap the longer ID to 32 bits with a table or other structure. Alternatively a new + * extra_codec_tag + size could be added but for this a clear advantage must be demonstrated + * first. + * - encoding: Set by user, if not then the default based on codec_id will be used. + * - decoding: Set by user, will be converted to uppercase by libavcodec during init. + */ + unsigned int codec_tag; + +#if FF_API_STREAM_CODEC_TAG + /** + * @deprecated this field is unused + */ + attribute_deprecated + unsigned int stream_codec_tag; +#endif + + void *priv_data; + + /** + * Private context used for internal data. + * + * Unlike priv_data, this is not codec-specific. It is used in general + * libavcodec functions. + */ + struct AVCodecInternal *internal; + + /** + * Private data of the user, can be used to carry app specific stuff. + * - encoding: Set by user. + * - decoding: Set by user. + */ + void *opaque; + + /** + * the average bitrate + * - encoding: Set by user; unused for constant quantizer encoding. + * - decoding: Set by user, may be overwritten by libavcodec + * if this info is available in the stream + */ + int64_t bit_rate; + + /** + * number of bits the bitstream is allowed to diverge from the reference. + * the reference can be CBR (for CBR pass1) or VBR (for pass2) + * - encoding: Set by user; unused for constant quantizer encoding. + * - decoding: unused + */ + int bit_rate_tolerance; + + /** + * Global quality for codecs which cannot change it per frame. + * This should be proportional to MPEG-1/2/4 qscale. + * - encoding: Set by user. + * - decoding: unused + */ + int global_quality; + + /** + * - encoding: Set by user. + * - decoding: unused + */ + int compression_level; +#define FF_COMPRESSION_DEFAULT -1 + + /** + * AV_CODEC_FLAG_*. + * - encoding: Set by user. + * - decoding: Set by user. + */ + int flags; + + /** + * AV_CODEC_FLAG2_* + * - encoding: Set by user. + * - decoding: Set by user. + */ + int flags2; + + /** + * some codecs need / can use extradata like Huffman tables. + * MJPEG: Huffman tables + * rv10: additional flags + * MPEG-4: global headers (they can be in the bitstream or here) + * The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger + * than extradata_size to avoid problems if it is read with the bitstream reader. + * The bytewise contents of extradata must not depend on the architecture or CPU endianness. + * - encoding: Set/allocated/freed by libavcodec. + * - decoding: Set/allocated/freed by user. + */ + uint8_t *extradata; + int extradata_size; + + /** + * This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identically 1. + * This often, but not always is the inverse of the frame rate or field rate + * for video. 1/time_base is not the average frame rate if the frame rate is not + * constant. + * + * Like containers, elementary streams also can store timestamps, 1/time_base + * is the unit in which these timestamps are specified. + * As example of such codec time base see ISO/IEC 14496-2:2001(E) + * vop_time_increment_resolution and fixed_vop_rate + * (fixed_vop_rate == 0 implies that it is different from the framerate) + * + * - encoding: MUST be set by user. + * - decoding: the use of this field for decoding is deprecated. + * Use framerate instead. + */ + AVRational time_base; + + /** + * For some codecs, the time base is closer to the field rate than the frame rate. + * Most notably, H.264 and MPEG-2 specify time_base as half of frame duration + * if no telecine is used ... + * + * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. + */ + int ticks_per_frame; + + /** + * Codec delay. + * + * Encoding: Number of frames delay there will be from the encoder input to + * the decoder output. (we assume the decoder matches the spec) + * Decoding: Number of frames delay in addition to what a standard decoder + * as specified in the spec would produce. + * + * Video: + * Number of frames the decoded output will be delayed relative to the + * encoded input. + * + * Audio: + * For encoding, this field is unused (see initial_padding). + * + * For decoding, this is the number of samples the decoder needs to + * output before the decoder's output is valid. When seeking, you should + * start decoding this many samples prior to your desired seek point. + * + * - encoding: Set by libavcodec. + * - decoding: Set by libavcodec. + */ + int delay; + + + /* video only */ + /** + * picture width / height. + * + * @note Those fields may not match the values of the last + * AVFrame output by avcodec_decode_video2 due frame + * reordering. + * + * - encoding: MUST be set by user. + * - decoding: May be set by the user before opening the decoder if known e.g. + * from the container. Some decoders will require the dimensions + * to be set by the caller. During decoding, the decoder may + * overwrite those values as required while parsing the data. + */ + int width, height; + + /** + * Bitstream width / height, may be different from width/height e.g. when + * the decoded frame is cropped before being output or lowres is enabled. + * + * @note Those field may not match the value of the last + * AVFrame output by avcodec_receive_frame() due frame + * reordering. + * + * - encoding: unused + * - decoding: May be set by the user before opening the decoder if known + * e.g. from the container. During decoding, the decoder may + * overwrite those values as required while parsing the data. + */ + int coded_width, coded_height; + +#if FF_API_ASPECT_EXTENDED +#define FF_ASPECT_EXTENDED 15 +#endif + + /** + * the number of pictures in a group of pictures, or 0 for intra_only + * - encoding: Set by user. + * - decoding: unused + */ + int gop_size; + + /** + * Pixel format, see AV_PIX_FMT_xxx. + * May be set by the demuxer if known from headers. + * May be overridden by the decoder if it knows better. + * + * @note This field may not match the value of the last + * AVFrame output by avcodec_receive_frame() due frame + * reordering. + * + * - encoding: Set by user. + * - decoding: Set by user if known, overridden by libavcodec while + * parsing the data. + */ + enum AVPixelFormat pix_fmt; + +#if FF_API_MOTION_EST + /** + * This option does nothing + * @deprecated use codec private options instead + */ + attribute_deprecated int me_method; +#endif + + /** + * If non NULL, 'draw_horiz_band' is called by the libavcodec + * decoder to draw a horizontal band. It improves cache usage. Not + * all codecs can do that. You must check the codec capabilities + * beforehand. + * When multithreading is used, it may be called from multiple threads + * at the same time; threads might draw different parts of the same AVFrame, + * or multiple AVFrames, and there is no guarantee that slices will be drawn + * in order. + * The function is also used by hardware acceleration APIs. + * It is called at least once during frame decoding to pass + * the data needed for hardware render. + * In that mode instead of pixel data, AVFrame points to + * a structure specific to the acceleration API. The application + * reads the structure and can change some fields to indicate progress + * or mark state. + * - encoding: unused + * - decoding: Set by user. + * @param height the height of the slice + * @param y the y position of the slice + * @param type 1->top field, 2->bottom field, 3->frame + * @param offset offset into the AVFrame.data from which the slice should be read + */ + void (*draw_horiz_band)(struct AVCodecContext *s, + const AVFrame *src, int offset[AV_NUM_DATA_POINTERS], + int y, int type, int height); + + /** + * callback to negotiate the pixelFormat + * @param fmt is the list of formats which are supported by the codec, + * it is terminated by -1 as 0 is a valid format, the formats are ordered by quality. + * The first is always the native one. + * @note The callback may be called again immediately if initialization for + * the selected (hardware-accelerated) pixel format failed. + * @warning Behavior is undefined if the callback returns a value not + * in the fmt list of formats. + * @return the chosen format + * - encoding: unused + * - decoding: Set by user, if not set the native format will be chosen. + */ + enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt); + + /** + * maximum number of B-frames between non-B-frames + * Note: The output will be delayed by max_b_frames+1 relative to the input. + * - encoding: Set by user. + * - decoding: unused + */ + int max_b_frames; + + /** + * qscale factor between IP and B-frames + * If > 0 then the last P-frame quantizer will be used (q= lastp_q*factor+offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + * - encoding: Set by user. + * - decoding: unused + */ + float b_quant_factor; + +#if FF_API_RC_STRATEGY + /** @deprecated use codec private option instead */ + attribute_deprecated int rc_strategy; +#define FF_RC_STRATEGY_XVID 1 +#endif + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int b_frame_strategy; +#endif + + /** + * qscale offset between IP and B-frames + * - encoding: Set by user. + * - decoding: unused + */ + float b_quant_offset; + + /** + * Size of the frame reordering buffer in the decoder. + * For MPEG-2 it is 1 IPB or 0 low delay IP. + * - encoding: Set by libavcodec. + * - decoding: Set by libavcodec. + */ + int has_b_frames; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int mpeg_quant; +#endif + + /** + * qscale factor between P- and I-frames + * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor + offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + * - encoding: Set by user. + * - decoding: unused + */ + float i_quant_factor; + + /** + * qscale offset between P and I-frames + * - encoding: Set by user. + * - decoding: unused + */ + float i_quant_offset; + + /** + * luminance masking (0-> disabled) + * - encoding: Set by user. + * - decoding: unused + */ + float lumi_masking; + + /** + * temporary complexity masking (0-> disabled) + * - encoding: Set by user. + * - decoding: unused + */ + float temporal_cplx_masking; + + /** + * spatial complexity masking (0-> disabled) + * - encoding: Set by user. + * - decoding: unused + */ + float spatial_cplx_masking; + + /** + * p block masking (0-> disabled) + * - encoding: Set by user. + * - decoding: unused + */ + float p_masking; + + /** + * darkness masking (0-> disabled) + * - encoding: Set by user. + * - decoding: unused + */ + float dark_masking; + + /** + * slice count + * - encoding: Set by libavcodec. + * - decoding: Set by user (or 0). + */ + int slice_count; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int prediction_method; +#define FF_PRED_LEFT 0 +#define FF_PRED_PLANE 1 +#define FF_PRED_MEDIAN 2 +#endif + + /** + * slice offsets in the frame in bytes + * - encoding: Set/allocated by libavcodec. + * - decoding: Set/allocated by user (or NULL). + */ + int *slice_offset; + + /** + * sample aspect ratio (0 if unknown) + * That is the width of a pixel divided by the height of the pixel. + * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + AVRational sample_aspect_ratio; + + /** + * motion estimation comparison function + * - encoding: Set by user. + * - decoding: unused + */ + int me_cmp; + /** + * subpixel motion estimation comparison function + * - encoding: Set by user. + * - decoding: unused + */ + int me_sub_cmp; + /** + * macroblock comparison function (not supported yet) + * - encoding: Set by user. + * - decoding: unused + */ + int mb_cmp; + /** + * interlaced DCT comparison function + * - encoding: Set by user. + * - decoding: unused + */ + int ildct_cmp; +#define FF_CMP_SAD 0 +#define FF_CMP_SSE 1 +#define FF_CMP_SATD 2 +#define FF_CMP_DCT 3 +#define FF_CMP_PSNR 4 +#define FF_CMP_BIT 5 +#define FF_CMP_RD 6 +#define FF_CMP_ZERO 7 +#define FF_CMP_VSAD 8 +#define FF_CMP_VSSE 9 +#define FF_CMP_NSSE 10 +#define FF_CMP_W53 11 +#define FF_CMP_W97 12 +#define FF_CMP_DCTMAX 13 +#define FF_CMP_DCT264 14 +#define FF_CMP_MEDIAN_SAD 15 +#define FF_CMP_CHROMA 256 + + /** + * ME diamond size & shape + * - encoding: Set by user. + * - decoding: unused + */ + int dia_size; + + /** + * amount of previous MV predictors (2a+1 x 2a+1 square) + * - encoding: Set by user. + * - decoding: unused + */ + int last_predictor_count; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int pre_me; +#endif + + /** + * motion estimation prepass comparison function + * - encoding: Set by user. + * - decoding: unused + */ + int me_pre_cmp; + + /** + * ME prepass diamond size & shape + * - encoding: Set by user. + * - decoding: unused + */ + int pre_dia_size; + + /** + * subpel ME quality + * - encoding: Set by user. + * - decoding: unused + */ + int me_subpel_quality; + +#if FF_API_AFD + /** + * DTG active format information (additional aspect ratio + * information only used in DVB MPEG-2 transport streams) + * 0 if not set. + * + * - encoding: unused + * - decoding: Set by decoder. + * @deprecated Deprecated in favor of AVSideData + */ + attribute_deprecated int dtg_active_format; +#define FF_DTG_AFD_SAME 8 +#define FF_DTG_AFD_4_3 9 +#define FF_DTG_AFD_16_9 10 +#define FF_DTG_AFD_14_9 11 +#define FF_DTG_AFD_4_3_SP_14_9 13 +#define FF_DTG_AFD_16_9_SP_14_9 14 +#define FF_DTG_AFD_SP_4_3 15 +#endif /* FF_API_AFD */ + + /** + * maximum motion estimation search range in subpel units + * If 0 then no limit. + * + * - encoding: Set by user. + * - decoding: unused + */ + int me_range; + +#if FF_API_QUANT_BIAS + /** + * @deprecated use encoder private option instead + */ + attribute_deprecated int intra_quant_bias; +#define FF_DEFAULT_QUANT_BIAS 999999 + + /** + * @deprecated use encoder private option instead + */ + attribute_deprecated int inter_quant_bias; +#endif + + /** + * slice flags + * - encoding: unused + * - decoding: Set by user. + */ + int slice_flags; +#define SLICE_FLAG_CODED_ORDER 0x0001 ///< draw_horiz_band() is called in coded order instead of display +#define SLICE_FLAG_ALLOW_FIELD 0x0002 ///< allow draw_horiz_band() with field slices (MPEG-2 field pics) +#define SLICE_FLAG_ALLOW_PLANE 0x0004 ///< allow draw_horiz_band() with 1 component at a time (SVQ1) + +#if FF_API_XVMC + /** + * XVideo Motion Acceleration + * - encoding: forbidden + * - decoding: set by decoder + * @deprecated XvMC doesn't need it anymore. + */ + attribute_deprecated int xvmc_acceleration; +#endif /* FF_API_XVMC */ + + /** + * macroblock decision mode + * - encoding: Set by user. + * - decoding: unused + */ + int mb_decision; +#define FF_MB_DECISION_SIMPLE 0 ///< uses mb_cmp +#define FF_MB_DECISION_BITS 1 ///< chooses the one which needs the fewest bits +#define FF_MB_DECISION_RD 2 ///< rate distortion + + /** + * custom intra quantization matrix + * - encoding: Set by user, can be NULL. + * - decoding: Set by libavcodec. + */ + uint16_t *intra_matrix; + + /** + * custom inter quantization matrix + * - encoding: Set by user, can be NULL. + * - decoding: Set by libavcodec. + */ + uint16_t *inter_matrix; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int scenechange_threshold; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int noise_reduction; +#endif + +#if FF_API_MPV_OPT + /** + * @deprecated this field is unused + */ + attribute_deprecated + int me_threshold; + + /** + * @deprecated this field is unused + */ + attribute_deprecated + int mb_threshold; +#endif + + /** + * precision of the intra DC coefficient - 8 + * - encoding: Set by user. + * - decoding: Set by libavcodec + */ + int intra_dc_precision; + + /** + * Number of macroblock rows at the top which are skipped. + * - encoding: unused + * - decoding: Set by user. + */ + int skip_top; + + /** + * Number of macroblock rows at the bottom which are skipped. + * - encoding: unused + * - decoding: Set by user. + */ + int skip_bottom; + +#if FF_API_MPV_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + float border_masking; +#endif + + /** + * minimum MB Lagrange multiplier + * - encoding: Set by user. + * - decoding: unused + */ + int mb_lmin; + + /** + * maximum MB Lagrange multiplier + * - encoding: Set by user. + * - decoding: unused + */ + int mb_lmax; + +#if FF_API_PRIVATE_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + int me_penalty_compensation; +#endif + + /** + * - encoding: Set by user. + * - decoding: unused + */ + int bidir_refine; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int brd_scale; +#endif + + /** + * minimum GOP size + * - encoding: Set by user. + * - decoding: unused + */ + int keyint_min; + + /** + * number of reference frames + * - encoding: Set by user. + * - decoding: Set by lavc. + */ + int refs; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int chromaoffset; +#endif + +#if FF_API_UNUSED_MEMBERS + /** + * Multiplied by qscale for each frame and added to scene_change_score. + * - encoding: Set by user. + * - decoding: unused + */ + attribute_deprecated int scenechange_factor; +#endif + + /** + * Note: Value depends upon the compare function used for fullpel ME. + * - encoding: Set by user. + * - decoding: unused + */ + int mv0_threshold; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int b_sensitivity; +#endif + + /** + * Chromaticity coordinates of the source primaries. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorPrimaries color_primaries; + + /** + * Color Transfer Characteristic. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorTransferCharacteristic color_trc; + + /** + * YUV colorspace type. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorSpace colorspace; + + /** + * MPEG vs JPEG YUV range. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorRange color_range; + + /** + * This defines the location of chroma samples. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVChromaLocation chroma_sample_location; + + /** + * Number of slices. + * Indicates number of picture subdivisions. Used for parallelized + * decoding. + * - encoding: Set by user + * - decoding: unused + */ + int slices; + + /** Field order + * - encoding: set by libavcodec + * - decoding: Set by user. + */ + enum AVFieldOrder field_order; + + /* audio only */ + int sample_rate; ///< samples per second + int channels; ///< number of audio channels + + /** + * audio sample format + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + enum AVSampleFormat sample_fmt; ///< sample format + + /* The following data should not be initialized. */ + /** + * Number of samples per channel in an audio frame. + * + * - encoding: set by libavcodec in avcodec_open2(). Each submitted frame + * except the last must contain exactly frame_size samples per channel. + * May be 0 when the codec has AV_CODEC_CAP_VARIABLE_FRAME_SIZE set, then the + * frame size is not restricted. + * - decoding: may be set by some decoders to indicate constant frame size + */ + int frame_size; + + /** + * Frame counter, set by libavcodec. + * + * - decoding: total number of frames returned from the decoder so far. + * - encoding: total number of frames passed to the encoder so far. + * + * @note the counter is not incremented if encoding/decoding resulted in + * an error. + */ + int frame_number; + + /** + * number of bytes per packet if constant and known or 0 + * Used by some WAV based audio codecs. + */ + int block_align; + + /** + * Audio cutoff bandwidth (0 means "automatic") + * - encoding: Set by user. + * - decoding: unused + */ + int cutoff; + + /** + * Audio channel layout. + * - encoding: set by user. + * - decoding: set by user, may be overwritten by libavcodec. + */ + uint64_t channel_layout; + + /** + * Request decoder to use this channel layout if it can (0 for default) + * - encoding: unused + * - decoding: Set by user. + */ + uint64_t request_channel_layout; + + /** + * Type of service that the audio stream conveys. + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + enum AVAudioServiceType audio_service_type; + + /** + * desired sample format + * - encoding: Not used. + * - decoding: Set by user. + * Decoder will decode to this format if it can. + */ + enum AVSampleFormat request_sample_fmt; + + /** + * This callback is called at the beginning of each frame to get data + * buffer(s) for it. There may be one contiguous buffer for all the data or + * there may be a buffer per each data plane or anything in between. What + * this means is, you may set however many entries in buf[] you feel necessary. + * Each buffer must be reference-counted using the AVBuffer API (see description + * of buf[] below). + * + * The following fields will be set in the frame before this callback is + * called: + * - format + * - width, height (video only) + * - sample_rate, channel_layout, nb_samples (audio only) + * Their values may differ from the corresponding values in + * AVCodecContext. This callback must use the frame values, not the codec + * context values, to calculate the required buffer size. + * + * This callback must fill the following fields in the frame: + * - data[] + * - linesize[] + * - extended_data: + * * if the data is planar audio with more than 8 channels, then this + * callback must allocate and fill extended_data to contain all pointers + * to all data planes. data[] must hold as many pointers as it can. + * extended_data must be allocated with av_malloc() and will be freed in + * av_frame_unref(). + * * otherwise extended_data must point to data + * - buf[] must contain one or more pointers to AVBufferRef structures. Each of + * the frame's data and extended_data pointers must be contained in these. That + * is, one AVBufferRef for each allocated chunk of memory, not necessarily one + * AVBufferRef per data[] entry. See: av_buffer_create(), av_buffer_alloc(), + * and av_buffer_ref(). + * - extended_buf and nb_extended_buf must be allocated with av_malloc() by + * this callback and filled with the extra buffers if there are more + * buffers than buf[] can hold. extended_buf will be freed in + * av_frame_unref(). + * + * If AV_CODEC_CAP_DR1 is not set then get_buffer2() must call + * avcodec_default_get_buffer2() instead of providing buffers allocated by + * some other means. + * + * Each data plane must be aligned to the maximum required by the target + * CPU. + * + * @see avcodec_default_get_buffer2() + * + * Video: + * + * If AV_GET_BUFFER_FLAG_REF is set in flags then the frame may be reused + * (read and/or written to if it is writable) later by libavcodec. + * + * avcodec_align_dimensions2() should be used to find the required width and + * height, as they normally need to be rounded up to the next multiple of 16. + * + * Some decoders do not support linesizes changing between frames. + * + * If frame multithreading is used and thread_safe_callbacks is set, + * this callback may be called from a different thread, but not from more + * than one at once. Does not need to be reentrant. + * + * @see avcodec_align_dimensions2() + * + * Audio: + * + * Decoders request a buffer of a particular size by setting + * AVFrame.nb_samples prior to calling get_buffer2(). The decoder may, + * however, utilize only part of the buffer by setting AVFrame.nb_samples + * to a smaller value in the output frame. + * + * As a convenience, av_samples_get_buffer_size() and + * av_samples_fill_arrays() in libavutil may be used by custom get_buffer2() + * functions to find the required data size and to fill data pointers and + * linesize. In AVFrame.linesize, only linesize[0] may be set for audio + * since all planes must be the same size. + * + * @see av_samples_get_buffer_size(), av_samples_fill_arrays() + * + * - encoding: unused + * - decoding: Set by libavcodec, user can override. + */ + int (*get_buffer2)(struct AVCodecContext *s, AVFrame *frame, int flags); + + /** + * If non-zero, the decoded audio and video frames returned from + * avcodec_decode_video2() and avcodec_decode_audio4() are reference-counted + * and are valid indefinitely. The caller must free them with + * av_frame_unref() when they are not needed anymore. + * Otherwise, the decoded frames must not be freed by the caller and are + * only valid until the next decode call. + * + * This is always automatically enabled if avcodec_receive_frame() is used. + * + * - encoding: unused + * - decoding: set by the caller before avcodec_open2(). + */ + attribute_deprecated + int refcounted_frames; + + /* - encoding parameters */ + float qcompress; ///< amount of qscale change between easy & hard scenes (0.0-1.0) + float qblur; ///< amount of qscale smoothing over time (0.0-1.0) + + /** + * minimum quantizer + * - encoding: Set by user. + * - decoding: unused + */ + int qmin; + + /** + * maximum quantizer + * - encoding: Set by user. + * - decoding: unused + */ + int qmax; + + /** + * maximum quantizer difference between frames + * - encoding: Set by user. + * - decoding: unused + */ + int max_qdiff; + +#if FF_API_MPV_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + float rc_qsquish; + + attribute_deprecated + float rc_qmod_amp; + attribute_deprecated + int rc_qmod_freq; +#endif + + /** + * decoder bitstream buffer size + * - encoding: Set by user. + * - decoding: unused + */ + int rc_buffer_size; + + /** + * ratecontrol override, see RcOverride + * - encoding: Allocated/set/freed by user. + * - decoding: unused + */ + int rc_override_count; + RcOverride *rc_override; + +#if FF_API_MPV_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + const char *rc_eq; +#endif + + /** + * maximum bitrate + * - encoding: Set by user. + * - decoding: Set by user, may be overwritten by libavcodec. + */ + int64_t rc_max_rate; + + /** + * minimum bitrate + * - encoding: Set by user. + * - decoding: unused + */ + int64_t rc_min_rate; + +#if FF_API_MPV_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + float rc_buffer_aggressivity; + + attribute_deprecated + float rc_initial_cplx; +#endif + + /** + * Ratecontrol attempt to use, at maximum, of what can be used without an underflow. + * - encoding: Set by user. + * - decoding: unused. + */ + float rc_max_available_vbv_use; + + /** + * Ratecontrol attempt to use, at least, times the amount needed to prevent a vbv overflow. + * - encoding: Set by user. + * - decoding: unused. + */ + float rc_min_vbv_overflow_use; + + /** + * Number of bits which should be loaded into the rc buffer before decoding starts. + * - encoding: Set by user. + * - decoding: unused + */ + int rc_initial_buffer_occupancy; + +#if FF_API_CODER_TYPE +#define FF_CODER_TYPE_VLC 0 +#define FF_CODER_TYPE_AC 1 +#define FF_CODER_TYPE_RAW 2 +#define FF_CODER_TYPE_RLE 3 +#if FF_API_UNUSED_MEMBERS +#define FF_CODER_TYPE_DEFLATE 4 +#endif /* FF_API_UNUSED_MEMBERS */ + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + int coder_type; +#endif /* FF_API_CODER_TYPE */ + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int context_model; +#endif + +#if FF_API_MPV_OPT + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + int lmin; + + /** + * @deprecated use encoder private options instead + */ + attribute_deprecated + int lmax; +#endif + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int frame_skip_threshold; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int frame_skip_factor; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int frame_skip_exp; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int frame_skip_cmp; +#endif /* FF_API_PRIVATE_OPT */ + + /** + * trellis RD quantization + * - encoding: Set by user. + * - decoding: unused + */ + int trellis; + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int min_prediction_order; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int max_prediction_order; + + /** @deprecated use encoder private options instead */ + attribute_deprecated + int64_t timecode_frame_start; +#endif + +#if FF_API_RTP_CALLBACK + /** + * @deprecated unused + */ + /* The RTP callback: This function is called */ + /* every time the encoder has a packet to send. */ + /* It depends on the encoder if the data starts */ + /* with a Start Code (it should). H.263 does. */ + /* mb_nb contains the number of macroblocks */ + /* encoded in the RTP payload. */ + attribute_deprecated + void (*rtp_callback)(struct AVCodecContext *avctx, void *data, int size, int mb_nb); +#endif + +#if FF_API_PRIVATE_OPT + /** @deprecated use encoder private options instead */ + attribute_deprecated + int rtp_payload_size; /* The size of the RTP payload: the coder will */ + /* do its best to deliver a chunk with size */ + /* below rtp_payload_size, the chunk will start */ + /* with a start code on some codecs like H.263. */ + /* This doesn't take account of any particular */ + /* headers inside the transmitted RTP payload. */ +#endif + +#if FF_API_STAT_BITS + /* statistics, used for 2-pass encoding */ + attribute_deprecated + int mv_bits; + attribute_deprecated + int header_bits; + attribute_deprecated + int i_tex_bits; + attribute_deprecated + int p_tex_bits; + attribute_deprecated + int i_count; + attribute_deprecated + int p_count; + attribute_deprecated + int skip_count; + attribute_deprecated + int misc_bits; + + /** @deprecated this field is unused */ + attribute_deprecated + int frame_bits; +#endif + + /** + * pass1 encoding statistics output buffer + * - encoding: Set by libavcodec. + * - decoding: unused + */ + char *stats_out; + + /** + * pass2 encoding statistics input buffer + * Concatenated stuff from stats_out of pass1 should be placed here. + * - encoding: Allocated/set/freed by user. + * - decoding: unused + */ + char *stats_in; + + /** + * Work around bugs in encoders which sometimes cannot be detected automatically. + * - encoding: Set by user + * - decoding: Set by user + */ + int workaround_bugs; +#define FF_BUG_AUTODETECT 1 ///< autodetection +#if FF_API_OLD_MSMPEG4 +#define FF_BUG_OLD_MSMPEG4 2 +#endif +#define FF_BUG_XVID_ILACE 4 +#define FF_BUG_UMP4 8 +#define FF_BUG_NO_PADDING 16 +#define FF_BUG_AMV 32 +#if FF_API_AC_VLC +#define FF_BUG_AC_VLC 0 ///< Will be removed, libavcodec can now handle these non-compliant files by default. +#endif +#define FF_BUG_QPEL_CHROMA 64 +#define FF_BUG_STD_QPEL 128 +#define FF_BUG_QPEL_CHROMA2 256 +#define FF_BUG_DIRECT_BLOCKSIZE 512 +#define FF_BUG_EDGE 1024 +#define FF_BUG_HPEL_CHROMA 2048 +#define FF_BUG_DC_CLIP 4096 +#define FF_BUG_MS 8192 ///< Work around various bugs in Microsoft's broken decoders. +#define FF_BUG_TRUNCATED 16384 +#define FF_BUG_IEDGE 32768 + + /** + * strictly follow the standard (MPEG-4, ...). + * - encoding: Set by user. + * - decoding: Set by user. + * Setting this to STRICT or higher means the encoder and decoder will + * generally do stupid things, whereas setting it to unofficial or lower + * will mean the encoder might produce output that is not supported by all + * spec-compliant decoders. Decoders don't differentiate between normal, + * unofficial and experimental (that is, they always try to decode things + * when they can) unless they are explicitly asked to behave stupidly + * (=strictly conform to the specs) + */ + int strict_std_compliance; +#define FF_COMPLIANCE_VERY_STRICT 2 ///< Strictly conform to an older more strict version of the spec or reference software. +#define FF_COMPLIANCE_STRICT 1 ///< Strictly conform to all the things in the spec no matter what consequences. +#define FF_COMPLIANCE_NORMAL 0 +#define FF_COMPLIANCE_UNOFFICIAL -1 ///< Allow unofficial extensions +#define FF_COMPLIANCE_EXPERIMENTAL -2 ///< Allow nonstandardized experimental things. + + /** + * error concealment flags + * - encoding: unused + * - decoding: Set by user. + */ + int error_concealment; +#define FF_EC_GUESS_MVS 1 +#define FF_EC_DEBLOCK 2 +#define FF_EC_FAVOR_INTER 256 + + /** + * debug + * - encoding: Set by user. + * - decoding: Set by user. + */ + int debug; +#define FF_DEBUG_PICT_INFO 1 +#define FF_DEBUG_RC 2 +#define FF_DEBUG_BITSTREAM 4 +#define FF_DEBUG_MB_TYPE 8 +#define FF_DEBUG_QP 16 +#if FF_API_DEBUG_MV +/** + * @deprecated this option does nothing + */ +#define FF_DEBUG_MV 32 +#endif +#define FF_DEBUG_DCT_COEFF 0x00000040 +#define FF_DEBUG_SKIP 0x00000080 +#define FF_DEBUG_STARTCODE 0x00000100 +#if FF_API_UNUSED_MEMBERS +#define FF_DEBUG_PTS 0x00000200 +#endif /* FF_API_UNUSED_MEMBERS */ +#define FF_DEBUG_ER 0x00000400 +#define FF_DEBUG_MMCO 0x00000800 +#define FF_DEBUG_BUGS 0x00001000 +#if FF_API_DEBUG_MV +#define FF_DEBUG_VIS_QP 0x00002000 +#define FF_DEBUG_VIS_MB_TYPE 0x00004000 +#endif +#define FF_DEBUG_BUFFERS 0x00008000 +#define FF_DEBUG_THREADS 0x00010000 +#define FF_DEBUG_GREEN_MD 0x00800000 +#define FF_DEBUG_NOMC 0x01000000 + +#if FF_API_DEBUG_MV + /** + * debug + * - encoding: Set by user. + * - decoding: Set by user. + */ + int debug_mv; +#define FF_DEBUG_VIS_MV_P_FOR 0x00000001 // visualize forward predicted MVs of P-frames +#define FF_DEBUG_VIS_MV_B_FOR 0x00000002 // visualize forward predicted MVs of B-frames +#define FF_DEBUG_VIS_MV_B_BACK 0x00000004 // visualize backward predicted MVs of B-frames +#endif + + /** + * Error recognition; may misdetect some more or less valid parts as errors. + * - encoding: unused + * - decoding: Set by user. + */ + int err_recognition; + +/** + * Verify checksums embedded in the bitstream (could be of either encoded or + * decoded data, depending on the codec) and print an error message on mismatch. + * If AV_EF_EXPLODE is also set, a mismatching checksum will result in the + * decoder returning an error. + */ +#define AV_EF_CRCCHECK (1<<0) +#define AV_EF_BITSTREAM (1<<1) ///< detect bitstream specification deviations +#define AV_EF_BUFFER (1<<2) ///< detect improper bitstream length +#define AV_EF_EXPLODE (1<<3) ///< abort decoding on minor error detection + +#define AV_EF_IGNORE_ERR (1<<15) ///< ignore errors and continue +#define AV_EF_CAREFUL (1<<16) ///< consider things that violate the spec, are fast to calculate and have not been seen in the wild as errors +#define AV_EF_COMPLIANT (1<<17) ///< consider all spec non compliances as errors +#define AV_EF_AGGRESSIVE (1<<18) ///< consider things that a sane encoder should not do as an error + + + /** + * opaque 64-bit number (generally a PTS) that will be reordered and + * output in AVFrame.reordered_opaque + * - encoding: unused + * - decoding: Set by user. + */ + int64_t reordered_opaque; + + /** + * Hardware accelerator in use + * - encoding: unused. + * - decoding: Set by libavcodec + */ + struct AVHWAccel *hwaccel; + + /** + * Hardware accelerator context. + * For some hardware accelerators, a global context needs to be + * provided by the user. In that case, this holds display-dependent + * data FFmpeg cannot instantiate itself. Please refer to the + * FFmpeg HW accelerator documentation to know how to fill this + * is. e.g. for VA API, this is a struct vaapi_context. + * - encoding: unused + * - decoding: Set by user + */ + void *hwaccel_context; + + /** + * error + * - encoding: Set by libavcodec if flags & AV_CODEC_FLAG_PSNR. + * - decoding: unused + */ + uint64_t error[AV_NUM_DATA_POINTERS]; + + /** + * DCT algorithm, see FF_DCT_* below + * - encoding: Set by user. + * - decoding: unused + */ + int dct_algo; +#define FF_DCT_AUTO 0 +#define FF_DCT_FASTINT 1 +#define FF_DCT_INT 2 +#define FF_DCT_MMX 3 +#define FF_DCT_ALTIVEC 5 +#define FF_DCT_FAAN 6 + + /** + * IDCT algorithm, see FF_IDCT_* below. + * - encoding: Set by user. + * - decoding: Set by user. + */ + int idct_algo; +#define FF_IDCT_AUTO 0 +#define FF_IDCT_INT 1 +#define FF_IDCT_SIMPLE 2 +#define FF_IDCT_SIMPLEMMX 3 +#define FF_IDCT_ARM 7 +#define FF_IDCT_ALTIVEC 8 +#if FF_API_ARCH_SH4 +#define FF_IDCT_SH4 9 +#endif +#define FF_IDCT_SIMPLEARM 10 +#if FF_API_UNUSED_MEMBERS +#define FF_IDCT_IPP 13 +#endif /* FF_API_UNUSED_MEMBERS */ +#define FF_IDCT_XVID 14 +#if FF_API_IDCT_XVIDMMX +#define FF_IDCT_XVIDMMX 14 +#endif /* FF_API_IDCT_XVIDMMX */ +#define FF_IDCT_SIMPLEARMV5TE 16 +#define FF_IDCT_SIMPLEARMV6 17 +#if FF_API_ARCH_SPARC +#define FF_IDCT_SIMPLEVIS 18 +#endif +#define FF_IDCT_FAAN 20 +#define FF_IDCT_SIMPLENEON 22 +#if FF_API_ARCH_ALPHA +#define FF_IDCT_SIMPLEALPHA 23 +#endif +#define FF_IDCT_NONE 24 /* Used by XvMC to extract IDCT coefficients with FF_IDCT_PERM_NONE */ +#define FF_IDCT_SIMPLEAUTO 128 + + /** + * bits per sample/pixel from the demuxer (needed for huffyuv). + * - encoding: Set by libavcodec. + * - decoding: Set by user. + */ + int bits_per_coded_sample; + + /** + * Bits per sample/pixel of internal libavcodec pixel/sample format. + * - encoding: set by user. + * - decoding: set by libavcodec. + */ + int bits_per_raw_sample; + +#if FF_API_LOWRES + /** + * low resolution decoding, 1-> 1/2 size, 2->1/4 size + * - encoding: unused + * - decoding: Set by user. + */ + int lowres; +#endif + +#if FF_API_CODED_FRAME + /** + * the picture in the bitstream + * - encoding: Set by libavcodec. + * - decoding: unused + * + * @deprecated use the quality factor packet side data instead + */ + attribute_deprecated AVFrame *coded_frame; +#endif + + /** + * thread count + * is used to decide how many independent tasks should be passed to execute() + * - encoding: Set by user. + * - decoding: Set by user. + */ + int thread_count; + + /** + * Which multithreading methods to use. + * Use of FF_THREAD_FRAME will increase decoding delay by one frame per thread, + * so clients which cannot provide future frames should not use it. + * + * - encoding: Set by user, otherwise the default is used. + * - decoding: Set by user, otherwise the default is used. + */ + int thread_type; +#define FF_THREAD_FRAME 1 ///< Decode more than one frame at once +#define FF_THREAD_SLICE 2 ///< Decode more than one part of a single frame at once + + /** + * Which multithreading methods are in use by the codec. + * - encoding: Set by libavcodec. + * - decoding: Set by libavcodec. + */ + int active_thread_type; + + /** + * Set by the client if its custom get_buffer() callback can be called + * synchronously from another thread, which allows faster multithreaded decoding. + * draw_horiz_band() will be called from other threads regardless of this setting. + * Ignored if the default get_buffer() is used. + * - encoding: Set by user. + * - decoding: Set by user. + */ + int thread_safe_callbacks; + + /** + * The codec may call this to execute several independent things. + * It will return only after finishing all tasks. + * The user may replace this with some multithreaded implementation, + * the default implementation will execute the parts serially. + * @param count the number of things to execute + * - encoding: Set by libavcodec, user can override. + * - decoding: Set by libavcodec, user can override. + */ + int (*execute)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg), void *arg2, int *ret, int count, int size); + + /** + * The codec may call this to execute several independent things. + * It will return only after finishing all tasks. + * The user may replace this with some multithreaded implementation, + * the default implementation will execute the parts serially. + * Also see avcodec_thread_init and e.g. the --enable-pthread configure option. + * @param c context passed also to func + * @param count the number of things to execute + * @param arg2 argument passed unchanged to func + * @param ret return values of executed functions, must have space for "count" values. May be NULL. + * @param func function that will be called count times, with jobnr from 0 to count-1. + * threadnr will be in the range 0 to c->thread_count-1 < MAX_THREADS and so that no + * two instances of func executing at the same time will have the same threadnr. + * @return always 0 currently, but code should handle a future improvement where when any call to func + * returns < 0 no further calls to func may be done and < 0 is returned. + * - encoding: Set by libavcodec, user can override. + * - decoding: Set by libavcodec, user can override. + */ + int (*execute2)(struct AVCodecContext *c, int (*func)(struct AVCodecContext *c2, void *arg, int jobnr, int threadnr), void *arg2, int *ret, int count); + + /** + * noise vs. sse weight for the nsse comparison function + * - encoding: Set by user. + * - decoding: unused + */ + int nsse_weight; + + /** + * profile + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + int profile; +#define FF_PROFILE_UNKNOWN -99 +#define FF_PROFILE_RESERVED -100 + +#define FF_PROFILE_AAC_MAIN 0 +#define FF_PROFILE_AAC_LOW 1 +#define FF_PROFILE_AAC_SSR 2 +#define FF_PROFILE_AAC_LTP 3 +#define FF_PROFILE_AAC_HE 4 +#define FF_PROFILE_AAC_HE_V2 28 +#define FF_PROFILE_AAC_LD 22 +#define FF_PROFILE_AAC_ELD 38 +#define FF_PROFILE_MPEG2_AAC_LOW 128 +#define FF_PROFILE_MPEG2_AAC_HE 131 + +#define FF_PROFILE_DNXHD 0 +#define FF_PROFILE_DNXHR_LB 1 +#define FF_PROFILE_DNXHR_SQ 2 +#define FF_PROFILE_DNXHR_HQ 3 +#define FF_PROFILE_DNXHR_HQX 4 +#define FF_PROFILE_DNXHR_444 5 + +#define FF_PROFILE_DTS 20 +#define FF_PROFILE_DTS_ES 30 +#define FF_PROFILE_DTS_96_24 40 +#define FF_PROFILE_DTS_HD_HRA 50 +#define FF_PROFILE_DTS_HD_MA 60 +#define FF_PROFILE_DTS_EXPRESS 70 + +#define FF_PROFILE_MPEG2_422 0 +#define FF_PROFILE_MPEG2_HIGH 1 +#define FF_PROFILE_MPEG2_SS 2 +#define FF_PROFILE_MPEG2_SNR_SCALABLE 3 +#define FF_PROFILE_MPEG2_MAIN 4 +#define FF_PROFILE_MPEG2_SIMPLE 5 + +#define FF_PROFILE_H264_CONSTRAINED (1<<9) // 8+1; constraint_set1_flag +#define FF_PROFILE_H264_INTRA (1<<11) // 8+3; constraint_set3_flag + +#define FF_PROFILE_H264_BASELINE 66 +#define FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED) +#define FF_PROFILE_H264_MAIN 77 +#define FF_PROFILE_H264_EXTENDED 88 +#define FF_PROFILE_H264_HIGH 100 +#define FF_PROFILE_H264_HIGH_10 110 +#define FF_PROFILE_H264_HIGH_10_INTRA (110|FF_PROFILE_H264_INTRA) +#define FF_PROFILE_H264_MULTIVIEW_HIGH 118 +#define FF_PROFILE_H264_HIGH_422 122 +#define FF_PROFILE_H264_HIGH_422_INTRA (122|FF_PROFILE_H264_INTRA) +#define FF_PROFILE_H264_STEREO_HIGH 128 +#define FF_PROFILE_H264_HIGH_444 144 +#define FF_PROFILE_H264_HIGH_444_PREDICTIVE 244 +#define FF_PROFILE_H264_HIGH_444_INTRA (244|FF_PROFILE_H264_INTRA) +#define FF_PROFILE_H264_CAVLC_444 44 + +#define FF_PROFILE_VC1_SIMPLE 0 +#define FF_PROFILE_VC1_MAIN 1 +#define FF_PROFILE_VC1_COMPLEX 2 +#define FF_PROFILE_VC1_ADVANCED 3 + +#define FF_PROFILE_MPEG4_SIMPLE 0 +#define FF_PROFILE_MPEG4_SIMPLE_SCALABLE 1 +#define FF_PROFILE_MPEG4_CORE 2 +#define FF_PROFILE_MPEG4_MAIN 3 +#define FF_PROFILE_MPEG4_N_BIT 4 +#define FF_PROFILE_MPEG4_SCALABLE_TEXTURE 5 +#define FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION 6 +#define FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE 7 +#define FF_PROFILE_MPEG4_HYBRID 8 +#define FF_PROFILE_MPEG4_ADVANCED_REAL_TIME 9 +#define FF_PROFILE_MPEG4_CORE_SCALABLE 10 +#define FF_PROFILE_MPEG4_ADVANCED_CODING 11 +#define FF_PROFILE_MPEG4_ADVANCED_CORE 12 +#define FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE 13 +#define FF_PROFILE_MPEG4_SIMPLE_STUDIO 14 +#define FF_PROFILE_MPEG4_ADVANCED_SIMPLE 15 + +#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_0 1 +#define FF_PROFILE_JPEG2000_CSTREAM_RESTRICTION_1 2 +#define FF_PROFILE_JPEG2000_CSTREAM_NO_RESTRICTION 32768 +#define FF_PROFILE_JPEG2000_DCINEMA_2K 3 +#define FF_PROFILE_JPEG2000_DCINEMA_4K 4 + +#define FF_PROFILE_VP9_0 0 +#define FF_PROFILE_VP9_1 1 +#define FF_PROFILE_VP9_2 2 +#define FF_PROFILE_VP9_3 3 + +#define FF_PROFILE_HEVC_MAIN 1 +#define FF_PROFILE_HEVC_MAIN_10 2 +#define FF_PROFILE_HEVC_MAIN_STILL_PICTURE 3 +#define FF_PROFILE_HEVC_REXT 4 + + /** + * level + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + int level; +#define FF_LEVEL_UNKNOWN -99 + + /** + * Skip loop filtering for selected frames. + * - encoding: unused + * - decoding: Set by user. + */ + enum AVDiscard skip_loop_filter; + + /** + * Skip IDCT/dequantization for selected frames. + * - encoding: unused + * - decoding: Set by user. + */ + enum AVDiscard skip_idct; + + /** + * Skip decoding for selected frames. + * - encoding: unused + * - decoding: Set by user. + */ + enum AVDiscard skip_frame; + + /** + * Header containing style information for text subtitles. + * For SUBTITLE_ASS subtitle type, it should contain the whole ASS + * [Script Info] and [V4+ Styles] section, plus the [Events] line and + * the Format line following. It shouldn't include any Dialogue line. + * - encoding: Set/allocated/freed by user (before avcodec_open2()) + * - decoding: Set/allocated/freed by libavcodec (by avcodec_open2()) + */ + uint8_t *subtitle_header; + int subtitle_header_size; + +#if FF_API_ERROR_RATE + /** + * @deprecated use the 'error_rate' private AVOption of the mpegvideo + * encoders + */ + attribute_deprecated + int error_rate; +#endif + +#if FF_API_VBV_DELAY + /** + * VBV delay coded in the last frame (in periods of a 27 MHz clock). + * Used for compliant TS muxing. + * - encoding: Set by libavcodec. + * - decoding: unused. + * @deprecated this value is now exported as a part of + * AV_PKT_DATA_CPB_PROPERTIES packet side data + */ + attribute_deprecated + uint64_t vbv_delay; +#endif + +#if FF_API_SIDEDATA_ONLY_PKT + /** + * Encoding only and set by default. Allow encoders to output packets + * that do not contain any encoded data, only side data. + * + * Some encoders need to output such packets, e.g. to update some stream + * parameters at the end of encoding. + * + * @deprecated this field disables the default behaviour and + * it is kept only for compatibility. + */ + attribute_deprecated + int side_data_only_packets; +#endif + + /** + * Audio only. The number of "priming" samples (padding) inserted by the + * encoder at the beginning of the audio. I.e. this number of leading + * decoded samples must be discarded by the caller to get the original audio + * without leading padding. + * + * - decoding: unused + * - encoding: Set by libavcodec. The timestamps on the output packets are + * adjusted by the encoder so that they always refer to the + * first sample of the data actually contained in the packet, + * including any added padding. E.g. if the timebase is + * 1/samplerate and the timestamp of the first input sample is + * 0, the timestamp of the first output packet will be + * -initial_padding. + */ + int initial_padding; + + /** + * - decoding: For codecs that store a framerate value in the compressed + * bitstream, the decoder may export it here. { 0, 1} when + * unknown. + * - encoding: May be used to signal the framerate of CFR content to an + * encoder. + */ + AVRational framerate; + + /** + * Nominal unaccelerated pixel format, see AV_PIX_FMT_xxx. + * - encoding: unused. + * - decoding: Set by libavcodec before calling get_format() + */ + enum AVPixelFormat sw_pix_fmt; + + /** + * Timebase in which pkt_dts/pts and AVPacket.dts/pts are. + * - encoding unused. + * - decoding set by user. + */ + AVRational pkt_timebase; + + /** + * AVCodecDescriptor + * - encoding: unused. + * - decoding: set by libavcodec. + */ + const AVCodecDescriptor *codec_descriptor; + +#if !FF_API_LOWRES + /** + * low resolution decoding, 1-> 1/2 size, 2->1/4 size + * - encoding: unused + * - decoding: Set by user. + */ + int lowres; +#endif + + /** + * Current statistics for PTS correction. + * - decoding: maintained and used by libavcodec, not intended to be used by user apps + * - encoding: unused + */ + int64_t pts_correction_num_faulty_pts; /// Number of incorrect PTS values so far + int64_t pts_correction_num_faulty_dts; /// Number of incorrect DTS values so far + int64_t pts_correction_last_pts; /// PTS of the last frame + int64_t pts_correction_last_dts; /// DTS of the last frame + + /** + * Character encoding of the input subtitles file. + * - decoding: set by user + * - encoding: unused + */ + char *sub_charenc; + + /** + * Subtitles character encoding mode. Formats or codecs might be adjusting + * this setting (if they are doing the conversion themselves for instance). + * - decoding: set by libavcodec + * - encoding: unused + */ + int sub_charenc_mode; +#define FF_SUB_CHARENC_MODE_DO_NOTHING -1 ///< do nothing (demuxer outputs a stream supposed to be already in UTF-8, or the codec is bitmap for instance) +#define FF_SUB_CHARENC_MODE_AUTOMATIC 0 ///< libavcodec will select the mode itself +#define FF_SUB_CHARENC_MODE_PRE_DECODER 1 ///< the AVPacket data needs to be recoded to UTF-8 before being fed to the decoder, requires iconv + + /** + * Skip processing alpha if supported by codec. + * Note that if the format uses pre-multiplied alpha (common with VP6, + * and recommended due to better video quality/compression) + * the image will look as if alpha-blended onto a black background. + * However for formats that do not use pre-multiplied alpha + * there might be serious artefacts (though e.g. libswscale currently + * assumes pre-multiplied alpha anyway). + * + * - decoding: set by user + * - encoding: unused + */ + int skip_alpha; + + /** + * Number of samples to skip after a discontinuity + * - decoding: unused + * - encoding: set by libavcodec + */ + int seek_preroll; + +#if !FF_API_DEBUG_MV + /** + * debug motion vectors + * - encoding: Set by user. + * - decoding: Set by user. + */ + int debug_mv; +#define FF_DEBUG_VIS_MV_P_FOR 0x00000001 //visualize forward predicted MVs of P frames +#define FF_DEBUG_VIS_MV_B_FOR 0x00000002 //visualize forward predicted MVs of B frames +#define FF_DEBUG_VIS_MV_B_BACK 0x00000004 //visualize backward predicted MVs of B frames +#endif + + /** + * custom intra quantization matrix + * - encoding: Set by user, can be NULL. + * - decoding: unused. + */ + uint16_t *chroma_intra_matrix; + + /** + * dump format separator. + * can be ", " or "\n " or anything else + * - encoding: Set by user. + * - decoding: Set by user. + */ + uint8_t *dump_separator; + + /** + * ',' separated list of allowed decoders. + * If NULL then all are allowed + * - encoding: unused + * - decoding: set by user + */ + char *codec_whitelist; + + /** + * Properties of the stream that gets decoded + * - encoding: unused + * - decoding: set by libavcodec + */ + unsigned properties; +#define FF_CODEC_PROPERTY_LOSSLESS 0x00000001 +#define FF_CODEC_PROPERTY_CLOSED_CAPTIONS 0x00000002 + + /** + * Additional data associated with the entire coded stream. + * + * - decoding: unused + * - encoding: may be set by libavcodec after avcodec_open2(). + */ + AVPacketSideData *coded_side_data; + int nb_coded_side_data; + + /** + * A reference to the AVHWFramesContext describing the input (for encoding) + * or output (decoding) frames. The reference is set by the caller and + * afterwards owned (and freed) by libavcodec - it should never be read by + * the caller after being set. + * + * - decoding: This field should be set by the caller from the get_format() + * callback. The previous reference (if any) will always be + * unreffed by libavcodec before the get_format() call. + * + * If the default get_buffer2() is used with a hwaccel pixel + * format, then this AVHWFramesContext will be used for + * allocating the frame buffers. + * + * - encoding: For hardware encoders configured to use a hwaccel pixel + * format, this field should be set by the caller to a reference + * to the AVHWFramesContext describing input frames. + * AVHWFramesContext.format must be equal to + * AVCodecContext.pix_fmt. + * + * This field should be set before avcodec_open2() is called. + */ + AVBufferRef *hw_frames_ctx; + + /** + * Control the form of AVSubtitle.rects[N]->ass + * - decoding: set by user + * - encoding: unused + */ + int sub_text_format; +#define FF_SUB_TEXT_FMT_ASS 0 +#if FF_API_ASS_TIMING +#define FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS 1 +#endif + + /** + * Audio only. The amount of padding (in samples) appended by the encoder to + * the end of the audio. I.e. this number of decoded samples must be + * discarded by the caller from the end of the stream to get the original + * audio without any trailing padding. + * + * - decoding: unused + * - encoding: unused + */ + int trailing_padding; + + /** + * The number of pixels per image to maximally accept. + * + * - decoding: set by user + * - encoding: set by user + */ + int64_t max_pixels; + + /** + * A reference to the AVHWDeviceContext describing the device which will + * be used by a hardware encoder/decoder. The reference is set by the + * caller and afterwards owned (and freed) by libavcodec. + * + * This should be used if either the codec device does not require + * hardware frames or any that are used are to be allocated internally by + * libavcodec. If the user wishes to supply any of the frames used as + * encoder input or decoder output then hw_frames_ctx should be used + * instead. When hw_frames_ctx is set in get_format() for a decoder, this + * field will be ignored while decoding the associated stream segment, but + * may again be used on a following one after another get_format() call. + * + * For both encoders and decoders this field should be set before + * avcodec_open2() is called and must not be written to thereafter. + * + * Note that some decoders may require this field to be set initially in + * order to support hw_frames_ctx at all - in that case, all frames + * contexts used must be created on the same device. + */ + AVBufferRef *hw_device_ctx; + + /** + * Bit set of AV_HWACCEL_FLAG_* flags, which affect hardware accelerated + * decoding (if active). + * - encoding: unused + * - decoding: Set by user (either before avcodec_open2(), or in the + * AVCodecContext.get_format callback) + */ + int hwaccel_flags; + + /** + * Video decoding only. Certain video codecs support cropping, meaning that + * only a sub-rectangle of the decoded frame is intended for display. This + * option controls how cropping is handled by libavcodec. + * + * When set to 1 (the default), libavcodec will apply cropping internally. + * I.e. it will modify the output frame width/height fields and offset the + * data pointers (only by as much as possible while preserving alignment, or + * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that + * the frames output by the decoder refer only to the cropped area. The + * crop_* fields of the output frames will be zero. + * + * When set to 0, the width/height fields of the output frames will be set + * to the coded dimensions and the crop_* fields will describe the cropping + * rectangle. Applying the cropping is left to the caller. + * + * @warning When hardware acceleration with opaque output frames is used, + * libavcodec is unable to apply cropping from the top/left border. + * + * @note when this option is set to zero, the width/height fields of the + * AVCodecContext and output AVFrames have different meanings. The codec + * context fields store display dimensions (with the coded dimensions in + * coded_width/height), while the frame fields store the coded dimensions + * (with the display dimensions being determined by the crop_* fields). + */ + int apply_cropping; +} AVCodecContext; + +AVRational av_codec_get_pkt_timebase (const AVCodecContext *avctx); +void av_codec_set_pkt_timebase (AVCodecContext *avctx, AVRational val); + +const AVCodecDescriptor *av_codec_get_codec_descriptor(const AVCodecContext *avctx); +void av_codec_set_codec_descriptor(AVCodecContext *avctx, const AVCodecDescriptor *desc); + +unsigned av_codec_get_codec_properties(const AVCodecContext *avctx); + +int av_codec_get_lowres(const AVCodecContext *avctx); +void av_codec_set_lowres(AVCodecContext *avctx, int val); + +int av_codec_get_seek_preroll(const AVCodecContext *avctx); +void av_codec_set_seek_preroll(AVCodecContext *avctx, int val); + +uint16_t *av_codec_get_chroma_intra_matrix(const AVCodecContext *avctx); +void av_codec_set_chroma_intra_matrix(AVCodecContext *avctx, uint16_t *val); + +/** + * AVProfile. + */ +typedef struct AVProfile { + int profile; + const char *name; ///< short name for the profile +} AVProfile; + +typedef struct AVCodecDefault AVCodecDefault; + +struct AVSubtitle; + +/** + * AVCodec. + */ +typedef struct AVCodec { + /** + * Name of the codec implementation. + * The name is globally unique among encoders and among decoders (but an + * encoder and a decoder can share the same name). + * This is the primary way to find a codec from the user perspective. + */ + const char *name; + /** + * Descriptive name for the codec, meant to be more human readable than name. + * You should use the NULL_IF_CONFIG_SMALL() macro to define it. + */ + const char *long_name; + enum AVMediaType type; + enum AVCodecID id; + /** + * Codec capabilities. + * see AV_CODEC_CAP_* + */ + int capabilities; + const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0} + const enum AVPixelFormat *pix_fmts; ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1 + const int *supported_samplerates; ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0 + const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1 + const uint64_t *channel_layouts; ///< array of support channel layouts, or NULL if unknown. array is terminated by 0 + uint8_t max_lowres; ///< maximum value for lowres supported by the decoder + const AVClass *priv_class; ///< AVClass for the private context + const AVProfile *profiles; ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN} + + /***************************************************************** + * No fields below this line are part of the public API. They + * may not be used outside of libavcodec and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + int priv_data_size; + struct AVCodec *next; + /** + * @name Frame-level threading support functions + * @{ + */ + /** + * If defined, called on thread contexts when they are created. + * If the codec allocates writable tables in init(), re-allocate them here. + * priv_data will be set to a copy of the original. + */ + int (*init_thread_copy)(AVCodecContext *); + /** + * Copy necessary context variables from a previous thread context to the current one. + * If not defined, the next thread will start automatically; otherwise, the codec + * must call ff_thread_finish_setup(). + * + * dst and src will (rarely) point to the same context, in which case memcpy should be skipped. + */ + int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src); + /** @} */ + + /** + * Private codec-specific defaults. + */ + const AVCodecDefault *defaults; + + /** + * Initialize codec static data, called from avcodec_register(). + */ + void (*init_static_data)(struct AVCodec *codec); + + int (*init)(AVCodecContext *); + int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size, + const struct AVSubtitle *sub); + /** + * Encode data to an AVPacket. + * + * @param avctx codec context + * @param avpkt output AVPacket (may contain a user-provided buffer) + * @param[in] frame AVFrame containing the raw data to be encoded + * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a + * non-empty packet was returned in avpkt. + * @return 0 on success, negative error code on failure + */ + int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, + int *got_packet_ptr); + int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt); + int (*close)(AVCodecContext *); + /** + * Encode API with decoupled packet/frame dataflow. The API is the + * same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except + * that: + * - never called if the codec is closed or the wrong type, + * - if AV_CODEC_CAP_DELAY is not set, drain frames are never sent, + * - only one drain frame is ever passed down, + */ + int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame); + int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt); + + /** + * Decode API with decoupled packet/frame dataflow. This function is called + * to get one output frame. It should call ff_decode_get_packet() to obtain + * input data. + */ + int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame); + /** + * Flush buffers. + * Will be called when seeking + */ + void (*flush)(AVCodecContext *); + /** + * Internal codec capabilities. + * See FF_CODEC_CAP_* in internal.h + */ + int caps_internal; + + /** + * Decoding only, a comma-separated list of bitstream filters to apply to + * packets before decoding. + */ + const char *bsfs; +} AVCodec; + +int av_codec_get_max_lowres(const AVCodec *codec); + +struct MpegEncContext; + +/** + * @defgroup lavc_hwaccel AVHWAccel + * @{ + */ +typedef struct AVHWAccel { + /** + * Name of the hardware accelerated codec. + * The name is globally unique among encoders and among decoders (but an + * encoder and a decoder can share the same name). + */ + const char *name; + + /** + * Type of codec implemented by the hardware accelerator. + * + * See AVMEDIA_TYPE_xxx + */ + enum AVMediaType type; + + /** + * Codec implemented by the hardware accelerator. + * + * See AV_CODEC_ID_xxx + */ + enum AVCodecID id; + + /** + * Supported pixel format. + * + * Only hardware accelerated formats are supported here. + */ + enum AVPixelFormat pix_fmt; + + /** + * Hardware accelerated codec capabilities. + * see AV_HWACCEL_CODEC_CAP_* + */ + int capabilities; + + /***************************************************************** + * No fields below this line are part of the public API. They + * may not be used outside of libavcodec and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + struct AVHWAccel *next; + + /** + * Allocate a custom buffer + */ + int (*alloc_frame)(AVCodecContext *avctx, AVFrame *frame); + + /** + * Called at the beginning of each frame or field picture. + * + * Meaningful frame information (codec specific) is guaranteed to + * be parsed at this point. This function is mandatory. + * + * Note that buf can be NULL along with buf_size set to 0. + * Otherwise, this means the whole frame is available at this point. + * + * @param avctx the codec context + * @param buf the frame data buffer base + * @param buf_size the size of the frame in bytes + * @return zero if successful, a negative value otherwise + */ + int (*start_frame)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size); + + /** + * Callback for each slice. + * + * Meaningful slice information (codec specific) is guaranteed to + * be parsed at this point. This function is mandatory. + * The only exception is XvMC, that works on MB level. + * + * @param avctx the codec context + * @param buf the slice data buffer base + * @param buf_size the size of the slice in bytes + * @return zero if successful, a negative value otherwise + */ + int (*decode_slice)(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size); + + /** + * Called at the end of each frame or field picture. + * + * The whole picture is parsed at this point and can now be sent + * to the hardware accelerator. This function is mandatory. + * + * @param avctx the codec context + * @return zero if successful, a negative value otherwise + */ + int (*end_frame)(AVCodecContext *avctx); + + /** + * Size of per-frame hardware accelerator private data. + * + * Private data is allocated with av_mallocz() before + * AVCodecContext.get_buffer() and deallocated after + * AVCodecContext.release_buffer(). + */ + int frame_priv_data_size; + + /** + * Called for every Macroblock in a slice. + * + * XvMC uses it to replace the ff_mpv_reconstruct_mb(). + * Instead of decoding to raw picture, MB parameters are + * stored in an array provided by the video driver. + * + * @param s the mpeg context + */ + void (*decode_mb)(struct MpegEncContext *s); + + /** + * Initialize the hwaccel private data. + * + * This will be called from ff_get_format(), after hwaccel and + * hwaccel_context are set and the hwaccel private data in AVCodecInternal + * is allocated. + */ + int (*init)(AVCodecContext *avctx); + + /** + * Uninitialize the hwaccel private data. + * + * This will be called from get_format() or avcodec_close(), after hwaccel + * and hwaccel_context are already uninitialized. + */ + int (*uninit)(AVCodecContext *avctx); + + /** + * Size of the private data to allocate in + * AVCodecInternal.hwaccel_priv_data. + */ + int priv_data_size; + + /** + * Internal hwaccel capabilities. + */ + int caps_internal; +} AVHWAccel; + +/** + * HWAccel is experimental and is thus avoided in favor of non experimental + * codecs + */ +#define AV_HWACCEL_CODEC_CAP_EXPERIMENTAL 0x0200 + +/** + * Hardware acceleration should be used for decoding even if the codec level + * used is unknown or higher than the maximum supported level reported by the + * hardware driver. + * + * It's generally a good idea to pass this flag unless you have a specific + * reason not to, as hardware tends to under-report supported levels. + */ +#define AV_HWACCEL_FLAG_IGNORE_LEVEL (1 << 0) + +/** + * Hardware acceleration can output YUV pixel formats with a different chroma + * sampling than 4:2:0 and/or other than 8 bits per component. + */ +#define AV_HWACCEL_FLAG_ALLOW_HIGH_DEPTH (1 << 1) + +/** + * Hardware acceleration should still be attempted for decoding when the + * codec profile does not match the reported capabilities of the hardware. + * + * For example, this can be used to try to decode baseline profile H.264 + * streams in hardware - it will often succeed, because many streams marked + * as baseline profile actually conform to constrained baseline profile. + * + * @warning If the stream is actually not supported then the behaviour is + * undefined, and may include returning entirely incorrect output + * while indicating success. + */ +#define AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH (1 << 2) + +/** + * @} + */ + +#if FF_API_AVPICTURE +/** + * @defgroup lavc_picture AVPicture + * + * Functions for working with AVPicture + * @{ + */ + +/** + * Picture data structure. + * + * Up to four components can be stored into it, the last component is + * alpha. + * @deprecated use AVFrame or imgutils functions instead + */ +typedef struct AVPicture { + attribute_deprecated + uint8_t *data[AV_NUM_DATA_POINTERS]; ///< pointers to the image data planes + attribute_deprecated + int linesize[AV_NUM_DATA_POINTERS]; ///< number of bytes per line +} AVPicture; + +/** + * @} + */ +#endif + +enum AVSubtitleType { + SUBTITLE_NONE, + + SUBTITLE_BITMAP, ///< A bitmap, pict will be set + + /** + * Plain text, the text field must be set by the decoder and is + * authoritative. ass and pict fields may contain approximations. + */ + SUBTITLE_TEXT, + + /** + * Formatted text, the ass field must be set by the decoder and is + * authoritative. pict and text fields may contain approximations. + */ + SUBTITLE_ASS, +}; + +#define AV_SUBTITLE_FLAG_FORCED 0x00000001 + +typedef struct AVSubtitleRect { + int x; ///< top left corner of pict, undefined when pict is not set + int y; ///< top left corner of pict, undefined when pict is not set + int w; ///< width of pict, undefined when pict is not set + int h; ///< height of pict, undefined when pict is not set + int nb_colors; ///< number of colors in pict, undefined when pict is not set + +#if FF_API_AVPICTURE + /** + * @deprecated unused + */ + attribute_deprecated + AVPicture pict; +#endif + /** + * data+linesize for the bitmap of this subtitle. + * Can be set for text/ass as well once they are rendered. + */ + uint8_t *data[4]; + int linesize[4]; + + enum AVSubtitleType type; + + char *text; ///< 0 terminated plain UTF-8 text + + /** + * 0 terminated ASS/SSA compatible event line. + * The presentation of this is unaffected by the other values in this + * struct. + */ + char *ass; + + int flags; +} AVSubtitleRect; + +typedef struct AVSubtitle { + uint16_t format; /* 0 = graphics */ + uint32_t start_display_time; /* relative to packet pts, in ms */ + uint32_t end_display_time; /* relative to packet pts, in ms */ + unsigned num_rects; + AVSubtitleRect **rects; + int64_t pts; ///< Same as packet pts, in AV_TIME_BASE +} AVSubtitle; + +/** + * This struct describes the properties of an encoded stream. + * + * sizeof(AVCodecParameters) is not a part of the public ABI, this struct must + * be allocated with avcodec_parameters_alloc() and freed with + * avcodec_parameters_free(). + */ +typedef struct AVCodecParameters { + /** + * General type of the encoded data. + */ + enum AVMediaType codec_type; + /** + * Specific type of the encoded data (the codec used). + */ + enum AVCodecID codec_id; + /** + * Additional information about the codec (corresponds to the AVI FOURCC). + */ + uint32_t codec_tag; + + /** + * Extra binary data needed for initializing the decoder, codec-dependent. + * + * Must be allocated with av_malloc() and will be freed by + * avcodec_parameters_free(). The allocated size of extradata must be at + * least extradata_size + AV_INPUT_BUFFER_PADDING_SIZE, with the padding + * bytes zeroed. + */ + uint8_t *extradata; + /** + * Size of the extradata content in bytes. + */ + int extradata_size; + + /** + * - video: the pixel format, the value corresponds to enum AVPixelFormat. + * - audio: the sample format, the value corresponds to enum AVSampleFormat. + */ + int format; + + /** + * The average bitrate of the encoded data (in bits per second). + */ + int64_t bit_rate; + + /** + * The number of bits per sample in the codedwords. + * + * This is basically the bitrate per sample. It is mandatory for a bunch of + * formats to actually decode them. It's the number of bits for one sample in + * the actual coded bitstream. + * + * This could be for example 4 for ADPCM + * For PCM formats this matches bits_per_raw_sample + * Can be 0 + */ + int bits_per_coded_sample; + + /** + * This is the number of valid bits in each output sample. If the + * sample format has more bits, the least significant bits are additional + * padding bits, which are always 0. Use right shifts to reduce the sample + * to its actual size. For example, audio formats with 24 bit samples will + * have bits_per_raw_sample set to 24, and format set to AV_SAMPLE_FMT_S32. + * To get the original sample use "(int32_t)sample >> 8"." + * + * For ADPCM this might be 12 or 16 or similar + * Can be 0 + */ + int bits_per_raw_sample; + + /** + * Codec-specific bitstream restrictions that the stream conforms to. + */ + int profile; + int level; + + /** + * Video only. The dimensions of the video frame in pixels. + */ + int width; + int height; + + /** + * Video only. The aspect ratio (width / height) which a single pixel + * should have when displayed. + * + * When the aspect ratio is unknown / undefined, the numerator should be + * set to 0 (the denominator may have any value). + */ + AVRational sample_aspect_ratio; + + /** + * Video only. The order of the fields in interlaced video. + */ + enum AVFieldOrder field_order; + + /** + * Video only. Additional colorspace characteristics. + */ + enum AVColorRange color_range; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; + enum AVColorSpace color_space; + enum AVChromaLocation chroma_location; + + /** + * Video only. Number of delayed frames. + */ + int video_delay; + + /** + * Audio only. The channel layout bitmask. May be 0 if the channel layout is + * unknown or unspecified, otherwise the number of bits set must be equal to + * the channels field. + */ + uint64_t channel_layout; + /** + * Audio only. The number of audio channels. + */ + int channels; + /** + * Audio only. The number of audio samples per second. + */ + int sample_rate; + /** + * Audio only. The number of bytes per coded audio frame, required by some + * formats. + * + * Corresponds to nBlockAlign in WAVEFORMATEX. + */ + int block_align; + /** + * Audio only. Audio frame size, if known. Required by some formats to be static. + */ + int frame_size; + + /** + * Audio only. The amount of padding (in samples) inserted by the encoder at + * the beginning of the audio. I.e. this number of leading decoded samples + * must be discarded by the caller to get the original audio without leading + * padding. + */ + int initial_padding; + /** + * Audio only. The amount of padding (in samples) appended by the encoder to + * the end of the audio. I.e. this number of decoded samples must be + * discarded by the caller from the end of the stream to get the original + * audio without any trailing padding. + */ + int trailing_padding; + /** + * Audio only. Number of samples to skip after a discontinuity. + */ + int seek_preroll; +} AVCodecParameters; + +/** + * If c is NULL, returns the first registered codec, + * if c is non-NULL, returns the next registered codec after c, + * or NULL if c is the last one. + */ +AVCodec *av_codec_next(const AVCodec *c); + +/** + * Return the LIBAVCODEC_VERSION_INT constant. + */ +unsigned avcodec_version(void); + +/** + * Return the libavcodec build-time configuration. + */ +const char *avcodec_configuration(void); + +/** + * Return the libavcodec license. + */ +const char *avcodec_license(void); + +/** + * Register the codec codec and initialize libavcodec. + * + * @warning either this function or avcodec_register_all() must be called + * before any other libavcodec functions. + * + * @see avcodec_register_all() + */ +void avcodec_register(AVCodec *codec); + +/** + * Register all the codecs, parsers and bitstream filters which were enabled at + * configuration time. If you do not call this function you can select exactly + * which formats you want to support, by using the individual registration + * functions. + * + * @see avcodec_register + * @see av_register_codec_parser + * @see av_register_bitstream_filter + */ +void avcodec_register_all(void); + +/** + * Allocate an AVCodecContext and set its fields to default values. The + * resulting struct should be freed with avcodec_free_context(). + * + * @param codec if non-NULL, allocate private data and initialize defaults + * for the given codec. It is illegal to then call avcodec_open2() + * with a different codec. + * If NULL, then the codec-specific defaults won't be initialized, + * which may result in suboptimal default settings (this is + * important mainly for encoders, e.g. libx264). + * + * @return An AVCodecContext filled with default values or NULL on failure. + */ +AVCodecContext *avcodec_alloc_context3(const AVCodec *codec); + +/** + * Free the codec context and everything associated with it and write NULL to + * the provided pointer. + */ +void avcodec_free_context(AVCodecContext **avctx); + +#if FF_API_GET_CONTEXT_DEFAULTS +/** + * @deprecated This function should not be used, as closing and opening a codec + * context multiple time is not supported. A new codec context should be + * allocated for each new use. + */ +int avcodec_get_context_defaults3(AVCodecContext *s, const AVCodec *codec); +#endif + +/** + * Get the AVClass for AVCodecContext. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + */ +const AVClass *avcodec_get_class(void); + +#if FF_API_COPY_CONTEXT +/** + * Get the AVClass for AVFrame. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + */ +const AVClass *avcodec_get_frame_class(void); + +/** + * Get the AVClass for AVSubtitleRect. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + */ +const AVClass *avcodec_get_subtitle_rect_class(void); + +/** + * Copy the settings of the source AVCodecContext into the destination + * AVCodecContext. The resulting destination codec context will be + * unopened, i.e. you are required to call avcodec_open2() before you + * can use this AVCodecContext to decode/encode video/audio data. + * + * @param dest target codec context, should be initialized with + * avcodec_alloc_context3(NULL), but otherwise uninitialized + * @param src source codec context + * @return AVERROR() on error (e.g. memory allocation error), 0 on success + * + * @deprecated The semantics of this function are ill-defined and it should not + * be used. If you need to transfer the stream parameters from one codec context + * to another, use an intermediate AVCodecParameters instance and the + * avcodec_parameters_from_context() / avcodec_parameters_to_context() + * functions. + */ +attribute_deprecated +int avcodec_copy_context(AVCodecContext *dest, const AVCodecContext *src); +#endif + +/** + * Allocate a new AVCodecParameters and set its fields to default values + * (unknown/invalid/0). The returned struct must be freed with + * avcodec_parameters_free(). + */ +AVCodecParameters *avcodec_parameters_alloc(void); + +/** + * Free an AVCodecParameters instance and everything associated with it and + * write NULL to the supplied pointer. + */ +void avcodec_parameters_free(AVCodecParameters **par); + +/** + * Copy the contents of src to dst. Any allocated fields in dst are freed and + * replaced with newly allocated duplicates of the corresponding fields in src. + * + * @return >= 0 on success, a negative AVERROR code on failure. + */ +int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src); + +/** + * Fill the parameters struct based on the values from the supplied codec + * context. Any allocated fields in par are freed and replaced with duplicates + * of the corresponding fields in codec. + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int avcodec_parameters_from_context(AVCodecParameters *par, + const AVCodecContext *codec); + +/** + * Fill the codec context based on the values from the supplied codec + * parameters. Any allocated fields in codec that have a corresponding field in + * par are freed and replaced with duplicates of the corresponding field in par. + * Fields in codec that do not have a counterpart in par are not touched. + * + * @return >= 0 on success, a negative AVERROR code on failure. + */ +int avcodec_parameters_to_context(AVCodecContext *codec, + const AVCodecParameters *par); + +/** + * Initialize the AVCodecContext to use the given AVCodec. Prior to using this + * function the context has to be allocated with avcodec_alloc_context3(). + * + * The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(), + * avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for + * retrieving a codec. + * + * @warning This function is not thread safe! + * + * @note Always call this function before using decoding routines (such as + * @ref avcodec_receive_frame()). + * + * @code + * avcodec_register_all(); + * av_dict_set(&opts, "b", "2.5M", 0); + * codec = avcodec_find_decoder(AV_CODEC_ID_H264); + * if (!codec) + * exit(1); + * + * context = avcodec_alloc_context3(codec); + * + * if (avcodec_open2(context, codec, opts) < 0) + * exit(1); + * @endcode + * + * @param avctx The context to initialize. + * @param codec The codec to open this context for. If a non-NULL codec has been + * previously passed to avcodec_alloc_context3() or + * for this context, then this parameter MUST be either NULL or + * equal to the previously passed codec. + * @param options A dictionary filled with AVCodecContext and codec-private options. + * On return this object will be filled with options that were not found. + * + * @return zero on success, a negative value on error + * @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(), + * av_dict_set(), av_opt_find(). + */ +int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); + +/** + * Close a given AVCodecContext and free all the data associated with it + * (but not the AVCodecContext itself). + * + * Calling this function on an AVCodecContext that hasn't been opened will free + * the codec-specific data allocated in avcodec_alloc_context3() with a non-NULL + * codec. Subsequent calls will do nothing. + * + * @note Do not use this function. Use avcodec_free_context() to destroy a + * codec context (either open or closed). Opening and closing a codec context + * multiple times is not supported anymore -- use multiple codec contexts + * instead. + */ +int avcodec_close(AVCodecContext *avctx); + +/** + * Free all allocated data in the given subtitle struct. + * + * @param sub AVSubtitle to free. + */ +void avsubtitle_free(AVSubtitle *sub); + +/** + * @} + */ + +/** + * @addtogroup lavc_packet + * @{ + */ + +/** + * Allocate an AVPacket and set its fields to default values. The resulting + * struct must be freed using av_packet_free(). + * + * @return An AVPacket filled with default values or NULL on failure. + * + * @note this only allocates the AVPacket itself, not the data buffers. Those + * must be allocated through other means such as av_new_packet. + * + * @see av_new_packet + */ +AVPacket *av_packet_alloc(void); + +/** + * Create a new packet that references the same data as src. + * + * This is a shortcut for av_packet_alloc()+av_packet_ref(). + * + * @return newly created AVPacket on success, NULL on error. + * + * @see av_packet_alloc + * @see av_packet_ref + */ +AVPacket *av_packet_clone(const AVPacket *src); + +/** + * Free the packet, if the packet is reference counted, it will be + * unreferenced first. + * + * @param pkt packet to be freed. The pointer will be set to NULL. + * @note passing NULL is a no-op. + */ +void av_packet_free(AVPacket **pkt); + +/** + * Initialize optional fields of a packet with default values. + * + * Note, this does not touch the data and size members, which have to be + * initialized separately. + * + * @param pkt packet + */ +void av_init_packet(AVPacket *pkt); + +/** + * Allocate the payload of a packet and initialize its fields with + * default values. + * + * @param pkt packet + * @param size wanted payload size + * @return 0 if OK, AVERROR_xxx otherwise + */ +int av_new_packet(AVPacket *pkt, int size); + +/** + * Reduce packet size, correctly zeroing padding + * + * @param pkt packet + * @param size new size + */ +void av_shrink_packet(AVPacket *pkt, int size); + +/** + * Increase packet size, correctly zeroing padding + * + * @param pkt packet + * @param grow_by number of bytes by which to increase the size of the packet + */ +int av_grow_packet(AVPacket *pkt, int grow_by); + +/** + * Initialize a reference-counted packet from av_malloc()ed data. + * + * @param pkt packet to be initialized. This function will set the data, size, + * buf and destruct fields, all others are left untouched. + * @param data Data allocated by av_malloc() to be used as packet data. If this + * function returns successfully, the data is owned by the underlying AVBuffer. + * The caller may not access the data through other means. + * @param size size of data in bytes, without the padding. I.e. the full buffer + * size is assumed to be size + AV_INPUT_BUFFER_PADDING_SIZE. + * + * @return 0 on success, a negative AVERROR on error + */ +int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size); + +#if FF_API_AVPACKET_OLD_API +/** + * @warning This is a hack - the packet memory allocation stuff is broken. The + * packet is allocated if it was not really allocated. + * + * @deprecated Use av_packet_ref + */ +attribute_deprecated +int av_dup_packet(AVPacket *pkt); +/** + * Copy packet, including contents + * + * @return 0 on success, negative AVERROR on fail + * + * @deprecated Use av_packet_ref + */ +attribute_deprecated +int av_copy_packet(AVPacket *dst, const AVPacket *src); + +/** + * Copy packet side data + * + * @return 0 on success, negative AVERROR on fail + * + * @deprecated Use av_packet_copy_props + */ +attribute_deprecated +int av_copy_packet_side_data(AVPacket *dst, const AVPacket *src); + +/** + * Free a packet. + * + * @deprecated Use av_packet_unref + * + * @param pkt packet to free + */ +attribute_deprecated +void av_free_packet(AVPacket *pkt); +#endif +/** + * Allocate new information of a packet. + * + * @param pkt packet + * @param type side information type + * @param size side information size + * @return pointer to fresh allocated data or NULL otherwise + */ +uint8_t* av_packet_new_side_data(AVPacket *pkt, enum AVPacketSideDataType type, + int size); + +/** + * Wrap an existing array as a packet side data. + * + * @param pkt packet + * @param type side information type + * @param data the side data array. It must be allocated with the av_malloc() + * family of functions. The ownership of the data is transferred to + * pkt. + * @param size side information size + * @return a non-negative number on success, a negative AVERROR code on + * failure. On failure, the packet is unchanged and the data remains + * owned by the caller. + */ +int av_packet_add_side_data(AVPacket *pkt, enum AVPacketSideDataType type, + uint8_t *data, size_t size); + +/** + * Shrink the already allocated side data buffer + * + * @param pkt packet + * @param type side information type + * @param size new side information size + * @return 0 on success, < 0 on failure + */ +int av_packet_shrink_side_data(AVPacket *pkt, enum AVPacketSideDataType type, + int size); + +/** + * Get side information from packet. + * + * @param pkt packet + * @param type desired side information type + * @param size pointer for side information size to store (optional) + * @return pointer to data if present or NULL otherwise + */ +uint8_t* av_packet_get_side_data(const AVPacket *pkt, enum AVPacketSideDataType type, + int *size); + +#if FF_API_MERGE_SD_API +attribute_deprecated +int av_packet_merge_side_data(AVPacket *pkt); + +attribute_deprecated +int av_packet_split_side_data(AVPacket *pkt); +#endif + +const char *av_packet_side_data_name(enum AVPacketSideDataType type); + +/** + * Pack a dictionary for use in side_data. + * + * @param dict The dictionary to pack. + * @param size pointer to store the size of the returned data + * @return pointer to data if successful, NULL otherwise + */ +uint8_t *av_packet_pack_dictionary(AVDictionary *dict, int *size); +/** + * Unpack a dictionary from side_data. + * + * @param data data from side_data + * @param size size of the data + * @param dict the metadata storage dictionary + * @return 0 on success, < 0 on failure + */ +int av_packet_unpack_dictionary(const uint8_t *data, int size, AVDictionary **dict); + + +/** + * Convenience function to free all the side data stored. + * All the other fields stay untouched. + * + * @param pkt packet + */ +void av_packet_free_side_data(AVPacket *pkt); + +/** + * Setup a new reference to the data described by a given packet + * + * If src is reference-counted, setup dst as a new reference to the + * buffer in src. Otherwise allocate a new buffer in dst and copy the + * data from src into it. + * + * All the other fields are copied from src. + * + * @see av_packet_unref + * + * @param dst Destination packet + * @param src Source packet + * + * @return 0 on success, a negative AVERROR on error. + */ +int av_packet_ref(AVPacket *dst, const AVPacket *src); + +/** + * Wipe the packet. + * + * Unreference the buffer referenced by the packet and reset the + * remaining packet fields to their default values. + * + * @param pkt The packet to be unreferenced. + */ +void av_packet_unref(AVPacket *pkt); + +/** + * Move every field in src to dst and reset src. + * + * @see av_packet_unref + * + * @param src Source packet, will be reset + * @param dst Destination packet + */ +void av_packet_move_ref(AVPacket *dst, AVPacket *src); + +/** + * Copy only "properties" fields from src to dst. + * + * Properties for the purpose of this function are all the fields + * beside those related to the packet data (buf, data, size) + * + * @param dst Destination packet + * @param src Source packet + * + * @return 0 on success AVERROR on failure. + */ +int av_packet_copy_props(AVPacket *dst, const AVPacket *src); + +/** + * Convert valid timing fields (timestamps / durations) in a packet from one + * timebase to another. Timestamps with unknown values (AV_NOPTS_VALUE) will be + * ignored. + * + * @param pkt packet on which the conversion will be performed + * @param tb_src source timebase, in which the timing fields in pkt are + * expressed + * @param tb_dst destination timebase, to which the timing fields will be + * converted + */ +void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst); + +/** + * @} + */ + +/** + * @addtogroup lavc_decoding + * @{ + */ + +/** + * Find a registered decoder with a matching codec ID. + * + * @param id AVCodecID of the requested decoder + * @return A decoder if one was found, NULL otherwise. + */ +AVCodec *avcodec_find_decoder(enum AVCodecID id); + +/** + * Find a registered decoder with the specified name. + * + * @param name name of the requested decoder + * @return A decoder if one was found, NULL otherwise. + */ +AVCodec *avcodec_find_decoder_by_name(const char *name); + +/** + * The default callback for AVCodecContext.get_buffer2(). It is made public so + * it can be called by custom get_buffer2() implementations for decoders without + * AV_CODEC_CAP_DR1 set. + */ +int avcodec_default_get_buffer2(AVCodecContext *s, AVFrame *frame, int flags); + +#if FF_API_EMU_EDGE +/** + * Return the amount of padding in pixels which the get_buffer callback must + * provide around the edge of the image for codecs which do not have the + * CODEC_FLAG_EMU_EDGE flag. + * + * @return Required padding in pixels. + * + * @deprecated CODEC_FLAG_EMU_EDGE is deprecated, so this function is no longer + * needed + */ +attribute_deprecated +unsigned avcodec_get_edge_width(void); +#endif + +/** + * Modify width and height values so that they will result in a memory + * buffer that is acceptable for the codec if you do not use any horizontal + * padding. + * + * May only be used if a codec with AV_CODEC_CAP_DR1 has been opened. + */ +void avcodec_align_dimensions(AVCodecContext *s, int *width, int *height); + +/** + * Modify width and height values so that they will result in a memory + * buffer that is acceptable for the codec if you also ensure that all + * line sizes are a multiple of the respective linesize_align[i]. + * + * May only be used if a codec with AV_CODEC_CAP_DR1 has been opened. + */ +void avcodec_align_dimensions2(AVCodecContext *s, int *width, int *height, + int linesize_align[AV_NUM_DATA_POINTERS]); + +/** + * Converts AVChromaLocation to swscale x/y chroma position. + * + * The positions represent the chroma (0,0) position in a coordinates system + * with luma (0,0) representing the origin and luma(1,1) representing 256,256 + * + * @param xpos horizontal chroma sample position + * @param ypos vertical chroma sample position + */ +int avcodec_enum_to_chroma_pos(int *xpos, int *ypos, enum AVChromaLocation pos); + +/** + * Converts swscale x/y chroma position to AVChromaLocation. + * + * The positions represent the chroma (0,0) position in a coordinates system + * with luma (0,0) representing the origin and luma(1,1) representing 256,256 + * + * @param xpos horizontal chroma sample position + * @param ypos vertical chroma sample position + */ +enum AVChromaLocation avcodec_chroma_pos_to_enum(int xpos, int ypos); + +/** + * Decode the audio frame of size avpkt->size from avpkt->data into frame. + * + * Some decoders may support multiple frames in a single AVPacket. Such + * decoders would then just decode the first frame and the return value would be + * less than the packet size. In this case, avcodec_decode_audio4 has to be + * called again with an AVPacket containing the remaining data in order to + * decode the second frame, etc... Even if no frames are returned, the packet + * needs to be fed to the decoder with remaining data until it is completely + * consumed or an error occurs. + * + * Some decoders (those marked with AV_CODEC_CAP_DELAY) have a delay between input + * and output. This means that for some packets they will not immediately + * produce decoded output and need to be flushed at the end of decoding to get + * all the decoded data. Flushing is done by calling this function with packets + * with avpkt->data set to NULL and avpkt->size set to 0 until it stops + * returning samples. It is safe to flush even those decoders that are not + * marked with AV_CODEC_CAP_DELAY, then no samples will be returned. + * + * @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE + * larger than the actual read bytes because some optimized bitstream + * readers read 32 or 64 bits at once and could read over the end. + * + * @note The AVCodecContext MUST have been opened with @ref avcodec_open2() + * before packets may be fed to the decoder. + * + * @param avctx the codec context + * @param[out] frame The AVFrame in which to store decoded audio samples. + * The decoder will allocate a buffer for the decoded frame by + * calling the AVCodecContext.get_buffer2() callback. + * When AVCodecContext.refcounted_frames is set to 1, the frame is + * reference counted and the returned reference belongs to the + * caller. The caller must release the frame using av_frame_unref() + * when the frame is no longer needed. The caller may safely write + * to the frame if av_frame_is_writable() returns 1. + * When AVCodecContext.refcounted_frames is set to 0, the returned + * reference belongs to the decoder and is valid only until the + * next call to this function or until closing or flushing the + * decoder. The caller may not write to it. + * @param[out] got_frame_ptr Zero if no frame could be decoded, otherwise it is + * non-zero. Note that this field being set to zero + * does not mean that an error has occurred. For + * decoders with AV_CODEC_CAP_DELAY set, no given decode + * call is guaranteed to produce a frame. + * @param[in] avpkt The input AVPacket containing the input buffer. + * At least avpkt->data and avpkt->size should be set. Some + * decoders might also require additional fields to be set. + * @return A negative error code is returned if an error occurred during + * decoding, otherwise the number of bytes consumed from the input + * AVPacket is returned. + * +* @deprecated Use avcodec_send_packet() and avcodec_receive_frame(). + */ +attribute_deprecated +int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, + int *got_frame_ptr, const AVPacket *avpkt); + +/** + * Decode the video frame of size avpkt->size from avpkt->data into picture. + * Some decoders may support multiple frames in a single AVPacket, such + * decoders would then just decode the first frame. + * + * @warning The input buffer must be AV_INPUT_BUFFER_PADDING_SIZE larger than + * the actual read bytes because some optimized bitstream readers read 32 or 64 + * bits at once and could read over the end. + * + * @warning The end of the input buffer buf should be set to 0 to ensure that + * no overreading happens for damaged MPEG streams. + * + * @note Codecs which have the AV_CODEC_CAP_DELAY capability set have a delay + * between input and output, these need to be fed with avpkt->data=NULL, + * avpkt->size=0 at the end to return the remaining frames. + * + * @note The AVCodecContext MUST have been opened with @ref avcodec_open2() + * before packets may be fed to the decoder. + * + * @param avctx the codec context + * @param[out] picture The AVFrame in which the decoded video frame will be stored. + * Use av_frame_alloc() to get an AVFrame. The codec will + * allocate memory for the actual bitmap by calling the + * AVCodecContext.get_buffer2() callback. + * When AVCodecContext.refcounted_frames is set to 1, the frame is + * reference counted and the returned reference belongs to the + * caller. The caller must release the frame using av_frame_unref() + * when the frame is no longer needed. The caller may safely write + * to the frame if av_frame_is_writable() returns 1. + * When AVCodecContext.refcounted_frames is set to 0, the returned + * reference belongs to the decoder and is valid only until the + * next call to this function or until closing or flushing the + * decoder. The caller may not write to it. + * + * @param[in] avpkt The input AVPacket containing the input buffer. + * You can create such packet with av_init_packet() and by then setting + * data and size, some decoders might in addition need other fields like + * flags&AV_PKT_FLAG_KEY. All decoders are designed to use the least + * fields possible. + * @param[in,out] got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero. + * @return On error a negative value is returned, otherwise the number of bytes + * used or zero if no frame could be decompressed. + * + * @deprecated Use avcodec_send_packet() and avcodec_receive_frame(). + */ +attribute_deprecated +int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, + int *got_picture_ptr, + const AVPacket *avpkt); + +/** + * Decode a subtitle message. + * Return a negative value on error, otherwise return the number of bytes used. + * If no subtitle could be decompressed, got_sub_ptr is zero. + * Otherwise, the subtitle is stored in *sub. + * Note that AV_CODEC_CAP_DR1 is not available for subtitle codecs. This is for + * simplicity, because the performance difference is expect to be negligible + * and reusing a get_buffer written for video codecs would probably perform badly + * due to a potentially very different allocation pattern. + * + * Some decoders (those marked with AV_CODEC_CAP_DELAY) have a delay between input + * and output. This means that for some packets they will not immediately + * produce decoded output and need to be flushed at the end of decoding to get + * all the decoded data. Flushing is done by calling this function with packets + * with avpkt->data set to NULL and avpkt->size set to 0 until it stops + * returning subtitles. It is safe to flush even those decoders that are not + * marked with AV_CODEC_CAP_DELAY, then no subtitles will be returned. + * + * @note The AVCodecContext MUST have been opened with @ref avcodec_open2() + * before packets may be fed to the decoder. + * + * @param avctx the codec context + * @param[out] sub The Preallocated AVSubtitle in which the decoded subtitle will be stored, + * must be freed with avsubtitle_free if *got_sub_ptr is set. + * @param[in,out] got_sub_ptr Zero if no subtitle could be decompressed, otherwise, it is nonzero. + * @param[in] avpkt The input AVPacket containing the input buffer. + */ +int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub, + int *got_sub_ptr, + AVPacket *avpkt); + +/** + * Supply raw packet data as input to a decoder. + * + * Internally, this call will copy relevant AVCodecContext fields, which can + * influence decoding per-packet, and apply them when the packet is actually + * decoded. (For example AVCodecContext.skip_frame, which might direct the + * decoder to drop the frame contained by the packet sent with this function.) + * + * @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE + * larger than the actual read bytes because some optimized bitstream + * readers read 32 or 64 bits at once and could read over the end. + * + * @warning Do not mix this API with the legacy API (like avcodec_decode_video2()) + * on the same AVCodecContext. It will return unexpected results now + * or in future libavcodec versions. + * + * @note The AVCodecContext MUST have been opened with @ref avcodec_open2() + * before packets may be fed to the decoder. + * + * @param avctx codec context + * @param[in] avpkt The input AVPacket. Usually, this will be a single video + * frame, or several complete audio frames. + * Ownership of the packet remains with the caller, and the + * decoder will not write to the packet. The decoder may create + * a reference to the packet data (or copy it if the packet is + * not reference-counted). + * Unlike with older APIs, the packet is always fully consumed, + * and if it contains multiple frames (e.g. some audio codecs), + * will require you to call avcodec_receive_frame() multiple + * times afterwards before you can send a new packet. + * It can be NULL (or an AVPacket with data set to NULL and + * size set to 0); in this case, it is considered a flush + * packet, which signals the end of the stream. Sending the + * first flush packet will return success. Subsequent ones are + * unnecessary and will return AVERROR_EOF. If the decoder + * still has frames buffered, it will return them after sending + * a flush packet. + * + * @return 0 on success, otherwise negative error code: + * AVERROR(EAGAIN): input is not accepted in the current state - user + * must read output with avcodec_receive_frame() (once + * all output is read, the packet should be resent, and + * the call will not fail with EAGAIN). + * AVERROR_EOF: the decoder has been flushed, and no new packets can + * be sent to it (also returned if more than 1 flush + * packet is sent) + * AVERROR(EINVAL): codec not opened, it is an encoder, or requires flush + * AVERROR(ENOMEM): failed to add packet to internal queue, or similar + * other errors: legitimate decoding errors + */ +int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt); + +/** + * Return decoded output data from a decoder. + * + * @param avctx codec context + * @param frame This will be set to a reference-counted video or audio + * frame (depending on the decoder type) allocated by the + * decoder. Note that the function will always call + * av_frame_unref(frame) before doing anything else. + * + * @return + * 0: success, a frame was returned + * AVERROR(EAGAIN): output is not available in this state - user must try + * to send new input + * AVERROR_EOF: the decoder has been fully flushed, and there will be + * no more output frames + * AVERROR(EINVAL): codec not opened, or it is an encoder + * other negative values: legitimate decoding errors + */ +int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame); + +/** + * Supply a raw video or audio frame to the encoder. Use avcodec_receive_packet() + * to retrieve buffered output packets. + * + * @param avctx codec context + * @param[in] frame AVFrame containing the raw audio or video frame to be encoded. + * Ownership of the frame remains with the caller, and the + * encoder will not write to the frame. The encoder may create + * a reference to the frame data (or copy it if the frame is + * not reference-counted). + * It can be NULL, in which case it is considered a flush + * packet. This signals the end of the stream. If the encoder + * still has packets buffered, it will return them after this + * call. Once flushing mode has been entered, additional flush + * packets are ignored, and sending frames will return + * AVERROR_EOF. + * + * For audio: + * If AV_CODEC_CAP_VARIABLE_FRAME_SIZE is set, then each frame + * can have any number of samples. + * If it is not set, frame->nb_samples must be equal to + * avctx->frame_size for all frames except the last. + * The final frame may be smaller than avctx->frame_size. + * @return 0 on success, otherwise negative error code: + * AVERROR(EAGAIN): input is not accepted in the current state - user + * must read output with avcodec_receive_packet() (once + * all output is read, the packet should be resent, and + * the call will not fail with EAGAIN). + * AVERROR_EOF: the encoder has been flushed, and no new frames can + * be sent to it + * AVERROR(EINVAL): codec not opened, refcounted_frames not set, it is a + * decoder, or requires flush + * AVERROR(ENOMEM): failed to add packet to internal queue, or similar + * other errors: legitimate decoding errors + */ +int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame); + +/** + * Read encoded data from the encoder. + * + * @param avctx codec context + * @param avpkt This will be set to a reference-counted packet allocated by the + * encoder. Note that the function will always call + * av_frame_unref(frame) before doing anything else. + * @return 0 on success, otherwise negative error code: + * AVERROR(EAGAIN): output is not available in the current state - user + * must try to send input + * AVERROR_EOF: the encoder has been fully flushed, and there will be + * no more output packets + * AVERROR(EINVAL): codec not opened, or it is an encoder + * other errors: legitimate decoding errors + */ +int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt); + + +/** + * @defgroup lavc_parsing Frame parsing + * @{ + */ + +enum AVPictureStructure { + AV_PICTURE_STRUCTURE_UNKNOWN, //< unknown + AV_PICTURE_STRUCTURE_TOP_FIELD, //< coded as top field + AV_PICTURE_STRUCTURE_BOTTOM_FIELD, //< coded as bottom field + AV_PICTURE_STRUCTURE_FRAME, //< coded as frame +}; + +typedef struct AVCodecParserContext { + void *priv_data; + struct AVCodecParser *parser; + int64_t frame_offset; /* offset of the current frame */ + int64_t cur_offset; /* current offset + (incremented by each av_parser_parse()) */ + int64_t next_frame_offset; /* offset of the next frame */ + /* video info */ + int pict_type; /* XXX: Put it back in AVCodecContext. */ + /** + * This field is used for proper frame duration computation in lavf. + * It signals, how much longer the frame duration of the current frame + * is compared to normal frame duration. + * + * frame_duration = (1 + repeat_pict) * time_base + * + * It is used by codecs like H.264 to display telecined material. + */ + int repeat_pict; /* XXX: Put it back in AVCodecContext. */ + int64_t pts; /* pts of the current frame */ + int64_t dts; /* dts of the current frame */ + + /* private data */ + int64_t last_pts; + int64_t last_dts; + int fetch_timestamp; + +#define AV_PARSER_PTS_NB 4 + int cur_frame_start_index; + int64_t cur_frame_offset[AV_PARSER_PTS_NB]; + int64_t cur_frame_pts[AV_PARSER_PTS_NB]; + int64_t cur_frame_dts[AV_PARSER_PTS_NB]; + + int flags; +#define PARSER_FLAG_COMPLETE_FRAMES 0x0001 +#define PARSER_FLAG_ONCE 0x0002 +/// Set if the parser has a valid file offset +#define PARSER_FLAG_FETCHED_OFFSET 0x0004 +#define PARSER_FLAG_USE_CODEC_TS 0x1000 + + int64_t offset; ///< byte offset from starting packet start + int64_t cur_frame_end[AV_PARSER_PTS_NB]; + + /** + * Set by parser to 1 for key frames and 0 for non-key frames. + * It is initialized to -1, so if the parser doesn't set this flag, + * old-style fallback using AV_PICTURE_TYPE_I picture type as key frames + * will be used. + */ + int key_frame; + +#if FF_API_CONVERGENCE_DURATION + /** + * @deprecated unused + */ + attribute_deprecated + int64_t convergence_duration; +#endif + + // Timestamp generation support: + /** + * Synchronization point for start of timestamp generation. + * + * Set to >0 for sync point, 0 for no sync point and <0 for undefined + * (default). + * + * For example, this corresponds to presence of H.264 buffering period + * SEI message. + */ + int dts_sync_point; + + /** + * Offset of the current timestamp against last timestamp sync point in + * units of AVCodecContext.time_base. + * + * Set to INT_MIN when dts_sync_point unused. Otherwise, it must + * contain a valid timestamp offset. + * + * Note that the timestamp of sync point has usually a nonzero + * dts_ref_dts_delta, which refers to the previous sync point. Offset of + * the next frame after timestamp sync point will be usually 1. + * + * For example, this corresponds to H.264 cpb_removal_delay. + */ + int dts_ref_dts_delta; + + /** + * Presentation delay of current frame in units of AVCodecContext.time_base. + * + * Set to INT_MIN when dts_sync_point unused. Otherwise, it must + * contain valid non-negative timestamp delta (presentation time of a frame + * must not lie in the past). + * + * This delay represents the difference between decoding and presentation + * time of the frame. + * + * For example, this corresponds to H.264 dpb_output_delay. + */ + int pts_dts_delta; + + /** + * Position of the packet in file. + * + * Analogous to cur_frame_pts/dts + */ + int64_t cur_frame_pos[AV_PARSER_PTS_NB]; + + /** + * Byte position of currently parsed frame in stream. + */ + int64_t pos; + + /** + * Previous frame byte position. + */ + int64_t last_pos; + + /** + * Duration of the current frame. + * For audio, this is in units of 1 / AVCodecContext.sample_rate. + * For all other types, this is in units of AVCodecContext.time_base. + */ + int duration; + + enum AVFieldOrder field_order; + + /** + * Indicate whether a picture is coded as a frame, top field or bottom field. + * + * For example, H.264 field_pic_flag equal to 0 corresponds to + * AV_PICTURE_STRUCTURE_FRAME. An H.264 picture with field_pic_flag + * equal to 1 and bottom_field_flag equal to 0 corresponds to + * AV_PICTURE_STRUCTURE_TOP_FIELD. + */ + enum AVPictureStructure picture_structure; + + /** + * Picture number incremented in presentation or output order. + * This field may be reinitialized at the first picture of a new sequence. + * + * For example, this corresponds to H.264 PicOrderCnt. + */ + int output_picture_number; + + /** + * Dimensions of the decoded video intended for presentation. + */ + int width; + int height; + + /** + * Dimensions of the coded video. + */ + int coded_width; + int coded_height; + + /** + * The format of the coded data, corresponds to enum AVPixelFormat for video + * and for enum AVSampleFormat for audio. + * + * Note that a decoder can have considerable freedom in how exactly it + * decodes the data, so the format reported here might be different from the + * one returned by a decoder. + */ + int format; +} AVCodecParserContext; + +typedef struct AVCodecParser { + int codec_ids[5]; /* several codec IDs are permitted */ + int priv_data_size; + int (*parser_init)(AVCodecParserContext *s); + /* This callback never returns an error, a negative value means that + * the frame start was in a previous packet. */ + int (*parser_parse)(AVCodecParserContext *s, + AVCodecContext *avctx, + const uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *buf, int buf_size); + void (*parser_close)(AVCodecParserContext *s); + int (*split)(AVCodecContext *avctx, const uint8_t *buf, int buf_size); + struct AVCodecParser *next; +} AVCodecParser; + +AVCodecParser *av_parser_next(const AVCodecParser *c); + +void av_register_codec_parser(AVCodecParser *parser); +AVCodecParserContext *av_parser_init(int codec_id); + +/** + * Parse a packet. + * + * @param s parser context. + * @param avctx codec context. + * @param poutbuf set to pointer to parsed buffer or NULL if not yet finished. + * @param poutbuf_size set to size of parsed buffer or zero if not yet finished. + * @param buf input buffer. + * @param buf_size buffer size in bytes without the padding. I.e. the full buffer + size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE. + To signal EOF, this should be 0 (so that the last frame + can be output). + * @param pts input presentation timestamp. + * @param dts input decoding timestamp. + * @param pos input byte position in stream. + * @return the number of bytes of the input bitstream used. + * + * Example: + * @code + * while(in_len){ + * len = av_parser_parse2(myparser, AVCodecContext, &data, &size, + * in_data, in_len, + * pts, dts, pos); + * in_data += len; + * in_len -= len; + * + * if(size) + * decode_frame(data, size); + * } + * @endcode + */ +int av_parser_parse2(AVCodecParserContext *s, + AVCodecContext *avctx, + uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *buf, int buf_size, + int64_t pts, int64_t dts, + int64_t pos); + +/** + * @return 0 if the output buffer is a subset of the input, 1 if it is allocated and must be freed + * @deprecated use AVBitStreamFilter + */ +int av_parser_change(AVCodecParserContext *s, + AVCodecContext *avctx, + uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *buf, int buf_size, int keyframe); +void av_parser_close(AVCodecParserContext *s); + +/** + * @} + * @} + */ + +/** + * @addtogroup lavc_encoding + * @{ + */ + +/** + * Find a registered encoder with a matching codec ID. + * + * @param id AVCodecID of the requested encoder + * @return An encoder if one was found, NULL otherwise. + */ +AVCodec *avcodec_find_encoder(enum AVCodecID id); + +/** + * Find a registered encoder with the specified name. + * + * @param name name of the requested encoder + * @return An encoder if one was found, NULL otherwise. + */ +AVCodec *avcodec_find_encoder_by_name(const char *name); + +/** + * Encode a frame of audio. + * + * Takes input samples from frame and writes the next output packet, if + * available, to avpkt. The output packet does not necessarily contain data for + * the most recent frame, as encoders can delay, split, and combine input frames + * internally as needed. + * + * @param avctx codec context + * @param avpkt output AVPacket. + * The user can supply an output buffer by setting + * avpkt->data and avpkt->size prior to calling the + * function, but if the size of the user-provided data is not + * large enough, encoding will fail. If avpkt->data and + * avpkt->size are set, avpkt->destruct must also be set. All + * other AVPacket fields will be reset by the encoder using + * av_init_packet(). If avpkt->data is NULL, the encoder will + * allocate it. The encoder will set avpkt->size to the size + * of the output packet. + * + * If this function fails or produces no output, avpkt will be + * freed using av_packet_unref(). + * @param[in] frame AVFrame containing the raw audio data to be encoded. + * May be NULL when flushing an encoder that has the + * AV_CODEC_CAP_DELAY capability set. + * If AV_CODEC_CAP_VARIABLE_FRAME_SIZE is set, then each frame + * can have any number of samples. + * If it is not set, frame->nb_samples must be equal to + * avctx->frame_size for all frames except the last. + * The final frame may be smaller than avctx->frame_size. + * @param[out] got_packet_ptr This field is set to 1 by libavcodec if the + * output packet is non-empty, and to 0 if it is + * empty. If the function returns an error, the + * packet can be assumed to be invalid, and the + * value of got_packet_ptr is undefined and should + * not be used. + * @return 0 on success, negative error code on failure + * + * @deprecated use avcodec_send_frame()/avcodec_receive_packet() instead + */ +attribute_deprecated +int avcodec_encode_audio2(AVCodecContext *avctx, AVPacket *avpkt, + const AVFrame *frame, int *got_packet_ptr); + +/** + * Encode a frame of video. + * + * Takes input raw video data from frame and writes the next output packet, if + * available, to avpkt. The output packet does not necessarily contain data for + * the most recent frame, as encoders can delay and reorder input frames + * internally as needed. + * + * @param avctx codec context + * @param avpkt output AVPacket. + * The user can supply an output buffer by setting + * avpkt->data and avpkt->size prior to calling the + * function, but if the size of the user-provided data is not + * large enough, encoding will fail. All other AVPacket fields + * will be reset by the encoder using av_init_packet(). If + * avpkt->data is NULL, the encoder will allocate it. + * The encoder will set avpkt->size to the size of the + * output packet. The returned data (if any) belongs to the + * caller, he is responsible for freeing it. + * + * If this function fails or produces no output, avpkt will be + * freed using av_packet_unref(). + * @param[in] frame AVFrame containing the raw video data to be encoded. + * May be NULL when flushing an encoder that has the + * AV_CODEC_CAP_DELAY capability set. + * @param[out] got_packet_ptr This field is set to 1 by libavcodec if the + * output packet is non-empty, and to 0 if it is + * empty. If the function returns an error, the + * packet can be assumed to be invalid, and the + * value of got_packet_ptr is undefined and should + * not be used. + * @return 0 on success, negative error code on failure + * + * @deprecated use avcodec_send_frame()/avcodec_receive_packet() instead + */ +attribute_deprecated +int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt, + const AVFrame *frame, int *got_packet_ptr); + +int avcodec_encode_subtitle(AVCodecContext *avctx, uint8_t *buf, int buf_size, + const AVSubtitle *sub); + + +/** + * @} + */ + +#if FF_API_AVCODEC_RESAMPLE +/** + * @defgroup lavc_resample Audio resampling + * @ingroup libavc + * @deprecated use libswresample instead + * + * @{ + */ +struct ReSampleContext; +struct AVResampleContext; + +typedef struct ReSampleContext ReSampleContext; + +/** + * Initialize audio resampling context. + * + * @param output_channels number of output channels + * @param input_channels number of input channels + * @param output_rate output sample rate + * @param input_rate input sample rate + * @param sample_fmt_out requested output sample format + * @param sample_fmt_in input sample format + * @param filter_length length of each FIR filter in the filterbank relative to the cutoff frequency + * @param log2_phase_count log2 of the number of entries in the polyphase filterbank + * @param linear if 1 then the used FIR filter will be linearly interpolated + between the 2 closest, if 0 the closest will be used + * @param cutoff cutoff frequency, 1.0 corresponds to half the output sampling rate + * @return allocated ReSampleContext, NULL if error occurred + */ +attribute_deprecated +ReSampleContext *av_audio_resample_init(int output_channels, int input_channels, + int output_rate, int input_rate, + enum AVSampleFormat sample_fmt_out, + enum AVSampleFormat sample_fmt_in, + int filter_length, int log2_phase_count, + int linear, double cutoff); + +attribute_deprecated +int audio_resample(ReSampleContext *s, short *output, short *input, int nb_samples); + +/** + * Free resample context. + * + * @param s a non-NULL pointer to a resample context previously + * created with av_audio_resample_init() + */ +attribute_deprecated +void audio_resample_close(ReSampleContext *s); + + +/** + * Initialize an audio resampler. + * Note, if either rate is not an integer then simply scale both rates up so they are. + * @param filter_length length of each FIR filter in the filterbank relative to the cutoff freq + * @param log2_phase_count log2 of the number of entries in the polyphase filterbank + * @param linear If 1 then the used FIR filter will be linearly interpolated + between the 2 closest, if 0 the closest will be used + * @param cutoff cutoff frequency, 1.0 corresponds to half the output sampling rate + */ +attribute_deprecated +struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff); + +/** + * Resample an array of samples using a previously configured context. + * @param src an array of unconsumed samples + * @param consumed the number of samples of src which have been consumed are returned here + * @param src_size the number of unconsumed samples available + * @param dst_size the amount of space in samples available in dst + * @param update_ctx If this is 0 then the context will not be modified, that way several channels can be resampled with the same context. + * @return the number of samples written in dst or -1 if an error occurred + */ +attribute_deprecated +int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx); + + +/** + * Compensate samplerate/timestamp drift. The compensation is done by changing + * the resampler parameters, so no audible clicks or similar distortions occur + * @param compensation_distance distance in output samples over which the compensation should be performed + * @param sample_delta number of output samples which should be output less + * + * example: av_resample_compensate(c, 10, 500) + * here instead of 510 samples only 500 samples would be output + * + * note, due to rounding the actual compensation might be slightly different, + * especially if the compensation_distance is large and the in_rate used during init is small + */ +attribute_deprecated +void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance); +attribute_deprecated +void av_resample_close(struct AVResampleContext *c); + +/** + * @} + */ +#endif + +#if FF_API_AVPICTURE +/** + * @addtogroup lavc_picture + * @{ + */ + +/** + * @deprecated unused + */ +attribute_deprecated +int avpicture_alloc(AVPicture *picture, enum AVPixelFormat pix_fmt, int width, int height); + +/** + * @deprecated unused + */ +attribute_deprecated +void avpicture_free(AVPicture *picture); + +/** + * @deprecated use av_image_fill_arrays() instead. + */ +attribute_deprecated +int avpicture_fill(AVPicture *picture, const uint8_t *ptr, + enum AVPixelFormat pix_fmt, int width, int height); + +/** + * @deprecated use av_image_copy_to_buffer() instead. + */ +attribute_deprecated +int avpicture_layout(const AVPicture *src, enum AVPixelFormat pix_fmt, + int width, int height, + unsigned char *dest, int dest_size); + +/** + * @deprecated use av_image_get_buffer_size() instead. + */ +attribute_deprecated +int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height); + +/** + * @deprecated av_image_copy() instead. + */ +attribute_deprecated +void av_picture_copy(AVPicture *dst, const AVPicture *src, + enum AVPixelFormat pix_fmt, int width, int height); + +/** + * @deprecated unused + */ +attribute_deprecated +int av_picture_crop(AVPicture *dst, const AVPicture *src, + enum AVPixelFormat pix_fmt, int top_band, int left_band); + +/** + * @deprecated unused + */ +attribute_deprecated +int av_picture_pad(AVPicture *dst, const AVPicture *src, int height, int width, enum AVPixelFormat pix_fmt, + int padtop, int padbottom, int padleft, int padright, int *color); + +/** + * @} + */ +#endif + +/** + * @defgroup lavc_misc Utility functions + * @ingroup libavc + * + * Miscellaneous utility functions related to both encoding and decoding + * (or neither). + * @{ + */ + +/** + * @defgroup lavc_misc_pixfmt Pixel formats + * + * Functions for working with pixel formats. + * @{ + */ + +#if FF_API_GETCHROMA +/** + * @deprecated Use av_pix_fmt_get_chroma_sub_sample + */ + +attribute_deprecated +void avcodec_get_chroma_sub_sample(enum AVPixelFormat pix_fmt, int *h_shift, int *v_shift); +#endif + +/** + * Return a value representing the fourCC code associated to the + * pixel format pix_fmt, or 0 if no associated fourCC code can be + * found. + */ +unsigned int avcodec_pix_fmt_to_codec_tag(enum AVPixelFormat pix_fmt); + +/** + * @deprecated see av_get_pix_fmt_loss() + */ +int avcodec_get_pix_fmt_loss(enum AVPixelFormat dst_pix_fmt, enum AVPixelFormat src_pix_fmt, + int has_alpha); + +/** + * Find the best pixel format to convert to given a certain source pixel + * format. When converting from one pixel format to another, information loss + * may occur. For example, when converting from RGB24 to GRAY, the color + * information will be lost. Similarly, other losses occur when converting from + * some formats to other formats. avcodec_find_best_pix_fmt_of_2() searches which of + * the given pixel formats should be used to suffer the least amount of loss. + * The pixel formats from which it chooses one, are determined by the + * pix_fmt_list parameter. + * + * + * @param[in] pix_fmt_list AV_PIX_FMT_NONE terminated array of pixel formats to choose from + * @param[in] src_pix_fmt source pixel format + * @param[in] has_alpha Whether the source pixel format alpha channel is used. + * @param[out] loss_ptr Combination of flags informing you what kind of losses will occur. + * @return The best pixel format to convert to or -1 if none was found. + */ +enum AVPixelFormat avcodec_find_best_pix_fmt_of_list(const enum AVPixelFormat *pix_fmt_list, + enum AVPixelFormat src_pix_fmt, + int has_alpha, int *loss_ptr); + +/** + * @deprecated see av_find_best_pix_fmt_of_2() + */ +enum AVPixelFormat avcodec_find_best_pix_fmt_of_2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2, + enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr); + +attribute_deprecated +enum AVPixelFormat avcodec_find_best_pix_fmt2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2, + enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr); + +enum AVPixelFormat avcodec_default_get_format(struct AVCodecContext *s, const enum AVPixelFormat * fmt); + +/** + * @} + */ + +#if FF_API_SET_DIMENSIONS +/** + * @deprecated this function is not supposed to be used from outside of lavc + */ +attribute_deprecated +void avcodec_set_dimensions(AVCodecContext *s, int width, int height); +#endif + +#if FF_API_TAG_STRING +/** + * Put a string representing the codec tag codec_tag in buf. + * + * @param buf buffer to place codec tag in + * @param buf_size size in bytes of buf + * @param codec_tag codec tag to assign + * @return the length of the string that would have been generated if + * enough space had been available, excluding the trailing null + * + * @deprecated see av_fourcc_make_string() and av_fourcc2str(). + */ +attribute_deprecated +size_t av_get_codec_tag_string(char *buf, size_t buf_size, unsigned int codec_tag); +#endif + +void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode); + +/** + * Return a name for the specified profile, if available. + * + * @param codec the codec that is searched for the given profile + * @param profile the profile value for which a name is requested + * @return A name for the profile if found, NULL otherwise. + */ +const char *av_get_profile_name(const AVCodec *codec, int profile); + +/** + * Return a name for the specified profile, if available. + * + * @param codec_id the ID of the codec to which the requested profile belongs + * @param profile the profile value for which a name is requested + * @return A name for the profile if found, NULL otherwise. + * + * @note unlike av_get_profile_name(), which searches a list of profiles + * supported by a specific decoder or encoder implementation, this + * function searches the list of profiles from the AVCodecDescriptor + */ +const char *avcodec_profile_name(enum AVCodecID codec_id, int profile); + +int avcodec_default_execute(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2),void *arg, int *ret, int count, int size); +int avcodec_default_execute2(AVCodecContext *c, int (*func)(AVCodecContext *c2, void *arg2, int, int),void *arg, int *ret, int count); +//FIXME func typedef + +/** + * Fill AVFrame audio data and linesize pointers. + * + * The buffer buf must be a preallocated buffer with a size big enough + * to contain the specified samples amount. The filled AVFrame data + * pointers will point to this buffer. + * + * AVFrame extended_data channel pointers are allocated if necessary for + * planar audio. + * + * @param frame the AVFrame + * frame->nb_samples must be set prior to calling the + * function. This function fills in frame->data, + * frame->extended_data, frame->linesize[0]. + * @param nb_channels channel count + * @param sample_fmt sample format + * @param buf buffer to use for frame data + * @param buf_size size of buffer + * @param align plane size sample alignment (0 = default) + * @return >=0 on success, negative error code on failure + * @todo return the size in bytes required to store the samples in + * case of success, at the next libavutil bump + */ +int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels, + enum AVSampleFormat sample_fmt, const uint8_t *buf, + int buf_size, int align); + +/** + * Reset the internal decoder state / flush internal buffers. Should be called + * e.g. when seeking or when switching to a different stream. + * + * @note when refcounted frames are not used (i.e. avctx->refcounted_frames is 0), + * this invalidates the frames previously returned from the decoder. When + * refcounted frames are used, the decoder just releases any references it might + * keep internally, but the caller's reference remains valid. + */ +void avcodec_flush_buffers(AVCodecContext *avctx); + +/** + * Return codec bits per sample. + * + * @param[in] codec_id the codec + * @return Number of bits per sample or zero if unknown for the given codec. + */ +int av_get_bits_per_sample(enum AVCodecID codec_id); + +/** + * Return the PCM codec associated with a sample format. + * @param be endianness, 0 for little, 1 for big, + * -1 (or anything else) for native + * @return AV_CODEC_ID_PCM_* or AV_CODEC_ID_NONE + */ +enum AVCodecID av_get_pcm_codec(enum AVSampleFormat fmt, int be); + +/** + * Return codec bits per sample. + * Only return non-zero if the bits per sample is exactly correct, not an + * approximation. + * + * @param[in] codec_id the codec + * @return Number of bits per sample or zero if unknown for the given codec. + */ +int av_get_exact_bits_per_sample(enum AVCodecID codec_id); + +/** + * Return audio frame duration. + * + * @param avctx codec context + * @param frame_bytes size of the frame, or 0 if unknown + * @return frame duration, in samples, if known. 0 if not able to + * determine. + */ +int av_get_audio_frame_duration(AVCodecContext *avctx, int frame_bytes); + +/** + * This function is the same as av_get_audio_frame_duration(), except it works + * with AVCodecParameters instead of an AVCodecContext. + */ +int av_get_audio_frame_duration2(AVCodecParameters *par, int frame_bytes); + +#if FF_API_OLD_BSF +typedef struct AVBitStreamFilterContext { + void *priv_data; + const struct AVBitStreamFilter *filter; + AVCodecParserContext *parser; + struct AVBitStreamFilterContext *next; + /** + * Internal default arguments, used if NULL is passed to av_bitstream_filter_filter(). + * Not for access by library users. + */ + char *args; +} AVBitStreamFilterContext; +#endif + +typedef struct AVBSFInternal AVBSFInternal; + +/** + * The bitstream filter state. + * + * This struct must be allocated with av_bsf_alloc() and freed with + * av_bsf_free(). + * + * The fields in the struct will only be changed (by the caller or by the + * filter) as described in their documentation, and are to be considered + * immutable otherwise. + */ +typedef struct AVBSFContext { + /** + * A class for logging and AVOptions + */ + const AVClass *av_class; + + /** + * The bitstream filter this context is an instance of. + */ + const struct AVBitStreamFilter *filter; + + /** + * Opaque libavcodec internal data. Must not be touched by the caller in any + * way. + */ + AVBSFInternal *internal; + + /** + * Opaque filter-specific private data. If filter->priv_class is non-NULL, + * this is an AVOptions-enabled struct. + */ + void *priv_data; + + /** + * Parameters of the input stream. This field is allocated in + * av_bsf_alloc(), it needs to be filled by the caller before + * av_bsf_init(). + */ + AVCodecParameters *par_in; + + /** + * Parameters of the output stream. This field is allocated in + * av_bsf_alloc(), it is set by the filter in av_bsf_init(). + */ + AVCodecParameters *par_out; + + /** + * The timebase used for the timestamps of the input packets. Set by the + * caller before av_bsf_init(). + */ + AVRational time_base_in; + + /** + * The timebase used for the timestamps of the output packets. Set by the + * filter in av_bsf_init(). + */ + AVRational time_base_out; +} AVBSFContext; + +typedef struct AVBitStreamFilter { + const char *name; + + /** + * A list of codec ids supported by the filter, terminated by + * AV_CODEC_ID_NONE. + * May be NULL, in that case the bitstream filter works with any codec id. + */ + const enum AVCodecID *codec_ids; + + /** + * A class for the private data, used to declare bitstream filter private + * AVOptions. This field is NULL for bitstream filters that do not declare + * any options. + * + * If this field is non-NULL, the first member of the filter private data + * must be a pointer to AVClass, which will be set by libavcodec generic + * code to this class. + */ + const AVClass *priv_class; + + /***************************************************************** + * No fields below this line are part of the public API. They + * may not be used outside of libavcodec and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + + int priv_data_size; + int (*init)(AVBSFContext *ctx); + int (*filter)(AVBSFContext *ctx, AVPacket *pkt); + void (*close)(AVBSFContext *ctx); +} AVBitStreamFilter; + +#if FF_API_OLD_BSF +/** + * Register a bitstream filter. + * + * The filter will be accessible to the application code through + * av_bitstream_filter_next() or can be directly initialized with + * av_bitstream_filter_init(). + * + * @see avcodec_register_all() + */ +attribute_deprecated +void av_register_bitstream_filter(AVBitStreamFilter *bsf); + +/** + * Create and initialize a bitstream filter context given a bitstream + * filter name. + * + * The returned context must be freed with av_bitstream_filter_close(). + * + * @param name the name of the bitstream filter + * @return a bitstream filter context if a matching filter was found + * and successfully initialized, NULL otherwise + */ +attribute_deprecated +AVBitStreamFilterContext *av_bitstream_filter_init(const char *name); + +/** + * Filter bitstream. + * + * This function filters the buffer buf with size buf_size, and places the + * filtered buffer in the buffer pointed to by poutbuf. + * + * The output buffer must be freed by the caller. + * + * @param bsfc bitstream filter context created by av_bitstream_filter_init() + * @param avctx AVCodecContext accessed by the filter, may be NULL. + * If specified, this must point to the encoder context of the + * output stream the packet is sent to. + * @param args arguments which specify the filter configuration, may be NULL + * @param poutbuf pointer which is updated to point to the filtered buffer + * @param poutbuf_size pointer which is updated to the filtered buffer size in bytes + * @param buf buffer containing the data to filter + * @param buf_size size in bytes of buf + * @param keyframe set to non-zero if the buffer to filter corresponds to a key-frame packet data + * @return >= 0 in case of success, or a negative error code in case of failure + * + * If the return value is positive, an output buffer is allocated and + * is available in *poutbuf, and is distinct from the input buffer. + * + * If the return value is 0, the output buffer is not allocated and + * should be considered identical to the input buffer, or in case + * *poutbuf was set it points to the input buffer (not necessarily to + * its starting address). A special case is if *poutbuf was set to NULL and + * *poutbuf_size was set to 0, which indicates the packet should be dropped. + */ +attribute_deprecated +int av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc, + AVCodecContext *avctx, const char *args, + uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *buf, int buf_size, int keyframe); + +/** + * Release bitstream filter context. + * + * @param bsf the bitstream filter context created with + * av_bitstream_filter_init(), can be NULL + */ +attribute_deprecated +void av_bitstream_filter_close(AVBitStreamFilterContext *bsf); + +/** + * If f is NULL, return the first registered bitstream filter, + * if f is non-NULL, return the next registered bitstream filter + * after f, or NULL if f is the last one. + * + * This function can be used to iterate over all registered bitstream + * filters. + */ +attribute_deprecated +AVBitStreamFilter *av_bitstream_filter_next(const AVBitStreamFilter *f); +#endif + +/** + * @return a bitstream filter with the specified name or NULL if no such + * bitstream filter exists. + */ +const AVBitStreamFilter *av_bsf_get_by_name(const char *name); + +/** + * Iterate over all registered bitstream filters. + * + * @param opaque a pointer where libavcodec will store the iteration state. Must + * point to NULL to start the iteration. + * + * @return the next registered bitstream filter or NULL when the iteration is + * finished + */ +const AVBitStreamFilter *av_bsf_next(void **opaque); + +/** + * Allocate a context for a given bitstream filter. The caller must fill in the + * context parameters as described in the documentation and then call + * av_bsf_init() before sending any data to the filter. + * + * @param filter the filter for which to allocate an instance. + * @param ctx a pointer into which the pointer to the newly-allocated context + * will be written. It must be freed with av_bsf_free() after the + * filtering is done. + * + * @return 0 on success, a negative AVERROR code on failure + */ +int av_bsf_alloc(const AVBitStreamFilter *filter, AVBSFContext **ctx); + +/** + * Prepare the filter for use, after all the parameters and options have been + * set. + */ +int av_bsf_init(AVBSFContext *ctx); + +/** + * Submit a packet for filtering. + * + * After sending each packet, the filter must be completely drained by calling + * av_bsf_receive_packet() repeatedly until it returns AVERROR(EAGAIN) or + * AVERROR_EOF. + * + * @param pkt the packet to filter. The bitstream filter will take ownership of + * the packet and reset the contents of pkt. pkt is not touched if an error occurs. + * This parameter may be NULL, which signals the end of the stream (i.e. no more + * packets will be sent). That will cause the filter to output any packets it + * may have buffered internally. + * + * @return 0 on success, a negative AVERROR on error. + */ +int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt); + +/** + * Retrieve a filtered packet. + * + * @param[out] pkt this struct will be filled with the contents of the filtered + * packet. It is owned by the caller and must be freed using + * av_packet_unref() when it is no longer needed. + * This parameter should be "clean" (i.e. freshly allocated + * with av_packet_alloc() or unreffed with av_packet_unref()) + * when this function is called. If this function returns + * successfully, the contents of pkt will be completely + * overwritten by the returned data. On failure, pkt is not + * touched. + * + * @return 0 on success. AVERROR(EAGAIN) if more packets need to be sent to the + * filter (using av_bsf_send_packet()) to get more output. AVERROR_EOF if there + * will be no further output from the filter. Another negative AVERROR value if + * an error occurs. + * + * @note one input packet may result in several output packets, so after sending + * a packet with av_bsf_send_packet(), this function needs to be called + * repeatedly until it stops returning 0. It is also possible for a filter to + * output fewer packets than were sent to it, so this function may return + * AVERROR(EAGAIN) immediately after a successful av_bsf_send_packet() call. + */ +int av_bsf_receive_packet(AVBSFContext *ctx, AVPacket *pkt); + +/** + * Free a bitstream filter context and everything associated with it; write NULL + * into the supplied pointer. + */ +void av_bsf_free(AVBSFContext **ctx); + +/** + * Get the AVClass for AVBSFContext. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + */ +const AVClass *av_bsf_get_class(void); + +/** + * Structure for chain/list of bitstream filters. + * Empty list can be allocated by av_bsf_list_alloc(). + */ +typedef struct AVBSFList AVBSFList; + +/** + * Allocate empty list of bitstream filters. + * The list must be later freed by av_bsf_list_free() + * or finalized by av_bsf_list_finalize(). + * + * @return Pointer to @ref AVBSFList on success, NULL in case of failure + */ +AVBSFList *av_bsf_list_alloc(void); + +/** + * Free list of bitstream filters. + * + * @param lst Pointer to pointer returned by av_bsf_list_alloc() + */ +void av_bsf_list_free(AVBSFList **lst); + +/** + * Append bitstream filter to the list of bitstream filters. + * + * @param lst List to append to + * @param bsf Filter context to be appended + * + * @return >=0 on success, negative AVERROR in case of failure + */ +int av_bsf_list_append(AVBSFList *lst, AVBSFContext *bsf); + +/** + * Construct new bitstream filter context given it's name and options + * and append it to the list of bitstream filters. + * + * @param lst List to append to + * @param bsf_name Name of the bitstream filter + * @param options Options for the bitstream filter, can be set to NULL + * + * @return >=0 on success, negative AVERROR in case of failure + */ +int av_bsf_list_append2(AVBSFList *lst, const char * bsf_name, AVDictionary **options); +/** + * Finalize list of bitstream filters. + * + * This function will transform @ref AVBSFList to single @ref AVBSFContext, + * so the whole chain of bitstream filters can be treated as single filter + * freshly allocated by av_bsf_alloc(). + * If the call is successful, @ref AVBSFList structure is freed and lst + * will be set to NULL. In case of failure, caller is responsible for + * freeing the structure by av_bsf_list_free() + * + * @param lst Filter list structure to be transformed + * @param[out] bsf Pointer to be set to newly created @ref AVBSFContext structure + * representing the chain of bitstream filters + * + * @return >=0 on success, negative AVERROR in case of failure + */ +int av_bsf_list_finalize(AVBSFList **lst, AVBSFContext **bsf); + +/** + * Parse string describing list of bitstream filters and create single + * @ref AVBSFContext describing the whole chain of bitstream filters. + * Resulting @ref AVBSFContext can be treated as any other @ref AVBSFContext freshly + * allocated by av_bsf_alloc(). + * + * @param str String describing chain of bitstream filters in format + * `bsf1[=opt1=val1:opt2=val2][,bsf2]` + * @param[out] bsf Pointer to be set to newly created @ref AVBSFContext structure + * representing the chain of bitstream filters + * + * @return >=0 on success, negative AVERROR in case of failure + */ +int av_bsf_list_parse_str(const char *str, AVBSFContext **bsf); + +/** + * Get null/pass-through bitstream filter. + * + * @param[out] bsf Pointer to be set to new instance of pass-through bitstream filter + * + * @return + */ +int av_bsf_get_null_filter(AVBSFContext **bsf); + +/* memory */ + +/** + * Same behaviour av_fast_malloc but the buffer has additional + * AV_INPUT_BUFFER_PADDING_SIZE at the end which will always be 0. + * + * In addition the whole buffer will initially and after resizes + * be 0-initialized so that no uninitialized data will ever appear. + */ +void av_fast_padded_malloc(void *ptr, unsigned int *size, size_t min_size); + +/** + * Same behaviour av_fast_padded_malloc except that buffer will always + * be 0-initialized after call. + */ +void av_fast_padded_mallocz(void *ptr, unsigned int *size, size_t min_size); + +/** + * Encode extradata length to a buffer. Used by xiph codecs. + * + * @param s buffer to write to; must be at least (v/255+1) bytes long + * @param v size of extradata in bytes + * @return number of bytes written to the buffer. + */ +unsigned int av_xiphlacing(unsigned char *s, unsigned int v); + +#if FF_API_MISSING_SAMPLE +/** + * Log a generic warning message about a missing feature. This function is + * intended to be used internally by FFmpeg (libavcodec, libavformat, etc.) + * only, and would normally not be used by applications. + * @param[in] avc a pointer to an arbitrary struct of which the first field is + * a pointer to an AVClass struct + * @param[in] feature string containing the name of the missing feature + * @param[in] want_sample indicates if samples are wanted which exhibit this feature. + * If want_sample is non-zero, additional verbiage will be added to the log + * message which tells the user how to report samples to the development + * mailing list. + * @deprecated Use avpriv_report_missing_feature() instead. + */ +attribute_deprecated +void av_log_missing_feature(void *avc, const char *feature, int want_sample); + +/** + * Log a generic warning message asking for a sample. This function is + * intended to be used internally by FFmpeg (libavcodec, libavformat, etc.) + * only, and would normally not be used by applications. + * @param[in] avc a pointer to an arbitrary struct of which the first field is + * a pointer to an AVClass struct + * @param[in] msg string containing an optional message, or NULL if no message + * @deprecated Use avpriv_request_sample() instead. + */ +attribute_deprecated +void av_log_ask_for_sample(void *avc, const char *msg, ...) av_printf_format(2, 3); +#endif /* FF_API_MISSING_SAMPLE */ + +/** + * Register the hardware accelerator hwaccel. + */ +void av_register_hwaccel(AVHWAccel *hwaccel); + +/** + * If hwaccel is NULL, returns the first registered hardware accelerator, + * if hwaccel is non-NULL, returns the next registered hardware accelerator + * after hwaccel, or NULL if hwaccel is the last one. + */ +AVHWAccel *av_hwaccel_next(const AVHWAccel *hwaccel); + + +/** + * Lock operation used by lockmgr + */ +enum AVLockOp { + AV_LOCK_CREATE, ///< Create a mutex + AV_LOCK_OBTAIN, ///< Lock the mutex + AV_LOCK_RELEASE, ///< Unlock the mutex + AV_LOCK_DESTROY, ///< Free mutex resources +}; + +/** + * Register a user provided lock manager supporting the operations + * specified by AVLockOp. The "mutex" argument to the function points + * to a (void *) where the lockmgr should store/get a pointer to a user + * allocated mutex. It is NULL upon AV_LOCK_CREATE and equal to the + * value left by the last call for all other ops. If the lock manager is + * unable to perform the op then it should leave the mutex in the same + * state as when it was called and return a non-zero value. However, + * when called with AV_LOCK_DESTROY the mutex will always be assumed to + * have been successfully destroyed. If av_lockmgr_register succeeds + * it will return a non-negative value, if it fails it will return a + * negative value and destroy all mutex and unregister all callbacks. + * av_lockmgr_register is not thread-safe, it must be called from a + * single thread before any calls which make use of locking are used. + * + * @param cb User defined callback. av_lockmgr_register invokes calls + * to this callback and the previously registered callback. + * The callback will be used to create more than one mutex + * each of which must be backed by its own underlying locking + * mechanism (i.e. do not use a single static object to + * implement your lock manager). If cb is set to NULL the + * lockmgr will be unregistered. + */ +int av_lockmgr_register(int (*cb)(void **mutex, enum AVLockOp op)); + +/** + * Get the type of the given codec. + */ +enum AVMediaType avcodec_get_type(enum AVCodecID codec_id); + +/** + * Get the name of a codec. + * @return a static string identifying the codec; never NULL + */ +const char *avcodec_get_name(enum AVCodecID id); + +/** + * @return a positive value if s is open (i.e. avcodec_open2() was called on it + * with no corresponding avcodec_close()), 0 otherwise. + */ +int avcodec_is_open(AVCodecContext *s); + +/** + * @return a non-zero number if codec is an encoder, zero otherwise + */ +int av_codec_is_encoder(const AVCodec *codec); + +/** + * @return a non-zero number if codec is a decoder, zero otherwise + */ +int av_codec_is_decoder(const AVCodec *codec); + +/** + * @return descriptor for given codec ID or NULL if no descriptor exists. + */ +const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id); + +/** + * Iterate over all codec descriptors known to libavcodec. + * + * @param prev previous descriptor. NULL to get the first descriptor. + * + * @return next descriptor or NULL after the last descriptor + */ +const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev); + +/** + * @return codec descriptor with the given name or NULL if no such descriptor + * exists. + */ +const AVCodecDescriptor *avcodec_descriptor_get_by_name(const char *name); + +/** + * Allocate a CPB properties structure and initialize its fields to default + * values. + * + * @param size if non-NULL, the size of the allocated struct will be written + * here. This is useful for embedding it in side data. + * + * @return the newly allocated struct or NULL on failure + */ +AVCPBProperties *av_cpb_properties_alloc(size_t *size); + +/** + * @} + */ + +#endif /* AVCODEC_AVCODEC_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/avdct.h b/thrid-party/ffmpeg/include/libavcodec/avdct.h new file mode 100644 index 0000000000..272422e44c --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/avdct.h @@ -0,0 +1,84 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_AVDCT_H +#define AVCODEC_AVDCT_H + +#include "libavutil/opt.h" + +/** + * AVDCT context. + * @note function pointers can be NULL if the specific features have been + * disabled at build time. + */ +typedef struct AVDCT { + const AVClass *av_class; + + void (*idct)(int16_t *block /* align 16 */); + + /** + * IDCT input permutation. + * Several optimized IDCTs need a permutated input (relative to the + * normal order of the reference IDCT). + * This permutation must be performed before the idct_put/add. + * Note, normally this can be merged with the zigzag/alternate scan
+ * An example to avoid confusion: + * - (->decode coeffs -> zigzag reorder -> dequant -> reference IDCT -> ...) + * - (x -> reference DCT -> reference IDCT -> x) + * - (x -> reference DCT -> simple_mmx_perm = idct_permutation + * -> simple_idct_mmx -> x) + * - (-> decode coeffs -> zigzag reorder -> simple_mmx_perm -> dequant + * -> simple_idct_mmx -> ...) + */ + uint8_t idct_permutation[64]; + + void (*fdct)(int16_t *block /* align 16 */); + + + /** + * DCT algorithm. + * must use AVOptions to set this field. + */ + int dct_algo; + + /** + * IDCT algorithm. + * must use AVOptions to set this field. + */ + int idct_algo; + + void (*get_pixels)(int16_t *block /* align 16 */, + const uint8_t *pixels /* align 8 */, + ptrdiff_t line_size); + + int bits_per_sample; +} AVDCT; + +/** + * Allocates a AVDCT context. + * This needs to be initialized with avcodec_dct_init() after optionally + * configuring it with AVOptions. + * + * To free it use av_free() + */ +AVDCT *avcodec_dct_alloc(void); +int avcodec_dct_init(AVDCT *); + +const AVClass *avcodec_dct_get_class(void); + +#endif /* AVCODEC_AVDCT_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/avfft.h b/thrid-party/ffmpeg/include/libavcodec/avfft.h new file mode 100644 index 0000000000..0c0f9b8d8d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/avfft.h @@ -0,0 +1,118 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_AVFFT_H +#define AVCODEC_AVFFT_H + +/** + * @file + * @ingroup lavc_fft + * FFT functions + */ + +/** + * @defgroup lavc_fft FFT functions + * @ingroup lavc_misc + * + * @{ + */ + +typedef float FFTSample; + +typedef struct FFTComplex { + FFTSample re, im; +} FFTComplex; + +typedef struct FFTContext FFTContext; + +/** + * Set up a complex FFT. + * @param nbits log2 of the length of the input array + * @param inverse if 0 perform the forward transform, if 1 perform the inverse + */ +FFTContext *av_fft_init(int nbits, int inverse); + +/** + * Do the permutation needed BEFORE calling ff_fft_calc(). + */ +void av_fft_permute(FFTContext *s, FFTComplex *z); + +/** + * Do a complex FFT with the parameters defined in av_fft_init(). The + * input data must be permuted before. No 1.0/sqrt(n) normalization is done. + */ +void av_fft_calc(FFTContext *s, FFTComplex *z); + +void av_fft_end(FFTContext *s); + +FFTContext *av_mdct_init(int nbits, int inverse, double scale); +void av_imdct_calc(FFTContext *s, FFTSample *output, const FFTSample *input); +void av_imdct_half(FFTContext *s, FFTSample *output, const FFTSample *input); +void av_mdct_calc(FFTContext *s, FFTSample *output, const FFTSample *input); +void av_mdct_end(FFTContext *s); + +/* Real Discrete Fourier Transform */ + +enum RDFTransformType { + DFT_R2C, + IDFT_C2R, + IDFT_R2C, + DFT_C2R, +}; + +typedef struct RDFTContext RDFTContext; + +/** + * Set up a real FFT. + * @param nbits log2 of the length of the input array + * @param trans the type of transform + */ +RDFTContext *av_rdft_init(int nbits, enum RDFTransformType trans); +void av_rdft_calc(RDFTContext *s, FFTSample *data); +void av_rdft_end(RDFTContext *s); + +/* Discrete Cosine Transform */ + +typedef struct DCTContext DCTContext; + +enum DCTTransformType { + DCT_II = 0, + DCT_III, + DCT_I, + DST_I, +}; + +/** + * Set up DCT. + * + * @param nbits size of the input array: + * (1 << nbits) for DCT-II, DCT-III and DST-I + * (1 << nbits) + 1 for DCT-I + * @param type the type of transform + * + * @note the first element of the input of DST-I is ignored + */ +DCTContext *av_dct_init(int nbits, enum DCTTransformType type); +void av_dct_calc(DCTContext *s, FFTSample *data); +void av_dct_end (DCTContext *s); + +/** + * @} + */ + +#endif /* AVCODEC_AVFFT_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/d3d11va.h b/thrid-party/ffmpeg/include/libavcodec/d3d11va.h new file mode 100644 index 0000000000..6816b6c1e6 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/d3d11va.h @@ -0,0 +1,112 @@ +/* + * Direct3D11 HW acceleration + * + * copyright (c) 2009 Laurent Aimar + * copyright (c) 2015 Steve Lhomme + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_D3D11VA_H +#define AVCODEC_D3D11VA_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_d3d11va + * Public libavcodec D3D11VA header. + */ + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0602 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0602 +#endif + +#include +#include + +/** + * @defgroup lavc_codec_hwaccel_d3d11va Direct3D11 + * @ingroup lavc_codec_hwaccel + * + * @{ + */ + +#define FF_DXVA2_WORKAROUND_SCALING_LIST_ZIGZAG 1 ///< Work around for Direct3D11 and old UVD/UVD+ ATI video cards +#define FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO 2 ///< Work around for Direct3D11 and old Intel GPUs with ClearVideo interface + +/** + * This structure is used to provides the necessary configurations and data + * to the Direct3D11 FFmpeg HWAccel implementation. + * + * The application must make it available as AVCodecContext.hwaccel_context. + * + * Use av_d3d11va_alloc_context() exclusively to allocate an AVD3D11VAContext. + */ +typedef struct AVD3D11VAContext { + /** + * D3D11 decoder object + */ + ID3D11VideoDecoder *decoder; + + /** + * D3D11 VideoContext + */ + ID3D11VideoContext *video_context; + + /** + * D3D11 configuration used to create the decoder + */ + D3D11_VIDEO_DECODER_CONFIG *cfg; + + /** + * The number of surface in the surface array + */ + unsigned surface_count; + + /** + * The array of Direct3D surfaces used to create the decoder + */ + ID3D11VideoDecoderOutputView **surface; + + /** + * A bit field configuring the workarounds needed for using the decoder + */ + uint64_t workaround; + + /** + * Private to the FFmpeg AVHWAccel implementation + */ + unsigned report_id; + + /** + * Mutex to access video_context + */ + HANDLE context_mutex; +} AVD3D11VAContext; + +/** + * Allocate an AVD3D11VAContext. + * + * @return Newly-allocated AVD3D11VAContext or NULL on failure. + */ +AVD3D11VAContext *av_d3d11va_alloc_context(void); + +/** + * @} + */ + +#endif /* AVCODEC_D3D11VA_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/dirac.h b/thrid-party/ffmpeg/include/libavcodec/dirac.h new file mode 100644 index 0000000000..e6d9d346d9 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/dirac.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 Marco Gerards + * Copyright (C) 2009 David Conrad + * Copyright (C) 2011 Jordi Ortiz + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_DIRAC_H +#define AVCODEC_DIRAC_H + +/** + * @file + * Interface to Dirac Decoder/Encoder + * @author Marco Gerards + * @author David Conrad + * @author Jordi Ortiz + */ + +#include "avcodec.h" + +/** + * The spec limits the number of wavelet decompositions to 4 for both + * level 1 (VC-2) and 128 (long-gop default). + * 5 decompositions is the maximum before >16-bit buffers are needed. + * Schroedinger allows this for DD 9,7 and 13,7 wavelets only, limiting + * the others to 4 decompositions (or 3 for the fidelity filter). + * + * We use this instead of MAX_DECOMPOSITIONS to save some memory. + */ +#define MAX_DWT_LEVELS 5 + +/** + * Parse code values: + * + * Dirac Specification -> + * 9.6.1 Table 9.1 + * + * VC-2 Specification -> + * 10.4.1 Table 10.1 + */ + +enum DiracParseCodes { + DIRAC_PCODE_SEQ_HEADER = 0x00, + DIRAC_PCODE_END_SEQ = 0x10, + DIRAC_PCODE_AUX = 0x20, + DIRAC_PCODE_PAD = 0x30, + DIRAC_PCODE_PICTURE_CODED = 0x08, + DIRAC_PCODE_PICTURE_RAW = 0x48, + DIRAC_PCODE_PICTURE_LOW_DEL = 0xC8, + DIRAC_PCODE_PICTURE_HQ = 0xE8, + DIRAC_PCODE_INTER_NOREF_CO1 = 0x0A, + DIRAC_PCODE_INTER_NOREF_CO2 = 0x09, + DIRAC_PCODE_INTER_REF_CO1 = 0x0D, + DIRAC_PCODE_INTER_REF_CO2 = 0x0E, + DIRAC_PCODE_INTRA_REF_CO = 0x0C, + DIRAC_PCODE_INTRA_REF_RAW = 0x4C, + DIRAC_PCODE_INTRA_REF_PICT = 0xCC, + DIRAC_PCODE_MAGIC = 0x42424344, +}; + +typedef struct DiracVersionInfo { + int major; + int minor; +} DiracVersionInfo; + +typedef struct AVDiracSeqHeader { + unsigned width; + unsigned height; + uint8_t chroma_format; ///< 0: 444 1: 422 2: 420 + + uint8_t interlaced; + uint8_t top_field_first; + + uint8_t frame_rate_index; ///< index into dirac_frame_rate[] + uint8_t aspect_ratio_index; ///< index into dirac_aspect_ratio[] + + uint16_t clean_width; + uint16_t clean_height; + uint16_t clean_left_offset; + uint16_t clean_right_offset; + + uint8_t pixel_range_index; ///< index into dirac_pixel_range_presets[] + uint8_t color_spec_index; ///< index into dirac_color_spec_presets[] + + int profile; + int level; + + AVRational framerate; + AVRational sample_aspect_ratio; + + enum AVPixelFormat pix_fmt; + enum AVColorRange color_range; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; + enum AVColorSpace colorspace; + + DiracVersionInfo version; + int bit_depth; +} AVDiracSeqHeader; + +/** + * Parse a Dirac sequence header. + * + * @param dsh this function will allocate and fill an AVDiracSeqHeader struct + * and write it into this pointer. The caller must free it with + * av_free(). + * @param buf the data buffer + * @param buf_size the size of the data buffer in bytes + * @param log_ctx if non-NULL, this function will log errors here + * @return 0 on success, a negative AVERROR code on failure + */ +int av_dirac_parse_sequence_header(AVDiracSeqHeader **dsh, + const uint8_t *buf, size_t buf_size, + void *log_ctx); + +#endif /* AVCODEC_DIRAC_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/dv_profile.h b/thrid-party/ffmpeg/include/libavcodec/dv_profile.h new file mode 100644 index 0000000000..9380a66f07 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/dv_profile.h @@ -0,0 +1,83 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_DV_PROFILE_H +#define AVCODEC_DV_PROFILE_H + +#include + +#include "libavutil/pixfmt.h" +#include "libavutil/rational.h" +#include "avcodec.h" + +/* minimum number of bytes to read from a DV stream in order to + * determine the profile */ +#define DV_PROFILE_BYTES (6 * 80) /* 6 DIF blocks */ + + +/* + * AVDVProfile is used to express the differences between various + * DV flavors. For now it's primarily used for differentiating + * 525/60 and 625/50, but the plans are to use it for various + * DV specs as well (e.g. SMPTE314M vs. IEC 61834). + */ +typedef struct AVDVProfile { + int dsf; /* value of the dsf in the DV header */ + int video_stype; /* stype for VAUX source pack */ + int frame_size; /* total size of one frame in bytes */ + int difseg_size; /* number of DIF segments per DIF channel */ + int n_difchan; /* number of DIF channels per frame */ + AVRational time_base; /* 1/framerate */ + int ltc_divisor; /* FPS from the LTS standpoint */ + int height; /* picture height in pixels */ + int width; /* picture width in pixels */ + AVRational sar[2]; /* sample aspect ratios for 4:3 and 16:9 */ + enum AVPixelFormat pix_fmt; /* picture pixel format */ + int bpm; /* blocks per macroblock */ + const uint8_t *block_sizes; /* AC block sizes, in bits */ + int audio_stride; /* size of audio_shuffle table */ + int audio_min_samples[3]; /* min amount of audio samples */ + /* for 48kHz, 44.1kHz and 32kHz */ + int audio_samples_dist[5]; /* how many samples are supposed to be */ + /* in each frame in a 5 frames window */ + const uint8_t (*audio_shuffle)[9]; /* PCM shuffling table */ +} AVDVProfile; + +/** + * Get a DV profile for the provided compressed frame. + * + * @param sys the profile used for the previous frame, may be NULL + * @param frame the compressed data buffer + * @param buf_size size of the buffer in bytes + * @return the DV profile for the supplied data or NULL on failure + */ +const AVDVProfile *av_dv_frame_profile(const AVDVProfile *sys, + const uint8_t *frame, unsigned buf_size); + +/** + * Get a DV profile for the provided stream parameters. + */ +const AVDVProfile *av_dv_codec_profile(int width, int height, enum AVPixelFormat pix_fmt); + +/** + * Get a DV profile for the provided stream parameters. + * The frame rate is used as a best-effort parameter. + */ +const AVDVProfile *av_dv_codec_profile2(int width, int height, enum AVPixelFormat pix_fmt, AVRational frame_rate); + +#endif /* AVCODEC_DV_PROFILE_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/dxva2.h b/thrid-party/ffmpeg/include/libavcodec/dxva2.h new file mode 100644 index 0000000000..22c93992f2 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/dxva2.h @@ -0,0 +1,93 @@ +/* + * DXVA2 HW acceleration + * + * copyright (c) 2009 Laurent Aimar + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_DXVA2_H +#define AVCODEC_DXVA2_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_dxva2 + * Public libavcodec DXVA2 header. + */ + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0602 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0602 +#endif + +#include +#include +#include + +/** + * @defgroup lavc_codec_hwaccel_dxva2 DXVA2 + * @ingroup lavc_codec_hwaccel + * + * @{ + */ + +#define FF_DXVA2_WORKAROUND_SCALING_LIST_ZIGZAG 1 ///< Work around for DXVA2 and old UVD/UVD+ ATI video cards +#define FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO 2 ///< Work around for DXVA2 and old Intel GPUs with ClearVideo interface + +/** + * This structure is used to provides the necessary configurations and data + * to the DXVA2 FFmpeg HWAccel implementation. + * + * The application must make it available as AVCodecContext.hwaccel_context. + */ +struct dxva_context { + /** + * DXVA2 decoder object + */ + IDirectXVideoDecoder *decoder; + + /** + * DXVA2 configuration used to create the decoder + */ + const DXVA2_ConfigPictureDecode *cfg; + + /** + * The number of surface in the surface array + */ + unsigned surface_count; + + /** + * The array of Direct3D surfaces used to create the decoder + */ + LPDIRECT3DSURFACE9 *surface; + + /** + * A bit field configuring the workarounds needed for using the decoder + */ + uint64_t workaround; + + /** + * Private to the FFmpeg AVHWAccel implementation + */ + unsigned report_id; +}; + +/** + * @} + */ + +#endif /* AVCODEC_DXVA2_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/jni.h b/thrid-party/ffmpeg/include/libavcodec/jni.h new file mode 100644 index 0000000000..dd99e92611 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/jni.h @@ -0,0 +1,46 @@ +/* + * JNI public API functions + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_JNI_H +#define AVCODEC_JNI_H + +/* + * Manually set a Java virtual machine which will be used to retrieve the JNI + * environment. Once a Java VM is set it cannot be changed afterwards, meaning + * you can call multiple times av_jni_set_java_vm with the same Java VM pointer + * however it will error out if you try to set a different Java VM. + * + * @param vm Java virtual machine + * @param log_ctx context used for logging, can be NULL + * @return 0 on success, < 0 otherwise + */ +int av_jni_set_java_vm(void *vm, void *log_ctx); + +/* + * Get the Java virtual machine which has been set with av_jni_set_java_vm. + * + * @param vm Java virtual machine + * @return a pointer to the Java virtual machine + */ +void *av_jni_get_java_vm(void *log_ctx); + +#endif /* AVCODEC_JNI_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/mediacodec.h b/thrid-party/ffmpeg/include/libavcodec/mediacodec.h new file mode 100644 index 0000000000..5606d24a1e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/mediacodec.h @@ -0,0 +1,88 @@ +/* + * Android MediaCodec public API + * + * Copyright (c) 2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_MEDIACODEC_H +#define AVCODEC_MEDIACODEC_H + +#include "libavcodec/avcodec.h" + +/** + * This structure holds a reference to a android/view/Surface object that will + * be used as output by the decoder. + * + */ +typedef struct AVMediaCodecContext { + + /** + * android/view/Surface object reference. + */ + void *surface; + +} AVMediaCodecContext; + +/** + * Allocate and initialize a MediaCodec context. + * + * When decoding with MediaCodec is finished, the caller must free the + * MediaCodec context with av_mediacodec_default_free. + * + * @return a pointer to a newly allocated AVMediaCodecContext on success, NULL otherwise + */ +AVMediaCodecContext *av_mediacodec_alloc_context(void); + +/** + * Convenience function that sets up the MediaCodec context. + * + * @param avctx codec context + * @param ctx MediaCodec context to initialize + * @param surface reference to an android/view/Surface + * @return 0 on success, < 0 otherwise + */ +int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface); + +/** + * This function must be called to free the MediaCodec context initialized with + * av_mediacodec_default_init(). + * + * @param avctx codec context + */ +void av_mediacodec_default_free(AVCodecContext *avctx); + +/** + * Opaque structure representing a MediaCodec buffer to render. + */ +typedef struct MediaCodecBuffer AVMediaCodecBuffer; + +/** + * Release a MediaCodec buffer and render it to the surface that is associated + * with the decoder. This function should only be called once on a given + * buffer, once released the underlying buffer returns to the codec, thus + * subsequent calls to this function will have no effect. + * + * @param buffer the buffer to render + * @param render 1 to release and render the buffer to the surface or 0 to + * discard the buffer + * @return 0 on success, < 0 otherwise + */ +int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render); + +#endif /* AVCODEC_MEDIACODEC_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/qsv.h b/thrid-party/ffmpeg/include/libavcodec/qsv.h new file mode 100644 index 0000000000..b77158ec26 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/qsv.h @@ -0,0 +1,107 @@ +/* + * Intel MediaSDK QSV public API + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_QSV_H +#define AVCODEC_QSV_H + +#include + +#include "libavutil/buffer.h" + +/** + * This struct is used for communicating QSV parameters between libavcodec and + * the caller. It is managed by the caller and must be assigned to + * AVCodecContext.hwaccel_context. + * - decoding: hwaccel_context must be set on return from the get_format() + * callback + * - encoding: hwaccel_context must be set before avcodec_open2() + */ +typedef struct AVQSVContext { + /** + * If non-NULL, the session to use for encoding or decoding. + * Otherwise, libavcodec will try to create an internal session. + */ + mfxSession session; + + /** + * The IO pattern to use. + */ + int iopattern; + + /** + * Extra buffers to pass to encoder or decoder initialization. + */ + mfxExtBuffer **ext_buffers; + int nb_ext_buffers; + + /** + * Encoding only. If this field is set to non-zero by the caller, libavcodec + * will create an mfxExtOpaqueSurfaceAlloc extended buffer and pass it to + * the encoder initialization. This only makes sense if iopattern is also + * set to MFX_IOPATTERN_IN_OPAQUE_MEMORY. + * + * The number of allocated opaque surfaces will be the sum of the number + * required by the encoder and the user-provided value nb_opaque_surfaces. + * The array of the opaque surfaces will be exported to the caller through + * the opaque_surfaces field. + */ + int opaque_alloc; + + /** + * Encoding only, and only if opaque_alloc is set to non-zero. Before + * calling avcodec_open2(), the caller should set this field to the number + * of extra opaque surfaces to allocate beyond what is required by the + * encoder. + * + * On return from avcodec_open2(), this field will be set by libavcodec to + * the total number of allocated opaque surfaces. + */ + int nb_opaque_surfaces; + + /** + * Encoding only, and only if opaque_alloc is set to non-zero. On return + * from avcodec_open2(), this field will be used by libavcodec to export the + * array of the allocated opaque surfaces to the caller, so they can be + * passed to other parts of the pipeline. + * + * The buffer reference exported here is owned and managed by libavcodec, + * the callers should make their own reference with av_buffer_ref() and free + * it with av_buffer_unref() when it is no longer needed. + * + * The buffer data is an nb_opaque_surfaces-sized array of mfxFrameSurface1. + */ + AVBufferRef *opaque_surfaces; + + /** + * Encoding only, and only if opaque_alloc is set to non-zero. On return + * from avcodec_open2(), this field will be set to the surface type used in + * the opaque allocation request. + */ + int opaque_alloc_type; +} AVQSVContext; + +/** + * Allocate a new context. + * + * It must be freed by the caller with av_free(). + */ +AVQSVContext *av_qsv_alloc_context(void); + +#endif /* AVCODEC_QSV_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/vaapi.h b/thrid-party/ffmpeg/include/libavcodec/vaapi.h new file mode 100644 index 0000000000..bb28455329 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/vaapi.h @@ -0,0 +1,195 @@ +/* + * Video Acceleration API (shared data between FFmpeg and the video player) + * HW decode acceleration for MPEG-2, MPEG-4, H.264 and VC-1 + * + * Copyright (C) 2008-2009 Splitted-Desktop Systems + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_VAAPI_H +#define AVCODEC_VAAPI_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_vaapi + * Public libavcodec VA API header. + */ + +#include +#include "libavutil/attributes.h" +#include "version.h" + +#if FF_API_STRUCT_VAAPI_CONTEXT + +/** + * @defgroup lavc_codec_hwaccel_vaapi VA API Decoding + * @ingroup lavc_codec_hwaccel + * @{ + */ + +/** + * This structure is used to share data between the FFmpeg library and + * the client video application. + * This shall be zero-allocated and available as + * AVCodecContext.hwaccel_context. All user members can be set once + * during initialization or through each AVCodecContext.get_buffer() + * function call. In any case, they must be valid prior to calling + * decoding functions. + * + * Deprecated: use AVCodecContext.hw_frames_ctx instead. + */ +struct attribute_deprecated vaapi_context { + /** + * Window system dependent data + * + * - encoding: unused + * - decoding: Set by user + */ + void *display; + + /** + * Configuration ID + * + * - encoding: unused + * - decoding: Set by user + */ + uint32_t config_id; + + /** + * Context ID (video decode pipeline) + * + * - encoding: unused + * - decoding: Set by user + */ + uint32_t context_id; + +#if FF_API_VAAPI_CONTEXT + /** + * VAPictureParameterBuffer ID + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + uint32_t pic_param_buf_id; + + /** + * VAIQMatrixBuffer ID + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + uint32_t iq_matrix_buf_id; + + /** + * VABitPlaneBuffer ID (for VC-1 decoding) + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + uint32_t bitplane_buf_id; + + /** + * Slice parameter/data buffer IDs + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + uint32_t *slice_buf_ids; + + /** + * Number of effective slice buffer IDs to send to the HW + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + unsigned int n_slice_buf_ids; + + /** + * Size of pre-allocated slice_buf_ids + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + unsigned int slice_buf_ids_alloc; + + /** + * Pointer to VASliceParameterBuffers + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + void *slice_params; + + /** + * Size of a VASliceParameterBuffer element + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + unsigned int slice_param_size; + + /** + * Size of pre-allocated slice_params + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + unsigned int slice_params_alloc; + + /** + * Number of slices currently filled in + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + unsigned int slice_count; + + /** + * Pointer to slice data buffer base + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + const uint8_t *slice_data; + + /** + * Current size of slice data + * + * - encoding: unused + * - decoding: Set by libavcodec + */ + attribute_deprecated + uint32_t slice_data_size; +#endif +}; + +/* @} */ + +#endif /* FF_API_STRUCT_VAAPI_CONTEXT */ + +#endif /* AVCODEC_VAAPI_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/vda.h b/thrid-party/ffmpeg/include/libavcodec/vda.h new file mode 100644 index 0000000000..bde14e31d7 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/vda.h @@ -0,0 +1,230 @@ +/* + * VDA HW acceleration + * + * copyright (c) 2011 Sebastien Zwickert + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_VDA_H +#define AVCODEC_VDA_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_vda + * Public libavcodec VDA header. + */ + +#include "libavcodec/avcodec.h" + +#include + +// emmintrin.h is unable to compile with -std=c99 -Werror=missing-prototypes +// http://openradar.appspot.com/8026390 +#undef __GNUC_STDC_INLINE__ + +#define Picture QuickdrawPicture +#include +#undef Picture + +#include "libavcodec/version.h" + +// extra flags not defined in VDADecoder.h +enum { + kVDADecodeInfo_Asynchronous = 1UL << 0, + kVDADecodeInfo_FrameDropped = 1UL << 1 +}; + +/** + * @defgroup lavc_codec_hwaccel_vda VDA + * @ingroup lavc_codec_hwaccel + * + * @{ + */ + +/** + * This structure is used to provide the necessary configurations and data + * to the VDA FFmpeg HWAccel implementation. + * + * The application must make it available as AVCodecContext.hwaccel_context. + */ +struct vda_context { + /** + * VDA decoder object. + * + * - encoding: unused + * - decoding: Set/Unset by libavcodec. + */ + VDADecoder decoder; + + /** + * The Core Video pixel buffer that contains the current image data. + * + * encoding: unused + * decoding: Set by libavcodec. Unset by user. + */ + CVPixelBufferRef cv_buffer; + + /** + * Use the hardware decoder in synchronous mode. + * + * encoding: unused + * decoding: Set by user. + */ + int use_sync_decoding; + + /** + * The frame width. + * + * - encoding: unused + * - decoding: Set/Unset by user. + */ + int width; + + /** + * The frame height. + * + * - encoding: unused + * - decoding: Set/Unset by user. + */ + int height; + + /** + * The frame format. + * + * - encoding: unused + * - decoding: Set/Unset by user. + */ + int format; + + /** + * The pixel format for output image buffers. + * + * - encoding: unused + * - decoding: Set/Unset by user. + */ + OSType cv_pix_fmt_type; + + /** + * unused + */ + uint8_t *priv_bitstream; + + /** + * unused + */ + int priv_bitstream_size; + + /** + * unused + */ + int priv_allocated_size; + + /** + * Use av_buffer to manage buffer. + * When the flag is set, the CVPixelBuffers returned by the decoder will + * be released automatically, so you have to retain them if necessary. + * Not setting this flag may cause memory leak. + * + * encoding: unused + * decoding: Set by user. + */ + int use_ref_buffer; +}; + +/** Create the video decoder. */ +int ff_vda_create_decoder(struct vda_context *vda_ctx, + uint8_t *extradata, + int extradata_size); + +/** Destroy the video decoder. */ +int ff_vda_destroy_decoder(struct vda_context *vda_ctx); + +/** + * This struct holds all the information that needs to be passed + * between the caller and libavcodec for initializing VDA decoding. + * Its size is not a part of the public ABI, it must be allocated with + * av_vda_alloc_context() and freed with av_free(). + */ +typedef struct AVVDAContext { + /** + * VDA decoder object. Created and freed by the caller. + */ + VDADecoder decoder; + + /** + * The output callback that must be passed to VDADecoderCreate. + * Set by av_vda_alloc_context(). + */ + VDADecoderOutputCallback output_callback; + + /** + * CVPixelBuffer Format Type that VDA will use for decoded frames; set by + * the caller. + */ + OSType cv_pix_fmt_type; +} AVVDAContext; + +/** + * Allocate and initialize a VDA context. + * + * This function should be called from the get_format() callback when the caller + * selects the AV_PIX_FMT_VDA format. The caller must then create the decoder + * object (using the output callback provided by libavcodec) that will be used + * for VDA-accelerated decoding. + * + * When decoding with VDA is finished, the caller must destroy the decoder + * object and free the VDA context using av_free(). + * + * @return the newly allocated context or NULL on failure + */ +AVVDAContext *av_vda_alloc_context(void); + +/** + * This is a convenience function that creates and sets up the VDA context using + * an internal implementation. + * + * @param avctx the corresponding codec context + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int av_vda_default_init(AVCodecContext *avctx); + +/** + * This is a convenience function that creates and sets up the VDA context using + * an internal implementation. + * + * @param avctx the corresponding codec context + * @param vdactx the VDA context to use + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int av_vda_default_init2(AVCodecContext *avctx, AVVDAContext *vdactx); + +/** + * This function must be called to free the VDA context initialized with + * av_vda_default_init(). + * + * @param avctx the corresponding codec context + */ +void av_vda_default_free(AVCodecContext *avctx); + +/** + * @} + */ + +#endif /* AVCODEC_VDA_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/vdpau.h b/thrid-party/ffmpeg/include/libavcodec/vdpau.h new file mode 100644 index 0000000000..855d387d9a --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/vdpau.h @@ -0,0 +1,253 @@ +/* + * The Video Decode and Presentation API for UNIX (VDPAU) is used for + * hardware-accelerated decoding of MPEG-1/2, H.264 and VC-1. + * + * Copyright (C) 2008 NVIDIA + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_VDPAU_H +#define AVCODEC_VDPAU_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_vdpau + * Public libavcodec VDPAU header. + */ + + +/** + * @defgroup lavc_codec_hwaccel_vdpau VDPAU Decoder and Renderer + * @ingroup lavc_codec_hwaccel + * + * VDPAU hardware acceleration has two modules + * - VDPAU decoding + * - VDPAU presentation + * + * The VDPAU decoding module parses all headers using FFmpeg + * parsing mechanisms and uses VDPAU for the actual decoding. + * + * As per the current implementation, the actual decoding + * and rendering (API calls) are done as part of the VDPAU + * presentation (vo_vdpau.c) module. + * + * @{ + */ + +#include + +#include "libavutil/avconfig.h" +#include "libavutil/attributes.h" + +#include "avcodec.h" +#include "version.h" + +#if FF_API_BUFS_VDPAU +union AVVDPAUPictureInfo { + VdpPictureInfoH264 h264; + VdpPictureInfoMPEG1Or2 mpeg; + VdpPictureInfoVC1 vc1; + VdpPictureInfoMPEG4Part2 mpeg4; +}; +#endif + +struct AVCodecContext; +struct AVFrame; + +typedef int (*AVVDPAU_Render2)(struct AVCodecContext *, struct AVFrame *, + const VdpPictureInfo *, uint32_t, + const VdpBitstreamBuffer *); + +/** + * This structure is used to share data between the libavcodec library and + * the client video application. + * The user shall allocate the structure via the av_alloc_vdpau_hwaccel + * function and make it available as + * AVCodecContext.hwaccel_context. Members can be set by the user once + * during initialization or through each AVCodecContext.get_buffer() + * function call. In any case, they must be valid prior to calling + * decoding functions. + * + * The size of this structure is not a part of the public ABI and must not + * be used outside of libavcodec. Use av_vdpau_alloc_context() to allocate an + * AVVDPAUContext. + */ +typedef struct AVVDPAUContext { + /** + * VDPAU decoder handle + * + * Set by user. + */ + VdpDecoder decoder; + + /** + * VDPAU decoder render callback + * + * Set by the user. + */ + VdpDecoderRender *render; + +#if FF_API_BUFS_VDPAU + /** + * VDPAU picture information + * + * Set by libavcodec. + */ + attribute_deprecated + union AVVDPAUPictureInfo info; + + /** + * Allocated size of the bitstream_buffers table. + * + * Set by libavcodec. + */ + attribute_deprecated + int bitstream_buffers_allocated; + + /** + * Useful bitstream buffers in the bitstream buffers table. + * + * Set by libavcodec. + */ + attribute_deprecated + int bitstream_buffers_used; + + /** + * Table of bitstream buffers. + * The user is responsible for freeing this buffer using av_freep(). + * + * Set by libavcodec. + */ + attribute_deprecated + VdpBitstreamBuffer *bitstream_buffers; +#endif + AVVDPAU_Render2 render2; +} AVVDPAUContext; + +/** + * @brief allocation function for AVVDPAUContext + * + * Allows extending the struct without breaking API/ABI + */ +AVVDPAUContext *av_alloc_vdpaucontext(void); + +AVVDPAU_Render2 av_vdpau_hwaccel_get_render2(const AVVDPAUContext *); +void av_vdpau_hwaccel_set_render2(AVVDPAUContext *, AVVDPAU_Render2); + +/** + * Associate a VDPAU device with a codec context for hardware acceleration. + * This function is meant to be called from the get_format() codec callback, + * or earlier. It can also be called after avcodec_flush_buffers() to change + * the underlying VDPAU device mid-stream (e.g. to recover from non-transparent + * display preemption). + * + * @note get_format() must return AV_PIX_FMT_VDPAU if this function completes + * successfully. + * + * @param avctx decoding context whose get_format() callback is invoked + * @param device VDPAU device handle to use for hardware acceleration + * @param get_proc_address VDPAU device driver + * @param flags zero of more OR'd AV_HWACCEL_FLAG_* flags + * + * @return 0 on success, an AVERROR code on failure. + */ +int av_vdpau_bind_context(AVCodecContext *avctx, VdpDevice device, + VdpGetProcAddress *get_proc_address, unsigned flags); + +/** + * Gets the parameters to create an adequate VDPAU video surface for the codec + * context using VDPAU hardware decoding acceleration. + * + * @note Behavior is undefined if the context was not successfully bound to a + * VDPAU device using av_vdpau_bind_context(). + * + * @param avctx the codec context being used for decoding the stream + * @param type storage space for the VDPAU video surface chroma type + * (or NULL to ignore) + * @param width storage space for the VDPAU video surface pixel width + * (or NULL to ignore) + * @param height storage space for the VDPAU video surface pixel height + * (or NULL to ignore) + * + * @return 0 on success, a negative AVERROR code on failure. + */ +int av_vdpau_get_surface_parameters(AVCodecContext *avctx, VdpChromaType *type, + uint32_t *width, uint32_t *height); + +/** + * Allocate an AVVDPAUContext. + * + * @return Newly-allocated AVVDPAUContext or NULL on failure. + */ +AVVDPAUContext *av_vdpau_alloc_context(void); + +#if FF_API_VDPAU_PROFILE +/** + * Get a decoder profile that should be used for initializing a VDPAU decoder. + * Should be called from the AVCodecContext.get_format() callback. + * + * @deprecated Use av_vdpau_bind_context() instead. + * + * @param avctx the codec context being used for decoding the stream + * @param profile a pointer into which the result will be written on success. + * The contents of profile are undefined if this function returns + * an error. + * + * @return 0 on success (non-negative), a negative AVERROR on failure. + */ +attribute_deprecated +int av_vdpau_get_profile(AVCodecContext *avctx, VdpDecoderProfile *profile); +#endif + +#if FF_API_CAP_VDPAU +/** @brief The videoSurface is used for rendering. */ +#define FF_VDPAU_STATE_USED_FOR_RENDER 1 + +/** + * @brief The videoSurface is needed for reference/prediction. + * The codec manipulates this. + */ +#define FF_VDPAU_STATE_USED_FOR_REFERENCE 2 + +/** + * @brief This structure is used as a callback between the FFmpeg + * decoder (vd_) and presentation (vo_) module. + * This is used for defining a video frame containing surface, + * picture parameter, bitstream information etc which are passed + * between the FFmpeg decoder and its clients. + */ +struct vdpau_render_state { + VdpVideoSurface surface; ///< Used as rendered surface, never changed. + + int state; ///< Holds FF_VDPAU_STATE_* values. + + /** picture parameter information for all supported codecs */ + union AVVDPAUPictureInfo info; + + /** Describe size/location of the compressed video data. + Set to 0 when freeing bitstream_buffers. */ + int bitstream_buffers_allocated; + int bitstream_buffers_used; + /** The user is responsible for freeing this buffer using av_freep(). */ + VdpBitstreamBuffer *bitstream_buffers; +}; +#endif + +/* @}*/ + +#endif /* AVCODEC_VDPAU_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/version.h b/thrid-party/ffmpeg/include/libavcodec/version.h new file mode 100644 index 0000000000..10d9ac4eb3 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/version.h @@ -0,0 +1,243 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_VERSION_H +#define AVCODEC_VERSION_H + +/** + * @file + * @ingroup libavc + * Libavcodec version macros. + */ + +#include "libavutil/version.h" + +#define LIBAVCODEC_VERSION_MAJOR 57 +#define LIBAVCODEC_VERSION_MINOR 107 +#define LIBAVCODEC_VERSION_MICRO 100 + +#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ + LIBAVCODEC_VERSION_MINOR, \ + LIBAVCODEC_VERSION_MICRO) +#define LIBAVCODEC_VERSION AV_VERSION(LIBAVCODEC_VERSION_MAJOR, \ + LIBAVCODEC_VERSION_MINOR, \ + LIBAVCODEC_VERSION_MICRO) +#define LIBAVCODEC_BUILD LIBAVCODEC_VERSION_INT + +#define LIBAVCODEC_IDENT "Lavc" AV_STRINGIFY(LIBAVCODEC_VERSION) + +/** + * FF_API_* defines may be placed below to indicate public API that will be + * dropped at a future version bump. The defines themselves are not part of + * the public API and may change, break or disappear at any time. + * + * @note, when bumping the major version it is recommended to manually + * disable each FF_API_* in its own commit instead of disabling them all + * at once through the bump. This improves the git bisect-ability of the change. + */ + +#ifndef FF_API_VIMA_DECODER +#define FF_API_VIMA_DECODER (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AUDIO_CONVERT +#define FF_API_AUDIO_CONVERT (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AVCODEC_RESAMPLE +#define FF_API_AVCODEC_RESAMPLE FF_API_AUDIO_CONVERT +#endif +#ifndef FF_API_MISSING_SAMPLE +#define FF_API_MISSING_SAMPLE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LOWRES +#define FF_API_LOWRES (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_CAP_VDPAU +#define FF_API_CAP_VDPAU (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_BUFS_VDPAU +#define FF_API_BUFS_VDPAU (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_VOXWARE +#define FF_API_VOXWARE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_SET_DIMENSIONS +#define FF_API_SET_DIMENSIONS (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_DEBUG_MV +#define FF_API_DEBUG_MV (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AC_VLC +#define FF_API_AC_VLC (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_OLD_MSMPEG4 +#define FF_API_OLD_MSMPEG4 (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_ASPECT_EXTENDED +#define FF_API_ASPECT_EXTENDED (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_ARCH_ALPHA +#define FF_API_ARCH_ALPHA (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_XVMC +#define FF_API_XVMC (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_ERROR_RATE +#define FF_API_ERROR_RATE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_QSCALE_TYPE +#define FF_API_QSCALE_TYPE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_MB_TYPE +#define FF_API_MB_TYPE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_MAX_BFRAMES +#define FF_API_MAX_BFRAMES (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_NEG_LINESIZES +#define FF_API_NEG_LINESIZES (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_EMU_EDGE +#define FF_API_EMU_EDGE (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_ARCH_SH4 +#define FF_API_ARCH_SH4 (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_ARCH_SPARC +#define FF_API_ARCH_SPARC (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_UNUSED_MEMBERS +#define FF_API_UNUSED_MEMBERS (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_IDCT_XVIDMMX +#define FF_API_IDCT_XVIDMMX (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_INPUT_PRESERVED +#define FF_API_INPUT_PRESERVED (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_NORMALIZE_AQP +#define FF_API_NORMALIZE_AQP (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_GMC +#define FF_API_GMC (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_MV0 +#define FF_API_MV0 (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_CODEC_NAME +#define FF_API_CODEC_NAME (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AFD +#define FF_API_AFD (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_VISMV +/* XXX: don't forget to drop the -vismv documentation */ +#define FF_API_VISMV (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AUDIOENC_DELAY +#define FF_API_AUDIOENC_DELAY (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_VAAPI_CONTEXT +#define FF_API_VAAPI_CONTEXT (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_MERGE_SD +#define FF_API_MERGE_SD (LIBAVCODEC_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_AVCTX_TIMEBASE +#define FF_API_AVCTX_TIMEBASE (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_MPV_OPT +#define FF_API_MPV_OPT (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_STREAM_CODEC_TAG +#define FF_API_STREAM_CODEC_TAG (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_QUANT_BIAS +#define FF_API_QUANT_BIAS (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_RC_STRATEGY +#define FF_API_RC_STRATEGY (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_CODED_FRAME +#define FF_API_CODED_FRAME (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_MOTION_EST +#define FF_API_MOTION_EST (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_WITHOUT_PREFIX +#define FF_API_WITHOUT_PREFIX (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_SIDEDATA_ONLY_PKT +#define FF_API_SIDEDATA_ONLY_PKT (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_VDPAU_PROFILE +#define FF_API_VDPAU_PROFILE (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_CONVERGENCE_DURATION +#define FF_API_CONVERGENCE_DURATION (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_AVPICTURE +#define FF_API_AVPICTURE (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_AVPACKET_OLD_API +#define FF_API_AVPACKET_OLD_API (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_RTP_CALLBACK +#define FF_API_RTP_CALLBACK (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_VBV_DELAY +#define FF_API_VBV_DELAY (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_CODER_TYPE +#define FF_API_CODER_TYPE (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_STAT_BITS +#define FF_API_STAT_BITS (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_PRIVATE_OPT +#define FF_API_PRIVATE_OPT (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_ASS_TIMING +#define FF_API_ASS_TIMING (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_OLD_BSF +#define FF_API_OLD_BSF (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_COPY_CONTEXT +#define FF_API_COPY_CONTEXT (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_GET_CONTEXT_DEFAULTS +#define FF_API_GET_CONTEXT_DEFAULTS (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_NVENC_OLD_NAME +#define FF_API_NVENC_OLD_NAME (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_STRUCT_VAAPI_CONTEXT +#define FF_API_STRUCT_VAAPI_CONTEXT (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_MERGE_SD_API +#define FF_API_MERGE_SD_API (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_TAG_STRING +#define FF_API_TAG_STRING (LIBAVCODEC_VERSION_MAJOR < 59) +#endif +#ifndef FF_API_GETCHROMA +#define FF_API_GETCHROMA (LIBAVCODEC_VERSION_MAJOR < 59) +#endif + + +#endif /* AVCODEC_VERSION_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/videotoolbox.h b/thrid-party/ffmpeg/include/libavcodec/videotoolbox.h new file mode 100644 index 0000000000..af2db0d580 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/videotoolbox.h @@ -0,0 +1,127 @@ +/* + * Videotoolbox hardware acceleration + * + * copyright (c) 2012 Sebastien Zwickert + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_VIDEOTOOLBOX_H +#define AVCODEC_VIDEOTOOLBOX_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_videotoolbox + * Public libavcodec Videotoolbox header. + */ + +#include + +#define Picture QuickdrawPicture +#include +#undef Picture + +#include "libavcodec/avcodec.h" + +/** + * This struct holds all the information that needs to be passed + * between the caller and libavcodec for initializing Videotoolbox decoding. + * Its size is not a part of the public ABI, it must be allocated with + * av_videotoolbox_alloc_context() and freed with av_free(). + */ +typedef struct AVVideotoolboxContext { + /** + * Videotoolbox decompression session object. + * Created and freed the caller. + */ + VTDecompressionSessionRef session; + + /** + * The output callback that must be passed to the session. + * Set by av_videottoolbox_default_init() + */ + VTDecompressionOutputCallback output_callback; + + /** + * CVPixelBuffer Format Type that Videotoolbox will use for decoded frames. + * set by the caller. If this is set to 0, then no specific format is + * requested from the decoder, and its native format is output. + */ + OSType cv_pix_fmt_type; + + /** + * CoreMedia Format Description that Videotoolbox will use to create the decompression session. + * Set by the caller. + */ + CMVideoFormatDescriptionRef cm_fmt_desc; + + /** + * CoreMedia codec type that Videotoolbox will use to create the decompression session. + * Set by the caller. + */ + int cm_codec_type; +} AVVideotoolboxContext; + +/** + * Allocate and initialize a Videotoolbox context. + * + * This function should be called from the get_format() callback when the caller + * selects the AV_PIX_FMT_VIDETOOLBOX format. The caller must then create + * the decoder object (using the output callback provided by libavcodec) that + * will be used for Videotoolbox-accelerated decoding. + * + * When decoding with Videotoolbox is finished, the caller must destroy the decoder + * object and free the Videotoolbox context using av_free(). + * + * @return the newly allocated context or NULL on failure + */ +AVVideotoolboxContext *av_videotoolbox_alloc_context(void); + +/** + * This is a convenience function that creates and sets up the Videotoolbox context using + * an internal implementation. + * + * @param avctx the corresponding codec context + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int av_videotoolbox_default_init(AVCodecContext *avctx); + +/** + * This is a convenience function that creates and sets up the Videotoolbox context using + * an internal implementation. + * + * @param avctx the corresponding codec context + * @param vtctx the Videotoolbox context to use + * + * @return >= 0 on success, a negative AVERROR code on failure + */ +int av_videotoolbox_default_init2(AVCodecContext *avctx, AVVideotoolboxContext *vtctx); + +/** + * This function must be called to free the Videotoolbox context initialized with + * av_videotoolbox_default_init(). + * + * @param avctx the corresponding codec context + */ +void av_videotoolbox_default_free(AVCodecContext *avctx); + +/** + * @} + */ + +#endif /* AVCODEC_VIDEOTOOLBOX_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/vorbis_parser.h b/thrid-party/ffmpeg/include/libavcodec/vorbis_parser.h new file mode 100644 index 0000000000..789932ac49 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/vorbis_parser.h @@ -0,0 +1,74 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * A public API for Vorbis parsing + * + * Determines the duration for each packet. + */ + +#ifndef AVCODEC_VORBIS_PARSER_H +#define AVCODEC_VORBIS_PARSER_H + +#include + +typedef struct AVVorbisParseContext AVVorbisParseContext; + +/** + * Allocate and initialize the Vorbis parser using headers in the extradata. + */ +AVVorbisParseContext *av_vorbis_parse_init(const uint8_t *extradata, + int extradata_size); + +/** + * Free the parser and everything associated with it. + */ +void av_vorbis_parse_free(AVVorbisParseContext **s); + +#define VORBIS_FLAG_HEADER 0x00000001 +#define VORBIS_FLAG_COMMENT 0x00000002 +#define VORBIS_FLAG_SETUP 0x00000004 + +/** + * Get the duration for a Vorbis packet. + * + * If @p flags is @c NULL, + * special frames are considered invalid. + * + * @param s Vorbis parser context + * @param buf buffer containing a Vorbis frame + * @param buf_size size of the buffer + * @param flags flags for special frames + */ +int av_vorbis_parse_frame_flags(AVVorbisParseContext *s, const uint8_t *buf, + int buf_size, int *flags); + +/** + * Get the duration for a Vorbis packet. + * + * @param s Vorbis parser context + * @param buf buffer containing a Vorbis frame + * @param buf_size size of the buffer + */ +int av_vorbis_parse_frame(AVVorbisParseContext *s, const uint8_t *buf, + int buf_size); + +void av_vorbis_parse_reset(AVVorbisParseContext *s); + +#endif /* AVCODEC_VORBIS_PARSER_H */ diff --git a/thrid-party/ffmpeg/include/libavcodec/xvmc.h b/thrid-party/ffmpeg/include/libavcodec/xvmc.h new file mode 100644 index 0000000000..465ee78d6e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavcodec/xvmc.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003 Ivan Kalvachev + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_XVMC_H +#define AVCODEC_XVMC_H + +/** + * @file + * @ingroup lavc_codec_hwaccel_xvmc + * Public libavcodec XvMC header. + */ + +#include + +#include "libavutil/attributes.h" +#include "version.h" +#include "avcodec.h" + +/** + * @defgroup lavc_codec_hwaccel_xvmc XvMC + * @ingroup lavc_codec_hwaccel + * + * @{ + */ + +#define AV_XVMC_ID 0x1DC711C0 /**< special value to ensure that regular pixel routines haven't corrupted the struct + the number is 1337 speak for the letters IDCT MCo (motion compensation) */ + +struct attribute_deprecated xvmc_pix_fmt { + /** The field contains the special constant value AV_XVMC_ID. + It is used as a test that the application correctly uses the API, + and that there is no corruption caused by pixel routines. + - application - set during initialization + - libavcodec - unchanged + */ + int xvmc_id; + + /** Pointer to the block array allocated by XvMCCreateBlocks(). + The array has to be freed by XvMCDestroyBlocks(). + Each group of 64 values represents one data block of differential + pixel information (in MoCo mode) or coefficients for IDCT. + - application - set the pointer during initialization + - libavcodec - fills coefficients/pixel data into the array + */ + short* data_blocks; + + /** Pointer to the macroblock description array allocated by + XvMCCreateMacroBlocks() and freed by XvMCDestroyMacroBlocks(). + - application - set the pointer during initialization + - libavcodec - fills description data into the array + */ + XvMCMacroBlock* mv_blocks; + + /** Number of macroblock descriptions that can be stored in the mv_blocks + array. + - application - set during initialization + - libavcodec - unchanged + */ + int allocated_mv_blocks; + + /** Number of blocks that can be stored at once in the data_blocks array. + - application - set during initialization + - libavcodec - unchanged + */ + int allocated_data_blocks; + + /** Indicate that the hardware would interpret data_blocks as IDCT + coefficients and perform IDCT on them. + - application - set during initialization + - libavcodec - unchanged + */ + int idct; + + /** In MoCo mode it indicates that intra macroblocks are assumed to be in + unsigned format; same as the XVMC_INTRA_UNSIGNED flag. + - application - set during initialization + - libavcodec - unchanged + */ + int unsigned_intra; + + /** Pointer to the surface allocated by XvMCCreateSurface(). + It has to be freed by XvMCDestroySurface() on application exit. + It identifies the frame and its state on the video hardware. + - application - set during initialization + - libavcodec - unchanged + */ + XvMCSurface* p_surface; + +/** Set by the decoder before calling ff_draw_horiz_band(), + needed by the XvMCRenderSurface function. */ +//@{ + /** Pointer to the surface used as past reference + - application - unchanged + - libavcodec - set + */ + XvMCSurface* p_past_surface; + + /** Pointer to the surface used as future reference + - application - unchanged + - libavcodec - set + */ + XvMCSurface* p_future_surface; + + /** top/bottom field or frame + - application - unchanged + - libavcodec - set + */ + unsigned int picture_structure; + + /** XVMC_SECOND_FIELD - 1st or 2nd field in the sequence + - application - unchanged + - libavcodec - set + */ + unsigned int flags; +//}@ + + /** Number of macroblock descriptions in the mv_blocks array + that have already been passed to the hardware. + - application - zeroes it on get_buffer(). + A successful ff_draw_horiz_band() may increment it + with filled_mb_block_num or zero both. + - libavcodec - unchanged + */ + int start_mv_blocks_num; + + /** Number of new macroblock descriptions in the mv_blocks array (after + start_mv_blocks_num) that are filled by libavcodec and have to be + passed to the hardware. + - application - zeroes it on get_buffer() or after successful + ff_draw_horiz_band(). + - libavcodec - increment with one of each stored MB + */ + int filled_mv_blocks_num; + + /** Number of the next free data block; one data block consists of + 64 short values in the data_blocks array. + All blocks before this one have already been claimed by placing their + position into the corresponding block description structure field, + that are part of the mv_blocks array. + - application - zeroes it on get_buffer(). + A successful ff_draw_horiz_band() may zero it together + with start_mb_blocks_num. + - libavcodec - each decoded macroblock increases it by the number + of coded blocks it contains. + */ + int next_free_data_block_num; +}; + +/** + * @} + */ + +#endif /* AVCODEC_XVMC_H */ diff --git a/thrid-party/ffmpeg/include/libavformat/avformat.h b/thrid-party/ffmpeg/include/libavformat/avformat.h new file mode 100644 index 0000000000..b0de66ac14 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavformat/avformat.h @@ -0,0 +1,3008 @@ +/* + * copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_AVFORMAT_H +#define AVFORMAT_AVFORMAT_H + +/** + * @file + * @ingroup libavf + * Main libavformat public API header + */ + +/** + * @defgroup libavf libavformat + * I/O and Muxing/Demuxing Library + * + * Libavformat (lavf) is a library for dealing with various media container + * formats. Its main two purposes are demuxing - i.e. splitting a media file + * into component streams, and the reverse process of muxing - writing supplied + * data in a specified container format. It also has an @ref lavf_io + * "I/O module" which supports a number of protocols for accessing the data (e.g. + * file, tcp, http and others). Before using lavf, you need to call + * av_register_all() to register all compiled muxers, demuxers and protocols. + * Unless you are absolutely sure you won't use libavformat's network + * capabilities, you should also call avformat_network_init(). + * + * A supported input format is described by an AVInputFormat struct, conversely + * an output format is described by AVOutputFormat. You can iterate over all + * registered input/output formats using the av_iformat_next() / + * av_oformat_next() functions. The protocols layer is not part of the public + * API, so you can only get the names of supported protocols with the + * avio_enum_protocols() function. + * + * Main lavf structure used for both muxing and demuxing is AVFormatContext, + * which exports all information about the file being read or written. As with + * most Libavformat structures, its size is not part of public ABI, so it cannot be + * allocated on stack or directly with av_malloc(). To create an + * AVFormatContext, use avformat_alloc_context() (some functions, like + * avformat_open_input() might do that for you). + * + * Most importantly an AVFormatContext contains: + * @li the @ref AVFormatContext.iformat "input" or @ref AVFormatContext.oformat + * "output" format. It is either autodetected or set by user for input; + * always set by user for output. + * @li an @ref AVFormatContext.streams "array" of AVStreams, which describe all + * elementary streams stored in the file. AVStreams are typically referred to + * using their index in this array. + * @li an @ref AVFormatContext.pb "I/O context". It is either opened by lavf or + * set by user for input, always set by user for output (unless you are dealing + * with an AVFMT_NOFILE format). + * + * @section lavf_options Passing options to (de)muxers + * It is possible to configure lavf muxers and demuxers using the @ref avoptions + * mechanism. Generic (format-independent) libavformat options are provided by + * AVFormatContext, they can be examined from a user program by calling + * av_opt_next() / av_opt_find() on an allocated AVFormatContext (or its AVClass + * from avformat_get_class()). Private (format-specific) options are provided by + * AVFormatContext.priv_data if and only if AVInputFormat.priv_class / + * AVOutputFormat.priv_class of the corresponding format struct is non-NULL. + * Further options may be provided by the @ref AVFormatContext.pb "I/O context", + * if its AVClass is non-NULL, and the protocols layer. See the discussion on + * nesting in @ref avoptions documentation to learn how to access those. + * + * @section urls + * URL strings in libavformat are made of a scheme/protocol, a ':', and a + * scheme specific string. URLs without a scheme and ':' used for local files + * are supported but deprecated. "file:" should be used for local files. + * + * It is important that the scheme string is not taken from untrusted + * sources without checks. + * + * Note that some schemes/protocols are quite powerful, allowing access to + * both local and remote files, parts of them, concatenations of them, local + * audio and video devices and so on. + * + * @{ + * + * @defgroup lavf_decoding Demuxing + * @{ + * Demuxers read a media file and split it into chunks of data (@em packets). A + * @ref AVPacket "packet" contains one or more encoded frames which belongs to a + * single elementary stream. In the lavf API this process is represented by the + * avformat_open_input() function for opening a file, av_read_frame() for + * reading a single packet and finally avformat_close_input(), which does the + * cleanup. + * + * @section lavf_decoding_open Opening a media file + * The minimum information required to open a file is its URL, which + * is passed to avformat_open_input(), as in the following code: + * @code + * const char *url = "file:in.mp3"; + * AVFormatContext *s = NULL; + * int ret = avformat_open_input(&s, url, NULL, NULL); + * if (ret < 0) + * abort(); + * @endcode + * The above code attempts to allocate an AVFormatContext, open the + * specified file (autodetecting the format) and read the header, exporting the + * information stored there into s. Some formats do not have a header or do not + * store enough information there, so it is recommended that you call the + * avformat_find_stream_info() function which tries to read and decode a few + * frames to find missing information. + * + * In some cases you might want to preallocate an AVFormatContext yourself with + * avformat_alloc_context() and do some tweaking on it before passing it to + * avformat_open_input(). One such case is when you want to use custom functions + * for reading input data instead of lavf internal I/O layer. + * To do that, create your own AVIOContext with avio_alloc_context(), passing + * your reading callbacks to it. Then set the @em pb field of your + * AVFormatContext to newly created AVIOContext. + * + * Since the format of the opened file is in general not known until after + * avformat_open_input() has returned, it is not possible to set demuxer private + * options on a preallocated context. Instead, the options should be passed to + * avformat_open_input() wrapped in an AVDictionary: + * @code + * AVDictionary *options = NULL; + * av_dict_set(&options, "video_size", "640x480", 0); + * av_dict_set(&options, "pixel_format", "rgb24", 0); + * + * if (avformat_open_input(&s, url, NULL, &options) < 0) + * abort(); + * av_dict_free(&options); + * @endcode + * This code passes the private options 'video_size' and 'pixel_format' to the + * demuxer. They would be necessary for e.g. the rawvideo demuxer, since it + * cannot know how to interpret raw video data otherwise. If the format turns + * out to be something different than raw video, those options will not be + * recognized by the demuxer and therefore will not be applied. Such unrecognized + * options are then returned in the options dictionary (recognized options are + * consumed). The calling program can handle such unrecognized options as it + * wishes, e.g. + * @code + * AVDictionaryEntry *e; + * if (e = av_dict_get(options, "", NULL, AV_DICT_IGNORE_SUFFIX)) { + * fprintf(stderr, "Option %s not recognized by the demuxer.\n", e->key); + * abort(); + * } + * @endcode + * + * After you have finished reading the file, you must close it with + * avformat_close_input(). It will free everything associated with the file. + * + * @section lavf_decoding_read Reading from an opened file + * Reading data from an opened AVFormatContext is done by repeatedly calling + * av_read_frame() on it. Each call, if successful, will return an AVPacket + * containing encoded data for one AVStream, identified by + * AVPacket.stream_index. This packet may be passed straight into the libavcodec + * decoding functions avcodec_send_packet() or avcodec_decode_subtitle2() if the + * caller wishes to decode the data. + * + * AVPacket.pts, AVPacket.dts and AVPacket.duration timing information will be + * set if known. They may also be unset (i.e. AV_NOPTS_VALUE for + * pts/dts, 0 for duration) if the stream does not provide them. The timing + * information will be in AVStream.time_base units, i.e. it has to be + * multiplied by the timebase to convert them to seconds. + * + * If AVPacket.buf is set on the returned packet, then the packet is + * allocated dynamically and the user may keep it indefinitely. + * Otherwise, if AVPacket.buf is NULL, the packet data is backed by a + * static storage somewhere inside the demuxer and the packet is only valid + * until the next av_read_frame() call or closing the file. If the caller + * requires a longer lifetime, av_dup_packet() will make an av_malloc()ed copy + * of it. + * In both cases, the packet must be freed with av_packet_unref() when it is no + * longer needed. + * + * @section lavf_decoding_seek Seeking + * @} + * + * @defgroup lavf_encoding Muxing + * @{ + * Muxers take encoded data in the form of @ref AVPacket "AVPackets" and write + * it into files or other output bytestreams in the specified container format. + * + * The main API functions for muxing are avformat_write_header() for writing the + * file header, av_write_frame() / av_interleaved_write_frame() for writing the + * packets and av_write_trailer() for finalizing the file. + * + * At the beginning of the muxing process, the caller must first call + * avformat_alloc_context() to create a muxing context. The caller then sets up + * the muxer by filling the various fields in this context: + * + * - The @ref AVFormatContext.oformat "oformat" field must be set to select the + * muxer that will be used. + * - Unless the format is of the AVFMT_NOFILE type, the @ref AVFormatContext.pb + * "pb" field must be set to an opened IO context, either returned from + * avio_open2() or a custom one. + * - Unless the format is of the AVFMT_NOSTREAMS type, at least one stream must + * be created with the avformat_new_stream() function. The caller should fill + * the @ref AVStream.codecpar "stream codec parameters" information, such as the + * codec @ref AVCodecParameters.codec_type "type", @ref AVCodecParameters.codec_id + * "id" and other parameters (e.g. width / height, the pixel or sample format, + * etc.) as known. The @ref AVStream.time_base "stream timebase" should + * be set to the timebase that the caller desires to use for this stream (note + * that the timebase actually used by the muxer can be different, as will be + * described later). + * - It is advised to manually initialize only the relevant fields in + * AVCodecParameters, rather than using @ref avcodec_parameters_copy() during + * remuxing: there is no guarantee that the codec context values remain valid + * for both input and output format contexts. + * - The caller may fill in additional information, such as @ref + * AVFormatContext.metadata "global" or @ref AVStream.metadata "per-stream" + * metadata, @ref AVFormatContext.chapters "chapters", @ref + * AVFormatContext.programs "programs", etc. as described in the + * AVFormatContext documentation. Whether such information will actually be + * stored in the output depends on what the container format and the muxer + * support. + * + * When the muxing context is fully set up, the caller must call + * avformat_write_header() to initialize the muxer internals and write the file + * header. Whether anything actually is written to the IO context at this step + * depends on the muxer, but this function must always be called. Any muxer + * private options must be passed in the options parameter to this function. + * + * The data is then sent to the muxer by repeatedly calling av_write_frame() or + * av_interleaved_write_frame() (consult those functions' documentation for + * discussion on the difference between them; only one of them may be used with + * a single muxing context, they should not be mixed). Do note that the timing + * information on the packets sent to the muxer must be in the corresponding + * AVStream's timebase. That timebase is set by the muxer (in the + * avformat_write_header() step) and may be different from the timebase + * requested by the caller. + * + * Once all the data has been written, the caller must call av_write_trailer() + * to flush any buffered packets and finalize the output file, then close the IO + * context (if any) and finally free the muxing context with + * avformat_free_context(). + * @} + * + * @defgroup lavf_io I/O Read/Write + * @{ + * @section lavf_io_dirlist Directory listing + * The directory listing API makes it possible to list files on remote servers. + * + * Some of possible use cases: + * - an "open file" dialog to choose files from a remote location, + * - a recursive media finder providing a player with an ability to play all + * files from a given directory. + * + * @subsection lavf_io_dirlist_open Opening a directory + * At first, a directory needs to be opened by calling avio_open_dir() + * supplied with a URL and, optionally, ::AVDictionary containing + * protocol-specific parameters. The function returns zero or positive + * integer and allocates AVIODirContext on success. + * + * @code + * AVIODirContext *ctx = NULL; + * if (avio_open_dir(&ctx, "smb://example.com/some_dir", NULL) < 0) { + * fprintf(stderr, "Cannot open directory.\n"); + * abort(); + * } + * @endcode + * + * This code tries to open a sample directory using smb protocol without + * any additional parameters. + * + * @subsection lavf_io_dirlist_read Reading entries + * Each directory's entry (i.e. file, another directory, anything else + * within ::AVIODirEntryType) is represented by AVIODirEntry. + * Reading consecutive entries from an opened AVIODirContext is done by + * repeatedly calling avio_read_dir() on it. Each call returns zero or + * positive integer if successful. Reading can be stopped right after the + * NULL entry has been read -- it means there are no entries left to be + * read. The following code reads all entries from a directory associated + * with ctx and prints their names to standard output. + * @code + * AVIODirEntry *entry = NULL; + * for (;;) { + * if (avio_read_dir(ctx, &entry) < 0) { + * fprintf(stderr, "Cannot list directory.\n"); + * abort(); + * } + * if (!entry) + * break; + * printf("%s\n", entry->name); + * avio_free_directory_entry(&entry); + * } + * @endcode + * @} + * + * @defgroup lavf_codec Demuxers + * @{ + * @defgroup lavf_codec_native Native Demuxers + * @{ + * @} + * @defgroup lavf_codec_wrappers External library wrappers + * @{ + * @} + * @} + * @defgroup lavf_protos I/O Protocols + * @{ + * @} + * @defgroup lavf_internal Internal + * @{ + * @} + * @} + */ + +#include +#include /* FILE */ +#include "libavcodec/avcodec.h" +#include "libavutil/dict.h" +#include "libavutil/log.h" + +#include "avio.h" +#include "libavformat/version.h" + +struct AVFormatContext; + +struct AVDeviceInfoList; +struct AVDeviceCapabilitiesQuery; + +/** + * @defgroup metadata_api Public Metadata API + * @{ + * @ingroup libavf + * The metadata API allows libavformat to export metadata tags to a client + * application when demuxing. Conversely it allows a client application to + * set metadata when muxing. + * + * Metadata is exported or set as pairs of key/value strings in the 'metadata' + * fields of the AVFormatContext, AVStream, AVChapter and AVProgram structs + * using the @ref lavu_dict "AVDictionary" API. Like all strings in FFmpeg, + * metadata is assumed to be UTF-8 encoded Unicode. Note that metadata + * exported by demuxers isn't checked to be valid UTF-8 in most cases. + * + * Important concepts to keep in mind: + * - Keys are unique; there can never be 2 tags with the same key. This is + * also meant semantically, i.e., a demuxer should not knowingly produce + * several keys that are literally different but semantically identical. + * E.g., key=Author5, key=Author6. In this example, all authors must be + * placed in the same tag. + * - Metadata is flat, not hierarchical; there are no subtags. If you + * want to store, e.g., the email address of the child of producer Alice + * and actor Bob, that could have key=alice_and_bobs_childs_email_address. + * - Several modifiers can be applied to the tag name. This is done by + * appending a dash character ('-') and the modifier name in the order + * they appear in the list below -- e.g. foo-eng-sort, not foo-sort-eng. + * - language -- a tag whose value is localized for a particular language + * is appended with the ISO 639-2/B 3-letter language code. + * For example: Author-ger=Michael, Author-eng=Mike + * The original/default language is in the unqualified "Author" tag. + * A demuxer should set a default if it sets any translated tag. + * - sorting -- a modified version of a tag that should be used for + * sorting will have '-sort' appended. E.g. artist="The Beatles", + * artist-sort="Beatles, The". + * - Some protocols and demuxers support metadata updates. After a successful + * call to av_read_packet(), AVFormatContext.event_flags or AVStream.event_flags + * will be updated to indicate if metadata changed. In order to detect metadata + * changes on a stream, you need to loop through all streams in the AVFormatContext + * and check their individual event_flags. + * + * - Demuxers attempt to export metadata in a generic format, however tags + * with no generic equivalents are left as they are stored in the container. + * Follows a list of generic tag names: + * + @verbatim + album -- name of the set this work belongs to + album_artist -- main creator of the set/album, if different from artist. + e.g. "Various Artists" for compilation albums. + artist -- main creator of the work + comment -- any additional description of the file. + composer -- who composed the work, if different from artist. + copyright -- name of copyright holder. + creation_time-- date when the file was created, preferably in ISO 8601. + date -- date when the work was created, preferably in ISO 8601. + disc -- number of a subset, e.g. disc in a multi-disc collection. + encoder -- name/settings of the software/hardware that produced the file. + encoded_by -- person/group who created the file. + filename -- original name of the file. + genre -- . + language -- main language in which the work is performed, preferably + in ISO 639-2 format. Multiple languages can be specified by + separating them with commas. + performer -- artist who performed the work, if different from artist. + E.g for "Also sprach Zarathustra", artist would be "Richard + Strauss" and performer "London Philharmonic Orchestra". + publisher -- name of the label/publisher. + service_name -- name of the service in broadcasting (channel name). + service_provider -- name of the service provider in broadcasting. + title -- name of the work. + track -- number of this work in the set, can be in form current/total. + variant_bitrate -- the total bitrate of the bitrate variant that the current stream is part of + @endverbatim + * + * Look in the examples section for an application example how to use the Metadata API. + * + * @} + */ + +/* packet functions */ + + +/** + * Allocate and read the payload of a packet and initialize its + * fields with default values. + * + * @param s associated IO context + * @param pkt packet + * @param size desired payload size + * @return >0 (read size) if OK, AVERROR_xxx otherwise + */ +int av_get_packet(AVIOContext *s, AVPacket *pkt, int size); + + +/** + * Read data and append it to the current content of the AVPacket. + * If pkt->size is 0 this is identical to av_get_packet. + * Note that this uses av_grow_packet and thus involves a realloc + * which is inefficient. Thus this function should only be used + * when there is no reasonable way to know (an upper bound of) + * the final size. + * + * @param s associated IO context + * @param pkt packet + * @param size amount of data to read + * @return >0 (read size) if OK, AVERROR_xxx otherwise, previous data + * will not be lost even if an error occurs. + */ +int av_append_packet(AVIOContext *s, AVPacket *pkt, int size); + +#if FF_API_LAVF_FRAC +/*************************************************/ +/* fractional numbers for exact pts handling */ + +/** + * The exact value of the fractional number is: 'val + num / den'. + * num is assumed to be 0 <= num < den. + */ +typedef struct AVFrac { + int64_t val, num, den; +} AVFrac; +#endif + +/*************************************************/ +/* input/output formats */ + +struct AVCodecTag; + +/** + * This structure contains the data a format has to probe a file. + */ +typedef struct AVProbeData { + const char *filename; + unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */ + int buf_size; /**< Size of buf except extra allocated bytes */ + const char *mime_type; /**< mime_type, when known. */ +} AVProbeData; + +#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4) +#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1) + +#define AVPROBE_SCORE_EXTENSION 50 ///< score for file extension +#define AVPROBE_SCORE_MIME 75 ///< score for file mime type +#define AVPROBE_SCORE_MAX 100 ///< maximum score + +#define AVPROBE_PADDING_SIZE 32 ///< extra allocated bytes at the end of the probe buffer + +/// Demuxer will use avio_open, no opened file should be provided by the caller. +#define AVFMT_NOFILE 0x0001 +#define AVFMT_NEEDNUMBER 0x0002 /**< Needs '%d' in filename. */ +#define AVFMT_SHOW_IDS 0x0008 /**< Show format stream IDs numbers. */ +#if FF_API_LAVF_FMT_RAWPICTURE +#define AVFMT_RAWPICTURE 0x0020 /**< Format wants AVPicture structure for + raw picture data. @deprecated Not used anymore */ +#endif +#define AVFMT_GLOBALHEADER 0x0040 /**< Format wants global header. */ +#define AVFMT_NOTIMESTAMPS 0x0080 /**< Format does not need / have any timestamps. */ +#define AVFMT_GENERIC_INDEX 0x0100 /**< Use generic index building code. */ +#define AVFMT_TS_DISCONT 0x0200 /**< Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ +#define AVFMT_VARIABLE_FPS 0x0400 /**< Format allows variable fps. */ +#define AVFMT_NODIMENSIONS 0x0800 /**< Format does not need width/height */ +#define AVFMT_NOSTREAMS 0x1000 /**< Format does not require any streams */ +#define AVFMT_NOBINSEARCH 0x2000 /**< Format does not allow to fall back on binary search via read_timestamp */ +#define AVFMT_NOGENSEARCH 0x4000 /**< Format does not allow to fall back on generic search */ +#define AVFMT_NO_BYTE_SEEK 0x8000 /**< Format does not allow seeking by bytes */ +#define AVFMT_ALLOW_FLUSH 0x10000 /**< Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function. */ +#define AVFMT_TS_NONSTRICT 0x20000 /**< Format does not require strictly + increasing timestamps, but they must + still be monotonic */ +#define AVFMT_TS_NEGATIVE 0x40000 /**< Format allows muxing negative + timestamps. If not set the timestamp + will be shifted in av_write_frame and + av_interleaved_write_frame so they + start from 0. + The user or muxer can override this through + AVFormatContext.avoid_negative_ts + */ + +#define AVFMT_SEEK_TO_PTS 0x4000000 /**< Seeking is based on PTS */ + +/** + * @addtogroup lavf_encoding + * @{ + */ +typedef struct AVOutputFormat { + const char *name; + /** + * Descriptive name for the format, meant to be more human-readable + * than name. You should use the NULL_IF_CONFIG_SMALL() macro + * to define it. + */ + const char *long_name; + const char *mime_type; + const char *extensions; /**< comma-separated filename extensions */ + /* output support */ + enum AVCodecID audio_codec; /**< default audio codec */ + enum AVCodecID video_codec; /**< default video codec */ + enum AVCodecID subtitle_codec; /**< default subtitle codec */ + /** + * can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, + * AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS, + * AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH, + * AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE + */ + int flags; + + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + const struct AVCodecTag * const *codec_tag; + + + const AVClass *priv_class; ///< AVClass for the private context + + /***************************************************************** + * No fields below this line are part of the public API. They + * may not be used outside of libavformat and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + struct AVOutputFormat *next; + /** + * size of private data so that it can be allocated in the wrapper + */ + int priv_data_size; + + int (*write_header)(struct AVFormatContext *); + /** + * Write a packet. If AVFMT_ALLOW_FLUSH is set in flags, + * pkt can be NULL in order to flush data buffered in the muxer. + * When flushing, return 0 if there still is more data to flush, + * or 1 if everything was flushed and there is no more buffered + * data. + */ + int (*write_packet)(struct AVFormatContext *, AVPacket *pkt); + int (*write_trailer)(struct AVFormatContext *); + /** + * Currently only used to set pixel format if not YUV420P. + */ + int (*interleave_packet)(struct AVFormatContext *, AVPacket *out, + AVPacket *in, int flush); + /** + * Test if the given codec can be stored in this container. + * + * @return 1 if the codec is supported, 0 if it is not. + * A negative number if unknown. + * MKTAG('A', 'P', 'I', 'C') if the codec is only supported as AV_DISPOSITION_ATTACHED_PIC + */ + int (*query_codec)(enum AVCodecID id, int std_compliance); + + void (*get_output_timestamp)(struct AVFormatContext *s, int stream, + int64_t *dts, int64_t *wall); + /** + * Allows sending messages from application to device. + */ + int (*control_message)(struct AVFormatContext *s, int type, + void *data, size_t data_size); + + /** + * Write an uncoded AVFrame. + * + * See av_write_uncoded_frame() for details. + * + * The library will free *frame afterwards, but the muxer can prevent it + * by setting the pointer to NULL. + */ + int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index, + AVFrame **frame, unsigned flags); + /** + * Returns device list with it properties. + * @see avdevice_list_devices() for more details. + */ + int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list); + /** + * Initialize device capabilities submodule. + * @see avdevice_capabilities_create() for more details. + */ + int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + /** + * Free device capabilities submodule. + * @see avdevice_capabilities_free() for more details. + */ + int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + enum AVCodecID data_codec; /**< default data codec */ + /** + * Initialize format. May allocate data here, and set any AVFormatContext or + * AVStream parameters that need to be set before packets are sent. + * This method must not write output. + * + * Return 0 if streams were fully configured, 1 if not, negative AVERROR on failure + * + * Any allocations made here must be freed in deinit(). + */ + int (*init)(struct AVFormatContext *); + /** + * Deinitialize format. If present, this is called whenever the muxer is being + * destroyed, regardless of whether or not the header has been written. + * + * If a trailer is being written, this is called after write_trailer(). + * + * This is called if init() fails as well. + */ + void (*deinit)(struct AVFormatContext *); + /** + * Set up any necessary bitstream filtering and extract any extra data needed + * for the global header. + * Return 0 if more packets from this stream must be checked; 1 if not. + */ + int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt); +} AVOutputFormat; +/** + * @} + */ + +/** + * @addtogroup lavf_decoding + * @{ + */ +typedef struct AVInputFormat { + /** + * A comma separated list of short names for the format. New names + * may be appended with a minor bump. + */ + const char *name; + + /** + * Descriptive name for the format, meant to be more human-readable + * than name. You should use the NULL_IF_CONFIG_SMALL() macro + * to define it. + */ + const char *long_name; + + /** + * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, + * AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH, + * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS. + */ + int flags; + + /** + * If extensions are defined, then no probe is done. You should + * usually not use extension format guessing because it is not + * reliable enough + */ + const char *extensions; + + const struct AVCodecTag * const *codec_tag; + + const AVClass *priv_class; ///< AVClass for the private context + + /** + * Comma-separated list of mime types. + * It is used check for matching mime types while probing. + * @see av_probe_input_format2 + */ + const char *mime_type; + + /***************************************************************** + * No fields below this line are part of the public API. They + * may not be used outside of libavformat and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + struct AVInputFormat *next; + + /** + * Raw demuxers store their codec ID here. + */ + int raw_codec_id; + + /** + * Size of private data so that it can be allocated in the wrapper. + */ + int priv_data_size; + + /** + * Tell if a given file has a chance of being parsed as this format. + * The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes + * big so you do not have to check for that unless you need more. + */ + int (*read_probe)(AVProbeData *); + + /** + * Read the format header and initialize the AVFormatContext + * structure. Return 0 if OK. 'avformat_new_stream' should be + * called to create new streams. + */ + int (*read_header)(struct AVFormatContext *); + + /** + * Read one packet and put it in 'pkt'. pts and flags are also + * set. 'avformat_new_stream' can be called only if the flag + * AVFMTCTX_NOHEADER is used and only in the calling thread (not in a + * background thread). + * @return 0 on success, < 0 on error. + * When returning an error, pkt must not have been allocated + * or must be freed before returning + */ + int (*read_packet)(struct AVFormatContext *, AVPacket *pkt); + + /** + * Close the stream. The AVFormatContext and AVStreams are not + * freed by this function + */ + int (*read_close)(struct AVFormatContext *); + + /** + * Seek to a given timestamp relative to the frames in + * stream component stream_index. + * @param stream_index Must not be -1. + * @param flags Selects which direction should be preferred if no exact + * match is available. + * @return >= 0 on success (but not necessarily the new offset) + */ + int (*read_seek)(struct AVFormatContext *, + int stream_index, int64_t timestamp, int flags); + + /** + * Get the next timestamp in stream[stream_index].time_base units. + * @return the timestamp or AV_NOPTS_VALUE if an error occurred + */ + int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index, + int64_t *pos, int64_t pos_limit); + + /** + * Start/resume playing - only meaningful if using a network-based format + * (RTSP). + */ + int (*read_play)(struct AVFormatContext *); + + /** + * Pause playing - only meaningful if using a network-based format + * (RTSP). + */ + int (*read_pause)(struct AVFormatContext *); + + /** + * Seek to timestamp ts. + * Seeking will be done so that the point from which all active streams + * can be presented successfully will be closest to ts and within min/max_ts. + * Active streams are all streams that have AVStream.discard < AVDISCARD_ALL. + */ + int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); + + /** + * Returns device list with it properties. + * @see avdevice_list_devices() for more details. + */ + int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list); + + /** + * Initialize device capabilities submodule. + * @see avdevice_capabilities_create() for more details. + */ + int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); + + /** + * Free device capabilities submodule. + * @see avdevice_capabilities_free() for more details. + */ + int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps); +} AVInputFormat; +/** + * @} + */ + +enum AVStreamParseType { + AVSTREAM_PARSE_NONE, + AVSTREAM_PARSE_FULL, /**< full parsing and repack */ + AVSTREAM_PARSE_HEADERS, /**< Only parse headers, do not repack. */ + AVSTREAM_PARSE_TIMESTAMPS, /**< full parsing and interpolation of timestamps for frames not starting on a packet boundary */ + AVSTREAM_PARSE_FULL_ONCE, /**< full parsing and repack of the first frame only, only implemented for H.264 currently */ + AVSTREAM_PARSE_FULL_RAW=MKTAG(0,'R','A','W'), /**< full parsing and repack with timestamp and position generation by parser for raw + this assumes that each packet in the file contains no demuxer level headers and + just codec level data, otherwise position generation would fail */ +}; + +typedef struct AVIndexEntry { + int64_t pos; + int64_t timestamp; /**< + * Timestamp in AVStream.time_base units, preferably the time from which on correctly decoded frames are available + * when seeking to this entry. That means preferable PTS on keyframe based formats. + * But demuxers can choose to store a different timestamp, if it is more convenient for the implementation or nothing better + * is known + */ +#define AVINDEX_KEYFRAME 0x0001 +#define AVINDEX_DISCARD_FRAME 0x0002 /** + * Flag is used to indicate which frame should be discarded after decoding. + */ + int flags:2; + int size:30; //Yeah, trying to keep the size of this small to reduce memory requirements (it is 24 vs. 32 bytes due to possible 8-byte alignment). + int min_distance; /**< Minimum distance between this and the previous keyframe, used to avoid unneeded searching. */ +} AVIndexEntry; + +#define AV_DISPOSITION_DEFAULT 0x0001 +#define AV_DISPOSITION_DUB 0x0002 +#define AV_DISPOSITION_ORIGINAL 0x0004 +#define AV_DISPOSITION_COMMENT 0x0008 +#define AV_DISPOSITION_LYRICS 0x0010 +#define AV_DISPOSITION_KARAOKE 0x0020 + +/** + * Track should be used during playback by default. + * Useful for subtitle track that should be displayed + * even when user did not explicitly ask for subtitles. + */ +#define AV_DISPOSITION_FORCED 0x0040 +#define AV_DISPOSITION_HEARING_IMPAIRED 0x0080 /**< stream for hearing impaired audiences */ +#define AV_DISPOSITION_VISUAL_IMPAIRED 0x0100 /**< stream for visual impaired audiences */ +#define AV_DISPOSITION_CLEAN_EFFECTS 0x0200 /**< stream without voice */ +/** + * The stream is stored in the file as an attached picture/"cover art" (e.g. + * APIC frame in ID3v2). The first (usually only) packet associated with it + * will be returned among the first few packets read from the file unless + * seeking takes place. It can also be accessed at any time in + * AVStream.attached_pic. + */ +#define AV_DISPOSITION_ATTACHED_PIC 0x0400 +/** + * The stream is sparse, and contains thumbnail images, often corresponding + * to chapter markers. Only ever used with AV_DISPOSITION_ATTACHED_PIC. + */ +#define AV_DISPOSITION_TIMED_THUMBNAILS 0x0800 + +typedef struct AVStreamInternal AVStreamInternal; + +/** + * To specify text track kind (different from subtitles default). + */ +#define AV_DISPOSITION_CAPTIONS 0x10000 +#define AV_DISPOSITION_DESCRIPTIONS 0x20000 +#define AV_DISPOSITION_METADATA 0x40000 + +/** + * Options for behavior on timestamp wrap detection. + */ +#define AV_PTS_WRAP_IGNORE 0 ///< ignore the wrap +#define AV_PTS_WRAP_ADD_OFFSET 1 ///< add the format specific offset on wrap detection +#define AV_PTS_WRAP_SUB_OFFSET -1 ///< subtract the format specific offset on wrap detection + +/** + * Stream structure. + * New fields can be added to the end with minor version bumps. + * Removal, reordering and changes to existing fields require a major + * version bump. + * sizeof(AVStream) must not be used outside libav*. + */ +typedef struct AVStream { + int index; /**< stream index in AVFormatContext */ + /** + * Format-specific stream ID. + * decoding: set by libavformat + * encoding: set by the user, replaced by libavformat if left unset + */ + int id; +#if FF_API_LAVF_AVCTX + /** + * @deprecated use the codecpar struct instead + */ + attribute_deprecated + AVCodecContext *codec; +#endif + void *priv_data; + +#if FF_API_LAVF_FRAC + /** + * @deprecated this field is unused + */ + attribute_deprecated + struct AVFrac pts; +#endif + + /** + * This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. + * + * decoding: set by libavformat + * encoding: May be set by the caller before avformat_write_header() to + * provide a hint to the muxer about the desired timebase. In + * avformat_write_header(), the muxer will overwrite this field + * with the timebase that will actually be used for the timestamps + * written into the file (which may or may not be related to the + * user-provided one, depending on the format). + */ + AVRational time_base; + + /** + * Decoding: pts of the first frame of the stream in presentation order, in stream time base. + * Only set this if you are absolutely 100% sure that the value you set + * it to really is the pts of the first frame. + * This may be undefined (AV_NOPTS_VALUE). + * @note The ASF header does NOT contain a correct start_time the ASF + * demuxer must NOT set this. + */ + int64_t start_time; + + /** + * Decoding: duration of the stream, in stream time base. + * If a source file does not specify a duration, but does specify + * a bitrate, this value will be estimated from bitrate and file size. + * + * Encoding: May be set by the caller before avformat_write_header() to + * provide a hint to the muxer about the estimated duration. + */ + int64_t duration; + + int64_t nb_frames; ///< number of frames in this stream if known or 0 + + int disposition; /**< AV_DISPOSITION_* bit field */ + + enum AVDiscard discard; ///< Selects which packets can be discarded at will and do not need to be demuxed. + + /** + * sample aspect ratio (0 if unknown) + * - encoding: Set by user. + * - decoding: Set by libavformat. + */ + AVRational sample_aspect_ratio; + + AVDictionary *metadata; + + /** + * Average framerate + * + * - demuxing: May be set by libavformat when creating the stream or in + * avformat_find_stream_info(). + * - muxing: May be set by the caller before avformat_write_header(). + */ + AVRational avg_frame_rate; + + /** + * For streams with AV_DISPOSITION_ATTACHED_PIC disposition, this packet + * will contain the attached picture. + * + * decoding: set by libavformat, must not be modified by the caller. + * encoding: unused + */ + AVPacket attached_pic; + + /** + * An array of side data that applies to the whole stream (i.e. the + * container does not allow it to change between packets). + * + * There may be no overlap between the side data in this array and side data + * in the packets. I.e. a given side data is either exported by the muxer + * (demuxing) / set by the caller (muxing) in this array, then it never + * appears in the packets, or the side data is exported / sent through + * the packets (always in the first packet where the value becomes known or + * changes), then it does not appear in this array. + * + * - demuxing: Set by libavformat when the stream is created. + * - muxing: May be set by the caller before avformat_write_header(). + * + * Freed by libavformat in avformat_free_context(). + * + * @see av_format_inject_global_side_data() + */ + AVPacketSideData *side_data; + /** + * The number of elements in the AVStream.side_data array. + */ + int nb_side_data; + + /** + * Flags for the user to detect events happening on the stream. Flags must + * be cleared by the user once the event has been handled. + * A combination of AVSTREAM_EVENT_FLAG_*. + */ + int event_flags; +#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata. + + /***************************************************************** + * All fields below this line are not part of the public API. They + * may not be used outside of libavformat and can be changed and + * removed at will. + * Internal note: be aware that physically removing these fields + * will break ABI. Replace removed fields with dummy fields, and + * add new fields to AVStreamInternal. + ***************************************************************** + */ + + /** + * Stream information used internally by avformat_find_stream_info() + */ +#define MAX_STD_TIMEBASES (30*12+30+3+6) + struct { + int64_t last_dts; + int64_t duration_gcd; + int duration_count; + int64_t rfps_duration_sum; + double (*duration_error)[2][MAX_STD_TIMEBASES]; + int64_t codec_info_duration; + int64_t codec_info_duration_fields; + + /** + * 0 -> decoder has not been searched for yet. + * >0 -> decoder found + * <0 -> decoder with codec_id == -found_decoder has not been found + */ + int found_decoder; + + int64_t last_duration; + + /** + * Those are used for average framerate estimation. + */ + int64_t fps_first_dts; + int fps_first_dts_idx; + int64_t fps_last_dts; + int fps_last_dts_idx; + + } *info; + + int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */ + + // Timestamp generation support: + /** + * Timestamp corresponding to the last dts sync point. + * + * Initialized when AVCodecParserContext.dts_sync_point >= 0 and + * a DTS is received from the underlying container. Otherwise set to + * AV_NOPTS_VALUE by default. + */ + int64_t first_dts; + int64_t cur_dts; + int64_t last_IP_pts; + int last_IP_duration; + + /** + * Number of packets to buffer for codec probing + */ + int probe_packets; + + /** + * Number of frames that have been demuxed during avformat_find_stream_info() + */ + int codec_info_nb_frames; + + /* av_read_frame() support */ + enum AVStreamParseType need_parsing; + struct AVCodecParserContext *parser; + + /** + * last packet in packet_buffer for this stream when muxing. + */ + struct AVPacketList *last_in_packet_buffer; + AVProbeData probe_data; +#define MAX_REORDER_DELAY 16 + int64_t pts_buffer[MAX_REORDER_DELAY+1]; + + AVIndexEntry *index_entries; /**< Only used if the format does not + support seeking natively. */ + int nb_index_entries; + unsigned int index_entries_allocated_size; + + /** + * Real base framerate of the stream. + * This is the lowest framerate with which all timestamps can be + * represented accurately (it is the least common multiple of all + * framerates in the stream). Note, this value is just a guess! + * For example, if the time base is 1/90000 and all frames have either + * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1. + * + * Code outside avformat should access this field using: + * av_stream_get/set_r_frame_rate(stream) + */ + AVRational r_frame_rate; + + /** + * Stream Identifier + * This is the MPEG-TS stream identifier +1 + * 0 means unknown + */ + int stream_identifier; + + int64_t interleaver_chunk_size; + int64_t interleaver_chunk_duration; + + /** + * stream probing state + * -1 -> probing finished + * 0 -> no probing requested + * rest -> perform probing with request_probe being the minimum score to accept. + * NOT PART OF PUBLIC API + */ + int request_probe; + /** + * Indicates that everything up to the next keyframe + * should be discarded. + */ + int skip_to_keyframe; + + /** + * Number of samples to skip at the start of the frame decoded from the next packet. + */ + int skip_samples; + + /** + * If not 0, the number of samples that should be skipped from the start of + * the stream (the samples are removed from packets with pts==0, which also + * assumes negative timestamps do not happen). + * Intended for use with formats such as mp3 with ad-hoc gapless audio + * support. + */ + int64_t start_skip_samples; + + /** + * If not 0, the first audio sample that should be discarded from the stream. + * This is broken by design (needs global sample count), but can't be + * avoided for broken by design formats such as mp3 with ad-hoc gapless + * audio support. + */ + int64_t first_discard_sample; + + /** + * The sample after last sample that is intended to be discarded after + * first_discard_sample. Works on frame boundaries only. Used to prevent + * early EOF if the gapless info is broken (considered concatenated mp3s). + */ + int64_t last_discard_sample; + + /** + * Number of internally decoded frames, used internally in libavformat, do not access + * its lifetime differs from info which is why it is not in that structure. + */ + int nb_decoded_frames; + + /** + * Timestamp offset added to timestamps before muxing + * NOT PART OF PUBLIC API + */ + int64_t mux_ts_offset; + + /** + * Internal data to check for wrapping of the time stamp + */ + int64_t pts_wrap_reference; + + /** + * Options for behavior, when a wrap is detected. + * + * Defined by AV_PTS_WRAP_ values. + * + * If correction is enabled, there are two possibilities: + * If the first time stamp is near the wrap point, the wrap offset + * will be subtracted, which will create negative time stamps. + * Otherwise the offset will be added. + */ + int pts_wrap_behavior; + + /** + * Internal data to prevent doing update_initial_durations() twice + */ + int update_initial_durations_done; + + /** + * Internal data to generate dts from pts + */ + int64_t pts_reorder_error[MAX_REORDER_DELAY+1]; + uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1]; + + /** + * Internal data to analyze DTS and detect faulty mpeg streams + */ + int64_t last_dts_for_order_check; + uint8_t dts_ordered; + uint8_t dts_misordered; + + /** + * Internal data to inject global side data + */ + int inject_global_side_data; + + /***************************************************************** + * All fields above this line are not part of the public API. + * Fields below are part of the public API and ABI again. + ***************************************************************** + */ + + /** + * String containing paris of key and values describing recommended encoder configuration. + * Paris are separated by ','. + * Keys are separated from values by '='. + */ + char *recommended_encoder_configuration; + + /** + * display aspect ratio (0 if unknown) + * - encoding: unused + * - decoding: Set by libavformat to calculate sample_aspect_ratio internally + */ + AVRational display_aspect_ratio; + + struct FFFrac *priv_pts; + + /** + * An opaque field for libavformat internal usage. + * Must not be accessed in any way by callers. + */ + AVStreamInternal *internal; + + /* + * Codec parameters associated with this stream. Allocated and freed by + * libavformat in avformat_new_stream() and avformat_free_context() + * respectively. + * + * - demuxing: filled by libavformat on stream creation or in + * avformat_find_stream_info() + * - muxing: filled by the caller before avformat_write_header() + */ + AVCodecParameters *codecpar; +} AVStream; + +AVRational av_stream_get_r_frame_rate(const AVStream *s); +void av_stream_set_r_frame_rate(AVStream *s, AVRational r); +struct AVCodecParserContext *av_stream_get_parser(const AVStream *s); +char* av_stream_get_recommended_encoder_configuration(const AVStream *s); +void av_stream_set_recommended_encoder_configuration(AVStream *s, char *configuration); + +/** + * Returns the pts of the last muxed packet + its duration + * + * the retuned value is undefined when used with a demuxer. + */ +int64_t av_stream_get_end_pts(const AVStream *st); + +#define AV_PROGRAM_RUNNING 1 + +/** + * New fields can be added to the end with minor version bumps. + * Removal, reordering and changes to existing fields require a major + * version bump. + * sizeof(AVProgram) must not be used outside libav*. + */ +typedef struct AVProgram { + int id; + int flags; + enum AVDiscard discard; ///< selects which program to discard and which to feed to the caller + unsigned int *stream_index; + unsigned int nb_stream_indexes; + AVDictionary *metadata; + + int program_num; + int pmt_pid; + int pcr_pid; + + /***************************************************************** + * All fields below this line are not part of the public API. They + * may not be used outside of libavformat and can be changed and + * removed at will. + * New public fields should be added right above. + ***************************************************************** + */ + int64_t start_time; + int64_t end_time; + + int64_t pts_wrap_reference; ///< reference dts for wrap detection + int pts_wrap_behavior; ///< behavior on wrap detection +} AVProgram; + +#define AVFMTCTX_NOHEADER 0x0001 /**< signal that no header is present + (streams are added dynamically) */ + +typedef struct AVChapter { + int id; ///< unique ID to identify the chapter + AVRational time_base; ///< time base in which the start/end timestamps are specified + int64_t start, end; ///< chapter start/end time in time_base units + AVDictionary *metadata; +} AVChapter; + + +/** + * Callback used by devices to communicate with application. + */ +typedef int (*av_format_control_message)(struct AVFormatContext *s, int type, + void *data, size_t data_size); + +typedef int (*AVOpenCallback)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, + const AVIOInterruptCB *int_cb, AVDictionary **options); + +/** + * The duration of a video can be estimated through various ways, and this enum can be used + * to know how the duration was estimated. + */ +enum AVDurationEstimationMethod { + AVFMT_DURATION_FROM_PTS, ///< Duration accurately estimated from PTSes + AVFMT_DURATION_FROM_STREAM, ///< Duration estimated from a stream with a known duration + AVFMT_DURATION_FROM_BITRATE ///< Duration estimated from bitrate (less accurate) +}; + +typedef struct AVFormatInternal AVFormatInternal; + +/** + * Format I/O context. + * New fields can be added to the end with minor version bumps. + * Removal, reordering and changes to existing fields require a major + * version bump. + * sizeof(AVFormatContext) must not be used outside libav*, use + * avformat_alloc_context() to create an AVFormatContext. + * + * Fields can be accessed through AVOptions (av_opt*), + * the name string used matches the associated command line parameter name and + * can be found in libavformat/options_table.h. + * The AVOption/command line parameter names differ in some cases from the C + * structure field names for historic reasons or brevity. + */ +typedef struct AVFormatContext { + /** + * A class for logging and @ref avoptions. Set by avformat_alloc_context(). + * Exports (de)muxer private options if they exist. + */ + const AVClass *av_class; + + /** + * The input container format. + * + * Demuxing only, set by avformat_open_input(). + */ + struct AVInputFormat *iformat; + + /** + * The output container format. + * + * Muxing only, must be set by the caller before avformat_write_header(). + */ + struct AVOutputFormat *oformat; + + /** + * Format private data. This is an AVOptions-enabled struct + * if and only if iformat/oformat.priv_class is not NULL. + * + * - muxing: set by avformat_write_header() + * - demuxing: set by avformat_open_input() + */ + void *priv_data; + + /** + * I/O context. + * + * - demuxing: either set by the user before avformat_open_input() (then + * the user must close it manually) or set by avformat_open_input(). + * - muxing: set by the user before avformat_write_header(). The caller must + * take care of closing / freeing the IO context. + * + * Do NOT set this field if AVFMT_NOFILE flag is set in + * iformat/oformat.flags. In such a case, the (de)muxer will handle + * I/O in some other way and this field will be NULL. + */ + AVIOContext *pb; + + /* stream info */ + /** + * Flags signalling stream properties. A combination of AVFMTCTX_*. + * Set by libavformat. + */ + int ctx_flags; + + /** + * Number of elements in AVFormatContext.streams. + * + * Set by avformat_new_stream(), must not be modified by any other code. + */ + unsigned int nb_streams; + /** + * A list of all streams in the file. New streams are created with + * avformat_new_stream(). + * + * - demuxing: streams are created by libavformat in avformat_open_input(). + * If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams may also + * appear in av_read_frame(). + * - muxing: streams are created by the user before avformat_write_header(). + * + * Freed by libavformat in avformat_free_context(). + */ + AVStream **streams; + + /** + * input or output filename + * + * - demuxing: set by avformat_open_input() + * - muxing: may be set by the caller before avformat_write_header() + */ + char filename[1024]; + + /** + * Position of the first frame of the component, in + * AV_TIME_BASE fractional seconds. NEVER set this value directly: + * It is deduced from the AVStream values. + * + * Demuxing only, set by libavformat. + */ + int64_t start_time; + + /** + * Duration of the stream, in AV_TIME_BASE fractional + * seconds. Only set this value if you know none of the individual stream + * durations and also do not set any of them. This is deduced from the + * AVStream values if not set. + * + * Demuxing only, set by libavformat. + */ + int64_t duration; + + /** + * Total stream bitrate in bit/s, 0 if not + * available. Never set it directly if the file_size and the + * duration are known as FFmpeg can compute it automatically. + */ + int64_t bit_rate; + + unsigned int packet_size; + int max_delay; + + /** + * Flags modifying the (de)muxer behaviour. A combination of AVFMT_FLAG_*. + * Set by the user before avformat_open_input() / avformat_write_header(). + */ + int flags; +#define AVFMT_FLAG_GENPTS 0x0001 ///< Generate missing pts even if it requires parsing future frames. +#define AVFMT_FLAG_IGNIDX 0x0002 ///< Ignore index. +#define AVFMT_FLAG_NONBLOCK 0x0004 ///< Do not block when reading packets from input. +#define AVFMT_FLAG_IGNDTS 0x0008 ///< Ignore DTS on frames that contain both DTS & PTS +#define AVFMT_FLAG_NOFILLIN 0x0010 ///< Do not infer any values from other values, just return what is stored in the container +#define AVFMT_FLAG_NOPARSE 0x0020 ///< Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled +#define AVFMT_FLAG_NOBUFFER 0x0040 ///< Do not buffer frames when possible +#define AVFMT_FLAG_CUSTOM_IO 0x0080 ///< The caller has supplied a custom AVIOContext, don't avio_close() it. +#define AVFMT_FLAG_DISCARD_CORRUPT 0x0100 ///< Discard frames marked corrupted +#define AVFMT_FLAG_FLUSH_PACKETS 0x0200 ///< Flush the AVIOContext every packet. +/** + * When muxing, try to avoid writing any random/volatile data to the output. + * This includes any random IDs, real-time timestamps/dates, muxer version, etc. + * + * This flag is mainly intended for testing. + */ +#define AVFMT_FLAG_BITEXACT 0x0400 +#define AVFMT_FLAG_MP4A_LATM 0x8000 ///< Enable RTP MP4A-LATM payload +#define AVFMT_FLAG_SORT_DTS 0x10000 ///< try to interleave outputted packets by dts (using this flag can slow demuxing down) +#define AVFMT_FLAG_PRIV_OPT 0x20000 ///< Enable use of private options by delaying codec open (this could be made default once all code is converted) +#if FF_API_LAVF_KEEPSIDE_FLAG +#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Don't merge side data but keep it separate. Deprecated, will be the default. +#endif +#define AVFMT_FLAG_FAST_SEEK 0x80000 ///< Enable fast, but inaccurate seeks for some formats +#define AVFMT_FLAG_SHORTEST 0x100000 ///< Stop muxing when the shortest stream stops. +#define AVFMT_FLAG_AUTO_BSF 0x200000 ///< Wait for packet data before writing a header, and add bitstream filters as requested by the muxer + + /** + * Maximum size of the data read from input for determining + * the input container format. + * Demuxing only, set by the caller before avformat_open_input(). + */ + int64_t probesize; + + /** + * Maximum duration (in AV_TIME_BASE units) of the data read + * from input in avformat_find_stream_info(). + * Demuxing only, set by the caller before avformat_find_stream_info(). + * Can be set to 0 to let avformat choose using a heuristic. + */ + int64_t max_analyze_duration; + + const uint8_t *key; + int keylen; + + unsigned int nb_programs; + AVProgram **programs; + + /** + * Forced video codec_id. + * Demuxing: Set by user. + */ + enum AVCodecID video_codec_id; + + /** + * Forced audio codec_id. + * Demuxing: Set by user. + */ + enum AVCodecID audio_codec_id; + + /** + * Forced subtitle codec_id. + * Demuxing: Set by user. + */ + enum AVCodecID subtitle_codec_id; + + /** + * Maximum amount of memory in bytes to use for the index of each stream. + * If the index exceeds this size, entries will be discarded as + * needed to maintain a smaller size. This can lead to slower or less + * accurate seeking (depends on demuxer). + * Demuxers for which a full in-memory index is mandatory will ignore + * this. + * - muxing: unused + * - demuxing: set by user + */ + unsigned int max_index_size; + + /** + * Maximum amount of memory in bytes to use for buffering frames + * obtained from realtime capture devices. + */ + unsigned int max_picture_buffer; + + /** + * Number of chapters in AVChapter array. + * When muxing, chapters are normally written in the file header, + * so nb_chapters should normally be initialized before write_header + * is called. Some muxers (e.g. mov and mkv) can also write chapters + * in the trailer. To write chapters in the trailer, nb_chapters + * must be zero when write_header is called and non-zero when + * write_trailer is called. + * - muxing: set by user + * - demuxing: set by libavformat + */ + unsigned int nb_chapters; + AVChapter **chapters; + + /** + * Metadata that applies to the whole file. + * + * - demuxing: set by libavformat in avformat_open_input() + * - muxing: may be set by the caller before avformat_write_header() + * + * Freed by libavformat in avformat_free_context(). + */ + AVDictionary *metadata; + + /** + * Start time of the stream in real world time, in microseconds + * since the Unix epoch (00:00 1st January 1970). That is, pts=0 in the + * stream was captured at this real world time. + * - muxing: Set by the caller before avformat_write_header(). If set to + * either 0 or AV_NOPTS_VALUE, then the current wall-time will + * be used. + * - demuxing: Set by libavformat. AV_NOPTS_VALUE if unknown. Note that + * the value may become known after some number of frames + * have been received. + */ + int64_t start_time_realtime; + + /** + * The number of frames used for determining the framerate in + * avformat_find_stream_info(). + * Demuxing only, set by the caller before avformat_find_stream_info(). + */ + int fps_probe_size; + + /** + * Error recognition; higher values will detect more errors but may + * misdetect some more or less valid parts as errors. + * Demuxing only, set by the caller before avformat_open_input(). + */ + int error_recognition; + + /** + * Custom interrupt callbacks for the I/O layer. + * + * demuxing: set by the user before avformat_open_input(). + * muxing: set by the user before avformat_write_header() + * (mainly useful for AVFMT_NOFILE formats). The callback + * should also be passed to avio_open2() if it's used to + * open the file. + */ + AVIOInterruptCB interrupt_callback; + + /** + * Flags to enable debugging. + */ + int debug; +#define FF_FDEBUG_TS 0x0001 + + /** + * Maximum buffering duration for interleaving. + * + * To ensure all the streams are interleaved correctly, + * av_interleaved_write_frame() will wait until it has at least one packet + * for each stream before actually writing any packets to the output file. + * When some streams are "sparse" (i.e. there are large gaps between + * successive packets), this can result in excessive buffering. + * + * This field specifies the maximum difference between the timestamps of the + * first and the last packet in the muxing queue, above which libavformat + * will output a packet regardless of whether it has queued a packet for all + * the streams. + * + * Muxing only, set by the caller before avformat_write_header(). + */ + int64_t max_interleave_delta; + + /** + * Allow non-standard and experimental extension + * @see AVCodecContext.strict_std_compliance + */ + int strict_std_compliance; + + /** + * Flags for the user to detect events happening on the file. Flags must + * be cleared by the user once the event has been handled. + * A combination of AVFMT_EVENT_FLAG_*. + */ + int event_flags; +#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata. + + /** + * Maximum number of packets to read while waiting for the first timestamp. + * Decoding only. + */ + int max_ts_probe; + + /** + * Avoid negative timestamps during muxing. + * Any value of the AVFMT_AVOID_NEG_TS_* constants. + * Note, this only works when using av_interleaved_write_frame. (interleave_packet_per_dts is in use) + * - muxing: Set by user + * - demuxing: unused + */ + int avoid_negative_ts; +#define AVFMT_AVOID_NEG_TS_AUTO -1 ///< Enabled when required by target format +#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative +#define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< Shift timestamps so that they start at 0 + + /** + * Transport stream id. + * This will be moved into demuxer private options. Thus no API/ABI compatibility + */ + int ts_id; + + /** + * Audio preload in microseconds. + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user + * - decoding: unused + */ + int audio_preload; + + /** + * Max chunk time in microseconds. + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user + * - decoding: unused + */ + int max_chunk_duration; + + /** + * Max chunk size in bytes + * Note, not all formats support this and unpredictable things may happen if it is used when not supported. + * - encoding: Set by user + * - decoding: unused + */ + int max_chunk_size; + + /** + * forces the use of wallclock timestamps as pts/dts of packets + * This has undefined results in the presence of B frames. + * - encoding: unused + * - decoding: Set by user + */ + int use_wallclock_as_timestamps; + + /** + * avio flags, used to force AVIO_FLAG_DIRECT. + * - encoding: unused + * - decoding: Set by user + */ + int avio_flags; + + /** + * The duration field can be estimated through various ways, and this field can be used + * to know how the duration was estimated. + * - encoding: unused + * - decoding: Read by user + */ + enum AVDurationEstimationMethod duration_estimation_method; + + /** + * Skip initial bytes when opening stream + * - encoding: unused + * - decoding: Set by user + */ + int64_t skip_initial_bytes; + + /** + * Correct single timestamp overflows + * - encoding: unused + * - decoding: Set by user + */ + unsigned int correct_ts_overflow; + + /** + * Force seeking to any (also non key) frames. + * - encoding: unused + * - decoding: Set by user + */ + int seek2any; + + /** + * Flush the I/O context after each packet. + * - encoding: Set by user + * - decoding: unused + */ + int flush_packets; + + /** + * format probing score. + * The maximal score is AVPROBE_SCORE_MAX, its set when the demuxer probes + * the format. + * - encoding: unused + * - decoding: set by avformat, read by user + */ + int probe_score; + + /** + * number of bytes to read maximally to identify format. + * - encoding: unused + * - decoding: set by user + */ + int format_probesize; + + /** + * ',' separated list of allowed decoders. + * If NULL then all are allowed + * - encoding: unused + * - decoding: set by user + */ + char *codec_whitelist; + + /** + * ',' separated list of allowed demuxers. + * If NULL then all are allowed + * - encoding: unused + * - decoding: set by user + */ + char *format_whitelist; + + /** + * An opaque field for libavformat internal usage. + * Must not be accessed in any way by callers. + */ + AVFormatInternal *internal; + + /** + * IO repositioned flag. + * This is set by avformat when the underlaying IO context read pointer + * is repositioned, for example when doing byte based seeking. + * Demuxers can use the flag to detect such changes. + */ + int io_repositioned; + + /** + * Forced video codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user + */ + AVCodec *video_codec; + + /** + * Forced audio codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user + */ + AVCodec *audio_codec; + + /** + * Forced subtitle codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user + */ + AVCodec *subtitle_codec; + + /** + * Forced data codec. + * This allows forcing a specific decoder, even when there are multiple with + * the same codec_id. + * Demuxing: Set by user + */ + AVCodec *data_codec; + + /** + * Number of bytes to be written as padding in a metadata header. + * Demuxing: Unused. + * Muxing: Set by user via av_format_set_metadata_header_padding. + */ + int metadata_header_padding; + + /** + * User data. + * This is a place for some private data of the user. + */ + void *opaque; + + /** + * Callback used by devices to communicate with application. + */ + av_format_control_message control_message_cb; + + /** + * Output timestamp offset, in microseconds. + * Muxing: set by user + */ + int64_t output_ts_offset; + + /** + * dump format separator. + * can be ", " or "\n " or anything else + * - muxing: Set by user. + * - demuxing: Set by user. + */ + uint8_t *dump_separator; + + /** + * Forced Data codec_id. + * Demuxing: Set by user. + */ + enum AVCodecID data_codec_id; + +#if FF_API_OLD_OPEN_CALLBACKS + /** + * Called to open further IO contexts when needed for demuxing. + * + * This can be set by the user application to perform security checks on + * the URLs before opening them. + * The function should behave like avio_open2(), AVFormatContext is provided + * as contextual information and to reach AVFormatContext.opaque. + * + * If NULL then some simple checks are used together with avio_open2(). + * + * Must not be accessed directly from outside avformat. + * @See av_format_set_open_cb() + * + * Demuxing: Set by user. + * + * @deprecated Use io_open and io_close. + */ + attribute_deprecated + int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); +#endif + + /** + * ',' separated list of allowed protocols. + * - encoding: unused + * - decoding: set by user + */ + char *protocol_whitelist; + + /* + * A callback for opening new IO streams. + * + * Whenever a muxer or a demuxer needs to open an IO stream (typically from + * avformat_open_input() for demuxers, but for certain formats can happen at + * other times as well), it will call this callback to obtain an IO context. + * + * @param s the format context + * @param pb on success, the newly opened IO context should be returned here + * @param url the url to open + * @param flags a combination of AVIO_FLAG_* + * @param options a dictionary of additional options, with the same + * semantics as in avio_open2() + * @return 0 on success, a negative AVERROR code on failure + * + * @note Certain muxers and demuxers do nesting, i.e. they open one or more + * additional internal format contexts. Thus the AVFormatContext pointer + * passed to this callback may be different from the one facing the caller. + * It will, however, have the same 'opaque' field. + */ + int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, + int flags, AVDictionary **options); + + /** + * A callback for closing the streams opened with AVFormatContext.io_open(). + */ + void (*io_close)(struct AVFormatContext *s, AVIOContext *pb); + + /** + * ',' separated list of disallowed protocols. + * - encoding: unused + * - decoding: set by user + */ + char *protocol_blacklist; + + /** + * The maximum number of streams. + * - encoding: unused + * - decoding: set by user + */ + int max_streams; +} AVFormatContext; + +/** + * Accessors for some AVFormatContext fields. These used to be provided for ABI + * compatibility, and do not need to be used anymore. + */ +int av_format_get_probe_score(const AVFormatContext *s); +AVCodec * av_format_get_video_codec(const AVFormatContext *s); +void av_format_set_video_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_audio_codec(const AVFormatContext *s); +void av_format_set_audio_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_subtitle_codec(const AVFormatContext *s); +void av_format_set_subtitle_codec(AVFormatContext *s, AVCodec *c); +AVCodec * av_format_get_data_codec(const AVFormatContext *s); +void av_format_set_data_codec(AVFormatContext *s, AVCodec *c); +int av_format_get_metadata_header_padding(const AVFormatContext *s); +void av_format_set_metadata_header_padding(AVFormatContext *s, int c); +void * av_format_get_opaque(const AVFormatContext *s); +void av_format_set_opaque(AVFormatContext *s, void *opaque); +av_format_control_message av_format_get_control_message_cb(const AVFormatContext *s); +void av_format_set_control_message_cb(AVFormatContext *s, av_format_control_message callback); +#if FF_API_OLD_OPEN_CALLBACKS +attribute_deprecated AVOpenCallback av_format_get_open_cb(const AVFormatContext *s); +attribute_deprecated void av_format_set_open_cb(AVFormatContext *s, AVOpenCallback callback); +#endif + +/** + * This function will cause global side data to be injected in the next packet + * of each stream as well as after any subsequent seek. + */ +void av_format_inject_global_side_data(AVFormatContext *s); + +/** + * Returns the method used to set ctx->duration. + * + * @return AVFMT_DURATION_FROM_PTS, AVFMT_DURATION_FROM_STREAM, or AVFMT_DURATION_FROM_BITRATE. + */ +enum AVDurationEstimationMethod av_fmt_ctx_get_duration_estimation_method(const AVFormatContext* ctx); + +typedef struct AVPacketList { + AVPacket pkt; + struct AVPacketList *next; +} AVPacketList; + + +/** + * @defgroup lavf_core Core functions + * @ingroup libavf + * + * Functions for querying libavformat capabilities, allocating core structures, + * etc. + * @{ + */ + +/** + * Return the LIBAVFORMAT_VERSION_INT constant. + */ +unsigned avformat_version(void); + +/** + * Return the libavformat build-time configuration. + */ +const char *avformat_configuration(void); + +/** + * Return the libavformat license. + */ +const char *avformat_license(void); + +/** + * Initialize libavformat and register all the muxers, demuxers and + * protocols. If you do not call this function, then you can select + * exactly which formats you want to support. + * + * @see av_register_input_format() + * @see av_register_output_format() + */ +void av_register_all(void); + +void av_register_input_format(AVInputFormat *format); +void av_register_output_format(AVOutputFormat *format); + +/** + * Do global initialization of network components. This is optional, + * but recommended, since it avoids the overhead of implicitly + * doing the setup for each session. + * + * Calling this function will become mandatory if using network + * protocols at some major version bump. + */ +int avformat_network_init(void); + +/** + * Undo the initialization done by avformat_network_init. + */ +int avformat_network_deinit(void); + +/** + * If f is NULL, returns the first registered input format, + * if f is non-NULL, returns the next registered input format after f + * or NULL if f is the last one. + */ +AVInputFormat *av_iformat_next(const AVInputFormat *f); + +/** + * If f is NULL, returns the first registered output format, + * if f is non-NULL, returns the next registered output format after f + * or NULL if f is the last one. + */ +AVOutputFormat *av_oformat_next(const AVOutputFormat *f); + +/** + * Allocate an AVFormatContext. + * avformat_free_context() can be used to free the context and everything + * allocated by the framework within it. + */ +AVFormatContext *avformat_alloc_context(void); + +/** + * Free an AVFormatContext and all its streams. + * @param s context to free + */ +void avformat_free_context(AVFormatContext *s); + +/** + * Get the AVClass for AVFormatContext. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + */ +const AVClass *avformat_get_class(void); + +/** + * Add a new stream to a media file. + * + * When demuxing, it is called by the demuxer in read_header(). If the + * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also + * be called in read_packet(). + * + * When muxing, should be called by the user before avformat_write_header(). + * + * User is required to call avcodec_close() and avformat_free_context() to + * clean up the allocation by avformat_new_stream(). + * + * @param s media file handle + * @param c If non-NULL, the AVCodecContext corresponding to the new stream + * will be initialized to use this codec. This is needed for e.g. codec-specific + * defaults to be set, so codec should be provided if it is known. + * + * @return newly created stream or NULL on error. + */ +AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c); + +/** + * Wrap an existing array as stream side data. + * + * @param st stream + * @param type side information type + * @param data the side data array. It must be allocated with the av_malloc() + * family of functions. The ownership of the data is transferred to + * st. + * @param size side information size + * @return zero on success, a negative AVERROR code on failure. On failure, + * the stream is unchanged and the data remains owned by the caller. + */ +int av_stream_add_side_data(AVStream *st, enum AVPacketSideDataType type, + uint8_t *data, size_t size); + +/** + * Allocate new information from stream. + * + * @param stream stream + * @param type desired side information type + * @param size side information size + * @return pointer to fresh allocated data or NULL otherwise + */ +uint8_t *av_stream_new_side_data(AVStream *stream, + enum AVPacketSideDataType type, int size); +/** + * Get side information from stream. + * + * @param stream stream + * @param type desired side information type + * @param size pointer for side information size to store (optional) + * @return pointer to data if present or NULL otherwise + */ +#if FF_API_NOCONST_GET_SIDE_DATA +uint8_t *av_stream_get_side_data(AVStream *stream, + enum AVPacketSideDataType type, int *size); +#else +uint8_t *av_stream_get_side_data(const AVStream *stream, + enum AVPacketSideDataType type, int *size); +#endif + +AVProgram *av_new_program(AVFormatContext *s, int id); + +/** + * @} + */ + + +/** + * Allocate an AVFormatContext for an output format. + * avformat_free_context() can be used to free the context and + * everything allocated by the framework within it. + * + * @param *ctx is set to the created format context, or to NULL in + * case of failure + * @param oformat format to use for allocating the context, if NULL + * format_name and filename are used instead + * @param format_name the name of output format to use for allocating the + * context, if NULL filename is used instead + * @param filename the name of the filename to use for allocating the + * context, may be NULL + * @return >= 0 in case of success, a negative AVERROR code in case of + * failure + */ +int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, + const char *format_name, const char *filename); + +/** + * @addtogroup lavf_decoding + * @{ + */ + +/** + * Find AVInputFormat based on the short name of the input format. + */ +AVInputFormat *av_find_input_format(const char *short_name); + +/** + * Guess the file format. + * + * @param pd data to be probed + * @param is_opened Whether the file is already opened; determines whether + * demuxers with or without AVFMT_NOFILE are probed. + */ +AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened); + +/** + * Guess the file format. + * + * @param pd data to be probed + * @param is_opened Whether the file is already opened; determines whether + * demuxers with or without AVFMT_NOFILE are probed. + * @param score_max A probe score larger that this is required to accept a + * detection, the variable is set to the actual detection + * score afterwards. + * If the score is <= AVPROBE_SCORE_MAX / 4 it is recommended + * to retry with a larger probe buffer. + */ +AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max); + +/** + * Guess the file format. + * + * @param is_opened Whether the file is already opened; determines whether + * demuxers with or without AVFMT_NOFILE are probed. + * @param score_ret The score of the best detection. + */ +AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret); + +/** + * Probe a bytestream to determine the input format. Each time a probe returns + * with a score that is too low, the probe buffer size is increased and another + * attempt is made. When the maximum probe size is reached, the input format + * with the highest score is returned. + * + * @param pb the bytestream to probe + * @param fmt the input format is put here + * @param url the url of the stream + * @param logctx the log context + * @param offset the offset within the bytestream to probe from + * @param max_probe_size the maximum probe buffer size (zero for default) + * @return the score in case of success, a negative value corresponding to an + * the maximal score is AVPROBE_SCORE_MAX + * AVERROR code otherwise + */ +int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, + const char *url, void *logctx, + unsigned int offset, unsigned int max_probe_size); + +/** + * Like av_probe_input_buffer2() but returns 0 on success + */ +int av_probe_input_buffer(AVIOContext *pb, AVInputFormat **fmt, + const char *url, void *logctx, + unsigned int offset, unsigned int max_probe_size); + +/** + * Open an input stream and read the header. The codecs are not opened. + * The stream must be closed with avformat_close_input(). + * + * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context). + * May be a pointer to NULL, in which case an AVFormatContext is allocated by this + * function and written into ps. + * Note that a user-supplied AVFormatContext will be freed on failure. + * @param url URL of the stream to open. + * @param fmt If non-NULL, this parameter forces a specific input format. + * Otherwise the format is autodetected. + * @param options A dictionary filled with AVFormatContext and demuxer-private options. + * On return this parameter will be destroyed and replaced with a dict containing + * options that were not found. May be NULL. + * + * @return 0 on success, a negative AVERROR on failure. + * + * @note If you want to use custom IO, preallocate the format context and set its pb field. + */ +int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options); + +attribute_deprecated +int av_demuxer_open(AVFormatContext *ic); + +/** + * Read packets of a media file to get stream information. This + * is useful for file formats with no headers such as MPEG. This + * function also computes the real framerate in case of MPEG-2 repeat + * frame mode. + * The logical file position is not changed by this function; + * examined packets may be buffered for later processing. + * + * @param ic media file handle + * @param options If non-NULL, an ic.nb_streams long array of pointers to + * dictionaries, where i-th member contains options for + * codec corresponding to i-th stream. + * On return each dictionary will be filled with options that were not found. + * @return >=0 if OK, AVERROR_xxx on error + * + * @note this function isn't guaranteed to open all the codecs, so + * options being non-empty at return is a perfectly normal behavior. + * + * @todo Let the user decide somehow what information is needed so that + * we do not waste time getting stuff the user does not need. + */ +int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options); + +/** + * Find the programs which belong to a given stream. + * + * @param ic media file handle + * @param last the last found program, the search will start after this + * program, or from the beginning if it is NULL + * @param s stream index + * @return the next program which belongs to s, NULL if no program is found or + * the last program is not among the programs of ic. + */ +AVProgram *av_find_program_from_stream(AVFormatContext *ic, AVProgram *last, int s); + +void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned int idx); + +/** + * Find the "best" stream in the file. + * The best stream is determined according to various heuristics as the most + * likely to be what the user expects. + * If the decoder parameter is non-NULL, av_find_best_stream will find the + * default decoder for the stream's codec; streams for which no decoder can + * be found are ignored. + * + * @param ic media file handle + * @param type stream type: video, audio, subtitles, etc. + * @param wanted_stream_nb user-requested stream number, + * or -1 for automatic selection + * @param related_stream try to find a stream related (eg. in the same + * program) to this one, or -1 if none + * @param decoder_ret if non-NULL, returns the decoder for the + * selected stream + * @param flags flags; none are currently defined + * @return the non-negative stream number in case of success, + * AVERROR_STREAM_NOT_FOUND if no stream with the requested type + * could be found, + * AVERROR_DECODER_NOT_FOUND if streams were found but no decoder + * @note If av_find_best_stream returns successfully and decoder_ret is not + * NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec. + */ +int av_find_best_stream(AVFormatContext *ic, + enum AVMediaType type, + int wanted_stream_nb, + int related_stream, + AVCodec **decoder_ret, + int flags); + +/** + * Return the next frame of a stream. + * This function returns what is stored in the file, and does not validate + * that what is there are valid frames for the decoder. It will split what is + * stored in the file into frames and return one for each call. It will not + * omit invalid data between valid frames so as to give the decoder the maximum + * information possible for decoding. + * + * If pkt->buf is NULL, then the packet is valid until the next + * av_read_frame() or until avformat_close_input(). Otherwise the packet + * is valid indefinitely. In both cases the packet must be freed with + * av_packet_unref when it is no longer needed. For video, the packet contains + * exactly one frame. For audio, it contains an integer number of frames if each + * frame has a known fixed size (e.g. PCM or ADPCM data). If the audio frames + * have a variable size (e.g. MPEG audio), then it contains one frame. + * + * pkt->pts, pkt->dts and pkt->duration are always set to correct + * values in AVStream.time_base units (and guessed if the format cannot + * provide them). pkt->pts can be AV_NOPTS_VALUE if the video format + * has B-frames, so it is better to rely on pkt->dts if you do not + * decompress the payload. + * + * @return 0 if OK, < 0 on error or end of file + */ +int av_read_frame(AVFormatContext *s, AVPacket *pkt); + +/** + * Seek to the keyframe at timestamp. + * 'timestamp' in 'stream_index'. + * + * @param s media file handle + * @param stream_index If stream_index is (-1), a default + * stream is selected, and timestamp is automatically converted + * from AV_TIME_BASE units to the stream specific time_base. + * @param timestamp Timestamp in AVStream.time_base units + * or, if no stream is specified, in AV_TIME_BASE units. + * @param flags flags which select direction and seeking mode + * @return >= 0 on success + */ +int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, + int flags); + +/** + * Seek to timestamp ts. + * Seeking will be done so that the point from which all active streams + * can be presented successfully will be closest to ts and within min/max_ts. + * Active streams are all streams that have AVStream.discard < AVDISCARD_ALL. + * + * If flags contain AVSEEK_FLAG_BYTE, then all timestamps are in bytes and + * are the file position (this may not be supported by all demuxers). + * If flags contain AVSEEK_FLAG_FRAME, then all timestamps are in frames + * in the stream with stream_index (this may not be supported by all demuxers). + * Otherwise all timestamps are in units of the stream selected by stream_index + * or if stream_index is -1, in AV_TIME_BASE units. + * If flags contain AVSEEK_FLAG_ANY, then non-keyframes are treated as + * keyframes (this may not be supported by all demuxers). + * If flags contain AVSEEK_FLAG_BACKWARD, it is ignored. + * + * @param s media file handle + * @param stream_index index of the stream which is used as time base reference + * @param min_ts smallest acceptable timestamp + * @param ts target timestamp + * @param max_ts largest acceptable timestamp + * @param flags flags + * @return >=0 on success, error code otherwise + * + * @note This is part of the new seek API which is still under construction. + * Thus do not use this yet. It may change at any time, do not expect + * ABI compatibility yet! + */ +int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); + +/** + * Discard all internally buffered data. This can be useful when dealing with + * discontinuities in the byte stream. Generally works only with formats that + * can resync. This includes headerless formats like MPEG-TS/TS but should also + * work with NUT, Ogg and in a limited way AVI for example. + * + * The set of streams, the detected duration, stream parameters and codecs do + * not change when calling this function. If you want a complete reset, it's + * better to open a new AVFormatContext. + * + * This does not flush the AVIOContext (s->pb). If necessary, call + * avio_flush(s->pb) before calling this function. + * + * @param s media file handle + * @return >=0 on success, error code otherwise + */ +int avformat_flush(AVFormatContext *s); + +/** + * Start playing a network-based stream (e.g. RTSP stream) at the + * current position. + */ +int av_read_play(AVFormatContext *s); + +/** + * Pause a network-based stream (e.g. RTSP stream). + * + * Use av_read_play() to resume it. + */ +int av_read_pause(AVFormatContext *s); + +/** + * Close an opened input AVFormatContext. Free it and all its contents + * and set *s to NULL. + */ +void avformat_close_input(AVFormatContext **s); +/** + * @} + */ + +#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward +#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes +#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes +#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number + +/** + * @addtogroup lavf_encoding + * @{ + */ + +#define AVSTREAM_INIT_IN_WRITE_HEADER 0 ///< stream parameters initialized in avformat_write_header +#define AVSTREAM_INIT_IN_INIT_OUTPUT 1 ///< stream parameters initialized in avformat_init_output + +/** + * Allocate the stream private data and write the stream header to + * an output media file. + * + * @param s Media file handle, must be allocated with avformat_alloc_context(). + * Its oformat field must be set to the desired output format; + * Its pb field must be set to an already opened AVIOContext. + * @param options An AVDictionary filled with AVFormatContext and muxer-private options. + * On return this parameter will be destroyed and replaced with a dict containing + * options that were not found. May be NULL. + * + * @return AVSTREAM_INIT_IN_WRITE_HEADER on success if the codec had not already been fully initialized in avformat_init, + * AVSTREAM_INIT_IN_INIT_OUTPUT on success if the codec had already been fully initialized in avformat_init, + * negative AVERROR on failure. + * + * @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_init_output. + */ +av_warn_unused_result +int avformat_write_header(AVFormatContext *s, AVDictionary **options); + +/** + * Allocate the stream private data and initialize the codec, but do not write the header. + * May optionally be used before avformat_write_header to initialize stream parameters + * before actually writing the header. + * If using this function, do not pass the same options to avformat_write_header. + * + * @param s Media file handle, must be allocated with avformat_alloc_context(). + * Its oformat field must be set to the desired output format; + * Its pb field must be set to an already opened AVIOContext. + * @param options An AVDictionary filled with AVFormatContext and muxer-private options. + * On return this parameter will be destroyed and replaced with a dict containing + * options that were not found. May be NULL. + * + * @return AVSTREAM_INIT_IN_WRITE_HEADER on success if the codec requires avformat_write_header to fully initialize, + * AVSTREAM_INIT_IN_INIT_OUTPUT on success if the codec has been fully initialized, + * negative AVERROR on failure. + * + * @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_write_header. + */ +av_warn_unused_result +int avformat_init_output(AVFormatContext *s, AVDictionary **options); + +/** + * Write a packet to an output media file. + * + * This function passes the packet directly to the muxer, without any buffering + * or reordering. The caller is responsible for correctly interleaving the + * packets if the format requires it. Callers that want libavformat to handle + * the interleaving should call av_interleaved_write_frame() instead of this + * function. + * + * @param s media file handle + * @param pkt The packet containing the data to be written. Note that unlike + * av_interleaved_write_frame(), this function does not take + * ownership of the packet passed to it (though some muxers may make + * an internal reference to the input packet). + *
+ * This parameter can be NULL (at any time, not just at the end), in + * order to immediately flush data buffered within the muxer, for + * muxers that buffer up data internally before writing it to the + * output. + *
+ * Packet's @ref AVPacket.stream_index "stream_index" field must be + * set to the index of the corresponding stream in @ref + * AVFormatContext.streams "s->streams". + *
+ * The timestamps (@ref AVPacket.pts "pts", @ref AVPacket.dts "dts") + * must be set to correct values in the stream's timebase (unless the + * output format is flagged with the AVFMT_NOTIMESTAMPS flag, then + * they can be set to AV_NOPTS_VALUE). + * The dts for subsequent packets passed to this function must be strictly + * increasing when compared in their respective timebases (unless the + * output format is flagged with the AVFMT_TS_NONSTRICT, then they + * merely have to be nondecreasing). @ref AVPacket.duration + * "duration") should also be set if known. + * @return < 0 on error, = 0 if OK, 1 if flushed and there is no more data to flush + * + * @see av_interleaved_write_frame() + */ +int av_write_frame(AVFormatContext *s, AVPacket *pkt); + +/** + * Write a packet to an output media file ensuring correct interleaving. + * + * This function will buffer the packets internally as needed to make sure the + * packets in the output file are properly interleaved in the order of + * increasing dts. Callers doing their own interleaving should call + * av_write_frame() instead of this function. + * + * Using this function instead of av_write_frame() can give muxers advance + * knowledge of future packets, improving e.g. the behaviour of the mp4 + * muxer for VFR content in fragmenting mode. + * + * @param s media file handle + * @param pkt The packet containing the data to be written. + *
+ * If the packet is reference-counted, this function will take + * ownership of this reference and unreference it later when it sees + * fit. + * The caller must not access the data through this reference after + * this function returns. If the packet is not reference-counted, + * libavformat will make a copy. + *
+ * This parameter can be NULL (at any time, not just at the end), to + * flush the interleaving queues. + *
+ * Packet's @ref AVPacket.stream_index "stream_index" field must be + * set to the index of the corresponding stream in @ref + * AVFormatContext.streams "s->streams". + *
+ * The timestamps (@ref AVPacket.pts "pts", @ref AVPacket.dts "dts") + * must be set to correct values in the stream's timebase (unless the + * output format is flagged with the AVFMT_NOTIMESTAMPS flag, then + * they can be set to AV_NOPTS_VALUE). + * The dts for subsequent packets in one stream must be strictly + * increasing (unless the output format is flagged with the + * AVFMT_TS_NONSTRICT, then they merely have to be nondecreasing). + * @ref AVPacket.duration "duration") should also be set if known. + * + * @return 0 on success, a negative AVERROR on error. Libavformat will always + * take care of freeing the packet, even if this function fails. + * + * @see av_write_frame(), AVFormatContext.max_interleave_delta + */ +int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt); + +/** + * Write an uncoded frame to an output media file. + * + * The frame must be correctly interleaved according to the container + * specification; if not, then av_interleaved_write_frame() must be used. + * + * See av_interleaved_write_frame() for details. + */ +int av_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Write an uncoded frame to an output media file. + * + * If the muxer supports it, this function makes it possible to write an AVFrame + * structure directly, without encoding it into a packet. + * It is mostly useful for devices and similar special muxers that use raw + * video or PCM data and will not serialize it into a byte stream. + * + * To test whether it is possible to use it with a given muxer and stream, + * use av_write_uncoded_frame_query(). + * + * The caller gives up ownership of the frame and must not access it + * afterwards. + * + * @return >=0 for success, a negative code on error + */ +int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index, + AVFrame *frame); + +/** + * Test whether a muxer supports uncoded frame. + * + * @return >=0 if an uncoded frame can be written to that muxer and stream, + * <0 if not + */ +int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index); + +/** + * Write the stream trailer to an output media file and free the + * file private data. + * + * May only be called after a successful call to avformat_write_header. + * + * @param s media file handle + * @return 0 if OK, AVERROR_xxx on error + */ +int av_write_trailer(AVFormatContext *s); + +/** + * Return the output format in the list of registered output formats + * which best matches the provided parameters, or return NULL if + * there is no match. + * + * @param short_name if non-NULL checks if short_name matches with the + * names of the registered formats + * @param filename if non-NULL checks if filename terminates with the + * extensions of the registered formats + * @param mime_type if non-NULL checks if mime_type matches with the + * MIME type of the registered formats + */ +AVOutputFormat *av_guess_format(const char *short_name, + const char *filename, + const char *mime_type); + +/** + * Guess the codec ID based upon muxer and filename. + */ +enum AVCodecID av_guess_codec(AVOutputFormat *fmt, const char *short_name, + const char *filename, const char *mime_type, + enum AVMediaType type); + +/** + * Get timing information for the data currently output. + * The exact meaning of "currently output" depends on the format. + * It is mostly relevant for devices that have an internal buffer and/or + * work in real time. + * @param s media file handle + * @param stream stream in the media file + * @param[out] dts DTS of the last packet output for the stream, in stream + * time_base units + * @param[out] wall absolute time when that packet whas output, + * in microsecond + * @return 0 if OK, AVERROR(ENOSYS) if the format does not support it + * Note: some formats or devices may not allow to measure dts and wall + * atomically. + */ +int av_get_output_timestamp(struct AVFormatContext *s, int stream, + int64_t *dts, int64_t *wall); + + +/** + * @} + */ + + +/** + * @defgroup lavf_misc Utility functions + * @ingroup libavf + * @{ + * + * Miscellaneous utility functions related to both muxing and demuxing + * (or neither). + */ + +/** + * Send a nice hexadecimal dump of a buffer to the specified file stream. + * + * @param f The file stream pointer where the dump should be sent to. + * @param buf buffer + * @param size buffer size + * + * @see av_hex_dump_log, av_pkt_dump2, av_pkt_dump_log2 + */ +void av_hex_dump(FILE *f, const uint8_t *buf, int size); + +/** + * Send a nice hexadecimal dump of a buffer to the log. + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message, lower values signifying + * higher importance. + * @param buf buffer + * @param size buffer size + * + * @see av_hex_dump, av_pkt_dump2, av_pkt_dump_log2 + */ +void av_hex_dump_log(void *avcl, int level, const uint8_t *buf, int size); + +/** + * Send a nice dump of a packet to the specified file stream. + * + * @param f The file stream pointer where the dump should be sent to. + * @param pkt packet to dump + * @param dump_payload True if the payload must be displayed, too. + * @param st AVStream that the packet belongs to + */ +void av_pkt_dump2(FILE *f, const AVPacket *pkt, int dump_payload, const AVStream *st); + + +/** + * Send a nice dump of a packet to the log. + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message, lower values signifying + * higher importance. + * @param pkt packet to dump + * @param dump_payload True if the payload must be displayed, too. + * @param st AVStream that the packet belongs to + */ +void av_pkt_dump_log2(void *avcl, int level, const AVPacket *pkt, int dump_payload, + const AVStream *st); + +/** + * Get the AVCodecID for the given codec tag tag. + * If no codec id is found returns AV_CODEC_ID_NONE. + * + * @param tags list of supported codec_id-codec_tag pairs, as stored + * in AVInputFormat.codec_tag and AVOutputFormat.codec_tag + * @param tag codec tag to match to a codec ID + */ +enum AVCodecID av_codec_get_id(const struct AVCodecTag * const *tags, unsigned int tag); + +/** + * Get the codec tag for the given codec id id. + * If no codec tag is found returns 0. + * + * @param tags list of supported codec_id-codec_tag pairs, as stored + * in AVInputFormat.codec_tag and AVOutputFormat.codec_tag + * @param id codec ID to match to a codec tag + */ +unsigned int av_codec_get_tag(const struct AVCodecTag * const *tags, enum AVCodecID id); + +/** + * Get the codec tag for the given codec id. + * + * @param tags list of supported codec_id - codec_tag pairs, as stored + * in AVInputFormat.codec_tag and AVOutputFormat.codec_tag + * @param id codec id that should be searched for in the list + * @param tag A pointer to the found tag + * @return 0 if id was not found in tags, > 0 if it was found + */ +int av_codec_get_tag2(const struct AVCodecTag * const *tags, enum AVCodecID id, + unsigned int *tag); + +int av_find_default_stream_index(AVFormatContext *s); + +/** + * Get the index for a specific timestamp. + * + * @param st stream that the timestamp belongs to + * @param timestamp timestamp to retrieve the index for + * @param flags if AVSEEK_FLAG_BACKWARD then the returned index will correspond + * to the timestamp which is <= the requested one, if backward + * is 0, then it will be >= + * if AVSEEK_FLAG_ANY seek to any frame, only keyframes otherwise + * @return < 0 if no such timestamp could be found + */ +int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags); + +/** + * Add an index entry into a sorted list. Update the entry if the list + * already contains it. + * + * @param timestamp timestamp in the time base of the given stream + */ +int av_add_index_entry(AVStream *st, int64_t pos, int64_t timestamp, + int size, int distance, int flags); + + +/** + * Split a URL string into components. + * + * The pointers to buffers for storing individual components may be null, + * in order to ignore that component. Buffers for components not found are + * set to empty strings. If the port is not found, it is set to a negative + * value. + * + * @param proto the buffer for the protocol + * @param proto_size the size of the proto buffer + * @param authorization the buffer for the authorization + * @param authorization_size the size of the authorization buffer + * @param hostname the buffer for the host name + * @param hostname_size the size of the hostname buffer + * @param port_ptr a pointer to store the port number in + * @param path the buffer for the path + * @param path_size the size of the path buffer + * @param url the URL to split + */ +void av_url_split(char *proto, int proto_size, + char *authorization, int authorization_size, + char *hostname, int hostname_size, + int *port_ptr, + char *path, int path_size, + const char *url); + + +/** + * Print detailed information about the input or output format, such as + * duration, bitrate, streams, container, programs, metadata, side data, + * codec and time base. + * + * @param ic the context to analyze + * @param index index of the stream to dump information about + * @param url the URL to print, such as source or destination file + * @param is_output Select whether the specified context is an input(0) or output(1) + */ +void av_dump_format(AVFormatContext *ic, + int index, + const char *url, + int is_output); + + +#define AV_FRAME_FILENAME_FLAGS_MULTIPLE 1 ///< Allow multiple %d + +/** + * Return in 'buf' the path with '%d' replaced by a number. + * + * Also handles the '%0nd' format where 'n' is the total number + * of digits and '%%'. + * + * @param buf destination buffer + * @param buf_size destination buffer size + * @param path numbered sequence string + * @param number frame number + * @param flags AV_FRAME_FILENAME_FLAGS_* + * @return 0 if OK, -1 on format error + */ +int av_get_frame_filename2(char *buf, int buf_size, + const char *path, int number, int flags); + +int av_get_frame_filename(char *buf, int buf_size, + const char *path, int number); + +/** + * Check whether filename actually is a numbered sequence generator. + * + * @param filename possible numbered sequence string + * @return 1 if a valid numbered sequence string, 0 otherwise + */ +int av_filename_number_test(const char *filename); + +/** + * Generate an SDP for an RTP session. + * + * Note, this overwrites the id values of AVStreams in the muxer contexts + * for getting unique dynamic payload types. + * + * @param ac array of AVFormatContexts describing the RTP streams. If the + * array is composed by only one context, such context can contain + * multiple AVStreams (one AVStream per RTP stream). Otherwise, + * all the contexts in the array (an AVCodecContext per RTP stream) + * must contain only one AVStream. + * @param n_files number of AVCodecContexts contained in ac + * @param buf buffer where the SDP will be stored (must be allocated by + * the caller) + * @param size the size of the buffer + * @return 0 if OK, AVERROR_xxx on error + */ +int av_sdp_create(AVFormatContext *ac[], int n_files, char *buf, int size); + +/** + * Return a positive value if the given filename has one of the given + * extensions, 0 otherwise. + * + * @param filename file name to check against the given extensions + * @param extensions a comma-separated list of filename extensions + */ +int av_match_ext(const char *filename, const char *extensions); + +/** + * Test if the given container can store a codec. + * + * @param ofmt container to check for compatibility + * @param codec_id codec to potentially store in container + * @param std_compliance standards compliance level, one of FF_COMPLIANCE_* + * + * @return 1 if codec with ID codec_id can be stored in ofmt, 0 if it cannot. + * A negative number if this information is not available. + */ +int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id, + int std_compliance); + +/** + * @defgroup riff_fourcc RIFF FourCCs + * @{ + * Get the tables mapping RIFF FourCCs to libavcodec AVCodecIDs. The tables are + * meant to be passed to av_codec_get_id()/av_codec_get_tag() as in the + * following code: + * @code + * uint32_t tag = MKTAG('H', '2', '6', '4'); + * const struct AVCodecTag *table[] = { avformat_get_riff_video_tags(), 0 }; + * enum AVCodecID id = av_codec_get_id(table, tag); + * @endcode + */ +/** + * @return the table mapping RIFF FourCCs for video to libavcodec AVCodecID. + */ +const struct AVCodecTag *avformat_get_riff_video_tags(void); +/** + * @return the table mapping RIFF FourCCs for audio to AVCodecID. + */ +const struct AVCodecTag *avformat_get_riff_audio_tags(void); +/** + * @return the table mapping MOV FourCCs for video to libavcodec AVCodecID. + */ +const struct AVCodecTag *avformat_get_mov_video_tags(void); +/** + * @return the table mapping MOV FourCCs for audio to AVCodecID. + */ +const struct AVCodecTag *avformat_get_mov_audio_tags(void); + +/** + * @} + */ + +/** + * Guess the sample aspect ratio of a frame, based on both the stream and the + * frame aspect ratio. + * + * Since the frame aspect ratio is set by the codec but the stream aspect ratio + * is set by the demuxer, these two may not be equal. This function tries to + * return the value that you should use if you would like to display the frame. + * + * Basic logic is to use the stream aspect ratio if it is set to something sane + * otherwise use the frame aspect ratio. This way a container setting, which is + * usually easy to modify can override the coded value in the frames. + * + * @param format the format context which the stream is part of + * @param stream the stream which the frame is part of + * @param frame the frame with the aspect ratio to be determined + * @return the guessed (valid) sample_aspect_ratio, 0/1 if no idea + */ +AVRational av_guess_sample_aspect_ratio(AVFormatContext *format, AVStream *stream, AVFrame *frame); + +/** + * Guess the frame rate, based on both the container and codec information. + * + * @param ctx the format context which the stream is part of + * @param stream the stream which the frame is part of + * @param frame the frame for which the frame rate should be determined, may be NULL + * @return the guessed (valid) frame rate, 0/1 if no idea + */ +AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame); + +/** + * Check if the stream st contained in s is matched by the stream specifier + * spec. + * + * See the "stream specifiers" chapter in the documentation for the syntax + * of spec. + * + * @return >0 if st is matched by spec; + * 0 if st is not matched by spec; + * AVERROR code if spec is invalid + * + * @note A stream specifier can match several streams in the format. + */ +int avformat_match_stream_specifier(AVFormatContext *s, AVStream *st, + const char *spec); + +int avformat_queue_attached_pictures(AVFormatContext *s); + +#if FF_API_OLD_BSF +/** + * Apply a list of bitstream filters to a packet. + * + * @param codec AVCodecContext, usually from an AVStream + * @param pkt the packet to apply filters to. If, on success, the returned + * packet has size == 0 and side_data_elems == 0, it indicates that + * the packet should be dropped + * @param bsfc a NULL-terminated list of filters to apply + * @return >=0 on success; + * AVERROR code on failure + */ +attribute_deprecated +int av_apply_bitstream_filters(AVCodecContext *codec, AVPacket *pkt, + AVBitStreamFilterContext *bsfc); +#endif + +enum AVTimebaseSource { + AVFMT_TBCF_AUTO = -1, + AVFMT_TBCF_DECODER, + AVFMT_TBCF_DEMUXER, +#if FF_API_R_FRAME_RATE + AVFMT_TBCF_R_FRAMERATE, +#endif +}; + +/** + * Transfer internal timing information from one stream to another. + * + * This function is useful when doing stream copy. + * + * @param ofmt target output format for ost + * @param ost output stream which needs timings copy and adjustments + * @param ist reference input stream to copy timings from + * @param copy_tb define from where the stream codec timebase needs to be imported + */ +int avformat_transfer_internal_stream_timing_info(const AVOutputFormat *ofmt, + AVStream *ost, const AVStream *ist, + enum AVTimebaseSource copy_tb); + +/** + * Get the internal codec timebase from a stream. + * + * @param st input stream to extract the timebase from + */ +AVRational av_stream_get_codec_timebase(const AVStream *st); + +/** + * @} + */ + +#endif /* AVFORMAT_AVFORMAT_H */ diff --git a/thrid-party/ffmpeg/include/libavformat/avio.h b/thrid-party/ffmpeg/include/libavformat/avio.h new file mode 100644 index 0000000000..f9c5972ada --- /dev/null +++ b/thrid-party/ffmpeg/include/libavformat/avio.h @@ -0,0 +1,867 @@ +/* + * copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef AVFORMAT_AVIO_H +#define AVFORMAT_AVIO_H + +/** + * @file + * @ingroup lavf_io + * Buffered I/O operations + */ + +#include + +#include "libavutil/common.h" +#include "libavutil/dict.h" +#include "libavutil/log.h" + +#include "libavformat/version.h" + +/** + * Seeking works like for a local file. + */ +#define AVIO_SEEKABLE_NORMAL (1 << 0) + +/** + * Seeking by timestamp with avio_seek_time() is possible. + */ +#define AVIO_SEEKABLE_TIME (1 << 1) + +/** + * Callback for checking whether to abort blocking functions. + * AVERROR_EXIT is returned in this case by the interrupted + * function. During blocking operations, callback is called with + * opaque as parameter. If the callback returns 1, the + * blocking operation will be aborted. + * + * No members can be added to this struct without a major bump, if + * new elements have been added after this struct in AVFormatContext + * or AVIOContext. + */ +typedef struct AVIOInterruptCB { + int (*callback)(void*); + void *opaque; +} AVIOInterruptCB; + +/** + * Directory entry types. + */ +enum AVIODirEntryType { + AVIO_ENTRY_UNKNOWN, + AVIO_ENTRY_BLOCK_DEVICE, + AVIO_ENTRY_CHARACTER_DEVICE, + AVIO_ENTRY_DIRECTORY, + AVIO_ENTRY_NAMED_PIPE, + AVIO_ENTRY_SYMBOLIC_LINK, + AVIO_ENTRY_SOCKET, + AVIO_ENTRY_FILE, + AVIO_ENTRY_SERVER, + AVIO_ENTRY_SHARE, + AVIO_ENTRY_WORKGROUP, +}; + +/** + * Describes single entry of the directory. + * + * Only name and type fields are guaranteed be set. + * Rest of fields are protocol or/and platform dependent and might be unknown. + */ +typedef struct AVIODirEntry { + char *name; /**< Filename */ + int type; /**< Type of the entry */ + int utf8; /**< Set to 1 when name is encoded with UTF-8, 0 otherwise. + Name can be encoded with UTF-8 even though 0 is set. */ + int64_t size; /**< File size in bytes, -1 if unknown. */ + int64_t modification_timestamp; /**< Time of last modification in microseconds since unix + epoch, -1 if unknown. */ + int64_t access_timestamp; /**< Time of last access in microseconds since unix epoch, + -1 if unknown. */ + int64_t status_change_timestamp; /**< Time of last status change in microseconds since unix + epoch, -1 if unknown. */ + int64_t user_id; /**< User ID of owner, -1 if unknown. */ + int64_t group_id; /**< Group ID of owner, -1 if unknown. */ + int64_t filemode; /**< Unix file mode, -1 if unknown. */ +} AVIODirEntry; + +typedef struct AVIODirContext { + struct URLContext *url_context; +} AVIODirContext; + +/** + * Different data types that can be returned via the AVIO + * write_data_type callback. + */ +enum AVIODataMarkerType { + /** + * Header data; this needs to be present for the stream to be decodeable. + */ + AVIO_DATA_MARKER_HEADER, + /** + * A point in the output bytestream where a decoder can start decoding + * (i.e. a keyframe). A demuxer/decoder given the data flagged with + * AVIO_DATA_MARKER_HEADER, followed by any AVIO_DATA_MARKER_SYNC_POINT, + * should give decodeable results. + */ + AVIO_DATA_MARKER_SYNC_POINT, + /** + * A point in the output bytestream where a demuxer can start parsing + * (for non self synchronizing bytestream formats). That is, any + * non-keyframe packet start point. + */ + AVIO_DATA_MARKER_BOUNDARY_POINT, + /** + * This is any, unlabelled data. It can either be a muxer not marking + * any positions at all, it can be an actual boundary/sync point + * that the muxer chooses not to mark, or a later part of a packet/fragment + * that is cut into multiple write callbacks due to limited IO buffer size. + */ + AVIO_DATA_MARKER_UNKNOWN, + /** + * Trailer data, which doesn't contain actual content, but only for + * finalizing the output file. + */ + AVIO_DATA_MARKER_TRAILER, + /** + * A point in the output bytestream where the underlying AVIOContext might + * flush the buffer depending on latency or buffering requirements. Typically + * means the end of a packet. + */ + AVIO_DATA_MARKER_FLUSH_POINT, +}; + +/** + * Bytestream IO Context. + * New fields can be added to the end with minor version bumps. + * Removal, reordering and changes to existing fields require a major + * version bump. + * sizeof(AVIOContext) must not be used outside libav*. + * + * @note None of the function pointers in AVIOContext should be called + * directly, they should only be set by the client application + * when implementing custom I/O. Normally these are set to the + * function pointers specified in avio_alloc_context() + */ +typedef struct AVIOContext { + /** + * A class for private options. + * + * If this AVIOContext is created by avio_open2(), av_class is set and + * passes the options down to protocols. + * + * If this AVIOContext is manually allocated, then av_class may be set by + * the caller. + * + * warning -- this field can be NULL, be sure to not pass this AVIOContext + * to any av_opt_* functions in that case. + */ + const AVClass *av_class; + + /* + * The following shows the relationship between buffer, buf_ptr, + * buf_ptr_max, buf_end, buf_size, and pos, when reading and when writing + * (since AVIOContext is used for both): + * + ********************************************************************************** + * READING + ********************************************************************************** + * + * | buffer_size | + * |---------------------------------------| + * | | + * + * buffer buf_ptr buf_end + * +---------------+-----------------------+ + * |/ / / / / / / /|/ / / / / / /| | + * read buffer: |/ / consumed / | to be read /| | + * |/ / / / / / / /|/ / / / / / /| | + * +---------------+-----------------------+ + * + * pos + * +-------------------------------------------+-----------------+ + * input file: | | | + * +-------------------------------------------+-----------------+ + * + * + ********************************************************************************** + * WRITING + ********************************************************************************** + * + * | buffer_size | + * |--------------------------------------| + * | | + * + * buf_ptr_max + * buffer (buf_ptr) buf_end + * +-----------------------+--------------+ + * |/ / / / / / / / / / / /| | + * write buffer: | / / to be flushed / / | | + * |/ / / / / / / / / / / /| | + * +-----------------------+--------------+ + * buf_ptr can be in this + * due to a backward seek + * + * pos + * +-------------+----------------------------------------------+ + * output file: | | | + * +-------------+----------------------------------------------+ + * + */ + unsigned char *buffer; /**< Start of the buffer. */ + int buffer_size; /**< Maximum buffer size */ + unsigned char *buf_ptr; /**< Current position in the buffer */ + unsigned char *buf_end; /**< End of the data, may be less than + buffer+buffer_size if the read function returned + less data than requested, e.g. for streams where + no more data has been received yet. */ + void *opaque; /**< A private pointer, passed to the read/write/seek/... + functions. */ + int (*read_packet)(void *opaque, uint8_t *buf, int buf_size); + int (*write_packet)(void *opaque, uint8_t *buf, int buf_size); + int64_t (*seek)(void *opaque, int64_t offset, int whence); + int64_t pos; /**< position in the file of the current buffer */ + int must_flush; /**< unused */ + int eof_reached; /**< true if eof reached */ + int write_flag; /**< true if open for writing */ + int max_packet_size; + unsigned long checksum; + unsigned char *checksum_ptr; + unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size); + int error; /**< contains the error code or 0 if no error happened */ + /** + * Pause or resume playback for network streaming protocols - e.g. MMS. + */ + int (*read_pause)(void *opaque, int pause); + /** + * Seek to a given timestamp in stream with the specified stream_index. + * Needed for some network streaming protocols which don't support seeking + * to byte position. + */ + int64_t (*read_seek)(void *opaque, int stream_index, + int64_t timestamp, int flags); + /** + * A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable. + */ + int seekable; + + /** + * max filesize, used to limit allocations + * This field is internal to libavformat and access from outside is not allowed. + */ + int64_t maxsize; + + /** + * avio_read and avio_write should if possible be satisfied directly + * instead of going through a buffer, and avio_seek will always + * call the underlying seek function directly. + */ + int direct; + + /** + * Bytes read statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int64_t bytes_read; + + /** + * seek statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int seek_count; + + /** + * writeout statistic + * This field is internal to libavformat and access from outside is not allowed. + */ + int writeout_count; + + /** + * Original buffer size + * used internally after probing and ensure seekback to reset the buffer size + * This field is internal to libavformat and access from outside is not allowed. + */ + int orig_buffer_size; + + /** + * Threshold to favor readahead over seek. + * This is current internal only, do not use from outside. + */ + int short_seek_threshold; + + /** + * ',' separated list of allowed protocols. + */ + const char *protocol_whitelist; + + /** + * ',' separated list of disallowed protocols. + */ + const char *protocol_blacklist; + + /** + * A callback that is used instead of write_packet. + */ + int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size, + enum AVIODataMarkerType type, int64_t time); + /** + * If set, don't call write_data_type separately for AVIO_DATA_MARKER_BOUNDARY_POINT, + * but ignore them and treat them as AVIO_DATA_MARKER_UNKNOWN (to avoid needlessly + * small chunks of data returned from the callback). + */ + int ignore_boundary_point; + + /** + * Internal, not meant to be used from outside of AVIOContext. + */ + enum AVIODataMarkerType current_type; + int64_t last_time; + + /** + * A callback that is used instead of short_seek_threshold. + * This is current internal only, do not use from outside. + */ + int (*short_seek_get)(void *opaque); + + int64_t written; + + /** + * Maximum reached position before a backward seek in the write buffer, + * used keeping track of already written data for a later flush. + */ + unsigned char *buf_ptr_max; + + /** + * Try to buffer at least this amount of data before flushing it + */ + int min_packet_size; +} AVIOContext; + +/** + * Return the name of the protocol that will handle the passed URL. + * + * NULL is returned if no protocol could be found for the given URL. + * + * @return Name of the protocol or NULL. + */ +const char *avio_find_protocol_name(const char *url); + +/** + * Return AVIO_FLAG_* access flags corresponding to the access permissions + * of the resource in url, or a negative value corresponding to an + * AVERROR code in case of failure. The returned access flags are + * masked by the value in flags. + * + * @note This function is intrinsically unsafe, in the sense that the + * checked resource may change its existence or permission status from + * one call to another. Thus you should not trust the returned value, + * unless you are sure that no other processes are accessing the + * checked resource. + */ +int avio_check(const char *url, int flags); + +/** + * Move or rename a resource. + * + * @note url_src and url_dst should share the same protocol and authority. + * + * @param url_src url to resource to be moved + * @param url_dst new url to resource if the operation succeeded + * @return >=0 on success or negative on error. + */ +int avpriv_io_move(const char *url_src, const char *url_dst); + +/** + * Delete a resource. + * + * @param url resource to be deleted. + * @return >=0 on success or negative on error. + */ +int avpriv_io_delete(const char *url); + +/** + * Open directory for reading. + * + * @param s directory read context. Pointer to a NULL pointer must be passed. + * @param url directory to be listed. + * @param options A dictionary filled with protocol-private options. On return + * this parameter will be destroyed and replaced with a dictionary + * containing options that were not found. May be NULL. + * @return >=0 on success or negative on error. + */ +int avio_open_dir(AVIODirContext **s, const char *url, AVDictionary **options); + +/** + * Get next directory entry. + * + * Returned entry must be freed with avio_free_directory_entry(). In particular + * it may outlive AVIODirContext. + * + * @param s directory read context. + * @param[out] next next entry or NULL when no more entries. + * @return >=0 on success or negative on error. End of list is not considered an + * error. + */ +int avio_read_dir(AVIODirContext *s, AVIODirEntry **next); + +/** + * Close directory. + * + * @note Entries created using avio_read_dir() are not deleted and must be + * freeded with avio_free_directory_entry(). + * + * @param s directory read context. + * @return >=0 on success or negative on error. + */ +int avio_close_dir(AVIODirContext **s); + +/** + * Free entry allocated by avio_read_dir(). + * + * @param entry entry to be freed. + */ +void avio_free_directory_entry(AVIODirEntry **entry); + +/** + * Allocate and initialize an AVIOContext for buffered I/O. It must be later + * freed with avio_context_free(). + * + * @param buffer Memory block for input/output operations via AVIOContext. + * The buffer must be allocated with av_malloc() and friends. + * It may be freed and replaced with a new buffer by libavformat. + * AVIOContext.buffer holds the buffer currently in use, + * which must be later freed with av_free(). + * @param buffer_size The buffer size is very important for performance. + * For protocols with fixed blocksize it should be set to this blocksize. + * For others a typical size is a cache page, e.g. 4kb. + * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise. + * @param opaque An opaque pointer to user-specific data. + * @param read_packet A function for refilling the buffer, may be NULL. + * @param write_packet A function for writing the buffer contents, may be NULL. + * The function may not change the input buffers content. + * @param seek A function for seeking to specified byte position, may be NULL. + * + * @return Allocated AVIOContext or NULL on failure. + */ +AVIOContext *avio_alloc_context( + unsigned char *buffer, + int buffer_size, + int write_flag, + void *opaque, + int (*read_packet)(void *opaque, uint8_t *buf, int buf_size), + int (*write_packet)(void *opaque, uint8_t *buf, int buf_size), + int64_t (*seek)(void *opaque, int64_t offset, int whence)); + +/** + * Free the supplied IO context and everything associated with it. + * + * @param s Double pointer to the IO context. This function will write NULL + * into s. + */ +void avio_context_free(AVIOContext **s); + +void avio_w8(AVIOContext *s, int b); +void avio_write(AVIOContext *s, const unsigned char *buf, int size); +void avio_wl64(AVIOContext *s, uint64_t val); +void avio_wb64(AVIOContext *s, uint64_t val); +void avio_wl32(AVIOContext *s, unsigned int val); +void avio_wb32(AVIOContext *s, unsigned int val); +void avio_wl24(AVIOContext *s, unsigned int val); +void avio_wb24(AVIOContext *s, unsigned int val); +void avio_wl16(AVIOContext *s, unsigned int val); +void avio_wb16(AVIOContext *s, unsigned int val); + +/** + * Write a NULL-terminated string. + * @return number of bytes written. + */ +int avio_put_str(AVIOContext *s, const char *str); + +/** + * Convert an UTF-8 string to UTF-16LE and write it. + * @param s the AVIOContext + * @param str NULL-terminated UTF-8 string + * + * @return number of bytes written. + */ +int avio_put_str16le(AVIOContext *s, const char *str); + +/** + * Convert an UTF-8 string to UTF-16BE and write it. + * @param s the AVIOContext + * @param str NULL-terminated UTF-8 string + * + * @return number of bytes written. + */ +int avio_put_str16be(AVIOContext *s, const char *str); + +/** + * Mark the written bytestream as a specific type. + * + * Zero-length ranges are omitted from the output. + * + * @param time the stream time the current bytestream pos corresponds to + * (in AV_TIME_BASE units), or AV_NOPTS_VALUE if unknown or not + * applicable + * @param type the kind of data written starting at the current pos + */ +void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type); + +/** + * ORing this as the "whence" parameter to a seek function causes it to + * return the filesize without seeking anywhere. Supporting this is optional. + * If it is not supported then the seek function will return <0. + */ +#define AVSEEK_SIZE 0x10000 + +/** + * Passing this flag as the "whence" parameter to a seek function causes it to + * seek by any means (like reopening and linear reading) or other normally unreasonable + * means that can be extremely slow. + * This may be ignored by the seek code. + */ +#define AVSEEK_FORCE 0x20000 + +/** + * fseek() equivalent for AVIOContext. + * @return new position or AVERROR. + */ +int64_t avio_seek(AVIOContext *s, int64_t offset, int whence); + +/** + * Skip given number of bytes forward + * @return new position or AVERROR. + */ +int64_t avio_skip(AVIOContext *s, int64_t offset); + +/** + * ftell() equivalent for AVIOContext. + * @return position or AVERROR. + */ +static av_always_inline int64_t avio_tell(AVIOContext *s) +{ + return avio_seek(s, 0, SEEK_CUR); +} + +/** + * Get the filesize. + * @return filesize or AVERROR + */ +int64_t avio_size(AVIOContext *s); + +/** + * feof() equivalent for AVIOContext. + * @return non zero if and only if end of file + */ +int avio_feof(AVIOContext *s); +#if FF_API_URL_FEOF +/** + * @deprecated use avio_feof() + */ +attribute_deprecated +int url_feof(AVIOContext *s); +#endif + +/** @warning Writes up to 4 KiB per call */ +int avio_printf(AVIOContext *s, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Force flushing of buffered data. + * + * For write streams, force the buffered data to be immediately written to the output, + * without to wait to fill the internal buffer. + * + * For read streams, discard all currently buffered data, and advance the + * reported file position to that of the underlying stream. This does not + * read new data, and does not perform any seeks. + */ +void avio_flush(AVIOContext *s); + +/** + * Read size bytes from AVIOContext into buf. + * @return number of bytes read or AVERROR + */ +int avio_read(AVIOContext *s, unsigned char *buf, int size); + +/** + * Read size bytes from AVIOContext into buf. Unlike avio_read(), this is allowed + * to read fewer bytes than requested. The missing bytes can be read in the next + * call. This always tries to read at least 1 byte. + * Useful to reduce latency in certain cases. + * @return number of bytes read or AVERROR + */ +int avio_read_partial(AVIOContext *s, unsigned char *buf, int size); + +/** + * @name Functions for reading from AVIOContext + * @{ + * + * @note return 0 if EOF, so you cannot use it if EOF handling is + * necessary + */ +int avio_r8 (AVIOContext *s); +unsigned int avio_rl16(AVIOContext *s); +unsigned int avio_rl24(AVIOContext *s); +unsigned int avio_rl32(AVIOContext *s); +uint64_t avio_rl64(AVIOContext *s); +unsigned int avio_rb16(AVIOContext *s); +unsigned int avio_rb24(AVIOContext *s); +unsigned int avio_rb32(AVIOContext *s); +uint64_t avio_rb64(AVIOContext *s); +/** + * @} + */ + +/** + * Read a string from pb into buf. The reading will terminate when either + * a NULL character was encountered, maxlen bytes have been read, or nothing + * more can be read from pb. The result is guaranteed to be NULL-terminated, it + * will be truncated if buf is too small. + * Note that the string is not interpreted or validated in any way, it + * might get truncated in the middle of a sequence for multi-byte encodings. + * + * @return number of bytes read (is always <= maxlen). + * If reading ends on EOF or error, the return value will be one more than + * bytes actually read. + */ +int avio_get_str(AVIOContext *pb, int maxlen, char *buf, int buflen); + +/** + * Read a UTF-16 string from pb and convert it to UTF-8. + * The reading will terminate when either a null or invalid character was + * encountered or maxlen bytes have been read. + * @return number of bytes read (is always <= maxlen) + */ +int avio_get_str16le(AVIOContext *pb, int maxlen, char *buf, int buflen); +int avio_get_str16be(AVIOContext *pb, int maxlen, char *buf, int buflen); + + +/** + * @name URL open modes + * The flags argument to avio_open must be one of the following + * constants, optionally ORed with other flags. + * @{ + */ +#define AVIO_FLAG_READ 1 /**< read-only */ +#define AVIO_FLAG_WRITE 2 /**< write-only */ +#define AVIO_FLAG_READ_WRITE (AVIO_FLAG_READ|AVIO_FLAG_WRITE) /**< read-write pseudo flag */ +/** + * @} + */ + +/** + * Use non-blocking mode. + * If this flag is set, operations on the context will return + * AVERROR(EAGAIN) if they can not be performed immediately. + * If this flag is not set, operations on the context will never return + * AVERROR(EAGAIN). + * Note that this flag does not affect the opening/connecting of the + * context. Connecting a protocol will always block if necessary (e.g. on + * network protocols) but never hang (e.g. on busy devices). + * Warning: non-blocking protocols is work-in-progress; this flag may be + * silently ignored. + */ +#define AVIO_FLAG_NONBLOCK 8 + +/** + * Use direct mode. + * avio_read and avio_write should if possible be satisfied directly + * instead of going through a buffer, and avio_seek will always + * call the underlying seek function directly. + */ +#define AVIO_FLAG_DIRECT 0x8000 + +/** + * Create and initialize a AVIOContext for accessing the + * resource indicated by url. + * @note When the resource indicated by url has been opened in + * read+write mode, the AVIOContext can be used only for writing. + * + * @param s Used to return the pointer to the created AVIOContext. + * In case of failure the pointed to value is set to NULL. + * @param url resource to access + * @param flags flags which control how the resource indicated by url + * is to be opened + * @return >= 0 in case of success, a negative value corresponding to an + * AVERROR code in case of failure + */ +int avio_open(AVIOContext **s, const char *url, int flags); + +/** + * Create and initialize a AVIOContext for accessing the + * resource indicated by url. + * @note When the resource indicated by url has been opened in + * read+write mode, the AVIOContext can be used only for writing. + * + * @param s Used to return the pointer to the created AVIOContext. + * In case of failure the pointed to value is set to NULL. + * @param url resource to access + * @param flags flags which control how the resource indicated by url + * is to be opened + * @param int_cb an interrupt callback to be used at the protocols level + * @param options A dictionary filled with protocol-private options. On return + * this parameter will be destroyed and replaced with a dict containing options + * that were not found. May be NULL. + * @return >= 0 in case of success, a negative value corresponding to an + * AVERROR code in case of failure + */ +int avio_open2(AVIOContext **s, const char *url, int flags, + const AVIOInterruptCB *int_cb, AVDictionary **options); + +/** + * Close the resource accessed by the AVIOContext s and free it. + * This function can only be used if s was opened by avio_open(). + * + * The internal buffer is automatically flushed before closing the + * resource. + * + * @return 0 on success, an AVERROR < 0 on error. + * @see avio_closep + */ +int avio_close(AVIOContext *s); + +/** + * Close the resource accessed by the AVIOContext *s, free it + * and set the pointer pointing to it to NULL. + * This function can only be used if s was opened by avio_open(). + * + * The internal buffer is automatically flushed before closing the + * resource. + * + * @return 0 on success, an AVERROR < 0 on error. + * @see avio_close + */ +int avio_closep(AVIOContext **s); + + +/** + * Open a write only memory stream. + * + * @param s new IO context + * @return zero if no error. + */ +int avio_open_dyn_buf(AVIOContext **s); + +/** + * Return the written size and a pointer to the buffer. + * The AVIOContext stream is left intact. + * The buffer must NOT be freed. + * No padding is added to the buffer. + * + * @param s IO context + * @param pbuffer pointer to a byte buffer + * @return the length of the byte buffer + */ +int avio_get_dyn_buf(AVIOContext *s, uint8_t **pbuffer); + +/** + * Return the written size and a pointer to the buffer. The buffer + * must be freed with av_free(). + * Padding of AV_INPUT_BUFFER_PADDING_SIZE is added to the buffer. + * + * @param s IO context + * @param pbuffer pointer to a byte buffer + * @return the length of the byte buffer + */ +int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer); + +/** + * Iterate through names of available protocols. + * + * @param opaque A private pointer representing current protocol. + * It must be a pointer to NULL on first iteration and will + * be updated by successive calls to avio_enum_protocols. + * @param output If set to 1, iterate over output protocols, + * otherwise over input protocols. + * + * @return A static string containing the name of current protocol or NULL + */ +const char *avio_enum_protocols(void **opaque, int output); + +/** + * Pause and resume playing - only meaningful if using a network streaming + * protocol (e.g. MMS). + * + * @param h IO context from which to call the read_pause function pointer + * @param pause 1 for pause, 0 for resume + */ +int avio_pause(AVIOContext *h, int pause); + +/** + * Seek to a given timestamp relative to some component stream. + * Only meaningful if using a network streaming protocol (e.g. MMS.). + * + * @param h IO context from which to call the seek function pointers + * @param stream_index The stream index that the timestamp is relative to. + * If stream_index is (-1) the timestamp should be in AV_TIME_BASE + * units from the beginning of the presentation. + * If a stream_index >= 0 is used and the protocol does not support + * seeking based on component streams, the call will fail. + * @param timestamp timestamp in AVStream.time_base units + * or if there is no stream specified then in AV_TIME_BASE units. + * @param flags Optional combination of AVSEEK_FLAG_BACKWARD, AVSEEK_FLAG_BYTE + * and AVSEEK_FLAG_ANY. The protocol may silently ignore + * AVSEEK_FLAG_BACKWARD and AVSEEK_FLAG_ANY, but AVSEEK_FLAG_BYTE will + * fail if used and not supported. + * @return >= 0 on success + * @see AVInputFormat::read_seek + */ +int64_t avio_seek_time(AVIOContext *h, int stream_index, + int64_t timestamp, int flags); + +/* Avoid a warning. The header can not be included because it breaks c++. */ +struct AVBPrint; + +/** + * Read contents of h into print buffer, up to max_size bytes, or up to EOF. + * + * @return 0 for success (max_size bytes read or EOF reached), negative error + * code otherwise + */ +int avio_read_to_bprint(AVIOContext *h, struct AVBPrint *pb, size_t max_size); + +/** + * Accept and allocate a client context on a server context. + * @param s the server context + * @param c the client context, must be unallocated + * @return >= 0 on success or a negative value corresponding + * to an AVERROR on failure + */ +int avio_accept(AVIOContext *s, AVIOContext **c); + +/** + * Perform one step of the protocol handshake to accept a new client. + * This function must be called on a client returned by avio_accept() before + * using it as a read/write context. + * It is separate from avio_accept() because it may block. + * A step of the handshake is defined by places where the application may + * decide to change the proceedings. + * For example, on a protocol with a request header and a reply header, each + * one can constitute a step because the application may use the parameters + * from the request to change parameters in the reply; or each individual + * chunk of the request can constitute a step. + * If the handshake is already finished, avio_handshake() does nothing and + * returns 0 immediately. + * + * @param c the client context to perform the handshake on + * @return 0 on a complete and successful handshake + * > 0 if the handshake progressed, but is not complete + * < 0 for an AVERROR code + */ +int avio_handshake(AVIOContext *c); +#endif /* AVFORMAT_AVIO_H */ diff --git a/thrid-party/ffmpeg/include/libavformat/version.h b/thrid-party/ffmpeg/include/libavformat/version.h new file mode 100644 index 0000000000..878917d65d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavformat/version.h @@ -0,0 +1,105 @@ +/* + * Version macros. + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_VERSION_H +#define AVFORMAT_VERSION_H + +/** + * @file + * @ingroup libavf + * Libavformat version macros + */ + +#include "libavutil/version.h" + +// Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium) +// Also please add any ticket numbers that you believe might be affected here +#define LIBAVFORMAT_VERSION_MAJOR 57 +#define LIBAVFORMAT_VERSION_MINOR 83 +#define LIBAVFORMAT_VERSION_MICRO 100 + +#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ + LIBAVFORMAT_VERSION_MINOR, \ + LIBAVFORMAT_VERSION_MICRO) +#define LIBAVFORMAT_VERSION AV_VERSION(LIBAVFORMAT_VERSION_MAJOR, \ + LIBAVFORMAT_VERSION_MINOR, \ + LIBAVFORMAT_VERSION_MICRO) +#define LIBAVFORMAT_BUILD LIBAVFORMAT_VERSION_INT + +#define LIBAVFORMAT_IDENT "Lavf" AV_STRINGIFY(LIBAVFORMAT_VERSION) + +/** + * FF_API_* defines may be placed below to indicate public API that will be + * dropped at a future version bump. The defines themselves are not part of + * the public API and may change, break or disappear at any time. + * + * @note, when bumping the major version it is recommended to manually + * disable each FF_API_* in its own commit instead of disabling them all + * at once through the bump. This improves the git bisect-ability of the change. + * + */ +#ifndef FF_API_LAVF_BITEXACT +#define FF_API_LAVF_BITEXACT (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_FRAC +#define FF_API_LAVF_FRAC (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_CODEC_TB +#define FF_API_LAVF_CODEC_TB (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_URL_FEOF +#define FF_API_URL_FEOF (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_FMT_RAWPICTURE +#define FF_API_LAVF_FMT_RAWPICTURE (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_COMPUTE_PKT_FIELDS2 +#define FF_API_COMPUTE_PKT_FIELDS2 (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_OLD_OPEN_CALLBACKS +#define FF_API_OLD_OPEN_CALLBACKS (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_AVCTX +#define FF_API_LAVF_AVCTX (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_NOCONST_GET_SIDE_DATA +#define FF_API_NOCONST_GET_SIDE_DATA (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_HTTP_USER_AGENT +#define FF_API_HTTP_USER_AGENT (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_HLS_WRAP +#define FF_API_HLS_WRAP (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_MERGE_SD +#define FF_API_LAVF_MERGE_SD (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_LAVF_KEEPSIDE_FLAG +#define FF_API_LAVF_KEEPSIDE_FLAG (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif +#ifndef FF_API_OLD_ROTATE_API +#define FF_API_OLD_ROTATE_API (LIBAVFORMAT_VERSION_MAJOR < 58) +#endif + + +#ifndef FF_API_R_FRAME_RATE +#define FF_API_R_FRAME_RATE 1 +#endif +#endif /* AVFORMAT_VERSION_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/adler32.h b/thrid-party/ffmpeg/include/libavutil/adler32.h new file mode 100644 index 0000000000..a1f035b734 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/adler32.h @@ -0,0 +1,60 @@ +/* + * copyright (c) 2006 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_adler32 + * Public header for Adler-32 hash function implementation. + */ + +#ifndef AVUTIL_ADLER32_H +#define AVUTIL_ADLER32_H + +#include +#include "attributes.h" + +/** + * @defgroup lavu_adler32 Adler-32 + * @ingroup lavu_hash + * Adler-32 hash function implementation. + * + * @{ + */ + +/** + * Calculate the Adler32 checksum of a buffer. + * + * Passing the return value to a subsequent av_adler32_update() call + * allows the checksum of multiple buffers to be calculated as though + * they were concatenated. + * + * @param adler initial checksum value + * @param buf pointer to input buffer + * @param len size of input buffer + * @return updated checksum + */ +unsigned long av_adler32_update(unsigned long adler, const uint8_t *buf, + unsigned int len) av_pure; + +/** + * @} + */ + +#endif /* AVUTIL_ADLER32_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/aes.h b/thrid-party/ffmpeg/include/libavutil/aes.h new file mode 100644 index 0000000000..09efbda107 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/aes.h @@ -0,0 +1,65 @@ +/* + * copyright (c) 2007 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AES_H +#define AVUTIL_AES_H + +#include + +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_aes AES + * @ingroup lavu_crypto + * @{ + */ + +extern const int av_aes_size; + +struct AVAES; + +/** + * Allocate an AVAES context. + */ +struct AVAES *av_aes_alloc(void); + +/** + * Initialize an AVAES context. + * @param key_bits 128, 192 or 256 + * @param decrypt 0 for encryption, 1 for decryption + */ +int av_aes_init(struct AVAES *a, const uint8_t *key, int key_bits, int decrypt); + +/** + * Encrypt or decrypt a buffer using a previously initialized context. + * @param count number of 16 byte blocks + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param iv initialization vector for CBC mode, if NULL then ECB will be used + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_aes_crypt(struct AVAES *a, uint8_t *dst, const uint8_t *src, int count, uint8_t *iv, int decrypt); + +/** + * @} + */ + +#endif /* AVUTIL_AES_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/aes_ctr.h b/thrid-party/ffmpeg/include/libavutil/aes_ctr.h new file mode 100644 index 0000000000..f596fa6a46 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/aes_ctr.h @@ -0,0 +1,83 @@ +/* + * AES-CTR cipher + * Copyright (c) 2015 Eran Kornblau + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AES_CTR_H +#define AVUTIL_AES_CTR_H + +#include + +#include "attributes.h" +#include "version.h" + +#define AES_CTR_KEY_SIZE (16) +#define AES_CTR_IV_SIZE (8) + +struct AVAESCTR; + +/** + * Allocate an AVAESCTR context. + */ +struct AVAESCTR *av_aes_ctr_alloc(void); + +/** + * Initialize an AVAESCTR context. + * @param key encryption key, must have a length of AES_CTR_KEY_SIZE + */ +int av_aes_ctr_init(struct AVAESCTR *a, const uint8_t *key); + +/** + * Release an AVAESCTR context. + */ +void av_aes_ctr_free(struct AVAESCTR *a); + +/** + * Process a buffer using a previously initialized context. + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param size the size of src and dst + */ +void av_aes_ctr_crypt(struct AVAESCTR *a, uint8_t *dst, const uint8_t *src, int size); + +/** + * Get the current iv + */ +const uint8_t* av_aes_ctr_get_iv(struct AVAESCTR *a); + +/** + * Generate a random iv + */ +void av_aes_ctr_set_random_iv(struct AVAESCTR *a); + +/** + * Forcefully change the iv + */ +void av_aes_ctr_set_iv(struct AVAESCTR *a, const uint8_t* iv); + +/** + * Increment the top 64 bit of the iv (performed after each frame) + */ +void av_aes_ctr_increment_iv(struct AVAESCTR *a); + +/** + * @} + */ + +#endif /* AVUTIL_AES_CTR_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/attributes.h b/thrid-party/ffmpeg/include/libavutil/attributes.h new file mode 100644 index 0000000000..54d1901116 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/attributes.h @@ -0,0 +1,167 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Macro definitions for various function/variable attributes + */ + +#ifndef AVUTIL_ATTRIBUTES_H +#define AVUTIL_ATTRIBUTES_H + +#ifdef __GNUC__ +# define AV_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y)) +# define AV_GCC_VERSION_AT_MOST(x,y) (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y)) +#else +# define AV_GCC_VERSION_AT_LEAST(x,y) 0 +# define AV_GCC_VERSION_AT_MOST(x,y) 0 +#endif + +#ifndef av_always_inline +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define av_always_inline __attribute__((always_inline)) inline +#elif defined(_MSC_VER) +# define av_always_inline __forceinline +#else +# define av_always_inline inline +#endif +#endif + +#ifndef av_extern_inline +#if defined(__ICL) && __ICL >= 1210 || defined(__GNUC_STDC_INLINE__) +# define av_extern_inline extern inline +#else +# define av_extern_inline inline +#endif +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,4) +# define av_warn_unused_result __attribute__((warn_unused_result)) +#else +# define av_warn_unused_result +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define av_noinline __attribute__((noinline)) +#elif defined(_MSC_VER) +# define av_noinline __declspec(noinline) +#else +# define av_noinline +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define av_pure __attribute__((pure)) +#else +# define av_pure +#endif + +#if AV_GCC_VERSION_AT_LEAST(2,6) +# define av_const __attribute__((const)) +#else +# define av_const +#endif + +#if AV_GCC_VERSION_AT_LEAST(4,3) +# define av_cold __attribute__((cold)) +#else +# define av_cold +#endif + +#if AV_GCC_VERSION_AT_LEAST(4,1) && !defined(__llvm__) +# define av_flatten __attribute__((flatten)) +#else +# define av_flatten +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,1) +# define attribute_deprecated __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define attribute_deprecated __declspec(deprecated) +#else +# define attribute_deprecated +#endif + +/** + * Disable warnings about deprecated features + * This is useful for sections of code kept for backward compatibility and + * scheduled for removal. + */ +#ifndef AV_NOWARN_DEPRECATED +#if AV_GCC_VERSION_AT_LEAST(4,6) +# define AV_NOWARN_DEPRECATED(code) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ + code \ + _Pragma("GCC diagnostic pop") +#elif defined(_MSC_VER) +# define AV_NOWARN_DEPRECATED(code) \ + __pragma(warning(push)) \ + __pragma(warning(disable : 4996)) \ + code; \ + __pragma(warning(pop)) +#else +# define AV_NOWARN_DEPRECATED(code) code +#endif +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define av_unused __attribute__((unused)) +#else +# define av_unused +#endif + +/** + * Mark a variable as used and prevent the compiler from optimizing it + * away. This is useful for variables accessed only from inline + * assembler without the compiler being aware. + */ +#if AV_GCC_VERSION_AT_LEAST(3,1) || defined(__clang__) +# define av_used __attribute__((used)) +#else +# define av_used +#endif + +#if AV_GCC_VERSION_AT_LEAST(3,3) +# define av_alias __attribute__((may_alias)) +#else +# define av_alias +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__) +# define av_uninit(x) x=x +#else +# define av_uninit(x) x +#endif + +#ifdef __GNUC__ +# define av_builtin_constant_p __builtin_constant_p +# define av_printf_format(fmtpos, attrpos) __attribute__((__format__(__printf__, fmtpos, attrpos))) +#else +# define av_builtin_constant_p(x) 0 +# define av_printf_format(fmtpos, attrpos) +#endif + +#if AV_GCC_VERSION_AT_LEAST(2,5) +# define av_noreturn __attribute__((noreturn)) +#else +# define av_noreturn +#endif + +#endif /* AVUTIL_ATTRIBUTES_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/audio_fifo.h b/thrid-party/ffmpeg/include/libavutil/audio_fifo.h new file mode 100644 index 0000000000..d8a9194a8d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/audio_fifo.h @@ -0,0 +1,187 @@ +/* + * Audio FIFO + * Copyright (c) 2012 Justin Ruggles + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Audio FIFO Buffer + */ + +#ifndef AVUTIL_AUDIO_FIFO_H +#define AVUTIL_AUDIO_FIFO_H + +#include "avutil.h" +#include "fifo.h" +#include "samplefmt.h" + +/** + * @addtogroup lavu_audio + * @{ + * + * @defgroup lavu_audiofifo Audio FIFO Buffer + * @{ + */ + +/** + * Context for an Audio FIFO Buffer. + * + * - Operates at the sample level rather than the byte level. + * - Supports multiple channels with either planar or packed sample format. + * - Automatic reallocation when writing to a full buffer. + */ +typedef struct AVAudioFifo AVAudioFifo; + +/** + * Free an AVAudioFifo. + * + * @param af AVAudioFifo to free + */ +void av_audio_fifo_free(AVAudioFifo *af); + +/** + * Allocate an AVAudioFifo. + * + * @param sample_fmt sample format + * @param channels number of channels + * @param nb_samples initial allocation size, in samples + * @return newly allocated AVAudioFifo, or NULL on error + */ +AVAudioFifo *av_audio_fifo_alloc(enum AVSampleFormat sample_fmt, int channels, + int nb_samples); + +/** + * Reallocate an AVAudioFifo. + * + * @param af AVAudioFifo to reallocate + * @param nb_samples new allocation size, in samples + * @return 0 if OK, or negative AVERROR code on failure + */ +av_warn_unused_result +int av_audio_fifo_realloc(AVAudioFifo *af, int nb_samples); + +/** + * Write data to an AVAudioFifo. + * + * The AVAudioFifo will be reallocated automatically if the available space + * is less than nb_samples. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param af AVAudioFifo to write to + * @param data audio data plane pointers + * @param nb_samples number of samples to write + * @return number of samples actually written, or negative AVERROR + * code on failure. If successful, the number of samples + * actually written will always be nb_samples. + */ +int av_audio_fifo_write(AVAudioFifo *af, void **data, int nb_samples); + +/** + * Peek data from an AVAudioFifo. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param af AVAudioFifo to read from + * @param data audio data plane pointers + * @param nb_samples number of samples to peek + * @return number of samples actually peek, or negative AVERROR code + * on failure. The number of samples actually peek will not + * be greater than nb_samples, and will only be less than + * nb_samples if av_audio_fifo_size is less than nb_samples. + */ +int av_audio_fifo_peek(AVAudioFifo *af, void **data, int nb_samples); + +/** + * Peek data from an AVAudioFifo. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param af AVAudioFifo to read from + * @param data audio data plane pointers + * @param nb_samples number of samples to peek + * @param offset offset from current read position + * @return number of samples actually peek, or negative AVERROR code + * on failure. The number of samples actually peek will not + * be greater than nb_samples, and will only be less than + * nb_samples if av_audio_fifo_size is less than nb_samples. + */ +int av_audio_fifo_peek_at(AVAudioFifo *af, void **data, int nb_samples, int offset); + +/** + * Read data from an AVAudioFifo. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param af AVAudioFifo to read from + * @param data audio data plane pointers + * @param nb_samples number of samples to read + * @return number of samples actually read, or negative AVERROR code + * on failure. The number of samples actually read will not + * be greater than nb_samples, and will only be less than + * nb_samples if av_audio_fifo_size is less than nb_samples. + */ +int av_audio_fifo_read(AVAudioFifo *af, void **data, int nb_samples); + +/** + * Drain data from an AVAudioFifo. + * + * Removes the data without reading it. + * + * @param af AVAudioFifo to drain + * @param nb_samples number of samples to drain + * @return 0 if OK, or negative AVERROR code on failure + */ +int av_audio_fifo_drain(AVAudioFifo *af, int nb_samples); + +/** + * Reset the AVAudioFifo buffer. + * + * This empties all data in the buffer. + * + * @param af AVAudioFifo to reset + */ +void av_audio_fifo_reset(AVAudioFifo *af); + +/** + * Get the current number of samples in the AVAudioFifo available for reading. + * + * @param af the AVAudioFifo to query + * @return number of samples available for reading + */ +int av_audio_fifo_size(AVAudioFifo *af); + +/** + * Get the current number of samples in the AVAudioFifo available for writing. + * + * @param af the AVAudioFifo to query + * @return number of samples available for writing + */ +int av_audio_fifo_space(AVAudioFifo *af); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_AUDIO_FIFO_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/avassert.h b/thrid-party/ffmpeg/include/libavutil/avassert.h new file mode 100644 index 0000000000..46f3fea580 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/avassert.h @@ -0,0 +1,75 @@ +/* + * copyright (c) 2010 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * simple assert() macros that are a bit more flexible than ISO C assert(). + * @author Michael Niedermayer + */ + +#ifndef AVUTIL_AVASSERT_H +#define AVUTIL_AVASSERT_H + +#include +#include "avutil.h" +#include "log.h" + +/** + * assert() equivalent, that is always enabled. + */ +#define av_assert0(cond) do { \ + if (!(cond)) { \ + av_log(NULL, AV_LOG_PANIC, "Assertion %s failed at %s:%d\n", \ + AV_STRINGIFY(cond), __FILE__, __LINE__); \ + abort(); \ + } \ +} while (0) + + +/** + * assert() equivalent, that does not lie in speed critical code. + * These asserts() thus can be enabled without fearing speed loss. + */ +#if defined(ASSERT_LEVEL) && ASSERT_LEVEL > 0 +#define av_assert1(cond) av_assert0(cond) +#else +#define av_assert1(cond) ((void)0) +#endif + + +/** + * assert() equivalent, that does lie in speed critical code. + */ +#if defined(ASSERT_LEVEL) && ASSERT_LEVEL > 1 +#define av_assert2(cond) av_assert0(cond) +#define av_assert2_fpu() av_assert0_fpu() +#else +#define av_assert2(cond) ((void)0) +#define av_assert2_fpu() ((void)0) +#endif + +/** + * Assert that floating point opperations can be executed. + * + * This will av_assert0() that the cpu is not in MMX state on X86 + */ +void av_assert0_fpu(void); + +#endif /* AVUTIL_AVASSERT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/avconfig.h b/thrid-party/ffmpeg/include/libavutil/avconfig.h new file mode 100644 index 0000000000..f10aa6186b --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/avconfig.h @@ -0,0 +1,6 @@ +/* Generated by ffconf */ +#ifndef AVUTIL_AVCONFIG_H +#define AVUTIL_AVCONFIG_H +#define AV_HAVE_BIGENDIAN 0 +#define AV_HAVE_FAST_UNALIGNED 1 +#endif /* AVUTIL_AVCONFIG_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/avstring.h b/thrid-party/ffmpeg/include/libavutil/avstring.h new file mode 100644 index 0000000000..04d2695640 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/avstring.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2007 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AVSTRING_H +#define AVUTIL_AVSTRING_H + +#include +#include +#include "attributes.h" + +/** + * @addtogroup lavu_string + * @{ + */ + +/** + * Return non-zero if pfx is a prefix of str. If it is, *ptr is set to + * the address of the first character in str after the prefix. + * + * @param str input string + * @param pfx prefix to test + * @param ptr updated if the prefix is matched inside str + * @return non-zero if the prefix matches, zero otherwise + */ +int av_strstart(const char *str, const char *pfx, const char **ptr); + +/** + * Return non-zero if pfx is a prefix of str independent of case. If + * it is, *ptr is set to the address of the first character in str + * after the prefix. + * + * @param str input string + * @param pfx prefix to test + * @param ptr updated if the prefix is matched inside str + * @return non-zero if the prefix matches, zero otherwise + */ +int av_stristart(const char *str, const char *pfx, const char **ptr); + +/** + * Locate the first case-independent occurrence in the string haystack + * of the string needle. A zero-length string needle is considered to + * match at the start of haystack. + * + * This function is a case-insensitive version of the standard strstr(). + * + * @param haystack string to search in + * @param needle string to search for + * @return pointer to the located match within haystack + * or a null pointer if no match + */ +char *av_stristr(const char *haystack, const char *needle); + +/** + * Locate the first occurrence of the string needle in the string haystack + * where not more than hay_length characters are searched. A zero-length + * string needle is considered to match at the start of haystack. + * + * This function is a length-limited version of the standard strstr(). + * + * @param haystack string to search in + * @param needle string to search for + * @param hay_length length of string to search in + * @return pointer to the located match within haystack + * or a null pointer if no match + */ +char *av_strnstr(const char *haystack, const char *needle, size_t hay_length); + +/** + * Copy the string src to dst, but no more than size - 1 bytes, and + * null-terminate dst. + * + * This function is the same as BSD strlcpy(). + * + * @param dst destination buffer + * @param src source string + * @param size size of destination buffer + * @return the length of src + * + * @warning since the return value is the length of src, src absolutely + * _must_ be a properly 0-terminated string, otherwise this will read beyond + * the end of the buffer and possibly crash. + */ +size_t av_strlcpy(char *dst, const char *src, size_t size); + +/** + * Append the string src to the string dst, but to a total length of + * no more than size - 1 bytes, and null-terminate dst. + * + * This function is similar to BSD strlcat(), but differs when + * size <= strlen(dst). + * + * @param dst destination buffer + * @param src source string + * @param size size of destination buffer + * @return the total length of src and dst + * + * @warning since the return value use the length of src and dst, these + * absolutely _must_ be a properly 0-terminated strings, otherwise this + * will read beyond the end of the buffer and possibly crash. + */ +size_t av_strlcat(char *dst, const char *src, size_t size); + +/** + * Append output to a string, according to a format. Never write out of + * the destination buffer, and always put a terminating 0 within + * the buffer. + * @param dst destination buffer (string to which the output is + * appended) + * @param size total size of the destination buffer + * @param fmt printf-compatible format string, specifying how the + * following parameters are used + * @return the length of the string that would have been generated + * if enough space had been available + */ +size_t av_strlcatf(char *dst, size_t size, const char *fmt, ...) av_printf_format(3, 4); + +/** + * Get the count of continuous non zero chars starting from the beginning. + * + * @param len maximum number of characters to check in the string, that + * is the maximum value which is returned by the function + */ +static inline size_t av_strnlen(const char *s, size_t len) +{ + size_t i; + for (i = 0; i < len && s[i]; i++) + ; + return i; +} + +/** + * Print arguments following specified format into a large enough auto + * allocated buffer. It is similar to GNU asprintf(). + * @param fmt printf-compatible format string, specifying how the + * following parameters are used. + * @return the allocated string + * @note You have to free the string yourself with av_free(). + */ +char *av_asprintf(const char *fmt, ...) av_printf_format(1, 2); + +/** + * Convert a number to an av_malloced string. + */ +char *av_d2str(double d); + +/** + * Unescape the given string until a non escaped terminating char, + * and return the token corresponding to the unescaped string. + * + * The normal \ and ' escaping is supported. Leading and trailing + * whitespaces are removed, unless they are escaped with '\' or are + * enclosed between ''. + * + * @param buf the buffer to parse, buf will be updated to point to the + * terminating char + * @param term a 0-terminated list of terminating chars + * @return the malloced unescaped string, which must be av_freed by + * the user, NULL in case of allocation failure + */ +char *av_get_token(const char **buf, const char *term); + +/** + * Split the string into several tokens which can be accessed by + * successive calls to av_strtok(). + * + * A token is defined as a sequence of characters not belonging to the + * set specified in delim. + * + * On the first call to av_strtok(), s should point to the string to + * parse, and the value of saveptr is ignored. In subsequent calls, s + * should be NULL, and saveptr should be unchanged since the previous + * call. + * + * This function is similar to strtok_r() defined in POSIX.1. + * + * @param s the string to parse, may be NULL + * @param delim 0-terminated list of token delimiters, must be non-NULL + * @param saveptr user-provided pointer which points to stored + * information necessary for av_strtok() to continue scanning the same + * string. saveptr is updated to point to the next character after the + * first delimiter found, or to NULL if the string was terminated + * @return the found token, or NULL when no token is found + */ +char *av_strtok(char *s, const char *delim, char **saveptr); + +/** + * Locale-independent conversion of ASCII isdigit. + */ +static inline av_const int av_isdigit(int c) +{ + return c >= '0' && c <= '9'; +} + +/** + * Locale-independent conversion of ASCII isgraph. + */ +static inline av_const int av_isgraph(int c) +{ + return c > 32 && c < 127; +} + +/** + * Locale-independent conversion of ASCII isspace. + */ +static inline av_const int av_isspace(int c) +{ + return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || + c == '\v'; +} + +/** + * Locale-independent conversion of ASCII characters to uppercase. + */ +static inline av_const int av_toupper(int c) +{ + if (c >= 'a' && c <= 'z') + c ^= 0x20; + return c; +} + +/** + * Locale-independent conversion of ASCII characters to lowercase. + */ +static inline av_const int av_tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + c ^= 0x20; + return c; +} + +/** + * Locale-independent conversion of ASCII isxdigit. + */ +static inline av_const int av_isxdigit(int c) +{ + c = av_tolower(c); + return av_isdigit(c) || (c >= 'a' && c <= 'f'); +} + +/** + * Locale-independent case-insensitive compare. + * @note This means only ASCII-range characters are case-insensitive + */ +int av_strcasecmp(const char *a, const char *b); + +/** + * Locale-independent case-insensitive compare. + * @note This means only ASCII-range characters are case-insensitive + */ +int av_strncasecmp(const char *a, const char *b, size_t n); + +/** + * Locale-independent strings replace. + * @note This means only ASCII-range characters are replace + */ +char *av_strireplace(const char *str, const char *from, const char *to); + +/** + * Thread safe basename. + * @param path the path, on DOS both \ and / are considered separators. + * @return pointer to the basename substring. + */ +const char *av_basename(const char *path); + +/** + * Thread safe dirname. + * @param path the path, on DOS both \ and / are considered separators. + * @return the path with the separator replaced by the string terminator or ".". + * @note the function may change the input string. + */ +const char *av_dirname(char *path); + +/** + * Match instances of a name in a comma-separated list of names. + * List entries are checked from the start to the end of the names list, + * the first match ends further processing. If an entry prefixed with '-' + * matches, then 0 is returned. The "ALL" list entry is considered to + * match all names. + * + * @param name Name to look for. + * @param names List of names. + * @return 1 on match, 0 otherwise. + */ +int av_match_name(const char *name, const char *names); + +/** + * Append path component to the existing path. + * Path separator '/' is placed between when needed. + * Resulting string have to be freed with av_free(). + * @param path base path + * @param component component to be appended + * @return new path or NULL on error. + */ +char *av_append_path_component(const char *path, const char *component); + +enum AVEscapeMode { + AV_ESCAPE_MODE_AUTO, ///< Use auto-selected escaping mode. + AV_ESCAPE_MODE_BACKSLASH, ///< Use backslash escaping. + AV_ESCAPE_MODE_QUOTE, ///< Use single-quote escaping. +}; + +/** + * Consider spaces special and escape them even in the middle of the + * string. + * + * This is equivalent to adding the whitespace characters to the special + * characters lists, except it is guaranteed to use the exact same list + * of whitespace characters as the rest of libavutil. + */ +#define AV_ESCAPE_FLAG_WHITESPACE (1 << 0) + +/** + * Escape only specified special characters. + * Without this flag, escape also any characters that may be considered + * special by av_get_token(), such as the single quote. + */ +#define AV_ESCAPE_FLAG_STRICT (1 << 1) + +/** + * Escape string in src, and put the escaped string in an allocated + * string in *dst, which must be freed with av_free(). + * + * @param dst pointer where an allocated string is put + * @param src string to escape, must be non-NULL + * @param special_chars string containing the special characters which + * need to be escaped, can be NULL + * @param mode escape mode to employ, see AV_ESCAPE_MODE_* macros. + * Any unknown value for mode will be considered equivalent to + * AV_ESCAPE_MODE_BACKSLASH, but this behaviour can change without + * notice. + * @param flags flags which control how to escape, see AV_ESCAPE_FLAG_ macros + * @return the length of the allocated string, or a negative error code in case of error + * @see av_bprint_escape() + */ +av_warn_unused_result +int av_escape(char **dst, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags); + +#define AV_UTF8_FLAG_ACCEPT_INVALID_BIG_CODES 1 ///< accept codepoints over 0x10FFFF +#define AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS 2 ///< accept non-characters - 0xFFFE and 0xFFFF +#define AV_UTF8_FLAG_ACCEPT_SURROGATES 4 ///< accept UTF-16 surrogates codes +#define AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES 8 ///< exclude control codes not accepted by XML + +#define AV_UTF8_FLAG_ACCEPT_ALL \ + AV_UTF8_FLAG_ACCEPT_INVALID_BIG_CODES|AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS|AV_UTF8_FLAG_ACCEPT_SURROGATES + +/** + * Read and decode a single UTF-8 code point (character) from the + * buffer in *buf, and update *buf to point to the next byte to + * decode. + * + * In case of an invalid byte sequence, the pointer will be updated to + * the next byte after the invalid sequence and the function will + * return an error code. + * + * Depending on the specified flags, the function will also fail in + * case the decoded code point does not belong to a valid range. + * + * @note For speed-relevant code a carefully implemented use of + * GET_UTF8() may be preferred. + * + * @param codep pointer used to return the parsed code in case of success. + * The value in *codep is set even in case the range check fails. + * @param bufp pointer to the address the first byte of the sequence + * to decode, updated by the function to point to the + * byte next after the decoded sequence + * @param buf_end pointer to the end of the buffer, points to the next + * byte past the last in the buffer. This is used to + * avoid buffer overreads (in case of an unfinished + * UTF-8 sequence towards the end of the buffer). + * @param flags a collection of AV_UTF8_FLAG_* flags + * @return >= 0 in case a sequence was successfully read, a negative + * value in case of invalid sequence + */ +av_warn_unused_result +int av_utf8_decode(int32_t *codep, const uint8_t **bufp, const uint8_t *buf_end, + unsigned int flags); + +/** + * Check if a name is in a list. + * @returns 0 if not found, or the 1 based index where it has been found in the + * list. + */ +int av_match_list(const char *name, const char *list, char separator); + +/** + * @} + */ + +#endif /* AVUTIL_AVSTRING_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/avutil.h b/thrid-party/ffmpeg/include/libavutil/avutil.h new file mode 100644 index 0000000000..4d633156d1 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/avutil.h @@ -0,0 +1,365 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_AVUTIL_H +#define AVUTIL_AVUTIL_H + +/** + * @file + * @ingroup lavu + * Convenience header that includes @ref lavu "libavutil"'s core. + */ + +/** + * @mainpage + * + * @section ffmpeg_intro Introduction + * + * This document describes the usage of the different libraries + * provided by FFmpeg. + * + * @li @ref libavc "libavcodec" encoding/decoding library + * @li @ref lavfi "libavfilter" graph-based frame editing library + * @li @ref libavf "libavformat" I/O and muxing/demuxing library + * @li @ref lavd "libavdevice" special devices muxing/demuxing library + * @li @ref lavu "libavutil" common utility library + * @li @ref lswr "libswresample" audio resampling, format conversion and mixing + * @li @ref lpp "libpostproc" post processing library + * @li @ref libsws "libswscale" color conversion and scaling library + * + * @section ffmpeg_versioning Versioning and compatibility + * + * Each of the FFmpeg libraries contains a version.h header, which defines a + * major, minor and micro version number with the + * LIBRARYNAME_VERSION_{MAJOR,MINOR,MICRO} macros. The major version + * number is incremented with backward incompatible changes - e.g. removing + * parts of the public API, reordering public struct members, etc. The minor + * version number is incremented for backward compatible API changes or major + * new features - e.g. adding a new public function or a new decoder. The micro + * version number is incremented for smaller changes that a calling program + * might still want to check for - e.g. changing behavior in a previously + * unspecified situation. + * + * FFmpeg guarantees backward API and ABI compatibility for each library as long + * as its major version number is unchanged. This means that no public symbols + * will be removed or renamed. Types and names of the public struct members and + * values of public macros and enums will remain the same (unless they were + * explicitly declared as not part of the public API). Documented behavior will + * not change. + * + * In other words, any correct program that works with a given FFmpeg snapshot + * should work just as well without any changes with any later snapshot with the + * same major versions. This applies to both rebuilding the program against new + * FFmpeg versions or to replacing the dynamic FFmpeg libraries that a program + * links against. + * + * However, new public symbols may be added and new members may be appended to + * public structs whose size is not part of public ABI (most public structs in + * FFmpeg). New macros and enum values may be added. Behavior in undocumented + * situations may change slightly (and be documented). All those are accompanied + * by an entry in doc/APIchanges and incrementing either the minor or micro + * version number. + */ + +/** + * @defgroup lavu libavutil + * Common code shared across all FFmpeg libraries. + * + * @note + * libavutil is designed to be modular. In most cases, in order to use the + * functions provided by one component of libavutil you must explicitly include + * the specific header containing that feature. If you are only using + * media-related components, you could simply include libavutil/avutil.h, which + * brings in most of the "core" components. + * + * @{ + * + * @defgroup lavu_crypto Crypto and Hashing + * + * @{ + * @} + * + * @defgroup lavu_math Mathematics + * @{ + * + * @} + * + * @defgroup lavu_string String Manipulation + * + * @{ + * + * @} + * + * @defgroup lavu_mem Memory Management + * + * @{ + * + * @} + * + * @defgroup lavu_data Data Structures + * @{ + * + * @} + * + * @defgroup lavu_video Video related + * + * @{ + * + * @} + * + * @defgroup lavu_audio Audio related + * + * @{ + * + * @} + * + * @defgroup lavu_error Error Codes + * + * @{ + * + * @} + * + * @defgroup lavu_log Logging Facility + * + * @{ + * + * @} + * + * @defgroup lavu_misc Other + * + * @{ + * + * @defgroup preproc_misc Preprocessor String Macros + * + * @{ + * + * @} + * + * @defgroup version_utils Library Version Macros + * + * @{ + * + * @} + */ + + +/** + * @addtogroup lavu_ver + * @{ + */ + +/** + * Return the LIBAVUTIL_VERSION_INT constant. + */ +unsigned avutil_version(void); + +/** + * Return an informative version string. This usually is the actual release + * version number or a git commit description. This string has no fixed format + * and can change any time. It should never be parsed by code. + */ +const char *av_version_info(void); + +/** + * Return the libavutil build-time configuration. + */ +const char *avutil_configuration(void); + +/** + * Return the libavutil license. + */ +const char *avutil_license(void); + +/** + * @} + */ + +/** + * @addtogroup lavu_media Media Type + * @brief Media Type + */ + +enum AVMediaType { + AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA + AVMEDIA_TYPE_VIDEO, + AVMEDIA_TYPE_AUDIO, + AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous + AVMEDIA_TYPE_SUBTITLE, + AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse + AVMEDIA_TYPE_NB +}; + +/** + * Return a string describing the media_type enum, NULL if media_type + * is unknown. + */ +const char *av_get_media_type_string(enum AVMediaType media_type); + +/** + * @defgroup lavu_const Constants + * @{ + * + * @defgroup lavu_enc Encoding specific + * + * @note those definition should move to avcodec + * @{ + */ + +#define FF_LAMBDA_SHIFT 7 +#define FF_LAMBDA_SCALE (1< + +/** + * @defgroup lavu_base64 Base64 + * @ingroup lavu_crypto + * @{ + */ + +/** + * Decode a base64-encoded string. + * + * @param out buffer for decoded data + * @param in null-terminated input string + * @param out_size size in bytes of the out buffer, must be at + * least 3/4 of the length of in, that is AV_BASE64_DECODE_SIZE(strlen(in)) + * @return number of bytes written, or a negative value in case of + * invalid input + */ +int av_base64_decode(uint8_t *out, const char *in, int out_size); + +/** + * Calculate the output size in bytes needed to decode a base64 string + * with length x to a data buffer. + */ +#define AV_BASE64_DECODE_SIZE(x) ((x) * 3LL / 4) + +/** + * Encode data to base64 and null-terminate. + * + * @param out buffer for encoded data + * @param out_size size in bytes of the out buffer (including the + * null terminator), must be at least AV_BASE64_SIZE(in_size) + * @param in input buffer containing the data to encode + * @param in_size size in bytes of the in buffer + * @return out or NULL in case of error + */ +char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); + +/** + * Calculate the output size needed to base64-encode x bytes to a + * null-terminated string. + */ +#define AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) + + /** + * @} + */ + +#endif /* AVUTIL_BASE64_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/blowfish.h b/thrid-party/ffmpeg/include/libavutil/blowfish.h new file mode 100644 index 0000000000..9e289a40da --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/blowfish.h @@ -0,0 +1,82 @@ +/* + * Blowfish algorithm + * Copyright (c) 2012 Samuel Pitoiset + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_BLOWFISH_H +#define AVUTIL_BLOWFISH_H + +#include + +/** + * @defgroup lavu_blowfish Blowfish + * @ingroup lavu_crypto + * @{ + */ + +#define AV_BF_ROUNDS 16 + +typedef struct AVBlowfish { + uint32_t p[AV_BF_ROUNDS + 2]; + uint32_t s[4][256]; +} AVBlowfish; + +/** + * Allocate an AVBlowfish context. + */ +AVBlowfish *av_blowfish_alloc(void); + +/** + * Initialize an AVBlowfish context. + * + * @param ctx an AVBlowfish context + * @param key a key + * @param key_len length of the key + */ +void av_blowfish_init(struct AVBlowfish *ctx, const uint8_t *key, int key_len); + +/** + * Encrypt or decrypt a buffer using a previously initialized context. + * + * @param ctx an AVBlowfish context + * @param xl left four bytes halves of input to be encrypted + * @param xr right four bytes halves of input to be encrypted + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_blowfish_crypt_ecb(struct AVBlowfish *ctx, uint32_t *xl, uint32_t *xr, + int decrypt); + +/** + * Encrypt or decrypt a buffer using a previously initialized context. + * + * @param ctx an AVBlowfish context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param iv initialization vector for CBC mode, if NULL ECB will be used + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_blowfish_crypt(struct AVBlowfish *ctx, uint8_t *dst, const uint8_t *src, + int count, uint8_t *iv, int decrypt); + +/** + * @} + */ + +#endif /* AVUTIL_BLOWFISH_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/bprint.h b/thrid-party/ffmpeg/include/libavutil/bprint.h new file mode 100644 index 0000000000..c09b1ac1e1 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/bprint.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2012 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_BPRINT_H +#define AVUTIL_BPRINT_H + +#include + +#include "attributes.h" +#include "avstring.h" + +/** + * Define a structure with extra padding to a fixed size + * This helps ensuring binary compatibility with future versions. + */ + +#define FF_PAD_STRUCTURE(name, size, ...) \ +struct ff_pad_helper_##name { __VA_ARGS__ }; \ +typedef struct name { \ + __VA_ARGS__ \ + char reserved_padding[size - sizeof(struct ff_pad_helper_##name)]; \ +} name; + +/** + * Buffer to print data progressively + * + * The string buffer grows as necessary and is always 0-terminated. + * The content of the string is never accessed, and thus is + * encoding-agnostic and can even hold binary data. + * + * Small buffers are kept in the structure itself, and thus require no + * memory allocation at all (unless the contents of the buffer is needed + * after the structure goes out of scope). This is almost as lightweight as + * declaring a local "char buf[512]". + * + * The length of the string can go beyond the allocated size: the buffer is + * then truncated, but the functions still keep account of the actual total + * length. + * + * In other words, buf->len can be greater than buf->size and records the + * total length of what would have been to the buffer if there had been + * enough memory. + * + * Append operations do not need to be tested for failure: if a memory + * allocation fails, data stop being appended to the buffer, but the length + * is still updated. This situation can be tested with + * av_bprint_is_complete(). + * + * The size_max field determines several possible behaviours: + * + * size_max = -1 (= UINT_MAX) or any large value will let the buffer be + * reallocated as necessary, with an amortized linear cost. + * + * size_max = 0 prevents writing anything to the buffer: only the total + * length is computed. The write operations can then possibly be repeated in + * a buffer with exactly the necessary size + * (using size_init = size_max = len + 1). + * + * size_max = 1 is automatically replaced by the exact size available in the + * structure itself, thus ensuring no dynamic memory allocation. The + * internal buffer is large enough to hold a reasonable paragraph of text, + * such as the current paragraph. + */ + +FF_PAD_STRUCTURE(AVBPrint, 1024, + char *str; /**< string so far */ + unsigned len; /**< length so far */ + unsigned size; /**< allocated memory */ + unsigned size_max; /**< maximum allocated memory */ + char reserved_internal_buffer[1]; +) + +/** + * Convenience macros for special values for av_bprint_init() size_max + * parameter. + */ +#define AV_BPRINT_SIZE_UNLIMITED ((unsigned)-1) +#define AV_BPRINT_SIZE_AUTOMATIC 1 +#define AV_BPRINT_SIZE_COUNT_ONLY 0 + +/** + * Init a print buffer. + * + * @param buf buffer to init + * @param size_init initial size (including the final 0) + * @param size_max maximum size; + * 0 means do not write anything, just count the length; + * 1 is replaced by the maximum value for automatic storage; + * any large value means that the internal buffer will be + * reallocated as needed up to that limit; -1 is converted to + * UINT_MAX, the largest limit possible. + * Check also AV_BPRINT_SIZE_* macros. + */ +void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max); + +/** + * Init a print buffer using a pre-existing buffer. + * + * The buffer will not be reallocated. + * + * @param buf buffer structure to init + * @param buffer byte buffer to use for the string data + * @param size size of buffer + */ +void av_bprint_init_for_buffer(AVBPrint *buf, char *buffer, unsigned size); + +/** + * Append a formatted string to a print buffer. + */ +void av_bprintf(AVBPrint *buf, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Append a formatted string to a print buffer. + */ +void av_vbprintf(AVBPrint *buf, const char *fmt, va_list vl_arg); + +/** + * Append char c n times to a print buffer. + */ +void av_bprint_chars(AVBPrint *buf, char c, unsigned n); + +/** + * Append data to a print buffer. + * + * param buf bprint buffer to use + * param data pointer to data + * param size size of data + */ +void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size); + +struct tm; +/** + * Append a formatted date and time to a print buffer. + * + * param buf bprint buffer to use + * param fmt date and time format string, see strftime() + * param tm broken-down time structure to translate + * + * @note due to poor design of the standard strftime function, it may + * produce poor results if the format string expands to a very long text and + * the bprint buffer is near the limit stated by the size_max option. + */ +void av_bprint_strftime(AVBPrint *buf, const char *fmt, const struct tm *tm); + +/** + * Allocate bytes in the buffer for external use. + * + * @param[in] buf buffer structure + * @param[in] size required size + * @param[out] mem pointer to the memory area + * @param[out] actual_size size of the memory area after allocation; + * can be larger or smaller than size + */ +void av_bprint_get_buffer(AVBPrint *buf, unsigned size, + unsigned char **mem, unsigned *actual_size); + +/** + * Reset the string to "" but keep internal allocated data. + */ +void av_bprint_clear(AVBPrint *buf); + +/** + * Test if the print buffer is complete (not truncated). + * + * It may have been truncated due to a memory allocation failure + * or the size_max limit (compare size and size_max if necessary). + */ +static inline int av_bprint_is_complete(const AVBPrint *buf) +{ + return buf->len < buf->size; +} + +/** + * Finalize a print buffer. + * + * The print buffer can no longer be used afterwards, + * but the len and size fields are still valid. + * + * @arg[out] ret_str if not NULL, used to return a permanent copy of the + * buffer contents, or NULL if memory allocation fails; + * if NULL, the buffer is discarded and freed + * @return 0 for success or error code (probably AVERROR(ENOMEM)) + */ +int av_bprint_finalize(AVBPrint *buf, char **ret_str); + +/** + * Escape the content in src and append it to dstbuf. + * + * @param dstbuf already inited destination bprint buffer + * @param src string containing the text to escape + * @param special_chars string containing the special characters which + * need to be escaped, can be NULL + * @param mode escape mode to employ, see AV_ESCAPE_MODE_* macros. + * Any unknown value for mode will be considered equivalent to + * AV_ESCAPE_MODE_BACKSLASH, but this behaviour can change without + * notice. + * @param flags flags which control how to escape, see AV_ESCAPE_FLAG_* macros + */ +void av_bprint_escape(AVBPrint *dstbuf, const char *src, const char *special_chars, + enum AVEscapeMode mode, int flags); + +#endif /* AVUTIL_BPRINT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/bswap.h b/thrid-party/ffmpeg/include/libavutil/bswap.h new file mode 100644 index 0000000000..91cb79538d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/bswap.h @@ -0,0 +1,109 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * byte swapping routines + */ + +#ifndef AVUTIL_BSWAP_H +#define AVUTIL_BSWAP_H + +#include +#include "libavutil/avconfig.h" +#include "attributes.h" + +#ifdef HAVE_AV_CONFIG_H + +#include "config.h" + +#if ARCH_AARCH64 +# include "aarch64/bswap.h" +#elif ARCH_ARM +# include "arm/bswap.h" +#elif ARCH_AVR32 +# include "avr32/bswap.h" +#elif ARCH_SH4 +# include "sh4/bswap.h" +#elif ARCH_X86 +# include "x86/bswap.h" +#endif + +#endif /* HAVE_AV_CONFIG_H */ + +#define AV_BSWAP16C(x) (((x) << 8 & 0xff00) | ((x) >> 8 & 0x00ff)) +#define AV_BSWAP32C(x) (AV_BSWAP16C(x) << 16 | AV_BSWAP16C((x) >> 16)) +#define AV_BSWAP64C(x) (AV_BSWAP32C(x) << 32 | AV_BSWAP32C((x) >> 32)) + +#define AV_BSWAPC(s, x) AV_BSWAP##s##C(x) + +#ifndef av_bswap16 +static av_always_inline av_const uint16_t av_bswap16(uint16_t x) +{ + x= (x>>8) | (x<<8); + return x; +} +#endif + +#ifndef av_bswap32 +static av_always_inline av_const uint32_t av_bswap32(uint32_t x) +{ + return AV_BSWAP32C(x); +} +#endif + +#ifndef av_bswap64 +static inline uint64_t av_const av_bswap64(uint64_t x) +{ + return (uint64_t)av_bswap32(x) << 32 | av_bswap32(x >> 32); +} +#endif + +// be2ne ... big-endian to native-endian +// le2ne ... little-endian to native-endian + +#if AV_HAVE_BIGENDIAN +#define av_be2ne16(x) (x) +#define av_be2ne32(x) (x) +#define av_be2ne64(x) (x) +#define av_le2ne16(x) av_bswap16(x) +#define av_le2ne32(x) av_bswap32(x) +#define av_le2ne64(x) av_bswap64(x) +#define AV_BE2NEC(s, x) (x) +#define AV_LE2NEC(s, x) AV_BSWAPC(s, x) +#else +#define av_be2ne16(x) av_bswap16(x) +#define av_be2ne32(x) av_bswap32(x) +#define av_be2ne64(x) av_bswap64(x) +#define av_le2ne16(x) (x) +#define av_le2ne32(x) (x) +#define av_le2ne64(x) (x) +#define AV_BE2NEC(s, x) AV_BSWAPC(s, x) +#define AV_LE2NEC(s, x) (x) +#endif + +#define AV_BE2NE16C(x) AV_BE2NEC(16, x) +#define AV_BE2NE32C(x) AV_BE2NEC(32, x) +#define AV_BE2NE64C(x) AV_BE2NEC(64, x) +#define AV_LE2NE16C(x) AV_LE2NEC(16, x) +#define AV_LE2NE32C(x) AV_LE2NEC(32, x) +#define AV_LE2NE64C(x) AV_LE2NEC(64, x) + +#endif /* AVUTIL_BSWAP_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/buffer.h b/thrid-party/ffmpeg/include/libavutil/buffer.h new file mode 100644 index 0000000000..73b6bd0b14 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/buffer.h @@ -0,0 +1,291 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_buffer + * refcounted data buffer API + */ + +#ifndef AVUTIL_BUFFER_H +#define AVUTIL_BUFFER_H + +#include + +/** + * @defgroup lavu_buffer AVBuffer + * @ingroup lavu_data + * + * @{ + * AVBuffer is an API for reference-counted data buffers. + * + * There are two core objects in this API -- AVBuffer and AVBufferRef. AVBuffer + * represents the data buffer itself; it is opaque and not meant to be accessed + * by the caller directly, but only through AVBufferRef. However, the caller may + * e.g. compare two AVBuffer pointers to check whether two different references + * are describing the same data buffer. AVBufferRef represents a single + * reference to an AVBuffer and it is the object that may be manipulated by the + * caller directly. + * + * There are two functions provided for creating a new AVBuffer with a single + * reference -- av_buffer_alloc() to just allocate a new buffer, and + * av_buffer_create() to wrap an existing array in an AVBuffer. From an existing + * reference, additional references may be created with av_buffer_ref(). + * Use av_buffer_unref() to free a reference (this will automatically free the + * data once all the references are freed). + * + * The convention throughout this API and the rest of FFmpeg is such that the + * buffer is considered writable if there exists only one reference to it (and + * it has not been marked as read-only). The av_buffer_is_writable() function is + * provided to check whether this is true and av_buffer_make_writable() will + * automatically create a new writable buffer when necessary. + * Of course nothing prevents the calling code from violating this convention, + * however that is safe only when all the existing references are under its + * control. + * + * @note Referencing and unreferencing the buffers is thread-safe and thus + * may be done from multiple threads simultaneously without any need for + * additional locking. + * + * @note Two different references to the same buffer can point to different + * parts of the buffer (i.e. their AVBufferRef.data will not be equal). + */ + +/** + * A reference counted buffer type. It is opaque and is meant to be used through + * references (AVBufferRef). + */ +typedef struct AVBuffer AVBuffer; + +/** + * A reference to a data buffer. + * + * The size of this struct is not a part of the public ABI and it is not meant + * to be allocated directly. + */ +typedef struct AVBufferRef { + AVBuffer *buffer; + + /** + * The data buffer. It is considered writable if and only if + * this is the only reference to the buffer, in which case + * av_buffer_is_writable() returns 1. + */ + uint8_t *data; + /** + * Size of data in bytes. + */ + int size; +} AVBufferRef; + +/** + * Allocate an AVBuffer of the given size using av_malloc(). + * + * @return an AVBufferRef of given size or NULL when out of memory + */ +AVBufferRef *av_buffer_alloc(int size); + +/** + * Same as av_buffer_alloc(), except the returned buffer will be initialized + * to zero. + */ +AVBufferRef *av_buffer_allocz(int size); + +/** + * Always treat the buffer as read-only, even when it has only one + * reference. + */ +#define AV_BUFFER_FLAG_READONLY (1 << 0) + +/** + * Create an AVBuffer from an existing array. + * + * If this function is successful, data is owned by the AVBuffer. The caller may + * only access data through the returned AVBufferRef and references derived from + * it. + * If this function fails, data is left untouched. + * @param data data array + * @param size size of data in bytes + * @param free a callback for freeing this buffer's data + * @param opaque parameter to be got for processing or passed to free + * @param flags a combination of AV_BUFFER_FLAG_* + * + * @return an AVBufferRef referring to data on success, NULL on failure. + */ +AVBufferRef *av_buffer_create(uint8_t *data, int size, + void (*free)(void *opaque, uint8_t *data), + void *opaque, int flags); + +/** + * Default free callback, which calls av_free() on the buffer data. + * This function is meant to be passed to av_buffer_create(), not called + * directly. + */ +void av_buffer_default_free(void *opaque, uint8_t *data); + +/** + * Create a new reference to an AVBuffer. + * + * @return a new AVBufferRef referring to the same AVBuffer as buf or NULL on + * failure. + */ +AVBufferRef *av_buffer_ref(AVBufferRef *buf); + +/** + * Free a given reference and automatically free the buffer if there are no more + * references to it. + * + * @param buf the reference to be freed. The pointer is set to NULL on return. + */ +void av_buffer_unref(AVBufferRef **buf); + +/** + * @return 1 if the caller may write to the data referred to by buf (which is + * true if and only if buf is the only reference to the underlying AVBuffer). + * Return 0 otherwise. + * A positive answer is valid until av_buffer_ref() is called on buf. + */ +int av_buffer_is_writable(const AVBufferRef *buf); + +/** + * @return the opaque parameter set by av_buffer_create. + */ +void *av_buffer_get_opaque(const AVBufferRef *buf); + +int av_buffer_get_ref_count(const AVBufferRef *buf); + +/** + * Create a writable reference from a given buffer reference, avoiding data copy + * if possible. + * + * @param buf buffer reference to make writable. On success, buf is either left + * untouched, or it is unreferenced and a new writable AVBufferRef is + * written in its place. On failure, buf is left untouched. + * @return 0 on success, a negative AVERROR on failure. + */ +int av_buffer_make_writable(AVBufferRef **buf); + +/** + * Reallocate a given buffer. + * + * @param buf a buffer reference to reallocate. On success, buf will be + * unreferenced and a new reference with the required size will be + * written in its place. On failure buf will be left untouched. *buf + * may be NULL, then a new buffer is allocated. + * @param size required new buffer size. + * @return 0 on success, a negative AVERROR on failure. + * + * @note the buffer is actually reallocated with av_realloc() only if it was + * initially allocated through av_buffer_realloc(NULL) and there is only one + * reference to it (i.e. the one passed to this function). In all other cases + * a new buffer is allocated and the data is copied. + */ +int av_buffer_realloc(AVBufferRef **buf, int size); + +/** + * @} + */ + +/** + * @defgroup lavu_bufferpool AVBufferPool + * @ingroup lavu_data + * + * @{ + * AVBufferPool is an API for a lock-free thread-safe pool of AVBuffers. + * + * Frequently allocating and freeing large buffers may be slow. AVBufferPool is + * meant to solve this in cases when the caller needs a set of buffers of the + * same size (the most obvious use case being buffers for raw video or audio + * frames). + * + * At the beginning, the user must call av_buffer_pool_init() to create the + * buffer pool. Then whenever a buffer is needed, call av_buffer_pool_get() to + * get a reference to a new buffer, similar to av_buffer_alloc(). This new + * reference works in all aspects the same way as the one created by + * av_buffer_alloc(). However, when the last reference to this buffer is + * unreferenced, it is returned to the pool instead of being freed and will be + * reused for subsequent av_buffer_pool_get() calls. + * + * When the caller is done with the pool and no longer needs to allocate any new + * buffers, av_buffer_pool_uninit() must be called to mark the pool as freeable. + * Once all the buffers are released, it will automatically be freed. + * + * Allocating and releasing buffers with this API is thread-safe as long as + * either the default alloc callback is used, or the user-supplied one is + * thread-safe. + */ + +/** + * The buffer pool. This structure is opaque and not meant to be accessed + * directly. It is allocated with av_buffer_pool_init() and freed with + * av_buffer_pool_uninit(). + */ +typedef struct AVBufferPool AVBufferPool; + +/** + * Allocate and initialize a buffer pool. + * + * @param size size of each buffer in this pool + * @param alloc a function that will be used to allocate new buffers when the + * pool is empty. May be NULL, then the default allocator will be used + * (av_buffer_alloc()). + * @return newly created buffer pool on success, NULL on error. + */ +AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size)); + +/** + * Allocate and initialize a buffer pool with a more complex allocator. + * + * @param size size of each buffer in this pool + * @param opaque arbitrary user data used by the allocator + * @param alloc a function that will be used to allocate new buffers when the + * pool is empty. + * @param pool_free a function that will be called immediately before the pool + * is freed. I.e. after av_buffer_pool_uninit() is called + * by the caller and all the frames are returned to the pool + * and freed. It is intended to uninitialize the user opaque + * data. + * @return newly created buffer pool on success, NULL on error. + */ +AVBufferPool *av_buffer_pool_init2(int size, void *opaque, + AVBufferRef* (*alloc)(void *opaque, int size), + void (*pool_free)(void *opaque)); + +/** + * Mark the pool as being available for freeing. It will actually be freed only + * once all the allocated buffers associated with the pool are released. Thus it + * is safe to call this function while some of the allocated buffers are still + * in use. + * + * @param pool pointer to the pool to be freed. It will be set to NULL. + */ +void av_buffer_pool_uninit(AVBufferPool **pool); + +/** + * Allocate a new AVBuffer, reusing an old buffer from the pool when available. + * This function may be called simultaneously from multiple threads. + * + * @return a reference to the new buffer on success, NULL on error. + */ +AVBufferRef *av_buffer_pool_get(AVBufferPool *pool); + +/** + * @} + */ + +#endif /* AVUTIL_BUFFER_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/camellia.h b/thrid-party/ffmpeg/include/libavutil/camellia.h new file mode 100644 index 0000000000..e674c9b9a4 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/camellia.h @@ -0,0 +1,70 @@ +/* + * An implementation of the CAMELLIA algorithm as mentioned in RFC3713 + * Copyright (c) 2014 Supraja Meedinti + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_CAMELLIA_H +#define AVUTIL_CAMELLIA_H + +#include + + +/** + * @file + * @brief Public header for libavutil CAMELLIA algorithm + * @defgroup lavu_camellia CAMELLIA + * @ingroup lavu_crypto + * @{ + */ + +extern const int av_camellia_size; + +struct AVCAMELLIA; + +/** + * Allocate an AVCAMELLIA context + * To free the struct: av_free(ptr) + */ +struct AVCAMELLIA *av_camellia_alloc(void); + +/** + * Initialize an AVCAMELLIA context. + * + * @param ctx an AVCAMELLIA context + * @param key a key of 16, 24, 32 bytes used for encryption/decryption + * @param key_bits number of keybits: possible are 128, 192, 256 + */ +int av_camellia_init(struct AVCAMELLIA *ctx, const uint8_t *key, int key_bits); + +/** + * Encrypt or decrypt a buffer using a previously initialized context + * + * @param ctx an AVCAMELLIA context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 16 byte blocks + * @paran iv initialization vector for CBC mode, NULL for ECB mode + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_camellia_crypt(struct AVCAMELLIA *ctx, uint8_t *dst, const uint8_t *src, int count, uint8_t* iv, int decrypt); + +/** + * @} + */ +#endif /* AVUTIL_CAMELLIA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/cast5.h b/thrid-party/ffmpeg/include/libavutil/cast5.h new file mode 100644 index 0000000000..ad5b347e68 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/cast5.h @@ -0,0 +1,80 @@ +/* + * An implementation of the CAST128 algorithm as mentioned in RFC2144 + * Copyright (c) 2014 Supraja Meedinti + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_CAST5_H +#define AVUTIL_CAST5_H + +#include + + +/** + * @file + * @brief Public header for libavutil CAST5 algorithm + * @defgroup lavu_cast5 CAST5 + * @ingroup lavu_crypto + * @{ + */ + +extern const int av_cast5_size; + +struct AVCAST5; + +/** + * Allocate an AVCAST5 context + * To free the struct: av_free(ptr) + */ +struct AVCAST5 *av_cast5_alloc(void); +/** + * Initialize an AVCAST5 context. + * + * @param ctx an AVCAST5 context + * @param key a key of 5,6,...16 bytes used for encryption/decryption + * @param key_bits number of keybits: possible are 40,48,...,128 + * @return 0 on success, less than 0 on failure + */ +int av_cast5_init(struct AVCAST5 *ctx, const uint8_t *key, int key_bits); + +/** + * Encrypt or decrypt a buffer using a previously initialized context, ECB mode only + * + * @param ctx an AVCAST5 context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_cast5_crypt(struct AVCAST5 *ctx, uint8_t *dst, const uint8_t *src, int count, int decrypt); + +/** + * Encrypt or decrypt a buffer using a previously initialized context + * + * @param ctx an AVCAST5 context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param iv initialization vector for CBC mode, NULL for ECB mode + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_cast5_crypt2(struct AVCAST5 *ctx, uint8_t *dst, const uint8_t *src, int count, uint8_t *iv, int decrypt); +/** + * @} + */ +#endif /* AVUTIL_CAST5_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/channel_layout.h b/thrid-party/ffmpeg/include/libavutil/channel_layout.h new file mode 100644 index 0000000000..50bb8f03c5 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/channel_layout.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2006 Michael Niedermayer + * Copyright (c) 2008 Peter Ross + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_CHANNEL_LAYOUT_H +#define AVUTIL_CHANNEL_LAYOUT_H + +#include + +/** + * @file + * audio channel layout utility functions + */ + +/** + * @addtogroup lavu_audio + * @{ + */ + +/** + * @defgroup channel_masks Audio channel masks + * + * A channel layout is a 64-bits integer with a bit set for every channel. + * The number of bits set must be equal to the number of channels. + * The value 0 means that the channel layout is not known. + * @note this data structure is not powerful enough to handle channels + * combinations that have the same channel multiple times, such as + * dual-mono. + * + * @{ + */ +#define AV_CH_FRONT_LEFT 0x00000001 +#define AV_CH_FRONT_RIGHT 0x00000002 +#define AV_CH_FRONT_CENTER 0x00000004 +#define AV_CH_LOW_FREQUENCY 0x00000008 +#define AV_CH_BACK_LEFT 0x00000010 +#define AV_CH_BACK_RIGHT 0x00000020 +#define AV_CH_FRONT_LEFT_OF_CENTER 0x00000040 +#define AV_CH_FRONT_RIGHT_OF_CENTER 0x00000080 +#define AV_CH_BACK_CENTER 0x00000100 +#define AV_CH_SIDE_LEFT 0x00000200 +#define AV_CH_SIDE_RIGHT 0x00000400 +#define AV_CH_TOP_CENTER 0x00000800 +#define AV_CH_TOP_FRONT_LEFT 0x00001000 +#define AV_CH_TOP_FRONT_CENTER 0x00002000 +#define AV_CH_TOP_FRONT_RIGHT 0x00004000 +#define AV_CH_TOP_BACK_LEFT 0x00008000 +#define AV_CH_TOP_BACK_CENTER 0x00010000 +#define AV_CH_TOP_BACK_RIGHT 0x00020000 +#define AV_CH_STEREO_LEFT 0x20000000 ///< Stereo downmix. +#define AV_CH_STEREO_RIGHT 0x40000000 ///< See AV_CH_STEREO_LEFT. +#define AV_CH_WIDE_LEFT 0x0000000080000000ULL +#define AV_CH_WIDE_RIGHT 0x0000000100000000ULL +#define AV_CH_SURROUND_DIRECT_LEFT 0x0000000200000000ULL +#define AV_CH_SURROUND_DIRECT_RIGHT 0x0000000400000000ULL +#define AV_CH_LOW_FREQUENCY_2 0x0000000800000000ULL + +/** Channel mask value used for AVCodecContext.request_channel_layout + to indicate that the user requests the channel order of the decoder output + to be the native codec channel order. */ +#define AV_CH_LAYOUT_NATIVE 0x8000000000000000ULL + +/** + * @} + * @defgroup channel_mask_c Audio channel layouts + * @{ + * */ +#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER) +#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) +#define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER) +#define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) +#define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) +#define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT) +#define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_5POINT0_BACK (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) +#define AV_CH_LAYOUT_5POINT1_BACK (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_6POINT0_FRONT (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) +#define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_6POINT1_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER) +#define AV_CH_LAYOUT_6POINT1_FRONT (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY) +#define AV_CH_LAYOUT_7POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) +#define AV_CH_LAYOUT_7POINT0_FRONT (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) +#define AV_CH_LAYOUT_7POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT) +#define AV_CH_LAYOUT_7POINT1_WIDE (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) +#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER) +#define AV_CH_LAYOUT_OCTAGONAL (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT) +#define AV_CH_LAYOUT_HEXADECAGONAL (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT) +#define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT) + +enum AVMatrixEncoding { + AV_MATRIX_ENCODING_NONE, + AV_MATRIX_ENCODING_DOLBY, + AV_MATRIX_ENCODING_DPLII, + AV_MATRIX_ENCODING_DPLIIX, + AV_MATRIX_ENCODING_DPLIIZ, + AV_MATRIX_ENCODING_DOLBYEX, + AV_MATRIX_ENCODING_DOLBYHEADPHONE, + AV_MATRIX_ENCODING_NB +}; + +/** + * Return a channel layout id that matches name, or 0 if no match is found. + * + * name can be one or several of the following notations, + * separated by '+' or '|': + * - the name of an usual channel layout (mono, stereo, 4.0, quad, 5.0, + * 5.0(side), 5.1, 5.1(side), 7.1, 7.1(wide), downmix); + * - the name of a single channel (FL, FR, FC, LFE, BL, BR, FLC, FRC, BC, + * SL, SR, TC, TFL, TFC, TFR, TBL, TBC, TBR, DL, DR); + * - a number of channels, in decimal, followed by 'c', yielding + * the default channel layout for that number of channels (@see + * av_get_default_channel_layout); + * - a channel layout mask, in hexadecimal starting with "0x" (see the + * AV_CH_* macros). + * + * Example: "stereo+FC" = "2c+FC" = "2c+1c" = "0x7" + */ +uint64_t av_get_channel_layout(const char *name); + +/** + * Return a channel layout and the number of channels based on the specified name. + * + * This function is similar to (@see av_get_channel_layout), but can also parse + * unknown channel layout specifications. + * + * @param[in] name channel layout specification string + * @param[out] channel_layout parsed channel layout (0 if unknown) + * @param[out] nb_channels number of channels + * + * @return 0 on success, AVERROR(EINVAL) if the parsing fails. + */ +int av_get_extended_channel_layout(const char *name, uint64_t* channel_layout, int* nb_channels); + +/** + * Return a description of a channel layout. + * If nb_channels is <= 0, it is guessed from the channel_layout. + * + * @param buf put here the string containing the channel layout + * @param buf_size size in bytes of the buffer + */ +void av_get_channel_layout_string(char *buf, int buf_size, int nb_channels, uint64_t channel_layout); + +struct AVBPrint; +/** + * Append a description of a channel layout to a bprint buffer. + */ +void av_bprint_channel_layout(struct AVBPrint *bp, int nb_channels, uint64_t channel_layout); + +/** + * Return the number of channels in the channel layout. + */ +int av_get_channel_layout_nb_channels(uint64_t channel_layout); + +/** + * Return default channel layout for a given number of channels. + */ +int64_t av_get_default_channel_layout(int nb_channels); + +/** + * Get the index of a channel in channel_layout. + * + * @param channel a channel layout describing exactly one channel which must be + * present in channel_layout. + * + * @return index of channel in channel_layout on success, a negative AVERROR + * on error. + */ +int av_get_channel_layout_channel_index(uint64_t channel_layout, + uint64_t channel); + +/** + * Get the channel with the given index in channel_layout. + */ +uint64_t av_channel_layout_extract_channel(uint64_t channel_layout, int index); + +/** + * Get the name of a given channel. + * + * @return channel name on success, NULL on error. + */ +const char *av_get_channel_name(uint64_t channel); + +/** + * Get the description of a given channel. + * + * @param channel a channel layout with a single channel + * @return channel description on success, NULL on error + */ +const char *av_get_channel_description(uint64_t channel); + +/** + * Get the value and name of a standard channel layout. + * + * @param[in] index index in an internal list, starting at 0 + * @param[out] layout channel layout mask + * @param[out] name name of the layout + * @return 0 if the layout exists, + * <0 if index is beyond the limits + */ +int av_get_standard_channel_layout(unsigned index, uint64_t *layout, + const char **name); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_CHANNEL_LAYOUT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/common.h b/thrid-party/ffmpeg/include/libavutil/common.h new file mode 100644 index 0000000000..8142b31fdb --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/common.h @@ -0,0 +1,530 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * common internal and external API header + */ + +#ifndef AVUTIL_COMMON_H +#define AVUTIL_COMMON_H + +#if defined(__cplusplus) && !defined(__STDC_CONSTANT_MACROS) && !defined(UINT64_C) +#error missing -D__STDC_CONSTANT_MACROS / #define __STDC_CONSTANT_MACROS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "attributes.h" +#include "macros.h" +#include "version.h" +#include "libavutil/avconfig.h" + +#if AV_HAVE_BIGENDIAN +# define AV_NE(be, le) (be) +#else +# define AV_NE(be, le) (le) +#endif + +//rounded division & shift +#define RSHIFT(a,b) ((a) > 0 ? ((a) + ((1<<(b))>>1))>>(b) : ((a) + ((1<<(b))>>1)-1)>>(b)) +/* assume b>0 */ +#define ROUNDED_DIV(a,b) (((a)>0 ? (a) + ((b)>>1) : (a) - ((b)>>1))/(b)) +/* Fast a/(1<=0 and b>=0 */ +#define AV_CEIL_RSHIFT(a,b) (!av_builtin_constant_p(b) ? -((-(a)) >> (b)) \ + : ((a) + (1<<(b)) - 1) >> (b)) +/* Backwards compat. */ +#define FF_CEIL_RSHIFT AV_CEIL_RSHIFT + +#define FFUDIV(a,b) (((a)>0 ?(a):(a)-(b)+1) / (b)) +#define FFUMOD(a,b) ((a)-(b)*FFUDIV(a,b)) + +/** + * Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as they + * are not representable as absolute values of their type. This is the same + * as with *abs() + * @see FFNABS() + */ +#define FFABS(a) ((a) >= 0 ? (a) : (-(a))) +#define FFSIGN(a) ((a) > 0 ? 1 : -1) + +/** + * Negative Absolute value. + * this works for all integers of all types. + * As with many macros, this evaluates its argument twice, it thus must not have + * a sideeffect, that is FFNABS(x++) has undefined behavior. + */ +#define FFNABS(a) ((a) <= 0 ? (a) : (-(a))) + +/** + * Comparator. + * For two numerical expressions x and y, gives 1 if x > y, -1 if x < y, and 0 + * if x == y. This is useful for instance in a qsort comparator callback. + * Furthermore, compilers are able to optimize this to branchless code, and + * there is no risk of overflow with signed types. + * As with many macros, this evaluates its argument multiple times, it thus + * must not have a side-effect. + */ +#define FFDIFFSIGN(x,y) (((x)>(y)) - ((x)<(y))) + +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMAX3(a,b,c) FFMAX(FFMAX(a,b),c) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) +#define FFMIN3(a,b,c) FFMIN(FFMIN(a,b),c) + +#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0) +#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) + +/* misc math functions */ + +#ifdef HAVE_AV_CONFIG_H +# include "config.h" +# include "intmath.h" +#endif + +/* Pull in unguarded fallback defines at the end of this file. */ +#include "common.h" + +#ifndef av_log2 +av_const int av_log2(unsigned v); +#endif + +#ifndef av_log2_16bit +av_const int av_log2_16bit(unsigned v); +#endif + +/** + * Clip a signed integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int av_clip_c(int a, int amin, int amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** + * Clip a signed 64bit integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** + * Clip a signed integer value into the 0-255 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const uint8_t av_clip_uint8_c(int a) +{ + if (a&(~0xFF)) return (-a)>>31; + else return a; +} + +/** + * Clip a signed integer value into the -128,127 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int8_t av_clip_int8_c(int a) +{ + if ((a+0x80U) & ~0xFF) return (a>>31) ^ 0x7F; + else return a; +} + +/** + * Clip a signed integer value into the 0-65535 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const uint16_t av_clip_uint16_c(int a) +{ + if (a&(~0xFFFF)) return (-a)>>31; + else return a; +} + +/** + * Clip a signed integer value into the -32768,32767 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int16_t av_clip_int16_c(int a) +{ + if ((a+0x8000U) & ~0xFFFF) return (a>>31) ^ 0x7FFF; + else return a; +} + +/** + * Clip a signed 64-bit integer value into the -2147483648,2147483647 range. + * @param a value to clip + * @return clipped value + */ +static av_always_inline av_const int32_t av_clipl_int32_c(int64_t a) +{ + if ((a+0x80000000u) & ~UINT64_C(0xFFFFFFFF)) return (int32_t)((a>>63) ^ 0x7FFFFFFF); + else return (int32_t)a; +} + +/** + * Clip a signed integer into the -(2^p),(2^p-1) range. + * @param a value to clip + * @param p bit position to clip at + * @return clipped value + */ +static av_always_inline av_const int av_clip_intp2_c(int a, int p) +{ + if (((unsigned)a + (1 << p)) & ~((2 << p) - 1)) + return (a >> 31) ^ ((1 << p) - 1); + else + return a; +} + +/** + * Clip a signed integer to an unsigned power of two range. + * @param a value to clip + * @param p bit position to clip at + * @return clipped value + */ +static av_always_inline av_const unsigned av_clip_uintp2_c(int a, int p) +{ + if (a & ~((1<> 31 & ((1<= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** + * Clip a double value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const double av_clipd_c(double a, double amin, double amax) +{ +#if defined(HAVE_AV_CONFIG_H) && defined(ASSERT_LEVEL) && ASSERT_LEVEL >= 2 + if (amin > amax) abort(); +#endif + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +/** Compute ceil(log2(x)). + * @param x value used to compute ceil(log2(x)) + * @return computed ceiling of log2(x) + */ +static av_always_inline av_const int av_ceil_log2_c(int x) +{ + return av_log2((x - 1) << 1); +} + +/** + * Count number of bits set to one in x + * @param x value to count bits of + * @return the number of bits set to one in x + */ +static av_always_inline av_const int av_popcount_c(uint32_t x) +{ + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x += x >> 8; + return (x + (x >> 16)) & 0x3F; +} + +/** + * Count number of bits set to one in x + * @param x value to count bits of + * @return the number of bits set to one in x + */ +static av_always_inline av_const int av_popcount64_c(uint64_t x) +{ + return av_popcount((uint32_t)x) + av_popcount((uint32_t)(x >> 32)); +} + +static av_always_inline av_const int av_parity_c(uint32_t v) +{ + return av_popcount(v) & 1; +} + +#define MKTAG(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((unsigned)(d) << 24)) +#define MKBETAG(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +/** + * Convert a UTF-8 character (up to 4 bytes) to its 32-bit UCS-4 encoded form. + * + * @param val Output value, must be an lvalue of type uint32_t. + * @param GET_BYTE Expression reading one byte from the input. + * Evaluated up to 7 times (4 for the currently + * assigned Unicode range). With a memory buffer + * input, this could be *ptr++. + * @param ERROR Expression to be evaluated on invalid input, + * typically a goto statement. + * + * @warning ERROR should not contain a loop control statement which + * could interact with the internal while loop, and should force an + * exit from the macro code (e.g. through a goto or a return) in order + * to prevent undefined results. + */ +#define GET_UTF8(val, GET_BYTE, ERROR)\ + val= (GET_BYTE);\ + {\ + uint32_t top = (val & 128) >> 1;\ + if ((val & 0xc0) == 0x80 || val >= 0xFE)\ + ERROR\ + while (val & top) {\ + int tmp= (GET_BYTE) - 128;\ + if(tmp>>6)\ + ERROR\ + val= (val<<6) + tmp;\ + top <<= 5;\ + }\ + val &= (top << 1) - 1;\ + } + +/** + * Convert a UTF-16 character (2 or 4 bytes) to its 32-bit UCS-4 encoded form. + * + * @param val Output value, must be an lvalue of type uint32_t. + * @param GET_16BIT Expression returning two bytes of UTF-16 data converted + * to native byte order. Evaluated one or two times. + * @param ERROR Expression to be evaluated on invalid input, + * typically a goto statement. + */ +#define GET_UTF16(val, GET_16BIT, ERROR)\ + val = GET_16BIT;\ + {\ + unsigned int hi = val - 0xD800;\ + if (hi < 0x800) {\ + val = GET_16BIT - 0xDC00;\ + if (val > 0x3FFU || hi > 0x3FFU)\ + ERROR\ + val += (hi<<10) + 0x10000;\ + }\ + }\ + +/** + * @def PUT_UTF8(val, tmp, PUT_BYTE) + * Convert a 32-bit Unicode character to its UTF-8 encoded form (up to 4 bytes long). + * @param val is an input-only argument and should be of type uint32_t. It holds + * a UCS-4 encoded Unicode character that is to be converted to UTF-8. If + * val is given as a function it is executed only once. + * @param tmp is a temporary variable and should be of type uint8_t. It + * represents an intermediate value during conversion that is to be + * output by PUT_BYTE. + * @param PUT_BYTE writes the converted UTF-8 bytes to any proper destination. + * It could be a function or a statement, and uses tmp as the input byte. + * For example, PUT_BYTE could be "*output++ = tmp;" PUT_BYTE will be + * executed up to 4 times for values in the valid UTF-8 range and up to + * 7 times in the general case, depending on the length of the converted + * Unicode character. + */ +#define PUT_UTF8(val, tmp, PUT_BYTE)\ + {\ + int bytes, shift;\ + uint32_t in = val;\ + if (in < 0x80) {\ + tmp = in;\ + PUT_BYTE\ + } else {\ + bytes = (av_log2(in) + 4) / 5;\ + shift = (bytes - 1) * 6;\ + tmp = (256 - (256 >> bytes)) | (in >> shift);\ + PUT_BYTE\ + while (shift >= 6) {\ + shift -= 6;\ + tmp = 0x80 | ((in >> shift) & 0x3f);\ + PUT_BYTE\ + }\ + }\ + } + +/** + * @def PUT_UTF16(val, tmp, PUT_16BIT) + * Convert a 32-bit Unicode character to its UTF-16 encoded form (2 or 4 bytes). + * @param val is an input-only argument and should be of type uint32_t. It holds + * a UCS-4 encoded Unicode character that is to be converted to UTF-16. If + * val is given as a function it is executed only once. + * @param tmp is a temporary variable and should be of type uint16_t. It + * represents an intermediate value during conversion that is to be + * output by PUT_16BIT. + * @param PUT_16BIT writes the converted UTF-16 data to any proper destination + * in desired endianness. It could be a function or a statement, and uses tmp + * as the input byte. For example, PUT_BYTE could be "*output++ = tmp;" + * PUT_BYTE will be executed 1 or 2 times depending on input character. + */ +#define PUT_UTF16(val, tmp, PUT_16BIT)\ + {\ + uint32_t in = val;\ + if (in < 0x10000) {\ + tmp = in;\ + PUT_16BIT\ + } else {\ + tmp = 0xD800 | ((in - 0x10000) >> 10);\ + PUT_16BIT\ + tmp = 0xDC00 | ((in - 0x10000) & 0x3FF);\ + PUT_16BIT\ + }\ + }\ + + + +#include "mem.h" + +#ifdef HAVE_AV_CONFIG_H +# include "internal.h" +#endif /* HAVE_AV_CONFIG_H */ + +#endif /* AVUTIL_COMMON_H */ + +/* + * The following definitions are outside the multiple inclusion guard + * to ensure they are immediately available in intmath.h. + */ + +#ifndef av_ceil_log2 +# define av_ceil_log2 av_ceil_log2_c +#endif +#ifndef av_clip +# define av_clip av_clip_c +#endif +#ifndef av_clip64 +# define av_clip64 av_clip64_c +#endif +#ifndef av_clip_uint8 +# define av_clip_uint8 av_clip_uint8_c +#endif +#ifndef av_clip_int8 +# define av_clip_int8 av_clip_int8_c +#endif +#ifndef av_clip_uint16 +# define av_clip_uint16 av_clip_uint16_c +#endif +#ifndef av_clip_int16 +# define av_clip_int16 av_clip_int16_c +#endif +#ifndef av_clipl_int32 +# define av_clipl_int32 av_clipl_int32_c +#endif +#ifndef av_clip_intp2 +# define av_clip_intp2 av_clip_intp2_c +#endif +#ifndef av_clip_uintp2 +# define av_clip_uintp2 av_clip_uintp2_c +#endif +#ifndef av_mod_uintp2 +# define av_mod_uintp2 av_mod_uintp2_c +#endif +#ifndef av_sat_add32 +# define av_sat_add32 av_sat_add32_c +#endif +#ifndef av_sat_dadd32 +# define av_sat_dadd32 av_sat_dadd32_c +#endif +#ifndef av_clipf +# define av_clipf av_clipf_c +#endif +#ifndef av_clipd +# define av_clipd av_clipd_c +#endif +#ifndef av_popcount +# define av_popcount av_popcount_c +#endif +#ifndef av_popcount64 +# define av_popcount64 av_popcount64_c +#endif +#ifndef av_parity +# define av_parity av_parity_c +#endif diff --git a/thrid-party/ffmpeg/include/libavutil/cpu.h b/thrid-party/ffmpeg/include/libavutil/cpu.h new file mode 100644 index 0000000000..9e5d40affe --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/cpu.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_CPU_H +#define AVUTIL_CPU_H + +#include + +#include "attributes.h" + +#define AV_CPU_FLAG_FORCE 0x80000000 /* force usage of selected flags (OR) */ + + /* lower 16 bits - CPU features */ +#define AV_CPU_FLAG_MMX 0x0001 ///< standard MMX +#define AV_CPU_FLAG_MMXEXT 0x0002 ///< SSE integer functions or AMD MMX ext +#define AV_CPU_FLAG_MMX2 0x0002 ///< SSE integer functions or AMD MMX ext +#define AV_CPU_FLAG_3DNOW 0x0004 ///< AMD 3DNOW +#define AV_CPU_FLAG_SSE 0x0008 ///< SSE functions +#define AV_CPU_FLAG_SSE2 0x0010 ///< PIV SSE2 functions +#define AV_CPU_FLAG_SSE2SLOW 0x40000000 ///< SSE2 supported, but usually not faster + ///< than regular MMX/SSE (e.g. Core1) +#define AV_CPU_FLAG_3DNOWEXT 0x0020 ///< AMD 3DNowExt +#define AV_CPU_FLAG_SSE3 0x0040 ///< Prescott SSE3 functions +#define AV_CPU_FLAG_SSE3SLOW 0x20000000 ///< SSE3 supported, but usually not faster + ///< than regular MMX/SSE (e.g. Core1) +#define AV_CPU_FLAG_SSSE3 0x0080 ///< Conroe SSSE3 functions +#define AV_CPU_FLAG_SSSE3SLOW 0x4000000 ///< SSSE3 supported, but usually not faster +#define AV_CPU_FLAG_ATOM 0x10000000 ///< Atom processor, some SSSE3 instructions are slower +#define AV_CPU_FLAG_SSE4 0x0100 ///< Penryn SSE4.1 functions +#define AV_CPU_FLAG_SSE42 0x0200 ///< Nehalem SSE4.2 functions +#define AV_CPU_FLAG_AESNI 0x80000 ///< Advanced Encryption Standard functions +#define AV_CPU_FLAG_AVX 0x4000 ///< AVX functions: requires OS support even if YMM registers aren't used +#define AV_CPU_FLAG_AVXSLOW 0x8000000 ///< AVX supported, but slow when using YMM registers (e.g. Bulldozer) +#define AV_CPU_FLAG_XOP 0x0400 ///< Bulldozer XOP functions +#define AV_CPU_FLAG_FMA4 0x0800 ///< Bulldozer FMA4 functions +#define AV_CPU_FLAG_CMOV 0x1000 ///< supports cmov instruction +#define AV_CPU_FLAG_AVX2 0x8000 ///< AVX2 functions: requires OS support even if YMM registers aren't used +#define AV_CPU_FLAG_FMA3 0x10000 ///< Haswell FMA3 functions +#define AV_CPU_FLAG_BMI1 0x20000 ///< Bit Manipulation Instruction Set 1 +#define AV_CPU_FLAG_BMI2 0x40000 ///< Bit Manipulation Instruction Set 2 + +#define AV_CPU_FLAG_ALTIVEC 0x0001 ///< standard +#define AV_CPU_FLAG_VSX 0x0002 ///< ISA 2.06 +#define AV_CPU_FLAG_POWER8 0x0004 ///< ISA 2.07 + +#define AV_CPU_FLAG_ARMV5TE (1 << 0) +#define AV_CPU_FLAG_ARMV6 (1 << 1) +#define AV_CPU_FLAG_ARMV6T2 (1 << 2) +#define AV_CPU_FLAG_VFP (1 << 3) +#define AV_CPU_FLAG_VFPV3 (1 << 4) +#define AV_CPU_FLAG_NEON (1 << 5) +#define AV_CPU_FLAG_ARMV8 (1 << 6) +#define AV_CPU_FLAG_VFP_VM (1 << 7) ///< VFPv2 vector mode, deprecated in ARMv7-A and unavailable in various CPUs implementations +#define AV_CPU_FLAG_SETEND (1 <<16) + +/** + * Return the flags which specify extensions supported by the CPU. + * The returned value is affected by av_force_cpu_flags() if that was used + * before. So av_get_cpu_flags() can easily be used in an application to + * detect the enabled cpu flags. + */ +int av_get_cpu_flags(void); + +/** + * Disables cpu detection and forces the specified flags. + * -1 is a special case that disables forcing of specific flags. + */ +void av_force_cpu_flags(int flags); + +/** + * Set a mask on flags returned by av_get_cpu_flags(). + * This function is mainly useful for testing. + * Please use av_force_cpu_flags() and av_get_cpu_flags() instead which are more flexible + */ +attribute_deprecated void av_set_cpu_flags_mask(int mask); + +/** + * Parse CPU flags from a string. + * + * The returned flags contain the specified flags as well as related unspecified flags. + * + * This function exists only for compatibility with libav. + * Please use av_parse_cpu_caps() when possible. + * @return a combination of AV_CPU_* flags, negative on error. + */ +attribute_deprecated +int av_parse_cpu_flags(const char *s); + +/** + * Parse CPU caps from a string and update the given AV_CPU_* flags based on that. + * + * @return negative on error. + */ +int av_parse_cpu_caps(unsigned *flags, const char *s); + +/** + * @return the number of logical CPU cores present. + */ +int av_cpu_count(void); + +/** + * Get the maximum data alignment that may be required by FFmpeg. + * + * Note that this is affected by the build configuration and the CPU flags mask, + * so e.g. if the CPU supports AVX, but libavutil has been built with + * --disable-avx or the AV_CPU_FLAG_AVX flag has been disabled through + * av_set_cpu_flags_mask(), then this function will behave as if AVX is not + * present. + */ +size_t av_cpu_max_align(void); + +#endif /* AVUTIL_CPU_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/crc.h b/thrid-party/ffmpeg/include/libavutil/crc.h new file mode 100644 index 0000000000..2a1b0d7624 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/crc.h @@ -0,0 +1,103 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_crc32 + * Public header for CRC hash function implementation. + */ + +#ifndef AVUTIL_CRC_H +#define AVUTIL_CRC_H + +#include +#include +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_crc32 CRC + * @ingroup lavu_hash + * CRC (Cyclic Redundancy Check) hash function implementation. + * + * This module supports numerous CRC polynomials, in addition to the most + * widely used CRC-32-IEEE. See @ref AVCRCId for a list of available + * polynomials. + * + * @{ + */ + +typedef uint32_t AVCRC; + +typedef enum { + AV_CRC_8_ATM, + AV_CRC_16_ANSI, + AV_CRC_16_CCITT, + AV_CRC_32_IEEE, + AV_CRC_32_IEEE_LE, /*< reversed bitorder version of AV_CRC_32_IEEE */ + AV_CRC_16_ANSI_LE, /*< reversed bitorder version of AV_CRC_16_ANSI */ +#if FF_API_CRC_BIG_TABLE + AV_CRC_24_IEEE = 12, +#else + AV_CRC_24_IEEE, +#endif /* FF_API_CRC_BIG_TABLE */ + AV_CRC_MAX, /*< Not part of public API! Do not use outside libavutil. */ +}AVCRCId; + +/** + * Initialize a CRC table. + * @param ctx must be an array of size sizeof(AVCRC)*257 or sizeof(AVCRC)*1024 + * @param le If 1, the lowest bit represents the coefficient for the highest + * exponent of the corresponding polynomial (both for poly and + * actual CRC). + * If 0, you must swap the CRC parameter and the result of av_crc + * if you need the standard representation (can be simplified in + * most cases to e.g. bswap16): + * av_bswap32(crc << (32-bits)) + * @param bits number of bits for the CRC + * @param poly generator polynomial without the x**bits coefficient, in the + * representation as specified by le + * @param ctx_size size of ctx in bytes + * @return <0 on failure + */ +int av_crc_init(AVCRC *ctx, int le, int bits, uint32_t poly, int ctx_size); + +/** + * Get an initialized standard CRC table. + * @param crc_id ID of a standard CRC + * @return a pointer to the CRC table or NULL on failure + */ +const AVCRC *av_crc_get_table(AVCRCId crc_id); + +/** + * Calculate the CRC of a block. + * @param crc CRC of previous blocks if any or initial value for CRC + * @return CRC updated with the data from the given block + * + * @see av_crc_init() "le" parameter + */ +uint32_t av_crc(const AVCRC *ctx, uint32_t crc, + const uint8_t *buffer, size_t length) av_pure; + +/** + * @} + */ + +#endif /* AVUTIL_CRC_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/des.h b/thrid-party/ffmpeg/include/libavutil/des.h new file mode 100644 index 0000000000..4cf11f5bca --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/des.h @@ -0,0 +1,77 @@ +/* + * DES encryption/decryption + * Copyright (c) 2007 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_DES_H +#define AVUTIL_DES_H + +#include + +/** + * @defgroup lavu_des DES + * @ingroup lavu_crypto + * @{ + */ + +typedef struct AVDES { + uint64_t round_keys[3][16]; + int triple_des; +} AVDES; + +/** + * Allocate an AVDES context. + */ +AVDES *av_des_alloc(void); + +/** + * @brief Initializes an AVDES context. + * + * @param key_bits must be 64 or 192 + * @param decrypt 0 for encryption/CBC-MAC, 1 for decryption + * @return zero on success, negative value otherwise + */ +int av_des_init(struct AVDES *d, const uint8_t *key, int key_bits, int decrypt); + +/** + * @brief Encrypts / decrypts using the DES algorithm. + * + * @param count number of 8 byte blocks + * @param dst destination array, can be equal to src, must be 8-byte aligned + * @param src source array, can be equal to dst, must be 8-byte aligned, may be NULL + * @param iv initialization vector for CBC mode, if NULL then ECB will be used, + * must be 8-byte aligned + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_des_crypt(struct AVDES *d, uint8_t *dst, const uint8_t *src, int count, uint8_t *iv, int decrypt); + +/** + * @brief Calculates CBC-MAC using the DES algorithm. + * + * @param count number of 8 byte blocks + * @param dst destination array, can be equal to src, must be 8-byte aligned + * @param src source array, can be equal to dst, must be 8-byte aligned, may be NULL + */ +void av_des_mac(struct AVDES *d, uint8_t *dst, const uint8_t *src, int count); + +/** + * @} + */ + +#endif /* AVUTIL_DES_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/dict.h b/thrid-party/ffmpeg/include/libavutil/dict.h new file mode 100644 index 0000000000..118f1f00ed --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/dict.h @@ -0,0 +1,200 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Public dictionary API. + * @deprecated + * AVDictionary is provided for compatibility with libav. It is both in + * implementation as well as API inefficient. It does not scale and is + * extremely slow with large dictionaries. + * It is recommended that new code uses our tree container from tree.c/h + * where applicable, which uses AVL trees to achieve O(log n) performance. + */ + +#ifndef AVUTIL_DICT_H +#define AVUTIL_DICT_H + +#include + +#include "version.h" + +/** + * @addtogroup lavu_dict AVDictionary + * @ingroup lavu_data + * + * @brief Simple key:value store + * + * @{ + * Dictionaries are used for storing key:value pairs. To create + * an AVDictionary, simply pass an address of a NULL pointer to + * av_dict_set(). NULL can be used as an empty dictionary wherever + * a pointer to an AVDictionary is required. + * Use av_dict_get() to retrieve an entry or iterate over all + * entries and finally av_dict_free() to free the dictionary + * and all its contents. + * + @code + AVDictionary *d = NULL; // "create" an empty dictionary + AVDictionaryEntry *t = NULL; + + av_dict_set(&d, "foo", "bar", 0); // add an entry + + char *k = av_strdup("key"); // if your strings are already allocated, + char *v = av_strdup("value"); // you can avoid copying them like this + av_dict_set(&d, k, v, AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + + while (t = av_dict_get(d, "", t, AV_DICT_IGNORE_SUFFIX)) { + <....> // iterate over all entries in d + } + av_dict_free(&d); + @endcode + */ + +#define AV_DICT_MATCH_CASE 1 /**< Only get an entry with exact-case key match. Only relevant in av_dict_get(). */ +#define AV_DICT_IGNORE_SUFFIX 2 /**< Return first entry in a dictionary whose first part corresponds to the search key, + ignoring the suffix of the found key string. Only relevant in av_dict_get(). */ +#define AV_DICT_DONT_STRDUP_KEY 4 /**< Take ownership of a key that's been + allocated with av_malloc() or another memory allocation function. */ +#define AV_DICT_DONT_STRDUP_VAL 8 /**< Take ownership of a value that's been + allocated with av_malloc() or another memory allocation function. */ +#define AV_DICT_DONT_OVERWRITE 16 ///< Don't overwrite existing entries. +#define AV_DICT_APPEND 32 /**< If the entry already exists, append to it. Note that no + delimiter is added, the strings are simply concatenated. */ +#define AV_DICT_MULTIKEY 64 /**< Allow to store several equal keys in the dictionary */ + +typedef struct AVDictionaryEntry { + char *key; + char *value; +} AVDictionaryEntry; + +typedef struct AVDictionary AVDictionary; + +/** + * Get a dictionary entry with matching key. + * + * The returned entry key or value must not be changed, or it will + * cause undefined behavior. + * + * To iterate through all the dictionary entries, you can set the matching key + * to the null string "" and set the AV_DICT_IGNORE_SUFFIX flag. + * + * @param prev Set to the previous matching element to find the next. + * If set to NULL the first matching element is returned. + * @param key matching key + * @param flags a collection of AV_DICT_* flags controlling how the entry is retrieved + * @return found entry or NULL in case no matching entry was found in the dictionary + */ +AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key, + const AVDictionaryEntry *prev, int flags); + +/** + * Get number of entries in dictionary. + * + * @param m dictionary + * @return number of entries in dictionary + */ +int av_dict_count(const AVDictionary *m); + +/** + * Set the given entry in *pm, overwriting an existing entry. + * + * Note: If AV_DICT_DONT_STRDUP_KEY or AV_DICT_DONT_STRDUP_VAL is set, + * these arguments will be freed on error. + * + * Warning: Adding a new entry to a dictionary invalidates all existing entries + * previously returned with av_dict_get. + * + * @param pm pointer to a pointer to a dictionary struct. If *pm is NULL + * a dictionary struct is allocated and put in *pm. + * @param key entry key to add to *pm (will either be av_strduped or added as a new key depending on flags) + * @param value entry value to add to *pm (will be av_strduped or added as a new key depending on flags). + * Passing a NULL value will cause an existing entry to be deleted. + * @return >= 0 on success otherwise an error code <0 + */ +int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags); + +/** + * Convenience wrapper for av_dict_set that converts the value to a string + * and stores it. + * + * Note: If AV_DICT_DONT_STRDUP_KEY is set, key will be freed on error. + */ +int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags); + +/** + * Parse the key/value pairs list and add the parsed entries to a dictionary. + * + * In case of failure, all the successfully set entries are stored in + * *pm. You may need to manually free the created dictionary. + * + * @param key_val_sep a 0-terminated list of characters used to separate + * key from value + * @param pairs_sep a 0-terminated list of characters used to separate + * two pairs from each other + * @param flags flags to use when adding to dictionary. + * AV_DICT_DONT_STRDUP_KEY and AV_DICT_DONT_STRDUP_VAL + * are ignored since the key/value tokens will always + * be duplicated. + * @return 0 on success, negative AVERROR code on failure + */ +int av_dict_parse_string(AVDictionary **pm, const char *str, + const char *key_val_sep, const char *pairs_sep, + int flags); + +/** + * Copy entries from one AVDictionary struct into another. + * @param dst pointer to a pointer to a AVDictionary struct. If *dst is NULL, + * this function will allocate a struct for you and put it in *dst + * @param src pointer to source AVDictionary struct + * @param flags flags to use when setting entries in *dst + * @note metadata is read using the AV_DICT_IGNORE_SUFFIX flag + * @return 0 on success, negative AVERROR code on failure. If dst was allocated + * by this function, callers should free the associated memory. + */ +int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags); + +/** + * Free all the memory allocated for an AVDictionary struct + * and all keys and values. + */ +void av_dict_free(AVDictionary **m); + +/** + * Get dictionary entries as a string. + * + * Create a string containing dictionary's entries. + * Such string may be passed back to av_dict_parse_string(). + * @note String is escaped with backslashes ('\'). + * + * @param[in] m dictionary + * @param[out] buffer Pointer to buffer that will be allocated with string containg entries. + * Buffer must be freed by the caller when is no longer needed. + * @param[in] key_val_sep character used to separate key from value + * @param[in] pairs_sep character used to separate two pairs from each other + * @return >= 0 on success, negative on error + * @warning Separators cannot be neither '\\' nor '\0'. They also cannot be the same. + */ +int av_dict_get_string(const AVDictionary *m, char **buffer, + const char key_val_sep, const char pairs_sep); + +/** + * @} + */ + +#endif /* AVUTIL_DICT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/display.h b/thrid-party/ffmpeg/include/libavutil/display.h new file mode 100644 index 0000000000..515adad795 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/display.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014 Vittorio Giovara + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Display matrix + */ + +#ifndef AVUTIL_DISPLAY_H +#define AVUTIL_DISPLAY_H + +#include +#include "common.h" + +/** + * @addtogroup lavu_video + * @{ + * + * @defgroup lavu_video_display Display transformation matrix functions + * @{ + */ + +/** + * @addtogroup lavu_video_display + * The display transformation matrix specifies an affine transformation that + * should be applied to video frames for correct presentation. It is compatible + * with the matrices stored in the ISO/IEC 14496-12 container format. + * + * The data is a 3x3 matrix represented as a 9-element array: + * + * @code{.unparsed} + * | a b u | + * (a, b, u, c, d, v, x, y, w) -> | c d v | + * | x y w | + * @endcode + * + * All numbers are stored in native endianness, as 16.16 fixed-point values, + * except for u, v and w, which are stored as 2.30 fixed-point values. + * + * The transformation maps a point (p, q) in the source (pre-transformation) + * frame to the point (p', q') in the destination (post-transformation) frame as + * follows: + * + * @code{.unparsed} + * | a b u | + * (p, q, 1) . | c d v | = z * (p', q', 1) + * | x y w | + * @endcode + * + * The transformation can also be more explicitly written in components as + * follows: + * + * @code{.unparsed} + * p' = (a * p + c * q + x) / z; + * q' = (b * p + d * q + y) / z; + * z = u * p + v * q + w + * @endcode + */ + +/** + * Extract the rotation component of the transformation matrix. + * + * @param matrix the transformation matrix + * @return the angle (in degrees) by which the transformation rotates the frame + * counterclockwise. The angle will be in range [-180.0, 180.0], + * or NaN if the matrix is singular. + * + * @note floating point numbers are inherently inexact, so callers are + * recommended to round the return value to nearest integer before use. + */ +double av_display_rotation_get(const int32_t matrix[9]); + +/** + * Initialize a transformation matrix describing a pure counterclockwise + * rotation by the specified angle (in degrees). + * + * @param matrix an allocated transformation matrix (will be fully overwritten + * by this function) + * @param angle rotation angle in degrees. + */ +void av_display_rotation_set(int32_t matrix[9], double angle); + +/** + * Flip the input matrix horizontally and/or vertically. + * + * @param matrix an allocated transformation matrix + * @param hflip whether the matrix should be flipped horizontally + * @param vflip whether the matrix should be flipped vertically + */ +void av_display_matrix_flip(int32_t matrix[9], int hflip, int vflip); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_DISPLAY_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/downmix_info.h b/thrid-party/ffmpeg/include/libavutil/downmix_info.h new file mode 100644 index 0000000000..221cf5bf9b --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/downmix_info.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2014 Tim Walker + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_DOWNMIX_INFO_H +#define AVUTIL_DOWNMIX_INFO_H + +#include "frame.h" + +/** + * @file + * audio downmix medatata + */ + +/** + * @addtogroup lavu_audio + * @{ + */ + +/** + * @defgroup downmix_info Audio downmix metadata + * @{ + */ + +/** + * Possible downmix types. + */ +enum AVDownmixType { + AV_DOWNMIX_TYPE_UNKNOWN, /**< Not indicated. */ + AV_DOWNMIX_TYPE_LORO, /**< Lo/Ro 2-channel downmix (Stereo). */ + AV_DOWNMIX_TYPE_LTRT, /**< Lt/Rt 2-channel downmix, Dolby Surround compatible. */ + AV_DOWNMIX_TYPE_DPLII, /**< Lt/Rt 2-channel downmix, Dolby Pro Logic II compatible. */ + AV_DOWNMIX_TYPE_NB /**< Number of downmix types. Not part of ABI. */ +}; + +/** + * This structure describes optional metadata relevant to a downmix procedure. + * + * All fields are set by the decoder to the value indicated in the audio + * bitstream (if present), or to a "sane" default otherwise. + */ +typedef struct AVDownmixInfo { + /** + * Type of downmix preferred by the mastering engineer. + */ + enum AVDownmixType preferred_downmix_type; + + /** + * Absolute scale factor representing the nominal level of the center + * channel during a regular downmix. + */ + double center_mix_level; + + /** + * Absolute scale factor representing the nominal level of the center + * channel during an Lt/Rt compatible downmix. + */ + double center_mix_level_ltrt; + + /** + * Absolute scale factor representing the nominal level of the surround + * channels during a regular downmix. + */ + double surround_mix_level; + + /** + * Absolute scale factor representing the nominal level of the surround + * channels during an Lt/Rt compatible downmix. + */ + double surround_mix_level_ltrt; + + /** + * Absolute scale factor representing the level at which the LFE data is + * mixed into L/R channels during downmixing. + */ + double lfe_mix_level; +} AVDownmixInfo; + +/** + * Get a frame's AV_FRAME_DATA_DOWNMIX_INFO side data for editing. + * + * If the side data is absent, it is created and added to the frame. + * + * @param frame the frame for which the side data is to be obtained or created + * + * @return the AVDownmixInfo structure to be edited by the caller, or NULL if + * the structure cannot be allocated. + */ +AVDownmixInfo *av_downmix_info_update_side_data(AVFrame *frame); + +/** + * @} + */ + +/** + * @} + */ + +#endif /* AVUTIL_DOWNMIX_INFO_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/error.h b/thrid-party/ffmpeg/include/libavutil/error.h new file mode 100644 index 0000000000..71df4da353 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/error.h @@ -0,0 +1,126 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * error code definitions + */ + +#ifndef AVUTIL_ERROR_H +#define AVUTIL_ERROR_H + +#include +#include + +/** + * @addtogroup lavu_error + * + * @{ + */ + + +/* error handling */ +#if EDOM > 0 +#define AVERROR(e) (-(e)) ///< Returns a negative error code from a POSIX error code, to return from library functions. +#define AVUNERROR(e) (-(e)) ///< Returns a POSIX error code from a library function error return value. +#else +/* Some platforms have E* and errno already negated. */ +#define AVERROR(e) (e) +#define AVUNERROR(e) (e) +#endif + +#define FFERRTAG(a, b, c, d) (-(int)MKTAG(a, b, c, d)) + +#define AVERROR_BSF_NOT_FOUND FFERRTAG(0xF8,'B','S','F') ///< Bitstream filter not found +#define AVERROR_BUG FFERRTAG( 'B','U','G','!') ///< Internal bug, also see AVERROR_BUG2 +#define AVERROR_BUFFER_TOO_SMALL FFERRTAG( 'B','U','F','S') ///< Buffer too small +#define AVERROR_DECODER_NOT_FOUND FFERRTAG(0xF8,'D','E','C') ///< Decoder not found +#define AVERROR_DEMUXER_NOT_FOUND FFERRTAG(0xF8,'D','E','M') ///< Demuxer not found +#define AVERROR_ENCODER_NOT_FOUND FFERRTAG(0xF8,'E','N','C') ///< Encoder not found +#define AVERROR_EOF FFERRTAG( 'E','O','F',' ') ///< End of file +#define AVERROR_EXIT FFERRTAG( 'E','X','I','T') ///< Immediate exit was requested; the called function should not be restarted +#define AVERROR_EXTERNAL FFERRTAG( 'E','X','T',' ') ///< Generic error in an external library +#define AVERROR_FILTER_NOT_FOUND FFERRTAG(0xF8,'F','I','L') ///< Filter not found +#define AVERROR_INVALIDDATA FFERRTAG( 'I','N','D','A') ///< Invalid data found when processing input +#define AVERROR_MUXER_NOT_FOUND FFERRTAG(0xF8,'M','U','X') ///< Muxer not found +#define AVERROR_OPTION_NOT_FOUND FFERRTAG(0xF8,'O','P','T') ///< Option not found +#define AVERROR_PATCHWELCOME FFERRTAG( 'P','A','W','E') ///< Not yet implemented in FFmpeg, patches welcome +#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') ///< Protocol not found + +#define AVERROR_STREAM_NOT_FOUND FFERRTAG(0xF8,'S','T','R') ///< Stream not found +/** + * This is semantically identical to AVERROR_BUG + * it has been introduced in Libav after our AVERROR_BUG and with a modified value. + */ +#define AVERROR_BUG2 FFERRTAG( 'B','U','G',' ') +#define AVERROR_UNKNOWN FFERRTAG( 'U','N','K','N') ///< Unknown error, typically from an external library +#define AVERROR_EXPERIMENTAL (-0x2bb2afa8) ///< Requested feature is flagged experimental. Set strict_std_compliance if you really want to use it. +#define AVERROR_INPUT_CHANGED (-0x636e6701) ///< Input changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_OUTPUT_CHANGED) +#define AVERROR_OUTPUT_CHANGED (-0x636e6702) ///< Output changed between calls. Reconfiguration is required. (can be OR-ed with AVERROR_INPUT_CHANGED) +/* HTTP & RTSP errors */ +#define AVERROR_HTTP_BAD_REQUEST FFERRTAG(0xF8,'4','0','0') +#define AVERROR_HTTP_UNAUTHORIZED FFERRTAG(0xF8,'4','0','1') +#define AVERROR_HTTP_FORBIDDEN FFERRTAG(0xF8,'4','0','3') +#define AVERROR_HTTP_NOT_FOUND FFERRTAG(0xF8,'4','0','4') +#define AVERROR_HTTP_OTHER_4XX FFERRTAG(0xF8,'4','X','X') +#define AVERROR_HTTP_SERVER_ERROR FFERRTAG(0xF8,'5','X','X') + +#define AV_ERROR_MAX_STRING_SIZE 64 + +/** + * Put a description of the AVERROR code errnum in errbuf. + * In case of failure the global variable errno is set to indicate the + * error. Even in case of failure av_strerror() will print a generic + * error message indicating the errnum provided to errbuf. + * + * @param errnum error code to describe + * @param errbuf buffer to which description is written + * @param errbuf_size the size in bytes of errbuf + * @return 0 on success, a negative value if a description for errnum + * cannot be found + */ +int av_strerror(int errnum, char *errbuf, size_t errbuf_size); + +/** + * Fill the provided buffer with a string containing an error string + * corresponding to the AVERROR code errnum. + * + * @param errbuf a buffer + * @param errbuf_size size in bytes of errbuf + * @param errnum error code to describe + * @return the buffer in input, filled with the error description + * @see av_strerror() + */ +static inline char *av_make_error_string(char *errbuf, size_t errbuf_size, int errnum) +{ + av_strerror(errnum, errbuf, errbuf_size); + return errbuf; +} + +/** + * Convenience macro, the return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_err2str(errnum) \ + av_make_error_string((char[AV_ERROR_MAX_STRING_SIZE]){0}, AV_ERROR_MAX_STRING_SIZE, errnum) + +/** + * @} + */ + +#endif /* AVUTIL_ERROR_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/eval.h b/thrid-party/ffmpeg/include/libavutil/eval.h new file mode 100644 index 0000000000..dacd22b96e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/eval.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2002 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * simple arithmetic expression evaluator + */ + +#ifndef AVUTIL_EVAL_H +#define AVUTIL_EVAL_H + +#include "avutil.h" + +typedef struct AVExpr AVExpr; + +/** + * Parse and evaluate an expression. + * Note, this is significantly slower than av_expr_eval(). + * + * @param res a pointer to a double where is put the result value of + * the expression, or NAN in case of error + * @param s expression as a zero terminated string, for example "1+2^3+5*5+sin(2/3)" + * @param const_names NULL terminated array of zero terminated strings of constant identifiers, for example {"PI", "E", 0} + * @param const_values a zero terminated array of values for the identifiers from const_names + * @param func1_names NULL terminated array of zero terminated strings of funcs1 identifiers + * @param funcs1 NULL terminated array of function pointers for functions which take 1 argument + * @param func2_names NULL terminated array of zero terminated strings of funcs2 identifiers + * @param funcs2 NULL terminated array of function pointers for functions which take 2 arguments + * @param opaque a pointer which will be passed to all functions from funcs1 and funcs2 + * @param log_ctx parent logging context + * @return >= 0 in case of success, a negative value corresponding to an + * AVERROR code otherwise + */ +int av_expr_parse_and_eval(double *res, const char *s, + const char * const *const_names, const double *const_values, + const char * const *func1_names, double (* const *funcs1)(void *, double), + const char * const *func2_names, double (* const *funcs2)(void *, double, double), + void *opaque, int log_offset, void *log_ctx); + +/** + * Parse an expression. + * + * @param expr a pointer where is put an AVExpr containing the parsed + * value in case of successful parsing, or NULL otherwise. + * The pointed to AVExpr must be freed with av_expr_free() by the user + * when it is not needed anymore. + * @param s expression as a zero terminated string, for example "1+2^3+5*5+sin(2/3)" + * @param const_names NULL terminated array of zero terminated strings of constant identifiers, for example {"PI", "E", 0} + * @param func1_names NULL terminated array of zero terminated strings of funcs1 identifiers + * @param funcs1 NULL terminated array of function pointers for functions which take 1 argument + * @param func2_names NULL terminated array of zero terminated strings of funcs2 identifiers + * @param funcs2 NULL terminated array of function pointers for functions which take 2 arguments + * @param log_ctx parent logging context + * @return >= 0 in case of success, a negative value corresponding to an + * AVERROR code otherwise + */ +int av_expr_parse(AVExpr **expr, const char *s, + const char * const *const_names, + const char * const *func1_names, double (* const *funcs1)(void *, double), + const char * const *func2_names, double (* const *funcs2)(void *, double, double), + int log_offset, void *log_ctx); + +/** + * Evaluate a previously parsed expression. + * + * @param const_values a zero terminated array of values for the identifiers from av_expr_parse() const_names + * @param opaque a pointer which will be passed to all functions from funcs1 and funcs2 + * @return the value of the expression + */ +double av_expr_eval(AVExpr *e, const double *const_values, void *opaque); + +/** + * Free a parsed expression previously created with av_expr_parse(). + */ +void av_expr_free(AVExpr *e); + +/** + * Parse the string in numstr and return its value as a double. If + * the string is empty, contains only whitespaces, or does not contain + * an initial substring that has the expected syntax for a + * floating-point number, no conversion is performed. In this case, + * returns a value of zero and the value returned in tail is the value + * of numstr. + * + * @param numstr a string representing a number, may contain one of + * the International System number postfixes, for example 'K', 'M', + * 'G'. If 'i' is appended after the postfix, powers of 2 are used + * instead of powers of 10. The 'B' postfix multiplies the value by + * 8, and can be appended after another postfix or used alone. This + * allows using for example 'KB', 'MiB', 'G' and 'B' as postfix. + * @param tail if non-NULL puts here the pointer to the char next + * after the last parsed character + */ +double av_strtod(const char *numstr, char **tail); + +#endif /* AVUTIL_EVAL_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/ffversion.h b/thrid-party/ffmpeg/include/libavutil/ffversion.h new file mode 100644 index 0000000000..f6deb6fe6b --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/ffversion.h @@ -0,0 +1,5 @@ +/* Automatically generated by version.sh, do not manually edit! */ +#ifndef AVUTIL_FFVERSION_H +#define AVUTIL_FFVERSION_H +#define FFMPEG_VERSION "3.4.1" +#endif /* AVUTIL_FFVERSION_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/fifo.h b/thrid-party/ffmpeg/include/libavutil/fifo.h new file mode 100644 index 0000000000..dc7bc6f0dd --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/fifo.h @@ -0,0 +1,179 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * a very simple circular buffer FIFO implementation + */ + +#ifndef AVUTIL_FIFO_H +#define AVUTIL_FIFO_H + +#include +#include "avutil.h" +#include "attributes.h" + +typedef struct AVFifoBuffer { + uint8_t *buffer; + uint8_t *rptr, *wptr, *end; + uint32_t rndx, wndx; +} AVFifoBuffer; + +/** + * Initialize an AVFifoBuffer. + * @param size of FIFO + * @return AVFifoBuffer or NULL in case of memory allocation failure + */ +AVFifoBuffer *av_fifo_alloc(unsigned int size); + +/** + * Initialize an AVFifoBuffer. + * @param nmemb number of elements + * @param size size of the single element + * @return AVFifoBuffer or NULL in case of memory allocation failure + */ +AVFifoBuffer *av_fifo_alloc_array(size_t nmemb, size_t size); + +/** + * Free an AVFifoBuffer. + * @param f AVFifoBuffer to free + */ +void av_fifo_free(AVFifoBuffer *f); + +/** + * Free an AVFifoBuffer and reset pointer to NULL. + * @param f AVFifoBuffer to free + */ +void av_fifo_freep(AVFifoBuffer **f); + +/** + * Reset the AVFifoBuffer to the state right after av_fifo_alloc, in particular it is emptied. + * @param f AVFifoBuffer to reset + */ +void av_fifo_reset(AVFifoBuffer *f); + +/** + * Return the amount of data in bytes in the AVFifoBuffer, that is the + * amount of data you can read from it. + * @param f AVFifoBuffer to read from + * @return size + */ +int av_fifo_size(const AVFifoBuffer *f); + +/** + * Return the amount of space in bytes in the AVFifoBuffer, that is the + * amount of data you can write into it. + * @param f AVFifoBuffer to write into + * @return size + */ +int av_fifo_space(const AVFifoBuffer *f); + +/** + * Feed data at specific position from an AVFifoBuffer to a user-supplied callback. + * Similar as av_fifo_gereric_read but without discarding data. + * @param f AVFifoBuffer to read from + * @param offset offset from current read position + * @param buf_size number of bytes to read + * @param func generic read function + * @param dest data destination + */ +int av_fifo_generic_peek_at(AVFifoBuffer *f, void *dest, int offset, int buf_size, void (*func)(void*, void*, int)); + +/** + * Feed data from an AVFifoBuffer to a user-supplied callback. + * Similar as av_fifo_gereric_read but without discarding data. + * @param f AVFifoBuffer to read from + * @param buf_size number of bytes to read + * @param func generic read function + * @param dest data destination + */ +int av_fifo_generic_peek(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int)); + +/** + * Feed data from an AVFifoBuffer to a user-supplied callback. + * @param f AVFifoBuffer to read from + * @param buf_size number of bytes to read + * @param func generic read function + * @param dest data destination + */ +int av_fifo_generic_read(AVFifoBuffer *f, void *dest, int buf_size, void (*func)(void*, void*, int)); + +/** + * Feed data from a user-supplied callback to an AVFifoBuffer. + * @param f AVFifoBuffer to write to + * @param src data source; non-const since it may be used as a + * modifiable context by the function defined in func + * @param size number of bytes to write + * @param func generic write function; the first parameter is src, + * the second is dest_buf, the third is dest_buf_size. + * func must return the number of bytes written to dest_buf, or <= 0 to + * indicate no more data available to write. + * If func is NULL, src is interpreted as a simple byte array for source data. + * @return the number of bytes written to the FIFO + */ +int av_fifo_generic_write(AVFifoBuffer *f, void *src, int size, int (*func)(void*, void*, int)); + +/** + * Resize an AVFifoBuffer. + * In case of reallocation failure, the old FIFO is kept unchanged. + * + * @param f AVFifoBuffer to resize + * @param size new AVFifoBuffer size in bytes + * @return <0 for failure, >=0 otherwise + */ +int av_fifo_realloc2(AVFifoBuffer *f, unsigned int size); + +/** + * Enlarge an AVFifoBuffer. + * In case of reallocation failure, the old FIFO is kept unchanged. + * The new fifo size may be larger than the requested size. + * + * @param f AVFifoBuffer to resize + * @param additional_space the amount of space in bytes to allocate in addition to av_fifo_size() + * @return <0 for failure, >=0 otherwise + */ +int av_fifo_grow(AVFifoBuffer *f, unsigned int additional_space); + +/** + * Read and discard the specified amount of data from an AVFifoBuffer. + * @param f AVFifoBuffer to read from + * @param size amount of data to read in bytes + */ +void av_fifo_drain(AVFifoBuffer *f, int size); + +/** + * Return a pointer to the data stored in a FIFO buffer at a certain offset. + * The FIFO buffer is not modified. + * + * @param f AVFifoBuffer to peek at, f must be non-NULL + * @param offs an offset in bytes, its absolute value must be less + * than the used buffer size or the returned pointer will + * point outside to the buffer data. + * The used buffer size can be checked with av_fifo_size(). + */ +static inline uint8_t *av_fifo_peek2(const AVFifoBuffer *f, int offs) +{ + uint8_t *ptr = f->rptr + offs; + if (ptr >= f->end) + ptr = f->buffer + (ptr - f->end); + else if (ptr < f->buffer) + ptr = f->end - (f->buffer - ptr); + return ptr; +} + +#endif /* AVUTIL_FIFO_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/file.h b/thrid-party/ffmpeg/include/libavutil/file.h new file mode 100644 index 0000000000..8666c7b1d5 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/file.h @@ -0,0 +1,69 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_FILE_H +#define AVUTIL_FILE_H + +#include + +#include "avutil.h" + +/** + * @file + * Misc file utilities. + */ + +/** + * Read the file with name filename, and put its content in a newly + * allocated buffer or map it with mmap() when available. + * In case of success set *bufptr to the read or mmapped buffer, and + * *size to the size in bytes of the buffer in *bufptr. + * The returned buffer must be released with av_file_unmap(). + * + * @param log_offset loglevel offset used for logging + * @param log_ctx context used for logging + * @return a non negative number in case of success, a negative value + * corresponding to an AVERROR error code in case of failure + */ +av_warn_unused_result +int av_file_map(const char *filename, uint8_t **bufptr, size_t *size, + int log_offset, void *log_ctx); + +/** + * Unmap or free the buffer bufptr created by av_file_map(). + * + * @param size size in bytes of bufptr, must be the same as returned + * by av_file_map() + */ +void av_file_unmap(uint8_t *bufptr, size_t size); + +/** + * Wrapper to work around the lack of mkstemp() on mingw. + * Also, tries to create file in /tmp first, if possible. + * *prefix can be a character constant; *filename will be allocated internally. + * @return file descriptor of opened file (or negative value corresponding to an + * AVERROR code on error) + * and opened file name in **filename. + * @note On very old libcs it is necessary to set a secure umask before + * calling this, av_tempfile() can't call umask itself as it is used in + * libraries and could interfere with the calling application. + * @deprecated as fd numbers cannot be passed saftely between libs on some platforms + */ +int av_tempfile(const char *prefix, char **filename, int log_offset, void *log_ctx); + +#endif /* AVUTIL_FILE_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/frame.h b/thrid-party/ffmpeg/include/libavutil/frame.h new file mode 100644 index 0000000000..abe4f4fd17 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/frame.h @@ -0,0 +1,821 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_frame + * reference-counted frame API + */ + +#ifndef AVUTIL_FRAME_H +#define AVUTIL_FRAME_H + +#include +#include + +#include "avutil.h" +#include "buffer.h" +#include "dict.h" +#include "rational.h" +#include "samplefmt.h" +#include "pixfmt.h" +#include "version.h" + + +/** + * @defgroup lavu_frame AVFrame + * @ingroup lavu_data + * + * @{ + * AVFrame is an abstraction for reference-counted raw multimedia data. + */ + +enum AVFrameSideDataType { + /** + * The data is the AVPanScan struct defined in libavcodec. + */ + AV_FRAME_DATA_PANSCAN, + /** + * ATSC A53 Part 4 Closed Captions. + * A53 CC bitstream is stored as uint8_t in AVFrameSideData.data. + * The number of bytes of CC data is AVFrameSideData.size. + */ + AV_FRAME_DATA_A53_CC, + /** + * Stereoscopic 3d metadata. + * The data is the AVStereo3D struct defined in libavutil/stereo3d.h. + */ + AV_FRAME_DATA_STEREO3D, + /** + * The data is the AVMatrixEncoding enum defined in libavutil/channel_layout.h. + */ + AV_FRAME_DATA_MATRIXENCODING, + /** + * Metadata relevant to a downmix procedure. + * The data is the AVDownmixInfo struct defined in libavutil/downmix_info.h. + */ + AV_FRAME_DATA_DOWNMIX_INFO, + /** + * ReplayGain information in the form of the AVReplayGain struct. + */ + AV_FRAME_DATA_REPLAYGAIN, + /** + * This side data contains a 3x3 transformation matrix describing an affine + * transformation that needs to be applied to the frame for correct + * presentation. + * + * See libavutil/display.h for a detailed description of the data. + */ + AV_FRAME_DATA_DISPLAYMATRIX, + /** + * Active Format Description data consisting of a single byte as specified + * in ETSI TS 101 154 using AVActiveFormatDescription enum. + */ + AV_FRAME_DATA_AFD, + /** + * Motion vectors exported by some codecs (on demand through the export_mvs + * flag set in the libavcodec AVCodecContext flags2 option). + * The data is the AVMotionVector struct defined in + * libavutil/motion_vector.h. + */ + AV_FRAME_DATA_MOTION_VECTORS, + /** + * Recommmends skipping the specified number of samples. This is exported + * only if the "skip_manual" AVOption is set in libavcodec. + * This has the same format as AV_PKT_DATA_SKIP_SAMPLES. + * @code + * u32le number of samples to skip from start of this packet + * u32le number of samples to skip from end of this packet + * u8 reason for start skip + * u8 reason for end skip (0=padding silence, 1=convergence) + * @endcode + */ + AV_FRAME_DATA_SKIP_SAMPLES, + /** + * This side data must be associated with an audio frame and corresponds to + * enum AVAudioServiceType defined in avcodec.h. + */ + AV_FRAME_DATA_AUDIO_SERVICE_TYPE, + /** + * Mastering display metadata associated with a video frame. The payload is + * an AVMasteringDisplayMetadata type and contains information about the + * mastering display color volume. + */ + AV_FRAME_DATA_MASTERING_DISPLAY_METADATA, + /** + * The GOP timecode in 25 bit timecode format. Data format is 64-bit integer. + * This is set on the first frame of a GOP that has a temporal reference of 0. + */ + AV_FRAME_DATA_GOP_TIMECODE, + + /** + * The data represents the AVSphericalMapping structure defined in + * libavutil/spherical.h. + */ + AV_FRAME_DATA_SPHERICAL, + + /** + * Content light level (based on CTA-861.3). This payload contains data in + * the form of the AVContentLightMetadata struct. + */ + AV_FRAME_DATA_CONTENT_LIGHT_LEVEL, + + /** + * The data contains an ICC profile as an opaque octet buffer following the + * format described by ISO 15076-1 with an optional name defined in the + * metadata key entry "name". + */ + AV_FRAME_DATA_ICC_PROFILE, +}; + +enum AVActiveFormatDescription { + AV_AFD_SAME = 8, + AV_AFD_4_3 = 9, + AV_AFD_16_9 = 10, + AV_AFD_14_9 = 11, + AV_AFD_4_3_SP_14_9 = 13, + AV_AFD_16_9_SP_14_9 = 14, + AV_AFD_SP_4_3 = 15, +}; + + +/** + * Structure to hold side data for an AVFrame. + * + * sizeof(AVFrameSideData) is not a part of the public ABI, so new fields may be added + * to the end with a minor bump. + */ +typedef struct AVFrameSideData { + enum AVFrameSideDataType type; + uint8_t *data; + int size; + AVDictionary *metadata; + AVBufferRef *buf; +} AVFrameSideData; + +/** + * This structure describes decoded (raw) audio or video data. + * + * AVFrame must be allocated using av_frame_alloc(). Note that this only + * allocates the AVFrame itself, the buffers for the data must be managed + * through other means (see below). + * AVFrame must be freed with av_frame_free(). + * + * AVFrame is typically allocated once and then reused multiple times to hold + * different data (e.g. a single AVFrame to hold frames received from a + * decoder). In such a case, av_frame_unref() will free any references held by + * the frame and reset it to its original clean state before it + * is reused again. + * + * The data described by an AVFrame is usually reference counted through the + * AVBuffer API. The underlying buffer references are stored in AVFrame.buf / + * AVFrame.extended_buf. An AVFrame is considered to be reference counted if at + * least one reference is set, i.e. if AVFrame.buf[0] != NULL. In such a case, + * every single data plane must be contained in one of the buffers in + * AVFrame.buf or AVFrame.extended_buf. + * There may be a single buffer for all the data, or one separate buffer for + * each plane, or anything in between. + * + * sizeof(AVFrame) is not a part of the public ABI, so new fields may be added + * to the end with a minor bump. + * + * Fields can be accessed through AVOptions, the name string used, matches the + * C structure field name for fields accessible through AVOptions. The AVClass + * for AVFrame can be obtained from avcodec_get_frame_class() + */ +typedef struct AVFrame { +#define AV_NUM_DATA_POINTERS 8 + /** + * pointer to the picture/channel planes. + * This might be different from the first allocated byte + * + * Some decoders access areas outside 0,0 - width,height, please + * see avcodec_align_dimensions2(). Some filters and swscale can read + * up to 16 bytes beyond the planes, if these filters are to be used, + * then 16 extra bytes must be allocated. + * + * NOTE: Except for hwaccel formats, pointers not needed by the format + * MUST be set to NULL. + */ + uint8_t *data[AV_NUM_DATA_POINTERS]; + + /** + * For video, size in bytes of each picture line. + * For audio, size in bytes of each plane. + * + * For audio, only linesize[0] may be set. For planar audio, each channel + * plane must be the same size. + * + * For video the linesizes should be multiples of the CPUs alignment + * preference, this is 16 or 32 for modern desktop CPUs. + * Some code requires such alignment other code can be slower without + * correct alignment, for yet other it makes no difference. + * + * @note The linesize may be larger than the size of usable data -- there + * may be extra padding present for performance reasons. + */ + int linesize[AV_NUM_DATA_POINTERS]; + + /** + * pointers to the data planes/channels. + * + * For video, this should simply point to data[]. + * + * For planar audio, each channel has a separate data pointer, and + * linesize[0] contains the size of each channel buffer. + * For packed audio, there is just one data pointer, and linesize[0] + * contains the total size of the buffer for all channels. + * + * Note: Both data and extended_data should always be set in a valid frame, + * but for planar audio with more channels that can fit in data, + * extended_data must be used in order to access all channels. + */ + uint8_t **extended_data; + + /** + * @name Video dimensions + * Video frames only. The coded dimensions (in pixels) of the video frame, + * i.e. the size of the rectangle that contains some well-defined values. + * + * @note The part of the frame intended for display/presentation is further + * restricted by the @ref cropping "Cropping rectangle". + * @{ + */ + int width, height; + /** + * @} + */ + + /** + * number of audio samples (per channel) described by this frame + */ + int nb_samples; + + /** + * format of the frame, -1 if unknown or unset + * Values correspond to enum AVPixelFormat for video frames, + * enum AVSampleFormat for audio) + */ + int format; + + /** + * 1 -> keyframe, 0-> not + */ + int key_frame; + + /** + * Picture type of the frame. + */ + enum AVPictureType pict_type; + + /** + * Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. + */ + AVRational sample_aspect_ratio; + + /** + * Presentation timestamp in time_base units (time when frame should be shown to user). + */ + int64_t pts; + +#if FF_API_PKT_PTS + /** + * PTS copied from the AVPacket that was decoded to produce this frame. + * @deprecated use the pts field instead + */ + attribute_deprecated + int64_t pkt_pts; +#endif + + /** + * DTS copied from the AVPacket that triggered returning this frame. (if frame threading isn't used) + * This is also the Presentation time of this AVFrame calculated from + * only AVPacket.dts values without pts values. + */ + int64_t pkt_dts; + + /** + * picture number in bitstream order + */ + int coded_picture_number; + /** + * picture number in display order + */ + int display_picture_number; + + /** + * quality (between 1 (good) and FF_LAMBDA_MAX (bad)) + */ + int quality; + + /** + * for some private data of the user + */ + void *opaque; + +#if FF_API_ERROR_FRAME + /** + * @deprecated unused + */ + attribute_deprecated + uint64_t error[AV_NUM_DATA_POINTERS]; +#endif + + /** + * When decoding, this signals how much the picture must be delayed. + * extra_delay = repeat_pict / (2*fps) + */ + int repeat_pict; + + /** + * The content of the picture is interlaced. + */ + int interlaced_frame; + + /** + * If the content is interlaced, is top field displayed first. + */ + int top_field_first; + + /** + * Tell user application that palette has changed from previous frame. + */ + int palette_has_changed; + + /** + * reordered opaque 64 bits (generally an integer or a double precision float + * PTS but can be anything). + * The user sets AVCodecContext.reordered_opaque to represent the input at + * that time, + * the decoder reorders values as needed and sets AVFrame.reordered_opaque + * to exactly one of the values provided by the user through AVCodecContext.reordered_opaque + * @deprecated in favor of pkt_pts + */ + int64_t reordered_opaque; + + /** + * Sample rate of the audio data. + */ + int sample_rate; + + /** + * Channel layout of the audio data. + */ + uint64_t channel_layout; + + /** + * AVBuffer references backing the data for this frame. If all elements of + * this array are NULL, then this frame is not reference counted. This array + * must be filled contiguously -- if buf[i] is non-NULL then buf[j] must + * also be non-NULL for all j < i. + * + * There may be at most one AVBuffer per data plane, so for video this array + * always contains all the references. For planar audio with more than + * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in + * this array. Then the extra AVBufferRef pointers are stored in the + * extended_buf array. + */ + AVBufferRef *buf[AV_NUM_DATA_POINTERS]; + + /** + * For planar audio which requires more than AV_NUM_DATA_POINTERS + * AVBufferRef pointers, this array will hold all the references which + * cannot fit into AVFrame.buf. + * + * Note that this is different from AVFrame.extended_data, which always + * contains all the pointers. This array only contains the extra pointers, + * which cannot fit into AVFrame.buf. + * + * This array is always allocated using av_malloc() by whoever constructs + * the frame. It is freed in av_frame_unref(). + */ + AVBufferRef **extended_buf; + /** + * Number of elements in extended_buf. + */ + int nb_extended_buf; + + AVFrameSideData **side_data; + int nb_side_data; + +/** + * @defgroup lavu_frame_flags AV_FRAME_FLAGS + * @ingroup lavu_frame + * Flags describing additional frame properties. + * + * @{ + */ + +/** + * The frame data may be corrupted, e.g. due to decoding errors. + */ +#define AV_FRAME_FLAG_CORRUPT (1 << 0) +/** + * A flag to mark the frames which need to be decoded, but shouldn't be output. + */ +#define AV_FRAME_FLAG_DISCARD (1 << 2) +/** + * @} + */ + + /** + * Frame flags, a combination of @ref lavu_frame_flags + */ + int flags; + + /** + * MPEG vs JPEG YUV range. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorRange color_range; + + enum AVColorPrimaries color_primaries; + + enum AVColorTransferCharacteristic color_trc; + + /** + * YUV colorspace type. + * - encoding: Set by user + * - decoding: Set by libavcodec + */ + enum AVColorSpace colorspace; + + enum AVChromaLocation chroma_location; + + /** + * frame timestamp estimated using various heuristics, in stream time base + * - encoding: unused + * - decoding: set by libavcodec, read by user. + */ + int64_t best_effort_timestamp; + + /** + * reordered pos from the last AVPacket that has been input into the decoder + * - encoding: unused + * - decoding: Read by user. + */ + int64_t pkt_pos; + + /** + * duration of the corresponding packet, expressed in + * AVStream->time_base units, 0 if unknown. + * - encoding: unused + * - decoding: Read by user. + */ + int64_t pkt_duration; + + /** + * metadata. + * - encoding: Set by user. + * - decoding: Set by libavcodec. + */ + AVDictionary *metadata; + + /** + * decode error flags of the frame, set to a combination of + * FF_DECODE_ERROR_xxx flags if the decoder produced a frame, but there + * were errors during the decoding. + * - encoding: unused + * - decoding: set by libavcodec, read by user. + */ + int decode_error_flags; +#define FF_DECODE_ERROR_INVALID_BITSTREAM 1 +#define FF_DECODE_ERROR_MISSING_REFERENCE 2 + + /** + * number of audio channels, only used for audio. + * - encoding: unused + * - decoding: Read by user. + */ + int channels; + + /** + * size of the corresponding packet containing the compressed + * frame. + * It is set to a negative value if unknown. + * - encoding: unused + * - decoding: set by libavcodec, read by user. + */ + int pkt_size; + +#if FF_API_FRAME_QP + /** + * QP table + */ + attribute_deprecated + int8_t *qscale_table; + /** + * QP store stride + */ + attribute_deprecated + int qstride; + + attribute_deprecated + int qscale_type; + + AVBufferRef *qp_table_buf; +#endif + /** + * For hwaccel-format frames, this should be a reference to the + * AVHWFramesContext describing the frame. + */ + AVBufferRef *hw_frames_ctx; + + /** + * AVBufferRef for free use by the API user. FFmpeg will never check the + * contents of the buffer ref. FFmpeg calls av_buffer_unref() on it when + * the frame is unreferenced. av_frame_copy_props() calls create a new + * reference with av_buffer_ref() for the target frame's opaque_ref field. + * + * This is unrelated to the opaque field, although it serves a similar + * purpose. + */ + AVBufferRef *opaque_ref; + + /** + * @anchor cropping + * @name Cropping + * Video frames only. The number of pixels to discard from the the + * top/bottom/left/right border of the frame to obtain the sub-rectangle of + * the frame intended for presentation. + * @{ + */ + size_t crop_top; + size_t crop_bottom; + size_t crop_left; + size_t crop_right; + /** + * @} + */ +} AVFrame; + +/** + * Accessors for some AVFrame fields. These used to be provided for ABI + * compatibility, and do not need to be used anymore. + */ +int64_t av_frame_get_best_effort_timestamp(const AVFrame *frame); +void av_frame_set_best_effort_timestamp(AVFrame *frame, int64_t val); +int64_t av_frame_get_pkt_duration (const AVFrame *frame); +void av_frame_set_pkt_duration (AVFrame *frame, int64_t val); +int64_t av_frame_get_pkt_pos (const AVFrame *frame); +void av_frame_set_pkt_pos (AVFrame *frame, int64_t val); +int64_t av_frame_get_channel_layout (const AVFrame *frame); +void av_frame_set_channel_layout (AVFrame *frame, int64_t val); +int av_frame_get_channels (const AVFrame *frame); +void av_frame_set_channels (AVFrame *frame, int val); +int av_frame_get_sample_rate (const AVFrame *frame); +void av_frame_set_sample_rate (AVFrame *frame, int val); +AVDictionary *av_frame_get_metadata (const AVFrame *frame); +void av_frame_set_metadata (AVFrame *frame, AVDictionary *val); +int av_frame_get_decode_error_flags (const AVFrame *frame); +void av_frame_set_decode_error_flags (AVFrame *frame, int val); +int av_frame_get_pkt_size(const AVFrame *frame); +void av_frame_set_pkt_size(AVFrame *frame, int val); +AVDictionary **avpriv_frame_get_metadatap(AVFrame *frame); +#if FF_API_FRAME_QP +int8_t *av_frame_get_qp_table(AVFrame *f, int *stride, int *type); +int av_frame_set_qp_table(AVFrame *f, AVBufferRef *buf, int stride, int type); +#endif +enum AVColorSpace av_frame_get_colorspace(const AVFrame *frame); +void av_frame_set_colorspace(AVFrame *frame, enum AVColorSpace val); +enum AVColorRange av_frame_get_color_range(const AVFrame *frame); +void av_frame_set_color_range(AVFrame *frame, enum AVColorRange val); + +/** + * Get the name of a colorspace. + * @return a static string identifying the colorspace; can be NULL. + */ +const char *av_get_colorspace_name(enum AVColorSpace val); + +/** + * Allocate an AVFrame and set its fields to default values. The resulting + * struct must be freed using av_frame_free(). + * + * @return An AVFrame filled with default values or NULL on failure. + * + * @note this only allocates the AVFrame itself, not the data buffers. Those + * must be allocated through other means, e.g. with av_frame_get_buffer() or + * manually. + */ +AVFrame *av_frame_alloc(void); + +/** + * Free the frame and any dynamically allocated objects in it, + * e.g. extended_data. If the frame is reference counted, it will be + * unreferenced first. + * + * @param frame frame to be freed. The pointer will be set to NULL. + */ +void av_frame_free(AVFrame **frame); + +/** + * Set up a new reference to the data described by the source frame. + * + * Copy frame properties from src to dst and create a new reference for each + * AVBufferRef from src. + * + * If src is not reference counted, new buffers are allocated and the data is + * copied. + * + * @warning: dst MUST have been either unreferenced with av_frame_unref(dst), + * or newly allocated with av_frame_alloc() before calling this + * function, or undefined behavior will occur. + * + * @return 0 on success, a negative AVERROR on error + */ +int av_frame_ref(AVFrame *dst, const AVFrame *src); + +/** + * Create a new frame that references the same data as src. + * + * This is a shortcut for av_frame_alloc()+av_frame_ref(). + * + * @return newly created AVFrame on success, NULL on error. + */ +AVFrame *av_frame_clone(const AVFrame *src); + +/** + * Unreference all the buffers referenced by frame and reset the frame fields. + */ +void av_frame_unref(AVFrame *frame); + +/** + * Move everything contained in src to dst and reset src. + * + * @warning: dst is not unreferenced, but directly overwritten without reading + * or deallocating its contents. Call av_frame_unref(dst) manually + * before calling this function to ensure that no memory is leaked. + */ +void av_frame_move_ref(AVFrame *dst, AVFrame *src); + +/** + * Allocate new buffer(s) for audio or video data. + * + * The following fields must be set on frame before calling this function: + * - format (pixel format for video, sample format for audio) + * - width and height for video + * - nb_samples and channel_layout for audio + * + * This function will fill AVFrame.data and AVFrame.buf arrays and, if + * necessary, allocate and fill AVFrame.extended_data and AVFrame.extended_buf. + * For planar formats, one buffer will be allocated for each plane. + * + * @warning: if frame already has been allocated, calling this function will + * leak memory. In addition, undefined behavior can occur in certain + * cases. + * + * @param frame frame in which to store the new buffers. + * @param align Required buffer size alignment. If equal to 0, alignment will be + * chosen automatically for the current CPU. It is highly + * recommended to pass 0 here unless you know what you are doing. + * + * @return 0 on success, a negative AVERROR on error. + */ +int av_frame_get_buffer(AVFrame *frame, int align); + +/** + * Check if the frame data is writable. + * + * @return A positive value if the frame data is writable (which is true if and + * only if each of the underlying buffers has only one reference, namely the one + * stored in this frame). Return 0 otherwise. + * + * If 1 is returned the answer is valid until av_buffer_ref() is called on any + * of the underlying AVBufferRefs (e.g. through av_frame_ref() or directly). + * + * @see av_frame_make_writable(), av_buffer_is_writable() + */ +int av_frame_is_writable(AVFrame *frame); + +/** + * Ensure that the frame data is writable, avoiding data copy if possible. + * + * Do nothing if the frame is writable, allocate new buffers and copy the data + * if it is not. + * + * @return 0 on success, a negative AVERROR on error. + * + * @see av_frame_is_writable(), av_buffer_is_writable(), + * av_buffer_make_writable() + */ +int av_frame_make_writable(AVFrame *frame); + +/** + * Copy the frame data from src to dst. + * + * This function does not allocate anything, dst must be already initialized and + * allocated with the same parameters as src. + * + * This function only copies the frame data (i.e. the contents of the data / + * extended data arrays), not any other properties. + * + * @return >= 0 on success, a negative AVERROR on error. + */ +int av_frame_copy(AVFrame *dst, const AVFrame *src); + +/** + * Copy only "metadata" fields from src to dst. + * + * Metadata for the purpose of this function are those fields that do not affect + * the data layout in the buffers. E.g. pts, sample rate (for audio) or sample + * aspect ratio (for video), but not width/height or channel layout. + * Side data is also copied. + */ +int av_frame_copy_props(AVFrame *dst, const AVFrame *src); + +/** + * Get the buffer reference a given data plane is stored in. + * + * @param plane index of the data plane of interest in frame->extended_data. + * + * @return the buffer reference that contains the plane or NULL if the input + * frame is not valid. + */ +AVBufferRef *av_frame_get_plane_buffer(AVFrame *frame, int plane); + +/** + * Add a new side data to a frame. + * + * @param frame a frame to which the side data should be added + * @param type type of the added side data + * @param size size of the side data + * + * @return newly added side data on success, NULL on error + */ +AVFrameSideData *av_frame_new_side_data(AVFrame *frame, + enum AVFrameSideDataType type, + int size); + +/** + * @return a pointer to the side data of a given type on success, NULL if there + * is no side data with such type in this frame. + */ +AVFrameSideData *av_frame_get_side_data(const AVFrame *frame, + enum AVFrameSideDataType type); + +/** + * If side data of the supplied type exists in the frame, free it and remove it + * from the frame. + */ +void av_frame_remove_side_data(AVFrame *frame, enum AVFrameSideDataType type); + + +/** + * Flags for frame cropping. + */ +enum { + /** + * Apply the maximum possible cropping, even if it requires setting the + * AVFrame.data[] entries to unaligned pointers. Passing unaligned data + * to FFmpeg API is generally not allowed, and causes undefined behavior + * (such as crashes). You can pass unaligned data only to FFmpeg APIs that + * are explicitly documented to accept it. Use this flag only if you + * absolutely know what you are doing. + */ + AV_FRAME_CROP_UNALIGNED = 1 << 0, +}; + +/** + * Crop the given video AVFrame according to its crop_left/crop_top/crop_right/ + * crop_bottom fields. If cropping is successful, the function will adjust the + * data pointers and the width/height fields, and set the crop fields to 0. + * + * In all cases, the cropping boundaries will be rounded to the inherent + * alignment of the pixel format. In some cases, such as for opaque hwaccel + * formats, the left/top cropping is ignored. The crop fields are set to 0 even + * if the cropping was rounded or ignored. + * + * @param frame the frame which should be cropped + * @param flags Some combination of AV_FRAME_CROP_* flags, or 0. + * + * @return >= 0 on success, a negative AVERROR on error. If the cropping fields + * were invalid, AVERROR(ERANGE) is returned, and nothing is changed. + */ +int av_frame_apply_cropping(AVFrame *frame, int flags); + +/** + * @return a string identifying the side data type + */ +const char *av_frame_side_data_name(enum AVFrameSideDataType type); + +/** + * @} + */ + +#endif /* AVUTIL_FRAME_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hash.h b/thrid-party/ffmpeg/include/libavutil/hash.h new file mode 100644 index 0000000000..a20b8934f1 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hash.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2013 Reimar Döffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_hash_generic + * Generic hashing API + */ + +#ifndef AVUTIL_HASH_H +#define AVUTIL_HASH_H + +#include + +/** + * @defgroup lavu_hash Hash Functions + * @ingroup lavu_crypto + * Hash functions useful in multimedia. + * + * Hash functions are widely used in multimedia, from error checking and + * concealment to internal regression testing. libavutil has efficient + * implementations of a variety of hash functions that may be useful for + * FFmpeg and other multimedia applications. + * + * @{ + * + * @defgroup lavu_hash_generic Generic Hashing API + * An abstraction layer for all hash functions supported by libavutil. + * + * If your application needs to support a wide range of different hash + * functions, then the Generic Hashing API is for you. It provides a generic, + * reusable API for @ref lavu_hash "all hash functions" implemented in libavutil. + * If you just need to use one particular hash function, use the @ref lavu_hash + * "individual hash" directly. + * + * @section Sample Code + * + * A basic template for using the Generic Hashing API follows: + * + * @code + * struct AVHashContext *ctx = NULL; + * const char *hash_name = NULL; + * uint8_t *output_buf = NULL; + * + * // Select from a string returned by av_hash_names() + * hash_name = ...; + * + * // Allocate a hash context + * ret = av_hash_alloc(&ctx, hash_name); + * if (ret < 0) + * return ret; + * + * // Initialize the hash context + * av_hash_init(ctx); + * + * // Update the hash context with data + * while (data_left) { + * av_hash_update(ctx, data, size); + * } + * + * // Now we have no more data, so it is time to finalize the hash and get the + * // output. But we need to first allocate an output buffer. Note that you can + * // use any memory allocation function, including malloc(), not just + * // av_malloc(). + * output_buf = av_malloc(av_hash_get_size(ctx)); + * if (!output_buf) + * return AVERROR(ENOMEM); + * + * // Finalize the hash context. + * // You can use any of the av_hash_final*() functions provided, for other + * // output formats. If you do so, be sure to adjust the memory allocation + * // above. See the function documentation below for the exact amount of extra + * // memory needed. + * av_hash_final(ctx, output_buffer); + * + * // Free the context + * av_hash_freep(&ctx); + * @endcode + * + * @section Hash Function-Specific Information + * If the CRC32 hash is selected, the #AV_CRC_32_IEEE polynomial will be + * used. + * + * If the Murmur3 hash is selected, the default seed will be used. See @ref + * lavu_murmur3_seedinfo "Murmur3" for more information. + * + * @{ + */ + +/** + * @example ffhash.c + * This example is a simple command line application that takes one or more + * arguments. It demonstrates a typical use of the hashing API with allocation, + * initialization, updating, and finalizing. + */ + +struct AVHashContext; + +/** + * Allocate a hash context for the algorithm specified by name. + * + * @return >= 0 for success, a negative error code for failure + * + * @note The context is not initialized after a call to this function; you must + * call av_hash_init() to do so. + */ +int av_hash_alloc(struct AVHashContext **ctx, const char *name); + +/** + * Get the names of available hash algorithms. + * + * This function can be used to enumerate the algorithms. + * + * @param[in] i Index of the hash algorithm, starting from 0 + * @return Pointer to a static string or `NULL` if `i` is out of range + */ +const char *av_hash_names(int i); + +/** + * Get the name of the algorithm corresponding to the given hash context. + */ +const char *av_hash_get_name(const struct AVHashContext *ctx); + +/** + * Maximum value that av_hash_get_size() will currently return. + * + * You can use this if you absolutely want or need to use static allocation for + * the output buffer and are fine with not supporting hashes newly added to + * libavutil without recompilation. + * + * @warning + * Adding new hashes with larger sizes, and increasing the macro while doing + * so, will not be considered an ABI change. To prevent your code from + * overflowing a buffer, either dynamically allocate the output buffer with + * av_hash_get_size(), or limit your use of the Hashing API to hashes that are + * already in FFmpeg during the time of compilation. + */ +#define AV_HASH_MAX_SIZE 64 + +/** + * Get the size of the resulting hash value in bytes. + * + * The maximum value this function will currently return is available as macro + * #AV_HASH_MAX_SIZE. + * + * @param[in] ctx Hash context + * @return Size of the hash value in bytes + */ +int av_hash_get_size(const struct AVHashContext *ctx); + +/** + * Initialize or reset a hash context. + * + * @param[in,out] ctx Hash context + */ +void av_hash_init(struct AVHashContext *ctx); + +/** + * Update a hash context with additional data. + * + * @param[in,out] ctx Hash context + * @param[in] src Data to be added to the hash context + * @param[in] len Size of the additional data + */ +void av_hash_update(struct AVHashContext *ctx, const uint8_t *src, int len); + +/** + * Finalize a hash context and compute the actual hash value. + * + * The minimum size of `dst` buffer is given by av_hash_get_size() or + * #AV_HASH_MAX_SIZE. The use of the latter macro is discouraged. + * + * It is not safe to update or finalize a hash context again, if it has already + * been finalized. + * + * @param[in,out] ctx Hash context + * @param[out] dst Where the final hash value will be stored + * + * @see av_hash_final_bin() provides an alternative API + */ +void av_hash_final(struct AVHashContext *ctx, uint8_t *dst); + +/** + * Finalize a hash context and store the actual hash value in a buffer. + * + * It is not safe to update or finalize a hash context again, if it has already + * been finalized. + * + * If `size` is smaller than the hash size (given by av_hash_get_size()), the + * hash is truncated; if size is larger, the buffer is padded with 0. + * + * @param[in,out] ctx Hash context + * @param[out] dst Where the final hash value will be stored + * @param[in] size Number of bytes to write to `dst` + */ +void av_hash_final_bin(struct AVHashContext *ctx, uint8_t *dst, int size); + +/** + * Finalize a hash context and store the hexadecimal representation of the + * actual hash value as a string. + * + * It is not safe to update or finalize a hash context again, if it has already + * been finalized. + * + * The string is always 0-terminated. + * + * If `size` is smaller than `2 * hash_size + 1`, where `hash_size` is the + * value returned by av_hash_get_size(), the string will be truncated. + * + * @param[in,out] ctx Hash context + * @param[out] dst Where the string will be stored + * @param[in] size Maximum number of bytes to write to `dst` + */ +void av_hash_final_hex(struct AVHashContext *ctx, uint8_t *dst, int size); + +/** + * Finalize a hash context and store the Base64 representation of the + * actual hash value as a string. + * + * It is not safe to update or finalize a hash context again, if it has already + * been finalized. + * + * The string is always 0-terminated. + * + * If `size` is smaller than AV_BASE64_SIZE(hash_size), where `hash_size` is + * the value returned by av_hash_get_size(), the string will be truncated. + * + * @param[in,out] ctx Hash context + * @param[out] dst Where the final hash value will be stored + * @param[in] size Maximum number of bytes to write to `dst` + */ +void av_hash_final_b64(struct AVHashContext *ctx, uint8_t *dst, int size); + +/** + * Free hash context and set hash context pointer to `NULL`. + * + * @param[in,out] ctx Pointer to hash context + */ +void av_hash_freep(struct AVHashContext **ctx); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_HASH_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hmac.h b/thrid-party/ffmpeg/include/libavutil/hmac.h new file mode 100644 index 0000000000..576a0a4fb9 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hmac.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2012 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HMAC_H +#define AVUTIL_HMAC_H + +#include + +#include "version.h" +/** + * @defgroup lavu_hmac HMAC + * @ingroup lavu_crypto + * @{ + */ + +enum AVHMACType { + AV_HMAC_MD5, + AV_HMAC_SHA1, + AV_HMAC_SHA224, + AV_HMAC_SHA256, + AV_HMAC_SHA384 = 12, + AV_HMAC_SHA512, +}; + +typedef struct AVHMAC AVHMAC; + +/** + * Allocate an AVHMAC context. + * @param type The hash function used for the HMAC. + */ +AVHMAC *av_hmac_alloc(enum AVHMACType type); + +/** + * Free an AVHMAC context. + * @param ctx The context to free, may be NULL + */ +void av_hmac_free(AVHMAC *ctx); + +/** + * Initialize an AVHMAC context with an authentication key. + * @param ctx The HMAC context + * @param key The authentication key + * @param keylen The length of the key, in bytes + */ +void av_hmac_init(AVHMAC *ctx, const uint8_t *key, unsigned int keylen); + +/** + * Hash data with the HMAC. + * @param ctx The HMAC context + * @param data The data to hash + * @param len The length of the data, in bytes + */ +void av_hmac_update(AVHMAC *ctx, const uint8_t *data, unsigned int len); + +/** + * Finish hashing and output the HMAC digest. + * @param ctx The HMAC context + * @param out The output buffer to write the digest into + * @param outlen The length of the out buffer, in bytes + * @return The number of bytes written to out, or a negative error code. + */ +int av_hmac_final(AVHMAC *ctx, uint8_t *out, unsigned int outlen); + +/** + * Hash an array of data with a key. + * @param ctx The HMAC context + * @param data The data to hash + * @param len The length of the data, in bytes + * @param key The authentication key + * @param keylen The length of the key, in bytes + * @param out The output buffer to write the digest into + * @param outlen The length of the out buffer, in bytes + * @return The number of bytes written to out, or a negative error code. + */ +int av_hmac_calc(AVHMAC *ctx, const uint8_t *data, unsigned int len, + const uint8_t *key, unsigned int keylen, + uint8_t *out, unsigned int outlen); + +/** + * @} + */ + +#endif /* AVUTIL_HMAC_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext.h b/thrid-party/ffmpeg/include/libavutil/hwcontext.h new file mode 100644 index 0000000000..03334e20e0 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext.h @@ -0,0 +1,582 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_H +#define AVUTIL_HWCONTEXT_H + +#include "buffer.h" +#include "frame.h" +#include "log.h" +#include "pixfmt.h" + +enum AVHWDeviceType { + AV_HWDEVICE_TYPE_VDPAU, + AV_HWDEVICE_TYPE_CUDA, + AV_HWDEVICE_TYPE_VAAPI, + AV_HWDEVICE_TYPE_DXVA2, + AV_HWDEVICE_TYPE_QSV, + AV_HWDEVICE_TYPE_VIDEOTOOLBOX, + AV_HWDEVICE_TYPE_NONE, + AV_HWDEVICE_TYPE_D3D11VA, + AV_HWDEVICE_TYPE_DRM, +}; + +typedef struct AVHWDeviceInternal AVHWDeviceInternal; + +/** + * This struct aggregates all the (hardware/vendor-specific) "high-level" state, + * i.e. state that is not tied to a concrete processing configuration. + * E.g., in an API that supports hardware-accelerated encoding and decoding, + * this struct will (if possible) wrap the state that is common to both encoding + * and decoding and from which specific instances of encoders or decoders can be + * derived. + * + * This struct is reference-counted with the AVBuffer mechanism. The + * av_hwdevice_ctx_alloc() constructor yields a reference, whose data field + * points to the actual AVHWDeviceContext. Further objects derived from + * AVHWDeviceContext (such as AVHWFramesContext, describing a frame pool with + * specific properties) will hold an internal reference to it. After all the + * references are released, the AVHWDeviceContext itself will be freed, + * optionally invoking a user-specified callback for uninitializing the hardware + * state. + */ +typedef struct AVHWDeviceContext { + /** + * A class for logging. Set by av_hwdevice_ctx_alloc(). + */ + const AVClass *av_class; + + /** + * Private data used internally by libavutil. Must not be accessed in any + * way by the caller. + */ + AVHWDeviceInternal *internal; + + /** + * This field identifies the underlying API used for hardware access. + * + * This field is set when this struct is allocated and never changed + * afterwards. + */ + enum AVHWDeviceType type; + + /** + * The format-specific data, allocated and freed by libavutil along with + * this context. + * + * Should be cast by the user to the format-specific context defined in the + * corresponding header (hwcontext_*.h) and filled as described in the + * documentation before calling av_hwdevice_ctx_init(). + * + * After calling av_hwdevice_ctx_init() this struct should not be modified + * by the caller. + */ + void *hwctx; + + /** + * This field may be set by the caller before calling av_hwdevice_ctx_init(). + * + * If non-NULL, this callback will be called when the last reference to + * this context is unreferenced, immediately before it is freed. + * + * @note when other objects (e.g an AVHWFramesContext) are derived from this + * struct, this callback will be invoked after all such child objects + * are fully uninitialized and their respective destructors invoked. + */ + void (*free)(struct AVHWDeviceContext *ctx); + + /** + * Arbitrary user data, to be used e.g. by the free() callback. + */ + void *user_opaque; +} AVHWDeviceContext; + +typedef struct AVHWFramesInternal AVHWFramesInternal; + +/** + * This struct describes a set or pool of "hardware" frames (i.e. those with + * data not located in normal system memory). All the frames in the pool are + * assumed to be allocated in the same way and interchangeable. + * + * This struct is reference-counted with the AVBuffer mechanism and tied to a + * given AVHWDeviceContext instance. The av_hwframe_ctx_alloc() constructor + * yields a reference, whose data field points to the actual AVHWFramesContext + * struct. + */ +typedef struct AVHWFramesContext { + /** + * A class for logging. + */ + const AVClass *av_class; + + /** + * Private data used internally by libavutil. Must not be accessed in any + * way by the caller. + */ + AVHWFramesInternal *internal; + + /** + * A reference to the parent AVHWDeviceContext. This reference is owned and + * managed by the enclosing AVHWFramesContext, but the caller may derive + * additional references from it. + */ + AVBufferRef *device_ref; + + /** + * The parent AVHWDeviceContext. This is simply a pointer to + * device_ref->data provided for convenience. + * + * Set by libavutil in av_hwframe_ctx_init(). + */ + AVHWDeviceContext *device_ctx; + + /** + * The format-specific data, allocated and freed automatically along with + * this context. + * + * Should be cast by the user to the format-specific context defined in the + * corresponding header (hwframe_*.h) and filled as described in the + * documentation before calling av_hwframe_ctx_init(). + * + * After any frames using this context are created, the contents of this + * struct should not be modified by the caller. + */ + void *hwctx; + + /** + * This field may be set by the caller before calling av_hwframe_ctx_init(). + * + * If non-NULL, this callback will be called when the last reference to + * this context is unreferenced, immediately before it is freed. + */ + void (*free)(struct AVHWFramesContext *ctx); + + /** + * Arbitrary user data, to be used e.g. by the free() callback. + */ + void *user_opaque; + + /** + * A pool from which the frames are allocated by av_hwframe_get_buffer(). + * This field may be set by the caller before calling av_hwframe_ctx_init(). + * The buffers returned by calling av_buffer_pool_get() on this pool must + * have the properties described in the documentation in the corresponding hw + * type's header (hwcontext_*.h). The pool will be freed strictly before + * this struct's free() callback is invoked. + * + * This field may be NULL, then libavutil will attempt to allocate a pool + * internally. Note that certain device types enforce pools allocated at + * fixed size (frame count), which cannot be extended dynamically. In such a + * case, initial_pool_size must be set appropriately. + */ + AVBufferPool *pool; + + /** + * Initial size of the frame pool. If a device type does not support + * dynamically resizing the pool, then this is also the maximum pool size. + * + * May be set by the caller before calling av_hwframe_ctx_init(). Must be + * set if pool is NULL and the device type does not support dynamic pools. + */ + int initial_pool_size; + + /** + * The pixel format identifying the underlying HW surface type. + * + * Must be a hwaccel format, i.e. the corresponding descriptor must have the + * AV_PIX_FMT_FLAG_HWACCEL flag set. + * + * Must be set by the user before calling av_hwframe_ctx_init(). + */ + enum AVPixelFormat format; + + /** + * The pixel format identifying the actual data layout of the hardware + * frames. + * + * Must be set by the caller before calling av_hwframe_ctx_init(). + * + * @note when the underlying API does not provide the exact data layout, but + * only the colorspace/bit depth, this field should be set to the fully + * planar version of that format (e.g. for 8-bit 420 YUV it should be + * AV_PIX_FMT_YUV420P, not AV_PIX_FMT_NV12 or anything else). + */ + enum AVPixelFormat sw_format; + + /** + * The allocated dimensions of the frames in this pool. + * + * Must be set by the user before calling av_hwframe_ctx_init(). + */ + int width, height; +} AVHWFramesContext; + +/** + * Look up an AVHWDeviceType by name. + * + * @param name String name of the device type (case-insensitive). + * @return The type from enum AVHWDeviceType, or AV_HWDEVICE_TYPE_NONE if + * not found. + */ +enum AVHWDeviceType av_hwdevice_find_type_by_name(const char *name); + +/** Get the string name of an AVHWDeviceType. + * + * @param type Type from enum AVHWDeviceType. + * @return Pointer to a static string containing the name, or NULL if the type + * is not valid. + */ +const char *av_hwdevice_get_type_name(enum AVHWDeviceType type); + +/** + * Iterate over supported device types. + * + * @param type AV_HWDEVICE_TYPE_NONE initially, then the previous type + * returned by this function in subsequent iterations. + * @return The next usable device type from enum AVHWDeviceType, or + * AV_HWDEVICE_TYPE_NONE if there are no more. + */ +enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev); + +/** + * Allocate an AVHWDeviceContext for a given hardware type. + * + * @param type the type of the hardware device to allocate. + * @return a reference to the newly created AVHWDeviceContext on success or NULL + * on failure. + */ +AVBufferRef *av_hwdevice_ctx_alloc(enum AVHWDeviceType type); + +/** + * Finalize the device context before use. This function must be called after + * the context is filled with all the required information and before it is + * used in any way. + * + * @param ref a reference to the AVHWDeviceContext + * @return 0 on success, a negative AVERROR code on failure + */ +int av_hwdevice_ctx_init(AVBufferRef *ref); + +/** + * Open a device of the specified type and create an AVHWDeviceContext for it. + * + * This is a convenience function intended to cover the simple cases. Callers + * who need to fine-tune device creation/management should open the device + * manually and then wrap it in an AVHWDeviceContext using + * av_hwdevice_ctx_alloc()/av_hwdevice_ctx_init(). + * + * The returned context is already initialized and ready for use, the caller + * should not call av_hwdevice_ctx_init() on it. The user_opaque/free fields of + * the created AVHWDeviceContext are set by this function and should not be + * touched by the caller. + * + * @param device_ctx On success, a reference to the newly-created device context + * will be written here. The reference is owned by the caller + * and must be released with av_buffer_unref() when no longer + * needed. On failure, NULL will be written to this pointer. + * @param type The type of the device to create. + * @param device A type-specific string identifying the device to open. + * @param opts A dictionary of additional (type-specific) options to use in + * opening the device. The dictionary remains owned by the caller. + * @param flags currently unused + * + * @return 0 on success, a negative AVERROR code on failure. + */ +int av_hwdevice_ctx_create(AVBufferRef **device_ctx, enum AVHWDeviceType type, + const char *device, AVDictionary *opts, int flags); + +/** + * Create a new device of the specified type from an existing device. + * + * If the source device is a device of the target type or was originally + * derived from such a device (possibly through one or more intermediate + * devices of other types), then this will return a reference to the + * existing device of the same type as is requested. + * + * Otherwise, it will attempt to derive a new device from the given source + * device. If direct derivation to the new type is not implemented, it will + * attempt the same derivation from each ancestor of the source device in + * turn looking for an implemented derivation method. + * + * @param dst_ctx On success, a reference to the newly-created + * AVHWDeviceContext. + * @param type The type of the new device to create. + * @param src_ctx A reference to an existing AVHWDeviceContext which will be + * used to create the new device. + * @param flags Currently unused; should be set to zero. + * @return Zero on success, a negative AVERROR code on failure. + */ +int av_hwdevice_ctx_create_derived(AVBufferRef **dst_ctx, + enum AVHWDeviceType type, + AVBufferRef *src_ctx, int flags); + + +/** + * Allocate an AVHWFramesContext tied to a given device context. + * + * @param device_ctx a reference to a AVHWDeviceContext. This function will make + * a new reference for internal use, the one passed to the + * function remains owned by the caller. + * @return a reference to the newly created AVHWFramesContext on success or NULL + * on failure. + */ +AVBufferRef *av_hwframe_ctx_alloc(AVBufferRef *device_ctx); + +/** + * Finalize the context before use. This function must be called after the + * context is filled with all the required information and before it is attached + * to any frames. + * + * @param ref a reference to the AVHWFramesContext + * @return 0 on success, a negative AVERROR code on failure + */ +int av_hwframe_ctx_init(AVBufferRef *ref); + +/** + * Allocate a new frame attached to the given AVHWFramesContext. + * + * @param hwframe_ctx a reference to an AVHWFramesContext + * @param frame an empty (freshly allocated or unreffed) frame to be filled with + * newly allocated buffers. + * @param flags currently unused, should be set to zero + * @return 0 on success, a negative AVERROR code on failure + */ +int av_hwframe_get_buffer(AVBufferRef *hwframe_ctx, AVFrame *frame, int flags); + +/** + * Copy data to or from a hw surface. At least one of dst/src must have an + * AVHWFramesContext attached. + * + * If src has an AVHWFramesContext attached, then the format of dst (if set) + * must use one of the formats returned by av_hwframe_transfer_get_formats(src, + * AV_HWFRAME_TRANSFER_DIRECTION_FROM). + * If dst has an AVHWFramesContext attached, then the format of src must use one + * of the formats returned by av_hwframe_transfer_get_formats(dst, + * AV_HWFRAME_TRANSFER_DIRECTION_TO) + * + * dst may be "clean" (i.e. with data/buf pointers unset), in which case the + * data buffers will be allocated by this function using av_frame_get_buffer(). + * If dst->format is set, then this format will be used, otherwise (when + * dst->format is AV_PIX_FMT_NONE) the first acceptable format will be chosen. + * + * The two frames must have matching allocated dimensions (i.e. equal to + * AVHWFramesContext.width/height), since not all device types support + * transferring a sub-rectangle of the whole surface. The display dimensions + * (i.e. AVFrame.width/height) may be smaller than the allocated dimensions, but + * also have to be equal for both frames. When the display dimensions are + * smaller than the allocated dimensions, the content of the padding in the + * destination frame is unspecified. + * + * @param dst the destination frame. dst is not touched on failure. + * @param src the source frame. + * @param flags currently unused, should be set to zero + * @return 0 on success, a negative AVERROR error code on failure. + */ +int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags); + +enum AVHWFrameTransferDirection { + /** + * Transfer the data from the queried hw frame. + */ + AV_HWFRAME_TRANSFER_DIRECTION_FROM, + + /** + * Transfer the data to the queried hw frame. + */ + AV_HWFRAME_TRANSFER_DIRECTION_TO, +}; + +/** + * Get a list of possible source or target formats usable in + * av_hwframe_transfer_data(). + * + * @param hwframe_ctx the frame context to obtain the information for + * @param dir the direction of the transfer + * @param formats the pointer to the output format list will be written here. + * The list is terminated with AV_PIX_FMT_NONE and must be freed + * by the caller when no longer needed using av_free(). + * If this function returns successfully, the format list will + * have at least one item (not counting the terminator). + * On failure, the contents of this pointer are unspecified. + * @param flags currently unused, should be set to zero + * @return 0 on success, a negative AVERROR code on failure. + */ +int av_hwframe_transfer_get_formats(AVBufferRef *hwframe_ctx, + enum AVHWFrameTransferDirection dir, + enum AVPixelFormat **formats, int flags); + + +/** + * This struct describes the constraints on hardware frames attached to + * a given device with a hardware-specific configuration. This is returned + * by av_hwdevice_get_hwframe_constraints() and must be freed by + * av_hwframe_constraints_free() after use. + */ +typedef struct AVHWFramesConstraints { + /** + * A list of possible values for format in the hw_frames_ctx, + * terminated by AV_PIX_FMT_NONE. This member will always be filled. + */ + enum AVPixelFormat *valid_hw_formats; + + /** + * A list of possible values for sw_format in the hw_frames_ctx, + * terminated by AV_PIX_FMT_NONE. Can be NULL if this information is + * not known. + */ + enum AVPixelFormat *valid_sw_formats; + + /** + * The minimum size of frames in this hw_frames_ctx. + * (Zero if not known.) + */ + int min_width; + int min_height; + + /** + * The maximum size of frames in this hw_frames_ctx. + * (INT_MAX if not known / no limit.) + */ + int max_width; + int max_height; +} AVHWFramesConstraints; + +/** + * Allocate a HW-specific configuration structure for a given HW device. + * After use, the user must free all members as required by the specific + * hardware structure being used, then free the structure itself with + * av_free(). + * + * @param device_ctx a reference to the associated AVHWDeviceContext. + * @return The newly created HW-specific configuration structure on + * success or NULL on failure. + */ +void *av_hwdevice_hwconfig_alloc(AVBufferRef *device_ctx); + +/** + * Get the constraints on HW frames given a device and the HW-specific + * configuration to be used with that device. If no HW-specific + * configuration is provided, returns the maximum possible capabilities + * of the device. + * + * @param ref a reference to the associated AVHWDeviceContext. + * @param hwconfig a filled HW-specific configuration structure, or NULL + * to return the maximum possible capabilities of the device. + * @return AVHWFramesConstraints structure describing the constraints + * on the device, or NULL if not available. + */ +AVHWFramesConstraints *av_hwdevice_get_hwframe_constraints(AVBufferRef *ref, + const void *hwconfig); + +/** + * Free an AVHWFrameConstraints structure. + * + * @param constraints The (filled or unfilled) AVHWFrameConstraints structure. + */ +void av_hwframe_constraints_free(AVHWFramesConstraints **constraints); + + +/** + * Flags to apply to frame mappings. + */ +enum { + /** + * The mapping must be readable. + */ + AV_HWFRAME_MAP_READ = 1 << 0, + /** + * The mapping must be writeable. + */ + AV_HWFRAME_MAP_WRITE = 1 << 1, + /** + * The mapped frame will be overwritten completely in subsequent + * operations, so the current frame data need not be loaded. Any values + * which are not overwritten are unspecified. + */ + AV_HWFRAME_MAP_OVERWRITE = 1 << 2, + /** + * The mapping must be direct. That is, there must not be any copying in + * the map or unmap steps. Note that performance of direct mappings may + * be much lower than normal memory. + */ + AV_HWFRAME_MAP_DIRECT = 1 << 3, +}; + +/** + * Map a hardware frame. + * + * This has a number of different possible effects, depending on the format + * and origin of the src and dst frames. On input, src should be a usable + * frame with valid buffers and dst should be blank (typically as just created + * by av_frame_alloc()). src should have an associated hwframe context, and + * dst may optionally have a format and associated hwframe context. + * + * If src was created by mapping a frame from the hwframe context of dst, + * then this function undoes the mapping - dst is replaced by a reference to + * the frame that src was originally mapped from. + * + * If both src and dst have an associated hwframe context, then this function + * attempts to map the src frame from its hardware context to that of dst and + * then fill dst with appropriate data to be usable there. This will only be + * possible if the hwframe contexts and associated devices are compatible - + * given compatible devices, av_hwframe_ctx_create_derived() can be used to + * create a hwframe context for dst in which mapping should be possible. + * + * If src has a hwframe context but dst does not, then the src frame is + * mapped to normal memory and should thereafter be usable as a normal frame. + * If the format is set on dst, then the mapping will attempt to create dst + * with that format and fail if it is not possible. If format is unset (is + * AV_PIX_FMT_NONE) then dst will be mapped with whatever the most appropriate + * format to use is (probably the sw_format of the src hwframe context). + * + * A return value of AVERROR(ENOSYS) indicates that the mapping is not + * possible with the given arguments and hwframe setup, while other return + * values indicate that it failed somehow. + * + * @param dst Destination frame, to contain the mapping. + * @param src Source frame, to be mapped. + * @param flags Some combination of AV_HWFRAME_MAP_* flags. + * @return Zero on success, negative AVERROR code on failure. + */ +int av_hwframe_map(AVFrame *dst, const AVFrame *src, int flags); + + +/** + * Create and initialise an AVHWFramesContext as a mapping of another existing + * AVHWFramesContext on a different device. + * + * av_hwframe_ctx_init() should not be called after this. + * + * @param derived_frame_ctx On success, a reference to the newly created + * AVHWFramesContext. + * @param derived_device_ctx A reference to the device to create the new + * AVHWFramesContext on. + * @param source_frame_ctx A reference to an existing AVHWFramesContext + * which will be mapped to the derived context. + * @param flags Some combination of AV_HWFRAME_MAP_* flags, defining the + * mapping parameters to apply to frames which are allocated + * in the derived device. + * @return Zero on success, negative AVERROR code on failure. + */ +int av_hwframe_ctx_create_derived(AVBufferRef **derived_frame_ctx, + enum AVPixelFormat format, + AVBufferRef *derived_device_ctx, + AVBufferRef *source_frame_ctx, + int flags); + +#endif /* AVUTIL_HWCONTEXT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_cuda.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_cuda.h new file mode 100644 index 0000000000..12dae8449e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_cuda.h @@ -0,0 +1,51 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef AVUTIL_HWCONTEXT_CUDA_H +#define AVUTIL_HWCONTEXT_CUDA_H + +#ifndef CUDA_VERSION +#include +#endif + +#include "pixfmt.h" + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_CUDA. + * + * This API supports dynamic frame pools. AVHWFramesContext.pool must return + * AVBufferRefs whose data pointer is a CUdeviceptr. + */ + +typedef struct AVCUDADeviceContextInternal AVCUDADeviceContextInternal; + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVCUDADeviceContext { + CUcontext cuda_ctx; + AVCUDADeviceContextInternal *internal; +} AVCUDADeviceContext; + +/** + * AVHWFramesContext.hwctx is currently not used + */ + +#endif /* AVUTIL_HWCONTEXT_CUDA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_d3d11va.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_d3d11va.h new file mode 100644 index 0000000000..98db7ce343 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_d3d11va.h @@ -0,0 +1,168 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_D3D11VA_H +#define AVUTIL_HWCONTEXT_D3D11VA_H + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_D3D11VA. + * + * The default pool implementation will be fixed-size if initial_pool_size is + * set (and allocate elements from an array texture). Otherwise it will allocate + * individual textures. Be aware that decoding requires a single array texture. + * + * Using sw_format==AV_PIX_FMT_YUV420P has special semantics, and maps to + * DXGI_FORMAT_420_OPAQUE. av_hwframe_transfer_data() is not supported for + * this format. Refer to MSDN for details. + * + * av_hwdevice_ctx_create() for this device type supports a key named "debug" + * for the AVDictionary entry. If this is set to any value, the device creation + * code will try to load various supported D3D debugging layers. + */ + +#include + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVD3D11VADeviceContext { + /** + * Device used for texture creation and access. This can also be used to + * set the libavcodec decoding device. + * + * Must be set by the user. This is the only mandatory field - the other + * device context fields are set from this and are available for convenience. + * + * Deallocating the AVHWDeviceContext will always release this interface, + * and it does not matter whether it was user-allocated. + */ + ID3D11Device *device; + + /** + * If unset, this will be set from the device field on init. + * + * Deallocating the AVHWDeviceContext will always release this interface, + * and it does not matter whether it was user-allocated. + */ + ID3D11DeviceContext *device_context; + + /** + * If unset, this will be set from the device field on init. + * + * Deallocating the AVHWDeviceContext will always release this interface, + * and it does not matter whether it was user-allocated. + */ + ID3D11VideoDevice *video_device; + + /** + * If unset, this will be set from the device_context field on init. + * + * Deallocating the AVHWDeviceContext will always release this interface, + * and it does not matter whether it was user-allocated. + */ + ID3D11VideoContext *video_context; + + /** + * Callbacks for locking. They protect accesses to device_context and + * video_context calls. They also protect access to the internal staging + * texture (for av_hwframe_transfer_data() calls). They do NOT protect + * access to hwcontext or decoder state in general. + * + * If unset on init, the hwcontext implementation will set them to use an + * internal mutex. + * + * The underlying lock must be recursive. lock_ctx is for free use by the + * locking implementation. + */ + void (*lock)(void *lock_ctx); + void (*unlock)(void *lock_ctx); + void *lock_ctx; +} AVD3D11VADeviceContext; + +/** + * D3D11 frame descriptor for pool allocation. + * + * In user-allocated pools, AVHWFramesContext.pool must return AVBufferRefs + * with the data pointer pointing at an object of this type describing the + * planes of the frame. + * + * This has no use outside of custom allocation, and AVFrame AVBufferRef do not + * necessarily point to an instance of this struct. + */ +typedef struct AVD3D11FrameDescriptor { + /** + * The texture in which the frame is located. The reference count is + * managed by the AVBufferRef, and destroying the reference will release + * the interface. + * + * Normally stored in AVFrame.data[0]. + */ + ID3D11Texture2D *texture; + + /** + * The index into the array texture element representing the frame, or 0 + * if the texture is not an array texture. + * + * Normally stored in AVFrame.data[1] (cast from intptr_t). + */ + intptr_t index; +} AVD3D11FrameDescriptor; + +/** + * This struct is allocated as AVHWFramesContext.hwctx + */ +typedef struct AVD3D11VAFramesContext { + /** + * The canonical texture used for pool allocation. If this is set to NULL + * on init, the hwframes implementation will allocate and set an array + * texture if initial_pool_size > 0. + * + * The only situation when the API user should set this is: + * - the user wants to do manual pool allocation (setting + * AVHWFramesContext.pool), instead of letting AVHWFramesContext + * allocate the pool + * - of an array texture + * - and wants it to use it for decoding + * - this has to be done before calling av_hwframe_ctx_init() + * + * Deallocating the AVHWFramesContext will always release this interface, + * and it does not matter whether it was user-allocated. + * + * This is in particular used by the libavcodec D3D11VA hwaccel, which + * requires a single array texture. It will create ID3D11VideoDecoderOutputView + * objects for each array texture element on decoder initialization. + */ + ID3D11Texture2D *texture; + + /** + * D3D11_TEXTURE2D_DESC.BindFlags used for texture creation. The user must + * at least set D3D11_BIND_DECODER if the frames context is to be used for + * video decoding. + * This field is ignored/invalid if a user-allocated texture is provided. + */ + UINT BindFlags; + + /** + * D3D11_TEXTURE2D_DESC.MiscFlags used for texture creation. + * This field is ignored/invalid if a user-allocated texture is provided. + */ + UINT MiscFlags; +} AVD3D11VAFramesContext; + +#endif /* AVUTIL_HWCONTEXT_D3D11VA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_drm.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_drm.h new file mode 100644 index 0000000000..2e225451e1 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_drm.h @@ -0,0 +1,166 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_DRM_H +#define AVUTIL_HWCONTEXT_DRM_H + +#include +#include + +/** + * @file + * API-specific header for AV_HWDEVICE_TYPE_DRM. + * + * Internal frame allocation is not currently supported - all frames + * must be allocated by the user. Thus AVHWFramesContext is always + * NULL, though this may change if support for frame allocation is + * added in future. + */ + +enum { + /** + * The maximum number of layers/planes in a DRM frame. + */ + AV_DRM_MAX_PLANES = 4 +}; + +/** + * DRM object descriptor. + * + * Describes a single DRM object, addressing it as a PRIME file + * descriptor. + */ +typedef struct AVDRMObjectDescriptor { + /** + * DRM PRIME fd for the object. + */ + int fd; + /** + * Total size of the object. + * + * (This includes any parts not which do not contain image data.) + */ + size_t size; + /** + * Format modifier applied to the object (DRM_FORMAT_MOD_*). + */ + uint64_t format_modifier; +} AVDRMObjectDescriptor; + +/** + * DRM plane descriptor. + * + * Describes a single plane of a layer, which is contained within + * a single object. + */ +typedef struct AVDRMPlaneDescriptor { + /** + * Index of the object containing this plane in the objects + * array of the enclosing frame descriptor. + */ + int object_index; + /** + * Offset within that object of this plane. + */ + ptrdiff_t offset; + /** + * Pitch (linesize) of this plane. + */ + ptrdiff_t pitch; +} AVDRMPlaneDescriptor; + +/** + * DRM layer descriptor. + * + * Describes a single layer within a frame. This has the structure + * defined by its format, and will contain one or more planes. + */ +typedef struct AVDRMLayerDescriptor { + /** + * Format of the layer (DRM_FORMAT_*). + */ + uint32_t format; + /** + * Number of planes in the layer. + * + * This must match the number of planes required by format. + */ + int nb_planes; + /** + * Array of planes in this layer. + */ + AVDRMPlaneDescriptor planes[AV_DRM_MAX_PLANES]; +} AVDRMLayerDescriptor; + +/** + * DRM frame descriptor. + * + * This is used as the data pointer for AV_PIX_FMT_DRM_PRIME frames. + * It is also used by user-allocated frame pools - allocating in + * AVHWFramesContext.pool must return AVBufferRefs which contain + * an object of this type. + * + * The fields of this structure should be set such it can be + * imported directly by EGL using the EGL_EXT_image_dma_buf_import + * and EGL_EXT_image_dma_buf_import_modifiers extensions. + * (Note that the exact layout of a particular format may vary between + * platforms - we only specify that the same platform should be able + * to import it.) + * + * The total number of planes must not exceed AV_DRM_MAX_PLANES, and + * the order of the planes by increasing layer index followed by + * increasing plane index must be the same as the order which would + * be used for the data pointers in the equivalent software format. + */ +typedef struct AVDRMFrameDescriptor { + /** + * Number of DRM objects making up this frame. + */ + int nb_objects; + /** + * Array of objects making up the frame. + */ + AVDRMObjectDescriptor objects[AV_DRM_MAX_PLANES]; + /** + * Number of layers in the frame. + */ + int nb_layers; + /** + * Array of layers in the frame. + */ + AVDRMLayerDescriptor layers[AV_DRM_MAX_PLANES]; +} AVDRMFrameDescriptor; + +/** + * DRM device. + * + * Allocated as AVHWDeviceContext.hwctx. + */ +typedef struct AVDRMDeviceContext { + /** + * File descriptor of DRM device. + * + * This is used as the device to create frames on, and may also be + * used in some derivation and mapping operations. + * + * If no device is required, set to -1. + */ + int fd; +} AVDRMDeviceContext; + +#endif /* AVUTIL_HWCONTEXT_DRM_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_dxva2.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_dxva2.h new file mode 100644 index 0000000000..e1b79bc0de --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_dxva2.h @@ -0,0 +1,75 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef AVUTIL_HWCONTEXT_DXVA2_H +#define AVUTIL_HWCONTEXT_DXVA2_H + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_DXVA2. + * + * Only fixed-size pools are supported. + * + * For user-allocated pools, AVHWFramesContext.pool must return AVBufferRefs + * with the data pointer set to a pointer to IDirect3DSurface9. + */ + +#include +#include + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVDXVA2DeviceContext { + IDirect3DDeviceManager9 *devmgr; +} AVDXVA2DeviceContext; + +/** + * This struct is allocated as AVHWFramesContext.hwctx + */ +typedef struct AVDXVA2FramesContext { + /** + * The surface type (e.g. DXVA2_VideoProcessorRenderTarget or + * DXVA2_VideoDecoderRenderTarget). Must be set by the caller. + */ + DWORD surface_type; + + /** + * The surface pool. When an external pool is not provided by the caller, + * this will be managed (allocated and filled on init, freed on uninit) by + * libavutil. + */ + IDirect3DSurface9 **surfaces; + int nb_surfaces; + + /** + * Certain drivers require the decoder to be destroyed before the surfaces. + * To allow internally managed pools to work properly in such cases, this + * field is provided. + * + * If it is non-NULL, libavutil will call IDirectXVideoDecoder_Release() on + * it just before the internal surface pool is freed. + * + * This is for convenience only. Some code uses other methods to manage the + * decoder reference. + */ + IDirectXVideoDecoder *decoder_to_release; +} AVDXVA2FramesContext; + +#endif /* AVUTIL_HWCONTEXT_DXVA2_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_qsv.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_qsv.h new file mode 100644 index 0000000000..b98d611cfc --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_qsv.h @@ -0,0 +1,53 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_QSV_H +#define AVUTIL_HWCONTEXT_QSV_H + +#include + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_QSV. + * + * This API does not support dynamic frame pools. AVHWFramesContext.pool must + * contain AVBufferRefs whose data pointer points to an mfxFrameSurface1 struct. + */ + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVQSVDeviceContext { + mfxSession session; +} AVQSVDeviceContext; + +/** + * This struct is allocated as AVHWFramesContext.hwctx + */ +typedef struct AVQSVFramesContext { + mfxFrameSurface1 *surfaces; + int nb_surfaces; + + /** + * A combination of MFX_MEMTYPE_* describing the frame pool. + */ + int frame_type; +} AVQSVFramesContext; + +#endif /* AVUTIL_HWCONTEXT_QSV_H */ + diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_vaapi.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_vaapi.h new file mode 100644 index 0000000000..0b2e071cb3 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_vaapi.h @@ -0,0 +1,117 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_VAAPI_H +#define AVUTIL_HWCONTEXT_VAAPI_H + +#include + +/** + * @file + * API-specific header for AV_HWDEVICE_TYPE_VAAPI. + * + * Dynamic frame pools are supported, but note that any pool used as a render + * target is required to be of fixed size in order to be be usable as an + * argument to vaCreateContext(). + * + * For user-allocated pools, AVHWFramesContext.pool must return AVBufferRefs + * with the data pointer set to a VASurfaceID. + */ + +enum { + /** + * The quirks field has been set by the user and should not be detected + * automatically by av_hwdevice_ctx_init(). + */ + AV_VAAPI_DRIVER_QUIRK_USER_SET = (1 << 0), + /** + * The driver does not destroy parameter buffers when they are used by + * vaRenderPicture(). Additional code will be required to destroy them + * separately afterwards. + */ + AV_VAAPI_DRIVER_QUIRK_RENDER_PARAM_BUFFERS = (1 << 1), + + /** + * The driver does not support the VASurfaceAttribMemoryType attribute, + * so the surface allocation code will not try to use it. + */ + AV_VAAPI_DRIVER_QUIRK_ATTRIB_MEMTYPE = (1 << 2), + + /** + * The driver does not support surface attributes at all. + * The surface allocation code will never pass them to surface allocation, + * and the results of the vaQuerySurfaceAttributes() call will be faked. + */ + AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES = (1 << 3), +}; + +/** + * VAAPI connection details. + * + * Allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVVAAPIDeviceContext { + /** + * The VADisplay handle, to be filled by the user. + */ + VADisplay display; + /** + * Driver quirks to apply - this is filled by av_hwdevice_ctx_init(), + * with reference to a table of known drivers, unless the + * AV_VAAPI_DRIVER_QUIRK_USER_SET bit is already present. The user + * may need to refer to this field when performing any later + * operations using VAAPI with the same VADisplay. + */ + unsigned int driver_quirks; +} AVVAAPIDeviceContext; + +/** + * VAAPI-specific data associated with a frame pool. + * + * Allocated as AVHWFramesContext.hwctx. + */ +typedef struct AVVAAPIFramesContext { + /** + * Set by the user to apply surface attributes to all surfaces in + * the frame pool. If null, default settings are used. + */ + VASurfaceAttrib *attributes; + int nb_attributes; + /** + * The surfaces IDs of all surfaces in the pool after creation. + * Only valid if AVHWFramesContext.initial_pool_size was positive. + * These are intended to be used as the render_targets arguments to + * vaCreateContext(). + */ + VASurfaceID *surface_ids; + int nb_surfaces; +} AVVAAPIFramesContext; + +/** + * VAAPI hardware pipeline configuration details. + * + * Allocated with av_hwdevice_hwconfig_alloc(). + */ +typedef struct AVVAAPIHWConfig { + /** + * ID of a VAAPI pipeline configuration. + */ + VAConfigID config_id; +} AVVAAPIHWConfig; + +#endif /* AVUTIL_HWCONTEXT_VAAPI_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_vdpau.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_vdpau.h new file mode 100644 index 0000000000..1b7ea1e443 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_vdpau.h @@ -0,0 +1,44 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_VDPAU_H +#define AVUTIL_HWCONTEXT_VDPAU_H + +#include + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_VDPAU. + * + * This API supports dynamic frame pools. AVHWFramesContext.pool must return + * AVBufferRefs whose data pointer is a VdpVideoSurface. + */ + +/** + * This struct is allocated as AVHWDeviceContext.hwctx + */ +typedef struct AVVDPAUDeviceContext { + VdpDevice device; + VdpGetProcAddress *get_proc_address; +} AVVDPAUDeviceContext; + +/** + * AVHWFramesContext.hwctx is currently not used + */ + +#endif /* AVUTIL_HWCONTEXT_VDPAU_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h b/thrid-party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h new file mode 100644 index 0000000000..380918d92e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/hwcontext_videotoolbox.h @@ -0,0 +1,54 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HWCONTEXT_VIDEOTOOLBOX_H +#define AVUTIL_HWCONTEXT_VIDEOTOOLBOX_H + +#include + +#include + +#include "pixfmt.h" + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_VIDEOTOOLBOX. + * + * This API currently does not support frame allocation, as the raw VideoToolbox + * API does allocation, and FFmpeg itself never has the need to allocate frames. + * + * If the API user sets a custom pool, AVHWFramesContext.pool must return + * AVBufferRefs whose data pointer is a CVImageBufferRef or CVPixelBufferRef. + * + * Currently AVHWDeviceContext.hwctx and AVHWFramesContext.hwctx are always + * NULL. + */ + +/** + * Convert a VideoToolbox (actually CoreVideo) format to AVPixelFormat. + * Returns AV_PIX_FMT_NONE if no known equivalent was found. + */ +enum AVPixelFormat av_map_videotoolbox_format_to_pixfmt(uint32_t cv_fmt); + +/** + * Convert an AVPixelFormat to a VideoToolbox (actually CoreVideo) format. + * Returns 0 if no known equivalent was found. + */ +uint32_t av_map_videotoolbox_format_from_pixfmt(enum AVPixelFormat pix_fmt); + +#endif /* AVUTIL_HWCONTEXT_VIDEOTOOLBOX_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/imgutils.h b/thrid-party/ffmpeg/include/libavutil/imgutils.h new file mode 100644 index 0000000000..5b790ecf0a --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/imgutils.h @@ -0,0 +1,277 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_IMGUTILS_H +#define AVUTIL_IMGUTILS_H + +/** + * @file + * misc image utilities + * + * @addtogroup lavu_picture + * @{ + */ + +#include "avutil.h" +#include "pixdesc.h" +#include "rational.h" + +/** + * Compute the max pixel step for each plane of an image with a + * format described by pixdesc. + * + * The pixel step is the distance in bytes between the first byte of + * the group of bytes which describe a pixel component and the first + * byte of the successive group in the same plane for the same + * component. + * + * @param max_pixsteps an array which is filled with the max pixel step + * for each plane. Since a plane may contain different pixel + * components, the computed max_pixsteps[plane] is relative to the + * component in the plane with the max pixel step. + * @param max_pixstep_comps an array which is filled with the component + * for each plane which has the max pixel step. May be NULL. + */ +void av_image_fill_max_pixsteps(int max_pixsteps[4], int max_pixstep_comps[4], + const AVPixFmtDescriptor *pixdesc); + +/** + * Compute the size of an image line with format pix_fmt and width + * width for the plane plane. + * + * @return the computed size in bytes + */ +int av_image_get_linesize(enum AVPixelFormat pix_fmt, int width, int plane); + +/** + * Fill plane linesizes for an image with pixel format pix_fmt and + * width width. + * + * @param linesizes array to be filled with the linesize for each plane + * @return >= 0 in case of success, a negative error code otherwise + */ +int av_image_fill_linesizes(int linesizes[4], enum AVPixelFormat pix_fmt, int width); + +/** + * Fill plane data pointers for an image with pixel format pix_fmt and + * height height. + * + * @param data pointers array to be filled with the pointer for each image plane + * @param ptr the pointer to a buffer which will contain the image + * @param linesizes the array containing the linesize for each + * plane, should be filled by av_image_fill_linesizes() + * @return the size in bytes required for the image buffer, a negative + * error code in case of failure + */ +int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height, + uint8_t *ptr, const int linesizes[4]); + +/** + * Allocate an image with size w and h and pixel format pix_fmt, and + * fill pointers and linesizes accordingly. + * The allocated image buffer has to be freed by using + * av_freep(&pointers[0]). + * + * @param align the value to use for buffer size alignment + * @return the size in bytes required for the image buffer, a negative + * error code in case of failure + */ +int av_image_alloc(uint8_t *pointers[4], int linesizes[4], + int w, int h, enum AVPixelFormat pix_fmt, int align); + +/** + * Copy image plane from src to dst. + * That is, copy "height" number of lines of "bytewidth" bytes each. + * The first byte of each successive line is separated by *_linesize + * bytes. + * + * bytewidth must be contained by both absolute values of dst_linesize + * and src_linesize, otherwise the function behavior is undefined. + * + * @param dst_linesize linesize for the image plane in dst + * @param src_linesize linesize for the image plane in src + */ +void av_image_copy_plane(uint8_t *dst, int dst_linesize, + const uint8_t *src, int src_linesize, + int bytewidth, int height); + +/** + * Copy image in src_data to dst_data. + * + * @param dst_linesizes linesizes for the image in dst_data + * @param src_linesizes linesizes for the image in src_data + */ +void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4], + const uint8_t *src_data[4], const int src_linesizes[4], + enum AVPixelFormat pix_fmt, int width, int height); + +/** + * Copy image data located in uncacheable (e.g. GPU mapped) memory. Where + * available, this function will use special functionality for reading from such + * memory, which may result in greatly improved performance compared to plain + * av_image_copy(). + * + * The data pointers and the linesizes must be aligned to the maximum required + * by the CPU architecture. + * + * @note The linesize parameters have the type ptrdiff_t here, while they are + * int for av_image_copy(). + * @note On x86, the linesizes currently need to be aligned to the cacheline + * size (i.e. 64) to get improved performance. + */ +void av_image_copy_uc_from(uint8_t *dst_data[4], const ptrdiff_t dst_linesizes[4], + const uint8_t *src_data[4], const ptrdiff_t src_linesizes[4], + enum AVPixelFormat pix_fmt, int width, int height); + +/** + * Setup the data pointers and linesizes based on the specified image + * parameters and the provided array. + * + * The fields of the given image are filled in by using the src + * address which points to the image data buffer. Depending on the + * specified pixel format, one or multiple image data pointers and + * line sizes will be set. If a planar format is specified, several + * pointers will be set pointing to the different picture planes and + * the line sizes of the different planes will be stored in the + * lines_sizes array. Call with src == NULL to get the required + * size for the src buffer. + * + * To allocate the buffer and fill in the dst_data and dst_linesize in + * one call, use av_image_alloc(). + * + * @param dst_data data pointers to be filled in + * @param dst_linesize linesizes for the image in dst_data to be filled in + * @param src buffer which will contain or contains the actual image data, can be NULL + * @param pix_fmt the pixel format of the image + * @param width the width of the image in pixels + * @param height the height of the image in pixels + * @param align the value used in src for linesize alignment + * @return the size in bytes required for src, a negative error code + * in case of failure + */ +int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4], + const uint8_t *src, + enum AVPixelFormat pix_fmt, int width, int height, int align); + +/** + * Return the size in bytes of the amount of data required to store an + * image with the given parameters. + * + * @param pix_fmt the pixel format of the image + * @param width the width of the image in pixels + * @param height the height of the image in pixels + * @param align the assumed linesize alignment + * @return the buffer size in bytes, a negative error code in case of failure + */ +int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align); + +/** + * Copy image data from an image into a buffer. + * + * av_image_get_buffer_size() can be used to compute the required size + * for the buffer to fill. + * + * @param dst a buffer into which picture data will be copied + * @param dst_size the size in bytes of dst + * @param src_data pointers containing the source image data + * @param src_linesize linesizes for the image in src_data + * @param pix_fmt the pixel format of the source image + * @param width the width of the source image in pixels + * @param height the height of the source image in pixels + * @param align the assumed linesize alignment for dst + * @return the number of bytes written to dst, or a negative value + * (error code) on error + */ +int av_image_copy_to_buffer(uint8_t *dst, int dst_size, + const uint8_t * const src_data[4], const int src_linesize[4], + enum AVPixelFormat pix_fmt, int width, int height, int align); + +/** + * Check if the given dimension of an image is valid, meaning that all + * bytes of the image can be addressed with a signed int. + * + * @param w the width of the picture + * @param h the height of the picture + * @param log_offset the offset to sum to the log level for logging with log_ctx + * @param log_ctx the parent logging context, it may be NULL + * @return >= 0 if valid, a negative error code otherwise + */ +int av_image_check_size(unsigned int w, unsigned int h, int log_offset, void *log_ctx); + +/** + * Check if the given dimension of an image is valid, meaning that all + * bytes of a plane of an image with the specified pix_fmt can be addressed + * with a signed int. + * + * @param w the width of the picture + * @param h the height of the picture + * @param max_pixels the maximum number of pixels the user wants to accept + * @param pix_fmt the pixel format, can be AV_PIX_FMT_NONE if unknown. + * @param log_offset the offset to sum to the log level for logging with log_ctx + * @param log_ctx the parent logging context, it may be NULL + * @return >= 0 if valid, a negative error code otherwise + */ +int av_image_check_size2(unsigned int w, unsigned int h, int64_t max_pixels, enum AVPixelFormat pix_fmt, int log_offset, void *log_ctx); + +/** + * Check if the given sample aspect ratio of an image is valid. + * + * It is considered invalid if the denominator is 0 or if applying the ratio + * to the image size would make the smaller dimension less than 1. If the + * sar numerator is 0, it is considered unknown and will return as valid. + * + * @param w width of the image + * @param h height of the image + * @param sar sample aspect ratio of the image + * @return 0 if valid, a negative AVERROR code otherwise + */ +int av_image_check_sar(unsigned int w, unsigned int h, AVRational sar); + +/** + * Overwrite the image data with black. This is suitable for filling a + * sub-rectangle of an image, meaning the padding between the right most pixel + * and the left most pixel on the next line will not be overwritten. For some + * formats, the image size might be rounded up due to inherent alignment. + * + * If the pixel format has alpha, the alpha is cleared to opaque. + * + * This can return an error if the pixel format is not supported. Normally, all + * non-hwaccel pixel formats should be supported. + * + * Passing NULL for dst_data is allowed. Then the function returns whether the + * operation would have succeeded. (It can return an error if the pix_fmt is + * not supported.) + * + * @param dst_data data pointers to destination image + * @param dst_linesize linesizes for the destination image + * @param pix_fmt the pixel format of the image + * @param range the color range of the image (important for colorspaces such as YUV) + * @param width the width of the image in pixels + * @param height the height of the image in pixels + * @return 0 if the image data was cleared, a negative AVERROR code otherwise + */ +int av_image_fill_black(uint8_t *dst_data[4], const ptrdiff_t dst_linesize[4], + enum AVPixelFormat pix_fmt, enum AVColorRange range, + int width, int height); + +/** + * @} + */ + + +#endif /* AVUTIL_IMGUTILS_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/intfloat.h b/thrid-party/ffmpeg/include/libavutil/intfloat.h new file mode 100644 index 0000000000..fe3d7ec4a5 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/intfloat.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 Mans Rullgard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_INTFLOAT_H +#define AVUTIL_INTFLOAT_H + +#include +#include "attributes.h" + +union av_intfloat32 { + uint32_t i; + float f; +}; + +union av_intfloat64 { + uint64_t i; + double f; +}; + +/** + * Reinterpret a 32-bit integer as a float. + */ +static av_always_inline float av_int2float(uint32_t i) +{ + union av_intfloat32 v; + v.i = i; + return v.f; +} + +/** + * Reinterpret a float as a 32-bit integer. + */ +static av_always_inline uint32_t av_float2int(float f) +{ + union av_intfloat32 v; + v.f = f; + return v.i; +} + +/** + * Reinterpret a 64-bit integer as a double. + */ +static av_always_inline double av_int2double(uint64_t i) +{ + union av_intfloat64 v; + v.i = i; + return v.f; +} + +/** + * Reinterpret a double as a 64-bit integer. + */ +static av_always_inline uint64_t av_double2int(double f) +{ + union av_intfloat64 v; + v.f = f; + return v.i; +} + +#endif /* AVUTIL_INTFLOAT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/intreadwrite.h b/thrid-party/ffmpeg/include/libavutil/intreadwrite.h new file mode 100644 index 0000000000..d54d4b91d6 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/intreadwrite.h @@ -0,0 +1,634 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_INTREADWRITE_H +#define AVUTIL_INTREADWRITE_H + +#include +#include "libavutil/avconfig.h" +#include "attributes.h" +#include "bswap.h" + +typedef union { + uint64_t u64; + uint32_t u32[2]; + uint16_t u16[4]; + uint8_t u8 [8]; + double f64; + float f32[2]; +} av_alias av_alias64; + +typedef union { + uint32_t u32; + uint16_t u16[2]; + uint8_t u8 [4]; + float f32; +} av_alias av_alias32; + +typedef union { + uint16_t u16; + uint8_t u8 [2]; +} av_alias av_alias16; + +/* + * Arch-specific headers can provide any combination of + * AV_[RW][BLN](16|24|32|48|64) and AV_(COPY|SWAP|ZERO)(64|128) macros. + * Preprocessor symbols must be defined, even if these are implemented + * as inline functions. + * + * R/W means read/write, B/L/N means big/little/native endianness. + * The following macros require aligned access, compared to their + * unaligned variants: AV_(COPY|SWAP|ZERO)(64|128), AV_[RW]N[8-64]A. + * Incorrect usage may range from abysmal performance to crash + * depending on the platform. + * + * The unaligned variants are AV_[RW][BLN][8-64] and AV_COPY*U. + */ + +#ifdef HAVE_AV_CONFIG_H + +#include "config.h" + +#if ARCH_ARM +# include "arm/intreadwrite.h" +#elif ARCH_AVR32 +# include "avr32/intreadwrite.h" +#elif ARCH_MIPS +# include "mips/intreadwrite.h" +#elif ARCH_PPC +# include "ppc/intreadwrite.h" +#elif ARCH_TOMI +# include "tomi/intreadwrite.h" +#elif ARCH_X86 +# include "x86/intreadwrite.h" +#endif + +#endif /* HAVE_AV_CONFIG_H */ + +/* + * Map AV_RNXX <-> AV_R[BL]XX for all variants provided by per-arch headers. + */ + +#if AV_HAVE_BIGENDIAN + +# if defined(AV_RN16) && !defined(AV_RB16) +# define AV_RB16(p) AV_RN16(p) +# elif !defined(AV_RN16) && defined(AV_RB16) +# define AV_RN16(p) AV_RB16(p) +# endif + +# if defined(AV_WN16) && !defined(AV_WB16) +# define AV_WB16(p, v) AV_WN16(p, v) +# elif !defined(AV_WN16) && defined(AV_WB16) +# define AV_WN16(p, v) AV_WB16(p, v) +# endif + +# if defined(AV_RN24) && !defined(AV_RB24) +# define AV_RB24(p) AV_RN24(p) +# elif !defined(AV_RN24) && defined(AV_RB24) +# define AV_RN24(p) AV_RB24(p) +# endif + +# if defined(AV_WN24) && !defined(AV_WB24) +# define AV_WB24(p, v) AV_WN24(p, v) +# elif !defined(AV_WN24) && defined(AV_WB24) +# define AV_WN24(p, v) AV_WB24(p, v) +# endif + +# if defined(AV_RN32) && !defined(AV_RB32) +# define AV_RB32(p) AV_RN32(p) +# elif !defined(AV_RN32) && defined(AV_RB32) +# define AV_RN32(p) AV_RB32(p) +# endif + +# if defined(AV_WN32) && !defined(AV_WB32) +# define AV_WB32(p, v) AV_WN32(p, v) +# elif !defined(AV_WN32) && defined(AV_WB32) +# define AV_WN32(p, v) AV_WB32(p, v) +# endif + +# if defined(AV_RN48) && !defined(AV_RB48) +# define AV_RB48(p) AV_RN48(p) +# elif !defined(AV_RN48) && defined(AV_RB48) +# define AV_RN48(p) AV_RB48(p) +# endif + +# if defined(AV_WN48) && !defined(AV_WB48) +# define AV_WB48(p, v) AV_WN48(p, v) +# elif !defined(AV_WN48) && defined(AV_WB48) +# define AV_WN48(p, v) AV_WB48(p, v) +# endif + +# if defined(AV_RN64) && !defined(AV_RB64) +# define AV_RB64(p) AV_RN64(p) +# elif !defined(AV_RN64) && defined(AV_RB64) +# define AV_RN64(p) AV_RB64(p) +# endif + +# if defined(AV_WN64) && !defined(AV_WB64) +# define AV_WB64(p, v) AV_WN64(p, v) +# elif !defined(AV_WN64) && defined(AV_WB64) +# define AV_WN64(p, v) AV_WB64(p, v) +# endif + +#else /* AV_HAVE_BIGENDIAN */ + +# if defined(AV_RN16) && !defined(AV_RL16) +# define AV_RL16(p) AV_RN16(p) +# elif !defined(AV_RN16) && defined(AV_RL16) +# define AV_RN16(p) AV_RL16(p) +# endif + +# if defined(AV_WN16) && !defined(AV_WL16) +# define AV_WL16(p, v) AV_WN16(p, v) +# elif !defined(AV_WN16) && defined(AV_WL16) +# define AV_WN16(p, v) AV_WL16(p, v) +# endif + +# if defined(AV_RN24) && !defined(AV_RL24) +# define AV_RL24(p) AV_RN24(p) +# elif !defined(AV_RN24) && defined(AV_RL24) +# define AV_RN24(p) AV_RL24(p) +# endif + +# if defined(AV_WN24) && !defined(AV_WL24) +# define AV_WL24(p, v) AV_WN24(p, v) +# elif !defined(AV_WN24) && defined(AV_WL24) +# define AV_WN24(p, v) AV_WL24(p, v) +# endif + +# if defined(AV_RN32) && !defined(AV_RL32) +# define AV_RL32(p) AV_RN32(p) +# elif !defined(AV_RN32) && defined(AV_RL32) +# define AV_RN32(p) AV_RL32(p) +# endif + +# if defined(AV_WN32) && !defined(AV_WL32) +# define AV_WL32(p, v) AV_WN32(p, v) +# elif !defined(AV_WN32) && defined(AV_WL32) +# define AV_WN32(p, v) AV_WL32(p, v) +# endif + +# if defined(AV_RN48) && !defined(AV_RL48) +# define AV_RL48(p) AV_RN48(p) +# elif !defined(AV_RN48) && defined(AV_RL48) +# define AV_RN48(p) AV_RL48(p) +# endif + +# if defined(AV_WN48) && !defined(AV_WL48) +# define AV_WL48(p, v) AV_WN48(p, v) +# elif !defined(AV_WN48) && defined(AV_WL48) +# define AV_WN48(p, v) AV_WL48(p, v) +# endif + +# if defined(AV_RN64) && !defined(AV_RL64) +# define AV_RL64(p) AV_RN64(p) +# elif !defined(AV_RN64) && defined(AV_RL64) +# define AV_RN64(p) AV_RL64(p) +# endif + +# if defined(AV_WN64) && !defined(AV_WL64) +# define AV_WL64(p, v) AV_WN64(p, v) +# elif !defined(AV_WN64) && defined(AV_WL64) +# define AV_WN64(p, v) AV_WL64(p, v) +# endif + +#endif /* !AV_HAVE_BIGENDIAN */ + +/* + * Define AV_[RW]N helper macros to simplify definitions not provided + * by per-arch headers. + */ + +#if defined(__GNUC__) && !defined(__TI_COMPILER_VERSION__) + +union unaligned_64 { uint64_t l; } __attribute__((packed)) av_alias; +union unaligned_32 { uint32_t l; } __attribute__((packed)) av_alias; +union unaligned_16 { uint16_t l; } __attribute__((packed)) av_alias; + +# define AV_RN(s, p) (((const union unaligned_##s *) (p))->l) +# define AV_WN(s, p, v) ((((union unaligned_##s *) (p))->l) = (v)) + +#elif defined(__DECC) + +# define AV_RN(s, p) (*((const __unaligned uint##s##_t*)(p))) +# define AV_WN(s, p, v) (*((__unaligned uint##s##_t*)(p)) = (v)) + +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_X64)) && AV_HAVE_FAST_UNALIGNED + +# define AV_RN(s, p) (*((const __unaligned uint##s##_t*)(p))) +# define AV_WN(s, p, v) (*((__unaligned uint##s##_t*)(p)) = (v)) + +#elif AV_HAVE_FAST_UNALIGNED + +# define AV_RN(s, p) (((const av_alias##s*)(p))->u##s) +# define AV_WN(s, p, v) (((av_alias##s*)(p))->u##s = (v)) + +#else + +#ifndef AV_RB16 +# define AV_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x))[1]) +#endif +#ifndef AV_WB16 +# define AV_WB16(p, val) do { \ + uint16_t d = (val); \ + ((uint8_t*)(p))[1] = (d); \ + ((uint8_t*)(p))[0] = (d)>>8; \ + } while(0) +#endif + +#ifndef AV_RL16 +# define AV_RL16(x) \ + ((((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL16 +# define AV_WL16(p, val) do { \ + uint16_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + } while(0) +#endif + +#ifndef AV_RB32 +# define AV_RB32(x) \ + (((uint32_t)((const uint8_t*)(x))[0] << 24) | \ + (((const uint8_t*)(x))[1] << 16) | \ + (((const uint8_t*)(x))[2] << 8) | \ + ((const uint8_t*)(x))[3]) +#endif +#ifndef AV_WB32 +# define AV_WB32(p, val) do { \ + uint32_t d = (val); \ + ((uint8_t*)(p))[3] = (d); \ + ((uint8_t*)(p))[2] = (d)>>8; \ + ((uint8_t*)(p))[1] = (d)>>16; \ + ((uint8_t*)(p))[0] = (d)>>24; \ + } while(0) +#endif + +#ifndef AV_RL32 +# define AV_RL32(x) \ + (((uint32_t)((const uint8_t*)(x))[3] << 24) | \ + (((const uint8_t*)(x))[2] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL32 +# define AV_WL32(p, val) do { \ + uint32_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + } while(0) +#endif + +#ifndef AV_RB64 +# define AV_RB64(x) \ + (((uint64_t)((const uint8_t*)(x))[0] << 56) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 48) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[5] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[6] << 8) | \ + (uint64_t)((const uint8_t*)(x))[7]) +#endif +#ifndef AV_WB64 +# define AV_WB64(p, val) do { \ + uint64_t d = (val); \ + ((uint8_t*)(p))[7] = (d); \ + ((uint8_t*)(p))[6] = (d)>>8; \ + ((uint8_t*)(p))[5] = (d)>>16; \ + ((uint8_t*)(p))[4] = (d)>>24; \ + ((uint8_t*)(p))[3] = (d)>>32; \ + ((uint8_t*)(p))[2] = (d)>>40; \ + ((uint8_t*)(p))[1] = (d)>>48; \ + ((uint8_t*)(p))[0] = (d)>>56; \ + } while(0) +#endif + +#ifndef AV_RL64 +# define AV_RL64(x) \ + (((uint64_t)((const uint8_t*)(x))[7] << 56) | \ + ((uint64_t)((const uint8_t*)(x))[6] << 48) | \ + ((uint64_t)((const uint8_t*)(x))[5] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 8) | \ + (uint64_t)((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL64 +# define AV_WL64(p, val) do { \ + uint64_t d = (val); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + ((uint8_t*)(p))[4] = (d)>>32; \ + ((uint8_t*)(p))[5] = (d)>>40; \ + ((uint8_t*)(p))[6] = (d)>>48; \ + ((uint8_t*)(p))[7] = (d)>>56; \ + } while(0) +#endif + +#if AV_HAVE_BIGENDIAN +# define AV_RN(s, p) AV_RB##s(p) +# define AV_WN(s, p, v) AV_WB##s(p, v) +#else +# define AV_RN(s, p) AV_RL##s(p) +# define AV_WN(s, p, v) AV_WL##s(p, v) +#endif + +#endif /* HAVE_FAST_UNALIGNED */ + +#ifndef AV_RN16 +# define AV_RN16(p) AV_RN(16, p) +#endif + +#ifndef AV_RN32 +# define AV_RN32(p) AV_RN(32, p) +#endif + +#ifndef AV_RN64 +# define AV_RN64(p) AV_RN(64, p) +#endif + +#ifndef AV_WN16 +# define AV_WN16(p, v) AV_WN(16, p, v) +#endif + +#ifndef AV_WN32 +# define AV_WN32(p, v) AV_WN(32, p, v) +#endif + +#ifndef AV_WN64 +# define AV_WN64(p, v) AV_WN(64, p, v) +#endif + +#if AV_HAVE_BIGENDIAN +# define AV_RB(s, p) AV_RN##s(p) +# define AV_WB(s, p, v) AV_WN##s(p, v) +# define AV_RL(s, p) av_bswap##s(AV_RN##s(p)) +# define AV_WL(s, p, v) AV_WN##s(p, av_bswap##s(v)) +#else +# define AV_RB(s, p) av_bswap##s(AV_RN##s(p)) +# define AV_WB(s, p, v) AV_WN##s(p, av_bswap##s(v)) +# define AV_RL(s, p) AV_RN##s(p) +# define AV_WL(s, p, v) AV_WN##s(p, v) +#endif + +#define AV_RB8(x) (((const uint8_t*)(x))[0]) +#define AV_WB8(p, d) do { ((uint8_t*)(p))[0] = (d); } while(0) + +#define AV_RL8(x) AV_RB8(x) +#define AV_WL8(p, d) AV_WB8(p, d) + +#ifndef AV_RB16 +# define AV_RB16(p) AV_RB(16, p) +#endif +#ifndef AV_WB16 +# define AV_WB16(p, v) AV_WB(16, p, v) +#endif + +#ifndef AV_RL16 +# define AV_RL16(p) AV_RL(16, p) +#endif +#ifndef AV_WL16 +# define AV_WL16(p, v) AV_WL(16, p, v) +#endif + +#ifndef AV_RB32 +# define AV_RB32(p) AV_RB(32, p) +#endif +#ifndef AV_WB32 +# define AV_WB32(p, v) AV_WB(32, p, v) +#endif + +#ifndef AV_RL32 +# define AV_RL32(p) AV_RL(32, p) +#endif +#ifndef AV_WL32 +# define AV_WL32(p, v) AV_WL(32, p, v) +#endif + +#ifndef AV_RB64 +# define AV_RB64(p) AV_RB(64, p) +#endif +#ifndef AV_WB64 +# define AV_WB64(p, v) AV_WB(64, p, v) +#endif + +#ifndef AV_RL64 +# define AV_RL64(p) AV_RL(64, p) +#endif +#ifndef AV_WL64 +# define AV_WL64(p, v) AV_WL(64, p, v) +#endif + +#ifndef AV_RB24 +# define AV_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) +#endif +#ifndef AV_WB24 +# define AV_WB24(p, d) do { \ + ((uint8_t*)(p))[2] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[0] = (d)>>16; \ + } while(0) +#endif + +#ifndef AV_RL24 +# define AV_RL24(x) \ + ((((const uint8_t*)(x))[2] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL24 +# define AV_WL24(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + } while(0) +#endif + +#ifndef AV_RB48 +# define AV_RB48(x) \ + (((uint64_t)((const uint8_t*)(x))[0] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 8) | \ + (uint64_t)((const uint8_t*)(x))[5]) +#endif +#ifndef AV_WB48 +# define AV_WB48(p, darg) do { \ + uint64_t d = (darg); \ + ((uint8_t*)(p))[5] = (d); \ + ((uint8_t*)(p))[4] = (d)>>8; \ + ((uint8_t*)(p))[3] = (d)>>16; \ + ((uint8_t*)(p))[2] = (d)>>24; \ + ((uint8_t*)(p))[1] = (d)>>32; \ + ((uint8_t*)(p))[0] = (d)>>40; \ + } while(0) +#endif + +#ifndef AV_RL48 +# define AV_RL48(x) \ + (((uint64_t)((const uint8_t*)(x))[5] << 40) | \ + ((uint64_t)((const uint8_t*)(x))[4] << 32) | \ + ((uint64_t)((const uint8_t*)(x))[3] << 24) | \ + ((uint64_t)((const uint8_t*)(x))[2] << 16) | \ + ((uint64_t)((const uint8_t*)(x))[1] << 8) | \ + (uint64_t)((const uint8_t*)(x))[0]) +#endif +#ifndef AV_WL48 +# define AV_WL48(p, darg) do { \ + uint64_t d = (darg); \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + ((uint8_t*)(p))[4] = (d)>>32; \ + ((uint8_t*)(p))[5] = (d)>>40; \ + } while(0) +#endif + +/* + * The AV_[RW]NA macros access naturally aligned data + * in a type-safe way. + */ + +#define AV_RNA(s, p) (((const av_alias##s*)(p))->u##s) +#define AV_WNA(s, p, v) (((av_alias##s*)(p))->u##s = (v)) + +#ifndef AV_RN16A +# define AV_RN16A(p) AV_RNA(16, p) +#endif + +#ifndef AV_RN32A +# define AV_RN32A(p) AV_RNA(32, p) +#endif + +#ifndef AV_RN64A +# define AV_RN64A(p) AV_RNA(64, p) +#endif + +#ifndef AV_WN16A +# define AV_WN16A(p, v) AV_WNA(16, p, v) +#endif + +#ifndef AV_WN32A +# define AV_WN32A(p, v) AV_WNA(32, p, v) +#endif + +#ifndef AV_WN64A +# define AV_WN64A(p, v) AV_WNA(64, p, v) +#endif + +/* + * The AV_COPYxxU macros are suitable for copying data to/from unaligned + * memory locations. + */ + +#define AV_COPYU(n, d, s) AV_WN##n(d, AV_RN##n(s)); + +#ifndef AV_COPY16U +# define AV_COPY16U(d, s) AV_COPYU(16, d, s) +#endif + +#ifndef AV_COPY32U +# define AV_COPY32U(d, s) AV_COPYU(32, d, s) +#endif + +#ifndef AV_COPY64U +# define AV_COPY64U(d, s) AV_COPYU(64, d, s) +#endif + +#ifndef AV_COPY128U +# define AV_COPY128U(d, s) \ + do { \ + AV_COPY64U(d, s); \ + AV_COPY64U((char *)(d) + 8, (const char *)(s) + 8); \ + } while(0) +#endif + +/* Parameters for AV_COPY*, AV_SWAP*, AV_ZERO* must be + * naturally aligned. They may be implemented using MMX, + * so emms_c() must be called before using any float code + * afterwards. + */ + +#define AV_COPY(n, d, s) \ + (((av_alias##n*)(d))->u##n = ((const av_alias##n*)(s))->u##n) + +#ifndef AV_COPY16 +# define AV_COPY16(d, s) AV_COPY(16, d, s) +#endif + +#ifndef AV_COPY32 +# define AV_COPY32(d, s) AV_COPY(32, d, s) +#endif + +#ifndef AV_COPY64 +# define AV_COPY64(d, s) AV_COPY(64, d, s) +#endif + +#ifndef AV_COPY128 +# define AV_COPY128(d, s) \ + do { \ + AV_COPY64(d, s); \ + AV_COPY64((char*)(d)+8, (char*)(s)+8); \ + } while(0) +#endif + +#define AV_SWAP(n, a, b) FFSWAP(av_alias##n, *(av_alias##n*)(a), *(av_alias##n*)(b)) + +#ifndef AV_SWAP64 +# define AV_SWAP64(a, b) AV_SWAP(64, a, b) +#endif + +#define AV_ZERO(n, d) (((av_alias##n*)(d))->u##n = 0) + +#ifndef AV_ZERO16 +# define AV_ZERO16(d) AV_ZERO(16, d) +#endif + +#ifndef AV_ZERO32 +# define AV_ZERO32(d) AV_ZERO(32, d) +#endif + +#ifndef AV_ZERO64 +# define AV_ZERO64(d) AV_ZERO(64, d) +#endif + +#ifndef AV_ZERO128 +# define AV_ZERO128(d) \ + do { \ + AV_ZERO64(d); \ + AV_ZERO64((char*)(d)+8); \ + } while(0) +#endif + +#endif /* AVUTIL_INTREADWRITE_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/lfg.h b/thrid-party/ffmpeg/include/libavutil/lfg.h new file mode 100644 index 0000000000..03f779ad8a --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/lfg.h @@ -0,0 +1,71 @@ +/* + * Lagged Fibonacci PRNG + * Copyright (c) 2008 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LFG_H +#define AVUTIL_LFG_H + +#include + +typedef struct AVLFG { + unsigned int state[64]; + int index; +} AVLFG; + +void av_lfg_init(AVLFG *c, unsigned int seed); + +/** + * Seed the state of the ALFG using binary data. + * + * Return value: 0 on success, negative value (AVERROR) on failure. + */ +int av_lfg_init_from_data(AVLFG *c, const uint8_t *data, unsigned int length); + +/** + * Get the next random unsigned 32-bit number using an ALFG. + * + * Please also consider a simple LCG like state= state*1664525+1013904223, + * it may be good enough and faster for your specific use case. + */ +static inline unsigned int av_lfg_get(AVLFG *c){ + c->state[c->index & 63] = c->state[(c->index-24) & 63] + c->state[(c->index-55) & 63]; + return c->state[c->index++ & 63]; +} + +/** + * Get the next random unsigned 32-bit number using a MLFG. + * + * Please also consider av_lfg_get() above, it is faster. + */ +static inline unsigned int av_mlfg_get(AVLFG *c){ + unsigned int a= c->state[(c->index-55) & 63]; + unsigned int b= c->state[(c->index-24) & 63]; + return c->state[c->index++ & 63] = 2*a*b+a+b; +} + +/** + * Get the next two numbers generated by a Box-Muller Gaussian + * generator using the random numbers issued by lfg. + * + * @param out array where the two generated numbers are placed + */ +void av_bmg_get(AVLFG *lfg, double out[2]); + +#endif /* AVUTIL_LFG_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/log.h b/thrid-party/ffmpeg/include/libavutil/log.h new file mode 100644 index 0000000000..f0a57385df --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/log.h @@ -0,0 +1,376 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LOG_H +#define AVUTIL_LOG_H + +#include +#include "avutil.h" +#include "attributes.h" +#include "version.h" + +typedef enum { + AV_CLASS_CATEGORY_NA = 0, + AV_CLASS_CATEGORY_INPUT, + AV_CLASS_CATEGORY_OUTPUT, + AV_CLASS_CATEGORY_MUXER, + AV_CLASS_CATEGORY_DEMUXER, + AV_CLASS_CATEGORY_ENCODER, + AV_CLASS_CATEGORY_DECODER, + AV_CLASS_CATEGORY_FILTER, + AV_CLASS_CATEGORY_BITSTREAM_FILTER, + AV_CLASS_CATEGORY_SWSCALER, + AV_CLASS_CATEGORY_SWRESAMPLER, + AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40, + AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, + AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT, + AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT, + AV_CLASS_CATEGORY_DEVICE_OUTPUT, + AV_CLASS_CATEGORY_DEVICE_INPUT, + AV_CLASS_CATEGORY_NB ///< not part of ABI/API +}AVClassCategory; + +#define AV_IS_INPUT_DEVICE(category) \ + (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_INPUT)) + +#define AV_IS_OUTPUT_DEVICE(category) \ + (((category) == AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT) || \ + ((category) == AV_CLASS_CATEGORY_DEVICE_OUTPUT)) + +struct AVOptionRanges; + +/** + * Describe the class of an AVClass context structure. That is an + * arbitrary struct of which the first field is a pointer to an + * AVClass struct (e.g. AVCodecContext, AVFormatContext etc.). + */ +typedef struct AVClass { + /** + * The name of the class; usually it is the same name as the + * context structure type to which the AVClass is associated. + */ + const char* class_name; + + /** + * A pointer to a function which returns the name of a context + * instance ctx associated with the class. + */ + const char* (*item_name)(void* ctx); + + /** + * a pointer to the first option specified in the class if any or NULL + * + * @see av_set_default_options() + */ + const struct AVOption *option; + + /** + * LIBAVUTIL_VERSION with which this structure was created. + * This is used to allow fields to be added without requiring major + * version bumps everywhere. + */ + + int version; + + /** + * Offset in the structure where log_level_offset is stored. + * 0 means there is no such variable + */ + int log_level_offset_offset; + + /** + * Offset in the structure where a pointer to the parent context for + * logging is stored. For example a decoder could pass its AVCodecContext + * to eval as such a parent context, which an av_log() implementation + * could then leverage to display the parent context. + * The offset can be NULL. + */ + int parent_log_context_offset; + + /** + * Return next AVOptions-enabled child or NULL + */ + void* (*child_next)(void *obj, void *prev); + + /** + * Return an AVClass corresponding to the next potential + * AVOptions-enabled child. + * + * The difference between child_next and this is that + * child_next iterates over _already existing_ objects, while + * child_class_next iterates over _all possible_ children. + */ + const struct AVClass* (*child_class_next)(const struct AVClass *prev); + + /** + * Category used for visualization (like color) + * This is only set if the category is equal for all objects using this class. + * available since version (51 << 16 | 56 << 8 | 100) + */ + AVClassCategory category; + + /** + * Callback to return the category. + * available since version (51 << 16 | 59 << 8 | 100) + */ + AVClassCategory (*get_category)(void* ctx); + + /** + * Callback to return the supported/allowed ranges. + * available since version (52.12) + */ + int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags); +} AVClass; + +/** + * @addtogroup lavu_log + * + * @{ + * + * @defgroup lavu_log_constants Logging Constants + * + * @{ + */ + +/** + * Print no output. + */ +#define AV_LOG_QUIET -8 + +/** + * Something went really wrong and we will crash now. + */ +#define AV_LOG_PANIC 0 + +/** + * Something went wrong and recovery is not possible. + * For example, no header was found for a format which depends + * on headers or an illegal combination of parameters is used. + */ +#define AV_LOG_FATAL 8 + +/** + * Something went wrong and cannot losslessly be recovered. + * However, not all future data is affected. + */ +#define AV_LOG_ERROR 16 + +/** + * Something somehow does not look correct. This may or may not + * lead to problems. An example would be the use of '-vstrict -2'. + */ +#define AV_LOG_WARNING 24 + +/** + * Standard information. + */ +#define AV_LOG_INFO 32 + +/** + * Detailed information. + */ +#define AV_LOG_VERBOSE 40 + +/** + * Stuff which is only useful for libav* developers. + */ +#define AV_LOG_DEBUG 48 + +/** + * Extremely verbose debugging, useful for libav* development. + */ +#define AV_LOG_TRACE 56 + +#define AV_LOG_MAX_OFFSET (AV_LOG_TRACE - AV_LOG_QUIET) + +/** + * @} + */ + +/** + * Sets additional colors for extended debugging sessions. + * @code + av_log(ctx, AV_LOG_DEBUG|AV_LOG_C(134), "Message in purple\n"); + @endcode + * Requires 256color terminal support. Uses outside debugging is not + * recommended. + */ +#define AV_LOG_C(x) ((x) << 8) + +/** + * Send the specified message to the log if the level is less than or equal + * to the current av_log_level. By default, all logging messages are sent to + * stderr. This behavior can be altered by setting a different logging callback + * function. + * @see av_log_set_callback + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct or NULL if general log. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + */ +void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4); + + +/** + * Send the specified message to the log if the level is less than or equal + * to the current av_log_level. By default, all logging messages are sent to + * stderr. This behavior can be altered by setting a different logging callback + * function. + * @see av_log_set_callback + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + * @param vl The arguments referenced by the format string. + */ +void av_vlog(void *avcl, int level, const char *fmt, va_list vl); + +/** + * Get the current log level + * + * @see lavu_log_constants + * + * @return Current log level + */ +int av_log_get_level(void); + +/** + * Set the log level + * + * @see lavu_log_constants + * + * @param level Logging level + */ +void av_log_set_level(int level); + +/** + * Set the logging callback + * + * @note The callback must be thread safe, even if the application does not use + * threads itself as some codecs are multithreaded. + * + * @see av_log_default_callback + * + * @param callback A logging function with a compatible signature. + */ +void av_log_set_callback(void (*callback)(void*, int, const char*, va_list)); + +/** + * Default logging callback + * + * It prints the message to stderr, optionally colorizing it. + * + * @param avcl A pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct. + * @param level The importance level of the message expressed using a @ref + * lavu_log_constants "Logging Constant". + * @param fmt The format string (printf-compatible) that specifies how + * subsequent arguments are converted to output. + * @param vl The arguments referenced by the format string. + */ +void av_log_default_callback(void *avcl, int level, const char *fmt, + va_list vl); + +/** + * Return the context name + * + * @param ctx The AVClass context + * + * @return The AVClass class_name + */ +const char* av_default_item_name(void* ctx); +AVClassCategory av_default_get_category(void *ptr); + +/** + * Format a line of log the same way as the default callback. + * @param line buffer to receive the formatted line + * @param line_size size of the buffer + * @param print_prefix used to store whether the prefix must be printed; + * must point to a persistent integer initially set to 1 + */ +void av_log_format_line(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix); + +/** + * Format a line of log the same way as the default callback. + * @param line buffer to receive the formatted line; + * may be NULL if line_size is 0 + * @param line_size size of the buffer; at most line_size-1 characters will + * be written to the buffer, plus one null terminator + * @param print_prefix used to store whether the prefix must be printed; + * must point to a persistent integer initially set to 1 + * @return Returns a negative value if an error occurred, otherwise returns + * the number of characters that would have been written for a + * sufficiently large buffer, not including the terminating null + * character. If the return value is not less than line_size, it means + * that the log message was truncated to fit the buffer. + */ +int av_log_format_line2(void *ptr, int level, const char *fmt, va_list vl, + char *line, int line_size, int *print_prefix); + +#if FF_API_DLOG +/** + * av_dlog macros + * @deprecated unused + * Useful to print debug messages that shouldn't get compiled in normally. + */ + +#ifdef DEBUG +# define av_dlog(pctx, ...) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__) +#else +# define av_dlog(pctx, ...) do { if (0) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0) +#endif +#endif /* FF_API_DLOG */ + +/** + * Skip repeated messages, this requires the user app to use av_log() instead of + * (f)printf as the 2 would otherwise interfere and lead to + * "Last message repeated x times" messages below (f)printf messages with some + * bad luck. + * Also to receive the last, "last repeated" line if any, the user app must + * call av_log(NULL, AV_LOG_QUIET, "%s", ""); at the end + */ +#define AV_LOG_SKIP_REPEATED 1 + +/** + * Include the log severity in messages originating from codecs. + * + * Results in messages such as: + * [rawvideo @ 0xDEADBEEF] [error] encode did not produce valid pts + */ +#define AV_LOG_PRINT_LEVEL 2 + +void av_log_set_flags(int arg); +int av_log_get_flags(void); + +/** + * @} + */ + +#endif /* AVUTIL_LOG_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/lzo.h b/thrid-party/ffmpeg/include/libavutil/lzo.h new file mode 100644 index 0000000000..c03403992d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/lzo.h @@ -0,0 +1,66 @@ +/* + * LZO 1x decompression + * copyright (c) 2006 Reimar Doeffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_LZO_H +#define AVUTIL_LZO_H + +/** + * @defgroup lavu_lzo LZO + * @ingroup lavu_crypto + * + * @{ + */ + +#include + +/** @name Error flags returned by av_lzo1x_decode + * @{ */ +/// end of the input buffer reached before decoding finished +#define AV_LZO_INPUT_DEPLETED 1 +/// decoded data did not fit into output buffer +#define AV_LZO_OUTPUT_FULL 2 +/// a reference to previously decoded data was wrong +#define AV_LZO_INVALID_BACKPTR 4 +/// a non-specific error in the compressed bitstream +#define AV_LZO_ERROR 8 +/** @} */ + +#define AV_LZO_INPUT_PADDING 8 +#define AV_LZO_OUTPUT_PADDING 12 + +/** + * @brief Decodes LZO 1x compressed data. + * @param out output buffer + * @param outlen size of output buffer, number of bytes left are returned here + * @param in input buffer + * @param inlen size of input buffer, number of bytes left are returned here + * @return 0 on success, otherwise a combination of the error flags above + * + * Make sure all buffers are appropriately padded, in must provide + * AV_LZO_INPUT_PADDING, out must provide AV_LZO_OUTPUT_PADDING additional bytes. + */ +int av_lzo1x_decode(void *out, int *outlen, const void *in, int *inlen); + +/** + * @} + */ + +#endif /* AVUTIL_LZO_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/macros.h b/thrid-party/ffmpeg/include/libavutil/macros.h new file mode 100644 index 0000000000..2007ee5619 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/macros.h @@ -0,0 +1,50 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu + * Utility Preprocessor macros + */ + +#ifndef AVUTIL_MACROS_H +#define AVUTIL_MACROS_H + +/** + * @addtogroup preproc_misc Preprocessor String Macros + * + * String manipulation macros + * + * @{ + */ + +#define AV_STRINGIFY(s) AV_TOSTRING(s) +#define AV_TOSTRING(s) #s + +#define AV_GLUE(a, b) a ## b +#define AV_JOIN(a, b) AV_GLUE(a, b) + +/** + * @} + */ + +#define AV_PRAGMA(s) _Pragma(#s) + +#define FFALIGN(x, a) (((x)+(a)-1)&~((a)-1)) + +#endif /* AVUTIL_MACROS_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/mastering_display_metadata.h b/thrid-party/ffmpeg/include/libavutil/mastering_display_metadata.h new file mode 100644 index 0000000000..847b0b62c6 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/mastering_display_metadata.h @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2016 Neil Birkbeck + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_MASTERING_DISPLAY_METADATA_H +#define AVUTIL_MASTERING_DISPLAY_METADATA_H + +#include "frame.h" +#include "rational.h" + + +/** + * Mastering display metadata capable of representing the color volume of + * the display used to master the content (SMPTE 2086:2014). + * + * To be used as payload of a AVFrameSideData or AVPacketSideData with the + * appropriate type. + * + * @note The struct should be allocated with av_mastering_display_metadata_alloc() + * and its size is not a part of the public ABI. + */ +typedef struct AVMasteringDisplayMetadata { + /** + * CIE 1931 xy chromaticity coords of color primaries (r, g, b order). + */ + AVRational display_primaries[3][2]; + + /** + * CIE 1931 xy chromaticity coords of white point. + */ + AVRational white_point[2]; + + /** + * Min luminance of mastering display (cd/m^2). + */ + AVRational min_luminance; + + /** + * Max luminance of mastering display (cd/m^2). + */ + AVRational max_luminance; + + /** + * Flag indicating whether the display primaries (and white point) are set. + */ + int has_primaries; + + /** + * Flag indicating whether the luminance (min_ and max_) have been set. + */ + int has_luminance; + +} AVMasteringDisplayMetadata; + +/** + * Allocate an AVMasteringDisplayMetadata structure and set its fields to + * default values. The resulting struct can be freed using av_freep(). + * + * @return An AVMasteringDisplayMetadata filled with default values or NULL + * on failure. + */ +AVMasteringDisplayMetadata *av_mastering_display_metadata_alloc(void); + +/** + * Allocate a complete AVMasteringDisplayMetadata and add it to the frame. + * + * @param frame The frame which side data is added to. + * + * @return The AVMasteringDisplayMetadata structure to be filled by caller. + */ +AVMasteringDisplayMetadata *av_mastering_display_metadata_create_side_data(AVFrame *frame); + +/** + * Content light level needed by to transmit HDR over HDMI (CTA-861.3). + * + * To be used as payload of a AVFrameSideData or AVPacketSideData with the + * appropriate type. + * + * @note The struct should be allocated with av_content_light_metadata_alloc() + * and its size is not a part of the public ABI. + */ +typedef struct AVContentLightMetadata { + /** + * Max content light level (cd/m^2). + */ + unsigned MaxCLL; + + /** + * Max average light level per frame (cd/m^2). + */ + unsigned MaxFALL; +} AVContentLightMetadata; + +/** + * Allocate an AVContentLightMetadata structure and set its fields to + * default values. The resulting struct can be freed using av_freep(). + * + * @return An AVContentLightMetadata filled with default values or NULL + * on failure. + */ +AVContentLightMetadata *av_content_light_metadata_alloc(size_t *size); + +/** + * Allocate a complete AVContentLightMetadata and add it to the frame. + * + * @param frame The frame which side data is added to. + * + * @return The AVContentLightMetadata structure to be filled by caller. + */ +AVContentLightMetadata *av_content_light_metadata_create_side_data(AVFrame *frame); + +#endif /* AVUTIL_MASTERING_DISPLAY_METADATA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/mathematics.h b/thrid-party/ffmpeg/include/libavutil/mathematics.h new file mode 100644 index 0000000000..54901800ba --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/mathematics.h @@ -0,0 +1,242 @@ +/* + * copyright (c) 2005-2012 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @addtogroup lavu_math + * Mathematical utilities for working with timestamp and time base. + */ + +#ifndef AVUTIL_MATHEMATICS_H +#define AVUTIL_MATHEMATICS_H + +#include +#include +#include "attributes.h" +#include "rational.h" +#include "intfloat.h" + +#ifndef M_E +#define M_E 2.7182818284590452354 /* e */ +#endif +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif +#ifndef M_LOG2_10 +#define M_LOG2_10 3.32192809488736234787 /* log_2 10 */ +#endif +#ifndef M_PHI +#define M_PHI 1.61803398874989484820 /* phi / golden ratio */ +#endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif +#ifndef NAN +#define NAN av_int2float(0x7fc00000) +#endif +#ifndef INFINITY +#define INFINITY av_int2float(0x7f800000) +#endif + +/** + * @addtogroup lavu_math + * + * @{ + */ + +/** + * Rounding methods. + */ +enum AVRounding { + AV_ROUND_ZERO = 0, ///< Round toward zero. + AV_ROUND_INF = 1, ///< Round away from zero. + AV_ROUND_DOWN = 2, ///< Round toward -infinity. + AV_ROUND_UP = 3, ///< Round toward +infinity. + AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero. + /** + * Flag telling rescaling functions to pass `INT64_MIN`/`MAX` through + * unchanged, avoiding special cases for #AV_NOPTS_VALUE. + * + * Unlike other values of the enumeration AVRounding, this value is a + * bitmask that must be used in conjunction with another value of the + * enumeration through a bitwise OR, in order to set behavior for normal + * cases. + * + * @code{.c} + * av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + * // Rescaling 3: + * // Calculating 3 * 1 / 2 + * // 3 / 2 is rounded up to 2 + * // => 2 + * + * av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); + * // Rescaling AV_NOPTS_VALUE: + * // AV_NOPTS_VALUE == INT64_MIN + * // AV_NOPTS_VALUE is passed through + * // => AV_NOPTS_VALUE + * @endcode + */ + AV_ROUND_PASS_MINMAX = 8192, +}; + +/** + * Compute the greatest common divisor of two integer operands. + * + * @param a,b Operands + * @return GCD of a and b up to sign; if a >= 0 and b >= 0, return value is >= 0; + * if a == 0 and b == 0, returns 0. + */ +int64_t av_const av_gcd(int64_t a, int64_t b); + +/** + * Rescale a 64-bit integer with rounding to nearest. + * + * The operation is mathematically equivalent to `a * b / c`, but writing that + * directly can overflow. + * + * This function is equivalent to av_rescale_rnd() with #AV_ROUND_NEAR_INF. + * + * @see av_rescale_rnd(), av_rescale_q(), av_rescale_q_rnd() + */ +int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const; + +/** + * Rescale a 64-bit integer with specified rounding. + * + * The operation is mathematically equivalent to `a * b / c`, but writing that + * directly can overflow, and does not support different rounding methods. + * + * @see av_rescale(), av_rescale_q(), av_rescale_q_rnd() + */ +int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const; + +/** + * Rescale a 64-bit integer by 2 rational numbers. + * + * The operation is mathematically equivalent to `a * bq / cq`. + * + * This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF. + * + * @see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd() + */ +int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const; + +/** + * Rescale a 64-bit integer by 2 rational numbers with specified rounding. + * + * The operation is mathematically equivalent to `a * bq / cq`. + * + * @see av_rescale(), av_rescale_rnd(), av_rescale_q() + */ +int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, + enum AVRounding rnd) av_const; + +/** + * Compare two timestamps each in its own time base. + * + * @return One of the following values: + * - -1 if `ts_a` is before `ts_b` + * - 1 if `ts_a` is after `ts_b` + * - 0 if they represent the same position + * + * @warning + * The result of the function is undefined if one of the timestamps is outside + * the `int64_t` range when represented in the other's timebase. + */ +int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b); + +/** + * Compare the remainders of two integer operands divided by a common divisor. + * + * In other words, compare the least significant `log2(mod)` bits of integers + * `a` and `b`. + * + * @code{.c} + * av_compare_mod(0x11, 0x02, 0x10) < 0 // since 0x11 % 0x10 (0x1) < 0x02 % 0x10 (0x2) + * av_compare_mod(0x11, 0x02, 0x20) > 0 // since 0x11 % 0x20 (0x11) > 0x02 % 0x20 (0x02) + * @endcode + * + * @param a,b Operands + * @param mod Divisor; must be a power of 2 + * @return + * - a negative value if `a % mod < b % mod` + * - a positive value if `a % mod > b % mod` + * - zero if `a % mod == b % mod` + */ +int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod); + +/** + * Rescale a timestamp while preserving known durations. + * + * This function is designed to be called per audio packet to scale the input + * timestamp to a different time base. Compared to a simple av_rescale_q() + * call, this function is robust against possible inconsistent frame durations. + * + * The `last` parameter is a state variable that must be preserved for all + * subsequent calls for the same stream. For the first call, `*last` should be + * initialized to #AV_NOPTS_VALUE. + * + * @param[in] in_tb Input time base + * @param[in] in_ts Input timestamp + * @param[in] fs_tb Duration time base; typically this is finer-grained + * (greater) than `in_tb` and `out_tb` + * @param[in] duration Duration till the next call to this function (i.e. + * duration of the current packet/frame) + * @param[in,out] last Pointer to a timestamp expressed in terms of + * `fs_tb`, acting as a state variable + * @param[in] out_tb Output timebase + * @return Timestamp expressed in terms of `out_tb` + * + * @note In the context of this function, "duration" is in term of samples, not + * seconds. + */ +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb); + +/** + * Add a value to a timestamp. + * + * This function guarantees that when the same value is repeatly added that + * no accumulation of rounding errors occurs. + * + * @param[in] ts Input timestamp + * @param[in] ts_tb Input timestamp time base + * @param[in] inc Value to be added + * @param[in] inc_tb Time base of `inc` + */ +int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc); + + +/** + * @} + */ + +#endif /* AVUTIL_MATHEMATICS_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/md5.h b/thrid-party/ffmpeg/include/libavutil/md5.h new file mode 100644 index 0000000000..ca72ccbf83 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/md5.h @@ -0,0 +1,98 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_md5 + * Public header for MD5 hash function implementation. + */ + +#ifndef AVUTIL_MD5_H +#define AVUTIL_MD5_H + +#include +#include + +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_md5 MD5 + * @ingroup lavu_hash + * MD5 hash function implementation. + * + * @{ + */ + +extern const int av_md5_size; + +struct AVMD5; + +/** + * Allocate an AVMD5 context. + */ +struct AVMD5 *av_md5_alloc(void); + +/** + * Initialize MD5 hashing. + * + * @param ctx pointer to the function context (of size av_md5_size) + */ +void av_md5_init(struct AVMD5 *ctx); + +/** + * Update hash value. + * + * @param ctx hash function context + * @param src input data to update hash with + * @param len input data length + */ +#if FF_API_CRYPTO_SIZE_T +void av_md5_update(struct AVMD5 *ctx, const uint8_t *src, int len); +#else +void av_md5_update(struct AVMD5 *ctx, const uint8_t *src, size_t len); +#endif + +/** + * Finish hashing and output digest value. + * + * @param ctx hash function context + * @param dst buffer where output digest value is stored + */ +void av_md5_final(struct AVMD5 *ctx, uint8_t *dst); + +/** + * Hash an array of data. + * + * @param dst The output buffer to write the digest into + * @param src The data to hash + * @param len The length of the data, in bytes + */ +#if FF_API_CRYPTO_SIZE_T +void av_md5_sum(uint8_t *dst, const uint8_t *src, const int len); +#else +void av_md5_sum(uint8_t *dst, const uint8_t *src, size_t len); +#endif + +/** + * @} + */ + +#endif /* AVUTIL_MD5_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/mem.h b/thrid-party/ffmpeg/include/libavutil/mem.h new file mode 100644 index 0000000000..527cd03191 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/mem.h @@ -0,0 +1,699 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_mem + * Memory handling functions + */ + +#ifndef AVUTIL_MEM_H +#define AVUTIL_MEM_H + +#include +#include + +#include "attributes.h" +#include "error.h" +#include "avutil.h" + +/** + * @addtogroup lavu_mem + * Utilities for manipulating memory. + * + * FFmpeg has several applications of memory that are not required of a typical + * program. For example, the computing-heavy components like video decoding and + * encoding can be sped up significantly through the use of aligned memory. + * + * However, for each of FFmpeg's applications of memory, there might not be a + * recognized or standardized API for that specific use. Memory alignment, for + * instance, varies wildly depending on operating systems, architectures, and + * compilers. Hence, this component of @ref libavutil is created to make + * dealing with memory consistently possible on all platforms. + * + * @{ + * + * @defgroup lavu_mem_macros Alignment Macros + * Helper macros for declaring aligned variables. + * @{ + */ + +/** + * @def DECLARE_ALIGNED(n,t,v) + * Declare a variable that is aligned in memory. + * + * @code{.c} + * DECLARE_ALIGNED(16, uint16_t, aligned_int) = 42; + * DECLARE_ALIGNED(32, uint8_t, aligned_array)[128]; + * + * // The default-alignment equivalent would be + * uint16_t aligned_int = 42; + * uint8_t aligned_array[128]; + * @endcode + * + * @param n Minimum alignment in bytes + * @param t Type of the variable (or array element) + * @param v Name of the variable + */ + +/** + * @def DECLARE_ASM_CONST(n,t,v) + * Declare a static constant aligned variable appropriate for use in inline + * assembly code. + * + * @code{.c} + * DECLARE_ASM_CONST(16, uint64_t, pw_08) = UINT64_C(0x0008000800080008); + * @endcode + * + * @param n Minimum alignment in bytes + * @param t Type of the variable (or array element) + * @param v Name of the variable + */ + +#if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1110 || defined(__SUNPRO_C) + #define DECLARE_ALIGNED(n,t,v) t __attribute__ ((aligned (n))) v + #define DECLARE_ASM_CONST(n,t,v) const t __attribute__ ((aligned (n))) v +#elif defined(__TI_COMPILER_VERSION__) + #define DECLARE_ALIGNED(n,t,v) \ + AV_PRAGMA(DATA_ALIGN(v,n)) \ + t __attribute__((aligned(n))) v + #define DECLARE_ASM_CONST(n,t,v) \ + AV_PRAGMA(DATA_ALIGN(v,n)) \ + static const t __attribute__((aligned(n))) v +#elif defined(__DJGPP__) + #define DECLARE_ALIGNED(n,t,v) t __attribute__ ((aligned (FFMIN(n, 16)))) v + #define DECLARE_ASM_CONST(n,t,v) static const t av_used __attribute__ ((aligned (FFMIN(n, 16)))) v +#elif defined(__GNUC__) || defined(__clang__) + #define DECLARE_ALIGNED(n,t,v) t __attribute__ ((aligned (n))) v + #define DECLARE_ASM_CONST(n,t,v) static const t av_used __attribute__ ((aligned (n))) v +#elif defined(_MSC_VER) + #define DECLARE_ALIGNED(n,t,v) __declspec(align(n)) t v + #define DECLARE_ASM_CONST(n,t,v) __declspec(align(n)) static const t v +#else + #define DECLARE_ALIGNED(n,t,v) t v + #define DECLARE_ASM_CONST(n,t,v) static const t v +#endif + +/** + * @} + */ + +/** + * @defgroup lavu_mem_attrs Function Attributes + * Function attributes applicable to memory handling functions. + * + * These function attributes can help compilers emit more useful warnings, or + * generate better code. + * @{ + */ + +/** + * @def av_malloc_attrib + * Function attribute denoting a malloc-like function. + * + * @see Function attribute `malloc` in GCC's documentation + */ + +#if AV_GCC_VERSION_AT_LEAST(3,1) + #define av_malloc_attrib __attribute__((__malloc__)) +#else + #define av_malloc_attrib +#endif + +/** + * @def av_alloc_size(...) + * Function attribute used on a function that allocates memory, whose size is + * given by the specified parameter(s). + * + * @code{.c} + * void *av_malloc(size_t size) av_alloc_size(1); + * void *av_calloc(size_t nmemb, size_t size) av_alloc_size(1, 2); + * @endcode + * + * @param ... One or two parameter indexes, separated by a comma + * + * @see Function attribute `alloc_size` in GCC's documentation + */ + +#if AV_GCC_VERSION_AT_LEAST(4,3) + #define av_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else + #define av_alloc_size(...) +#endif + +/** + * @} + */ + +/** + * @defgroup lavu_mem_funcs Heap Management + * Functions responsible for allocating, freeing, and copying memory. + * + * All memory allocation functions have a built-in upper limit of `INT_MAX` + * bytes. This may be changed with av_max_alloc(), although exercise extreme + * caution when doing so. + * + * @{ + */ + +/** + * Allocate a memory block with alignment suitable for all memory accesses + * (including vectors if available on the CPU). + * + * @param size Size in bytes for the memory block to be allocated + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * @see av_mallocz() + */ +void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1); + +/** + * Allocate a memory block with alignment suitable for all memory accesses + * (including vectors if available on the CPU) and zero all the bytes of the + * block. + * + * @param size Size in bytes for the memory block to be allocated + * @return Pointer to the allocated block, or `NULL` if it cannot be allocated + * @see av_malloc() + */ +void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1); + +/** + * Allocate a memory block for an array with av_malloc(). + * + * The allocated memory will have size `size * nmemb` bytes. + * + * @param nmemb Number of element + * @param size Size of a single element + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * @see av_malloc() + */ +av_alloc_size(1, 2) static inline void *av_malloc_array(size_t nmemb, size_t size) +{ + if (!size || nmemb >= INT_MAX / size) + return NULL; + return av_malloc(nmemb * size); +} + +/** + * Allocate a memory block for an array with av_mallocz(). + * + * The allocated memory will have size `size * nmemb` bytes. + * + * @param nmemb Number of elements + * @param size Size of the single element + * @return Pointer to the allocated block, or `NULL` if the block cannot + * be allocated + * + * @see av_mallocz() + * @see av_malloc_array() + */ +av_alloc_size(1, 2) static inline void *av_mallocz_array(size_t nmemb, size_t size) +{ + if (!size || nmemb >= INT_MAX / size) + return NULL; + return av_mallocz(nmemb * size); +} + +/** + * Non-inlined equivalent of av_mallocz_array(). + * + * Created for symmetry with the calloc() C function. + */ +void *av_calloc(size_t nmemb, size_t size) av_malloc_attrib; + +/** + * Allocate, reallocate, or free a block of memory. + * + * If `ptr` is `NULL` and `size` > 0, allocate a new block. If `size` is + * zero, free the memory block pointed to by `ptr`. Otherwise, expand or + * shrink that block of memory according to `size`. + * + * @param ptr Pointer to a memory block already allocated with + * av_realloc() or `NULL` + * @param size Size in bytes of the memory block to be allocated or + * reallocated + * + * @return Pointer to a newly-reallocated block or `NULL` if the block + * cannot be reallocated or the function is used to free the memory block + * + * @warning Unlike av_malloc(), the returned pointer is not guaranteed to be + * correctly aligned. + * @see av_fast_realloc() + * @see av_reallocp() + */ +void *av_realloc(void *ptr, size_t size) av_alloc_size(2); + +/** + * Allocate, reallocate, or free a block of memory through a pointer to a + * pointer. + * + * If `*ptr` is `NULL` and `size` > 0, allocate a new block. If `size` is + * zero, free the memory block pointed to by `*ptr`. Otherwise, expand or + * shrink that block of memory according to `size`. + * + * @param[in,out] ptr Pointer to a pointer to a memory block already allocated + * with av_realloc(), or a pointer to `NULL`. The pointer + * is updated on success, or freed on failure. + * @param[in] size Size in bytes for the memory block to be allocated or + * reallocated + * + * @return Zero on success, an AVERROR error code on failure + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. + */ +av_warn_unused_result +int av_reallocp(void *ptr, size_t size); + +/** + * Allocate, reallocate, or free a block of memory. + * + * This function does the same thing as av_realloc(), except: + * - It takes two size arguments and allocates `nelem * elsize` bytes, + * after checking the result of the multiplication for integer overflow. + * - It frees the input block in case of failure, thus avoiding the memory + * leak with the classic + * @code{.c} + * buf = realloc(buf); + * if (!buf) + * return -1; + * @endcode + * pattern. + */ +void *av_realloc_f(void *ptr, size_t nelem, size_t elsize); + +/** + * Allocate, reallocate, or free an array. + * + * If `ptr` is `NULL` and `nmemb` > 0, allocate a new block. If + * `nmemb` is zero, free the memory block pointed to by `ptr`. + * + * @param ptr Pointer to a memory block already allocated with + * av_realloc() or `NULL` + * @param nmemb Number of elements in the array + * @param size Size of the single element of the array + * + * @return Pointer to a newly-reallocated block or NULL if the block + * cannot be reallocated or the function is used to free the memory block + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. + * @see av_reallocp_array() + */ +av_alloc_size(2, 3) void *av_realloc_array(void *ptr, size_t nmemb, size_t size); + +/** + * Allocate, reallocate, or free an array through a pointer to a pointer. + * + * If `*ptr` is `NULL` and `nmemb` > 0, allocate a new block. If `nmemb` is + * zero, free the memory block pointed to by `*ptr`. + * + * @param[in,out] ptr Pointer to a pointer to a memory block already + * allocated with av_realloc(), or a pointer to `NULL`. + * The pointer is updated on success, or freed on failure. + * @param[in] nmemb Number of elements + * @param[in] size Size of the single element + * + * @return Zero on success, an AVERROR error code on failure + * + * @warning Unlike av_malloc(), the allocated memory is not guaranteed to be + * correctly aligned. + */ +av_alloc_size(2, 3) int av_reallocp_array(void *ptr, size_t nmemb, size_t size); + +/** + * Reallocate the given buffer if it is not large enough, otherwise do nothing. + * + * If the given buffer is `NULL`, then a new uninitialized buffer is allocated. + * + * If the given buffer is not large enough, and reallocation fails, `NULL` is + * returned and `*size` is set to 0, but the original buffer is not changed or + * freed. + * + * A typical use pattern follows: + * + * @code{.c} + * uint8_t *buf = ...; + * uint8_t *new_buf = av_fast_realloc(buf, ¤t_size, size_needed); + * if (!new_buf) { + * // Allocation failed; clean up original buffer + * av_freep(&buf); + * return AVERROR(ENOMEM); + * } + * @endcode + * + * @param[in,out] ptr Already allocated buffer, or `NULL` + * @param[in,out] size Pointer to current size of buffer `ptr`. `*size` is + * changed to `min_size` in case of success or 0 in + * case of failure + * @param[in] min_size New size of buffer `ptr` + * @return `ptr` if the buffer is large enough, a pointer to newly reallocated + * buffer if the buffer was not large enough, or `NULL` in case of + * error + * @see av_realloc() + * @see av_fast_malloc() + */ +void *av_fast_realloc(void *ptr, unsigned int *size, size_t min_size); + +/** + * Allocate a buffer, reusing the given one if large enough. + * + * Contrary to av_fast_realloc(), the current buffer contents might not be + * preserved and on error the old buffer is freed, thus no special handling to + * avoid memleaks is necessary. + * + * `*ptr` is allowed to be `NULL`, in which case allocation always happens if + * `size_needed` is greater than 0. + * + * @code{.c} + * uint8_t *buf = ...; + * av_fast_malloc(&buf, ¤t_size, size_needed); + * if (!buf) { + * // Allocation failed; buf already freed + * return AVERROR(ENOMEM); + * } + * @endcode + * + * @param[in,out] ptr Pointer to pointer to an already allocated buffer. + * `*ptr` will be overwritten with pointer to new + * buffer on success or `NULL` on failure + * @param[in,out] size Pointer to current size of buffer `*ptr`. `*size` is + * changed to `min_size` in case of success or 0 in + * case of failure + * @param[in] min_size New size of buffer `*ptr` + * @see av_realloc() + * @see av_fast_mallocz() + */ +void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size); + +/** + * Allocate and clear a buffer, reusing the given one if large enough. + * + * Like av_fast_malloc(), but all newly allocated space is initially cleared. + * Reused buffer is not cleared. + * + * `*ptr` is allowed to be `NULL`, in which case allocation always happens if + * `size_needed` is greater than 0. + * + * @param[in,out] ptr Pointer to pointer to an already allocated buffer. + * `*ptr` will be overwritten with pointer to new + * buffer on success or `NULL` on failure + * @param[in,out] size Pointer to current size of buffer `*ptr`. `*size` is + * changed to `min_size` in case of success or 0 in + * case of failure + * @param[in] min_size New size of buffer `*ptr` + * @see av_fast_malloc() + */ +void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size); + +/** + * Free a memory block which has been allocated with a function of av_malloc() + * or av_realloc() family. + * + * @param ptr Pointer to the memory block which should be freed. + * + * @note `ptr = NULL` is explicitly allowed. + * @note It is recommended that you use av_freep() instead, to prevent leaving + * behind dangling pointers. + * @see av_freep() + */ +void av_free(void *ptr); + +/** + * Free a memory block which has been allocated with a function of av_malloc() + * or av_realloc() family, and set the pointer pointing to it to `NULL`. + * + * @code{.c} + * uint8_t *buf = av_malloc(16); + * av_free(buf); + * // buf now contains a dangling pointer to freed memory, and accidental + * // dereference of buf will result in a use-after-free, which may be a + * // security risk. + * + * uint8_t *buf = av_malloc(16); + * av_freep(&buf); + * // buf is now NULL, and accidental dereference will only result in a + * // NULL-pointer dereference. + * @endcode + * + * @param ptr Pointer to the pointer to the memory block which should be freed + * @note `*ptr = NULL` is safe and leads to no action. + * @see av_free() + */ +void av_freep(void *ptr); + +/** + * Duplicate a string. + * + * @param s String to be duplicated + * @return Pointer to a newly-allocated string containing a + * copy of `s` or `NULL` if the string cannot be allocated + * @see av_strndup() + */ +char *av_strdup(const char *s) av_malloc_attrib; + +/** + * Duplicate a substring of a string. + * + * @param s String to be duplicated + * @param len Maximum length of the resulting string (not counting the + * terminating byte) + * @return Pointer to a newly-allocated string containing a + * substring of `s` or `NULL` if the string cannot be allocated + */ +char *av_strndup(const char *s, size_t len) av_malloc_attrib; + +/** + * Duplicate a buffer with av_malloc(). + * + * @param p Buffer to be duplicated + * @param size Size in bytes of the buffer copied + * @return Pointer to a newly allocated buffer containing a + * copy of `p` or `NULL` if the buffer cannot be allocated + */ +void *av_memdup(const void *p, size_t size); + +/** + * Overlapping memcpy() implementation. + * + * @param dst Destination buffer + * @param back Number of bytes back to start copying (i.e. the initial size of + * the overlapping window); must be > 0 + * @param cnt Number of bytes to copy; must be >= 0 + * + * @note `cnt > back` is valid, this will copy the bytes we just copied, + * thus creating a repeating pattern with a period length of `back`. + */ +void av_memcpy_backptr(uint8_t *dst, int back, int cnt); + +/** + * @} + */ + +/** + * @defgroup lavu_mem_dynarray Dynamic Array + * + * Utilities to make an array grow when needed. + * + * Sometimes, the programmer would want to have an array that can grow when + * needed. The libavutil dynamic array utilities fill that need. + * + * libavutil supports two systems of appending elements onto a dynamically + * allocated array, the first one storing the pointer to the value in the + * array, and the second storing the value directly. In both systems, the + * caller is responsible for maintaining a variable containing the length of + * the array, as well as freeing of the array after use. + * + * The first system stores pointers to values in a block of dynamically + * allocated memory. Since only pointers are stored, the function does not need + * to know the size of the type. Both av_dynarray_add() and + * av_dynarray_add_nofree() implement this system. + * + * @code + * type **array = NULL; //< an array of pointers to values + * int nb = 0; //< a variable to keep track of the length of the array + * + * type to_be_added = ...; + * type to_be_added2 = ...; + * + * av_dynarray_add(&array, &nb, &to_be_added); + * if (nb == 0) + * return AVERROR(ENOMEM); + * + * av_dynarray_add(&array, &nb, &to_be_added2); + * if (nb == 0) + * return AVERROR(ENOMEM); + * + * // Now: + * // nb == 2 + * // &to_be_added == array[0] + * // &to_be_added2 == array[1] + * + * av_freep(&array); + * @endcode + * + * The second system stores the value directly in a block of memory. As a + * result, the function has to know the size of the type. av_dynarray2_add() + * implements this mechanism. + * + * @code + * type *array = NULL; //< an array of values + * int nb = 0; //< a variable to keep track of the length of the array + * + * type to_be_added = ...; + * type to_be_added2 = ...; + * + * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array), NULL); + * if (!addr) + * return AVERROR(ENOMEM); + * memcpy(addr, &to_be_added, sizeof(to_be_added)); + * + * // Shortcut of the above. + * type *addr = av_dynarray2_add((void **)&array, &nb, sizeof(*array), + * (const void *)&to_be_added2); + * if (!addr) + * return AVERROR(ENOMEM); + * + * // Now: + * // nb == 2 + * // to_be_added == array[0] + * // to_be_added2 == array[1] + * + * av_freep(&array); + * @endcode + * + * @{ + */ + +/** + * Add the pointer to an element to a dynamic array. + * + * The array to grow is supposed to be an array of pointers to + * structures, and the element to add must be a pointer to an already + * allocated structure. + * + * The array is reallocated when its size reaches powers of 2. + * Therefore, the amortized cost of adding an element is constant. + * + * In case of success, the pointer to the array is updated in order to + * point to the new grown array, and the number pointed to by `nb_ptr` + * is incremented. + * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and + * `*nb_ptr` is set to 0. + * + * @param[in,out] tab_ptr Pointer to the array to grow + * @param[in,out] nb_ptr Pointer to the number of elements in the array + * @param[in] elem Element to add + * @see av_dynarray_add_nofree(), av_dynarray2_add() + */ +void av_dynarray_add(void *tab_ptr, int *nb_ptr, void *elem); + +/** + * Add an element to a dynamic array. + * + * Function has the same functionality as av_dynarray_add(), + * but it doesn't free memory on fails. It returns error code + * instead and leave current buffer untouched. + * + * @return >=0 on success, negative otherwise + * @see av_dynarray_add(), av_dynarray2_add() + */ +av_warn_unused_result +int av_dynarray_add_nofree(void *tab_ptr, int *nb_ptr, void *elem); + +/** + * Add an element of size `elem_size` to a dynamic array. + * + * The array is reallocated when its number of elements reaches powers of 2. + * Therefore, the amortized cost of adding an element is constant. + * + * In case of success, the pointer to the array is updated in order to + * point to the new grown array, and the number pointed to by `nb_ptr` + * is incremented. + * In case of failure, the array is freed, `*tab_ptr` is set to `NULL` and + * `*nb_ptr` is set to 0. + * + * @param[in,out] tab_ptr Pointer to the array to grow + * @param[in,out] nb_ptr Pointer to the number of elements in the array + * @param[in] elem_size Size in bytes of an element in the array + * @param[in] elem_data Pointer to the data of the element to add. If + * `NULL`, the space of the newly added element is + * allocated but left uninitialized. + * + * @return Pointer to the data of the element to copy in the newly allocated + * space + * @see av_dynarray_add(), av_dynarray_add_nofree() + */ +void *av_dynarray2_add(void **tab_ptr, int *nb_ptr, size_t elem_size, + const uint8_t *elem_data); + +/** + * @} + */ + +/** + * @defgroup lavu_mem_misc Miscellaneous Functions + * + * Other functions related to memory allocation. + * + * @{ + */ + +/** + * Multiply two `size_t` values checking for overflow. + * + * @param[in] a,b Operands of multiplication + * @param[out] r Pointer to the result of the operation + * @return 0 on success, AVERROR(EINVAL) on overflow + */ +static inline int av_size_mult(size_t a, size_t b, size_t *r) +{ + size_t t = a * b; + /* Hack inspired from glibc: don't try the division if nelem and elsize + * are both less than sqrt(SIZE_MAX). */ + if ((a | b) >= ((size_t)1 << (sizeof(size_t) * 4)) && a && t / a != b) + return AVERROR(EINVAL); + *r = t; + return 0; +} + +/** + * Set the maximum size that may be allocated in one block. + * + * The value specified with this function is effective for all libavutil's @ref + * lavu_mem_funcs "heap management functions." + * + * By default, the max value is defined as `INT_MAX`. + * + * @param max Value to be set as the new maximum size + * + * @warning Exercise extreme caution when using this function. Don't touch + * this if you do not understand the full consequence of doing so. + */ +void av_max_alloc(size_t max); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_MEM_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/motion_vector.h b/thrid-party/ffmpeg/include/libavutil/motion_vector.h new file mode 100644 index 0000000000..ec29556388 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/motion_vector.h @@ -0,0 +1,57 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_MOTION_VECTOR_H +#define AVUTIL_MOTION_VECTOR_H + +#include + +typedef struct AVMotionVector { + /** + * Where the current macroblock comes from; negative value when it comes + * from the past, positive value when it comes from the future. + * XXX: set exact relative ref frame reference instead of a +/- 1 "direction". + */ + int32_t source; + /** + * Width and height of the block. + */ + uint8_t w, h; + /** + * Absolute source position. Can be outside the frame area. + */ + int16_t src_x, src_y; + /** + * Absolute destination position. Can be outside the frame area. + */ + int16_t dst_x, dst_y; + /** + * Extra flag information. + * Currently unused. + */ + uint64_t flags; + /** + * Motion vector + * src_x = dst_x + motion_x / motion_scale + * src_y = dst_y + motion_y / motion_scale + */ + int32_t motion_x, motion_y; + uint16_t motion_scale; +} AVMotionVector; + +#endif /* AVUTIL_MOTION_VECTOR_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/murmur3.h b/thrid-party/ffmpeg/include/libavutil/murmur3.h new file mode 100644 index 0000000000..6a1694c08d --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/murmur3.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013 Reimar Döffinger + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_murmur3 + * Public header for MurmurHash3 hash function implementation. + */ + +#ifndef AVUTIL_MURMUR3_H +#define AVUTIL_MURMUR3_H + +#include + +/** + * @defgroup lavu_murmur3 Murmur3 + * @ingroup lavu_hash + * MurmurHash3 hash function implementation. + * + * MurmurHash3 is a non-cryptographic hash function, of which three + * incompatible versions were created by its inventor Austin Appleby: + * + * - 32-bit output + * - 128-bit output for 32-bit platforms + * - 128-bit output for 64-bit platforms + * + * FFmpeg only implements the last variant: 128-bit output designed for 64-bit + * platforms. Even though the hash function was designed for 64-bit platforms, + * the function in reality works on 32-bit systems too, only with reduced + * performance. + * + * @anchor lavu_murmur3_seedinfo + * By design, MurmurHash3 requires a seed to operate. In response to this, + * libavutil provides two functions for hash initiation, one that requires a + * seed (av_murmur3_init_seeded()) and one that uses a fixed arbitrary integer + * as the seed, and therefore does not (av_murmur3_init()). + * + * To make hashes comparable, you should provide the same seed for all calls to + * this hash function -- if you are supplying one yourself, that is. + * + * @{ + */ + +/** + * Allocate an AVMurMur3 hash context. + * + * @return Uninitialized hash context or `NULL` in case of error + */ +struct AVMurMur3 *av_murmur3_alloc(void); + +/** + * Initialize or reinitialize an AVMurMur3 hash context with a seed. + * + * @param[out] c Hash context + * @param[in] seed Random seed + * + * @see av_murmur3_init() + * @see @ref lavu_murmur3_seedinfo "Detailed description" on a discussion of + * seeds for MurmurHash3. + */ +void av_murmur3_init_seeded(struct AVMurMur3 *c, uint64_t seed); + +/** + * Initialize or reinitialize an AVMurMur3 hash context. + * + * Equivalent to av_murmur3_init_seeded() with a built-in seed. + * + * @param[out] c Hash context + * + * @see av_murmur3_init_seeded() + * @see @ref lavu_murmur3_seedinfo "Detailed description" on a discussion of + * seeds for MurmurHash3. + */ +void av_murmur3_init(struct AVMurMur3 *c); + +/** + * Update hash context with new data. + * + * @param[out] c Hash context + * @param[in] src Input data to update hash with + * @param[in] len Number of bytes to read from `src` + */ +void av_murmur3_update(struct AVMurMur3 *c, const uint8_t *src, int len); + +/** + * Finish hashing and output digest value. + * + * @param[in,out] c Hash context + * @param[out] dst Buffer where output digest value is stored + */ +void av_murmur3_final(struct AVMurMur3 *c, uint8_t dst[16]); + +/** + * @} + */ + +#endif /* AVUTIL_MURMUR3_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/opt.h b/thrid-party/ffmpeg/include/libavutil/opt.h new file mode 100644 index 0000000000..0d893795de --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/opt.h @@ -0,0 +1,866 @@ +/* + * AVOptions + * copyright (c) 2005 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_OPT_H +#define AVUTIL_OPT_H + +/** + * @file + * AVOptions + */ + +#include "rational.h" +#include "avutil.h" +#include "dict.h" +#include "log.h" +#include "pixfmt.h" +#include "samplefmt.h" +#include "version.h" + +/** + * @defgroup avoptions AVOptions + * @ingroup lavu_data + * @{ + * AVOptions provide a generic system to declare options on arbitrary structs + * ("objects"). An option can have a help text, a type and a range of possible + * values. Options may then be enumerated, read and written to. + * + * @section avoptions_implement Implementing AVOptions + * This section describes how to add AVOptions capabilities to a struct. + * + * All AVOptions-related information is stored in an AVClass. Therefore + * the first member of the struct should be a pointer to an AVClass describing it. + * The option field of the AVClass must be set to a NULL-terminated static array + * of AVOptions. Each AVOption must have a non-empty name, a type, a default + * value and for number-type AVOptions also a range of allowed values. It must + * also declare an offset in bytes from the start of the struct, where the field + * associated with this AVOption is located. Other fields in the AVOption struct + * should also be set when applicable, but are not required. + * + * The following example illustrates an AVOptions-enabled struct: + * @code + * typedef struct test_struct { + * const AVClass *class; + * int int_opt; + * char *str_opt; + * uint8_t *bin_opt; + * int bin_len; + * } test_struct; + * + * static const AVOption test_options[] = { + * { "test_int", "This is a test option of int type.", offsetof(test_struct, int_opt), + * AV_OPT_TYPE_INT, { .i64 = -1 }, INT_MIN, INT_MAX }, + * { "test_str", "This is a test option of string type.", offsetof(test_struct, str_opt), + * AV_OPT_TYPE_STRING }, + * { "test_bin", "This is a test option of binary type.", offsetof(test_struct, bin_opt), + * AV_OPT_TYPE_BINARY }, + * { NULL }, + * }; + * + * static const AVClass test_class = { + * .class_name = "test class", + * .item_name = av_default_item_name, + * .option = test_options, + * .version = LIBAVUTIL_VERSION_INT, + * }; + * @endcode + * + * Next, when allocating your struct, you must ensure that the AVClass pointer + * is set to the correct value. Then, av_opt_set_defaults() can be called to + * initialize defaults. After that the struct is ready to be used with the + * AVOptions API. + * + * When cleaning up, you may use the av_opt_free() function to automatically + * free all the allocated string and binary options. + * + * Continuing with the above example: + * + * @code + * test_struct *alloc_test_struct(void) + * { + * test_struct *ret = av_mallocz(sizeof(*ret)); + * ret->class = &test_class; + * av_opt_set_defaults(ret); + * return ret; + * } + * void free_test_struct(test_struct **foo) + * { + * av_opt_free(*foo); + * av_freep(foo); + * } + * @endcode + * + * @subsection avoptions_implement_nesting Nesting + * It may happen that an AVOptions-enabled struct contains another + * AVOptions-enabled struct as a member (e.g. AVCodecContext in + * libavcodec exports generic options, while its priv_data field exports + * codec-specific options). In such a case, it is possible to set up the + * parent struct to export a child's options. To do that, simply + * implement AVClass.child_next() and AVClass.child_class_next() in the + * parent struct's AVClass. + * Assuming that the test_struct from above now also contains a + * child_struct field: + * + * @code + * typedef struct child_struct { + * AVClass *class; + * int flags_opt; + * } child_struct; + * static const AVOption child_opts[] = { + * { "test_flags", "This is a test option of flags type.", + * offsetof(child_struct, flags_opt), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT_MIN, INT_MAX }, + * { NULL }, + * }; + * static const AVClass child_class = { + * .class_name = "child class", + * .item_name = av_default_item_name, + * .option = child_opts, + * .version = LIBAVUTIL_VERSION_INT, + * }; + * + * void *child_next(void *obj, void *prev) + * { + * test_struct *t = obj; + * if (!prev && t->child_struct) + * return t->child_struct; + * return NULL + * } + * const AVClass child_class_next(const AVClass *prev) + * { + * return prev ? NULL : &child_class; + * } + * @endcode + * Putting child_next() and child_class_next() as defined above into + * test_class will now make child_struct's options accessible through + * test_struct (again, proper setup as described above needs to be done on + * child_struct right after it is created). + * + * From the above example it might not be clear why both child_next() + * and child_class_next() are needed. The distinction is that child_next() + * iterates over actually existing objects, while child_class_next() + * iterates over all possible child classes. E.g. if an AVCodecContext + * was initialized to use a codec which has private options, then its + * child_next() will return AVCodecContext.priv_data and finish + * iterating. OTOH child_class_next() on AVCodecContext.av_class will + * iterate over all available codecs with private options. + * + * @subsection avoptions_implement_named_constants Named constants + * It is possible to create named constants for options. Simply set the unit + * field of the option the constants should apply to a string and + * create the constants themselves as options of type AV_OPT_TYPE_CONST + * with their unit field set to the same string. + * Their default_val field should contain the value of the named + * constant. + * For example, to add some named constants for the test_flags option + * above, put the following into the child_opts array: + * @code + * { "test_flags", "This is a test option of flags type.", + * offsetof(child_struct, flags_opt), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT_MIN, INT_MAX, "test_unit" }, + * { "flag1", "This is a flag with value 16", 0, AV_OPT_TYPE_CONST, { .i64 = 16 }, 0, 0, "test_unit" }, + * @endcode + * + * @section avoptions_use Using AVOptions + * This section deals with accessing options in an AVOptions-enabled struct. + * Such structs in FFmpeg are e.g. AVCodecContext in libavcodec or + * AVFormatContext in libavformat. + * + * @subsection avoptions_use_examine Examining AVOptions + * The basic functions for examining options are av_opt_next(), which iterates + * over all options defined for one object, and av_opt_find(), which searches + * for an option with the given name. + * + * The situation is more complicated with nesting. An AVOptions-enabled struct + * may have AVOptions-enabled children. Passing the AV_OPT_SEARCH_CHILDREN flag + * to av_opt_find() will make the function search children recursively. + * + * For enumerating there are basically two cases. The first is when you want to + * get all options that may potentially exist on the struct and its children + * (e.g. when constructing documentation). In that case you should call + * av_opt_child_class_next() recursively on the parent struct's AVClass. The + * second case is when you have an already initialized struct with all its + * children and you want to get all options that can be actually written or read + * from it. In that case you should call av_opt_child_next() recursively (and + * av_opt_next() on each result). + * + * @subsection avoptions_use_get_set Reading and writing AVOptions + * When setting options, you often have a string read directly from the + * user. In such a case, simply passing it to av_opt_set() is enough. For + * non-string type options, av_opt_set() will parse the string according to the + * option type. + * + * Similarly av_opt_get() will read any option type and convert it to a string + * which will be returned. Do not forget that the string is allocated, so you + * have to free it with av_free(). + * + * In some cases it may be more convenient to put all options into an + * AVDictionary and call av_opt_set_dict() on it. A specific case of this + * are the format/codec open functions in lavf/lavc which take a dictionary + * filled with option as a parameter. This makes it possible to set some options + * that cannot be set otherwise, since e.g. the input file format is not known + * before the file is actually opened. + */ + +enum AVOptionType{ + AV_OPT_TYPE_FLAGS, + AV_OPT_TYPE_INT, + AV_OPT_TYPE_INT64, + AV_OPT_TYPE_DOUBLE, + AV_OPT_TYPE_FLOAT, + AV_OPT_TYPE_STRING, + AV_OPT_TYPE_RATIONAL, + AV_OPT_TYPE_BINARY, ///< offset must point to a pointer immediately followed by an int for the length + AV_OPT_TYPE_DICT, + AV_OPT_TYPE_UINT64, + AV_OPT_TYPE_CONST = 128, + AV_OPT_TYPE_IMAGE_SIZE = MKBETAG('S','I','Z','E'), ///< offset must point to two consecutive integers + AV_OPT_TYPE_PIXEL_FMT = MKBETAG('P','F','M','T'), + AV_OPT_TYPE_SAMPLE_FMT = MKBETAG('S','F','M','T'), + AV_OPT_TYPE_VIDEO_RATE = MKBETAG('V','R','A','T'), ///< offset must point to AVRational + AV_OPT_TYPE_DURATION = MKBETAG('D','U','R',' '), + AV_OPT_TYPE_COLOR = MKBETAG('C','O','L','R'), + AV_OPT_TYPE_CHANNEL_LAYOUT = MKBETAG('C','H','L','A'), + AV_OPT_TYPE_BOOL = MKBETAG('B','O','O','L'), +}; + +/** + * AVOption + */ +typedef struct AVOption { + const char *name; + + /** + * short English help text + * @todo What about other languages? + */ + const char *help; + + /** + * The offset relative to the context structure where the option + * value is stored. It should be 0 for named constants. + */ + int offset; + enum AVOptionType type; + + /** + * the default value for scalar options + */ + union { + int64_t i64; + double dbl; + const char *str; + /* TODO those are unused now */ + AVRational q; + } default_val; + double min; ///< minimum valid value for the option + double max; ///< maximum valid value for the option + + int flags; +#define AV_OPT_FLAG_ENCODING_PARAM 1 ///< a generic parameter which can be set by the user for muxing or encoding +#define AV_OPT_FLAG_DECODING_PARAM 2 ///< a generic parameter which can be set by the user for demuxing or decoding +#if FF_API_OPT_TYPE_METADATA +#define AV_OPT_FLAG_METADATA 4 ///< some data extracted or inserted into the file like title, comment, ... +#endif +#define AV_OPT_FLAG_AUDIO_PARAM 8 +#define AV_OPT_FLAG_VIDEO_PARAM 16 +#define AV_OPT_FLAG_SUBTITLE_PARAM 32 +/** + * The option is intended for exporting values to the caller. + */ +#define AV_OPT_FLAG_EXPORT 64 +/** + * The option may not be set through the AVOptions API, only read. + * This flag only makes sense when AV_OPT_FLAG_EXPORT is also set. + */ +#define AV_OPT_FLAG_READONLY 128 +#define AV_OPT_FLAG_FILTERING_PARAM (1<<16) ///< a generic parameter which can be set by the user for filtering +//FIXME think about enc-audio, ... style flags + + /** + * The logical unit to which the option belongs. Non-constant + * options and corresponding named constants share the same + * unit. May be NULL. + */ + const char *unit; +} AVOption; + +/** + * A single allowed range of values, or a single allowed value. + */ +typedef struct AVOptionRange { + const char *str; + /** + * Value range. + * For string ranges this represents the min/max length. + * For dimensions this represents the min/max pixel count or width/height in multi-component case. + */ + double value_min, value_max; + /** + * Value's component range. + * For string this represents the unicode range for chars, 0-127 limits to ASCII. + */ + double component_min, component_max; + /** + * Range flag. + * If set to 1 the struct encodes a range, if set to 0 a single value. + */ + int is_range; +} AVOptionRange; + +/** + * List of AVOptionRange structs. + */ +typedef struct AVOptionRanges { + /** + * Array of option ranges. + * + * Most of option types use just one component. + * Following describes multi-component option types: + * + * AV_OPT_TYPE_IMAGE_SIZE: + * component index 0: range of pixel count (width * height). + * component index 1: range of width. + * component index 2: range of height. + * + * @note To obtain multi-component version of this structure, user must + * provide AV_OPT_MULTI_COMPONENT_RANGE to av_opt_query_ranges or + * av_opt_query_ranges_default function. + * + * Multi-component range can be read as in following example: + * + * @code + * int range_index, component_index; + * AVOptionRanges *ranges; + * AVOptionRange *range[3]; //may require more than 3 in the future. + * av_opt_query_ranges(&ranges, obj, key, AV_OPT_MULTI_COMPONENT_RANGE); + * for (range_index = 0; range_index < ranges->nb_ranges; range_index++) { + * for (component_index = 0; component_index < ranges->nb_components; component_index++) + * range[component_index] = ranges->range[ranges->nb_ranges * component_index + range_index]; + * //do something with range here. + * } + * av_opt_freep_ranges(&ranges); + * @endcode + */ + AVOptionRange **range; + /** + * Number of ranges per component. + */ + int nb_ranges; + /** + * Number of componentes. + */ + int nb_components; +} AVOptionRanges; + +/** + * Show the obj options. + * + * @param req_flags requested flags for the options to show. Show only the + * options for which it is opt->flags & req_flags. + * @param rej_flags rejected flags for the options to show. Show only the + * options for which it is !(opt->flags & req_flags). + * @param av_log_obj log context to use for showing the options + */ +int av_opt_show2(void *obj, void *av_log_obj, int req_flags, int rej_flags); + +/** + * Set the values of all AVOption fields to their default values. + * + * @param s an AVOption-enabled struct (its first member must be a pointer to AVClass) + */ +void av_opt_set_defaults(void *s); + +/** + * Set the values of all AVOption fields to their default values. Only these + * AVOption fields for which (opt->flags & mask) == flags will have their + * default applied to s. + * + * @param s an AVOption-enabled struct (its first member must be a pointer to AVClass) + * @param mask combination of AV_OPT_FLAG_* + * @param flags combination of AV_OPT_FLAG_* + */ +void av_opt_set_defaults2(void *s, int mask, int flags); + +/** + * Parse the key/value pairs list in opts. For each key/value pair + * found, stores the value in the field in ctx that is named like the + * key. ctx must be an AVClass context, storing is done using + * AVOptions. + * + * @param opts options string to parse, may be NULL + * @param key_val_sep a 0-terminated list of characters used to + * separate key from value + * @param pairs_sep a 0-terminated list of characters used to separate + * two pairs from each other + * @return the number of successfully set key/value pairs, or a negative + * value corresponding to an AVERROR code in case of error: + * AVERROR(EINVAL) if opts cannot be parsed, + * the error code issued by av_opt_set() if a key/value pair + * cannot be set + */ +int av_set_options_string(void *ctx, const char *opts, + const char *key_val_sep, const char *pairs_sep); + +/** + * Parse the key-value pairs list in opts. For each key=value pair found, + * set the value of the corresponding option in ctx. + * + * @param ctx the AVClass object to set options on + * @param opts the options string, key-value pairs separated by a + * delimiter + * @param shorthand a NULL-terminated array of options names for shorthand + * notation: if the first field in opts has no key part, + * the key is taken from the first element of shorthand; + * then again for the second, etc., until either opts is + * finished, shorthand is finished or a named option is + * found; after that, all options must be named + * @param key_val_sep a 0-terminated list of characters used to separate + * key from value, for example '=' + * @param pairs_sep a 0-terminated list of characters used to separate + * two pairs from each other, for example ':' or ',' + * @return the number of successfully set key=value pairs, or a negative + * value corresponding to an AVERROR code in case of error: + * AVERROR(EINVAL) if opts cannot be parsed, + * the error code issued by av_set_string3() if a key/value pair + * cannot be set + * + * Options names must use only the following characters: a-z A-Z 0-9 - . / _ + * Separators must use characters distinct from option names and from each + * other. + */ +int av_opt_set_from_string(void *ctx, const char *opts, + const char *const *shorthand, + const char *key_val_sep, const char *pairs_sep); +/** + * Free all allocated objects in obj. + */ +void av_opt_free(void *obj); + +/** + * Check whether a particular flag is set in a flags field. + * + * @param field_name the name of the flag field option + * @param flag_name the name of the flag to check + * @return non-zero if the flag is set, zero if the flag isn't set, + * isn't of the right type, or the flags field doesn't exist. + */ +int av_opt_flag_is_set(void *obj, const char *field_name, const char *flag_name); + +/** + * Set all the options from a given dictionary on an object. + * + * @param obj a struct whose first element is a pointer to AVClass + * @param options options to process. This dictionary will be freed and replaced + * by a new one containing all options not found in obj. + * Of course this new dictionary needs to be freed by caller + * with av_dict_free(). + * + * @return 0 on success, a negative AVERROR if some option was found in obj, + * but could not be set. + * + * @see av_dict_copy() + */ +int av_opt_set_dict(void *obj, struct AVDictionary **options); + + +/** + * Set all the options from a given dictionary on an object. + * + * @param obj a struct whose first element is a pointer to AVClass + * @param options options to process. This dictionary will be freed and replaced + * by a new one containing all options not found in obj. + * Of course this new dictionary needs to be freed by caller + * with av_dict_free(). + * @param search_flags A combination of AV_OPT_SEARCH_*. + * + * @return 0 on success, a negative AVERROR if some option was found in obj, + * but could not be set. + * + * @see av_dict_copy() + */ +int av_opt_set_dict2(void *obj, struct AVDictionary **options, int search_flags); + +/** + * Extract a key-value pair from the beginning of a string. + * + * @param ropts pointer to the options string, will be updated to + * point to the rest of the string (one of the pairs_sep + * or the final NUL) + * @param key_val_sep a 0-terminated list of characters used to separate + * key from value, for example '=' + * @param pairs_sep a 0-terminated list of characters used to separate + * two pairs from each other, for example ':' or ',' + * @param flags flags; see the AV_OPT_FLAG_* values below + * @param rkey parsed key; must be freed using av_free() + * @param rval parsed value; must be freed using av_free() + * + * @return >=0 for success, or a negative value corresponding to an + * AVERROR code in case of error; in particular: + * AVERROR(EINVAL) if no key is present + * + */ +int av_opt_get_key_value(const char **ropts, + const char *key_val_sep, const char *pairs_sep, + unsigned flags, + char **rkey, char **rval); + +enum { + + /** + * Accept to parse a value without a key; the key will then be returned + * as NULL. + */ + AV_OPT_FLAG_IMPLICIT_KEY = 1, +}; + +/** + * @defgroup opt_eval_funcs Evaluating option strings + * @{ + * This group of functions can be used to evaluate option strings + * and get numbers out of them. They do the same thing as av_opt_set(), + * except the result is written into the caller-supplied pointer. + * + * @param obj a struct whose first element is a pointer to AVClass. + * @param o an option for which the string is to be evaluated. + * @param val string to be evaluated. + * @param *_out value of the string will be written here. + * + * @return 0 on success, a negative number on failure. + */ +int av_opt_eval_flags (void *obj, const AVOption *o, const char *val, int *flags_out); +int av_opt_eval_int (void *obj, const AVOption *o, const char *val, int *int_out); +int av_opt_eval_int64 (void *obj, const AVOption *o, const char *val, int64_t *int64_out); +int av_opt_eval_float (void *obj, const AVOption *o, const char *val, float *float_out); +int av_opt_eval_double(void *obj, const AVOption *o, const char *val, double *double_out); +int av_opt_eval_q (void *obj, const AVOption *o, const char *val, AVRational *q_out); +/** + * @} + */ + +#define AV_OPT_SEARCH_CHILDREN (1 << 0) /**< Search in possible children of the + given object first. */ +/** + * The obj passed to av_opt_find() is fake -- only a double pointer to AVClass + * instead of a required pointer to a struct containing AVClass. This is + * useful for searching for options without needing to allocate the corresponding + * object. + */ +#define AV_OPT_SEARCH_FAKE_OBJ (1 << 1) + +/** + * In av_opt_get, return NULL if the option has a pointer type and is set to NULL, + * rather than returning an empty string. + */ +#define AV_OPT_ALLOW_NULL (1 << 2) + +/** + * Allows av_opt_query_ranges and av_opt_query_ranges_default to return more than + * one component for certain option types. + * @see AVOptionRanges for details. + */ +#define AV_OPT_MULTI_COMPONENT_RANGE (1 << 12) + +/** + * Look for an option in an object. Consider only options which + * have all the specified flags set. + * + * @param[in] obj A pointer to a struct whose first element is a + * pointer to an AVClass. + * Alternatively a double pointer to an AVClass, if + * AV_OPT_SEARCH_FAKE_OBJ search flag is set. + * @param[in] name The name of the option to look for. + * @param[in] unit When searching for named constants, name of the unit + * it belongs to. + * @param opt_flags Find only options with all the specified flags set (AV_OPT_FLAG). + * @param search_flags A combination of AV_OPT_SEARCH_*. + * + * @return A pointer to the option found, or NULL if no option + * was found. + * + * @note Options found with AV_OPT_SEARCH_CHILDREN flag may not be settable + * directly with av_opt_set(). Use special calls which take an options + * AVDictionary (e.g. avformat_open_input()) to set options found with this + * flag. + */ +const AVOption *av_opt_find(void *obj, const char *name, const char *unit, + int opt_flags, int search_flags); + +/** + * Look for an option in an object. Consider only options which + * have all the specified flags set. + * + * @param[in] obj A pointer to a struct whose first element is a + * pointer to an AVClass. + * Alternatively a double pointer to an AVClass, if + * AV_OPT_SEARCH_FAKE_OBJ search flag is set. + * @param[in] name The name of the option to look for. + * @param[in] unit When searching for named constants, name of the unit + * it belongs to. + * @param opt_flags Find only options with all the specified flags set (AV_OPT_FLAG). + * @param search_flags A combination of AV_OPT_SEARCH_*. + * @param[out] target_obj if non-NULL, an object to which the option belongs will be + * written here. It may be different from obj if AV_OPT_SEARCH_CHILDREN is present + * in search_flags. This parameter is ignored if search_flags contain + * AV_OPT_SEARCH_FAKE_OBJ. + * + * @return A pointer to the option found, or NULL if no option + * was found. + */ +const AVOption *av_opt_find2(void *obj, const char *name, const char *unit, + int opt_flags, int search_flags, void **target_obj); + +/** + * Iterate over all AVOptions belonging to obj. + * + * @param obj an AVOptions-enabled struct or a double pointer to an + * AVClass describing it. + * @param prev result of the previous call to av_opt_next() on this object + * or NULL + * @return next AVOption or NULL + */ +const AVOption *av_opt_next(const void *obj, const AVOption *prev); + +/** + * Iterate over AVOptions-enabled children of obj. + * + * @param prev result of a previous call to this function or NULL + * @return next AVOptions-enabled child or NULL + */ +void *av_opt_child_next(void *obj, void *prev); + +/** + * Iterate over potential AVOptions-enabled children of parent. + * + * @param prev result of a previous call to this function or NULL + * @return AVClass corresponding to next potential child or NULL + */ +const AVClass *av_opt_child_class_next(const AVClass *parent, const AVClass *prev); + +/** + * @defgroup opt_set_funcs Option setting functions + * @{ + * Those functions set the field of obj with the given name to value. + * + * @param[in] obj A struct whose first element is a pointer to an AVClass. + * @param[in] name the name of the field to set + * @param[in] val The value to set. In case of av_opt_set() if the field is not + * of a string type, then the given string is parsed. + * SI postfixes and some named scalars are supported. + * If the field is of a numeric type, it has to be a numeric or named + * scalar. Behavior with more than one scalar and +- infix operators + * is undefined. + * If the field is of a flags type, it has to be a sequence of numeric + * scalars or named flags separated by '+' or '-'. Prefixing a flag + * with '+' causes it to be set without affecting the other flags; + * similarly, '-' unsets a flag. + * @param search_flags flags passed to av_opt_find2. I.e. if AV_OPT_SEARCH_CHILDREN + * is passed here, then the option may be set on a child of obj. + * + * @return 0 if the value has been set, or an AVERROR code in case of + * error: + * AVERROR_OPTION_NOT_FOUND if no matching option exists + * AVERROR(ERANGE) if the value is out of range + * AVERROR(EINVAL) if the value is not valid + */ +int av_opt_set (void *obj, const char *name, const char *val, int search_flags); +int av_opt_set_int (void *obj, const char *name, int64_t val, int search_flags); +int av_opt_set_double (void *obj, const char *name, double val, int search_flags); +int av_opt_set_q (void *obj, const char *name, AVRational val, int search_flags); +int av_opt_set_bin (void *obj, const char *name, const uint8_t *val, int size, int search_flags); +int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_flags); +int av_opt_set_pixel_fmt (void *obj, const char *name, enum AVPixelFormat fmt, int search_flags); +int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags); +int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int search_flags); +int av_opt_set_channel_layout(void *obj, const char *name, int64_t ch_layout, int search_flags); +/** + * @note Any old dictionary present is discarded and replaced with a copy of the new one. The + * caller still owns val is and responsible for freeing it. + */ +int av_opt_set_dict_val(void *obj, const char *name, const AVDictionary *val, int search_flags); + +/** + * Set a binary option to an integer list. + * + * @param obj AVClass object to set options on + * @param name name of the binary option + * @param val pointer to an integer list (must have the correct type with + * regard to the contents of the list) + * @param term list terminator (usually 0 or -1) + * @param flags search flags + */ +#define av_opt_set_int_list(obj, name, val, term, flags) \ + (av_int_list_length(val, term) > INT_MAX / sizeof(*(val)) ? \ + AVERROR(EINVAL) : \ + av_opt_set_bin(obj, name, (const uint8_t *)(val), \ + av_int_list_length(val, term) * sizeof(*(val)), flags)) + +/** + * @} + */ + +/** + * @defgroup opt_get_funcs Option getting functions + * @{ + * Those functions get a value of the option with the given name from an object. + * + * @param[in] obj a struct whose first element is a pointer to an AVClass. + * @param[in] name name of the option to get. + * @param[in] search_flags flags passed to av_opt_find2. I.e. if AV_OPT_SEARCH_CHILDREN + * is passed here, then the option may be found in a child of obj. + * @param[out] out_val value of the option will be written here + * @return >=0 on success, a negative error code otherwise + */ +/** + * @note the returned string will be av_malloc()ed and must be av_free()ed by the caller + * + * @note if AV_OPT_ALLOW_NULL is set in search_flags in av_opt_get, and the option has + * AV_OPT_TYPE_STRING or AV_OPT_TYPE_BINARY and is set to NULL, *out_val will be set + * to NULL instead of an allocated empty string. + */ +int av_opt_get (void *obj, const char *name, int search_flags, uint8_t **out_val); +int av_opt_get_int (void *obj, const char *name, int search_flags, int64_t *out_val); +int av_opt_get_double (void *obj, const char *name, int search_flags, double *out_val); +int av_opt_get_q (void *obj, const char *name, int search_flags, AVRational *out_val); +int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_out, int *h_out); +int av_opt_get_pixel_fmt (void *obj, const char *name, int search_flags, enum AVPixelFormat *out_fmt); +int av_opt_get_sample_fmt(void *obj, const char *name, int search_flags, enum AVSampleFormat *out_fmt); +int av_opt_get_video_rate(void *obj, const char *name, int search_flags, AVRational *out_val); +int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int64_t *ch_layout); +/** + * @param[out] out_val The returned dictionary is a copy of the actual value and must + * be freed with av_dict_free() by the caller + */ +int av_opt_get_dict_val(void *obj, const char *name, int search_flags, AVDictionary **out_val); +/** + * @} + */ +/** + * Gets a pointer to the requested field in a struct. + * This function allows accessing a struct even when its fields are moved or + * renamed since the application making the access has been compiled, + * + * @returns a pointer to the field, it can be cast to the correct type and read + * or written to. + */ +void *av_opt_ptr(const AVClass *avclass, void *obj, const char *name); + +/** + * Free an AVOptionRanges struct and set it to NULL. + */ +void av_opt_freep_ranges(AVOptionRanges **ranges); + +/** + * Get a list of allowed ranges for the given option. + * + * The returned list may depend on other fields in obj like for example profile. + * + * @param flags is a bitmask of flags, undefined flags should not be set and should be ignored + * AV_OPT_SEARCH_FAKE_OBJ indicates that the obj is a double pointer to a AVClass instead of a full instance + * AV_OPT_MULTI_COMPONENT_RANGE indicates that function may return more than one component, @see AVOptionRanges + * + * The result must be freed with av_opt_freep_ranges. + * + * @return number of compontents returned on success, a negative errro code otherwise + */ +int av_opt_query_ranges(AVOptionRanges **, void *obj, const char *key, int flags); + +/** + * Copy options from src object into dest object. + * + * Options that require memory allocation (e.g. string or binary) are malloc'ed in dest object. + * Original memory allocated for such options is freed unless both src and dest options points to the same memory. + * + * @param dest Object to copy from + * @param src Object to copy into + * @return 0 on success, negative on error + */ +int av_opt_copy(void *dest, const void *src); + +/** + * Get a default list of allowed ranges for the given option. + * + * This list is constructed without using the AVClass.query_ranges() callback + * and can be used as fallback from within the callback. + * + * @param flags is a bitmask of flags, undefined flags should not be set and should be ignored + * AV_OPT_SEARCH_FAKE_OBJ indicates that the obj is a double pointer to a AVClass instead of a full instance + * AV_OPT_MULTI_COMPONENT_RANGE indicates that function may return more than one component, @see AVOptionRanges + * + * The result must be freed with av_opt_free_ranges. + * + * @return number of compontents returned on success, a negative errro code otherwise + */ +int av_opt_query_ranges_default(AVOptionRanges **, void *obj, const char *key, int flags); + +/** + * Check if given option is set to its default value. + * + * Options o must belong to the obj. This function must not be called to check child's options state. + * @see av_opt_is_set_to_default_by_name(). + * + * @param obj AVClass object to check option on + * @param o option to be checked + * @return >0 when option is set to its default, + * 0 when option is not set its default, + * <0 on error + */ +int av_opt_is_set_to_default(void *obj, const AVOption *o); + +/** + * Check if given option is set to its default value. + * + * @param obj AVClass object to check option on + * @param name option name + * @param search_flags combination of AV_OPT_SEARCH_* + * @return >0 when option is set to its default, + * 0 when option is not set its default, + * <0 on error + */ +int av_opt_is_set_to_default_by_name(void *obj, const char *name, int search_flags); + + +#define AV_OPT_SERIALIZE_SKIP_DEFAULTS 0x00000001 ///< Serialize options that are not set to default values only. +#define AV_OPT_SERIALIZE_OPT_FLAGS_EXACT 0x00000002 ///< Serialize options that exactly match opt_flags only. + +/** + * Serialize object's options. + * + * Create a string containing object's serialized options. + * Such string may be passed back to av_opt_set_from_string() in order to restore option values. + * A key/value or pairs separator occurring in the serialized value or + * name string are escaped through the av_escape() function. + * + * @param[in] obj AVClass object to serialize + * @param[in] opt_flags serialize options with all the specified flags set (AV_OPT_FLAG) + * @param[in] flags combination of AV_OPT_SERIALIZE_* flags + * @param[out] buffer Pointer to buffer that will be allocated with string containg serialized options. + * Buffer must be freed by the caller when is no longer needed. + * @param[in] key_val_sep character used to separate key from value + * @param[in] pairs_sep character used to separate two pairs from each other + * @return >= 0 on success, negative on error + * @warning Separators cannot be neither '\\' nor '\0'. They also cannot be the same. + */ +int av_opt_serialize(void *obj, int opt_flags, int flags, char **buffer, + const char key_val_sep, const char pairs_sep); +/** + * @} + */ + +#endif /* AVUTIL_OPT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/parseutils.h b/thrid-party/ffmpeg/include/libavutil/parseutils.h new file mode 100644 index 0000000000..e66d24b76e --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/parseutils.h @@ -0,0 +1,193 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_PARSEUTILS_H +#define AVUTIL_PARSEUTILS_H + +#include + +#include "rational.h" + +/** + * @file + * misc parsing utilities + */ + +/** + * Parse str and store the parsed ratio in q. + * + * Note that a ratio with infinite (1/0) or negative value is + * considered valid, so you should check on the returned value if you + * want to exclude those values. + * + * The undefined value can be expressed using the "0:0" string. + * + * @param[in,out] q pointer to the AVRational which will contain the ratio + * @param[in] str the string to parse: it has to be a string in the format + * num:den, a float number or an expression + * @param[in] max the maximum allowed numerator and denominator + * @param[in] log_offset log level offset which is applied to the log + * level of log_ctx + * @param[in] log_ctx parent logging context + * @return >= 0 on success, a negative error code otherwise + */ +int av_parse_ratio(AVRational *q, const char *str, int max, + int log_offset, void *log_ctx); + +#define av_parse_ratio_quiet(rate, str, max) \ + av_parse_ratio(rate, str, max, AV_LOG_MAX_OFFSET, NULL) + +/** + * Parse str and put in width_ptr and height_ptr the detected values. + * + * @param[in,out] width_ptr pointer to the variable which will contain the detected + * width value + * @param[in,out] height_ptr pointer to the variable which will contain the detected + * height value + * @param[in] str the string to parse: it has to be a string in the format + * width x height or a valid video size abbreviation. + * @return >= 0 on success, a negative error code otherwise + */ +int av_parse_video_size(int *width_ptr, int *height_ptr, const char *str); + +/** + * Parse str and store the detected values in *rate. + * + * @param[in,out] rate pointer to the AVRational which will contain the detected + * frame rate + * @param[in] str the string to parse: it has to be a string in the format + * rate_num / rate_den, a float number or a valid video rate abbreviation + * @return >= 0 on success, a negative error code otherwise + */ +int av_parse_video_rate(AVRational *rate, const char *str); + +/** + * Put the RGBA values that correspond to color_string in rgba_color. + * + * @param color_string a string specifying a color. It can be the name of + * a color (case insensitive match) or a [0x|#]RRGGBB[AA] sequence, + * possibly followed by "@" and a string representing the alpha + * component. + * The alpha component may be a string composed by "0x" followed by an + * hexadecimal number or a decimal number between 0.0 and 1.0, which + * represents the opacity value (0x00/0.0 means completely transparent, + * 0xff/1.0 completely opaque). + * If the alpha component is not specified then 0xff is assumed. + * The string "random" will result in a random color. + * @param slen length of the initial part of color_string containing the + * color. It can be set to -1 if color_string is a null terminated string + * containing nothing else than the color. + * @return >= 0 in case of success, a negative value in case of + * failure (for example if color_string cannot be parsed). + */ +int av_parse_color(uint8_t *rgba_color, const char *color_string, int slen, + void *log_ctx); + +/** + * Get the name of a color from the internal table of hard-coded named + * colors. + * + * This function is meant to enumerate the color names recognized by + * av_parse_color(). + * + * @param color_idx index of the requested color, starting from 0 + * @param rgbp if not NULL, will point to a 3-elements array with the color value in RGB + * @return the color name string or NULL if color_idx is not in the array + */ +const char *av_get_known_color_name(int color_idx, const uint8_t **rgb); + +/** + * Parse timestr and return in *time a corresponding number of + * microseconds. + * + * @param timeval puts here the number of microseconds corresponding + * to the string in timestr. If the string represents a duration, it + * is the number of microseconds contained in the time interval. If + * the string is a date, is the number of microseconds since 1st of + * January, 1970 up to the time of the parsed date. If timestr cannot + * be successfully parsed, set *time to INT64_MIN. + + * @param timestr a string representing a date or a duration. + * - If a date the syntax is: + * @code + * [{YYYY-MM-DD|YYYYMMDD}[T|t| ]]{{HH:MM:SS[.m...]]]}|{HHMMSS[.m...]]]}}[Z] + * now + * @endcode + * If the value is "now" it takes the current time. + * Time is local time unless Z is appended, in which case it is + * interpreted as UTC. + * If the year-month-day part is not specified it takes the current + * year-month-day. + * - If a duration the syntax is: + * @code + * [-][HH:]MM:SS[.m...] + * [-]S+[.m...] + * @endcode + * @param duration flag which tells how to interpret timestr, if not + * zero timestr is interpreted as a duration, otherwise as a date + * @return >= 0 in case of success, a negative value corresponding to an + * AVERROR code otherwise + */ +int av_parse_time(int64_t *timeval, const char *timestr, int duration); + +/** + * Attempt to find a specific tag in a URL. + * + * syntax: '?tag1=val1&tag2=val2...'. Little URL decoding is done. + * Return 1 if found. + */ +int av_find_info_tag(char *arg, int arg_size, const char *tag1, const char *info); + +/** + * Simplified version of strptime + * + * Parse the input string p according to the format string fmt and + * store its results in the structure dt. + * This implementation supports only a subset of the formats supported + * by the standard strptime(). + * + * The supported input field descriptors are listed below. + * - %H: the hour as a decimal number, using a 24-hour clock, in the + * range '00' through '23' + * - %J: hours as a decimal number, in the range '0' through INT_MAX + * - %M: the minute as a decimal number, using a 24-hour clock, in the + * range '00' through '59' + * - %S: the second as a decimal number, using a 24-hour clock, in the + * range '00' through '59' + * - %Y: the year as a decimal number, using the Gregorian calendar + * - %m: the month as a decimal number, in the range '1' through '12' + * - %d: the day of the month as a decimal number, in the range '1' + * through '31' + * - %T: alias for '%H:%M:%S' + * - %%: a literal '%' + * + * @return a pointer to the first character not processed in this function + * call. In case the input string contains more characters than + * required by the format string the return value points right after + * the last consumed input character. In case the whole input string + * is consumed the return value points to the null byte at the end of + * the string. On failure NULL is returned. + */ +char *av_small_strptime(const char *p, const char *fmt, struct tm *dt); + +/** + * Convert the decomposed UTC time in tm to a time_t value. + */ +time_t av_timegm(struct tm *tm); + +#endif /* AVUTIL_PARSEUTILS_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/pixdesc.h b/thrid-party/ffmpeg/include/libavutil/pixdesc.h new file mode 100644 index 0000000000..fc3737c4ad --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/pixdesc.h @@ -0,0 +1,430 @@ +/* + * pixel format descriptor + * Copyright (c) 2009 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_PIXDESC_H +#define AVUTIL_PIXDESC_H + +#include + +#include "attributes.h" +#include "pixfmt.h" +#include "version.h" + +typedef struct AVComponentDescriptor { + /** + * Which of the 4 planes contains the component. + */ + int plane; + + /** + * Number of elements between 2 horizontally consecutive pixels. + * Elements are bits for bitstream formats, bytes otherwise. + */ + int step; + + /** + * Number of elements before the component of the first pixel. + * Elements are bits for bitstream formats, bytes otherwise. + */ + int offset; + + /** + * Number of least significant bits that must be shifted away + * to get the value. + */ + int shift; + + /** + * Number of bits in the component. + */ + int depth; + +#if FF_API_PLUS1_MINUS1 + /** deprecated, use step instead */ + attribute_deprecated int step_minus1; + + /** deprecated, use depth instead */ + attribute_deprecated int depth_minus1; + + /** deprecated, use offset instead */ + attribute_deprecated int offset_plus1; +#endif +} AVComponentDescriptor; + +/** + * Descriptor that unambiguously describes how the bits of a pixel are + * stored in the up to 4 data planes of an image. It also stores the + * subsampling factors and number of components. + * + * @note This is separate of the colorspace (RGB, YCbCr, YPbPr, JPEG-style YUV + * and all the YUV variants) AVPixFmtDescriptor just stores how values + * are stored not what these values represent. + */ +typedef struct AVPixFmtDescriptor { + const char *name; + uint8_t nb_components; ///< The number of components each pixel has, (1-4) + + /** + * Amount to shift the luma width right to find the chroma width. + * For YV12 this is 1 for example. + * chroma_width = AV_CEIL_RSHIFT(luma_width, log2_chroma_w) + * The note above is needed to ensure rounding up. + * This value only refers to the chroma components. + */ + uint8_t log2_chroma_w; + + /** + * Amount to shift the luma height right to find the chroma height. + * For YV12 this is 1 for example. + * chroma_height= AV_CEIL_RSHIFT(luma_height, log2_chroma_h) + * The note above is needed to ensure rounding up. + * This value only refers to the chroma components. + */ + uint8_t log2_chroma_h; + + /** + * Combination of AV_PIX_FMT_FLAG_... flags. + */ + uint64_t flags; + + /** + * Parameters that describe how pixels are packed. + * If the format has 1 or 2 components, then luma is 0. + * If the format has 3 or 4 components: + * if the RGB flag is set then 0 is red, 1 is green and 2 is blue; + * otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V. + * + * If present, the Alpha channel is always the last component. + */ + AVComponentDescriptor comp[4]; + + /** + * Alternative comma-separated names. + */ + const char *alias; +} AVPixFmtDescriptor; + +/** + * Pixel format is big-endian. + */ +#define AV_PIX_FMT_FLAG_BE (1 << 0) +/** + * Pixel format has a palette in data[1], values are indexes in this palette. + */ +#define AV_PIX_FMT_FLAG_PAL (1 << 1) +/** + * All values of a component are bit-wise packed end to end. + */ +#define AV_PIX_FMT_FLAG_BITSTREAM (1 << 2) +/** + * Pixel format is an HW accelerated format. + */ +#define AV_PIX_FMT_FLAG_HWACCEL (1 << 3) +/** + * At least one pixel component is not in the first data plane. + */ +#define AV_PIX_FMT_FLAG_PLANAR (1 << 4) +/** + * The pixel format contains RGB-like data (as opposed to YUV/grayscale). + */ +#define AV_PIX_FMT_FLAG_RGB (1 << 5) + +/** + * The pixel format is "pseudo-paletted". This means that it contains a + * fixed palette in the 2nd plane but the palette is fixed/constant for each + * PIX_FMT. This allows interpreting the data as if it was PAL8, which can + * in some cases be simpler. Or the data can be interpreted purely based on + * the pixel format without using the palette. + * An example of a pseudo-paletted format is AV_PIX_FMT_GRAY8 + */ +#define AV_PIX_FMT_FLAG_PSEUDOPAL (1 << 6) + +/** + * The pixel format has an alpha channel. This is set on all formats that + * support alpha in some way. The exception is AV_PIX_FMT_PAL8, which can + * carry alpha as part of the palette. Details are explained in the + * AVPixelFormat enum, and are also encoded in the corresponding + * AVPixFmtDescriptor. + * + * The alpha is always straight, never pre-multiplied. + * + * If a codec or a filter does not support alpha, it should set all alpha to + * opaque, or use the equivalent pixel formats without alpha component, e.g. + * AV_PIX_FMT_RGB0 (or AV_PIX_FMT_RGB24 etc.) instead of AV_PIX_FMT_RGBA. + */ +#define AV_PIX_FMT_FLAG_ALPHA (1 << 7) + +/** + * The pixel format is following a Bayer pattern + */ +#define AV_PIX_FMT_FLAG_BAYER (1 << 8) + +/** + * The pixel format contains IEEE-754 floating point values. Precision (double, + * single, or half) should be determined by the pixel size (64, 32, or 16 bits). + */ +#define AV_PIX_FMT_FLAG_FLOAT (1 << 9) + +/** + * Return the number of bits per pixel used by the pixel format + * described by pixdesc. Note that this is not the same as the number + * of bits per sample. + * + * The returned number of bits refers to the number of bits actually + * used for storing the pixel information, that is padding bits are + * not counted. + */ +int av_get_bits_per_pixel(const AVPixFmtDescriptor *pixdesc); + +/** + * Return the number of bits per pixel for the pixel format + * described by pixdesc, including any padding or unused bits. + */ +int av_get_padded_bits_per_pixel(const AVPixFmtDescriptor *pixdesc); + +/** + * @return a pixel format descriptor for provided pixel format or NULL if + * this pixel format is unknown. + */ +const AVPixFmtDescriptor *av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt); + +/** + * Iterate over all pixel format descriptors known to libavutil. + * + * @param prev previous descriptor. NULL to get the first descriptor. + * + * @return next descriptor or NULL after the last descriptor + */ +const AVPixFmtDescriptor *av_pix_fmt_desc_next(const AVPixFmtDescriptor *prev); + +/** + * @return an AVPixelFormat id described by desc, or AV_PIX_FMT_NONE if desc + * is not a valid pointer to a pixel format descriptor. + */ +enum AVPixelFormat av_pix_fmt_desc_get_id(const AVPixFmtDescriptor *desc); + +/** + * Utility function to access log2_chroma_w log2_chroma_h from + * the pixel format AVPixFmtDescriptor. + * + * See av_get_chroma_sub_sample() for a function that asserts a + * valid pixel format instead of returning an error code. + * Its recommended that you use avcodec_get_chroma_sub_sample unless + * you do check the return code! + * + * @param[in] pix_fmt the pixel format + * @param[out] h_shift store log2_chroma_w (horizontal/width shift) + * @param[out] v_shift store log2_chroma_h (vertical/height shift) + * + * @return 0 on success, AVERROR(ENOSYS) on invalid or unknown pixel format + */ +int av_pix_fmt_get_chroma_sub_sample(enum AVPixelFormat pix_fmt, + int *h_shift, int *v_shift); + +/** + * @return number of planes in pix_fmt, a negative AVERROR if pix_fmt is not a + * valid pixel format. + */ +int av_pix_fmt_count_planes(enum AVPixelFormat pix_fmt); + +/** + * @return the name for provided color range or NULL if unknown. + */ +const char *av_color_range_name(enum AVColorRange range); + +/** + * @return the AVColorRange value for name or an AVError if not found. + */ +int av_color_range_from_name(const char *name); + +/** + * @return the name for provided color primaries or NULL if unknown. + */ +const char *av_color_primaries_name(enum AVColorPrimaries primaries); + +/** + * @return the AVColorPrimaries value for name or an AVError if not found. + */ +int av_color_primaries_from_name(const char *name); + +/** + * @return the name for provided color transfer or NULL if unknown. + */ +const char *av_color_transfer_name(enum AVColorTransferCharacteristic transfer); + +/** + * @return the AVColorTransferCharacteristic value for name or an AVError if not found. + */ +int av_color_transfer_from_name(const char *name); + +/** + * @return the name for provided color space or NULL if unknown. + */ +const char *av_color_space_name(enum AVColorSpace space); + +/** + * @return the AVColorSpace value for name or an AVError if not found. + */ +int av_color_space_from_name(const char *name); + +/** + * @return the name for provided chroma location or NULL if unknown. + */ +const char *av_chroma_location_name(enum AVChromaLocation location); + +/** + * @return the AVChromaLocation value for name or an AVError if not found. + */ +int av_chroma_location_from_name(const char *name); + +/** + * Return the pixel format corresponding to name. + * + * If there is no pixel format with name name, then looks for a + * pixel format with the name corresponding to the native endian + * format of name. + * For example in a little-endian system, first looks for "gray16", + * then for "gray16le". + * + * Finally if no pixel format has been found, returns AV_PIX_FMT_NONE. + */ +enum AVPixelFormat av_get_pix_fmt(const char *name); + +/** + * Return the short name for a pixel format, NULL in case pix_fmt is + * unknown. + * + * @see av_get_pix_fmt(), av_get_pix_fmt_string() + */ +const char *av_get_pix_fmt_name(enum AVPixelFormat pix_fmt); + +/** + * Print in buf the string corresponding to the pixel format with + * number pix_fmt, or a header if pix_fmt is negative. + * + * @param buf the buffer where to write the string + * @param buf_size the size of buf + * @param pix_fmt the number of the pixel format to print the + * corresponding info string, or a negative value to print the + * corresponding header. + */ +char *av_get_pix_fmt_string(char *buf, int buf_size, + enum AVPixelFormat pix_fmt); + +/** + * Read a line from an image, and write the values of the + * pixel format component c to dst. + * + * @param data the array containing the pointers to the planes of the image + * @param linesize the array containing the linesizes of the image + * @param desc the pixel format descriptor for the image + * @param x the horizontal coordinate of the first pixel to read + * @param y the vertical coordinate of the first pixel to read + * @param w the width of the line to read, that is the number of + * values to write to dst + * @param read_pal_component if not zero and the format is a paletted + * format writes the values corresponding to the palette + * component c in data[1] to dst, rather than the palette indexes in + * data[0]. The behavior is undefined if the format is not paletted. + */ +void av_read_image_line(uint16_t *dst, const uint8_t *data[4], + const int linesize[4], const AVPixFmtDescriptor *desc, + int x, int y, int c, int w, int read_pal_component); + +/** + * Write the values from src to the pixel format component c of an + * image line. + * + * @param src array containing the values to write + * @param data the array containing the pointers to the planes of the + * image to write into. It is supposed to be zeroed. + * @param linesize the array containing the linesizes of the image + * @param desc the pixel format descriptor for the image + * @param x the horizontal coordinate of the first pixel to write + * @param y the vertical coordinate of the first pixel to write + * @param w the width of the line to write, that is the number of + * values to write to the image line + */ +void av_write_image_line(const uint16_t *src, uint8_t *data[4], + const int linesize[4], const AVPixFmtDescriptor *desc, + int x, int y, int c, int w); + +/** + * Utility function to swap the endianness of a pixel format. + * + * @param[in] pix_fmt the pixel format + * + * @return pixel format with swapped endianness if it exists, + * otherwise AV_PIX_FMT_NONE + */ +enum AVPixelFormat av_pix_fmt_swap_endianness(enum AVPixelFormat pix_fmt); + +#define FF_LOSS_RESOLUTION 0x0001 /**< loss due to resolution change */ +#define FF_LOSS_DEPTH 0x0002 /**< loss due to color depth change */ +#define FF_LOSS_COLORSPACE 0x0004 /**< loss due to color space conversion */ +#define FF_LOSS_ALPHA 0x0008 /**< loss of alpha bits */ +#define FF_LOSS_COLORQUANT 0x0010 /**< loss due to color quantization */ +#define FF_LOSS_CHROMA 0x0020 /**< loss of chroma (e.g. RGB to gray conversion) */ + +/** + * Compute what kind of losses will occur when converting from one specific + * pixel format to another. + * When converting from one pixel format to another, information loss may occur. + * For example, when converting from RGB24 to GRAY, the color information will + * be lost. Similarly, other losses occur when converting from some formats to + * other formats. These losses can involve loss of chroma, but also loss of + * resolution, loss of color depth, loss due to the color space conversion, loss + * of the alpha bits or loss due to color quantization. + * av_get_fix_fmt_loss() informs you about the various types of losses + * which will occur when converting from one pixel format to another. + * + * @param[in] dst_pix_fmt destination pixel format + * @param[in] src_pix_fmt source pixel format + * @param[in] has_alpha Whether the source pixel format alpha channel is used. + * @return Combination of flags informing you what kind of losses will occur + * (maximum loss for an invalid dst_pix_fmt). + */ +int av_get_pix_fmt_loss(enum AVPixelFormat dst_pix_fmt, + enum AVPixelFormat src_pix_fmt, + int has_alpha); + +/** + * Compute what kind of losses will occur when converting from one specific + * pixel format to another. + * When converting from one pixel format to another, information loss may occur. + * For example, when converting from RGB24 to GRAY, the color information will + * be lost. Similarly, other losses occur when converting from some formats to + * other formats. These losses can involve loss of chroma, but also loss of + * resolution, loss of color depth, loss due to the color space conversion, loss + * of the alpha bits or loss due to color quantization. + * av_get_fix_fmt_loss() informs you about the various types of losses + * which will occur when converting from one pixel format to another. + * + * @param[in] dst_pix_fmt destination pixel format + * @param[in] src_pix_fmt source pixel format + * @param[in] has_alpha Whether the source pixel format alpha channel is used. + * @return Combination of flags informing you what kind of losses will occur + * (maximum loss for an invalid dst_pix_fmt). + */ +enum AVPixelFormat av_find_best_pix_fmt_of_2(enum AVPixelFormat dst_pix_fmt1, enum AVPixelFormat dst_pix_fmt2, + enum AVPixelFormat src_pix_fmt, int has_alpha, int *loss_ptr); + +#endif /* AVUTIL_PIXDESC_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/pixelutils.h b/thrid-party/ffmpeg/include/libavutil/pixelutils.h new file mode 100644 index 0000000000..a8dbc157e1 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/pixelutils.h @@ -0,0 +1,52 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_PIXELUTILS_H +#define AVUTIL_PIXELUTILS_H + +#include +#include +#include "common.h" + +/** + * Sum of abs(src1[x] - src2[x]) + */ +typedef int (*av_pixelutils_sad_fn)(const uint8_t *src1, ptrdiff_t stride1, + const uint8_t *src2, ptrdiff_t stride2); + +/** + * Get a potentially optimized pointer to a Sum-of-absolute-differences + * function (see the av_pixelutils_sad_fn prototype). + * + * @param w_bits 1< + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_PIXFMT_H +#define AVUTIL_PIXFMT_H + +/** + * @file + * pixel format definitions + */ + +#include "libavutil/avconfig.h" +#include "version.h" + +#define AVPALETTE_SIZE 1024 +#define AVPALETTE_COUNT 256 + +/** + * Pixel format. + * + * @note + * AV_PIX_FMT_RGB32 is handled in an endian-specific manner. An RGBA + * color is put together as: + * (A << 24) | (R << 16) | (G << 8) | B + * This is stored as BGRA on little-endian CPU architectures and ARGB on + * big-endian CPUs. + * + * @par + * When the pixel format is palettized RGB32 (AV_PIX_FMT_PAL8), the palettized + * image data is stored in AVFrame.data[0]. The palette is transported in + * AVFrame.data[1], is 1024 bytes long (256 4-byte entries) and is + * formatted the same as in AV_PIX_FMT_RGB32 described above (i.e., it is + * also endian-specific). Note also that the individual RGB32 palette + * components stored in AVFrame.data[1] should be in the range 0..255. + * This is important as many custom PAL8 video codecs that were designed + * to run on the IBM VGA graphics adapter use 6-bit palette components. + * + * @par + * For all the 8 bits per pixel formats, an RGB32 palette is in data[1] like + * for pal8. This palette is filled in automatically by the function + * allocating the picture. + */ +enum AVPixelFormat { + AV_PIX_FMT_NONE = -1, + AV_PIX_FMT_YUV420P, ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) + AV_PIX_FMT_YUYV422, ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr + AV_PIX_FMT_RGB24, ///< packed RGB 8:8:8, 24bpp, RGBRGB... + AV_PIX_FMT_BGR24, ///< packed RGB 8:8:8, 24bpp, BGRBGR... + AV_PIX_FMT_YUV422P, ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples) + AV_PIX_FMT_YUV444P, ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples) + AV_PIX_FMT_YUV410P, ///< planar YUV 4:1:0, 9bpp, (1 Cr & Cb sample per 4x4 Y samples) + AV_PIX_FMT_YUV411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) + AV_PIX_FMT_GRAY8, ///< Y , 8bpp + AV_PIX_FMT_MONOWHITE, ///< Y , 1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb + AV_PIX_FMT_MONOBLACK, ///< Y , 1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb + AV_PIX_FMT_PAL8, ///< 8 bits with AV_PIX_FMT_RGB32 palette + AV_PIX_FMT_YUVJ420P, ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range + AV_PIX_FMT_YUVJ422P, ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV422P and setting color_range + AV_PIX_FMT_YUVJ444P, ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV444P and setting color_range +#if FF_API_XVMC + AV_PIX_FMT_XVMC_MPEG2_MC,///< XVideo Motion Acceleration via common packet passing + AV_PIX_FMT_XVMC_MPEG2_IDCT, + AV_PIX_FMT_XVMC = AV_PIX_FMT_XVMC_MPEG2_IDCT, +#endif /* FF_API_XVMC */ + AV_PIX_FMT_UYVY422, ///< packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1 + AV_PIX_FMT_UYYVYY411, ///< packed YUV 4:1:1, 12bpp, Cb Y0 Y1 Cr Y2 Y3 + AV_PIX_FMT_BGR8, ///< packed RGB 3:3:2, 8bpp, (msb)2B 3G 3R(lsb) + AV_PIX_FMT_BGR4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1B 2G 1R(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits + AV_PIX_FMT_BGR4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1B 2G 1R(lsb) + AV_PIX_FMT_RGB8, ///< packed RGB 3:3:2, 8bpp, (msb)2R 3G 3B(lsb) + AV_PIX_FMT_RGB4, ///< packed RGB 1:2:1 bitstream, 4bpp, (msb)1R 2G 1B(lsb), a byte contains two pixels, the first pixel in the byte is the one composed by the 4 msb bits + AV_PIX_FMT_RGB4_BYTE, ///< packed RGB 1:2:1, 8bpp, (msb)1R 2G 1B(lsb) + AV_PIX_FMT_NV12, ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V) + AV_PIX_FMT_NV21, ///< as above, but U and V bytes are swapped + + AV_PIX_FMT_ARGB, ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB... + AV_PIX_FMT_RGBA, ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA... + AV_PIX_FMT_ABGR, ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR... + AV_PIX_FMT_BGRA, ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA... + + AV_PIX_FMT_GRAY16BE, ///< Y , 16bpp, big-endian + AV_PIX_FMT_GRAY16LE, ///< Y , 16bpp, little-endian + AV_PIX_FMT_YUV440P, ///< planar YUV 4:4:0 (1 Cr & Cb sample per 1x2 Y samples) + AV_PIX_FMT_YUVJ440P, ///< planar YUV 4:4:0 full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV440P and setting color_range + AV_PIX_FMT_YUVA420P, ///< planar YUV 4:2:0, 20bpp, (1 Cr & Cb sample per 2x2 Y & A samples) +#if FF_API_VDPAU + AV_PIX_FMT_VDPAU_H264,///< H.264 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers + AV_PIX_FMT_VDPAU_MPEG1,///< MPEG-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers + AV_PIX_FMT_VDPAU_MPEG2,///< MPEG-2 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers + AV_PIX_FMT_VDPAU_WMV3,///< WMV3 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers + AV_PIX_FMT_VDPAU_VC1, ///< VC-1 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers +#endif + AV_PIX_FMT_RGB48BE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as big-endian + AV_PIX_FMT_RGB48LE, ///< packed RGB 16:16:16, 48bpp, 16R, 16G, 16B, the 2-byte value for each R/G/B component is stored as little-endian + + AV_PIX_FMT_RGB565BE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), big-endian + AV_PIX_FMT_RGB565LE, ///< packed RGB 5:6:5, 16bpp, (msb) 5R 6G 5B(lsb), little-endian + AV_PIX_FMT_RGB555BE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian , X=unused/undefined + AV_PIX_FMT_RGB555LE, ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined + + AV_PIX_FMT_BGR565BE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), big-endian + AV_PIX_FMT_BGR565LE, ///< packed BGR 5:6:5, 16bpp, (msb) 5B 6G 5R(lsb), little-endian + AV_PIX_FMT_BGR555BE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), big-endian , X=unused/undefined + AV_PIX_FMT_BGR555LE, ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), little-endian, X=unused/undefined + +#if FF_API_VAAPI + /** @name Deprecated pixel formats */ + /**@{*/ + AV_PIX_FMT_VAAPI_MOCO, ///< HW acceleration through VA API at motion compensation entry-point, Picture.data[3] contains a vaapi_render_state struct which contains macroblocks as well as various fields extracted from headers + AV_PIX_FMT_VAAPI_IDCT, ///< HW acceleration through VA API at IDCT entry-point, Picture.data[3] contains a vaapi_render_state struct which contains fields extracted from headers + AV_PIX_FMT_VAAPI_VLD, ///< HW decoding through VA API, Picture.data[3] contains a VASurfaceID + /**@}*/ + AV_PIX_FMT_VAAPI = AV_PIX_FMT_VAAPI_VLD, +#else + /** + * Hardware acceleration through VA-API, data[3] contains a + * VASurfaceID. + */ + AV_PIX_FMT_VAAPI, +#endif + + AV_PIX_FMT_YUV420P16LE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P16BE, ///< planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV422P16LE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV422P16BE, ///< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV444P16LE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P16BE, ///< planar YUV 4:4:4, 48bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian +#if FF_API_VDPAU + AV_PIX_FMT_VDPAU_MPEG4, ///< MPEG-4 HW decoding with VDPAU, data[0] contains a vdpau_render_state struct which contains the bitstream of the slices as well as various fields extracted from headers +#endif + AV_PIX_FMT_DXVA2_VLD, ///< HW decoding through DXVA2, Picture.data[3] contains a LPDIRECT3DSURFACE9 pointer + + AV_PIX_FMT_RGB444LE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_RGB444BE, ///< packed RGB 4:4:4, 16bpp, (msb)4X 4R 4G 4B(lsb), big-endian, X=unused/undefined + AV_PIX_FMT_BGR444LE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), little-endian, X=unused/undefined + AV_PIX_FMT_BGR444BE, ///< packed BGR 4:4:4, 16bpp, (msb)4X 4B 4G 4R(lsb), big-endian, X=unused/undefined + AV_PIX_FMT_YA8, ///< 8 bits gray, 8 bits alpha + + AV_PIX_FMT_Y400A = AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8 + AV_PIX_FMT_GRAY8A= AV_PIX_FMT_YA8, ///< alias for AV_PIX_FMT_YA8 + + AV_PIX_FMT_BGR48BE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as big-endian + AV_PIX_FMT_BGR48LE, ///< packed RGB 16:16:16, 48bpp, 16B, 16G, 16R, the 2-byte value for each R/G/B component is stored as little-endian + + /** + * The following 12 formats have the disadvantage of needing 1 format for each bit depth. + * Notice that each 9/10 bits sample is stored in 16 bits with extra padding. + * If you want to support multiple bit depths, then using AV_PIX_FMT_YUV420P16* with the bpp stored separately is better. + */ + AV_PIX_FMT_YUV420P9BE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P9LE, ///< planar YUV 4:2:0, 13.5bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P10BE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P10LE,///< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV422P10BE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P10LE,///< planar YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV444P9BE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P9LE, ///< planar YUV 4:4:4, 27bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P10BE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P10LE,///< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV422P9BE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P9LE, ///< planar YUV 4:2:2, 18bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_VDA_VLD, ///< hardware decoding through VDA + AV_PIX_FMT_GBRP, ///< planar GBR 4:4:4 24bpp + AV_PIX_FMT_GBR24P = AV_PIX_FMT_GBRP, // alias for #AV_PIX_FMT_GBRP + AV_PIX_FMT_GBRP9BE, ///< planar GBR 4:4:4 27bpp, big-endian + AV_PIX_FMT_GBRP9LE, ///< planar GBR 4:4:4 27bpp, little-endian + AV_PIX_FMT_GBRP10BE, ///< planar GBR 4:4:4 30bpp, big-endian + AV_PIX_FMT_GBRP10LE, ///< planar GBR 4:4:4 30bpp, little-endian + AV_PIX_FMT_GBRP16BE, ///< planar GBR 4:4:4 48bpp, big-endian + AV_PIX_FMT_GBRP16LE, ///< planar GBR 4:4:4 48bpp, little-endian + AV_PIX_FMT_YUVA422P, ///< planar YUV 4:2:2 24bpp, (1 Cr & Cb sample per 2x1 Y & A samples) + AV_PIX_FMT_YUVA444P, ///< planar YUV 4:4:4 32bpp, (1 Cr & Cb sample per 1x1 Y & A samples) + AV_PIX_FMT_YUVA420P9BE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), big-endian + AV_PIX_FMT_YUVA420P9LE, ///< planar YUV 4:2:0 22.5bpp, (1 Cr & Cb sample per 2x2 Y & A samples), little-endian + AV_PIX_FMT_YUVA422P9BE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), big-endian + AV_PIX_FMT_YUVA422P9LE, ///< planar YUV 4:2:2 27bpp, (1 Cr & Cb sample per 2x1 Y & A samples), little-endian + AV_PIX_FMT_YUVA444P9BE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), big-endian + AV_PIX_FMT_YUVA444P9LE, ///< planar YUV 4:4:4 36bpp, (1 Cr & Cb sample per 1x1 Y & A samples), little-endian + AV_PIX_FMT_YUVA420P10BE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian) + AV_PIX_FMT_YUVA420P10LE, ///< planar YUV 4:2:0 25bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian) + AV_PIX_FMT_YUVA422P10BE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA422P10LE, ///< planar YUV 4:2:2 30bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA444P10BE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA444P10LE, ///< planar YUV 4:4:4 40bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA420P16BE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, big-endian) + AV_PIX_FMT_YUVA420P16LE, ///< planar YUV 4:2:0 40bpp, (1 Cr & Cb sample per 2x2 Y & A samples, little-endian) + AV_PIX_FMT_YUVA422P16BE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA422P16LE, ///< planar YUV 4:2:2 48bpp, (1 Cr & Cb sample per 2x1 Y & A samples, little-endian) + AV_PIX_FMT_YUVA444P16BE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, big-endian) + AV_PIX_FMT_YUVA444P16LE, ///< planar YUV 4:4:4 64bpp, (1 Cr & Cb sample per 1x1 Y & A samples, little-endian) + + AV_PIX_FMT_VDPAU, ///< HW acceleration through VDPAU, Picture.data[3] contains a VdpVideoSurface + + AV_PIX_FMT_XYZ12LE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as little-endian, the 4 lower bits are set to 0 + AV_PIX_FMT_XYZ12BE, ///< packed XYZ 4:4:4, 36 bpp, (msb) 12X, 12Y, 12Z (lsb), the 2-byte value for each X/Y/Z is stored as big-endian, the 4 lower bits are set to 0 + AV_PIX_FMT_NV16, ///< interleaved chroma YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples) + AV_PIX_FMT_NV20LE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_NV20BE, ///< interleaved chroma YUV 4:2:2, 20bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + + AV_PIX_FMT_RGBA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian + AV_PIX_FMT_RGBA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16R, 16G, 16B, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian + AV_PIX_FMT_BGRA64BE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as big-endian + AV_PIX_FMT_BGRA64LE, ///< packed RGBA 16:16:16:16, 64bpp, 16B, 16G, 16R, 16A, the 2-byte value for each R/G/B/A component is stored as little-endian + + AV_PIX_FMT_YVYU422, ///< packed YUV 4:2:2, 16bpp, Y0 Cr Y1 Cb + + AV_PIX_FMT_VDA, ///< HW acceleration through VDA, data[3] contains a CVPixelBufferRef + + AV_PIX_FMT_YA16BE, ///< 16 bits gray, 16 bits alpha (big-endian) + AV_PIX_FMT_YA16LE, ///< 16 bits gray, 16 bits alpha (little-endian) + + AV_PIX_FMT_GBRAP, ///< planar GBRA 4:4:4:4 32bpp + AV_PIX_FMT_GBRAP16BE, ///< planar GBRA 4:4:4:4 64bpp, big-endian + AV_PIX_FMT_GBRAP16LE, ///< planar GBRA 4:4:4:4 64bpp, little-endian + /** + * HW acceleration through QSV, data[3] contains a pointer to the + * mfxFrameSurface1 structure. + */ + AV_PIX_FMT_QSV, + /** + * HW acceleration though MMAL, data[3] contains a pointer to the + * MMAL_BUFFER_HEADER_T structure. + */ + AV_PIX_FMT_MMAL, + + AV_PIX_FMT_D3D11VA_VLD, ///< HW decoding through Direct3D11 via old API, Picture.data[3] contains a ID3D11VideoDecoderOutputView pointer + + /** + * HW acceleration through CUDA. data[i] contain CUdeviceptr pointers + * exactly as for system memory frames. + */ + AV_PIX_FMT_CUDA, + + AV_PIX_FMT_0RGB=0x123+4,///< packed RGB 8:8:8, 32bpp, XRGBXRGB... X=unused/undefined + AV_PIX_FMT_RGB0, ///< packed RGB 8:8:8, 32bpp, RGBXRGBX... X=unused/undefined + AV_PIX_FMT_0BGR, ///< packed BGR 8:8:8, 32bpp, XBGRXBGR... X=unused/undefined + AV_PIX_FMT_BGR0, ///< packed BGR 8:8:8, 32bpp, BGRXBGRX... X=unused/undefined + + AV_PIX_FMT_YUV420P12BE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P12LE, ///< planar YUV 4:2:0,18bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV420P14BE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), big-endian + AV_PIX_FMT_YUV420P14LE, ///< planar YUV 4:2:0,21bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian + AV_PIX_FMT_YUV422P12BE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P12LE, ///< planar YUV 4:2:2,24bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV422P14BE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), big-endian + AV_PIX_FMT_YUV422P14LE, ///< planar YUV 4:2:2,28bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian + AV_PIX_FMT_YUV444P12BE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P12LE, ///< planar YUV 4:4:4,36bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_YUV444P14BE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), big-endian + AV_PIX_FMT_YUV444P14LE, ///< planar YUV 4:4:4,42bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian + AV_PIX_FMT_GBRP12BE, ///< planar GBR 4:4:4 36bpp, big-endian + AV_PIX_FMT_GBRP12LE, ///< planar GBR 4:4:4 36bpp, little-endian + AV_PIX_FMT_GBRP14BE, ///< planar GBR 4:4:4 42bpp, big-endian + AV_PIX_FMT_GBRP14LE, ///< planar GBR 4:4:4 42bpp, little-endian + AV_PIX_FMT_YUVJ411P, ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples) full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV411P and setting color_range + + AV_PIX_FMT_BAYER_BGGR8, ///< bayer, BGBG..(odd line), GRGR..(even line), 8-bit samples */ + AV_PIX_FMT_BAYER_RGGB8, ///< bayer, RGRG..(odd line), GBGB..(even line), 8-bit samples */ + AV_PIX_FMT_BAYER_GBRG8, ///< bayer, GBGB..(odd line), RGRG..(even line), 8-bit samples */ + AV_PIX_FMT_BAYER_GRBG8, ///< bayer, GRGR..(odd line), BGBG..(even line), 8-bit samples */ + AV_PIX_FMT_BAYER_BGGR16LE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, little-endian */ + AV_PIX_FMT_BAYER_BGGR16BE, ///< bayer, BGBG..(odd line), GRGR..(even line), 16-bit samples, big-endian */ + AV_PIX_FMT_BAYER_RGGB16LE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, little-endian */ + AV_PIX_FMT_BAYER_RGGB16BE, ///< bayer, RGRG..(odd line), GBGB..(even line), 16-bit samples, big-endian */ + AV_PIX_FMT_BAYER_GBRG16LE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, little-endian */ + AV_PIX_FMT_BAYER_GBRG16BE, ///< bayer, GBGB..(odd line), RGRG..(even line), 16-bit samples, big-endian */ + AV_PIX_FMT_BAYER_GRBG16LE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, little-endian */ + AV_PIX_FMT_BAYER_GRBG16BE, ///< bayer, GRGR..(odd line), BGBG..(even line), 16-bit samples, big-endian */ +#if !FF_API_XVMC + AV_PIX_FMT_XVMC,///< XVideo Motion Acceleration via common packet passing +#endif /* !FF_API_XVMC */ + AV_PIX_FMT_YUV440P10LE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian + AV_PIX_FMT_YUV440P10BE, ///< planar YUV 4:4:0,20bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian + AV_PIX_FMT_YUV440P12LE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), little-endian + AV_PIX_FMT_YUV440P12BE, ///< planar YUV 4:4:0,24bpp, (1 Cr & Cb sample per 1x2 Y samples), big-endian + AV_PIX_FMT_AYUV64LE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), little-endian + AV_PIX_FMT_AYUV64BE, ///< packed AYUV 4:4:4,64bpp (1 Cr & Cb sample per 1x1 Y & A samples), big-endian + + AV_PIX_FMT_VIDEOTOOLBOX, ///< hardware decoding through Videotoolbox + + AV_PIX_FMT_P010LE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, little-endian + AV_PIX_FMT_P010BE, ///< like NV12, with 10bpp per component, data in the high bits, zeros in the low bits, big-endian + + AV_PIX_FMT_GBRAP12BE, ///< planar GBR 4:4:4:4 48bpp, big-endian + AV_PIX_FMT_GBRAP12LE, ///< planar GBR 4:4:4:4 48bpp, little-endian + + AV_PIX_FMT_GBRAP10BE, ///< planar GBR 4:4:4:4 40bpp, big-endian + AV_PIX_FMT_GBRAP10LE, ///< planar GBR 4:4:4:4 40bpp, little-endian + + AV_PIX_FMT_MEDIACODEC, ///< hardware decoding through MediaCodec + + AV_PIX_FMT_GRAY12BE, ///< Y , 12bpp, big-endian + AV_PIX_FMT_GRAY12LE, ///< Y , 12bpp, little-endian + AV_PIX_FMT_GRAY10BE, ///< Y , 10bpp, big-endian + AV_PIX_FMT_GRAY10LE, ///< Y , 10bpp, little-endian + + AV_PIX_FMT_P016LE, ///< like NV12, with 16bpp per component, little-endian + AV_PIX_FMT_P016BE, ///< like NV12, with 16bpp per component, big-endian + + /** + * Hardware surfaces for Direct3D11. + * + * This is preferred over the legacy AV_PIX_FMT_D3D11VA_VLD. The new D3D11 + * hwaccel API and filtering support AV_PIX_FMT_D3D11 only. + * + * data[0] contains a ID3D11Texture2D pointer, and data[1] contains the + * texture array index of the frame as intptr_t if the ID3D11Texture2D is + * an array texture (or always 0 if it's a normal texture). + */ + AV_PIX_FMT_D3D11, + + AV_PIX_FMT_GRAY9BE, ///< Y , 9bpp, big-endian + AV_PIX_FMT_GRAY9LE, ///< Y , 9bpp, little-endian + + AV_PIX_FMT_GBRPF32BE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp, big-endian + AV_PIX_FMT_GBRPF32LE, ///< IEEE-754 single precision planar GBR 4:4:4, 96bpp, little-endian + AV_PIX_FMT_GBRAPF32BE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, big-endian + AV_PIX_FMT_GBRAPF32LE, ///< IEEE-754 single precision planar GBRA 4:4:4:4, 128bpp, little-endian + + /** + * DRM-managed buffers exposed through PRIME buffer sharing. + * + * data[0] points to an AVDRMFrameDescriptor. + */ + AV_PIX_FMT_DRM_PRIME, + + AV_PIX_FMT_NB ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions +}; + +#if AV_HAVE_BIGENDIAN +# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##be +#else +# define AV_PIX_FMT_NE(be, le) AV_PIX_FMT_##le +#endif + +#define AV_PIX_FMT_RGB32 AV_PIX_FMT_NE(ARGB, BGRA) +#define AV_PIX_FMT_RGB32_1 AV_PIX_FMT_NE(RGBA, ABGR) +#define AV_PIX_FMT_BGR32 AV_PIX_FMT_NE(ABGR, RGBA) +#define AV_PIX_FMT_BGR32_1 AV_PIX_FMT_NE(BGRA, ARGB) +#define AV_PIX_FMT_0RGB32 AV_PIX_FMT_NE(0RGB, BGR0) +#define AV_PIX_FMT_0BGR32 AV_PIX_FMT_NE(0BGR, RGB0) + +#define AV_PIX_FMT_GRAY9 AV_PIX_FMT_NE(GRAY9BE, GRAY9LE) +#define AV_PIX_FMT_GRAY10 AV_PIX_FMT_NE(GRAY10BE, GRAY10LE) +#define AV_PIX_FMT_GRAY12 AV_PIX_FMT_NE(GRAY12BE, GRAY12LE) +#define AV_PIX_FMT_GRAY16 AV_PIX_FMT_NE(GRAY16BE, GRAY16LE) +#define AV_PIX_FMT_YA16 AV_PIX_FMT_NE(YA16BE, YA16LE) +#define AV_PIX_FMT_RGB48 AV_PIX_FMT_NE(RGB48BE, RGB48LE) +#define AV_PIX_FMT_RGB565 AV_PIX_FMT_NE(RGB565BE, RGB565LE) +#define AV_PIX_FMT_RGB555 AV_PIX_FMT_NE(RGB555BE, RGB555LE) +#define AV_PIX_FMT_RGB444 AV_PIX_FMT_NE(RGB444BE, RGB444LE) +#define AV_PIX_FMT_RGBA64 AV_PIX_FMT_NE(RGBA64BE, RGBA64LE) +#define AV_PIX_FMT_BGR48 AV_PIX_FMT_NE(BGR48BE, BGR48LE) +#define AV_PIX_FMT_BGR565 AV_PIX_FMT_NE(BGR565BE, BGR565LE) +#define AV_PIX_FMT_BGR555 AV_PIX_FMT_NE(BGR555BE, BGR555LE) +#define AV_PIX_FMT_BGR444 AV_PIX_FMT_NE(BGR444BE, BGR444LE) +#define AV_PIX_FMT_BGRA64 AV_PIX_FMT_NE(BGRA64BE, BGRA64LE) + +#define AV_PIX_FMT_YUV420P9 AV_PIX_FMT_NE(YUV420P9BE , YUV420P9LE) +#define AV_PIX_FMT_YUV422P9 AV_PIX_FMT_NE(YUV422P9BE , YUV422P9LE) +#define AV_PIX_FMT_YUV444P9 AV_PIX_FMT_NE(YUV444P9BE , YUV444P9LE) +#define AV_PIX_FMT_YUV420P10 AV_PIX_FMT_NE(YUV420P10BE, YUV420P10LE) +#define AV_PIX_FMT_YUV422P10 AV_PIX_FMT_NE(YUV422P10BE, YUV422P10LE) +#define AV_PIX_FMT_YUV440P10 AV_PIX_FMT_NE(YUV440P10BE, YUV440P10LE) +#define AV_PIX_FMT_YUV444P10 AV_PIX_FMT_NE(YUV444P10BE, YUV444P10LE) +#define AV_PIX_FMT_YUV420P12 AV_PIX_FMT_NE(YUV420P12BE, YUV420P12LE) +#define AV_PIX_FMT_YUV422P12 AV_PIX_FMT_NE(YUV422P12BE, YUV422P12LE) +#define AV_PIX_FMT_YUV440P12 AV_PIX_FMT_NE(YUV440P12BE, YUV440P12LE) +#define AV_PIX_FMT_YUV444P12 AV_PIX_FMT_NE(YUV444P12BE, YUV444P12LE) +#define AV_PIX_FMT_YUV420P14 AV_PIX_FMT_NE(YUV420P14BE, YUV420P14LE) +#define AV_PIX_FMT_YUV422P14 AV_PIX_FMT_NE(YUV422P14BE, YUV422P14LE) +#define AV_PIX_FMT_YUV444P14 AV_PIX_FMT_NE(YUV444P14BE, YUV444P14LE) +#define AV_PIX_FMT_YUV420P16 AV_PIX_FMT_NE(YUV420P16BE, YUV420P16LE) +#define AV_PIX_FMT_YUV422P16 AV_PIX_FMT_NE(YUV422P16BE, YUV422P16LE) +#define AV_PIX_FMT_YUV444P16 AV_PIX_FMT_NE(YUV444P16BE, YUV444P16LE) + +#define AV_PIX_FMT_GBRP9 AV_PIX_FMT_NE(GBRP9BE , GBRP9LE) +#define AV_PIX_FMT_GBRP10 AV_PIX_FMT_NE(GBRP10BE, GBRP10LE) +#define AV_PIX_FMT_GBRP12 AV_PIX_FMT_NE(GBRP12BE, GBRP12LE) +#define AV_PIX_FMT_GBRP14 AV_PIX_FMT_NE(GBRP14BE, GBRP14LE) +#define AV_PIX_FMT_GBRP16 AV_PIX_FMT_NE(GBRP16BE, GBRP16LE) +#define AV_PIX_FMT_GBRAP10 AV_PIX_FMT_NE(GBRAP10BE, GBRAP10LE) +#define AV_PIX_FMT_GBRAP12 AV_PIX_FMT_NE(GBRAP12BE, GBRAP12LE) +#define AV_PIX_FMT_GBRAP16 AV_PIX_FMT_NE(GBRAP16BE, GBRAP16LE) + +#define AV_PIX_FMT_BAYER_BGGR16 AV_PIX_FMT_NE(BAYER_BGGR16BE, BAYER_BGGR16LE) +#define AV_PIX_FMT_BAYER_RGGB16 AV_PIX_FMT_NE(BAYER_RGGB16BE, BAYER_RGGB16LE) +#define AV_PIX_FMT_BAYER_GBRG16 AV_PIX_FMT_NE(BAYER_GBRG16BE, BAYER_GBRG16LE) +#define AV_PIX_FMT_BAYER_GRBG16 AV_PIX_FMT_NE(BAYER_GRBG16BE, BAYER_GRBG16LE) + +#define AV_PIX_FMT_GBRPF32 AV_PIX_FMT_NE(GBRPF32BE, GBRPF32LE) +#define AV_PIX_FMT_GBRAPF32 AV_PIX_FMT_NE(GBRAPF32BE, GBRAPF32LE) + +#define AV_PIX_FMT_YUVA420P9 AV_PIX_FMT_NE(YUVA420P9BE , YUVA420P9LE) +#define AV_PIX_FMT_YUVA422P9 AV_PIX_FMT_NE(YUVA422P9BE , YUVA422P9LE) +#define AV_PIX_FMT_YUVA444P9 AV_PIX_FMT_NE(YUVA444P9BE , YUVA444P9LE) +#define AV_PIX_FMT_YUVA420P10 AV_PIX_FMT_NE(YUVA420P10BE, YUVA420P10LE) +#define AV_PIX_FMT_YUVA422P10 AV_PIX_FMT_NE(YUVA422P10BE, YUVA422P10LE) +#define AV_PIX_FMT_YUVA444P10 AV_PIX_FMT_NE(YUVA444P10BE, YUVA444P10LE) +#define AV_PIX_FMT_YUVA420P16 AV_PIX_FMT_NE(YUVA420P16BE, YUVA420P16LE) +#define AV_PIX_FMT_YUVA422P16 AV_PIX_FMT_NE(YUVA422P16BE, YUVA422P16LE) +#define AV_PIX_FMT_YUVA444P16 AV_PIX_FMT_NE(YUVA444P16BE, YUVA444P16LE) + +#define AV_PIX_FMT_XYZ12 AV_PIX_FMT_NE(XYZ12BE, XYZ12LE) +#define AV_PIX_FMT_NV20 AV_PIX_FMT_NE(NV20BE, NV20LE) +#define AV_PIX_FMT_AYUV64 AV_PIX_FMT_NE(AYUV64BE, AYUV64LE) +#define AV_PIX_FMT_P010 AV_PIX_FMT_NE(P010BE, P010LE) +#define AV_PIX_FMT_P016 AV_PIX_FMT_NE(P016BE, P016LE) + +/** + * Chromaticity coordinates of the source primaries. + * These values match the ones defined by ISO/IEC 23001-8_2013 § 7.1. + */ +enum AVColorPrimaries { + AVCOL_PRI_RESERVED0 = 0, + AVCOL_PRI_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP177 Annex B + AVCOL_PRI_UNSPECIFIED = 2, + AVCOL_PRI_RESERVED = 3, + AVCOL_PRI_BT470M = 4, ///< also FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + + AVCOL_PRI_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM + AVCOL_PRI_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC + AVCOL_PRI_SMPTE240M = 7, ///< functionally identical to above + AVCOL_PRI_FILM = 8, ///< colour filters using Illuminant C + AVCOL_PRI_BT2020 = 9, ///< ITU-R BT2020 + AVCOL_PRI_SMPTE428 = 10, ///< SMPTE ST 428-1 (CIE 1931 XYZ) + AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428, + AVCOL_PRI_SMPTE431 = 11, ///< SMPTE ST 431-2 (2011) / DCI P3 + AVCOL_PRI_SMPTE432 = 12, ///< SMPTE ST 432-1 (2010) / P3 D65 / Display P3 + AVCOL_PRI_JEDEC_P22 = 22, ///< JEDEC P22 phosphors + AVCOL_PRI_NB ///< Not part of ABI +}; + +/** + * Color Transfer Characteristic. + * These values match the ones defined by ISO/IEC 23001-8_2013 § 7.2. + */ +enum AVColorTransferCharacteristic { + AVCOL_TRC_RESERVED0 = 0, + AVCOL_TRC_BT709 = 1, ///< also ITU-R BT1361 + AVCOL_TRC_UNSPECIFIED = 2, + AVCOL_TRC_RESERVED = 3, + AVCOL_TRC_GAMMA22 = 4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM + AVCOL_TRC_GAMMA28 = 5, ///< also ITU-R BT470BG + AVCOL_TRC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC + AVCOL_TRC_SMPTE240M = 7, + AVCOL_TRC_LINEAR = 8, ///< "Linear transfer characteristics" + AVCOL_TRC_LOG = 9, ///< "Logarithmic transfer characteristic (100:1 range)" + AVCOL_TRC_LOG_SQRT = 10, ///< "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)" + AVCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4 + AVCOL_TRC_BT1361_ECG = 12, ///< ITU-R BT1361 Extended Colour Gamut + AVCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC) + AVCOL_TRC_BT2020_10 = 14, ///< ITU-R BT2020 for 10-bit system + AVCOL_TRC_BT2020_12 = 15, ///< ITU-R BT2020 for 12-bit system + AVCOL_TRC_SMPTE2084 = 16, ///< SMPTE ST 2084 for 10-, 12-, 14- and 16-bit systems + AVCOL_TRC_SMPTEST2084 = AVCOL_TRC_SMPTE2084, + AVCOL_TRC_SMPTE428 = 17, ///< SMPTE ST 428-1 + AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428, + AVCOL_TRC_ARIB_STD_B67 = 18, ///< ARIB STD-B67, known as "Hybrid log-gamma" + AVCOL_TRC_NB ///< Not part of ABI +}; + +/** + * YUV colorspace type. + * These values match the ones defined by ISO/IEC 23001-8_2013 § 7.3. + */ +enum AVColorSpace { + AVCOL_SPC_RGB = 0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB) + AVCOL_SPC_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / SMPTE RP177 Annex B + AVCOL_SPC_UNSPECIFIED = 2, + AVCOL_SPC_RESERVED = 3, + AVCOL_SPC_FCC = 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20) + AVCOL_SPC_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601 + AVCOL_SPC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC + AVCOL_SPC_SMPTE240M = 7, ///< functionally identical to above + AVCOL_SPC_YCGCO = 8, ///< Used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16 + AVCOL_SPC_YCOCG = AVCOL_SPC_YCGCO, + AVCOL_SPC_BT2020_NCL = 9, ///< ITU-R BT2020 non-constant luminance system + AVCOL_SPC_BT2020_CL = 10, ///< ITU-R BT2020 constant luminance system + AVCOL_SPC_SMPTE2085 = 11, ///< SMPTE 2085, Y'D'zD'x + AVCOL_SPC_CHROMA_DERIVED_NCL = 12, ///< Chromaticity-derived non-constant luminance system + AVCOL_SPC_CHROMA_DERIVED_CL = 13, ///< Chromaticity-derived constant luminance system + AVCOL_SPC_ICTCP = 14, ///< ITU-R BT.2100-0, ICtCp + AVCOL_SPC_NB ///< Not part of ABI +}; + +/** + * MPEG vs JPEG YUV range. + */ +enum AVColorRange { + AVCOL_RANGE_UNSPECIFIED = 0, + AVCOL_RANGE_MPEG = 1, ///< the normal 219*2^(n-8) "MPEG" YUV ranges + AVCOL_RANGE_JPEG = 2, ///< the normal 2^n-1 "JPEG" YUV ranges + AVCOL_RANGE_NB ///< Not part of ABI +}; + +/** + * Location of chroma samples. + * + * Illustration showing the location of the first (top left) chroma sample of the + * image, the left shows only luma, the right + * shows the location of the chroma sample, the 2 could be imagined to overlay + * each other but are drawn separately due to limitations of ASCII + * + * 1st 2nd 1st 2nd horizontal luma sample positions + * v v v v + * ______ ______ + *1st luma line > |X X ... |3 4 X ... X are luma samples, + * | |1 2 1-6 are possible chroma positions + *2nd luma line > |X X ... |5 6 X ... 0 is undefined/unknown position + */ +enum AVChromaLocation { + AVCHROMA_LOC_UNSPECIFIED = 0, + AVCHROMA_LOC_LEFT = 1, ///< MPEG-2/4 4:2:0, H.264 default for 4:2:0 + AVCHROMA_LOC_CENTER = 2, ///< MPEG-1 4:2:0, JPEG 4:2:0, H.263 4:2:0 + AVCHROMA_LOC_TOPLEFT = 3, ///< ITU-R 601, SMPTE 274M 296M S314M(DV 4:1:1), mpeg2 4:2:2 + AVCHROMA_LOC_TOP = 4, + AVCHROMA_LOC_BOTTOMLEFT = 5, + AVCHROMA_LOC_BOTTOM = 6, + AVCHROMA_LOC_NB ///< Not part of ABI +}; + +#endif /* AVUTIL_PIXFMT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/random_seed.h b/thrid-party/ffmpeg/include/libavutil/random_seed.h new file mode 100644 index 0000000000..0462a048e0 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/random_seed.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009 Baptiste Coudurier + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_RANDOM_SEED_H +#define AVUTIL_RANDOM_SEED_H + +#include +/** + * @addtogroup lavu_crypto + * @{ + */ + +/** + * Get a seed to use in conjunction with random functions. + * This function tries to provide a good seed at a best effort bases. + * Its possible to call this function multiple times if more bits are needed. + * It can be quite slow, which is why it should only be used as seed for a faster + * PRNG. The quality of the seed depends on the platform. + */ +uint32_t av_get_random_seed(void); + +/** + * @} + */ + +#endif /* AVUTIL_RANDOM_SEED_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/rational.h b/thrid-party/ffmpeg/include/libavutil/rational.h new file mode 100644 index 0000000000..5c6b67b4e9 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/rational.h @@ -0,0 +1,214 @@ +/* + * rational numbers + * Copyright (c) 2003 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_math_rational + * Utilties for rational number calculation. + * @author Michael Niedermayer + */ + +#ifndef AVUTIL_RATIONAL_H +#define AVUTIL_RATIONAL_H + +#include +#include +#include "attributes.h" + +/** + * @defgroup lavu_math_rational AVRational + * @ingroup lavu_math + * Rational number calculation. + * + * While rational numbers can be expressed as floating-point numbers, the + * conversion process is a lossy one, so are floating-point operations. On the + * other hand, the nature of FFmpeg demands highly accurate calculation of + * timestamps. This set of rational number utilities serves as a generic + * interface for manipulating rational numbers as pairs of numerators and + * denominators. + * + * Many of the functions that operate on AVRational's have the suffix `_q`, in + * reference to the mathematical symbol "ℚ" (Q) which denotes the set of all + * rational numbers. + * + * @{ + */ + +/** + * Rational number (pair of numerator and denominator). + */ +typedef struct AVRational{ + int num; ///< Numerator + int den; ///< Denominator +} AVRational; + +/** + * Create an AVRational. + * + * Useful for compilers that do not support compound literals. + * + * @note The return value is not reduced. + * @see av_reduce() + */ +static inline AVRational av_make_q(int num, int den) +{ + AVRational r = { num, den }; + return r; +} + +/** + * Compare two rationals. + * + * @param a First rational + * @param b Second rational + * + * @return One of the following values: + * - 0 if `a == b` + * - 1 if `a > b` + * - -1 if `a < b` + * - `INT_MIN` if one of the values is of the form `0 / 0` + */ +static inline int av_cmp_q(AVRational a, AVRational b){ + const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den; + + if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1; + else if(b.den && a.den) return 0; + else if(a.num && b.num) return (a.num>>31) - (b.num>>31); + else return INT_MIN; +} + +/** + * Convert an AVRational to a `double`. + * @param a AVRational to convert + * @return `a` in floating-point form + * @see av_d2q() + */ +static inline double av_q2d(AVRational a){ + return a.num / (double) a.den; +} + +/** + * Reduce a fraction. + * + * This is useful for framerate calculations. + * + * @param[out] dst_num Destination numerator + * @param[out] dst_den Destination denominator + * @param[in] num Source numerator + * @param[in] den Source denominator + * @param[in] max Maximum allowed values for `dst_num` & `dst_den` + * @return 1 if the operation is exact, 0 otherwise + */ +int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max); + +/** + * Multiply two rationals. + * @param b First rational + * @param c Second rational + * @return b*c + */ +AVRational av_mul_q(AVRational b, AVRational c) av_const; + +/** + * Divide one rational by another. + * @param b First rational + * @param c Second rational + * @return b/c + */ +AVRational av_div_q(AVRational b, AVRational c) av_const; + +/** + * Add two rationals. + * @param b First rational + * @param c Second rational + * @return b+c + */ +AVRational av_add_q(AVRational b, AVRational c) av_const; + +/** + * Subtract one rational from another. + * @param b First rational + * @param c Second rational + * @return b-c + */ +AVRational av_sub_q(AVRational b, AVRational c) av_const; + +/** + * Invert a rational. + * @param q value + * @return 1 / q + */ +static av_always_inline AVRational av_inv_q(AVRational q) +{ + AVRational r = { q.den, q.num }; + return r; +} + +/** + * Convert a double precision floating point number to a rational. + * + * In case of infinity, the returned value is expressed as `{1, 0}` or + * `{-1, 0}` depending on the sign. + * + * @param d `double` to convert + * @param max Maximum allowed numerator and denominator + * @return `d` in AVRational form + * @see av_q2d() + */ +AVRational av_d2q(double d, int max) av_const; + +/** + * Find which of the two rationals is closer to another rational. + * + * @param q Rational to be compared against + * @param q1,q2 Rationals to be tested + * @return One of the following values: + * - 1 if `q1` is nearer to `q` than `q2` + * - -1 if `q2` is nearer to `q` than `q1` + * - 0 if they have the same distance + */ +int av_nearer_q(AVRational q, AVRational q1, AVRational q2); + +/** + * Find the value in a list of rationals nearest a given reference rational. + * + * @param q Reference rational + * @param q_list Array of rationals terminated by `{0, 0}` + * @return Index of the nearest value found in the array + */ +int av_find_nearest_q_idx(AVRational q, const AVRational* q_list); + +/** + * Convert an AVRational to a IEEE 32-bit `float` expressed in fixed-point + * format. + * + * @param q Rational to be converted + * @return Equivalent floating-point value, expressed as an unsigned 32-bit + * integer. + * @note The returned value is platform-indepedant. + */ +uint32_t av_q2intfloat(AVRational q); + +/** + * @} + */ + +#endif /* AVUTIL_RATIONAL_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/rc4.h b/thrid-party/ffmpeg/include/libavutil/rc4.h new file mode 100644 index 0000000000..029cd2ad58 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/rc4.h @@ -0,0 +1,66 @@ +/* + * RC4 encryption/decryption/pseudo-random number generator + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_RC4_H +#define AVUTIL_RC4_H + +#include + +/** + * @defgroup lavu_rc4 RC4 + * @ingroup lavu_crypto + * @{ + */ + +typedef struct AVRC4 { + uint8_t state[256]; + int x, y; +} AVRC4; + +/** + * Allocate an AVRC4 context. + */ +AVRC4 *av_rc4_alloc(void); + +/** + * @brief Initializes an AVRC4 context. + * + * @param key_bits must be a multiple of 8 + * @param decrypt 0 for encryption, 1 for decryption, currently has no effect + * @return zero on success, negative value otherwise + */ +int av_rc4_init(struct AVRC4 *d, const uint8_t *key, int key_bits, int decrypt); + +/** + * @brief Encrypts / decrypts using the RC4 algorithm. + * + * @param count number of bytes + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst, may be NULL + * @param iv not (yet) used for RC4, should be NULL + * @param decrypt 0 for encryption, 1 for decryption, not (yet) used + */ +void av_rc4_crypt(struct AVRC4 *d, uint8_t *dst, const uint8_t *src, int count, uint8_t *iv, int decrypt); + +/** + * @} + */ + +#endif /* AVUTIL_RC4_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/replaygain.h b/thrid-party/ffmpeg/include/libavutil/replaygain.h new file mode 100644 index 0000000000..b49bf1a3d9 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/replaygain.h @@ -0,0 +1,50 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_REPLAYGAIN_H +#define AVUTIL_REPLAYGAIN_H + +#include + +/** + * ReplayGain information (see + * http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification). + * The size of this struct is a part of the public ABI. + */ +typedef struct AVReplayGain { + /** + * Track replay gain in microbels (divide by 100000 to get the value in dB). + * Should be set to INT32_MIN when unknown. + */ + int32_t track_gain; + /** + * Peak track amplitude, with 100000 representing full scale (but values + * may overflow). 0 when unknown. + */ + uint32_t track_peak; + /** + * Same as track_gain, but for the whole album. + */ + int32_t album_gain; + /** + * Same as track_peak, but for the whole album, + */ + uint32_t album_peak; +} AVReplayGain; + +#endif /* AVUTIL_REPLAYGAIN_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/ripemd.h b/thrid-party/ffmpeg/include/libavutil/ripemd.h new file mode 100644 index 0000000000..6d6bb3208f --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/ripemd.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 Michael Niedermayer + * Copyright (C) 2013 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_ripemd + * Public header for RIPEMD hash function implementation. + */ + +#ifndef AVUTIL_RIPEMD_H +#define AVUTIL_RIPEMD_H + +#include + +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_ripemd RIPEMD + * @ingroup lavu_hash + * RIPEMD hash function implementation. + * + * @{ + */ + +extern const int av_ripemd_size; + +struct AVRIPEMD; + +/** + * Allocate an AVRIPEMD context. + */ +struct AVRIPEMD *av_ripemd_alloc(void); + +/** + * Initialize RIPEMD hashing. + * + * @param context pointer to the function context (of size av_ripemd_size) + * @param bits number of bits in digest (128, 160, 256 or 320 bits) + * @return zero if initialization succeeded, -1 otherwise + */ +int av_ripemd_init(struct AVRIPEMD* context, int bits); + +/** + * Update hash value. + * + * @param context hash function context + * @param data input data to update hash with + * @param len input data length + */ +void av_ripemd_update(struct AVRIPEMD* context, const uint8_t* data, unsigned int len); + +/** + * Finish hashing and output digest value. + * + * @param context hash function context + * @param digest buffer where output digest value is stored + */ +void av_ripemd_final(struct AVRIPEMD* context, uint8_t *digest); + +/** + * @} + */ + +#endif /* AVUTIL_RIPEMD_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/samplefmt.h b/thrid-party/ffmpeg/include/libavutil/samplefmt.h new file mode 100644 index 0000000000..8cd43ae856 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/samplefmt.h @@ -0,0 +1,272 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_SAMPLEFMT_H +#define AVUTIL_SAMPLEFMT_H + +#include + +#include "avutil.h" +#include "attributes.h" + +/** + * @addtogroup lavu_audio + * @{ + * + * @defgroup lavu_sampfmts Audio sample formats + * + * Audio sample format enumeration and related convenience functions. + * @{ + */ + +/** + * Audio sample formats + * + * - The data described by the sample format is always in native-endian order. + * Sample values can be expressed by native C types, hence the lack of a signed + * 24-bit sample format even though it is a common raw audio data format. + * + * - The floating-point formats are based on full volume being in the range + * [-1.0, 1.0]. Any values outside this range are beyond full volume level. + * + * - The data layout as used in av_samples_fill_arrays() and elsewhere in FFmpeg + * (such as AVFrame in libavcodec) is as follows: + * + * @par + * For planar sample formats, each audio channel is in a separate data plane, + * and linesize is the buffer size, in bytes, for a single plane. All data + * planes must be the same size. For packed sample formats, only the first data + * plane is used, and samples for each channel are interleaved. In this case, + * linesize is the buffer size, in bytes, for the 1 plane. + * + */ +enum AVSampleFormat { + AV_SAMPLE_FMT_NONE = -1, + AV_SAMPLE_FMT_U8, ///< unsigned 8 bits + AV_SAMPLE_FMT_S16, ///< signed 16 bits + AV_SAMPLE_FMT_S32, ///< signed 32 bits + AV_SAMPLE_FMT_FLT, ///< float + AV_SAMPLE_FMT_DBL, ///< double + + AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar + AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar + AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar + AV_SAMPLE_FMT_FLTP, ///< float, planar + AV_SAMPLE_FMT_DBLP, ///< double, planar + AV_SAMPLE_FMT_S64, ///< signed 64 bits + AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar + + AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically +}; + +/** + * Return the name of sample_fmt, or NULL if sample_fmt is not + * recognized. + */ +const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt); + +/** + * Return a sample format corresponding to name, or AV_SAMPLE_FMT_NONE + * on error. + */ +enum AVSampleFormat av_get_sample_fmt(const char *name); + +/** + * Return the planar<->packed alternative form of the given sample format, or + * AV_SAMPLE_FMT_NONE on error. If the passed sample_fmt is already in the + * requested planar/packed format, the format returned is the same as the + * input. + */ +enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar); + +/** + * Get the packed alternative form of the given sample format. + * + * If the passed sample_fmt is already in packed format, the format returned is + * the same as the input. + * + * @return the packed alternative form of the given sample format or + AV_SAMPLE_FMT_NONE on error. + */ +enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt); + +/** + * Get the planar alternative form of the given sample format. + * + * If the passed sample_fmt is already in planar format, the format returned is + * the same as the input. + * + * @return the planar alternative form of the given sample format or + AV_SAMPLE_FMT_NONE on error. + */ +enum AVSampleFormat av_get_planar_sample_fmt(enum AVSampleFormat sample_fmt); + +/** + * Generate a string corresponding to the sample format with + * sample_fmt, or a header if sample_fmt is negative. + * + * @param buf the buffer where to write the string + * @param buf_size the size of buf + * @param sample_fmt the number of the sample format to print the + * corresponding info string, or a negative value to print the + * corresponding header. + * @return the pointer to the filled buffer or NULL if sample_fmt is + * unknown or in case of other errors + */ +char *av_get_sample_fmt_string(char *buf, int buf_size, enum AVSampleFormat sample_fmt); + +/** + * Return number of bytes per sample. + * + * @param sample_fmt the sample format + * @return number of bytes per sample or zero if unknown for the given + * sample format + */ +int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt); + +/** + * Check if the sample format is planar. + * + * @param sample_fmt the sample format to inspect + * @return 1 if the sample format is planar, 0 if it is interleaved + */ +int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt); + +/** + * Get the required buffer size for the given audio parameters. + * + * @param[out] linesize calculated linesize, may be NULL + * @param nb_channels the number of channels + * @param nb_samples the number of samples in a single channel + * @param sample_fmt the sample format + * @param align buffer size alignment (0 = default, 1 = no alignment) + * @return required buffer size, or negative error code on failure + */ +int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples, + enum AVSampleFormat sample_fmt, int align); + +/** + * @} + * + * @defgroup lavu_sampmanip Samples manipulation + * + * Functions that manipulate audio samples + * @{ + */ + +/** + * Fill plane data pointers and linesize for samples with sample + * format sample_fmt. + * + * The audio_data array is filled with the pointers to the samples data planes: + * for planar, set the start point of each channel's data within the buffer, + * for packed, set the start point of the entire buffer only. + * + * The value pointed to by linesize is set to the aligned size of each + * channel's data buffer for planar layout, or to the aligned size of the + * buffer for all channels for packed layout. + * + * The buffer in buf must be big enough to contain all the samples + * (use av_samples_get_buffer_size() to compute its minimum size), + * otherwise the audio_data pointers will point to invalid data. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param[out] audio_data array to be filled with the pointer for each channel + * @param[out] linesize calculated linesize, may be NULL + * @param buf the pointer to a buffer containing the samples + * @param nb_channels the number of channels + * @param nb_samples the number of samples in a single channel + * @param sample_fmt the sample format + * @param align buffer size alignment (0 = default, 1 = no alignment) + * @return >=0 on success or a negative error code on failure + * @todo return minimum size in bytes required for the buffer in case + * of success at the next bump + */ +int av_samples_fill_arrays(uint8_t **audio_data, int *linesize, + const uint8_t *buf, + int nb_channels, int nb_samples, + enum AVSampleFormat sample_fmt, int align); + +/** + * Allocate a samples buffer for nb_samples samples, and fill data pointers and + * linesize accordingly. + * The allocated samples buffer can be freed by using av_freep(&audio_data[0]) + * Allocated data will be initialized to silence. + * + * @see enum AVSampleFormat + * The documentation for AVSampleFormat describes the data layout. + * + * @param[out] audio_data array to be filled with the pointer for each channel + * @param[out] linesize aligned size for audio buffer(s), may be NULL + * @param nb_channels number of audio channels + * @param nb_samples number of samples per channel + * @param align buffer size alignment (0 = default, 1 = no alignment) + * @return >=0 on success or a negative error code on failure + * @todo return the size of the allocated buffer in case of success at the next bump + * @see av_samples_fill_arrays() + * @see av_samples_alloc_array_and_samples() + */ +int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels, + int nb_samples, enum AVSampleFormat sample_fmt, int align); + +/** + * Allocate a data pointers array, samples buffer for nb_samples + * samples, and fill data pointers and linesize accordingly. + * + * This is the same as av_samples_alloc(), but also allocates the data + * pointers array. + * + * @see av_samples_alloc() + */ +int av_samples_alloc_array_and_samples(uint8_t ***audio_data, int *linesize, int nb_channels, + int nb_samples, enum AVSampleFormat sample_fmt, int align); + +/** + * Copy samples from src to dst. + * + * @param dst destination array of pointers to data planes + * @param src source array of pointers to data planes + * @param dst_offset offset in samples at which the data will be written to dst + * @param src_offset offset in samples at which the data will be read from src + * @param nb_samples number of samples to be copied + * @param nb_channels number of audio channels + * @param sample_fmt audio sample format + */ +int av_samples_copy(uint8_t **dst, uint8_t * const *src, int dst_offset, + int src_offset, int nb_samples, int nb_channels, + enum AVSampleFormat sample_fmt); + +/** + * Fill an audio buffer with silence. + * + * @param audio_data array of pointers to data planes + * @param offset offset in samples at which to start filling + * @param nb_samples number of samples to fill + * @param nb_channels number of audio channels + * @param sample_fmt audio sample format + */ +int av_samples_set_silence(uint8_t **audio_data, int offset, int nb_samples, + int nb_channels, enum AVSampleFormat sample_fmt); + +/** + * @} + * @} + */ +#endif /* AVUTIL_SAMPLEFMT_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/sha.h b/thrid-party/ffmpeg/include/libavutil/sha.h new file mode 100644 index 0000000000..c0180e5729 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/sha.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_sha + * Public header for SHA-1 & SHA-256 hash function implementations. + */ + +#ifndef AVUTIL_SHA_H +#define AVUTIL_SHA_H + +#include +#include + +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_sha SHA + * @ingroup lavu_hash + * SHA-1 and SHA-256 (Secure Hash Algorithm) hash function implementations. + * + * This module supports the following SHA hash functions: + * + * - SHA-1: 160 bits + * - SHA-224: 224 bits, as a variant of SHA-2 + * - SHA-256: 256 bits, as a variant of SHA-2 + * + * @see For SHA-384, SHA-512, and variants thereof, see @ref lavu_sha512. + * + * @{ + */ + +extern const int av_sha_size; + +struct AVSHA; + +/** + * Allocate an AVSHA context. + */ +struct AVSHA *av_sha_alloc(void); + +/** + * Initialize SHA-1 or SHA-2 hashing. + * + * @param context pointer to the function context (of size av_sha_size) + * @param bits number of bits in digest (SHA-1 - 160 bits, SHA-2 224 or 256 bits) + * @return zero if initialization succeeded, -1 otherwise + */ +int av_sha_init(struct AVSHA* context, int bits); + +/** + * Update hash value. + * + * @param ctx hash function context + * @param data input data to update hash with + * @param len input data length + */ +#if FF_API_CRYPTO_SIZE_T +void av_sha_update(struct AVSHA *ctx, const uint8_t *data, unsigned int len); +#else +void av_sha_update(struct AVSHA *ctx, const uint8_t *data, size_t len); +#endif + +/** + * Finish hashing and output digest value. + * + * @param context hash function context + * @param digest buffer where output digest value is stored + */ +void av_sha_final(struct AVSHA* context, uint8_t *digest); + +/** + * @} + */ + +#endif /* AVUTIL_SHA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/sha512.h b/thrid-party/ffmpeg/include/libavutil/sha512.h new file mode 100644 index 0000000000..bef714b41c --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/sha512.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 Michael Niedermayer + * Copyright (C) 2013 James Almer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_sha512 + * Public header for SHA-512 implementation. + */ + +#ifndef AVUTIL_SHA512_H +#define AVUTIL_SHA512_H + +#include +#include + +#include "attributes.h" +#include "version.h" + +/** + * @defgroup lavu_sha512 SHA-512 + * @ingroup lavu_hash + * SHA-512 (Secure Hash Algorithm) hash function implementations. + * + * This module supports the following SHA-2 hash functions: + * + * - SHA-512/224: 224 bits + * - SHA-512/256: 256 bits + * - SHA-384: 384 bits + * - SHA-512: 512 bits + * + * @see For SHA-1, SHA-256, and variants thereof, see @ref lavu_sha. + * + * @{ + */ + +extern const int av_sha512_size; + +struct AVSHA512; + +/** + * Allocate an AVSHA512 context. + */ +struct AVSHA512 *av_sha512_alloc(void); + +/** + * Initialize SHA-2 512 hashing. + * + * @param context pointer to the function context (of size av_sha512_size) + * @param bits number of bits in digest (224, 256, 384 or 512 bits) + * @return zero if initialization succeeded, -1 otherwise + */ +int av_sha512_init(struct AVSHA512* context, int bits); + +/** + * Update hash value. + * + * @param context hash function context + * @param data input data to update hash with + * @param len input data length + */ +#if FF_API_CRYPTO_SIZE_T +void av_sha512_update(struct AVSHA512* context, const uint8_t* data, unsigned int len); +#else +void av_sha512_update(struct AVSHA512* context, const uint8_t* data, size_t len); +#endif + +/** + * Finish hashing and output digest value. + * + * @param context hash function context + * @param digest buffer where output digest value is stored + */ +void av_sha512_final(struct AVSHA512* context, uint8_t *digest); + +/** + * @} + */ + +#endif /* AVUTIL_SHA512_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/spherical.h b/thrid-party/ffmpeg/include/libavutil/spherical.h new file mode 100644 index 0000000000..cef759cf27 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/spherical.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2016 Vittorio Giovara + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Spherical video + */ + +#ifndef AVUTIL_SPHERICAL_H +#define AVUTIL_SPHERICAL_H + +#include +#include + +/** + * @addtogroup lavu_video + * @{ + * + * @defgroup lavu_video_spherical Spherical video mapping + * @{ + */ + +/** + * @addtogroup lavu_video_spherical + * A spherical video file contains surfaces that need to be mapped onto a + * sphere. Depending on how the frame was converted, a different distortion + * transformation or surface recomposition function needs to be applied before + * the video should be mapped and displayed. + */ + +/** + * Projection of the video surface(s) on a sphere. + */ +enum AVSphericalProjection { + /** + * Video represents a sphere mapped on a flat surface using + * equirectangular projection. + */ + AV_SPHERICAL_EQUIRECTANGULAR, + + /** + * Video frame is split into 6 faces of a cube, and arranged on a + * 3x2 layout. Faces are oriented upwards for the front, left, right, + * and back faces. The up face is oriented so the top of the face is + * forwards and the down face is oriented so the top of the face is + * to the back. + */ + AV_SPHERICAL_CUBEMAP, + + /** + * Video represents a portion of a sphere mapped on a flat surface + * using equirectangular projection. The @ref bounding fields indicate + * the position of the current video in a larger surface. + */ + AV_SPHERICAL_EQUIRECTANGULAR_TILE, +}; + +/** + * This structure describes how to handle spherical videos, outlining + * information about projection, initial layout, and any other view modifier. + * + * @note The struct must be allocated with av_spherical_alloc() and + * its size is not a part of the public ABI. + */ +typedef struct AVSphericalMapping { + /** + * Projection type. + */ + enum AVSphericalProjection projection; + + /** + * @name Initial orientation + * @{ + * There fields describe additional rotations applied to the sphere after + * the video frame is mapped onto it. The sphere is rotated around the + * viewer, who remains stationary. The order of transformation is always + * yaw, followed by pitch, and finally by roll. + * + * The coordinate system matches the one defined in OpenGL, where the + * forward vector (z) is coming out of screen, and it is equivalent to + * a rotation matrix of R = r_y(yaw) * r_x(pitch) * r_z(roll). + * + * A positive yaw rotates the portion of the sphere in front of the viewer + * toward their right. A positive pitch rotates the portion of the sphere + * in front of the viewer upwards. A positive roll tilts the portion of + * the sphere in front of the viewer to the viewer's right. + * + * These values are exported as 16.16 fixed point. + * + * See this equirectangular projection as example: + * + * @code{.unparsed} + * Yaw + * -180 0 180 + * 90 +-------------+-------------+ 180 + * | | | up + * P | | | y| forward + * i | ^ | | /z + * t 0 +-------------X-------------+ 0 Roll | / + * c | | | | / + * h | | | 0|/_____right + * | | | x + * -90 +-------------+-------------+ -180 + * + * X - the default camera center + * ^ - the default up vector + * @endcode + */ + int32_t yaw; ///< Rotation around the up vector [-180, 180]. + int32_t pitch; ///< Rotation around the right vector [-90, 90]. + int32_t roll; ///< Rotation around the forward vector [-180, 180]. + /** + * @} + */ + + /** + * @name Bounding rectangle + * @anchor bounding + * @{ + * These fields indicate the location of the current tile, and where + * it should be mapped relative to the original surface. They are + * exported as 0.32 fixed point, and can be converted to classic + * pixel values with av_spherical_bounds(). + * + * @code{.unparsed} + * +----------------+----------+ + * | |bound_top | + * | +--------+ | + * | bound_left |tile | | + * +<---------->| |<--->+bound_right + * | +--------+ | + * | | | + * | bound_bottom| | + * +----------------+----------+ + * @endcode + * + * If needed, the original video surface dimensions can be derived + * by adding the current stream or frame size to the related bounds, + * like in the following example: + * + * @code{c} + * original_width = tile->width + bound_left + bound_right; + * original_height = tile->height + bound_top + bound_bottom; + * @endcode + * + * @note These values are valid only for the tiled equirectangular + * projection type (@ref AV_SPHERICAL_EQUIRECTANGULAR_TILE), + * and should be ignored in all other cases. + */ + uint32_t bound_left; ///< Distance from the left edge + uint32_t bound_top; ///< Distance from the top edge + uint32_t bound_right; ///< Distance from the right edge + uint32_t bound_bottom; ///< Distance from the bottom edge + /** + * @} + */ + + /** + * Number of pixels to pad from the edge of each cube face. + * + * @note This value is valid for only for the cubemap projection type + * (@ref AV_SPHERICAL_CUBEMAP), and should be ignored in all other + * cases. + */ + uint32_t padding; +} AVSphericalMapping; + +/** + * Allocate a AVSphericalVideo structure and initialize its fields to default + * values. + * + * @return the newly allocated struct or NULL on failure + */ +AVSphericalMapping *av_spherical_alloc(size_t *size); + +/** + * Convert the @ref bounding fields from an AVSphericalVideo + * from 0.32 fixed point to pixels. + * + * @param map The AVSphericalVideo map to read bound values from. + * @param width Width of the current frame or stream. + * @param height Height of the current frame or stream. + * @param left Pixels from the left edge. + * @param top Pixels from the top edge. + * @param right Pixels from the right edge. + * @param bottom Pixels from the bottom edge. + */ +void av_spherical_tile_bounds(const AVSphericalMapping *map, + size_t width, size_t height, + size_t *left, size_t *top, + size_t *right, size_t *bottom); + +/** + * Provide a human-readable name of a given AVSphericalProjection. + * + * @param projection The input AVSphericalProjection. + * + * @return The name of the AVSphericalProjection, or "unknown". + */ +const char *av_spherical_projection_name(enum AVSphericalProjection projection); + +/** + * Get the AVSphericalProjection form a human-readable name. + * + * @param name The input string. + * + * @return The AVSphericalProjection value, or -1 if not found. + */ +int av_spherical_from_name(const char *name); +/** + * @} + * @} + */ + +#endif /* AVUTIL_SPHERICAL_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/stereo3d.h b/thrid-party/ffmpeg/include/libavutil/stereo3d.h new file mode 100644 index 0000000000..54f4c4c5c7 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/stereo3d.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2013 Vittorio Giovara + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Stereoscopic video + */ + +#ifndef AVUTIL_STEREO3D_H +#define AVUTIL_STEREO3D_H + +#include + +#include "frame.h" + +/** + * @addtogroup lavu_video + * @{ + * + * @defgroup lavu_video_stereo3d Stereo3D types and functions + * @{ + */ + +/** + * @addtogroup lavu_video_stereo3d + * A stereoscopic video file consists in multiple views embedded in a single + * frame, usually describing two views of a scene. This file describes all + * possible codec-independent view arrangements. + * */ + +/** + * List of possible 3D Types + */ +enum AVStereo3DType { + /** + * Video is not stereoscopic (and metadata has to be there). + */ + AV_STEREO3D_2D, + + /** + * Views are next to each other. + * + * @code{.unparsed} + * LLLLRRRR + * LLLLRRRR + * LLLLRRRR + * ... + * @endcode + */ + AV_STEREO3D_SIDEBYSIDE, + + /** + * Views are on top of each other. + * + * @code{.unparsed} + * LLLLLLLL + * LLLLLLLL + * RRRRRRRR + * RRRRRRRR + * @endcode + */ + AV_STEREO3D_TOPBOTTOM, + + /** + * Views are alternated temporally. + * + * @code{.unparsed} + * frame0 frame1 frame2 ... + * LLLLLLLL RRRRRRRR LLLLLLLL + * LLLLLLLL RRRRRRRR LLLLLLLL + * LLLLLLLL RRRRRRRR LLLLLLLL + * ... ... ... + * @endcode + */ + AV_STEREO3D_FRAMESEQUENCE, + + /** + * Views are packed in a checkerboard-like structure per pixel. + * + * @code{.unparsed} + * LRLRLRLR + * RLRLRLRL + * LRLRLRLR + * ... + * @endcode + */ + AV_STEREO3D_CHECKERBOARD, + + /** + * Views are next to each other, but when upscaling + * apply a checkerboard pattern. + * + * @code{.unparsed} + * LLLLRRRR L L L L R R R R + * LLLLRRRR => L L L L R R R R + * LLLLRRRR L L L L R R R R + * LLLLRRRR L L L L R R R R + * @endcode + */ + AV_STEREO3D_SIDEBYSIDE_QUINCUNX, + + /** + * Views are packed per line, as if interlaced. + * + * @code{.unparsed} + * LLLLLLLL + * RRRRRRRR + * LLLLLLLL + * ... + * @endcode + */ + AV_STEREO3D_LINES, + + /** + * Views are packed per column. + * + * @code{.unparsed} + * LRLRLRLR + * LRLRLRLR + * LRLRLRLR + * ... + * @endcode + */ + AV_STEREO3D_COLUMNS, +}; + + +/** + * Inverted views, Right/Bottom represents the left view. + */ +#define AV_STEREO3D_FLAG_INVERT (1 << 0) + +/** + * Stereo 3D type: this structure describes how two videos are packed + * within a single video surface, with additional information as needed. + * + * @note The struct must be allocated with av_stereo3d_alloc() and + * its size is not a part of the public ABI. + */ +typedef struct AVStereo3D { + /** + * How views are packed within the video. + */ + enum AVStereo3DType type; + + /** + * Additional information about the frame packing. + */ + int flags; +} AVStereo3D; + +/** + * Allocate an AVStereo3D structure and set its fields to default values. + * The resulting struct can be freed using av_freep(). + * + * @return An AVStereo3D filled with default values or NULL on failure. + */ +AVStereo3D *av_stereo3d_alloc(void); + +/** + * Allocate a complete AVFrameSideData and add it to the frame. + * + * @param frame The frame which side data is added to. + * + * @return The AVStereo3D structure to be filled by caller. + */ +AVStereo3D *av_stereo3d_create_side_data(AVFrame *frame); + +/** + * Provide a human-readable name of a given stereo3d type. + * + * @param type The input stereo3d type value. + * + * @return The name of the stereo3d value, or "unknown". + */ +const char *av_stereo3d_type_name(unsigned int type); + +/** + * Get the AVStereo3DType form a human-readable name. + * + * @param name The input string. + * + * @return The AVStereo3DType value, or -1 if not found. + */ +int av_stereo3d_from_name(const char *name); + +/** + * @} + * @} + */ + +#endif /* AVUTIL_STEREO3D_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/tea.h b/thrid-party/ffmpeg/include/libavutil/tea.h new file mode 100644 index 0000000000..dd929bdafd --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/tea.h @@ -0,0 +1,71 @@ +/* + * A 32-bit implementation of the TEA algorithm + * Copyright (c) 2015 Vesselin Bontchev + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_TEA_H +#define AVUTIL_TEA_H + +#include + +/** + * @file + * @brief Public header for libavutil TEA algorithm + * @defgroup lavu_tea TEA + * @ingroup lavu_crypto + * @{ + */ + +extern const int av_tea_size; + +struct AVTEA; + +/** + * Allocate an AVTEA context + * To free the struct: av_free(ptr) + */ +struct AVTEA *av_tea_alloc(void); + +/** + * Initialize an AVTEA context. + * + * @param ctx an AVTEA context + * @param key a key of 16 bytes used for encryption/decryption + * @param rounds the number of rounds in TEA (64 is the "standard") + */ +void av_tea_init(struct AVTEA *ctx, const uint8_t key[16], int rounds); + +/** + * Encrypt or decrypt a buffer using a previously initialized context. + * + * @param ctx an AVTEA context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param iv initialization vector for CBC mode, if NULL then ECB will be used + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_tea_crypt(struct AVTEA *ctx, uint8_t *dst, const uint8_t *src, + int count, uint8_t *iv, int decrypt); + +/** + * @} + */ + +#endif /* AVUTIL_TEA_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/threadmessage.h b/thrid-party/ffmpeg/include/libavutil/threadmessage.h new file mode 100644 index 0000000000..8480a0a3db --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/threadmessage.h @@ -0,0 +1,107 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_THREADMESSAGE_H +#define AVUTIL_THREADMESSAGE_H + +typedef struct AVThreadMessageQueue AVThreadMessageQueue; + +typedef enum AVThreadMessageFlags { + + /** + * Perform non-blocking operation. + * If this flag is set, send and recv operations are non-blocking and + * return AVERROR(EAGAIN) immediately if they can not proceed. + */ + AV_THREAD_MESSAGE_NONBLOCK = 1, + +} AVThreadMessageFlags; + +/** + * Allocate a new message queue. + * + * @param mq pointer to the message queue + * @param nelem maximum number of elements in the queue + * @param elsize size of each element in the queue + * @return >=0 for success; <0 for error, in particular AVERROR(ENOSYS) if + * lavu was built without thread support + */ +int av_thread_message_queue_alloc(AVThreadMessageQueue **mq, + unsigned nelem, + unsigned elsize); + +/** + * Free a message queue. + * + * The message queue must no longer be in use by another thread. + */ +void av_thread_message_queue_free(AVThreadMessageQueue **mq); + +/** + * Send a message on the queue. + */ +int av_thread_message_queue_send(AVThreadMessageQueue *mq, + void *msg, + unsigned flags); + +/** + * Receive a message from the queue. + */ +int av_thread_message_queue_recv(AVThreadMessageQueue *mq, + void *msg, + unsigned flags); + +/** + * Set the sending error code. + * + * If the error code is set to non-zero, av_thread_message_queue_send() will + * return it immediately. Conventional values, such as AVERROR_EOF or + * AVERROR(EAGAIN), can be used to cause the sending thread to stop or + * suspend its operation. + */ +void av_thread_message_queue_set_err_send(AVThreadMessageQueue *mq, + int err); + +/** + * Set the receiving error code. + * + * If the error code is set to non-zero, av_thread_message_queue_recv() will + * return it immediately when there are no longer available messages. + * Conventional values, such as AVERROR_EOF or AVERROR(EAGAIN), can be used + * to cause the receiving thread to stop or suspend its operation. + */ +void av_thread_message_queue_set_err_recv(AVThreadMessageQueue *mq, + int err); + +/** + * Set the optional free message callback function which will be called if an + * operation is removing messages from the queue. + */ +void av_thread_message_queue_set_free_func(AVThreadMessageQueue *mq, + void (*free_func)(void *msg)); + +/** + * Flush the message queue + * + * This function is mostly equivalent to reading and free-ing every message + * except that it will be done in a single operation (no lock/unlock between + * reads). + */ +void av_thread_message_flush(AVThreadMessageQueue *mq); + +#endif /* AVUTIL_THREADMESSAGE_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/time.h b/thrid-party/ffmpeg/include/libavutil/time.h new file mode 100644 index 0000000000..dc169b064a --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/time.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000-2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_TIME_H +#define AVUTIL_TIME_H + +#include + +/** + * Get the current time in microseconds. + */ +int64_t av_gettime(void); + +/** + * Get the current time in microseconds since some unspecified starting point. + * On platforms that support it, the time comes from a monotonic clock + * This property makes this time source ideal for measuring relative time. + * The returned values may not be monotonic on platforms where a monotonic + * clock is not available. + */ +int64_t av_gettime_relative(void); + +/** + * Indicates with a boolean result if the av_gettime_relative() time source + * is monotonic. + */ +int av_gettime_relative_is_monotonic(void); + +/** + * Sleep for a period of time. Although the duration is expressed in + * microseconds, the actual delay may be rounded to the precision of the + * system timer. + * + * @param usec Number of microseconds to sleep. + * @return zero on success or (negative) error code. + */ +int av_usleep(unsigned usec); + +#endif /* AVUTIL_TIME_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/timecode.h b/thrid-party/ffmpeg/include/libavutil/timecode.h new file mode 100644 index 0000000000..37c1361bc2 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/timecode.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2006 Smartjog S.A.S, Baptiste Coudurier + * Copyright (c) 2011-2012 Smartjog S.A.S, Clément Bœsch + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Timecode helpers header + */ + +#ifndef AVUTIL_TIMECODE_H +#define AVUTIL_TIMECODE_H + +#include +#include "rational.h" + +#define AV_TIMECODE_STR_SIZE 23 + +enum AVTimecodeFlag { + AV_TIMECODE_FLAG_DROPFRAME = 1<<0, ///< timecode is drop frame + AV_TIMECODE_FLAG_24HOURSMAX = 1<<1, ///< timecode wraps after 24 hours + AV_TIMECODE_FLAG_ALLOWNEGATIVE = 1<<2, ///< negative time values are allowed +}; + +typedef struct { + int start; ///< timecode frame start (first base frame number) + uint32_t flags; ///< flags such as drop frame, +24 hours support, ... + AVRational rate; ///< frame rate in rational form + unsigned fps; ///< frame per second; must be consistent with the rate field +} AVTimecode; + +/** + * Adjust frame number for NTSC drop frame time code. + * + * @param framenum frame number to adjust + * @param fps frame per second, 30 or 60 + * @return adjusted frame number + * @warning adjustment is only valid in NTSC 29.97 and 59.94 + */ +int av_timecode_adjust_ntsc_framenum2(int framenum, int fps); + +/** + * Convert frame number to SMPTE 12M binary representation. + * + * @param tc timecode data correctly initialized + * @param framenum frame number + * @return the SMPTE binary representation + * + * @note Frame number adjustment is automatically done in case of drop timecode, + * you do NOT have to call av_timecode_adjust_ntsc_framenum2(). + * @note The frame number is relative to tc->start. + * @note Color frame (CF), binary group flags (BGF) and biphase mark polarity + * correction (PC) bits are set to zero. + */ +uint32_t av_timecode_get_smpte_from_framenum(const AVTimecode *tc, int framenum); + +/** + * Load timecode string in buf. + * + * @param buf destination buffer, must be at least AV_TIMECODE_STR_SIZE long + * @param tc timecode data correctly initialized + * @param framenum frame number + * @return the buf parameter + * + * @note Timecode representation can be a negative timecode and have more than + * 24 hours, but will only be honored if the flags are correctly set. + * @note The frame number is relative to tc->start. + */ +char *av_timecode_make_string(const AVTimecode *tc, char *buf, int framenum); + +/** + * Get the timecode string from the SMPTE timecode format. + * + * @param buf destination buffer, must be at least AV_TIMECODE_STR_SIZE long + * @param tcsmpte the 32-bit SMPTE timecode + * @param prevent_df prevent the use of a drop flag when it is known the DF bit + * is arbitrary + * @return the buf parameter + */ +char *av_timecode_make_smpte_tc_string(char *buf, uint32_t tcsmpte, int prevent_df); + +/** + * Get the timecode string from the 25-bit timecode format (MPEG GOP format). + * + * @param buf destination buffer, must be at least AV_TIMECODE_STR_SIZE long + * @param tc25bit the 25-bits timecode + * @return the buf parameter + */ +char *av_timecode_make_mpeg_tc_string(char *buf, uint32_t tc25bit); + +/** + * Init a timecode struct with the passed parameters. + * + * @param log_ctx a pointer to an arbitrary struct of which the first field + * is a pointer to an AVClass struct (used for av_log) + * @param tc pointer to an allocated AVTimecode + * @param rate frame rate in rational form + * @param flags miscellaneous flags such as drop frame, +24 hours, ... + * (see AVTimecodeFlag) + * @param frame_start the first frame number + * @return 0 on success, AVERROR otherwise + */ +int av_timecode_init(AVTimecode *tc, AVRational rate, int flags, int frame_start, void *log_ctx); + +/** + * Parse timecode representation (hh:mm:ss[:;.]ff). + * + * @param log_ctx a pointer to an arbitrary struct of which the first field is a + * pointer to an AVClass struct (used for av_log). + * @param tc pointer to an allocated AVTimecode + * @param rate frame rate in rational form + * @param str timecode string which will determine the frame start + * @return 0 on success, AVERROR otherwise + */ +int av_timecode_init_from_string(AVTimecode *tc, AVRational rate, const char *str, void *log_ctx); + +/** + * Check if the timecode feature is available for the given frame rate + * + * @return 0 if supported, <0 otherwise + */ +int av_timecode_check_frame_rate(AVRational rate); + +#endif /* AVUTIL_TIMECODE_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/timestamp.h b/thrid-party/ffmpeg/include/libavutil/timestamp.h new file mode 100644 index 0000000000..e082f01b40 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/timestamp.h @@ -0,0 +1,78 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * timestamp utils, mostly useful for debugging/logging purposes + */ + +#ifndef AVUTIL_TIMESTAMP_H +#define AVUTIL_TIMESTAMP_H + +#include "common.h" + +#if defined(__cplusplus) && !defined(__STDC_FORMAT_MACROS) && !defined(PRId64) +#error missing -D__STDC_FORMAT_MACROS / #define __STDC_FORMAT_MACROS +#endif + +#define AV_TS_MAX_STRING_SIZE 32 + +/** + * Fill the provided buffer with a string containing a timestamp + * representation. + * + * @param buf a buffer with size in bytes of at least AV_TS_MAX_STRING_SIZE + * @param ts the timestamp to represent + * @return the buffer in input + */ +static inline char *av_ts_make_string(char *buf, int64_t ts) +{ + if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS"); + else snprintf(buf, AV_TS_MAX_STRING_SIZE, "%" PRId64, ts); + return buf; +} + +/** + * Convenience macro, the return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_ts2str(ts) av_ts_make_string((char[AV_TS_MAX_STRING_SIZE]){0}, ts) + +/** + * Fill the provided buffer with a string containing a timestamp time + * representation. + * + * @param buf a buffer with size in bytes of at least AV_TS_MAX_STRING_SIZE + * @param ts the timestamp to represent + * @param tb the timebase of the timestamp + * @return the buffer in input + */ +static inline char *av_ts_make_time_string(char *buf, int64_t ts, AVRational *tb) +{ + if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS"); + else snprintf(buf, AV_TS_MAX_STRING_SIZE, "%.6g", av_q2d(*tb) * ts); + return buf; +} + +/** + * Convenience macro, the return value should be used only directly in + * function arguments but never stand-alone. + */ +#define av_ts2timestr(ts, tb) av_ts_make_time_string((char[AV_TS_MAX_STRING_SIZE]){0}, ts, tb) + +#endif /* AVUTIL_TIMESTAMP_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/tree.h b/thrid-party/ffmpeg/include/libavutil/tree.h new file mode 100644 index 0000000000..d5e0aebfbd --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/tree.h @@ -0,0 +1,138 @@ +/* + * copyright (c) 2006 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * A tree container. + * @author Michael Niedermayer + */ + +#ifndef AVUTIL_TREE_H +#define AVUTIL_TREE_H + +#include "attributes.h" +#include "version.h" + +/** + * @addtogroup lavu_tree AVTree + * @ingroup lavu_data + * + * Low-complexity tree container + * + * Insertion, removal, finding equal, largest which is smaller than and + * smallest which is larger than, all have O(log n) worst-case complexity. + * @{ + */ + + +struct AVTreeNode; +extern const int av_tree_node_size; + +/** + * Allocate an AVTreeNode. + */ +struct AVTreeNode *av_tree_node_alloc(void); + +/** + * Find an element. + * @param root a pointer to the root node of the tree + * @param next If next is not NULL, then next[0] will contain the previous + * element and next[1] the next element. If either does not exist, + * then the corresponding entry in next is unchanged. + * @param cmp compare function used to compare elements in the tree, + * API identical to that of Standard C's qsort + * It is guaranteed that the first and only the first argument to cmp() + * will be the key parameter to av_tree_find(), thus it could if the + * user wants, be a different type (like an opaque context). + * @return An element with cmp(key, elem) == 0 or NULL if no such element + * exists in the tree. + */ +void *av_tree_find(const struct AVTreeNode *root, void *key, + int (*cmp)(const void *key, const void *b), void *next[2]); + +/** + * Insert or remove an element. + * + * If *next is NULL, then the supplied element will be removed if it exists. + * If *next is non-NULL, then the supplied element will be inserted, unless + * it already exists in the tree. + * + * @param rootp A pointer to a pointer to the root node of the tree; note that + * the root node can change during insertions, this is required + * to keep the tree balanced. + * @param key pointer to the element key to insert in the tree + * @param next Used to allocate and free AVTreeNodes. For insertion the user + * must set it to an allocated and zeroed object of at least + * av_tree_node_size bytes size. av_tree_insert() will set it to + * NULL if it has been consumed. + * For deleting elements *next is set to NULL by the user and + * av_tree_insert() will set it to the AVTreeNode which was + * used for the removed element. + * This allows the use of flat arrays, which have + * lower overhead compared to many malloced elements. + * You might want to define a function like: + * @code + * void *tree_insert(struct AVTreeNode **rootp, void *key, + * int (*cmp)(void *key, const void *b), + * AVTreeNode **next) + * { + * if (!*next) + * *next = av_mallocz(av_tree_node_size); + * return av_tree_insert(rootp, key, cmp, next); + * } + * void *tree_remove(struct AVTreeNode **rootp, void *key, + * int (*cmp)(void *key, const void *b, AVTreeNode **next)) + * { + * av_freep(next); + * return av_tree_insert(rootp, key, cmp, next); + * } + * @endcode + * @param cmp compare function used to compare elements in the tree, API identical + * to that of Standard C's qsort + * @return If no insertion happened, the found element; if an insertion or + * removal happened, then either key or NULL will be returned. + * Which one it is depends on the tree state and the implementation. You + * should make no assumptions that it's one or the other in the code. + */ +void *av_tree_insert(struct AVTreeNode **rootp, void *key, + int (*cmp)(const void *key, const void *b), + struct AVTreeNode **next); + +void av_tree_destroy(struct AVTreeNode *t); + +/** + * Apply enu(opaque, &elem) to all the elements in the tree in a given range. + * + * @param cmp a comparison function that returns < 0 for an element below the + * range, > 0 for an element above the range and == 0 for an + * element inside the range + * + * @note The cmp function should use the same ordering used to construct the + * tree. + */ +void av_tree_enumerate(struct AVTreeNode *t, void *opaque, + int (*cmp)(void *opaque, void *elem), + int (*enu)(void *opaque, void *elem)); + +/** + * @} + */ + +#endif /* AVUTIL_TREE_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/twofish.h b/thrid-party/ffmpeg/include/libavutil/twofish.h new file mode 100644 index 0000000000..813cfecdf8 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/twofish.h @@ -0,0 +1,70 @@ +/* + * An implementation of the TwoFish algorithm + * Copyright (c) 2015 Supraja Meedinti + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_TWOFISH_H +#define AVUTIL_TWOFISH_H + +#include + + +/** + * @file + * @brief Public header for libavutil TWOFISH algorithm + * @defgroup lavu_twofish TWOFISH + * @ingroup lavu_crypto + * @{ + */ + +extern const int av_twofish_size; + +struct AVTWOFISH; + +/** + * Allocate an AVTWOFISH context + * To free the struct: av_free(ptr) + */ +struct AVTWOFISH *av_twofish_alloc(void); + +/** + * Initialize an AVTWOFISH context. + * + * @param ctx an AVTWOFISH context + * @param key a key of size ranging from 1 to 32 bytes used for encryption/decryption + * @param key_bits number of keybits: 128, 192, 256 If less than the required, padded with zeroes to nearest valid value; return value is 0 if key_bits is 128/192/256, -1 if less than 0, 1 otherwise + */ +int av_twofish_init(struct AVTWOFISH *ctx, const uint8_t *key, int key_bits); + +/** + * Encrypt or decrypt a buffer using a previously initialized context + * + * @param ctx an AVTWOFISH context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 16 byte blocks + * @paran iv initialization vector for CBC mode, NULL for ECB mode + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_twofish_crypt(struct AVTWOFISH *ctx, uint8_t *dst, const uint8_t *src, int count, uint8_t* iv, int decrypt); + +/** + * @} + */ +#endif /* AVUTIL_TWOFISH_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/version.h b/thrid-party/ffmpeg/include/libavutil/version.h new file mode 100644 index 0000000000..f594dc0691 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/version.h @@ -0,0 +1,149 @@ +/* + * copyright (c) 2003 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu + * Libavutil version macros + */ + +#ifndef AVUTIL_VERSION_H +#define AVUTIL_VERSION_H + +#include "macros.h" + +/** + * @addtogroup version_utils + * + * Useful to check and match library version in order to maintain + * backward compatibility. + * + * The FFmpeg libraries follow a versioning sheme very similar to + * Semantic Versioning (http://semver.org/) + * The difference is that the component called PATCH is called MICRO in FFmpeg + * and its value is reset to 100 instead of 0 to keep it above or equal to 100. + * Also we do not increase MICRO for every bugfix or change in git master. + * + * Prior to FFmpeg 3.2 point releases did not change any lib version number to + * avoid aliassing different git master checkouts. + * Starting with FFmpeg 3.2, the released library versions will occupy + * a separate MAJOR.MINOR that is not used on the master development branch. + * That is if we branch a release of master 55.10.123 we will bump to 55.11.100 + * for the release and master will continue at 55.12.100 after it. Each new + * point release will then bump the MICRO improving the usefulness of the lib + * versions. + * + * @{ + */ + +#define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c)) +#define AV_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define AV_VERSION(a, b, c) AV_VERSION_DOT(a, b, c) + +/** + * Extract version components from the full ::AV_VERSION_INT int as returned + * by functions like ::avformat_version() and ::avcodec_version() + */ +#define AV_VERSION_MAJOR(a) ((a) >> 16) +#define AV_VERSION_MINOR(a) (((a) & 0x00FF00) >> 8) +#define AV_VERSION_MICRO(a) ((a) & 0xFF) + +/** + * @} + */ + +/** + * @defgroup lavu_ver Version and Build diagnostics + * + * Macros and function useful to check at compiletime and at runtime + * which version of libavutil is in use. + * + * @{ + */ + + +#define LIBAVUTIL_VERSION_MAJOR 55 +#define LIBAVUTIL_VERSION_MINOR 78 +#define LIBAVUTIL_VERSION_MICRO 100 + +#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, \ + LIBAVUTIL_VERSION_MICRO) +#define LIBAVUTIL_VERSION AV_VERSION(LIBAVUTIL_VERSION_MAJOR, \ + LIBAVUTIL_VERSION_MINOR, \ + LIBAVUTIL_VERSION_MICRO) +#define LIBAVUTIL_BUILD LIBAVUTIL_VERSION_INT + +#define LIBAVUTIL_IDENT "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION) + +/** + * @defgroup lavu_depr_guards Deprecation Guards + * FF_API_* defines may be placed below to indicate public API that will be + * dropped at a future version bump. The defines themselves are not part of + * the public API and may change, break or disappear at any time. + * + * @note, when bumping the major version it is recommended to manually + * disable each FF_API_* in its own commit instead of disabling them all + * at once through the bump. This improves the git bisect-ability of the change. + * + * @{ + */ + +#ifndef FF_API_VDPAU +#define FF_API_VDPAU (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_XVMC +#define FF_API_XVMC (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_OPT_TYPE_METADATA +#define FF_API_OPT_TYPE_METADATA (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_DLOG +#define FF_API_DLOG (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_VAAPI +#define FF_API_VAAPI (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_FRAME_QP +#define FF_API_FRAME_QP (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_PLUS1_MINUS1 +#define FF_API_PLUS1_MINUS1 (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_ERROR_FRAME +#define FF_API_ERROR_FRAME (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_CRC_BIG_TABLE +#define FF_API_CRC_BIG_TABLE (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_PKT_PTS +#define FF_API_PKT_PTS (LIBAVUTIL_VERSION_MAJOR < 56) +#endif +#ifndef FF_API_CRYPTO_SIZE_T +#define FF_API_CRYPTO_SIZE_T (LIBAVUTIL_VERSION_MAJOR < 56) +#endif + + +/** + * @} + * @} + */ + +#endif /* AVUTIL_VERSION_H */ diff --git a/thrid-party/ffmpeg/include/libavutil/xtea.h b/thrid-party/ffmpeg/include/libavutil/xtea.h new file mode 100644 index 0000000000..735427c109 --- /dev/null +++ b/thrid-party/ffmpeg/include/libavutil/xtea.h @@ -0,0 +1,94 @@ +/* + * A 32-bit implementation of the XTEA algorithm + * Copyright (c) 2012 Samuel Pitoiset + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_XTEA_H +#define AVUTIL_XTEA_H + +#include + +/** + * @file + * @brief Public header for libavutil XTEA algorithm + * @defgroup lavu_xtea XTEA + * @ingroup lavu_crypto + * @{ + */ + +typedef struct AVXTEA { + uint32_t key[16]; +} AVXTEA; + +/** + * Allocate an AVXTEA context. + */ +AVXTEA *av_xtea_alloc(void); + +/** + * Initialize an AVXTEA context. + * + * @param ctx an AVXTEA context + * @param key a key of 16 bytes used for encryption/decryption, + * interpreted as big endian 32 bit numbers + */ +void av_xtea_init(struct AVXTEA *ctx, const uint8_t key[16]); + +/** + * Initialize an AVXTEA context. + * + * @param ctx an AVXTEA context + * @param key a key of 16 bytes used for encryption/decryption, + * interpreted as little endian 32 bit numbers + */ +void av_xtea_le_init(struct AVXTEA *ctx, const uint8_t key[16]); + +/** + * Encrypt or decrypt a buffer using a previously initialized context, + * in big endian format. + * + * @param ctx an AVXTEA context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param iv initialization vector for CBC mode, if NULL then ECB will be used + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_xtea_crypt(struct AVXTEA *ctx, uint8_t *dst, const uint8_t *src, + int count, uint8_t *iv, int decrypt); + +/** + * Encrypt or decrypt a buffer using a previously initialized context, + * in little endian format. + * + * @param ctx an AVXTEA context + * @param dst destination array, can be equal to src + * @param src source array, can be equal to dst + * @param count number of 8 byte blocks + * @param iv initialization vector for CBC mode, if NULL then ECB will be used + * @param decrypt 0 for encryption, 1 for decryption + */ +void av_xtea_le_crypt(struct AVXTEA *ctx, uint8_t *dst, const uint8_t *src, + int count, uint8_t *iv, int decrypt); + +/** + * @} + */ + +#endif /* AVUTIL_XTEA_H */ diff --git a/thrid-party/ffmpeg/include/libswresample/swresample.h b/thrid-party/ffmpeg/include/libswresample/swresample.h new file mode 100644 index 0000000000..c7b84fbcac --- /dev/null +++ b/thrid-party/ffmpeg/include/libswresample/swresample.h @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2011-2013 Michael Niedermayer (michaelni@gmx.at) + * + * This file is part of libswresample + * + * libswresample is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libswresample is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with libswresample; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SWRESAMPLE_SWRESAMPLE_H +#define SWRESAMPLE_SWRESAMPLE_H + +/** + * @file + * @ingroup lswr + * libswresample public header + */ + +/** + * @defgroup lswr libswresample + * @{ + * + * Audio resampling, sample format conversion and mixing library. + * + * Interaction with lswr is done through SwrContext, which is + * allocated with swr_alloc() or swr_alloc_set_opts(). It is opaque, so all parameters + * must be set with the @ref avoptions API. + * + * The first thing you will need to do in order to use lswr is to allocate + * SwrContext. This can be done with swr_alloc() or swr_alloc_set_opts(). If you + * are using the former, you must set options through the @ref avoptions API. + * The latter function provides the same feature, but it allows you to set some + * common options in the same statement. + * + * For example the following code will setup conversion from planar float sample + * format to interleaved signed 16-bit integer, downsampling from 48kHz to + * 44.1kHz and downmixing from 5.1 channels to stereo (using the default mixing + * matrix). This is using the swr_alloc() function. + * @code + * SwrContext *swr = swr_alloc(); + * av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_5POINT1, 0); + * av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + * av_opt_set_int(swr, "in_sample_rate", 48000, 0); + * av_opt_set_int(swr, "out_sample_rate", 44100, 0); + * av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); + * av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + * @endcode + * + * The same job can be done using swr_alloc_set_opts() as well: + * @code + * SwrContext *swr = swr_alloc_set_opts(NULL, // we're allocating a new context + * AV_CH_LAYOUT_STEREO, // out_ch_layout + * AV_SAMPLE_FMT_S16, // out_sample_fmt + * 44100, // out_sample_rate + * AV_CH_LAYOUT_5POINT1, // in_ch_layout + * AV_SAMPLE_FMT_FLTP, // in_sample_fmt + * 48000, // in_sample_rate + * 0, // log_offset + * NULL); // log_ctx + * @endcode + * + * Once all values have been set, it must be initialized with swr_init(). If + * you need to change the conversion parameters, you can change the parameters + * using @ref AVOptions, as described above in the first example; or by using + * swr_alloc_set_opts(), but with the first argument the allocated context. + * You must then call swr_init() again. + * + * The conversion itself is done by repeatedly calling swr_convert(). + * Note that the samples may get buffered in swr if you provide insufficient + * output space or if sample rate conversion is done, which requires "future" + * samples. Samples that do not require future input can be retrieved at any + * time by using swr_convert() (in_count can be set to 0). + * At the end of conversion the resampling buffer can be flushed by calling + * swr_convert() with NULL in and 0 in_count. + * + * The samples used in the conversion process can be managed with the libavutil + * @ref lavu_sampmanip "samples manipulation" API, including av_samples_alloc() + * function used in the following example. + * + * The delay between input and output, can at any time be found by using + * swr_get_delay(). + * + * The following code demonstrates the conversion loop assuming the parameters + * from above and caller-defined functions get_input() and handle_output(): + * @code + * uint8_t **input; + * int in_samples; + * + * while (get_input(&input, &in_samples)) { + * uint8_t *output; + * int out_samples = av_rescale_rnd(swr_get_delay(swr, 48000) + + * in_samples, 44100, 48000, AV_ROUND_UP); + * av_samples_alloc(&output, NULL, 2, out_samples, + * AV_SAMPLE_FMT_S16, 0); + * out_samples = swr_convert(swr, &output, out_samples, + * input, in_samples); + * handle_output(output, out_samples); + * av_freep(&output); + * } + * @endcode + * + * When the conversion is finished, the conversion + * context and everything associated with it must be freed with swr_free(). + * A swr_close() function is also available, but it exists mainly for + * compatibility with libavresample, and is not required to be called. + * + * There will be no memory leak if the data is not completely flushed before + * swr_free(). + */ + +#include +#include "libavutil/channel_layout.h" +#include "libavutil/frame.h" +#include "libavutil/samplefmt.h" + +#include "libswresample/version.h" + +/** + * @name Option constants + * These constants are used for the @ref avoptions interface for lswr. + * @{ + * + */ + +#define SWR_FLAG_RESAMPLE 1 ///< Force resampling even if equal sample rate +//TODO use int resample ? +//long term TODO can we enable this dynamically? + +/** Dithering algorithms */ +enum SwrDitherType { + SWR_DITHER_NONE = 0, + SWR_DITHER_RECTANGULAR, + SWR_DITHER_TRIANGULAR, + SWR_DITHER_TRIANGULAR_HIGHPASS, + + SWR_DITHER_NS = 64, ///< not part of API/ABI + SWR_DITHER_NS_LIPSHITZ, + SWR_DITHER_NS_F_WEIGHTED, + SWR_DITHER_NS_MODIFIED_E_WEIGHTED, + SWR_DITHER_NS_IMPROVED_E_WEIGHTED, + SWR_DITHER_NS_SHIBATA, + SWR_DITHER_NS_LOW_SHIBATA, + SWR_DITHER_NS_HIGH_SHIBATA, + SWR_DITHER_NB, ///< not part of API/ABI +}; + +/** Resampling Engines */ +enum SwrEngine { + SWR_ENGINE_SWR, /**< SW Resampler */ + SWR_ENGINE_SOXR, /**< SoX Resampler */ + SWR_ENGINE_NB, ///< not part of API/ABI +}; + +/** Resampling Filter Types */ +enum SwrFilterType { + SWR_FILTER_TYPE_CUBIC, /**< Cubic */ + SWR_FILTER_TYPE_BLACKMAN_NUTTALL, /**< Blackman Nuttall windowed sinc */ + SWR_FILTER_TYPE_KAISER, /**< Kaiser windowed sinc */ +}; + +/** + * @} + */ + +/** + * The libswresample context. Unlike libavcodec and libavformat, this structure + * is opaque. This means that if you would like to set options, you must use + * the @ref avoptions API and cannot directly set values to members of the + * structure. + */ +typedef struct SwrContext SwrContext; + +/** + * Get the AVClass for SwrContext. It can be used in combination with + * AV_OPT_SEARCH_FAKE_OBJ for examining options. + * + * @see av_opt_find(). + * @return the AVClass of SwrContext + */ +const AVClass *swr_get_class(void); + +/** + * @name SwrContext constructor functions + * @{ + */ + +/** + * Allocate SwrContext. + * + * If you use this function you will need to set the parameters (manually or + * with swr_alloc_set_opts()) before calling swr_init(). + * + * @see swr_alloc_set_opts(), swr_init(), swr_free() + * @return NULL on error, allocated context otherwise + */ +struct SwrContext *swr_alloc(void); + +/** + * Initialize context after user parameters have been set. + * @note The context must be configured using the AVOption API. + * + * @see av_opt_set_int() + * @see av_opt_set_dict() + * + * @param[in,out] s Swr context to initialize + * @return AVERROR error code in case of failure. + */ +int swr_init(struct SwrContext *s); + +/** + * Check whether an swr context has been initialized or not. + * + * @param[in] s Swr context to check + * @see swr_init() + * @return positive if it has been initialized, 0 if not initialized + */ +int swr_is_initialized(struct SwrContext *s); + +/** + * Allocate SwrContext if needed and set/reset common parameters. + * + * This function does not require s to be allocated with swr_alloc(). On the + * other hand, swr_alloc() can use swr_alloc_set_opts() to set the parameters + * on the allocated context. + * + * @param s existing Swr context if available, or NULL if not + * @param out_ch_layout output channel layout (AV_CH_LAYOUT_*) + * @param out_sample_fmt output sample format (AV_SAMPLE_FMT_*). + * @param out_sample_rate output sample rate (frequency in Hz) + * @param in_ch_layout input channel layout (AV_CH_LAYOUT_*) + * @param in_sample_fmt input sample format (AV_SAMPLE_FMT_*). + * @param in_sample_rate input sample rate (frequency in Hz) + * @param log_offset logging level offset + * @param log_ctx parent logging context, can be NULL + * + * @see swr_init(), swr_free() + * @return NULL on error, allocated context otherwise + */ +struct SwrContext *swr_alloc_set_opts(struct SwrContext *s, + int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, + int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, + int log_offset, void *log_ctx); + +/** + * @} + * + * @name SwrContext destructor functions + * @{ + */ + +/** + * Free the given SwrContext and set the pointer to NULL. + * + * @param[in] s a pointer to a pointer to Swr context + */ +void swr_free(struct SwrContext **s); + +/** + * Closes the context so that swr_is_initialized() returns 0. + * + * The context can be brought back to life by running swr_init(), + * swr_init() can also be used without swr_close(). + * This function is mainly provided for simplifying the usecase + * where one tries to support libavresample and libswresample. + * + * @param[in,out] s Swr context to be closed + */ +void swr_close(struct SwrContext *s); + +/** + * @} + * + * @name Core conversion functions + * @{ + */ + +/** Convert audio. + * + * in and in_count can be set to 0 to flush the last few samples out at the + * end. + * + * If more input is provided than output space, then the input will be buffered. + * You can avoid this buffering by using swr_get_out_samples() to retrieve an + * upper bound on the required number of output samples for the given number of + * input samples. Conversion will run directly without copying whenever possible. + * + * @param s allocated Swr context, with parameters set + * @param out output buffers, only the first one need be set in case of packed audio + * @param out_count amount of space available for output in samples per channel + * @param in input buffers, only the first one need to be set in case of packed audio + * @param in_count number of input samples available in one channel + * + * @return number of samples output per channel, negative value on error + */ +int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, + const uint8_t **in , int in_count); + +/** + * Convert the next timestamp from input to output + * timestamps are in 1/(in_sample_rate * out_sample_rate) units. + * + * @note There are 2 slightly differently behaving modes. + * @li When automatic timestamp compensation is not used, (min_compensation >= FLT_MAX) + * in this case timestamps will be passed through with delays compensated + * @li When automatic timestamp compensation is used, (min_compensation < FLT_MAX) + * in this case the output timestamps will match output sample numbers. + * See ffmpeg-resampler(1) for the two modes of compensation. + * + * @param s[in] initialized Swr context + * @param pts[in] timestamp for the next input sample, INT64_MIN if unknown + * @see swr_set_compensation(), swr_drop_output(), and swr_inject_silence() are + * function used internally for timestamp compensation. + * @return the output timestamp for the next output sample + */ +int64_t swr_next_pts(struct SwrContext *s, int64_t pts); + +/** + * @} + * + * @name Low-level option setting functions + * These functons provide a means to set low-level options that is not possible + * with the AVOption API. + * @{ + */ + +/** + * Activate resampling compensation ("soft" compensation). This function is + * internally called when needed in swr_next_pts(). + * + * @param[in,out] s allocated Swr context. If it is not initialized, + * or SWR_FLAG_RESAMPLE is not set, swr_init() is + * called with the flag set. + * @param[in] sample_delta delta in PTS per sample + * @param[in] compensation_distance number of samples to compensate for + * @return >= 0 on success, AVERROR error codes if: + * @li @c s is NULL, + * @li @c compensation_distance is less than 0, + * @li @c compensation_distance is 0 but sample_delta is not, + * @li compensation unsupported by resampler, or + * @li swr_init() fails when called. + */ +int swr_set_compensation(struct SwrContext *s, int sample_delta, int compensation_distance); + +/** + * Set a customized input channel mapping. + * + * @param[in,out] s allocated Swr context, not yet initialized + * @param[in] channel_map customized input channel mapping (array of channel + * indexes, -1 for a muted channel) + * @return >= 0 on success, or AVERROR error code in case of failure. + */ +int swr_set_channel_mapping(struct SwrContext *s, const int *channel_map); + +/** + * Generate a channel mixing matrix. + * + * This function is the one used internally by libswresample for building the + * default mixing matrix. It is made public just as a utility function for + * building custom matrices. + * + * @param in_layout input channel layout + * @param out_layout output channel layout + * @param center_mix_level mix level for the center channel + * @param surround_mix_level mix level for the surround channel(s) + * @param lfe_mix_level mix level for the low-frequency effects channel + * @param rematrix_maxval if 1.0, coefficients will be normalized to prevent + * overflow. if INT_MAX, coefficients will not be + * normalized. + * @param[out] matrix mixing coefficients; matrix[i + stride * o] is + * the weight of input channel i in output channel o. + * @param stride distance between adjacent input channels in the + * matrix array + * @param matrix_encoding matrixed stereo downmix mode (e.g. dplii) + * @param log_ctx parent logging context, can be NULL + * @return 0 on success, negative AVERROR code on failure + */ +int swr_build_matrix(uint64_t in_layout, uint64_t out_layout, + double center_mix_level, double surround_mix_level, + double lfe_mix_level, double rematrix_maxval, + double rematrix_volume, double *matrix, + int stride, enum AVMatrixEncoding matrix_encoding, + void *log_ctx); + +/** + * Set a customized remix matrix. + * + * @param s allocated Swr context, not yet initialized + * @param matrix remix coefficients; matrix[i + stride * o] is + * the weight of input channel i in output channel o + * @param stride offset between lines of the matrix + * @return >= 0 on success, or AVERROR error code in case of failure. + */ +int swr_set_matrix(struct SwrContext *s, const double *matrix, int stride); + +/** + * @} + * + * @name Sample handling functions + * @{ + */ + +/** + * Drops the specified number of output samples. + * + * This function, along with swr_inject_silence(), is called by swr_next_pts() + * if needed for "hard" compensation. + * + * @param s allocated Swr context + * @param count number of samples to be dropped + * + * @return >= 0 on success, or a negative AVERROR code on failure + */ +int swr_drop_output(struct SwrContext *s, int count); + +/** + * Injects the specified number of silence samples. + * + * This function, along with swr_drop_output(), is called by swr_next_pts() + * if needed for "hard" compensation. + * + * @param s allocated Swr context + * @param count number of samples to be dropped + * + * @return >= 0 on success, or a negative AVERROR code on failure + */ +int swr_inject_silence(struct SwrContext *s, int count); + +/** + * Gets the delay the next input sample will experience relative to the next output sample. + * + * Swresample can buffer data if more input has been provided than available + * output space, also converting between sample rates needs a delay. + * This function returns the sum of all such delays. + * The exact delay is not necessarily an integer value in either input or + * output sample rate. Especially when downsampling by a large value, the + * output sample rate may be a poor choice to represent the delay, similarly + * for upsampling and the input sample rate. + * + * @param s swr context + * @param base timebase in which the returned delay will be: + * @li if it's set to 1 the returned delay is in seconds + * @li if it's set to 1000 the returned delay is in milliseconds + * @li if it's set to the input sample rate then the returned + * delay is in input samples + * @li if it's set to the output sample rate then the returned + * delay is in output samples + * @li if it's the least common multiple of in_sample_rate and + * out_sample_rate then an exact rounding-free delay will be + * returned + * @returns the delay in 1 / @c base units. + */ +int64_t swr_get_delay(struct SwrContext *s, int64_t base); + +/** + * Find an upper bound on the number of samples that the next swr_convert + * call will output, if called with in_samples of input samples. This + * depends on the internal state, and anything changing the internal state + * (like further swr_convert() calls) will may change the number of samples + * swr_get_out_samples() returns for the same number of input samples. + * + * @param in_samples number of input samples. + * @note any call to swr_inject_silence(), swr_convert(), swr_next_pts() + * or swr_set_compensation() invalidates this limit + * @note it is recommended to pass the correct available buffer size + * to all functions like swr_convert() even if swr_get_out_samples() + * indicates that less would be used. + * @returns an upper bound on the number of samples that the next swr_convert + * will output or a negative value to indicate an error + */ +int swr_get_out_samples(struct SwrContext *s, int in_samples); + +/** + * @} + * + * @name Configuration accessors + * @{ + */ + +/** + * Return the @ref LIBSWRESAMPLE_VERSION_INT constant. + * + * This is useful to check if the build-time libswresample has the same version + * as the run-time one. + * + * @returns the unsigned int-typed version + */ +unsigned swresample_version(void); + +/** + * Return the swr build-time configuration. + * + * @returns the build-time @c ./configure flags + */ +const char *swresample_configuration(void); + +/** + * Return the swr license. + * + * @returns the license of libswresample, determined at build-time + */ +const char *swresample_license(void); + +/** + * @} + * + * @name AVFrame based API + * @{ + */ + +/** + * Convert the samples in the input AVFrame and write them to the output AVFrame. + * + * Input and output AVFrames must have channel_layout, sample_rate and format set. + * + * If the output AVFrame does not have the data pointers allocated the nb_samples + * field will be set using av_frame_get_buffer() + * is called to allocate the frame. + * + * The output AVFrame can be NULL or have fewer allocated samples than required. + * In this case, any remaining samples not written to the output will be added + * to an internal FIFO buffer, to be returned at the next call to this function + * or to swr_convert(). + * + * If converting sample rate, there may be data remaining in the internal + * resampling delay buffer. swr_get_delay() tells the number of + * remaining samples. To get this data as output, call this function or + * swr_convert() with NULL input. + * + * If the SwrContext configuration does not match the output and + * input AVFrame settings the conversion does not take place and depending on + * which AVFrame is not matching AVERROR_OUTPUT_CHANGED, AVERROR_INPUT_CHANGED + * or the result of a bitwise-OR of them is returned. + * + * @see swr_delay() + * @see swr_convert() + * @see swr_get_delay() + * + * @param swr audio resample context + * @param output output AVFrame + * @param input input AVFrame + * @return 0 on success, AVERROR on failure or nonmatching + * configuration. + */ +int swr_convert_frame(SwrContext *swr, + AVFrame *output, const AVFrame *input); + +/** + * Configure or reconfigure the SwrContext using the information + * provided by the AVFrames. + * + * The original resampling context is reset even on failure. + * The function calls swr_close() internally if the context is open. + * + * @see swr_close(); + * + * @param swr audio resample context + * @param output output AVFrame + * @param input input AVFrame + * @return 0 on success, AVERROR on failure. + */ +int swr_config_frame(SwrContext *swr, const AVFrame *out, const AVFrame *in); + +/** + * @} + * @} + */ + +#endif /* SWRESAMPLE_SWRESAMPLE_H */ diff --git a/thrid-party/ffmpeg/include/libswresample/version.h b/thrid-party/ffmpeg/include/libswresample/version.h new file mode 100644 index 0000000000..6a66173f3b --- /dev/null +++ b/thrid-party/ffmpeg/include/libswresample/version.h @@ -0,0 +1,45 @@ +/* + * Version macros. + * + * This file is part of libswresample + * + * libswresample is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libswresample is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with libswresample; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SWRESAMPLE_VERSION_H +#define SWRESAMPLE_VERSION_H + +/** + * @file + * Libswresample version macros + */ + +#include "libavutil/avutil.h" + +#define LIBSWRESAMPLE_VERSION_MAJOR 2 +#define LIBSWRESAMPLE_VERSION_MINOR 9 +#define LIBSWRESAMPLE_VERSION_MICRO 100 + +#define LIBSWRESAMPLE_VERSION_INT AV_VERSION_INT(LIBSWRESAMPLE_VERSION_MAJOR, \ + LIBSWRESAMPLE_VERSION_MINOR, \ + LIBSWRESAMPLE_VERSION_MICRO) +#define LIBSWRESAMPLE_VERSION AV_VERSION(LIBSWRESAMPLE_VERSION_MAJOR, \ + LIBSWRESAMPLE_VERSION_MINOR, \ + LIBSWRESAMPLE_VERSION_MICRO) +#define LIBSWRESAMPLE_BUILD LIBSWRESAMPLE_VERSION_INT + +#define LIBSWRESAMPLE_IDENT "SwR" AV_STRINGIFY(LIBSWRESAMPLE_VERSION) + +#endif /* SWRESAMPLE_VERSION_H */ diff --git a/thrid-party/ffmpeg/lib/libavcodec.a b/thrid-party/ffmpeg/lib/libavcodec.a new file mode 100644 index 0000000000..1cf3fe3c50 Binary files /dev/null and b/thrid-party/ffmpeg/lib/libavcodec.a differ diff --git a/thrid-party/ffmpeg/lib/libavformat.a b/thrid-party/ffmpeg/lib/libavformat.a new file mode 100644 index 0000000000..d2489cfbf5 Binary files /dev/null and b/thrid-party/ffmpeg/lib/libavformat.a differ diff --git a/thrid-party/ffmpeg/lib/libavutil.a b/thrid-party/ffmpeg/lib/libavutil.a new file mode 100644 index 0000000000..bd2641b14f Binary files /dev/null and b/thrid-party/ffmpeg/lib/libavutil.a differ diff --git a/thrid-party/ffmpeg/lib/libswresample.a b/thrid-party/ffmpeg/lib/libswresample.a new file mode 100644 index 0000000000..adfa5985e1 Binary files /dev/null and b/thrid-party/ffmpeg/lib/libswresample.a differ diff --git a/thrid-party/libjpeg-turbo/jconfig.h b/thrid-party/libjpeg-turbo/jconfig.h new file mode 100644 index 0000000000..6e8e9c5380 --- /dev/null +++ b/thrid-party/libjpeg-turbo/jconfig.h @@ -0,0 +1,73 @@ +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ +#define JPEG_LIB_VERSION 80 + +/* libjpeg-turbo version */ +#define LIBJPEG_TURBO_VERSION 2.0.3 + +/* libjpeg-turbo version in integer form */ +#define LIBJPEG_TURBO_VERSION_NUMBER 2000003 + +/* Support arithmetic encoding */ +#define C_ARITH_CODING_SUPPORTED 1 + +/* Support arithmetic decoding */ +#define D_ARITH_CODING_SUPPORTED 1 + +/* Support in-memory source/destination managers */ +/* #undef MEM_SRCDST_SUPPORTED */ + +/* Use accelerated SIMD routines. */ +#define WITH_SIMD 1 + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define if you need to include to get size_t. */ +#define NEED_SYS_TYPES_H 1 + +/* Define if you have BSD-like bzero and bcopy in rather than + memset/memcpy in . */ +/* #undef NEED_BSD_STRINGS */ + +/* Define to 1 if the system has the type `unsigned char'. */ +#define HAVE_UNSIGNED_CHAR 1 + +/* Define to 1 if the system has the type `unsigned short'. */ +#define HAVE_UNSIGNED_SHORT 1 + +/* Compiler does not support pointers to undefined structures. */ +/* #undef INCOMPLETE_TYPES_BROKEN */ + +/* Define if your (broken) compiler shifts signed values as if they were + unsigned. */ +/* #undef RIGHT_SHIFT_IS_UNSIGNED */ + +/* Define to 1 if type `char' is unsigned and you are not using gcc. */ +#ifndef __CHAR_UNSIGNED__ +/* #undef __CHAR_UNSIGNED__ */ +#endif + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/thrid-party/libjpeg-turbo/jerror.h b/thrid-party/libjpeg-turbo/jerror.h new file mode 100644 index 0000000000..933a3690fd --- /dev/null +++ b/thrid-party/libjpeg-turbo/jerror.h @@ -0,0 +1,316 @@ +/* + * jerror.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1994-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2014, 2017, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code, string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code, string) code, + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +#if JPEG_LIB_VERSION < 70 +JMESSAGE(JERR_ARITH_NOTIMPL, "Sorry, arithmetic coding is not implemented") +#endif +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +#if JPEG_LIB_VERSION >= 70 +JMESSAGE(JERR_BAD_CROP_SPEC, "Invalid crop request") +#endif +JMESSAGE(JERR_BAD_DCT_COEF, "DCT coefficient out of range") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +#if JPEG_LIB_VERSION >= 70 +JMESSAGE(JERR_BAD_DROP_SAMPLING, + "Component index %d: mismatching sampling ratio %d:%d, %d:%d, %c") +#endif +JMESSAGE(JERR_BAD_HUFF_TABLE, "Bogus Huffman table definition") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_LIB_VERSION, + "Wrong JPEG library version: library is %d, caller expects %d") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_STRUCT_SIZE, + "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +#if JPEG_LIB_VERSION >= 70 +JMESSAGE(JERR_NO_ARITH_TABLE, "Arithmetic table 0x%02x was not defined") +#endif +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT_SHORT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_EXTENSION, "JFIF extension marker: type 0x%02x, length %u") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_THUMB_JPEG, + "JFIF extension marker: JPEG-compressed thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_PALETTE, + "JFIF extension marker: palette thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_RGB, + "JFIF extension marker: RGB thumbnail image, length %u") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +#if JPEG_LIB_VERSION >= 70 +JMESSAGE(JWRN_ARITH_BAD_CODE, "Corrupt JPEG data: bad arithmetic code") +#endif +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") +#if JPEG_LIB_VERSION < 70 +JMESSAGE(JERR_BAD_CROP_SPEC, "Invalid crop request") +#if defined(C_ARITH_CODING_SUPPORTED) || defined(D_ARITH_CODING_SUPPORTED) +JMESSAGE(JERR_NO_ARITH_TABLE, "Arithmetic table 0x%02x was not defined") +JMESSAGE(JWRN_ARITH_BAD_CODE, "Corrupt JPEG data: bad arithmetic code") +#endif +#endif +JMESSAGE(JWRN_BOGUS_ICC, "Corrupt JPEG data: bad ICC marker") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo, code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXIT1(cinfo, code, p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXIT2(cinfo, code, p1, p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXIT3(cinfo, code, p1, p2, p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXIT4(cinfo, code, p1, p2, p3, p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXITS(cinfo, code, str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo, code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), -1)) +#define WARNMS1(cinfo, code, p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), -1)) +#define WARNMS2(cinfo, code, p1, p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo, lvl, code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl))) +#define TRACEMS1(cinfo, lvl, code, p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl))) +#define TRACEMS2(cinfo, lvl, code, p1, p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl))) +#define TRACEMS3(cinfo, lvl, code, p1, p2, p3) \ + MAKESTMT(int *_mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl)); ) +#define TRACEMS4(cinfo, lvl, code, p1, p2, p3, p4) \ + MAKESTMT(int *_mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl)); ) +#define TRACEMS5(cinfo, lvl, code, p1, p2, p3, p4, p5) \ + MAKESTMT(int *_mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl)); ) +#define TRACEMS8(cinfo, lvl, code, p1, p2, p3, p4, p5, p6, p7, p8) \ + MAKESTMT(int *_mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl)); ) +#define TRACEMSS(cinfo, lvl, code, str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr)(cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/thrid-party/libjpeg-turbo/jmorecfg.h b/thrid-party/libjpeg-turbo/jmorecfg.h new file mode 100644 index 0000000000..d0b930079a --- /dev/null +++ b/thrid-party/libjpeg-turbo/jmorecfg.h @@ -0,0 +1,421 @@ +/* + * jmorecfg.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 1997-2009 by Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2009, 2011, 2014-2015, 2018, D. R. Commander. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of Rec. ITU-T T.81 | ISO/IEC 10918-1, set this to 255. + * However, darn few applications need more than 4 channels (maybe 5 for CMYK + + * alpha mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int)(value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef __CHAR_UNSIGNED__ +#define GETJSAMPLE(value) ((int)(value)) +#else +#define GETJSAMPLE(value) ((int)(value) & 0xFF) +#endif /* __CHAR_UNSIGNED__ */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int)(value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef __CHAR_UNSIGNED__ +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* __CHAR_UNSIGNED__ */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef __CHAR_UNSIGNED__ +typedef char UINT8; +#else /* not __CHAR_UNSIGNED__ */ +typedef short UINT8; +#endif /* __CHAR_UNSIGNED__ */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. + * + * NOTE: The INT32 typedef dates back to libjpeg v5 (1994.) Integers were + * sometimes 16-bit back then (MS-DOS), which is why INT32 is typedef'd to + * long. It also wasn't common (or at least as common) in 1994 for INT32 to be + * defined by platform headers. Since then, however, INT32 is defined in + * several other common places: + * + * Xmd.h (X11 header) typedefs INT32 to int on 64-bit platforms and long on + * 32-bit platforms (i.e always a 32-bit signed type.) + * + * basetsd.h (Win32 header) typedefs INT32 to int (always a 32-bit signed type + * on modern platforms.) + * + * qglobal.h (Qt header) typedefs INT32 to int (always a 32-bit signed type on + * modern platforms.) + * + * This is a recipe for conflict, since "long" and "int" aren't always + * compatible types. Since the definition of INT32 has technically been part + * of the libjpeg API for more than 20 years, we can't remove it, but we do not + * use it internally any longer. We instead define a separate type (JLONG) + * for internal use, which ensures that internal behavior will always be the + * same regardless of any external headers that may be included. + */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +#ifndef _BASETSD_H_ /* Microsoft defines it in basetsd.h */ +#ifndef _BASETSD_H /* MinGW is slightly different */ +#ifndef QGLOBAL_H /* Qt defines it in qglobal.h */ +typedef long INT32; +#endif +#endif +#endif +#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. (Note that changing this datatype will + * potentially require modifying the SIMD code. The x86-64 SIMD extensions, + * in particular, assume a 32-bit JDIMENSION.) + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These macros are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions; + * in particular, you'll need to do that to make the library a Windows DLL. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +/* a function called through method pointers: */ +#define METHODDEF(type) static type +/* a function used only in its module: */ +#define LOCAL(type) static type +/* a function referenced thru EXTERNs: */ +#define GLOBAL(type) type +/* a reference to a GLOBAL function: */ +#define EXTERN(type) extern type + + +/* Originally, this macro was used as a way of defining function prototypes + * for both modern compilers as well as older compilers that did not support + * prototype parameters. libjpeg-turbo has never supported these older, + * non-ANSI compilers, but the macro is still included because there is some + * software out there that uses it. + */ + +#define JMETHOD(type, methodname, arglist) type (*methodname) arglist + + +/* libjpeg-turbo no longer supports platforms that have far symbols (MS-DOS), + * but again, some software relies on this macro. + */ + +#undef FAR +#define FAR + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +#ifndef HAVE_BOOLEAN +typedef int boolean; +#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Capability options common to encoder and decoder: */ + +#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * The RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros are a vestigial + * feature of libjpeg. The idea was that, if an application developer needed + * to compress from/decompress to a BGR/BGRX/RGBX/XBGR/XRGB buffer, they could + * change these macros, rebuild libjpeg, and link their application statically + * with it. In reality, few people ever did this, because there were some + * severe restrictions involved (cjpeg and djpeg no longer worked properly, + * compressing/decompressing RGB JPEGs no longer worked properly, and the color + * quantizer wouldn't work with pixel sizes other than 3.) Furthermore, since + * all of the O/S-supplied versions of libjpeg were built with the default + * values of RGB_RED, RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE, many applications + * have come to regard these values as immutable. + * + * The libjpeg-turbo colorspace extensions provide a much cleaner way of + * compressing from/decompressing to buffers with arbitrary component orders + * and pixel sizes. Thus, we do not support changing the values of RGB_RED, + * RGB_GREEN, RGB_BLUE, or RGB_PIXELSIZE. In addition to the restrictions + * listed above, changing these values will also break the SIMD extensions and + * the regression tests. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ + +#define JPEG_NUMCS 17 + +#define EXT_RGB_RED 0 +#define EXT_RGB_GREEN 1 +#define EXT_RGB_BLUE 2 +#define EXT_RGB_PIXELSIZE 3 + +#define EXT_RGBX_RED 0 +#define EXT_RGBX_GREEN 1 +#define EXT_RGBX_BLUE 2 +#define EXT_RGBX_PIXELSIZE 4 + +#define EXT_BGR_RED 2 +#define EXT_BGR_GREEN 1 +#define EXT_BGR_BLUE 0 +#define EXT_BGR_PIXELSIZE 3 + +#define EXT_BGRX_RED 2 +#define EXT_BGRX_GREEN 1 +#define EXT_BGRX_BLUE 0 +#define EXT_BGRX_PIXELSIZE 4 + +#define EXT_XBGR_RED 3 +#define EXT_XBGR_GREEN 2 +#define EXT_XBGR_BLUE 1 +#define EXT_XBGR_PIXELSIZE 4 + +#define EXT_XRGB_RED 1 +#define EXT_XRGB_GREEN 2 +#define EXT_XRGB_BLUE 3 +#define EXT_XRGB_PIXELSIZE 4 + +static const int rgb_red[JPEG_NUMCS] = { + -1, -1, RGB_RED, -1, -1, -1, EXT_RGB_RED, EXT_RGBX_RED, + EXT_BGR_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED, + EXT_RGBX_RED, EXT_BGRX_RED, EXT_XBGR_RED, EXT_XRGB_RED, + -1 +}; + +static const int rgb_green[JPEG_NUMCS] = { + -1, -1, RGB_GREEN, -1, -1, -1, EXT_RGB_GREEN, EXT_RGBX_GREEN, + EXT_BGR_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN, + EXT_RGBX_GREEN, EXT_BGRX_GREEN, EXT_XBGR_GREEN, EXT_XRGB_GREEN, + -1 +}; + +static const int rgb_blue[JPEG_NUMCS] = { + -1, -1, RGB_BLUE, -1, -1, -1, EXT_RGB_BLUE, EXT_RGBX_BLUE, + EXT_BGR_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE, + EXT_RGBX_BLUE, EXT_BGRX_BLUE, EXT_XBGR_BLUE, EXT_XRGB_BLUE, + -1 +}; + +static const int rgb_pixelsize[JPEG_NUMCS] = { + -1, -1, RGB_PIXELSIZE, -1, -1, -1, EXT_RGB_PIXELSIZE, EXT_RGBX_PIXELSIZE, + EXT_BGR_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE, + EXT_RGBX_PIXELSIZE, EXT_BGRX_PIXELSIZE, EXT_XBGR_PIXELSIZE, EXT_XRGB_PIXELSIZE, + -1 +}; + +/* Definitions for speed-related optimizations. */ + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#ifndef WITH_SIMD +#define MULTIPLIER int /* type for fastest integer multiply */ +#else +#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ +#endif +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + */ + +#ifndef FAST_FLOAT +#define FAST_FLOAT float +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/thrid-party/libjpeg-turbo/jpeglib.h b/thrid-party/libjpeg-turbo/jpeglib.h new file mode 100644 index 0000000000..33f8ad2791 --- /dev/null +++ b/thrid-party/libjpeg-turbo/jpeglib.h @@ -0,0 +1,1132 @@ +/* + * jpeglib.h + * + * This file was part of the Independent JPEG Group's software: + * Copyright (C) 1991-1998, Thomas G. Lane. + * Modified 2002-2009 by Guido Vollbeding. + * libjpeg-turbo Modifications: + * Copyright (C) 2009-2011, 2013-2014, 2016-2017, D. R. Commander. + * Copyright (C) 2015, Google, Inc. + * For conditions of distribution and use, see the accompanying README.ijg + * file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jconfig.h" /* widely used configuration options */ +#endif +#include "jmorecfg.h" /* seldom changed options */ + + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +extern "C" { +#endif +#endif + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + */ + +typedef JSAMPLE *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This array gives the coefficient quantizers in natural array order + * (not the zigzag order in which they are stored in a JPEG DQT marker). + * CAUTION: IJG versions prior to v6a kept this array in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values from 1 to 16 are supported. + * Note that different components may receive different IDCT scalings. + */ +#if JPEG_LIB_VERSION >= 70 + int DCT_h_scaled_size; + int DCT_v_scaled_size; +#else + int DCT_scaled_size; +#endif + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_[h_]scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_[h_]scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + JQUANT_TBL *quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void *dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + +/* The decompressor can save APPn and COM markers in a list of these: */ + +typedef struct jpeg_marker_struct *jpeg_saved_marker_ptr; + +struct jpeg_marker_struct { + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET *data; /* the data contained in the marker */ + /* the marker length word is not counted in data_length or original_length */ +}; + +/* Known color spaces. */ + +#define JCS_EXTENSIONS 1 +#define JCS_ALPHA_EXTENSIONS 1 + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue as specified by the RGB_RED, + RGB_GREEN, RGB_BLUE, and RGB_PIXELSIZE macros */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK, /* Y/Cb/Cr/K */ + JCS_EXT_RGB, /* red/green/blue */ + JCS_EXT_RGBX, /* red/green/blue/x */ + JCS_EXT_BGR, /* blue/green/red */ + JCS_EXT_BGRX, /* blue/green/red/x */ + JCS_EXT_XBGR, /* x/blue/green/red */ + JCS_EXT_XRGB, /* x/red/green/blue */ + /* When out_color_space it set to JCS_EXT_RGBX, JCS_EXT_BGRX, JCS_EXT_XBGR, + or JCS_EXT_XRGB during decompression, the X byte is undefined, and in + order to ensure the best performance, libjpeg-turbo can set that byte to + whatever value it wishes. Use the following colorspace constants to + ensure that the X byte is set to 0xFF, so that it can be interpreted as an + opaque alpha channel. */ + JCS_EXT_RGBA, /* red/green/blue/alpha */ + JCS_EXT_BGRA, /* blue/green/red/alpha */ + JCS_EXT_ABGR, /* alpha/blue/green/red */ + JCS_EXT_ARGB, /* alpha/red/green/blue */ + JCS_RGB565 /* 5-bit red/6-bit green/5-bit blue */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr *err; /* Error handler module */ \ + struct jpeg_memory_mgr *mem; /* Memory manager module */ \ + struct jpeg_progress_mgr *progress; /* Progress monitor, or NULL if none */ \ + void *client_data; /* Available for use by application */ \ + boolean is_decompressor; /* So common code can tell which is which */ \ + int global_state /* For checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct *j_common_ptr; +typedef struct jpeg_compress_struct *j_compress_ptr; +typedef struct jpeg_decompress_struct *j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr *dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + +#if JPEG_LIB_VERSION >= 70 + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + JDIMENSION jpeg_width; /* scaled JPEG image width */ + JDIMENSION jpeg_height; /* scaled JPEG image height */ + /* Dimensions of actual JPEG image that will be written to file, + * derived from input dimensions by scaling factors above. + * These fields are computed by jpeg_start_compress(). + * You can also use jpeg_calc_jpeg_dimensions() to determine these values + * in advance of calling jpeg_start_compress(). + */ +#endif + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info *comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL *quant_tbl_ptrs[NUM_QUANT_TBLS]; +#if JPEG_LIB_VERSION >= 70 + int q_scale_factor[NUM_QUANT_TBLS]; +#endif + /* ptrs to coefficient quantization tables, or NULL if not defined, + * and corresponding scale factors (percentage, initialized 100). + */ + + JHUFF_TBL *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info *scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ +#if JPEG_LIB_VERSION >= 70 + boolean do_fancy_downsampling; /* TRUE=apply fancy downsampling */ +#endif + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + UINT8 JFIF_minor_version; + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + +#if JPEG_LIB_VERSION >= 70 + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ +#endif + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + +#if JPEG_LIB_VERSION >= 80 + int block_size; /* the basic DCT block size: 1..16 */ + const int *natural_order; /* natural-order position array */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) */ +#endif + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master *master; + struct jpeg_c_main_controller *main; + struct jpeg_c_prep_controller *prep; + struct jpeg_c_coef_controller *coef; + struct jpeg_marker_writer *marker; + struct jpeg_color_converter *cconvert; + struct jpeg_downsampler *downsample; + struct jpeg_forward_dct *fdct; + struct jpeg_entropy_encoder *entropy; + jpeg_scan_info *script_space; /* workspace for jpeg_simple_progression */ + int script_space_size; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr *src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL *quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info *comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + +#if JPEG_LIB_VERSION >= 80 + boolean is_baseline; /* TRUE if Baseline SOF0 encountered */ +#endif + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ + UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_minor_version; + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Aside from the specific data retained from APPn markers known to the + * library, the uninterpreted contents of any or all APPn and COM markers + * can be saved in a list for examination by the application. + */ + jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + +#if JPEG_LIB_VERSION >= 70 + int min_DCT_h_scaled_size; /* smallest DCT_h_scaled_size of any component */ + int min_DCT_v_scaled_size; /* smallest DCT_v_scaled_size of any component */ +#else + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ +#endif + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_[v_]scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE *sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + +#if JPEG_LIB_VERSION >= 80 + /* These fields are derived from Se of first SOS marker. + */ + int block_size; /* the basic DCT block size: 1..16 */ + const int *natural_order; /* natural-order position array for entropy decode */ + int lim_Se; /* min( Se, DCTSIZE2-1 ) for entropy decode */ +#endif + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master *master; + struct jpeg_d_main_controller *main; + struct jpeg_d_coef_controller *coef; + struct jpeg_d_post_controller *post; + struct jpeg_input_controller *inputctl; + struct jpeg_marker_reader *marker; + struct jpeg_entropy_decoder *entropy; + struct jpeg_inverse_dct *idct; + struct jpeg_upsampler *upsample; + struct jpeg_color_deconverter *cconvert; + struct jpeg_color_quantizer *cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + void (*error_exit) (j_common_ptr cinfo); + /* Conditionally emit a trace or warning message */ + void (*emit_message) (j_common_ptr cinfo, int msg_level); + /* Routine that actually outputs a trace or error message */ + void (*output_message) (j_common_ptr cinfo); + /* Format a message string for the most recent JPEG error or message */ + void (*format_message) (j_common_ptr cinfo, char *buffer); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + void (*reset_error_mgr) (j_common_ptr cinfo); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const *jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const *addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + void (*progress_monitor) (j_common_ptr cinfo); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET *next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + void (*init_destination) (j_compress_ptr cinfo); + boolean (*empty_output_buffer) (j_compress_ptr cinfo); + void (*term_destination) (j_compress_ptr cinfo); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET *next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + void (*init_source) (j_decompress_ptr cinfo); + boolean (*fill_input_buffer) (j_decompress_ptr cinfo); + void (*skip_input_data) (j_decompress_ptr cinfo, long num_bytes); + boolean (*resync_to_restart) (j_decompress_ptr cinfo, int desired); + void (*term_source) (j_decompress_ptr cinfo); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control *jvirt_sarray_ptr; +typedef struct jvirt_barray_control *jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + void *(*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); + void *(*alloc_large) (j_common_ptr cinfo, int pool_id, + size_t sizeofobject); + JSAMPARRAY (*alloc_sarray) (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows); + JBLOCKARRAY (*alloc_barray) (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows); + jvirt_sarray_ptr (*request_virt_sarray) (j_common_ptr cinfo, int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess); + jvirt_barray_ptr (*request_virt_barray) (j_common_ptr cinfo, int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess); + void (*realize_virt_arrays) (j_common_ptr cinfo); + JSAMPARRAY (*access_virt_sarray) (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable); + JBLOCKARRAY (*access_virt_barray) (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable); + void (*free_pool) (j_common_ptr cinfo, int pool_id); + void (*self_destruct) (j_common_ptr cinfo); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; + + /* Maximum allocation request accepted by alloc_large. */ + long max_alloc_chunk; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef boolean (*jpeg_marker_parser_method) (j_decompress_ptr cinfo); + + +/* Originally, this macro was used as a way of defining function prototypes + * for both modern compilers as well as older compilers that did not support + * prototype parameters. libjpeg-turbo has never supported these older, + * non-ANSI compilers, but the macro is still included because there is some + * software out there that uses it. + */ + +#define JPP(arglist) arglist + + +/* Default error-management setup */ +EXTERN(struct jpeg_error_mgr *) jpeg_std_error(struct jpeg_error_mgr *err); + +/* Initialization of JPEG compression objects. + * jpeg_create_compress() and jpeg_create_decompress() are the exported + * names that applications should call. These expand to calls on + * jpeg_CreateCompress and jpeg_CreateDecompress with additional information + * passed for version mismatch checking. + * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx. + */ +#define jpeg_create_compress(cinfo) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_compress_struct)) +#define jpeg_create_decompress(cinfo) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_decompress_struct)) +EXTERN(void) jpeg_CreateCompress(j_compress_ptr cinfo, int version, + size_t structsize); +EXTERN(void) jpeg_CreateDecompress(j_decompress_ptr cinfo, int version, + size_t structsize); +/* Destruction of JPEG compression objects */ +EXTERN(void) jpeg_destroy_compress(j_compress_ptr cinfo); +EXTERN(void) jpeg_destroy_decompress(j_decompress_ptr cinfo); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN(void) jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile); +EXTERN(void) jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile); + +#if JPEG_LIB_VERSION >= 80 || defined(MEM_SRCDST_SUPPORTED) +/* Data source and destination managers: memory buffers. */ +EXTERN(void) jpeg_mem_dest(j_compress_ptr cinfo, unsigned char **outbuffer, + unsigned long *outsize); +EXTERN(void) jpeg_mem_src(j_decompress_ptr cinfo, + const unsigned char *inbuffer, unsigned long insize); +#endif + +/* Default parameter setup for compression */ +EXTERN(void) jpeg_set_defaults(j_compress_ptr cinfo); +/* Compression parameter setup aids */ +EXTERN(void) jpeg_set_colorspace(j_compress_ptr cinfo, + J_COLOR_SPACE colorspace); +EXTERN(void) jpeg_default_colorspace(j_compress_ptr cinfo); +EXTERN(void) jpeg_set_quality(j_compress_ptr cinfo, int quality, + boolean force_baseline); +EXTERN(void) jpeg_set_linear_quality(j_compress_ptr cinfo, int scale_factor, + boolean force_baseline); +#if JPEG_LIB_VERSION >= 70 +EXTERN(void) jpeg_default_qtables(j_compress_ptr cinfo, + boolean force_baseline); +#endif +EXTERN(void) jpeg_add_quant_table(j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline); +EXTERN(int) jpeg_quality_scaling(int quality); +EXTERN(void) jpeg_simple_progression(j_compress_ptr cinfo); +EXTERN(void) jpeg_suppress_tables(j_compress_ptr cinfo, boolean suppress); +EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table(j_common_ptr cinfo); +EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table(j_common_ptr cinfo); + +/* Main entry points for compression */ +EXTERN(void) jpeg_start_compress(j_compress_ptr cinfo, + boolean write_all_tables); +EXTERN(JDIMENSION) jpeg_write_scanlines(j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines); +EXTERN(void) jpeg_finish_compress(j_compress_ptr cinfo); + +#if JPEG_LIB_VERSION >= 70 +/* Precalculate JPEG dimensions for current compression parameters. */ +EXTERN(void) jpeg_calc_jpeg_dimensions(j_compress_ptr cinfo); +#endif + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines); + +/* Write a special marker. See libjpeg.txt concerning safe usage. */ +EXTERN(void) jpeg_write_marker(j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen); +/* Same, but piecemeal. */ +EXTERN(void) jpeg_write_m_header(j_compress_ptr cinfo, int marker, + unsigned int datalen); +EXTERN(void) jpeg_write_m_byte(j_compress_ptr cinfo, int val); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN(void) jpeg_write_tables(j_compress_ptr cinfo); + +/* Write ICC profile. See libjpeg.txt for usage information. */ +EXTERN(void) jpeg_write_icc_profile(j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len); + + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN(int) jpeg_read_header(j_decompress_ptr cinfo, boolean require_image); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN(boolean) jpeg_start_decompress(j_decompress_ptr cinfo); +EXTERN(JDIMENSION) jpeg_read_scanlines(j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines); +EXTERN(JDIMENSION) jpeg_skip_scanlines(j_decompress_ptr cinfo, + JDIMENSION num_lines); +EXTERN(void) jpeg_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset, + JDIMENSION *width); +EXTERN(boolean) jpeg_finish_decompress(j_decompress_ptr cinfo); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines); + +/* Additional entry points for buffered-image mode. */ +EXTERN(boolean) jpeg_has_multiple_scans(j_decompress_ptr cinfo); +EXTERN(boolean) jpeg_start_output(j_decompress_ptr cinfo, int scan_number); +EXTERN(boolean) jpeg_finish_output(j_decompress_ptr cinfo); +EXTERN(boolean) jpeg_input_complete(j_decompress_ptr cinfo); +EXTERN(void) jpeg_new_colormap(j_decompress_ptr cinfo); +EXTERN(int) jpeg_consume_input(j_decompress_ptr cinfo); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +#if JPEG_LIB_VERSION >= 80 +EXTERN(void) jpeg_core_output_dimensions(j_decompress_ptr cinfo); +#endif +EXTERN(void) jpeg_calc_output_dimensions(j_decompress_ptr cinfo); + +/* Control saving of COM and APPn markers into marker_list. */ +EXTERN(void) jpeg_save_markers(j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN(void) jpeg_set_marker_processor(j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients(j_decompress_ptr cinfo); +EXTERN(void) jpeg_write_coefficients(j_compress_ptr cinfo, + jvirt_barray_ptr *coef_arrays); +EXTERN(void) jpeg_copy_critical_parameters(j_decompress_ptr srcinfo, + j_compress_ptr dstinfo); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN(void) jpeg_abort_compress(j_compress_ptr cinfo); +EXTERN(void) jpeg_abort_decompress(j_decompress_ptr cinfo); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN(void) jpeg_abort(j_common_ptr cinfo); +EXTERN(void) jpeg_destroy(j_common_ptr cinfo); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN(boolean) jpeg_resync_to_restart(j_decompress_ptr cinfo, int desired); + +/* Read ICC profile. See libjpeg.txt for usage information. */ +EXTERN(boolean) jpeg_read_icc_profile(j_decompress_ptr cinfo, + JOCTET **icc_data_ptr, + unsigned int *icc_data_len); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +#ifndef DONT_USE_EXTERN_C +} +#endif +#endif + +#endif /* JPEGLIB_H */ diff --git a/thrid-party/libjpeg-turbo/libturbojpeg.a b/thrid-party/libjpeg-turbo/libturbojpeg.a new file mode 100644 index 0000000000..39a39a3b01 Binary files /dev/null and b/thrid-party/libjpeg-turbo/libturbojpeg.a differ diff --git a/thrid-party/libjpeg-turbo/turbojpeg.h b/thrid-party/libjpeg-turbo/turbojpeg.h new file mode 100644 index 0000000000..9c0a3713a5 --- /dev/null +++ b/thrid-party/libjpeg-turbo/turbojpeg.h @@ -0,0 +1,1744 @@ +/* + * Copyright (C)2009-2015, 2017 D. R. Commander. 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 the libjpeg-turbo Project 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 HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (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 __TURBOJPEG_H__ +#define __TURBOJPEG_H__ + +#if defined(_WIN32) && defined(DLLDEFINE) +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif +#define DLLCALL + + +/** + * @addtogroup TurboJPEG + * TurboJPEG API. This API provides an interface for generating, decoding, and + * transforming planar YUV and JPEG images in memory. + * + * @anchor YUVnotes + * YUV Image Format Notes + * ---------------------- + * Technically, the JPEG format uses the YCbCr colorspace (which is technically + * not a colorspace but a color transform), but per the convention of the + * digital video community, the TurboJPEG API uses "YUV" to refer to an image + * format consisting of Y, Cb, and Cr image planes. + * + * Each plane is simply a 2D array of bytes, each byte representing the value + * of one of the components (Y, Cb, or Cr) at a particular location in the + * image. The width and height of each plane are determined by the image + * width, height, and level of chrominance subsampling. The luminance plane + * width is the image width padded to the nearest multiple of the horizontal + * subsampling factor (2 in the case of 4:2:0 and 4:2:2, 4 in the case of + * 4:1:1, 1 in the case of 4:4:4 or grayscale.) Similarly, the luminance plane + * height is the image height padded to the nearest multiple of the vertical + * subsampling factor (2 in the case of 4:2:0 or 4:4:0, 1 in the case of 4:4:4 + * or grayscale.) This is irrespective of any additional padding that may be + * specified as an argument to the various YUV functions. The chrominance + * plane width is equal to the luminance plane width divided by the horizontal + * subsampling factor, and the chrominance plane height is equal to the + * luminance plane height divided by the vertical subsampling factor. + * + * For example, if the source image is 35 x 35 pixels and 4:2:2 subsampling is + * used, then the luminance plane would be 36 x 35 bytes, and each of the + * chrominance planes would be 18 x 35 bytes. If you specify a line padding of + * 4 bytes on top of this, then the luminance plane would be 36 x 35 bytes, and + * each of the chrominance planes would be 20 x 35 bytes. + * + * @{ + */ + + +/** + * The number of chrominance subsampling options + */ +#define TJ_NUMSAMP 6 + +/** + * Chrominance subsampling options. + * When pixels are converted from RGB to YCbCr (see #TJCS_YCbCr) or from CMYK + * to YCCK (see #TJCS_YCCK) as part of the JPEG compression process, some of + * the Cb and Cr (chrominance) components can be discarded or averaged together + * to produce a smaller image with little perceptible loss of image clarity + * (the human eye is more sensitive to small changes in brightness than to + * small changes in color.) This is called "chrominance subsampling". + */ +enum TJSAMP { + /** + * 4:4:4 chrominance subsampling (no chrominance subsampling). The JPEG or + * YUV image will contain one chrominance component for every pixel in the + * source image. + */ + TJSAMP_444 = 0, + /** + * 4:2:2 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 2x1 block of pixels in the source image. + */ + TJSAMP_422, + /** + * 4:2:0 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 2x2 block of pixels in the source image. + */ + TJSAMP_420, + /** + * Grayscale. The JPEG or YUV image will contain no chrominance components. + */ + TJSAMP_GRAY, + /** + * 4:4:0 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 1x2 block of pixels in the source image. + * + * @note 4:4:0 subsampling is not fully accelerated in libjpeg-turbo. + */ + TJSAMP_440, + /** + * 4:1:1 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 4x1 block of pixels in the source image. + * JPEG images compressed with 4:1:1 subsampling will be almost exactly the + * same size as those compressed with 4:2:0 subsampling, and in the + * aggregate, both subsampling methods produce approximately the same + * perceptual quality. However, 4:1:1 is better able to reproduce sharp + * horizontal features. + * + * @note 4:1:1 subsampling is not fully accelerated in libjpeg-turbo. + */ + TJSAMP_411 +}; + +/** + * MCU block width (in pixels) for a given level of chrominance subsampling. + * MCU block sizes: + * - 8x8 for no subsampling or grayscale + * - 16x8 for 4:2:2 + * - 8x16 for 4:4:0 + * - 16x16 for 4:2:0 + * - 32x8 for 4:1:1 + */ +static const int tjMCUWidth[TJ_NUMSAMP] = { 8, 16, 16, 8, 8, 32 }; + +/** + * MCU block height (in pixels) for a given level of chrominance subsampling. + * MCU block sizes: + * - 8x8 for no subsampling or grayscale + * - 16x8 for 4:2:2 + * - 8x16 for 4:4:0 + * - 16x16 for 4:2:0 + * - 32x8 for 4:1:1 + */ +static const int tjMCUHeight[TJ_NUMSAMP] = { 8, 8, 16, 8, 16, 8 }; + + +/** + * The number of pixel formats + */ +#define TJ_NUMPF 12 + +/** + * Pixel formats + */ +enum TJPF { + /** + * RGB pixel format. The red, green, and blue components in the image are + * stored in 3-byte pixels in the order R, G, B from lowest to highest byte + * address within each pixel. + */ + TJPF_RGB = 0, + /** + * BGR pixel format. The red, green, and blue components in the image are + * stored in 3-byte pixels in the order B, G, R from lowest to highest byte + * address within each pixel. + */ + TJPF_BGR, + /** + * RGBX pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order R, G, B from lowest to highest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_RGBX, + /** + * BGRX pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order B, G, R from lowest to highest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_BGRX, + /** + * XBGR pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order R, G, B from highest to lowest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_XBGR, + /** + * XRGB pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order B, G, R from highest to lowest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_XRGB, + /** + * Grayscale pixel format. Each 1-byte pixel represents a luminance + * (brightness) level from 0 to 255. + */ + TJPF_GRAY, + /** + * RGBA pixel format. This is the same as @ref TJPF_RGBX, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_RGBA, + /** + * BGRA pixel format. This is the same as @ref TJPF_BGRX, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_BGRA, + /** + * ABGR pixel format. This is the same as @ref TJPF_XBGR, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_ABGR, + /** + * ARGB pixel format. This is the same as @ref TJPF_XRGB, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_ARGB, + /** + * CMYK pixel format. Unlike RGB, which is an additive color model used + * primarily for display, CMYK (Cyan/Magenta/Yellow/Key) is a subtractive + * color model used primarily for printing. In the CMYK color model, the + * value of each color component typically corresponds to an amount of cyan, + * magenta, yellow, or black ink that is applied to a white background. In + * order to convert between CMYK and RGB, it is necessary to use a color + * management system (CMS.) A CMS will attempt to map colors within the + * printer's gamut to perceptually similar colors in the display's gamut and + * vice versa, but the mapping is typically not 1:1 or reversible, nor can it + * be defined with a simple formula. Thus, such a conversion is out of scope + * for a codec library. However, the TurboJPEG API allows for compressing + * CMYK pixels into a YCCK JPEG image (see #TJCS_YCCK) and decompressing YCCK + * JPEG images into CMYK pixels. + */ + TJPF_CMYK, + /** + * Unknown pixel format. Currently this is only used by #tjLoadImage(). + */ + TJPF_UNKNOWN = -1 +}; + +/** + * Red offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the red component is offset from the start of the pixel. For + * instance, if a pixel of format TJ_BGRX is stored in char pixel[], + * then the red component will be pixel[tjRedOffset[TJ_BGRX]]. This + * will be -1 if the pixel format does not have a red component. + */ +static const int tjRedOffset[TJ_NUMPF] = { + 0, 2, 0, 2, 3, 1, -1, 0, 2, 3, 1, -1 +}; +/** + * Green offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the green component is offset from the start of the pixel. + * For instance, if a pixel of format TJ_BGRX is stored in + * char pixel[], then the green component will be + * pixel[tjGreenOffset[TJ_BGRX]]. This will be -1 if the pixel format + * does not have a green component. + */ +static const int tjGreenOffset[TJ_NUMPF] = { + 1, 1, 1, 1, 2, 2, -1, 1, 1, 2, 2, -1 +}; +/** + * Blue offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the Blue component is offset from the start of the pixel. For + * instance, if a pixel of format TJ_BGRX is stored in char pixel[], + * then the blue component will be pixel[tjBlueOffset[TJ_BGRX]]. This + * will be -1 if the pixel format does not have a blue component. + */ +static const int tjBlueOffset[TJ_NUMPF] = { + 2, 0, 2, 0, 1, 3, -1, 2, 0, 1, 3, -1 +}; +/** + * Alpha offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the Alpha component is offset from the start of the pixel. + * For instance, if a pixel of format TJ_BGRA is stored in + * char pixel[], then the alpha component will be + * pixel[tjAlphaOffset[TJ_BGRA]]. This will be -1 if the pixel format + * does not have an alpha component. + */ +static const int tjAlphaOffset[TJ_NUMPF] = { + -1, -1, -1, -1, -1, -1, -1, 3, 3, 0, 0, -1 +}; +/** + * Pixel size (in bytes) for a given pixel format + */ +static const int tjPixelSize[TJ_NUMPF] = { + 3, 3, 4, 4, 4, 4, 1, 4, 4, 4, 4, 4 +}; + + +/** + * The number of JPEG colorspaces + */ +#define TJ_NUMCS 5 + +/** + * JPEG colorspaces + */ +enum TJCS { + /** + * RGB colorspace. When compressing the JPEG image, the R, G, and B + * components in the source image are reordered into image planes, but no + * colorspace conversion or subsampling is performed. RGB JPEG images can be + * decompressed to any of the extended RGB pixel formats or grayscale, but + * they cannot be decompressed to YUV images. + */ + TJCS_RGB = 0, + /** + * YCbCr colorspace. YCbCr is not an absolute colorspace but rather a + * mathematical transformation of RGB designed solely for storage and + * transmission. YCbCr images must be converted to RGB before they can + * actually be displayed. In the YCbCr colorspace, the Y (luminance) + * component represents the black & white portion of the original image, and + * the Cb and Cr (chrominance) components represent the color portion of the + * original image. Originally, the analog equivalent of this transformation + * allowed the same signal to drive both black & white and color televisions, + * but JPEG images use YCbCr primarily because it allows the color data to be + * optionally subsampled for the purposes of reducing bandwidth or disk + * space. YCbCr is the most common JPEG colorspace, and YCbCr JPEG images + * can be compressed from and decompressed to any of the extended RGB pixel + * formats or grayscale, or they can be decompressed to YUV planar images. + */ + TJCS_YCbCr, + /** + * Grayscale colorspace. The JPEG image retains only the luminance data (Y + * component), and any color data from the source image is discarded. + * Grayscale JPEG images can be compressed from and decompressed to any of + * the extended RGB pixel formats or grayscale, or they can be decompressed + * to YUV planar images. + */ + TJCS_GRAY, + /** + * CMYK colorspace. When compressing the JPEG image, the C, M, Y, and K + * components in the source image are reordered into image planes, but no + * colorspace conversion or subsampling is performed. CMYK JPEG images can + * only be decompressed to CMYK pixels. + */ + TJCS_CMYK, + /** + * YCCK colorspace. YCCK (AKA "YCbCrK") is not an absolute colorspace but + * rather a mathematical transformation of CMYK designed solely for storage + * and transmission. It is to CMYK as YCbCr is to RGB. CMYK pixels can be + * reversibly transformed into YCCK, and as with YCbCr, the chrominance + * components in the YCCK pixels can be subsampled without incurring major + * perceptual loss. YCCK JPEG images can only be compressed from and + * decompressed to CMYK pixels. + */ + TJCS_YCCK +}; + + +/** + * The uncompressed source/destination image is stored in bottom-up (Windows, + * OpenGL) order, not top-down (X11) order. + */ +#define TJFLAG_BOTTOMUP 2 +/** + * When decompressing an image that was compressed using chrominance + * subsampling, use the fastest chrominance upsampling algorithm available in + * the underlying codec. The default is to use smooth upsampling, which + * creates a smooth transition between neighboring chrominance components in + * order to reduce upsampling artifacts in the decompressed image. + */ +#define TJFLAG_FASTUPSAMPLE 256 +/** + * Disable buffer (re)allocation. If passed to one of the JPEG compression or + * transform functions, this flag will cause those functions to generate an + * error if the JPEG image buffer is invalid or too small rather than + * attempting to allocate or reallocate that buffer. This reproduces the + * behavior of earlier versions of TurboJPEG. + */ +#define TJFLAG_NOREALLOC 1024 +/** + * Use the fastest DCT/IDCT algorithm available in the underlying codec. The + * default if this flag is not specified is implementation-specific. For + * example, the implementation of TurboJPEG for libjpeg[-turbo] uses the fast + * algorithm by default when compressing, because this has been shown to have + * only a very slight effect on accuracy, but it uses the accurate algorithm + * when decompressing, because this has been shown to have a larger effect. + */ +#define TJFLAG_FASTDCT 2048 +/** + * Use the most accurate DCT/IDCT algorithm available in the underlying codec. + * The default if this flag is not specified is implementation-specific. For + * example, the implementation of TurboJPEG for libjpeg[-turbo] uses the fast + * algorithm by default when compressing, because this has been shown to have + * only a very slight effect on accuracy, but it uses the accurate algorithm + * when decompressing, because this has been shown to have a larger effect. + */ +#define TJFLAG_ACCURATEDCT 4096 +/** + * Immediately discontinue the current compression/decompression/transform + * operation if the underlying codec throws a warning (non-fatal error). The + * default behavior is to allow the operation to complete unless a fatal error + * is encountered. + */ +#define TJFLAG_STOPONWARNING 8192 +/** + * Use progressive entropy coding in JPEG images generated by the compression + * and transform functions. Progressive entropy coding will generally improve + * compression relative to baseline entropy coding (the default), but it will + * reduce compression and decompression performance considerably. + */ +#define TJFLAG_PROGRESSIVE 16384 + + +/** + * The number of error codes + */ +#define TJ_NUMERR 2 + +/** + * Error codes + */ +enum TJERR { + /** + * The error was non-fatal and recoverable, but the image may still be + * corrupt. + */ + TJERR_WARNING = 0, + /** + * The error was fatal and non-recoverable. + */ + TJERR_FATAL +}; + + +/** + * The number of transform operations + */ +#define TJ_NUMXOP 8 + +/** + * Transform operations for #tjTransform() + */ +enum TJXOP { + /** + * Do not transform the position of the image pixels + */ + TJXOP_NONE = 0, + /** + * Flip (mirror) image horizontally. This transform is imperfect if there + * are any partial MCU blocks on the right edge (see #TJXOPT_PERFECT.) + */ + TJXOP_HFLIP, + /** + * Flip (mirror) image vertically. This transform is imperfect if there are + * any partial MCU blocks on the bottom edge (see #TJXOPT_PERFECT.) + */ + TJXOP_VFLIP, + /** + * Transpose image (flip/mirror along upper left to lower right axis.) This + * transform is always perfect. + */ + TJXOP_TRANSPOSE, + /** + * Transverse transpose image (flip/mirror along upper right to lower left + * axis.) This transform is imperfect if there are any partial MCU blocks in + * the image (see #TJXOPT_PERFECT.) + */ + TJXOP_TRANSVERSE, + /** + * Rotate image clockwise by 90 degrees. This transform is imperfect if + * there are any partial MCU blocks on the bottom edge (see + * #TJXOPT_PERFECT.) + */ + TJXOP_ROT90, + /** + * Rotate image 180 degrees. This transform is imperfect if there are any + * partial MCU blocks in the image (see #TJXOPT_PERFECT.) + */ + TJXOP_ROT180, + /** + * Rotate image counter-clockwise by 90 degrees. This transform is imperfect + * if there are any partial MCU blocks on the right edge (see + * #TJXOPT_PERFECT.) + */ + TJXOP_ROT270 +}; + + +/** + * This option will cause #tjTransform() to return an error if the transform is + * not perfect. Lossless transforms operate on MCU blocks, whose size depends + * on the level of chrominance subsampling used (see #tjMCUWidth + * and #tjMCUHeight.) If the image's width or height is not evenly divisible + * by the MCU block size, then there will be partial MCU blocks on the right + * and/or bottom edges. It is not possible to move these partial MCU blocks to + * the top or left of the image, so any transform that would require that is + * "imperfect." If this option is not specified, then any partial MCU blocks + * that cannot be transformed will be left in place, which will create + * odd-looking strips on the right or bottom edge of the image. + */ +#define TJXOPT_PERFECT 1 +/** + * This option will cause #tjTransform() to discard any partial MCU blocks that + * cannot be transformed. + */ +#define TJXOPT_TRIM 2 +/** + * This option will enable lossless cropping. See #tjTransform() for more + * information. + */ +#define TJXOPT_CROP 4 +/** + * This option will discard the color data in the input image and produce + * a grayscale output image. + */ +#define TJXOPT_GRAY 8 +/** + * This option will prevent #tjTransform() from outputting a JPEG image for + * this particular transform (this can be used in conjunction with a custom + * filter to capture the transformed DCT coefficients without transcoding + * them.) + */ +#define TJXOPT_NOOUTPUT 16 +/** + * This option will enable progressive entropy coding in the output image + * generated by this particular transform. Progressive entropy coding will + * generally improve compression relative to baseline entropy coding (the + * default), but it will reduce compression and decompression performance + * considerably. + */ +#define TJXOPT_PROGRESSIVE 32 +/** + * This option will prevent #tjTransform() from copying any extra markers + * (including EXIF and ICC profile data) from the source image to the output + * image. + */ +#define TJXOPT_COPYNONE 64 + + +/** + * Scaling factor + */ +typedef struct { + /** + * Numerator + */ + int num; + /** + * Denominator + */ + int denom; +} tjscalingfactor; + +/** + * Cropping region + */ +typedef struct { + /** + * The left boundary of the cropping region. This must be evenly divisible + * by the MCU block width (see #tjMCUWidth.) + */ + int x; + /** + * The upper boundary of the cropping region. This must be evenly divisible + * by the MCU block height (see #tjMCUHeight.) + */ + int y; + /** + * The width of the cropping region. Setting this to 0 is the equivalent of + * setting it to the width of the source JPEG image - x. + */ + int w; + /** + * The height of the cropping region. Setting this to 0 is the equivalent of + * setting it to the height of the source JPEG image - y. + */ + int h; +} tjregion; + +/** + * Lossless transform + */ +typedef struct tjtransform { + /** + * Cropping region + */ + tjregion r; + /** + * One of the @ref TJXOP "transform operations" + */ + int op; + /** + * The bitwise OR of one of more of the @ref TJXOPT_CROP "transform options" + */ + int options; + /** + * Arbitrary data that can be accessed within the body of the callback + * function + */ + void *data; + /** + * A callback function that can be used to modify the DCT coefficients + * after they are losslessly transformed but before they are transcoded to a + * new JPEG image. This allows for custom filters or other transformations + * to be applied in the frequency domain. + * + * @param coeffs pointer to an array of transformed DCT coefficients. (NOTE: + * this pointer is not guaranteed to be valid once the callback returns, so + * applications wishing to hand off the DCT coefficients to another function + * or library should make a copy of them within the body of the callback.) + * + * @param arrayRegion #tjregion structure containing the width and height of + * the array pointed to by coeffs as well as its offset relative to + * the component plane. TurboJPEG implementations may choose to split each + * component plane into multiple DCT coefficient arrays and call the callback + * function once for each array. + * + * @param planeRegion #tjregion structure containing the width and height of + * the component plane to which coeffs belongs + * + * @param componentID ID number of the component plane to which + * coeffs belongs (Y, Cb, and Cr have, respectively, ID's of 0, 1, + * and 2 in typical JPEG images.) + * + * @param transformID ID number of the transformed image to which + * coeffs belongs. This is the same as the index of the transform + * in the transforms array that was passed to #tjTransform(). + * + * @param transform a pointer to a #tjtransform structure that specifies the + * parameters and/or cropping region for this transform + * + * @return 0 if the callback was successful, or -1 if an error occurred. + */ + int (*customFilter) (short *coeffs, tjregion arrayRegion, + tjregion planeRegion, int componentIndex, + int transformIndex, struct tjtransform *transform); +} tjtransform; + +/** + * TurboJPEG instance handle + */ +typedef void *tjhandle; + + +/** + * Pad the given width to the nearest 32-bit boundary + */ +#define TJPAD(width) (((width) + 3) & (~3)) + +/** + * Compute the scaled value of dimension using the given scaling + * factor. This macro performs the integer equivalent of ceil(dimension * + * scalingFactor). + */ +#define TJSCALED(dimension, scalingFactor) \ + ((dimension * scalingFactor.num + scalingFactor.denom - 1) / \ + scalingFactor.denom) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Create a TurboJPEG compressor instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr2().) + */ +DLLEXPORT tjhandle tjInitCompress(void); + + +/** + * Compress an RGB, grayscale, or CMYK image into a JPEG image. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * + * @param srcBuf pointer to an image buffer containing RGB, grayscale, or + * CMYK pixels to be compressed + * + * @param width width (in pixels) of the source image + * + * @param pitch bytes per line in the source image. Normally, this should be + * width * #tjPixelSize[pixelFormat] if the image is unpadded, or + * #TJPAD(width * #tjPixelSize[pixelFormat]) if each line of the image + * is padded to the nearest 32-bit boundary, as is the case for Windows + * bitmaps. You can also be clever and use this parameter to skip lines, etc. + * Setting this parameter to 0 is the equivalent of setting it to + * width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the source image + * + * @param pixelFormat pixel format of the source image (see @ref TJPF + * "Pixel formats".) + * + * @param jpegBuf address of a pointer to an image buffer that will receive the + * JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer + * to accommodate the size of the JPEG image. Thus, you can choose to: + * -# pre-allocate the JPEG buffer with an arbitrary size using #tjAlloc() and + * let TurboJPEG grow the buffer as needed, + * -# set *jpegBuf to NULL to tell TurboJPEG to allocate the buffer + * for you, or + * -# pre-allocate the buffer to a "worst case" size determined by calling + * #tjBufSize(). This should ensure that the buffer never has to be + * re-allocated (setting #TJFLAG_NOREALLOC guarantees that it won't be.) + * . + * If you choose option 1, *jpegSize should be set to the size of your + * pre-allocated buffer. In any case, unless you have set #TJFLAG_NOREALLOC, + * you should always check *jpegBuf upon return from this function, as + * it may have changed. + * + * @param jpegSize pointer to an unsigned long variable that holds the size of + * the JPEG image buffer. If *jpegBuf points to a pre-allocated + * buffer, then *jpegSize should be set to the size of the buffer. + * Upon return, *jpegSize will contain the size of the JPEG image (in + * bytes.) If *jpegBuf points to a JPEG image buffer that is being + * reused from a previous call to one of the JPEG compression functions, then + * *jpegSize is ignored. + * + * @param jpegSubsamp the level of chrominance subsampling to be used when + * generating the JPEG image (see @ref TJSAMP + * "Chrominance subsampling options".) + * + * @param jpegQual the image quality of the generated JPEG image (1 = worst, + * 100 = best) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjCompress2(tjhandle handle, const unsigned char *srcBuf, + int width, int pitch, int height, int pixelFormat, + unsigned char **jpegBuf, unsigned long *jpegSize, + int jpegSubsamp, int jpegQual, int flags); + + +/** + * Compress a YUV planar image into a JPEG image. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * + * @param srcBuf pointer to an image buffer containing a YUV planar image to be + * compressed. The size of this buffer should match the value returned by + * #tjBufSizeYUV2() for the given image width, height, padding, and level of + * chrominance subsampling. The Y, U (Cb), and V (Cr) image planes should be + * stored sequentially in the source buffer (refer to @ref YUVnotes + * "YUV Image Format Notes".) + * + * @param width width (in pixels) of the source image. If the width is not an + * even multiple of the MCU block width (see #tjMCUWidth), then an intermediate + * buffer copy will be performed within TurboJPEG. + * + * @param pad the line padding used in the source image. For instance, if each + * line in each plane of the YUV image is padded to the nearest multiple of 4 + * bytes, then pad should be set to 4. + * + * @param height height (in pixels) of the source image. If the height is not + * an even multiple of the MCU block height (see #tjMCUHeight), then an + * intermediate buffer copy will be performed within TurboJPEG. + * + * @param subsamp the level of chrominance subsampling used in the source + * image (see @ref TJSAMP "Chrominance subsampling options".) + * + * @param jpegBuf address of a pointer to an image buffer that will receive the + * JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer to + * accommodate the size of the JPEG image. Thus, you can choose to: + * -# pre-allocate the JPEG buffer with an arbitrary size using #tjAlloc() and + * let TurboJPEG grow the buffer as needed, + * -# set *jpegBuf to NULL to tell TurboJPEG to allocate the buffer + * for you, or + * -# pre-allocate the buffer to a "worst case" size determined by calling + * #tjBufSize(). This should ensure that the buffer never has to be + * re-allocated (setting #TJFLAG_NOREALLOC guarantees that it won't be.) + * . + * If you choose option 1, *jpegSize should be set to the size of your + * pre-allocated buffer. In any case, unless you have set #TJFLAG_NOREALLOC, + * you should always check *jpegBuf upon return from this function, as + * it may have changed. + * + * @param jpegSize pointer to an unsigned long variable that holds the size of + * the JPEG image buffer. If *jpegBuf points to a pre-allocated + * buffer, then *jpegSize should be set to the size of the buffer. + * Upon return, *jpegSize will contain the size of the JPEG image (in + * bytes.) If *jpegBuf points to a JPEG image buffer that is being + * reused from a previous call to one of the JPEG compression functions, then + * *jpegSize is ignored. + * + * @param jpegQual the image quality of the generated JPEG image (1 = worst, + * 100 = best) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjCompressFromYUV(tjhandle handle, const unsigned char *srcBuf, + int width, int pad, int height, int subsamp, + unsigned char **jpegBuf, + unsigned long *jpegSize, int jpegQual, + int flags); + + +/** + * Compress a set of Y, U (Cb), and V (Cr) image planes into a JPEG image. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * + * @param srcPlanes an array of pointers to Y, U (Cb), and V (Cr) image planes + * (or just a Y plane, if compressing a grayscale image) that contain a YUV + * image to be compressed. These planes can be contiguous or non-contiguous in + * memory. The size of each plane should match the value returned by + * #tjPlaneSizeYUV() for the given image width, height, strides, and level of + * chrominance subsampling. Refer to @ref YUVnotes "YUV Image Format Notes" + * for more details. + * + * @param width width (in pixels) of the source image. If the width is not an + * even multiple of the MCU block width (see #tjMCUWidth), then an intermediate + * buffer copy will be performed within TurboJPEG. + * + * @param strides an array of integers, each specifying the number of bytes per + * line in the corresponding plane of the YUV source image. Setting the stride + * for any plane to 0 is the same as setting it to the plane width (see + * @ref YUVnotes "YUV Image Format Notes".) If strides is NULL, then + * the strides for all planes will be set to their respective plane widths. + * You can adjust the strides in order to specify an arbitrary amount of line + * padding in each plane or to create a JPEG image from a subregion of a larger + * YUV planar image. + * + * @param height height (in pixels) of the source image. If the height is not + * an even multiple of the MCU block height (see #tjMCUHeight), then an + * intermediate buffer copy will be performed within TurboJPEG. + * + * @param subsamp the level of chrominance subsampling used in the source + * image (see @ref TJSAMP "Chrominance subsampling options".) + * + * @param jpegBuf address of a pointer to an image buffer that will receive the + * JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer to + * accommodate the size of the JPEG image. Thus, you can choose to: + * -# pre-allocate the JPEG buffer with an arbitrary size using #tjAlloc() and + * let TurboJPEG grow the buffer as needed, + * -# set *jpegBuf to NULL to tell TurboJPEG to allocate the buffer + * for you, or + * -# pre-allocate the buffer to a "worst case" size determined by calling + * #tjBufSize(). This should ensure that the buffer never has to be + * re-allocated (setting #TJFLAG_NOREALLOC guarantees that it won't be.) + * . + * If you choose option 1, *jpegSize should be set to the size of your + * pre-allocated buffer. In any case, unless you have set #TJFLAG_NOREALLOC, + * you should always check *jpegBuf upon return from this function, as + * it may have changed. + * + * @param jpegSize pointer to an unsigned long variable that holds the size of + * the JPEG image buffer. If *jpegBuf points to a pre-allocated + * buffer, then *jpegSize should be set to the size of the buffer. + * Upon return, *jpegSize will contain the size of the JPEG image (in + * bytes.) If *jpegBuf points to a JPEG image buffer that is being + * reused from a previous call to one of the JPEG compression functions, then + * *jpegSize is ignored. + * + * @param jpegQual the image quality of the generated JPEG image (1 = worst, + * 100 = best) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjCompressFromYUVPlanes(tjhandle handle, + const unsigned char **srcPlanes, + int width, const int *strides, + int height, int subsamp, + unsigned char **jpegBuf, + unsigned long *jpegSize, int jpegQual, + int flags); + + +/** + * The maximum size of the buffer (in bytes) required to hold a JPEG image with + * the given parameters. The number of bytes returned by this function is + * larger than the size of the uncompressed source image. The reason for this + * is that the JPEG format uses 16-bit coefficients, and it is thus possible + * for a very high-quality JPEG image with very high-frequency content to + * expand rather than compress when converted to the JPEG format. Such images + * represent a very rare corner case, but since there is no way to predict the + * size of a JPEG image prior to compression, the corner case has to be + * handled. + * + * @param width width (in pixels) of the image + * + * @param height height (in pixels) of the image + * + * @param jpegSubsamp the level of chrominance subsampling to be used when + * generating the JPEG image (see @ref TJSAMP + * "Chrominance subsampling options".) + * + * @return the maximum size of the buffer (in bytes) required to hold the + * image, or -1 if the arguments are out of bounds. + */ +DLLEXPORT unsigned long tjBufSize(int width, int height, int jpegSubsamp); + + +/** + * The size of the buffer (in bytes) required to hold a YUV planar image with + * the given parameters. + * + * @param width width (in pixels) of the image + * + * @param pad the width of each line in each plane of the image is padded to + * the nearest multiple of this number of bytes (must be a power of 2.) + * + * @param height height (in pixels) of the image + * + * @param subsamp level of chrominance subsampling in the image (see + * @ref TJSAMP "Chrominance subsampling options".) + * + * @return the size of the buffer (in bytes) required to hold the image, or + * -1 if the arguments are out of bounds. + */ +DLLEXPORT unsigned long tjBufSizeYUV2(int width, int pad, int height, + int subsamp); + + +/** + * The size of the buffer (in bytes) required to hold a YUV image plane with + * the given parameters. + * + * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb, 2 = V/Cr) + * + * @param width width (in pixels) of the YUV image. NOTE: this is the width of + * the whole image, not the plane width. + * + * @param stride bytes per line in the image plane. Setting this to 0 is the + * equivalent of setting it to the plane width. + * + * @param height height (in pixels) of the YUV image. NOTE: this is the height + * of the whole image, not the plane height. + * + * @param subsamp level of chrominance subsampling in the image (see + * @ref TJSAMP "Chrominance subsampling options".) + * + * @return the size of the buffer (in bytes) required to hold the YUV image + * plane, or -1 if the arguments are out of bounds. + */ +DLLEXPORT unsigned long tjPlaneSizeYUV(int componentID, int width, int stride, + int height, int subsamp); + + +/** + * The plane width of a YUV image plane with the given parameters. Refer to + * @ref YUVnotes "YUV Image Format Notes" for a description of plane width. + * + * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb, 2 = V/Cr) + * + * @param width width (in pixels) of the YUV image + * + * @param subsamp level of chrominance subsampling in the image (see + * @ref TJSAMP "Chrominance subsampling options".) + * + * @return the plane width of a YUV image plane with the given parameters, or + * -1 if the arguments are out of bounds. + */ +DLLEXPORT int tjPlaneWidth(int componentID, int width, int subsamp); + + +/** + * The plane height of a YUV image plane with the given parameters. Refer to + * @ref YUVnotes "YUV Image Format Notes" for a description of plane height. + * + * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb, 2 = V/Cr) + * + * @param height height (in pixels) of the YUV image + * + * @param subsamp level of chrominance subsampling in the image (see + * @ref TJSAMP "Chrominance subsampling options".) + * + * @return the plane height of a YUV image plane with the given parameters, or + * -1 if the arguments are out of bounds. + */ +DLLEXPORT int tjPlaneHeight(int componentID, int height, int subsamp); + + +/** + * Encode an RGB or grayscale image into a YUV planar image. This function + * uses the accelerated color conversion routines in the underlying + * codec but does not execute any of the other steps in the JPEG compression + * process. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * + * @param srcBuf pointer to an image buffer containing RGB or grayscale pixels + * to be encoded + * + * @param width width (in pixels) of the source image + * + * @param pitch bytes per line in the source image. Normally, this should be + * width * #tjPixelSize[pixelFormat] if the image is unpadded, or + * #TJPAD(width * #tjPixelSize[pixelFormat]) if each line of the image + * is padded to the nearest 32-bit boundary, as is the case for Windows + * bitmaps. You can also be clever and use this parameter to skip lines, etc. + * Setting this parameter to 0 is the equivalent of setting it to + * width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the source image + * + * @param pixelFormat pixel format of the source image (see @ref TJPF + * "Pixel formats".) + * + * @param dstBuf pointer to an image buffer that will receive the YUV image. + * Use #tjBufSizeYUV2() to determine the appropriate size for this buffer based + * on the image width, height, padding, and level of chrominance subsampling. + * The Y, U (Cb), and V (Cr) image planes will be stored sequentially in the + * buffer (refer to @ref YUVnotes "YUV Image Format Notes".) + * + * @param pad the width of each line in each plane of the YUV image will be + * padded to the nearest multiple of this number of bytes (must be a power of + * 2.) To generate images suitable for X Video, pad should be set to + * 4. + * + * @param subsamp the level of chrominance subsampling to be used when + * generating the YUV image (see @ref TJSAMP + * "Chrominance subsampling options".) To generate images suitable for X + * Video, subsamp should be set to @ref TJSAMP_420. This produces an + * image compatible with the I420 (AKA "YUV420P") format. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjEncodeYUV3(tjhandle handle, const unsigned char *srcBuf, + int width, int pitch, int height, int pixelFormat, + unsigned char *dstBuf, int pad, int subsamp, + int flags); + + +/** + * Encode an RGB or grayscale image into separate Y, U (Cb), and V (Cr) image + * planes. This function uses the accelerated color conversion routines in the + * underlying codec but does not execute any of the other steps in the JPEG + * compression process. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * + * @param srcBuf pointer to an image buffer containing RGB or grayscale pixels + * to be encoded + * + * @param width width (in pixels) of the source image + * + * @param pitch bytes per line in the source image. Normally, this should be + * width * #tjPixelSize[pixelFormat] if the image is unpadded, or + * #TJPAD(width * #tjPixelSize[pixelFormat]) if each line of the image + * is padded to the nearest 32-bit boundary, as is the case for Windows + * bitmaps. You can also be clever and use this parameter to skip lines, etc. + * Setting this parameter to 0 is the equivalent of setting it to + * width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the source image + * + * @param pixelFormat pixel format of the source image (see @ref TJPF + * "Pixel formats".) + * + * @param dstPlanes an array of pointers to Y, U (Cb), and V (Cr) image planes + * (or just a Y plane, if generating a grayscale image) that will receive the + * encoded image. These planes can be contiguous or non-contiguous in memory. + * Use #tjPlaneSizeYUV() to determine the appropriate size for each plane based + * on the image width, height, strides, and level of chrominance subsampling. + * Refer to @ref YUVnotes "YUV Image Format Notes" for more details. + * + * @param strides an array of integers, each specifying the number of bytes per + * line in the corresponding plane of the output image. Setting the stride for + * any plane to 0 is the same as setting it to the plane width (see + * @ref YUVnotes "YUV Image Format Notes".) If strides is NULL, then + * the strides for all planes will be set to their respective plane widths. + * You can adjust the strides in order to add an arbitrary amount of line + * padding to each plane or to encode an RGB or grayscale image into a + * subregion of a larger YUV planar image. + * + * @param subsamp the level of chrominance subsampling to be used when + * generating the YUV image (see @ref TJSAMP + * "Chrominance subsampling options".) To generate images suitable for X + * Video, subsamp should be set to @ref TJSAMP_420. This produces an + * image compatible with the I420 (AKA "YUV420P") format. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjEncodeYUVPlanes(tjhandle handle, const unsigned char *srcBuf, + int width, int pitch, int height, + int pixelFormat, unsigned char **dstPlanes, + int *strides, int subsamp, int flags); + + +/** + * Create a TurboJPEG decompressor instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr2().) +*/ +DLLEXPORT tjhandle tjInitDecompress(void); + + +/** + * Retrieve information about a JPEG image without decompressing it. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param jpegBuf pointer to a buffer containing a JPEG image + * + * @param jpegSize size of the JPEG image (in bytes) + * + * @param width pointer to an integer variable that will receive the width (in + * pixels) of the JPEG image + * + * @param height pointer to an integer variable that will receive the height + * (in pixels) of the JPEG image + * + * @param jpegSubsamp pointer to an integer variable that will receive the + * level of chrominance subsampling used when the JPEG image was compressed + * (see @ref TJSAMP "Chrominance subsampling options".) + * + * @param jpegColorspace pointer to an integer variable that will receive one + * of the JPEG colorspace constants, indicating the colorspace of the JPEG + * image (see @ref TJCS "JPEG colorspaces".) + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) +*/ +DLLEXPORT int tjDecompressHeader3(tjhandle handle, + const unsigned char *jpegBuf, + unsigned long jpegSize, int *width, + int *height, int *jpegSubsamp, + int *jpegColorspace); + + +/** + * Returns a list of fractional scaling factors that the JPEG decompressor in + * this implementation of TurboJPEG supports. + * + * @param numscalingfactors pointer to an integer variable that will receive + * the number of elements in the list + * + * @return a pointer to a list of fractional scaling factors, or NULL if an + * error is encountered (see #tjGetErrorStr2().) +*/ +DLLEXPORT tjscalingfactor *tjGetScalingFactors(int *numscalingfactors); + + +/** + * Decompress a JPEG image to an RGB, grayscale, or CMYK image. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param jpegBuf pointer to a buffer containing the JPEG image to decompress + * + * @param jpegSize size of the JPEG image (in bytes) + * + * @param dstBuf pointer to an image buffer that will receive the decompressed + * image. This buffer should normally be pitch * scaledHeight bytes + * in size, where scaledHeight can be determined by calling + * #TJSCALED() with the JPEG image height and one of the scaling factors + * returned by #tjGetScalingFactors(). The dstBuf pointer may also be + * used to decompress into a specific region of a larger buffer. + * + * @param width desired width (in pixels) of the destination image. If this is + * different than the width of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired width. If width is + * set to 0, then only the height will be considered when determining the + * scaled image size. + * + * @param pitch bytes per line in the destination image. Normally, this is + * scaledWidth * #tjPixelSize[pixelFormat] if the decompressed image + * is unpadded, else #TJPAD(scaledWidth * #tjPixelSize[pixelFormat]) + * if each line of the decompressed image is padded to the nearest 32-bit + * boundary, as is the case for Windows bitmaps. (NOTE: scaledWidth + * can be determined by calling #TJSCALED() with the JPEG image width and one + * of the scaling factors returned by #tjGetScalingFactors().) You can also be + * clever and use the pitch parameter to skip lines, etc. Setting this + * parameter to 0 is the equivalent of setting it to + * scaledWidth * #tjPixelSize[pixelFormat]. + * + * @param height desired height (in pixels) of the destination image. If this + * is different than the height of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired height. If height + * is set to 0, then only the width will be considered when determining the + * scaled image size. + * + * @param pixelFormat pixel format of the destination image (see @ref + * TJPF "Pixel formats".) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjDecompress2(tjhandle handle, const unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, + int width, int pitch, int height, int pixelFormat, + int flags); + + +/** + * Decompress a JPEG image to a YUV planar image. This function performs JPEG + * decompression but leaves out the color conversion step, so a planar YUV + * image is generated instead of an RGB image. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param jpegBuf pointer to a buffer containing the JPEG image to decompress + * + * @param jpegSize size of the JPEG image (in bytes) + * + * @param dstBuf pointer to an image buffer that will receive the YUV image. + * Use #tjBufSizeYUV2() to determine the appropriate size for this buffer based + * on the image width, height, padding, and level of subsampling. The Y, + * U (Cb), and V (Cr) image planes will be stored sequentially in the buffer + * (refer to @ref YUVnotes "YUV Image Format Notes".) + * + * @param width desired width (in pixels) of the YUV image. If this is + * different than the width of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired width. If width is + * set to 0, then only the height will be considered when determining the + * scaled image size. If the scaled width is not an even multiple of the MCU + * block width (see #tjMCUWidth), then an intermediate buffer copy will be + * performed within TurboJPEG. + * + * @param pad the width of each line in each plane of the YUV image will be + * padded to the nearest multiple of this number of bytes (must be a power of + * 2.) To generate images suitable for X Video, pad should be set to + * 4. + * + * @param height desired height (in pixels) of the YUV image. If this is + * different than the height of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired height. If height + * is set to 0, then only the width will be considered when determining the + * scaled image size. If the scaled height is not an even multiple of the MCU + * block height (see #tjMCUHeight), then an intermediate buffer copy will be + * performed within TurboJPEG. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjDecompressToYUV2(tjhandle handle, const unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, + int width, int pad, int height, int flags); + + +/** + * Decompress a JPEG image into separate Y, U (Cb), and V (Cr) image + * planes. This function performs JPEG decompression but leaves out the color + * conversion step, so a planar YUV image is generated instead of an RGB image. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param jpegBuf pointer to a buffer containing the JPEG image to decompress + * + * @param jpegSize size of the JPEG image (in bytes) + * + * @param dstPlanes an array of pointers to Y, U (Cb), and V (Cr) image planes + * (or just a Y plane, if decompressing a grayscale image) that will receive + * the YUV image. These planes can be contiguous or non-contiguous in memory. + * Use #tjPlaneSizeYUV() to determine the appropriate size for each plane based + * on the scaled image width, scaled image height, strides, and level of + * chrominance subsampling. Refer to @ref YUVnotes "YUV Image Format Notes" + * for more details. + * + * @param width desired width (in pixels) of the YUV image. If this is + * different than the width of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired width. If width is + * set to 0, then only the height will be considered when determining the + * scaled image size. If the scaled width is not an even multiple of the MCU + * block width (see #tjMCUWidth), then an intermediate buffer copy will be + * performed within TurboJPEG. + * + * @param strides an array of integers, each specifying the number of bytes per + * line in the corresponding plane of the output image. Setting the stride for + * any plane to 0 is the same as setting it to the scaled plane width (see + * @ref YUVnotes "YUV Image Format Notes".) If strides is NULL, then + * the strides for all planes will be set to their respective scaled plane + * widths. You can adjust the strides in order to add an arbitrary amount of + * line padding to each plane or to decompress the JPEG image into a subregion + * of a larger YUV planar image. + * + * @param height desired height (in pixels) of the YUV image. If this is + * different than the height of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the largest + * possible image that will fit within the desired height. If height + * is set to 0, then only the width will be considered when determining the + * scaled image size. If the scaled height is not an even multiple of the MCU + * block height (see #tjMCUHeight), then an intermediate buffer copy will be + * performed within TurboJPEG. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjDecompressToYUVPlanes(tjhandle handle, + const unsigned char *jpegBuf, + unsigned long jpegSize, + unsigned char **dstPlanes, int width, + int *strides, int height, int flags); + + +/** + * Decode a YUV planar image into an RGB or grayscale image. This function + * uses the accelerated color conversion routines in the underlying + * codec but does not execute any of the other steps in the JPEG decompression + * process. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param srcBuf pointer to an image buffer containing a YUV planar image to be + * decoded. The size of this buffer should match the value returned by + * #tjBufSizeYUV2() for the given image width, height, padding, and level of + * chrominance subsampling. The Y, U (Cb), and V (Cr) image planes should be + * stored sequentially in the source buffer (refer to @ref YUVnotes + * "YUV Image Format Notes".) + * + * @param pad Use this parameter to specify that the width of each line in each + * plane of the YUV source image is padded to the nearest multiple of this + * number of bytes (must be a power of 2.) + * + * @param subsamp the level of chrominance subsampling used in the YUV source + * image (see @ref TJSAMP "Chrominance subsampling options".) + * + * @param dstBuf pointer to an image buffer that will receive the decoded + * image. This buffer should normally be pitch * height bytes in + * size, but the dstBuf pointer can also be used to decode into a + * specific region of a larger buffer. + * + * @param width width (in pixels) of the source and destination images + * + * @param pitch bytes per line in the destination image. Normally, this should + * be width * #tjPixelSize[pixelFormat] if the destination image is + * unpadded, or #TJPAD(width * #tjPixelSize[pixelFormat]) if each line + * of the destination image should be padded to the nearest 32-bit boundary, as + * is the case for Windows bitmaps. You can also be clever and use the pitch + * parameter to skip lines, etc. Setting this parameter to 0 is the equivalent + * of setting it to width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the source and destination images + * + * @param pixelFormat pixel format of the destination image (see @ref TJPF + * "Pixel formats".) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjDecodeYUV(tjhandle handle, const unsigned char *srcBuf, + int pad, int subsamp, unsigned char *dstBuf, + int width, int pitch, int height, int pixelFormat, + int flags); + + +/** + * Decode a set of Y, U (Cb), and V (Cr) image planes into an RGB or grayscale + * image. This function uses the accelerated color conversion routines in the + * underlying codec but does not execute any of the other steps in the JPEG + * decompression process. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * + * @param srcPlanes an array of pointers to Y, U (Cb), and V (Cr) image planes + * (or just a Y plane, if decoding a grayscale image) that contain a YUV image + * to be decoded. These planes can be contiguous or non-contiguous in memory. + * The size of each plane should match the value returned by #tjPlaneSizeYUV() + * for the given image width, height, strides, and level of chrominance + * subsampling. Refer to @ref YUVnotes "YUV Image Format Notes" for more + * details. + * + * @param strides an array of integers, each specifying the number of bytes per + * line in the corresponding plane of the YUV source image. Setting the stride + * for any plane to 0 is the same as setting it to the plane width (see + * @ref YUVnotes "YUV Image Format Notes".) If strides is NULL, then + * the strides for all planes will be set to their respective plane widths. + * You can adjust the strides in order to specify an arbitrary amount of line + * padding in each plane or to decode a subregion of a larger YUV planar image. + * + * @param subsamp the level of chrominance subsampling used in the YUV source + * image (see @ref TJSAMP "Chrominance subsampling options".) + * + * @param dstBuf pointer to an image buffer that will receive the decoded + * image. This buffer should normally be pitch * height bytes in + * size, but the dstBuf pointer can also be used to decode into a + * specific region of a larger buffer. + * + * @param width width (in pixels) of the source and destination images + * + * @param pitch bytes per line in the destination image. Normally, this should + * be width * #tjPixelSize[pixelFormat] if the destination image is + * unpadded, or #TJPAD(width * #tjPixelSize[pixelFormat]) if each line + * of the destination image should be padded to the nearest 32-bit boundary, as + * is the case for Windows bitmaps. You can also be clever and use the pitch + * parameter to skip lines, etc. Setting this parameter to 0 is the equivalent + * of setting it to width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the source and destination images + * + * @param pixelFormat pixel format of the destination image (see @ref TJPF + * "Pixel formats".) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjDecodeYUVPlanes(tjhandle handle, + const unsigned char **srcPlanes, + const int *strides, int subsamp, + unsigned char *dstBuf, int width, int pitch, + int height, int pixelFormat, int flags); + + +/** + * Create a new TurboJPEG transformer instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr2().) + */ +DLLEXPORT tjhandle tjInitTransform(void); + + +/** + * Losslessly transform a JPEG image into another JPEG image. Lossless + * transforms work by moving the raw DCT coefficients from one JPEG image + * structure to another without altering the values of the coefficients. While + * this is typically faster than decompressing the image, transforming it, and + * re-compressing it, lossless transforms are not free. Each lossless + * transform requires reading and performing Huffman decoding on all of the + * coefficients in the source image, regardless of the size of the destination + * image. Thus, this function provides a means of generating multiple + * transformed images from the same source or applying multiple + * transformations simultaneously, in order to eliminate the need to read the + * source coefficients multiple times. + * + * @param handle a handle to a TurboJPEG transformer instance + * + * @param jpegBuf pointer to a buffer containing the JPEG source image to + * transform + * + * @param jpegSize size of the JPEG source image (in bytes) + * + * @param n the number of transformed JPEG images to generate + * + * @param dstBufs pointer to an array of n image buffers. dstBufs[i] + * will receive a JPEG image that has been transformed using the parameters in + * transforms[i]. TurboJPEG has the ability to reallocate the JPEG + * buffer to accommodate the size of the JPEG image. Thus, you can choose to: + * -# pre-allocate the JPEG buffer with an arbitrary size using #tjAlloc() and + * let TurboJPEG grow the buffer as needed, + * -# set dstBufs[i] to NULL to tell TurboJPEG to allocate the buffer + * for you, or + * -# pre-allocate the buffer to a "worst case" size determined by calling + * #tjBufSize() with the transformed or cropped width and height. Under normal + * circumstances, this should ensure that the buffer never has to be + * re-allocated (setting #TJFLAG_NOREALLOC guarantees that it won't be.) Note, + * however, that there are some rare cases (such as transforming images with a + * large amount of embedded EXIF or ICC profile data) in which the output image + * will be larger than the worst-case size, and #TJFLAG_NOREALLOC cannot be + * used in those cases. + * . + * If you choose option 1, dstSizes[i] should be set to the size of + * your pre-allocated buffer. In any case, unless you have set + * #TJFLAG_NOREALLOC, you should always check dstBufs[i] upon return + * from this function, as it may have changed. + * + * @param dstSizes pointer to an array of n unsigned long variables that will + * receive the actual sizes (in bytes) of each transformed JPEG image. If + * dstBufs[i] points to a pre-allocated buffer, then + * dstSizes[i] should be set to the size of the buffer. Upon return, + * dstSizes[i] will contain the size of the JPEG image (in bytes.) + * + * @param transforms pointer to an array of n #tjtransform structures, each of + * which specifies the transform parameters and/or cropping region for the + * corresponding transformed output image. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_ACCURATEDCT + * "flags" + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2() + * and #tjGetErrorCode().) + */ +DLLEXPORT int tjTransform(tjhandle handle, const unsigned char *jpegBuf, + unsigned long jpegSize, int n, + unsigned char **dstBufs, unsigned long *dstSizes, + tjtransform *transforms, int flags); + + +/** + * Destroy a TurboJPEG compressor, decompressor, or transformer instance. + * + * @param handle a handle to a TurboJPEG compressor, decompressor or + * transformer instance + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2().) + */ +DLLEXPORT int tjDestroy(tjhandle handle); + + +/** + * Allocate an image buffer for use with TurboJPEG. You should always use + * this function to allocate the JPEG destination buffer(s) for the compression + * and transform functions unless you are disabling automatic buffer + * (re)allocation (by setting #TJFLAG_NOREALLOC.) + * + * @param bytes the number of bytes to allocate + * + * @return a pointer to a newly-allocated buffer with the specified number of + * bytes. + * + * @sa tjFree() + */ +DLLEXPORT unsigned char *tjAlloc(int bytes); + + +/** + * Load an uncompressed image from disk into memory. + * + * @param filename name of a file containing an uncompressed image in Windows + * BMP or PBMPLUS (PPM/PGM) format + * + * @param width pointer to an integer variable that will receive the width (in + * pixels) of the uncompressed image + * + * @param align row alignment of the image buffer to be returned (must be a + * power of 2.) For instance, setting this parameter to 4 will cause all rows + * in the image buffer to be padded to the nearest 32-bit boundary, and setting + * this parameter to 1 will cause all rows in the image buffer to be unpadded. + * + * @param height pointer to an integer variable that will receive the height + * (in pixels) of the uncompressed image + * + * @param pixelFormat pointer to an integer variable that specifies or will + * receive the pixel format of the uncompressed image buffer. The behavior of + * #tjLoadImage() will vary depending on the value of *pixelFormat + * passed to the function: + * - @ref TJPF_UNKNOWN : The uncompressed image buffer returned by the function + * will use the most optimal pixel format for the file type, and + * *pixelFormat will contain the ID of this pixel format upon + * successful return from the function. + * - @ref TJPF_GRAY : Only PGM files and 8-bit BMP files with a grayscale + * colormap can be loaded. + * - @ref TJPF_CMYK : The RGB or grayscale pixels stored in the file will be + * converted using a quick & dirty algorithm that is suitable only for testing + * purposes (proper conversion between CMYK and other formats requires a color + * management system.) + * - Other @ref TJPF "pixel formats" : The uncompressed image buffer will use + * the specified pixel format, and pixel format conversion will be performed if + * necessary. + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP + * "flags". + * + * @return a pointer to a newly-allocated buffer containing the uncompressed + * image, converted to the chosen pixel format and with the chosen row + * alignment, or NULL if an error occurred (see #tjGetErrorStr2().) This + * buffer should be freed using #tjFree(). + */ +DLLEXPORT unsigned char *tjLoadImage(const char *filename, int *width, + int align, int *height, int *pixelFormat, + int flags); + + +/** + * Save an uncompressed image from memory to disk. + * + * @param filename name of a file to which to save the uncompressed image. + * The image will be stored in Windows BMP or PBMPLUS (PPM/PGM) format, + * depending on the file extension. + * + * @param buffer pointer to an image buffer containing RGB, grayscale, or + * CMYK pixels to be saved + * + * @param width width (in pixels) of the uncompressed image + * + * @param pitch bytes per line in the image buffer. Setting this parameter to + * 0 is the equivalent of setting it to + * width * #tjPixelSize[pixelFormat]. + * + * @param height height (in pixels) of the uncompressed image + * + * @param pixelFormat pixel format of the image buffer (see @ref TJPF + * "Pixel formats".) If this parameter is set to @ref TJPF_GRAY, then the + * image will be stored in PGM or 8-bit (indexed color) BMP format. Otherwise, + * the image will be stored in PPM or 24-bit BMP format. If this parameter + * is set to @ref TJPF_CMYK, then the CMYK pixels will be converted to RGB + * using a quick & dirty algorithm that is suitable only for testing (proper + * conversion between CMYK and other formats requires a color management + * system.) + * + * @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP + * "flags". + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr2().) + */ +DLLEXPORT int tjSaveImage(const char *filename, unsigned char *buffer, + int width, int pitch, int height, int pixelFormat, + int flags); + + +/** + * Free an image buffer previously allocated by TurboJPEG. You should always + * use this function to free JPEG destination buffer(s) that were automatically + * (re)allocated by the compression and transform functions or that were + * manually allocated using #tjAlloc(). + * + * @param buffer address of the buffer to free + * + * @sa tjAlloc() + */ +DLLEXPORT void tjFree(unsigned char *buffer); + + +/** + * Returns a descriptive error message explaining why the last command failed. + * + * @param handle a handle to a TurboJPEG compressor, decompressor, or + * transformer instance, or NULL if the error was generated by a global + * function (but note that retrieving the error message for a global function + * is not thread-safe.) + * + * @return a descriptive error message explaining why the last command failed. + */ +DLLEXPORT char *tjGetErrorStr2(tjhandle handle); + + +/** + * Returns a code indicating the severity of the last error. See + * @ref TJERR "Error codes". + * + * @param handle a handle to a TurboJPEG compressor, decompressor or + * transformer instance + * + * @return a code indicating the severity of the last error. See + * @ref TJERR "Error codes". + */ +DLLEXPORT int tjGetErrorCode(tjhandle handle); + + +/* Deprecated functions and macros */ +#define TJFLAG_FORCEMMX 8 +#define TJFLAG_FORCESSE 16 +#define TJFLAG_FORCESSE2 32 +#define TJFLAG_FORCESSE3 128 + + +/* Backward compatibility functions and macros (nothing to see here) */ +#define NUMSUBOPT TJ_NUMSAMP +#define TJ_444 TJSAMP_444 +#define TJ_422 TJSAMP_422 +#define TJ_420 TJSAMP_420 +#define TJ_411 TJSAMP_420 +#define TJ_GRAYSCALE TJSAMP_GRAY + +#define TJ_BGR 1 +#define TJ_BOTTOMUP TJFLAG_BOTTOMUP +#define TJ_FORCEMMX TJFLAG_FORCEMMX +#define TJ_FORCESSE TJFLAG_FORCESSE +#define TJ_FORCESSE2 TJFLAG_FORCESSE2 +#define TJ_ALPHAFIRST 64 +#define TJ_FORCESSE3 TJFLAG_FORCESSE3 +#define TJ_FASTUPSAMPLE TJFLAG_FASTUPSAMPLE +#define TJ_YUV 512 + +DLLEXPORT unsigned long TJBUFSIZE(int width, int height); + +DLLEXPORT unsigned long TJBUFSIZEYUV(int width, int height, int jpegSubsamp); + +DLLEXPORT unsigned long tjBufSizeYUV(int width, int height, int subsamp); + +DLLEXPORT int tjCompress(tjhandle handle, unsigned char *srcBuf, int width, + int pitch, int height, int pixelSize, + unsigned char *dstBuf, unsigned long *compressedSize, + int jpegSubsamp, int jpegQual, int flags); + +DLLEXPORT int tjEncodeYUV(tjhandle handle, unsigned char *srcBuf, int width, + int pitch, int height, int pixelSize, + unsigned char *dstBuf, int subsamp, int flags); + +DLLEXPORT int tjEncodeYUV2(tjhandle handle, unsigned char *srcBuf, int width, + int pitch, int height, int pixelFormat, + unsigned char *dstBuf, int subsamp, int flags); + +DLLEXPORT int tjDecompressHeader(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, int *width, + int *height); + +DLLEXPORT int tjDecompressHeader2(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, int *width, + int *height, int *jpegSubsamp); + +DLLEXPORT int tjDecompress(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, + int width, int pitch, int height, int pixelSize, + int flags); + +DLLEXPORT int tjDecompressToYUV(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, + int flags); + +DLLEXPORT char *tjGetErrorStr(void); + + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram-Mac/libwebp/include/webp/decode.h b/thrid-party/libwebp/include/webp/decode.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/decode.h rename to thrid-party/libwebp/include/webp/decode.h diff --git a/Telegram-Mac/libwebp/include/webp/demux.h b/thrid-party/libwebp/include/webp/demux.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/demux.h rename to thrid-party/libwebp/include/webp/demux.h diff --git a/Telegram-Mac/libwebp/include/webp/encode.h b/thrid-party/libwebp/include/webp/encode.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/encode.h rename to thrid-party/libwebp/include/webp/encode.h diff --git a/Telegram-Mac/libwebp/include/webp/mux.h b/thrid-party/libwebp/include/webp/mux.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/mux.h rename to thrid-party/libwebp/include/webp/mux.h diff --git a/Telegram-Mac/libwebp/include/webp/mux_types.h b/thrid-party/libwebp/include/webp/mux_types.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/mux_types.h rename to thrid-party/libwebp/include/webp/mux_types.h diff --git a/Telegram-Mac/libwebp/include/webp/types.h b/thrid-party/libwebp/include/webp/types.h similarity index 100% rename from Telegram-Mac/libwebp/include/webp/types.h rename to thrid-party/libwebp/include/webp/types.h diff --git a/Telegram-Mac/libwebp/lib/libwebp.a b/thrid-party/libwebp/lib/libwebp.a similarity index 100% rename from Telegram-Mac/libwebp/lib/libwebp.a rename to thrid-party/libwebp/lib/libwebp.a diff --git a/Telegram-Mac/libwebp/lib/libwebpdemux.a b/thrid-party/libwebp/lib/libwebpdemux.a similarity index 100% rename from Telegram-Mac/libwebp/lib/libwebpdemux.a rename to thrid-party/libwebp/lib/libwebpdemux.a diff --git a/Telegram-Mac/libwebp/lib/libwebpmux.a b/thrid-party/libwebp/lib/libwebpmux.a similarity index 100% rename from Telegram-Mac/libwebp/lib/libwebpmux.a rename to thrid-party/libwebp/lib/libwebpmux.a diff --git a/thrid-party/objc/BuildConfig.h b/thrid-party/objc/BuildConfig.h new file mode 100644 index 0000000000..577177c4fa --- /dev/null +++ b/thrid-party/objc/BuildConfig.h @@ -0,0 +1,26 @@ +#import + +@interface DeviceSpecificEncryptionParameters : NSObject + +@property (nonatomic, strong) NSData * _Nonnull key; +@property (nonatomic, strong) NSData * _Nonnull salt; + +@end + +@interface LocalPrivateKey : NSObject { + SecKeyRef _privateKey; + SecKeyRef _publicKey; +} + +- (NSData * _Nullable)encrypt:(NSData * _Nonnull)data; +- (NSData * _Nullable)decrypt:(NSData * _Nonnull)data cancelled:(bool *)cancelled; + +@end + +@interface BuildConfig : NSObject + ++ (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId; + ++ (LocalPrivateKey * _Nullable)createApplicationSecretKey; + +@end diff --git a/thrid-party/objc/BuildConfig.m b/thrid-party/objc/BuildConfig.m new file mode 100644 index 0000000000..a967d7f7e5 --- /dev/null +++ b/thrid-party/objc/BuildConfig.m @@ -0,0 +1,340 @@ +#import "BuildConfig.h" + +#import +#import + + + + +static NSString *telegramApplicationSecretKey = @"telegramApplicationSecretKey_v7"; + + +@implementation LocalPrivateKey + +- (instancetype _Nonnull)initWithPrivateKey:(SecKeyRef)privateKey publicKey:(SecKeyRef)publicKey { + self = [super init]; + if (self != nil) { + _privateKey = (SecKeyRef)CFRetain(privateKey); + _publicKey = (SecKeyRef)CFRetain(publicKey); + } + return self; +} + +- (void)dealloc { + CFRelease(_privateKey); + CFRelease(_publicKey); +} + +- (NSData * _Nullable)getPublicKey { + NSData *result = CFBridgingRelease(SecKeyCopyExternalRepresentation(_publicKey, nil)); + return result; +} + +- (NSData * _Nullable)encrypt:(NSData * _Nonnull)data { + if (data.length % 16 != 0) { + return nil; + } + + CFErrorRef error = NULL; + NSData *cipherText = (NSData *)CFBridgingRelease(SecKeyCreateEncryptedData(_publicKey, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM, (__bridge CFDataRef)data, &error)); + + if (!cipherText) { + __unused NSError *err = CFBridgingRelease(error); + return nil; + } + + return cipherText; +} + +- (NSData * _Nullable)decrypt:(NSData * _Nonnull)data cancelled:(bool *)cancelled { + CFErrorRef error = NULL; + NSData *plainText = (NSData *)CFBridgingRelease(SecKeyCreateDecryptedData(_privateKey, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM, (__bridge CFDataRef)data, &error)); + + if (!plainText) { + __unused NSError *err = CFBridgingRelease(error); + if (err.code == -2) { + if (cancelled) { + *cancelled = true; + } + } + return nil; + } + + return plainText; +} + +@end + + +@interface BuildConfig () { + NSData * _Nullable _bundleData; + int32_t _apiId; + NSString * _Nonnull _apiHash; + NSString * _Nullable _hockeyAppId; +} + +@end + +@implementation DeviceSpecificEncryptionParameters + +- (instancetype)initWithKey:(NSData * _Nonnull)key salt:(NSData * _Nonnull)salt { + self = [super init]; + if (self != nil) { + _key = key; + _salt = salt; + } + return self; +} + +@end + +@implementation BuildConfig ++ (NSString *)bundleId { + NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: + (__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass, + @"bundleSeedID", kSecAttrAccount, + @"", kSecAttrService, + (id)kCFBooleanTrue, kSecReturnAttributes, + nil]; + CFDictionaryRef result = nil; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + if (status == errSecItemNotFound) { + status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + } + if (status != errSecSuccess) { + return nil; + } + NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup]; + NSArray *components = [accessGroup componentsSeparatedByString:@"."]; + NSString *bundleSeedID = [[components objectEnumerator] nextObject]; + CFRelease(result); + return @"6N38VWS5BX"; +} + + + ++ (NSString * _Nullable)bundleSeedId { + return @"6N38VWS5BX"; +} + ++ (NSData * _Nonnull)applicationSecretTag:(bool)isCheckKey { + if (isCheckKey) { + return [[telegramApplicationSecretKey stringByAppendingString:@"_check"] dataUsingEncoding:NSUTF8StringEncoding]; + } else { + return [telegramApplicationSecretKey dataUsingEncoding:NSUTF8StringEncoding]; + } +} + ++ (LocalPrivateKey * _Nullable)getApplicationSecretKey:(NSString * _Nonnull)baseAppBundleId isCheckKey:(bool)isCheckKey { + NSString *bundleSeedId = [self bundleSeedId]; + if (bundleSeedId == nil) { + return nil; + } + + NSData *applicationTag = [self applicationSecretTag:isCheckKey]; + NSString *accessGroup = bundleSeedId;//[bundleSeedId stringByAppendingFormat:@".%@", baseAppBundleId]; + + NSDictionary *query = @{ + (id)kSecClass: (id)kSecClassKey, + (id)kSecAttrApplicationTag: applicationTag, + (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom, + // (id)kSecAttrAccessGroup: (id)accessGroup, + (id)kSecReturnRef: @YES + }; + SecKeyRef privateKey = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey); + if (status != errSecSuccess) { + return nil; + } + + SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); + if (!publicKey) { + if (privateKey) { + CFRelease(privateKey); + } + return nil; + } + + LocalPrivateKey *result = [[LocalPrivateKey alloc] initWithPrivateKey:privateKey publicKey:publicKey]; + + if (publicKey) { + CFRelease(publicKey); + } + if (privateKey) { + CFRelease(privateKey); + } + + return result; +} + ++ (bool)removeApplicationSecretKey:(NSString * _Nonnull)baseAppBundleId isCheckKey:(bool)isCheckKey { + NSString *bundleSeedId = [self bundleSeedId]; + if (bundleSeedId == nil) { + return nil; + } + + NSData *applicationTag = [self applicationSecretTag:isCheckKey]; + NSString *accessGroup = [bundleSeedId stringByAppendingFormat:@".%@", baseAppBundleId]; + + NSDictionary *query = @{ + (id)kSecClass: (id)kSecClassKey, + (id)kSecAttrApplicationTag: applicationTag, + (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom, + // (id)kSecAttrAccessGroup: (id)accessGroup + }; + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + if (status != errSecSuccess) { + return false; + } + return true; +} + ++ (LocalPrivateKey * _Nullable)createApplicationSecretKey { + + NSDictionary *attributes = @{ + (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom, + (id)kSecAttrKeySizeInBits: @256, + (id)kSecPrivateKeyAttrs: @{ + (id)kSecAttrIsPermanent: @YES, + }, + }; + + CFErrorRef error = NULL; + SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error); + if (!privateKey) { + + __unused NSError *err = CFBridgingRelease(error); + return nil; + } + + SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); + if (!publicKey) { + if (privateKey) { + CFRelease(privateKey); + } + + __unused NSError *err = CFBridgingRelease(error); + return nil; + } + LocalPrivateKey *result = [[LocalPrivateKey alloc] initWithPrivateKey:privateKey publicKey:publicKey]; + + if (publicKey) { + CFRelease(publicKey); + } + if (privateKey) { + CFRelease(privateKey); + } + return result; +} + ++ (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId { + CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); + + NSString *filePath = [rootPath stringByAppendingPathComponent:@".tempkey"]; + NSString *encryptedPath = [rootPath stringByAppendingPathComponent:@".tempkeyEncrypted"]; + + NSData *currentData = [NSData dataWithContentsOfFile:filePath]; + NSData *resultData = nil; + if (currentData != nil && currentData.length == 32 + 16) { + resultData = currentData; + } + if (resultData == nil) { + NSMutableData *randomData = [[NSMutableData alloc] initWithLength:32 + 16]; + int result = SecRandomCopyBytes(kSecRandomDefault, randomData.length, [randomData mutableBytes]); + if (currentData != nil && currentData.length == 32) { // upgrade key with salt + [currentData getBytes:randomData.mutableBytes length:32]; + } + resultData = randomData; + [resultData writeToFile:filePath atomically:false]; + } + + /*if (@available(iOS 11, *)) { + NSData *currentEncryptedData = [NSData dataWithContentsOfFile:encryptedPath]; + + LocalPrivateKey *localPrivateKey = [self getLocalPrivateKey:baseAppBundleId]; + + if (localPrivateKey == nil) { + localPrivateKey = [self addLocalPrivateKey:baseAppBundleId]; + } + + if (localPrivateKey != nil) { + if (currentEncryptedData != nil) { + NSData *decryptedData = [localPrivateKey decrypt:currentEncryptedData]; + + if (![resultData isEqualToData:decryptedData]) { + NSData *encryptedData = [localPrivateKey encrypt:resultData]; + [encryptedData writeToFile:encryptedPath atomically:false]; + //assert(false); + } + } else { + NSData *encryptedData = [localPrivateKey encrypt:resultData]; + [encryptedData writeToFile:encryptedPath atomically:false]; + } + } + }*/ + + CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); + NSLog(@"deviceSpecificEncryptionParameters took %f ms", (endTime - startTime) * 1000.0); + + NSData *key = [resultData subdataWithRange:NSMakeRange(0, 32)]; + NSData *salt = [resultData subdataWithRange:NSMakeRange(32, 16)]; + return [[DeviceSpecificEncryptionParameters alloc] initWithKey:key salt:salt]; +} + ++ (dispatch_queue_t)encryptionQueue { + static dispatch_queue_t instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = dispatch_queue_create("encryptionQueue", 0); + }); + return instance; +} +// +// +//+ (void)encryptApplicationSecret:(NSData * _Nonnull)secret baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable, NSData * _Nullable))completion { +// dispatch_async([self encryptionQueue], ^{ +// LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false]; +// if (privateKey == nil) { +// [self removeApplicationSecretKey:baseAppBundleId isCheckKey:false]; +// [self removeApplicationSecretKey:baseAppBundleId isCheckKey:true]; +// privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:false]; +// privateKey = [self addApplicationSecretKey:baseAppBundleId isCheckKey:true]; +// } +// if (privateKey == nil) { +// completion(nil, nil); +// return; +// } +// NSData *result = [privateKey encrypt:secret]; +// completion(result, [privateKey getPublicKey]); +// }); +//} +// +//+ (void)decryptApplicationSecret:(NSData * _Nonnull)secret publicKey:(NSData * _Nonnull)publicKey baseAppBundleId:(NSString * _Nonnull)baseAppBundleId completion:(void (^)(NSData * _Nullable))completion { +// dispatch_async([self encryptionQueue], ^{ +// LocalPrivateKey *privateKey = [self getApplicationSecretKey:baseAppBundleId isCheckKey:false]; +// if (privateKey == nil) { +// completion(nil); +// return; +// } +// if (privateKey == nil) { +// completion(nil); +// return; +// } +// NSData *currentPublicKey = [privateKey getPublicKey]; +// if (currentPublicKey == nil) { +// completion(nil); +// return; +// } +// if (![publicKey isEqualToData:currentPublicKey]) { +// completion(nil); +// return; +// } +// NSData *result = [privateKey decrypt:secret cancelled:nil]; +// completion(result); +// }); +//} +// +// + + +@end diff --git a/thrid-party/objc/FFMpegGlobals.h b/thrid-party/objc/FFMpegGlobals.h new file mode 100644 index 0000000000..500eb82b9c --- /dev/null +++ b/thrid-party/objc/FFMpegGlobals.h @@ -0,0 +1,19 @@ +// +// NSObject+FFMpegGlobals.h +// Telegram +// +// Created by Mikhail Filimonov on 17/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FFMpegGlobals : NSObject + ++ (void)initializeGlobals; + +@end + +NS_ASSUME_NONNULL_END diff --git a/thrid-party/objc/FFMpegGlobals.m b/thrid-party/objc/FFMpegGlobals.m new file mode 100644 index 0000000000..a1aa15cb56 --- /dev/null +++ b/thrid-party/objc/FFMpegGlobals.m @@ -0,0 +1,20 @@ +// +// NSObject+FFMpegGlobals.m +// Telegram +// +// Created by Mikhail Filimonov on 17/02/2019. +// Copyright © 2019 Telegram. All rights reserved. +// + +#import "FFMpegGlobals.h" +#import "libavformat/avformat.h" + + + +@implementation FFMpegGlobals + ++ (void)initializeGlobals { + av_register_all(); +} + +@end diff --git a/thrid-party/objc/FFMpegRemuxer.h b/thrid-party/objc/FFMpegRemuxer.h new file mode 100644 index 0000000000..76c519608c --- /dev/null +++ b/thrid-party/objc/FFMpegRemuxer.h @@ -0,0 +1,11 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FFMpegRemuxer : NSObject + ++ (bool)remux:(NSString * _Nonnull)path to:(NSString * _Nonnull)outPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/thrid-party/objc/FFMpegRemuxer.m b/thrid-party/objc/FFMpegRemuxer.m new file mode 100644 index 0000000000..f7fce3b73b --- /dev/null +++ b/thrid-party/objc/FFMpegRemuxer.m @@ -0,0 +1,138 @@ +#import "FFMpegRemuxer.h" + +#include "libavutil/timestamp.h" +#include "libavformat/avformat.h" +#include "libavcodec/avcodec.h" + +@implementation FFMpegRemuxer + ++ (bool)remux:(NSString * _Nonnull)path to:(NSString * _Nonnull)outPath { + + AVFormatContext *input_format_context = NULL, *output_format_context = NULL; + AVPacket packet; + const char *in_filename, *out_filename; + int ret, i; + int stream_index = 0; + int *streams_list = NULL; + int number_of_streams = 0; + int fragmented_mp4_options = 1; + + in_filename = [path UTF8String]; + out_filename = [outPath UTF8String]; + + if ((ret = avformat_open_input(&input_format_context, in_filename, av_find_input_format("mov"), NULL)) < 0) { + fprintf(stderr, "Could not open input file '%s'", in_filename); + goto end; + } + if ((ret = avformat_find_stream_info(input_format_context, NULL)) < 0) { + fprintf(stderr, "Failed to retrieve input stream information"); + goto end; + } + + avformat_alloc_output_context2(&output_format_context, NULL, NULL, out_filename); + if (!output_format_context) { + fprintf(stderr, "Could not create output context\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + + number_of_streams = input_format_context->nb_streams; + streams_list = av_mallocz_array(number_of_streams, sizeof(*streams_list)); + + if (!streams_list) { + ret = AVERROR(ENOMEM); + goto end; + } + + for (i = 0; i < input_format_context->nb_streams; i++) { + AVStream *out_stream; + AVStream *in_stream = input_format_context->streams[i]; + AVCodecParameters *in_codecpar = in_stream->codecpar; + if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && + in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + streams_list[i] = -1; + continue; + } + streams_list[i] = stream_index++; + out_stream = avformat_new_stream(output_format_context, NULL); + if (!out_stream) { + fprintf(stderr, "Failed allocating output stream\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar); + if (ret < 0) { + fprintf(stderr, "Failed to copy codec parameters\n"); + goto end; + } + } + // https://ffmpeg.org/doxygen/trunk/group__lavf__misc.html#gae2645941f2dc779c307eb6314fd39f10 + //av_dump_format(output_format_context, 0, out_filename, 1); + + // unless it's a no file (we'll talk later about that) write to the disk (FLAG_WRITE) + // but basically it's a way to save the file to a buffer so you can store it + // wherever you want. + if (!(output_format_context->oformat->flags & AVFMT_NOFILE)) { + ret = avio_open(&output_format_context->pb, out_filename, AVIO_FLAG_WRITE); + if (ret < 0) { + fprintf(stderr, "Could not open output file '%s'", out_filename); + goto end; + } + } + AVDictionary* opts = NULL; + + if (fragmented_mp4_options) { + // https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API/Transcoding_assets_for_MSE + av_dict_set(&opts, "movflags", "dash+faststart+global_sidx+skip_trailer", 0); + } + // https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga18b7b10bb5b94c4842de18166bc677cb + ret = avformat_write_header(output_format_context, &opts); + if (ret < 0) { + fprintf(stderr, "Error occurred when opening output file\n"); + goto end; + } + while (1) { + AVStream *in_stream, *out_stream; + ret = av_read_frame(input_format_context, &packet); + if (ret < 0) + break; + in_stream = input_format_context->streams[packet.stream_index]; + if (packet.stream_index >= number_of_streams || streams_list[packet.stream_index] < 0) { + av_packet_unref(&packet); + continue; + } + packet.stream_index = streams_list[packet.stream_index]; + out_stream = output_format_context->streams[packet.stream_index]; + /* copy packet */ + packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base); + // https://ffmpeg.org/doxygen/trunk/structAVPacket.html#ab5793d8195cf4789dfb3913b7a693903 + packet.pos = -1; + + //https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga37352ed2c63493c38219d935e71db6c1 + ret = av_interleaved_write_frame(output_format_context, &packet); + if (ret < 0) { + fprintf(stderr, "Error muxing packet\n"); + break; + } + av_packet_unref(&packet); + } + //https://ffmpeg.org/doxygen/trunk/group__lavf__encoding.html#ga7f14007e7dc8f481f054b21614dfec13 + av_write_trailer(output_format_context); +end: + avformat_close_input(&input_format_context); + /* close output */ + if (output_format_context && !(output_format_context->oformat->flags & AVFMT_NOFILE)) + avio_closep(&output_format_context->pb); + avformat_free_context(output_format_context); + av_freep(&streams_list); + if (ret < 0 && ret != AVERROR_EOF) { + fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); + return false; + } + return true; +} + +@end diff --git a/thrid-party/objc/GZip.h b/thrid-party/objc/GZip.h new file mode 100644 index 0000000000..ff33d2fb2f --- /dev/null +++ b/thrid-party/objc/GZip.h @@ -0,0 +1,17 @@ +#ifndef Telegram_GZip_h +#define Telegram_GZip_h + +#import + +#ifdef __cplusplus +extern "C" { +#endif + + NSData *TGGZipData(NSData * __nonnull data, float level); + NSData * _Nullable TGGUnzipData(NSData *data, uint sizeLimit); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/objc/GZip.m b/thrid-party/objc/GZip.m new file mode 100644 index 0000000000..fac7fdb666 --- /dev/null +++ b/thrid-party/objc/GZip.m @@ -0,0 +1,87 @@ +#import "GZip.h" + + +#import + +bool TGIsGzippedData(NSData *data) { + const UInt8 *bytes = (const UInt8 *)data.bytes; + return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); +} + +NSData *TGGZipData(NSData *data, float level) { + if (data.length == 0 || TGIsGzippedData(data)) { + return data; + } + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = (uint)data.length; + stream.next_in = (Bytef *)(void *)data.bytes; + stream.total_out = 0; + stream.avail_out = 0; + + static const NSUInteger ChunkSize = 16384; + + NSMutableData *output = nil; + int compression = (level < 0.0f) ? Z_DEFAULT_COMPRESSION : (int)(roundf(level * 9)); + if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) { + output = [NSMutableData dataWithLength:ChunkSize]; + while (stream.avail_out == 0) { + if (stream.total_out >= output.length) { + output.length += ChunkSize; + } + stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out; + stream.avail_out = (uInt)(output.length - stream.total_out); + deflate(&stream, Z_FINISH); + } + deflateEnd(&stream); + output.length = stream.total_out; + } + + return output; +} + +NSData * _Nullable TGGUnzipData(NSData *data, uint sizeLimit) +{ + if (data.length == 0 || !TGIsGzippedData(data)) { + return nil; + } + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.avail_in = (uint)data.length; + stream.next_in = (Bytef *)data.bytes; + stream.total_out = 0; + stream.avail_out = 0; + + NSMutableData *output = nil; + if (inflateInit2(&stream, 47) == Z_OK) { + int status = Z_OK; + output = [NSMutableData dataWithCapacity:data.length * 2]; + while (status == Z_OK) { + if (sizeLimit > 0 && stream.total_out > sizeLimit) { + return nil; + } + + if (stream.total_out >= output.length) { + output.length = output.length + data.length / 2; + } + stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out; + stream.avail_out = (uInt)(output.length - stream.total_out); + status = inflate(&stream, Z_SYNC_FLUSH); + } + if (inflateEnd(&stream) == Z_OK) { + if (status == Z_STREAM_END) { + output.length = stream.total_out; + } else if (sizeLimit > 0 && output.length > sizeLimit) { + return nil; + } + } + } + + return output; +} + diff --git a/thrid-party/objc/HackUtils.h b/thrid-party/objc/HackUtils.h new file mode 100644 index 0000000000..cbc69bf859 --- /dev/null +++ b/thrid-party/objc/HackUtils.h @@ -0,0 +1,16 @@ +// +// HackUtils.h +// Messenger for Telegram +// +// Created by Dmitry Kondratyev on 3/25/14. +// Copyright (c) 2014 keepcoder. All rights reserved. +// + +#import + +@interface HackUtils : NSObject + ++ (NSArray *)findElementsByClass:(NSString *)className inView:(NSView *)view; ++ (void)printViews:(NSView *)containerView; +@end + diff --git a/thrid-party/objc/HackUtils.m b/thrid-party/objc/HackUtils.m new file mode 100644 index 0000000000..2451aa2b63 --- /dev/null +++ b/thrid-party/objc/HackUtils.m @@ -0,0 +1,58 @@ +// +// HackUtils.m +// Messenger for Telegram +// +// Created by Dmitry Kondratyev on 3/25/14. +// Copyright (c) 2014 keepcoder. All rights reserved. +// + +#import "HackUtils.h" +#import +#import +@implementation HackUtils + ++ (NSArray *)findElementsByClass:(NSString *)className inView:(NSView *)view { +// [self printViews:view]; + NSArray *array = [self findElementsByClass:className inView:view array:nil]; + return array; +} + ++ (NSArray *)findElementsByClass:(NSString *)className inView:(NSView *)view array:(NSMutableArray *)array { + if(!array) + array = [[NSMutableArray alloc] init]; + + for (NSView *viewC in view.subviews) { + +// MTLog(@"viewC.className %@ %@", viewC.className, className); + + if([viewC.className isEqualToString:className]) { + [array addObject:viewC]; + } + + if([viewC respondsToSelector:@selector(subviews)]) { + [self findElementsByClass:className inView:viewC array:array]; + } + } + return array; +} + + + ++ (void)printViews:(NSView *)containerView { + [self printViews:containerView j:0]; +} + ++ (void)printViews:(NSView *)containerView j:(int)j { + for (id c in containerView.subviews) { + NSString *lol = @""; + for(int i = 0; i < j; i++) { + lol = [lol stringByAppendingString:@" "]; + } + NSLog(@"%@ %@", lol, NSStringFromClass([c class])); + if([c respondsToSelector:@selector(subviews)]) { + [self printViews:c j:j + 1]; + } + } +} + +@end diff --git a/thrid-party/objc/MIHSliderView.h b/thrid-party/objc/MIHSliderView.h deleted file mode 100755 index 31cc4295da..0000000000 --- a/thrid-party/objc/MIHSliderView.h +++ /dev/null @@ -1,152 +0,0 @@ -// MIHSliderView.h -// -// Copyright (c) 2013 Michael Hohl. 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. - -#import -#import - -/** - `MIHSliderView` is a Core Animation based slider view to display a couple of views each after another. - Sliders are well known from web development were they are often used to display images or news. - With `MIHSliderView` it is possible to display any NSView inside a slider. - - For more information about MIHSliderView have a look at http://github.com/hohl/MIHSliderView - */ - -typedef enum { - MIHSliderTransitionFade, - MIHSliderTransitionPushVertical, - MIHSliderTransitionPushHorizontalFromLeft, - MIHSliderTransitionPushHorizontalFromRight -} MIHSliderTransition; - -@class MIHSliderDotsControl; - -@interface MIHSliderView : NSView - -///---------------------- -/// @name Managing Slides -///---------------------- - -/** - Array of all slides which the view displays. Use `addSlide:` and `removeSlide:` to manage the slides. - */ -@property (retain, readonly) NSArray *slides; - -/** - Adds a slide which should get displayed by the view. - - @param aSlide the slide to add at the last index - */ -- (void)addSlide:(NSView *)aSlide; - -/** - Removes the passed slide from the slider. - - @param aSlide the slide to remove from the slider - */ -- (void)removeSlide:(NSView *)aSlide; - -///-------------------------------- -/// @name Controlling Slider Output -///-------------------------------- - -/** - Index of the current displayed slide. -To change the displayed slide use `displaySlideAtIndex:`. - */ -@property (assign, readonly) NSUInteger indexOfDisplayedSlide; - -/** - The slide which is displayed at the moment. - To change the displayed slide use `displaySlideAtIndex:`. - */ -@property (retain, readonly) NSView *displayedSlide; - -/** - Displays a specific slide. - - @param aIndex the index of the slide which should get displayed. - */ -- (void)displaySlideAtIndex:(NSUInteger)aIndex; - -///---------------------------------- -/// @name Slide Transition Properties -///---------------------------------- - -/** - Defines the animiation of the transition between the slides. - */ -@property (assign) MIHSliderTransition transitionStyle; - -/** - If this property is set to YES slides will automatically switch to the next slide after a short time. - - @discussion This property is set to YES per default. - */ -@property (assign) BOOL scheduledTransition; - -/** - If this property is set to YES slide 1 will get displayed after the last slide. Otherwise scheduled transitions will - get stopped after the last transition. - - @discussion This property is set to YES per default. - */ -@property (assign) BOOL repeatingScheduledTransition; - -/** - Defines the number of seconds a slide should get displayed before transiting to the next slide. - - @discussion This property is set to 5.0 seconds per default. - */ -@property (assign) NSTimeInterval scheduledTransitionInterval; - -/** - Defines the duration of the transition animation. - - @discussion This property is set to 0.6 seconds per default. - */ -@property (assign) NSTimeInterval transitionAnimationDuration; - -///----------------------------- -/// @name Look & Feel Properties -///----------------------------- - -/** - Container view for the dots. Via this class you are able to customize the look of the dots. - */ -@property (retain) MIHSliderDotsControl *dotsControl; - -@end - -@interface MIHSliderDotsControl : NSView - -/** - Image which is used to display a normal (not highligted) dot. - */ -@property (retain) NSImage *normalDotImage; - -/** - Image which is used to display a highlighted (means the current one) dot. - */ -@property (retain) NSImage *highlightedDotImage; - -@end diff --git a/thrid-party/objc/MIHSliderView.m b/thrid-party/objc/MIHSliderView.m deleted file mode 100755 index d3b3cdfb75..0000000000 --- a/thrid-party/objc/MIHSliderView.m +++ /dev/null @@ -1,359 +0,0 @@ -// MIHSliderView.m -// -// Copyright (c) 2013 Michael Hohl. 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. - -#import "MIHSliderView.h" - -const NSTimeInterval kDefaultScheduledTransitionTimeInterval = 4.0; -const NSTimeInterval kDefaultTransitionAnimationDuration = 0.6; - -@interface MIHSliderView () // private API -@property (assign) NSUInteger indexOfDisplayedSlide; -@property (retain) NSView *displayedSlide; -@property (retain) NSTimer *transitionTimer; -@property (retain) NSView *contentView; -@property (assign) CGFloat scrollDeltaX; -@property (assign) CGFloat scrollDeltaY; -- (void)_prepareView; -- (void)_prepareTransitionTimer; -- (void)_prepareTransitionToIndex:(NSUInteger)index; -- (void)_handleTimerTick:(id)userInfo; -- (void)_dotViewSelected:(NSImageView *)dotView; -@end - -@interface MIHSliderDotsControl () // private API -@property (nonatomic, assign) NSUInteger dotsCount; -@property (nonatomic, assign) NSUInteger indexOfHighlightedDot; -@property (assign) MIHSliderView *sliderView; -@end - -@implementation MIHSliderView { - NSMutableArray *_slides; - NSTimeInterval _scheduledTransitionInterval; -} - -- (id)init -{ - self = [super init]; - if (self) { - [self _prepareView]; - _slides = [[NSMutableArray alloc] init]; - _transitionStyle = MIHSliderTransitionFade; - _scheduledTransitionInterval = kDefaultScheduledTransitionTimeInterval; - _transitionAnimationDuration = kDefaultTransitionAnimationDuration; - _repeatingScheduledTransition = YES; - self.scheduledTransition = YES; - } - return self; -} - -- (id)initWithFrame:(NSRect)frameRect -{ - self = [super initWithFrame:frameRect]; - if (self) { - [self _prepareView]; - _slides = [[NSMutableArray alloc] init]; - _transitionStyle = MIHSliderTransitionFade; - _scheduledTransitionInterval = kDefaultScheduledTransitionTimeInterval; - _transitionAnimationDuration = kDefaultTransitionAnimationDuration; - _repeatingScheduledTransition = YES; - self.scheduledTransition = YES; - } - return self; -} - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (self) { - [self _prepareView]; - _slides = [[NSMutableArray alloc] initWithArray:[aDecoder decodeObjectForKey:@"_slides"]]; - self.indexOfDisplayedSlide = [aDecoder decodeIntegerForKey:@"indexOfDisplayedSlide"]; - _transitionStyle = [aDecoder decodeIntForKey:@"_transitionStyle"]; - _scheduledTransitionInterval = [aDecoder decodeDoubleForKey:@"_scheduledTransitionInterval"]; - _transitionAnimationDuration = [aDecoder decodeDoubleForKey:@"_transitionAnimationDuration"]; - _repeatingScheduledTransition = [aDecoder decodeBoolForKey:@"_repeatingScheduledTransition"]; - _dotsControl = [aDecoder decodeObjectForKey:@"_dotsControl"]; - self.scheduledTransition = [aDecoder decodeBoolForKey:@"scheduledTransition"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_slides forKey:@"_slides"]; - [aCoder encodeInteger:self.indexOfDisplayedSlide forKey:@"indexOfDisplayedSlide"]; - [aCoder encodeInt:_transitionStyle forKey:@"_transitionStyle"]; - [aCoder encodeDouble:_scheduledTransitionInterval forKey:@"_scheduledTransitionInterval"]; - [aCoder encodeDouble:_transitionAnimationDuration forKey:@"_transitionAnimationDuration"]; - [aCoder encodeBool:_repeatingScheduledTransition forKey:@"_repeatingScheduledTransition"]; - [aCoder encodeObject:_dotsControl forKey:@"_dotsControl"]; - [aCoder encodeBool:self.scheduledTransition forKey:@"scheduledTransition"]; - [super encodeWithCoder:aCoder]; -} - -#pragma mark - - -- (void)addSlide:(NSView *)aSlide -{ - [_slides addObject:aSlide]; - if ([_slides count] == 1) { - [self displaySlideAtIndex:0]; - } - self.dotsControl.dotsCount = self.slides.count; -} - -- (void)removeSlide:(NSView *)aSlide -{ - [_slides removeObject:aSlide]; - if (self.indexOfDisplayedSlide >= [_slides count]) { - self.indexOfDisplayedSlide = 0; - } - self.dotsControl.dotsCount = self.slides.count; -} - -#pragma mark - - -- (void)displaySlideAtIndex:(NSUInteger)aIndex -{ - self.dotsControl.indexOfHighlightedDot = aIndex; - - NSView *slideToDisplay = [self.slides objectAtIndex:aIndex]; - if (slideToDisplay == self.displayedSlide) return; - slideToDisplay.frame = self.bounds; - [slideToDisplay setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - - if (self.displayedSlide == nil) { - [self.contentView addSubview:slideToDisplay]; - self.displayedSlide = slideToDisplay; - self.indexOfDisplayedSlide = aIndex; - return; - } - - [NSAnimationContext beginGrouping]; - [self _prepareTransitionToIndex:aIndex]; - [[self.contentView animator] replaceSubview:self.displayedSlide with:slideToDisplay]; - [NSAnimationContext endGrouping]; - - self.displayedSlide = slideToDisplay; - self.indexOfDisplayedSlide = aIndex; -} - -- (void)setScheduledTransition:(BOOL)scheduledTransition -{ - if (scheduledTransition) { - [self _prepareTransitionTimer]; - } else { - [self.transitionTimer invalidate]; - self.transitionTimer = nil; - } -} - -- (BOOL)scheduledTransition -{ - return (self.transitionTimer != nil); -} - -- (void)setScheduledTransitionInterval:(NSTimeInterval)scheduledTransitionInterval -{ - _scheduledTransitionInterval = scheduledTransitionInterval; - if (self.scheduledTransition) { - [self _prepareTransitionTimer]; - } -} - -- (NSTimeInterval)scheduledTransitionInterval -{ - return _scheduledTransitionInterval; -} - -- (void)scrollWheel:(NSEvent *)theEvent { - if (theEvent.phase == NSEventPhaseBegan) { - self.scrollDeltaX = 0; - self.scrollDeltaY = 0; - } else if (theEvent.phase == NSEventPhaseChanged) { - self.scrollDeltaX += theEvent.scrollingDeltaX; - self.scrollDeltaY += theEvent.scrollingDeltaY; - if (self.scrollDeltaY == 0) { - [super scrollWheel:theEvent]; - } - } else if (theEvent.phase == NSEventPhaseEnded) { - if (self.scrollDeltaX > 50) { - self.transitionStyle = MIHSliderTransitionPushHorizontalFromLeft; - [self displaySlideAtIndex:(self.indexOfDisplayedSlide - 1) % self.slides.count]; - if (self.scheduledTransition) { - [self _prepareTransitionTimer]; - } - } else if (self.scrollDeltaX < - 50) { - self.transitionStyle = MIHSliderTransitionPushHorizontalFromRight; - [self displaySlideAtIndex:(self.indexOfDisplayedSlide + 1) % self.slides.count]; - if (self.scheduledTransition) { - [self _prepareTransitionTimer]; - } - } - } else if (theEvent.phase == NSEventPhaseCancelled) { - self.scrollDeltaX = 0; - self.scrollDeltaY = 0; - } else { - [super scrollWheel:theEvent]; - } -} - -#pragma mark - - -- (void)_prepareView -{ - [self setAcceptsTouchEvents:YES]; - self.contentView = [[NSView alloc] initWithFrame:self.bounds]; - self.contentView.wantsLayer = YES; - [self addSubview:self.contentView]; - _dotsControl = [[MIHSliderDotsControl alloc] init]; - _dotsControl.sliderView = self; - _dotsControl.normalDotImage = [NSImage imageNamed:@"Icon_SliderNormal"]; - _dotsControl.highlightedDotImage = [NSImage imageNamed:@"Icon_SliderHighlighted"]; - _dotsControl.wantsLayer = YES; - [self addSubview:_dotsControl positioned:NSWindowAbove relativeTo:self.contentView]; -} - -- (void)_handleTimerTick:(id)userInfo -{ - if (self.slides.count > 0 && (_repeatingScheduledTransition || self.indexOfDisplayedSlide + 1 < self.slides.count)) { - [self displaySlideAtIndex:(self.indexOfDisplayedSlide + 1) % self.slides.count]; - } -} - - -- (void)_dotViewSelected:(NSView *)dotView -{ - if (self.scheduledTransition) { - [self _prepareTransitionTimer]; // <- this will restart the timer - } - if (dotView.tag >= 0 && dotView.tag < self.slides.count) { - NSLog(@"dotView, %ld crrut View:%ld", dotView.tag, self.dotsControl.indexOfHighlightedDot); - - self.transitionStyle = dotView.tag > self.dotsControl.indexOfHighlightedDot ? MIHSliderTransitionPushHorizontalFromRight: MIHSliderTransitionPushHorizontalFromLeft; - - [self displaySlideAtIndex:dotView.tag]; - } -} - -- (void)_prepareTransitionToIndex:(NSUInteger)aIndex -{ - const CFTimeInterval duration = self.transitionAnimationDuration * ([self.window currentEvent].modifierFlags & NSShiftKeyMask) ? 1.5 : 0.3; - CATransition *transition = [CATransition animation]; - [transition setDuration:duration]; - [transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; - switch (self.transitionStyle) { - case MIHSliderTransitionFade: - [transition setType:kCATransitionFade]; - break; - - case MIHSliderTransitionPushHorizontalFromLeft: - [transition setType:kCATransitionPush]; - //[transition setSubtype:(self.indexOfDisplayedSlide < aIndex ? kCATransitionFromRight : kCATransitionFromLeft)]; - [transition setSubtype:kCATransitionFromLeft]; - break; - case MIHSliderTransitionPushHorizontalFromRight: - [transition setType:kCATransitionPush]; - //[transition setSubtype:(self.indexOfDisplayedSlide < aIndex ? kCATransitionFromRight : kCATransitionFromLeft)]; - [transition setSubtype:kCATransitionFromRight]; - break; - case MIHSliderTransitionPushVertical: - [transition setType:kCATransitionMoveIn]; - //[transition setSubtype:(self.indexOfDisplayedSlide < aIndex ? kCATransitionFromTop : kCATransitionFromBottom)]; - [transition setSubtype:kCATransitionFromTop]; - break; - } - [self.contentView setAnimations:[NSDictionary dictionaryWithObject:transition forKey:@"subviews"]]; -} - -- (void)_prepareTransitionTimer -{ - [self.transitionTimer invalidate]; - self.transitionTimer = [NSTimer scheduledTimerWithTimeInterval:self.scheduledTransitionInterval - target:self - selector:@selector(_handleTimerTick:) - userInfo:nil - repeats:YES]; -} - -@end - -const CGFloat kDotImageSize = 15.0; -const CGFloat kSpaceBetweenDotCenters = 22.0; -const CGFloat kDotContainerY = 8.0; - -@implementation MIHSliderDotsControl - -- (id)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (self) { - _normalDotImage = [aDecoder decodeObjectForKey:@"_normalDotImage"]; - _highlightedDotImage = [aDecoder decodeObjectForKey:@"_highlightedDotImage"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_normalDotImage forKey:@"_normalDotImage"]; - [aCoder encodeObject:_highlightedDotImage forKey:@"_highlightedDotImage"]; - [super encodeWithCoder:aCoder]; -} - -- (void)setDotsCount:(NSUInteger)dotsCount -{ - _dotsCount = dotsCount; - - NSArray *subviewsToRemove = [NSArray arrayWithArray:self.subviews]; - [subviewsToRemove enumerateObjectsUsingBlock:^(NSView *subview, NSUInteger index, BOOL *stop) { - [subview removeFromSuperview]; - }]; - - for (NSUInteger currentDotIndex = 0; currentDotIndex < _dotsCount; currentDotIndex++) { - NSButton *dotView = [[NSButton alloc] initWithFrame:NSMakeRect(currentDotIndex * kSpaceBetweenDotCenters, 0.0, kDotImageSize, kDotImageSize)]; - [dotView setImage:(_indexOfHighlightedDot == currentDotIndex ? self.highlightedDotImage : self.normalDotImage)]; - [dotView setAlternateImage:[dotView image]]; - [dotView setButtonType:NSMomentaryChangeButton]; - [dotView setBordered:NO]; - [dotView setTag:currentDotIndex]; - [dotView setTarget:self.sliderView]; - [dotView setAction:@selector(_dotViewSelected:)]; - - [self addSubview:dotView]; - } - - self.frame = NSMakeRect((self.sliderView.bounds.size.width - (dotsCount * kSpaceBetweenDotCenters + kDotImageSize) + kSpaceBetweenDotCenters) / 2, kDotContainerY, dotsCount * kSpaceBetweenDotCenters + kDotImageSize, kDotImageSize); -} - -- (void)setIndexOfHighlightedDot:(NSUInteger)indexOfHighlightedDot -{ - _indexOfHighlightedDot = indexOfHighlightedDot; - - [self.subviews enumerateObjectsUsingBlock:^(NSView *subview, NSUInteger index, BOOL *stop) { - if ([subview isKindOfClass:[NSButton class]]) { - [(NSButton *)subview setImage:(indexOfHighlightedDot == subview.tag ? self.highlightedDotImage : self.normalDotImage)]; - } - }]; -} - -@end diff --git a/thrid-party/objc/NSImage+RHResizableImageAdditions.h b/thrid-party/objc/NSImage+RHResizableImageAdditions.h new file mode 100755 index 0000000000..a1e6656f88 --- /dev/null +++ b/thrid-party/objc/NSImage+RHResizableImageAdditions.h @@ -0,0 +1,142 @@ +// +// NSImage+RHResizableImageAdditions.h +// +// Created by Richard Heard on 15/04/13. +// Copyright (c) 2013 Richard Heard. All rights reserved. +// +// 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. The name of the author may not 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. +// +// Adds a class and category on NSImage that allows for super easy NSDrawNinePartImage +// drawing. It also provides a re-implementation of NSDrawNinePartImage that +// handles stretching and tiling in all directions. We attempt to mirror the +// iOS5+ resizableImage methods somewhat and the code has been tested on OS X 10.8 +// in both StandardDPI and HiDPI modes. + +// Rf enabled, we use RHDrawNinePartImage() instead of NSDrawNinePartImage() to draw your resizable images. +// (RHDrawNinePartImage() supports stretching for all pieces; RHDrawNinePartImage() only supports stretching for the center piece.) + +#ifndef USE_RH_NINE_PART_IMAGE + #define USE_RH_NINE_PART_IMAGE 0 +#endif + +#import + +@class RHResizableImage; + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + +typedef NSEdgeInsets RHEdgeInsets; + +#else + +typedef struct _RHEdgeInsets { + CGFloat top, left, bottom, right; // Specify amount to inset (positive) for each of the edges. values can be negative to 'outset' +} RHEdgeInsets; + +#endif + +extern RHEdgeInsets RHEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right); +extern CGRect RHEdgeInsetsInsetRect(CGRect rect, RHEdgeInsets insets, BOOL flipped); // If flipped origin is top-left otherwise origin is bottom-left (OSX Default is NO) +extern BOOL RHEdgeInsetsEqualToEdgeInsets(RHEdgeInsets insets1, RHEdgeInsets insets2); +extern const RHEdgeInsets RHEdgeInsetsZero; + +extern NSString *NSStringFromRHEdgeInsets(RHEdgeInsets insets); +extern RHEdgeInsets RHEdgeInsetsFromString(NSString* string); + +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + +typedef NSImageResizingMode RHResizableImageResizingMode; +enum { + RHResizableImageResizingModeTile = NSImageResizingModeTile, + RHResizableImageResizingModeStretch = NSImageResizingModeStretch, +}; + +#else + +typedef NS_ENUM(NSInteger, RHResizableImageResizingMode) { + RHResizableImageResizingModeTile, + RHResizableImageResizingModeStretch, +}; + +#endif + + +@interface NSImage (RHResizableImageAdditions) + +-(RHResizableImage *)resizableImageWithCapInsets:(RHEdgeInsets)capInsets; // Create a resizable version of this image. the interior is tiled when drawn. +-(RHResizableImage *)resizableImageWithCapInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode; // The interior is resized according to the resizingMode + +-(RHResizableImage *)stretchableImageWithLeftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight; // Right cap is calculated as width - leftCapWidth - 1; bottom cap is calculated as height - topCapWidth - 1; + + +-(void)drawTiledInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; +-(void)drawStretchedInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; + +@end + + + +@interface RHResizableImage : NSImage { + // ivars are private + RHEdgeInsets _capInsets; + RHResizableImageResizingMode _resizingMode; + + NSArray *_imagePieces; + + NSBitmapImageRep *_cachedImageRep; + NSSize _cachedImageSize; + CGFloat _cachedImageDeviceScale; +} + +-(id)initWithImage:(NSImage *)image leftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight; // right cap is calculated as width - leftCapWidth - 1; bottom cap is calculated as height - topCapWidth - 1; + +-(id)initWithImage:(NSImage *)image capInsets:(RHEdgeInsets)capInsets; +-(id)initWithImage:(NSImage *)image capInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode; // designated initializer + +@property RHEdgeInsets capInsets; // Default is RHEdgeInsetsZero +@property RHResizableImageResizingMode resizingMode; // Default is UIImageResizingModeTile + +-(void)drawInRect:(NSRect)rect; +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha; +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; +-(void)drawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; + +-(void)originalDrawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints; //super passthrough + + +@end + +// utilities +extern __nonnull CGImageRef TGImageByReferencingRectOfExistingImage(NSImage *image, NSRect rect); + +extern NSImage* RHImageByReferencingRectOfExistingImage(NSImage *image, NSRect rect); +extern NSArray* RHNinePartPiecesFromImageWithInsets(NSImage *image, RHEdgeInsets capInsets); +extern CGFloat RHContextGetDeviceScale(CGContextRef context); + +// nine part +extern void RHDrawNinePartImage(NSRect frame, NSImage *topLeftCorner, NSImage *topEdgeFill, NSImage *topRightCorner, NSImage *leftEdgeFill, NSImage *centerFill, NSImage *rightEdgeFill, NSImage *bottomLeftCorner, NSImage *bottomEdgeFill, NSImage *bottomRightCorner, NSCompositingOperation op, CGFloat alphaFraction, BOOL shouldTile); + +extern void RHDrawImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction, BOOL tile); +extern void RHDrawTiledImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction); +extern void RHDrawStretchedImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction); + diff --git a/thrid-party/objc/NSImage+RHResizableImageAdditions.m b/thrid-party/objc/NSImage+RHResizableImageAdditions.m new file mode 100755 index 0000000000..dd3700fe44 --- /dev/null +++ b/thrid-party/objc/NSImage+RHResizableImageAdditions.m @@ -0,0 +1,459 @@ +// +// NSImage+RHResizableImageAdditions.m +// +// Created by Richard Heard on 15/04/13. +// Copyright (c) 2013 Richard Heard. All rights reserved. +// +// 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. The name of the author may not 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. +// + + +#import "NSImage+RHResizableImageAdditions.h" +#import "RHARCSupport.h" + + +//========== +#pragma mark - RHEdgeInsets + +RHEdgeInsets RHEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right){ + RHEdgeInsets insets = {top, left, bottom, right}; + return insets; +} + +CGRect RHEdgeInsetsInsetRect(CGRect rect, RHEdgeInsets insets, BOOL flipped){ + rect.origin.x += insets.left; + rect.origin.y += flipped ? insets.top : insets.bottom; + rect.size.width -= (insets.left + insets.right); + rect.size.height -= (insets.top + insets.bottom); + return rect; +} + +extern BOOL RHEdgeInsetsEqualToEdgeInsets(RHEdgeInsets insets1, RHEdgeInsets insets2){ + return insets1.left == insets2.left && insets1.top == insets2.top && insets1.right == insets2.right && insets1.bottom == insets2.bottom; +} + +const RHEdgeInsets RHEdgeInsetsZero = {0.0f, 0.0f, 0.0f, 0.0f}; + +NSString *NSStringFromRHEdgeInsets(RHEdgeInsets insets){ + return [NSString stringWithFormat:@"{%lg,%lg,%lg,%lg}", insets.top, insets.left, insets.bottom, insets.right]; +} + +RHEdgeInsets RHEdgeInsetsFromString(NSString* string){ + RHEdgeInsets result = RHEdgeInsetsZero; + if(string != nil && [string respondsToSelector:@selector(cStringUsingEncoding:)]){ + sscanf([string cStringUsingEncoding:NSUTF8StringEncoding], "{%lg,%lg,%lg,%lg}", &result.top, &result.left, &result.bottom, &result.right); + } + return result; +} + + + +//========== +#pragma mark - NSImage+RHResizableImageAdditions + + +@implementation NSImage (RHResizableImageAdditions) + +-(RHResizableImage*)resizableImageWithCapInsets:(RHEdgeInsets)capInsets{ + RHResizableImage *new = [[RHResizableImage alloc] initWithImage:self capInsets:capInsets]; + return arc_autorelease(new); +} + +-(RHResizableImage*)resizableImageWithCapInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode{ + RHResizableImage *new = [[RHResizableImage alloc] initWithImage:self capInsets:capInsets resizingMode:resizingMode]; + return arc_autorelease(new); +} + +-(RHResizableImage*)stretchableImageWithLeftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight{ + RHResizableImage *new = [[RHResizableImage alloc] initWithImage:self leftCapWidth:leftCapWidth topCapHeight:topCapHeight]; + return arc_autorelease(new); +} + +-(void)drawTiledInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta{ + RHDrawTiledImageInRect(self, rect, op, delta); +} + +-(void)drawStretchedInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)delta{ + RHDrawStretchedImageInRect(self, rect, op, delta); +} + +@end + + + +//========== +#pragma mark - RHResizableImage + + +@implementation RHResizableImage + +@synthesize capInsets=_capInsets; +@synthesize resizingMode=_resizingMode; + +-(id)initWithImage:(NSImage*)image leftCapWidth:(CGFloat)leftCapWidth topCapHeight:(CGFloat)topCapHeight{ + CGFloat rightCapWidth = image.size.width - leftCapWidth - 1.0f; + CGFloat bottomCapHeight = image.size.height - topCapHeight - 1.0f; + return [self initWithImage:image capInsets:RHEdgeInsetsMake(topCapHeight, leftCapWidth, bottomCapHeight, rightCapWidth)]; +} + +-(id)initWithImage:(NSImage*)image capInsets:(RHEdgeInsets)capInsets{ + return [self initWithImage:image capInsets:capInsets resizingMode:RHResizableImageResizingModeTile]; +} +-(id)initWithImage:(NSImage*)image capInsets:(RHEdgeInsets)capInsets resizingMode:(RHResizableImageResizingMode)resizingMode{ + self = [super initWithData:[image TIFFRepresentation]]; + + if (self){ + _capInsets = capInsets; + _resizingMode = resizingMode; + + _imagePieces = arc_retain(RHNinePartPiecesFromImageWithInsets(self, _capInsets)); + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + RHResizableImage *ri = [super copyWithZone: zone]; + + ri->_capInsets = _capInsets; + ri->_imagePieces = arc_retain(_imagePieces); + ri->_cachedImageRep = arc_retain(_cachedImageRep); + ri->_cachedImageSize = _cachedImageSize; + ri->_cachedImageDeviceScale = _cachedImageDeviceScale; + ri->_resizingMode = _resizingMode; + + return ri; +} + +-(void)dealloc{ + arc_release_nil(_imagePieces); + arc_release_nil(_cachedImageRep); + + arc_super_dealloc(); +} + +#pragma mark - drawing +-(void)drawInRect:(NSRect)rect{ + [self drawInRect:rect operation:NSCompositeSourceOver fraction:1.0f]; +} + +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha{ + [self drawInRect:rect operation:op fraction:requestedAlpha respectFlipped:YES hints:nil]; +} +-(void)drawInRect:(NSRect)rect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints{ + [self drawInRect:rect fromRect:NSZeroRect operation:op fraction:requestedAlpha respectFlipped:YES hints:nil]; +} + +-(void)drawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints{ + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + + //if our current cached image ref size does not match, throw away the cached image + //we also treat the current contexts scale as an invalidator so we don't draw the old, cached result. + if (!NSEqualSizes(rect.size, _cachedImageSize) || _cachedImageDeviceScale != RHContextGetDeviceScale(context)){ + arc_release_nil(_cachedImageRep); + _cachedImageSize = NSZeroSize; + _cachedImageDeviceScale = 0.0f; + } + + + //if we don't have a cached image rep, create one now + if (!_cachedImageRep){ + + //cache our cache invalidation flags + _cachedImageSize = rect.size; + _cachedImageDeviceScale = RHContextGetDeviceScale(context); + + //create our own NSBitmapImageRep directly because calling -[NSImage lockFocus] and then drawing an + //image causes it to use the largest available (ie @2x) image representation, even though our current + //contexts scale is 1 (on non HiDPI screens) meaning that we inadvertently would use @2x assets to draw for @1x contexts + _cachedImageRep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:_cachedImageSize.width * _cachedImageDeviceScale + pixelsHigh:_cachedImageSize.height * _cachedImageDeviceScale + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:[[[self representations] lastObject] colorSpaceName] + bytesPerRow:0 + bitsPerPixel:32]; + [_cachedImageRep setSize:rect.size]; + + if (!_cachedImageRep){ + NSLog(@"Error: failed to create NSBitmapImageRep from rep: %@", [[self representations] lastObject]); + return; + } + + NSGraphicsContext *newContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:_cachedImageRep]; + if (!newContext){ + NSLog(@"Error: failed to create NSGraphicsContext from rep: %@", _cachedImageRep); + arc_release_nil(_cachedImageRep); + return; + } + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:newContext]; + + NSRect drawRect = NSMakeRect(0.0f, 0.0f, _cachedImageSize.width, _cachedImageSize.height); + + [[NSColor clearColor] setFill]; + NSRectFill(drawRect); + + +#if USE_RH_NINE_PART_IMAGE + BOOL shouldTile = (_resizingMode == RHResizableImageResizingModeTile); + RHDrawNinePartImage(drawRect, + [_imagePieces objectAtIndex:0], [_imagePieces objectAtIndex:1], [_imagePieces objectAtIndex:2], + [_imagePieces objectAtIndex:3], [_imagePieces objectAtIndex:4], [_imagePieces objectAtIndex:5], + [_imagePieces objectAtIndex:6], [_imagePieces objectAtIndex:7], [_imagePieces objectAtIndex:8], + NSCompositeSourceOver, 1.0f, shouldTile); +#else + NSDrawNinePartImage(drawRect, + [_imagePieces objectAtIndex:0], [_imagePieces objectAtIndex:1], [_imagePieces objectAtIndex:2], + [_imagePieces objectAtIndex:3], [_imagePieces objectAtIndex:4], [_imagePieces objectAtIndex:5], + [_imagePieces objectAtIndex:6], [_imagePieces objectAtIndex:7], [_imagePieces objectAtIndex:8], + NSCompositeSourceOver, 1.0f, NO); + + //if we want a center stretch, we need to draw this separately, clearing center first + //also note that this only stretches the center, if you also want all sides stretched, + // you should use RHDrawNinePartImage() via USE_RH_NINE_PART_IMAGE = 1 + BOOL shouldStretch = (_resizingMode == RHResizableImageResizingModeStretch); + if (shouldStretch){ + NSImage *centerImage = [_imagePieces objectAtIndex:4]; + NSRect centerRect = NSRectFromCGRect(RHEdgeInsetsInsetRect(NSRectToCGRect(drawRect), _capInsets, NO)); + CGContextClearRect([[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(centerRect)); + RHDrawStretchedImageInRect(centerImage, centerRect, NSCompositeSourceOver, 1.0f); + } + +#endif + [NSGraphicsContext restoreGraphicsState]; + } + + //finally draw the cached image rep + fromRect = NSMakeRect(0.0f, 0.0f, _cachedImageSize.width, _cachedImageSize.height); + [_cachedImageRep drawInRect:rect fromRect:fromRect operation:op fraction:requestedAlpha respectFlipped:respectContextIsFlipped hints:hints]; + +} + +-(void)originalDrawInRect:(NSRect)rect fromRect:(NSRect)fromRect operation:(NSCompositingOperation)op fraction:(CGFloat)requestedAlpha respectFlipped:(BOOL)respectContextIsFlipped hints:(NSDictionary *)hints{ + [super drawInRect:rect fromRect:fromRect operation:op fraction:requestedAlpha respectFlipped:respectContextIsFlipped hints:hints]; +} + +@end + + + +//========== +#pragma mark - utilities + + + + +NSImage* RHImageByReferencingRectOfExistingImage(NSImage *image, NSRect rect){ + NSImage *newImage = [[NSImage alloc] initWithSize:rect.size]; + if (!NSIsEmptyRect(rect)){ + //we operate on all of our NSBitmapImageRep representations; otherwise we loose either @1x or @2x representation + for (NSBitmapImageRep *rep in image.representations) { + //skip and non bitmap image reps + if (![rep isKindOfClass:[NSBitmapImageRep class]]) continue; + + //scale the captureRect for the current representation because CGImage only works in pixels + CGFloat scaleFactor = rep.pixelsHigh / rep.size.height; + CGRect captureRect = CGRectMake(scaleFactor * rect.origin.x, scaleFactor * rect.origin.y, scaleFactor * rect.size.width, scaleFactor * rect.size.height); + + //flip our y axis, because CGImage's origin is top-left + captureRect.origin.y = rep.pixelsHigh - captureRect.origin.y - captureRect.size.height; + + CGImageRef cgImage = CGImageCreateWithImageInRect(rep.CGImage, captureRect); + if (!cgImage){ + NSLog(@"RHImageByReferencingRectOfExistingImage: Error: Failed to create CGImage with CGImageCreateWithImageInRect() for imageRep:%@, rect:%@.", rep, NSStringFromRect(NSRectFromCGRect(captureRect))); + continue; + } + + //create a new BitmapImageRep for the new CGImage. The CGImage just points to the large image, so no pixels are copied by this operation + NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; + [newRep setSize:rect.size]; + CGImageRelease(cgImage); + [newImage addRepresentation:newRep]; + arc_release(newRep); + } + } + + [newImage recache]; + return arc_autorelease(newImage); + +} + +__nonnull CGImageRef TGImageByReferencingRectOfExistingImage(NSImage *image, NSRect rect){ + if (!NSIsEmptyRect(rect)){ + //we operate on all of our NSBitmapImageRep representations; otherwise we loose either @1x or @2x representation + for (NSBitmapImageRep *rep in image.representations) { + //skip and non bitmap image reps + if (![rep isKindOfClass:[NSBitmapImageRep class]]) continue; + + //scale the captureRect for the current representation because CGImage only works in pixels + CGFloat scaleFactor = rep.pixelsHigh / rep.size.height; + CGRect captureRect = CGRectMake(scaleFactor * rect.origin.x, scaleFactor * rect.origin.y, scaleFactor * rect.size.width, scaleFactor * rect.size.height); + + //flip our y axis, because CGImage's origin is top-left + captureRect.origin.y = rep.pixelsHigh - captureRect.origin.y - captureRect.size.height; + + CGImageRef cgImage = CGImageCreateWithImageInRect(rep.CGImage, captureRect); + if (!cgImage){ + NSLog(@"RHImageByReferencingRectOfExistingImage: Error: Failed to create CGImage with CGImageCreateWithImageInRect() for imageRep:%@, rect:%@.", rep, NSStringFromRect(NSRectFromCGRect(captureRect))); + continue; + } + + return cgImage; + } + } + + return CGImageCreateWithImageInRect(nil, NSMakeRect(0, 0, image.size.width, image.size.height)); +} + + + + +NSArray* RHNinePartPiecesFromImageWithInsets(NSImage *image, RHEdgeInsets capInsets){ + + CGFloat imageWidth = image.size.width; + CGFloat imageHeight = image.size.height; + + CGFloat leftCapWidth = capInsets.left; + CGFloat topCapHeight = capInsets.top; + CGFloat rightCapWidth = capInsets.right; + CGFloat bottomCapHeight = capInsets.bottom; + + NSSize centerSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight); + + + NSImage *topLeftCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, imageHeight - topCapHeight, leftCapWidth, topCapHeight)); + NSImage *topEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight)); + NSImage *topRightCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight)); + + NSImage *leftEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, bottomCapHeight, leftCapWidth, centerSize.height)); + NSImage *centerFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height)); + NSImage *rightEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height)); + + NSImage *bottomLeftCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(0.0f, 0.0f, leftCapWidth, bottomCapHeight)); + NSImage *bottomEdgeFill = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(leftCapWidth, 0.0f, centerSize.width, bottomCapHeight)); + NSImage *bottomRightCorner = RHImageByReferencingRectOfExistingImage(image, NSMakeRect(imageWidth - rightCapWidth, 0.0f, rightCapWidth, bottomCapHeight)); + + return [NSArray arrayWithObjects:topLeftCorner, topEdgeFill, topRightCorner, leftEdgeFill, centerFill, rightEdgeFill, bottomLeftCorner, bottomEdgeFill, bottomRightCorner, nil]; +} + + +CGFloat RHContextGetDeviceScale(CGContextRef context){ + CGSize backingSize = CGContextConvertSizeToDeviceSpace(context, CGSizeMake(1.0f, 1.0f)); + return backingSize.width; +} + +//========== +#pragma mark - nine part + + + +void RHDrawNinePartImage(NSRect frame, NSImage *topLeftCorner, NSImage *topEdgeFill, NSImage *topRightCorner, NSImage *leftEdgeFill, NSImage *centerFill, NSImage *rightEdgeFill, NSImage *bottomLeftCorner, NSImage *bottomEdgeFill, NSImage *bottomRightCorner, NSCompositingOperation op, CGFloat alphaFraction, BOOL shouldTile){ + + CGFloat imageWidth = frame.size.width; + CGFloat imageHeight = frame.size.height; + + CGFloat leftCapWidth = topLeftCorner.size.width; + CGFloat topCapHeight = topLeftCorner.size.height; + CGFloat rightCapWidth = bottomRightCorner.size.width; + CGFloat bottomCapHeight = bottomRightCorner.size.height; + + NSSize centerSize = NSMakeSize(imageWidth - leftCapWidth - rightCapWidth, imageHeight - topCapHeight - bottomCapHeight); + + NSRect topLeftCornerRect = NSMakeRect(0.0f, imageHeight - topCapHeight, leftCapWidth, topCapHeight); + NSRect topEdgeFillRect = NSMakeRect(leftCapWidth, imageHeight - topCapHeight, centerSize.width, topCapHeight); + NSRect topRightCornerRect = NSMakeRect(imageWidth - rightCapWidth, imageHeight - topCapHeight, rightCapWidth, topCapHeight); + + NSRect leftEdgeFillRect = NSMakeRect(0.0f, bottomCapHeight, leftCapWidth, centerSize.height); + NSRect centerFillRect = NSMakeRect(leftCapWidth, bottomCapHeight, centerSize.width, centerSize.height); + NSRect rightEdgeFillRect = NSMakeRect(imageWidth - rightCapWidth, bottomCapHeight, rightCapWidth, centerSize.height); + + NSRect bottomLeftCornerRect = NSMakeRect(0.0f, 0.0f, leftCapWidth, bottomCapHeight); + NSRect bottomEdgeFillRect = NSMakeRect(leftCapWidth, 0.0f, centerSize.width, bottomCapHeight); + NSRect bottomRightCornerRect = NSMakeRect(imageWidth - rightCapWidth, 0.0f, rightCapWidth, bottomCapHeight); + + + RHDrawImageInRect(topLeftCorner, topLeftCornerRect, op, fraction, NO); + RHDrawImageInRect(topEdgeFill, topEdgeFillRect, op, fraction, shouldTile); + RHDrawImageInRect(topRightCorner, topRightCornerRect, op, fraction, NO); + + RHDrawImageInRect(leftEdgeFill, leftEdgeFillRect, op, fraction, shouldTile); + RHDrawImageInRect(centerFill, centerFillRect, op, fraction, shouldTile); + RHDrawImageInRect(rightEdgeFill, rightEdgeFillRect, op, fraction, shouldTile); + + RHDrawImageInRect(bottomLeftCorner, bottomLeftCornerRect, op, fraction, NO); + RHDrawImageInRect(bottomEdgeFill, bottomEdgeFillRect, op, fraction, shouldTile); + RHDrawImageInRect(bottomRightCorner, bottomRightCornerRect, op, fraction, NO); + +} + +void RHDrawImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction, BOOL tile){ + if (tile){ + RHDrawTiledImageInRect(image, rect, op, fraction); + } else { + RHDrawStretchedImageInRect(image, rect, op, fraction); + } +} + + +void RHDrawTiledImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction){ + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(context); + + [[NSGraphicsContext currentContext] setCompositingOperation:op]; + CGContextSetAlpha(context, fraction); + + //pass in the images actual size in points rather than rect. This gives us the actual best representation for the current context. if we passed in rect directly, we would always get the @2x representation because NSImage assumes more pixels are always better. + NSRect outRect = NSMakeRect(0.0f, 0.0f, image.size.width, image.size.height); + CGImageRef imageRef = [image CGImageForProposedRect:&outRect context:[NSGraphicsContext currentContext] hints:NULL]; + + CGContextClipToRect(context, NSRectToCGRect(rect)); + CGContextDrawTiledImage(context, CGRectMake(rect.origin.x, rect.origin.y, image.size.width, image.size.height), imageRef); + + CGContextRestoreGState(context); +} + +void RHDrawStretchedImageInRect(NSImage* image, NSRect rect, NSCompositingOperation op, CGFloat fraction){ + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(context); + + [[NSGraphicsContext currentContext] setCompositingOperation:op]; + CGContextSetAlpha(context, fraction); + + //we pass in the images actual size rather than rect. if we passed in rect directly, we would always get the @2x ref. (10.8s workaround for single axis stretching was -[NSImage matchesOnlyOnBestFittingAxis], however this wont work for stretching in 2 dimensions) + NSRect outRect = NSMakeRect(0.0f, 0.0f, image.size.width, image.size.height); + CGImageRef imageRef = [image CGImageForProposedRect:&outRect context:[NSGraphicsContext currentContext] hints:NULL]; + + CGContextClipToRect(context, NSRectToCGRect(rect)); + CGContextDrawImage(context, NSRectToCGRect(rect), imageRef); + + CGContextRestoreGState(context); +} + + + diff --git a/thrid-party/objc/NumberPluralizationForm.m b/thrid-party/objc/NumberPluralizationForm.m index fcbfb5424b..f675cf9486 100644 --- a/thrid-party/objc/NumberPluralizationForm.m +++ b/thrid-party/objc/NumberPluralizationForm.m @@ -348,6 +348,9 @@ NumberPluralizationForm numberPluralizationForm(unsigned int lc, int n) { unsigned int languageCodehash(NSString *code) { NSString *rawCode = code; + if ([rawCode hasSuffix:@"-raw"]) { + rawCode = [rawCode substringToIndex:[rawCode rangeOfString:@"-"].location]; + } NSRange range = [code rangeOfString:@"_"]; if (range.location != NSNotFound) { rawCode = [code substringToIndex:range.location]; diff --git a/thrid-party/objc/RHARCSupport.h b/thrid-party/objc/RHARCSupport.h new file mode 100755 index 0000000000..898619e063 --- /dev/null +++ b/thrid-party/objc/RHARCSupport.h @@ -0,0 +1,115 @@ +// +// RHARCSupport.h +// +// Created by Richard Heard on 3/07/12. +// Copyright (c) 2012 Richard Heard. All rights reserved. +// +// 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. The name of the author may not 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. +// +// supporting macros for code to allow building with and without arc + + +#ifndef __has_feature +// not LLVM Compiler +#define __has_feature(x) 0 +#endif + +#ifndef CF_CONSUMED +#if __has_feature(attribute_cf_consumed) +#define CF_CONSUMED __attribute__((cf_consumed)) +#else +#define CF_CONSUMED +#endif +#endif + + +// ----- ARC Enabled ----- +#if __has_feature(objc_arc) && !defined(ARC_IS_ENABLED) + +#define ARC_IS_ENABLED 1 + +//define retain count macro wrappers +#define arc_retain(x) (x) +#define arc_release(x) +#define arc_release_nil(x) (x = nil) +#define arc_autorelease(x) (x) +#define arc_super_dealloc() + +//add CF bridging methods +#define ARCBridgingRetain(x) CFBridgingRetain(x) +#define ARCBridgingRelease(x) CFBridgingRelease(x) + +#endif + + +// ----- ARC Disabled ----- +#if !__has_feature(objc_arc) && !defined(ARC_IS_ENABLED) + +#define ARC_IS_ENABLED 0 + +//define retain count macro wrappers +#define arc_retain(x) ([x retain]) +#define arc_release(x) ([x release]) +#define arc_release_nil(x) [x release]; x = nil; +#define arc_autorelease(x) ([x autorelease]) +#define arc_super_dealloc() ([super dealloc]) + +//add arc keywords if not already defined +#ifndef __bridge +#define __bridge +#endif +#ifndef __bridge_retained +#define __bridge_retained +#endif +#ifndef __bridge_transfer +#define __bridge_transfer +#endif + +#ifndef __autoreleasing +#define __autoreleasing +#endif +#ifndef __strong +#define __strong +#endif +#ifndef __weak +#define __weak +#endif +#ifndef __unsafe_unretained +#define __unsafe_unretained +#endif + +//add CF bridging methods (we inline these ourselves because they are not included in older sdks) +NS_INLINE CF_RETURNS_RETAINED CFTypeRef ARCBridgingRetain(id X) { + return X ? CFRetain((CFTypeRef)X) : NULL; +} + +NS_INLINE id ARCBridgingRelease(CFTypeRef CF_CONSUMED X) { + return [(id)CFMakeCollectable(X) autorelease]; +} + +#endif + + +//if clarity helper +#define ARC_IS_NOT_ENABLED (!(ARC_IS_ENABLED)) + diff --git a/thrid-party/objc/TGDataItem.h b/thrid-party/objc/TGDataItem.h index 68de93aadd..2f528e3113 100644 --- a/thrid-party/objc/TGDataItem.h +++ b/thrid-party/objc/TGDataItem.h @@ -1,8 +1,9 @@ #import + @interface TGDataItem : NSObject -- (instancetype)initWithTempFile; + - (instancetype)initWithFilePath:(NSString *)filePath; - (void)moveToPath:(NSString *)path; diff --git a/thrid-party/objc/TGDataItem.m b/thrid-party/objc/TGDataItem.m index cbbe305453..8b7e614eb7 100644 --- a/thrid-party/objc/TGDataItem.m +++ b/thrid-party/objc/TGDataItem.m @@ -15,6 +15,8 @@ @interface TGDataItem () @end + + @implementation TGDataItem - (void)_commonInit @@ -23,41 +25,21 @@ - (void)_commonInit _data = [[NSMutableData alloc] init]; } -- (instancetype)initWithTempFile -{ - self = [super init]; - if (self != nil) - { - [self _commonInit]; - - [_queue dispatch:^ - { - int64_t randomId = 0; - arc4random_buf(&randomId, 8); - _fileName = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"%" PRIx64 ".ogg", randomId]]; - _fileExists = false; - }]; - } - return self; -} - - (instancetype)initWithFilePath:(NSString *)filePath { self = [super init]; if (self != nil) { [self _commonInit]; - - [_queue dispatch:^ - { - _fileName = filePath; - _length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue]; - _fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName]; - }]; + int64_t randomId = 0; + arc4random_buf(&randomId, 8); + _fileName = filePath; + _fileExists = false; } return self; } + - (void)moveToPath:(NSString *)path { [_queue dispatch:^ @@ -90,7 +72,6 @@ - (void)appendData:(NSData *)data [file synchronizeFile]; [file closeFile]; _length += data.length; - [_data appendData:data]; }]; } diff --git a/thrid-party/objc/TGOpusAudioRecorder.h b/thrid-party/objc/TGOpusAudioRecorder.h deleted file mode 100644 index 51658ba966..0000000000 --- a/thrid-party/objc/TGOpusAudioRecorder.h +++ /dev/null @@ -1,20 +0,0 @@ - -#import - -@class TGDataItem; -@class TGAudioWaveform; - -@interface TGOpusAudioRecorder : NSObject - -@property (nonatomic, copy) void (^pauseRecording)(); -@property (nonatomic, copy) void (^micLevel)(CGFloat); - -- (instancetype)initWithFileEncryption:(bool)fileEncryption; - -- (void)_beginAudioSession:(bool)speaker; -- (void)prepareRecord:(bool)playTone completion:(void (^)())completion; -- (void)record; -- (TGDataItem *)stopRecording:(NSTimeInterval *)recordedDuration waveform:(__autoreleasing TGAudioWaveform **)waveform; -- (NSTimeInterval)currentDuration; - -@end diff --git a/thrid-party/objc/TGOpusAudioRecorder.m b/thrid-party/objc/TGOpusAudioRecorder.m deleted file mode 100644 index 465e1ca7ac..0000000000 --- a/thrid-party/objc/TGOpusAudioRecorder.m +++ /dev/null @@ -1,825 +0,0 @@ - -#import "TGOpusAudioRecorder.h" - -#import "ATQueue.h" - - -#import -#import - -#import "opus.h" -#import "opusenc.h" - -#import "TGDataItem.h" -#import "TGAudioWaveform.h" - -#define kOutputBus 0 -#define kInputBus 1 - -static const int TGOpusAudioRecorderSampleRate = 48000; - -typedef struct -{ - AudioComponentInstance audioUnit; - bool audioUnitStarted; - bool audioUnitInitialized; - - AudioComponentInstance playbackUnit; - bool playbackUnitStarted; - bool playbackUnitInitialized; - - int globalAudioRecorderId; -} TGOpusAudioRecorderContext; - -static NSData *toneAudioBuffer; -static int64_t toneAudioOffset; - -static TGOpusAudioRecorderContext globalRecorderContext = { .audioUnit = NULL, .audioUnitStarted = false, .audioUnitInitialized = false, .playbackUnit = NULL, .playbackUnitStarted = false, .playbackUnitInitialized = false, .globalAudioRecorderId = -1}; -static __weak TGOpusAudioRecorder *globalRecorder = nil; - -static dispatch_semaphore_t playSoundSemaphore = nil; - -@interface TGOpusAudioRecorder () -{ - TGDataItem *_tempFileItem; - - TGOggOpusWriter *_oggWriter; - - NSMutableData *_audioBuffer; - - NSString *_liveUploadPath; - - - bool _recording; - bool _waitForTone; - NSTimeInterval _waitForToneStart; - bool _stopped; - - NSMutableData *_waveformSamples; - int16_t _waveformPeak; - int _waveformPeakCount; - - int16_t _micLevelPeak; - int _micLevelPeakCount; - - AudioDeviceID _defaultInputDeviceID; -} - -@property (nonatomic) int recorderId; - -@end - -@implementation TGOpusAudioRecorder - -- (instancetype)initWithFileEncryption:(bool)fileEncryption -{ - self = [super init]; - if (self != nil) - { - _defaultInputDeviceID = kAudioDeviceUnknown; - _tempFileItem = [[TGDataItem alloc] initWithTempFile]; - - _waveformSamples = [[NSMutableData alloc] init]; - - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - static int nextRecorderId = 1; - _recorderId = nextRecorderId++; - globalRecorderContext.globalAudioRecorderId = _recorderId; - - globalRecorder = self; - - static int nextActionId = 0; - int actionId = nextActionId++; - _liveUploadPath = [[NSString alloc] initWithFormat:@"/tg/liveUpload/(%d)", actionId]; - - }]; - } - return self; -} - -- (void)dealloc -{ - - [self cleanup]; -} - -+ (ATQueue *)processingQueue -{ - static ATQueue *queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioRecorderQueue"]; - }); - - return queue; -} - -- (void)cleanup -{ - intptr_t objectId = (intptr_t)self; - int recorderId = _recorderId; - - globalRecorder = nil; - - _oggWriter = nil; - - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - if (globalRecorderContext.globalAudioRecorderId == recorderId) - { - globalRecorderContext.globalAudioRecorderId++; - - if (globalRecorderContext.audioUnitStarted && globalRecorderContext.audioUnit != NULL) - { - OSStatus status = noErr; - status = AudioOutputUnitStop(globalRecorderContext.audioUnit); - if (status != noErr) - NSLog(@"[TGOpusAudioRecorder%ld AudioOutputUnitStop failed: %d]", objectId, (int)status); - - globalRecorderContext.audioUnitStarted = false; - } - - if (globalRecorderContext.audioUnit != NULL) - { - OSStatus status = noErr; - status = AudioComponentInstanceDispose(globalRecorderContext.audioUnit); - if (status != noErr) - NSLog(@"[TGOpusAudioRecorder%ld AudioComponentInstanceDispose failed: %d]", objectId, (int)status); - - globalRecorderContext.audioUnit = NULL; - } - - if (globalRecorderContext.playbackUnitStarted && globalRecorderContext.playbackUnit != NULL) - { - OSStatus status = noErr; - status = AudioOutputUnitStop(globalRecorderContext.playbackUnit); - if (status != noErr) - NSLog(@"[TGOpusAudioRecorder%ld playback AudioOutputUnitStop failed: %d]", objectId, (int)status); - - globalRecorderContext.playbackUnitStarted = false; - } - - if (globalRecorderContext.playbackUnit != NULL) - { - OSStatus status = noErr; - status = AudioComponentInstanceDispose(globalRecorderContext.playbackUnit); - if (status != noErr) - NSLog(@"[TGOpusAudioRecorder%ld playback AudioComponentInstanceDispose failed: %d]", objectId, (int)status); - - globalRecorderContext.playbackUnit = NULL; - } - } - }]; - - [self _endAudioSession]; -} - -- (void)_beginAudioSession:(bool)speaker -{ - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - NSLog(@"_beginAudioSession completed"); - if (self->_pauseRecording) { - self->_pauseRecording(); - } - } synchronous:true]; -} - -- (void)_endAudioSession -{ - -} - -- (void)prepareRecord:(bool)playTone completion:(void (^)())completion -{ - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - if (_stopped) { - if (completion) { - completion(); - } - return; - } - - - OSStatus status = noErr; - - - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_HALOutput; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc); - AudioComponentInstanceNew(inputComponent, &globalRecorderContext.audioUnit); - - UInt32 one = 1; - status = AudioUnitSetProperty(globalRecorderContext.audioUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, - kInputBus, - &one, - sizeof(one)); - - if (status != noErr) { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitSetProperty kAudioOutputUnitProperty_EnableIO failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - UInt32 zero = 0; - status = AudioUnitSetProperty(globalRecorderContext.audioUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, - kOutputBus, - &zero, - sizeof(UInt32)); - - if (status != noErr) { - NSLog(@"[TGOpusAudioRecorder%@ kAudioOutputUnitProperty_EnableIO kAudioUnitScope_Output failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - if(_defaultInputDeviceID == kAudioDeviceUnknown) - { - AudioObjectPropertyAddress propertyAddress; - propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; - propertyAddress.mElement = kAudioObjectPropertyElementMaster; - - AudioDeviceID thisDeviceID; - UInt32 propsize = sizeof(AudioDeviceID); - status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propsize, &thisDeviceID); - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ AudioObjectGetPropertyData kAudioObjectSystemObject failed: %d]", self, (int)status); - [self cleanup]; - return; - } - _defaultInputDeviceID = thisDeviceID; - } - - status = AudioUnitSetProperty( globalRecorderContext.audioUnit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - kOutputBus, - &_defaultInputDeviceID, - sizeof(AudioDeviceID) ); - - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ kAudioOutputUnitProperty_CurrentDevice kAudioUnitScope_Global failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - UInt32 propertySize = sizeof(AudioDeviceID) ; - AudioObjectPropertyAddress defaultDeviceProperty; - defaultDeviceProperty.mScope = kAudioObjectPropertyScopeGlobal; - defaultDeviceProperty.mElement = kAudioObjectPropertyElementMaster; - defaultDeviceProperty.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; - - - status = AudioObjectGetPropertyDataSize(_defaultInputDeviceID, - &defaultDeviceProperty, - 0, - NULL, - &propertySize); - - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ AudioObjectGetPropertyDataSize _defaultInputDeviceID failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - int m_valueCount = propertySize / sizeof(AudioValueRange) ; - - AudioValueRange m_valueTabe[m_valueCount]; - - status = AudioObjectGetPropertyData(_defaultInputDeviceID, - &defaultDeviceProperty, - 0, - NULL, - &propertySize, - m_valueTabe); - - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ AudioObjectGetPropertyData _defaultInputDeviceID failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - - Float64 sampleRate = 44100; - - for(UInt32 i = 0 ; i < m_valueCount ; ++i) - { - if ((int)m_valueTabe[i].mMinimum == TGOpusAudioRecorderSampleRate) { - sampleRate = m_valueTabe[i].mMinimum; - break; - } - } - - AudioValueRange inputSampleRate; - inputSampleRate.mMinimum = sampleRate; - inputSampleRate.mMaximum = sampleRate; - defaultDeviceProperty.mSelector = kAudioDevicePropertyNominalSampleRate; - status = AudioObjectSetPropertyData(_defaultInputDeviceID, - &defaultDeviceProperty, - 0, - NULL, - sizeof(inputSampleRate), - &inputSampleRate); - - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ AudioObjectSetPropertyData kAudioDevicePropertyNominalSampleRate failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - AudioStreamBasicDescription inputAudioFormat; - inputAudioFormat.mSampleRate = sampleRate; - inputAudioFormat.mFormatID = kAudioFormatLinearPCM; - inputAudioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - inputAudioFormat.mFramesPerPacket = 1; - inputAudioFormat.mChannelsPerFrame = 1; - inputAudioFormat.mBitsPerChannel = 16; - inputAudioFormat.mBytesPerPacket = 2; - inputAudioFormat.mBytesPerFrame = 2; - status = AudioUnitSetProperty(globalRecorderContext.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &inputAudioFormat, sizeof(inputAudioFormat)); - - if (status != noErr) { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitSetProperty kAudioUnitProperty_StreamFormat failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - status = AudioUnitSetProperty(globalRecorderContext.audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inputAudioFormat, sizeof(inputAudioFormat)); - if (status != noErr) { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitSetProperty kAudioUnitProperty_StreamFormat failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - AURenderCallbackStruct callbackStruct; - callbackStruct.inputProc = &TGOpusRecordingCallback; - callbackStruct.inputProcRefCon = (void *)(intptr_t)_recorderId; - if (AudioUnitSetProperty(globalRecorderContext.audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callbackStruct, sizeof(callbackStruct)) != noErr) { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitSetProperty kAudioOutputUnitProperty_SetInputCallback failed]", self); - [self cleanup]; - return; - } - - if (AudioUnitSetProperty(globalRecorderContext.audioUnit, kAudioUnitProperty_ShouldAllocateBuffer, kAudioUnitScope_Output, 0, &zero, sizeof(zero)) != noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitSetProperty kAudioUnitProperty_ShouldAllocateBuffer failed]", self); - [self cleanup]; - return; - } - - _oggWriter = [[TGOggOpusWriter alloc] init]; - if (![_oggWriter beginWithDataItem:_tempFileItem]) - { - NSLog(@"[TGOpusAudioRecorder%@ error initializing ogg opus writer]", self); - [self cleanup]; - return; - } - - CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); - - status = AudioUnitInitialize(globalRecorderContext.audioUnit); - if (status == noErr) - globalRecorderContext.audioUnitInitialized = true; - else - { - NSLog(@"[TGOpusAudioRecorder%@ AudioUnitInitialize failed: %d]", self, (int)status); - [self cleanup]; - - return; - } - - NSLog(@"[TGOpusAudioRecorder%@ setup time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0); - - status = AudioOutputUnitStart(globalRecorderContext.audioUnit); - if (status == noErr) - { - NSLog(@"[TGOpusAudioRecorder%@ initialization time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0); - NSLog(@"[TGOpusAudioRecorder%@ started]", self); - globalRecorderContext.audioUnitStarted = true; - } - else - { - NSLog(@"[TGOpusAudioRecorder%@ AudioOutputUnitStart failed: %d]", self, (int)status); - [self cleanup]; - return; - } - - - if (playTone) { - [self loadToneBuffer]; - _waitForTone = true; - toneAudioOffset = 0; - - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_HALOutput; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc); - AudioComponentInstanceNew(inputComponent, &globalRecorderContext.playbackUnit); - - OSStatus status = noErr; - - static const UInt32 one = 1; - status = AudioUnitSetProperty(globalRecorderContext.playbackUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &one, sizeof(one)); - if (status != noErr) - { - NSLog(@"[TGOpusAudioPlayer#%@ AudioUnitSetProperty kAudioOutputUnitProperty_EnableIO failed: %d]", self, (int)status); - [self cleanup]; - - return; - } - - AudioStreamBasicDescription outputAudioFormat; - outputAudioFormat.mSampleRate = sampleRate; - outputAudioFormat.mFormatID = kAudioFormatLinearPCM; - outputAudioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - outputAudioFormat.mFramesPerPacket = 1; - outputAudioFormat.mChannelsPerFrame = 1; - outputAudioFormat.mBitsPerChannel = 16; - outputAudioFormat.mBytesPerPacket = 2; - outputAudioFormat.mBytesPerFrame = 2; - status = AudioUnitSetProperty(globalRecorderContext.playbackUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &outputAudioFormat, sizeof(outputAudioFormat)); - if (status != noErr) - { - NSLog(@"[TGOpusAudioPlayer#%@ playback AudioUnitSetProperty kAudioUnitProperty_StreamFormat failed: %d]", self, (int)status); - [self cleanup]; - - return; - } - - AURenderCallbackStruct callbackStruct; - callbackStruct.inputProc = &TGOpusAudioPlayerCallback; - callbackStruct.inputProcRefCon = (void *)(intptr_t)_recorderId; - if (AudioUnitSetProperty(globalRecorderContext.playbackUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct)) != noErr) - { - NSLog(@"[TGOpusAudioPlayer#%@ playback AudioUnitSetProperty kAudioUnitProperty_SetRenderCallback failed]", self); - [self cleanup]; - - return; - } - - status = AudioUnitInitialize(globalRecorderContext.playbackUnit); - if (status == noErr) - { - status = AudioOutputUnitStart(globalRecorderContext.playbackUnit); - if (status != noErr) - { - NSLog(@"[TGOpusAudioRecorder#%@ playback AudioOutputUnitStart failed: %d]", self, (int)status); - } - } else { - NSLog(@"[TGOpusAudioRecorder#%@ playback AudioUnitInitialize failed: %d]", self, (int)status); - [self cleanup]; - } - - _waitForToneStart = CACurrentMediaTime(); - } - - if (completion) { - completion(); - } - - }]; - -} - -- (void)record { - _recording = true; -} - -static OSStatus TGOpusAudioPlayerCallback(void *inRefCon, __unused AudioUnitRenderActionFlags *ioActionFlags, __unused const AudioTimeStamp *inTimeStamp, __unused UInt32 inBusNumber, __unused UInt32 inNumberFrames, AudioBufferList *ioData) -{ - if (globalRecorderContext.globalAudioRecorderId != (int)inRefCon) - return noErr; - - for (int i = 0; i < (int)ioData->mNumberBuffers; i++) - { - AudioBuffer *buffer = &ioData->mBuffers[i]; - buffer->mNumberChannels = 1; - - int audioBytesToCopy = MAX(0, MIN((int)buffer->mDataByteSize, ((int)toneAudioBuffer.length) - (int)toneAudioOffset)); - if (audioBytesToCopy != 0) { - memcpy(buffer->mData, toneAudioBuffer.bytes + (int)toneAudioOffset, audioBytesToCopy); - toneAudioOffset += audioBytesToCopy; - } - - int remainingBytes = ((int)buffer->mDataByteSize) - audioBytesToCopy; - if (remainingBytes > 0) { - memset(buffer->mData + buffer->mDataByteSize - remainingBytes, 0, remainingBytes); - } - } - - return noErr; -} - - -static OSStatus TGOpusRecordingCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, __unused AudioBufferList *ioData) -{ - @autoreleasepool - { - if (globalRecorderContext.globalAudioRecorderId != (int)inRefCon) - return noErr; - - AudioBuffer buffer; - buffer.mNumberChannels = 1; - buffer.mDataByteSize = inNumberFrames * 2; - buffer.mData = malloc(inNumberFrames * 2); - - AudioBufferList bufferList; - bufferList.mNumberBuffers = 1; - bufferList.mBuffers[0] = buffer; - OSStatus status = AudioUnitRender(globalRecorderContext.audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList); - if (status == noErr) - { - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - TGOpusAudioRecorder *recorder = globalRecorder; - if (recorder != nil && recorder.recorderId == (int)(intptr_t)inRefCon && recorder->_recording) { - - if (recorder->_waitForTone) { - if (CACurrentMediaTime() - recorder->_waitForToneStart > 0.3) { - [recorder _processBuffer:&buffer]; - } - } else { - [recorder _processBuffer:&buffer]; - } - } - - free(buffer.mData); - }]; - } - } - - return noErr; -} - -- (void)processWaveformPreview:(int16_t const *)samples count:(int)count { - for (int i = 0; i < count; i++) { - int16_t sample = samples[i]; - if (sample < 0) { - sample = -sample; - } - - if (_waveformPeak < sample) { - _waveformPeak = sample; - } - _waveformPeakCount++; - - if (_waveformPeakCount >= 100) { - [_waveformSamples appendBytes:&_waveformPeak length:2]; - _waveformPeak = 0; - _waveformPeakCount = 0; - } - - if (_micLevelPeak < sample) { - _micLevelPeak = sample; - } - _micLevelPeakCount++; - - if (_micLevelPeakCount >= 1200) { - if (_micLevel) { - CGFloat level = (CGFloat)_micLevelPeak / 4000.0; - _micLevel(level); - } - _micLevelPeak = 0; - _micLevelPeakCount = 0; - } - } -} - -- (void)_processBuffer:(AudioBuffer const *)buffer -{ - @autoreleasepool - { - if (_oggWriter == nil) - return; - - static const int millisecondsPerPacket = 60; - static const int encoderPacketSizeInBytes = 16000 / 1000 * millisecondsPerPacket * 2; - - unsigned char currentEncoderPacket[encoderPacketSizeInBytes]; - - int bufferOffset = 0; - - while (true) - { - int currentEncoderPacketSize = 0; - - while (currentEncoderPacketSize < encoderPacketSizeInBytes) - { - if (_audioBuffer.length != 0) - { - int takenBytes = MIN((int)_audioBuffer.length, encoderPacketSizeInBytes - currentEncoderPacketSize); - if (takenBytes != 0) - { - memcpy(currentEncoderPacket + currentEncoderPacketSize, _audioBuffer.bytes, takenBytes); - [_audioBuffer replaceBytesInRange:NSMakeRange(0, takenBytes) withBytes:NULL length:0]; - currentEncoderPacketSize += takenBytes; - } - } - else if (bufferOffset < (int)buffer->mDataByteSize) - { - int takenBytes = MIN((int)buffer->mDataByteSize - bufferOffset, encoderPacketSizeInBytes - currentEncoderPacketSize); - if (takenBytes != 0) - { - memcpy(currentEncoderPacket + currentEncoderPacketSize, ((const char *)buffer->mData) + bufferOffset, takenBytes); - bufferOffset += takenBytes; - currentEncoderPacketSize += takenBytes; - } - } - else - break; - } - - if (currentEncoderPacketSize < encoderPacketSizeInBytes) - { - if (_audioBuffer == nil) - _audioBuffer = [[NSMutableData alloc] initWithCapacity:encoderPacketSizeInBytes]; - [_audioBuffer appendBytes:currentEncoderPacket length:currentEncoderPacketSize]; - - break; - } - else - { - NSUInteger previousBytesWritten = [_oggWriter encodedBytes]; - [self processWaveformPreview:(int16_t const *)currentEncoderPacket count:currentEncoderPacketSize / 2]; - [_oggWriter writeFrame:currentEncoderPacket frameByteCount:(NSUInteger)currentEncoderPacketSize]; - NSUInteger currentBytesWritten = [_oggWriter encodedBytes]; - if (currentBytesWritten != previousBytesWritten) - { - // update live data - } - } - } - } -} - -- (TGDataItem *)stopRecording:(NSTimeInterval *)recordedDuration waveform:(__autoreleasing TGAudioWaveform **)waveform -{ - _stopped = true; - __block TGDataItem *dataItemResult = nil; - __block NSTimeInterval durationResult = 0.0; - - __block NSUInteger totalBytes = 0; - - [[TGOpusAudioRecorder processingQueue] dispatch:^ - { - if (_oggWriter != nil && [_oggWriter writeFrame:NULL frameByteCount:0]) - { - dataItemResult = _tempFileItem; - durationResult = [_oggWriter encodedDuration]; - totalBytes = [_oggWriter encodedBytes]; - } - - [self cleanup]; - } synchronous:true]; - - int16_t scaledSamples[100]; - memset(scaledSamples, 0, 100 * 2); - int16_t *samples = _waveformSamples.mutableBytes; - int count = (int)_waveformSamples.length / 2; - for (int i = 0; i < count; i++) { - int16_t sample = samples[i]; - int index = i * 100 / count; - if (scaledSamples[index] < sample) { - scaledSamples[index] = sample; - } - } - - int16_t peak = 0; - int64_t sumSamples = 0; - for (int i = 0; i < 100; i++) { - int16_t sample = scaledSamples[i]; - if (peak < sample) { - peak = sample; - } - sumSamples += peak; - } - uint16_t calculatedPeak = 0; - calculatedPeak = (uint16_t)(sumSamples * 1.8f / 100); - - if (calculatedPeak < 2500) { - calculatedPeak = 2500; - } - - for (int i = 0; i < 100; i++) { - uint16_t sample = (uint16_t)((int64_t)samples[i]); - if (sample > calculatedPeak) { - scaledSamples[i] = calculatedPeak; - } - } - - TGAudioWaveform *resultWaveform = [[TGAudioWaveform alloc] initWithSamples:[NSData dataWithBytes:scaledSamples length:100 * 2] peak:calculatedPeak]; - NSData *bitstream = [resultWaveform bitstream]; - resultWaveform = [[TGAudioWaveform alloc] initWithBitstream:bitstream bitsPerSample:5]; - - if (recordedDuration != NULL) - *recordedDuration = durationResult; - - if (waveform != NULL) { - *waveform = resultWaveform; - } - -// if (liveData != NULL) -// { -// dispatch_sync([ActionStageInstance() globalStageDispatchQueue], ^ -// { -// TGLiveUploadActor *actor = (TGLiveUploadActor *)[ActionStageInstance() executingActorWithPath:_liveUploadPath]; -// *liveData = [actor finishRestOfFile:totalBytes]; -// }); -// } - - return dataItemResult; -} - -- (NSTimeInterval)currentDuration -{ - return [_oggWriter encodedDuration]; -} - -- (void)loadToneBuffer { - if (toneAudioBuffer != nil) { - return; - } - - NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, - [NSNumber numberWithFloat:48000.0], AVSampleRateKey, - [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, - [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, - [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, - [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, - nil]; - - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[[NSBundle mainBundle] URLForResource:@"begin_record" withExtension: @"caf"] options:nil]; - if (asset == nil) { - NSLog(@"asset is not defined!"); - return; - } - - NSError *assetError = nil; - AVAssetReader *iPodAssetReader = [AVAssetReader assetReaderWithAsset:asset error:&assetError]; - if (assetError) { - NSLog (@"error: %@", assetError); - return; - } - - AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings]; - - if (! [iPodAssetReader canAddOutput: readerOutput]) { - NSLog (@"can't add reader output... die!"); - return; - } - - // add output reader to reader - [iPodAssetReader addOutput: readerOutput]; - - if (! [iPodAssetReader startReading]) { - NSLog(@"Unable to start reading!"); - return; - } - - NSMutableData *data = [[NSMutableData alloc] init]; - while (iPodAssetReader.status == AVAssetReaderStatusReading) { - // Check if the available buffer space is enough to hold at least one cycle of the sample data - CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer]; - - if (nextBuffer) { - AudioBufferList abl; - CMBlockBufferRef blockBuffer = NULL; - CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer); - UInt64 size = CMSampleBufferGetTotalSampleSize(nextBuffer); - if (size != 0) { - [data appendBytes:abl.mBuffers[0].mData length:size]; - } - - CFRelease(nextBuffer); - if (blockBuffer) { - CFRelease(blockBuffer); - } - } - else { - break; - } - } - - toneAudioBuffer = data; -} - -@end diff --git a/thrid-party/objc/mrz/TGPassportMRZ.h b/thrid-party/objc/mrz/TGPassportMRZ.h new file mode 100644 index 0000000000..1702193876 --- /dev/null +++ b/thrid-party/objc/mrz/TGPassportMRZ.h @@ -0,0 +1,23 @@ +#import + +@interface TGPassportMRZ : NSObject + +@property (nonatomic, readonly) NSString *documentType; +@property (nonatomic, readonly) NSString *documentSubtype; +@property (nonatomic, readonly) NSString *issuingCountry; +@property (nonatomic, readonly) NSString *lastName; +@property (nonatomic, readonly) NSString *firstName; +@property (nonatomic, readonly) NSString *documentNumber; +@property (nonatomic, readonly) NSString *nationality; +@property (nonatomic, readonly) NSDate *birthDate; +@property (nonatomic, readonly) NSString *gender; +@property (nonatomic, readonly) NSDate *expiryDate; +@property (nonatomic, readonly) NSString *optional1; +@property (nonatomic, readonly) NSString *optional2; + ++ (instancetype)parseLines:(NSArray *)lines; + +@end + +extern const NSUInteger TGPassportTD1Length; +extern const NSUInteger TGPassportTD23Length; diff --git a/thrid-party/objc/mrz/TGPassportMRZ.m b/thrid-party/objc/mrz/TGPassportMRZ.m new file mode 100644 index 0000000000..23697e5eae --- /dev/null +++ b/thrid-party/objc/mrz/TGPassportMRZ.m @@ -0,0 +1,320 @@ +#import "TGPassportMRZ.h" + +const NSUInteger TGPassportTD1Length = 30; +const NSUInteger TGPassportTD23Length = 44; +NSString *const TGPassportEmptyCharacter = @"<"; + +@implementation TGPassportMRZ + /* + @property (nonatomic, readonly) NSString *documentType; + @property (nonatomic, readonly) NSString *documentSubtype; + @property (nonatomic, readonly) NSString *issuingCountry; + @property (nonatomic, readonly) NSString *lastName; + @property (nonatomic, readonly) NSString *firstName; + @property (nonatomic, readonly) NSString *documentNumber; + @property (nonatomic, readonly) NSString *nationality; + @property (nonatomic, readonly) NSDate *birthDate; + @property (nonatomic, readonly) NSString *gender; + @property (nonatomic, readonly) NSDate *expiryDate; + @property (nonatomic, readonly) NSString *optional1; + @property (nonatomic, readonly) NSString *optional2; + + */ + -(NSString *)description { + return [NSString stringWithFormat:@"firstName: %@, lastName: %@, documentType: %@, documentSubtype: %@, issuingCountry: %@, documentNumber: %@, nationality: %@, birthDate: %@, gender: %@, expiryDate: %@", self.firstName, self.lastName, self.documentType, self.documentSubtype, self.issuingCountry, self.documentNumber, self.nationality, self.birthDate, self.gender, self.expiryDate]; + } + ++ (instancetype)parseLines:(NSArray *)lines + { + if (lines.count == 2) + { + if (lines[0].length != TGPassportTD23Length || lines[1].length != TGPassportTD23Length) + return nil; + + TGPassportMRZ *result = [[TGPassportMRZ alloc] init]; + result->_documentType = [lines[0] substringToIndex:1]; + result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]]; + result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]]; + + NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter]; + NSString *fullName = [[lines[0] substringWithRange:NSMakeRange(5, 39)] stringByTrimmingCharactersInSet:emptyCharacterSet]; + NSArray *names = [fullName componentsSeparatedByString:@"<<"]; + result->_lastName = [self nameString:names.firstObject]; + result->_firstName = [self nameString:names.lastObject]; + + if ([result->_documentType isEqualToString:@"P"] && [result->_documentSubtype isEqualToString:@"N"] && [result->_issuingCountry isEqualToString:@"RUS"]) + { + NSString *lastName = [self transliterateRussianMRZString:result->_lastName]; + result->_lastName = [self transliterateRussianName:lastName]; + NSString *firstName = [self transliterateRussianMRZString:result->_firstName]; + result->_firstName = [self transliterateRussianName:firstName]; + } + + NSString *documentNumber = [self ensureNumberString:[lines[1] substringToIndex:9]]; + NSInteger documentNumberCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(9, 1)]] integerValue]; + if ([self isDataValid:documentNumber check:documentNumberCheck]) + result->_documentNumber = documentNumber; + + result->_nationality = [lines[1] substringWithRange:NSMakeRange(10, 3)]; + NSString *birthDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(13, 6)]]; + NSInteger birthDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(19, 1)]] integerValue]; + if ([self isDataValid:birthDate check:birthDateCheck]) + result->_birthDate = [self dateFromString:birthDate]; + + NSString *gender = [lines[1] substringWithRange:NSMakeRange(20, 1)]; + if ([gender isEqualToString:TGPassportEmptyCharacter]) + gender = nil; + result->_gender = gender; + + NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(21, 6)]]; + NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(27, 1)]] integerValue]; + if ([self isDataValid:expiryDate check:expiryDateCheck]) + result->_expiryDate = [self dateFromString:expiryDate]; + + NSString *optional1 = [lines[1] substringWithRange:NSMakeRange(28, 14)]; + NSString *optional1CheckString = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(42, 1)]]; + NSInteger optional1CheckValue = [optional1CheckString isEqualToString:TGPassportEmptyCharacter] ? 0 : [optional1CheckString integerValue]; + if ([self isDataValid:optional1 check:optional1CheckValue]) + result->_optional1 = [self cleanString:optional1]; + + NSString *data = [NSString stringWithFormat:@"%@%d%@%d%@%d%@%@", documentNumber, (int)documentNumberCheck, birthDate, (int)birthDateCheck, expiryDate, (int)expiryDateCheck, optional1, optional1CheckString]; + NSInteger dataCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(43, 1)]] integerValue]; + if ([self isDataValid:data check:dataCheck]) + return result; + } + else if (lines.count == 3) + { + if (lines[0].length != TGPassportTD1Length || lines[1].length != TGPassportTD1Length || lines[2].length != TGPassportTD1Length) + return nil; + + TGPassportMRZ *result = [[TGPassportMRZ alloc] init]; + result->_documentType = [lines[0] substringToIndex:1]; + result->_documentSubtype = [self cleanString:[lines[0] substringWithRange:NSMakeRange(1, 1)]]; + result->_issuingCountry = [self cleanString:[lines[0] substringWithRange:NSMakeRange(2, 3)]]; + + NSString *documentNumber = [self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(5, 9)]]; + NSInteger documentNumberCheck = [[self ensureNumberString:[lines[0] substringWithRange:NSMakeRange(14, 1)]] integerValue]; + if ([self isDataValid:documentNumber check:documentNumberCheck]) + result->_documentNumber = documentNumber; + + NSString *optional1 = [lines[0] substringWithRange:NSMakeRange(15, 15)]; + result->_optional1 = [self cleanString:optional1]; + + NSString *birthDate = [self ensureNumberString:[lines[1] substringToIndex:6]]; + NSInteger birthDateCheck = [[lines[1] substringWithRange:NSMakeRange(6, 1)] integerValue]; + if ([self isDataValid:birthDate check:birthDateCheck]) + result->_birthDate = [self dateFromString:birthDate]; + + NSString *gender = [lines[1] substringWithRange:NSMakeRange(7, 1)]; + if ([gender isEqualToString:TGPassportEmptyCharacter]) + gender = nil; + result->_gender = gender; + + NSString *expiryDate = [self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(8, 6)]]; + NSInteger expiryDateCheck = [[self ensureNumberString:[lines[1] substringWithRange:NSMakeRange(14, 1)]] integerValue]; + if ([self isDataValid:expiryDate check:expiryDateCheck]) + result->_expiryDate = [self dateFromString:expiryDate]; + + result->_nationality = [lines[1] substringWithRange:NSMakeRange(15, 3)]; + + NSString *optional2 = [lines[1] substringWithRange:NSMakeRange(18, 11)]; + result->_optional2 = optional2; + + NSCharacterSet *emptyCharacterSet = [NSCharacterSet characterSetWithCharactersInString:TGPassportEmptyCharacter]; + NSString *fullName = [self ensureAlphaString:lines[2]]; + fullName = [fullName stringByTrimmingCharactersInSet:emptyCharacterSet]; + NSArray *names = [fullName componentsSeparatedByString:@"<<"]; + result->_lastName = [self nameString:names.firstObject]; + result->_firstName = [self nameString:names.lastObject]; + + return result; + } + return nil; + } + ++ (NSDateFormatter *)dateFormatter + { + static dispatch_once_t onceToken; + static NSDateFormatter *dateFormatter; + dispatch_once(&onceToken, ^ + { + dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"YYMMdd"; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + }); + return dateFormatter; + } + + ++ (NSDate *)dateFromString:(NSString *)string + { + return [[self dateFormatter] dateFromString:string]; + } + ++ (NSString *)cleanString:(NSString *)string + { + return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@""]; + } + ++ (NSString *)nameString:(NSString *)string + { + return [string stringByReplacingOccurrencesOfString:TGPassportEmptyCharacter withString:@" "]; + } + ++ (NSString *)ensureNumberString:(NSString *)string + { + return [[[[string stringByReplacingOccurrencesOfString:@"O" withString:@"0"] stringByReplacingOccurrencesOfString:@"U" withString:@"0"] stringByReplacingOccurrencesOfString:@"Q" withString:@"0"] stringByReplacingOccurrencesOfString:@"J" withString:@"0"]; + } + ++ (NSString *)ensureAlphaString:(NSString *)string + { + NSCharacterSet *validChars = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZ<"]; + NSCharacterSet *invalidChars = [validChars invertedSet]; + + string = [string stringByReplacingOccurrencesOfString:@"0" withString:@"O"]; + return [self string:string byReplacingCharactersInSet:invalidChars withString:TGPassportEmptyCharacter]; + } + ++ (NSString *)string:(NSString *)string byReplacingCharactersInSet:(NSCharacterSet *)charSet withString:(NSString *)aString { + NSMutableString *s = [NSMutableString stringWithCapacity:string.length]; + for (NSUInteger i = 0; i < string.length; ++i) { + unichar c = [string characterAtIndex:i]; + if (![charSet characterIsMember:c]) { + [s appendFormat:@"%C", c]; + } else { + [s appendString:aString]; + } + } + return s; +} + ++ (NSString *)transliterateRussianMRZString:(NSString *)string + { + NSDictionary *map = @ + { + @"A": @"А", + @"B": @"Б", + @"V": @"В", + @"G": @"Г", + @"D": @"Д", + @"E": @"Е", + @"2": @"Ё", + @"J": @"Ж", + @"Z": @"З", + @"I": @"И", + @"Q": @"Й", + @"K": @"К", + @"L": @"Л", + @"M": @"М", + @"N": @"Н", + @"O": @"О", + @"P": @"П", + @"R": @"Р", + @"S": @"С", + @"T": @"Т", + @"U": @"У", + @"F": @"Ф", + @"H": @"Х", + @"C": @"Ц", + @"3": @"Ч", + @"4": @"Ш", + @"W": @"Щ", + @"X": @"Ъ", + @"Y": @"Ы", + @"9": @"Ь", + @"6": @"Э", + @"7": @"Ю", + @"8": @"Я" + }; + + NSMutableString *result = [[NSMutableString alloc] init]; + [string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) + { + if (substring == nil) + return; + + NSString *letter = map[substring]; + if (letter != nil) + [result appendString:letter]; + }]; + return result; + } + ++ (NSString *)transliterateRussianName:(NSString *)string + { + NSDictionary *map = @ + { + @"А": @"A", + @"Б": @"B", + @"В": @"V", + @"Г": @"G", + @"Д": @"D", + @"Е": @"E", + @"Ё": @"E", + @"Ж": @"ZH", + @"З": @"Z", + @"И": @"I", + @"Й": @"I", + @"К": @"K", + @"Л": @"L", + @"М": @"M", + @"Н": @"N", + @"О": @"O", + @"П": @"P", + @"Р": @"R", + @"С": @"S", + @"Т": @"T", + @"У": @"U", + @"Ф": @"F", + @"Х": @"KH", + @"Ц": @"TS", + @"Ч": @"CH", + @"Ш": @"SH", + @"Щ": @"SHCH", + @"Ъ": @"IE", + @"Ы": @"Y", + @"Ь": @"", + @"Э": @"E", + @"Ю": @"IU", + @"Я": @"IA" + }; + + NSMutableString *result = [[NSMutableString alloc] init]; + [string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) + { + if (substring == nil) + return; + + NSString *letter = map[substring]; + if (letter != nil) + [result appendString:letter]; + }]; + return result; + } + ++ (bool)isDataValid:(NSString *)data check:(NSInteger)check + { + int32_t sum = 0; + uint8_t w[3] = { 7, 3, 1 }; + + for (NSUInteger i = 0; i < data.length; i++) + { + unichar c = [data characterAtIndex:i]; + NSInteger d = 0; + if (c >= '0' && c <= '9') + d = c - '0'; + else if (c >= 'A' && c <= 'Z') + d = (10 + c) - 'A'; + else if (c != '<') + return false; + + sum += d * w[i % 3]; + } + + if (sum % 10 != check) + return false; + + return true; + } + + @end diff --git a/thrid-party/objc/ocr/fast-edge.cpp b/thrid-party/objc/ocr/fast-edge.cpp new file mode 100755 index 0000000000..0e67626501 --- /dev/null +++ b/thrid-party/objc/ocr/fast-edge.cpp @@ -0,0 +1,550 @@ +/* + FAST-EDGE + Copyright (c) 2009 Benjamin C. Haynor + + 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 +#include +#include "fast-edge.h" + +#define LOW_THRESHOLD_PERCENTAGE 0.8 // percentage of the high threshold value that the low threshold shall be set at +#define PI 3.14159265 +#define HIGH_THRESHOLD_PERCENTAGE 0.10 // percentage of pixels that meet the high threshold - for example 0.15 will ensure that at least 15% of edge pixels are considered to meet the high threshold + +#define min(X,Y) ((X) < (Y) ? (X) : (Y)) +#define max(X,Y) ((X) < (Y) ? (Y) : (X)) + +namespace ocr{ +/* + CANNY EDGE DETECT + DOES NOT PERFORM NOISE REDUCTION - PERFORM NOISE REDUCTION PRIOR TO USE + Noise reduction omitted, as some applications benefit from morphological operations such as opening or closing as opposed to Gaussian noise reduction + If your application always takes the same size input image, uncomment the definitions of WIDTH and HEIGHT in the header file and define them to the size of your input image, + otherwise the required intermediate arrays will be dynamically allocated. + If WIDTH and HEIGHT are defined, the arrays will be allocated in the compiler directive that follows: +*/ +#ifdef WIDTH +int g[WIDTH * HEIGHT], dir[WIDTH * HEIGHT] = {0}; +unsigned char img_scratch_data[WIDTH * HEIGHT] = {0}; +#endif +void canny_edge_detect(struct image * img_in, struct image * img_out) { + struct image img_scratch; + int high, low; + #ifndef WIDTH + int * g = (int*)calloc(static_cast(img_in->width*img_in->height), sizeof(int)); + int * dir = (int*)calloc(static_cast(img_in->width*img_in->height), sizeof(int)); + unsigned char * img_scratch_data = (unsigned char*)calloc(static_cast(img_in->width*img_in->height), sizeof(unsigned char)); + #endif + img_scratch.width = img_in->width; + img_scratch.height = img_in->height; + img_scratch.pixel_data = img_scratch_data; + calc_gradient_sobel(img_in, g, dir); + //printf("*** performing non-maximum suppression ***\n"); + non_max_suppression(&img_scratch, g, dir); + estimate_threshold(&img_scratch, &high, &low); + hysteresis(high, low, &img_scratch, img_out); + #ifndef WIDTH + free(g); + free(dir); + free(img_scratch_data); + #endif +} + +/* + GAUSSIAN_NOISE_ REDUCE + apply 5x5 Gaussian convolution filter, shrinks the image by 4 pixels in each direction, using Gaussian filter found here: + http://en.wikipedia.org/wiki/Canny_edge_detector +*/ +void gaussian_noise_reduce(struct image * img_in, struct image * img_out) +{ + #ifdef CLOCK + clock_t start = clock(); + #endif + int w, h, x, y, max_x, max_y; + w = img_in->width; + h = img_in->height; + img_out->width = w; + img_out->height = h; + max_x = w - 2; + max_y = w * (h - 2); + for (y = w * 2; y < max_y; y += w) { + for (x = 2; x < max_x; x++) { + img_out->pixel_data[x + y] = (2 * img_in->pixel_data[x + y - 2 - w - w] + + 4 * img_in->pixel_data[x + y - 1 - w - w] + + 5 * img_in->pixel_data[x + y - w - w] + + 4 * img_in->pixel_data[x + y + 1 - w - w] + + 2 * img_in->pixel_data[x + y + 2 - w - w] + + 4 * img_in->pixel_data[x + y - 2 - w] + + 9 * img_in->pixel_data[x + y - 1 - w] + + 12 * img_in->pixel_data[x + y - w] + + 9 * img_in->pixel_data[x + y + 1 - w] + + 4 * img_in->pixel_data[x + y + 2 - w] + + 5 * img_in->pixel_data[x + y - 2] + + 12 * img_in->pixel_data[x + y - 1] + + 15 * img_in->pixel_data[x + y] + + 12 * img_in->pixel_data[x + y + 1] + + 5 * img_in->pixel_data[x + y + 2] + + 4 * img_in->pixel_data[x + y - 2 + w] + + 9 * img_in->pixel_data[x + y - 1 + w] + + 12 * img_in->pixel_data[x + y + w] + + 9 * img_in->pixel_data[x + y + 1 + w] + + 4 * img_in->pixel_data[x + y + 2 + w] + + 2 * img_in->pixel_data[x + y - 2 + w + w] + + 4 * img_in->pixel_data[x + y - 1 + w + w] + + 5 * img_in->pixel_data[x + y + w + w] + + 4 * img_in->pixel_data[x + y + 1 + w + w] + + 2 * img_in->pixel_data[x + y + 2 + w + w]) / 159; + } + } + #ifdef CLOCK + printf("Gaussian noise reduction - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +/* + CALC_GRADIENT_SOBEL + calculates the result of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle +*/ +/*void calc_gradient_sobel(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) {*/ +void calc_gradient_sobel(struct image * img_in, int g[], int dir[]) { + #ifdef CLOCK + clock_t start = clock(); + #endif + int w, h, x, y, max_x, max_y, g_x, g_y; + float g_div; + w = img_in->width; + h = img_in->height; + max_x = w - 3; + max_y = w * (h - 3); + for (y = w * 3; y < max_y; y += w) { + for (x = 3; x < max_x; x++) { + g_x = (2 * img_in->pixel_data[x + y + 1] + + img_in->pixel_data[x + y - w + 1] + + img_in->pixel_data[x + y + w + 1] + - 2 * img_in->pixel_data[x + y - 1] + - img_in->pixel_data[x + y - w - 1] + - img_in->pixel_data[x + y + w - 1]); + g_y = 2 * img_in->pixel_data[x + y - w] + + img_in->pixel_data[x + y - w + 1] + + img_in->pixel_data[x + y - w - 1] + - 2 * img_in->pixel_data[x + y + w] + - img_in->pixel_data[x + y + w + 1] + - img_in->pixel_data[x + y + w - 1]; + #ifndef ABS_APPROX + g[x + y] = sqrt(g_x * g_x + g_y * g_y); + #endif + #ifdef ABS_APPROX + g[x + y] = abs(g_x[x + y]) + abs(g_y[x + y]); + #endif + if (g_x == 0) { + dir[x + y] = 2; + } else { + g_div = g_y / (float) g_x; + /* the following commented-out code is slightly faster than the code that follows, but is a slightly worse approximation for determining the edge direction angle + if (g_div < 0) { + if (g_div < -1) { + dir[n] = 0; + } else { + dir[n] = 1; + } + } else { + if (g_div > 1) { + dir[n] = 0; + } else { + dir[n] = 3; + } + } + */ + if (g_div < 0) { + if (g_div < -2.41421356237) { + dir[x + y] = 0; + } else { + if (g_div < -0.414213562373) { + dir[x + y] = 1; + } else { + dir[x + y] = 2; + } + } + } else { + if (g_div > 2.41421356237) { + dir[x + y] = 0; + } else { + if (g_div > 0.414213562373) { + dir[x + y] = 3; + } else { + dir[x + y] = 2; + } + } + } + } + } + + } + #ifdef CLOCK + printf("Calculate gradient Sobel - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +/* + CALC_GRADIENT_SCHARR + calculates the result of the Scharr version of the Sobel operator - http://en.wikipedia.org/wiki/Sobel_operator - and estimates edge direction angle + may have better rotational symmetry +*/ +void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]) {//float theta[]) { + #ifdef CLOCK + clock_t start = clock(); + #endif + int w, h, x, y, max_x, max_y, n; + float g_div; + w = img_in->width; + h = img_in->height; + max_x = w - 1; + max_y = w * (h - 1); + n = 0; + for (y = w; y < max_y; y += w) { + for (x = 1; x < max_x; x++) { + g_x[n] = (10 * img_in->pixel_data[x + y + 1] + + 3 * img_in->pixel_data[x + y - w + 1] + + 3 * img_in->pixel_data[x + y + w + 1] + - 10 * img_in->pixel_data[x + y - 1] + - 3 * img_in->pixel_data[x + y - w - 1] + - 3 * img_in->pixel_data[x + y + w - 1]); + g_y[n] = 10 * img_in->pixel_data[x + y - w] + + 3 * img_in->pixel_data[x + y - w + 1] + + 3 * img_in->pixel_data[x + y - w - 1] + - 10 * img_in->pixel_data[x + y + w] + - 3 * img_in->pixel_data[x + y + w + 1] + - 3 * img_in->pixel_data[x + y + w - 1]; + #ifndef ABS_APPROX + g[n] = sqrt(g_x[n] * g_x[n] + g_y[n] * g_y[n]); + #endif + #ifdef ABS_APPROX + g[n] = abs(g_x[n]) + abs(g_y[n]); + #endif + if (g_x[n] == 0) { + dir[n] = 2; + } else { + g_div = g_y[n] / (float) g_x[n]; + if (g_div < 0) { + if (g_div < -2.41421356237) { + dir[n] = 0; + } else { + if (g_div < -0.414213562373) { + dir[n] = 1; + } else { + dir[n] = 2; + } + } + } else { + if (g_div > 2.41421356237) { + dir[n] = 0; + } else { + if (g_div > 0.414213562373) { + dir[n] = 3; + } else { + dir[n] = 2; + } + } + } + } + n++; + } + } + #ifdef CLOCK + printf("Calculate gradient Scharr - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} +/* + NON_MAX_SUPPRESSION + using the estimates of the Gx and Gy image gradients and the edge direction angle determines whether the magnitude of the gradient assumes a local maximum in the gradient direction + if the rounded edge direction angle is 0 degrees, checks the north and south directions + if the rounded edge direction angle is 45 degrees, checks the northwest and southeast directions + if the rounded edge direction angle is 90 degrees, checks the east and west directions + if the rounded edge direction angle is 135 degrees, checks the northeast and southwest directions +*/ +void non_max_suppression(struct image * img, int g[], int dir[]) {//float theta[]) { + #ifdef CLOCK + clock_t start = clock(); + #endif + int w, h, x, y, max_x, max_y; + w = img->width; + h = img->height; + max_x = w; + max_y = w * h; + for (y = 0; y < max_y; y += w) { + for (x = 0; x < max_x; x++) { + switch (dir[x + y]) { + case 0: + if(x+y-w-1<0){ + continue; + } + if (g[x + y] > g[x + y - w] && g[x + y] > g[x + y + w]) { + if (g[x + y] > 255) { + img->pixel_data[x + y] = 0xFF; + } else { + img->pixel_data[x + y] = g[x + y]; + } + } else { + img->pixel_data[x + y] = 0x00; + } + break; + case 1: + if(x+y-w-1<0){ + continue; + } + if (g[x + y] > g[x + y - w - 1] && g[x + y] > g[x + y + w + 1]) { + if (g[x + y] > 255) { + img->pixel_data[x + y] = 0xFF; + } else { + img->pixel_data[x + y] = g[x + y]; + } + } else { + img->pixel_data[x + y] = 0x00; + } + break; + case 2: + if (g[x + y] > g[x + y - 1] && g[x + y] > g[x + y + 1]) { + if (g[x + y] > 255) { + img->pixel_data[x + y] = 0xFF; + } else { + img->pixel_data[x + y] = g[x + y]; + } + } else { + img->pixel_data[x + y] = 0x00; + } + break; + case 3: + if(x+y-w-1<0){ + continue; + } + if (g[x + y] > g[x + y - w + 1] && g[x + y] > g[x + y + w - 1]) { + if (g[x + y] > 255) { + img->pixel_data[x + y] = 0xFF; + } else { + img->pixel_data[x + y] = g[x + y]; + } + } else { + img->pixel_data[x + y] = 0x00; + } + break; + default: + printf("ERROR - direction outside range 0 to 3"); + break; + } + } + } + #ifdef CLOCK + printf("Non-maximum suppression - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} +/* + ESTIMATE_THRESHOLD + estimates hysteresis threshold, assuming that the top X% (as defined by the HIGH_THRESHOLD_PERCENTAGE) of edge pixels with the greatest intesity are true edges + and that the low threshold is equal to the quantity of the high threshold plus the total number of 0s at the low end of the histogram divided by 2 +*/ +void estimate_threshold(struct image * img, int * high, int * low) { + #ifdef CLOCK + clock_t start = clock(); + #endif + int i, max, pixels, high_cutoff; + int histogram[256]; + max = img->width * img->height; + for (i = 0; i < 256; i++) { + histogram[i] = 0; + } + for (i = 0; i < max; i++) { + histogram[img->pixel_data[i]]++; + } + pixels = (max - histogram[0]) * HIGH_THRESHOLD_PERCENTAGE; + high_cutoff = 0; + i = 255; + while (high_cutoff < pixels) { + high_cutoff += histogram[i]; + i--; + } + *high = i; + i = 1; + while (histogram[i] == 0) { + i++; + } + *low = (*high + i) * LOW_THRESHOLD_PERCENTAGE; + #ifdef PRINT_HISTOGRAM + for (i = 0; i < 256; i++) { + printf("i %d count %d\n", i, histogram[i]); + } + #endif + + #ifdef CLOCK + printf("Estimate threshold - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +void hysteresis (int high, int low, struct image * img_in, struct image * img_out) +{ + #ifdef CLOCK + clock_t start = clock(); + #endif + int x, y, n, max; + max = img_in->width * img_in->height; + for (n = 0; n < max; n++) { + img_out->pixel_data[n] = 0x00; + } + for (y=0; y < img_out->height; y++) { + for (x=0; x < img_out->width; x++) { + if (img_in->pixel_data[y * img_out->width + x] >= high) { + trace (x, y, low, img_in, img_out); + } + } + } + #ifdef CLOCK + printf("Hysteresis - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +int trace(int x, int y, int low, struct image * img_in, struct image * img_out) +{ + int y_off, x_off;//, flag; + if (img_out->pixel_data[y * img_out->width + x] == 0) + { + img_out->pixel_data[y * img_out->width + x] = 0xFF; + for (y_off = -1; y_off <=1; y_off++) + { + for(x_off = -1; x_off <= 1; x_off++) + { + if (!(y == 0 && x_off == 0) && range(img_in, x + x_off, y + y_off) && img_in->pixel_data[(y + y_off) * img_out->width + x + x_off] >= low) { + if (trace(x + x_off, y + y_off, low, img_in, img_out)) + { + return(1); + } + } + } + } + return(1); + } + return(0); +} + +int range(struct image * img, int x, int y) +{ + if ((x < 0) || (x >= img->width)) { + return(0); + } + if ((y < 0) || (y >= img->height)) { + return(0); + } + return(1); +} + +void dilate_1d_h(struct image * img, struct image * img_out) { + int x, y, offset, y_max; + y_max = img->height * (img->width - 2); + for (y = 2 * img->width; y < y_max; y += img->width) { + for (x = 2; x < img->width - 2; x++) { + offset = x + y; + img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]); + } + } +} + +void dilate_1d_v(struct image * img, struct image * img_out) { + int x, y, offset, y_max; + y_max = img->height * (img->width - 2); + for (y = 2 * img->width; y < y_max; y += img->width) { + for (x = 2; x < img->width - 2; x++) { + offset = x + y; + img_out->pixel_data[offset] = max(max(max(max(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]); + } + } +} + +void erode_1d_h(struct image * img, struct image * img_out) { + int x, y, offset, y_max; + y_max = img->height * (img->width - 2); + for (y = 2 * img->width; y < y_max; y += img->width) { + for (x = 2; x < img->width - 2; x++) { + offset = x + y; + img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2], img->pixel_data[offset-1]), img->pixel_data[offset]), img->pixel_data[offset+1]), img->pixel_data[offset+2]); + } + } +} + +void erode_1d_v(struct image * img, struct image * img_out) { + int x, y, offset, y_max; + y_max = img->height * (img->width - 2); + for (y = 2 * img->width; y < y_max; y += img->width) { + for (x = 2; x < img->width - 2; x++) { + offset = x + y; + img_out->pixel_data[offset] = min(min(min(min(img->pixel_data[offset-2 * img->width], img->pixel_data[offset-img->width]), img->pixel_data[offset]), img->pixel_data[offset+img->width]), img->pixel_data[offset+2*img->width]); + } + } +} + +void erode(struct image * img_in, struct image * img_scratch, struct image * img_out) { + #ifdef CLOCK + clock_t start = clock(); + #endif + erode_1d_h(img_in, img_scratch); + erode_1d_v(img_scratch, img_out); + #ifdef CLOCK + printf("Erosion - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out) { + #ifdef CLOCK + clock_t start = clock(); + #endif + dilate_1d_h(img_in, img_scratch); + dilate_1d_v(img_scratch, img_out); + #ifdef CLOCK + printf("Dilation - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) { + #ifdef CLOCK + clock_t start = clock(); + #endif + erode(img_in, img_scratch, img_scratch2); + dilate(img_scratch2, img_scratch, img_out); + #ifdef CLOCK + printf("Morphological opening - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out) { + #ifdef CLOCK + clock_t start = clock(); + #endif + dilate(img_in, img_scratch, img_scratch2); + erode(img_scratch2, img_scratch, img_out); + #ifdef CLOCK + printf("Morphological closing - time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC); + #endif +} + +} diff --git a/thrid-party/objc/ocr/fast-edge.h b/thrid-party/objc/ocr/fast-edge.h new file mode 100755 index 0000000000..4ebee6c545 --- /dev/null +++ b/thrid-party/objc/ocr/fast-edge.h @@ -0,0 +1,61 @@ +/* + FAST-EDGE + Copyright (c) 2009 Benjamin C. Haynor + + 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 _FASTEDGE +#define _FASTEDGE + +namespace ocr{ +//#define WIDTH 640 // uncomment to define width for situations where width is always known +//#define HEIGHT 480 // uncomment to define heigh for situations where height is always known + +//#define CLOCK // uncomment to show running times of image processing functions (in seconds) +//#define ABS_APPROX // uncomment to use the absolute value approximation of sqrt(Gx ^ 2 + Gy ^2) +//#define PRINT_HISTOGRAM // uncomment to print the histogram used to estimate the threshold + struct image { + int width; + int height; + unsigned char * pixel_data; + }; + +void canny_edge_detect(struct image * img_in, struct image * img_out); +void gaussian_noise_reduce(struct image * img_in, struct image * img_out); +void calc_gradient_sobel(struct image * img_in, int g[], int dir[]); +void calc_gradient_scharr(struct image * img_in, int g_x[], int g_y[], int g[], int dir[]); +void non_max_suppression(struct image * img, int g[], int dir[]); +void estimate_threshold(struct image * img, int * high, int * low); +void hysteresis (int high, int low, struct image * img_in, struct image * img_out); +int trace (int x, int y, int low, struct image * img_in, struct image * img_out); +int range (struct image * img, int x, int y); +void dilate_1d_h(struct image * img, struct image * img_out); +void dilate_1d_v(struct image * img, struct image * img_out); +void erode_1d_h(struct image * img, struct image * img_out); +void erode_1d_v(struct image * img, struct image * img_out); +void erode(struct image * img_in, struct image * img_scratch, struct image * img_out); +void dilate(struct image * img_in, struct image * img_scratch, struct image * img_out); +void morph_open(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out); +void morph_close(struct image * img_in, struct image * img_scratch, struct image * img_scratch2, struct image * img_out); +} +#endif diff --git a/thrid-party/objc/ocr/genann.c b/thrid-party/objc/ocr/genann.c new file mode 100755 index 0000000000..11c80bbaf9 --- /dev/null +++ b/thrid-party/objc/ocr/genann.c @@ -0,0 +1,357 @@ +/* + * GENANN - Minimal C Artificial Neural Network + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + +#include "genann.h" + +#include +#include +#include +#include +#include + +#define LOOKUP_SIZE 4096 + +double genann_act_sigmoid(double a) { + if (a < -45.0) return 0; + if (a > 45.0) return 1; + return 1.0 / (1 + exp(-a)); +} + + +double genann_act_sigmoid_cached(double a) { + /* If you're optimizing for memory usage, just + * delete this entire function and replace references + * of genann_act_sigmoid_cached to genann_act_sigmoid + */ + const double min = -15.0; + const double max = 15.0; + static double interval; + static int initialized = 0; + static double lookup[LOOKUP_SIZE]; + + /* Calculate entire lookup table on first run. */ + if (!initialized) { + interval = (max - min) / LOOKUP_SIZE; + int i; + for (i = 0; i < LOOKUP_SIZE; ++i) { + lookup[i] = genann_act_sigmoid(min + interval * i); + } + /* This is down here to make this thread safe. */ + initialized = 1; + } + + int i; + i = (int)((a-min)/interval+0.5); + if (i <= 0) return lookup[0]; + if (i >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE-1]; + return lookup[i]; +} + + +double genann_act_threshold(double a) { + return a > 0; +} + + +double genann_act_linear(double a) { + return a; +} + + +genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) { + if (hidden_layers < 0) return 0; + if (inputs < 1) return 0; + if (outputs < 1) return 0; + if (hidden_layers > 0 && hidden < 1) return 0; + + + const int hidden_weights = hidden_layers ? (inputs+1) * hidden + (hidden_layers-1) * (hidden+1) * hidden : 0; + const int output_weights = (hidden_layers ? (hidden+1) : (inputs+1)) * outputs; + const int total_weights = (hidden_weights + output_weights); + + const int total_neurons = (inputs + hidden * hidden_layers + outputs); + + /* Allocate extra size for weights, outputs, and deltas. */ + const int size = sizeof(genann) + sizeof(double) * (total_weights + total_neurons + (total_neurons - inputs)); + genann *ret = malloc(size); + if (!ret) return 0; + + ret->inputs = inputs; + ret->hidden_layers = hidden_layers; + ret->hidden = hidden; + ret->outputs = outputs; + + ret->total_weights = total_weights; + ret->total_neurons = total_neurons; + + /* Set pointers. */ + ret->weight = (double*)((char*)ret + sizeof(genann)); + ret->output = ret->weight + ret->total_weights; + ret->delta = ret->output + ret->total_neurons; + + genann_randomize(ret); + + ret->activation_hidden = genann_act_sigmoid_cached; + ret->activation_output = genann_act_sigmoid_cached; + + return ret; +} + + +genann *genann_read(FILE *in) { + int inputs, hidden_layers, hidden, outputs; + fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs); + + genann *ann = genann_init(inputs, hidden_layers, hidden, outputs); + + int i; + for (i = 0; i < ann->total_weights; ++i) { + fscanf(in, " %le", ann->weight + i); + } + + return ann; +} + + +genann *genann_copy(genann const *ann) { + const int size = sizeof(genann) + sizeof(double) * (ann->total_weights + ann->total_neurons + (ann->total_neurons - ann->inputs)); + genann *ret = malloc(size); + if (!ret) return 0; + + memcpy(ret, ann, size); + + /* Set pointers. */ + ret->weight = (double*)((char*)ret + sizeof(genann)); + ret->output = ret->weight + ret->total_weights; + ret->delta = ret->output + ret->total_neurons; + + return ret; +} + + +void genann_randomize(genann *ann) { + int i; + for (i = 0; i < ann->total_weights; ++i) { + double r = GENANN_RANDOM(); + /* Sets weights from -0.5 to 0.5. */ + ann->weight[i] = r - 0.5; + } +} + + +void genann_free(genann *ann) { + /* The weight, output, and delta pointers go to the same buffer. */ + free(ann); +} + + +double const *genann_run(genann const *ann, double const *inputs) { + double const *w = ann->weight; + double *o = ann->output + ann->inputs; + double const *i = ann->output; + + /* Copy the inputs to the scratch area, where we also store each neuron's + * output, for consistency. This way the first layer isn't a special case. */ + memcpy(ann->output, inputs, sizeof(double) * ann->inputs); + + int h, j, k; + + const genann_actfun act = ann->activation_hidden; + const genann_actfun acto = ann->activation_output; + + /* Figure hidden layers, if any. */ + for (h = 0; h < ann->hidden_layers; ++h) { + for (j = 0; j < ann->hidden; ++j) { + double sum = 0; + for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) { + if (k == 0) { + sum += *w++ * -1.0; + } else { + sum += *w++ * i[k-1]; + } + } + *o++ = act(sum); + } + + + i += (h == 0 ? ann->inputs : ann->hidden); + } + + double const *ret = o; + + /* Figure output layer. */ + for (j = 0; j < ann->outputs; ++j) { + double sum = 0; + for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { + if (k == 0) { + sum += *w++ * -1.0; + } else { + sum += *w++ * i[k-1]; + } + } + *o++ = acto(sum); + } + + /* Sanity check that we used all weights and wrote all outputs. */ + assert(w - ann->weight == ann->total_weights); + assert(o - ann->output == ann->total_neurons); + + return ret; +} + + +void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate) { + /* To begin with, we must run the network forward. */ + genann_run(ann, inputs); + + int h, j, k; + + /* First set the output layer deltas. */ + { + double const *o = ann->output + ann->inputs + ann->hidden * ann->hidden_layers; /* First output. */ + double *d = ann->delta + ann->hidden * ann->hidden_layers; /* First delta. */ + double const *t = desired_outputs; /* First desired output. */ + + + /* Set output layer deltas. */ + if (ann->activation_output == genann_act_linear) { + for (j = 0; j < ann->outputs; ++j) { + *d++ = *t++ - *o++; + } + } else { + for (j = 0; j < ann->outputs; ++j) { + *d++ = (*t - *o) * *o * (1.0 - *o); + ++o; ++t; + } + } + } + + + /* Set hidden layer deltas, start on last layer and work backwards. */ + /* Note that loop is skipped in the case of hidden_layers == 0. */ + for (h = ann->hidden_layers - 1; h >= 0; --h) { + + /* Find first output and delta in this layer. */ + double const *o = ann->output + ann->inputs + (h * ann->hidden); + double *d = ann->delta + (h * ann->hidden); + + /* Find first delta in following layer (which may be hidden or output). */ + double const * const dd = ann->delta + ((h+1) * ann->hidden); + + /* Find first weight in following layer (which may be hidden or output). */ + double const * const ww = ann->weight + ((ann->inputs+1) * ann->hidden) + ((ann->hidden+1) * ann->hidden * (h)); + + for (j = 0; j < ann->hidden; ++j) { + + double delta = 0; + + for (k = 0; k < (h == ann->hidden_layers-1 ? ann->outputs : ann->hidden); ++k) { + const double forward_delta = dd[k]; + const int windex = k * (ann->hidden + 1) + (j + 1); + const double forward_weight = ww[windex]; + delta += forward_delta * forward_weight; + } + + *d = *o * (1.0-*o) * delta; + ++d; ++o; + } + } + + + /* Train the outputs. */ + { + /* Find first output delta. */ + double const *d = ann->delta + ann->hidden * ann->hidden_layers; /* First output delta. */ + + /* Find first weight to first output delta. */ + double *w = ann->weight + (ann->hidden_layers + ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * ann->hidden * (ann->hidden_layers-1)) + : (0)); + + /* Find first output in previous layer. */ + double const * const i = ann->output + (ann->hidden_layers + ? (ann->inputs + (ann->hidden) * (ann->hidden_layers-1)) + : 0); + + /* Set output layer weights. */ + for (j = 0; j < ann->outputs; ++j) { + for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { + if (k == 0) { + *w++ += *d * learning_rate * -1.0; + } else { + *w++ += *d * learning_rate * i[k-1]; + } + } + + ++d; + } + + assert(w - ann->weight == ann->total_weights); + } + + + /* Train the hidden layers. */ + for (h = ann->hidden_layers - 1; h >= 0; --h) { + + /* Find first delta in this layer. */ + double const *d = ann->delta + (h * ann->hidden); + + /* Find first input to this layer. */ + double const *i = ann->output + (h + ? (ann->inputs + ann->hidden * (h-1)) + : 0); + + /* Find first weight to this layer. */ + double *w = ann->weight + (h + ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * (ann->hidden) * (h-1)) + : 0); + + + for (j = 0; j < ann->hidden; ++j) { + for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) { + if (k == 0) { + *w++ += *d * learning_rate * -1.0; + } else { + *w++ += *d * learning_rate * i[k-1]; + } + } + ++d; + } + + } + +} + + +void genann_write(genann const *ann, FILE *out) { + fprintf(out, "%d %d %d %d", ann->inputs, ann->hidden_layers, ann->hidden, ann->outputs); + + int i; + for (i = 0; i < ann->total_weights; ++i) { + fprintf(out, " %.20e", ann->weight[i]); + } +} + + diff --git a/thrid-party/objc/ocr/genann.h b/thrid-party/objc/ocr/genann.h new file mode 100755 index 0000000000..3678ab60b4 --- /dev/null +++ b/thrid-party/objc/ocr/genann.h @@ -0,0 +1,110 @@ +/* + * GENANN - Minimal C Artificial Neural Network + * + * Copyright (c) 2015, 2016 Lewis Van Winkle + * + * http://CodePlea.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + + +#ifndef __GENANN_H__ +#define __GENANN_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef GENANN_RANDOM +/* We use the following for uniform random numbers between 0 and 1. + * If you have a better function, redefine this macro. */ +#define GENANN_RANDOM() (((double)rand())/RAND_MAX) +#endif + + +typedef double (*genann_actfun)(double a); + + +typedef struct genann { + /* How many inputs, outputs, and hidden neurons. */ + int inputs, hidden_layers, hidden, outputs; + + /* Which activation function to use for hidden neurons. Default: gennann_act_sigmoid_cached*/ + genann_actfun activation_hidden; + + /* Which activation function to use for output. Default: gennann_act_sigmoid_cached*/ + genann_actfun activation_output; + + /* Total number of weights, and size of weights buffer. */ + int total_weights; + + /* Total number of neurons + inputs and size of output buffer. */ + int total_neurons; + + /* All weights (total_weights long). */ + double *weight; + + /* Stores input array and output of each neuron (total_neurons long). */ + double *output; + + /* Stores delta of each hidden and output neuron (total_neurons - inputs long). */ + double *delta; + +} genann; + + + +/* Creates and returns a new ann. */ +genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs); + +/* Creates ANN from file saved with genann_write. */ +genann *genann_read(FILE *in); + +/* Sets weights randomly. Called by init. */ +void genann_randomize(genann *ann); + +/* Returns a new copy of ann. */ +genann *genann_copy(genann const *ann); + +/* Frees the memory used by an ann. */ +void genann_free(genann *ann); + +/* Runs the feedforward algorithm to calculate the ann's output. */ +double const *genann_run(genann const *ann, double const *inputs); + +/* Does a single backprop update. */ +void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate); + +/* Saves the ann. */ +void genann_write(genann const *ann, FILE *out); + + +double genann_act_sigmoid(double a); +double genann_act_sigmoid_cached(double a); +double genann_act_threshold(double a); +double genann_act_linear(double a); + + +#ifdef __cplusplus +} +#endif + +#endif /*__GENANN_H__*/ diff --git a/thrid-party/objc/ocr/ocr.h b/thrid-party/objc/ocr/ocr.h new file mode 100644 index 0000000000..edc49d8a6f --- /dev/null +++ b/thrid-party/objc/ocr/ocr.h @@ -0,0 +1,11 @@ +#import + +#ifdef __cplusplus +extern "C" { +#endif + +NSString *recognizeMRZ(CGImageRef input, CGRect *boundingRect); + +#ifdef __cplusplus +} +#endif diff --git a/thrid-party/objc/ocr/ocr.mm b/thrid-party/objc/ocr/ocr.mm new file mode 100755 index 0000000000..3374190b8f --- /dev/null +++ b/thrid-party/objc/ocr/ocr.mm @@ -0,0 +1,630 @@ +#import "ocr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "fast-edge.h" +#include "genann.h" +#import + +#ifndef max +#define max(a, b) (a>b ? a : b) +#define min(a, b) (a detectLines(struct image* img, int threshold){ + // The size of the neighbourhood in which to search for other local maxima + const int neighbourhoodSize = 4; + + // How many discrete values of theta shall we check? + const int maxTheta = 180; + + // Using maxTheta, work out the step + const double thetaStep = M_PI / maxTheta; + + int width=img->width; + int height=img->height; + // Calculate the maximum height the hough array needs to have + int houghHeight = (int) (sqrt(2.0) * max(height, width)) / 2; + + // Double the height of the hough array to cope with negative r values + int doubleHeight = 2 * houghHeight; + + // Create the hough array + int* houghArray = new int[maxTheta*doubleHeight]; + memset(houghArray, 0, sizeof(int)*maxTheta*doubleHeight); + + // Find edge points and vote in array + int centerX = width / 2; + int centerY = height / 2; + + // Count how many points there are + int numPoints = 0; + + // cache the values of sin and cos for faster processing + double* sinCache = new double[maxTheta]; + double* cosCache = new double[maxTheta]; + for (int t = 0; t < maxTheta; t++) { + double realTheta = t * thetaStep; + sinCache[t] = sin(realTheta); + cosCache[t] = cos(realTheta); + } + + // Now find edge points and update the hough array + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + // Find non-black pixels + if ((img->pixel_data[y*width+x] & 0x000000ff) != 0) { + // Go through each value of theta + for (int t = 0; t < maxTheta; t++) { + + //Work out the r values for each theta step + int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t])); + + // this copes with negative values of r + r += houghHeight; + + if (r < 0 || r >= doubleHeight) continue; + + // Increment the hough array + houghArray[t*doubleHeight+r]++; + + } + + numPoints++; + } + } + } + + // Initialise the vector of lines that we'll return + std::vector lines; + + // Only proceed if the hough array is not empty + if (numPoints == 0){ + delete[] houghArray; + delete[] sinCache; + delete[] cosCache; + return lines; + } + + // Search for local peaks above threshold to draw + for (int t = 0; t < maxTheta; t++) { + //loop: + for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) { + + // Only consider points above threshold + if (houghArray[t*doubleHeight+r] > threshold) { + + int peak = houghArray[t*doubleHeight+r]; + + // Check that this peak is indeed the local maxima + for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) { + for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) { + int dt = t + dx; + int dr = r + dy; + if (dt < 0) dt = dt + maxTheta; + else if (dt >= maxTheta) dt = dt - maxTheta; + if (houghArray[dt*doubleHeight+dr] > peak) { + // found a bigger point nearby, skip + goto loop; + } + } + } + + // calculate the true value of theta + double theta = t * thetaStep; + + // add the line to the vector + line l={theta, (double)r-houghHeight}; + lines.push_back(l); + + } + loop: + continue; + } + } + + delete[] houghArray; + delete[] sinCache; + delete[] cosCache; + return lines; + } +} + +NSDictionary *findCornerPoints(CGImage *bitmap) { + CGImageRef imageRef = bitmap; + uint32_t width = (uint32_t)CGImageGetWidth(imageRef); + uint32_t height = (uint32_t)CGImageGetHeight(imageRef); + + struct ocr::image imgIn, imgOut; + imgIn.width = imgOut.width = width; + imgIn.height = imgOut.height = height; + imgIn.pixel_data = (uint8_t *)malloc(width * height); + imgOut.pixel_data = (uint8_t *)calloc(width * height, 1); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char)); + NSUInteger bytesPerPixel = 4; + NSUInteger bytesPerRow = bytesPerPixel * width; + NSUInteger bitsPerComponent = 8; + CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colorSpace); + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); + + CGContextRelease(context); + + for(unsigned int y=0;y> 8) + ((px & 0xFF0000) >> 16))/3); + } + } + + ocr::canny_edge_detect(&imgIn, &imgOut); + + std::vector lines=ocr::detectLines(&imgOut, 100); + for(NSUInteger i = 0; i < width * height; i++) { + imgOut.pixel_data[i]/=2; + } + std::vector> parallelGroups; + for(int i = 0; i < 36; i++) { + parallelGroups.emplace_back(); + } + ocr::line *left = NULL; + ocr::line *right = NULL; + ocr::line *top = NULL; + ocr::line *bottom = NULL; + for(std::vector::iterator l = lines.begin(); l!= lines.end();) { + // remove lines at irrelevant angles + if(!(l->theta>M_PI*0.4 && l->thetathetatheta>M_PI*0.9)){ + l=lines.erase(l); + continue; + } + // remove vertical lines close to the middle of the image + if((l->thetatheta>M_PI*0.9) && (uint32_t)abs((int)l->r) < height / 4){ + l=lines.erase(l); + continue; + } + // find the leftmost and rightmost lines + if(l->thetatheta>M_PI*0.9){ + double rk=l->theta<0.5 ? 1.0 : -1.0; + if(!left || left->r>l->r*rk){ + left=&*l; + } + if(!right || right->rr*rk){ + right=&*l; + } + } + // group parallel-ish lines with 5-degree increments + parallelGroups[(uint32_t)floor(l->theta / M_PI * 36)].push_back(*l); + ++l; + } + + // the text on the page tends to produce a lot of parallel lines - so we assume the top & bottom edges of the page + // are topmost & bottommost lines in the largest group of horizontal lines + std::vector& largestParallelGroup=parallelGroups[0]; + for(std::vector>::iterator group=parallelGroups.begin();group!=parallelGroups.end();++group){ + if(largestParallelGroup.size()size()) + largestParallelGroup=*group; + } + + for(std::vector::iterator l=largestParallelGroup.begin();l!=largestParallelGroup.end();++l){ + // If the image is horizontal, we assume it's just the data page or an ID card so we're going for the topmost line. + // If it's vertical, it likely contains both the data page and the page adjacent to it so we're going for the line that is closest to the center of the image. + // Nobody in their right mind is going to be taking vertical pictures of ID cards, right? + if(width>height){ + if(!top || top->r>l->r){ + top=&*l; + } + }else{ + if(!top || fabs(l->r)r)){ + top=&*l; + } + } + if(!bottom || bottom->rr){ + bottom=&*l; + } + } + + bool foundTopLeft=false, foundTopRight=false, foundBottomLeft=false, foundBottomRight=false; + NSMutableDictionary *points = [[NSMutableDictionary alloc] init]; + + if(top && bottom && left && right){ + //LOGI("bottom theta %f", bottom->theta); + if(bottom->theta>1.65 || bottom->theta<1.55){ + //LOGD("left: %f, right: %f\n", left->r, right->r); + double centerX=width/2.0; + double centerY=height/2.0; + double ltsin=sin(left->theta); + double ltcos=cos(left->theta); + double rtsin=sin(right->theta); + double rtcos=cos(right->theta); + double ttsin=sin(top->theta); + double ttcos=cos(top->theta); + double btsin=sin(bottom->theta); + double btcos=cos(bottom->theta); + for (int y = -((int)height)/4; y < (int)height; y++) { + int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX); + int ty = (int) (((top->r - ((lx - centerX) * ttcos)) / ttsin) + centerY); + if(ty==y){ + points[@0]=@(lx); + points[@1]=@(y); + foundTopLeft=true; + if(foundTopRight) + break; + } + int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX); + ty = (int) (((top->r - ((rx - centerX) * ttcos)) / ttsin) + centerY); + if(ty==y){ + points[@2]=@(rx); + points[@3]=@(y); + foundTopRight=true; + if(foundTopLeft) + break; + } + } + for (int y = height+height/3; y>=0; y--) { + int lx = (int) (((left->r - ((y - centerY) * ltsin)) / ltcos) + centerX); + int by = (int) (((bottom->r - ((lx - centerX) * btcos)) / btsin) + centerY); + if(by==y){ + points[@4]=@(lx); + points[@5]=@(y); + foundBottomLeft=true; + if(foundBottomRight) + break; + } + int rx = (int) (((right->r - ((y - centerY) * rtsin)) / rtcos) + centerX); + by = (int) (((bottom->r - ((rx - centerX) * btcos)) / btsin) + centerY); + if(by==y){ + points[@6]=@(rx); + points[@7]=@(y); + foundBottomRight=true; + if(foundBottomLeft) + break; + } + } + }else{ + //LOGD("No perspective correction needed"); + } + } + + free(imgIn.pixel_data); + free(imgOut.pixel_data); + + if(foundTopLeft && foundTopRight && foundBottomLeft && foundBottomRight) { + return points; + } + return nil; +} + +#define Mask8(x) ( (x) & 0xFF ) +#define R(x) ( Mask8(x) ) +#define G(x) ( Mask8(x >> 8 ) ) +#define B(x) ( Mask8(x >> 16) ) + +NSArray *binarizeAndFindCharacters(CGImage *inBmp, CGImage **outBinaryImage) { + CGImageRef imageRef = inBmp; + uint32_t width = (uint32_t)CGImageGetWidth(imageRef); + uint32_t height = (uint32_t)CGImageGetHeight(imageRef); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 4, sizeof(unsigned char)); + NSUInteger bytesPerPixel = 4; + NSUInteger bytesPerRow = bytesPerPixel * width; + NSUInteger bitsPerComponent = 8; + CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNoneSkipFirst); + CGColorSpaceRelease(colorSpace); + CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); + CGContextRelease(context); + + uint8_t *outPixels = (uint8_t *)malloc(width * height * 1); + + uint32_t histogram[256]={0}; + uint32_t intensitySum=0; + for(unsigned int y=0;y best_sigma) { + best_sigma = sigma; + threshold = thresh; + } + } + + for(unsigned int y=0;y0 + && outPixels[width * y + x +1]==0 + && outPixels[width * y + x -1]==0 + && outPixels[width * (y + 1) + x]==0 + && outPixels[width * (y - 1) + x]==0){ + outPixels[width * y + x]=0; + } + } + } + + if (outBinaryImage != nil) + { + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + CGContextRef context = CGBitmapContextCreate(outPixels, width, height, 8, width, colorSpace, kCGImageAlphaNone); + CGColorSpaceRelease(colorSpace); + + CGImageRef imgRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + *outBinaryImage = imgRef; + } + // search from the bottom up for continuous areas of mostly empty pixels + unsigned int consecutiveEmptyRows=0; + std::vector> emptyAreaYs; + for(unsigned int y=height-1;y>=height/2;y--){ + unsigned int consecutiveEmptyPixels=0; + unsigned int maxEmptyPixels=0; + for(unsigned int x=0;xwidth/10*8){ + consecutiveEmptyRows++; + }else if(consecutiveEmptyRows>0){ + emptyAreaYs.emplace_back(y, y+consecutiveEmptyRows); + consecutiveEmptyRows=0; + } + } + + NSMutableArray *result = [[NSMutableArray alloc] init]; + // using the areas found above, do the same thing but horizontally and between them in an attempt to ultimately find the bounds of the MRZ characters + for(std::vector>::iterator p=emptyAreaYs.begin();p!=emptyAreaYs.end();++p){ + std::vector>::iterator next=std::next(p); + if(next!=emptyAreaYs.end()){ + unsigned int lineHeight=p->first-next->second; + // An MRZ line can't really be this thin so this probably isn't one + if(lineHeight<10) + continue; + unsigned int consecutiveEmptyCols=0; + std::vector> emptyAreaXs; + for(unsigned int x=0;xsecond;yfirst;y++){ + if(outPixels[width * y + x]==0){ + consecutiveEmptyPixels++; + }else{ + maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels); + consecutiveEmptyPixels=0; + if(y>p->first-3) + bottomFilledPixels++; + } + } + maxEmptyPixels=max(maxEmptyPixels, consecutiveEmptyPixels); + if(lineHeight-maxEmptyPixels0){ + emptyAreaXs.emplace_back(x-consecutiveEmptyCols, x); + consecutiveEmptyCols=0; + } + } + if(consecutiveEmptyCols>0){ + emptyAreaXs.emplace_back(width-consecutiveEmptyCols, width); + } + if(emptyAreaXs.size()>30){ + bool foundLeftPadding=false; + NSMutableArray *rects = [[NSMutableArray alloc] init]; + for(std::vector>::iterator h=emptyAreaXs.begin();h!=emptyAreaXs.end();++h){ + std::vector>::iterator nextH=std::next(h); + if(!foundLeftPadding && h->second-h->first>width/35){ + foundLeftPadding=true; + }else if(foundLeftPadding && h->second-h->first>width/30){ + if(rects.count>=30){ + break; + }else{ + // restart the search because now we've (hopefully) found the real padding + [rects removeAllObjects]; + } + } + if(nextH!=emptyAreaXs.end() && foundLeftPadding){ + unsigned int top=next->second; + unsigned int bottom=p->first; + // move the top and bottom edges towards each other as part of normalization + for(unsigned int y=top;ysecond; xfirst; x++){ + if(outPixels[width * y + x]!=0){ + top=y; + found=true; + break; + } + } + if(found) + break; + } + for(unsigned int y=bottom;y>top;y--){ + bool found=false; + for(unsigned int x=h->second; xfirst; x++){ + if(outPixels[width * y + x]!=0){ + bottom=y; + found=true; + break; + } + } + if(found) + break; + } + if(bottom-topsecond, top, nextH->first - h->second, bottom - top); + [rects addObject:[NSValue valueWithRect:rect]]; + } + } + } + [result addObject:rects]; + if((rects.count>=44 && result.count == 2) || (rects.count>=30 && result.count==3)){ + break; + } + } + } + } + + free(outPixels); + + if(result.count == 0) + return NULL; + + return result; +} + +NSString *performRecognition(CGImage *bitmap, int numRows, int numCols) +{ + NSString *filePath = [[NSBundle mainBundle] pathForResource:@"ocr_nn" ofType:@"bin"]; + NSData *nnData = [NSData dataWithContentsOfFile:filePath]; + + struct genann* ann=genann_init(150, 1, 90, 37); + memcpy(ann->weight, nnData.bytes, sizeof(double)*ann->total_weights); + + NSMutableString *res = [[NSMutableString alloc] init]; + const char* alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890<"; + + CGImageRef imageRef = bitmap; + uint32_t width = (uint32_t)CGImageGetWidth(imageRef); + uint32_t height = (uint32_t)CGImageGetHeight(imageRef); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + uint8_t *bitmapPixels = (uint8_t *)calloc(height * width * 1, sizeof(unsigned char)); + NSUInteger bytesPerPixel = 1; + NSUInteger bytesPerRow = bytesPerPixel * width; + NSUInteger bitsPerComponent = 8; + CGContextRef context = CGBitmapContextCreate(bitmapPixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaNone); + CGColorSpaceRelease(colorSpace); + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); + CGContextRelease(context); + + double nnInput[150]; + for(int row=0;row(col*10); + unsigned int offY=static_cast(row*15); + for(unsigned int y=0;y<15;y++){ + for(unsigned int x=0;x<10;x++){ + nnInput[y*10+x]=(double)bitmapPixels[bytesPerRow * (offY+y) + offX + x]/255.0; + } + } + const double* nnOut=genann_run(ann, nnInput); + unsigned int bestIndex=0; + for(unsigned int i=0;i<37;i++){ + if(nnOut[i]>nnOut[bestIndex]) + bestIndex=i; + } + + [res appendString:[NSString stringWithFormat:@"%c", alphabet[bestIndex]]]; + } + if(row!=numRows-1) + [res appendString:@"\n"]; + } + genann_free(ann); + return res; +} + +NSString *recognizeMRZ(CGImage *input, CGRect *outBoundingRect) +{ + CGImageRef binaryImage; + NSArray *charRects = binarizeAndFindCharacters(input, &binaryImage); + if (charRects.count == 0) + return nil; + + uint32_t width = 10 * (int)[charRects.firstObject count]; + uint32_t height = 15 * (int)charRects.count; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width, colorSpace, kCGImageAlphaNone); + CGColorSpaceRelease(colorSpace); + + int x, y = 0; + for (NSArray *line in charRects) + { + x = 0; + for (NSValue *v in line) + { + CGRect rect = v.rectValue; + CGRect dest = CGRectMake(x * 10, y * 15, 10, 15); + CGImageRef charImage = CGImageCreateWithImageInRect(binaryImage, rect); + CGContextDrawImage(context, dest, charImage); + CGImageRelease(charImage); + + x++; + } + y++; + } + CGImageRelease(binaryImage); + CGImageRef charsImageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + + NSString *result = performRecognition(charsImageRef, (int)charRects.count, (int)[charRects.firstObject count]); + if (result != nil && outBoundingRect != NULL) + { + CGRect firstRect = [[charRects.firstObject firstObject] CGRectValue]; + firstRect.origin.y = CGImageGetHeight(charsImageRef) - firstRect.origin.y; + CGRect lastRect = [[charRects.lastObject lastObject] rectValue]; + lastRect.origin.y = CGImageGetHeight(charsImageRef) - lastRect.origin.y; + CGRect boundingRect = CGRectMake(CGRectGetMinX(firstRect), CGRectGetMinY(firstRect), CGRectGetMaxX(lastRect) - CGRectGetMinX(firstRect), CGRectGetMaxY(lastRect) - CGRectGetMinY(firstRect)); + *outBoundingRect = boundingRect; + } + return result; +} diff --git a/thrid-party/objc/opengl/TGVideoCameraGLRenderer.m b/thrid-party/objc/opengl/TGVideoCameraGLRenderer.m index 461688025a..96aa748a3e 100644 --- a/thrid-party/objc/opengl/TGVideoCameraGLRenderer.m +++ b/thrid-party/objc/opengl/TGVideoCameraGLRenderer.m @@ -59,6 +59,7 @@ - (void)setupOffscreenRenderContext kCGLPFADoubleBuffer, kCGLPFASampleBuffers, (CGLPixelFormatAttribute)1, kCGLPFASamples, (CGLPixelFormatAttribute)4, + kCGLPFAAllowOfflineRenderers, (CGLPixelFormatAttribute)0 }; CGLPixelFormatObj pixelFormat = NULL; @@ -78,6 +79,8 @@ - (void)setupOffscreenRenderContext - (void)dealloc { + CGLSetCurrentContext(nil); + CGLReleaseContext(_currentContext); [self deleteBuffers]; } @@ -235,6 +238,7 @@ - (CVPixelBufferRef)copyRenderedPixelBuffer:(CVPixelBufferRef)pixelBuffer return NULL; CGLContextObj oldContext = CGLGetCurrentContext(); + if (oldContext != _currentContext) { CGLSetCurrentContext(_currentContext); } @@ -356,8 +360,6 @@ - (CVPixelBufferRef)copyRenderedPixelBuffer:(CVPixelBufferRef)pixelBuffer CGLUnlockContext(_currentContext); if (oldContext != _currentContext) { CGLSetCurrentContext(oldContext); - if (oldContext) - CFRelease(oldContext); } @@ -455,8 +457,6 @@ - (bool)initializeBuffersWithOutputSize:(CGSize)outputSize retainedBufferCountHi CGLUnlockContext(_currentContext); if (oldContext != _currentContext) { CGLSetCurrentContext(oldContext); - if (oldContext) - CFRelease(oldContext); } @@ -524,8 +524,6 @@ - (void)deleteBuffers if (oldContext != _currentContext) { CGLSetCurrentContext(oldContext); - if (oldContext) - CFRelease(oldContext); } diff --git a/thrid-party/objc/vimeo/YTVimeoVideo.h b/thrid-party/objc/vimeo/YTVimeoVideo.h index fd5034ec6c..31b1d245fd 100755 --- a/thrid-party/objc/vimeo/YTVimeoVideo.h +++ b/thrid-party/objc/vimeo/YTVimeoVideo.h @@ -127,14 +127,14 @@ It is very important that you do not create a subclass of `YTVimeoVideo` * @see YTVimeoVideoQuality * @return The highest quality stream URL. */ --(NSURL *)highestQualityStreamURL; +-(NSURL * __nullable)highestQualityStreamURL; /** * Extracts the lowest quality stream URL. * * @see YTVimeoVideoQuality * @return The lowest quality stream URL. */ --(NSURL *)lowestQualityStreamURL; +-(NSURL * __nullable)lowestQualityStreamURL; /** * The HTTP Live Stream URL for the video. */ diff --git a/thrid-party/objc/vimeo/YTVimeoVideo.m b/thrid-party/objc/vimeo/YTVimeoVideo.m index 5ba744ff76..06dc891919 100755 --- a/thrid-party/objc/vimeo/YTVimeoVideo.m +++ b/thrid-party/objc/vimeo/YTVimeoVideo.m @@ -115,22 +115,20 @@ - (void)extractVideoInfoWithCompletionHandler:(void (^)(NSError *error))completi }); } -#pragma mark - --(NSURL *)highestQualityStreamURL{ +-(NSURL * __nullable)highestQualityStreamURL{ NSURL *url = self.streamURLs[@(YTVimeoVideoQualityHD1080)] ?: self.streamURLs[@(YTVimeoVideoQualityHD720)]?: self.streamURLs[@(YTVimeoVideoQualityMedium540)]?: self.streamURLs [@(YTVimeoVideoQualityMedium480)]?: self.streamURLs[@(YTVimeoVideoQualityMedium360)]?:self.streamURLs[@(YTVimeoVideoQualityLow270)]; return url; } --(NSURL *)lowestQualityStreamURL{ +-(NSURL * __nullable)lowestQualityStreamURL{ NSURL *url = self.streamURLs[@(YTVimeoVideoQualityLow270)] ?: self.streamURLs[@(YTVimeoVideoQualityMedium360)] ?: self.streamURLs [@(YTVimeoVideoQualityMedium480)]?: self.streamURLs[@(YTVimeoVideoQualityMedium540)]?: self.streamURLs[@(YTVimeoVideoQualityHD720)]?:self.streamURLs[@(YTVimeoVideoQualityHD1080)]; return url; } -#pragma mark - NSObject - (BOOL) isEqual:(id)object { diff --git a/thrid-party/objc/youtube/XCDYouTubePlayerScript.m b/thrid-party/objc/youtube/XCDYouTubePlayerScript.m index 6736020425..9aaf021401 100755 --- a/thrid-party/objc/youtube/XCDYouTubePlayerScript.m +++ b/thrid-party/objc/youtube/XCDYouTubePlayerScript.m @@ -80,6 +80,10 @@ - (instancetype) initWithString:(NSString *)string return self; } +-(void)dealloc { + int bp = 0; + bp++; +} - (NSString *) unscrambleSignature:(NSString *)scrambledSignature { diff --git a/thrid-party/objc/youtube/XCDYouTubeVideoOperation.m b/thrid-party/objc/youtube/XCDYouTubeVideoOperation.m index ec971ddff9..ee24d152b4 100755 --- a/thrid-party/objc/youtube/XCDYouTubeVideoOperation.m +++ b/thrid-party/objc/youtube/XCDYouTubeVideoOperation.m @@ -298,6 +298,11 @@ - (void) finishWithError [self finish]; } +-(void)dealloc { + int bp = 0; + bp += 1; +} + - (void) finish { self.isExecuting = NO; diff --git a/thrid-party/openssl/lib/libcrypto.a b/thrid-party/openssl/lib/libcrypto.a new file mode 100644 index 0000000000..7343d9795f Binary files /dev/null and b/thrid-party/openssl/lib/libcrypto.a differ diff --git a/thrid-party/openssl/openssl/aes.h b/thrid-party/openssl/openssl/aes.h new file mode 100644 index 0000000000..245c552abd --- /dev/null +++ b/thrid-party/openssl/openssl/aes.h @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_AES_H +# define HEADER_AES_H + +# include + +# include +# ifdef __cplusplus +extern "C" { +# endif + +# define AES_ENCRYPT 1 +# define AES_DECRYPT 0 + +/* + * Because array size can't be a const in C, the following two are macros. + * Both sizes are in bytes. + */ +# define AES_MAXNR 14 +# define AES_BLOCK_SIZE 16 + +/* This should be a hidden type, but EVP requires that the size be known */ +struct aes_key_st { +# ifdef AES_LONG + unsigned long rd_key[4 * (AES_MAXNR + 1)]; +# else + unsigned int rd_key[4 * (AES_MAXNR + 1)]; +# endif + int rounds; +}; +typedef struct aes_key_st AES_KEY; + +const char *AES_options(void); + +int AES_set_encrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); +int AES_set_decrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); + +void AES_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); +void AES_decrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); + +void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key, const int enc); +void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, const int enc); +void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, int *num, const int enc); +void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, int *num, const int enc); +void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, int *num, const int enc); +void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, int *num); +/* NB: the IV is _two_ blocks long */ +void AES_ige_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + unsigned char *ivec, const int enc); +/* NB: the IV is _four_ blocks long */ +void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const AES_KEY *key, + const AES_KEY *key2, const unsigned char *ivec, + const int enc); + +int AES_wrap_key(AES_KEY *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, unsigned int inlen); +int AES_unwrap_key(AES_KEY *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, unsigned int inlen); + + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/thrid-party/openssl/openssl/asn1.h b/thrid-party/openssl/openssl/asn1.h new file mode 100644 index 0000000000..9522eec18f --- /dev/null +++ b/thrid-party/openssl/openssl/asn1.h @@ -0,0 +1,886 @@ +/* + * Copyright 1995-2017 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ASN1_H +# define HEADER_ASN1_H + +# include +# include +# include +# include +# include +# include +# include + +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif + +# ifdef OPENSSL_BUILD_SHLIBCRYPTO +# undef OPENSSL_EXTERN +# define OPENSSL_EXTERN OPENSSL_EXPORT +# endif + +#ifdef __cplusplus +extern "C" { +#endif + +# define V_ASN1_UNIVERSAL 0x00 +# define V_ASN1_APPLICATION 0x40 +# define V_ASN1_CONTEXT_SPECIFIC 0x80 +# define V_ASN1_PRIVATE 0xc0 + +# define V_ASN1_CONSTRUCTED 0x20 +# define V_ASN1_PRIMITIVE_TAG 0x1f +# define V_ASN1_PRIMATIVE_TAG /*compat*/ V_ASN1_PRIMITIVE_TAG + +# define V_ASN1_APP_CHOOSE -2/* let the recipient choose */ +# define V_ASN1_OTHER -3/* used in ASN1_TYPE */ +# define V_ASN1_ANY -4/* used in ASN1 template code */ + +# define V_ASN1_UNDEF -1 +/* ASN.1 tag values */ +# define V_ASN1_EOC 0 +# define V_ASN1_BOOLEAN 1 /**/ +# define V_ASN1_INTEGER 2 +# define V_ASN1_BIT_STRING 3 +# define V_ASN1_OCTET_STRING 4 +# define V_ASN1_NULL 5 +# define V_ASN1_OBJECT 6 +# define V_ASN1_OBJECT_DESCRIPTOR 7 +# define V_ASN1_EXTERNAL 8 +# define V_ASN1_REAL 9 +# define V_ASN1_ENUMERATED 10 +# define V_ASN1_UTF8STRING 12 +# define V_ASN1_SEQUENCE 16 +# define V_ASN1_SET 17 +# define V_ASN1_NUMERICSTRING 18 /**/ +# define V_ASN1_PRINTABLESTRING 19 +# define V_ASN1_T61STRING 20 +# define V_ASN1_TELETEXSTRING 20/* alias */ +# define V_ASN1_VIDEOTEXSTRING 21 /**/ +# define V_ASN1_IA5STRING 22 +# define V_ASN1_UTCTIME 23 +# define V_ASN1_GENERALIZEDTIME 24 /**/ +# define V_ASN1_GRAPHICSTRING 25 /**/ +# define V_ASN1_ISO64STRING 26 /**/ +# define V_ASN1_VISIBLESTRING 26/* alias */ +# define V_ASN1_GENERALSTRING 27 /**/ +# define V_ASN1_UNIVERSALSTRING 28 /**/ +# define V_ASN1_BMPSTRING 30 + +/* + * NB the constants below are used internally by ASN1_INTEGER + * and ASN1_ENUMERATED to indicate the sign. They are *not* on + * the wire tag values. + */ + +# define V_ASN1_NEG 0x100 +# define V_ASN1_NEG_INTEGER (2 | V_ASN1_NEG) +# define V_ASN1_NEG_ENUMERATED (10 | V_ASN1_NEG) + +/* For use with d2i_ASN1_type_bytes() */ +# define B_ASN1_NUMERICSTRING 0x0001 +# define B_ASN1_PRINTABLESTRING 0x0002 +# define B_ASN1_T61STRING 0x0004 +# define B_ASN1_TELETEXSTRING 0x0004 +# define B_ASN1_VIDEOTEXSTRING 0x0008 +# define B_ASN1_IA5STRING 0x0010 +# define B_ASN1_GRAPHICSTRING 0x0020 +# define B_ASN1_ISO64STRING 0x0040 +# define B_ASN1_VISIBLESTRING 0x0040 +# define B_ASN1_GENERALSTRING 0x0080 +# define B_ASN1_UNIVERSALSTRING 0x0100 +# define B_ASN1_OCTET_STRING 0x0200 +# define B_ASN1_BIT_STRING 0x0400 +# define B_ASN1_BMPSTRING 0x0800 +# define B_ASN1_UNKNOWN 0x1000 +# define B_ASN1_UTF8STRING 0x2000 +# define B_ASN1_UTCTIME 0x4000 +# define B_ASN1_GENERALIZEDTIME 0x8000 +# define B_ASN1_SEQUENCE 0x10000 +/* For use with ASN1_mbstring_copy() */ +# define MBSTRING_FLAG 0x1000 +# define MBSTRING_UTF8 (MBSTRING_FLAG) +# define MBSTRING_ASC (MBSTRING_FLAG|1) +# define MBSTRING_BMP (MBSTRING_FLAG|2) +# define MBSTRING_UNIV (MBSTRING_FLAG|4) +# define SMIME_OLDMIME 0x400 +# define SMIME_CRLFEOL 0x800 +# define SMIME_STREAM 0x1000 + struct X509_algor_st; +DEFINE_STACK_OF(X509_ALGOR) + +# define ASN1_STRING_FLAG_BITS_LEFT 0x08/* Set if 0x07 has bits left value */ +/* + * This indicates that the ASN1_STRING is not a real value but just a place + * holder for the location where indefinite length constructed data should be + * inserted in the memory buffer + */ +# define ASN1_STRING_FLAG_NDEF 0x010 + +/* + * This flag is used by the CMS code to indicate that a string is not + * complete and is a place holder for content when it had all been accessed. + * The flag will be reset when content has been written to it. + */ + +# define ASN1_STRING_FLAG_CONT 0x020 +/* + * This flag is used by ASN1 code to indicate an ASN1_STRING is an MSTRING + * type. + */ +# define ASN1_STRING_FLAG_MSTRING 0x040 +/* String is embedded and only content should be freed */ +# define ASN1_STRING_FLAG_EMBED 0x080 +/* String should be parsed in RFC 5280's time format */ +# define ASN1_STRING_FLAG_X509_TIME 0x100 +/* This is the base type that holds just about everything :-) */ +struct asn1_string_st { + int length; + int type; + unsigned char *data; + /* + * The value of the following field depends on the type being held. It + * is mostly being used for BIT_STRING so if the input data has a + * non-zero 'unused bits' value, it will be handled correctly + */ + long flags; +}; + +/* + * ASN1_ENCODING structure: this is used to save the received encoding of an + * ASN1 type. This is useful to get round problems with invalid encodings + * which can break signatures. + */ + +typedef struct ASN1_ENCODING_st { + unsigned char *enc; /* DER encoding */ + long len; /* Length of encoding */ + int modified; /* set to 1 if 'enc' is invalid */ +} ASN1_ENCODING; + +/* Used with ASN1 LONG type: if a long is set to this it is omitted */ +# define ASN1_LONG_UNDEF 0x7fffffffL + +# define STABLE_FLAGS_MALLOC 0x01 +/* + * A zero passed to ASN1_STRING_TABLE_new_add for the flags is interpreted + * as "don't change" and STABLE_FLAGS_MALLOC is always set. By setting + * STABLE_FLAGS_MALLOC only we can clear the existing value. Use the alias + * STABLE_FLAGS_CLEAR to reflect this. + */ +# define STABLE_FLAGS_CLEAR STABLE_FLAGS_MALLOC +# define STABLE_NO_MASK 0x02 +# define DIRSTRING_TYPE \ + (B_ASN1_PRINTABLESTRING|B_ASN1_T61STRING|B_ASN1_BMPSTRING|B_ASN1_UTF8STRING) +# define PKCS9STRING_TYPE (DIRSTRING_TYPE|B_ASN1_IA5STRING) + +typedef struct asn1_string_table_st { + int nid; + long minsize; + long maxsize; + unsigned long mask; + unsigned long flags; +} ASN1_STRING_TABLE; + +DEFINE_STACK_OF(ASN1_STRING_TABLE) + +/* size limits: this stuff is taken straight from RFC2459 */ + +# define ub_name 32768 +# define ub_common_name 64 +# define ub_locality_name 128 +# define ub_state_name 128 +# define ub_organization_name 64 +# define ub_organization_unit_name 64 +# define ub_title 64 +# define ub_email_address 128 + +/* + * Declarations for template structures: for full definitions see asn1t.h + */ +typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE; +typedef struct ASN1_TLC_st ASN1_TLC; +/* This is just an opaque pointer */ +typedef struct ASN1_VALUE_st ASN1_VALUE; + +/* Declare ASN1 functions: the implement macro in in asn1t.h */ + +# define DECLARE_ASN1_FUNCTIONS(type) DECLARE_ASN1_FUNCTIONS_name(type, type) + +# define DECLARE_ASN1_ALLOC_FUNCTIONS(type) \ + DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, type) + +# define DECLARE_ASN1_FUNCTIONS_name(type, name) \ + DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ + DECLARE_ASN1_ENCODE_FUNCTIONS(type, name, name) + +# define DECLARE_ASN1_FUNCTIONS_fname(type, itname, name) \ + DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ + DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) + +# define DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) \ + type *d2i_##name(type **a, const unsigned char **in, long len); \ + int i2d_##name(type *a, unsigned char **out); \ + DECLARE_ASN1_ITEM(itname) + +# define DECLARE_ASN1_ENCODE_FUNCTIONS_const(type, name) \ + type *d2i_##name(type **a, const unsigned char **in, long len); \ + int i2d_##name(const type *a, unsigned char **out); \ + DECLARE_ASN1_ITEM(name) + +# define DECLARE_ASN1_NDEF_FUNCTION(name) \ + int i2d_##name##_NDEF(name *a, unsigned char **out); + +# define DECLARE_ASN1_FUNCTIONS_const(name) \ + DECLARE_ASN1_ALLOC_FUNCTIONS(name) \ + DECLARE_ASN1_ENCODE_FUNCTIONS_const(name, name) + +# define DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ + type *name##_new(void); \ + void name##_free(type *a); + +# define DECLARE_ASN1_PRINT_FUNCTION(stname) \ + DECLARE_ASN1_PRINT_FUNCTION_fname(stname, stname) + +# define DECLARE_ASN1_PRINT_FUNCTION_fname(stname, fname) \ + int fname##_print_ctx(BIO *out, stname *x, int indent, \ + const ASN1_PCTX *pctx); + +# define D2I_OF(type) type *(*)(type **,const unsigned char **,long) +# define I2D_OF(type) int (*)(type *,unsigned char **) +# define I2D_OF_const(type) int (*)(const type *,unsigned char **) + +# define CHECKED_D2I_OF(type, d2i) \ + ((d2i_of_void*) (1 ? d2i : ((D2I_OF(type))0))) +# define CHECKED_I2D_OF(type, i2d) \ + ((i2d_of_void*) (1 ? i2d : ((I2D_OF(type))0))) +# define CHECKED_NEW_OF(type, xnew) \ + ((void *(*)(void)) (1 ? xnew : ((type *(*)(void))0))) +# define CHECKED_PTR_OF(type, p) \ + ((void*) (1 ? p : (type*)0)) +# define CHECKED_PPTR_OF(type, p) \ + ((void**) (1 ? p : (type**)0)) + +# define TYPEDEF_D2I_OF(type) typedef type *d2i_of_##type(type **,const unsigned char **,long) +# define TYPEDEF_I2D_OF(type) typedef int i2d_of_##type(type *,unsigned char **) +# define TYPEDEF_D2I2D_OF(type) TYPEDEF_D2I_OF(type); TYPEDEF_I2D_OF(type) + +TYPEDEF_D2I2D_OF(void); + +/*- + * The following macros and typedefs allow an ASN1_ITEM + * to be embedded in a structure and referenced. Since + * the ASN1_ITEM pointers need to be globally accessible + * (possibly from shared libraries) they may exist in + * different forms. On platforms that support it the + * ASN1_ITEM structure itself will be globally exported. + * Other platforms will export a function that returns + * an ASN1_ITEM pointer. + * + * To handle both cases transparently the macros below + * should be used instead of hard coding an ASN1_ITEM + * pointer in a structure. + * + * The structure will look like this: + * + * typedef struct SOMETHING_st { + * ... + * ASN1_ITEM_EXP *iptr; + * ... + * } SOMETHING; + * + * It would be initialised as e.g.: + * + * SOMETHING somevar = {...,ASN1_ITEM_ref(X509),...}; + * + * and the actual pointer extracted with: + * + * const ASN1_ITEM *it = ASN1_ITEM_ptr(somevar.iptr); + * + * Finally an ASN1_ITEM pointer can be extracted from an + * appropriate reference with: ASN1_ITEM_rptr(X509). This + * would be used when a function takes an ASN1_ITEM * argument. + * + */ + +# ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION + +/* ASN1_ITEM pointer exported type */ +typedef const ASN1_ITEM ASN1_ITEM_EXP; + +/* Macro to obtain ASN1_ITEM pointer from exported type */ +# define ASN1_ITEM_ptr(iptr) (iptr) + +/* Macro to include ASN1_ITEM pointer from base type */ +# define ASN1_ITEM_ref(iptr) (&(iptr##_it)) + +# define ASN1_ITEM_rptr(ref) (&(ref##_it)) + +# define DECLARE_ASN1_ITEM(name) \ + OPENSSL_EXTERN const ASN1_ITEM name##_it; + +# else + +/* + * Platforms that can't easily handle shared global variables are declared as + * functions returning ASN1_ITEM pointers. + */ + +/* ASN1_ITEM pointer exported type */ +typedef const ASN1_ITEM *ASN1_ITEM_EXP (void); + +/* Macro to obtain ASN1_ITEM pointer from exported type */ +# define ASN1_ITEM_ptr(iptr) (iptr()) + +/* Macro to include ASN1_ITEM pointer from base type */ +# define ASN1_ITEM_ref(iptr) (iptr##_it) + +# define ASN1_ITEM_rptr(ref) (ref##_it()) + +# define DECLARE_ASN1_ITEM(name) \ + const ASN1_ITEM * name##_it(void); + +# endif + +/* Parameters used by ASN1_STRING_print_ex() */ + +/* + * These determine which characters to escape: RFC2253 special characters, + * control characters and MSB set characters + */ + +# define ASN1_STRFLGS_ESC_2253 1 +# define ASN1_STRFLGS_ESC_CTRL 2 +# define ASN1_STRFLGS_ESC_MSB 4 + +/* + * This flag determines how we do escaping: normally RC2253 backslash only, + * set this to use backslash and quote. + */ + +# define ASN1_STRFLGS_ESC_QUOTE 8 + +/* These three flags are internal use only. */ + +/* Character is a valid PrintableString character */ +# define CHARTYPE_PRINTABLESTRING 0x10 +/* Character needs escaping if it is the first character */ +# define CHARTYPE_FIRST_ESC_2253 0x20 +/* Character needs escaping if it is the last character */ +# define CHARTYPE_LAST_ESC_2253 0x40 + +/* + * NB the internal flags are safely reused below by flags handled at the top + * level. + */ + +/* + * If this is set we convert all character strings to UTF8 first + */ + +# define ASN1_STRFLGS_UTF8_CONVERT 0x10 + +/* + * If this is set we don't attempt to interpret content: just assume all + * strings are 1 byte per character. This will produce some pretty odd + * looking output! + */ + +# define ASN1_STRFLGS_IGNORE_TYPE 0x20 + +/* If this is set we include the string type in the output */ +# define ASN1_STRFLGS_SHOW_TYPE 0x40 + +/* + * This determines which strings to display and which to 'dump' (hex dump of + * content octets or DER encoding). We can only dump non character strings or + * everything. If we don't dump 'unknown' they are interpreted as character + * strings with 1 octet per character and are subject to the usual escaping + * options. + */ + +# define ASN1_STRFLGS_DUMP_ALL 0x80 +# define ASN1_STRFLGS_DUMP_UNKNOWN 0x100 + +/* + * These determine what 'dumping' does, we can dump the content octets or the + * DER encoding: both use the RFC2253 #XXXXX notation. + */ + +# define ASN1_STRFLGS_DUMP_DER 0x200 + +/* + * This flag specifies that RC2254 escaping shall be performed. + */ +#define ASN1_STRFLGS_ESC_2254 0x400 + +/* + * All the string flags consistent with RFC2253, escaping control characters + * isn't essential in RFC2253 but it is advisable anyway. + */ + +# define ASN1_STRFLGS_RFC2253 (ASN1_STRFLGS_ESC_2253 | \ + ASN1_STRFLGS_ESC_CTRL | \ + ASN1_STRFLGS_ESC_MSB | \ + ASN1_STRFLGS_UTF8_CONVERT | \ + ASN1_STRFLGS_DUMP_UNKNOWN | \ + ASN1_STRFLGS_DUMP_DER) + +DEFINE_STACK_OF(ASN1_INTEGER) + +DEFINE_STACK_OF(ASN1_GENERALSTRING) + +DEFINE_STACK_OF(ASN1_UTF8STRING) + +typedef struct asn1_type_st { + int type; + union { + char *ptr; + ASN1_BOOLEAN boolean; + ASN1_STRING *asn1_string; + ASN1_OBJECT *object; + ASN1_INTEGER *integer; + ASN1_ENUMERATED *enumerated; + ASN1_BIT_STRING *bit_string; + ASN1_OCTET_STRING *octet_string; + ASN1_PRINTABLESTRING *printablestring; + ASN1_T61STRING *t61string; + ASN1_IA5STRING *ia5string; + ASN1_GENERALSTRING *generalstring; + ASN1_BMPSTRING *bmpstring; + ASN1_UNIVERSALSTRING *universalstring; + ASN1_UTCTIME *utctime; + ASN1_GENERALIZEDTIME *generalizedtime; + ASN1_VISIBLESTRING *visiblestring; + ASN1_UTF8STRING *utf8string; + /* + * set and sequence are left complete and still contain the set or + * sequence bytes + */ + ASN1_STRING *set; + ASN1_STRING *sequence; + ASN1_VALUE *asn1_value; + } value; +} ASN1_TYPE; + +DEFINE_STACK_OF(ASN1_TYPE) + +typedef STACK_OF(ASN1_TYPE) ASN1_SEQUENCE_ANY; + +DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SEQUENCE_ANY) +DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SET_ANY) + +/* This is used to contain a list of bit names */ +typedef struct BIT_STRING_BITNAME_st { + int bitnum; + const char *lname; + const char *sname; +} BIT_STRING_BITNAME; + +# define B_ASN1_TIME \ + B_ASN1_UTCTIME | \ + B_ASN1_GENERALIZEDTIME + +# define B_ASN1_PRINTABLE \ + B_ASN1_NUMERICSTRING| \ + B_ASN1_PRINTABLESTRING| \ + B_ASN1_T61STRING| \ + B_ASN1_IA5STRING| \ + B_ASN1_BIT_STRING| \ + B_ASN1_UNIVERSALSTRING|\ + B_ASN1_BMPSTRING|\ + B_ASN1_UTF8STRING|\ + B_ASN1_SEQUENCE|\ + B_ASN1_UNKNOWN + +# define B_ASN1_DIRECTORYSTRING \ + B_ASN1_PRINTABLESTRING| \ + B_ASN1_TELETEXSTRING|\ + B_ASN1_BMPSTRING|\ + B_ASN1_UNIVERSALSTRING|\ + B_ASN1_UTF8STRING + +# define B_ASN1_DISPLAYTEXT \ + B_ASN1_IA5STRING| \ + B_ASN1_VISIBLESTRING| \ + B_ASN1_BMPSTRING|\ + B_ASN1_UTF8STRING + +DECLARE_ASN1_FUNCTIONS_fname(ASN1_TYPE, ASN1_ANY, ASN1_TYPE) + +int ASN1_TYPE_get(const ASN1_TYPE *a); +void ASN1_TYPE_set(ASN1_TYPE *a, int type, void *value); +int ASN1_TYPE_set1(ASN1_TYPE *a, int type, const void *value); +int ASN1_TYPE_cmp(const ASN1_TYPE *a, const ASN1_TYPE *b); + +ASN1_TYPE *ASN1_TYPE_pack_sequence(const ASN1_ITEM *it, void *s, ASN1_TYPE **t); +void *ASN1_TYPE_unpack_sequence(const ASN1_ITEM *it, const ASN1_TYPE *t); + +ASN1_OBJECT *ASN1_OBJECT_new(void); +void ASN1_OBJECT_free(ASN1_OBJECT *a); +int i2d_ASN1_OBJECT(const ASN1_OBJECT *a, unsigned char **pp); +ASN1_OBJECT *d2i_ASN1_OBJECT(ASN1_OBJECT **a, const unsigned char **pp, + long length); + +DECLARE_ASN1_ITEM(ASN1_OBJECT) + +DEFINE_STACK_OF(ASN1_OBJECT) + +ASN1_STRING *ASN1_STRING_new(void); +void ASN1_STRING_free(ASN1_STRING *a); +void ASN1_STRING_clear_free(ASN1_STRING *a); +int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str); +ASN1_STRING *ASN1_STRING_dup(const ASN1_STRING *a); +ASN1_STRING *ASN1_STRING_type_new(int type); +int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b); + /* + * Since this is used to store all sorts of things, via macros, for now, + * make its data void * + */ +int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len); +void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len); +int ASN1_STRING_length(const ASN1_STRING *x); +void ASN1_STRING_length_set(ASN1_STRING *x, int n); +int ASN1_STRING_type(const ASN1_STRING *x); +DEPRECATEDIN_1_1_0(unsigned char *ASN1_STRING_data(ASN1_STRING *x)) +const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *x); + +DECLARE_ASN1_FUNCTIONS(ASN1_BIT_STRING) +int ASN1_BIT_STRING_set(ASN1_BIT_STRING *a, unsigned char *d, int length); +int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *a, int n, int value); +int ASN1_BIT_STRING_get_bit(const ASN1_BIT_STRING *a, int n); +int ASN1_BIT_STRING_check(const ASN1_BIT_STRING *a, + const unsigned char *flags, int flags_len); + +int ASN1_BIT_STRING_name_print(BIO *out, ASN1_BIT_STRING *bs, + BIT_STRING_BITNAME *tbl, int indent); +int ASN1_BIT_STRING_num_asc(const char *name, BIT_STRING_BITNAME *tbl); +int ASN1_BIT_STRING_set_asc(ASN1_BIT_STRING *bs, const char *name, int value, + BIT_STRING_BITNAME *tbl); + +DECLARE_ASN1_FUNCTIONS(ASN1_INTEGER) +ASN1_INTEGER *d2i_ASN1_UINTEGER(ASN1_INTEGER **a, const unsigned char **pp, + long length); +ASN1_INTEGER *ASN1_INTEGER_dup(const ASN1_INTEGER *x); +int ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); + +DECLARE_ASN1_FUNCTIONS(ASN1_ENUMERATED) + +int ASN1_UTCTIME_check(const ASN1_UTCTIME *a); +ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *s, time_t t); +ASN1_UTCTIME *ASN1_UTCTIME_adj(ASN1_UTCTIME *s, time_t t, + int offset_day, long offset_sec); +int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str); +int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *s, time_t t); + +int ASN1_GENERALIZEDTIME_check(const ASN1_GENERALIZEDTIME *a); +ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *s, + time_t t); +ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_adj(ASN1_GENERALIZEDTIME *s, + time_t t, int offset_day, + long offset_sec); +int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s, const char *str); + +int ASN1_TIME_diff(int *pday, int *psec, + const ASN1_TIME *from, const ASN1_TIME *to); + +DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING) +ASN1_OCTET_STRING *ASN1_OCTET_STRING_dup(const ASN1_OCTET_STRING *a); +int ASN1_OCTET_STRING_cmp(const ASN1_OCTET_STRING *a, + const ASN1_OCTET_STRING *b); +int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data, + int len); + +DECLARE_ASN1_FUNCTIONS(ASN1_VISIBLESTRING) +DECLARE_ASN1_FUNCTIONS(ASN1_UNIVERSALSTRING) +DECLARE_ASN1_FUNCTIONS(ASN1_UTF8STRING) +DECLARE_ASN1_FUNCTIONS(ASN1_NULL) +DECLARE_ASN1_FUNCTIONS(ASN1_BMPSTRING) + +int UTF8_getc(const unsigned char *str, int len, unsigned long *val); +int UTF8_putc(unsigned char *str, int len, unsigned long value); + +DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, ASN1_PRINTABLE) + +DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DIRECTORYSTRING) +DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DISPLAYTEXT) +DECLARE_ASN1_FUNCTIONS(ASN1_PRINTABLESTRING) +DECLARE_ASN1_FUNCTIONS(ASN1_T61STRING) +DECLARE_ASN1_FUNCTIONS(ASN1_IA5STRING) +DECLARE_ASN1_FUNCTIONS(ASN1_GENERALSTRING) +DECLARE_ASN1_FUNCTIONS(ASN1_UTCTIME) +DECLARE_ASN1_FUNCTIONS(ASN1_GENERALIZEDTIME) +DECLARE_ASN1_FUNCTIONS(ASN1_TIME) + +DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_NDEF) + +ASN1_TIME *ASN1_TIME_set(ASN1_TIME *s, time_t t); +ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, time_t t, + int offset_day, long offset_sec); +int ASN1_TIME_check(const ASN1_TIME *t); +ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *t, + ASN1_GENERALIZEDTIME **out); +int ASN1_TIME_set_string(ASN1_TIME *s, const char *str); +int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str); +int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm); +int ASN1_TIME_normalize(ASN1_TIME *s); +int ASN1_TIME_cmp_time_t(const ASN1_TIME *s, time_t t); +int ASN1_TIME_compare(const ASN1_TIME *a, const ASN1_TIME *b); + +int i2a_ASN1_INTEGER(BIO *bp, const ASN1_INTEGER *a); +int a2i_ASN1_INTEGER(BIO *bp, ASN1_INTEGER *bs, char *buf, int size); +int i2a_ASN1_ENUMERATED(BIO *bp, const ASN1_ENUMERATED *a); +int a2i_ASN1_ENUMERATED(BIO *bp, ASN1_ENUMERATED *bs, char *buf, int size); +int i2a_ASN1_OBJECT(BIO *bp, const ASN1_OBJECT *a); +int a2i_ASN1_STRING(BIO *bp, ASN1_STRING *bs, char *buf, int size); +int i2a_ASN1_STRING(BIO *bp, const ASN1_STRING *a, int type); +int i2t_ASN1_OBJECT(char *buf, int buf_len, const ASN1_OBJECT *a); + +int a2d_ASN1_OBJECT(unsigned char *out, int olen, const char *buf, int num); +ASN1_OBJECT *ASN1_OBJECT_create(int nid, unsigned char *data, int len, + const char *sn, const char *ln); + +int ASN1_INTEGER_get_int64(int64_t *pr, const ASN1_INTEGER *a); +int ASN1_INTEGER_set_int64(ASN1_INTEGER *a, int64_t r); +int ASN1_INTEGER_get_uint64(uint64_t *pr, const ASN1_INTEGER *a); +int ASN1_INTEGER_set_uint64(ASN1_INTEGER *a, uint64_t r); + +int ASN1_INTEGER_set(ASN1_INTEGER *a, long v); +long ASN1_INTEGER_get(const ASN1_INTEGER *a); +ASN1_INTEGER *BN_to_ASN1_INTEGER(const BIGNUM *bn, ASN1_INTEGER *ai); +BIGNUM *ASN1_INTEGER_to_BN(const ASN1_INTEGER *ai, BIGNUM *bn); + +int ASN1_ENUMERATED_get_int64(int64_t *pr, const ASN1_ENUMERATED *a); +int ASN1_ENUMERATED_set_int64(ASN1_ENUMERATED *a, int64_t r); + + +int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v); +long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a); +ASN1_ENUMERATED *BN_to_ASN1_ENUMERATED(const BIGNUM *bn, ASN1_ENUMERATED *ai); +BIGNUM *ASN1_ENUMERATED_to_BN(const ASN1_ENUMERATED *ai, BIGNUM *bn); + +/* General */ +/* given a string, return the correct type, max is the maximum length */ +int ASN1_PRINTABLE_type(const unsigned char *s, int max); + +unsigned long ASN1_tag2bit(int tag); + +/* SPECIALS */ +int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag, + int *pclass, long omax); +int ASN1_check_infinite_end(unsigned char **p, long len); +int ASN1_const_check_infinite_end(const unsigned char **p, long len); +void ASN1_put_object(unsigned char **pp, int constructed, int length, + int tag, int xclass); +int ASN1_put_eoc(unsigned char **pp); +int ASN1_object_size(int constructed, int length, int tag); + +/* Used to implement other functions */ +void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, void *x); + +# define ASN1_dup_of(type,i2d,d2i,x) \ + ((type*)ASN1_dup(CHECKED_I2D_OF(type, i2d), \ + CHECKED_D2I_OF(type, d2i), \ + CHECKED_PTR_OF(type, x))) + +# define ASN1_dup_of_const(type,i2d,d2i,x) \ + ((type*)ASN1_dup(CHECKED_I2D_OF(const type, i2d), \ + CHECKED_D2I_OF(type, d2i), \ + CHECKED_PTR_OF(const type, x))) + +void *ASN1_item_dup(const ASN1_ITEM *it, void *x); + +/* ASN1 alloc/free macros for when a type is only used internally */ + +# define M_ASN1_new_of(type) (type *)ASN1_item_new(ASN1_ITEM_rptr(type)) +# define M_ASN1_free_of(x, type) \ + ASN1_item_free(CHECKED_PTR_OF(type, x), ASN1_ITEM_rptr(type)) + +# ifndef OPENSSL_NO_STDIO +void *ASN1_d2i_fp(void *(*xnew) (void), d2i_of_void *d2i, FILE *in, void **x); + +# define ASN1_d2i_fp_of(type,xnew,d2i,in,x) \ + ((type*)ASN1_d2i_fp(CHECKED_NEW_OF(type, xnew), \ + CHECKED_D2I_OF(type, d2i), \ + in, \ + CHECKED_PPTR_OF(type, x))) + +void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x); +int ASN1_i2d_fp(i2d_of_void *i2d, FILE *out, void *x); + +# define ASN1_i2d_fp_of(type,i2d,out,x) \ + (ASN1_i2d_fp(CHECKED_I2D_OF(type, i2d), \ + out, \ + CHECKED_PTR_OF(type, x))) + +# define ASN1_i2d_fp_of_const(type,i2d,out,x) \ + (ASN1_i2d_fp(CHECKED_I2D_OF(const type, i2d), \ + out, \ + CHECKED_PTR_OF(const type, x))) + +int ASN1_item_i2d_fp(const ASN1_ITEM *it, FILE *out, void *x); +int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str, unsigned long flags); +# endif + +int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in); + +void *ASN1_d2i_bio(void *(*xnew) (void), d2i_of_void *d2i, BIO *in, void **x); + +# define ASN1_d2i_bio_of(type,xnew,d2i,in,x) \ + ((type*)ASN1_d2i_bio( CHECKED_NEW_OF(type, xnew), \ + CHECKED_D2I_OF(type, d2i), \ + in, \ + CHECKED_PPTR_OF(type, x))) + +void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x); +int ASN1_i2d_bio(i2d_of_void *i2d, BIO *out, unsigned char *x); + +# define ASN1_i2d_bio_of(type,i2d,out,x) \ + (ASN1_i2d_bio(CHECKED_I2D_OF(type, i2d), \ + out, \ + CHECKED_PTR_OF(type, x))) + +# define ASN1_i2d_bio_of_const(type,i2d,out,x) \ + (ASN1_i2d_bio(CHECKED_I2D_OF(const type, i2d), \ + out, \ + CHECKED_PTR_OF(const type, x))) + +int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x); +int ASN1_UTCTIME_print(BIO *fp, const ASN1_UTCTIME *a); +int ASN1_GENERALIZEDTIME_print(BIO *fp, const ASN1_GENERALIZEDTIME *a); +int ASN1_TIME_print(BIO *fp, const ASN1_TIME *a); +int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v); +int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str, unsigned long flags); +int ASN1_buf_print(BIO *bp, const unsigned char *buf, size_t buflen, int off); +int ASN1_bn_print(BIO *bp, const char *number, const BIGNUM *num, + unsigned char *buf, int off); +int ASN1_parse(BIO *bp, const unsigned char *pp, long len, int indent); +int ASN1_parse_dump(BIO *bp, const unsigned char *pp, long len, int indent, + int dump); +const char *ASN1_tag2str(int tag); + +/* Used to load and write Netscape format cert */ + +int ASN1_UNIVERSALSTRING_to_string(ASN1_UNIVERSALSTRING *s); + +int ASN1_TYPE_set_octetstring(ASN1_TYPE *a, unsigned char *data, int len); +int ASN1_TYPE_get_octetstring(const ASN1_TYPE *a, unsigned char *data, int max_len); +int ASN1_TYPE_set_int_octetstring(ASN1_TYPE *a, long num, + unsigned char *data, int len); +int ASN1_TYPE_get_int_octetstring(const ASN1_TYPE *a, long *num, + unsigned char *data, int max_len); + +void *ASN1_item_unpack(const ASN1_STRING *oct, const ASN1_ITEM *it); + +ASN1_STRING *ASN1_item_pack(void *obj, const ASN1_ITEM *it, + ASN1_OCTET_STRING **oct); + +void ASN1_STRING_set_default_mask(unsigned long mask); +int ASN1_STRING_set_default_mask_asc(const char *p); +unsigned long ASN1_STRING_get_default_mask(void); +int ASN1_mbstring_copy(ASN1_STRING **out, const unsigned char *in, int len, + int inform, unsigned long mask); +int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len, + int inform, unsigned long mask, + long minsize, long maxsize); + +ASN1_STRING *ASN1_STRING_set_by_NID(ASN1_STRING **out, + const unsigned char *in, int inlen, + int inform, int nid); +ASN1_STRING_TABLE *ASN1_STRING_TABLE_get(int nid); +int ASN1_STRING_TABLE_add(int, long, long, unsigned long, unsigned long); +void ASN1_STRING_TABLE_cleanup(void); + +/* ASN1 template functions */ + +/* Old API compatible functions */ +ASN1_VALUE *ASN1_item_new(const ASN1_ITEM *it); +void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it); +ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in, + long len, const ASN1_ITEM *it); +int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it); +int ASN1_item_ndef_i2d(ASN1_VALUE *val, unsigned char **out, + const ASN1_ITEM *it); + +void ASN1_add_oid_module(void); +void ASN1_add_stable_module(void); + +ASN1_TYPE *ASN1_generate_nconf(const char *str, CONF *nconf); +ASN1_TYPE *ASN1_generate_v3(const char *str, X509V3_CTX *cnf); +int ASN1_str2mask(const char *str, unsigned long *pmask); + +/* ASN1 Print flags */ + +/* Indicate missing OPTIONAL fields */ +# define ASN1_PCTX_FLAGS_SHOW_ABSENT 0x001 +/* Mark start and end of SEQUENCE */ +# define ASN1_PCTX_FLAGS_SHOW_SEQUENCE 0x002 +/* Mark start and end of SEQUENCE/SET OF */ +# define ASN1_PCTX_FLAGS_SHOW_SSOF 0x004 +/* Show the ASN1 type of primitives */ +# define ASN1_PCTX_FLAGS_SHOW_TYPE 0x008 +/* Don't show ASN1 type of ANY */ +# define ASN1_PCTX_FLAGS_NO_ANY_TYPE 0x010 +/* Don't show ASN1 type of MSTRINGs */ +# define ASN1_PCTX_FLAGS_NO_MSTRING_TYPE 0x020 +/* Don't show field names in SEQUENCE */ +# define ASN1_PCTX_FLAGS_NO_FIELD_NAME 0x040 +/* Show structure names of each SEQUENCE field */ +# define ASN1_PCTX_FLAGS_SHOW_FIELD_STRUCT_NAME 0x080 +/* Don't show structure name even at top level */ +# define ASN1_PCTX_FLAGS_NO_STRUCT_NAME 0x100 + +int ASN1_item_print(BIO *out, ASN1_VALUE *ifld, int indent, + const ASN1_ITEM *it, const ASN1_PCTX *pctx); +ASN1_PCTX *ASN1_PCTX_new(void); +void ASN1_PCTX_free(ASN1_PCTX *p); +unsigned long ASN1_PCTX_get_flags(const ASN1_PCTX *p); +void ASN1_PCTX_set_flags(ASN1_PCTX *p, unsigned long flags); +unsigned long ASN1_PCTX_get_nm_flags(const ASN1_PCTX *p); +void ASN1_PCTX_set_nm_flags(ASN1_PCTX *p, unsigned long flags); +unsigned long ASN1_PCTX_get_cert_flags(const ASN1_PCTX *p); +void ASN1_PCTX_set_cert_flags(ASN1_PCTX *p, unsigned long flags); +unsigned long ASN1_PCTX_get_oid_flags(const ASN1_PCTX *p); +void ASN1_PCTX_set_oid_flags(ASN1_PCTX *p, unsigned long flags); +unsigned long ASN1_PCTX_get_str_flags(const ASN1_PCTX *p); +void ASN1_PCTX_set_str_flags(ASN1_PCTX *p, unsigned long flags); + +ASN1_SCTX *ASN1_SCTX_new(int (*scan_cb) (ASN1_SCTX *ctx)); +void ASN1_SCTX_free(ASN1_SCTX *p); +const ASN1_ITEM *ASN1_SCTX_get_item(ASN1_SCTX *p); +const ASN1_TEMPLATE *ASN1_SCTX_get_template(ASN1_SCTX *p); +unsigned long ASN1_SCTX_get_flags(ASN1_SCTX *p); +void ASN1_SCTX_set_app_data(ASN1_SCTX *p, void *data); +void *ASN1_SCTX_get_app_data(ASN1_SCTX *p); + +const BIO_METHOD *BIO_f_asn1(void); + +BIO *BIO_new_NDEF(BIO *out, ASN1_VALUE *val, const ASN1_ITEM *it); + +int i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, + const ASN1_ITEM *it); +int PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, + const char *hdr, const ASN1_ITEM *it); +int SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags, + int ctype_nid, int econt_nid, + STACK_OF(X509_ALGOR) *mdalgs, const ASN1_ITEM *it); +ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it); +int SMIME_crlf_copy(BIO *in, BIO *out, int flags); +int SMIME_text(BIO *in, BIO *out); + +const ASN1_ITEM *ASN1_ITEM_lookup(const char *name); +const ASN1_ITEM *ASN1_ITEM_get(size_t i); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/asn1_mac.h b/thrid-party/openssl/openssl/asn1_mac.h new file mode 100644 index 0000000000..7ac1782a3f --- /dev/null +++ b/thrid-party/openssl/openssl/asn1_mac.h @@ -0,0 +1,10 @@ +/* + * Copyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#error "This file is obsolete; please update your software." diff --git a/thrid-party/openssl/openssl/asn1err.h b/thrid-party/openssl/openssl/asn1err.h new file mode 100644 index 0000000000..faed5a5518 --- /dev/null +++ b/thrid-party/openssl/openssl/asn1err.h @@ -0,0 +1,256 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ASN1ERR_H +# define HEADER_ASN1ERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_ASN1_strings(void); + +/* + * ASN1 function codes. + */ +# define ASN1_F_A2D_ASN1_OBJECT 100 +# define ASN1_F_A2I_ASN1_INTEGER 102 +# define ASN1_F_A2I_ASN1_STRING 103 +# define ASN1_F_APPEND_EXP 176 +# define ASN1_F_ASN1_BIO_INIT 113 +# define ASN1_F_ASN1_BIT_STRING_SET_BIT 183 +# define ASN1_F_ASN1_CB 177 +# define ASN1_F_ASN1_CHECK_TLEN 104 +# define ASN1_F_ASN1_COLLECT 106 +# define ASN1_F_ASN1_D2I_EX_PRIMITIVE 108 +# define ASN1_F_ASN1_D2I_FP 109 +# define ASN1_F_ASN1_D2I_READ_BIO 107 +# define ASN1_F_ASN1_DIGEST 184 +# define ASN1_F_ASN1_DO_ADB 110 +# define ASN1_F_ASN1_DO_LOCK 233 +# define ASN1_F_ASN1_DUP 111 +# define ASN1_F_ASN1_ENC_SAVE 115 +# define ASN1_F_ASN1_EX_C2I 204 +# define ASN1_F_ASN1_FIND_END 190 +# define ASN1_F_ASN1_GENERALIZEDTIME_ADJ 216 +# define ASN1_F_ASN1_GENERATE_V3 178 +# define ASN1_F_ASN1_GET_INT64 224 +# define ASN1_F_ASN1_GET_OBJECT 114 +# define ASN1_F_ASN1_GET_UINT64 225 +# define ASN1_F_ASN1_I2D_BIO 116 +# define ASN1_F_ASN1_I2D_FP 117 +# define ASN1_F_ASN1_ITEM_D2I_FP 206 +# define ASN1_F_ASN1_ITEM_DUP 191 +# define ASN1_F_ASN1_ITEM_EMBED_D2I 120 +# define ASN1_F_ASN1_ITEM_EMBED_NEW 121 +# define ASN1_F_ASN1_ITEM_FLAGS_I2D 118 +# define ASN1_F_ASN1_ITEM_I2D_BIO 192 +# define ASN1_F_ASN1_ITEM_I2D_FP 193 +# define ASN1_F_ASN1_ITEM_PACK 198 +# define ASN1_F_ASN1_ITEM_SIGN 195 +# define ASN1_F_ASN1_ITEM_SIGN_CTX 220 +# define ASN1_F_ASN1_ITEM_UNPACK 199 +# define ASN1_F_ASN1_ITEM_VERIFY 197 +# define ASN1_F_ASN1_MBSTRING_NCOPY 122 +# define ASN1_F_ASN1_OBJECT_NEW 123 +# define ASN1_F_ASN1_OUTPUT_DATA 214 +# define ASN1_F_ASN1_PCTX_NEW 205 +# define ASN1_F_ASN1_PRIMITIVE_NEW 119 +# define ASN1_F_ASN1_SCTX_NEW 221 +# define ASN1_F_ASN1_SIGN 128 +# define ASN1_F_ASN1_STR2TYPE 179 +# define ASN1_F_ASN1_STRING_GET_INT64 227 +# define ASN1_F_ASN1_STRING_GET_UINT64 230 +# define ASN1_F_ASN1_STRING_SET 186 +# define ASN1_F_ASN1_STRING_TABLE_ADD 129 +# define ASN1_F_ASN1_STRING_TO_BN 228 +# define ASN1_F_ASN1_STRING_TYPE_NEW 130 +# define ASN1_F_ASN1_TEMPLATE_EX_D2I 132 +# define ASN1_F_ASN1_TEMPLATE_NEW 133 +# define ASN1_F_ASN1_TEMPLATE_NOEXP_D2I 131 +# define ASN1_F_ASN1_TIME_ADJ 217 +# define ASN1_F_ASN1_TYPE_GET_INT_OCTETSTRING 134 +# define ASN1_F_ASN1_TYPE_GET_OCTETSTRING 135 +# define ASN1_F_ASN1_UTCTIME_ADJ 218 +# define ASN1_F_ASN1_VERIFY 137 +# define ASN1_F_B64_READ_ASN1 209 +# define ASN1_F_B64_WRITE_ASN1 210 +# define ASN1_F_BIO_NEW_NDEF 208 +# define ASN1_F_BITSTR_CB 180 +# define ASN1_F_BN_TO_ASN1_STRING 229 +# define ASN1_F_C2I_ASN1_BIT_STRING 189 +# define ASN1_F_C2I_ASN1_INTEGER 194 +# define ASN1_F_C2I_ASN1_OBJECT 196 +# define ASN1_F_C2I_IBUF 226 +# define ASN1_F_C2I_UINT64_INT 101 +# define ASN1_F_COLLECT_DATA 140 +# define ASN1_F_D2I_ASN1_OBJECT 147 +# define ASN1_F_D2I_ASN1_UINTEGER 150 +# define ASN1_F_D2I_AUTOPRIVATEKEY 207 +# define ASN1_F_D2I_PRIVATEKEY 154 +# define ASN1_F_D2I_PUBLICKEY 155 +# define ASN1_F_DO_BUF 142 +# define ASN1_F_DO_CREATE 124 +# define ASN1_F_DO_DUMP 125 +# define ASN1_F_DO_TCREATE 222 +# define ASN1_F_I2A_ASN1_OBJECT 126 +# define ASN1_F_I2D_ASN1_BIO_STREAM 211 +# define ASN1_F_I2D_ASN1_OBJECT 143 +# define ASN1_F_I2D_DSA_PUBKEY 161 +# define ASN1_F_I2D_EC_PUBKEY 181 +# define ASN1_F_I2D_PRIVATEKEY 163 +# define ASN1_F_I2D_PUBLICKEY 164 +# define ASN1_F_I2D_RSA_PUBKEY 165 +# define ASN1_F_LONG_C2I 166 +# define ASN1_F_NDEF_PREFIX 127 +# define ASN1_F_NDEF_SUFFIX 136 +# define ASN1_F_OID_MODULE_INIT 174 +# define ASN1_F_PARSE_TAGGING 182 +# define ASN1_F_PKCS5_PBE2_SET_IV 167 +# define ASN1_F_PKCS5_PBE2_SET_SCRYPT 231 +# define ASN1_F_PKCS5_PBE_SET 202 +# define ASN1_F_PKCS5_PBE_SET0_ALGOR 215 +# define ASN1_F_PKCS5_PBKDF2_SET 219 +# define ASN1_F_PKCS5_SCRYPT_SET 232 +# define ASN1_F_SMIME_READ_ASN1 212 +# define ASN1_F_SMIME_TEXT 213 +# define ASN1_F_STABLE_GET 138 +# define ASN1_F_STBL_MODULE_INIT 223 +# define ASN1_F_UINT32_C2I 105 +# define ASN1_F_UINT32_NEW 139 +# define ASN1_F_UINT64_C2I 112 +# define ASN1_F_UINT64_NEW 141 +# define ASN1_F_X509_CRL_ADD0_REVOKED 169 +# define ASN1_F_X509_INFO_NEW 170 +# define ASN1_F_X509_NAME_ENCODE 203 +# define ASN1_F_X509_NAME_EX_D2I 158 +# define ASN1_F_X509_NAME_EX_NEW 171 +# define ASN1_F_X509_PKEY_NEW 173 + +/* + * ASN1 reason codes. + */ +# define ASN1_R_ADDING_OBJECT 171 +# define ASN1_R_ASN1_PARSE_ERROR 203 +# define ASN1_R_ASN1_SIG_PARSE_ERROR 204 +# define ASN1_R_AUX_ERROR 100 +# define ASN1_R_BAD_OBJECT_HEADER 102 +# define ASN1_R_BMPSTRING_IS_WRONG_LENGTH 214 +# define ASN1_R_BN_LIB 105 +# define ASN1_R_BOOLEAN_IS_WRONG_LENGTH 106 +# define ASN1_R_BUFFER_TOO_SMALL 107 +# define ASN1_R_CIPHER_HAS_NO_OBJECT_IDENTIFIER 108 +# define ASN1_R_CONTEXT_NOT_INITIALISED 217 +# define ASN1_R_DATA_IS_WRONG 109 +# define ASN1_R_DECODE_ERROR 110 +# define ASN1_R_DEPTH_EXCEEDED 174 +# define ASN1_R_DIGEST_AND_KEY_TYPE_NOT_SUPPORTED 198 +# define ASN1_R_ENCODE_ERROR 112 +# define ASN1_R_ERROR_GETTING_TIME 173 +# define ASN1_R_ERROR_LOADING_SECTION 172 +# define ASN1_R_ERROR_SETTING_CIPHER_PARAMS 114 +# define ASN1_R_EXPECTING_AN_INTEGER 115 +# define ASN1_R_EXPECTING_AN_OBJECT 116 +# define ASN1_R_EXPLICIT_LENGTH_MISMATCH 119 +# define ASN1_R_EXPLICIT_TAG_NOT_CONSTRUCTED 120 +# define ASN1_R_FIELD_MISSING 121 +# define ASN1_R_FIRST_NUM_TOO_LARGE 122 +# define ASN1_R_HEADER_TOO_LONG 123 +# define ASN1_R_ILLEGAL_BITSTRING_FORMAT 175 +# define ASN1_R_ILLEGAL_BOOLEAN 176 +# define ASN1_R_ILLEGAL_CHARACTERS 124 +# define ASN1_R_ILLEGAL_FORMAT 177 +# define ASN1_R_ILLEGAL_HEX 178 +# define ASN1_R_ILLEGAL_IMPLICIT_TAG 179 +# define ASN1_R_ILLEGAL_INTEGER 180 +# define ASN1_R_ILLEGAL_NEGATIVE_VALUE 226 +# define ASN1_R_ILLEGAL_NESTED_TAGGING 181 +# define ASN1_R_ILLEGAL_NULL 125 +# define ASN1_R_ILLEGAL_NULL_VALUE 182 +# define ASN1_R_ILLEGAL_OBJECT 183 +# define ASN1_R_ILLEGAL_OPTIONAL_ANY 126 +# define ASN1_R_ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE 170 +# define ASN1_R_ILLEGAL_PADDING 221 +# define ASN1_R_ILLEGAL_TAGGED_ANY 127 +# define ASN1_R_ILLEGAL_TIME_VALUE 184 +# define ASN1_R_ILLEGAL_ZERO_CONTENT 222 +# define ASN1_R_INTEGER_NOT_ASCII_FORMAT 185 +# define ASN1_R_INTEGER_TOO_LARGE_FOR_LONG 128 +# define ASN1_R_INVALID_BIT_STRING_BITS_LEFT 220 +# define ASN1_R_INVALID_BMPSTRING_LENGTH 129 +# define ASN1_R_INVALID_DIGIT 130 +# define ASN1_R_INVALID_MIME_TYPE 205 +# define ASN1_R_INVALID_MODIFIER 186 +# define ASN1_R_INVALID_NUMBER 187 +# define ASN1_R_INVALID_OBJECT_ENCODING 216 +# define ASN1_R_INVALID_SCRYPT_PARAMETERS 227 +# define ASN1_R_INVALID_SEPARATOR 131 +# define ASN1_R_INVALID_STRING_TABLE_VALUE 218 +# define ASN1_R_INVALID_UNIVERSALSTRING_LENGTH 133 +# define ASN1_R_INVALID_UTF8STRING 134 +# define ASN1_R_INVALID_VALUE 219 +# define ASN1_R_LIST_ERROR 188 +# define ASN1_R_MIME_NO_CONTENT_TYPE 206 +# define ASN1_R_MIME_PARSE_ERROR 207 +# define ASN1_R_MIME_SIG_PARSE_ERROR 208 +# define ASN1_R_MISSING_EOC 137 +# define ASN1_R_MISSING_SECOND_NUMBER 138 +# define ASN1_R_MISSING_VALUE 189 +# define ASN1_R_MSTRING_NOT_UNIVERSAL 139 +# define ASN1_R_MSTRING_WRONG_TAG 140 +# define ASN1_R_NESTED_ASN1_STRING 197 +# define ASN1_R_NESTED_TOO_DEEP 201 +# define ASN1_R_NON_HEX_CHARACTERS 141 +# define ASN1_R_NOT_ASCII_FORMAT 190 +# define ASN1_R_NOT_ENOUGH_DATA 142 +# define ASN1_R_NO_CONTENT_TYPE 209 +# define ASN1_R_NO_MATCHING_CHOICE_TYPE 143 +# define ASN1_R_NO_MULTIPART_BODY_FAILURE 210 +# define ASN1_R_NO_MULTIPART_BOUNDARY 211 +# define ASN1_R_NO_SIG_CONTENT_TYPE 212 +# define ASN1_R_NULL_IS_WRONG_LENGTH 144 +# define ASN1_R_OBJECT_NOT_ASCII_FORMAT 191 +# define ASN1_R_ODD_NUMBER_OF_CHARS 145 +# define ASN1_R_SECOND_NUMBER_TOO_LARGE 147 +# define ASN1_R_SEQUENCE_LENGTH_MISMATCH 148 +# define ASN1_R_SEQUENCE_NOT_CONSTRUCTED 149 +# define ASN1_R_SEQUENCE_OR_SET_NEEDS_CONFIG 192 +# define ASN1_R_SHORT_LINE 150 +# define ASN1_R_SIG_INVALID_MIME_TYPE 213 +# define ASN1_R_STREAMING_NOT_SUPPORTED 202 +# define ASN1_R_STRING_TOO_LONG 151 +# define ASN1_R_STRING_TOO_SHORT 152 +# define ASN1_R_THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD 154 +# define ASN1_R_TIME_NOT_ASCII_FORMAT 193 +# define ASN1_R_TOO_LARGE 223 +# define ASN1_R_TOO_LONG 155 +# define ASN1_R_TOO_SMALL 224 +# define ASN1_R_TYPE_NOT_CONSTRUCTED 156 +# define ASN1_R_TYPE_NOT_PRIMITIVE 195 +# define ASN1_R_UNEXPECTED_EOC 159 +# define ASN1_R_UNIVERSALSTRING_IS_WRONG_LENGTH 215 +# define ASN1_R_UNKNOWN_FORMAT 160 +# define ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM 161 +# define ASN1_R_UNKNOWN_OBJECT_TYPE 162 +# define ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE 163 +# define ASN1_R_UNKNOWN_SIGNATURE_ALGORITHM 199 +# define ASN1_R_UNKNOWN_TAG 194 +# define ASN1_R_UNSUPPORTED_ANY_DEFINED_BY_TYPE 164 +# define ASN1_R_UNSUPPORTED_CIPHER 228 +# define ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE 167 +# define ASN1_R_UNSUPPORTED_TYPE 196 +# define ASN1_R_WRONG_INTEGER_TYPE 225 +# define ASN1_R_WRONG_PUBLIC_KEY_TYPE 200 +# define ASN1_R_WRONG_TAG 168 + +#endif diff --git a/thrid-party/openssl/openssl/asn1t.h b/thrid-party/openssl/openssl/asn1t.h new file mode 100644 index 0000000000..a450ba0d9d --- /dev/null +++ b/thrid-party/openssl/openssl/asn1t.h @@ -0,0 +1,945 @@ +/* + * Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ASN1T_H +# define HEADER_ASN1T_H + +# include +# include +# include + +# ifdef OPENSSL_BUILD_SHLIBCRYPTO +# undef OPENSSL_EXTERN +# define OPENSSL_EXTERN OPENSSL_EXPORT +# endif + +/* ASN1 template defines, structures and functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +# ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION + +/* Macro to obtain ASN1_ADB pointer from a type (only used internally) */ +# define ASN1_ADB_ptr(iptr) ((const ASN1_ADB *)(iptr)) + +/* Macros for start and end of ASN1_ITEM definition */ + +# define ASN1_ITEM_start(itname) \ + const ASN1_ITEM itname##_it = { + +# define static_ASN1_ITEM_start(itname) \ + static const ASN1_ITEM itname##_it = { + +# define ASN1_ITEM_end(itname) \ + }; + +# else + +/* Macro to obtain ASN1_ADB pointer from a type (only used internally) */ +# define ASN1_ADB_ptr(iptr) ((const ASN1_ADB *)((iptr)())) + +/* Macros for start and end of ASN1_ITEM definition */ + +# define ASN1_ITEM_start(itname) \ + const ASN1_ITEM * itname##_it(void) \ + { \ + static const ASN1_ITEM local_it = { + +# define static_ASN1_ITEM_start(itname) \ + static ASN1_ITEM_start(itname) + +# define ASN1_ITEM_end(itname) \ + }; \ + return &local_it; \ + } + +# endif + +/* Macros to aid ASN1 template writing */ + +# define ASN1_ITEM_TEMPLATE(tname) \ + static const ASN1_TEMPLATE tname##_item_tt + +# define ASN1_ITEM_TEMPLATE_END(tname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_PRIMITIVE,\ + -1,\ + &tname##_item_tt,\ + 0,\ + NULL,\ + 0,\ + #tname \ + ASN1_ITEM_end(tname) +# define static_ASN1_ITEM_TEMPLATE_END(tname) \ + ;\ + static_ASN1_ITEM_start(tname) \ + ASN1_ITYPE_PRIMITIVE,\ + -1,\ + &tname##_item_tt,\ + 0,\ + NULL,\ + 0,\ + #tname \ + ASN1_ITEM_end(tname) + +/* This is a ASN1 type which just embeds a template */ + +/*- + * This pair helps declare a SEQUENCE. We can do: + * + * ASN1_SEQUENCE(stname) = { + * ... SEQUENCE components ... + * } ASN1_SEQUENCE_END(stname) + * + * This will produce an ASN1_ITEM called stname_it + * for a structure called stname. + * + * If you want the same structure but a different + * name then use: + * + * ASN1_SEQUENCE(itname) = { + * ... SEQUENCE components ... + * } ASN1_SEQUENCE_END_name(stname, itname) + * + * This will create an item called itname_it using + * a structure called stname. + */ + +# define ASN1_SEQUENCE(tname) \ + static const ASN1_TEMPLATE tname##_seq_tt[] + +# define ASN1_SEQUENCE_END(stname) ASN1_SEQUENCE_END_name(stname, stname) + +# define static_ASN1_SEQUENCE_END(stname) static_ASN1_SEQUENCE_END_name(stname, stname) + +# define ASN1_SEQUENCE_END_name(stname, tname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(stname),\ + #tname \ + ASN1_ITEM_end(tname) + +# define static_ASN1_SEQUENCE_END_name(stname, tname) \ + ;\ + static_ASN1_ITEM_start(tname) \ + ASN1_ITYPE_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +# define ASN1_NDEF_SEQUENCE(tname) \ + ASN1_SEQUENCE(tname) + +# define ASN1_NDEF_SEQUENCE_cb(tname, cb) \ + ASN1_SEQUENCE_cb(tname, cb) + +# define ASN1_SEQUENCE_cb(tname, cb) \ + static const ASN1_AUX tname##_aux = {NULL, 0, 0, 0, cb, 0}; \ + ASN1_SEQUENCE(tname) + +# define ASN1_BROKEN_SEQUENCE(tname) \ + static const ASN1_AUX tname##_aux = {NULL, ASN1_AFLG_BROKEN, 0, 0, 0, 0}; \ + ASN1_SEQUENCE(tname) + +# define ASN1_SEQUENCE_ref(tname, cb) \ + static const ASN1_AUX tname##_aux = {NULL, ASN1_AFLG_REFCOUNT, offsetof(tname, references), offsetof(tname, lock), cb, 0}; \ + ASN1_SEQUENCE(tname) + +# define ASN1_SEQUENCE_enc(tname, enc, cb) \ + static const ASN1_AUX tname##_aux = {NULL, ASN1_AFLG_ENCODING, 0, 0, cb, offsetof(tname, enc)}; \ + ASN1_SEQUENCE(tname) + +# define ASN1_NDEF_SEQUENCE_END(tname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_NDEF_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(tname),\ + #tname \ + ASN1_ITEM_end(tname) +# define static_ASN1_NDEF_SEQUENCE_END(tname) \ + ;\ + static_ASN1_ITEM_start(tname) \ + ASN1_ITYPE_NDEF_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(tname),\ + #tname \ + ASN1_ITEM_end(tname) + +# define ASN1_BROKEN_SEQUENCE_END(stname) ASN1_SEQUENCE_END_ref(stname, stname) +# define static_ASN1_BROKEN_SEQUENCE_END(stname) \ + static_ASN1_SEQUENCE_END_ref(stname, stname) + +# define ASN1_SEQUENCE_END_enc(stname, tname) ASN1_SEQUENCE_END_ref(stname, tname) + +# define ASN1_SEQUENCE_END_cb(stname, tname) ASN1_SEQUENCE_END_ref(stname, tname) +# define static_ASN1_SEQUENCE_END_cb(stname, tname) static_ASN1_SEQUENCE_END_ref(stname, tname) + +# define ASN1_SEQUENCE_END_ref(stname, tname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + &tname##_aux,\ + sizeof(stname),\ + #tname \ + ASN1_ITEM_end(tname) +# define static_ASN1_SEQUENCE_END_ref(stname, tname) \ + ;\ + static_ASN1_ITEM_start(tname) \ + ASN1_ITYPE_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + &tname##_aux,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +# define ASN1_NDEF_SEQUENCE_END_cb(stname, tname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_NDEF_SEQUENCE,\ + V_ASN1_SEQUENCE,\ + tname##_seq_tt,\ + sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),\ + &tname##_aux,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +/*- + * This pair helps declare a CHOICE type. We can do: + * + * ASN1_CHOICE(chname) = { + * ... CHOICE options ... + * ASN1_CHOICE_END(chname) + * + * This will produce an ASN1_ITEM called chname_it + * for a structure called chname. The structure + * definition must look like this: + * typedef struct { + * int type; + * union { + * ASN1_SOMETHING *opt1; + * ASN1_SOMEOTHER *opt2; + * } value; + * } chname; + * + * the name of the selector must be 'type'. + * to use an alternative selector name use the + * ASN1_CHOICE_END_selector() version. + */ + +# define ASN1_CHOICE(tname) \ + static const ASN1_TEMPLATE tname##_ch_tt[] + +# define ASN1_CHOICE_cb(tname, cb) \ + static const ASN1_AUX tname##_aux = {NULL, 0, 0, 0, cb, 0}; \ + ASN1_CHOICE(tname) + +# define ASN1_CHOICE_END(stname) ASN1_CHOICE_END_name(stname, stname) + +# define static_ASN1_CHOICE_END(stname) static_ASN1_CHOICE_END_name(stname, stname) + +# define ASN1_CHOICE_END_name(stname, tname) ASN1_CHOICE_END_selector(stname, tname, type) + +# define static_ASN1_CHOICE_END_name(stname, tname) static_ASN1_CHOICE_END_selector(stname, tname, type) + +# define ASN1_CHOICE_END_selector(stname, tname, selname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_CHOICE,\ + offsetof(stname,selname) ,\ + tname##_ch_tt,\ + sizeof(tname##_ch_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +# define static_ASN1_CHOICE_END_selector(stname, tname, selname) \ + ;\ + static_ASN1_ITEM_start(tname) \ + ASN1_ITYPE_CHOICE,\ + offsetof(stname,selname) ,\ + tname##_ch_tt,\ + sizeof(tname##_ch_tt) / sizeof(ASN1_TEMPLATE),\ + NULL,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +# define ASN1_CHOICE_END_cb(stname, tname, selname) \ + ;\ + ASN1_ITEM_start(tname) \ + ASN1_ITYPE_CHOICE,\ + offsetof(stname,selname) ,\ + tname##_ch_tt,\ + sizeof(tname##_ch_tt) / sizeof(ASN1_TEMPLATE),\ + &tname##_aux,\ + sizeof(stname),\ + #stname \ + ASN1_ITEM_end(tname) + +/* This helps with the template wrapper form of ASN1_ITEM */ + +# define ASN1_EX_TEMPLATE_TYPE(flags, tag, name, type) { \ + (flags), (tag), 0,\ + #name, ASN1_ITEM_ref(type) } + +/* These help with SEQUENCE or CHOICE components */ + +/* used to declare other types */ + +# define ASN1_EX_TYPE(flags, tag, stname, field, type) { \ + (flags), (tag), offsetof(stname, field),\ + #field, ASN1_ITEM_ref(type) } + +/* implicit and explicit helper macros */ + +# define ASN1_IMP_EX(stname, field, type, tag, ex) \ + ASN1_EX_TYPE(ASN1_TFLG_IMPLICIT | (ex), tag, stname, field, type) + +# define ASN1_EXP_EX(stname, field, type, tag, ex) \ + ASN1_EX_TYPE(ASN1_TFLG_EXPLICIT | (ex), tag, stname, field, type) + +/* Any defined by macros: the field used is in the table itself */ + +# ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION +# define ASN1_ADB_OBJECT(tblname) { ASN1_TFLG_ADB_OID, -1, 0, #tblname, (const ASN1_ITEM *)&(tblname##_adb) } +# define ASN1_ADB_INTEGER(tblname) { ASN1_TFLG_ADB_INT, -1, 0, #tblname, (const ASN1_ITEM *)&(tblname##_adb) } +# else +# define ASN1_ADB_OBJECT(tblname) { ASN1_TFLG_ADB_OID, -1, 0, #tblname, tblname##_adb } +# define ASN1_ADB_INTEGER(tblname) { ASN1_TFLG_ADB_INT, -1, 0, #tblname, tblname##_adb } +# endif +/* Plain simple type */ +# define ASN1_SIMPLE(stname, field, type) ASN1_EX_TYPE(0,0, stname, field, type) +/* Embedded simple type */ +# define ASN1_EMBED(stname, field, type) ASN1_EX_TYPE(ASN1_TFLG_EMBED,0, stname, field, type) + +/* OPTIONAL simple type */ +# define ASN1_OPT(stname, field, type) ASN1_EX_TYPE(ASN1_TFLG_OPTIONAL, 0, stname, field, type) +# define ASN1_OPT_EMBED(stname, field, type) ASN1_EX_TYPE(ASN1_TFLG_OPTIONAL|ASN1_TFLG_EMBED, 0, stname, field, type) + +/* IMPLICIT tagged simple type */ +# define ASN1_IMP(stname, field, type, tag) ASN1_IMP_EX(stname, field, type, tag, 0) +# define ASN1_IMP_EMBED(stname, field, type, tag) ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_EMBED) + +/* IMPLICIT tagged OPTIONAL simple type */ +# define ASN1_IMP_OPT(stname, field, type, tag) ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_OPTIONAL) +# define ASN1_IMP_OPT_EMBED(stname, field, type, tag) ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_OPTIONAL|ASN1_TFLG_EMBED) + +/* Same as above but EXPLICIT */ + +# define ASN1_EXP(stname, field, type, tag) ASN1_EXP_EX(stname, field, type, tag, 0) +# define ASN1_EXP_EMBED(stname, field, type, tag) ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_EMBED) +# define ASN1_EXP_OPT(stname, field, type, tag) ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_OPTIONAL) +# define ASN1_EXP_OPT_EMBED(stname, field, type, tag) ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_OPTIONAL|ASN1_TFLG_EMBED) + +/* SEQUENCE OF type */ +# define ASN1_SEQUENCE_OF(stname, field, type) \ + ASN1_EX_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, stname, field, type) + +/* OPTIONAL SEQUENCE OF */ +# define ASN1_SEQUENCE_OF_OPT(stname, field, type) \ + ASN1_EX_TYPE(ASN1_TFLG_SEQUENCE_OF|ASN1_TFLG_OPTIONAL, 0, stname, field, type) + +/* Same as above but for SET OF */ + +# define ASN1_SET_OF(stname, field, type) \ + ASN1_EX_TYPE(ASN1_TFLG_SET_OF, 0, stname, field, type) + +# define ASN1_SET_OF_OPT(stname, field, type) \ + ASN1_EX_TYPE(ASN1_TFLG_SET_OF|ASN1_TFLG_OPTIONAL, 0, stname, field, type) + +/* Finally compound types of SEQUENCE, SET, IMPLICIT, EXPLICIT and OPTIONAL */ + +# define ASN1_IMP_SET_OF(stname, field, type, tag) \ + ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_SET_OF) + +# define ASN1_EXP_SET_OF(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_SET_OF) + +# define ASN1_IMP_SET_OF_OPT(stname, field, type, tag) \ + ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_SET_OF|ASN1_TFLG_OPTIONAL) + +# define ASN1_EXP_SET_OF_OPT(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_SET_OF|ASN1_TFLG_OPTIONAL) + +# define ASN1_IMP_SEQUENCE_OF(stname, field, type, tag) \ + ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_SEQUENCE_OF) + +# define ASN1_IMP_SEQUENCE_OF_OPT(stname, field, type, tag) \ + ASN1_IMP_EX(stname, field, type, tag, ASN1_TFLG_SEQUENCE_OF|ASN1_TFLG_OPTIONAL) + +# define ASN1_EXP_SEQUENCE_OF(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_SEQUENCE_OF) + +# define ASN1_EXP_SEQUENCE_OF_OPT(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_SEQUENCE_OF|ASN1_TFLG_OPTIONAL) + +/* EXPLICIT using indefinite length constructed form */ +# define ASN1_NDEF_EXP(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_NDEF) + +/* EXPLICIT OPTIONAL using indefinite length constructed form */ +# define ASN1_NDEF_EXP_OPT(stname, field, type, tag) \ + ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_OPTIONAL|ASN1_TFLG_NDEF) + +/* Macros for the ASN1_ADB structure */ + +# define ASN1_ADB(name) \ + static const ASN1_ADB_TABLE name##_adbtbl[] + +# ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION + +# define ASN1_ADB_END(name, flags, field, adb_cb, def, none) \ + ;\ + static const ASN1_ADB name##_adb = {\ + flags,\ + offsetof(name, field),\ + adb_cb,\ + name##_adbtbl,\ + sizeof(name##_adbtbl) / sizeof(ASN1_ADB_TABLE),\ + def,\ + none\ + } + +# else + +# define ASN1_ADB_END(name, flags, field, adb_cb, def, none) \ + ;\ + static const ASN1_ITEM *name##_adb(void) \ + { \ + static const ASN1_ADB internal_adb = \ + {\ + flags,\ + offsetof(name, field),\ + adb_cb,\ + name##_adbtbl,\ + sizeof(name##_adbtbl) / sizeof(ASN1_ADB_TABLE),\ + def,\ + none\ + }; \ + return (const ASN1_ITEM *) &internal_adb; \ + } \ + void dummy_function(void) + +# endif + +# define ADB_ENTRY(val, template) {val, template} + +# define ASN1_ADB_TEMPLATE(name) \ + static const ASN1_TEMPLATE name##_tt + +/* + * This is the ASN1 template structure that defines a wrapper round the + * actual type. It determines the actual position of the field in the value + * structure, various flags such as OPTIONAL and the field name. + */ + +struct ASN1_TEMPLATE_st { + unsigned long flags; /* Various flags */ + long tag; /* tag, not used if no tagging */ + unsigned long offset; /* Offset of this field in structure */ + const char *field_name; /* Field name */ + ASN1_ITEM_EXP *item; /* Relevant ASN1_ITEM or ASN1_ADB */ +}; + +/* Macro to extract ASN1_ITEM and ASN1_ADB pointer from ASN1_TEMPLATE */ + +# define ASN1_TEMPLATE_item(t) (t->item_ptr) +# define ASN1_TEMPLATE_adb(t) (t->item_ptr) + +typedef struct ASN1_ADB_TABLE_st ASN1_ADB_TABLE; +typedef struct ASN1_ADB_st ASN1_ADB; + +struct ASN1_ADB_st { + unsigned long flags; /* Various flags */ + unsigned long offset; /* Offset of selector field */ + int (*adb_cb)(long *psel); /* Application callback */ + const ASN1_ADB_TABLE *tbl; /* Table of possible types */ + long tblcount; /* Number of entries in tbl */ + const ASN1_TEMPLATE *default_tt; /* Type to use if no match */ + const ASN1_TEMPLATE *null_tt; /* Type to use if selector is NULL */ +}; + +struct ASN1_ADB_TABLE_st { + long value; /* NID for an object or value for an int */ + const ASN1_TEMPLATE tt; /* item for this value */ +}; + +/* template flags */ + +/* Field is optional */ +# define ASN1_TFLG_OPTIONAL (0x1) + +/* Field is a SET OF */ +# define ASN1_TFLG_SET_OF (0x1 << 1) + +/* Field is a SEQUENCE OF */ +# define ASN1_TFLG_SEQUENCE_OF (0x2 << 1) + +/* + * Special case: this refers to a SET OF that will be sorted into DER order + * when encoded *and* the corresponding STACK will be modified to match the + * new order. + */ +# define ASN1_TFLG_SET_ORDER (0x3 << 1) + +/* Mask for SET OF or SEQUENCE OF */ +# define ASN1_TFLG_SK_MASK (0x3 << 1) + +/* + * These flags mean the tag should be taken from the tag field. If EXPLICIT + * then the underlying type is used for the inner tag. + */ + +/* IMPLICIT tagging */ +# define ASN1_TFLG_IMPTAG (0x1 << 3) + +/* EXPLICIT tagging, inner tag from underlying type */ +# define ASN1_TFLG_EXPTAG (0x2 << 3) + +# define ASN1_TFLG_TAG_MASK (0x3 << 3) + +/* context specific IMPLICIT */ +# define ASN1_TFLG_IMPLICIT (ASN1_TFLG_IMPTAG|ASN1_TFLG_CONTEXT) + +/* context specific EXPLICIT */ +# define ASN1_TFLG_EXPLICIT (ASN1_TFLG_EXPTAG|ASN1_TFLG_CONTEXT) + +/* + * If tagging is in force these determine the type of tag to use. Otherwise + * the tag is determined by the underlying type. These values reflect the + * actual octet format. + */ + +/* Universal tag */ +# define ASN1_TFLG_UNIVERSAL (0x0<<6) +/* Application tag */ +# define ASN1_TFLG_APPLICATION (0x1<<6) +/* Context specific tag */ +# define ASN1_TFLG_CONTEXT (0x2<<6) +/* Private tag */ +# define ASN1_TFLG_PRIVATE (0x3<<6) + +# define ASN1_TFLG_TAG_CLASS (0x3<<6) + +/* + * These are for ANY DEFINED BY type. In this case the 'item' field points to + * an ASN1_ADB structure which contains a table of values to decode the + * relevant type + */ + +# define ASN1_TFLG_ADB_MASK (0x3<<8) + +# define ASN1_TFLG_ADB_OID (0x1<<8) + +# define ASN1_TFLG_ADB_INT (0x1<<9) + +/* + * This flag when present in a SEQUENCE OF, SET OF or EXPLICIT causes + * indefinite length constructed encoding to be used if required. + */ + +# define ASN1_TFLG_NDEF (0x1<<11) + +/* Field is embedded and not a pointer */ +# define ASN1_TFLG_EMBED (0x1 << 12) + +/* This is the actual ASN1 item itself */ + +struct ASN1_ITEM_st { + char itype; /* The item type, primitive, SEQUENCE, CHOICE + * or extern */ + long utype; /* underlying type */ + const ASN1_TEMPLATE *templates; /* If SEQUENCE or CHOICE this contains + * the contents */ + long tcount; /* Number of templates if SEQUENCE or CHOICE */ + const void *funcs; /* functions that handle this type */ + long size; /* Structure size (usually) */ + const char *sname; /* Structure name */ +}; + +/*- + * These are values for the itype field and + * determine how the type is interpreted. + * + * For PRIMITIVE types the underlying type + * determines the behaviour if items is NULL. + * + * Otherwise templates must contain a single + * template and the type is treated in the + * same way as the type specified in the template. + * + * For SEQUENCE types the templates field points + * to the members, the size field is the + * structure size. + * + * For CHOICE types the templates field points + * to each possible member (typically a union) + * and the 'size' field is the offset of the + * selector. + * + * The 'funcs' field is used for application + * specific functions. + * + * The EXTERN type uses a new style d2i/i2d. + * The new style should be used where possible + * because it avoids things like the d2i IMPLICIT + * hack. + * + * MSTRING is a multiple string type, it is used + * for a CHOICE of character strings where the + * actual strings all occupy an ASN1_STRING + * structure. In this case the 'utype' field + * has a special meaning, it is used as a mask + * of acceptable types using the B_ASN1 constants. + * + * NDEF_SEQUENCE is the same as SEQUENCE except + * that it will use indefinite length constructed + * encoding if requested. + * + */ + +# define ASN1_ITYPE_PRIMITIVE 0x0 + +# define ASN1_ITYPE_SEQUENCE 0x1 + +# define ASN1_ITYPE_CHOICE 0x2 + +# define ASN1_ITYPE_EXTERN 0x4 + +# define ASN1_ITYPE_MSTRING 0x5 + +# define ASN1_ITYPE_NDEF_SEQUENCE 0x6 + +/* + * Cache for ASN1 tag and length, so we don't keep re-reading it for things + * like CHOICE + */ + +struct ASN1_TLC_st { + char valid; /* Values below are valid */ + int ret; /* return value */ + long plen; /* length */ + int ptag; /* class value */ + int pclass; /* class value */ + int hdrlen; /* header length */ +}; + +/* Typedefs for ASN1 function pointers */ +typedef int ASN1_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, + const ASN1_ITEM *it, int tag, int aclass, char opt, + ASN1_TLC *ctx); + +typedef int ASN1_ex_i2d(ASN1_VALUE **pval, unsigned char **out, + const ASN1_ITEM *it, int tag, int aclass); +typedef int ASN1_ex_new_func(ASN1_VALUE **pval, const ASN1_ITEM *it); +typedef void ASN1_ex_free_func(ASN1_VALUE **pval, const ASN1_ITEM *it); + +typedef int ASN1_ex_print_func(BIO *out, ASN1_VALUE **pval, + int indent, const char *fname, + const ASN1_PCTX *pctx); + +typedef int ASN1_primitive_i2c(ASN1_VALUE **pval, unsigned char *cont, + int *putype, const ASN1_ITEM *it); +typedef int ASN1_primitive_c2i(ASN1_VALUE **pval, const unsigned char *cont, + int len, int utype, char *free_cont, + const ASN1_ITEM *it); +typedef int ASN1_primitive_print(BIO *out, ASN1_VALUE **pval, + const ASN1_ITEM *it, int indent, + const ASN1_PCTX *pctx); + +typedef struct ASN1_EXTERN_FUNCS_st { + void *app_data; + ASN1_ex_new_func *asn1_ex_new; + ASN1_ex_free_func *asn1_ex_free; + ASN1_ex_free_func *asn1_ex_clear; + ASN1_ex_d2i *asn1_ex_d2i; + ASN1_ex_i2d *asn1_ex_i2d; + ASN1_ex_print_func *asn1_ex_print; +} ASN1_EXTERN_FUNCS; + +typedef struct ASN1_PRIMITIVE_FUNCS_st { + void *app_data; + unsigned long flags; + ASN1_ex_new_func *prim_new; + ASN1_ex_free_func *prim_free; + ASN1_ex_free_func *prim_clear; + ASN1_primitive_c2i *prim_c2i; + ASN1_primitive_i2c *prim_i2c; + ASN1_primitive_print *prim_print; +} ASN1_PRIMITIVE_FUNCS; + +/* + * This is the ASN1_AUX structure: it handles various miscellaneous + * requirements. For example the use of reference counts and an informational + * callback. The "informational callback" is called at various points during + * the ASN1 encoding and decoding. It can be used to provide minor + * customisation of the structures used. This is most useful where the + * supplied routines *almost* do the right thing but need some extra help at + * a few points. If the callback returns zero then it is assumed a fatal + * error has occurred and the main operation should be abandoned. If major + * changes in the default behaviour are required then an external type is + * more appropriate. + */ + +typedef int ASN1_aux_cb(int operation, ASN1_VALUE **in, const ASN1_ITEM *it, + void *exarg); + +typedef struct ASN1_AUX_st { + void *app_data; + int flags; + int ref_offset; /* Offset of reference value */ + int ref_lock; /* Lock type to use */ + ASN1_aux_cb *asn1_cb; + int enc_offset; /* Offset of ASN1_ENCODING structure */ +} ASN1_AUX; + +/* For print related callbacks exarg points to this structure */ +typedef struct ASN1_PRINT_ARG_st { + BIO *out; + int indent; + const ASN1_PCTX *pctx; +} ASN1_PRINT_ARG; + +/* For streaming related callbacks exarg points to this structure */ +typedef struct ASN1_STREAM_ARG_st { + /* BIO to stream through */ + BIO *out; + /* BIO with filters appended */ + BIO *ndef_bio; + /* Streaming I/O boundary */ + unsigned char **boundary; +} ASN1_STREAM_ARG; + +/* Flags in ASN1_AUX */ + +/* Use a reference count */ +# define ASN1_AFLG_REFCOUNT 1 +/* Save the encoding of structure (useful for signatures) */ +# define ASN1_AFLG_ENCODING 2 +/* The Sequence length is invalid */ +# define ASN1_AFLG_BROKEN 4 + +/* operation values for asn1_cb */ + +# define ASN1_OP_NEW_PRE 0 +# define ASN1_OP_NEW_POST 1 +# define ASN1_OP_FREE_PRE 2 +# define ASN1_OP_FREE_POST 3 +# define ASN1_OP_D2I_PRE 4 +# define ASN1_OP_D2I_POST 5 +# define ASN1_OP_I2D_PRE 6 +# define ASN1_OP_I2D_POST 7 +# define ASN1_OP_PRINT_PRE 8 +# define ASN1_OP_PRINT_POST 9 +# define ASN1_OP_STREAM_PRE 10 +# define ASN1_OP_STREAM_POST 11 +# define ASN1_OP_DETACHED_PRE 12 +# define ASN1_OP_DETACHED_POST 13 + +/* Macro to implement a primitive type */ +# define IMPLEMENT_ASN1_TYPE(stname) IMPLEMENT_ASN1_TYPE_ex(stname, stname, 0) +# define IMPLEMENT_ASN1_TYPE_ex(itname, vname, ex) \ + ASN1_ITEM_start(itname) \ + ASN1_ITYPE_PRIMITIVE, V_##vname, NULL, 0, NULL, ex, #itname \ + ASN1_ITEM_end(itname) + +/* Macro to implement a multi string type */ +# define IMPLEMENT_ASN1_MSTRING(itname, mask) \ + ASN1_ITEM_start(itname) \ + ASN1_ITYPE_MSTRING, mask, NULL, 0, NULL, sizeof(ASN1_STRING), #itname \ + ASN1_ITEM_end(itname) + +# define IMPLEMENT_EXTERN_ASN1(sname, tag, fptrs) \ + ASN1_ITEM_start(sname) \ + ASN1_ITYPE_EXTERN, \ + tag, \ + NULL, \ + 0, \ + &fptrs, \ + 0, \ + #sname \ + ASN1_ITEM_end(sname) + +/* Macro to implement standard functions in terms of ASN1_ITEM structures */ + +# define IMPLEMENT_ASN1_FUNCTIONS(stname) IMPLEMENT_ASN1_FUNCTIONS_fname(stname, stname, stname) + +# define IMPLEMENT_ASN1_FUNCTIONS_name(stname, itname) IMPLEMENT_ASN1_FUNCTIONS_fname(stname, itname, itname) + +# define IMPLEMENT_ASN1_FUNCTIONS_ENCODE_name(stname, itname) \ + IMPLEMENT_ASN1_FUNCTIONS_ENCODE_fname(stname, itname, itname) + +# define IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(stname) \ + IMPLEMENT_ASN1_ALLOC_FUNCTIONS_pfname(static, stname, stname, stname) + +# define IMPLEMENT_ASN1_ALLOC_FUNCTIONS(stname) \ + IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, stname, stname) + +# define IMPLEMENT_ASN1_ALLOC_FUNCTIONS_pfname(pre, stname, itname, fname) \ + pre stname *fname##_new(void) \ + { \ + return (stname *)ASN1_item_new(ASN1_ITEM_rptr(itname)); \ + } \ + pre void fname##_free(stname *a) \ + { \ + ASN1_item_free((ASN1_VALUE *)a, ASN1_ITEM_rptr(itname)); \ + } + +# define IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) \ + stname *fname##_new(void) \ + { \ + return (stname *)ASN1_item_new(ASN1_ITEM_rptr(itname)); \ + } \ + void fname##_free(stname *a) \ + { \ + ASN1_item_free((ASN1_VALUE *)a, ASN1_ITEM_rptr(itname)); \ + } + +# define IMPLEMENT_ASN1_FUNCTIONS_fname(stname, itname, fname) \ + IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) \ + IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) + +# define IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) \ + stname *d2i_##fname(stname **a, const unsigned char **in, long len) \ + { \ + return (stname *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, ASN1_ITEM_rptr(itname));\ + } \ + int i2d_##fname(stname *a, unsigned char **out) \ + { \ + return ASN1_item_i2d((ASN1_VALUE *)a, out, ASN1_ITEM_rptr(itname));\ + } + +# define IMPLEMENT_ASN1_NDEF_FUNCTION(stname) \ + int i2d_##stname##_NDEF(stname *a, unsigned char **out) \ + { \ + return ASN1_item_ndef_i2d((ASN1_VALUE *)a, out, ASN1_ITEM_rptr(stname));\ + } + +# define IMPLEMENT_STATIC_ASN1_ENCODE_FUNCTIONS(stname) \ + static stname *d2i_##stname(stname **a, \ + const unsigned char **in, long len) \ + { \ + return (stname *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, \ + ASN1_ITEM_rptr(stname)); \ + } \ + static int i2d_##stname(stname *a, unsigned char **out) \ + { \ + return ASN1_item_i2d((ASN1_VALUE *)a, out, \ + ASN1_ITEM_rptr(stname)); \ + } + +/* + * This includes evil casts to remove const: they will go away when full ASN1 + * constification is done. + */ +# define IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(stname, itname, fname) \ + stname *d2i_##fname(stname **a, const unsigned char **in, long len) \ + { \ + return (stname *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, ASN1_ITEM_rptr(itname));\ + } \ + int i2d_##fname(const stname *a, unsigned char **out) \ + { \ + return ASN1_item_i2d((ASN1_VALUE *)a, out, ASN1_ITEM_rptr(itname));\ + } + +# define IMPLEMENT_ASN1_DUP_FUNCTION(stname) \ + stname * stname##_dup(stname *x) \ + { \ + return ASN1_item_dup(ASN1_ITEM_rptr(stname), x); \ + } + +# define IMPLEMENT_ASN1_PRINT_FUNCTION(stname) \ + IMPLEMENT_ASN1_PRINT_FUNCTION_fname(stname, stname, stname) + +# define IMPLEMENT_ASN1_PRINT_FUNCTION_fname(stname, itname, fname) \ + int fname##_print_ctx(BIO *out, stname *x, int indent, \ + const ASN1_PCTX *pctx) \ + { \ + return ASN1_item_print(out, (ASN1_VALUE *)x, indent, \ + ASN1_ITEM_rptr(itname), pctx); \ + } + +# define IMPLEMENT_ASN1_FUNCTIONS_const(name) \ + IMPLEMENT_ASN1_FUNCTIONS_const_fname(name, name, name) + +# define IMPLEMENT_ASN1_FUNCTIONS_const_fname(stname, itname, fname) \ + IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(stname, itname, fname) \ + IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) + +/* external definitions for primitive types */ + +DECLARE_ASN1_ITEM(ASN1_BOOLEAN) +DECLARE_ASN1_ITEM(ASN1_TBOOLEAN) +DECLARE_ASN1_ITEM(ASN1_FBOOLEAN) +DECLARE_ASN1_ITEM(ASN1_SEQUENCE) +DECLARE_ASN1_ITEM(CBIGNUM) +DECLARE_ASN1_ITEM(BIGNUM) +DECLARE_ASN1_ITEM(INT32) +DECLARE_ASN1_ITEM(ZINT32) +DECLARE_ASN1_ITEM(UINT32) +DECLARE_ASN1_ITEM(ZUINT32) +DECLARE_ASN1_ITEM(INT64) +DECLARE_ASN1_ITEM(ZINT64) +DECLARE_ASN1_ITEM(UINT64) +DECLARE_ASN1_ITEM(ZUINT64) + +# if OPENSSL_API_COMPAT < 0x10200000L +/* + * LONG and ZLONG are strongly discouraged for use as stored data, as the + * underlying C type (long) differs in size depending on the architecture. + * They are designed with 32-bit longs in mind. + */ +DECLARE_ASN1_ITEM(LONG) +DECLARE_ASN1_ITEM(ZLONG) +# endif + +DEFINE_STACK_OF(ASN1_VALUE) + +/* Functions used internally by the ASN1 code */ + +int ASN1_item_ex_new(ASN1_VALUE **pval, const ASN1_ITEM *it); +void ASN1_item_ex_free(ASN1_VALUE **pval, const ASN1_ITEM *it); + +int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, + const ASN1_ITEM *it, int tag, int aclass, char opt, + ASN1_TLC *ctx); + +int ASN1_item_ex_i2d(ASN1_VALUE **pval, unsigned char **out, + const ASN1_ITEM *it, int tag, int aclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/async.h b/thrid-party/openssl/openssl/async.h new file mode 100644 index 0000000000..7052b89052 --- /dev/null +++ b/thrid-party/openssl/openssl/async.h @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#ifndef HEADER_ASYNC_H +# define HEADER_ASYNC_H + +#if defined(_WIN32) +# if defined(BASETYPES) || defined(_WINDEF_H) +/* application has to include to use this */ +#define OSSL_ASYNC_FD HANDLE +#define OSSL_BAD_ASYNC_FD INVALID_HANDLE_VALUE +# endif +#else +#define OSSL_ASYNC_FD int +#define OSSL_BAD_ASYNC_FD -1 +#endif +# include + + +# ifdef __cplusplus +extern "C" { +# endif + +typedef struct async_job_st ASYNC_JOB; +typedef struct async_wait_ctx_st ASYNC_WAIT_CTX; + +#define ASYNC_ERR 0 +#define ASYNC_NO_JOBS 1 +#define ASYNC_PAUSE 2 +#define ASYNC_FINISH 3 + +int ASYNC_init_thread(size_t max_size, size_t init_size); +void ASYNC_cleanup_thread(void); + +#ifdef OSSL_ASYNC_FD +ASYNC_WAIT_CTX *ASYNC_WAIT_CTX_new(void); +void ASYNC_WAIT_CTX_free(ASYNC_WAIT_CTX *ctx); +int ASYNC_WAIT_CTX_set_wait_fd(ASYNC_WAIT_CTX *ctx, const void *key, + OSSL_ASYNC_FD fd, + void *custom_data, + void (*cleanup)(ASYNC_WAIT_CTX *, const void *, + OSSL_ASYNC_FD, void *)); +int ASYNC_WAIT_CTX_get_fd(ASYNC_WAIT_CTX *ctx, const void *key, + OSSL_ASYNC_FD *fd, void **custom_data); +int ASYNC_WAIT_CTX_get_all_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *fd, + size_t *numfds); +int ASYNC_WAIT_CTX_get_changed_fds(ASYNC_WAIT_CTX *ctx, OSSL_ASYNC_FD *addfd, + size_t *numaddfds, OSSL_ASYNC_FD *delfd, + size_t *numdelfds); +int ASYNC_WAIT_CTX_clear_fd(ASYNC_WAIT_CTX *ctx, const void *key); +#endif + +int ASYNC_is_capable(void); + +int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *ctx, int *ret, + int (*func)(void *), void *args, size_t size); +int ASYNC_pause_job(void); + +ASYNC_JOB *ASYNC_get_current_job(void); +ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job); +void ASYNC_block_pause(void); +void ASYNC_unblock_pause(void); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/asyncerr.h b/thrid-party/openssl/openssl/asyncerr.h new file mode 100644 index 0000000000..91afbbb2f5 --- /dev/null +++ b/thrid-party/openssl/openssl/asyncerr.h @@ -0,0 +1,42 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ASYNCERR_H +# define HEADER_ASYNCERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_ASYNC_strings(void); + +/* + * ASYNC function codes. + */ +# define ASYNC_F_ASYNC_CTX_NEW 100 +# define ASYNC_F_ASYNC_INIT_THREAD 101 +# define ASYNC_F_ASYNC_JOB_NEW 102 +# define ASYNC_F_ASYNC_PAUSE_JOB 103 +# define ASYNC_F_ASYNC_START_FUNC 104 +# define ASYNC_F_ASYNC_START_JOB 105 +# define ASYNC_F_ASYNC_WAIT_CTX_SET_WAIT_FD 106 + +/* + * ASYNC reason codes. + */ +# define ASYNC_R_FAILED_TO_SET_POOL 101 +# define ASYNC_R_FAILED_TO_SWAP_CONTEXT 102 +# define ASYNC_R_INIT_FAILED 105 +# define ASYNC_R_INVALID_POOL_SIZE 103 + +#endif diff --git a/thrid-party/openssl/openssl/bio.h b/thrid-party/openssl/openssl/bio.h new file mode 100644 index 0000000000..e1fddfb796 --- /dev/null +++ b/thrid-party/openssl/openssl/bio.h @@ -0,0 +1,800 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BIO_H +# define HEADER_BIO_H + +# include + +# ifndef OPENSSL_NO_STDIO +# include +# endif +# include + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* There are the classes of BIOs */ +# define BIO_TYPE_DESCRIPTOR 0x0100 /* socket, fd, connect or accept */ +# define BIO_TYPE_FILTER 0x0200 +# define BIO_TYPE_SOURCE_SINK 0x0400 + +/* These are the 'types' of BIOs */ +# define BIO_TYPE_NONE 0 +# define BIO_TYPE_MEM ( 1|BIO_TYPE_SOURCE_SINK) +# define BIO_TYPE_FILE ( 2|BIO_TYPE_SOURCE_SINK) + +# define BIO_TYPE_FD ( 4|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) +# define BIO_TYPE_SOCKET ( 5|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) +# define BIO_TYPE_NULL ( 6|BIO_TYPE_SOURCE_SINK) +# define BIO_TYPE_SSL ( 7|BIO_TYPE_FILTER) +# define BIO_TYPE_MD ( 8|BIO_TYPE_FILTER) +# define BIO_TYPE_BUFFER ( 9|BIO_TYPE_FILTER) +# define BIO_TYPE_CIPHER (10|BIO_TYPE_FILTER) +# define BIO_TYPE_BASE64 (11|BIO_TYPE_FILTER) +# define BIO_TYPE_CONNECT (12|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) +# define BIO_TYPE_ACCEPT (13|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) + +# define BIO_TYPE_NBIO_TEST (16|BIO_TYPE_FILTER)/* server proxy BIO */ +# define BIO_TYPE_NULL_FILTER (17|BIO_TYPE_FILTER) +# define BIO_TYPE_BIO (19|BIO_TYPE_SOURCE_SINK)/* half a BIO pair */ +# define BIO_TYPE_LINEBUFFER (20|BIO_TYPE_FILTER) +# define BIO_TYPE_DGRAM (21|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) +# define BIO_TYPE_ASN1 (22|BIO_TYPE_FILTER) +# define BIO_TYPE_COMP (23|BIO_TYPE_FILTER) +# ifndef OPENSSL_NO_SCTP +# define BIO_TYPE_DGRAM_SCTP (24|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR) +# endif + +#define BIO_TYPE_START 128 + +/* + * BIO_FILENAME_READ|BIO_CLOSE to open or close on free. + * BIO_set_fp(in,stdin,BIO_NOCLOSE); + */ +# define BIO_NOCLOSE 0x00 +# define BIO_CLOSE 0x01 + +/* + * These are used in the following macros and are passed to BIO_ctrl() + */ +# define BIO_CTRL_RESET 1/* opt - rewind/zero etc */ +# define BIO_CTRL_EOF 2/* opt - are we at the eof */ +# define BIO_CTRL_INFO 3/* opt - extra tit-bits */ +# define BIO_CTRL_SET 4/* man - set the 'IO' type */ +# define BIO_CTRL_GET 5/* man - get the 'IO' type */ +# define BIO_CTRL_PUSH 6/* opt - internal, used to signify change */ +# define BIO_CTRL_POP 7/* opt - internal, used to signify change */ +# define BIO_CTRL_GET_CLOSE 8/* man - set the 'close' on free */ +# define BIO_CTRL_SET_CLOSE 9/* man - set the 'close' on free */ +# define BIO_CTRL_PENDING 10/* opt - is their more data buffered */ +# define BIO_CTRL_FLUSH 11/* opt - 'flush' buffered output */ +# define BIO_CTRL_DUP 12/* man - extra stuff for 'duped' BIO */ +# define BIO_CTRL_WPENDING 13/* opt - number of bytes still to write */ +# define BIO_CTRL_SET_CALLBACK 14/* opt - set callback function */ +# define BIO_CTRL_GET_CALLBACK 15/* opt - set callback function */ + +# define BIO_CTRL_PEEK 29/* BIO_f_buffer special */ +# define BIO_CTRL_SET_FILENAME 30/* BIO_s_file special */ + +/* dgram BIO stuff */ +# define BIO_CTRL_DGRAM_CONNECT 31/* BIO dgram special */ +# define BIO_CTRL_DGRAM_SET_CONNECTED 32/* allow for an externally connected + * socket to be passed in */ +# define BIO_CTRL_DGRAM_SET_RECV_TIMEOUT 33/* setsockopt, essentially */ +# define BIO_CTRL_DGRAM_GET_RECV_TIMEOUT 34/* getsockopt, essentially */ +# define BIO_CTRL_DGRAM_SET_SEND_TIMEOUT 35/* setsockopt, essentially */ +# define BIO_CTRL_DGRAM_GET_SEND_TIMEOUT 36/* getsockopt, essentially */ + +# define BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP 37/* flag whether the last */ +# define BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP 38/* I/O operation tiemd out */ + +/* #ifdef IP_MTU_DISCOVER */ +# define BIO_CTRL_DGRAM_MTU_DISCOVER 39/* set DF bit on egress packets */ +/* #endif */ + +# define BIO_CTRL_DGRAM_QUERY_MTU 40/* as kernel for current MTU */ +# define BIO_CTRL_DGRAM_GET_FALLBACK_MTU 47 +# define BIO_CTRL_DGRAM_GET_MTU 41/* get cached value for MTU */ +# define BIO_CTRL_DGRAM_SET_MTU 42/* set cached value for MTU. + * want to use this if asking + * the kernel fails */ + +# define BIO_CTRL_DGRAM_MTU_EXCEEDED 43/* check whether the MTU was + * exceed in the previous write + * operation */ + +# define BIO_CTRL_DGRAM_GET_PEER 46 +# define BIO_CTRL_DGRAM_SET_PEER 44/* Destination for the data */ + +# define BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT 45/* Next DTLS handshake timeout + * to adjust socket timeouts */ +# define BIO_CTRL_DGRAM_SET_DONT_FRAG 48 + +# define BIO_CTRL_DGRAM_GET_MTU_OVERHEAD 49 + +/* Deliberately outside of OPENSSL_NO_SCTP - used in bss_dgram.c */ +# define BIO_CTRL_DGRAM_SCTP_SET_IN_HANDSHAKE 50 +# ifndef OPENSSL_NO_SCTP +/* SCTP stuff */ +# define BIO_CTRL_DGRAM_SCTP_ADD_AUTH_KEY 51 +# define BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY 52 +# define BIO_CTRL_DGRAM_SCTP_AUTH_CCS_RCVD 53 +# define BIO_CTRL_DGRAM_SCTP_GET_SNDINFO 60 +# define BIO_CTRL_DGRAM_SCTP_SET_SNDINFO 61 +# define BIO_CTRL_DGRAM_SCTP_GET_RCVINFO 62 +# define BIO_CTRL_DGRAM_SCTP_SET_RCVINFO 63 +# define BIO_CTRL_DGRAM_SCTP_GET_PRINFO 64 +# define BIO_CTRL_DGRAM_SCTP_SET_PRINFO 65 +# define BIO_CTRL_DGRAM_SCTP_SAVE_SHUTDOWN 70 +# endif + +# define BIO_CTRL_DGRAM_SET_PEEK_MODE 71 + +/* modifiers */ +# define BIO_FP_READ 0x02 +# define BIO_FP_WRITE 0x04 +# define BIO_FP_APPEND 0x08 +# define BIO_FP_TEXT 0x10 + +# define BIO_FLAGS_READ 0x01 +# define BIO_FLAGS_WRITE 0x02 +# define BIO_FLAGS_IO_SPECIAL 0x04 +# define BIO_FLAGS_RWS (BIO_FLAGS_READ|BIO_FLAGS_WRITE|BIO_FLAGS_IO_SPECIAL) +# define BIO_FLAGS_SHOULD_RETRY 0x08 +# ifndef BIO_FLAGS_UPLINK +/* + * "UPLINK" flag denotes file descriptors provided by application. It + * defaults to 0, as most platforms don't require UPLINK interface. + */ +# define BIO_FLAGS_UPLINK 0 +# endif + +# define BIO_FLAGS_BASE64_NO_NL 0x100 + +/* + * This is used with memory BIOs: + * BIO_FLAGS_MEM_RDONLY means we shouldn't free up or change the data in any way; + * BIO_FLAGS_NONCLEAR_RST means we shouldn't clear data on reset. + */ +# define BIO_FLAGS_MEM_RDONLY 0x200 +# define BIO_FLAGS_NONCLEAR_RST 0x400 + +typedef union bio_addr_st BIO_ADDR; +typedef struct bio_addrinfo_st BIO_ADDRINFO; + +int BIO_get_new_index(void); +void BIO_set_flags(BIO *b, int flags); +int BIO_test_flags(const BIO *b, int flags); +void BIO_clear_flags(BIO *b, int flags); + +# define BIO_get_flags(b) BIO_test_flags(b, ~(0x0)) +# define BIO_set_retry_special(b) \ + BIO_set_flags(b, (BIO_FLAGS_IO_SPECIAL|BIO_FLAGS_SHOULD_RETRY)) +# define BIO_set_retry_read(b) \ + BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) +# define BIO_set_retry_write(b) \ + BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) + +/* These are normally used internally in BIOs */ +# define BIO_clear_retry_flags(b) \ + BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) +# define BIO_get_retry_flags(b) \ + BIO_test_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) + +/* These should be used by the application to tell why we should retry */ +# define BIO_should_read(a) BIO_test_flags(a, BIO_FLAGS_READ) +# define BIO_should_write(a) BIO_test_flags(a, BIO_FLAGS_WRITE) +# define BIO_should_io_special(a) BIO_test_flags(a, BIO_FLAGS_IO_SPECIAL) +# define BIO_retry_type(a) BIO_test_flags(a, BIO_FLAGS_RWS) +# define BIO_should_retry(a) BIO_test_flags(a, BIO_FLAGS_SHOULD_RETRY) + +/* + * The next three are used in conjunction with the BIO_should_io_special() + * condition. After this returns true, BIO *BIO_get_retry_BIO(BIO *bio, int + * *reason); will walk the BIO stack and return the 'reason' for the special + * and the offending BIO. Given a BIO, BIO_get_retry_reason(bio) will return + * the code. + */ +/* + * Returned from the SSL bio when the certificate retrieval code had an error + */ +# define BIO_RR_SSL_X509_LOOKUP 0x01 +/* Returned from the connect BIO when a connect would have blocked */ +# define BIO_RR_CONNECT 0x02 +/* Returned from the accept BIO when an accept would have blocked */ +# define BIO_RR_ACCEPT 0x03 + +/* These are passed by the BIO callback */ +# define BIO_CB_FREE 0x01 +# define BIO_CB_READ 0x02 +# define BIO_CB_WRITE 0x03 +# define BIO_CB_PUTS 0x04 +# define BIO_CB_GETS 0x05 +# define BIO_CB_CTRL 0x06 + +/* + * The callback is called before and after the underling operation, The + * BIO_CB_RETURN flag indicates if it is after the call + */ +# define BIO_CB_RETURN 0x80 +# define BIO_CB_return(a) ((a)|BIO_CB_RETURN) +# define BIO_cb_pre(a) (!((a)&BIO_CB_RETURN)) +# define BIO_cb_post(a) ((a)&BIO_CB_RETURN) + +typedef long (*BIO_callback_fn)(BIO *b, int oper, const char *argp, int argi, + long argl, long ret); +typedef long (*BIO_callback_fn_ex)(BIO *b, int oper, const char *argp, + size_t len, int argi, + long argl, int ret, size_t *processed); +BIO_callback_fn BIO_get_callback(const BIO *b); +void BIO_set_callback(BIO *b, BIO_callback_fn callback); + +BIO_callback_fn_ex BIO_get_callback_ex(const BIO *b); +void BIO_set_callback_ex(BIO *b, BIO_callback_fn_ex callback); + +char *BIO_get_callback_arg(const BIO *b); +void BIO_set_callback_arg(BIO *b, char *arg); + +typedef struct bio_method_st BIO_METHOD; + +const char *BIO_method_name(const BIO *b); +int BIO_method_type(const BIO *b); + +typedef int BIO_info_cb(BIO *, int, int); +typedef BIO_info_cb bio_info_cb; /* backward compatibility */ + +DEFINE_STACK_OF(BIO) + +/* Prefix and suffix callback in ASN1 BIO */ +typedef int asn1_ps_func (BIO *b, unsigned char **pbuf, int *plen, + void *parg); + +# ifndef OPENSSL_NO_SCTP +/* SCTP parameter structs */ +struct bio_dgram_sctp_sndinfo { + uint16_t snd_sid; + uint16_t snd_flags; + uint32_t snd_ppid; + uint32_t snd_context; +}; + +struct bio_dgram_sctp_rcvinfo { + uint16_t rcv_sid; + uint16_t rcv_ssn; + uint16_t rcv_flags; + uint32_t rcv_ppid; + uint32_t rcv_tsn; + uint32_t rcv_cumtsn; + uint32_t rcv_context; +}; + +struct bio_dgram_sctp_prinfo { + uint16_t pr_policy; + uint32_t pr_value; +}; +# endif + +/* + * #define BIO_CONN_get_param_hostname BIO_ctrl + */ + +# define BIO_C_SET_CONNECT 100 +# define BIO_C_DO_STATE_MACHINE 101 +# define BIO_C_SET_NBIO 102 +/* # define BIO_C_SET_PROXY_PARAM 103 */ +# define BIO_C_SET_FD 104 +# define BIO_C_GET_FD 105 +# define BIO_C_SET_FILE_PTR 106 +# define BIO_C_GET_FILE_PTR 107 +# define BIO_C_SET_FILENAME 108 +# define BIO_C_SET_SSL 109 +# define BIO_C_GET_SSL 110 +# define BIO_C_SET_MD 111 +# define BIO_C_GET_MD 112 +# define BIO_C_GET_CIPHER_STATUS 113 +# define BIO_C_SET_BUF_MEM 114 +# define BIO_C_GET_BUF_MEM_PTR 115 +# define BIO_C_GET_BUFF_NUM_LINES 116 +# define BIO_C_SET_BUFF_SIZE 117 +# define BIO_C_SET_ACCEPT 118 +# define BIO_C_SSL_MODE 119 +# define BIO_C_GET_MD_CTX 120 +/* # define BIO_C_GET_PROXY_PARAM 121 */ +# define BIO_C_SET_BUFF_READ_DATA 122/* data to read first */ +# define BIO_C_GET_CONNECT 123 +# define BIO_C_GET_ACCEPT 124 +# define BIO_C_SET_SSL_RENEGOTIATE_BYTES 125 +# define BIO_C_GET_SSL_NUM_RENEGOTIATES 126 +# define BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT 127 +# define BIO_C_FILE_SEEK 128 +# define BIO_C_GET_CIPHER_CTX 129 +# define BIO_C_SET_BUF_MEM_EOF_RETURN 130/* return end of input + * value */ +# define BIO_C_SET_BIND_MODE 131 +# define BIO_C_GET_BIND_MODE 132 +# define BIO_C_FILE_TELL 133 +# define BIO_C_GET_SOCKS 134 +# define BIO_C_SET_SOCKS 135 + +# define BIO_C_SET_WRITE_BUF_SIZE 136/* for BIO_s_bio */ +# define BIO_C_GET_WRITE_BUF_SIZE 137 +# define BIO_C_MAKE_BIO_PAIR 138 +# define BIO_C_DESTROY_BIO_PAIR 139 +# define BIO_C_GET_WRITE_GUARANTEE 140 +# define BIO_C_GET_READ_REQUEST 141 +# define BIO_C_SHUTDOWN_WR 142 +# define BIO_C_NREAD0 143 +# define BIO_C_NREAD 144 +# define BIO_C_NWRITE0 145 +# define BIO_C_NWRITE 146 +# define BIO_C_RESET_READ_REQUEST 147 +# define BIO_C_SET_MD_CTX 148 + +# define BIO_C_SET_PREFIX 149 +# define BIO_C_GET_PREFIX 150 +# define BIO_C_SET_SUFFIX 151 +# define BIO_C_GET_SUFFIX 152 + +# define BIO_C_SET_EX_ARG 153 +# define BIO_C_GET_EX_ARG 154 + +# define BIO_C_SET_CONNECT_MODE 155 + +# define BIO_set_app_data(s,arg) BIO_set_ex_data(s,0,arg) +# define BIO_get_app_data(s) BIO_get_ex_data(s,0) + +# define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL) + +# ifndef OPENSSL_NO_SOCK +/* IP families we support, for BIO_s_connect() and BIO_s_accept() */ +/* Note: the underlying operating system may not support some of them */ +# define BIO_FAMILY_IPV4 4 +# define BIO_FAMILY_IPV6 6 +# define BIO_FAMILY_IPANY 256 + +/* BIO_s_connect() */ +# define BIO_set_conn_hostname(b,name) BIO_ctrl(b,BIO_C_SET_CONNECT,0, \ + (char *)(name)) +# define BIO_set_conn_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,1, \ + (char *)(port)) +# define BIO_set_conn_address(b,addr) BIO_ctrl(b,BIO_C_SET_CONNECT,2, \ + (char *)(addr)) +# define BIO_set_conn_ip_family(b,f) BIO_int_ctrl(b,BIO_C_SET_CONNECT,3,f) +# define BIO_get_conn_hostname(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,0)) +# define BIO_get_conn_port(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,1)) +# define BIO_get_conn_address(b) ((const BIO_ADDR *)BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,2)) +# define BIO_get_conn_ip_family(b) BIO_ctrl(b,BIO_C_GET_CONNECT,3,NULL) +# define BIO_set_conn_mode(b,n) BIO_ctrl(b,BIO_C_SET_CONNECT_MODE,(n),NULL) + +/* BIO_s_accept() */ +# define BIO_set_accept_name(b,name) BIO_ctrl(b,BIO_C_SET_ACCEPT,0, \ + (char *)(name)) +# define BIO_set_accept_port(b,port) BIO_ctrl(b,BIO_C_SET_ACCEPT,1, \ + (char *)(port)) +# define BIO_get_accept_name(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,0)) +# define BIO_get_accept_port(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,1)) +# define BIO_get_peer_name(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,2)) +# define BIO_get_peer_port(b) ((const char *)BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,3)) +/* #define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL) */ +# define BIO_set_nbio_accept(b,n) BIO_ctrl(b,BIO_C_SET_ACCEPT,2,(n)?(void *)"a":NULL) +# define BIO_set_accept_bios(b,bio) BIO_ctrl(b,BIO_C_SET_ACCEPT,3, \ + (char *)(bio)) +# define BIO_set_accept_ip_family(b,f) BIO_int_ctrl(b,BIO_C_SET_ACCEPT,4,f) +# define BIO_get_accept_ip_family(b) BIO_ctrl(b,BIO_C_GET_ACCEPT,4,NULL) + +/* Aliases kept for backward compatibility */ +# define BIO_BIND_NORMAL 0 +# define BIO_BIND_REUSEADDR BIO_SOCK_REUSEADDR +# define BIO_BIND_REUSEADDR_IF_UNUSED BIO_SOCK_REUSEADDR +# define BIO_set_bind_mode(b,mode) BIO_ctrl(b,BIO_C_SET_BIND_MODE,mode,NULL) +# define BIO_get_bind_mode(b) BIO_ctrl(b,BIO_C_GET_BIND_MODE,0,NULL) + +/* BIO_s_accept() and BIO_s_connect() */ +# define BIO_do_connect(b) BIO_do_handshake(b) +# define BIO_do_accept(b) BIO_do_handshake(b) +# endif /* OPENSSL_NO_SOCK */ + +# define BIO_do_handshake(b) BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL) + +/* BIO_s_datagram(), BIO_s_fd(), BIO_s_socket(), BIO_s_accept() and BIO_s_connect() */ +# define BIO_set_fd(b,fd,c) BIO_int_ctrl(b,BIO_C_SET_FD,c,fd) +# define BIO_get_fd(b,c) BIO_ctrl(b,BIO_C_GET_FD,0,(char *)(c)) + +/* BIO_s_file() */ +# define BIO_set_fp(b,fp,c) BIO_ctrl(b,BIO_C_SET_FILE_PTR,c,(char *)(fp)) +# define BIO_get_fp(b,fpp) BIO_ctrl(b,BIO_C_GET_FILE_PTR,0,(char *)(fpp)) + +/* BIO_s_fd() and BIO_s_file() */ +# define BIO_seek(b,ofs) (int)BIO_ctrl(b,BIO_C_FILE_SEEK,ofs,NULL) +# define BIO_tell(b) (int)BIO_ctrl(b,BIO_C_FILE_TELL,0,NULL) + +/* + * name is cast to lose const, but might be better to route through a + * function so we can do it safely + */ +# ifdef CONST_STRICT +/* + * If you are wondering why this isn't defined, its because CONST_STRICT is + * purely a compile-time kludge to allow const to be checked. + */ +int BIO_read_filename(BIO *b, const char *name); +# else +# define BIO_read_filename(b,name) (int)BIO_ctrl(b,BIO_C_SET_FILENAME, \ + BIO_CLOSE|BIO_FP_READ,(char *)(name)) +# endif +# define BIO_write_filename(b,name) (int)BIO_ctrl(b,BIO_C_SET_FILENAME, \ + BIO_CLOSE|BIO_FP_WRITE,name) +# define BIO_append_filename(b,name) (int)BIO_ctrl(b,BIO_C_SET_FILENAME, \ + BIO_CLOSE|BIO_FP_APPEND,name) +# define BIO_rw_filename(b,name) (int)BIO_ctrl(b,BIO_C_SET_FILENAME, \ + BIO_CLOSE|BIO_FP_READ|BIO_FP_WRITE,name) + +/* + * WARNING WARNING, this ups the reference count on the read bio of the SSL + * structure. This is because the ssl read BIO is now pointed to by the + * next_bio field in the bio. So when you free the BIO, make sure you are + * doing a BIO_free_all() to catch the underlying BIO. + */ +# define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)(ssl)) +# define BIO_get_ssl(b,sslp) BIO_ctrl(b,BIO_C_GET_SSL,0,(char *)(sslp)) +# define BIO_set_ssl_mode(b,client) BIO_ctrl(b,BIO_C_SSL_MODE,client,NULL) +# define BIO_set_ssl_renegotiate_bytes(b,num) \ + BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_BYTES,num,NULL) +# define BIO_get_num_renegotiates(b) \ + BIO_ctrl(b,BIO_C_GET_SSL_NUM_RENEGOTIATES,0,NULL) +# define BIO_set_ssl_renegotiate_timeout(b,seconds) \ + BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL) + +/* defined in evp.h */ +/* #define BIO_set_md(b,md) BIO_ctrl(b,BIO_C_SET_MD,1,(char *)(md)) */ + +# define BIO_get_mem_data(b,pp) BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)(pp)) +# define BIO_set_mem_buf(b,bm,c) BIO_ctrl(b,BIO_C_SET_BUF_MEM,c,(char *)(bm)) +# define BIO_get_mem_ptr(b,pp) BIO_ctrl(b,BIO_C_GET_BUF_MEM_PTR,0, \ + (char *)(pp)) +# define BIO_set_mem_eof_return(b,v) \ + BIO_ctrl(b,BIO_C_SET_BUF_MEM_EOF_RETURN,v,NULL) + +/* For the BIO_f_buffer() type */ +# define BIO_get_buffer_num_lines(b) BIO_ctrl(b,BIO_C_GET_BUFF_NUM_LINES,0,NULL) +# define BIO_set_buffer_size(b,size) BIO_ctrl(b,BIO_C_SET_BUFF_SIZE,size,NULL) +# define BIO_set_read_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,0) +# define BIO_set_write_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,1) +# define BIO_set_buffer_read_data(b,buf,num) BIO_ctrl(b,BIO_C_SET_BUFF_READ_DATA,num,buf) + +/* Don't use the next one unless you know what you are doing :-) */ +# define BIO_dup_state(b,ret) BIO_ctrl(b,BIO_CTRL_DUP,0,(char *)(ret)) + +# define BIO_reset(b) (int)BIO_ctrl(b,BIO_CTRL_RESET,0,NULL) +# define BIO_eof(b) (int)BIO_ctrl(b,BIO_CTRL_EOF,0,NULL) +# define BIO_set_close(b,c) (int)BIO_ctrl(b,BIO_CTRL_SET_CLOSE,(c),NULL) +# define BIO_get_close(b) (int)BIO_ctrl(b,BIO_CTRL_GET_CLOSE,0,NULL) +# define BIO_pending(b) (int)BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) +# define BIO_wpending(b) (int)BIO_ctrl(b,BIO_CTRL_WPENDING,0,NULL) +/* ...pending macros have inappropriate return type */ +size_t BIO_ctrl_pending(BIO *b); +size_t BIO_ctrl_wpending(BIO *b); +# define BIO_flush(b) (int)BIO_ctrl(b,BIO_CTRL_FLUSH,0,NULL) +# define BIO_get_info_callback(b,cbp) (int)BIO_ctrl(b,BIO_CTRL_GET_CALLBACK,0, \ + cbp) +# define BIO_set_info_callback(b,cb) (int)BIO_callback_ctrl(b,BIO_CTRL_SET_CALLBACK,cb) + +/* For the BIO_f_buffer() type */ +# define BIO_buffer_get_num_lines(b) BIO_ctrl(b,BIO_CTRL_GET,0,NULL) +# define BIO_buffer_peek(b,s,l) BIO_ctrl(b,BIO_CTRL_PEEK,(l),(s)) + +/* For BIO_s_bio() */ +# define BIO_set_write_buf_size(b,size) (int)BIO_ctrl(b,BIO_C_SET_WRITE_BUF_SIZE,size,NULL) +# define BIO_get_write_buf_size(b,size) (size_t)BIO_ctrl(b,BIO_C_GET_WRITE_BUF_SIZE,size,NULL) +# define BIO_make_bio_pair(b1,b2) (int)BIO_ctrl(b1,BIO_C_MAKE_BIO_PAIR,0,b2) +# define BIO_destroy_bio_pair(b) (int)BIO_ctrl(b,BIO_C_DESTROY_BIO_PAIR,0,NULL) +# define BIO_shutdown_wr(b) (int)BIO_ctrl(b, BIO_C_SHUTDOWN_WR, 0, NULL) +/* macros with inappropriate type -- but ...pending macros use int too: */ +# define BIO_get_write_guarantee(b) (int)BIO_ctrl(b,BIO_C_GET_WRITE_GUARANTEE,0,NULL) +# define BIO_get_read_request(b) (int)BIO_ctrl(b,BIO_C_GET_READ_REQUEST,0,NULL) +size_t BIO_ctrl_get_write_guarantee(BIO *b); +size_t BIO_ctrl_get_read_request(BIO *b); +int BIO_ctrl_reset_read_request(BIO *b); + +/* ctrl macros for dgram */ +# define BIO_ctrl_dgram_connect(b,peer) \ + (int)BIO_ctrl(b,BIO_CTRL_DGRAM_CONNECT,0, (char *)(peer)) +# define BIO_ctrl_set_connected(b,peer) \ + (int)BIO_ctrl(b, BIO_CTRL_DGRAM_SET_CONNECTED, 0, (char *)(peer)) +# define BIO_dgram_recv_timedout(b) \ + (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP, 0, NULL) +# define BIO_dgram_send_timedout(b) \ + (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP, 0, NULL) +# define BIO_dgram_get_peer(b,peer) \ + (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_PEER, 0, (char *)(peer)) +# define BIO_dgram_set_peer(b,peer) \ + (int)BIO_ctrl(b, BIO_CTRL_DGRAM_SET_PEER, 0, (char *)(peer)) +# define BIO_dgram_get_mtu_overhead(b) \ + (unsigned int)BIO_ctrl((b), BIO_CTRL_DGRAM_GET_MTU_OVERHEAD, 0, NULL) + +#define BIO_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_BIO, l, p, newf, dupf, freef) +int BIO_set_ex_data(BIO *bio, int idx, void *data); +void *BIO_get_ex_data(BIO *bio, int idx); +uint64_t BIO_number_read(BIO *bio); +uint64_t BIO_number_written(BIO *bio); + +/* For BIO_f_asn1() */ +int BIO_asn1_set_prefix(BIO *b, asn1_ps_func *prefix, + asn1_ps_func *prefix_free); +int BIO_asn1_get_prefix(BIO *b, asn1_ps_func **pprefix, + asn1_ps_func **pprefix_free); +int BIO_asn1_set_suffix(BIO *b, asn1_ps_func *suffix, + asn1_ps_func *suffix_free); +int BIO_asn1_get_suffix(BIO *b, asn1_ps_func **psuffix, + asn1_ps_func **psuffix_free); + +const BIO_METHOD *BIO_s_file(void); +BIO *BIO_new_file(const char *filename, const char *mode); +# ifndef OPENSSL_NO_STDIO +BIO *BIO_new_fp(FILE *stream, int close_flag); +# endif +BIO *BIO_new(const BIO_METHOD *type); +int BIO_free(BIO *a); +void BIO_set_data(BIO *a, void *ptr); +void *BIO_get_data(BIO *a); +void BIO_set_init(BIO *a, int init); +int BIO_get_init(BIO *a); +void BIO_set_shutdown(BIO *a, int shut); +int BIO_get_shutdown(BIO *a); +void BIO_vfree(BIO *a); +int BIO_up_ref(BIO *a); +int BIO_read(BIO *b, void *data, int dlen); +int BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes); +int BIO_gets(BIO *bp, char *buf, int size); +int BIO_write(BIO *b, const void *data, int dlen); +int BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written); +int BIO_puts(BIO *bp, const char *buf); +int BIO_indent(BIO *b, int indent, int max); +long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg); +long BIO_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp); +void *BIO_ptr_ctrl(BIO *bp, int cmd, long larg); +long BIO_int_ctrl(BIO *bp, int cmd, long larg, int iarg); +BIO *BIO_push(BIO *b, BIO *append); +BIO *BIO_pop(BIO *b); +void BIO_free_all(BIO *a); +BIO *BIO_find_type(BIO *b, int bio_type); +BIO *BIO_next(BIO *b); +void BIO_set_next(BIO *b, BIO *next); +BIO *BIO_get_retry_BIO(BIO *bio, int *reason); +int BIO_get_retry_reason(BIO *bio); +void BIO_set_retry_reason(BIO *bio, int reason); +BIO *BIO_dup_chain(BIO *in); + +int BIO_nread0(BIO *bio, char **buf); +int BIO_nread(BIO *bio, char **buf, int num); +int BIO_nwrite0(BIO *bio, char **buf); +int BIO_nwrite(BIO *bio, char **buf, int num); + +long BIO_debug_callback(BIO *bio, int cmd, const char *argp, int argi, + long argl, long ret); + +const BIO_METHOD *BIO_s_mem(void); +const BIO_METHOD *BIO_s_secmem(void); +BIO *BIO_new_mem_buf(const void *buf, int len); +# ifndef OPENSSL_NO_SOCK +const BIO_METHOD *BIO_s_socket(void); +const BIO_METHOD *BIO_s_connect(void); +const BIO_METHOD *BIO_s_accept(void); +# endif +const BIO_METHOD *BIO_s_fd(void); +const BIO_METHOD *BIO_s_log(void); +const BIO_METHOD *BIO_s_bio(void); +const BIO_METHOD *BIO_s_null(void); +const BIO_METHOD *BIO_f_null(void); +const BIO_METHOD *BIO_f_buffer(void); +const BIO_METHOD *BIO_f_linebuffer(void); +const BIO_METHOD *BIO_f_nbio_test(void); +# ifndef OPENSSL_NO_DGRAM +const BIO_METHOD *BIO_s_datagram(void); +int BIO_dgram_non_fatal_error(int error); +BIO *BIO_new_dgram(int fd, int close_flag); +# ifndef OPENSSL_NO_SCTP +const BIO_METHOD *BIO_s_datagram_sctp(void); +BIO *BIO_new_dgram_sctp(int fd, int close_flag); +int BIO_dgram_is_sctp(BIO *bio); +int BIO_dgram_sctp_notification_cb(BIO *b, + void (*handle_notifications) (BIO *bio, + void *context, + void *buf), + void *context); +int BIO_dgram_sctp_wait_for_dry(BIO *b); +int BIO_dgram_sctp_msg_waiting(BIO *b); +# endif +# endif + +# ifndef OPENSSL_NO_SOCK +int BIO_sock_should_retry(int i); +int BIO_sock_non_fatal_error(int error); +# endif + +int BIO_fd_should_retry(int i); +int BIO_fd_non_fatal_error(int error); +int BIO_dump_cb(int (*cb) (const void *data, size_t len, void *u), + void *u, const char *s, int len); +int BIO_dump_indent_cb(int (*cb) (const void *data, size_t len, void *u), + void *u, const char *s, int len, int indent); +int BIO_dump(BIO *b, const char *bytes, int len); +int BIO_dump_indent(BIO *b, const char *bytes, int len, int indent); +# ifndef OPENSSL_NO_STDIO +int BIO_dump_fp(FILE *fp, const char *s, int len); +int BIO_dump_indent_fp(FILE *fp, const char *s, int len, int indent); +# endif +int BIO_hex_string(BIO *out, int indent, int width, unsigned char *data, + int datalen); + +# ifndef OPENSSL_NO_SOCK +BIO_ADDR *BIO_ADDR_new(void); +int BIO_ADDR_rawmake(BIO_ADDR *ap, int family, + const void *where, size_t wherelen, unsigned short port); +void BIO_ADDR_free(BIO_ADDR *); +void BIO_ADDR_clear(BIO_ADDR *ap); +int BIO_ADDR_family(const BIO_ADDR *ap); +int BIO_ADDR_rawaddress(const BIO_ADDR *ap, void *p, size_t *l); +unsigned short BIO_ADDR_rawport(const BIO_ADDR *ap); +char *BIO_ADDR_hostname_string(const BIO_ADDR *ap, int numeric); +char *BIO_ADDR_service_string(const BIO_ADDR *ap, int numeric); +char *BIO_ADDR_path_string(const BIO_ADDR *ap); + +const BIO_ADDRINFO *BIO_ADDRINFO_next(const BIO_ADDRINFO *bai); +int BIO_ADDRINFO_family(const BIO_ADDRINFO *bai); +int BIO_ADDRINFO_socktype(const BIO_ADDRINFO *bai); +int BIO_ADDRINFO_protocol(const BIO_ADDRINFO *bai); +const BIO_ADDR *BIO_ADDRINFO_address(const BIO_ADDRINFO *bai); +void BIO_ADDRINFO_free(BIO_ADDRINFO *bai); + +enum BIO_hostserv_priorities { + BIO_PARSE_PRIO_HOST, BIO_PARSE_PRIO_SERV +}; +int BIO_parse_hostserv(const char *hostserv, char **host, char **service, + enum BIO_hostserv_priorities hostserv_prio); +enum BIO_lookup_type { + BIO_LOOKUP_CLIENT, BIO_LOOKUP_SERVER +}; +int BIO_lookup(const char *host, const char *service, + enum BIO_lookup_type lookup_type, + int family, int socktype, BIO_ADDRINFO **res); +int BIO_lookup_ex(const char *host, const char *service, + int lookup_type, int family, int socktype, int protocol, + BIO_ADDRINFO **res); +int BIO_sock_error(int sock); +int BIO_socket_ioctl(int fd, long type, void *arg); +int BIO_socket_nbio(int fd, int mode); +int BIO_sock_init(void); +# if OPENSSL_API_COMPAT < 0x10100000L +# define BIO_sock_cleanup() while(0) continue +# endif +int BIO_set_tcp_ndelay(int sock, int turn_on); + +DEPRECATEDIN_1_1_0(struct hostent *BIO_gethostbyname(const char *name)) +DEPRECATEDIN_1_1_0(int BIO_get_port(const char *str, unsigned short *port_ptr)) +DEPRECATEDIN_1_1_0(int BIO_get_host_ip(const char *str, unsigned char *ip)) +DEPRECATEDIN_1_1_0(int BIO_get_accept_socket(char *host_port, int mode)) +DEPRECATEDIN_1_1_0(int BIO_accept(int sock, char **ip_port)) + +union BIO_sock_info_u { + BIO_ADDR *addr; +}; +enum BIO_sock_info_type { + BIO_SOCK_INFO_ADDRESS +}; +int BIO_sock_info(int sock, + enum BIO_sock_info_type type, union BIO_sock_info_u *info); + +# define BIO_SOCK_REUSEADDR 0x01 +# define BIO_SOCK_V6_ONLY 0x02 +# define BIO_SOCK_KEEPALIVE 0x04 +# define BIO_SOCK_NONBLOCK 0x08 +# define BIO_SOCK_NODELAY 0x10 + +int BIO_socket(int domain, int socktype, int protocol, int options); +int BIO_connect(int sock, const BIO_ADDR *addr, int options); +int BIO_bind(int sock, const BIO_ADDR *addr, int options); +int BIO_listen(int sock, const BIO_ADDR *addr, int options); +int BIO_accept_ex(int accept_sock, BIO_ADDR *addr, int options); +int BIO_closesocket(int sock); + +BIO *BIO_new_socket(int sock, int close_flag); +BIO *BIO_new_connect(const char *host_port); +BIO *BIO_new_accept(const char *host_port); +# endif /* OPENSSL_NO_SOCK*/ + +BIO *BIO_new_fd(int fd, int close_flag); + +int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, + BIO **bio2, size_t writebuf2); +/* + * If successful, returns 1 and in *bio1, *bio2 two BIO pair endpoints. + * Otherwise returns 0 and sets *bio1 and *bio2 to NULL. Size 0 uses default + * value. + */ + +void BIO_copy_next_retry(BIO *b); + +/* + * long BIO_ghbn_ctrl(int cmd,int iarg,char *parg); + */ + +# define ossl_bio__attr__(x) +# if defined(__GNUC__) && defined(__STDC_VERSION__) \ + && !defined(__APPLE__) + /* + * Because we support the 'z' modifier, which made its appearance in C99, + * we can't use __attribute__ with pre C99 dialects. + */ +# if __STDC_VERSION__ >= 199901L +# undef ossl_bio__attr__ +# define ossl_bio__attr__ __attribute__ +# if __GNUC__*10 + __GNUC_MINOR__ >= 44 +# define ossl_bio__printf__ __gnu_printf__ +# else +# define ossl_bio__printf__ __printf__ +# endif +# endif +# endif +int BIO_printf(BIO *bio, const char *format, ...) +ossl_bio__attr__((__format__(ossl_bio__printf__, 2, 3))); +int BIO_vprintf(BIO *bio, const char *format, va_list args) +ossl_bio__attr__((__format__(ossl_bio__printf__, 2, 0))); +int BIO_snprintf(char *buf, size_t n, const char *format, ...) +ossl_bio__attr__((__format__(ossl_bio__printf__, 3, 4))); +int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args) +ossl_bio__attr__((__format__(ossl_bio__printf__, 3, 0))); +# undef ossl_bio__attr__ +# undef ossl_bio__printf__ + + +BIO_METHOD *BIO_meth_new(int type, const char *name); +void BIO_meth_free(BIO_METHOD *biom); +int (*BIO_meth_get_write(const BIO_METHOD *biom)) (BIO *, const char *, int); +int (*BIO_meth_get_write_ex(const BIO_METHOD *biom)) (BIO *, const char *, size_t, + size_t *); +int BIO_meth_set_write(BIO_METHOD *biom, + int (*write) (BIO *, const char *, int)); +int BIO_meth_set_write_ex(BIO_METHOD *biom, + int (*bwrite) (BIO *, const char *, size_t, size_t *)); +int (*BIO_meth_get_read(const BIO_METHOD *biom)) (BIO *, char *, int); +int (*BIO_meth_get_read_ex(const BIO_METHOD *biom)) (BIO *, char *, size_t, size_t *); +int BIO_meth_set_read(BIO_METHOD *biom, + int (*read) (BIO *, char *, int)); +int BIO_meth_set_read_ex(BIO_METHOD *biom, + int (*bread) (BIO *, char *, size_t, size_t *)); +int (*BIO_meth_get_puts(const BIO_METHOD *biom)) (BIO *, const char *); +int BIO_meth_set_puts(BIO_METHOD *biom, + int (*puts) (BIO *, const char *)); +int (*BIO_meth_get_gets(const BIO_METHOD *biom)) (BIO *, char *, int); +int BIO_meth_set_gets(BIO_METHOD *biom, + int (*gets) (BIO *, char *, int)); +long (*BIO_meth_get_ctrl(const BIO_METHOD *biom)) (BIO *, int, long, void *); +int BIO_meth_set_ctrl(BIO_METHOD *biom, + long (*ctrl) (BIO *, int, long, void *)); +int (*BIO_meth_get_create(const BIO_METHOD *bion)) (BIO *); +int BIO_meth_set_create(BIO_METHOD *biom, int (*create) (BIO *)); +int (*BIO_meth_get_destroy(const BIO_METHOD *biom)) (BIO *); +int BIO_meth_set_destroy(BIO_METHOD *biom, int (*destroy) (BIO *)); +long (*BIO_meth_get_callback_ctrl(const BIO_METHOD *biom)) + (BIO *, int, BIO_info_cb *); +int BIO_meth_set_callback_ctrl(BIO_METHOD *biom, + long (*callback_ctrl) (BIO *, int, + BIO_info_cb *)); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/bioerr.h b/thrid-party/openssl/openssl/bioerr.h new file mode 100644 index 0000000000..46e2c96ee3 --- /dev/null +++ b/thrid-party/openssl/openssl/bioerr.h @@ -0,0 +1,124 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BIOERR_H +# define HEADER_BIOERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_BIO_strings(void); + +/* + * BIO function codes. + */ +# define BIO_F_ACPT_STATE 100 +# define BIO_F_ADDRINFO_WRAP 148 +# define BIO_F_ADDR_STRINGS 134 +# define BIO_F_BIO_ACCEPT 101 +# define BIO_F_BIO_ACCEPT_EX 137 +# define BIO_F_BIO_ACCEPT_NEW 152 +# define BIO_F_BIO_ADDR_NEW 144 +# define BIO_F_BIO_BIND 147 +# define BIO_F_BIO_CALLBACK_CTRL 131 +# define BIO_F_BIO_CONNECT 138 +# define BIO_F_BIO_CONNECT_NEW 153 +# define BIO_F_BIO_CTRL 103 +# define BIO_F_BIO_GETS 104 +# define BIO_F_BIO_GET_HOST_IP 106 +# define BIO_F_BIO_GET_NEW_INDEX 102 +# define BIO_F_BIO_GET_PORT 107 +# define BIO_F_BIO_LISTEN 139 +# define BIO_F_BIO_LOOKUP 135 +# define BIO_F_BIO_LOOKUP_EX 143 +# define BIO_F_BIO_MAKE_PAIR 121 +# define BIO_F_BIO_METH_NEW 146 +# define BIO_F_BIO_NEW 108 +# define BIO_F_BIO_NEW_DGRAM_SCTP 145 +# define BIO_F_BIO_NEW_FILE 109 +# define BIO_F_BIO_NEW_MEM_BUF 126 +# define BIO_F_BIO_NREAD 123 +# define BIO_F_BIO_NREAD0 124 +# define BIO_F_BIO_NWRITE 125 +# define BIO_F_BIO_NWRITE0 122 +# define BIO_F_BIO_PARSE_HOSTSERV 136 +# define BIO_F_BIO_PUTS 110 +# define BIO_F_BIO_READ 111 +# define BIO_F_BIO_READ_EX 105 +# define BIO_F_BIO_READ_INTERN 120 +# define BIO_F_BIO_SOCKET 140 +# define BIO_F_BIO_SOCKET_NBIO 142 +# define BIO_F_BIO_SOCK_INFO 141 +# define BIO_F_BIO_SOCK_INIT 112 +# define BIO_F_BIO_WRITE 113 +# define BIO_F_BIO_WRITE_EX 119 +# define BIO_F_BIO_WRITE_INTERN 128 +# define BIO_F_BUFFER_CTRL 114 +# define BIO_F_CONN_CTRL 127 +# define BIO_F_CONN_STATE 115 +# define BIO_F_DGRAM_SCTP_NEW 149 +# define BIO_F_DGRAM_SCTP_READ 132 +# define BIO_F_DGRAM_SCTP_WRITE 133 +# define BIO_F_DOAPR_OUTCH 150 +# define BIO_F_FILE_CTRL 116 +# define BIO_F_FILE_READ 130 +# define BIO_F_LINEBUFFER_CTRL 129 +# define BIO_F_LINEBUFFER_NEW 151 +# define BIO_F_MEM_WRITE 117 +# define BIO_F_NBIOF_NEW 154 +# define BIO_F_SLG_WRITE 155 +# define BIO_F_SSL_NEW 118 + +/* + * BIO reason codes. + */ +# define BIO_R_ACCEPT_ERROR 100 +# define BIO_R_ADDRINFO_ADDR_IS_NOT_AF_INET 141 +# define BIO_R_AMBIGUOUS_HOST_OR_SERVICE 129 +# define BIO_R_BAD_FOPEN_MODE 101 +# define BIO_R_BROKEN_PIPE 124 +# define BIO_R_CONNECT_ERROR 103 +# define BIO_R_GETHOSTBYNAME_ADDR_IS_NOT_AF_INET 107 +# define BIO_R_GETSOCKNAME_ERROR 132 +# define BIO_R_GETSOCKNAME_TRUNCATED_ADDRESS 133 +# define BIO_R_GETTING_SOCKTYPE 134 +# define BIO_R_INVALID_ARGUMENT 125 +# define BIO_R_INVALID_SOCKET 135 +# define BIO_R_IN_USE 123 +# define BIO_R_LENGTH_TOO_LONG 102 +# define BIO_R_LISTEN_V6_ONLY 136 +# define BIO_R_LOOKUP_RETURNED_NOTHING 142 +# define BIO_R_MALFORMED_HOST_OR_SERVICE 130 +# define BIO_R_NBIO_CONNECT_ERROR 110 +# define BIO_R_NO_ACCEPT_ADDR_OR_SERVICE_SPECIFIED 143 +# define BIO_R_NO_HOSTNAME_OR_SERVICE_SPECIFIED 144 +# define BIO_R_NO_PORT_DEFINED 113 +# define BIO_R_NO_SUCH_FILE 128 +# define BIO_R_NULL_PARAMETER 115 +# define BIO_R_UNABLE_TO_BIND_SOCKET 117 +# define BIO_R_UNABLE_TO_CREATE_SOCKET 118 +# define BIO_R_UNABLE_TO_KEEPALIVE 137 +# define BIO_R_UNABLE_TO_LISTEN_SOCKET 119 +# define BIO_R_UNABLE_TO_NODELAY 138 +# define BIO_R_UNABLE_TO_REUSEADDR 139 +# define BIO_R_UNAVAILABLE_IP_FAMILY 145 +# define BIO_R_UNINITIALIZED 120 +# define BIO_R_UNKNOWN_INFO_TYPE 140 +# define BIO_R_UNSUPPORTED_IP_FAMILY 146 +# define BIO_R_UNSUPPORTED_METHOD 121 +# define BIO_R_UNSUPPORTED_PROTOCOL_FAMILY 131 +# define BIO_R_WRITE_TO_READ_ONLY_BIO 126 +# define BIO_R_WSASTARTUP 122 + +#endif diff --git a/thrid-party/openssl/openssl/blowfish.h b/thrid-party/openssl/openssl/blowfish.h new file mode 100644 index 0000000000..cd3e460e98 --- /dev/null +++ b/thrid-party/openssl/openssl/blowfish.h @@ -0,0 +1,61 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BLOWFISH_H +# define HEADER_BLOWFISH_H + +# include + +# ifndef OPENSSL_NO_BF +# include +# ifdef __cplusplus +extern "C" { +# endif + +# define BF_ENCRYPT 1 +# define BF_DECRYPT 0 + +/*- + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ! BF_LONG has to be at least 32 bits wide. ! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ +# define BF_LONG unsigned int + +# define BF_ROUNDS 16 +# define BF_BLOCK 8 + +typedef struct bf_key_st { + BF_LONG P[BF_ROUNDS + 2]; + BF_LONG S[4 * 256]; +} BF_KEY; + +void BF_set_key(BF_KEY *key, int len, const unsigned char *data); + +void BF_encrypt(BF_LONG *data, const BF_KEY *key); +void BF_decrypt(BF_LONG *data, const BF_KEY *key); + +void BF_ecb_encrypt(const unsigned char *in, unsigned char *out, + const BF_KEY *key, int enc); +void BF_cbc_encrypt(const unsigned char *in, unsigned char *out, long length, + const BF_KEY *schedule, unsigned char *ivec, int enc); +void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, const BF_KEY *schedule, + unsigned char *ivec, int *num, int enc); +void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, const BF_KEY *schedule, + unsigned char *ivec, int *num); +const char *BF_options(void); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/bn.h b/thrid-party/openssl/openssl/bn.h new file mode 100644 index 0000000000..8af05d00e5 --- /dev/null +++ b/thrid-party/openssl/openssl/bn.h @@ -0,0 +1,539 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BN_H +# define HEADER_BN_H + +# include +# ifndef OPENSSL_NO_STDIO +# include +# endif +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * 64-bit processor with LP64 ABI + */ +# ifdef SIXTY_FOUR_BIT_LONG +# define BN_ULONG unsigned long +# define BN_BYTES 8 +# endif + +/* + * 64-bit processor other than LP64 ABI + */ +# ifdef SIXTY_FOUR_BIT +# define BN_ULONG unsigned long long +# define BN_BYTES 8 +# endif + +# ifdef THIRTY_TWO_BIT +# define BN_ULONG unsigned int +# define BN_BYTES 4 +# endif + +# define BN_BITS2 (BN_BYTES * 8) +# define BN_BITS (BN_BITS2 * 2) +# define BN_TBIT ((BN_ULONG)1 << (BN_BITS2 - 1)) + +# define BN_FLG_MALLOCED 0x01 +# define BN_FLG_STATIC_DATA 0x02 + +/* + * avoid leaking exponent information through timing, + * BN_mod_exp_mont() will call BN_mod_exp_mont_consttime, + * BN_div() will call BN_div_no_branch, + * BN_mod_inverse() will call BN_mod_inverse_no_branch. + */ +# define BN_FLG_CONSTTIME 0x04 +# define BN_FLG_SECURE 0x08 + +# if OPENSSL_API_COMPAT < 0x00908000L +/* deprecated name for the flag */ +# define BN_FLG_EXP_CONSTTIME BN_FLG_CONSTTIME +# define BN_FLG_FREE 0x8000 /* used for debugging */ +# endif + +void BN_set_flags(BIGNUM *b, int n); +int BN_get_flags(const BIGNUM *b, int n); + +/* Values for |top| in BN_rand() */ +#define BN_RAND_TOP_ANY -1 +#define BN_RAND_TOP_ONE 0 +#define BN_RAND_TOP_TWO 1 + +/* Values for |bottom| in BN_rand() */ +#define BN_RAND_BOTTOM_ANY 0 +#define BN_RAND_BOTTOM_ODD 1 + +/* + * get a clone of a BIGNUM with changed flags, for *temporary* use only (the + * two BIGNUMs cannot be used in parallel!). Also only for *read only* use. The + * value |dest| should be a newly allocated BIGNUM obtained via BN_new() that + * has not been otherwise initialised or used. + */ +void BN_with_flags(BIGNUM *dest, const BIGNUM *b, int flags); + +/* Wrapper function to make using BN_GENCB easier */ +int BN_GENCB_call(BN_GENCB *cb, int a, int b); + +BN_GENCB *BN_GENCB_new(void); +void BN_GENCB_free(BN_GENCB *cb); + +/* Populate a BN_GENCB structure with an "old"-style callback */ +void BN_GENCB_set_old(BN_GENCB *gencb, void (*callback) (int, int, void *), + void *cb_arg); + +/* Populate a BN_GENCB structure with a "new"-style callback */ +void BN_GENCB_set(BN_GENCB *gencb, int (*callback) (int, int, BN_GENCB *), + void *cb_arg); + +void *BN_GENCB_get_arg(BN_GENCB *cb); + +# define BN_prime_checks 0 /* default: select number of iterations based + * on the size of the number */ + +/* + * BN_prime_checks_for_size() returns the number of Miller-Rabin iterations + * that will be done for checking that a random number is probably prime. The + * error rate for accepting a composite number as prime depends on the size of + * the prime |b|. The error rates used are for calculating an RSA key with 2 primes, + * and so the level is what you would expect for a key of double the size of the + * prime. + * + * This table is generated using the algorithm of FIPS PUB 186-4 + * Digital Signature Standard (DSS), section F.1, page 117. + * (https://dx.doi.org/10.6028/NIST.FIPS.186-4) + * + * The following magma script was used to generate the output: + * securitybits:=125; + * k:=1024; + * for t:=1 to 65 do + * for M:=3 to Floor(2*Sqrt(k-1)-1) do + * S:=0; + * // Sum over m + * for m:=3 to M do + * s:=0; + * // Sum over j + * for j:=2 to m do + * s+:=(RealField(32)!2)^-(j+(k-1)/j); + * end for; + * S+:=2^(m-(m-1)*t)*s; + * end for; + * A:=2^(k-2-M*t); + * B:=8*(Pi(RealField(32))^2-6)/3*2^(k-2)*S; + * pkt:=2.00743*Log(2)*k*2^-k*(A+B); + * seclevel:=Floor(-Log(2,pkt)); + * if seclevel ge securitybits then + * printf "k: %5o, security: %o bits (t: %o, M: %o)\n",k,seclevel,t,M; + * break; + * end if; + * end for; + * if seclevel ge securitybits then break; end if; + * end for; + * + * It can be run online at: + * http://magma.maths.usyd.edu.au/calc + * + * And will output: + * k: 1024, security: 129 bits (t: 6, M: 23) + * + * k is the number of bits of the prime, securitybits is the level we want to + * reach. + * + * prime length | RSA key size | # MR tests | security level + * -------------+--------------|------------+--------------- + * (b) >= 6394 | >= 12788 | 3 | 256 bit + * (b) >= 3747 | >= 7494 | 3 | 192 bit + * (b) >= 1345 | >= 2690 | 4 | 128 bit + * (b) >= 1080 | >= 2160 | 5 | 128 bit + * (b) >= 852 | >= 1704 | 5 | 112 bit + * (b) >= 476 | >= 952 | 5 | 80 bit + * (b) >= 400 | >= 800 | 6 | 80 bit + * (b) >= 347 | >= 694 | 7 | 80 bit + * (b) >= 308 | >= 616 | 8 | 80 bit + * (b) >= 55 | >= 110 | 27 | 64 bit + * (b) >= 6 | >= 12 | 34 | 64 bit + */ + +# define BN_prime_checks_for_size(b) ((b) >= 3747 ? 3 : \ + (b) >= 1345 ? 4 : \ + (b) >= 476 ? 5 : \ + (b) >= 400 ? 6 : \ + (b) >= 347 ? 7 : \ + (b) >= 308 ? 8 : \ + (b) >= 55 ? 27 : \ + /* b >= 6 */ 34) + +# define BN_num_bytes(a) ((BN_num_bits(a)+7)/8) + +int BN_abs_is_word(const BIGNUM *a, const BN_ULONG w); +int BN_is_zero(const BIGNUM *a); +int BN_is_one(const BIGNUM *a); +int BN_is_word(const BIGNUM *a, const BN_ULONG w); +int BN_is_odd(const BIGNUM *a); + +# define BN_one(a) (BN_set_word((a),1)) + +void BN_zero_ex(BIGNUM *a); + +# if OPENSSL_API_COMPAT >= 0x00908000L +# define BN_zero(a) BN_zero_ex(a) +# else +# define BN_zero(a) (BN_set_word((a),0)) +# endif + +const BIGNUM *BN_value_one(void); +char *BN_options(void); +BN_CTX *BN_CTX_new(void); +BN_CTX *BN_CTX_secure_new(void); +void BN_CTX_free(BN_CTX *c); +void BN_CTX_start(BN_CTX *ctx); +BIGNUM *BN_CTX_get(BN_CTX *ctx); +void BN_CTX_end(BN_CTX *ctx); +int BN_rand(BIGNUM *rnd, int bits, int top, int bottom); +int BN_priv_rand(BIGNUM *rnd, int bits, int top, int bottom); +int BN_rand_range(BIGNUM *rnd, const BIGNUM *range); +int BN_priv_rand_range(BIGNUM *rnd, const BIGNUM *range); +int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom); +int BN_pseudo_rand_range(BIGNUM *rnd, const BIGNUM *range); +int BN_num_bits(const BIGNUM *a); +int BN_num_bits_word(BN_ULONG l); +int BN_security_bits(int L, int N); +BIGNUM *BN_new(void); +BIGNUM *BN_secure_new(void); +void BN_clear_free(BIGNUM *a); +BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b); +void BN_swap(BIGNUM *a, BIGNUM *b); +BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2bin(const BIGNUM *a, unsigned char *to); +int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen); +BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen); +BIGNUM *BN_mpi2bn(const unsigned char *s, int len, BIGNUM *ret); +int BN_bn2mpi(const BIGNUM *a, unsigned char *to); +int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +int BN_sqr(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx); +/** BN_set_negative sets sign of a BIGNUM + * \param b pointer to the BIGNUM object + * \param n 0 if the BIGNUM b should be positive and a value != 0 otherwise + */ +void BN_set_negative(BIGNUM *b, int n); +/** BN_is_negative returns 1 if the BIGNUM is negative + * \param b pointer to the BIGNUM object + * \return 1 if a < 0 and 0 otherwise + */ +int BN_is_negative(const BIGNUM *b); + +int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d, + BN_CTX *ctx); +# define BN_mod(rem,m,d,ctx) BN_div(NULL,(rem),(m),(d),(ctx)) +int BN_nnmod(BIGNUM *r, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx); +int BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, + BN_CTX *ctx); +int BN_mod_add_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const BIGNUM *m); +int BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, + BN_CTX *ctx); +int BN_mod_sub_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const BIGNUM *m); +int BN_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, + BN_CTX *ctx); +int BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); +int BN_mod_lshift1(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); +int BN_mod_lshift1_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *m); +int BN_mod_lshift(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m, + BN_CTX *ctx); +int BN_mod_lshift_quick(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m); + +BN_ULONG BN_mod_word(const BIGNUM *a, BN_ULONG w); +BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w); +int BN_mul_word(BIGNUM *a, BN_ULONG w); +int BN_add_word(BIGNUM *a, BN_ULONG w); +int BN_sub_word(BIGNUM *a, BN_ULONG w); +int BN_set_word(BIGNUM *a, BN_ULONG w); +BN_ULONG BN_get_word(const BIGNUM *a); + +int BN_cmp(const BIGNUM *a, const BIGNUM *b); +void BN_free(BIGNUM *a); +int BN_is_bit_set(const BIGNUM *a, int n); +int BN_lshift(BIGNUM *r, const BIGNUM *a, int n); +int BN_lshift1(BIGNUM *r, const BIGNUM *a); +int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); + +int BN_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx); +int BN_mod_exp_mont(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx); +int BN_mod_exp_mont_consttime(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, + BN_MONT_CTX *in_mont); +int BN_mod_exp_mont_word(BIGNUM *r, BN_ULONG a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx); +int BN_mod_exp2_mont(BIGNUM *r, const BIGNUM *a1, const BIGNUM *p1, + const BIGNUM *a2, const BIGNUM *p2, const BIGNUM *m, + BN_CTX *ctx, BN_MONT_CTX *m_ctx); +int BN_mod_exp_simple(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx); + +int BN_mask_bits(BIGNUM *a, int n); +# ifndef OPENSSL_NO_STDIO +int BN_print_fp(FILE *fp, const BIGNUM *a); +# endif +int BN_print(BIO *bio, const BIGNUM *a); +int BN_reciprocal(BIGNUM *r, const BIGNUM *m, int len, BN_CTX *ctx); +int BN_rshift(BIGNUM *r, const BIGNUM *a, int n); +int BN_rshift1(BIGNUM *r, const BIGNUM *a); +void BN_clear(BIGNUM *a); +BIGNUM *BN_dup(const BIGNUM *a); +int BN_ucmp(const BIGNUM *a, const BIGNUM *b); +int BN_set_bit(BIGNUM *a, int n); +int BN_clear_bit(BIGNUM *a, int n); +char *BN_bn2hex(const BIGNUM *a); +char *BN_bn2dec(const BIGNUM *a); +int BN_hex2bn(BIGNUM **a, const char *str); +int BN_dec2bn(BIGNUM **a, const char *str); +int BN_asc2bn(BIGNUM **a, const char *str); +int BN_gcd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); +int BN_kronecker(const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); /* returns + * -2 for + * error */ +BIGNUM *BN_mod_inverse(BIGNUM *ret, + const BIGNUM *a, const BIGNUM *n, BN_CTX *ctx); +BIGNUM *BN_mod_sqrt(BIGNUM *ret, + const BIGNUM *a, const BIGNUM *n, BN_CTX *ctx); + +void BN_consttime_swap(BN_ULONG swap, BIGNUM *a, BIGNUM *b, int nwords); + +/* Deprecated versions */ +DEPRECATEDIN_0_9_8(BIGNUM *BN_generate_prime(BIGNUM *ret, int bits, int safe, + const BIGNUM *add, + const BIGNUM *rem, + void (*callback) (int, int, + void *), + void *cb_arg)) +DEPRECATEDIN_0_9_8(int + BN_is_prime(const BIGNUM *p, int nchecks, + void (*callback) (int, int, void *), + BN_CTX *ctx, void *cb_arg)) +DEPRECATEDIN_0_9_8(int + BN_is_prime_fasttest(const BIGNUM *p, int nchecks, + void (*callback) (int, int, void *), + BN_CTX *ctx, void *cb_arg, + int do_trial_division)) + +/* Newer versions */ +int BN_generate_prime_ex(BIGNUM *ret, int bits, int safe, const BIGNUM *add, + const BIGNUM *rem, BN_GENCB *cb); +int BN_is_prime_ex(const BIGNUM *p, int nchecks, BN_CTX *ctx, BN_GENCB *cb); +int BN_is_prime_fasttest_ex(const BIGNUM *p, int nchecks, BN_CTX *ctx, + int do_trial_division, BN_GENCB *cb); + +int BN_X931_generate_Xpq(BIGNUM *Xp, BIGNUM *Xq, int nbits, BN_CTX *ctx); + +int BN_X931_derive_prime_ex(BIGNUM *p, BIGNUM *p1, BIGNUM *p2, + const BIGNUM *Xp, const BIGNUM *Xp1, + const BIGNUM *Xp2, const BIGNUM *e, BN_CTX *ctx, + BN_GENCB *cb); +int BN_X931_generate_prime_ex(BIGNUM *p, BIGNUM *p1, BIGNUM *p2, BIGNUM *Xp1, + BIGNUM *Xp2, const BIGNUM *Xp, const BIGNUM *e, + BN_CTX *ctx, BN_GENCB *cb); + +BN_MONT_CTX *BN_MONT_CTX_new(void); +int BN_mod_mul_montgomery(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + BN_MONT_CTX *mont, BN_CTX *ctx); +int BN_to_montgomery(BIGNUM *r, const BIGNUM *a, BN_MONT_CTX *mont, + BN_CTX *ctx); +int BN_from_montgomery(BIGNUM *r, const BIGNUM *a, BN_MONT_CTX *mont, + BN_CTX *ctx); +void BN_MONT_CTX_free(BN_MONT_CTX *mont); +int BN_MONT_CTX_set(BN_MONT_CTX *mont, const BIGNUM *mod, BN_CTX *ctx); +BN_MONT_CTX *BN_MONT_CTX_copy(BN_MONT_CTX *to, BN_MONT_CTX *from); +BN_MONT_CTX *BN_MONT_CTX_set_locked(BN_MONT_CTX **pmont, CRYPTO_RWLOCK *lock, + const BIGNUM *mod, BN_CTX *ctx); + +/* BN_BLINDING flags */ +# define BN_BLINDING_NO_UPDATE 0x00000001 +# define BN_BLINDING_NO_RECREATE 0x00000002 + +BN_BLINDING *BN_BLINDING_new(const BIGNUM *A, const BIGNUM *Ai, BIGNUM *mod); +void BN_BLINDING_free(BN_BLINDING *b); +int BN_BLINDING_update(BN_BLINDING *b, BN_CTX *ctx); +int BN_BLINDING_convert(BIGNUM *n, BN_BLINDING *b, BN_CTX *ctx); +int BN_BLINDING_invert(BIGNUM *n, BN_BLINDING *b, BN_CTX *ctx); +int BN_BLINDING_convert_ex(BIGNUM *n, BIGNUM *r, BN_BLINDING *b, BN_CTX *); +int BN_BLINDING_invert_ex(BIGNUM *n, const BIGNUM *r, BN_BLINDING *b, + BN_CTX *); + +int BN_BLINDING_is_current_thread(BN_BLINDING *b); +void BN_BLINDING_set_current_thread(BN_BLINDING *b); +int BN_BLINDING_lock(BN_BLINDING *b); +int BN_BLINDING_unlock(BN_BLINDING *b); + +unsigned long BN_BLINDING_get_flags(const BN_BLINDING *); +void BN_BLINDING_set_flags(BN_BLINDING *, unsigned long); +BN_BLINDING *BN_BLINDING_create_param(BN_BLINDING *b, + const BIGNUM *e, BIGNUM *m, BN_CTX *ctx, + int (*bn_mod_exp) (BIGNUM *r, + const BIGNUM *a, + const BIGNUM *p, + const BIGNUM *m, + BN_CTX *ctx, + BN_MONT_CTX *m_ctx), + BN_MONT_CTX *m_ctx); + +DEPRECATEDIN_0_9_8(void BN_set_params(int mul, int high, int low, int mont)) +DEPRECATEDIN_0_9_8(int BN_get_params(int which)) /* 0, mul, 1 high, 2 low, 3 + * mont */ + +BN_RECP_CTX *BN_RECP_CTX_new(void); +void BN_RECP_CTX_free(BN_RECP_CTX *recp); +int BN_RECP_CTX_set(BN_RECP_CTX *recp, const BIGNUM *rdiv, BN_CTX *ctx); +int BN_mod_mul_reciprocal(BIGNUM *r, const BIGNUM *x, const BIGNUM *y, + BN_RECP_CTX *recp, BN_CTX *ctx); +int BN_mod_exp_recp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx); +int BN_div_recp(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, + BN_RECP_CTX *recp, BN_CTX *ctx); + +# ifndef OPENSSL_NO_EC2M + +/* + * Functions for arithmetic over binary polynomials represented by BIGNUMs. + * The BIGNUM::neg property of BIGNUMs representing binary polynomials is + * ignored. Note that input arguments are not const so that their bit arrays + * can be expanded to the appropriate size if needed. + */ + +/* + * r = a + b + */ +int BN_GF2m_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); +# define BN_GF2m_sub(r, a, b) BN_GF2m_add(r, a, b) +/* + * r=a mod p + */ +int BN_GF2m_mod(BIGNUM *r, const BIGNUM *a, const BIGNUM *p); +/* r = (a * b) mod p */ +int BN_GF2m_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const BIGNUM *p, BN_CTX *ctx); +/* r = (a * a) mod p */ +int BN_GF2m_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +/* r = (1 / b) mod p */ +int BN_GF2m_mod_inv(BIGNUM *r, const BIGNUM *b, const BIGNUM *p, BN_CTX *ctx); +/* r = (a / b) mod p */ +int BN_GF2m_mod_div(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const BIGNUM *p, BN_CTX *ctx); +/* r = (a ^ b) mod p */ +int BN_GF2m_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const BIGNUM *p, BN_CTX *ctx); +/* r = sqrt(a) mod p */ +int BN_GF2m_mod_sqrt(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + BN_CTX *ctx); +/* r^2 + r = a mod p */ +int BN_GF2m_mod_solve_quad(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + BN_CTX *ctx); +# define BN_GF2m_cmp(a, b) BN_ucmp((a), (b)) +/*- + * Some functions allow for representation of the irreducible polynomials + * as an unsigned int[], say p. The irreducible f(t) is then of the form: + * t^p[0] + t^p[1] + ... + t^p[k] + * where m = p[0] > p[1] > ... > p[k] = 0. + */ +/* r = a mod p */ +int BN_GF2m_mod_arr(BIGNUM *r, const BIGNUM *a, const int p[]); +/* r = (a * b) mod p */ +int BN_GF2m_mod_mul_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const int p[], BN_CTX *ctx); +/* r = (a * a) mod p */ +int BN_GF2m_mod_sqr_arr(BIGNUM *r, const BIGNUM *a, const int p[], + BN_CTX *ctx); +/* r = (1 / b) mod p */ +int BN_GF2m_mod_inv_arr(BIGNUM *r, const BIGNUM *b, const int p[], + BN_CTX *ctx); +/* r = (a / b) mod p */ +int BN_GF2m_mod_div_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const int p[], BN_CTX *ctx); +/* r = (a ^ b) mod p */ +int BN_GF2m_mod_exp_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, + const int p[], BN_CTX *ctx); +/* r = sqrt(a) mod p */ +int BN_GF2m_mod_sqrt_arr(BIGNUM *r, const BIGNUM *a, + const int p[], BN_CTX *ctx); +/* r^2 + r = a mod p */ +int BN_GF2m_mod_solve_quad_arr(BIGNUM *r, const BIGNUM *a, + const int p[], BN_CTX *ctx); +int BN_GF2m_poly2arr(const BIGNUM *a, int p[], int max); +int BN_GF2m_arr2poly(const int p[], BIGNUM *a); + +# endif + +/* + * faster mod functions for the 'NIST primes' 0 <= a < p^2 + */ +int BN_nist_mod_192(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +int BN_nist_mod_224(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +int BN_nist_mod_256(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +int BN_nist_mod_384(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); +int BN_nist_mod_521(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); + +const BIGNUM *BN_get0_nist_prime_192(void); +const BIGNUM *BN_get0_nist_prime_224(void); +const BIGNUM *BN_get0_nist_prime_256(void); +const BIGNUM *BN_get0_nist_prime_384(void); +const BIGNUM *BN_get0_nist_prime_521(void); + +int (*BN_nist_mod_func(const BIGNUM *p)) (BIGNUM *r, const BIGNUM *a, + const BIGNUM *field, BN_CTX *ctx); + +int BN_generate_dsa_nonce(BIGNUM *out, const BIGNUM *range, + const BIGNUM *priv, const unsigned char *message, + size_t message_len, BN_CTX *ctx); + +/* Primes from RFC 2409 */ +BIGNUM *BN_get_rfc2409_prime_768(BIGNUM *bn); +BIGNUM *BN_get_rfc2409_prime_1024(BIGNUM *bn); + +/* Primes from RFC 3526 */ +BIGNUM *BN_get_rfc3526_prime_1536(BIGNUM *bn); +BIGNUM *BN_get_rfc3526_prime_2048(BIGNUM *bn); +BIGNUM *BN_get_rfc3526_prime_3072(BIGNUM *bn); +BIGNUM *BN_get_rfc3526_prime_4096(BIGNUM *bn); +BIGNUM *BN_get_rfc3526_prime_6144(BIGNUM *bn); +BIGNUM *BN_get_rfc3526_prime_8192(BIGNUM *bn); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define get_rfc2409_prime_768 BN_get_rfc2409_prime_768 +# define get_rfc2409_prime_1024 BN_get_rfc2409_prime_1024 +# define get_rfc3526_prime_1536 BN_get_rfc3526_prime_1536 +# define get_rfc3526_prime_2048 BN_get_rfc3526_prime_2048 +# define get_rfc3526_prime_3072 BN_get_rfc3526_prime_3072 +# define get_rfc3526_prime_4096 BN_get_rfc3526_prime_4096 +# define get_rfc3526_prime_6144 BN_get_rfc3526_prime_6144 +# define get_rfc3526_prime_8192 BN_get_rfc3526_prime_8192 +# endif + +int BN_bntest_rand(BIGNUM *rnd, int bits, int top, int bottom); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/bnerr.h b/thrid-party/openssl/openssl/bnerr.h new file mode 100644 index 0000000000..9f3c7cfaab --- /dev/null +++ b/thrid-party/openssl/openssl/bnerr.h @@ -0,0 +1,100 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BNERR_H +# define HEADER_BNERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_BN_strings(void); + +/* + * BN function codes. + */ +# define BN_F_BNRAND 127 +# define BN_F_BNRAND_RANGE 138 +# define BN_F_BN_BLINDING_CONVERT_EX 100 +# define BN_F_BN_BLINDING_CREATE_PARAM 128 +# define BN_F_BN_BLINDING_INVERT_EX 101 +# define BN_F_BN_BLINDING_NEW 102 +# define BN_F_BN_BLINDING_UPDATE 103 +# define BN_F_BN_BN2DEC 104 +# define BN_F_BN_BN2HEX 105 +# define BN_F_BN_COMPUTE_WNAF 142 +# define BN_F_BN_CTX_GET 116 +# define BN_F_BN_CTX_NEW 106 +# define BN_F_BN_CTX_START 129 +# define BN_F_BN_DIV 107 +# define BN_F_BN_DIV_RECP 130 +# define BN_F_BN_EXP 123 +# define BN_F_BN_EXPAND_INTERNAL 120 +# define BN_F_BN_GENCB_NEW 143 +# define BN_F_BN_GENERATE_DSA_NONCE 140 +# define BN_F_BN_GENERATE_PRIME_EX 141 +# define BN_F_BN_GF2M_MOD 131 +# define BN_F_BN_GF2M_MOD_EXP 132 +# define BN_F_BN_GF2M_MOD_MUL 133 +# define BN_F_BN_GF2M_MOD_SOLVE_QUAD 134 +# define BN_F_BN_GF2M_MOD_SOLVE_QUAD_ARR 135 +# define BN_F_BN_GF2M_MOD_SQR 136 +# define BN_F_BN_GF2M_MOD_SQRT 137 +# define BN_F_BN_LSHIFT 145 +# define BN_F_BN_MOD_EXP2_MONT 118 +# define BN_F_BN_MOD_EXP_MONT 109 +# define BN_F_BN_MOD_EXP_MONT_CONSTTIME 124 +# define BN_F_BN_MOD_EXP_MONT_WORD 117 +# define BN_F_BN_MOD_EXP_RECP 125 +# define BN_F_BN_MOD_EXP_SIMPLE 126 +# define BN_F_BN_MOD_INVERSE 110 +# define BN_F_BN_MOD_INVERSE_NO_BRANCH 139 +# define BN_F_BN_MOD_LSHIFT_QUICK 119 +# define BN_F_BN_MOD_SQRT 121 +# define BN_F_BN_MONT_CTX_NEW 149 +# define BN_F_BN_MPI2BN 112 +# define BN_F_BN_NEW 113 +# define BN_F_BN_POOL_GET 147 +# define BN_F_BN_RAND 114 +# define BN_F_BN_RAND_RANGE 122 +# define BN_F_BN_RECP_CTX_NEW 150 +# define BN_F_BN_RSHIFT 146 +# define BN_F_BN_SET_WORDS 144 +# define BN_F_BN_STACK_PUSH 148 +# define BN_F_BN_USUB 115 + +/* + * BN reason codes. + */ +# define BN_R_ARG2_LT_ARG3 100 +# define BN_R_BAD_RECIPROCAL 101 +# define BN_R_BIGNUM_TOO_LONG 114 +# define BN_R_BITS_TOO_SMALL 118 +# define BN_R_CALLED_WITH_EVEN_MODULUS 102 +# define BN_R_DIV_BY_ZERO 103 +# define BN_R_ENCODING_ERROR 104 +# define BN_R_EXPAND_ON_STATIC_BIGNUM_DATA 105 +# define BN_R_INPUT_NOT_REDUCED 110 +# define BN_R_INVALID_LENGTH 106 +# define BN_R_INVALID_RANGE 115 +# define BN_R_INVALID_SHIFT 119 +# define BN_R_NOT_A_SQUARE 111 +# define BN_R_NOT_INITIALIZED 107 +# define BN_R_NO_INVERSE 108 +# define BN_R_NO_SOLUTION 116 +# define BN_R_PRIVATE_KEY_TOO_LARGE 117 +# define BN_R_P_IS_NOT_PRIME 112 +# define BN_R_TOO_MANY_ITERATIONS 113 +# define BN_R_TOO_MANY_TEMPORARY_VARIABLES 109 + +#endif diff --git a/thrid-party/openssl/openssl/buffer.h b/thrid-party/openssl/openssl/buffer.h new file mode 100644 index 0000000000..d2765766b7 --- /dev/null +++ b/thrid-party/openssl/openssl/buffer.h @@ -0,0 +1,58 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BUFFER_H +# define HEADER_BUFFER_H + +# include +# ifndef HEADER_CRYPTO_H +# include +# endif +# include + + +#ifdef __cplusplus +extern "C" { +#endif + +# include +# include + +/* + * These names are outdated as of OpenSSL 1.1; a future release + * will move them to be deprecated. + */ +# define BUF_strdup(s) OPENSSL_strdup(s) +# define BUF_strndup(s, size) OPENSSL_strndup(s, size) +# define BUF_memdup(data, size) OPENSSL_memdup(data, size) +# define BUF_strlcpy(dst, src, size) OPENSSL_strlcpy(dst, src, size) +# define BUF_strlcat(dst, src, size) OPENSSL_strlcat(dst, src, size) +# define BUF_strnlen(str, maxlen) OPENSSL_strnlen(str, maxlen) + +struct buf_mem_st { + size_t length; /* current number of bytes */ + char *data; + size_t max; /* size of buffer */ + unsigned long flags; +}; + +# define BUF_MEM_FLAG_SECURE 0x01 + +BUF_MEM *BUF_MEM_new(void); +BUF_MEM *BUF_MEM_new_ex(unsigned long flags); +void BUF_MEM_free(BUF_MEM *a); +size_t BUF_MEM_grow(BUF_MEM *str, size_t len); +size_t BUF_MEM_grow_clean(BUF_MEM *str, size_t len); +void BUF_reverse(unsigned char *out, const unsigned char *in, size_t siz); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/buffererr.h b/thrid-party/openssl/openssl/buffererr.h new file mode 100644 index 0000000000..04f6ff7a83 --- /dev/null +++ b/thrid-party/openssl/openssl/buffererr.h @@ -0,0 +1,34 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_BUFERR_H +# define HEADER_BUFERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_BUF_strings(void); + +/* + * BUF function codes. + */ +# define BUF_F_BUF_MEM_GROW 100 +# define BUF_F_BUF_MEM_GROW_CLEAN 105 +# define BUF_F_BUF_MEM_NEW 101 + +/* + * BUF reason codes. + */ + +#endif diff --git a/thrid-party/openssl/openssl/camellia.h b/thrid-party/openssl/openssl/camellia.h new file mode 100644 index 0000000000..151f3c1349 --- /dev/null +++ b/thrid-party/openssl/openssl/camellia.h @@ -0,0 +1,83 @@ +/* + * Copyright 2006-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CAMELLIA_H +# define HEADER_CAMELLIA_H + +# include + +# ifndef OPENSSL_NO_CAMELLIA +# include +#ifdef __cplusplus +extern "C" { +#endif + +# define CAMELLIA_ENCRYPT 1 +# define CAMELLIA_DECRYPT 0 + +/* + * Because array size can't be a const in C, the following two are macros. + * Both sizes are in bytes. + */ + +/* This should be a hidden type, but EVP requires that the size be known */ + +# define CAMELLIA_BLOCK_SIZE 16 +# define CAMELLIA_TABLE_BYTE_LEN 272 +# define CAMELLIA_TABLE_WORD_LEN (CAMELLIA_TABLE_BYTE_LEN / 4) + +typedef unsigned int KEY_TABLE_TYPE[CAMELLIA_TABLE_WORD_LEN]; /* to match + * with WORD */ + +struct camellia_key_st { + union { + double d; /* ensures 64-bit align */ + KEY_TABLE_TYPE rd_key; + } u; + int grand_rounds; +}; +typedef struct camellia_key_st CAMELLIA_KEY; + +int Camellia_set_key(const unsigned char *userKey, const int bits, + CAMELLIA_KEY *key); + +void Camellia_encrypt(const unsigned char *in, unsigned char *out, + const CAMELLIA_KEY *key); +void Camellia_decrypt(const unsigned char *in, unsigned char *out, + const CAMELLIA_KEY *key); + +void Camellia_ecb_encrypt(const unsigned char *in, unsigned char *out, + const CAMELLIA_KEY *key, const int enc); +void Camellia_cbc_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char *ivec, const int enc); +void Camellia_cfb128_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char *ivec, int *num, const int enc); +void Camellia_cfb1_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char *ivec, int *num, const int enc); +void Camellia_cfb8_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char *ivec, int *num, const int enc); +void Camellia_ofb128_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char *ivec, int *num); +void Camellia_ctr128_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const CAMELLIA_KEY *key, + unsigned char ivec[CAMELLIA_BLOCK_SIZE], + unsigned char ecount_buf[CAMELLIA_BLOCK_SIZE], + unsigned int *num); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/cast.h b/thrid-party/openssl/openssl/cast.h new file mode 100644 index 0000000000..2cc89ae013 --- /dev/null +++ b/thrid-party/openssl/openssl/cast.h @@ -0,0 +1,53 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CAST_H +# define HEADER_CAST_H + +# include + +# ifndef OPENSSL_NO_CAST +# ifdef __cplusplus +extern "C" { +# endif + +# define CAST_ENCRYPT 1 +# define CAST_DECRYPT 0 + +# define CAST_LONG unsigned int + +# define CAST_BLOCK 8 +# define CAST_KEY_LENGTH 16 + +typedef struct cast_key_st { + CAST_LONG data[32]; + int short_key; /* Use reduced rounds for short key */ +} CAST_KEY; + +void CAST_set_key(CAST_KEY *key, int len, const unsigned char *data); +void CAST_ecb_encrypt(const unsigned char *in, unsigned char *out, + const CAST_KEY *key, int enc); +void CAST_encrypt(CAST_LONG *data, const CAST_KEY *key); +void CAST_decrypt(CAST_LONG *data, const CAST_KEY *key); +void CAST_cbc_encrypt(const unsigned char *in, unsigned char *out, + long length, const CAST_KEY *ks, unsigned char *iv, + int enc); +void CAST_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, const CAST_KEY *schedule, + unsigned char *ivec, int *num, int enc); +void CAST_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, const CAST_KEY *schedule, + unsigned char *ivec, int *num); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/cmac.h b/thrid-party/openssl/openssl/cmac.h new file mode 100644 index 0000000000..3535a9abf7 --- /dev/null +++ b/thrid-party/openssl/openssl/cmac.h @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CMAC_H +# define HEADER_CMAC_H + +# ifndef OPENSSL_NO_CMAC + +#ifdef __cplusplus +extern "C" { +#endif + +# include + +/* Opaque */ +typedef struct CMAC_CTX_st CMAC_CTX; + +CMAC_CTX *CMAC_CTX_new(void); +void CMAC_CTX_cleanup(CMAC_CTX *ctx); +void CMAC_CTX_free(CMAC_CTX *ctx); +EVP_CIPHER_CTX *CMAC_CTX_get0_cipher_ctx(CMAC_CTX *ctx); +int CMAC_CTX_copy(CMAC_CTX *out, const CMAC_CTX *in); + +int CMAC_Init(CMAC_CTX *ctx, const void *key, size_t keylen, + const EVP_CIPHER *cipher, ENGINE *impl); +int CMAC_Update(CMAC_CTX *ctx, const void *data, size_t dlen); +int CMAC_Final(CMAC_CTX *ctx, unsigned char *out, size_t *poutlen); +int CMAC_resume(CMAC_CTX *ctx); + +#ifdef __cplusplus +} +#endif + +# endif +#endif diff --git a/thrid-party/openssl/openssl/cms.h b/thrid-party/openssl/openssl/cms.h new file mode 100644 index 0000000000..c7627968c7 --- /dev/null +++ b/thrid-party/openssl/openssl/cms.h @@ -0,0 +1,339 @@ +/* + * Copyright 2008-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CMS_H +# define HEADER_CMS_H + +# include + +# ifndef OPENSSL_NO_CMS +# include +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +typedef struct CMS_ContentInfo_st CMS_ContentInfo; +typedef struct CMS_SignerInfo_st CMS_SignerInfo; +typedef struct CMS_CertificateChoices CMS_CertificateChoices; +typedef struct CMS_RevocationInfoChoice_st CMS_RevocationInfoChoice; +typedef struct CMS_RecipientInfo_st CMS_RecipientInfo; +typedef struct CMS_ReceiptRequest_st CMS_ReceiptRequest; +typedef struct CMS_Receipt_st CMS_Receipt; +typedef struct CMS_RecipientEncryptedKey_st CMS_RecipientEncryptedKey; +typedef struct CMS_OtherKeyAttribute_st CMS_OtherKeyAttribute; + +DEFINE_STACK_OF(CMS_SignerInfo) +DEFINE_STACK_OF(CMS_RecipientEncryptedKey) +DEFINE_STACK_OF(CMS_RecipientInfo) +DEFINE_STACK_OF(CMS_RevocationInfoChoice) +DECLARE_ASN1_FUNCTIONS(CMS_ContentInfo) +DECLARE_ASN1_FUNCTIONS(CMS_ReceiptRequest) +DECLARE_ASN1_PRINT_FUNCTION(CMS_ContentInfo) + +# define CMS_SIGNERINFO_ISSUER_SERIAL 0 +# define CMS_SIGNERINFO_KEYIDENTIFIER 1 + +# define CMS_RECIPINFO_NONE -1 +# define CMS_RECIPINFO_TRANS 0 +# define CMS_RECIPINFO_AGREE 1 +# define CMS_RECIPINFO_KEK 2 +# define CMS_RECIPINFO_PASS 3 +# define CMS_RECIPINFO_OTHER 4 + +/* S/MIME related flags */ + +# define CMS_TEXT 0x1 +# define CMS_NOCERTS 0x2 +# define CMS_NO_CONTENT_VERIFY 0x4 +# define CMS_NO_ATTR_VERIFY 0x8 +# define CMS_NOSIGS \ + (CMS_NO_CONTENT_VERIFY|CMS_NO_ATTR_VERIFY) +# define CMS_NOINTERN 0x10 +# define CMS_NO_SIGNER_CERT_VERIFY 0x20 +# define CMS_NOVERIFY 0x20 +# define CMS_DETACHED 0x40 +# define CMS_BINARY 0x80 +# define CMS_NOATTR 0x100 +# define CMS_NOSMIMECAP 0x200 +# define CMS_NOOLDMIMETYPE 0x400 +# define CMS_CRLFEOL 0x800 +# define CMS_STREAM 0x1000 +# define CMS_NOCRL 0x2000 +# define CMS_PARTIAL 0x4000 +# define CMS_REUSE_DIGEST 0x8000 +# define CMS_USE_KEYID 0x10000 +# define CMS_DEBUG_DECRYPT 0x20000 +# define CMS_KEY_PARAM 0x40000 +# define CMS_ASCIICRLF 0x80000 + +const ASN1_OBJECT *CMS_get0_type(const CMS_ContentInfo *cms); + +BIO *CMS_dataInit(CMS_ContentInfo *cms, BIO *icont); +int CMS_dataFinal(CMS_ContentInfo *cms, BIO *bio); + +ASN1_OCTET_STRING **CMS_get0_content(CMS_ContentInfo *cms); +int CMS_is_detached(CMS_ContentInfo *cms); +int CMS_set_detached(CMS_ContentInfo *cms, int detached); + +# ifdef HEADER_PEM_H +DECLARE_PEM_rw_const(CMS, CMS_ContentInfo) +# endif +int CMS_stream(unsigned char ***boundary, CMS_ContentInfo *cms); +CMS_ContentInfo *d2i_CMS_bio(BIO *bp, CMS_ContentInfo **cms); +int i2d_CMS_bio(BIO *bp, CMS_ContentInfo *cms); + +BIO *BIO_new_CMS(BIO *out, CMS_ContentInfo *cms); +int i2d_CMS_bio_stream(BIO *out, CMS_ContentInfo *cms, BIO *in, int flags); +int PEM_write_bio_CMS_stream(BIO *out, CMS_ContentInfo *cms, BIO *in, + int flags); +CMS_ContentInfo *SMIME_read_CMS(BIO *bio, BIO **bcont); +int SMIME_write_CMS(BIO *bio, CMS_ContentInfo *cms, BIO *data, int flags); + +int CMS_final(CMS_ContentInfo *cms, BIO *data, BIO *dcont, + unsigned int flags); + +CMS_ContentInfo *CMS_sign(X509 *signcert, EVP_PKEY *pkey, + STACK_OF(X509) *certs, BIO *data, + unsigned int flags); + +CMS_ContentInfo *CMS_sign_receipt(CMS_SignerInfo *si, + X509 *signcert, EVP_PKEY *pkey, + STACK_OF(X509) *certs, unsigned int flags); + +int CMS_data(CMS_ContentInfo *cms, BIO *out, unsigned int flags); +CMS_ContentInfo *CMS_data_create(BIO *in, unsigned int flags); + +int CMS_digest_verify(CMS_ContentInfo *cms, BIO *dcont, BIO *out, + unsigned int flags); +CMS_ContentInfo *CMS_digest_create(BIO *in, const EVP_MD *md, + unsigned int flags); + +int CMS_EncryptedData_decrypt(CMS_ContentInfo *cms, + const unsigned char *key, size_t keylen, + BIO *dcont, BIO *out, unsigned int flags); + +CMS_ContentInfo *CMS_EncryptedData_encrypt(BIO *in, const EVP_CIPHER *cipher, + const unsigned char *key, + size_t keylen, unsigned int flags); + +int CMS_EncryptedData_set1_key(CMS_ContentInfo *cms, const EVP_CIPHER *ciph, + const unsigned char *key, size_t keylen); + +int CMS_verify(CMS_ContentInfo *cms, STACK_OF(X509) *certs, + X509_STORE *store, BIO *dcont, BIO *out, unsigned int flags); + +int CMS_verify_receipt(CMS_ContentInfo *rcms, CMS_ContentInfo *ocms, + STACK_OF(X509) *certs, + X509_STORE *store, unsigned int flags); + +STACK_OF(X509) *CMS_get0_signers(CMS_ContentInfo *cms); + +CMS_ContentInfo *CMS_encrypt(STACK_OF(X509) *certs, BIO *in, + const EVP_CIPHER *cipher, unsigned int flags); + +int CMS_decrypt(CMS_ContentInfo *cms, EVP_PKEY *pkey, X509 *cert, + BIO *dcont, BIO *out, unsigned int flags); + +int CMS_decrypt_set1_pkey(CMS_ContentInfo *cms, EVP_PKEY *pk, X509 *cert); +int CMS_decrypt_set1_key(CMS_ContentInfo *cms, + unsigned char *key, size_t keylen, + const unsigned char *id, size_t idlen); +int CMS_decrypt_set1_password(CMS_ContentInfo *cms, + unsigned char *pass, ossl_ssize_t passlen); + +STACK_OF(CMS_RecipientInfo) *CMS_get0_RecipientInfos(CMS_ContentInfo *cms); +int CMS_RecipientInfo_type(CMS_RecipientInfo *ri); +EVP_PKEY_CTX *CMS_RecipientInfo_get0_pkey_ctx(CMS_RecipientInfo *ri); +CMS_ContentInfo *CMS_EnvelopedData_create(const EVP_CIPHER *cipher); +CMS_RecipientInfo *CMS_add1_recipient_cert(CMS_ContentInfo *cms, + X509 *recip, unsigned int flags); +int CMS_RecipientInfo_set0_pkey(CMS_RecipientInfo *ri, EVP_PKEY *pkey); +int CMS_RecipientInfo_ktri_cert_cmp(CMS_RecipientInfo *ri, X509 *cert); +int CMS_RecipientInfo_ktri_get0_algs(CMS_RecipientInfo *ri, + EVP_PKEY **pk, X509 **recip, + X509_ALGOR **palg); +int CMS_RecipientInfo_ktri_get0_signer_id(CMS_RecipientInfo *ri, + ASN1_OCTET_STRING **keyid, + X509_NAME **issuer, + ASN1_INTEGER **sno); + +CMS_RecipientInfo *CMS_add0_recipient_key(CMS_ContentInfo *cms, int nid, + unsigned char *key, size_t keylen, + unsigned char *id, size_t idlen, + ASN1_GENERALIZEDTIME *date, + ASN1_OBJECT *otherTypeId, + ASN1_TYPE *otherType); + +int CMS_RecipientInfo_kekri_get0_id(CMS_RecipientInfo *ri, + X509_ALGOR **palg, + ASN1_OCTET_STRING **pid, + ASN1_GENERALIZEDTIME **pdate, + ASN1_OBJECT **potherid, + ASN1_TYPE **pothertype); + +int CMS_RecipientInfo_set0_key(CMS_RecipientInfo *ri, + unsigned char *key, size_t keylen); + +int CMS_RecipientInfo_kekri_id_cmp(CMS_RecipientInfo *ri, + const unsigned char *id, size_t idlen); + +int CMS_RecipientInfo_set0_password(CMS_RecipientInfo *ri, + unsigned char *pass, + ossl_ssize_t passlen); + +CMS_RecipientInfo *CMS_add0_recipient_password(CMS_ContentInfo *cms, + int iter, int wrap_nid, + int pbe_nid, + unsigned char *pass, + ossl_ssize_t passlen, + const EVP_CIPHER *kekciph); + +int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri); +int CMS_RecipientInfo_encrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri); + +int CMS_uncompress(CMS_ContentInfo *cms, BIO *dcont, BIO *out, + unsigned int flags); +CMS_ContentInfo *CMS_compress(BIO *in, int comp_nid, unsigned int flags); + +int CMS_set1_eContentType(CMS_ContentInfo *cms, const ASN1_OBJECT *oid); +const ASN1_OBJECT *CMS_get0_eContentType(CMS_ContentInfo *cms); + +CMS_CertificateChoices *CMS_add0_CertificateChoices(CMS_ContentInfo *cms); +int CMS_add0_cert(CMS_ContentInfo *cms, X509 *cert); +int CMS_add1_cert(CMS_ContentInfo *cms, X509 *cert); +STACK_OF(X509) *CMS_get1_certs(CMS_ContentInfo *cms); + +CMS_RevocationInfoChoice *CMS_add0_RevocationInfoChoice(CMS_ContentInfo *cms); +int CMS_add0_crl(CMS_ContentInfo *cms, X509_CRL *crl); +int CMS_add1_crl(CMS_ContentInfo *cms, X509_CRL *crl); +STACK_OF(X509_CRL) *CMS_get1_crls(CMS_ContentInfo *cms); + +int CMS_SignedData_init(CMS_ContentInfo *cms); +CMS_SignerInfo *CMS_add1_signer(CMS_ContentInfo *cms, + X509 *signer, EVP_PKEY *pk, const EVP_MD *md, + unsigned int flags); +EVP_PKEY_CTX *CMS_SignerInfo_get0_pkey_ctx(CMS_SignerInfo *si); +EVP_MD_CTX *CMS_SignerInfo_get0_md_ctx(CMS_SignerInfo *si); +STACK_OF(CMS_SignerInfo) *CMS_get0_SignerInfos(CMS_ContentInfo *cms); + +void CMS_SignerInfo_set1_signer_cert(CMS_SignerInfo *si, X509 *signer); +int CMS_SignerInfo_get0_signer_id(CMS_SignerInfo *si, + ASN1_OCTET_STRING **keyid, + X509_NAME **issuer, ASN1_INTEGER **sno); +int CMS_SignerInfo_cert_cmp(CMS_SignerInfo *si, X509 *cert); +int CMS_set1_signers_certs(CMS_ContentInfo *cms, STACK_OF(X509) *certs, + unsigned int flags); +void CMS_SignerInfo_get0_algs(CMS_SignerInfo *si, EVP_PKEY **pk, + X509 **signer, X509_ALGOR **pdig, + X509_ALGOR **psig); +ASN1_OCTET_STRING *CMS_SignerInfo_get0_signature(CMS_SignerInfo *si); +int CMS_SignerInfo_sign(CMS_SignerInfo *si); +int CMS_SignerInfo_verify(CMS_SignerInfo *si); +int CMS_SignerInfo_verify_content(CMS_SignerInfo *si, BIO *chain); + +int CMS_add_smimecap(CMS_SignerInfo *si, STACK_OF(X509_ALGOR) *algs); +int CMS_add_simple_smimecap(STACK_OF(X509_ALGOR) **algs, + int algnid, int keysize); +int CMS_add_standard_smimecap(STACK_OF(X509_ALGOR) **smcap); + +int CMS_signed_get_attr_count(const CMS_SignerInfo *si); +int CMS_signed_get_attr_by_NID(const CMS_SignerInfo *si, int nid, + int lastpos); +int CMS_signed_get_attr_by_OBJ(const CMS_SignerInfo *si, const ASN1_OBJECT *obj, + int lastpos); +X509_ATTRIBUTE *CMS_signed_get_attr(const CMS_SignerInfo *si, int loc); +X509_ATTRIBUTE *CMS_signed_delete_attr(CMS_SignerInfo *si, int loc); +int CMS_signed_add1_attr(CMS_SignerInfo *si, X509_ATTRIBUTE *attr); +int CMS_signed_add1_attr_by_OBJ(CMS_SignerInfo *si, + const ASN1_OBJECT *obj, int type, + const void *bytes, int len); +int CMS_signed_add1_attr_by_NID(CMS_SignerInfo *si, + int nid, int type, + const void *bytes, int len); +int CMS_signed_add1_attr_by_txt(CMS_SignerInfo *si, + const char *attrname, int type, + const void *bytes, int len); +void *CMS_signed_get0_data_by_OBJ(CMS_SignerInfo *si, const ASN1_OBJECT *oid, + int lastpos, int type); + +int CMS_unsigned_get_attr_count(const CMS_SignerInfo *si); +int CMS_unsigned_get_attr_by_NID(const CMS_SignerInfo *si, int nid, + int lastpos); +int CMS_unsigned_get_attr_by_OBJ(const CMS_SignerInfo *si, + const ASN1_OBJECT *obj, int lastpos); +X509_ATTRIBUTE *CMS_unsigned_get_attr(const CMS_SignerInfo *si, int loc); +X509_ATTRIBUTE *CMS_unsigned_delete_attr(CMS_SignerInfo *si, int loc); +int CMS_unsigned_add1_attr(CMS_SignerInfo *si, X509_ATTRIBUTE *attr); +int CMS_unsigned_add1_attr_by_OBJ(CMS_SignerInfo *si, + const ASN1_OBJECT *obj, int type, + const void *bytes, int len); +int CMS_unsigned_add1_attr_by_NID(CMS_SignerInfo *si, + int nid, int type, + const void *bytes, int len); +int CMS_unsigned_add1_attr_by_txt(CMS_SignerInfo *si, + const char *attrname, int type, + const void *bytes, int len); +void *CMS_unsigned_get0_data_by_OBJ(CMS_SignerInfo *si, ASN1_OBJECT *oid, + int lastpos, int type); + +int CMS_get1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest **prr); +CMS_ReceiptRequest *CMS_ReceiptRequest_create0(unsigned char *id, int idlen, + int allorfirst, + STACK_OF(GENERAL_NAMES) + *receiptList, STACK_OF(GENERAL_NAMES) + *receiptsTo); +int CMS_add1_ReceiptRequest(CMS_SignerInfo *si, CMS_ReceiptRequest *rr); +void CMS_ReceiptRequest_get0_values(CMS_ReceiptRequest *rr, + ASN1_STRING **pcid, + int *pallorfirst, + STACK_OF(GENERAL_NAMES) **plist, + STACK_OF(GENERAL_NAMES) **prto); +int CMS_RecipientInfo_kari_get0_alg(CMS_RecipientInfo *ri, + X509_ALGOR **palg, + ASN1_OCTET_STRING **pukm); +STACK_OF(CMS_RecipientEncryptedKey) +*CMS_RecipientInfo_kari_get0_reks(CMS_RecipientInfo *ri); + +int CMS_RecipientInfo_kari_get0_orig_id(CMS_RecipientInfo *ri, + X509_ALGOR **pubalg, + ASN1_BIT_STRING **pubkey, + ASN1_OCTET_STRING **keyid, + X509_NAME **issuer, + ASN1_INTEGER **sno); + +int CMS_RecipientInfo_kari_orig_id_cmp(CMS_RecipientInfo *ri, X509 *cert); + +int CMS_RecipientEncryptedKey_get0_id(CMS_RecipientEncryptedKey *rek, + ASN1_OCTET_STRING **keyid, + ASN1_GENERALIZEDTIME **tm, + CMS_OtherKeyAttribute **other, + X509_NAME **issuer, ASN1_INTEGER **sno); +int CMS_RecipientEncryptedKey_cert_cmp(CMS_RecipientEncryptedKey *rek, + X509 *cert); +int CMS_RecipientInfo_kari_set0_pkey(CMS_RecipientInfo *ri, EVP_PKEY *pk); +EVP_CIPHER_CTX *CMS_RecipientInfo_kari_get0_ctx(CMS_RecipientInfo *ri); +int CMS_RecipientInfo_kari_decrypt(CMS_ContentInfo *cms, + CMS_RecipientInfo *ri, + CMS_RecipientEncryptedKey *rek); + +int CMS_SharedInfo_encode(unsigned char **pder, X509_ALGOR *kekalg, + ASN1_OCTET_STRING *ukm, int keylen); + +/* Backward compatibility for spelling errors. */ +# define CMS_R_UNKNOWN_DIGEST_ALGORITM CMS_R_UNKNOWN_DIGEST_ALGORITHM +# define CMS_R_UNSUPPORTED_RECPIENTINFO_TYPE \ + CMS_R_UNSUPPORTED_RECIPIENTINFO_TYPE + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/cmserr.h b/thrid-party/openssl/openssl/cmserr.h new file mode 100644 index 0000000000..7dbc13dc93 --- /dev/null +++ b/thrid-party/openssl/openssl/cmserr.h @@ -0,0 +1,202 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CMSERR_H +# define HEADER_CMSERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_CMS + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_CMS_strings(void); + +/* + * CMS function codes. + */ +# define CMS_F_CHECK_CONTENT 99 +# define CMS_F_CMS_ADD0_CERT 164 +# define CMS_F_CMS_ADD0_RECIPIENT_KEY 100 +# define CMS_F_CMS_ADD0_RECIPIENT_PASSWORD 165 +# define CMS_F_CMS_ADD1_RECEIPTREQUEST 158 +# define CMS_F_CMS_ADD1_RECIPIENT_CERT 101 +# define CMS_F_CMS_ADD1_SIGNER 102 +# define CMS_F_CMS_ADD1_SIGNINGTIME 103 +# define CMS_F_CMS_COMPRESS 104 +# define CMS_F_CMS_COMPRESSEDDATA_CREATE 105 +# define CMS_F_CMS_COMPRESSEDDATA_INIT_BIO 106 +# define CMS_F_CMS_COPY_CONTENT 107 +# define CMS_F_CMS_COPY_MESSAGEDIGEST 108 +# define CMS_F_CMS_DATA 109 +# define CMS_F_CMS_DATAFINAL 110 +# define CMS_F_CMS_DATAINIT 111 +# define CMS_F_CMS_DECRYPT 112 +# define CMS_F_CMS_DECRYPT_SET1_KEY 113 +# define CMS_F_CMS_DECRYPT_SET1_PASSWORD 166 +# define CMS_F_CMS_DECRYPT_SET1_PKEY 114 +# define CMS_F_CMS_DIGESTALGORITHM_FIND_CTX 115 +# define CMS_F_CMS_DIGESTALGORITHM_INIT_BIO 116 +# define CMS_F_CMS_DIGESTEDDATA_DO_FINAL 117 +# define CMS_F_CMS_DIGEST_VERIFY 118 +# define CMS_F_CMS_ENCODE_RECEIPT 161 +# define CMS_F_CMS_ENCRYPT 119 +# define CMS_F_CMS_ENCRYPTEDCONTENT_INIT 179 +# define CMS_F_CMS_ENCRYPTEDCONTENT_INIT_BIO 120 +# define CMS_F_CMS_ENCRYPTEDDATA_DECRYPT 121 +# define CMS_F_CMS_ENCRYPTEDDATA_ENCRYPT 122 +# define CMS_F_CMS_ENCRYPTEDDATA_SET1_KEY 123 +# define CMS_F_CMS_ENVELOPEDDATA_CREATE 124 +# define CMS_F_CMS_ENVELOPEDDATA_INIT_BIO 125 +# define CMS_F_CMS_ENVELOPED_DATA_INIT 126 +# define CMS_F_CMS_ENV_ASN1_CTRL 171 +# define CMS_F_CMS_FINAL 127 +# define CMS_F_CMS_GET0_CERTIFICATE_CHOICES 128 +# define CMS_F_CMS_GET0_CONTENT 129 +# define CMS_F_CMS_GET0_ECONTENT_TYPE 130 +# define CMS_F_CMS_GET0_ENVELOPED 131 +# define CMS_F_CMS_GET0_REVOCATION_CHOICES 132 +# define CMS_F_CMS_GET0_SIGNED 133 +# define CMS_F_CMS_MSGSIGDIGEST_ADD1 162 +# define CMS_F_CMS_RECEIPTREQUEST_CREATE0 159 +# define CMS_F_CMS_RECEIPT_VERIFY 160 +# define CMS_F_CMS_RECIPIENTINFO_DECRYPT 134 +# define CMS_F_CMS_RECIPIENTINFO_ENCRYPT 169 +# define CMS_F_CMS_RECIPIENTINFO_KARI_ENCRYPT 178 +# define CMS_F_CMS_RECIPIENTINFO_KARI_GET0_ALG 175 +# define CMS_F_CMS_RECIPIENTINFO_KARI_GET0_ORIG_ID 173 +# define CMS_F_CMS_RECIPIENTINFO_KARI_GET0_REKS 172 +# define CMS_F_CMS_RECIPIENTINFO_KARI_ORIG_ID_CMP 174 +# define CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT 135 +# define CMS_F_CMS_RECIPIENTINFO_KEKRI_ENCRYPT 136 +# define CMS_F_CMS_RECIPIENTINFO_KEKRI_GET0_ID 137 +# define CMS_F_CMS_RECIPIENTINFO_KEKRI_ID_CMP 138 +# define CMS_F_CMS_RECIPIENTINFO_KTRI_CERT_CMP 139 +# define CMS_F_CMS_RECIPIENTINFO_KTRI_DECRYPT 140 +# define CMS_F_CMS_RECIPIENTINFO_KTRI_ENCRYPT 141 +# define CMS_F_CMS_RECIPIENTINFO_KTRI_GET0_ALGS 142 +# define CMS_F_CMS_RECIPIENTINFO_KTRI_GET0_SIGNER_ID 143 +# define CMS_F_CMS_RECIPIENTINFO_PWRI_CRYPT 167 +# define CMS_F_CMS_RECIPIENTINFO_SET0_KEY 144 +# define CMS_F_CMS_RECIPIENTINFO_SET0_PASSWORD 168 +# define CMS_F_CMS_RECIPIENTINFO_SET0_PKEY 145 +# define CMS_F_CMS_SD_ASN1_CTRL 170 +# define CMS_F_CMS_SET1_IAS 176 +# define CMS_F_CMS_SET1_KEYID 177 +# define CMS_F_CMS_SET1_SIGNERIDENTIFIER 146 +# define CMS_F_CMS_SET_DETACHED 147 +# define CMS_F_CMS_SIGN 148 +# define CMS_F_CMS_SIGNED_DATA_INIT 149 +# define CMS_F_CMS_SIGNERINFO_CONTENT_SIGN 150 +# define CMS_F_CMS_SIGNERINFO_SIGN 151 +# define CMS_F_CMS_SIGNERINFO_VERIFY 152 +# define CMS_F_CMS_SIGNERINFO_VERIFY_CERT 153 +# define CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT 154 +# define CMS_F_CMS_SIGN_RECEIPT 163 +# define CMS_F_CMS_SI_CHECK_ATTRIBUTES 183 +# define CMS_F_CMS_STREAM 155 +# define CMS_F_CMS_UNCOMPRESS 156 +# define CMS_F_CMS_VERIFY 157 +# define CMS_F_KEK_UNWRAP_KEY 180 + +/* + * CMS reason codes. + */ +# define CMS_R_ADD_SIGNER_ERROR 99 +# define CMS_R_ATTRIBUTE_ERROR 161 +# define CMS_R_CERTIFICATE_ALREADY_PRESENT 175 +# define CMS_R_CERTIFICATE_HAS_NO_KEYID 160 +# define CMS_R_CERTIFICATE_VERIFY_ERROR 100 +# define CMS_R_CIPHER_INITIALISATION_ERROR 101 +# define CMS_R_CIPHER_PARAMETER_INITIALISATION_ERROR 102 +# define CMS_R_CMS_DATAFINAL_ERROR 103 +# define CMS_R_CMS_LIB 104 +# define CMS_R_CONTENTIDENTIFIER_MISMATCH 170 +# define CMS_R_CONTENT_NOT_FOUND 105 +# define CMS_R_CONTENT_TYPE_MISMATCH 171 +# define CMS_R_CONTENT_TYPE_NOT_COMPRESSED_DATA 106 +# define CMS_R_CONTENT_TYPE_NOT_ENVELOPED_DATA 107 +# define CMS_R_CONTENT_TYPE_NOT_SIGNED_DATA 108 +# define CMS_R_CONTENT_VERIFY_ERROR 109 +# define CMS_R_CTRL_ERROR 110 +# define CMS_R_CTRL_FAILURE 111 +# define CMS_R_DECRYPT_ERROR 112 +# define CMS_R_ERROR_GETTING_PUBLIC_KEY 113 +# define CMS_R_ERROR_READING_MESSAGEDIGEST_ATTRIBUTE 114 +# define CMS_R_ERROR_SETTING_KEY 115 +# define CMS_R_ERROR_SETTING_RECIPIENTINFO 116 +# define CMS_R_INVALID_ENCRYPTED_KEY_LENGTH 117 +# define CMS_R_INVALID_KEY_ENCRYPTION_PARAMETER 176 +# define CMS_R_INVALID_KEY_LENGTH 118 +# define CMS_R_MD_BIO_INIT_ERROR 119 +# define CMS_R_MESSAGEDIGEST_ATTRIBUTE_WRONG_LENGTH 120 +# define CMS_R_MESSAGEDIGEST_WRONG_LENGTH 121 +# define CMS_R_MSGSIGDIGEST_ERROR 172 +# define CMS_R_MSGSIGDIGEST_VERIFICATION_FAILURE 162 +# define CMS_R_MSGSIGDIGEST_WRONG_LENGTH 163 +# define CMS_R_NEED_ONE_SIGNER 164 +# define CMS_R_NOT_A_SIGNED_RECEIPT 165 +# define CMS_R_NOT_ENCRYPTED_DATA 122 +# define CMS_R_NOT_KEK 123 +# define CMS_R_NOT_KEY_AGREEMENT 181 +# define CMS_R_NOT_KEY_TRANSPORT 124 +# define CMS_R_NOT_PWRI 177 +# define CMS_R_NOT_SUPPORTED_FOR_THIS_KEY_TYPE 125 +# define CMS_R_NO_CIPHER 126 +# define CMS_R_NO_CONTENT 127 +# define CMS_R_NO_CONTENT_TYPE 173 +# define CMS_R_NO_DEFAULT_DIGEST 128 +# define CMS_R_NO_DIGEST_SET 129 +# define CMS_R_NO_KEY 130 +# define CMS_R_NO_KEY_OR_CERT 174 +# define CMS_R_NO_MATCHING_DIGEST 131 +# define CMS_R_NO_MATCHING_RECIPIENT 132 +# define CMS_R_NO_MATCHING_SIGNATURE 166 +# define CMS_R_NO_MSGSIGDIGEST 167 +# define CMS_R_NO_PASSWORD 178 +# define CMS_R_NO_PRIVATE_KEY 133 +# define CMS_R_NO_PUBLIC_KEY 134 +# define CMS_R_NO_RECEIPT_REQUEST 168 +# define CMS_R_NO_SIGNERS 135 +# define CMS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 136 +# define CMS_R_RECEIPT_DECODE_ERROR 169 +# define CMS_R_RECIPIENT_ERROR 137 +# define CMS_R_SIGNER_CERTIFICATE_NOT_FOUND 138 +# define CMS_R_SIGNFINAL_ERROR 139 +# define CMS_R_SMIME_TEXT_ERROR 140 +# define CMS_R_STORE_INIT_ERROR 141 +# define CMS_R_TYPE_NOT_COMPRESSED_DATA 142 +# define CMS_R_TYPE_NOT_DATA 143 +# define CMS_R_TYPE_NOT_DIGESTED_DATA 144 +# define CMS_R_TYPE_NOT_ENCRYPTED_DATA 145 +# define CMS_R_TYPE_NOT_ENVELOPED_DATA 146 +# define CMS_R_UNABLE_TO_FINALIZE_CONTEXT 147 +# define CMS_R_UNKNOWN_CIPHER 148 +# define CMS_R_UNKNOWN_DIGEST_ALGORITHM 149 +# define CMS_R_UNKNOWN_ID 150 +# define CMS_R_UNSUPPORTED_COMPRESSION_ALGORITHM 151 +# define CMS_R_UNSUPPORTED_CONTENT_TYPE 152 +# define CMS_R_UNSUPPORTED_KEK_ALGORITHM 153 +# define CMS_R_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM 179 +# define CMS_R_UNSUPPORTED_RECIPIENTINFO_TYPE 155 +# define CMS_R_UNSUPPORTED_RECIPIENT_TYPE 154 +# define CMS_R_UNSUPPORTED_TYPE 156 +# define CMS_R_UNWRAP_ERROR 157 +# define CMS_R_UNWRAP_FAILURE 180 +# define CMS_R_VERIFICATION_FAILURE 158 +# define CMS_R_WRAP_ERROR 159 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/comp.h b/thrid-party/openssl/openssl/comp.h new file mode 100644 index 0000000000..d814d3cf25 --- /dev/null +++ b/thrid-party/openssl/openssl/comp.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_COMP_H +# define HEADER_COMP_H + +# include + +# ifndef OPENSSL_NO_COMP +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + + + +COMP_CTX *COMP_CTX_new(COMP_METHOD *meth); +const COMP_METHOD *COMP_CTX_get_method(const COMP_CTX *ctx); +int COMP_CTX_get_type(const COMP_CTX* comp); +int COMP_get_type(const COMP_METHOD *meth); +const char *COMP_get_name(const COMP_METHOD *meth); +void COMP_CTX_free(COMP_CTX *ctx); + +int COMP_compress_block(COMP_CTX *ctx, unsigned char *out, int olen, + unsigned char *in, int ilen); +int COMP_expand_block(COMP_CTX *ctx, unsigned char *out, int olen, + unsigned char *in, int ilen); + +COMP_METHOD *COMP_zlib(void); + +#if OPENSSL_API_COMPAT < 0x10100000L +#define COMP_zlib_cleanup() while(0) continue +#endif + +# ifdef HEADER_BIO_H +# ifdef ZLIB +const BIO_METHOD *BIO_f_zlib(void); +# endif +# endif + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/comperr.h b/thrid-party/openssl/openssl/comperr.h new file mode 100644 index 0000000000..90231e9aa3 --- /dev/null +++ b/thrid-party/openssl/openssl/comperr.h @@ -0,0 +1,44 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_COMPERR_H +# define HEADER_COMPERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_COMP + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_COMP_strings(void); + +/* + * COMP function codes. + */ +# define COMP_F_BIO_ZLIB_FLUSH 99 +# define COMP_F_BIO_ZLIB_NEW 100 +# define COMP_F_BIO_ZLIB_READ 101 +# define COMP_F_BIO_ZLIB_WRITE 102 +# define COMP_F_COMP_CTX_NEW 103 + +/* + * COMP reason codes. + */ +# define COMP_R_ZLIB_DEFLATE_ERROR 99 +# define COMP_R_ZLIB_INFLATE_ERROR 100 +# define COMP_R_ZLIB_NOT_SUPPORTED 101 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/conf.h b/thrid-party/openssl/openssl/conf.h new file mode 100644 index 0000000000..7336cd2f1d --- /dev/null +++ b/thrid-party/openssl/openssl/conf.h @@ -0,0 +1,168 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CONF_H +# define HEADER_CONF_H + +# include +# include +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *section; + char *name; + char *value; +} CONF_VALUE; + +DEFINE_STACK_OF(CONF_VALUE) +DEFINE_LHASH_OF(CONF_VALUE); + +struct conf_st; +struct conf_method_st; +typedef struct conf_method_st CONF_METHOD; + +struct conf_method_st { + const char *name; + CONF *(*create) (CONF_METHOD *meth); + int (*init) (CONF *conf); + int (*destroy) (CONF *conf); + int (*destroy_data) (CONF *conf); + int (*load_bio) (CONF *conf, BIO *bp, long *eline); + int (*dump) (const CONF *conf, BIO *bp); + int (*is_number) (const CONF *conf, char c); + int (*to_int) (const CONF *conf, char c); + int (*load) (CONF *conf, const char *name, long *eline); +}; + +/* Module definitions */ + +typedef struct conf_imodule_st CONF_IMODULE; +typedef struct conf_module_st CONF_MODULE; + +DEFINE_STACK_OF(CONF_MODULE) +DEFINE_STACK_OF(CONF_IMODULE) + +/* DSO module function typedefs */ +typedef int conf_init_func (CONF_IMODULE *md, const CONF *cnf); +typedef void conf_finish_func (CONF_IMODULE *md); + +# define CONF_MFLAGS_IGNORE_ERRORS 0x1 +# define CONF_MFLAGS_IGNORE_RETURN_CODES 0x2 +# define CONF_MFLAGS_SILENT 0x4 +# define CONF_MFLAGS_NO_DSO 0x8 +# define CONF_MFLAGS_IGNORE_MISSING_FILE 0x10 +# define CONF_MFLAGS_DEFAULT_SECTION 0x20 + +int CONF_set_default_method(CONF_METHOD *meth); +void CONF_set_nconf(CONF *conf, LHASH_OF(CONF_VALUE) *hash); +LHASH_OF(CONF_VALUE) *CONF_load(LHASH_OF(CONF_VALUE) *conf, const char *file, + long *eline); +# ifndef OPENSSL_NO_STDIO +LHASH_OF(CONF_VALUE) *CONF_load_fp(LHASH_OF(CONF_VALUE) *conf, FILE *fp, + long *eline); +# endif +LHASH_OF(CONF_VALUE) *CONF_load_bio(LHASH_OF(CONF_VALUE) *conf, BIO *bp, + long *eline); +STACK_OF(CONF_VALUE) *CONF_get_section(LHASH_OF(CONF_VALUE) *conf, + const char *section); +char *CONF_get_string(LHASH_OF(CONF_VALUE) *conf, const char *group, + const char *name); +long CONF_get_number(LHASH_OF(CONF_VALUE) *conf, const char *group, + const char *name); +void CONF_free(LHASH_OF(CONF_VALUE) *conf); +#ifndef OPENSSL_NO_STDIO +int CONF_dump_fp(LHASH_OF(CONF_VALUE) *conf, FILE *out); +#endif +int CONF_dump_bio(LHASH_OF(CONF_VALUE) *conf, BIO *out); + +DEPRECATEDIN_1_1_0(void OPENSSL_config(const char *config_name)) + +#if OPENSSL_API_COMPAT < 0x10100000L +# define OPENSSL_no_config() \ + OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG, NULL) +#endif + +/* + * New conf code. The semantics are different from the functions above. If + * that wasn't the case, the above functions would have been replaced + */ + +struct conf_st { + CONF_METHOD *meth; + void *meth_data; + LHASH_OF(CONF_VALUE) *data; +}; + +CONF *NCONF_new(CONF_METHOD *meth); +CONF_METHOD *NCONF_default(void); +CONF_METHOD *NCONF_WIN32(void); +void NCONF_free(CONF *conf); +void NCONF_free_data(CONF *conf); + +int NCONF_load(CONF *conf, const char *file, long *eline); +# ifndef OPENSSL_NO_STDIO +int NCONF_load_fp(CONF *conf, FILE *fp, long *eline); +# endif +int NCONF_load_bio(CONF *conf, BIO *bp, long *eline); +STACK_OF(CONF_VALUE) *NCONF_get_section(const CONF *conf, + const char *section); +char *NCONF_get_string(const CONF *conf, const char *group, const char *name); +int NCONF_get_number_e(const CONF *conf, const char *group, const char *name, + long *result); +#ifndef OPENSSL_NO_STDIO +int NCONF_dump_fp(const CONF *conf, FILE *out); +#endif +int NCONF_dump_bio(const CONF *conf, BIO *out); + +#define NCONF_get_number(c,g,n,r) NCONF_get_number_e(c,g,n,r) + +/* Module functions */ + +int CONF_modules_load(const CONF *cnf, const char *appname, + unsigned long flags); +int CONF_modules_load_file(const char *filename, const char *appname, + unsigned long flags); +void CONF_modules_unload(int all); +void CONF_modules_finish(void); +#if OPENSSL_API_COMPAT < 0x10100000L +# define CONF_modules_free() while(0) continue +#endif +int CONF_module_add(const char *name, conf_init_func *ifunc, + conf_finish_func *ffunc); + +const char *CONF_imodule_get_name(const CONF_IMODULE *md); +const char *CONF_imodule_get_value(const CONF_IMODULE *md); +void *CONF_imodule_get_usr_data(const CONF_IMODULE *md); +void CONF_imodule_set_usr_data(CONF_IMODULE *md, void *usr_data); +CONF_MODULE *CONF_imodule_get_module(const CONF_IMODULE *md); +unsigned long CONF_imodule_get_flags(const CONF_IMODULE *md); +void CONF_imodule_set_flags(CONF_IMODULE *md, unsigned long flags); +void *CONF_module_get_usr_data(CONF_MODULE *pmod); +void CONF_module_set_usr_data(CONF_MODULE *pmod, void *usr_data); + +char *CONF_get1_default_config_file(void); + +int CONF_parse_list(const char *list, int sep, int nospc, + int (*list_cb) (const char *elem, int len, void *usr), + void *arg); + +void OPENSSL_load_builtin_modules(void); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/conf_api.h b/thrid-party/openssl/openssl/conf_api.h new file mode 100644 index 0000000000..a0275ad79b --- /dev/null +++ b/thrid-party/openssl/openssl/conf_api.h @@ -0,0 +1,40 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CONF_API_H +# define HEADER_CONF_API_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Up until OpenSSL 0.9.5a, this was new_section */ +CONF_VALUE *_CONF_new_section(CONF *conf, const char *section); +/* Up until OpenSSL 0.9.5a, this was get_section */ +CONF_VALUE *_CONF_get_section(const CONF *conf, const char *section); +/* Up until OpenSSL 0.9.5a, this was CONF_get_section */ +STACK_OF(CONF_VALUE) *_CONF_get_section_values(const CONF *conf, + const char *section); + +int _CONF_add_string(CONF *conf, CONF_VALUE *section, CONF_VALUE *value); +char *_CONF_get_string(const CONF *conf, const char *section, + const char *name); +long _CONF_get_number(const CONF *conf, const char *section, + const char *name); + +int _CONF_new_data(CONF *conf); +void _CONF_free_data(CONF *conf); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/conferr.h b/thrid-party/openssl/openssl/conferr.h new file mode 100644 index 0000000000..32b9229185 --- /dev/null +++ b/thrid-party/openssl/openssl/conferr.h @@ -0,0 +1,76 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CONFERR_H +# define HEADER_CONFERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_CONF_strings(void); + +/* + * CONF function codes. + */ +# define CONF_F_CONF_DUMP_FP 104 +# define CONF_F_CONF_LOAD 100 +# define CONF_F_CONF_LOAD_FP 103 +# define CONF_F_CONF_PARSE_LIST 119 +# define CONF_F_DEF_LOAD 120 +# define CONF_F_DEF_LOAD_BIO 121 +# define CONF_F_GET_NEXT_FILE 107 +# define CONF_F_MODULE_ADD 122 +# define CONF_F_MODULE_INIT 115 +# define CONF_F_MODULE_LOAD_DSO 117 +# define CONF_F_MODULE_RUN 118 +# define CONF_F_NCONF_DUMP_BIO 105 +# define CONF_F_NCONF_DUMP_FP 106 +# define CONF_F_NCONF_GET_NUMBER_E 112 +# define CONF_F_NCONF_GET_SECTION 108 +# define CONF_F_NCONF_GET_STRING 109 +# define CONF_F_NCONF_LOAD 113 +# define CONF_F_NCONF_LOAD_BIO 110 +# define CONF_F_NCONF_LOAD_FP 114 +# define CONF_F_NCONF_NEW 111 +# define CONF_F_PROCESS_INCLUDE 116 +# define CONF_F_SSL_MODULE_INIT 123 +# define CONF_F_STR_COPY 101 + +/* + * CONF reason codes. + */ +# define CONF_R_ERROR_LOADING_DSO 110 +# define CONF_R_LIST_CANNOT_BE_NULL 115 +# define CONF_R_MISSING_CLOSE_SQUARE_BRACKET 100 +# define CONF_R_MISSING_EQUAL_SIGN 101 +# define CONF_R_MISSING_INIT_FUNCTION 112 +# define CONF_R_MODULE_INITIALIZATION_ERROR 109 +# define CONF_R_NO_CLOSE_BRACE 102 +# define CONF_R_NO_CONF 105 +# define CONF_R_NO_CONF_OR_ENVIRONMENT_VARIABLE 106 +# define CONF_R_NO_SECTION 107 +# define CONF_R_NO_SUCH_FILE 114 +# define CONF_R_NO_VALUE 108 +# define CONF_R_NUMBER_TOO_LARGE 121 +# define CONF_R_RECURSIVE_DIRECTORY_INCLUDE 111 +# define CONF_R_SSL_COMMAND_SECTION_EMPTY 117 +# define CONF_R_SSL_COMMAND_SECTION_NOT_FOUND 118 +# define CONF_R_SSL_SECTION_EMPTY 119 +# define CONF_R_SSL_SECTION_NOT_FOUND 120 +# define CONF_R_UNABLE_TO_CREATE_NEW_SECTION 103 +# define CONF_R_UNKNOWN_MODULE_NAME 113 +# define CONF_R_VARIABLE_EXPANSION_TOO_LONG 116 +# define CONF_R_VARIABLE_HAS_NO_VALUE 104 + +#endif diff --git a/thrid-party/openssl/openssl/crypto.h b/thrid-party/openssl/openssl/crypto.h new file mode 100644 index 0000000000..7d0b526236 --- /dev/null +++ b/thrid-party/openssl/openssl/crypto.h @@ -0,0 +1,445 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CRYPTO_H +# define HEADER_CRYPTO_H + +# include +# include + +# include + +# ifndef OPENSSL_NO_STDIO +# include +# endif + +# include +# include +# include +# include +# include + +# ifdef CHARSET_EBCDIC +# include +# endif + +/* + * Resolve problems on some operating systems with symbol names that clash + * one way or another + */ +# include + +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif + +#ifdef __cplusplus +extern "C" { +#endif + +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSLeay OpenSSL_version_num +# define SSLeay_version OpenSSL_version +# define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER +# define SSLEAY_VERSION OPENSSL_VERSION +# define SSLEAY_CFLAGS OPENSSL_CFLAGS +# define SSLEAY_BUILT_ON OPENSSL_BUILT_ON +# define SSLEAY_PLATFORM OPENSSL_PLATFORM +# define SSLEAY_DIR OPENSSL_DIR + +/* + * Old type for allocating dynamic locks. No longer used. Use the new thread + * API instead. + */ +typedef struct { + int dummy; +} CRYPTO_dynlock; + +# endif /* OPENSSL_API_COMPAT */ + +typedef void CRYPTO_RWLOCK; + +CRYPTO_RWLOCK *CRYPTO_THREAD_lock_new(void); +int CRYPTO_THREAD_read_lock(CRYPTO_RWLOCK *lock); +int CRYPTO_THREAD_write_lock(CRYPTO_RWLOCK *lock); +int CRYPTO_THREAD_unlock(CRYPTO_RWLOCK *lock); +void CRYPTO_THREAD_lock_free(CRYPTO_RWLOCK *lock); + +int CRYPTO_atomic_add(int *val, int amount, int *ret, CRYPTO_RWLOCK *lock); + +/* + * The following can be used to detect memory leaks in the library. If + * used, it turns on malloc checking + */ +# define CRYPTO_MEM_CHECK_OFF 0x0 /* Control only */ +# define CRYPTO_MEM_CHECK_ON 0x1 /* Control and mode bit */ +# define CRYPTO_MEM_CHECK_ENABLE 0x2 /* Control and mode bit */ +# define CRYPTO_MEM_CHECK_DISABLE 0x3 /* Control only */ + +struct crypto_ex_data_st { + STACK_OF(void) *sk; +}; +DEFINE_STACK_OF(void) + +/* + * Per class, we have a STACK of function pointers. + */ +# define CRYPTO_EX_INDEX_SSL 0 +# define CRYPTO_EX_INDEX_SSL_CTX 1 +# define CRYPTO_EX_INDEX_SSL_SESSION 2 +# define CRYPTO_EX_INDEX_X509 3 +# define CRYPTO_EX_INDEX_X509_STORE 4 +# define CRYPTO_EX_INDEX_X509_STORE_CTX 5 +# define CRYPTO_EX_INDEX_DH 6 +# define CRYPTO_EX_INDEX_DSA 7 +# define CRYPTO_EX_INDEX_EC_KEY 8 +# define CRYPTO_EX_INDEX_RSA 9 +# define CRYPTO_EX_INDEX_ENGINE 10 +# define CRYPTO_EX_INDEX_UI 11 +# define CRYPTO_EX_INDEX_BIO 12 +# define CRYPTO_EX_INDEX_APP 13 +# define CRYPTO_EX_INDEX_UI_METHOD 14 +# define CRYPTO_EX_INDEX_DRBG 15 +# define CRYPTO_EX_INDEX__COUNT 16 + +/* No longer needed, so this is a no-op */ +#define OPENSSL_malloc_init() while(0) continue + +int CRYPTO_mem_ctrl(int mode); + +# define OPENSSL_malloc(num) \ + CRYPTO_malloc(num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_zalloc(num) \ + CRYPTO_zalloc(num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_realloc(addr, num) \ + CRYPTO_realloc(addr, num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_clear_realloc(addr, old_num, num) \ + CRYPTO_clear_realloc(addr, old_num, num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_clear_free(addr, num) \ + CRYPTO_clear_free(addr, num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_free(addr) \ + CRYPTO_free(addr, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_memdup(str, s) \ + CRYPTO_memdup((str), s, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_strdup(str) \ + CRYPTO_strdup(str, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_strndup(str, n) \ + CRYPTO_strndup(str, n, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_secure_malloc(num) \ + CRYPTO_secure_malloc(num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_secure_zalloc(num) \ + CRYPTO_secure_zalloc(num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_secure_free(addr) \ + CRYPTO_secure_free(addr, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_secure_clear_free(addr, num) \ + CRYPTO_secure_clear_free(addr, num, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_secure_actual_size(ptr) \ + CRYPTO_secure_actual_size(ptr) + +size_t OPENSSL_strlcpy(char *dst, const char *src, size_t siz); +size_t OPENSSL_strlcat(char *dst, const char *src, size_t siz); +size_t OPENSSL_strnlen(const char *str, size_t maxlen); +char *OPENSSL_buf2hexstr(const unsigned char *buffer, long len); +unsigned char *OPENSSL_hexstr2buf(const char *str, long *len); +int OPENSSL_hexchar2int(unsigned char c); + +# define OPENSSL_MALLOC_MAX_NELEMS(type) (((1U<<(sizeof(int)*8-1))-1)/sizeof(type)) + +unsigned long OpenSSL_version_num(void); +const char *OpenSSL_version(int type); +# define OPENSSL_VERSION 0 +# define OPENSSL_CFLAGS 1 +# define OPENSSL_BUILT_ON 2 +# define OPENSSL_PLATFORM 3 +# define OPENSSL_DIR 4 +# define OPENSSL_ENGINES_DIR 5 + +int OPENSSL_issetugid(void); + +typedef void CRYPTO_EX_new (void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int idx, long argl, void *argp); +typedef void CRYPTO_EX_free (void *parent, void *ptr, CRYPTO_EX_DATA *ad, + int idx, long argl, void *argp); +typedef int CRYPTO_EX_dup (CRYPTO_EX_DATA *to, const CRYPTO_EX_DATA *from, + void *from_d, int idx, long argl, void *argp); +__owur int CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, + CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, + CRYPTO_EX_free *free_func); +/* No longer use an index. */ +int CRYPTO_free_ex_index(int class_index, int idx); + +/* + * Initialise/duplicate/free CRYPTO_EX_DATA variables corresponding to a + * given class (invokes whatever per-class callbacks are applicable) + */ +int CRYPTO_new_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad); +int CRYPTO_dup_ex_data(int class_index, CRYPTO_EX_DATA *to, + const CRYPTO_EX_DATA *from); + +void CRYPTO_free_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad); + +/* + * Get/set data in a CRYPTO_EX_DATA variable corresponding to a particular + * index (relative to the class type involved) + */ +int CRYPTO_set_ex_data(CRYPTO_EX_DATA *ad, int idx, void *val); +void *CRYPTO_get_ex_data(const CRYPTO_EX_DATA *ad, int idx); + +# if OPENSSL_API_COMPAT < 0x10100000L +/* + * This function cleans up all "ex_data" state. It mustn't be called under + * potential race-conditions. + */ +# define CRYPTO_cleanup_all_ex_data() while(0) continue + +/* + * The old locking functions have been removed completely without compatibility + * macros. This is because the old functions either could not properly report + * errors, or the returned error values were not clearly documented. + * Replacing the locking functions with no-ops would cause race condition + * issues in the affected applications. It is far better for them to fail at + * compile time. + * On the other hand, the locking callbacks are no longer used. Consequently, + * the callback management functions can be safely replaced with no-op macros. + */ +# define CRYPTO_num_locks() (1) +# define CRYPTO_set_locking_callback(func) +# define CRYPTO_get_locking_callback() (NULL) +# define CRYPTO_set_add_lock_callback(func) +# define CRYPTO_get_add_lock_callback() (NULL) + +/* + * These defines where used in combination with the old locking callbacks, + * they are not called anymore, but old code that's not called might still + * use them. + */ +# define CRYPTO_LOCK 1 +# define CRYPTO_UNLOCK 2 +# define CRYPTO_READ 4 +# define CRYPTO_WRITE 8 + +/* This structure is no longer used */ +typedef struct crypto_threadid_st { + int dummy; +} CRYPTO_THREADID; +/* Only use CRYPTO_THREADID_set_[numeric|pointer]() within callbacks */ +# define CRYPTO_THREADID_set_numeric(id, val) +# define CRYPTO_THREADID_set_pointer(id, ptr) +# define CRYPTO_THREADID_set_callback(threadid_func) (0) +# define CRYPTO_THREADID_get_callback() (NULL) +# define CRYPTO_THREADID_current(id) +# define CRYPTO_THREADID_cmp(a, b) (-1) +# define CRYPTO_THREADID_cpy(dest, src) +# define CRYPTO_THREADID_hash(id) (0UL) + +# if OPENSSL_API_COMPAT < 0x10000000L +# define CRYPTO_set_id_callback(func) +# define CRYPTO_get_id_callback() (NULL) +# define CRYPTO_thread_id() (0UL) +# endif /* OPENSSL_API_COMPAT < 0x10000000L */ + +# define CRYPTO_set_dynlock_create_callback(dyn_create_function) +# define CRYPTO_set_dynlock_lock_callback(dyn_lock_function) +# define CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function) +# define CRYPTO_get_dynlock_create_callback() (NULL) +# define CRYPTO_get_dynlock_lock_callback() (NULL) +# define CRYPTO_get_dynlock_destroy_callback() (NULL) +# endif /* OPENSSL_API_COMPAT < 0x10100000L */ + +int CRYPTO_set_mem_functions( + void *(*m) (size_t, const char *, int), + void *(*r) (void *, size_t, const char *, int), + void (*f) (void *, const char *, int)); +int CRYPTO_set_mem_debug(int flag); +void CRYPTO_get_mem_functions( + void *(**m) (size_t, const char *, int), + void *(**r) (void *, size_t, const char *, int), + void (**f) (void *, const char *, int)); + +void *CRYPTO_malloc(size_t num, const char *file, int line); +void *CRYPTO_zalloc(size_t num, const char *file, int line); +void *CRYPTO_memdup(const void *str, size_t siz, const char *file, int line); +char *CRYPTO_strdup(const char *str, const char *file, int line); +char *CRYPTO_strndup(const char *str, size_t s, const char *file, int line); +void CRYPTO_free(void *ptr, const char *file, int line); +void CRYPTO_clear_free(void *ptr, size_t num, const char *file, int line); +void *CRYPTO_realloc(void *addr, size_t num, const char *file, int line); +void *CRYPTO_clear_realloc(void *addr, size_t old_num, size_t num, + const char *file, int line); + +int CRYPTO_secure_malloc_init(size_t sz, int minsize); +int CRYPTO_secure_malloc_done(void); +void *CRYPTO_secure_malloc(size_t num, const char *file, int line); +void *CRYPTO_secure_zalloc(size_t num, const char *file, int line); +void CRYPTO_secure_free(void *ptr, const char *file, int line); +void CRYPTO_secure_clear_free(void *ptr, size_t num, + const char *file, int line); +int CRYPTO_secure_allocated(const void *ptr); +int CRYPTO_secure_malloc_initialized(void); +size_t CRYPTO_secure_actual_size(void *ptr); +size_t CRYPTO_secure_used(void); + +void OPENSSL_cleanse(void *ptr, size_t len); + +# ifndef OPENSSL_NO_CRYPTO_MDEBUG +# define OPENSSL_mem_debug_push(info) \ + CRYPTO_mem_debug_push(info, OPENSSL_FILE, OPENSSL_LINE) +# define OPENSSL_mem_debug_pop() \ + CRYPTO_mem_debug_pop() +int CRYPTO_mem_debug_push(const char *info, const char *file, int line); +int CRYPTO_mem_debug_pop(void); +void CRYPTO_get_alloc_counts(int *mcount, int *rcount, int *fcount); + +/*- + * Debugging functions (enabled by CRYPTO_set_mem_debug(1)) + * The flag argument has the following significance: + * 0: called before the actual memory allocation has taken place + * 1: called after the actual memory allocation has taken place + */ +void CRYPTO_mem_debug_malloc(void *addr, size_t num, int flag, + const char *file, int line); +void CRYPTO_mem_debug_realloc(void *addr1, void *addr2, size_t num, int flag, + const char *file, int line); +void CRYPTO_mem_debug_free(void *addr, int flag, + const char *file, int line); + +int CRYPTO_mem_leaks_cb(int (*cb) (const char *str, size_t len, void *u), + void *u); +# ifndef OPENSSL_NO_STDIO +int CRYPTO_mem_leaks_fp(FILE *); +# endif +int CRYPTO_mem_leaks(BIO *bio); +# endif + +/* die if we have to */ +ossl_noreturn void OPENSSL_die(const char *assertion, const char *file, int line); +# if OPENSSL_API_COMPAT < 0x10100000L +# define OpenSSLDie(f,l,a) OPENSSL_die((a),(f),(l)) +# endif +# define OPENSSL_assert(e) \ + (void)((e) ? 0 : (OPENSSL_die("assertion failed: " #e, OPENSSL_FILE, OPENSSL_LINE), 1)) + +int OPENSSL_isservice(void); + +int FIPS_mode(void); +int FIPS_mode_set(int r); + +void OPENSSL_init(void); +# ifdef OPENSSL_SYS_UNIX +void OPENSSL_fork_prepare(void); +void OPENSSL_fork_parent(void); +void OPENSSL_fork_child(void); +# endif + +struct tm *OPENSSL_gmtime(const time_t *timer, struct tm *result); +int OPENSSL_gmtime_adj(struct tm *tm, int offset_day, long offset_sec); +int OPENSSL_gmtime_diff(int *pday, int *psec, + const struct tm *from, const struct tm *to); + +/* + * CRYPTO_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. + * It takes an amount of time dependent on |len|, but independent of the + * contents of |a| and |b|. Unlike memcmp, it cannot be used to put elements + * into a defined order as the return value when a != b is undefined, other + * than to be non-zero. + */ +int CRYPTO_memcmp(const void * in_a, const void * in_b, size_t len); + +/* Standard initialisation options */ +# define OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS 0x00000001L +# define OPENSSL_INIT_LOAD_CRYPTO_STRINGS 0x00000002L +# define OPENSSL_INIT_ADD_ALL_CIPHERS 0x00000004L +# define OPENSSL_INIT_ADD_ALL_DIGESTS 0x00000008L +# define OPENSSL_INIT_NO_ADD_ALL_CIPHERS 0x00000010L +# define OPENSSL_INIT_NO_ADD_ALL_DIGESTS 0x00000020L +# define OPENSSL_INIT_LOAD_CONFIG 0x00000040L +# define OPENSSL_INIT_NO_LOAD_CONFIG 0x00000080L +# define OPENSSL_INIT_ASYNC 0x00000100L +# define OPENSSL_INIT_ENGINE_RDRAND 0x00000200L +# define OPENSSL_INIT_ENGINE_DYNAMIC 0x00000400L +# define OPENSSL_INIT_ENGINE_OPENSSL 0x00000800L +# define OPENSSL_INIT_ENGINE_CRYPTODEV 0x00001000L +# define OPENSSL_INIT_ENGINE_CAPI 0x00002000L +# define OPENSSL_INIT_ENGINE_PADLOCK 0x00004000L +# define OPENSSL_INIT_ENGINE_AFALG 0x00008000L +/* OPENSSL_INIT_ZLIB 0x00010000L */ +# define OPENSSL_INIT_ATFORK 0x00020000L +/* OPENSSL_INIT_BASE_ONLY 0x00040000L */ +# define OPENSSL_INIT_NO_ATEXIT 0x00080000L +/* OPENSSL_INIT flag range 0xfff00000 reserved for OPENSSL_init_ssl() */ +/* Max OPENSSL_INIT flag value is 0x80000000 */ + +/* openssl and dasync not counted as builtin */ +# define OPENSSL_INIT_ENGINE_ALL_BUILTIN \ + (OPENSSL_INIT_ENGINE_RDRAND | OPENSSL_INIT_ENGINE_DYNAMIC \ + | OPENSSL_INIT_ENGINE_CRYPTODEV | OPENSSL_INIT_ENGINE_CAPI | \ + OPENSSL_INIT_ENGINE_PADLOCK) + + +/* Library initialisation functions */ +void OPENSSL_cleanup(void); +int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +int OPENSSL_atexit(void (*handler)(void)); +void OPENSSL_thread_stop(void); + +/* Low-level control of initialization */ +OPENSSL_INIT_SETTINGS *OPENSSL_INIT_new(void); +# ifndef OPENSSL_NO_STDIO +int OPENSSL_INIT_set_config_filename(OPENSSL_INIT_SETTINGS *settings, + const char *config_filename); +void OPENSSL_INIT_set_config_file_flags(OPENSSL_INIT_SETTINGS *settings, + unsigned long flags); +int OPENSSL_INIT_set_config_appname(OPENSSL_INIT_SETTINGS *settings, + const char *config_appname); +# endif +void OPENSSL_INIT_free(OPENSSL_INIT_SETTINGS *settings); + +# if defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG) +# if defined(_WIN32) +# if defined(BASETYPES) || defined(_WINDEF_H) +/* application has to include in order to use this */ +typedef DWORD CRYPTO_THREAD_LOCAL; +typedef DWORD CRYPTO_THREAD_ID; + +typedef LONG CRYPTO_ONCE; +# define CRYPTO_ONCE_STATIC_INIT 0 +# endif +# else +# include +typedef pthread_once_t CRYPTO_ONCE; +typedef pthread_key_t CRYPTO_THREAD_LOCAL; +typedef pthread_t CRYPTO_THREAD_ID; + +# define CRYPTO_ONCE_STATIC_INIT PTHREAD_ONCE_INIT +# endif +# endif + +# if !defined(CRYPTO_ONCE_STATIC_INIT) +typedef unsigned int CRYPTO_ONCE; +typedef unsigned int CRYPTO_THREAD_LOCAL; +typedef unsigned int CRYPTO_THREAD_ID; +# define CRYPTO_ONCE_STATIC_INIT 0 +# endif + +int CRYPTO_THREAD_run_once(CRYPTO_ONCE *once, void (*init)(void)); + +int CRYPTO_THREAD_init_local(CRYPTO_THREAD_LOCAL *key, void (*cleanup)(void *)); +void *CRYPTO_THREAD_get_local(CRYPTO_THREAD_LOCAL *key); +int CRYPTO_THREAD_set_local(CRYPTO_THREAD_LOCAL *key, void *val); +int CRYPTO_THREAD_cleanup_local(CRYPTO_THREAD_LOCAL *key); + +CRYPTO_THREAD_ID CRYPTO_THREAD_get_current_id(void); +int CRYPTO_THREAD_compare_id(CRYPTO_THREAD_ID a, CRYPTO_THREAD_ID b); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/cryptoerr.h b/thrid-party/openssl/openssl/cryptoerr.h new file mode 100644 index 0000000000..3db5a4ee99 --- /dev/null +++ b/thrid-party/openssl/openssl/cryptoerr.h @@ -0,0 +1,57 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CRYPTOERR_H +# define HEADER_CRYPTOERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_CRYPTO_strings(void); + +/* + * CRYPTO function codes. + */ +# define CRYPTO_F_CMAC_CTX_NEW 120 +# define CRYPTO_F_CRYPTO_DUP_EX_DATA 110 +# define CRYPTO_F_CRYPTO_FREE_EX_DATA 111 +# define CRYPTO_F_CRYPTO_GET_EX_NEW_INDEX 100 +# define CRYPTO_F_CRYPTO_MEMDUP 115 +# define CRYPTO_F_CRYPTO_NEW_EX_DATA 112 +# define CRYPTO_F_CRYPTO_OCB128_COPY_CTX 121 +# define CRYPTO_F_CRYPTO_OCB128_INIT 122 +# define CRYPTO_F_CRYPTO_SET_EX_DATA 102 +# define CRYPTO_F_FIPS_MODE_SET 109 +# define CRYPTO_F_GET_AND_LOCK 113 +# define CRYPTO_F_OPENSSL_ATEXIT 114 +# define CRYPTO_F_OPENSSL_BUF2HEXSTR 117 +# define CRYPTO_F_OPENSSL_FOPEN 119 +# define CRYPTO_F_OPENSSL_HEXSTR2BUF 118 +# define CRYPTO_F_OPENSSL_INIT_CRYPTO 116 +# define CRYPTO_F_OPENSSL_LH_NEW 126 +# define CRYPTO_F_OPENSSL_SK_DEEP_COPY 127 +# define CRYPTO_F_OPENSSL_SK_DUP 128 +# define CRYPTO_F_PKEY_HMAC_INIT 123 +# define CRYPTO_F_PKEY_POLY1305_INIT 124 +# define CRYPTO_F_PKEY_SIPHASH_INIT 125 +# define CRYPTO_F_SK_RESERVE 129 + +/* + * CRYPTO reason codes. + */ +# define CRYPTO_R_FIPS_MODE_NOT_SUPPORTED 101 +# define CRYPTO_R_ILLEGAL_HEX_DIGIT 102 +# define CRYPTO_R_ODD_NUMBER_OF_DIGITS 103 + +#endif diff --git a/thrid-party/openssl/openssl/ct.h b/thrid-party/openssl/openssl/ct.h new file mode 100644 index 0000000000..d4262fa048 --- /dev/null +++ b/thrid-party/openssl/openssl/ct.h @@ -0,0 +1,476 @@ +/* + * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CT_H +# define HEADER_CT_H + +# include + +# ifndef OPENSSL_NO_CT +# include +# include +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + + +/* Minimum RSA key size, from RFC6962 */ +# define SCT_MIN_RSA_BITS 2048 + +/* All hashes are SHA256 in v1 of Certificate Transparency */ +# define CT_V1_HASHLEN SHA256_DIGEST_LENGTH + +typedef enum { + CT_LOG_ENTRY_TYPE_NOT_SET = -1, + CT_LOG_ENTRY_TYPE_X509 = 0, + CT_LOG_ENTRY_TYPE_PRECERT = 1 +} ct_log_entry_type_t; + +typedef enum { + SCT_VERSION_NOT_SET = -1, + SCT_VERSION_V1 = 0 +} sct_version_t; + +typedef enum { + SCT_SOURCE_UNKNOWN, + SCT_SOURCE_TLS_EXTENSION, + SCT_SOURCE_X509V3_EXTENSION, + SCT_SOURCE_OCSP_STAPLED_RESPONSE +} sct_source_t; + +typedef enum { + SCT_VALIDATION_STATUS_NOT_SET, + SCT_VALIDATION_STATUS_UNKNOWN_LOG, + SCT_VALIDATION_STATUS_VALID, + SCT_VALIDATION_STATUS_INVALID, + SCT_VALIDATION_STATUS_UNVERIFIED, + SCT_VALIDATION_STATUS_UNKNOWN_VERSION +} sct_validation_status_t; + +DEFINE_STACK_OF(SCT) +DEFINE_STACK_OF(CTLOG) + +/****************************************** + * CT policy evaluation context functions * + ******************************************/ + +/* + * Creates a new, empty policy evaluation context. + * The caller is responsible for calling CT_POLICY_EVAL_CTX_free when finished + * with the CT_POLICY_EVAL_CTX. + */ +CT_POLICY_EVAL_CTX *CT_POLICY_EVAL_CTX_new(void); + +/* Deletes a policy evaluation context and anything it owns. */ +void CT_POLICY_EVAL_CTX_free(CT_POLICY_EVAL_CTX *ctx); + +/* Gets the peer certificate that the SCTs are for */ +X509* CT_POLICY_EVAL_CTX_get0_cert(const CT_POLICY_EVAL_CTX *ctx); + +/* + * Sets the certificate associated with the received SCTs. + * Increments the reference count of cert. + * Returns 1 on success, 0 otherwise. + */ +int CT_POLICY_EVAL_CTX_set1_cert(CT_POLICY_EVAL_CTX *ctx, X509 *cert); + +/* Gets the issuer of the aforementioned certificate */ +X509* CT_POLICY_EVAL_CTX_get0_issuer(const CT_POLICY_EVAL_CTX *ctx); + +/* + * Sets the issuer of the certificate associated with the received SCTs. + * Increments the reference count of issuer. + * Returns 1 on success, 0 otherwise. + */ +int CT_POLICY_EVAL_CTX_set1_issuer(CT_POLICY_EVAL_CTX *ctx, X509 *issuer); + +/* Gets the CT logs that are trusted sources of SCTs */ +const CTLOG_STORE *CT_POLICY_EVAL_CTX_get0_log_store(const CT_POLICY_EVAL_CTX *ctx); + +/* Sets the log store that is in use. It must outlive the CT_POLICY_EVAL_CTX. */ +void CT_POLICY_EVAL_CTX_set_shared_CTLOG_STORE(CT_POLICY_EVAL_CTX *ctx, + CTLOG_STORE *log_store); + +/* + * Gets the time, in milliseconds since the Unix epoch, that will be used as the + * current time when checking whether an SCT was issued in the future. + * Such SCTs will fail validation, as required by RFC6962. + */ +uint64_t CT_POLICY_EVAL_CTX_get_time(const CT_POLICY_EVAL_CTX *ctx); + +/* + * Sets the time to evaluate SCTs against, in milliseconds since the Unix epoch. + * If an SCT's timestamp is after this time, it will be interpreted as having + * been issued in the future. RFC6962 states that "TLS clients MUST reject SCTs + * whose timestamp is in the future", so an SCT will not validate in this case. + */ +void CT_POLICY_EVAL_CTX_set_time(CT_POLICY_EVAL_CTX *ctx, uint64_t time_in_ms); + +/***************** + * SCT functions * + *****************/ + +/* + * Creates a new, blank SCT. + * The caller is responsible for calling SCT_free when finished with the SCT. + */ +SCT *SCT_new(void); + +/* + * Creates a new SCT from some base64-encoded strings. + * The caller is responsible for calling SCT_free when finished with the SCT. + */ +SCT *SCT_new_from_base64(unsigned char version, + const char *logid_base64, + ct_log_entry_type_t entry_type, + uint64_t timestamp, + const char *extensions_base64, + const char *signature_base64); + +/* + * Frees the SCT and the underlying data structures. + */ +void SCT_free(SCT *sct); + +/* + * Free a stack of SCTs, and the underlying SCTs themselves. + * Intended to be compatible with X509V3_EXT_FREE. + */ +void SCT_LIST_free(STACK_OF(SCT) *a); + +/* + * Returns the version of the SCT. + */ +sct_version_t SCT_get_version(const SCT *sct); + +/* + * Set the version of an SCT. + * Returns 1 on success, 0 if the version is unrecognized. + */ +__owur int SCT_set_version(SCT *sct, sct_version_t version); + +/* + * Returns the log entry type of the SCT. + */ +ct_log_entry_type_t SCT_get_log_entry_type(const SCT *sct); + +/* + * Set the log entry type of an SCT. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set_log_entry_type(SCT *sct, ct_log_entry_type_t entry_type); + +/* + * Gets the ID of the log that an SCT came from. + * Ownership of the log ID remains with the SCT. + * Returns the length of the log ID. + */ +size_t SCT_get0_log_id(const SCT *sct, unsigned char **log_id); + +/* + * Set the log ID of an SCT to point directly to the *log_id specified. + * The SCT takes ownership of the specified pointer. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set0_log_id(SCT *sct, unsigned char *log_id, size_t log_id_len); + +/* + * Set the log ID of an SCT. + * This makes a copy of the log_id. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set1_log_id(SCT *sct, const unsigned char *log_id, + size_t log_id_len); + +/* + * Returns the timestamp for the SCT (epoch time in milliseconds). + */ +uint64_t SCT_get_timestamp(const SCT *sct); + +/* + * Set the timestamp of an SCT (epoch time in milliseconds). + */ +void SCT_set_timestamp(SCT *sct, uint64_t timestamp); + +/* + * Return the NID for the signature used by the SCT. + * For CT v1, this will be either NID_sha256WithRSAEncryption or + * NID_ecdsa_with_SHA256 (or NID_undef if incorrect/unset). + */ +int SCT_get_signature_nid(const SCT *sct); + +/* + * Set the signature type of an SCT + * For CT v1, this should be either NID_sha256WithRSAEncryption or + * NID_ecdsa_with_SHA256. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set_signature_nid(SCT *sct, int nid); + +/* + * Set *ext to point to the extension data for the SCT. ext must not be NULL. + * The SCT retains ownership of this pointer. + * Returns length of the data pointed to. + */ +size_t SCT_get0_extensions(const SCT *sct, unsigned char **ext); + +/* + * Set the extensions of an SCT to point directly to the *ext specified. + * The SCT takes ownership of the specified pointer. + */ +void SCT_set0_extensions(SCT *sct, unsigned char *ext, size_t ext_len); + +/* + * Set the extensions of an SCT. + * This takes a copy of the ext. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set1_extensions(SCT *sct, const unsigned char *ext, + size_t ext_len); + +/* + * Set *sig to point to the signature for the SCT. sig must not be NULL. + * The SCT retains ownership of this pointer. + * Returns length of the data pointed to. + */ +size_t SCT_get0_signature(const SCT *sct, unsigned char **sig); + +/* + * Set the signature of an SCT to point directly to the *sig specified. + * The SCT takes ownership of the specified pointer. + */ +void SCT_set0_signature(SCT *sct, unsigned char *sig, size_t sig_len); + +/* + * Set the signature of an SCT to be a copy of the *sig specified. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set1_signature(SCT *sct, const unsigned char *sig, + size_t sig_len); + +/* + * The origin of this SCT, e.g. TLS extension, OCSP response, etc. + */ +sct_source_t SCT_get_source(const SCT *sct); + +/* + * Set the origin of this SCT, e.g. TLS extension, OCSP response, etc. + * Returns 1 on success, 0 otherwise. + */ +__owur int SCT_set_source(SCT *sct, sct_source_t source); + +/* + * Returns a text string describing the validation status of |sct|. + */ +const char *SCT_validation_status_string(const SCT *sct); + +/* + * Pretty-prints an |sct| to |out|. + * It will be indented by the number of spaces specified by |indent|. + * If |logs| is not NULL, it will be used to lookup the CT log that the SCT came + * from, so that the log name can be printed. + */ +void SCT_print(const SCT *sct, BIO *out, int indent, const CTLOG_STORE *logs); + +/* + * Pretty-prints an |sct_list| to |out|. + * It will be indented by the number of spaces specified by |indent|. + * SCTs will be delimited by |separator|. + * If |logs| is not NULL, it will be used to lookup the CT log that each SCT + * came from, so that the log names can be printed. + */ +void SCT_LIST_print(const STACK_OF(SCT) *sct_list, BIO *out, int indent, + const char *separator, const CTLOG_STORE *logs); + +/* + * Gets the last result of validating this SCT. + * If it has not been validated yet, returns SCT_VALIDATION_STATUS_NOT_SET. + */ +sct_validation_status_t SCT_get_validation_status(const SCT *sct); + +/* + * Validates the given SCT with the provided context. + * Sets the "validation_status" field of the SCT. + * Returns 1 if the SCT is valid and the signature verifies. + * Returns 0 if the SCT is invalid or could not be verified. + * Returns -1 if an error occurs. + */ +__owur int SCT_validate(SCT *sct, const CT_POLICY_EVAL_CTX *ctx); + +/* + * Validates the given list of SCTs with the provided context. + * Sets the "validation_status" field of each SCT. + * Returns 1 if there are no invalid SCTs and all signatures verify. + * Returns 0 if at least one SCT is invalid or could not be verified. + * Returns a negative integer if an error occurs. + */ +__owur int SCT_LIST_validate(const STACK_OF(SCT) *scts, + CT_POLICY_EVAL_CTX *ctx); + + +/********************************* + * SCT parsing and serialisation * + *********************************/ + +/* + * Serialize (to TLS format) a stack of SCTs and return the length. + * "a" must not be NULL. + * If "pp" is NULL, just return the length of what would have been serialized. + * If "pp" is not NULL and "*pp" is null, function will allocate a new pointer + * for data that caller is responsible for freeing (only if function returns + * successfully). + * If "pp" is NULL and "*pp" is not NULL, caller is responsible for ensuring + * that "*pp" is large enough to accept all of the serialized data. + * Returns < 0 on error, >= 0 indicating bytes written (or would have been) + * on success. + */ +__owur int i2o_SCT_LIST(const STACK_OF(SCT) *a, unsigned char **pp); + +/* + * Convert TLS format SCT list to a stack of SCTs. + * If "a" or "*a" is NULL, a new stack will be created that the caller is + * responsible for freeing (by calling SCT_LIST_free). + * "**pp" and "*pp" must not be NULL. + * Upon success, "*pp" will point to after the last bytes read, and a stack + * will be returned. + * Upon failure, a NULL pointer will be returned, and the position of "*pp" is + * not defined. + */ +STACK_OF(SCT) *o2i_SCT_LIST(STACK_OF(SCT) **a, const unsigned char **pp, + size_t len); + +/* + * Serialize (to DER format) a stack of SCTs and return the length. + * "a" must not be NULL. + * If "pp" is NULL, just returns the length of what would have been serialized. + * If "pp" is not NULL and "*pp" is null, function will allocate a new pointer + * for data that caller is responsible for freeing (only if function returns + * successfully). + * If "pp" is NULL and "*pp" is not NULL, caller is responsible for ensuring + * that "*pp" is large enough to accept all of the serialized data. + * Returns < 0 on error, >= 0 indicating bytes written (or would have been) + * on success. + */ +__owur int i2d_SCT_LIST(const STACK_OF(SCT) *a, unsigned char **pp); + +/* + * Parses an SCT list in DER format and returns it. + * If "a" or "*a" is NULL, a new stack will be created that the caller is + * responsible for freeing (by calling SCT_LIST_free). + * "**pp" and "*pp" must not be NULL. + * Upon success, "*pp" will point to after the last bytes read, and a stack + * will be returned. + * Upon failure, a NULL pointer will be returned, and the position of "*pp" is + * not defined. + */ +STACK_OF(SCT) *d2i_SCT_LIST(STACK_OF(SCT) **a, const unsigned char **pp, + long len); + +/* + * Serialize (to TLS format) an |sct| and write it to |out|. + * If |out| is null, no SCT will be output but the length will still be returned. + * If |out| points to a null pointer, a string will be allocated to hold the + * TLS-format SCT. It is the responsibility of the caller to free it. + * If |out| points to an allocated string, the TLS-format SCT will be written + * to it. + * The length of the SCT in TLS format will be returned. + */ +__owur int i2o_SCT(const SCT *sct, unsigned char **out); + +/* + * Parses an SCT in TLS format and returns it. + * If |psct| is not null, it will end up pointing to the parsed SCT. If it + * already points to a non-null pointer, the pointer will be free'd. + * |in| should be a pointer to a string containing the TLS-format SCT. + * |in| will be advanced to the end of the SCT if parsing succeeds. + * |len| should be the length of the SCT in |in|. + * Returns NULL if an error occurs. + * If the SCT is an unsupported version, only the SCT's 'sct' and 'sct_len' + * fields will be populated (with |in| and |len| respectively). + */ +SCT *o2i_SCT(SCT **psct, const unsigned char **in, size_t len); + +/******************** + * CT log functions * + ********************/ + +/* + * Creates a new CT log instance with the given |public_key| and |name|. + * Takes ownership of |public_key| but copies |name|. + * Returns NULL if malloc fails or if |public_key| cannot be converted to DER. + * Should be deleted by the caller using CTLOG_free when no longer needed. + */ +CTLOG *CTLOG_new(EVP_PKEY *public_key, const char *name); + +/* + * Creates a new CTLOG instance with the base64-encoded SubjectPublicKeyInfo DER + * in |pkey_base64|. The |name| is a string to help users identify this log. + * Returns 1 on success, 0 on failure. + * Should be deleted by the caller using CTLOG_free when no longer needed. + */ +int CTLOG_new_from_base64(CTLOG ** ct_log, + const char *pkey_base64, const char *name); + +/* + * Deletes a CT log instance and its fields. + */ +void CTLOG_free(CTLOG *log); + +/* Gets the name of the CT log */ +const char *CTLOG_get0_name(const CTLOG *log); +/* Gets the ID of the CT log */ +void CTLOG_get0_log_id(const CTLOG *log, const uint8_t **log_id, + size_t *log_id_len); +/* Gets the public key of the CT log */ +EVP_PKEY *CTLOG_get0_public_key(const CTLOG *log); + +/************************** + * CT log store functions * + **************************/ + +/* + * Creates a new CT log store. + * Should be deleted by the caller using CTLOG_STORE_free when no longer needed. + */ +CTLOG_STORE *CTLOG_STORE_new(void); + +/* + * Deletes a CT log store and all of the CT log instances held within. + */ +void CTLOG_STORE_free(CTLOG_STORE *store); + +/* + * Finds a CT log in the store based on its log ID. + * Returns the CT log, or NULL if no match is found. + */ +const CTLOG *CTLOG_STORE_get0_log_by_id(const CTLOG_STORE *store, + const uint8_t *log_id, + size_t log_id_len); + +/* + * Loads a CT log list into a |store| from a |file|. + * Returns 1 if loading is successful, or 0 otherwise. + */ +__owur int CTLOG_STORE_load_file(CTLOG_STORE *store, const char *file); + +/* + * Loads the default CT log list into a |store|. + * See internal/cryptlib.h for the environment variable and file path that are + * consulted to find the default file. + * Returns 1 if loading is successful, or 0 otherwise. + */ +__owur int CTLOG_STORE_load_default_file(CTLOG_STORE *store); + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/cterr.h b/thrid-party/openssl/openssl/cterr.h new file mode 100644 index 0000000000..feb7bc5663 --- /dev/null +++ b/thrid-party/openssl/openssl/cterr.h @@ -0,0 +1,80 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_CTERR_H +# define HEADER_CTERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_CT + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_CT_strings(void); + +/* + * CT function codes. + */ +# define CT_F_CTLOG_NEW 117 +# define CT_F_CTLOG_NEW_FROM_BASE64 118 +# define CT_F_CTLOG_NEW_FROM_CONF 119 +# define CT_F_CTLOG_STORE_LOAD_CTX_NEW 122 +# define CT_F_CTLOG_STORE_LOAD_FILE 123 +# define CT_F_CTLOG_STORE_LOAD_LOG 130 +# define CT_F_CTLOG_STORE_NEW 131 +# define CT_F_CT_BASE64_DECODE 124 +# define CT_F_CT_POLICY_EVAL_CTX_NEW 133 +# define CT_F_CT_V1_LOG_ID_FROM_PKEY 125 +# define CT_F_I2O_SCT 107 +# define CT_F_I2O_SCT_LIST 108 +# define CT_F_I2O_SCT_SIGNATURE 109 +# define CT_F_O2I_SCT 110 +# define CT_F_O2I_SCT_LIST 111 +# define CT_F_O2I_SCT_SIGNATURE 112 +# define CT_F_SCT_CTX_NEW 126 +# define CT_F_SCT_CTX_VERIFY 128 +# define CT_F_SCT_NEW 100 +# define CT_F_SCT_NEW_FROM_BASE64 127 +# define CT_F_SCT_SET0_LOG_ID 101 +# define CT_F_SCT_SET1_EXTENSIONS 114 +# define CT_F_SCT_SET1_LOG_ID 115 +# define CT_F_SCT_SET1_SIGNATURE 116 +# define CT_F_SCT_SET_LOG_ENTRY_TYPE 102 +# define CT_F_SCT_SET_SIGNATURE_NID 103 +# define CT_F_SCT_SET_VERSION 104 + +/* + * CT reason codes. + */ +# define CT_R_BASE64_DECODE_ERROR 108 +# define CT_R_INVALID_LOG_ID_LENGTH 100 +# define CT_R_LOG_CONF_INVALID 109 +# define CT_R_LOG_CONF_INVALID_KEY 110 +# define CT_R_LOG_CONF_MISSING_DESCRIPTION 111 +# define CT_R_LOG_CONF_MISSING_KEY 112 +# define CT_R_LOG_KEY_INVALID 113 +# define CT_R_SCT_FUTURE_TIMESTAMP 116 +# define CT_R_SCT_INVALID 104 +# define CT_R_SCT_INVALID_SIGNATURE 107 +# define CT_R_SCT_LIST_INVALID 105 +# define CT_R_SCT_LOG_ID_MISMATCH 114 +# define CT_R_SCT_NOT_SET 106 +# define CT_R_SCT_UNSUPPORTED_VERSION 115 +# define CT_R_UNRECOGNIZED_SIGNATURE_NID 101 +# define CT_R_UNSUPPORTED_ENTRY_TYPE 102 +# define CT_R_UNSUPPORTED_VERSION 103 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/des.h b/thrid-party/openssl/openssl/des.h new file mode 100644 index 0000000000..be4abbdfd0 --- /dev/null +++ b/thrid-party/openssl/openssl/des.h @@ -0,0 +1,174 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DES_H +# define HEADER_DES_H + +# include + +# ifndef OPENSSL_NO_DES +# ifdef __cplusplus +extern "C" { +# endif +# include + +typedef unsigned int DES_LONG; + +# ifdef OPENSSL_BUILD_SHLIBCRYPTO +# undef OPENSSL_EXTERN +# define OPENSSL_EXTERN OPENSSL_EXPORT +# endif + +typedef unsigned char DES_cblock[8]; +typedef /* const */ unsigned char const_DES_cblock[8]; +/* + * With "const", gcc 2.8.1 on Solaris thinks that DES_cblock * and + * const_DES_cblock * are incompatible pointer types. + */ + +typedef struct DES_ks { + union { + DES_cblock cblock; + /* + * make sure things are correct size on machines with 8 byte longs + */ + DES_LONG deslong[2]; + } ks[16]; +} DES_key_schedule; + +# define DES_KEY_SZ (sizeof(DES_cblock)) +# define DES_SCHEDULE_SZ (sizeof(DES_key_schedule)) + +# define DES_ENCRYPT 1 +# define DES_DECRYPT 0 + +# define DES_CBC_MODE 0 +# define DES_PCBC_MODE 1 + +# define DES_ecb2_encrypt(i,o,k1,k2,e) \ + DES_ecb3_encrypt((i),(o),(k1),(k2),(k1),(e)) + +# define DES_ede2_cbc_encrypt(i,o,l,k1,k2,iv,e) \ + DES_ede3_cbc_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(e)) + +# define DES_ede2_cfb64_encrypt(i,o,l,k1,k2,iv,n,e) \ + DES_ede3_cfb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n),(e)) + +# define DES_ede2_ofb64_encrypt(i,o,l,k1,k2,iv,n) \ + DES_ede3_ofb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n)) + +OPENSSL_DECLARE_GLOBAL(int, DES_check_key); /* defaults to false */ +# define DES_check_key OPENSSL_GLOBAL_REF(DES_check_key) + +const char *DES_options(void); +void DES_ecb3_encrypt(const_DES_cblock *input, DES_cblock *output, + DES_key_schedule *ks1, DES_key_schedule *ks2, + DES_key_schedule *ks3, int enc); +DES_LONG DES_cbc_cksum(const unsigned char *input, DES_cblock *output, + long length, DES_key_schedule *schedule, + const_DES_cblock *ivec); +/* DES_cbc_encrypt does not update the IV! Use DES_ncbc_encrypt instead. */ +void DES_cbc_encrypt(const unsigned char *input, unsigned char *output, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int enc); +void DES_ncbc_encrypt(const unsigned char *input, unsigned char *output, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int enc); +void DES_xcbc_encrypt(const unsigned char *input, unsigned char *output, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, const_DES_cblock *inw, + const_DES_cblock *outw, int enc); +void DES_cfb_encrypt(const unsigned char *in, unsigned char *out, int numbits, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int enc); +void DES_ecb_encrypt(const_DES_cblock *input, DES_cblock *output, + DES_key_schedule *ks, int enc); + +/* + * This is the DES encryption function that gets called by just about every + * other DES routine in the library. You should not use this function except + * to implement 'modes' of DES. I say this because the functions that call + * this routine do the conversion from 'char *' to long, and this needs to be + * done to make sure 'non-aligned' memory access do not occur. The + * characters are loaded 'little endian'. Data is a pointer to 2 unsigned + * long's and ks is the DES_key_schedule to use. enc, is non zero specifies + * encryption, zero if decryption. + */ +void DES_encrypt1(DES_LONG *data, DES_key_schedule *ks, int enc); + +/* + * This functions is the same as DES_encrypt1() except that the DES initial + * permutation (IP) and final permutation (FP) have been left out. As for + * DES_encrypt1(), you should not use this function. It is used by the + * routines in the library that implement triple DES. IP() DES_encrypt2() + * DES_encrypt2() DES_encrypt2() FP() is the same as DES_encrypt1() + * DES_encrypt1() DES_encrypt1() except faster :-). + */ +void DES_encrypt2(DES_LONG *data, DES_key_schedule *ks, int enc); + +void DES_encrypt3(DES_LONG *data, DES_key_schedule *ks1, + DES_key_schedule *ks2, DES_key_schedule *ks3); +void DES_decrypt3(DES_LONG *data, DES_key_schedule *ks1, + DES_key_schedule *ks2, DES_key_schedule *ks3); +void DES_ede3_cbc_encrypt(const unsigned char *input, unsigned char *output, + long length, + DES_key_schedule *ks1, DES_key_schedule *ks2, + DES_key_schedule *ks3, DES_cblock *ivec, int enc); +void DES_ede3_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, DES_key_schedule *ks1, + DES_key_schedule *ks2, DES_key_schedule *ks3, + DES_cblock *ivec, int *num, int enc); +void DES_ede3_cfb_encrypt(const unsigned char *in, unsigned char *out, + int numbits, long length, DES_key_schedule *ks1, + DES_key_schedule *ks2, DES_key_schedule *ks3, + DES_cblock *ivec, int enc); +void DES_ede3_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, DES_key_schedule *ks1, + DES_key_schedule *ks2, DES_key_schedule *ks3, + DES_cblock *ivec, int *num); +char *DES_fcrypt(const char *buf, const char *salt, char *ret); +char *DES_crypt(const char *buf, const char *salt); +void DES_ofb_encrypt(const unsigned char *in, unsigned char *out, int numbits, + long length, DES_key_schedule *schedule, + DES_cblock *ivec); +void DES_pcbc_encrypt(const unsigned char *input, unsigned char *output, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int enc); +DES_LONG DES_quad_cksum(const unsigned char *input, DES_cblock output[], + long length, int out_count, DES_cblock *seed); +int DES_random_key(DES_cblock *ret); +void DES_set_odd_parity(DES_cblock *key); +int DES_check_key_parity(const_DES_cblock *key); +int DES_is_weak_key(const_DES_cblock *key); +/* + * DES_set_key (= set_key = DES_key_sched = key_sched) calls + * DES_set_key_checked if global variable DES_check_key is set, + * DES_set_key_unchecked otherwise. + */ +int DES_set_key(const_DES_cblock *key, DES_key_schedule *schedule); +int DES_key_sched(const_DES_cblock *key, DES_key_schedule *schedule); +int DES_set_key_checked(const_DES_cblock *key, DES_key_schedule *schedule); +void DES_set_key_unchecked(const_DES_cblock *key, DES_key_schedule *schedule); +void DES_string_to_key(const char *str, DES_cblock *key); +void DES_string_to_2keys(const char *str, DES_cblock *key1, DES_cblock *key2); +void DES_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int *num, int enc); +void DES_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, DES_key_schedule *schedule, + DES_cblock *ivec, int *num); + +# define DES_fixup_key_parity DES_set_odd_parity + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/des_old.h b/thrid-party/openssl/openssl/des_old.h new file mode 100644 index 0000000000..ee7607a241 --- /dev/null +++ b/thrid-party/openssl/openssl/des_old.h @@ -0,0 +1,497 @@ +/* crypto/des/des_old.h */ + +/*- + * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + * + * The function names in here are deprecated and are only present to + * provide an interface compatible with openssl 0.9.6 and older as + * well as libdes. OpenSSL now provides functions where "des_" has + * been replaced with "DES_" in the names, to make it possible to + * make incompatible changes that are needed for C type security and + * other stuff. + * + * This include files has two compatibility modes: + * + * - If OPENSSL_DES_LIBDES_COMPATIBILITY is defined, you get an API + * that is compatible with libdes and SSLeay. + * - If OPENSSL_DES_LIBDES_COMPATIBILITY isn't defined, you get an + * API that is compatible with OpenSSL 0.9.5x to 0.9.6x. + * + * Note that these modes break earlier snapshots of OpenSSL, where + * libdes compatibility was the only available mode or (later on) the + * prefered compatibility mode. However, after much consideration + * (and more or less violent discussions with external parties), it + * was concluded that OpenSSL should be compatible with earlier versions + * of itself before anything else. Also, in all honesty, libdes is + * an old beast that shouldn't really be used any more. + * + * Please consider starting to use the DES_ functions rather than the + * des_ ones. The des_ functions will disappear completely before + * OpenSSL 1.0! + * + * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + */ + +/* + * Written by Richard Levitte (richard@levitte.org) for the OpenSSL project + * 2001. + */ +/* ==================================================================== + * Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#ifndef HEADER_DES_H +# define HEADER_DES_H + +# include /* OPENSSL_EXTERN, OPENSSL_NO_DES, DES_LONG */ + +# ifdef OPENSSL_NO_DES +# error DES is disabled. +# endif + +# ifndef HEADER_NEW_DES_H +# error You must include des.h, not des_old.h directly. +# endif + +# ifdef _KERBEROS_DES_H +# error replaces . +# endif + +# include + +# ifdef OPENSSL_BUILD_SHLIBCRYPTO +# undef OPENSSL_EXTERN +# define OPENSSL_EXTERN OPENSSL_EXPORT +# endif + +#ifdef __cplusplus +extern "C" { +#endif + +# ifdef _ +# undef _ +# endif + +typedef unsigned char _ossl_old_des_cblock[8]; +typedef struct _ossl_old_des_ks_struct { + union { + _ossl_old_des_cblock _; + /* + * make sure things are correct size on machines with 8 byte longs + */ + DES_LONG pad[2]; + } ks; +} _ossl_old_des_key_schedule[16]; + +# ifndef OPENSSL_DES_LIBDES_COMPATIBILITY +# define des_cblock DES_cblock +# define const_des_cblock const_DES_cblock +# define des_key_schedule DES_key_schedule +# define des_ecb3_encrypt(i,o,k1,k2,k3,e)\ + DES_ecb3_encrypt((i),(o),&(k1),&(k2),&(k3),(e)) +# define des_ede3_cbc_encrypt(i,o,l,k1,k2,k3,iv,e)\ + DES_ede3_cbc_encrypt((i),(o),(l),&(k1),&(k2),&(k3),(iv),(e)) +# define des_ede3_cbcm_encrypt(i,o,l,k1,k2,k3,iv1,iv2,e)\ + DES_ede3_cbcm_encrypt((i),(o),(l),&(k1),&(k2),&(k3),(iv1),(iv2),(e)) +# define des_ede3_cfb64_encrypt(i,o,l,k1,k2,k3,iv,n,e)\ + DES_ede3_cfb64_encrypt((i),(o),(l),&(k1),&(k2),&(k3),(iv),(n),(e)) +# define des_ede3_ofb64_encrypt(i,o,l,k1,k2,k3,iv,n)\ + DES_ede3_ofb64_encrypt((i),(o),(l),&(k1),&(k2),&(k3),(iv),(n)) +# define des_options()\ + DES_options() +# define des_cbc_cksum(i,o,l,k,iv)\ + DES_cbc_cksum((i),(o),(l),&(k),(iv)) +# define des_cbc_encrypt(i,o,l,k,iv,e)\ + DES_cbc_encrypt((i),(o),(l),&(k),(iv),(e)) +# define des_ncbc_encrypt(i,o,l,k,iv,e)\ + DES_ncbc_encrypt((i),(o),(l),&(k),(iv),(e)) +# define des_xcbc_encrypt(i,o,l,k,iv,inw,outw,e)\ + DES_xcbc_encrypt((i),(o),(l),&(k),(iv),(inw),(outw),(e)) +# define des_cfb_encrypt(i,o,n,l,k,iv,e)\ + DES_cfb_encrypt((i),(o),(n),(l),&(k),(iv),(e)) +# define des_ecb_encrypt(i,o,k,e)\ + DES_ecb_encrypt((i),(o),&(k),(e)) +# define des_encrypt1(d,k,e)\ + DES_encrypt1((d),&(k),(e)) +# define des_encrypt2(d,k,e)\ + DES_encrypt2((d),&(k),(e)) +# define des_encrypt3(d,k1,k2,k3)\ + DES_encrypt3((d),&(k1),&(k2),&(k3)) +# define des_decrypt3(d,k1,k2,k3)\ + DES_decrypt3((d),&(k1),&(k2),&(k3)) +# define des_xwhite_in2out(k,i,o)\ + DES_xwhite_in2out((k),(i),(o)) +# define des_enc_read(f,b,l,k,iv)\ + DES_enc_read((f),(b),(l),&(k),(iv)) +# define des_enc_write(f,b,l,k,iv)\ + DES_enc_write((f),(b),(l),&(k),(iv)) +# define des_fcrypt(b,s,r)\ + DES_fcrypt((b),(s),(r)) +# if 0 +# define des_crypt(b,s)\ + DES_crypt((b),(s)) +# if !defined(PERL5) && !defined(__FreeBSD__) && !defined(NeXT) && !defined(__OpenBSD__) +# define crypt(b,s)\ + DES_crypt((b),(s)) +# endif +# endif +# define des_ofb_encrypt(i,o,n,l,k,iv)\ + DES_ofb_encrypt((i),(o),(n),(l),&(k),(iv)) +# define des_pcbc_encrypt(i,o,l,k,iv,e)\ + DES_pcbc_encrypt((i),(o),(l),&(k),(iv),(e)) +# define des_quad_cksum(i,o,l,c,s)\ + DES_quad_cksum((i),(o),(l),(c),(s)) +# define des_random_seed(k)\ + _ossl_096_des_random_seed((k)) +# define des_random_key(r)\ + DES_random_key((r)) +# define des_read_password(k,p,v) \ + DES_read_password((k),(p),(v)) +# define des_read_2passwords(k1,k2,p,v) \ + DES_read_2passwords((k1),(k2),(p),(v)) +# define des_set_odd_parity(k)\ + DES_set_odd_parity((k)) +# define des_check_key_parity(k)\ + DES_check_key_parity((k)) +# define des_is_weak_key(k)\ + DES_is_weak_key((k)) +# define des_set_key(k,ks)\ + DES_set_key((k),&(ks)) +# define des_key_sched(k,ks)\ + DES_key_sched((k),&(ks)) +# define des_set_key_checked(k,ks)\ + DES_set_key_checked((k),&(ks)) +# define des_set_key_unchecked(k,ks)\ + DES_set_key_unchecked((k),&(ks)) +# define des_string_to_key(s,k)\ + DES_string_to_key((s),(k)) +# define des_string_to_2keys(s,k1,k2)\ + DES_string_to_2keys((s),(k1),(k2)) +# define des_cfb64_encrypt(i,o,l,ks,iv,n,e)\ + DES_cfb64_encrypt((i),(o),(l),&(ks),(iv),(n),(e)) +# define des_ofb64_encrypt(i,o,l,ks,iv,n)\ + DES_ofb64_encrypt((i),(o),(l),&(ks),(iv),(n)) + +# define des_ecb2_encrypt(i,o,k1,k2,e) \ + des_ecb3_encrypt((i),(o),(k1),(k2),(k1),(e)) + +# define des_ede2_cbc_encrypt(i,o,l,k1,k2,iv,e) \ + des_ede3_cbc_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(e)) + +# define des_ede2_cfb64_encrypt(i,o,l,k1,k2,iv,n,e) \ + des_ede3_cfb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n),(e)) + +# define des_ede2_ofb64_encrypt(i,o,l,k1,k2,iv,n) \ + des_ede3_ofb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n)) + +# define des_check_key DES_check_key +# define des_rw_mode DES_rw_mode +# else /* libdes compatibility */ +/* + * Map all symbol names to _ossl_old_des_* form, so we avoid all clashes with + * libdes + */ +# define des_cblock _ossl_old_des_cblock +# define des_key_schedule _ossl_old_des_key_schedule +# define des_ecb3_encrypt(i,o,k1,k2,k3,e)\ + _ossl_old_des_ecb3_encrypt((i),(o),(k1),(k2),(k3),(e)) +# define des_ede3_cbc_encrypt(i,o,l,k1,k2,k3,iv,e)\ + _ossl_old_des_ede3_cbc_encrypt((i),(o),(l),(k1),(k2),(k3),(iv),(e)) +# define des_ede3_cfb64_encrypt(i,o,l,k1,k2,k3,iv,n,e)\ + _ossl_old_des_ede3_cfb64_encrypt((i),(o),(l),(k1),(k2),(k3),(iv),(n),(e)) +# define des_ede3_ofb64_encrypt(i,o,l,k1,k2,k3,iv,n)\ + _ossl_old_des_ede3_ofb64_encrypt((i),(o),(l),(k1),(k2),(k3),(iv),(n)) +# define des_options()\ + _ossl_old_des_options() +# define des_cbc_cksum(i,o,l,k,iv)\ + _ossl_old_des_cbc_cksum((i),(o),(l),(k),(iv)) +# define des_cbc_encrypt(i,o,l,k,iv,e)\ + _ossl_old_des_cbc_encrypt((i),(o),(l),(k),(iv),(e)) +# define des_ncbc_encrypt(i,o,l,k,iv,e)\ + _ossl_old_des_ncbc_encrypt((i),(o),(l),(k),(iv),(e)) +# define des_xcbc_encrypt(i,o,l,k,iv,inw,outw,e)\ + _ossl_old_des_xcbc_encrypt((i),(o),(l),(k),(iv),(inw),(outw),(e)) +# define des_cfb_encrypt(i,o,n,l,k,iv,e)\ + _ossl_old_des_cfb_encrypt((i),(o),(n),(l),(k),(iv),(e)) +# define des_ecb_encrypt(i,o,k,e)\ + _ossl_old_des_ecb_encrypt((i),(o),(k),(e)) +# define des_encrypt(d,k,e)\ + _ossl_old_des_encrypt((d),(k),(e)) +# define des_encrypt2(d,k,e)\ + _ossl_old_des_encrypt2((d),(k),(e)) +# define des_encrypt3(d,k1,k2,k3)\ + _ossl_old_des_encrypt3((d),(k1),(k2),(k3)) +# define des_decrypt3(d,k1,k2,k3)\ + _ossl_old_des_decrypt3((d),(k1),(k2),(k3)) +# define des_xwhite_in2out(k,i,o)\ + _ossl_old_des_xwhite_in2out((k),(i),(o)) +# define des_enc_read(f,b,l,k,iv)\ + _ossl_old_des_enc_read((f),(b),(l),(k),(iv)) +# define des_enc_write(f,b,l,k,iv)\ + _ossl_old_des_enc_write((f),(b),(l),(k),(iv)) +# define des_fcrypt(b,s,r)\ + _ossl_old_des_fcrypt((b),(s),(r)) +# define des_crypt(b,s)\ + _ossl_old_des_crypt((b),(s)) +# if 0 +# define crypt(b,s)\ + _ossl_old_crypt((b),(s)) +# endif +# define des_ofb_encrypt(i,o,n,l,k,iv)\ + _ossl_old_des_ofb_encrypt((i),(o),(n),(l),(k),(iv)) +# define des_pcbc_encrypt(i,o,l,k,iv,e)\ + _ossl_old_des_pcbc_encrypt((i),(o),(l),(k),(iv),(e)) +# define des_quad_cksum(i,o,l,c,s)\ + _ossl_old_des_quad_cksum((i),(o),(l),(c),(s)) +# define des_random_seed(k)\ + _ossl_old_des_random_seed((k)) +# define des_random_key(r)\ + _ossl_old_des_random_key((r)) +# define des_read_password(k,p,v) \ + _ossl_old_des_read_password((k),(p),(v)) +# define des_read_2passwords(k1,k2,p,v) \ + _ossl_old_des_read_2passwords((k1),(k2),(p),(v)) +# define des_set_odd_parity(k)\ + _ossl_old_des_set_odd_parity((k)) +# define des_is_weak_key(k)\ + _ossl_old_des_is_weak_key((k)) +# define des_set_key(k,ks)\ + _ossl_old_des_set_key((k),(ks)) +# define des_key_sched(k,ks)\ + _ossl_old_des_key_sched((k),(ks)) +# define des_string_to_key(s,k)\ + _ossl_old_des_string_to_key((s),(k)) +# define des_string_to_2keys(s,k1,k2)\ + _ossl_old_des_string_to_2keys((s),(k1),(k2)) +# define des_cfb64_encrypt(i,o,l,ks,iv,n,e)\ + _ossl_old_des_cfb64_encrypt((i),(o),(l),(ks),(iv),(n),(e)) +# define des_ofb64_encrypt(i,o,l,ks,iv,n)\ + _ossl_old_des_ofb64_encrypt((i),(o),(l),(ks),(iv),(n)) + +# define des_ecb2_encrypt(i,o,k1,k2,e) \ + des_ecb3_encrypt((i),(o),(k1),(k2),(k1),(e)) + +# define des_ede2_cbc_encrypt(i,o,l,k1,k2,iv,e) \ + des_ede3_cbc_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(e)) + +# define des_ede2_cfb64_encrypt(i,o,l,k1,k2,iv,n,e) \ + des_ede3_cfb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n),(e)) + +# define des_ede2_ofb64_encrypt(i,o,l,k1,k2,iv,n) \ + des_ede3_ofb64_encrypt((i),(o),(l),(k1),(k2),(k1),(iv),(n)) + +# define des_check_key DES_check_key +# define des_rw_mode DES_rw_mode +# endif + +const char *_ossl_old_des_options(void); +void _ossl_old_des_ecb3_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, + _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3, int enc); +DES_LONG _ossl_old_des_cbc_cksum(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec); +void _ossl_old_des_cbc_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int enc); +void _ossl_old_des_ncbc_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int enc); +void _ossl_old_des_xcbc_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, + _ossl_old_des_cblock *inw, + _ossl_old_des_cblock *outw, int enc); +void _ossl_old_des_cfb_encrypt(unsigned char *in, unsigned char *out, + int numbits, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int enc); +void _ossl_old_des_ecb_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, + _ossl_old_des_key_schedule ks, int enc); +void _ossl_old_des_encrypt(DES_LONG *data, _ossl_old_des_key_schedule ks, + int enc); +void _ossl_old_des_encrypt2(DES_LONG *data, _ossl_old_des_key_schedule ks, + int enc); +void _ossl_old_des_encrypt3(DES_LONG *data, _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3); +void _ossl_old_des_decrypt3(DES_LONG *data, _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3); +void _ossl_old_des_ede3_cbc_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3, + _ossl_old_des_cblock *ivec, int enc); +void _ossl_old_des_ede3_cfb64_encrypt(unsigned char *in, unsigned char *out, + long length, + _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3, + _ossl_old_des_cblock *ivec, int *num, + int enc); +void _ossl_old_des_ede3_ofb64_encrypt(unsigned char *in, unsigned char *out, + long length, + _ossl_old_des_key_schedule ks1, + _ossl_old_des_key_schedule ks2, + _ossl_old_des_key_schedule ks3, + _ossl_old_des_cblock *ivec, int *num); +# if 0 +void _ossl_old_des_xwhite_in2out(_ossl_old_des_cblock (*des_key), + _ossl_old_des_cblock (*in_white), + _ossl_old_des_cblock (*out_white)); +# endif + +int _ossl_old_des_enc_read(int fd, char *buf, int len, + _ossl_old_des_key_schedule sched, + _ossl_old_des_cblock *iv); +int _ossl_old_des_enc_write(int fd, char *buf, int len, + _ossl_old_des_key_schedule sched, + _ossl_old_des_cblock *iv); +char *_ossl_old_des_fcrypt(const char *buf, const char *salt, char *ret); +char *_ossl_old_des_crypt(const char *buf, const char *salt); +# if !defined(PERL5) && !defined(NeXT) +char *_ossl_old_crypt(const char *buf, const char *salt); +# endif +void _ossl_old_des_ofb_encrypt(unsigned char *in, unsigned char *out, + int numbits, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec); +void _ossl_old_des_pcbc_encrypt(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int enc); +DES_LONG _ossl_old_des_quad_cksum(_ossl_old_des_cblock *input, + _ossl_old_des_cblock *output, long length, + int out_count, _ossl_old_des_cblock *seed); +void _ossl_old_des_random_seed(_ossl_old_des_cblock key); +void _ossl_old_des_random_key(_ossl_old_des_cblock ret); +int _ossl_old_des_read_password(_ossl_old_des_cblock *key, const char *prompt, + int verify); +int _ossl_old_des_read_2passwords(_ossl_old_des_cblock *key1, + _ossl_old_des_cblock *key2, + const char *prompt, int verify); +void _ossl_old_des_set_odd_parity(_ossl_old_des_cblock *key); +int _ossl_old_des_is_weak_key(_ossl_old_des_cblock *key); +int _ossl_old_des_set_key(_ossl_old_des_cblock *key, + _ossl_old_des_key_schedule schedule); +int _ossl_old_des_key_sched(_ossl_old_des_cblock *key, + _ossl_old_des_key_schedule schedule); +void _ossl_old_des_string_to_key(char *str, _ossl_old_des_cblock *key); +void _ossl_old_des_string_to_2keys(char *str, _ossl_old_des_cblock *key1, + _ossl_old_des_cblock *key2); +void _ossl_old_des_cfb64_encrypt(unsigned char *in, unsigned char *out, + long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int *num, + int enc); +void _ossl_old_des_ofb64_encrypt(unsigned char *in, unsigned char *out, + long length, + _ossl_old_des_key_schedule schedule, + _ossl_old_des_cblock *ivec, int *num); + +void _ossl_096_des_random_seed(des_cblock *key); + +/* + * The following definitions provide compatibility with the MIT Kerberos + * library. The _ossl_old_des_key_schedule structure is not binary + * compatible. + */ + +# define _KERBEROS_DES_H + +# define KRBDES_ENCRYPT DES_ENCRYPT +# define KRBDES_DECRYPT DES_DECRYPT + +# ifdef KERBEROS +# define ENCRYPT DES_ENCRYPT +# define DECRYPT DES_DECRYPT +# endif + +# ifndef NCOMPAT +# define C_Block des_cblock +# define Key_schedule des_key_schedule +# define KEY_SZ DES_KEY_SZ +# define string_to_key des_string_to_key +# define read_pw_string des_read_pw_string +# define random_key des_random_key +# define pcbc_encrypt des_pcbc_encrypt +# define set_key des_set_key +# define key_sched des_key_sched +# define ecb_encrypt des_ecb_encrypt +# define cbc_encrypt des_cbc_encrypt +# define ncbc_encrypt des_ncbc_encrypt +# define xcbc_encrypt des_xcbc_encrypt +# define cbc_cksum des_cbc_cksum +# define quad_cksum des_quad_cksum +# define check_parity des_check_key_parity +# endif + +# define des_fixup_key_parity DES_fixup_key_parity + +#ifdef __cplusplus +} +#endif + +/* for DES_read_pw_string et al */ +# include + +#endif diff --git a/thrid-party/openssl/openssl/dh.h b/thrid-party/openssl/openssl/dh.h new file mode 100644 index 0000000000..3527540cdd --- /dev/null +++ b/thrid-party/openssl/openssl/dh.h @@ -0,0 +1,340 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DH_H +# define HEADER_DH_H + +# include + +# ifndef OPENSSL_NO_DH +# include +# include +# include +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif +# include + +# ifdef __cplusplus +extern "C" { +# endif + +# ifndef OPENSSL_DH_MAX_MODULUS_BITS +# define OPENSSL_DH_MAX_MODULUS_BITS 10000 +# endif + +# define OPENSSL_DH_FIPS_MIN_MODULUS_BITS 1024 + +# define DH_FLAG_CACHE_MONT_P 0x01 + +# if OPENSSL_API_COMPAT < 0x10100000L +/* + * Does nothing. Previously this switched off constant time behaviour. + */ +# define DH_FLAG_NO_EXP_CONSTTIME 0x00 +# endif + +/* + * If this flag is set the DH method is FIPS compliant and can be used in + * FIPS mode. This is set in the validated module method. If an application + * sets this flag in its own methods it is its responsibility to ensure the + * result is compliant. + */ + +# define DH_FLAG_FIPS_METHOD 0x0400 + +/* + * If this flag is set the operations normally disabled in FIPS mode are + * permitted it is then the applications responsibility to ensure that the + * usage is compliant. + */ + +# define DH_FLAG_NON_FIPS_ALLOW 0x0400 + +/* Already defined in ossl_typ.h */ +/* typedef struct dh_st DH; */ +/* typedef struct dh_method DH_METHOD; */ + +DECLARE_ASN1_ITEM(DHparams) + +# define DH_GENERATOR_2 2 +/* #define DH_GENERATOR_3 3 */ +# define DH_GENERATOR_5 5 + +/* DH_check error codes */ +# define DH_CHECK_P_NOT_PRIME 0x01 +# define DH_CHECK_P_NOT_SAFE_PRIME 0x02 +# define DH_UNABLE_TO_CHECK_GENERATOR 0x04 +# define DH_NOT_SUITABLE_GENERATOR 0x08 +# define DH_CHECK_Q_NOT_PRIME 0x10 +# define DH_CHECK_INVALID_Q_VALUE 0x20 +# define DH_CHECK_INVALID_J_VALUE 0x40 + +/* DH_check_pub_key error codes */ +# define DH_CHECK_PUBKEY_TOO_SMALL 0x01 +# define DH_CHECK_PUBKEY_TOO_LARGE 0x02 +# define DH_CHECK_PUBKEY_INVALID 0x04 + +/* + * primes p where (p-1)/2 is prime too are called "safe"; we define this for + * backward compatibility: + */ +# define DH_CHECK_P_NOT_STRONG_PRIME DH_CHECK_P_NOT_SAFE_PRIME + +# define d2i_DHparams_fp(fp,x) \ + (DH *)ASN1_d2i_fp((char *(*)())DH_new, \ + (char *(*)())d2i_DHparams, \ + (fp), \ + (unsigned char **)(x)) +# define i2d_DHparams_fp(fp,x) \ + ASN1_i2d_fp(i2d_DHparams,(fp), (unsigned char *)(x)) +# define d2i_DHparams_bio(bp,x) \ + ASN1_d2i_bio_of(DH, DH_new, d2i_DHparams, bp, x) +# define i2d_DHparams_bio(bp,x) \ + ASN1_i2d_bio_of_const(DH,i2d_DHparams,bp,x) + +# define d2i_DHxparams_fp(fp,x) \ + (DH *)ASN1_d2i_fp((char *(*)())DH_new, \ + (char *(*)())d2i_DHxparams, \ + (fp), \ + (unsigned char **)(x)) +# define i2d_DHxparams_fp(fp,x) \ + ASN1_i2d_fp(i2d_DHxparams,(fp), (unsigned char *)(x)) +# define d2i_DHxparams_bio(bp,x) \ + ASN1_d2i_bio_of(DH, DH_new, d2i_DHxparams, bp, x) +# define i2d_DHxparams_bio(bp,x) \ + ASN1_i2d_bio_of_const(DH, i2d_DHxparams, bp, x) + +DH *DHparams_dup(DH *); + +const DH_METHOD *DH_OpenSSL(void); + +void DH_set_default_method(const DH_METHOD *meth); +const DH_METHOD *DH_get_default_method(void); +int DH_set_method(DH *dh, const DH_METHOD *meth); +DH *DH_new_method(ENGINE *engine); + +DH *DH_new(void); +void DH_free(DH *dh); +int DH_up_ref(DH *dh); +int DH_bits(const DH *dh); +int DH_size(const DH *dh); +int DH_security_bits(const DH *dh); +#define DH_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_DH, l, p, newf, dupf, freef) +int DH_set_ex_data(DH *d, int idx, void *arg); +void *DH_get_ex_data(DH *d, int idx); + +/* Deprecated version */ +DEPRECATEDIN_0_9_8(DH *DH_generate_parameters(int prime_len, int generator, + void (*callback) (int, int, + void *), + void *cb_arg)) + +/* New version */ +int DH_generate_parameters_ex(DH *dh, int prime_len, int generator, + BN_GENCB *cb); + +int DH_check_params_ex(const DH *dh); +int DH_check_ex(const DH *dh); +int DH_check_pub_key_ex(const DH *dh, const BIGNUM *pub_key); +int DH_check_params(const DH *dh, int *ret); +int DH_check(const DH *dh, int *codes); +int DH_check_pub_key(const DH *dh, const BIGNUM *pub_key, int *codes); +int DH_generate_key(DH *dh); +int DH_compute_key(unsigned char *key, const BIGNUM *pub_key, DH *dh); +int DH_compute_key_padded(unsigned char *key, const BIGNUM *pub_key, DH *dh); +DH *d2i_DHparams(DH **a, const unsigned char **pp, long length); +int i2d_DHparams(const DH *a, unsigned char **pp); +DH *d2i_DHxparams(DH **a, const unsigned char **pp, long length); +int i2d_DHxparams(const DH *a, unsigned char **pp); +# ifndef OPENSSL_NO_STDIO +int DHparams_print_fp(FILE *fp, const DH *x); +# endif +int DHparams_print(BIO *bp, const DH *x); + +/* RFC 5114 parameters */ +DH *DH_get_1024_160(void); +DH *DH_get_2048_224(void); +DH *DH_get_2048_256(void); + +/* Named parameters, currently RFC7919 */ +DH *DH_new_by_nid(int nid); +int DH_get_nid(const DH *dh); + +# ifndef OPENSSL_NO_CMS +/* RFC2631 KDF */ +int DH_KDF_X9_42(unsigned char *out, size_t outlen, + const unsigned char *Z, size_t Zlen, + ASN1_OBJECT *key_oid, + const unsigned char *ukm, size_t ukmlen, const EVP_MD *md); +# endif + +void DH_get0_pqg(const DH *dh, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); +int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); +void DH_get0_key(const DH *dh, + const BIGNUM **pub_key, const BIGNUM **priv_key); +int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); +const BIGNUM *DH_get0_p(const DH *dh); +const BIGNUM *DH_get0_q(const DH *dh); +const BIGNUM *DH_get0_g(const DH *dh); +const BIGNUM *DH_get0_priv_key(const DH *dh); +const BIGNUM *DH_get0_pub_key(const DH *dh); +void DH_clear_flags(DH *dh, int flags); +int DH_test_flags(const DH *dh, int flags); +void DH_set_flags(DH *dh, int flags); +ENGINE *DH_get0_engine(DH *d); +long DH_get_length(const DH *dh); +int DH_set_length(DH *dh, long length); + +DH_METHOD *DH_meth_new(const char *name, int flags); +void DH_meth_free(DH_METHOD *dhm); +DH_METHOD *DH_meth_dup(const DH_METHOD *dhm); +const char *DH_meth_get0_name(const DH_METHOD *dhm); +int DH_meth_set1_name(DH_METHOD *dhm, const char *name); +int DH_meth_get_flags(const DH_METHOD *dhm); +int DH_meth_set_flags(DH_METHOD *dhm, int flags); +void *DH_meth_get0_app_data(const DH_METHOD *dhm); +int DH_meth_set0_app_data(DH_METHOD *dhm, void *app_data); +int (*DH_meth_get_generate_key(const DH_METHOD *dhm)) (DH *); +int DH_meth_set_generate_key(DH_METHOD *dhm, int (*generate_key) (DH *)); +int (*DH_meth_get_compute_key(const DH_METHOD *dhm)) + (unsigned char *key, const BIGNUM *pub_key, DH *dh); +int DH_meth_set_compute_key(DH_METHOD *dhm, + int (*compute_key) (unsigned char *key, const BIGNUM *pub_key, DH *dh)); +int (*DH_meth_get_bn_mod_exp(const DH_METHOD *dhm)) + (const DH *, BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *, BN_MONT_CTX *); +int DH_meth_set_bn_mod_exp(DH_METHOD *dhm, + int (*bn_mod_exp) (const DH *, BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, BN_CTX *, BN_MONT_CTX *)); +int (*DH_meth_get_init(const DH_METHOD *dhm))(DH *); +int DH_meth_set_init(DH_METHOD *dhm, int (*init)(DH *)); +int (*DH_meth_get_finish(const DH_METHOD *dhm)) (DH *); +int DH_meth_set_finish(DH_METHOD *dhm, int (*finish) (DH *)); +int (*DH_meth_get_generate_params(const DH_METHOD *dhm)) + (DH *, int, int, BN_GENCB *); +int DH_meth_set_generate_params(DH_METHOD *dhm, + int (*generate_params) (DH *, int, int, BN_GENCB *)); + + +# define EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, len) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN, len, NULL) + +# define EVP_PKEY_CTX_set_dh_paramgen_subprime_len(ctx, len) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_PARAMGEN_SUBPRIME_LEN, len, NULL) + +# define EVP_PKEY_CTX_set_dh_paramgen_type(ctx, typ) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_PARAMGEN_TYPE, typ, NULL) + +# define EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, gen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_PARAMGEN_GENERATOR, gen, NULL) + +# define EVP_PKEY_CTX_set_dh_rfc5114(ctx, gen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_RFC5114, gen, NULL) + +# define EVP_PKEY_CTX_set_dhx_rfc5114(ctx, gen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DH_RFC5114, gen, NULL) + +# define EVP_PKEY_CTX_set_dh_nid(ctx, nid) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, \ + EVP_PKEY_OP_PARAMGEN | EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_DH_NID, nid, NULL) + +# define EVP_PKEY_CTX_set_dh_pad(ctx, pad) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_PAD, pad, NULL) + +# define EVP_PKEY_CTX_set_dh_kdf_type(ctx, kdf) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_TYPE, kdf, NULL) + +# define EVP_PKEY_CTX_get_dh_kdf_type(ctx) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_TYPE, -2, NULL) + +# define EVP_PKEY_CTX_set0_dh_kdf_oid(ctx, oid) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_OID, 0, (void *)(oid)) + +# define EVP_PKEY_CTX_get0_dh_kdf_oid(ctx, poid) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_DH_KDF_OID, 0, (void *)(poid)) + +# define EVP_PKEY_CTX_set_dh_kdf_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_get_dh_kdf_md(ctx, pmd) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_DH_KDF_MD, 0, (void *)(pmd)) + +# define EVP_PKEY_CTX_set_dh_kdf_outlen(ctx, len) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_OUTLEN, len, NULL) + +# define EVP_PKEY_CTX_get_dh_kdf_outlen(ctx, plen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_DH_KDF_OUTLEN, 0, (void *)(plen)) + +# define EVP_PKEY_CTX_set0_dh_kdf_ukm(ctx, p, plen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_DH_KDF_UKM, plen, (void *)(p)) + +# define EVP_PKEY_CTX_get0_dh_kdf_ukm(ctx, p) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DHX, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_DH_KDF_UKM, 0, (void *)(p)) + +# define EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN (EVP_PKEY_ALG_CTRL + 1) +# define EVP_PKEY_CTRL_DH_PARAMGEN_GENERATOR (EVP_PKEY_ALG_CTRL + 2) +# define EVP_PKEY_CTRL_DH_RFC5114 (EVP_PKEY_ALG_CTRL + 3) +# define EVP_PKEY_CTRL_DH_PARAMGEN_SUBPRIME_LEN (EVP_PKEY_ALG_CTRL + 4) +# define EVP_PKEY_CTRL_DH_PARAMGEN_TYPE (EVP_PKEY_ALG_CTRL + 5) +# define EVP_PKEY_CTRL_DH_KDF_TYPE (EVP_PKEY_ALG_CTRL + 6) +# define EVP_PKEY_CTRL_DH_KDF_MD (EVP_PKEY_ALG_CTRL + 7) +# define EVP_PKEY_CTRL_GET_DH_KDF_MD (EVP_PKEY_ALG_CTRL + 8) +# define EVP_PKEY_CTRL_DH_KDF_OUTLEN (EVP_PKEY_ALG_CTRL + 9) +# define EVP_PKEY_CTRL_GET_DH_KDF_OUTLEN (EVP_PKEY_ALG_CTRL + 10) +# define EVP_PKEY_CTRL_DH_KDF_UKM (EVP_PKEY_ALG_CTRL + 11) +# define EVP_PKEY_CTRL_GET_DH_KDF_UKM (EVP_PKEY_ALG_CTRL + 12) +# define EVP_PKEY_CTRL_DH_KDF_OID (EVP_PKEY_ALG_CTRL + 13) +# define EVP_PKEY_CTRL_GET_DH_KDF_OID (EVP_PKEY_ALG_CTRL + 14) +# define EVP_PKEY_CTRL_DH_NID (EVP_PKEY_ALG_CTRL + 15) +# define EVP_PKEY_CTRL_DH_PAD (EVP_PKEY_ALG_CTRL + 16) + +/* KDF types */ +# define EVP_PKEY_DH_KDF_NONE 1 +# ifndef OPENSSL_NO_CMS +# define EVP_PKEY_DH_KDF_X9_42 2 +# endif + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/dherr.h b/thrid-party/openssl/openssl/dherr.h new file mode 100644 index 0000000000..916b3bed0b --- /dev/null +++ b/thrid-party/openssl/openssl/dherr.h @@ -0,0 +1,88 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DHERR_H +# define HEADER_DHERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_DH + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_DH_strings(void); + +/* + * DH function codes. + */ +# define DH_F_COMPUTE_KEY 102 +# define DH_F_DHPARAMS_PRINT_FP 101 +# define DH_F_DH_BUILTIN_GENPARAMS 106 +# define DH_F_DH_CHECK_EX 121 +# define DH_F_DH_CHECK_PARAMS_EX 122 +# define DH_F_DH_CHECK_PUB_KEY_EX 123 +# define DH_F_DH_CMS_DECRYPT 114 +# define DH_F_DH_CMS_SET_PEERKEY 115 +# define DH_F_DH_CMS_SET_SHARED_INFO 116 +# define DH_F_DH_METH_DUP 117 +# define DH_F_DH_METH_NEW 118 +# define DH_F_DH_METH_SET1_NAME 119 +# define DH_F_DH_NEW_BY_NID 104 +# define DH_F_DH_NEW_METHOD 105 +# define DH_F_DH_PARAM_DECODE 107 +# define DH_F_DH_PKEY_PUBLIC_CHECK 124 +# define DH_F_DH_PRIV_DECODE 110 +# define DH_F_DH_PRIV_ENCODE 111 +# define DH_F_DH_PUB_DECODE 108 +# define DH_F_DH_PUB_ENCODE 109 +# define DH_F_DO_DH_PRINT 100 +# define DH_F_GENERATE_KEY 103 +# define DH_F_PKEY_DH_CTRL_STR 120 +# define DH_F_PKEY_DH_DERIVE 112 +# define DH_F_PKEY_DH_INIT 125 +# define DH_F_PKEY_DH_KEYGEN 113 + +/* + * DH reason codes. + */ +# define DH_R_BAD_GENERATOR 101 +# define DH_R_BN_DECODE_ERROR 109 +# define DH_R_BN_ERROR 106 +# define DH_R_CHECK_INVALID_J_VALUE 115 +# define DH_R_CHECK_INVALID_Q_VALUE 116 +# define DH_R_CHECK_PUBKEY_INVALID 122 +# define DH_R_CHECK_PUBKEY_TOO_LARGE 123 +# define DH_R_CHECK_PUBKEY_TOO_SMALL 124 +# define DH_R_CHECK_P_NOT_PRIME 117 +# define DH_R_CHECK_P_NOT_SAFE_PRIME 118 +# define DH_R_CHECK_Q_NOT_PRIME 119 +# define DH_R_DECODE_ERROR 104 +# define DH_R_INVALID_PARAMETER_NAME 110 +# define DH_R_INVALID_PARAMETER_NID 114 +# define DH_R_INVALID_PUBKEY 102 +# define DH_R_KDF_PARAMETER_ERROR 112 +# define DH_R_KEYS_NOT_SET 108 +# define DH_R_MISSING_PUBKEY 125 +# define DH_R_MODULUS_TOO_LARGE 103 +# define DH_R_NOT_SUITABLE_GENERATOR 120 +# define DH_R_NO_PARAMETERS_SET 107 +# define DH_R_NO_PRIVATE_VALUE 100 +# define DH_R_PARAMETER_ENCODING_ERROR 105 +# define DH_R_PEER_KEY_ERROR 111 +# define DH_R_SHARED_INFO_ERROR 113 +# define DH_R_UNABLE_TO_CHECK_GENERATOR 121 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/dsa.h b/thrid-party/openssl/openssl/dsa.h new file mode 100644 index 0000000000..822eff347a --- /dev/null +++ b/thrid-party/openssl/openssl/dsa.h @@ -0,0 +1,238 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DSA_H +# define HEADER_DSA_H + +# include + +# ifndef OPENSSL_NO_DSA +# ifdef __cplusplus +extern "C" { +# endif +# include +# include +# include +# include +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif +# include + +# ifndef OPENSSL_DSA_MAX_MODULUS_BITS +# define OPENSSL_DSA_MAX_MODULUS_BITS 10000 +# endif + +# define OPENSSL_DSA_FIPS_MIN_MODULUS_BITS 1024 + +# define DSA_FLAG_CACHE_MONT_P 0x01 +# if OPENSSL_API_COMPAT < 0x10100000L +/* + * Does nothing. Previously this switched off constant time behaviour. + */ +# define DSA_FLAG_NO_EXP_CONSTTIME 0x00 +# endif + +/* + * If this flag is set the DSA method is FIPS compliant and can be used in + * FIPS mode. This is set in the validated module method. If an application + * sets this flag in its own methods it is its responsibility to ensure the + * result is compliant. + */ + +# define DSA_FLAG_FIPS_METHOD 0x0400 + +/* + * If this flag is set the operations normally disabled in FIPS mode are + * permitted it is then the applications responsibility to ensure that the + * usage is compliant. + */ + +# define DSA_FLAG_NON_FIPS_ALLOW 0x0400 +# define DSA_FLAG_FIPS_CHECKED 0x0800 + +/* Already defined in ossl_typ.h */ +/* typedef struct dsa_st DSA; */ +/* typedef struct dsa_method DSA_METHOD; */ + +typedef struct DSA_SIG_st DSA_SIG; + +# define d2i_DSAparams_fp(fp,x) (DSA *)ASN1_d2i_fp((char *(*)())DSA_new, \ + (char *(*)())d2i_DSAparams,(fp),(unsigned char **)(x)) +# define i2d_DSAparams_fp(fp,x) ASN1_i2d_fp(i2d_DSAparams,(fp), \ + (unsigned char *)(x)) +# define d2i_DSAparams_bio(bp,x) ASN1_d2i_bio_of(DSA,DSA_new,d2i_DSAparams,bp,x) +# define i2d_DSAparams_bio(bp,x) ASN1_i2d_bio_of_const(DSA,i2d_DSAparams,bp,x) + +DSA *DSAparams_dup(DSA *x); +DSA_SIG *DSA_SIG_new(void); +void DSA_SIG_free(DSA_SIG *a); +int i2d_DSA_SIG(const DSA_SIG *a, unsigned char **pp); +DSA_SIG *d2i_DSA_SIG(DSA_SIG **v, const unsigned char **pp, long length); +void DSA_SIG_get0(const DSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); +int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s); + +DSA_SIG *DSA_do_sign(const unsigned char *dgst, int dlen, DSA *dsa); +int DSA_do_verify(const unsigned char *dgst, int dgst_len, + DSA_SIG *sig, DSA *dsa); + +const DSA_METHOD *DSA_OpenSSL(void); + +void DSA_set_default_method(const DSA_METHOD *); +const DSA_METHOD *DSA_get_default_method(void); +int DSA_set_method(DSA *dsa, const DSA_METHOD *); +const DSA_METHOD *DSA_get_method(DSA *d); + +DSA *DSA_new(void); +DSA *DSA_new_method(ENGINE *engine); +void DSA_free(DSA *r); +/* "up" the DSA object's reference count */ +int DSA_up_ref(DSA *r); +int DSA_size(const DSA *); +int DSA_bits(const DSA *d); +int DSA_security_bits(const DSA *d); + /* next 4 return -1 on error */ +DEPRECATEDIN_1_2_0(int DSA_sign_setup(DSA *dsa, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)) +int DSA_sign(int type, const unsigned char *dgst, int dlen, + unsigned char *sig, unsigned int *siglen, DSA *dsa); +int DSA_verify(int type, const unsigned char *dgst, int dgst_len, + const unsigned char *sigbuf, int siglen, DSA *dsa); +#define DSA_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_DSA, l, p, newf, dupf, freef) +int DSA_set_ex_data(DSA *d, int idx, void *arg); +void *DSA_get_ex_data(DSA *d, int idx); + +DSA *d2i_DSAPublicKey(DSA **a, const unsigned char **pp, long length); +DSA *d2i_DSAPrivateKey(DSA **a, const unsigned char **pp, long length); +DSA *d2i_DSAparams(DSA **a, const unsigned char **pp, long length); + +/* Deprecated version */ +DEPRECATEDIN_0_9_8(DSA *DSA_generate_parameters(int bits, + unsigned char *seed, + int seed_len, + int *counter_ret, + unsigned long *h_ret, void + (*callback) (int, int, + void *), + void *cb_arg)) + +/* New version */ +int DSA_generate_parameters_ex(DSA *dsa, int bits, + const unsigned char *seed, int seed_len, + int *counter_ret, unsigned long *h_ret, + BN_GENCB *cb); + +int DSA_generate_key(DSA *a); +int i2d_DSAPublicKey(const DSA *a, unsigned char **pp); +int i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); +int i2d_DSAparams(const DSA *a, unsigned char **pp); + +int DSAparams_print(BIO *bp, const DSA *x); +int DSA_print(BIO *bp, const DSA *x, int off); +# ifndef OPENSSL_NO_STDIO +int DSAparams_print_fp(FILE *fp, const DSA *x); +int DSA_print_fp(FILE *bp, const DSA *x, int off); +# endif + +# define DSS_prime_checks 64 +/* + * Primality test according to FIPS PUB 186-4, Appendix C.3. Since we only + * have one value here we set the number of checks to 64 which is the 128 bit + * security level that is the highest level and valid for creating a 3072 bit + * DSA key. + */ +# define DSA_is_prime(n, callback, cb_arg) \ + BN_is_prime(n, DSS_prime_checks, callback, NULL, cb_arg) + +# ifndef OPENSSL_NO_DH +/* + * Convert DSA structure (key or just parameters) into DH structure (be + * careful to avoid small subgroup attacks when using this!) + */ +DH *DSA_dup_DH(const DSA *r); +# endif + +# define EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, nbits) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \ + EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, nbits, NULL) + +# define EVP_PKEY_CTRL_DSA_PARAMGEN_BITS (EVP_PKEY_ALG_CTRL + 1) +# define EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS (EVP_PKEY_ALG_CTRL + 2) +# define EVP_PKEY_CTRL_DSA_PARAMGEN_MD (EVP_PKEY_ALG_CTRL + 3) + +void DSA_get0_pqg(const DSA *d, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); +void DSA_get0_key(const DSA *d, + const BIGNUM **pub_key, const BIGNUM **priv_key); +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); +const BIGNUM *DSA_get0_p(const DSA *d); +const BIGNUM *DSA_get0_q(const DSA *d); +const BIGNUM *DSA_get0_g(const DSA *d); +const BIGNUM *DSA_get0_pub_key(const DSA *d); +const BIGNUM *DSA_get0_priv_key(const DSA *d); +void DSA_clear_flags(DSA *d, int flags); +int DSA_test_flags(const DSA *d, int flags); +void DSA_set_flags(DSA *d, int flags); +ENGINE *DSA_get0_engine(DSA *d); + +DSA_METHOD *DSA_meth_new(const char *name, int flags); +void DSA_meth_free(DSA_METHOD *dsam); +DSA_METHOD *DSA_meth_dup(const DSA_METHOD *dsam); +const char *DSA_meth_get0_name(const DSA_METHOD *dsam); +int DSA_meth_set1_name(DSA_METHOD *dsam, const char *name); +int DSA_meth_get_flags(const DSA_METHOD *dsam); +int DSA_meth_set_flags(DSA_METHOD *dsam, int flags); +void *DSA_meth_get0_app_data(const DSA_METHOD *dsam); +int DSA_meth_set0_app_data(DSA_METHOD *dsam, void *app_data); +DSA_SIG *(*DSA_meth_get_sign(const DSA_METHOD *dsam)) + (const unsigned char *, int, DSA *); +int DSA_meth_set_sign(DSA_METHOD *dsam, + DSA_SIG *(*sign) (const unsigned char *, int, DSA *)); +int (*DSA_meth_get_sign_setup(const DSA_METHOD *dsam)) + (DSA *, BN_CTX *, BIGNUM **, BIGNUM **); +int DSA_meth_set_sign_setup(DSA_METHOD *dsam, + int (*sign_setup) (DSA *, BN_CTX *, BIGNUM **, BIGNUM **)); +int (*DSA_meth_get_verify(const DSA_METHOD *dsam)) + (const unsigned char *, int, DSA_SIG *, DSA *); +int DSA_meth_set_verify(DSA_METHOD *dsam, + int (*verify) (const unsigned char *, int, DSA_SIG *, DSA *)); +int (*DSA_meth_get_mod_exp(const DSA_METHOD *dsam)) + (DSA *, BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, const BIGNUM *, BN_CTX *, BN_MONT_CTX *); +int DSA_meth_set_mod_exp(DSA_METHOD *dsam, + int (*mod_exp) (DSA *, BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *, + BN_MONT_CTX *)); +int (*DSA_meth_get_bn_mod_exp(const DSA_METHOD *dsam)) + (DSA *, BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *, BN_MONT_CTX *); +int DSA_meth_set_bn_mod_exp(DSA_METHOD *dsam, + int (*bn_mod_exp) (DSA *, BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, BN_CTX *, BN_MONT_CTX *)); +int (*DSA_meth_get_init(const DSA_METHOD *dsam))(DSA *); +int DSA_meth_set_init(DSA_METHOD *dsam, int (*init)(DSA *)); +int (*DSA_meth_get_finish(const DSA_METHOD *dsam)) (DSA *); +int DSA_meth_set_finish(DSA_METHOD *dsam, int (*finish) (DSA *)); +int (*DSA_meth_get_paramgen(const DSA_METHOD *dsam)) + (DSA *, int, const unsigned char *, int, int *, unsigned long *, + BN_GENCB *); +int DSA_meth_set_paramgen(DSA_METHOD *dsam, + int (*paramgen) (DSA *, int, const unsigned char *, int, int *, + unsigned long *, BN_GENCB *)); +int (*DSA_meth_get_keygen(const DSA_METHOD *dsam)) (DSA *); +int DSA_meth_set_keygen(DSA_METHOD *dsam, int (*keygen) (DSA *)); + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/dsaerr.h b/thrid-party/openssl/openssl/dsaerr.h new file mode 100644 index 0000000000..495a1ac89d --- /dev/null +++ b/thrid-party/openssl/openssl/dsaerr.h @@ -0,0 +1,72 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DSAERR_H +# define HEADER_DSAERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_DSA + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_DSA_strings(void); + +/* + * DSA function codes. + */ +# define DSA_F_DSAPARAMS_PRINT 100 +# define DSA_F_DSAPARAMS_PRINT_FP 101 +# define DSA_F_DSA_BUILTIN_PARAMGEN 125 +# define DSA_F_DSA_BUILTIN_PARAMGEN2 126 +# define DSA_F_DSA_DO_SIGN 112 +# define DSA_F_DSA_DO_VERIFY 113 +# define DSA_F_DSA_METH_DUP 127 +# define DSA_F_DSA_METH_NEW 128 +# define DSA_F_DSA_METH_SET1_NAME 129 +# define DSA_F_DSA_NEW_METHOD 103 +# define DSA_F_DSA_PARAM_DECODE 119 +# define DSA_F_DSA_PRINT_FP 105 +# define DSA_F_DSA_PRIV_DECODE 115 +# define DSA_F_DSA_PRIV_ENCODE 116 +# define DSA_F_DSA_PUB_DECODE 117 +# define DSA_F_DSA_PUB_ENCODE 118 +# define DSA_F_DSA_SIGN 106 +# define DSA_F_DSA_SIGN_SETUP 107 +# define DSA_F_DSA_SIG_NEW 102 +# define DSA_F_OLD_DSA_PRIV_DECODE 122 +# define DSA_F_PKEY_DSA_CTRL 120 +# define DSA_F_PKEY_DSA_CTRL_STR 104 +# define DSA_F_PKEY_DSA_KEYGEN 121 + +/* + * DSA reason codes. + */ +# define DSA_R_BAD_Q_VALUE 102 +# define DSA_R_BN_DECODE_ERROR 108 +# define DSA_R_BN_ERROR 109 +# define DSA_R_DECODE_ERROR 104 +# define DSA_R_INVALID_DIGEST_TYPE 106 +# define DSA_R_INVALID_PARAMETERS 112 +# define DSA_R_MISSING_PARAMETERS 101 +# define DSA_R_MISSING_PRIVATE_KEY 111 +# define DSA_R_MODULUS_TOO_LARGE 103 +# define DSA_R_NO_PARAMETERS_SET 107 +# define DSA_R_PARAMETER_ENCODING_ERROR 105 +# define DSA_R_Q_NOT_PRIME 113 +# define DSA_R_SEED_LEN_SMALL 110 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/dso.h b/thrid-party/openssl/openssl/dso.h new file mode 100644 index 0000000000..c9013f5cea --- /dev/null +++ b/thrid-party/openssl/openssl/dso.h @@ -0,0 +1,451 @@ +/* dso.h */ +/* + * Written by Geoff Thorpe (geoff@geoffthorpe.net) for the OpenSSL project + * 2000. + */ +/* ==================================================================== + * Copyright (c) 2000 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#ifndef HEADER_DSO_H +# define HEADER_DSO_H + +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* These values are used as commands to DSO_ctrl() */ +# define DSO_CTRL_GET_FLAGS 1 +# define DSO_CTRL_SET_FLAGS 2 +# define DSO_CTRL_OR_FLAGS 3 + +/* + * By default, DSO_load() will translate the provided filename into a form + * typical for the platform (more specifically the DSO_METHOD) using the + * dso_name_converter function of the method. Eg. win32 will transform "blah" + * into "blah.dll", and dlfcn will transform it into "libblah.so". The + * behaviour can be overriden by setting the name_converter callback in the + * DSO object (using DSO_set_name_converter()). This callback could even + * utilise the DSO_METHOD's converter too if it only wants to override + * behaviour for one or two possible DSO methods. However, the following flag + * can be set in a DSO to prevent *any* native name-translation at all - eg. + * if the caller has prompted the user for a path to a driver library so the + * filename should be interpreted as-is. + */ +# define DSO_FLAG_NO_NAME_TRANSLATION 0x01 +/* + * An extra flag to give if only the extension should be added as + * translation. This is obviously only of importance on Unix and other + * operating systems where the translation also may prefix the name with + * something, like 'lib', and ignored everywhere else. This flag is also + * ignored if DSO_FLAG_NO_NAME_TRANSLATION is used at the same time. + */ +# define DSO_FLAG_NAME_TRANSLATION_EXT_ONLY 0x02 + +/* + * The following flag controls the translation of symbol names to upper case. + * This is currently only being implemented for OpenVMS. + */ +# define DSO_FLAG_UPCASE_SYMBOL 0x10 + +/* + * This flag loads the library with public symbols. Meaning: The exported + * symbols of this library are public to all libraries loaded after this + * library. At the moment only implemented in unix. + */ +# define DSO_FLAG_GLOBAL_SYMBOLS 0x20 + +typedef void (*DSO_FUNC_TYPE) (void); + +typedef struct dso_st DSO; + +/* + * The function prototype used for method functions (or caller-provided + * callbacks) that transform filenames. They are passed a DSO structure + * pointer (or NULL if they are to be used independantly of a DSO object) and + * a filename to transform. They should either return NULL (if there is an + * error condition) or a newly allocated string containing the transformed + * form that the caller will need to free with OPENSSL_free() when done. + */ +typedef char *(*DSO_NAME_CONVERTER_FUNC)(DSO *, const char *); +/* + * The function prototype used for method functions (or caller-provided + * callbacks) that merge two file specifications. They are passed a DSO + * structure pointer (or NULL if they are to be used independantly of a DSO + * object) and two file specifications to merge. They should either return + * NULL (if there is an error condition) or a newly allocated string + * containing the result of merging that the caller will need to free with + * OPENSSL_free() when done. Here, merging means that bits and pieces are + * taken from each of the file specifications and added together in whatever + * fashion that is sensible for the DSO method in question. The only rule + * that really applies is that if the two specification contain pieces of the + * same type, the copy from the first string takes priority. One could see + * it as the first specification is the one given by the user and the second + * being a bunch of defaults to add on if they're missing in the first. + */ +typedef char *(*DSO_MERGER_FUNC)(DSO *, const char *, const char *); + +typedef struct dso_meth_st { + const char *name; + /* + * Loads a shared library, NB: new DSO_METHODs must ensure that a + * successful load populates the loaded_filename field, and likewise a + * successful unload OPENSSL_frees and NULLs it out. + */ + int (*dso_load) (DSO *dso); + /* Unloads a shared library */ + int (*dso_unload) (DSO *dso); + /* Binds a variable */ + void *(*dso_bind_var) (DSO *dso, const char *symname); + /* + * Binds a function - assumes a return type of DSO_FUNC_TYPE. This should + * be cast to the real function prototype by the caller. Platforms that + * don't have compatible representations for different prototypes (this + * is possible within ANSI C) are highly unlikely to have shared + * libraries at all, let alone a DSO_METHOD implemented for them. + */ + DSO_FUNC_TYPE (*dso_bind_func) (DSO *dso, const char *symname); +/* I don't think this would actually be used in any circumstances. */ +# if 0 + /* Unbinds a variable */ + int (*dso_unbind_var) (DSO *dso, char *symname, void *symptr); + /* Unbinds a function */ + int (*dso_unbind_func) (DSO *dso, char *symname, DSO_FUNC_TYPE symptr); +# endif + /* + * The generic (yuck) "ctrl()" function. NB: Negative return values + * (rather than zero) indicate errors. + */ + long (*dso_ctrl) (DSO *dso, int cmd, long larg, void *parg); + /* + * The default DSO_METHOD-specific function for converting filenames to a + * canonical native form. + */ + DSO_NAME_CONVERTER_FUNC dso_name_converter; + /* + * The default DSO_METHOD-specific function for converting filenames to a + * canonical native form. + */ + DSO_MERGER_FUNC dso_merger; + /* [De]Initialisation handlers. */ + int (*init) (DSO *dso); + int (*finish) (DSO *dso); + /* Return pathname of the module containing location */ + int (*pathbyaddr) (void *addr, char *path, int sz); + /* Perform global symbol lookup, i.e. among *all* modules */ + void *(*globallookup) (const char *symname); +} DSO_METHOD; + +/**********************************************************************/ +/* The low-level handle type used to refer to a loaded shared library */ + +struct dso_st { + DSO_METHOD *meth; + /* + * Standard dlopen uses a (void *). Win32 uses a HANDLE. VMS doesn't use + * anything but will need to cache the filename for use in the dso_bind + * handler. All in all, let each method control its own destiny. + * "Handles" and such go in a STACK. + */ + STACK_OF(void) *meth_data; + int references; + int flags; + /* + * For use by applications etc ... use this for your bits'n'pieces, don't + * touch meth_data! + */ + CRYPTO_EX_DATA ex_data; + /* + * If this callback function pointer is set to non-NULL, then it will be + * used in DSO_load() in place of meth->dso_name_converter. NB: This + * should normally set using DSO_set_name_converter(). + */ + DSO_NAME_CONVERTER_FUNC name_converter; + /* + * If this callback function pointer is set to non-NULL, then it will be + * used in DSO_load() in place of meth->dso_merger. NB: This should + * normally set using DSO_set_merger(). + */ + DSO_MERGER_FUNC merger; + /* + * This is populated with (a copy of) the platform-independant filename + * used for this DSO. + */ + char *filename; + /* + * This is populated with (a copy of) the translated filename by which + * the DSO was actually loaded. It is NULL iff the DSO is not currently + * loaded. NB: This is here because the filename translation process may + * involve a callback being invoked more than once not only to convert to + * a platform-specific form, but also to try different filenames in the + * process of trying to perform a load. As such, this variable can be + * used to indicate (a) whether this DSO structure corresponds to a + * loaded library or not, and (b) the filename with which it was actually + * loaded. + */ + char *loaded_filename; +}; + +DSO *DSO_new(void); +DSO *DSO_new_method(DSO_METHOD *method); +int DSO_free(DSO *dso); +int DSO_flags(DSO *dso); +int DSO_up_ref(DSO *dso); +long DSO_ctrl(DSO *dso, int cmd, long larg, void *parg); + +/* + * This function sets the DSO's name_converter callback. If it is non-NULL, + * then it will be used instead of the associated DSO_METHOD's function. If + * oldcb is non-NULL then it is set to the function pointer value being + * replaced. Return value is non-zero for success. + */ +int DSO_set_name_converter(DSO *dso, DSO_NAME_CONVERTER_FUNC cb, + DSO_NAME_CONVERTER_FUNC *oldcb); +/* + * These functions can be used to get/set the platform-independant filename + * used for a DSO. NB: set will fail if the DSO is already loaded. + */ +const char *DSO_get_filename(DSO *dso); +int DSO_set_filename(DSO *dso, const char *filename); +/* + * This function will invoke the DSO's name_converter callback to translate a + * filename, or if the callback isn't set it will instead use the DSO_METHOD's + * converter. If "filename" is NULL, the "filename" in the DSO itself will be + * used. If the DSO_FLAG_NO_NAME_TRANSLATION flag is set, then the filename is + * simply duplicated. NB: This function is usually called from within a + * DSO_METHOD during the processing of a DSO_load() call, and is exposed so + * that caller-created DSO_METHODs can do the same thing. A non-NULL return + * value will need to be OPENSSL_free()'d. + */ +char *DSO_convert_filename(DSO *dso, const char *filename); +/* + * This function will invoke the DSO's merger callback to merge two file + * specifications, or if the callback isn't set it will instead use the + * DSO_METHOD's merger. A non-NULL return value will need to be + * OPENSSL_free()'d. + */ +char *DSO_merge(DSO *dso, const char *filespec1, const char *filespec2); +/* + * If the DSO is currently loaded, this returns the filename that it was + * loaded under, otherwise it returns NULL. So it is also useful as a test as + * to whether the DSO is currently loaded. NB: This will not necessarily + * return the same value as DSO_convert_filename(dso, dso->filename), because + * the DSO_METHOD's load function may have tried a variety of filenames (with + * and/or without the aid of the converters) before settling on the one it + * actually loaded. + */ +const char *DSO_get_loaded_filename(DSO *dso); + +void DSO_set_default_method(DSO_METHOD *meth); +DSO_METHOD *DSO_get_default_method(void); +DSO_METHOD *DSO_get_method(DSO *dso); +DSO_METHOD *DSO_set_method(DSO *dso, DSO_METHOD *meth); + +/* + * The all-singing all-dancing load function, you normally pass NULL for the + * first and third parameters. Use DSO_up and DSO_free for subsequent + * reference count handling. Any flags passed in will be set in the + * constructed DSO after its init() function but before the load operation. + * If 'dso' is non-NULL, 'flags' is ignored. + */ +DSO *DSO_load(DSO *dso, const char *filename, DSO_METHOD *meth, int flags); + +/* This function binds to a variable inside a shared library. */ +void *DSO_bind_var(DSO *dso, const char *symname); + +/* This function binds to a function inside a shared library. */ +DSO_FUNC_TYPE DSO_bind_func(DSO *dso, const char *symname); + +/* + * This method is the default, but will beg, borrow, or steal whatever method + * should be the default on any particular platform (including + * DSO_METH_null() if necessary). + */ +DSO_METHOD *DSO_METHOD_openssl(void); + +/* + * This method is defined for all platforms - if a platform has no DSO + * support then this will be the only method! + */ +DSO_METHOD *DSO_METHOD_null(void); + +/* + * If DSO_DLFCN is defined, the standard dlfcn.h-style functions (dlopen, + * dlclose, dlsym, etc) will be used and incorporated into this method. If + * not, this method will return NULL. + */ +DSO_METHOD *DSO_METHOD_dlfcn(void); + +/* + * If DSO_DL is defined, the standard dl.h-style functions (shl_load, + * shl_unload, shl_findsym, etc) will be used and incorporated into this + * method. If not, this method will return NULL. + */ +DSO_METHOD *DSO_METHOD_dl(void); + +/* If WIN32 is defined, use DLLs. If not, return NULL. */ +DSO_METHOD *DSO_METHOD_win32(void); + +/* If VMS is defined, use shared images. If not, return NULL. */ +DSO_METHOD *DSO_METHOD_vms(void); + +/* + * This function writes null-terminated pathname of DSO module containing + * 'addr' into 'sz' large caller-provided 'path' and returns the number of + * characters [including trailing zero] written to it. If 'sz' is 0 or + * negative, 'path' is ignored and required amount of charachers [including + * trailing zero] to accomodate pathname is returned. If 'addr' is NULL, then + * pathname of cryptolib itself is returned. Negative or zero return value + * denotes error. + */ +int DSO_pathbyaddr(void *addr, char *path, int sz); + +/* + * This function should be used with caution! It looks up symbols in *all* + * loaded modules and if module gets unloaded by somebody else attempt to + * dereference the pointer is doomed to have fatal consequences. Primary + * usage for this function is to probe *core* system functionality, e.g. + * check if getnameinfo(3) is available at run-time without bothering about + * OS-specific details such as libc.so.versioning or where does it actually + * reside: in libc itself or libsocket. + */ +void *DSO_global_lookup(const char *name); + +/* If BeOS is defined, use shared images. If not, return NULL. */ +DSO_METHOD *DSO_METHOD_beos(void); + +/* BEGIN ERROR CODES */ +/* + * The following lines are auto generated by the script mkerr.pl. Any changes + * made after this point may be overwritten when the script is next run. + */ +void ERR_load_DSO_strings(void); + +/* Error codes for the DSO functions. */ + +/* Function codes. */ +# define DSO_F_BEOS_BIND_FUNC 144 +# define DSO_F_BEOS_BIND_VAR 145 +# define DSO_F_BEOS_LOAD 146 +# define DSO_F_BEOS_NAME_CONVERTER 147 +# define DSO_F_BEOS_UNLOAD 148 +# define DSO_F_DLFCN_BIND_FUNC 100 +# define DSO_F_DLFCN_BIND_VAR 101 +# define DSO_F_DLFCN_LOAD 102 +# define DSO_F_DLFCN_MERGER 130 +# define DSO_F_DLFCN_NAME_CONVERTER 123 +# define DSO_F_DLFCN_UNLOAD 103 +# define DSO_F_DL_BIND_FUNC 104 +# define DSO_F_DL_BIND_VAR 105 +# define DSO_F_DL_LOAD 106 +# define DSO_F_DL_MERGER 131 +# define DSO_F_DL_NAME_CONVERTER 124 +# define DSO_F_DL_UNLOAD 107 +# define DSO_F_DSO_BIND_FUNC 108 +# define DSO_F_DSO_BIND_VAR 109 +# define DSO_F_DSO_CONVERT_FILENAME 126 +# define DSO_F_DSO_CTRL 110 +# define DSO_F_DSO_FREE 111 +# define DSO_F_DSO_GET_FILENAME 127 +# define DSO_F_DSO_GET_LOADED_FILENAME 128 +# define DSO_F_DSO_GLOBAL_LOOKUP 139 +# define DSO_F_DSO_LOAD 112 +# define DSO_F_DSO_MERGE 132 +# define DSO_F_DSO_NEW_METHOD 113 +# define DSO_F_DSO_PATHBYADDR 140 +# define DSO_F_DSO_SET_FILENAME 129 +# define DSO_F_DSO_SET_NAME_CONVERTER 122 +# define DSO_F_DSO_UP_REF 114 +# define DSO_F_GLOBAL_LOOKUP_FUNC 138 +# define DSO_F_PATHBYADDR 137 +# define DSO_F_VMS_BIND_SYM 115 +# define DSO_F_VMS_LOAD 116 +# define DSO_F_VMS_MERGER 133 +# define DSO_F_VMS_UNLOAD 117 +# define DSO_F_WIN32_BIND_FUNC 118 +# define DSO_F_WIN32_BIND_VAR 119 +# define DSO_F_WIN32_GLOBALLOOKUP 142 +# define DSO_F_WIN32_GLOBALLOOKUP_FUNC 143 +# define DSO_F_WIN32_JOINER 135 +# define DSO_F_WIN32_LOAD 120 +# define DSO_F_WIN32_MERGER 134 +# define DSO_F_WIN32_NAME_CONVERTER 125 +# define DSO_F_WIN32_PATHBYADDR 141 +# define DSO_F_WIN32_SPLITTER 136 +# define DSO_F_WIN32_UNLOAD 121 + +/* Reason codes. */ +# define DSO_R_CTRL_FAILED 100 +# define DSO_R_DSO_ALREADY_LOADED 110 +# define DSO_R_EMPTY_FILE_STRUCTURE 113 +# define DSO_R_FAILURE 114 +# define DSO_R_FILENAME_TOO_BIG 101 +# define DSO_R_FINISH_FAILED 102 +# define DSO_R_INCORRECT_FILE_SYNTAX 115 +# define DSO_R_LOAD_FAILED 103 +# define DSO_R_NAME_TRANSLATION_FAILED 109 +# define DSO_R_NO_FILENAME 111 +# define DSO_R_NO_FILE_SPECIFICATION 116 +# define DSO_R_NULL_HANDLE 104 +# define DSO_R_SET_FILENAME_FAILED 112 +# define DSO_R_STACK_ERROR 105 +# define DSO_R_SYM_FAILURE 106 +# define DSO_R_UNLOAD_FAILED 107 +# define DSO_R_UNSUPPORTED 108 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/dtls1.h b/thrid-party/openssl/openssl/dtls1.h new file mode 100644 index 0000000000..a312e386cf --- /dev/null +++ b/thrid-party/openssl/openssl/dtls1.h @@ -0,0 +1,55 @@ +/* + * Copyright 2005-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DTLS1_H +# define HEADER_DTLS1_H + +#ifdef __cplusplus +extern "C" { +#endif + +# define DTLS1_VERSION 0xFEFF +# define DTLS1_2_VERSION 0xFEFD +# define DTLS_MIN_VERSION DTLS1_VERSION +# define DTLS_MAX_VERSION DTLS1_2_VERSION +# define DTLS1_VERSION_MAJOR 0xFE + +# define DTLS1_BAD_VER 0x0100 + +/* Special value for method supporting multiple versions */ +# define DTLS_ANY_VERSION 0x1FFFF + +/* lengths of messages */ +/* + * Actually the max cookie length in DTLS is 255. But we can't change this now + * due to compatibility concerns. + */ +# define DTLS1_COOKIE_LENGTH 256 + +# define DTLS1_RT_HEADER_LENGTH 13 + +# define DTLS1_HM_HEADER_LENGTH 12 + +# define DTLS1_HM_BAD_FRAGMENT -2 +# define DTLS1_HM_FRAGMENT_RETRY -3 + +# define DTLS1_CCS_HEADER_LENGTH 1 + +# define DTLS1_AL_HEADER_LENGTH 2 + +/* Timeout multipliers (timeout slice is defined in apps/timeouts.h */ +# define DTLS1_TMO_READ_COUNT 2 +# define DTLS1_TMO_WRITE_COUNT 2 + +# define DTLS1_TMO_ALERT_COUNT 12 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/e_os2.h b/thrid-party/openssl/openssl/e_os2.h new file mode 100644 index 0000000000..97a776cdac --- /dev/null +++ b/thrid-party/openssl/openssl/e_os2.h @@ -0,0 +1,300 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_E_OS2_H +# define HEADER_E_OS2_H + +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * Detect operating systems. This probably needs completing. + * The result is that at least one OPENSSL_SYS_os macro should be defined. + * However, if none is defined, Unix is assumed. + **/ + +# define OPENSSL_SYS_UNIX + +/* --------------------- Microsoft operating systems ---------------------- */ + +/* + * Note that MSDOS actually denotes 32-bit environments running on top of + * MS-DOS, such as DJGPP one. + */ +# if defined(OPENSSL_SYS_MSDOS) +# undef OPENSSL_SYS_UNIX +# endif + +/* + * For 32 bit environment, there seems to be the CygWin environment and then + * all the others that try to do the same thing Microsoft does... + */ +/* + * UEFI lives here because it might be built with a Microsoft toolchain and + * we need to avoid the false positive match on Windows. + */ +# if defined(OPENSSL_SYS_UEFI) +# undef OPENSSL_SYS_UNIX +# elif defined(OPENSSL_SYS_UWIN) +# undef OPENSSL_SYS_UNIX +# define OPENSSL_SYS_WIN32_UWIN +# else +# if defined(__CYGWIN__) || defined(OPENSSL_SYS_CYGWIN) +# define OPENSSL_SYS_WIN32_CYGWIN +# else +# if defined(_WIN32) || defined(OPENSSL_SYS_WIN32) +# undef OPENSSL_SYS_UNIX +# if !defined(OPENSSL_SYS_WIN32) +# define OPENSSL_SYS_WIN32 +# endif +# endif +# if defined(_WIN64) || defined(OPENSSL_SYS_WIN64) +# undef OPENSSL_SYS_UNIX +# if !defined(OPENSSL_SYS_WIN64) +# define OPENSSL_SYS_WIN64 +# endif +# endif +# if defined(OPENSSL_SYS_WINNT) +# undef OPENSSL_SYS_UNIX +# endif +# if defined(OPENSSL_SYS_WINCE) +# undef OPENSSL_SYS_UNIX +# endif +# endif +# endif + +/* Anything that tries to look like Microsoft is "Windows" */ +# if defined(OPENSSL_SYS_WIN32) || defined(OPENSSL_SYS_WIN64) || defined(OPENSSL_SYS_WINNT) || defined(OPENSSL_SYS_WINCE) +# undef OPENSSL_SYS_UNIX +# define OPENSSL_SYS_WINDOWS +# ifndef OPENSSL_SYS_MSDOS +# define OPENSSL_SYS_MSDOS +# endif +# endif + +/* + * DLL settings. This part is a bit tough, because it's up to the + * application implementor how he or she will link the application, so it + * requires some macro to be used. + */ +# ifdef OPENSSL_SYS_WINDOWS +# ifndef OPENSSL_OPT_WINDLL +# if defined(_WINDLL) /* This is used when building OpenSSL to + * indicate that DLL linkage should be used */ +# define OPENSSL_OPT_WINDLL +# endif +# endif +# endif + +/* ------------------------------- OpenVMS -------------------------------- */ +# if defined(__VMS) || defined(VMS) || defined(OPENSSL_SYS_VMS) +# if !defined(OPENSSL_SYS_VMS) +# undef OPENSSL_SYS_UNIX +# endif +# define OPENSSL_SYS_VMS +# if defined(__DECC) +# define OPENSSL_SYS_VMS_DECC +# elif defined(__DECCXX) +# define OPENSSL_SYS_VMS_DECC +# define OPENSSL_SYS_VMS_DECCXX +# else +# define OPENSSL_SYS_VMS_NODECC +# endif +# endif + +/* -------------------------------- Unix ---------------------------------- */ +# ifdef OPENSSL_SYS_UNIX +# if defined(linux) || defined(__linux__) && !defined(OPENSSL_SYS_LINUX) +# define OPENSSL_SYS_LINUX +# endif +# if defined(_AIX) && !defined(OPENSSL_SYS_AIX) +# define OPENSSL_SYS_AIX +# endif +# endif + +/* -------------------------------- VOS ----------------------------------- */ +# if defined(__VOS__) && !defined(OPENSSL_SYS_VOS) +# define OPENSSL_SYS_VOS +# ifdef __HPPA__ +# define OPENSSL_SYS_VOS_HPPA +# endif +# ifdef __IA32__ +# define OPENSSL_SYS_VOS_IA32 +# endif +# endif + +/** + * That's it for OS-specific stuff + *****************************************************************************/ + +/* Specials for I/O an exit */ +# ifdef OPENSSL_SYS_MSDOS +# define OPENSSL_UNISTD_IO +# define OPENSSL_DECLARE_EXIT extern void exit(int); +# else +# define OPENSSL_UNISTD_IO OPENSSL_UNISTD +# define OPENSSL_DECLARE_EXIT /* declared in unistd.h */ +# endif + +/*- + * OPENSSL_EXTERN is normally used to declare a symbol with possible extra + * attributes to handle its presence in a shared library. + * OPENSSL_EXPORT is used to define a symbol with extra possible attributes + * to make it visible in a shared library. + * Care needs to be taken when a header file is used both to declare and + * define symbols. Basically, for any library that exports some global + * variables, the following code must be present in the header file that + * declares them, before OPENSSL_EXTERN is used: + * + * #ifdef SOME_BUILD_FLAG_MACRO + * # undef OPENSSL_EXTERN + * # define OPENSSL_EXTERN OPENSSL_EXPORT + * #endif + * + * The default is to have OPENSSL_EXPORT and OPENSSL_EXTERN + * have some generally sensible values. + */ + +# if defined(OPENSSL_SYS_WINDOWS) && defined(OPENSSL_OPT_WINDLL) +# define OPENSSL_EXPORT extern __declspec(dllexport) +# define OPENSSL_EXTERN extern __declspec(dllimport) +# else +# define OPENSSL_EXPORT extern +# define OPENSSL_EXTERN extern +# endif + +/*- + * Macros to allow global variables to be reached through function calls when + * required (if a shared library version requires it, for example. + * The way it's done allows definitions like this: + * + * // in foobar.c + * OPENSSL_IMPLEMENT_GLOBAL(int,foobar,0) + * // in foobar.h + * OPENSSL_DECLARE_GLOBAL(int,foobar); + * #define foobar OPENSSL_GLOBAL_REF(foobar) + */ +# ifdef OPENSSL_EXPORT_VAR_AS_FUNCTION +# define OPENSSL_IMPLEMENT_GLOBAL(type,name,value) \ + type *_shadow_##name(void) \ + { static type _hide_##name=value; return &_hide_##name; } +# define OPENSSL_DECLARE_GLOBAL(type,name) type *_shadow_##name(void) +# define OPENSSL_GLOBAL_REF(name) (*(_shadow_##name())) +# else +# define OPENSSL_IMPLEMENT_GLOBAL(type,name,value) type _shadow_##name=value; +# define OPENSSL_DECLARE_GLOBAL(type,name) OPENSSL_EXPORT type _shadow_##name +# define OPENSSL_GLOBAL_REF(name) _shadow_##name +# endif + +# ifdef _WIN32 +# ifdef _WIN64 +# define ossl_ssize_t __int64 +# define OSSL_SSIZE_MAX _I64_MAX +# else +# define ossl_ssize_t int +# define OSSL_SSIZE_MAX INT_MAX +# endif +# endif + +# if defined(OPENSSL_SYS_UEFI) && !defined(ossl_ssize_t) +# define ossl_ssize_t INTN +# define OSSL_SSIZE_MAX MAX_INTN +# endif + +# ifndef ossl_ssize_t +# define ossl_ssize_t ssize_t +# if defined(SSIZE_MAX) +# define OSSL_SSIZE_MAX SSIZE_MAX +# elif defined(_POSIX_SSIZE_MAX) +# define OSSL_SSIZE_MAX _POSIX_SSIZE_MAX +# else +# define OSSL_SSIZE_MAX ((ssize_t)(SIZE_MAX>>1)) +# endif +# endif + +# ifdef DEBUG_UNUSED +# define __owur __attribute__((__warn_unused_result__)) +# else +# define __owur +# endif + +/* Standard integer types */ +# if defined(OPENSSL_SYS_UEFI) +typedef INT8 int8_t; +typedef UINT8 uint8_t; +typedef INT16 int16_t; +typedef UINT16 uint16_t; +typedef INT32 int32_t; +typedef UINT32 uint32_t; +typedef INT64 int64_t; +typedef UINT64 uint64_t; +# elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ + defined(__osf__) || defined(__sgi) || defined(__hpux) || \ + defined(OPENSSL_SYS_VMS) || defined (__OpenBSD__) +# include +# elif defined(_MSC_VER) && _MSC_VER<=1500 +/* + * minimally required typdefs for systems not supporting inttypes.h or + * stdint.h: currently just older VC++ + */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +# else +# include +# endif + +/* ossl_inline: portable inline definition usable in public headers */ +# if !defined(inline) && !defined(__cplusplus) +# if defined(__STDC_VERSION__) && __STDC_VERSION__>=199901L + /* just use inline */ +# define ossl_inline inline +# elif defined(__GNUC__) && __GNUC__>=2 +# define ossl_inline __inline__ +# elif defined(_MSC_VER) + /* + * Visual Studio: inline is available in C++ only, however + * __inline is available for C, see + * http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx + */ +# define ossl_inline __inline +# else +# define ossl_inline +# endif +# else +# define ossl_inline inline +# endif + +# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define ossl_noreturn _Noreturn +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define ossl_noreturn __attribute__((noreturn)) +# else +# define ossl_noreturn +# endif + +/* ossl_unused: portable unused attribute for use in public headers */ +# if defined(__GNUC__) +# define ossl_unused __attribute__((unused)) +# else +# define ossl_unused +# endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/ebcdic.h b/thrid-party/openssl/openssl/ebcdic.h new file mode 100644 index 0000000000..aa01285599 --- /dev/null +++ b/thrid-party/openssl/openssl/ebcdic.h @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_EBCDIC_H +# define HEADER_EBCDIC_H + +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Avoid name clashes with other applications */ +# define os_toascii _openssl_os_toascii +# define os_toebcdic _openssl_os_toebcdic +# define ebcdic2ascii _openssl_ebcdic2ascii +# define ascii2ebcdic _openssl_ascii2ebcdic + +extern const unsigned char os_toascii[256]; +extern const unsigned char os_toebcdic[256]; +void *ebcdic2ascii(void *dest, const void *srce, size_t count); +void *ascii2ebcdic(void *dest, const void *srce, size_t count); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/ec.h b/thrid-party/openssl/openssl/ec.h new file mode 100644 index 0000000000..5af9ebdc7f --- /dev/null +++ b/thrid-party/openssl/openssl/ec.h @@ -0,0 +1,1479 @@ +/* + * Copyright 2002-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_EC_H +# define HEADER_EC_H + +# include + +# ifndef OPENSSL_NO_EC +# include +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif +# include +# ifdef __cplusplus +extern "C" { +# endif + +# ifndef OPENSSL_ECC_MAX_FIELD_BITS +# define OPENSSL_ECC_MAX_FIELD_BITS 661 +# endif + +/** Enum for the point conversion form as defined in X9.62 (ECDSA) + * for the encoding of a elliptic curve point (x,y) */ +typedef enum { + /** the point is encoded as z||x, where the octet z specifies + * which solution of the quadratic equation y is */ + POINT_CONVERSION_COMPRESSED = 2, + /** the point is encoded as z||x||y, where z is the octet 0x04 */ + POINT_CONVERSION_UNCOMPRESSED = 4, + /** the point is encoded as z||x||y, where the octet z specifies + * which solution of the quadratic equation y is */ + POINT_CONVERSION_HYBRID = 6 +} point_conversion_form_t; + +typedef struct ec_method_st EC_METHOD; +typedef struct ec_group_st EC_GROUP; +typedef struct ec_point_st EC_POINT; +typedef struct ecpk_parameters_st ECPKPARAMETERS; +typedef struct ec_parameters_st ECPARAMETERS; + +/********************************************************************/ +/* EC_METHODs for curves over GF(p) */ +/********************************************************************/ + +/** Returns the basic GFp ec methods which provides the basis for the + * optimized methods. + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_simple_method(void); + +/** Returns GFp methods using montgomery multiplication. + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_mont_method(void); + +/** Returns GFp methods using optimized methods for NIST recommended curves + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_nist_method(void); + +# ifndef OPENSSL_NO_EC_NISTP_64_GCC_128 +/** Returns 64-bit optimized methods for nistp224 + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_nistp224_method(void); + +/** Returns 64-bit optimized methods for nistp256 + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_nistp256_method(void); + +/** Returns 64-bit optimized methods for nistp521 + * \return EC_METHOD object + */ +const EC_METHOD *EC_GFp_nistp521_method(void); +# endif + +# ifndef OPENSSL_NO_EC2M +/********************************************************************/ +/* EC_METHOD for curves over GF(2^m) */ +/********************************************************************/ + +/** Returns the basic GF2m ec method + * \return EC_METHOD object + */ +const EC_METHOD *EC_GF2m_simple_method(void); + +# endif + +/********************************************************************/ +/* EC_GROUP functions */ +/********************************************************************/ + +/** Creates a new EC_GROUP object + * \param meth EC_METHOD to use + * \return newly created EC_GROUP object or NULL in case of an error. + */ +EC_GROUP *EC_GROUP_new(const EC_METHOD *meth); + +/** Frees a EC_GROUP object + * \param group EC_GROUP object to be freed. + */ +void EC_GROUP_free(EC_GROUP *group); + +/** Clears and frees a EC_GROUP object + * \param group EC_GROUP object to be cleared and freed. + */ +void EC_GROUP_clear_free(EC_GROUP *group); + +/** Copies EC_GROUP objects. Note: both EC_GROUPs must use the same EC_METHOD. + * \param dst destination EC_GROUP object + * \param src source EC_GROUP object + * \return 1 on success and 0 if an error occurred. + */ +int EC_GROUP_copy(EC_GROUP *dst, const EC_GROUP *src); + +/** Creates a new EC_GROUP object and copies the copies the content + * form src to the newly created EC_KEY object + * \param src source EC_GROUP object + * \return newly created EC_GROUP object or NULL in case of an error. + */ +EC_GROUP *EC_GROUP_dup(const EC_GROUP *src); + +/** Returns the EC_METHOD of the EC_GROUP object. + * \param group EC_GROUP object + * \return EC_METHOD used in this EC_GROUP object. + */ +const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group); + +/** Returns the field type of the EC_METHOD. + * \param meth EC_METHOD object + * \return NID of the underlying field type OID. + */ +int EC_METHOD_get_field_type(const EC_METHOD *meth); + +/** Sets the generator and its order/cofactor of a EC_GROUP object. + * \param group EC_GROUP object + * \param generator EC_POINT object with the generator. + * \param order the order of the group generated by the generator. + * \param cofactor the index of the sub-group generated by the generator + * in the group of all points on the elliptic curve. + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_set_generator(EC_GROUP *group, const EC_POINT *generator, + const BIGNUM *order, const BIGNUM *cofactor); + +/** Returns the generator of a EC_GROUP object. + * \param group EC_GROUP object + * \return the currently used generator (possibly NULL). + */ +const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *group); + +/** Returns the montgomery data for order(Generator) + * \param group EC_GROUP object + * \return the currently used montgomery data (possibly NULL). +*/ +BN_MONT_CTX *EC_GROUP_get_mont_data(const EC_GROUP *group); + +/** Gets the order of a EC_GROUP + * \param group EC_GROUP object + * \param order BIGNUM to which the order is copied + * \param ctx unused + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_get_order(const EC_GROUP *group, BIGNUM *order, BN_CTX *ctx); + +/** Gets the order of an EC_GROUP + * \param group EC_GROUP object + * \return the group order + */ +const BIGNUM *EC_GROUP_get0_order(const EC_GROUP *group); + +/** Gets the number of bits of the order of an EC_GROUP + * \param group EC_GROUP object + * \return number of bits of group order. + */ +int EC_GROUP_order_bits(const EC_GROUP *group); + +/** Gets the cofactor of a EC_GROUP + * \param group EC_GROUP object + * \param cofactor BIGNUM to which the cofactor is copied + * \param ctx unused + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_get_cofactor(const EC_GROUP *group, BIGNUM *cofactor, + BN_CTX *ctx); + +/** Gets the cofactor of an EC_GROUP + * \param group EC_GROUP object + * \return the group cofactor + */ +const BIGNUM *EC_GROUP_get0_cofactor(const EC_GROUP *group); + +/** Sets the name of a EC_GROUP object + * \param group EC_GROUP object + * \param nid NID of the curve name OID + */ +void EC_GROUP_set_curve_name(EC_GROUP *group, int nid); + +/** Returns the curve name of a EC_GROUP object + * \param group EC_GROUP object + * \return NID of the curve name OID or 0 if not set. + */ +int EC_GROUP_get_curve_name(const EC_GROUP *group); + +void EC_GROUP_set_asn1_flag(EC_GROUP *group, int flag); +int EC_GROUP_get_asn1_flag(const EC_GROUP *group); + +void EC_GROUP_set_point_conversion_form(EC_GROUP *group, + point_conversion_form_t form); +point_conversion_form_t EC_GROUP_get_point_conversion_form(const EC_GROUP *); + +unsigned char *EC_GROUP_get0_seed(const EC_GROUP *x); +size_t EC_GROUP_get_seed_len(const EC_GROUP *); +size_t EC_GROUP_set_seed(EC_GROUP *, const unsigned char *, size_t len); + +/** Sets the parameters of a ec curve defined by y^2 = x^3 + a*x + b (for GFp) + * or y^2 + x*y = x^3 + a*x^2 + b (for GF2m) + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM with parameter a of the equation + * \param b BIGNUM with parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_set_curve(EC_GROUP *group, const BIGNUM *p, const BIGNUM *a, + const BIGNUM *b, BN_CTX *ctx); + +/** Gets the parameters of the ec curve defined by y^2 = x^3 + a*x + b (for GFp) + * or y^2 + x*y = x^3 + a*x^2 + b (for GF2m) + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM for parameter a of the equation + * \param b BIGNUM for parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_get_curve(const EC_GROUP *group, BIGNUM *p, BIGNUM *a, BIGNUM *b, + BN_CTX *ctx); + +/** Sets the parameters of an ec curve. Synonym for EC_GROUP_set_curve + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM with parameter a of the equation + * \param b BIGNUM with parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_GROUP_set_curve_GFp(EC_GROUP *group, const BIGNUM *p, + const BIGNUM *a, const BIGNUM *b, + BN_CTX *ctx)) + +/** Gets the parameters of an ec curve. Synonym for EC_GROUP_get_curve + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM for parameter a of the equation + * \param b BIGNUM for parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_GROUP_get_curve_GFp(const EC_GROUP *group, BIGNUM *p, + BIGNUM *a, BIGNUM *b, + BN_CTX *ctx)) + +# ifndef OPENSSL_NO_EC2M +/** Sets the parameter of an ec curve. Synonym for EC_GROUP_set_curve + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM with parameter a of the equation + * \param b BIGNUM with parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_GROUP_set_curve_GF2m(EC_GROUP *group, const BIGNUM *p, + const BIGNUM *a, const BIGNUM *b, + BN_CTX *ctx)) + +/** Gets the parameters of an ec curve. Synonym for EC_GROUP_get_curve + * \param group EC_GROUP object + * \param p BIGNUM with the prime number (GFp) or the polynomial + * defining the underlying field (GF2m) + * \param a BIGNUM for parameter a of the equation + * \param b BIGNUM for parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_GROUP_get_curve_GF2m(const EC_GROUP *group, BIGNUM *p, + BIGNUM *a, BIGNUM *b, + BN_CTX *ctx)) +# endif +/** Returns the number of bits needed to represent a field element + * \param group EC_GROUP object + * \return number of bits needed to represent a field element + */ +int EC_GROUP_get_degree(const EC_GROUP *group); + +/** Checks whether the parameter in the EC_GROUP define a valid ec group + * \param group EC_GROUP object + * \param ctx BN_CTX object (optional) + * \return 1 if group is a valid ec group and 0 otherwise + */ +int EC_GROUP_check(const EC_GROUP *group, BN_CTX *ctx); + +/** Checks whether the discriminant of the elliptic curve is zero or not + * \param group EC_GROUP object + * \param ctx BN_CTX object (optional) + * \return 1 if the discriminant is not zero and 0 otherwise + */ +int EC_GROUP_check_discriminant(const EC_GROUP *group, BN_CTX *ctx); + +/** Compares two EC_GROUP objects + * \param a first EC_GROUP object + * \param b second EC_GROUP object + * \param ctx BN_CTX object (optional) + * \return 0 if the groups are equal, 1 if not, or -1 on error + */ +int EC_GROUP_cmp(const EC_GROUP *a, const EC_GROUP *b, BN_CTX *ctx); + +/* + * EC_GROUP_new_GF*() calls EC_GROUP_new() and EC_GROUP_set_GF*() after + * choosing an appropriate EC_METHOD + */ + +/** Creates a new EC_GROUP object with the specified parameters defined + * over GFp (defined by the equation y^2 = x^3 + a*x + b) + * \param p BIGNUM with the prime number + * \param a BIGNUM with the parameter a of the equation + * \param b BIGNUM with the parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return newly created EC_GROUP object with the specified parameters + */ +EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a, + const BIGNUM *b, BN_CTX *ctx); +# ifndef OPENSSL_NO_EC2M +/** Creates a new EC_GROUP object with the specified parameters defined + * over GF2m (defined by the equation y^2 + x*y = x^3 + a*x^2 + b) + * \param p BIGNUM with the polynomial defining the underlying field + * \param a BIGNUM with the parameter a of the equation + * \param b BIGNUM with the parameter b of the equation + * \param ctx BN_CTX object (optional) + * \return newly created EC_GROUP object with the specified parameters + */ +EC_GROUP *EC_GROUP_new_curve_GF2m(const BIGNUM *p, const BIGNUM *a, + const BIGNUM *b, BN_CTX *ctx); +# endif + +/** Creates a EC_GROUP object with a curve specified by a NID + * \param nid NID of the OID of the curve name + * \return newly created EC_GROUP object with specified curve or NULL + * if an error occurred + */ +EC_GROUP *EC_GROUP_new_by_curve_name(int nid); + +/** Creates a new EC_GROUP object from an ECPARAMETERS object + * \param params pointer to the ECPARAMETERS object + * \return newly created EC_GROUP object with specified curve or NULL + * if an error occurred + */ +EC_GROUP *EC_GROUP_new_from_ecparameters(const ECPARAMETERS *params); + +/** Creates an ECPARAMETERS object for the given EC_GROUP object. + * \param group pointer to the EC_GROUP object + * \param params pointer to an existing ECPARAMETERS object or NULL + * \return pointer to the new ECPARAMETERS object or NULL + * if an error occurred. + */ +ECPARAMETERS *EC_GROUP_get_ecparameters(const EC_GROUP *group, + ECPARAMETERS *params); + +/** Creates a new EC_GROUP object from an ECPKPARAMETERS object + * \param params pointer to an existing ECPKPARAMETERS object, or NULL + * \return newly created EC_GROUP object with specified curve, or NULL + * if an error occurred + */ +EC_GROUP *EC_GROUP_new_from_ecpkparameters(const ECPKPARAMETERS *params); + +/** Creates an ECPKPARAMETERS object for the given EC_GROUP object. + * \param group pointer to the EC_GROUP object + * \param params pointer to an existing ECPKPARAMETERS object or NULL + * \return pointer to the new ECPKPARAMETERS object or NULL + * if an error occurred. + */ +ECPKPARAMETERS *EC_GROUP_get_ecpkparameters(const EC_GROUP *group, + ECPKPARAMETERS *params); + +/********************************************************************/ +/* handling of internal curves */ +/********************************************************************/ + +typedef struct { + int nid; + const char *comment; +} EC_builtin_curve; + +/* + * EC_builtin_curves(EC_builtin_curve *r, size_t size) returns number of all + * available curves or zero if a error occurred. In case r is not zero, + * nitems EC_builtin_curve structures are filled with the data of the first + * nitems internal groups + */ +size_t EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); + +const char *EC_curve_nid2nist(int nid); +int EC_curve_nist2nid(const char *name); + +/********************************************************************/ +/* EC_POINT functions */ +/********************************************************************/ + +/** Creates a new EC_POINT object for the specified EC_GROUP + * \param group EC_GROUP the underlying EC_GROUP object + * \return newly created EC_POINT object or NULL if an error occurred + */ +EC_POINT *EC_POINT_new(const EC_GROUP *group); + +/** Frees a EC_POINT object + * \param point EC_POINT object to be freed + */ +void EC_POINT_free(EC_POINT *point); + +/** Clears and frees a EC_POINT object + * \param point EC_POINT object to be cleared and freed + */ +void EC_POINT_clear_free(EC_POINT *point); + +/** Copies EC_POINT object + * \param dst destination EC_POINT object + * \param src source EC_POINT object + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_copy(EC_POINT *dst, const EC_POINT *src); + +/** Creates a new EC_POINT object and copies the content of the supplied + * EC_POINT + * \param src source EC_POINT object + * \param group underlying the EC_GROUP object + * \return newly created EC_POINT object or NULL if an error occurred + */ +EC_POINT *EC_POINT_dup(const EC_POINT *src, const EC_GROUP *group); + +/** Returns the EC_METHOD used in EC_POINT object + * \param point EC_POINT object + * \return the EC_METHOD used + */ +const EC_METHOD *EC_POINT_method_of(const EC_POINT *point); + +/** Sets a point to infinity (neutral element) + * \param group underlying EC_GROUP object + * \param point EC_POINT to set to infinity + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_set_to_infinity(const EC_GROUP *group, EC_POINT *point); + +/** Sets the jacobian projective coordinates of a EC_POINT over GFp + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with the x-coordinate + * \param y BIGNUM with the y-coordinate + * \param z BIGNUM with the z-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_set_Jprojective_coordinates_GFp(const EC_GROUP *group, + EC_POINT *p, const BIGNUM *x, + const BIGNUM *y, const BIGNUM *z, + BN_CTX *ctx); + +/** Gets the jacobian projective coordinates of a EC_POINT over GFp + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM for the x-coordinate + * \param y BIGNUM for the y-coordinate + * \param z BIGNUM for the z-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *group, + const EC_POINT *p, BIGNUM *x, + BIGNUM *y, BIGNUM *z, + BN_CTX *ctx); + +/** Sets the affine coordinates of an EC_POINT + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with the x-coordinate + * \param y BIGNUM with the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_set_affine_coordinates(const EC_GROUP *group, EC_POINT *p, + const BIGNUM *x, const BIGNUM *y, + BN_CTX *ctx); + +/** Gets the affine coordinates of an EC_POINT. + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM for the x-coordinate + * \param y BIGNUM for the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_get_affine_coordinates(const EC_GROUP *group, const EC_POINT *p, + BIGNUM *x, BIGNUM *y, BN_CTX *ctx); + +/** Sets the affine coordinates of an EC_POINT. A synonym of + * EC_POINT_set_affine_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with the x-coordinate + * \param y BIGNUM with the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *group, + EC_POINT *p, + const BIGNUM *x, + const BIGNUM *y, + BN_CTX *ctx)) + +/** Gets the affine coordinates of an EC_POINT. A synonym of + * EC_POINT_get_affine_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM for the x-coordinate + * \param y BIGNUM for the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group, + const EC_POINT *p, + BIGNUM *x, + BIGNUM *y, + BN_CTX *ctx)) + +/** Sets the x9.62 compressed coordinates of a EC_POINT + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with x-coordinate + * \param y_bit integer with the y-Bit (either 0 or 1) + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_set_compressed_coordinates(const EC_GROUP *group, EC_POINT *p, + const BIGNUM *x, int y_bit, + BN_CTX *ctx); + +/** Sets the x9.62 compressed coordinates of a EC_POINT. A synonym of + * EC_POINT_set_compressed_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with x-coordinate + * \param y_bit integer with the y-Bit (either 0 or 1) + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *group, + EC_POINT *p, + const BIGNUM *x, + int y_bit, + BN_CTX *ctx)) +# ifndef OPENSSL_NO_EC2M +/** Sets the affine coordinates of an EC_POINT. A synonym of + * EC_POINT_set_affine_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with the x-coordinate + * \param y BIGNUM with the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *group, + EC_POINT *p, + const BIGNUM *x, + const BIGNUM *y, + BN_CTX *ctx)) + +/** Gets the affine coordinates of an EC_POINT. A synonym of + * EC_POINT_get_affine_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM for the x-coordinate + * \param y BIGNUM for the y-coordinate + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *group, + const EC_POINT *p, + BIGNUM *x, + BIGNUM *y, + BN_CTX *ctx)) + +/** Sets the x9.62 compressed coordinates of a EC_POINT. A synonym of + * EC_POINT_set_compressed_coordinates + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param x BIGNUM with x-coordinate + * \param y_bit integer with the y-Bit (either 0 or 1) + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +DEPRECATEDIN_1_2_0(int EC_POINT_set_compressed_coordinates_GF2m(const EC_GROUP *group, + EC_POINT *p, + const BIGNUM *x, + int y_bit, + BN_CTX *ctx)) +# endif +/** Encodes a EC_POINT object to a octet string + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param form point conversion form + * \param buf memory buffer for the result. If NULL the function returns + * required buffer size. + * \param len length of the memory buffer + * \param ctx BN_CTX object (optional) + * \return the length of the encoded octet string or 0 if an error occurred + */ +size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *p, + point_conversion_form_t form, + unsigned char *buf, size_t len, BN_CTX *ctx); + +/** Decodes a EC_POINT from a octet string + * \param group underlying EC_GROUP object + * \param p EC_POINT object + * \param buf memory buffer with the encoded ec point + * \param len length of the encoded ec point + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_oct2point(const EC_GROUP *group, EC_POINT *p, + const unsigned char *buf, size_t len, BN_CTX *ctx); + +/** Encodes an EC_POINT object to an allocated octet string + * \param group underlying EC_GROUP object + * \param point EC_POINT object + * \param form point conversion form + * \param pbuf returns pointer to allocated buffer + * \param ctx BN_CTX object (optional) + * \return the length of the encoded octet string or 0 if an error occurred + */ +size_t EC_POINT_point2buf(const EC_GROUP *group, const EC_POINT *point, + point_conversion_form_t form, + unsigned char **pbuf, BN_CTX *ctx); + +/* other interfaces to point2oct/oct2point: */ +BIGNUM *EC_POINT_point2bn(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BIGNUM *, BN_CTX *); +EC_POINT *EC_POINT_bn2point(const EC_GROUP *, const BIGNUM *, + EC_POINT *, BN_CTX *); +char *EC_POINT_point2hex(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BN_CTX *); +EC_POINT *EC_POINT_hex2point(const EC_GROUP *, const char *, + EC_POINT *, BN_CTX *); + +/********************************************************************/ +/* functions for doing EC_POINT arithmetic */ +/********************************************************************/ + +/** Computes the sum of two EC_POINT + * \param group underlying EC_GROUP object + * \param r EC_POINT object for the result (r = a + b) + * \param a EC_POINT object with the first summand + * \param b EC_POINT object with the second summand + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, + const EC_POINT *b, BN_CTX *ctx); + +/** Computes the double of a EC_POINT + * \param group underlying EC_GROUP object + * \param r EC_POINT object for the result (r = 2 * a) + * \param a EC_POINT object + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_dbl(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, + BN_CTX *ctx); + +/** Computes the inverse of a EC_POINT + * \param group underlying EC_GROUP object + * \param a EC_POINT object to be inverted (it's used for the result as well) + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_invert(const EC_GROUP *group, EC_POINT *a, BN_CTX *ctx); + +/** Checks whether the point is the neutral element of the group + * \param group the underlying EC_GROUP object + * \param p EC_POINT object + * \return 1 if the point is the neutral element and 0 otherwise + */ +int EC_POINT_is_at_infinity(const EC_GROUP *group, const EC_POINT *p); + +/** Checks whether the point is on the curve + * \param group underlying EC_GROUP object + * \param point EC_POINT object to check + * \param ctx BN_CTX object (optional) + * \return 1 if the point is on the curve, 0 if not, or -1 on error + */ +int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, + BN_CTX *ctx); + +/** Compares two EC_POINTs + * \param group underlying EC_GROUP object + * \param a first EC_POINT object + * \param b second EC_POINT object + * \param ctx BN_CTX object (optional) + * \return 1 if the points are not equal, 0 if they are, or -1 on error + */ +int EC_POINT_cmp(const EC_GROUP *group, const EC_POINT *a, const EC_POINT *b, + BN_CTX *ctx); + +int EC_POINT_make_affine(const EC_GROUP *group, EC_POINT *point, BN_CTX *ctx); +int EC_POINTs_make_affine(const EC_GROUP *group, size_t num, + EC_POINT *points[], BN_CTX *ctx); + +/** Computes r = generator * n + sum_{i=0}^{num-1} p[i] * m[i] + * \param group underlying EC_GROUP object + * \param r EC_POINT object for the result + * \param n BIGNUM with the multiplier for the group generator (optional) + * \param num number further summands + * \param p array of size num of EC_POINT objects + * \param m array of size num of BIGNUM objects + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINTs_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, + size_t num, const EC_POINT *p[], const BIGNUM *m[], + BN_CTX *ctx); + +/** Computes r = generator * n + q * m + * \param group underlying EC_GROUP object + * \param r EC_POINT object for the result + * \param n BIGNUM with the multiplier for the group generator (optional) + * \param q EC_POINT object with the first factor of the second summand + * \param m BIGNUM with the second factor of the second summand + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, + const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); + +/** Stores multiples of generator for faster point multiplication + * \param group EC_GROUP object + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ +int EC_GROUP_precompute_mult(EC_GROUP *group, BN_CTX *ctx); + +/** Reports whether a precomputation has been done + * \param group EC_GROUP object + * \return 1 if a pre-computation has been done and 0 otherwise + */ +int EC_GROUP_have_precompute_mult(const EC_GROUP *group); + +/********************************************************************/ +/* ASN1 stuff */ +/********************************************************************/ + +DECLARE_ASN1_ITEM(ECPKPARAMETERS) +DECLARE_ASN1_ALLOC_FUNCTIONS(ECPKPARAMETERS) +DECLARE_ASN1_ITEM(ECPARAMETERS) +DECLARE_ASN1_ALLOC_FUNCTIONS(ECPARAMETERS) + +/* + * EC_GROUP_get_basis_type() returns the NID of the basis type used to + * represent the field elements + */ +int EC_GROUP_get_basis_type(const EC_GROUP *); +# ifndef OPENSSL_NO_EC2M +int EC_GROUP_get_trinomial_basis(const EC_GROUP *, unsigned int *k); +int EC_GROUP_get_pentanomial_basis(const EC_GROUP *, unsigned int *k1, + unsigned int *k2, unsigned int *k3); +# endif + +# define OPENSSL_EC_EXPLICIT_CURVE 0x000 +# define OPENSSL_EC_NAMED_CURVE 0x001 + +EC_GROUP *d2i_ECPKParameters(EC_GROUP **, const unsigned char **in, long len); +int i2d_ECPKParameters(const EC_GROUP *, unsigned char **out); + +# define d2i_ECPKParameters_bio(bp,x) ASN1_d2i_bio_of(EC_GROUP,NULL,d2i_ECPKParameters,bp,x) +# define i2d_ECPKParameters_bio(bp,x) ASN1_i2d_bio_of_const(EC_GROUP,i2d_ECPKParameters,bp,x) +# define d2i_ECPKParameters_fp(fp,x) (EC_GROUP *)ASN1_d2i_fp(NULL, \ + (char *(*)())d2i_ECPKParameters,(fp),(unsigned char **)(x)) +# define i2d_ECPKParameters_fp(fp,x) ASN1_i2d_fp(i2d_ECPKParameters,(fp), \ + (unsigned char *)(x)) + +int ECPKParameters_print(BIO *bp, const EC_GROUP *x, int off); +# ifndef OPENSSL_NO_STDIO +int ECPKParameters_print_fp(FILE *fp, const EC_GROUP *x, int off); +# endif + +/********************************************************************/ +/* EC_KEY functions */ +/********************************************************************/ + +/* some values for the encoding_flag */ +# define EC_PKEY_NO_PARAMETERS 0x001 +# define EC_PKEY_NO_PUBKEY 0x002 + +/* some values for the flags field */ +# define EC_FLAG_NON_FIPS_ALLOW 0x1 +# define EC_FLAG_FIPS_CHECKED 0x2 +# define EC_FLAG_COFACTOR_ECDH 0x1000 + +/** Creates a new EC_KEY object. + * \return EC_KEY object or NULL if an error occurred. + */ +EC_KEY *EC_KEY_new(void); + +int EC_KEY_get_flags(const EC_KEY *key); + +void EC_KEY_set_flags(EC_KEY *key, int flags); + +void EC_KEY_clear_flags(EC_KEY *key, int flags); + +/** Creates a new EC_KEY object using a named curve as underlying + * EC_GROUP object. + * \param nid NID of the named curve. + * \return EC_KEY object or NULL if an error occurred. + */ +EC_KEY *EC_KEY_new_by_curve_name(int nid); + +/** Frees a EC_KEY object. + * \param key EC_KEY object to be freed. + */ +void EC_KEY_free(EC_KEY *key); + +/** Copies a EC_KEY object. + * \param dst destination EC_KEY object + * \param src src EC_KEY object + * \return dst or NULL if an error occurred. + */ +EC_KEY *EC_KEY_copy(EC_KEY *dst, const EC_KEY *src); + +/** Creates a new EC_KEY object and copies the content from src to it. + * \param src the source EC_KEY object + * \return newly created EC_KEY object or NULL if an error occurred. + */ +EC_KEY *EC_KEY_dup(const EC_KEY *src); + +/** Increases the internal reference count of a EC_KEY object. + * \param key EC_KEY object + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_up_ref(EC_KEY *key); + +/** Returns the ENGINE object of a EC_KEY object + * \param eckey EC_KEY object + * \return the ENGINE object (possibly NULL). + */ +ENGINE *EC_KEY_get0_engine(const EC_KEY *eckey); + +/** Returns the EC_GROUP object of a EC_KEY object + * \param key EC_KEY object + * \return the EC_GROUP object (possibly NULL). + */ +const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key); + +/** Sets the EC_GROUP of a EC_KEY object. + * \param key EC_KEY object + * \param group EC_GROUP to use in the EC_KEY object (note: the EC_KEY + * object will use an own copy of the EC_GROUP). + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_set_group(EC_KEY *key, const EC_GROUP *group); + +/** Returns the private key of a EC_KEY object. + * \param key EC_KEY object + * \return a BIGNUM with the private key (possibly NULL). + */ +const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *key); + +/** Sets the private key of a EC_KEY object. + * \param key EC_KEY object + * \param prv BIGNUM with the private key (note: the EC_KEY object + * will use an own copy of the BIGNUM). + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_set_private_key(EC_KEY *key, const BIGNUM *prv); + +/** Returns the public key of a EC_KEY object. + * \param key the EC_KEY object + * \return a EC_POINT object with the public key (possibly NULL) + */ +const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *key); + +/** Sets the public key of a EC_KEY object. + * \param key EC_KEY object + * \param pub EC_POINT object with the public key (note: the EC_KEY object + * will use an own copy of the EC_POINT object). + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_set_public_key(EC_KEY *key, const EC_POINT *pub); + +unsigned EC_KEY_get_enc_flags(const EC_KEY *key); +void EC_KEY_set_enc_flags(EC_KEY *eckey, unsigned int flags); +point_conversion_form_t EC_KEY_get_conv_form(const EC_KEY *key); +void EC_KEY_set_conv_form(EC_KEY *eckey, point_conversion_form_t cform); + +#define EC_KEY_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_EC_KEY, l, p, newf, dupf, freef) +int EC_KEY_set_ex_data(EC_KEY *key, int idx, void *arg); +void *EC_KEY_get_ex_data(const EC_KEY *key, int idx); + +/* wrapper functions for the underlying EC_GROUP object */ +void EC_KEY_set_asn1_flag(EC_KEY *eckey, int asn1_flag); + +/** Creates a table of pre-computed multiples of the generator to + * accelerate further EC_KEY operations. + * \param key EC_KEY object + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_precompute_mult(EC_KEY *key, BN_CTX *ctx); + +/** Creates a new ec private (and optional a new public) key. + * \param key EC_KEY object + * \return 1 on success and 0 if an error occurred. + */ +int EC_KEY_generate_key(EC_KEY *key); + +/** Verifies that a private and/or public key is valid. + * \param key the EC_KEY object + * \return 1 on success and 0 otherwise. + */ +int EC_KEY_check_key(const EC_KEY *key); + +/** Indicates if an EC_KEY can be used for signing. + * \param eckey the EC_KEY object + * \return 1 if can can sign and 0 otherwise. + */ +int EC_KEY_can_sign(const EC_KEY *eckey); + +/** Sets a public key from affine coordinates performing + * necessary NIST PKV tests. + * \param key the EC_KEY object + * \param x public key x coordinate + * \param y public key y coordinate + * \return 1 on success and 0 otherwise. + */ +int EC_KEY_set_public_key_affine_coordinates(EC_KEY *key, BIGNUM *x, + BIGNUM *y); + +/** Encodes an EC_KEY public key to an allocated octet string + * \param key key to encode + * \param form point conversion form + * \param pbuf returns pointer to allocated buffer + * \param ctx BN_CTX object (optional) + * \return the length of the encoded octet string or 0 if an error occurred + */ +size_t EC_KEY_key2buf(const EC_KEY *key, point_conversion_form_t form, + unsigned char **pbuf, BN_CTX *ctx); + +/** Decodes a EC_KEY public key from a octet string + * \param key key to decode + * \param buf memory buffer with the encoded ec point + * \param len length of the encoded ec point + * \param ctx BN_CTX object (optional) + * \return 1 on success and 0 if an error occurred + */ + +int EC_KEY_oct2key(EC_KEY *key, const unsigned char *buf, size_t len, + BN_CTX *ctx); + +/** Decodes an EC_KEY private key from an octet string + * \param key key to decode + * \param buf memory buffer with the encoded private key + * \param len length of the encoded key + * \return 1 on success and 0 if an error occurred + */ + +int EC_KEY_oct2priv(EC_KEY *key, const unsigned char *buf, size_t len); + +/** Encodes a EC_KEY private key to an octet string + * \param key key to encode + * \param buf memory buffer for the result. If NULL the function returns + * required buffer size. + * \param len length of the memory buffer + * \return the length of the encoded octet string or 0 if an error occurred + */ + +size_t EC_KEY_priv2oct(const EC_KEY *key, unsigned char *buf, size_t len); + +/** Encodes an EC_KEY private key to an allocated octet string + * \param eckey key to encode + * \param pbuf returns pointer to allocated buffer + * \return the length of the encoded octet string or 0 if an error occurred + */ +size_t EC_KEY_priv2buf(const EC_KEY *eckey, unsigned char **pbuf); + +/********************************************************************/ +/* de- and encoding functions for SEC1 ECPrivateKey */ +/********************************************************************/ + +/** Decodes a private key from a memory buffer. + * \param key a pointer to a EC_KEY object which should be used (or NULL) + * \param in pointer to memory with the DER encoded private key + * \param len length of the DER encoded private key + * \return the decoded private key or NULL if an error occurred. + */ +EC_KEY *d2i_ECPrivateKey(EC_KEY **key, const unsigned char **in, long len); + +/** Encodes a private key object and stores the result in a buffer. + * \param key the EC_KEY object to encode + * \param out the buffer for the result (if NULL the function returns number + * of bytes needed). + * \return 1 on success and 0 if an error occurred. + */ +int i2d_ECPrivateKey(EC_KEY *key, unsigned char **out); + +/********************************************************************/ +/* de- and encoding functions for EC parameters */ +/********************************************************************/ + +/** Decodes ec parameter from a memory buffer. + * \param key a pointer to a EC_KEY object which should be used (or NULL) + * \param in pointer to memory with the DER encoded ec parameters + * \param len length of the DER encoded ec parameters + * \return a EC_KEY object with the decoded parameters or NULL if an error + * occurred. + */ +EC_KEY *d2i_ECParameters(EC_KEY **key, const unsigned char **in, long len); + +/** Encodes ec parameter and stores the result in a buffer. + * \param key the EC_KEY object with ec parameters to encode + * \param out the buffer for the result (if NULL the function returns number + * of bytes needed). + * \return 1 on success and 0 if an error occurred. + */ +int i2d_ECParameters(EC_KEY *key, unsigned char **out); + +/********************************************************************/ +/* de- and encoding functions for EC public key */ +/* (octet string, not DER -- hence 'o2i' and 'i2o') */ +/********************************************************************/ + +/** Decodes a ec public key from a octet string. + * \param key a pointer to a EC_KEY object which should be used + * \param in memory buffer with the encoded public key + * \param len length of the encoded public key + * \return EC_KEY object with decoded public key or NULL if an error + * occurred. + */ +EC_KEY *o2i_ECPublicKey(EC_KEY **key, const unsigned char **in, long len); + +/** Encodes a ec public key in an octet string. + * \param key the EC_KEY object with the public key + * \param out the buffer for the result (if NULL the function returns number + * of bytes needed). + * \return 1 on success and 0 if an error occurred + */ +int i2o_ECPublicKey(const EC_KEY *key, unsigned char **out); + +/** Prints out the ec parameters on human readable form. + * \param bp BIO object to which the information is printed + * \param key EC_KEY object + * \return 1 on success and 0 if an error occurred + */ +int ECParameters_print(BIO *bp, const EC_KEY *key); + +/** Prints out the contents of a EC_KEY object + * \param bp BIO object to which the information is printed + * \param key EC_KEY object + * \param off line offset + * \return 1 on success and 0 if an error occurred + */ +int EC_KEY_print(BIO *bp, const EC_KEY *key, int off); + +# ifndef OPENSSL_NO_STDIO +/** Prints out the ec parameters on human readable form. + * \param fp file descriptor to which the information is printed + * \param key EC_KEY object + * \return 1 on success and 0 if an error occurred + */ +int ECParameters_print_fp(FILE *fp, const EC_KEY *key); + +/** Prints out the contents of a EC_KEY object + * \param fp file descriptor to which the information is printed + * \param key EC_KEY object + * \param off line offset + * \return 1 on success and 0 if an error occurred + */ +int EC_KEY_print_fp(FILE *fp, const EC_KEY *key, int off); + +# endif + +const EC_KEY_METHOD *EC_KEY_OpenSSL(void); +const EC_KEY_METHOD *EC_KEY_get_default_method(void); +void EC_KEY_set_default_method(const EC_KEY_METHOD *meth); +const EC_KEY_METHOD *EC_KEY_get_method(const EC_KEY *key); +int EC_KEY_set_method(EC_KEY *key, const EC_KEY_METHOD *meth); +EC_KEY *EC_KEY_new_method(ENGINE *engine); + +/** The old name for ecdh_KDF_X9_63 + * The ECDH KDF specification has been mistakingly attributed to ANSI X9.62, + * it is actually specified in ANSI X9.63. + * This identifier is retained for backwards compatibility + */ +int ECDH_KDF_X9_62(unsigned char *out, size_t outlen, + const unsigned char *Z, size_t Zlen, + const unsigned char *sinfo, size_t sinfolen, + const EVP_MD *md); + +int ECDH_compute_key(void *out, size_t outlen, const EC_POINT *pub_key, + const EC_KEY *ecdh, + void *(*KDF) (const void *in, size_t inlen, + void *out, size_t *outlen)); + +typedef struct ECDSA_SIG_st ECDSA_SIG; + +/** Allocates and initialize a ECDSA_SIG structure + * \return pointer to a ECDSA_SIG structure or NULL if an error occurred + */ +ECDSA_SIG *ECDSA_SIG_new(void); + +/** frees a ECDSA_SIG structure + * \param sig pointer to the ECDSA_SIG structure + */ +void ECDSA_SIG_free(ECDSA_SIG *sig); + +/** DER encode content of ECDSA_SIG object (note: this function modifies *pp + * (*pp += length of the DER encoded signature)). + * \param sig pointer to the ECDSA_SIG object + * \param pp pointer to a unsigned char pointer for the output or NULL + * \return the length of the DER encoded ECDSA_SIG object or a negative value + * on error + */ +int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp); + +/** Decodes a DER encoded ECDSA signature (note: this function changes *pp + * (*pp += len)). + * \param sig pointer to ECDSA_SIG pointer (may be NULL) + * \param pp memory buffer with the DER encoded signature + * \param len length of the buffer + * \return pointer to the decoded ECDSA_SIG structure (or NULL) + */ +ECDSA_SIG *d2i_ECDSA_SIG(ECDSA_SIG **sig, const unsigned char **pp, long len); + +/** Accessor for r and s fields of ECDSA_SIG + * \param sig pointer to ECDSA_SIG structure + * \param pr pointer to BIGNUM pointer for r (may be NULL) + * \param ps pointer to BIGNUM pointer for s (may be NULL) + */ +void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); + +/** Accessor for r field of ECDSA_SIG + * \param sig pointer to ECDSA_SIG structure + */ +const BIGNUM *ECDSA_SIG_get0_r(const ECDSA_SIG *sig); + +/** Accessor for s field of ECDSA_SIG + * \param sig pointer to ECDSA_SIG structure + */ +const BIGNUM *ECDSA_SIG_get0_s(const ECDSA_SIG *sig); + +/** Setter for r and s fields of ECDSA_SIG + * \param sig pointer to ECDSA_SIG structure + * \param r pointer to BIGNUM for r (may be NULL) + * \param s pointer to BIGNUM for s (may be NULL) + */ +int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s); + +/** Computes the ECDSA signature of the given hash value using + * the supplied private key and returns the created signature. + * \param dgst pointer to the hash value + * \param dgst_len length of the hash value + * \param eckey EC_KEY object containing a private EC key + * \return pointer to a ECDSA_SIG structure or NULL if an error occurred + */ +ECDSA_SIG *ECDSA_do_sign(const unsigned char *dgst, int dgst_len, + EC_KEY *eckey); + +/** Computes ECDSA signature of a given hash value using the supplied + * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). + * \param dgst pointer to the hash value to sign + * \param dgstlen length of the hash value + * \param kinv BIGNUM with a pre-computed inverse k (optional) + * \param rp BIGNUM with a pre-computed rp value (optional), + * see ECDSA_sign_setup + * \param eckey EC_KEY object containing a private EC key + * \return pointer to a ECDSA_SIG structure or NULL if an error occurred + */ +ECDSA_SIG *ECDSA_do_sign_ex(const unsigned char *dgst, int dgstlen, + const BIGNUM *kinv, const BIGNUM *rp, + EC_KEY *eckey); + +/** Verifies that the supplied signature is a valid ECDSA + * signature of the supplied hash value using the supplied public key. + * \param dgst pointer to the hash value + * \param dgst_len length of the hash value + * \param sig ECDSA_SIG structure + * \param eckey EC_KEY object containing a public EC key + * \return 1 if the signature is valid, 0 if the signature is invalid + * and -1 on error + */ +int ECDSA_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey); + +/** Precompute parts of the signing operation + * \param eckey EC_KEY object containing a private EC key + * \param ctx BN_CTX object (optional) + * \param kinv BIGNUM pointer for the inverse of k + * \param rp BIGNUM pointer for x coordinate of k * generator + * \return 1 on success and 0 otherwise + */ +int ECDSA_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, BIGNUM **rp); + +/** Computes ECDSA signature of a given hash value using the supplied + * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). + * \param type this parameter is ignored + * \param dgst pointer to the hash value to sign + * \param dgstlen length of the hash value + * \param sig memory for the DER encoded created signature + * \param siglen pointer to the length of the returned signature + * \param eckey EC_KEY object containing a private EC key + * \return 1 on success and 0 otherwise + */ +int ECDSA_sign(int type, const unsigned char *dgst, int dgstlen, + unsigned char *sig, unsigned int *siglen, EC_KEY *eckey); + +/** Computes ECDSA signature of a given hash value using the supplied + * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). + * \param type this parameter is ignored + * \param dgst pointer to the hash value to sign + * \param dgstlen length of the hash value + * \param sig buffer to hold the DER encoded signature + * \param siglen pointer to the length of the returned signature + * \param kinv BIGNUM with a pre-computed inverse k (optional) + * \param rp BIGNUM with a pre-computed rp value (optional), + * see ECDSA_sign_setup + * \param eckey EC_KEY object containing a private EC key + * \return 1 on success and 0 otherwise + */ +int ECDSA_sign_ex(int type, const unsigned char *dgst, int dgstlen, + unsigned char *sig, unsigned int *siglen, + const BIGNUM *kinv, const BIGNUM *rp, EC_KEY *eckey); + +/** Verifies that the given signature is valid ECDSA signature + * of the supplied hash value using the specified public key. + * \param type this parameter is ignored + * \param dgst pointer to the hash value + * \param dgstlen length of the hash value + * \param sig pointer to the DER encoded signature + * \param siglen length of the DER encoded signature + * \param eckey EC_KEY object containing a public EC key + * \return 1 if the signature is valid, 0 if the signature is invalid + * and -1 on error + */ +int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, + const unsigned char *sig, int siglen, EC_KEY *eckey); + +/** Returns the maximum length of the DER encoded signature + * \param eckey EC_KEY object + * \return numbers of bytes required for the DER encoded signature + */ +int ECDSA_size(const EC_KEY *eckey); + +/********************************************************************/ +/* EC_KEY_METHOD constructors, destructors, writers and accessors */ +/********************************************************************/ + +EC_KEY_METHOD *EC_KEY_METHOD_new(const EC_KEY_METHOD *meth); +void EC_KEY_METHOD_free(EC_KEY_METHOD *meth); +void EC_KEY_METHOD_set_init(EC_KEY_METHOD *meth, + int (*init)(EC_KEY *key), + void (*finish)(EC_KEY *key), + int (*copy)(EC_KEY *dest, const EC_KEY *src), + int (*set_group)(EC_KEY *key, const EC_GROUP *grp), + int (*set_private)(EC_KEY *key, + const BIGNUM *priv_key), + int (*set_public)(EC_KEY *key, + const EC_POINT *pub_key)); + +void EC_KEY_METHOD_set_keygen(EC_KEY_METHOD *meth, + int (*keygen)(EC_KEY *key)); + +void EC_KEY_METHOD_set_compute_key(EC_KEY_METHOD *meth, + int (*ckey)(unsigned char **psec, + size_t *pseclen, + const EC_POINT *pub_key, + const EC_KEY *ecdh)); + +void EC_KEY_METHOD_set_sign(EC_KEY_METHOD *meth, + int (*sign)(int type, const unsigned char *dgst, + int dlen, unsigned char *sig, + unsigned int *siglen, + const BIGNUM *kinv, const BIGNUM *r, + EC_KEY *eckey), + int (*sign_setup)(EC_KEY *eckey, BN_CTX *ctx_in, + BIGNUM **kinvp, BIGNUM **rp), + ECDSA_SIG *(*sign_sig)(const unsigned char *dgst, + int dgst_len, + const BIGNUM *in_kinv, + const BIGNUM *in_r, + EC_KEY *eckey)); + +void EC_KEY_METHOD_set_verify(EC_KEY_METHOD *meth, + int (*verify)(int type, const unsigned + char *dgst, int dgst_len, + const unsigned char *sigbuf, + int sig_len, EC_KEY *eckey), + int (*verify_sig)(const unsigned char *dgst, + int dgst_len, + const ECDSA_SIG *sig, + EC_KEY *eckey)); + +void EC_KEY_METHOD_get_init(const EC_KEY_METHOD *meth, + int (**pinit)(EC_KEY *key), + void (**pfinish)(EC_KEY *key), + int (**pcopy)(EC_KEY *dest, const EC_KEY *src), + int (**pset_group)(EC_KEY *key, + const EC_GROUP *grp), + int (**pset_private)(EC_KEY *key, + const BIGNUM *priv_key), + int (**pset_public)(EC_KEY *key, + const EC_POINT *pub_key)); + +void EC_KEY_METHOD_get_keygen(const EC_KEY_METHOD *meth, + int (**pkeygen)(EC_KEY *key)); + +void EC_KEY_METHOD_get_compute_key(const EC_KEY_METHOD *meth, + int (**pck)(unsigned char **psec, + size_t *pseclen, + const EC_POINT *pub_key, + const EC_KEY *ecdh)); + +void EC_KEY_METHOD_get_sign(const EC_KEY_METHOD *meth, + int (**psign)(int type, const unsigned char *dgst, + int dlen, unsigned char *sig, + unsigned int *siglen, + const BIGNUM *kinv, const BIGNUM *r, + EC_KEY *eckey), + int (**psign_setup)(EC_KEY *eckey, BN_CTX *ctx_in, + BIGNUM **kinvp, BIGNUM **rp), + ECDSA_SIG *(**psign_sig)(const unsigned char *dgst, + int dgst_len, + const BIGNUM *in_kinv, + const BIGNUM *in_r, + EC_KEY *eckey)); + +void EC_KEY_METHOD_get_verify(const EC_KEY_METHOD *meth, + int (**pverify)(int type, const unsigned + char *dgst, int dgst_len, + const unsigned char *sigbuf, + int sig_len, EC_KEY *eckey), + int (**pverify_sig)(const unsigned char *dgst, + int dgst_len, + const ECDSA_SIG *sig, + EC_KEY *eckey)); + +# define ECParameters_dup(x) ASN1_dup_of(EC_KEY,i2d_ECParameters,d2i_ECParameters,x) + +# ifndef __cplusplus +# if defined(__SUNPRO_C) +# if __SUNPRO_C >= 0x520 +# pragma error_messages (default,E_ARRAY_OF_INCOMPLETE_NONAME,E_ARRAY_OF_INCOMPLETE) +# endif +# endif +# endif + +# define EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_PARAMGEN|EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_EC_PARAMGEN_CURVE_NID, nid, NULL) + +# define EVP_PKEY_CTX_set_ec_param_enc(ctx, flag) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_PARAMGEN|EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_EC_PARAM_ENC, flag, NULL) + +# define EVP_PKEY_CTX_set_ecdh_cofactor_mode(ctx, flag) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_ECDH_COFACTOR, flag, NULL) + +# define EVP_PKEY_CTX_get_ecdh_cofactor_mode(ctx) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_ECDH_COFACTOR, -2, NULL) + +# define EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, kdf) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_KDF_TYPE, kdf, NULL) + +# define EVP_PKEY_CTX_get_ecdh_kdf_type(ctx) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_KDF_TYPE, -2, NULL) + +# define EVP_PKEY_CTX_set_ecdh_kdf_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_KDF_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_get_ecdh_kdf_md(ctx, pmd) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_EC_KDF_MD, 0, (void *)(pmd)) + +# define EVP_PKEY_CTX_set_ecdh_kdf_outlen(ctx, len) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_KDF_OUTLEN, len, NULL) + +# define EVP_PKEY_CTX_get_ecdh_kdf_outlen(ctx, plen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_EC_KDF_OUTLEN, 0, \ + (void *)(plen)) + +# define EVP_PKEY_CTX_set0_ecdh_kdf_ukm(ctx, p, plen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_EC_KDF_UKM, plen, (void *)(p)) + +# define EVP_PKEY_CTX_get0_ecdh_kdf_ukm(ctx, p) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, \ + EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_GET_EC_KDF_UKM, 0, (void *)(p)) + +/* SM2 will skip the operation check so no need to pass operation here */ +# define EVP_PKEY_CTX_set1_id(ctx, id, id_len) \ + EVP_PKEY_CTX_ctrl(ctx, -1, -1, \ + EVP_PKEY_CTRL_SET1_ID, (int)id_len, (void*)(id)) + +# define EVP_PKEY_CTX_get1_id(ctx, id) \ + EVP_PKEY_CTX_ctrl(ctx, -1, -1, \ + EVP_PKEY_CTRL_GET1_ID, 0, (void*)(id)) + +# define EVP_PKEY_CTX_get1_id_len(ctx, id_len) \ + EVP_PKEY_CTX_ctrl(ctx, -1, -1, \ + EVP_PKEY_CTRL_GET1_ID_LEN, 0, (void*)(id_len)) + +# define EVP_PKEY_CTRL_EC_PARAMGEN_CURVE_NID (EVP_PKEY_ALG_CTRL + 1) +# define EVP_PKEY_CTRL_EC_PARAM_ENC (EVP_PKEY_ALG_CTRL + 2) +# define EVP_PKEY_CTRL_EC_ECDH_COFACTOR (EVP_PKEY_ALG_CTRL + 3) +# define EVP_PKEY_CTRL_EC_KDF_TYPE (EVP_PKEY_ALG_CTRL + 4) +# define EVP_PKEY_CTRL_EC_KDF_MD (EVP_PKEY_ALG_CTRL + 5) +# define EVP_PKEY_CTRL_GET_EC_KDF_MD (EVP_PKEY_ALG_CTRL + 6) +# define EVP_PKEY_CTRL_EC_KDF_OUTLEN (EVP_PKEY_ALG_CTRL + 7) +# define EVP_PKEY_CTRL_GET_EC_KDF_OUTLEN (EVP_PKEY_ALG_CTRL + 8) +# define EVP_PKEY_CTRL_EC_KDF_UKM (EVP_PKEY_ALG_CTRL + 9) +# define EVP_PKEY_CTRL_GET_EC_KDF_UKM (EVP_PKEY_ALG_CTRL + 10) +# define EVP_PKEY_CTRL_SET1_ID (EVP_PKEY_ALG_CTRL + 11) +# define EVP_PKEY_CTRL_GET1_ID (EVP_PKEY_ALG_CTRL + 12) +# define EVP_PKEY_CTRL_GET1_ID_LEN (EVP_PKEY_ALG_CTRL + 13) +/* KDF types */ +# define EVP_PKEY_ECDH_KDF_NONE 1 +# define EVP_PKEY_ECDH_KDF_X9_63 2 +/** The old name for EVP_PKEY_ECDH_KDF_X9_63 + * The ECDH KDF specification has been mistakingly attributed to ANSI X9.62, + * it is actually specified in ANSI X9.63. + * This identifier is retained for backwards compatibility + */ +# define EVP_PKEY_ECDH_KDF_X9_62 EVP_PKEY_ECDH_KDF_X9_63 + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/ecdh.h b/thrid-party/openssl/openssl/ecdh.h new file mode 100644 index 0000000000..681f3d5e55 --- /dev/null +++ b/thrid-party/openssl/openssl/ecdh.h @@ -0,0 +1,10 @@ +/* + * Copyright 2002-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include diff --git a/thrid-party/openssl/openssl/ecdsa.h b/thrid-party/openssl/openssl/ecdsa.h new file mode 100644 index 0000000000..681f3d5e55 --- /dev/null +++ b/thrid-party/openssl/openssl/ecdsa.h @@ -0,0 +1,10 @@ +/* + * Copyright 2002-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include diff --git a/thrid-party/openssl/openssl/ecerr.h b/thrid-party/openssl/openssl/ecerr.h new file mode 100644 index 0000000000..f7b9183456 --- /dev/null +++ b/thrid-party/openssl/openssl/ecerr.h @@ -0,0 +1,275 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ECERR_H +# define HEADER_ECERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_EC + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_EC_strings(void); + +/* + * EC function codes. + */ +# define EC_F_BN_TO_FELEM 224 +# define EC_F_D2I_ECPARAMETERS 144 +# define EC_F_D2I_ECPKPARAMETERS 145 +# define EC_F_D2I_ECPRIVATEKEY 146 +# define EC_F_DO_EC_KEY_PRINT 221 +# define EC_F_ECDH_CMS_DECRYPT 238 +# define EC_F_ECDH_CMS_SET_SHARED_INFO 239 +# define EC_F_ECDH_COMPUTE_KEY 246 +# define EC_F_ECDH_SIMPLE_COMPUTE_KEY 257 +# define EC_F_ECDSA_DO_SIGN_EX 251 +# define EC_F_ECDSA_DO_VERIFY 252 +# define EC_F_ECDSA_SIGN_EX 254 +# define EC_F_ECDSA_SIGN_SETUP 248 +# define EC_F_ECDSA_SIG_NEW 265 +# define EC_F_ECDSA_VERIFY 253 +# define EC_F_ECD_ITEM_VERIFY 270 +# define EC_F_ECKEY_PARAM2TYPE 223 +# define EC_F_ECKEY_PARAM_DECODE 212 +# define EC_F_ECKEY_PRIV_DECODE 213 +# define EC_F_ECKEY_PRIV_ENCODE 214 +# define EC_F_ECKEY_PUB_DECODE 215 +# define EC_F_ECKEY_PUB_ENCODE 216 +# define EC_F_ECKEY_TYPE2PARAM 220 +# define EC_F_ECPARAMETERS_PRINT 147 +# define EC_F_ECPARAMETERS_PRINT_FP 148 +# define EC_F_ECPKPARAMETERS_PRINT 149 +# define EC_F_ECPKPARAMETERS_PRINT_FP 150 +# define EC_F_ECP_NISTZ256_GET_AFFINE 240 +# define EC_F_ECP_NISTZ256_INV_MOD_ORD 275 +# define EC_F_ECP_NISTZ256_MULT_PRECOMPUTE 243 +# define EC_F_ECP_NISTZ256_POINTS_MUL 241 +# define EC_F_ECP_NISTZ256_PRE_COMP_NEW 244 +# define EC_F_ECP_NISTZ256_WINDOWED_MUL 242 +# define EC_F_ECX_KEY_OP 266 +# define EC_F_ECX_PRIV_ENCODE 267 +# define EC_F_ECX_PUB_ENCODE 268 +# define EC_F_EC_ASN1_GROUP2CURVE 153 +# define EC_F_EC_ASN1_GROUP2FIELDID 154 +# define EC_F_EC_GF2M_MONTGOMERY_POINT_MULTIPLY 208 +# define EC_F_EC_GF2M_SIMPLE_FIELD_INV 296 +# define EC_F_EC_GF2M_SIMPLE_GROUP_CHECK_DISCRIMINANT 159 +# define EC_F_EC_GF2M_SIMPLE_GROUP_SET_CURVE 195 +# define EC_F_EC_GF2M_SIMPLE_LADDER_POST 285 +# define EC_F_EC_GF2M_SIMPLE_LADDER_PRE 288 +# define EC_F_EC_GF2M_SIMPLE_OCT2POINT 160 +# define EC_F_EC_GF2M_SIMPLE_POINT2OCT 161 +# define EC_F_EC_GF2M_SIMPLE_POINTS_MUL 289 +# define EC_F_EC_GF2M_SIMPLE_POINT_GET_AFFINE_COORDINATES 162 +# define EC_F_EC_GF2M_SIMPLE_POINT_SET_AFFINE_COORDINATES 163 +# define EC_F_EC_GF2M_SIMPLE_SET_COMPRESSED_COORDINATES 164 +# define EC_F_EC_GFP_MONT_FIELD_DECODE 133 +# define EC_F_EC_GFP_MONT_FIELD_ENCODE 134 +# define EC_F_EC_GFP_MONT_FIELD_INV 297 +# define EC_F_EC_GFP_MONT_FIELD_MUL 131 +# define EC_F_EC_GFP_MONT_FIELD_SET_TO_ONE 209 +# define EC_F_EC_GFP_MONT_FIELD_SQR 132 +# define EC_F_EC_GFP_MONT_GROUP_SET_CURVE 189 +# define EC_F_EC_GFP_NISTP224_GROUP_SET_CURVE 225 +# define EC_F_EC_GFP_NISTP224_POINTS_MUL 228 +# define EC_F_EC_GFP_NISTP224_POINT_GET_AFFINE_COORDINATES 226 +# define EC_F_EC_GFP_NISTP256_GROUP_SET_CURVE 230 +# define EC_F_EC_GFP_NISTP256_POINTS_MUL 231 +# define EC_F_EC_GFP_NISTP256_POINT_GET_AFFINE_COORDINATES 232 +# define EC_F_EC_GFP_NISTP521_GROUP_SET_CURVE 233 +# define EC_F_EC_GFP_NISTP521_POINTS_MUL 234 +# define EC_F_EC_GFP_NISTP521_POINT_GET_AFFINE_COORDINATES 235 +# define EC_F_EC_GFP_NIST_FIELD_MUL 200 +# define EC_F_EC_GFP_NIST_FIELD_SQR 201 +# define EC_F_EC_GFP_NIST_GROUP_SET_CURVE 202 +# define EC_F_EC_GFP_SIMPLE_BLIND_COORDINATES 287 +# define EC_F_EC_GFP_SIMPLE_FIELD_INV 298 +# define EC_F_EC_GFP_SIMPLE_GROUP_CHECK_DISCRIMINANT 165 +# define EC_F_EC_GFP_SIMPLE_GROUP_SET_CURVE 166 +# define EC_F_EC_GFP_SIMPLE_MAKE_AFFINE 102 +# define EC_F_EC_GFP_SIMPLE_OCT2POINT 103 +# define EC_F_EC_GFP_SIMPLE_POINT2OCT 104 +# define EC_F_EC_GFP_SIMPLE_POINTS_MAKE_AFFINE 137 +# define EC_F_EC_GFP_SIMPLE_POINT_GET_AFFINE_COORDINATES 167 +# define EC_F_EC_GFP_SIMPLE_POINT_SET_AFFINE_COORDINATES 168 +# define EC_F_EC_GFP_SIMPLE_SET_COMPRESSED_COORDINATES 169 +# define EC_F_EC_GROUP_CHECK 170 +# define EC_F_EC_GROUP_CHECK_DISCRIMINANT 171 +# define EC_F_EC_GROUP_COPY 106 +# define EC_F_EC_GROUP_GET_CURVE 291 +# define EC_F_EC_GROUP_GET_CURVE_GF2M 172 +# define EC_F_EC_GROUP_GET_CURVE_GFP 130 +# define EC_F_EC_GROUP_GET_DEGREE 173 +# define EC_F_EC_GROUP_GET_ECPARAMETERS 261 +# define EC_F_EC_GROUP_GET_ECPKPARAMETERS 262 +# define EC_F_EC_GROUP_GET_PENTANOMIAL_BASIS 193 +# define EC_F_EC_GROUP_GET_TRINOMIAL_BASIS 194 +# define EC_F_EC_GROUP_NEW 108 +# define EC_F_EC_GROUP_NEW_BY_CURVE_NAME 174 +# define EC_F_EC_GROUP_NEW_FROM_DATA 175 +# define EC_F_EC_GROUP_NEW_FROM_ECPARAMETERS 263 +# define EC_F_EC_GROUP_NEW_FROM_ECPKPARAMETERS 264 +# define EC_F_EC_GROUP_SET_CURVE 292 +# define EC_F_EC_GROUP_SET_CURVE_GF2M 176 +# define EC_F_EC_GROUP_SET_CURVE_GFP 109 +# define EC_F_EC_GROUP_SET_GENERATOR 111 +# define EC_F_EC_GROUP_SET_SEED 286 +# define EC_F_EC_KEY_CHECK_KEY 177 +# define EC_F_EC_KEY_COPY 178 +# define EC_F_EC_KEY_GENERATE_KEY 179 +# define EC_F_EC_KEY_NEW 182 +# define EC_F_EC_KEY_NEW_METHOD 245 +# define EC_F_EC_KEY_OCT2PRIV 255 +# define EC_F_EC_KEY_PRINT 180 +# define EC_F_EC_KEY_PRINT_FP 181 +# define EC_F_EC_KEY_PRIV2BUF 279 +# define EC_F_EC_KEY_PRIV2OCT 256 +# define EC_F_EC_KEY_SET_PUBLIC_KEY_AFFINE_COORDINATES 229 +# define EC_F_EC_KEY_SIMPLE_CHECK_KEY 258 +# define EC_F_EC_KEY_SIMPLE_OCT2PRIV 259 +# define EC_F_EC_KEY_SIMPLE_PRIV2OCT 260 +# define EC_F_EC_PKEY_CHECK 273 +# define EC_F_EC_PKEY_PARAM_CHECK 274 +# define EC_F_EC_POINTS_MAKE_AFFINE 136 +# define EC_F_EC_POINTS_MUL 290 +# define EC_F_EC_POINT_ADD 112 +# define EC_F_EC_POINT_BN2POINT 280 +# define EC_F_EC_POINT_CMP 113 +# define EC_F_EC_POINT_COPY 114 +# define EC_F_EC_POINT_DBL 115 +# define EC_F_EC_POINT_GET_AFFINE_COORDINATES 293 +# define EC_F_EC_POINT_GET_AFFINE_COORDINATES_GF2M 183 +# define EC_F_EC_POINT_GET_AFFINE_COORDINATES_GFP 116 +# define EC_F_EC_POINT_GET_JPROJECTIVE_COORDINATES_GFP 117 +# define EC_F_EC_POINT_INVERT 210 +# define EC_F_EC_POINT_IS_AT_INFINITY 118 +# define EC_F_EC_POINT_IS_ON_CURVE 119 +# define EC_F_EC_POINT_MAKE_AFFINE 120 +# define EC_F_EC_POINT_NEW 121 +# define EC_F_EC_POINT_OCT2POINT 122 +# define EC_F_EC_POINT_POINT2BUF 281 +# define EC_F_EC_POINT_POINT2OCT 123 +# define EC_F_EC_POINT_SET_AFFINE_COORDINATES 294 +# define EC_F_EC_POINT_SET_AFFINE_COORDINATES_GF2M 185 +# define EC_F_EC_POINT_SET_AFFINE_COORDINATES_GFP 124 +# define EC_F_EC_POINT_SET_COMPRESSED_COORDINATES 295 +# define EC_F_EC_POINT_SET_COMPRESSED_COORDINATES_GF2M 186 +# define EC_F_EC_POINT_SET_COMPRESSED_COORDINATES_GFP 125 +# define EC_F_EC_POINT_SET_JPROJECTIVE_COORDINATES_GFP 126 +# define EC_F_EC_POINT_SET_TO_INFINITY 127 +# define EC_F_EC_PRE_COMP_NEW 196 +# define EC_F_EC_SCALAR_MUL_LADDER 284 +# define EC_F_EC_WNAF_MUL 187 +# define EC_F_EC_WNAF_PRECOMPUTE_MULT 188 +# define EC_F_I2D_ECPARAMETERS 190 +# define EC_F_I2D_ECPKPARAMETERS 191 +# define EC_F_I2D_ECPRIVATEKEY 192 +# define EC_F_I2O_ECPUBLICKEY 151 +# define EC_F_NISTP224_PRE_COMP_NEW 227 +# define EC_F_NISTP256_PRE_COMP_NEW 236 +# define EC_F_NISTP521_PRE_COMP_NEW 237 +# define EC_F_O2I_ECPUBLICKEY 152 +# define EC_F_OLD_EC_PRIV_DECODE 222 +# define EC_F_OSSL_ECDH_COMPUTE_KEY 247 +# define EC_F_OSSL_ECDSA_SIGN_SIG 249 +# define EC_F_OSSL_ECDSA_VERIFY_SIG 250 +# define EC_F_PKEY_ECD_CTRL 271 +# define EC_F_PKEY_ECD_DIGESTSIGN 272 +# define EC_F_PKEY_ECD_DIGESTSIGN25519 276 +# define EC_F_PKEY_ECD_DIGESTSIGN448 277 +# define EC_F_PKEY_ECX_DERIVE 269 +# define EC_F_PKEY_EC_CTRL 197 +# define EC_F_PKEY_EC_CTRL_STR 198 +# define EC_F_PKEY_EC_DERIVE 217 +# define EC_F_PKEY_EC_INIT 282 +# define EC_F_PKEY_EC_KDF_DERIVE 283 +# define EC_F_PKEY_EC_KEYGEN 199 +# define EC_F_PKEY_EC_PARAMGEN 219 +# define EC_F_PKEY_EC_SIGN 218 +# define EC_F_VALIDATE_ECX_DERIVE 278 + +/* + * EC reason codes. + */ +# define EC_R_ASN1_ERROR 115 +# define EC_R_BAD_SIGNATURE 156 +# define EC_R_BIGNUM_OUT_OF_RANGE 144 +# define EC_R_BUFFER_TOO_SMALL 100 +# define EC_R_CANNOT_INVERT 165 +# define EC_R_COORDINATES_OUT_OF_RANGE 146 +# define EC_R_CURVE_DOES_NOT_SUPPORT_ECDH 160 +# define EC_R_CURVE_DOES_NOT_SUPPORT_SIGNING 159 +# define EC_R_D2I_ECPKPARAMETERS_FAILURE 117 +# define EC_R_DECODE_ERROR 142 +# define EC_R_DISCRIMINANT_IS_ZERO 118 +# define EC_R_EC_GROUP_NEW_BY_NAME_FAILURE 119 +# define EC_R_FIELD_TOO_LARGE 143 +# define EC_R_GF2M_NOT_SUPPORTED 147 +# define EC_R_GROUP2PKPARAMETERS_FAILURE 120 +# define EC_R_I2D_ECPKPARAMETERS_FAILURE 121 +# define EC_R_INCOMPATIBLE_OBJECTS 101 +# define EC_R_INVALID_ARGUMENT 112 +# define EC_R_INVALID_COMPRESSED_POINT 110 +# define EC_R_INVALID_COMPRESSION_BIT 109 +# define EC_R_INVALID_CURVE 141 +# define EC_R_INVALID_DIGEST 151 +# define EC_R_INVALID_DIGEST_TYPE 138 +# define EC_R_INVALID_ENCODING 102 +# define EC_R_INVALID_FIELD 103 +# define EC_R_INVALID_FORM 104 +# define EC_R_INVALID_GROUP_ORDER 122 +# define EC_R_INVALID_KEY 116 +# define EC_R_INVALID_OUTPUT_LENGTH 161 +# define EC_R_INVALID_PEER_KEY 133 +# define EC_R_INVALID_PENTANOMIAL_BASIS 132 +# define EC_R_INVALID_PRIVATE_KEY 123 +# define EC_R_INVALID_TRINOMIAL_BASIS 137 +# define EC_R_KDF_PARAMETER_ERROR 148 +# define EC_R_KEYS_NOT_SET 140 +# define EC_R_LADDER_POST_FAILURE 136 +# define EC_R_LADDER_PRE_FAILURE 153 +# define EC_R_LADDER_STEP_FAILURE 162 +# define EC_R_MISSING_PARAMETERS 124 +# define EC_R_MISSING_PRIVATE_KEY 125 +# define EC_R_NEED_NEW_SETUP_VALUES 157 +# define EC_R_NOT_A_NIST_PRIME 135 +# define EC_R_NOT_IMPLEMENTED 126 +# define EC_R_NOT_INITIALIZED 111 +# define EC_R_NO_PARAMETERS_SET 139 +# define EC_R_NO_PRIVATE_VALUE 154 +# define EC_R_OPERATION_NOT_SUPPORTED 152 +# define EC_R_PASSED_NULL_PARAMETER 134 +# define EC_R_PEER_KEY_ERROR 149 +# define EC_R_PKPARAMETERS2GROUP_FAILURE 127 +# define EC_R_POINT_ARITHMETIC_FAILURE 155 +# define EC_R_POINT_AT_INFINITY 106 +# define EC_R_POINT_COORDINATES_BLIND_FAILURE 163 +# define EC_R_POINT_IS_NOT_ON_CURVE 107 +# define EC_R_RANDOM_NUMBER_GENERATION_FAILED 158 +# define EC_R_SHARED_INFO_ERROR 150 +# define EC_R_SLOT_FULL 108 +# define EC_R_UNDEFINED_GENERATOR 113 +# define EC_R_UNDEFINED_ORDER 128 +# define EC_R_UNKNOWN_COFACTOR 164 +# define EC_R_UNKNOWN_GROUP 129 +# define EC_R_UNKNOWN_ORDER 114 +# define EC_R_UNSUPPORTED_FIELD 131 +# define EC_R_WRONG_CURVE_PARAMETERS 145 +# define EC_R_WRONG_ORDER 130 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/engine.h b/thrid-party/openssl/openssl/engine.h new file mode 100644 index 0000000000..0780f0fb5f --- /dev/null +++ b/thrid-party/openssl/openssl/engine.h @@ -0,0 +1,751 @@ +/* + * Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ENGINE_H +# define HEADER_ENGINE_H + +# include + +# ifndef OPENSSL_NO_ENGINE +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# include +# include +# include +# include +# include +# include +# include +# endif +# include +# include +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +/* + * These flags are used to control combinations of algorithm (methods) by + * bitwise "OR"ing. + */ +# define ENGINE_METHOD_RSA (unsigned int)0x0001 +# define ENGINE_METHOD_DSA (unsigned int)0x0002 +# define ENGINE_METHOD_DH (unsigned int)0x0004 +# define ENGINE_METHOD_RAND (unsigned int)0x0008 +# define ENGINE_METHOD_CIPHERS (unsigned int)0x0040 +# define ENGINE_METHOD_DIGESTS (unsigned int)0x0080 +# define ENGINE_METHOD_PKEY_METHS (unsigned int)0x0200 +# define ENGINE_METHOD_PKEY_ASN1_METHS (unsigned int)0x0400 +# define ENGINE_METHOD_EC (unsigned int)0x0800 +/* Obvious all-or-nothing cases. */ +# define ENGINE_METHOD_ALL (unsigned int)0xFFFF +# define ENGINE_METHOD_NONE (unsigned int)0x0000 + +/* + * This(ese) flag(s) controls behaviour of the ENGINE_TABLE mechanism used + * internally to control registration of ENGINE implementations, and can be + * set by ENGINE_set_table_flags(). The "NOINIT" flag prevents attempts to + * initialise registered ENGINEs if they are not already initialised. + */ +# define ENGINE_TABLE_FLAG_NOINIT (unsigned int)0x0001 + +/* ENGINE flags that can be set by ENGINE_set_flags(). */ +/* Not used */ +/* #define ENGINE_FLAGS_MALLOCED 0x0001 */ + +/* + * This flag is for ENGINEs that wish to handle the various 'CMD'-related + * control commands on their own. Without this flag, ENGINE_ctrl() handles + * these control commands on behalf of the ENGINE using their "cmd_defns" + * data. + */ +# define ENGINE_FLAGS_MANUAL_CMD_CTRL (int)0x0002 + +/* + * This flag is for ENGINEs who return new duplicate structures when found + * via "ENGINE_by_id()". When an ENGINE must store state (eg. if + * ENGINE_ctrl() commands are called in sequence as part of some stateful + * process like key-generation setup and execution), it can set this flag - + * then each attempt to obtain the ENGINE will result in it being copied into + * a new structure. Normally, ENGINEs don't declare this flag so + * ENGINE_by_id() just increments the existing ENGINE's structural reference + * count. + */ +# define ENGINE_FLAGS_BY_ID_COPY (int)0x0004 + +/* + * This flag if for an ENGINE that does not want its methods registered as + * part of ENGINE_register_all_complete() for example if the methods are not + * usable as default methods. + */ + +# define ENGINE_FLAGS_NO_REGISTER_ALL (int)0x0008 + +/* + * ENGINEs can support their own command types, and these flags are used in + * ENGINE_CTRL_GET_CMD_FLAGS to indicate to the caller what kind of input + * each command expects. Currently only numeric and string input is + * supported. If a control command supports none of the _NUMERIC, _STRING, or + * _NO_INPUT options, then it is regarded as an "internal" control command - + * and not for use in config setting situations. As such, they're not + * available to the ENGINE_ctrl_cmd_string() function, only raw ENGINE_ctrl() + * access. Changes to this list of 'command types' should be reflected + * carefully in ENGINE_cmd_is_executable() and ENGINE_ctrl_cmd_string(). + */ + +/* accepts a 'long' input value (3rd parameter to ENGINE_ctrl) */ +# define ENGINE_CMD_FLAG_NUMERIC (unsigned int)0x0001 +/* + * accepts string input (cast from 'void*' to 'const char *', 4th parameter + * to ENGINE_ctrl) + */ +# define ENGINE_CMD_FLAG_STRING (unsigned int)0x0002 +/* + * Indicates that the control command takes *no* input. Ie. the control + * command is unparameterised. + */ +# define ENGINE_CMD_FLAG_NO_INPUT (unsigned int)0x0004 +/* + * Indicates that the control command is internal. This control command won't + * be shown in any output, and is only usable through the ENGINE_ctrl_cmd() + * function. + */ +# define ENGINE_CMD_FLAG_INTERNAL (unsigned int)0x0008 + +/* + * NB: These 3 control commands are deprecated and should not be used. + * ENGINEs relying on these commands should compile conditional support for + * compatibility (eg. if these symbols are defined) but should also migrate + * the same functionality to their own ENGINE-specific control functions that + * can be "discovered" by calling applications. The fact these control + * commands wouldn't be "executable" (ie. usable by text-based config) + * doesn't change the fact that application code can find and use them + * without requiring per-ENGINE hacking. + */ + +/* + * These flags are used to tell the ctrl function what should be done. All + * command numbers are shared between all engines, even if some don't make + * sense to some engines. In such a case, they do nothing but return the + * error ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED. + */ +# define ENGINE_CTRL_SET_LOGSTREAM 1 +# define ENGINE_CTRL_SET_PASSWORD_CALLBACK 2 +# define ENGINE_CTRL_HUP 3/* Close and reinitialise + * any handles/connections + * etc. */ +# define ENGINE_CTRL_SET_USER_INTERFACE 4/* Alternative to callback */ +# define ENGINE_CTRL_SET_CALLBACK_DATA 5/* User-specific data, used + * when calling the password + * callback and the user + * interface */ +# define ENGINE_CTRL_LOAD_CONFIGURATION 6/* Load a configuration, + * given a string that + * represents a file name + * or so */ +# define ENGINE_CTRL_LOAD_SECTION 7/* Load data from a given + * section in the already + * loaded configuration */ + +/* + * These control commands allow an application to deal with an arbitrary + * engine in a dynamic way. Warn: Negative return values indicate errors FOR + * THESE COMMANDS because zero is used to indicate 'end-of-list'. Other + * commands, including ENGINE-specific command types, return zero for an + * error. An ENGINE can choose to implement these ctrl functions, and can + * internally manage things however it chooses - it does so by setting the + * ENGINE_FLAGS_MANUAL_CMD_CTRL flag (using ENGINE_set_flags()). Otherwise + * the ENGINE_ctrl() code handles this on the ENGINE's behalf using the + * cmd_defns data (set using ENGINE_set_cmd_defns()). This means an ENGINE's + * ctrl() handler need only implement its own commands - the above "meta" + * commands will be taken care of. + */ + +/* + * Returns non-zero if the supplied ENGINE has a ctrl() handler. If "not", + * then all the remaining control commands will return failure, so it is + * worth checking this first if the caller is trying to "discover" the + * engine's capabilities and doesn't want errors generated unnecessarily. + */ +# define ENGINE_CTRL_HAS_CTRL_FUNCTION 10 +/* + * Returns a positive command number for the first command supported by the + * engine. Returns zero if no ctrl commands are supported. + */ +# define ENGINE_CTRL_GET_FIRST_CMD_TYPE 11 +/* + * The 'long' argument specifies a command implemented by the engine, and the + * return value is the next command supported, or zero if there are no more. + */ +# define ENGINE_CTRL_GET_NEXT_CMD_TYPE 12 +/* + * The 'void*' argument is a command name (cast from 'const char *'), and the + * return value is the command that corresponds to it. + */ +# define ENGINE_CTRL_GET_CMD_FROM_NAME 13 +/* + * The next two allow a command to be converted into its corresponding string + * form. In each case, the 'long' argument supplies the command. In the + * NAME_LEN case, the return value is the length of the command name (not + * counting a trailing EOL). In the NAME case, the 'void*' argument must be a + * string buffer large enough, and it will be populated with the name of the + * command (WITH a trailing EOL). + */ +# define ENGINE_CTRL_GET_NAME_LEN_FROM_CMD 14 +# define ENGINE_CTRL_GET_NAME_FROM_CMD 15 +/* The next two are similar but give a "short description" of a command. */ +# define ENGINE_CTRL_GET_DESC_LEN_FROM_CMD 16 +# define ENGINE_CTRL_GET_DESC_FROM_CMD 17 +/* + * With this command, the return value is the OR'd combination of + * ENGINE_CMD_FLAG_*** values that indicate what kind of input a given + * engine-specific ctrl command expects. + */ +# define ENGINE_CTRL_GET_CMD_FLAGS 18 + +/* + * ENGINE implementations should start the numbering of their own control + * commands from this value. (ie. ENGINE_CMD_BASE, ENGINE_CMD_BASE + 1, etc). + */ +# define ENGINE_CMD_BASE 200 + +/* + * NB: These 2 nCipher "chil" control commands are deprecated, and their + * functionality is now available through ENGINE-specific control commands + * (exposed through the above-mentioned 'CMD'-handling). Code using these 2 + * commands should be migrated to the more general command handling before + * these are removed. + */ + +/* Flags specific to the nCipher "chil" engine */ +# define ENGINE_CTRL_CHIL_SET_FORKCHECK 100 + /* + * Depending on the value of the (long)i argument, this sets or + * unsets the SimpleForkCheck flag in the CHIL API to enable or + * disable checking and workarounds for applications that fork(). + */ +# define ENGINE_CTRL_CHIL_NO_LOCKING 101 + /* + * This prevents the initialisation function from providing mutex + * callbacks to the nCipher library. + */ + +/* + * If an ENGINE supports its own specific control commands and wishes the + * framework to handle the above 'ENGINE_CMD_***'-manipulation commands on + * its behalf, it should supply a null-terminated array of ENGINE_CMD_DEFN + * entries to ENGINE_set_cmd_defns(). It should also implement a ctrl() + * handler that supports the stated commands (ie. the "cmd_num" entries as + * described by the array). NB: The array must be ordered in increasing order + * of cmd_num. "null-terminated" means that the last ENGINE_CMD_DEFN element + * has cmd_num set to zero and/or cmd_name set to NULL. + */ +typedef struct ENGINE_CMD_DEFN_st { + unsigned int cmd_num; /* The command number */ + const char *cmd_name; /* The command name itself */ + const char *cmd_desc; /* A short description of the command */ + unsigned int cmd_flags; /* The input the command expects */ +} ENGINE_CMD_DEFN; + +/* Generic function pointer */ +typedef int (*ENGINE_GEN_FUNC_PTR) (void); +/* Generic function pointer taking no arguments */ +typedef int (*ENGINE_GEN_INT_FUNC_PTR) (ENGINE *); +/* Specific control function pointer */ +typedef int (*ENGINE_CTRL_FUNC_PTR) (ENGINE *, int, long, void *, + void (*f) (void)); +/* Generic load_key function pointer */ +typedef EVP_PKEY *(*ENGINE_LOAD_KEY_PTR)(ENGINE *, const char *, + UI_METHOD *ui_method, + void *callback_data); +typedef int (*ENGINE_SSL_CLIENT_CERT_PTR) (ENGINE *, SSL *ssl, + STACK_OF(X509_NAME) *ca_dn, + X509 **pcert, EVP_PKEY **pkey, + STACK_OF(X509) **pother, + UI_METHOD *ui_method, + void *callback_data); +/*- + * These callback types are for an ENGINE's handler for cipher and digest logic. + * These handlers have these prototypes; + * int foo(ENGINE *e, const EVP_CIPHER **cipher, const int **nids, int nid); + * int foo(ENGINE *e, const EVP_MD **digest, const int **nids, int nid); + * Looking at how to implement these handlers in the case of cipher support, if + * the framework wants the EVP_CIPHER for 'nid', it will call; + * foo(e, &p_evp_cipher, NULL, nid); (return zero for failure) + * If the framework wants a list of supported 'nid's, it will call; + * foo(e, NULL, &p_nids, 0); (returns number of 'nids' or -1 for error) + */ +/* + * Returns to a pointer to the array of supported cipher 'nid's. If the + * second parameter is non-NULL it is set to the size of the returned array. + */ +typedef int (*ENGINE_CIPHERS_PTR) (ENGINE *, const EVP_CIPHER **, + const int **, int); +typedef int (*ENGINE_DIGESTS_PTR) (ENGINE *, const EVP_MD **, const int **, + int); +typedef int (*ENGINE_PKEY_METHS_PTR) (ENGINE *, EVP_PKEY_METHOD **, + const int **, int); +typedef int (*ENGINE_PKEY_ASN1_METHS_PTR) (ENGINE *, EVP_PKEY_ASN1_METHOD **, + const int **, int); +/* + * STRUCTURE functions ... all of these functions deal with pointers to + * ENGINE structures where the pointers have a "structural reference". This + * means that their reference is to allowed access to the structure but it + * does not imply that the structure is functional. To simply increment or + * decrement the structural reference count, use ENGINE_by_id and + * ENGINE_free. NB: This is not required when iterating using ENGINE_get_next + * as it will automatically decrement the structural reference count of the + * "current" ENGINE and increment the structural reference count of the + * ENGINE it returns (unless it is NULL). + */ + +/* Get the first/last "ENGINE" type available. */ +ENGINE *ENGINE_get_first(void); +ENGINE *ENGINE_get_last(void); +/* Iterate to the next/previous "ENGINE" type (NULL = end of the list). */ +ENGINE *ENGINE_get_next(ENGINE *e); +ENGINE *ENGINE_get_prev(ENGINE *e); +/* Add another "ENGINE" type into the array. */ +int ENGINE_add(ENGINE *e); +/* Remove an existing "ENGINE" type from the array. */ +int ENGINE_remove(ENGINE *e); +/* Retrieve an engine from the list by its unique "id" value. */ +ENGINE *ENGINE_by_id(const char *id); + +#if OPENSSL_API_COMPAT < 0x10100000L +# define ENGINE_load_openssl() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_OPENSSL, NULL) +# define ENGINE_load_dynamic() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_DYNAMIC, NULL) +# ifndef OPENSSL_NO_STATIC_ENGINE +# define ENGINE_load_padlock() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_PADLOCK, NULL) +# define ENGINE_load_capi() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_CAPI, NULL) +# define ENGINE_load_afalg() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_AFALG, NULL) +# endif +# define ENGINE_load_cryptodev() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_CRYPTODEV, NULL) +# define ENGINE_load_rdrand() \ + OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_RDRAND, NULL) +#endif +void ENGINE_load_builtin_engines(void); + +/* + * Get and set global flags (ENGINE_TABLE_FLAG_***) for the implementation + * "registry" handling. + */ +unsigned int ENGINE_get_table_flags(void); +void ENGINE_set_table_flags(unsigned int flags); + +/*- Manage registration of ENGINEs per "table". For each type, there are 3 + * functions; + * ENGINE_register_***(e) - registers the implementation from 'e' (if it has one) + * ENGINE_unregister_***(e) - unregister the implementation from 'e' + * ENGINE_register_all_***() - call ENGINE_register_***() for each 'e' in the list + * Cleanup is automatically registered from each table when required. + */ + +int ENGINE_register_RSA(ENGINE *e); +void ENGINE_unregister_RSA(ENGINE *e); +void ENGINE_register_all_RSA(void); + +int ENGINE_register_DSA(ENGINE *e); +void ENGINE_unregister_DSA(ENGINE *e); +void ENGINE_register_all_DSA(void); + +int ENGINE_register_EC(ENGINE *e); +void ENGINE_unregister_EC(ENGINE *e); +void ENGINE_register_all_EC(void); + +int ENGINE_register_DH(ENGINE *e); +void ENGINE_unregister_DH(ENGINE *e); +void ENGINE_register_all_DH(void); + +int ENGINE_register_RAND(ENGINE *e); +void ENGINE_unregister_RAND(ENGINE *e); +void ENGINE_register_all_RAND(void); + +int ENGINE_register_ciphers(ENGINE *e); +void ENGINE_unregister_ciphers(ENGINE *e); +void ENGINE_register_all_ciphers(void); + +int ENGINE_register_digests(ENGINE *e); +void ENGINE_unregister_digests(ENGINE *e); +void ENGINE_register_all_digests(void); + +int ENGINE_register_pkey_meths(ENGINE *e); +void ENGINE_unregister_pkey_meths(ENGINE *e); +void ENGINE_register_all_pkey_meths(void); + +int ENGINE_register_pkey_asn1_meths(ENGINE *e); +void ENGINE_unregister_pkey_asn1_meths(ENGINE *e); +void ENGINE_register_all_pkey_asn1_meths(void); + +/* + * These functions register all support from the above categories. Note, use + * of these functions can result in static linkage of code your application + * may not need. If you only need a subset of functionality, consider using + * more selective initialisation. + */ +int ENGINE_register_complete(ENGINE *e); +int ENGINE_register_all_complete(void); + +/* + * Send parameterised control commands to the engine. The possibilities to + * send down an integer, a pointer to data or a function pointer are + * provided. Any of the parameters may or may not be NULL, depending on the + * command number. In actuality, this function only requires a structural + * (rather than functional) reference to an engine, but many control commands + * may require the engine be functional. The caller should be aware of trying + * commands that require an operational ENGINE, and only use functional + * references in such situations. + */ +int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void)); + +/* + * This function tests if an ENGINE-specific command is usable as a + * "setting". Eg. in an application's config file that gets processed through + * ENGINE_ctrl_cmd_string(). If this returns zero, it is not available to + * ENGINE_ctrl_cmd_string(), only ENGINE_ctrl(). + */ +int ENGINE_cmd_is_executable(ENGINE *e, int cmd); + +/* + * This function works like ENGINE_ctrl() with the exception of taking a + * command name instead of a command number, and can handle optional + * commands. See the comment on ENGINE_ctrl_cmd_string() for an explanation + * on how to use the cmd_name and cmd_optional. + */ +int ENGINE_ctrl_cmd(ENGINE *e, const char *cmd_name, + long i, void *p, void (*f) (void), int cmd_optional); + +/* + * This function passes a command-name and argument to an ENGINE. The + * cmd_name is converted to a command number and the control command is + * called using 'arg' as an argument (unless the ENGINE doesn't support such + * a command, in which case no control command is called). The command is + * checked for input flags, and if necessary the argument will be converted + * to a numeric value. If cmd_optional is non-zero, then if the ENGINE + * doesn't support the given cmd_name the return value will be success + * anyway. This function is intended for applications to use so that users + * (or config files) can supply engine-specific config data to the ENGINE at + * run-time to control behaviour of specific engines. As such, it shouldn't + * be used for calling ENGINE_ctrl() functions that return data, deal with + * binary data, or that are otherwise supposed to be used directly through + * ENGINE_ctrl() in application code. Any "return" data from an ENGINE_ctrl() + * operation in this function will be lost - the return value is interpreted + * as failure if the return value is zero, success otherwise, and this + * function returns a boolean value as a result. In other words, vendors of + * 'ENGINE'-enabled devices should write ENGINE implementations with + * parameterisations that work in this scheme, so that compliant ENGINE-based + * applications can work consistently with the same configuration for the + * same ENGINE-enabled devices, across applications. + */ +int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg, + int cmd_optional); + +/* + * These functions are useful for manufacturing new ENGINE structures. They + * don't address reference counting at all - one uses them to populate an + * ENGINE structure with personalised implementations of things prior to + * using it directly or adding it to the builtin ENGINE list in OpenSSL. + * These are also here so that the ENGINE structure doesn't have to be + * exposed and break binary compatibility! + */ +ENGINE *ENGINE_new(void); +int ENGINE_free(ENGINE *e); +int ENGINE_up_ref(ENGINE *e); +int ENGINE_set_id(ENGINE *e, const char *id); +int ENGINE_set_name(ENGINE *e, const char *name); +int ENGINE_set_RSA(ENGINE *e, const RSA_METHOD *rsa_meth); +int ENGINE_set_DSA(ENGINE *e, const DSA_METHOD *dsa_meth); +int ENGINE_set_EC(ENGINE *e, const EC_KEY_METHOD *ecdsa_meth); +int ENGINE_set_DH(ENGINE *e, const DH_METHOD *dh_meth); +int ENGINE_set_RAND(ENGINE *e, const RAND_METHOD *rand_meth); +int ENGINE_set_destroy_function(ENGINE *e, ENGINE_GEN_INT_FUNC_PTR destroy_f); +int ENGINE_set_init_function(ENGINE *e, ENGINE_GEN_INT_FUNC_PTR init_f); +int ENGINE_set_finish_function(ENGINE *e, ENGINE_GEN_INT_FUNC_PTR finish_f); +int ENGINE_set_ctrl_function(ENGINE *e, ENGINE_CTRL_FUNC_PTR ctrl_f); +int ENGINE_set_load_privkey_function(ENGINE *e, + ENGINE_LOAD_KEY_PTR loadpriv_f); +int ENGINE_set_load_pubkey_function(ENGINE *e, ENGINE_LOAD_KEY_PTR loadpub_f); +int ENGINE_set_load_ssl_client_cert_function(ENGINE *e, + ENGINE_SSL_CLIENT_CERT_PTR + loadssl_f); +int ENGINE_set_ciphers(ENGINE *e, ENGINE_CIPHERS_PTR f); +int ENGINE_set_digests(ENGINE *e, ENGINE_DIGESTS_PTR f); +int ENGINE_set_pkey_meths(ENGINE *e, ENGINE_PKEY_METHS_PTR f); +int ENGINE_set_pkey_asn1_meths(ENGINE *e, ENGINE_PKEY_ASN1_METHS_PTR f); +int ENGINE_set_flags(ENGINE *e, int flags); +int ENGINE_set_cmd_defns(ENGINE *e, const ENGINE_CMD_DEFN *defns); +/* These functions allow control over any per-structure ENGINE data. */ +#define ENGINE_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_ENGINE, l, p, newf, dupf, freef) +int ENGINE_set_ex_data(ENGINE *e, int idx, void *arg); +void *ENGINE_get_ex_data(const ENGINE *e, int idx); + +#if OPENSSL_API_COMPAT < 0x10100000L +/* + * This function previously cleaned up anything that needs it. Auto-deinit will + * now take care of it so it is no longer required to call this function. + */ +# define ENGINE_cleanup() while(0) continue +#endif + +/* + * These return values from within the ENGINE structure. These can be useful + * with functional references as well as structural references - it depends + * which you obtained. Using the result for functional purposes if you only + * obtained a structural reference may be problematic! + */ +const char *ENGINE_get_id(const ENGINE *e); +const char *ENGINE_get_name(const ENGINE *e); +const RSA_METHOD *ENGINE_get_RSA(const ENGINE *e); +const DSA_METHOD *ENGINE_get_DSA(const ENGINE *e); +const EC_KEY_METHOD *ENGINE_get_EC(const ENGINE *e); +const DH_METHOD *ENGINE_get_DH(const ENGINE *e); +const RAND_METHOD *ENGINE_get_RAND(const ENGINE *e); +ENGINE_GEN_INT_FUNC_PTR ENGINE_get_destroy_function(const ENGINE *e); +ENGINE_GEN_INT_FUNC_PTR ENGINE_get_init_function(const ENGINE *e); +ENGINE_GEN_INT_FUNC_PTR ENGINE_get_finish_function(const ENGINE *e); +ENGINE_CTRL_FUNC_PTR ENGINE_get_ctrl_function(const ENGINE *e); +ENGINE_LOAD_KEY_PTR ENGINE_get_load_privkey_function(const ENGINE *e); +ENGINE_LOAD_KEY_PTR ENGINE_get_load_pubkey_function(const ENGINE *e); +ENGINE_SSL_CLIENT_CERT_PTR ENGINE_get_ssl_client_cert_function(const ENGINE + *e); +ENGINE_CIPHERS_PTR ENGINE_get_ciphers(const ENGINE *e); +ENGINE_DIGESTS_PTR ENGINE_get_digests(const ENGINE *e); +ENGINE_PKEY_METHS_PTR ENGINE_get_pkey_meths(const ENGINE *e); +ENGINE_PKEY_ASN1_METHS_PTR ENGINE_get_pkey_asn1_meths(const ENGINE *e); +const EVP_CIPHER *ENGINE_get_cipher(ENGINE *e, int nid); +const EVP_MD *ENGINE_get_digest(ENGINE *e, int nid); +const EVP_PKEY_METHOD *ENGINE_get_pkey_meth(ENGINE *e, int nid); +const EVP_PKEY_ASN1_METHOD *ENGINE_get_pkey_asn1_meth(ENGINE *e, int nid); +const EVP_PKEY_ASN1_METHOD *ENGINE_get_pkey_asn1_meth_str(ENGINE *e, + const char *str, + int len); +const EVP_PKEY_ASN1_METHOD *ENGINE_pkey_asn1_find_str(ENGINE **pe, + const char *str, + int len); +const ENGINE_CMD_DEFN *ENGINE_get_cmd_defns(const ENGINE *e); +int ENGINE_get_flags(const ENGINE *e); + +/* + * FUNCTIONAL functions. These functions deal with ENGINE structures that + * have (or will) be initialised for use. Broadly speaking, the structural + * functions are useful for iterating the list of available engine types, + * creating new engine types, and other "list" operations. These functions + * actually deal with ENGINEs that are to be used. As such these functions + * can fail (if applicable) when particular engines are unavailable - eg. if + * a hardware accelerator is not attached or not functioning correctly. Each + * ENGINE has 2 reference counts; structural and functional. Every time a + * functional reference is obtained or released, a corresponding structural + * reference is automatically obtained or released too. + */ + +/* + * Initialise a engine type for use (or up its reference count if it's + * already in use). This will fail if the engine is not currently operational + * and cannot initialise. + */ +int ENGINE_init(ENGINE *e); +/* + * Free a functional reference to a engine type. This does not require a + * corresponding call to ENGINE_free as it also releases a structural + * reference. + */ +int ENGINE_finish(ENGINE *e); + +/* + * The following functions handle keys that are stored in some secondary + * location, handled by the engine. The storage may be on a card or + * whatever. + */ +EVP_PKEY *ENGINE_load_private_key(ENGINE *e, const char *key_id, + UI_METHOD *ui_method, void *callback_data); +EVP_PKEY *ENGINE_load_public_key(ENGINE *e, const char *key_id, + UI_METHOD *ui_method, void *callback_data); +int ENGINE_load_ssl_client_cert(ENGINE *e, SSL *s, + STACK_OF(X509_NAME) *ca_dn, X509 **pcert, + EVP_PKEY **ppkey, STACK_OF(X509) **pother, + UI_METHOD *ui_method, void *callback_data); + +/* + * This returns a pointer for the current ENGINE structure that is (by + * default) performing any RSA operations. The value returned is an + * incremented reference, so it should be free'd (ENGINE_finish) before it is + * discarded. + */ +ENGINE *ENGINE_get_default_RSA(void); +/* Same for the other "methods" */ +ENGINE *ENGINE_get_default_DSA(void); +ENGINE *ENGINE_get_default_EC(void); +ENGINE *ENGINE_get_default_DH(void); +ENGINE *ENGINE_get_default_RAND(void); +/* + * These functions can be used to get a functional reference to perform + * ciphering or digesting corresponding to "nid". + */ +ENGINE *ENGINE_get_cipher_engine(int nid); +ENGINE *ENGINE_get_digest_engine(int nid); +ENGINE *ENGINE_get_pkey_meth_engine(int nid); +ENGINE *ENGINE_get_pkey_asn1_meth_engine(int nid); + +/* + * This sets a new default ENGINE structure for performing RSA operations. If + * the result is non-zero (success) then the ENGINE structure will have had + * its reference count up'd so the caller should still free their own + * reference 'e'. + */ +int ENGINE_set_default_RSA(ENGINE *e); +int ENGINE_set_default_string(ENGINE *e, const char *def_list); +/* Same for the other "methods" */ +int ENGINE_set_default_DSA(ENGINE *e); +int ENGINE_set_default_EC(ENGINE *e); +int ENGINE_set_default_DH(ENGINE *e); +int ENGINE_set_default_RAND(ENGINE *e); +int ENGINE_set_default_ciphers(ENGINE *e); +int ENGINE_set_default_digests(ENGINE *e); +int ENGINE_set_default_pkey_meths(ENGINE *e); +int ENGINE_set_default_pkey_asn1_meths(ENGINE *e); + +/* + * The combination "set" - the flags are bitwise "OR"d from the + * ENGINE_METHOD_*** defines above. As with the "ENGINE_register_complete()" + * function, this function can result in unnecessary static linkage. If your + * application requires only specific functionality, consider using more + * selective functions. + */ +int ENGINE_set_default(ENGINE *e, unsigned int flags); + +void ENGINE_add_conf_module(void); + +/* Deprecated functions ... */ +/* int ENGINE_clear_defaults(void); */ + +/**************************/ +/* DYNAMIC ENGINE SUPPORT */ +/**************************/ + +/* Binary/behaviour compatibility levels */ +# define OSSL_DYNAMIC_VERSION (unsigned long)0x00030000 +/* + * Binary versions older than this are too old for us (whether we're a loader + * or a loadee) + */ +# define OSSL_DYNAMIC_OLDEST (unsigned long)0x00030000 + +/* + * When compiling an ENGINE entirely as an external shared library, loadable + * by the "dynamic" ENGINE, these types are needed. The 'dynamic_fns' + * structure type provides the calling application's (or library's) error + * functionality and memory management function pointers to the loaded + * library. These should be used/set in the loaded library code so that the + * loading application's 'state' will be used/changed in all operations. The + * 'static_state' pointer allows the loaded library to know if it shares the + * same static data as the calling application (or library), and thus whether + * these callbacks need to be set or not. + */ +typedef void *(*dyn_MEM_malloc_fn) (size_t, const char *, int); +typedef void *(*dyn_MEM_realloc_fn) (void *, size_t, const char *, int); +typedef void (*dyn_MEM_free_fn) (void *, const char *, int); +typedef struct st_dynamic_MEM_fns { + dyn_MEM_malloc_fn malloc_fn; + dyn_MEM_realloc_fn realloc_fn; + dyn_MEM_free_fn free_fn; +} dynamic_MEM_fns; +/* + * FIXME: Perhaps the memory and locking code (crypto.h) should declare and + * use these types so we (and any other dependent code) can simplify a bit?? + */ +/* The top-level structure */ +typedef struct st_dynamic_fns { + void *static_state; + dynamic_MEM_fns mem_fns; +} dynamic_fns; + +/* + * The version checking function should be of this prototype. NB: The + * ossl_version value passed in is the OSSL_DYNAMIC_VERSION of the loading + * code. If this function returns zero, it indicates a (potential) version + * incompatibility and the loaded library doesn't believe it can proceed. + * Otherwise, the returned value is the (latest) version supported by the + * loading library. The loader may still decide that the loaded code's + * version is unsatisfactory and could veto the load. The function is + * expected to be implemented with the symbol name "v_check", and a default + * implementation can be fully instantiated with + * IMPLEMENT_DYNAMIC_CHECK_FN(). + */ +typedef unsigned long (*dynamic_v_check_fn) (unsigned long ossl_version); +# define IMPLEMENT_DYNAMIC_CHECK_FN() \ + OPENSSL_EXPORT unsigned long v_check(unsigned long v); \ + OPENSSL_EXPORT unsigned long v_check(unsigned long v) { \ + if (v >= OSSL_DYNAMIC_OLDEST) return OSSL_DYNAMIC_VERSION; \ + return 0; } + +/* + * This function is passed the ENGINE structure to initialise with its own + * function and command settings. It should not adjust the structural or + * functional reference counts. If this function returns zero, (a) the load + * will be aborted, (b) the previous ENGINE state will be memcpy'd back onto + * the structure, and (c) the shared library will be unloaded. So + * implementations should do their own internal cleanup in failure + * circumstances otherwise they could leak. The 'id' parameter, if non-NULL, + * represents the ENGINE id that the loader is looking for. If this is NULL, + * the shared library can choose to return failure or to initialise a + * 'default' ENGINE. If non-NULL, the shared library must initialise only an + * ENGINE matching the passed 'id'. The function is expected to be + * implemented with the symbol name "bind_engine". A standard implementation + * can be instantiated with IMPLEMENT_DYNAMIC_BIND_FN(fn) where the parameter + * 'fn' is a callback function that populates the ENGINE structure and + * returns an int value (zero for failure). 'fn' should have prototype; + * [static] int fn(ENGINE *e, const char *id); + */ +typedef int (*dynamic_bind_engine) (ENGINE *e, const char *id, + const dynamic_fns *fns); +# define IMPLEMENT_DYNAMIC_BIND_FN(fn) \ + OPENSSL_EXPORT \ + int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns); \ + OPENSSL_EXPORT \ + int bind_engine(ENGINE *e, const char *id, const dynamic_fns *fns) { \ + if (ENGINE_get_static_state() == fns->static_state) goto skip_cbs; \ + CRYPTO_set_mem_functions(fns->mem_fns.malloc_fn, \ + fns->mem_fns.realloc_fn, \ + fns->mem_fns.free_fn); \ + skip_cbs: \ + if (!fn(e, id)) return 0; \ + return 1; } + +/* + * If the loading application (or library) and the loaded ENGINE library + * share the same static data (eg. they're both dynamically linked to the + * same libcrypto.so) we need a way to avoid trying to set system callbacks - + * this would fail, and for the same reason that it's unnecessary to try. If + * the loaded ENGINE has (or gets from through the loader) its own copy of + * the libcrypto static data, we will need to set the callbacks. The easiest + * way to detect this is to have a function that returns a pointer to some + * static data and let the loading application and loaded ENGINE compare + * their respective values. + */ +void *ENGINE_get_static_state(void); + +# if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +DEPRECATEDIN_1_1_0(void ENGINE_setup_bsd_cryptodev(void)) +# endif + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/engineerr.h b/thrid-party/openssl/openssl/engineerr.h new file mode 100644 index 0000000000..05e84bd2a2 --- /dev/null +++ b/thrid-party/openssl/openssl/engineerr.h @@ -0,0 +1,111 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ENGINEERR_H +# define HEADER_ENGINEERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_ENGINE + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_ENGINE_strings(void); + +/* + * ENGINE function codes. + */ +# define ENGINE_F_DIGEST_UPDATE 198 +# define ENGINE_F_DYNAMIC_CTRL 180 +# define ENGINE_F_DYNAMIC_GET_DATA_CTX 181 +# define ENGINE_F_DYNAMIC_LOAD 182 +# define ENGINE_F_DYNAMIC_SET_DATA_CTX 183 +# define ENGINE_F_ENGINE_ADD 105 +# define ENGINE_F_ENGINE_BY_ID 106 +# define ENGINE_F_ENGINE_CMD_IS_EXECUTABLE 170 +# define ENGINE_F_ENGINE_CTRL 142 +# define ENGINE_F_ENGINE_CTRL_CMD 178 +# define ENGINE_F_ENGINE_CTRL_CMD_STRING 171 +# define ENGINE_F_ENGINE_FINISH 107 +# define ENGINE_F_ENGINE_GET_CIPHER 185 +# define ENGINE_F_ENGINE_GET_DIGEST 186 +# define ENGINE_F_ENGINE_GET_FIRST 195 +# define ENGINE_F_ENGINE_GET_LAST 196 +# define ENGINE_F_ENGINE_GET_NEXT 115 +# define ENGINE_F_ENGINE_GET_PKEY_ASN1_METH 193 +# define ENGINE_F_ENGINE_GET_PKEY_METH 192 +# define ENGINE_F_ENGINE_GET_PREV 116 +# define ENGINE_F_ENGINE_INIT 119 +# define ENGINE_F_ENGINE_LIST_ADD 120 +# define ENGINE_F_ENGINE_LIST_REMOVE 121 +# define ENGINE_F_ENGINE_LOAD_PRIVATE_KEY 150 +# define ENGINE_F_ENGINE_LOAD_PUBLIC_KEY 151 +# define ENGINE_F_ENGINE_LOAD_SSL_CLIENT_CERT 194 +# define ENGINE_F_ENGINE_NEW 122 +# define ENGINE_F_ENGINE_PKEY_ASN1_FIND_STR 197 +# define ENGINE_F_ENGINE_REMOVE 123 +# define ENGINE_F_ENGINE_SET_DEFAULT_STRING 189 +# define ENGINE_F_ENGINE_SET_ID 129 +# define ENGINE_F_ENGINE_SET_NAME 130 +# define ENGINE_F_ENGINE_TABLE_REGISTER 184 +# define ENGINE_F_ENGINE_UNLOCKED_FINISH 191 +# define ENGINE_F_ENGINE_UP_REF 190 +# define ENGINE_F_INT_CLEANUP_ITEM 199 +# define ENGINE_F_INT_CTRL_HELPER 172 +# define ENGINE_F_INT_ENGINE_CONFIGURE 188 +# define ENGINE_F_INT_ENGINE_MODULE_INIT 187 +# define ENGINE_F_OSSL_HMAC_INIT 200 + +/* + * ENGINE reason codes. + */ +# define ENGINE_R_ALREADY_LOADED 100 +# define ENGINE_R_ARGUMENT_IS_NOT_A_NUMBER 133 +# define ENGINE_R_CMD_NOT_EXECUTABLE 134 +# define ENGINE_R_COMMAND_TAKES_INPUT 135 +# define ENGINE_R_COMMAND_TAKES_NO_INPUT 136 +# define ENGINE_R_CONFLICTING_ENGINE_ID 103 +# define ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED 119 +# define ENGINE_R_DSO_FAILURE 104 +# define ENGINE_R_DSO_NOT_FOUND 132 +# define ENGINE_R_ENGINES_SECTION_ERROR 148 +# define ENGINE_R_ENGINE_CONFIGURATION_ERROR 102 +# define ENGINE_R_ENGINE_IS_NOT_IN_LIST 105 +# define ENGINE_R_ENGINE_SECTION_ERROR 149 +# define ENGINE_R_FAILED_LOADING_PRIVATE_KEY 128 +# define ENGINE_R_FAILED_LOADING_PUBLIC_KEY 129 +# define ENGINE_R_FINISH_FAILED 106 +# define ENGINE_R_ID_OR_NAME_MISSING 108 +# define ENGINE_R_INIT_FAILED 109 +# define ENGINE_R_INTERNAL_LIST_ERROR 110 +# define ENGINE_R_INVALID_ARGUMENT 143 +# define ENGINE_R_INVALID_CMD_NAME 137 +# define ENGINE_R_INVALID_CMD_NUMBER 138 +# define ENGINE_R_INVALID_INIT_VALUE 151 +# define ENGINE_R_INVALID_STRING 150 +# define ENGINE_R_NOT_INITIALISED 117 +# define ENGINE_R_NOT_LOADED 112 +# define ENGINE_R_NO_CONTROL_FUNCTION 120 +# define ENGINE_R_NO_INDEX 144 +# define ENGINE_R_NO_LOAD_FUNCTION 125 +# define ENGINE_R_NO_REFERENCE 130 +# define ENGINE_R_NO_SUCH_ENGINE 116 +# define ENGINE_R_UNIMPLEMENTED_CIPHER 146 +# define ENGINE_R_UNIMPLEMENTED_DIGEST 147 +# define ENGINE_R_UNIMPLEMENTED_PUBLIC_KEY_METHOD 101 +# define ENGINE_R_VERSION_INCOMPATIBILITY 145 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/err.h b/thrid-party/openssl/openssl/err.h new file mode 100644 index 0000000000..b49f88129e --- /dev/null +++ b/thrid-party/openssl/openssl/err.h @@ -0,0 +1,274 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ERR_H +# define HEADER_ERR_H + +# include + +# ifndef OPENSSL_NO_STDIO +# include +# include +# endif + +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# ifndef OPENSSL_NO_ERR +# define ERR_PUT_error(a,b,c,d,e) ERR_put_error(a,b,c,d,e) +# else +# define ERR_PUT_error(a,b,c,d,e) ERR_put_error(a,b,c,NULL,0) +# endif + +# include + +# define ERR_TXT_MALLOCED 0x01 +# define ERR_TXT_STRING 0x02 + +# define ERR_FLAG_MARK 0x01 +# define ERR_FLAG_CLEAR 0x02 + +# define ERR_NUM_ERRORS 16 +typedef struct err_state_st { + int err_flags[ERR_NUM_ERRORS]; + unsigned long err_buffer[ERR_NUM_ERRORS]; + char *err_data[ERR_NUM_ERRORS]; + int err_data_flags[ERR_NUM_ERRORS]; + const char *err_file[ERR_NUM_ERRORS]; + int err_line[ERR_NUM_ERRORS]; + int top, bottom; +} ERR_STATE; + +/* library */ +# define ERR_LIB_NONE 1 +# define ERR_LIB_SYS 2 +# define ERR_LIB_BN 3 +# define ERR_LIB_RSA 4 +# define ERR_LIB_DH 5 +# define ERR_LIB_EVP 6 +# define ERR_LIB_BUF 7 +# define ERR_LIB_OBJ 8 +# define ERR_LIB_PEM 9 +# define ERR_LIB_DSA 10 +# define ERR_LIB_X509 11 +/* #define ERR_LIB_METH 12 */ +# define ERR_LIB_ASN1 13 +# define ERR_LIB_CONF 14 +# define ERR_LIB_CRYPTO 15 +# define ERR_LIB_EC 16 +# define ERR_LIB_SSL 20 +/* #define ERR_LIB_SSL23 21 */ +/* #define ERR_LIB_SSL2 22 */ +/* #define ERR_LIB_SSL3 23 */ +/* #define ERR_LIB_RSAREF 30 */ +/* #define ERR_LIB_PROXY 31 */ +# define ERR_LIB_BIO 32 +# define ERR_LIB_PKCS7 33 +# define ERR_LIB_X509V3 34 +# define ERR_LIB_PKCS12 35 +# define ERR_LIB_RAND 36 +# define ERR_LIB_DSO 37 +# define ERR_LIB_ENGINE 38 +# define ERR_LIB_OCSP 39 +# define ERR_LIB_UI 40 +# define ERR_LIB_COMP 41 +# define ERR_LIB_ECDSA 42 +# define ERR_LIB_ECDH 43 +# define ERR_LIB_OSSL_STORE 44 +# define ERR_LIB_FIPS 45 +# define ERR_LIB_CMS 46 +# define ERR_LIB_TS 47 +# define ERR_LIB_HMAC 48 +/* # define ERR_LIB_JPAKE 49 */ +# define ERR_LIB_CT 50 +# define ERR_LIB_ASYNC 51 +# define ERR_LIB_KDF 52 +# define ERR_LIB_SM2 53 + +# define ERR_LIB_USER 128 + +# define SYSerr(f,r) ERR_PUT_error(ERR_LIB_SYS,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define BNerr(f,r) ERR_PUT_error(ERR_LIB_BN,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define RSAerr(f,r) ERR_PUT_error(ERR_LIB_RSA,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define DHerr(f,r) ERR_PUT_error(ERR_LIB_DH,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define EVPerr(f,r) ERR_PUT_error(ERR_LIB_EVP,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define BUFerr(f,r) ERR_PUT_error(ERR_LIB_BUF,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define OBJerr(f,r) ERR_PUT_error(ERR_LIB_OBJ,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define PEMerr(f,r) ERR_PUT_error(ERR_LIB_PEM,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define DSAerr(f,r) ERR_PUT_error(ERR_LIB_DSA,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define X509err(f,r) ERR_PUT_error(ERR_LIB_X509,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ASN1err(f,r) ERR_PUT_error(ERR_LIB_ASN1,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define CONFerr(f,r) ERR_PUT_error(ERR_LIB_CONF,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define CRYPTOerr(f,r) ERR_PUT_error(ERR_LIB_CRYPTO,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ECerr(f,r) ERR_PUT_error(ERR_LIB_EC,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define SSLerr(f,r) ERR_PUT_error(ERR_LIB_SSL,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define BIOerr(f,r) ERR_PUT_error(ERR_LIB_BIO,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define PKCS7err(f,r) ERR_PUT_error(ERR_LIB_PKCS7,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define X509V3err(f,r) ERR_PUT_error(ERR_LIB_X509V3,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define PKCS12err(f,r) ERR_PUT_error(ERR_LIB_PKCS12,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define RANDerr(f,r) ERR_PUT_error(ERR_LIB_RAND,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define DSOerr(f,r) ERR_PUT_error(ERR_LIB_DSO,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ENGINEerr(f,r) ERR_PUT_error(ERR_LIB_ENGINE,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define OCSPerr(f,r) ERR_PUT_error(ERR_LIB_OCSP,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define UIerr(f,r) ERR_PUT_error(ERR_LIB_UI,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define COMPerr(f,r) ERR_PUT_error(ERR_LIB_COMP,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ECDSAerr(f,r) ERR_PUT_error(ERR_LIB_ECDSA,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ECDHerr(f,r) ERR_PUT_error(ERR_LIB_ECDH,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define OSSL_STOREerr(f,r) ERR_PUT_error(ERR_LIB_OSSL_STORE,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define FIPSerr(f,r) ERR_PUT_error(ERR_LIB_FIPS,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define CMSerr(f,r) ERR_PUT_error(ERR_LIB_CMS,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define TSerr(f,r) ERR_PUT_error(ERR_LIB_TS,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define HMACerr(f,r) ERR_PUT_error(ERR_LIB_HMAC,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define CTerr(f,r) ERR_PUT_error(ERR_LIB_CT,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define ASYNCerr(f,r) ERR_PUT_error(ERR_LIB_ASYNC,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define KDFerr(f,r) ERR_PUT_error(ERR_LIB_KDF,(f),(r),OPENSSL_FILE,OPENSSL_LINE) +# define SM2err(f,r) ERR_PUT_error(ERR_LIB_SM2,(f),(r),OPENSSL_FILE,OPENSSL_LINE) + +# define ERR_PACK(l,f,r) ( \ + (((unsigned int)(l) & 0x0FF) << 24L) | \ + (((unsigned int)(f) & 0xFFF) << 12L) | \ + (((unsigned int)(r) & 0xFFF) ) ) +# define ERR_GET_LIB(l) (int)(((l) >> 24L) & 0x0FFL) +# define ERR_GET_FUNC(l) (int)(((l) >> 12L) & 0xFFFL) +# define ERR_GET_REASON(l) (int)( (l) & 0xFFFL) +# define ERR_FATAL_ERROR(l) (int)( (l) & ERR_R_FATAL) + +/* OS functions */ +# define SYS_F_FOPEN 1 +# define SYS_F_CONNECT 2 +# define SYS_F_GETSERVBYNAME 3 +# define SYS_F_SOCKET 4 +# define SYS_F_IOCTLSOCKET 5 +# define SYS_F_BIND 6 +# define SYS_F_LISTEN 7 +# define SYS_F_ACCEPT 8 +# define SYS_F_WSASTARTUP 9/* Winsock stuff */ +# define SYS_F_OPENDIR 10 +# define SYS_F_FREAD 11 +# define SYS_F_GETADDRINFO 12 +# define SYS_F_GETNAMEINFO 13 +# define SYS_F_SETSOCKOPT 14 +# define SYS_F_GETSOCKOPT 15 +# define SYS_F_GETSOCKNAME 16 +# define SYS_F_GETHOSTBYNAME 17 +# define SYS_F_FFLUSH 18 +# define SYS_F_OPEN 19 +# define SYS_F_CLOSE 20 +# define SYS_F_IOCTL 21 +# define SYS_F_STAT 22 +# define SYS_F_FCNTL 23 +# define SYS_F_FSTAT 24 + +/* reasons */ +# define ERR_R_SYS_LIB ERR_LIB_SYS/* 2 */ +# define ERR_R_BN_LIB ERR_LIB_BN/* 3 */ +# define ERR_R_RSA_LIB ERR_LIB_RSA/* 4 */ +# define ERR_R_DH_LIB ERR_LIB_DH/* 5 */ +# define ERR_R_EVP_LIB ERR_LIB_EVP/* 6 */ +# define ERR_R_BUF_LIB ERR_LIB_BUF/* 7 */ +# define ERR_R_OBJ_LIB ERR_LIB_OBJ/* 8 */ +# define ERR_R_PEM_LIB ERR_LIB_PEM/* 9 */ +# define ERR_R_DSA_LIB ERR_LIB_DSA/* 10 */ +# define ERR_R_X509_LIB ERR_LIB_X509/* 11 */ +# define ERR_R_ASN1_LIB ERR_LIB_ASN1/* 13 */ +# define ERR_R_EC_LIB ERR_LIB_EC/* 16 */ +# define ERR_R_BIO_LIB ERR_LIB_BIO/* 32 */ +# define ERR_R_PKCS7_LIB ERR_LIB_PKCS7/* 33 */ +# define ERR_R_X509V3_LIB ERR_LIB_X509V3/* 34 */ +# define ERR_R_ENGINE_LIB ERR_LIB_ENGINE/* 38 */ +# define ERR_R_UI_LIB ERR_LIB_UI/* 40 */ +# define ERR_R_ECDSA_LIB ERR_LIB_ECDSA/* 42 */ +# define ERR_R_OSSL_STORE_LIB ERR_LIB_OSSL_STORE/* 44 */ + +# define ERR_R_NESTED_ASN1_ERROR 58 +# define ERR_R_MISSING_ASN1_EOS 63 + +/* fatal error */ +# define ERR_R_FATAL 64 +# define ERR_R_MALLOC_FAILURE (1|ERR_R_FATAL) +# define ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED (2|ERR_R_FATAL) +# define ERR_R_PASSED_NULL_PARAMETER (3|ERR_R_FATAL) +# define ERR_R_INTERNAL_ERROR (4|ERR_R_FATAL) +# define ERR_R_DISABLED (5|ERR_R_FATAL) +# define ERR_R_INIT_FAIL (6|ERR_R_FATAL) +# define ERR_R_PASSED_INVALID_ARGUMENT (7) +# define ERR_R_OPERATION_FAIL (8|ERR_R_FATAL) + +/* + * 99 is the maximum possible ERR_R_... code, higher values are reserved for + * the individual libraries + */ + +typedef struct ERR_string_data_st { + unsigned long error; + const char *string; +} ERR_STRING_DATA; + +DEFINE_LHASH_OF(ERR_STRING_DATA); + +void ERR_put_error(int lib, int func, int reason, const char *file, int line); +void ERR_set_error_data(char *data, int flags); + +unsigned long ERR_get_error(void); +unsigned long ERR_get_error_line(const char **file, int *line); +unsigned long ERR_get_error_line_data(const char **file, int *line, + const char **data, int *flags); +unsigned long ERR_peek_error(void); +unsigned long ERR_peek_error_line(const char **file, int *line); +unsigned long ERR_peek_error_line_data(const char **file, int *line, + const char **data, int *flags); +unsigned long ERR_peek_last_error(void); +unsigned long ERR_peek_last_error_line(const char **file, int *line); +unsigned long ERR_peek_last_error_line_data(const char **file, int *line, + const char **data, int *flags); +void ERR_clear_error(void); +char *ERR_error_string(unsigned long e, char *buf); +void ERR_error_string_n(unsigned long e, char *buf, size_t len); +const char *ERR_lib_error_string(unsigned long e); +const char *ERR_func_error_string(unsigned long e); +const char *ERR_reason_error_string(unsigned long e); +void ERR_print_errors_cb(int (*cb) (const char *str, size_t len, void *u), + void *u); +# ifndef OPENSSL_NO_STDIO +void ERR_print_errors_fp(FILE *fp); +# endif +void ERR_print_errors(BIO *bp); +void ERR_add_error_data(int num, ...); +void ERR_add_error_vdata(int num, va_list args); +int ERR_load_strings(int lib, ERR_STRING_DATA *str); +int ERR_load_strings_const(const ERR_STRING_DATA *str); +int ERR_unload_strings(int lib, ERR_STRING_DATA *str); +int ERR_load_ERR_strings(void); + +#if OPENSSL_API_COMPAT < 0x10100000L +# define ERR_load_crypto_strings() \ + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) +# define ERR_free_strings() while(0) continue +#endif + +DEPRECATEDIN_1_1_0(void ERR_remove_thread_state(void *)) +DEPRECATEDIN_1_0_0(void ERR_remove_state(unsigned long pid)) +ERR_STATE *ERR_get_state(void); + +int ERR_get_next_error_library(void); + +int ERR_set_mark(void); +int ERR_pop_to_mark(void); +int ERR_clear_last_mark(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/evp.h b/thrid-party/openssl/openssl/evp.h new file mode 100644 index 0000000000..545654a98b --- /dev/null +++ b/thrid-party/openssl/openssl/evp.h @@ -0,0 +1,1638 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_ENVELOPE_H +# define HEADER_ENVELOPE_H + +# include +# include +# include +# include +# include + +# define EVP_MAX_MD_SIZE 64/* longest known is SHA512 */ +# define EVP_MAX_KEY_LENGTH 64 +# define EVP_MAX_IV_LENGTH 16 +# define EVP_MAX_BLOCK_LENGTH 32 + +# define PKCS5_SALT_LEN 8 +/* Default PKCS#5 iteration count */ +# define PKCS5_DEFAULT_ITER 2048 + +# include + +# define EVP_PK_RSA 0x0001 +# define EVP_PK_DSA 0x0002 +# define EVP_PK_DH 0x0004 +# define EVP_PK_EC 0x0008 +# define EVP_PKT_SIGN 0x0010 +# define EVP_PKT_ENC 0x0020 +# define EVP_PKT_EXCH 0x0040 +# define EVP_PKS_RSA 0x0100 +# define EVP_PKS_DSA 0x0200 +# define EVP_PKS_EC 0x0400 + +# define EVP_PKEY_NONE NID_undef +# define EVP_PKEY_RSA NID_rsaEncryption +# define EVP_PKEY_RSA2 NID_rsa +# define EVP_PKEY_RSA_PSS NID_rsassaPss +# define EVP_PKEY_DSA NID_dsa +# define EVP_PKEY_DSA1 NID_dsa_2 +# define EVP_PKEY_DSA2 NID_dsaWithSHA +# define EVP_PKEY_DSA3 NID_dsaWithSHA1 +# define EVP_PKEY_DSA4 NID_dsaWithSHA1_2 +# define EVP_PKEY_DH NID_dhKeyAgreement +# define EVP_PKEY_DHX NID_dhpublicnumber +# define EVP_PKEY_EC NID_X9_62_id_ecPublicKey +# define EVP_PKEY_SM2 NID_sm2 +# define EVP_PKEY_HMAC NID_hmac +# define EVP_PKEY_CMAC NID_cmac +# define EVP_PKEY_SCRYPT NID_id_scrypt +# define EVP_PKEY_TLS1_PRF NID_tls1_prf +# define EVP_PKEY_HKDF NID_hkdf +# define EVP_PKEY_POLY1305 NID_poly1305 +# define EVP_PKEY_SIPHASH NID_siphash +# define EVP_PKEY_X25519 NID_X25519 +# define EVP_PKEY_ED25519 NID_ED25519 +# define EVP_PKEY_X448 NID_X448 +# define EVP_PKEY_ED448 NID_ED448 + +#ifdef __cplusplus +extern "C" { +#endif + +# define EVP_PKEY_MO_SIGN 0x0001 +# define EVP_PKEY_MO_VERIFY 0x0002 +# define EVP_PKEY_MO_ENCRYPT 0x0004 +# define EVP_PKEY_MO_DECRYPT 0x0008 + +# ifndef EVP_MD +EVP_MD *EVP_MD_meth_new(int md_type, int pkey_type); +EVP_MD *EVP_MD_meth_dup(const EVP_MD *md); +void EVP_MD_meth_free(EVP_MD *md); + +int EVP_MD_meth_set_input_blocksize(EVP_MD *md, int blocksize); +int EVP_MD_meth_set_result_size(EVP_MD *md, int resultsize); +int EVP_MD_meth_set_app_datasize(EVP_MD *md, int datasize); +int EVP_MD_meth_set_flags(EVP_MD *md, unsigned long flags); +int EVP_MD_meth_set_init(EVP_MD *md, int (*init)(EVP_MD_CTX *ctx)); +int EVP_MD_meth_set_update(EVP_MD *md, int (*update)(EVP_MD_CTX *ctx, + const void *data, + size_t count)); +int EVP_MD_meth_set_final(EVP_MD *md, int (*final)(EVP_MD_CTX *ctx, + unsigned char *md)); +int EVP_MD_meth_set_copy(EVP_MD *md, int (*copy)(EVP_MD_CTX *to, + const EVP_MD_CTX *from)); +int EVP_MD_meth_set_cleanup(EVP_MD *md, int (*cleanup)(EVP_MD_CTX *ctx)); +int EVP_MD_meth_set_ctrl(EVP_MD *md, int (*ctrl)(EVP_MD_CTX *ctx, int cmd, + int p1, void *p2)); + +int EVP_MD_meth_get_input_blocksize(const EVP_MD *md); +int EVP_MD_meth_get_result_size(const EVP_MD *md); +int EVP_MD_meth_get_app_datasize(const EVP_MD *md); +unsigned long EVP_MD_meth_get_flags(const EVP_MD *md); +int (*EVP_MD_meth_get_init(const EVP_MD *md))(EVP_MD_CTX *ctx); +int (*EVP_MD_meth_get_update(const EVP_MD *md))(EVP_MD_CTX *ctx, + const void *data, + size_t count); +int (*EVP_MD_meth_get_final(const EVP_MD *md))(EVP_MD_CTX *ctx, + unsigned char *md); +int (*EVP_MD_meth_get_copy(const EVP_MD *md))(EVP_MD_CTX *to, + const EVP_MD_CTX *from); +int (*EVP_MD_meth_get_cleanup(const EVP_MD *md))(EVP_MD_CTX *ctx); +int (*EVP_MD_meth_get_ctrl(const EVP_MD *md))(EVP_MD_CTX *ctx, int cmd, + int p1, void *p2); + +/* digest can only handle a single block */ +# define EVP_MD_FLAG_ONESHOT 0x0001 + +/* digest is extensible-output function, XOF */ +# define EVP_MD_FLAG_XOF 0x0002 + +/* DigestAlgorithmIdentifier flags... */ + +# define EVP_MD_FLAG_DIGALGID_MASK 0x0018 + +/* NULL or absent parameter accepted. Use NULL */ + +# define EVP_MD_FLAG_DIGALGID_NULL 0x0000 + +/* NULL or absent parameter accepted. Use NULL for PKCS#1 otherwise absent */ + +# define EVP_MD_FLAG_DIGALGID_ABSENT 0x0008 + +/* Custom handling via ctrl */ + +# define EVP_MD_FLAG_DIGALGID_CUSTOM 0x0018 + +/* Note if suitable for use in FIPS mode */ +# define EVP_MD_FLAG_FIPS 0x0400 + +/* Digest ctrls */ + +# define EVP_MD_CTRL_DIGALGID 0x1 +# define EVP_MD_CTRL_MICALG 0x2 +# define EVP_MD_CTRL_XOF_LEN 0x3 + +/* Minimum Algorithm specific ctrl value */ + +# define EVP_MD_CTRL_ALG_CTRL 0x1000 + +# endif /* !EVP_MD */ + +/* values for EVP_MD_CTX flags */ + +# define EVP_MD_CTX_FLAG_ONESHOT 0x0001/* digest update will be + * called once only */ +# define EVP_MD_CTX_FLAG_CLEANED 0x0002/* context has already been + * cleaned */ +# define EVP_MD_CTX_FLAG_REUSE 0x0004/* Don't free up ctx->md_data + * in EVP_MD_CTX_reset */ +/* + * FIPS and pad options are ignored in 1.0.0, definitions are here so we + * don't accidentally reuse the values for other purposes. + */ + +# define EVP_MD_CTX_FLAG_NON_FIPS_ALLOW 0x0008/* Allow use of non FIPS + * digest in FIPS mode */ + +/* + * The following PAD options are also currently ignored in 1.0.0, digest + * parameters are handled through EVP_DigestSign*() and EVP_DigestVerify*() + * instead. + */ +# define EVP_MD_CTX_FLAG_PAD_MASK 0xF0/* RSA mode to use */ +# define EVP_MD_CTX_FLAG_PAD_PKCS1 0x00/* PKCS#1 v1.5 mode */ +# define EVP_MD_CTX_FLAG_PAD_X931 0x10/* X9.31 mode */ +# define EVP_MD_CTX_FLAG_PAD_PSS 0x20/* PSS mode */ + +# define EVP_MD_CTX_FLAG_NO_INIT 0x0100/* Don't initialize md_data */ +/* + * Some functions such as EVP_DigestSign only finalise copies of internal + * contexts so additional data can be included after the finalisation call. + * This is inefficient if this functionality is not required: it is disabled + * if the following flag is set. + */ +# define EVP_MD_CTX_FLAG_FINALISE 0x0200 +/* NOTE: 0x0400 is reserved for internal usage in evp_int.h */ + +EVP_CIPHER *EVP_CIPHER_meth_new(int cipher_type, int block_size, int key_len); +EVP_CIPHER *EVP_CIPHER_meth_dup(const EVP_CIPHER *cipher); +void EVP_CIPHER_meth_free(EVP_CIPHER *cipher); + +int EVP_CIPHER_meth_set_iv_length(EVP_CIPHER *cipher, int iv_len); +int EVP_CIPHER_meth_set_flags(EVP_CIPHER *cipher, unsigned long flags); +int EVP_CIPHER_meth_set_impl_ctx_size(EVP_CIPHER *cipher, int ctx_size); +int EVP_CIPHER_meth_set_init(EVP_CIPHER *cipher, + int (*init) (EVP_CIPHER_CTX *ctx, + const unsigned char *key, + const unsigned char *iv, + int enc)); +int EVP_CIPHER_meth_set_do_cipher(EVP_CIPHER *cipher, + int (*do_cipher) (EVP_CIPHER_CTX *ctx, + unsigned char *out, + const unsigned char *in, + size_t inl)); +int EVP_CIPHER_meth_set_cleanup(EVP_CIPHER *cipher, + int (*cleanup) (EVP_CIPHER_CTX *)); +int EVP_CIPHER_meth_set_set_asn1_params(EVP_CIPHER *cipher, + int (*set_asn1_parameters) (EVP_CIPHER_CTX *, + ASN1_TYPE *)); +int EVP_CIPHER_meth_set_get_asn1_params(EVP_CIPHER *cipher, + int (*get_asn1_parameters) (EVP_CIPHER_CTX *, + ASN1_TYPE *)); +int EVP_CIPHER_meth_set_ctrl(EVP_CIPHER *cipher, + int (*ctrl) (EVP_CIPHER_CTX *, int type, + int arg, void *ptr)); + +int (*EVP_CIPHER_meth_get_init(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *ctx, + const unsigned char *key, + const unsigned char *iv, + int enc); +int (*EVP_CIPHER_meth_get_do_cipher(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *ctx, + unsigned char *out, + const unsigned char *in, + size_t inl); +int (*EVP_CIPHER_meth_get_cleanup(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *); +int (*EVP_CIPHER_meth_get_set_asn1_params(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *, + ASN1_TYPE *); +int (*EVP_CIPHER_meth_get_get_asn1_params(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *, + ASN1_TYPE *); +int (*EVP_CIPHER_meth_get_ctrl(const EVP_CIPHER *cipher))(EVP_CIPHER_CTX *, + int type, int arg, + void *ptr); + +/* Values for cipher flags */ + +/* Modes for ciphers */ + +# define EVP_CIPH_STREAM_CIPHER 0x0 +# define EVP_CIPH_ECB_MODE 0x1 +# define EVP_CIPH_CBC_MODE 0x2 +# define EVP_CIPH_CFB_MODE 0x3 +# define EVP_CIPH_OFB_MODE 0x4 +# define EVP_CIPH_CTR_MODE 0x5 +# define EVP_CIPH_GCM_MODE 0x6 +# define EVP_CIPH_CCM_MODE 0x7 +# define EVP_CIPH_XTS_MODE 0x10001 +# define EVP_CIPH_WRAP_MODE 0x10002 +# define EVP_CIPH_OCB_MODE 0x10003 +# define EVP_CIPH_MODE 0xF0007 +/* Set if variable length cipher */ +# define EVP_CIPH_VARIABLE_LENGTH 0x8 +/* Set if the iv handling should be done by the cipher itself */ +# define EVP_CIPH_CUSTOM_IV 0x10 +/* Set if the cipher's init() function should be called if key is NULL */ +# define EVP_CIPH_ALWAYS_CALL_INIT 0x20 +/* Call ctrl() to init cipher parameters */ +# define EVP_CIPH_CTRL_INIT 0x40 +/* Don't use standard key length function */ +# define EVP_CIPH_CUSTOM_KEY_LENGTH 0x80 +/* Don't use standard block padding */ +# define EVP_CIPH_NO_PADDING 0x100 +/* cipher handles random key generation */ +# define EVP_CIPH_RAND_KEY 0x200 +/* cipher has its own additional copying logic */ +# define EVP_CIPH_CUSTOM_COPY 0x400 +/* Don't use standard iv length function */ +# define EVP_CIPH_CUSTOM_IV_LENGTH 0x800 +/* Allow use default ASN1 get/set iv */ +# define EVP_CIPH_FLAG_DEFAULT_ASN1 0x1000 +/* Buffer length in bits not bytes: CFB1 mode only */ +# define EVP_CIPH_FLAG_LENGTH_BITS 0x2000 +/* Note if suitable for use in FIPS mode */ +# define EVP_CIPH_FLAG_FIPS 0x4000 +/* Allow non FIPS cipher in FIPS mode */ +# define EVP_CIPH_FLAG_NON_FIPS_ALLOW 0x8000 +/* + * Cipher handles any and all padding logic as well as finalisation. + */ +# define EVP_CIPH_FLAG_CUSTOM_CIPHER 0x100000 +# define EVP_CIPH_FLAG_AEAD_CIPHER 0x200000 +# define EVP_CIPH_FLAG_TLS1_1_MULTIBLOCK 0x400000 +/* Cipher can handle pipeline operations */ +# define EVP_CIPH_FLAG_PIPELINE 0X800000 + +/* + * Cipher context flag to indicate we can handle wrap mode: if allowed in + * older applications it could overflow buffers. + */ + +# define EVP_CIPHER_CTX_FLAG_WRAP_ALLOW 0x1 + +/* ctrl() values */ + +# define EVP_CTRL_INIT 0x0 +# define EVP_CTRL_SET_KEY_LENGTH 0x1 +# define EVP_CTRL_GET_RC2_KEY_BITS 0x2 +# define EVP_CTRL_SET_RC2_KEY_BITS 0x3 +# define EVP_CTRL_GET_RC5_ROUNDS 0x4 +# define EVP_CTRL_SET_RC5_ROUNDS 0x5 +# define EVP_CTRL_RAND_KEY 0x6 +# define EVP_CTRL_PBE_PRF_NID 0x7 +# define EVP_CTRL_COPY 0x8 +# define EVP_CTRL_AEAD_SET_IVLEN 0x9 +# define EVP_CTRL_AEAD_GET_TAG 0x10 +# define EVP_CTRL_AEAD_SET_TAG 0x11 +# define EVP_CTRL_AEAD_SET_IV_FIXED 0x12 +# define EVP_CTRL_GCM_SET_IVLEN EVP_CTRL_AEAD_SET_IVLEN +# define EVP_CTRL_GCM_GET_TAG EVP_CTRL_AEAD_GET_TAG +# define EVP_CTRL_GCM_SET_TAG EVP_CTRL_AEAD_SET_TAG +# define EVP_CTRL_GCM_SET_IV_FIXED EVP_CTRL_AEAD_SET_IV_FIXED +# define EVP_CTRL_GCM_IV_GEN 0x13 +# define EVP_CTRL_CCM_SET_IVLEN EVP_CTRL_AEAD_SET_IVLEN +# define EVP_CTRL_CCM_GET_TAG EVP_CTRL_AEAD_GET_TAG +# define EVP_CTRL_CCM_SET_TAG EVP_CTRL_AEAD_SET_TAG +# define EVP_CTRL_CCM_SET_IV_FIXED EVP_CTRL_AEAD_SET_IV_FIXED +# define EVP_CTRL_CCM_SET_L 0x14 +# define EVP_CTRL_CCM_SET_MSGLEN 0x15 +/* + * AEAD cipher deduces payload length and returns number of bytes required to + * store MAC and eventual padding. Subsequent call to EVP_Cipher even + * appends/verifies MAC. + */ +# define EVP_CTRL_AEAD_TLS1_AAD 0x16 +/* Used by composite AEAD ciphers, no-op in GCM, CCM... */ +# define EVP_CTRL_AEAD_SET_MAC_KEY 0x17 +/* Set the GCM invocation field, decrypt only */ +# define EVP_CTRL_GCM_SET_IV_INV 0x18 + +# define EVP_CTRL_TLS1_1_MULTIBLOCK_AAD 0x19 +# define EVP_CTRL_TLS1_1_MULTIBLOCK_ENCRYPT 0x1a +# define EVP_CTRL_TLS1_1_MULTIBLOCK_DECRYPT 0x1b +# define EVP_CTRL_TLS1_1_MULTIBLOCK_MAX_BUFSIZE 0x1c + +# define EVP_CTRL_SSL3_MASTER_SECRET 0x1d + +/* EVP_CTRL_SET_SBOX takes the char * specifying S-boxes */ +# define EVP_CTRL_SET_SBOX 0x1e +/* + * EVP_CTRL_SBOX_USED takes a 'size_t' and 'char *', pointing at a + * pre-allocated buffer with specified size + */ +# define EVP_CTRL_SBOX_USED 0x1f +/* EVP_CTRL_KEY_MESH takes 'size_t' number of bytes to mesh the key after, + * 0 switches meshing off + */ +# define EVP_CTRL_KEY_MESH 0x20 +/* EVP_CTRL_BLOCK_PADDING_MODE takes the padding mode */ +# define EVP_CTRL_BLOCK_PADDING_MODE 0x21 + +/* Set the output buffers to use for a pipelined operation */ +# define EVP_CTRL_SET_PIPELINE_OUTPUT_BUFS 0x22 +/* Set the input buffers to use for a pipelined operation */ +# define EVP_CTRL_SET_PIPELINE_INPUT_BUFS 0x23 +/* Set the input buffer lengths to use for a pipelined operation */ +# define EVP_CTRL_SET_PIPELINE_INPUT_LENS 0x24 + +# define EVP_CTRL_GET_IVLEN 0x25 + +/* Padding modes */ +#define EVP_PADDING_PKCS7 1 +#define EVP_PADDING_ISO7816_4 2 +#define EVP_PADDING_ANSI923 3 +#define EVP_PADDING_ISO10126 4 +#define EVP_PADDING_ZERO 5 + +/* RFC 5246 defines additional data to be 13 bytes in length */ +# define EVP_AEAD_TLS1_AAD_LEN 13 + +typedef struct { + unsigned char *out; + const unsigned char *inp; + size_t len; + unsigned int interleave; +} EVP_CTRL_TLS1_1_MULTIBLOCK_PARAM; + +/* GCM TLS constants */ +/* Length of fixed part of IV derived from PRF */ +# define EVP_GCM_TLS_FIXED_IV_LEN 4 +/* Length of explicit part of IV part of TLS records */ +# define EVP_GCM_TLS_EXPLICIT_IV_LEN 8 +/* Length of tag for TLS */ +# define EVP_GCM_TLS_TAG_LEN 16 + +/* CCM TLS constants */ +/* Length of fixed part of IV derived from PRF */ +# define EVP_CCM_TLS_FIXED_IV_LEN 4 +/* Length of explicit part of IV part of TLS records */ +# define EVP_CCM_TLS_EXPLICIT_IV_LEN 8 +/* Total length of CCM IV length for TLS */ +# define EVP_CCM_TLS_IV_LEN 12 +/* Length of tag for TLS */ +# define EVP_CCM_TLS_TAG_LEN 16 +/* Length of CCM8 tag for TLS */ +# define EVP_CCM8_TLS_TAG_LEN 8 + +/* Length of tag for TLS */ +# define EVP_CHACHAPOLY_TLS_TAG_LEN 16 + +typedef struct evp_cipher_info_st { + const EVP_CIPHER *cipher; + unsigned char iv[EVP_MAX_IV_LENGTH]; +} EVP_CIPHER_INFO; + + +/* Password based encryption function */ +typedef int (EVP_PBE_KEYGEN) (EVP_CIPHER_CTX *ctx, const char *pass, + int passlen, ASN1_TYPE *param, + const EVP_CIPHER *cipher, const EVP_MD *md, + int en_de); + +# ifndef OPENSSL_NO_RSA +# define EVP_PKEY_assign_RSA(pkey,rsa) EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ + (char *)(rsa)) +# endif + +# ifndef OPENSSL_NO_DSA +# define EVP_PKEY_assign_DSA(pkey,dsa) EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ + (char *)(dsa)) +# endif + +# ifndef OPENSSL_NO_DH +# define EVP_PKEY_assign_DH(pkey,dh) EVP_PKEY_assign((pkey),EVP_PKEY_DH,\ + (char *)(dh)) +# endif + +# ifndef OPENSSL_NO_EC +# define EVP_PKEY_assign_EC_KEY(pkey,eckey) EVP_PKEY_assign((pkey),EVP_PKEY_EC,\ + (char *)(eckey)) +# endif +# ifndef OPENSSL_NO_SIPHASH +# define EVP_PKEY_assign_SIPHASH(pkey,shkey) EVP_PKEY_assign((pkey),EVP_PKEY_SIPHASH,\ + (char *)(shkey)) +# endif + +# ifndef OPENSSL_NO_POLY1305 +# define EVP_PKEY_assign_POLY1305(pkey,polykey) EVP_PKEY_assign((pkey),EVP_PKEY_POLY1305,\ + (char *)(polykey)) +# endif + +/* Add some extra combinations */ +# define EVP_get_digestbynid(a) EVP_get_digestbyname(OBJ_nid2sn(a)) +# define EVP_get_digestbyobj(a) EVP_get_digestbynid(OBJ_obj2nid(a)) +# define EVP_get_cipherbynid(a) EVP_get_cipherbyname(OBJ_nid2sn(a)) +# define EVP_get_cipherbyobj(a) EVP_get_cipherbynid(OBJ_obj2nid(a)) + +int EVP_MD_type(const EVP_MD *md); +# define EVP_MD_nid(e) EVP_MD_type(e) +# define EVP_MD_name(e) OBJ_nid2sn(EVP_MD_nid(e)) +int EVP_MD_pkey_type(const EVP_MD *md); +int EVP_MD_size(const EVP_MD *md); +int EVP_MD_block_size(const EVP_MD *md); +unsigned long EVP_MD_flags(const EVP_MD *md); + +const EVP_MD *EVP_MD_CTX_md(const EVP_MD_CTX *ctx); +int (*EVP_MD_CTX_update_fn(EVP_MD_CTX *ctx))(EVP_MD_CTX *ctx, + const void *data, size_t count); +void EVP_MD_CTX_set_update_fn(EVP_MD_CTX *ctx, + int (*update) (EVP_MD_CTX *ctx, + const void *data, size_t count)); +# define EVP_MD_CTX_size(e) EVP_MD_size(EVP_MD_CTX_md(e)) +# define EVP_MD_CTX_block_size(e) EVP_MD_block_size(EVP_MD_CTX_md(e)) +# define EVP_MD_CTX_type(e) EVP_MD_type(EVP_MD_CTX_md(e)) +EVP_PKEY_CTX *EVP_MD_CTX_pkey_ctx(const EVP_MD_CTX *ctx); +void EVP_MD_CTX_set_pkey_ctx(EVP_MD_CTX *ctx, EVP_PKEY_CTX *pctx); +void *EVP_MD_CTX_md_data(const EVP_MD_CTX *ctx); + +int EVP_CIPHER_nid(const EVP_CIPHER *cipher); +# define EVP_CIPHER_name(e) OBJ_nid2sn(EVP_CIPHER_nid(e)) +int EVP_CIPHER_block_size(const EVP_CIPHER *cipher); +int EVP_CIPHER_impl_ctx_size(const EVP_CIPHER *cipher); +int EVP_CIPHER_key_length(const EVP_CIPHER *cipher); +int EVP_CIPHER_iv_length(const EVP_CIPHER *cipher); +unsigned long EVP_CIPHER_flags(const EVP_CIPHER *cipher); +# define EVP_CIPHER_mode(e) (EVP_CIPHER_flags(e) & EVP_CIPH_MODE) + +const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_encrypting(const EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_nid(const EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_block_size(const EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_key_length(const EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_iv_length(const EVP_CIPHER_CTX *ctx); +const unsigned char *EVP_CIPHER_CTX_iv(const EVP_CIPHER_CTX *ctx); +const unsigned char *EVP_CIPHER_CTX_original_iv(const EVP_CIPHER_CTX *ctx); +unsigned char *EVP_CIPHER_CTX_iv_noconst(EVP_CIPHER_CTX *ctx); +unsigned char *EVP_CIPHER_CTX_buf_noconst(EVP_CIPHER_CTX *ctx); +int EVP_CIPHER_CTX_num(const EVP_CIPHER_CTX *ctx); +void EVP_CIPHER_CTX_set_num(EVP_CIPHER_CTX *ctx, int num); +int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in); +void *EVP_CIPHER_CTX_get_app_data(const EVP_CIPHER_CTX *ctx); +void EVP_CIPHER_CTX_set_app_data(EVP_CIPHER_CTX *ctx, void *data); +void *EVP_CIPHER_CTX_get_cipher_data(const EVP_CIPHER_CTX *ctx); +void *EVP_CIPHER_CTX_set_cipher_data(EVP_CIPHER_CTX *ctx, void *cipher_data); +# define EVP_CIPHER_CTX_type(c) EVP_CIPHER_type(EVP_CIPHER_CTX_cipher(c)) +# if OPENSSL_API_COMPAT < 0x10100000L +# define EVP_CIPHER_CTX_flags(c) EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(c)) +# endif +# define EVP_CIPHER_CTX_mode(c) EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(c)) + +# define EVP_ENCODE_LENGTH(l) ((((l)+2)/3*4)+((l)/48+1)*2+80) +# define EVP_DECODE_LENGTH(l) (((l)+3)/4*3+80) + +# define EVP_SignInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c) +# define EVP_SignInit(a,b) EVP_DigestInit(a,b) +# define EVP_SignUpdate(a,b,c) EVP_DigestUpdate(a,b,c) +# define EVP_VerifyInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c) +# define EVP_VerifyInit(a,b) EVP_DigestInit(a,b) +# define EVP_VerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c) +# define EVP_OpenUpdate(a,b,c,d,e) EVP_DecryptUpdate(a,b,c,d,e) +# define EVP_SealUpdate(a,b,c,d,e) EVP_EncryptUpdate(a,b,c,d,e) +# define EVP_DigestSignUpdate(a,b,c) EVP_DigestUpdate(a,b,c) +# define EVP_DigestVerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c) + +# ifdef CONST_STRICT +void BIO_set_md(BIO *, const EVP_MD *md); +# else +# define BIO_set_md(b,md) BIO_ctrl(b,BIO_C_SET_MD,0,(char *)(md)) +# endif +# define BIO_get_md(b,mdp) BIO_ctrl(b,BIO_C_GET_MD,0,(char *)(mdp)) +# define BIO_get_md_ctx(b,mdcp) BIO_ctrl(b,BIO_C_GET_MD_CTX,0, \ + (char *)(mdcp)) +# define BIO_set_md_ctx(b,mdcp) BIO_ctrl(b,BIO_C_SET_MD_CTX,0, \ + (char *)(mdcp)) +# define BIO_get_cipher_status(b) BIO_ctrl(b,BIO_C_GET_CIPHER_STATUS,0,NULL) +# define BIO_get_cipher_ctx(b,c_pp) BIO_ctrl(b,BIO_C_GET_CIPHER_CTX,0, \ + (char *)(c_pp)) + +/*__owur*/ int EVP_Cipher(EVP_CIPHER_CTX *c, + unsigned char *out, + const unsigned char *in, unsigned int inl); + +# define EVP_add_cipher_alias(n,alias) \ + OBJ_NAME_add((alias),OBJ_NAME_TYPE_CIPHER_METH|OBJ_NAME_ALIAS,(n)) +# define EVP_add_digest_alias(n,alias) \ + OBJ_NAME_add((alias),OBJ_NAME_TYPE_MD_METH|OBJ_NAME_ALIAS,(n)) +# define EVP_delete_cipher_alias(alias) \ + OBJ_NAME_remove(alias,OBJ_NAME_TYPE_CIPHER_METH|OBJ_NAME_ALIAS); +# define EVP_delete_digest_alias(alias) \ + OBJ_NAME_remove(alias,OBJ_NAME_TYPE_MD_METH|OBJ_NAME_ALIAS); + +int EVP_MD_CTX_ctrl(EVP_MD_CTX *ctx, int cmd, int p1, void *p2); +EVP_MD_CTX *EVP_MD_CTX_new(void); +int EVP_MD_CTX_reset(EVP_MD_CTX *ctx); +void EVP_MD_CTX_free(EVP_MD_CTX *ctx); +# define EVP_MD_CTX_create() EVP_MD_CTX_new() +# define EVP_MD_CTX_init(ctx) EVP_MD_CTX_reset((ctx)) +# define EVP_MD_CTX_destroy(ctx) EVP_MD_CTX_free((ctx)) +__owur int EVP_MD_CTX_copy_ex(EVP_MD_CTX *out, const EVP_MD_CTX *in); +void EVP_MD_CTX_set_flags(EVP_MD_CTX *ctx, int flags); +void EVP_MD_CTX_clear_flags(EVP_MD_CTX *ctx, int flags); +int EVP_MD_CTX_test_flags(const EVP_MD_CTX *ctx, int flags); +__owur int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, + ENGINE *impl); +__owur int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, + size_t cnt); +__owur int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, + unsigned int *s); +__owur int EVP_Digest(const void *data, size_t count, + unsigned char *md, unsigned int *size, + const EVP_MD *type, ENGINE *impl); + +__owur int EVP_MD_CTX_copy(EVP_MD_CTX *out, const EVP_MD_CTX *in); +__owur int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type); +__owur int EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, + unsigned int *s); +__owur int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, + size_t len); + +int EVP_read_pw_string(char *buf, int length, const char *prompt, int verify); +int EVP_read_pw_string_min(char *buf, int minlen, int maxlen, + const char *prompt, int verify); +void EVP_set_pw_prompt(const char *prompt); +char *EVP_get_pw_prompt(void); + +__owur int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md, + const unsigned char *salt, + const unsigned char *data, int datal, int count, + unsigned char *key, unsigned char *iv); + +void EVP_CIPHER_CTX_set_flags(EVP_CIPHER_CTX *ctx, int flags); +void EVP_CIPHER_CTX_clear_flags(EVP_CIPHER_CTX *ctx, int flags); +int EVP_CIPHER_CTX_test_flags(const EVP_CIPHER_CTX *ctx, int flags); + +__owur int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, + const unsigned char *key, const unsigned char *iv); +/*__owur*/ int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, + const EVP_CIPHER *cipher, ENGINE *impl, + const unsigned char *key, + const unsigned char *iv); +/*__owur*/ int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl, const unsigned char *in, int inl); +/*__owur*/ int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl); +/*__owur*/ int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl); + +__owur int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, + const unsigned char *key, const unsigned char *iv); +/*__owur*/ int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, + const EVP_CIPHER *cipher, ENGINE *impl, + const unsigned char *key, + const unsigned char *iv); +/*__owur*/ int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl, const unsigned char *in, int inl); +__owur int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm, + int *outl); +/*__owur*/ int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, + int *outl); + +__owur int EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, + const unsigned char *key, const unsigned char *iv, + int enc); +/*__owur*/ int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, + const EVP_CIPHER *cipher, ENGINE *impl, + const unsigned char *key, + const unsigned char *iv, int enc); +__owur int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl, const unsigned char *in, int inl); +__owur int EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm, + int *outl); +__owur int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, + int *outl); + +__owur int EVP_SignFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s, + EVP_PKEY *pkey); + +__owur int EVP_DigestSign(EVP_MD_CTX *ctx, unsigned char *sigret, + size_t *siglen, const unsigned char *tbs, + size_t tbslen); + +__owur int EVP_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sigbuf, + unsigned int siglen, EVP_PKEY *pkey); + +__owur int EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, + size_t siglen, const unsigned char *tbs, + size_t tbslen); + +/*__owur*/ int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, + const EVP_MD *type, ENGINE *e, + EVP_PKEY *pkey); +__owur int EVP_DigestSignFinal(EVP_MD_CTX *ctx, unsigned char *sigret, + size_t *siglen); + +__owur int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, + const EVP_MD *type, ENGINE *e, + EVP_PKEY *pkey); +__owur int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sig, + size_t siglen); + +# ifndef OPENSSL_NO_RSA +__owur int EVP_OpenInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, + const unsigned char *ek, int ekl, + const unsigned char *iv, EVP_PKEY *priv); +__owur int EVP_OpenFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); + +__owur int EVP_SealInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, + unsigned char **ek, int *ekl, unsigned char *iv, + EVP_PKEY **pubk, int npubk); +__owur int EVP_SealFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); +# endif + +EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void); +void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx); +int EVP_ENCODE_CTX_copy(EVP_ENCODE_CTX *dctx, EVP_ENCODE_CTX *sctx); +int EVP_ENCODE_CTX_num(EVP_ENCODE_CTX *ctx); +void EVP_EncodeInit(EVP_ENCODE_CTX *ctx); +int EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, + const unsigned char *in, int inl); +void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl); +int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n); + +void EVP_DecodeInit(EVP_ENCODE_CTX *ctx); +int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, + const unsigned char *in, int inl); +int EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned + char *out, int *outl); +int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define EVP_CIPHER_CTX_init(c) EVP_CIPHER_CTX_reset(c) +# define EVP_CIPHER_CTX_cleanup(c) EVP_CIPHER_CTX_reset(c) +# endif +EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void); +int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c); +void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *c); +int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); +int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad); +int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); +int EVP_CIPHER_CTX_rand_key(EVP_CIPHER_CTX *ctx, unsigned char *key); + +const BIO_METHOD *BIO_f_md(void); +const BIO_METHOD *BIO_f_base64(void); +const BIO_METHOD *BIO_f_cipher(void); +const BIO_METHOD *BIO_f_reliable(void); +__owur int BIO_set_cipher(BIO *b, const EVP_CIPHER *c, const unsigned char *k, + const unsigned char *i, int enc); + +const EVP_MD *EVP_md_null(void); +# ifndef OPENSSL_NO_MD2 +const EVP_MD *EVP_md2(void); +# endif +# ifndef OPENSSL_NO_MD4 +const EVP_MD *EVP_md4(void); +# endif +# ifndef OPENSSL_NO_MD5 +const EVP_MD *EVP_md5(void); +const EVP_MD *EVP_md5_sha1(void); +# endif +# ifndef OPENSSL_NO_BLAKE2 +const EVP_MD *EVP_blake2b512(void); +const EVP_MD *EVP_blake2s256(void); +# endif +const EVP_MD *EVP_sha1(void); +const EVP_MD *EVP_sha224(void); +const EVP_MD *EVP_sha256(void); +const EVP_MD *EVP_sha384(void); +const EVP_MD *EVP_sha512(void); +const EVP_MD *EVP_sha512_224(void); +const EVP_MD *EVP_sha512_256(void); +const EVP_MD *EVP_sha3_224(void); +const EVP_MD *EVP_sha3_256(void); +const EVP_MD *EVP_sha3_384(void); +const EVP_MD *EVP_sha3_512(void); +const EVP_MD *EVP_shake128(void); +const EVP_MD *EVP_shake256(void); +# ifndef OPENSSL_NO_MDC2 +const EVP_MD *EVP_mdc2(void); +# endif +# ifndef OPENSSL_NO_RMD160 +const EVP_MD *EVP_ripemd160(void); +# endif +# ifndef OPENSSL_NO_WHIRLPOOL +const EVP_MD *EVP_whirlpool(void); +# endif +# ifndef OPENSSL_NO_SM3 +const EVP_MD *EVP_sm3(void); +# endif +const EVP_CIPHER *EVP_enc_null(void); /* does nothing :-) */ +# ifndef OPENSSL_NO_DES +const EVP_CIPHER *EVP_des_ecb(void); +const EVP_CIPHER *EVP_des_ede(void); +const EVP_CIPHER *EVP_des_ede3(void); +const EVP_CIPHER *EVP_des_ede_ecb(void); +const EVP_CIPHER *EVP_des_ede3_ecb(void); +const EVP_CIPHER *EVP_des_cfb64(void); +# define EVP_des_cfb EVP_des_cfb64 +const EVP_CIPHER *EVP_des_cfb1(void); +const EVP_CIPHER *EVP_des_cfb8(void); +const EVP_CIPHER *EVP_des_ede_cfb64(void); +# define EVP_des_ede_cfb EVP_des_ede_cfb64 +const EVP_CIPHER *EVP_des_ede3_cfb64(void); +# define EVP_des_ede3_cfb EVP_des_ede3_cfb64 +const EVP_CIPHER *EVP_des_ede3_cfb1(void); +const EVP_CIPHER *EVP_des_ede3_cfb8(void); +const EVP_CIPHER *EVP_des_ofb(void); +const EVP_CIPHER *EVP_des_ede_ofb(void); +const EVP_CIPHER *EVP_des_ede3_ofb(void); +const EVP_CIPHER *EVP_des_cbc(void); +const EVP_CIPHER *EVP_des_ede_cbc(void); +const EVP_CIPHER *EVP_des_ede3_cbc(void); +const EVP_CIPHER *EVP_desx_cbc(void); +const EVP_CIPHER *EVP_des_ede3_wrap(void); +/* + * This should now be supported through the dev_crypto ENGINE. But also, why + * are rc4 and md5 declarations made here inside a "NO_DES" precompiler + * branch? + */ +# endif +# ifndef OPENSSL_NO_RC4 +const EVP_CIPHER *EVP_rc4(void); +const EVP_CIPHER *EVP_rc4_40(void); +# ifndef OPENSSL_NO_MD5 +const EVP_CIPHER *EVP_rc4_hmac_md5(void); +# endif +# endif +# ifndef OPENSSL_NO_IDEA +const EVP_CIPHER *EVP_idea_ecb(void); +const EVP_CIPHER *EVP_idea_cfb64(void); +# define EVP_idea_cfb EVP_idea_cfb64 +const EVP_CIPHER *EVP_idea_ofb(void); +const EVP_CIPHER *EVP_idea_cbc(void); +# endif +# ifndef OPENSSL_NO_RC2 +const EVP_CIPHER *EVP_rc2_ecb(void); +const EVP_CIPHER *EVP_rc2_cbc(void); +const EVP_CIPHER *EVP_rc2_40_cbc(void); +const EVP_CIPHER *EVP_rc2_64_cbc(void); +const EVP_CIPHER *EVP_rc2_cfb64(void); +# define EVP_rc2_cfb EVP_rc2_cfb64 +const EVP_CIPHER *EVP_rc2_ofb(void); +# endif +# ifndef OPENSSL_NO_BF +const EVP_CIPHER *EVP_bf_ecb(void); +const EVP_CIPHER *EVP_bf_cbc(void); +const EVP_CIPHER *EVP_bf_cfb64(void); +# define EVP_bf_cfb EVP_bf_cfb64 +const EVP_CIPHER *EVP_bf_ofb(void); +# endif +# ifndef OPENSSL_NO_CAST +const EVP_CIPHER *EVP_cast5_ecb(void); +const EVP_CIPHER *EVP_cast5_cbc(void); +const EVP_CIPHER *EVP_cast5_cfb64(void); +# define EVP_cast5_cfb EVP_cast5_cfb64 +const EVP_CIPHER *EVP_cast5_ofb(void); +# endif +# ifndef OPENSSL_NO_RC5 +const EVP_CIPHER *EVP_rc5_32_12_16_cbc(void); +const EVP_CIPHER *EVP_rc5_32_12_16_ecb(void); +const EVP_CIPHER *EVP_rc5_32_12_16_cfb64(void); +# define EVP_rc5_32_12_16_cfb EVP_rc5_32_12_16_cfb64 +const EVP_CIPHER *EVP_rc5_32_12_16_ofb(void); +# endif +const EVP_CIPHER *EVP_aes_128_ecb(void); +const EVP_CIPHER *EVP_aes_128_cbc(void); +const EVP_CIPHER *EVP_aes_128_cfb1(void); +const EVP_CIPHER *EVP_aes_128_cfb8(void); +const EVP_CIPHER *EVP_aes_128_cfb128(void); +# define EVP_aes_128_cfb EVP_aes_128_cfb128 +const EVP_CIPHER *EVP_aes_128_ofb(void); +const EVP_CIPHER *EVP_aes_128_ctr(void); +const EVP_CIPHER *EVP_aes_128_ccm(void); +const EVP_CIPHER *EVP_aes_128_gcm(void); +const EVP_CIPHER *EVP_aes_128_xts(void); +const EVP_CIPHER *EVP_aes_128_wrap(void); +const EVP_CIPHER *EVP_aes_128_wrap_pad(void); +# ifndef OPENSSL_NO_OCB +const EVP_CIPHER *EVP_aes_128_ocb(void); +# endif +const EVP_CIPHER *EVP_aes_192_ecb(void); +const EVP_CIPHER *EVP_aes_192_cbc(void); +const EVP_CIPHER *EVP_aes_192_cfb1(void); +const EVP_CIPHER *EVP_aes_192_cfb8(void); +const EVP_CIPHER *EVP_aes_192_cfb128(void); +# define EVP_aes_192_cfb EVP_aes_192_cfb128 +const EVP_CIPHER *EVP_aes_192_ofb(void); +const EVP_CIPHER *EVP_aes_192_ctr(void); +const EVP_CIPHER *EVP_aes_192_ccm(void); +const EVP_CIPHER *EVP_aes_192_gcm(void); +const EVP_CIPHER *EVP_aes_192_wrap(void); +const EVP_CIPHER *EVP_aes_192_wrap_pad(void); +# ifndef OPENSSL_NO_OCB +const EVP_CIPHER *EVP_aes_192_ocb(void); +# endif +const EVP_CIPHER *EVP_aes_256_ecb(void); +const EVP_CIPHER *EVP_aes_256_cbc(void); +const EVP_CIPHER *EVP_aes_256_cfb1(void); +const EVP_CIPHER *EVP_aes_256_cfb8(void); +const EVP_CIPHER *EVP_aes_256_cfb128(void); +# define EVP_aes_256_cfb EVP_aes_256_cfb128 +const EVP_CIPHER *EVP_aes_256_ofb(void); +const EVP_CIPHER *EVP_aes_256_ctr(void); +const EVP_CIPHER *EVP_aes_256_ccm(void); +const EVP_CIPHER *EVP_aes_256_gcm(void); +const EVP_CIPHER *EVP_aes_256_xts(void); +const EVP_CIPHER *EVP_aes_256_wrap(void); +const EVP_CIPHER *EVP_aes_256_wrap_pad(void); +# ifndef OPENSSL_NO_OCB +const EVP_CIPHER *EVP_aes_256_ocb(void); +# endif +const EVP_CIPHER *EVP_aes_128_cbc_hmac_sha1(void); +const EVP_CIPHER *EVP_aes_256_cbc_hmac_sha1(void); +const EVP_CIPHER *EVP_aes_128_cbc_hmac_sha256(void); +const EVP_CIPHER *EVP_aes_256_cbc_hmac_sha256(void); +# ifndef OPENSSL_NO_ARIA +const EVP_CIPHER *EVP_aria_128_ecb(void); +const EVP_CIPHER *EVP_aria_128_cbc(void); +const EVP_CIPHER *EVP_aria_128_cfb1(void); +const EVP_CIPHER *EVP_aria_128_cfb8(void); +const EVP_CIPHER *EVP_aria_128_cfb128(void); +# define EVP_aria_128_cfb EVP_aria_128_cfb128 +const EVP_CIPHER *EVP_aria_128_ctr(void); +const EVP_CIPHER *EVP_aria_128_ofb(void); +const EVP_CIPHER *EVP_aria_128_gcm(void); +const EVP_CIPHER *EVP_aria_128_ccm(void); +const EVP_CIPHER *EVP_aria_192_ecb(void); +const EVP_CIPHER *EVP_aria_192_cbc(void); +const EVP_CIPHER *EVP_aria_192_cfb1(void); +const EVP_CIPHER *EVP_aria_192_cfb8(void); +const EVP_CIPHER *EVP_aria_192_cfb128(void); +# define EVP_aria_192_cfb EVP_aria_192_cfb128 +const EVP_CIPHER *EVP_aria_192_ctr(void); +const EVP_CIPHER *EVP_aria_192_ofb(void); +const EVP_CIPHER *EVP_aria_192_gcm(void); +const EVP_CIPHER *EVP_aria_192_ccm(void); +const EVP_CIPHER *EVP_aria_256_ecb(void); +const EVP_CIPHER *EVP_aria_256_cbc(void); +const EVP_CIPHER *EVP_aria_256_cfb1(void); +const EVP_CIPHER *EVP_aria_256_cfb8(void); +const EVP_CIPHER *EVP_aria_256_cfb128(void); +# define EVP_aria_256_cfb EVP_aria_256_cfb128 +const EVP_CIPHER *EVP_aria_256_ctr(void); +const EVP_CIPHER *EVP_aria_256_ofb(void); +const EVP_CIPHER *EVP_aria_256_gcm(void); +const EVP_CIPHER *EVP_aria_256_ccm(void); +# endif +# ifndef OPENSSL_NO_CAMELLIA +const EVP_CIPHER *EVP_camellia_128_ecb(void); +const EVP_CIPHER *EVP_camellia_128_cbc(void); +const EVP_CIPHER *EVP_camellia_128_cfb1(void); +const EVP_CIPHER *EVP_camellia_128_cfb8(void); +const EVP_CIPHER *EVP_camellia_128_cfb128(void); +# define EVP_camellia_128_cfb EVP_camellia_128_cfb128 +const EVP_CIPHER *EVP_camellia_128_ofb(void); +const EVP_CIPHER *EVP_camellia_128_ctr(void); +const EVP_CIPHER *EVP_camellia_192_ecb(void); +const EVP_CIPHER *EVP_camellia_192_cbc(void); +const EVP_CIPHER *EVP_camellia_192_cfb1(void); +const EVP_CIPHER *EVP_camellia_192_cfb8(void); +const EVP_CIPHER *EVP_camellia_192_cfb128(void); +# define EVP_camellia_192_cfb EVP_camellia_192_cfb128 +const EVP_CIPHER *EVP_camellia_192_ofb(void); +const EVP_CIPHER *EVP_camellia_192_ctr(void); +const EVP_CIPHER *EVP_camellia_256_ecb(void); +const EVP_CIPHER *EVP_camellia_256_cbc(void); +const EVP_CIPHER *EVP_camellia_256_cfb1(void); +const EVP_CIPHER *EVP_camellia_256_cfb8(void); +const EVP_CIPHER *EVP_camellia_256_cfb128(void); +# define EVP_camellia_256_cfb EVP_camellia_256_cfb128 +const EVP_CIPHER *EVP_camellia_256_ofb(void); +const EVP_CIPHER *EVP_camellia_256_ctr(void); +# endif +# ifndef OPENSSL_NO_CHACHA +const EVP_CIPHER *EVP_chacha20(void); +# ifndef OPENSSL_NO_POLY1305 +const EVP_CIPHER *EVP_chacha20_poly1305(void); +# endif +# endif + +# ifndef OPENSSL_NO_SEED +const EVP_CIPHER *EVP_seed_ecb(void); +const EVP_CIPHER *EVP_seed_cbc(void); +const EVP_CIPHER *EVP_seed_cfb128(void); +# define EVP_seed_cfb EVP_seed_cfb128 +const EVP_CIPHER *EVP_seed_ofb(void); +# endif + +# ifndef OPENSSL_NO_SM4 +const EVP_CIPHER *EVP_sm4_ecb(void); +const EVP_CIPHER *EVP_sm4_cbc(void); +const EVP_CIPHER *EVP_sm4_cfb128(void); +# define EVP_sm4_cfb EVP_sm4_cfb128 +const EVP_CIPHER *EVP_sm4_ofb(void); +const EVP_CIPHER *EVP_sm4_ctr(void); +# endif + +# if OPENSSL_API_COMPAT < 0x10100000L +# define OPENSSL_add_all_algorithms_conf() \ + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS \ + | OPENSSL_INIT_LOAD_CONFIG, NULL) +# define OPENSSL_add_all_algorithms_noconf() \ + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) + +# ifdef OPENSSL_LOAD_CONF +# define OpenSSL_add_all_algorithms() OPENSSL_add_all_algorithms_conf() +# else +# define OpenSSL_add_all_algorithms() OPENSSL_add_all_algorithms_noconf() +# endif + +# define OpenSSL_add_all_ciphers() \ + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS, NULL) +# define OpenSSL_add_all_digests() \ + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) + +# define EVP_cleanup() while(0) continue +# endif + +int EVP_add_cipher(const EVP_CIPHER *cipher); +int EVP_add_digest(const EVP_MD *digest); + +const EVP_CIPHER *EVP_get_cipherbyname(const char *name); +const EVP_MD *EVP_get_digestbyname(const char *name); + +void EVP_CIPHER_do_all(void (*fn) (const EVP_CIPHER *ciph, + const char *from, const char *to, void *x), + void *arg); +void EVP_CIPHER_do_all_sorted(void (*fn) + (const EVP_CIPHER *ciph, const char *from, + const char *to, void *x), void *arg); + +void EVP_MD_do_all(void (*fn) (const EVP_MD *ciph, + const char *from, const char *to, void *x), + void *arg); +void EVP_MD_do_all_sorted(void (*fn) + (const EVP_MD *ciph, const char *from, + const char *to, void *x), void *arg); + +int EVP_PKEY_decrypt_old(unsigned char *dec_key, + const unsigned char *enc_key, int enc_key_len, + EVP_PKEY *private_key); +int EVP_PKEY_encrypt_old(unsigned char *enc_key, + const unsigned char *key, int key_len, + EVP_PKEY *pub_key); +int EVP_PKEY_type(int type); +int EVP_PKEY_id(const EVP_PKEY *pkey); +int EVP_PKEY_base_id(const EVP_PKEY *pkey); +int EVP_PKEY_bits(const EVP_PKEY *pkey); +int EVP_PKEY_security_bits(const EVP_PKEY *pkey); +int EVP_PKEY_size(const EVP_PKEY *pkey); +int EVP_PKEY_set_type(EVP_PKEY *pkey, int type); +int EVP_PKEY_set_type_str(EVP_PKEY *pkey, const char *str, int len); +int EVP_PKEY_set_alias_type(EVP_PKEY *pkey, int type); +# ifndef OPENSSL_NO_ENGINE +int EVP_PKEY_set1_engine(EVP_PKEY *pkey, ENGINE *e); +ENGINE *EVP_PKEY_get0_engine(const EVP_PKEY *pkey); +# endif +int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key); +void *EVP_PKEY_get0(const EVP_PKEY *pkey); +const unsigned char *EVP_PKEY_get0_hmac(const EVP_PKEY *pkey, size_t *len); +# ifndef OPENSSL_NO_POLY1305 +const unsigned char *EVP_PKEY_get0_poly1305(const EVP_PKEY *pkey, size_t *len); +# endif +# ifndef OPENSSL_NO_SIPHASH +const unsigned char *EVP_PKEY_get0_siphash(const EVP_PKEY *pkey, size_t *len); +# endif + +# ifndef OPENSSL_NO_RSA +struct rsa_st; +int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, struct rsa_st *key); +struct rsa_st *EVP_PKEY_get0_RSA(EVP_PKEY *pkey); +struct rsa_st *EVP_PKEY_get1_RSA(EVP_PKEY *pkey); +# endif +# ifndef OPENSSL_NO_DSA +struct dsa_st; +int EVP_PKEY_set1_DSA(EVP_PKEY *pkey, struct dsa_st *key); +struct dsa_st *EVP_PKEY_get0_DSA(EVP_PKEY *pkey); +struct dsa_st *EVP_PKEY_get1_DSA(EVP_PKEY *pkey); +# endif +# ifndef OPENSSL_NO_DH +struct dh_st; +int EVP_PKEY_set1_DH(EVP_PKEY *pkey, struct dh_st *key); +struct dh_st *EVP_PKEY_get0_DH(EVP_PKEY *pkey); +struct dh_st *EVP_PKEY_get1_DH(EVP_PKEY *pkey); +# endif +# ifndef OPENSSL_NO_EC +struct ec_key_st; +int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey, struct ec_key_st *key); +struct ec_key_st *EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey); +struct ec_key_st *EVP_PKEY_get1_EC_KEY(EVP_PKEY *pkey); +# endif + +EVP_PKEY *EVP_PKEY_new(void); +int EVP_PKEY_up_ref(EVP_PKEY *pkey); +void EVP_PKEY_free(EVP_PKEY *pkey); + +EVP_PKEY *d2i_PublicKey(int type, EVP_PKEY **a, const unsigned char **pp, + long length); +int i2d_PublicKey(EVP_PKEY *a, unsigned char **pp); + +EVP_PKEY *d2i_PrivateKey(int type, EVP_PKEY **a, const unsigned char **pp, + long length); +EVP_PKEY *d2i_AutoPrivateKey(EVP_PKEY **a, const unsigned char **pp, + long length); +int i2d_PrivateKey(EVP_PKEY *a, unsigned char **pp); + +int EVP_PKEY_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from); +int EVP_PKEY_missing_parameters(const EVP_PKEY *pkey); +int EVP_PKEY_save_parameters(EVP_PKEY *pkey, int mode); +int EVP_PKEY_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b); + +int EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b); + +int EVP_PKEY_print_public(BIO *out, const EVP_PKEY *pkey, + int indent, ASN1_PCTX *pctx); +int EVP_PKEY_print_private(BIO *out, const EVP_PKEY *pkey, + int indent, ASN1_PCTX *pctx); +int EVP_PKEY_print_params(BIO *out, const EVP_PKEY *pkey, + int indent, ASN1_PCTX *pctx); + +int EVP_PKEY_get_default_digest_nid(EVP_PKEY *pkey, int *pnid); + +int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *pkey, + const unsigned char *pt, size_t ptlen); +size_t EVP_PKEY_get1_tls_encodedpoint(EVP_PKEY *pkey, unsigned char **ppt); + +int EVP_CIPHER_type(const EVP_CIPHER *ctx); + +/* calls methods */ +int EVP_CIPHER_param_to_asn1(EVP_CIPHER_CTX *c, ASN1_TYPE *type); +int EVP_CIPHER_asn1_to_param(EVP_CIPHER_CTX *c, ASN1_TYPE *type); + +/* These are used by EVP_CIPHER methods */ +int EVP_CIPHER_set_asn1_iv(EVP_CIPHER_CTX *c, ASN1_TYPE *type); +int EVP_CIPHER_get_asn1_iv(EVP_CIPHER_CTX *c, ASN1_TYPE *type); + +/* PKCS5 password based encryption */ +int PKCS5_PBE_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, + ASN1_TYPE *param, const EVP_CIPHER *cipher, + const EVP_MD *md, int en_de); +int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, + const unsigned char *salt, int saltlen, int iter, + int keylen, unsigned char *out); +int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, + const unsigned char *salt, int saltlen, int iter, + const EVP_MD *digest, int keylen, unsigned char *out); +int PKCS5_v2_PBE_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, + ASN1_TYPE *param, const EVP_CIPHER *cipher, + const EVP_MD *md, int en_de); + +#ifndef OPENSSL_NO_SCRYPT +int EVP_PBE_scrypt(const char *pass, size_t passlen, + const unsigned char *salt, size_t saltlen, + uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem, + unsigned char *key, size_t keylen); + +int PKCS5_v2_scrypt_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, + int passlen, ASN1_TYPE *param, + const EVP_CIPHER *c, const EVP_MD *md, int en_de); +#endif + +void PKCS5_PBE_add(void); + +int EVP_PBE_CipherInit(ASN1_OBJECT *pbe_obj, const char *pass, int passlen, + ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de); + +/* PBE type */ + +/* Can appear as the outermost AlgorithmIdentifier */ +# define EVP_PBE_TYPE_OUTER 0x0 +/* Is an PRF type OID */ +# define EVP_PBE_TYPE_PRF 0x1 +/* Is a PKCS#5 v2.0 KDF */ +# define EVP_PBE_TYPE_KDF 0x2 + +int EVP_PBE_alg_add_type(int pbe_type, int pbe_nid, int cipher_nid, + int md_nid, EVP_PBE_KEYGEN *keygen); +int EVP_PBE_alg_add(int nid, const EVP_CIPHER *cipher, const EVP_MD *md, + EVP_PBE_KEYGEN *keygen); +int EVP_PBE_find(int type, int pbe_nid, int *pcnid, int *pmnid, + EVP_PBE_KEYGEN **pkeygen); +void EVP_PBE_cleanup(void); +int EVP_PBE_get(int *ptype, int *ppbe_nid, size_t num); + +# define ASN1_PKEY_ALIAS 0x1 +# define ASN1_PKEY_DYNAMIC 0x2 +# define ASN1_PKEY_SIGPARAM_NULL 0x4 + +# define ASN1_PKEY_CTRL_PKCS7_SIGN 0x1 +# define ASN1_PKEY_CTRL_PKCS7_ENCRYPT 0x2 +# define ASN1_PKEY_CTRL_DEFAULT_MD_NID 0x3 +# define ASN1_PKEY_CTRL_CMS_SIGN 0x5 +# define ASN1_PKEY_CTRL_CMS_ENVELOPE 0x7 +# define ASN1_PKEY_CTRL_CMS_RI_TYPE 0x8 + +# define ASN1_PKEY_CTRL_SET1_TLS_ENCPT 0x9 +# define ASN1_PKEY_CTRL_GET1_TLS_ENCPT 0xa + +int EVP_PKEY_asn1_get_count(void); +const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_get0(int idx); +const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_find(ENGINE **pe, int type); +const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_find_str(ENGINE **pe, + const char *str, int len); +int EVP_PKEY_asn1_add0(const EVP_PKEY_ASN1_METHOD *ameth); +int EVP_PKEY_asn1_add_alias(int to, int from); +int EVP_PKEY_asn1_get0_info(int *ppkey_id, int *pkey_base_id, + int *ppkey_flags, const char **pinfo, + const char **ppem_str, + const EVP_PKEY_ASN1_METHOD *ameth); + +const EVP_PKEY_ASN1_METHOD *EVP_PKEY_get0_asn1(const EVP_PKEY *pkey); +EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_new(int id, int flags, + const char *pem_str, + const char *info); +void EVP_PKEY_asn1_copy(EVP_PKEY_ASN1_METHOD *dst, + const EVP_PKEY_ASN1_METHOD *src); +void EVP_PKEY_asn1_free(EVP_PKEY_ASN1_METHOD *ameth); +void EVP_PKEY_asn1_set_public(EVP_PKEY_ASN1_METHOD *ameth, + int (*pub_decode) (EVP_PKEY *pk, + X509_PUBKEY *pub), + int (*pub_encode) (X509_PUBKEY *pub, + const EVP_PKEY *pk), + int (*pub_cmp) (const EVP_PKEY *a, + const EVP_PKEY *b), + int (*pub_print) (BIO *out, + const EVP_PKEY *pkey, + int indent, ASN1_PCTX *pctx), + int (*pkey_size) (const EVP_PKEY *pk), + int (*pkey_bits) (const EVP_PKEY *pk)); +void EVP_PKEY_asn1_set_private(EVP_PKEY_ASN1_METHOD *ameth, + int (*priv_decode) (EVP_PKEY *pk, + const PKCS8_PRIV_KEY_INFO + *p8inf), + int (*priv_encode) (PKCS8_PRIV_KEY_INFO *p8, + const EVP_PKEY *pk), + int (*priv_print) (BIO *out, + const EVP_PKEY *pkey, + int indent, + ASN1_PCTX *pctx)); +void EVP_PKEY_asn1_set_param(EVP_PKEY_ASN1_METHOD *ameth, + int (*param_decode) (EVP_PKEY *pkey, + const unsigned char **pder, + int derlen), + int (*param_encode) (const EVP_PKEY *pkey, + unsigned char **pder), + int (*param_missing) (const EVP_PKEY *pk), + int (*param_copy) (EVP_PKEY *to, + const EVP_PKEY *from), + int (*param_cmp) (const EVP_PKEY *a, + const EVP_PKEY *b), + int (*param_print) (BIO *out, + const EVP_PKEY *pkey, + int indent, + ASN1_PCTX *pctx)); + +void EVP_PKEY_asn1_set_free(EVP_PKEY_ASN1_METHOD *ameth, + void (*pkey_free) (EVP_PKEY *pkey)); +void EVP_PKEY_asn1_set_ctrl(EVP_PKEY_ASN1_METHOD *ameth, + int (*pkey_ctrl) (EVP_PKEY *pkey, int op, + long arg1, void *arg2)); +void EVP_PKEY_asn1_set_item(EVP_PKEY_ASN1_METHOD *ameth, + int (*item_verify) (EVP_MD_CTX *ctx, + const ASN1_ITEM *it, + void *asn, + X509_ALGOR *a, + ASN1_BIT_STRING *sig, + EVP_PKEY *pkey), + int (*item_sign) (EVP_MD_CTX *ctx, + const ASN1_ITEM *it, + void *asn, + X509_ALGOR *alg1, + X509_ALGOR *alg2, + ASN1_BIT_STRING *sig)); + +void EVP_PKEY_asn1_set_siginf(EVP_PKEY_ASN1_METHOD *ameth, + int (*siginf_set) (X509_SIG_INFO *siginf, + const X509_ALGOR *alg, + const ASN1_STRING *sig)); + +void EVP_PKEY_asn1_set_check(EVP_PKEY_ASN1_METHOD *ameth, + int (*pkey_check) (const EVP_PKEY *pk)); + +void EVP_PKEY_asn1_set_public_check(EVP_PKEY_ASN1_METHOD *ameth, + int (*pkey_pub_check) (const EVP_PKEY *pk)); + +void EVP_PKEY_asn1_set_param_check(EVP_PKEY_ASN1_METHOD *ameth, + int (*pkey_param_check) (const EVP_PKEY *pk)); + +void EVP_PKEY_asn1_set_set_priv_key(EVP_PKEY_ASN1_METHOD *ameth, + int (*set_priv_key) (EVP_PKEY *pk, + const unsigned char + *priv, + size_t len)); +void EVP_PKEY_asn1_set_set_pub_key(EVP_PKEY_ASN1_METHOD *ameth, + int (*set_pub_key) (EVP_PKEY *pk, + const unsigned char *pub, + size_t len)); +void EVP_PKEY_asn1_set_get_priv_key(EVP_PKEY_ASN1_METHOD *ameth, + int (*get_priv_key) (const EVP_PKEY *pk, + unsigned char *priv, + size_t *len)); +void EVP_PKEY_asn1_set_get_pub_key(EVP_PKEY_ASN1_METHOD *ameth, + int (*get_pub_key) (const EVP_PKEY *pk, + unsigned char *pub, + size_t *len)); + +void EVP_PKEY_asn1_set_security_bits(EVP_PKEY_ASN1_METHOD *ameth, + int (*pkey_security_bits) (const EVP_PKEY + *pk)); + +# define EVP_PKEY_OP_UNDEFINED 0 +# define EVP_PKEY_OP_PARAMGEN (1<<1) +# define EVP_PKEY_OP_KEYGEN (1<<2) +# define EVP_PKEY_OP_SIGN (1<<3) +# define EVP_PKEY_OP_VERIFY (1<<4) +# define EVP_PKEY_OP_VERIFYRECOVER (1<<5) +# define EVP_PKEY_OP_SIGNCTX (1<<6) +# define EVP_PKEY_OP_VERIFYCTX (1<<7) +# define EVP_PKEY_OP_ENCRYPT (1<<8) +# define EVP_PKEY_OP_DECRYPT (1<<9) +# define EVP_PKEY_OP_DERIVE (1<<10) + +# define EVP_PKEY_OP_TYPE_SIG \ + (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY | EVP_PKEY_OP_VERIFYRECOVER \ + | EVP_PKEY_OP_SIGNCTX | EVP_PKEY_OP_VERIFYCTX) + +# define EVP_PKEY_OP_TYPE_CRYPT \ + (EVP_PKEY_OP_ENCRYPT | EVP_PKEY_OP_DECRYPT) + +# define EVP_PKEY_OP_TYPE_NOGEN \ + (EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT | EVP_PKEY_OP_DERIVE) + +# define EVP_PKEY_OP_TYPE_GEN \ + (EVP_PKEY_OP_PARAMGEN | EVP_PKEY_OP_KEYGEN) + +# define EVP_PKEY_CTX_set_signature_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_TYPE_SIG, \ + EVP_PKEY_CTRL_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_get_signature_md(ctx, pmd) \ + EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_TYPE_SIG, \ + EVP_PKEY_CTRL_GET_MD, 0, (void *)(pmd)) + +# define EVP_PKEY_CTX_set_mac_key(ctx, key, len) \ + EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_SET_MAC_KEY, len, (void *)(key)) + +# define EVP_PKEY_CTRL_MD 1 +# define EVP_PKEY_CTRL_PEER_KEY 2 + +# define EVP_PKEY_CTRL_PKCS7_ENCRYPT 3 +# define EVP_PKEY_CTRL_PKCS7_DECRYPT 4 + +# define EVP_PKEY_CTRL_PKCS7_SIGN 5 + +# define EVP_PKEY_CTRL_SET_MAC_KEY 6 + +# define EVP_PKEY_CTRL_DIGESTINIT 7 + +/* Used by GOST key encryption in TLS */ +# define EVP_PKEY_CTRL_SET_IV 8 + +# define EVP_PKEY_CTRL_CMS_ENCRYPT 9 +# define EVP_PKEY_CTRL_CMS_DECRYPT 10 +# define EVP_PKEY_CTRL_CMS_SIGN 11 + +# define EVP_PKEY_CTRL_CIPHER 12 + +# define EVP_PKEY_CTRL_GET_MD 13 + +# define EVP_PKEY_CTRL_SET_DIGEST_SIZE 14 + +# define EVP_PKEY_ALG_CTRL 0x1000 + +# define EVP_PKEY_FLAG_AUTOARGLEN 2 +/* + * Method handles all operations: don't assume any digest related defaults. + */ +# define EVP_PKEY_FLAG_SIGCTX_CUSTOM 4 + +const EVP_PKEY_METHOD *EVP_PKEY_meth_find(int type); +EVP_PKEY_METHOD *EVP_PKEY_meth_new(int id, int flags); +void EVP_PKEY_meth_get0_info(int *ppkey_id, int *pflags, + const EVP_PKEY_METHOD *meth); +void EVP_PKEY_meth_copy(EVP_PKEY_METHOD *dst, const EVP_PKEY_METHOD *src); +void EVP_PKEY_meth_free(EVP_PKEY_METHOD *pmeth); +int EVP_PKEY_meth_add0(const EVP_PKEY_METHOD *pmeth); +int EVP_PKEY_meth_remove(const EVP_PKEY_METHOD *pmeth); +size_t EVP_PKEY_meth_get_count(void); +const EVP_PKEY_METHOD *EVP_PKEY_meth_get0(size_t idx); + +EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); +EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e); +EVP_PKEY_CTX *EVP_PKEY_CTX_dup(EVP_PKEY_CTX *ctx); +void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); + +int EVP_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int keytype, int optype, + int cmd, int p1, void *p2); +int EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, + const char *value); +int EVP_PKEY_CTX_ctrl_uint64(EVP_PKEY_CTX *ctx, int keytype, int optype, + int cmd, uint64_t value); + +int EVP_PKEY_CTX_str2ctrl(EVP_PKEY_CTX *ctx, int cmd, const char *str); +int EVP_PKEY_CTX_hex2ctrl(EVP_PKEY_CTX *ctx, int cmd, const char *hex); + +int EVP_PKEY_CTX_md(EVP_PKEY_CTX *ctx, int optype, int cmd, const char *md); + +int EVP_PKEY_CTX_get_operation(EVP_PKEY_CTX *ctx); +void EVP_PKEY_CTX_set0_keygen_info(EVP_PKEY_CTX *ctx, int *dat, int datlen); + +EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e, + const unsigned char *key, int keylen); +EVP_PKEY *EVP_PKEY_new_raw_private_key(int type, ENGINE *e, + const unsigned char *priv, + size_t len); +EVP_PKEY *EVP_PKEY_new_raw_public_key(int type, ENGINE *e, + const unsigned char *pub, + size_t len); +int EVP_PKEY_get_raw_private_key(const EVP_PKEY *pkey, unsigned char *priv, + size_t *len); +int EVP_PKEY_get_raw_public_key(const EVP_PKEY *pkey, unsigned char *pub, + size_t *len); + +EVP_PKEY *EVP_PKEY_new_CMAC_key(ENGINE *e, const unsigned char *priv, + size_t len, const EVP_CIPHER *cipher); + +void EVP_PKEY_CTX_set_data(EVP_PKEY_CTX *ctx, void *data); +void *EVP_PKEY_CTX_get_data(EVP_PKEY_CTX *ctx); +EVP_PKEY *EVP_PKEY_CTX_get0_pkey(EVP_PKEY_CTX *ctx); + +EVP_PKEY *EVP_PKEY_CTX_get0_peerkey(EVP_PKEY_CTX *ctx); + +void EVP_PKEY_CTX_set_app_data(EVP_PKEY_CTX *ctx, void *data); +void *EVP_PKEY_CTX_get_app_data(EVP_PKEY_CTX *ctx); + +int EVP_PKEY_sign_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_sign(EVP_PKEY_CTX *ctx, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen); +int EVP_PKEY_verify_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_verify(EVP_PKEY_CTX *ctx, + const unsigned char *sig, size_t siglen, + const unsigned char *tbs, size_t tbslen); +int EVP_PKEY_verify_recover_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_verify_recover(EVP_PKEY_CTX *ctx, + unsigned char *rout, size_t *routlen, + const unsigned char *sig, size_t siglen); +int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, + unsigned char *out, size_t *outlen, + const unsigned char *in, size_t inlen); +int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, + unsigned char *out, size_t *outlen, + const unsigned char *in, size_t inlen); + +int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer); +int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen); + +typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx); + +int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_paramgen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); +int EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx); +int EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); +int EVP_PKEY_check(EVP_PKEY_CTX *ctx); +int EVP_PKEY_public_check(EVP_PKEY_CTX *ctx); +int EVP_PKEY_param_check(EVP_PKEY_CTX *ctx); + +void EVP_PKEY_CTX_set_cb(EVP_PKEY_CTX *ctx, EVP_PKEY_gen_cb *cb); +EVP_PKEY_gen_cb *EVP_PKEY_CTX_get_cb(EVP_PKEY_CTX *ctx); + +int EVP_PKEY_CTX_get_keygen_info(EVP_PKEY_CTX *ctx, int idx); + +void EVP_PKEY_meth_set_init(EVP_PKEY_METHOD *pmeth, + int (*init) (EVP_PKEY_CTX *ctx)); + +void EVP_PKEY_meth_set_copy(EVP_PKEY_METHOD *pmeth, + int (*copy) (EVP_PKEY_CTX *dst, + EVP_PKEY_CTX *src)); + +void EVP_PKEY_meth_set_cleanup(EVP_PKEY_METHOD *pmeth, + void (*cleanup) (EVP_PKEY_CTX *ctx)); + +void EVP_PKEY_meth_set_paramgen(EVP_PKEY_METHOD *pmeth, + int (*paramgen_init) (EVP_PKEY_CTX *ctx), + int (*paramgen) (EVP_PKEY_CTX *ctx, + EVP_PKEY *pkey)); + +void EVP_PKEY_meth_set_keygen(EVP_PKEY_METHOD *pmeth, + int (*keygen_init) (EVP_PKEY_CTX *ctx), + int (*keygen) (EVP_PKEY_CTX *ctx, + EVP_PKEY *pkey)); + +void EVP_PKEY_meth_set_sign(EVP_PKEY_METHOD *pmeth, + int (*sign_init) (EVP_PKEY_CTX *ctx), + int (*sign) (EVP_PKEY_CTX *ctx, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_set_verify(EVP_PKEY_METHOD *pmeth, + int (*verify_init) (EVP_PKEY_CTX *ctx), + int (*verify) (EVP_PKEY_CTX *ctx, + const unsigned char *sig, + size_t siglen, + const unsigned char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_set_verify_recover(EVP_PKEY_METHOD *pmeth, + int (*verify_recover_init) (EVP_PKEY_CTX + *ctx), + int (*verify_recover) (EVP_PKEY_CTX + *ctx, + unsigned char + *sig, + size_t *siglen, + const unsigned + char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_set_signctx(EVP_PKEY_METHOD *pmeth, + int (*signctx_init) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx), + int (*signctx) (EVP_PKEY_CTX *ctx, + unsigned char *sig, + size_t *siglen, + EVP_MD_CTX *mctx)); + +void EVP_PKEY_meth_set_verifyctx(EVP_PKEY_METHOD *pmeth, + int (*verifyctx_init) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx), + int (*verifyctx) (EVP_PKEY_CTX *ctx, + const unsigned char *sig, + int siglen, + EVP_MD_CTX *mctx)); + +void EVP_PKEY_meth_set_encrypt(EVP_PKEY_METHOD *pmeth, + int (*encrypt_init) (EVP_PKEY_CTX *ctx), + int (*encryptfn) (EVP_PKEY_CTX *ctx, + unsigned char *out, + size_t *outlen, + const unsigned char *in, + size_t inlen)); + +void EVP_PKEY_meth_set_decrypt(EVP_PKEY_METHOD *pmeth, + int (*decrypt_init) (EVP_PKEY_CTX *ctx), + int (*decrypt) (EVP_PKEY_CTX *ctx, + unsigned char *out, + size_t *outlen, + const unsigned char *in, + size_t inlen)); + +void EVP_PKEY_meth_set_derive(EVP_PKEY_METHOD *pmeth, + int (*derive_init) (EVP_PKEY_CTX *ctx), + int (*derive) (EVP_PKEY_CTX *ctx, + unsigned char *key, + size_t *keylen)); + +void EVP_PKEY_meth_set_ctrl(EVP_PKEY_METHOD *pmeth, + int (*ctrl) (EVP_PKEY_CTX *ctx, int type, int p1, + void *p2), + int (*ctrl_str) (EVP_PKEY_CTX *ctx, + const char *type, + const char *value)); + +void EVP_PKEY_meth_set_check(EVP_PKEY_METHOD *pmeth, + int (*check) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_set_public_check(EVP_PKEY_METHOD *pmeth, + int (*check) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_set_param_check(EVP_PKEY_METHOD *pmeth, + int (*check) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_set_digest_custom(EVP_PKEY_METHOD *pmeth, + int (*digest_custom) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx)); + +void EVP_PKEY_meth_get_init(const EVP_PKEY_METHOD *pmeth, + int (**pinit) (EVP_PKEY_CTX *ctx)); + +void EVP_PKEY_meth_get_copy(const EVP_PKEY_METHOD *pmeth, + int (**pcopy) (EVP_PKEY_CTX *dst, + EVP_PKEY_CTX *src)); + +void EVP_PKEY_meth_get_cleanup(const EVP_PKEY_METHOD *pmeth, + void (**pcleanup) (EVP_PKEY_CTX *ctx)); + +void EVP_PKEY_meth_get_paramgen(const EVP_PKEY_METHOD *pmeth, + int (**pparamgen_init) (EVP_PKEY_CTX *ctx), + int (**pparamgen) (EVP_PKEY_CTX *ctx, + EVP_PKEY *pkey)); + +void EVP_PKEY_meth_get_keygen(const EVP_PKEY_METHOD *pmeth, + int (**pkeygen_init) (EVP_PKEY_CTX *ctx), + int (**pkeygen) (EVP_PKEY_CTX *ctx, + EVP_PKEY *pkey)); + +void EVP_PKEY_meth_get_sign(const EVP_PKEY_METHOD *pmeth, + int (**psign_init) (EVP_PKEY_CTX *ctx), + int (**psign) (EVP_PKEY_CTX *ctx, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_get_verify(const EVP_PKEY_METHOD *pmeth, + int (**pverify_init) (EVP_PKEY_CTX *ctx), + int (**pverify) (EVP_PKEY_CTX *ctx, + const unsigned char *sig, + size_t siglen, + const unsigned char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_get_verify_recover(const EVP_PKEY_METHOD *pmeth, + int (**pverify_recover_init) (EVP_PKEY_CTX + *ctx), + int (**pverify_recover) (EVP_PKEY_CTX + *ctx, + unsigned char + *sig, + size_t *siglen, + const unsigned + char *tbs, + size_t tbslen)); + +void EVP_PKEY_meth_get_signctx(const EVP_PKEY_METHOD *pmeth, + int (**psignctx_init) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx), + int (**psignctx) (EVP_PKEY_CTX *ctx, + unsigned char *sig, + size_t *siglen, + EVP_MD_CTX *mctx)); + +void EVP_PKEY_meth_get_verifyctx(const EVP_PKEY_METHOD *pmeth, + int (**pverifyctx_init) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx), + int (**pverifyctx) (EVP_PKEY_CTX *ctx, + const unsigned char *sig, + int siglen, + EVP_MD_CTX *mctx)); + +void EVP_PKEY_meth_get_encrypt(const EVP_PKEY_METHOD *pmeth, + int (**pencrypt_init) (EVP_PKEY_CTX *ctx), + int (**pencryptfn) (EVP_PKEY_CTX *ctx, + unsigned char *out, + size_t *outlen, + const unsigned char *in, + size_t inlen)); + +void EVP_PKEY_meth_get_decrypt(const EVP_PKEY_METHOD *pmeth, + int (**pdecrypt_init) (EVP_PKEY_CTX *ctx), + int (**pdecrypt) (EVP_PKEY_CTX *ctx, + unsigned char *out, + size_t *outlen, + const unsigned char *in, + size_t inlen)); + +void EVP_PKEY_meth_get_derive(const EVP_PKEY_METHOD *pmeth, + int (**pderive_init) (EVP_PKEY_CTX *ctx), + int (**pderive) (EVP_PKEY_CTX *ctx, + unsigned char *key, + size_t *keylen)); + +void EVP_PKEY_meth_get_ctrl(const EVP_PKEY_METHOD *pmeth, + int (**pctrl) (EVP_PKEY_CTX *ctx, int type, int p1, + void *p2), + int (**pctrl_str) (EVP_PKEY_CTX *ctx, + const char *type, + const char *value)); + +void EVP_PKEY_meth_get_check(const EVP_PKEY_METHOD *pmeth, + int (**pcheck) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_get_public_check(const EVP_PKEY_METHOD *pmeth, + int (**pcheck) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_get_param_check(const EVP_PKEY_METHOD *pmeth, + int (**pcheck) (EVP_PKEY *pkey)); + +void EVP_PKEY_meth_get_digest_custom(EVP_PKEY_METHOD *pmeth, + int (**pdigest_custom) (EVP_PKEY_CTX *ctx, + EVP_MD_CTX *mctx)); +void EVP_add_alg_module(void); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/evperr.h b/thrid-party/openssl/openssl/evperr.h new file mode 100644 index 0000000000..6a651f5563 --- /dev/null +++ b/thrid-party/openssl/openssl/evperr.h @@ -0,0 +1,204 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_EVPERR_H +# define HEADER_EVPERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_EVP_strings(void); + +/* + * EVP function codes. + */ +# define EVP_F_AESNI_INIT_KEY 165 +# define EVP_F_AESNI_XTS_INIT_KEY 207 +# define EVP_F_AES_GCM_CTRL 196 +# define EVP_F_AES_INIT_KEY 133 +# define EVP_F_AES_OCB_CIPHER 169 +# define EVP_F_AES_T4_INIT_KEY 178 +# define EVP_F_AES_T4_XTS_INIT_KEY 208 +# define EVP_F_AES_WRAP_CIPHER 170 +# define EVP_F_AES_XTS_INIT_KEY 209 +# define EVP_F_ALG_MODULE_INIT 177 +# define EVP_F_ARIA_CCM_INIT_KEY 175 +# define EVP_F_ARIA_GCM_CTRL 197 +# define EVP_F_ARIA_GCM_INIT_KEY 176 +# define EVP_F_ARIA_INIT_KEY 185 +# define EVP_F_B64_NEW 198 +# define EVP_F_CAMELLIA_INIT_KEY 159 +# define EVP_F_CHACHA20_POLY1305_CTRL 182 +# define EVP_F_CMLL_T4_INIT_KEY 179 +# define EVP_F_DES_EDE3_WRAP_CIPHER 171 +# define EVP_F_DO_SIGVER_INIT 161 +# define EVP_F_ENC_NEW 199 +# define EVP_F_EVP_CIPHERINIT_EX 123 +# define EVP_F_EVP_CIPHER_ASN1_TO_PARAM 204 +# define EVP_F_EVP_CIPHER_CTX_COPY 163 +# define EVP_F_EVP_CIPHER_CTX_CTRL 124 +# define EVP_F_EVP_CIPHER_CTX_SET_KEY_LENGTH 122 +# define EVP_F_EVP_CIPHER_PARAM_TO_ASN1 205 +# define EVP_F_EVP_DECRYPTFINAL_EX 101 +# define EVP_F_EVP_DECRYPTUPDATE 166 +# define EVP_F_EVP_DIGESTFINALXOF 174 +# define EVP_F_EVP_DIGESTINIT_EX 128 +# define EVP_F_EVP_ENCRYPTDECRYPTUPDATE 219 +# define EVP_F_EVP_ENCRYPTFINAL_EX 127 +# define EVP_F_EVP_ENCRYPTUPDATE 167 +# define EVP_F_EVP_MD_CTX_COPY_EX 110 +# define EVP_F_EVP_MD_SIZE 162 +# define EVP_F_EVP_OPENINIT 102 +# define EVP_F_EVP_PBE_ALG_ADD 115 +# define EVP_F_EVP_PBE_ALG_ADD_TYPE 160 +# define EVP_F_EVP_PBE_CIPHERINIT 116 +# define EVP_F_EVP_PBE_SCRYPT 181 +# define EVP_F_EVP_PKCS82PKEY 111 +# define EVP_F_EVP_PKEY2PKCS8 113 +# define EVP_F_EVP_PKEY_ASN1_ADD0 188 +# define EVP_F_EVP_PKEY_CHECK 186 +# define EVP_F_EVP_PKEY_COPY_PARAMETERS 103 +# define EVP_F_EVP_PKEY_CTX_CTRL 137 +# define EVP_F_EVP_PKEY_CTX_CTRL_STR 150 +# define EVP_F_EVP_PKEY_CTX_DUP 156 +# define EVP_F_EVP_PKEY_CTX_MD 168 +# define EVP_F_EVP_PKEY_DECRYPT 104 +# define EVP_F_EVP_PKEY_DECRYPT_INIT 138 +# define EVP_F_EVP_PKEY_DECRYPT_OLD 151 +# define EVP_F_EVP_PKEY_DERIVE 153 +# define EVP_F_EVP_PKEY_DERIVE_INIT 154 +# define EVP_F_EVP_PKEY_DERIVE_SET_PEER 155 +# define EVP_F_EVP_PKEY_ENCRYPT 105 +# define EVP_F_EVP_PKEY_ENCRYPT_INIT 139 +# define EVP_F_EVP_PKEY_ENCRYPT_OLD 152 +# define EVP_F_EVP_PKEY_GET0_DH 119 +# define EVP_F_EVP_PKEY_GET0_DSA 120 +# define EVP_F_EVP_PKEY_GET0_EC_KEY 131 +# define EVP_F_EVP_PKEY_GET0_HMAC 183 +# define EVP_F_EVP_PKEY_GET0_POLY1305 184 +# define EVP_F_EVP_PKEY_GET0_RSA 121 +# define EVP_F_EVP_PKEY_GET0_SIPHASH 172 +# define EVP_F_EVP_PKEY_GET_RAW_PRIVATE_KEY 202 +# define EVP_F_EVP_PKEY_GET_RAW_PUBLIC_KEY 203 +# define EVP_F_EVP_PKEY_KEYGEN 146 +# define EVP_F_EVP_PKEY_KEYGEN_INIT 147 +# define EVP_F_EVP_PKEY_METH_ADD0 194 +# define EVP_F_EVP_PKEY_METH_NEW 195 +# define EVP_F_EVP_PKEY_NEW 106 +# define EVP_F_EVP_PKEY_NEW_CMAC_KEY 193 +# define EVP_F_EVP_PKEY_NEW_RAW_PRIVATE_KEY 191 +# define EVP_F_EVP_PKEY_NEW_RAW_PUBLIC_KEY 192 +# define EVP_F_EVP_PKEY_PARAMGEN 148 +# define EVP_F_EVP_PKEY_PARAMGEN_INIT 149 +# define EVP_F_EVP_PKEY_PARAM_CHECK 189 +# define EVP_F_EVP_PKEY_PUBLIC_CHECK 190 +# define EVP_F_EVP_PKEY_SET1_ENGINE 187 +# define EVP_F_EVP_PKEY_SET_ALIAS_TYPE 206 +# define EVP_F_EVP_PKEY_SIGN 140 +# define EVP_F_EVP_PKEY_SIGN_INIT 141 +# define EVP_F_EVP_PKEY_VERIFY 142 +# define EVP_F_EVP_PKEY_VERIFY_INIT 143 +# define EVP_F_EVP_PKEY_VERIFY_RECOVER 144 +# define EVP_F_EVP_PKEY_VERIFY_RECOVER_INIT 145 +# define EVP_F_EVP_SIGNFINAL 107 +# define EVP_F_EVP_VERIFYFINAL 108 +# define EVP_F_INT_CTX_NEW 157 +# define EVP_F_OK_NEW 200 +# define EVP_F_PKCS5_PBE_KEYIVGEN 117 +# define EVP_F_PKCS5_V2_PBE_KEYIVGEN 118 +# define EVP_F_PKCS5_V2_PBKDF2_KEYIVGEN 164 +# define EVP_F_PKCS5_V2_SCRYPT_KEYIVGEN 180 +# define EVP_F_PKEY_SET_TYPE 158 +# define EVP_F_RC2_MAGIC_TO_METH 109 +# define EVP_F_RC5_CTRL 125 +# define EVP_F_R_32_12_16_INIT_KEY 242 +# define EVP_F_S390X_AES_GCM_CTRL 201 +# define EVP_F_UPDATE 173 + +/* + * EVP reason codes. + */ +# define EVP_R_AES_KEY_SETUP_FAILED 143 +# define EVP_R_ARIA_KEY_SETUP_FAILED 176 +# define EVP_R_BAD_DECRYPT 100 +# define EVP_R_BAD_KEY_LENGTH 195 +# define EVP_R_BUFFER_TOO_SMALL 155 +# define EVP_R_CAMELLIA_KEY_SETUP_FAILED 157 +# define EVP_R_CIPHER_PARAMETER_ERROR 122 +# define EVP_R_COMMAND_NOT_SUPPORTED 147 +# define EVP_R_COPY_ERROR 173 +# define EVP_R_CTRL_NOT_IMPLEMENTED 132 +# define EVP_R_CTRL_OPERATION_NOT_IMPLEMENTED 133 +# define EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH 138 +# define EVP_R_DECODE_ERROR 114 +# define EVP_R_DIFFERENT_KEY_TYPES 101 +# define EVP_R_DIFFERENT_PARAMETERS 153 +# define EVP_R_ERROR_LOADING_SECTION 165 +# define EVP_R_ERROR_SETTING_FIPS_MODE 166 +# define EVP_R_EXPECTING_AN_HMAC_KEY 174 +# define EVP_R_EXPECTING_AN_RSA_KEY 127 +# define EVP_R_EXPECTING_A_DH_KEY 128 +# define EVP_R_EXPECTING_A_DSA_KEY 129 +# define EVP_R_EXPECTING_A_EC_KEY 142 +# define EVP_R_EXPECTING_A_POLY1305_KEY 164 +# define EVP_R_EXPECTING_A_SIPHASH_KEY 175 +# define EVP_R_FIPS_MODE_NOT_SUPPORTED 167 +# define EVP_R_GET_RAW_KEY_FAILED 182 +# define EVP_R_ILLEGAL_SCRYPT_PARAMETERS 171 +# define EVP_R_INITIALIZATION_ERROR 134 +# define EVP_R_INPUT_NOT_INITIALIZED 111 +# define EVP_R_INVALID_DIGEST 152 +# define EVP_R_INVALID_FIPS_MODE 168 +# define EVP_R_INVALID_KEY 163 +# define EVP_R_INVALID_KEY_LENGTH 130 +# define EVP_R_INVALID_OPERATION 148 +# define EVP_R_KEYGEN_FAILURE 120 +# define EVP_R_KEY_SETUP_FAILED 180 +# define EVP_R_MEMORY_LIMIT_EXCEEDED 172 +# define EVP_R_MESSAGE_DIGEST_IS_NULL 159 +# define EVP_R_METHOD_NOT_SUPPORTED 144 +# define EVP_R_MISSING_PARAMETERS 103 +# define EVP_R_NOT_XOF_OR_INVALID_LENGTH 178 +# define EVP_R_NO_CIPHER_SET 131 +# define EVP_R_NO_DEFAULT_DIGEST 158 +# define EVP_R_NO_DIGEST_SET 139 +# define EVP_R_NO_KEY_SET 154 +# define EVP_R_NO_OPERATION_SET 149 +# define EVP_R_ONLY_ONESHOT_SUPPORTED 177 +# define EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 150 +# define EVP_R_OPERATON_NOT_INITIALIZED 151 +# define EVP_R_PARTIALLY_OVERLAPPING 162 +# define EVP_R_PBKDF2_ERROR 181 +# define EVP_R_PKEY_APPLICATION_ASN1_METHOD_ALREADY_REGISTERED 179 +# define EVP_R_PRIVATE_KEY_DECODE_ERROR 145 +# define EVP_R_PRIVATE_KEY_ENCODE_ERROR 146 +# define EVP_R_PUBLIC_KEY_NOT_RSA 106 +# define EVP_R_UNKNOWN_CIPHER 160 +# define EVP_R_UNKNOWN_DIGEST 161 +# define EVP_R_UNKNOWN_OPTION 169 +# define EVP_R_UNKNOWN_PBE_ALGORITHM 121 +# define EVP_R_UNSUPPORTED_ALGORITHM 156 +# define EVP_R_UNSUPPORTED_CIPHER 107 +# define EVP_R_UNSUPPORTED_KEYLENGTH 123 +# define EVP_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION 124 +# define EVP_R_UNSUPPORTED_KEY_SIZE 108 +# define EVP_R_UNSUPPORTED_NUMBER_OF_ROUNDS 135 +# define EVP_R_UNSUPPORTED_PRF 125 +# define EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM 118 +# define EVP_R_UNSUPPORTED_SALT_TYPE 126 +# define EVP_R_WRAP_MODE_NOT_ALLOWED 170 +# define EVP_R_WRONG_FINAL_BLOCK_LENGTH 109 +# define EVP_R_XTS_DUPLICATED_KEYS 183 + +#endif diff --git a/thrid-party/openssl/openssl/hmac.h b/thrid-party/openssl/openssl/hmac.h new file mode 100644 index 0000000000..458efc1d51 --- /dev/null +++ b/thrid-party/openssl/openssl/hmac.h @@ -0,0 +1,51 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_HMAC_H +# define HEADER_HMAC_H + +# include + +# include + +# if OPENSSL_API_COMPAT < 0x10200000L +# define HMAC_MAX_MD_CBLOCK 128 /* Deprecated */ +# endif + +#ifdef __cplusplus +extern "C" { +#endif + +size_t HMAC_size(const HMAC_CTX *e); +HMAC_CTX *HMAC_CTX_new(void); +int HMAC_CTX_reset(HMAC_CTX *ctx); +void HMAC_CTX_free(HMAC_CTX *ctx); + +DEPRECATEDIN_1_1_0(__owur int HMAC_Init(HMAC_CTX *ctx, const void *key, int len, + const EVP_MD *md)) + +/*__owur*/ int HMAC_Init_ex(HMAC_CTX *ctx, const void *key, int len, + const EVP_MD *md, ENGINE *impl); +/*__owur*/ int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, + size_t len); +/*__owur*/ int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, + unsigned int *len); +unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len, + const unsigned char *d, size_t n, unsigned char *md, + unsigned int *md_len); +__owur int HMAC_CTX_copy(HMAC_CTX *dctx, HMAC_CTX *sctx); + +void HMAC_CTX_set_flags(HMAC_CTX *ctx, unsigned long flags); +const EVP_MD *HMAC_CTX_get_md(const HMAC_CTX *ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/idea.h b/thrid-party/openssl/openssl/idea.h new file mode 100644 index 0000000000..4334f3ea71 --- /dev/null +++ b/thrid-party/openssl/openssl/idea.h @@ -0,0 +1,64 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_IDEA_H +# define HEADER_IDEA_H + +# include + +# ifndef OPENSSL_NO_IDEA +# ifdef __cplusplus +extern "C" { +# endif + +typedef unsigned int IDEA_INT; + +# define IDEA_ENCRYPT 1 +# define IDEA_DECRYPT 0 + +# define IDEA_BLOCK 8 +# define IDEA_KEY_LENGTH 16 + +typedef struct idea_key_st { + IDEA_INT data[9][6]; +} IDEA_KEY_SCHEDULE; + +const char *IDEA_options(void); +void IDEA_ecb_encrypt(const unsigned char *in, unsigned char *out, + IDEA_KEY_SCHEDULE *ks); +void IDEA_set_encrypt_key(const unsigned char *key, IDEA_KEY_SCHEDULE *ks); +void IDEA_set_decrypt_key(IDEA_KEY_SCHEDULE *ek, IDEA_KEY_SCHEDULE *dk); +void IDEA_cbc_encrypt(const unsigned char *in, unsigned char *out, + long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv, + int enc); +void IDEA_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv, + int *num, int enc); +void IDEA_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv, + int *num); +void IDEA_encrypt(unsigned long *in, IDEA_KEY_SCHEDULE *ks); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define idea_options IDEA_options +# define idea_ecb_encrypt IDEA_ecb_encrypt +# define idea_set_encrypt_key IDEA_set_encrypt_key +# define idea_set_decrypt_key IDEA_set_decrypt_key +# define idea_cbc_encrypt IDEA_cbc_encrypt +# define idea_cfb64_encrypt IDEA_cfb64_encrypt +# define idea_ofb64_encrypt IDEA_ofb64_encrypt +# define idea_encrypt IDEA_encrypt +# endif + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/kdf.h b/thrid-party/openssl/openssl/kdf.h new file mode 100644 index 0000000000..5abd4c3714 --- /dev/null +++ b/thrid-party/openssl/openssl/kdf.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_KDF_H +# define HEADER_KDF_H + +# include +#ifdef __cplusplus +extern "C" { +#endif + +# define EVP_PKEY_CTRL_TLS_MD (EVP_PKEY_ALG_CTRL) +# define EVP_PKEY_CTRL_TLS_SECRET (EVP_PKEY_ALG_CTRL + 1) +# define EVP_PKEY_CTRL_TLS_SEED (EVP_PKEY_ALG_CTRL + 2) +# define EVP_PKEY_CTRL_HKDF_MD (EVP_PKEY_ALG_CTRL + 3) +# define EVP_PKEY_CTRL_HKDF_SALT (EVP_PKEY_ALG_CTRL + 4) +# define EVP_PKEY_CTRL_HKDF_KEY (EVP_PKEY_ALG_CTRL + 5) +# define EVP_PKEY_CTRL_HKDF_INFO (EVP_PKEY_ALG_CTRL + 6) +# define EVP_PKEY_CTRL_HKDF_MODE (EVP_PKEY_ALG_CTRL + 7) +# define EVP_PKEY_CTRL_PASS (EVP_PKEY_ALG_CTRL + 8) +# define EVP_PKEY_CTRL_SCRYPT_SALT (EVP_PKEY_ALG_CTRL + 9) +# define EVP_PKEY_CTRL_SCRYPT_N (EVP_PKEY_ALG_CTRL + 10) +# define EVP_PKEY_CTRL_SCRYPT_R (EVP_PKEY_ALG_CTRL + 11) +# define EVP_PKEY_CTRL_SCRYPT_P (EVP_PKEY_ALG_CTRL + 12) +# define EVP_PKEY_CTRL_SCRYPT_MAXMEM_BYTES (EVP_PKEY_ALG_CTRL + 13) + +# define EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND 0 +# define EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY 1 +# define EVP_PKEY_HKDEF_MODE_EXPAND_ONLY 2 + +# define EVP_PKEY_CTX_set_tls1_prf_md(pctx, md) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_TLS_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, sec, seclen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_TLS_SECRET, seclen, (void *)(sec)) + +# define EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seedlen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_TLS_SEED, seedlen, (void *)(seed)) + +# define EVP_PKEY_CTX_set_hkdf_md(pctx, md) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_HKDF_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, saltlen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_HKDF_SALT, saltlen, (void *)(salt)) + +# define EVP_PKEY_CTX_set1_hkdf_key(pctx, key, keylen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_HKDF_KEY, keylen, (void *)(key)) + +# define EVP_PKEY_CTX_add1_hkdf_info(pctx, info, infolen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_HKDF_INFO, infolen, (void *)(info)) + +# define EVP_PKEY_CTX_hkdf_mode(pctx, mode) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_HKDF_MODE, mode, NULL) + +# define EVP_PKEY_CTX_set1_pbe_pass(pctx, pass, passlen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_PASS, passlen, (void *)(pass)) + +# define EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) \ + EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_SCRYPT_SALT, saltlen, (void *)(salt)) + +# define EVP_PKEY_CTX_set_scrypt_N(pctx, n) \ + EVP_PKEY_CTX_ctrl_uint64(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_SCRYPT_N, n) + +# define EVP_PKEY_CTX_set_scrypt_r(pctx, r) \ + EVP_PKEY_CTX_ctrl_uint64(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_SCRYPT_R, r) + +# define EVP_PKEY_CTX_set_scrypt_p(pctx, p) \ + EVP_PKEY_CTX_ctrl_uint64(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_SCRYPT_P, p) + +# define EVP_PKEY_CTX_set_scrypt_maxmem_bytes(pctx, maxmem_bytes) \ + EVP_PKEY_CTX_ctrl_uint64(pctx, -1, EVP_PKEY_OP_DERIVE, \ + EVP_PKEY_CTRL_SCRYPT_MAXMEM_BYTES, maxmem_bytes) + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/kdferr.h b/thrid-party/openssl/openssl/kdferr.h new file mode 100644 index 0000000000..3f51bd0228 --- /dev/null +++ b/thrid-party/openssl/openssl/kdferr.h @@ -0,0 +1,55 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_KDFERR_H +# define HEADER_KDFERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_KDF_strings(void); + +/* + * KDF function codes. + */ +# define KDF_F_PKEY_HKDF_CTRL_STR 103 +# define KDF_F_PKEY_HKDF_DERIVE 102 +# define KDF_F_PKEY_HKDF_INIT 108 +# define KDF_F_PKEY_SCRYPT_CTRL_STR 104 +# define KDF_F_PKEY_SCRYPT_CTRL_UINT64 105 +# define KDF_F_PKEY_SCRYPT_DERIVE 109 +# define KDF_F_PKEY_SCRYPT_INIT 106 +# define KDF_F_PKEY_SCRYPT_SET_MEMBUF 107 +# define KDF_F_PKEY_TLS1_PRF_CTRL_STR 100 +# define KDF_F_PKEY_TLS1_PRF_DERIVE 101 +# define KDF_F_PKEY_TLS1_PRF_INIT 110 +# define KDF_F_TLS1_PRF_ALG 111 + +/* + * KDF reason codes. + */ +# define KDF_R_INVALID_DIGEST 100 +# define KDF_R_MISSING_ITERATION_COUNT 109 +# define KDF_R_MISSING_KEY 104 +# define KDF_R_MISSING_MESSAGE_DIGEST 105 +# define KDF_R_MISSING_PARAMETER 101 +# define KDF_R_MISSING_PASS 110 +# define KDF_R_MISSING_SALT 111 +# define KDF_R_MISSING_SECRET 107 +# define KDF_R_MISSING_SEED 106 +# define KDF_R_UNKNOWN_PARAMETER_TYPE 103 +# define KDF_R_VALUE_ERROR 108 +# define KDF_R_VALUE_MISSING 102 + +#endif diff --git a/thrid-party/openssl/openssl/krb5_asn.h b/thrid-party/openssl/openssl/krb5_asn.h new file mode 100644 index 0000000000..9cf5a26dd8 --- /dev/null +++ b/thrid-party/openssl/openssl/krb5_asn.h @@ -0,0 +1,240 @@ +/* krb5_asn.h */ +/* + * Written by Vern Staats for the OpenSSL project, ** + * using ocsp/{*.h,*asn*.c} as a starting point + */ + +/* ==================================================================== + * Copyright (c) 1998-2000 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#ifndef HEADER_KRB5_ASN_H +# define HEADER_KRB5_ASN_H + +/* + * #include + */ +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ASN.1 from Kerberos RFC 1510 + */ + +/*- EncryptedData ::= SEQUENCE { + * etype[0] INTEGER, -- EncryptionType + * kvno[1] INTEGER OPTIONAL, + * cipher[2] OCTET STRING -- ciphertext + * } + */ +typedef struct krb5_encdata_st { + ASN1_INTEGER *etype; + ASN1_INTEGER *kvno; + ASN1_OCTET_STRING *cipher; +} KRB5_ENCDATA; + +DECLARE_STACK_OF(KRB5_ENCDATA) + +/*- PrincipalName ::= SEQUENCE { + * name-type[0] INTEGER, + * name-string[1] SEQUENCE OF GeneralString + * } + */ +typedef struct krb5_princname_st { + ASN1_INTEGER *nametype; + STACK_OF(ASN1_GENERALSTRING) *namestring; +} KRB5_PRINCNAME; + +DECLARE_STACK_OF(KRB5_PRINCNAME) + +/*- Ticket ::= [APPLICATION 1] SEQUENCE { + * tkt-vno[0] INTEGER, + * realm[1] Realm, + * sname[2] PrincipalName, + * enc-part[3] EncryptedData + * } + */ +typedef struct krb5_tktbody_st { + ASN1_INTEGER *tktvno; + ASN1_GENERALSTRING *realm; + KRB5_PRINCNAME *sname; + KRB5_ENCDATA *encdata; +} KRB5_TKTBODY; + +typedef STACK_OF(KRB5_TKTBODY) KRB5_TICKET; +DECLARE_STACK_OF(KRB5_TKTBODY) + +/*- AP-REQ ::= [APPLICATION 14] SEQUENCE { + * pvno[0] INTEGER, + * msg-type[1] INTEGER, + * ap-options[2] APOptions, + * ticket[3] Ticket, + * authenticator[4] EncryptedData + * } + * + * APOptions ::= BIT STRING { + * reserved(0), use-session-key(1), mutual-required(2) } + */ +typedef struct krb5_ap_req_st { + ASN1_INTEGER *pvno; + ASN1_INTEGER *msgtype; + ASN1_BIT_STRING *apoptions; + KRB5_TICKET *ticket; + KRB5_ENCDATA *authenticator; +} KRB5_APREQBODY; + +typedef STACK_OF(KRB5_APREQBODY) KRB5_APREQ; +DECLARE_STACK_OF(KRB5_APREQBODY) + +/* Authenticator Stuff */ + +/*- Checksum ::= SEQUENCE { + * cksumtype[0] INTEGER, + * checksum[1] OCTET STRING + * } + */ +typedef struct krb5_checksum_st { + ASN1_INTEGER *ctype; + ASN1_OCTET_STRING *checksum; +} KRB5_CHECKSUM; + +DECLARE_STACK_OF(KRB5_CHECKSUM) + +/*- EncryptionKey ::= SEQUENCE { + * keytype[0] INTEGER, + * keyvalue[1] OCTET STRING + * } + */ +typedef struct krb5_encryptionkey_st { + ASN1_INTEGER *ktype; + ASN1_OCTET_STRING *keyvalue; +} KRB5_ENCKEY; + +DECLARE_STACK_OF(KRB5_ENCKEY) + +/*- AuthorizationData ::= SEQUENCE OF SEQUENCE { + * ad-type[0] INTEGER, + * ad-data[1] OCTET STRING + * } + */ +typedef struct krb5_authorization_st { + ASN1_INTEGER *adtype; + ASN1_OCTET_STRING *addata; +} KRB5_AUTHDATA; + +DECLARE_STACK_OF(KRB5_AUTHDATA) + +/*- -- Unencrypted authenticator + * Authenticator ::= [APPLICATION 2] SEQUENCE { + * authenticator-vno[0] INTEGER, + * crealm[1] Realm, + * cname[2] PrincipalName, + * cksum[3] Checksum OPTIONAL, + * cusec[4] INTEGER, + * ctime[5] KerberosTime, + * subkey[6] EncryptionKey OPTIONAL, + * seq-number[7] INTEGER OPTIONAL, + * authorization-data[8] AuthorizationData OPTIONAL + * } + */ +typedef struct krb5_authenticator_st { + ASN1_INTEGER *avno; + ASN1_GENERALSTRING *crealm; + KRB5_PRINCNAME *cname; + KRB5_CHECKSUM *cksum; + ASN1_INTEGER *cusec; + ASN1_GENERALIZEDTIME *ctime; + KRB5_ENCKEY *subkey; + ASN1_INTEGER *seqnum; + KRB5_AUTHDATA *authorization; +} KRB5_AUTHENTBODY; + +typedef STACK_OF(KRB5_AUTHENTBODY) KRB5_AUTHENT; +DECLARE_STACK_OF(KRB5_AUTHENTBODY) + +/*- DECLARE_ASN1_FUNCTIONS(type) = DECLARE_ASN1_FUNCTIONS_name(type, type) = + * type *name##_new(void); + * void name##_free(type *a); + * DECLARE_ASN1_ENCODE_FUNCTIONS(type, name, name) = + * DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) = + * type *d2i_##name(type **a, const unsigned char **in, long len); + * int i2d_##name(type *a, unsigned char **out); + * DECLARE_ASN1_ITEM(itname) = OPENSSL_EXTERN const ASN1_ITEM itname##_it + */ + +DECLARE_ASN1_FUNCTIONS(KRB5_ENCDATA) +DECLARE_ASN1_FUNCTIONS(KRB5_PRINCNAME) +DECLARE_ASN1_FUNCTIONS(KRB5_TKTBODY) +DECLARE_ASN1_FUNCTIONS(KRB5_APREQBODY) +DECLARE_ASN1_FUNCTIONS(KRB5_TICKET) +DECLARE_ASN1_FUNCTIONS(KRB5_APREQ) + +DECLARE_ASN1_FUNCTIONS(KRB5_CHECKSUM) +DECLARE_ASN1_FUNCTIONS(KRB5_ENCKEY) +DECLARE_ASN1_FUNCTIONS(KRB5_AUTHDATA) +DECLARE_ASN1_FUNCTIONS(KRB5_AUTHENTBODY) +DECLARE_ASN1_FUNCTIONS(KRB5_AUTHENT) + +/* BEGIN ERROR CODES */ +/* + * The following lines are auto generated by the script mkerr.pl. Any changes + * made after this point may be overwritten when the script is next run. + */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/kssl.h b/thrid-party/openssl/openssl/kssl.h new file mode 100644 index 0000000000..ae8a51f472 --- /dev/null +++ b/thrid-party/openssl/openssl/kssl.h @@ -0,0 +1,197 @@ +/* ssl/kssl.h */ +/* + * Written by Vern Staats for the OpenSSL project + * 2000. project 2000. + */ +/* ==================================================================== + * Copyright (c) 2000 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +/* + ** 19990701 VRS Started. + */ + +#ifndef KSSL_H +# define KSSL_H + +# include + +# ifndef OPENSSL_NO_KRB5 + +# include +# include +# include +# ifdef OPENSSL_SYS_WIN32 +/* + * These can sometimes get redefined indirectly by krb5 header files after + * they get undefed in ossl_typ.h + */ +# undef X509_NAME +# undef X509_EXTENSIONS +# undef OCSP_REQUEST +# undef OCSP_RESPONSE +# endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Depending on which KRB5 implementation used, some types from + * the other may be missing. Resolve that here and now + */ +# ifdef KRB5_HEIMDAL +typedef unsigned char krb5_octet; +# define FAR +# else + +# ifndef FAR +# define FAR +# endif + +# endif + +/*- + * Uncomment this to debug kssl problems or + * to trace usage of the Kerberos session key + * + * #define KSSL_DEBUG + */ + +# ifndef KRB5SVC +# define KRB5SVC "host" +# endif + +# ifndef KRB5KEYTAB +# define KRB5KEYTAB "/etc/krb5.keytab" +# endif + +# ifndef KRB5SENDAUTH +# define KRB5SENDAUTH 1 +# endif + +# ifndef KRB5CHECKAUTH +# define KRB5CHECKAUTH 1 +# endif + +# ifndef KSSL_CLOCKSKEW +# define KSSL_CLOCKSKEW 300; +# endif + +# define KSSL_ERR_MAX 255 +typedef struct kssl_err_st { + int reason; + char text[KSSL_ERR_MAX + 1]; +} KSSL_ERR; + +/*- Context for passing + * (1) Kerberos session key to SSL, and + * (2) Config data between application and SSL lib + */ +typedef struct kssl_ctx_st { + /* used by: disposition: */ + char *service_name; /* C,S default ok (kssl) */ + char *service_host; /* C input, REQUIRED */ + char *client_princ; /* S output from krb5 ticket */ + char *keytab_file; /* S NULL (/etc/krb5.keytab) */ + char *cred_cache; /* C NULL (default) */ + krb5_enctype enctype; + int length; + krb5_octet FAR *key; +} KSSL_CTX; + +# define KSSL_CLIENT 1 +# define KSSL_SERVER 2 +# define KSSL_SERVICE 3 +# define KSSL_KEYTAB 4 + +# define KSSL_CTX_OK 0 +# define KSSL_CTX_ERR 1 +# define KSSL_NOMEM 2 + +/* Public (for use by applications that use OpenSSL with Kerberos 5 support */ +krb5_error_code kssl_ctx_setstring(KSSL_CTX *kssl_ctx, int which, char *text); +KSSL_CTX *kssl_ctx_new(void); +KSSL_CTX *kssl_ctx_free(KSSL_CTX *kssl_ctx); +void kssl_ctx_show(KSSL_CTX *kssl_ctx); +krb5_error_code kssl_ctx_setprinc(KSSL_CTX *kssl_ctx, int which, + krb5_data *realm, krb5_data *entity, + int nentities); +krb5_error_code kssl_cget_tkt(KSSL_CTX *kssl_ctx, krb5_data **enc_tktp, + krb5_data *authenp, KSSL_ERR *kssl_err); +krb5_error_code kssl_sget_tkt(KSSL_CTX *kssl_ctx, krb5_data *indata, + krb5_ticket_times *ttimes, KSSL_ERR *kssl_err); +krb5_error_code kssl_ctx_setkey(KSSL_CTX *kssl_ctx, krb5_keyblock *session); +void kssl_err_set(KSSL_ERR *kssl_err, int reason, char *text); +void kssl_krb5_free_data_contents(krb5_context context, krb5_data *data); +krb5_error_code kssl_build_principal_2(krb5_context context, + krb5_principal *princ, int rlen, + const char *realm, int slen, + const char *svc, int hlen, + const char *host); +krb5_error_code kssl_validate_times(krb5_timestamp atime, + krb5_ticket_times *ttimes); +krb5_error_code kssl_check_authent(KSSL_CTX *kssl_ctx, krb5_data *authentp, + krb5_timestamp *atimep, + KSSL_ERR *kssl_err); +unsigned char *kssl_skip_confound(krb5_enctype enctype, unsigned char *authn); + +void SSL_set0_kssl_ctx(SSL *s, KSSL_CTX *kctx); +KSSL_CTX *SSL_get0_kssl_ctx(SSL *s); +char *kssl_ctx_get0_client_princ(KSSL_CTX *kctx); + +#ifdef __cplusplus +} +#endif +# endif /* OPENSSL_NO_KRB5 */ +#endif /* KSSL_H */ diff --git a/thrid-party/openssl/openssl/lhash.h b/thrid-party/openssl/openssl/lhash.h new file mode 100644 index 0000000000..47b99d17fb --- /dev/null +++ b/thrid-party/openssl/openssl/lhash.h @@ -0,0 +1,242 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Header for dynamic hash table routines Author - Eric Young + */ + +#ifndef HEADER_LHASH_H +# define HEADER_LHASH_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct lhash_node_st OPENSSL_LH_NODE; +typedef int (*OPENSSL_LH_COMPFUNC) (const void *, const void *); +typedef unsigned long (*OPENSSL_LH_HASHFUNC) (const void *); +typedef void (*OPENSSL_LH_DOALL_FUNC) (void *); +typedef void (*OPENSSL_LH_DOALL_FUNCARG) (void *, void *); +typedef struct lhash_st OPENSSL_LHASH; + +/* + * Macros for declaring and implementing type-safe wrappers for LHASH + * callbacks. This way, callbacks can be provided to LHASH structures without + * function pointer casting and the macro-defined callbacks provide + * per-variable casting before deferring to the underlying type-specific + * callbacks. NB: It is possible to place a "static" in front of both the + * DECLARE and IMPLEMENT macros if the functions are strictly internal. + */ + +/* First: "hash" functions */ +# define DECLARE_LHASH_HASH_FN(name, o_type) \ + unsigned long name##_LHASH_HASH(const void *); +# define IMPLEMENT_LHASH_HASH_FN(name, o_type) \ + unsigned long name##_LHASH_HASH(const void *arg) { \ + const o_type *a = arg; \ + return name##_hash(a); } +# define LHASH_HASH_FN(name) name##_LHASH_HASH + +/* Second: "compare" functions */ +# define DECLARE_LHASH_COMP_FN(name, o_type) \ + int name##_LHASH_COMP(const void *, const void *); +# define IMPLEMENT_LHASH_COMP_FN(name, o_type) \ + int name##_LHASH_COMP(const void *arg1, const void *arg2) { \ + const o_type *a = arg1; \ + const o_type *b = arg2; \ + return name##_cmp(a,b); } +# define LHASH_COMP_FN(name) name##_LHASH_COMP + +/* Fourth: "doall_arg" functions */ +# define DECLARE_LHASH_DOALL_ARG_FN(name, o_type, a_type) \ + void name##_LHASH_DOALL_ARG(void *, void *); +# define IMPLEMENT_LHASH_DOALL_ARG_FN(name, o_type, a_type) \ + void name##_LHASH_DOALL_ARG(void *arg1, void *arg2) { \ + o_type *a = arg1; \ + a_type *b = arg2; \ + name##_doall_arg(a, b); } +# define LHASH_DOALL_ARG_FN(name) name##_LHASH_DOALL_ARG + + +# define LH_LOAD_MULT 256 + +int OPENSSL_LH_error(OPENSSL_LHASH *lh); +OPENSSL_LHASH *OPENSSL_LH_new(OPENSSL_LH_HASHFUNC h, OPENSSL_LH_COMPFUNC c); +void OPENSSL_LH_free(OPENSSL_LHASH *lh); +void *OPENSSL_LH_insert(OPENSSL_LHASH *lh, void *data); +void *OPENSSL_LH_delete(OPENSSL_LHASH *lh, const void *data); +void *OPENSSL_LH_retrieve(OPENSSL_LHASH *lh, const void *data); +void OPENSSL_LH_doall(OPENSSL_LHASH *lh, OPENSSL_LH_DOALL_FUNC func); +void OPENSSL_LH_doall_arg(OPENSSL_LHASH *lh, OPENSSL_LH_DOALL_FUNCARG func, void *arg); +unsigned long OPENSSL_LH_strhash(const char *c); +unsigned long OPENSSL_LH_num_items(const OPENSSL_LHASH *lh); +unsigned long OPENSSL_LH_get_down_load(const OPENSSL_LHASH *lh); +void OPENSSL_LH_set_down_load(OPENSSL_LHASH *lh, unsigned long down_load); + +# ifndef OPENSSL_NO_STDIO +void OPENSSL_LH_stats(const OPENSSL_LHASH *lh, FILE *fp); +void OPENSSL_LH_node_stats(const OPENSSL_LHASH *lh, FILE *fp); +void OPENSSL_LH_node_usage_stats(const OPENSSL_LHASH *lh, FILE *fp); +# endif +void OPENSSL_LH_stats_bio(const OPENSSL_LHASH *lh, BIO *out); +void OPENSSL_LH_node_stats_bio(const OPENSSL_LHASH *lh, BIO *out); +void OPENSSL_LH_node_usage_stats_bio(const OPENSSL_LHASH *lh, BIO *out); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define _LHASH OPENSSL_LHASH +# define LHASH_NODE OPENSSL_LH_NODE +# define lh_error OPENSSL_LH_error +# define lh_new OPENSSL_LH_new +# define lh_free OPENSSL_LH_free +# define lh_insert OPENSSL_LH_insert +# define lh_delete OPENSSL_LH_delete +# define lh_retrieve OPENSSL_LH_retrieve +# define lh_doall OPENSSL_LH_doall +# define lh_doall_arg OPENSSL_LH_doall_arg +# define lh_strhash OPENSSL_LH_strhash +# define lh_num_items OPENSSL_LH_num_items +# ifndef OPENSSL_NO_STDIO +# define lh_stats OPENSSL_LH_stats +# define lh_node_stats OPENSSL_LH_node_stats +# define lh_node_usage_stats OPENSSL_LH_node_usage_stats +# endif +# define lh_stats_bio OPENSSL_LH_stats_bio +# define lh_node_stats_bio OPENSSL_LH_node_stats_bio +# define lh_node_usage_stats_bio OPENSSL_LH_node_usage_stats_bio +# endif + +/* Type checking... */ + +# define LHASH_OF(type) struct lhash_st_##type + +# define DEFINE_LHASH_OF(type) \ + LHASH_OF(type) { union lh_##type##_dummy { void* d1; unsigned long d2; int d3; } dummy; }; \ + static ossl_inline LHASH_OF(type) * \ + lh_##type##_new(unsigned long (*hfn)(const type *), \ + int (*cfn)(const type *, const type *)) \ + { \ + return (LHASH_OF(type) *) \ + OPENSSL_LH_new((OPENSSL_LH_HASHFUNC)hfn, (OPENSSL_LH_COMPFUNC)cfn); \ + } \ + static ossl_unused ossl_inline void lh_##type##_free(LHASH_OF(type) *lh) \ + { \ + OPENSSL_LH_free((OPENSSL_LHASH *)lh); \ + } \ + static ossl_unused ossl_inline type *lh_##type##_insert(LHASH_OF(type) *lh, type *d) \ + { \ + return (type *)OPENSSL_LH_insert((OPENSSL_LHASH *)lh, d); \ + } \ + static ossl_unused ossl_inline type *lh_##type##_delete(LHASH_OF(type) *lh, const type *d) \ + { \ + return (type *)OPENSSL_LH_delete((OPENSSL_LHASH *)lh, d); \ + } \ + static ossl_unused ossl_inline type *lh_##type##_retrieve(LHASH_OF(type) *lh, const type *d) \ + { \ + return (type *)OPENSSL_LH_retrieve((OPENSSL_LHASH *)lh, d); \ + } \ + static ossl_unused ossl_inline int lh_##type##_error(LHASH_OF(type) *lh) \ + { \ + return OPENSSL_LH_error((OPENSSL_LHASH *)lh); \ + } \ + static ossl_unused ossl_inline unsigned long lh_##type##_num_items(LHASH_OF(type) *lh) \ + { \ + return OPENSSL_LH_num_items((OPENSSL_LHASH *)lh); \ + } \ + static ossl_unused ossl_inline void lh_##type##_node_stats_bio(const LHASH_OF(type) *lh, BIO *out) \ + { \ + OPENSSL_LH_node_stats_bio((const OPENSSL_LHASH *)lh, out); \ + } \ + static ossl_unused ossl_inline void lh_##type##_node_usage_stats_bio(const LHASH_OF(type) *lh, BIO *out) \ + { \ + OPENSSL_LH_node_usage_stats_bio((const OPENSSL_LHASH *)lh, out); \ + } \ + static ossl_unused ossl_inline void lh_##type##_stats_bio(const LHASH_OF(type) *lh, BIO *out) \ + { \ + OPENSSL_LH_stats_bio((const OPENSSL_LHASH *)lh, out); \ + } \ + static ossl_unused ossl_inline unsigned long lh_##type##_get_down_load(LHASH_OF(type) *lh) \ + { \ + return OPENSSL_LH_get_down_load((OPENSSL_LHASH *)lh); \ + } \ + static ossl_unused ossl_inline void lh_##type##_set_down_load(LHASH_OF(type) *lh, unsigned long dl) \ + { \ + OPENSSL_LH_set_down_load((OPENSSL_LHASH *)lh, dl); \ + } \ + static ossl_unused ossl_inline void lh_##type##_doall(LHASH_OF(type) *lh, \ + void (*doall)(type *)) \ + { \ + OPENSSL_LH_doall((OPENSSL_LHASH *)lh, (OPENSSL_LH_DOALL_FUNC)doall); \ + } \ + LHASH_OF(type) + +#define IMPLEMENT_LHASH_DOALL_ARG_CONST(type, argtype) \ + int_implement_lhash_doall(type, argtype, const type) + +#define IMPLEMENT_LHASH_DOALL_ARG(type, argtype) \ + int_implement_lhash_doall(type, argtype, type) + +#define int_implement_lhash_doall(type, argtype, cbargtype) \ + static ossl_unused ossl_inline void \ + lh_##type##_doall_##argtype(LHASH_OF(type) *lh, \ + void (*fn)(cbargtype *, argtype *), \ + argtype *arg) \ + { \ + OPENSSL_LH_doall_arg((OPENSSL_LHASH *)lh, (OPENSSL_LH_DOALL_FUNCARG)fn, (void *)arg); \ + } \ + LHASH_OF(type) + +DEFINE_LHASH_OF(OPENSSL_STRING); +# ifdef _MSC_VER +/* + * push and pop this warning: + * warning C4090: 'function': different 'const' qualifiers + */ +# pragma warning (push) +# pragma warning (disable: 4090) +# endif + +DEFINE_LHASH_OF(OPENSSL_CSTRING); + +# ifdef _MSC_VER +# pragma warning (pop) +# endif + +/* + * If called without higher optimization (min. -xO3) the Oracle Developer + * Studio compiler generates code for the defined (static inline) functions + * above. + * This would later lead to the linker complaining about missing symbols when + * this header file is included but the resulting object is not linked against + * the Crypto library (openssl#6912). + */ +# ifdef __SUNPRO_C +# pragma weak OPENSSL_LH_new +# pragma weak OPENSSL_LH_free +# pragma weak OPENSSL_LH_insert +# pragma weak OPENSSL_LH_delete +# pragma weak OPENSSL_LH_retrieve +# pragma weak OPENSSL_LH_error +# pragma weak OPENSSL_LH_num_items +# pragma weak OPENSSL_LH_node_stats_bio +# pragma weak OPENSSL_LH_node_usage_stats_bio +# pragma weak OPENSSL_LH_stats_bio +# pragma weak OPENSSL_LH_get_down_load +# pragma weak OPENSSL_LH_set_down_load +# pragma weak OPENSSL_LH_doall +# pragma weak OPENSSL_LH_doall_arg +# endif /* __SUNPRO_C */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/md2.h b/thrid-party/openssl/openssl/md2.h new file mode 100644 index 0000000000..7faf8e3d65 --- /dev/null +++ b/thrid-party/openssl/openssl/md2.h @@ -0,0 +1,44 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_MD2_H +# define HEADER_MD2_H + +# include + +# ifndef OPENSSL_NO_MD2 +# include +# ifdef __cplusplus +extern "C" { +# endif + +typedef unsigned char MD2_INT; + +# define MD2_DIGEST_LENGTH 16 +# define MD2_BLOCK 16 + +typedef struct MD2state_st { + unsigned int num; + unsigned char data[MD2_BLOCK]; + MD2_INT cksm[MD2_BLOCK]; + MD2_INT state[MD2_BLOCK]; +} MD2_CTX; + +const char *MD2_options(void); +int MD2_Init(MD2_CTX *c); +int MD2_Update(MD2_CTX *c, const unsigned char *data, size_t len); +int MD2_Final(unsigned char *md, MD2_CTX *c); +unsigned char *MD2(const unsigned char *d, size_t n, unsigned char *md); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/md4.h b/thrid-party/openssl/openssl/md4.h new file mode 100644 index 0000000000..940e29db40 --- /dev/null +++ b/thrid-party/openssl/openssl/md4.h @@ -0,0 +1,51 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_MD4_H +# define HEADER_MD4_H + +# include + +# ifndef OPENSSL_NO_MD4 +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +/*- + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ! MD4_LONG has to be at least 32 bits wide. ! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ +# define MD4_LONG unsigned int + +# define MD4_CBLOCK 64 +# define MD4_LBLOCK (MD4_CBLOCK/4) +# define MD4_DIGEST_LENGTH 16 + +typedef struct MD4state_st { + MD4_LONG A, B, C, D; + MD4_LONG Nl, Nh; + MD4_LONG data[MD4_LBLOCK]; + unsigned int num; +} MD4_CTX; + +int MD4_Init(MD4_CTX *c); +int MD4_Update(MD4_CTX *c, const void *data, size_t len); +int MD4_Final(unsigned char *md, MD4_CTX *c); +unsigned char *MD4(const unsigned char *d, size_t n, unsigned char *md); +void MD4_Transform(MD4_CTX *c, const unsigned char *b); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/md5.h b/thrid-party/openssl/openssl/md5.h new file mode 100644 index 0000000000..2deb772118 --- /dev/null +++ b/thrid-party/openssl/openssl/md5.h @@ -0,0 +1,50 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_MD5_H +# define HEADER_MD5_H + +# include + +# ifndef OPENSSL_NO_MD5 +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ! MD5_LONG has to be at least 32 bits wide. ! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ +# define MD5_LONG unsigned int + +# define MD5_CBLOCK 64 +# define MD5_LBLOCK (MD5_CBLOCK/4) +# define MD5_DIGEST_LENGTH 16 + +typedef struct MD5state_st { + MD5_LONG A, B, C, D; + MD5_LONG Nl, Nh; + MD5_LONG data[MD5_LBLOCK]; + unsigned int num; +} MD5_CTX; + +int MD5_Init(MD5_CTX *c); +int MD5_Update(MD5_CTX *c, const void *data, size_t len); +int MD5_Final(unsigned char *md, MD5_CTX *c); +unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md); +void MD5_Transform(MD5_CTX *c, const unsigned char *b); +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/mdc2.h b/thrid-party/openssl/openssl/mdc2.h new file mode 100644 index 0000000000..aabd2bfaad --- /dev/null +++ b/thrid-party/openssl/openssl/mdc2.h @@ -0,0 +1,42 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_MDC2_H +# define HEADER_MDC2_H + +# include + +#ifndef OPENSSL_NO_MDC2 +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +# define MDC2_BLOCK 8 +# define MDC2_DIGEST_LENGTH 16 + +typedef struct mdc2_ctx_st { + unsigned int num; + unsigned char data[MDC2_BLOCK]; + DES_cblock h, hh; + int pad_type; /* either 1 or 2, default 1 */ +} MDC2_CTX; + +int MDC2_Init(MDC2_CTX *c); +int MDC2_Update(MDC2_CTX *c, const unsigned char *data, size_t len); +int MDC2_Final(unsigned char *md, MDC2_CTX *c); +unsigned char *MDC2(const unsigned char *d, size_t n, unsigned char *md); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/modes.h b/thrid-party/openssl/openssl/modes.h new file mode 100644 index 0000000000..d544f98d55 --- /dev/null +++ b/thrid-party/openssl/openssl/modes.h @@ -0,0 +1,208 @@ +/* + * Copyright 2008-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_MODES_H +# define HEADER_MODES_H + +# include + +# ifdef __cplusplus +extern "C" { +# endif +typedef void (*block128_f) (const unsigned char in[16], + unsigned char out[16], const void *key); + +typedef void (*cbc128_f) (const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], int enc); + +typedef void (*ctr128_f) (const unsigned char *in, unsigned char *out, + size_t blocks, const void *key, + const unsigned char ivec[16]); + +typedef void (*ccm128_f) (const unsigned char *in, unsigned char *out, + size_t blocks, const void *key, + const unsigned char ivec[16], + unsigned char cmac[16]); + +void CRYPTO_cbc128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], block128_f block); +void CRYPTO_cbc128_decrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], block128_f block); + +void CRYPTO_ctr128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], + unsigned char ecount_buf[16], unsigned int *num, + block128_f block); + +void CRYPTO_ctr128_encrypt_ctr32(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], + unsigned char ecount_buf[16], + unsigned int *num, ctr128_f ctr); + +void CRYPTO_ofb128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], int *num, + block128_f block); + +void CRYPTO_cfb128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], int *num, + int enc, block128_f block); +void CRYPTO_cfb128_8_encrypt(const unsigned char *in, unsigned char *out, + size_t length, const void *key, + unsigned char ivec[16], int *num, + int enc, block128_f block); +void CRYPTO_cfb128_1_encrypt(const unsigned char *in, unsigned char *out, + size_t bits, const void *key, + unsigned char ivec[16], int *num, + int enc, block128_f block); + +size_t CRYPTO_cts128_encrypt_block(const unsigned char *in, + unsigned char *out, size_t len, + const void *key, unsigned char ivec[16], + block128_f block); +size_t CRYPTO_cts128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], cbc128_f cbc); +size_t CRYPTO_cts128_decrypt_block(const unsigned char *in, + unsigned char *out, size_t len, + const void *key, unsigned char ivec[16], + block128_f block); +size_t CRYPTO_cts128_decrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], cbc128_f cbc); + +size_t CRYPTO_nistcts128_encrypt_block(const unsigned char *in, + unsigned char *out, size_t len, + const void *key, + unsigned char ivec[16], + block128_f block); +size_t CRYPTO_nistcts128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], cbc128_f cbc); +size_t CRYPTO_nistcts128_decrypt_block(const unsigned char *in, + unsigned char *out, size_t len, + const void *key, + unsigned char ivec[16], + block128_f block); +size_t CRYPTO_nistcts128_decrypt(const unsigned char *in, unsigned char *out, + size_t len, const void *key, + unsigned char ivec[16], cbc128_f cbc); + +typedef struct gcm128_context GCM128_CONTEXT; + +GCM128_CONTEXT *CRYPTO_gcm128_new(void *key, block128_f block); +void CRYPTO_gcm128_init(GCM128_CONTEXT *ctx, void *key, block128_f block); +void CRYPTO_gcm128_setiv(GCM128_CONTEXT *ctx, const unsigned char *iv, + size_t len); +int CRYPTO_gcm128_aad(GCM128_CONTEXT *ctx, const unsigned char *aad, + size_t len); +int CRYPTO_gcm128_encrypt(GCM128_CONTEXT *ctx, + const unsigned char *in, unsigned char *out, + size_t len); +int CRYPTO_gcm128_decrypt(GCM128_CONTEXT *ctx, + const unsigned char *in, unsigned char *out, + size_t len); +int CRYPTO_gcm128_encrypt_ctr32(GCM128_CONTEXT *ctx, + const unsigned char *in, unsigned char *out, + size_t len, ctr128_f stream); +int CRYPTO_gcm128_decrypt_ctr32(GCM128_CONTEXT *ctx, + const unsigned char *in, unsigned char *out, + size_t len, ctr128_f stream); +int CRYPTO_gcm128_finish(GCM128_CONTEXT *ctx, const unsigned char *tag, + size_t len); +void CRYPTO_gcm128_tag(GCM128_CONTEXT *ctx, unsigned char *tag, size_t len); +void CRYPTO_gcm128_release(GCM128_CONTEXT *ctx); + +typedef struct ccm128_context CCM128_CONTEXT; + +void CRYPTO_ccm128_init(CCM128_CONTEXT *ctx, + unsigned int M, unsigned int L, void *key, + block128_f block); +int CRYPTO_ccm128_setiv(CCM128_CONTEXT *ctx, const unsigned char *nonce, + size_t nlen, size_t mlen); +void CRYPTO_ccm128_aad(CCM128_CONTEXT *ctx, const unsigned char *aad, + size_t alen); +int CRYPTO_ccm128_encrypt(CCM128_CONTEXT *ctx, const unsigned char *inp, + unsigned char *out, size_t len); +int CRYPTO_ccm128_decrypt(CCM128_CONTEXT *ctx, const unsigned char *inp, + unsigned char *out, size_t len); +int CRYPTO_ccm128_encrypt_ccm64(CCM128_CONTEXT *ctx, const unsigned char *inp, + unsigned char *out, size_t len, + ccm128_f stream); +int CRYPTO_ccm128_decrypt_ccm64(CCM128_CONTEXT *ctx, const unsigned char *inp, + unsigned char *out, size_t len, + ccm128_f stream); +size_t CRYPTO_ccm128_tag(CCM128_CONTEXT *ctx, unsigned char *tag, size_t len); + +typedef struct xts128_context XTS128_CONTEXT; + +int CRYPTO_xts128_encrypt(const XTS128_CONTEXT *ctx, + const unsigned char iv[16], + const unsigned char *inp, unsigned char *out, + size_t len, int enc); + +size_t CRYPTO_128_wrap(void *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, size_t inlen, + block128_f block); + +size_t CRYPTO_128_unwrap(void *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, size_t inlen, + block128_f block); +size_t CRYPTO_128_wrap_pad(void *key, const unsigned char *icv, + unsigned char *out, const unsigned char *in, + size_t inlen, block128_f block); +size_t CRYPTO_128_unwrap_pad(void *key, const unsigned char *icv, + unsigned char *out, const unsigned char *in, + size_t inlen, block128_f block); + +# ifndef OPENSSL_NO_OCB +typedef struct ocb128_context OCB128_CONTEXT; + +typedef void (*ocb128_f) (const unsigned char *in, unsigned char *out, + size_t blocks, const void *key, + size_t start_block_num, + unsigned char offset_i[16], + const unsigned char L_[][16], + unsigned char checksum[16]); + +OCB128_CONTEXT *CRYPTO_ocb128_new(void *keyenc, void *keydec, + block128_f encrypt, block128_f decrypt, + ocb128_f stream); +int CRYPTO_ocb128_init(OCB128_CONTEXT *ctx, void *keyenc, void *keydec, + block128_f encrypt, block128_f decrypt, + ocb128_f stream); +int CRYPTO_ocb128_copy_ctx(OCB128_CONTEXT *dest, OCB128_CONTEXT *src, + void *keyenc, void *keydec); +int CRYPTO_ocb128_setiv(OCB128_CONTEXT *ctx, const unsigned char *iv, + size_t len, size_t taglen); +int CRYPTO_ocb128_aad(OCB128_CONTEXT *ctx, const unsigned char *aad, + size_t len); +int CRYPTO_ocb128_encrypt(OCB128_CONTEXT *ctx, const unsigned char *in, + unsigned char *out, size_t len); +int CRYPTO_ocb128_decrypt(OCB128_CONTEXT *ctx, const unsigned char *in, + unsigned char *out, size_t len); +int CRYPTO_ocb128_finish(OCB128_CONTEXT *ctx, const unsigned char *tag, + size_t len); +int CRYPTO_ocb128_tag(OCB128_CONTEXT *ctx, unsigned char *tag, size_t len); +void CRYPTO_ocb128_cleanup(OCB128_CONTEXT *ctx); +# endif /* OPENSSL_NO_OCB */ + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/thrid-party/openssl/openssl/obj_mac.h b/thrid-party/openssl/openssl/obj_mac.h new file mode 100644 index 0000000000..47dafe48d0 --- /dev/null +++ b/thrid-party/openssl/openssl/obj_mac.h @@ -0,0 +1,5198 @@ +/* + * WARNING: do not edit! + * Generated by crypto/objects/objects.pl + * + * Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved. + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#define SN_undef "UNDEF" +#define LN_undef "undefined" +#define NID_undef 0 +#define OBJ_undef 0L + +#define SN_itu_t "ITU-T" +#define LN_itu_t "itu-t" +#define NID_itu_t 645 +#define OBJ_itu_t 0L + +#define NID_ccitt 404 +#define OBJ_ccitt OBJ_itu_t + +#define SN_iso "ISO" +#define LN_iso "iso" +#define NID_iso 181 +#define OBJ_iso 1L + +#define SN_joint_iso_itu_t "JOINT-ISO-ITU-T" +#define LN_joint_iso_itu_t "joint-iso-itu-t" +#define NID_joint_iso_itu_t 646 +#define OBJ_joint_iso_itu_t 2L + +#define NID_joint_iso_ccitt 393 +#define OBJ_joint_iso_ccitt OBJ_joint_iso_itu_t + +#define SN_member_body "member-body" +#define LN_member_body "ISO Member Body" +#define NID_member_body 182 +#define OBJ_member_body OBJ_iso,2L + +#define SN_identified_organization "identified-organization" +#define NID_identified_organization 676 +#define OBJ_identified_organization OBJ_iso,3L + +#define SN_hmac_md5 "HMAC-MD5" +#define LN_hmac_md5 "hmac-md5" +#define NID_hmac_md5 780 +#define OBJ_hmac_md5 OBJ_identified_organization,6L,1L,5L,5L,8L,1L,1L + +#define SN_hmac_sha1 "HMAC-SHA1" +#define LN_hmac_sha1 "hmac-sha1" +#define NID_hmac_sha1 781 +#define OBJ_hmac_sha1 OBJ_identified_organization,6L,1L,5L,5L,8L,1L,2L + +#define SN_x509ExtAdmission "x509ExtAdmission" +#define LN_x509ExtAdmission "Professional Information or basis for Admission" +#define NID_x509ExtAdmission 1093 +#define OBJ_x509ExtAdmission OBJ_identified_organization,36L,8L,3L,3L + +#define SN_certicom_arc "certicom-arc" +#define NID_certicom_arc 677 +#define OBJ_certicom_arc OBJ_identified_organization,132L + +#define SN_ieee "ieee" +#define NID_ieee 1170 +#define OBJ_ieee OBJ_identified_organization,111L + +#define SN_ieee_siswg "ieee-siswg" +#define LN_ieee_siswg "IEEE Security in Storage Working Group" +#define NID_ieee_siswg 1171 +#define OBJ_ieee_siswg OBJ_ieee,2L,1619L + +#define SN_international_organizations "international-organizations" +#define LN_international_organizations "International Organizations" +#define NID_international_organizations 647 +#define OBJ_international_organizations OBJ_joint_iso_itu_t,23L + +#define SN_wap "wap" +#define NID_wap 678 +#define OBJ_wap OBJ_international_organizations,43L + +#define SN_wap_wsg "wap-wsg" +#define NID_wap_wsg 679 +#define OBJ_wap_wsg OBJ_wap,1L + +#define SN_selected_attribute_types "selected-attribute-types" +#define LN_selected_attribute_types "Selected Attribute Types" +#define NID_selected_attribute_types 394 +#define OBJ_selected_attribute_types OBJ_joint_iso_itu_t,5L,1L,5L + +#define SN_clearance "clearance" +#define NID_clearance 395 +#define OBJ_clearance OBJ_selected_attribute_types,55L + +#define SN_ISO_US "ISO-US" +#define LN_ISO_US "ISO US Member Body" +#define NID_ISO_US 183 +#define OBJ_ISO_US OBJ_member_body,840L + +#define SN_X9_57 "X9-57" +#define LN_X9_57 "X9.57" +#define NID_X9_57 184 +#define OBJ_X9_57 OBJ_ISO_US,10040L + +#define SN_X9cm "X9cm" +#define LN_X9cm "X9.57 CM ?" +#define NID_X9cm 185 +#define OBJ_X9cm OBJ_X9_57,4L + +#define SN_ISO_CN "ISO-CN" +#define LN_ISO_CN "ISO CN Member Body" +#define NID_ISO_CN 1140 +#define OBJ_ISO_CN OBJ_member_body,156L + +#define SN_oscca "oscca" +#define NID_oscca 1141 +#define OBJ_oscca OBJ_ISO_CN,10197L + +#define SN_sm_scheme "sm-scheme" +#define NID_sm_scheme 1142 +#define OBJ_sm_scheme OBJ_oscca,1L + +#define SN_dsa "DSA" +#define LN_dsa "dsaEncryption" +#define NID_dsa 116 +#define OBJ_dsa OBJ_X9cm,1L + +#define SN_dsaWithSHA1 "DSA-SHA1" +#define LN_dsaWithSHA1 "dsaWithSHA1" +#define NID_dsaWithSHA1 113 +#define OBJ_dsaWithSHA1 OBJ_X9cm,3L + +#define SN_ansi_X9_62 "ansi-X9-62" +#define LN_ansi_X9_62 "ANSI X9.62" +#define NID_ansi_X9_62 405 +#define OBJ_ansi_X9_62 OBJ_ISO_US,10045L + +#define OBJ_X9_62_id_fieldType OBJ_ansi_X9_62,1L + +#define SN_X9_62_prime_field "prime-field" +#define NID_X9_62_prime_field 406 +#define OBJ_X9_62_prime_field OBJ_X9_62_id_fieldType,1L + +#define SN_X9_62_characteristic_two_field "characteristic-two-field" +#define NID_X9_62_characteristic_two_field 407 +#define OBJ_X9_62_characteristic_two_field OBJ_X9_62_id_fieldType,2L + +#define SN_X9_62_id_characteristic_two_basis "id-characteristic-two-basis" +#define NID_X9_62_id_characteristic_two_basis 680 +#define OBJ_X9_62_id_characteristic_two_basis OBJ_X9_62_characteristic_two_field,3L + +#define SN_X9_62_onBasis "onBasis" +#define NID_X9_62_onBasis 681 +#define OBJ_X9_62_onBasis OBJ_X9_62_id_characteristic_two_basis,1L + +#define SN_X9_62_tpBasis "tpBasis" +#define NID_X9_62_tpBasis 682 +#define OBJ_X9_62_tpBasis OBJ_X9_62_id_characteristic_two_basis,2L + +#define SN_X9_62_ppBasis "ppBasis" +#define NID_X9_62_ppBasis 683 +#define OBJ_X9_62_ppBasis OBJ_X9_62_id_characteristic_two_basis,3L + +#define OBJ_X9_62_id_publicKeyType OBJ_ansi_X9_62,2L + +#define SN_X9_62_id_ecPublicKey "id-ecPublicKey" +#define NID_X9_62_id_ecPublicKey 408 +#define OBJ_X9_62_id_ecPublicKey OBJ_X9_62_id_publicKeyType,1L + +#define OBJ_X9_62_ellipticCurve OBJ_ansi_X9_62,3L + +#define OBJ_X9_62_c_TwoCurve OBJ_X9_62_ellipticCurve,0L + +#define SN_X9_62_c2pnb163v1 "c2pnb163v1" +#define NID_X9_62_c2pnb163v1 684 +#define OBJ_X9_62_c2pnb163v1 OBJ_X9_62_c_TwoCurve,1L + +#define SN_X9_62_c2pnb163v2 "c2pnb163v2" +#define NID_X9_62_c2pnb163v2 685 +#define OBJ_X9_62_c2pnb163v2 OBJ_X9_62_c_TwoCurve,2L + +#define SN_X9_62_c2pnb163v3 "c2pnb163v3" +#define NID_X9_62_c2pnb163v3 686 +#define OBJ_X9_62_c2pnb163v3 OBJ_X9_62_c_TwoCurve,3L + +#define SN_X9_62_c2pnb176v1 "c2pnb176v1" +#define NID_X9_62_c2pnb176v1 687 +#define OBJ_X9_62_c2pnb176v1 OBJ_X9_62_c_TwoCurve,4L + +#define SN_X9_62_c2tnb191v1 "c2tnb191v1" +#define NID_X9_62_c2tnb191v1 688 +#define OBJ_X9_62_c2tnb191v1 OBJ_X9_62_c_TwoCurve,5L + +#define SN_X9_62_c2tnb191v2 "c2tnb191v2" +#define NID_X9_62_c2tnb191v2 689 +#define OBJ_X9_62_c2tnb191v2 OBJ_X9_62_c_TwoCurve,6L + +#define SN_X9_62_c2tnb191v3 "c2tnb191v3" +#define NID_X9_62_c2tnb191v3 690 +#define OBJ_X9_62_c2tnb191v3 OBJ_X9_62_c_TwoCurve,7L + +#define SN_X9_62_c2onb191v4 "c2onb191v4" +#define NID_X9_62_c2onb191v4 691 +#define OBJ_X9_62_c2onb191v4 OBJ_X9_62_c_TwoCurve,8L + +#define SN_X9_62_c2onb191v5 "c2onb191v5" +#define NID_X9_62_c2onb191v5 692 +#define OBJ_X9_62_c2onb191v5 OBJ_X9_62_c_TwoCurve,9L + +#define SN_X9_62_c2pnb208w1 "c2pnb208w1" +#define NID_X9_62_c2pnb208w1 693 +#define OBJ_X9_62_c2pnb208w1 OBJ_X9_62_c_TwoCurve,10L + +#define SN_X9_62_c2tnb239v1 "c2tnb239v1" +#define NID_X9_62_c2tnb239v1 694 +#define OBJ_X9_62_c2tnb239v1 OBJ_X9_62_c_TwoCurve,11L + +#define SN_X9_62_c2tnb239v2 "c2tnb239v2" +#define NID_X9_62_c2tnb239v2 695 +#define OBJ_X9_62_c2tnb239v2 OBJ_X9_62_c_TwoCurve,12L + +#define SN_X9_62_c2tnb239v3 "c2tnb239v3" +#define NID_X9_62_c2tnb239v3 696 +#define OBJ_X9_62_c2tnb239v3 OBJ_X9_62_c_TwoCurve,13L + +#define SN_X9_62_c2onb239v4 "c2onb239v4" +#define NID_X9_62_c2onb239v4 697 +#define OBJ_X9_62_c2onb239v4 OBJ_X9_62_c_TwoCurve,14L + +#define SN_X9_62_c2onb239v5 "c2onb239v5" +#define NID_X9_62_c2onb239v5 698 +#define OBJ_X9_62_c2onb239v5 OBJ_X9_62_c_TwoCurve,15L + +#define SN_X9_62_c2pnb272w1 "c2pnb272w1" +#define NID_X9_62_c2pnb272w1 699 +#define OBJ_X9_62_c2pnb272w1 OBJ_X9_62_c_TwoCurve,16L + +#define SN_X9_62_c2pnb304w1 "c2pnb304w1" +#define NID_X9_62_c2pnb304w1 700 +#define OBJ_X9_62_c2pnb304w1 OBJ_X9_62_c_TwoCurve,17L + +#define SN_X9_62_c2tnb359v1 "c2tnb359v1" +#define NID_X9_62_c2tnb359v1 701 +#define OBJ_X9_62_c2tnb359v1 OBJ_X9_62_c_TwoCurve,18L + +#define SN_X9_62_c2pnb368w1 "c2pnb368w1" +#define NID_X9_62_c2pnb368w1 702 +#define OBJ_X9_62_c2pnb368w1 OBJ_X9_62_c_TwoCurve,19L + +#define SN_X9_62_c2tnb431r1 "c2tnb431r1" +#define NID_X9_62_c2tnb431r1 703 +#define OBJ_X9_62_c2tnb431r1 OBJ_X9_62_c_TwoCurve,20L + +#define OBJ_X9_62_primeCurve OBJ_X9_62_ellipticCurve,1L + +#define SN_X9_62_prime192v1 "prime192v1" +#define NID_X9_62_prime192v1 409 +#define OBJ_X9_62_prime192v1 OBJ_X9_62_primeCurve,1L + +#define SN_X9_62_prime192v2 "prime192v2" +#define NID_X9_62_prime192v2 410 +#define OBJ_X9_62_prime192v2 OBJ_X9_62_primeCurve,2L + +#define SN_X9_62_prime192v3 "prime192v3" +#define NID_X9_62_prime192v3 411 +#define OBJ_X9_62_prime192v3 OBJ_X9_62_primeCurve,3L + +#define SN_X9_62_prime239v1 "prime239v1" +#define NID_X9_62_prime239v1 412 +#define OBJ_X9_62_prime239v1 OBJ_X9_62_primeCurve,4L + +#define SN_X9_62_prime239v2 "prime239v2" +#define NID_X9_62_prime239v2 413 +#define OBJ_X9_62_prime239v2 OBJ_X9_62_primeCurve,5L + +#define SN_X9_62_prime239v3 "prime239v3" +#define NID_X9_62_prime239v3 414 +#define OBJ_X9_62_prime239v3 OBJ_X9_62_primeCurve,6L + +#define SN_X9_62_prime256v1 "prime256v1" +#define NID_X9_62_prime256v1 415 +#define OBJ_X9_62_prime256v1 OBJ_X9_62_primeCurve,7L + +#define OBJ_X9_62_id_ecSigType OBJ_ansi_X9_62,4L + +#define SN_ecdsa_with_SHA1 "ecdsa-with-SHA1" +#define NID_ecdsa_with_SHA1 416 +#define OBJ_ecdsa_with_SHA1 OBJ_X9_62_id_ecSigType,1L + +#define SN_ecdsa_with_Recommended "ecdsa-with-Recommended" +#define NID_ecdsa_with_Recommended 791 +#define OBJ_ecdsa_with_Recommended OBJ_X9_62_id_ecSigType,2L + +#define SN_ecdsa_with_Specified "ecdsa-with-Specified" +#define NID_ecdsa_with_Specified 792 +#define OBJ_ecdsa_with_Specified OBJ_X9_62_id_ecSigType,3L + +#define SN_ecdsa_with_SHA224 "ecdsa-with-SHA224" +#define NID_ecdsa_with_SHA224 793 +#define OBJ_ecdsa_with_SHA224 OBJ_ecdsa_with_Specified,1L + +#define SN_ecdsa_with_SHA256 "ecdsa-with-SHA256" +#define NID_ecdsa_with_SHA256 794 +#define OBJ_ecdsa_with_SHA256 OBJ_ecdsa_with_Specified,2L + +#define SN_ecdsa_with_SHA384 "ecdsa-with-SHA384" +#define NID_ecdsa_with_SHA384 795 +#define OBJ_ecdsa_with_SHA384 OBJ_ecdsa_with_Specified,3L + +#define SN_ecdsa_with_SHA512 "ecdsa-with-SHA512" +#define NID_ecdsa_with_SHA512 796 +#define OBJ_ecdsa_with_SHA512 OBJ_ecdsa_with_Specified,4L + +#define OBJ_secg_ellipticCurve OBJ_certicom_arc,0L + +#define SN_secp112r1 "secp112r1" +#define NID_secp112r1 704 +#define OBJ_secp112r1 OBJ_secg_ellipticCurve,6L + +#define SN_secp112r2 "secp112r2" +#define NID_secp112r2 705 +#define OBJ_secp112r2 OBJ_secg_ellipticCurve,7L + +#define SN_secp128r1 "secp128r1" +#define NID_secp128r1 706 +#define OBJ_secp128r1 OBJ_secg_ellipticCurve,28L + +#define SN_secp128r2 "secp128r2" +#define NID_secp128r2 707 +#define OBJ_secp128r2 OBJ_secg_ellipticCurve,29L + +#define SN_secp160k1 "secp160k1" +#define NID_secp160k1 708 +#define OBJ_secp160k1 OBJ_secg_ellipticCurve,9L + +#define SN_secp160r1 "secp160r1" +#define NID_secp160r1 709 +#define OBJ_secp160r1 OBJ_secg_ellipticCurve,8L + +#define SN_secp160r2 "secp160r2" +#define NID_secp160r2 710 +#define OBJ_secp160r2 OBJ_secg_ellipticCurve,30L + +#define SN_secp192k1 "secp192k1" +#define NID_secp192k1 711 +#define OBJ_secp192k1 OBJ_secg_ellipticCurve,31L + +#define SN_secp224k1 "secp224k1" +#define NID_secp224k1 712 +#define OBJ_secp224k1 OBJ_secg_ellipticCurve,32L + +#define SN_secp224r1 "secp224r1" +#define NID_secp224r1 713 +#define OBJ_secp224r1 OBJ_secg_ellipticCurve,33L + +#define SN_secp256k1 "secp256k1" +#define NID_secp256k1 714 +#define OBJ_secp256k1 OBJ_secg_ellipticCurve,10L + +#define SN_secp384r1 "secp384r1" +#define NID_secp384r1 715 +#define OBJ_secp384r1 OBJ_secg_ellipticCurve,34L + +#define SN_secp521r1 "secp521r1" +#define NID_secp521r1 716 +#define OBJ_secp521r1 OBJ_secg_ellipticCurve,35L + +#define SN_sect113r1 "sect113r1" +#define NID_sect113r1 717 +#define OBJ_sect113r1 OBJ_secg_ellipticCurve,4L + +#define SN_sect113r2 "sect113r2" +#define NID_sect113r2 718 +#define OBJ_sect113r2 OBJ_secg_ellipticCurve,5L + +#define SN_sect131r1 "sect131r1" +#define NID_sect131r1 719 +#define OBJ_sect131r1 OBJ_secg_ellipticCurve,22L + +#define SN_sect131r2 "sect131r2" +#define NID_sect131r2 720 +#define OBJ_sect131r2 OBJ_secg_ellipticCurve,23L + +#define SN_sect163k1 "sect163k1" +#define NID_sect163k1 721 +#define OBJ_sect163k1 OBJ_secg_ellipticCurve,1L + +#define SN_sect163r1 "sect163r1" +#define NID_sect163r1 722 +#define OBJ_sect163r1 OBJ_secg_ellipticCurve,2L + +#define SN_sect163r2 "sect163r2" +#define NID_sect163r2 723 +#define OBJ_sect163r2 OBJ_secg_ellipticCurve,15L + +#define SN_sect193r1 "sect193r1" +#define NID_sect193r1 724 +#define OBJ_sect193r1 OBJ_secg_ellipticCurve,24L + +#define SN_sect193r2 "sect193r2" +#define NID_sect193r2 725 +#define OBJ_sect193r2 OBJ_secg_ellipticCurve,25L + +#define SN_sect233k1 "sect233k1" +#define NID_sect233k1 726 +#define OBJ_sect233k1 OBJ_secg_ellipticCurve,26L + +#define SN_sect233r1 "sect233r1" +#define NID_sect233r1 727 +#define OBJ_sect233r1 OBJ_secg_ellipticCurve,27L + +#define SN_sect239k1 "sect239k1" +#define NID_sect239k1 728 +#define OBJ_sect239k1 OBJ_secg_ellipticCurve,3L + +#define SN_sect283k1 "sect283k1" +#define NID_sect283k1 729 +#define OBJ_sect283k1 OBJ_secg_ellipticCurve,16L + +#define SN_sect283r1 "sect283r1" +#define NID_sect283r1 730 +#define OBJ_sect283r1 OBJ_secg_ellipticCurve,17L + +#define SN_sect409k1 "sect409k1" +#define NID_sect409k1 731 +#define OBJ_sect409k1 OBJ_secg_ellipticCurve,36L + +#define SN_sect409r1 "sect409r1" +#define NID_sect409r1 732 +#define OBJ_sect409r1 OBJ_secg_ellipticCurve,37L + +#define SN_sect571k1 "sect571k1" +#define NID_sect571k1 733 +#define OBJ_sect571k1 OBJ_secg_ellipticCurve,38L + +#define SN_sect571r1 "sect571r1" +#define NID_sect571r1 734 +#define OBJ_sect571r1 OBJ_secg_ellipticCurve,39L + +#define OBJ_wap_wsg_idm_ecid OBJ_wap_wsg,4L + +#define SN_wap_wsg_idm_ecid_wtls1 "wap-wsg-idm-ecid-wtls1" +#define NID_wap_wsg_idm_ecid_wtls1 735 +#define OBJ_wap_wsg_idm_ecid_wtls1 OBJ_wap_wsg_idm_ecid,1L + +#define SN_wap_wsg_idm_ecid_wtls3 "wap-wsg-idm-ecid-wtls3" +#define NID_wap_wsg_idm_ecid_wtls3 736 +#define OBJ_wap_wsg_idm_ecid_wtls3 OBJ_wap_wsg_idm_ecid,3L + +#define SN_wap_wsg_idm_ecid_wtls4 "wap-wsg-idm-ecid-wtls4" +#define NID_wap_wsg_idm_ecid_wtls4 737 +#define OBJ_wap_wsg_idm_ecid_wtls4 OBJ_wap_wsg_idm_ecid,4L + +#define SN_wap_wsg_idm_ecid_wtls5 "wap-wsg-idm-ecid-wtls5" +#define NID_wap_wsg_idm_ecid_wtls5 738 +#define OBJ_wap_wsg_idm_ecid_wtls5 OBJ_wap_wsg_idm_ecid,5L + +#define SN_wap_wsg_idm_ecid_wtls6 "wap-wsg-idm-ecid-wtls6" +#define NID_wap_wsg_idm_ecid_wtls6 739 +#define OBJ_wap_wsg_idm_ecid_wtls6 OBJ_wap_wsg_idm_ecid,6L + +#define SN_wap_wsg_idm_ecid_wtls7 "wap-wsg-idm-ecid-wtls7" +#define NID_wap_wsg_idm_ecid_wtls7 740 +#define OBJ_wap_wsg_idm_ecid_wtls7 OBJ_wap_wsg_idm_ecid,7L + +#define SN_wap_wsg_idm_ecid_wtls8 "wap-wsg-idm-ecid-wtls8" +#define NID_wap_wsg_idm_ecid_wtls8 741 +#define OBJ_wap_wsg_idm_ecid_wtls8 OBJ_wap_wsg_idm_ecid,8L + +#define SN_wap_wsg_idm_ecid_wtls9 "wap-wsg-idm-ecid-wtls9" +#define NID_wap_wsg_idm_ecid_wtls9 742 +#define OBJ_wap_wsg_idm_ecid_wtls9 OBJ_wap_wsg_idm_ecid,9L + +#define SN_wap_wsg_idm_ecid_wtls10 "wap-wsg-idm-ecid-wtls10" +#define NID_wap_wsg_idm_ecid_wtls10 743 +#define OBJ_wap_wsg_idm_ecid_wtls10 OBJ_wap_wsg_idm_ecid,10L + +#define SN_wap_wsg_idm_ecid_wtls11 "wap-wsg-idm-ecid-wtls11" +#define NID_wap_wsg_idm_ecid_wtls11 744 +#define OBJ_wap_wsg_idm_ecid_wtls11 OBJ_wap_wsg_idm_ecid,11L + +#define SN_wap_wsg_idm_ecid_wtls12 "wap-wsg-idm-ecid-wtls12" +#define NID_wap_wsg_idm_ecid_wtls12 745 +#define OBJ_wap_wsg_idm_ecid_wtls12 OBJ_wap_wsg_idm_ecid,12L + +#define SN_cast5_cbc "CAST5-CBC" +#define LN_cast5_cbc "cast5-cbc" +#define NID_cast5_cbc 108 +#define OBJ_cast5_cbc OBJ_ISO_US,113533L,7L,66L,10L + +#define SN_cast5_ecb "CAST5-ECB" +#define LN_cast5_ecb "cast5-ecb" +#define NID_cast5_ecb 109 + +#define SN_cast5_cfb64 "CAST5-CFB" +#define LN_cast5_cfb64 "cast5-cfb" +#define NID_cast5_cfb64 110 + +#define SN_cast5_ofb64 "CAST5-OFB" +#define LN_cast5_ofb64 "cast5-ofb" +#define NID_cast5_ofb64 111 + +#define LN_pbeWithMD5AndCast5_CBC "pbeWithMD5AndCast5CBC" +#define NID_pbeWithMD5AndCast5_CBC 112 +#define OBJ_pbeWithMD5AndCast5_CBC OBJ_ISO_US,113533L,7L,66L,12L + +#define SN_id_PasswordBasedMAC "id-PasswordBasedMAC" +#define LN_id_PasswordBasedMAC "password based MAC" +#define NID_id_PasswordBasedMAC 782 +#define OBJ_id_PasswordBasedMAC OBJ_ISO_US,113533L,7L,66L,13L + +#define SN_id_DHBasedMac "id-DHBasedMac" +#define LN_id_DHBasedMac "Diffie-Hellman based MAC" +#define NID_id_DHBasedMac 783 +#define OBJ_id_DHBasedMac OBJ_ISO_US,113533L,7L,66L,30L + +#define SN_rsadsi "rsadsi" +#define LN_rsadsi "RSA Data Security, Inc." +#define NID_rsadsi 1 +#define OBJ_rsadsi OBJ_ISO_US,113549L + +#define SN_pkcs "pkcs" +#define LN_pkcs "RSA Data Security, Inc. PKCS" +#define NID_pkcs 2 +#define OBJ_pkcs OBJ_rsadsi,1L + +#define SN_pkcs1 "pkcs1" +#define NID_pkcs1 186 +#define OBJ_pkcs1 OBJ_pkcs,1L + +#define LN_rsaEncryption "rsaEncryption" +#define NID_rsaEncryption 6 +#define OBJ_rsaEncryption OBJ_pkcs1,1L + +#define SN_md2WithRSAEncryption "RSA-MD2" +#define LN_md2WithRSAEncryption "md2WithRSAEncryption" +#define NID_md2WithRSAEncryption 7 +#define OBJ_md2WithRSAEncryption OBJ_pkcs1,2L + +#define SN_md4WithRSAEncryption "RSA-MD4" +#define LN_md4WithRSAEncryption "md4WithRSAEncryption" +#define NID_md4WithRSAEncryption 396 +#define OBJ_md4WithRSAEncryption OBJ_pkcs1,3L + +#define SN_md5WithRSAEncryption "RSA-MD5" +#define LN_md5WithRSAEncryption "md5WithRSAEncryption" +#define NID_md5WithRSAEncryption 8 +#define OBJ_md5WithRSAEncryption OBJ_pkcs1,4L + +#define SN_sha1WithRSAEncryption "RSA-SHA1" +#define LN_sha1WithRSAEncryption "sha1WithRSAEncryption" +#define NID_sha1WithRSAEncryption 65 +#define OBJ_sha1WithRSAEncryption OBJ_pkcs1,5L + +#define SN_rsaesOaep "RSAES-OAEP" +#define LN_rsaesOaep "rsaesOaep" +#define NID_rsaesOaep 919 +#define OBJ_rsaesOaep OBJ_pkcs1,7L + +#define SN_mgf1 "MGF1" +#define LN_mgf1 "mgf1" +#define NID_mgf1 911 +#define OBJ_mgf1 OBJ_pkcs1,8L + +#define SN_pSpecified "PSPECIFIED" +#define LN_pSpecified "pSpecified" +#define NID_pSpecified 935 +#define OBJ_pSpecified OBJ_pkcs1,9L + +#define SN_rsassaPss "RSASSA-PSS" +#define LN_rsassaPss "rsassaPss" +#define NID_rsassaPss 912 +#define OBJ_rsassaPss OBJ_pkcs1,10L + +#define SN_sha256WithRSAEncryption "RSA-SHA256" +#define LN_sha256WithRSAEncryption "sha256WithRSAEncryption" +#define NID_sha256WithRSAEncryption 668 +#define OBJ_sha256WithRSAEncryption OBJ_pkcs1,11L + +#define SN_sha384WithRSAEncryption "RSA-SHA384" +#define LN_sha384WithRSAEncryption "sha384WithRSAEncryption" +#define NID_sha384WithRSAEncryption 669 +#define OBJ_sha384WithRSAEncryption OBJ_pkcs1,12L + +#define SN_sha512WithRSAEncryption "RSA-SHA512" +#define LN_sha512WithRSAEncryption "sha512WithRSAEncryption" +#define NID_sha512WithRSAEncryption 670 +#define OBJ_sha512WithRSAEncryption OBJ_pkcs1,13L + +#define SN_sha224WithRSAEncryption "RSA-SHA224" +#define LN_sha224WithRSAEncryption "sha224WithRSAEncryption" +#define NID_sha224WithRSAEncryption 671 +#define OBJ_sha224WithRSAEncryption OBJ_pkcs1,14L + +#define SN_sha512_224WithRSAEncryption "RSA-SHA512/224" +#define LN_sha512_224WithRSAEncryption "sha512-224WithRSAEncryption" +#define NID_sha512_224WithRSAEncryption 1145 +#define OBJ_sha512_224WithRSAEncryption OBJ_pkcs1,15L + +#define SN_sha512_256WithRSAEncryption "RSA-SHA512/256" +#define LN_sha512_256WithRSAEncryption "sha512-256WithRSAEncryption" +#define NID_sha512_256WithRSAEncryption 1146 +#define OBJ_sha512_256WithRSAEncryption OBJ_pkcs1,16L + +#define SN_pkcs3 "pkcs3" +#define NID_pkcs3 27 +#define OBJ_pkcs3 OBJ_pkcs,3L + +#define LN_dhKeyAgreement "dhKeyAgreement" +#define NID_dhKeyAgreement 28 +#define OBJ_dhKeyAgreement OBJ_pkcs3,1L + +#define SN_pkcs5 "pkcs5" +#define NID_pkcs5 187 +#define OBJ_pkcs5 OBJ_pkcs,5L + +#define SN_pbeWithMD2AndDES_CBC "PBE-MD2-DES" +#define LN_pbeWithMD2AndDES_CBC "pbeWithMD2AndDES-CBC" +#define NID_pbeWithMD2AndDES_CBC 9 +#define OBJ_pbeWithMD2AndDES_CBC OBJ_pkcs5,1L + +#define SN_pbeWithMD5AndDES_CBC "PBE-MD5-DES" +#define LN_pbeWithMD5AndDES_CBC "pbeWithMD5AndDES-CBC" +#define NID_pbeWithMD5AndDES_CBC 10 +#define OBJ_pbeWithMD5AndDES_CBC OBJ_pkcs5,3L + +#define SN_pbeWithMD2AndRC2_CBC "PBE-MD2-RC2-64" +#define LN_pbeWithMD2AndRC2_CBC "pbeWithMD2AndRC2-CBC" +#define NID_pbeWithMD2AndRC2_CBC 168 +#define OBJ_pbeWithMD2AndRC2_CBC OBJ_pkcs5,4L + +#define SN_pbeWithMD5AndRC2_CBC "PBE-MD5-RC2-64" +#define LN_pbeWithMD5AndRC2_CBC "pbeWithMD5AndRC2-CBC" +#define NID_pbeWithMD5AndRC2_CBC 169 +#define OBJ_pbeWithMD5AndRC2_CBC OBJ_pkcs5,6L + +#define SN_pbeWithSHA1AndDES_CBC "PBE-SHA1-DES" +#define LN_pbeWithSHA1AndDES_CBC "pbeWithSHA1AndDES-CBC" +#define NID_pbeWithSHA1AndDES_CBC 170 +#define OBJ_pbeWithSHA1AndDES_CBC OBJ_pkcs5,10L + +#define SN_pbeWithSHA1AndRC2_CBC "PBE-SHA1-RC2-64" +#define LN_pbeWithSHA1AndRC2_CBC "pbeWithSHA1AndRC2-CBC" +#define NID_pbeWithSHA1AndRC2_CBC 68 +#define OBJ_pbeWithSHA1AndRC2_CBC OBJ_pkcs5,11L + +#define LN_id_pbkdf2 "PBKDF2" +#define NID_id_pbkdf2 69 +#define OBJ_id_pbkdf2 OBJ_pkcs5,12L + +#define LN_pbes2 "PBES2" +#define NID_pbes2 161 +#define OBJ_pbes2 OBJ_pkcs5,13L + +#define LN_pbmac1 "PBMAC1" +#define NID_pbmac1 162 +#define OBJ_pbmac1 OBJ_pkcs5,14L + +#define SN_pkcs7 "pkcs7" +#define NID_pkcs7 20 +#define OBJ_pkcs7 OBJ_pkcs,7L + +#define LN_pkcs7_data "pkcs7-data" +#define NID_pkcs7_data 21 +#define OBJ_pkcs7_data OBJ_pkcs7,1L + +#define LN_pkcs7_signed "pkcs7-signedData" +#define NID_pkcs7_signed 22 +#define OBJ_pkcs7_signed OBJ_pkcs7,2L + +#define LN_pkcs7_enveloped "pkcs7-envelopedData" +#define NID_pkcs7_enveloped 23 +#define OBJ_pkcs7_enveloped OBJ_pkcs7,3L + +#define LN_pkcs7_signedAndEnveloped "pkcs7-signedAndEnvelopedData" +#define NID_pkcs7_signedAndEnveloped 24 +#define OBJ_pkcs7_signedAndEnveloped OBJ_pkcs7,4L + +#define LN_pkcs7_digest "pkcs7-digestData" +#define NID_pkcs7_digest 25 +#define OBJ_pkcs7_digest OBJ_pkcs7,5L + +#define LN_pkcs7_encrypted "pkcs7-encryptedData" +#define NID_pkcs7_encrypted 26 +#define OBJ_pkcs7_encrypted OBJ_pkcs7,6L + +#define SN_pkcs9 "pkcs9" +#define NID_pkcs9 47 +#define OBJ_pkcs9 OBJ_pkcs,9L + +#define LN_pkcs9_emailAddress "emailAddress" +#define NID_pkcs9_emailAddress 48 +#define OBJ_pkcs9_emailAddress OBJ_pkcs9,1L + +#define LN_pkcs9_unstructuredName "unstructuredName" +#define NID_pkcs9_unstructuredName 49 +#define OBJ_pkcs9_unstructuredName OBJ_pkcs9,2L + +#define LN_pkcs9_contentType "contentType" +#define NID_pkcs9_contentType 50 +#define OBJ_pkcs9_contentType OBJ_pkcs9,3L + +#define LN_pkcs9_messageDigest "messageDigest" +#define NID_pkcs9_messageDigest 51 +#define OBJ_pkcs9_messageDigest OBJ_pkcs9,4L + +#define LN_pkcs9_signingTime "signingTime" +#define NID_pkcs9_signingTime 52 +#define OBJ_pkcs9_signingTime OBJ_pkcs9,5L + +#define LN_pkcs9_countersignature "countersignature" +#define NID_pkcs9_countersignature 53 +#define OBJ_pkcs9_countersignature OBJ_pkcs9,6L + +#define LN_pkcs9_challengePassword "challengePassword" +#define NID_pkcs9_challengePassword 54 +#define OBJ_pkcs9_challengePassword OBJ_pkcs9,7L + +#define LN_pkcs9_unstructuredAddress "unstructuredAddress" +#define NID_pkcs9_unstructuredAddress 55 +#define OBJ_pkcs9_unstructuredAddress OBJ_pkcs9,8L + +#define LN_pkcs9_extCertAttributes "extendedCertificateAttributes" +#define NID_pkcs9_extCertAttributes 56 +#define OBJ_pkcs9_extCertAttributes OBJ_pkcs9,9L + +#define SN_ext_req "extReq" +#define LN_ext_req "Extension Request" +#define NID_ext_req 172 +#define OBJ_ext_req OBJ_pkcs9,14L + +#define SN_SMIMECapabilities "SMIME-CAPS" +#define LN_SMIMECapabilities "S/MIME Capabilities" +#define NID_SMIMECapabilities 167 +#define OBJ_SMIMECapabilities OBJ_pkcs9,15L + +#define SN_SMIME "SMIME" +#define LN_SMIME "S/MIME" +#define NID_SMIME 188 +#define OBJ_SMIME OBJ_pkcs9,16L + +#define SN_id_smime_mod "id-smime-mod" +#define NID_id_smime_mod 189 +#define OBJ_id_smime_mod OBJ_SMIME,0L + +#define SN_id_smime_ct "id-smime-ct" +#define NID_id_smime_ct 190 +#define OBJ_id_smime_ct OBJ_SMIME,1L + +#define SN_id_smime_aa "id-smime-aa" +#define NID_id_smime_aa 191 +#define OBJ_id_smime_aa OBJ_SMIME,2L + +#define SN_id_smime_alg "id-smime-alg" +#define NID_id_smime_alg 192 +#define OBJ_id_smime_alg OBJ_SMIME,3L + +#define SN_id_smime_cd "id-smime-cd" +#define NID_id_smime_cd 193 +#define OBJ_id_smime_cd OBJ_SMIME,4L + +#define SN_id_smime_spq "id-smime-spq" +#define NID_id_smime_spq 194 +#define OBJ_id_smime_spq OBJ_SMIME,5L + +#define SN_id_smime_cti "id-smime-cti" +#define NID_id_smime_cti 195 +#define OBJ_id_smime_cti OBJ_SMIME,6L + +#define SN_id_smime_mod_cms "id-smime-mod-cms" +#define NID_id_smime_mod_cms 196 +#define OBJ_id_smime_mod_cms OBJ_id_smime_mod,1L + +#define SN_id_smime_mod_ess "id-smime-mod-ess" +#define NID_id_smime_mod_ess 197 +#define OBJ_id_smime_mod_ess OBJ_id_smime_mod,2L + +#define SN_id_smime_mod_oid "id-smime-mod-oid" +#define NID_id_smime_mod_oid 198 +#define OBJ_id_smime_mod_oid OBJ_id_smime_mod,3L + +#define SN_id_smime_mod_msg_v3 "id-smime-mod-msg-v3" +#define NID_id_smime_mod_msg_v3 199 +#define OBJ_id_smime_mod_msg_v3 OBJ_id_smime_mod,4L + +#define SN_id_smime_mod_ets_eSignature_88 "id-smime-mod-ets-eSignature-88" +#define NID_id_smime_mod_ets_eSignature_88 200 +#define OBJ_id_smime_mod_ets_eSignature_88 OBJ_id_smime_mod,5L + +#define SN_id_smime_mod_ets_eSignature_97 "id-smime-mod-ets-eSignature-97" +#define NID_id_smime_mod_ets_eSignature_97 201 +#define OBJ_id_smime_mod_ets_eSignature_97 OBJ_id_smime_mod,6L + +#define SN_id_smime_mod_ets_eSigPolicy_88 "id-smime-mod-ets-eSigPolicy-88" +#define NID_id_smime_mod_ets_eSigPolicy_88 202 +#define OBJ_id_smime_mod_ets_eSigPolicy_88 OBJ_id_smime_mod,7L + +#define SN_id_smime_mod_ets_eSigPolicy_97 "id-smime-mod-ets-eSigPolicy-97" +#define NID_id_smime_mod_ets_eSigPolicy_97 203 +#define OBJ_id_smime_mod_ets_eSigPolicy_97 OBJ_id_smime_mod,8L + +#define SN_id_smime_ct_receipt "id-smime-ct-receipt" +#define NID_id_smime_ct_receipt 204 +#define OBJ_id_smime_ct_receipt OBJ_id_smime_ct,1L + +#define SN_id_smime_ct_authData "id-smime-ct-authData" +#define NID_id_smime_ct_authData 205 +#define OBJ_id_smime_ct_authData OBJ_id_smime_ct,2L + +#define SN_id_smime_ct_publishCert "id-smime-ct-publishCert" +#define NID_id_smime_ct_publishCert 206 +#define OBJ_id_smime_ct_publishCert OBJ_id_smime_ct,3L + +#define SN_id_smime_ct_TSTInfo "id-smime-ct-TSTInfo" +#define NID_id_smime_ct_TSTInfo 207 +#define OBJ_id_smime_ct_TSTInfo OBJ_id_smime_ct,4L + +#define SN_id_smime_ct_TDTInfo "id-smime-ct-TDTInfo" +#define NID_id_smime_ct_TDTInfo 208 +#define OBJ_id_smime_ct_TDTInfo OBJ_id_smime_ct,5L + +#define SN_id_smime_ct_contentInfo "id-smime-ct-contentInfo" +#define NID_id_smime_ct_contentInfo 209 +#define OBJ_id_smime_ct_contentInfo OBJ_id_smime_ct,6L + +#define SN_id_smime_ct_DVCSRequestData "id-smime-ct-DVCSRequestData" +#define NID_id_smime_ct_DVCSRequestData 210 +#define OBJ_id_smime_ct_DVCSRequestData OBJ_id_smime_ct,7L + +#define SN_id_smime_ct_DVCSResponseData "id-smime-ct-DVCSResponseData" +#define NID_id_smime_ct_DVCSResponseData 211 +#define OBJ_id_smime_ct_DVCSResponseData OBJ_id_smime_ct,8L + +#define SN_id_smime_ct_compressedData "id-smime-ct-compressedData" +#define NID_id_smime_ct_compressedData 786 +#define OBJ_id_smime_ct_compressedData OBJ_id_smime_ct,9L + +#define SN_id_smime_ct_contentCollection "id-smime-ct-contentCollection" +#define NID_id_smime_ct_contentCollection 1058 +#define OBJ_id_smime_ct_contentCollection OBJ_id_smime_ct,19L + +#define SN_id_smime_ct_authEnvelopedData "id-smime-ct-authEnvelopedData" +#define NID_id_smime_ct_authEnvelopedData 1059 +#define OBJ_id_smime_ct_authEnvelopedData OBJ_id_smime_ct,23L + +#define SN_id_ct_asciiTextWithCRLF "id-ct-asciiTextWithCRLF" +#define NID_id_ct_asciiTextWithCRLF 787 +#define OBJ_id_ct_asciiTextWithCRLF OBJ_id_smime_ct,27L + +#define SN_id_ct_xml "id-ct-xml" +#define NID_id_ct_xml 1060 +#define OBJ_id_ct_xml OBJ_id_smime_ct,28L + +#define SN_id_smime_aa_receiptRequest "id-smime-aa-receiptRequest" +#define NID_id_smime_aa_receiptRequest 212 +#define OBJ_id_smime_aa_receiptRequest OBJ_id_smime_aa,1L + +#define SN_id_smime_aa_securityLabel "id-smime-aa-securityLabel" +#define NID_id_smime_aa_securityLabel 213 +#define OBJ_id_smime_aa_securityLabel OBJ_id_smime_aa,2L + +#define SN_id_smime_aa_mlExpandHistory "id-smime-aa-mlExpandHistory" +#define NID_id_smime_aa_mlExpandHistory 214 +#define OBJ_id_smime_aa_mlExpandHistory OBJ_id_smime_aa,3L + +#define SN_id_smime_aa_contentHint "id-smime-aa-contentHint" +#define NID_id_smime_aa_contentHint 215 +#define OBJ_id_smime_aa_contentHint OBJ_id_smime_aa,4L + +#define SN_id_smime_aa_msgSigDigest "id-smime-aa-msgSigDigest" +#define NID_id_smime_aa_msgSigDigest 216 +#define OBJ_id_smime_aa_msgSigDigest OBJ_id_smime_aa,5L + +#define SN_id_smime_aa_encapContentType "id-smime-aa-encapContentType" +#define NID_id_smime_aa_encapContentType 217 +#define OBJ_id_smime_aa_encapContentType OBJ_id_smime_aa,6L + +#define SN_id_smime_aa_contentIdentifier "id-smime-aa-contentIdentifier" +#define NID_id_smime_aa_contentIdentifier 218 +#define OBJ_id_smime_aa_contentIdentifier OBJ_id_smime_aa,7L + +#define SN_id_smime_aa_macValue "id-smime-aa-macValue" +#define NID_id_smime_aa_macValue 219 +#define OBJ_id_smime_aa_macValue OBJ_id_smime_aa,8L + +#define SN_id_smime_aa_equivalentLabels "id-smime-aa-equivalentLabels" +#define NID_id_smime_aa_equivalentLabels 220 +#define OBJ_id_smime_aa_equivalentLabels OBJ_id_smime_aa,9L + +#define SN_id_smime_aa_contentReference "id-smime-aa-contentReference" +#define NID_id_smime_aa_contentReference 221 +#define OBJ_id_smime_aa_contentReference OBJ_id_smime_aa,10L + +#define SN_id_smime_aa_encrypKeyPref "id-smime-aa-encrypKeyPref" +#define NID_id_smime_aa_encrypKeyPref 222 +#define OBJ_id_smime_aa_encrypKeyPref OBJ_id_smime_aa,11L + +#define SN_id_smime_aa_signingCertificate "id-smime-aa-signingCertificate" +#define NID_id_smime_aa_signingCertificate 223 +#define OBJ_id_smime_aa_signingCertificate OBJ_id_smime_aa,12L + +#define SN_id_smime_aa_smimeEncryptCerts "id-smime-aa-smimeEncryptCerts" +#define NID_id_smime_aa_smimeEncryptCerts 224 +#define OBJ_id_smime_aa_smimeEncryptCerts OBJ_id_smime_aa,13L + +#define SN_id_smime_aa_timeStampToken "id-smime-aa-timeStampToken" +#define NID_id_smime_aa_timeStampToken 225 +#define OBJ_id_smime_aa_timeStampToken OBJ_id_smime_aa,14L + +#define SN_id_smime_aa_ets_sigPolicyId "id-smime-aa-ets-sigPolicyId" +#define NID_id_smime_aa_ets_sigPolicyId 226 +#define OBJ_id_smime_aa_ets_sigPolicyId OBJ_id_smime_aa,15L + +#define SN_id_smime_aa_ets_commitmentType "id-smime-aa-ets-commitmentType" +#define NID_id_smime_aa_ets_commitmentType 227 +#define OBJ_id_smime_aa_ets_commitmentType OBJ_id_smime_aa,16L + +#define SN_id_smime_aa_ets_signerLocation "id-smime-aa-ets-signerLocation" +#define NID_id_smime_aa_ets_signerLocation 228 +#define OBJ_id_smime_aa_ets_signerLocation OBJ_id_smime_aa,17L + +#define SN_id_smime_aa_ets_signerAttr "id-smime-aa-ets-signerAttr" +#define NID_id_smime_aa_ets_signerAttr 229 +#define OBJ_id_smime_aa_ets_signerAttr OBJ_id_smime_aa,18L + +#define SN_id_smime_aa_ets_otherSigCert "id-smime-aa-ets-otherSigCert" +#define NID_id_smime_aa_ets_otherSigCert 230 +#define OBJ_id_smime_aa_ets_otherSigCert OBJ_id_smime_aa,19L + +#define SN_id_smime_aa_ets_contentTimestamp "id-smime-aa-ets-contentTimestamp" +#define NID_id_smime_aa_ets_contentTimestamp 231 +#define OBJ_id_smime_aa_ets_contentTimestamp OBJ_id_smime_aa,20L + +#define SN_id_smime_aa_ets_CertificateRefs "id-smime-aa-ets-CertificateRefs" +#define NID_id_smime_aa_ets_CertificateRefs 232 +#define OBJ_id_smime_aa_ets_CertificateRefs OBJ_id_smime_aa,21L + +#define SN_id_smime_aa_ets_RevocationRefs "id-smime-aa-ets-RevocationRefs" +#define NID_id_smime_aa_ets_RevocationRefs 233 +#define OBJ_id_smime_aa_ets_RevocationRefs OBJ_id_smime_aa,22L + +#define SN_id_smime_aa_ets_certValues "id-smime-aa-ets-certValues" +#define NID_id_smime_aa_ets_certValues 234 +#define OBJ_id_smime_aa_ets_certValues OBJ_id_smime_aa,23L + +#define SN_id_smime_aa_ets_revocationValues "id-smime-aa-ets-revocationValues" +#define NID_id_smime_aa_ets_revocationValues 235 +#define OBJ_id_smime_aa_ets_revocationValues OBJ_id_smime_aa,24L + +#define SN_id_smime_aa_ets_escTimeStamp "id-smime-aa-ets-escTimeStamp" +#define NID_id_smime_aa_ets_escTimeStamp 236 +#define OBJ_id_smime_aa_ets_escTimeStamp OBJ_id_smime_aa,25L + +#define SN_id_smime_aa_ets_certCRLTimestamp "id-smime-aa-ets-certCRLTimestamp" +#define NID_id_smime_aa_ets_certCRLTimestamp 237 +#define OBJ_id_smime_aa_ets_certCRLTimestamp OBJ_id_smime_aa,26L + +#define SN_id_smime_aa_ets_archiveTimeStamp "id-smime-aa-ets-archiveTimeStamp" +#define NID_id_smime_aa_ets_archiveTimeStamp 238 +#define OBJ_id_smime_aa_ets_archiveTimeStamp OBJ_id_smime_aa,27L + +#define SN_id_smime_aa_signatureType "id-smime-aa-signatureType" +#define NID_id_smime_aa_signatureType 239 +#define OBJ_id_smime_aa_signatureType OBJ_id_smime_aa,28L + +#define SN_id_smime_aa_dvcs_dvc "id-smime-aa-dvcs-dvc" +#define NID_id_smime_aa_dvcs_dvc 240 +#define OBJ_id_smime_aa_dvcs_dvc OBJ_id_smime_aa,29L + +#define SN_id_smime_aa_signingCertificateV2 "id-smime-aa-signingCertificateV2" +#define NID_id_smime_aa_signingCertificateV2 1086 +#define OBJ_id_smime_aa_signingCertificateV2 OBJ_id_smime_aa,47L + +#define SN_id_smime_alg_ESDHwith3DES "id-smime-alg-ESDHwith3DES" +#define NID_id_smime_alg_ESDHwith3DES 241 +#define OBJ_id_smime_alg_ESDHwith3DES OBJ_id_smime_alg,1L + +#define SN_id_smime_alg_ESDHwithRC2 "id-smime-alg-ESDHwithRC2" +#define NID_id_smime_alg_ESDHwithRC2 242 +#define OBJ_id_smime_alg_ESDHwithRC2 OBJ_id_smime_alg,2L + +#define SN_id_smime_alg_3DESwrap "id-smime-alg-3DESwrap" +#define NID_id_smime_alg_3DESwrap 243 +#define OBJ_id_smime_alg_3DESwrap OBJ_id_smime_alg,3L + +#define SN_id_smime_alg_RC2wrap "id-smime-alg-RC2wrap" +#define NID_id_smime_alg_RC2wrap 244 +#define OBJ_id_smime_alg_RC2wrap OBJ_id_smime_alg,4L + +#define SN_id_smime_alg_ESDH "id-smime-alg-ESDH" +#define NID_id_smime_alg_ESDH 245 +#define OBJ_id_smime_alg_ESDH OBJ_id_smime_alg,5L + +#define SN_id_smime_alg_CMS3DESwrap "id-smime-alg-CMS3DESwrap" +#define NID_id_smime_alg_CMS3DESwrap 246 +#define OBJ_id_smime_alg_CMS3DESwrap OBJ_id_smime_alg,6L + +#define SN_id_smime_alg_CMSRC2wrap "id-smime-alg-CMSRC2wrap" +#define NID_id_smime_alg_CMSRC2wrap 247 +#define OBJ_id_smime_alg_CMSRC2wrap OBJ_id_smime_alg,7L + +#define SN_id_alg_PWRI_KEK "id-alg-PWRI-KEK" +#define NID_id_alg_PWRI_KEK 893 +#define OBJ_id_alg_PWRI_KEK OBJ_id_smime_alg,9L + +#define SN_id_smime_cd_ldap "id-smime-cd-ldap" +#define NID_id_smime_cd_ldap 248 +#define OBJ_id_smime_cd_ldap OBJ_id_smime_cd,1L + +#define SN_id_smime_spq_ets_sqt_uri "id-smime-spq-ets-sqt-uri" +#define NID_id_smime_spq_ets_sqt_uri 249 +#define OBJ_id_smime_spq_ets_sqt_uri OBJ_id_smime_spq,1L + +#define SN_id_smime_spq_ets_sqt_unotice "id-smime-spq-ets-sqt-unotice" +#define NID_id_smime_spq_ets_sqt_unotice 250 +#define OBJ_id_smime_spq_ets_sqt_unotice OBJ_id_smime_spq,2L + +#define SN_id_smime_cti_ets_proofOfOrigin "id-smime-cti-ets-proofOfOrigin" +#define NID_id_smime_cti_ets_proofOfOrigin 251 +#define OBJ_id_smime_cti_ets_proofOfOrigin OBJ_id_smime_cti,1L + +#define SN_id_smime_cti_ets_proofOfReceipt "id-smime-cti-ets-proofOfReceipt" +#define NID_id_smime_cti_ets_proofOfReceipt 252 +#define OBJ_id_smime_cti_ets_proofOfReceipt OBJ_id_smime_cti,2L + +#define SN_id_smime_cti_ets_proofOfDelivery "id-smime-cti-ets-proofOfDelivery" +#define NID_id_smime_cti_ets_proofOfDelivery 253 +#define OBJ_id_smime_cti_ets_proofOfDelivery OBJ_id_smime_cti,3L + +#define SN_id_smime_cti_ets_proofOfSender "id-smime-cti-ets-proofOfSender" +#define NID_id_smime_cti_ets_proofOfSender 254 +#define OBJ_id_smime_cti_ets_proofOfSender OBJ_id_smime_cti,4L + +#define SN_id_smime_cti_ets_proofOfApproval "id-smime-cti-ets-proofOfApproval" +#define NID_id_smime_cti_ets_proofOfApproval 255 +#define OBJ_id_smime_cti_ets_proofOfApproval OBJ_id_smime_cti,5L + +#define SN_id_smime_cti_ets_proofOfCreation "id-smime-cti-ets-proofOfCreation" +#define NID_id_smime_cti_ets_proofOfCreation 256 +#define OBJ_id_smime_cti_ets_proofOfCreation OBJ_id_smime_cti,6L + +#define LN_friendlyName "friendlyName" +#define NID_friendlyName 156 +#define OBJ_friendlyName OBJ_pkcs9,20L + +#define LN_localKeyID "localKeyID" +#define NID_localKeyID 157 +#define OBJ_localKeyID OBJ_pkcs9,21L + +#define SN_ms_csp_name "CSPName" +#define LN_ms_csp_name "Microsoft CSP Name" +#define NID_ms_csp_name 417 +#define OBJ_ms_csp_name 1L,3L,6L,1L,4L,1L,311L,17L,1L + +#define SN_LocalKeySet "LocalKeySet" +#define LN_LocalKeySet "Microsoft Local Key set" +#define NID_LocalKeySet 856 +#define OBJ_LocalKeySet 1L,3L,6L,1L,4L,1L,311L,17L,2L + +#define OBJ_certTypes OBJ_pkcs9,22L + +#define LN_x509Certificate "x509Certificate" +#define NID_x509Certificate 158 +#define OBJ_x509Certificate OBJ_certTypes,1L + +#define LN_sdsiCertificate "sdsiCertificate" +#define NID_sdsiCertificate 159 +#define OBJ_sdsiCertificate OBJ_certTypes,2L + +#define OBJ_crlTypes OBJ_pkcs9,23L + +#define LN_x509Crl "x509Crl" +#define NID_x509Crl 160 +#define OBJ_x509Crl OBJ_crlTypes,1L + +#define OBJ_pkcs12 OBJ_pkcs,12L + +#define OBJ_pkcs12_pbeids OBJ_pkcs12,1L + +#define SN_pbe_WithSHA1And128BitRC4 "PBE-SHA1-RC4-128" +#define LN_pbe_WithSHA1And128BitRC4 "pbeWithSHA1And128BitRC4" +#define NID_pbe_WithSHA1And128BitRC4 144 +#define OBJ_pbe_WithSHA1And128BitRC4 OBJ_pkcs12_pbeids,1L + +#define SN_pbe_WithSHA1And40BitRC4 "PBE-SHA1-RC4-40" +#define LN_pbe_WithSHA1And40BitRC4 "pbeWithSHA1And40BitRC4" +#define NID_pbe_WithSHA1And40BitRC4 145 +#define OBJ_pbe_WithSHA1And40BitRC4 OBJ_pkcs12_pbeids,2L + +#define SN_pbe_WithSHA1And3_Key_TripleDES_CBC "PBE-SHA1-3DES" +#define LN_pbe_WithSHA1And3_Key_TripleDES_CBC "pbeWithSHA1And3-KeyTripleDES-CBC" +#define NID_pbe_WithSHA1And3_Key_TripleDES_CBC 146 +#define OBJ_pbe_WithSHA1And3_Key_TripleDES_CBC OBJ_pkcs12_pbeids,3L + +#define SN_pbe_WithSHA1And2_Key_TripleDES_CBC "PBE-SHA1-2DES" +#define LN_pbe_WithSHA1And2_Key_TripleDES_CBC "pbeWithSHA1And2-KeyTripleDES-CBC" +#define NID_pbe_WithSHA1And2_Key_TripleDES_CBC 147 +#define OBJ_pbe_WithSHA1And2_Key_TripleDES_CBC OBJ_pkcs12_pbeids,4L + +#define SN_pbe_WithSHA1And128BitRC2_CBC "PBE-SHA1-RC2-128" +#define LN_pbe_WithSHA1And128BitRC2_CBC "pbeWithSHA1And128BitRC2-CBC" +#define NID_pbe_WithSHA1And128BitRC2_CBC 148 +#define OBJ_pbe_WithSHA1And128BitRC2_CBC OBJ_pkcs12_pbeids,5L + +#define SN_pbe_WithSHA1And40BitRC2_CBC "PBE-SHA1-RC2-40" +#define LN_pbe_WithSHA1And40BitRC2_CBC "pbeWithSHA1And40BitRC2-CBC" +#define NID_pbe_WithSHA1And40BitRC2_CBC 149 +#define OBJ_pbe_WithSHA1And40BitRC2_CBC OBJ_pkcs12_pbeids,6L + +#define OBJ_pkcs12_Version1 OBJ_pkcs12,10L + +#define OBJ_pkcs12_BagIds OBJ_pkcs12_Version1,1L + +#define LN_keyBag "keyBag" +#define NID_keyBag 150 +#define OBJ_keyBag OBJ_pkcs12_BagIds,1L + +#define LN_pkcs8ShroudedKeyBag "pkcs8ShroudedKeyBag" +#define NID_pkcs8ShroudedKeyBag 151 +#define OBJ_pkcs8ShroudedKeyBag OBJ_pkcs12_BagIds,2L + +#define LN_certBag "certBag" +#define NID_certBag 152 +#define OBJ_certBag OBJ_pkcs12_BagIds,3L + +#define LN_crlBag "crlBag" +#define NID_crlBag 153 +#define OBJ_crlBag OBJ_pkcs12_BagIds,4L + +#define LN_secretBag "secretBag" +#define NID_secretBag 154 +#define OBJ_secretBag OBJ_pkcs12_BagIds,5L + +#define LN_safeContentsBag "safeContentsBag" +#define NID_safeContentsBag 155 +#define OBJ_safeContentsBag OBJ_pkcs12_BagIds,6L + +#define SN_md2 "MD2" +#define LN_md2 "md2" +#define NID_md2 3 +#define OBJ_md2 OBJ_rsadsi,2L,2L + +#define SN_md4 "MD4" +#define LN_md4 "md4" +#define NID_md4 257 +#define OBJ_md4 OBJ_rsadsi,2L,4L + +#define SN_md5 "MD5" +#define LN_md5 "md5" +#define NID_md5 4 +#define OBJ_md5 OBJ_rsadsi,2L,5L + +#define SN_md5_sha1 "MD5-SHA1" +#define LN_md5_sha1 "md5-sha1" +#define NID_md5_sha1 114 + +#define LN_hmacWithMD5 "hmacWithMD5" +#define NID_hmacWithMD5 797 +#define OBJ_hmacWithMD5 OBJ_rsadsi,2L,6L + +#define LN_hmacWithSHA1 "hmacWithSHA1" +#define NID_hmacWithSHA1 163 +#define OBJ_hmacWithSHA1 OBJ_rsadsi,2L,7L + +#define SN_sm2 "SM2" +#define LN_sm2 "sm2" +#define NID_sm2 1172 +#define OBJ_sm2 OBJ_sm_scheme,301L + +#define SN_sm3 "SM3" +#define LN_sm3 "sm3" +#define NID_sm3 1143 +#define OBJ_sm3 OBJ_sm_scheme,401L + +#define SN_sm3WithRSAEncryption "RSA-SM3" +#define LN_sm3WithRSAEncryption "sm3WithRSAEncryption" +#define NID_sm3WithRSAEncryption 1144 +#define OBJ_sm3WithRSAEncryption OBJ_sm_scheme,504L + +#define LN_hmacWithSHA224 "hmacWithSHA224" +#define NID_hmacWithSHA224 798 +#define OBJ_hmacWithSHA224 OBJ_rsadsi,2L,8L + +#define LN_hmacWithSHA256 "hmacWithSHA256" +#define NID_hmacWithSHA256 799 +#define OBJ_hmacWithSHA256 OBJ_rsadsi,2L,9L + +#define LN_hmacWithSHA384 "hmacWithSHA384" +#define NID_hmacWithSHA384 800 +#define OBJ_hmacWithSHA384 OBJ_rsadsi,2L,10L + +#define LN_hmacWithSHA512 "hmacWithSHA512" +#define NID_hmacWithSHA512 801 +#define OBJ_hmacWithSHA512 OBJ_rsadsi,2L,11L + +#define LN_hmacWithSHA512_224 "hmacWithSHA512-224" +#define NID_hmacWithSHA512_224 1193 +#define OBJ_hmacWithSHA512_224 OBJ_rsadsi,2L,12L + +#define LN_hmacWithSHA512_256 "hmacWithSHA512-256" +#define NID_hmacWithSHA512_256 1194 +#define OBJ_hmacWithSHA512_256 OBJ_rsadsi,2L,13L + +#define SN_rc2_cbc "RC2-CBC" +#define LN_rc2_cbc "rc2-cbc" +#define NID_rc2_cbc 37 +#define OBJ_rc2_cbc OBJ_rsadsi,3L,2L + +#define SN_rc2_ecb "RC2-ECB" +#define LN_rc2_ecb "rc2-ecb" +#define NID_rc2_ecb 38 + +#define SN_rc2_cfb64 "RC2-CFB" +#define LN_rc2_cfb64 "rc2-cfb" +#define NID_rc2_cfb64 39 + +#define SN_rc2_ofb64 "RC2-OFB" +#define LN_rc2_ofb64 "rc2-ofb" +#define NID_rc2_ofb64 40 + +#define SN_rc2_40_cbc "RC2-40-CBC" +#define LN_rc2_40_cbc "rc2-40-cbc" +#define NID_rc2_40_cbc 98 + +#define SN_rc2_64_cbc "RC2-64-CBC" +#define LN_rc2_64_cbc "rc2-64-cbc" +#define NID_rc2_64_cbc 166 + +#define SN_rc4 "RC4" +#define LN_rc4 "rc4" +#define NID_rc4 5 +#define OBJ_rc4 OBJ_rsadsi,3L,4L + +#define SN_rc4_40 "RC4-40" +#define LN_rc4_40 "rc4-40" +#define NID_rc4_40 97 + +#define SN_des_ede3_cbc "DES-EDE3-CBC" +#define LN_des_ede3_cbc "des-ede3-cbc" +#define NID_des_ede3_cbc 44 +#define OBJ_des_ede3_cbc OBJ_rsadsi,3L,7L + +#define SN_rc5_cbc "RC5-CBC" +#define LN_rc5_cbc "rc5-cbc" +#define NID_rc5_cbc 120 +#define OBJ_rc5_cbc OBJ_rsadsi,3L,8L + +#define SN_rc5_ecb "RC5-ECB" +#define LN_rc5_ecb "rc5-ecb" +#define NID_rc5_ecb 121 + +#define SN_rc5_cfb64 "RC5-CFB" +#define LN_rc5_cfb64 "rc5-cfb" +#define NID_rc5_cfb64 122 + +#define SN_rc5_ofb64 "RC5-OFB" +#define LN_rc5_ofb64 "rc5-ofb" +#define NID_rc5_ofb64 123 + +#define SN_ms_ext_req "msExtReq" +#define LN_ms_ext_req "Microsoft Extension Request" +#define NID_ms_ext_req 171 +#define OBJ_ms_ext_req 1L,3L,6L,1L,4L,1L,311L,2L,1L,14L + +#define SN_ms_code_ind "msCodeInd" +#define LN_ms_code_ind "Microsoft Individual Code Signing" +#define NID_ms_code_ind 134 +#define OBJ_ms_code_ind 1L,3L,6L,1L,4L,1L,311L,2L,1L,21L + +#define SN_ms_code_com "msCodeCom" +#define LN_ms_code_com "Microsoft Commercial Code Signing" +#define NID_ms_code_com 135 +#define OBJ_ms_code_com 1L,3L,6L,1L,4L,1L,311L,2L,1L,22L + +#define SN_ms_ctl_sign "msCTLSign" +#define LN_ms_ctl_sign "Microsoft Trust List Signing" +#define NID_ms_ctl_sign 136 +#define OBJ_ms_ctl_sign 1L,3L,6L,1L,4L,1L,311L,10L,3L,1L + +#define SN_ms_sgc "msSGC" +#define LN_ms_sgc "Microsoft Server Gated Crypto" +#define NID_ms_sgc 137 +#define OBJ_ms_sgc 1L,3L,6L,1L,4L,1L,311L,10L,3L,3L + +#define SN_ms_efs "msEFS" +#define LN_ms_efs "Microsoft Encrypted File System" +#define NID_ms_efs 138 +#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L + +#define SN_ms_smartcard_login "msSmartcardLogin" +#define LN_ms_smartcard_login "Microsoft Smartcardlogin" +#define NID_ms_smartcard_login 648 +#define OBJ_ms_smartcard_login 1L,3L,6L,1L,4L,1L,311L,20L,2L,2L + +#define SN_ms_upn "msUPN" +#define LN_ms_upn "Microsoft Universal Principal Name" +#define NID_ms_upn 649 +#define OBJ_ms_upn 1L,3L,6L,1L,4L,1L,311L,20L,2L,3L + +#define SN_idea_cbc "IDEA-CBC" +#define LN_idea_cbc "idea-cbc" +#define NID_idea_cbc 34 +#define OBJ_idea_cbc 1L,3L,6L,1L,4L,1L,188L,7L,1L,1L,2L + +#define SN_idea_ecb "IDEA-ECB" +#define LN_idea_ecb "idea-ecb" +#define NID_idea_ecb 36 + +#define SN_idea_cfb64 "IDEA-CFB" +#define LN_idea_cfb64 "idea-cfb" +#define NID_idea_cfb64 35 + +#define SN_idea_ofb64 "IDEA-OFB" +#define LN_idea_ofb64 "idea-ofb" +#define NID_idea_ofb64 46 + +#define SN_bf_cbc "BF-CBC" +#define LN_bf_cbc "bf-cbc" +#define NID_bf_cbc 91 +#define OBJ_bf_cbc 1L,3L,6L,1L,4L,1L,3029L,1L,2L + +#define SN_bf_ecb "BF-ECB" +#define LN_bf_ecb "bf-ecb" +#define NID_bf_ecb 92 + +#define SN_bf_cfb64 "BF-CFB" +#define LN_bf_cfb64 "bf-cfb" +#define NID_bf_cfb64 93 + +#define SN_bf_ofb64 "BF-OFB" +#define LN_bf_ofb64 "bf-ofb" +#define NID_bf_ofb64 94 + +#define SN_id_pkix "PKIX" +#define NID_id_pkix 127 +#define OBJ_id_pkix 1L,3L,6L,1L,5L,5L,7L + +#define SN_id_pkix_mod "id-pkix-mod" +#define NID_id_pkix_mod 258 +#define OBJ_id_pkix_mod OBJ_id_pkix,0L + +#define SN_id_pe "id-pe" +#define NID_id_pe 175 +#define OBJ_id_pe OBJ_id_pkix,1L + +#define SN_id_qt "id-qt" +#define NID_id_qt 259 +#define OBJ_id_qt OBJ_id_pkix,2L + +#define SN_id_kp "id-kp" +#define NID_id_kp 128 +#define OBJ_id_kp OBJ_id_pkix,3L + +#define SN_id_it "id-it" +#define NID_id_it 260 +#define OBJ_id_it OBJ_id_pkix,4L + +#define SN_id_pkip "id-pkip" +#define NID_id_pkip 261 +#define OBJ_id_pkip OBJ_id_pkix,5L + +#define SN_id_alg "id-alg" +#define NID_id_alg 262 +#define OBJ_id_alg OBJ_id_pkix,6L + +#define SN_id_cmc "id-cmc" +#define NID_id_cmc 263 +#define OBJ_id_cmc OBJ_id_pkix,7L + +#define SN_id_on "id-on" +#define NID_id_on 264 +#define OBJ_id_on OBJ_id_pkix,8L + +#define SN_id_pda "id-pda" +#define NID_id_pda 265 +#define OBJ_id_pda OBJ_id_pkix,9L + +#define SN_id_aca "id-aca" +#define NID_id_aca 266 +#define OBJ_id_aca OBJ_id_pkix,10L + +#define SN_id_qcs "id-qcs" +#define NID_id_qcs 267 +#define OBJ_id_qcs OBJ_id_pkix,11L + +#define SN_id_cct "id-cct" +#define NID_id_cct 268 +#define OBJ_id_cct OBJ_id_pkix,12L + +#define SN_id_ppl "id-ppl" +#define NID_id_ppl 662 +#define OBJ_id_ppl OBJ_id_pkix,21L + +#define SN_id_ad "id-ad" +#define NID_id_ad 176 +#define OBJ_id_ad OBJ_id_pkix,48L + +#define SN_id_pkix1_explicit_88 "id-pkix1-explicit-88" +#define NID_id_pkix1_explicit_88 269 +#define OBJ_id_pkix1_explicit_88 OBJ_id_pkix_mod,1L + +#define SN_id_pkix1_implicit_88 "id-pkix1-implicit-88" +#define NID_id_pkix1_implicit_88 270 +#define OBJ_id_pkix1_implicit_88 OBJ_id_pkix_mod,2L + +#define SN_id_pkix1_explicit_93 "id-pkix1-explicit-93" +#define NID_id_pkix1_explicit_93 271 +#define OBJ_id_pkix1_explicit_93 OBJ_id_pkix_mod,3L + +#define SN_id_pkix1_implicit_93 "id-pkix1-implicit-93" +#define NID_id_pkix1_implicit_93 272 +#define OBJ_id_pkix1_implicit_93 OBJ_id_pkix_mod,4L + +#define SN_id_mod_crmf "id-mod-crmf" +#define NID_id_mod_crmf 273 +#define OBJ_id_mod_crmf OBJ_id_pkix_mod,5L + +#define SN_id_mod_cmc "id-mod-cmc" +#define NID_id_mod_cmc 274 +#define OBJ_id_mod_cmc OBJ_id_pkix_mod,6L + +#define SN_id_mod_kea_profile_88 "id-mod-kea-profile-88" +#define NID_id_mod_kea_profile_88 275 +#define OBJ_id_mod_kea_profile_88 OBJ_id_pkix_mod,7L + +#define SN_id_mod_kea_profile_93 "id-mod-kea-profile-93" +#define NID_id_mod_kea_profile_93 276 +#define OBJ_id_mod_kea_profile_93 OBJ_id_pkix_mod,8L + +#define SN_id_mod_cmp "id-mod-cmp" +#define NID_id_mod_cmp 277 +#define OBJ_id_mod_cmp OBJ_id_pkix_mod,9L + +#define SN_id_mod_qualified_cert_88 "id-mod-qualified-cert-88" +#define NID_id_mod_qualified_cert_88 278 +#define OBJ_id_mod_qualified_cert_88 OBJ_id_pkix_mod,10L + +#define SN_id_mod_qualified_cert_93 "id-mod-qualified-cert-93" +#define NID_id_mod_qualified_cert_93 279 +#define OBJ_id_mod_qualified_cert_93 OBJ_id_pkix_mod,11L + +#define SN_id_mod_attribute_cert "id-mod-attribute-cert" +#define NID_id_mod_attribute_cert 280 +#define OBJ_id_mod_attribute_cert OBJ_id_pkix_mod,12L + +#define SN_id_mod_timestamp_protocol "id-mod-timestamp-protocol" +#define NID_id_mod_timestamp_protocol 281 +#define OBJ_id_mod_timestamp_protocol OBJ_id_pkix_mod,13L + +#define SN_id_mod_ocsp "id-mod-ocsp" +#define NID_id_mod_ocsp 282 +#define OBJ_id_mod_ocsp OBJ_id_pkix_mod,14L + +#define SN_id_mod_dvcs "id-mod-dvcs" +#define NID_id_mod_dvcs 283 +#define OBJ_id_mod_dvcs OBJ_id_pkix_mod,15L + +#define SN_id_mod_cmp2000 "id-mod-cmp2000" +#define NID_id_mod_cmp2000 284 +#define OBJ_id_mod_cmp2000 OBJ_id_pkix_mod,16L + +#define SN_info_access "authorityInfoAccess" +#define LN_info_access "Authority Information Access" +#define NID_info_access 177 +#define OBJ_info_access OBJ_id_pe,1L + +#define SN_biometricInfo "biometricInfo" +#define LN_biometricInfo "Biometric Info" +#define NID_biometricInfo 285 +#define OBJ_biometricInfo OBJ_id_pe,2L + +#define SN_qcStatements "qcStatements" +#define NID_qcStatements 286 +#define OBJ_qcStatements OBJ_id_pe,3L + +#define SN_ac_auditEntity "ac-auditEntity" +#define NID_ac_auditEntity 287 +#define OBJ_ac_auditEntity OBJ_id_pe,4L + +#define SN_ac_targeting "ac-targeting" +#define NID_ac_targeting 288 +#define OBJ_ac_targeting OBJ_id_pe,5L + +#define SN_aaControls "aaControls" +#define NID_aaControls 289 +#define OBJ_aaControls OBJ_id_pe,6L + +#define SN_sbgp_ipAddrBlock "sbgp-ipAddrBlock" +#define NID_sbgp_ipAddrBlock 290 +#define OBJ_sbgp_ipAddrBlock OBJ_id_pe,7L + +#define SN_sbgp_autonomousSysNum "sbgp-autonomousSysNum" +#define NID_sbgp_autonomousSysNum 291 +#define OBJ_sbgp_autonomousSysNum OBJ_id_pe,8L + +#define SN_sbgp_routerIdentifier "sbgp-routerIdentifier" +#define NID_sbgp_routerIdentifier 292 +#define OBJ_sbgp_routerIdentifier OBJ_id_pe,9L + +#define SN_ac_proxying "ac-proxying" +#define NID_ac_proxying 397 +#define OBJ_ac_proxying OBJ_id_pe,10L + +#define SN_sinfo_access "subjectInfoAccess" +#define LN_sinfo_access "Subject Information Access" +#define NID_sinfo_access 398 +#define OBJ_sinfo_access OBJ_id_pe,11L + +#define SN_proxyCertInfo "proxyCertInfo" +#define LN_proxyCertInfo "Proxy Certificate Information" +#define NID_proxyCertInfo 663 +#define OBJ_proxyCertInfo OBJ_id_pe,14L + +#define SN_tlsfeature "tlsfeature" +#define LN_tlsfeature "TLS Feature" +#define NID_tlsfeature 1020 +#define OBJ_tlsfeature OBJ_id_pe,24L + +#define SN_id_qt_cps "id-qt-cps" +#define LN_id_qt_cps "Policy Qualifier CPS" +#define NID_id_qt_cps 164 +#define OBJ_id_qt_cps OBJ_id_qt,1L + +#define SN_id_qt_unotice "id-qt-unotice" +#define LN_id_qt_unotice "Policy Qualifier User Notice" +#define NID_id_qt_unotice 165 +#define OBJ_id_qt_unotice OBJ_id_qt,2L + +#define SN_textNotice "textNotice" +#define NID_textNotice 293 +#define OBJ_textNotice OBJ_id_qt,3L + +#define SN_server_auth "serverAuth" +#define LN_server_auth "TLS Web Server Authentication" +#define NID_server_auth 129 +#define OBJ_server_auth OBJ_id_kp,1L + +#define SN_client_auth "clientAuth" +#define LN_client_auth "TLS Web Client Authentication" +#define NID_client_auth 130 +#define OBJ_client_auth OBJ_id_kp,2L + +#define SN_code_sign "codeSigning" +#define LN_code_sign "Code Signing" +#define NID_code_sign 131 +#define OBJ_code_sign OBJ_id_kp,3L + +#define SN_email_protect "emailProtection" +#define LN_email_protect "E-mail Protection" +#define NID_email_protect 132 +#define OBJ_email_protect OBJ_id_kp,4L + +#define SN_ipsecEndSystem "ipsecEndSystem" +#define LN_ipsecEndSystem "IPSec End System" +#define NID_ipsecEndSystem 294 +#define OBJ_ipsecEndSystem OBJ_id_kp,5L + +#define SN_ipsecTunnel "ipsecTunnel" +#define LN_ipsecTunnel "IPSec Tunnel" +#define NID_ipsecTunnel 295 +#define OBJ_ipsecTunnel OBJ_id_kp,6L + +#define SN_ipsecUser "ipsecUser" +#define LN_ipsecUser "IPSec User" +#define NID_ipsecUser 296 +#define OBJ_ipsecUser OBJ_id_kp,7L + +#define SN_time_stamp "timeStamping" +#define LN_time_stamp "Time Stamping" +#define NID_time_stamp 133 +#define OBJ_time_stamp OBJ_id_kp,8L + +#define SN_OCSP_sign "OCSPSigning" +#define LN_OCSP_sign "OCSP Signing" +#define NID_OCSP_sign 180 +#define OBJ_OCSP_sign OBJ_id_kp,9L + +#define SN_dvcs "DVCS" +#define LN_dvcs "dvcs" +#define NID_dvcs 297 +#define OBJ_dvcs OBJ_id_kp,10L + +#define SN_ipsec_IKE "ipsecIKE" +#define LN_ipsec_IKE "ipsec Internet Key Exchange" +#define NID_ipsec_IKE 1022 +#define OBJ_ipsec_IKE OBJ_id_kp,17L + +#define SN_capwapAC "capwapAC" +#define LN_capwapAC "Ctrl/provision WAP Access" +#define NID_capwapAC 1023 +#define OBJ_capwapAC OBJ_id_kp,18L + +#define SN_capwapWTP "capwapWTP" +#define LN_capwapWTP "Ctrl/Provision WAP Termination" +#define NID_capwapWTP 1024 +#define OBJ_capwapWTP OBJ_id_kp,19L + +#define SN_sshClient "secureShellClient" +#define LN_sshClient "SSH Client" +#define NID_sshClient 1025 +#define OBJ_sshClient OBJ_id_kp,21L + +#define SN_sshServer "secureShellServer" +#define LN_sshServer "SSH Server" +#define NID_sshServer 1026 +#define OBJ_sshServer OBJ_id_kp,22L + +#define SN_sendRouter "sendRouter" +#define LN_sendRouter "Send Router" +#define NID_sendRouter 1027 +#define OBJ_sendRouter OBJ_id_kp,23L + +#define SN_sendProxiedRouter "sendProxiedRouter" +#define LN_sendProxiedRouter "Send Proxied Router" +#define NID_sendProxiedRouter 1028 +#define OBJ_sendProxiedRouter OBJ_id_kp,24L + +#define SN_sendOwner "sendOwner" +#define LN_sendOwner "Send Owner" +#define NID_sendOwner 1029 +#define OBJ_sendOwner OBJ_id_kp,25L + +#define SN_sendProxiedOwner "sendProxiedOwner" +#define LN_sendProxiedOwner "Send Proxied Owner" +#define NID_sendProxiedOwner 1030 +#define OBJ_sendProxiedOwner OBJ_id_kp,26L + +#define SN_cmcCA "cmcCA" +#define LN_cmcCA "CMC Certificate Authority" +#define NID_cmcCA 1131 +#define OBJ_cmcCA OBJ_id_kp,27L + +#define SN_cmcRA "cmcRA" +#define LN_cmcRA "CMC Registration Authority" +#define NID_cmcRA 1132 +#define OBJ_cmcRA OBJ_id_kp,28L + +#define SN_id_it_caProtEncCert "id-it-caProtEncCert" +#define NID_id_it_caProtEncCert 298 +#define OBJ_id_it_caProtEncCert OBJ_id_it,1L + +#define SN_id_it_signKeyPairTypes "id-it-signKeyPairTypes" +#define NID_id_it_signKeyPairTypes 299 +#define OBJ_id_it_signKeyPairTypes OBJ_id_it,2L + +#define SN_id_it_encKeyPairTypes "id-it-encKeyPairTypes" +#define NID_id_it_encKeyPairTypes 300 +#define OBJ_id_it_encKeyPairTypes OBJ_id_it,3L + +#define SN_id_it_preferredSymmAlg "id-it-preferredSymmAlg" +#define NID_id_it_preferredSymmAlg 301 +#define OBJ_id_it_preferredSymmAlg OBJ_id_it,4L + +#define SN_id_it_caKeyUpdateInfo "id-it-caKeyUpdateInfo" +#define NID_id_it_caKeyUpdateInfo 302 +#define OBJ_id_it_caKeyUpdateInfo OBJ_id_it,5L + +#define SN_id_it_currentCRL "id-it-currentCRL" +#define NID_id_it_currentCRL 303 +#define OBJ_id_it_currentCRL OBJ_id_it,6L + +#define SN_id_it_unsupportedOIDs "id-it-unsupportedOIDs" +#define NID_id_it_unsupportedOIDs 304 +#define OBJ_id_it_unsupportedOIDs OBJ_id_it,7L + +#define SN_id_it_subscriptionRequest "id-it-subscriptionRequest" +#define NID_id_it_subscriptionRequest 305 +#define OBJ_id_it_subscriptionRequest OBJ_id_it,8L + +#define SN_id_it_subscriptionResponse "id-it-subscriptionResponse" +#define NID_id_it_subscriptionResponse 306 +#define OBJ_id_it_subscriptionResponse OBJ_id_it,9L + +#define SN_id_it_keyPairParamReq "id-it-keyPairParamReq" +#define NID_id_it_keyPairParamReq 307 +#define OBJ_id_it_keyPairParamReq OBJ_id_it,10L + +#define SN_id_it_keyPairParamRep "id-it-keyPairParamRep" +#define NID_id_it_keyPairParamRep 308 +#define OBJ_id_it_keyPairParamRep OBJ_id_it,11L + +#define SN_id_it_revPassphrase "id-it-revPassphrase" +#define NID_id_it_revPassphrase 309 +#define OBJ_id_it_revPassphrase OBJ_id_it,12L + +#define SN_id_it_implicitConfirm "id-it-implicitConfirm" +#define NID_id_it_implicitConfirm 310 +#define OBJ_id_it_implicitConfirm OBJ_id_it,13L + +#define SN_id_it_confirmWaitTime "id-it-confirmWaitTime" +#define NID_id_it_confirmWaitTime 311 +#define OBJ_id_it_confirmWaitTime OBJ_id_it,14L + +#define SN_id_it_origPKIMessage "id-it-origPKIMessage" +#define NID_id_it_origPKIMessage 312 +#define OBJ_id_it_origPKIMessage OBJ_id_it,15L + +#define SN_id_it_suppLangTags "id-it-suppLangTags" +#define NID_id_it_suppLangTags 784 +#define OBJ_id_it_suppLangTags OBJ_id_it,16L + +#define SN_id_regCtrl "id-regCtrl" +#define NID_id_regCtrl 313 +#define OBJ_id_regCtrl OBJ_id_pkip,1L + +#define SN_id_regInfo "id-regInfo" +#define NID_id_regInfo 314 +#define OBJ_id_regInfo OBJ_id_pkip,2L + +#define SN_id_regCtrl_regToken "id-regCtrl-regToken" +#define NID_id_regCtrl_regToken 315 +#define OBJ_id_regCtrl_regToken OBJ_id_regCtrl,1L + +#define SN_id_regCtrl_authenticator "id-regCtrl-authenticator" +#define NID_id_regCtrl_authenticator 316 +#define OBJ_id_regCtrl_authenticator OBJ_id_regCtrl,2L + +#define SN_id_regCtrl_pkiPublicationInfo "id-regCtrl-pkiPublicationInfo" +#define NID_id_regCtrl_pkiPublicationInfo 317 +#define OBJ_id_regCtrl_pkiPublicationInfo OBJ_id_regCtrl,3L + +#define SN_id_regCtrl_pkiArchiveOptions "id-regCtrl-pkiArchiveOptions" +#define NID_id_regCtrl_pkiArchiveOptions 318 +#define OBJ_id_regCtrl_pkiArchiveOptions OBJ_id_regCtrl,4L + +#define SN_id_regCtrl_oldCertID "id-regCtrl-oldCertID" +#define NID_id_regCtrl_oldCertID 319 +#define OBJ_id_regCtrl_oldCertID OBJ_id_regCtrl,5L + +#define SN_id_regCtrl_protocolEncrKey "id-regCtrl-protocolEncrKey" +#define NID_id_regCtrl_protocolEncrKey 320 +#define OBJ_id_regCtrl_protocolEncrKey OBJ_id_regCtrl,6L + +#define SN_id_regInfo_utf8Pairs "id-regInfo-utf8Pairs" +#define NID_id_regInfo_utf8Pairs 321 +#define OBJ_id_regInfo_utf8Pairs OBJ_id_regInfo,1L + +#define SN_id_regInfo_certReq "id-regInfo-certReq" +#define NID_id_regInfo_certReq 322 +#define OBJ_id_regInfo_certReq OBJ_id_regInfo,2L + +#define SN_id_alg_des40 "id-alg-des40" +#define NID_id_alg_des40 323 +#define OBJ_id_alg_des40 OBJ_id_alg,1L + +#define SN_id_alg_noSignature "id-alg-noSignature" +#define NID_id_alg_noSignature 324 +#define OBJ_id_alg_noSignature OBJ_id_alg,2L + +#define SN_id_alg_dh_sig_hmac_sha1 "id-alg-dh-sig-hmac-sha1" +#define NID_id_alg_dh_sig_hmac_sha1 325 +#define OBJ_id_alg_dh_sig_hmac_sha1 OBJ_id_alg,3L + +#define SN_id_alg_dh_pop "id-alg-dh-pop" +#define NID_id_alg_dh_pop 326 +#define OBJ_id_alg_dh_pop OBJ_id_alg,4L + +#define SN_id_cmc_statusInfo "id-cmc-statusInfo" +#define NID_id_cmc_statusInfo 327 +#define OBJ_id_cmc_statusInfo OBJ_id_cmc,1L + +#define SN_id_cmc_identification "id-cmc-identification" +#define NID_id_cmc_identification 328 +#define OBJ_id_cmc_identification OBJ_id_cmc,2L + +#define SN_id_cmc_identityProof "id-cmc-identityProof" +#define NID_id_cmc_identityProof 329 +#define OBJ_id_cmc_identityProof OBJ_id_cmc,3L + +#define SN_id_cmc_dataReturn "id-cmc-dataReturn" +#define NID_id_cmc_dataReturn 330 +#define OBJ_id_cmc_dataReturn OBJ_id_cmc,4L + +#define SN_id_cmc_transactionId "id-cmc-transactionId" +#define NID_id_cmc_transactionId 331 +#define OBJ_id_cmc_transactionId OBJ_id_cmc,5L + +#define SN_id_cmc_senderNonce "id-cmc-senderNonce" +#define NID_id_cmc_senderNonce 332 +#define OBJ_id_cmc_senderNonce OBJ_id_cmc,6L + +#define SN_id_cmc_recipientNonce "id-cmc-recipientNonce" +#define NID_id_cmc_recipientNonce 333 +#define OBJ_id_cmc_recipientNonce OBJ_id_cmc,7L + +#define SN_id_cmc_addExtensions "id-cmc-addExtensions" +#define NID_id_cmc_addExtensions 334 +#define OBJ_id_cmc_addExtensions OBJ_id_cmc,8L + +#define SN_id_cmc_encryptedPOP "id-cmc-encryptedPOP" +#define NID_id_cmc_encryptedPOP 335 +#define OBJ_id_cmc_encryptedPOP OBJ_id_cmc,9L + +#define SN_id_cmc_decryptedPOP "id-cmc-decryptedPOP" +#define NID_id_cmc_decryptedPOP 336 +#define OBJ_id_cmc_decryptedPOP OBJ_id_cmc,10L + +#define SN_id_cmc_lraPOPWitness "id-cmc-lraPOPWitness" +#define NID_id_cmc_lraPOPWitness 337 +#define OBJ_id_cmc_lraPOPWitness OBJ_id_cmc,11L + +#define SN_id_cmc_getCert "id-cmc-getCert" +#define NID_id_cmc_getCert 338 +#define OBJ_id_cmc_getCert OBJ_id_cmc,15L + +#define SN_id_cmc_getCRL "id-cmc-getCRL" +#define NID_id_cmc_getCRL 339 +#define OBJ_id_cmc_getCRL OBJ_id_cmc,16L + +#define SN_id_cmc_revokeRequest "id-cmc-revokeRequest" +#define NID_id_cmc_revokeRequest 340 +#define OBJ_id_cmc_revokeRequest OBJ_id_cmc,17L + +#define SN_id_cmc_regInfo "id-cmc-regInfo" +#define NID_id_cmc_regInfo 341 +#define OBJ_id_cmc_regInfo OBJ_id_cmc,18L + +#define SN_id_cmc_responseInfo "id-cmc-responseInfo" +#define NID_id_cmc_responseInfo 342 +#define OBJ_id_cmc_responseInfo OBJ_id_cmc,19L + +#define SN_id_cmc_queryPending "id-cmc-queryPending" +#define NID_id_cmc_queryPending 343 +#define OBJ_id_cmc_queryPending OBJ_id_cmc,21L + +#define SN_id_cmc_popLinkRandom "id-cmc-popLinkRandom" +#define NID_id_cmc_popLinkRandom 344 +#define OBJ_id_cmc_popLinkRandom OBJ_id_cmc,22L + +#define SN_id_cmc_popLinkWitness "id-cmc-popLinkWitness" +#define NID_id_cmc_popLinkWitness 345 +#define OBJ_id_cmc_popLinkWitness OBJ_id_cmc,23L + +#define SN_id_cmc_confirmCertAcceptance "id-cmc-confirmCertAcceptance" +#define NID_id_cmc_confirmCertAcceptance 346 +#define OBJ_id_cmc_confirmCertAcceptance OBJ_id_cmc,24L + +#define SN_id_on_personalData "id-on-personalData" +#define NID_id_on_personalData 347 +#define OBJ_id_on_personalData OBJ_id_on,1L + +#define SN_id_on_permanentIdentifier "id-on-permanentIdentifier" +#define LN_id_on_permanentIdentifier "Permanent Identifier" +#define NID_id_on_permanentIdentifier 858 +#define OBJ_id_on_permanentIdentifier OBJ_id_on,3L + +#define SN_id_pda_dateOfBirth "id-pda-dateOfBirth" +#define NID_id_pda_dateOfBirth 348 +#define OBJ_id_pda_dateOfBirth OBJ_id_pda,1L + +#define SN_id_pda_placeOfBirth "id-pda-placeOfBirth" +#define NID_id_pda_placeOfBirth 349 +#define OBJ_id_pda_placeOfBirth OBJ_id_pda,2L + +#define SN_id_pda_gender "id-pda-gender" +#define NID_id_pda_gender 351 +#define OBJ_id_pda_gender OBJ_id_pda,3L + +#define SN_id_pda_countryOfCitizenship "id-pda-countryOfCitizenship" +#define NID_id_pda_countryOfCitizenship 352 +#define OBJ_id_pda_countryOfCitizenship OBJ_id_pda,4L + +#define SN_id_pda_countryOfResidence "id-pda-countryOfResidence" +#define NID_id_pda_countryOfResidence 353 +#define OBJ_id_pda_countryOfResidence OBJ_id_pda,5L + +#define SN_id_aca_authenticationInfo "id-aca-authenticationInfo" +#define NID_id_aca_authenticationInfo 354 +#define OBJ_id_aca_authenticationInfo OBJ_id_aca,1L + +#define SN_id_aca_accessIdentity "id-aca-accessIdentity" +#define NID_id_aca_accessIdentity 355 +#define OBJ_id_aca_accessIdentity OBJ_id_aca,2L + +#define SN_id_aca_chargingIdentity "id-aca-chargingIdentity" +#define NID_id_aca_chargingIdentity 356 +#define OBJ_id_aca_chargingIdentity OBJ_id_aca,3L + +#define SN_id_aca_group "id-aca-group" +#define NID_id_aca_group 357 +#define OBJ_id_aca_group OBJ_id_aca,4L + +#define SN_id_aca_role "id-aca-role" +#define NID_id_aca_role 358 +#define OBJ_id_aca_role OBJ_id_aca,5L + +#define SN_id_aca_encAttrs "id-aca-encAttrs" +#define NID_id_aca_encAttrs 399 +#define OBJ_id_aca_encAttrs OBJ_id_aca,6L + +#define SN_id_qcs_pkixQCSyntax_v1 "id-qcs-pkixQCSyntax-v1" +#define NID_id_qcs_pkixQCSyntax_v1 359 +#define OBJ_id_qcs_pkixQCSyntax_v1 OBJ_id_qcs,1L + +#define SN_id_cct_crs "id-cct-crs" +#define NID_id_cct_crs 360 +#define OBJ_id_cct_crs OBJ_id_cct,1L + +#define SN_id_cct_PKIData "id-cct-PKIData" +#define NID_id_cct_PKIData 361 +#define OBJ_id_cct_PKIData OBJ_id_cct,2L + +#define SN_id_cct_PKIResponse "id-cct-PKIResponse" +#define NID_id_cct_PKIResponse 362 +#define OBJ_id_cct_PKIResponse OBJ_id_cct,3L + +#define SN_id_ppl_anyLanguage "id-ppl-anyLanguage" +#define LN_id_ppl_anyLanguage "Any language" +#define NID_id_ppl_anyLanguage 664 +#define OBJ_id_ppl_anyLanguage OBJ_id_ppl,0L + +#define SN_id_ppl_inheritAll "id-ppl-inheritAll" +#define LN_id_ppl_inheritAll "Inherit all" +#define NID_id_ppl_inheritAll 665 +#define OBJ_id_ppl_inheritAll OBJ_id_ppl,1L + +#define SN_Independent "id-ppl-independent" +#define LN_Independent "Independent" +#define NID_Independent 667 +#define OBJ_Independent OBJ_id_ppl,2L + +#define SN_ad_OCSP "OCSP" +#define LN_ad_OCSP "OCSP" +#define NID_ad_OCSP 178 +#define OBJ_ad_OCSP OBJ_id_ad,1L + +#define SN_ad_ca_issuers "caIssuers" +#define LN_ad_ca_issuers "CA Issuers" +#define NID_ad_ca_issuers 179 +#define OBJ_ad_ca_issuers OBJ_id_ad,2L + +#define SN_ad_timeStamping "ad_timestamping" +#define LN_ad_timeStamping "AD Time Stamping" +#define NID_ad_timeStamping 363 +#define OBJ_ad_timeStamping OBJ_id_ad,3L + +#define SN_ad_dvcs "AD_DVCS" +#define LN_ad_dvcs "ad dvcs" +#define NID_ad_dvcs 364 +#define OBJ_ad_dvcs OBJ_id_ad,4L + +#define SN_caRepository "caRepository" +#define LN_caRepository "CA Repository" +#define NID_caRepository 785 +#define OBJ_caRepository OBJ_id_ad,5L + +#define OBJ_id_pkix_OCSP OBJ_ad_OCSP + +#define SN_id_pkix_OCSP_basic "basicOCSPResponse" +#define LN_id_pkix_OCSP_basic "Basic OCSP Response" +#define NID_id_pkix_OCSP_basic 365 +#define OBJ_id_pkix_OCSP_basic OBJ_id_pkix_OCSP,1L + +#define SN_id_pkix_OCSP_Nonce "Nonce" +#define LN_id_pkix_OCSP_Nonce "OCSP Nonce" +#define NID_id_pkix_OCSP_Nonce 366 +#define OBJ_id_pkix_OCSP_Nonce OBJ_id_pkix_OCSP,2L + +#define SN_id_pkix_OCSP_CrlID "CrlID" +#define LN_id_pkix_OCSP_CrlID "OCSP CRL ID" +#define NID_id_pkix_OCSP_CrlID 367 +#define OBJ_id_pkix_OCSP_CrlID OBJ_id_pkix_OCSP,3L + +#define SN_id_pkix_OCSP_acceptableResponses "acceptableResponses" +#define LN_id_pkix_OCSP_acceptableResponses "Acceptable OCSP Responses" +#define NID_id_pkix_OCSP_acceptableResponses 368 +#define OBJ_id_pkix_OCSP_acceptableResponses OBJ_id_pkix_OCSP,4L + +#define SN_id_pkix_OCSP_noCheck "noCheck" +#define LN_id_pkix_OCSP_noCheck "OCSP No Check" +#define NID_id_pkix_OCSP_noCheck 369 +#define OBJ_id_pkix_OCSP_noCheck OBJ_id_pkix_OCSP,5L + +#define SN_id_pkix_OCSP_archiveCutoff "archiveCutoff" +#define LN_id_pkix_OCSP_archiveCutoff "OCSP Archive Cutoff" +#define NID_id_pkix_OCSP_archiveCutoff 370 +#define OBJ_id_pkix_OCSP_archiveCutoff OBJ_id_pkix_OCSP,6L + +#define SN_id_pkix_OCSP_serviceLocator "serviceLocator" +#define LN_id_pkix_OCSP_serviceLocator "OCSP Service Locator" +#define NID_id_pkix_OCSP_serviceLocator 371 +#define OBJ_id_pkix_OCSP_serviceLocator OBJ_id_pkix_OCSP,7L + +#define SN_id_pkix_OCSP_extendedStatus "extendedStatus" +#define LN_id_pkix_OCSP_extendedStatus "Extended OCSP Status" +#define NID_id_pkix_OCSP_extendedStatus 372 +#define OBJ_id_pkix_OCSP_extendedStatus OBJ_id_pkix_OCSP,8L + +#define SN_id_pkix_OCSP_valid "valid" +#define NID_id_pkix_OCSP_valid 373 +#define OBJ_id_pkix_OCSP_valid OBJ_id_pkix_OCSP,9L + +#define SN_id_pkix_OCSP_path "path" +#define NID_id_pkix_OCSP_path 374 +#define OBJ_id_pkix_OCSP_path OBJ_id_pkix_OCSP,10L + +#define SN_id_pkix_OCSP_trustRoot "trustRoot" +#define LN_id_pkix_OCSP_trustRoot "Trust Root" +#define NID_id_pkix_OCSP_trustRoot 375 +#define OBJ_id_pkix_OCSP_trustRoot OBJ_id_pkix_OCSP,11L + +#define SN_algorithm "algorithm" +#define LN_algorithm "algorithm" +#define NID_algorithm 376 +#define OBJ_algorithm 1L,3L,14L,3L,2L + +#define SN_md5WithRSA "RSA-NP-MD5" +#define LN_md5WithRSA "md5WithRSA" +#define NID_md5WithRSA 104 +#define OBJ_md5WithRSA OBJ_algorithm,3L + +#define SN_des_ecb "DES-ECB" +#define LN_des_ecb "des-ecb" +#define NID_des_ecb 29 +#define OBJ_des_ecb OBJ_algorithm,6L + +#define SN_des_cbc "DES-CBC" +#define LN_des_cbc "des-cbc" +#define NID_des_cbc 31 +#define OBJ_des_cbc OBJ_algorithm,7L + +#define SN_des_ofb64 "DES-OFB" +#define LN_des_ofb64 "des-ofb" +#define NID_des_ofb64 45 +#define OBJ_des_ofb64 OBJ_algorithm,8L + +#define SN_des_cfb64 "DES-CFB" +#define LN_des_cfb64 "des-cfb" +#define NID_des_cfb64 30 +#define OBJ_des_cfb64 OBJ_algorithm,9L + +#define SN_rsaSignature "rsaSignature" +#define NID_rsaSignature 377 +#define OBJ_rsaSignature OBJ_algorithm,11L + +#define SN_dsa_2 "DSA-old" +#define LN_dsa_2 "dsaEncryption-old" +#define NID_dsa_2 67 +#define OBJ_dsa_2 OBJ_algorithm,12L + +#define SN_dsaWithSHA "DSA-SHA" +#define LN_dsaWithSHA "dsaWithSHA" +#define NID_dsaWithSHA 66 +#define OBJ_dsaWithSHA OBJ_algorithm,13L + +#define SN_shaWithRSAEncryption "RSA-SHA" +#define LN_shaWithRSAEncryption "shaWithRSAEncryption" +#define NID_shaWithRSAEncryption 42 +#define OBJ_shaWithRSAEncryption OBJ_algorithm,15L + +#define SN_des_ede_ecb "DES-EDE" +#define LN_des_ede_ecb "des-ede" +#define NID_des_ede_ecb 32 +#define OBJ_des_ede_ecb OBJ_algorithm,17L + +#define SN_des_ede3_ecb "DES-EDE3" +#define LN_des_ede3_ecb "des-ede3" +#define NID_des_ede3_ecb 33 + +#define SN_des_ede_cbc "DES-EDE-CBC" +#define LN_des_ede_cbc "des-ede-cbc" +#define NID_des_ede_cbc 43 + +#define SN_des_ede_cfb64 "DES-EDE-CFB" +#define LN_des_ede_cfb64 "des-ede-cfb" +#define NID_des_ede_cfb64 60 + +#define SN_des_ede3_cfb64 "DES-EDE3-CFB" +#define LN_des_ede3_cfb64 "des-ede3-cfb" +#define NID_des_ede3_cfb64 61 + +#define SN_des_ede_ofb64 "DES-EDE-OFB" +#define LN_des_ede_ofb64 "des-ede-ofb" +#define NID_des_ede_ofb64 62 + +#define SN_des_ede3_ofb64 "DES-EDE3-OFB" +#define LN_des_ede3_ofb64 "des-ede3-ofb" +#define NID_des_ede3_ofb64 63 + +#define SN_desx_cbc "DESX-CBC" +#define LN_desx_cbc "desx-cbc" +#define NID_desx_cbc 80 + +#define SN_sha "SHA" +#define LN_sha "sha" +#define NID_sha 41 +#define OBJ_sha OBJ_algorithm,18L + +#define SN_sha1 "SHA1" +#define LN_sha1 "sha1" +#define NID_sha1 64 +#define OBJ_sha1 OBJ_algorithm,26L + +#define SN_dsaWithSHA1_2 "DSA-SHA1-old" +#define LN_dsaWithSHA1_2 "dsaWithSHA1-old" +#define NID_dsaWithSHA1_2 70 +#define OBJ_dsaWithSHA1_2 OBJ_algorithm,27L + +#define SN_sha1WithRSA "RSA-SHA1-2" +#define LN_sha1WithRSA "sha1WithRSA" +#define NID_sha1WithRSA 115 +#define OBJ_sha1WithRSA OBJ_algorithm,29L + +#define SN_ripemd160 "RIPEMD160" +#define LN_ripemd160 "ripemd160" +#define NID_ripemd160 117 +#define OBJ_ripemd160 1L,3L,36L,3L,2L,1L + +#define SN_ripemd160WithRSA "RSA-RIPEMD160" +#define LN_ripemd160WithRSA "ripemd160WithRSA" +#define NID_ripemd160WithRSA 119 +#define OBJ_ripemd160WithRSA 1L,3L,36L,3L,3L,1L,2L + +#define SN_blake2b512 "BLAKE2b512" +#define LN_blake2b512 "blake2b512" +#define NID_blake2b512 1056 +#define OBJ_blake2b512 1L,3L,6L,1L,4L,1L,1722L,12L,2L,1L,16L + +#define SN_blake2s256 "BLAKE2s256" +#define LN_blake2s256 "blake2s256" +#define NID_blake2s256 1057 +#define OBJ_blake2s256 1L,3L,6L,1L,4L,1L,1722L,12L,2L,2L,8L + +#define SN_sxnet "SXNetID" +#define LN_sxnet "Strong Extranet ID" +#define NID_sxnet 143 +#define OBJ_sxnet 1L,3L,101L,1L,4L,1L + +#define SN_X500 "X500" +#define LN_X500 "directory services (X.500)" +#define NID_X500 11 +#define OBJ_X500 2L,5L + +#define SN_X509 "X509" +#define NID_X509 12 +#define OBJ_X509 OBJ_X500,4L + +#define SN_commonName "CN" +#define LN_commonName "commonName" +#define NID_commonName 13 +#define OBJ_commonName OBJ_X509,3L + +#define SN_surname "SN" +#define LN_surname "surname" +#define NID_surname 100 +#define OBJ_surname OBJ_X509,4L + +#define LN_serialNumber "serialNumber" +#define NID_serialNumber 105 +#define OBJ_serialNumber OBJ_X509,5L + +#define SN_countryName "C" +#define LN_countryName "countryName" +#define NID_countryName 14 +#define OBJ_countryName OBJ_X509,6L + +#define SN_localityName "L" +#define LN_localityName "localityName" +#define NID_localityName 15 +#define OBJ_localityName OBJ_X509,7L + +#define SN_stateOrProvinceName "ST" +#define LN_stateOrProvinceName "stateOrProvinceName" +#define NID_stateOrProvinceName 16 +#define OBJ_stateOrProvinceName OBJ_X509,8L + +#define SN_streetAddress "street" +#define LN_streetAddress "streetAddress" +#define NID_streetAddress 660 +#define OBJ_streetAddress OBJ_X509,9L + +#define SN_organizationName "O" +#define LN_organizationName "organizationName" +#define NID_organizationName 17 +#define OBJ_organizationName OBJ_X509,10L + +#define SN_organizationalUnitName "OU" +#define LN_organizationalUnitName "organizationalUnitName" +#define NID_organizationalUnitName 18 +#define OBJ_organizationalUnitName OBJ_X509,11L + +#define SN_title "title" +#define LN_title "title" +#define NID_title 106 +#define OBJ_title OBJ_X509,12L + +#define LN_description "description" +#define NID_description 107 +#define OBJ_description OBJ_X509,13L + +#define LN_searchGuide "searchGuide" +#define NID_searchGuide 859 +#define OBJ_searchGuide OBJ_X509,14L + +#define LN_businessCategory "businessCategory" +#define NID_businessCategory 860 +#define OBJ_businessCategory OBJ_X509,15L + +#define LN_postalAddress "postalAddress" +#define NID_postalAddress 861 +#define OBJ_postalAddress OBJ_X509,16L + +#define LN_postalCode "postalCode" +#define NID_postalCode 661 +#define OBJ_postalCode OBJ_X509,17L + +#define LN_postOfficeBox "postOfficeBox" +#define NID_postOfficeBox 862 +#define OBJ_postOfficeBox OBJ_X509,18L + +#define LN_physicalDeliveryOfficeName "physicalDeliveryOfficeName" +#define NID_physicalDeliveryOfficeName 863 +#define OBJ_physicalDeliveryOfficeName OBJ_X509,19L + +#define LN_telephoneNumber "telephoneNumber" +#define NID_telephoneNumber 864 +#define OBJ_telephoneNumber OBJ_X509,20L + +#define LN_telexNumber "telexNumber" +#define NID_telexNumber 865 +#define OBJ_telexNumber OBJ_X509,21L + +#define LN_teletexTerminalIdentifier "teletexTerminalIdentifier" +#define NID_teletexTerminalIdentifier 866 +#define OBJ_teletexTerminalIdentifier OBJ_X509,22L + +#define LN_facsimileTelephoneNumber "facsimileTelephoneNumber" +#define NID_facsimileTelephoneNumber 867 +#define OBJ_facsimileTelephoneNumber OBJ_X509,23L + +#define LN_x121Address "x121Address" +#define NID_x121Address 868 +#define OBJ_x121Address OBJ_X509,24L + +#define LN_internationaliSDNNumber "internationaliSDNNumber" +#define NID_internationaliSDNNumber 869 +#define OBJ_internationaliSDNNumber OBJ_X509,25L + +#define LN_registeredAddress "registeredAddress" +#define NID_registeredAddress 870 +#define OBJ_registeredAddress OBJ_X509,26L + +#define LN_destinationIndicator "destinationIndicator" +#define NID_destinationIndicator 871 +#define OBJ_destinationIndicator OBJ_X509,27L + +#define LN_preferredDeliveryMethod "preferredDeliveryMethod" +#define NID_preferredDeliveryMethod 872 +#define OBJ_preferredDeliveryMethod OBJ_X509,28L + +#define LN_presentationAddress "presentationAddress" +#define NID_presentationAddress 873 +#define OBJ_presentationAddress OBJ_X509,29L + +#define LN_supportedApplicationContext "supportedApplicationContext" +#define NID_supportedApplicationContext 874 +#define OBJ_supportedApplicationContext OBJ_X509,30L + +#define SN_member "member" +#define NID_member 875 +#define OBJ_member OBJ_X509,31L + +#define SN_owner "owner" +#define NID_owner 876 +#define OBJ_owner OBJ_X509,32L + +#define LN_roleOccupant "roleOccupant" +#define NID_roleOccupant 877 +#define OBJ_roleOccupant OBJ_X509,33L + +#define SN_seeAlso "seeAlso" +#define NID_seeAlso 878 +#define OBJ_seeAlso OBJ_X509,34L + +#define LN_userPassword "userPassword" +#define NID_userPassword 879 +#define OBJ_userPassword OBJ_X509,35L + +#define LN_userCertificate "userCertificate" +#define NID_userCertificate 880 +#define OBJ_userCertificate OBJ_X509,36L + +#define LN_cACertificate "cACertificate" +#define NID_cACertificate 881 +#define OBJ_cACertificate OBJ_X509,37L + +#define LN_authorityRevocationList "authorityRevocationList" +#define NID_authorityRevocationList 882 +#define OBJ_authorityRevocationList OBJ_X509,38L + +#define LN_certificateRevocationList "certificateRevocationList" +#define NID_certificateRevocationList 883 +#define OBJ_certificateRevocationList OBJ_X509,39L + +#define LN_crossCertificatePair "crossCertificatePair" +#define NID_crossCertificatePair 884 +#define OBJ_crossCertificatePair OBJ_X509,40L + +#define SN_name "name" +#define LN_name "name" +#define NID_name 173 +#define OBJ_name OBJ_X509,41L + +#define SN_givenName "GN" +#define LN_givenName "givenName" +#define NID_givenName 99 +#define OBJ_givenName OBJ_X509,42L + +#define SN_initials "initials" +#define LN_initials "initials" +#define NID_initials 101 +#define OBJ_initials OBJ_X509,43L + +#define LN_generationQualifier "generationQualifier" +#define NID_generationQualifier 509 +#define OBJ_generationQualifier OBJ_X509,44L + +#define LN_x500UniqueIdentifier "x500UniqueIdentifier" +#define NID_x500UniqueIdentifier 503 +#define OBJ_x500UniqueIdentifier OBJ_X509,45L + +#define SN_dnQualifier "dnQualifier" +#define LN_dnQualifier "dnQualifier" +#define NID_dnQualifier 174 +#define OBJ_dnQualifier OBJ_X509,46L + +#define LN_enhancedSearchGuide "enhancedSearchGuide" +#define NID_enhancedSearchGuide 885 +#define OBJ_enhancedSearchGuide OBJ_X509,47L + +#define LN_protocolInformation "protocolInformation" +#define NID_protocolInformation 886 +#define OBJ_protocolInformation OBJ_X509,48L + +#define LN_distinguishedName "distinguishedName" +#define NID_distinguishedName 887 +#define OBJ_distinguishedName OBJ_X509,49L + +#define LN_uniqueMember "uniqueMember" +#define NID_uniqueMember 888 +#define OBJ_uniqueMember OBJ_X509,50L + +#define LN_houseIdentifier "houseIdentifier" +#define NID_houseIdentifier 889 +#define OBJ_houseIdentifier OBJ_X509,51L + +#define LN_supportedAlgorithms "supportedAlgorithms" +#define NID_supportedAlgorithms 890 +#define OBJ_supportedAlgorithms OBJ_X509,52L + +#define LN_deltaRevocationList "deltaRevocationList" +#define NID_deltaRevocationList 891 +#define OBJ_deltaRevocationList OBJ_X509,53L + +#define SN_dmdName "dmdName" +#define NID_dmdName 892 +#define OBJ_dmdName OBJ_X509,54L + +#define LN_pseudonym "pseudonym" +#define NID_pseudonym 510 +#define OBJ_pseudonym OBJ_X509,65L + +#define SN_role "role" +#define LN_role "role" +#define NID_role 400 +#define OBJ_role OBJ_X509,72L + +#define LN_organizationIdentifier "organizationIdentifier" +#define NID_organizationIdentifier 1089 +#define OBJ_organizationIdentifier OBJ_X509,97L + +#define SN_countryCode3c "c3" +#define LN_countryCode3c "countryCode3c" +#define NID_countryCode3c 1090 +#define OBJ_countryCode3c OBJ_X509,98L + +#define SN_countryCode3n "n3" +#define LN_countryCode3n "countryCode3n" +#define NID_countryCode3n 1091 +#define OBJ_countryCode3n OBJ_X509,99L + +#define LN_dnsName "dnsName" +#define NID_dnsName 1092 +#define OBJ_dnsName OBJ_X509,100L + +#define SN_X500algorithms "X500algorithms" +#define LN_X500algorithms "directory services - algorithms" +#define NID_X500algorithms 378 +#define OBJ_X500algorithms OBJ_X500,8L + +#define SN_rsa "RSA" +#define LN_rsa "rsa" +#define NID_rsa 19 +#define OBJ_rsa OBJ_X500algorithms,1L,1L + +#define SN_mdc2WithRSA "RSA-MDC2" +#define LN_mdc2WithRSA "mdc2WithRSA" +#define NID_mdc2WithRSA 96 +#define OBJ_mdc2WithRSA OBJ_X500algorithms,3L,100L + +#define SN_mdc2 "MDC2" +#define LN_mdc2 "mdc2" +#define NID_mdc2 95 +#define OBJ_mdc2 OBJ_X500algorithms,3L,101L + +#define SN_id_ce "id-ce" +#define NID_id_ce 81 +#define OBJ_id_ce OBJ_X500,29L + +#define SN_subject_directory_attributes "subjectDirectoryAttributes" +#define LN_subject_directory_attributes "X509v3 Subject Directory Attributes" +#define NID_subject_directory_attributes 769 +#define OBJ_subject_directory_attributes OBJ_id_ce,9L + +#define SN_subject_key_identifier "subjectKeyIdentifier" +#define LN_subject_key_identifier "X509v3 Subject Key Identifier" +#define NID_subject_key_identifier 82 +#define OBJ_subject_key_identifier OBJ_id_ce,14L + +#define SN_key_usage "keyUsage" +#define LN_key_usage "X509v3 Key Usage" +#define NID_key_usage 83 +#define OBJ_key_usage OBJ_id_ce,15L + +#define SN_private_key_usage_period "privateKeyUsagePeriod" +#define LN_private_key_usage_period "X509v3 Private Key Usage Period" +#define NID_private_key_usage_period 84 +#define OBJ_private_key_usage_period OBJ_id_ce,16L + +#define SN_subject_alt_name "subjectAltName" +#define LN_subject_alt_name "X509v3 Subject Alternative Name" +#define NID_subject_alt_name 85 +#define OBJ_subject_alt_name OBJ_id_ce,17L + +#define SN_issuer_alt_name "issuerAltName" +#define LN_issuer_alt_name "X509v3 Issuer Alternative Name" +#define NID_issuer_alt_name 86 +#define OBJ_issuer_alt_name OBJ_id_ce,18L + +#define SN_basic_constraints "basicConstraints" +#define LN_basic_constraints "X509v3 Basic Constraints" +#define NID_basic_constraints 87 +#define OBJ_basic_constraints OBJ_id_ce,19L + +#define SN_crl_number "crlNumber" +#define LN_crl_number "X509v3 CRL Number" +#define NID_crl_number 88 +#define OBJ_crl_number OBJ_id_ce,20L + +#define SN_crl_reason "CRLReason" +#define LN_crl_reason "X509v3 CRL Reason Code" +#define NID_crl_reason 141 +#define OBJ_crl_reason OBJ_id_ce,21L + +#define SN_invalidity_date "invalidityDate" +#define LN_invalidity_date "Invalidity Date" +#define NID_invalidity_date 142 +#define OBJ_invalidity_date OBJ_id_ce,24L + +#define SN_delta_crl "deltaCRL" +#define LN_delta_crl "X509v3 Delta CRL Indicator" +#define NID_delta_crl 140 +#define OBJ_delta_crl OBJ_id_ce,27L + +#define SN_issuing_distribution_point "issuingDistributionPoint" +#define LN_issuing_distribution_point "X509v3 Issuing Distribution Point" +#define NID_issuing_distribution_point 770 +#define OBJ_issuing_distribution_point OBJ_id_ce,28L + +#define SN_certificate_issuer "certificateIssuer" +#define LN_certificate_issuer "X509v3 Certificate Issuer" +#define NID_certificate_issuer 771 +#define OBJ_certificate_issuer OBJ_id_ce,29L + +#define SN_name_constraints "nameConstraints" +#define LN_name_constraints "X509v3 Name Constraints" +#define NID_name_constraints 666 +#define OBJ_name_constraints OBJ_id_ce,30L + +#define SN_crl_distribution_points "crlDistributionPoints" +#define LN_crl_distribution_points "X509v3 CRL Distribution Points" +#define NID_crl_distribution_points 103 +#define OBJ_crl_distribution_points OBJ_id_ce,31L + +#define SN_certificate_policies "certificatePolicies" +#define LN_certificate_policies "X509v3 Certificate Policies" +#define NID_certificate_policies 89 +#define OBJ_certificate_policies OBJ_id_ce,32L + +#define SN_any_policy "anyPolicy" +#define LN_any_policy "X509v3 Any Policy" +#define NID_any_policy 746 +#define OBJ_any_policy OBJ_certificate_policies,0L + +#define SN_policy_mappings "policyMappings" +#define LN_policy_mappings "X509v3 Policy Mappings" +#define NID_policy_mappings 747 +#define OBJ_policy_mappings OBJ_id_ce,33L + +#define SN_authority_key_identifier "authorityKeyIdentifier" +#define LN_authority_key_identifier "X509v3 Authority Key Identifier" +#define NID_authority_key_identifier 90 +#define OBJ_authority_key_identifier OBJ_id_ce,35L + +#define SN_policy_constraints "policyConstraints" +#define LN_policy_constraints "X509v3 Policy Constraints" +#define NID_policy_constraints 401 +#define OBJ_policy_constraints OBJ_id_ce,36L + +#define SN_ext_key_usage "extendedKeyUsage" +#define LN_ext_key_usage "X509v3 Extended Key Usage" +#define NID_ext_key_usage 126 +#define OBJ_ext_key_usage OBJ_id_ce,37L + +#define SN_freshest_crl "freshestCRL" +#define LN_freshest_crl "X509v3 Freshest CRL" +#define NID_freshest_crl 857 +#define OBJ_freshest_crl OBJ_id_ce,46L + +#define SN_inhibit_any_policy "inhibitAnyPolicy" +#define LN_inhibit_any_policy "X509v3 Inhibit Any Policy" +#define NID_inhibit_any_policy 748 +#define OBJ_inhibit_any_policy OBJ_id_ce,54L + +#define SN_target_information "targetInformation" +#define LN_target_information "X509v3 AC Targeting" +#define NID_target_information 402 +#define OBJ_target_information OBJ_id_ce,55L + +#define SN_no_rev_avail "noRevAvail" +#define LN_no_rev_avail "X509v3 No Revocation Available" +#define NID_no_rev_avail 403 +#define OBJ_no_rev_avail OBJ_id_ce,56L + +#define SN_anyExtendedKeyUsage "anyExtendedKeyUsage" +#define LN_anyExtendedKeyUsage "Any Extended Key Usage" +#define NID_anyExtendedKeyUsage 910 +#define OBJ_anyExtendedKeyUsage OBJ_ext_key_usage,0L + +#define SN_netscape "Netscape" +#define LN_netscape "Netscape Communications Corp." +#define NID_netscape 57 +#define OBJ_netscape 2L,16L,840L,1L,113730L + +#define SN_netscape_cert_extension "nsCertExt" +#define LN_netscape_cert_extension "Netscape Certificate Extension" +#define NID_netscape_cert_extension 58 +#define OBJ_netscape_cert_extension OBJ_netscape,1L + +#define SN_netscape_data_type "nsDataType" +#define LN_netscape_data_type "Netscape Data Type" +#define NID_netscape_data_type 59 +#define OBJ_netscape_data_type OBJ_netscape,2L + +#define SN_netscape_cert_type "nsCertType" +#define LN_netscape_cert_type "Netscape Cert Type" +#define NID_netscape_cert_type 71 +#define OBJ_netscape_cert_type OBJ_netscape_cert_extension,1L + +#define SN_netscape_base_url "nsBaseUrl" +#define LN_netscape_base_url "Netscape Base Url" +#define NID_netscape_base_url 72 +#define OBJ_netscape_base_url OBJ_netscape_cert_extension,2L + +#define SN_netscape_revocation_url "nsRevocationUrl" +#define LN_netscape_revocation_url "Netscape Revocation Url" +#define NID_netscape_revocation_url 73 +#define OBJ_netscape_revocation_url OBJ_netscape_cert_extension,3L + +#define SN_netscape_ca_revocation_url "nsCaRevocationUrl" +#define LN_netscape_ca_revocation_url "Netscape CA Revocation Url" +#define NID_netscape_ca_revocation_url 74 +#define OBJ_netscape_ca_revocation_url OBJ_netscape_cert_extension,4L + +#define SN_netscape_renewal_url "nsRenewalUrl" +#define LN_netscape_renewal_url "Netscape Renewal Url" +#define NID_netscape_renewal_url 75 +#define OBJ_netscape_renewal_url OBJ_netscape_cert_extension,7L + +#define SN_netscape_ca_policy_url "nsCaPolicyUrl" +#define LN_netscape_ca_policy_url "Netscape CA Policy Url" +#define NID_netscape_ca_policy_url 76 +#define OBJ_netscape_ca_policy_url OBJ_netscape_cert_extension,8L + +#define SN_netscape_ssl_server_name "nsSslServerName" +#define LN_netscape_ssl_server_name "Netscape SSL Server Name" +#define NID_netscape_ssl_server_name 77 +#define OBJ_netscape_ssl_server_name OBJ_netscape_cert_extension,12L + +#define SN_netscape_comment "nsComment" +#define LN_netscape_comment "Netscape Comment" +#define NID_netscape_comment 78 +#define OBJ_netscape_comment OBJ_netscape_cert_extension,13L + +#define SN_netscape_cert_sequence "nsCertSequence" +#define LN_netscape_cert_sequence "Netscape Certificate Sequence" +#define NID_netscape_cert_sequence 79 +#define OBJ_netscape_cert_sequence OBJ_netscape_data_type,5L + +#define SN_ns_sgc "nsSGC" +#define LN_ns_sgc "Netscape Server Gated Crypto" +#define NID_ns_sgc 139 +#define OBJ_ns_sgc OBJ_netscape,4L,1L + +#define SN_org "ORG" +#define LN_org "org" +#define NID_org 379 +#define OBJ_org OBJ_iso,3L + +#define SN_dod "DOD" +#define LN_dod "dod" +#define NID_dod 380 +#define OBJ_dod OBJ_org,6L + +#define SN_iana "IANA" +#define LN_iana "iana" +#define NID_iana 381 +#define OBJ_iana OBJ_dod,1L + +#define OBJ_internet OBJ_iana + +#define SN_Directory "directory" +#define LN_Directory "Directory" +#define NID_Directory 382 +#define OBJ_Directory OBJ_internet,1L + +#define SN_Management "mgmt" +#define LN_Management "Management" +#define NID_Management 383 +#define OBJ_Management OBJ_internet,2L + +#define SN_Experimental "experimental" +#define LN_Experimental "Experimental" +#define NID_Experimental 384 +#define OBJ_Experimental OBJ_internet,3L + +#define SN_Private "private" +#define LN_Private "Private" +#define NID_Private 385 +#define OBJ_Private OBJ_internet,4L + +#define SN_Security "security" +#define LN_Security "Security" +#define NID_Security 386 +#define OBJ_Security OBJ_internet,5L + +#define SN_SNMPv2 "snmpv2" +#define LN_SNMPv2 "SNMPv2" +#define NID_SNMPv2 387 +#define OBJ_SNMPv2 OBJ_internet,6L + +#define LN_Mail "Mail" +#define NID_Mail 388 +#define OBJ_Mail OBJ_internet,7L + +#define SN_Enterprises "enterprises" +#define LN_Enterprises "Enterprises" +#define NID_Enterprises 389 +#define OBJ_Enterprises OBJ_Private,1L + +#define SN_dcObject "dcobject" +#define LN_dcObject "dcObject" +#define NID_dcObject 390 +#define OBJ_dcObject OBJ_Enterprises,1466L,344L + +#define SN_mime_mhs "mime-mhs" +#define LN_mime_mhs "MIME MHS" +#define NID_mime_mhs 504 +#define OBJ_mime_mhs OBJ_Mail,1L + +#define SN_mime_mhs_headings "mime-mhs-headings" +#define LN_mime_mhs_headings "mime-mhs-headings" +#define NID_mime_mhs_headings 505 +#define OBJ_mime_mhs_headings OBJ_mime_mhs,1L + +#define SN_mime_mhs_bodies "mime-mhs-bodies" +#define LN_mime_mhs_bodies "mime-mhs-bodies" +#define NID_mime_mhs_bodies 506 +#define OBJ_mime_mhs_bodies OBJ_mime_mhs,2L + +#define SN_id_hex_partial_message "id-hex-partial-message" +#define LN_id_hex_partial_message "id-hex-partial-message" +#define NID_id_hex_partial_message 507 +#define OBJ_id_hex_partial_message OBJ_mime_mhs_headings,1L + +#define SN_id_hex_multipart_message "id-hex-multipart-message" +#define LN_id_hex_multipart_message "id-hex-multipart-message" +#define NID_id_hex_multipart_message 508 +#define OBJ_id_hex_multipart_message OBJ_mime_mhs_headings,2L + +#define SN_zlib_compression "ZLIB" +#define LN_zlib_compression "zlib compression" +#define NID_zlib_compression 125 +#define OBJ_zlib_compression OBJ_id_smime_alg,8L + +#define OBJ_csor 2L,16L,840L,1L,101L,3L + +#define OBJ_nistAlgorithms OBJ_csor,4L + +#define OBJ_aes OBJ_nistAlgorithms,1L + +#define SN_aes_128_ecb "AES-128-ECB" +#define LN_aes_128_ecb "aes-128-ecb" +#define NID_aes_128_ecb 418 +#define OBJ_aes_128_ecb OBJ_aes,1L + +#define SN_aes_128_cbc "AES-128-CBC" +#define LN_aes_128_cbc "aes-128-cbc" +#define NID_aes_128_cbc 419 +#define OBJ_aes_128_cbc OBJ_aes,2L + +#define SN_aes_128_ofb128 "AES-128-OFB" +#define LN_aes_128_ofb128 "aes-128-ofb" +#define NID_aes_128_ofb128 420 +#define OBJ_aes_128_ofb128 OBJ_aes,3L + +#define SN_aes_128_cfb128 "AES-128-CFB" +#define LN_aes_128_cfb128 "aes-128-cfb" +#define NID_aes_128_cfb128 421 +#define OBJ_aes_128_cfb128 OBJ_aes,4L + +#define SN_id_aes128_wrap "id-aes128-wrap" +#define NID_id_aes128_wrap 788 +#define OBJ_id_aes128_wrap OBJ_aes,5L + +#define SN_aes_128_gcm "id-aes128-GCM" +#define LN_aes_128_gcm "aes-128-gcm" +#define NID_aes_128_gcm 895 +#define OBJ_aes_128_gcm OBJ_aes,6L + +#define SN_aes_128_ccm "id-aes128-CCM" +#define LN_aes_128_ccm "aes-128-ccm" +#define NID_aes_128_ccm 896 +#define OBJ_aes_128_ccm OBJ_aes,7L + +#define SN_id_aes128_wrap_pad "id-aes128-wrap-pad" +#define NID_id_aes128_wrap_pad 897 +#define OBJ_id_aes128_wrap_pad OBJ_aes,8L + +#define SN_aes_192_ecb "AES-192-ECB" +#define LN_aes_192_ecb "aes-192-ecb" +#define NID_aes_192_ecb 422 +#define OBJ_aes_192_ecb OBJ_aes,21L + +#define SN_aes_192_cbc "AES-192-CBC" +#define LN_aes_192_cbc "aes-192-cbc" +#define NID_aes_192_cbc 423 +#define OBJ_aes_192_cbc OBJ_aes,22L + +#define SN_aes_192_ofb128 "AES-192-OFB" +#define LN_aes_192_ofb128 "aes-192-ofb" +#define NID_aes_192_ofb128 424 +#define OBJ_aes_192_ofb128 OBJ_aes,23L + +#define SN_aes_192_cfb128 "AES-192-CFB" +#define LN_aes_192_cfb128 "aes-192-cfb" +#define NID_aes_192_cfb128 425 +#define OBJ_aes_192_cfb128 OBJ_aes,24L + +#define SN_id_aes192_wrap "id-aes192-wrap" +#define NID_id_aes192_wrap 789 +#define OBJ_id_aes192_wrap OBJ_aes,25L + +#define SN_aes_192_gcm "id-aes192-GCM" +#define LN_aes_192_gcm "aes-192-gcm" +#define NID_aes_192_gcm 898 +#define OBJ_aes_192_gcm OBJ_aes,26L + +#define SN_aes_192_ccm "id-aes192-CCM" +#define LN_aes_192_ccm "aes-192-ccm" +#define NID_aes_192_ccm 899 +#define OBJ_aes_192_ccm OBJ_aes,27L + +#define SN_id_aes192_wrap_pad "id-aes192-wrap-pad" +#define NID_id_aes192_wrap_pad 900 +#define OBJ_id_aes192_wrap_pad OBJ_aes,28L + +#define SN_aes_256_ecb "AES-256-ECB" +#define LN_aes_256_ecb "aes-256-ecb" +#define NID_aes_256_ecb 426 +#define OBJ_aes_256_ecb OBJ_aes,41L + +#define SN_aes_256_cbc "AES-256-CBC" +#define LN_aes_256_cbc "aes-256-cbc" +#define NID_aes_256_cbc 427 +#define OBJ_aes_256_cbc OBJ_aes,42L + +#define SN_aes_256_ofb128 "AES-256-OFB" +#define LN_aes_256_ofb128 "aes-256-ofb" +#define NID_aes_256_ofb128 428 +#define OBJ_aes_256_ofb128 OBJ_aes,43L + +#define SN_aes_256_cfb128 "AES-256-CFB" +#define LN_aes_256_cfb128 "aes-256-cfb" +#define NID_aes_256_cfb128 429 +#define OBJ_aes_256_cfb128 OBJ_aes,44L + +#define SN_id_aes256_wrap "id-aes256-wrap" +#define NID_id_aes256_wrap 790 +#define OBJ_id_aes256_wrap OBJ_aes,45L + +#define SN_aes_256_gcm "id-aes256-GCM" +#define LN_aes_256_gcm "aes-256-gcm" +#define NID_aes_256_gcm 901 +#define OBJ_aes_256_gcm OBJ_aes,46L + +#define SN_aes_256_ccm "id-aes256-CCM" +#define LN_aes_256_ccm "aes-256-ccm" +#define NID_aes_256_ccm 902 +#define OBJ_aes_256_ccm OBJ_aes,47L + +#define SN_id_aes256_wrap_pad "id-aes256-wrap-pad" +#define NID_id_aes256_wrap_pad 903 +#define OBJ_id_aes256_wrap_pad OBJ_aes,48L + +#define SN_aes_128_xts "AES-128-XTS" +#define LN_aes_128_xts "aes-128-xts" +#define NID_aes_128_xts 913 +#define OBJ_aes_128_xts OBJ_ieee_siswg,0L,1L,1L + +#define SN_aes_256_xts "AES-256-XTS" +#define LN_aes_256_xts "aes-256-xts" +#define NID_aes_256_xts 914 +#define OBJ_aes_256_xts OBJ_ieee_siswg,0L,1L,2L + +#define SN_aes_128_cfb1 "AES-128-CFB1" +#define LN_aes_128_cfb1 "aes-128-cfb1" +#define NID_aes_128_cfb1 650 + +#define SN_aes_192_cfb1 "AES-192-CFB1" +#define LN_aes_192_cfb1 "aes-192-cfb1" +#define NID_aes_192_cfb1 651 + +#define SN_aes_256_cfb1 "AES-256-CFB1" +#define LN_aes_256_cfb1 "aes-256-cfb1" +#define NID_aes_256_cfb1 652 + +#define SN_aes_128_cfb8 "AES-128-CFB8" +#define LN_aes_128_cfb8 "aes-128-cfb8" +#define NID_aes_128_cfb8 653 + +#define SN_aes_192_cfb8 "AES-192-CFB8" +#define LN_aes_192_cfb8 "aes-192-cfb8" +#define NID_aes_192_cfb8 654 + +#define SN_aes_256_cfb8 "AES-256-CFB8" +#define LN_aes_256_cfb8 "aes-256-cfb8" +#define NID_aes_256_cfb8 655 + +#define SN_aes_128_ctr "AES-128-CTR" +#define LN_aes_128_ctr "aes-128-ctr" +#define NID_aes_128_ctr 904 + +#define SN_aes_192_ctr "AES-192-CTR" +#define LN_aes_192_ctr "aes-192-ctr" +#define NID_aes_192_ctr 905 + +#define SN_aes_256_ctr "AES-256-CTR" +#define LN_aes_256_ctr "aes-256-ctr" +#define NID_aes_256_ctr 906 + +#define SN_aes_128_ocb "AES-128-OCB" +#define LN_aes_128_ocb "aes-128-ocb" +#define NID_aes_128_ocb 958 + +#define SN_aes_192_ocb "AES-192-OCB" +#define LN_aes_192_ocb "aes-192-ocb" +#define NID_aes_192_ocb 959 + +#define SN_aes_256_ocb "AES-256-OCB" +#define LN_aes_256_ocb "aes-256-ocb" +#define NID_aes_256_ocb 960 + +#define SN_des_cfb1 "DES-CFB1" +#define LN_des_cfb1 "des-cfb1" +#define NID_des_cfb1 656 + +#define SN_des_cfb8 "DES-CFB8" +#define LN_des_cfb8 "des-cfb8" +#define NID_des_cfb8 657 + +#define SN_des_ede3_cfb1 "DES-EDE3-CFB1" +#define LN_des_ede3_cfb1 "des-ede3-cfb1" +#define NID_des_ede3_cfb1 658 + +#define SN_des_ede3_cfb8 "DES-EDE3-CFB8" +#define LN_des_ede3_cfb8 "des-ede3-cfb8" +#define NID_des_ede3_cfb8 659 + +#define OBJ_nist_hashalgs OBJ_nistAlgorithms,2L + +#define SN_sha256 "SHA256" +#define LN_sha256 "sha256" +#define NID_sha256 672 +#define OBJ_sha256 OBJ_nist_hashalgs,1L + +#define SN_sha384 "SHA384" +#define LN_sha384 "sha384" +#define NID_sha384 673 +#define OBJ_sha384 OBJ_nist_hashalgs,2L + +#define SN_sha512 "SHA512" +#define LN_sha512 "sha512" +#define NID_sha512 674 +#define OBJ_sha512 OBJ_nist_hashalgs,3L + +#define SN_sha224 "SHA224" +#define LN_sha224 "sha224" +#define NID_sha224 675 +#define OBJ_sha224 OBJ_nist_hashalgs,4L + +#define SN_sha512_224 "SHA512-224" +#define LN_sha512_224 "sha512-224" +#define NID_sha512_224 1094 +#define OBJ_sha512_224 OBJ_nist_hashalgs,5L + +#define SN_sha512_256 "SHA512-256" +#define LN_sha512_256 "sha512-256" +#define NID_sha512_256 1095 +#define OBJ_sha512_256 OBJ_nist_hashalgs,6L + +#define SN_sha3_224 "SHA3-224" +#define LN_sha3_224 "sha3-224" +#define NID_sha3_224 1096 +#define OBJ_sha3_224 OBJ_nist_hashalgs,7L + +#define SN_sha3_256 "SHA3-256" +#define LN_sha3_256 "sha3-256" +#define NID_sha3_256 1097 +#define OBJ_sha3_256 OBJ_nist_hashalgs,8L + +#define SN_sha3_384 "SHA3-384" +#define LN_sha3_384 "sha3-384" +#define NID_sha3_384 1098 +#define OBJ_sha3_384 OBJ_nist_hashalgs,9L + +#define SN_sha3_512 "SHA3-512" +#define LN_sha3_512 "sha3-512" +#define NID_sha3_512 1099 +#define OBJ_sha3_512 OBJ_nist_hashalgs,10L + +#define SN_shake128 "SHAKE128" +#define LN_shake128 "shake128" +#define NID_shake128 1100 +#define OBJ_shake128 OBJ_nist_hashalgs,11L + +#define SN_shake256 "SHAKE256" +#define LN_shake256 "shake256" +#define NID_shake256 1101 +#define OBJ_shake256 OBJ_nist_hashalgs,12L + +#define SN_hmac_sha3_224 "id-hmacWithSHA3-224" +#define LN_hmac_sha3_224 "hmac-sha3-224" +#define NID_hmac_sha3_224 1102 +#define OBJ_hmac_sha3_224 OBJ_nist_hashalgs,13L + +#define SN_hmac_sha3_256 "id-hmacWithSHA3-256" +#define LN_hmac_sha3_256 "hmac-sha3-256" +#define NID_hmac_sha3_256 1103 +#define OBJ_hmac_sha3_256 OBJ_nist_hashalgs,14L + +#define SN_hmac_sha3_384 "id-hmacWithSHA3-384" +#define LN_hmac_sha3_384 "hmac-sha3-384" +#define NID_hmac_sha3_384 1104 +#define OBJ_hmac_sha3_384 OBJ_nist_hashalgs,15L + +#define SN_hmac_sha3_512 "id-hmacWithSHA3-512" +#define LN_hmac_sha3_512 "hmac-sha3-512" +#define NID_hmac_sha3_512 1105 +#define OBJ_hmac_sha3_512 OBJ_nist_hashalgs,16L + +#define OBJ_dsa_with_sha2 OBJ_nistAlgorithms,3L + +#define SN_dsa_with_SHA224 "dsa_with_SHA224" +#define NID_dsa_with_SHA224 802 +#define OBJ_dsa_with_SHA224 OBJ_dsa_with_sha2,1L + +#define SN_dsa_with_SHA256 "dsa_with_SHA256" +#define NID_dsa_with_SHA256 803 +#define OBJ_dsa_with_SHA256 OBJ_dsa_with_sha2,2L + +#define OBJ_sigAlgs OBJ_nistAlgorithms,3L + +#define SN_dsa_with_SHA384 "id-dsa-with-sha384" +#define LN_dsa_with_SHA384 "dsa_with_SHA384" +#define NID_dsa_with_SHA384 1106 +#define OBJ_dsa_with_SHA384 OBJ_sigAlgs,3L + +#define SN_dsa_with_SHA512 "id-dsa-with-sha512" +#define LN_dsa_with_SHA512 "dsa_with_SHA512" +#define NID_dsa_with_SHA512 1107 +#define OBJ_dsa_with_SHA512 OBJ_sigAlgs,4L + +#define SN_dsa_with_SHA3_224 "id-dsa-with-sha3-224" +#define LN_dsa_with_SHA3_224 "dsa_with_SHA3-224" +#define NID_dsa_with_SHA3_224 1108 +#define OBJ_dsa_with_SHA3_224 OBJ_sigAlgs,5L + +#define SN_dsa_with_SHA3_256 "id-dsa-with-sha3-256" +#define LN_dsa_with_SHA3_256 "dsa_with_SHA3-256" +#define NID_dsa_with_SHA3_256 1109 +#define OBJ_dsa_with_SHA3_256 OBJ_sigAlgs,6L + +#define SN_dsa_with_SHA3_384 "id-dsa-with-sha3-384" +#define LN_dsa_with_SHA3_384 "dsa_with_SHA3-384" +#define NID_dsa_with_SHA3_384 1110 +#define OBJ_dsa_with_SHA3_384 OBJ_sigAlgs,7L + +#define SN_dsa_with_SHA3_512 "id-dsa-with-sha3-512" +#define LN_dsa_with_SHA3_512 "dsa_with_SHA3-512" +#define NID_dsa_with_SHA3_512 1111 +#define OBJ_dsa_with_SHA3_512 OBJ_sigAlgs,8L + +#define SN_ecdsa_with_SHA3_224 "id-ecdsa-with-sha3-224" +#define LN_ecdsa_with_SHA3_224 "ecdsa_with_SHA3-224" +#define NID_ecdsa_with_SHA3_224 1112 +#define OBJ_ecdsa_with_SHA3_224 OBJ_sigAlgs,9L + +#define SN_ecdsa_with_SHA3_256 "id-ecdsa-with-sha3-256" +#define LN_ecdsa_with_SHA3_256 "ecdsa_with_SHA3-256" +#define NID_ecdsa_with_SHA3_256 1113 +#define OBJ_ecdsa_with_SHA3_256 OBJ_sigAlgs,10L + +#define SN_ecdsa_with_SHA3_384 "id-ecdsa-with-sha3-384" +#define LN_ecdsa_with_SHA3_384 "ecdsa_with_SHA3-384" +#define NID_ecdsa_with_SHA3_384 1114 +#define OBJ_ecdsa_with_SHA3_384 OBJ_sigAlgs,11L + +#define SN_ecdsa_with_SHA3_512 "id-ecdsa-with-sha3-512" +#define LN_ecdsa_with_SHA3_512 "ecdsa_with_SHA3-512" +#define NID_ecdsa_with_SHA3_512 1115 +#define OBJ_ecdsa_with_SHA3_512 OBJ_sigAlgs,12L + +#define SN_RSA_SHA3_224 "id-rsassa-pkcs1-v1_5-with-sha3-224" +#define LN_RSA_SHA3_224 "RSA-SHA3-224" +#define NID_RSA_SHA3_224 1116 +#define OBJ_RSA_SHA3_224 OBJ_sigAlgs,13L + +#define SN_RSA_SHA3_256 "id-rsassa-pkcs1-v1_5-with-sha3-256" +#define LN_RSA_SHA3_256 "RSA-SHA3-256" +#define NID_RSA_SHA3_256 1117 +#define OBJ_RSA_SHA3_256 OBJ_sigAlgs,14L + +#define SN_RSA_SHA3_384 "id-rsassa-pkcs1-v1_5-with-sha3-384" +#define LN_RSA_SHA3_384 "RSA-SHA3-384" +#define NID_RSA_SHA3_384 1118 +#define OBJ_RSA_SHA3_384 OBJ_sigAlgs,15L + +#define SN_RSA_SHA3_512 "id-rsassa-pkcs1-v1_5-with-sha3-512" +#define LN_RSA_SHA3_512 "RSA-SHA3-512" +#define NID_RSA_SHA3_512 1119 +#define OBJ_RSA_SHA3_512 OBJ_sigAlgs,16L + +#define SN_hold_instruction_code "holdInstructionCode" +#define LN_hold_instruction_code "Hold Instruction Code" +#define NID_hold_instruction_code 430 +#define OBJ_hold_instruction_code OBJ_id_ce,23L + +#define OBJ_holdInstruction OBJ_X9_57,2L + +#define SN_hold_instruction_none "holdInstructionNone" +#define LN_hold_instruction_none "Hold Instruction None" +#define NID_hold_instruction_none 431 +#define OBJ_hold_instruction_none OBJ_holdInstruction,1L + +#define SN_hold_instruction_call_issuer "holdInstructionCallIssuer" +#define LN_hold_instruction_call_issuer "Hold Instruction Call Issuer" +#define NID_hold_instruction_call_issuer 432 +#define OBJ_hold_instruction_call_issuer OBJ_holdInstruction,2L + +#define SN_hold_instruction_reject "holdInstructionReject" +#define LN_hold_instruction_reject "Hold Instruction Reject" +#define NID_hold_instruction_reject 433 +#define OBJ_hold_instruction_reject OBJ_holdInstruction,3L + +#define SN_data "data" +#define NID_data 434 +#define OBJ_data OBJ_itu_t,9L + +#define SN_pss "pss" +#define NID_pss 435 +#define OBJ_pss OBJ_data,2342L + +#define SN_ucl "ucl" +#define NID_ucl 436 +#define OBJ_ucl OBJ_pss,19200300L + +#define SN_pilot "pilot" +#define NID_pilot 437 +#define OBJ_pilot OBJ_ucl,100L + +#define LN_pilotAttributeType "pilotAttributeType" +#define NID_pilotAttributeType 438 +#define OBJ_pilotAttributeType OBJ_pilot,1L + +#define LN_pilotAttributeSyntax "pilotAttributeSyntax" +#define NID_pilotAttributeSyntax 439 +#define OBJ_pilotAttributeSyntax OBJ_pilot,3L + +#define LN_pilotObjectClass "pilotObjectClass" +#define NID_pilotObjectClass 440 +#define OBJ_pilotObjectClass OBJ_pilot,4L + +#define LN_pilotGroups "pilotGroups" +#define NID_pilotGroups 441 +#define OBJ_pilotGroups OBJ_pilot,10L + +#define LN_iA5StringSyntax "iA5StringSyntax" +#define NID_iA5StringSyntax 442 +#define OBJ_iA5StringSyntax OBJ_pilotAttributeSyntax,4L + +#define LN_caseIgnoreIA5StringSyntax "caseIgnoreIA5StringSyntax" +#define NID_caseIgnoreIA5StringSyntax 443 +#define OBJ_caseIgnoreIA5StringSyntax OBJ_pilotAttributeSyntax,5L + +#define LN_pilotObject "pilotObject" +#define NID_pilotObject 444 +#define OBJ_pilotObject OBJ_pilotObjectClass,3L + +#define LN_pilotPerson "pilotPerson" +#define NID_pilotPerson 445 +#define OBJ_pilotPerson OBJ_pilotObjectClass,4L + +#define SN_account "account" +#define NID_account 446 +#define OBJ_account OBJ_pilotObjectClass,5L + +#define SN_document "document" +#define NID_document 447 +#define OBJ_document OBJ_pilotObjectClass,6L + +#define SN_room "room" +#define NID_room 448 +#define OBJ_room OBJ_pilotObjectClass,7L + +#define LN_documentSeries "documentSeries" +#define NID_documentSeries 449 +#define OBJ_documentSeries OBJ_pilotObjectClass,9L + +#define SN_Domain "domain" +#define LN_Domain "Domain" +#define NID_Domain 392 +#define OBJ_Domain OBJ_pilotObjectClass,13L + +#define LN_rFC822localPart "rFC822localPart" +#define NID_rFC822localPart 450 +#define OBJ_rFC822localPart OBJ_pilotObjectClass,14L + +#define LN_dNSDomain "dNSDomain" +#define NID_dNSDomain 451 +#define OBJ_dNSDomain OBJ_pilotObjectClass,15L + +#define LN_domainRelatedObject "domainRelatedObject" +#define NID_domainRelatedObject 452 +#define OBJ_domainRelatedObject OBJ_pilotObjectClass,17L + +#define LN_friendlyCountry "friendlyCountry" +#define NID_friendlyCountry 453 +#define OBJ_friendlyCountry OBJ_pilotObjectClass,18L + +#define LN_simpleSecurityObject "simpleSecurityObject" +#define NID_simpleSecurityObject 454 +#define OBJ_simpleSecurityObject OBJ_pilotObjectClass,19L + +#define LN_pilotOrganization "pilotOrganization" +#define NID_pilotOrganization 455 +#define OBJ_pilotOrganization OBJ_pilotObjectClass,20L + +#define LN_pilotDSA "pilotDSA" +#define NID_pilotDSA 456 +#define OBJ_pilotDSA OBJ_pilotObjectClass,21L + +#define LN_qualityLabelledData "qualityLabelledData" +#define NID_qualityLabelledData 457 +#define OBJ_qualityLabelledData OBJ_pilotObjectClass,22L + +#define SN_userId "UID" +#define LN_userId "userId" +#define NID_userId 458 +#define OBJ_userId OBJ_pilotAttributeType,1L + +#define LN_textEncodedORAddress "textEncodedORAddress" +#define NID_textEncodedORAddress 459 +#define OBJ_textEncodedORAddress OBJ_pilotAttributeType,2L + +#define SN_rfc822Mailbox "mail" +#define LN_rfc822Mailbox "rfc822Mailbox" +#define NID_rfc822Mailbox 460 +#define OBJ_rfc822Mailbox OBJ_pilotAttributeType,3L + +#define SN_info "info" +#define NID_info 461 +#define OBJ_info OBJ_pilotAttributeType,4L + +#define LN_favouriteDrink "favouriteDrink" +#define NID_favouriteDrink 462 +#define OBJ_favouriteDrink OBJ_pilotAttributeType,5L + +#define LN_roomNumber "roomNumber" +#define NID_roomNumber 463 +#define OBJ_roomNumber OBJ_pilotAttributeType,6L + +#define SN_photo "photo" +#define NID_photo 464 +#define OBJ_photo OBJ_pilotAttributeType,7L + +#define LN_userClass "userClass" +#define NID_userClass 465 +#define OBJ_userClass OBJ_pilotAttributeType,8L + +#define SN_host "host" +#define NID_host 466 +#define OBJ_host OBJ_pilotAttributeType,9L + +#define SN_manager "manager" +#define NID_manager 467 +#define OBJ_manager OBJ_pilotAttributeType,10L + +#define LN_documentIdentifier "documentIdentifier" +#define NID_documentIdentifier 468 +#define OBJ_documentIdentifier OBJ_pilotAttributeType,11L + +#define LN_documentTitle "documentTitle" +#define NID_documentTitle 469 +#define OBJ_documentTitle OBJ_pilotAttributeType,12L + +#define LN_documentVersion "documentVersion" +#define NID_documentVersion 470 +#define OBJ_documentVersion OBJ_pilotAttributeType,13L + +#define LN_documentAuthor "documentAuthor" +#define NID_documentAuthor 471 +#define OBJ_documentAuthor OBJ_pilotAttributeType,14L + +#define LN_documentLocation "documentLocation" +#define NID_documentLocation 472 +#define OBJ_documentLocation OBJ_pilotAttributeType,15L + +#define LN_homeTelephoneNumber "homeTelephoneNumber" +#define NID_homeTelephoneNumber 473 +#define OBJ_homeTelephoneNumber OBJ_pilotAttributeType,20L + +#define SN_secretary "secretary" +#define NID_secretary 474 +#define OBJ_secretary OBJ_pilotAttributeType,21L + +#define LN_otherMailbox "otherMailbox" +#define NID_otherMailbox 475 +#define OBJ_otherMailbox OBJ_pilotAttributeType,22L + +#define LN_lastModifiedTime "lastModifiedTime" +#define NID_lastModifiedTime 476 +#define OBJ_lastModifiedTime OBJ_pilotAttributeType,23L + +#define LN_lastModifiedBy "lastModifiedBy" +#define NID_lastModifiedBy 477 +#define OBJ_lastModifiedBy OBJ_pilotAttributeType,24L + +#define SN_domainComponent "DC" +#define LN_domainComponent "domainComponent" +#define NID_domainComponent 391 +#define OBJ_domainComponent OBJ_pilotAttributeType,25L + +#define LN_aRecord "aRecord" +#define NID_aRecord 478 +#define OBJ_aRecord OBJ_pilotAttributeType,26L + +#define LN_pilotAttributeType27 "pilotAttributeType27" +#define NID_pilotAttributeType27 479 +#define OBJ_pilotAttributeType27 OBJ_pilotAttributeType,27L + +#define LN_mXRecord "mXRecord" +#define NID_mXRecord 480 +#define OBJ_mXRecord OBJ_pilotAttributeType,28L + +#define LN_nSRecord "nSRecord" +#define NID_nSRecord 481 +#define OBJ_nSRecord OBJ_pilotAttributeType,29L + +#define LN_sOARecord "sOARecord" +#define NID_sOARecord 482 +#define OBJ_sOARecord OBJ_pilotAttributeType,30L + +#define LN_cNAMERecord "cNAMERecord" +#define NID_cNAMERecord 483 +#define OBJ_cNAMERecord OBJ_pilotAttributeType,31L + +#define LN_associatedDomain "associatedDomain" +#define NID_associatedDomain 484 +#define OBJ_associatedDomain OBJ_pilotAttributeType,37L + +#define LN_associatedName "associatedName" +#define NID_associatedName 485 +#define OBJ_associatedName OBJ_pilotAttributeType,38L + +#define LN_homePostalAddress "homePostalAddress" +#define NID_homePostalAddress 486 +#define OBJ_homePostalAddress OBJ_pilotAttributeType,39L + +#define LN_personalTitle "personalTitle" +#define NID_personalTitle 487 +#define OBJ_personalTitle OBJ_pilotAttributeType,40L + +#define LN_mobileTelephoneNumber "mobileTelephoneNumber" +#define NID_mobileTelephoneNumber 488 +#define OBJ_mobileTelephoneNumber OBJ_pilotAttributeType,41L + +#define LN_pagerTelephoneNumber "pagerTelephoneNumber" +#define NID_pagerTelephoneNumber 489 +#define OBJ_pagerTelephoneNumber OBJ_pilotAttributeType,42L + +#define LN_friendlyCountryName "friendlyCountryName" +#define NID_friendlyCountryName 490 +#define OBJ_friendlyCountryName OBJ_pilotAttributeType,43L + +#define SN_uniqueIdentifier "uid" +#define LN_uniqueIdentifier "uniqueIdentifier" +#define NID_uniqueIdentifier 102 +#define OBJ_uniqueIdentifier OBJ_pilotAttributeType,44L + +#define LN_organizationalStatus "organizationalStatus" +#define NID_organizationalStatus 491 +#define OBJ_organizationalStatus OBJ_pilotAttributeType,45L + +#define LN_janetMailbox "janetMailbox" +#define NID_janetMailbox 492 +#define OBJ_janetMailbox OBJ_pilotAttributeType,46L + +#define LN_mailPreferenceOption "mailPreferenceOption" +#define NID_mailPreferenceOption 493 +#define OBJ_mailPreferenceOption OBJ_pilotAttributeType,47L + +#define LN_buildingName "buildingName" +#define NID_buildingName 494 +#define OBJ_buildingName OBJ_pilotAttributeType,48L + +#define LN_dSAQuality "dSAQuality" +#define NID_dSAQuality 495 +#define OBJ_dSAQuality OBJ_pilotAttributeType,49L + +#define LN_singleLevelQuality "singleLevelQuality" +#define NID_singleLevelQuality 496 +#define OBJ_singleLevelQuality OBJ_pilotAttributeType,50L + +#define LN_subtreeMinimumQuality "subtreeMinimumQuality" +#define NID_subtreeMinimumQuality 497 +#define OBJ_subtreeMinimumQuality OBJ_pilotAttributeType,51L + +#define LN_subtreeMaximumQuality "subtreeMaximumQuality" +#define NID_subtreeMaximumQuality 498 +#define OBJ_subtreeMaximumQuality OBJ_pilotAttributeType,52L + +#define LN_personalSignature "personalSignature" +#define NID_personalSignature 499 +#define OBJ_personalSignature OBJ_pilotAttributeType,53L + +#define LN_dITRedirect "dITRedirect" +#define NID_dITRedirect 500 +#define OBJ_dITRedirect OBJ_pilotAttributeType,54L + +#define SN_audio "audio" +#define NID_audio 501 +#define OBJ_audio OBJ_pilotAttributeType,55L + +#define LN_documentPublisher "documentPublisher" +#define NID_documentPublisher 502 +#define OBJ_documentPublisher OBJ_pilotAttributeType,56L + +#define SN_id_set "id-set" +#define LN_id_set "Secure Electronic Transactions" +#define NID_id_set 512 +#define OBJ_id_set OBJ_international_organizations,42L + +#define SN_set_ctype "set-ctype" +#define LN_set_ctype "content types" +#define NID_set_ctype 513 +#define OBJ_set_ctype OBJ_id_set,0L + +#define SN_set_msgExt "set-msgExt" +#define LN_set_msgExt "message extensions" +#define NID_set_msgExt 514 +#define OBJ_set_msgExt OBJ_id_set,1L + +#define SN_set_attr "set-attr" +#define NID_set_attr 515 +#define OBJ_set_attr OBJ_id_set,3L + +#define SN_set_policy "set-policy" +#define NID_set_policy 516 +#define OBJ_set_policy OBJ_id_set,5L + +#define SN_set_certExt "set-certExt" +#define LN_set_certExt "certificate extensions" +#define NID_set_certExt 517 +#define OBJ_set_certExt OBJ_id_set,7L + +#define SN_set_brand "set-brand" +#define NID_set_brand 518 +#define OBJ_set_brand OBJ_id_set,8L + +#define SN_setct_PANData "setct-PANData" +#define NID_setct_PANData 519 +#define OBJ_setct_PANData OBJ_set_ctype,0L + +#define SN_setct_PANToken "setct-PANToken" +#define NID_setct_PANToken 520 +#define OBJ_setct_PANToken OBJ_set_ctype,1L + +#define SN_setct_PANOnly "setct-PANOnly" +#define NID_setct_PANOnly 521 +#define OBJ_setct_PANOnly OBJ_set_ctype,2L + +#define SN_setct_OIData "setct-OIData" +#define NID_setct_OIData 522 +#define OBJ_setct_OIData OBJ_set_ctype,3L + +#define SN_setct_PI "setct-PI" +#define NID_setct_PI 523 +#define OBJ_setct_PI OBJ_set_ctype,4L + +#define SN_setct_PIData "setct-PIData" +#define NID_setct_PIData 524 +#define OBJ_setct_PIData OBJ_set_ctype,5L + +#define SN_setct_PIDataUnsigned "setct-PIDataUnsigned" +#define NID_setct_PIDataUnsigned 525 +#define OBJ_setct_PIDataUnsigned OBJ_set_ctype,6L + +#define SN_setct_HODInput "setct-HODInput" +#define NID_setct_HODInput 526 +#define OBJ_setct_HODInput OBJ_set_ctype,7L + +#define SN_setct_AuthResBaggage "setct-AuthResBaggage" +#define NID_setct_AuthResBaggage 527 +#define OBJ_setct_AuthResBaggage OBJ_set_ctype,8L + +#define SN_setct_AuthRevReqBaggage "setct-AuthRevReqBaggage" +#define NID_setct_AuthRevReqBaggage 528 +#define OBJ_setct_AuthRevReqBaggage OBJ_set_ctype,9L + +#define SN_setct_AuthRevResBaggage "setct-AuthRevResBaggage" +#define NID_setct_AuthRevResBaggage 529 +#define OBJ_setct_AuthRevResBaggage OBJ_set_ctype,10L + +#define SN_setct_CapTokenSeq "setct-CapTokenSeq" +#define NID_setct_CapTokenSeq 530 +#define OBJ_setct_CapTokenSeq OBJ_set_ctype,11L + +#define SN_setct_PInitResData "setct-PInitResData" +#define NID_setct_PInitResData 531 +#define OBJ_setct_PInitResData OBJ_set_ctype,12L + +#define SN_setct_PI_TBS "setct-PI-TBS" +#define NID_setct_PI_TBS 532 +#define OBJ_setct_PI_TBS OBJ_set_ctype,13L + +#define SN_setct_PResData "setct-PResData" +#define NID_setct_PResData 533 +#define OBJ_setct_PResData OBJ_set_ctype,14L + +#define SN_setct_AuthReqTBS "setct-AuthReqTBS" +#define NID_setct_AuthReqTBS 534 +#define OBJ_setct_AuthReqTBS OBJ_set_ctype,16L + +#define SN_setct_AuthResTBS "setct-AuthResTBS" +#define NID_setct_AuthResTBS 535 +#define OBJ_setct_AuthResTBS OBJ_set_ctype,17L + +#define SN_setct_AuthResTBSX "setct-AuthResTBSX" +#define NID_setct_AuthResTBSX 536 +#define OBJ_setct_AuthResTBSX OBJ_set_ctype,18L + +#define SN_setct_AuthTokenTBS "setct-AuthTokenTBS" +#define NID_setct_AuthTokenTBS 537 +#define OBJ_setct_AuthTokenTBS OBJ_set_ctype,19L + +#define SN_setct_CapTokenData "setct-CapTokenData" +#define NID_setct_CapTokenData 538 +#define OBJ_setct_CapTokenData OBJ_set_ctype,20L + +#define SN_setct_CapTokenTBS "setct-CapTokenTBS" +#define NID_setct_CapTokenTBS 539 +#define OBJ_setct_CapTokenTBS OBJ_set_ctype,21L + +#define SN_setct_AcqCardCodeMsg "setct-AcqCardCodeMsg" +#define NID_setct_AcqCardCodeMsg 540 +#define OBJ_setct_AcqCardCodeMsg OBJ_set_ctype,22L + +#define SN_setct_AuthRevReqTBS "setct-AuthRevReqTBS" +#define NID_setct_AuthRevReqTBS 541 +#define OBJ_setct_AuthRevReqTBS OBJ_set_ctype,23L + +#define SN_setct_AuthRevResData "setct-AuthRevResData" +#define NID_setct_AuthRevResData 542 +#define OBJ_setct_AuthRevResData OBJ_set_ctype,24L + +#define SN_setct_AuthRevResTBS "setct-AuthRevResTBS" +#define NID_setct_AuthRevResTBS 543 +#define OBJ_setct_AuthRevResTBS OBJ_set_ctype,25L + +#define SN_setct_CapReqTBS "setct-CapReqTBS" +#define NID_setct_CapReqTBS 544 +#define OBJ_setct_CapReqTBS OBJ_set_ctype,26L + +#define SN_setct_CapReqTBSX "setct-CapReqTBSX" +#define NID_setct_CapReqTBSX 545 +#define OBJ_setct_CapReqTBSX OBJ_set_ctype,27L + +#define SN_setct_CapResData "setct-CapResData" +#define NID_setct_CapResData 546 +#define OBJ_setct_CapResData OBJ_set_ctype,28L + +#define SN_setct_CapRevReqTBS "setct-CapRevReqTBS" +#define NID_setct_CapRevReqTBS 547 +#define OBJ_setct_CapRevReqTBS OBJ_set_ctype,29L + +#define SN_setct_CapRevReqTBSX "setct-CapRevReqTBSX" +#define NID_setct_CapRevReqTBSX 548 +#define OBJ_setct_CapRevReqTBSX OBJ_set_ctype,30L + +#define SN_setct_CapRevResData "setct-CapRevResData" +#define NID_setct_CapRevResData 549 +#define OBJ_setct_CapRevResData OBJ_set_ctype,31L + +#define SN_setct_CredReqTBS "setct-CredReqTBS" +#define NID_setct_CredReqTBS 550 +#define OBJ_setct_CredReqTBS OBJ_set_ctype,32L + +#define SN_setct_CredReqTBSX "setct-CredReqTBSX" +#define NID_setct_CredReqTBSX 551 +#define OBJ_setct_CredReqTBSX OBJ_set_ctype,33L + +#define SN_setct_CredResData "setct-CredResData" +#define NID_setct_CredResData 552 +#define OBJ_setct_CredResData OBJ_set_ctype,34L + +#define SN_setct_CredRevReqTBS "setct-CredRevReqTBS" +#define NID_setct_CredRevReqTBS 553 +#define OBJ_setct_CredRevReqTBS OBJ_set_ctype,35L + +#define SN_setct_CredRevReqTBSX "setct-CredRevReqTBSX" +#define NID_setct_CredRevReqTBSX 554 +#define OBJ_setct_CredRevReqTBSX OBJ_set_ctype,36L + +#define SN_setct_CredRevResData "setct-CredRevResData" +#define NID_setct_CredRevResData 555 +#define OBJ_setct_CredRevResData OBJ_set_ctype,37L + +#define SN_setct_PCertReqData "setct-PCertReqData" +#define NID_setct_PCertReqData 556 +#define OBJ_setct_PCertReqData OBJ_set_ctype,38L + +#define SN_setct_PCertResTBS "setct-PCertResTBS" +#define NID_setct_PCertResTBS 557 +#define OBJ_setct_PCertResTBS OBJ_set_ctype,39L + +#define SN_setct_BatchAdminReqData "setct-BatchAdminReqData" +#define NID_setct_BatchAdminReqData 558 +#define OBJ_setct_BatchAdminReqData OBJ_set_ctype,40L + +#define SN_setct_BatchAdminResData "setct-BatchAdminResData" +#define NID_setct_BatchAdminResData 559 +#define OBJ_setct_BatchAdminResData OBJ_set_ctype,41L + +#define SN_setct_CardCInitResTBS "setct-CardCInitResTBS" +#define NID_setct_CardCInitResTBS 560 +#define OBJ_setct_CardCInitResTBS OBJ_set_ctype,42L + +#define SN_setct_MeAqCInitResTBS "setct-MeAqCInitResTBS" +#define NID_setct_MeAqCInitResTBS 561 +#define OBJ_setct_MeAqCInitResTBS OBJ_set_ctype,43L + +#define SN_setct_RegFormResTBS "setct-RegFormResTBS" +#define NID_setct_RegFormResTBS 562 +#define OBJ_setct_RegFormResTBS OBJ_set_ctype,44L + +#define SN_setct_CertReqData "setct-CertReqData" +#define NID_setct_CertReqData 563 +#define OBJ_setct_CertReqData OBJ_set_ctype,45L + +#define SN_setct_CertReqTBS "setct-CertReqTBS" +#define NID_setct_CertReqTBS 564 +#define OBJ_setct_CertReqTBS OBJ_set_ctype,46L + +#define SN_setct_CertResData "setct-CertResData" +#define NID_setct_CertResData 565 +#define OBJ_setct_CertResData OBJ_set_ctype,47L + +#define SN_setct_CertInqReqTBS "setct-CertInqReqTBS" +#define NID_setct_CertInqReqTBS 566 +#define OBJ_setct_CertInqReqTBS OBJ_set_ctype,48L + +#define SN_setct_ErrorTBS "setct-ErrorTBS" +#define NID_setct_ErrorTBS 567 +#define OBJ_setct_ErrorTBS OBJ_set_ctype,49L + +#define SN_setct_PIDualSignedTBE "setct-PIDualSignedTBE" +#define NID_setct_PIDualSignedTBE 568 +#define OBJ_setct_PIDualSignedTBE OBJ_set_ctype,50L + +#define SN_setct_PIUnsignedTBE "setct-PIUnsignedTBE" +#define NID_setct_PIUnsignedTBE 569 +#define OBJ_setct_PIUnsignedTBE OBJ_set_ctype,51L + +#define SN_setct_AuthReqTBE "setct-AuthReqTBE" +#define NID_setct_AuthReqTBE 570 +#define OBJ_setct_AuthReqTBE OBJ_set_ctype,52L + +#define SN_setct_AuthResTBE "setct-AuthResTBE" +#define NID_setct_AuthResTBE 571 +#define OBJ_setct_AuthResTBE OBJ_set_ctype,53L + +#define SN_setct_AuthResTBEX "setct-AuthResTBEX" +#define NID_setct_AuthResTBEX 572 +#define OBJ_setct_AuthResTBEX OBJ_set_ctype,54L + +#define SN_setct_AuthTokenTBE "setct-AuthTokenTBE" +#define NID_setct_AuthTokenTBE 573 +#define OBJ_setct_AuthTokenTBE OBJ_set_ctype,55L + +#define SN_setct_CapTokenTBE "setct-CapTokenTBE" +#define NID_setct_CapTokenTBE 574 +#define OBJ_setct_CapTokenTBE OBJ_set_ctype,56L + +#define SN_setct_CapTokenTBEX "setct-CapTokenTBEX" +#define NID_setct_CapTokenTBEX 575 +#define OBJ_setct_CapTokenTBEX OBJ_set_ctype,57L + +#define SN_setct_AcqCardCodeMsgTBE "setct-AcqCardCodeMsgTBE" +#define NID_setct_AcqCardCodeMsgTBE 576 +#define OBJ_setct_AcqCardCodeMsgTBE OBJ_set_ctype,58L + +#define SN_setct_AuthRevReqTBE "setct-AuthRevReqTBE" +#define NID_setct_AuthRevReqTBE 577 +#define OBJ_setct_AuthRevReqTBE OBJ_set_ctype,59L + +#define SN_setct_AuthRevResTBE "setct-AuthRevResTBE" +#define NID_setct_AuthRevResTBE 578 +#define OBJ_setct_AuthRevResTBE OBJ_set_ctype,60L + +#define SN_setct_AuthRevResTBEB "setct-AuthRevResTBEB" +#define NID_setct_AuthRevResTBEB 579 +#define OBJ_setct_AuthRevResTBEB OBJ_set_ctype,61L + +#define SN_setct_CapReqTBE "setct-CapReqTBE" +#define NID_setct_CapReqTBE 580 +#define OBJ_setct_CapReqTBE OBJ_set_ctype,62L + +#define SN_setct_CapReqTBEX "setct-CapReqTBEX" +#define NID_setct_CapReqTBEX 581 +#define OBJ_setct_CapReqTBEX OBJ_set_ctype,63L + +#define SN_setct_CapResTBE "setct-CapResTBE" +#define NID_setct_CapResTBE 582 +#define OBJ_setct_CapResTBE OBJ_set_ctype,64L + +#define SN_setct_CapRevReqTBE "setct-CapRevReqTBE" +#define NID_setct_CapRevReqTBE 583 +#define OBJ_setct_CapRevReqTBE OBJ_set_ctype,65L + +#define SN_setct_CapRevReqTBEX "setct-CapRevReqTBEX" +#define NID_setct_CapRevReqTBEX 584 +#define OBJ_setct_CapRevReqTBEX OBJ_set_ctype,66L + +#define SN_setct_CapRevResTBE "setct-CapRevResTBE" +#define NID_setct_CapRevResTBE 585 +#define OBJ_setct_CapRevResTBE OBJ_set_ctype,67L + +#define SN_setct_CredReqTBE "setct-CredReqTBE" +#define NID_setct_CredReqTBE 586 +#define OBJ_setct_CredReqTBE OBJ_set_ctype,68L + +#define SN_setct_CredReqTBEX "setct-CredReqTBEX" +#define NID_setct_CredReqTBEX 587 +#define OBJ_setct_CredReqTBEX OBJ_set_ctype,69L + +#define SN_setct_CredResTBE "setct-CredResTBE" +#define NID_setct_CredResTBE 588 +#define OBJ_setct_CredResTBE OBJ_set_ctype,70L + +#define SN_setct_CredRevReqTBE "setct-CredRevReqTBE" +#define NID_setct_CredRevReqTBE 589 +#define OBJ_setct_CredRevReqTBE OBJ_set_ctype,71L + +#define SN_setct_CredRevReqTBEX "setct-CredRevReqTBEX" +#define NID_setct_CredRevReqTBEX 590 +#define OBJ_setct_CredRevReqTBEX OBJ_set_ctype,72L + +#define SN_setct_CredRevResTBE "setct-CredRevResTBE" +#define NID_setct_CredRevResTBE 591 +#define OBJ_setct_CredRevResTBE OBJ_set_ctype,73L + +#define SN_setct_BatchAdminReqTBE "setct-BatchAdminReqTBE" +#define NID_setct_BatchAdminReqTBE 592 +#define OBJ_setct_BatchAdminReqTBE OBJ_set_ctype,74L + +#define SN_setct_BatchAdminResTBE "setct-BatchAdminResTBE" +#define NID_setct_BatchAdminResTBE 593 +#define OBJ_setct_BatchAdminResTBE OBJ_set_ctype,75L + +#define SN_setct_RegFormReqTBE "setct-RegFormReqTBE" +#define NID_setct_RegFormReqTBE 594 +#define OBJ_setct_RegFormReqTBE OBJ_set_ctype,76L + +#define SN_setct_CertReqTBE "setct-CertReqTBE" +#define NID_setct_CertReqTBE 595 +#define OBJ_setct_CertReqTBE OBJ_set_ctype,77L + +#define SN_setct_CertReqTBEX "setct-CertReqTBEX" +#define NID_setct_CertReqTBEX 596 +#define OBJ_setct_CertReqTBEX OBJ_set_ctype,78L + +#define SN_setct_CertResTBE "setct-CertResTBE" +#define NID_setct_CertResTBE 597 +#define OBJ_setct_CertResTBE OBJ_set_ctype,79L + +#define SN_setct_CRLNotificationTBS "setct-CRLNotificationTBS" +#define NID_setct_CRLNotificationTBS 598 +#define OBJ_setct_CRLNotificationTBS OBJ_set_ctype,80L + +#define SN_setct_CRLNotificationResTBS "setct-CRLNotificationResTBS" +#define NID_setct_CRLNotificationResTBS 599 +#define OBJ_setct_CRLNotificationResTBS OBJ_set_ctype,81L + +#define SN_setct_BCIDistributionTBS "setct-BCIDistributionTBS" +#define NID_setct_BCIDistributionTBS 600 +#define OBJ_setct_BCIDistributionTBS OBJ_set_ctype,82L + +#define SN_setext_genCrypt "setext-genCrypt" +#define LN_setext_genCrypt "generic cryptogram" +#define NID_setext_genCrypt 601 +#define OBJ_setext_genCrypt OBJ_set_msgExt,1L + +#define SN_setext_miAuth "setext-miAuth" +#define LN_setext_miAuth "merchant initiated auth" +#define NID_setext_miAuth 602 +#define OBJ_setext_miAuth OBJ_set_msgExt,3L + +#define SN_setext_pinSecure "setext-pinSecure" +#define NID_setext_pinSecure 603 +#define OBJ_setext_pinSecure OBJ_set_msgExt,4L + +#define SN_setext_pinAny "setext-pinAny" +#define NID_setext_pinAny 604 +#define OBJ_setext_pinAny OBJ_set_msgExt,5L + +#define SN_setext_track2 "setext-track2" +#define NID_setext_track2 605 +#define OBJ_setext_track2 OBJ_set_msgExt,7L + +#define SN_setext_cv "setext-cv" +#define LN_setext_cv "additional verification" +#define NID_setext_cv 606 +#define OBJ_setext_cv OBJ_set_msgExt,8L + +#define SN_set_policy_root "set-policy-root" +#define NID_set_policy_root 607 +#define OBJ_set_policy_root OBJ_set_policy,0L + +#define SN_setCext_hashedRoot "setCext-hashedRoot" +#define NID_setCext_hashedRoot 608 +#define OBJ_setCext_hashedRoot OBJ_set_certExt,0L + +#define SN_setCext_certType "setCext-certType" +#define NID_setCext_certType 609 +#define OBJ_setCext_certType OBJ_set_certExt,1L + +#define SN_setCext_merchData "setCext-merchData" +#define NID_setCext_merchData 610 +#define OBJ_setCext_merchData OBJ_set_certExt,2L + +#define SN_setCext_cCertRequired "setCext-cCertRequired" +#define NID_setCext_cCertRequired 611 +#define OBJ_setCext_cCertRequired OBJ_set_certExt,3L + +#define SN_setCext_tunneling "setCext-tunneling" +#define NID_setCext_tunneling 612 +#define OBJ_setCext_tunneling OBJ_set_certExt,4L + +#define SN_setCext_setExt "setCext-setExt" +#define NID_setCext_setExt 613 +#define OBJ_setCext_setExt OBJ_set_certExt,5L + +#define SN_setCext_setQualf "setCext-setQualf" +#define NID_setCext_setQualf 614 +#define OBJ_setCext_setQualf OBJ_set_certExt,6L + +#define SN_setCext_PGWYcapabilities "setCext-PGWYcapabilities" +#define NID_setCext_PGWYcapabilities 615 +#define OBJ_setCext_PGWYcapabilities OBJ_set_certExt,7L + +#define SN_setCext_TokenIdentifier "setCext-TokenIdentifier" +#define NID_setCext_TokenIdentifier 616 +#define OBJ_setCext_TokenIdentifier OBJ_set_certExt,8L + +#define SN_setCext_Track2Data "setCext-Track2Data" +#define NID_setCext_Track2Data 617 +#define OBJ_setCext_Track2Data OBJ_set_certExt,9L + +#define SN_setCext_TokenType "setCext-TokenType" +#define NID_setCext_TokenType 618 +#define OBJ_setCext_TokenType OBJ_set_certExt,10L + +#define SN_setCext_IssuerCapabilities "setCext-IssuerCapabilities" +#define NID_setCext_IssuerCapabilities 619 +#define OBJ_setCext_IssuerCapabilities OBJ_set_certExt,11L + +#define SN_setAttr_Cert "setAttr-Cert" +#define NID_setAttr_Cert 620 +#define OBJ_setAttr_Cert OBJ_set_attr,0L + +#define SN_setAttr_PGWYcap "setAttr-PGWYcap" +#define LN_setAttr_PGWYcap "payment gateway capabilities" +#define NID_setAttr_PGWYcap 621 +#define OBJ_setAttr_PGWYcap OBJ_set_attr,1L + +#define SN_setAttr_TokenType "setAttr-TokenType" +#define NID_setAttr_TokenType 622 +#define OBJ_setAttr_TokenType OBJ_set_attr,2L + +#define SN_setAttr_IssCap "setAttr-IssCap" +#define LN_setAttr_IssCap "issuer capabilities" +#define NID_setAttr_IssCap 623 +#define OBJ_setAttr_IssCap OBJ_set_attr,3L + +#define SN_set_rootKeyThumb "set-rootKeyThumb" +#define NID_set_rootKeyThumb 624 +#define OBJ_set_rootKeyThumb OBJ_setAttr_Cert,0L + +#define SN_set_addPolicy "set-addPolicy" +#define NID_set_addPolicy 625 +#define OBJ_set_addPolicy OBJ_setAttr_Cert,1L + +#define SN_setAttr_Token_EMV "setAttr-Token-EMV" +#define NID_setAttr_Token_EMV 626 +#define OBJ_setAttr_Token_EMV OBJ_setAttr_TokenType,1L + +#define SN_setAttr_Token_B0Prime "setAttr-Token-B0Prime" +#define NID_setAttr_Token_B0Prime 627 +#define OBJ_setAttr_Token_B0Prime OBJ_setAttr_TokenType,2L + +#define SN_setAttr_IssCap_CVM "setAttr-IssCap-CVM" +#define NID_setAttr_IssCap_CVM 628 +#define OBJ_setAttr_IssCap_CVM OBJ_setAttr_IssCap,3L + +#define SN_setAttr_IssCap_T2 "setAttr-IssCap-T2" +#define NID_setAttr_IssCap_T2 629 +#define OBJ_setAttr_IssCap_T2 OBJ_setAttr_IssCap,4L + +#define SN_setAttr_IssCap_Sig "setAttr-IssCap-Sig" +#define NID_setAttr_IssCap_Sig 630 +#define OBJ_setAttr_IssCap_Sig OBJ_setAttr_IssCap,5L + +#define SN_setAttr_GenCryptgrm "setAttr-GenCryptgrm" +#define LN_setAttr_GenCryptgrm "generate cryptogram" +#define NID_setAttr_GenCryptgrm 631 +#define OBJ_setAttr_GenCryptgrm OBJ_setAttr_IssCap_CVM,1L + +#define SN_setAttr_T2Enc "setAttr-T2Enc" +#define LN_setAttr_T2Enc "encrypted track 2" +#define NID_setAttr_T2Enc 632 +#define OBJ_setAttr_T2Enc OBJ_setAttr_IssCap_T2,1L + +#define SN_setAttr_T2cleartxt "setAttr-T2cleartxt" +#define LN_setAttr_T2cleartxt "cleartext track 2" +#define NID_setAttr_T2cleartxt 633 +#define OBJ_setAttr_T2cleartxt OBJ_setAttr_IssCap_T2,2L + +#define SN_setAttr_TokICCsig "setAttr-TokICCsig" +#define LN_setAttr_TokICCsig "ICC or token signature" +#define NID_setAttr_TokICCsig 634 +#define OBJ_setAttr_TokICCsig OBJ_setAttr_IssCap_Sig,1L + +#define SN_setAttr_SecDevSig "setAttr-SecDevSig" +#define LN_setAttr_SecDevSig "secure device signature" +#define NID_setAttr_SecDevSig 635 +#define OBJ_setAttr_SecDevSig OBJ_setAttr_IssCap_Sig,2L + +#define SN_set_brand_IATA_ATA "set-brand-IATA-ATA" +#define NID_set_brand_IATA_ATA 636 +#define OBJ_set_brand_IATA_ATA OBJ_set_brand,1L + +#define SN_set_brand_Diners "set-brand-Diners" +#define NID_set_brand_Diners 637 +#define OBJ_set_brand_Diners OBJ_set_brand,30L + +#define SN_set_brand_AmericanExpress "set-brand-AmericanExpress" +#define NID_set_brand_AmericanExpress 638 +#define OBJ_set_brand_AmericanExpress OBJ_set_brand,34L + +#define SN_set_brand_JCB "set-brand-JCB" +#define NID_set_brand_JCB 639 +#define OBJ_set_brand_JCB OBJ_set_brand,35L + +#define SN_set_brand_Visa "set-brand-Visa" +#define NID_set_brand_Visa 640 +#define OBJ_set_brand_Visa OBJ_set_brand,4L + +#define SN_set_brand_MasterCard "set-brand-MasterCard" +#define NID_set_brand_MasterCard 641 +#define OBJ_set_brand_MasterCard OBJ_set_brand,5L + +#define SN_set_brand_Novus "set-brand-Novus" +#define NID_set_brand_Novus 642 +#define OBJ_set_brand_Novus OBJ_set_brand,6011L + +#define SN_des_cdmf "DES-CDMF" +#define LN_des_cdmf "des-cdmf" +#define NID_des_cdmf 643 +#define OBJ_des_cdmf OBJ_rsadsi,3L,10L + +#define SN_rsaOAEPEncryptionSET "rsaOAEPEncryptionSET" +#define NID_rsaOAEPEncryptionSET 644 +#define OBJ_rsaOAEPEncryptionSET OBJ_rsadsi,1L,1L,6L + +#define SN_ipsec3 "Oakley-EC2N-3" +#define LN_ipsec3 "ipsec3" +#define NID_ipsec3 749 + +#define SN_ipsec4 "Oakley-EC2N-4" +#define LN_ipsec4 "ipsec4" +#define NID_ipsec4 750 + +#define SN_whirlpool "whirlpool" +#define NID_whirlpool 804 +#define OBJ_whirlpool OBJ_iso,0L,10118L,3L,0L,55L + +#define SN_cryptopro "cryptopro" +#define NID_cryptopro 805 +#define OBJ_cryptopro OBJ_member_body,643L,2L,2L + +#define SN_cryptocom "cryptocom" +#define NID_cryptocom 806 +#define OBJ_cryptocom OBJ_member_body,643L,2L,9L + +#define SN_id_tc26 "id-tc26" +#define NID_id_tc26 974 +#define OBJ_id_tc26 OBJ_member_body,643L,7L,1L + +#define SN_id_GostR3411_94_with_GostR3410_2001 "id-GostR3411-94-with-GostR3410-2001" +#define LN_id_GostR3411_94_with_GostR3410_2001 "GOST R 34.11-94 with GOST R 34.10-2001" +#define NID_id_GostR3411_94_with_GostR3410_2001 807 +#define OBJ_id_GostR3411_94_with_GostR3410_2001 OBJ_cryptopro,3L + +#define SN_id_GostR3411_94_with_GostR3410_94 "id-GostR3411-94-with-GostR3410-94" +#define LN_id_GostR3411_94_with_GostR3410_94 "GOST R 34.11-94 with GOST R 34.10-94" +#define NID_id_GostR3411_94_with_GostR3410_94 808 +#define OBJ_id_GostR3411_94_with_GostR3410_94 OBJ_cryptopro,4L + +#define SN_id_GostR3411_94 "md_gost94" +#define LN_id_GostR3411_94 "GOST R 34.11-94" +#define NID_id_GostR3411_94 809 +#define OBJ_id_GostR3411_94 OBJ_cryptopro,9L + +#define SN_id_HMACGostR3411_94 "id-HMACGostR3411-94" +#define LN_id_HMACGostR3411_94 "HMAC GOST 34.11-94" +#define NID_id_HMACGostR3411_94 810 +#define OBJ_id_HMACGostR3411_94 OBJ_cryptopro,10L + +#define SN_id_GostR3410_2001 "gost2001" +#define LN_id_GostR3410_2001 "GOST R 34.10-2001" +#define NID_id_GostR3410_2001 811 +#define OBJ_id_GostR3410_2001 OBJ_cryptopro,19L + +#define SN_id_GostR3410_94 "gost94" +#define LN_id_GostR3410_94 "GOST R 34.10-94" +#define NID_id_GostR3410_94 812 +#define OBJ_id_GostR3410_94 OBJ_cryptopro,20L + +#define SN_id_Gost28147_89 "gost89" +#define LN_id_Gost28147_89 "GOST 28147-89" +#define NID_id_Gost28147_89 813 +#define OBJ_id_Gost28147_89 OBJ_cryptopro,21L + +#define SN_gost89_cnt "gost89-cnt" +#define NID_gost89_cnt 814 + +#define SN_gost89_cnt_12 "gost89-cnt-12" +#define NID_gost89_cnt_12 975 + +#define SN_gost89_cbc "gost89-cbc" +#define NID_gost89_cbc 1009 + +#define SN_gost89_ecb "gost89-ecb" +#define NID_gost89_ecb 1010 + +#define SN_gost89_ctr "gost89-ctr" +#define NID_gost89_ctr 1011 + +#define SN_id_Gost28147_89_MAC "gost-mac" +#define LN_id_Gost28147_89_MAC "GOST 28147-89 MAC" +#define NID_id_Gost28147_89_MAC 815 +#define OBJ_id_Gost28147_89_MAC OBJ_cryptopro,22L + +#define SN_gost_mac_12 "gost-mac-12" +#define NID_gost_mac_12 976 + +#define SN_id_GostR3411_94_prf "prf-gostr3411-94" +#define LN_id_GostR3411_94_prf "GOST R 34.11-94 PRF" +#define NID_id_GostR3411_94_prf 816 +#define OBJ_id_GostR3411_94_prf OBJ_cryptopro,23L + +#define SN_id_GostR3410_2001DH "id-GostR3410-2001DH" +#define LN_id_GostR3410_2001DH "GOST R 34.10-2001 DH" +#define NID_id_GostR3410_2001DH 817 +#define OBJ_id_GostR3410_2001DH OBJ_cryptopro,98L + +#define SN_id_GostR3410_94DH "id-GostR3410-94DH" +#define LN_id_GostR3410_94DH "GOST R 34.10-94 DH" +#define NID_id_GostR3410_94DH 818 +#define OBJ_id_GostR3410_94DH OBJ_cryptopro,99L + +#define SN_id_Gost28147_89_CryptoPro_KeyMeshing "id-Gost28147-89-CryptoPro-KeyMeshing" +#define NID_id_Gost28147_89_CryptoPro_KeyMeshing 819 +#define OBJ_id_Gost28147_89_CryptoPro_KeyMeshing OBJ_cryptopro,14L,1L + +#define SN_id_Gost28147_89_None_KeyMeshing "id-Gost28147-89-None-KeyMeshing" +#define NID_id_Gost28147_89_None_KeyMeshing 820 +#define OBJ_id_Gost28147_89_None_KeyMeshing OBJ_cryptopro,14L,0L + +#define SN_id_GostR3411_94_TestParamSet "id-GostR3411-94-TestParamSet" +#define NID_id_GostR3411_94_TestParamSet 821 +#define OBJ_id_GostR3411_94_TestParamSet OBJ_cryptopro,30L,0L + +#define SN_id_GostR3411_94_CryptoProParamSet "id-GostR3411-94-CryptoProParamSet" +#define NID_id_GostR3411_94_CryptoProParamSet 822 +#define OBJ_id_GostR3411_94_CryptoProParamSet OBJ_cryptopro,30L,1L + +#define SN_id_Gost28147_89_TestParamSet "id-Gost28147-89-TestParamSet" +#define NID_id_Gost28147_89_TestParamSet 823 +#define OBJ_id_Gost28147_89_TestParamSet OBJ_cryptopro,31L,0L + +#define SN_id_Gost28147_89_CryptoPro_A_ParamSet "id-Gost28147-89-CryptoPro-A-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_A_ParamSet 824 +#define OBJ_id_Gost28147_89_CryptoPro_A_ParamSet OBJ_cryptopro,31L,1L + +#define SN_id_Gost28147_89_CryptoPro_B_ParamSet "id-Gost28147-89-CryptoPro-B-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_B_ParamSet 825 +#define OBJ_id_Gost28147_89_CryptoPro_B_ParamSet OBJ_cryptopro,31L,2L + +#define SN_id_Gost28147_89_CryptoPro_C_ParamSet "id-Gost28147-89-CryptoPro-C-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_C_ParamSet 826 +#define OBJ_id_Gost28147_89_CryptoPro_C_ParamSet OBJ_cryptopro,31L,3L + +#define SN_id_Gost28147_89_CryptoPro_D_ParamSet "id-Gost28147-89-CryptoPro-D-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_D_ParamSet 827 +#define OBJ_id_Gost28147_89_CryptoPro_D_ParamSet OBJ_cryptopro,31L,4L + +#define SN_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet "id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet 828 +#define OBJ_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet OBJ_cryptopro,31L,5L + +#define SN_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet "id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet 829 +#define OBJ_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet OBJ_cryptopro,31L,6L + +#define SN_id_Gost28147_89_CryptoPro_RIC_1_ParamSet "id-Gost28147-89-CryptoPro-RIC-1-ParamSet" +#define NID_id_Gost28147_89_CryptoPro_RIC_1_ParamSet 830 +#define OBJ_id_Gost28147_89_CryptoPro_RIC_1_ParamSet OBJ_cryptopro,31L,7L + +#define SN_id_GostR3410_94_TestParamSet "id-GostR3410-94-TestParamSet" +#define NID_id_GostR3410_94_TestParamSet 831 +#define OBJ_id_GostR3410_94_TestParamSet OBJ_cryptopro,32L,0L + +#define SN_id_GostR3410_94_CryptoPro_A_ParamSet "id-GostR3410-94-CryptoPro-A-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_A_ParamSet 832 +#define OBJ_id_GostR3410_94_CryptoPro_A_ParamSet OBJ_cryptopro,32L,2L + +#define SN_id_GostR3410_94_CryptoPro_B_ParamSet "id-GostR3410-94-CryptoPro-B-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_B_ParamSet 833 +#define OBJ_id_GostR3410_94_CryptoPro_B_ParamSet OBJ_cryptopro,32L,3L + +#define SN_id_GostR3410_94_CryptoPro_C_ParamSet "id-GostR3410-94-CryptoPro-C-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_C_ParamSet 834 +#define OBJ_id_GostR3410_94_CryptoPro_C_ParamSet OBJ_cryptopro,32L,4L + +#define SN_id_GostR3410_94_CryptoPro_D_ParamSet "id-GostR3410-94-CryptoPro-D-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_D_ParamSet 835 +#define OBJ_id_GostR3410_94_CryptoPro_D_ParamSet OBJ_cryptopro,32L,5L + +#define SN_id_GostR3410_94_CryptoPro_XchA_ParamSet "id-GostR3410-94-CryptoPro-XchA-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_XchA_ParamSet 836 +#define OBJ_id_GostR3410_94_CryptoPro_XchA_ParamSet OBJ_cryptopro,33L,1L + +#define SN_id_GostR3410_94_CryptoPro_XchB_ParamSet "id-GostR3410-94-CryptoPro-XchB-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_XchB_ParamSet 837 +#define OBJ_id_GostR3410_94_CryptoPro_XchB_ParamSet OBJ_cryptopro,33L,2L + +#define SN_id_GostR3410_94_CryptoPro_XchC_ParamSet "id-GostR3410-94-CryptoPro-XchC-ParamSet" +#define NID_id_GostR3410_94_CryptoPro_XchC_ParamSet 838 +#define OBJ_id_GostR3410_94_CryptoPro_XchC_ParamSet OBJ_cryptopro,33L,3L + +#define SN_id_GostR3410_2001_TestParamSet "id-GostR3410-2001-TestParamSet" +#define NID_id_GostR3410_2001_TestParamSet 839 +#define OBJ_id_GostR3410_2001_TestParamSet OBJ_cryptopro,35L,0L + +#define SN_id_GostR3410_2001_CryptoPro_A_ParamSet "id-GostR3410-2001-CryptoPro-A-ParamSet" +#define NID_id_GostR3410_2001_CryptoPro_A_ParamSet 840 +#define OBJ_id_GostR3410_2001_CryptoPro_A_ParamSet OBJ_cryptopro,35L,1L + +#define SN_id_GostR3410_2001_CryptoPro_B_ParamSet "id-GostR3410-2001-CryptoPro-B-ParamSet" +#define NID_id_GostR3410_2001_CryptoPro_B_ParamSet 841 +#define OBJ_id_GostR3410_2001_CryptoPro_B_ParamSet OBJ_cryptopro,35L,2L + +#define SN_id_GostR3410_2001_CryptoPro_C_ParamSet "id-GostR3410-2001-CryptoPro-C-ParamSet" +#define NID_id_GostR3410_2001_CryptoPro_C_ParamSet 842 +#define OBJ_id_GostR3410_2001_CryptoPro_C_ParamSet OBJ_cryptopro,35L,3L + +#define SN_id_GostR3410_2001_CryptoPro_XchA_ParamSet "id-GostR3410-2001-CryptoPro-XchA-ParamSet" +#define NID_id_GostR3410_2001_CryptoPro_XchA_ParamSet 843 +#define OBJ_id_GostR3410_2001_CryptoPro_XchA_ParamSet OBJ_cryptopro,36L,0L + +#define SN_id_GostR3410_2001_CryptoPro_XchB_ParamSet "id-GostR3410-2001-CryptoPro-XchB-ParamSet" +#define NID_id_GostR3410_2001_CryptoPro_XchB_ParamSet 844 +#define OBJ_id_GostR3410_2001_CryptoPro_XchB_ParamSet OBJ_cryptopro,36L,1L + +#define SN_id_GostR3410_94_a "id-GostR3410-94-a" +#define NID_id_GostR3410_94_a 845 +#define OBJ_id_GostR3410_94_a OBJ_id_GostR3410_94,1L + +#define SN_id_GostR3410_94_aBis "id-GostR3410-94-aBis" +#define NID_id_GostR3410_94_aBis 846 +#define OBJ_id_GostR3410_94_aBis OBJ_id_GostR3410_94,2L + +#define SN_id_GostR3410_94_b "id-GostR3410-94-b" +#define NID_id_GostR3410_94_b 847 +#define OBJ_id_GostR3410_94_b OBJ_id_GostR3410_94,3L + +#define SN_id_GostR3410_94_bBis "id-GostR3410-94-bBis" +#define NID_id_GostR3410_94_bBis 848 +#define OBJ_id_GostR3410_94_bBis OBJ_id_GostR3410_94,4L + +#define SN_id_Gost28147_89_cc "id-Gost28147-89-cc" +#define LN_id_Gost28147_89_cc "GOST 28147-89 Cryptocom ParamSet" +#define NID_id_Gost28147_89_cc 849 +#define OBJ_id_Gost28147_89_cc OBJ_cryptocom,1L,6L,1L + +#define SN_id_GostR3410_94_cc "gost94cc" +#define LN_id_GostR3410_94_cc "GOST 34.10-94 Cryptocom" +#define NID_id_GostR3410_94_cc 850 +#define OBJ_id_GostR3410_94_cc OBJ_cryptocom,1L,5L,3L + +#define SN_id_GostR3410_2001_cc "gost2001cc" +#define LN_id_GostR3410_2001_cc "GOST 34.10-2001 Cryptocom" +#define NID_id_GostR3410_2001_cc 851 +#define OBJ_id_GostR3410_2001_cc OBJ_cryptocom,1L,5L,4L + +#define SN_id_GostR3411_94_with_GostR3410_94_cc "id-GostR3411-94-with-GostR3410-94-cc" +#define LN_id_GostR3411_94_with_GostR3410_94_cc "GOST R 34.11-94 with GOST R 34.10-94 Cryptocom" +#define NID_id_GostR3411_94_with_GostR3410_94_cc 852 +#define OBJ_id_GostR3411_94_with_GostR3410_94_cc OBJ_cryptocom,1L,3L,3L + +#define SN_id_GostR3411_94_with_GostR3410_2001_cc "id-GostR3411-94-with-GostR3410-2001-cc" +#define LN_id_GostR3411_94_with_GostR3410_2001_cc "GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom" +#define NID_id_GostR3411_94_with_GostR3410_2001_cc 853 +#define OBJ_id_GostR3411_94_with_GostR3410_2001_cc OBJ_cryptocom,1L,3L,4L + +#define SN_id_GostR3410_2001_ParamSet_cc "id-GostR3410-2001-ParamSet-cc" +#define LN_id_GostR3410_2001_ParamSet_cc "GOST R 3410-2001 Parameter Set Cryptocom" +#define NID_id_GostR3410_2001_ParamSet_cc 854 +#define OBJ_id_GostR3410_2001_ParamSet_cc OBJ_cryptocom,1L,8L,1L + +#define SN_id_tc26_algorithms "id-tc26-algorithms" +#define NID_id_tc26_algorithms 977 +#define OBJ_id_tc26_algorithms OBJ_id_tc26,1L + +#define SN_id_tc26_sign "id-tc26-sign" +#define NID_id_tc26_sign 978 +#define OBJ_id_tc26_sign OBJ_id_tc26_algorithms,1L + +#define SN_id_GostR3410_2012_256 "gost2012_256" +#define LN_id_GostR3410_2012_256 "GOST R 34.10-2012 with 256 bit modulus" +#define NID_id_GostR3410_2012_256 979 +#define OBJ_id_GostR3410_2012_256 OBJ_id_tc26_sign,1L + +#define SN_id_GostR3410_2012_512 "gost2012_512" +#define LN_id_GostR3410_2012_512 "GOST R 34.10-2012 with 512 bit modulus" +#define NID_id_GostR3410_2012_512 980 +#define OBJ_id_GostR3410_2012_512 OBJ_id_tc26_sign,2L + +#define SN_id_tc26_digest "id-tc26-digest" +#define NID_id_tc26_digest 981 +#define OBJ_id_tc26_digest OBJ_id_tc26_algorithms,2L + +#define SN_id_GostR3411_2012_256 "md_gost12_256" +#define LN_id_GostR3411_2012_256 "GOST R 34.11-2012 with 256 bit hash" +#define NID_id_GostR3411_2012_256 982 +#define OBJ_id_GostR3411_2012_256 OBJ_id_tc26_digest,2L + +#define SN_id_GostR3411_2012_512 "md_gost12_512" +#define LN_id_GostR3411_2012_512 "GOST R 34.11-2012 with 512 bit hash" +#define NID_id_GostR3411_2012_512 983 +#define OBJ_id_GostR3411_2012_512 OBJ_id_tc26_digest,3L + +#define SN_id_tc26_signwithdigest "id-tc26-signwithdigest" +#define NID_id_tc26_signwithdigest 984 +#define OBJ_id_tc26_signwithdigest OBJ_id_tc26_algorithms,3L + +#define SN_id_tc26_signwithdigest_gost3410_2012_256 "id-tc26-signwithdigest-gost3410-2012-256" +#define LN_id_tc26_signwithdigest_gost3410_2012_256 "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" +#define NID_id_tc26_signwithdigest_gost3410_2012_256 985 +#define OBJ_id_tc26_signwithdigest_gost3410_2012_256 OBJ_id_tc26_signwithdigest,2L + +#define SN_id_tc26_signwithdigest_gost3410_2012_512 "id-tc26-signwithdigest-gost3410-2012-512" +#define LN_id_tc26_signwithdigest_gost3410_2012_512 "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" +#define NID_id_tc26_signwithdigest_gost3410_2012_512 986 +#define OBJ_id_tc26_signwithdigest_gost3410_2012_512 OBJ_id_tc26_signwithdigest,3L + +#define SN_id_tc26_mac "id-tc26-mac" +#define NID_id_tc26_mac 987 +#define OBJ_id_tc26_mac OBJ_id_tc26_algorithms,4L + +#define SN_id_tc26_hmac_gost_3411_2012_256 "id-tc26-hmac-gost-3411-2012-256" +#define LN_id_tc26_hmac_gost_3411_2012_256 "HMAC GOST 34.11-2012 256 bit" +#define NID_id_tc26_hmac_gost_3411_2012_256 988 +#define OBJ_id_tc26_hmac_gost_3411_2012_256 OBJ_id_tc26_mac,1L + +#define SN_id_tc26_hmac_gost_3411_2012_512 "id-tc26-hmac-gost-3411-2012-512" +#define LN_id_tc26_hmac_gost_3411_2012_512 "HMAC GOST 34.11-2012 512 bit" +#define NID_id_tc26_hmac_gost_3411_2012_512 989 +#define OBJ_id_tc26_hmac_gost_3411_2012_512 OBJ_id_tc26_mac,2L + +#define SN_id_tc26_cipher "id-tc26-cipher" +#define NID_id_tc26_cipher 990 +#define OBJ_id_tc26_cipher OBJ_id_tc26_algorithms,5L + +#define SN_id_tc26_cipher_gostr3412_2015_magma "id-tc26-cipher-gostr3412-2015-magma" +#define NID_id_tc26_cipher_gostr3412_2015_magma 1173 +#define OBJ_id_tc26_cipher_gostr3412_2015_magma OBJ_id_tc26_cipher,1L + +#define SN_id_tc26_cipher_gostr3412_2015_magma_ctracpkm "id-tc26-cipher-gostr3412-2015-magma-ctracpkm" +#define NID_id_tc26_cipher_gostr3412_2015_magma_ctracpkm 1174 +#define OBJ_id_tc26_cipher_gostr3412_2015_magma_ctracpkm OBJ_id_tc26_cipher_gostr3412_2015_magma,1L + +#define SN_id_tc26_cipher_gostr3412_2015_magma_ctracpkm_omac "id-tc26-cipher-gostr3412-2015-magma-ctracpkm-omac" +#define NID_id_tc26_cipher_gostr3412_2015_magma_ctracpkm_omac 1175 +#define OBJ_id_tc26_cipher_gostr3412_2015_magma_ctracpkm_omac OBJ_id_tc26_cipher_gostr3412_2015_magma,2L + +#define SN_id_tc26_cipher_gostr3412_2015_kuznyechik "id-tc26-cipher-gostr3412-2015-kuznyechik" +#define NID_id_tc26_cipher_gostr3412_2015_kuznyechik 1176 +#define OBJ_id_tc26_cipher_gostr3412_2015_kuznyechik OBJ_id_tc26_cipher,2L + +#define SN_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm "id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm" +#define NID_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm 1177 +#define OBJ_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm OBJ_id_tc26_cipher_gostr3412_2015_kuznyechik,1L + +#define SN_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac "id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm-omac" +#define NID_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac 1178 +#define OBJ_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac OBJ_id_tc26_cipher_gostr3412_2015_kuznyechik,2L + +#define SN_id_tc26_agreement "id-tc26-agreement" +#define NID_id_tc26_agreement 991 +#define OBJ_id_tc26_agreement OBJ_id_tc26_algorithms,6L + +#define SN_id_tc26_agreement_gost_3410_2012_256 "id-tc26-agreement-gost-3410-2012-256" +#define NID_id_tc26_agreement_gost_3410_2012_256 992 +#define OBJ_id_tc26_agreement_gost_3410_2012_256 OBJ_id_tc26_agreement,1L + +#define SN_id_tc26_agreement_gost_3410_2012_512 "id-tc26-agreement-gost-3410-2012-512" +#define NID_id_tc26_agreement_gost_3410_2012_512 993 +#define OBJ_id_tc26_agreement_gost_3410_2012_512 OBJ_id_tc26_agreement,2L + +#define SN_id_tc26_wrap "id-tc26-wrap" +#define NID_id_tc26_wrap 1179 +#define OBJ_id_tc26_wrap OBJ_id_tc26_algorithms,7L + +#define SN_id_tc26_wrap_gostr3412_2015_magma "id-tc26-wrap-gostr3412-2015-magma" +#define NID_id_tc26_wrap_gostr3412_2015_magma 1180 +#define OBJ_id_tc26_wrap_gostr3412_2015_magma OBJ_id_tc26_wrap,1L + +#define SN_id_tc26_wrap_gostr3412_2015_magma_kexp15 "id-tc26-wrap-gostr3412-2015-magma-kexp15" +#define NID_id_tc26_wrap_gostr3412_2015_magma_kexp15 1181 +#define OBJ_id_tc26_wrap_gostr3412_2015_magma_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_magma,1L + +#define SN_id_tc26_wrap_gostr3412_2015_kuznyechik "id-tc26-wrap-gostr3412-2015-kuznyechik" +#define NID_id_tc26_wrap_gostr3412_2015_kuznyechik 1182 +#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik OBJ_id_tc26_wrap,2L + +#define SN_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 "id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15" +#define NID_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 1183 +#define OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik_kexp15 OBJ_id_tc26_wrap_gostr3412_2015_kuznyechik,1L + +#define SN_id_tc26_constants "id-tc26-constants" +#define NID_id_tc26_constants 994 +#define OBJ_id_tc26_constants OBJ_id_tc26,2L + +#define SN_id_tc26_sign_constants "id-tc26-sign-constants" +#define NID_id_tc26_sign_constants 995 +#define OBJ_id_tc26_sign_constants OBJ_id_tc26_constants,1L + +#define SN_id_tc26_gost_3410_2012_256_constants "id-tc26-gost-3410-2012-256-constants" +#define NID_id_tc26_gost_3410_2012_256_constants 1147 +#define OBJ_id_tc26_gost_3410_2012_256_constants OBJ_id_tc26_sign_constants,1L + +#define SN_id_tc26_gost_3410_2012_256_paramSetA "id-tc26-gost-3410-2012-256-paramSetA" +#define LN_id_tc26_gost_3410_2012_256_paramSetA "GOST R 34.10-2012 (256 bit) ParamSet A" +#define NID_id_tc26_gost_3410_2012_256_paramSetA 1148 +#define OBJ_id_tc26_gost_3410_2012_256_paramSetA OBJ_id_tc26_gost_3410_2012_256_constants,1L + +#define SN_id_tc26_gost_3410_2012_256_paramSetB "id-tc26-gost-3410-2012-256-paramSetB" +#define LN_id_tc26_gost_3410_2012_256_paramSetB "GOST R 34.10-2012 (256 bit) ParamSet B" +#define NID_id_tc26_gost_3410_2012_256_paramSetB 1184 +#define OBJ_id_tc26_gost_3410_2012_256_paramSetB OBJ_id_tc26_gost_3410_2012_256_constants,2L + +#define SN_id_tc26_gost_3410_2012_256_paramSetC "id-tc26-gost-3410-2012-256-paramSetC" +#define LN_id_tc26_gost_3410_2012_256_paramSetC "GOST R 34.10-2012 (256 bit) ParamSet C" +#define NID_id_tc26_gost_3410_2012_256_paramSetC 1185 +#define OBJ_id_tc26_gost_3410_2012_256_paramSetC OBJ_id_tc26_gost_3410_2012_256_constants,3L + +#define SN_id_tc26_gost_3410_2012_256_paramSetD "id-tc26-gost-3410-2012-256-paramSetD" +#define LN_id_tc26_gost_3410_2012_256_paramSetD "GOST R 34.10-2012 (256 bit) ParamSet D" +#define NID_id_tc26_gost_3410_2012_256_paramSetD 1186 +#define OBJ_id_tc26_gost_3410_2012_256_paramSetD OBJ_id_tc26_gost_3410_2012_256_constants,4L + +#define SN_id_tc26_gost_3410_2012_512_constants "id-tc26-gost-3410-2012-512-constants" +#define NID_id_tc26_gost_3410_2012_512_constants 996 +#define OBJ_id_tc26_gost_3410_2012_512_constants OBJ_id_tc26_sign_constants,2L + +#define SN_id_tc26_gost_3410_2012_512_paramSetTest "id-tc26-gost-3410-2012-512-paramSetTest" +#define LN_id_tc26_gost_3410_2012_512_paramSetTest "GOST R 34.10-2012 (512 bit) testing parameter set" +#define NID_id_tc26_gost_3410_2012_512_paramSetTest 997 +#define OBJ_id_tc26_gost_3410_2012_512_paramSetTest OBJ_id_tc26_gost_3410_2012_512_constants,0L + +#define SN_id_tc26_gost_3410_2012_512_paramSetA "id-tc26-gost-3410-2012-512-paramSetA" +#define LN_id_tc26_gost_3410_2012_512_paramSetA "GOST R 34.10-2012 (512 bit) ParamSet A" +#define NID_id_tc26_gost_3410_2012_512_paramSetA 998 +#define OBJ_id_tc26_gost_3410_2012_512_paramSetA OBJ_id_tc26_gost_3410_2012_512_constants,1L + +#define SN_id_tc26_gost_3410_2012_512_paramSetB "id-tc26-gost-3410-2012-512-paramSetB" +#define LN_id_tc26_gost_3410_2012_512_paramSetB "GOST R 34.10-2012 (512 bit) ParamSet B" +#define NID_id_tc26_gost_3410_2012_512_paramSetB 999 +#define OBJ_id_tc26_gost_3410_2012_512_paramSetB OBJ_id_tc26_gost_3410_2012_512_constants,2L + +#define SN_id_tc26_gost_3410_2012_512_paramSetC "id-tc26-gost-3410-2012-512-paramSetC" +#define LN_id_tc26_gost_3410_2012_512_paramSetC "GOST R 34.10-2012 (512 bit) ParamSet C" +#define NID_id_tc26_gost_3410_2012_512_paramSetC 1149 +#define OBJ_id_tc26_gost_3410_2012_512_paramSetC OBJ_id_tc26_gost_3410_2012_512_constants,3L + +#define SN_id_tc26_digest_constants "id-tc26-digest-constants" +#define NID_id_tc26_digest_constants 1000 +#define OBJ_id_tc26_digest_constants OBJ_id_tc26_constants,2L + +#define SN_id_tc26_cipher_constants "id-tc26-cipher-constants" +#define NID_id_tc26_cipher_constants 1001 +#define OBJ_id_tc26_cipher_constants OBJ_id_tc26_constants,5L + +#define SN_id_tc26_gost_28147_constants "id-tc26-gost-28147-constants" +#define NID_id_tc26_gost_28147_constants 1002 +#define OBJ_id_tc26_gost_28147_constants OBJ_id_tc26_cipher_constants,1L + +#define SN_id_tc26_gost_28147_param_Z "id-tc26-gost-28147-param-Z" +#define LN_id_tc26_gost_28147_param_Z "GOST 28147-89 TC26 parameter set" +#define NID_id_tc26_gost_28147_param_Z 1003 +#define OBJ_id_tc26_gost_28147_param_Z OBJ_id_tc26_gost_28147_constants,1L + +#define SN_INN "INN" +#define LN_INN "INN" +#define NID_INN 1004 +#define OBJ_INN OBJ_member_body,643L,3L,131L,1L,1L + +#define SN_OGRN "OGRN" +#define LN_OGRN "OGRN" +#define NID_OGRN 1005 +#define OBJ_OGRN OBJ_member_body,643L,100L,1L + +#define SN_SNILS "SNILS" +#define LN_SNILS "SNILS" +#define NID_SNILS 1006 +#define OBJ_SNILS OBJ_member_body,643L,100L,3L + +#define SN_subjectSignTool "subjectSignTool" +#define LN_subjectSignTool "Signing Tool of Subject" +#define NID_subjectSignTool 1007 +#define OBJ_subjectSignTool OBJ_member_body,643L,100L,111L + +#define SN_issuerSignTool "issuerSignTool" +#define LN_issuerSignTool "Signing Tool of Issuer" +#define NID_issuerSignTool 1008 +#define OBJ_issuerSignTool OBJ_member_body,643L,100L,112L + +#define SN_grasshopper_ecb "grasshopper-ecb" +#define NID_grasshopper_ecb 1012 + +#define SN_grasshopper_ctr "grasshopper-ctr" +#define NID_grasshopper_ctr 1013 + +#define SN_grasshopper_ofb "grasshopper-ofb" +#define NID_grasshopper_ofb 1014 + +#define SN_grasshopper_cbc "grasshopper-cbc" +#define NID_grasshopper_cbc 1015 + +#define SN_grasshopper_cfb "grasshopper-cfb" +#define NID_grasshopper_cfb 1016 + +#define SN_grasshopper_mac "grasshopper-mac" +#define NID_grasshopper_mac 1017 + +#define SN_magma_ecb "magma-ecb" +#define NID_magma_ecb 1187 + +#define SN_magma_ctr "magma-ctr" +#define NID_magma_ctr 1188 + +#define SN_magma_ofb "magma-ofb" +#define NID_magma_ofb 1189 + +#define SN_magma_cbc "magma-cbc" +#define NID_magma_cbc 1190 + +#define SN_magma_cfb "magma-cfb" +#define NID_magma_cfb 1191 + +#define SN_magma_mac "magma-mac" +#define NID_magma_mac 1192 + +#define SN_camellia_128_cbc "CAMELLIA-128-CBC" +#define LN_camellia_128_cbc "camellia-128-cbc" +#define NID_camellia_128_cbc 751 +#define OBJ_camellia_128_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,2L + +#define SN_camellia_192_cbc "CAMELLIA-192-CBC" +#define LN_camellia_192_cbc "camellia-192-cbc" +#define NID_camellia_192_cbc 752 +#define OBJ_camellia_192_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,3L + +#define SN_camellia_256_cbc "CAMELLIA-256-CBC" +#define LN_camellia_256_cbc "camellia-256-cbc" +#define NID_camellia_256_cbc 753 +#define OBJ_camellia_256_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,4L + +#define SN_id_camellia128_wrap "id-camellia128-wrap" +#define NID_id_camellia128_wrap 907 +#define OBJ_id_camellia128_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,2L + +#define SN_id_camellia192_wrap "id-camellia192-wrap" +#define NID_id_camellia192_wrap 908 +#define OBJ_id_camellia192_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,3L + +#define SN_id_camellia256_wrap "id-camellia256-wrap" +#define NID_id_camellia256_wrap 909 +#define OBJ_id_camellia256_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,4L + +#define OBJ_ntt_ds 0L,3L,4401L,5L + +#define OBJ_camellia OBJ_ntt_ds,3L,1L,9L + +#define SN_camellia_128_ecb "CAMELLIA-128-ECB" +#define LN_camellia_128_ecb "camellia-128-ecb" +#define NID_camellia_128_ecb 754 +#define OBJ_camellia_128_ecb OBJ_camellia,1L + +#define SN_camellia_128_ofb128 "CAMELLIA-128-OFB" +#define LN_camellia_128_ofb128 "camellia-128-ofb" +#define NID_camellia_128_ofb128 766 +#define OBJ_camellia_128_ofb128 OBJ_camellia,3L + +#define SN_camellia_128_cfb128 "CAMELLIA-128-CFB" +#define LN_camellia_128_cfb128 "camellia-128-cfb" +#define NID_camellia_128_cfb128 757 +#define OBJ_camellia_128_cfb128 OBJ_camellia,4L + +#define SN_camellia_128_gcm "CAMELLIA-128-GCM" +#define LN_camellia_128_gcm "camellia-128-gcm" +#define NID_camellia_128_gcm 961 +#define OBJ_camellia_128_gcm OBJ_camellia,6L + +#define SN_camellia_128_ccm "CAMELLIA-128-CCM" +#define LN_camellia_128_ccm "camellia-128-ccm" +#define NID_camellia_128_ccm 962 +#define OBJ_camellia_128_ccm OBJ_camellia,7L + +#define SN_camellia_128_ctr "CAMELLIA-128-CTR" +#define LN_camellia_128_ctr "camellia-128-ctr" +#define NID_camellia_128_ctr 963 +#define OBJ_camellia_128_ctr OBJ_camellia,9L + +#define SN_camellia_128_cmac "CAMELLIA-128-CMAC" +#define LN_camellia_128_cmac "camellia-128-cmac" +#define NID_camellia_128_cmac 964 +#define OBJ_camellia_128_cmac OBJ_camellia,10L + +#define SN_camellia_192_ecb "CAMELLIA-192-ECB" +#define LN_camellia_192_ecb "camellia-192-ecb" +#define NID_camellia_192_ecb 755 +#define OBJ_camellia_192_ecb OBJ_camellia,21L + +#define SN_camellia_192_ofb128 "CAMELLIA-192-OFB" +#define LN_camellia_192_ofb128 "camellia-192-ofb" +#define NID_camellia_192_ofb128 767 +#define OBJ_camellia_192_ofb128 OBJ_camellia,23L + +#define SN_camellia_192_cfb128 "CAMELLIA-192-CFB" +#define LN_camellia_192_cfb128 "camellia-192-cfb" +#define NID_camellia_192_cfb128 758 +#define OBJ_camellia_192_cfb128 OBJ_camellia,24L + +#define SN_camellia_192_gcm "CAMELLIA-192-GCM" +#define LN_camellia_192_gcm "camellia-192-gcm" +#define NID_camellia_192_gcm 965 +#define OBJ_camellia_192_gcm OBJ_camellia,26L + +#define SN_camellia_192_ccm "CAMELLIA-192-CCM" +#define LN_camellia_192_ccm "camellia-192-ccm" +#define NID_camellia_192_ccm 966 +#define OBJ_camellia_192_ccm OBJ_camellia,27L + +#define SN_camellia_192_ctr "CAMELLIA-192-CTR" +#define LN_camellia_192_ctr "camellia-192-ctr" +#define NID_camellia_192_ctr 967 +#define OBJ_camellia_192_ctr OBJ_camellia,29L + +#define SN_camellia_192_cmac "CAMELLIA-192-CMAC" +#define LN_camellia_192_cmac "camellia-192-cmac" +#define NID_camellia_192_cmac 968 +#define OBJ_camellia_192_cmac OBJ_camellia,30L + +#define SN_camellia_256_ecb "CAMELLIA-256-ECB" +#define LN_camellia_256_ecb "camellia-256-ecb" +#define NID_camellia_256_ecb 756 +#define OBJ_camellia_256_ecb OBJ_camellia,41L + +#define SN_camellia_256_ofb128 "CAMELLIA-256-OFB" +#define LN_camellia_256_ofb128 "camellia-256-ofb" +#define NID_camellia_256_ofb128 768 +#define OBJ_camellia_256_ofb128 OBJ_camellia,43L + +#define SN_camellia_256_cfb128 "CAMELLIA-256-CFB" +#define LN_camellia_256_cfb128 "camellia-256-cfb" +#define NID_camellia_256_cfb128 759 +#define OBJ_camellia_256_cfb128 OBJ_camellia,44L + +#define SN_camellia_256_gcm "CAMELLIA-256-GCM" +#define LN_camellia_256_gcm "camellia-256-gcm" +#define NID_camellia_256_gcm 969 +#define OBJ_camellia_256_gcm OBJ_camellia,46L + +#define SN_camellia_256_ccm "CAMELLIA-256-CCM" +#define LN_camellia_256_ccm "camellia-256-ccm" +#define NID_camellia_256_ccm 970 +#define OBJ_camellia_256_ccm OBJ_camellia,47L + +#define SN_camellia_256_ctr "CAMELLIA-256-CTR" +#define LN_camellia_256_ctr "camellia-256-ctr" +#define NID_camellia_256_ctr 971 +#define OBJ_camellia_256_ctr OBJ_camellia,49L + +#define SN_camellia_256_cmac "CAMELLIA-256-CMAC" +#define LN_camellia_256_cmac "camellia-256-cmac" +#define NID_camellia_256_cmac 972 +#define OBJ_camellia_256_cmac OBJ_camellia,50L + +#define SN_camellia_128_cfb1 "CAMELLIA-128-CFB1" +#define LN_camellia_128_cfb1 "camellia-128-cfb1" +#define NID_camellia_128_cfb1 760 + +#define SN_camellia_192_cfb1 "CAMELLIA-192-CFB1" +#define LN_camellia_192_cfb1 "camellia-192-cfb1" +#define NID_camellia_192_cfb1 761 + +#define SN_camellia_256_cfb1 "CAMELLIA-256-CFB1" +#define LN_camellia_256_cfb1 "camellia-256-cfb1" +#define NID_camellia_256_cfb1 762 + +#define SN_camellia_128_cfb8 "CAMELLIA-128-CFB8" +#define LN_camellia_128_cfb8 "camellia-128-cfb8" +#define NID_camellia_128_cfb8 763 + +#define SN_camellia_192_cfb8 "CAMELLIA-192-CFB8" +#define LN_camellia_192_cfb8 "camellia-192-cfb8" +#define NID_camellia_192_cfb8 764 + +#define SN_camellia_256_cfb8 "CAMELLIA-256-CFB8" +#define LN_camellia_256_cfb8 "camellia-256-cfb8" +#define NID_camellia_256_cfb8 765 + +#define OBJ_aria 1L,2L,410L,200046L,1L,1L + +#define SN_aria_128_ecb "ARIA-128-ECB" +#define LN_aria_128_ecb "aria-128-ecb" +#define NID_aria_128_ecb 1065 +#define OBJ_aria_128_ecb OBJ_aria,1L + +#define SN_aria_128_cbc "ARIA-128-CBC" +#define LN_aria_128_cbc "aria-128-cbc" +#define NID_aria_128_cbc 1066 +#define OBJ_aria_128_cbc OBJ_aria,2L + +#define SN_aria_128_cfb128 "ARIA-128-CFB" +#define LN_aria_128_cfb128 "aria-128-cfb" +#define NID_aria_128_cfb128 1067 +#define OBJ_aria_128_cfb128 OBJ_aria,3L + +#define SN_aria_128_ofb128 "ARIA-128-OFB" +#define LN_aria_128_ofb128 "aria-128-ofb" +#define NID_aria_128_ofb128 1068 +#define OBJ_aria_128_ofb128 OBJ_aria,4L + +#define SN_aria_128_ctr "ARIA-128-CTR" +#define LN_aria_128_ctr "aria-128-ctr" +#define NID_aria_128_ctr 1069 +#define OBJ_aria_128_ctr OBJ_aria,5L + +#define SN_aria_192_ecb "ARIA-192-ECB" +#define LN_aria_192_ecb "aria-192-ecb" +#define NID_aria_192_ecb 1070 +#define OBJ_aria_192_ecb OBJ_aria,6L + +#define SN_aria_192_cbc "ARIA-192-CBC" +#define LN_aria_192_cbc "aria-192-cbc" +#define NID_aria_192_cbc 1071 +#define OBJ_aria_192_cbc OBJ_aria,7L + +#define SN_aria_192_cfb128 "ARIA-192-CFB" +#define LN_aria_192_cfb128 "aria-192-cfb" +#define NID_aria_192_cfb128 1072 +#define OBJ_aria_192_cfb128 OBJ_aria,8L + +#define SN_aria_192_ofb128 "ARIA-192-OFB" +#define LN_aria_192_ofb128 "aria-192-ofb" +#define NID_aria_192_ofb128 1073 +#define OBJ_aria_192_ofb128 OBJ_aria,9L + +#define SN_aria_192_ctr "ARIA-192-CTR" +#define LN_aria_192_ctr "aria-192-ctr" +#define NID_aria_192_ctr 1074 +#define OBJ_aria_192_ctr OBJ_aria,10L + +#define SN_aria_256_ecb "ARIA-256-ECB" +#define LN_aria_256_ecb "aria-256-ecb" +#define NID_aria_256_ecb 1075 +#define OBJ_aria_256_ecb OBJ_aria,11L + +#define SN_aria_256_cbc "ARIA-256-CBC" +#define LN_aria_256_cbc "aria-256-cbc" +#define NID_aria_256_cbc 1076 +#define OBJ_aria_256_cbc OBJ_aria,12L + +#define SN_aria_256_cfb128 "ARIA-256-CFB" +#define LN_aria_256_cfb128 "aria-256-cfb" +#define NID_aria_256_cfb128 1077 +#define OBJ_aria_256_cfb128 OBJ_aria,13L + +#define SN_aria_256_ofb128 "ARIA-256-OFB" +#define LN_aria_256_ofb128 "aria-256-ofb" +#define NID_aria_256_ofb128 1078 +#define OBJ_aria_256_ofb128 OBJ_aria,14L + +#define SN_aria_256_ctr "ARIA-256-CTR" +#define LN_aria_256_ctr "aria-256-ctr" +#define NID_aria_256_ctr 1079 +#define OBJ_aria_256_ctr OBJ_aria,15L + +#define SN_aria_128_cfb1 "ARIA-128-CFB1" +#define LN_aria_128_cfb1 "aria-128-cfb1" +#define NID_aria_128_cfb1 1080 + +#define SN_aria_192_cfb1 "ARIA-192-CFB1" +#define LN_aria_192_cfb1 "aria-192-cfb1" +#define NID_aria_192_cfb1 1081 + +#define SN_aria_256_cfb1 "ARIA-256-CFB1" +#define LN_aria_256_cfb1 "aria-256-cfb1" +#define NID_aria_256_cfb1 1082 + +#define SN_aria_128_cfb8 "ARIA-128-CFB8" +#define LN_aria_128_cfb8 "aria-128-cfb8" +#define NID_aria_128_cfb8 1083 + +#define SN_aria_192_cfb8 "ARIA-192-CFB8" +#define LN_aria_192_cfb8 "aria-192-cfb8" +#define NID_aria_192_cfb8 1084 + +#define SN_aria_256_cfb8 "ARIA-256-CFB8" +#define LN_aria_256_cfb8 "aria-256-cfb8" +#define NID_aria_256_cfb8 1085 + +#define SN_aria_128_ccm "ARIA-128-CCM" +#define LN_aria_128_ccm "aria-128-ccm" +#define NID_aria_128_ccm 1120 +#define OBJ_aria_128_ccm OBJ_aria,37L + +#define SN_aria_192_ccm "ARIA-192-CCM" +#define LN_aria_192_ccm "aria-192-ccm" +#define NID_aria_192_ccm 1121 +#define OBJ_aria_192_ccm OBJ_aria,38L + +#define SN_aria_256_ccm "ARIA-256-CCM" +#define LN_aria_256_ccm "aria-256-ccm" +#define NID_aria_256_ccm 1122 +#define OBJ_aria_256_ccm OBJ_aria,39L + +#define SN_aria_128_gcm "ARIA-128-GCM" +#define LN_aria_128_gcm "aria-128-gcm" +#define NID_aria_128_gcm 1123 +#define OBJ_aria_128_gcm OBJ_aria,34L + +#define SN_aria_192_gcm "ARIA-192-GCM" +#define LN_aria_192_gcm "aria-192-gcm" +#define NID_aria_192_gcm 1124 +#define OBJ_aria_192_gcm OBJ_aria,35L + +#define SN_aria_256_gcm "ARIA-256-GCM" +#define LN_aria_256_gcm "aria-256-gcm" +#define NID_aria_256_gcm 1125 +#define OBJ_aria_256_gcm OBJ_aria,36L + +#define SN_kisa "KISA" +#define LN_kisa "kisa" +#define NID_kisa 773 +#define OBJ_kisa OBJ_member_body,410L,200004L + +#define SN_seed_ecb "SEED-ECB" +#define LN_seed_ecb "seed-ecb" +#define NID_seed_ecb 776 +#define OBJ_seed_ecb OBJ_kisa,1L,3L + +#define SN_seed_cbc "SEED-CBC" +#define LN_seed_cbc "seed-cbc" +#define NID_seed_cbc 777 +#define OBJ_seed_cbc OBJ_kisa,1L,4L + +#define SN_seed_cfb128 "SEED-CFB" +#define LN_seed_cfb128 "seed-cfb" +#define NID_seed_cfb128 779 +#define OBJ_seed_cfb128 OBJ_kisa,1L,5L + +#define SN_seed_ofb128 "SEED-OFB" +#define LN_seed_ofb128 "seed-ofb" +#define NID_seed_ofb128 778 +#define OBJ_seed_ofb128 OBJ_kisa,1L,6L + +#define SN_sm4_ecb "SM4-ECB" +#define LN_sm4_ecb "sm4-ecb" +#define NID_sm4_ecb 1133 +#define OBJ_sm4_ecb OBJ_sm_scheme,104L,1L + +#define SN_sm4_cbc "SM4-CBC" +#define LN_sm4_cbc "sm4-cbc" +#define NID_sm4_cbc 1134 +#define OBJ_sm4_cbc OBJ_sm_scheme,104L,2L + +#define SN_sm4_ofb128 "SM4-OFB" +#define LN_sm4_ofb128 "sm4-ofb" +#define NID_sm4_ofb128 1135 +#define OBJ_sm4_ofb128 OBJ_sm_scheme,104L,3L + +#define SN_sm4_cfb128 "SM4-CFB" +#define LN_sm4_cfb128 "sm4-cfb" +#define NID_sm4_cfb128 1137 +#define OBJ_sm4_cfb128 OBJ_sm_scheme,104L,4L + +#define SN_sm4_cfb1 "SM4-CFB1" +#define LN_sm4_cfb1 "sm4-cfb1" +#define NID_sm4_cfb1 1136 +#define OBJ_sm4_cfb1 OBJ_sm_scheme,104L,5L + +#define SN_sm4_cfb8 "SM4-CFB8" +#define LN_sm4_cfb8 "sm4-cfb8" +#define NID_sm4_cfb8 1138 +#define OBJ_sm4_cfb8 OBJ_sm_scheme,104L,6L + +#define SN_sm4_ctr "SM4-CTR" +#define LN_sm4_ctr "sm4-ctr" +#define NID_sm4_ctr 1139 +#define OBJ_sm4_ctr OBJ_sm_scheme,104L,7L + +#define SN_hmac "HMAC" +#define LN_hmac "hmac" +#define NID_hmac 855 + +#define SN_cmac "CMAC" +#define LN_cmac "cmac" +#define NID_cmac 894 + +#define SN_rc4_hmac_md5 "RC4-HMAC-MD5" +#define LN_rc4_hmac_md5 "rc4-hmac-md5" +#define NID_rc4_hmac_md5 915 + +#define SN_aes_128_cbc_hmac_sha1 "AES-128-CBC-HMAC-SHA1" +#define LN_aes_128_cbc_hmac_sha1 "aes-128-cbc-hmac-sha1" +#define NID_aes_128_cbc_hmac_sha1 916 + +#define SN_aes_192_cbc_hmac_sha1 "AES-192-CBC-HMAC-SHA1" +#define LN_aes_192_cbc_hmac_sha1 "aes-192-cbc-hmac-sha1" +#define NID_aes_192_cbc_hmac_sha1 917 + +#define SN_aes_256_cbc_hmac_sha1 "AES-256-CBC-HMAC-SHA1" +#define LN_aes_256_cbc_hmac_sha1 "aes-256-cbc-hmac-sha1" +#define NID_aes_256_cbc_hmac_sha1 918 + +#define SN_aes_128_cbc_hmac_sha256 "AES-128-CBC-HMAC-SHA256" +#define LN_aes_128_cbc_hmac_sha256 "aes-128-cbc-hmac-sha256" +#define NID_aes_128_cbc_hmac_sha256 948 + +#define SN_aes_192_cbc_hmac_sha256 "AES-192-CBC-HMAC-SHA256" +#define LN_aes_192_cbc_hmac_sha256 "aes-192-cbc-hmac-sha256" +#define NID_aes_192_cbc_hmac_sha256 949 + +#define SN_aes_256_cbc_hmac_sha256 "AES-256-CBC-HMAC-SHA256" +#define LN_aes_256_cbc_hmac_sha256 "aes-256-cbc-hmac-sha256" +#define NID_aes_256_cbc_hmac_sha256 950 + +#define SN_chacha20_poly1305 "ChaCha20-Poly1305" +#define LN_chacha20_poly1305 "chacha20-poly1305" +#define NID_chacha20_poly1305 1018 + +#define SN_chacha20 "ChaCha20" +#define LN_chacha20 "chacha20" +#define NID_chacha20 1019 + +#define SN_dhpublicnumber "dhpublicnumber" +#define LN_dhpublicnumber "X9.42 DH" +#define NID_dhpublicnumber 920 +#define OBJ_dhpublicnumber OBJ_ISO_US,10046L,2L,1L + +#define SN_brainpoolP160r1 "brainpoolP160r1" +#define NID_brainpoolP160r1 921 +#define OBJ_brainpoolP160r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,1L + +#define SN_brainpoolP160t1 "brainpoolP160t1" +#define NID_brainpoolP160t1 922 +#define OBJ_brainpoolP160t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,2L + +#define SN_brainpoolP192r1 "brainpoolP192r1" +#define NID_brainpoolP192r1 923 +#define OBJ_brainpoolP192r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,3L + +#define SN_brainpoolP192t1 "brainpoolP192t1" +#define NID_brainpoolP192t1 924 +#define OBJ_brainpoolP192t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,4L + +#define SN_brainpoolP224r1 "brainpoolP224r1" +#define NID_brainpoolP224r1 925 +#define OBJ_brainpoolP224r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,5L + +#define SN_brainpoolP224t1 "brainpoolP224t1" +#define NID_brainpoolP224t1 926 +#define OBJ_brainpoolP224t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,6L + +#define SN_brainpoolP256r1 "brainpoolP256r1" +#define NID_brainpoolP256r1 927 +#define OBJ_brainpoolP256r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,7L + +#define SN_brainpoolP256t1 "brainpoolP256t1" +#define NID_brainpoolP256t1 928 +#define OBJ_brainpoolP256t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,8L + +#define SN_brainpoolP320r1 "brainpoolP320r1" +#define NID_brainpoolP320r1 929 +#define OBJ_brainpoolP320r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,9L + +#define SN_brainpoolP320t1 "brainpoolP320t1" +#define NID_brainpoolP320t1 930 +#define OBJ_brainpoolP320t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,10L + +#define SN_brainpoolP384r1 "brainpoolP384r1" +#define NID_brainpoolP384r1 931 +#define OBJ_brainpoolP384r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,11L + +#define SN_brainpoolP384t1 "brainpoolP384t1" +#define NID_brainpoolP384t1 932 +#define OBJ_brainpoolP384t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,12L + +#define SN_brainpoolP512r1 "brainpoolP512r1" +#define NID_brainpoolP512r1 933 +#define OBJ_brainpoolP512r1 1L,3L,36L,3L,3L,2L,8L,1L,1L,13L + +#define SN_brainpoolP512t1 "brainpoolP512t1" +#define NID_brainpoolP512t1 934 +#define OBJ_brainpoolP512t1 1L,3L,36L,3L,3L,2L,8L,1L,1L,14L + +#define OBJ_x9_63_scheme 1L,3L,133L,16L,840L,63L,0L + +#define OBJ_secg_scheme OBJ_certicom_arc,1L + +#define SN_dhSinglePass_stdDH_sha1kdf_scheme "dhSinglePass-stdDH-sha1kdf-scheme" +#define NID_dhSinglePass_stdDH_sha1kdf_scheme 936 +#define OBJ_dhSinglePass_stdDH_sha1kdf_scheme OBJ_x9_63_scheme,2L + +#define SN_dhSinglePass_stdDH_sha224kdf_scheme "dhSinglePass-stdDH-sha224kdf-scheme" +#define NID_dhSinglePass_stdDH_sha224kdf_scheme 937 +#define OBJ_dhSinglePass_stdDH_sha224kdf_scheme OBJ_secg_scheme,11L,0L + +#define SN_dhSinglePass_stdDH_sha256kdf_scheme "dhSinglePass-stdDH-sha256kdf-scheme" +#define NID_dhSinglePass_stdDH_sha256kdf_scheme 938 +#define OBJ_dhSinglePass_stdDH_sha256kdf_scheme OBJ_secg_scheme,11L,1L + +#define SN_dhSinglePass_stdDH_sha384kdf_scheme "dhSinglePass-stdDH-sha384kdf-scheme" +#define NID_dhSinglePass_stdDH_sha384kdf_scheme 939 +#define OBJ_dhSinglePass_stdDH_sha384kdf_scheme OBJ_secg_scheme,11L,2L + +#define SN_dhSinglePass_stdDH_sha512kdf_scheme "dhSinglePass-stdDH-sha512kdf-scheme" +#define NID_dhSinglePass_stdDH_sha512kdf_scheme 940 +#define OBJ_dhSinglePass_stdDH_sha512kdf_scheme OBJ_secg_scheme,11L,3L + +#define SN_dhSinglePass_cofactorDH_sha1kdf_scheme "dhSinglePass-cofactorDH-sha1kdf-scheme" +#define NID_dhSinglePass_cofactorDH_sha1kdf_scheme 941 +#define OBJ_dhSinglePass_cofactorDH_sha1kdf_scheme OBJ_x9_63_scheme,3L + +#define SN_dhSinglePass_cofactorDH_sha224kdf_scheme "dhSinglePass-cofactorDH-sha224kdf-scheme" +#define NID_dhSinglePass_cofactorDH_sha224kdf_scheme 942 +#define OBJ_dhSinglePass_cofactorDH_sha224kdf_scheme OBJ_secg_scheme,14L,0L + +#define SN_dhSinglePass_cofactorDH_sha256kdf_scheme "dhSinglePass-cofactorDH-sha256kdf-scheme" +#define NID_dhSinglePass_cofactorDH_sha256kdf_scheme 943 +#define OBJ_dhSinglePass_cofactorDH_sha256kdf_scheme OBJ_secg_scheme,14L,1L + +#define SN_dhSinglePass_cofactorDH_sha384kdf_scheme "dhSinglePass-cofactorDH-sha384kdf-scheme" +#define NID_dhSinglePass_cofactorDH_sha384kdf_scheme 944 +#define OBJ_dhSinglePass_cofactorDH_sha384kdf_scheme OBJ_secg_scheme,14L,2L + +#define SN_dhSinglePass_cofactorDH_sha512kdf_scheme "dhSinglePass-cofactorDH-sha512kdf-scheme" +#define NID_dhSinglePass_cofactorDH_sha512kdf_scheme 945 +#define OBJ_dhSinglePass_cofactorDH_sha512kdf_scheme OBJ_secg_scheme,14L,3L + +#define SN_dh_std_kdf "dh-std-kdf" +#define NID_dh_std_kdf 946 + +#define SN_dh_cofactor_kdf "dh-cofactor-kdf" +#define NID_dh_cofactor_kdf 947 + +#define SN_ct_precert_scts "ct_precert_scts" +#define LN_ct_precert_scts "CT Precertificate SCTs" +#define NID_ct_precert_scts 951 +#define OBJ_ct_precert_scts 1L,3L,6L,1L,4L,1L,11129L,2L,4L,2L + +#define SN_ct_precert_poison "ct_precert_poison" +#define LN_ct_precert_poison "CT Precertificate Poison" +#define NID_ct_precert_poison 952 +#define OBJ_ct_precert_poison 1L,3L,6L,1L,4L,1L,11129L,2L,4L,3L + +#define SN_ct_precert_signer "ct_precert_signer" +#define LN_ct_precert_signer "CT Precertificate Signer" +#define NID_ct_precert_signer 953 +#define OBJ_ct_precert_signer 1L,3L,6L,1L,4L,1L,11129L,2L,4L,4L + +#define SN_ct_cert_scts "ct_cert_scts" +#define LN_ct_cert_scts "CT Certificate SCTs" +#define NID_ct_cert_scts 954 +#define OBJ_ct_cert_scts 1L,3L,6L,1L,4L,1L,11129L,2L,4L,5L + +#define SN_jurisdictionLocalityName "jurisdictionL" +#define LN_jurisdictionLocalityName "jurisdictionLocalityName" +#define NID_jurisdictionLocalityName 955 +#define OBJ_jurisdictionLocalityName 1L,3L,6L,1L,4L,1L,311L,60L,2L,1L,1L + +#define SN_jurisdictionStateOrProvinceName "jurisdictionST" +#define LN_jurisdictionStateOrProvinceName "jurisdictionStateOrProvinceName" +#define NID_jurisdictionStateOrProvinceName 956 +#define OBJ_jurisdictionStateOrProvinceName 1L,3L,6L,1L,4L,1L,311L,60L,2L,1L,2L + +#define SN_jurisdictionCountryName "jurisdictionC" +#define LN_jurisdictionCountryName "jurisdictionCountryName" +#define NID_jurisdictionCountryName 957 +#define OBJ_jurisdictionCountryName 1L,3L,6L,1L,4L,1L,311L,60L,2L,1L,3L + +#define SN_id_scrypt "id-scrypt" +#define LN_id_scrypt "scrypt" +#define NID_id_scrypt 973 +#define OBJ_id_scrypt 1L,3L,6L,1L,4L,1L,11591L,4L,11L + +#define SN_tls1_prf "TLS1-PRF" +#define LN_tls1_prf "tls1-prf" +#define NID_tls1_prf 1021 + +#define SN_hkdf "HKDF" +#define LN_hkdf "hkdf" +#define NID_hkdf 1036 + +#define SN_id_pkinit "id-pkinit" +#define NID_id_pkinit 1031 +#define OBJ_id_pkinit 1L,3L,6L,1L,5L,2L,3L + +#define SN_pkInitClientAuth "pkInitClientAuth" +#define LN_pkInitClientAuth "PKINIT Client Auth" +#define NID_pkInitClientAuth 1032 +#define OBJ_pkInitClientAuth OBJ_id_pkinit,4L + +#define SN_pkInitKDC "pkInitKDC" +#define LN_pkInitKDC "Signing KDC Response" +#define NID_pkInitKDC 1033 +#define OBJ_pkInitKDC OBJ_id_pkinit,5L + +#define SN_X25519 "X25519" +#define NID_X25519 1034 +#define OBJ_X25519 1L,3L,101L,110L + +#define SN_X448 "X448" +#define NID_X448 1035 +#define OBJ_X448 1L,3L,101L,111L + +#define SN_ED25519 "ED25519" +#define NID_ED25519 1087 +#define OBJ_ED25519 1L,3L,101L,112L + +#define SN_ED448 "ED448" +#define NID_ED448 1088 +#define OBJ_ED448 1L,3L,101L,113L + +#define SN_kx_rsa "KxRSA" +#define LN_kx_rsa "kx-rsa" +#define NID_kx_rsa 1037 + +#define SN_kx_ecdhe "KxECDHE" +#define LN_kx_ecdhe "kx-ecdhe" +#define NID_kx_ecdhe 1038 + +#define SN_kx_dhe "KxDHE" +#define LN_kx_dhe "kx-dhe" +#define NID_kx_dhe 1039 + +#define SN_kx_ecdhe_psk "KxECDHE-PSK" +#define LN_kx_ecdhe_psk "kx-ecdhe-psk" +#define NID_kx_ecdhe_psk 1040 + +#define SN_kx_dhe_psk "KxDHE-PSK" +#define LN_kx_dhe_psk "kx-dhe-psk" +#define NID_kx_dhe_psk 1041 + +#define SN_kx_rsa_psk "KxRSA_PSK" +#define LN_kx_rsa_psk "kx-rsa-psk" +#define NID_kx_rsa_psk 1042 + +#define SN_kx_psk "KxPSK" +#define LN_kx_psk "kx-psk" +#define NID_kx_psk 1043 + +#define SN_kx_srp "KxSRP" +#define LN_kx_srp "kx-srp" +#define NID_kx_srp 1044 + +#define SN_kx_gost "KxGOST" +#define LN_kx_gost "kx-gost" +#define NID_kx_gost 1045 + +#define SN_kx_any "KxANY" +#define LN_kx_any "kx-any" +#define NID_kx_any 1063 + +#define SN_auth_rsa "AuthRSA" +#define LN_auth_rsa "auth-rsa" +#define NID_auth_rsa 1046 + +#define SN_auth_ecdsa "AuthECDSA" +#define LN_auth_ecdsa "auth-ecdsa" +#define NID_auth_ecdsa 1047 + +#define SN_auth_psk "AuthPSK" +#define LN_auth_psk "auth-psk" +#define NID_auth_psk 1048 + +#define SN_auth_dss "AuthDSS" +#define LN_auth_dss "auth-dss" +#define NID_auth_dss 1049 + +#define SN_auth_gost01 "AuthGOST01" +#define LN_auth_gost01 "auth-gost01" +#define NID_auth_gost01 1050 + +#define SN_auth_gost12 "AuthGOST12" +#define LN_auth_gost12 "auth-gost12" +#define NID_auth_gost12 1051 + +#define SN_auth_srp "AuthSRP" +#define LN_auth_srp "auth-srp" +#define NID_auth_srp 1052 + +#define SN_auth_null "AuthNULL" +#define LN_auth_null "auth-null" +#define NID_auth_null 1053 + +#define SN_auth_any "AuthANY" +#define LN_auth_any "auth-any" +#define NID_auth_any 1064 + +#define SN_poly1305 "Poly1305" +#define LN_poly1305 "poly1305" +#define NID_poly1305 1061 + +#define SN_siphash "SipHash" +#define LN_siphash "siphash" +#define NID_siphash 1062 + +#define SN_ffdhe2048 "ffdhe2048" +#define NID_ffdhe2048 1126 + +#define SN_ffdhe3072 "ffdhe3072" +#define NID_ffdhe3072 1127 + +#define SN_ffdhe4096 "ffdhe4096" +#define NID_ffdhe4096 1128 + +#define SN_ffdhe6144 "ffdhe6144" +#define NID_ffdhe6144 1129 + +#define SN_ffdhe8192 "ffdhe8192" +#define NID_ffdhe8192 1130 + +#define SN_ISO_UA "ISO-UA" +#define NID_ISO_UA 1150 +#define OBJ_ISO_UA OBJ_member_body,804L + +#define SN_ua_pki "ua-pki" +#define NID_ua_pki 1151 +#define OBJ_ua_pki OBJ_ISO_UA,2L,1L,1L,1L + +#define SN_dstu28147 "dstu28147" +#define LN_dstu28147 "DSTU Gost 28147-2009" +#define NID_dstu28147 1152 +#define OBJ_dstu28147 OBJ_ua_pki,1L,1L,1L + +#define SN_dstu28147_ofb "dstu28147-ofb" +#define LN_dstu28147_ofb "DSTU Gost 28147-2009 OFB mode" +#define NID_dstu28147_ofb 1153 +#define OBJ_dstu28147_ofb OBJ_dstu28147,2L + +#define SN_dstu28147_cfb "dstu28147-cfb" +#define LN_dstu28147_cfb "DSTU Gost 28147-2009 CFB mode" +#define NID_dstu28147_cfb 1154 +#define OBJ_dstu28147_cfb OBJ_dstu28147,3L + +#define SN_dstu28147_wrap "dstu28147-wrap" +#define LN_dstu28147_wrap "DSTU Gost 28147-2009 key wrap" +#define NID_dstu28147_wrap 1155 +#define OBJ_dstu28147_wrap OBJ_dstu28147,5L + +#define SN_hmacWithDstu34311 "hmacWithDstu34311" +#define LN_hmacWithDstu34311 "HMAC DSTU Gost 34311-95" +#define NID_hmacWithDstu34311 1156 +#define OBJ_hmacWithDstu34311 OBJ_ua_pki,1L,1L,2L + +#define SN_dstu34311 "dstu34311" +#define LN_dstu34311 "DSTU Gost 34311-95" +#define NID_dstu34311 1157 +#define OBJ_dstu34311 OBJ_ua_pki,1L,2L,1L + +#define SN_dstu4145le "dstu4145le" +#define LN_dstu4145le "DSTU 4145-2002 little endian" +#define NID_dstu4145le 1158 +#define OBJ_dstu4145le OBJ_ua_pki,1L,3L,1L,1L + +#define SN_dstu4145be "dstu4145be" +#define LN_dstu4145be "DSTU 4145-2002 big endian" +#define NID_dstu4145be 1159 +#define OBJ_dstu4145be OBJ_dstu4145le,1L,1L + +#define SN_uacurve0 "uacurve0" +#define LN_uacurve0 "DSTU curve 0" +#define NID_uacurve0 1160 +#define OBJ_uacurve0 OBJ_dstu4145le,2L,0L + +#define SN_uacurve1 "uacurve1" +#define LN_uacurve1 "DSTU curve 1" +#define NID_uacurve1 1161 +#define OBJ_uacurve1 OBJ_dstu4145le,2L,1L + +#define SN_uacurve2 "uacurve2" +#define LN_uacurve2 "DSTU curve 2" +#define NID_uacurve2 1162 +#define OBJ_uacurve2 OBJ_dstu4145le,2L,2L + +#define SN_uacurve3 "uacurve3" +#define LN_uacurve3 "DSTU curve 3" +#define NID_uacurve3 1163 +#define OBJ_uacurve3 OBJ_dstu4145le,2L,3L + +#define SN_uacurve4 "uacurve4" +#define LN_uacurve4 "DSTU curve 4" +#define NID_uacurve4 1164 +#define OBJ_uacurve4 OBJ_dstu4145le,2L,4L + +#define SN_uacurve5 "uacurve5" +#define LN_uacurve5 "DSTU curve 5" +#define NID_uacurve5 1165 +#define OBJ_uacurve5 OBJ_dstu4145le,2L,5L + +#define SN_uacurve6 "uacurve6" +#define LN_uacurve6 "DSTU curve 6" +#define NID_uacurve6 1166 +#define OBJ_uacurve6 OBJ_dstu4145le,2L,6L + +#define SN_uacurve7 "uacurve7" +#define LN_uacurve7 "DSTU curve 7" +#define NID_uacurve7 1167 +#define OBJ_uacurve7 OBJ_dstu4145le,2L,7L + +#define SN_uacurve8 "uacurve8" +#define LN_uacurve8 "DSTU curve 8" +#define NID_uacurve8 1168 +#define OBJ_uacurve8 OBJ_dstu4145le,2L,8L + +#define SN_uacurve9 "uacurve9" +#define LN_uacurve9 "DSTU curve 9" +#define NID_uacurve9 1169 +#define OBJ_uacurve9 OBJ_dstu4145le,2L,9L diff --git a/thrid-party/openssl/openssl/objects.h b/thrid-party/openssl/openssl/objects.h new file mode 100644 index 0000000000..5e8b5762f8 --- /dev/null +++ b/thrid-party/openssl/openssl/objects.h @@ -0,0 +1,175 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OBJECTS_H +# define HEADER_OBJECTS_H + +# include +# include +# include +# include + +# define OBJ_NAME_TYPE_UNDEF 0x00 +# define OBJ_NAME_TYPE_MD_METH 0x01 +# define OBJ_NAME_TYPE_CIPHER_METH 0x02 +# define OBJ_NAME_TYPE_PKEY_METH 0x03 +# define OBJ_NAME_TYPE_COMP_METH 0x04 +# define OBJ_NAME_TYPE_NUM 0x05 + +# define OBJ_NAME_ALIAS 0x8000 + +# define OBJ_BSEARCH_VALUE_ON_NOMATCH 0x01 +# define OBJ_BSEARCH_FIRST_VALUE_ON_MATCH 0x02 + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct obj_name_st { + int type; + int alias; + const char *name; + const char *data; +} OBJ_NAME; + +# define OBJ_create_and_add_object(a,b,c) OBJ_create(a,b,c) + +int OBJ_NAME_init(void); +int OBJ_NAME_new_index(unsigned long (*hash_func) (const char *), + int (*cmp_func) (const char *, const char *), + void (*free_func) (const char *, int, const char *)); +const char *OBJ_NAME_get(const char *name, int type); +int OBJ_NAME_add(const char *name, int type, const char *data); +int OBJ_NAME_remove(const char *name, int type); +void OBJ_NAME_cleanup(int type); /* -1 for everything */ +void OBJ_NAME_do_all(int type, void (*fn) (const OBJ_NAME *, void *arg), + void *arg); +void OBJ_NAME_do_all_sorted(int type, + void (*fn) (const OBJ_NAME *, void *arg), + void *arg); + +ASN1_OBJECT *OBJ_dup(const ASN1_OBJECT *o); +ASN1_OBJECT *OBJ_nid2obj(int n); +const char *OBJ_nid2ln(int n); +const char *OBJ_nid2sn(int n); +int OBJ_obj2nid(const ASN1_OBJECT *o); +ASN1_OBJECT *OBJ_txt2obj(const char *s, int no_name); +int OBJ_obj2txt(char *buf, int buf_len, const ASN1_OBJECT *a, int no_name); +int OBJ_txt2nid(const char *s); +int OBJ_ln2nid(const char *s); +int OBJ_sn2nid(const char *s); +int OBJ_cmp(const ASN1_OBJECT *a, const ASN1_OBJECT *b); +const void *OBJ_bsearch_(const void *key, const void *base, int num, int size, + int (*cmp) (const void *, const void *)); +const void *OBJ_bsearch_ex_(const void *key, const void *base, int num, + int size, + int (*cmp) (const void *, const void *), + int flags); + +# define _DECLARE_OBJ_BSEARCH_CMP_FN(scope, type1, type2, nm) \ + static int nm##_cmp_BSEARCH_CMP_FN(const void *, const void *); \ + static int nm##_cmp(type1 const *, type2 const *); \ + scope type2 * OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) + +# define DECLARE_OBJ_BSEARCH_CMP_FN(type1, type2, cmp) \ + _DECLARE_OBJ_BSEARCH_CMP_FN(static, type1, type2, cmp) +# define DECLARE_OBJ_BSEARCH_GLOBAL_CMP_FN(type1, type2, nm) \ + type2 * OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) + +/*- + * Unsolved problem: if a type is actually a pointer type, like + * nid_triple is, then its impossible to get a const where you need + * it. Consider: + * + * typedef int nid_triple[3]; + * const void *a_; + * const nid_triple const *a = a_; + * + * The assignment discards a const because what you really want is: + * + * const int const * const *a = a_; + * + * But if you do that, you lose the fact that a is an array of 3 ints, + * which breaks comparison functions. + * + * Thus we end up having to cast, sadly, or unpack the + * declarations. Or, as I finally did in this case, declare nid_triple + * to be a struct, which it should have been in the first place. + * + * Ben, August 2008. + * + * Also, strictly speaking not all types need be const, but handling + * the non-constness means a lot of complication, and in practice + * comparison routines do always not touch their arguments. + */ + +# define IMPLEMENT_OBJ_BSEARCH_CMP_FN(type1, type2, nm) \ + static int nm##_cmp_BSEARCH_CMP_FN(const void *a_, const void *b_) \ + { \ + type1 const *a = a_; \ + type2 const *b = b_; \ + return nm##_cmp(a,b); \ + } \ + static type2 *OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) \ + { \ + return (type2 *)OBJ_bsearch_(key, base, num, sizeof(type2), \ + nm##_cmp_BSEARCH_CMP_FN); \ + } \ + extern void dummy_prototype(void) + +# define IMPLEMENT_OBJ_BSEARCH_GLOBAL_CMP_FN(type1, type2, nm) \ + static int nm##_cmp_BSEARCH_CMP_FN(const void *a_, const void *b_) \ + { \ + type1 const *a = a_; \ + type2 const *b = b_; \ + return nm##_cmp(a,b); \ + } \ + type2 *OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) \ + { \ + return (type2 *)OBJ_bsearch_(key, base, num, sizeof(type2), \ + nm##_cmp_BSEARCH_CMP_FN); \ + } \ + extern void dummy_prototype(void) + +# define OBJ_bsearch(type1,key,type2,base,num,cmp) \ + ((type2 *)OBJ_bsearch_(CHECKED_PTR_OF(type1,key),CHECKED_PTR_OF(type2,base), \ + num,sizeof(type2), \ + ((void)CHECKED_PTR_OF(type1,cmp##_type_1), \ + (void)CHECKED_PTR_OF(type2,cmp##_type_2), \ + cmp##_BSEARCH_CMP_FN))) + +# define OBJ_bsearch_ex(type1,key,type2,base,num,cmp,flags) \ + ((type2 *)OBJ_bsearch_ex_(CHECKED_PTR_OF(type1,key),CHECKED_PTR_OF(type2,base), \ + num,sizeof(type2), \ + ((void)CHECKED_PTR_OF(type1,cmp##_type_1), \ + (void)type_2=CHECKED_PTR_OF(type2,cmp##_type_2), \ + cmp##_BSEARCH_CMP_FN)),flags) + +int OBJ_new_nid(int num); +int OBJ_add_object(const ASN1_OBJECT *obj); +int OBJ_create(const char *oid, const char *sn, const char *ln); +#if OPENSSL_API_COMPAT < 0x10100000L +# define OBJ_cleanup() while(0) continue +#endif +int OBJ_create_objects(BIO *in); + +size_t OBJ_length(const ASN1_OBJECT *obj); +const unsigned char *OBJ_get0_data(const ASN1_OBJECT *obj); + +int OBJ_find_sigid_algs(int signid, int *pdig_nid, int *ppkey_nid); +int OBJ_find_sigid_by_algs(int *psignid, int dig_nid, int pkey_nid); +int OBJ_add_sigid(int signid, int dig_id, int pkey_id); +void OBJ_sigid_free(void); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/objectserr.h b/thrid-party/openssl/openssl/objectserr.h new file mode 100644 index 0000000000..02e166f1ac --- /dev/null +++ b/thrid-party/openssl/openssl/objectserr.h @@ -0,0 +1,42 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OBJERR_H +# define HEADER_OBJERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_OBJ_strings(void); + +/* + * OBJ function codes. + */ +# define OBJ_F_OBJ_ADD_OBJECT 105 +# define OBJ_F_OBJ_ADD_SIGID 107 +# define OBJ_F_OBJ_CREATE 100 +# define OBJ_F_OBJ_DUP 101 +# define OBJ_F_OBJ_NAME_NEW_INDEX 106 +# define OBJ_F_OBJ_NID2LN 102 +# define OBJ_F_OBJ_NID2OBJ 103 +# define OBJ_F_OBJ_NID2SN 104 +# define OBJ_F_OBJ_TXT2OBJ 108 + +/* + * OBJ reason codes. + */ +# define OBJ_R_OID_EXISTS 102 +# define OBJ_R_UNKNOWN_NID 101 + +#endif diff --git a/thrid-party/openssl/openssl/ocsp.h b/thrid-party/openssl/openssl/ocsp.h new file mode 100644 index 0000000000..8582fe1ee1 --- /dev/null +++ b/thrid-party/openssl/openssl/ocsp.h @@ -0,0 +1,352 @@ +/* + * Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OCSP_H +# define HEADER_OCSP_H + +#include + +/* + * These definitions are outside the OPENSSL_NO_OCSP guard because although for + * historical reasons they have OCSP_* names, they can actually be used + * independently of OCSP. E.g. see RFC5280 + */ +/*- + * CRLReason ::= ENUMERATED { + * unspecified (0), + * keyCompromise (1), + * cACompromise (2), + * affiliationChanged (3), + * superseded (4), + * cessationOfOperation (5), + * certificateHold (6), + * removeFromCRL (8) } + */ +# define OCSP_REVOKED_STATUS_NOSTATUS -1 +# define OCSP_REVOKED_STATUS_UNSPECIFIED 0 +# define OCSP_REVOKED_STATUS_KEYCOMPROMISE 1 +# define OCSP_REVOKED_STATUS_CACOMPROMISE 2 +# define OCSP_REVOKED_STATUS_AFFILIATIONCHANGED 3 +# define OCSP_REVOKED_STATUS_SUPERSEDED 4 +# define OCSP_REVOKED_STATUS_CESSATIONOFOPERATION 5 +# define OCSP_REVOKED_STATUS_CERTIFICATEHOLD 6 +# define OCSP_REVOKED_STATUS_REMOVEFROMCRL 8 + + +# ifndef OPENSSL_NO_OCSP + +# include +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Various flags and values */ + +# define OCSP_DEFAULT_NONCE_LENGTH 16 + +# define OCSP_NOCERTS 0x1 +# define OCSP_NOINTERN 0x2 +# define OCSP_NOSIGS 0x4 +# define OCSP_NOCHAIN 0x8 +# define OCSP_NOVERIFY 0x10 +# define OCSP_NOEXPLICIT 0x20 +# define OCSP_NOCASIGN 0x40 +# define OCSP_NODELEGATED 0x80 +# define OCSP_NOCHECKS 0x100 +# define OCSP_TRUSTOTHER 0x200 +# define OCSP_RESPID_KEY 0x400 +# define OCSP_NOTIME 0x800 + +typedef struct ocsp_cert_id_st OCSP_CERTID; + +DEFINE_STACK_OF(OCSP_CERTID) + +typedef struct ocsp_one_request_st OCSP_ONEREQ; + +DEFINE_STACK_OF(OCSP_ONEREQ) + +typedef struct ocsp_req_info_st OCSP_REQINFO; +typedef struct ocsp_signature_st OCSP_SIGNATURE; +typedef struct ocsp_request_st OCSP_REQUEST; + +# define OCSP_RESPONSE_STATUS_SUCCESSFUL 0 +# define OCSP_RESPONSE_STATUS_MALFORMEDREQUEST 1 +# define OCSP_RESPONSE_STATUS_INTERNALERROR 2 +# define OCSP_RESPONSE_STATUS_TRYLATER 3 +# define OCSP_RESPONSE_STATUS_SIGREQUIRED 5 +# define OCSP_RESPONSE_STATUS_UNAUTHORIZED 6 + +typedef struct ocsp_resp_bytes_st OCSP_RESPBYTES; + +# define V_OCSP_RESPID_NAME 0 +# define V_OCSP_RESPID_KEY 1 + +DEFINE_STACK_OF(OCSP_RESPID) + +typedef struct ocsp_revoked_info_st OCSP_REVOKEDINFO; + +# define V_OCSP_CERTSTATUS_GOOD 0 +# define V_OCSP_CERTSTATUS_REVOKED 1 +# define V_OCSP_CERTSTATUS_UNKNOWN 2 + +typedef struct ocsp_cert_status_st OCSP_CERTSTATUS; +typedef struct ocsp_single_response_st OCSP_SINGLERESP; + +DEFINE_STACK_OF(OCSP_SINGLERESP) + +typedef struct ocsp_response_data_st OCSP_RESPDATA; + +typedef struct ocsp_basic_response_st OCSP_BASICRESP; + +typedef struct ocsp_crl_id_st OCSP_CRLID; +typedef struct ocsp_service_locator_st OCSP_SERVICELOC; + +# define PEM_STRING_OCSP_REQUEST "OCSP REQUEST" +# define PEM_STRING_OCSP_RESPONSE "OCSP RESPONSE" + +# define d2i_OCSP_REQUEST_bio(bp,p) ASN1_d2i_bio_of(OCSP_REQUEST,OCSP_REQUEST_new,d2i_OCSP_REQUEST,bp,p) + +# define d2i_OCSP_RESPONSE_bio(bp,p) ASN1_d2i_bio_of(OCSP_RESPONSE,OCSP_RESPONSE_new,d2i_OCSP_RESPONSE,bp,p) + +# define PEM_read_bio_OCSP_REQUEST(bp,x,cb) (OCSP_REQUEST *)PEM_ASN1_read_bio( \ + (char *(*)())d2i_OCSP_REQUEST,PEM_STRING_OCSP_REQUEST, \ + bp,(char **)(x),cb,NULL) + +# define PEM_read_bio_OCSP_RESPONSE(bp,x,cb)(OCSP_RESPONSE *)PEM_ASN1_read_bio(\ + (char *(*)())d2i_OCSP_RESPONSE,PEM_STRING_OCSP_RESPONSE, \ + bp,(char **)(x),cb,NULL) + +# define PEM_write_bio_OCSP_REQUEST(bp,o) \ + PEM_ASN1_write_bio((int (*)())i2d_OCSP_REQUEST,PEM_STRING_OCSP_REQUEST,\ + bp,(char *)(o), NULL,NULL,0,NULL,NULL) + +# define PEM_write_bio_OCSP_RESPONSE(bp,o) \ + PEM_ASN1_write_bio((int (*)())i2d_OCSP_RESPONSE,PEM_STRING_OCSP_RESPONSE,\ + bp,(char *)(o), NULL,NULL,0,NULL,NULL) + +# define i2d_OCSP_RESPONSE_bio(bp,o) ASN1_i2d_bio_of(OCSP_RESPONSE,i2d_OCSP_RESPONSE,bp,o) + +# define i2d_OCSP_REQUEST_bio(bp,o) ASN1_i2d_bio_of(OCSP_REQUEST,i2d_OCSP_REQUEST,bp,o) + +# define ASN1_BIT_STRING_digest(data,type,md,len) \ + ASN1_item_digest(ASN1_ITEM_rptr(ASN1_BIT_STRING),type,data,md,len) + +# define OCSP_CERTSTATUS_dup(cs)\ + (OCSP_CERTSTATUS*)ASN1_dup((int(*)())i2d_OCSP_CERTSTATUS,\ + (char *(*)())d2i_OCSP_CERTSTATUS,(char *)(cs)) + +OCSP_CERTID *OCSP_CERTID_dup(OCSP_CERTID *id); + +OCSP_RESPONSE *OCSP_sendreq_bio(BIO *b, const char *path, OCSP_REQUEST *req); +OCSP_REQ_CTX *OCSP_sendreq_new(BIO *io, const char *path, OCSP_REQUEST *req, + int maxline); +int OCSP_REQ_CTX_nbio(OCSP_REQ_CTX *rctx); +int OCSP_sendreq_nbio(OCSP_RESPONSE **presp, OCSP_REQ_CTX *rctx); +OCSP_REQ_CTX *OCSP_REQ_CTX_new(BIO *io, int maxline); +void OCSP_REQ_CTX_free(OCSP_REQ_CTX *rctx); +void OCSP_set_max_response_length(OCSP_REQ_CTX *rctx, unsigned long len); +int OCSP_REQ_CTX_i2d(OCSP_REQ_CTX *rctx, const ASN1_ITEM *it, + ASN1_VALUE *val); +int OCSP_REQ_CTX_nbio_d2i(OCSP_REQ_CTX *rctx, ASN1_VALUE **pval, + const ASN1_ITEM *it); +BIO *OCSP_REQ_CTX_get0_mem_bio(OCSP_REQ_CTX *rctx); +int OCSP_REQ_CTX_http(OCSP_REQ_CTX *rctx, const char *op, const char *path); +int OCSP_REQ_CTX_set1_req(OCSP_REQ_CTX *rctx, OCSP_REQUEST *req); +int OCSP_REQ_CTX_add1_header(OCSP_REQ_CTX *rctx, + const char *name, const char *value); + +OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *dgst, const X509 *subject, + const X509 *issuer); + +OCSP_CERTID *OCSP_cert_id_new(const EVP_MD *dgst, + const X509_NAME *issuerName, + const ASN1_BIT_STRING *issuerKey, + const ASN1_INTEGER *serialNumber); + +OCSP_ONEREQ *OCSP_request_add0_id(OCSP_REQUEST *req, OCSP_CERTID *cid); + +int OCSP_request_add1_nonce(OCSP_REQUEST *req, unsigned char *val, int len); +int OCSP_basic_add1_nonce(OCSP_BASICRESP *resp, unsigned char *val, int len); +int OCSP_check_nonce(OCSP_REQUEST *req, OCSP_BASICRESP *bs); +int OCSP_copy_nonce(OCSP_BASICRESP *resp, OCSP_REQUEST *req); + +int OCSP_request_set1_name(OCSP_REQUEST *req, X509_NAME *nm); +int OCSP_request_add1_cert(OCSP_REQUEST *req, X509 *cert); + +int OCSP_request_sign(OCSP_REQUEST *req, + X509 *signer, + EVP_PKEY *key, + const EVP_MD *dgst, + STACK_OF(X509) *certs, unsigned long flags); + +int OCSP_response_status(OCSP_RESPONSE *resp); +OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *resp); + +const ASN1_OCTET_STRING *OCSP_resp_get0_signature(const OCSP_BASICRESP *bs); +const X509_ALGOR *OCSP_resp_get0_tbs_sigalg(const OCSP_BASICRESP *bs); +const OCSP_RESPDATA *OCSP_resp_get0_respdata(const OCSP_BASICRESP *bs); +int OCSP_resp_get0_signer(OCSP_BASICRESP *bs, X509 **signer, + STACK_OF(X509) *extra_certs); + +int OCSP_resp_count(OCSP_BASICRESP *bs); +OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *bs, int idx); +const ASN1_GENERALIZEDTIME *OCSP_resp_get0_produced_at(const OCSP_BASICRESP* bs); +const STACK_OF(X509) *OCSP_resp_get0_certs(const OCSP_BASICRESP *bs); +int OCSP_resp_get0_id(const OCSP_BASICRESP *bs, + const ASN1_OCTET_STRING **pid, + const X509_NAME **pname); +int OCSP_resp_get1_id(const OCSP_BASICRESP *bs, + ASN1_OCTET_STRING **pid, + X509_NAME **pname); + +int OCSP_resp_find(OCSP_BASICRESP *bs, OCSP_CERTID *id, int last); +int OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason, + ASN1_GENERALIZEDTIME **revtime, + ASN1_GENERALIZEDTIME **thisupd, + ASN1_GENERALIZEDTIME **nextupd); +int OCSP_resp_find_status(OCSP_BASICRESP *bs, OCSP_CERTID *id, int *status, + int *reason, + ASN1_GENERALIZEDTIME **revtime, + ASN1_GENERALIZEDTIME **thisupd, + ASN1_GENERALIZEDTIME **nextupd); +int OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, + ASN1_GENERALIZEDTIME *nextupd, long sec, long maxsec); + +int OCSP_request_verify(OCSP_REQUEST *req, STACK_OF(X509) *certs, + X509_STORE *store, unsigned long flags); + +int OCSP_parse_url(const char *url, char **phost, char **pport, char **ppath, + int *pssl); + +int OCSP_id_issuer_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b); +int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b); + +int OCSP_request_onereq_count(OCSP_REQUEST *req); +OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *req, int i); +OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *one); +int OCSP_id_get0_info(ASN1_OCTET_STRING **piNameHash, ASN1_OBJECT **pmd, + ASN1_OCTET_STRING **pikeyHash, + ASN1_INTEGER **pserial, OCSP_CERTID *cid); +int OCSP_request_is_signed(OCSP_REQUEST *req); +OCSP_RESPONSE *OCSP_response_create(int status, OCSP_BASICRESP *bs); +OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *rsp, + OCSP_CERTID *cid, + int status, int reason, + ASN1_TIME *revtime, + ASN1_TIME *thisupd, + ASN1_TIME *nextupd); +int OCSP_basic_add1_cert(OCSP_BASICRESP *resp, X509 *cert); +int OCSP_basic_sign(OCSP_BASICRESP *brsp, + X509 *signer, EVP_PKEY *key, const EVP_MD *dgst, + STACK_OF(X509) *certs, unsigned long flags); +int OCSP_basic_sign_ctx(OCSP_BASICRESP *brsp, + X509 *signer, EVP_MD_CTX *ctx, + STACK_OF(X509) *certs, unsigned long flags); +int OCSP_RESPID_set_by_name(OCSP_RESPID *respid, X509 *cert); +int OCSP_RESPID_set_by_key(OCSP_RESPID *respid, X509 *cert); +int OCSP_RESPID_match(OCSP_RESPID *respid, X509 *cert); + +X509_EXTENSION *OCSP_crlID_new(const char *url, long *n, char *tim); + +X509_EXTENSION *OCSP_accept_responses_new(char **oids); + +X509_EXTENSION *OCSP_archive_cutoff_new(char *tim); + +X509_EXTENSION *OCSP_url_svcloc_new(X509_NAME *issuer, const char **urls); + +int OCSP_REQUEST_get_ext_count(OCSP_REQUEST *x); +int OCSP_REQUEST_get_ext_by_NID(OCSP_REQUEST *x, int nid, int lastpos); +int OCSP_REQUEST_get_ext_by_OBJ(OCSP_REQUEST *x, const ASN1_OBJECT *obj, + int lastpos); +int OCSP_REQUEST_get_ext_by_critical(OCSP_REQUEST *x, int crit, int lastpos); +X509_EXTENSION *OCSP_REQUEST_get_ext(OCSP_REQUEST *x, int loc); +X509_EXTENSION *OCSP_REQUEST_delete_ext(OCSP_REQUEST *x, int loc); +void *OCSP_REQUEST_get1_ext_d2i(OCSP_REQUEST *x, int nid, int *crit, + int *idx); +int OCSP_REQUEST_add1_ext_i2d(OCSP_REQUEST *x, int nid, void *value, int crit, + unsigned long flags); +int OCSP_REQUEST_add_ext(OCSP_REQUEST *x, X509_EXTENSION *ex, int loc); + +int OCSP_ONEREQ_get_ext_count(OCSP_ONEREQ *x); +int OCSP_ONEREQ_get_ext_by_NID(OCSP_ONEREQ *x, int nid, int lastpos); +int OCSP_ONEREQ_get_ext_by_OBJ(OCSP_ONEREQ *x, const ASN1_OBJECT *obj, int lastpos); +int OCSP_ONEREQ_get_ext_by_critical(OCSP_ONEREQ *x, int crit, int lastpos); +X509_EXTENSION *OCSP_ONEREQ_get_ext(OCSP_ONEREQ *x, int loc); +X509_EXTENSION *OCSP_ONEREQ_delete_ext(OCSP_ONEREQ *x, int loc); +void *OCSP_ONEREQ_get1_ext_d2i(OCSP_ONEREQ *x, int nid, int *crit, int *idx); +int OCSP_ONEREQ_add1_ext_i2d(OCSP_ONEREQ *x, int nid, void *value, int crit, + unsigned long flags); +int OCSP_ONEREQ_add_ext(OCSP_ONEREQ *x, X509_EXTENSION *ex, int loc); + +int OCSP_BASICRESP_get_ext_count(OCSP_BASICRESP *x); +int OCSP_BASICRESP_get_ext_by_NID(OCSP_BASICRESP *x, int nid, int lastpos); +int OCSP_BASICRESP_get_ext_by_OBJ(OCSP_BASICRESP *x, const ASN1_OBJECT *obj, + int lastpos); +int OCSP_BASICRESP_get_ext_by_critical(OCSP_BASICRESP *x, int crit, + int lastpos); +X509_EXTENSION *OCSP_BASICRESP_get_ext(OCSP_BASICRESP *x, int loc); +X509_EXTENSION *OCSP_BASICRESP_delete_ext(OCSP_BASICRESP *x, int loc); +void *OCSP_BASICRESP_get1_ext_d2i(OCSP_BASICRESP *x, int nid, int *crit, + int *idx); +int OCSP_BASICRESP_add1_ext_i2d(OCSP_BASICRESP *x, int nid, void *value, + int crit, unsigned long flags); +int OCSP_BASICRESP_add_ext(OCSP_BASICRESP *x, X509_EXTENSION *ex, int loc); + +int OCSP_SINGLERESP_get_ext_count(OCSP_SINGLERESP *x); +int OCSP_SINGLERESP_get_ext_by_NID(OCSP_SINGLERESP *x, int nid, int lastpos); +int OCSP_SINGLERESP_get_ext_by_OBJ(OCSP_SINGLERESP *x, const ASN1_OBJECT *obj, + int lastpos); +int OCSP_SINGLERESP_get_ext_by_critical(OCSP_SINGLERESP *x, int crit, + int lastpos); +X509_EXTENSION *OCSP_SINGLERESP_get_ext(OCSP_SINGLERESP *x, int loc); +X509_EXTENSION *OCSP_SINGLERESP_delete_ext(OCSP_SINGLERESP *x, int loc); +void *OCSP_SINGLERESP_get1_ext_d2i(OCSP_SINGLERESP *x, int nid, int *crit, + int *idx); +int OCSP_SINGLERESP_add1_ext_i2d(OCSP_SINGLERESP *x, int nid, void *value, + int crit, unsigned long flags); +int OCSP_SINGLERESP_add_ext(OCSP_SINGLERESP *x, X509_EXTENSION *ex, int loc); +const OCSP_CERTID *OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *x); + +DECLARE_ASN1_FUNCTIONS(OCSP_SINGLERESP) +DECLARE_ASN1_FUNCTIONS(OCSP_CERTSTATUS) +DECLARE_ASN1_FUNCTIONS(OCSP_REVOKEDINFO) +DECLARE_ASN1_FUNCTIONS(OCSP_BASICRESP) +DECLARE_ASN1_FUNCTIONS(OCSP_RESPDATA) +DECLARE_ASN1_FUNCTIONS(OCSP_RESPID) +DECLARE_ASN1_FUNCTIONS(OCSP_RESPONSE) +DECLARE_ASN1_FUNCTIONS(OCSP_RESPBYTES) +DECLARE_ASN1_FUNCTIONS(OCSP_ONEREQ) +DECLARE_ASN1_FUNCTIONS(OCSP_CERTID) +DECLARE_ASN1_FUNCTIONS(OCSP_REQUEST) +DECLARE_ASN1_FUNCTIONS(OCSP_SIGNATURE) +DECLARE_ASN1_FUNCTIONS(OCSP_REQINFO) +DECLARE_ASN1_FUNCTIONS(OCSP_CRLID) +DECLARE_ASN1_FUNCTIONS(OCSP_SERVICELOC) + +const char *OCSP_response_status_str(long s); +const char *OCSP_cert_status_str(long s); +const char *OCSP_crl_reason_str(long s); + +int OCSP_REQUEST_print(BIO *bp, OCSP_REQUEST *a, unsigned long flags); +int OCSP_RESPONSE_print(BIO *bp, OCSP_RESPONSE *o, unsigned long flags); + +int OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, + X509_STORE *st, unsigned long flags); + + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/ocsperr.h b/thrid-party/openssl/openssl/ocsperr.h new file mode 100644 index 0000000000..8dd9e01a17 --- /dev/null +++ b/thrid-party/openssl/openssl/ocsperr.h @@ -0,0 +1,78 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OCSPERR_H +# define HEADER_OCSPERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_OCSP + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_OCSP_strings(void); + +/* + * OCSP function codes. + */ +# define OCSP_F_D2I_OCSP_NONCE 102 +# define OCSP_F_OCSP_BASIC_ADD1_STATUS 103 +# define OCSP_F_OCSP_BASIC_SIGN 104 +# define OCSP_F_OCSP_BASIC_SIGN_CTX 119 +# define OCSP_F_OCSP_BASIC_VERIFY 105 +# define OCSP_F_OCSP_CERT_ID_NEW 101 +# define OCSP_F_OCSP_CHECK_DELEGATED 106 +# define OCSP_F_OCSP_CHECK_IDS 107 +# define OCSP_F_OCSP_CHECK_ISSUER 108 +# define OCSP_F_OCSP_CHECK_VALIDITY 115 +# define OCSP_F_OCSP_MATCH_ISSUERID 109 +# define OCSP_F_OCSP_PARSE_URL 114 +# define OCSP_F_OCSP_REQUEST_SIGN 110 +# define OCSP_F_OCSP_REQUEST_VERIFY 116 +# define OCSP_F_OCSP_RESPONSE_GET1_BASIC 111 +# define OCSP_F_PARSE_HTTP_LINE1 118 + +/* + * OCSP reason codes. + */ +# define OCSP_R_CERTIFICATE_VERIFY_ERROR 101 +# define OCSP_R_DIGEST_ERR 102 +# define OCSP_R_ERROR_IN_NEXTUPDATE_FIELD 122 +# define OCSP_R_ERROR_IN_THISUPDATE_FIELD 123 +# define OCSP_R_ERROR_PARSING_URL 121 +# define OCSP_R_MISSING_OCSPSIGNING_USAGE 103 +# define OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE 124 +# define OCSP_R_NOT_BASIC_RESPONSE 104 +# define OCSP_R_NO_CERTIFICATES_IN_CHAIN 105 +# define OCSP_R_NO_RESPONSE_DATA 108 +# define OCSP_R_NO_REVOKED_TIME 109 +# define OCSP_R_NO_SIGNER_KEY 130 +# define OCSP_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 110 +# define OCSP_R_REQUEST_NOT_SIGNED 128 +# define OCSP_R_RESPONSE_CONTAINS_NO_REVOCATION_DATA 111 +# define OCSP_R_ROOT_CA_NOT_TRUSTED 112 +# define OCSP_R_SERVER_RESPONSE_ERROR 114 +# define OCSP_R_SERVER_RESPONSE_PARSE_ERROR 115 +# define OCSP_R_SIGNATURE_FAILURE 117 +# define OCSP_R_SIGNER_CERTIFICATE_NOT_FOUND 118 +# define OCSP_R_STATUS_EXPIRED 125 +# define OCSP_R_STATUS_NOT_YET_VALID 126 +# define OCSP_R_STATUS_TOO_OLD 127 +# define OCSP_R_UNKNOWN_MESSAGE_DIGEST 119 +# define OCSP_R_UNKNOWN_NID 120 +# define OCSP_R_UNSUPPORTED_REQUESTORNAME_TYPE 129 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/opensslconf.h b/thrid-party/openssl/openssl/opensslconf.h new file mode 100644 index 0000000000..55ebf8c603 --- /dev/null +++ b/thrid-party/openssl/openssl/opensslconf.h @@ -0,0 +1,195 @@ +/* + * WARNING: do not edit! + * Generated by Makefile from include/openssl/opensslconf.h.in + * + * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef OPENSSL_ALGORITHM_DEFINES +# error OPENSSL_ALGORITHM_DEFINES no longer supported +#endif + +/* + * OpenSSL was configured with the following options: + */ + +#ifndef OPENSSL_SYS_MACOSX +# define OPENSSL_SYS_MACOSX 1 +#endif +#ifndef OPENSSL_NO_MD2 +# define OPENSSL_NO_MD2 +#endif +#ifndef OPENSSL_NO_RC5 +# define OPENSSL_NO_RC5 +#endif +#ifndef OPENSSL_THREADS +# define OPENSSL_THREADS +#endif +#ifndef OPENSSL_RAND_SEED_OS +# define OPENSSL_RAND_SEED_OS +#endif +#ifndef OPENSSL_NO_ASAN +# define OPENSSL_NO_ASAN +#endif +#ifndef OPENSSL_NO_CRYPTO_MDEBUG +# define OPENSSL_NO_CRYPTO_MDEBUG +#endif +#ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE +# define OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE +#endif +#ifndef OPENSSL_NO_DEVCRYPTOENG +# define OPENSSL_NO_DEVCRYPTOENG +#endif +#ifndef OPENSSL_NO_EGD +# define OPENSSL_NO_EGD +#endif +#ifndef OPENSSL_NO_EXTERNAL_TESTS +# define OPENSSL_NO_EXTERNAL_TESTS +#endif +#ifndef OPENSSL_NO_FUZZ_AFL +# define OPENSSL_NO_FUZZ_AFL +#endif +#ifndef OPENSSL_NO_FUZZ_LIBFUZZER +# define OPENSSL_NO_FUZZ_LIBFUZZER +#endif +#ifndef OPENSSL_NO_HEARTBEATS +# define OPENSSL_NO_HEARTBEATS +#endif +#ifndef OPENSSL_NO_MSAN +# define OPENSSL_NO_MSAN +#endif +#ifndef OPENSSL_NO_SCTP +# define OPENSSL_NO_SCTP +#endif +#ifndef OPENSSL_NO_SSL_TRACE +# define OPENSSL_NO_SSL_TRACE +#endif +#ifndef OPENSSL_NO_SSL3 +# define OPENSSL_NO_SSL3 +#endif +#ifndef OPENSSL_NO_SSL3_METHOD +# define OPENSSL_NO_SSL3_METHOD +#endif +#ifndef OPENSSL_NO_UBSAN +# define OPENSSL_NO_UBSAN +#endif +#ifndef OPENSSL_NO_UNIT_TEST +# define OPENSSL_NO_UNIT_TEST +#endif +#ifndef OPENSSL_NO_WEAK_SSL_CIPHERS +# define OPENSSL_NO_WEAK_SSL_CIPHERS +#endif +#ifndef OPENSSL_NO_STATIC_ENGINE +# define OPENSSL_NO_STATIC_ENGINE +#endif +#ifndef OPENSSL_NO_AFALGENG +# define OPENSSL_NO_AFALGENG +#endif + + +/* + * Sometimes OPENSSSL_NO_xxx ends up with an empty file and some compilers + * don't like that. This will hopefully silence them. + */ +#define NON_EMPTY_TRANSLATION_UNIT static void *dummy = &dummy; + +/* + * Applications should use -DOPENSSL_API_COMPAT= to suppress the + * declarations of functions deprecated in or before . Otherwise, they + * still won't see them if the library has been built to disable deprecated + * functions. + */ +#ifndef DECLARE_DEPRECATED +# define DECLARE_DEPRECATED(f) f; +# ifdef __GNUC__ +# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0) +# undef DECLARE_DEPRECATED +# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated)); +# endif +# endif +#endif + +#ifndef OPENSSL_FILE +# ifdef OPENSSL_NO_FILENAMES +# define OPENSSL_FILE "" +# define OPENSSL_LINE 0 +# else +# define OPENSSL_FILE __FILE__ +# define OPENSSL_LINE __LINE__ +# endif +#endif + +#ifndef OPENSSL_MIN_API +# define OPENSSL_MIN_API 0 +#endif + +#if !defined(OPENSSL_API_COMPAT) || OPENSSL_API_COMPAT < OPENSSL_MIN_API +# undef OPENSSL_API_COMPAT +# define OPENSSL_API_COMPAT OPENSSL_MIN_API +#endif + +/* + * Do not deprecate things to be deprecated in version 1.2.0 before the + * OpenSSL version number matches. + */ +#if OPENSSL_VERSION_NUMBER < 0x10200000L +# define DEPRECATEDIN_1_2_0(f) f; +#elif OPENSSL_API_COMPAT < 0x10200000L +# define DEPRECATEDIN_1_2_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_2_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x10100000L +# define DEPRECATEDIN_1_1_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_1_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x10000000L +# define DEPRECATEDIN_1_0_0(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_1_0_0(f) +#endif + +#if OPENSSL_API_COMPAT < 0x00908000L +# define DEPRECATEDIN_0_9_8(f) DECLARE_DEPRECATED(f) +#else +# define DEPRECATEDIN_0_9_8(f) +#endif + +/* Generate 80386 code? */ +#undef I386_ONLY + +#undef OPENSSL_UNISTD +#define OPENSSL_UNISTD + +#undef OPENSSL_EXPORT_VAR_AS_FUNCTION + +/* + * The following are cipher-specific, but are part of the public API. + */ +#if !defined(OPENSSL_SYS_UEFI) +# undef BN_LLONG +/* Only one for the following should be defined */ +# define SIXTY_FOUR_BIT_LONG +# undef SIXTY_FOUR_BIT +# undef THIRTY_TWO_BIT +#endif + +#define RC4_INT unsigned int + +#ifdef __cplusplus +} +#endif diff --git a/thrid-party/openssl/openssl/opensslv.h b/thrid-party/openssl/openssl/opensslv.h new file mode 100644 index 0000000000..c28e632c44 --- /dev/null +++ b/thrid-party/openssl/openssl/opensslv.h @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OPENSSLV_H +# define HEADER_OPENSSLV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*- + * Numeric release version identifier: + * MNNFFPPS: major minor fix patch status + * The status nibble has one of the values 0 for development, 1 to e for betas + * 1 to 14, and f for release. The patch level is exactly that. + * For example: + * 0.9.3-dev 0x00903000 + * 0.9.3-beta1 0x00903001 + * 0.9.3-beta2-dev 0x00903002 + * 0.9.3-beta2 0x00903002 (same as ...beta2-dev) + * 0.9.3 0x0090300f + * 0.9.3a 0x0090301f + * 0.9.4 0x0090400f + * 1.2.3z 0x102031af + * + * For continuity reasons (because 0.9.5 is already out, and is coded + * 0x00905100), between 0.9.5 and 0.9.6 the coding of the patch level + * part is slightly different, by setting the highest bit. This means + * that 0.9.5a looks like this: 0x0090581f. At 0.9.6, we can start + * with 0x0090600S... + * + * (Prior to 0.9.3-dev a different scheme was used: 0.9.2b is 0x0922.) + * (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for + * major minor fix final patch/beta) + */ +# define OPENSSL_VERSION_NUMBER 0x1010104fL +# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1d 10 Sep 2019" + +/*- + * The macros below are to be used for shared library (.so, .dll, ...) + * versioning. That kind of versioning works a bit differently between + * operating systems. The most usual scheme is to set a major and a minor + * number, and have the runtime loader check that the major number is equal + * to what it was at application link time, while the minor number has to + * be greater or equal to what it was at application link time. With this + * scheme, the version number is usually part of the file name, like this: + * + * libcrypto.so.0.9 + * + * Some unixen also make a softlink with the major version number only: + * + * libcrypto.so.0 + * + * On Tru64 and IRIX 6.x it works a little bit differently. There, the + * shared library version is stored in the file, and is actually a series + * of versions, separated by colons. The rightmost version present in the + * library when linking an application is stored in the application to be + * matched at run time. When the application is run, a check is done to + * see if the library version stored in the application matches any of the + * versions in the version string of the library itself. + * This version string can be constructed in any way, depending on what + * kind of matching is desired. However, to implement the same scheme as + * the one used in the other unixen, all compatible versions, from lowest + * to highest, should be part of the string. Consecutive builds would + * give the following versions strings: + * + * 3.0 + * 3.0:3.1 + * 3.0:3.1:3.2 + * 4.0 + * 4.0:4.1 + * + * Notice how version 4 is completely incompatible with version, and + * therefore give the breach you can see. + * + * There may be other schemes as well that I haven't yet discovered. + * + * So, here's the way it works here: first of all, the library version + * number doesn't need at all to match the overall OpenSSL version. + * However, it's nice and more understandable if it actually does. + * The current library version is stored in the macro SHLIB_VERSION_NUMBER, + * which is just a piece of text in the format "M.m.e" (Major, minor, edit). + * For the sake of Tru64, IRIX, and any other OS that behaves in similar ways, + * we need to keep a history of version numbers, which is done in the + * macro SHLIB_VERSION_HISTORY. The numbers are separated by colons and + * should only keep the versions that are binary compatible with the current. + */ +# define SHLIB_VERSION_HISTORY "" +# define SHLIB_VERSION_NUMBER "1.1" + + +#ifdef __cplusplus +} +#endif +#endif /* HEADER_OPENSSLV_H */ diff --git a/thrid-party/openssl/openssl/ossl_typ.h b/thrid-party/openssl/openssl/ossl_typ.h new file mode 100644 index 0000000000..7993ca28f3 --- /dev/null +++ b/thrid-party/openssl/openssl/ossl_typ.h @@ -0,0 +1,196 @@ +/* + * Copyright 2001-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OPENSSL_TYPES_H +# define HEADER_OPENSSL_TYPES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +# include + +# ifdef NO_ASN1_TYPEDEFS +# define ASN1_INTEGER ASN1_STRING +# define ASN1_ENUMERATED ASN1_STRING +# define ASN1_BIT_STRING ASN1_STRING +# define ASN1_OCTET_STRING ASN1_STRING +# define ASN1_PRINTABLESTRING ASN1_STRING +# define ASN1_T61STRING ASN1_STRING +# define ASN1_IA5STRING ASN1_STRING +# define ASN1_UTCTIME ASN1_STRING +# define ASN1_GENERALIZEDTIME ASN1_STRING +# define ASN1_TIME ASN1_STRING +# define ASN1_GENERALSTRING ASN1_STRING +# define ASN1_UNIVERSALSTRING ASN1_STRING +# define ASN1_BMPSTRING ASN1_STRING +# define ASN1_VISIBLESTRING ASN1_STRING +# define ASN1_UTF8STRING ASN1_STRING +# define ASN1_BOOLEAN int +# define ASN1_NULL int +# else +typedef struct asn1_string_st ASN1_INTEGER; +typedef struct asn1_string_st ASN1_ENUMERATED; +typedef struct asn1_string_st ASN1_BIT_STRING; +typedef struct asn1_string_st ASN1_OCTET_STRING; +typedef struct asn1_string_st ASN1_PRINTABLESTRING; +typedef struct asn1_string_st ASN1_T61STRING; +typedef struct asn1_string_st ASN1_IA5STRING; +typedef struct asn1_string_st ASN1_GENERALSTRING; +typedef struct asn1_string_st ASN1_UNIVERSALSTRING; +typedef struct asn1_string_st ASN1_BMPSTRING; +typedef struct asn1_string_st ASN1_UTCTIME; +typedef struct asn1_string_st ASN1_TIME; +typedef struct asn1_string_st ASN1_GENERALIZEDTIME; +typedef struct asn1_string_st ASN1_VISIBLESTRING; +typedef struct asn1_string_st ASN1_UTF8STRING; +typedef struct asn1_string_st ASN1_STRING; +typedef int ASN1_BOOLEAN; +typedef int ASN1_NULL; +# endif + +typedef struct asn1_object_st ASN1_OBJECT; + +typedef struct ASN1_ITEM_st ASN1_ITEM; +typedef struct asn1_pctx_st ASN1_PCTX; +typedef struct asn1_sctx_st ASN1_SCTX; + +# ifdef _WIN32 +# undef X509_NAME +# undef X509_EXTENSIONS +# undef PKCS7_ISSUER_AND_SERIAL +# undef PKCS7_SIGNER_INFO +# undef OCSP_REQUEST +# undef OCSP_RESPONSE +# endif + +# ifdef BIGNUM +# undef BIGNUM +# endif +struct dane_st; +typedef struct bio_st BIO; +typedef struct bignum_st BIGNUM; +typedef struct bignum_ctx BN_CTX; +typedef struct bn_blinding_st BN_BLINDING; +typedef struct bn_mont_ctx_st BN_MONT_CTX; +typedef struct bn_recp_ctx_st BN_RECP_CTX; +typedef struct bn_gencb_st BN_GENCB; + +typedef struct buf_mem_st BUF_MEM; + +typedef struct evp_cipher_st EVP_CIPHER; +typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX; +typedef struct evp_md_st EVP_MD; +typedef struct evp_md_ctx_st EVP_MD_CTX; +typedef struct evp_pkey_st EVP_PKEY; + +typedef struct evp_pkey_asn1_method_st EVP_PKEY_ASN1_METHOD; + +typedef struct evp_pkey_method_st EVP_PKEY_METHOD; +typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; + +typedef struct evp_Encode_Ctx_st EVP_ENCODE_CTX; + +typedef struct hmac_ctx_st HMAC_CTX; + +typedef struct dh_st DH; +typedef struct dh_method DH_METHOD; + +typedef struct dsa_st DSA; +typedef struct dsa_method DSA_METHOD; + +typedef struct rsa_st RSA; +typedef struct rsa_meth_st RSA_METHOD; + +typedef struct ec_key_st EC_KEY; +typedef struct ec_key_method_st EC_KEY_METHOD; + +typedef struct rand_meth_st RAND_METHOD; +typedef struct rand_drbg_st RAND_DRBG; + +typedef struct ssl_dane_st SSL_DANE; +typedef struct x509_st X509; +typedef struct X509_algor_st X509_ALGOR; +typedef struct X509_crl_st X509_CRL; +typedef struct x509_crl_method_st X509_CRL_METHOD; +typedef struct x509_revoked_st X509_REVOKED; +typedef struct X509_name_st X509_NAME; +typedef struct X509_pubkey_st X509_PUBKEY; +typedef struct x509_store_st X509_STORE; +typedef struct x509_store_ctx_st X509_STORE_CTX; + +typedef struct x509_object_st X509_OBJECT; +typedef struct x509_lookup_st X509_LOOKUP; +typedef struct x509_lookup_method_st X509_LOOKUP_METHOD; +typedef struct X509_VERIFY_PARAM_st X509_VERIFY_PARAM; + +typedef struct x509_sig_info_st X509_SIG_INFO; + +typedef struct pkcs8_priv_key_info_st PKCS8_PRIV_KEY_INFO; + +typedef struct v3_ext_ctx X509V3_CTX; +typedef struct conf_st CONF; +typedef struct ossl_init_settings_st OPENSSL_INIT_SETTINGS; + +typedef struct ui_st UI; +typedef struct ui_method_st UI_METHOD; + +typedef struct engine_st ENGINE; +typedef struct ssl_st SSL; +typedef struct ssl_ctx_st SSL_CTX; + +typedef struct comp_ctx_st COMP_CTX; +typedef struct comp_method_st COMP_METHOD; + +typedef struct X509_POLICY_NODE_st X509_POLICY_NODE; +typedef struct X509_POLICY_LEVEL_st X509_POLICY_LEVEL; +typedef struct X509_POLICY_TREE_st X509_POLICY_TREE; +typedef struct X509_POLICY_CACHE_st X509_POLICY_CACHE; + +typedef struct AUTHORITY_KEYID_st AUTHORITY_KEYID; +typedef struct DIST_POINT_st DIST_POINT; +typedef struct ISSUING_DIST_POINT_st ISSUING_DIST_POINT; +typedef struct NAME_CONSTRAINTS_st NAME_CONSTRAINTS; + +typedef struct crypto_ex_data_st CRYPTO_EX_DATA; + +typedef struct ocsp_req_ctx_st OCSP_REQ_CTX; +typedef struct ocsp_response_st OCSP_RESPONSE; +typedef struct ocsp_responder_id_st OCSP_RESPID; + +typedef struct sct_st SCT; +typedef struct sct_ctx_st SCT_CTX; +typedef struct ctlog_st CTLOG; +typedef struct ctlog_store_st CTLOG_STORE; +typedef struct ct_policy_eval_ctx_st CT_POLICY_EVAL_CTX; + +typedef struct ossl_store_info_st OSSL_STORE_INFO; +typedef struct ossl_store_search_st OSSL_STORE_SEARCH; + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && \ + defined(INTMAX_MAX) && defined(UINTMAX_MAX) +typedef intmax_t ossl_intmax_t; +typedef uintmax_t ossl_uintmax_t; +#else +/* + * Not long long, because the C-library can only be expected to provide + * strtoll(), strtoull() at the same time as intmax_t and strtoimax(), + * strtoumax(). Since we use these for parsing arguments, we need the + * conversion functions, not just the sizes. + */ +typedef long ossl_intmax_t; +typedef unsigned long ossl_uintmax_t; +#endif + +#ifdef __cplusplus +} +#endif +#endif /* def HEADER_OPENSSL_TYPES_H */ diff --git a/thrid-party/openssl/openssl/pem.h b/thrid-party/openssl/openssl/pem.h new file mode 100644 index 0000000000..2ef5b5d04c --- /dev/null +++ b/thrid-party/openssl/openssl/pem.h @@ -0,0 +1,378 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PEM_H +# define HEADER_PEM_H + +# include +# include +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# define PEM_BUFSIZE 1024 + +# define PEM_STRING_X509_OLD "X509 CERTIFICATE" +# define PEM_STRING_X509 "CERTIFICATE" +# define PEM_STRING_X509_TRUSTED "TRUSTED CERTIFICATE" +# define PEM_STRING_X509_REQ_OLD "NEW CERTIFICATE REQUEST" +# define PEM_STRING_X509_REQ "CERTIFICATE REQUEST" +# define PEM_STRING_X509_CRL "X509 CRL" +# define PEM_STRING_EVP_PKEY "ANY PRIVATE KEY" +# define PEM_STRING_PUBLIC "PUBLIC KEY" +# define PEM_STRING_RSA "RSA PRIVATE KEY" +# define PEM_STRING_RSA_PUBLIC "RSA PUBLIC KEY" +# define PEM_STRING_DSA "DSA PRIVATE KEY" +# define PEM_STRING_DSA_PUBLIC "DSA PUBLIC KEY" +# define PEM_STRING_PKCS7 "PKCS7" +# define PEM_STRING_PKCS7_SIGNED "PKCS #7 SIGNED DATA" +# define PEM_STRING_PKCS8 "ENCRYPTED PRIVATE KEY" +# define PEM_STRING_PKCS8INF "PRIVATE KEY" +# define PEM_STRING_DHPARAMS "DH PARAMETERS" +# define PEM_STRING_DHXPARAMS "X9.42 DH PARAMETERS" +# define PEM_STRING_SSL_SESSION "SSL SESSION PARAMETERS" +# define PEM_STRING_DSAPARAMS "DSA PARAMETERS" +# define PEM_STRING_ECDSA_PUBLIC "ECDSA PUBLIC KEY" +# define PEM_STRING_ECPARAMETERS "EC PARAMETERS" +# define PEM_STRING_ECPRIVATEKEY "EC PRIVATE KEY" +# define PEM_STRING_PARAMETERS "PARAMETERS" +# define PEM_STRING_CMS "CMS" + +# define PEM_TYPE_ENCRYPTED 10 +# define PEM_TYPE_MIC_ONLY 20 +# define PEM_TYPE_MIC_CLEAR 30 +# define PEM_TYPE_CLEAR 40 + +/* + * These macros make the PEM_read/PEM_write functions easier to maintain and + * write. Now they are all implemented with either: IMPLEMENT_PEM_rw(...) or + * IMPLEMENT_PEM_rw_cb(...) + */ + +# ifdef OPENSSL_NO_STDIO + +# define IMPLEMENT_PEM_read_fp(name, type, str, asn1) /**/ +# define IMPLEMENT_PEM_write_fp(name, type, str, asn1) /**/ +# define IMPLEMENT_PEM_write_fp_const(name, type, str, asn1) /**/ +# define IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1) /**/ +# define IMPLEMENT_PEM_write_cb_fp_const(name, type, str, asn1) /**/ +# else + +# define IMPLEMENT_PEM_read_fp(name, type, str, asn1) \ +type *PEM_read_##name(FILE *fp, type **x, pem_password_cb *cb, void *u)\ +{ \ +return PEM_ASN1_read((d2i_of_void *)d2i_##asn1, str,fp,(void **)x,cb,u); \ +} + +# define IMPLEMENT_PEM_write_fp(name, type, str, asn1) \ +int PEM_write_##name(FILE *fp, type *x) \ +{ \ +return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,x,NULL,NULL,0,NULL,NULL); \ +} + +# define IMPLEMENT_PEM_write_fp_const(name, type, str, asn1) \ +int PEM_write_##name(FILE *fp, const type *x) \ +{ \ +return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,(void *)x,NULL,NULL,0,NULL,NULL); \ +} + +# define IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1) \ +int PEM_write_##name(FILE *fp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, \ + void *u) \ + { \ + return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,x,enc,kstr,klen,cb,u); \ + } + +# define IMPLEMENT_PEM_write_cb_fp_const(name, type, str, asn1) \ +int PEM_write_##name(FILE *fp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, \ + void *u) \ + { \ + return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,x,enc,kstr,klen,cb,u); \ + } + +# endif + +# define IMPLEMENT_PEM_read_bio(name, type, str, asn1) \ +type *PEM_read_bio_##name(BIO *bp, type **x, pem_password_cb *cb, void *u)\ +{ \ +return PEM_ASN1_read_bio((d2i_of_void *)d2i_##asn1, str,bp,(void **)x,cb,u); \ +} + +# define IMPLEMENT_PEM_write_bio(name, type, str, asn1) \ +int PEM_write_bio_##name(BIO *bp, type *x) \ +{ \ +return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,x,NULL,NULL,0,NULL,NULL); \ +} + +# define IMPLEMENT_PEM_write_bio_const(name, type, str, asn1) \ +int PEM_write_bio_##name(BIO *bp, const type *x) \ +{ \ +return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,(void *)x,NULL,NULL,0,NULL,NULL); \ +} + +# define IMPLEMENT_PEM_write_cb_bio(name, type, str, asn1) \ +int PEM_write_bio_##name(BIO *bp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, void *u) \ + { \ + return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,x,enc,kstr,klen,cb,u); \ + } + +# define IMPLEMENT_PEM_write_cb_bio_const(name, type, str, asn1) \ +int PEM_write_bio_##name(BIO *bp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, void *u) \ + { \ + return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,(void *)x,enc,kstr,klen,cb,u); \ + } + +# define IMPLEMENT_PEM_write(name, type, str, asn1) \ + IMPLEMENT_PEM_write_bio(name, type, str, asn1) \ + IMPLEMENT_PEM_write_fp(name, type, str, asn1) + +# define IMPLEMENT_PEM_write_const(name, type, str, asn1) \ + IMPLEMENT_PEM_write_bio_const(name, type, str, asn1) \ + IMPLEMENT_PEM_write_fp_const(name, type, str, asn1) + +# define IMPLEMENT_PEM_write_cb(name, type, str, asn1) \ + IMPLEMENT_PEM_write_cb_bio(name, type, str, asn1) \ + IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1) + +# define IMPLEMENT_PEM_write_cb_const(name, type, str, asn1) \ + IMPLEMENT_PEM_write_cb_bio_const(name, type, str, asn1) \ + IMPLEMENT_PEM_write_cb_fp_const(name, type, str, asn1) + +# define IMPLEMENT_PEM_read(name, type, str, asn1) \ + IMPLEMENT_PEM_read_bio(name, type, str, asn1) \ + IMPLEMENT_PEM_read_fp(name, type, str, asn1) + +# define IMPLEMENT_PEM_rw(name, type, str, asn1) \ + IMPLEMENT_PEM_read(name, type, str, asn1) \ + IMPLEMENT_PEM_write(name, type, str, asn1) + +# define IMPLEMENT_PEM_rw_const(name, type, str, asn1) \ + IMPLEMENT_PEM_read(name, type, str, asn1) \ + IMPLEMENT_PEM_write_const(name, type, str, asn1) + +# define IMPLEMENT_PEM_rw_cb(name, type, str, asn1) \ + IMPLEMENT_PEM_read(name, type, str, asn1) \ + IMPLEMENT_PEM_write_cb(name, type, str, asn1) + +/* These are the same except they are for the declarations */ + +# if defined(OPENSSL_NO_STDIO) + +# define DECLARE_PEM_read_fp(name, type) /**/ +# define DECLARE_PEM_write_fp(name, type) /**/ +# define DECLARE_PEM_write_fp_const(name, type) /**/ +# define DECLARE_PEM_write_cb_fp(name, type) /**/ +# else + +# define DECLARE_PEM_read_fp(name, type) \ + type *PEM_read_##name(FILE *fp, type **x, pem_password_cb *cb, void *u); + +# define DECLARE_PEM_write_fp(name, type) \ + int PEM_write_##name(FILE *fp, type *x); + +# define DECLARE_PEM_write_fp_const(name, type) \ + int PEM_write_##name(FILE *fp, const type *x); + +# define DECLARE_PEM_write_cb_fp(name, type) \ + int PEM_write_##name(FILE *fp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, void *u); + +# endif + +# define DECLARE_PEM_read_bio(name, type) \ + type *PEM_read_bio_##name(BIO *bp, type **x, pem_password_cb *cb, void *u); + +# define DECLARE_PEM_write_bio(name, type) \ + int PEM_write_bio_##name(BIO *bp, type *x); + +# define DECLARE_PEM_write_bio_const(name, type) \ + int PEM_write_bio_##name(BIO *bp, const type *x); + +# define DECLARE_PEM_write_cb_bio(name, type) \ + int PEM_write_bio_##name(BIO *bp, type *x, const EVP_CIPHER *enc, \ + unsigned char *kstr, int klen, pem_password_cb *cb, void *u); + +# define DECLARE_PEM_write(name, type) \ + DECLARE_PEM_write_bio(name, type) \ + DECLARE_PEM_write_fp(name, type) +# define DECLARE_PEM_write_const(name, type) \ + DECLARE_PEM_write_bio_const(name, type) \ + DECLARE_PEM_write_fp_const(name, type) +# define DECLARE_PEM_write_cb(name, type) \ + DECLARE_PEM_write_cb_bio(name, type) \ + DECLARE_PEM_write_cb_fp(name, type) +# define DECLARE_PEM_read(name, type) \ + DECLARE_PEM_read_bio(name, type) \ + DECLARE_PEM_read_fp(name, type) +# define DECLARE_PEM_rw(name, type) \ + DECLARE_PEM_read(name, type) \ + DECLARE_PEM_write(name, type) +# define DECLARE_PEM_rw_const(name, type) \ + DECLARE_PEM_read(name, type) \ + DECLARE_PEM_write_const(name, type) +# define DECLARE_PEM_rw_cb(name, type) \ + DECLARE_PEM_read(name, type) \ + DECLARE_PEM_write_cb(name, type) +typedef int pem_password_cb (char *buf, int size, int rwflag, void *userdata); + +int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher); +int PEM_do_header(EVP_CIPHER_INFO *cipher, unsigned char *data, long *len, + pem_password_cb *callback, void *u); + +int PEM_read_bio(BIO *bp, char **name, char **header, + unsigned char **data, long *len); +# define PEM_FLAG_SECURE 0x1 +# define PEM_FLAG_EAY_COMPATIBLE 0x2 +# define PEM_FLAG_ONLY_B64 0x4 +int PEM_read_bio_ex(BIO *bp, char **name, char **header, + unsigned char **data, long *len, unsigned int flags); +int PEM_bytes_read_bio_secmem(unsigned char **pdata, long *plen, char **pnm, + const char *name, BIO *bp, pem_password_cb *cb, + void *u); +int PEM_write_bio(BIO *bp, const char *name, const char *hdr, + const unsigned char *data, long len); +int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm, + const char *name, BIO *bp, pem_password_cb *cb, + void *u); +void *PEM_ASN1_read_bio(d2i_of_void *d2i, const char *name, BIO *bp, void **x, + pem_password_cb *cb, void *u); +int PEM_ASN1_write_bio(i2d_of_void *i2d, const char *name, BIO *bp, void *x, + const EVP_CIPHER *enc, unsigned char *kstr, int klen, + pem_password_cb *cb, void *u); + +STACK_OF(X509_INFO) *PEM_X509_INFO_read_bio(BIO *bp, STACK_OF(X509_INFO) *sk, + pem_password_cb *cb, void *u); +int PEM_X509_INFO_write_bio(BIO *bp, X509_INFO *xi, EVP_CIPHER *enc, + unsigned char *kstr, int klen, + pem_password_cb *cd, void *u); + +#ifndef OPENSSL_NO_STDIO +int PEM_read(FILE *fp, char **name, char **header, + unsigned char **data, long *len); +int PEM_write(FILE *fp, const char *name, const char *hdr, + const unsigned char *data, long len); +void *PEM_ASN1_read(d2i_of_void *d2i, const char *name, FILE *fp, void **x, + pem_password_cb *cb, void *u); +int PEM_ASN1_write(i2d_of_void *i2d, const char *name, FILE *fp, + void *x, const EVP_CIPHER *enc, unsigned char *kstr, + int klen, pem_password_cb *callback, void *u); +STACK_OF(X509_INFO) *PEM_X509_INFO_read(FILE *fp, STACK_OF(X509_INFO) *sk, + pem_password_cb *cb, void *u); +#endif + +int PEM_SignInit(EVP_MD_CTX *ctx, EVP_MD *type); +int PEM_SignUpdate(EVP_MD_CTX *ctx, unsigned char *d, unsigned int cnt); +int PEM_SignFinal(EVP_MD_CTX *ctx, unsigned char *sigret, + unsigned int *siglen, EVP_PKEY *pkey); + +/* The default pem_password_cb that's used internally */ +int PEM_def_callback(char *buf, int num, int rwflag, void *userdata); +void PEM_proc_type(char *buf, int type); +void PEM_dek_info(char *buf, const char *type, int len, char *str); + +# include + +DECLARE_PEM_rw(X509, X509) +DECLARE_PEM_rw(X509_AUX, X509) +DECLARE_PEM_rw(X509_REQ, X509_REQ) +DECLARE_PEM_write(X509_REQ_NEW, X509_REQ) +DECLARE_PEM_rw(X509_CRL, X509_CRL) +DECLARE_PEM_rw(PKCS7, PKCS7) +DECLARE_PEM_rw(NETSCAPE_CERT_SEQUENCE, NETSCAPE_CERT_SEQUENCE) +DECLARE_PEM_rw(PKCS8, X509_SIG) +DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO) +# ifndef OPENSSL_NO_RSA +DECLARE_PEM_rw_cb(RSAPrivateKey, RSA) +DECLARE_PEM_rw_const(RSAPublicKey, RSA) +DECLARE_PEM_rw(RSA_PUBKEY, RSA) +# endif +# ifndef OPENSSL_NO_DSA +DECLARE_PEM_rw_cb(DSAPrivateKey, DSA) +DECLARE_PEM_rw(DSA_PUBKEY, DSA) +DECLARE_PEM_rw_const(DSAparams, DSA) +# endif +# ifndef OPENSSL_NO_EC +DECLARE_PEM_rw_const(ECPKParameters, EC_GROUP) +DECLARE_PEM_rw_cb(ECPrivateKey, EC_KEY) +DECLARE_PEM_rw(EC_PUBKEY, EC_KEY) +# endif +# ifndef OPENSSL_NO_DH +DECLARE_PEM_rw_const(DHparams, DH) +DECLARE_PEM_write_const(DHxparams, DH) +# endif +DECLARE_PEM_rw_cb(PrivateKey, EVP_PKEY) +DECLARE_PEM_rw(PUBKEY, EVP_PKEY) + +int PEM_write_bio_PrivateKey_traditional(BIO *bp, EVP_PKEY *x, + const EVP_CIPHER *enc, + unsigned char *kstr, int klen, + pem_password_cb *cb, void *u); + +int PEM_write_bio_PKCS8PrivateKey_nid(BIO *bp, EVP_PKEY *x, int nid, + char *kstr, int klen, + pem_password_cb *cb, void *u); +int PEM_write_bio_PKCS8PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, + char *, int, pem_password_cb *, void *); +int i2d_PKCS8PrivateKey_bio(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc, + char *kstr, int klen, + pem_password_cb *cb, void *u); +int i2d_PKCS8PrivateKey_nid_bio(BIO *bp, EVP_PKEY *x, int nid, + char *kstr, int klen, + pem_password_cb *cb, void *u); +EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, + void *u); + +# ifndef OPENSSL_NO_STDIO +int i2d_PKCS8PrivateKey_fp(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, + char *kstr, int klen, + pem_password_cb *cb, void *u); +int i2d_PKCS8PrivateKey_nid_fp(FILE *fp, EVP_PKEY *x, int nid, + char *kstr, int klen, + pem_password_cb *cb, void *u); +int PEM_write_PKCS8PrivateKey_nid(FILE *fp, EVP_PKEY *x, int nid, + char *kstr, int klen, + pem_password_cb *cb, void *u); + +EVP_PKEY *d2i_PKCS8PrivateKey_fp(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, + void *u); + +int PEM_write_PKCS8PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, + char *kstr, int klen, pem_password_cb *cd, + void *u); +# endif +EVP_PKEY *PEM_read_bio_Parameters(BIO *bp, EVP_PKEY **x); +int PEM_write_bio_Parameters(BIO *bp, EVP_PKEY *x); + +# ifndef OPENSSL_NO_DSA +EVP_PKEY *b2i_PrivateKey(const unsigned char **in, long length); +EVP_PKEY *b2i_PublicKey(const unsigned char **in, long length); +EVP_PKEY *b2i_PrivateKey_bio(BIO *in); +EVP_PKEY *b2i_PublicKey_bio(BIO *in); +int i2b_PrivateKey_bio(BIO *out, EVP_PKEY *pk); +int i2b_PublicKey_bio(BIO *out, EVP_PKEY *pk); +# ifndef OPENSSL_NO_RC4 +EVP_PKEY *b2i_PVK_bio(BIO *in, pem_password_cb *cb, void *u); +int i2b_PVK_bio(BIO *out, EVP_PKEY *pk, int enclevel, + pem_password_cb *cb, void *u); +# endif +# endif + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/pem2.h b/thrid-party/openssl/openssl/pem2.h new file mode 100644 index 0000000000..038fe790ac --- /dev/null +++ b/thrid-party/openssl/openssl/pem2.h @@ -0,0 +1,13 @@ +/* + * Copyright 1999-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PEM2_H +# define HEADER_PEM2_H +# include +#endif diff --git a/thrid-party/openssl/openssl/pemerr.h b/thrid-party/openssl/openssl/pemerr.h new file mode 100644 index 0000000000..0c45918f3c --- /dev/null +++ b/thrid-party/openssl/openssl/pemerr.h @@ -0,0 +1,103 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PEMERR_H +# define HEADER_PEMERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_PEM_strings(void); + +/* + * PEM function codes. + */ +# define PEM_F_B2I_DSS 127 +# define PEM_F_B2I_PVK_BIO 128 +# define PEM_F_B2I_RSA 129 +# define PEM_F_CHECK_BITLEN_DSA 130 +# define PEM_F_CHECK_BITLEN_RSA 131 +# define PEM_F_D2I_PKCS8PRIVATEKEY_BIO 120 +# define PEM_F_D2I_PKCS8PRIVATEKEY_FP 121 +# define PEM_F_DO_B2I 132 +# define PEM_F_DO_B2I_BIO 133 +# define PEM_F_DO_BLOB_HEADER 134 +# define PEM_F_DO_I2B 146 +# define PEM_F_DO_PK8PKEY 126 +# define PEM_F_DO_PK8PKEY_FP 125 +# define PEM_F_DO_PVK_BODY 135 +# define PEM_F_DO_PVK_HEADER 136 +# define PEM_F_GET_HEADER_AND_DATA 143 +# define PEM_F_GET_NAME 144 +# define PEM_F_I2B_PVK 137 +# define PEM_F_I2B_PVK_BIO 138 +# define PEM_F_LOAD_IV 101 +# define PEM_F_PEM_ASN1_READ 102 +# define PEM_F_PEM_ASN1_READ_BIO 103 +# define PEM_F_PEM_ASN1_WRITE 104 +# define PEM_F_PEM_ASN1_WRITE_BIO 105 +# define PEM_F_PEM_DEF_CALLBACK 100 +# define PEM_F_PEM_DO_HEADER 106 +# define PEM_F_PEM_GET_EVP_CIPHER_INFO 107 +# define PEM_F_PEM_READ 108 +# define PEM_F_PEM_READ_BIO 109 +# define PEM_F_PEM_READ_BIO_DHPARAMS 141 +# define PEM_F_PEM_READ_BIO_EX 145 +# define PEM_F_PEM_READ_BIO_PARAMETERS 140 +# define PEM_F_PEM_READ_BIO_PRIVATEKEY 123 +# define PEM_F_PEM_READ_DHPARAMS 142 +# define PEM_F_PEM_READ_PRIVATEKEY 124 +# define PEM_F_PEM_SIGNFINAL 112 +# define PEM_F_PEM_WRITE 113 +# define PEM_F_PEM_WRITE_BIO 114 +# define PEM_F_PEM_WRITE_PRIVATEKEY 139 +# define PEM_F_PEM_X509_INFO_READ 115 +# define PEM_F_PEM_X509_INFO_READ_BIO 116 +# define PEM_F_PEM_X509_INFO_WRITE_BIO 117 + +/* + * PEM reason codes. + */ +# define PEM_R_BAD_BASE64_DECODE 100 +# define PEM_R_BAD_DECRYPT 101 +# define PEM_R_BAD_END_LINE 102 +# define PEM_R_BAD_IV_CHARS 103 +# define PEM_R_BAD_MAGIC_NUMBER 116 +# define PEM_R_BAD_PASSWORD_READ 104 +# define PEM_R_BAD_VERSION_NUMBER 117 +# define PEM_R_BIO_WRITE_FAILURE 118 +# define PEM_R_CIPHER_IS_NULL 127 +# define PEM_R_ERROR_CONVERTING_PRIVATE_KEY 115 +# define PEM_R_EXPECTING_PRIVATE_KEY_BLOB 119 +# define PEM_R_EXPECTING_PUBLIC_KEY_BLOB 120 +# define PEM_R_HEADER_TOO_LONG 128 +# define PEM_R_INCONSISTENT_HEADER 121 +# define PEM_R_KEYBLOB_HEADER_PARSE_ERROR 122 +# define PEM_R_KEYBLOB_TOO_SHORT 123 +# define PEM_R_MISSING_DEK_IV 129 +# define PEM_R_NOT_DEK_INFO 105 +# define PEM_R_NOT_ENCRYPTED 106 +# define PEM_R_NOT_PROC_TYPE 107 +# define PEM_R_NO_START_LINE 108 +# define PEM_R_PROBLEMS_GETTING_PASSWORD 109 +# define PEM_R_PVK_DATA_TOO_SHORT 124 +# define PEM_R_PVK_TOO_SHORT 125 +# define PEM_R_READ_KEY 111 +# define PEM_R_SHORT_HEADER 112 +# define PEM_R_UNEXPECTED_DEK_IV 130 +# define PEM_R_UNSUPPORTED_CIPHER 113 +# define PEM_R_UNSUPPORTED_ENCRYPTION 114 +# define PEM_R_UNSUPPORTED_KEY_COMPONENTS 126 + +#endif diff --git a/thrid-party/openssl/openssl/pkcs12.h b/thrid-party/openssl/openssl/pkcs12.h new file mode 100644 index 0000000000..3f43dad6d9 --- /dev/null +++ b/thrid-party/openssl/openssl/pkcs12.h @@ -0,0 +1,223 @@ +/* + * Copyright 1999-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PKCS12_H +# define HEADER_PKCS12_H + +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# define PKCS12_KEY_ID 1 +# define PKCS12_IV_ID 2 +# define PKCS12_MAC_ID 3 + +/* Default iteration count */ +# ifndef PKCS12_DEFAULT_ITER +# define PKCS12_DEFAULT_ITER PKCS5_DEFAULT_ITER +# endif + +# define PKCS12_MAC_KEY_LENGTH 20 + +# define PKCS12_SALT_LEN 8 + +/* It's not clear if these are actually needed... */ +# define PKCS12_key_gen PKCS12_key_gen_utf8 +# define PKCS12_add_friendlyname PKCS12_add_friendlyname_utf8 + +/* MS key usage constants */ + +# define KEY_EX 0x10 +# define KEY_SIG 0x80 + +typedef struct PKCS12_MAC_DATA_st PKCS12_MAC_DATA; + +typedef struct PKCS12_st PKCS12; + +typedef struct PKCS12_SAFEBAG_st PKCS12_SAFEBAG; + +DEFINE_STACK_OF(PKCS12_SAFEBAG) + +typedef struct pkcs12_bag_st PKCS12_BAGS; + +# define PKCS12_ERROR 0 +# define PKCS12_OK 1 + +/* Compatibility macros */ + +#if OPENSSL_API_COMPAT < 0x10100000L + +# define M_PKCS12_bag_type PKCS12_bag_type +# define M_PKCS12_cert_bag_type PKCS12_cert_bag_type +# define M_PKCS12_crl_bag_type PKCS12_cert_bag_type + +# define PKCS12_certbag2x509 PKCS12_SAFEBAG_get1_cert +# define PKCS12_certbag2scrl PKCS12_SAFEBAG_get1_crl +# define PKCS12_bag_type PKCS12_SAFEBAG_get_nid +# define PKCS12_cert_bag_type PKCS12_SAFEBAG_get_bag_nid +# define PKCS12_x5092certbag PKCS12_SAFEBAG_create_cert +# define PKCS12_x509crl2certbag PKCS12_SAFEBAG_create_crl +# define PKCS12_MAKE_KEYBAG PKCS12_SAFEBAG_create0_p8inf +# define PKCS12_MAKE_SHKEYBAG PKCS12_SAFEBAG_create_pkcs8_encrypt + +#endif + +DEPRECATEDIN_1_1_0(ASN1_TYPE *PKCS12_get_attr(const PKCS12_SAFEBAG *bag, int attr_nid)) + +ASN1_TYPE *PKCS8_get_attr(PKCS8_PRIV_KEY_INFO *p8, int attr_nid); +int PKCS12_mac_present(const PKCS12 *p12); +void PKCS12_get0_mac(const ASN1_OCTET_STRING **pmac, + const X509_ALGOR **pmacalg, + const ASN1_OCTET_STRING **psalt, + const ASN1_INTEGER **piter, + const PKCS12 *p12); + +const ASN1_TYPE *PKCS12_SAFEBAG_get0_attr(const PKCS12_SAFEBAG *bag, + int attr_nid); +const ASN1_OBJECT *PKCS12_SAFEBAG_get0_type(const PKCS12_SAFEBAG *bag); +int PKCS12_SAFEBAG_get_nid(const PKCS12_SAFEBAG *bag); +int PKCS12_SAFEBAG_get_bag_nid(const PKCS12_SAFEBAG *bag); + +X509 *PKCS12_SAFEBAG_get1_cert(const PKCS12_SAFEBAG *bag); +X509_CRL *PKCS12_SAFEBAG_get1_crl(const PKCS12_SAFEBAG *bag); +const STACK_OF(PKCS12_SAFEBAG) * +PKCS12_SAFEBAG_get0_safes(const PKCS12_SAFEBAG *bag); +const PKCS8_PRIV_KEY_INFO *PKCS12_SAFEBAG_get0_p8inf(const PKCS12_SAFEBAG *bag); +const X509_SIG *PKCS12_SAFEBAG_get0_pkcs8(const PKCS12_SAFEBAG *bag); + +PKCS12_SAFEBAG *PKCS12_SAFEBAG_create_cert(X509 *x509); +PKCS12_SAFEBAG *PKCS12_SAFEBAG_create_crl(X509_CRL *crl); +PKCS12_SAFEBAG *PKCS12_SAFEBAG_create0_p8inf(PKCS8_PRIV_KEY_INFO *p8); +PKCS12_SAFEBAG *PKCS12_SAFEBAG_create0_pkcs8(X509_SIG *p8); +PKCS12_SAFEBAG *PKCS12_SAFEBAG_create_pkcs8_encrypt(int pbe_nid, + const char *pass, + int passlen, + unsigned char *salt, + int saltlen, int iter, + PKCS8_PRIV_KEY_INFO *p8inf); + +PKCS12_SAFEBAG *PKCS12_item_pack_safebag(void *obj, const ASN1_ITEM *it, + int nid1, int nid2); +PKCS8_PRIV_KEY_INFO *PKCS8_decrypt(const X509_SIG *p8, const char *pass, + int passlen); +PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_skey(const PKCS12_SAFEBAG *bag, + const char *pass, int passlen); +X509_SIG *PKCS8_encrypt(int pbe_nid, const EVP_CIPHER *cipher, + const char *pass, int passlen, unsigned char *salt, + int saltlen, int iter, PKCS8_PRIV_KEY_INFO *p8); +X509_SIG *PKCS8_set0_pbe(const char *pass, int passlen, + PKCS8_PRIV_KEY_INFO *p8inf, X509_ALGOR *pbe); +PKCS7 *PKCS12_pack_p7data(STACK_OF(PKCS12_SAFEBAG) *sk); +STACK_OF(PKCS12_SAFEBAG) *PKCS12_unpack_p7data(PKCS7 *p7); +PKCS7 *PKCS12_pack_p7encdata(int pbe_nid, const char *pass, int passlen, + unsigned char *salt, int saltlen, int iter, + STACK_OF(PKCS12_SAFEBAG) *bags); +STACK_OF(PKCS12_SAFEBAG) *PKCS12_unpack_p7encdata(PKCS7 *p7, const char *pass, + int passlen); + +int PKCS12_pack_authsafes(PKCS12 *p12, STACK_OF(PKCS7) *safes); +STACK_OF(PKCS7) *PKCS12_unpack_authsafes(const PKCS12 *p12); + +int PKCS12_add_localkeyid(PKCS12_SAFEBAG *bag, unsigned char *name, + int namelen); +int PKCS12_add_friendlyname_asc(PKCS12_SAFEBAG *bag, const char *name, + int namelen); +int PKCS12_add_friendlyname_utf8(PKCS12_SAFEBAG *bag, const char *name, + int namelen); +int PKCS12_add_CSPName_asc(PKCS12_SAFEBAG *bag, const char *name, + int namelen); +int PKCS12_add_friendlyname_uni(PKCS12_SAFEBAG *bag, + const unsigned char *name, int namelen); +int PKCS8_add_keyusage(PKCS8_PRIV_KEY_INFO *p8, int usage); +ASN1_TYPE *PKCS12_get_attr_gen(const STACK_OF(X509_ATTRIBUTE) *attrs, + int attr_nid); +char *PKCS12_get_friendlyname(PKCS12_SAFEBAG *bag); +const STACK_OF(X509_ATTRIBUTE) * +PKCS12_SAFEBAG_get0_attrs(const PKCS12_SAFEBAG *bag); +unsigned char *PKCS12_pbe_crypt(const X509_ALGOR *algor, + const char *pass, int passlen, + const unsigned char *in, int inlen, + unsigned char **data, int *datalen, + int en_de); +void *PKCS12_item_decrypt_d2i(const X509_ALGOR *algor, const ASN1_ITEM *it, + const char *pass, int passlen, + const ASN1_OCTET_STRING *oct, int zbuf); +ASN1_OCTET_STRING *PKCS12_item_i2d_encrypt(X509_ALGOR *algor, + const ASN1_ITEM *it, + const char *pass, int passlen, + void *obj, int zbuf); +PKCS12 *PKCS12_init(int mode); +int PKCS12_key_gen_asc(const char *pass, int passlen, unsigned char *salt, + int saltlen, int id, int iter, int n, + unsigned char *out, const EVP_MD *md_type); +int PKCS12_key_gen_uni(unsigned char *pass, int passlen, unsigned char *salt, + int saltlen, int id, int iter, int n, + unsigned char *out, const EVP_MD *md_type); +int PKCS12_key_gen_utf8(const char *pass, int passlen, unsigned char *salt, + int saltlen, int id, int iter, int n, + unsigned char *out, const EVP_MD *md_type); +int PKCS12_PBE_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, + ASN1_TYPE *param, const EVP_CIPHER *cipher, + const EVP_MD *md_type, int en_de); +int PKCS12_gen_mac(PKCS12 *p12, const char *pass, int passlen, + unsigned char *mac, unsigned int *maclen); +int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen); +int PKCS12_set_mac(PKCS12 *p12, const char *pass, int passlen, + unsigned char *salt, int saltlen, int iter, + const EVP_MD *md_type); +int PKCS12_setup_mac(PKCS12 *p12, int iter, unsigned char *salt, + int saltlen, const EVP_MD *md_type); +unsigned char *OPENSSL_asc2uni(const char *asc, int asclen, + unsigned char **uni, int *unilen); +char *OPENSSL_uni2asc(const unsigned char *uni, int unilen); +unsigned char *OPENSSL_utf82uni(const char *asc, int asclen, + unsigned char **uni, int *unilen); +char *OPENSSL_uni2utf8(const unsigned char *uni, int unilen); + +DECLARE_ASN1_FUNCTIONS(PKCS12) +DECLARE_ASN1_FUNCTIONS(PKCS12_MAC_DATA) +DECLARE_ASN1_FUNCTIONS(PKCS12_SAFEBAG) +DECLARE_ASN1_FUNCTIONS(PKCS12_BAGS) + +DECLARE_ASN1_ITEM(PKCS12_SAFEBAGS) +DECLARE_ASN1_ITEM(PKCS12_AUTHSAFES) + +void PKCS12_PBE_add(void); +int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, + STACK_OF(X509) **ca); +PKCS12 *PKCS12_create(const char *pass, const char *name, EVP_PKEY *pkey, + X509 *cert, STACK_OF(X509) *ca, int nid_key, int nid_cert, + int iter, int mac_iter, int keytype); + +PKCS12_SAFEBAG *PKCS12_add_cert(STACK_OF(PKCS12_SAFEBAG) **pbags, X509 *cert); +PKCS12_SAFEBAG *PKCS12_add_key(STACK_OF(PKCS12_SAFEBAG) **pbags, + EVP_PKEY *key, int key_usage, int iter, + int key_nid, const char *pass); +int PKCS12_add_safe(STACK_OF(PKCS7) **psafes, STACK_OF(PKCS12_SAFEBAG) *bags, + int safe_nid, int iter, const char *pass); +PKCS12 *PKCS12_add_safes(STACK_OF(PKCS7) *safes, int p7_nid); + +int i2d_PKCS12_bio(BIO *bp, PKCS12 *p12); +# ifndef OPENSSL_NO_STDIO +int i2d_PKCS12_fp(FILE *fp, PKCS12 *p12); +# endif +PKCS12 *d2i_PKCS12_bio(BIO *bp, PKCS12 **p12); +# ifndef OPENSSL_NO_STDIO +PKCS12 *d2i_PKCS12_fp(FILE *fp, PKCS12 **p12); +# endif +int PKCS12_newpass(PKCS12 *p12, const char *oldpass, const char *newpass); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/pkcs12err.h b/thrid-party/openssl/openssl/pkcs12err.h new file mode 100644 index 0000000000..eff5eb2602 --- /dev/null +++ b/thrid-party/openssl/openssl/pkcs12err.h @@ -0,0 +1,81 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PKCS12ERR_H +# define HEADER_PKCS12ERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_PKCS12_strings(void); + +/* + * PKCS12 function codes. + */ +# define PKCS12_F_OPENSSL_ASC2UNI 121 +# define PKCS12_F_OPENSSL_UNI2ASC 124 +# define PKCS12_F_OPENSSL_UNI2UTF8 127 +# define PKCS12_F_OPENSSL_UTF82UNI 129 +# define PKCS12_F_PKCS12_CREATE 105 +# define PKCS12_F_PKCS12_GEN_MAC 107 +# define PKCS12_F_PKCS12_INIT 109 +# define PKCS12_F_PKCS12_ITEM_DECRYPT_D2I 106 +# define PKCS12_F_PKCS12_ITEM_I2D_ENCRYPT 108 +# define PKCS12_F_PKCS12_ITEM_PACK_SAFEBAG 117 +# define PKCS12_F_PKCS12_KEY_GEN_ASC 110 +# define PKCS12_F_PKCS12_KEY_GEN_UNI 111 +# define PKCS12_F_PKCS12_KEY_GEN_UTF8 116 +# define PKCS12_F_PKCS12_NEWPASS 128 +# define PKCS12_F_PKCS12_PACK_P7DATA 114 +# define PKCS12_F_PKCS12_PACK_P7ENCDATA 115 +# define PKCS12_F_PKCS12_PARSE 118 +# define PKCS12_F_PKCS12_PBE_CRYPT 119 +# define PKCS12_F_PKCS12_PBE_KEYIVGEN 120 +# define PKCS12_F_PKCS12_SAFEBAG_CREATE0_P8INF 112 +# define PKCS12_F_PKCS12_SAFEBAG_CREATE0_PKCS8 113 +# define PKCS12_F_PKCS12_SAFEBAG_CREATE_PKCS8_ENCRYPT 133 +# define PKCS12_F_PKCS12_SETUP_MAC 122 +# define PKCS12_F_PKCS12_SET_MAC 123 +# define PKCS12_F_PKCS12_UNPACK_AUTHSAFES 130 +# define PKCS12_F_PKCS12_UNPACK_P7DATA 131 +# define PKCS12_F_PKCS12_VERIFY_MAC 126 +# define PKCS12_F_PKCS8_ENCRYPT 125 +# define PKCS12_F_PKCS8_SET0_PBE 132 + +/* + * PKCS12 reason codes. + */ +# define PKCS12_R_CANT_PACK_STRUCTURE 100 +# define PKCS12_R_CONTENT_TYPE_NOT_DATA 121 +# define PKCS12_R_DECODE_ERROR 101 +# define PKCS12_R_ENCODE_ERROR 102 +# define PKCS12_R_ENCRYPT_ERROR 103 +# define PKCS12_R_ERROR_SETTING_ENCRYPTED_DATA_TYPE 120 +# define PKCS12_R_INVALID_NULL_ARGUMENT 104 +# define PKCS12_R_INVALID_NULL_PKCS12_POINTER 105 +# define PKCS12_R_IV_GEN_ERROR 106 +# define PKCS12_R_KEY_GEN_ERROR 107 +# define PKCS12_R_MAC_ABSENT 108 +# define PKCS12_R_MAC_GENERATION_ERROR 109 +# define PKCS12_R_MAC_SETUP_ERROR 110 +# define PKCS12_R_MAC_STRING_SET_ERROR 111 +# define PKCS12_R_MAC_VERIFY_FAILURE 113 +# define PKCS12_R_PARSE_ERROR 114 +# define PKCS12_R_PKCS12_ALGOR_CIPHERINIT_ERROR 115 +# define PKCS12_R_PKCS12_CIPHERFINAL_ERROR 116 +# define PKCS12_R_PKCS12_PBE_CRYPT_ERROR 117 +# define PKCS12_R_UNKNOWN_DIGEST_ALGORITHM 118 +# define PKCS12_R_UNSUPPORTED_PKCS12_MODE 119 + +#endif diff --git a/thrid-party/openssl/openssl/pkcs7.h b/thrid-party/openssl/openssl/pkcs7.h new file mode 100644 index 0000000000..9b66e002d2 --- /dev/null +++ b/thrid-party/openssl/openssl/pkcs7.h @@ -0,0 +1,319 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PKCS7_H +# define HEADER_PKCS7_H + +# include +# include +# include + +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/*- +Encryption_ID DES-CBC +Digest_ID MD5 +Digest_Encryption_ID rsaEncryption +Key_Encryption_ID rsaEncryption +*/ + +typedef struct pkcs7_issuer_and_serial_st { + X509_NAME *issuer; + ASN1_INTEGER *serial; +} PKCS7_ISSUER_AND_SERIAL; + +typedef struct pkcs7_signer_info_st { + ASN1_INTEGER *version; /* version 1 */ + PKCS7_ISSUER_AND_SERIAL *issuer_and_serial; + X509_ALGOR *digest_alg; + STACK_OF(X509_ATTRIBUTE) *auth_attr; /* [ 0 ] */ + X509_ALGOR *digest_enc_alg; + ASN1_OCTET_STRING *enc_digest; + STACK_OF(X509_ATTRIBUTE) *unauth_attr; /* [ 1 ] */ + /* The private key to sign with */ + EVP_PKEY *pkey; +} PKCS7_SIGNER_INFO; + +DEFINE_STACK_OF(PKCS7_SIGNER_INFO) + +typedef struct pkcs7_recip_info_st { + ASN1_INTEGER *version; /* version 0 */ + PKCS7_ISSUER_AND_SERIAL *issuer_and_serial; + X509_ALGOR *key_enc_algor; + ASN1_OCTET_STRING *enc_key; + X509 *cert; /* get the pub-key from this */ +} PKCS7_RECIP_INFO; + +DEFINE_STACK_OF(PKCS7_RECIP_INFO) + +typedef struct pkcs7_signed_st { + ASN1_INTEGER *version; /* version 1 */ + STACK_OF(X509_ALGOR) *md_algs; /* md used */ + STACK_OF(X509) *cert; /* [ 0 ] */ + STACK_OF(X509_CRL) *crl; /* [ 1 ] */ + STACK_OF(PKCS7_SIGNER_INFO) *signer_info; + struct pkcs7_st *contents; +} PKCS7_SIGNED; +/* + * The above structure is very very similar to PKCS7_SIGN_ENVELOPE. How about + * merging the two + */ + +typedef struct pkcs7_enc_content_st { + ASN1_OBJECT *content_type; + X509_ALGOR *algorithm; + ASN1_OCTET_STRING *enc_data; /* [ 0 ] */ + const EVP_CIPHER *cipher; +} PKCS7_ENC_CONTENT; + +typedef struct pkcs7_enveloped_st { + ASN1_INTEGER *version; /* version 0 */ + STACK_OF(PKCS7_RECIP_INFO) *recipientinfo; + PKCS7_ENC_CONTENT *enc_data; +} PKCS7_ENVELOPE; + +typedef struct pkcs7_signedandenveloped_st { + ASN1_INTEGER *version; /* version 1 */ + STACK_OF(X509_ALGOR) *md_algs; /* md used */ + STACK_OF(X509) *cert; /* [ 0 ] */ + STACK_OF(X509_CRL) *crl; /* [ 1 ] */ + STACK_OF(PKCS7_SIGNER_INFO) *signer_info; + PKCS7_ENC_CONTENT *enc_data; + STACK_OF(PKCS7_RECIP_INFO) *recipientinfo; +} PKCS7_SIGN_ENVELOPE; + +typedef struct pkcs7_digest_st { + ASN1_INTEGER *version; /* version 0 */ + X509_ALGOR *md; /* md used */ + struct pkcs7_st *contents; + ASN1_OCTET_STRING *digest; +} PKCS7_DIGEST; + +typedef struct pkcs7_encrypted_st { + ASN1_INTEGER *version; /* version 0 */ + PKCS7_ENC_CONTENT *enc_data; +} PKCS7_ENCRYPT; + +typedef struct pkcs7_st { + /* + * The following is non NULL if it contains ASN1 encoding of this + * structure + */ + unsigned char *asn1; + long length; +# define PKCS7_S_HEADER 0 +# define PKCS7_S_BODY 1 +# define PKCS7_S_TAIL 2 + int state; /* used during processing */ + int detached; + ASN1_OBJECT *type; + /* content as defined by the type */ + /* + * all encryption/message digests are applied to the 'contents', leaving + * out the 'type' field. + */ + union { + char *ptr; + /* NID_pkcs7_data */ + ASN1_OCTET_STRING *data; + /* NID_pkcs7_signed */ + PKCS7_SIGNED *sign; + /* NID_pkcs7_enveloped */ + PKCS7_ENVELOPE *enveloped; + /* NID_pkcs7_signedAndEnveloped */ + PKCS7_SIGN_ENVELOPE *signed_and_enveloped; + /* NID_pkcs7_digest */ + PKCS7_DIGEST *digest; + /* NID_pkcs7_encrypted */ + PKCS7_ENCRYPT *encrypted; + /* Anything else */ + ASN1_TYPE *other; + } d; +} PKCS7; + +DEFINE_STACK_OF(PKCS7) + +# define PKCS7_OP_SET_DETACHED_SIGNATURE 1 +# define PKCS7_OP_GET_DETACHED_SIGNATURE 2 + +# define PKCS7_get_signed_attributes(si) ((si)->auth_attr) +# define PKCS7_get_attributes(si) ((si)->unauth_attr) + +# define PKCS7_type_is_signed(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_signed) +# define PKCS7_type_is_encrypted(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_encrypted) +# define PKCS7_type_is_enveloped(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_enveloped) +# define PKCS7_type_is_signedAndEnveloped(a) \ + (OBJ_obj2nid((a)->type) == NID_pkcs7_signedAndEnveloped) +# define PKCS7_type_is_data(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_data) +# define PKCS7_type_is_digest(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_digest) + +# define PKCS7_set_detached(p,v) \ + PKCS7_ctrl(p,PKCS7_OP_SET_DETACHED_SIGNATURE,v,NULL) +# define PKCS7_get_detached(p) \ + PKCS7_ctrl(p,PKCS7_OP_GET_DETACHED_SIGNATURE,0,NULL) + +# define PKCS7_is_detached(p7) (PKCS7_type_is_signed(p7) && PKCS7_get_detached(p7)) + +/* S/MIME related flags */ + +# define PKCS7_TEXT 0x1 +# define PKCS7_NOCERTS 0x2 +# define PKCS7_NOSIGS 0x4 +# define PKCS7_NOCHAIN 0x8 +# define PKCS7_NOINTERN 0x10 +# define PKCS7_NOVERIFY 0x20 +# define PKCS7_DETACHED 0x40 +# define PKCS7_BINARY 0x80 +# define PKCS7_NOATTR 0x100 +# define PKCS7_NOSMIMECAP 0x200 +# define PKCS7_NOOLDMIMETYPE 0x400 +# define PKCS7_CRLFEOL 0x800 +# define PKCS7_STREAM 0x1000 +# define PKCS7_NOCRL 0x2000 +# define PKCS7_PARTIAL 0x4000 +# define PKCS7_REUSE_DIGEST 0x8000 +# define PKCS7_NO_DUAL_CONTENT 0x10000 + +/* Flags: for compatibility with older code */ + +# define SMIME_TEXT PKCS7_TEXT +# define SMIME_NOCERTS PKCS7_NOCERTS +# define SMIME_NOSIGS PKCS7_NOSIGS +# define SMIME_NOCHAIN PKCS7_NOCHAIN +# define SMIME_NOINTERN PKCS7_NOINTERN +# define SMIME_NOVERIFY PKCS7_NOVERIFY +# define SMIME_DETACHED PKCS7_DETACHED +# define SMIME_BINARY PKCS7_BINARY +# define SMIME_NOATTR PKCS7_NOATTR + +/* CRLF ASCII canonicalisation */ +# define SMIME_ASCIICRLF 0x80000 + +DECLARE_ASN1_FUNCTIONS(PKCS7_ISSUER_AND_SERIAL) + +int PKCS7_ISSUER_AND_SERIAL_digest(PKCS7_ISSUER_AND_SERIAL *data, + const EVP_MD *type, unsigned char *md, + unsigned int *len); +# ifndef OPENSSL_NO_STDIO +PKCS7 *d2i_PKCS7_fp(FILE *fp, PKCS7 **p7); +int i2d_PKCS7_fp(FILE *fp, PKCS7 *p7); +# endif +PKCS7 *PKCS7_dup(PKCS7 *p7); +PKCS7 *d2i_PKCS7_bio(BIO *bp, PKCS7 **p7); +int i2d_PKCS7_bio(BIO *bp, PKCS7 *p7); +int i2d_PKCS7_bio_stream(BIO *out, PKCS7 *p7, BIO *in, int flags); +int PEM_write_bio_PKCS7_stream(BIO *out, PKCS7 *p7, BIO *in, int flags); + +DECLARE_ASN1_FUNCTIONS(PKCS7_SIGNER_INFO) +DECLARE_ASN1_FUNCTIONS(PKCS7_RECIP_INFO) +DECLARE_ASN1_FUNCTIONS(PKCS7_SIGNED) +DECLARE_ASN1_FUNCTIONS(PKCS7_ENC_CONTENT) +DECLARE_ASN1_FUNCTIONS(PKCS7_ENVELOPE) +DECLARE_ASN1_FUNCTIONS(PKCS7_SIGN_ENVELOPE) +DECLARE_ASN1_FUNCTIONS(PKCS7_DIGEST) +DECLARE_ASN1_FUNCTIONS(PKCS7_ENCRYPT) +DECLARE_ASN1_FUNCTIONS(PKCS7) + +DECLARE_ASN1_ITEM(PKCS7_ATTR_SIGN) +DECLARE_ASN1_ITEM(PKCS7_ATTR_VERIFY) + +DECLARE_ASN1_NDEF_FUNCTION(PKCS7) +DECLARE_ASN1_PRINT_FUNCTION(PKCS7) + +long PKCS7_ctrl(PKCS7 *p7, int cmd, long larg, char *parg); + +int PKCS7_set_type(PKCS7 *p7, int type); +int PKCS7_set0_type_other(PKCS7 *p7, int type, ASN1_TYPE *other); +int PKCS7_set_content(PKCS7 *p7, PKCS7 *p7_data); +int PKCS7_SIGNER_INFO_set(PKCS7_SIGNER_INFO *p7i, X509 *x509, EVP_PKEY *pkey, + const EVP_MD *dgst); +int PKCS7_SIGNER_INFO_sign(PKCS7_SIGNER_INFO *si); +int PKCS7_add_signer(PKCS7 *p7, PKCS7_SIGNER_INFO *p7i); +int PKCS7_add_certificate(PKCS7 *p7, X509 *x509); +int PKCS7_add_crl(PKCS7 *p7, X509_CRL *x509); +int PKCS7_content_new(PKCS7 *p7, int nid); +int PKCS7_dataVerify(X509_STORE *cert_store, X509_STORE_CTX *ctx, + BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si); +int PKCS7_signatureVerify(BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si, + X509 *x509); + +BIO *PKCS7_dataInit(PKCS7 *p7, BIO *bio); +int PKCS7_dataFinal(PKCS7 *p7, BIO *bio); +BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert); + +PKCS7_SIGNER_INFO *PKCS7_add_signature(PKCS7 *p7, X509 *x509, + EVP_PKEY *pkey, const EVP_MD *dgst); +X509 *PKCS7_cert_from_signer_info(PKCS7 *p7, PKCS7_SIGNER_INFO *si); +int PKCS7_set_digest(PKCS7 *p7, const EVP_MD *md); +STACK_OF(PKCS7_SIGNER_INFO) *PKCS7_get_signer_info(PKCS7 *p7); + +PKCS7_RECIP_INFO *PKCS7_add_recipient(PKCS7 *p7, X509 *x509); +void PKCS7_SIGNER_INFO_get0_algs(PKCS7_SIGNER_INFO *si, EVP_PKEY **pk, + X509_ALGOR **pdig, X509_ALGOR **psig); +void PKCS7_RECIP_INFO_get0_alg(PKCS7_RECIP_INFO *ri, X509_ALGOR **penc); +int PKCS7_add_recipient_info(PKCS7 *p7, PKCS7_RECIP_INFO *ri); +int PKCS7_RECIP_INFO_set(PKCS7_RECIP_INFO *p7i, X509 *x509); +int PKCS7_set_cipher(PKCS7 *p7, const EVP_CIPHER *cipher); +int PKCS7_stream(unsigned char ***boundary, PKCS7 *p7); + +PKCS7_ISSUER_AND_SERIAL *PKCS7_get_issuer_and_serial(PKCS7 *p7, int idx); +ASN1_OCTET_STRING *PKCS7_digest_from_attributes(STACK_OF(X509_ATTRIBUTE) *sk); +int PKCS7_add_signed_attribute(PKCS7_SIGNER_INFO *p7si, int nid, int type, + void *data); +int PKCS7_add_attribute(PKCS7_SIGNER_INFO *p7si, int nid, int atrtype, + void *value); +ASN1_TYPE *PKCS7_get_attribute(PKCS7_SIGNER_INFO *si, int nid); +ASN1_TYPE *PKCS7_get_signed_attribute(PKCS7_SIGNER_INFO *si, int nid); +int PKCS7_set_signed_attributes(PKCS7_SIGNER_INFO *p7si, + STACK_OF(X509_ATTRIBUTE) *sk); +int PKCS7_set_attributes(PKCS7_SIGNER_INFO *p7si, + STACK_OF(X509_ATTRIBUTE) *sk); + +PKCS7 *PKCS7_sign(X509 *signcert, EVP_PKEY *pkey, STACK_OF(X509) *certs, + BIO *data, int flags); + +PKCS7_SIGNER_INFO *PKCS7_sign_add_signer(PKCS7 *p7, + X509 *signcert, EVP_PKEY *pkey, + const EVP_MD *md, int flags); + +int PKCS7_final(PKCS7 *p7, BIO *data, int flags); +int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store, + BIO *indata, BIO *out, int flags); +STACK_OF(X509) *PKCS7_get0_signers(PKCS7 *p7, STACK_OF(X509) *certs, + int flags); +PKCS7 *PKCS7_encrypt(STACK_OF(X509) *certs, BIO *in, const EVP_CIPHER *cipher, + int flags); +int PKCS7_decrypt(PKCS7 *p7, EVP_PKEY *pkey, X509 *cert, BIO *data, + int flags); + +int PKCS7_add_attrib_smimecap(PKCS7_SIGNER_INFO *si, + STACK_OF(X509_ALGOR) *cap); +STACK_OF(X509_ALGOR) *PKCS7_get_smimecap(PKCS7_SIGNER_INFO *si); +int PKCS7_simple_smimecap(STACK_OF(X509_ALGOR) *sk, int nid, int arg); + +int PKCS7_add_attrib_content_type(PKCS7_SIGNER_INFO *si, ASN1_OBJECT *coid); +int PKCS7_add0_attrib_signing_time(PKCS7_SIGNER_INFO *si, ASN1_TIME *t); +int PKCS7_add1_attrib_digest(PKCS7_SIGNER_INFO *si, + const unsigned char *md, int mdlen); + +int SMIME_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *data, int flags); +PKCS7 *SMIME_read_PKCS7(BIO *bio, BIO **bcont); + +BIO *BIO_new_PKCS7(BIO *out, PKCS7 *p7); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/pkcs7err.h b/thrid-party/openssl/openssl/pkcs7err.h new file mode 100644 index 0000000000..02e0299a3c --- /dev/null +++ b/thrid-party/openssl/openssl/pkcs7err.h @@ -0,0 +1,103 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_PKCS7ERR_H +# define HEADER_PKCS7ERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_PKCS7_strings(void); + +/* + * PKCS7 function codes. + */ +# define PKCS7_F_DO_PKCS7_SIGNED_ATTRIB 136 +# define PKCS7_F_PKCS7_ADD0_ATTRIB_SIGNING_TIME 135 +# define PKCS7_F_PKCS7_ADD_ATTRIB_SMIMECAP 118 +# define PKCS7_F_PKCS7_ADD_CERTIFICATE 100 +# define PKCS7_F_PKCS7_ADD_CRL 101 +# define PKCS7_F_PKCS7_ADD_RECIPIENT_INFO 102 +# define PKCS7_F_PKCS7_ADD_SIGNATURE 131 +# define PKCS7_F_PKCS7_ADD_SIGNER 103 +# define PKCS7_F_PKCS7_BIO_ADD_DIGEST 125 +# define PKCS7_F_PKCS7_COPY_EXISTING_DIGEST 138 +# define PKCS7_F_PKCS7_CTRL 104 +# define PKCS7_F_PKCS7_DATADECODE 112 +# define PKCS7_F_PKCS7_DATAFINAL 128 +# define PKCS7_F_PKCS7_DATAINIT 105 +# define PKCS7_F_PKCS7_DATAVERIFY 107 +# define PKCS7_F_PKCS7_DECRYPT 114 +# define PKCS7_F_PKCS7_DECRYPT_RINFO 133 +# define PKCS7_F_PKCS7_ENCODE_RINFO 132 +# define PKCS7_F_PKCS7_ENCRYPT 115 +# define PKCS7_F_PKCS7_FINAL 134 +# define PKCS7_F_PKCS7_FIND_DIGEST 127 +# define PKCS7_F_PKCS7_GET0_SIGNERS 124 +# define PKCS7_F_PKCS7_RECIP_INFO_SET 130 +# define PKCS7_F_PKCS7_SET_CIPHER 108 +# define PKCS7_F_PKCS7_SET_CONTENT 109 +# define PKCS7_F_PKCS7_SET_DIGEST 126 +# define PKCS7_F_PKCS7_SET_TYPE 110 +# define PKCS7_F_PKCS7_SIGN 116 +# define PKCS7_F_PKCS7_SIGNATUREVERIFY 113 +# define PKCS7_F_PKCS7_SIGNER_INFO_SET 129 +# define PKCS7_F_PKCS7_SIGNER_INFO_SIGN 139 +# define PKCS7_F_PKCS7_SIGN_ADD_SIGNER 137 +# define PKCS7_F_PKCS7_SIMPLE_SMIMECAP 119 +# define PKCS7_F_PKCS7_VERIFY 117 + +/* + * PKCS7 reason codes. + */ +# define PKCS7_R_CERTIFICATE_VERIFY_ERROR 117 +# define PKCS7_R_CIPHER_HAS_NO_OBJECT_IDENTIFIER 144 +# define PKCS7_R_CIPHER_NOT_INITIALIZED 116 +# define PKCS7_R_CONTENT_AND_DATA_PRESENT 118 +# define PKCS7_R_CTRL_ERROR 152 +# define PKCS7_R_DECRYPT_ERROR 119 +# define PKCS7_R_DIGEST_FAILURE 101 +# define PKCS7_R_ENCRYPTION_CTRL_FAILURE 149 +# define PKCS7_R_ENCRYPTION_NOT_SUPPORTED_FOR_THIS_KEY_TYPE 150 +# define PKCS7_R_ERROR_ADDING_RECIPIENT 120 +# define PKCS7_R_ERROR_SETTING_CIPHER 121 +# define PKCS7_R_INVALID_NULL_POINTER 143 +# define PKCS7_R_INVALID_SIGNED_DATA_TYPE 155 +# define PKCS7_R_NO_CONTENT 122 +# define PKCS7_R_NO_DEFAULT_DIGEST 151 +# define PKCS7_R_NO_MATCHING_DIGEST_TYPE_FOUND 154 +# define PKCS7_R_NO_RECIPIENT_MATCHES_CERTIFICATE 115 +# define PKCS7_R_NO_SIGNATURES_ON_DATA 123 +# define PKCS7_R_NO_SIGNERS 142 +# define PKCS7_R_OPERATION_NOT_SUPPORTED_ON_THIS_TYPE 104 +# define PKCS7_R_PKCS7_ADD_SIGNATURE_ERROR 124 +# define PKCS7_R_PKCS7_ADD_SIGNER_ERROR 153 +# define PKCS7_R_PKCS7_DATASIGN 145 +# define PKCS7_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 127 +# define PKCS7_R_SIGNATURE_FAILURE 105 +# define PKCS7_R_SIGNER_CERTIFICATE_NOT_FOUND 128 +# define PKCS7_R_SIGNING_CTRL_FAILURE 147 +# define PKCS7_R_SIGNING_NOT_SUPPORTED_FOR_THIS_KEY_TYPE 148 +# define PKCS7_R_SMIME_TEXT_ERROR 129 +# define PKCS7_R_UNABLE_TO_FIND_CERTIFICATE 106 +# define PKCS7_R_UNABLE_TO_FIND_MEM_BIO 107 +# define PKCS7_R_UNABLE_TO_FIND_MESSAGE_DIGEST 108 +# define PKCS7_R_UNKNOWN_DIGEST_TYPE 109 +# define PKCS7_R_UNKNOWN_OPERATION 110 +# define PKCS7_R_UNSUPPORTED_CIPHER_TYPE 111 +# define PKCS7_R_UNSUPPORTED_CONTENT_TYPE 112 +# define PKCS7_R_WRONG_CONTENT_TYPE 113 +# define PKCS7_R_WRONG_PKCS7_TYPE 114 + +#endif diff --git a/thrid-party/openssl/openssl/pqueue.h b/thrid-party/openssl/openssl/pqueue.h new file mode 100644 index 0000000000..d40d9c7d85 --- /dev/null +++ b/thrid-party/openssl/openssl/pqueue.h @@ -0,0 +1,99 @@ +/* crypto/pqueue/pqueue.h */ +/* + * DTLS implementation written by Nagendra Modadugu + * (nagendra@cs.stanford.edu) for the OpenSSL project 2005. + */ +/* ==================================================================== + * Copyright (c) 1999-2005 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#ifndef HEADER_PQUEUE_H +# define HEADER_PQUEUE_H + +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif +typedef struct _pqueue *pqueue; + +typedef struct _pitem { + unsigned char priority[8]; /* 64-bit value in big-endian encoding */ + void *data; + struct _pitem *next; +} pitem; + +typedef struct _pitem *piterator; + +pitem *pitem_new(unsigned char *prio64be, void *data); +void pitem_free(pitem *item); + +pqueue pqueue_new(void); +void pqueue_free(pqueue pq); + +pitem *pqueue_insert(pqueue pq, pitem *item); +pitem *pqueue_peek(pqueue pq); +pitem *pqueue_pop(pqueue pq); +pitem *pqueue_find(pqueue pq, unsigned char *prio64be); +pitem *pqueue_iterator(pqueue pq); +pitem *pqueue_next(piterator *iter); + +void pqueue_print(pqueue pq); +int pqueue_size(pqueue pq); + +#ifdef __cplusplus +} +#endif +#endif /* ! HEADER_PQUEUE_H */ diff --git a/thrid-party/openssl/openssl/rand.h b/thrid-party/openssl/openssl/rand.h new file mode 100644 index 0000000000..38a2a2718f --- /dev/null +++ b/thrid-party/openssl/openssl/rand.h @@ -0,0 +1,77 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RAND_H +# define HEADER_RAND_H + +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +struct rand_meth_st { + int (*seed) (const void *buf, int num); + int (*bytes) (unsigned char *buf, int num); + void (*cleanup) (void); + int (*add) (const void *buf, int num, double randomness); + int (*pseudorand) (unsigned char *buf, int num); + int (*status) (void); +}; + +int RAND_set_rand_method(const RAND_METHOD *meth); +const RAND_METHOD *RAND_get_rand_method(void); +# ifndef OPENSSL_NO_ENGINE +int RAND_set_rand_engine(ENGINE *engine); +# endif + +RAND_METHOD *RAND_OpenSSL(void); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define RAND_cleanup() while(0) continue +# endif +int RAND_bytes(unsigned char *buf, int num); +int RAND_priv_bytes(unsigned char *buf, int num); +DEPRECATEDIN_1_1_0(int RAND_pseudo_bytes(unsigned char *buf, int num)) + +void RAND_seed(const void *buf, int num); +void RAND_keep_random_devices_open(int keep); + +# if defined(__ANDROID__) && defined(__NDK_FPABI__) +__NDK_FPABI__ /* __attribute__((pcs("aapcs"))) on ARM */ +# endif +void RAND_add(const void *buf, int num, double randomness); +int RAND_load_file(const char *file, long max_bytes); +int RAND_write_file(const char *file); +const char *RAND_file_name(char *file, size_t num); +int RAND_status(void); + +# ifndef OPENSSL_NO_EGD +int RAND_query_egd_bytes(const char *path, unsigned char *buf, int bytes); +int RAND_egd(const char *path); +int RAND_egd_bytes(const char *path, int bytes); +# endif + +int RAND_poll(void); + +# if defined(_WIN32) && (defined(BASETYPES) || defined(_WINDEF_H)) +/* application has to include in order to use these */ +DEPRECATEDIN_1_1_0(void RAND_screen(void)) +DEPRECATEDIN_1_1_0(int RAND_event(UINT, WPARAM, LPARAM)) +# endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/rand_drbg.h b/thrid-party/openssl/openssl/rand_drbg.h new file mode 100644 index 0000000000..45b731b73c --- /dev/null +++ b/thrid-party/openssl/openssl/rand_drbg.h @@ -0,0 +1,130 @@ +/* + * Copyright 2017-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_DRBG_RAND_H +# define HEADER_DRBG_RAND_H + +# include +# include +# include + +/* + * RAND_DRBG flags + * + * Note: if new flags are added, the constant `rand_drbg_used_flags` + * in drbg_lib.c needs to be updated accordingly. + */ + +/* In CTR mode, disable derivation function ctr_df */ +# define RAND_DRBG_FLAG_CTR_NO_DF 0x1 + + +# if OPENSSL_API_COMPAT < 0x10200000L +/* This #define was replaced by an internal constant and should not be used. */ +# define RAND_DRBG_USED_FLAGS (RAND_DRBG_FLAG_CTR_NO_DF) +# endif + +/* + * Default security strength (in the sense of [NIST SP 800-90Ar1]) + * + * NIST SP 800-90Ar1 supports the strength of the DRBG being smaller than that + * of the cipher by collecting less entropy. The current DRBG implementation + * does not take RAND_DRBG_STRENGTH into account and sets the strength of the + * DRBG to that of the cipher. + * + * RAND_DRBG_STRENGTH is currently only used for the legacy RAND + * implementation. + * + * Currently supported ciphers are: NID_aes_128_ctr, NID_aes_192_ctr and + * NID_aes_256_ctr + */ +# define RAND_DRBG_STRENGTH 256 +/* Default drbg type */ +# define RAND_DRBG_TYPE NID_aes_256_ctr +/* Default drbg flags */ +# define RAND_DRBG_FLAGS 0 + + +# ifdef __cplusplus +extern "C" { +# endif + +/* + * Object lifetime functions. + */ +RAND_DRBG *RAND_DRBG_new(int type, unsigned int flags, RAND_DRBG *parent); +RAND_DRBG *RAND_DRBG_secure_new(int type, unsigned int flags, RAND_DRBG *parent); +int RAND_DRBG_set(RAND_DRBG *drbg, int type, unsigned int flags); +int RAND_DRBG_set_defaults(int type, unsigned int flags); +int RAND_DRBG_instantiate(RAND_DRBG *drbg, + const unsigned char *pers, size_t perslen); +int RAND_DRBG_uninstantiate(RAND_DRBG *drbg); +void RAND_DRBG_free(RAND_DRBG *drbg); + +/* + * Object "use" functions. + */ +int RAND_DRBG_reseed(RAND_DRBG *drbg, + const unsigned char *adin, size_t adinlen, + int prediction_resistance); +int RAND_DRBG_generate(RAND_DRBG *drbg, unsigned char *out, size_t outlen, + int prediction_resistance, + const unsigned char *adin, size_t adinlen); +int RAND_DRBG_bytes(RAND_DRBG *drbg, unsigned char *out, size_t outlen); + +int RAND_DRBG_set_reseed_interval(RAND_DRBG *drbg, unsigned int interval); +int RAND_DRBG_set_reseed_time_interval(RAND_DRBG *drbg, time_t interval); + +int RAND_DRBG_set_reseed_defaults( + unsigned int master_reseed_interval, + unsigned int slave_reseed_interval, + time_t master_reseed_time_interval, + time_t slave_reseed_time_interval + ); + +RAND_DRBG *RAND_DRBG_get0_master(void); +RAND_DRBG *RAND_DRBG_get0_public(void); +RAND_DRBG *RAND_DRBG_get0_private(void); + +/* + * EXDATA + */ +# define RAND_DRBG_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_DRBG, l, p, newf, dupf, freef) +int RAND_DRBG_set_ex_data(RAND_DRBG *drbg, int idx, void *arg); +void *RAND_DRBG_get_ex_data(const RAND_DRBG *drbg, int idx); + +/* + * Callback function typedefs + */ +typedef size_t (*RAND_DRBG_get_entropy_fn)(RAND_DRBG *drbg, + unsigned char **pout, + int entropy, size_t min_len, + size_t max_len, + int prediction_resistance); +typedef void (*RAND_DRBG_cleanup_entropy_fn)(RAND_DRBG *ctx, + unsigned char *out, size_t outlen); +typedef size_t (*RAND_DRBG_get_nonce_fn)(RAND_DRBG *drbg, unsigned char **pout, + int entropy, size_t min_len, + size_t max_len); +typedef void (*RAND_DRBG_cleanup_nonce_fn)(RAND_DRBG *drbg, + unsigned char *out, size_t outlen); + +int RAND_DRBG_set_callbacks(RAND_DRBG *drbg, + RAND_DRBG_get_entropy_fn get_entropy, + RAND_DRBG_cleanup_entropy_fn cleanup_entropy, + RAND_DRBG_get_nonce_fn get_nonce, + RAND_DRBG_cleanup_nonce_fn cleanup_nonce); + + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/thrid-party/openssl/openssl/randerr.h b/thrid-party/openssl/openssl/randerr.h new file mode 100644 index 0000000000..70d1a17a4c --- /dev/null +++ b/thrid-party/openssl/openssl/randerr.h @@ -0,0 +1,92 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RANDERR_H +# define HEADER_RANDERR_H + +# include + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_RAND_strings(void); + +/* + * RAND function codes. + */ +# define RAND_F_DRBG_BYTES 101 +# define RAND_F_DRBG_GET_ENTROPY 105 +# define RAND_F_DRBG_SETUP 117 +# define RAND_F_GET_ENTROPY 106 +# define RAND_F_RAND_BYTES 100 +# define RAND_F_RAND_DRBG_ENABLE_LOCKING 119 +# define RAND_F_RAND_DRBG_GENERATE 107 +# define RAND_F_RAND_DRBG_GET_ENTROPY 120 +# define RAND_F_RAND_DRBG_GET_NONCE 123 +# define RAND_F_RAND_DRBG_INSTANTIATE 108 +# define RAND_F_RAND_DRBG_NEW 109 +# define RAND_F_RAND_DRBG_RESEED 110 +# define RAND_F_RAND_DRBG_RESTART 102 +# define RAND_F_RAND_DRBG_SET 104 +# define RAND_F_RAND_DRBG_SET_DEFAULTS 121 +# define RAND_F_RAND_DRBG_UNINSTANTIATE 118 +# define RAND_F_RAND_LOAD_FILE 111 +# define RAND_F_RAND_POOL_ACQUIRE_ENTROPY 122 +# define RAND_F_RAND_POOL_ADD 103 +# define RAND_F_RAND_POOL_ADD_BEGIN 113 +# define RAND_F_RAND_POOL_ADD_END 114 +# define RAND_F_RAND_POOL_ATTACH 124 +# define RAND_F_RAND_POOL_BYTES_NEEDED 115 +# define RAND_F_RAND_POOL_GROW 125 +# define RAND_F_RAND_POOL_NEW 116 +# define RAND_F_RAND_WRITE_FILE 112 + +/* + * RAND reason codes. + */ +# define RAND_R_ADDITIONAL_INPUT_TOO_LONG 102 +# define RAND_R_ALREADY_INSTANTIATED 103 +# define RAND_R_ARGUMENT_OUT_OF_RANGE 105 +# define RAND_R_CANNOT_OPEN_FILE 121 +# define RAND_R_DRBG_ALREADY_INITIALIZED 129 +# define RAND_R_DRBG_NOT_INITIALISED 104 +# define RAND_R_ENTROPY_INPUT_TOO_LONG 106 +# define RAND_R_ENTROPY_OUT_OF_RANGE 124 +# define RAND_R_ERROR_ENTROPY_POOL_WAS_IGNORED 127 +# define RAND_R_ERROR_INITIALISING_DRBG 107 +# define RAND_R_ERROR_INSTANTIATING_DRBG 108 +# define RAND_R_ERROR_RETRIEVING_ADDITIONAL_INPUT 109 +# define RAND_R_ERROR_RETRIEVING_ENTROPY 110 +# define RAND_R_ERROR_RETRIEVING_NONCE 111 +# define RAND_R_FAILED_TO_CREATE_LOCK 126 +# define RAND_R_FUNC_NOT_IMPLEMENTED 101 +# define RAND_R_FWRITE_ERROR 123 +# define RAND_R_GENERATE_ERROR 112 +# define RAND_R_INTERNAL_ERROR 113 +# define RAND_R_IN_ERROR_STATE 114 +# define RAND_R_NOT_A_REGULAR_FILE 122 +# define RAND_R_NOT_INSTANTIATED 115 +# define RAND_R_NO_DRBG_IMPLEMENTATION_SELECTED 128 +# define RAND_R_PARENT_LOCKING_NOT_ENABLED 130 +# define RAND_R_PARENT_STRENGTH_TOO_WEAK 131 +# define RAND_R_PERSONALISATION_STRING_TOO_LONG 116 +# define RAND_R_PREDICTION_RESISTANCE_NOT_SUPPORTED 133 +# define RAND_R_PRNG_NOT_SEEDED 100 +# define RAND_R_RANDOM_POOL_OVERFLOW 125 +# define RAND_R_RANDOM_POOL_UNDERFLOW 134 +# define RAND_R_REQUEST_TOO_LARGE_FOR_DRBG 117 +# define RAND_R_RESEED_ERROR 118 +# define RAND_R_SELFTEST_FAILURE 119 +# define RAND_R_TOO_LITTLE_NONCE_REQUESTED 135 +# define RAND_R_TOO_MUCH_NONCE_REQUESTED 136 +# define RAND_R_UNSUPPORTED_DRBG_FLAGS 132 +# define RAND_R_UNSUPPORTED_DRBG_TYPE 120 + +#endif diff --git a/thrid-party/openssl/openssl/rc2.h b/thrid-party/openssl/openssl/rc2.h new file mode 100644 index 0000000000..585f9e4c38 --- /dev/null +++ b/thrid-party/openssl/openssl/rc2.h @@ -0,0 +1,51 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RC2_H +# define HEADER_RC2_H + +# include + +# ifndef OPENSSL_NO_RC2 +# ifdef __cplusplus +extern "C" { +# endif + +typedef unsigned int RC2_INT; + +# define RC2_ENCRYPT 1 +# define RC2_DECRYPT 0 + +# define RC2_BLOCK 8 +# define RC2_KEY_LENGTH 16 + +typedef struct rc2_key_st { + RC2_INT data[64]; +} RC2_KEY; + +void RC2_set_key(RC2_KEY *key, int len, const unsigned char *data, int bits); +void RC2_ecb_encrypt(const unsigned char *in, unsigned char *out, + RC2_KEY *key, int enc); +void RC2_encrypt(unsigned long *data, RC2_KEY *key); +void RC2_decrypt(unsigned long *data, RC2_KEY *key); +void RC2_cbc_encrypt(const unsigned char *in, unsigned char *out, long length, + RC2_KEY *ks, unsigned char *iv, int enc); +void RC2_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, RC2_KEY *schedule, unsigned char *ivec, + int *num, int enc); +void RC2_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, RC2_KEY *schedule, unsigned char *ivec, + int *num); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/rc4.h b/thrid-party/openssl/openssl/rc4.h new file mode 100644 index 0000000000..86803b37fb --- /dev/null +++ b/thrid-party/openssl/openssl/rc4.h @@ -0,0 +1,36 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RC4_H +# define HEADER_RC4_H + +# include + +# ifndef OPENSSL_NO_RC4 +# include +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct rc4_key_st { + RC4_INT x, y; + RC4_INT data[256]; +} RC4_KEY; + +const char *RC4_options(void); +void RC4_set_key(RC4_KEY *key, int len, const unsigned char *data); +void RC4(RC4_KEY *key, size_t len, const unsigned char *indata, + unsigned char *outdata); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/rc5.h b/thrid-party/openssl/openssl/rc5.h new file mode 100644 index 0000000000..793f88e4e8 --- /dev/null +++ b/thrid-party/openssl/openssl/rc5.h @@ -0,0 +1,63 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RC5_H +# define HEADER_RC5_H + +# include + +# ifndef OPENSSL_NO_RC5 +# ifdef __cplusplus +extern "C" { +# endif + +# define RC5_ENCRYPT 1 +# define RC5_DECRYPT 0 + +# define RC5_32_INT unsigned int + +# define RC5_32_BLOCK 8 +# define RC5_32_KEY_LENGTH 16/* This is a default, max is 255 */ + +/* + * This are the only values supported. Tweak the code if you want more The + * most supported modes will be RC5-32/12/16 RC5-32/16/8 + */ +# define RC5_8_ROUNDS 8 +# define RC5_12_ROUNDS 12 +# define RC5_16_ROUNDS 16 + +typedef struct rc5_key_st { + /* Number of rounds */ + int rounds; + RC5_32_INT data[2 * (RC5_16_ROUNDS + 1)]; +} RC5_32_KEY; + +void RC5_32_set_key(RC5_32_KEY *key, int len, const unsigned char *data, + int rounds); +void RC5_32_ecb_encrypt(const unsigned char *in, unsigned char *out, + RC5_32_KEY *key, int enc); +void RC5_32_encrypt(unsigned long *data, RC5_32_KEY *key); +void RC5_32_decrypt(unsigned long *data, RC5_32_KEY *key); +void RC5_32_cbc_encrypt(const unsigned char *in, unsigned char *out, + long length, RC5_32_KEY *ks, unsigned char *iv, + int enc); +void RC5_32_cfb64_encrypt(const unsigned char *in, unsigned char *out, + long length, RC5_32_KEY *schedule, + unsigned char *ivec, int *num, int enc); +void RC5_32_ofb64_encrypt(const unsigned char *in, unsigned char *out, + long length, RC5_32_KEY *schedule, + unsigned char *ivec, int *num); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/ripemd.h b/thrid-party/openssl/openssl/ripemd.h new file mode 100644 index 0000000000..c42026aa42 --- /dev/null +++ b/thrid-party/openssl/openssl/ripemd.h @@ -0,0 +1,47 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RIPEMD_H +# define HEADER_RIPEMD_H + +# include + +#ifndef OPENSSL_NO_RMD160 +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +# define RIPEMD160_LONG unsigned int + +# define RIPEMD160_CBLOCK 64 +# define RIPEMD160_LBLOCK (RIPEMD160_CBLOCK/4) +# define RIPEMD160_DIGEST_LENGTH 20 + +typedef struct RIPEMD160state_st { + RIPEMD160_LONG A, B, C, D, E; + RIPEMD160_LONG Nl, Nh; + RIPEMD160_LONG data[RIPEMD160_LBLOCK]; + unsigned int num; +} RIPEMD160_CTX; + +int RIPEMD160_Init(RIPEMD160_CTX *c); +int RIPEMD160_Update(RIPEMD160_CTX *c, const void *data, size_t len); +int RIPEMD160_Final(unsigned char *md, RIPEMD160_CTX *c); +unsigned char *RIPEMD160(const unsigned char *d, size_t n, unsigned char *md); +void RIPEMD160_Transform(RIPEMD160_CTX *c, const unsigned char *b); + +# ifdef __cplusplus +} +# endif +# endif + + +#endif diff --git a/thrid-party/openssl/openssl/rsa.h b/thrid-party/openssl/openssl/rsa.h new file mode 100644 index 0000000000..cdce1264eb --- /dev/null +++ b/thrid-party/openssl/openssl/rsa.h @@ -0,0 +1,512 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RSA_H +# define HEADER_RSA_H + +# include + +# ifndef OPENSSL_NO_RSA +# include +# include +# include +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif +# include +# ifdef __cplusplus +extern "C" { +# endif + +/* The types RSA and RSA_METHOD are defined in ossl_typ.h */ + +# ifndef OPENSSL_RSA_MAX_MODULUS_BITS +# define OPENSSL_RSA_MAX_MODULUS_BITS 16384 +# endif + +# define OPENSSL_RSA_FIPS_MIN_MODULUS_BITS 1024 + +# ifndef OPENSSL_RSA_SMALL_MODULUS_BITS +# define OPENSSL_RSA_SMALL_MODULUS_BITS 3072 +# endif +# ifndef OPENSSL_RSA_MAX_PUBEXP_BITS + +/* exponent limit enforced for "large" modulus only */ +# define OPENSSL_RSA_MAX_PUBEXP_BITS 64 +# endif + +# define RSA_3 0x3L +# define RSA_F4 0x10001L + +/* based on RFC 8017 appendix A.1.2 */ +# define RSA_ASN1_VERSION_DEFAULT 0 +# define RSA_ASN1_VERSION_MULTI 1 + +# define RSA_DEFAULT_PRIME_NUM 2 + +# define RSA_METHOD_FLAG_NO_CHECK 0x0001/* don't check pub/private + * match */ + +# define RSA_FLAG_CACHE_PUBLIC 0x0002 +# define RSA_FLAG_CACHE_PRIVATE 0x0004 +# define RSA_FLAG_BLINDING 0x0008 +# define RSA_FLAG_THREAD_SAFE 0x0010 +/* + * This flag means the private key operations will be handled by rsa_mod_exp + * and that they do not depend on the private key components being present: + * for example a key stored in external hardware. Without this flag + * bn_mod_exp gets called when private key components are absent. + */ +# define RSA_FLAG_EXT_PKEY 0x0020 + +/* + * new with 0.9.6j and 0.9.7b; the built-in + * RSA implementation now uses blinding by + * default (ignoring RSA_FLAG_BLINDING), + * but other engines might not need it + */ +# define RSA_FLAG_NO_BLINDING 0x0080 +# if OPENSSL_API_COMPAT < 0x10100000L +/* + * Does nothing. Previously this switched off constant time behaviour. + */ +# define RSA_FLAG_NO_CONSTTIME 0x0000 +# endif +# if OPENSSL_API_COMPAT < 0x00908000L +/* deprecated name for the flag*/ +/* + * new with 0.9.7h; the built-in RSA + * implementation now uses constant time + * modular exponentiation for secret exponents + * by default. This flag causes the + * faster variable sliding window method to + * be used for all exponents. + */ +# define RSA_FLAG_NO_EXP_CONSTTIME RSA_FLAG_NO_CONSTTIME +# endif + +# define EVP_PKEY_CTX_set_rsa_padding(ctx, pad) \ + RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_RSA_PADDING, pad, NULL) + +# define EVP_PKEY_CTX_get_rsa_padding(ctx, ppad) \ + RSA_pkey_ctx_ctrl(ctx, -1, EVP_PKEY_CTRL_GET_RSA_PADDING, 0, ppad) + +# define EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, len) \ + RSA_pkey_ctx_ctrl(ctx, (EVP_PKEY_OP_SIGN|EVP_PKEY_OP_VERIFY), \ + EVP_PKEY_CTRL_RSA_PSS_SALTLEN, len, NULL) +/* Salt length matches digest */ +# define RSA_PSS_SALTLEN_DIGEST -1 +/* Verify only: auto detect salt length */ +# define RSA_PSS_SALTLEN_AUTO -2 +/* Set salt length to maximum possible */ +# define RSA_PSS_SALTLEN_MAX -3 +/* Old compatible max salt length for sign only */ +# define RSA_PSS_SALTLEN_MAX_SIGN -2 + +# define EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx, len) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA_PSS, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_PSS_SALTLEN, len, NULL) + +# define EVP_PKEY_CTX_get_rsa_pss_saltlen(ctx, plen) \ + RSA_pkey_ctx_ctrl(ctx, (EVP_PKEY_OP_SIGN|EVP_PKEY_OP_VERIFY), \ + EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN, 0, plen) + +# define EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) \ + RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL) + +# define EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx, pubexp) \ + RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP, 0, pubexp) + +# define EVP_PKEY_CTX_set_rsa_keygen_primes(ctx, primes) \ + RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_KEYGEN_PRIMES, primes, NULL) + +# define EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) \ + RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_RSA_MGF1_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA_PSS, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_MGF1_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_RSA_OAEP_MD, 0, (void *)(md)) + +# define EVP_PKEY_CTX_get_rsa_mgf1_md(ctx, pmd) \ + RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_TYPE_SIG | EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_GET_RSA_MGF1_MD, 0, (void *)(pmd)) + +# define EVP_PKEY_CTX_get_rsa_oaep_md(ctx, pmd) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_GET_RSA_OAEP_MD, 0, (void *)(pmd)) + +# define EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, l, llen) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_RSA_OAEP_LABEL, llen, (void *)(l)) + +# define EVP_PKEY_CTX_get0_rsa_oaep_label(ctx, l) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_CRYPT, \ + EVP_PKEY_CTRL_GET_RSA_OAEP_LABEL, 0, (void *)(l)) + +# define EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, md) \ + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA_PSS, \ + EVP_PKEY_OP_KEYGEN, EVP_PKEY_CTRL_MD, \ + 0, (void *)(md)) + +# define EVP_PKEY_CTRL_RSA_PADDING (EVP_PKEY_ALG_CTRL + 1) +# define EVP_PKEY_CTRL_RSA_PSS_SALTLEN (EVP_PKEY_ALG_CTRL + 2) + +# define EVP_PKEY_CTRL_RSA_KEYGEN_BITS (EVP_PKEY_ALG_CTRL + 3) +# define EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP (EVP_PKEY_ALG_CTRL + 4) +# define EVP_PKEY_CTRL_RSA_MGF1_MD (EVP_PKEY_ALG_CTRL + 5) + +# define EVP_PKEY_CTRL_GET_RSA_PADDING (EVP_PKEY_ALG_CTRL + 6) +# define EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN (EVP_PKEY_ALG_CTRL + 7) +# define EVP_PKEY_CTRL_GET_RSA_MGF1_MD (EVP_PKEY_ALG_CTRL + 8) + +# define EVP_PKEY_CTRL_RSA_OAEP_MD (EVP_PKEY_ALG_CTRL + 9) +# define EVP_PKEY_CTRL_RSA_OAEP_LABEL (EVP_PKEY_ALG_CTRL + 10) + +# define EVP_PKEY_CTRL_GET_RSA_OAEP_MD (EVP_PKEY_ALG_CTRL + 11) +# define EVP_PKEY_CTRL_GET_RSA_OAEP_LABEL (EVP_PKEY_ALG_CTRL + 12) + +# define EVP_PKEY_CTRL_RSA_KEYGEN_PRIMES (EVP_PKEY_ALG_CTRL + 13) + +# define RSA_PKCS1_PADDING 1 +# define RSA_SSLV23_PADDING 2 +# define RSA_NO_PADDING 3 +# define RSA_PKCS1_OAEP_PADDING 4 +# define RSA_X931_PADDING 5 +/* EVP_PKEY_ only */ +# define RSA_PKCS1_PSS_PADDING 6 + +# define RSA_PKCS1_PADDING_SIZE 11 + +# define RSA_set_app_data(s,arg) RSA_set_ex_data(s,0,arg) +# define RSA_get_app_data(s) RSA_get_ex_data(s,0) + +RSA *RSA_new(void); +RSA *RSA_new_method(ENGINE *engine); +int RSA_bits(const RSA *rsa); +int RSA_size(const RSA *rsa); +int RSA_security_bits(const RSA *rsa); + +int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); +int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); +int RSA_set0_crt_params(RSA *r,BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); +int RSA_set0_multi_prime_params(RSA *r, BIGNUM *primes[], BIGNUM *exps[], + BIGNUM *coeffs[], int pnum); +void RSA_get0_key(const RSA *r, + const BIGNUM **n, const BIGNUM **e, const BIGNUM **d); +void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); +int RSA_get_multi_prime_extra_count(const RSA *r); +int RSA_get0_multi_prime_factors(const RSA *r, const BIGNUM *primes[]); +void RSA_get0_crt_params(const RSA *r, + const BIGNUM **dmp1, const BIGNUM **dmq1, + const BIGNUM **iqmp); +int RSA_get0_multi_prime_crt_params(const RSA *r, const BIGNUM *exps[], + const BIGNUM *coeffs[]); +const BIGNUM *RSA_get0_n(const RSA *d); +const BIGNUM *RSA_get0_e(const RSA *d); +const BIGNUM *RSA_get0_d(const RSA *d); +const BIGNUM *RSA_get0_p(const RSA *d); +const BIGNUM *RSA_get0_q(const RSA *d); +const BIGNUM *RSA_get0_dmp1(const RSA *r); +const BIGNUM *RSA_get0_dmq1(const RSA *r); +const BIGNUM *RSA_get0_iqmp(const RSA *r); +void RSA_clear_flags(RSA *r, int flags); +int RSA_test_flags(const RSA *r, int flags); +void RSA_set_flags(RSA *r, int flags); +int RSA_get_version(RSA *r); +ENGINE *RSA_get0_engine(const RSA *r); + +/* Deprecated version */ +DEPRECATEDIN_0_9_8(RSA *RSA_generate_key(int bits, unsigned long e, void + (*callback) (int, int, void *), + void *cb_arg)) + +/* New version */ +int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); +/* Multi-prime version */ +int RSA_generate_multi_prime_key(RSA *rsa, int bits, int primes, + BIGNUM *e, BN_GENCB *cb); + +int RSA_X931_derive_ex(RSA *rsa, BIGNUM *p1, BIGNUM *p2, BIGNUM *q1, + BIGNUM *q2, const BIGNUM *Xp1, const BIGNUM *Xp2, + const BIGNUM *Xp, const BIGNUM *Xq1, const BIGNUM *Xq2, + const BIGNUM *Xq, const BIGNUM *e, BN_GENCB *cb); +int RSA_X931_generate_key_ex(RSA *rsa, int bits, const BIGNUM *e, + BN_GENCB *cb); + +int RSA_check_key(const RSA *); +int RSA_check_key_ex(const RSA *, BN_GENCB *cb); + /* next 4 return -1 on error */ +int RSA_public_encrypt(int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_private_encrypt(int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_public_decrypt(int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_private_decrypt(int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +void RSA_free(RSA *r); +/* "up" the RSA object's reference count */ +int RSA_up_ref(RSA *r); + +int RSA_flags(const RSA *r); + +void RSA_set_default_method(const RSA_METHOD *meth); +const RSA_METHOD *RSA_get_default_method(void); +const RSA_METHOD *RSA_null_method(void); +const RSA_METHOD *RSA_get_method(const RSA *rsa); +int RSA_set_method(RSA *rsa, const RSA_METHOD *meth); + +/* these are the actual RSA functions */ +const RSA_METHOD *RSA_PKCS1_OpenSSL(void); + +int RSA_pkey_ctx_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2); + +DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPublicKey) +DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPrivateKey) + +typedef struct rsa_pss_params_st { + X509_ALGOR *hashAlgorithm; + X509_ALGOR *maskGenAlgorithm; + ASN1_INTEGER *saltLength; + ASN1_INTEGER *trailerField; + /* Decoded hash algorithm from maskGenAlgorithm */ + X509_ALGOR *maskHash; +} RSA_PSS_PARAMS; + +DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS) + +typedef struct rsa_oaep_params_st { + X509_ALGOR *hashFunc; + X509_ALGOR *maskGenFunc; + X509_ALGOR *pSourceFunc; + /* Decoded hash algorithm from maskGenFunc */ + X509_ALGOR *maskHash; +} RSA_OAEP_PARAMS; + +DECLARE_ASN1_FUNCTIONS(RSA_OAEP_PARAMS) + +# ifndef OPENSSL_NO_STDIO +int RSA_print_fp(FILE *fp, const RSA *r, int offset); +# endif + +int RSA_print(BIO *bp, const RSA *r, int offset); + +/* + * The following 2 functions sign and verify a X509_SIG ASN1 object inside + * PKCS#1 padded RSA encryption + */ +int RSA_sign(int type, const unsigned char *m, unsigned int m_length, + unsigned char *sigret, unsigned int *siglen, RSA *rsa); +int RSA_verify(int type, const unsigned char *m, unsigned int m_length, + const unsigned char *sigbuf, unsigned int siglen, RSA *rsa); + +/* + * The following 2 function sign and verify a ASN1_OCTET_STRING object inside + * PKCS#1 padded RSA encryption + */ +int RSA_sign_ASN1_OCTET_STRING(int type, + const unsigned char *m, unsigned int m_length, + unsigned char *sigret, unsigned int *siglen, + RSA *rsa); +int RSA_verify_ASN1_OCTET_STRING(int type, const unsigned char *m, + unsigned int m_length, unsigned char *sigbuf, + unsigned int siglen, RSA *rsa); + +int RSA_blinding_on(RSA *rsa, BN_CTX *ctx); +void RSA_blinding_off(RSA *rsa); +BN_BLINDING *RSA_setup_blinding(RSA *rsa, BN_CTX *ctx); + +int RSA_padding_add_PKCS1_type_1(unsigned char *to, int tlen, + const unsigned char *f, int fl); +int RSA_padding_check_PKCS1_type_1(unsigned char *to, int tlen, + const unsigned char *f, int fl, + int rsa_len); +int RSA_padding_add_PKCS1_type_2(unsigned char *to, int tlen, + const unsigned char *f, int fl); +int RSA_padding_check_PKCS1_type_2(unsigned char *to, int tlen, + const unsigned char *f, int fl, + int rsa_len); +int PKCS1_MGF1(unsigned char *mask, long len, const unsigned char *seed, + long seedlen, const EVP_MD *dgst); +int RSA_padding_add_PKCS1_OAEP(unsigned char *to, int tlen, + const unsigned char *f, int fl, + const unsigned char *p, int pl); +int RSA_padding_check_PKCS1_OAEP(unsigned char *to, int tlen, + const unsigned char *f, int fl, int rsa_len, + const unsigned char *p, int pl); +int RSA_padding_add_PKCS1_OAEP_mgf1(unsigned char *to, int tlen, + const unsigned char *from, int flen, + const unsigned char *param, int plen, + const EVP_MD *md, const EVP_MD *mgf1md); +int RSA_padding_check_PKCS1_OAEP_mgf1(unsigned char *to, int tlen, + const unsigned char *from, int flen, + int num, const unsigned char *param, + int plen, const EVP_MD *md, + const EVP_MD *mgf1md); +int RSA_padding_add_SSLv23(unsigned char *to, int tlen, + const unsigned char *f, int fl); +int RSA_padding_check_SSLv23(unsigned char *to, int tlen, + const unsigned char *f, int fl, int rsa_len); +int RSA_padding_add_none(unsigned char *to, int tlen, const unsigned char *f, + int fl); +int RSA_padding_check_none(unsigned char *to, int tlen, + const unsigned char *f, int fl, int rsa_len); +int RSA_padding_add_X931(unsigned char *to, int tlen, const unsigned char *f, + int fl); +int RSA_padding_check_X931(unsigned char *to, int tlen, + const unsigned char *f, int fl, int rsa_len); +int RSA_X931_hash_id(int nid); + +int RSA_verify_PKCS1_PSS(RSA *rsa, const unsigned char *mHash, + const EVP_MD *Hash, const unsigned char *EM, + int sLen); +int RSA_padding_add_PKCS1_PSS(RSA *rsa, unsigned char *EM, + const unsigned char *mHash, const EVP_MD *Hash, + int sLen); + +int RSA_verify_PKCS1_PSS_mgf1(RSA *rsa, const unsigned char *mHash, + const EVP_MD *Hash, const EVP_MD *mgf1Hash, + const unsigned char *EM, int sLen); + +int RSA_padding_add_PKCS1_PSS_mgf1(RSA *rsa, unsigned char *EM, + const unsigned char *mHash, + const EVP_MD *Hash, const EVP_MD *mgf1Hash, + int sLen); + +#define RSA_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_RSA, l, p, newf, dupf, freef) +int RSA_set_ex_data(RSA *r, int idx, void *arg); +void *RSA_get_ex_data(const RSA *r, int idx); + +RSA *RSAPublicKey_dup(RSA *rsa); +RSA *RSAPrivateKey_dup(RSA *rsa); + +/* + * If this flag is set the RSA method is FIPS compliant and can be used in + * FIPS mode. This is set in the validated module method. If an application + * sets this flag in its own methods it is its responsibility to ensure the + * result is compliant. + */ + +# define RSA_FLAG_FIPS_METHOD 0x0400 + +/* + * If this flag is set the operations normally disabled in FIPS mode are + * permitted it is then the applications responsibility to ensure that the + * usage is compliant. + */ + +# define RSA_FLAG_NON_FIPS_ALLOW 0x0400 +/* + * Application has decided PRNG is good enough to generate a key: don't + * check. + */ +# define RSA_FLAG_CHECKED 0x0800 + +RSA_METHOD *RSA_meth_new(const char *name, int flags); +void RSA_meth_free(RSA_METHOD *meth); +RSA_METHOD *RSA_meth_dup(const RSA_METHOD *meth); +const char *RSA_meth_get0_name(const RSA_METHOD *meth); +int RSA_meth_set1_name(RSA_METHOD *meth, const char *name); +int RSA_meth_get_flags(const RSA_METHOD *meth); +int RSA_meth_set_flags(RSA_METHOD *meth, int flags); +void *RSA_meth_get0_app_data(const RSA_METHOD *meth); +int RSA_meth_set0_app_data(RSA_METHOD *meth, void *app_data); +int (*RSA_meth_get_pub_enc(const RSA_METHOD *meth)) + (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_meth_set_pub_enc(RSA_METHOD *rsa, + int (*pub_enc) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +int (*RSA_meth_get_pub_dec(const RSA_METHOD *meth)) + (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_meth_set_pub_dec(RSA_METHOD *rsa, + int (*pub_dec) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +int (*RSA_meth_get_priv_enc(const RSA_METHOD *meth)) + (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_meth_set_priv_enc(RSA_METHOD *rsa, + int (*priv_enc) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +int (*RSA_meth_get_priv_dec(const RSA_METHOD *meth)) + (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, int padding); +int RSA_meth_set_priv_dec(RSA_METHOD *rsa, + int (*priv_dec) (int flen, const unsigned char *from, + unsigned char *to, RSA *rsa, + int padding)); +int (*RSA_meth_get_mod_exp(const RSA_METHOD *meth)) + (BIGNUM *r0, const BIGNUM *i, RSA *rsa, BN_CTX *ctx); +int RSA_meth_set_mod_exp(RSA_METHOD *rsa, + int (*mod_exp) (BIGNUM *r0, const BIGNUM *i, RSA *rsa, + BN_CTX *ctx)); +int (*RSA_meth_get_bn_mod_exp(const RSA_METHOD *meth)) + (BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx); +int RSA_meth_set_bn_mod_exp(RSA_METHOD *rsa, + int (*bn_mod_exp) (BIGNUM *r, + const BIGNUM *a, + const BIGNUM *p, + const BIGNUM *m, + BN_CTX *ctx, + BN_MONT_CTX *m_ctx)); +int (*RSA_meth_get_init(const RSA_METHOD *meth)) (RSA *rsa); +int RSA_meth_set_init(RSA_METHOD *rsa, int (*init) (RSA *rsa)); +int (*RSA_meth_get_finish(const RSA_METHOD *meth)) (RSA *rsa); +int RSA_meth_set_finish(RSA_METHOD *rsa, int (*finish) (RSA *rsa)); +int (*RSA_meth_get_sign(const RSA_METHOD *meth)) + (int type, + const unsigned char *m, unsigned int m_length, + unsigned char *sigret, unsigned int *siglen, + const RSA *rsa); +int RSA_meth_set_sign(RSA_METHOD *rsa, + int (*sign) (int type, const unsigned char *m, + unsigned int m_length, + unsigned char *sigret, unsigned int *siglen, + const RSA *rsa)); +int (*RSA_meth_get_verify(const RSA_METHOD *meth)) + (int dtype, const unsigned char *m, + unsigned int m_length, const unsigned char *sigbuf, + unsigned int siglen, const RSA *rsa); +int RSA_meth_set_verify(RSA_METHOD *rsa, + int (*verify) (int dtype, const unsigned char *m, + unsigned int m_length, + const unsigned char *sigbuf, + unsigned int siglen, const RSA *rsa)); +int (*RSA_meth_get_keygen(const RSA_METHOD *meth)) + (RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); +int RSA_meth_set_keygen(RSA_METHOD *rsa, + int (*keygen) (RSA *rsa, int bits, BIGNUM *e, + BN_GENCB *cb)); +int (*RSA_meth_get_multi_prime_keygen(const RSA_METHOD *meth)) + (RSA *rsa, int bits, int primes, BIGNUM *e, BN_GENCB *cb); +int RSA_meth_set_multi_prime_keygen(RSA_METHOD *meth, + int (*keygen) (RSA *rsa, int bits, + int primes, BIGNUM *e, + BN_GENCB *cb)); + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/rsaerr.h b/thrid-party/openssl/openssl/rsaerr.h new file mode 100644 index 0000000000..59b15e13e9 --- /dev/null +++ b/thrid-party/openssl/openssl/rsaerr.h @@ -0,0 +1,167 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_RSAERR_H +# define HEADER_RSAERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_RSA_strings(void); + +/* + * RSA function codes. + */ +# define RSA_F_CHECK_PADDING_MD 140 +# define RSA_F_ENCODE_PKCS1 146 +# define RSA_F_INT_RSA_VERIFY 145 +# define RSA_F_OLD_RSA_PRIV_DECODE 147 +# define RSA_F_PKEY_PSS_INIT 165 +# define RSA_F_PKEY_RSA_CTRL 143 +# define RSA_F_PKEY_RSA_CTRL_STR 144 +# define RSA_F_PKEY_RSA_SIGN 142 +# define RSA_F_PKEY_RSA_VERIFY 149 +# define RSA_F_PKEY_RSA_VERIFYRECOVER 141 +# define RSA_F_RSA_ALGOR_TO_MD 156 +# define RSA_F_RSA_BUILTIN_KEYGEN 129 +# define RSA_F_RSA_CHECK_KEY 123 +# define RSA_F_RSA_CHECK_KEY_EX 160 +# define RSA_F_RSA_CMS_DECRYPT 159 +# define RSA_F_RSA_CMS_VERIFY 158 +# define RSA_F_RSA_ITEM_VERIFY 148 +# define RSA_F_RSA_METH_DUP 161 +# define RSA_F_RSA_METH_NEW 162 +# define RSA_F_RSA_METH_SET1_NAME 163 +# define RSA_F_RSA_MGF1_TO_MD 157 +# define RSA_F_RSA_MULTIP_INFO_NEW 166 +# define RSA_F_RSA_NEW_METHOD 106 +# define RSA_F_RSA_NULL 124 +# define RSA_F_RSA_NULL_PRIVATE_DECRYPT 132 +# define RSA_F_RSA_NULL_PRIVATE_ENCRYPT 133 +# define RSA_F_RSA_NULL_PUBLIC_DECRYPT 134 +# define RSA_F_RSA_NULL_PUBLIC_ENCRYPT 135 +# define RSA_F_RSA_OSSL_PRIVATE_DECRYPT 101 +# define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT 102 +# define RSA_F_RSA_OSSL_PUBLIC_DECRYPT 103 +# define RSA_F_RSA_OSSL_PUBLIC_ENCRYPT 104 +# define RSA_F_RSA_PADDING_ADD_NONE 107 +# define RSA_F_RSA_PADDING_ADD_PKCS1_OAEP 121 +# define RSA_F_RSA_PADDING_ADD_PKCS1_OAEP_MGF1 154 +# define RSA_F_RSA_PADDING_ADD_PKCS1_PSS 125 +# define RSA_F_RSA_PADDING_ADD_PKCS1_PSS_MGF1 152 +# define RSA_F_RSA_PADDING_ADD_PKCS1_TYPE_1 108 +# define RSA_F_RSA_PADDING_ADD_PKCS1_TYPE_2 109 +# define RSA_F_RSA_PADDING_ADD_SSLV23 110 +# define RSA_F_RSA_PADDING_ADD_X931 127 +# define RSA_F_RSA_PADDING_CHECK_NONE 111 +# define RSA_F_RSA_PADDING_CHECK_PKCS1_OAEP 122 +# define RSA_F_RSA_PADDING_CHECK_PKCS1_OAEP_MGF1 153 +# define RSA_F_RSA_PADDING_CHECK_PKCS1_TYPE_1 112 +# define RSA_F_RSA_PADDING_CHECK_PKCS1_TYPE_2 113 +# define RSA_F_RSA_PADDING_CHECK_SSLV23 114 +# define RSA_F_RSA_PADDING_CHECK_X931 128 +# define RSA_F_RSA_PARAM_DECODE 164 +# define RSA_F_RSA_PRINT 115 +# define RSA_F_RSA_PRINT_FP 116 +# define RSA_F_RSA_PRIV_DECODE 150 +# define RSA_F_RSA_PRIV_ENCODE 138 +# define RSA_F_RSA_PSS_GET_PARAM 151 +# define RSA_F_RSA_PSS_TO_CTX 155 +# define RSA_F_RSA_PUB_DECODE 139 +# define RSA_F_RSA_SETUP_BLINDING 136 +# define RSA_F_RSA_SIGN 117 +# define RSA_F_RSA_SIGN_ASN1_OCTET_STRING 118 +# define RSA_F_RSA_VERIFY 119 +# define RSA_F_RSA_VERIFY_ASN1_OCTET_STRING 120 +# define RSA_F_RSA_VERIFY_PKCS1_PSS_MGF1 126 +# define RSA_F_SETUP_TBUF 167 + +/* + * RSA reason codes. + */ +# define RSA_R_ALGORITHM_MISMATCH 100 +# define RSA_R_BAD_E_VALUE 101 +# define RSA_R_BAD_FIXED_HEADER_DECRYPT 102 +# define RSA_R_BAD_PAD_BYTE_COUNT 103 +# define RSA_R_BAD_SIGNATURE 104 +# define RSA_R_BLOCK_TYPE_IS_NOT_01 106 +# define RSA_R_BLOCK_TYPE_IS_NOT_02 107 +# define RSA_R_DATA_GREATER_THAN_MOD_LEN 108 +# define RSA_R_DATA_TOO_LARGE 109 +# define RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE 110 +# define RSA_R_DATA_TOO_LARGE_FOR_MODULUS 132 +# define RSA_R_DATA_TOO_SMALL 111 +# define RSA_R_DATA_TOO_SMALL_FOR_KEY_SIZE 122 +# define RSA_R_DIGEST_DOES_NOT_MATCH 158 +# define RSA_R_DIGEST_NOT_ALLOWED 145 +# define RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY 112 +# define RSA_R_DMP1_NOT_CONGRUENT_TO_D 124 +# define RSA_R_DMQ1_NOT_CONGRUENT_TO_D 125 +# define RSA_R_D_E_NOT_CONGRUENT_TO_1 123 +# define RSA_R_FIRST_OCTET_INVALID 133 +# define RSA_R_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE 144 +# define RSA_R_INVALID_DIGEST 157 +# define RSA_R_INVALID_DIGEST_LENGTH 143 +# define RSA_R_INVALID_HEADER 137 +# define RSA_R_INVALID_LABEL 160 +# define RSA_R_INVALID_MESSAGE_LENGTH 131 +# define RSA_R_INVALID_MGF1_MD 156 +# define RSA_R_INVALID_MULTI_PRIME_KEY 167 +# define RSA_R_INVALID_OAEP_PARAMETERS 161 +# define RSA_R_INVALID_PADDING 138 +# define RSA_R_INVALID_PADDING_MODE 141 +# define RSA_R_INVALID_PSS_PARAMETERS 149 +# define RSA_R_INVALID_PSS_SALTLEN 146 +# define RSA_R_INVALID_SALT_LENGTH 150 +# define RSA_R_INVALID_TRAILER 139 +# define RSA_R_INVALID_X931_DIGEST 142 +# define RSA_R_IQMP_NOT_INVERSE_OF_Q 126 +# define RSA_R_KEY_PRIME_NUM_INVALID 165 +# define RSA_R_KEY_SIZE_TOO_SMALL 120 +# define RSA_R_LAST_OCTET_INVALID 134 +# define RSA_R_MISSING_PRIVATE_KEY 179 +# define RSA_R_MGF1_DIGEST_NOT_ALLOWED 152 +# define RSA_R_MODULUS_TOO_LARGE 105 +# define RSA_R_MP_COEFFICIENT_NOT_INVERSE_OF_R 168 +# define RSA_R_MP_EXPONENT_NOT_CONGRUENT_TO_D 169 +# define RSA_R_MP_R_NOT_PRIME 170 +# define RSA_R_NO_PUBLIC_EXPONENT 140 +# define RSA_R_NULL_BEFORE_BLOCK_MISSING 113 +# define RSA_R_N_DOES_NOT_EQUAL_PRODUCT_OF_PRIMES 172 +# define RSA_R_N_DOES_NOT_EQUAL_P_Q 127 +# define RSA_R_OAEP_DECODING_ERROR 121 +# define RSA_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 148 +# define RSA_R_PADDING_CHECK_FAILED 114 +# define RSA_R_PKCS_DECODING_ERROR 159 +# define RSA_R_PSS_SALTLEN_TOO_SMALL 164 +# define RSA_R_P_NOT_PRIME 128 +# define RSA_R_Q_NOT_PRIME 129 +# define RSA_R_RSA_OPERATIONS_NOT_SUPPORTED 130 +# define RSA_R_SLEN_CHECK_FAILED 136 +# define RSA_R_SLEN_RECOVERY_FAILED 135 +# define RSA_R_SSLV3_ROLLBACK_ATTACK 115 +# define RSA_R_THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD 116 +# define RSA_R_UNKNOWN_ALGORITHM_TYPE 117 +# define RSA_R_UNKNOWN_DIGEST 166 +# define RSA_R_UNKNOWN_MASK_DIGEST 151 +# define RSA_R_UNKNOWN_PADDING_TYPE 118 +# define RSA_R_UNSUPPORTED_ENCRYPTION_TYPE 162 +# define RSA_R_UNSUPPORTED_LABEL_SOURCE 163 +# define RSA_R_UNSUPPORTED_MASK_ALGORITHM 153 +# define RSA_R_UNSUPPORTED_MASK_PARAMETER 154 +# define RSA_R_UNSUPPORTED_SIGNATURE_TYPE 155 +# define RSA_R_VALUE_MISSING 147 +# define RSA_R_WRONG_SIGNATURE_LENGTH 119 + +#endif diff --git a/thrid-party/openssl/openssl/safestack.h b/thrid-party/openssl/openssl/safestack.h new file mode 100644 index 0000000000..38b5578978 --- /dev/null +++ b/thrid-party/openssl/openssl/safestack.h @@ -0,0 +1,207 @@ +/* + * Copyright 1999-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SAFESTACK_H +# define HEADER_SAFESTACK_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# define STACK_OF(type) struct stack_st_##type + +# define SKM_DEFINE_STACK_OF(t1, t2, t3) \ + STACK_OF(t1); \ + typedef int (*sk_##t1##_compfunc)(const t3 * const *a, const t3 *const *b); \ + typedef void (*sk_##t1##_freefunc)(t3 *a); \ + typedef t3 * (*sk_##t1##_copyfunc)(const t3 *a); \ + static ossl_unused ossl_inline int sk_##t1##_num(const STACK_OF(t1) *sk) \ + { \ + return OPENSSL_sk_num((const OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_value(const STACK_OF(t1) *sk, int idx) \ + { \ + return (t2 *)OPENSSL_sk_value((const OPENSSL_STACK *)sk, idx); \ + } \ + static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_new(sk_##t1##_compfunc compare) \ + { \ + return (STACK_OF(t1) *)OPENSSL_sk_new((OPENSSL_sk_compfunc)compare); \ + } \ + static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_new_null(void) \ + { \ + return (STACK_OF(t1) *)OPENSSL_sk_new_null(); \ + } \ + static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_new_reserve(sk_##t1##_compfunc compare, int n) \ + { \ + return (STACK_OF(t1) *)OPENSSL_sk_new_reserve((OPENSSL_sk_compfunc)compare, n); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_reserve(STACK_OF(t1) *sk, int n) \ + { \ + return OPENSSL_sk_reserve((OPENSSL_STACK *)sk, n); \ + } \ + static ossl_unused ossl_inline void sk_##t1##_free(STACK_OF(t1) *sk) \ + { \ + OPENSSL_sk_free((OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline void sk_##t1##_zero(STACK_OF(t1) *sk) \ + { \ + OPENSSL_sk_zero((OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_delete(STACK_OF(t1) *sk, int i) \ + { \ + return (t2 *)OPENSSL_sk_delete((OPENSSL_STACK *)sk, i); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_delete_ptr(STACK_OF(t1) *sk, t2 *ptr) \ + { \ + return (t2 *)OPENSSL_sk_delete_ptr((OPENSSL_STACK *)sk, \ + (const void *)ptr); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_push(STACK_OF(t1) *sk, t2 *ptr) \ + { \ + return OPENSSL_sk_push((OPENSSL_STACK *)sk, (const void *)ptr); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_unshift(STACK_OF(t1) *sk, t2 *ptr) \ + { \ + return OPENSSL_sk_unshift((OPENSSL_STACK *)sk, (const void *)ptr); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_pop(STACK_OF(t1) *sk) \ + { \ + return (t2 *)OPENSSL_sk_pop((OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_shift(STACK_OF(t1) *sk) \ + { \ + return (t2 *)OPENSSL_sk_shift((OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline void sk_##t1##_pop_free(STACK_OF(t1) *sk, sk_##t1##_freefunc freefunc) \ + { \ + OPENSSL_sk_pop_free((OPENSSL_STACK *)sk, (OPENSSL_sk_freefunc)freefunc); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_insert(STACK_OF(t1) *sk, t2 *ptr, int idx) \ + { \ + return OPENSSL_sk_insert((OPENSSL_STACK *)sk, (const void *)ptr, idx); \ + } \ + static ossl_unused ossl_inline t2 *sk_##t1##_set(STACK_OF(t1) *sk, int idx, t2 *ptr) \ + { \ + return (t2 *)OPENSSL_sk_set((OPENSSL_STACK *)sk, idx, (const void *)ptr); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_find(STACK_OF(t1) *sk, t2 *ptr) \ + { \ + return OPENSSL_sk_find((OPENSSL_STACK *)sk, (const void *)ptr); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_find_ex(STACK_OF(t1) *sk, t2 *ptr) \ + { \ + return OPENSSL_sk_find_ex((OPENSSL_STACK *)sk, (const void *)ptr); \ + } \ + static ossl_unused ossl_inline void sk_##t1##_sort(STACK_OF(t1) *sk) \ + { \ + OPENSSL_sk_sort((OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline int sk_##t1##_is_sorted(const STACK_OF(t1) *sk) \ + { \ + return OPENSSL_sk_is_sorted((const OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline STACK_OF(t1) * sk_##t1##_dup(const STACK_OF(t1) *sk) \ + { \ + return (STACK_OF(t1) *)OPENSSL_sk_dup((const OPENSSL_STACK *)sk); \ + } \ + static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_deep_copy(const STACK_OF(t1) *sk, \ + sk_##t1##_copyfunc copyfunc, \ + sk_##t1##_freefunc freefunc) \ + { \ + return (STACK_OF(t1) *)OPENSSL_sk_deep_copy((const OPENSSL_STACK *)sk, \ + (OPENSSL_sk_copyfunc)copyfunc, \ + (OPENSSL_sk_freefunc)freefunc); \ + } \ + static ossl_unused ossl_inline sk_##t1##_compfunc sk_##t1##_set_cmp_func(STACK_OF(t1) *sk, sk_##t1##_compfunc compare) \ + { \ + return (sk_##t1##_compfunc)OPENSSL_sk_set_cmp_func((OPENSSL_STACK *)sk, (OPENSSL_sk_compfunc)compare); \ + } + +# define DEFINE_SPECIAL_STACK_OF(t1, t2) SKM_DEFINE_STACK_OF(t1, t2, t2) +# define DEFINE_STACK_OF(t) SKM_DEFINE_STACK_OF(t, t, t) +# define DEFINE_SPECIAL_STACK_OF_CONST(t1, t2) \ + SKM_DEFINE_STACK_OF(t1, const t2, t2) +# define DEFINE_STACK_OF_CONST(t) SKM_DEFINE_STACK_OF(t, const t, t) + +/*- + * Strings are special: normally an lhash entry will point to a single + * (somewhat) mutable object. In the case of strings: + * + * a) Instead of a single char, there is an array of chars, NUL-terminated. + * b) The string may have be immutable. + * + * So, they need their own declarations. Especially important for + * type-checking tools, such as Deputy. + * + * In practice, however, it appears to be hard to have a const + * string. For now, I'm settling for dealing with the fact it is a + * string at all. + */ +typedef char *OPENSSL_STRING; +typedef const char *OPENSSL_CSTRING; + +/*- + * Confusingly, LHASH_OF(STRING) deals with char ** throughout, but + * STACK_OF(STRING) is really more like STACK_OF(char), only, as mentioned + * above, instead of a single char each entry is a NUL-terminated array of + * chars. So, we have to implement STRING specially for STACK_OF. This is + * dealt with in the autogenerated macros below. + */ +DEFINE_SPECIAL_STACK_OF(OPENSSL_STRING, char) +DEFINE_SPECIAL_STACK_OF_CONST(OPENSSL_CSTRING, char) + +/* + * Similarly, we sometimes use a block of characters, NOT nul-terminated. + * These should also be distinguished from "normal" stacks. + */ +typedef void *OPENSSL_BLOCK; +DEFINE_SPECIAL_STACK_OF(OPENSSL_BLOCK, void) + +/* + * If called without higher optimization (min. -xO3) the Oracle Developer + * Studio compiler generates code for the defined (static inline) functions + * above. + * This would later lead to the linker complaining about missing symbols when + * this header file is included but the resulting object is not linked against + * the Crypto library (openssl#6912). + */ +# ifdef __SUNPRO_C +# pragma weak OPENSSL_sk_num +# pragma weak OPENSSL_sk_value +# pragma weak OPENSSL_sk_new +# pragma weak OPENSSL_sk_new_null +# pragma weak OPENSSL_sk_new_reserve +# pragma weak OPENSSL_sk_reserve +# pragma weak OPENSSL_sk_free +# pragma weak OPENSSL_sk_zero +# pragma weak OPENSSL_sk_delete +# pragma weak OPENSSL_sk_delete_ptr +# pragma weak OPENSSL_sk_push +# pragma weak OPENSSL_sk_unshift +# pragma weak OPENSSL_sk_pop +# pragma weak OPENSSL_sk_shift +# pragma weak OPENSSL_sk_pop_free +# pragma weak OPENSSL_sk_insert +# pragma weak OPENSSL_sk_set +# pragma weak OPENSSL_sk_find +# pragma weak OPENSSL_sk_find_ex +# pragma weak OPENSSL_sk_sort +# pragma weak OPENSSL_sk_is_sorted +# pragma weak OPENSSL_sk_dup +# pragma weak OPENSSL_sk_deep_copy +# pragma weak OPENSSL_sk_set_cmp_func +# endif /* __SUNPRO_C */ + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/seed.h b/thrid-party/openssl/openssl/seed.h new file mode 100644 index 0000000000..de10b08572 --- /dev/null +++ b/thrid-party/openssl/openssl/seed.h @@ -0,0 +1,96 @@ +/* + * Copyright 2007-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * Copyright (c) 2007 KISA(Korea Information Security Agency). All rights reserved. + * + * 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. Neither the name of author 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (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 HEADER_SEED_H +# define HEADER_SEED_H + +# include + +# ifndef OPENSSL_NO_SEED +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* look whether we need 'long' to get 32 bits */ +# ifdef AES_LONG +# ifndef SEED_LONG +# define SEED_LONG 1 +# endif +# endif + +# include + +# define SEED_BLOCK_SIZE 16 +# define SEED_KEY_LENGTH 16 + +typedef struct seed_key_st { +# ifdef SEED_LONG + unsigned long data[32]; +# else + unsigned int data[32]; +# endif +} SEED_KEY_SCHEDULE; + +void SEED_set_key(const unsigned char rawkey[SEED_KEY_LENGTH], + SEED_KEY_SCHEDULE *ks); + +void SEED_encrypt(const unsigned char s[SEED_BLOCK_SIZE], + unsigned char d[SEED_BLOCK_SIZE], + const SEED_KEY_SCHEDULE *ks); +void SEED_decrypt(const unsigned char s[SEED_BLOCK_SIZE], + unsigned char d[SEED_BLOCK_SIZE], + const SEED_KEY_SCHEDULE *ks); + +void SEED_ecb_encrypt(const unsigned char *in, unsigned char *out, + const SEED_KEY_SCHEDULE *ks, int enc); +void SEED_cbc_encrypt(const unsigned char *in, unsigned char *out, size_t len, + const SEED_KEY_SCHEDULE *ks, + unsigned char ivec[SEED_BLOCK_SIZE], int enc); +void SEED_cfb128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const SEED_KEY_SCHEDULE *ks, + unsigned char ivec[SEED_BLOCK_SIZE], int *num, + int enc); +void SEED_ofb128_encrypt(const unsigned char *in, unsigned char *out, + size_t len, const SEED_KEY_SCHEDULE *ks, + unsigned char ivec[SEED_BLOCK_SIZE], int *num); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/sha.h b/thrid-party/openssl/openssl/sha.h new file mode 100644 index 0000000000..6a1eb0de8b --- /dev/null +++ b/thrid-party/openssl/openssl/sha.h @@ -0,0 +1,119 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SHA_H +# define HEADER_SHA_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/*- + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ! SHA_LONG has to be at least 32 bits wide. ! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ +# define SHA_LONG unsigned int + +# define SHA_LBLOCK 16 +# define SHA_CBLOCK (SHA_LBLOCK*4)/* SHA treats input data as a + * contiguous array of 32 bit wide + * big-endian values. */ +# define SHA_LAST_BLOCK (SHA_CBLOCK-8) +# define SHA_DIGEST_LENGTH 20 + +typedef struct SHAstate_st { + SHA_LONG h0, h1, h2, h3, h4; + SHA_LONG Nl, Nh; + SHA_LONG data[SHA_LBLOCK]; + unsigned int num; +} SHA_CTX; + +int SHA1_Init(SHA_CTX *c); +int SHA1_Update(SHA_CTX *c, const void *data, size_t len); +int SHA1_Final(unsigned char *md, SHA_CTX *c); +unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md); +void SHA1_Transform(SHA_CTX *c, const unsigned char *data); + +# define SHA256_CBLOCK (SHA_LBLOCK*4)/* SHA-256 treats input data as a + * contiguous array of 32 bit wide + * big-endian values. */ + +typedef struct SHA256state_st { + SHA_LONG h[8]; + SHA_LONG Nl, Nh; + SHA_LONG data[SHA_LBLOCK]; + unsigned int num, md_len; +} SHA256_CTX; + +int SHA224_Init(SHA256_CTX *c); +int SHA224_Update(SHA256_CTX *c, const void *data, size_t len); +int SHA224_Final(unsigned char *md, SHA256_CTX *c); +unsigned char *SHA224(const unsigned char *d, size_t n, unsigned char *md); +int SHA256_Init(SHA256_CTX *c); +int SHA256_Update(SHA256_CTX *c, const void *data, size_t len); +int SHA256_Final(unsigned char *md, SHA256_CTX *c); +unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); +void SHA256_Transform(SHA256_CTX *c, const unsigned char *data); + +# define SHA224_DIGEST_LENGTH 28 +# define SHA256_DIGEST_LENGTH 32 +# define SHA384_DIGEST_LENGTH 48 +# define SHA512_DIGEST_LENGTH 64 + +/* + * Unlike 32-bit digest algorithms, SHA-512 *relies* on SHA_LONG64 + * being exactly 64-bit wide. See Implementation Notes in sha512.c + * for further details. + */ +/* + * SHA-512 treats input data as a + * contiguous array of 64 bit + * wide big-endian values. + */ +# define SHA512_CBLOCK (SHA_LBLOCK*8) +# if (defined(_WIN32) || defined(_WIN64)) && !defined(__MINGW32__) +# define SHA_LONG64 unsigned __int64 +# define U64(C) C##UI64 +# elif defined(__arch64__) +# define SHA_LONG64 unsigned long +# define U64(C) C##UL +# else +# define SHA_LONG64 unsigned long long +# define U64(C) C##ULL +# endif + +typedef struct SHA512state_st { + SHA_LONG64 h[8]; + SHA_LONG64 Nl, Nh; + union { + SHA_LONG64 d[SHA_LBLOCK]; + unsigned char p[SHA512_CBLOCK]; + } u; + unsigned int num, md_len; +} SHA512_CTX; + +int SHA384_Init(SHA512_CTX *c); +int SHA384_Update(SHA512_CTX *c, const void *data, size_t len); +int SHA384_Final(unsigned char *md, SHA512_CTX *c); +unsigned char *SHA384(const unsigned char *d, size_t n, unsigned char *md); +int SHA512_Init(SHA512_CTX *c); +int SHA512_Update(SHA512_CTX *c, const void *data, size_t len); +int SHA512_Final(unsigned char *md, SHA512_CTX *c); +unsigned char *SHA512(const unsigned char *d, size_t n, unsigned char *md); +void SHA512_Transform(SHA512_CTX *c, const unsigned char *data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/srp.h b/thrid-party/openssl/openssl/srp.h new file mode 100644 index 0000000000..aaf13558e3 --- /dev/null +++ b/thrid-party/openssl/openssl/srp.h @@ -0,0 +1,135 @@ +/* + * Copyright 2004-2018 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2004, EdelKey Project. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + * + * Originally written by Christophe Renou and Peter Sylvester, + * for the EdelKey project. + */ + +#ifndef HEADER_SRP_H +# define HEADER_SRP_H + +#include + +#ifndef OPENSSL_NO_SRP +# include +# include +# include +# include +# include + +# ifdef __cplusplus +extern "C" { +# endif + +typedef struct SRP_gN_cache_st { + char *b64_bn; + BIGNUM *bn; +} SRP_gN_cache; + + +DEFINE_STACK_OF(SRP_gN_cache) + +typedef struct SRP_user_pwd_st { + /* Owned by us. */ + char *id; + BIGNUM *s; + BIGNUM *v; + /* Not owned by us. */ + const BIGNUM *g; + const BIGNUM *N; + /* Owned by us. */ + char *info; +} SRP_user_pwd; + +void SRP_user_pwd_free(SRP_user_pwd *user_pwd); + +DEFINE_STACK_OF(SRP_user_pwd) + +typedef struct SRP_VBASE_st { + STACK_OF(SRP_user_pwd) *users_pwd; + STACK_OF(SRP_gN_cache) *gN_cache; +/* to simulate a user */ + char *seed_key; + const BIGNUM *default_g; + const BIGNUM *default_N; +} SRP_VBASE; + +/* + * Internal structure storing N and g pair + */ +typedef struct SRP_gN_st { + char *id; + const BIGNUM *g; + const BIGNUM *N; +} SRP_gN; + +DEFINE_STACK_OF(SRP_gN) + +SRP_VBASE *SRP_VBASE_new(char *seed_key); +void SRP_VBASE_free(SRP_VBASE *vb); +int SRP_VBASE_init(SRP_VBASE *vb, char *verifier_file); + +/* This method ignores the configured seed and fails for an unknown user. */ +DEPRECATEDIN_1_1_0(SRP_user_pwd *SRP_VBASE_get_by_user(SRP_VBASE *vb, char *username)) +/* NOTE: unlike in SRP_VBASE_get_by_user, caller owns the returned pointer.*/ +SRP_user_pwd *SRP_VBASE_get1_by_user(SRP_VBASE *vb, char *username); + +char *SRP_create_verifier(const char *user, const char *pass, char **salt, + char **verifier, const char *N, const char *g); +int SRP_create_verifier_BN(const char *user, const char *pass, BIGNUM **salt, + BIGNUM **verifier, const BIGNUM *N, + const BIGNUM *g); + +# define SRP_NO_ERROR 0 +# define SRP_ERR_VBASE_INCOMPLETE_FILE 1 +# define SRP_ERR_VBASE_BN_LIB 2 +# define SRP_ERR_OPEN_FILE 3 +# define SRP_ERR_MEMORY 4 + +# define DB_srptype 0 +# define DB_srpverifier 1 +# define DB_srpsalt 2 +# define DB_srpid 3 +# define DB_srpgN 4 +# define DB_srpinfo 5 +# undef DB_NUMBER +# define DB_NUMBER 6 + +# define DB_SRP_INDEX 'I' +# define DB_SRP_VALID 'V' +# define DB_SRP_REVOKED 'R' +# define DB_SRP_MODIF 'v' + +/* see srp.c */ +char *SRP_check_known_gN_param(const BIGNUM *g, const BIGNUM *N); +SRP_gN *SRP_get_default_gN(const char *id); + +/* server side .... */ +BIGNUM *SRP_Calc_server_key(const BIGNUM *A, const BIGNUM *v, const BIGNUM *u, + const BIGNUM *b, const BIGNUM *N); +BIGNUM *SRP_Calc_B(const BIGNUM *b, const BIGNUM *N, const BIGNUM *g, + const BIGNUM *v); +int SRP_Verify_A_mod_N(const BIGNUM *A, const BIGNUM *N); +BIGNUM *SRP_Calc_u(const BIGNUM *A, const BIGNUM *B, const BIGNUM *N); + +/* client side .... */ +BIGNUM *SRP_Calc_x(const BIGNUM *s, const char *user, const char *pass); +BIGNUM *SRP_Calc_A(const BIGNUM *a, const BIGNUM *N, const BIGNUM *g); +BIGNUM *SRP_Calc_client_key(const BIGNUM *N, const BIGNUM *B, const BIGNUM *g, + const BIGNUM *x, const BIGNUM *a, const BIGNUM *u); +int SRP_Verify_B_mod_N(const BIGNUM *B, const BIGNUM *N); + +# define SRP_MINIMAL_N 1024 + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/srtp.h b/thrid-party/openssl/openssl/srtp.h new file mode 100644 index 0000000000..0b57c2356c --- /dev/null +++ b/thrid-party/openssl/openssl/srtp.h @@ -0,0 +1,50 @@ +/* + * Copyright 2011-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * DTLS code by Eric Rescorla + * + * Copyright (C) 2006, Network Resonance, Inc. Copyright (C) 2011, RTFM, Inc. + */ + +#ifndef HEADER_D1_SRTP_H +# define HEADER_D1_SRTP_H + +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# define SRTP_AES128_CM_SHA1_80 0x0001 +# define SRTP_AES128_CM_SHA1_32 0x0002 +# define SRTP_AES128_F8_SHA1_80 0x0003 +# define SRTP_AES128_F8_SHA1_32 0x0004 +# define SRTP_NULL_SHA1_80 0x0005 +# define SRTP_NULL_SHA1_32 0x0006 + +/* AEAD SRTP protection profiles from RFC 7714 */ +# define SRTP_AEAD_AES_128_GCM 0x0007 +# define SRTP_AEAD_AES_256_GCM 0x0008 + +# ifndef OPENSSL_NO_SRTP + +__owur int SSL_CTX_set_tlsext_use_srtp(SSL_CTX *ctx, const char *profiles); +__owur int SSL_set_tlsext_use_srtp(SSL *ssl, const char *profiles); + +__owur STACK_OF(SRTP_PROTECTION_PROFILE) *SSL_get_srtp_profiles(SSL *ssl); +__owur SRTP_PROTECTION_PROFILE *SSL_get_selected_srtp_profile(SSL *s); + +# endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/ssl.h b/thrid-party/openssl/openssl/ssl.h new file mode 100644 index 0000000000..6724ccf2d2 --- /dev/null +++ b/thrid-party/openssl/openssl/ssl.h @@ -0,0 +1,2438 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * Copyright 2005 Nokia. All rights reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SSL_H +# define HEADER_SSL_H + +# include +# include +# include +# include +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# include +# include +# endif +# include +# include +# include +# include + +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* OpenSSL version number for ASN.1 encoding of the session information */ +/*- + * Version 0 - initial version + * Version 1 - added the optional peer certificate + */ +# define SSL_SESSION_ASN1_VERSION 0x0001 + +# define SSL_MAX_SSL_SESSION_ID_LENGTH 32 +# define SSL_MAX_SID_CTX_LENGTH 32 + +# define SSL_MIN_RSA_MODULUS_LENGTH_IN_BYTES (512/8) +# define SSL_MAX_KEY_ARG_LENGTH 8 +# define SSL_MAX_MASTER_KEY_LENGTH 48 + +/* The maximum number of encrypt/decrypt pipelines we can support */ +# define SSL_MAX_PIPELINES 32 + +/* text strings for the ciphers */ + +/* These are used to specify which ciphers to use and not to use */ + +# define SSL_TXT_LOW "LOW" +# define SSL_TXT_MEDIUM "MEDIUM" +# define SSL_TXT_HIGH "HIGH" +# define SSL_TXT_FIPS "FIPS" + +# define SSL_TXT_aNULL "aNULL" +# define SSL_TXT_eNULL "eNULL" +# define SSL_TXT_NULL "NULL" + +# define SSL_TXT_kRSA "kRSA" +# define SSL_TXT_kDHr "kDHr"/* this cipher class has been removed */ +# define SSL_TXT_kDHd "kDHd"/* this cipher class has been removed */ +# define SSL_TXT_kDH "kDH"/* this cipher class has been removed */ +# define SSL_TXT_kEDH "kEDH"/* alias for kDHE */ +# define SSL_TXT_kDHE "kDHE" +# define SSL_TXT_kECDHr "kECDHr"/* this cipher class has been removed */ +# define SSL_TXT_kECDHe "kECDHe"/* this cipher class has been removed */ +# define SSL_TXT_kECDH "kECDH"/* this cipher class has been removed */ +# define SSL_TXT_kEECDH "kEECDH"/* alias for kECDHE */ +# define SSL_TXT_kECDHE "kECDHE" +# define SSL_TXT_kPSK "kPSK" +# define SSL_TXT_kRSAPSK "kRSAPSK" +# define SSL_TXT_kECDHEPSK "kECDHEPSK" +# define SSL_TXT_kDHEPSK "kDHEPSK" +# define SSL_TXT_kGOST "kGOST" +# define SSL_TXT_kSRP "kSRP" + +# define SSL_TXT_aRSA "aRSA" +# define SSL_TXT_aDSS "aDSS" +# define SSL_TXT_aDH "aDH"/* this cipher class has been removed */ +# define SSL_TXT_aECDH "aECDH"/* this cipher class has been removed */ +# define SSL_TXT_aECDSA "aECDSA" +# define SSL_TXT_aPSK "aPSK" +# define SSL_TXT_aGOST94 "aGOST94" +# define SSL_TXT_aGOST01 "aGOST01" +# define SSL_TXT_aGOST12 "aGOST12" +# define SSL_TXT_aGOST "aGOST" +# define SSL_TXT_aSRP "aSRP" + +# define SSL_TXT_DSS "DSS" +# define SSL_TXT_DH "DH" +# define SSL_TXT_DHE "DHE"/* same as "kDHE:-ADH" */ +# define SSL_TXT_EDH "EDH"/* alias for DHE */ +# define SSL_TXT_ADH "ADH" +# define SSL_TXT_RSA "RSA" +# define SSL_TXT_ECDH "ECDH" +# define SSL_TXT_EECDH "EECDH"/* alias for ECDHE" */ +# define SSL_TXT_ECDHE "ECDHE"/* same as "kECDHE:-AECDH" */ +# define SSL_TXT_AECDH "AECDH" +# define SSL_TXT_ECDSA "ECDSA" +# define SSL_TXT_PSK "PSK" +# define SSL_TXT_SRP "SRP" + +# define SSL_TXT_DES "DES" +# define SSL_TXT_3DES "3DES" +# define SSL_TXT_RC4 "RC4" +# define SSL_TXT_RC2 "RC2" +# define SSL_TXT_IDEA "IDEA" +# define SSL_TXT_SEED "SEED" +# define SSL_TXT_AES128 "AES128" +# define SSL_TXT_AES256 "AES256" +# define SSL_TXT_AES "AES" +# define SSL_TXT_AES_GCM "AESGCM" +# define SSL_TXT_AES_CCM "AESCCM" +# define SSL_TXT_AES_CCM_8 "AESCCM8" +# define SSL_TXT_CAMELLIA128 "CAMELLIA128" +# define SSL_TXT_CAMELLIA256 "CAMELLIA256" +# define SSL_TXT_CAMELLIA "CAMELLIA" +# define SSL_TXT_CHACHA20 "CHACHA20" +# define SSL_TXT_GOST "GOST89" +# define SSL_TXT_ARIA "ARIA" +# define SSL_TXT_ARIA_GCM "ARIAGCM" +# define SSL_TXT_ARIA128 "ARIA128" +# define SSL_TXT_ARIA256 "ARIA256" + +# define SSL_TXT_MD5 "MD5" +# define SSL_TXT_SHA1 "SHA1" +# define SSL_TXT_SHA "SHA"/* same as "SHA1" */ +# define SSL_TXT_GOST94 "GOST94" +# define SSL_TXT_GOST89MAC "GOST89MAC" +# define SSL_TXT_GOST12 "GOST12" +# define SSL_TXT_GOST89MAC12 "GOST89MAC12" +# define SSL_TXT_SHA256 "SHA256" +# define SSL_TXT_SHA384 "SHA384" + +# define SSL_TXT_SSLV3 "SSLv3" +# define SSL_TXT_TLSV1 "TLSv1" +# define SSL_TXT_TLSV1_1 "TLSv1.1" +# define SSL_TXT_TLSV1_2 "TLSv1.2" + +# define SSL_TXT_ALL "ALL" + +/*- + * COMPLEMENTOF* definitions. These identifiers are used to (de-select) + * ciphers normally not being used. + * Example: "RC4" will activate all ciphers using RC4 including ciphers + * without authentication, which would normally disabled by DEFAULT (due + * the "!ADH" being part of default). Therefore "RC4:!COMPLEMENTOFDEFAULT" + * will make sure that it is also disabled in the specific selection. + * COMPLEMENTOF* identifiers are portable between version, as adjustments + * to the default cipher setup will also be included here. + * + * COMPLEMENTOFDEFAULT does not experience the same special treatment that + * DEFAULT gets, as only selection is being done and no sorting as needed + * for DEFAULT. + */ +# define SSL_TXT_CMPALL "COMPLEMENTOFALL" +# define SSL_TXT_CMPDEF "COMPLEMENTOFDEFAULT" + +/* + * The following cipher list is used by default. It also is substituted when + * an application-defined cipher list string starts with 'DEFAULT'. + * This applies to ciphersuites for TLSv1.2 and below. + */ +# define SSL_DEFAULT_CIPHER_LIST "ALL:!COMPLEMENTOFDEFAULT:!eNULL" +/* This is the default set of TLSv1.3 ciphersuites */ +# if !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305) +# define TLS_DEFAULT_CIPHERSUITES "TLS_AES_256_GCM_SHA384:" \ + "TLS_CHACHA20_POLY1305_SHA256:" \ + "TLS_AES_128_GCM_SHA256" +# else +# define TLS_DEFAULT_CIPHERSUITES "TLS_AES_256_GCM_SHA384:" \ + "TLS_AES_128_GCM_SHA256" +#endif +/* + * As of OpenSSL 1.0.0, ssl_create_cipher_list() in ssl/ssl_ciph.c always + * starts with a reasonable order, and all we have to do for DEFAULT is + * throwing out anonymous and unencrypted ciphersuites! (The latter are not + * actually enabled by ALL, but "ALL:RSA" would enable some of them.) + */ + +/* Used in SSL_set_shutdown()/SSL_get_shutdown(); */ +# define SSL_SENT_SHUTDOWN 1 +# define SSL_RECEIVED_SHUTDOWN 2 + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +# define SSL_FILETYPE_ASN1 X509_FILETYPE_ASN1 +# define SSL_FILETYPE_PEM X509_FILETYPE_PEM + +/* + * This is needed to stop compilers complaining about the 'struct ssl_st *' + * function parameters used to prototype callbacks in SSL_CTX. + */ +typedef struct ssl_st *ssl_crock_st; +typedef struct tls_session_ticket_ext_st TLS_SESSION_TICKET_EXT; +typedef struct ssl_method_st SSL_METHOD; +typedef struct ssl_cipher_st SSL_CIPHER; +typedef struct ssl_session_st SSL_SESSION; +typedef struct tls_sigalgs_st TLS_SIGALGS; +typedef struct ssl_conf_ctx_st SSL_CONF_CTX; +typedef struct ssl_comp_st SSL_COMP; + +STACK_OF(SSL_CIPHER); +STACK_OF(SSL_COMP); + +/* SRTP protection profiles for use with the use_srtp extension (RFC 5764)*/ +typedef struct srtp_protection_profile_st { + const char *name; + unsigned long id; +} SRTP_PROTECTION_PROFILE; + +DEFINE_STACK_OF(SRTP_PROTECTION_PROFILE) + +typedef int (*tls_session_ticket_ext_cb_fn)(SSL *s, const unsigned char *data, + int len, void *arg); +typedef int (*tls_session_secret_cb_fn)(SSL *s, void *secret, int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + const SSL_CIPHER **cipher, void *arg); + +/* Extension context codes */ +/* This extension is only allowed in TLS */ +#define SSL_EXT_TLS_ONLY 0x0001 +/* This extension is only allowed in DTLS */ +#define SSL_EXT_DTLS_ONLY 0x0002 +/* Some extensions may be allowed in DTLS but we don't implement them for it */ +#define SSL_EXT_TLS_IMPLEMENTATION_ONLY 0x0004 +/* Most extensions are not defined for SSLv3 but EXT_TYPE_renegotiate is */ +#define SSL_EXT_SSL3_ALLOWED 0x0008 +/* Extension is only defined for TLS1.2 and below */ +#define SSL_EXT_TLS1_2_AND_BELOW_ONLY 0x0010 +/* Extension is only defined for TLS1.3 and above */ +#define SSL_EXT_TLS1_3_ONLY 0x0020 +/* Ignore this extension during parsing if we are resuming */ +#define SSL_EXT_IGNORE_ON_RESUMPTION 0x0040 +#define SSL_EXT_CLIENT_HELLO 0x0080 +/* Really means TLS1.2 or below */ +#define SSL_EXT_TLS1_2_SERVER_HELLO 0x0100 +#define SSL_EXT_TLS1_3_SERVER_HELLO 0x0200 +#define SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS 0x0400 +#define SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST 0x0800 +#define SSL_EXT_TLS1_3_CERTIFICATE 0x1000 +#define SSL_EXT_TLS1_3_NEW_SESSION_TICKET 0x2000 +#define SSL_EXT_TLS1_3_CERTIFICATE_REQUEST 0x4000 + +/* Typedefs for handling custom extensions */ + +typedef int (*custom_ext_add_cb)(SSL *s, unsigned int ext_type, + const unsigned char **out, size_t *outlen, + int *al, void *add_arg); + +typedef void (*custom_ext_free_cb)(SSL *s, unsigned int ext_type, + const unsigned char *out, void *add_arg); + +typedef int (*custom_ext_parse_cb)(SSL *s, unsigned int ext_type, + const unsigned char *in, size_t inlen, + int *al, void *parse_arg); + + +typedef int (*SSL_custom_ext_add_cb_ex)(SSL *s, unsigned int ext_type, + unsigned int context, + const unsigned char **out, + size_t *outlen, X509 *x, + size_t chainidx, + int *al, void *add_arg); + +typedef void (*SSL_custom_ext_free_cb_ex)(SSL *s, unsigned int ext_type, + unsigned int context, + const unsigned char *out, + void *add_arg); + +typedef int (*SSL_custom_ext_parse_cb_ex)(SSL *s, unsigned int ext_type, + unsigned int context, + const unsigned char *in, + size_t inlen, X509 *x, + size_t chainidx, + int *al, void *parse_arg); + +/* Typedef for verification callback */ +typedef int (*SSL_verify_cb)(int preverify_ok, X509_STORE_CTX *x509_ctx); + +/* + * Some values are reserved until OpenSSL 1.2.0 because they were previously + * included in SSL_OP_ALL in a 1.1.x release. + * + * Reserved value (until OpenSSL 1.2.0) 0x00000001U + * Reserved value (until OpenSSL 1.2.0) 0x00000002U + */ +/* Allow initial connection to servers that don't support RI */ +# define SSL_OP_LEGACY_SERVER_CONNECT 0x00000004U + +/* Reserved value (until OpenSSL 1.2.0) 0x00000008U */ +# define SSL_OP_TLSEXT_PADDING 0x00000010U +/* Reserved value (until OpenSSL 1.2.0) 0x00000020U */ +# define SSL_OP_SAFARI_ECDHE_ECDSA_BUG 0x00000040U +/* + * Reserved value (until OpenSSL 1.2.0) 0x00000080U + * Reserved value (until OpenSSL 1.2.0) 0x00000100U + * Reserved value (until OpenSSL 1.2.0) 0x00000200U + */ + +/* In TLSv1.3 allow a non-(ec)dhe based kex_mode */ +# define SSL_OP_ALLOW_NO_DHE_KEX 0x00000400U + +/* + * Disable SSL 3.0/TLS 1.0 CBC vulnerability workaround that was added in + * OpenSSL 0.9.6d. Usually (depending on the application protocol) the + * workaround is not needed. Unfortunately some broken SSL/TLS + * implementations cannot handle it at all, which is why we include it in + * SSL_OP_ALL. Added in 0.9.6e + */ +# define SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0x00000800U + +/* DTLS options */ +# define SSL_OP_NO_QUERY_MTU 0x00001000U +/* Turn on Cookie Exchange (on relevant for servers) */ +# define SSL_OP_COOKIE_EXCHANGE 0x00002000U +/* Don't use RFC4507 ticket extension */ +# define SSL_OP_NO_TICKET 0x00004000U +# ifndef OPENSSL_NO_DTLS1_METHOD +/* Use Cisco's "speshul" version of DTLS_BAD_VER + * (only with deprecated DTLSv1_client_method()) */ +# define SSL_OP_CISCO_ANYCONNECT 0x00008000U +# endif + +/* As server, disallow session resumption on renegotiation */ +# define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0x00010000U +/* Don't use compression even if supported */ +# define SSL_OP_NO_COMPRESSION 0x00020000U +/* Permit unsafe legacy renegotiation */ +# define SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000U +/* Disable encrypt-then-mac */ +# define SSL_OP_NO_ENCRYPT_THEN_MAC 0x00080000U + +/* + * Enable TLSv1.3 Compatibility mode. This is on by default. A future version + * of OpenSSL may have this disabled by default. + */ +# define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0x00100000U + +/* Prioritize Chacha20Poly1305 when client does. + * Modifies SSL_OP_CIPHER_SERVER_PREFERENCE */ +# define SSL_OP_PRIORITIZE_CHACHA 0x00200000U + +/* + * Set on servers to choose the cipher according to the server's preferences + */ +# define SSL_OP_CIPHER_SERVER_PREFERENCE 0x00400000U +/* + * If set, a server will allow a client to issue a SSLv3.0 version number as + * latest version supported in the premaster secret, even when TLSv1.0 + * (version 3.1) was announced in the client hello. Normally this is + * forbidden to prevent version rollback attacks. + */ +# define SSL_OP_TLS_ROLLBACK_BUG 0x00800000U + +/* + * Switches off automatic TLSv1.3 anti-replay protection for early data. This + * is a server-side option only (no effect on the client). + */ +# define SSL_OP_NO_ANTI_REPLAY 0x01000000U + +# define SSL_OP_NO_SSLv3 0x02000000U +# define SSL_OP_NO_TLSv1 0x04000000U +# define SSL_OP_NO_TLSv1_2 0x08000000U +# define SSL_OP_NO_TLSv1_1 0x10000000U +# define SSL_OP_NO_TLSv1_3 0x20000000U + +# define SSL_OP_NO_DTLSv1 0x04000000U +# define SSL_OP_NO_DTLSv1_2 0x08000000U + +# define SSL_OP_NO_SSL_MASK (SSL_OP_NO_SSLv3|\ + SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2|SSL_OP_NO_TLSv1_3) +# define SSL_OP_NO_DTLS_MASK (SSL_OP_NO_DTLSv1|SSL_OP_NO_DTLSv1_2) + +/* Disallow all renegotiation */ +# define SSL_OP_NO_RENEGOTIATION 0x40000000U + +/* + * Make server add server-hello extension from early version of cryptopro + * draft, when GOST ciphersuite is negotiated. Required for interoperability + * with CryptoPro CSP 3.x + */ +# define SSL_OP_CRYPTOPRO_TLSEXT_BUG 0x80000000U + +/* + * SSL_OP_ALL: various bug workarounds that should be rather harmless. + * This used to be 0x000FFFFFL before 0.9.7. + * This used to be 0x80000BFFU before 1.1.1. + */ +# define SSL_OP_ALL (SSL_OP_CRYPTOPRO_TLSEXT_BUG|\ + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS|\ + SSL_OP_LEGACY_SERVER_CONNECT|\ + SSL_OP_TLSEXT_PADDING|\ + SSL_OP_SAFARI_ECDHE_ECDSA_BUG) + +/* OBSOLETE OPTIONS: retained for compatibility */ + +/* Removed from OpenSSL 1.1.0. Was 0x00000001L */ +/* Related to removed SSLv2. */ +# define SSL_OP_MICROSOFT_SESS_ID_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00000002L */ +/* Related to removed SSLv2. */ +# define SSL_OP_NETSCAPE_CHALLENGE_BUG 0x0 +/* Removed from OpenSSL 0.9.8q and 1.0.0c. Was 0x00000008L */ +/* Dead forever, see CVE-2010-4180 */ +# define SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0x0 +/* Removed from OpenSSL 1.0.1h and 1.0.2. Was 0x00000010L */ +/* Refers to ancient SSLREF and SSLv2. */ +# define SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00000020 */ +# define SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0x0 +/* Removed from OpenSSL 0.9.7h and 0.9.8b. Was 0x00000040L */ +# define SSL_OP_MSIE_SSLV2_RSA_PADDING 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00000080 */ +/* Ancient SSLeay version. */ +# define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00000100L */ +# define SSL_OP_TLS_D5_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00000200L */ +# define SSL_OP_TLS_BLOCK_PADDING_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00080000L */ +# define SSL_OP_SINGLE_ECDH_USE 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x00100000L */ +# define SSL_OP_SINGLE_DH_USE 0x0 +/* Removed from OpenSSL 1.0.1k and 1.0.2. Was 0x00200000L */ +# define SSL_OP_EPHEMERAL_RSA 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x01000000L */ +# define SSL_OP_NO_SSLv2 0x0 +/* Removed from OpenSSL 1.0.1. Was 0x08000000L */ +# define SSL_OP_PKCS1_CHECK_1 0x0 +/* Removed from OpenSSL 1.0.1. Was 0x10000000L */ +# define SSL_OP_PKCS1_CHECK_2 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x20000000L */ +# define SSL_OP_NETSCAPE_CA_DN_BUG 0x0 +/* Removed from OpenSSL 1.1.0. Was 0x40000000L */ +# define SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG 0x0 + +/* + * Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success + * when just a single record has been written): + */ +# define SSL_MODE_ENABLE_PARTIAL_WRITE 0x00000001U +/* + * Make it possible to retry SSL_write() with changed buffer location (buffer + * contents must stay the same!); this is not the default to avoid the + * misconception that non-blocking SSL_write() behaves like non-blocking + * write(): + */ +# define SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER 0x00000002U +/* + * Never bother the application with retries if the transport is blocking: + */ +# define SSL_MODE_AUTO_RETRY 0x00000004U +/* Don't attempt to automatically build certificate chain */ +# define SSL_MODE_NO_AUTO_CHAIN 0x00000008U +/* + * Save RAM by releasing read and write buffers when they're empty. (SSL3 and + * TLS only.) Released buffers are freed. + */ +# define SSL_MODE_RELEASE_BUFFERS 0x00000010U +/* + * Send the current time in the Random fields of the ClientHello and + * ServerHello records for compatibility with hypothetical implementations + * that require it. + */ +# define SSL_MODE_SEND_CLIENTHELLO_TIME 0x00000020U +# define SSL_MODE_SEND_SERVERHELLO_TIME 0x00000040U +/* + * Send TLS_FALLBACK_SCSV in the ClientHello. To be set only by applications + * that reconnect with a downgraded protocol version; see + * draft-ietf-tls-downgrade-scsv-00 for details. DO NOT ENABLE THIS if your + * application attempts a normal handshake. Only use this in explicit + * fallback retries, following the guidance in + * draft-ietf-tls-downgrade-scsv-00. + */ +# define SSL_MODE_SEND_FALLBACK_SCSV 0x00000080U +/* + * Support Asynchronous operation + */ +# define SSL_MODE_ASYNC 0x00000100U + +/* + * When using DTLS/SCTP, include the terminating zero in the label + * used for computing the endpoint-pair shared secret. Required for + * interoperability with implementations having this bug like these + * older version of OpenSSL: + * - OpenSSL 1.0.0 series + * - OpenSSL 1.0.1 series + * - OpenSSL 1.0.2 series + * - OpenSSL 1.1.0 series + * - OpenSSL 1.1.1 and 1.1.1a + */ +# define SSL_MODE_DTLS_SCTP_LABEL_LENGTH_BUG 0x00000400U + +/* Cert related flags */ +/* + * Many implementations ignore some aspects of the TLS standards such as + * enforcing certificate chain algorithms. When this is set we enforce them. + */ +# define SSL_CERT_FLAG_TLS_STRICT 0x00000001U + +/* Suite B modes, takes same values as certificate verify flags */ +# define SSL_CERT_FLAG_SUITEB_128_LOS_ONLY 0x10000 +/* Suite B 192 bit only mode */ +# define SSL_CERT_FLAG_SUITEB_192_LOS 0x20000 +/* Suite B 128 bit mode allowing 192 bit algorithms */ +# define SSL_CERT_FLAG_SUITEB_128_LOS 0x30000 + +/* Perform all sorts of protocol violations for testing purposes */ +# define SSL_CERT_FLAG_BROKEN_PROTOCOL 0x10000000 + +/* Flags for building certificate chains */ +/* Treat any existing certificates as untrusted CAs */ +# define SSL_BUILD_CHAIN_FLAG_UNTRUSTED 0x1 +/* Don't include root CA in chain */ +# define SSL_BUILD_CHAIN_FLAG_NO_ROOT 0x2 +/* Just check certificates already there */ +# define SSL_BUILD_CHAIN_FLAG_CHECK 0x4 +/* Ignore verification errors */ +# define SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR 0x8 +/* Clear verification errors from queue */ +# define SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR 0x10 + +/* Flags returned by SSL_check_chain */ +/* Certificate can be used with this session */ +# define CERT_PKEY_VALID 0x1 +/* Certificate can also be used for signing */ +# define CERT_PKEY_SIGN 0x2 +/* EE certificate signing algorithm OK */ +# define CERT_PKEY_EE_SIGNATURE 0x10 +/* CA signature algorithms OK */ +# define CERT_PKEY_CA_SIGNATURE 0x20 +/* EE certificate parameters OK */ +# define CERT_PKEY_EE_PARAM 0x40 +/* CA certificate parameters OK */ +# define CERT_PKEY_CA_PARAM 0x80 +/* Signing explicitly allowed as opposed to SHA1 fallback */ +# define CERT_PKEY_EXPLICIT_SIGN 0x100 +/* Client CA issuer names match (always set for server cert) */ +# define CERT_PKEY_ISSUER_NAME 0x200 +/* Cert type matches client types (always set for server cert) */ +# define CERT_PKEY_CERT_TYPE 0x400 +/* Cert chain suitable to Suite B */ +# define CERT_PKEY_SUITEB 0x800 + +# define SSL_CONF_FLAG_CMDLINE 0x1 +# define SSL_CONF_FLAG_FILE 0x2 +# define SSL_CONF_FLAG_CLIENT 0x4 +# define SSL_CONF_FLAG_SERVER 0x8 +# define SSL_CONF_FLAG_SHOW_ERRORS 0x10 +# define SSL_CONF_FLAG_CERTIFICATE 0x20 +# define SSL_CONF_FLAG_REQUIRE_PRIVATE 0x40 +/* Configuration value types */ +# define SSL_CONF_TYPE_UNKNOWN 0x0 +# define SSL_CONF_TYPE_STRING 0x1 +# define SSL_CONF_TYPE_FILE 0x2 +# define SSL_CONF_TYPE_DIR 0x3 +# define SSL_CONF_TYPE_NONE 0x4 + +/* Maximum length of the application-controlled segment of a a TLSv1.3 cookie */ +# define SSL_COOKIE_LENGTH 4096 + +/* + * Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value, they + * cannot be used to clear bits. + */ + +unsigned long SSL_CTX_get_options(const SSL_CTX *ctx); +unsigned long SSL_get_options(const SSL *s); +unsigned long SSL_CTX_clear_options(SSL_CTX *ctx, unsigned long op); +unsigned long SSL_clear_options(SSL *s, unsigned long op); +unsigned long SSL_CTX_set_options(SSL_CTX *ctx, unsigned long op); +unsigned long SSL_set_options(SSL *s, unsigned long op); + +# define SSL_CTX_set_mode(ctx,op) \ + SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) +# define SSL_CTX_clear_mode(ctx,op) \ + SSL_CTX_ctrl((ctx),SSL_CTRL_CLEAR_MODE,(op),NULL) +# define SSL_CTX_get_mode(ctx) \ + SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,0,NULL) +# define SSL_clear_mode(ssl,op) \ + SSL_ctrl((ssl),SSL_CTRL_CLEAR_MODE,(op),NULL) +# define SSL_set_mode(ssl,op) \ + SSL_ctrl((ssl),SSL_CTRL_MODE,(op),NULL) +# define SSL_get_mode(ssl) \ + SSL_ctrl((ssl),SSL_CTRL_MODE,0,NULL) +# define SSL_set_mtu(ssl, mtu) \ + SSL_ctrl((ssl),SSL_CTRL_SET_MTU,(mtu),NULL) +# define DTLS_set_link_mtu(ssl, mtu) \ + SSL_ctrl((ssl),DTLS_CTRL_SET_LINK_MTU,(mtu),NULL) +# define DTLS_get_link_min_mtu(ssl) \ + SSL_ctrl((ssl),DTLS_CTRL_GET_LINK_MIN_MTU,0,NULL) + +# define SSL_get_secure_renegotiation_support(ssl) \ + SSL_ctrl((ssl), SSL_CTRL_GET_RI_SUPPORT, 0, NULL) + +# ifndef OPENSSL_NO_HEARTBEATS +# define SSL_heartbeat(ssl) \ + SSL_ctrl((ssl),SSL_CTRL_DTLS_EXT_SEND_HEARTBEAT,0,NULL) +# endif + +# define SSL_CTX_set_cert_flags(ctx,op) \ + SSL_CTX_ctrl((ctx),SSL_CTRL_CERT_FLAGS,(op),NULL) +# define SSL_set_cert_flags(s,op) \ + SSL_ctrl((s),SSL_CTRL_CERT_FLAGS,(op),NULL) +# define SSL_CTX_clear_cert_flags(ctx,op) \ + SSL_CTX_ctrl((ctx),SSL_CTRL_CLEAR_CERT_FLAGS,(op),NULL) +# define SSL_clear_cert_flags(s,op) \ + SSL_ctrl((s),SSL_CTRL_CLEAR_CERT_FLAGS,(op),NULL) + +void SSL_CTX_set_msg_callback(SSL_CTX *ctx, + void (*cb) (int write_p, int version, + int content_type, const void *buf, + size_t len, SSL *ssl, void *arg)); +void SSL_set_msg_callback(SSL *ssl, + void (*cb) (int write_p, int version, + int content_type, const void *buf, + size_t len, SSL *ssl, void *arg)); +# define SSL_CTX_set_msg_callback_arg(ctx, arg) SSL_CTX_ctrl((ctx), SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, (arg)) +# define SSL_set_msg_callback_arg(ssl, arg) SSL_ctrl((ssl), SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, (arg)) + +# define SSL_get_extms_support(s) \ + SSL_ctrl((s),SSL_CTRL_GET_EXTMS_SUPPORT,0,NULL) + +# ifndef OPENSSL_NO_SRP + +/* see tls_srp.c */ +__owur int SSL_SRP_CTX_init(SSL *s); +__owur int SSL_CTX_SRP_CTX_init(SSL_CTX *ctx); +int SSL_SRP_CTX_free(SSL *ctx); +int SSL_CTX_SRP_CTX_free(SSL_CTX *ctx); +__owur int SSL_srp_server_param_with_username(SSL *s, int *ad); +__owur int SRP_Calc_A_param(SSL *s); + +# endif + +/* 100k max cert list */ +# define SSL_MAX_CERT_LIST_DEFAULT 1024*100 + +# define SSL_SESSION_CACHE_MAX_SIZE_DEFAULT (1024*20) + +/* + * This callback type is used inside SSL_CTX, SSL, and in the functions that + * set them. It is used to override the generation of SSL/TLS session IDs in + * a server. Return value should be zero on an error, non-zero to proceed. + * Also, callbacks should themselves check if the id they generate is unique + * otherwise the SSL handshake will fail with an error - callbacks can do + * this using the 'ssl' value they're passed by; + * SSL_has_matching_session_id(ssl, id, *id_len) The length value passed in + * is set at the maximum size the session ID can be. In SSLv3/TLSv1 it is 32 + * bytes. The callback can alter this length to be less if desired. It is + * also an error for the callback to set the size to zero. + */ +typedef int (*GEN_SESSION_CB) (SSL *ssl, unsigned char *id, + unsigned int *id_len); + +# define SSL_SESS_CACHE_OFF 0x0000 +# define SSL_SESS_CACHE_CLIENT 0x0001 +# define SSL_SESS_CACHE_SERVER 0x0002 +# define SSL_SESS_CACHE_BOTH (SSL_SESS_CACHE_CLIENT|SSL_SESS_CACHE_SERVER) +# define SSL_SESS_CACHE_NO_AUTO_CLEAR 0x0080 +/* enough comments already ... see SSL_CTX_set_session_cache_mode(3) */ +# define SSL_SESS_CACHE_NO_INTERNAL_LOOKUP 0x0100 +# define SSL_SESS_CACHE_NO_INTERNAL_STORE 0x0200 +# define SSL_SESS_CACHE_NO_INTERNAL \ + (SSL_SESS_CACHE_NO_INTERNAL_LOOKUP|SSL_SESS_CACHE_NO_INTERNAL_STORE) + +LHASH_OF(SSL_SESSION) *SSL_CTX_sessions(SSL_CTX *ctx); +# define SSL_CTX_sess_number(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_NUMBER,0,NULL) +# define SSL_CTX_sess_connect(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_CONNECT,0,NULL) +# define SSL_CTX_sess_connect_good(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_CONNECT_GOOD,0,NULL) +# define SSL_CTX_sess_connect_renegotiate(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_CONNECT_RENEGOTIATE,0,NULL) +# define SSL_CTX_sess_accept(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_ACCEPT,0,NULL) +# define SSL_CTX_sess_accept_renegotiate(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_ACCEPT_RENEGOTIATE,0,NULL) +# define SSL_CTX_sess_accept_good(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_ACCEPT_GOOD,0,NULL) +# define SSL_CTX_sess_hits(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_HIT,0,NULL) +# define SSL_CTX_sess_cb_hits(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_CB_HIT,0,NULL) +# define SSL_CTX_sess_misses(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_MISSES,0,NULL) +# define SSL_CTX_sess_timeouts(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_TIMEOUTS,0,NULL) +# define SSL_CTX_sess_cache_full(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SESS_CACHE_FULL,0,NULL) + +void SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, + int (*new_session_cb) (struct ssl_st *ssl, + SSL_SESSION *sess)); +int (*SSL_CTX_sess_get_new_cb(SSL_CTX *ctx)) (struct ssl_st *ssl, + SSL_SESSION *sess); +void SSL_CTX_sess_set_remove_cb(SSL_CTX *ctx, + void (*remove_session_cb) (struct ssl_ctx_st + *ctx, + SSL_SESSION *sess)); +void (*SSL_CTX_sess_get_remove_cb(SSL_CTX *ctx)) (struct ssl_ctx_st *ctx, + SSL_SESSION *sess); +void SSL_CTX_sess_set_get_cb(SSL_CTX *ctx, + SSL_SESSION *(*get_session_cb) (struct ssl_st + *ssl, + const unsigned char + *data, int len, + int *copy)); +SSL_SESSION *(*SSL_CTX_sess_get_get_cb(SSL_CTX *ctx)) (struct ssl_st *ssl, + const unsigned char *data, + int len, int *copy); +void SSL_CTX_set_info_callback(SSL_CTX *ctx, + void (*cb) (const SSL *ssl, int type, int val)); +void (*SSL_CTX_get_info_callback(SSL_CTX *ctx)) (const SSL *ssl, int type, + int val); +void SSL_CTX_set_client_cert_cb(SSL_CTX *ctx, + int (*client_cert_cb) (SSL *ssl, X509 **x509, + EVP_PKEY **pkey)); +int (*SSL_CTX_get_client_cert_cb(SSL_CTX *ctx)) (SSL *ssl, X509 **x509, + EVP_PKEY **pkey); +# ifndef OPENSSL_NO_ENGINE +__owur int SSL_CTX_set_client_cert_engine(SSL_CTX *ctx, ENGINE *e); +# endif +void SSL_CTX_set_cookie_generate_cb(SSL_CTX *ctx, + int (*app_gen_cookie_cb) (SSL *ssl, + unsigned char + *cookie, + unsigned int + *cookie_len)); +void SSL_CTX_set_cookie_verify_cb(SSL_CTX *ctx, + int (*app_verify_cookie_cb) (SSL *ssl, + const unsigned + char *cookie, + unsigned int + cookie_len)); + +void SSL_CTX_set_stateless_cookie_generate_cb( + SSL_CTX *ctx, + int (*gen_stateless_cookie_cb) (SSL *ssl, + unsigned char *cookie, + size_t *cookie_len)); +void SSL_CTX_set_stateless_cookie_verify_cb( + SSL_CTX *ctx, + int (*verify_stateless_cookie_cb) (SSL *ssl, + const unsigned char *cookie, + size_t cookie_len)); +# ifndef OPENSSL_NO_NEXTPROTONEG + +typedef int (*SSL_CTX_npn_advertised_cb_func)(SSL *ssl, + const unsigned char **out, + unsigned int *outlen, + void *arg); +void SSL_CTX_set_next_protos_advertised_cb(SSL_CTX *s, + SSL_CTX_npn_advertised_cb_func cb, + void *arg); +# define SSL_CTX_set_npn_advertised_cb SSL_CTX_set_next_protos_advertised_cb + +typedef int (*SSL_CTX_npn_select_cb_func)(SSL *s, + unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg); +void SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, + SSL_CTX_npn_select_cb_func cb, + void *arg); +# define SSL_CTX_set_npn_select_cb SSL_CTX_set_next_proto_select_cb + +void SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, + unsigned *len); +# define SSL_get0_npn_negotiated SSL_get0_next_proto_negotiated +# endif + +__owur int SSL_select_next_proto(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const unsigned char *client, + unsigned int client_len); + +# define OPENSSL_NPN_UNSUPPORTED 0 +# define OPENSSL_NPN_NEGOTIATED 1 +# define OPENSSL_NPN_NO_OVERLAP 2 + +__owur int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos, + unsigned int protos_len); +__owur int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, + unsigned int protos_len); +typedef int (*SSL_CTX_alpn_select_cb_func)(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg); +void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, + SSL_CTX_alpn_select_cb_func cb, + void *arg); +void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, + unsigned int *len); + +# ifndef OPENSSL_NO_PSK +/* + * the maximum length of the buffer given to callbacks containing the + * resulting identity/psk + */ +# define PSK_MAX_IDENTITY_LEN 128 +# define PSK_MAX_PSK_LEN 256 +typedef unsigned int (*SSL_psk_client_cb_func)(SSL *ssl, + const char *hint, + char *identity, + unsigned int max_identity_len, + unsigned char *psk, + unsigned int max_psk_len); +void SSL_CTX_set_psk_client_callback(SSL_CTX *ctx, SSL_psk_client_cb_func cb); +void SSL_set_psk_client_callback(SSL *ssl, SSL_psk_client_cb_func cb); + +typedef unsigned int (*SSL_psk_server_cb_func)(SSL *ssl, + const char *identity, + unsigned char *psk, + unsigned int max_psk_len); +void SSL_CTX_set_psk_server_callback(SSL_CTX *ctx, SSL_psk_server_cb_func cb); +void SSL_set_psk_server_callback(SSL *ssl, SSL_psk_server_cb_func cb); + +__owur int SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *identity_hint); +__owur int SSL_use_psk_identity_hint(SSL *s, const char *identity_hint); +const char *SSL_get_psk_identity_hint(const SSL *s); +const char *SSL_get_psk_identity(const SSL *s); +# endif + +typedef int (*SSL_psk_find_session_cb_func)(SSL *ssl, + const unsigned char *identity, + size_t identity_len, + SSL_SESSION **sess); +typedef int (*SSL_psk_use_session_cb_func)(SSL *ssl, const EVP_MD *md, + const unsigned char **id, + size_t *idlen, + SSL_SESSION **sess); + +void SSL_set_psk_find_session_callback(SSL *s, SSL_psk_find_session_cb_func cb); +void SSL_CTX_set_psk_find_session_callback(SSL_CTX *ctx, + SSL_psk_find_session_cb_func cb); +void SSL_set_psk_use_session_callback(SSL *s, SSL_psk_use_session_cb_func cb); +void SSL_CTX_set_psk_use_session_callback(SSL_CTX *ctx, + SSL_psk_use_session_cb_func cb); + +/* Register callbacks to handle custom TLS Extensions for client or server. */ + +__owur int SSL_CTX_has_client_custom_ext(const SSL_CTX *ctx, + unsigned int ext_type); + +__owur int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, + unsigned int ext_type, + custom_ext_add_cb add_cb, + custom_ext_free_cb free_cb, + void *add_arg, + custom_ext_parse_cb parse_cb, + void *parse_arg); + +__owur int SSL_CTX_add_server_custom_ext(SSL_CTX *ctx, + unsigned int ext_type, + custom_ext_add_cb add_cb, + custom_ext_free_cb free_cb, + void *add_arg, + custom_ext_parse_cb parse_cb, + void *parse_arg); + +__owur int SSL_CTX_add_custom_ext(SSL_CTX *ctx, unsigned int ext_type, + unsigned int context, + SSL_custom_ext_add_cb_ex add_cb, + SSL_custom_ext_free_cb_ex free_cb, + void *add_arg, + SSL_custom_ext_parse_cb_ex parse_cb, + void *parse_arg); + +__owur int SSL_extension_supported(unsigned int ext_type); + +# define SSL_NOTHING 1 +# define SSL_WRITING 2 +# define SSL_READING 3 +# define SSL_X509_LOOKUP 4 +# define SSL_ASYNC_PAUSED 5 +# define SSL_ASYNC_NO_JOBS 6 +# define SSL_CLIENT_HELLO_CB 7 + +/* These will only be used when doing non-blocking IO */ +# define SSL_want_nothing(s) (SSL_want(s) == SSL_NOTHING) +# define SSL_want_read(s) (SSL_want(s) == SSL_READING) +# define SSL_want_write(s) (SSL_want(s) == SSL_WRITING) +# define SSL_want_x509_lookup(s) (SSL_want(s) == SSL_X509_LOOKUP) +# define SSL_want_async(s) (SSL_want(s) == SSL_ASYNC_PAUSED) +# define SSL_want_async_job(s) (SSL_want(s) == SSL_ASYNC_NO_JOBS) +# define SSL_want_client_hello_cb(s) (SSL_want(s) == SSL_CLIENT_HELLO_CB) + +# define SSL_MAC_FLAG_READ_MAC_STREAM 1 +# define SSL_MAC_FLAG_WRITE_MAC_STREAM 2 + +/* + * A callback for logging out TLS key material. This callback should log out + * |line| followed by a newline. + */ +typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line); + +/* + * SSL_CTX_set_keylog_callback configures a callback to log key material. This + * is intended for debugging use with tools like Wireshark. The cb function + * should log line followed by a newline. + */ +void SSL_CTX_set_keylog_callback(SSL_CTX *ctx, SSL_CTX_keylog_cb_func cb); + +/* + * SSL_CTX_get_keylog_callback returns the callback configured by + * SSL_CTX_set_keylog_callback. + */ +SSL_CTX_keylog_cb_func SSL_CTX_get_keylog_callback(const SSL_CTX *ctx); + +int SSL_CTX_set_max_early_data(SSL_CTX *ctx, uint32_t max_early_data); +uint32_t SSL_CTX_get_max_early_data(const SSL_CTX *ctx); +int SSL_set_max_early_data(SSL *s, uint32_t max_early_data); +uint32_t SSL_get_max_early_data(const SSL *s); +int SSL_CTX_set_recv_max_early_data(SSL_CTX *ctx, uint32_t recv_max_early_data); +uint32_t SSL_CTX_get_recv_max_early_data(const SSL_CTX *ctx); +int SSL_set_recv_max_early_data(SSL *s, uint32_t recv_max_early_data); +uint32_t SSL_get_recv_max_early_data(const SSL *s); + +#ifdef __cplusplus +} +#endif + +# include +# include +# include /* This is mostly sslv3 with a few tweaks */ +# include /* Datagram TLS */ +# include /* Support for the use_srtp extension */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * These need to be after the above set of includes due to a compiler bug + * in VisualStudio 2015 + */ +DEFINE_STACK_OF_CONST(SSL_CIPHER) +DEFINE_STACK_OF(SSL_COMP) + +/* compatibility */ +# define SSL_set_app_data(s,arg) (SSL_set_ex_data(s,0,(char *)(arg))) +# define SSL_get_app_data(s) (SSL_get_ex_data(s,0)) +# define SSL_SESSION_set_app_data(s,a) (SSL_SESSION_set_ex_data(s,0, \ + (char *)(a))) +# define SSL_SESSION_get_app_data(s) (SSL_SESSION_get_ex_data(s,0)) +# define SSL_CTX_get_app_data(ctx) (SSL_CTX_get_ex_data(ctx,0)) +# define SSL_CTX_set_app_data(ctx,arg) (SSL_CTX_set_ex_data(ctx,0, \ + (char *)(arg))) +DEPRECATEDIN_1_1_0(void SSL_set_debug(SSL *s, int debug)) + +/* TLSv1.3 KeyUpdate message types */ +/* -1 used so that this is an invalid value for the on-the-wire protocol */ +#define SSL_KEY_UPDATE_NONE -1 +/* Values as defined for the on-the-wire protocol */ +#define SSL_KEY_UPDATE_NOT_REQUESTED 0 +#define SSL_KEY_UPDATE_REQUESTED 1 + +/* + * The valid handshake states (one for each type message sent and one for each + * type of message received). There are also two "special" states: + * TLS = TLS or DTLS state + * DTLS = DTLS specific state + * CR/SR = Client Read/Server Read + * CW/SW = Client Write/Server Write + * + * The "special" states are: + * TLS_ST_BEFORE = No handshake has been initiated yet + * TLS_ST_OK = A handshake has been successfully completed + */ +typedef enum { + TLS_ST_BEFORE, + TLS_ST_OK, + DTLS_ST_CR_HELLO_VERIFY_REQUEST, + TLS_ST_CR_SRVR_HELLO, + TLS_ST_CR_CERT, + TLS_ST_CR_CERT_STATUS, + TLS_ST_CR_KEY_EXCH, + TLS_ST_CR_CERT_REQ, + TLS_ST_CR_SRVR_DONE, + TLS_ST_CR_SESSION_TICKET, + TLS_ST_CR_CHANGE, + TLS_ST_CR_FINISHED, + TLS_ST_CW_CLNT_HELLO, + TLS_ST_CW_CERT, + TLS_ST_CW_KEY_EXCH, + TLS_ST_CW_CERT_VRFY, + TLS_ST_CW_CHANGE, + TLS_ST_CW_NEXT_PROTO, + TLS_ST_CW_FINISHED, + TLS_ST_SW_HELLO_REQ, + TLS_ST_SR_CLNT_HELLO, + DTLS_ST_SW_HELLO_VERIFY_REQUEST, + TLS_ST_SW_SRVR_HELLO, + TLS_ST_SW_CERT, + TLS_ST_SW_KEY_EXCH, + TLS_ST_SW_CERT_REQ, + TLS_ST_SW_SRVR_DONE, + TLS_ST_SR_CERT, + TLS_ST_SR_KEY_EXCH, + TLS_ST_SR_CERT_VRFY, + TLS_ST_SR_NEXT_PROTO, + TLS_ST_SR_CHANGE, + TLS_ST_SR_FINISHED, + TLS_ST_SW_SESSION_TICKET, + TLS_ST_SW_CERT_STATUS, + TLS_ST_SW_CHANGE, + TLS_ST_SW_FINISHED, + TLS_ST_SW_ENCRYPTED_EXTENSIONS, + TLS_ST_CR_ENCRYPTED_EXTENSIONS, + TLS_ST_CR_CERT_VRFY, + TLS_ST_SW_CERT_VRFY, + TLS_ST_CR_HELLO_REQ, + TLS_ST_SW_KEY_UPDATE, + TLS_ST_CW_KEY_UPDATE, + TLS_ST_SR_KEY_UPDATE, + TLS_ST_CR_KEY_UPDATE, + TLS_ST_EARLY_DATA, + TLS_ST_PENDING_EARLY_DATA_END, + TLS_ST_CW_END_OF_EARLY_DATA, + TLS_ST_SR_END_OF_EARLY_DATA +} OSSL_HANDSHAKE_STATE; + +/* + * Most of the following state values are no longer used and are defined to be + * the closest equivalent value in the current state machine code. Not all + * defines have an equivalent and are set to a dummy value (-1). SSL_ST_CONNECT + * and SSL_ST_ACCEPT are still in use in the definition of SSL_CB_ACCEPT_LOOP, + * SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP and SSL_CB_CONNECT_EXIT. + */ + +# define SSL_ST_CONNECT 0x1000 +# define SSL_ST_ACCEPT 0x2000 + +# define SSL_ST_MASK 0x0FFF + +# define SSL_CB_LOOP 0x01 +# define SSL_CB_EXIT 0x02 +# define SSL_CB_READ 0x04 +# define SSL_CB_WRITE 0x08 +# define SSL_CB_ALERT 0x4000/* used in callback */ +# define SSL_CB_READ_ALERT (SSL_CB_ALERT|SSL_CB_READ) +# define SSL_CB_WRITE_ALERT (SSL_CB_ALERT|SSL_CB_WRITE) +# define SSL_CB_ACCEPT_LOOP (SSL_ST_ACCEPT|SSL_CB_LOOP) +# define SSL_CB_ACCEPT_EXIT (SSL_ST_ACCEPT|SSL_CB_EXIT) +# define SSL_CB_CONNECT_LOOP (SSL_ST_CONNECT|SSL_CB_LOOP) +# define SSL_CB_CONNECT_EXIT (SSL_ST_CONNECT|SSL_CB_EXIT) +# define SSL_CB_HANDSHAKE_START 0x10 +# define SSL_CB_HANDSHAKE_DONE 0x20 + +/* Is the SSL_connection established? */ +# define SSL_in_connect_init(a) (SSL_in_init(a) && !SSL_is_server(a)) +# define SSL_in_accept_init(a) (SSL_in_init(a) && SSL_is_server(a)) +int SSL_in_init(const SSL *s); +int SSL_in_before(const SSL *s); +int SSL_is_init_finished(const SSL *s); + +/* + * The following 3 states are kept in ssl->rlayer.rstate when reads fail, you + * should not need these + */ +# define SSL_ST_READ_HEADER 0xF0 +# define SSL_ST_READ_BODY 0xF1 +# define SSL_ST_READ_DONE 0xF2 + +/*- + * Obtain latest Finished message + * -- that we sent (SSL_get_finished) + * -- that we expected from peer (SSL_get_peer_finished). + * Returns length (0 == no Finished so far), copies up to 'count' bytes. + */ +size_t SSL_get_finished(const SSL *s, void *buf, size_t count); +size_t SSL_get_peer_finished(const SSL *s, void *buf, size_t count); + +/* + * use either SSL_VERIFY_NONE or SSL_VERIFY_PEER, the last 3 options are + * 'ored' with SSL_VERIFY_PEER if they are desired + */ +# define SSL_VERIFY_NONE 0x00 +# define SSL_VERIFY_PEER 0x01 +# define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02 +# define SSL_VERIFY_CLIENT_ONCE 0x04 +# define SSL_VERIFY_POST_HANDSHAKE 0x08 + +# if OPENSSL_API_COMPAT < 0x10100000L +# define OpenSSL_add_ssl_algorithms() SSL_library_init() +# define SSLeay_add_ssl_algorithms() SSL_library_init() +# endif + +/* More backward compatibility */ +# define SSL_get_cipher(s) \ + SSL_CIPHER_get_name(SSL_get_current_cipher(s)) +# define SSL_get_cipher_bits(s,np) \ + SSL_CIPHER_get_bits(SSL_get_current_cipher(s),np) +# define SSL_get_cipher_version(s) \ + SSL_CIPHER_get_version(SSL_get_current_cipher(s)) +# define SSL_get_cipher_name(s) \ + SSL_CIPHER_get_name(SSL_get_current_cipher(s)) +# define SSL_get_time(a) SSL_SESSION_get_time(a) +# define SSL_set_time(a,b) SSL_SESSION_set_time((a),(b)) +# define SSL_get_timeout(a) SSL_SESSION_get_timeout(a) +# define SSL_set_timeout(a,b) SSL_SESSION_set_timeout((a),(b)) + +# define d2i_SSL_SESSION_bio(bp,s_id) ASN1_d2i_bio_of(SSL_SESSION,SSL_SESSION_new,d2i_SSL_SESSION,bp,s_id) +# define i2d_SSL_SESSION_bio(bp,s_id) ASN1_i2d_bio_of(SSL_SESSION,i2d_SSL_SESSION,bp,s_id) + +DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION) +# define SSL_AD_REASON_OFFSET 1000/* offset to get SSL_R_... value + * from SSL_AD_... */ +/* These alert types are for SSLv3 and TLSv1 */ +# define SSL_AD_CLOSE_NOTIFY SSL3_AD_CLOSE_NOTIFY +/* fatal */ +# define SSL_AD_UNEXPECTED_MESSAGE SSL3_AD_UNEXPECTED_MESSAGE +/* fatal */ +# define SSL_AD_BAD_RECORD_MAC SSL3_AD_BAD_RECORD_MAC +# define SSL_AD_DECRYPTION_FAILED TLS1_AD_DECRYPTION_FAILED +# define SSL_AD_RECORD_OVERFLOW TLS1_AD_RECORD_OVERFLOW +/* fatal */ +# define SSL_AD_DECOMPRESSION_FAILURE SSL3_AD_DECOMPRESSION_FAILURE +/* fatal */ +# define SSL_AD_HANDSHAKE_FAILURE SSL3_AD_HANDSHAKE_FAILURE +/* Not for TLS */ +# define SSL_AD_NO_CERTIFICATE SSL3_AD_NO_CERTIFICATE +# define SSL_AD_BAD_CERTIFICATE SSL3_AD_BAD_CERTIFICATE +# define SSL_AD_UNSUPPORTED_CERTIFICATE SSL3_AD_UNSUPPORTED_CERTIFICATE +# define SSL_AD_CERTIFICATE_REVOKED SSL3_AD_CERTIFICATE_REVOKED +# define SSL_AD_CERTIFICATE_EXPIRED SSL3_AD_CERTIFICATE_EXPIRED +# define SSL_AD_CERTIFICATE_UNKNOWN SSL3_AD_CERTIFICATE_UNKNOWN +/* fatal */ +# define SSL_AD_ILLEGAL_PARAMETER SSL3_AD_ILLEGAL_PARAMETER +/* fatal */ +# define SSL_AD_UNKNOWN_CA TLS1_AD_UNKNOWN_CA +/* fatal */ +# define SSL_AD_ACCESS_DENIED TLS1_AD_ACCESS_DENIED +/* fatal */ +# define SSL_AD_DECODE_ERROR TLS1_AD_DECODE_ERROR +# define SSL_AD_DECRYPT_ERROR TLS1_AD_DECRYPT_ERROR +/* fatal */ +# define SSL_AD_EXPORT_RESTRICTION TLS1_AD_EXPORT_RESTRICTION +/* fatal */ +# define SSL_AD_PROTOCOL_VERSION TLS1_AD_PROTOCOL_VERSION +/* fatal */ +# define SSL_AD_INSUFFICIENT_SECURITY TLS1_AD_INSUFFICIENT_SECURITY +/* fatal */ +# define SSL_AD_INTERNAL_ERROR TLS1_AD_INTERNAL_ERROR +# define SSL_AD_USER_CANCELLED TLS1_AD_USER_CANCELLED +# define SSL_AD_NO_RENEGOTIATION TLS1_AD_NO_RENEGOTIATION +# define SSL_AD_MISSING_EXTENSION TLS13_AD_MISSING_EXTENSION +# define SSL_AD_CERTIFICATE_REQUIRED TLS13_AD_CERTIFICATE_REQUIRED +# define SSL_AD_UNSUPPORTED_EXTENSION TLS1_AD_UNSUPPORTED_EXTENSION +# define SSL_AD_CERTIFICATE_UNOBTAINABLE TLS1_AD_CERTIFICATE_UNOBTAINABLE +# define SSL_AD_UNRECOGNIZED_NAME TLS1_AD_UNRECOGNIZED_NAME +# define SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE +# define SSL_AD_BAD_CERTIFICATE_HASH_VALUE TLS1_AD_BAD_CERTIFICATE_HASH_VALUE +/* fatal */ +# define SSL_AD_UNKNOWN_PSK_IDENTITY TLS1_AD_UNKNOWN_PSK_IDENTITY +/* fatal */ +# define SSL_AD_INAPPROPRIATE_FALLBACK TLS1_AD_INAPPROPRIATE_FALLBACK +# define SSL_AD_NO_APPLICATION_PROTOCOL TLS1_AD_NO_APPLICATION_PROTOCOL +# define SSL_ERROR_NONE 0 +# define SSL_ERROR_SSL 1 +# define SSL_ERROR_WANT_READ 2 +# define SSL_ERROR_WANT_WRITE 3 +# define SSL_ERROR_WANT_X509_LOOKUP 4 +# define SSL_ERROR_SYSCALL 5/* look at error stack/return + * value/errno */ +# define SSL_ERROR_ZERO_RETURN 6 +# define SSL_ERROR_WANT_CONNECT 7 +# define SSL_ERROR_WANT_ACCEPT 8 +# define SSL_ERROR_WANT_ASYNC 9 +# define SSL_ERROR_WANT_ASYNC_JOB 10 +# define SSL_ERROR_WANT_CLIENT_HELLO_CB 11 +# define SSL_CTRL_SET_TMP_DH 3 +# define SSL_CTRL_SET_TMP_ECDH 4 +# define SSL_CTRL_SET_TMP_DH_CB 6 +# define SSL_CTRL_GET_CLIENT_CERT_REQUEST 9 +# define SSL_CTRL_GET_NUM_RENEGOTIATIONS 10 +# define SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS 11 +# define SSL_CTRL_GET_TOTAL_RENEGOTIATIONS 12 +# define SSL_CTRL_GET_FLAGS 13 +# define SSL_CTRL_EXTRA_CHAIN_CERT 14 +# define SSL_CTRL_SET_MSG_CALLBACK 15 +# define SSL_CTRL_SET_MSG_CALLBACK_ARG 16 +/* only applies to datagram connections */ +# define SSL_CTRL_SET_MTU 17 +/* Stats */ +# define SSL_CTRL_SESS_NUMBER 20 +# define SSL_CTRL_SESS_CONNECT 21 +# define SSL_CTRL_SESS_CONNECT_GOOD 22 +# define SSL_CTRL_SESS_CONNECT_RENEGOTIATE 23 +# define SSL_CTRL_SESS_ACCEPT 24 +# define SSL_CTRL_SESS_ACCEPT_GOOD 25 +# define SSL_CTRL_SESS_ACCEPT_RENEGOTIATE 26 +# define SSL_CTRL_SESS_HIT 27 +# define SSL_CTRL_SESS_CB_HIT 28 +# define SSL_CTRL_SESS_MISSES 29 +# define SSL_CTRL_SESS_TIMEOUTS 30 +# define SSL_CTRL_SESS_CACHE_FULL 31 +# define SSL_CTRL_MODE 33 +# define SSL_CTRL_GET_READ_AHEAD 40 +# define SSL_CTRL_SET_READ_AHEAD 41 +# define SSL_CTRL_SET_SESS_CACHE_SIZE 42 +# define SSL_CTRL_GET_SESS_CACHE_SIZE 43 +# define SSL_CTRL_SET_SESS_CACHE_MODE 44 +# define SSL_CTRL_GET_SESS_CACHE_MODE 45 +# define SSL_CTRL_GET_MAX_CERT_LIST 50 +# define SSL_CTRL_SET_MAX_CERT_LIST 51 +# define SSL_CTRL_SET_MAX_SEND_FRAGMENT 52 +/* see tls1.h for macros based on these */ +# define SSL_CTRL_SET_TLSEXT_SERVERNAME_CB 53 +# define SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG 54 +# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 +# define SSL_CTRL_SET_TLSEXT_DEBUG_CB 56 +# define SSL_CTRL_SET_TLSEXT_DEBUG_ARG 57 +# define SSL_CTRL_GET_TLSEXT_TICKET_KEYS 58 +# define SSL_CTRL_SET_TLSEXT_TICKET_KEYS 59 +/*# define SSL_CTRL_SET_TLSEXT_OPAQUE_PRF_INPUT 60 */ +/*# define SSL_CTRL_SET_TLSEXT_OPAQUE_PRF_INPUT_CB 61 */ +/*# define SSL_CTRL_SET_TLSEXT_OPAQUE_PRF_INPUT_CB_ARG 62 */ +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB 63 +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB_ARG 64 +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE 65 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_EXTS 66 +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_EXTS 67 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_IDS 68 +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_IDS 69 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP 70 +# define SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP 71 +# define SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB 72 +# define SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB 75 +# define SSL_CTRL_SET_SRP_VERIFY_PARAM_CB 76 +# define SSL_CTRL_SET_SRP_GIVE_CLIENT_PWD_CB 77 +# define SSL_CTRL_SET_SRP_ARG 78 +# define SSL_CTRL_SET_TLS_EXT_SRP_USERNAME 79 +# define SSL_CTRL_SET_TLS_EXT_SRP_STRENGTH 80 +# define SSL_CTRL_SET_TLS_EXT_SRP_PASSWORD 81 +# ifndef OPENSSL_NO_HEARTBEATS +# define SSL_CTRL_DTLS_EXT_SEND_HEARTBEAT 85 +# define SSL_CTRL_GET_DTLS_EXT_HEARTBEAT_PENDING 86 +# define SSL_CTRL_SET_DTLS_EXT_HEARTBEAT_NO_REQUESTS 87 +# endif +# define DTLS_CTRL_GET_TIMEOUT 73 +# define DTLS_CTRL_HANDLE_TIMEOUT 74 +# define SSL_CTRL_GET_RI_SUPPORT 76 +# define SSL_CTRL_CLEAR_MODE 78 +# define SSL_CTRL_SET_NOT_RESUMABLE_SESS_CB 79 +# define SSL_CTRL_GET_EXTRA_CHAIN_CERTS 82 +# define SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS 83 +# define SSL_CTRL_CHAIN 88 +# define SSL_CTRL_CHAIN_CERT 89 +# define SSL_CTRL_GET_GROUPS 90 +# define SSL_CTRL_SET_GROUPS 91 +# define SSL_CTRL_SET_GROUPS_LIST 92 +# define SSL_CTRL_GET_SHARED_GROUP 93 +# define SSL_CTRL_SET_SIGALGS 97 +# define SSL_CTRL_SET_SIGALGS_LIST 98 +# define SSL_CTRL_CERT_FLAGS 99 +# define SSL_CTRL_CLEAR_CERT_FLAGS 100 +# define SSL_CTRL_SET_CLIENT_SIGALGS 101 +# define SSL_CTRL_SET_CLIENT_SIGALGS_LIST 102 +# define SSL_CTRL_GET_CLIENT_CERT_TYPES 103 +# define SSL_CTRL_SET_CLIENT_CERT_TYPES 104 +# define SSL_CTRL_BUILD_CERT_CHAIN 105 +# define SSL_CTRL_SET_VERIFY_CERT_STORE 106 +# define SSL_CTRL_SET_CHAIN_CERT_STORE 107 +# define SSL_CTRL_GET_PEER_SIGNATURE_NID 108 +# define SSL_CTRL_GET_PEER_TMP_KEY 109 +# define SSL_CTRL_GET_RAW_CIPHERLIST 110 +# define SSL_CTRL_GET_EC_POINT_FORMATS 111 +# define SSL_CTRL_GET_CHAIN_CERTS 115 +# define SSL_CTRL_SELECT_CURRENT_CERT 116 +# define SSL_CTRL_SET_CURRENT_CERT 117 +# define SSL_CTRL_SET_DH_AUTO 118 +# define DTLS_CTRL_SET_LINK_MTU 120 +# define DTLS_CTRL_GET_LINK_MIN_MTU 121 +# define SSL_CTRL_GET_EXTMS_SUPPORT 122 +# define SSL_CTRL_SET_MIN_PROTO_VERSION 123 +# define SSL_CTRL_SET_MAX_PROTO_VERSION 124 +# define SSL_CTRL_SET_SPLIT_SEND_FRAGMENT 125 +# define SSL_CTRL_SET_MAX_PIPELINES 126 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE 127 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB 128 +# define SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB_ARG 129 +# define SSL_CTRL_GET_MIN_PROTO_VERSION 130 +# define SSL_CTRL_GET_MAX_PROTO_VERSION 131 +# define SSL_CTRL_GET_SIGNATURE_NID 132 +# define SSL_CTRL_GET_TMP_KEY 133 +# define SSL_CERT_SET_FIRST 1 +# define SSL_CERT_SET_NEXT 2 +# define SSL_CERT_SET_SERVER 3 +# define DTLSv1_get_timeout(ssl, arg) \ + SSL_ctrl(ssl,DTLS_CTRL_GET_TIMEOUT,0, (void *)(arg)) +# define DTLSv1_handle_timeout(ssl) \ + SSL_ctrl(ssl,DTLS_CTRL_HANDLE_TIMEOUT,0, NULL) +# define SSL_num_renegotiations(ssl) \ + SSL_ctrl((ssl),SSL_CTRL_GET_NUM_RENEGOTIATIONS,0,NULL) +# define SSL_clear_num_renegotiations(ssl) \ + SSL_ctrl((ssl),SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS,0,NULL) +# define SSL_total_renegotiations(ssl) \ + SSL_ctrl((ssl),SSL_CTRL_GET_TOTAL_RENEGOTIATIONS,0,NULL) +# define SSL_CTX_set_tmp_dh(ctx,dh) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_TMP_DH,0,(char *)(dh)) +# define SSL_CTX_set_tmp_ecdh(ctx,ecdh) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_TMP_ECDH,0,(char *)(ecdh)) +# define SSL_CTX_set_dh_auto(ctx, onoff) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_DH_AUTO,onoff,NULL) +# define SSL_set_dh_auto(s, onoff) \ + SSL_ctrl(s,SSL_CTRL_SET_DH_AUTO,onoff,NULL) +# define SSL_set_tmp_dh(ssl,dh) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TMP_DH,0,(char *)(dh)) +# define SSL_set_tmp_ecdh(ssl,ecdh) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TMP_ECDH,0,(char *)(ecdh)) +# define SSL_CTX_add_extra_chain_cert(ctx,x509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)(x509)) +# define SSL_CTX_get_extra_chain_certs(ctx,px509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_EXTRA_CHAIN_CERTS,0,px509) +# define SSL_CTX_get_extra_chain_certs_only(ctx,px509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_EXTRA_CHAIN_CERTS,1,px509) +# define SSL_CTX_clear_extra_chain_certs(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS,0,NULL) +# define SSL_CTX_set0_chain(ctx,sk) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_CHAIN,0,(char *)(sk)) +# define SSL_CTX_set1_chain(ctx,sk) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_CHAIN,1,(char *)(sk)) +# define SSL_CTX_add0_chain_cert(ctx,x509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_CHAIN_CERT,0,(char *)(x509)) +# define SSL_CTX_add1_chain_cert(ctx,x509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_CHAIN_CERT,1,(char *)(x509)) +# define SSL_CTX_get0_chain_certs(ctx,px509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_CHAIN_CERTS,0,px509) +# define SSL_CTX_clear_chain_certs(ctx) \ + SSL_CTX_set0_chain(ctx,NULL) +# define SSL_CTX_build_cert_chain(ctx, flags) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_BUILD_CERT_CHAIN, flags, NULL) +# define SSL_CTX_select_current_cert(ctx,x509) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509)) +# define SSL_CTX_set_current_cert(ctx, op) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CURRENT_CERT, op, NULL) +# define SSL_CTX_set0_verify_cert_store(ctx,st) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st)) +# define SSL_CTX_set1_verify_cert_store(ctx,st) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_VERIFY_CERT_STORE,1,(char *)(st)) +# define SSL_CTX_set0_chain_cert_store(ctx,st) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st)) +# define SSL_CTX_set1_chain_cert_store(ctx,st) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st)) +# define SSL_set0_chain(s,sk) \ + SSL_ctrl(s,SSL_CTRL_CHAIN,0,(char *)(sk)) +# define SSL_set1_chain(s,sk) \ + SSL_ctrl(s,SSL_CTRL_CHAIN,1,(char *)(sk)) +# define SSL_add0_chain_cert(s,x509) \ + SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,0,(char *)(x509)) +# define SSL_add1_chain_cert(s,x509) \ + SSL_ctrl(s,SSL_CTRL_CHAIN_CERT,1,(char *)(x509)) +# define SSL_get0_chain_certs(s,px509) \ + SSL_ctrl(s,SSL_CTRL_GET_CHAIN_CERTS,0,px509) +# define SSL_clear_chain_certs(s) \ + SSL_set0_chain(s,NULL) +# define SSL_build_cert_chain(s, flags) \ + SSL_ctrl(s,SSL_CTRL_BUILD_CERT_CHAIN, flags, NULL) +# define SSL_select_current_cert(s,x509) \ + SSL_ctrl(s,SSL_CTRL_SELECT_CURRENT_CERT,0,(char *)(x509)) +# define SSL_set_current_cert(s,op) \ + SSL_ctrl(s,SSL_CTRL_SET_CURRENT_CERT, op, NULL) +# define SSL_set0_verify_cert_store(s,st) \ + SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,0,(char *)(st)) +# define SSL_set1_verify_cert_store(s,st) \ + SSL_ctrl(s,SSL_CTRL_SET_VERIFY_CERT_STORE,1,(char *)(st)) +# define SSL_set0_chain_cert_store(s,st) \ + SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,0,(char *)(st)) +# define SSL_set1_chain_cert_store(s,st) \ + SSL_ctrl(s,SSL_CTRL_SET_CHAIN_CERT_STORE,1,(char *)(st)) +# define SSL_get1_groups(s, glist) \ + SSL_ctrl(s,SSL_CTRL_GET_GROUPS,0,(int*)(glist)) +# define SSL_CTX_set1_groups(ctx, glist, glistlen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist)) +# define SSL_CTX_set1_groups_list(ctx, s) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(s)) +# define SSL_set1_groups(s, glist, glistlen) \ + SSL_ctrl(s,SSL_CTRL_SET_GROUPS,glistlen,(char *)(glist)) +# define SSL_set1_groups_list(s, str) \ + SSL_ctrl(s,SSL_CTRL_SET_GROUPS_LIST,0,(char *)(str)) +# define SSL_get_shared_group(s, n) \ + SSL_ctrl(s,SSL_CTRL_GET_SHARED_GROUP,n,NULL) +# define SSL_CTX_set1_sigalgs(ctx, slist, slistlen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist)) +# define SSL_CTX_set1_sigalgs_list(ctx, s) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(s)) +# define SSL_set1_sigalgs(s, slist, slistlen) \ + SSL_ctrl(s,SSL_CTRL_SET_SIGALGS,slistlen,(int *)(slist)) +# define SSL_set1_sigalgs_list(s, str) \ + SSL_ctrl(s,SSL_CTRL_SET_SIGALGS_LIST,0,(char *)(str)) +# define SSL_CTX_set1_client_sigalgs(ctx, slist, slistlen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist)) +# define SSL_CTX_set1_client_sigalgs_list(ctx, s) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(s)) +# define SSL_set1_client_sigalgs(s, slist, slistlen) \ + SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS,slistlen,(int *)(slist)) +# define SSL_set1_client_sigalgs_list(s, str) \ + SSL_ctrl(s,SSL_CTRL_SET_CLIENT_SIGALGS_LIST,0,(char *)(str)) +# define SSL_get0_certificate_types(s, clist) \ + SSL_ctrl(s, SSL_CTRL_GET_CLIENT_CERT_TYPES, 0, (char *)(clist)) +# define SSL_CTX_set1_client_certificate_types(ctx, clist, clistlen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_CLIENT_CERT_TYPES,clistlen, \ + (char *)(clist)) +# define SSL_set1_client_certificate_types(s, clist, clistlen) \ + SSL_ctrl(s,SSL_CTRL_SET_CLIENT_CERT_TYPES,clistlen,(char *)(clist)) +# define SSL_get_signature_nid(s, pn) \ + SSL_ctrl(s,SSL_CTRL_GET_SIGNATURE_NID,0,pn) +# define SSL_get_peer_signature_nid(s, pn) \ + SSL_ctrl(s,SSL_CTRL_GET_PEER_SIGNATURE_NID,0,pn) +# define SSL_get_peer_tmp_key(s, pk) \ + SSL_ctrl(s,SSL_CTRL_GET_PEER_TMP_KEY,0,pk) +# define SSL_get_tmp_key(s, pk) \ + SSL_ctrl(s,SSL_CTRL_GET_TMP_KEY,0,pk) +# define SSL_get0_raw_cipherlist(s, plst) \ + SSL_ctrl(s,SSL_CTRL_GET_RAW_CIPHERLIST,0,plst) +# define SSL_get0_ec_point_formats(s, plst) \ + SSL_ctrl(s,SSL_CTRL_GET_EC_POINT_FORMATS,0,plst) +# define SSL_CTX_set_min_proto_version(ctx, version) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, NULL) +# define SSL_CTX_set_max_proto_version(ctx, version) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, NULL) +# define SSL_CTX_get_min_proto_version(ctx) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, NULL) +# define SSL_CTX_get_max_proto_version(ctx) \ + SSL_CTX_ctrl(ctx, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, NULL) +# define SSL_set_min_proto_version(s, version) \ + SSL_ctrl(s, SSL_CTRL_SET_MIN_PROTO_VERSION, version, NULL) +# define SSL_set_max_proto_version(s, version) \ + SSL_ctrl(s, SSL_CTRL_SET_MAX_PROTO_VERSION, version, NULL) +# define SSL_get_min_proto_version(s) \ + SSL_ctrl(s, SSL_CTRL_GET_MIN_PROTO_VERSION, 0, NULL) +# define SSL_get_max_proto_version(s) \ + SSL_ctrl(s, SSL_CTRL_GET_MAX_PROTO_VERSION, 0, NULL) + +/* Backwards compatibility, original 1.1.0 names */ +# define SSL_CTRL_GET_SERVER_TMP_KEY \ + SSL_CTRL_GET_PEER_TMP_KEY +# define SSL_get_server_tmp_key(s, pk) \ + SSL_get_peer_tmp_key(s, pk) + +/* + * The following symbol names are old and obsolete. They are kept + * for compatibility reasons only and should not be used anymore. + */ +# define SSL_CTRL_GET_CURVES SSL_CTRL_GET_GROUPS +# define SSL_CTRL_SET_CURVES SSL_CTRL_SET_GROUPS +# define SSL_CTRL_SET_CURVES_LIST SSL_CTRL_SET_GROUPS_LIST +# define SSL_CTRL_GET_SHARED_CURVE SSL_CTRL_GET_SHARED_GROUP + +# define SSL_get1_curves SSL_get1_groups +# define SSL_CTX_set1_curves SSL_CTX_set1_groups +# define SSL_CTX_set1_curves_list SSL_CTX_set1_groups_list +# define SSL_set1_curves SSL_set1_groups +# define SSL_set1_curves_list SSL_set1_groups_list +# define SSL_get_shared_curve SSL_get_shared_group + + +# if OPENSSL_API_COMPAT < 0x10100000L +/* Provide some compatibility macros for removed functionality. */ +# define SSL_CTX_need_tmp_RSA(ctx) 0 +# define SSL_CTX_set_tmp_rsa(ctx,rsa) 1 +# define SSL_need_tmp_RSA(ssl) 0 +# define SSL_set_tmp_rsa(ssl,rsa) 1 +# define SSL_CTX_set_ecdh_auto(dummy, onoff) ((onoff) != 0) +# define SSL_set_ecdh_auto(dummy, onoff) ((onoff) != 0) +/* + * We "pretend" to call the callback to avoid warnings about unused static + * functions. + */ +# define SSL_CTX_set_tmp_rsa_callback(ctx, cb) while(0) (cb)(NULL, 0, 0) +# define SSL_set_tmp_rsa_callback(ssl, cb) while(0) (cb)(NULL, 0, 0) +# endif +__owur const BIO_METHOD *BIO_f_ssl(void); +__owur BIO *BIO_new_ssl(SSL_CTX *ctx, int client); +__owur BIO *BIO_new_ssl_connect(SSL_CTX *ctx); +__owur BIO *BIO_new_buffer_ssl_connect(SSL_CTX *ctx); +__owur int BIO_ssl_copy_session_id(BIO *to, BIO *from); +void BIO_ssl_shutdown(BIO *ssl_bio); + +__owur int SSL_CTX_set_cipher_list(SSL_CTX *, const char *str); +__owur SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth); +int SSL_CTX_up_ref(SSL_CTX *ctx); +void SSL_CTX_free(SSL_CTX *); +__owur long SSL_CTX_set_timeout(SSL_CTX *ctx, long t); +__owur long SSL_CTX_get_timeout(const SSL_CTX *ctx); +__owur X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *); +void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); +void SSL_CTX_set1_cert_store(SSL_CTX *, X509_STORE *); +__owur int SSL_want(const SSL *s); +__owur int SSL_clear(SSL *s); + +void SSL_CTX_flush_sessions(SSL_CTX *ctx, long tm); + +__owur const SSL_CIPHER *SSL_get_current_cipher(const SSL *s); +__owur const SSL_CIPHER *SSL_get_pending_cipher(const SSL *s); +__owur int SSL_CIPHER_get_bits(const SSL_CIPHER *c, int *alg_bits); +__owur const char *SSL_CIPHER_get_version(const SSL_CIPHER *c); +__owur const char *SSL_CIPHER_get_name(const SSL_CIPHER *c); +__owur const char *SSL_CIPHER_standard_name(const SSL_CIPHER *c); +__owur const char *OPENSSL_cipher_name(const char *rfc_name); +__owur uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *c); +__owur uint16_t SSL_CIPHER_get_protocol_id(const SSL_CIPHER *c); +__owur int SSL_CIPHER_get_kx_nid(const SSL_CIPHER *c); +__owur int SSL_CIPHER_get_auth_nid(const SSL_CIPHER *c); +__owur const EVP_MD *SSL_CIPHER_get_handshake_digest(const SSL_CIPHER *c); +__owur int SSL_CIPHER_is_aead(const SSL_CIPHER *c); + +__owur int SSL_get_fd(const SSL *s); +__owur int SSL_get_rfd(const SSL *s); +__owur int SSL_get_wfd(const SSL *s); +__owur const char *SSL_get_cipher_list(const SSL *s, int n); +__owur char *SSL_get_shared_ciphers(const SSL *s, char *buf, int size); +__owur int SSL_get_read_ahead(const SSL *s); +__owur int SSL_pending(const SSL *s); +__owur int SSL_has_pending(const SSL *s); +# ifndef OPENSSL_NO_SOCK +__owur int SSL_set_fd(SSL *s, int fd); +__owur int SSL_set_rfd(SSL *s, int fd); +__owur int SSL_set_wfd(SSL *s, int fd); +# endif +void SSL_set0_rbio(SSL *s, BIO *rbio); +void SSL_set0_wbio(SSL *s, BIO *wbio); +void SSL_set_bio(SSL *s, BIO *rbio, BIO *wbio); +__owur BIO *SSL_get_rbio(const SSL *s); +__owur BIO *SSL_get_wbio(const SSL *s); +__owur int SSL_set_cipher_list(SSL *s, const char *str); +__owur int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); +__owur int SSL_set_ciphersuites(SSL *s, const char *str); +void SSL_set_read_ahead(SSL *s, int yes); +__owur int SSL_get_verify_mode(const SSL *s); +__owur int SSL_get_verify_depth(const SSL *s); +__owur SSL_verify_cb SSL_get_verify_callback(const SSL *s); +void SSL_set_verify(SSL *s, int mode, SSL_verify_cb callback); +void SSL_set_verify_depth(SSL *s, int depth); +void SSL_set_cert_cb(SSL *s, int (*cb) (SSL *ssl, void *arg), void *arg); +# ifndef OPENSSL_NO_RSA +__owur int SSL_use_RSAPrivateKey(SSL *ssl, RSA *rsa); +__owur int SSL_use_RSAPrivateKey_ASN1(SSL *ssl, const unsigned char *d, + long len); +# endif +__owur int SSL_use_PrivateKey(SSL *ssl, EVP_PKEY *pkey); +__owur int SSL_use_PrivateKey_ASN1(int pk, SSL *ssl, const unsigned char *d, + long len); +__owur int SSL_use_certificate(SSL *ssl, X509 *x); +__owur int SSL_use_certificate_ASN1(SSL *ssl, const unsigned char *d, int len); +__owur int SSL_use_cert_and_key(SSL *ssl, X509 *x509, EVP_PKEY *privatekey, + STACK_OF(X509) *chain, int override); + + +/* serverinfo file format versions */ +# define SSL_SERVERINFOV1 1 +# define SSL_SERVERINFOV2 2 + +/* Set serverinfo data for the current active cert. */ +__owur int SSL_CTX_use_serverinfo(SSL_CTX *ctx, const unsigned char *serverinfo, + size_t serverinfo_length); +__owur int SSL_CTX_use_serverinfo_ex(SSL_CTX *ctx, unsigned int version, + const unsigned char *serverinfo, + size_t serverinfo_length); +__owur int SSL_CTX_use_serverinfo_file(SSL_CTX *ctx, const char *file); + +#ifndef OPENSSL_NO_RSA +__owur int SSL_use_RSAPrivateKey_file(SSL *ssl, const char *file, int type); +#endif + +__owur int SSL_use_PrivateKey_file(SSL *ssl, const char *file, int type); +__owur int SSL_use_certificate_file(SSL *ssl, const char *file, int type); + +#ifndef OPENSSL_NO_RSA +__owur int SSL_CTX_use_RSAPrivateKey_file(SSL_CTX *ctx, const char *file, + int type); +#endif +__owur int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, + int type); +__owur int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, + int type); +/* PEM type */ +__owur int SSL_CTX_use_certificate_chain_file(SSL_CTX *ctx, const char *file); +__owur int SSL_use_certificate_chain_file(SSL *ssl, const char *file); +__owur STACK_OF(X509_NAME) *SSL_load_client_CA_file(const char *file); +__owur int SSL_add_file_cert_subjects_to_stack(STACK_OF(X509_NAME) *stackCAs, + const char *file); +int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stackCAs, + const char *dir); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSL_load_error_strings() \ + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ + | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) +# endif + +__owur const char *SSL_state_string(const SSL *s); +__owur const char *SSL_rstate_string(const SSL *s); +__owur const char *SSL_state_string_long(const SSL *s); +__owur const char *SSL_rstate_string_long(const SSL *s); +__owur long SSL_SESSION_get_time(const SSL_SESSION *s); +__owur long SSL_SESSION_set_time(SSL_SESSION *s, long t); +__owur long SSL_SESSION_get_timeout(const SSL_SESSION *s); +__owur long SSL_SESSION_set_timeout(SSL_SESSION *s, long t); +__owur int SSL_SESSION_get_protocol_version(const SSL_SESSION *s); +__owur int SSL_SESSION_set_protocol_version(SSL_SESSION *s, int version); + +__owur const char *SSL_SESSION_get0_hostname(const SSL_SESSION *s); +__owur int SSL_SESSION_set1_hostname(SSL_SESSION *s, const char *hostname); +void SSL_SESSION_get0_alpn_selected(const SSL_SESSION *s, + const unsigned char **alpn, + size_t *len); +__owur int SSL_SESSION_set1_alpn_selected(SSL_SESSION *s, + const unsigned char *alpn, + size_t len); +__owur const SSL_CIPHER *SSL_SESSION_get0_cipher(const SSL_SESSION *s); +__owur int SSL_SESSION_set_cipher(SSL_SESSION *s, const SSL_CIPHER *cipher); +__owur int SSL_SESSION_has_ticket(const SSL_SESSION *s); +__owur unsigned long SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s); +void SSL_SESSION_get0_ticket(const SSL_SESSION *s, const unsigned char **tick, + size_t *len); +__owur uint32_t SSL_SESSION_get_max_early_data(const SSL_SESSION *s); +__owur int SSL_SESSION_set_max_early_data(SSL_SESSION *s, + uint32_t max_early_data); +__owur int SSL_copy_session_id(SSL *to, const SSL *from); +__owur X509 *SSL_SESSION_get0_peer(SSL_SESSION *s); +__owur int SSL_SESSION_set1_id_context(SSL_SESSION *s, + const unsigned char *sid_ctx, + unsigned int sid_ctx_len); +__owur int SSL_SESSION_set1_id(SSL_SESSION *s, const unsigned char *sid, + unsigned int sid_len); +__owur int SSL_SESSION_is_resumable(const SSL_SESSION *s); + +__owur SSL_SESSION *SSL_SESSION_new(void); +__owur SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *src); +const unsigned char *SSL_SESSION_get_id(const SSL_SESSION *s, + unsigned int *len); +const unsigned char *SSL_SESSION_get0_id_context(const SSL_SESSION *s, + unsigned int *len); +__owur unsigned int SSL_SESSION_get_compress_id(const SSL_SESSION *s); +# ifndef OPENSSL_NO_STDIO +int SSL_SESSION_print_fp(FILE *fp, const SSL_SESSION *ses); +# endif +int SSL_SESSION_print(BIO *fp, const SSL_SESSION *ses); +int SSL_SESSION_print_keylog(BIO *bp, const SSL_SESSION *x); +int SSL_SESSION_up_ref(SSL_SESSION *ses); +void SSL_SESSION_free(SSL_SESSION *ses); +__owur int i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); +__owur int SSL_set_session(SSL *to, SSL_SESSION *session); +int SSL_CTX_add_session(SSL_CTX *ctx, SSL_SESSION *session); +int SSL_CTX_remove_session(SSL_CTX *ctx, SSL_SESSION *session); +__owur int SSL_CTX_set_generate_session_id(SSL_CTX *ctx, GEN_SESSION_CB cb); +__owur int SSL_set_generate_session_id(SSL *s, GEN_SESSION_CB cb); +__owur int SSL_has_matching_session_id(const SSL *s, + const unsigned char *id, + unsigned int id_len); +SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, + long length); + +# ifdef HEADER_X509_H +__owur X509 *SSL_get_peer_certificate(const SSL *s); +# endif + +__owur STACK_OF(X509) *SSL_get_peer_cert_chain(const SSL *s); + +__owur int SSL_CTX_get_verify_mode(const SSL_CTX *ctx); +__owur int SSL_CTX_get_verify_depth(const SSL_CTX *ctx); +__owur SSL_verify_cb SSL_CTX_get_verify_callback(const SSL_CTX *ctx); +void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb callback); +void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth); +void SSL_CTX_set_cert_verify_callback(SSL_CTX *ctx, + int (*cb) (X509_STORE_CTX *, void *), + void *arg); +void SSL_CTX_set_cert_cb(SSL_CTX *c, int (*cb) (SSL *ssl, void *arg), + void *arg); +# ifndef OPENSSL_NO_RSA +__owur int SSL_CTX_use_RSAPrivateKey(SSL_CTX *ctx, RSA *rsa); +__owur int SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, const unsigned char *d, + long len); +# endif +__owur int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey); +__owur int SSL_CTX_use_PrivateKey_ASN1(int pk, SSL_CTX *ctx, + const unsigned char *d, long len); +__owur int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x); +__owur int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, int len, + const unsigned char *d); +__owur int SSL_CTX_use_cert_and_key(SSL_CTX *ctx, X509 *x509, EVP_PKEY *privatekey, + STACK_OF(X509) *chain, int override); + +void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_password_cb *cb); +void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u); +pem_password_cb *SSL_CTX_get_default_passwd_cb(SSL_CTX *ctx); +void *SSL_CTX_get_default_passwd_cb_userdata(SSL_CTX *ctx); +void SSL_set_default_passwd_cb(SSL *s, pem_password_cb *cb); +void SSL_set_default_passwd_cb_userdata(SSL *s, void *u); +pem_password_cb *SSL_get_default_passwd_cb(SSL *s); +void *SSL_get_default_passwd_cb_userdata(SSL *s); + +__owur int SSL_CTX_check_private_key(const SSL_CTX *ctx); +__owur int SSL_check_private_key(const SSL *ctx); + +__owur int SSL_CTX_set_session_id_context(SSL_CTX *ctx, + const unsigned char *sid_ctx, + unsigned int sid_ctx_len); + +SSL *SSL_new(SSL_CTX *ctx); +int SSL_up_ref(SSL *s); +int SSL_is_dtls(const SSL *s); +__owur int SSL_set_session_id_context(SSL *ssl, const unsigned char *sid_ctx, + unsigned int sid_ctx_len); + +__owur int SSL_CTX_set_purpose(SSL_CTX *ctx, int purpose); +__owur int SSL_set_purpose(SSL *ssl, int purpose); +__owur int SSL_CTX_set_trust(SSL_CTX *ctx, int trust); +__owur int SSL_set_trust(SSL *ssl, int trust); + +__owur int SSL_set1_host(SSL *s, const char *hostname); +__owur int SSL_add1_host(SSL *s, const char *hostname); +__owur const char *SSL_get0_peername(SSL *s); +void SSL_set_hostflags(SSL *s, unsigned int flags); + +__owur int SSL_CTX_dane_enable(SSL_CTX *ctx); +__owur int SSL_CTX_dane_mtype_set(SSL_CTX *ctx, const EVP_MD *md, + uint8_t mtype, uint8_t ord); +__owur int SSL_dane_enable(SSL *s, const char *basedomain); +__owur int SSL_dane_tlsa_add(SSL *s, uint8_t usage, uint8_t selector, + uint8_t mtype, unsigned const char *data, size_t dlen); +__owur int SSL_get0_dane_authority(SSL *s, X509 **mcert, EVP_PKEY **mspki); +__owur int SSL_get0_dane_tlsa(SSL *s, uint8_t *usage, uint8_t *selector, + uint8_t *mtype, unsigned const char **data, + size_t *dlen); +/* + * Bridge opacity barrier between libcrypt and libssl, also needed to support + * offline testing in test/danetest.c + */ +SSL_DANE *SSL_get0_dane(SSL *ssl); +/* + * DANE flags + */ +unsigned long SSL_CTX_dane_set_flags(SSL_CTX *ctx, unsigned long flags); +unsigned long SSL_CTX_dane_clear_flags(SSL_CTX *ctx, unsigned long flags); +unsigned long SSL_dane_set_flags(SSL *ssl, unsigned long flags); +unsigned long SSL_dane_clear_flags(SSL *ssl, unsigned long flags); + +__owur int SSL_CTX_set1_param(SSL_CTX *ctx, X509_VERIFY_PARAM *vpm); +__owur int SSL_set1_param(SSL *ssl, X509_VERIFY_PARAM *vpm); + +__owur X509_VERIFY_PARAM *SSL_CTX_get0_param(SSL_CTX *ctx); +__owur X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl); + +# ifndef OPENSSL_NO_SRP +int SSL_CTX_set_srp_username(SSL_CTX *ctx, char *name); +int SSL_CTX_set_srp_password(SSL_CTX *ctx, char *password); +int SSL_CTX_set_srp_strength(SSL_CTX *ctx, int strength); +int SSL_CTX_set_srp_client_pwd_callback(SSL_CTX *ctx, + char *(*cb) (SSL *, void *)); +int SSL_CTX_set_srp_verify_param_callback(SSL_CTX *ctx, + int (*cb) (SSL *, void *)); +int SSL_CTX_set_srp_username_callback(SSL_CTX *ctx, + int (*cb) (SSL *, int *, void *)); +int SSL_CTX_set_srp_cb_arg(SSL_CTX *ctx, void *arg); + +int SSL_set_srp_server_param(SSL *s, const BIGNUM *N, const BIGNUM *g, + BIGNUM *sa, BIGNUM *v, char *info); +int SSL_set_srp_server_param_pw(SSL *s, const char *user, const char *pass, + const char *grp); + +__owur BIGNUM *SSL_get_srp_g(SSL *s); +__owur BIGNUM *SSL_get_srp_N(SSL *s); + +__owur char *SSL_get_srp_username(SSL *s); +__owur char *SSL_get_srp_userinfo(SSL *s); +# endif + +/* + * ClientHello callback and helpers. + */ + +# define SSL_CLIENT_HELLO_SUCCESS 1 +# define SSL_CLIENT_HELLO_ERROR 0 +# define SSL_CLIENT_HELLO_RETRY (-1) + +typedef int (*SSL_client_hello_cb_fn) (SSL *s, int *al, void *arg); +void SSL_CTX_set_client_hello_cb(SSL_CTX *c, SSL_client_hello_cb_fn cb, + void *arg); +int SSL_client_hello_isv2(SSL *s); +unsigned int SSL_client_hello_get0_legacy_version(SSL *s); +size_t SSL_client_hello_get0_random(SSL *s, const unsigned char **out); +size_t SSL_client_hello_get0_session_id(SSL *s, const unsigned char **out); +size_t SSL_client_hello_get0_ciphers(SSL *s, const unsigned char **out); +size_t SSL_client_hello_get0_compression_methods(SSL *s, + const unsigned char **out); +int SSL_client_hello_get1_extensions_present(SSL *s, int **out, size_t *outlen); +int SSL_client_hello_get0_ext(SSL *s, unsigned int type, + const unsigned char **out, size_t *outlen); + +void SSL_certs_clear(SSL *s); +void SSL_free(SSL *ssl); +# ifdef OSSL_ASYNC_FD +/* + * Windows application developer has to include windows.h to use these. + */ +__owur int SSL_waiting_for_async(SSL *s); +__owur int SSL_get_all_async_fds(SSL *s, OSSL_ASYNC_FD *fds, size_t *numfds); +__owur int SSL_get_changed_async_fds(SSL *s, OSSL_ASYNC_FD *addfd, + size_t *numaddfds, OSSL_ASYNC_FD *delfd, + size_t *numdelfds); +# endif +__owur int SSL_accept(SSL *ssl); +__owur int SSL_stateless(SSL *s); +__owur int SSL_connect(SSL *ssl); +__owur int SSL_read(SSL *ssl, void *buf, int num); +__owur int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); + +# define SSL_READ_EARLY_DATA_ERROR 0 +# define SSL_READ_EARLY_DATA_SUCCESS 1 +# define SSL_READ_EARLY_DATA_FINISH 2 + +__owur int SSL_read_early_data(SSL *s, void *buf, size_t num, + size_t *readbytes); +__owur int SSL_peek(SSL *ssl, void *buf, int num); +__owur int SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +__owur int SSL_write(SSL *ssl, const void *buf, int num); +__owur int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); +__owur int SSL_write_early_data(SSL *s, const void *buf, size_t num, + size_t *written); +long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg); +long SSL_callback_ctrl(SSL *, int, void (*)(void)); +long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg); +long SSL_CTX_callback_ctrl(SSL_CTX *, int, void (*)(void)); + +# define SSL_EARLY_DATA_NOT_SENT 0 +# define SSL_EARLY_DATA_REJECTED 1 +# define SSL_EARLY_DATA_ACCEPTED 2 + +__owur int SSL_get_early_data_status(const SSL *s); + +__owur int SSL_get_error(const SSL *s, int ret_code); +__owur const char *SSL_get_version(const SSL *s); + +/* This sets the 'default' SSL version that SSL_new() will create */ +__owur int SSL_CTX_set_ssl_version(SSL_CTX *ctx, const SSL_METHOD *meth); + +# ifndef OPENSSL_NO_SSL3_METHOD +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *SSLv3_method(void)) /* SSLv3 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *SSLv3_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *SSLv3_client_method(void)) +# endif + +#define SSLv23_method TLS_method +#define SSLv23_server_method TLS_server_method +#define SSLv23_client_method TLS_client_method + +/* Negotiate highest available SSL/TLS version */ +__owur const SSL_METHOD *TLS_method(void); +__owur const SSL_METHOD *TLS_server_method(void); +__owur const SSL_METHOD *TLS_client_method(void); + +# ifndef OPENSSL_NO_TLS1_METHOD +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_method(void)) /* TLSv1.0 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_client_method(void)) +# endif + +# ifndef OPENSSL_NO_TLS1_1_METHOD +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_1_method(void)) /* TLSv1.1 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_1_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_1_client_method(void)) +# endif + +# ifndef OPENSSL_NO_TLS1_2_METHOD +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_2_method(void)) /* TLSv1.2 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_2_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *TLSv1_2_client_method(void)) +# endif + +# ifndef OPENSSL_NO_DTLS1_METHOD +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_method(void)) /* DTLSv1.0 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_client_method(void)) +# endif + +# ifndef OPENSSL_NO_DTLS1_2_METHOD +/* DTLSv1.2 */ +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_2_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_2_server_method(void)) +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_2_client_method(void)) +# endif + +__owur const SSL_METHOD *DTLS_method(void); /* DTLS 1.0 and 1.2 */ +__owur const SSL_METHOD *DTLS_server_method(void); /* DTLS 1.0 and 1.2 */ +__owur const SSL_METHOD *DTLS_client_method(void); /* DTLS 1.0 and 1.2 */ + +__owur size_t DTLS_get_data_mtu(const SSL *s); + +__owur STACK_OF(SSL_CIPHER) *SSL_get_ciphers(const SSL *s); +__owur STACK_OF(SSL_CIPHER) *SSL_CTX_get_ciphers(const SSL_CTX *ctx); +__owur STACK_OF(SSL_CIPHER) *SSL_get_client_ciphers(const SSL *s); +__owur STACK_OF(SSL_CIPHER) *SSL_get1_supported_ciphers(SSL *s); + +__owur int SSL_do_handshake(SSL *s); +int SSL_key_update(SSL *s, int updatetype); +int SSL_get_key_update_type(const SSL *s); +int SSL_renegotiate(SSL *s); +int SSL_renegotiate_abbreviated(SSL *s); +__owur int SSL_renegotiate_pending(const SSL *s); +int SSL_shutdown(SSL *s); +__owur int SSL_verify_client_post_handshake(SSL *s); +void SSL_CTX_set_post_handshake_auth(SSL_CTX *ctx, int val); +void SSL_set_post_handshake_auth(SSL *s, int val); + +__owur const SSL_METHOD *SSL_CTX_get_ssl_method(const SSL_CTX *ctx); +__owur const SSL_METHOD *SSL_get_ssl_method(const SSL *s); +__owur int SSL_set_ssl_method(SSL *s, const SSL_METHOD *method); +__owur const char *SSL_alert_type_string_long(int value); +__owur const char *SSL_alert_type_string(int value); +__owur const char *SSL_alert_desc_string_long(int value); +__owur const char *SSL_alert_desc_string(int value); + +void SSL_set0_CA_list(SSL *s, STACK_OF(X509_NAME) *name_list); +void SSL_CTX_set0_CA_list(SSL_CTX *ctx, STACK_OF(X509_NAME) *name_list); +__owur const STACK_OF(X509_NAME) *SSL_get0_CA_list(const SSL *s); +__owur const STACK_OF(X509_NAME) *SSL_CTX_get0_CA_list(const SSL_CTX *ctx); +__owur int SSL_add1_to_CA_list(SSL *ssl, const X509 *x); +__owur int SSL_CTX_add1_to_CA_list(SSL_CTX *ctx, const X509 *x); +__owur const STACK_OF(X509_NAME) *SSL_get0_peer_CA_list(const SSL *s); + +void SSL_set_client_CA_list(SSL *s, STACK_OF(X509_NAME) *name_list); +void SSL_CTX_set_client_CA_list(SSL_CTX *ctx, STACK_OF(X509_NAME) *name_list); +__owur STACK_OF(X509_NAME) *SSL_get_client_CA_list(const SSL *s); +__owur STACK_OF(X509_NAME) *SSL_CTX_get_client_CA_list(const SSL_CTX *s); +__owur int SSL_add_client_CA(SSL *ssl, X509 *x); +__owur int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x); + +void SSL_set_connect_state(SSL *s); +void SSL_set_accept_state(SSL *s); + +__owur long SSL_get_default_timeout(const SSL *s); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSL_library_init() OPENSSL_init_ssl(0, NULL) +# endif + +__owur char *SSL_CIPHER_description(const SSL_CIPHER *, char *buf, int size); +__owur STACK_OF(X509_NAME) *SSL_dup_CA_list(const STACK_OF(X509_NAME) *sk); + +__owur SSL *SSL_dup(SSL *ssl); + +__owur X509 *SSL_get_certificate(const SSL *ssl); +/* + * EVP_PKEY + */ +struct evp_pkey_st *SSL_get_privatekey(const SSL *ssl); + +__owur X509 *SSL_CTX_get0_certificate(const SSL_CTX *ctx); +__owur EVP_PKEY *SSL_CTX_get0_privatekey(const SSL_CTX *ctx); + +void SSL_CTX_set_quiet_shutdown(SSL_CTX *ctx, int mode); +__owur int SSL_CTX_get_quiet_shutdown(const SSL_CTX *ctx); +void SSL_set_quiet_shutdown(SSL *ssl, int mode); +__owur int SSL_get_quiet_shutdown(const SSL *ssl); +void SSL_set_shutdown(SSL *ssl, int mode); +__owur int SSL_get_shutdown(const SSL *ssl); +__owur int SSL_version(const SSL *ssl); +__owur int SSL_client_version(const SSL *s); +__owur int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx); +__owur int SSL_CTX_set_default_verify_dir(SSL_CTX *ctx); +__owur int SSL_CTX_set_default_verify_file(SSL_CTX *ctx); +__owur int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, + const char *CApath); +# define SSL_get0_session SSL_get_session/* just peek at pointer */ +__owur SSL_SESSION *SSL_get_session(const SSL *ssl); +__owur SSL_SESSION *SSL_get1_session(SSL *ssl); /* obtain a reference count */ +__owur SSL_CTX *SSL_get_SSL_CTX(const SSL *ssl); +SSL_CTX *SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx); +void SSL_set_info_callback(SSL *ssl, + void (*cb) (const SSL *ssl, int type, int val)); +void (*SSL_get_info_callback(const SSL *ssl)) (const SSL *ssl, int type, + int val); +__owur OSSL_HANDSHAKE_STATE SSL_get_state(const SSL *ssl); + +void SSL_set_verify_result(SSL *ssl, long v); +__owur long SSL_get_verify_result(const SSL *ssl); +__owur STACK_OF(X509) *SSL_get0_verified_chain(const SSL *s); + +__owur size_t SSL_get_client_random(const SSL *ssl, unsigned char *out, + size_t outlen); +__owur size_t SSL_get_server_random(const SSL *ssl, unsigned char *out, + size_t outlen); +__owur size_t SSL_SESSION_get_master_key(const SSL_SESSION *sess, + unsigned char *out, size_t outlen); +__owur int SSL_SESSION_set1_master_key(SSL_SESSION *sess, + const unsigned char *in, size_t len); +uint8_t SSL_SESSION_get_max_fragment_length(const SSL_SESSION *sess); + +#define SSL_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, l, p, newf, dupf, freef) +__owur int SSL_set_ex_data(SSL *ssl, int idx, void *data); +void *SSL_get_ex_data(const SSL *ssl, int idx); +#define SSL_SESSION_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL_SESSION, l, p, newf, dupf, freef) +__owur int SSL_SESSION_set_ex_data(SSL_SESSION *ss, int idx, void *data); +void *SSL_SESSION_get_ex_data(const SSL_SESSION *ss, int idx); +#define SSL_CTX_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL_CTX, l, p, newf, dupf, freef) +__owur int SSL_CTX_set_ex_data(SSL_CTX *ssl, int idx, void *data); +void *SSL_CTX_get_ex_data(const SSL_CTX *ssl, int idx); + +__owur int SSL_get_ex_data_X509_STORE_CTX_idx(void); + +# define SSL_CTX_sess_set_cache_size(ctx,t) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_SIZE,t,NULL) +# define SSL_CTX_sess_get_cache_size(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_SESS_CACHE_SIZE,0,NULL) +# define SSL_CTX_set_session_cache_mode(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_MODE,m,NULL) +# define SSL_CTX_get_session_cache_mode(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_SESS_CACHE_MODE,0,NULL) + +# define SSL_CTX_get_default_read_ahead(ctx) SSL_CTX_get_read_ahead(ctx) +# define SSL_CTX_set_default_read_ahead(ctx,m) SSL_CTX_set_read_ahead(ctx,m) +# define SSL_CTX_get_read_ahead(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_READ_AHEAD,0,NULL) +# define SSL_CTX_set_read_ahead(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_READ_AHEAD,m,NULL) +# define SSL_CTX_get_max_cert_list(ctx) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_MAX_CERT_LIST,0,NULL) +# define SSL_CTX_set_max_cert_list(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_MAX_CERT_LIST,m,NULL) +# define SSL_get_max_cert_list(ssl) \ + SSL_ctrl(ssl,SSL_CTRL_GET_MAX_CERT_LIST,0,NULL) +# define SSL_set_max_cert_list(ssl,m) \ + SSL_ctrl(ssl,SSL_CTRL_SET_MAX_CERT_LIST,m,NULL) + +# define SSL_CTX_set_max_send_fragment(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_MAX_SEND_FRAGMENT,m,NULL) +# define SSL_set_max_send_fragment(ssl,m) \ + SSL_ctrl(ssl,SSL_CTRL_SET_MAX_SEND_FRAGMENT,m,NULL) +# define SSL_CTX_set_split_send_fragment(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SPLIT_SEND_FRAGMENT,m,NULL) +# define SSL_set_split_send_fragment(ssl,m) \ + SSL_ctrl(ssl,SSL_CTRL_SET_SPLIT_SEND_FRAGMENT,m,NULL) +# define SSL_CTX_set_max_pipelines(ctx,m) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_MAX_PIPELINES,m,NULL) +# define SSL_set_max_pipelines(ssl,m) \ + SSL_ctrl(ssl,SSL_CTRL_SET_MAX_PIPELINES,m,NULL) + +void SSL_CTX_set_default_read_buffer_len(SSL_CTX *ctx, size_t len); +void SSL_set_default_read_buffer_len(SSL *s, size_t len); + +# ifndef OPENSSL_NO_DH +/* NB: the |keylength| is only applicable when is_export is true */ +void SSL_CTX_set_tmp_dh_callback(SSL_CTX *ctx, + DH *(*dh) (SSL *ssl, int is_export, + int keylength)); +void SSL_set_tmp_dh_callback(SSL *ssl, + DH *(*dh) (SSL *ssl, int is_export, + int keylength)); +# endif + +__owur const COMP_METHOD *SSL_get_current_compression(const SSL *s); +__owur const COMP_METHOD *SSL_get_current_expansion(const SSL *s); +__owur const char *SSL_COMP_get_name(const COMP_METHOD *comp); +__owur const char *SSL_COMP_get0_name(const SSL_COMP *comp); +__owur int SSL_COMP_get_id(const SSL_COMP *comp); +STACK_OF(SSL_COMP) *SSL_COMP_get_compression_methods(void); +__owur STACK_OF(SSL_COMP) *SSL_COMP_set0_compression_methods(STACK_OF(SSL_COMP) + *meths); +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSL_COMP_free_compression_methods() while(0) continue +# endif +__owur int SSL_COMP_add_compression_method(int id, COMP_METHOD *cm); + +const SSL_CIPHER *SSL_CIPHER_find(SSL *ssl, const unsigned char *ptr); +int SSL_CIPHER_get_cipher_nid(const SSL_CIPHER *c); +int SSL_CIPHER_get_digest_nid(const SSL_CIPHER *c); +int SSL_bytes_to_cipher_list(SSL *s, const unsigned char *bytes, size_t len, + int isv2format, STACK_OF(SSL_CIPHER) **sk, + STACK_OF(SSL_CIPHER) **scsvs); + +/* TLS extensions functions */ +__owur int SSL_set_session_ticket_ext(SSL *s, void *ext_data, int ext_len); + +__owur int SSL_set_session_ticket_ext_cb(SSL *s, + tls_session_ticket_ext_cb_fn cb, + void *arg); + +/* Pre-shared secret session resumption functions */ +__owur int SSL_set_session_secret_cb(SSL *s, + tls_session_secret_cb_fn session_secret_cb, + void *arg); + +void SSL_CTX_set_not_resumable_session_callback(SSL_CTX *ctx, + int (*cb) (SSL *ssl, + int + is_forward_secure)); + +void SSL_set_not_resumable_session_callback(SSL *ssl, + int (*cb) (SSL *ssl, + int is_forward_secure)); + +void SSL_CTX_set_record_padding_callback(SSL_CTX *ctx, + size_t (*cb) (SSL *ssl, int type, + size_t len, void *arg)); +void SSL_CTX_set_record_padding_callback_arg(SSL_CTX *ctx, void *arg); +void *SSL_CTX_get_record_padding_callback_arg(const SSL_CTX *ctx); +int SSL_CTX_set_block_padding(SSL_CTX *ctx, size_t block_size); + +void SSL_set_record_padding_callback(SSL *ssl, + size_t (*cb) (SSL *ssl, int type, + size_t len, void *arg)); +void SSL_set_record_padding_callback_arg(SSL *ssl, void *arg); +void *SSL_get_record_padding_callback_arg(const SSL *ssl); +int SSL_set_block_padding(SSL *ssl, size_t block_size); + +int SSL_set_num_tickets(SSL *s, size_t num_tickets); +size_t SSL_get_num_tickets(const SSL *s); +int SSL_CTX_set_num_tickets(SSL_CTX *ctx, size_t num_tickets); +size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSL_cache_hit(s) SSL_session_reused(s) +# endif + +__owur int SSL_session_reused(const SSL *s); +__owur int SSL_is_server(const SSL *s); + +__owur __owur SSL_CONF_CTX *SSL_CONF_CTX_new(void); +int SSL_CONF_CTX_finish(SSL_CONF_CTX *cctx); +void SSL_CONF_CTX_free(SSL_CONF_CTX *cctx); +unsigned int SSL_CONF_CTX_set_flags(SSL_CONF_CTX *cctx, unsigned int flags); +__owur unsigned int SSL_CONF_CTX_clear_flags(SSL_CONF_CTX *cctx, + unsigned int flags); +__owur int SSL_CONF_CTX_set1_prefix(SSL_CONF_CTX *cctx, const char *pre); + +void SSL_CONF_CTX_set_ssl(SSL_CONF_CTX *cctx, SSL *ssl); +void SSL_CONF_CTX_set_ssl_ctx(SSL_CONF_CTX *cctx, SSL_CTX *ctx); + +__owur int SSL_CONF_cmd(SSL_CONF_CTX *cctx, const char *cmd, const char *value); +__owur int SSL_CONF_cmd_argv(SSL_CONF_CTX *cctx, int *pargc, char ***pargv); +__owur int SSL_CONF_cmd_value_type(SSL_CONF_CTX *cctx, const char *cmd); + +void SSL_add_ssl_module(void); +int SSL_config(SSL *s, const char *name); +int SSL_CTX_config(SSL_CTX *ctx, const char *name); + +# ifndef OPENSSL_NO_SSL_TRACE +void SSL_trace(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg); +# endif + +# ifndef OPENSSL_NO_SOCK +int DTLSv1_listen(SSL *s, BIO_ADDR *client); +# endif + +# ifndef OPENSSL_NO_CT + +/* + * A callback for verifying that the received SCTs are sufficient. + * Expected to return 1 if they are sufficient, otherwise 0. + * May return a negative integer if an error occurs. + * A connection should be aborted if the SCTs are deemed insufficient. + */ +typedef int (*ssl_ct_validation_cb)(const CT_POLICY_EVAL_CTX *ctx, + const STACK_OF(SCT) *scts, void *arg); + +/* + * Sets a |callback| that is invoked upon receipt of ServerHelloDone to validate + * the received SCTs. + * If the callback returns a non-positive result, the connection is terminated. + * Call this function before beginning a handshake. + * If a NULL |callback| is provided, SCT validation is disabled. + * |arg| is arbitrary userdata that will be passed to the callback whenever it + * is invoked. Ownership of |arg| remains with the caller. + * + * NOTE: A side-effect of setting a CT callback is that an OCSP stapled response + * will be requested. + */ +int SSL_set_ct_validation_callback(SSL *s, ssl_ct_validation_cb callback, + void *arg); +int SSL_CTX_set_ct_validation_callback(SSL_CTX *ctx, + ssl_ct_validation_cb callback, + void *arg); +#define SSL_disable_ct(s) \ + ((void) SSL_set_validation_callback((s), NULL, NULL)) +#define SSL_CTX_disable_ct(ctx) \ + ((void) SSL_CTX_set_validation_callback((ctx), NULL, NULL)) + +/* + * The validation type enumerates the available behaviours of the built-in SSL + * CT validation callback selected via SSL_enable_ct() and SSL_CTX_enable_ct(). + * The underlying callback is a static function in libssl. + */ +enum { + SSL_CT_VALIDATION_PERMISSIVE = 0, + SSL_CT_VALIDATION_STRICT +}; + +/* + * Enable CT by setting up a callback that implements one of the built-in + * validation variants. The SSL_CT_VALIDATION_PERMISSIVE variant always + * continues the handshake, the application can make appropriate decisions at + * handshake completion. The SSL_CT_VALIDATION_STRICT variant requires at + * least one valid SCT, or else handshake termination will be requested. The + * handshake may continue anyway if SSL_VERIFY_NONE is in effect. + */ +int SSL_enable_ct(SSL *s, int validation_mode); +int SSL_CTX_enable_ct(SSL_CTX *ctx, int validation_mode); + +/* + * Report whether a non-NULL callback is enabled. + */ +int SSL_ct_is_enabled(const SSL *s); +int SSL_CTX_ct_is_enabled(const SSL_CTX *ctx); + +/* Gets the SCTs received from a connection */ +const STACK_OF(SCT) *SSL_get0_peer_scts(SSL *s); + +/* + * Loads the CT log list from the default location. + * If a CTLOG_STORE has previously been set using SSL_CTX_set_ctlog_store, + * the log information loaded from this file will be appended to the + * CTLOG_STORE. + * Returns 1 on success, 0 otherwise. + */ +int SSL_CTX_set_default_ctlog_list_file(SSL_CTX *ctx); + +/* + * Loads the CT log list from the specified file path. + * If a CTLOG_STORE has previously been set using SSL_CTX_set_ctlog_store, + * the log information loaded from this file will be appended to the + * CTLOG_STORE. + * Returns 1 on success, 0 otherwise. + */ +int SSL_CTX_set_ctlog_list_file(SSL_CTX *ctx, const char *path); + +/* + * Sets the CT log list used by all SSL connections created from this SSL_CTX. + * Ownership of the CTLOG_STORE is transferred to the SSL_CTX. + */ +void SSL_CTX_set0_ctlog_store(SSL_CTX *ctx, CTLOG_STORE *logs); + +/* + * Gets the CT log list used by all SSL connections created from this SSL_CTX. + * This will be NULL unless one of the following functions has been called: + * - SSL_CTX_set_default_ctlog_list_file + * - SSL_CTX_set_ctlog_list_file + * - SSL_CTX_set_ctlog_store + */ +const CTLOG_STORE *SSL_CTX_get0_ctlog_store(const SSL_CTX *ctx); + +# endif /* OPENSSL_NO_CT */ + +/* What the "other" parameter contains in security callback */ +/* Mask for type */ +# define SSL_SECOP_OTHER_TYPE 0xffff0000 +# define SSL_SECOP_OTHER_NONE 0 +# define SSL_SECOP_OTHER_CIPHER (1 << 16) +# define SSL_SECOP_OTHER_CURVE (2 << 16) +# define SSL_SECOP_OTHER_DH (3 << 16) +# define SSL_SECOP_OTHER_PKEY (4 << 16) +# define SSL_SECOP_OTHER_SIGALG (5 << 16) +# define SSL_SECOP_OTHER_CERT (6 << 16) + +/* Indicated operation refers to peer key or certificate */ +# define SSL_SECOP_PEER 0x1000 + +/* Values for "op" parameter in security callback */ + +/* Called to filter ciphers */ +/* Ciphers client supports */ +# define SSL_SECOP_CIPHER_SUPPORTED (1 | SSL_SECOP_OTHER_CIPHER) +/* Cipher shared by client/server */ +# define SSL_SECOP_CIPHER_SHARED (2 | SSL_SECOP_OTHER_CIPHER) +/* Sanity check of cipher server selects */ +# define SSL_SECOP_CIPHER_CHECK (3 | SSL_SECOP_OTHER_CIPHER) +/* Curves supported by client */ +# define SSL_SECOP_CURVE_SUPPORTED (4 | SSL_SECOP_OTHER_CURVE) +/* Curves shared by client/server */ +# define SSL_SECOP_CURVE_SHARED (5 | SSL_SECOP_OTHER_CURVE) +/* Sanity check of curve server selects */ +# define SSL_SECOP_CURVE_CHECK (6 | SSL_SECOP_OTHER_CURVE) +/* Temporary DH key */ +# define SSL_SECOP_TMP_DH (7 | SSL_SECOP_OTHER_PKEY) +/* SSL/TLS version */ +# define SSL_SECOP_VERSION (9 | SSL_SECOP_OTHER_NONE) +/* Session tickets */ +# define SSL_SECOP_TICKET (10 | SSL_SECOP_OTHER_NONE) +/* Supported signature algorithms sent to peer */ +# define SSL_SECOP_SIGALG_SUPPORTED (11 | SSL_SECOP_OTHER_SIGALG) +/* Shared signature algorithm */ +# define SSL_SECOP_SIGALG_SHARED (12 | SSL_SECOP_OTHER_SIGALG) +/* Sanity check signature algorithm allowed */ +# define SSL_SECOP_SIGALG_CHECK (13 | SSL_SECOP_OTHER_SIGALG) +/* Used to get mask of supported public key signature algorithms */ +# define SSL_SECOP_SIGALG_MASK (14 | SSL_SECOP_OTHER_SIGALG) +/* Use to see if compression is allowed */ +# define SSL_SECOP_COMPRESSION (15 | SSL_SECOP_OTHER_NONE) +/* EE key in certificate */ +# define SSL_SECOP_EE_KEY (16 | SSL_SECOP_OTHER_CERT) +/* CA key in certificate */ +# define SSL_SECOP_CA_KEY (17 | SSL_SECOP_OTHER_CERT) +/* CA digest algorithm in certificate */ +# define SSL_SECOP_CA_MD (18 | SSL_SECOP_OTHER_CERT) +/* Peer EE key in certificate */ +# define SSL_SECOP_PEER_EE_KEY (SSL_SECOP_EE_KEY | SSL_SECOP_PEER) +/* Peer CA key in certificate */ +# define SSL_SECOP_PEER_CA_KEY (SSL_SECOP_CA_KEY | SSL_SECOP_PEER) +/* Peer CA digest algorithm in certificate */ +# define SSL_SECOP_PEER_CA_MD (SSL_SECOP_CA_MD | SSL_SECOP_PEER) + +void SSL_set_security_level(SSL *s, int level); +__owur int SSL_get_security_level(const SSL *s); +void SSL_set_security_callback(SSL *s, + int (*cb) (const SSL *s, const SSL_CTX *ctx, + int op, int bits, int nid, + void *other, void *ex)); +int (*SSL_get_security_callback(const SSL *s)) (const SSL *s, + const SSL_CTX *ctx, int op, + int bits, int nid, void *other, + void *ex); +void SSL_set0_security_ex_data(SSL *s, void *ex); +__owur void *SSL_get0_security_ex_data(const SSL *s); + +void SSL_CTX_set_security_level(SSL_CTX *ctx, int level); +__owur int SSL_CTX_get_security_level(const SSL_CTX *ctx); +void SSL_CTX_set_security_callback(SSL_CTX *ctx, + int (*cb) (const SSL *s, const SSL_CTX *ctx, + int op, int bits, int nid, + void *other, void *ex)); +int (*SSL_CTX_get_security_callback(const SSL_CTX *ctx)) (const SSL *s, + const SSL_CTX *ctx, + int op, int bits, + int nid, + void *other, + void *ex); +void SSL_CTX_set0_security_ex_data(SSL_CTX *ctx, void *ex); +__owur void *SSL_CTX_get0_security_ex_data(const SSL_CTX *ctx); + +/* OPENSSL_INIT flag 0x010000 reserved for internal use */ +# define OPENSSL_INIT_NO_LOAD_SSL_STRINGS 0x00100000L +# define OPENSSL_INIT_LOAD_SSL_STRINGS 0x00200000L + +# define OPENSSL_INIT_SSL_DEFAULT \ + (OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS) + +int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); + +# ifndef OPENSSL_NO_UNIT_TEST +__owur const struct openssl_ssl_test_functions *SSL_test_functions(void); +# endif + +__owur int SSL_free_buffers(SSL *ssl); +__owur int SSL_alloc_buffers(SSL *ssl); + +/* Status codes passed to the decrypt session ticket callback. Some of these + * are for internal use only and are never passed to the callback. */ +typedef int SSL_TICKET_STATUS; + +/* Support for ticket appdata */ +/* fatal error, malloc failure */ +# define SSL_TICKET_FATAL_ERR_MALLOC 0 +/* fatal error, either from parsing or decrypting the ticket */ +# define SSL_TICKET_FATAL_ERR_OTHER 1 +/* No ticket present */ +# define SSL_TICKET_NONE 2 +/* Empty ticket present */ +# define SSL_TICKET_EMPTY 3 +/* the ticket couldn't be decrypted */ +# define SSL_TICKET_NO_DECRYPT 4 +/* a ticket was successfully decrypted */ +# define SSL_TICKET_SUCCESS 5 +/* same as above but the ticket needs to be renewed */ +# define SSL_TICKET_SUCCESS_RENEW 6 + +/* Return codes for the decrypt session ticket callback */ +typedef int SSL_TICKET_RETURN; + +/* An error occurred */ +#define SSL_TICKET_RETURN_ABORT 0 +/* Do not use the ticket, do not send a renewed ticket to the client */ +#define SSL_TICKET_RETURN_IGNORE 1 +/* Do not use the ticket, send a renewed ticket to the client */ +#define SSL_TICKET_RETURN_IGNORE_RENEW 2 +/* Use the ticket, do not send a renewed ticket to the client */ +#define SSL_TICKET_RETURN_USE 3 +/* Use the ticket, send a renewed ticket to the client */ +#define SSL_TICKET_RETURN_USE_RENEW 4 + +typedef int (*SSL_CTX_generate_session_ticket_fn)(SSL *s, void *arg); +typedef SSL_TICKET_RETURN (*SSL_CTX_decrypt_session_ticket_fn)(SSL *s, SSL_SESSION *ss, + const unsigned char *keyname, + size_t keyname_length, + SSL_TICKET_STATUS status, + void *arg); +int SSL_CTX_set_session_ticket_cb(SSL_CTX *ctx, + SSL_CTX_generate_session_ticket_fn gen_cb, + SSL_CTX_decrypt_session_ticket_fn dec_cb, + void *arg); +int SSL_SESSION_set1_ticket_appdata(SSL_SESSION *ss, const void *data, size_t len); +int SSL_SESSION_get0_ticket_appdata(SSL_SESSION *ss, void **data, size_t *len); + +extern const char SSL_version_str[]; + +typedef unsigned int (*DTLS_timer_cb)(SSL *s, unsigned int timer_us); + +void DTLS_set_timer_cb(SSL *s, DTLS_timer_cb cb); + + +typedef int (*SSL_allow_early_data_cb_fn)(SSL *s, void *arg); +void SSL_CTX_set_allow_early_data_cb(SSL_CTX *ctx, + SSL_allow_early_data_cb_fn cb, + void *arg); +void SSL_set_allow_early_data_cb(SSL *s, + SSL_allow_early_data_cb_fn cb, + void *arg); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/ssl2.h b/thrid-party/openssl/openssl/ssl2.h new file mode 100644 index 0000000000..5321bd272c --- /dev/null +++ b/thrid-party/openssl/openssl/ssl2.h @@ -0,0 +1,24 @@ +/* + * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SSL2_H +# define HEADER_SSL2_H + +#ifdef __cplusplus +extern "C" { +#endif + +# define SSL2_VERSION 0x0002 + +# define SSL2_MT_CLIENT_HELLO 1 + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/ssl23.h b/thrid-party/openssl/openssl/ssl23.h new file mode 100644 index 0000000000..9de4685af9 --- /dev/null +++ b/thrid-party/openssl/openssl/ssl23.h @@ -0,0 +1,84 @@ +/* ssl/ssl23.h */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED 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 OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (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 licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#ifndef HEADER_SSL23_H +# define HEADER_SSL23_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * client + */ +/* write to server */ +# define SSL23_ST_CW_CLNT_HELLO_A (0x210|SSL_ST_CONNECT) +# define SSL23_ST_CW_CLNT_HELLO_B (0x211|SSL_ST_CONNECT) +/* read from server */ +# define SSL23_ST_CR_SRVR_HELLO_A (0x220|SSL_ST_CONNECT) +# define SSL23_ST_CR_SRVR_HELLO_B (0x221|SSL_ST_CONNECT) + +/* server */ +/* read from client */ +# define SSL23_ST_SR_CLNT_HELLO_A (0x210|SSL_ST_ACCEPT) +# define SSL23_ST_SR_CLNT_HELLO_B (0x211|SSL_ST_ACCEPT) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/ssl3.h b/thrid-party/openssl/openssl/ssl3.h new file mode 100644 index 0000000000..8d01fcc487 --- /dev/null +++ b/thrid-party/openssl/openssl/ssl3.h @@ -0,0 +1,339 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SSL3_H +# define HEADER_SSL3_H + +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Signalling cipher suite value from RFC 5746 + * (TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + */ +# define SSL3_CK_SCSV 0x030000FF + +/* + * Signalling cipher suite value from draft-ietf-tls-downgrade-scsv-00 + * (TLS_FALLBACK_SCSV) + */ +# define SSL3_CK_FALLBACK_SCSV 0x03005600 + +# define SSL3_CK_RSA_NULL_MD5 0x03000001 +# define SSL3_CK_RSA_NULL_SHA 0x03000002 +# define SSL3_CK_RSA_RC4_40_MD5 0x03000003 +# define SSL3_CK_RSA_RC4_128_MD5 0x03000004 +# define SSL3_CK_RSA_RC4_128_SHA 0x03000005 +# define SSL3_CK_RSA_RC2_40_MD5 0x03000006 +# define SSL3_CK_RSA_IDEA_128_SHA 0x03000007 +# define SSL3_CK_RSA_DES_40_CBC_SHA 0x03000008 +# define SSL3_CK_RSA_DES_64_CBC_SHA 0x03000009 +# define SSL3_CK_RSA_DES_192_CBC3_SHA 0x0300000A + +# define SSL3_CK_DH_DSS_DES_40_CBC_SHA 0x0300000B +# define SSL3_CK_DH_DSS_DES_64_CBC_SHA 0x0300000C +# define SSL3_CK_DH_DSS_DES_192_CBC3_SHA 0x0300000D +# define SSL3_CK_DH_RSA_DES_40_CBC_SHA 0x0300000E +# define SSL3_CK_DH_RSA_DES_64_CBC_SHA 0x0300000F +# define SSL3_CK_DH_RSA_DES_192_CBC3_SHA 0x03000010 + +# define SSL3_CK_DHE_DSS_DES_40_CBC_SHA 0x03000011 +# define SSL3_CK_EDH_DSS_DES_40_CBC_SHA SSL3_CK_DHE_DSS_DES_40_CBC_SHA +# define SSL3_CK_DHE_DSS_DES_64_CBC_SHA 0x03000012 +# define SSL3_CK_EDH_DSS_DES_64_CBC_SHA SSL3_CK_DHE_DSS_DES_64_CBC_SHA +# define SSL3_CK_DHE_DSS_DES_192_CBC3_SHA 0x03000013 +# define SSL3_CK_EDH_DSS_DES_192_CBC3_SHA SSL3_CK_DHE_DSS_DES_192_CBC3_SHA +# define SSL3_CK_DHE_RSA_DES_40_CBC_SHA 0x03000014 +# define SSL3_CK_EDH_RSA_DES_40_CBC_SHA SSL3_CK_DHE_RSA_DES_40_CBC_SHA +# define SSL3_CK_DHE_RSA_DES_64_CBC_SHA 0x03000015 +# define SSL3_CK_EDH_RSA_DES_64_CBC_SHA SSL3_CK_DHE_RSA_DES_64_CBC_SHA +# define SSL3_CK_DHE_RSA_DES_192_CBC3_SHA 0x03000016 +# define SSL3_CK_EDH_RSA_DES_192_CBC3_SHA SSL3_CK_DHE_RSA_DES_192_CBC3_SHA + +# define SSL3_CK_ADH_RC4_40_MD5 0x03000017 +# define SSL3_CK_ADH_RC4_128_MD5 0x03000018 +# define SSL3_CK_ADH_DES_40_CBC_SHA 0x03000019 +# define SSL3_CK_ADH_DES_64_CBC_SHA 0x0300001A +# define SSL3_CK_ADH_DES_192_CBC_SHA 0x0300001B + +/* a bundle of RFC standard cipher names, generated from ssl3_ciphers[] */ +# define SSL3_RFC_RSA_NULL_MD5 "TLS_RSA_WITH_NULL_MD5" +# define SSL3_RFC_RSA_NULL_SHA "TLS_RSA_WITH_NULL_SHA" +# define SSL3_RFC_RSA_DES_192_CBC3_SHA "TLS_RSA_WITH_3DES_EDE_CBC_SHA" +# define SSL3_RFC_DHE_DSS_DES_192_CBC3_SHA "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA" +# define SSL3_RFC_DHE_RSA_DES_192_CBC3_SHA "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" +# define SSL3_RFC_ADH_DES_192_CBC_SHA "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" +# define SSL3_RFC_RSA_IDEA_128_SHA "TLS_RSA_WITH_IDEA_CBC_SHA" +# define SSL3_RFC_RSA_RC4_128_MD5 "TLS_RSA_WITH_RC4_128_MD5" +# define SSL3_RFC_RSA_RC4_128_SHA "TLS_RSA_WITH_RC4_128_SHA" +# define SSL3_RFC_ADH_RC4_128_MD5 "TLS_DH_anon_WITH_RC4_128_MD5" + +# define SSL3_TXT_RSA_NULL_MD5 "NULL-MD5" +# define SSL3_TXT_RSA_NULL_SHA "NULL-SHA" +# define SSL3_TXT_RSA_RC4_40_MD5 "EXP-RC4-MD5" +# define SSL3_TXT_RSA_RC4_128_MD5 "RC4-MD5" +# define SSL3_TXT_RSA_RC4_128_SHA "RC4-SHA" +# define SSL3_TXT_RSA_RC2_40_MD5 "EXP-RC2-CBC-MD5" +# define SSL3_TXT_RSA_IDEA_128_SHA "IDEA-CBC-SHA" +# define SSL3_TXT_RSA_DES_40_CBC_SHA "EXP-DES-CBC-SHA" +# define SSL3_TXT_RSA_DES_64_CBC_SHA "DES-CBC-SHA" +# define SSL3_TXT_RSA_DES_192_CBC3_SHA "DES-CBC3-SHA" + +# define SSL3_TXT_DH_DSS_DES_40_CBC_SHA "EXP-DH-DSS-DES-CBC-SHA" +# define SSL3_TXT_DH_DSS_DES_64_CBC_SHA "DH-DSS-DES-CBC-SHA" +# define SSL3_TXT_DH_DSS_DES_192_CBC3_SHA "DH-DSS-DES-CBC3-SHA" +# define SSL3_TXT_DH_RSA_DES_40_CBC_SHA "EXP-DH-RSA-DES-CBC-SHA" +# define SSL3_TXT_DH_RSA_DES_64_CBC_SHA "DH-RSA-DES-CBC-SHA" +# define SSL3_TXT_DH_RSA_DES_192_CBC3_SHA "DH-RSA-DES-CBC3-SHA" + +# define SSL3_TXT_DHE_DSS_DES_40_CBC_SHA "EXP-DHE-DSS-DES-CBC-SHA" +# define SSL3_TXT_DHE_DSS_DES_64_CBC_SHA "DHE-DSS-DES-CBC-SHA" +# define SSL3_TXT_DHE_DSS_DES_192_CBC3_SHA "DHE-DSS-DES-CBC3-SHA" +# define SSL3_TXT_DHE_RSA_DES_40_CBC_SHA "EXP-DHE-RSA-DES-CBC-SHA" +# define SSL3_TXT_DHE_RSA_DES_64_CBC_SHA "DHE-RSA-DES-CBC-SHA" +# define SSL3_TXT_DHE_RSA_DES_192_CBC3_SHA "DHE-RSA-DES-CBC3-SHA" + +/* + * This next block of six "EDH" labels is for backward compatibility with + * older versions of OpenSSL. New code should use the six "DHE" labels above + * instead: + */ +# define SSL3_TXT_EDH_DSS_DES_40_CBC_SHA "EXP-EDH-DSS-DES-CBC-SHA" +# define SSL3_TXT_EDH_DSS_DES_64_CBC_SHA "EDH-DSS-DES-CBC-SHA" +# define SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA "EDH-DSS-DES-CBC3-SHA" +# define SSL3_TXT_EDH_RSA_DES_40_CBC_SHA "EXP-EDH-RSA-DES-CBC-SHA" +# define SSL3_TXT_EDH_RSA_DES_64_CBC_SHA "EDH-RSA-DES-CBC-SHA" +# define SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA "EDH-RSA-DES-CBC3-SHA" + +# define SSL3_TXT_ADH_RC4_40_MD5 "EXP-ADH-RC4-MD5" +# define SSL3_TXT_ADH_RC4_128_MD5 "ADH-RC4-MD5" +# define SSL3_TXT_ADH_DES_40_CBC_SHA "EXP-ADH-DES-CBC-SHA" +# define SSL3_TXT_ADH_DES_64_CBC_SHA "ADH-DES-CBC-SHA" +# define SSL3_TXT_ADH_DES_192_CBC_SHA "ADH-DES-CBC3-SHA" + +# define SSL3_SSL_SESSION_ID_LENGTH 32 +# define SSL3_MAX_SSL_SESSION_ID_LENGTH 32 + +# define SSL3_MASTER_SECRET_SIZE 48 +# define SSL3_RANDOM_SIZE 32 +# define SSL3_SESSION_ID_SIZE 32 +# define SSL3_RT_HEADER_LENGTH 5 + +# define SSL3_HM_HEADER_LENGTH 4 + +# ifndef SSL3_ALIGN_PAYLOAD + /* + * Some will argue that this increases memory footprint, but it's not + * actually true. Point is that malloc has to return at least 64-bit aligned + * pointers, meaning that allocating 5 bytes wastes 3 bytes in either case. + * Suggested pre-gaping simply moves these wasted bytes from the end of + * allocated region to its front, but makes data payload aligned, which + * improves performance:-) + */ +# define SSL3_ALIGN_PAYLOAD 8 +# else +# if (SSL3_ALIGN_PAYLOAD&(SSL3_ALIGN_PAYLOAD-1))!=0 +# error "insane SSL3_ALIGN_PAYLOAD" +# undef SSL3_ALIGN_PAYLOAD +# endif +# endif + +/* + * This is the maximum MAC (digest) size used by the SSL library. Currently + * maximum of 20 is used by SHA1, but we reserve for future extension for + * 512-bit hashes. + */ + +# define SSL3_RT_MAX_MD_SIZE 64 + +/* + * Maximum block size used in all ciphersuites. Currently 16 for AES. + */ + +# define SSL_RT_MAX_CIPHER_BLOCK_SIZE 16 + +# define SSL3_RT_MAX_EXTRA (16384) + +/* Maximum plaintext length: defined by SSL/TLS standards */ +# define SSL3_RT_MAX_PLAIN_LENGTH 16384 +/* Maximum compression overhead: defined by SSL/TLS standards */ +# define SSL3_RT_MAX_COMPRESSED_OVERHEAD 1024 + +/* + * The standards give a maximum encryption overhead of 1024 bytes. In + * practice the value is lower than this. The overhead is the maximum number + * of padding bytes (256) plus the mac size. + */ +# define SSL3_RT_MAX_ENCRYPTED_OVERHEAD (256 + SSL3_RT_MAX_MD_SIZE) +# define SSL3_RT_MAX_TLS13_ENCRYPTED_OVERHEAD 256 + +/* + * OpenSSL currently only uses a padding length of at most one block so the + * send overhead is smaller. + */ + +# define SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD \ + (SSL_RT_MAX_CIPHER_BLOCK_SIZE + SSL3_RT_MAX_MD_SIZE) + +/* If compression isn't used don't include the compression overhead */ + +# ifdef OPENSSL_NO_COMP +# define SSL3_RT_MAX_COMPRESSED_LENGTH SSL3_RT_MAX_PLAIN_LENGTH +# else +# define SSL3_RT_MAX_COMPRESSED_LENGTH \ + (SSL3_RT_MAX_PLAIN_LENGTH+SSL3_RT_MAX_COMPRESSED_OVERHEAD) +# endif +# define SSL3_RT_MAX_ENCRYPTED_LENGTH \ + (SSL3_RT_MAX_ENCRYPTED_OVERHEAD+SSL3_RT_MAX_COMPRESSED_LENGTH) +# define SSL3_RT_MAX_TLS13_ENCRYPTED_LENGTH \ + (SSL3_RT_MAX_PLAIN_LENGTH + SSL3_RT_MAX_TLS13_ENCRYPTED_OVERHEAD) +# define SSL3_RT_MAX_PACKET_SIZE \ + (SSL3_RT_MAX_ENCRYPTED_LENGTH+SSL3_RT_HEADER_LENGTH) + +# define SSL3_MD_CLIENT_FINISHED_CONST "\x43\x4C\x4E\x54" +# define SSL3_MD_SERVER_FINISHED_CONST "\x53\x52\x56\x52" + +# define SSL3_VERSION 0x0300 +# define SSL3_VERSION_MAJOR 0x03 +# define SSL3_VERSION_MINOR 0x00 + +# define SSL3_RT_CHANGE_CIPHER_SPEC 20 +# define SSL3_RT_ALERT 21 +# define SSL3_RT_HANDSHAKE 22 +# define SSL3_RT_APPLICATION_DATA 23 +# define DTLS1_RT_HEARTBEAT 24 + +/* Pseudo content types to indicate additional parameters */ +# define TLS1_RT_CRYPTO 0x1000 +# define TLS1_RT_CRYPTO_PREMASTER (TLS1_RT_CRYPTO | 0x1) +# define TLS1_RT_CRYPTO_CLIENT_RANDOM (TLS1_RT_CRYPTO | 0x2) +# define TLS1_RT_CRYPTO_SERVER_RANDOM (TLS1_RT_CRYPTO | 0x3) +# define TLS1_RT_CRYPTO_MASTER (TLS1_RT_CRYPTO | 0x4) + +# define TLS1_RT_CRYPTO_READ 0x0000 +# define TLS1_RT_CRYPTO_WRITE 0x0100 +# define TLS1_RT_CRYPTO_MAC (TLS1_RT_CRYPTO | 0x5) +# define TLS1_RT_CRYPTO_KEY (TLS1_RT_CRYPTO | 0x6) +# define TLS1_RT_CRYPTO_IV (TLS1_RT_CRYPTO | 0x7) +# define TLS1_RT_CRYPTO_FIXED_IV (TLS1_RT_CRYPTO | 0x8) + +/* Pseudo content types for SSL/TLS header info */ +# define SSL3_RT_HEADER 0x100 +# define SSL3_RT_INNER_CONTENT_TYPE 0x101 + +# define SSL3_AL_WARNING 1 +# define SSL3_AL_FATAL 2 + +# define SSL3_AD_CLOSE_NOTIFY 0 +# define SSL3_AD_UNEXPECTED_MESSAGE 10/* fatal */ +# define SSL3_AD_BAD_RECORD_MAC 20/* fatal */ +# define SSL3_AD_DECOMPRESSION_FAILURE 30/* fatal */ +# define SSL3_AD_HANDSHAKE_FAILURE 40/* fatal */ +# define SSL3_AD_NO_CERTIFICATE 41 +# define SSL3_AD_BAD_CERTIFICATE 42 +# define SSL3_AD_UNSUPPORTED_CERTIFICATE 43 +# define SSL3_AD_CERTIFICATE_REVOKED 44 +# define SSL3_AD_CERTIFICATE_EXPIRED 45 +# define SSL3_AD_CERTIFICATE_UNKNOWN 46 +# define SSL3_AD_ILLEGAL_PARAMETER 47/* fatal */ + +# define TLS1_HB_REQUEST 1 +# define TLS1_HB_RESPONSE 2 + + +# define SSL3_CT_RSA_SIGN 1 +# define SSL3_CT_DSS_SIGN 2 +# define SSL3_CT_RSA_FIXED_DH 3 +# define SSL3_CT_DSS_FIXED_DH 4 +# define SSL3_CT_RSA_EPHEMERAL_DH 5 +# define SSL3_CT_DSS_EPHEMERAL_DH 6 +# define SSL3_CT_FORTEZZA_DMS 20 +/* + * SSL3_CT_NUMBER is used to size arrays and it must be large enough to + * contain all of the cert types defined for *either* SSLv3 and TLSv1. + */ +# define SSL3_CT_NUMBER 10 + +# if defined(TLS_CT_NUMBER) +# if TLS_CT_NUMBER != SSL3_CT_NUMBER +# error "SSL/TLS CT_NUMBER values do not match" +# endif +# endif + +/* No longer used as of OpenSSL 1.1.1 */ +# define SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS 0x0001 + +/* Removed from OpenSSL 1.1.0 */ +# define TLS1_FLAGS_TLS_PADDING_BUG 0x0 + +# define TLS1_FLAGS_SKIP_CERT_VERIFY 0x0010 + +/* Set if we encrypt then mac instead of usual mac then encrypt */ +# define TLS1_FLAGS_ENCRYPT_THEN_MAC_READ 0x0100 +# define TLS1_FLAGS_ENCRYPT_THEN_MAC TLS1_FLAGS_ENCRYPT_THEN_MAC_READ + +/* Set if extended master secret extension received from peer */ +# define TLS1_FLAGS_RECEIVED_EXTMS 0x0200 + +# define TLS1_FLAGS_ENCRYPT_THEN_MAC_WRITE 0x0400 + +# define TLS1_FLAGS_STATELESS 0x0800 + +# define SSL3_MT_HELLO_REQUEST 0 +# define SSL3_MT_CLIENT_HELLO 1 +# define SSL3_MT_SERVER_HELLO 2 +# define SSL3_MT_NEWSESSION_TICKET 4 +# define SSL3_MT_END_OF_EARLY_DATA 5 +# define SSL3_MT_ENCRYPTED_EXTENSIONS 8 +# define SSL3_MT_CERTIFICATE 11 +# define SSL3_MT_SERVER_KEY_EXCHANGE 12 +# define SSL3_MT_CERTIFICATE_REQUEST 13 +# define SSL3_MT_SERVER_DONE 14 +# define SSL3_MT_CERTIFICATE_VERIFY 15 +# define SSL3_MT_CLIENT_KEY_EXCHANGE 16 +# define SSL3_MT_FINISHED 20 +# define SSL3_MT_CERTIFICATE_URL 21 +# define SSL3_MT_CERTIFICATE_STATUS 22 +# define SSL3_MT_SUPPLEMENTAL_DATA 23 +# define SSL3_MT_KEY_UPDATE 24 +# ifndef OPENSSL_NO_NEXTPROTONEG +# define SSL3_MT_NEXT_PROTO 67 +# endif +# define SSL3_MT_MESSAGE_HASH 254 +# define DTLS1_MT_HELLO_VERIFY_REQUEST 3 + +/* Dummy message type for handling CCS like a normal handshake message */ +# define SSL3_MT_CHANGE_CIPHER_SPEC 0x0101 + +# define SSL3_MT_CCS 1 + +/* These are used when changing over to a new cipher */ +# define SSL3_CC_READ 0x001 +# define SSL3_CC_WRITE 0x002 +# define SSL3_CC_CLIENT 0x010 +# define SSL3_CC_SERVER 0x020 +# define SSL3_CC_EARLY 0x040 +# define SSL3_CC_HANDSHAKE 0x080 +# define SSL3_CC_APPLICATION 0x100 +# define SSL3_CHANGE_CIPHER_CLIENT_WRITE (SSL3_CC_CLIENT|SSL3_CC_WRITE) +# define SSL3_CHANGE_CIPHER_SERVER_READ (SSL3_CC_SERVER|SSL3_CC_READ) +# define SSL3_CHANGE_CIPHER_CLIENT_READ (SSL3_CC_CLIENT|SSL3_CC_READ) +# define SSL3_CHANGE_CIPHER_SERVER_WRITE (SSL3_CC_SERVER|SSL3_CC_WRITE) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/sslerr.h b/thrid-party/openssl/openssl/sslerr.h new file mode 100644 index 0000000000..3d6850dea3 --- /dev/null +++ b/thrid-party/openssl/openssl/sslerr.h @@ -0,0 +1,772 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SSLERR_H +# define HEADER_SSLERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_SSL_strings(void); + +/* + * SSL function codes. + */ +# define SSL_F_ADD_CLIENT_KEY_SHARE_EXT 438 +# define SSL_F_ADD_KEY_SHARE 512 +# define SSL_F_BYTES_TO_CIPHER_LIST 519 +# define SSL_F_CHECK_SUITEB_CIPHER_LIST 331 +# define SSL_F_CIPHERSUITE_CB 622 +# define SSL_F_CONSTRUCT_CA_NAMES 552 +# define SSL_F_CONSTRUCT_KEY_EXCHANGE_TBS 553 +# define SSL_F_CONSTRUCT_STATEFUL_TICKET 636 +# define SSL_F_CONSTRUCT_STATELESS_TICKET 637 +# define SSL_F_CREATE_SYNTHETIC_MESSAGE_HASH 539 +# define SSL_F_CREATE_TICKET_PREQUEL 638 +# define SSL_F_CT_MOVE_SCTS 345 +# define SSL_F_CT_STRICT 349 +# define SSL_F_CUSTOM_EXT_ADD 554 +# define SSL_F_CUSTOM_EXT_PARSE 555 +# define SSL_F_D2I_SSL_SESSION 103 +# define SSL_F_DANE_CTX_ENABLE 347 +# define SSL_F_DANE_MTYPE_SET 393 +# define SSL_F_DANE_TLSA_ADD 394 +# define SSL_F_DERIVE_SECRET_KEY_AND_IV 514 +# define SSL_F_DO_DTLS1_WRITE 245 +# define SSL_F_DO_SSL3_WRITE 104 +# define SSL_F_DTLS1_BUFFER_RECORD 247 +# define SSL_F_DTLS1_CHECK_TIMEOUT_NUM 318 +# define SSL_F_DTLS1_HEARTBEAT 305 +# define SSL_F_DTLS1_HM_FRAGMENT_NEW 623 +# define SSL_F_DTLS1_PREPROCESS_FRAGMENT 288 +# define SSL_F_DTLS1_PROCESS_BUFFERED_RECORDS 424 +# define SSL_F_DTLS1_PROCESS_RECORD 257 +# define SSL_F_DTLS1_READ_BYTES 258 +# define SSL_F_DTLS1_READ_FAILED 339 +# define SSL_F_DTLS1_RETRANSMIT_MESSAGE 390 +# define SSL_F_DTLS1_WRITE_APP_DATA_BYTES 268 +# define SSL_F_DTLS1_WRITE_BYTES 545 +# define SSL_F_DTLSV1_LISTEN 350 +# define SSL_F_DTLS_CONSTRUCT_CHANGE_CIPHER_SPEC 371 +# define SSL_F_DTLS_CONSTRUCT_HELLO_VERIFY_REQUEST 385 +# define SSL_F_DTLS_GET_REASSEMBLED_MESSAGE 370 +# define SSL_F_DTLS_PROCESS_HELLO_VERIFY 386 +# define SSL_F_DTLS_RECORD_LAYER_NEW 635 +# define SSL_F_DTLS_WAIT_FOR_DRY 592 +# define SSL_F_EARLY_DATA_COUNT_OK 532 +# define SSL_F_FINAL_EARLY_DATA 556 +# define SSL_F_FINAL_EC_PT_FORMATS 485 +# define SSL_F_FINAL_EMS 486 +# define SSL_F_FINAL_KEY_SHARE 503 +# define SSL_F_FINAL_MAXFRAGMENTLEN 557 +# define SSL_F_FINAL_RENEGOTIATE 483 +# define SSL_F_FINAL_SERVER_NAME 558 +# define SSL_F_FINAL_SIG_ALGS 497 +# define SSL_F_GET_CERT_VERIFY_TBS_DATA 588 +# define SSL_F_NSS_KEYLOG_INT 500 +# define SSL_F_OPENSSL_INIT_SSL 342 +# define SSL_F_OSSL_STATEM_CLIENT13_READ_TRANSITION 436 +# define SSL_F_OSSL_STATEM_CLIENT13_WRITE_TRANSITION 598 +# define SSL_F_OSSL_STATEM_CLIENT_CONSTRUCT_MESSAGE 430 +# define SSL_F_OSSL_STATEM_CLIENT_POST_PROCESS_MESSAGE 593 +# define SSL_F_OSSL_STATEM_CLIENT_PROCESS_MESSAGE 594 +# define SSL_F_OSSL_STATEM_CLIENT_READ_TRANSITION 417 +# define SSL_F_OSSL_STATEM_CLIENT_WRITE_TRANSITION 599 +# define SSL_F_OSSL_STATEM_SERVER13_READ_TRANSITION 437 +# define SSL_F_OSSL_STATEM_SERVER13_WRITE_TRANSITION 600 +# define SSL_F_OSSL_STATEM_SERVER_CONSTRUCT_MESSAGE 431 +# define SSL_F_OSSL_STATEM_SERVER_POST_PROCESS_MESSAGE 601 +# define SSL_F_OSSL_STATEM_SERVER_POST_WORK 602 +# define SSL_F_OSSL_STATEM_SERVER_PROCESS_MESSAGE 603 +# define SSL_F_OSSL_STATEM_SERVER_READ_TRANSITION 418 +# define SSL_F_OSSL_STATEM_SERVER_WRITE_TRANSITION 604 +# define SSL_F_PARSE_CA_NAMES 541 +# define SSL_F_PITEM_NEW 624 +# define SSL_F_PQUEUE_NEW 625 +# define SSL_F_PROCESS_KEY_SHARE_EXT 439 +# define SSL_F_READ_STATE_MACHINE 352 +# define SSL_F_SET_CLIENT_CIPHERSUITE 540 +# define SSL_F_SRP_GENERATE_CLIENT_MASTER_SECRET 595 +# define SSL_F_SRP_GENERATE_SERVER_MASTER_SECRET 589 +# define SSL_F_SRP_VERIFY_SERVER_PARAM 596 +# define SSL_F_SSL3_CHANGE_CIPHER_STATE 129 +# define SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM 130 +# define SSL_F_SSL3_CTRL 213 +# define SSL_F_SSL3_CTX_CTRL 133 +# define SSL_F_SSL3_DIGEST_CACHED_RECORDS 293 +# define SSL_F_SSL3_DO_CHANGE_CIPHER_SPEC 292 +# define SSL_F_SSL3_ENC 608 +# define SSL_F_SSL3_FINAL_FINISH_MAC 285 +# define SSL_F_SSL3_FINISH_MAC 587 +# define SSL_F_SSL3_GENERATE_KEY_BLOCK 238 +# define SSL_F_SSL3_GENERATE_MASTER_SECRET 388 +# define SSL_F_SSL3_GET_RECORD 143 +# define SSL_F_SSL3_INIT_FINISHED_MAC 397 +# define SSL_F_SSL3_OUTPUT_CERT_CHAIN 147 +# define SSL_F_SSL3_READ_BYTES 148 +# define SSL_F_SSL3_READ_N 149 +# define SSL_F_SSL3_SETUP_KEY_BLOCK 157 +# define SSL_F_SSL3_SETUP_READ_BUFFER 156 +# define SSL_F_SSL3_SETUP_WRITE_BUFFER 291 +# define SSL_F_SSL3_WRITE_BYTES 158 +# define SSL_F_SSL3_WRITE_PENDING 159 +# define SSL_F_SSL_ADD_CERT_CHAIN 316 +# define SSL_F_SSL_ADD_CERT_TO_BUF 319 +# define SSL_F_SSL_ADD_CERT_TO_WPACKET 493 +# define SSL_F_SSL_ADD_CLIENTHELLO_RENEGOTIATE_EXT 298 +# define SSL_F_SSL_ADD_CLIENTHELLO_TLSEXT 277 +# define SSL_F_SSL_ADD_CLIENTHELLO_USE_SRTP_EXT 307 +# define SSL_F_SSL_ADD_DIR_CERT_SUBJECTS_TO_STACK 215 +# define SSL_F_SSL_ADD_FILE_CERT_SUBJECTS_TO_STACK 216 +# define SSL_F_SSL_ADD_SERVERHELLO_RENEGOTIATE_EXT 299 +# define SSL_F_SSL_ADD_SERVERHELLO_TLSEXT 278 +# define SSL_F_SSL_ADD_SERVERHELLO_USE_SRTP_EXT 308 +# define SSL_F_SSL_BAD_METHOD 160 +# define SSL_F_SSL_BUILD_CERT_CHAIN 332 +# define SSL_F_SSL_BYTES_TO_CIPHER_LIST 161 +# define SSL_F_SSL_CACHE_CIPHERLIST 520 +# define SSL_F_SSL_CERT_ADD0_CHAIN_CERT 346 +# define SSL_F_SSL_CERT_DUP 221 +# define SSL_F_SSL_CERT_NEW 162 +# define SSL_F_SSL_CERT_SET0_CHAIN 340 +# define SSL_F_SSL_CHECK_PRIVATE_KEY 163 +# define SSL_F_SSL_CHECK_SERVERHELLO_TLSEXT 280 +# define SSL_F_SSL_CHECK_SRP_EXT_CLIENTHELLO 606 +# define SSL_F_SSL_CHECK_SRVR_ECC_CERT_AND_ALG 279 +# define SSL_F_SSL_CHOOSE_CLIENT_VERSION 607 +# define SSL_F_SSL_CIPHER_DESCRIPTION 626 +# define SSL_F_SSL_CIPHER_LIST_TO_BYTES 425 +# define SSL_F_SSL_CIPHER_PROCESS_RULESTR 230 +# define SSL_F_SSL_CIPHER_STRENGTH_SORT 231 +# define SSL_F_SSL_CLEAR 164 +# define SSL_F_SSL_CLIENT_HELLO_GET1_EXTENSIONS_PRESENT 627 +# define SSL_F_SSL_COMP_ADD_COMPRESSION_METHOD 165 +# define SSL_F_SSL_CONF_CMD 334 +# define SSL_F_SSL_CREATE_CIPHER_LIST 166 +# define SSL_F_SSL_CTRL 232 +# define SSL_F_SSL_CTX_CHECK_PRIVATE_KEY 168 +# define SSL_F_SSL_CTX_ENABLE_CT 398 +# define SSL_F_SSL_CTX_MAKE_PROFILES 309 +# define SSL_F_SSL_CTX_NEW 169 +# define SSL_F_SSL_CTX_SET_ALPN_PROTOS 343 +# define SSL_F_SSL_CTX_SET_CIPHER_LIST 269 +# define SSL_F_SSL_CTX_SET_CLIENT_CERT_ENGINE 290 +# define SSL_F_SSL_CTX_SET_CT_VALIDATION_CALLBACK 396 +# define SSL_F_SSL_CTX_SET_SESSION_ID_CONTEXT 219 +# define SSL_F_SSL_CTX_SET_SSL_VERSION 170 +# define SSL_F_SSL_CTX_SET_TLSEXT_MAX_FRAGMENT_LENGTH 551 +# define SSL_F_SSL_CTX_USE_CERTIFICATE 171 +# define SSL_F_SSL_CTX_USE_CERTIFICATE_ASN1 172 +# define SSL_F_SSL_CTX_USE_CERTIFICATE_FILE 173 +# define SSL_F_SSL_CTX_USE_PRIVATEKEY 174 +# define SSL_F_SSL_CTX_USE_PRIVATEKEY_ASN1 175 +# define SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE 176 +# define SSL_F_SSL_CTX_USE_PSK_IDENTITY_HINT 272 +# define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY 177 +# define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_ASN1 178 +# define SSL_F_SSL_CTX_USE_RSAPRIVATEKEY_FILE 179 +# define SSL_F_SSL_CTX_USE_SERVERINFO 336 +# define SSL_F_SSL_CTX_USE_SERVERINFO_EX 543 +# define SSL_F_SSL_CTX_USE_SERVERINFO_FILE 337 +# define SSL_F_SSL_DANE_DUP 403 +# define SSL_F_SSL_DANE_ENABLE 395 +# define SSL_F_SSL_DERIVE 590 +# define SSL_F_SSL_DO_CONFIG 391 +# define SSL_F_SSL_DO_HANDSHAKE 180 +# define SSL_F_SSL_DUP_CA_LIST 408 +# define SSL_F_SSL_ENABLE_CT 402 +# define SSL_F_SSL_GENERATE_PKEY_GROUP 559 +# define SSL_F_SSL_GENERATE_SESSION_ID 547 +# define SSL_F_SSL_GET_NEW_SESSION 181 +# define SSL_F_SSL_GET_PREV_SESSION 217 +# define SSL_F_SSL_GET_SERVER_CERT_INDEX 322 +# define SSL_F_SSL_GET_SIGN_PKEY 183 +# define SSL_F_SSL_HANDSHAKE_HASH 560 +# define SSL_F_SSL_INIT_WBIO_BUFFER 184 +# define SSL_F_SSL_KEY_UPDATE 515 +# define SSL_F_SSL_LOAD_CLIENT_CA_FILE 185 +# define SSL_F_SSL_LOG_MASTER_SECRET 498 +# define SSL_F_SSL_LOG_RSA_CLIENT_KEY_EXCHANGE 499 +# define SSL_F_SSL_MODULE_INIT 392 +# define SSL_F_SSL_NEW 186 +# define SSL_F_SSL_NEXT_PROTO_VALIDATE 565 +# define SSL_F_SSL_PARSE_CLIENTHELLO_RENEGOTIATE_EXT 300 +# define SSL_F_SSL_PARSE_CLIENTHELLO_TLSEXT 302 +# define SSL_F_SSL_PARSE_CLIENTHELLO_USE_SRTP_EXT 310 +# define SSL_F_SSL_PARSE_SERVERHELLO_RENEGOTIATE_EXT 301 +# define SSL_F_SSL_PARSE_SERVERHELLO_TLSEXT 303 +# define SSL_F_SSL_PARSE_SERVERHELLO_USE_SRTP_EXT 311 +# define SSL_F_SSL_PEEK 270 +# define SSL_F_SSL_PEEK_EX 432 +# define SSL_F_SSL_PEEK_INTERNAL 522 +# define SSL_F_SSL_READ 223 +# define SSL_F_SSL_READ_EARLY_DATA 529 +# define SSL_F_SSL_READ_EX 434 +# define SSL_F_SSL_READ_INTERNAL 523 +# define SSL_F_SSL_RENEGOTIATE 516 +# define SSL_F_SSL_RENEGOTIATE_ABBREVIATED 546 +# define SSL_F_SSL_SCAN_CLIENTHELLO_TLSEXT 320 +# define SSL_F_SSL_SCAN_SERVERHELLO_TLSEXT 321 +# define SSL_F_SSL_SESSION_DUP 348 +# define SSL_F_SSL_SESSION_NEW 189 +# define SSL_F_SSL_SESSION_PRINT_FP 190 +# define SSL_F_SSL_SESSION_SET1_ID 423 +# define SSL_F_SSL_SESSION_SET1_ID_CONTEXT 312 +# define SSL_F_SSL_SET_ALPN_PROTOS 344 +# define SSL_F_SSL_SET_CERT 191 +# define SSL_F_SSL_SET_CERT_AND_KEY 621 +# define SSL_F_SSL_SET_CIPHER_LIST 271 +# define SSL_F_SSL_SET_CT_VALIDATION_CALLBACK 399 +# define SSL_F_SSL_SET_FD 192 +# define SSL_F_SSL_SET_PKEY 193 +# define SSL_F_SSL_SET_RFD 194 +# define SSL_F_SSL_SET_SESSION 195 +# define SSL_F_SSL_SET_SESSION_ID_CONTEXT 218 +# define SSL_F_SSL_SET_SESSION_TICKET_EXT 294 +# define SSL_F_SSL_SET_TLSEXT_MAX_FRAGMENT_LENGTH 550 +# define SSL_F_SSL_SET_WFD 196 +# define SSL_F_SSL_SHUTDOWN 224 +# define SSL_F_SSL_SRP_CTX_INIT 313 +# define SSL_F_SSL_START_ASYNC_JOB 389 +# define SSL_F_SSL_UNDEFINED_FUNCTION 197 +# define SSL_F_SSL_UNDEFINED_VOID_FUNCTION 244 +# define SSL_F_SSL_USE_CERTIFICATE 198 +# define SSL_F_SSL_USE_CERTIFICATE_ASN1 199 +# define SSL_F_SSL_USE_CERTIFICATE_FILE 200 +# define SSL_F_SSL_USE_PRIVATEKEY 201 +# define SSL_F_SSL_USE_PRIVATEKEY_ASN1 202 +# define SSL_F_SSL_USE_PRIVATEKEY_FILE 203 +# define SSL_F_SSL_USE_PSK_IDENTITY_HINT 273 +# define SSL_F_SSL_USE_RSAPRIVATEKEY 204 +# define SSL_F_SSL_USE_RSAPRIVATEKEY_ASN1 205 +# define SSL_F_SSL_USE_RSAPRIVATEKEY_FILE 206 +# define SSL_F_SSL_VALIDATE_CT 400 +# define SSL_F_SSL_VERIFY_CERT_CHAIN 207 +# define SSL_F_SSL_VERIFY_CLIENT_POST_HANDSHAKE 616 +# define SSL_F_SSL_WRITE 208 +# define SSL_F_SSL_WRITE_EARLY_DATA 526 +# define SSL_F_SSL_WRITE_EARLY_FINISH 527 +# define SSL_F_SSL_WRITE_EX 433 +# define SSL_F_SSL_WRITE_INTERNAL 524 +# define SSL_F_STATE_MACHINE 353 +# define SSL_F_TLS12_CHECK_PEER_SIGALG 333 +# define SSL_F_TLS12_COPY_SIGALGS 533 +# define SSL_F_TLS13_CHANGE_CIPHER_STATE 440 +# define SSL_F_TLS13_ENC 609 +# define SSL_F_TLS13_FINAL_FINISH_MAC 605 +# define SSL_F_TLS13_GENERATE_SECRET 591 +# define SSL_F_TLS13_HKDF_EXPAND 561 +# define SSL_F_TLS13_RESTORE_HANDSHAKE_DIGEST_FOR_PHA 617 +# define SSL_F_TLS13_SAVE_HANDSHAKE_DIGEST_FOR_PHA 618 +# define SSL_F_TLS13_SETUP_KEY_BLOCK 441 +# define SSL_F_TLS1_CHANGE_CIPHER_STATE 209 +# define SSL_F_TLS1_CHECK_DUPLICATE_EXTENSIONS 341 +# define SSL_F_TLS1_ENC 401 +# define SSL_F_TLS1_EXPORT_KEYING_MATERIAL 314 +# define SSL_F_TLS1_GET_CURVELIST 338 +# define SSL_F_TLS1_PRF 284 +# define SSL_F_TLS1_SAVE_U16 628 +# define SSL_F_TLS1_SETUP_KEY_BLOCK 211 +# define SSL_F_TLS1_SET_GROUPS 629 +# define SSL_F_TLS1_SET_RAW_SIGALGS 630 +# define SSL_F_TLS1_SET_SERVER_SIGALGS 335 +# define SSL_F_TLS1_SET_SHARED_SIGALGS 631 +# define SSL_F_TLS1_SET_SIGALGS 632 +# define SSL_F_TLS_CHOOSE_SIGALG 513 +# define SSL_F_TLS_CLIENT_KEY_EXCHANGE_POST_WORK 354 +# define SSL_F_TLS_COLLECT_EXTENSIONS 435 +# define SSL_F_TLS_CONSTRUCT_CERTIFICATE_AUTHORITIES 542 +# define SSL_F_TLS_CONSTRUCT_CERTIFICATE_REQUEST 372 +# define SSL_F_TLS_CONSTRUCT_CERT_STATUS 429 +# define SSL_F_TLS_CONSTRUCT_CERT_STATUS_BODY 494 +# define SSL_F_TLS_CONSTRUCT_CERT_VERIFY 496 +# define SSL_F_TLS_CONSTRUCT_CHANGE_CIPHER_SPEC 427 +# define SSL_F_TLS_CONSTRUCT_CKE_DHE 404 +# define SSL_F_TLS_CONSTRUCT_CKE_ECDHE 405 +# define SSL_F_TLS_CONSTRUCT_CKE_GOST 406 +# define SSL_F_TLS_CONSTRUCT_CKE_PSK_PREAMBLE 407 +# define SSL_F_TLS_CONSTRUCT_CKE_RSA 409 +# define SSL_F_TLS_CONSTRUCT_CKE_SRP 410 +# define SSL_F_TLS_CONSTRUCT_CLIENT_CERTIFICATE 484 +# define SSL_F_TLS_CONSTRUCT_CLIENT_HELLO 487 +# define SSL_F_TLS_CONSTRUCT_CLIENT_KEY_EXCHANGE 488 +# define SSL_F_TLS_CONSTRUCT_CLIENT_VERIFY 489 +# define SSL_F_TLS_CONSTRUCT_CTOS_ALPN 466 +# define SSL_F_TLS_CONSTRUCT_CTOS_CERTIFICATE 355 +# define SSL_F_TLS_CONSTRUCT_CTOS_COOKIE 535 +# define SSL_F_TLS_CONSTRUCT_CTOS_EARLY_DATA 530 +# define SSL_F_TLS_CONSTRUCT_CTOS_EC_PT_FORMATS 467 +# define SSL_F_TLS_CONSTRUCT_CTOS_EMS 468 +# define SSL_F_TLS_CONSTRUCT_CTOS_ETM 469 +# define SSL_F_TLS_CONSTRUCT_CTOS_HELLO 356 +# define SSL_F_TLS_CONSTRUCT_CTOS_KEY_EXCHANGE 357 +# define SSL_F_TLS_CONSTRUCT_CTOS_KEY_SHARE 470 +# define SSL_F_TLS_CONSTRUCT_CTOS_MAXFRAGMENTLEN 549 +# define SSL_F_TLS_CONSTRUCT_CTOS_NPN 471 +# define SSL_F_TLS_CONSTRUCT_CTOS_PADDING 472 +# define SSL_F_TLS_CONSTRUCT_CTOS_POST_HANDSHAKE_AUTH 619 +# define SSL_F_TLS_CONSTRUCT_CTOS_PSK 501 +# define SSL_F_TLS_CONSTRUCT_CTOS_PSK_KEX_MODES 509 +# define SSL_F_TLS_CONSTRUCT_CTOS_RENEGOTIATE 473 +# define SSL_F_TLS_CONSTRUCT_CTOS_SCT 474 +# define SSL_F_TLS_CONSTRUCT_CTOS_SERVER_NAME 475 +# define SSL_F_TLS_CONSTRUCT_CTOS_SESSION_TICKET 476 +# define SSL_F_TLS_CONSTRUCT_CTOS_SIG_ALGS 477 +# define SSL_F_TLS_CONSTRUCT_CTOS_SRP 478 +# define SSL_F_TLS_CONSTRUCT_CTOS_STATUS_REQUEST 479 +# define SSL_F_TLS_CONSTRUCT_CTOS_SUPPORTED_GROUPS 480 +# define SSL_F_TLS_CONSTRUCT_CTOS_SUPPORTED_VERSIONS 481 +# define SSL_F_TLS_CONSTRUCT_CTOS_USE_SRTP 482 +# define SSL_F_TLS_CONSTRUCT_CTOS_VERIFY 358 +# define SSL_F_TLS_CONSTRUCT_ENCRYPTED_EXTENSIONS 443 +# define SSL_F_TLS_CONSTRUCT_END_OF_EARLY_DATA 536 +# define SSL_F_TLS_CONSTRUCT_EXTENSIONS 447 +# define SSL_F_TLS_CONSTRUCT_FINISHED 359 +# define SSL_F_TLS_CONSTRUCT_HELLO_REQUEST 373 +# define SSL_F_TLS_CONSTRUCT_HELLO_RETRY_REQUEST 510 +# define SSL_F_TLS_CONSTRUCT_KEY_UPDATE 517 +# define SSL_F_TLS_CONSTRUCT_NEW_SESSION_TICKET 428 +# define SSL_F_TLS_CONSTRUCT_NEXT_PROTO 426 +# define SSL_F_TLS_CONSTRUCT_SERVER_CERTIFICATE 490 +# define SSL_F_TLS_CONSTRUCT_SERVER_HELLO 491 +# define SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE 492 +# define SSL_F_TLS_CONSTRUCT_STOC_ALPN 451 +# define SSL_F_TLS_CONSTRUCT_STOC_CERTIFICATE 374 +# define SSL_F_TLS_CONSTRUCT_STOC_COOKIE 613 +# define SSL_F_TLS_CONSTRUCT_STOC_CRYPTOPRO_BUG 452 +# define SSL_F_TLS_CONSTRUCT_STOC_DONE 375 +# define SSL_F_TLS_CONSTRUCT_STOC_EARLY_DATA 531 +# define SSL_F_TLS_CONSTRUCT_STOC_EARLY_DATA_INFO 525 +# define SSL_F_TLS_CONSTRUCT_STOC_EC_PT_FORMATS 453 +# define SSL_F_TLS_CONSTRUCT_STOC_EMS 454 +# define SSL_F_TLS_CONSTRUCT_STOC_ETM 455 +# define SSL_F_TLS_CONSTRUCT_STOC_HELLO 376 +# define SSL_F_TLS_CONSTRUCT_STOC_KEY_EXCHANGE 377 +# define SSL_F_TLS_CONSTRUCT_STOC_KEY_SHARE 456 +# define SSL_F_TLS_CONSTRUCT_STOC_MAXFRAGMENTLEN 548 +# define SSL_F_TLS_CONSTRUCT_STOC_NEXT_PROTO_NEG 457 +# define SSL_F_TLS_CONSTRUCT_STOC_PSK 504 +# define SSL_F_TLS_CONSTRUCT_STOC_RENEGOTIATE 458 +# define SSL_F_TLS_CONSTRUCT_STOC_SERVER_NAME 459 +# define SSL_F_TLS_CONSTRUCT_STOC_SESSION_TICKET 460 +# define SSL_F_TLS_CONSTRUCT_STOC_STATUS_REQUEST 461 +# define SSL_F_TLS_CONSTRUCT_STOC_SUPPORTED_GROUPS 544 +# define SSL_F_TLS_CONSTRUCT_STOC_SUPPORTED_VERSIONS 611 +# define SSL_F_TLS_CONSTRUCT_STOC_USE_SRTP 462 +# define SSL_F_TLS_EARLY_POST_PROCESS_CLIENT_HELLO 521 +# define SSL_F_TLS_FINISH_HANDSHAKE 597 +# define SSL_F_TLS_GET_MESSAGE_BODY 351 +# define SSL_F_TLS_GET_MESSAGE_HEADER 387 +# define SSL_F_TLS_HANDLE_ALPN 562 +# define SSL_F_TLS_HANDLE_STATUS_REQUEST 563 +# define SSL_F_TLS_PARSE_CERTIFICATE_AUTHORITIES 566 +# define SSL_F_TLS_PARSE_CLIENTHELLO_TLSEXT 449 +# define SSL_F_TLS_PARSE_CTOS_ALPN 567 +# define SSL_F_TLS_PARSE_CTOS_COOKIE 614 +# define SSL_F_TLS_PARSE_CTOS_EARLY_DATA 568 +# define SSL_F_TLS_PARSE_CTOS_EC_PT_FORMATS 569 +# define SSL_F_TLS_PARSE_CTOS_EMS 570 +# define SSL_F_TLS_PARSE_CTOS_KEY_SHARE 463 +# define SSL_F_TLS_PARSE_CTOS_MAXFRAGMENTLEN 571 +# define SSL_F_TLS_PARSE_CTOS_POST_HANDSHAKE_AUTH 620 +# define SSL_F_TLS_PARSE_CTOS_PSK 505 +# define SSL_F_TLS_PARSE_CTOS_PSK_KEX_MODES 572 +# define SSL_F_TLS_PARSE_CTOS_RENEGOTIATE 464 +# define SSL_F_TLS_PARSE_CTOS_SERVER_NAME 573 +# define SSL_F_TLS_PARSE_CTOS_SESSION_TICKET 574 +# define SSL_F_TLS_PARSE_CTOS_SIG_ALGS 575 +# define SSL_F_TLS_PARSE_CTOS_SIG_ALGS_CERT 615 +# define SSL_F_TLS_PARSE_CTOS_SRP 576 +# define SSL_F_TLS_PARSE_CTOS_STATUS_REQUEST 577 +# define SSL_F_TLS_PARSE_CTOS_SUPPORTED_GROUPS 578 +# define SSL_F_TLS_PARSE_CTOS_USE_SRTP 465 +# define SSL_F_TLS_PARSE_STOC_ALPN 579 +# define SSL_F_TLS_PARSE_STOC_COOKIE 534 +# define SSL_F_TLS_PARSE_STOC_EARLY_DATA 538 +# define SSL_F_TLS_PARSE_STOC_EARLY_DATA_INFO 528 +# define SSL_F_TLS_PARSE_STOC_EC_PT_FORMATS 580 +# define SSL_F_TLS_PARSE_STOC_KEY_SHARE 445 +# define SSL_F_TLS_PARSE_STOC_MAXFRAGMENTLEN 581 +# define SSL_F_TLS_PARSE_STOC_NPN 582 +# define SSL_F_TLS_PARSE_STOC_PSK 502 +# define SSL_F_TLS_PARSE_STOC_RENEGOTIATE 448 +# define SSL_F_TLS_PARSE_STOC_SCT 564 +# define SSL_F_TLS_PARSE_STOC_SERVER_NAME 583 +# define SSL_F_TLS_PARSE_STOC_SESSION_TICKET 584 +# define SSL_F_TLS_PARSE_STOC_STATUS_REQUEST 585 +# define SSL_F_TLS_PARSE_STOC_SUPPORTED_VERSIONS 612 +# define SSL_F_TLS_PARSE_STOC_USE_SRTP 446 +# define SSL_F_TLS_POST_PROCESS_CLIENT_HELLO 378 +# define SSL_F_TLS_POST_PROCESS_CLIENT_KEY_EXCHANGE 384 +# define SSL_F_TLS_PREPARE_CLIENT_CERTIFICATE 360 +# define SSL_F_TLS_PROCESS_AS_HELLO_RETRY_REQUEST 610 +# define SSL_F_TLS_PROCESS_CERTIFICATE_REQUEST 361 +# define SSL_F_TLS_PROCESS_CERT_STATUS 362 +# define SSL_F_TLS_PROCESS_CERT_STATUS_BODY 495 +# define SSL_F_TLS_PROCESS_CERT_VERIFY 379 +# define SSL_F_TLS_PROCESS_CHANGE_CIPHER_SPEC 363 +# define SSL_F_TLS_PROCESS_CKE_DHE 411 +# define SSL_F_TLS_PROCESS_CKE_ECDHE 412 +# define SSL_F_TLS_PROCESS_CKE_GOST 413 +# define SSL_F_TLS_PROCESS_CKE_PSK_PREAMBLE 414 +# define SSL_F_TLS_PROCESS_CKE_RSA 415 +# define SSL_F_TLS_PROCESS_CKE_SRP 416 +# define SSL_F_TLS_PROCESS_CLIENT_CERTIFICATE 380 +# define SSL_F_TLS_PROCESS_CLIENT_HELLO 381 +# define SSL_F_TLS_PROCESS_CLIENT_KEY_EXCHANGE 382 +# define SSL_F_TLS_PROCESS_ENCRYPTED_EXTENSIONS 444 +# define SSL_F_TLS_PROCESS_END_OF_EARLY_DATA 537 +# define SSL_F_TLS_PROCESS_FINISHED 364 +# define SSL_F_TLS_PROCESS_HELLO_REQ 507 +# define SSL_F_TLS_PROCESS_HELLO_RETRY_REQUEST 511 +# define SSL_F_TLS_PROCESS_INITIAL_SERVER_FLIGHT 442 +# define SSL_F_TLS_PROCESS_KEY_EXCHANGE 365 +# define SSL_F_TLS_PROCESS_KEY_UPDATE 518 +# define SSL_F_TLS_PROCESS_NEW_SESSION_TICKET 366 +# define SSL_F_TLS_PROCESS_NEXT_PROTO 383 +# define SSL_F_TLS_PROCESS_SERVER_CERTIFICATE 367 +# define SSL_F_TLS_PROCESS_SERVER_DONE 368 +# define SSL_F_TLS_PROCESS_SERVER_HELLO 369 +# define SSL_F_TLS_PROCESS_SKE_DHE 419 +# define SSL_F_TLS_PROCESS_SKE_ECDHE 420 +# define SSL_F_TLS_PROCESS_SKE_PSK_PREAMBLE 421 +# define SSL_F_TLS_PROCESS_SKE_SRP 422 +# define SSL_F_TLS_PSK_DO_BINDER 506 +# define SSL_F_TLS_SCAN_CLIENTHELLO_TLSEXT 450 +# define SSL_F_TLS_SETUP_HANDSHAKE 508 +# define SSL_F_USE_CERTIFICATE_CHAIN_FILE 220 +# define SSL_F_WPACKET_INTERN_INIT_LEN 633 +# define SSL_F_WPACKET_START_SUB_PACKET_LEN__ 634 +# define SSL_F_WRITE_STATE_MACHINE 586 + +/* + * SSL reason codes. + */ +# define SSL_R_APPLICATION_DATA_AFTER_CLOSE_NOTIFY 291 +# define SSL_R_APP_DATA_IN_HANDSHAKE 100 +# define SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT 272 +# define SSL_R_AT_LEAST_TLS_1_0_NEEDED_IN_FIPS_MODE 143 +# define SSL_R_AT_LEAST_TLS_1_2_NEEDED_IN_SUITEB_MODE 158 +# define SSL_R_BAD_CHANGE_CIPHER_SPEC 103 +# define SSL_R_BAD_CIPHER 186 +# define SSL_R_BAD_DATA 390 +# define SSL_R_BAD_DATA_RETURNED_BY_CALLBACK 106 +# define SSL_R_BAD_DECOMPRESSION 107 +# define SSL_R_BAD_DH_VALUE 102 +# define SSL_R_BAD_DIGEST_LENGTH 111 +# define SSL_R_BAD_EARLY_DATA 233 +# define SSL_R_BAD_ECC_CERT 304 +# define SSL_R_BAD_ECPOINT 306 +# define SSL_R_BAD_EXTENSION 110 +# define SSL_R_BAD_HANDSHAKE_LENGTH 332 +# define SSL_R_BAD_HANDSHAKE_STATE 236 +# define SSL_R_BAD_HELLO_REQUEST 105 +# define SSL_R_BAD_HRR_VERSION 263 +# define SSL_R_BAD_KEY_SHARE 108 +# define SSL_R_BAD_KEY_UPDATE 122 +# define SSL_R_BAD_LEGACY_VERSION 292 +# define SSL_R_BAD_LENGTH 271 +# define SSL_R_BAD_PACKET 240 +# define SSL_R_BAD_PACKET_LENGTH 115 +# define SSL_R_BAD_PROTOCOL_VERSION_NUMBER 116 +# define SSL_R_BAD_PSK 219 +# define SSL_R_BAD_PSK_IDENTITY 114 +# define SSL_R_BAD_RECORD_TYPE 443 +# define SSL_R_BAD_RSA_ENCRYPT 119 +# define SSL_R_BAD_SIGNATURE 123 +# define SSL_R_BAD_SRP_A_LENGTH 347 +# define SSL_R_BAD_SRP_PARAMETERS 371 +# define SSL_R_BAD_SRTP_MKI_VALUE 352 +# define SSL_R_BAD_SRTP_PROTECTION_PROFILE_LIST 353 +# define SSL_R_BAD_SSL_FILETYPE 124 +# define SSL_R_BAD_VALUE 384 +# define SSL_R_BAD_WRITE_RETRY 127 +# define SSL_R_BINDER_DOES_NOT_VERIFY 253 +# define SSL_R_BIO_NOT_SET 128 +# define SSL_R_BLOCK_CIPHER_PAD_IS_WRONG 129 +# define SSL_R_BN_LIB 130 +# define SSL_R_CALLBACK_FAILED 234 +# define SSL_R_CANNOT_CHANGE_CIPHER 109 +# define SSL_R_CA_DN_LENGTH_MISMATCH 131 +# define SSL_R_CA_KEY_TOO_SMALL 397 +# define SSL_R_CA_MD_TOO_WEAK 398 +# define SSL_R_CCS_RECEIVED_EARLY 133 +# define SSL_R_CERTIFICATE_VERIFY_FAILED 134 +# define SSL_R_CERT_CB_ERROR 377 +# define SSL_R_CERT_LENGTH_MISMATCH 135 +# define SSL_R_CIPHERSUITE_DIGEST_HAS_CHANGED 218 +# define SSL_R_CIPHER_CODE_WRONG_LENGTH 137 +# define SSL_R_CIPHER_OR_HASH_UNAVAILABLE 138 +# define SSL_R_CLIENTHELLO_TLSEXT 226 +# define SSL_R_COMPRESSED_LENGTH_TOO_LONG 140 +# define SSL_R_COMPRESSION_DISABLED 343 +# define SSL_R_COMPRESSION_FAILURE 141 +# define SSL_R_COMPRESSION_ID_NOT_WITHIN_PRIVATE_RANGE 307 +# define SSL_R_COMPRESSION_LIBRARY_ERROR 142 +# define SSL_R_CONNECTION_TYPE_NOT_SET 144 +# define SSL_R_CONTEXT_NOT_DANE_ENABLED 167 +# define SSL_R_COOKIE_GEN_CALLBACK_FAILURE 400 +# define SSL_R_COOKIE_MISMATCH 308 +# define SSL_R_CUSTOM_EXT_HANDLER_ALREADY_INSTALLED 206 +# define SSL_R_DANE_ALREADY_ENABLED 172 +# define SSL_R_DANE_CANNOT_OVERRIDE_MTYPE_FULL 173 +# define SSL_R_DANE_NOT_ENABLED 175 +# define SSL_R_DANE_TLSA_BAD_CERTIFICATE 180 +# define SSL_R_DANE_TLSA_BAD_CERTIFICATE_USAGE 184 +# define SSL_R_DANE_TLSA_BAD_DATA_LENGTH 189 +# define SSL_R_DANE_TLSA_BAD_DIGEST_LENGTH 192 +# define SSL_R_DANE_TLSA_BAD_MATCHING_TYPE 200 +# define SSL_R_DANE_TLSA_BAD_PUBLIC_KEY 201 +# define SSL_R_DANE_TLSA_BAD_SELECTOR 202 +# define SSL_R_DANE_TLSA_NULL_DATA 203 +# define SSL_R_DATA_BETWEEN_CCS_AND_FINISHED 145 +# define SSL_R_DATA_LENGTH_TOO_LONG 146 +# define SSL_R_DECRYPTION_FAILED 147 +# define SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC 281 +# define SSL_R_DH_KEY_TOO_SMALL 394 +# define SSL_R_DH_PUBLIC_VALUE_LENGTH_IS_WRONG 148 +# define SSL_R_DIGEST_CHECK_FAILED 149 +# define SSL_R_DTLS_MESSAGE_TOO_BIG 334 +# define SSL_R_DUPLICATE_COMPRESSION_ID 309 +# define SSL_R_ECC_CERT_NOT_FOR_SIGNING 318 +# define SSL_R_ECDH_REQUIRED_FOR_SUITEB_MODE 374 +# define SSL_R_EE_KEY_TOO_SMALL 399 +# define SSL_R_EMPTY_SRTP_PROTECTION_PROFILE_LIST 354 +# define SSL_R_ENCRYPTED_LENGTH_TOO_LONG 150 +# define SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST 151 +# define SSL_R_ERROR_SETTING_TLSA_BASE_DOMAIN 204 +# define SSL_R_EXCEEDS_MAX_FRAGMENT_SIZE 194 +# define SSL_R_EXCESSIVE_MESSAGE_SIZE 152 +# define SSL_R_EXTENSION_NOT_RECEIVED 279 +# define SSL_R_EXTRA_DATA_IN_MESSAGE 153 +# define SSL_R_EXT_LENGTH_MISMATCH 163 +# define SSL_R_FAILED_TO_INIT_ASYNC 405 +# define SSL_R_FRAGMENTED_CLIENT_HELLO 401 +# define SSL_R_GOT_A_FIN_BEFORE_A_CCS 154 +# define SSL_R_HTTPS_PROXY_REQUEST 155 +# define SSL_R_HTTP_REQUEST 156 +# define SSL_R_ILLEGAL_POINT_COMPRESSION 162 +# define SSL_R_ILLEGAL_SUITEB_DIGEST 380 +# define SSL_R_INAPPROPRIATE_FALLBACK 373 +# define SSL_R_INCONSISTENT_COMPRESSION 340 +# define SSL_R_INCONSISTENT_EARLY_DATA_ALPN 222 +# define SSL_R_INCONSISTENT_EARLY_DATA_SNI 231 +# define SSL_R_INCONSISTENT_EXTMS 104 +# define SSL_R_INSUFFICIENT_SECURITY 241 +# define SSL_R_INVALID_ALERT 205 +# define SSL_R_INVALID_CCS_MESSAGE 260 +# define SSL_R_INVALID_CERTIFICATE_OR_ALG 238 +# define SSL_R_INVALID_COMMAND 280 +# define SSL_R_INVALID_COMPRESSION_ALGORITHM 341 +# define SSL_R_INVALID_CONFIG 283 +# define SSL_R_INVALID_CONFIGURATION_NAME 113 +# define SSL_R_INVALID_CONTEXT 282 +# define SSL_R_INVALID_CT_VALIDATION_TYPE 212 +# define SSL_R_INVALID_KEY_UPDATE_TYPE 120 +# define SSL_R_INVALID_MAX_EARLY_DATA 174 +# define SSL_R_INVALID_NULL_CMD_NAME 385 +# define SSL_R_INVALID_SEQUENCE_NUMBER 402 +# define SSL_R_INVALID_SERVERINFO_DATA 388 +# define SSL_R_INVALID_SESSION_ID 999 +# define SSL_R_INVALID_SRP_USERNAME 357 +# define SSL_R_INVALID_STATUS_RESPONSE 328 +# define SSL_R_INVALID_TICKET_KEYS_LENGTH 325 +# define SSL_R_LENGTH_MISMATCH 159 +# define SSL_R_LENGTH_TOO_LONG 404 +# define SSL_R_LENGTH_TOO_SHORT 160 +# define SSL_R_LIBRARY_BUG 274 +# define SSL_R_LIBRARY_HAS_NO_CIPHERS 161 +# define SSL_R_MISSING_DSA_SIGNING_CERT 165 +# define SSL_R_MISSING_ECDSA_SIGNING_CERT 381 +# define SSL_R_MISSING_FATAL 256 +# define SSL_R_MISSING_PARAMETERS 290 +# define SSL_R_MISSING_RSA_CERTIFICATE 168 +# define SSL_R_MISSING_RSA_ENCRYPTING_CERT 169 +# define SSL_R_MISSING_RSA_SIGNING_CERT 170 +# define SSL_R_MISSING_SIGALGS_EXTENSION 112 +# define SSL_R_MISSING_SIGNING_CERT 221 +# define SSL_R_MISSING_SRP_PARAM 358 +# define SSL_R_MISSING_SUPPORTED_GROUPS_EXTENSION 209 +# define SSL_R_MISSING_TMP_DH_KEY 171 +# define SSL_R_MISSING_TMP_ECDH_KEY 311 +# define SSL_R_MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA 293 +# define SSL_R_NOT_ON_RECORD_BOUNDARY 182 +# define SSL_R_NOT_REPLACING_CERTIFICATE 289 +# define SSL_R_NOT_SERVER 284 +# define SSL_R_NO_APPLICATION_PROTOCOL 235 +# define SSL_R_NO_CERTIFICATES_RETURNED 176 +# define SSL_R_NO_CERTIFICATE_ASSIGNED 177 +# define SSL_R_NO_CERTIFICATE_SET 179 +# define SSL_R_NO_CHANGE_FOLLOWING_HRR 214 +# define SSL_R_NO_CIPHERS_AVAILABLE 181 +# define SSL_R_NO_CIPHERS_SPECIFIED 183 +# define SSL_R_NO_CIPHER_MATCH 185 +# define SSL_R_NO_CLIENT_CERT_METHOD 331 +# define SSL_R_NO_COMPRESSION_SPECIFIED 187 +# define SSL_R_NO_COOKIE_CALLBACK_SET 287 +# define SSL_R_NO_GOST_CERTIFICATE_SENT_BY_PEER 330 +# define SSL_R_NO_METHOD_SPECIFIED 188 +# define SSL_R_NO_PEM_EXTENSIONS 389 +# define SSL_R_NO_PRIVATE_KEY_ASSIGNED 190 +# define SSL_R_NO_PROTOCOLS_AVAILABLE 191 +# define SSL_R_NO_RENEGOTIATION 339 +# define SSL_R_NO_REQUIRED_DIGEST 324 +# define SSL_R_NO_SHARED_CIPHER 193 +# define SSL_R_NO_SHARED_GROUPS 410 +# define SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS 376 +# define SSL_R_NO_SRTP_PROFILES 359 +# define SSL_R_NO_SUITABLE_KEY_SHARE 101 +# define SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM 118 +# define SSL_R_NO_VALID_SCTS 216 +# define SSL_R_NO_VERIFY_COOKIE_CALLBACK 403 +# define SSL_R_NULL_SSL_CTX 195 +# define SSL_R_NULL_SSL_METHOD_PASSED 196 +# define SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED 197 +# define SSL_R_OLD_SESSION_COMPRESSION_ALGORITHM_NOT_RETURNED 344 +# define SSL_R_OVERFLOW_ERROR 237 +# define SSL_R_PACKET_LENGTH_TOO_LONG 198 +# define SSL_R_PARSE_TLSEXT 227 +# define SSL_R_PATH_TOO_LONG 270 +# define SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE 199 +# define SSL_R_PEM_NAME_BAD_PREFIX 391 +# define SSL_R_PEM_NAME_TOO_SHORT 392 +# define SSL_R_PIPELINE_FAILURE 406 +# define SSL_R_POST_HANDSHAKE_AUTH_ENCODING_ERR 278 +# define SSL_R_PRIVATE_KEY_MISMATCH 288 +# define SSL_R_PROTOCOL_IS_SHUTDOWN 207 +# define SSL_R_PSK_IDENTITY_NOT_FOUND 223 +# define SSL_R_PSK_NO_CLIENT_CB 224 +# define SSL_R_PSK_NO_SERVER_CB 225 +# define SSL_R_READ_BIO_NOT_SET 211 +# define SSL_R_READ_TIMEOUT_EXPIRED 312 +# define SSL_R_RECORD_LENGTH_MISMATCH 213 +# define SSL_R_RECORD_TOO_SMALL 298 +# define SSL_R_RENEGOTIATE_EXT_TOO_LONG 335 +# define SSL_R_RENEGOTIATION_ENCODING_ERR 336 +# define SSL_R_RENEGOTIATION_MISMATCH 337 +# define SSL_R_REQUEST_PENDING 285 +# define SSL_R_REQUEST_SENT 286 +# define SSL_R_REQUIRED_CIPHER_MISSING 215 +# define SSL_R_REQUIRED_COMPRESSION_ALGORITHM_MISSING 342 +# define SSL_R_SCSV_RECEIVED_WHEN_RENEGOTIATING 345 +# define SSL_R_SCT_VERIFICATION_FAILED 208 +# define SSL_R_SERVERHELLO_TLSEXT 275 +# define SSL_R_SESSION_ID_CONTEXT_UNINITIALIZED 277 +# define SSL_R_SHUTDOWN_WHILE_IN_INIT 407 +# define SSL_R_SIGNATURE_ALGORITHMS_ERROR 360 +# define SSL_R_SIGNATURE_FOR_NON_SIGNING_CERTIFICATE 220 +# define SSL_R_SRP_A_CALC 361 +# define SSL_R_SRTP_COULD_NOT_ALLOCATE_PROFILES 362 +# define SSL_R_SRTP_PROTECTION_PROFILE_LIST_TOO_LONG 363 +# define SSL_R_SRTP_UNKNOWN_PROTECTION_PROFILE 364 +# define SSL_R_SSL3_EXT_INVALID_MAX_FRAGMENT_LENGTH 232 +# define SSL_R_SSL3_EXT_INVALID_SERVERNAME 319 +# define SSL_R_SSL3_EXT_INVALID_SERVERNAME_TYPE 320 +# define SSL_R_SSL3_SESSION_ID_TOO_LONG 300 +# define SSL_R_SSLV3_ALERT_BAD_CERTIFICATE 1042 +# define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020 +# define SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED 1045 +# define SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED 1044 +# define SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN 1046 +# define SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE 1030 +# define SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE 1040 +# define SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER 1047 +# define SSL_R_SSLV3_ALERT_NO_CERTIFICATE 1041 +# define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010 +# define SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE 1043 +# define SSL_R_SSL_COMMAND_SECTION_EMPTY 117 +# define SSL_R_SSL_COMMAND_SECTION_NOT_FOUND 125 +# define SSL_R_SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION 228 +# define SSL_R_SSL_HANDSHAKE_FAILURE 229 +# define SSL_R_SSL_LIBRARY_HAS_NO_CIPHERS 230 +# define SSL_R_SSL_NEGATIVE_LENGTH 372 +# define SSL_R_SSL_SECTION_EMPTY 126 +# define SSL_R_SSL_SECTION_NOT_FOUND 136 +# define SSL_R_SSL_SESSION_ID_CALLBACK_FAILED 301 +# define SSL_R_SSL_SESSION_ID_CONFLICT 302 +# define SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG 273 +# define SSL_R_SSL_SESSION_ID_HAS_BAD_LENGTH 303 +# define SSL_R_SSL_SESSION_ID_TOO_LONG 408 +# define SSL_R_SSL_SESSION_VERSION_MISMATCH 210 +# define SSL_R_STILL_IN_INIT 121 +# define SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED 1116 +# define SSL_R_TLSV13_ALERT_MISSING_EXTENSION 1109 +# define SSL_R_TLSV1_ALERT_ACCESS_DENIED 1049 +# define SSL_R_TLSV1_ALERT_DECODE_ERROR 1050 +# define SSL_R_TLSV1_ALERT_DECRYPTION_FAILED 1021 +# define SSL_R_TLSV1_ALERT_DECRYPT_ERROR 1051 +# define SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION 1060 +# define SSL_R_TLSV1_ALERT_INAPPROPRIATE_FALLBACK 1086 +# define SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY 1071 +# define SSL_R_TLSV1_ALERT_INTERNAL_ERROR 1080 +# define SSL_R_TLSV1_ALERT_NO_RENEGOTIATION 1100 +# define SSL_R_TLSV1_ALERT_PROTOCOL_VERSION 1070 +# define SSL_R_TLSV1_ALERT_RECORD_OVERFLOW 1022 +# define SSL_R_TLSV1_ALERT_UNKNOWN_CA 1048 +# define SSL_R_TLSV1_ALERT_USER_CANCELLED 1090 +# define SSL_R_TLSV1_BAD_CERTIFICATE_HASH_VALUE 1114 +# define SSL_R_TLSV1_BAD_CERTIFICATE_STATUS_RESPONSE 1113 +# define SSL_R_TLSV1_CERTIFICATE_UNOBTAINABLE 1111 +# define SSL_R_TLSV1_UNRECOGNIZED_NAME 1112 +# define SSL_R_TLSV1_UNSUPPORTED_EXTENSION 1110 +# define SSL_R_TLS_HEARTBEAT_PEER_DOESNT_ACCEPT 365 +# define SSL_R_TLS_HEARTBEAT_PENDING 366 +# define SSL_R_TLS_ILLEGAL_EXPORTER_LABEL 367 +# define SSL_R_TLS_INVALID_ECPOINTFORMAT_LIST 157 +# define SSL_R_TOO_MANY_KEY_UPDATES 132 +# define SSL_R_TOO_MANY_WARN_ALERTS 409 +# define SSL_R_TOO_MUCH_EARLY_DATA 164 +# define SSL_R_UNABLE_TO_FIND_ECDH_PARAMETERS 314 +# define SSL_R_UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS 239 +# define SSL_R_UNABLE_TO_LOAD_SSL3_MD5_ROUTINES 242 +# define SSL_R_UNABLE_TO_LOAD_SSL3_SHA1_ROUTINES 243 +# define SSL_R_UNEXPECTED_CCS_MESSAGE 262 +# define SSL_R_UNEXPECTED_END_OF_EARLY_DATA 178 +# define SSL_R_UNEXPECTED_MESSAGE 244 +# define SSL_R_UNEXPECTED_RECORD 245 +# define SSL_R_UNINITIALIZED 276 +# define SSL_R_UNKNOWN_ALERT_TYPE 246 +# define SSL_R_UNKNOWN_CERTIFICATE_TYPE 247 +# define SSL_R_UNKNOWN_CIPHER_RETURNED 248 +# define SSL_R_UNKNOWN_CIPHER_TYPE 249 +# define SSL_R_UNKNOWN_CMD_NAME 386 +# define SSL_R_UNKNOWN_COMMAND 139 +# define SSL_R_UNKNOWN_DIGEST 368 +# define SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE 250 +# define SSL_R_UNKNOWN_PKEY_TYPE 251 +# define SSL_R_UNKNOWN_PROTOCOL 252 +# define SSL_R_UNKNOWN_SSL_VERSION 254 +# define SSL_R_UNKNOWN_STATE 255 +# define SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED 338 +# define SSL_R_UNSOLICITED_EXTENSION 217 +# define SSL_R_UNSUPPORTED_COMPRESSION_ALGORITHM 257 +# define SSL_R_UNSUPPORTED_ELLIPTIC_CURVE 315 +# define SSL_R_UNSUPPORTED_PROTOCOL 258 +# define SSL_R_UNSUPPORTED_SSL_VERSION 259 +# define SSL_R_UNSUPPORTED_STATUS_TYPE 329 +# define SSL_R_USE_SRTP_NOT_NEGOTIATED 369 +# define SSL_R_VERSION_TOO_HIGH 166 +# define SSL_R_VERSION_TOO_LOW 396 +# define SSL_R_WRONG_CERTIFICATE_TYPE 383 +# define SSL_R_WRONG_CIPHER_RETURNED 261 +# define SSL_R_WRONG_CURVE 378 +# define SSL_R_WRONG_SIGNATURE_LENGTH 264 +# define SSL_R_WRONG_SIGNATURE_SIZE 265 +# define SSL_R_WRONG_SIGNATURE_TYPE 370 +# define SSL_R_WRONG_SSL_VERSION 266 +# define SSL_R_WRONG_VERSION_NUMBER 267 +# define SSL_R_X509_LIB 268 +# define SSL_R_X509_VERIFICATION_SETUP_PROBLEMS 269 + +#endif diff --git a/thrid-party/openssl/openssl/stack.h b/thrid-party/openssl/openssl/stack.h new file mode 100644 index 0000000000..cfc075057a --- /dev/null +++ b/thrid-party/openssl/openssl/stack.h @@ -0,0 +1,83 @@ +/* + * Copyright 1995-2017 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_STACK_H +# define HEADER_STACK_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stack_st OPENSSL_STACK; /* Use STACK_OF(...) instead */ + +typedef int (*OPENSSL_sk_compfunc)(const void *, const void *); +typedef void (*OPENSSL_sk_freefunc)(void *); +typedef void *(*OPENSSL_sk_copyfunc)(const void *); + +int OPENSSL_sk_num(const OPENSSL_STACK *); +void *OPENSSL_sk_value(const OPENSSL_STACK *, int); + +void *OPENSSL_sk_set(OPENSSL_STACK *st, int i, const void *data); + +OPENSSL_STACK *OPENSSL_sk_new(OPENSSL_sk_compfunc cmp); +OPENSSL_STACK *OPENSSL_sk_new_null(void); +OPENSSL_STACK *OPENSSL_sk_new_reserve(OPENSSL_sk_compfunc c, int n); +int OPENSSL_sk_reserve(OPENSSL_STACK *st, int n); +void OPENSSL_sk_free(OPENSSL_STACK *); +void OPENSSL_sk_pop_free(OPENSSL_STACK *st, void (*func) (void *)); +OPENSSL_STACK *OPENSSL_sk_deep_copy(const OPENSSL_STACK *, + OPENSSL_sk_copyfunc c, + OPENSSL_sk_freefunc f); +int OPENSSL_sk_insert(OPENSSL_STACK *sk, const void *data, int where); +void *OPENSSL_sk_delete(OPENSSL_STACK *st, int loc); +void *OPENSSL_sk_delete_ptr(OPENSSL_STACK *st, const void *p); +int OPENSSL_sk_find(OPENSSL_STACK *st, const void *data); +int OPENSSL_sk_find_ex(OPENSSL_STACK *st, const void *data); +int OPENSSL_sk_push(OPENSSL_STACK *st, const void *data); +int OPENSSL_sk_unshift(OPENSSL_STACK *st, const void *data); +void *OPENSSL_sk_shift(OPENSSL_STACK *st); +void *OPENSSL_sk_pop(OPENSSL_STACK *st); +void OPENSSL_sk_zero(OPENSSL_STACK *st); +OPENSSL_sk_compfunc OPENSSL_sk_set_cmp_func(OPENSSL_STACK *sk, + OPENSSL_sk_compfunc cmp); +OPENSSL_STACK *OPENSSL_sk_dup(const OPENSSL_STACK *st); +void OPENSSL_sk_sort(OPENSSL_STACK *st); +int OPENSSL_sk_is_sorted(const OPENSSL_STACK *st); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define _STACK OPENSSL_STACK +# define sk_num OPENSSL_sk_num +# define sk_value OPENSSL_sk_value +# define sk_set OPENSSL_sk_set +# define sk_new OPENSSL_sk_new +# define sk_new_null OPENSSL_sk_new_null +# define sk_free OPENSSL_sk_free +# define sk_pop_free OPENSSL_sk_pop_free +# define sk_deep_copy OPENSSL_sk_deep_copy +# define sk_insert OPENSSL_sk_insert +# define sk_delete OPENSSL_sk_delete +# define sk_delete_ptr OPENSSL_sk_delete_ptr +# define sk_find OPENSSL_sk_find +# define sk_find_ex OPENSSL_sk_find_ex +# define sk_push OPENSSL_sk_push +# define sk_unshift OPENSSL_sk_unshift +# define sk_shift OPENSSL_sk_shift +# define sk_pop OPENSSL_sk_pop +# define sk_zero OPENSSL_sk_zero +# define sk_set_cmp_func OPENSSL_sk_set_cmp_func +# define sk_dup OPENSSL_sk_dup +# define sk_sort OPENSSL_sk_sort +# define sk_is_sorted OPENSSL_sk_is_sorted +# endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/store.h b/thrid-party/openssl/openssl/store.h new file mode 100644 index 0000000000..a40a7339e6 --- /dev/null +++ b/thrid-party/openssl/openssl/store.h @@ -0,0 +1,266 @@ +/* + * Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OSSL_STORE_H +# define HEADER_OSSL_STORE_H + +# include +# include +# include +# include + +# ifdef __cplusplus +extern "C" { +# endif + +/*- + * The main OSSL_STORE functions. + * ------------------------------ + * + * These allow applications to open a channel to a resource with supported + * data (keys, certs, crls, ...), read the data a piece at a time and decide + * what to do with it, and finally close. + */ + +typedef struct ossl_store_ctx_st OSSL_STORE_CTX; + +/* + * Typedef for the OSSL_STORE_INFO post processing callback. This can be used + * to massage the given OSSL_STORE_INFO, or to drop it entirely (by returning + * NULL). + */ +typedef OSSL_STORE_INFO *(*OSSL_STORE_post_process_info_fn)(OSSL_STORE_INFO *, + void *); + +/* + * Open a channel given a URI. The given UI method will be used any time the + * loader needs extra input, for example when a password or pin is needed, and + * will be passed the same user data every time it's needed in this context. + * + * Returns a context reference which represents the channel to communicate + * through. + */ +OSSL_STORE_CTX *OSSL_STORE_open(const char *uri, const UI_METHOD *ui_method, + void *ui_data, + OSSL_STORE_post_process_info_fn post_process, + void *post_process_data); + +/* + * Control / fine tune the OSSL_STORE channel. |cmd| determines what is to be + * done, and depends on the underlying loader (use OSSL_STORE_get0_scheme to + * determine which loader is used), except for common commands (see below). + * Each command takes different arguments. + */ +int OSSL_STORE_ctrl(OSSL_STORE_CTX *ctx, int cmd, ... /* args */); +int OSSL_STORE_vctrl(OSSL_STORE_CTX *ctx, int cmd, va_list args); + +/* + * Common ctrl commands that different loaders may choose to support. + */ +/* int on = 0 or 1; STORE_ctrl(ctx, STORE_C_USE_SECMEM, &on); */ +# define OSSL_STORE_C_USE_SECMEM 1 +/* Where custom commands start */ +# define OSSL_STORE_C_CUSTOM_START 100 + +/* + * Read one data item (a key, a cert, a CRL) that is supported by the OSSL_STORE + * functionality, given a context. + * Returns a OSSL_STORE_INFO pointer, from which OpenSSL typed data can be + * extracted with OSSL_STORE_INFO_get0_PKEY(), OSSL_STORE_INFO_get0_CERT(), ... + * NULL is returned on error, which may include that the data found at the URI + * can't be figured out for certain or is ambiguous. + */ +OSSL_STORE_INFO *OSSL_STORE_load(OSSL_STORE_CTX *ctx); + +/* + * Check if end of data (end of file) is reached + * Returns 1 on end, 0 otherwise. + */ +int OSSL_STORE_eof(OSSL_STORE_CTX *ctx); + +/* + * Check if an error occurred + * Returns 1 if it did, 0 otherwise. + */ +int OSSL_STORE_error(OSSL_STORE_CTX *ctx); + +/* + * Close the channel + * Returns 1 on success, 0 on error. + */ +int OSSL_STORE_close(OSSL_STORE_CTX *ctx); + + +/*- + * Extracting OpenSSL types from and creating new OSSL_STORE_INFOs + * --------------------------------------------------------------- + */ + +/* + * Types of data that can be ossl_stored in a OSSL_STORE_INFO. + * OSSL_STORE_INFO_NAME is typically found when getting a listing of + * available "files" / "tokens" / what have you. + */ +# define OSSL_STORE_INFO_NAME 1 /* char * */ +# define OSSL_STORE_INFO_PARAMS 2 /* EVP_PKEY * */ +# define OSSL_STORE_INFO_PKEY 3 /* EVP_PKEY * */ +# define OSSL_STORE_INFO_CERT 4 /* X509 * */ +# define OSSL_STORE_INFO_CRL 5 /* X509_CRL * */ + +/* + * Functions to generate OSSL_STORE_INFOs, one function for each type we + * support having in them, as well as a generic constructor. + * + * In all cases, ownership of the object is transferred to the OSSL_STORE_INFO + * and will therefore be freed when the OSSL_STORE_INFO is freed. + */ +OSSL_STORE_INFO *OSSL_STORE_INFO_new_NAME(char *name); +int OSSL_STORE_INFO_set0_NAME_description(OSSL_STORE_INFO *info, char *desc); +OSSL_STORE_INFO *OSSL_STORE_INFO_new_PARAMS(EVP_PKEY *params); +OSSL_STORE_INFO *OSSL_STORE_INFO_new_PKEY(EVP_PKEY *pkey); +OSSL_STORE_INFO *OSSL_STORE_INFO_new_CERT(X509 *x509); +OSSL_STORE_INFO *OSSL_STORE_INFO_new_CRL(X509_CRL *crl); + +/* + * Functions to try to extract data from a OSSL_STORE_INFO. + */ +int OSSL_STORE_INFO_get_type(const OSSL_STORE_INFO *info); +const char *OSSL_STORE_INFO_get0_NAME(const OSSL_STORE_INFO *info); +char *OSSL_STORE_INFO_get1_NAME(const OSSL_STORE_INFO *info); +const char *OSSL_STORE_INFO_get0_NAME_description(const OSSL_STORE_INFO *info); +char *OSSL_STORE_INFO_get1_NAME_description(const OSSL_STORE_INFO *info); +EVP_PKEY *OSSL_STORE_INFO_get0_PARAMS(const OSSL_STORE_INFO *info); +EVP_PKEY *OSSL_STORE_INFO_get1_PARAMS(const OSSL_STORE_INFO *info); +EVP_PKEY *OSSL_STORE_INFO_get0_PKEY(const OSSL_STORE_INFO *info); +EVP_PKEY *OSSL_STORE_INFO_get1_PKEY(const OSSL_STORE_INFO *info); +X509 *OSSL_STORE_INFO_get0_CERT(const OSSL_STORE_INFO *info); +X509 *OSSL_STORE_INFO_get1_CERT(const OSSL_STORE_INFO *info); +X509_CRL *OSSL_STORE_INFO_get0_CRL(const OSSL_STORE_INFO *info); +X509_CRL *OSSL_STORE_INFO_get1_CRL(const OSSL_STORE_INFO *info); + +const char *OSSL_STORE_INFO_type_string(int type); + +/* + * Free the OSSL_STORE_INFO + */ +void OSSL_STORE_INFO_free(OSSL_STORE_INFO *info); + + +/*- + * Functions to construct a search URI from a base URI and search criteria + * ----------------------------------------------------------------------- + */ + +/* OSSL_STORE search types */ +# define OSSL_STORE_SEARCH_BY_NAME 1 /* subject in certs, issuer in CRLs */ +# define OSSL_STORE_SEARCH_BY_ISSUER_SERIAL 2 +# define OSSL_STORE_SEARCH_BY_KEY_FINGERPRINT 3 +# define OSSL_STORE_SEARCH_BY_ALIAS 4 + +/* To check what search types the scheme handler supports */ +int OSSL_STORE_supports_search(OSSL_STORE_CTX *ctx, int search_type); + +/* Search term constructors */ +/* + * The input is considered to be owned by the caller, and must therefore + * remain present throughout the lifetime of the returned OSSL_STORE_SEARCH + */ +OSSL_STORE_SEARCH *OSSL_STORE_SEARCH_by_name(X509_NAME *name); +OSSL_STORE_SEARCH *OSSL_STORE_SEARCH_by_issuer_serial(X509_NAME *name, + const ASN1_INTEGER + *serial); +OSSL_STORE_SEARCH *OSSL_STORE_SEARCH_by_key_fingerprint(const EVP_MD *digest, + const unsigned char + *bytes, size_t len); +OSSL_STORE_SEARCH *OSSL_STORE_SEARCH_by_alias(const char *alias); + +/* Search term destructor */ +void OSSL_STORE_SEARCH_free(OSSL_STORE_SEARCH *search); + +/* Search term accessors */ +int OSSL_STORE_SEARCH_get_type(const OSSL_STORE_SEARCH *criterion); +X509_NAME *OSSL_STORE_SEARCH_get0_name(OSSL_STORE_SEARCH *criterion); +const ASN1_INTEGER *OSSL_STORE_SEARCH_get0_serial(const OSSL_STORE_SEARCH + *criterion); +const unsigned char *OSSL_STORE_SEARCH_get0_bytes(const OSSL_STORE_SEARCH + *criterion, size_t *length); +const char *OSSL_STORE_SEARCH_get0_string(const OSSL_STORE_SEARCH *criterion); +const EVP_MD *OSSL_STORE_SEARCH_get0_digest(const OSSL_STORE_SEARCH *criterion); + +/* + * Add search criterion and expected return type (which can be unspecified) + * to the loading channel. This MUST happen before the first OSSL_STORE_load(). + */ +int OSSL_STORE_expect(OSSL_STORE_CTX *ctx, int expected_type); +int OSSL_STORE_find(OSSL_STORE_CTX *ctx, OSSL_STORE_SEARCH *search); + + +/*- + * Function to register a loader for the given URI scheme. + * ------------------------------------------------------- + * + * The loader receives all the main components of an URI except for the + * scheme. + */ + +typedef struct ossl_store_loader_st OSSL_STORE_LOADER; +OSSL_STORE_LOADER *OSSL_STORE_LOADER_new(ENGINE *e, const char *scheme); +const ENGINE *OSSL_STORE_LOADER_get0_engine(const OSSL_STORE_LOADER *loader); +const char *OSSL_STORE_LOADER_get0_scheme(const OSSL_STORE_LOADER *loader); +/* struct ossl_store_loader_ctx_st is defined differently by each loader */ +typedef struct ossl_store_loader_ctx_st OSSL_STORE_LOADER_CTX; +typedef OSSL_STORE_LOADER_CTX *(*OSSL_STORE_open_fn)(const OSSL_STORE_LOADER + *loader, + const char *uri, + const UI_METHOD *ui_method, + void *ui_data); +int OSSL_STORE_LOADER_set_open(OSSL_STORE_LOADER *loader, + OSSL_STORE_open_fn open_function); +typedef int (*OSSL_STORE_ctrl_fn)(OSSL_STORE_LOADER_CTX *ctx, int cmd, + va_list args); +int OSSL_STORE_LOADER_set_ctrl(OSSL_STORE_LOADER *loader, + OSSL_STORE_ctrl_fn ctrl_function); +typedef int (*OSSL_STORE_expect_fn)(OSSL_STORE_LOADER_CTX *ctx, int expected); +int OSSL_STORE_LOADER_set_expect(OSSL_STORE_LOADER *loader, + OSSL_STORE_expect_fn expect_function); +typedef int (*OSSL_STORE_find_fn)(OSSL_STORE_LOADER_CTX *ctx, + OSSL_STORE_SEARCH *criteria); +int OSSL_STORE_LOADER_set_find(OSSL_STORE_LOADER *loader, + OSSL_STORE_find_fn find_function); +typedef OSSL_STORE_INFO *(*OSSL_STORE_load_fn)(OSSL_STORE_LOADER_CTX *ctx, + const UI_METHOD *ui_method, + void *ui_data); +int OSSL_STORE_LOADER_set_load(OSSL_STORE_LOADER *loader, + OSSL_STORE_load_fn load_function); +typedef int (*OSSL_STORE_eof_fn)(OSSL_STORE_LOADER_CTX *ctx); +int OSSL_STORE_LOADER_set_eof(OSSL_STORE_LOADER *loader, + OSSL_STORE_eof_fn eof_function); +typedef int (*OSSL_STORE_error_fn)(OSSL_STORE_LOADER_CTX *ctx); +int OSSL_STORE_LOADER_set_error(OSSL_STORE_LOADER *loader, + OSSL_STORE_error_fn error_function); +typedef int (*OSSL_STORE_close_fn)(OSSL_STORE_LOADER_CTX *ctx); +int OSSL_STORE_LOADER_set_close(OSSL_STORE_LOADER *loader, + OSSL_STORE_close_fn close_function); +void OSSL_STORE_LOADER_free(OSSL_STORE_LOADER *loader); + +int OSSL_STORE_register_loader(OSSL_STORE_LOADER *loader); +OSSL_STORE_LOADER *OSSL_STORE_unregister_loader(const char *scheme); + +/*- + * Functions to list STORE loaders + * ------------------------------- + */ +int OSSL_STORE_do_all_loaders(void (*do_function) (const OSSL_STORE_LOADER + *loader, void *do_arg), + void *do_arg); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/storeerr.h b/thrid-party/openssl/openssl/storeerr.h new file mode 100644 index 0000000000..190eab07fb --- /dev/null +++ b/thrid-party/openssl/openssl/storeerr.h @@ -0,0 +1,91 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_OSSL_STOREERR_H +# define HEADER_OSSL_STOREERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_OSSL_STORE_strings(void); + +/* + * OSSL_STORE function codes. + */ +# define OSSL_STORE_F_FILE_CTRL 129 +# define OSSL_STORE_F_FILE_FIND 138 +# define OSSL_STORE_F_FILE_GET_PASS 118 +# define OSSL_STORE_F_FILE_LOAD 119 +# define OSSL_STORE_F_FILE_LOAD_TRY_DECODE 124 +# define OSSL_STORE_F_FILE_NAME_TO_URI 126 +# define OSSL_STORE_F_FILE_OPEN 120 +# define OSSL_STORE_F_OSSL_STORE_ATTACH_PEM_BIO 127 +# define OSSL_STORE_F_OSSL_STORE_EXPECT 130 +# define OSSL_STORE_F_OSSL_STORE_FILE_ATTACH_PEM_BIO_INT 128 +# define OSSL_STORE_F_OSSL_STORE_FIND 131 +# define OSSL_STORE_F_OSSL_STORE_GET0_LOADER_INT 100 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_CERT 101 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_CRL 102 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_NAME 103 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_NAME_DESCRIPTION 135 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_PARAMS 104 +# define OSSL_STORE_F_OSSL_STORE_INFO_GET1_PKEY 105 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_CERT 106 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_CRL 107 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_EMBEDDED 123 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_NAME 109 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_PARAMS 110 +# define OSSL_STORE_F_OSSL_STORE_INFO_NEW_PKEY 111 +# define OSSL_STORE_F_OSSL_STORE_INFO_SET0_NAME_DESCRIPTION 134 +# define OSSL_STORE_F_OSSL_STORE_INIT_ONCE 112 +# define OSSL_STORE_F_OSSL_STORE_LOADER_NEW 113 +# define OSSL_STORE_F_OSSL_STORE_OPEN 114 +# define OSSL_STORE_F_OSSL_STORE_OPEN_INT 115 +# define OSSL_STORE_F_OSSL_STORE_REGISTER_LOADER_INT 117 +# define OSSL_STORE_F_OSSL_STORE_SEARCH_BY_ALIAS 132 +# define OSSL_STORE_F_OSSL_STORE_SEARCH_BY_ISSUER_SERIAL 133 +# define OSSL_STORE_F_OSSL_STORE_SEARCH_BY_KEY_FINGERPRINT 136 +# define OSSL_STORE_F_OSSL_STORE_SEARCH_BY_NAME 137 +# define OSSL_STORE_F_OSSL_STORE_UNREGISTER_LOADER_INT 116 +# define OSSL_STORE_F_TRY_DECODE_PARAMS 121 +# define OSSL_STORE_F_TRY_DECODE_PKCS12 122 +# define OSSL_STORE_F_TRY_DECODE_PKCS8ENCRYPTED 125 + +/* + * OSSL_STORE reason codes. + */ +# define OSSL_STORE_R_AMBIGUOUS_CONTENT_TYPE 107 +# define OSSL_STORE_R_BAD_PASSWORD_READ 115 +# define OSSL_STORE_R_ERROR_VERIFYING_PKCS12_MAC 113 +# define OSSL_STORE_R_FINGERPRINT_SIZE_DOES_NOT_MATCH_DIGEST 121 +# define OSSL_STORE_R_INVALID_SCHEME 106 +# define OSSL_STORE_R_IS_NOT_A 112 +# define OSSL_STORE_R_LOADER_INCOMPLETE 116 +# define OSSL_STORE_R_LOADING_STARTED 117 +# define OSSL_STORE_R_NOT_A_CERTIFICATE 100 +# define OSSL_STORE_R_NOT_A_CRL 101 +# define OSSL_STORE_R_NOT_A_KEY 102 +# define OSSL_STORE_R_NOT_A_NAME 103 +# define OSSL_STORE_R_NOT_PARAMETERS 104 +# define OSSL_STORE_R_PASSPHRASE_CALLBACK_ERROR 114 +# define OSSL_STORE_R_PATH_MUST_BE_ABSOLUTE 108 +# define OSSL_STORE_R_SEARCH_ONLY_SUPPORTED_FOR_DIRECTORIES 119 +# define OSSL_STORE_R_UI_PROCESS_INTERRUPTED_OR_CANCELLED 109 +# define OSSL_STORE_R_UNREGISTERED_SCHEME 105 +# define OSSL_STORE_R_UNSUPPORTED_CONTENT_TYPE 110 +# define OSSL_STORE_R_UNSUPPORTED_OPERATION 118 +# define OSSL_STORE_R_UNSUPPORTED_SEARCH_TYPE 120 +# define OSSL_STORE_R_URI_AUTHORITY_UNSUPPORTED 111 + +#endif diff --git a/thrid-party/openssl/openssl/symhacks.h b/thrid-party/openssl/openssl/symhacks.h new file mode 100644 index 0000000000..156ea6e4ee --- /dev/null +++ b/thrid-party/openssl/openssl/symhacks.h @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_SYMHACKS_H +# define HEADER_SYMHACKS_H + +# include + +/* Case insensitive linking causes problems.... */ +# if defined(OPENSSL_SYS_VMS) +# undef ERR_load_CRYPTO_strings +# define ERR_load_CRYPTO_strings ERR_load_CRYPTOlib_strings +# undef OCSP_crlID_new +# define OCSP_crlID_new OCSP_crlID2_new + +# undef d2i_ECPARAMETERS +# define d2i_ECPARAMETERS d2i_UC_ECPARAMETERS +# undef i2d_ECPARAMETERS +# define i2d_ECPARAMETERS i2d_UC_ECPARAMETERS +# undef d2i_ECPKPARAMETERS +# define d2i_ECPKPARAMETERS d2i_UC_ECPKPARAMETERS +# undef i2d_ECPKPARAMETERS +# define i2d_ECPKPARAMETERS i2d_UC_ECPKPARAMETERS + +/* This one clashes with CMS_data_create */ +# undef cms_Data_create +# define cms_Data_create priv_cms_Data_create + +# endif + +#endif /* ! defined HEADER_VMS_IDHACKS_H */ diff --git a/thrid-party/openssl/openssl/tls1.h b/thrid-party/openssl/openssl/tls1.h new file mode 100644 index 0000000000..76d9fda46e --- /dev/null +++ b/thrid-party/openssl/openssl/tls1.h @@ -0,0 +1,1237 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * Copyright 2005 Nokia. All rights reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_TLS1_H +# define HEADER_TLS1_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Default security level if not overridden at config time */ +# ifndef OPENSSL_TLS_SECURITY_LEVEL +# define OPENSSL_TLS_SECURITY_LEVEL 1 +# endif + +# define TLS1_VERSION 0x0301 +# define TLS1_1_VERSION 0x0302 +# define TLS1_2_VERSION 0x0303 +# define TLS1_3_VERSION 0x0304 +# define TLS_MAX_VERSION TLS1_3_VERSION + +/* Special value for method supporting multiple versions */ +# define TLS_ANY_VERSION 0x10000 + +# define TLS1_VERSION_MAJOR 0x03 +# define TLS1_VERSION_MINOR 0x01 + +# define TLS1_1_VERSION_MAJOR 0x03 +# define TLS1_1_VERSION_MINOR 0x02 + +# define TLS1_2_VERSION_MAJOR 0x03 +# define TLS1_2_VERSION_MINOR 0x03 + +# define TLS1_get_version(s) \ + ((SSL_version(s) >> 8) == TLS1_VERSION_MAJOR ? SSL_version(s) : 0) + +# define TLS1_get_client_version(s) \ + ((SSL_client_version(s) >> 8) == TLS1_VERSION_MAJOR ? SSL_client_version(s) : 0) + +# define TLS1_AD_DECRYPTION_FAILED 21 +# define TLS1_AD_RECORD_OVERFLOW 22 +# define TLS1_AD_UNKNOWN_CA 48/* fatal */ +# define TLS1_AD_ACCESS_DENIED 49/* fatal */ +# define TLS1_AD_DECODE_ERROR 50/* fatal */ +# define TLS1_AD_DECRYPT_ERROR 51 +# define TLS1_AD_EXPORT_RESTRICTION 60/* fatal */ +# define TLS1_AD_PROTOCOL_VERSION 70/* fatal */ +# define TLS1_AD_INSUFFICIENT_SECURITY 71/* fatal */ +# define TLS1_AD_INTERNAL_ERROR 80/* fatal */ +# define TLS1_AD_INAPPROPRIATE_FALLBACK 86/* fatal */ +# define TLS1_AD_USER_CANCELLED 90 +# define TLS1_AD_NO_RENEGOTIATION 100 +/* TLSv1.3 alerts */ +# define TLS13_AD_MISSING_EXTENSION 109 /* fatal */ +# define TLS13_AD_CERTIFICATE_REQUIRED 116 /* fatal */ +/* codes 110-114 are from RFC3546 */ +# define TLS1_AD_UNSUPPORTED_EXTENSION 110 +# define TLS1_AD_CERTIFICATE_UNOBTAINABLE 111 +# define TLS1_AD_UNRECOGNIZED_NAME 112 +# define TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE 113 +# define TLS1_AD_BAD_CERTIFICATE_HASH_VALUE 114 +# define TLS1_AD_UNKNOWN_PSK_IDENTITY 115/* fatal */ +# define TLS1_AD_NO_APPLICATION_PROTOCOL 120 /* fatal */ + +/* ExtensionType values from RFC3546 / RFC4366 / RFC6066 */ +# define TLSEXT_TYPE_server_name 0 +# define TLSEXT_TYPE_max_fragment_length 1 +# define TLSEXT_TYPE_client_certificate_url 2 +# define TLSEXT_TYPE_trusted_ca_keys 3 +# define TLSEXT_TYPE_truncated_hmac 4 +# define TLSEXT_TYPE_status_request 5 +/* ExtensionType values from RFC4681 */ +# define TLSEXT_TYPE_user_mapping 6 +/* ExtensionType values from RFC5878 */ +# define TLSEXT_TYPE_client_authz 7 +# define TLSEXT_TYPE_server_authz 8 +/* ExtensionType values from RFC6091 */ +# define TLSEXT_TYPE_cert_type 9 + +/* ExtensionType values from RFC4492 */ +/* + * Prior to TLSv1.3 the supported_groups extension was known as + * elliptic_curves + */ +# define TLSEXT_TYPE_supported_groups 10 +# define TLSEXT_TYPE_elliptic_curves TLSEXT_TYPE_supported_groups +# define TLSEXT_TYPE_ec_point_formats 11 + + +/* ExtensionType value from RFC5054 */ +# define TLSEXT_TYPE_srp 12 + +/* ExtensionType values from RFC5246 */ +# define TLSEXT_TYPE_signature_algorithms 13 + +/* ExtensionType value from RFC5764 */ +# define TLSEXT_TYPE_use_srtp 14 + +/* ExtensionType value from RFC5620 */ +# define TLSEXT_TYPE_heartbeat 15 + +/* ExtensionType value from RFC7301 */ +# define TLSEXT_TYPE_application_layer_protocol_negotiation 16 + +/* + * Extension type for Certificate Transparency + * https://tools.ietf.org/html/rfc6962#section-3.3.1 + */ +# define TLSEXT_TYPE_signed_certificate_timestamp 18 + +/* + * ExtensionType value for TLS padding extension. + * http://tools.ietf.org/html/draft-agl-tls-padding + */ +# define TLSEXT_TYPE_padding 21 + +/* ExtensionType value from RFC7366 */ +# define TLSEXT_TYPE_encrypt_then_mac 22 + +/* ExtensionType value from RFC7627 */ +# define TLSEXT_TYPE_extended_master_secret 23 + +/* ExtensionType value from RFC4507 */ +# define TLSEXT_TYPE_session_ticket 35 + +/* As defined for TLS1.3 */ +# define TLSEXT_TYPE_psk 41 +# define TLSEXT_TYPE_early_data 42 +# define TLSEXT_TYPE_supported_versions 43 +# define TLSEXT_TYPE_cookie 44 +# define TLSEXT_TYPE_psk_kex_modes 45 +# define TLSEXT_TYPE_certificate_authorities 47 +# define TLSEXT_TYPE_post_handshake_auth 49 +# define TLSEXT_TYPE_signature_algorithms_cert 50 +# define TLSEXT_TYPE_key_share 51 + +/* Temporary extension type */ +# define TLSEXT_TYPE_renegotiate 0xff01 + +# ifndef OPENSSL_NO_NEXTPROTONEG +/* This is not an IANA defined extension number */ +# define TLSEXT_TYPE_next_proto_neg 13172 +# endif + +/* NameType value from RFC3546 */ +# define TLSEXT_NAMETYPE_host_name 0 +/* status request value from RFC3546 */ +# define TLSEXT_STATUSTYPE_ocsp 1 + +/* ECPointFormat values from RFC4492 */ +# define TLSEXT_ECPOINTFORMAT_first 0 +# define TLSEXT_ECPOINTFORMAT_uncompressed 0 +# define TLSEXT_ECPOINTFORMAT_ansiX962_compressed_prime 1 +# define TLSEXT_ECPOINTFORMAT_ansiX962_compressed_char2 2 +# define TLSEXT_ECPOINTFORMAT_last 2 + +/* Signature and hash algorithms from RFC5246 */ +# define TLSEXT_signature_anonymous 0 +# define TLSEXT_signature_rsa 1 +# define TLSEXT_signature_dsa 2 +# define TLSEXT_signature_ecdsa 3 +# define TLSEXT_signature_gostr34102001 237 +# define TLSEXT_signature_gostr34102012_256 238 +# define TLSEXT_signature_gostr34102012_512 239 + +/* Total number of different signature algorithms */ +# define TLSEXT_signature_num 7 + +# define TLSEXT_hash_none 0 +# define TLSEXT_hash_md5 1 +# define TLSEXT_hash_sha1 2 +# define TLSEXT_hash_sha224 3 +# define TLSEXT_hash_sha256 4 +# define TLSEXT_hash_sha384 5 +# define TLSEXT_hash_sha512 6 +# define TLSEXT_hash_gostr3411 237 +# define TLSEXT_hash_gostr34112012_256 238 +# define TLSEXT_hash_gostr34112012_512 239 + +/* Total number of different digest algorithms */ + +# define TLSEXT_hash_num 10 + +/* Flag set for unrecognised algorithms */ +# define TLSEXT_nid_unknown 0x1000000 + +/* ECC curves */ + +# define TLSEXT_curve_P_256 23 +# define TLSEXT_curve_P_384 24 + +/* OpenSSL value to disable maximum fragment length extension */ +# define TLSEXT_max_fragment_length_DISABLED 0 +/* Allowed values for max fragment length extension */ +# define TLSEXT_max_fragment_length_512 1 +# define TLSEXT_max_fragment_length_1024 2 +# define TLSEXT_max_fragment_length_2048 3 +# define TLSEXT_max_fragment_length_4096 4 + +int SSL_CTX_set_tlsext_max_fragment_length(SSL_CTX *ctx, uint8_t mode); +int SSL_set_tlsext_max_fragment_length(SSL *ssl, uint8_t mode); + +# define TLSEXT_MAXLEN_host_name 255 + +__owur const char *SSL_get_servername(const SSL *s, const int type); +__owur int SSL_get_servername_type(const SSL *s); +/* + * SSL_export_keying_material exports a value derived from the master secret, + * as specified in RFC 5705. It writes |olen| bytes to |out| given a label and + * optional context. (Since a zero length context is allowed, the |use_context| + * flag controls whether a context is included.) It returns 1 on success and + * 0 or -1 otherwise. + */ +__owur int SSL_export_keying_material(SSL *s, unsigned char *out, size_t olen, + const char *label, size_t llen, + const unsigned char *context, + size_t contextlen, int use_context); + +/* + * SSL_export_keying_material_early exports a value derived from the + * early exporter master secret, as specified in + * https://tools.ietf.org/html/draft-ietf-tls-tls13-23. It writes + * |olen| bytes to |out| given a label and optional context. It + * returns 1 on success and 0 otherwise. + */ +__owur int SSL_export_keying_material_early(SSL *s, unsigned char *out, + size_t olen, const char *label, + size_t llen, + const unsigned char *context, + size_t contextlen); + +int SSL_get_peer_signature_type_nid(const SSL *s, int *pnid); +int SSL_get_signature_type_nid(const SSL *s, int *pnid); + +int SSL_get_sigalgs(SSL *s, int idx, + int *psign, int *phash, int *psignandhash, + unsigned char *rsig, unsigned char *rhash); + +int SSL_get_shared_sigalgs(SSL *s, int idx, + int *psign, int *phash, int *psignandhash, + unsigned char *rsig, unsigned char *rhash); + +__owur int SSL_check_chain(SSL *s, X509 *x, EVP_PKEY *pk, STACK_OF(X509) *chain); + +# define SSL_set_tlsext_host_name(s,name) \ + SSL_ctrl(s,SSL_CTRL_SET_TLSEXT_HOSTNAME,TLSEXT_NAMETYPE_host_name,\ + (void *)name) + +# define SSL_set_tlsext_debug_callback(ssl, cb) \ + SSL_callback_ctrl(ssl,SSL_CTRL_SET_TLSEXT_DEBUG_CB,\ + (void (*)(void))cb) + +# define SSL_set_tlsext_debug_arg(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_DEBUG_ARG,0,arg) + +# define SSL_get_tlsext_status_type(ssl) \ + SSL_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE,0,NULL) + +# define SSL_set_tlsext_status_type(ssl, type) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE,type,NULL) + +# define SSL_get_tlsext_status_exts(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_EXTS,0,arg) + +# define SSL_set_tlsext_status_exts(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_EXTS,0,arg) + +# define SSL_get_tlsext_status_ids(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_IDS,0,arg) + +# define SSL_set_tlsext_status_ids(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_IDS,0,arg) + +# define SSL_get_tlsext_status_ocsp_resp(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP,0,arg) + +# define SSL_set_tlsext_status_ocsp_resp(ssl, arg, arglen) \ + SSL_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP,arglen,arg) + +# define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \ + SSL_CTX_callback_ctrl(ctx,SSL_CTRL_SET_TLSEXT_SERVERNAME_CB,\ + (void (*)(void))cb) + +# define SSL_TLSEXT_ERR_OK 0 +# define SSL_TLSEXT_ERR_ALERT_WARNING 1 +# define SSL_TLSEXT_ERR_ALERT_FATAL 2 +# define SSL_TLSEXT_ERR_NOACK 3 + +# define SSL_CTX_set_tlsext_servername_arg(ctx, arg) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG,0,arg) + +# define SSL_CTX_get_tlsext_ticket_keys(ctx, keys, keylen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_GET_TLSEXT_TICKET_KEYS,keylen,keys) +# define SSL_CTX_set_tlsext_ticket_keys(ctx, keys, keylen) \ + SSL_CTX_ctrl(ctx,SSL_CTRL_SET_TLSEXT_TICKET_KEYS,keylen,keys) + +# define SSL_CTX_get_tlsext_status_cb(ssl, cb) \ + SSL_CTX_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB,0,(void *)cb) +# define SSL_CTX_set_tlsext_status_cb(ssl, cb) \ + SSL_CTX_callback_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB,\ + (void (*)(void))cb) + +# define SSL_CTX_get_tlsext_status_arg(ssl, arg) \ + SSL_CTX_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB_ARG,0,arg) +# define SSL_CTX_set_tlsext_status_arg(ssl, arg) \ + SSL_CTX_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB_ARG,0,arg) + +# define SSL_CTX_set_tlsext_status_type(ssl, type) \ + SSL_CTX_ctrl(ssl,SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE,type,NULL) + +# define SSL_CTX_get_tlsext_status_type(ssl) \ + SSL_CTX_ctrl(ssl,SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE,0,NULL) + +# define SSL_CTX_set_tlsext_ticket_key_cb(ssl, cb) \ + SSL_CTX_callback_ctrl(ssl,SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB,\ + (void (*)(void))cb) + +# ifndef OPENSSL_NO_HEARTBEATS +# define SSL_DTLSEXT_HB_ENABLED 0x01 +# define SSL_DTLSEXT_HB_DONT_SEND_REQUESTS 0x02 +# define SSL_DTLSEXT_HB_DONT_RECV_REQUESTS 0x04 +# define SSL_get_dtlsext_heartbeat_pending(ssl) \ + SSL_ctrl(ssl,SSL_CTRL_GET_DTLS_EXT_HEARTBEAT_PENDING,0,NULL) +# define SSL_set_dtlsext_heartbeat_no_requests(ssl, arg) \ + SSL_ctrl(ssl,SSL_CTRL_SET_DTLS_EXT_HEARTBEAT_NO_REQUESTS,arg,NULL) + +# if OPENSSL_API_COMPAT < 0x10100000L +# define SSL_CTRL_TLS_EXT_SEND_HEARTBEAT \ + SSL_CTRL_DTLS_EXT_SEND_HEARTBEAT +# define SSL_CTRL_GET_TLS_EXT_HEARTBEAT_PENDING \ + SSL_CTRL_GET_DTLS_EXT_HEARTBEAT_PENDING +# define SSL_CTRL_SET_TLS_EXT_HEARTBEAT_NO_REQUESTS \ + SSL_CTRL_SET_DTLS_EXT_HEARTBEAT_NO_REQUESTS +# define SSL_TLSEXT_HB_ENABLED \ + SSL_DTLSEXT_HB_ENABLED +# define SSL_TLSEXT_HB_DONT_SEND_REQUESTS \ + SSL_DTLSEXT_HB_DONT_SEND_REQUESTS +# define SSL_TLSEXT_HB_DONT_RECV_REQUESTS \ + SSL_DTLSEXT_HB_DONT_RECV_REQUESTS +# define SSL_get_tlsext_heartbeat_pending(ssl) \ + SSL_get_dtlsext_heartbeat_pending(ssl) +# define SSL_set_tlsext_heartbeat_no_requests(ssl, arg) \ + SSL_set_dtlsext_heartbeat_no_requests(ssl,arg) +# endif +# endif + +/* PSK ciphersuites from 4279 */ +# define TLS1_CK_PSK_WITH_RC4_128_SHA 0x0300008A +# define TLS1_CK_PSK_WITH_3DES_EDE_CBC_SHA 0x0300008B +# define TLS1_CK_PSK_WITH_AES_128_CBC_SHA 0x0300008C +# define TLS1_CK_PSK_WITH_AES_256_CBC_SHA 0x0300008D +# define TLS1_CK_DHE_PSK_WITH_RC4_128_SHA 0x0300008E +# define TLS1_CK_DHE_PSK_WITH_3DES_EDE_CBC_SHA 0x0300008F +# define TLS1_CK_DHE_PSK_WITH_AES_128_CBC_SHA 0x03000090 +# define TLS1_CK_DHE_PSK_WITH_AES_256_CBC_SHA 0x03000091 +# define TLS1_CK_RSA_PSK_WITH_RC4_128_SHA 0x03000092 +# define TLS1_CK_RSA_PSK_WITH_3DES_EDE_CBC_SHA 0x03000093 +# define TLS1_CK_RSA_PSK_WITH_AES_128_CBC_SHA 0x03000094 +# define TLS1_CK_RSA_PSK_WITH_AES_256_CBC_SHA 0x03000095 + +/* PSK ciphersuites from 5487 */ +# define TLS1_CK_PSK_WITH_AES_128_GCM_SHA256 0x030000A8 +# define TLS1_CK_PSK_WITH_AES_256_GCM_SHA384 0x030000A9 +# define TLS1_CK_DHE_PSK_WITH_AES_128_GCM_SHA256 0x030000AA +# define TLS1_CK_DHE_PSK_WITH_AES_256_GCM_SHA384 0x030000AB +# define TLS1_CK_RSA_PSK_WITH_AES_128_GCM_SHA256 0x030000AC +# define TLS1_CK_RSA_PSK_WITH_AES_256_GCM_SHA384 0x030000AD +# define TLS1_CK_PSK_WITH_AES_128_CBC_SHA256 0x030000AE +# define TLS1_CK_PSK_WITH_AES_256_CBC_SHA384 0x030000AF +# define TLS1_CK_PSK_WITH_NULL_SHA256 0x030000B0 +# define TLS1_CK_PSK_WITH_NULL_SHA384 0x030000B1 +# define TLS1_CK_DHE_PSK_WITH_AES_128_CBC_SHA256 0x030000B2 +# define TLS1_CK_DHE_PSK_WITH_AES_256_CBC_SHA384 0x030000B3 +# define TLS1_CK_DHE_PSK_WITH_NULL_SHA256 0x030000B4 +# define TLS1_CK_DHE_PSK_WITH_NULL_SHA384 0x030000B5 +# define TLS1_CK_RSA_PSK_WITH_AES_128_CBC_SHA256 0x030000B6 +# define TLS1_CK_RSA_PSK_WITH_AES_256_CBC_SHA384 0x030000B7 +# define TLS1_CK_RSA_PSK_WITH_NULL_SHA256 0x030000B8 +# define TLS1_CK_RSA_PSK_WITH_NULL_SHA384 0x030000B9 + +/* NULL PSK ciphersuites from RFC4785 */ +# define TLS1_CK_PSK_WITH_NULL_SHA 0x0300002C +# define TLS1_CK_DHE_PSK_WITH_NULL_SHA 0x0300002D +# define TLS1_CK_RSA_PSK_WITH_NULL_SHA 0x0300002E + +/* AES ciphersuites from RFC3268 */ +# define TLS1_CK_RSA_WITH_AES_128_SHA 0x0300002F +# define TLS1_CK_DH_DSS_WITH_AES_128_SHA 0x03000030 +# define TLS1_CK_DH_RSA_WITH_AES_128_SHA 0x03000031 +# define TLS1_CK_DHE_DSS_WITH_AES_128_SHA 0x03000032 +# define TLS1_CK_DHE_RSA_WITH_AES_128_SHA 0x03000033 +# define TLS1_CK_ADH_WITH_AES_128_SHA 0x03000034 +# define TLS1_CK_RSA_WITH_AES_256_SHA 0x03000035 +# define TLS1_CK_DH_DSS_WITH_AES_256_SHA 0x03000036 +# define TLS1_CK_DH_RSA_WITH_AES_256_SHA 0x03000037 +# define TLS1_CK_DHE_DSS_WITH_AES_256_SHA 0x03000038 +# define TLS1_CK_DHE_RSA_WITH_AES_256_SHA 0x03000039 +# define TLS1_CK_ADH_WITH_AES_256_SHA 0x0300003A + +/* TLS v1.2 ciphersuites */ +# define TLS1_CK_RSA_WITH_NULL_SHA256 0x0300003B +# define TLS1_CK_RSA_WITH_AES_128_SHA256 0x0300003C +# define TLS1_CK_RSA_WITH_AES_256_SHA256 0x0300003D +# define TLS1_CK_DH_DSS_WITH_AES_128_SHA256 0x0300003E +# define TLS1_CK_DH_RSA_WITH_AES_128_SHA256 0x0300003F +# define TLS1_CK_DHE_DSS_WITH_AES_128_SHA256 0x03000040 + +/* Camellia ciphersuites from RFC4132 */ +# define TLS1_CK_RSA_WITH_CAMELLIA_128_CBC_SHA 0x03000041 +# define TLS1_CK_DH_DSS_WITH_CAMELLIA_128_CBC_SHA 0x03000042 +# define TLS1_CK_DH_RSA_WITH_CAMELLIA_128_CBC_SHA 0x03000043 +# define TLS1_CK_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA 0x03000044 +# define TLS1_CK_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA 0x03000045 +# define TLS1_CK_ADH_WITH_CAMELLIA_128_CBC_SHA 0x03000046 + +/* TLS v1.2 ciphersuites */ +# define TLS1_CK_DHE_RSA_WITH_AES_128_SHA256 0x03000067 +# define TLS1_CK_DH_DSS_WITH_AES_256_SHA256 0x03000068 +# define TLS1_CK_DH_RSA_WITH_AES_256_SHA256 0x03000069 +# define TLS1_CK_DHE_DSS_WITH_AES_256_SHA256 0x0300006A +# define TLS1_CK_DHE_RSA_WITH_AES_256_SHA256 0x0300006B +# define TLS1_CK_ADH_WITH_AES_128_SHA256 0x0300006C +# define TLS1_CK_ADH_WITH_AES_256_SHA256 0x0300006D + +/* Camellia ciphersuites from RFC4132 */ +# define TLS1_CK_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000084 +# define TLS1_CK_DH_DSS_WITH_CAMELLIA_256_CBC_SHA 0x03000085 +# define TLS1_CK_DH_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000086 +# define TLS1_CK_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA 0x03000087 +# define TLS1_CK_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000088 +# define TLS1_CK_ADH_WITH_CAMELLIA_256_CBC_SHA 0x03000089 + +/* SEED ciphersuites from RFC4162 */ +# define TLS1_CK_RSA_WITH_SEED_SHA 0x03000096 +# define TLS1_CK_DH_DSS_WITH_SEED_SHA 0x03000097 +# define TLS1_CK_DH_RSA_WITH_SEED_SHA 0x03000098 +# define TLS1_CK_DHE_DSS_WITH_SEED_SHA 0x03000099 +# define TLS1_CK_DHE_RSA_WITH_SEED_SHA 0x0300009A +# define TLS1_CK_ADH_WITH_SEED_SHA 0x0300009B + +/* TLS v1.2 GCM ciphersuites from RFC5288 */ +# define TLS1_CK_RSA_WITH_AES_128_GCM_SHA256 0x0300009C +# define TLS1_CK_RSA_WITH_AES_256_GCM_SHA384 0x0300009D +# define TLS1_CK_DHE_RSA_WITH_AES_128_GCM_SHA256 0x0300009E +# define TLS1_CK_DHE_RSA_WITH_AES_256_GCM_SHA384 0x0300009F +# define TLS1_CK_DH_RSA_WITH_AES_128_GCM_SHA256 0x030000A0 +# define TLS1_CK_DH_RSA_WITH_AES_256_GCM_SHA384 0x030000A1 +# define TLS1_CK_DHE_DSS_WITH_AES_128_GCM_SHA256 0x030000A2 +# define TLS1_CK_DHE_DSS_WITH_AES_256_GCM_SHA384 0x030000A3 +# define TLS1_CK_DH_DSS_WITH_AES_128_GCM_SHA256 0x030000A4 +# define TLS1_CK_DH_DSS_WITH_AES_256_GCM_SHA384 0x030000A5 +# define TLS1_CK_ADH_WITH_AES_128_GCM_SHA256 0x030000A6 +# define TLS1_CK_ADH_WITH_AES_256_GCM_SHA384 0x030000A7 + +/* CCM ciphersuites from RFC6655 */ +# define TLS1_CK_RSA_WITH_AES_128_CCM 0x0300C09C +# define TLS1_CK_RSA_WITH_AES_256_CCM 0x0300C09D +# define TLS1_CK_DHE_RSA_WITH_AES_128_CCM 0x0300C09E +# define TLS1_CK_DHE_RSA_WITH_AES_256_CCM 0x0300C09F +# define TLS1_CK_RSA_WITH_AES_128_CCM_8 0x0300C0A0 +# define TLS1_CK_RSA_WITH_AES_256_CCM_8 0x0300C0A1 +# define TLS1_CK_DHE_RSA_WITH_AES_128_CCM_8 0x0300C0A2 +# define TLS1_CK_DHE_RSA_WITH_AES_256_CCM_8 0x0300C0A3 +# define TLS1_CK_PSK_WITH_AES_128_CCM 0x0300C0A4 +# define TLS1_CK_PSK_WITH_AES_256_CCM 0x0300C0A5 +# define TLS1_CK_DHE_PSK_WITH_AES_128_CCM 0x0300C0A6 +# define TLS1_CK_DHE_PSK_WITH_AES_256_CCM 0x0300C0A7 +# define TLS1_CK_PSK_WITH_AES_128_CCM_8 0x0300C0A8 +# define TLS1_CK_PSK_WITH_AES_256_CCM_8 0x0300C0A9 +# define TLS1_CK_DHE_PSK_WITH_AES_128_CCM_8 0x0300C0AA +# define TLS1_CK_DHE_PSK_WITH_AES_256_CCM_8 0x0300C0AB + +/* CCM ciphersuites from RFC7251 */ +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_CCM 0x0300C0AC +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_CCM 0x0300C0AD +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_CCM_8 0x0300C0AE +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_CCM_8 0x0300C0AF + +/* TLS 1.2 Camellia SHA-256 ciphersuites from RFC5932 */ +# define TLS1_CK_RSA_WITH_CAMELLIA_128_CBC_SHA256 0x030000BA +# define TLS1_CK_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 0x030000BB +# define TLS1_CK_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 0x030000BC +# define TLS1_CK_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 0x030000BD +# define TLS1_CK_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 0x030000BE +# define TLS1_CK_ADH_WITH_CAMELLIA_128_CBC_SHA256 0x030000BF + +# define TLS1_CK_RSA_WITH_CAMELLIA_256_CBC_SHA256 0x030000C0 +# define TLS1_CK_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 0x030000C1 +# define TLS1_CK_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 0x030000C2 +# define TLS1_CK_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 0x030000C3 +# define TLS1_CK_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 0x030000C4 +# define TLS1_CK_ADH_WITH_CAMELLIA_256_CBC_SHA256 0x030000C5 + +/* ECC ciphersuites from RFC4492 */ +# define TLS1_CK_ECDH_ECDSA_WITH_NULL_SHA 0x0300C001 +# define TLS1_CK_ECDH_ECDSA_WITH_RC4_128_SHA 0x0300C002 +# define TLS1_CK_ECDH_ECDSA_WITH_DES_192_CBC3_SHA 0x0300C003 +# define TLS1_CK_ECDH_ECDSA_WITH_AES_128_CBC_SHA 0x0300C004 +# define TLS1_CK_ECDH_ECDSA_WITH_AES_256_CBC_SHA 0x0300C005 + +# define TLS1_CK_ECDHE_ECDSA_WITH_NULL_SHA 0x0300C006 +# define TLS1_CK_ECDHE_ECDSA_WITH_RC4_128_SHA 0x0300C007 +# define TLS1_CK_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA 0x0300C008 +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0x0300C009 +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0x0300C00A + +# define TLS1_CK_ECDH_RSA_WITH_NULL_SHA 0x0300C00B +# define TLS1_CK_ECDH_RSA_WITH_RC4_128_SHA 0x0300C00C +# define TLS1_CK_ECDH_RSA_WITH_DES_192_CBC3_SHA 0x0300C00D +# define TLS1_CK_ECDH_RSA_WITH_AES_128_CBC_SHA 0x0300C00E +# define TLS1_CK_ECDH_RSA_WITH_AES_256_CBC_SHA 0x0300C00F + +# define TLS1_CK_ECDHE_RSA_WITH_NULL_SHA 0x0300C010 +# define TLS1_CK_ECDHE_RSA_WITH_RC4_128_SHA 0x0300C011 +# define TLS1_CK_ECDHE_RSA_WITH_DES_192_CBC3_SHA 0x0300C012 +# define TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA 0x0300C013 +# define TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA 0x0300C014 + +# define TLS1_CK_ECDH_anon_WITH_NULL_SHA 0x0300C015 +# define TLS1_CK_ECDH_anon_WITH_RC4_128_SHA 0x0300C016 +# define TLS1_CK_ECDH_anon_WITH_DES_192_CBC3_SHA 0x0300C017 +# define TLS1_CK_ECDH_anon_WITH_AES_128_CBC_SHA 0x0300C018 +# define TLS1_CK_ECDH_anon_WITH_AES_256_CBC_SHA 0x0300C019 + +/* SRP ciphersuites from RFC 5054 */ +# define TLS1_CK_SRP_SHA_WITH_3DES_EDE_CBC_SHA 0x0300C01A +# define TLS1_CK_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA 0x0300C01B +# define TLS1_CK_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA 0x0300C01C +# define TLS1_CK_SRP_SHA_WITH_AES_128_CBC_SHA 0x0300C01D +# define TLS1_CK_SRP_SHA_RSA_WITH_AES_128_CBC_SHA 0x0300C01E +# define TLS1_CK_SRP_SHA_DSS_WITH_AES_128_CBC_SHA 0x0300C01F +# define TLS1_CK_SRP_SHA_WITH_AES_256_CBC_SHA 0x0300C020 +# define TLS1_CK_SRP_SHA_RSA_WITH_AES_256_CBC_SHA 0x0300C021 +# define TLS1_CK_SRP_SHA_DSS_WITH_AES_256_CBC_SHA 0x0300C022 + +/* ECDH HMAC based ciphersuites from RFC5289 */ +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_SHA256 0x0300C023 +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_SHA384 0x0300C024 +# define TLS1_CK_ECDH_ECDSA_WITH_AES_128_SHA256 0x0300C025 +# define TLS1_CK_ECDH_ECDSA_WITH_AES_256_SHA384 0x0300C026 +# define TLS1_CK_ECDHE_RSA_WITH_AES_128_SHA256 0x0300C027 +# define TLS1_CK_ECDHE_RSA_WITH_AES_256_SHA384 0x0300C028 +# define TLS1_CK_ECDH_RSA_WITH_AES_128_SHA256 0x0300C029 +# define TLS1_CK_ECDH_RSA_WITH_AES_256_SHA384 0x0300C02A + +/* ECDH GCM based ciphersuites from RFC5289 */ +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0x0300C02B +# define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0x0300C02C +# define TLS1_CK_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0x0300C02D +# define TLS1_CK_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 0x0300C02E +# define TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0x0300C02F +# define TLS1_CK_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0x0300C030 +# define TLS1_CK_ECDH_RSA_WITH_AES_128_GCM_SHA256 0x0300C031 +# define TLS1_CK_ECDH_RSA_WITH_AES_256_GCM_SHA384 0x0300C032 + +/* ECDHE PSK ciphersuites from RFC5489 */ +# define TLS1_CK_ECDHE_PSK_WITH_RC4_128_SHA 0x0300C033 +# define TLS1_CK_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA 0x0300C034 +# define TLS1_CK_ECDHE_PSK_WITH_AES_128_CBC_SHA 0x0300C035 +# define TLS1_CK_ECDHE_PSK_WITH_AES_256_CBC_SHA 0x0300C036 + +# define TLS1_CK_ECDHE_PSK_WITH_AES_128_CBC_SHA256 0x0300C037 +# define TLS1_CK_ECDHE_PSK_WITH_AES_256_CBC_SHA384 0x0300C038 + +/* NULL PSK ciphersuites from RFC4785 */ +# define TLS1_CK_ECDHE_PSK_WITH_NULL_SHA 0x0300C039 +# define TLS1_CK_ECDHE_PSK_WITH_NULL_SHA256 0x0300C03A +# define TLS1_CK_ECDHE_PSK_WITH_NULL_SHA384 0x0300C03B + +/* Camellia-CBC ciphersuites from RFC6367 */ +# define TLS1_CK_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0x0300C072 +# define TLS1_CK_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0x0300C073 +# define TLS1_CK_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0x0300C074 +# define TLS1_CK_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0x0300C075 +# define TLS1_CK_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 0x0300C076 +# define TLS1_CK_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 0x0300C077 +# define TLS1_CK_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 0x0300C078 +# define TLS1_CK_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 0x0300C079 + +# define TLS1_CK_PSK_WITH_CAMELLIA_128_CBC_SHA256 0x0300C094 +# define TLS1_CK_PSK_WITH_CAMELLIA_256_CBC_SHA384 0x0300C095 +# define TLS1_CK_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 0x0300C096 +# define TLS1_CK_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 0x0300C097 +# define TLS1_CK_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 0x0300C098 +# define TLS1_CK_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 0x0300C099 +# define TLS1_CK_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 0x0300C09A +# define TLS1_CK_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 0x0300C09B + +/* draft-ietf-tls-chacha20-poly1305-03 */ +# define TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305 0x0300CCA8 +# define TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 0x0300CCA9 +# define TLS1_CK_DHE_RSA_WITH_CHACHA20_POLY1305 0x0300CCAA +# define TLS1_CK_PSK_WITH_CHACHA20_POLY1305 0x0300CCAB +# define TLS1_CK_ECDHE_PSK_WITH_CHACHA20_POLY1305 0x0300CCAC +# define TLS1_CK_DHE_PSK_WITH_CHACHA20_POLY1305 0x0300CCAD +# define TLS1_CK_RSA_PSK_WITH_CHACHA20_POLY1305 0x0300CCAE + +/* TLS v1.3 ciphersuites */ +# define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301 +# define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302 +# define TLS1_3_CK_CHACHA20_POLY1305_SHA256 0x03001303 +# define TLS1_3_CK_AES_128_CCM_SHA256 0x03001304 +# define TLS1_3_CK_AES_128_CCM_8_SHA256 0x03001305 + +/* Aria ciphersuites from RFC6209 */ +# define TLS1_CK_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C050 +# define TLS1_CK_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C051 +# define TLS1_CK_DHE_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C052 +# define TLS1_CK_DHE_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C053 +# define TLS1_CK_DH_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C054 +# define TLS1_CK_DH_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C055 +# define TLS1_CK_DHE_DSS_WITH_ARIA_128_GCM_SHA256 0x0300C056 +# define TLS1_CK_DHE_DSS_WITH_ARIA_256_GCM_SHA384 0x0300C057 +# define TLS1_CK_DH_DSS_WITH_ARIA_128_GCM_SHA256 0x0300C058 +# define TLS1_CK_DH_DSS_WITH_ARIA_256_GCM_SHA384 0x0300C059 +# define TLS1_CK_DH_anon_WITH_ARIA_128_GCM_SHA256 0x0300C05A +# define TLS1_CK_DH_anon_WITH_ARIA_256_GCM_SHA384 0x0300C05B +# define TLS1_CK_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 0x0300C05C +# define TLS1_CK_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 0x0300C05D +# define TLS1_CK_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 0x0300C05E +# define TLS1_CK_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 0x0300C05F +# define TLS1_CK_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C060 +# define TLS1_CK_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C061 +# define TLS1_CK_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 0x0300C062 +# define TLS1_CK_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 0x0300C063 +# define TLS1_CK_PSK_WITH_ARIA_128_GCM_SHA256 0x0300C06A +# define TLS1_CK_PSK_WITH_ARIA_256_GCM_SHA384 0x0300C06B +# define TLS1_CK_DHE_PSK_WITH_ARIA_128_GCM_SHA256 0x0300C06C +# define TLS1_CK_DHE_PSK_WITH_ARIA_256_GCM_SHA384 0x0300C06D +# define TLS1_CK_RSA_PSK_WITH_ARIA_128_GCM_SHA256 0x0300C06E +# define TLS1_CK_RSA_PSK_WITH_ARIA_256_GCM_SHA384 0x0300C06F + +/* a bundle of RFC standard cipher names, generated from ssl3_ciphers[] */ +# define TLS1_RFC_RSA_WITH_AES_128_SHA "TLS_RSA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_DHE_DSS_WITH_AES_128_SHA "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_AES_128_SHA "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_ADH_WITH_AES_128_SHA "TLS_DH_anon_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_RSA_WITH_AES_256_SHA "TLS_RSA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_DHE_DSS_WITH_AES_256_SHA "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_AES_256_SHA "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_ADH_WITH_AES_256_SHA "TLS_DH_anon_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_RSA_WITH_NULL_SHA256 "TLS_RSA_WITH_NULL_SHA256" +# define TLS1_RFC_RSA_WITH_AES_128_SHA256 "TLS_RSA_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_RSA_WITH_AES_256_SHA256 "TLS_RSA_WITH_AES_256_CBC_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_AES_128_SHA256 "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_AES_128_SHA256 "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_AES_256_SHA256 "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_AES_256_SHA256 "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" +# define TLS1_RFC_ADH_WITH_AES_128_SHA256 "TLS_DH_anon_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_ADH_WITH_AES_256_SHA256 "TLS_DH_anon_WITH_AES_256_CBC_SHA256" +# define TLS1_RFC_RSA_WITH_AES_128_GCM_SHA256 "TLS_RSA_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_RSA_WITH_AES_256_GCM_SHA384 "TLS_RSA_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_DHE_RSA_WITH_AES_128_GCM_SHA256 "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_AES_256_GCM_SHA384 "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_DHE_DSS_WITH_AES_128_GCM_SHA256 "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_AES_256_GCM_SHA384 "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_ADH_WITH_AES_128_GCM_SHA256 "TLS_DH_anon_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_ADH_WITH_AES_256_GCM_SHA384 "TLS_DH_anon_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_RSA_WITH_AES_128_CCM "TLS_RSA_WITH_AES_128_CCM" +# define TLS1_RFC_RSA_WITH_AES_256_CCM "TLS_RSA_WITH_AES_256_CCM" +# define TLS1_RFC_DHE_RSA_WITH_AES_128_CCM "TLS_DHE_RSA_WITH_AES_128_CCM" +# define TLS1_RFC_DHE_RSA_WITH_AES_256_CCM "TLS_DHE_RSA_WITH_AES_256_CCM" +# define TLS1_RFC_RSA_WITH_AES_128_CCM_8 "TLS_RSA_WITH_AES_128_CCM_8" +# define TLS1_RFC_RSA_WITH_AES_256_CCM_8 "TLS_RSA_WITH_AES_256_CCM_8" +# define TLS1_RFC_DHE_RSA_WITH_AES_128_CCM_8 "TLS_DHE_RSA_WITH_AES_128_CCM_8" +# define TLS1_RFC_DHE_RSA_WITH_AES_256_CCM_8 "TLS_DHE_RSA_WITH_AES_256_CCM_8" +# define TLS1_RFC_PSK_WITH_AES_128_CCM "TLS_PSK_WITH_AES_128_CCM" +# define TLS1_RFC_PSK_WITH_AES_256_CCM "TLS_PSK_WITH_AES_256_CCM" +# define TLS1_RFC_DHE_PSK_WITH_AES_128_CCM "TLS_DHE_PSK_WITH_AES_128_CCM" +# define TLS1_RFC_DHE_PSK_WITH_AES_256_CCM "TLS_DHE_PSK_WITH_AES_256_CCM" +# define TLS1_RFC_PSK_WITH_AES_128_CCM_8 "TLS_PSK_WITH_AES_128_CCM_8" +# define TLS1_RFC_PSK_WITH_AES_256_CCM_8 "TLS_PSK_WITH_AES_256_CCM_8" +# define TLS1_RFC_DHE_PSK_WITH_AES_128_CCM_8 "TLS_PSK_DHE_WITH_AES_128_CCM_8" +# define TLS1_RFC_DHE_PSK_WITH_AES_256_CCM_8 "TLS_PSK_DHE_WITH_AES_256_CCM_8" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_128_CCM "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_256_CCM "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_128_CCM_8 "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_256_CCM_8 "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8" +# define TLS1_3_RFC_AES_128_GCM_SHA256 "TLS_AES_128_GCM_SHA256" +# define TLS1_3_RFC_AES_256_GCM_SHA384 "TLS_AES_256_GCM_SHA384" +# define TLS1_3_RFC_CHACHA20_POLY1305_SHA256 "TLS_CHACHA20_POLY1305_SHA256" +# define TLS1_3_RFC_AES_128_CCM_SHA256 "TLS_AES_128_CCM_SHA256" +# define TLS1_3_RFC_AES_128_CCM_8_SHA256 "TLS_AES_128_CCM_8_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_NULL_SHA "TLS_ECDHE_ECDSA_WITH_NULL_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_128_CBC_SHA "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_256_CBC_SHA "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_ECDHE_RSA_WITH_NULL_SHA "TLS_ECDHE_RSA_WITH_NULL_SHA" +# define TLS1_RFC_ECDHE_RSA_WITH_DES_192_CBC3_SHA "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_128_CBC_SHA "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_256_CBC_SHA "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_ECDH_anon_WITH_NULL_SHA "TLS_ECDH_anon_WITH_NULL_SHA" +# define TLS1_RFC_ECDH_anon_WITH_DES_192_CBC3_SHA "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_ECDH_anon_WITH_AES_128_CBC_SHA "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_ECDH_anon_WITH_AES_256_CBC_SHA "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_128_SHA256 "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_256_SHA384 "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_128_SHA256 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_256_SHA384 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_128_GCM_SHA256 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_AES_256_GCM_SHA384 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_PSK_WITH_NULL_SHA "TLS_PSK_WITH_NULL_SHA" +# define TLS1_RFC_DHE_PSK_WITH_NULL_SHA "TLS_DHE_PSK_WITH_NULL_SHA" +# define TLS1_RFC_RSA_PSK_WITH_NULL_SHA "TLS_RSA_PSK_WITH_NULL_SHA" +# define TLS1_RFC_PSK_WITH_3DES_EDE_CBC_SHA "TLS_PSK_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_PSK_WITH_AES_128_CBC_SHA "TLS_PSK_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_PSK_WITH_AES_256_CBC_SHA "TLS_PSK_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_DHE_PSK_WITH_3DES_EDE_CBC_SHA "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_DHE_PSK_WITH_AES_128_CBC_SHA "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_DHE_PSK_WITH_AES_256_CBC_SHA "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_RSA_PSK_WITH_3DES_EDE_CBC_SHA "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_RSA_PSK_WITH_AES_128_CBC_SHA "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_RSA_PSK_WITH_AES_256_CBC_SHA "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_PSK_WITH_AES_128_GCM_SHA256 "TLS_PSK_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_PSK_WITH_AES_256_GCM_SHA384 "TLS_PSK_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_DHE_PSK_WITH_AES_128_GCM_SHA256 "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_AES_256_GCM_SHA384 "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_RSA_PSK_WITH_AES_128_GCM_SHA256 "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_AES_256_GCM_SHA384 "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" +# define TLS1_RFC_PSK_WITH_AES_128_CBC_SHA256 "TLS_PSK_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_PSK_WITH_AES_256_CBC_SHA384 "TLS_PSK_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_PSK_WITH_NULL_SHA256 "TLS_PSK_WITH_NULL_SHA256" +# define TLS1_RFC_PSK_WITH_NULL_SHA384 "TLS_PSK_WITH_NULL_SHA384" +# define TLS1_RFC_DHE_PSK_WITH_AES_128_CBC_SHA256 "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_AES_256_CBC_SHA384 "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_DHE_PSK_WITH_NULL_SHA256 "TLS_DHE_PSK_WITH_NULL_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_NULL_SHA384 "TLS_DHE_PSK_WITH_NULL_SHA384" +# define TLS1_RFC_RSA_PSK_WITH_AES_128_CBC_SHA256 "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_AES_256_CBC_SHA384 "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_RSA_PSK_WITH_NULL_SHA256 "TLS_RSA_PSK_WITH_NULL_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_NULL_SHA384 "TLS_RSA_PSK_WITH_NULL_SHA384" +# define TLS1_RFC_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_ECDHE_PSK_WITH_AES_128_CBC_SHA "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_ECDHE_PSK_WITH_AES_256_CBC_SHA "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_ECDHE_PSK_WITH_AES_128_CBC_SHA256 "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_PSK_WITH_AES_256_CBC_SHA384 "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" +# define TLS1_RFC_ECDHE_PSK_WITH_NULL_SHA "TLS_ECDHE_PSK_WITH_NULL_SHA" +# define TLS1_RFC_ECDHE_PSK_WITH_NULL_SHA256 "TLS_ECDHE_PSK_WITH_NULL_SHA256" +# define TLS1_RFC_ECDHE_PSK_WITH_NULL_SHA384 "TLS_ECDHE_PSK_WITH_NULL_SHA384" +# define TLS1_RFC_SRP_SHA_WITH_3DES_EDE_CBC_SHA "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" +# define TLS1_RFC_SRP_SHA_WITH_AES_128_CBC_SHA "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_SRP_SHA_RSA_WITH_AES_128_CBC_SHA "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_SRP_SHA_DSS_WITH_AES_128_CBC_SHA "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" +# define TLS1_RFC_SRP_SHA_WITH_AES_256_CBC_SHA "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_SRP_SHA_RSA_WITH_AES_256_CBC_SHA "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_SRP_SHA_DSS_WITH_AES_256_CBC_SHA "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_CHACHA20_POLY1305 "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_CHACHA20_POLY1305 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_PSK_WITH_CHACHA20_POLY1305 "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_ECDHE_PSK_WITH_CHACHA20_POLY1305 "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_CHACHA20_POLY1305 "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_CHACHA20_POLY1305 "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" +# define TLS1_RFC_RSA_WITH_CAMELLIA_128_CBC_SHA256 "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_ADH_WITH_CAMELLIA_128_CBC_SHA256 "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_RSA_WITH_CAMELLIA_256_CBC_SHA256 "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" +# define TLS1_RFC_ADH_WITH_CAMELLIA_256_CBC_SHA256 "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" +# define TLS1_RFC_RSA_WITH_CAMELLIA_256_CBC_SHA "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" +# define TLS1_RFC_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" +# define TLS1_RFC_ADH_WITH_CAMELLIA_256_CBC_SHA "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" +# define TLS1_RFC_RSA_WITH_CAMELLIA_128_CBC_SHA "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" +# define TLS1_RFC_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" +# define TLS1_RFC_ADH_WITH_CAMELLIA_128_CBC_SHA "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_PSK_WITH_CAMELLIA_128_CBC_SHA256 "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_PSK_WITH_CAMELLIA_256_CBC_SHA384 "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" +# define TLS1_RFC_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" +# define TLS1_RFC_RSA_WITH_SEED_SHA "TLS_RSA_WITH_SEED_CBC_SHA" +# define TLS1_RFC_DHE_DSS_WITH_SEED_SHA "TLS_DHE_DSS_WITH_SEED_CBC_SHA" +# define TLS1_RFC_DHE_RSA_WITH_SEED_SHA "TLS_DHE_RSA_WITH_SEED_CBC_SHA" +# define TLS1_RFC_ADH_WITH_SEED_SHA "TLS_DH_anon_WITH_SEED_CBC_SHA" +# define TLS1_RFC_ECDHE_PSK_WITH_RC4_128_SHA "TLS_ECDHE_PSK_WITH_RC4_128_SHA" +# define TLS1_RFC_ECDH_anon_WITH_RC4_128_SHA "TLS_ECDH_anon_WITH_RC4_128_SHA" +# define TLS1_RFC_ECDHE_ECDSA_WITH_RC4_128_SHA "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" +# define TLS1_RFC_ECDHE_RSA_WITH_RC4_128_SHA "TLS_ECDHE_RSA_WITH_RC4_128_SHA" +# define TLS1_RFC_PSK_WITH_RC4_128_SHA "TLS_PSK_WITH_RC4_128_SHA" +# define TLS1_RFC_RSA_PSK_WITH_RC4_128_SHA "TLS_RSA_PSK_WITH_RC4_128_SHA" +# define TLS1_RFC_DHE_PSK_WITH_RC4_128_SHA "TLS_DHE_PSK_WITH_RC4_128_SHA" +# define TLS1_RFC_RSA_WITH_ARIA_128_GCM_SHA256 "TLS_RSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_RSA_WITH_ARIA_256_GCM_SHA384 "TLS_RSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DHE_RSA_WITH_ARIA_128_GCM_SHA256 "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DHE_RSA_WITH_ARIA_256_GCM_SHA384 "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DH_RSA_WITH_ARIA_128_GCM_SHA256 "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DH_RSA_WITH_ARIA_256_GCM_SHA384 "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DHE_DSS_WITH_ARIA_128_GCM_SHA256 "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DHE_DSS_WITH_ARIA_256_GCM_SHA384 "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DH_DSS_WITH_ARIA_128_GCM_SHA256 "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DH_DSS_WITH_ARIA_256_GCM_SHA384 "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DH_anon_WITH_ARIA_128_GCM_SHA256 "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DH_anon_WITH_ARIA_256_GCM_SHA384 "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_PSK_WITH_ARIA_128_GCM_SHA256 "TLS_PSK_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_PSK_WITH_ARIA_256_GCM_SHA384 "TLS_PSK_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_DHE_PSK_WITH_ARIA_128_GCM_SHA256 "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_DHE_PSK_WITH_ARIA_256_GCM_SHA384 "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384" +# define TLS1_RFC_RSA_PSK_WITH_ARIA_128_GCM_SHA256 "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256" +# define TLS1_RFC_RSA_PSK_WITH_ARIA_256_GCM_SHA384 "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384" + + +/* + * XXX Backward compatibility alert: Older versions of OpenSSL gave some DHE + * ciphers names with "EDH" instead of "DHE". Going forward, we should be + * using DHE everywhere, though we may indefinitely maintain aliases for + * users or configurations that used "EDH" + */ +# define TLS1_TXT_DHE_DSS_WITH_RC4_128_SHA "DHE-DSS-RC4-SHA" + +# define TLS1_TXT_PSK_WITH_NULL_SHA "PSK-NULL-SHA" +# define TLS1_TXT_DHE_PSK_WITH_NULL_SHA "DHE-PSK-NULL-SHA" +# define TLS1_TXT_RSA_PSK_WITH_NULL_SHA "RSA-PSK-NULL-SHA" + +/* AES ciphersuites from RFC3268 */ +# define TLS1_TXT_RSA_WITH_AES_128_SHA "AES128-SHA" +# define TLS1_TXT_DH_DSS_WITH_AES_128_SHA "DH-DSS-AES128-SHA" +# define TLS1_TXT_DH_RSA_WITH_AES_128_SHA "DH-RSA-AES128-SHA" +# define TLS1_TXT_DHE_DSS_WITH_AES_128_SHA "DHE-DSS-AES128-SHA" +# define TLS1_TXT_DHE_RSA_WITH_AES_128_SHA "DHE-RSA-AES128-SHA" +# define TLS1_TXT_ADH_WITH_AES_128_SHA "ADH-AES128-SHA" + +# define TLS1_TXT_RSA_WITH_AES_256_SHA "AES256-SHA" +# define TLS1_TXT_DH_DSS_WITH_AES_256_SHA "DH-DSS-AES256-SHA" +# define TLS1_TXT_DH_RSA_WITH_AES_256_SHA "DH-RSA-AES256-SHA" +# define TLS1_TXT_DHE_DSS_WITH_AES_256_SHA "DHE-DSS-AES256-SHA" +# define TLS1_TXT_DHE_RSA_WITH_AES_256_SHA "DHE-RSA-AES256-SHA" +# define TLS1_TXT_ADH_WITH_AES_256_SHA "ADH-AES256-SHA" + +/* ECC ciphersuites from RFC4492 */ +# define TLS1_TXT_ECDH_ECDSA_WITH_NULL_SHA "ECDH-ECDSA-NULL-SHA" +# define TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA "ECDH-ECDSA-RC4-SHA" +# define TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA "ECDH-ECDSA-DES-CBC3-SHA" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA "ECDH-ECDSA-AES128-SHA" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA "ECDH-ECDSA-AES256-SHA" + +# define TLS1_TXT_ECDHE_ECDSA_WITH_NULL_SHA "ECDHE-ECDSA-NULL-SHA" +# define TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA "ECDHE-ECDSA-RC4-SHA" +# define TLS1_TXT_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA "ECDHE-ECDSA-DES-CBC3-SHA" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA "ECDHE-ECDSA-AES128-SHA" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA "ECDHE-ECDSA-AES256-SHA" + +# define TLS1_TXT_ECDH_RSA_WITH_NULL_SHA "ECDH-RSA-NULL-SHA" +# define TLS1_TXT_ECDH_RSA_WITH_RC4_128_SHA "ECDH-RSA-RC4-SHA" +# define TLS1_TXT_ECDH_RSA_WITH_DES_192_CBC3_SHA "ECDH-RSA-DES-CBC3-SHA" +# define TLS1_TXT_ECDH_RSA_WITH_AES_128_CBC_SHA "ECDH-RSA-AES128-SHA" +# define TLS1_TXT_ECDH_RSA_WITH_AES_256_CBC_SHA "ECDH-RSA-AES256-SHA" + +# define TLS1_TXT_ECDHE_RSA_WITH_NULL_SHA "ECDHE-RSA-NULL-SHA" +# define TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA "ECDHE-RSA-RC4-SHA" +# define TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA "ECDHE-RSA-DES-CBC3-SHA" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA "ECDHE-RSA-AES128-SHA" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA "ECDHE-RSA-AES256-SHA" + +# define TLS1_TXT_ECDH_anon_WITH_NULL_SHA "AECDH-NULL-SHA" +# define TLS1_TXT_ECDH_anon_WITH_RC4_128_SHA "AECDH-RC4-SHA" +# define TLS1_TXT_ECDH_anon_WITH_DES_192_CBC3_SHA "AECDH-DES-CBC3-SHA" +# define TLS1_TXT_ECDH_anon_WITH_AES_128_CBC_SHA "AECDH-AES128-SHA" +# define TLS1_TXT_ECDH_anon_WITH_AES_256_CBC_SHA "AECDH-AES256-SHA" + +/* PSK ciphersuites from RFC 4279 */ +# define TLS1_TXT_PSK_WITH_RC4_128_SHA "PSK-RC4-SHA" +# define TLS1_TXT_PSK_WITH_3DES_EDE_CBC_SHA "PSK-3DES-EDE-CBC-SHA" +# define TLS1_TXT_PSK_WITH_AES_128_CBC_SHA "PSK-AES128-CBC-SHA" +# define TLS1_TXT_PSK_WITH_AES_256_CBC_SHA "PSK-AES256-CBC-SHA" + +# define TLS1_TXT_DHE_PSK_WITH_RC4_128_SHA "DHE-PSK-RC4-SHA" +# define TLS1_TXT_DHE_PSK_WITH_3DES_EDE_CBC_SHA "DHE-PSK-3DES-EDE-CBC-SHA" +# define TLS1_TXT_DHE_PSK_WITH_AES_128_CBC_SHA "DHE-PSK-AES128-CBC-SHA" +# define TLS1_TXT_DHE_PSK_WITH_AES_256_CBC_SHA "DHE-PSK-AES256-CBC-SHA" +# define TLS1_TXT_RSA_PSK_WITH_RC4_128_SHA "RSA-PSK-RC4-SHA" +# define TLS1_TXT_RSA_PSK_WITH_3DES_EDE_CBC_SHA "RSA-PSK-3DES-EDE-CBC-SHA" +# define TLS1_TXT_RSA_PSK_WITH_AES_128_CBC_SHA "RSA-PSK-AES128-CBC-SHA" +# define TLS1_TXT_RSA_PSK_WITH_AES_256_CBC_SHA "RSA-PSK-AES256-CBC-SHA" + +/* PSK ciphersuites from RFC 5487 */ +# define TLS1_TXT_PSK_WITH_AES_128_GCM_SHA256 "PSK-AES128-GCM-SHA256" +# define TLS1_TXT_PSK_WITH_AES_256_GCM_SHA384 "PSK-AES256-GCM-SHA384" +# define TLS1_TXT_DHE_PSK_WITH_AES_128_GCM_SHA256 "DHE-PSK-AES128-GCM-SHA256" +# define TLS1_TXT_DHE_PSK_WITH_AES_256_GCM_SHA384 "DHE-PSK-AES256-GCM-SHA384" +# define TLS1_TXT_RSA_PSK_WITH_AES_128_GCM_SHA256 "RSA-PSK-AES128-GCM-SHA256" +# define TLS1_TXT_RSA_PSK_WITH_AES_256_GCM_SHA384 "RSA-PSK-AES256-GCM-SHA384" + +# define TLS1_TXT_PSK_WITH_AES_128_CBC_SHA256 "PSK-AES128-CBC-SHA256" +# define TLS1_TXT_PSK_WITH_AES_256_CBC_SHA384 "PSK-AES256-CBC-SHA384" +# define TLS1_TXT_PSK_WITH_NULL_SHA256 "PSK-NULL-SHA256" +# define TLS1_TXT_PSK_WITH_NULL_SHA384 "PSK-NULL-SHA384" + +# define TLS1_TXT_DHE_PSK_WITH_AES_128_CBC_SHA256 "DHE-PSK-AES128-CBC-SHA256" +# define TLS1_TXT_DHE_PSK_WITH_AES_256_CBC_SHA384 "DHE-PSK-AES256-CBC-SHA384" +# define TLS1_TXT_DHE_PSK_WITH_NULL_SHA256 "DHE-PSK-NULL-SHA256" +# define TLS1_TXT_DHE_PSK_WITH_NULL_SHA384 "DHE-PSK-NULL-SHA384" + +# define TLS1_TXT_RSA_PSK_WITH_AES_128_CBC_SHA256 "RSA-PSK-AES128-CBC-SHA256" +# define TLS1_TXT_RSA_PSK_WITH_AES_256_CBC_SHA384 "RSA-PSK-AES256-CBC-SHA384" +# define TLS1_TXT_RSA_PSK_WITH_NULL_SHA256 "RSA-PSK-NULL-SHA256" +# define TLS1_TXT_RSA_PSK_WITH_NULL_SHA384 "RSA-PSK-NULL-SHA384" + +/* SRP ciphersuite from RFC 5054 */ +# define TLS1_TXT_SRP_SHA_WITH_3DES_EDE_CBC_SHA "SRP-3DES-EDE-CBC-SHA" +# define TLS1_TXT_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA "SRP-RSA-3DES-EDE-CBC-SHA" +# define TLS1_TXT_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA "SRP-DSS-3DES-EDE-CBC-SHA" +# define TLS1_TXT_SRP_SHA_WITH_AES_128_CBC_SHA "SRP-AES-128-CBC-SHA" +# define TLS1_TXT_SRP_SHA_RSA_WITH_AES_128_CBC_SHA "SRP-RSA-AES-128-CBC-SHA" +# define TLS1_TXT_SRP_SHA_DSS_WITH_AES_128_CBC_SHA "SRP-DSS-AES-128-CBC-SHA" +# define TLS1_TXT_SRP_SHA_WITH_AES_256_CBC_SHA "SRP-AES-256-CBC-SHA" +# define TLS1_TXT_SRP_SHA_RSA_WITH_AES_256_CBC_SHA "SRP-RSA-AES-256-CBC-SHA" +# define TLS1_TXT_SRP_SHA_DSS_WITH_AES_256_CBC_SHA "SRP-DSS-AES-256-CBC-SHA" + +/* Camellia ciphersuites from RFC4132 */ +# define TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA "CAMELLIA128-SHA" +# define TLS1_TXT_DH_DSS_WITH_CAMELLIA_128_CBC_SHA "DH-DSS-CAMELLIA128-SHA" +# define TLS1_TXT_DH_RSA_WITH_CAMELLIA_128_CBC_SHA "DH-RSA-CAMELLIA128-SHA" +# define TLS1_TXT_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA "DHE-DSS-CAMELLIA128-SHA" +# define TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA "DHE-RSA-CAMELLIA128-SHA" +# define TLS1_TXT_ADH_WITH_CAMELLIA_128_CBC_SHA "ADH-CAMELLIA128-SHA" + +# define TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA "CAMELLIA256-SHA" +# define TLS1_TXT_DH_DSS_WITH_CAMELLIA_256_CBC_SHA "DH-DSS-CAMELLIA256-SHA" +# define TLS1_TXT_DH_RSA_WITH_CAMELLIA_256_CBC_SHA "DH-RSA-CAMELLIA256-SHA" +# define TLS1_TXT_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA "DHE-DSS-CAMELLIA256-SHA" +# define TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA "DHE-RSA-CAMELLIA256-SHA" +# define TLS1_TXT_ADH_WITH_CAMELLIA_256_CBC_SHA "ADH-CAMELLIA256-SHA" + +/* TLS 1.2 Camellia SHA-256 ciphersuites from RFC5932 */ +# define TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA256 "CAMELLIA128-SHA256" +# define TLS1_TXT_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 "DH-DSS-CAMELLIA128-SHA256" +# define TLS1_TXT_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 "DH-RSA-CAMELLIA128-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 "DHE-DSS-CAMELLIA128-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 "DHE-RSA-CAMELLIA128-SHA256" +# define TLS1_TXT_ADH_WITH_CAMELLIA_128_CBC_SHA256 "ADH-CAMELLIA128-SHA256" + +# define TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA256 "CAMELLIA256-SHA256" +# define TLS1_TXT_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 "DH-DSS-CAMELLIA256-SHA256" +# define TLS1_TXT_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 "DH-RSA-CAMELLIA256-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 "DHE-DSS-CAMELLIA256-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 "DHE-RSA-CAMELLIA256-SHA256" +# define TLS1_TXT_ADH_WITH_CAMELLIA_256_CBC_SHA256 "ADH-CAMELLIA256-SHA256" + +# define TLS1_TXT_PSK_WITH_CAMELLIA_128_CBC_SHA256 "PSK-CAMELLIA128-SHA256" +# define TLS1_TXT_PSK_WITH_CAMELLIA_256_CBC_SHA384 "PSK-CAMELLIA256-SHA384" +# define TLS1_TXT_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 "DHE-PSK-CAMELLIA128-SHA256" +# define TLS1_TXT_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 "DHE-PSK-CAMELLIA256-SHA384" +# define TLS1_TXT_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 "RSA-PSK-CAMELLIA128-SHA256" +# define TLS1_TXT_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 "RSA-PSK-CAMELLIA256-SHA384" +# define TLS1_TXT_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 "ECDHE-PSK-CAMELLIA128-SHA256" +# define TLS1_TXT_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 "ECDHE-PSK-CAMELLIA256-SHA384" + +/* SEED ciphersuites from RFC4162 */ +# define TLS1_TXT_RSA_WITH_SEED_SHA "SEED-SHA" +# define TLS1_TXT_DH_DSS_WITH_SEED_SHA "DH-DSS-SEED-SHA" +# define TLS1_TXT_DH_RSA_WITH_SEED_SHA "DH-RSA-SEED-SHA" +# define TLS1_TXT_DHE_DSS_WITH_SEED_SHA "DHE-DSS-SEED-SHA" +# define TLS1_TXT_DHE_RSA_WITH_SEED_SHA "DHE-RSA-SEED-SHA" +# define TLS1_TXT_ADH_WITH_SEED_SHA "ADH-SEED-SHA" + +/* TLS v1.2 ciphersuites */ +# define TLS1_TXT_RSA_WITH_NULL_SHA256 "NULL-SHA256" +# define TLS1_TXT_RSA_WITH_AES_128_SHA256 "AES128-SHA256" +# define TLS1_TXT_RSA_WITH_AES_256_SHA256 "AES256-SHA256" +# define TLS1_TXT_DH_DSS_WITH_AES_128_SHA256 "DH-DSS-AES128-SHA256" +# define TLS1_TXT_DH_RSA_WITH_AES_128_SHA256 "DH-RSA-AES128-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_AES_128_SHA256 "DHE-DSS-AES128-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 "DHE-RSA-AES128-SHA256" +# define TLS1_TXT_DH_DSS_WITH_AES_256_SHA256 "DH-DSS-AES256-SHA256" +# define TLS1_TXT_DH_RSA_WITH_AES_256_SHA256 "DH-RSA-AES256-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_AES_256_SHA256 "DHE-DSS-AES256-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 "DHE-RSA-AES256-SHA256" +# define TLS1_TXT_ADH_WITH_AES_128_SHA256 "ADH-AES128-SHA256" +# define TLS1_TXT_ADH_WITH_AES_256_SHA256 "ADH-AES256-SHA256" + +/* TLS v1.2 GCM ciphersuites from RFC5288 */ +# define TLS1_TXT_RSA_WITH_AES_128_GCM_SHA256 "AES128-GCM-SHA256" +# define TLS1_TXT_RSA_WITH_AES_256_GCM_SHA384 "AES256-GCM-SHA384" +# define TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 "DHE-RSA-AES128-GCM-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 "DHE-RSA-AES256-GCM-SHA384" +# define TLS1_TXT_DH_RSA_WITH_AES_128_GCM_SHA256 "DH-RSA-AES128-GCM-SHA256" +# define TLS1_TXT_DH_RSA_WITH_AES_256_GCM_SHA384 "DH-RSA-AES256-GCM-SHA384" +# define TLS1_TXT_DHE_DSS_WITH_AES_128_GCM_SHA256 "DHE-DSS-AES128-GCM-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_AES_256_GCM_SHA384 "DHE-DSS-AES256-GCM-SHA384" +# define TLS1_TXT_DH_DSS_WITH_AES_128_GCM_SHA256 "DH-DSS-AES128-GCM-SHA256" +# define TLS1_TXT_DH_DSS_WITH_AES_256_GCM_SHA384 "DH-DSS-AES256-GCM-SHA384" +# define TLS1_TXT_ADH_WITH_AES_128_GCM_SHA256 "ADH-AES128-GCM-SHA256" +# define TLS1_TXT_ADH_WITH_AES_256_GCM_SHA384 "ADH-AES256-GCM-SHA384" + +/* CCM ciphersuites from RFC6655 */ +# define TLS1_TXT_RSA_WITH_AES_128_CCM "AES128-CCM" +# define TLS1_TXT_RSA_WITH_AES_256_CCM "AES256-CCM" +# define TLS1_TXT_DHE_RSA_WITH_AES_128_CCM "DHE-RSA-AES128-CCM" +# define TLS1_TXT_DHE_RSA_WITH_AES_256_CCM "DHE-RSA-AES256-CCM" + +# define TLS1_TXT_RSA_WITH_AES_128_CCM_8 "AES128-CCM8" +# define TLS1_TXT_RSA_WITH_AES_256_CCM_8 "AES256-CCM8" +# define TLS1_TXT_DHE_RSA_WITH_AES_128_CCM_8 "DHE-RSA-AES128-CCM8" +# define TLS1_TXT_DHE_RSA_WITH_AES_256_CCM_8 "DHE-RSA-AES256-CCM8" + +# define TLS1_TXT_PSK_WITH_AES_128_CCM "PSK-AES128-CCM" +# define TLS1_TXT_PSK_WITH_AES_256_CCM "PSK-AES256-CCM" +# define TLS1_TXT_DHE_PSK_WITH_AES_128_CCM "DHE-PSK-AES128-CCM" +# define TLS1_TXT_DHE_PSK_WITH_AES_256_CCM "DHE-PSK-AES256-CCM" + +# define TLS1_TXT_PSK_WITH_AES_128_CCM_8 "PSK-AES128-CCM8" +# define TLS1_TXT_PSK_WITH_AES_256_CCM_8 "PSK-AES256-CCM8" +# define TLS1_TXT_DHE_PSK_WITH_AES_128_CCM_8 "DHE-PSK-AES128-CCM8" +# define TLS1_TXT_DHE_PSK_WITH_AES_256_CCM_8 "DHE-PSK-AES256-CCM8" + +/* CCM ciphersuites from RFC7251 */ +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CCM "ECDHE-ECDSA-AES128-CCM" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CCM "ECDHE-ECDSA-AES256-CCM" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CCM_8 "ECDHE-ECDSA-AES128-CCM8" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CCM_8 "ECDHE-ECDSA-AES256-CCM8" + +/* ECDH HMAC based ciphersuites from RFC5289 */ +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_SHA256 "ECDHE-ECDSA-AES128-SHA256" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_SHA384 "ECDHE-ECDSA-AES256-SHA384" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_128_SHA256 "ECDH-ECDSA-AES128-SHA256" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_256_SHA384 "ECDH-ECDSA-AES256-SHA384" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 "ECDHE-RSA-AES128-SHA256" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 "ECDHE-RSA-AES256-SHA384" +# define TLS1_TXT_ECDH_RSA_WITH_AES_128_SHA256 "ECDH-RSA-AES128-SHA256" +# define TLS1_TXT_ECDH_RSA_WITH_AES_256_SHA384 "ECDH-RSA-AES256-SHA384" + +/* ECDH GCM based ciphersuites from RFC5289 */ +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 "ECDHE-ECDSA-AES128-GCM-SHA256" +# define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 "ECDHE-ECDSA-AES256-GCM-SHA384" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 "ECDH-ECDSA-AES128-GCM-SHA256" +# define TLS1_TXT_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 "ECDH-ECDSA-AES256-GCM-SHA384" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 "ECDHE-RSA-AES128-GCM-SHA256" +# define TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 "ECDHE-RSA-AES256-GCM-SHA384" +# define TLS1_TXT_ECDH_RSA_WITH_AES_128_GCM_SHA256 "ECDH-RSA-AES128-GCM-SHA256" +# define TLS1_TXT_ECDH_RSA_WITH_AES_256_GCM_SHA384 "ECDH-RSA-AES256-GCM-SHA384" + +/* TLS v1.2 PSK GCM ciphersuites from RFC5487 */ +# define TLS1_TXT_PSK_WITH_AES_128_GCM_SHA256 "PSK-AES128-GCM-SHA256" +# define TLS1_TXT_PSK_WITH_AES_256_GCM_SHA384 "PSK-AES256-GCM-SHA384" + +/* ECDHE PSK ciphersuites from RFC 5489 */ +# define TLS1_TXT_ECDHE_PSK_WITH_RC4_128_SHA "ECDHE-PSK-RC4-SHA" +# define TLS1_TXT_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA "ECDHE-PSK-3DES-EDE-CBC-SHA" +# define TLS1_TXT_ECDHE_PSK_WITH_AES_128_CBC_SHA "ECDHE-PSK-AES128-CBC-SHA" +# define TLS1_TXT_ECDHE_PSK_WITH_AES_256_CBC_SHA "ECDHE-PSK-AES256-CBC-SHA" + +# define TLS1_TXT_ECDHE_PSK_WITH_AES_128_CBC_SHA256 "ECDHE-PSK-AES128-CBC-SHA256" +# define TLS1_TXT_ECDHE_PSK_WITH_AES_256_CBC_SHA384 "ECDHE-PSK-AES256-CBC-SHA384" + +# define TLS1_TXT_ECDHE_PSK_WITH_NULL_SHA "ECDHE-PSK-NULL-SHA" +# define TLS1_TXT_ECDHE_PSK_WITH_NULL_SHA256 "ECDHE-PSK-NULL-SHA256" +# define TLS1_TXT_ECDHE_PSK_WITH_NULL_SHA384 "ECDHE-PSK-NULL-SHA384" + +/* Camellia-CBC ciphersuites from RFC6367 */ +# define TLS1_TXT_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 "ECDHE-ECDSA-CAMELLIA128-SHA256" +# define TLS1_TXT_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 "ECDHE-ECDSA-CAMELLIA256-SHA384" +# define TLS1_TXT_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 "ECDH-ECDSA-CAMELLIA128-SHA256" +# define TLS1_TXT_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 "ECDH-ECDSA-CAMELLIA256-SHA384" +# define TLS1_TXT_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 "ECDHE-RSA-CAMELLIA128-SHA256" +# define TLS1_TXT_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 "ECDHE-RSA-CAMELLIA256-SHA384" +# define TLS1_TXT_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 "ECDH-RSA-CAMELLIA128-SHA256" +# define TLS1_TXT_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 "ECDH-RSA-CAMELLIA256-SHA384" + +/* draft-ietf-tls-chacha20-poly1305-03 */ +# define TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 "ECDHE-RSA-CHACHA20-POLY1305" +# define TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 "ECDHE-ECDSA-CHACHA20-POLY1305" +# define TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 "DHE-RSA-CHACHA20-POLY1305" +# define TLS1_TXT_PSK_WITH_CHACHA20_POLY1305 "PSK-CHACHA20-POLY1305" +# define TLS1_TXT_ECDHE_PSK_WITH_CHACHA20_POLY1305 "ECDHE-PSK-CHACHA20-POLY1305" +# define TLS1_TXT_DHE_PSK_WITH_CHACHA20_POLY1305 "DHE-PSK-CHACHA20-POLY1305" +# define TLS1_TXT_RSA_PSK_WITH_CHACHA20_POLY1305 "RSA-PSK-CHACHA20-POLY1305" + +/* Aria ciphersuites from RFC6209 */ +# define TLS1_TXT_RSA_WITH_ARIA_128_GCM_SHA256 "ARIA128-GCM-SHA256" +# define TLS1_TXT_RSA_WITH_ARIA_256_GCM_SHA384 "ARIA256-GCM-SHA384" +# define TLS1_TXT_DHE_RSA_WITH_ARIA_128_GCM_SHA256 "DHE-RSA-ARIA128-GCM-SHA256" +# define TLS1_TXT_DHE_RSA_WITH_ARIA_256_GCM_SHA384 "DHE-RSA-ARIA256-GCM-SHA384" +# define TLS1_TXT_DH_RSA_WITH_ARIA_128_GCM_SHA256 "DH-RSA-ARIA128-GCM-SHA256" +# define TLS1_TXT_DH_RSA_WITH_ARIA_256_GCM_SHA384 "DH-RSA-ARIA256-GCM-SHA384" +# define TLS1_TXT_DHE_DSS_WITH_ARIA_128_GCM_SHA256 "DHE-DSS-ARIA128-GCM-SHA256" +# define TLS1_TXT_DHE_DSS_WITH_ARIA_256_GCM_SHA384 "DHE-DSS-ARIA256-GCM-SHA384" +# define TLS1_TXT_DH_DSS_WITH_ARIA_128_GCM_SHA256 "DH-DSS-ARIA128-GCM-SHA256" +# define TLS1_TXT_DH_DSS_WITH_ARIA_256_GCM_SHA384 "DH-DSS-ARIA256-GCM-SHA384" +# define TLS1_TXT_DH_anon_WITH_ARIA_128_GCM_SHA256 "ADH-ARIA128-GCM-SHA256" +# define TLS1_TXT_DH_anon_WITH_ARIA_256_GCM_SHA384 "ADH-ARIA256-GCM-SHA384" +# define TLS1_TXT_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 "ECDHE-ECDSA-ARIA128-GCM-SHA256" +# define TLS1_TXT_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 "ECDHE-ECDSA-ARIA256-GCM-SHA384" +# define TLS1_TXT_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 "ECDH-ECDSA-ARIA128-GCM-SHA256" +# define TLS1_TXT_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 "ECDH-ECDSA-ARIA256-GCM-SHA384" +# define TLS1_TXT_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 "ECDHE-ARIA128-GCM-SHA256" +# define TLS1_TXT_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 "ECDHE-ARIA256-GCM-SHA384" +# define TLS1_TXT_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 "ECDH-ARIA128-GCM-SHA256" +# define TLS1_TXT_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 "ECDH-ARIA256-GCM-SHA384" +# define TLS1_TXT_PSK_WITH_ARIA_128_GCM_SHA256 "PSK-ARIA128-GCM-SHA256" +# define TLS1_TXT_PSK_WITH_ARIA_256_GCM_SHA384 "PSK-ARIA256-GCM-SHA384" +# define TLS1_TXT_DHE_PSK_WITH_ARIA_128_GCM_SHA256 "DHE-PSK-ARIA128-GCM-SHA256" +# define TLS1_TXT_DHE_PSK_WITH_ARIA_256_GCM_SHA384 "DHE-PSK-ARIA256-GCM-SHA384" +# define TLS1_TXT_RSA_PSK_WITH_ARIA_128_GCM_SHA256 "RSA-PSK-ARIA128-GCM-SHA256" +# define TLS1_TXT_RSA_PSK_WITH_ARIA_256_GCM_SHA384 "RSA-PSK-ARIA256-GCM-SHA384" + +# define TLS_CT_RSA_SIGN 1 +# define TLS_CT_DSS_SIGN 2 +# define TLS_CT_RSA_FIXED_DH 3 +# define TLS_CT_DSS_FIXED_DH 4 +# define TLS_CT_ECDSA_SIGN 64 +# define TLS_CT_RSA_FIXED_ECDH 65 +# define TLS_CT_ECDSA_FIXED_ECDH 66 +# define TLS_CT_GOST01_SIGN 22 +# define TLS_CT_GOST12_SIGN 238 +# define TLS_CT_GOST12_512_SIGN 239 + +/* + * when correcting this number, correct also SSL3_CT_NUMBER in ssl3.h (see + * comment there) + */ +# define TLS_CT_NUMBER 10 + +# if defined(SSL3_CT_NUMBER) +# if TLS_CT_NUMBER != SSL3_CT_NUMBER +# error "SSL/TLS CT_NUMBER values do not match" +# endif +# endif + +# define TLS1_FINISH_MAC_LENGTH 12 + +# define TLS_MD_MAX_CONST_SIZE 22 +# define TLS_MD_CLIENT_FINISH_CONST "client finished" +# define TLS_MD_CLIENT_FINISH_CONST_SIZE 15 +# define TLS_MD_SERVER_FINISH_CONST "server finished" +# define TLS_MD_SERVER_FINISH_CONST_SIZE 15 +# define TLS_MD_KEY_EXPANSION_CONST "key expansion" +# define TLS_MD_KEY_EXPANSION_CONST_SIZE 13 +# define TLS_MD_CLIENT_WRITE_KEY_CONST "client write key" +# define TLS_MD_CLIENT_WRITE_KEY_CONST_SIZE 16 +# define TLS_MD_SERVER_WRITE_KEY_CONST "server write key" +# define TLS_MD_SERVER_WRITE_KEY_CONST_SIZE 16 +# define TLS_MD_IV_BLOCK_CONST "IV block" +# define TLS_MD_IV_BLOCK_CONST_SIZE 8 +# define TLS_MD_MASTER_SECRET_CONST "master secret" +# define TLS_MD_MASTER_SECRET_CONST_SIZE 13 +# define TLS_MD_EXTENDED_MASTER_SECRET_CONST "extended master secret" +# define TLS_MD_EXTENDED_MASTER_SECRET_CONST_SIZE 22 + +# ifdef CHARSET_EBCDIC +# undef TLS_MD_CLIENT_FINISH_CONST +/* + * client finished + */ +# define TLS_MD_CLIENT_FINISH_CONST "\x63\x6c\x69\x65\x6e\x74\x20\x66\x69\x6e\x69\x73\x68\x65\x64" + +# undef TLS_MD_SERVER_FINISH_CONST +/* + * server finished + */ +# define TLS_MD_SERVER_FINISH_CONST "\x73\x65\x72\x76\x65\x72\x20\x66\x69\x6e\x69\x73\x68\x65\x64" + +# undef TLS_MD_SERVER_WRITE_KEY_CONST +/* + * server write key + */ +# define TLS_MD_SERVER_WRITE_KEY_CONST "\x73\x65\x72\x76\x65\x72\x20\x77\x72\x69\x74\x65\x20\x6b\x65\x79" + +# undef TLS_MD_KEY_EXPANSION_CONST +/* + * key expansion + */ +# define TLS_MD_KEY_EXPANSION_CONST "\x6b\x65\x79\x20\x65\x78\x70\x61\x6e\x73\x69\x6f\x6e" + +# undef TLS_MD_CLIENT_WRITE_KEY_CONST +/* + * client write key + */ +# define TLS_MD_CLIENT_WRITE_KEY_CONST "\x63\x6c\x69\x65\x6e\x74\x20\x77\x72\x69\x74\x65\x20\x6b\x65\x79" + +# undef TLS_MD_SERVER_WRITE_KEY_CONST +/* + * server write key + */ +# define TLS_MD_SERVER_WRITE_KEY_CONST "\x73\x65\x72\x76\x65\x72\x20\x77\x72\x69\x74\x65\x20\x6b\x65\x79" + +# undef TLS_MD_IV_BLOCK_CONST +/* + * IV block + */ +# define TLS_MD_IV_BLOCK_CONST "\x49\x56\x20\x62\x6c\x6f\x63\x6b" + +# undef TLS_MD_MASTER_SECRET_CONST +/* + * master secret + */ +# define TLS_MD_MASTER_SECRET_CONST "\x6d\x61\x73\x74\x65\x72\x20\x73\x65\x63\x72\x65\x74" +# undef TLS_MD_EXTENDED_MASTER_SECRET_CONST +/* + * extended master secret + */ +# define TLS_MD_EXTENDED_MASTER_SECRET_CONST "\x65\x78\x74\x65\x6e\x64\x65\x64\x20\x6d\x61\x73\x74\x65\x72\x20\x73\x65\x63\x72\x65\x74" +# endif + +/* TLS Session Ticket extension struct */ +struct tls_session_ticket_ext_st { + unsigned short length; + void *data; +}; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/ts.h b/thrid-party/openssl/openssl/ts.h new file mode 100644 index 0000000000..3b58aa527e --- /dev/null +++ b/thrid-party/openssl/openssl/ts.h @@ -0,0 +1,559 @@ +/* + * Copyright 2006-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_TS_H +# define HEADER_TS_H + +# include + +# ifndef OPENSSL_NO_TS +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +# include +# include + +typedef struct TS_msg_imprint_st TS_MSG_IMPRINT; +typedef struct TS_req_st TS_REQ; +typedef struct TS_accuracy_st TS_ACCURACY; +typedef struct TS_tst_info_st TS_TST_INFO; + +/* Possible values for status. */ +# define TS_STATUS_GRANTED 0 +# define TS_STATUS_GRANTED_WITH_MODS 1 +# define TS_STATUS_REJECTION 2 +# define TS_STATUS_WAITING 3 +# define TS_STATUS_REVOCATION_WARNING 4 +# define TS_STATUS_REVOCATION_NOTIFICATION 5 + +/* Possible values for failure_info. */ +# define TS_INFO_BAD_ALG 0 +# define TS_INFO_BAD_REQUEST 2 +# define TS_INFO_BAD_DATA_FORMAT 5 +# define TS_INFO_TIME_NOT_AVAILABLE 14 +# define TS_INFO_UNACCEPTED_POLICY 15 +# define TS_INFO_UNACCEPTED_EXTENSION 16 +# define TS_INFO_ADD_INFO_NOT_AVAILABLE 17 +# define TS_INFO_SYSTEM_FAILURE 25 + + +typedef struct TS_status_info_st TS_STATUS_INFO; +typedef struct ESS_issuer_serial ESS_ISSUER_SERIAL; +typedef struct ESS_cert_id ESS_CERT_ID; +typedef struct ESS_signing_cert ESS_SIGNING_CERT; + +DEFINE_STACK_OF(ESS_CERT_ID) + +typedef struct ESS_cert_id_v2_st ESS_CERT_ID_V2; +typedef struct ESS_signing_cert_v2_st ESS_SIGNING_CERT_V2; + +DEFINE_STACK_OF(ESS_CERT_ID_V2) + +typedef struct TS_resp_st TS_RESP; + +TS_REQ *TS_REQ_new(void); +void TS_REQ_free(TS_REQ *a); +int i2d_TS_REQ(const TS_REQ *a, unsigned char **pp); +TS_REQ *d2i_TS_REQ(TS_REQ **a, const unsigned char **pp, long length); + +TS_REQ *TS_REQ_dup(TS_REQ *a); + +#ifndef OPENSSL_NO_STDIO +TS_REQ *d2i_TS_REQ_fp(FILE *fp, TS_REQ **a); +int i2d_TS_REQ_fp(FILE *fp, TS_REQ *a); +#endif +TS_REQ *d2i_TS_REQ_bio(BIO *fp, TS_REQ **a); +int i2d_TS_REQ_bio(BIO *fp, TS_REQ *a); + +TS_MSG_IMPRINT *TS_MSG_IMPRINT_new(void); +void TS_MSG_IMPRINT_free(TS_MSG_IMPRINT *a); +int i2d_TS_MSG_IMPRINT(const TS_MSG_IMPRINT *a, unsigned char **pp); +TS_MSG_IMPRINT *d2i_TS_MSG_IMPRINT(TS_MSG_IMPRINT **a, + const unsigned char **pp, long length); + +TS_MSG_IMPRINT *TS_MSG_IMPRINT_dup(TS_MSG_IMPRINT *a); + +#ifndef OPENSSL_NO_STDIO +TS_MSG_IMPRINT *d2i_TS_MSG_IMPRINT_fp(FILE *fp, TS_MSG_IMPRINT **a); +int i2d_TS_MSG_IMPRINT_fp(FILE *fp, TS_MSG_IMPRINT *a); +#endif +TS_MSG_IMPRINT *d2i_TS_MSG_IMPRINT_bio(BIO *bio, TS_MSG_IMPRINT **a); +int i2d_TS_MSG_IMPRINT_bio(BIO *bio, TS_MSG_IMPRINT *a); + +TS_RESP *TS_RESP_new(void); +void TS_RESP_free(TS_RESP *a); +int i2d_TS_RESP(const TS_RESP *a, unsigned char **pp); +TS_RESP *d2i_TS_RESP(TS_RESP **a, const unsigned char **pp, long length); +TS_TST_INFO *PKCS7_to_TS_TST_INFO(PKCS7 *token); +TS_RESP *TS_RESP_dup(TS_RESP *a); + +#ifndef OPENSSL_NO_STDIO +TS_RESP *d2i_TS_RESP_fp(FILE *fp, TS_RESP **a); +int i2d_TS_RESP_fp(FILE *fp, TS_RESP *a); +#endif +TS_RESP *d2i_TS_RESP_bio(BIO *bio, TS_RESP **a); +int i2d_TS_RESP_bio(BIO *bio, TS_RESP *a); + +TS_STATUS_INFO *TS_STATUS_INFO_new(void); +void TS_STATUS_INFO_free(TS_STATUS_INFO *a); +int i2d_TS_STATUS_INFO(const TS_STATUS_INFO *a, unsigned char **pp); +TS_STATUS_INFO *d2i_TS_STATUS_INFO(TS_STATUS_INFO **a, + const unsigned char **pp, long length); +TS_STATUS_INFO *TS_STATUS_INFO_dup(TS_STATUS_INFO *a); + +TS_TST_INFO *TS_TST_INFO_new(void); +void TS_TST_INFO_free(TS_TST_INFO *a); +int i2d_TS_TST_INFO(const TS_TST_INFO *a, unsigned char **pp); +TS_TST_INFO *d2i_TS_TST_INFO(TS_TST_INFO **a, const unsigned char **pp, + long length); +TS_TST_INFO *TS_TST_INFO_dup(TS_TST_INFO *a); + +#ifndef OPENSSL_NO_STDIO +TS_TST_INFO *d2i_TS_TST_INFO_fp(FILE *fp, TS_TST_INFO **a); +int i2d_TS_TST_INFO_fp(FILE *fp, TS_TST_INFO *a); +#endif +TS_TST_INFO *d2i_TS_TST_INFO_bio(BIO *bio, TS_TST_INFO **a); +int i2d_TS_TST_INFO_bio(BIO *bio, TS_TST_INFO *a); + +TS_ACCURACY *TS_ACCURACY_new(void); +void TS_ACCURACY_free(TS_ACCURACY *a); +int i2d_TS_ACCURACY(const TS_ACCURACY *a, unsigned char **pp); +TS_ACCURACY *d2i_TS_ACCURACY(TS_ACCURACY **a, const unsigned char **pp, + long length); +TS_ACCURACY *TS_ACCURACY_dup(TS_ACCURACY *a); + +ESS_ISSUER_SERIAL *ESS_ISSUER_SERIAL_new(void); +void ESS_ISSUER_SERIAL_free(ESS_ISSUER_SERIAL *a); +int i2d_ESS_ISSUER_SERIAL(const ESS_ISSUER_SERIAL *a, unsigned char **pp); +ESS_ISSUER_SERIAL *d2i_ESS_ISSUER_SERIAL(ESS_ISSUER_SERIAL **a, + const unsigned char **pp, + long length); +ESS_ISSUER_SERIAL *ESS_ISSUER_SERIAL_dup(ESS_ISSUER_SERIAL *a); + +ESS_CERT_ID *ESS_CERT_ID_new(void); +void ESS_CERT_ID_free(ESS_CERT_ID *a); +int i2d_ESS_CERT_ID(const ESS_CERT_ID *a, unsigned char **pp); +ESS_CERT_ID *d2i_ESS_CERT_ID(ESS_CERT_ID **a, const unsigned char **pp, + long length); +ESS_CERT_ID *ESS_CERT_ID_dup(ESS_CERT_ID *a); + +ESS_SIGNING_CERT *ESS_SIGNING_CERT_new(void); +void ESS_SIGNING_CERT_free(ESS_SIGNING_CERT *a); +int i2d_ESS_SIGNING_CERT(const ESS_SIGNING_CERT *a, unsigned char **pp); +ESS_SIGNING_CERT *d2i_ESS_SIGNING_CERT(ESS_SIGNING_CERT **a, + const unsigned char **pp, long length); +ESS_SIGNING_CERT *ESS_SIGNING_CERT_dup(ESS_SIGNING_CERT *a); + +ESS_CERT_ID_V2 *ESS_CERT_ID_V2_new(void); +void ESS_CERT_ID_V2_free(ESS_CERT_ID_V2 *a); +int i2d_ESS_CERT_ID_V2(const ESS_CERT_ID_V2 *a, unsigned char **pp); +ESS_CERT_ID_V2 *d2i_ESS_CERT_ID_V2(ESS_CERT_ID_V2 **a, + const unsigned char **pp, long length); +ESS_CERT_ID_V2 *ESS_CERT_ID_V2_dup(ESS_CERT_ID_V2 *a); + +ESS_SIGNING_CERT_V2 *ESS_SIGNING_CERT_V2_new(void); +void ESS_SIGNING_CERT_V2_free(ESS_SIGNING_CERT_V2 *a); +int i2d_ESS_SIGNING_CERT_V2(const ESS_SIGNING_CERT_V2 *a, unsigned char **pp); +ESS_SIGNING_CERT_V2 *d2i_ESS_SIGNING_CERT_V2(ESS_SIGNING_CERT_V2 **a, + const unsigned char **pp, + long length); +ESS_SIGNING_CERT_V2 *ESS_SIGNING_CERT_V2_dup(ESS_SIGNING_CERT_V2 *a); + +int TS_REQ_set_version(TS_REQ *a, long version); +long TS_REQ_get_version(const TS_REQ *a); + +int TS_STATUS_INFO_set_status(TS_STATUS_INFO *a, int i); +const ASN1_INTEGER *TS_STATUS_INFO_get0_status(const TS_STATUS_INFO *a); + +const STACK_OF(ASN1_UTF8STRING) * +TS_STATUS_INFO_get0_text(const TS_STATUS_INFO *a); + +const ASN1_BIT_STRING * +TS_STATUS_INFO_get0_failure_info(const TS_STATUS_INFO *a); + +int TS_REQ_set_msg_imprint(TS_REQ *a, TS_MSG_IMPRINT *msg_imprint); +TS_MSG_IMPRINT *TS_REQ_get_msg_imprint(TS_REQ *a); + +int TS_MSG_IMPRINT_set_algo(TS_MSG_IMPRINT *a, X509_ALGOR *alg); +X509_ALGOR *TS_MSG_IMPRINT_get_algo(TS_MSG_IMPRINT *a); + +int TS_MSG_IMPRINT_set_msg(TS_MSG_IMPRINT *a, unsigned char *d, int len); +ASN1_OCTET_STRING *TS_MSG_IMPRINT_get_msg(TS_MSG_IMPRINT *a); + +int TS_REQ_set_policy_id(TS_REQ *a, const ASN1_OBJECT *policy); +ASN1_OBJECT *TS_REQ_get_policy_id(TS_REQ *a); + +int TS_REQ_set_nonce(TS_REQ *a, const ASN1_INTEGER *nonce); +const ASN1_INTEGER *TS_REQ_get_nonce(const TS_REQ *a); + +int TS_REQ_set_cert_req(TS_REQ *a, int cert_req); +int TS_REQ_get_cert_req(const TS_REQ *a); + +STACK_OF(X509_EXTENSION) *TS_REQ_get_exts(TS_REQ *a); +void TS_REQ_ext_free(TS_REQ *a); +int TS_REQ_get_ext_count(TS_REQ *a); +int TS_REQ_get_ext_by_NID(TS_REQ *a, int nid, int lastpos); +int TS_REQ_get_ext_by_OBJ(TS_REQ *a, const ASN1_OBJECT *obj, int lastpos); +int TS_REQ_get_ext_by_critical(TS_REQ *a, int crit, int lastpos); +X509_EXTENSION *TS_REQ_get_ext(TS_REQ *a, int loc); +X509_EXTENSION *TS_REQ_delete_ext(TS_REQ *a, int loc); +int TS_REQ_add_ext(TS_REQ *a, X509_EXTENSION *ex, int loc); +void *TS_REQ_get_ext_d2i(TS_REQ *a, int nid, int *crit, int *idx); + +/* Function declarations for TS_REQ defined in ts/ts_req_print.c */ + +int TS_REQ_print_bio(BIO *bio, TS_REQ *a); + +/* Function declarations for TS_RESP defined in ts/ts_resp_utils.c */ + +int TS_RESP_set_status_info(TS_RESP *a, TS_STATUS_INFO *info); +TS_STATUS_INFO *TS_RESP_get_status_info(TS_RESP *a); + +/* Caller loses ownership of PKCS7 and TS_TST_INFO objects. */ +void TS_RESP_set_tst_info(TS_RESP *a, PKCS7 *p7, TS_TST_INFO *tst_info); +PKCS7 *TS_RESP_get_token(TS_RESP *a); +TS_TST_INFO *TS_RESP_get_tst_info(TS_RESP *a); + +int TS_TST_INFO_set_version(TS_TST_INFO *a, long version); +long TS_TST_INFO_get_version(const TS_TST_INFO *a); + +int TS_TST_INFO_set_policy_id(TS_TST_INFO *a, ASN1_OBJECT *policy_id); +ASN1_OBJECT *TS_TST_INFO_get_policy_id(TS_TST_INFO *a); + +int TS_TST_INFO_set_msg_imprint(TS_TST_INFO *a, TS_MSG_IMPRINT *msg_imprint); +TS_MSG_IMPRINT *TS_TST_INFO_get_msg_imprint(TS_TST_INFO *a); + +int TS_TST_INFO_set_serial(TS_TST_INFO *a, const ASN1_INTEGER *serial); +const ASN1_INTEGER *TS_TST_INFO_get_serial(const TS_TST_INFO *a); + +int TS_TST_INFO_set_time(TS_TST_INFO *a, const ASN1_GENERALIZEDTIME *gtime); +const ASN1_GENERALIZEDTIME *TS_TST_INFO_get_time(const TS_TST_INFO *a); + +int TS_TST_INFO_set_accuracy(TS_TST_INFO *a, TS_ACCURACY *accuracy); +TS_ACCURACY *TS_TST_INFO_get_accuracy(TS_TST_INFO *a); + +int TS_ACCURACY_set_seconds(TS_ACCURACY *a, const ASN1_INTEGER *seconds); +const ASN1_INTEGER *TS_ACCURACY_get_seconds(const TS_ACCURACY *a); + +int TS_ACCURACY_set_millis(TS_ACCURACY *a, const ASN1_INTEGER *millis); +const ASN1_INTEGER *TS_ACCURACY_get_millis(const TS_ACCURACY *a); + +int TS_ACCURACY_set_micros(TS_ACCURACY *a, const ASN1_INTEGER *micros); +const ASN1_INTEGER *TS_ACCURACY_get_micros(const TS_ACCURACY *a); + +int TS_TST_INFO_set_ordering(TS_TST_INFO *a, int ordering); +int TS_TST_INFO_get_ordering(const TS_TST_INFO *a); + +int TS_TST_INFO_set_nonce(TS_TST_INFO *a, const ASN1_INTEGER *nonce); +const ASN1_INTEGER *TS_TST_INFO_get_nonce(const TS_TST_INFO *a); + +int TS_TST_INFO_set_tsa(TS_TST_INFO *a, GENERAL_NAME *tsa); +GENERAL_NAME *TS_TST_INFO_get_tsa(TS_TST_INFO *a); + +STACK_OF(X509_EXTENSION) *TS_TST_INFO_get_exts(TS_TST_INFO *a); +void TS_TST_INFO_ext_free(TS_TST_INFO *a); +int TS_TST_INFO_get_ext_count(TS_TST_INFO *a); +int TS_TST_INFO_get_ext_by_NID(TS_TST_INFO *a, int nid, int lastpos); +int TS_TST_INFO_get_ext_by_OBJ(TS_TST_INFO *a, const ASN1_OBJECT *obj, + int lastpos); +int TS_TST_INFO_get_ext_by_critical(TS_TST_INFO *a, int crit, int lastpos); +X509_EXTENSION *TS_TST_INFO_get_ext(TS_TST_INFO *a, int loc); +X509_EXTENSION *TS_TST_INFO_delete_ext(TS_TST_INFO *a, int loc); +int TS_TST_INFO_add_ext(TS_TST_INFO *a, X509_EXTENSION *ex, int loc); +void *TS_TST_INFO_get_ext_d2i(TS_TST_INFO *a, int nid, int *crit, int *idx); + +/* + * Declarations related to response generation, defined in ts/ts_resp_sign.c. + */ + +/* Optional flags for response generation. */ + +/* Don't include the TSA name in response. */ +# define TS_TSA_NAME 0x01 + +/* Set ordering to true in response. */ +# define TS_ORDERING 0x02 + +/* + * Include the signer certificate and the other specified certificates in + * the ESS signing certificate attribute beside the PKCS7 signed data. + * Only the signer certificates is included by default. + */ +# define TS_ESS_CERT_ID_CHAIN 0x04 + +/* Forward declaration. */ +struct TS_resp_ctx; + +/* This must return a unique number less than 160 bits long. */ +typedef ASN1_INTEGER *(*TS_serial_cb) (struct TS_resp_ctx *, void *); + +/* + * This must return the seconds and microseconds since Jan 1, 1970 in the sec + * and usec variables allocated by the caller. Return non-zero for success + * and zero for failure. + */ +typedef int (*TS_time_cb) (struct TS_resp_ctx *, void *, long *sec, + long *usec); + +/* + * This must process the given extension. It can modify the TS_TST_INFO + * object of the context. Return values: !0 (processed), 0 (error, it must + * set the status info/failure info of the response). + */ +typedef int (*TS_extension_cb) (struct TS_resp_ctx *, X509_EXTENSION *, + void *); + +typedef struct TS_resp_ctx TS_RESP_CTX; + +DEFINE_STACK_OF_CONST(EVP_MD) + +/* Creates a response context that can be used for generating responses. */ +TS_RESP_CTX *TS_RESP_CTX_new(void); +void TS_RESP_CTX_free(TS_RESP_CTX *ctx); + +/* This parameter must be set. */ +int TS_RESP_CTX_set_signer_cert(TS_RESP_CTX *ctx, X509 *signer); + +/* This parameter must be set. */ +int TS_RESP_CTX_set_signer_key(TS_RESP_CTX *ctx, EVP_PKEY *key); + +int TS_RESP_CTX_set_signer_digest(TS_RESP_CTX *ctx, + const EVP_MD *signer_digest); +int TS_RESP_CTX_set_ess_cert_id_digest(TS_RESP_CTX *ctx, const EVP_MD *md); + +/* This parameter must be set. */ +int TS_RESP_CTX_set_def_policy(TS_RESP_CTX *ctx, const ASN1_OBJECT *def_policy); + +/* No additional certs are included in the response by default. */ +int TS_RESP_CTX_set_certs(TS_RESP_CTX *ctx, STACK_OF(X509) *certs); + +/* + * Adds a new acceptable policy, only the default policy is accepted by + * default. + */ +int TS_RESP_CTX_add_policy(TS_RESP_CTX *ctx, const ASN1_OBJECT *policy); + +/* + * Adds a new acceptable message digest. Note that no message digests are + * accepted by default. The md argument is shared with the caller. + */ +int TS_RESP_CTX_add_md(TS_RESP_CTX *ctx, const EVP_MD *md); + +/* Accuracy is not included by default. */ +int TS_RESP_CTX_set_accuracy(TS_RESP_CTX *ctx, + int secs, int millis, int micros); + +/* + * Clock precision digits, i.e. the number of decimal digits: '0' means sec, + * '3' msec, '6' usec, and so on. Default is 0. + */ +int TS_RESP_CTX_set_clock_precision_digits(TS_RESP_CTX *ctx, + unsigned clock_precision_digits); +/* At most we accept usec precision. */ +# define TS_MAX_CLOCK_PRECISION_DIGITS 6 + +/* Maximum status message length */ +# define TS_MAX_STATUS_LENGTH (1024 * 1024) + +/* No flags are set by default. */ +void TS_RESP_CTX_add_flags(TS_RESP_CTX *ctx, int flags); + +/* Default callback always returns a constant. */ +void TS_RESP_CTX_set_serial_cb(TS_RESP_CTX *ctx, TS_serial_cb cb, void *data); + +/* Default callback uses the gettimeofday() and gmtime() system calls. */ +void TS_RESP_CTX_set_time_cb(TS_RESP_CTX *ctx, TS_time_cb cb, void *data); + +/* + * Default callback rejects all extensions. The extension callback is called + * when the TS_TST_INFO object is already set up and not signed yet. + */ +/* FIXME: extension handling is not tested yet. */ +void TS_RESP_CTX_set_extension_cb(TS_RESP_CTX *ctx, + TS_extension_cb cb, void *data); + +/* The following methods can be used in the callbacks. */ +int TS_RESP_CTX_set_status_info(TS_RESP_CTX *ctx, + int status, const char *text); + +/* Sets the status info only if it is still TS_STATUS_GRANTED. */ +int TS_RESP_CTX_set_status_info_cond(TS_RESP_CTX *ctx, + int status, const char *text); + +int TS_RESP_CTX_add_failure_info(TS_RESP_CTX *ctx, int failure); + +/* The get methods below can be used in the extension callback. */ +TS_REQ *TS_RESP_CTX_get_request(TS_RESP_CTX *ctx); + +TS_TST_INFO *TS_RESP_CTX_get_tst_info(TS_RESP_CTX *ctx); + +/* + * Creates the signed TS_TST_INFO and puts it in TS_RESP. + * In case of errors it sets the status info properly. + * Returns NULL only in case of memory allocation/fatal error. + */ +TS_RESP *TS_RESP_create_response(TS_RESP_CTX *ctx, BIO *req_bio); + +/* + * Declarations related to response verification, + * they are defined in ts/ts_resp_verify.c. + */ + +int TS_RESP_verify_signature(PKCS7 *token, STACK_OF(X509) *certs, + X509_STORE *store, X509 **signer_out); + +/* Context structure for the generic verify method. */ + +/* Verify the signer's certificate and the signature of the response. */ +# define TS_VFY_SIGNATURE (1u << 0) +/* Verify the version number of the response. */ +# define TS_VFY_VERSION (1u << 1) +/* Verify if the policy supplied by the user matches the policy of the TSA. */ +# define TS_VFY_POLICY (1u << 2) +/* + * Verify the message imprint provided by the user. This flag should not be + * specified with TS_VFY_DATA. + */ +# define TS_VFY_IMPRINT (1u << 3) +/* + * Verify the message imprint computed by the verify method from the user + * provided data and the MD algorithm of the response. This flag should not + * be specified with TS_VFY_IMPRINT. + */ +# define TS_VFY_DATA (1u << 4) +/* Verify the nonce value. */ +# define TS_VFY_NONCE (1u << 5) +/* Verify if the TSA name field matches the signer certificate. */ +# define TS_VFY_SIGNER (1u << 6) +/* Verify if the TSA name field equals to the user provided name. */ +# define TS_VFY_TSA_NAME (1u << 7) + +/* You can use the following convenience constants. */ +# define TS_VFY_ALL_IMPRINT (TS_VFY_SIGNATURE \ + | TS_VFY_VERSION \ + | TS_VFY_POLICY \ + | TS_VFY_IMPRINT \ + | TS_VFY_NONCE \ + | TS_VFY_SIGNER \ + | TS_VFY_TSA_NAME) +# define TS_VFY_ALL_DATA (TS_VFY_SIGNATURE \ + | TS_VFY_VERSION \ + | TS_VFY_POLICY \ + | TS_VFY_DATA \ + | TS_VFY_NONCE \ + | TS_VFY_SIGNER \ + | TS_VFY_TSA_NAME) + +typedef struct TS_verify_ctx TS_VERIFY_CTX; + +int TS_RESP_verify_response(TS_VERIFY_CTX *ctx, TS_RESP *response); +int TS_RESP_verify_token(TS_VERIFY_CTX *ctx, PKCS7 *token); + +/* + * Declarations related to response verification context, + */ +TS_VERIFY_CTX *TS_VERIFY_CTX_new(void); +void TS_VERIFY_CTX_init(TS_VERIFY_CTX *ctx); +void TS_VERIFY_CTX_free(TS_VERIFY_CTX *ctx); +void TS_VERIFY_CTX_cleanup(TS_VERIFY_CTX *ctx); +int TS_VERIFY_CTX_set_flags(TS_VERIFY_CTX *ctx, int f); +int TS_VERIFY_CTX_add_flags(TS_VERIFY_CTX *ctx, int f); +BIO *TS_VERIFY_CTX_set_data(TS_VERIFY_CTX *ctx, BIO *b); +unsigned char *TS_VERIFY_CTX_set_imprint(TS_VERIFY_CTX *ctx, + unsigned char *hexstr, long len); +X509_STORE *TS_VERIFY_CTX_set_store(TS_VERIFY_CTX *ctx, X509_STORE *s); +STACK_OF(X509) *TS_VERIFY_CTS_set_certs(TS_VERIFY_CTX *ctx, STACK_OF(X509) *certs); + +/*- + * If ctx is NULL, it allocates and returns a new object, otherwise + * it returns ctx. It initialises all the members as follows: + * flags = TS_VFY_ALL_IMPRINT & ~(TS_VFY_TSA_NAME | TS_VFY_SIGNATURE) + * certs = NULL + * store = NULL + * policy = policy from the request or NULL if absent (in this case + * TS_VFY_POLICY is cleared from flags as well) + * md_alg = MD algorithm from request + * imprint, imprint_len = imprint from request + * data = NULL + * nonce, nonce_len = nonce from the request or NULL if absent (in this case + * TS_VFY_NONCE is cleared from flags as well) + * tsa_name = NULL + * Important: after calling this method TS_VFY_SIGNATURE should be added! + */ +TS_VERIFY_CTX *TS_REQ_to_TS_VERIFY_CTX(TS_REQ *req, TS_VERIFY_CTX *ctx); + +/* Function declarations for TS_RESP defined in ts/ts_resp_print.c */ + +int TS_RESP_print_bio(BIO *bio, TS_RESP *a); +int TS_STATUS_INFO_print_bio(BIO *bio, TS_STATUS_INFO *a); +int TS_TST_INFO_print_bio(BIO *bio, TS_TST_INFO *a); + +/* Common utility functions defined in ts/ts_lib.c */ + +int TS_ASN1_INTEGER_print_bio(BIO *bio, const ASN1_INTEGER *num); +int TS_OBJ_print_bio(BIO *bio, const ASN1_OBJECT *obj); +int TS_ext_print_bio(BIO *bio, const STACK_OF(X509_EXTENSION) *extensions); +int TS_X509_ALGOR_print_bio(BIO *bio, const X509_ALGOR *alg); +int TS_MSG_IMPRINT_print_bio(BIO *bio, TS_MSG_IMPRINT *msg); + +/* + * Function declarations for handling configuration options, defined in + * ts/ts_conf.c + */ + +X509 *TS_CONF_load_cert(const char *file); +STACK_OF(X509) *TS_CONF_load_certs(const char *file); +EVP_PKEY *TS_CONF_load_key(const char *file, const char *pass); +const char *TS_CONF_get_tsa_section(CONF *conf, const char *section); +int TS_CONF_set_serial(CONF *conf, const char *section, TS_serial_cb cb, + TS_RESP_CTX *ctx); +#ifndef OPENSSL_NO_ENGINE +int TS_CONF_set_crypto_device(CONF *conf, const char *section, + const char *device); +int TS_CONF_set_default_engine(const char *name); +#endif +int TS_CONF_set_signer_cert(CONF *conf, const char *section, + const char *cert, TS_RESP_CTX *ctx); +int TS_CONF_set_certs(CONF *conf, const char *section, const char *certs, + TS_RESP_CTX *ctx); +int TS_CONF_set_signer_key(CONF *conf, const char *section, + const char *key, const char *pass, + TS_RESP_CTX *ctx); +int TS_CONF_set_signer_digest(CONF *conf, const char *section, + const char *md, TS_RESP_CTX *ctx); +int TS_CONF_set_def_policy(CONF *conf, const char *section, + const char *policy, TS_RESP_CTX *ctx); +int TS_CONF_set_policies(CONF *conf, const char *section, TS_RESP_CTX *ctx); +int TS_CONF_set_digests(CONF *conf, const char *section, TS_RESP_CTX *ctx); +int TS_CONF_set_accuracy(CONF *conf, const char *section, TS_RESP_CTX *ctx); +int TS_CONF_set_clock_precision_digits(CONF *conf, const char *section, + TS_RESP_CTX *ctx); +int TS_CONF_set_ordering(CONF *conf, const char *section, TS_RESP_CTX *ctx); +int TS_CONF_set_tsa_name(CONF *conf, const char *section, TS_RESP_CTX *ctx); +int TS_CONF_set_ess_cert_id_chain(CONF *conf, const char *section, + TS_RESP_CTX *ctx); +int TS_CONF_set_ess_cert_id_digest(CONF *conf, const char *section, + TS_RESP_CTX *ctx); + +# ifdef __cplusplus +} +# endif +# endif +#endif diff --git a/thrid-party/openssl/openssl/tserr.h b/thrid-party/openssl/openssl/tserr.h new file mode 100644 index 0000000000..07f23339c8 --- /dev/null +++ b/thrid-party/openssl/openssl/tserr.h @@ -0,0 +1,132 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_TSERR_H +# define HEADER_TSERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# include + +# ifndef OPENSSL_NO_TS + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_TS_strings(void); + +/* + * TS function codes. + */ +# define TS_F_DEF_SERIAL_CB 110 +# define TS_F_DEF_TIME_CB 111 +# define TS_F_ESS_ADD_SIGNING_CERT 112 +# define TS_F_ESS_ADD_SIGNING_CERT_V2 147 +# define TS_F_ESS_CERT_ID_NEW_INIT 113 +# define TS_F_ESS_CERT_ID_V2_NEW_INIT 156 +# define TS_F_ESS_SIGNING_CERT_NEW_INIT 114 +# define TS_F_ESS_SIGNING_CERT_V2_NEW_INIT 157 +# define TS_F_INT_TS_RESP_VERIFY_TOKEN 149 +# define TS_F_PKCS7_TO_TS_TST_INFO 148 +# define TS_F_TS_ACCURACY_SET_MICROS 115 +# define TS_F_TS_ACCURACY_SET_MILLIS 116 +# define TS_F_TS_ACCURACY_SET_SECONDS 117 +# define TS_F_TS_CHECK_IMPRINTS 100 +# define TS_F_TS_CHECK_NONCES 101 +# define TS_F_TS_CHECK_POLICY 102 +# define TS_F_TS_CHECK_SIGNING_CERTS 103 +# define TS_F_TS_CHECK_STATUS_INFO 104 +# define TS_F_TS_COMPUTE_IMPRINT 145 +# define TS_F_TS_CONF_INVALID 151 +# define TS_F_TS_CONF_LOAD_CERT 153 +# define TS_F_TS_CONF_LOAD_CERTS 154 +# define TS_F_TS_CONF_LOAD_KEY 155 +# define TS_F_TS_CONF_LOOKUP_FAIL 152 +# define TS_F_TS_CONF_SET_DEFAULT_ENGINE 146 +# define TS_F_TS_GET_STATUS_TEXT 105 +# define TS_F_TS_MSG_IMPRINT_SET_ALGO 118 +# define TS_F_TS_REQ_SET_MSG_IMPRINT 119 +# define TS_F_TS_REQ_SET_NONCE 120 +# define TS_F_TS_REQ_SET_POLICY_ID 121 +# define TS_F_TS_RESP_CREATE_RESPONSE 122 +# define TS_F_TS_RESP_CREATE_TST_INFO 123 +# define TS_F_TS_RESP_CTX_ADD_FAILURE_INFO 124 +# define TS_F_TS_RESP_CTX_ADD_MD 125 +# define TS_F_TS_RESP_CTX_ADD_POLICY 126 +# define TS_F_TS_RESP_CTX_NEW 127 +# define TS_F_TS_RESP_CTX_SET_ACCURACY 128 +# define TS_F_TS_RESP_CTX_SET_CERTS 129 +# define TS_F_TS_RESP_CTX_SET_DEF_POLICY 130 +# define TS_F_TS_RESP_CTX_SET_SIGNER_CERT 131 +# define TS_F_TS_RESP_CTX_SET_STATUS_INFO 132 +# define TS_F_TS_RESP_GET_POLICY 133 +# define TS_F_TS_RESP_SET_GENTIME_WITH_PRECISION 134 +# define TS_F_TS_RESP_SET_STATUS_INFO 135 +# define TS_F_TS_RESP_SET_TST_INFO 150 +# define TS_F_TS_RESP_SIGN 136 +# define TS_F_TS_RESP_VERIFY_SIGNATURE 106 +# define TS_F_TS_TST_INFO_SET_ACCURACY 137 +# define TS_F_TS_TST_INFO_SET_MSG_IMPRINT 138 +# define TS_F_TS_TST_INFO_SET_NONCE 139 +# define TS_F_TS_TST_INFO_SET_POLICY_ID 140 +# define TS_F_TS_TST_INFO_SET_SERIAL 141 +# define TS_F_TS_TST_INFO_SET_TIME 142 +# define TS_F_TS_TST_INFO_SET_TSA 143 +# define TS_F_TS_VERIFY 108 +# define TS_F_TS_VERIFY_CERT 109 +# define TS_F_TS_VERIFY_CTX_NEW 144 + +/* + * TS reason codes. + */ +# define TS_R_BAD_PKCS7_TYPE 132 +# define TS_R_BAD_TYPE 133 +# define TS_R_CANNOT_LOAD_CERT 137 +# define TS_R_CANNOT_LOAD_KEY 138 +# define TS_R_CERTIFICATE_VERIFY_ERROR 100 +# define TS_R_COULD_NOT_SET_ENGINE 127 +# define TS_R_COULD_NOT_SET_TIME 115 +# define TS_R_DETACHED_CONTENT 134 +# define TS_R_ESS_ADD_SIGNING_CERT_ERROR 116 +# define TS_R_ESS_ADD_SIGNING_CERT_V2_ERROR 139 +# define TS_R_ESS_SIGNING_CERTIFICATE_ERROR 101 +# define TS_R_INVALID_NULL_POINTER 102 +# define TS_R_INVALID_SIGNER_CERTIFICATE_PURPOSE 117 +# define TS_R_MESSAGE_IMPRINT_MISMATCH 103 +# define TS_R_NONCE_MISMATCH 104 +# define TS_R_NONCE_NOT_RETURNED 105 +# define TS_R_NO_CONTENT 106 +# define TS_R_NO_TIME_STAMP_TOKEN 107 +# define TS_R_PKCS7_ADD_SIGNATURE_ERROR 118 +# define TS_R_PKCS7_ADD_SIGNED_ATTR_ERROR 119 +# define TS_R_PKCS7_TO_TS_TST_INFO_FAILED 129 +# define TS_R_POLICY_MISMATCH 108 +# define TS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 120 +# define TS_R_RESPONSE_SETUP_ERROR 121 +# define TS_R_SIGNATURE_FAILURE 109 +# define TS_R_THERE_MUST_BE_ONE_SIGNER 110 +# define TS_R_TIME_SYSCALL_ERROR 122 +# define TS_R_TOKEN_NOT_PRESENT 130 +# define TS_R_TOKEN_PRESENT 131 +# define TS_R_TSA_NAME_MISMATCH 111 +# define TS_R_TSA_UNTRUSTED 112 +# define TS_R_TST_INFO_SETUP_ERROR 123 +# define TS_R_TS_DATASIGN 124 +# define TS_R_UNACCEPTABLE_POLICY 125 +# define TS_R_UNSUPPORTED_MD_ALGORITHM 126 +# define TS_R_UNSUPPORTED_VERSION 113 +# define TS_R_VAR_BAD_VALUE 135 +# define TS_R_VAR_LOOKUP_FAILURE 136 +# define TS_R_WRONG_CONTENT_TYPE 114 + +# endif +#endif diff --git a/thrid-party/openssl/openssl/txt_db.h b/thrid-party/openssl/openssl/txt_db.h new file mode 100644 index 0000000000..ec981a439f --- /dev/null +++ b/thrid-party/openssl/openssl/txt_db.h @@ -0,0 +1,57 @@ +/* + * Copyright 1995-2017 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_TXT_DB_H +# define HEADER_TXT_DB_H + +# include +# include +# include +# include + +# define DB_ERROR_OK 0 +# define DB_ERROR_MALLOC 1 +# define DB_ERROR_INDEX_CLASH 2 +# define DB_ERROR_INDEX_OUT_OF_RANGE 3 +# define DB_ERROR_NO_INDEX 4 +# define DB_ERROR_INSERT_INDEX_CLASH 5 +# define DB_ERROR_WRONG_NUM_FIELDS 6 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef OPENSSL_STRING *OPENSSL_PSTRING; +DEFINE_SPECIAL_STACK_OF(OPENSSL_PSTRING, OPENSSL_STRING) + +typedef struct txt_db_st { + int num_fields; + STACK_OF(OPENSSL_PSTRING) *data; + LHASH_OF(OPENSSL_STRING) **index; + int (**qual) (OPENSSL_STRING *); + long error; + long arg1; + long arg2; + OPENSSL_STRING *arg_row; +} TXT_DB; + +TXT_DB *TXT_DB_read(BIO *in, int num); +long TXT_DB_write(BIO *out, TXT_DB *db); +int TXT_DB_create_index(TXT_DB *db, int field, int (*qual) (OPENSSL_STRING *), + OPENSSL_LH_HASHFUNC hash, OPENSSL_LH_COMPFUNC cmp); +void TXT_DB_free(TXT_DB *db); +OPENSSL_STRING *TXT_DB_get_by_index(TXT_DB *db, int idx, + OPENSSL_STRING *value); +int TXT_DB_insert(TXT_DB *db, OPENSSL_STRING *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thrid-party/openssl/openssl/ui.h b/thrid-party/openssl/openssl/ui.h new file mode 100644 index 0000000000..7c721ec818 --- /dev/null +++ b/thrid-party/openssl/openssl/ui.h @@ -0,0 +1,368 @@ +/* + * Copyright 2001-2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_UI_H +# define HEADER_UI_H + +# include + +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# endif +# include +# include +# include +# include + +/* For compatibility reasons, the macro OPENSSL_NO_UI is currently retained */ +# if OPENSSL_API_COMPAT < 0x10200000L +# ifdef OPENSSL_NO_UI_CONSOLE +# define OPENSSL_NO_UI +# endif +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +/* + * All the following functions return -1 or NULL on error and in some cases + * (UI_process()) -2 if interrupted or in some other way cancelled. When + * everything is fine, they return 0, a positive value or a non-NULL pointer, + * all depending on their purpose. + */ + +/* Creators and destructor. */ +UI *UI_new(void); +UI *UI_new_method(const UI_METHOD *method); +void UI_free(UI *ui); + +/*- + The following functions are used to add strings to be printed and prompt + strings to prompt for data. The names are UI_{add,dup}__string + and UI_{add,dup}_input_boolean. + + UI_{add,dup}__string have the following meanings: + add add a text or prompt string. The pointers given to these + functions are used verbatim, no copying is done. + dup make a copy of the text or prompt string, then add the copy + to the collection of strings in the user interface. + + The function is a name for the functionality that the given + string shall be used for. It can be one of: + input use the string as data prompt. + verify use the string as verification prompt. This + is used to verify a previous input. + info use the string for informational output. + error use the string for error output. + Honestly, there's currently no difference between info and error for the + moment. + + UI_{add,dup}_input_boolean have the same semantics for "add" and "dup", + and are typically used when one wants to prompt for a yes/no response. + + All of the functions in this group take a UI and a prompt string. + The string input and verify addition functions also take a flag argument, + a buffer for the result to end up with, a minimum input size and a maximum + input size (the result buffer MUST be large enough to be able to contain + the maximum number of characters). Additionally, the verify addition + functions takes another buffer to compare the result against. + The boolean input functions take an action description string (which should + be safe to ignore if the expected user action is obvious, for example with + a dialog box with an OK button and a Cancel button), a string of acceptable + characters to mean OK and to mean Cancel. The two last strings are checked + to make sure they don't have common characters. Additionally, the same + flag argument as for the string input is taken, as well as a result buffer. + The result buffer is required to be at least one byte long. Depending on + the answer, the first character from the OK or the Cancel character strings + will be stored in the first byte of the result buffer. No NUL will be + added, so the result is *not* a string. + + On success, the all return an index of the added information. That index + is useful when retrieving results with UI_get0_result(). */ +int UI_add_input_string(UI *ui, const char *prompt, int flags, + char *result_buf, int minsize, int maxsize); +int UI_dup_input_string(UI *ui, const char *prompt, int flags, + char *result_buf, int minsize, int maxsize); +int UI_add_verify_string(UI *ui, const char *prompt, int flags, + char *result_buf, int minsize, int maxsize, + const char *test_buf); +int UI_dup_verify_string(UI *ui, const char *prompt, int flags, + char *result_buf, int minsize, int maxsize, + const char *test_buf); +int UI_add_input_boolean(UI *ui, const char *prompt, const char *action_desc, + const char *ok_chars, const char *cancel_chars, + int flags, char *result_buf); +int UI_dup_input_boolean(UI *ui, const char *prompt, const char *action_desc, + const char *ok_chars, const char *cancel_chars, + int flags, char *result_buf); +int UI_add_info_string(UI *ui, const char *text); +int UI_dup_info_string(UI *ui, const char *text); +int UI_add_error_string(UI *ui, const char *text); +int UI_dup_error_string(UI *ui, const char *text); + +/* These are the possible flags. They can be or'ed together. */ +/* Use to have echoing of input */ +# define UI_INPUT_FLAG_ECHO 0x01 +/* + * Use a default password. Where that password is found is completely up to + * the application, it might for example be in the user data set with + * UI_add_user_data(). It is not recommended to have more than one input in + * each UI being marked with this flag, or the application might get + * confused. + */ +# define UI_INPUT_FLAG_DEFAULT_PWD 0x02 + +/*- + * The user of these routines may want to define flags of their own. The core + * UI won't look at those, but will pass them on to the method routines. They + * must use higher bits so they don't get confused with the UI bits above. + * UI_INPUT_FLAG_USER_BASE tells which is the lowest bit to use. A good + * example of use is this: + * + * #define MY_UI_FLAG1 (0x01 << UI_INPUT_FLAG_USER_BASE) + * +*/ +# define UI_INPUT_FLAG_USER_BASE 16 + +/*- + * The following function helps construct a prompt. object_desc is a + * textual short description of the object, for example "pass phrase", + * and object_name is the name of the object (might be a card name or + * a file name. + * The returned string shall always be allocated on the heap with + * OPENSSL_malloc(), and need to be free'd with OPENSSL_free(). + * + * If the ui_method doesn't contain a pointer to a user-defined prompt + * constructor, a default string is built, looking like this: + * + * "Enter {object_desc} for {object_name}:" + * + * So, if object_desc has the value "pass phrase" and object_name has + * the value "foo.key", the resulting string is: + * + * "Enter pass phrase for foo.key:" +*/ +char *UI_construct_prompt(UI *ui_method, + const char *object_desc, const char *object_name); + +/* + * The following function is used to store a pointer to user-specific data. + * Any previous such pointer will be returned and replaced. + * + * For callback purposes, this function makes a lot more sense than using + * ex_data, since the latter requires that different parts of OpenSSL or + * applications share the same ex_data index. + * + * Note that the UI_OpenSSL() method completely ignores the user data. Other + * methods may not, however. + */ +void *UI_add_user_data(UI *ui, void *user_data); +/* + * Alternatively, this function is used to duplicate the user data. + * This uses the duplicator method function. The destroy function will + * be used to free the user data in this case. + */ +int UI_dup_user_data(UI *ui, void *user_data); +/* We need a user data retrieving function as well. */ +void *UI_get0_user_data(UI *ui); + +/* Return the result associated with a prompt given with the index i. */ +const char *UI_get0_result(UI *ui, int i); +int UI_get_result_length(UI *ui, int i); + +/* When all strings have been added, process the whole thing. */ +int UI_process(UI *ui); + +/* + * Give a user interface parameterised control commands. This can be used to + * send down an integer, a data pointer or a function pointer, as well as be + * used to get information from a UI. + */ +int UI_ctrl(UI *ui, int cmd, long i, void *p, void (*f) (void)); + +/* The commands */ +/* + * Use UI_CONTROL_PRINT_ERRORS with the value 1 to have UI_process print the + * OpenSSL error stack before printing any info or added error messages and + * before any prompting. + */ +# define UI_CTRL_PRINT_ERRORS 1 +/* + * Check if a UI_process() is possible to do again with the same instance of + * a user interface. This makes UI_ctrl() return 1 if it is redoable, and 0 + * if not. + */ +# define UI_CTRL_IS_REDOABLE 2 + +/* Some methods may use extra data */ +# define UI_set_app_data(s,arg) UI_set_ex_data(s,0,arg) +# define UI_get_app_data(s) UI_get_ex_data(s,0) + +# define UI_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_UI, l, p, newf, dupf, freef) +int UI_set_ex_data(UI *r, int idx, void *arg); +void *UI_get_ex_data(UI *r, int idx); + +/* Use specific methods instead of the built-in one */ +void UI_set_default_method(const UI_METHOD *meth); +const UI_METHOD *UI_get_default_method(void); +const UI_METHOD *UI_get_method(UI *ui); +const UI_METHOD *UI_set_method(UI *ui, const UI_METHOD *meth); + +# ifndef OPENSSL_NO_UI_CONSOLE + +/* The method with all the built-in thingies */ +UI_METHOD *UI_OpenSSL(void); + +# endif + +/* + * NULL method. Literally does nothing, but may serve as a placeholder + * to avoid internal default. + */ +const UI_METHOD *UI_null(void); + +/* ---------- For method writers ---------- */ +/*- + A method contains a number of functions that implement the low level + of the User Interface. The functions are: + + an opener This function starts a session, maybe by opening + a channel to a tty, or by opening a window. + a writer This function is called to write a given string, + maybe to the tty, maybe as a field label in a + window. + a flusher This function is called to flush everything that + has been output so far. It can be used to actually + display a dialog box after it has been built. + a reader This function is called to read a given prompt, + maybe from the tty, maybe from a field in a + window. Note that it's called with all string + structures, not only the prompt ones, so it must + check such things itself. + a closer This function closes the session, maybe by closing + the channel to the tty, or closing the window. + + All these functions are expected to return: + + 0 on error. + 1 on success. + -1 on out-of-band events, for example if some prompting has + been canceled (by pressing Ctrl-C, for example). This is + only checked when returned by the flusher or the reader. + + The way this is used, the opener is first called, then the writer for all + strings, then the flusher, then the reader for all strings and finally the + closer. Note that if you want to prompt from a terminal or other command + line interface, the best is to have the reader also write the prompts + instead of having the writer do it. If you want to prompt from a dialog + box, the writer can be used to build up the contents of the box, and the + flusher to actually display the box and run the event loop until all data + has been given, after which the reader only grabs the given data and puts + them back into the UI strings. + + All method functions take a UI as argument. Additionally, the writer and + the reader take a UI_STRING. +*/ + +/* + * The UI_STRING type is the data structure that contains all the needed info + * about a string or a prompt, including test data for a verification prompt. + */ +typedef struct ui_string_st UI_STRING; +DEFINE_STACK_OF(UI_STRING) + +/* + * The different types of strings that are currently supported. This is only + * needed by method authors. + */ +enum UI_string_types { + UIT_NONE = 0, + UIT_PROMPT, /* Prompt for a string */ + UIT_VERIFY, /* Prompt for a string and verify */ + UIT_BOOLEAN, /* Prompt for a yes/no response */ + UIT_INFO, /* Send info to the user */ + UIT_ERROR /* Send an error message to the user */ +}; + +/* Create and manipulate methods */ +UI_METHOD *UI_create_method(const char *name); +void UI_destroy_method(UI_METHOD *ui_method); +int UI_method_set_opener(UI_METHOD *method, int (*opener) (UI *ui)); +int UI_method_set_writer(UI_METHOD *method, + int (*writer) (UI *ui, UI_STRING *uis)); +int UI_method_set_flusher(UI_METHOD *method, int (*flusher) (UI *ui)); +int UI_method_set_reader(UI_METHOD *method, + int (*reader) (UI *ui, UI_STRING *uis)); +int UI_method_set_closer(UI_METHOD *method, int (*closer) (UI *ui)); +int UI_method_set_data_duplicator(UI_METHOD *method, + void *(*duplicator) (UI *ui, void *ui_data), + void (*destructor)(UI *ui, void *ui_data)); +int UI_method_set_prompt_constructor(UI_METHOD *method, + char *(*prompt_constructor) (UI *ui, + const char + *object_desc, + const char + *object_name)); +int UI_method_set_ex_data(UI_METHOD *method, int idx, void *data); +int (*UI_method_get_opener(const UI_METHOD *method)) (UI *); +int (*UI_method_get_writer(const UI_METHOD *method)) (UI *, UI_STRING *); +int (*UI_method_get_flusher(const UI_METHOD *method)) (UI *); +int (*UI_method_get_reader(const UI_METHOD *method)) (UI *, UI_STRING *); +int (*UI_method_get_closer(const UI_METHOD *method)) (UI *); +char *(*UI_method_get_prompt_constructor(const UI_METHOD *method)) + (UI *, const char *, const char *); +void *(*UI_method_get_data_duplicator(const UI_METHOD *method)) (UI *, void *); +void (*UI_method_get_data_destructor(const UI_METHOD *method)) (UI *, void *); +const void *UI_method_get_ex_data(const UI_METHOD *method, int idx); + +/* + * The following functions are helpers for method writers to access relevant + * data from a UI_STRING. + */ + +/* Return type of the UI_STRING */ +enum UI_string_types UI_get_string_type(UI_STRING *uis); +/* Return input flags of the UI_STRING */ +int UI_get_input_flags(UI_STRING *uis); +/* Return the actual string to output (the prompt, info or error) */ +const char *UI_get0_output_string(UI_STRING *uis); +/* + * Return the optional action string to output (the boolean prompt + * instruction) + */ +const char *UI_get0_action_string(UI_STRING *uis); +/* Return the result of a prompt */ +const char *UI_get0_result_string(UI_STRING *uis); +int UI_get_result_string_length(UI_STRING *uis); +/* + * Return the string to test the result against. Only useful with verifies. + */ +const char *UI_get0_test_string(UI_STRING *uis); +/* Return the required minimum size of the result */ +int UI_get_result_minsize(UI_STRING *uis); +/* Return the required maximum size of the result */ +int UI_get_result_maxsize(UI_STRING *uis); +/* Set the result of a UI_STRING. */ +int UI_set_result(UI *ui, UI_STRING *uis, const char *result); +int UI_set_result_ex(UI *ui, UI_STRING *uis, const char *result, int len); + +/* A couple of popular utility functions */ +int UI_UTIL_read_pw_string(char *buf, int length, const char *prompt, + int verify); +int UI_UTIL_read_pw(char *buf, char *buff, int size, const char *prompt, + int verify); +UI_METHOD *UI_UTIL_wrap_read_pem_callback(pem_password_cb *cb, int rwflag); + + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/ui_compat.h b/thrid-party/openssl/openssl/ui_compat.h new file mode 100644 index 0000000000..bf541542c0 --- /dev/null +++ b/thrid-party/openssl/openssl/ui_compat.h @@ -0,0 +1,88 @@ +/* crypto/ui/ui.h */ +/* + * Written by Richard Levitte (richard@levitte.org) for the OpenSSL project + * 2001. + */ +/* ==================================================================== + * Copyright (c) 2001 The OpenSSL Project. All rights reserved. + * + * 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. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (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 product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + +#ifndef HEADER_UI_COMPAT_H +# define HEADER_UI_COMPAT_H + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The following functions were previously part of the DES section, and are + * provided here for backward compatibility reasons. + */ + +# define des_read_pw_string(b,l,p,v) \ + _ossl_old_des_read_pw_string((b),(l),(p),(v)) +# define des_read_pw(b,bf,s,p,v) \ + _ossl_old_des_read_pw((b),(bf),(s),(p),(v)) + +int _ossl_old_des_read_pw_string(char *buf, int length, const char *prompt, + int verify); +int _ossl_old_des_read_pw(char *buf, char *buff, int size, const char *prompt, + int verify); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/uierr.h b/thrid-party/openssl/openssl/uierr.h new file mode 100644 index 0000000000..bd68864d0d --- /dev/null +++ b/thrid-party/openssl/openssl/uierr.h @@ -0,0 +1,65 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_UIERR_H +# define HEADER_UIERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_UI_strings(void); + +/* + * UI function codes. + */ +# define UI_F_CLOSE_CONSOLE 115 +# define UI_F_ECHO_CONSOLE 116 +# define UI_F_GENERAL_ALLOCATE_BOOLEAN 108 +# define UI_F_GENERAL_ALLOCATE_PROMPT 109 +# define UI_F_NOECHO_CONSOLE 117 +# define UI_F_OPEN_CONSOLE 114 +# define UI_F_UI_CONSTRUCT_PROMPT 121 +# define UI_F_UI_CREATE_METHOD 112 +# define UI_F_UI_CTRL 111 +# define UI_F_UI_DUP_ERROR_STRING 101 +# define UI_F_UI_DUP_INFO_STRING 102 +# define UI_F_UI_DUP_INPUT_BOOLEAN 110 +# define UI_F_UI_DUP_INPUT_STRING 103 +# define UI_F_UI_DUP_USER_DATA 118 +# define UI_F_UI_DUP_VERIFY_STRING 106 +# define UI_F_UI_GET0_RESULT 107 +# define UI_F_UI_GET_RESULT_LENGTH 119 +# define UI_F_UI_NEW_METHOD 104 +# define UI_F_UI_PROCESS 113 +# define UI_F_UI_SET_RESULT 105 +# define UI_F_UI_SET_RESULT_EX 120 + +/* + * UI reason codes. + */ +# define UI_R_COMMON_OK_AND_CANCEL_CHARACTERS 104 +# define UI_R_INDEX_TOO_LARGE 102 +# define UI_R_INDEX_TOO_SMALL 103 +# define UI_R_NO_RESULT_BUFFER 105 +# define UI_R_PROCESSING_ERROR 107 +# define UI_R_RESULT_TOO_LARGE 100 +# define UI_R_RESULT_TOO_SMALL 101 +# define UI_R_SYSASSIGN_ERROR 109 +# define UI_R_SYSDASSGN_ERROR 110 +# define UI_R_SYSQIOW_ERROR 111 +# define UI_R_UNKNOWN_CONTROL_COMMAND 106 +# define UI_R_UNKNOWN_TTYGET_ERRNO_VALUE 108 +# define UI_R_USER_DATA_DUPLICATION_UNSUPPORTED 112 + +#endif diff --git a/thrid-party/openssl/openssl/whrlpool.h b/thrid-party/openssl/openssl/whrlpool.h new file mode 100644 index 0000000000..20ea3503b7 --- /dev/null +++ b/thrid-party/openssl/openssl/whrlpool.h @@ -0,0 +1,48 @@ +/* + * Copyright 2005-2016 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_WHRLPOOL_H +# define HEADER_WHRLPOOL_H + +#include + +# ifndef OPENSSL_NO_WHIRLPOOL +# include +# include +# ifdef __cplusplus +extern "C" { +# endif + +# define WHIRLPOOL_DIGEST_LENGTH (512/8) +# define WHIRLPOOL_BBLOCK 512 +# define WHIRLPOOL_COUNTER (256/8) + +typedef struct { + union { + unsigned char c[WHIRLPOOL_DIGEST_LENGTH]; + /* double q is here to ensure 64-bit alignment */ + double q[WHIRLPOOL_DIGEST_LENGTH / sizeof(double)]; + } H; + unsigned char data[WHIRLPOOL_BBLOCK / 8]; + unsigned int bitoff; + size_t bitlen[WHIRLPOOL_COUNTER / sizeof(size_t)]; +} WHIRLPOOL_CTX; + +int WHIRLPOOL_Init(WHIRLPOOL_CTX *c); +int WHIRLPOOL_Update(WHIRLPOOL_CTX *c, const void *inp, size_t bytes); +void WHIRLPOOL_BitUpdate(WHIRLPOOL_CTX *c, const void *inp, size_t bits); +int WHIRLPOOL_Final(unsigned char *md, WHIRLPOOL_CTX *c); +unsigned char *WHIRLPOOL(const void *inp, size_t bytes, unsigned char *md); + +# ifdef __cplusplus +} +# endif +# endif + +#endif diff --git a/thrid-party/openssl/openssl/x509.h b/thrid-party/openssl/openssl/x509.h new file mode 100644 index 0000000000..39ca0ba575 --- /dev/null +++ b/thrid-party/openssl/openssl/x509.h @@ -0,0 +1,1047 @@ +/* + * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved. + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_X509_H +# define HEADER_X509_H + +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# if OPENSSL_API_COMPAT < 0x10100000L +# include +# include +# include +# endif + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Flags for X509_get_signature_info() */ +/* Signature info is valid */ +# define X509_SIG_INFO_VALID 0x1 +/* Signature is suitable for TLS use */ +# define X509_SIG_INFO_TLS 0x2 + +# define X509_FILETYPE_PEM 1 +# define X509_FILETYPE_ASN1 2 +# define X509_FILETYPE_DEFAULT 3 + +# define X509v3_KU_DIGITAL_SIGNATURE 0x0080 +# define X509v3_KU_NON_REPUDIATION 0x0040 +# define X509v3_KU_KEY_ENCIPHERMENT 0x0020 +# define X509v3_KU_DATA_ENCIPHERMENT 0x0010 +# define X509v3_KU_KEY_AGREEMENT 0x0008 +# define X509v3_KU_KEY_CERT_SIGN 0x0004 +# define X509v3_KU_CRL_SIGN 0x0002 +# define X509v3_KU_ENCIPHER_ONLY 0x0001 +# define X509v3_KU_DECIPHER_ONLY 0x8000 +# define X509v3_KU_UNDEF 0xffff + +struct X509_algor_st { + ASN1_OBJECT *algorithm; + ASN1_TYPE *parameter; +} /* X509_ALGOR */ ; + +typedef STACK_OF(X509_ALGOR) X509_ALGORS; + +typedef struct X509_val_st { + ASN1_TIME *notBefore; + ASN1_TIME *notAfter; +} X509_VAL; + +typedef struct X509_sig_st X509_SIG; + +typedef struct X509_name_entry_st X509_NAME_ENTRY; + +DEFINE_STACK_OF(X509_NAME_ENTRY) + +DEFINE_STACK_OF(X509_NAME) + +# define X509_EX_V_NETSCAPE_HACK 0x8000 +# define X509_EX_V_INIT 0x0001 +typedef struct X509_extension_st X509_EXTENSION; + +typedef STACK_OF(X509_EXTENSION) X509_EXTENSIONS; + +DEFINE_STACK_OF(X509_EXTENSION) + +typedef struct x509_attributes_st X509_ATTRIBUTE; + +DEFINE_STACK_OF(X509_ATTRIBUTE) + +typedef struct X509_req_info_st X509_REQ_INFO; + +typedef struct X509_req_st X509_REQ; + +typedef struct x509_cert_aux_st X509_CERT_AUX; + +typedef struct x509_cinf_st X509_CINF; + +DEFINE_STACK_OF(X509) + +/* This is used for a table of trust checking functions */ + +typedef struct x509_trust_st { + int trust; + int flags; + int (*check_trust) (struct x509_trust_st *, X509 *, int); + char *name; + int arg1; + void *arg2; +} X509_TRUST; + +DEFINE_STACK_OF(X509_TRUST) + +/* standard trust ids */ + +# define X509_TRUST_DEFAULT 0 /* Only valid in purpose settings */ + +# define X509_TRUST_COMPAT 1 +# define X509_TRUST_SSL_CLIENT 2 +# define X509_TRUST_SSL_SERVER 3 +# define X509_TRUST_EMAIL 4 +# define X509_TRUST_OBJECT_SIGN 5 +# define X509_TRUST_OCSP_SIGN 6 +# define X509_TRUST_OCSP_REQUEST 7 +# define X509_TRUST_TSA 8 + +/* Keep these up to date! */ +# define X509_TRUST_MIN 1 +# define X509_TRUST_MAX 8 + +/* trust_flags values */ +# define X509_TRUST_DYNAMIC (1U << 0) +# define X509_TRUST_DYNAMIC_NAME (1U << 1) +/* No compat trust if self-signed, preempts "DO_SS" */ +# define X509_TRUST_NO_SS_COMPAT (1U << 2) +/* Compat trust if no explicit accepted trust EKUs */ +# define X509_TRUST_DO_SS_COMPAT (1U << 3) +/* Accept "anyEKU" as a wildcard trust OID */ +# define X509_TRUST_OK_ANY_EKU (1U << 4) + +/* check_trust return codes */ + +# define X509_TRUST_TRUSTED 1 +# define X509_TRUST_REJECTED 2 +# define X509_TRUST_UNTRUSTED 3 + +/* Flags for X509_print_ex() */ + +# define X509_FLAG_COMPAT 0 +# define X509_FLAG_NO_HEADER 1L +# define X509_FLAG_NO_VERSION (1L << 1) +# define X509_FLAG_NO_SERIAL (1L << 2) +# define X509_FLAG_NO_SIGNAME (1L << 3) +# define X509_FLAG_NO_ISSUER (1L << 4) +# define X509_FLAG_NO_VALIDITY (1L << 5) +# define X509_FLAG_NO_SUBJECT (1L << 6) +# define X509_FLAG_NO_PUBKEY (1L << 7) +# define X509_FLAG_NO_EXTENSIONS (1L << 8) +# define X509_FLAG_NO_SIGDUMP (1L << 9) +# define X509_FLAG_NO_AUX (1L << 10) +# define X509_FLAG_NO_ATTRIBUTES (1L << 11) +# define X509_FLAG_NO_IDS (1L << 12) + +/* Flags specific to X509_NAME_print_ex() */ + +/* The field separator information */ + +# define XN_FLAG_SEP_MASK (0xf << 16) + +# define XN_FLAG_COMPAT 0/* Traditional; use old X509_NAME_print */ +# define XN_FLAG_SEP_COMMA_PLUS (1 << 16)/* RFC2253 ,+ */ +# define XN_FLAG_SEP_CPLUS_SPC (2 << 16)/* ,+ spaced: more readable */ +# define XN_FLAG_SEP_SPLUS_SPC (3 << 16)/* ;+ spaced */ +# define XN_FLAG_SEP_MULTILINE (4 << 16)/* One line per field */ + +# define XN_FLAG_DN_REV (1 << 20)/* Reverse DN order */ + +/* How the field name is shown */ + +# define XN_FLAG_FN_MASK (0x3 << 21) + +# define XN_FLAG_FN_SN 0/* Object short name */ +# define XN_FLAG_FN_LN (1 << 21)/* Object long name */ +# define XN_FLAG_FN_OID (2 << 21)/* Always use OIDs */ +# define XN_FLAG_FN_NONE (3 << 21)/* No field names */ + +# define XN_FLAG_SPC_EQ (1 << 23)/* Put spaces round '=' */ + +/* + * This determines if we dump fields we don't recognise: RFC2253 requires + * this. + */ + +# define XN_FLAG_DUMP_UNKNOWN_FIELDS (1 << 24) + +# define XN_FLAG_FN_ALIGN (1 << 25)/* Align field names to 20 + * characters */ + +/* Complete set of RFC2253 flags */ + +# define XN_FLAG_RFC2253 (ASN1_STRFLGS_RFC2253 | \ + XN_FLAG_SEP_COMMA_PLUS | \ + XN_FLAG_DN_REV | \ + XN_FLAG_FN_SN | \ + XN_FLAG_DUMP_UNKNOWN_FIELDS) + +/* readable oneline form */ + +# define XN_FLAG_ONELINE (ASN1_STRFLGS_RFC2253 | \ + ASN1_STRFLGS_ESC_QUOTE | \ + XN_FLAG_SEP_CPLUS_SPC | \ + XN_FLAG_SPC_EQ | \ + XN_FLAG_FN_SN) + +/* readable multiline form */ + +# define XN_FLAG_MULTILINE (ASN1_STRFLGS_ESC_CTRL | \ + ASN1_STRFLGS_ESC_MSB | \ + XN_FLAG_SEP_MULTILINE | \ + XN_FLAG_SPC_EQ | \ + XN_FLAG_FN_LN | \ + XN_FLAG_FN_ALIGN) + +DEFINE_STACK_OF(X509_REVOKED) + +typedef struct X509_crl_info_st X509_CRL_INFO; + +DEFINE_STACK_OF(X509_CRL) + +typedef struct private_key_st { + int version; + /* The PKCS#8 data types */ + X509_ALGOR *enc_algor; + ASN1_OCTET_STRING *enc_pkey; /* encrypted pub key */ + /* When decrypted, the following will not be NULL */ + EVP_PKEY *dec_pkey; + /* used to encrypt and decrypt */ + int key_length; + char *key_data; + int key_free; /* true if we should auto free key_data */ + /* expanded version of 'enc_algor' */ + EVP_CIPHER_INFO cipher; +} X509_PKEY; + +typedef struct X509_info_st { + X509 *x509; + X509_CRL *crl; + X509_PKEY *x_pkey; + EVP_CIPHER_INFO enc_cipher; + int enc_len; + char *enc_data; +} X509_INFO; + +DEFINE_STACK_OF(X509_INFO) + +/* + * The next 2 structures and their 8 routines are used to manipulate Netscape's + * spki structures - useful if you are writing a CA web page + */ +typedef struct Netscape_spkac_st { + X509_PUBKEY *pubkey; + ASN1_IA5STRING *challenge; /* challenge sent in atlas >= PR2 */ +} NETSCAPE_SPKAC; + +typedef struct Netscape_spki_st { + NETSCAPE_SPKAC *spkac; /* signed public key and challenge */ + X509_ALGOR sig_algor; + ASN1_BIT_STRING *signature; +} NETSCAPE_SPKI; + +/* Netscape certificate sequence structure */ +typedef struct Netscape_certificate_sequence { + ASN1_OBJECT *type; + STACK_OF(X509) *certs; +} NETSCAPE_CERT_SEQUENCE; + +/*- Unused (and iv length is wrong) +typedef struct CBCParameter_st + { + unsigned char iv[8]; + } CBC_PARAM; +*/ + +/* Password based encryption structure */ + +typedef struct PBEPARAM_st { + ASN1_OCTET_STRING *salt; + ASN1_INTEGER *iter; +} PBEPARAM; + +/* Password based encryption V2 structures */ + +typedef struct PBE2PARAM_st { + X509_ALGOR *keyfunc; + X509_ALGOR *encryption; +} PBE2PARAM; + +typedef struct PBKDF2PARAM_st { +/* Usually OCTET STRING but could be anything */ + ASN1_TYPE *salt; + ASN1_INTEGER *iter; + ASN1_INTEGER *keylength; + X509_ALGOR *prf; +} PBKDF2PARAM; + +#ifndef OPENSSL_NO_SCRYPT +typedef struct SCRYPT_PARAMS_st { + ASN1_OCTET_STRING *salt; + ASN1_INTEGER *costParameter; + ASN1_INTEGER *blockSize; + ASN1_INTEGER *parallelizationParameter; + ASN1_INTEGER *keyLength; +} SCRYPT_PARAMS; +#endif + +#ifdef __cplusplus +} +#endif + +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +# define X509_EXT_PACK_UNKNOWN 1 +# define X509_EXT_PACK_STRING 2 + +# define X509_extract_key(x) X509_get_pubkey(x)/*****/ +# define X509_REQ_extract_key(a) X509_REQ_get_pubkey(a) +# define X509_name_cmp(a,b) X509_NAME_cmp((a),(b)) + +void X509_CRL_set_default_method(const X509_CRL_METHOD *meth); +X509_CRL_METHOD *X509_CRL_METHOD_new(int (*crl_init) (X509_CRL *crl), + int (*crl_free) (X509_CRL *crl), + int (*crl_lookup) (X509_CRL *crl, + X509_REVOKED **ret, + ASN1_INTEGER *ser, + X509_NAME *issuer), + int (*crl_verify) (X509_CRL *crl, + EVP_PKEY *pk)); +void X509_CRL_METHOD_free(X509_CRL_METHOD *m); + +void X509_CRL_set_meth_data(X509_CRL *crl, void *dat); +void *X509_CRL_get_meth_data(X509_CRL *crl); + +const char *X509_verify_cert_error_string(long n); + +int X509_verify(X509 *a, EVP_PKEY *r); + +int X509_REQ_verify(X509_REQ *a, EVP_PKEY *r); +int X509_CRL_verify(X509_CRL *a, EVP_PKEY *r); +int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *a, EVP_PKEY *r); + +NETSCAPE_SPKI *NETSCAPE_SPKI_b64_decode(const char *str, int len); +char *NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *x); +EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *x); +int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *x, EVP_PKEY *pkey); + +int NETSCAPE_SPKI_print(BIO *out, NETSCAPE_SPKI *spki); + +int X509_signature_dump(BIO *bp, const ASN1_STRING *sig, int indent); +int X509_signature_print(BIO *bp, const X509_ALGOR *alg, + const ASN1_STRING *sig); + +int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); +int X509_sign_ctx(X509 *x, EVP_MD_CTX *ctx); +# ifndef OPENSSL_NO_OCSP +int X509_http_nbio(OCSP_REQ_CTX *rctx, X509 **pcert); +# endif +int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md); +int X509_REQ_sign_ctx(X509_REQ *x, EVP_MD_CTX *ctx); +int X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const EVP_MD *md); +int X509_CRL_sign_ctx(X509_CRL *x, EVP_MD_CTX *ctx); +# ifndef OPENSSL_NO_OCSP +int X509_CRL_http_nbio(OCSP_REQ_CTX *rctx, X509_CRL **pcrl); +# endif +int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *x, EVP_PKEY *pkey, const EVP_MD *md); + +int X509_pubkey_digest(const X509 *data, const EVP_MD *type, + unsigned char *md, unsigned int *len); +int X509_digest(const X509 *data, const EVP_MD *type, + unsigned char *md, unsigned int *len); +int X509_CRL_digest(const X509_CRL *data, const EVP_MD *type, + unsigned char *md, unsigned int *len); +int X509_REQ_digest(const X509_REQ *data, const EVP_MD *type, + unsigned char *md, unsigned int *len); +int X509_NAME_digest(const X509_NAME *data, const EVP_MD *type, + unsigned char *md, unsigned int *len); + +# ifndef OPENSSL_NO_STDIO +X509 *d2i_X509_fp(FILE *fp, X509 **x509); +int i2d_X509_fp(FILE *fp, X509 *x509); +X509_CRL *d2i_X509_CRL_fp(FILE *fp, X509_CRL **crl); +int i2d_X509_CRL_fp(FILE *fp, X509_CRL *crl); +X509_REQ *d2i_X509_REQ_fp(FILE *fp, X509_REQ **req); +int i2d_X509_REQ_fp(FILE *fp, X509_REQ *req); +# ifndef OPENSSL_NO_RSA +RSA *d2i_RSAPrivateKey_fp(FILE *fp, RSA **rsa); +int i2d_RSAPrivateKey_fp(FILE *fp, RSA *rsa); +RSA *d2i_RSAPublicKey_fp(FILE *fp, RSA **rsa); +int i2d_RSAPublicKey_fp(FILE *fp, RSA *rsa); +RSA *d2i_RSA_PUBKEY_fp(FILE *fp, RSA **rsa); +int i2d_RSA_PUBKEY_fp(FILE *fp, RSA *rsa); +# endif +# ifndef OPENSSL_NO_DSA +DSA *d2i_DSA_PUBKEY_fp(FILE *fp, DSA **dsa); +int i2d_DSA_PUBKEY_fp(FILE *fp, DSA *dsa); +DSA *d2i_DSAPrivateKey_fp(FILE *fp, DSA **dsa); +int i2d_DSAPrivateKey_fp(FILE *fp, DSA *dsa); +# endif +# ifndef OPENSSL_NO_EC +EC_KEY *d2i_EC_PUBKEY_fp(FILE *fp, EC_KEY **eckey); +int i2d_EC_PUBKEY_fp(FILE *fp, EC_KEY *eckey); +EC_KEY *d2i_ECPrivateKey_fp(FILE *fp, EC_KEY **eckey); +int i2d_ECPrivateKey_fp(FILE *fp, EC_KEY *eckey); +# endif +X509_SIG *d2i_PKCS8_fp(FILE *fp, X509_SIG **p8); +int i2d_PKCS8_fp(FILE *fp, X509_SIG *p8); +PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_fp(FILE *fp, + PKCS8_PRIV_KEY_INFO **p8inf); +int i2d_PKCS8_PRIV_KEY_INFO_fp(FILE *fp, PKCS8_PRIV_KEY_INFO *p8inf); +int i2d_PKCS8PrivateKeyInfo_fp(FILE *fp, EVP_PKEY *key); +int i2d_PrivateKey_fp(FILE *fp, EVP_PKEY *pkey); +EVP_PKEY *d2i_PrivateKey_fp(FILE *fp, EVP_PKEY **a); +int i2d_PUBKEY_fp(FILE *fp, EVP_PKEY *pkey); +EVP_PKEY *d2i_PUBKEY_fp(FILE *fp, EVP_PKEY **a); +# endif + +X509 *d2i_X509_bio(BIO *bp, X509 **x509); +int i2d_X509_bio(BIO *bp, X509 *x509); +X509_CRL *d2i_X509_CRL_bio(BIO *bp, X509_CRL **crl); +int i2d_X509_CRL_bio(BIO *bp, X509_CRL *crl); +X509_REQ *d2i_X509_REQ_bio(BIO *bp, X509_REQ **req); +int i2d_X509_REQ_bio(BIO *bp, X509_REQ *req); +# ifndef OPENSSL_NO_RSA +RSA *d2i_RSAPrivateKey_bio(BIO *bp, RSA **rsa); +int i2d_RSAPrivateKey_bio(BIO *bp, RSA *rsa); +RSA *d2i_RSAPublicKey_bio(BIO *bp, RSA **rsa); +int i2d_RSAPublicKey_bio(BIO *bp, RSA *rsa); +RSA *d2i_RSA_PUBKEY_bio(BIO *bp, RSA **rsa); +int i2d_RSA_PUBKEY_bio(BIO *bp, RSA *rsa); +# endif +# ifndef OPENSSL_NO_DSA +DSA *d2i_DSA_PUBKEY_bio(BIO *bp, DSA **dsa); +int i2d_DSA_PUBKEY_bio(BIO *bp, DSA *dsa); +DSA *d2i_DSAPrivateKey_bio(BIO *bp, DSA **dsa); +int i2d_DSAPrivateKey_bio(BIO *bp, DSA *dsa); +# endif +# ifndef OPENSSL_NO_EC +EC_KEY *d2i_EC_PUBKEY_bio(BIO *bp, EC_KEY **eckey); +int i2d_EC_PUBKEY_bio(BIO *bp, EC_KEY *eckey); +EC_KEY *d2i_ECPrivateKey_bio(BIO *bp, EC_KEY **eckey); +int i2d_ECPrivateKey_bio(BIO *bp, EC_KEY *eckey); +# endif +X509_SIG *d2i_PKCS8_bio(BIO *bp, X509_SIG **p8); +int i2d_PKCS8_bio(BIO *bp, X509_SIG *p8); +PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_bio(BIO *bp, + PKCS8_PRIV_KEY_INFO **p8inf); +int i2d_PKCS8_PRIV_KEY_INFO_bio(BIO *bp, PKCS8_PRIV_KEY_INFO *p8inf); +int i2d_PKCS8PrivateKeyInfo_bio(BIO *bp, EVP_PKEY *key); +int i2d_PrivateKey_bio(BIO *bp, EVP_PKEY *pkey); +EVP_PKEY *d2i_PrivateKey_bio(BIO *bp, EVP_PKEY **a); +int i2d_PUBKEY_bio(BIO *bp, EVP_PKEY *pkey); +EVP_PKEY *d2i_PUBKEY_bio(BIO *bp, EVP_PKEY **a); + +X509 *X509_dup(X509 *x509); +X509_ATTRIBUTE *X509_ATTRIBUTE_dup(X509_ATTRIBUTE *xa); +X509_EXTENSION *X509_EXTENSION_dup(X509_EXTENSION *ex); +X509_CRL *X509_CRL_dup(X509_CRL *crl); +X509_REVOKED *X509_REVOKED_dup(X509_REVOKED *rev); +X509_REQ *X509_REQ_dup(X509_REQ *req); +X509_ALGOR *X509_ALGOR_dup(X509_ALGOR *xn); +int X509_ALGOR_set0(X509_ALGOR *alg, ASN1_OBJECT *aobj, int ptype, + void *pval); +void X509_ALGOR_get0(const ASN1_OBJECT **paobj, int *pptype, + const void **ppval, const X509_ALGOR *algor); +void X509_ALGOR_set_md(X509_ALGOR *alg, const EVP_MD *md); +int X509_ALGOR_cmp(const X509_ALGOR *a, const X509_ALGOR *b); + +X509_NAME *X509_NAME_dup(X509_NAME *xn); +X509_NAME_ENTRY *X509_NAME_ENTRY_dup(X509_NAME_ENTRY *ne); + +int X509_cmp_time(const ASN1_TIME *s, time_t *t); +int X509_cmp_current_time(const ASN1_TIME *s); +ASN1_TIME *X509_time_adj(ASN1_TIME *s, long adj, time_t *t); +ASN1_TIME *X509_time_adj_ex(ASN1_TIME *s, + int offset_day, long offset_sec, time_t *t); +ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj); + +const char *X509_get_default_cert_area(void); +const char *X509_get_default_cert_dir(void); +const char *X509_get_default_cert_file(void); +const char *X509_get_default_cert_dir_env(void); +const char *X509_get_default_cert_file_env(void); +const char *X509_get_default_private_dir(void); + +X509_REQ *X509_to_X509_REQ(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); +X509 *X509_REQ_to_X509(X509_REQ *r, int days, EVP_PKEY *pkey); + +DECLARE_ASN1_FUNCTIONS(X509_ALGOR) +DECLARE_ASN1_ENCODE_FUNCTIONS(X509_ALGORS, X509_ALGORS, X509_ALGORS) +DECLARE_ASN1_FUNCTIONS(X509_VAL) + +DECLARE_ASN1_FUNCTIONS(X509_PUBKEY) + +int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey); +EVP_PKEY *X509_PUBKEY_get0(X509_PUBKEY *key); +EVP_PKEY *X509_PUBKEY_get(X509_PUBKEY *key); +int X509_get_pubkey_parameters(EVP_PKEY *pkey, STACK_OF(X509) *chain); +long X509_get_pathlen(X509 *x); +int i2d_PUBKEY(EVP_PKEY *a, unsigned char **pp); +EVP_PKEY *d2i_PUBKEY(EVP_PKEY **a, const unsigned char **pp, long length); +# ifndef OPENSSL_NO_RSA +int i2d_RSA_PUBKEY(RSA *a, unsigned char **pp); +RSA *d2i_RSA_PUBKEY(RSA **a, const unsigned char **pp, long length); +# endif +# ifndef OPENSSL_NO_DSA +int i2d_DSA_PUBKEY(DSA *a, unsigned char **pp); +DSA *d2i_DSA_PUBKEY(DSA **a, const unsigned char **pp, long length); +# endif +# ifndef OPENSSL_NO_EC +int i2d_EC_PUBKEY(EC_KEY *a, unsigned char **pp); +EC_KEY *d2i_EC_PUBKEY(EC_KEY **a, const unsigned char **pp, long length); +# endif + +DECLARE_ASN1_FUNCTIONS(X509_SIG) +void X509_SIG_get0(const X509_SIG *sig, const X509_ALGOR **palg, + const ASN1_OCTET_STRING **pdigest); +void X509_SIG_getm(X509_SIG *sig, X509_ALGOR **palg, + ASN1_OCTET_STRING **pdigest); + +DECLARE_ASN1_FUNCTIONS(X509_REQ_INFO) +DECLARE_ASN1_FUNCTIONS(X509_REQ) + +DECLARE_ASN1_FUNCTIONS(X509_ATTRIBUTE) +X509_ATTRIBUTE *X509_ATTRIBUTE_create(int nid, int atrtype, void *value); + +DECLARE_ASN1_FUNCTIONS(X509_EXTENSION) +DECLARE_ASN1_ENCODE_FUNCTIONS(X509_EXTENSIONS, X509_EXTENSIONS, X509_EXTENSIONS) + +DECLARE_ASN1_FUNCTIONS(X509_NAME_ENTRY) + +DECLARE_ASN1_FUNCTIONS(X509_NAME) + +int X509_NAME_set(X509_NAME **xn, X509_NAME *name); + +DECLARE_ASN1_FUNCTIONS(X509_CINF) + +DECLARE_ASN1_FUNCTIONS(X509) +DECLARE_ASN1_FUNCTIONS(X509_CERT_AUX) + +#define X509_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_X509, l, p, newf, dupf, freef) +int X509_set_ex_data(X509 *r, int idx, void *arg); +void *X509_get_ex_data(X509 *r, int idx); +int i2d_X509_AUX(X509 *a, unsigned char **pp); +X509 *d2i_X509_AUX(X509 **a, const unsigned char **pp, long length); + +int i2d_re_X509_tbs(X509 *x, unsigned char **pp); + +int X509_SIG_INFO_get(const X509_SIG_INFO *siginf, int *mdnid, int *pknid, + int *secbits, uint32_t *flags); +void X509_SIG_INFO_set(X509_SIG_INFO *siginf, int mdnid, int pknid, + int secbits, uint32_t flags); + +int X509_get_signature_info(X509 *x, int *mdnid, int *pknid, int *secbits, + uint32_t *flags); + +void X509_get0_signature(const ASN1_BIT_STRING **psig, + const X509_ALGOR **palg, const X509 *x); +int X509_get_signature_nid(const X509 *x); + +int X509_trusted(const X509 *x); +int X509_alias_set1(X509 *x, const unsigned char *name, int len); +int X509_keyid_set1(X509 *x, const unsigned char *id, int len); +unsigned char *X509_alias_get0(X509 *x, int *len); +unsigned char *X509_keyid_get0(X509 *x, int *len); +int (*X509_TRUST_set_default(int (*trust) (int, X509 *, int))) (int, X509 *, + int); +int X509_TRUST_set(int *t, int trust); +int X509_add1_trust_object(X509 *x, const ASN1_OBJECT *obj); +int X509_add1_reject_object(X509 *x, const ASN1_OBJECT *obj); +void X509_trust_clear(X509 *x); +void X509_reject_clear(X509 *x); + +STACK_OF(ASN1_OBJECT) *X509_get0_trust_objects(X509 *x); +STACK_OF(ASN1_OBJECT) *X509_get0_reject_objects(X509 *x); + +DECLARE_ASN1_FUNCTIONS(X509_REVOKED) +DECLARE_ASN1_FUNCTIONS(X509_CRL_INFO) +DECLARE_ASN1_FUNCTIONS(X509_CRL) + +int X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev); +int X509_CRL_get0_by_serial(X509_CRL *crl, + X509_REVOKED **ret, ASN1_INTEGER *serial); +int X509_CRL_get0_by_cert(X509_CRL *crl, X509_REVOKED **ret, X509 *x); + +X509_PKEY *X509_PKEY_new(void); +void X509_PKEY_free(X509_PKEY *a); + +DECLARE_ASN1_FUNCTIONS(NETSCAPE_SPKI) +DECLARE_ASN1_FUNCTIONS(NETSCAPE_SPKAC) +DECLARE_ASN1_FUNCTIONS(NETSCAPE_CERT_SEQUENCE) + +X509_INFO *X509_INFO_new(void); +void X509_INFO_free(X509_INFO *a); +char *X509_NAME_oneline(const X509_NAME *a, char *buf, int size); + +int ASN1_verify(i2d_of_void *i2d, X509_ALGOR *algor1, + ASN1_BIT_STRING *signature, char *data, EVP_PKEY *pkey); + +int ASN1_digest(i2d_of_void *i2d, const EVP_MD *type, char *data, + unsigned char *md, unsigned int *len); + +int ASN1_sign(i2d_of_void *i2d, X509_ALGOR *algor1, + X509_ALGOR *algor2, ASN1_BIT_STRING *signature, + char *data, EVP_PKEY *pkey, const EVP_MD *type); + +int ASN1_item_digest(const ASN1_ITEM *it, const EVP_MD *type, void *data, + unsigned char *md, unsigned int *len); + +int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *algor1, + ASN1_BIT_STRING *signature, void *data, EVP_PKEY *pkey); + +int ASN1_item_sign(const ASN1_ITEM *it, X509_ALGOR *algor1, + X509_ALGOR *algor2, ASN1_BIT_STRING *signature, void *data, + EVP_PKEY *pkey, const EVP_MD *type); +int ASN1_item_sign_ctx(const ASN1_ITEM *it, X509_ALGOR *algor1, + X509_ALGOR *algor2, ASN1_BIT_STRING *signature, + void *asn, EVP_MD_CTX *ctx); + +long X509_get_version(const X509 *x); +int X509_set_version(X509 *x, long version); +int X509_set_serialNumber(X509 *x, ASN1_INTEGER *serial); +ASN1_INTEGER *X509_get_serialNumber(X509 *x); +const ASN1_INTEGER *X509_get0_serialNumber(const X509 *x); +int X509_set_issuer_name(X509 *x, X509_NAME *name); +X509_NAME *X509_get_issuer_name(const X509 *a); +int X509_set_subject_name(X509 *x, X509_NAME *name); +X509_NAME *X509_get_subject_name(const X509 *a); +const ASN1_TIME * X509_get0_notBefore(const X509 *x); +ASN1_TIME *X509_getm_notBefore(const X509 *x); +int X509_set1_notBefore(X509 *x, const ASN1_TIME *tm); +const ASN1_TIME *X509_get0_notAfter(const X509 *x); +ASN1_TIME *X509_getm_notAfter(const X509 *x); +int X509_set1_notAfter(X509 *x, const ASN1_TIME *tm); +int X509_set_pubkey(X509 *x, EVP_PKEY *pkey); +int X509_up_ref(X509 *x); +int X509_get_signature_type(const X509 *x); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define X509_get_notBefore X509_getm_notBefore +# define X509_get_notAfter X509_getm_notAfter +# define X509_set_notBefore X509_set1_notBefore +# define X509_set_notAfter X509_set1_notAfter +#endif + + +/* + * This one is only used so that a binary form can output, as in + * i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x), &buf) + */ +X509_PUBKEY *X509_get_X509_PUBKEY(const X509 *x); +const STACK_OF(X509_EXTENSION) *X509_get0_extensions(const X509 *x); +void X509_get0_uids(const X509 *x, const ASN1_BIT_STRING **piuid, + const ASN1_BIT_STRING **psuid); +const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x); + +EVP_PKEY *X509_get0_pubkey(const X509 *x); +EVP_PKEY *X509_get_pubkey(X509 *x); +ASN1_BIT_STRING *X509_get0_pubkey_bitstr(const X509 *x); +int X509_certificate_type(const X509 *x, const EVP_PKEY *pubkey); + +long X509_REQ_get_version(const X509_REQ *req); +int X509_REQ_set_version(X509_REQ *x, long version); +X509_NAME *X509_REQ_get_subject_name(const X509_REQ *req); +int X509_REQ_set_subject_name(X509_REQ *req, X509_NAME *name); +void X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig, + const X509_ALGOR **palg); +int X509_REQ_get_signature_nid(const X509_REQ *req); +int i2d_re_X509_REQ_tbs(X509_REQ *req, unsigned char **pp); +int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey); +EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *req); +EVP_PKEY *X509_REQ_get0_pubkey(X509_REQ *req); +X509_PUBKEY *X509_REQ_get_X509_PUBKEY(X509_REQ *req); +int X509_REQ_extension_nid(int nid); +int *X509_REQ_get_extension_nids(void); +void X509_REQ_set_extension_nids(int *nids); +STACK_OF(X509_EXTENSION) *X509_REQ_get_extensions(X509_REQ *req); +int X509_REQ_add_extensions_nid(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts, + int nid); +int X509_REQ_add_extensions(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts); +int X509_REQ_get_attr_count(const X509_REQ *req); +int X509_REQ_get_attr_by_NID(const X509_REQ *req, int nid, int lastpos); +int X509_REQ_get_attr_by_OBJ(const X509_REQ *req, const ASN1_OBJECT *obj, + int lastpos); +X509_ATTRIBUTE *X509_REQ_get_attr(const X509_REQ *req, int loc); +X509_ATTRIBUTE *X509_REQ_delete_attr(X509_REQ *req, int loc); +int X509_REQ_add1_attr(X509_REQ *req, X509_ATTRIBUTE *attr); +int X509_REQ_add1_attr_by_OBJ(X509_REQ *req, + const ASN1_OBJECT *obj, int type, + const unsigned char *bytes, int len); +int X509_REQ_add1_attr_by_NID(X509_REQ *req, + int nid, int type, + const unsigned char *bytes, int len); +int X509_REQ_add1_attr_by_txt(X509_REQ *req, + const char *attrname, int type, + const unsigned char *bytes, int len); + +int X509_CRL_set_version(X509_CRL *x, long version); +int X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name); +int X509_CRL_set1_lastUpdate(X509_CRL *x, const ASN1_TIME *tm); +int X509_CRL_set1_nextUpdate(X509_CRL *x, const ASN1_TIME *tm); +int X509_CRL_sort(X509_CRL *crl); +int X509_CRL_up_ref(X509_CRL *crl); + +# if OPENSSL_API_COMPAT < 0x10100000L +# define X509_CRL_set_lastUpdate X509_CRL_set1_lastUpdate +# define X509_CRL_set_nextUpdate X509_CRL_set1_nextUpdate +#endif + +long X509_CRL_get_version(const X509_CRL *crl); +const ASN1_TIME *X509_CRL_get0_lastUpdate(const X509_CRL *crl); +const ASN1_TIME *X509_CRL_get0_nextUpdate(const X509_CRL *crl); +DEPRECATEDIN_1_1_0(ASN1_TIME *X509_CRL_get_lastUpdate(X509_CRL *crl)) +DEPRECATEDIN_1_1_0(ASN1_TIME *X509_CRL_get_nextUpdate(X509_CRL *crl)) +X509_NAME *X509_CRL_get_issuer(const X509_CRL *crl); +const STACK_OF(X509_EXTENSION) *X509_CRL_get0_extensions(const X509_CRL *crl); +STACK_OF(X509_REVOKED) *X509_CRL_get_REVOKED(X509_CRL *crl); +void X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig, + const X509_ALGOR **palg); +int X509_CRL_get_signature_nid(const X509_CRL *crl); +int i2d_re_X509_CRL_tbs(X509_CRL *req, unsigned char **pp); + +const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *x); +int X509_REVOKED_set_serialNumber(X509_REVOKED *x, ASN1_INTEGER *serial); +const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *x); +int X509_REVOKED_set_revocationDate(X509_REVOKED *r, ASN1_TIME *tm); +const STACK_OF(X509_EXTENSION) * +X509_REVOKED_get0_extensions(const X509_REVOKED *r); + +X509_CRL *X509_CRL_diff(X509_CRL *base, X509_CRL *newer, + EVP_PKEY *skey, const EVP_MD *md, unsigned int flags); + +int X509_REQ_check_private_key(X509_REQ *x509, EVP_PKEY *pkey); + +int X509_check_private_key(const X509 *x509, const EVP_PKEY *pkey); +int X509_chain_check_suiteb(int *perror_depth, + X509 *x, STACK_OF(X509) *chain, + unsigned long flags); +int X509_CRL_check_suiteb(X509_CRL *crl, EVP_PKEY *pk, unsigned long flags); +STACK_OF(X509) *X509_chain_up_ref(STACK_OF(X509) *chain); + +int X509_issuer_and_serial_cmp(const X509 *a, const X509 *b); +unsigned long X509_issuer_and_serial_hash(X509 *a); + +int X509_issuer_name_cmp(const X509 *a, const X509 *b); +unsigned long X509_issuer_name_hash(X509 *a); + +int X509_subject_name_cmp(const X509 *a, const X509 *b); +unsigned long X509_subject_name_hash(X509 *x); + +# ifndef OPENSSL_NO_MD5 +unsigned long X509_issuer_name_hash_old(X509 *a); +unsigned long X509_subject_name_hash_old(X509 *x); +# endif + +int X509_cmp(const X509 *a, const X509 *b); +int X509_NAME_cmp(const X509_NAME *a, const X509_NAME *b); +unsigned long X509_NAME_hash(X509_NAME *x); +unsigned long X509_NAME_hash_old(X509_NAME *x); + +int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b); +int X509_CRL_match(const X509_CRL *a, const X509_CRL *b); +int X509_aux_print(BIO *out, X509 *x, int indent); +# ifndef OPENSSL_NO_STDIO +int X509_print_ex_fp(FILE *bp, X509 *x, unsigned long nmflag, + unsigned long cflag); +int X509_print_fp(FILE *bp, X509 *x); +int X509_CRL_print_fp(FILE *bp, X509_CRL *x); +int X509_REQ_print_fp(FILE *bp, X509_REQ *req); +int X509_NAME_print_ex_fp(FILE *fp, const X509_NAME *nm, int indent, + unsigned long flags); +# endif + +int X509_NAME_print(BIO *bp, const X509_NAME *name, int obase); +int X509_NAME_print_ex(BIO *out, const X509_NAME *nm, int indent, + unsigned long flags); +int X509_print_ex(BIO *bp, X509 *x, unsigned long nmflag, + unsigned long cflag); +int X509_print(BIO *bp, X509 *x); +int X509_ocspid_print(BIO *bp, X509 *x); +int X509_CRL_print_ex(BIO *out, X509_CRL *x, unsigned long nmflag); +int X509_CRL_print(BIO *bp, X509_CRL *x); +int X509_REQ_print_ex(BIO *bp, X509_REQ *x, unsigned long nmflag, + unsigned long cflag); +int X509_REQ_print(BIO *bp, X509_REQ *req); + +int X509_NAME_entry_count(const X509_NAME *name); +int X509_NAME_get_text_by_NID(X509_NAME *name, int nid, char *buf, int len); +int X509_NAME_get_text_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, + char *buf, int len); + +/* + * NOTE: you should be passing -1, not 0 as lastpos. The functions that use + * lastpos, search after that position on. + */ +int X509_NAME_get_index_by_NID(X509_NAME *name, int nid, int lastpos); +int X509_NAME_get_index_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, + int lastpos); +X509_NAME_ENTRY *X509_NAME_get_entry(const X509_NAME *name, int loc); +X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *name, int loc); +int X509_NAME_add_entry(X509_NAME *name, const X509_NAME_ENTRY *ne, + int loc, int set); +int X509_NAME_add_entry_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, int type, + const unsigned char *bytes, int len, int loc, + int set); +int X509_NAME_add_entry_by_NID(X509_NAME *name, int nid, int type, + const unsigned char *bytes, int len, int loc, + int set); +X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_txt(X509_NAME_ENTRY **ne, + const char *field, int type, + const unsigned char *bytes, + int len); +X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_NID(X509_NAME_ENTRY **ne, int nid, + int type, + const unsigned char *bytes, + int len); +int X509_NAME_add_entry_by_txt(X509_NAME *name, const char *field, int type, + const unsigned char *bytes, int len, int loc, + int set); +X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_OBJ(X509_NAME_ENTRY **ne, + const ASN1_OBJECT *obj, int type, + const unsigned char *bytes, + int len); +int X509_NAME_ENTRY_set_object(X509_NAME_ENTRY *ne, const ASN1_OBJECT *obj); +int X509_NAME_ENTRY_set_data(X509_NAME_ENTRY *ne, int type, + const unsigned char *bytes, int len); +ASN1_OBJECT *X509_NAME_ENTRY_get_object(const X509_NAME_ENTRY *ne); +ASN1_STRING * X509_NAME_ENTRY_get_data(const X509_NAME_ENTRY *ne); +int X509_NAME_ENTRY_set(const X509_NAME_ENTRY *ne); + +int X509_NAME_get0_der(X509_NAME *nm, const unsigned char **pder, + size_t *pderlen); + +int X509v3_get_ext_count(const STACK_OF(X509_EXTENSION) *x); +int X509v3_get_ext_by_NID(const STACK_OF(X509_EXTENSION) *x, + int nid, int lastpos); +int X509v3_get_ext_by_OBJ(const STACK_OF(X509_EXTENSION) *x, + const ASN1_OBJECT *obj, int lastpos); +int X509v3_get_ext_by_critical(const STACK_OF(X509_EXTENSION) *x, + int crit, int lastpos); +X509_EXTENSION *X509v3_get_ext(const STACK_OF(X509_EXTENSION) *x, int loc); +X509_EXTENSION *X509v3_delete_ext(STACK_OF(X509_EXTENSION) *x, int loc); +STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x, + X509_EXTENSION *ex, int loc); + +int X509_get_ext_count(const X509 *x); +int X509_get_ext_by_NID(const X509 *x, int nid, int lastpos); +int X509_get_ext_by_OBJ(const X509 *x, const ASN1_OBJECT *obj, int lastpos); +int X509_get_ext_by_critical(const X509 *x, int crit, int lastpos); +X509_EXTENSION *X509_get_ext(const X509 *x, int loc); +X509_EXTENSION *X509_delete_ext(X509 *x, int loc); +int X509_add_ext(X509 *x, X509_EXTENSION *ex, int loc); +void *X509_get_ext_d2i(const X509 *x, int nid, int *crit, int *idx); +int X509_add1_ext_i2d(X509 *x, int nid, void *value, int crit, + unsigned long flags); + +int X509_CRL_get_ext_count(const X509_CRL *x); +int X509_CRL_get_ext_by_NID(const X509_CRL *x, int nid, int lastpos); +int X509_CRL_get_ext_by_OBJ(const X509_CRL *x, const ASN1_OBJECT *obj, + int lastpos); +int X509_CRL_get_ext_by_critical(const X509_CRL *x, int crit, int lastpos); +X509_EXTENSION *X509_CRL_get_ext(const X509_CRL *x, int loc); +X509_EXTENSION *X509_CRL_delete_ext(X509_CRL *x, int loc); +int X509_CRL_add_ext(X509_CRL *x, X509_EXTENSION *ex, int loc); +void *X509_CRL_get_ext_d2i(const X509_CRL *x, int nid, int *crit, int *idx); +int X509_CRL_add1_ext_i2d(X509_CRL *x, int nid, void *value, int crit, + unsigned long flags); + +int X509_REVOKED_get_ext_count(const X509_REVOKED *x); +int X509_REVOKED_get_ext_by_NID(const X509_REVOKED *x, int nid, int lastpos); +int X509_REVOKED_get_ext_by_OBJ(const X509_REVOKED *x, const ASN1_OBJECT *obj, + int lastpos); +int X509_REVOKED_get_ext_by_critical(const X509_REVOKED *x, int crit, + int lastpos); +X509_EXTENSION *X509_REVOKED_get_ext(const X509_REVOKED *x, int loc); +X509_EXTENSION *X509_REVOKED_delete_ext(X509_REVOKED *x, int loc); +int X509_REVOKED_add_ext(X509_REVOKED *x, X509_EXTENSION *ex, int loc); +void *X509_REVOKED_get_ext_d2i(const X509_REVOKED *x, int nid, int *crit, + int *idx); +int X509_REVOKED_add1_ext_i2d(X509_REVOKED *x, int nid, void *value, int crit, + unsigned long flags); + +X509_EXTENSION *X509_EXTENSION_create_by_NID(X509_EXTENSION **ex, + int nid, int crit, + ASN1_OCTET_STRING *data); +X509_EXTENSION *X509_EXTENSION_create_by_OBJ(X509_EXTENSION **ex, + const ASN1_OBJECT *obj, int crit, + ASN1_OCTET_STRING *data); +int X509_EXTENSION_set_object(X509_EXTENSION *ex, const ASN1_OBJECT *obj); +int X509_EXTENSION_set_critical(X509_EXTENSION *ex, int crit); +int X509_EXTENSION_set_data(X509_EXTENSION *ex, ASN1_OCTET_STRING *data); +ASN1_OBJECT *X509_EXTENSION_get_object(X509_EXTENSION *ex); +ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *ne); +int X509_EXTENSION_get_critical(const X509_EXTENSION *ex); + +int X509at_get_attr_count(const STACK_OF(X509_ATTRIBUTE) *x); +int X509at_get_attr_by_NID(const STACK_OF(X509_ATTRIBUTE) *x, int nid, + int lastpos); +int X509at_get_attr_by_OBJ(const STACK_OF(X509_ATTRIBUTE) *sk, + const ASN1_OBJECT *obj, int lastpos); +X509_ATTRIBUTE *X509at_get_attr(const STACK_OF(X509_ATTRIBUTE) *x, int loc); +X509_ATTRIBUTE *X509at_delete_attr(STACK_OF(X509_ATTRIBUTE) *x, int loc); +STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr(STACK_OF(X509_ATTRIBUTE) **x, + X509_ATTRIBUTE *attr); +STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_OBJ(STACK_OF(X509_ATTRIBUTE) + **x, const ASN1_OBJECT *obj, + int type, + const unsigned char *bytes, + int len); +STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_NID(STACK_OF(X509_ATTRIBUTE) + **x, int nid, int type, + const unsigned char *bytes, + int len); +STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_txt(STACK_OF(X509_ATTRIBUTE) + **x, const char *attrname, + int type, + const unsigned char *bytes, + int len); +void *X509at_get0_data_by_OBJ(STACK_OF(X509_ATTRIBUTE) *x, + const ASN1_OBJECT *obj, int lastpos, int type); +X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_NID(X509_ATTRIBUTE **attr, int nid, + int atrtype, const void *data, + int len); +X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_OBJ(X509_ATTRIBUTE **attr, + const ASN1_OBJECT *obj, + int atrtype, const void *data, + int len); +X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_txt(X509_ATTRIBUTE **attr, + const char *atrname, int type, + const unsigned char *bytes, + int len); +int X509_ATTRIBUTE_set1_object(X509_ATTRIBUTE *attr, const ASN1_OBJECT *obj); +int X509_ATTRIBUTE_set1_data(X509_ATTRIBUTE *attr, int attrtype, + const void *data, int len); +void *X509_ATTRIBUTE_get0_data(X509_ATTRIBUTE *attr, int idx, int atrtype, + void *data); +int X509_ATTRIBUTE_count(const X509_ATTRIBUTE *attr); +ASN1_OBJECT *X509_ATTRIBUTE_get0_object(X509_ATTRIBUTE *attr); +ASN1_TYPE *X509_ATTRIBUTE_get0_type(X509_ATTRIBUTE *attr, int idx); + +int EVP_PKEY_get_attr_count(const EVP_PKEY *key); +int EVP_PKEY_get_attr_by_NID(const EVP_PKEY *key, int nid, int lastpos); +int EVP_PKEY_get_attr_by_OBJ(const EVP_PKEY *key, const ASN1_OBJECT *obj, + int lastpos); +X509_ATTRIBUTE *EVP_PKEY_get_attr(const EVP_PKEY *key, int loc); +X509_ATTRIBUTE *EVP_PKEY_delete_attr(EVP_PKEY *key, int loc); +int EVP_PKEY_add1_attr(EVP_PKEY *key, X509_ATTRIBUTE *attr); +int EVP_PKEY_add1_attr_by_OBJ(EVP_PKEY *key, + const ASN1_OBJECT *obj, int type, + const unsigned char *bytes, int len); +int EVP_PKEY_add1_attr_by_NID(EVP_PKEY *key, + int nid, int type, + const unsigned char *bytes, int len); +int EVP_PKEY_add1_attr_by_txt(EVP_PKEY *key, + const char *attrname, int type, + const unsigned char *bytes, int len); + +int X509_verify_cert(X509_STORE_CTX *ctx); + +/* lookup a cert from a X509 STACK */ +X509 *X509_find_by_issuer_and_serial(STACK_OF(X509) *sk, X509_NAME *name, + ASN1_INTEGER *serial); +X509 *X509_find_by_subject(STACK_OF(X509) *sk, X509_NAME *name); + +DECLARE_ASN1_FUNCTIONS(PBEPARAM) +DECLARE_ASN1_FUNCTIONS(PBE2PARAM) +DECLARE_ASN1_FUNCTIONS(PBKDF2PARAM) +#ifndef OPENSSL_NO_SCRYPT +DECLARE_ASN1_FUNCTIONS(SCRYPT_PARAMS) +#endif + +int PKCS5_pbe_set0_algor(X509_ALGOR *algor, int alg, int iter, + const unsigned char *salt, int saltlen); + +X509_ALGOR *PKCS5_pbe_set(int alg, int iter, + const unsigned char *salt, int saltlen); +X509_ALGOR *PKCS5_pbe2_set(const EVP_CIPHER *cipher, int iter, + unsigned char *salt, int saltlen); +X509_ALGOR *PKCS5_pbe2_set_iv(const EVP_CIPHER *cipher, int iter, + unsigned char *salt, int saltlen, + unsigned char *aiv, int prf_nid); + +#ifndef OPENSSL_NO_SCRYPT +X509_ALGOR *PKCS5_pbe2_set_scrypt(const EVP_CIPHER *cipher, + const unsigned char *salt, int saltlen, + unsigned char *aiv, uint64_t N, uint64_t r, + uint64_t p); +#endif + +X509_ALGOR *PKCS5_pbkdf2_set(int iter, unsigned char *salt, int saltlen, + int prf_nid, int keylen); + +/* PKCS#8 utilities */ + +DECLARE_ASN1_FUNCTIONS(PKCS8_PRIV_KEY_INFO) + +EVP_PKEY *EVP_PKCS82PKEY(const PKCS8_PRIV_KEY_INFO *p8); +PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8(EVP_PKEY *pkey); + +int PKCS8_pkey_set0(PKCS8_PRIV_KEY_INFO *priv, ASN1_OBJECT *aobj, + int version, int ptype, void *pval, + unsigned char *penc, int penclen); +int PKCS8_pkey_get0(const ASN1_OBJECT **ppkalg, + const unsigned char **pk, int *ppklen, + const X509_ALGOR **pa, const PKCS8_PRIV_KEY_INFO *p8); + +const STACK_OF(X509_ATTRIBUTE) * +PKCS8_pkey_get0_attrs(const PKCS8_PRIV_KEY_INFO *p8); +int PKCS8_pkey_add1_attr_by_NID(PKCS8_PRIV_KEY_INFO *p8, int nid, int type, + const unsigned char *bytes, int len); + +int X509_PUBKEY_set0_param(X509_PUBKEY *pub, ASN1_OBJECT *aobj, + int ptype, void *pval, + unsigned char *penc, int penclen); +int X509_PUBKEY_get0_param(ASN1_OBJECT **ppkalg, + const unsigned char **pk, int *ppklen, + X509_ALGOR **pa, X509_PUBKEY *pub); + +int X509_check_trust(X509 *x, int id, int flags); +int X509_TRUST_get_count(void); +X509_TRUST *X509_TRUST_get0(int idx); +int X509_TRUST_get_by_id(int id); +int X509_TRUST_add(int id, int flags, int (*ck) (X509_TRUST *, X509 *, int), + const char *name, int arg1, void *arg2); +void X509_TRUST_cleanup(void); +int X509_TRUST_get_flags(const X509_TRUST *xp); +char *X509_TRUST_get0_name(const X509_TRUST *xp); +int X509_TRUST_get_trust(const X509_TRUST *xp); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/x509_vfy.h b/thrid-party/openssl/openssl/x509_vfy.h new file mode 100644 index 0000000000..adb8bce7cb --- /dev/null +++ b/thrid-party/openssl/openssl/x509_vfy.h @@ -0,0 +1,628 @@ +/* + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_X509_VFY_H +# define HEADER_X509_VFY_H + +/* + * Protect against recursion, x509.h and x509_vfy.h each include the other. + */ +# ifndef HEADER_X509_H +# include +# endif + +# include +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/*- +SSL_CTX -> X509_STORE + -> X509_LOOKUP + ->X509_LOOKUP_METHOD + -> X509_LOOKUP + ->X509_LOOKUP_METHOD + +SSL -> X509_STORE_CTX + ->X509_STORE + +The X509_STORE holds the tables etc for verification stuff. +A X509_STORE_CTX is used while validating a single certificate. +The X509_STORE has X509_LOOKUPs for looking up certs. +The X509_STORE then calls a function to actually verify the +certificate chain. +*/ + +typedef enum { + X509_LU_NONE = 0, + X509_LU_X509, X509_LU_CRL +} X509_LOOKUP_TYPE; + +#if OPENSSL_API_COMPAT < 0x10100000L +#define X509_LU_RETRY -1 +#define X509_LU_FAIL 0 +#endif + +DEFINE_STACK_OF(X509_LOOKUP) +DEFINE_STACK_OF(X509_OBJECT) +DEFINE_STACK_OF(X509_VERIFY_PARAM) + +int X509_STORE_set_depth(X509_STORE *store, int depth); + +typedef int (*X509_STORE_CTX_verify_cb)(int, X509_STORE_CTX *); +typedef int (*X509_STORE_CTX_verify_fn)(X509_STORE_CTX *); +typedef int (*X509_STORE_CTX_get_issuer_fn)(X509 **issuer, + X509_STORE_CTX *ctx, X509 *x); +typedef int (*X509_STORE_CTX_check_issued_fn)(X509_STORE_CTX *ctx, + X509 *x, X509 *issuer); +typedef int (*X509_STORE_CTX_check_revocation_fn)(X509_STORE_CTX *ctx); +typedef int (*X509_STORE_CTX_get_crl_fn)(X509_STORE_CTX *ctx, + X509_CRL **crl, X509 *x); +typedef int (*X509_STORE_CTX_check_crl_fn)(X509_STORE_CTX *ctx, X509_CRL *crl); +typedef int (*X509_STORE_CTX_cert_crl_fn)(X509_STORE_CTX *ctx, + X509_CRL *crl, X509 *x); +typedef int (*X509_STORE_CTX_check_policy_fn)(X509_STORE_CTX *ctx); +typedef STACK_OF(X509) *(*X509_STORE_CTX_lookup_certs_fn)(X509_STORE_CTX *ctx, + X509_NAME *nm); +typedef STACK_OF(X509_CRL) *(*X509_STORE_CTX_lookup_crls_fn)(X509_STORE_CTX *ctx, + X509_NAME *nm); +typedef int (*X509_STORE_CTX_cleanup_fn)(X509_STORE_CTX *ctx); + + +void X509_STORE_CTX_set_depth(X509_STORE_CTX *ctx, int depth); + +# define X509_STORE_CTX_set_app_data(ctx,data) \ + X509_STORE_CTX_set_ex_data(ctx,0,data) +# define X509_STORE_CTX_get_app_data(ctx) \ + X509_STORE_CTX_get_ex_data(ctx,0) + +# define X509_L_FILE_LOAD 1 +# define X509_L_ADD_DIR 2 + +# define X509_LOOKUP_load_file(x,name,type) \ + X509_LOOKUP_ctrl((x),X509_L_FILE_LOAD,(name),(long)(type),NULL) + +# define X509_LOOKUP_add_dir(x,name,type) \ + X509_LOOKUP_ctrl((x),X509_L_ADD_DIR,(name),(long)(type),NULL) + +# define X509_V_OK 0 +# define X509_V_ERR_UNSPECIFIED 1 +# define X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT 2 +# define X509_V_ERR_UNABLE_TO_GET_CRL 3 +# define X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE 4 +# define X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE 5 +# define X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY 6 +# define X509_V_ERR_CERT_SIGNATURE_FAILURE 7 +# define X509_V_ERR_CRL_SIGNATURE_FAILURE 8 +# define X509_V_ERR_CERT_NOT_YET_VALID 9 +# define X509_V_ERR_CERT_HAS_EXPIRED 10 +# define X509_V_ERR_CRL_NOT_YET_VALID 11 +# define X509_V_ERR_CRL_HAS_EXPIRED 12 +# define X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD 13 +# define X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD 14 +# define X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD 15 +# define X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD 16 +# define X509_V_ERR_OUT_OF_MEM 17 +# define X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT 18 +# define X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN 19 +# define X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY 20 +# define X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE 21 +# define X509_V_ERR_CERT_CHAIN_TOO_LONG 22 +# define X509_V_ERR_CERT_REVOKED 23 +# define X509_V_ERR_INVALID_CA 24 +# define X509_V_ERR_PATH_LENGTH_EXCEEDED 25 +# define X509_V_ERR_INVALID_PURPOSE 26 +# define X509_V_ERR_CERT_UNTRUSTED 27 +# define X509_V_ERR_CERT_REJECTED 28 +/* These are 'informational' when looking for issuer cert */ +# define X509_V_ERR_SUBJECT_ISSUER_MISMATCH 29 +# define X509_V_ERR_AKID_SKID_MISMATCH 30 +# define X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH 31 +# define X509_V_ERR_KEYUSAGE_NO_CERTSIGN 32 +# define X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER 33 +# define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34 +# define X509_V_ERR_KEYUSAGE_NO_CRL_SIGN 35 +# define X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION 36 +# define X509_V_ERR_INVALID_NON_CA 37 +# define X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED 38 +# define X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE 39 +# define X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED 40 +# define X509_V_ERR_INVALID_EXTENSION 41 +# define X509_V_ERR_INVALID_POLICY_EXTENSION 42 +# define X509_V_ERR_NO_EXPLICIT_POLICY 43 +# define X509_V_ERR_DIFFERENT_CRL_SCOPE 44 +# define X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE 45 +# define X509_V_ERR_UNNESTED_RESOURCE 46 +# define X509_V_ERR_PERMITTED_VIOLATION 47 +# define X509_V_ERR_EXCLUDED_VIOLATION 48 +# define X509_V_ERR_SUBTREE_MINMAX 49 +/* The application is not happy */ +# define X509_V_ERR_APPLICATION_VERIFICATION 50 +# define X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE 51 +# define X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX 52 +# define X509_V_ERR_UNSUPPORTED_NAME_SYNTAX 53 +# define X509_V_ERR_CRL_PATH_VALIDATION_ERROR 54 +/* Another issuer check debug option */ +# define X509_V_ERR_PATH_LOOP 55 +/* Suite B mode algorithm violation */ +# define X509_V_ERR_SUITE_B_INVALID_VERSION 56 +# define X509_V_ERR_SUITE_B_INVALID_ALGORITHM 57 +# define X509_V_ERR_SUITE_B_INVALID_CURVE 58 +# define X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM 59 +# define X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED 60 +# define X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256 61 +/* Host, email and IP check errors */ +# define X509_V_ERR_HOSTNAME_MISMATCH 62 +# define X509_V_ERR_EMAIL_MISMATCH 63 +# define X509_V_ERR_IP_ADDRESS_MISMATCH 64 +/* DANE TLSA errors */ +# define X509_V_ERR_DANE_NO_MATCH 65 +/* security level errors */ +# define X509_V_ERR_EE_KEY_TOO_SMALL 66 +# define X509_V_ERR_CA_KEY_TOO_SMALL 67 +# define X509_V_ERR_CA_MD_TOO_WEAK 68 +/* Caller error */ +# define X509_V_ERR_INVALID_CALL 69 +/* Issuer lookup error */ +# define X509_V_ERR_STORE_LOOKUP 70 +/* Certificate transparency */ +# define X509_V_ERR_NO_VALID_SCTS 71 + +# define X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION 72 +/* OCSP status errors */ +# define X509_V_ERR_OCSP_VERIFY_NEEDED 73 /* Need OCSP verification */ +# define X509_V_ERR_OCSP_VERIFY_FAILED 74 /* Couldn't verify cert through OCSP */ +# define X509_V_ERR_OCSP_CERT_UNKNOWN 75 /* Certificate wasn't recognized by the OCSP responder */ + +/* Certificate verify flags */ + +# if OPENSSL_API_COMPAT < 0x10100000L +# define X509_V_FLAG_CB_ISSUER_CHECK 0x0 /* Deprecated */ +# endif +/* Use check time instead of current time */ +# define X509_V_FLAG_USE_CHECK_TIME 0x2 +/* Lookup CRLs */ +# define X509_V_FLAG_CRL_CHECK 0x4 +/* Lookup CRLs for whole chain */ +# define X509_V_FLAG_CRL_CHECK_ALL 0x8 +/* Ignore unhandled critical extensions */ +# define X509_V_FLAG_IGNORE_CRITICAL 0x10 +/* Disable workarounds for broken certificates */ +# define X509_V_FLAG_X509_STRICT 0x20 +/* Enable proxy certificate validation */ +# define X509_V_FLAG_ALLOW_PROXY_CERTS 0x40 +/* Enable policy checking */ +# define X509_V_FLAG_POLICY_CHECK 0x80 +/* Policy variable require-explicit-policy */ +# define X509_V_FLAG_EXPLICIT_POLICY 0x100 +/* Policy variable inhibit-any-policy */ +# define X509_V_FLAG_INHIBIT_ANY 0x200 +/* Policy variable inhibit-policy-mapping */ +# define X509_V_FLAG_INHIBIT_MAP 0x400 +/* Notify callback that policy is OK */ +# define X509_V_FLAG_NOTIFY_POLICY 0x800 +/* Extended CRL features such as indirect CRLs, alternate CRL signing keys */ +# define X509_V_FLAG_EXTENDED_CRL_SUPPORT 0x1000 +/* Delta CRL support */ +# define X509_V_FLAG_USE_DELTAS 0x2000 +/* Check self-signed CA signature */ +# define X509_V_FLAG_CHECK_SS_SIGNATURE 0x4000 +/* Use trusted store first */ +# define X509_V_FLAG_TRUSTED_FIRST 0x8000 +/* Suite B 128 bit only mode: not normally used */ +# define X509_V_FLAG_SUITEB_128_LOS_ONLY 0x10000 +/* Suite B 192 bit only mode */ +# define X509_V_FLAG_SUITEB_192_LOS 0x20000 +/* Suite B 128 bit mode allowing 192 bit algorithms */ +# define X509_V_FLAG_SUITEB_128_LOS 0x30000 +/* Allow partial chains if at least one certificate is in trusted store */ +# define X509_V_FLAG_PARTIAL_CHAIN 0x80000 +/* + * If the initial chain is not trusted, do not attempt to build an alternative + * chain. Alternate chain checking was introduced in 1.1.0. Setting this flag + * will force the behaviour to match that of previous versions. + */ +# define X509_V_FLAG_NO_ALT_CHAINS 0x100000 +/* Do not check certificate/CRL validity against current time */ +# define X509_V_FLAG_NO_CHECK_TIME 0x200000 + +# define X509_VP_FLAG_DEFAULT 0x1 +# define X509_VP_FLAG_OVERWRITE 0x2 +# define X509_VP_FLAG_RESET_FLAGS 0x4 +# define X509_VP_FLAG_LOCKED 0x8 +# define X509_VP_FLAG_ONCE 0x10 + +/* Internal use: mask of policy related options */ +# define X509_V_FLAG_POLICY_MASK (X509_V_FLAG_POLICY_CHECK \ + | X509_V_FLAG_EXPLICIT_POLICY \ + | X509_V_FLAG_INHIBIT_ANY \ + | X509_V_FLAG_INHIBIT_MAP) + +int X509_OBJECT_idx_by_subject(STACK_OF(X509_OBJECT) *h, X509_LOOKUP_TYPE type, + X509_NAME *name); +X509_OBJECT *X509_OBJECT_retrieve_by_subject(STACK_OF(X509_OBJECT) *h, + X509_LOOKUP_TYPE type, + X509_NAME *name); +X509_OBJECT *X509_OBJECT_retrieve_match(STACK_OF(X509_OBJECT) *h, + X509_OBJECT *x); +int X509_OBJECT_up_ref_count(X509_OBJECT *a); +X509_OBJECT *X509_OBJECT_new(void); +void X509_OBJECT_free(X509_OBJECT *a); +X509_LOOKUP_TYPE X509_OBJECT_get_type(const X509_OBJECT *a); +X509 *X509_OBJECT_get0_X509(const X509_OBJECT *a); +int X509_OBJECT_set1_X509(X509_OBJECT *a, X509 *obj); +X509_CRL *X509_OBJECT_get0_X509_CRL(X509_OBJECT *a); +int X509_OBJECT_set1_X509_CRL(X509_OBJECT *a, X509_CRL *obj); +X509_STORE *X509_STORE_new(void); +void X509_STORE_free(X509_STORE *v); +int X509_STORE_lock(X509_STORE *ctx); +int X509_STORE_unlock(X509_STORE *ctx); +int X509_STORE_up_ref(X509_STORE *v); +STACK_OF(X509_OBJECT) *X509_STORE_get0_objects(X509_STORE *v); + +STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *st, X509_NAME *nm); +STACK_OF(X509_CRL) *X509_STORE_CTX_get1_crls(X509_STORE_CTX *st, X509_NAME *nm); +int X509_STORE_set_flags(X509_STORE *ctx, unsigned long flags); +int X509_STORE_set_purpose(X509_STORE *ctx, int purpose); +int X509_STORE_set_trust(X509_STORE *ctx, int trust); +int X509_STORE_set1_param(X509_STORE *ctx, X509_VERIFY_PARAM *pm); +X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *ctx); + +void X509_STORE_set_verify(X509_STORE *ctx, X509_STORE_CTX_verify_fn verify); +#define X509_STORE_set_verify_func(ctx, func) \ + X509_STORE_set_verify((ctx),(func)) +void X509_STORE_CTX_set_verify(X509_STORE_CTX *ctx, + X509_STORE_CTX_verify_fn verify); +X509_STORE_CTX_verify_fn X509_STORE_get_verify(X509_STORE *ctx); +void X509_STORE_set_verify_cb(X509_STORE *ctx, + X509_STORE_CTX_verify_cb verify_cb); +# define X509_STORE_set_verify_cb_func(ctx,func) \ + X509_STORE_set_verify_cb((ctx),(func)) +X509_STORE_CTX_verify_cb X509_STORE_get_verify_cb(X509_STORE *ctx); +void X509_STORE_set_get_issuer(X509_STORE *ctx, + X509_STORE_CTX_get_issuer_fn get_issuer); +X509_STORE_CTX_get_issuer_fn X509_STORE_get_get_issuer(X509_STORE *ctx); +void X509_STORE_set_check_issued(X509_STORE *ctx, + X509_STORE_CTX_check_issued_fn check_issued); +X509_STORE_CTX_check_issued_fn X509_STORE_get_check_issued(X509_STORE *ctx); +void X509_STORE_set_check_revocation(X509_STORE *ctx, + X509_STORE_CTX_check_revocation_fn check_revocation); +X509_STORE_CTX_check_revocation_fn X509_STORE_get_check_revocation(X509_STORE *ctx); +void X509_STORE_set_get_crl(X509_STORE *ctx, + X509_STORE_CTX_get_crl_fn get_crl); +X509_STORE_CTX_get_crl_fn X509_STORE_get_get_crl(X509_STORE *ctx); +void X509_STORE_set_check_crl(X509_STORE *ctx, + X509_STORE_CTX_check_crl_fn check_crl); +X509_STORE_CTX_check_crl_fn X509_STORE_get_check_crl(X509_STORE *ctx); +void X509_STORE_set_cert_crl(X509_STORE *ctx, + X509_STORE_CTX_cert_crl_fn cert_crl); +X509_STORE_CTX_cert_crl_fn X509_STORE_get_cert_crl(X509_STORE *ctx); +void X509_STORE_set_check_policy(X509_STORE *ctx, + X509_STORE_CTX_check_policy_fn check_policy); +X509_STORE_CTX_check_policy_fn X509_STORE_get_check_policy(X509_STORE *ctx); +void X509_STORE_set_lookup_certs(X509_STORE *ctx, + X509_STORE_CTX_lookup_certs_fn lookup_certs); +X509_STORE_CTX_lookup_certs_fn X509_STORE_get_lookup_certs(X509_STORE *ctx); +void X509_STORE_set_lookup_crls(X509_STORE *ctx, + X509_STORE_CTX_lookup_crls_fn lookup_crls); +#define X509_STORE_set_lookup_crls_cb(ctx, func) \ + X509_STORE_set_lookup_crls((ctx), (func)) +X509_STORE_CTX_lookup_crls_fn X509_STORE_get_lookup_crls(X509_STORE *ctx); +void X509_STORE_set_cleanup(X509_STORE *ctx, + X509_STORE_CTX_cleanup_fn cleanup); +X509_STORE_CTX_cleanup_fn X509_STORE_get_cleanup(X509_STORE *ctx); + +#define X509_STORE_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_X509_STORE, l, p, newf, dupf, freef) +int X509_STORE_set_ex_data(X509_STORE *ctx, int idx, void *data); +void *X509_STORE_get_ex_data(X509_STORE *ctx, int idx); + +X509_STORE_CTX *X509_STORE_CTX_new(void); + +int X509_STORE_CTX_get1_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); + +void X509_STORE_CTX_free(X509_STORE_CTX *ctx); +int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, + X509 *x509, STACK_OF(X509) *chain); +void X509_STORE_CTX_set0_trusted_stack(X509_STORE_CTX *ctx, STACK_OF(X509) *sk); +void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx); + +X509_STORE *X509_STORE_CTX_get0_store(X509_STORE_CTX *ctx); +X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *ctx); +STACK_OF(X509)* X509_STORE_CTX_get0_untrusted(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set0_untrusted(X509_STORE_CTX *ctx, STACK_OF(X509) *sk); +void X509_STORE_CTX_set_verify_cb(X509_STORE_CTX *ctx, + X509_STORE_CTX_verify_cb verify); +X509_STORE_CTX_verify_cb X509_STORE_CTX_get_verify_cb(X509_STORE_CTX *ctx); +X509_STORE_CTX_verify_fn X509_STORE_CTX_get_verify(X509_STORE_CTX *ctx); +X509_STORE_CTX_get_issuer_fn X509_STORE_CTX_get_get_issuer(X509_STORE_CTX *ctx); +X509_STORE_CTX_check_issued_fn X509_STORE_CTX_get_check_issued(X509_STORE_CTX *ctx); +X509_STORE_CTX_check_revocation_fn X509_STORE_CTX_get_check_revocation(X509_STORE_CTX *ctx); +X509_STORE_CTX_get_crl_fn X509_STORE_CTX_get_get_crl(X509_STORE_CTX *ctx); +X509_STORE_CTX_check_crl_fn X509_STORE_CTX_get_check_crl(X509_STORE_CTX *ctx); +X509_STORE_CTX_cert_crl_fn X509_STORE_CTX_get_cert_crl(X509_STORE_CTX *ctx); +X509_STORE_CTX_check_policy_fn X509_STORE_CTX_get_check_policy(X509_STORE_CTX *ctx); +X509_STORE_CTX_lookup_certs_fn X509_STORE_CTX_get_lookup_certs(X509_STORE_CTX *ctx); +X509_STORE_CTX_lookup_crls_fn X509_STORE_CTX_get_lookup_crls(X509_STORE_CTX *ctx); +X509_STORE_CTX_cleanup_fn X509_STORE_CTX_get_cleanup(X509_STORE_CTX *ctx); + +#if OPENSSL_API_COMPAT < 0x10100000L +# define X509_STORE_CTX_get_chain X509_STORE_CTX_get0_chain +# define X509_STORE_CTX_set_chain X509_STORE_CTX_set0_untrusted +# define X509_STORE_CTX_trusted_stack X509_STORE_CTX_set0_trusted_stack +# define X509_STORE_get_by_subject X509_STORE_CTX_get_by_subject +# define X509_STORE_get1_certs X509_STORE_CTX_get1_certs +# define X509_STORE_get1_crls X509_STORE_CTX_get1_crls +/* the following macro is misspelled; use X509_STORE_get1_certs instead */ +# define X509_STORE_get1_cert X509_STORE_CTX_get1_certs +/* the following macro is misspelled; use X509_STORE_get1_crls instead */ +# define X509_STORE_get1_crl X509_STORE_CTX_get1_crls +#endif + +X509_LOOKUP *X509_STORE_add_lookup(X509_STORE *v, X509_LOOKUP_METHOD *m); +X509_LOOKUP_METHOD *X509_LOOKUP_hash_dir(void); +X509_LOOKUP_METHOD *X509_LOOKUP_file(void); + +typedef int (*X509_LOOKUP_ctrl_fn)(X509_LOOKUP *ctx, int cmd, const char *argc, + long argl, char **ret); +typedef int (*X509_LOOKUP_get_by_subject_fn)(X509_LOOKUP *ctx, + X509_LOOKUP_TYPE type, + X509_NAME *name, + X509_OBJECT *ret); +typedef int (*X509_LOOKUP_get_by_issuer_serial_fn)(X509_LOOKUP *ctx, + X509_LOOKUP_TYPE type, + X509_NAME *name, + ASN1_INTEGER *serial, + X509_OBJECT *ret); +typedef int (*X509_LOOKUP_get_by_fingerprint_fn)(X509_LOOKUP *ctx, + X509_LOOKUP_TYPE type, + const unsigned char* bytes, + int len, + X509_OBJECT *ret); +typedef int (*X509_LOOKUP_get_by_alias_fn)(X509_LOOKUP *ctx, + X509_LOOKUP_TYPE type, + const char *str, + int len, + X509_OBJECT *ret); + +X509_LOOKUP_METHOD *X509_LOOKUP_meth_new(const char *name); +void X509_LOOKUP_meth_free(X509_LOOKUP_METHOD *method); + +int X509_LOOKUP_meth_set_new_item(X509_LOOKUP_METHOD *method, + int (*new_item) (X509_LOOKUP *ctx)); +int (*X509_LOOKUP_meth_get_new_item(const X509_LOOKUP_METHOD* method)) + (X509_LOOKUP *ctx); + +int X509_LOOKUP_meth_set_free(X509_LOOKUP_METHOD *method, + void (*free_fn) (X509_LOOKUP *ctx)); +void (*X509_LOOKUP_meth_get_free(const X509_LOOKUP_METHOD* method)) + (X509_LOOKUP *ctx); + +int X509_LOOKUP_meth_set_init(X509_LOOKUP_METHOD *method, + int (*init) (X509_LOOKUP *ctx)); +int (*X509_LOOKUP_meth_get_init(const X509_LOOKUP_METHOD* method)) + (X509_LOOKUP *ctx); + +int X509_LOOKUP_meth_set_shutdown(X509_LOOKUP_METHOD *method, + int (*shutdown) (X509_LOOKUP *ctx)); +int (*X509_LOOKUP_meth_get_shutdown(const X509_LOOKUP_METHOD* method)) + (X509_LOOKUP *ctx); + +int X509_LOOKUP_meth_set_ctrl(X509_LOOKUP_METHOD *method, + X509_LOOKUP_ctrl_fn ctrl_fn); +X509_LOOKUP_ctrl_fn X509_LOOKUP_meth_get_ctrl(const X509_LOOKUP_METHOD *method); + +int X509_LOOKUP_meth_set_get_by_subject(X509_LOOKUP_METHOD *method, + X509_LOOKUP_get_by_subject_fn fn); +X509_LOOKUP_get_by_subject_fn X509_LOOKUP_meth_get_get_by_subject( + const X509_LOOKUP_METHOD *method); + +int X509_LOOKUP_meth_set_get_by_issuer_serial(X509_LOOKUP_METHOD *method, + X509_LOOKUP_get_by_issuer_serial_fn fn); +X509_LOOKUP_get_by_issuer_serial_fn X509_LOOKUP_meth_get_get_by_issuer_serial( + const X509_LOOKUP_METHOD *method); + +int X509_LOOKUP_meth_set_get_by_fingerprint(X509_LOOKUP_METHOD *method, + X509_LOOKUP_get_by_fingerprint_fn fn); +X509_LOOKUP_get_by_fingerprint_fn X509_LOOKUP_meth_get_get_by_fingerprint( + const X509_LOOKUP_METHOD *method); + +int X509_LOOKUP_meth_set_get_by_alias(X509_LOOKUP_METHOD *method, + X509_LOOKUP_get_by_alias_fn fn); +X509_LOOKUP_get_by_alias_fn X509_LOOKUP_meth_get_get_by_alias( + const X509_LOOKUP_METHOD *method); + + +int X509_STORE_add_cert(X509_STORE *ctx, X509 *x); +int X509_STORE_add_crl(X509_STORE *ctx, X509_CRL *x); + +int X509_STORE_CTX_get_by_subject(X509_STORE_CTX *vs, X509_LOOKUP_TYPE type, + X509_NAME *name, X509_OBJECT *ret); +X509_OBJECT *X509_STORE_CTX_get_obj_by_subject(X509_STORE_CTX *vs, + X509_LOOKUP_TYPE type, + X509_NAME *name); + +int X509_LOOKUP_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc, + long argl, char **ret); + +int X509_load_cert_file(X509_LOOKUP *ctx, const char *file, int type); +int X509_load_crl_file(X509_LOOKUP *ctx, const char *file, int type); +int X509_load_cert_crl_file(X509_LOOKUP *ctx, const char *file, int type); + +X509_LOOKUP *X509_LOOKUP_new(X509_LOOKUP_METHOD *method); +void X509_LOOKUP_free(X509_LOOKUP *ctx); +int X509_LOOKUP_init(X509_LOOKUP *ctx); +int X509_LOOKUP_by_subject(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type, + X509_NAME *name, X509_OBJECT *ret); +int X509_LOOKUP_by_issuer_serial(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type, + X509_NAME *name, ASN1_INTEGER *serial, + X509_OBJECT *ret); +int X509_LOOKUP_by_fingerprint(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type, + const unsigned char *bytes, int len, + X509_OBJECT *ret); +int X509_LOOKUP_by_alias(X509_LOOKUP *ctx, X509_LOOKUP_TYPE type, + const char *str, int len, X509_OBJECT *ret); +int X509_LOOKUP_set_method_data(X509_LOOKUP *ctx, void *data); +void *X509_LOOKUP_get_method_data(const X509_LOOKUP *ctx); +X509_STORE *X509_LOOKUP_get_store(const X509_LOOKUP *ctx); +int X509_LOOKUP_shutdown(X509_LOOKUP *ctx); + +int X509_STORE_load_locations(X509_STORE *ctx, + const char *file, const char *dir); +int X509_STORE_set_default_paths(X509_STORE *ctx); + +#define X509_STORE_CTX_get_ex_new_index(l, p, newf, dupf, freef) \ + CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_X509_STORE_CTX, l, p, newf, dupf, freef) +int X509_STORE_CTX_set_ex_data(X509_STORE_CTX *ctx, int idx, void *data); +void *X509_STORE_CTX_get_ex_data(X509_STORE_CTX *ctx, int idx); +int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set_error(X509_STORE_CTX *ctx, int s); +int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set_error_depth(X509_STORE_CTX *ctx, int depth); +X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set_current_cert(X509_STORE_CTX *ctx, X509 *x); +X509 *X509_STORE_CTX_get0_current_issuer(X509_STORE_CTX *ctx); +X509_CRL *X509_STORE_CTX_get0_current_crl(X509_STORE_CTX *ctx); +X509_STORE_CTX *X509_STORE_CTX_get0_parent_ctx(X509_STORE_CTX *ctx); +STACK_OF(X509) *X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx); +STACK_OF(X509) *X509_STORE_CTX_get1_chain(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set_cert(X509_STORE_CTX *c, X509 *x); +void X509_STORE_CTX_set0_verified_chain(X509_STORE_CTX *c, STACK_OF(X509) *sk); +void X509_STORE_CTX_set0_crls(X509_STORE_CTX *c, STACK_OF(X509_CRL) *sk); +int X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); +int X509_STORE_CTX_set_trust(X509_STORE_CTX *ctx, int trust); +int X509_STORE_CTX_purpose_inherit(X509_STORE_CTX *ctx, int def_purpose, + int purpose, int trust); +void X509_STORE_CTX_set_flags(X509_STORE_CTX *ctx, unsigned long flags); +void X509_STORE_CTX_set_time(X509_STORE_CTX *ctx, unsigned long flags, + time_t t); + +X509_POLICY_TREE *X509_STORE_CTX_get0_policy_tree(X509_STORE_CTX *ctx); +int X509_STORE_CTX_get_explicit_policy(X509_STORE_CTX *ctx); +int X509_STORE_CTX_get_num_untrusted(X509_STORE_CTX *ctx); + +X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *ctx); +void X509_STORE_CTX_set0_param(X509_STORE_CTX *ctx, X509_VERIFY_PARAM *param); +int X509_STORE_CTX_set_default(X509_STORE_CTX *ctx, const char *name); + +/* + * Bridge opacity barrier between libcrypt and libssl, also needed to support + * offline testing in test/danetest.c + */ +void X509_STORE_CTX_set0_dane(X509_STORE_CTX *ctx, SSL_DANE *dane); +#define DANE_FLAG_NO_DANE_EE_NAMECHECKS (1L << 0) + +/* X509_VERIFY_PARAM functions */ + +X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); +void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *param); +int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *to, + const X509_VERIFY_PARAM *from); +int X509_VERIFY_PARAM_set1(X509_VERIFY_PARAM *to, + const X509_VERIFY_PARAM *from); +int X509_VERIFY_PARAM_set1_name(X509_VERIFY_PARAM *param, const char *name); +int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *param, + unsigned long flags); +int X509_VERIFY_PARAM_clear_flags(X509_VERIFY_PARAM *param, + unsigned long flags); +unsigned long X509_VERIFY_PARAM_get_flags(X509_VERIFY_PARAM *param); +int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *param, int purpose); +int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *param, int trust); +void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *param, int depth); +void X509_VERIFY_PARAM_set_auth_level(X509_VERIFY_PARAM *param, int auth_level); +time_t X509_VERIFY_PARAM_get_time(const X509_VERIFY_PARAM *param); +void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *param, time_t t); +int X509_VERIFY_PARAM_add0_policy(X509_VERIFY_PARAM *param, + ASN1_OBJECT *policy); +int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *param, + STACK_OF(ASN1_OBJECT) *policies); + +int X509_VERIFY_PARAM_set_inh_flags(X509_VERIFY_PARAM *param, + uint32_t flags); +uint32_t X509_VERIFY_PARAM_get_inh_flags(const X509_VERIFY_PARAM *param); + +int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, + const char *name, size_t namelen); +int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, + const char *name, size_t namelen); +void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, + unsigned int flags); +unsigned int X509_VERIFY_PARAM_get_hostflags(const X509_VERIFY_PARAM *param); +char *X509_VERIFY_PARAM_get0_peername(X509_VERIFY_PARAM *); +void X509_VERIFY_PARAM_move_peername(X509_VERIFY_PARAM *, X509_VERIFY_PARAM *); +int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *param, + const char *email, size_t emaillen); +int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *param, + const unsigned char *ip, size_t iplen); +int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *param, + const char *ipasc); + +int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *param); +int X509_VERIFY_PARAM_get_auth_level(const X509_VERIFY_PARAM *param); +const char *X509_VERIFY_PARAM_get0_name(const X509_VERIFY_PARAM *param); + +int X509_VERIFY_PARAM_add0_table(X509_VERIFY_PARAM *param); +int X509_VERIFY_PARAM_get_count(void); +const X509_VERIFY_PARAM *X509_VERIFY_PARAM_get0(int id); +const X509_VERIFY_PARAM *X509_VERIFY_PARAM_lookup(const char *name); +void X509_VERIFY_PARAM_table_cleanup(void); + +/* Non positive return values are errors */ +#define X509_PCY_TREE_FAILURE -2 /* Failure to satisfy explicit policy */ +#define X509_PCY_TREE_INVALID -1 /* Inconsistent or invalid extensions */ +#define X509_PCY_TREE_INTERNAL 0 /* Internal error, most likely malloc */ + +/* + * Positive return values form a bit mask, all but the first are internal to + * the library and don't appear in results from X509_policy_check(). + */ +#define X509_PCY_TREE_VALID 1 /* The policy tree is valid */ +#define X509_PCY_TREE_EMPTY 2 /* The policy tree is empty */ +#define X509_PCY_TREE_EXPLICIT 4 /* Explicit policy required */ + +int X509_policy_check(X509_POLICY_TREE **ptree, int *pexplicit_policy, + STACK_OF(X509) *certs, + STACK_OF(ASN1_OBJECT) *policy_oids, unsigned int flags); + +void X509_policy_tree_free(X509_POLICY_TREE *tree); + +int X509_policy_tree_level_count(const X509_POLICY_TREE *tree); +X509_POLICY_LEVEL *X509_policy_tree_get0_level(const X509_POLICY_TREE *tree, + int i); + +STACK_OF(X509_POLICY_NODE) *X509_policy_tree_get0_policies(const + X509_POLICY_TREE + *tree); + +STACK_OF(X509_POLICY_NODE) *X509_policy_tree_get0_user_policies(const + X509_POLICY_TREE + *tree); + +int X509_policy_level_node_count(X509_POLICY_LEVEL *level); + +X509_POLICY_NODE *X509_policy_level_get0_node(X509_POLICY_LEVEL *level, + int i); + +const ASN1_OBJECT *X509_policy_node_get0_policy(const X509_POLICY_NODE *node); + +STACK_OF(POLICYQUALINFO) *X509_policy_node_get0_qualifiers(const + X509_POLICY_NODE + *node); +const X509_POLICY_NODE *X509_policy_node_get0_parent(const X509_POLICY_NODE + *node); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thrid-party/openssl/openssl/x509err.h b/thrid-party/openssl/openssl/x509err.h new file mode 100644 index 0000000000..0273853172 --- /dev/null +++ b/thrid-party/openssl/openssl/x509err.h @@ -0,0 +1,130 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_X509ERR_H +# define HEADER_X509ERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_X509_strings(void); + +/* + * X509 function codes. + */ +# define X509_F_ADD_CERT_DIR 100 +# define X509_F_BUILD_CHAIN 106 +# define X509_F_BY_FILE_CTRL 101 +# define X509_F_CHECK_NAME_CONSTRAINTS 149 +# define X509_F_CHECK_POLICY 145 +# define X509_F_DANE_I2D 107 +# define X509_F_DIR_CTRL 102 +# define X509_F_GET_CERT_BY_SUBJECT 103 +# define X509_F_I2D_X509_AUX 151 +# define X509_F_LOOKUP_CERTS_SK 152 +# define X509_F_NETSCAPE_SPKI_B64_DECODE 129 +# define X509_F_NETSCAPE_SPKI_B64_ENCODE 130 +# define X509_F_NEW_DIR 153 +# define X509_F_X509AT_ADD1_ATTR 135 +# define X509_F_X509V3_ADD_EXT 104 +# define X509_F_X509_ATTRIBUTE_CREATE_BY_NID 136 +# define X509_F_X509_ATTRIBUTE_CREATE_BY_OBJ 137 +# define X509_F_X509_ATTRIBUTE_CREATE_BY_TXT 140 +# define X509_F_X509_ATTRIBUTE_GET0_DATA 139 +# define X509_F_X509_ATTRIBUTE_SET1_DATA 138 +# define X509_F_X509_CHECK_PRIVATE_KEY 128 +# define X509_F_X509_CRL_DIFF 105 +# define X509_F_X509_CRL_METHOD_NEW 154 +# define X509_F_X509_CRL_PRINT_FP 147 +# define X509_F_X509_EXTENSION_CREATE_BY_NID 108 +# define X509_F_X509_EXTENSION_CREATE_BY_OBJ 109 +# define X509_F_X509_GET_PUBKEY_PARAMETERS 110 +# define X509_F_X509_LOAD_CERT_CRL_FILE 132 +# define X509_F_X509_LOAD_CERT_FILE 111 +# define X509_F_X509_LOAD_CRL_FILE 112 +# define X509_F_X509_LOOKUP_METH_NEW 160 +# define X509_F_X509_LOOKUP_NEW 155 +# define X509_F_X509_NAME_ADD_ENTRY 113 +# define X509_F_X509_NAME_CANON 156 +# define X509_F_X509_NAME_ENTRY_CREATE_BY_NID 114 +# define X509_F_X509_NAME_ENTRY_CREATE_BY_TXT 131 +# define X509_F_X509_NAME_ENTRY_SET_OBJECT 115 +# define X509_F_X509_NAME_ONELINE 116 +# define X509_F_X509_NAME_PRINT 117 +# define X509_F_X509_OBJECT_NEW 150 +# define X509_F_X509_PRINT_EX_FP 118 +# define X509_F_X509_PUBKEY_DECODE 148 +# define X509_F_X509_PUBKEY_GET0 119 +# define X509_F_X509_PUBKEY_SET 120 +# define X509_F_X509_REQ_CHECK_PRIVATE_KEY 144 +# define X509_F_X509_REQ_PRINT_EX 121 +# define X509_F_X509_REQ_PRINT_FP 122 +# define X509_F_X509_REQ_TO_X509 123 +# define X509_F_X509_STORE_ADD_CERT 124 +# define X509_F_X509_STORE_ADD_CRL 125 +# define X509_F_X509_STORE_ADD_LOOKUP 157 +# define X509_F_X509_STORE_CTX_GET1_ISSUER 146 +# define X509_F_X509_STORE_CTX_INIT 143 +# define X509_F_X509_STORE_CTX_NEW 142 +# define X509_F_X509_STORE_CTX_PURPOSE_INHERIT 134 +# define X509_F_X509_STORE_NEW 158 +# define X509_F_X509_TO_X509_REQ 126 +# define X509_F_X509_TRUST_ADD 133 +# define X509_F_X509_TRUST_SET 141 +# define X509_F_X509_VERIFY_CERT 127 +# define X509_F_X509_VERIFY_PARAM_NEW 159 + +/* + * X509 reason codes. + */ +# define X509_R_AKID_MISMATCH 110 +# define X509_R_BAD_SELECTOR 133 +# define X509_R_BAD_X509_FILETYPE 100 +# define X509_R_BASE64_DECODE_ERROR 118 +# define X509_R_CANT_CHECK_DH_KEY 114 +# define X509_R_CERT_ALREADY_IN_HASH_TABLE 101 +# define X509_R_CRL_ALREADY_DELTA 127 +# define X509_R_CRL_VERIFY_FAILURE 131 +# define X509_R_IDP_MISMATCH 128 +# define X509_R_INVALID_ATTRIBUTES 138 +# define X509_R_INVALID_DIRECTORY 113 +# define X509_R_INVALID_FIELD_NAME 119 +# define X509_R_INVALID_TRUST 123 +# define X509_R_ISSUER_MISMATCH 129 +# define X509_R_KEY_TYPE_MISMATCH 115 +# define X509_R_KEY_VALUES_MISMATCH 116 +# define X509_R_LOADING_CERT_DIR 103 +# define X509_R_LOADING_DEFAULTS 104 +# define X509_R_METHOD_NOT_SUPPORTED 124 +# define X509_R_NAME_TOO_LONG 134 +# define X509_R_NEWER_CRL_NOT_NEWER 132 +# define X509_R_NO_CERTIFICATE_FOUND 135 +# define X509_R_NO_CERTIFICATE_OR_CRL_FOUND 136 +# define X509_R_NO_CERT_SET_FOR_US_TO_VERIFY 105 +# define X509_R_NO_CRL_FOUND 137 +# define X509_R_NO_CRL_NUMBER 130 +# define X509_R_PUBLIC_KEY_DECODE_ERROR 125 +# define X509_R_PUBLIC_KEY_ENCODE_ERROR 126 +# define X509_R_SHOULD_RETRY 106 +# define X509_R_UNABLE_TO_FIND_PARAMETERS_IN_CHAIN 107 +# define X509_R_UNABLE_TO_GET_CERTS_PUBLIC_KEY 108 +# define X509_R_UNKNOWN_KEY_TYPE 117 +# define X509_R_UNKNOWN_NID 109 +# define X509_R_UNKNOWN_PURPOSE_ID 121 +# define X509_R_UNKNOWN_TRUST_ID 120 +# define X509_R_UNSUPPORTED_ALGORITHM 111 +# define X509_R_WRONG_LOOKUP_TYPE 112 +# define X509_R_WRONG_TYPE 122 + +#endif diff --git a/thrid-party/openssl/openssl/x509v3.h b/thrid-party/openssl/openssl/x509v3.h new file mode 100644 index 0000000000..6c6eca38a5 --- /dev/null +++ b/thrid-party/openssl/openssl/x509v3.h @@ -0,0 +1,937 @@ +/* + * Copyright 1999-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_X509V3_H +# define HEADER_X509V3_H + +# include +# include +# include +# include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward reference */ +struct v3_ext_method; +struct v3_ext_ctx; + +/* Useful typedefs */ + +typedef void *(*X509V3_EXT_NEW)(void); +typedef void (*X509V3_EXT_FREE) (void *); +typedef void *(*X509V3_EXT_D2I)(void *, const unsigned char **, long); +typedef int (*X509V3_EXT_I2D) (void *, unsigned char **); +typedef STACK_OF(CONF_VALUE) * + (*X509V3_EXT_I2V) (const struct v3_ext_method *method, void *ext, + STACK_OF(CONF_VALUE) *extlist); +typedef void *(*X509V3_EXT_V2I)(const struct v3_ext_method *method, + struct v3_ext_ctx *ctx, + STACK_OF(CONF_VALUE) *values); +typedef char *(*X509V3_EXT_I2S)(const struct v3_ext_method *method, + void *ext); +typedef void *(*X509V3_EXT_S2I)(const struct v3_ext_method *method, + struct v3_ext_ctx *ctx, const char *str); +typedef int (*X509V3_EXT_I2R) (const struct v3_ext_method *method, void *ext, + BIO *out, int indent); +typedef void *(*X509V3_EXT_R2I)(const struct v3_ext_method *method, + struct v3_ext_ctx *ctx, const char *str); + +/* V3 extension structure */ + +struct v3_ext_method { + int ext_nid; + int ext_flags; +/* If this is set the following four fields are ignored */ + ASN1_ITEM_EXP *it; +/* Old style ASN1 calls */ + X509V3_EXT_NEW ext_new; + X509V3_EXT_FREE ext_free; + X509V3_EXT_D2I d2i; + X509V3_EXT_I2D i2d; +/* The following pair is used for string extensions */ + X509V3_EXT_I2S i2s; + X509V3_EXT_S2I s2i; +/* The following pair is used for multi-valued extensions */ + X509V3_EXT_I2V i2v; + X509V3_EXT_V2I v2i; +/* The following are used for raw extensions */ + X509V3_EXT_I2R i2r; + X509V3_EXT_R2I r2i; + void *usr_data; /* Any extension specific data */ +}; + +typedef struct X509V3_CONF_METHOD_st { + char *(*get_string) (void *db, const char *section, const char *value); + STACK_OF(CONF_VALUE) *(*get_section) (void *db, const char *section); + void (*free_string) (void *db, char *string); + void (*free_section) (void *db, STACK_OF(CONF_VALUE) *section); +} X509V3_CONF_METHOD; + +/* Context specific info */ +struct v3_ext_ctx { +# define CTX_TEST 0x1 +# define X509V3_CTX_REPLACE 0x2 + int flags; + X509 *issuer_cert; + X509 *subject_cert; + X509_REQ *subject_req; + X509_CRL *crl; + X509V3_CONF_METHOD *db_meth; + void *db; +/* Maybe more here */ +}; + +typedef struct v3_ext_method X509V3_EXT_METHOD; + +DEFINE_STACK_OF(X509V3_EXT_METHOD) + +/* ext_flags values */ +# define X509V3_EXT_DYNAMIC 0x1 +# define X509V3_EXT_CTX_DEP 0x2 +# define X509V3_EXT_MULTILINE 0x4 + +typedef BIT_STRING_BITNAME ENUMERATED_NAMES; + +typedef struct BASIC_CONSTRAINTS_st { + int ca; + ASN1_INTEGER *pathlen; +} BASIC_CONSTRAINTS; + +typedef struct PKEY_USAGE_PERIOD_st { + ASN1_GENERALIZEDTIME *notBefore; + ASN1_GENERALIZEDTIME *notAfter; +} PKEY_USAGE_PERIOD; + +typedef struct otherName_st { + ASN1_OBJECT *type_id; + ASN1_TYPE *value; +} OTHERNAME; + +typedef struct EDIPartyName_st { + ASN1_STRING *nameAssigner; + ASN1_STRING *partyName; +} EDIPARTYNAME; + +typedef struct GENERAL_NAME_st { +# define GEN_OTHERNAME 0 +# define GEN_EMAIL 1 +# define GEN_DNS 2 +# define GEN_X400 3 +# define GEN_DIRNAME 4 +# define GEN_EDIPARTY 5 +# define GEN_URI 6 +# define GEN_IPADD 7 +# define GEN_RID 8 + int type; + union { + char *ptr; + OTHERNAME *otherName; /* otherName */ + ASN1_IA5STRING *rfc822Name; + ASN1_IA5STRING *dNSName; + ASN1_TYPE *x400Address; + X509_NAME *directoryName; + EDIPARTYNAME *ediPartyName; + ASN1_IA5STRING *uniformResourceIdentifier; + ASN1_OCTET_STRING *iPAddress; + ASN1_OBJECT *registeredID; + /* Old names */ + ASN1_OCTET_STRING *ip; /* iPAddress */ + X509_NAME *dirn; /* dirn */ + ASN1_IA5STRING *ia5; /* rfc822Name, dNSName, + * uniformResourceIdentifier */ + ASN1_OBJECT *rid; /* registeredID */ + ASN1_TYPE *other; /* x400Address */ + } d; +} GENERAL_NAME; + +typedef struct ACCESS_DESCRIPTION_st { + ASN1_OBJECT *method; + GENERAL_NAME *location; +} ACCESS_DESCRIPTION; + +typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; + +typedef STACK_OF(ASN1_OBJECT) EXTENDED_KEY_USAGE; + +typedef STACK_OF(ASN1_INTEGER) TLS_FEATURE; + +DEFINE_STACK_OF(GENERAL_NAME) +typedef STACK_OF(GENERAL_NAME) GENERAL_NAMES; +DEFINE_STACK_OF(GENERAL_NAMES) + +DEFINE_STACK_OF(ACCESS_DESCRIPTION) + +typedef struct DIST_POINT_NAME_st { + int type; + union { + GENERAL_NAMES *fullname; + STACK_OF(X509_NAME_ENTRY) *relativename; + } name; +/* If relativename then this contains the full distribution point name */ + X509_NAME *dpname; +} DIST_POINT_NAME; +/* All existing reasons */ +# define CRLDP_ALL_REASONS 0x807f + +# define CRL_REASON_NONE -1 +# define CRL_REASON_UNSPECIFIED 0 +# define CRL_REASON_KEY_COMPROMISE 1 +# define CRL_REASON_CA_COMPROMISE 2 +# define CRL_REASON_AFFILIATION_CHANGED 3 +# define CRL_REASON_SUPERSEDED 4 +# define CRL_REASON_CESSATION_OF_OPERATION 5 +# define CRL_REASON_CERTIFICATE_HOLD 6 +# define CRL_REASON_REMOVE_FROM_CRL 8 +# define CRL_REASON_PRIVILEGE_WITHDRAWN 9 +# define CRL_REASON_AA_COMPROMISE 10 + +struct DIST_POINT_st { + DIST_POINT_NAME *distpoint; + ASN1_BIT_STRING *reasons; + GENERAL_NAMES *CRLissuer; + int dp_reasons; +}; + +typedef STACK_OF(DIST_POINT) CRL_DIST_POINTS; + +DEFINE_STACK_OF(DIST_POINT) + +struct AUTHORITY_KEYID_st { + ASN1_OCTET_STRING *keyid; + GENERAL_NAMES *issuer; + ASN1_INTEGER *serial; +}; + +/* Strong extranet structures */ + +typedef struct SXNET_ID_st { + ASN1_INTEGER *zone; + ASN1_OCTET_STRING *user; +} SXNETID; + +DEFINE_STACK_OF(SXNETID) + +typedef struct SXNET_st { + ASN1_INTEGER *version; + STACK_OF(SXNETID) *ids; +} SXNET; + +typedef struct NOTICEREF_st { + ASN1_STRING *organization; + STACK_OF(ASN1_INTEGER) *noticenos; +} NOTICEREF; + +typedef struct USERNOTICE_st { + NOTICEREF *noticeref; + ASN1_STRING *exptext; +} USERNOTICE; + +typedef struct POLICYQUALINFO_st { + ASN1_OBJECT *pqualid; + union { + ASN1_IA5STRING *cpsuri; + USERNOTICE *usernotice; + ASN1_TYPE *other; + } d; +} POLICYQUALINFO; + +DEFINE_STACK_OF(POLICYQUALINFO) + +typedef struct POLICYINFO_st { + ASN1_OBJECT *policyid; + STACK_OF(POLICYQUALINFO) *qualifiers; +} POLICYINFO; + +typedef STACK_OF(POLICYINFO) CERTIFICATEPOLICIES; + +DEFINE_STACK_OF(POLICYINFO) + +typedef struct POLICY_MAPPING_st { + ASN1_OBJECT *issuerDomainPolicy; + ASN1_OBJECT *subjectDomainPolicy; +} POLICY_MAPPING; + +DEFINE_STACK_OF(POLICY_MAPPING) + +typedef STACK_OF(POLICY_MAPPING) POLICY_MAPPINGS; + +typedef struct GENERAL_SUBTREE_st { + GENERAL_NAME *base; + ASN1_INTEGER *minimum; + ASN1_INTEGER *maximum; +} GENERAL_SUBTREE; + +DEFINE_STACK_OF(GENERAL_SUBTREE) + +struct NAME_CONSTRAINTS_st { + STACK_OF(GENERAL_SUBTREE) *permittedSubtrees; + STACK_OF(GENERAL_SUBTREE) *excludedSubtrees; +}; + +typedef struct POLICY_CONSTRAINTS_st { + ASN1_INTEGER *requireExplicitPolicy; + ASN1_INTEGER *inhibitPolicyMapping; +} POLICY_CONSTRAINTS; + +/* Proxy certificate structures, see RFC 3820 */ +typedef struct PROXY_POLICY_st { + ASN1_OBJECT *policyLanguage; + ASN1_OCTET_STRING *policy; +} PROXY_POLICY; + +typedef struct PROXY_CERT_INFO_EXTENSION_st { + ASN1_INTEGER *pcPathLengthConstraint; + PROXY_POLICY *proxyPolicy; +} PROXY_CERT_INFO_EXTENSION; + +DECLARE_ASN1_FUNCTIONS(PROXY_POLICY) +DECLARE_ASN1_FUNCTIONS(PROXY_CERT_INFO_EXTENSION) + +struct ISSUING_DIST_POINT_st { + DIST_POINT_NAME *distpoint; + int onlyuser; + int onlyCA; + ASN1_BIT_STRING *onlysomereasons; + int indirectCRL; + int onlyattr; +}; + +/* Values in idp_flags field */ +/* IDP present */ +# define IDP_PRESENT 0x1 +/* IDP values inconsistent */ +# define IDP_INVALID 0x2 +/* onlyuser true */ +# define IDP_ONLYUSER 0x4 +/* onlyCA true */ +# define IDP_ONLYCA 0x8 +/* onlyattr true */ +# define IDP_ONLYATTR 0x10 +/* indirectCRL true */ +# define IDP_INDIRECT 0x20 +/* onlysomereasons present */ +# define IDP_REASONS 0x40 + +# define X509V3_conf_err(val) ERR_add_error_data(6, \ + "section:", (val)->section, \ + ",name:", (val)->name, ",value:", (val)->value) + +# define X509V3_set_ctx_test(ctx) \ + X509V3_set_ctx(ctx, NULL, NULL, NULL, NULL, CTX_TEST) +# define X509V3_set_ctx_nodb(ctx) (ctx)->db = NULL; + +# define EXT_BITSTRING(nid, table) { nid, 0, ASN1_ITEM_ref(ASN1_BIT_STRING), \ + 0,0,0,0, \ + 0,0, \ + (X509V3_EXT_I2V)i2v_ASN1_BIT_STRING, \ + (X509V3_EXT_V2I)v2i_ASN1_BIT_STRING, \ + NULL, NULL, \ + table} + +# define EXT_IA5STRING(nid) { nid, 0, ASN1_ITEM_ref(ASN1_IA5STRING), \ + 0,0,0,0, \ + (X509V3_EXT_I2S)i2s_ASN1_IA5STRING, \ + (X509V3_EXT_S2I)s2i_ASN1_IA5STRING, \ + 0,0,0,0, \ + NULL} + +# define EXT_END { -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +/* X509_PURPOSE stuff */ + +# define EXFLAG_BCONS 0x1 +# define EXFLAG_KUSAGE 0x2 +# define EXFLAG_XKUSAGE 0x4 +# define EXFLAG_NSCERT 0x8 + +# define EXFLAG_CA 0x10 +/* Really self issued not necessarily self signed */ +# define EXFLAG_SI 0x20 +# define EXFLAG_V1 0x40 +# define EXFLAG_INVALID 0x80 +/* EXFLAG_SET is set to indicate that some values have been precomputed */ +# define EXFLAG_SET 0x100 +# define EXFLAG_CRITICAL 0x200 +# define EXFLAG_PROXY 0x400 + +# define EXFLAG_INVALID_POLICY 0x800 +# define EXFLAG_FRESHEST 0x1000 +/* Self signed */ +# define EXFLAG_SS 0x2000 + +# define KU_DIGITAL_SIGNATURE 0x0080 +# define KU_NON_REPUDIATION 0x0040 +# define KU_KEY_ENCIPHERMENT 0x0020 +# define KU_DATA_ENCIPHERMENT 0x0010 +# define KU_KEY_AGREEMENT 0x0008 +# define KU_KEY_CERT_SIGN 0x0004 +# define KU_CRL_SIGN 0x0002 +# define KU_ENCIPHER_ONLY 0x0001 +# define KU_DECIPHER_ONLY 0x8000 + +# define NS_SSL_CLIENT 0x80 +# define NS_SSL_SERVER 0x40 +# define NS_SMIME 0x20 +# define NS_OBJSIGN 0x10 +# define NS_SSL_CA 0x04 +# define NS_SMIME_CA 0x02 +# define NS_OBJSIGN_CA 0x01 +# define NS_ANY_CA (NS_SSL_CA|NS_SMIME_CA|NS_OBJSIGN_CA) + +# define XKU_SSL_SERVER 0x1 +# define XKU_SSL_CLIENT 0x2 +# define XKU_SMIME 0x4 +# define XKU_CODE_SIGN 0x8 +# define XKU_SGC 0x10 +# define XKU_OCSP_SIGN 0x20 +# define XKU_TIMESTAMP 0x40 +# define XKU_DVCS 0x80 +# define XKU_ANYEKU 0x100 + +# define X509_PURPOSE_DYNAMIC 0x1 +# define X509_PURPOSE_DYNAMIC_NAME 0x2 + +typedef struct x509_purpose_st { + int purpose; + int trust; /* Default trust ID */ + int flags; + int (*check_purpose) (const struct x509_purpose_st *, const X509 *, int); + char *name; + char *sname; + void *usr_data; +} X509_PURPOSE; + +# define X509_PURPOSE_SSL_CLIENT 1 +# define X509_PURPOSE_SSL_SERVER 2 +# define X509_PURPOSE_NS_SSL_SERVER 3 +# define X509_PURPOSE_SMIME_SIGN 4 +# define X509_PURPOSE_SMIME_ENCRYPT 5 +# define X509_PURPOSE_CRL_SIGN 6 +# define X509_PURPOSE_ANY 7 +# define X509_PURPOSE_OCSP_HELPER 8 +# define X509_PURPOSE_TIMESTAMP_SIGN 9 + +# define X509_PURPOSE_MIN 1 +# define X509_PURPOSE_MAX 9 + +/* Flags for X509V3_EXT_print() */ + +# define X509V3_EXT_UNKNOWN_MASK (0xfL << 16) +/* Return error for unknown extensions */ +# define X509V3_EXT_DEFAULT 0 +/* Print error for unknown extensions */ +# define X509V3_EXT_ERROR_UNKNOWN (1L << 16) +/* ASN1 parse unknown extensions */ +# define X509V3_EXT_PARSE_UNKNOWN (2L << 16) +/* BIO_dump unknown extensions */ +# define X509V3_EXT_DUMP_UNKNOWN (3L << 16) + +/* Flags for X509V3_add1_i2d */ + +# define X509V3_ADD_OP_MASK 0xfL +# define X509V3_ADD_DEFAULT 0L +# define X509V3_ADD_APPEND 1L +# define X509V3_ADD_REPLACE 2L +# define X509V3_ADD_REPLACE_EXISTING 3L +# define X509V3_ADD_KEEP_EXISTING 4L +# define X509V3_ADD_DELETE 5L +# define X509V3_ADD_SILENT 0x10 + +DEFINE_STACK_OF(X509_PURPOSE) + +DECLARE_ASN1_FUNCTIONS(BASIC_CONSTRAINTS) + +DECLARE_ASN1_FUNCTIONS(SXNET) +DECLARE_ASN1_FUNCTIONS(SXNETID) + +int SXNET_add_id_asc(SXNET **psx, const char *zone, const char *user, int userlen); +int SXNET_add_id_ulong(SXNET **psx, unsigned long lzone, const char *user, + int userlen); +int SXNET_add_id_INTEGER(SXNET **psx, ASN1_INTEGER *izone, const char *user, + int userlen); + +ASN1_OCTET_STRING *SXNET_get_id_asc(SXNET *sx, const char *zone); +ASN1_OCTET_STRING *SXNET_get_id_ulong(SXNET *sx, unsigned long lzone); +ASN1_OCTET_STRING *SXNET_get_id_INTEGER(SXNET *sx, ASN1_INTEGER *zone); + +DECLARE_ASN1_FUNCTIONS(AUTHORITY_KEYID) + +DECLARE_ASN1_FUNCTIONS(PKEY_USAGE_PERIOD) + +DECLARE_ASN1_FUNCTIONS(GENERAL_NAME) +GENERAL_NAME *GENERAL_NAME_dup(GENERAL_NAME *a); +int GENERAL_NAME_cmp(GENERAL_NAME *a, GENERAL_NAME *b); + +ASN1_BIT_STRING *v2i_ASN1_BIT_STRING(X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, + STACK_OF(CONF_VALUE) *nval); +STACK_OF(CONF_VALUE) *i2v_ASN1_BIT_STRING(X509V3_EXT_METHOD *method, + ASN1_BIT_STRING *bits, + STACK_OF(CONF_VALUE) *extlist); +char *i2s_ASN1_IA5STRING(X509V3_EXT_METHOD *method, ASN1_IA5STRING *ia5); +ASN1_IA5STRING *s2i_ASN1_IA5STRING(X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, const char *str); + +STACK_OF(CONF_VALUE) *i2v_GENERAL_NAME(X509V3_EXT_METHOD *method, + GENERAL_NAME *gen, + STACK_OF(CONF_VALUE) *ret); +int GENERAL_NAME_print(BIO *out, GENERAL_NAME *gen); + +DECLARE_ASN1_FUNCTIONS(GENERAL_NAMES) + +STACK_OF(CONF_VALUE) *i2v_GENERAL_NAMES(X509V3_EXT_METHOD *method, + GENERAL_NAMES *gen, + STACK_OF(CONF_VALUE) *extlist); +GENERAL_NAMES *v2i_GENERAL_NAMES(const X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval); + +DECLARE_ASN1_FUNCTIONS(OTHERNAME) +DECLARE_ASN1_FUNCTIONS(EDIPARTYNAME) +int OTHERNAME_cmp(OTHERNAME *a, OTHERNAME *b); +void GENERAL_NAME_set0_value(GENERAL_NAME *a, int type, void *value); +void *GENERAL_NAME_get0_value(const GENERAL_NAME *a, int *ptype); +int GENERAL_NAME_set0_othername(GENERAL_NAME *gen, + ASN1_OBJECT *oid, ASN1_TYPE *value); +int GENERAL_NAME_get0_otherName(const GENERAL_NAME *gen, + ASN1_OBJECT **poid, ASN1_TYPE **pvalue); + +char *i2s_ASN1_OCTET_STRING(X509V3_EXT_METHOD *method, + const ASN1_OCTET_STRING *ia5); +ASN1_OCTET_STRING *s2i_ASN1_OCTET_STRING(X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, const char *str); + +DECLARE_ASN1_FUNCTIONS(EXTENDED_KEY_USAGE) +int i2a_ACCESS_DESCRIPTION(BIO *bp, const ACCESS_DESCRIPTION *a); + +DECLARE_ASN1_ALLOC_FUNCTIONS(TLS_FEATURE) + +DECLARE_ASN1_FUNCTIONS(CERTIFICATEPOLICIES) +DECLARE_ASN1_FUNCTIONS(POLICYINFO) +DECLARE_ASN1_FUNCTIONS(POLICYQUALINFO) +DECLARE_ASN1_FUNCTIONS(USERNOTICE) +DECLARE_ASN1_FUNCTIONS(NOTICEREF) + +DECLARE_ASN1_FUNCTIONS(CRL_DIST_POINTS) +DECLARE_ASN1_FUNCTIONS(DIST_POINT) +DECLARE_ASN1_FUNCTIONS(DIST_POINT_NAME) +DECLARE_ASN1_FUNCTIONS(ISSUING_DIST_POINT) + +int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, X509_NAME *iname); + +int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc); +int NAME_CONSTRAINTS_check_CN(X509 *x, NAME_CONSTRAINTS *nc); + +DECLARE_ASN1_FUNCTIONS(ACCESS_DESCRIPTION) +DECLARE_ASN1_FUNCTIONS(AUTHORITY_INFO_ACCESS) + +DECLARE_ASN1_ITEM(POLICY_MAPPING) +DECLARE_ASN1_ALLOC_FUNCTIONS(POLICY_MAPPING) +DECLARE_ASN1_ITEM(POLICY_MAPPINGS) + +DECLARE_ASN1_ITEM(GENERAL_SUBTREE) +DECLARE_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE) + +DECLARE_ASN1_ITEM(NAME_CONSTRAINTS) +DECLARE_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS) + +DECLARE_ASN1_ALLOC_FUNCTIONS(POLICY_CONSTRAINTS) +DECLARE_ASN1_ITEM(POLICY_CONSTRAINTS) + +GENERAL_NAME *a2i_GENERAL_NAME(GENERAL_NAME *out, + const X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, int gen_type, + const char *value, int is_nc); + +# ifdef HEADER_CONF_H +GENERAL_NAME *v2i_GENERAL_NAME(const X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, CONF_VALUE *cnf); +GENERAL_NAME *v2i_GENERAL_NAME_ex(GENERAL_NAME *out, + const X509V3_EXT_METHOD *method, + X509V3_CTX *ctx, CONF_VALUE *cnf, + int is_nc); +void X509V3_conf_free(CONF_VALUE *val); + +X509_EXTENSION *X509V3_EXT_nconf_nid(CONF *conf, X509V3_CTX *ctx, int ext_nid, + const char *value); +X509_EXTENSION *X509V3_EXT_nconf(CONF *conf, X509V3_CTX *ctx, const char *name, + const char *value); +int X509V3_EXT_add_nconf_sk(CONF *conf, X509V3_CTX *ctx, const char *section, + STACK_OF(X509_EXTENSION) **sk); +int X509V3_EXT_add_nconf(CONF *conf, X509V3_CTX *ctx, const char *section, + X509 *cert); +int X509V3_EXT_REQ_add_nconf(CONF *conf, X509V3_CTX *ctx, const char *section, + X509_REQ *req); +int X509V3_EXT_CRL_add_nconf(CONF *conf, X509V3_CTX *ctx, const char *section, + X509_CRL *crl); + +X509_EXTENSION *X509V3_EXT_conf_nid(LHASH_OF(CONF_VALUE) *conf, + X509V3_CTX *ctx, int ext_nid, + const char *value); +X509_EXTENSION *X509V3_EXT_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, + const char *name, const char *value); +int X509V3_EXT_add_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, + const char *section, X509 *cert); +int X509V3_EXT_REQ_add_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, + const char *section, X509_REQ *req); +int X509V3_EXT_CRL_add_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, + const char *section, X509_CRL *crl); + +int X509V3_add_value_bool_nf(const char *name, int asn1_bool, + STACK_OF(CONF_VALUE) **extlist); +int X509V3_get_value_bool(const CONF_VALUE *value, int *asn1_bool); +int X509V3_get_value_int(const CONF_VALUE *value, ASN1_INTEGER **aint); +void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf); +void X509V3_set_conf_lhash(X509V3_CTX *ctx, LHASH_OF(CONF_VALUE) *lhash); +# endif + +char *X509V3_get_string(X509V3_CTX *ctx, const char *name, const char *section); +STACK_OF(CONF_VALUE) *X509V3_get_section(X509V3_CTX *ctx, const char *section); +void X509V3_string_free(X509V3_CTX *ctx, char *str); +void X509V3_section_free(X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *section); +void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject, + X509_REQ *req, X509_CRL *crl, int flags); + +int X509V3_add_value(const char *name, const char *value, + STACK_OF(CONF_VALUE) **extlist); +int X509V3_add_value_uchar(const char *name, const unsigned char *value, + STACK_OF(CONF_VALUE) **extlist); +int X509V3_add_value_bool(const char *name, int asn1_bool, + STACK_OF(CONF_VALUE) **extlist); +int X509V3_add_value_int(const char *name, const ASN1_INTEGER *aint, + STACK_OF(CONF_VALUE) **extlist); +char *i2s_ASN1_INTEGER(X509V3_EXT_METHOD *meth, const ASN1_INTEGER *aint); +ASN1_INTEGER *s2i_ASN1_INTEGER(X509V3_EXT_METHOD *meth, const char *value); +char *i2s_ASN1_ENUMERATED(X509V3_EXT_METHOD *meth, const ASN1_ENUMERATED *aint); +char *i2s_ASN1_ENUMERATED_TABLE(X509V3_EXT_METHOD *meth, + const ASN1_ENUMERATED *aint); +int X509V3_EXT_add(X509V3_EXT_METHOD *ext); +int X509V3_EXT_add_list(X509V3_EXT_METHOD *extlist); +int X509V3_EXT_add_alias(int nid_to, int nid_from); +void X509V3_EXT_cleanup(void); + +const X509V3_EXT_METHOD *X509V3_EXT_get(X509_EXTENSION *ext); +const X509V3_EXT_METHOD *X509V3_EXT_get_nid(int nid); +int X509V3_add_standard_extensions(void); +STACK_OF(CONF_VALUE) *X509V3_parse_list(const char *line); +void *X509V3_EXT_d2i(X509_EXTENSION *ext); +void *X509V3_get_d2i(const STACK_OF(X509_EXTENSION) *x, int nid, int *crit, + int *idx); + +X509_EXTENSION *X509V3_EXT_i2d(int ext_nid, int crit, void *ext_struc); +int X509V3_add1_i2d(STACK_OF(X509_EXTENSION) **x, int nid, void *value, + int crit, unsigned long flags); + +#if OPENSSL_API_COMPAT < 0x10100000L +/* The new declarations are in crypto.h, but the old ones were here. */ +# define hex_to_string OPENSSL_buf2hexstr +# define string_to_hex OPENSSL_hexstr2buf +#endif + +void X509V3_EXT_val_prn(BIO *out, STACK_OF(CONF_VALUE) *val, int indent, + int ml); +int X509V3_EXT_print(BIO *out, X509_EXTENSION *ext, unsigned long flag, + int indent); +#ifndef OPENSSL_NO_STDIO +int X509V3_EXT_print_fp(FILE *out, X509_EXTENSION *ext, int flag, int indent); +#endif +int X509V3_extensions_print(BIO *out, const char *title, + const STACK_OF(X509_EXTENSION) *exts, + unsigned long flag, int indent); + +int X509_check_ca(X509 *x); +int X509_check_purpose(X509 *x, int id, int ca); +int X509_supported_extension(X509_EXTENSION *ex); +int X509_PURPOSE_set(int *p, int purpose); +int X509_check_issued(X509 *issuer, X509 *subject); +int X509_check_akid(X509 *issuer, AUTHORITY_KEYID *akid); +void X509_set_proxy_flag(X509 *x); +void X509_set_proxy_pathlen(X509 *x, long l); +long X509_get_proxy_pathlen(X509 *x); + +uint32_t X509_get_extension_flags(X509 *x); +uint32_t X509_get_key_usage(X509 *x); +uint32_t X509_get_extended_key_usage(X509 *x); +const ASN1_OCTET_STRING *X509_get0_subject_key_id(X509 *x); +const ASN1_OCTET_STRING *X509_get0_authority_key_id(X509 *x); +const GENERAL_NAMES *X509_get0_authority_issuer(X509 *x); +const ASN1_INTEGER *X509_get0_authority_serial(X509 *x); + +int X509_PURPOSE_get_count(void); +X509_PURPOSE *X509_PURPOSE_get0(int idx); +int X509_PURPOSE_get_by_sname(const char *sname); +int X509_PURPOSE_get_by_id(int id); +int X509_PURPOSE_add(int id, int trust, int flags, + int (*ck) (const X509_PURPOSE *, const X509 *, int), + const char *name, const char *sname, void *arg); +char *X509_PURPOSE_get0_name(const X509_PURPOSE *xp); +char *X509_PURPOSE_get0_sname(const X509_PURPOSE *xp); +int X509_PURPOSE_get_trust(const X509_PURPOSE *xp); +void X509_PURPOSE_cleanup(void); +int X509_PURPOSE_get_id(const X509_PURPOSE *); + +STACK_OF(OPENSSL_STRING) *X509_get1_email(X509 *x); +STACK_OF(OPENSSL_STRING) *X509_REQ_get1_email(X509_REQ *x); +void X509_email_free(STACK_OF(OPENSSL_STRING) *sk); +STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); +/* Flags for X509_check_* functions */ + +/* + * Always check subject name for host match even if subject alt names present + */ +# define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 0x1 +/* Disable wildcard matching for dnsName fields and common name. */ +# define X509_CHECK_FLAG_NO_WILDCARDS 0x2 +/* Wildcards must not match a partial label. */ +# define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0x4 +/* Allow (non-partial) wildcards to match multiple labels. */ +# define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS 0x8 +/* Constraint verifier subdomain patterns to match a single labels. */ +# define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0x10 +/* Never check the subject CN */ +# define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 +/* + * Match reference identifiers starting with "." to any sub-domain. + * This is a non-public flag, turned on implicitly when the subject + * reference identity is a DNS name. + */ +# define _X509_CHECK_FLAG_DOT_SUBDOMAINS 0x8000 + +int X509_check_host(X509 *x, const char *chk, size_t chklen, + unsigned int flags, char **peername); +int X509_check_email(X509 *x, const char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags); + +ASN1_OCTET_STRING *a2i_IPADDRESS(const char *ipasc); +ASN1_OCTET_STRING *a2i_IPADDRESS_NC(const char *ipasc); +int X509V3_NAME_from_section(X509_NAME *nm, STACK_OF(CONF_VALUE) *dn_sk, + unsigned long chtype); + +void X509_POLICY_NODE_print(BIO *out, X509_POLICY_NODE *node, int indent); +DEFINE_STACK_OF(X509_POLICY_NODE) + +#ifndef OPENSSL_NO_RFC3779 +typedef struct ASRange_st { + ASN1_INTEGER *min, *max; +} ASRange; + +# define ASIdOrRange_id 0 +# define ASIdOrRange_range 1 + +typedef struct ASIdOrRange_st { + int type; + union { + ASN1_INTEGER *id; + ASRange *range; + } u; +} ASIdOrRange; + +typedef STACK_OF(ASIdOrRange) ASIdOrRanges; +DEFINE_STACK_OF(ASIdOrRange) + +# define ASIdentifierChoice_inherit 0 +# define ASIdentifierChoice_asIdsOrRanges 1 + +typedef struct ASIdentifierChoice_st { + int type; + union { + ASN1_NULL *inherit; + ASIdOrRanges *asIdsOrRanges; + } u; +} ASIdentifierChoice; + +typedef struct ASIdentifiers_st { + ASIdentifierChoice *asnum, *rdi; +} ASIdentifiers; + +DECLARE_ASN1_FUNCTIONS(ASRange) +DECLARE_ASN1_FUNCTIONS(ASIdOrRange) +DECLARE_ASN1_FUNCTIONS(ASIdentifierChoice) +DECLARE_ASN1_FUNCTIONS(ASIdentifiers) + +typedef struct IPAddressRange_st { + ASN1_BIT_STRING *min, *max; +} IPAddressRange; + +# define IPAddressOrRange_addressPrefix 0 +# define IPAddressOrRange_addressRange 1 + +typedef struct IPAddressOrRange_st { + int type; + union { + ASN1_BIT_STRING *addressPrefix; + IPAddressRange *addressRange; + } u; +} IPAddressOrRange; + +typedef STACK_OF(IPAddressOrRange) IPAddressOrRanges; +DEFINE_STACK_OF(IPAddressOrRange) + +# define IPAddressChoice_inherit 0 +# define IPAddressChoice_addressesOrRanges 1 + +typedef struct IPAddressChoice_st { + int type; + union { + ASN1_NULL *inherit; + IPAddressOrRanges *addressesOrRanges; + } u; +} IPAddressChoice; + +typedef struct IPAddressFamily_st { + ASN1_OCTET_STRING *addressFamily; + IPAddressChoice *ipAddressChoice; +} IPAddressFamily; + +typedef STACK_OF(IPAddressFamily) IPAddrBlocks; +DEFINE_STACK_OF(IPAddressFamily) + +DECLARE_ASN1_FUNCTIONS(IPAddressRange) +DECLARE_ASN1_FUNCTIONS(IPAddressOrRange) +DECLARE_ASN1_FUNCTIONS(IPAddressChoice) +DECLARE_ASN1_FUNCTIONS(IPAddressFamily) + +/* + * API tag for elements of the ASIdentifer SEQUENCE. + */ +# define V3_ASID_ASNUM 0 +# define V3_ASID_RDI 1 + +/* + * AFI values, assigned by IANA. It'd be nice to make the AFI + * handling code totally generic, but there are too many little things + * that would need to be defined for other address families for it to + * be worth the trouble. + */ +# define IANA_AFI_IPV4 1 +# define IANA_AFI_IPV6 2 + +/* + * Utilities to construct and extract values from RFC3779 extensions, + * since some of the encodings (particularly for IP address prefixes + * and ranges) are a bit tedious to work with directly. + */ +int X509v3_asid_add_inherit(ASIdentifiers *asid, int which); +int X509v3_asid_add_id_or_range(ASIdentifiers *asid, int which, + ASN1_INTEGER *min, ASN1_INTEGER *max); +int X509v3_addr_add_inherit(IPAddrBlocks *addr, + const unsigned afi, const unsigned *safi); +int X509v3_addr_add_prefix(IPAddrBlocks *addr, + const unsigned afi, const unsigned *safi, + unsigned char *a, const int prefixlen); +int X509v3_addr_add_range(IPAddrBlocks *addr, + const unsigned afi, const unsigned *safi, + unsigned char *min, unsigned char *max); +unsigned X509v3_addr_get_afi(const IPAddressFamily *f); +int X509v3_addr_get_range(IPAddressOrRange *aor, const unsigned afi, + unsigned char *min, unsigned char *max, + const int length); + +/* + * Canonical forms. + */ +int X509v3_asid_is_canonical(ASIdentifiers *asid); +int X509v3_addr_is_canonical(IPAddrBlocks *addr); +int X509v3_asid_canonize(ASIdentifiers *asid); +int X509v3_addr_canonize(IPAddrBlocks *addr); + +/* + * Tests for inheritance and containment. + */ +int X509v3_asid_inherits(ASIdentifiers *asid); +int X509v3_addr_inherits(IPAddrBlocks *addr); +int X509v3_asid_subset(ASIdentifiers *a, ASIdentifiers *b); +int X509v3_addr_subset(IPAddrBlocks *a, IPAddrBlocks *b); + +/* + * Check whether RFC 3779 extensions nest properly in chains. + */ +int X509v3_asid_validate_path(X509_STORE_CTX *); +int X509v3_addr_validate_path(X509_STORE_CTX *); +int X509v3_asid_validate_resource_set(STACK_OF(X509) *chain, + ASIdentifiers *ext, + int allow_inheritance); +int X509v3_addr_validate_resource_set(STACK_OF(X509) *chain, + IPAddrBlocks *ext, int allow_inheritance); + +#endif /* OPENSSL_NO_RFC3779 */ + +DEFINE_STACK_OF(ASN1_STRING) + +/* + * Admission Syntax + */ +typedef struct NamingAuthority_st NAMING_AUTHORITY; +typedef struct ProfessionInfo_st PROFESSION_INFO; +typedef struct Admissions_st ADMISSIONS; +typedef struct AdmissionSyntax_st ADMISSION_SYNTAX; +DECLARE_ASN1_FUNCTIONS(NAMING_AUTHORITY) +DECLARE_ASN1_FUNCTIONS(PROFESSION_INFO) +DECLARE_ASN1_FUNCTIONS(ADMISSIONS) +DECLARE_ASN1_FUNCTIONS(ADMISSION_SYNTAX) +DEFINE_STACK_OF(ADMISSIONS) +DEFINE_STACK_OF(PROFESSION_INFO) +typedef STACK_OF(PROFESSION_INFO) PROFESSION_INFOS; + +const ASN1_OBJECT *NAMING_AUTHORITY_get0_authorityId( + const NAMING_AUTHORITY *n); +const ASN1_IA5STRING *NAMING_AUTHORITY_get0_authorityURL( + const NAMING_AUTHORITY *n); +const ASN1_STRING *NAMING_AUTHORITY_get0_authorityText( + const NAMING_AUTHORITY *n); +void NAMING_AUTHORITY_set0_authorityId(NAMING_AUTHORITY *n, + ASN1_OBJECT* namingAuthorityId); +void NAMING_AUTHORITY_set0_authorityURL(NAMING_AUTHORITY *n, + ASN1_IA5STRING* namingAuthorityUrl); +void NAMING_AUTHORITY_set0_authorityText(NAMING_AUTHORITY *n, + ASN1_STRING* namingAuthorityText); + +const GENERAL_NAME *ADMISSION_SYNTAX_get0_admissionAuthority( + const ADMISSION_SYNTAX *as); +void ADMISSION_SYNTAX_set0_admissionAuthority( + ADMISSION_SYNTAX *as, GENERAL_NAME *aa); +const STACK_OF(ADMISSIONS) *ADMISSION_SYNTAX_get0_contentsOfAdmissions( + const ADMISSION_SYNTAX *as); +void ADMISSION_SYNTAX_set0_contentsOfAdmissions( + ADMISSION_SYNTAX *as, STACK_OF(ADMISSIONS) *a); +const GENERAL_NAME *ADMISSIONS_get0_admissionAuthority(const ADMISSIONS *a); +void ADMISSIONS_set0_admissionAuthority(ADMISSIONS *a, GENERAL_NAME *aa); +const NAMING_AUTHORITY *ADMISSIONS_get0_namingAuthority(const ADMISSIONS *a); +void ADMISSIONS_set0_namingAuthority(ADMISSIONS *a, NAMING_AUTHORITY *na); +const PROFESSION_INFOS *ADMISSIONS_get0_professionInfos(const ADMISSIONS *a); +void ADMISSIONS_set0_professionInfos(ADMISSIONS *a, PROFESSION_INFOS *pi); +const ASN1_OCTET_STRING *PROFESSION_INFO_get0_addProfessionInfo( + const PROFESSION_INFO *pi); +void PROFESSION_INFO_set0_addProfessionInfo( + PROFESSION_INFO *pi, ASN1_OCTET_STRING *aos); +const NAMING_AUTHORITY *PROFESSION_INFO_get0_namingAuthority( + const PROFESSION_INFO *pi); +void PROFESSION_INFO_set0_namingAuthority( + PROFESSION_INFO *pi, NAMING_AUTHORITY *na); +const STACK_OF(ASN1_STRING) *PROFESSION_INFO_get0_professionItems( + const PROFESSION_INFO *pi); +void PROFESSION_INFO_set0_professionItems( + PROFESSION_INFO *pi, STACK_OF(ASN1_STRING) *as); +const STACK_OF(ASN1_OBJECT) *PROFESSION_INFO_get0_professionOIDs( + const PROFESSION_INFO *pi); +void PROFESSION_INFO_set0_professionOIDs( + PROFESSION_INFO *pi, STACK_OF(ASN1_OBJECT) *po); +const ASN1_PRINTABLESTRING *PROFESSION_INFO_get0_registrationNumber( + const PROFESSION_INFO *pi); +void PROFESSION_INFO_set0_registrationNumber( + PROFESSION_INFO *pi, ASN1_PRINTABLESTRING *rn); + +# ifdef __cplusplus +} +# endif +#endif diff --git a/thrid-party/openssl/openssl/x509v3err.h b/thrid-party/openssl/openssl/x509v3err.h new file mode 100644 index 0000000000..5f25442f12 --- /dev/null +++ b/thrid-party/openssl/openssl/x509v3err.h @@ -0,0 +1,162 @@ +/* + * Generated by util/mkerr.pl DO NOT EDIT + * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef HEADER_X509V3ERR_H +# define HEADER_X509V3ERR_H + +# ifndef HEADER_SYMHACKS_H +# include +# endif + +# ifdef __cplusplus +extern "C" +# endif +int ERR_load_X509V3_strings(void); + +/* + * X509V3 function codes. + */ +# define X509V3_F_A2I_GENERAL_NAME 164 +# define X509V3_F_ADDR_VALIDATE_PATH_INTERNAL 166 +# define X509V3_F_ASIDENTIFIERCHOICE_CANONIZE 161 +# define X509V3_F_ASIDENTIFIERCHOICE_IS_CANONICAL 162 +# define X509V3_F_BIGNUM_TO_STRING 167 +# define X509V3_F_COPY_EMAIL 122 +# define X509V3_F_COPY_ISSUER 123 +# define X509V3_F_DO_DIRNAME 144 +# define X509V3_F_DO_EXT_I2D 135 +# define X509V3_F_DO_EXT_NCONF 151 +# define X509V3_F_GNAMES_FROM_SECTNAME 156 +# define X509V3_F_I2S_ASN1_ENUMERATED 121 +# define X509V3_F_I2S_ASN1_IA5STRING 149 +# define X509V3_F_I2S_ASN1_INTEGER 120 +# define X509V3_F_I2V_AUTHORITY_INFO_ACCESS 138 +# define X509V3_F_LEVEL_ADD_NODE 168 +# define X509V3_F_NOTICE_SECTION 132 +# define X509V3_F_NREF_NOS 133 +# define X509V3_F_POLICY_CACHE_CREATE 169 +# define X509V3_F_POLICY_CACHE_NEW 170 +# define X509V3_F_POLICY_DATA_NEW 171 +# define X509V3_F_POLICY_SECTION 131 +# define X509V3_F_PROCESS_PCI_VALUE 150 +# define X509V3_F_R2I_CERTPOL 130 +# define X509V3_F_R2I_PCI 155 +# define X509V3_F_S2I_ASN1_IA5STRING 100 +# define X509V3_F_S2I_ASN1_INTEGER 108 +# define X509V3_F_S2I_ASN1_OCTET_STRING 112 +# define X509V3_F_S2I_SKEY_ID 115 +# define X509V3_F_SET_DIST_POINT_NAME 158 +# define X509V3_F_SXNET_ADD_ID_ASC 125 +# define X509V3_F_SXNET_ADD_ID_INTEGER 126 +# define X509V3_F_SXNET_ADD_ID_ULONG 127 +# define X509V3_F_SXNET_GET_ID_ASC 128 +# define X509V3_F_SXNET_GET_ID_ULONG 129 +# define X509V3_F_TREE_INIT 172 +# define X509V3_F_V2I_ASIDENTIFIERS 163 +# define X509V3_F_V2I_ASN1_BIT_STRING 101 +# define X509V3_F_V2I_AUTHORITY_INFO_ACCESS 139 +# define X509V3_F_V2I_AUTHORITY_KEYID 119 +# define X509V3_F_V2I_BASIC_CONSTRAINTS 102 +# define X509V3_F_V2I_CRLD 134 +# define X509V3_F_V2I_EXTENDED_KEY_USAGE 103 +# define X509V3_F_V2I_GENERAL_NAMES 118 +# define X509V3_F_V2I_GENERAL_NAME_EX 117 +# define X509V3_F_V2I_IDP 157 +# define X509V3_F_V2I_IPADDRBLOCKS 159 +# define X509V3_F_V2I_ISSUER_ALT 153 +# define X509V3_F_V2I_NAME_CONSTRAINTS 147 +# define X509V3_F_V2I_POLICY_CONSTRAINTS 146 +# define X509V3_F_V2I_POLICY_MAPPINGS 145 +# define X509V3_F_V2I_SUBJECT_ALT 154 +# define X509V3_F_V2I_TLS_FEATURE 165 +# define X509V3_F_V3_GENERIC_EXTENSION 116 +# define X509V3_F_X509V3_ADD1_I2D 140 +# define X509V3_F_X509V3_ADD_VALUE 105 +# define X509V3_F_X509V3_EXT_ADD 104 +# define X509V3_F_X509V3_EXT_ADD_ALIAS 106 +# define X509V3_F_X509V3_EXT_I2D 136 +# define X509V3_F_X509V3_EXT_NCONF 152 +# define X509V3_F_X509V3_GET_SECTION 142 +# define X509V3_F_X509V3_GET_STRING 143 +# define X509V3_F_X509V3_GET_VALUE_BOOL 110 +# define X509V3_F_X509V3_PARSE_LIST 109 +# define X509V3_F_X509_PURPOSE_ADD 137 +# define X509V3_F_X509_PURPOSE_SET 141 + +/* + * X509V3 reason codes. + */ +# define X509V3_R_BAD_IP_ADDRESS 118 +# define X509V3_R_BAD_OBJECT 119 +# define X509V3_R_BN_DEC2BN_ERROR 100 +# define X509V3_R_BN_TO_ASN1_INTEGER_ERROR 101 +# define X509V3_R_DIRNAME_ERROR 149 +# define X509V3_R_DISTPOINT_ALREADY_SET 160 +# define X509V3_R_DUPLICATE_ZONE_ID 133 +# define X509V3_R_ERROR_CONVERTING_ZONE 131 +# define X509V3_R_ERROR_CREATING_EXTENSION 144 +# define X509V3_R_ERROR_IN_EXTENSION 128 +# define X509V3_R_EXPECTED_A_SECTION_NAME 137 +# define X509V3_R_EXTENSION_EXISTS 145 +# define X509V3_R_EXTENSION_NAME_ERROR 115 +# define X509V3_R_EXTENSION_NOT_FOUND 102 +# define X509V3_R_EXTENSION_SETTING_NOT_SUPPORTED 103 +# define X509V3_R_EXTENSION_VALUE_ERROR 116 +# define X509V3_R_ILLEGAL_EMPTY_EXTENSION 151 +# define X509V3_R_INCORRECT_POLICY_SYNTAX_TAG 152 +# define X509V3_R_INVALID_ASNUMBER 162 +# define X509V3_R_INVALID_ASRANGE 163 +# define X509V3_R_INVALID_BOOLEAN_STRING 104 +# define X509V3_R_INVALID_EXTENSION_STRING 105 +# define X509V3_R_INVALID_INHERITANCE 165 +# define X509V3_R_INVALID_IPADDRESS 166 +# define X509V3_R_INVALID_MULTIPLE_RDNS 161 +# define X509V3_R_INVALID_NAME 106 +# define X509V3_R_INVALID_NULL_ARGUMENT 107 +# define X509V3_R_INVALID_NULL_NAME 108 +# define X509V3_R_INVALID_NULL_VALUE 109 +# define X509V3_R_INVALID_NUMBER 140 +# define X509V3_R_INVALID_NUMBERS 141 +# define X509V3_R_INVALID_OBJECT_IDENTIFIER 110 +# define X509V3_R_INVALID_OPTION 138 +# define X509V3_R_INVALID_POLICY_IDENTIFIER 134 +# define X509V3_R_INVALID_PROXY_POLICY_SETTING 153 +# define X509V3_R_INVALID_PURPOSE 146 +# define X509V3_R_INVALID_SAFI 164 +# define X509V3_R_INVALID_SECTION 135 +# define X509V3_R_INVALID_SYNTAX 143 +# define X509V3_R_ISSUER_DECODE_ERROR 126 +# define X509V3_R_MISSING_VALUE 124 +# define X509V3_R_NEED_ORGANIZATION_AND_NUMBERS 142 +# define X509V3_R_NO_CONFIG_DATABASE 136 +# define X509V3_R_NO_ISSUER_CERTIFICATE 121 +# define X509V3_R_NO_ISSUER_DETAILS 127 +# define X509V3_R_NO_POLICY_IDENTIFIER 139 +# define X509V3_R_NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED 154 +# define X509V3_R_NO_PUBLIC_KEY 114 +# define X509V3_R_NO_SUBJECT_DETAILS 125 +# define X509V3_R_OPERATION_NOT_DEFINED 148 +# define X509V3_R_OTHERNAME_ERROR 147 +# define X509V3_R_POLICY_LANGUAGE_ALREADY_DEFINED 155 +# define X509V3_R_POLICY_PATH_LENGTH 156 +# define X509V3_R_POLICY_PATH_LENGTH_ALREADY_DEFINED 157 +# define X509V3_R_POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY 159 +# define X509V3_R_SECTION_NOT_FOUND 150 +# define X509V3_R_UNABLE_TO_GET_ISSUER_DETAILS 122 +# define X509V3_R_UNABLE_TO_GET_ISSUER_KEYID 123 +# define X509V3_R_UNKNOWN_BIT_STRING_ARGUMENT 111 +# define X509V3_R_UNKNOWN_EXTENSION 129 +# define X509V3_R_UNKNOWN_EXTENSION_NAME 130 +# define X509V3_R_UNKNOWN_OPTION 120 +# define X509V3_R_UNSUPPORTED_OPTION 117 +# define X509V3_R_UNSUPPORTED_TYPE 167 +# define X509V3_R_USER_TOO_LONG 132 + +#endif diff --git a/thrid-party/svg/Svg.h b/thrid-party/svg/Svg.h new file mode 100755 index 0000000000..cf2452907a --- /dev/null +++ b/thrid-party/svg/Svg.h @@ -0,0 +1,3 @@ + +#import +NSImage * _Nullable drawSvgImageNano(NSData * _Nonnull data, CGSize size); diff --git a/thrid-party/svg/Svg.m b/thrid-party/svg/Svg.m new file mode 100755 index 0000000000..e629941837 --- /dev/null +++ b/thrid-party/svg/Svg.m @@ -0,0 +1,249 @@ +#import "Svg.h" +#import +#import "nanosvg.h" + +CGSize aspectFillSize(CGSize size, CGSize bounds) { + CGFloat scale = MAX(bounds.width / MAX(1.0, size.width), bounds.height / MAX(1.0, size.height)); + return CGSizeMake(floor(size.width * scale), floor(size.height * scale)); +} + +@interface SvgXMLParsingDelegate : NSObject { + NSString *_elementName; + NSString *_currentStyleString; +} + +@property (nonatomic, strong, readonly) NSMutableDictionary *styles; + +@end + +@implementation SvgXMLParsingDelegate + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _styles = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { + _elementName = elementName; +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if ([_elementName isEqualToString:@"style"]) { + int currentClassNameStartIndex = -1; + int currentClassContentsStartIndex = -1; + + NSString *currentClassName = nil; + + NSCharacterSet *alphanumeric = [NSCharacterSet alphanumericCharacterSet]; + + for (int i = 0; i < _currentStyleString.length; i++) { + unichar c = [_currentStyleString characterAtIndex:i]; + if (currentClassNameStartIndex != -1) { + if (![alphanumeric characterIsMember:c]) { + currentClassName = [_currentStyleString substringWithRange:NSMakeRange(currentClassNameStartIndex, i - currentClassNameStartIndex)]; + currentClassNameStartIndex = -1; + } + } else if (currentClassContentsStartIndex != -1) { + if (c == '}') { + NSString *classContents = [_currentStyleString substringWithRange:NSMakeRange(currentClassContentsStartIndex, i - currentClassContentsStartIndex)]; + if (currentClassName != nil && classContents != nil) { + _styles[currentClassName] = classContents; + currentClassName = nil; + } + currentClassContentsStartIndex = -1; + } + } + + if (currentClassNameStartIndex == -1 && currentClassContentsStartIndex == -1) { + if (c == '.') { + currentClassNameStartIndex = i + 1; + } else if (c == '{') { + currentClassContentsStartIndex = i + 1; + } + } + } + } + _elementName = nil; +} + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if ([_elementName isEqualToString:@"style"]) { + if (_currentStyleString == nil) { + _currentStyleString = string; + } else { + _currentStyleString = [_currentStyleString stringByAppendingString:string]; + } + } +} + +@end + +NSImage * _Nullable drawSvgImageNano(NSData * _Nonnull data, CGSize size) { + NSDate *startTime = [NSDate date]; + + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; + if (parser == nil) { + return nil; + } + SvgXMLParsingDelegate *delegate = [[SvgXMLParsingDelegate alloc] init]; + parser.delegate = delegate; + [parser parse]; + + NSMutableString *xmlString = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (xmlString == nil) { + return nil; + } + + for (NSString *styleName in delegate.styles) { + NSString *styleValue = delegate.styles[styleName]; + [xmlString replaceOccurrencesOfString:[NSString stringWithFormat:@"class=\"%@\"", styleName] withString:[NSString stringWithFormat:@"style=\"%@\"", styleValue] options:0 range:NSMakeRange(0, xmlString.length)]; + } + + char *zeroTerminatedData = xmlString.UTF8String; + + NSVGimage *image = nsvgParse(zeroTerminatedData, "px", 96); + if (image == nil || image->width < 1.0f || image->height < 1.0f) { + return nil; + } + + double deltaTime = -1.0f * [startTime timeIntervalSinceNow]; + printf("parseTime = %f\n", deltaTime); + + startTime = [NSDate date]; + + NSColor *backgroundColor = [NSColor blackColor]; + NSColor *foregroundColor = [NSColor whiteColor]; + + + NSImage *result = [[NSImage alloc] initWithSize:size]; + + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide: size.width pixelsHigh: size.height bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: false isPlanar: true colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0]; + [result addRepresentation:rep]; + + [result lockFocus]; + + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + +// context.translateBy(x: size.width / 2.0, y: size.height / 2.0) +// context.scaleBy(x: 1.0, y: -1.0) +// context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + CGContextTranslateCTM(context, 0, size.height ); + CGContextScaleCTM(context, 1.0, -1.0); + + CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height)); + + CGSize svgSize = CGSizeMake(image->width, image->height); + CGSize drawingSize = aspectFillSize(svgSize, size); + + CGFloat scale = MAX(size.width / MAX(1.0, svgSize.width), size.height / MAX(1.0, svgSize.height)); + + CGContextScaleCTM(context, scale, scale); + CGContextTranslateCTM(context, (size.width - drawingSize.width) / 2.0, (size.height - drawingSize.height) / 2.0); + + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) { + continue; + } + + if (shape->fill.type != NSVG_PAINT_NONE) { + //CGContextSetFillColorWithColor(context, UIColorRGBA(shape->fill.color, shape->opacity).CGColor); + CGContextSetFillColorWithColor(context, [foregroundColor colorWithAlphaComponent:shape->opacity].CGColor); + + bool isFirst = true; + bool hasStartPoint = false; + CGPoint startPoint; + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + if (isFirst) { + CGContextBeginPath(context); + isFirst = false; + hasStartPoint = true; + startPoint.x = path->pts[0]; + startPoint.y = path->pts[1]; + } + CGContextMoveToPoint(context, path->pts[0], path->pts[1]); + for (int i = 0; i < path->npts - 1; i += 3) { + float *p = &path->pts[i * 2]; + CGContextAddCurveToPoint(context, p[2], p[3], p[4], p[5], p[6], p[7]); + } + + if (path->closed) { + if (hasStartPoint) { + hasStartPoint = false; + CGContextAddLineToPoint(context, startPoint.x, startPoint.y); + } + } + } + switch (shape->fillRule) { + case NSVG_FILLRULE_EVENODD: + CGContextEOFillPath(context); + break; + default: + CGContextFillPath(context); + break; + } + } + + if (shape->stroke.type != NSVG_PAINT_NONE) { + //CGContextSetStrokeColorWithColor(context, UIColorRGBA(shape->fill.color, shape->opacity).CGColor); + CGContextSetStrokeColorWithColor(context, [foregroundColor colorWithAlphaComponent:shape->opacity].CGColor); + CGContextSetMiterLimit(context, shape->miterLimit); + + CGContextSetLineWidth(context, shape->strokeWidth); + switch (shape->strokeLineCap) { + case NSVG_CAP_BUTT: + CGContextSetLineCap(context, kCGLineCapButt); + break; + case NSVG_CAP_ROUND: + CGContextSetLineCap(context, kCGLineCapRound); + break; + case NSVG_CAP_SQUARE: + CGContextSetLineCap(context, kCGLineCapSquare); + break; + default: + break; + } + switch (shape->strokeLineJoin) { + case NSVG_JOIN_BEVEL: + CGContextSetLineJoin(context, kCGLineJoinBevel); + break; + case NSVG_JOIN_MITER: + CGContextSetLineCap(context, kCGLineJoinMiter); + break; + case NSVG_JOIN_ROUND: + CGContextSetLineCap(context, kCGLineJoinRound); + break; + default: + break; + } + + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + CGContextBeginPath(context); + CGContextMoveToPoint(context, path->pts[0], path->pts[1]); + for (int i = 0; i < path->npts - 1; i += 3) { + float *p = &path->pts[i * 2]; + CGContextAddCurveToPoint(context, p[2], p[3], p[4], p[5], p[6], p[7]); + } + + if (path->closed) { + CGContextClosePath(context); + } + CGContextStrokePath(context); + } + } + } + + + [result unlockFocus]; + + deltaTime = -1.0f * [startTime timeIntervalSinceNow]; + printf("drawingTime = %f\n", deltaTime); + + nsvgDelete(image); + + return result; +} diff --git a/thrid-party/svg/nanosvg.c b/thrid-party/svg/nanosvg.c new file mode 100755 index 0000000000..aa3b4d429b --- /dev/null +++ b/thrid-party/svg/nanosvg.c @@ -0,0 +1,6 @@ +#include +#include +#include +#include +#define NANOSVG_IMPLEMENTATION +#include "nanosvg.h" diff --git a/thrid-party/svg/nanosvg.h b/thrid-party/svg/nanosvg.h new file mode 100644 index 0000000000..9b7b9c028c --- /dev/null +++ b/thrid-party/svg/nanosvg.h @@ -0,0 +1,2982 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + + // NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. + // + // The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. + // + // NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! + // + // The shapes in the SVG images are transformed by the viewBox and converted to specified units. + // That is, you should get the same looking data as your designed in your favorite app. + // + // NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose + // to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. + // + // The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. + // DPI (dots-per-inch) controls how the unit conversion is done. + // + // If you don't know or care about the units stuff, "px" and 96 should get you going. + + + /* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); + */ + + enum NSVGpaintType { + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 + }; + + enum NSVGspreadType { + NSVG_SPREAD_PAD = 0, + NSVG_SPREAD_REFLECT = 1, + NSVG_SPREAD_REPEAT = 2 + }; + + enum NSVGlineJoin { + NSVG_JOIN_MITER = 0, + NSVG_JOIN_ROUND = 1, + NSVG_JOIN_BEVEL = 2 + }; + + enum NSVGlineCap { + NSVG_CAP_BUTT = 0, + NSVG_CAP_ROUND = 1, + NSVG_CAP_SQUARE = 2 + }; + + enum NSVGfillRule { + NSVG_FILLRULE_NONZERO = 0, + NSVG_FILLRULE_EVENODD = 1 + }; + + enum NSVGflags { + NSVG_FLAGS_VISIBLE = 0x01 + }; + + typedef struct NSVGgradientStop { + unsigned int color; + float offset; + } NSVGgradientStop; + + typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; + } NSVGgradient; + + typedef struct NSVGpaint { + char type; + union { + unsigned int color; + NSVGgradient* gradient; + }; + } NSVGpaint; + + typedef struct NSVGpath + { + float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath* next; // Pointer to next path, or NULL if last element. + } NSVGpath; + + typedef struct NSVGshape + { + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + NSVGpath* paths; // Linked list of paths in the image. + struct NSVGshape* next; // Pointer to next shape, or NULL if last element. + } NSVGshape; + + typedef struct NSVGimage + { + float width; // Width of the image. + float height; // Height of the image. + NSVGshape* shapes; // Linked list of shapes in the image. + } NSVGimage; + + // Parses SVG file from a file, returns SVG image as paths. + NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); + + // Parses SVG file from a null terminated string, returns SVG image as paths. + // Important note: changes the string. + NSVGimage* nsvgParse(char* input, const char* units, float dpi); + + // Duplicates a path. + NSVGpath* nsvgDuplicatePath(NSVGpath* p); + + // Deletes an image. + void nsvgDelete(NSVGimage* image); + +#ifndef NANOSVG_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#endif // NANOSVG_H + +#ifdef NANOSVG_IMPLEMENTATION + +#include +#include +#include + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER +#pragma warning (disable: 4996) // Switch off security warnings +#pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings +#ifdef __cplusplus +#define NSVG_INLINE inline +#else +#define NSVG_INLINE +#endif +#else +#define NSVG_INLINE inline +#endif + + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int nsvg__isnum(char c) +{ + return strchr("0123456789+-.eE", c) != 0; +} + +static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } +static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } + + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent(char* s, + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + // Trim start white spaces + while (*s && nsvg__isspace(*s)) s++; + if (!*s) return; + + if (contentCb) + (*contentCb)(ud, s); +} + +static void nsvg__parseElement(char* s, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void* ud) +{ + const char* attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char* name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) s++; + if (*s) { *s++ = '\0'; } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { + char* name = NULL; + char* value = NULL; + + // Skip white space before the attrib name + while (*s && nsvg__isspace(*s)) s++; + if (!*s) break; + if (*s == '/') { + end = 1; + break; + } + name = s; + // Find end of the attrib name. + while (*s && !nsvg__isspace(*s) && *s != '=') s++; + if (*s) { *s++ = '\0'; } + // Skip until the beginning of the value. + while (*s && *s != '\"' && *s != '\'') s++; + if (!*s) break; + quote = *s; + s++; + // Store value and find the end of it. + value = s; + while (*s && *s != quote) s++; + if (*s) { *s++ = '\0'; } + + // Store only well formed attributes + if (name && value) { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if (start && startelCb) + (*startelCb)(ud, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +int nsvg__parseXML(char* input, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + char* s = input; + char* mark = s; + int state = NSVG_XML_CONTENT; + while (*s) { + if (*s == '<' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } else { + s++; + } + } + + return 1; +} + + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 + +enum NSVGgradientUnits { + NSVG_USER_SPACE = 0, + NSVG_OBJECT_SPACE = 1 +}; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits { + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +typedef struct NSVGcoordinate { + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData { + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData { + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData +{ + char id[64]; + char ref[64]; + char type; + union { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop* stops; + struct NSVGgradientData* next; +} NSVGgradientData; + +typedef struct NSVGattrib +{ + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGparser +{ + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float* pts; + int npts; + int cpts; + NSVGpath* plist; + NSVGimage* image; + NSVGgradientData* gradients; + NSVGshape* shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; +} NSVGparser; + +static void nsvg__xformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetTranslation(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +static void nsvg__xformSetScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetRotation(float* t, float a) +{ + float cs = cosf(a), sn = sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformMultiply(float* t, float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +static void nsvg__xformInverse(float* inv, float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nsvg__xformIdentity(t); + return; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); +} + +static void nsvg__xformPremultiply(float* t, float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nsvg__xformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2] + t[4]; + *dy = x*t[1] + y*t[3] + t[5]; +} + +static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2]; + *dy = x*t[1] + y*t[3]; +} + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds(float* pt, float* bounds) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + + +static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) +{ + double it = 1.0-t; + return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; +} + +static void nsvg__curveBounds(float* bounds, float* curve) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float* v0 = &curve[0]; + float* v1 = &curve[2]; + float* v2 = &curve[4]; + float* v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf(v0[0], v3[0]); + bounds[1] = nsvg__minf(v0[1], v3[1]); + bounds[2] = nsvg__maxf(v0[0], v3[0]); + bounds[3] = nsvg__maxf(v0[1], v3[1]); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) + return; + + // Add bezier curve inflection points in X and Y. + for (i = 0; i < 2; i++) { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + if (fabs(a) < NSVG_EPSILON) { + if (fabs(b) > NSVG_EPSILON) { + t = -c / b; + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } else { + b2ac = b*b - 4.0*c*a; + if (b2ac > NSVG_EPSILON) { + t = (-b + sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + t = (-b - sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } + for (j = 0; j < count; j++) { + v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); + bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); + bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); + } + } +} + +static NSVGparser* nsvg__createParser() +{ + NSVGparser* p; + p = (NSVGparser*)malloc(sizeof(NSVGparser)); + if (p == NULL) goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); + if (p->image == NULL) goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0,0,0); + p->attr[0].strokeColor = NSVG_RGB(0,0,0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = 1; + + return p; + +error: + if (p) { + if (p->image) free(p->image); + free(p); + } + return NULL; +} + +static void nsvg__deletePaths(NSVGpath* path) +{ + while (path) { + NSVGpath *next = path->next; + if (path->pts != NULL) + free(path->pts); + free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint* paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData* grad) +{ + NSVGgradientData* next; + while (grad != NULL) { + next = grad->next; + free(grad->stops); + free(grad); + grad = next; + } +} + +static void nsvg__deleteParser(NSVGparser* p) +{ + if (p != NULL) { + nsvg__deletePaths(p->plist); + nsvg__deleteGradientData(p->gradients); + nsvgDelete(p->image); + free(p->pts); + free(p); + } +} + +static void nsvg__resetPath(NSVGparser* p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser* p, float x, float y) +{ + if (p->npts+1 > p->cpts) { + p->cpts = p->cpts ? p->cpts*2 : 8; + p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); + if (!p->pts) return; + } + p->pts[p->npts*2+0] = x; + p->pts[p->npts*2+1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser* p, float x, float y) +{ + if (p->npts > 0) { + p->pts[(p->npts-1)*2+0] = x; + p->pts[(p->npts-1)*2+1] = y; + } else { + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__lineTo(NSVGparser* p, float x, float y) +{ + float px,py, dx,dy; + if (p->npts > 0) { + px = p->pts[(p->npts-1)*2+0]; + py = p->pts[(p->npts-1)*2+1]; + dx = x - px; + dy = y - py; + nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); + nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) +{ + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); +} + +static NSVGattrib* nsvg__getAttr(NSVGparser* p) +{ + return &p->attr[p->attrHead]; +} + +static void nsvg__pushAttr(NSVGparser* p) +{ + if (p->attrHead < NSVG_MAX_ATTR-1) { + p->attrHead++; + memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); + } +} + +static void nsvg__popAttr(NSVGparser* p) +{ + if (p->attrHead > 0) + p->attrHead--; +} + +static float nsvg__actualOrigX(NSVGparser* p) +{ + return p->viewMinx; +} + +static float nsvg__actualOrigY(NSVGparser* p) +{ + return p->viewMiny; +} + +static float nsvg__actualWidth(NSVGparser* p) +{ + return p->viewWidth; +} + +static float nsvg__actualHeight(NSVGparser* p) +{ + return p->viewHeight; +} + +static float nsvg__actualLength(NSVGparser* p) +{ + float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); + return sqrtf(w*w + h*h) / sqrtf(2.0f); +} + +static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) +{ + NSVGattrib* attr = nsvg__getAttr(p); + switch (c.units) { + case NSVG_UNITS_USER: return c.value; + case NSVG_UNITS_PX: return c.value; + case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; + case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; + case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; + case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; + case NSVG_UNITS_IN: return c.value * p->dpi; + case NSVG_UNITS_EM: return c.value * attr->fontSize; + case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; + default: return c.value; + } + return c.value; +} + +static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) +{ + NSVGgradientData* grad = p->gradients; + while (grad) { + if (strcmp(grad->id, id) == 0) + return grad; + grad = grad->next; + } + return NULL; +} + +static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGgradientData* data = NULL; + NSVGgradientData* ref = NULL; + NSVGgradientStop* stops = NULL; + NSVGgradient* grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + + data = nsvg__findGradientData(p, id); + if (data == NULL) return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + while (ref != NULL) { + if (stops == NULL && ref->stops != NULL) { + stops = ref->stops; + nstops = ref->nstops; + break; + } + ref = nsvg__findGradientData(p, ref->ref); + } + if (stops == NULL) return NULL; + + grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); + if (grad == NULL) return NULL; + + // The shape width and height. + if (data->units == NSVG_OBJECT_SPACE) { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } else { + ox = nsvg__actualOrigX(p); + oy = nsvg__actualOrigY(p); + sw = nsvg__actualWidth(p); + sh = nsvg__actualHeight(p); + } + sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); + + if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); + y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); + x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); + y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; grad->xform[1] = -dx; + grad->xform[2] = dx; grad->xform[3] = dy; + grad->xform[4] = x1; grad->xform[5] = y1; + } else { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); + cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); + fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); + fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); + r = nsvg__convertToPixels(p, data->radial.r, 0, sl); + // Calculate transform aligned to the circle + grad->xform[0] = r; grad->xform[1] = 0; + grad->xform[2] = 0; grad->xform[3] = r; + grad->xform[4] = cx; grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply(grad->xform, data->xform); + nsvg__xformMultiply(grad->xform, attr->xform); + + grad->spread = data->spread; + memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + +static float nsvg__getAverageScale(float* t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) +{ + NSVGpath* path; + float curve[4*2], curveBounds[4]; + int i, first = 1; + for (path = shape->paths; path != NULL; path = path->next) { + nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); + for (i = 0; i < path->npts-1; i += 3) { + nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); + nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); + nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); + nsvg__curveBounds(curveBounds, curve); + if (first) { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } else { + bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); + bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); + bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); + bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); + } + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + +static void nsvg__addShape(NSVGparser* p) +{ + NSVGattrib* attr = nsvg__getAttr(p); + float scale = 1.0f; + NSVGshape* shape; + NSVGpath* path; + int i; + + if (p->plist == NULL) + return; + + shape = (NSVGshape*)malloc(sizeof(NSVGshape)); + if (shape == NULL) goto error; + memset(shape, 0, sizeof(NSVGshape)); + + memcpy(shape->id, attr->id, sizeof shape->id); + scale = nsvg__getAverageScale(attr->xform); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char)attr->strokeDashCount; + for (i = 0; i < attr->strokeDashCount; i++) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + for (path = shape->paths->next; path != NULL; path = path->next) { + shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); + shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); + shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); + shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); + } + + // Set fill + if (attr->hasFill == 0) { + shape->fill.type = NSVG_PAINT_NONE; + } else if (attr->hasFill == 1) { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; + } else if (attr->hasFill == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); + if (shape->fill.gradient == NULL) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + + // Set stroke + if (attr->hasStroke == 0) { + shape->stroke.type = NSVG_PAINT_NONE; + } else if (attr->hasStroke == 1) { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; + } else if (attr->hasStroke == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); + if (shape->stroke.gradient == NULL) + shape->stroke.type = NSVG_PAINT_NONE; + } + + // Set flags + shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if (p->image->shapes == NULL) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + p->shapesTail = shape; + + return; + +error: + if (shape) free(shape); +} + +static void nsvg__addPath(NSVGparser* p, char closed) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGpath* path = NULL; + float bounds[4]; + float* curve; + int i; + + if (p->npts < 4) + return; + + /*if (closed) + nsvg__lineTo(p, p->pts[0], p->pts[1]);*/ + + path = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (path == NULL) goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (path->pts == NULL) goto error; + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for (i = 0; i < p->npts; ++i) + nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); + + // Find bounds + for (i = 0; i < path->npts-1; i += 3) { + curve = &path->pts[i*2]; + nsvg__curveBounds(bounds, curve); + if (i == 0) { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } else { + path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); + path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); + path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); + path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + if (path != NULL) { + if (path->pts != NULL) free(path->pts); + free(path); + } +} + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof(const char* s) +{ + char* cur = (char*)s; + char* end = NULL; + double res = 0.0, sign = 1.0; + long long intPart = 0, fracPart = 0; + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if (*cur == '+') { + cur++; + } else if (*cur == '-') { + sign = -1; + cur++; + } + + // Parse integer part + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + intPart = strtoll(cur, &end, 10); + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence + fracPart = strtoll(cur, &end, 10); + if (cur != end) { + res += (double)fracPart / pow(10.0, (double)(end - cur)); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if (!hasIntPart && !hasFracPart) + return 0.0; + + // Parse optional exponent + if (*cur == 'e' || *cur == 'E') { + long expPart = 0; + cur++; // skip 'E' + expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + if (cur != end) { + res *= pow(10.0, (double)expPart); + } + } + + return res * sign; +} + + +static const char* nsvg__parseNumber(const char* s, char* it, const int size) +{ + const int last = size-1; + int i = 0; + + // sign + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + // integer part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + if (*s == '.') { + // decimal point + if (i < last) it[i++] = *s; + s++; + // fraction part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + // exponent + if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { + if (i < last) it[i++] = *s; + s++; + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + it[i] = '\0'; + + return s; +} + +static const char* nsvg__getNextPathItem(const char* s, char* it) +{ + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { + s = nsvg__parseNumber(s, it, 64); + } else { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + +static unsigned int nsvg__parseColorHex(const char* str) +{ + unsigned int c = 0, r = 0, g = 0, b = 0; + int n = 0; + str++; // skip # + // Calculate number of characters. + while(str[n] && !nsvg__isspace(str[n])) + n++; + if (n == 6) { + sscanf(str, "%x", &c); + } else if (n == 3) { + sscanf(str, "%x", &c); + c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); + c |= c<<4; + } + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = c & 0xff; + return NSVG_RGB(r,g,b); +} + +static unsigned int nsvg__parseColorRGB(const char* str) +{ + int r = -1, g = -1, b = -1; + char s1[32]="", s2[32]=""; + sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); + if (strchr(s1, '%')) { + return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); + } else { + return NSVG_RGB(r,g,b); + } +} + +typedef struct NSVGNamedColor { + const char* name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = { + + { "red", NSVG_RGB(255, 0, 0) }, + { "green", NSVG_RGB( 0, 128, 0) }, + { "blue", NSVG_RGB( 0, 0, 255) }, + { "yellow", NSVG_RGB(255, 255, 0) }, + { "cyan", NSVG_RGB( 0, 255, 255) }, + { "magenta", NSVG_RGB(255, 0, 255) }, + { "black", NSVG_RGB( 0, 0, 0) }, + { "grey", NSVG_RGB(128, 128, 128) }, + { "gray", NSVG_RGB(128, 128, 128) }, + { "white", NSVG_RGB(255, 255, 255) }, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + { "aliceblue", NSVG_RGB(240, 248, 255) }, + { "antiquewhite", NSVG_RGB(250, 235, 215) }, + { "aqua", NSVG_RGB( 0, 255, 255) }, + { "aquamarine", NSVG_RGB(127, 255, 212) }, + { "azure", NSVG_RGB(240, 255, 255) }, + { "beige", NSVG_RGB(245, 245, 220) }, + { "bisque", NSVG_RGB(255, 228, 196) }, + { "blanchedalmond", NSVG_RGB(255, 235, 205) }, + { "blueviolet", NSVG_RGB(138, 43, 226) }, + { "brown", NSVG_RGB(165, 42, 42) }, + { "burlywood", NSVG_RGB(222, 184, 135) }, + { "cadetblue", NSVG_RGB( 95, 158, 160) }, + { "chartreuse", NSVG_RGB(127, 255, 0) }, + { "chocolate", NSVG_RGB(210, 105, 30) }, + { "coral", NSVG_RGB(255, 127, 80) }, + { "cornflowerblue", NSVG_RGB(100, 149, 237) }, + { "cornsilk", NSVG_RGB(255, 248, 220) }, + { "crimson", NSVG_RGB(220, 20, 60) }, + { "darkblue", NSVG_RGB( 0, 0, 139) }, + { "darkcyan", NSVG_RGB( 0, 139, 139) }, + { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, + { "darkgray", NSVG_RGB(169, 169, 169) }, + { "darkgreen", NSVG_RGB( 0, 100, 0) }, + { "darkgrey", NSVG_RGB(169, 169, 169) }, + { "darkkhaki", NSVG_RGB(189, 183, 107) }, + { "darkmagenta", NSVG_RGB(139, 0, 139) }, + { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, + { "darkorange", NSVG_RGB(255, 140, 0) }, + { "darkorchid", NSVG_RGB(153, 50, 204) }, + { "darkred", NSVG_RGB(139, 0, 0) }, + { "darksalmon", NSVG_RGB(233, 150, 122) }, + { "darkseagreen", NSVG_RGB(143, 188, 143) }, + { "darkslateblue", NSVG_RGB( 72, 61, 139) }, + { "darkslategray", NSVG_RGB( 47, 79, 79) }, + { "darkslategrey", NSVG_RGB( 47, 79, 79) }, + { "darkturquoise", NSVG_RGB( 0, 206, 209) }, + { "darkviolet", NSVG_RGB(148, 0, 211) }, + { "deeppink", NSVG_RGB(255, 20, 147) }, + { "deepskyblue", NSVG_RGB( 0, 191, 255) }, + { "dimgray", NSVG_RGB(105, 105, 105) }, + { "dimgrey", NSVG_RGB(105, 105, 105) }, + { "dodgerblue", NSVG_RGB( 30, 144, 255) }, + { "firebrick", NSVG_RGB(178, 34, 34) }, + { "floralwhite", NSVG_RGB(255, 250, 240) }, + { "forestgreen", NSVG_RGB( 34, 139, 34) }, + { "fuchsia", NSVG_RGB(255, 0, 255) }, + { "gainsboro", NSVG_RGB(220, 220, 220) }, + { "ghostwhite", NSVG_RGB(248, 248, 255) }, + { "gold", NSVG_RGB(255, 215, 0) }, + { "goldenrod", NSVG_RGB(218, 165, 32) }, + { "greenyellow", NSVG_RGB(173, 255, 47) }, + { "honeydew", NSVG_RGB(240, 255, 240) }, + { "hotpink", NSVG_RGB(255, 105, 180) }, + { "indianred", NSVG_RGB(205, 92, 92) }, + { "indigo", NSVG_RGB( 75, 0, 130) }, + { "ivory", NSVG_RGB(255, 255, 240) }, + { "khaki", NSVG_RGB(240, 230, 140) }, + { "lavender", NSVG_RGB(230, 230, 250) }, + { "lavenderblush", NSVG_RGB(255, 240, 245) }, + { "lawngreen", NSVG_RGB(124, 252, 0) }, + { "lemonchiffon", NSVG_RGB(255, 250, 205) }, + { "lightblue", NSVG_RGB(173, 216, 230) }, + { "lightcoral", NSVG_RGB(240, 128, 128) }, + { "lightcyan", NSVG_RGB(224, 255, 255) }, + { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, + { "lightgray", NSVG_RGB(211, 211, 211) }, + { "lightgreen", NSVG_RGB(144, 238, 144) }, + { "lightgrey", NSVG_RGB(211, 211, 211) }, + { "lightpink", NSVG_RGB(255, 182, 193) }, + { "lightsalmon", NSVG_RGB(255, 160, 122) }, + { "lightseagreen", NSVG_RGB( 32, 178, 170) }, + { "lightskyblue", NSVG_RGB(135, 206, 250) }, + { "lightslategray", NSVG_RGB(119, 136, 153) }, + { "lightslategrey", NSVG_RGB(119, 136, 153) }, + { "lightsteelblue", NSVG_RGB(176, 196, 222) }, + { "lightyellow", NSVG_RGB(255, 255, 224) }, + { "lime", NSVG_RGB( 0, 255, 0) }, + { "limegreen", NSVG_RGB( 50, 205, 50) }, + { "linen", NSVG_RGB(250, 240, 230) }, + { "maroon", NSVG_RGB(128, 0, 0) }, + { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, + { "mediumblue", NSVG_RGB( 0, 0, 205) }, + { "mediumorchid", NSVG_RGB(186, 85, 211) }, + { "mediumpurple", NSVG_RGB(147, 112, 219) }, + { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, + { "mediumslateblue", NSVG_RGB(123, 104, 238) }, + { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, + { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, + { "mediumvioletred", NSVG_RGB(199, 21, 133) }, + { "midnightblue", NSVG_RGB( 25, 25, 112) }, + { "mintcream", NSVG_RGB(245, 255, 250) }, + { "mistyrose", NSVG_RGB(255, 228, 225) }, + { "moccasin", NSVG_RGB(255, 228, 181) }, + { "navajowhite", NSVG_RGB(255, 222, 173) }, + { "navy", NSVG_RGB( 0, 0, 128) }, + { "oldlace", NSVG_RGB(253, 245, 230) }, + { "olive", NSVG_RGB(128, 128, 0) }, + { "olivedrab", NSVG_RGB(107, 142, 35) }, + { "orange", NSVG_RGB(255, 165, 0) }, + { "orangered", NSVG_RGB(255, 69, 0) }, + { "orchid", NSVG_RGB(218, 112, 214) }, + { "palegoldenrod", NSVG_RGB(238, 232, 170) }, + { "palegreen", NSVG_RGB(152, 251, 152) }, + { "paleturquoise", NSVG_RGB(175, 238, 238) }, + { "palevioletred", NSVG_RGB(219, 112, 147) }, + { "papayawhip", NSVG_RGB(255, 239, 213) }, + { "peachpuff", NSVG_RGB(255, 218, 185) }, + { "peru", NSVG_RGB(205, 133, 63) }, + { "pink", NSVG_RGB(255, 192, 203) }, + { "plum", NSVG_RGB(221, 160, 221) }, + { "powderblue", NSVG_RGB(176, 224, 230) }, + { "purple", NSVG_RGB(128, 0, 128) }, + { "rosybrown", NSVG_RGB(188, 143, 143) }, + { "royalblue", NSVG_RGB( 65, 105, 225) }, + { "saddlebrown", NSVG_RGB(139, 69, 19) }, + { "salmon", NSVG_RGB(250, 128, 114) }, + { "sandybrown", NSVG_RGB(244, 164, 96) }, + { "seagreen", NSVG_RGB( 46, 139, 87) }, + { "seashell", NSVG_RGB(255, 245, 238) }, + { "sienna", NSVG_RGB(160, 82, 45) }, + { "silver", NSVG_RGB(192, 192, 192) }, + { "skyblue", NSVG_RGB(135, 206, 235) }, + { "slateblue", NSVG_RGB(106, 90, 205) }, + { "slategray", NSVG_RGB(112, 128, 144) }, + { "slategrey", NSVG_RGB(112, 128, 144) }, + { "snow", NSVG_RGB(255, 250, 250) }, + { "springgreen", NSVG_RGB( 0, 255, 127) }, + { "steelblue", NSVG_RGB( 70, 130, 180) }, + { "tan", NSVG_RGB(210, 180, 140) }, + { "teal", NSVG_RGB( 0, 128, 128) }, + { "thistle", NSVG_RGB(216, 191, 216) }, + { "tomato", NSVG_RGB(255, 99, 71) }, + { "turquoise", NSVG_RGB( 64, 224, 208) }, + { "violet", NSVG_RGB(238, 130, 238) }, + { "wheat", NSVG_RGB(245, 222, 179) }, + { "whitesmoke", NSVG_RGB(245, 245, 245) }, + { "yellowgreen", NSVG_RGB(154, 205, 50) }, +#endif +}; + +static unsigned int nsvg__parseColorName(const char* str) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for (i = 0; i < ncolors; i++) { + if (strcmp(nsvg__colors[i].name, str) == 0) { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB(128, 128, 128); +} + +static unsigned int nsvg__parseColor(const char* str) +{ + size_t len = 0; + while(*str == ' ') ++str; + len = strlen(str); + if (len >= 1 && *str == '#') + return nsvg__parseColorHex(str); + else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') + return nsvg__parseColorRGB(str); + return nsvg__parseColorName(str); +} + +static float nsvg__parseOpacity(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + return val; +} + +static float nsvg__parseMiterLimit(const char* str) +{ + float val = nsvg__atof(str); + if (val < 0.0f) val = 0.0f; + return val; +} + +static int nsvg__parseUnits(const char* units) +{ + if (units[0] == 'p' && units[1] == 'x') + return NSVG_UNITS_PX; + else if (units[0] == 'p' && units[1] == 't') + return NSVG_UNITS_PT; + else if (units[0] == 'p' && units[1] == 'c') + return NSVG_UNITS_PC; + else if (units[0] == 'm' && units[1] == 'm') + return NSVG_UNITS_MM; + else if (units[0] == 'c' && units[1] == 'm') + return NSVG_UNITS_CM; + else if (units[0] == 'i' && units[1] == 'n') + return NSVG_UNITS_IN; + else if (units[0] == '%') + return NSVG_UNITS_PERCENT; + else if (units[0] == 'e' && units[1] == 'm') + return NSVG_UNITS_EM; + else if (units[0] == 'e' && units[1] == 'x') + return NSVG_UNITS_EX; + return NSVG_UNITS_USER; +} + +static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) +{ + NSVGcoordinate coord = {0, NSVG_UNITS_USER}; + char buf[64]; + coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); + coord.value = nsvg__atof(buf); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)nsvg__atof(it); + } else { + ++ptr; + } + } + return (int)(end - str); +} + + +static int nsvg__parseMatrix(float* xform, const char* str) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, t, 6, &na); + if (na != 6) return len; + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseTranslate(float* xform, const char* str) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseScale(float* xform, const char* str) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewX(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewY(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseRotate(float* xform, const char* str) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 3, &na); + if (na == 1) + args[1] = args[2] = 0.0f; + nsvg__xformIdentity(m); + + if (na > 1) { + nsvg__xformSetTranslation(t, -args[1], -args[2]); + nsvg__xformMultiply(m, t); + } + + nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); + nsvg__xformMultiply(m, t); + + if (na > 1) { + nsvg__xformSetTranslation(t, args[1], args[2]); + nsvg__xformMultiply(m, t); + } + + memcpy(xform, m, sizeof(float)*6); + + return len; +} + +static void nsvg__parseTransform(float* xform, const char* str) +{ + float t[6]; + nsvg__xformIdentity(xform); + while (*str) + { + if (strncmp(str, "matrix", 6) == 0) + str += nsvg__parseMatrix(t, str); + else if (strncmp(str, "translate", 9) == 0) + str += nsvg__parseTranslate(t, str); + else if (strncmp(str, "scale", 5) == 0) + str += nsvg__parseScale(t, str); + else if (strncmp(str, "rotate", 6) == 0) + str += nsvg__parseRotate(t, str); + else if (strncmp(str, "skewX", 5) == 0) + str += nsvg__parseSkewX(t, str); + else if (strncmp(str, "skewY", 5) == 0) + str += nsvg__parseSkewY(t, str); + else{ + ++str; + continue; + } + + nsvg__xformPremultiply(xform, t); + } +} + +static void nsvg__parseUrl(char* id, const char* str) +{ + int i = 0; + str += 4; // "url("; + if (*str == '#') + str++; + while (i < 63 && *str != ')') { + id[i] = *str++; + i++; + } + id[i] = '\0'; +} + +static char nsvg__parseLineCap(const char* str) +{ + if (strcmp(str, "butt") == 0) + return NSVG_CAP_BUTT; + else if (strcmp(str, "round") == 0) + return NSVG_CAP_ROUND; + else if (strcmp(str, "square") == 0) + return NSVG_CAP_SQUARE; + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + +static char nsvg__parseLineJoin(const char* str) +{ + if (strcmp(str, "miter") == 0) + return NSVG_JOIN_MITER; + else if (strcmp(str, "round") == 0) + return NSVG_JOIN_ROUND; + else if (strcmp(str, "bevel") == 0) + return NSVG_JOIN_BEVEL; + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + +static char nsvg__parseFillRule(const char* str) +{ + if (strcmp(str, "nonzero") == 0) + return NSVG_FILLRULE_NONZERO; + else if (strcmp(str, "evenodd") == 0) + return NSVG_FILLRULE_EVENODD; + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + +static const char* nsvg__getNextDashItem(const char* s, char* it) +{ + int n = 0; + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + // Advance until whitespace, comma or end. + while (*s && (!nsvg__isspace(*s) && *s != ',')) { + if (n < 63) + it[n++] = *s; + s++; + } + it[n++] = '\0'; + return s; +} + +static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if (str[0] == 'n') + return 0; + + // Parse dashes + while (*str) { + str = nsvg__getNextDashItem(str, item); + if (!*item) break; + if (count < NSVG_MAX_DASHES) + strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); + } + + for (i = 0; i < count; i++) + sum += strokeDashArray[i]; + if (sum <= 1e-6f) + count = 0; + + return count; +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str); + +static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) +{ + float xform[6]; + NSVGattrib* attr = nsvg__getAttr(p); + if (!attr) return 0; + + if (strcmp(name, "style") == 0) { + nsvg__parseStyle(p, value); + } else if (strcmp(name, "display") == 0) { + if (strcmp(value, "none") == 0) + attr->visible = 0; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + + } else if (strcmp(name, "fill") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasFill = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasFill = 2; + nsvg__parseUrl(attr->fillGradient, value); + } else { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "opacity") == 0) { + attr->opacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "fill-opacity") == 0) { + attr->fillOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasStroke = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasStroke = 2; + nsvg__parseUrl(attr->strokeGradient, value); + } else { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "stroke-width") == 0) { + attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-dasharray") == 0) { + attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); + } else if (strcmp(name, "stroke-dashoffset") == 0) { + attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-opacity") == 0) { + attr->strokeOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke-linecap") == 0) { + attr->strokeLineCap = nsvg__parseLineCap(value); + } else if (strcmp(name, "stroke-linejoin") == 0) { + attr->strokeLineJoin = nsvg__parseLineJoin(value); + } else if (strcmp(name, "stroke-miterlimit") == 0) { + attr->miterLimit = nsvg__parseMiterLimit(value); + } else if (strcmp(name, "fill-rule") == 0) { + attr->fillRule = nsvg__parseFillRule(value); + } else if (strcmp(name, "font-size") == 0) { + attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "transform") == 0) { + nsvg__parseTransform(xform, value); + nsvg__xformPremultiply(attr->xform, xform); + } else if (strcmp(name, "stop-color") == 0) { + attr->stopColor = nsvg__parseColor(value); + } else if (strcmp(name, "stop-opacity") == 0) { + attr->stopOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "offset") == 0) { + attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); + } else if (strcmp(name, "id") == 0) { + strncpy(attr->id, value, 63); + attr->id[63] = '\0'; + } else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; + ++str; + + n = (int)(str - start); + if (n > 511) n = 511; + if (n) memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; + + n = (int)(end - val); + if (n > 511) n = 511; + if (n) memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str) +{ + const char* start; + const char* end; + + while (*str) { + // Left Trim + while(*str && nsvg__isspace(*str)) ++str; + start = str; + while(*str && *str != ';') ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) + { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + } + return 0; +} + +static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2*x1 - *cpx2; + cy1 = 2*y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2*x1 - *cpx2; + cy = 2*y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) { return x*x; } +static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux,uy, vx,vy); + if (r < -1.0f) r = -1.0f; + if (r > 1.0f) r = 1.0f; + return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx*dx + dy*dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); + sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); + if (sa < 0.0f) sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; + cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle + da = nsvg__vecang(ux,uy, vx,vy); // Delta angle + + // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; + // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i/(float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser* p, const char** attr) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + s = nsvg__getNextPathItem(s, item); + if (!*item) break; + if (nsvg__isnum(item[0])) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; cpy2 = cpy; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs-2]; + cpy = args[nargs-1]; + cpx2 = cpx; cpy2 = cpy; + } + break; + } + nargs = 0; + } + } else { + cmd = item[0]; + rargs = nsvg__getArgsPerElement(cmd); + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } else if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser* p, const char** attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) rx = ry; + if (ry < 0.0f && rx > 0.0f) ry = rx; + if (rx < 0.0f) rx = 0.0f; + if (ry < 0.0f) ry = 0.0f; + if (rx > w/2.0f) rx = w/2.0f; + if (ry > h/2.0f) ry = h/2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x+w, y); + nsvg__lineTo(p, x+w, y+h); + nsvg__lineTo(p, x, y+h); + } else { + // Rounded rectangle + nsvg__moveTo(p, x+rx, y); + nsvg__lineTo(p, x+w-rx, y); + nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); + nsvg__lineTo(p, x+w, y+h-ry); + nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); + nsvg__lineTo(p, x+rx, y+h); + nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); + nsvg__lineTo(p, x, y+ry); + nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+r, cy); + nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); + nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); + nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); + nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+rx, cy); + nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); + nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); + nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); + nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser* p, const char** attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "viewBox") == 0) { + const char *s = attr[i + 1]; + char buf[64]; + s = nsvg__parseNumber(s, buf, 64); + p->viewMinx = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewMiny = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewWidth = nsvg__atof(buf); + while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; + if (!*s) return; + s = nsvg__parseNumber(s, buf, 64); + p->viewHeight = nsvg__atof(buf); + } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); + if (grad == NULL) return; + memset(grad, 0, sizeof(NSVGgradientData)); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { + grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); + grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { + grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + } + + nsvg__xformIdentity(grad->xform); + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "id") == 0) { + strncpy(grad->id, attr[i+1], 63); + grad->id[63] = '\0'; + } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "gradientUnits") == 0) { + if (strcmp(attr[i+1], "objectBoundingBox") == 0) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } else if (strcmp(attr[i], "gradientTransform") == 0) { + nsvg__parseTransform(grad->xform, attr[i + 1]); + } else if (strcmp(attr[i], "cx") == 0) { + grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "cy") == 0) { + grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "r") == 0) { + grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fx") == 0) { + grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fy") == 0) { + grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x1") == 0) { + grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y1") == 0) { + grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x2") == 0) { + grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y2") == 0) { + grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "spreadMethod") == 0) { + if (strcmp(attr[i+1], "pad") == 0) + grad->spread = NSVG_SPREAD_PAD; + else if (strcmp(attr[i+1], "reflect") == 0) + grad->spread = NSVG_SPREAD_REFLECT; + else if (strcmp(attr[i+1], "repeat") == 0) + grad->spread = NSVG_SPREAD_REPEAT; + } else if (strcmp(attr[i], "xlink:href") == 0) { + const char *href = attr[i+1]; + strncpy(grad->ref, href+1, 62); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + +static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) +{ + NSVGattrib* curAttr = nsvg__getAttr(p); + NSVGgradientData* grad; + NSVGgradientStop* stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for (i = 0; attr[i]; i += 2) { + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } + + // Add stop to the last gradient. + grad = p->gradients; + if (grad == NULL) return; + + grad->nstops++; + grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); + if (grad->stops == NULL) return; + + // Insert + idx = grad->nstops-1; + for (i = 0; i < grad->nstops-1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops-1) { + for (i = grad->nstops-1; i > idx; i--) + grad->stops[i] = grad->stops[i-1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void* ud, const char* el, const char** attr) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + } else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } +} + +static void nsvg__endElement(void* ud, const char* el) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (strcmp(el, "g") == 0) { + nsvg__popAttr(p); + } else if (strcmp(el, "path") == 0) { + p->pathFlag = 0; + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 0; + } +} + +static void nsvg__content(void* ud, const char* s) +{ + NSVG_NOTUSED(ud); + NSVG_NOTUSED(s); + // empty +} + +static void nsvg__imageBounds(NSVGparser* p, float* bounds) +{ + NSVGshape* shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply (grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply (grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx+sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i =0; i < path->npts; i++) { + pt = &path->pts[i*2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +NSVGimage* nsvgParse(char* input, const char* units, float dpi) +{ + NSVGparser* p; + NSVGimage* ret = 0; + + p = nsvg__createParser(); + if (p == NULL) { + return NULL; + } + p->dpi = dpi; + + nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + + // Scale to viewBox + nsvg__scaleToViewbox(p, units); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser(p); + + return ret; +} + +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)malloc(size+1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + free(data); + + return image; + +error: + if (fp) fclose(fp); + if (data) free(data); + if (image) nsvgDelete(image); + return NULL; +} + +NSVGpath* nsvgDuplicatePath(NSVGpath* p) +{ + NSVGpath* res = NULL; + + if (p == NULL) + return NULL; + + res = (NSVGpath*)malloc(sizeof(NSVGpath)); + if (res == NULL) goto error; + memset(res, 0, sizeof(NSVGpath)); + + res->pts = (float*)malloc(p->npts*2*sizeof(float)); + if (res->pts == NULL) goto error; + memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); + res->npts = p->npts; + + memcpy(res->bounds, p->bounds, sizeof(p->bounds)); + + res->closed = p->closed; + + return res; + +error: + if (res != NULL) { + free(res->pts); + free(res); + } + return NULL; +} + +void nsvgDelete(NSVGimage* image) +{ + NSVGshape *snext, *shape; + if (image == NULL) return; + shape = image->shapes; + while (shape != NULL) { + snext = shape->next; + nsvg__deletePaths(shape->paths); + nsvg__deletePaint(&shape->fill); + nsvg__deletePaint(&shape->stroke); + free(shape); + shape = snext; + } + free(image); +} + +#endif diff --git a/tools/cloud.strings b/tools/cloud.strings new file mode 100644 index 0000000000..af3a376467 Binary files /dev/null and b/tools/cloud.strings differ diff --git a/tools/generate-images.swift b/tools/generate-images.swift new file mode 100644 index 0000000000..57af60d73c --- /dev/null +++ b/tools/generate-images.swift @@ -0,0 +1,655 @@ +import AppKit +import Cocoa +func initialize() -> [String] { + var array:[String] = [] + array.append("dialogMuteImage") + array.append("dialogMuteImageSelected") + array.append("outgoingMessageImage") + array.append("readMessageImage") + array.append("outgoingMessageImageSelected") + array.append("readMessageImageSelected") + array.append("sendingImage") + array.append("sendingImageSelected") + array.append("secretImage") + array.append("secretImageSelected") + array.append("pinnedImage") + array.append("pinnedImageSelected") + array.append("verifiedImage") + array.append("verifiedImageSelected") + array.append("errorImage") + array.append("errorImageSelected") + array.append("chatSearch") + array.append("chatSearchActive") + array.append("chatCall") + array.append("chatActions") + array.append("chatFailedCall_incoming") + array.append("chatFailedCall_outgoing") + array.append("chatCall_incoming") + array.append("chatCall_outgoing") + array.append("chatFailedCallBubble_incoming") + array.append("chatFailedCallBubble_outgoing") + array.append("chatCallBubble_incoming") + array.append("chatCallBubble_outgoing") + array.append("chatFallbackCall") + array.append("chatFallbackCallBubble_incoming") + array.append("chatFallbackCallBubble_outgoing") + array.append("chatToggleSelected") + array.append("chatToggleUnselected") + array.append("chatMusicPlay") + array.append("chatMusicPlayBubble_incoming") + array.append("chatMusicPlayBubble_outgoing") + array.append("chatMusicPause") + array.append("chatMusicPauseBubble_incoming") + array.append("chatMusicPauseBubble_outgoing") + array.append("chatGradientBubble_incoming") + array.append("chatGradientBubble_outgoing") + array.append("chatBubble_none_incoming_withInset") + array.append("chatBubble_none_outgoing_withInset") + array.append("chatBubbleBorder_none_incoming_withInset") + array.append("chatBubbleBorder_none_outgoing_withInset") + array.append("chatBubble_both_incoming_withInset") + array.append("chatBubble_both_outgoing_withInset") + array.append("chatBubbleBorder_both_incoming_withInset") + array.append("chatBubbleBorder_both_outgoing_withInset") + array.append("composeNewChat") + array.append("composeNewChatActive") + array.append("composeNewGroup") + array.append("composeNewSecretChat") + array.append("composeNewChannel") + array.append("contactsNewContact") + array.append("chatReadMarkInBubble1_incoming") + array.append("chatReadMarkInBubble2_incoming") + array.append("chatReadMarkInBubble1_outgoing") + array.append("chatReadMarkInBubble2_outgoing") + array.append("chatReadMarkOutBubble1") + array.append("chatReadMarkOutBubble2") + array.append("chatReadMarkOverlayBubble1") + array.append("chatReadMarkOverlayBubble2") + array.append("sentFailed") + array.append("chatChannelViewsInBubble_incoming") + array.append("chatChannelViewsInBubble_outgoing") + array.append("chatChannelViewsOutBubble") + array.append("chatChannelViewsOverlayBubble") + array.append("chatNavigationBack") + array.append("peerInfoAddMember") + array.append("chatSearchUp") + array.append("chatSearchUpDisabled") + array.append("chatSearchDown") + array.append("chatSearchDownDisabled") + array.append("chatSearchCalendar") + array.append("dismissAccessory") + array.append("chatScrollUp") + array.append("chatScrollUpActive") + array.append("audioPlayerPlay") + array.append("audioPlayerPause") + array.append("audioPlayerNext") + array.append("audioPlayerPrev") + array.append("auduiPlayerDismiss") + array.append("audioPlayerRepeat") + array.append("audioPlayerRepeatActive") + array.append("audioPlayerLockedPlay") + array.append("audioPlayerLockedNext") + array.append("audioPlayerLockedPrev") + array.append("chatSendMessage") + array.append("chatSaveEditedMessage") + array.append("chatRecordVoice") + array.append("chatEntertainment") + array.append("chatInlineDismiss") + array.append("chatActiveReplyMarkup") + array.append("chatDisabledReplyMarkup") + array.append("chatSecretTimer") + array.append("chatForwardMessagesActive") + array.append("chatForwardMessagesInactive") + array.append("chatDeleteMessagesActive") + array.append("chatDeleteMessagesInactive") + array.append("generalNext") + array.append("generalNextActive") + array.append("generalSelect") + array.append("chatVoiceRecording") + array.append("chatVideoRecording") + array.append("chatRecord") + array.append("deleteItem") + array.append("deleteItemDisabled") + array.append("chatAttach") + array.append("chatAttachFile") + array.append("chatAttachPhoto") + array.append("chatAttachCamera") + array.append("chatAttachLocation") + array.append("chatAttachPoll") + array.append("mediaEmptyShared") + array.append("mediaEmptyFiles") + array.append("mediaEmptyMusic") + array.append("mediaEmptyLinks") + array.append("stickersAddFeatured") + array.append("stickersAddedFeatured") + array.append("stickersRemove") + array.append("peerMediaDownloadFileStart") + array.append("peerMediaDownloadFilePause") + array.append("stickersShare") + array.append("emojiRecentTab") + array.append("emojiSmileTab") + array.append("emojiNatureTab") + array.append("emojiFoodTab") + array.append("emojiSportTab") + array.append("emojiCarTab") + array.append("emojiObjectsTab") + array.append("emojiSymbolsTab") + array.append("emojiFlagsTab") + array.append("emojiRecentTabActive") + array.append("emojiSmileTabActive") + array.append("emojiNatureTabActive") + array.append("emojiFoodTabActive") + array.append("emojiSportTabActive") + array.append("emojiCarTabActive") + array.append("emojiObjectsTabActive") + array.append("emojiSymbolsTabActive") + array.append("emojiFlagsTabActive") + array.append("stickerBackground") + array.append("stickerBackgroundActive") + array.append("stickersTabRecent") + array.append("stickersTabGIF") + array.append("chatSendingInFrame_incoming") + array.append("chatSendingInHour_incoming") + array.append("chatSendingInMin_incoming") + array.append("chatSendingInFrame_outgoing") + array.append("chatSendingInHour_outgoing") + array.append("chatSendingInMin_outgoing") + array.append("chatSendingOutFrame") + array.append("chatSendingOutHour") + array.append("chatSendingOutMin") + array.append("chatSendingOverlayFrame") + array.append("chatSendingOverlayHour") + array.append("chatSendingOverlayMin") + array.append("chatActionUrl") + array.append("callInlineDecline") + array.append("callInlineMuted") + array.append("callInlineUnmuted") + array.append("eventLogTriangle") + array.append("channelIntro") + array.append("chatFileThumb") + array.append("chatFileThumbBubble_incoming") + array.append("chatFileThumbBubble_outgoing") + array.append("chatSecretThumb") + array.append("chatSecretThumbSmall") + array.append("chatMapPin") + array.append("chatSecretTitle") + array.append("emptySearch") + array.append("calendarBack") + array.append("calendarNext") + array.append("calendarBackDisabled") + array.append("calendarNextDisabled") + array.append("newChatCamera") + array.append("peerInfoVerify") + array.append("peerInfoVerifyProfile") + array.append("peerInfoCall") + array.append("callOutgoing") + array.append("recentDismiss") + array.append("recentDismissActive") + array.append("webgameShare") + array.append("chatSearchCancel") + array.append("chatSearchFrom") + array.append("callWindowDecline") + array.append("callWindowAccept") + array.append("callWindowMute") + array.append("callWindowUnmute") + array.append("callWindowClose") + array.append("callWindowDeviceSettings") + array.append("callSettings") + array.append("callWindowCancel") + array.append("chatActionEdit") + array.append("chatActionInfo") + array.append("chatActionMute") + array.append("chatActionUnmute") + array.append("chatActionClearHistory") + array.append("chatActionDeleteChat") + array.append("dismissPinned") + array.append("chatActionsActive") + array.append("chatEntertainmentSticker") + array.append("chatEmpty") + array.append("stickerPackClose") + array.append("stickerPackDelete") + array.append("modalShare") + array.append("modalClose") + array.append("ivChannelJoined") + array.append("chatListMention") + array.append("chatListMentionActive") + array.append("chatListMentionArchived") + array.append("chatListMentionArchivedActive") + array.append("chatMention") + array.append("chatMentionActive") + array.append("sliderControl") + array.append("sliderControlActive") + array.append("stickersTabFave") + array.append("chatInstantView") + array.append("chatInstantViewBubble_incoming") + array.append("chatInstantViewBubble_outgoing") + array.append("instantViewShare") + array.append("instantViewActions") + array.append("instantViewActionsActive") + array.append("instantViewSafari") + array.append("instantViewBack") + array.append("instantViewCheck") + array.append("groupStickerNotFound") + array.append("settingsAskQuestion") + array.append("settingsFaq") + array.append("settingsGeneral") + array.append("settingsLanguage") + array.append("settingsNotifications") + array.append("settingsSecurity") + array.append("settingsStickers") + array.append("settingsStorage") + array.append("settingsSessions") + array.append("settingsProxy") + array.append("settingsAppearance") + array.append("settingsPassport") + array.append("settingsWallet") + array.append("settingsUpdate") + array.append("settingsFilters") + array.append("settingsAskQuestionActive") + array.append("settingsFaqActive") + array.append("settingsGeneralActive") + array.append("settingsLanguageActive") + array.append("settingsNotificationsActive") + array.append("settingsSecurityActive") + array.append("settingsStickersActive") + array.append("settingsStorageActive") + array.append("settingsSessionsActive") + array.append("settingsProxyActive") + array.append("settingsAppearanceActive") + array.append("settingsPassportActive") + array.append("settingsWalletActive") + array.append("settingsUpdateActive") + array.append("settingsFiltersActive") + array.append("settingsProfile") + array.append("generalCheck") + array.append("settingsAbout") + array.append("settingsLogout") + array.append("fastSettingsLock") + array.append("fastSettingsDark") + array.append("fastSettingsSunny") + array.append("fastSettingsMute") + array.append("fastSettingsUnmute") + array.append("chatRecordVideo") + array.append("inputChannelMute") + array.append("inputChannelUnmute") + array.append("changePhoneNumberIntro") + array.append("peerSavedMessages") + array.append("previewSenderCollage") + array.append("previewSenderPhoto") + array.append("previewSenderFile") + array.append("previewSenderCrop") + array.append("previewSenderDelete") + array.append("previewSenderDeleteFile") + array.append("previewSenderArchive") + array.append("chatGroupToggleSelected") + array.append("chatGroupToggleUnselected") + array.append("successModalProgress") + array.append("accentColorSelect") + array.append("transparentBackground") + array.append("lottieTransparentBackground") + array.append("passcodeTouchId") + array.append("passcodeLogin") + array.append("confirmDeleteMessagesAccessory") + array.append("alertCheckBoxSelected") + array.append("alertCheckBoxUnselected") + array.append("confirmPinAccessory") + array.append("confirmDeleteChatAccessory") + array.append("stickersEmptySearch") + array.append("twoStepVerificationCreateIntro") + array.append("secureIdAuth") + array.append("ivAudioPlay") + array.append("ivAudioPause") + array.append("proxyEnable") + array.append("proxyEnabled") + array.append("proxyState") + array.append("proxyDeleteListItem") + array.append("proxyInfoListItem") + array.append("proxyConnectedListItem") + array.append("proxyAddProxy") + array.append("proxyNextWaitingListItem") + array.append("passportForgotPassword") + array.append("confirmAppAccessoryIcon") + array.append("passportPassport") + array.append("passportIdCardReverse") + array.append("passportIdCard") + array.append("passportSelfie") + array.append("passportDriverLicense") + array.append("chatOverlayVoiceRecording") + array.append("chatOverlayVideoRecording") + array.append("chatOverlaySendRecording") + array.append("chatOverlayLockArrowRecording") + array.append("chatOverlayLockerBodyRecording") + array.append("chatOverlayLockerHeadRecording") + array.append("locationPin") + array.append("locationMapPin") + array.append("locationMapLocate") + array.append("locationMapLocated") + array.append("passportSettings") + array.append("passportInfo") + array.append("editMessageMedia") + array.append("playerMusicPlaceholder") + array.append("chatMusicPlaceholder") + array.append("chatMusicPlaceholderCap") + array.append("searchArticle") + array.append("searchSaved") + array.append("archivedChats") + array.append("hintPeerActive") + array.append("hintPeerActiveSelected") + array.append("chatSwiping_delete") + array.append("chatSwiping_mute") + array.append("chatSwiping_unmute") + array.append("chatSwiping_read") + array.append("chatSwiping_unread") + array.append("chatSwiping_pin") + array.append("chatSwiping_unpin") + array.append("chatSwiping_archive") + array.append("chatSwiping_unarchive") + array.append("galleryPrev") + array.append("galleryNext") + array.append("galleryMore") + array.append("galleryShare") + array.append("galleryFastSave") + array.append("playingVoice1x") + array.append("playingVoice2x") + array.append("galleryRotate") + array.append("galleryZoomIn") + array.append("galleryZoomOut") + array.append("editMessageCurrentPhoto") + array.append("videoPlayerPlay") + array.append("videoPlayerPause") + array.append("videoPlayerEnterFullScreen") + array.append("videoPlayerExitFullScreen") + array.append("videoPlayerPIPIn") + array.append("videoPlayerPIPOut") + array.append("videoPlayerRewind15Forward") + array.append("videoPlayerRewind15Backward") + array.append("videoPlayerVolume") + array.append("videoPlayerVolumeOff") + array.append("videoPlayerClose") + array.append("videoPlayerSliderInteractor") + array.append("streamingVideoDownload") + array.append("videoCompactFetching") + array.append("compactStreamingFetchingCancel") + array.append("customLocalizationDelete") + array.append("pollAddOption") + array.append("pollDeleteOption") + array.append("resort") + array.append("chatPollVoteUnselected") + array.append("chatPollVoteUnselectedBubble_incoming") + array.append("chatPollVoteUnselectedBubble_outgoing") + array.append("peerInfoAdmins") + array.append("peerInfoPermissions") + array.append("peerInfoBanned") + array.append("peerInfoMembers") + array.append("chatUndoAction") + array.append("appUpdate") + array.append("inlineVideoSoundOff") + array.append("inlineVideoSoundOn") + array.append("logoutOptionAddAccount") + array.append("logoutOptionSetPasscode") + array.append("logoutOptionClearCache") + array.append("logoutOptionChangePhoneNumber") + array.append("logoutOptionContactSupport") + array.append("disableEmojiPrediction") + array.append("scam") + array.append("scamActive") + array.append("chatScam") + array.append("chatUnarchive") + array.append("chatArchive") + array.append("privacySettings_blocked") + array.append("privacySettings_activeSessions") + array.append("privacySettings_passcode") + array.append("privacySettings_twoStep") + array.append("deletedAccount") + array.append("stickerPackSelection") + array.append("stickerPackSelectionActive") + array.append("entertainment_Emoji") + array.append("entertainment_Stickers") + array.append("entertainment_Gifs") + array.append("entertainment_Search") + array.append("entertainment_Settings") + array.append("entertainment_SearchCancel") + array.append("scheduledAvatar") + array.append("scheduledInputAction") + array.append("verifyDialog") + array.append("verifyDialogActive") + array.append("chatInputScheduled") + array.append("appearanceAddPlatformTheme") + + array.append("wallet_close") + array.append("wallet_qr") + array.append("wallet_receive") + array.append("wallet_send") + array.append("wallet_settings") + array.append("wallet_update") + + array.append("wallet_passcode_visible") + array.append("wallet_passcode_hidden") + + + array.append("wallpaper_color_close") + array.append("wallpaper_color_add") + array.append("wallpaper_color_swap") + array.append("wallpaper_color_rotate") + + array.append("login_cap") + array.append("login_qr_cap") + + array.append("login_qr_empty_cap") + + array.append("chat_failed_scroller") + array.append("chat_failed_scroller_active") + + array.append("poll_quiz_unselected") + + array.append("poll_selected") + array.append("poll_selected_correct") + array.append("poll_selected_incorrect") + + + array.append("poll_selected_incoming") + array.append("poll_selected_correct_incoming") + array.append("poll_selected_incorrect_incoming") + + array.append("poll_selected_outgoing") + array.append("poll_selected_correct_outgoing") + array.append("poll_selected_incorrect_outgoing") + + array.append("chat_filter_edit") + array.append("chat_filter_add") + array.append("chat_filter_bots") + array.append("chat_filter_channels") + array.append("chat_filter_custom") + array.append("chat_filter_groups") + array.append("chat_filter_muted") + array.append("chat_filter_private_chats") + array.append("chat_filter_read") + array.append("chat_filter_secret_chats") + array.append("chat_filter_unmuted") + array.append("chat_filter_unread") + array.append("chat_filter_large_groups") + array.append("chat_filter_non_contacts") + array.append("chat_filter_archive") + + array.append("chat_filter_bots_avatar") + array.append("chat_filter_channels_avatar") + array.append("chat_filter_custom_avatar") + array.append("chat_filter_groups_avatar") + array.append("chat_filter_muted_avatar") + array.append("chat_filter_private_chats_avatar") + array.append("chat_filter_read_avatar") + array.append("chat_filter_secret_chats_avatar") + array.append("chat_filter_unmuted_avatar") + array.append("chat_filter_unread_avatar") + array.append("chat_filter_large_groups_avatar") + array.append("chat_filter_non_contacts_avatar") + array.append("chat_filter_archive_avatar") + + + array.append("group_invite_via_link") + + + array.append("tab_contacts") + array.append("tab_contacts_active") + array.append("tab_calls") + array.append("tab_calls_active") + array.append("tab_chats") + array.append("tab_chats_active") + array.append("tab_chats_active_filters") + array.append("tab_settings") + array.append("tab_settings_active") + + + array.append("profile_add_member") + array.append("profile_call") + array.append("profile_leave") + array.append("profile_message") + array.append("profile_more") + array.append("profile_mute") + array.append("profile_unmute") + array.append("profile_search") + array.append("profile_secret_chat") + array.append("profile_edit_photo") + array.append("profile_block") + array.append("profile_report") + array.append("profile_share") + array.append("profile_stats") + array.append("profile_unblock") + + + array.append("chat_quiz_explanation") + array.append("chat_quiz_explanation_bubble_incoming") + array.append("chat_quiz_explanation_bubble_outgoing") + + + array.append("stickers_add_featured") + + array.append("channel_info_promo") + array.append("channel_info_promo_bubble_incoming") + array.append("channel_info_promo_bubble_outgoing") + + array.append("chat_share_message") + array.append("chat_goto_message") + array.append("chat_swipe_reply") + array.append("chat_like_message") + array.append("chat_like_message_unlike") + + array.append("chat_like_inside") + array.append("chat_like_inside_bubble_incoming") + array.append("chat_like_inside_bubble_outgoing") + array.append("chat_like_inside_bubble_overlay") + + array.append("chat_like_inside_empty") + array.append("chat_like_inside_empty_bubble_incoming") + array.append("chat_like_inside_empty_bubble_outgoing") + array.append("chat_like_inside_empty_bubble_overlay") + + + array.append("gif_trending") + + + array.append("chat_list_thumb_play") + + + return array +} + + + +func generateClass() -> String { + + let items = initialize() + + var lines:[String] = [] + lines.append("import SwiftSignalKit") + lines.append("") + + lines.append("final class TelegramIconsTheme {") + + lines.append(" private var cached:Atomic<[String: CGImage]> = Atomic(value: [:])") + lines.append(" private var cachedWithInset:Atomic<[String: (CGImage, NSEdgeInsets)]> = Atomic(value: [:])") + lines.append("") + for item in items { + if item.hasSuffix("_withInset") { + lines.append(" var \(item): (CGImage, NSEdgeInsets) {") + lines.append(" if let image = cachedWithInset.with({ $0[\"\(item)\"] }) {") + lines.append(" return image") + lines.append(" } else {") + lines.append(" let image = _\(item)()") + lines.append(" _ = cachedWithInset.modify { current in ") + lines.append(" var current = current") + lines.append(" current[\"\(item)\"] = image") + lines.append(" return current") + lines.append(" }") + + + lines.append(" return image") + lines.append(" }") + lines.append(" }") + } else { + lines.append(" var \(item): CGImage {") + lines.append(" if let image = cached.with({ $0[\"\(item)\"] }) {") + lines.append(" return image") + lines.append(" } else {") + lines.append(" let image = _\(item)()") + lines.append(" _ = cached.modify { current in ") + lines.append(" var current = current") + lines.append(" current[\"\(item)\"] = image") + lines.append(" return current") + lines.append(" }") + + + lines.append(" return image") + lines.append(" }") + lines.append(" }") + } + + } + lines.append("") + + for item in items { + if item.hasSuffix("_withInset") { + lines.append(" private let _\(item): ()->(CGImage, NSEdgeInsets)") + } else { + lines.append(" private let _\(item): ()->CGImage") + } + } + + lines.append("") + + lines.append(" init(") + for item in items { + if item != items.last { + if item.hasSuffix("_withInset") { + lines.append(" \(item): @escaping()->(CGImage, NSEdgeInsets),") + } else { + lines.append(" \(item): @escaping()->CGImage,") + } + } else { + if item.hasSuffix("_withInset") { + lines.append(" \(item): @escaping()->(CGImage, NSEdgeInsets)") + } else { + lines.append(" \(item): @escaping()->CGImage") + } + } + } + lines.append(" ) {") + + for item in items { + lines.append(" self._\(item) = \(item)") + } + + lines.append(" }") + + + lines.append("}") + + let result = lines.joined(separator: "\n") + + + + return result +} + +print(FileManager.default.currentDirectoryPath) +try! generateClass().write(toFile: FileManager.default.currentDirectoryPath + "/Telegram-Mac/TelegramIconsTheme.swift", atomically: true, encoding: .utf8) diff --git a/tools/strings.stencil b/tools/strings.stencil new file mode 100644 index 0000000000..9251e79ef0 --- /dev/null +++ b/tools/strings.stencil @@ -0,0 +1,61 @@ +// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + _ p{{forloop.counter}}: {{type}}{% if not forloop.last %}, {% endif %} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + p{{forloop.counter}}{% if not forloop.last %}, {% endif %} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item sp %} +{{sp}} {% for string in item.strings %} +{{sp}} {% if not param.noComments %} +{{sp}} /// {{string.translation}} +{{sp}} {% endif %} +{{sp}} {% if string.types %} +{{sp}} {{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { +{{sp}} return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) +{{sp}} } +{{sp}} {% else %} +{{sp}} {{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } +{{sp}} {% endif %} +{{sp}} {% endfor %} +{{sp}} {% for child in item.children %} +{{sp}} {% call recursiveBlock table child sp %} +{{sp}} {% endfor %} +{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} final class {{enumName}} { + {% if tables.count > 1 %} + {% for table in tables %} + {{accessModifier}} final class {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% call recursiveBlock table.name table.levels " " %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels "" %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + return translate(key: key, args) + } +} + +private final class BundleToken {} +{% else %} +// No string found +{% endif %} \ No newline at end of file diff --git a/tools/swiftgen.sh b/tools/swiftgen.sh new file mode 100644 index 0000000000..76e9aede93 --- /dev/null +++ b/tools/swiftgen.sh @@ -0,0 +1 @@ +swiftgen strings -p ./tools/strings.stencil --output ./Telegram-Mac/Localizable.swift ./Telegram-Mac/en.lproj/Localizable.strings